From 355d214ececcb69a48db364ea80c58b1902ddebd Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Wed, 1 Jun 2016 15:43:25 -0400 Subject: ./move.sh --- src/Makefile | 28 - src/ac-power/Makefile | 33 +- src/activate/Makefile | 1 - src/activate/activate.c | 511 -- src/analyze/.gitignore | 1 - src/analyze/Makefile | 1 - src/analyze/analyze-verify.c | 306 - src/analyze/analyze-verify.h | 26 - src/analyze/analyze.c | 1492 ---- src/ask-password/Makefile | 1 - src/ask-password/ask-password.c | 179 - src/backlight/Makefile | 1 - src/backlight/backlight.c | 434 -- src/basic/.gitignore | 16 - src/basic/Makefile | 1 - src/basic/MurmurHash2.c | 86 - src/basic/MurmurHash2.h | 33 - src/basic/af-list.c | 56 - src/basic/af-list.h | 25 - src/basic/alloc-util.c | 83 - src/basic/alloc-util.h | 107 - src/basic/arphrd-list.c | 56 - src/basic/arphrd-list.h | 25 - src/basic/async.c | 94 - src/basic/async.h | 25 - src/basic/audit-util.c | 104 - src/basic/audit-util.h | 31 - src/basic/barrier.c | 415 -- src/basic/barrier.h | 91 - src/basic/bitmap.c | 220 - src/basic/bitmap.h | 50 - src/basic/blkid-util.h | 31 - src/basic/btrfs-ctree.h | 96 - src/basic/btrfs-util.c | 2075 ------ src/basic/btrfs-util.h | 131 - src/basic/build.h | 155 - src/basic/bus-label.c | 98 - src/basic/bus-label.h | 31 - src/basic/c-rbtree.c | 679 -- src/basic/c-rbtree.h | 297 - src/basic/calendarspec.c | 1087 --- src/basic/calendarspec.h | 58 - src/basic/cap-list.c | 66 - src/basic/cap-list.h | 24 - src/basic/capability-util.c | 361 - src/basic/capability-util.h | 57 - src/basic/cgroup-util.c | 2274 ------ src/basic/cgroup-util.h | 192 - src/basic/chattr-util.c | 107 - src/basic/chattr-util.h | 26 - src/basic/clock-util.c | 160 - src/basic/clock-util.h | 29 - src/basic/conf-files.c | 166 - src/basic/conf-files.h | 25 - src/basic/copy.c | 548 -- src/basic/copy.h | 35 - src/basic/cpu-set-util.c | 114 - src/basic/cpu-set-util.h | 32 - src/basic/def.h | 92 - src/basic/device-nodes.c | 80 - src/basic/device-nodes.h | 26 - src/basic/dirent-util.c | 78 - src/basic/dirent-util.h | 52 - src/basic/env-util.c | 624 -- src/basic/env-util.h | 51 - src/basic/errno-list.c | 57 - src/basic/errno-list.h | 25 - src/basic/escape.c | 474 -- src/basic/escape.h | 53 - src/basic/ether-addr-util.c | 44 - src/basic/ether-addr-util.h | 29 - src/basic/exit-status.c | 243 - src/basic/exit-status.h | 103 - src/basic/extract-word.c | 298 - src/basic/extract-word.h | 35 - src/basic/fd-util.c | 358 - src/basic/fd-util.h | 77 - src/basic/fdset.c | 286 - src/basic/fdset.h | 59 - src/basic/fileio-label.c | 68 - src/basic/fileio-label.h | 30 - src/basic/fileio.c | 1280 ---- src/basic/fileio.h | 84 - src/basic/formats-util.h | 63 - src/basic/fs-util.c | 513 -- src/basic/fs-util.h | 75 - src/basic/glob-util.c | 70 - src/basic/glob-util.h | 36 - src/basic/gunicode.c | 112 - src/basic/gunicode.h | 30 - src/basic/hash-funcs.c | 81 - src/basic/hash-funcs.h | 65 - src/basic/hashmap.c | 1805 ----- src/basic/hashmap.h | 372 - src/basic/hexdecoct.c | 753 -- src/basic/hexdecoct.h | 56 - src/basic/hostname-util.c | 224 - src/basic/hostname-util.h | 40 - src/basic/in-addr-util.c | 356 - src/basic/in-addr-util.h | 59 - src/basic/io-util.c | 264 - src/basic/io-util.h | 95 - src/basic/ioprio.h | 55 - src/basic/json.c | 871 --- src/basic/json.h | 90 - src/basic/label.c | 82 - src/basic/label.h | 28 - src/basic/linux/Makefile | 1 - src/basic/list.h | 182 - src/basic/locale-util.c | 319 - src/basic/locale-util.h | 73 - src/basic/lockfile-util.c | 153 - src/basic/lockfile-util.h | 39 - src/basic/log.c | 1170 --- src/basic/log.h | 250 - src/basic/login-util.c | 31 - src/basic/login-util.h | 29 - src/basic/macro.h | 395 - src/basic/memfd-util.c | 174 - src/basic/memfd-util.h | 36 - src/basic/mempool.c | 104 - src/basic/mempool.h | 47 - src/basic/missing.h | 1170 --- src/basic/mkdir-label.c | 38 - src/basic/mkdir.c | 128 - src/basic/mkdir.h | 38 - src/basic/mount-util.c | 531 -- src/basic/mount-util.h | 52 - src/basic/nss-util.h | 156 - src/basic/ordered-set.c | 64 - src/basic/ordered-set.h | 74 - src/basic/parse-util.c | 534 -- src/basic/parse-util.h | 95 - src/basic/path-util.c | 797 -- src/basic/path-util.h | 111 - src/basic/prioq.c | 320 - src/basic/prioq.h | 43 - src/basic/proc-cmdline.c | 176 - src/basic/proc-cmdline.h | 27 - src/basic/process-util.c | 793 -- src/basic/process-util.h | 103 - src/basic/random-util.c | 133 - src/basic/random-util.h | 39 - src/basic/ratelimit.c | 56 - src/basic/ratelimit.h | 58 - src/basic/refcnt.h | 34 - src/basic/replace-var.c | 112 - src/basic/replace-var.h | 22 - src/basic/rlimit-util.c | 271 - src/basic/rlimit-util.h | 36 - src/basic/rm-rf.c | 236 - src/basic/rm-rf.h | 32 - src/basic/securebits.h | 45 - src/basic/selinux-util.c | 493 -- src/basic/selinux-util.h | 51 - src/basic/set.h | 133 - src/basic/sigbus.c | 152 - src/basic/sigbus.h | 25 - src/basic/signal-util.c | 278 - src/basic/signal-util.h | 52 - src/basic/siphash24.c | 191 - src/basic/siphash24.h | 23 - src/basic/smack-util.c | 241 - src/basic/smack-util.h | 54 - src/basic/socket-label.c | 174 - src/basic/socket-util.c | 972 --- src/basic/socket-util.h | 139 - src/basic/sparse-endian.h | 88 - src/basic/special.h | 118 - src/basic/stat-util.c | 218 - src/basic/stat-util.h | 69 - src/basic/stdio-util.h | 76 - src/basic/strbuf.c | 200 - src/basic/strbuf.h | 54 - src/basic/string-table.c | 34 - src/basic/string-table.h | 105 - src/basic/string-util.c | 855 --- src/basic/string-util.h | 189 - src/basic/strv.c | 891 --- src/basic/strv.h | 171 - src/basic/strxcpyx.c | 100 - src/basic/strxcpyx.h | 31 - src/basic/syslog-util.c | 114 - src/basic/syslog-util.h | 32 - src/basic/terminal-util.c | 1148 --- src/basic/terminal-util.h | 126 - src/basic/time-util.c | 1116 --- src/basic/time-util.h | 151 - src/basic/umask-util.h | 46 - src/basic/unaligned.h | 111 - src/basic/unit-name.c | 1049 --- src/basic/unit-name.h | 368 - src/basic/user-util.c | 480 -- src/basic/user-util.h | 65 - src/basic/utf8.c | 409 -- src/basic/utf8.h | 59 - src/basic/util.c | 793 -- src/basic/util.h | 178 - src/basic/verbs.c | 101 - src/basic/verbs.h | 33 - src/basic/virt.c | 462 -- src/basic/virt.h | 72 - src/basic/web-util.c | 76 - src/basic/web-util.h | 30 - src/basic/xattr-util.c | 200 - src/basic/xattr-util.h | 37 - src/basic/xml.c | 255 - src/basic/xml.h | 32 - src/binfmt/Makefile | 1 - src/binfmt/binfmt.c | 203 - src/boot/Makefile | 55 +- src/boot/efi/Makefile | 128 + src/bootchart/Makefile | 1 - src/bootchart/bootchart.c | 531 -- src/bootchart/bootchart.conf | 26 - src/bootchart/bootchart.h | 119 - src/bootchart/store.c | 555 -- src/bootchart/store.h | 36 - src/bootchart/svg.c | 1375 ---- src/bootchart/svg.h | 35 - src/bus-proxyd/Makefile | 80 +- src/bus-proxyd/bus-proxyd.c | 2 +- src/bus-proxyd/bus-xml-policy.c | 2 +- src/bus-proxyd/driver.c | 2 +- src/bus-proxyd/driver.h | 2 +- src/bus-proxyd/proxy.c | 4 +- src/bus-proxyd/proxy.h | 2 +- src/bus-proxyd/stdio-bridge.c | 4 +- src/bus-proxyd/synthesize.c | 2 +- src/bus-proxyd/synthesize.h | 2 +- src/bus-proxyd/test-bus-xml-policy.c | 2 +- src/cgls/Makefile | 1 - src/cgls/cgls.c | 287 - src/cgroups-agent/Makefile | 1 - src/cgroups-agent/cgroups-agent.c | 65 - src/cgtop/Makefile | 1 - src/cgtop/cgtop.c | 1097 --- src/compat-libs/.gitignore | 1 - src/compat-libs/libsystemd-daemon.pc.in | 19 - src/compat-libs/libsystemd-daemon.sym | 27 - src/compat-libs/libsystemd-id128.pc.in | 18 - src/compat-libs/libsystemd-id128.sym | 21 - src/compat-libs/libsystemd-journal.pc.in | 19 - src/compat-libs/libsystemd-journal.sym | 111 - src/compat-libs/libsystemd-login.pc.in | 18 - src/compat-libs/libsystemd-login.sym | 87 - src/compat-libs/linkwarning.h | 35 - src/core/.gitignore | 3 - src/core/Makefile | 1 - src/core/audit-fd.c | 73 - src/core/audit-fd.h | 23 - src/core/automount.c | 1092 --- src/core/automount.h | 59 - src/core/bus-endpoint.c | 135 - src/core/bus-endpoint.h | 42 - src/core/bus-policy.c | 180 - src/core/bus-policy.h | 64 - src/core/busname.c | 1067 --- src/core/busname.h | 67 - src/core/cgroup.c | 1620 ---- src/core/cgroup.h | 155 - src/core/dbus-automount.c | 34 - src/core/dbus-automount.h | 23 - src/core/dbus-busname.c | 37 - src/core/dbus-busname.h | 23 - src/core/dbus-cgroup.c | 699 -- src/core/dbus-cgroup.h | 28 - src/core/dbus-device.c | 28 - src/core/dbus-device.h | 24 - src/core/dbus-execute.c | 1564 ---- src/core/dbus-execute.h | 45 - src/core/dbus-job.c | 193 - src/core/dbus-job.h | 31 - src/core/dbus-kill.c | 122 - src/core/dbus-kill.h | 29 - src/core/dbus-manager.c | 2137 ------ src/core/dbus-manager.h | 28 - src/core/dbus-mount.c | 211 - src/core/dbus-mount.h | 29 - src/core/dbus-path.c | 86 - src/core/dbus-path.h | 24 - src/core/dbus-scope.c | 229 - src/core/dbus-scope.h | 31 - src/core/dbus-service.c | 322 - src/core/dbus-service.h | 29 - src/core/dbus-slice.c | 52 - src/core/dbus-slice.h | 29 - src/core/dbus-socket.c | 182 - src/core/dbus-socket.h | 29 - src/core/dbus-swap.c | 115 - src/core/dbus-swap.h | 30 - src/core/dbus-target.c | 26 - src/core/dbus-target.h | 24 - src/core/dbus-timer.c | 352 - src/core/dbus-timer.h | 28 - src/core/dbus-unit.c | 1276 ---- src/core/dbus-unit.h | 40 - src/core/dbus.c | 1258 ---- src/core/dbus.h | 42 - src/core/device.c | 878 --- src/core/device.h | 47 - src/core/execute.c | 3165 -------- src/core/execute.h | 297 - src/core/failure-action.c | 125 - src/core/failure-action.h | 41 - src/core/hostname-setup.c | 68 - src/core/hostname-setup.h | 22 - src/core/ima-setup.c | 80 - src/core/ima-setup.h | 24 - src/core/job.c | 1249 ---- src/core/job.h | 242 - src/core/kill.c | 68 - src/core/kill.h | 65 - src/core/killall.c | 249 - src/core/killall.h | 22 - src/core/kmod-setup.c | 131 - src/core/kmod-setup.h | 22 - src/core/load-dropin.c | 81 - src/core/load-dropin.h | 34 - src/core/load-fragment-gperf.gperf.m4 | 378 - src/core/load-fragment.c | 3939 ---------- src/core/load-fragment.h | 122 - src/core/locale-setup.c | 124 - src/core/locale-setup.h | 22 - src/core/loopback-setup.c | 90 - src/core/loopback-setup.h | 22 - src/core/machine-id-setup.c | 375 - src/core/machine-id-setup.h | 23 - src/core/macros.systemd.in | 105 - src/core/main.c | 2142 ------ src/core/manager.c | 3124 -------- src/core/manager.h | 376 - src/core/mount-setup.c | 412 -- src/core/mount-setup.h | 30 - src/core/mount.c | 1873 ----- src/core/mount.h | 107 - src/core/namespace.c | 727 -- src/core/namespace.h | 64 - src/core/org.freedesktop.systemd1.conf | 212 - src/core/org.freedesktop.systemd1.policy.in.in | 70 - src/core/org.freedesktop.systemd1.service | 11 - src/core/path.c | 778 -- src/core/path.h | 92 - src/core/scope.c | 610 -- src/core/scope.h | 55 - src/core/selinux-access.c | 280 - src/core/selinux-access.h | 45 - src/core/selinux-setup.c | 121 - src/core/selinux-setup.h | 24 - src/core/service.c | 3366 --------- src/core/service.h | 220 - src/core/show-status.c | 124 - src/core/show-status.h | 39 - src/core/shutdown.c | 435 -- src/core/slice.c | 339 - src/core/slice.h | 32 - src/core/smack-setup.c | 346 - src/core/smack-setup.h | 24 - src/core/socket.c | 2866 -------- src/core/socket.h | 181 - src/core/swap.c | 1518 ---- src/core/swap.h | 109 - src/core/system.conf | 60 - src/core/systemd.pc.in | 34 - src/core/target.c | 223 - src/core/target.h | 30 - src/core/timer.c | 851 --- src/core/timer.h | 88 - src/core/transaction.c | 1092 --- src/core/transaction.h | 51 - src/core/triggers.systemd.in | 64 - src/core/umount.c | 613 -- src/core/umount.h | 28 - src/core/unit-printf.c | 309 - src/core/unit-printf.h | 26 - src/core/unit.c | 3756 ---------- src/core/unit.h | 630 -- src/core/user.conf | 44 - src/coredump/Makefile | 97 +- src/coredump/coredump.c | 6 +- src/coredump/coredumpctl.c | 2 +- src/cryptsetup/Makefile | 1 - src/cryptsetup/cryptsetup-generator.c | 509 -- src/cryptsetup/cryptsetup.c | 753 -- src/dbus1-generator/Makefile | 50 +- src/debug-generator/Makefile | 33 +- src/delta/Makefile | 1 - src/delta/delta.c | 643 -- src/detect-virt/Makefile | 36 +- src/escape/Makefile | 1 - src/escape/escape.c | 237 - src/firstboot/Makefile | 48 +- src/fsck/Makefile | 33 +- src/fsck/fsck.c | 4 +- src/fstab-generator/Makefile | 34 +- src/getty-generator/Makefile | 33 +- src/gpt-auto-generator/Makefile | 44 +- src/gpt-auto-generator/gpt-auto-generator.c | 2 +- src/hibernate-resume/Makefile | 61 +- src/hostname/Makefile | 80 +- src/hostname/hostnamectl.c | 4 +- src/hwdb/Makefile | 1 - src/import/Makefile | 192 +- src/import/curl-util.h | 2 +- src/import/export-raw.c | 2 +- src/import/export-raw.h | 2 +- src/import/export-tar.c | 2 +- src/import/export-tar.h | 2 +- src/import/export.c | 2 +- src/import/import-raw.c | 4 +- src/import/import-raw.h | 2 +- src/import/import-tar.c | 4 +- src/import/import-tar.h | 2 +- src/import/import.c | 2 +- src/import/importd.c | 2 +- src/import/pull-raw.c | 2 +- src/import/pull-raw.h | 2 +- src/import/pull-tar.c | 2 +- src/import/pull-tar.h | 2 +- src/import/pull.c | 2 +- src/initctl/Makefile | 33 +- src/initctl/initctl.c | 4 +- src/journal-remote/Makefile | 114 +- src/journal-remote/journal-gatewayd.c | 6 +- src/journal-remote/journal-remote-parse.h | 2 +- src/journal-remote/journal-remote.c | 2 +- src/journal-remote/journal-remote.h | 2 +- src/journal-remote/journal-upload.c | 2 +- src/journal-remote/journal-upload.h | 4 +- src/journal/Makefile | 362 +- src/journal/cat.c | 2 +- src/journal/catalog.c | 2 +- src/journal/catalog.h | 2 +- src/journal/catalog/systemd.be.catalog | 260 + src/journal/catalog/systemd.be@latin.catalog | 260 + src/journal/catalog/systemd.catalog | 334 + src/journal/catalog/systemd.da.catalog | 261 + src/journal/catalog/systemd.fr.catalog | 260 + src/journal/catalog/systemd.hu.catalog | 262 + src/journal/catalog/systemd.it.catalog | 254 + src/journal/catalog/systemd.ko.catalog | 264 + src/journal/catalog/systemd.pl.catalog | 261 + src/journal/catalog/systemd.pt_BR.catalog | 264 + src/journal/catalog/systemd.ru.catalog | 354 + src/journal/catalog/systemd.sr.catalog | 262 + src/journal/catalog/systemd.zh_CN.catalog | 253 + src/journal/catalog/systemd.zh_TW.catalog | 263 + src/journal/journal-def.h | 2 +- src/journal/journal-file.c | 2 +- src/journal/journal-file.h | 4 +- src/journal/journal-internal.h | 4 +- src/journal/journal-qrcode.h | 2 +- src/journal/journal-send.c | 2 +- src/journal/journal-vacuum.c | 2 +- src/journal/journalctl.c | 4 +- src/journal/journald-kmsg.c | 2 +- src/journal/journald-server.c | 6 +- src/journal/journald-server.h | 2 +- src/journal/journald-stream.c | 4 +- src/journal/journald-syslog.c | 2 +- src/journal/journald.c | 4 +- src/journal/sd-journal.c | 2 +- src/journal/test-catalog.c | 2 +- src/journal/test-journal-enum.c | 2 +- src/journal/test-journal-flush.c | 2 +- src/journal/test-journal-init.c | 2 +- src/journal/test-journal-interleaving.c | 2 +- src/journal/test-journal-match.c | 2 +- src/journal/test-journal-send.c | 2 +- src/journal/test-journal-stream.c | 2 +- src/kernel-install/Makefile | 34 +- src/kernel-install/bash-completion_kernel-install | 50 + src/kernel-install/kernel-install.xml | 192 + src/kernel-install/zsh-completion_kernel-install | 26 + src/libbasic/.gitignore | 16 + src/libbasic/Makefile | 280 + src/libbasic/MurmurHash2.c | 86 + src/libbasic/MurmurHash2.h | 33 + src/libbasic/af-list.c | 56 + src/libbasic/af-list.h | 25 + src/libbasic/alloc-util.c | 83 + src/libbasic/alloc-util.h | 107 + src/libbasic/arphrd-list.c | 56 + src/libbasic/arphrd-list.h | 25 + src/libbasic/async.c | 94 + src/libbasic/async.h | 25 + src/libbasic/audit-util.c | 104 + src/libbasic/audit-util.h | 31 + src/libbasic/barrier.c | 415 ++ src/libbasic/barrier.h | 91 + src/libbasic/bitmap.c | 220 + src/libbasic/bitmap.h | 50 + src/libbasic/blkid-util.h | 31 + src/libbasic/btrfs-ctree.h | 96 + src/libbasic/btrfs-util.c | 2075 ++++++ src/libbasic/btrfs-util.h | 131 + src/libbasic/build.h | 155 + src/libbasic/bus-label.c | 98 + src/libbasic/bus-label.h | 31 + src/libbasic/c-rbtree.c | 679 ++ src/libbasic/c-rbtree.h | 297 + src/libbasic/calendarspec.c | 1087 +++ src/libbasic/calendarspec.h | 58 + src/libbasic/cap-list.c | 66 + src/libbasic/cap-list.h | 24 + src/libbasic/capability-util.c | 361 + src/libbasic/capability-util.h | 57 + src/libbasic/cgroup-util.c | 2274 ++++++ src/libbasic/cgroup-util.h | 192 + src/libbasic/chattr-util.c | 107 + src/libbasic/chattr-util.h | 26 + src/libbasic/clock-util.c | 160 + src/libbasic/clock-util.h | 29 + src/libbasic/conf-files.c | 166 + src/libbasic/conf-files.h | 25 + src/libbasic/copy.c | 548 ++ src/libbasic/copy.h | 35 + src/libbasic/cpu-set-util.c | 114 + src/libbasic/cpu-set-util.h | 32 + src/libbasic/def.h | 92 + src/libbasic/device-nodes.c | 80 + src/libbasic/device-nodes.h | 26 + src/libbasic/dirent-util.c | 78 + src/libbasic/dirent-util.h | 52 + src/libbasic/env-util.c | 624 ++ src/libbasic/env-util.h | 51 + src/libbasic/errno-list.c | 57 + src/libbasic/errno-list.h | 25 + src/libbasic/escape.c | 474 ++ src/libbasic/escape.h | 53 + src/libbasic/ether-addr-util.c | 44 + src/libbasic/ether-addr-util.h | 29 + src/libbasic/exit-status.c | 243 + src/libbasic/exit-status.h | 103 + src/libbasic/extract-word.c | 298 + src/libbasic/extract-word.h | 35 + src/libbasic/fd-util.c | 358 + src/libbasic/fd-util.h | 77 + src/libbasic/fdset.c | 286 + src/libbasic/fdset.h | 59 + src/libbasic/fileio-label.c | 68 + src/libbasic/fileio-label.h | 30 + src/libbasic/fileio.c | 1280 ++++ src/libbasic/fileio.h | 84 + src/libbasic/formats-util.h | 63 + src/libbasic/fs-util.c | 513 ++ src/libbasic/fs-util.h | 75 + src/libbasic/glob-util.c | 70 + src/libbasic/glob-util.h | 36 + src/libbasic/gunicode.c | 112 + src/libbasic/gunicode.h | 30 + src/libbasic/hash-funcs.c | 81 + src/libbasic/hash-funcs.h | 65 + src/libbasic/hashmap.c | 1805 +++++ src/libbasic/hashmap.h | 372 + src/libbasic/hexdecoct.c | 753 ++ src/libbasic/hexdecoct.h | 56 + src/libbasic/hostname-util.c | 224 + src/libbasic/hostname-util.h | 40 + src/libbasic/in-addr-util.c | 356 + src/libbasic/in-addr-util.h | 59 + src/libbasic/io-util.c | 264 + src/libbasic/io-util.h | 95 + src/libbasic/ioprio.h | 55 + src/libbasic/json.c | 871 +++ src/libbasic/json.h | 90 + src/libbasic/label.c | 82 + src/libbasic/label.h | 28 + src/libbasic/list.h | 182 + src/libbasic/locale-util.c | 319 + src/libbasic/locale-util.h | 73 + src/libbasic/lockfile-util.c | 153 + src/libbasic/lockfile-util.h | 39 + src/libbasic/log.c | 1170 +++ src/libbasic/log.h | 250 + src/libbasic/login-util.c | 31 + src/libbasic/login-util.h | 29 + src/libbasic/macro.h | 395 + src/libbasic/memfd-util.c | 174 + src/libbasic/memfd-util.h | 36 + src/libbasic/mempool.c | 104 + src/libbasic/mempool.h | 47 + src/libbasic/missing.h | 1170 +++ src/libbasic/mkdir-label.c | 38 + src/libbasic/mkdir.c | 128 + src/libbasic/mkdir.h | 38 + src/libbasic/mount-util.c | 531 ++ src/libbasic/mount-util.h | 52 + src/libbasic/nss-util.h | 156 + src/libbasic/ordered-set.c | 64 + src/libbasic/ordered-set.h | 74 + src/libbasic/parse-util.c | 534 ++ src/libbasic/parse-util.h | 95 + src/libbasic/path-util.c | 797 ++ src/libbasic/path-util.h | 111 + src/libbasic/prioq.c | 320 + src/libbasic/prioq.h | 43 + src/libbasic/proc-cmdline.c | 176 + src/libbasic/proc-cmdline.h | 27 + src/libbasic/process-util.c | 793 ++ src/libbasic/process-util.h | 103 + src/libbasic/random-util.c | 133 + src/libbasic/random-util.h | 39 + src/libbasic/ratelimit.c | 56 + src/libbasic/ratelimit.h | 58 + src/libbasic/refcnt.h | 34 + src/libbasic/replace-var.c | 112 + src/libbasic/replace-var.h | 22 + src/libbasic/rlimit-util.c | 271 + src/libbasic/rlimit-util.h | 36 + src/libbasic/rm-rf.c | 236 + src/libbasic/rm-rf.h | 32 + src/libbasic/securebits.h | 45 + src/libbasic/selinux-util.c | 493 ++ src/libbasic/selinux-util.h | 51 + src/libbasic/set.h | 133 + src/libbasic/sigbus.c | 152 + src/libbasic/sigbus.h | 25 + src/libbasic/signal-util.c | 278 + src/libbasic/signal-util.h | 52 + src/libbasic/siphash24.c | 191 + src/libbasic/siphash24.h | 23 + src/libbasic/smack-util.c | 241 + src/libbasic/smack-util.h | 54 + src/libbasic/socket-label.c | 174 + src/libbasic/socket-util.c | 972 +++ src/libbasic/socket-util.h | 139 + src/libbasic/sparse-endian.h | 88 + src/libbasic/special.h | 118 + src/libbasic/stat-util.c | 218 + src/libbasic/stat-util.h | 69 + src/libbasic/stdio-util.h | 76 + src/libbasic/strbuf.c | 200 + src/libbasic/strbuf.h | 54 + src/libbasic/string-table.c | 34 + src/libbasic/string-table.h | 105 + src/libbasic/string-util.c | 855 +++ src/libbasic/string-util.h | 189 + src/libbasic/strv.c | 891 +++ src/libbasic/strv.h | 171 + src/libbasic/strxcpyx.c | 100 + src/libbasic/strxcpyx.h | 31 + src/libbasic/syslog-util.c | 114 + src/libbasic/syslog-util.h | 32 + src/libbasic/terminal-util.c | 1148 +++ src/libbasic/terminal-util.h | 126 + src/libbasic/time-util.c | 1116 +++ src/libbasic/time-util.h | 151 + src/libbasic/umask-util.h | 46 + src/libbasic/unaligned.h | 111 + src/libbasic/unit-name.c | 1049 +++ src/libbasic/unit-name.h | 368 + src/libbasic/user-util.c | 480 ++ src/libbasic/user-util.h | 65 + src/libbasic/utf8.c | 409 ++ src/libbasic/utf8.h | 59 + src/libbasic/util.c | 793 ++ src/libbasic/util.h | 178 + src/libbasic/verbs.c | 101 + src/libbasic/verbs.h | 33 + src/libbasic/virt.c | 462 ++ src/libbasic/virt.h | 72 + src/libbasic/web-util.c | 76 + src/libbasic/web-util.h | 30 + src/libbasic/xattr-util.c | 200 + src/libbasic/xattr-util.h | 37 + src/libbasic/xml.c | 255 + src/libbasic/xml.h | 32 + src/libcore/.gitignore | 3 + src/libcore/Makefile | 172 + src/libcore/audit-fd.c | 73 + src/libcore/audit-fd.h | 23 + src/libcore/automount.c | 1092 +++ src/libcore/automount.h | 59 + src/libcore/bus-endpoint.c | 135 + src/libcore/bus-endpoint.h | 42 + src/libcore/bus-policy.c | 180 + src/libcore/bus-policy.h | 64 + src/libcore/busname.c | 1067 +++ src/libcore/busname.h | 67 + src/libcore/cgroup.c | 1620 ++++ src/libcore/cgroup.h | 155 + src/libcore/dbus-automount.c | 34 + src/libcore/dbus-automount.h | 23 + src/libcore/dbus-busname.c | 37 + src/libcore/dbus-busname.h | 23 + src/libcore/dbus-cgroup.c | 699 ++ src/libcore/dbus-cgroup.h | 28 + src/libcore/dbus-device.c | 28 + src/libcore/dbus-device.h | 24 + src/libcore/dbus-execute.c | 1564 ++++ src/libcore/dbus-execute.h | 45 + src/libcore/dbus-job.c | 193 + src/libcore/dbus-job.h | 31 + src/libcore/dbus-kill.c | 122 + src/libcore/dbus-kill.h | 29 + src/libcore/dbus-manager.c | 2137 ++++++ src/libcore/dbus-manager.h | 28 + src/libcore/dbus-mount.c | 211 + src/libcore/dbus-mount.h | 29 + src/libcore/dbus-path.c | 86 + src/libcore/dbus-path.h | 24 + src/libcore/dbus-scope.c | 229 + src/libcore/dbus-scope.h | 31 + src/libcore/dbus-service.c | 322 + src/libcore/dbus-service.h | 29 + src/libcore/dbus-slice.c | 52 + src/libcore/dbus-slice.h | 29 + src/libcore/dbus-socket.c | 182 + src/libcore/dbus-socket.h | 29 + src/libcore/dbus-swap.c | 115 + src/libcore/dbus-swap.h | 30 + src/libcore/dbus-target.c | 26 + src/libcore/dbus-target.h | 24 + src/libcore/dbus-timer.c | 352 + src/libcore/dbus-timer.h | 28 + src/libcore/dbus-unit.c | 1276 ++++ src/libcore/dbus-unit.h | 40 + src/libcore/dbus.c | 1258 ++++ src/libcore/dbus.h | 42 + src/libcore/device.c | 878 +++ src/libcore/device.h | 47 + src/libcore/execute.c | 3165 ++++++++ src/libcore/execute.h | 297 + src/libcore/failure-action.c | 125 + src/libcore/failure-action.h | 41 + src/libcore/hostname-setup.c | 68 + src/libcore/hostname-setup.h | 22 + src/libcore/ima-setup.c | 80 + src/libcore/ima-setup.h | 24 + src/libcore/job.c | 1249 ++++ src/libcore/job.h | 242 + src/libcore/kill.c | 68 + src/libcore/kill.h | 65 + src/libcore/killall.c | 249 + src/libcore/killall.h | 22 + src/libcore/kmod-setup.c | 131 + src/libcore/kmod-setup.h | 22 + src/libcore/linux/auto_dev-ioctl.h | 228 + src/libcore/load-dropin.c | 81 + src/libcore/load-dropin.h | 34 + src/libcore/load-fragment-gperf.gperf.m4 | 378 + src/libcore/load-fragment.c | 3939 ++++++++++ src/libcore/load-fragment.h | 122 + src/libcore/locale-setup.c | 124 + src/libcore/locale-setup.h | 22 + src/libcore/loopback-setup.c | 90 + src/libcore/loopback-setup.h | 22 + src/libcore/machine-id-setup.c | 375 + src/libcore/machine-id-setup.h | 23 + src/libcore/manager.c | 3124 ++++++++ src/libcore/manager.h | 376 + src/libcore/mount-setup.c | 412 ++ src/libcore/mount-setup.h | 30 + src/libcore/mount.c | 1873 +++++ src/libcore/mount.h | 107 + src/libcore/namespace.c | 727 ++ src/libcore/namespace.h | 64 + src/libcore/path.c | 778 ++ src/libcore/path.h | 92 + src/libcore/scope.c | 610 ++ src/libcore/scope.h | 55 + src/libcore/selinux-access.c | 280 + src/libcore/selinux-access.h | 45 + src/libcore/selinux-setup.c | 121 + src/libcore/selinux-setup.h | 24 + src/libcore/service.c | 3366 +++++++++ src/libcore/service.h | 220 + src/libcore/show-status.c | 124 + src/libcore/show-status.h | 39 + src/libcore/shutdown.c | 435 ++ src/libcore/slice.c | 339 + src/libcore/slice.h | 32 + src/libcore/smack-setup.c | 346 + src/libcore/smack-setup.h | 24 + src/libcore/socket.c | 2866 ++++++++ src/libcore/socket.h | 181 + src/libcore/swap.c | 1518 ++++ src/libcore/swap.h | 109 + src/libcore/target.c | 223 + src/libcore/target.h | 30 + src/libcore/timer.c | 851 +++ src/libcore/timer.h | 88 + src/libcore/transaction.c | 1092 +++ src/libcore/transaction.h | 51 + src/libcore/umount.c | 613 ++ src/libcore/umount.h | 28 + src/libcore/unit-printf.c | 309 + src/libcore/unit-printf.h | 26 + src/libcore/unit.c | 3756 ++++++++++ src/libcore/unit.h | 630 ++ src/libfirewall/Makefile | 42 + src/libfirewall/firewall-util.c | 349 + src/libfirewall/firewall-util.h | 83 + src/libshared/Makefile | 140 + src/libshared/acl-util.c | 429 ++ src/libshared/acl-util.h | 48 + src/libshared/acpi-fpdt.c | 160 + src/libshared/acpi-fpdt.h | 24 + src/libshared/apparmor-util.c | 39 + src/libshared/apparmor-util.h | 24 + src/libshared/architecture.c | 176 + src/libshared/architecture.h | 207 + src/libshared/ask-password-api.c | 736 ++ src/libshared/ask-password-api.h | 38 + src/libshared/base-filesystem.c | 129 + src/libshared/base-filesystem.h | 24 + src/libshared/boot-timestamps.c | 64 + src/libshared/boot-timestamps.h | 25 + src/libshared/bus-util.c | 2412 ++++++ src/libshared/bus-util.h | 196 + src/libshared/cgroup-show.c | 278 + src/libshared/cgroup-show.h | 32 + src/libshared/clean-ipc.c | 365 + src/libshared/clean-ipc.h | 24 + src/libshared/condition.c | 541 ++ src/libshared/condition.h | 94 + src/libshared/conf-parser.c | 870 +++ src/libshared/conf-parser.h | 228 + src/libshared/dev-setup.c | 73 + src/libshared/dev-setup.h | 24 + src/libshared/dns-domain.c | 1322 ++++ src/libshared/dns-domain.h | 110 + src/libshared/dropin.c | 251 + src/libshared/dropin.h | 61 + src/libshared/efivars.c | 715 ++ src/libshared/efivars.h | 131 + src/libshared/fstab-util.c | 263 + src/libshared/fstab-util.h | 52 + src/libshared/generator.c | 193 + src/libshared/generator.h | 36 + src/libshared/gpt.h | 66 + src/libshared/ima-util.c | 32 + src/libshared/ima-util.h | 24 + src/libshared/import-util.c | 185 + src/libshared/import-util.h | 43 + src/libshared/initreq.h | 77 + src/libshared/install-printf.c | 133 + src/libshared/install-printf.h | 24 + src/libshared/install.c | 2474 +++++++ src/libshared/install.h | 156 + src/libshared/logs-show.c | 1305 ++++ src/libshared/logs-show.h | 73 + src/libshared/machine-image.c | 813 +++ src/libshared/machine-image.h | 77 + src/libshared/machine-pool.c | 427 ++ src/libshared/machine-pool.h | 30 + src/libshared/output-mode.h | 46 + src/libshared/pager.c | 223 + src/libshared/pager.h | 30 + src/libshared/path-lookup.c | 441 ++ src/libshared/path-lookup.h | 60 + src/libshared/ptyfwd.c | 487 ++ src/libshared/ptyfwd.h | 48 + src/libshared/resolve-util.c | 39 + src/libshared/resolve-util.h | 60 + src/libshared/seccomp-util.c | 90 + src/libshared/seccomp-util.h | 28 + src/libshared/sleep-config.c | 274 + src/libshared/sleep-config.h | 26 + src/libshared/spawn-ask-password-agent.c | 62 + src/libshared/spawn-ask-password-agent.h | 23 + src/libshared/spawn-polkit-agent.c | 98 + src/libshared/spawn-polkit-agent.h | 23 + src/libshared/specifier.c | 188 + src/libshared/specifier.h | 37 + src/libshared/switch-root.c | 156 + src/libshared/switch-root.h | 23 + src/libshared/sysctl-util.c | 73 + src/libshared/sysctl-util.h | 25 + src/libshared/test-tables.h | 60 + src/libshared/udev-util.h | 44 + src/libshared/uid-range.c | 208 + src/libshared/uid-range.h | 33 + src/libshared/utmp-wtmp.c | 445 ++ src/libshared/utmp-wtmp.h | 74 + src/libshared/watchdog.c | 164 + src/libshared/watchdog.h | 29 + src/libsystemd-network/Makefile | 181 +- src/libsystemd-network/dhcp-identifier.c | 2 +- src/libsystemd-network/dhcp-identifier.h | 2 +- src/libsystemd-network/dhcp-internal.h | 2 +- src/libsystemd-network/dhcp-lease-internal.h | 2 +- src/libsystemd-network/dhcp-server-internal.h | 4 +- src/libsystemd-network/dhcp6-internal.h | 2 +- src/libsystemd-network/dhcp6-lease-internal.h | 2 +- src/libsystemd-network/dhcp6-option.c | 2 +- src/libsystemd-network/lldp-internal.c | 2 +- src/libsystemd-network/lldp-internal.h | 2 +- src/libsystemd-network/lldp-network.h | 2 +- src/libsystemd-network/lldp-port.h | 4 +- src/libsystemd-network/lldp-tlv.h | 2 +- src/libsystemd-network/network-internal.c | 2 +- src/libsystemd-network/network-internal.h | 2 +- src/libsystemd-network/sd-dhcp-client.c | 2 +- src/libsystemd-network/sd-dhcp-lease.c | 2 +- src/libsystemd-network/sd-dhcp-server.c | 2 +- src/libsystemd-network/sd-dhcp6-client.c | 2 +- src/libsystemd-network/sd-ipv4acd.c | 2 +- src/libsystemd-network/sd-ipv4ll.c | 4 +- src/libsystemd-network/sd-lldp.c | 2 +- src/libsystemd-network/sd-ndisc.c | 2 +- src/libsystemd-network/test-acd.c | 6 +- src/libsystemd-network/test-dhcp-client.c | 4 +- src/libsystemd-network/test-dhcp-server.c | 4 +- src/libsystemd-network/test-dhcp6-client.c | 4 +- src/libsystemd-network/test-ipv4ll-manual.c | 6 +- src/libsystemd-network/test-ipv4ll.c | 2 +- src/libsystemd-network/test-lldp.c | 4 +- src/libsystemd-network/test-ndisc-rs.c | 2 +- src/libsystemd/Makefile | 284 +- src/libsystemd/compat-libs/.gitignore | 1 + src/libsystemd/compat-libs/Makefile | 146 + src/libsystemd/compat-libs/libsystemd-daemon.pc.in | 19 + src/libsystemd/compat-libs/libsystemd-daemon.sym | 27 + src/libsystemd/compat-libs/libsystemd-id128.pc.in | 18 + src/libsystemd/compat-libs/libsystemd-id128.sym | 21 + .../compat-libs/libsystemd-journal.pc.in | 19 + src/libsystemd/compat-libs/libsystemd-journal.sym | 111 + src/libsystemd/compat-libs/libsystemd-login.pc.in | 18 + src/libsystemd/compat-libs/libsystemd-login.sym | 87 + src/libsystemd/compat-libs/linkwarning.h | 35 + src/libsystemd/include/systemd/_sd-common.h | 83 + src/libsystemd/include/systemd/sd-bus-protocol.h | 102 + src/libsystemd/include/systemd/sd-bus-vtable.h | 141 + src/libsystemd/include/systemd/sd-bus.h | 456 ++ src/libsystemd/include/systemd/sd-daemon.h | 289 + src/libsystemd/include/systemd/sd-device.h | 100 + src/libsystemd/include/systemd/sd-dhcp-client.h | 124 + src/libsystemd/include/systemd/sd-dhcp-lease.h | 67 + src/libsystemd/include/systemd/sd-dhcp-server.h | 64 + src/libsystemd/include/systemd/sd-dhcp6-client.h | 112 + src/libsystemd/include/systemd/sd-dhcp6-lease.h | 52 + src/libsystemd/include/systemd/sd-event.h | 138 + src/libsystemd/include/systemd/sd-hwdb.h | 49 + src/libsystemd/include/systemd/sd-id128.h | 115 + src/libsystemd/include/systemd/sd-ipv4acd.h | 60 + src/libsystemd/include/systemd/sd-ipv4ll.h | 60 + src/libsystemd/include/systemd/sd-journal.h | 172 + src/libsystemd/include/systemd/sd-lldp.h | 87 + src/libsystemd/include/systemd/sd-login.h | 245 + src/libsystemd/include/systemd/sd-messages.h | 93 + src/libsystemd/include/systemd/sd-ndisc.h | 84 + src/libsystemd/include/systemd/sd-netlink.h | 161 + src/libsystemd/include/systemd/sd-network.h | 178 + src/libsystemd/include/systemd/sd-path.h | 91 + src/libsystemd/include/systemd/sd-resolve.h | 117 + src/libsystemd/include/systemd/sd-utf8.h | 32 + src/libsystemd/libsystemd-internal/Makefile | 123 + .../libsystemd-internal/sd-bus/DIFFERENCES | 25 + .../sd-bus/GVARIANT-SERIALIZATION | 110 + .../libsystemd-internal/sd-bus/PORTING-DBUS1 | 535 ++ .../libsystemd-internal/sd-bus/bus-bloom.c | 156 + .../libsystemd-internal/sd-bus/bus-bloom.h | 43 + .../libsystemd-internal/sd-bus/bus-common-errors.c | 87 + .../libsystemd-internal/sd-bus/bus-common-errors.h | 86 + .../libsystemd-internal/sd-bus/bus-container.c | 277 + .../libsystemd-internal/sd-bus/bus-container.h | 25 + .../libsystemd-internal/sd-bus/bus-control.c | 1586 ++++ .../libsystemd-internal/sd-bus/bus-control.h | 32 + .../libsystemd-internal/sd-bus/bus-convenience.c | 626 ++ .../libsystemd-internal/sd-bus/bus-creds.c | 1349 ++++ .../libsystemd-internal/sd-bus/bus-creds.h | 90 + .../libsystemd-internal/sd-bus/bus-dump.c | 602 ++ .../libsystemd-internal/sd-bus/bus-dump.h | 37 + .../libsystemd-internal/sd-bus/bus-error.c | 608 ++ .../libsystemd-internal/sd-bus/bus-error.h | 64 + .../libsystemd-internal/sd-bus/bus-gvariant.c | 311 + .../libsystemd-internal/sd-bus/bus-gvariant.h | 30 + .../libsystemd-internal/sd-bus/bus-internal.c | 374 + .../libsystemd-internal/sd-bus/bus-internal.h | 399 + .../libsystemd-internal/sd-bus/bus-introspect.c | 212 + .../libsystemd-internal/sd-bus/bus-introspect.h | 40 + .../libsystemd-internal/sd-bus/bus-kernel.c | 1826 +++++ .../libsystemd-internal/sd-bus/bus-kernel.h | 93 + .../libsystemd-internal/sd-bus/bus-match.c | 1218 ++++ .../libsystemd-internal/sd-bus/bus-match.h | 100 + .../libsystemd-internal/sd-bus/bus-message.c | 5949 +++++++++++++++ .../libsystemd-internal/sd-bus/bus-message.h | 244 + .../libsystemd-internal/sd-bus/bus-objects.c | 2806 +++++++ .../libsystemd-internal/sd-bus/bus-objects.h | 25 + .../libsystemd-internal/sd-bus/bus-protocol.h | 180 + .../libsystemd-internal/sd-bus/bus-signature.c | 158 + .../libsystemd-internal/sd-bus/bus-signature.h | 28 + .../libsystemd-internal/sd-bus/bus-slot.c | 286 + .../libsystemd-internal/sd-bus/bus-slot.h | 28 + .../libsystemd-internal/sd-bus/bus-socket.c | 1064 +++ .../libsystemd-internal/sd-bus/bus-socket.h | 37 + .../libsystemd-internal/sd-bus/bus-track.c | 337 + .../libsystemd-internal/sd-bus/bus-track.h | 22 + .../libsystemd-internal/sd-bus/bus-type.c | 176 + .../libsystemd-internal/sd-bus/bus-type.h | 37 + .../libsystemd-internal/sd-bus/busctl-introspect.c | 790 ++ .../libsystemd-internal/sd-bus/busctl-introspect.h | 32 + src/libsystemd/libsystemd-internal/sd-bus/busctl.c | 2073 ++++++ src/libsystemd/libsystemd-internal/sd-bus/kdbus.h | 980 +++ src/libsystemd/libsystemd-internal/sd-bus/sd-bus.c | 3794 ++++++++++ .../sd-bus/test-bus-benchmark.c | 371 + .../libsystemd-internal/sd-bus/test-bus-chat.c | 560 ++ .../libsystemd-internal/sd-bus/test-bus-cleanup.c | 95 + .../libsystemd-internal/sd-bus/test-bus-creds.c | 50 + .../libsystemd-internal/sd-bus/test-bus-error.c | 232 + .../libsystemd-internal/sd-bus/test-bus-gvariant.c | 224 + .../sd-bus/test-bus-introspect.c | 63 + .../sd-bus/test-bus-kernel-bloom.c | 141 + .../libsystemd-internal/sd-bus/test-bus-kernel.c | 190 + .../libsystemd-internal/sd-bus/test-bus-marshal.c | 432 ++ .../libsystemd-internal/sd-bus/test-bus-match.c | 159 + .../libsystemd-internal/sd-bus/test-bus-objects.c | 555 ++ .../libsystemd-internal/sd-bus/test-bus-proxy.c | 117 + .../libsystemd-internal/sd-bus/test-bus-server.c | 216 + .../sd-bus/test-bus-signature.c | 164 + .../sd-bus/test-bus-zero-copy.c | 210 + .../libsystemd-internal/sd-daemon/sd-daemon.c | 624 ++ .../sd-device/device-enumerator-private.h | 34 + .../sd-device/device-enumerator.c | 986 +++ .../sd-device/device-internal.h | 126 + .../libsystemd-internal/sd-device/device-private.c | 1119 +++ .../libsystemd-internal/sd-device/device-private.h | 68 + .../libsystemd-internal/sd-device/device-util.h | 52 + .../libsystemd-internal/sd-device/sd-device.c | 1873 +++++ .../libsystemd-internal/sd-event/sd-event.c | 2887 ++++++++ .../libsystemd-internal/sd-event/test-event.c | 357 + .../libsystemd-internal/sd-hwdb/hwdb-internal.h | 71 + .../libsystemd-internal/sd-hwdb/hwdb-util.h | 26 + .../libsystemd-internal/sd-hwdb/sd-hwdb.c | 470 ++ .../libsystemd-internal/sd-id128/sd-id128.c | 225 + .../libsystemd-internal/sd-login/sd-login.c | 1062 +++ .../libsystemd-internal/sd-login/test-login.c | 264 + .../sd-netlink/local-addresses.c | 276 + .../sd-netlink/local-addresses.h | 36 + .../sd-netlink/netlink-internal.h | 137 + .../sd-netlink/netlink-message.c | 964 +++ .../sd-netlink/netlink-socket.c | 474 ++ .../libsystemd-internal/sd-netlink/netlink-types.c | 653 ++ .../libsystemd-internal/sd-netlink/netlink-types.h | 94 + .../libsystemd-internal/sd-netlink/netlink-util.c | 170 + .../libsystemd-internal/sd-netlink/netlink-util.h | 39 + .../libsystemd-internal/sd-netlink/rtnl-message.c | 675 ++ .../libsystemd-internal/sd-netlink/sd-netlink.c | 954 +++ .../sd-netlink/test-local-addresses.c | 56 + .../libsystemd-internal/sd-netlink/test-netlink.c | 440 ++ .../libsystemd-internal/sd-network/network-util.c | 37 + .../libsystemd-internal/sd-network/network-util.h | 24 + .../libsystemd-internal/sd-network/sd-network.c | 373 + .../libsystemd-internal/sd-path/sd-path.c | 633 ++ .../libsystemd-internal/sd-resolve/sd-resolve.c | 1245 ++++ .../libsystemd-internal/sd-resolve/test-resolve.c | 114 + .../libsystemd-internal/sd-utf8/sd-utf8.c | 35 + .../libsystemd-journal-internal/Makefile | 97 + src/libsystemd/sd-bus/DIFFERENCES | 25 - src/libsystemd/sd-bus/GVARIANT-SERIALIZATION | 110 - src/libsystemd/sd-bus/Makefile | 1 - src/libsystemd/sd-bus/PORTING-DBUS1 | 535 -- src/libsystemd/sd-bus/bus-bloom.c | 156 - src/libsystemd/sd-bus/bus-bloom.h | 43 - src/libsystemd/sd-bus/bus-common-errors.c | 87 - src/libsystemd/sd-bus/bus-common-errors.h | 86 - src/libsystemd/sd-bus/bus-container.c | 277 - src/libsystemd/sd-bus/bus-container.h | 25 - src/libsystemd/sd-bus/bus-control.c | 1586 ---- src/libsystemd/sd-bus/bus-control.h | 32 - src/libsystemd/sd-bus/bus-convenience.c | 626 -- src/libsystemd/sd-bus/bus-creds.c | 1349 ---- src/libsystemd/sd-bus/bus-creds.h | 90 - src/libsystemd/sd-bus/bus-dump.c | 602 -- src/libsystemd/sd-bus/bus-dump.h | 37 - src/libsystemd/sd-bus/bus-error.c | 608 -- src/libsystemd/sd-bus/bus-error.h | 64 - src/libsystemd/sd-bus/bus-gvariant.c | 311 - src/libsystemd/sd-bus/bus-gvariant.h | 30 - src/libsystemd/sd-bus/bus-internal.c | 374 - src/libsystemd/sd-bus/bus-internal.h | 399 - src/libsystemd/sd-bus/bus-introspect.c | 212 - src/libsystemd/sd-bus/bus-introspect.h | 40 - src/libsystemd/sd-bus/bus-kernel.c | 1826 ----- src/libsystemd/sd-bus/bus-kernel.h | 93 - src/libsystemd/sd-bus/bus-match.c | 1218 ---- src/libsystemd/sd-bus/bus-match.h | 100 - src/libsystemd/sd-bus/bus-message.c | 5949 --------------- src/libsystemd/sd-bus/bus-message.h | 244 - src/libsystemd/sd-bus/bus-objects.c | 2806 ------- src/libsystemd/sd-bus/bus-objects.h | 25 - src/libsystemd/sd-bus/bus-protocol.h | 180 - src/libsystemd/sd-bus/bus-signature.c | 158 - src/libsystemd/sd-bus/bus-signature.h | 28 - src/libsystemd/sd-bus/bus-slot.c | 286 - src/libsystemd/sd-bus/bus-slot.h | 28 - src/libsystemd/sd-bus/bus-socket.c | 1064 --- src/libsystemd/sd-bus/bus-socket.h | 37 - src/libsystemd/sd-bus/bus-track.c | 337 - src/libsystemd/sd-bus/bus-track.h | 22 - src/libsystemd/sd-bus/bus-type.c | 176 - src/libsystemd/sd-bus/bus-type.h | 37 - src/libsystemd/sd-bus/busctl-introspect.c | 790 -- src/libsystemd/sd-bus/busctl-introspect.h | 32 - src/libsystemd/sd-bus/busctl.c | 2073 ------ src/libsystemd/sd-bus/kdbus.h | 980 --- src/libsystemd/sd-bus/sd-bus.c | 3794 ---------- src/libsystemd/sd-bus/test-bus-benchmark.c | 371 - src/libsystemd/sd-bus/test-bus-chat.c | 560 -- src/libsystemd/sd-bus/test-bus-cleanup.c | 95 - src/libsystemd/sd-bus/test-bus-creds.c | 50 - src/libsystemd/sd-bus/test-bus-error.c | 232 - src/libsystemd/sd-bus/test-bus-gvariant.c | 224 - src/libsystemd/sd-bus/test-bus-introspect.c | 63 - src/libsystemd/sd-bus/test-bus-kernel-bloom.c | 141 - src/libsystemd/sd-bus/test-bus-kernel.c | 190 - src/libsystemd/sd-bus/test-bus-marshal.c | 432 -- src/libsystemd/sd-bus/test-bus-match.c | 159 - src/libsystemd/sd-bus/test-bus-objects.c | 555 -- src/libsystemd/sd-bus/test-bus-proxy.c | 117 - src/libsystemd/sd-bus/test-bus-server.c | 216 - src/libsystemd/sd-bus/test-bus-signature.c | 164 - src/libsystemd/sd-bus/test-bus-zero-copy.c | 210 - src/libsystemd/sd-daemon/Makefile | 1 - src/libsystemd/sd-daemon/sd-daemon.c | 624 -- src/libsystemd/sd-device/Makefile | 1 - .../sd-device/device-enumerator-private.h | 34 - src/libsystemd/sd-device/device-enumerator.c | 986 --- src/libsystemd/sd-device/device-internal.h | 126 - src/libsystemd/sd-device/device-private.c | 1119 --- src/libsystemd/sd-device/device-private.h | 68 - src/libsystemd/sd-device/device-util.h | 52 - src/libsystemd/sd-device/sd-device.c | 1873 ----- src/libsystemd/sd-event/Makefile | 1 - src/libsystemd/sd-event/sd-event.c | 2887 -------- src/libsystemd/sd-event/test-event.c | 357 - src/libsystemd/sd-hwdb/Makefile | 1 - src/libsystemd/sd-hwdb/hwdb-internal.h | 71 - src/libsystemd/sd-hwdb/hwdb-util.h | 26 - src/libsystemd/sd-hwdb/sd-hwdb.c | 470 -- src/libsystemd/sd-id128/Makefile | 1 - src/libsystemd/sd-id128/sd-id128.c | 225 - src/libsystemd/sd-login/Makefile | 1 - src/libsystemd/sd-login/sd-login.c | 1062 --- src/libsystemd/sd-login/test-login.c | 264 - src/libsystemd/sd-netlink/Makefile | 1 - src/libsystemd/sd-netlink/local-addresses.c | 276 - src/libsystemd/sd-netlink/local-addresses.h | 36 - src/libsystemd/sd-netlink/netlink-internal.h | 137 - src/libsystemd/sd-netlink/netlink-message.c | 964 --- src/libsystemd/sd-netlink/netlink-socket.c | 474 -- src/libsystemd/sd-netlink/netlink-types.c | 653 -- src/libsystemd/sd-netlink/netlink-types.h | 94 - src/libsystemd/sd-netlink/netlink-util.c | 170 - src/libsystemd/sd-netlink/netlink-util.h | 39 - src/libsystemd/sd-netlink/rtnl-message.c | 675 -- src/libsystemd/sd-netlink/sd-netlink.c | 954 --- src/libsystemd/sd-netlink/test-local-addresses.c | 56 - src/libsystemd/sd-netlink/test-netlink.c | 440 -- src/libsystemd/sd-network/Makefile | 1 - src/libsystemd/sd-network/network-util.c | 37 - src/libsystemd/sd-network/network-util.h | 24 - src/libsystemd/sd-network/sd-network.c | 373 - src/libsystemd/sd-path/Makefile | 1 - src/libsystemd/sd-path/sd-path.c | 633 -- src/libsystemd/sd-resolve/Makefile | 1 - src/libsystemd/sd-resolve/sd-resolve.c | 1245 ---- src/libsystemd/sd-resolve/test-resolve.c | 114 - src/libsystemd/sd-utf8/Makefile | 1 - src/libsystemd/sd-utf8/sd-utf8.c | 35 - src/libudev/.gitignore | 1 - src/libudev/Makefile | 40 +- src/libudev/include/libudev.h | 206 + src/libudev/libudev-device-internal.h | 58 - src/libudev/libudev-device-private.c | 411 -- src/libudev/libudev-device.c | 958 --- src/libudev/libudev-enumerate.c | 419 -- src/libudev/libudev-hwdb.c | 146 - src/libudev/libudev-list.c | 352 - src/libudev/libudev-monitor.c | 845 --- src/libudev/libudev-private.h | 150 - src/libudev/libudev-queue.c | 268 - src/libudev/libudev-util.c | 268 - src/libudev/libudev.c | 253 - src/libudev/libudev.h | 206 - src/libudev/libudev.pc.in | 17 - src/libudev/libudev.sym | 120 - src/libudev/src/.gitignore | 1 + src/libudev/src/Makefile | 81 + src/libudev/src/libudev-device-internal.h | 58 + src/libudev/src/libudev-device-private.c | 411 ++ src/libudev/src/libudev-device.c | 958 +++ src/libudev/src/libudev-enumerate.c | 419 ++ src/libudev/src/libudev-hwdb.c | 146 + src/libudev/src/libudev-list.c | 352 + src/libudev/src/libudev-monitor.c | 845 +++ src/libudev/src/libudev-private.h | 150 + src/libudev/src/libudev-queue.c | 268 + src/libudev/src/libudev-util.c | 268 + src/libudev/src/libudev.c | 253 + src/libudev/src/libudev.pc.in | 17 + src/libudev/src/libudev.sym | 120 + src/locale/Makefile | 91 +- src/locale/localectl.c | 2 +- src/locale/localed.c | 2 +- src/login/Makefile | 244 +- src/login/inhibit.c | 2 +- src/login/loginctl.c | 2 +- src/login/logind-button.c | 2 +- src/login/logind-dbus.c | 2 +- src/login/logind-seat.c | 2 +- src/login/logind-session.c | 2 +- src/login/logind-utmp.c | 2 +- src/login/logind.c | 2 +- src/login/logind.h | 4 +- src/login/test-inhibit.c | 2 +- src/machine-id-setup/Makefile | 38 +- src/machine/.gitignore | 1 - src/machine/Makefile | 1 - src/machine/image-dbus.c | 361 - src/machine/image-dbus.h | 35 - src/machine/machine-dbus.c | 1435 ---- src/machine/machine-dbus.h | 43 - src/machine/machine.c | 659 -- src/machine/machine.h | 124 - src/machine/machinectl.c | 2678 ------- src/machine/machined-dbus.c | 1551 ---- src/machine/machined.c | 407 -- src/machine/machined.h | 78 - src/machine/nss-mymachines/nss-mymachines.c | 738 ++ src/machine/nss-mymachines/nss-mymachines.sym | 21 + src/machine/org.freedesktop.machine1.conf | 194 - src/machine/org.freedesktop.machine1.policy.in | 102 - src/machine/org.freedesktop.machine1.service | 12 - src/machine/src/.gitignore | 1 + src/machine/src/Makefile | 128 + src/machine/src/image-dbus.c | 361 + src/machine/src/image-dbus.h | 35 + src/machine/src/machine-dbus.c | 1435 ++++ src/machine/src/machine-dbus.h | 43 + src/machine/src/machine.c | 659 ++ src/machine/src/machine.h | 124 + src/machine/src/machinectl.c | 2678 +++++++ src/machine/src/machined-dbus.c | 1551 ++++ src/machine/src/machined.c | 407 ++ src/machine/src/machined.h | 78 + src/machine/src/org.freedesktop.machine1.conf | 194 + src/machine/src/org.freedesktop.machine1.policy.in | 102 + src/machine/src/org.freedesktop.machine1.service | 12 + src/machine/src/test-machine-tables.c | 29 + src/machine/test-machine-tables.c | 29 - src/modules-load/Makefile | 61 +- src/network/Makefile | 201 +- src/network/networkctl.c | 8 +- src/network/networkd-dhcp6.c | 2 +- src/network/networkd-link.h | 12 +- src/network/networkd-manager.c | 4 +- src/network/networkd-ndisc.c | 2 +- src/network/networkd-netdev-bond.c | 2 +- src/network/networkd-netdev-tunnel.c | 2 +- src/network/networkd-netdev-veth.c | 2 +- src/network/networkd-netdev-vxlan.c | 2 +- src/network/networkd-wait-online-link.c | 2 +- src/network/networkd-wait-online.c | 2 +- src/network/networkd-wait-online.h | 6 +- src/network/networkd.c | 2 +- src/network/networkd.h | 6 +- src/notify/Makefile | 1 - src/notify/notify.c | 203 - src/nspawn/.gitignore | 1 - src/nspawn/Makefile | 1 - src/nspawn/nspawn-cgroup.c | 163 - src/nspawn/nspawn-cgroup.h | 27 - src/nspawn/nspawn-expose-ports.c | 245 - src/nspawn/nspawn-expose-ports.h | 44 - src/nspawn/nspawn-gperf.gperf | 41 - src/nspawn/nspawn-mount.c | 942 --- src/nspawn/nspawn-mount.h | 69 - src/nspawn/nspawn-network.c | 540 -- src/nspawn/nspawn-network.h | 36 - src/nspawn/nspawn-register.c | 235 - src/nspawn/nspawn-register.h | 29 - src/nspawn/nspawn-settings.c | 394 - src/nspawn/nspawn-settings.h | 101 - src/nspawn/nspawn-setuid.c | 273 - src/nspawn/nspawn-setuid.h | 22 - src/nspawn/nspawn-stub-pid1.c | 170 - src/nspawn/nspawn-stub-pid1.h | 22 - src/nspawn/nspawn.c | 3700 ---------- src/nss-myhostname/Makefile | 48 +- src/nss-mymachines/Makefile | 1 - src/nss-mymachines/nss-mymachines.c | 738 -- src/nss-mymachines/nss-mymachines.sym | 21 - src/nss-resolve/Makefile | 1 - src/nss-resolve/nss-resolve.c | 704 -- src/nss-resolve/nss-resolve.sym | 19 - src/path/Makefile | 1 - src/path/path.c | 198 - src/quotacheck/Makefile | 1 - src/quotacheck/quotacheck.c | 124 - src/random-seed/Makefile | 1 - src/random-seed/random-seed.c | 176 - src/rc-local-generator/Makefile | 33 +- src/remount-fs/Makefile | 1 - src/remount-fs/remount-fs.c | 155 - src/reply-password/Makefile | 1 - src/reply-password/reply-password.c | 98 - src/resolve/.gitignore | 6 - src/resolve/Makefile | 1 - src/resolve/RFCs | 59 - src/resolve/dns-type.c | 306 - src/resolve/dns-type.h | 154 - src/resolve/nss-resolve/nss-resolve.c | 704 ++ src/resolve/nss-resolve/nss-resolve.sym | 19 + src/resolve/org.freedesktop.resolve1.conf | 27 - src/resolve/org.freedesktop.resolve1.service | 12 - src/resolve/resolve-tool.c | 1272 ---- src/resolve/resolved-bus.c | 1565 ---- src/resolve/resolved-bus.h | 25 - src/resolve/resolved-conf.c | 236 - src/resolve/resolved-conf.h | 36 - src/resolve/resolved-def.h | 38 - src/resolve/resolved-dns-answer.c | 858 --- src/resolve/resolved-dns-answer.h | 143 - src/resolve/resolved-dns-cache.c | 1088 --- src/resolve/resolved-dns-cache.h | 52 - src/resolve/resolved-dns-dnssec.c | 2211 ------ src/resolve/resolved-dns-dnssec.h | 102 - src/resolve/resolved-dns-packet.c | 2295 ------ src/resolve/resolved-dns-packet.h | 272 - src/resolve/resolved-dns-query.c | 1114 --- src/resolve/resolved-dns-query.h | 133 - src/resolve/resolved-dns-question.c | 468 -- src/resolve/resolved-dns-question.h | 69 - src/resolve/resolved-dns-rr.c | 1521 ---- src/resolve/resolved-dns-rr.h | 333 - src/resolve/resolved-dns-scope.c | 1028 --- src/resolve/resolved-dns-scope.h | 109 - src/resolve/resolved-dns-search-domain.c | 227 - src/resolve/resolved-dns-search-domain.h | 74 - src/resolve/resolved-dns-server.c | 740 -- src/resolve/resolved-dns-server.h | 143 - src/resolve/resolved-dns-stream.c | 403 - src/resolve/resolved-dns-stream.h | 61 - src/resolve/resolved-dns-synthesize.c | 413 -- src/resolve/resolved-dns-synthesize.h | 30 - src/resolve/resolved-dns-transaction.c | 3026 -------- src/resolve/resolved-dns-transaction.h | 177 - src/resolve/resolved-dns-trust-anchor.c | 743 -- src/resolve/resolved-dns-trust-anchor.h | 43 - src/resolve/resolved-dns-zone.c | 656 -- src/resolve/resolved-dns-zone.h | 81 - src/resolve/resolved-etc-hosts.c | 448 -- src/resolve/resolved-etc-hosts.h | 28 - src/resolve/resolved-gperf.gperf | 21 - src/resolve/resolved-link-bus.c | 550 -- src/resolve/resolved-link-bus.h | 38 - src/resolve/resolved-link.c | 840 --- src/resolve/resolved-link.h | 111 - src/resolve/resolved-llmnr.c | 476 -- src/resolve/resolved-llmnr.h | 32 - src/resolve/resolved-manager.c | 1239 ---- src/resolve/resolved-manager.h | 171 - src/resolve/resolved-mdns.c | 287 - src/resolve/resolved-mdns.h | 30 - src/resolve/resolved-resolv-conf.c | 267 - src/resolve/resolved-resolv-conf.h | 27 - src/resolve/resolved.c | 112 - src/resolve/resolved.conf.in | 19 - src/resolve/src/.gitignore | 6 + src/resolve/src/Makefile | 208 + src/resolve/src/RFCs | 59 + src/resolve/src/dns-type.c | 306 + src/resolve/src/dns-type.h | 154 + src/resolve/src/org.freedesktop.resolve1.conf | 27 + src/resolve/src/org.freedesktop.resolve1.service | 12 + src/resolve/src/resolve-tool.c | 1272 ++++ src/resolve/src/resolved-bus.c | 1565 ++++ src/resolve/src/resolved-bus.h | 25 + src/resolve/src/resolved-conf.c | 236 + src/resolve/src/resolved-conf.h | 36 + src/resolve/src/resolved-def.h | 38 + src/resolve/src/resolved-dns-answer.c | 858 +++ src/resolve/src/resolved-dns-answer.h | 143 + src/resolve/src/resolved-dns-cache.c | 1088 +++ src/resolve/src/resolved-dns-cache.h | 52 + src/resolve/src/resolved-dns-dnssec.c | 2211 ++++++ src/resolve/src/resolved-dns-dnssec.h | 102 + src/resolve/src/resolved-dns-packet.c | 2295 ++++++ src/resolve/src/resolved-dns-packet.h | 272 + src/resolve/src/resolved-dns-query.c | 1114 +++ src/resolve/src/resolved-dns-query.h | 133 + src/resolve/src/resolved-dns-question.c | 468 ++ src/resolve/src/resolved-dns-question.h | 69 + src/resolve/src/resolved-dns-rr.c | 1521 ++++ src/resolve/src/resolved-dns-rr.h | 333 + src/resolve/src/resolved-dns-scope.c | 1028 +++ src/resolve/src/resolved-dns-scope.h | 109 + src/resolve/src/resolved-dns-search-domain.c | 227 + src/resolve/src/resolved-dns-search-domain.h | 74 + src/resolve/src/resolved-dns-server.c | 740 ++ src/resolve/src/resolved-dns-server.h | 143 + src/resolve/src/resolved-dns-stream.c | 403 + src/resolve/src/resolved-dns-stream.h | 61 + src/resolve/src/resolved-dns-synthesize.c | 413 ++ src/resolve/src/resolved-dns-synthesize.h | 30 + src/resolve/src/resolved-dns-transaction.c | 3026 ++++++++ src/resolve/src/resolved-dns-transaction.h | 177 + src/resolve/src/resolved-dns-trust-anchor.c | 743 ++ src/resolve/src/resolved-dns-trust-anchor.h | 43 + src/resolve/src/resolved-dns-zone.c | 656 ++ src/resolve/src/resolved-dns-zone.h | 81 + src/resolve/src/resolved-etc-hosts.c | 448 ++ src/resolve/src/resolved-etc-hosts.h | 28 + src/resolve/src/resolved-gperf.gperf | 21 + src/resolve/src/resolved-link-bus.c | 550 ++ src/resolve/src/resolved-link-bus.h | 38 + src/resolve/src/resolved-link.c | 840 +++ src/resolve/src/resolved-link.h | 111 + src/resolve/src/resolved-llmnr.c | 476 ++ src/resolve/src/resolved-llmnr.h | 32 + src/resolve/src/resolved-manager.c | 1239 ++++ src/resolve/src/resolved-manager.h | 171 + src/resolve/src/resolved-mdns.c | 287 + src/resolve/src/resolved-mdns.h | 30 + src/resolve/src/resolved-resolv-conf.c | 267 + src/resolve/src/resolved-resolv-conf.h | 27 + src/resolve/src/resolved.c | 112 + src/resolve/src/resolved.conf.in | 19 + src/resolve/src/test-dnssec-complex.c | 236 + src/resolve/src/test-dnssec.c | 336 + src/resolve/src/test-resolve-tables.c | 27 + src/resolve/test-dnssec-complex.c | 236 - src/resolve/test-dnssec.c | 336 - src/resolve/test-resolve-tables.c | 27 - src/rfkill/Makefile | 1 - src/rfkill/rfkill.c | 426 -- src/run/Makefile | 1 - src/run/run.c | 1258 ---- src/shared/Makefile | 1 - src/shared/acl-util.c | 429 -- src/shared/acl-util.h | 48 - src/shared/acpi-fpdt.c | 160 - src/shared/acpi-fpdt.h | 24 - src/shared/apparmor-util.c | 39 - src/shared/apparmor-util.h | 24 - src/shared/architecture.c | 176 - src/shared/architecture.h | 207 - src/shared/ask-password-api.c | 736 -- src/shared/ask-password-api.h | 38 - src/shared/base-filesystem.c | 129 - src/shared/base-filesystem.h | 24 - src/shared/boot-timestamps.c | 64 - src/shared/boot-timestamps.h | 25 - src/shared/bus-util.c | 2412 ------ src/shared/bus-util.h | 196 - src/shared/cgroup-show.c | 278 - src/shared/cgroup-show.h | 32 - src/shared/clean-ipc.c | 365 - src/shared/clean-ipc.h | 24 - src/shared/condition.c | 541 -- src/shared/condition.h | 94 - src/shared/conf-parser.c | 870 --- src/shared/conf-parser.h | 228 - src/shared/dev-setup.c | 73 - src/shared/dev-setup.h | 24 - src/shared/dns-domain.c | 1322 ---- src/shared/dns-domain.h | 110 - src/shared/dropin.c | 251 - src/shared/dropin.h | 61 - src/shared/efivars.c | 715 -- src/shared/efivars.h | 131 - src/shared/firewall-util.c | 349 - src/shared/firewall-util.h | 83 - src/shared/fstab-util.c | 263 - src/shared/fstab-util.h | 52 - src/shared/generator.c | 193 - src/shared/generator.h | 36 - src/shared/gpt.h | 66 - src/shared/ima-util.c | 32 - src/shared/ima-util.h | 24 - src/shared/import-util.c | 185 - src/shared/import-util.h | 43 - src/shared/initreq.h | 77 - src/shared/install-printf.c | 133 - src/shared/install-printf.h | 24 - src/shared/install.c | 2474 ------- src/shared/install.h | 156 - src/shared/linux/auto_dev-ioctl.h | 228 - src/shared/logs-show.c | 1305 ---- src/shared/logs-show.h | 73 - src/shared/machine-image.c | 813 --- src/shared/machine-image.h | 77 - src/shared/machine-pool.c | 427 -- src/shared/machine-pool.h | 30 - src/shared/output-mode.h | 46 - src/shared/pager.c | 223 - src/shared/pager.h | 30 - src/shared/path-lookup.c | 441 -- src/shared/path-lookup.h | 60 - src/shared/ptyfwd.c | 487 -- src/shared/ptyfwd.h | 48 - src/shared/resolve-util.c | 39 - src/shared/resolve-util.h | 60 - src/shared/seccomp-util.c | 90 - src/shared/seccomp-util.h | 28 - src/shared/sleep-config.c | 274 - src/shared/sleep-config.h | 26 - src/shared/spawn-ask-password-agent.c | 62 - src/shared/spawn-ask-password-agent.h | 23 - src/shared/spawn-polkit-agent.c | 98 - src/shared/spawn-polkit-agent.h | 23 - src/shared/specifier.c | 188 - src/shared/specifier.h | 37 - src/shared/switch-root.c | 156 - src/shared/switch-root.h | 23 - src/shared/sysctl-util.c | 73 - src/shared/sysctl-util.h | 25 - src/shared/test-tables.h | 60 - src/shared/udev-util.h | 44 - src/shared/uid-range.c | 208 - src/shared/uid-range.h | 33 - src/shared/utmp-wtmp.c | 445 -- src/shared/utmp-wtmp.h | 74 - src/shared/watchdog.c | 164 - src/shared/watchdog.h | 29 - src/sleep/Makefile | 33 +- src/sleep/sleep.c | 2 +- src/socket-proxy/Makefile | 34 +- src/socket-proxy/socket-proxyd.c | 6 +- src/sysctl/Makefile | 33 +- src/system-update-generator/Makefile | 33 +- src/system/systemctl/Makefile | 32 + src/system/systemctl/systemctl.c | 7703 ++++++++++++++++++++ src/system/systemctl/systemd-sysv-install.SKELETON | 47 + src/system/systemd/Makefile | 67 + src/system/systemd/macros.systemd.in | 105 + src/system/systemd/main.c | 2142 ++++++ src/system/systemd/org.freedesktop.systemd1.conf | 212 + .../systemd/org.freedesktop.systemd1.policy.in.in | 70 + .../systemd/org.freedesktop.systemd1.service | 11 + src/system/systemd/system.conf | 60 + src/system/systemd/systemd.pc.in | 34 + src/system/systemd/triggers.systemd.in | 64 + src/system/systemd/user.conf | 44 + src/systemctl/Makefile | 1 - src/systemctl/systemctl.c | 7703 -------------------- src/systemctl/systemd-sysv-install.SKELETON | 47 - src/systemd-activate/Makefile | 36 + src/systemd-activate/activate.c | 511 ++ src/systemd-activate/systemd-activate.xml | 204 + src/systemd-analyze/.gitignore | 1 + src/systemd-analyze/Makefile | 39 + src/systemd-analyze/analyze-verify.c | 306 + src/systemd-analyze/analyze-verify.h | 26 + src/systemd-analyze/analyze.c | 1492 ++++ src/systemd-ask-password/Makefile | 32 + src/systemd-ask-password/ask-password.c | 179 + src/systemd-backlight/Makefile | 43 + src/systemd-backlight/backlight.c | 434 ++ src/systemd-binfmt/Makefile | 56 + src/systemd-binfmt/binfmt.c | 203 + src/systemd-bootchart/Makefile | 51 + src/systemd-bootchart/bootchart.c | 531 ++ src/systemd-bootchart/bootchart.conf | 26 + src/systemd-bootchart/bootchart.h | 119 + src/systemd-bootchart/store.c | 555 ++ src/systemd-bootchart/store.h | 36 + src/systemd-bootchart/svg.c | 1375 ++++ src/systemd-bootchart/svg.h | 35 + src/systemd-cgls/Makefile | 32 + src/systemd-cgls/cgls.c | 287 + src/systemd-cgroups-agent/Makefile | 32 + src/systemd-cgroups-agent/cgroups-agent.c | 65 + src/systemd-cgtop/Makefile | 32 + src/systemd-cgtop/cgtop.c | 1097 +++ src/systemd-cryptsetup/Makefile | 59 + src/systemd-cryptsetup/cryptsetup-generator.c | 509 ++ src/systemd-cryptsetup/cryptsetup.c | 753 ++ src/systemd-delta/Makefile | 32 + src/systemd-delta/delta.c | 643 ++ src/systemd-escape/Makefile | 32 + src/systemd-escape/escape.c | 237 + src/systemd-notify/Makefile | 32 + src/systemd-notify/notify.c | 203 + src/systemd-nspawn/.gitignore | 1 + src/systemd-nspawn/Makefile | 69 + src/systemd-nspawn/nspawn-cgroup.c | 163 + src/systemd-nspawn/nspawn-cgroup.h | 27 + src/systemd-nspawn/nspawn-expose-ports.c | 245 + src/systemd-nspawn/nspawn-expose-ports.h | 44 + src/systemd-nspawn/nspawn-gperf.gperf | 41 + src/systemd-nspawn/nspawn-mount.c | 942 +++ src/systemd-nspawn/nspawn-mount.h | 69 + src/systemd-nspawn/nspawn-network.c | 540 ++ src/systemd-nspawn/nspawn-network.h | 36 + src/systemd-nspawn/nspawn-register.c | 235 + src/systemd-nspawn/nspawn-register.h | 29 + src/systemd-nspawn/nspawn-settings.c | 394 + src/systemd-nspawn/nspawn-settings.h | 101 + src/systemd-nspawn/nspawn-setuid.c | 273 + src/systemd-nspawn/nspawn-setuid.h | 22 + src/systemd-nspawn/nspawn-stub-pid1.c | 170 + src/systemd-nspawn/nspawn-stub-pid1.h | 22 + src/systemd-nspawn/nspawn.c | 3700 ++++++++++ src/systemd-path/Makefile | 32 + src/systemd-path/path.c | 198 + src/systemd-quotacheck/Makefile | 46 + src/systemd-quotacheck/quotacheck.c | 124 + src/systemd-random-seed/Makefile | 47 + src/systemd-random-seed/random-seed.c | 176 + src/systemd-remount-fs/Makefile | 34 + src/systemd-remount-fs/remount-fs.c | 155 + src/systemd-reply-password/Makefile | 32 + src/systemd-reply-password/reply-password.c | 98 + src/systemd-rfkill/Makefile | 46 + src/systemd-rfkill/rfkill.c | 426 ++ src/systemd-run/Makefile | 32 + src/systemd-run/run.c | 1258 ++++ src/systemd-shutdown/Makefile | 38 + src/systemd-timedate/.gitignore | 1 + src/systemd-timedate/Makefile | 79 + .../org.freedesktop.timedate1.conf | 27 + .../org.freedesktop.timedate1.policy.in | 62 + .../org.freedesktop.timedate1.service | 12 + src/systemd-timedate/timedatectl.c | 514 ++ src/systemd-timedate/timedated.c | 733 ++ src/systemd-timesync/.gitignore | 2 + src/systemd-timesync/Makefile | 64 + src/systemd-timesync/timesyncd-conf.c | 106 + src/systemd-timesync/timesyncd-conf.h | 31 + src/systemd-timesync/timesyncd-gperf.gperf | 19 + src/systemd-timesync/timesyncd-manager.c | 1156 +++ src/systemd-timesync/timesyncd-manager.h | 104 + src/systemd-timesync/timesyncd-server.c | 150 + src/systemd-timesync/timesyncd-server.h | 65 + src/systemd-timesync/timesyncd.c | 164 + src/systemd-timesync/timesyncd.conf.in | 16 + src/systemd-tmpfiles/Makefile | 84 + src/systemd-tmpfiles/tmpfiles.c | 2364 ++++++ src/systemd-tty-ask-password-agent/Makefile | 32 + .../tty-ask-password-agent.c | 681 ++ src/systemd-update-done/Makefile | 32 + src/systemd-update-done/update-done.c | 115 + src/systemd-update-utmp/Makefile | 37 + src/systemd-update-utmp/update-utmp.c | 283 + src/systemd-user-sessions/user-sessions.c | 84 + src/systemd-vconsole/.gitignore | 1 + src/systemd-vconsole/90-vconsole.rules.in | 10 + src/systemd-vconsole/Makefile | 50 + src/systemd-vconsole/vconsole-setup.c | 326 + src/systemd/Makefile | 1 - src/systemd/_sd-common.h | 83 - src/systemd/sd-bus-protocol.h | 102 - src/systemd/sd-bus-vtable.h | 141 - src/systemd/sd-bus.h | 456 -- src/systemd/sd-daemon.h | 289 - src/systemd/sd-device.h | 100 - src/systemd/sd-dhcp-client.h | 124 - src/systemd/sd-dhcp-lease.h | 67 - src/systemd/sd-dhcp-server.h | 64 - src/systemd/sd-dhcp6-client.h | 112 - src/systemd/sd-dhcp6-lease.h | 52 - src/systemd/sd-event.h | 138 - src/systemd/sd-hwdb.h | 49 - src/systemd/sd-id128.h | 115 - src/systemd/sd-ipv4acd.h | 60 - src/systemd/sd-ipv4ll.h | 60 - src/systemd/sd-journal.h | 172 - src/systemd/sd-lldp.h | 87 - src/systemd/sd-login.h | 245 - src/systemd/sd-messages.h | 93 - src/systemd/sd-ndisc.h | 84 - src/systemd/sd-netlink.h | 161 - src/systemd/sd-network.h | 178 - src/systemd/sd-path.h | 91 - src/systemd/sd-resolve.h | 117 - src/systemd/sd-utf8.h | 32 - src/sysusers/Makefile | 62 +- src/sysv-generator/Makefile | 33 +- src/test/Makefile | 36 +- src/test/test-condition.c | 2 +- src/test/test-daemon.c | 2 +- src/test/test-helper.h | 2 +- src/test/test-id128.c | 4 +- src/test/test-netlink-manual.c | 2 +- src/timedate/.gitignore | 1 - src/timedate/Makefile | 1 - src/timedate/org.freedesktop.timedate1.conf | 27 - src/timedate/org.freedesktop.timedate1.policy.in | 62 - src/timedate/org.freedesktop.timedate1.service | 12 - src/timedate/timedatectl.c | 514 -- src/timedate/timedated.c | 733 -- src/timesync/.gitignore | 2 - src/timesync/Makefile | 1 - src/timesync/timesyncd-conf.c | 106 - src/timesync/timesyncd-conf.h | 31 - src/timesync/timesyncd-gperf.gperf | 19 - src/timesync/timesyncd-manager.c | 1156 --- src/timesync/timesyncd-manager.h | 104 - src/timesync/timesyncd-server.c | 150 - src/timesync/timesyncd-server.h | 65 - src/timesync/timesyncd.c | 164 - src/timesync/timesyncd.conf.in | 16 - src/tmpfiles/Makefile | 1 - src/tmpfiles/tmpfiles.c | 2364 ------ src/tty-ask-password-agent/Makefile | 1 - .../tty-ask-password-agent.c | 681 -- src/udev/Makefile | 288 +- src/udev/ata_id/Makefile | 36 +- src/udev/cdrom_id/Makefile | 39 +- src/udev/collect/Makefile | 36 +- src/udev/mtd_probe/Makefile | 38 +- src/udev/net/Makefile | 1 - src/udev/net/link-config.c | 2 +- src/udev/scsi_id/Makefile | 42 +- src/udev/udev-builtin-blkid.c | 2 +- src/udev/udev-builtin-hwdb.c | 2 +- src/udev/udev-builtin-uaccess.c | 2 +- src/udev/udev.h | 2 +- src/udev/udevd.c | 4 +- src/udev/v4l_id/Makefile | 39 +- src/update-done/Makefile | 1 - src/update-done/update-done.c | 115 - src/update-utmp/Makefile | 1 - src/update-utmp/update-utmp.c | 283 - src/user-sessions/Makefile | 1 - src/user-sessions/user-sessions.c | 84 - src/vconsole/.gitignore | 1 - src/vconsole/90-vconsole.rules.in | 10 - src/vconsole/Makefile | 1 - src/vconsole/vconsole-setup.c | 326 - 1724 files changed, 260805 insertions(+), 250327 deletions(-) delete mode 100644 src/Makefile mode change 120000 => 100644 src/ac-power/Makefile delete mode 120000 src/activate/Makefile delete mode 100644 src/activate/activate.c delete mode 100644 src/analyze/.gitignore delete mode 120000 src/analyze/Makefile delete mode 100644 src/analyze/analyze-verify.c delete mode 100644 src/analyze/analyze-verify.h delete mode 100644 src/analyze/analyze.c delete mode 120000 src/ask-password/Makefile delete mode 100644 src/ask-password/ask-password.c delete mode 120000 src/backlight/Makefile delete mode 100644 src/backlight/backlight.c delete mode 100644 src/basic/.gitignore delete mode 120000 src/basic/Makefile delete mode 100644 src/basic/MurmurHash2.c delete mode 100644 src/basic/MurmurHash2.h delete mode 100644 src/basic/af-list.c delete mode 100644 src/basic/af-list.h delete mode 100644 src/basic/alloc-util.c delete mode 100644 src/basic/alloc-util.h delete mode 100644 src/basic/arphrd-list.c delete mode 100644 src/basic/arphrd-list.h delete mode 100644 src/basic/async.c delete mode 100644 src/basic/async.h delete mode 100644 src/basic/audit-util.c delete mode 100644 src/basic/audit-util.h delete mode 100644 src/basic/barrier.c delete mode 100644 src/basic/barrier.h delete mode 100644 src/basic/bitmap.c delete mode 100644 src/basic/bitmap.h delete mode 100644 src/basic/blkid-util.h delete mode 100644 src/basic/btrfs-ctree.h delete mode 100644 src/basic/btrfs-util.c delete mode 100644 src/basic/btrfs-util.h delete mode 100644 src/basic/build.h delete mode 100644 src/basic/bus-label.c delete mode 100644 src/basic/bus-label.h delete mode 100644 src/basic/c-rbtree.c delete mode 100644 src/basic/c-rbtree.h delete mode 100644 src/basic/calendarspec.c delete mode 100644 src/basic/calendarspec.h delete mode 100644 src/basic/cap-list.c delete mode 100644 src/basic/cap-list.h delete mode 100644 src/basic/capability-util.c delete mode 100644 src/basic/capability-util.h delete mode 100644 src/basic/cgroup-util.c delete mode 100644 src/basic/cgroup-util.h delete mode 100644 src/basic/chattr-util.c delete mode 100644 src/basic/chattr-util.h delete mode 100644 src/basic/clock-util.c delete mode 100644 src/basic/clock-util.h delete mode 100644 src/basic/conf-files.c delete mode 100644 src/basic/conf-files.h delete mode 100644 src/basic/copy.c delete mode 100644 src/basic/copy.h delete mode 100644 src/basic/cpu-set-util.c delete mode 100644 src/basic/cpu-set-util.h delete mode 100644 src/basic/def.h delete mode 100644 src/basic/device-nodes.c delete mode 100644 src/basic/device-nodes.h delete mode 100644 src/basic/dirent-util.c delete mode 100644 src/basic/dirent-util.h delete mode 100644 src/basic/env-util.c delete mode 100644 src/basic/env-util.h delete mode 100644 src/basic/errno-list.c delete mode 100644 src/basic/errno-list.h delete mode 100644 src/basic/escape.c delete mode 100644 src/basic/escape.h delete mode 100644 src/basic/ether-addr-util.c delete mode 100644 src/basic/ether-addr-util.h delete mode 100644 src/basic/exit-status.c delete mode 100644 src/basic/exit-status.h delete mode 100644 src/basic/extract-word.c delete mode 100644 src/basic/extract-word.h delete mode 100644 src/basic/fd-util.c delete mode 100644 src/basic/fd-util.h delete mode 100644 src/basic/fdset.c delete mode 100644 src/basic/fdset.h delete mode 100644 src/basic/fileio-label.c delete mode 100644 src/basic/fileio-label.h delete mode 100644 src/basic/fileio.c delete mode 100644 src/basic/fileio.h delete mode 100644 src/basic/formats-util.h delete mode 100644 src/basic/fs-util.c delete mode 100644 src/basic/fs-util.h delete mode 100644 src/basic/glob-util.c delete mode 100644 src/basic/glob-util.h delete mode 100644 src/basic/gunicode.c delete mode 100644 src/basic/gunicode.h delete mode 100644 src/basic/hash-funcs.c delete mode 100644 src/basic/hash-funcs.h delete mode 100644 src/basic/hashmap.c delete mode 100644 src/basic/hashmap.h delete mode 100644 src/basic/hexdecoct.c delete mode 100644 src/basic/hexdecoct.h delete mode 100644 src/basic/hostname-util.c delete mode 100644 src/basic/hostname-util.h delete mode 100644 src/basic/in-addr-util.c delete mode 100644 src/basic/in-addr-util.h delete mode 100644 src/basic/io-util.c delete mode 100644 src/basic/io-util.h delete mode 100644 src/basic/ioprio.h delete mode 100644 src/basic/json.c delete mode 100644 src/basic/json.h delete mode 100644 src/basic/label.c delete mode 100644 src/basic/label.h delete mode 120000 src/basic/linux/Makefile delete mode 100644 src/basic/list.h delete mode 100644 src/basic/locale-util.c delete mode 100644 src/basic/locale-util.h delete mode 100644 src/basic/lockfile-util.c delete mode 100644 src/basic/lockfile-util.h delete mode 100644 src/basic/log.c delete mode 100644 src/basic/log.h delete mode 100644 src/basic/login-util.c delete mode 100644 src/basic/login-util.h delete mode 100644 src/basic/macro.h delete mode 100644 src/basic/memfd-util.c delete mode 100644 src/basic/memfd-util.h delete mode 100644 src/basic/mempool.c delete mode 100644 src/basic/mempool.h delete mode 100644 src/basic/missing.h delete mode 100644 src/basic/mkdir-label.c delete mode 100644 src/basic/mkdir.c delete mode 100644 src/basic/mkdir.h delete mode 100644 src/basic/mount-util.c delete mode 100644 src/basic/mount-util.h delete mode 100644 src/basic/nss-util.h delete mode 100644 src/basic/ordered-set.c delete mode 100644 src/basic/ordered-set.h delete mode 100644 src/basic/parse-util.c delete mode 100644 src/basic/parse-util.h delete mode 100644 src/basic/path-util.c delete mode 100644 src/basic/path-util.h delete mode 100644 src/basic/prioq.c delete mode 100644 src/basic/prioq.h delete mode 100644 src/basic/proc-cmdline.c delete mode 100644 src/basic/proc-cmdline.h delete mode 100644 src/basic/process-util.c delete mode 100644 src/basic/process-util.h delete mode 100644 src/basic/random-util.c delete mode 100644 src/basic/random-util.h delete mode 100644 src/basic/ratelimit.c delete mode 100644 src/basic/ratelimit.h delete mode 100644 src/basic/refcnt.h delete mode 100644 src/basic/replace-var.c delete mode 100644 src/basic/replace-var.h delete mode 100644 src/basic/rlimit-util.c delete mode 100644 src/basic/rlimit-util.h delete mode 100644 src/basic/rm-rf.c delete mode 100644 src/basic/rm-rf.h delete mode 100644 src/basic/securebits.h delete mode 100644 src/basic/selinux-util.c delete mode 100644 src/basic/selinux-util.h delete mode 100644 src/basic/set.h delete mode 100644 src/basic/sigbus.c delete mode 100644 src/basic/sigbus.h delete mode 100644 src/basic/signal-util.c delete mode 100644 src/basic/signal-util.h delete mode 100644 src/basic/siphash24.c delete mode 100644 src/basic/siphash24.h delete mode 100644 src/basic/smack-util.c delete mode 100644 src/basic/smack-util.h delete mode 100644 src/basic/socket-label.c delete mode 100644 src/basic/socket-util.c delete mode 100644 src/basic/socket-util.h delete mode 100644 src/basic/sparse-endian.h delete mode 100644 src/basic/special.h delete mode 100644 src/basic/stat-util.c delete mode 100644 src/basic/stat-util.h delete mode 100644 src/basic/stdio-util.h delete mode 100644 src/basic/strbuf.c delete mode 100644 src/basic/strbuf.h delete mode 100644 src/basic/string-table.c delete mode 100644 src/basic/string-table.h delete mode 100644 src/basic/string-util.c delete mode 100644 src/basic/string-util.h delete mode 100644 src/basic/strv.c delete mode 100644 src/basic/strv.h delete mode 100644 src/basic/strxcpyx.c delete mode 100644 src/basic/strxcpyx.h delete mode 100644 src/basic/syslog-util.c delete mode 100644 src/basic/syslog-util.h delete mode 100644 src/basic/terminal-util.c delete mode 100644 src/basic/terminal-util.h delete mode 100644 src/basic/time-util.c delete mode 100644 src/basic/time-util.h delete mode 100644 src/basic/umask-util.h delete mode 100644 src/basic/unaligned.h delete mode 100644 src/basic/unit-name.c delete mode 100644 src/basic/unit-name.h delete mode 100644 src/basic/user-util.c delete mode 100644 src/basic/user-util.h delete mode 100644 src/basic/utf8.c delete mode 100644 src/basic/utf8.h delete mode 100644 src/basic/util.c delete mode 100644 src/basic/util.h delete mode 100644 src/basic/verbs.c delete mode 100644 src/basic/verbs.h delete mode 100644 src/basic/virt.c delete mode 100644 src/basic/virt.h delete mode 100644 src/basic/web-util.c delete mode 100644 src/basic/web-util.h delete mode 100644 src/basic/xattr-util.c delete mode 100644 src/basic/xattr-util.h delete mode 100644 src/basic/xml.c delete mode 100644 src/basic/xml.h delete mode 120000 src/binfmt/Makefile delete mode 100644 src/binfmt/binfmt.c mode change 120000 => 100644 src/boot/Makefile create mode 100644 src/boot/efi/Makefile delete mode 120000 src/bootchart/Makefile delete mode 100644 src/bootchart/bootchart.c delete mode 100644 src/bootchart/bootchart.conf delete mode 100644 src/bootchart/bootchart.h delete mode 100644 src/bootchart/store.c delete mode 100644 src/bootchart/store.h delete mode 100644 src/bootchart/svg.c delete mode 100644 src/bootchart/svg.h mode change 120000 => 100644 src/bus-proxyd/Makefile delete mode 120000 src/cgls/Makefile delete mode 100644 src/cgls/cgls.c delete mode 120000 src/cgroups-agent/Makefile delete mode 100644 src/cgroups-agent/cgroups-agent.c delete mode 120000 src/cgtop/Makefile delete mode 100644 src/cgtop/cgtop.c delete mode 100644 src/compat-libs/.gitignore delete mode 100644 src/compat-libs/libsystemd-daemon.pc.in delete mode 100644 src/compat-libs/libsystemd-daemon.sym delete mode 100644 src/compat-libs/libsystemd-id128.pc.in delete mode 100644 src/compat-libs/libsystemd-id128.sym delete mode 100644 src/compat-libs/libsystemd-journal.pc.in delete mode 100644 src/compat-libs/libsystemd-journal.sym delete mode 100644 src/compat-libs/libsystemd-login.pc.in delete mode 100644 src/compat-libs/libsystemd-login.sym delete mode 100644 src/compat-libs/linkwarning.h delete mode 100644 src/core/.gitignore delete mode 120000 src/core/Makefile delete mode 100644 src/core/audit-fd.c delete mode 100644 src/core/audit-fd.h delete mode 100644 src/core/automount.c delete mode 100644 src/core/automount.h delete mode 100644 src/core/bus-endpoint.c delete mode 100644 src/core/bus-endpoint.h delete mode 100644 src/core/bus-policy.c delete mode 100644 src/core/bus-policy.h delete mode 100644 src/core/busname.c delete mode 100644 src/core/busname.h delete mode 100644 src/core/cgroup.c delete mode 100644 src/core/cgroup.h delete mode 100644 src/core/dbus-automount.c delete mode 100644 src/core/dbus-automount.h delete mode 100644 src/core/dbus-busname.c delete mode 100644 src/core/dbus-busname.h delete mode 100644 src/core/dbus-cgroup.c delete mode 100644 src/core/dbus-cgroup.h delete mode 100644 src/core/dbus-device.c delete mode 100644 src/core/dbus-device.h delete mode 100644 src/core/dbus-execute.c delete mode 100644 src/core/dbus-execute.h delete mode 100644 src/core/dbus-job.c delete mode 100644 src/core/dbus-job.h delete mode 100644 src/core/dbus-kill.c delete mode 100644 src/core/dbus-kill.h delete mode 100644 src/core/dbus-manager.c delete mode 100644 src/core/dbus-manager.h delete mode 100644 src/core/dbus-mount.c delete mode 100644 src/core/dbus-mount.h delete mode 100644 src/core/dbus-path.c delete mode 100644 src/core/dbus-path.h delete mode 100644 src/core/dbus-scope.c delete mode 100644 src/core/dbus-scope.h delete mode 100644 src/core/dbus-service.c delete mode 100644 src/core/dbus-service.h delete mode 100644 src/core/dbus-slice.c delete mode 100644 src/core/dbus-slice.h delete mode 100644 src/core/dbus-socket.c delete mode 100644 src/core/dbus-socket.h delete mode 100644 src/core/dbus-swap.c delete mode 100644 src/core/dbus-swap.h delete mode 100644 src/core/dbus-target.c delete mode 100644 src/core/dbus-target.h delete mode 100644 src/core/dbus-timer.c delete mode 100644 src/core/dbus-timer.h delete mode 100644 src/core/dbus-unit.c delete mode 100644 src/core/dbus-unit.h delete mode 100644 src/core/dbus.c delete mode 100644 src/core/dbus.h delete mode 100644 src/core/device.c delete mode 100644 src/core/device.h delete mode 100644 src/core/execute.c delete mode 100644 src/core/execute.h delete mode 100644 src/core/failure-action.c delete mode 100644 src/core/failure-action.h delete mode 100644 src/core/hostname-setup.c delete mode 100644 src/core/hostname-setup.h delete mode 100644 src/core/ima-setup.c delete mode 100644 src/core/ima-setup.h delete mode 100644 src/core/job.c delete mode 100644 src/core/job.h delete mode 100644 src/core/kill.c delete mode 100644 src/core/kill.h delete mode 100644 src/core/killall.c delete mode 100644 src/core/killall.h delete mode 100644 src/core/kmod-setup.c delete mode 100644 src/core/kmod-setup.h delete mode 100644 src/core/load-dropin.c delete mode 100644 src/core/load-dropin.h delete mode 100644 src/core/load-fragment-gperf.gperf.m4 delete mode 100644 src/core/load-fragment.c delete mode 100644 src/core/load-fragment.h delete mode 100644 src/core/locale-setup.c delete mode 100644 src/core/locale-setup.h delete mode 100644 src/core/loopback-setup.c delete mode 100644 src/core/loopback-setup.h delete mode 100644 src/core/machine-id-setup.c delete mode 100644 src/core/machine-id-setup.h delete mode 100644 src/core/macros.systemd.in delete mode 100644 src/core/main.c delete mode 100644 src/core/manager.c delete mode 100644 src/core/manager.h delete mode 100644 src/core/mount-setup.c delete mode 100644 src/core/mount-setup.h delete mode 100644 src/core/mount.c delete mode 100644 src/core/mount.h delete mode 100644 src/core/namespace.c delete mode 100644 src/core/namespace.h delete mode 100644 src/core/org.freedesktop.systemd1.conf delete mode 100644 src/core/org.freedesktop.systemd1.policy.in.in delete mode 100644 src/core/org.freedesktop.systemd1.service delete mode 100644 src/core/path.c delete mode 100644 src/core/path.h delete mode 100644 src/core/scope.c delete mode 100644 src/core/scope.h delete mode 100644 src/core/selinux-access.c delete mode 100644 src/core/selinux-access.h delete mode 100644 src/core/selinux-setup.c delete mode 100644 src/core/selinux-setup.h delete mode 100644 src/core/service.c delete mode 100644 src/core/service.h delete mode 100644 src/core/show-status.c delete mode 100644 src/core/show-status.h delete mode 100644 src/core/shutdown.c delete mode 100644 src/core/slice.c delete mode 100644 src/core/slice.h delete mode 100644 src/core/smack-setup.c delete mode 100644 src/core/smack-setup.h delete mode 100644 src/core/socket.c delete mode 100644 src/core/socket.h delete mode 100644 src/core/swap.c delete mode 100644 src/core/swap.h delete mode 100644 src/core/system.conf delete mode 100644 src/core/systemd.pc.in delete mode 100644 src/core/target.c delete mode 100644 src/core/target.h delete mode 100644 src/core/timer.c delete mode 100644 src/core/timer.h delete mode 100644 src/core/transaction.c delete mode 100644 src/core/transaction.h delete mode 100644 src/core/triggers.systemd.in delete mode 100644 src/core/umount.c delete mode 100644 src/core/umount.h delete mode 100644 src/core/unit-printf.c delete mode 100644 src/core/unit-printf.h delete mode 100644 src/core/unit.c delete mode 100644 src/core/unit.h delete mode 100644 src/core/user.conf mode change 120000 => 100644 src/coredump/Makefile delete mode 120000 src/cryptsetup/Makefile delete mode 100644 src/cryptsetup/cryptsetup-generator.c delete mode 100644 src/cryptsetup/cryptsetup.c mode change 120000 => 100644 src/dbus1-generator/Makefile mode change 120000 => 100644 src/debug-generator/Makefile delete mode 120000 src/delta/Makefile delete mode 100644 src/delta/delta.c mode change 120000 => 100644 src/detect-virt/Makefile delete mode 120000 src/escape/Makefile delete mode 100644 src/escape/escape.c mode change 120000 => 100644 src/firstboot/Makefile mode change 120000 => 100644 src/fsck/Makefile mode change 120000 => 100644 src/fstab-generator/Makefile mode change 120000 => 100644 src/getty-generator/Makefile mode change 120000 => 100644 src/gpt-auto-generator/Makefile mode change 120000 => 100644 src/hibernate-resume/Makefile mode change 120000 => 100644 src/hostname/Makefile delete mode 120000 src/hwdb/Makefile mode change 120000 => 100644 src/import/Makefile mode change 120000 => 100644 src/initctl/Makefile mode change 120000 => 100644 src/journal-remote/Makefile mode change 120000 => 100644 src/journal/Makefile create mode 100644 src/journal/catalog/systemd.be.catalog create mode 100644 src/journal/catalog/systemd.be@latin.catalog create mode 100644 src/journal/catalog/systemd.catalog create mode 100644 src/journal/catalog/systemd.da.catalog create mode 100644 src/journal/catalog/systemd.fr.catalog create mode 100644 src/journal/catalog/systemd.hu.catalog create mode 100644 src/journal/catalog/systemd.it.catalog create mode 100644 src/journal/catalog/systemd.ko.catalog create mode 100644 src/journal/catalog/systemd.pl.catalog create mode 100644 src/journal/catalog/systemd.pt_BR.catalog create mode 100644 src/journal/catalog/systemd.ru.catalog create mode 100644 src/journal/catalog/systemd.sr.catalog create mode 100644 src/journal/catalog/systemd.zh_CN.catalog create mode 100644 src/journal/catalog/systemd.zh_TW.catalog mode change 120000 => 100644 src/kernel-install/Makefile create mode 100644 src/kernel-install/bash-completion_kernel-install create mode 100644 src/kernel-install/kernel-install.xml create mode 100644 src/kernel-install/zsh-completion_kernel-install create mode 100644 src/libbasic/.gitignore create mode 100644 src/libbasic/Makefile create mode 100644 src/libbasic/MurmurHash2.c create mode 100644 src/libbasic/MurmurHash2.h create mode 100644 src/libbasic/af-list.c create mode 100644 src/libbasic/af-list.h create mode 100644 src/libbasic/alloc-util.c create mode 100644 src/libbasic/alloc-util.h create mode 100644 src/libbasic/arphrd-list.c create mode 100644 src/libbasic/arphrd-list.h create mode 100644 src/libbasic/async.c create mode 100644 src/libbasic/async.h create mode 100644 src/libbasic/audit-util.c create mode 100644 src/libbasic/audit-util.h create mode 100644 src/libbasic/barrier.c create mode 100644 src/libbasic/barrier.h create mode 100644 src/libbasic/bitmap.c create mode 100644 src/libbasic/bitmap.h create mode 100644 src/libbasic/blkid-util.h create mode 100644 src/libbasic/btrfs-ctree.h create mode 100644 src/libbasic/btrfs-util.c create mode 100644 src/libbasic/btrfs-util.h create mode 100644 src/libbasic/build.h create mode 100644 src/libbasic/bus-label.c create mode 100644 src/libbasic/bus-label.h create mode 100644 src/libbasic/c-rbtree.c create mode 100644 src/libbasic/c-rbtree.h create mode 100644 src/libbasic/calendarspec.c create mode 100644 src/libbasic/calendarspec.h create mode 100644 src/libbasic/cap-list.c create mode 100644 src/libbasic/cap-list.h create mode 100644 src/libbasic/capability-util.c create mode 100644 src/libbasic/capability-util.h create mode 100644 src/libbasic/cgroup-util.c create mode 100644 src/libbasic/cgroup-util.h create mode 100644 src/libbasic/chattr-util.c create mode 100644 src/libbasic/chattr-util.h create mode 100644 src/libbasic/clock-util.c create mode 100644 src/libbasic/clock-util.h create mode 100644 src/libbasic/conf-files.c create mode 100644 src/libbasic/conf-files.h create mode 100644 src/libbasic/copy.c create mode 100644 src/libbasic/copy.h create mode 100644 src/libbasic/cpu-set-util.c create mode 100644 src/libbasic/cpu-set-util.h create mode 100644 src/libbasic/def.h create mode 100644 src/libbasic/device-nodes.c create mode 100644 src/libbasic/device-nodes.h create mode 100644 src/libbasic/dirent-util.c create mode 100644 src/libbasic/dirent-util.h create mode 100644 src/libbasic/env-util.c create mode 100644 src/libbasic/env-util.h create mode 100644 src/libbasic/errno-list.c create mode 100644 src/libbasic/errno-list.h create mode 100644 src/libbasic/escape.c create mode 100644 src/libbasic/escape.h create mode 100644 src/libbasic/ether-addr-util.c create mode 100644 src/libbasic/ether-addr-util.h create mode 100644 src/libbasic/exit-status.c create mode 100644 src/libbasic/exit-status.h create mode 100644 src/libbasic/extract-word.c create mode 100644 src/libbasic/extract-word.h create mode 100644 src/libbasic/fd-util.c create mode 100644 src/libbasic/fd-util.h create mode 100644 src/libbasic/fdset.c create mode 100644 src/libbasic/fdset.h create mode 100644 src/libbasic/fileio-label.c create mode 100644 src/libbasic/fileio-label.h create mode 100644 src/libbasic/fileio.c create mode 100644 src/libbasic/fileio.h create mode 100644 src/libbasic/formats-util.h create mode 100644 src/libbasic/fs-util.c create mode 100644 src/libbasic/fs-util.h create mode 100644 src/libbasic/glob-util.c create mode 100644 src/libbasic/glob-util.h create mode 100644 src/libbasic/gunicode.c create mode 100644 src/libbasic/gunicode.h create mode 100644 src/libbasic/hash-funcs.c create mode 100644 src/libbasic/hash-funcs.h create mode 100644 src/libbasic/hashmap.c create mode 100644 src/libbasic/hashmap.h create mode 100644 src/libbasic/hexdecoct.c create mode 100644 src/libbasic/hexdecoct.h create mode 100644 src/libbasic/hostname-util.c create mode 100644 src/libbasic/hostname-util.h create mode 100644 src/libbasic/in-addr-util.c create mode 100644 src/libbasic/in-addr-util.h create mode 100644 src/libbasic/io-util.c create mode 100644 src/libbasic/io-util.h create mode 100644 src/libbasic/ioprio.h create mode 100644 src/libbasic/json.c create mode 100644 src/libbasic/json.h create mode 100644 src/libbasic/label.c create mode 100644 src/libbasic/label.h create mode 100644 src/libbasic/list.h create mode 100644 src/libbasic/locale-util.c create mode 100644 src/libbasic/locale-util.h create mode 100644 src/libbasic/lockfile-util.c create mode 100644 src/libbasic/lockfile-util.h create mode 100644 src/libbasic/log.c create mode 100644 src/libbasic/log.h create mode 100644 src/libbasic/login-util.c create mode 100644 src/libbasic/login-util.h create mode 100644 src/libbasic/macro.h create mode 100644 src/libbasic/memfd-util.c create mode 100644 src/libbasic/memfd-util.h create mode 100644 src/libbasic/mempool.c create mode 100644 src/libbasic/mempool.h create mode 100644 src/libbasic/missing.h create mode 100644 src/libbasic/mkdir-label.c create mode 100644 src/libbasic/mkdir.c create mode 100644 src/libbasic/mkdir.h create mode 100644 src/libbasic/mount-util.c create mode 100644 src/libbasic/mount-util.h create mode 100644 src/libbasic/nss-util.h create mode 100644 src/libbasic/ordered-set.c create mode 100644 src/libbasic/ordered-set.h create mode 100644 src/libbasic/parse-util.c create mode 100644 src/libbasic/parse-util.h create mode 100644 src/libbasic/path-util.c create mode 100644 src/libbasic/path-util.h create mode 100644 src/libbasic/prioq.c create mode 100644 src/libbasic/prioq.h create mode 100644 src/libbasic/proc-cmdline.c create mode 100644 src/libbasic/proc-cmdline.h create mode 100644 src/libbasic/process-util.c create mode 100644 src/libbasic/process-util.h create mode 100644 src/libbasic/random-util.c create mode 100644 src/libbasic/random-util.h create mode 100644 src/libbasic/ratelimit.c create mode 100644 src/libbasic/ratelimit.h create mode 100644 src/libbasic/refcnt.h create mode 100644 src/libbasic/replace-var.c create mode 100644 src/libbasic/replace-var.h create mode 100644 src/libbasic/rlimit-util.c create mode 100644 src/libbasic/rlimit-util.h create mode 100644 src/libbasic/rm-rf.c create mode 100644 src/libbasic/rm-rf.h create mode 100644 src/libbasic/securebits.h create mode 100644 src/libbasic/selinux-util.c create mode 100644 src/libbasic/selinux-util.h create mode 100644 src/libbasic/set.h create mode 100644 src/libbasic/sigbus.c create mode 100644 src/libbasic/sigbus.h create mode 100644 src/libbasic/signal-util.c create mode 100644 src/libbasic/signal-util.h create mode 100644 src/libbasic/siphash24.c create mode 100644 src/libbasic/siphash24.h create mode 100644 src/libbasic/smack-util.c create mode 100644 src/libbasic/smack-util.h create mode 100644 src/libbasic/socket-label.c create mode 100644 src/libbasic/socket-util.c create mode 100644 src/libbasic/socket-util.h create mode 100644 src/libbasic/sparse-endian.h create mode 100644 src/libbasic/special.h create mode 100644 src/libbasic/stat-util.c create mode 100644 src/libbasic/stat-util.h create mode 100644 src/libbasic/stdio-util.h create mode 100644 src/libbasic/strbuf.c create mode 100644 src/libbasic/strbuf.h create mode 100644 src/libbasic/string-table.c create mode 100644 src/libbasic/string-table.h create mode 100644 src/libbasic/string-util.c create mode 100644 src/libbasic/string-util.h create mode 100644 src/libbasic/strv.c create mode 100644 src/libbasic/strv.h create mode 100644 src/libbasic/strxcpyx.c create mode 100644 src/libbasic/strxcpyx.h create mode 100644 src/libbasic/syslog-util.c create mode 100644 src/libbasic/syslog-util.h create mode 100644 src/libbasic/terminal-util.c create mode 100644 src/libbasic/terminal-util.h create mode 100644 src/libbasic/time-util.c create mode 100644 src/libbasic/time-util.h create mode 100644 src/libbasic/umask-util.h create mode 100644 src/libbasic/unaligned.h create mode 100644 src/libbasic/unit-name.c create mode 100644 src/libbasic/unit-name.h create mode 100644 src/libbasic/user-util.c create mode 100644 src/libbasic/user-util.h create mode 100644 src/libbasic/utf8.c create mode 100644 src/libbasic/utf8.h create mode 100644 src/libbasic/util.c create mode 100644 src/libbasic/util.h create mode 100644 src/libbasic/verbs.c create mode 100644 src/libbasic/verbs.h create mode 100644 src/libbasic/virt.c create mode 100644 src/libbasic/virt.h create mode 100644 src/libbasic/web-util.c create mode 100644 src/libbasic/web-util.h create mode 100644 src/libbasic/xattr-util.c create mode 100644 src/libbasic/xattr-util.h create mode 100644 src/libbasic/xml.c create mode 100644 src/libbasic/xml.h create mode 100644 src/libcore/.gitignore create mode 100644 src/libcore/Makefile create mode 100644 src/libcore/audit-fd.c create mode 100644 src/libcore/audit-fd.h create mode 100644 src/libcore/automount.c create mode 100644 src/libcore/automount.h create mode 100644 src/libcore/bus-endpoint.c create mode 100644 src/libcore/bus-endpoint.h create mode 100644 src/libcore/bus-policy.c create mode 100644 src/libcore/bus-policy.h create mode 100644 src/libcore/busname.c create mode 100644 src/libcore/busname.h create mode 100644 src/libcore/cgroup.c create mode 100644 src/libcore/cgroup.h create mode 100644 src/libcore/dbus-automount.c create mode 100644 src/libcore/dbus-automount.h create mode 100644 src/libcore/dbus-busname.c create mode 100644 src/libcore/dbus-busname.h create mode 100644 src/libcore/dbus-cgroup.c create mode 100644 src/libcore/dbus-cgroup.h create mode 100644 src/libcore/dbus-device.c create mode 100644 src/libcore/dbus-device.h create mode 100644 src/libcore/dbus-execute.c create mode 100644 src/libcore/dbus-execute.h create mode 100644 src/libcore/dbus-job.c create mode 100644 src/libcore/dbus-job.h create mode 100644 src/libcore/dbus-kill.c create mode 100644 src/libcore/dbus-kill.h create mode 100644 src/libcore/dbus-manager.c create mode 100644 src/libcore/dbus-manager.h create mode 100644 src/libcore/dbus-mount.c create mode 100644 src/libcore/dbus-mount.h create mode 100644 src/libcore/dbus-path.c create mode 100644 src/libcore/dbus-path.h create mode 100644 src/libcore/dbus-scope.c create mode 100644 src/libcore/dbus-scope.h create mode 100644 src/libcore/dbus-service.c create mode 100644 src/libcore/dbus-service.h create mode 100644 src/libcore/dbus-slice.c create mode 100644 src/libcore/dbus-slice.h create mode 100644 src/libcore/dbus-socket.c create mode 100644 src/libcore/dbus-socket.h create mode 100644 src/libcore/dbus-swap.c create mode 100644 src/libcore/dbus-swap.h create mode 100644 src/libcore/dbus-target.c create mode 100644 src/libcore/dbus-target.h create mode 100644 src/libcore/dbus-timer.c create mode 100644 src/libcore/dbus-timer.h create mode 100644 src/libcore/dbus-unit.c create mode 100644 src/libcore/dbus-unit.h create mode 100644 src/libcore/dbus.c create mode 100644 src/libcore/dbus.h create mode 100644 src/libcore/device.c create mode 100644 src/libcore/device.h create mode 100644 src/libcore/execute.c create mode 100644 src/libcore/execute.h create mode 100644 src/libcore/failure-action.c create mode 100644 src/libcore/failure-action.h create mode 100644 src/libcore/hostname-setup.c create mode 100644 src/libcore/hostname-setup.h create mode 100644 src/libcore/ima-setup.c create mode 100644 src/libcore/ima-setup.h create mode 100644 src/libcore/job.c create mode 100644 src/libcore/job.h create mode 100644 src/libcore/kill.c create mode 100644 src/libcore/kill.h create mode 100644 src/libcore/killall.c create mode 100644 src/libcore/killall.h create mode 100644 src/libcore/kmod-setup.c create mode 100644 src/libcore/kmod-setup.h create mode 100644 src/libcore/linux/auto_dev-ioctl.h create mode 100644 src/libcore/load-dropin.c create mode 100644 src/libcore/load-dropin.h create mode 100644 src/libcore/load-fragment-gperf.gperf.m4 create mode 100644 src/libcore/load-fragment.c create mode 100644 src/libcore/load-fragment.h create mode 100644 src/libcore/locale-setup.c create mode 100644 src/libcore/locale-setup.h create mode 100644 src/libcore/loopback-setup.c create mode 100644 src/libcore/loopback-setup.h create mode 100644 src/libcore/machine-id-setup.c create mode 100644 src/libcore/machine-id-setup.h create mode 100644 src/libcore/manager.c create mode 100644 src/libcore/manager.h create mode 100644 src/libcore/mount-setup.c create mode 100644 src/libcore/mount-setup.h create mode 100644 src/libcore/mount.c create mode 100644 src/libcore/mount.h create mode 100644 src/libcore/namespace.c create mode 100644 src/libcore/namespace.h create mode 100644 src/libcore/path.c create mode 100644 src/libcore/path.h create mode 100644 src/libcore/scope.c create mode 100644 src/libcore/scope.h create mode 100644 src/libcore/selinux-access.c create mode 100644 src/libcore/selinux-access.h create mode 100644 src/libcore/selinux-setup.c create mode 100644 src/libcore/selinux-setup.h create mode 100644 src/libcore/service.c create mode 100644 src/libcore/service.h create mode 100644 src/libcore/show-status.c create mode 100644 src/libcore/show-status.h create mode 100644 src/libcore/shutdown.c create mode 100644 src/libcore/slice.c create mode 100644 src/libcore/slice.h create mode 100644 src/libcore/smack-setup.c create mode 100644 src/libcore/smack-setup.h create mode 100644 src/libcore/socket.c create mode 100644 src/libcore/socket.h create mode 100644 src/libcore/swap.c create mode 100644 src/libcore/swap.h create mode 100644 src/libcore/target.c create mode 100644 src/libcore/target.h create mode 100644 src/libcore/timer.c create mode 100644 src/libcore/timer.h create mode 100644 src/libcore/transaction.c create mode 100644 src/libcore/transaction.h create mode 100644 src/libcore/umount.c create mode 100644 src/libcore/umount.h create mode 100644 src/libcore/unit-printf.c create mode 100644 src/libcore/unit-printf.h create mode 100644 src/libcore/unit.c create mode 100644 src/libcore/unit.h create mode 100644 src/libfirewall/Makefile create mode 100644 src/libfirewall/firewall-util.c create mode 100644 src/libfirewall/firewall-util.h create mode 100644 src/libshared/Makefile create mode 100644 src/libshared/acl-util.c create mode 100644 src/libshared/acl-util.h create mode 100644 src/libshared/acpi-fpdt.c create mode 100644 src/libshared/acpi-fpdt.h create mode 100644 src/libshared/apparmor-util.c create mode 100644 src/libshared/apparmor-util.h create mode 100644 src/libshared/architecture.c create mode 100644 src/libshared/architecture.h create mode 100644 src/libshared/ask-password-api.c create mode 100644 src/libshared/ask-password-api.h create mode 100644 src/libshared/base-filesystem.c create mode 100644 src/libshared/base-filesystem.h create mode 100644 src/libshared/boot-timestamps.c create mode 100644 src/libshared/boot-timestamps.h create mode 100644 src/libshared/bus-util.c create mode 100644 src/libshared/bus-util.h create mode 100644 src/libshared/cgroup-show.c create mode 100644 src/libshared/cgroup-show.h create mode 100644 src/libshared/clean-ipc.c create mode 100644 src/libshared/clean-ipc.h create mode 100644 src/libshared/condition.c create mode 100644 src/libshared/condition.h create mode 100644 src/libshared/conf-parser.c create mode 100644 src/libshared/conf-parser.h create mode 100644 src/libshared/dev-setup.c create mode 100644 src/libshared/dev-setup.h create mode 100644 src/libshared/dns-domain.c create mode 100644 src/libshared/dns-domain.h create mode 100644 src/libshared/dropin.c create mode 100644 src/libshared/dropin.h create mode 100644 src/libshared/efivars.c create mode 100644 src/libshared/efivars.h create mode 100644 src/libshared/fstab-util.c create mode 100644 src/libshared/fstab-util.h create mode 100644 src/libshared/generator.c create mode 100644 src/libshared/generator.h create mode 100644 src/libshared/gpt.h create mode 100644 src/libshared/ima-util.c create mode 100644 src/libshared/ima-util.h create mode 100644 src/libshared/import-util.c create mode 100644 src/libshared/import-util.h create mode 100644 src/libshared/initreq.h create mode 100644 src/libshared/install-printf.c create mode 100644 src/libshared/install-printf.h create mode 100644 src/libshared/install.c create mode 100644 src/libshared/install.h create mode 100644 src/libshared/logs-show.c create mode 100644 src/libshared/logs-show.h create mode 100644 src/libshared/machine-image.c create mode 100644 src/libshared/machine-image.h create mode 100644 src/libshared/machine-pool.c create mode 100644 src/libshared/machine-pool.h create mode 100644 src/libshared/output-mode.h create mode 100644 src/libshared/pager.c create mode 100644 src/libshared/pager.h create mode 100644 src/libshared/path-lookup.c create mode 100644 src/libshared/path-lookup.h create mode 100644 src/libshared/ptyfwd.c create mode 100644 src/libshared/ptyfwd.h create mode 100644 src/libshared/resolve-util.c create mode 100644 src/libshared/resolve-util.h create mode 100644 src/libshared/seccomp-util.c create mode 100644 src/libshared/seccomp-util.h create mode 100644 src/libshared/sleep-config.c create mode 100644 src/libshared/sleep-config.h create mode 100644 src/libshared/spawn-ask-password-agent.c create mode 100644 src/libshared/spawn-ask-password-agent.h create mode 100644 src/libshared/spawn-polkit-agent.c create mode 100644 src/libshared/spawn-polkit-agent.h create mode 100644 src/libshared/specifier.c create mode 100644 src/libshared/specifier.h create mode 100644 src/libshared/switch-root.c create mode 100644 src/libshared/switch-root.h create mode 100644 src/libshared/sysctl-util.c create mode 100644 src/libshared/sysctl-util.h create mode 100644 src/libshared/test-tables.h create mode 100644 src/libshared/udev-util.h create mode 100644 src/libshared/uid-range.c create mode 100644 src/libshared/uid-range.h create mode 100644 src/libshared/utmp-wtmp.c create mode 100644 src/libshared/utmp-wtmp.h create mode 100644 src/libshared/watchdog.c create mode 100644 src/libshared/watchdog.h mode change 120000 => 100644 src/libsystemd-network/Makefile mode change 120000 => 100644 src/libsystemd/Makefile create mode 100644 src/libsystemd/compat-libs/.gitignore create mode 100644 src/libsystemd/compat-libs/Makefile create mode 100644 src/libsystemd/compat-libs/libsystemd-daemon.pc.in create mode 100644 src/libsystemd/compat-libs/libsystemd-daemon.sym create mode 100644 src/libsystemd/compat-libs/libsystemd-id128.pc.in create mode 100644 src/libsystemd/compat-libs/libsystemd-id128.sym create mode 100644 src/libsystemd/compat-libs/libsystemd-journal.pc.in create mode 100644 src/libsystemd/compat-libs/libsystemd-journal.sym create mode 100644 src/libsystemd/compat-libs/libsystemd-login.pc.in create mode 100644 src/libsystemd/compat-libs/libsystemd-login.sym create mode 100644 src/libsystemd/compat-libs/linkwarning.h create mode 100644 src/libsystemd/include/systemd/_sd-common.h create mode 100644 src/libsystemd/include/systemd/sd-bus-protocol.h create mode 100644 src/libsystemd/include/systemd/sd-bus-vtable.h create mode 100644 src/libsystemd/include/systemd/sd-bus.h create mode 100644 src/libsystemd/include/systemd/sd-daemon.h create mode 100644 src/libsystemd/include/systemd/sd-device.h create mode 100644 src/libsystemd/include/systemd/sd-dhcp-client.h create mode 100644 src/libsystemd/include/systemd/sd-dhcp-lease.h create mode 100644 src/libsystemd/include/systemd/sd-dhcp-server.h create mode 100644 src/libsystemd/include/systemd/sd-dhcp6-client.h create mode 100644 src/libsystemd/include/systemd/sd-dhcp6-lease.h create mode 100644 src/libsystemd/include/systemd/sd-event.h create mode 100644 src/libsystemd/include/systemd/sd-hwdb.h create mode 100644 src/libsystemd/include/systemd/sd-id128.h create mode 100644 src/libsystemd/include/systemd/sd-ipv4acd.h create mode 100644 src/libsystemd/include/systemd/sd-ipv4ll.h create mode 100644 src/libsystemd/include/systemd/sd-journal.h create mode 100644 src/libsystemd/include/systemd/sd-lldp.h create mode 100644 src/libsystemd/include/systemd/sd-login.h create mode 100644 src/libsystemd/include/systemd/sd-messages.h create mode 100644 src/libsystemd/include/systemd/sd-ndisc.h create mode 100644 src/libsystemd/include/systemd/sd-netlink.h create mode 100644 src/libsystemd/include/systemd/sd-network.h create mode 100644 src/libsystemd/include/systemd/sd-path.h create mode 100644 src/libsystemd/include/systemd/sd-resolve.h create mode 100644 src/libsystemd/include/systemd/sd-utf8.h create mode 100644 src/libsystemd/libsystemd-internal/Makefile create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/DIFFERENCES create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/GVARIANT-SERIALIZATION create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/PORTING-DBUS1 create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-container.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-container.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-control.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-control.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-convenience.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-creds.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-creds.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-dump.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-dump.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-error.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-error.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-internal.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-internal.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-match.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-match.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-message.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-message.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-objects.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-objects.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-protocol.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-signature.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-signature.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-slot.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-slot.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-socket.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-socket.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-track.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-track.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-type.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/bus-type.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/busctl-introspect.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/busctl-introspect.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/busctl.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/kdbus.h create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/sd-bus.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-benchmark.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-chat.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-cleanup.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-creds.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-error.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-gvariant.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-introspect.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel-bloom.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-marshal.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-match.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-objects.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-proxy.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-server.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-signature.c create mode 100644 src/libsystemd/libsystemd-internal/sd-bus/test-bus-zero-copy.c create mode 100644 src/libsystemd/libsystemd-internal/sd-daemon/sd-daemon.c create mode 100644 src/libsystemd/libsystemd-internal/sd-device/device-enumerator-private.h create mode 100644 src/libsystemd/libsystemd-internal/sd-device/device-enumerator.c create mode 100644 src/libsystemd/libsystemd-internal/sd-device/device-internal.h create mode 100644 src/libsystemd/libsystemd-internal/sd-device/device-private.c create mode 100644 src/libsystemd/libsystemd-internal/sd-device/device-private.h create mode 100644 src/libsystemd/libsystemd-internal/sd-device/device-util.h create mode 100644 src/libsystemd/libsystemd-internal/sd-device/sd-device.c create mode 100644 src/libsystemd/libsystemd-internal/sd-event/sd-event.c create mode 100644 src/libsystemd/libsystemd-internal/sd-event/test-event.c create mode 100644 src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-internal.h create mode 100644 src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-util.h create mode 100644 src/libsystemd/libsystemd-internal/sd-hwdb/sd-hwdb.c create mode 100644 src/libsystemd/libsystemd-internal/sd-id128/sd-id128.c create mode 100644 src/libsystemd/libsystemd-internal/sd-login/sd-login.c create mode 100644 src/libsystemd/libsystemd-internal/sd-login/test-login.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/local-addresses.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/local-addresses.h create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-internal.h create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-message.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-socket.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.h create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.h create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/rtnl-message.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/sd-netlink.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/test-local-addresses.c create mode 100644 src/libsystemd/libsystemd-internal/sd-netlink/test-netlink.c create mode 100644 src/libsystemd/libsystemd-internal/sd-network/network-util.c create mode 100644 src/libsystemd/libsystemd-internal/sd-network/network-util.h create mode 100644 src/libsystemd/libsystemd-internal/sd-network/sd-network.c create mode 100644 src/libsystemd/libsystemd-internal/sd-path/sd-path.c create mode 100644 src/libsystemd/libsystemd-internal/sd-resolve/sd-resolve.c create mode 100644 src/libsystemd/libsystemd-internal/sd-resolve/test-resolve.c create mode 100644 src/libsystemd/libsystemd-internal/sd-utf8/sd-utf8.c create mode 100644 src/libsystemd/libsystemd-journal-internal/Makefile delete mode 100644 src/libsystemd/sd-bus/DIFFERENCES delete mode 100644 src/libsystemd/sd-bus/GVARIANT-SERIALIZATION delete mode 120000 src/libsystemd/sd-bus/Makefile delete mode 100644 src/libsystemd/sd-bus/PORTING-DBUS1 delete mode 100644 src/libsystemd/sd-bus/bus-bloom.c delete mode 100644 src/libsystemd/sd-bus/bus-bloom.h delete mode 100644 src/libsystemd/sd-bus/bus-common-errors.c delete mode 100644 src/libsystemd/sd-bus/bus-common-errors.h delete mode 100644 src/libsystemd/sd-bus/bus-container.c delete mode 100644 src/libsystemd/sd-bus/bus-container.h delete mode 100644 src/libsystemd/sd-bus/bus-control.c delete mode 100644 src/libsystemd/sd-bus/bus-control.h delete mode 100644 src/libsystemd/sd-bus/bus-convenience.c delete mode 100644 src/libsystemd/sd-bus/bus-creds.c delete mode 100644 src/libsystemd/sd-bus/bus-creds.h delete mode 100644 src/libsystemd/sd-bus/bus-dump.c delete mode 100644 src/libsystemd/sd-bus/bus-dump.h delete mode 100644 src/libsystemd/sd-bus/bus-error.c delete mode 100644 src/libsystemd/sd-bus/bus-error.h delete mode 100644 src/libsystemd/sd-bus/bus-gvariant.c delete mode 100644 src/libsystemd/sd-bus/bus-gvariant.h delete mode 100644 src/libsystemd/sd-bus/bus-internal.c delete mode 100644 src/libsystemd/sd-bus/bus-internal.h delete mode 100644 src/libsystemd/sd-bus/bus-introspect.c delete mode 100644 src/libsystemd/sd-bus/bus-introspect.h delete mode 100644 src/libsystemd/sd-bus/bus-kernel.c delete mode 100644 src/libsystemd/sd-bus/bus-kernel.h delete mode 100644 src/libsystemd/sd-bus/bus-match.c delete mode 100644 src/libsystemd/sd-bus/bus-match.h delete mode 100644 src/libsystemd/sd-bus/bus-message.c delete mode 100644 src/libsystemd/sd-bus/bus-message.h delete mode 100644 src/libsystemd/sd-bus/bus-objects.c delete mode 100644 src/libsystemd/sd-bus/bus-objects.h delete mode 100644 src/libsystemd/sd-bus/bus-protocol.h delete mode 100644 src/libsystemd/sd-bus/bus-signature.c delete mode 100644 src/libsystemd/sd-bus/bus-signature.h delete mode 100644 src/libsystemd/sd-bus/bus-slot.c delete mode 100644 src/libsystemd/sd-bus/bus-slot.h delete mode 100644 src/libsystemd/sd-bus/bus-socket.c delete mode 100644 src/libsystemd/sd-bus/bus-socket.h delete mode 100644 src/libsystemd/sd-bus/bus-track.c delete mode 100644 src/libsystemd/sd-bus/bus-track.h delete mode 100644 src/libsystemd/sd-bus/bus-type.c delete mode 100644 src/libsystemd/sd-bus/bus-type.h delete mode 100644 src/libsystemd/sd-bus/busctl-introspect.c delete mode 100644 src/libsystemd/sd-bus/busctl-introspect.h delete mode 100644 src/libsystemd/sd-bus/busctl.c delete mode 100644 src/libsystemd/sd-bus/kdbus.h delete mode 100644 src/libsystemd/sd-bus/sd-bus.c delete mode 100644 src/libsystemd/sd-bus/test-bus-benchmark.c delete mode 100644 src/libsystemd/sd-bus/test-bus-chat.c delete mode 100644 src/libsystemd/sd-bus/test-bus-cleanup.c delete mode 100644 src/libsystemd/sd-bus/test-bus-creds.c delete mode 100644 src/libsystemd/sd-bus/test-bus-error.c delete mode 100644 src/libsystemd/sd-bus/test-bus-gvariant.c delete mode 100644 src/libsystemd/sd-bus/test-bus-introspect.c delete mode 100644 src/libsystemd/sd-bus/test-bus-kernel-bloom.c delete mode 100644 src/libsystemd/sd-bus/test-bus-kernel.c delete mode 100644 src/libsystemd/sd-bus/test-bus-marshal.c delete mode 100644 src/libsystemd/sd-bus/test-bus-match.c delete mode 100644 src/libsystemd/sd-bus/test-bus-objects.c delete mode 100644 src/libsystemd/sd-bus/test-bus-proxy.c delete mode 100644 src/libsystemd/sd-bus/test-bus-server.c delete mode 100644 src/libsystemd/sd-bus/test-bus-signature.c delete mode 100644 src/libsystemd/sd-bus/test-bus-zero-copy.c delete mode 120000 src/libsystemd/sd-daemon/Makefile delete mode 100644 src/libsystemd/sd-daemon/sd-daemon.c delete mode 120000 src/libsystemd/sd-device/Makefile delete mode 100644 src/libsystemd/sd-device/device-enumerator-private.h delete mode 100644 src/libsystemd/sd-device/device-enumerator.c delete mode 100644 src/libsystemd/sd-device/device-internal.h delete mode 100644 src/libsystemd/sd-device/device-private.c delete mode 100644 src/libsystemd/sd-device/device-private.h delete mode 100644 src/libsystemd/sd-device/device-util.h delete mode 100644 src/libsystemd/sd-device/sd-device.c delete mode 120000 src/libsystemd/sd-event/Makefile delete mode 100644 src/libsystemd/sd-event/sd-event.c delete mode 100644 src/libsystemd/sd-event/test-event.c delete mode 120000 src/libsystemd/sd-hwdb/Makefile delete mode 100644 src/libsystemd/sd-hwdb/hwdb-internal.h delete mode 100644 src/libsystemd/sd-hwdb/hwdb-util.h delete mode 100644 src/libsystemd/sd-hwdb/sd-hwdb.c delete mode 120000 src/libsystemd/sd-id128/Makefile delete mode 100644 src/libsystemd/sd-id128/sd-id128.c delete mode 120000 src/libsystemd/sd-login/Makefile delete mode 100644 src/libsystemd/sd-login/sd-login.c delete mode 100644 src/libsystemd/sd-login/test-login.c delete mode 120000 src/libsystemd/sd-netlink/Makefile delete mode 100644 src/libsystemd/sd-netlink/local-addresses.c delete mode 100644 src/libsystemd/sd-netlink/local-addresses.h delete mode 100644 src/libsystemd/sd-netlink/netlink-internal.h delete mode 100644 src/libsystemd/sd-netlink/netlink-message.c delete mode 100644 src/libsystemd/sd-netlink/netlink-socket.c delete mode 100644 src/libsystemd/sd-netlink/netlink-types.c delete mode 100644 src/libsystemd/sd-netlink/netlink-types.h delete mode 100644 src/libsystemd/sd-netlink/netlink-util.c delete mode 100644 src/libsystemd/sd-netlink/netlink-util.h delete mode 100644 src/libsystemd/sd-netlink/rtnl-message.c delete mode 100644 src/libsystemd/sd-netlink/sd-netlink.c delete mode 100644 src/libsystemd/sd-netlink/test-local-addresses.c delete mode 100644 src/libsystemd/sd-netlink/test-netlink.c delete mode 120000 src/libsystemd/sd-network/Makefile delete mode 100644 src/libsystemd/sd-network/network-util.c delete mode 100644 src/libsystemd/sd-network/network-util.h delete mode 100644 src/libsystemd/sd-network/sd-network.c delete mode 120000 src/libsystemd/sd-path/Makefile delete mode 100644 src/libsystemd/sd-path/sd-path.c delete mode 120000 src/libsystemd/sd-resolve/Makefile delete mode 100644 src/libsystemd/sd-resolve/sd-resolve.c delete mode 100644 src/libsystemd/sd-resolve/test-resolve.c delete mode 120000 src/libsystemd/sd-utf8/Makefile delete mode 100644 src/libsystemd/sd-utf8/sd-utf8.c delete mode 100644 src/libudev/.gitignore mode change 120000 => 100644 src/libudev/Makefile create mode 100644 src/libudev/include/libudev.h delete mode 100644 src/libudev/libudev-device-internal.h delete mode 100644 src/libudev/libudev-device-private.c delete mode 100644 src/libudev/libudev-device.c delete mode 100644 src/libudev/libudev-enumerate.c delete mode 100644 src/libudev/libudev-hwdb.c delete mode 100644 src/libudev/libudev-list.c delete mode 100644 src/libudev/libudev-monitor.c delete mode 100644 src/libudev/libudev-private.h delete mode 100644 src/libudev/libudev-queue.c delete mode 100644 src/libudev/libudev-util.c delete mode 100644 src/libudev/libudev.c delete mode 100644 src/libudev/libudev.h delete mode 100644 src/libudev/libudev.pc.in delete mode 100644 src/libudev/libudev.sym create mode 100644 src/libudev/src/.gitignore create mode 100644 src/libudev/src/Makefile create mode 100644 src/libudev/src/libudev-device-internal.h create mode 100644 src/libudev/src/libudev-device-private.c create mode 100644 src/libudev/src/libudev-device.c create mode 100644 src/libudev/src/libudev-enumerate.c create mode 100644 src/libudev/src/libudev-hwdb.c create mode 100644 src/libudev/src/libudev-list.c create mode 100644 src/libudev/src/libudev-monitor.c create mode 100644 src/libudev/src/libudev-private.h create mode 100644 src/libudev/src/libudev-queue.c create mode 100644 src/libudev/src/libudev-util.c create mode 100644 src/libudev/src/libudev.c create mode 100644 src/libudev/src/libudev.pc.in create mode 100644 src/libudev/src/libudev.sym mode change 120000 => 100644 src/locale/Makefile mode change 120000 => 100644 src/login/Makefile mode change 120000 => 100644 src/machine-id-setup/Makefile delete mode 100644 src/machine/.gitignore delete mode 120000 src/machine/Makefile delete mode 100644 src/machine/image-dbus.c delete mode 100644 src/machine/image-dbus.h delete mode 100644 src/machine/machine-dbus.c delete mode 100644 src/machine/machine-dbus.h delete mode 100644 src/machine/machine.c delete mode 100644 src/machine/machine.h delete mode 100644 src/machine/machinectl.c delete mode 100644 src/machine/machined-dbus.c delete mode 100644 src/machine/machined.c delete mode 100644 src/machine/machined.h create mode 100644 src/machine/nss-mymachines/nss-mymachines.c create mode 100644 src/machine/nss-mymachines/nss-mymachines.sym delete mode 100644 src/machine/org.freedesktop.machine1.conf delete mode 100644 src/machine/org.freedesktop.machine1.policy.in delete mode 100644 src/machine/org.freedesktop.machine1.service create mode 100644 src/machine/src/.gitignore create mode 100644 src/machine/src/Makefile create mode 100644 src/machine/src/image-dbus.c create mode 100644 src/machine/src/image-dbus.h create mode 100644 src/machine/src/machine-dbus.c create mode 100644 src/machine/src/machine-dbus.h create mode 100644 src/machine/src/machine.c create mode 100644 src/machine/src/machine.h create mode 100644 src/machine/src/machinectl.c create mode 100644 src/machine/src/machined-dbus.c create mode 100644 src/machine/src/machined.c create mode 100644 src/machine/src/machined.h create mode 100644 src/machine/src/org.freedesktop.machine1.conf create mode 100644 src/machine/src/org.freedesktop.machine1.policy.in create mode 100644 src/machine/src/org.freedesktop.machine1.service create mode 100644 src/machine/src/test-machine-tables.c delete mode 100644 src/machine/test-machine-tables.c mode change 120000 => 100644 src/modules-load/Makefile mode change 120000 => 100644 src/network/Makefile delete mode 120000 src/notify/Makefile delete mode 100644 src/notify/notify.c delete mode 100644 src/nspawn/.gitignore delete mode 120000 src/nspawn/Makefile delete mode 100644 src/nspawn/nspawn-cgroup.c delete mode 100644 src/nspawn/nspawn-cgroup.h delete mode 100644 src/nspawn/nspawn-expose-ports.c delete mode 100644 src/nspawn/nspawn-expose-ports.h delete mode 100644 src/nspawn/nspawn-gperf.gperf delete mode 100644 src/nspawn/nspawn-mount.c delete mode 100644 src/nspawn/nspawn-mount.h delete mode 100644 src/nspawn/nspawn-network.c delete mode 100644 src/nspawn/nspawn-network.h delete mode 100644 src/nspawn/nspawn-register.c delete mode 100644 src/nspawn/nspawn-register.h delete mode 100644 src/nspawn/nspawn-settings.c delete mode 100644 src/nspawn/nspawn-settings.h delete mode 100644 src/nspawn/nspawn-setuid.c delete mode 100644 src/nspawn/nspawn-setuid.h delete mode 100644 src/nspawn/nspawn-stub-pid1.c delete mode 100644 src/nspawn/nspawn-stub-pid1.h delete mode 100644 src/nspawn/nspawn.c mode change 120000 => 100644 src/nss-myhostname/Makefile delete mode 120000 src/nss-mymachines/Makefile delete mode 100644 src/nss-mymachines/nss-mymachines.c delete mode 100644 src/nss-mymachines/nss-mymachines.sym delete mode 120000 src/nss-resolve/Makefile delete mode 100644 src/nss-resolve/nss-resolve.c delete mode 100644 src/nss-resolve/nss-resolve.sym delete mode 120000 src/path/Makefile delete mode 100644 src/path/path.c delete mode 120000 src/quotacheck/Makefile delete mode 100644 src/quotacheck/quotacheck.c delete mode 120000 src/random-seed/Makefile delete mode 100644 src/random-seed/random-seed.c mode change 120000 => 100644 src/rc-local-generator/Makefile delete mode 120000 src/remount-fs/Makefile delete mode 100644 src/remount-fs/remount-fs.c delete mode 120000 src/reply-password/Makefile delete mode 100644 src/reply-password/reply-password.c delete mode 100644 src/resolve/.gitignore delete mode 120000 src/resolve/Makefile delete mode 100644 src/resolve/RFCs delete mode 100644 src/resolve/dns-type.c delete mode 100644 src/resolve/dns-type.h create mode 100644 src/resolve/nss-resolve/nss-resolve.c create mode 100644 src/resolve/nss-resolve/nss-resolve.sym delete mode 100644 src/resolve/org.freedesktop.resolve1.conf delete mode 100644 src/resolve/org.freedesktop.resolve1.service delete mode 100644 src/resolve/resolve-tool.c delete mode 100644 src/resolve/resolved-bus.c delete mode 100644 src/resolve/resolved-bus.h delete mode 100644 src/resolve/resolved-conf.c delete mode 100644 src/resolve/resolved-conf.h delete mode 100644 src/resolve/resolved-def.h delete mode 100644 src/resolve/resolved-dns-answer.c delete mode 100644 src/resolve/resolved-dns-answer.h delete mode 100644 src/resolve/resolved-dns-cache.c delete mode 100644 src/resolve/resolved-dns-cache.h delete mode 100644 src/resolve/resolved-dns-dnssec.c delete mode 100644 src/resolve/resolved-dns-dnssec.h delete mode 100644 src/resolve/resolved-dns-packet.c delete mode 100644 src/resolve/resolved-dns-packet.h delete mode 100644 src/resolve/resolved-dns-query.c delete mode 100644 src/resolve/resolved-dns-query.h delete mode 100644 src/resolve/resolved-dns-question.c delete mode 100644 src/resolve/resolved-dns-question.h delete mode 100644 src/resolve/resolved-dns-rr.c delete mode 100644 src/resolve/resolved-dns-rr.h delete mode 100644 src/resolve/resolved-dns-scope.c delete mode 100644 src/resolve/resolved-dns-scope.h delete mode 100644 src/resolve/resolved-dns-search-domain.c delete mode 100644 src/resolve/resolved-dns-search-domain.h delete mode 100644 src/resolve/resolved-dns-server.c delete mode 100644 src/resolve/resolved-dns-server.h delete mode 100644 src/resolve/resolved-dns-stream.c delete mode 100644 src/resolve/resolved-dns-stream.h delete mode 100644 src/resolve/resolved-dns-synthesize.c delete mode 100644 src/resolve/resolved-dns-synthesize.h delete mode 100644 src/resolve/resolved-dns-transaction.c delete mode 100644 src/resolve/resolved-dns-transaction.h delete mode 100644 src/resolve/resolved-dns-trust-anchor.c delete mode 100644 src/resolve/resolved-dns-trust-anchor.h delete mode 100644 src/resolve/resolved-dns-zone.c delete mode 100644 src/resolve/resolved-dns-zone.h delete mode 100644 src/resolve/resolved-etc-hosts.c delete mode 100644 src/resolve/resolved-etc-hosts.h delete mode 100644 src/resolve/resolved-gperf.gperf delete mode 100644 src/resolve/resolved-link-bus.c delete mode 100644 src/resolve/resolved-link-bus.h delete mode 100644 src/resolve/resolved-link.c delete mode 100644 src/resolve/resolved-link.h delete mode 100644 src/resolve/resolved-llmnr.c delete mode 100644 src/resolve/resolved-llmnr.h delete mode 100644 src/resolve/resolved-manager.c delete mode 100644 src/resolve/resolved-manager.h delete mode 100644 src/resolve/resolved-mdns.c delete mode 100644 src/resolve/resolved-mdns.h delete mode 100644 src/resolve/resolved-resolv-conf.c delete mode 100644 src/resolve/resolved-resolv-conf.h delete mode 100644 src/resolve/resolved.c delete mode 100644 src/resolve/resolved.conf.in create mode 100644 src/resolve/src/.gitignore create mode 100644 src/resolve/src/Makefile create mode 100644 src/resolve/src/RFCs create mode 100644 src/resolve/src/dns-type.c create mode 100644 src/resolve/src/dns-type.h create mode 100644 src/resolve/src/org.freedesktop.resolve1.conf create mode 100644 src/resolve/src/org.freedesktop.resolve1.service create mode 100644 src/resolve/src/resolve-tool.c create mode 100644 src/resolve/src/resolved-bus.c create mode 100644 src/resolve/src/resolved-bus.h create mode 100644 src/resolve/src/resolved-conf.c create mode 100644 src/resolve/src/resolved-conf.h create mode 100644 src/resolve/src/resolved-def.h create mode 100644 src/resolve/src/resolved-dns-answer.c create mode 100644 src/resolve/src/resolved-dns-answer.h create mode 100644 src/resolve/src/resolved-dns-cache.c create mode 100644 src/resolve/src/resolved-dns-cache.h create mode 100644 src/resolve/src/resolved-dns-dnssec.c create mode 100644 src/resolve/src/resolved-dns-dnssec.h create mode 100644 src/resolve/src/resolved-dns-packet.c create mode 100644 src/resolve/src/resolved-dns-packet.h create mode 100644 src/resolve/src/resolved-dns-query.c create mode 100644 src/resolve/src/resolved-dns-query.h create mode 100644 src/resolve/src/resolved-dns-question.c create mode 100644 src/resolve/src/resolved-dns-question.h create mode 100644 src/resolve/src/resolved-dns-rr.c create mode 100644 src/resolve/src/resolved-dns-rr.h create mode 100644 src/resolve/src/resolved-dns-scope.c create mode 100644 src/resolve/src/resolved-dns-scope.h create mode 100644 src/resolve/src/resolved-dns-search-domain.c create mode 100644 src/resolve/src/resolved-dns-search-domain.h create mode 100644 src/resolve/src/resolved-dns-server.c create mode 100644 src/resolve/src/resolved-dns-server.h create mode 100644 src/resolve/src/resolved-dns-stream.c create mode 100644 src/resolve/src/resolved-dns-stream.h create mode 100644 src/resolve/src/resolved-dns-synthesize.c create mode 100644 src/resolve/src/resolved-dns-synthesize.h create mode 100644 src/resolve/src/resolved-dns-transaction.c create mode 100644 src/resolve/src/resolved-dns-transaction.h create mode 100644 src/resolve/src/resolved-dns-trust-anchor.c create mode 100644 src/resolve/src/resolved-dns-trust-anchor.h create mode 100644 src/resolve/src/resolved-dns-zone.c create mode 100644 src/resolve/src/resolved-dns-zone.h create mode 100644 src/resolve/src/resolved-etc-hosts.c create mode 100644 src/resolve/src/resolved-etc-hosts.h create mode 100644 src/resolve/src/resolved-gperf.gperf create mode 100644 src/resolve/src/resolved-link-bus.c create mode 100644 src/resolve/src/resolved-link-bus.h create mode 100644 src/resolve/src/resolved-link.c create mode 100644 src/resolve/src/resolved-link.h create mode 100644 src/resolve/src/resolved-llmnr.c create mode 100644 src/resolve/src/resolved-llmnr.h create mode 100644 src/resolve/src/resolved-manager.c create mode 100644 src/resolve/src/resolved-manager.h create mode 100644 src/resolve/src/resolved-mdns.c create mode 100644 src/resolve/src/resolved-mdns.h create mode 100644 src/resolve/src/resolved-resolv-conf.c create mode 100644 src/resolve/src/resolved-resolv-conf.h create mode 100644 src/resolve/src/resolved.c create mode 100644 src/resolve/src/resolved.conf.in create mode 100644 src/resolve/src/test-dnssec-complex.c create mode 100644 src/resolve/src/test-dnssec.c create mode 100644 src/resolve/src/test-resolve-tables.c delete mode 100644 src/resolve/test-dnssec-complex.c delete mode 100644 src/resolve/test-dnssec.c delete mode 100644 src/resolve/test-resolve-tables.c delete mode 120000 src/rfkill/Makefile delete mode 100644 src/rfkill/rfkill.c delete mode 120000 src/run/Makefile delete mode 100644 src/run/run.c delete mode 120000 src/shared/Makefile delete mode 100644 src/shared/acl-util.c delete mode 100644 src/shared/acl-util.h delete mode 100644 src/shared/acpi-fpdt.c delete mode 100644 src/shared/acpi-fpdt.h delete mode 100644 src/shared/apparmor-util.c delete mode 100644 src/shared/apparmor-util.h delete mode 100644 src/shared/architecture.c delete mode 100644 src/shared/architecture.h delete mode 100644 src/shared/ask-password-api.c delete mode 100644 src/shared/ask-password-api.h delete mode 100644 src/shared/base-filesystem.c delete mode 100644 src/shared/base-filesystem.h delete mode 100644 src/shared/boot-timestamps.c delete mode 100644 src/shared/boot-timestamps.h delete mode 100644 src/shared/bus-util.c delete mode 100644 src/shared/bus-util.h delete mode 100644 src/shared/cgroup-show.c delete mode 100644 src/shared/cgroup-show.h delete mode 100644 src/shared/clean-ipc.c delete mode 100644 src/shared/clean-ipc.h delete mode 100644 src/shared/condition.c delete mode 100644 src/shared/condition.h delete mode 100644 src/shared/conf-parser.c delete mode 100644 src/shared/conf-parser.h delete mode 100644 src/shared/dev-setup.c delete mode 100644 src/shared/dev-setup.h delete mode 100644 src/shared/dns-domain.c delete mode 100644 src/shared/dns-domain.h delete mode 100644 src/shared/dropin.c delete mode 100644 src/shared/dropin.h delete mode 100644 src/shared/efivars.c delete mode 100644 src/shared/efivars.h delete mode 100644 src/shared/firewall-util.c delete mode 100644 src/shared/firewall-util.h delete mode 100644 src/shared/fstab-util.c delete mode 100644 src/shared/fstab-util.h delete mode 100644 src/shared/generator.c delete mode 100644 src/shared/generator.h delete mode 100644 src/shared/gpt.h delete mode 100644 src/shared/ima-util.c delete mode 100644 src/shared/ima-util.h delete mode 100644 src/shared/import-util.c delete mode 100644 src/shared/import-util.h delete mode 100644 src/shared/initreq.h delete mode 100644 src/shared/install-printf.c delete mode 100644 src/shared/install-printf.h delete mode 100644 src/shared/install.c delete mode 100644 src/shared/install.h delete mode 100644 src/shared/linux/auto_dev-ioctl.h delete mode 100644 src/shared/logs-show.c delete mode 100644 src/shared/logs-show.h delete mode 100644 src/shared/machine-image.c delete mode 100644 src/shared/machine-image.h delete mode 100644 src/shared/machine-pool.c delete mode 100644 src/shared/machine-pool.h delete mode 100644 src/shared/output-mode.h delete mode 100644 src/shared/pager.c delete mode 100644 src/shared/pager.h delete mode 100644 src/shared/path-lookup.c delete mode 100644 src/shared/path-lookup.h delete mode 100644 src/shared/ptyfwd.c delete mode 100644 src/shared/ptyfwd.h delete mode 100644 src/shared/resolve-util.c delete mode 100644 src/shared/resolve-util.h delete mode 100644 src/shared/seccomp-util.c delete mode 100644 src/shared/seccomp-util.h delete mode 100644 src/shared/sleep-config.c delete mode 100644 src/shared/sleep-config.h delete mode 100644 src/shared/spawn-ask-password-agent.c delete mode 100644 src/shared/spawn-ask-password-agent.h delete mode 100644 src/shared/spawn-polkit-agent.c delete mode 100644 src/shared/spawn-polkit-agent.h delete mode 100644 src/shared/specifier.c delete mode 100644 src/shared/specifier.h delete mode 100644 src/shared/switch-root.c delete mode 100644 src/shared/switch-root.h delete mode 100644 src/shared/sysctl-util.c delete mode 100644 src/shared/sysctl-util.h delete mode 100644 src/shared/test-tables.h delete mode 100644 src/shared/udev-util.h delete mode 100644 src/shared/uid-range.c delete mode 100644 src/shared/uid-range.h delete mode 100644 src/shared/utmp-wtmp.c delete mode 100644 src/shared/utmp-wtmp.h delete mode 100644 src/shared/watchdog.c delete mode 100644 src/shared/watchdog.h mode change 120000 => 100644 src/sleep/Makefile mode change 120000 => 100644 src/socket-proxy/Makefile mode change 120000 => 100644 src/sysctl/Makefile mode change 120000 => 100644 src/system-update-generator/Makefile create mode 100644 src/system/systemctl/Makefile create mode 100644 src/system/systemctl/systemctl.c create mode 100755 src/system/systemctl/systemd-sysv-install.SKELETON create mode 100644 src/system/systemd/Makefile create mode 100644 src/system/systemd/macros.systemd.in create mode 100644 src/system/systemd/main.c create mode 100644 src/system/systemd/org.freedesktop.systemd1.conf create mode 100644 src/system/systemd/org.freedesktop.systemd1.policy.in.in create mode 100644 src/system/systemd/org.freedesktop.systemd1.service create mode 100644 src/system/systemd/system.conf create mode 100644 src/system/systemd/systemd.pc.in create mode 100644 src/system/systemd/triggers.systemd.in create mode 100644 src/system/systemd/user.conf delete mode 120000 src/systemctl/Makefile delete mode 100644 src/systemctl/systemctl.c delete mode 100755 src/systemctl/systemd-sysv-install.SKELETON create mode 100644 src/systemd-activate/Makefile create mode 100644 src/systemd-activate/activate.c create mode 100644 src/systemd-activate/systemd-activate.xml create mode 100644 src/systemd-analyze/.gitignore create mode 100644 src/systemd-analyze/Makefile create mode 100644 src/systemd-analyze/analyze-verify.c create mode 100644 src/systemd-analyze/analyze-verify.h create mode 100644 src/systemd-analyze/analyze.c create mode 100644 src/systemd-ask-password/Makefile create mode 100644 src/systemd-ask-password/ask-password.c create mode 100644 src/systemd-backlight/Makefile create mode 100644 src/systemd-backlight/backlight.c create mode 100644 src/systemd-binfmt/Makefile create mode 100644 src/systemd-binfmt/binfmt.c create mode 100644 src/systemd-bootchart/Makefile create mode 100644 src/systemd-bootchart/bootchart.c create mode 100644 src/systemd-bootchart/bootchart.conf create mode 100644 src/systemd-bootchart/bootchart.h create mode 100644 src/systemd-bootchart/store.c create mode 100644 src/systemd-bootchart/store.h create mode 100644 src/systemd-bootchart/svg.c create mode 100644 src/systemd-bootchart/svg.h create mode 100644 src/systemd-cgls/Makefile create mode 100644 src/systemd-cgls/cgls.c create mode 100644 src/systemd-cgroups-agent/Makefile create mode 100644 src/systemd-cgroups-agent/cgroups-agent.c create mode 100644 src/systemd-cgtop/Makefile create mode 100644 src/systemd-cgtop/cgtop.c create mode 100644 src/systemd-cryptsetup/Makefile create mode 100644 src/systemd-cryptsetup/cryptsetup-generator.c create mode 100644 src/systemd-cryptsetup/cryptsetup.c create mode 100644 src/systemd-delta/Makefile create mode 100644 src/systemd-delta/delta.c create mode 100644 src/systemd-escape/Makefile create mode 100644 src/systemd-escape/escape.c create mode 100644 src/systemd-notify/Makefile create mode 100644 src/systemd-notify/notify.c create mode 100644 src/systemd-nspawn/.gitignore create mode 100644 src/systemd-nspawn/Makefile create mode 100644 src/systemd-nspawn/nspawn-cgroup.c create mode 100644 src/systemd-nspawn/nspawn-cgroup.h create mode 100644 src/systemd-nspawn/nspawn-expose-ports.c create mode 100644 src/systemd-nspawn/nspawn-expose-ports.h create mode 100644 src/systemd-nspawn/nspawn-gperf.gperf create mode 100644 src/systemd-nspawn/nspawn-mount.c create mode 100644 src/systemd-nspawn/nspawn-mount.h create mode 100644 src/systemd-nspawn/nspawn-network.c create mode 100644 src/systemd-nspawn/nspawn-network.h create mode 100644 src/systemd-nspawn/nspawn-register.c create mode 100644 src/systemd-nspawn/nspawn-register.h create mode 100644 src/systemd-nspawn/nspawn-settings.c create mode 100644 src/systemd-nspawn/nspawn-settings.h create mode 100644 src/systemd-nspawn/nspawn-setuid.c create mode 100644 src/systemd-nspawn/nspawn-setuid.h create mode 100644 src/systemd-nspawn/nspawn-stub-pid1.c create mode 100644 src/systemd-nspawn/nspawn-stub-pid1.h create mode 100644 src/systemd-nspawn/nspawn.c create mode 100644 src/systemd-path/Makefile create mode 100644 src/systemd-path/path.c create mode 100644 src/systemd-quotacheck/Makefile create mode 100644 src/systemd-quotacheck/quotacheck.c create mode 100644 src/systemd-random-seed/Makefile create mode 100644 src/systemd-random-seed/random-seed.c create mode 100644 src/systemd-remount-fs/Makefile create mode 100644 src/systemd-remount-fs/remount-fs.c create mode 100644 src/systemd-reply-password/Makefile create mode 100644 src/systemd-reply-password/reply-password.c create mode 100644 src/systemd-rfkill/Makefile create mode 100644 src/systemd-rfkill/rfkill.c create mode 100644 src/systemd-run/Makefile create mode 100644 src/systemd-run/run.c create mode 100644 src/systemd-shutdown/Makefile create mode 100644 src/systemd-timedate/.gitignore create mode 100644 src/systemd-timedate/Makefile create mode 100644 src/systemd-timedate/org.freedesktop.timedate1.conf create mode 100644 src/systemd-timedate/org.freedesktop.timedate1.policy.in create mode 100644 src/systemd-timedate/org.freedesktop.timedate1.service create mode 100644 src/systemd-timedate/timedatectl.c create mode 100644 src/systemd-timedate/timedated.c create mode 100644 src/systemd-timesync/.gitignore create mode 100644 src/systemd-timesync/Makefile create mode 100644 src/systemd-timesync/timesyncd-conf.c create mode 100644 src/systemd-timesync/timesyncd-conf.h create mode 100644 src/systemd-timesync/timesyncd-gperf.gperf create mode 100644 src/systemd-timesync/timesyncd-manager.c create mode 100644 src/systemd-timesync/timesyncd-manager.h create mode 100644 src/systemd-timesync/timesyncd-server.c create mode 100644 src/systemd-timesync/timesyncd-server.h create mode 100644 src/systemd-timesync/timesyncd.c create mode 100644 src/systemd-timesync/timesyncd.conf.in create mode 100644 src/systemd-tmpfiles/Makefile create mode 100644 src/systemd-tmpfiles/tmpfiles.c create mode 100644 src/systemd-tty-ask-password-agent/Makefile create mode 100644 src/systemd-tty-ask-password-agent/tty-ask-password-agent.c create mode 100644 src/systemd-update-done/Makefile create mode 100644 src/systemd-update-done/update-done.c create mode 100644 src/systemd-update-utmp/Makefile create mode 100644 src/systemd-update-utmp/update-utmp.c create mode 100644 src/systemd-user-sessions/user-sessions.c create mode 100644 src/systemd-vconsole/.gitignore create mode 100644 src/systemd-vconsole/90-vconsole.rules.in create mode 100644 src/systemd-vconsole/Makefile create mode 100644 src/systemd-vconsole/vconsole-setup.c delete mode 120000 src/systemd/Makefile delete mode 100644 src/systemd/_sd-common.h delete mode 100644 src/systemd/sd-bus-protocol.h delete mode 100644 src/systemd/sd-bus-vtable.h delete mode 100644 src/systemd/sd-bus.h delete mode 100644 src/systemd/sd-daemon.h delete mode 100644 src/systemd/sd-device.h delete mode 100644 src/systemd/sd-dhcp-client.h delete mode 100644 src/systemd/sd-dhcp-lease.h delete mode 100644 src/systemd/sd-dhcp-server.h delete mode 100644 src/systemd/sd-dhcp6-client.h delete mode 100644 src/systemd/sd-dhcp6-lease.h delete mode 100644 src/systemd/sd-event.h delete mode 100644 src/systemd/sd-hwdb.h delete mode 100644 src/systemd/sd-id128.h delete mode 100644 src/systemd/sd-ipv4acd.h delete mode 100644 src/systemd/sd-ipv4ll.h delete mode 100644 src/systemd/sd-journal.h delete mode 100644 src/systemd/sd-lldp.h delete mode 100644 src/systemd/sd-login.h delete mode 100644 src/systemd/sd-messages.h delete mode 100644 src/systemd/sd-ndisc.h delete mode 100644 src/systemd/sd-netlink.h delete mode 100644 src/systemd/sd-network.h delete mode 100644 src/systemd/sd-path.h delete mode 100644 src/systemd/sd-resolve.h delete mode 100644 src/systemd/sd-utf8.h mode change 120000 => 100644 src/sysusers/Makefile mode change 120000 => 100644 src/sysv-generator/Makefile mode change 120000 => 100644 src/test/Makefile delete mode 100644 src/timedate/.gitignore delete mode 120000 src/timedate/Makefile delete mode 100644 src/timedate/org.freedesktop.timedate1.conf delete mode 100644 src/timedate/org.freedesktop.timedate1.policy.in delete mode 100644 src/timedate/org.freedesktop.timedate1.service delete mode 100644 src/timedate/timedatectl.c delete mode 100644 src/timedate/timedated.c delete mode 100644 src/timesync/.gitignore delete mode 120000 src/timesync/Makefile delete mode 100644 src/timesync/timesyncd-conf.c delete mode 100644 src/timesync/timesyncd-conf.h delete mode 100644 src/timesync/timesyncd-gperf.gperf delete mode 100644 src/timesync/timesyncd-manager.c delete mode 100644 src/timesync/timesyncd-manager.h delete mode 100644 src/timesync/timesyncd-server.c delete mode 100644 src/timesync/timesyncd-server.h delete mode 100644 src/timesync/timesyncd.c delete mode 100644 src/timesync/timesyncd.conf.in delete mode 120000 src/tmpfiles/Makefile delete mode 100644 src/tmpfiles/tmpfiles.c delete mode 120000 src/tty-ask-password-agent/Makefile delete mode 100644 src/tty-ask-password-agent/tty-ask-password-agent.c mode change 120000 => 100644 src/udev/Makefile mode change 120000 => 100644 src/udev/ata_id/Makefile mode change 120000 => 100644 src/udev/cdrom_id/Makefile mode change 120000 => 100644 src/udev/collect/Makefile mode change 120000 => 100644 src/udev/mtd_probe/Makefile delete mode 120000 src/udev/net/Makefile mode change 120000 => 100644 src/udev/scsi_id/Makefile mode change 120000 => 100644 src/udev/v4l_id/Makefile delete mode 120000 src/update-done/Makefile delete mode 100644 src/update-done/update-done.c delete mode 120000 src/update-utmp/Makefile delete mode 100644 src/update-utmp/update-utmp.c delete mode 120000 src/user-sessions/Makefile delete mode 100644 src/user-sessions/user-sessions.c delete mode 100644 src/vconsole/.gitignore delete mode 100644 src/vconsole/90-vconsole.rules.in delete mode 120000 src/vconsole/Makefile delete mode 100644 src/vconsole/vconsole-setup.c (limited to 'src') diff --git a/src/Makefile b/src/Makefile deleted file mode 100644 index 9d07505194..0000000000 --- a/src/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -# This file is part of systemd. -# -# Copyright 2010 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 . - -# This file is a dirty trick to simplify compilation from within -# emacs. This file is not intended to be distributed. So, don't touch -# it, even better ignore it! - -all: - $(MAKE) -C .. - -clean: - $(MAKE) -C .. clean - -.PHONY: all clean diff --git a/src/ac-power/Makefile b/src/ac-power/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/ac-power/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/ac-power/Makefile b/src/ac-power/Makefile new file mode 100644 index 0000000000..b06af09332 --- /dev/null +++ b/src/ac-power/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_ac_power_SOURCES = \ + src/ac-power/ac-power.c + +systemd_ac_power_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/activate/Makefile b/src/activate/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/activate/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/activate/activate.c b/src/activate/activate.c deleted file mode 100644 index 0db4967edb..0000000000 --- a/src/activate/activate.c +++ /dev/null @@ -1,511 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "signal-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "strv.h" - -static char** arg_listen = NULL; -static bool arg_accept = false; -static int arg_socket_type = SOCK_STREAM; -static char** arg_args = NULL; -static char** arg_setenv = NULL; -static const char *arg_fdname = NULL; -static bool arg_inetd = false; - -static int add_epoll(int epoll_fd, int fd) { - struct epoll_event ev = { - .events = EPOLLIN - }; - int r; - - assert(epoll_fd >= 0); - assert(fd >= 0); - - ev.data.fd = fd; - r = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev); - if (r < 0) - return log_error_errno(errno, "Failed to add event on epoll fd:%d for fd:%d: %m", epoll_fd, fd); - - return 0; -} - -static int open_sockets(int *epoll_fd, bool accept) { - char **address; - int n, fd, r; - int count = 0; - - n = sd_listen_fds(true); - if (n < 0) - return log_error_errno(n, "Failed to read listening file descriptors from environment: %m"); - if (n > 0) { - log_info("Received %i descriptors via the environment.", n); - - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { - r = fd_cloexec(fd, arg_accept); - if (r < 0) - return r; - - count ++; - } - } - - /* Close logging and all other descriptors */ - if (arg_listen) { - int except[3 + n]; - - for (fd = 0; fd < SD_LISTEN_FDS_START + n; fd++) - except[fd] = fd; - - log_close(); - close_all_fds(except, 3 + n); - } - - /** Note: we leak some fd's on error here. I doesn't matter - * much, since the program will exit immediately anyway, but - * would be a pain to fix. - */ - - STRV_FOREACH(address, arg_listen) { - fd = make_socket_fd(LOG_DEBUG, *address, arg_socket_type, (arg_accept*SOCK_CLOEXEC)); - if (fd < 0) { - log_open(); - return log_error_errno(fd, "Failed to open '%s': %m", *address); - } - - assert(fd == SD_LISTEN_FDS_START + count); - count ++; - } - - if (arg_listen) - log_open(); - - *epoll_fd = epoll_create1(EPOLL_CLOEXEC); - if (*epoll_fd < 0) - return log_error_errno(errno, "Failed to create epoll object: %m"); - - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) { - _cleanup_free_ char *name = NULL; - - getsockname_pretty(fd, &name); - log_info("Listening on %s as %i.", strna(name), fd); - - r = add_epoll(*epoll_fd, fd); - if (r < 0) - return r; - } - - return count; -} - -static int exec_process(const char* name, char **argv, char **env, int start_fd, int n_fds) { - - _cleanup_strv_free_ char **envp = NULL; - _cleanup_free_ char *joined = NULL; - unsigned n_env = 0, length; - const char *tocopy; - unsigned i; - char **s; - int r; - - if (arg_inetd && n_fds != 1) { - log_error("--inetd only supported for single file descriptors."); - return -EINVAL; - } - - length = strv_length(arg_setenv); - - /* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID, LISTEN_FDNAMES, NULL */ - envp = new0(char *, length + 8); - if (!envp) - return log_oom(); - - STRV_FOREACH(s, arg_setenv) { - - if (strchr(*s, '=')) { - char *k; - - k = strdup(*s); - if (!k) - return log_oom(); - - envp[n_env++] = k; - } else { - _cleanup_free_ char *p; - const char *n; - - p = strappend(*s, "="); - if (!p) - return log_oom(); - - n = strv_find_prefix(env, p); - if (!n) - continue; - - envp[n_env] = strdup(n); - if (!envp[n_env]) - return log_oom(); - - n_env ++; - } - } - - FOREACH_STRING(tocopy, "TERM=", "PATH=", "USER=", "HOME=") { - const char *n; - - n = strv_find_prefix(env, tocopy); - if (!n) - continue; - - envp[n_env] = strdup(n); - if (!envp[n_env]) - return log_oom(); - - n_env ++; - } - - if (arg_inetd) { - assert(n_fds == 1); - - r = dup2(start_fd, STDIN_FILENO); - if (r < 0) - return log_error_errno(errno, "Failed to dup connection to stdin: %m"); - - r = dup2(start_fd, STDOUT_FILENO); - if (r < 0) - return log_error_errno(errno, "Failed to dup connection to stdout: %m"); - - start_fd = safe_close(start_fd); - } else { - if (start_fd != SD_LISTEN_FDS_START) { - assert(n_fds == 1); - - r = dup2(start_fd, SD_LISTEN_FDS_START); - if (r < 0) - return log_error_errno(errno, "Failed to dup connection: %m"); - - safe_close(start_fd); - start_fd = SD_LISTEN_FDS_START; - } - - if (asprintf((char**)(envp + n_env++), "LISTEN_FDS=%i", n_fds) < 0) - return log_oom(); - - if (asprintf((char**)(envp + n_env++), "LISTEN_PID=" PID_FMT, getpid()) < 0) - return log_oom(); - - if (arg_fdname) { - char *e; - - e = strappend("LISTEN_FDNAMES=", arg_fdname); - if (!e) - return log_oom(); - - for (i = 1; i < (unsigned) n_fds; i++) { - char *c; - - c = strjoin(e, ":", arg_fdname, NULL); - if (!c) { - free(e); - return log_oom(); - } - - free(e); - e = c; - } - - envp[n_env++] = e; - } - } - - joined = strv_join(argv, " "); - if (!joined) - return log_oom(); - - log_info("Execing %s (%s)", name, joined); - execvpe(name, argv, envp); - - return log_error_errno(errno, "Failed to execp %s (%s): %m", name, joined); -} - -static int fork_and_exec_process(const char* child, char** argv, char **env, int fd) { - _cleanup_free_ char *joined = NULL; - pid_t parent_pid, child_pid; - - joined = strv_join(argv, " "); - if (!joined) - return log_oom(); - - parent_pid = getpid(); - - child_pid = fork(); - if (child_pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - - /* In the child */ - if (child_pid == 0) { - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - /* Make sure the child goes away when the parent dies */ - if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) - _exit(EXIT_FAILURE); - - /* Check whether our parent died before we were able - * to set the death signal */ - if (getppid() != parent_pid) - _exit(EXIT_SUCCESS); - - exec_process(child, argv, env, fd, 1); - _exit(EXIT_FAILURE); - } - - log_info("Spawned %s (%s) as PID %d", child, joined, child_pid); - return 0; -} - -static int do_accept(const char* name, char **argv, char **envp, int fd) { - _cleanup_free_ char *local = NULL, *peer = NULL; - _cleanup_close_ int fd_accepted = -1; - - fd_accepted = accept4(fd, NULL, NULL, 0); - if (fd_accepted < 0) - return log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd); - - getsockname_pretty(fd_accepted, &local); - getpeername_pretty(fd_accepted, true, &peer); - log_info("Connection from %s to %s", strna(peer), strna(local)); - - return fork_and_exec_process(name, argv, envp, fd_accepted); -} - -/* SIGCHLD handler. */ -static void sigchld_hdl(int sig, siginfo_t *t, void *data) { - PROTECT_ERRNO; - - log_info("Child %d died with code %d", t->si_pid, t->si_status); - - /* Wait for a dead child. */ - (void) waitpid(t->si_pid, NULL, 0); -} - -static int install_chld_handler(void) { - static const struct sigaction act = { - .sa_flags = SA_SIGINFO, - .sa_sigaction = sigchld_hdl, - }; - - int r; - - r = sigaction(SIGCHLD, &act, 0); - if (r < 0) - return log_error_errno(errno, "Failed to install SIGCHLD handler: %m"); - - return 0; -} - -static void help(void) { - printf("%s [OPTIONS...]\n\n" - "Listen on sockets and launch child on connection.\n\n" - "Options:\n" - " -h --help Show this help and exit\n" - " --version Print version string and exit\n" - " -l --listen=ADDR Listen for raw connections at ADDR\n" - " -d --datagram Listen on datagram instead of stream socket\n" - " --seqpacket Listen on SOCK_SEQPACKET instead of stream socket\n" - " -a --accept Spawn separate child for each connection\n" - " -E --setenv=NAME[=VALUE] Pass an environment variable to children\n" - " --inetd Enable inetd file descriptor passing protocol\n" - "\n" - "Note: file descriptors from sd_listen_fds() will be passed through.\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_FDNAME, - ARG_SEQPACKET, - ARG_INETD, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "datagram", no_argument, NULL, 'd' }, - { "seqpacket", no_argument, NULL, ARG_SEQPACKET }, - { "listen", required_argument, NULL, 'l' }, - { "accept", no_argument, NULL, 'a' }, - { "setenv", required_argument, NULL, 'E' }, - { "environment", required_argument, NULL, 'E' }, /* legacy alias */ - { "fdname", required_argument, NULL, ARG_FDNAME }, - { "inetd", no_argument, NULL, ARG_INETD }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "+hl:aEd", options, NULL)) >= 0) - switch(c) { - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case 'l': - r = strv_extend(&arg_listen, optarg); - if (r < 0) - return log_oom(); - - break; - - case 'd': - if (arg_socket_type == SOCK_SEQPACKET) { - log_error("--datagram may not be combined with --seqpacket."); - return -EINVAL; - } - - arg_socket_type = SOCK_DGRAM; - break; - - case ARG_SEQPACKET: - if (arg_socket_type == SOCK_DGRAM) { - log_error("--seqpacket may not be combined with --datagram."); - return -EINVAL; - } - - arg_socket_type = SOCK_SEQPACKET; - break; - - case 'a': - arg_accept = true; - break; - - case 'E': - r = strv_extend(&arg_setenv, optarg); - if (r < 0) - return log_oom(); - - break; - - case ARG_FDNAME: - if (!fdname_is_valid(optarg)) { - log_error("File descriptor name %s is not valid, refusing.", optarg); - return -EINVAL; - } - - arg_fdname = optarg; - break; - - case ARG_INETD: - arg_inetd = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind == argc) { - log_error("%s: command to execute is missing.", - program_invocation_short_name); - return -EINVAL; - } - - if (arg_socket_type == SOCK_DGRAM && arg_accept) { - log_error("Datagram sockets do not accept connections. " - "The --datagram and --accept options may not be combined."); - return -EINVAL; - } - - arg_args = argv + optind; - - return 1 /* work to do */; -} - -int main(int argc, char **argv, char **envp) { - int r, n; - int epoll_fd = -1; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; - - r = install_chld_handler(); - if (r < 0) - return EXIT_FAILURE; - - n = open_sockets(&epoll_fd, arg_accept); - if (n < 0) - return EXIT_FAILURE; - if (n == 0) { - log_error("No sockets to listen on specified or passed in."); - return EXIT_FAILURE; - } - - for (;;) { - struct epoll_event event; - - r = epoll_wait(epoll_fd, &event, 1, -1); - if (r < 0) { - if (errno == EINTR) - continue; - - log_error_errno(errno, "epoll_wait() failed: %m"); - return EXIT_FAILURE; - } - - log_info("Communication attempt on fd %i.", event.data.fd); - if (arg_accept) { - r = do_accept(argv[optind], argv + optind, envp, event.data.fd); - if (r < 0) - return EXIT_FAILURE; - } else - break; - } - - exec_process(argv[optind], argv + optind, envp, SD_LISTEN_FDS_START, n); - - return EXIT_SUCCESS; -} diff --git a/src/analyze/.gitignore b/src/analyze/.gitignore deleted file mode 100644 index 752ea236c8..0000000000 --- a/src/analyze/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/systemd-analyze diff --git a/src/analyze/Makefile b/src/analyze/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/analyze/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/analyze/analyze-verify.c b/src/analyze/analyze-verify.c deleted file mode 100644 index d36c8db3d4..0000000000 --- a/src/analyze/analyze-verify.c +++ /dev/null @@ -1,306 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "alloc-util.h" -#include "analyze-verify.h" -#include "bus-error.h" -#include "bus-util.h" -#include "log.h" -#include "manager.h" -#include "pager.h" -#include "path-util.h" -#include "strv.h" -#include "unit-name.h" - -static int prepare_filename(const char *filename, char **ret) { - int r; - const char *name; - _cleanup_free_ char *abspath = NULL; - _cleanup_free_ char *dir = NULL; - _cleanup_free_ char *with_instance = NULL; - char *c; - - assert(filename); - assert(ret); - - r = path_make_absolute_cwd(filename, &abspath); - if (r < 0) - return r; - - name = basename(abspath); - if (!unit_name_is_valid(name, UNIT_NAME_ANY)) - return -EINVAL; - - if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { - r = unit_name_replace_instance(name, "i", &with_instance); - if (r < 0) - return r; - } - - dir = dirname_malloc(abspath); - if (!dir) - return -ENOMEM; - - if (with_instance) - c = path_join(NULL, dir, with_instance); - else - c = path_join(NULL, dir, name); - if (!c) - return -ENOMEM; - - *ret = c; - return 0; -} - -static int generate_path(char **var, char **filenames) { - char **filename; - - _cleanup_strv_free_ char **ans = NULL; - int r; - - STRV_FOREACH(filename, filenames) { - char *t; - - t = dirname_malloc(*filename); - if (!t) - return -ENOMEM; - - r = strv_consume(&ans, t); - if (r < 0) - return r; - } - - assert_se(strv_uniq(ans)); - - r = strv_extend(&ans, ""); - if (r < 0) - return r; - - *var = strv_join(ans, ":"); - if (!*var) - return -ENOMEM; - - return 0; -} - -static int verify_socket(Unit *u) { - int r; - - assert(u); - - if (u->type != UNIT_SOCKET) - return 0; - - /* Cannot run this without the service being around */ - - /* This makes sure instance is created if necessary. */ - r = socket_instantiate_service(SOCKET(u)); - if (r < 0) { - log_unit_error_errno(u, r, "Socket cannot be started, failed to create instance: %m"); - return r; - } - - /* This checks both type of sockets */ - if (UNIT_ISSET(SOCKET(u)->service)) { - Service *service; - - service = SERVICE(UNIT_DEREF(SOCKET(u)->service)); - log_unit_debug(u, "Using %s", UNIT(service)->id); - - if (UNIT(service)->load_state != UNIT_LOADED) { - log_unit_error(u, "Service %s not loaded, %s cannot be started.", UNIT(service)->id, u->id); - return -ENOENT; - } - } - - return 0; -} - -static int verify_executable(Unit *u, ExecCommand *exec) { - if (exec == NULL) - return 0; - - if (access(exec->path, X_OK) < 0) - return log_unit_error_errno(u, errno, "Command %s is not executable: %m", exec->path); - - return 0; -} - -static int verify_executables(Unit *u) { - ExecCommand *exec; - int r = 0, k; - unsigned i; - - assert(u); - - exec = u->type == UNIT_SOCKET ? SOCKET(u)->control_command : - u->type == UNIT_MOUNT ? MOUNT(u)->control_command : - u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL; - k = verify_executable(u, exec); - if (k < 0 && r == 0) - r = k; - - if (u->type == UNIT_SERVICE) - for (i = 0; i < ELEMENTSOF(SERVICE(u)->exec_command); i++) { - k = verify_executable(u, SERVICE(u)->exec_command[i]); - if (k < 0 && r == 0) - r = k; - } - - if (u->type == UNIT_SOCKET) - for (i = 0; i < ELEMENTSOF(SOCKET(u)->exec_command); i++) { - k = verify_executable(u, SOCKET(u)->exec_command[i]); - if (k < 0 && r == 0) - r = k; - } - - return r; -} - -static int verify_documentation(Unit *u, bool check_man) { - char **p; - int r = 0, k; - - STRV_FOREACH(p, u->documentation) { - log_unit_debug(u, "Found documentation item: %s", *p); - - if (check_man && startswith(*p, "man:")) { - k = show_man_page(*p + 4, true); - if (k != 0) { - if (k < 0) - log_unit_error_errno(u, r, "Can't show %s: %m", *p); - else { - log_unit_error_errno(u, r, "man %s command failed with code %d", *p + 4, k); - k = -ENOEXEC; - } - if (r == 0) - r = k; - } - } - } - - /* Check remote URLs? */ - - return r; -} - -static int verify_unit(Unit *u, bool check_man) { - _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; - int r, k; - - assert(u); - - if (log_get_max_level() >= LOG_DEBUG) - unit_dump(u, stdout, "\t"); - - log_unit_debug(u, "Creating %s/start job", u->id); - r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, &err, NULL); - if (r < 0) - log_unit_error_errno(u, r, "Failed to create %s/start: %s", u->id, bus_error_message(&err, r)); - - k = verify_socket(u); - if (k < 0 && r == 0) - r = k; - - k = verify_executables(u); - if (k < 0 && r == 0) - r = k; - - k = verify_documentation(u, check_man); - if (k < 0 && r == 0) - r = k; - - return r; -} - -int verify_units(char **filenames, ManagerRunningAs running_as, bool check_man) { - _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; - Manager *m = NULL; - FILE *serial = NULL; - FDSet *fdset = NULL; - - _cleanup_free_ char *var = NULL; - - char **filename; - int r = 0, k; - - Unit *units[strv_length(filenames)]; - int i, count = 0; - - if (strv_isempty(filenames)) - return 0; - - /* set the path */ - r = generate_path(&var, filenames); - if (r < 0) - return log_error_errno(r, "Failed to generate unit load path: %m"); - - assert_se(set_unit_path(var) >= 0); - - r = manager_new(running_as, true, &m); - if (r < 0) - return log_error_errno(r, "Failed to initialize manager: %m"); - - log_debug("Starting manager..."); - - r = manager_startup(m, serial, fdset); - if (r < 0) { - log_error_errno(r, "Failed to start manager: %m"); - goto finish; - } - - manager_clear_jobs(m); - - log_debug("Loading remaining units from the command line..."); - - STRV_FOREACH(filename, filenames) { - _cleanup_free_ char *prepared = NULL; - - log_debug("Handling %s...", *filename); - - k = prepare_filename(*filename, &prepared); - if (k < 0) { - log_error_errno(k, "Failed to prepare filename %s: %m", *filename); - if (r == 0) - r = k; - continue; - } - - k = manager_load_unit(m, NULL, prepared, &err, &units[count]); - if (k < 0) { - log_error_errno(k, "Failed to load %s: %m", *filename); - if (r == 0) - r = k; - } else - count ++; - } - - for (i = 0; i < count; i++) { - k = verify_unit(units[i], check_man); - if (k < 0 && r == 0) - r = k; - } - -finish: - manager_free(m); - - return r; -} diff --git a/src/analyze/analyze-verify.h b/src/analyze/analyze-verify.h deleted file mode 100644 index 54adad93e1..0000000000 --- a/src/analyze/analyze-verify.h +++ /dev/null @@ -1,26 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#pragma once - -#include - -#include "path-lookup.h" - -int verify_units(char **filenames, ManagerRunningAs running_as, bool check_man); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c deleted file mode 100644 index 1a28093b07..0000000000 --- a/src/analyze/analyze.c +++ /dev/null @@ -1,1492 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2013 Lennart Poettering - Copyright 2013 Simon Peeters - - 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 . -***/ - -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "analyze-verify.h" -#include "bus-error.h" -#include "bus-util.h" -#include "glob-util.h" -#include "hashmap.h" -#include "locale-util.h" -#include "log.h" -#include "pager.h" -#include "parse-util.h" -#include "special.h" -#include "strv.h" -#include "strxcpyx.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "util.h" - -#define SCALE_X (0.1 / 1000.0) /* pixels per us */ -#define SCALE_Y (20.0) - -#define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0)) - -#define svg(...) printf(__VA_ARGS__) - -#define svg_bar(class, x1, x2, y) \ - svg(" \n", \ - (class), \ - SCALE_X * (x1), SCALE_Y * (y), \ - SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0) - -#define svg_text(b, x, y, format, ...) \ - do { \ - svg(" ", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \ - svg(format, ## __VA_ARGS__); \ - svg("\n"); \ - } while(false) - -static enum dot { - DEP_ALL, - DEP_ORDER, - DEP_REQUIRE -} arg_dot = DEP_ALL; -static char** arg_dot_from_patterns = NULL; -static char** arg_dot_to_patterns = NULL; -static usec_t arg_fuzz = 0; -static bool arg_no_pager = false; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static char *arg_host = NULL; -static bool arg_user = false; -static bool arg_man = true; - -struct boot_times { - usec_t firmware_time; - usec_t loader_time; - usec_t kernel_time; - usec_t kernel_done_time; - usec_t initrd_time; - usec_t userspace_time; - usec_t finish_time; - usec_t security_start_time; - usec_t security_finish_time; - usec_t generators_start_time; - usec_t generators_finish_time; - usec_t unitsload_start_time; - usec_t unitsload_finish_time; - - /* - * If we're analyzing the user instance, all timestamps will be offset - * by its own start-up timestamp, which may be arbitrarily big. - * With "plot", this causes arbitrarily wide output SVG files which almost - * completely consist of empty space. Thus we cancel out this offset. - * - * This offset is subtracted from times above by acquire_boot_times(), - * but it still needs to be subtracted from unit-specific timestamps - * (so it is stored here for reference). - */ - usec_t reverse_offset; -}; - -struct unit_times { - char *name; - usec_t activating; - usec_t activated; - usec_t deactivated; - usec_t deactivating; - usec_t time; -}; - -struct host_info { - char *hostname; - char *kernel_name; - char *kernel_release; - char *kernel_version; - char *os_pretty_name; - char *virtualization; - char *architecture; -}; - -static void pager_open_if_enabled(void) { - - if (arg_no_pager) - return; - - pager_open(false); -} - -static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(property); - assert(val); - - r = sd_bus_get_property_trivial( - bus, - "org.freedesktop.systemd1", - path, - interface, - property, - &error, - 't', val); - - if (r < 0) { - log_error("Failed to parse reply: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(path); - assert(property); - assert(strv); - - r = sd_bus_get_property_strv( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - property, - &error, - strv); - if (r < 0) { - log_error("Failed to get unit property %s: %s", property, bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int compare_unit_time(const void *a, const void *b) { - return compare(((struct unit_times *)b)->time, - ((struct unit_times *)a)->time); -} - -static int compare_unit_start(const void *a, const void *b) { - return compare(((struct unit_times *)a)->activating, - ((struct unit_times *)b)->activating); -} - -static void free_unit_times(struct unit_times *t, unsigned n) { - struct unit_times *p; - - for (p = t; p < t + n; p++) - free(p->name); - - free(t); -} - -static void subtract_timestamp(usec_t *a, usec_t b) { - assert(a); - - if (*a > 0) { - assert(*a >= b); - *a -= b; - } -} - -static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) { - static struct boot_times times; - static bool cached = false; - - if (cached) - goto finish; - - assert_cc(sizeof(usec_t) == sizeof(uint64_t)); - - if (bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "FirmwareTimestampMonotonic", - ×.firmware_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LoaderTimestampMonotonic", - ×.loader_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "KernelTimestamp", - ×.kernel_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "InitRDTimestampMonotonic", - ×.initrd_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UserspaceTimestampMonotonic", - ×.userspace_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "FinishTimestampMonotonic", - ×.finish_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SecurityStartTimestampMonotonic", - ×.security_start_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SecurityFinishTimestampMonotonic", - ×.security_finish_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GeneratorsStartTimestampMonotonic", - ×.generators_start_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GeneratorsFinishTimestampMonotonic", - ×.generators_finish_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UnitsLoadStartTimestampMonotonic", - ×.unitsload_start_time) < 0 || - bus_get_uint64_property(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UnitsLoadFinishTimestampMonotonic", - ×.unitsload_finish_time) < 0) - return -EIO; - - if (times.finish_time <= 0) { - log_error("Bootup is not yet finished. Please try again later."); - return -EINPROGRESS; - } - - if (arg_user) { - /* - * User-instance-specific timestamps processing - * (see comment to reverse_offset in struct boot_times). - */ - times.reverse_offset = times.userspace_time; - - times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time = 0; - subtract_timestamp(×.finish_time, times.reverse_offset); - - subtract_timestamp(×.security_start_time, times.reverse_offset); - subtract_timestamp(×.security_finish_time, times.reverse_offset); - - subtract_timestamp(×.generators_start_time, times.reverse_offset); - subtract_timestamp(×.generators_finish_time, times.reverse_offset); - - subtract_timestamp(×.unitsload_start_time, times.reverse_offset); - subtract_timestamp(×.unitsload_finish_time, times.reverse_offset); - } else { - if (times.initrd_time) - times.kernel_done_time = times.initrd_time; - else - times.kernel_done_time = times.userspace_time; - } - - cached = true; - -finish: - *bt = × - return 0; -} - -static void free_host_info(struct host_info *hi) { - - if (!hi) - return; - - free(hi->hostname); - free(hi->kernel_name); - free(hi->kernel_release); - free(hi->kernel_version); - free(hi->os_pretty_name); - free(hi->virtualization); - free(hi->architecture); - free(hi); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info*, free_host_info); - -static int acquire_time_data(sd_bus *bus, struct unit_times **out) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r, c = 0; - struct boot_times *boot_times = NULL; - struct unit_times *unit_times = NULL; - size_t size = 0; - UnitInfo u; - - r = acquire_boot_times(bus, &boot_times); - if (r < 0) - goto fail; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListUnits", - &error, &reply, - NULL); - if (r < 0) { - log_error("Failed to list units: %s", bus_error_message(&error, -r)); - goto fail; - } - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); - if (r < 0) { - bus_log_parse_error(r); - goto fail; - } - - while ((r = bus_parse_unit_info(reply, &u)) > 0) { - struct unit_times *t; - - if (!GREEDY_REALLOC(unit_times, size, c+1)) { - r = log_oom(); - goto fail; - } - - t = unit_times+c; - t->name = NULL; - - assert_cc(sizeof(usec_t) == sizeof(uint64_t)); - - if (bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "InactiveExitTimestampMonotonic", - &t->activating) < 0 || - bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "ActiveEnterTimestampMonotonic", - &t->activated) < 0 || - bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "ActiveExitTimestampMonotonic", - &t->deactivating) < 0 || - bus_get_uint64_property(bus, u.unit_path, - "org.freedesktop.systemd1.Unit", - "InactiveEnterTimestampMonotonic", - &t->deactivated) < 0) { - r = -EIO; - goto fail; - } - - subtract_timestamp(&t->activating, boot_times->reverse_offset); - subtract_timestamp(&t->activated, boot_times->reverse_offset); - subtract_timestamp(&t->deactivating, boot_times->reverse_offset); - subtract_timestamp(&t->deactivated, boot_times->reverse_offset); - - if (t->activated >= t->activating) - t->time = t->activated - t->activating; - else if (t->deactivated >= t->activating) - t->time = t->deactivated - t->activating; - else - t->time = 0; - - if (t->activating == 0) - continue; - - t->name = strdup(u.id); - if (t->name == NULL) { - r = log_oom(); - goto fail; - } - c++; - } - if (r < 0) { - bus_log_parse_error(r); - goto fail; - } - - *out = unit_times; - return c; - -fail: - if (unit_times) - free_unit_times(unit_times, (unsigned) c); - return r; -} - -static int acquire_host_info(sd_bus *bus, struct host_info **hi) { - static const struct bus_properties_map hostname_map[] = { - { "Hostname", "s", NULL, offsetof(struct host_info, hostname) }, - { "KernelName", "s", NULL, offsetof(struct host_info, kernel_name) }, - { "KernelRelease", "s", NULL, offsetof(struct host_info, kernel_release) }, - { "KernelVersion", "s", NULL, offsetof(struct host_info, kernel_version) }, - { "OperatingSystemPrettyName", "s", NULL, offsetof(struct host_info, os_pretty_name) }, - {} - }; - - static const struct bus_properties_map manager_map[] = { - { "Virtualization", "s", NULL, offsetof(struct host_info, virtualization) }, - { "Architecture", "s", NULL, offsetof(struct host_info, architecture) }, - {} - }; - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(free_host_infop) struct host_info *host; - int r; - - host = new0(struct host_info, 1); - if (!host) - return log_oom(); - - r = bus_map_all_properties(bus, - "org.freedesktop.hostname1", - "/org/freedesktop/hostname1", - hostname_map, - host); - if (r < 0) - log_debug_errno(r, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error, r)); - - r = bus_map_all_properties(bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - manager_map, - host); - if (r < 0) - return log_error_errno(r, "Failed to get host information from systemd: %s", bus_error_message(&error, r)); - - *hi = host; - host = NULL; - - return 0; -} - -static int pretty_boot_time(sd_bus *bus, char **_buf) { - char ts[FORMAT_TIMESPAN_MAX]; - struct boot_times *t; - static char buf[4096]; - size_t size; - char *ptr; - int r; - - r = acquire_boot_times(bus, &t); - if (r < 0) - return r; - - ptr = buf; - size = sizeof(buf); - - size = strpcpyf(&ptr, size, "Startup finished in "); - if (t->firmware_time) - size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC)); - if (t->loader_time) - size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC)); - if (t->kernel_time) - size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC)); - if (t->initrd_time > 0) - size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC)); - - size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC)); - strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC)); - - ptr = strdup(buf); - if (!ptr) - return log_oom(); - - *_buf = ptr; - return 0; -} - -static void svg_graph_box(double height, double begin, double end) { - long long i; - - /* outside box, fill */ - svg("\n", - SCALE_X * (end - begin), SCALE_Y * height); - - for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) { - /* lines for each second */ - if (i % 5000000 == 0) - svg(" \n" - " %.01fs\n", - SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); - else if (i % 1000000 == 0) - svg(" \n" - " %.01fs\n", - SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); - else - svg(" \n", - SCALE_X * i, SCALE_X * i, SCALE_Y * height); - } -} - -static int analyze_plot(sd_bus *bus) { - _cleanup_(free_host_infop) struct host_info *host = NULL; - struct unit_times *times; - struct boot_times *boot; - int n, m = 1, y=0; - double width; - _cleanup_free_ char *pretty_times = NULL; - struct unit_times *u; - - n = acquire_boot_times(bus, &boot); - if (n < 0) - return n; - - n = pretty_boot_time(bus, &pretty_times); - if (n < 0) - return n; - - n = acquire_host_info(bus, &host); - if (n < 0) - return n; - - n = acquire_time_data(bus, ×); - if (n <= 0) - return n; - - qsort(times, n, sizeof(struct unit_times), compare_unit_start); - - width = SCALE_X * (boot->firmware_time + boot->finish_time); - if (width < 800.0) - width = 800.0; - - if (boot->firmware_time > boot->loader_time) - m++; - if (boot->loader_time) { - m++; - if (width < 1000.0) - width = 1000.0; - } - if (boot->initrd_time) - m++; - if (boot->kernel_time) - m++; - - for (u = times; u < times + n; u++) { - double text_start, text_width; - - if (u->activating < boot->userspace_time || - u->activating > boot->finish_time) { - u->name = mfree(u->name); - continue; - } - - /* If the text cannot fit on the left side then - * increase the svg width so it fits on the right. - * TODO: calculate the text width more accurately */ - text_width = 8.0 * strlen(u->name); - text_start = (boot->firmware_time + u->activating) * SCALE_X; - if (text_width > text_start && text_width + text_start > width) - width = text_width + text_start; - - if (u->deactivated > u->activating && u->deactivated <= boot->finish_time - && u->activated == 0 && u->deactivating == 0) - u->activated = u->deactivating = u->deactivated; - if (u->activated < u->activating || u->activated > boot->finish_time) - u->activated = boot->finish_time; - if (u->deactivating < u->activated || u->activated > boot->finish_time) - u->deactivating = boot->finish_time; - if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time) - u->deactivated = boot->finish_time; - m++; - } - - svg("\n" - "\n"); - - svg("\n\n", - 80.0 + width, 150.0 + (m * SCALE_Y) + - 5 * SCALE_Y /* legend */); - - /* write some basic info as a comment, including some help */ - svg("\n" - "\n" - "\n" - "\n" - "\n\n" - "\n\n", VERSION); - - /* style sheet */ - svg("\n \n\n\n"); - - svg("\n"); - svg("%s", pretty_times); - svg("%s %s (%s %s %s) %s %s", - isempty(host->os_pretty_name) ? "GNU/Linux" : host->os_pretty_name, - strempty(host->hostname), - strempty(host->kernel_name), - strempty(host->kernel_release), - strempty(host->kernel_version), - strempty(host->architecture), - strempty(host->virtualization)); - - svg("\n", 20.0 + (SCALE_X * boot->firmware_time)); - svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time); - - if (boot->firmware_time) { - svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y); - svg_text(true, -(double) boot->firmware_time, y, "firmware"); - y++; - } - if (boot->loader_time) { - svg_bar("loader", -(double) boot->loader_time, 0, y); - svg_text(true, -(double) boot->loader_time, y, "loader"); - y++; - } - if (boot->kernel_time) { - svg_bar("kernel", 0, boot->kernel_done_time, y); - svg_text(true, 0, y, "kernel"); - y++; - } - if (boot->initrd_time) { - svg_bar("initrd", boot->initrd_time, boot->userspace_time, y); - svg_text(true, boot->initrd_time, y, "initrd"); - y++; - } - svg_bar("active", boot->userspace_time, boot->finish_time, y); - svg_bar("security", boot->security_start_time, boot->security_finish_time, y); - svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y); - svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y); - svg_text(true, boot->userspace_time, y, "systemd"); - y++; - - for (u = times; u < times + n; u++) { - char ts[FORMAT_TIMESPAN_MAX]; - bool b; - - if (!u->name) - continue; - - svg_bar("activating", u->activating, u->activated, y); - svg_bar("active", u->activated, u->deactivating, y); - svg_bar("deactivating", u->deactivating, u->deactivated, y); - - /* place the text on the left if we have passed the half of the svg width */ - b = u->activating * SCALE_X < width / 2; - if (u->time) - svg_text(b, u->activating, y, "%s (%s)", - u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC)); - else - svg_text(b, u->activating, y, "%s", u->name); - y++; - } - - svg("\n"); - - /* Legend */ - svg("\n"); - y++; - svg_bar("activating", 0, 300000, y); - svg_text(true, 400000, y, "Activating"); - y++; - svg_bar("active", 0, 300000, y); - svg_text(true, 400000, y, "Active"); - y++; - svg_bar("deactivating", 0, 300000, y); - svg_text(true, 400000, y, "Deactivating"); - y++; - svg_bar("security", 0, 300000, y); - svg_text(true, 400000, y, "Setting up security module"); - y++; - svg_bar("generators", 0, 300000, y); - svg_text(true, 400000, y, "Generators"); - y++; - svg_bar("unitsload", 0, 300000, y); - svg_text(true, 400000, y, "Loading unit files"); - y++; - - svg("\n\n"); - - svg("\n"); - - free_unit_times(times, (unsigned) n); - - n = 0; - return n; -} - -static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches, - bool last, struct unit_times *times, struct boot_times *boot) { - unsigned int i; - char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX]; - - for (i = level; i != 0; i--) - printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERTICAL : DRAW_TREE_SPACE)); - - printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH)); - - if (times) { - if (times->time) - printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED, name, - format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC), - format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_NORMAL); - else if (times->activated > boot->userspace_time) - printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); - else - printf("%s", name); - } else - printf("%s", name); - printf("\n"); - - return 0; -} - -static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) { - _cleanup_free_ char *path = NULL; - - assert(bus); - assert(name); - assert(deps); - - path = unit_dbus_path_from_name(name); - if (path == NULL) - return -ENOMEM; - - return bus_get_unit_property_strv(bus, path, "After", deps); -} - -static Hashmap *unit_times_hashmap; - -static int list_dependencies_compare(const void *_a, const void *_b) { - const char **a = (const char**) _a, **b = (const char**) _b; - usec_t usa = 0, usb = 0; - struct unit_times *times; - - times = hashmap_get(unit_times_hashmap, *a); - if (times) - usa = times->activated; - times = hashmap_get(unit_times_hashmap, *b); - if (times) - usb = times->activated; - - return usb - usa; -} - -static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units, - unsigned int branches) { - _cleanup_strv_free_ char **deps = NULL; - char **c; - int r = 0; - usec_t service_longest = 0; - int to_print = 0; - struct unit_times *times; - struct boot_times *boot; - - if (strv_extend(units, name)) - return log_oom(); - - r = list_dependencies_get_dependencies(bus, name, &deps); - if (r < 0) - return r; - - qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare); - - r = acquire_boot_times(bus, &boot); - if (r < 0) - return r; - - STRV_FOREACH(c, deps) { - times = hashmap_get(unit_times_hashmap, *c); - if (times - && times->activated - && times->activated <= boot->finish_time - && (times->activated >= service_longest - || service_longest == 0)) { - service_longest = times->activated; - break; - } - } - - if (service_longest == 0 ) - return r; - - STRV_FOREACH(c, deps) { - times = hashmap_get(unit_times_hashmap, *c); - if (times && times->activated && times->activated <= boot->finish_time && (service_longest - times->activated) <= arg_fuzz) - to_print++; - } - - if (!to_print) - return r; - - STRV_FOREACH(c, deps) { - times = hashmap_get(unit_times_hashmap, *c); - if (!times - || !times->activated - || times->activated > boot->finish_time - || service_longest - times->activated > arg_fuzz) - continue; - - to_print--; - - r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot); - if (r < 0) - return r; - - if (strv_contains(*units, *c)) { - r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0), - true, NULL, boot); - if (r < 0) - return r; - continue; - } - - r = list_dependencies_one(bus, *c, level + 1, units, - (branches << 1) | (to_print ? 1 : 0)); - if (r < 0) - return r; - - if (!to_print) - break; - } - return 0; -} - -static int list_dependencies(sd_bus *bus, const char *name) { - _cleanup_strv_free_ char **units = NULL; - char ts[FORMAT_TIMESPAN_MAX]; - struct unit_times *times; - int r; - const char *id; - _cleanup_free_ char *path = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - struct boot_times *boot; - - assert(bus); - - path = unit_dbus_path_from_name(name); - if (path == NULL) - return -ENOMEM; - - r = sd_bus_get_property( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "Id", - &error, - &reply, - "s"); - if (r < 0) { - log_error("Failed to get ID: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "s", &id); - if (r < 0) - return bus_log_parse_error(r); - - times = hashmap_get(unit_times_hashmap, id); - - r = acquire_boot_times(bus, &boot); - if (r < 0) - return r; - - if (times) { - if (times->time) - printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED, id, - format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_NORMAL); - else if (times->activated > boot->userspace_time) - printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); - else - printf("%s\n", id); - } - - return list_dependencies_one(bus, name, 0, &units, 0); -} - -static int analyze_critical_chain(sd_bus *bus, char *names[]) { - struct unit_times *times; - unsigned int i; - Hashmap *h; - int n, r; - - n = acquire_time_data(bus, ×); - if (n <= 0) - return n; - - h = hashmap_new(&string_hash_ops); - if (!h) - return -ENOMEM; - - for (i = 0; i < (unsigned)n; i++) { - r = hashmap_put(h, times[i].name, ×[i]); - if (r < 0) - return r; - } - unit_times_hashmap = h; - - pager_open_if_enabled(); - - puts("The time after the unit is active or started is printed after the \"@\" character.\n" - "The time the unit takes to start is printed after the \"+\" character.\n"); - - if (!strv_isempty(names)) { - char **name; - STRV_FOREACH(name, names) - list_dependencies(bus, *name); - } else - list_dependencies(bus, SPECIAL_DEFAULT_TARGET); - - hashmap_free(h); - free_unit_times(times, (unsigned) n); - return 0; -} - -static int analyze_blame(sd_bus *bus) { - struct unit_times *times; - unsigned i; - int n; - - n = acquire_time_data(bus, ×); - if (n <= 0) - return n; - - qsort(times, n, sizeof(struct unit_times), compare_unit_time); - - pager_open_if_enabled(); - - for (i = 0; i < (unsigned) n; i++) { - char ts[FORMAT_TIMESPAN_MAX]; - - if (times[i].time > 0) - printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name); - } - - free_unit_times(times, (unsigned) n); - return 0; -} - -static int analyze_time(sd_bus *bus) { - _cleanup_free_ char *buf = NULL; - int r; - - r = pretty_boot_time(bus, &buf); - if (r < 0) - return r; - - puts(buf); - return 0; -} - -static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[], char* from_patterns[], char* to_patterns[]) { - _cleanup_strv_free_ char **units = NULL; - char **unit; - int r; - bool match_patterns; - - assert(u); - assert(prop); - assert(color); - - match_patterns = strv_fnmatch(patterns, u->id, 0); - - if (!strv_isempty(from_patterns) && - !match_patterns && - !strv_fnmatch(from_patterns, u->id, 0)) - return 0; - - r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units); - if (r < 0) - return r; - - STRV_FOREACH(unit, units) { - bool match_patterns2; - - match_patterns2 = strv_fnmatch(patterns, *unit, 0); - - if (!strv_isempty(to_patterns) && - !match_patterns2 && - !strv_fnmatch(to_patterns, *unit, 0)) - continue; - - if (!strv_isempty(patterns) && !match_patterns && !match_patterns2) - continue; - - printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color); - } - - return 0; -} - -static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *from_patterns[], char *to_patterns[]) { - int r; - - assert(bus); - assert(u); - - if (IN_SET(arg_dot, DEP_ORDER, DEP_ALL)) { - r = graph_one_property(bus, u, "After", "green", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - } - - if (IN_SET(arg_dot, DEP_REQUIRE, DEP_ALL)) { - r = graph_one_property(bus, u, "Requires", "black", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - r = graph_one_property(bus, u, "Requisite", "darkblue", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - r = graph_one_property(bus, u, "Wants", "grey66", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - r = graph_one_property(bus, u, "Conflicts", "red", patterns, from_patterns, to_patterns); - if (r < 0) - return r; - } - - return 0; -} - -static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) { - _cleanup_strv_free_ char **expanded_patterns = NULL; - char **pattern; - int r; - - STRV_FOREACH(pattern, patterns) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *unit = NULL, *unit_id = NULL; - - if (strv_extend(&expanded_patterns, *pattern) < 0) - return log_oom(); - - if (string_is_glob(*pattern)) - continue; - - unit = unit_dbus_path_from_name(*pattern); - if (!unit) - return log_oom(); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - unit, - "org.freedesktop.systemd1.Unit", - "Id", - &error, - &unit_id); - if (r < 0) - return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r)); - - if (!streq(*pattern, unit_id)) { - if (strv_extend(&expanded_patterns, unit_id) < 0) - return log_oom(); - } - } - - *ret = expanded_patterns; - expanded_patterns = NULL; /* do not free */ - - return 0; -} - -static int dot(sd_bus *bus, char* patterns[]) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_strv_free_ char **expanded_patterns = NULL; - _cleanup_strv_free_ char **expanded_from_patterns = NULL; - _cleanup_strv_free_ char **expanded_to_patterns = NULL; - int r; - UnitInfo u; - - r = expand_patterns(bus, patterns, &expanded_patterns); - if (r < 0) - return r; - - r = expand_patterns(bus, arg_dot_from_patterns, &expanded_from_patterns); - if (r < 0) - return r; - - r = expand_patterns(bus, arg_dot_to_patterns, &expanded_to_patterns); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListUnits", - &error, - &reply, - ""); - if (r < 0) { - log_error("Failed to list units: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); - if (r < 0) - return bus_log_parse_error(r); - - printf("digraph systemd {\n"); - - while ((r = bus_parse_unit_info(reply, &u)) > 0) { - - r = graph_one(bus, &u, expanded_patterns, expanded_from_patterns, expanded_to_patterns); - if (r < 0) - return r; - } - if (r < 0) - return bus_log_parse_error(r); - - printf("}\n"); - - log_info(" Color legend: black = Requires\n" - " dark blue = Requisite\n" - " dark grey = Wants\n" - " red = Conflicts\n" - " green = After\n"); - - if (on_tty()) - log_notice("-- You probably want to process this output with graphviz' dot tool.\n" - "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n"); - - return 0; -} - -static int dump(sd_bus *bus, char **args) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *text = NULL; - int r; - - if (!strv_isempty(args)) { - log_error("Too many arguments."); - return -E2BIG; - } - - pager_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Dump", - &error, - &reply, - ""); - if (r < 0) - return log_error_errno(r, "Failed issue method call: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "s", &text); - if (r < 0) - return bus_log_parse_error(r); - - fputs(text, stdout); - return 0; -} - -static int set_log_level(sd_bus *bus, char **args) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(args); - - if (strv_length(args) != 1) { - log_error("This command expects one argument only."); - return -E2BIG; - } - - r = sd_bus_set_property( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LogLevel", - &error, - "s", - args[0]); - if (r < 0) - return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); - - return 0; -} - -static int set_log_target(sd_bus *bus, char **args) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(args); - - if (strv_length(args) != 1) { - log_error("This command expects one argument only."); - return -E2BIG; - } - - r = sd_bus_set_property( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "LogTarget", - &error, - "s", - args[0]); - if (r < 0) - return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); - - return 0; -} - -static void help(void) { - - pager_open_if_enabled(); - - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Profile systemd, show unit dependencies, check unit files.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --system Operate on system systemd instance\n" - " --user Operate on user systemd instance\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --order Show only order in the graph\n" - " --require Show only requirement in the graph\n" - " --from-pattern=GLOB Show only origins in the graph\n" - " --to-pattern=GLOB Show only destinations in the graph\n" - " --fuzz=SECONDS Also print also services which finished SECONDS\n" - " earlier than the latest in the branch\n" - " --man[=BOOL] Do [not] check for existence of man pages\n\n" - "Commands:\n" - " time Print time spent in the kernel\n" - " blame Print list of running units ordered by time to init\n" - " critical-chain Print a tree of the time critical chain of units\n" - " plot Output SVG graphic showing service initialization\n" - " dot Output dependency graph in dot(1) format\n" - " set-log-level LEVEL Set logging threshold for manager\n" - " set-log-target TARGET Set logging target for manager\n" - " dump Output state serialization of service manager\n" - " verify FILE... Check unit files for correctness\n" - , program_invocation_short_name); - - /* When updating this list, including descriptions, apply - * changes to shell-completion/bash/systemd-analyze and - * shell-completion/zsh/_systemd-analyze too. */ -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_ORDER, - ARG_REQUIRE, - ARG_USER, - ARG_SYSTEM, - ARG_DOT_FROM_PATTERN, - ARG_DOT_TO_PATTERN, - ARG_FUZZ, - ARG_NO_PAGER, - ARG_MAN, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "order", no_argument, NULL, ARG_ORDER }, - { "require", no_argument, NULL, ARG_REQUIRE }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN }, - { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN }, - { "fuzz", required_argument, NULL, ARG_FUZZ }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "man", optional_argument, NULL, ARG_MAN }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - {} - }; - - int r, c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_USER: - arg_user = true; - break; - - case ARG_SYSTEM: - arg_user = false; - break; - - case ARG_ORDER: - arg_dot = DEP_ORDER; - break; - - case ARG_REQUIRE: - arg_dot = DEP_REQUIRE; - break; - - case ARG_DOT_FROM_PATTERN: - if (strv_extend(&arg_dot_from_patterns, optarg) < 0) - return log_oom(); - - break; - - case ARG_DOT_TO_PATTERN: - if (strv_extend(&arg_dot_to_patterns, optarg) < 0) - return log_oom(); - - break; - - case ARG_FUZZ: - r = parse_sec(optarg, &arg_fuzz); - if (r < 0) - return r; - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case ARG_MAN: - if (optarg) { - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --man= argument."); - return -EINVAL; - } - - arg_man = !!r; - } else - arg_man = true; - - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option code."); - } - - return 1; /* work to do */ -} - -int main(int argc, char *argv[]) { - int r; - - setlocale(LC_ALL, ""); - setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */ - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (streq_ptr(argv[optind], "verify")) - r = verify_units(argv+optind+1, - arg_user ? MANAGER_USER : MANAGER_SYSTEM, - arg_man); - else { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - - r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); - if (r < 0) { - log_error_errno(r, "Failed to create bus connection: %m"); - goto finish; - } - - if (!argv[optind] || streq(argv[optind], "time")) - r = analyze_time(bus); - else if (streq(argv[optind], "blame")) - r = analyze_blame(bus); - else if (streq(argv[optind], "critical-chain")) - r = analyze_critical_chain(bus, argv+optind+1); - else if (streq(argv[optind], "plot")) - r = analyze_plot(bus); - else if (streq(argv[optind], "dot")) - r = dot(bus, argv+optind+1); - else if (streq(argv[optind], "dump")) - r = dump(bus, argv+optind+1); - else if (streq(argv[optind], "set-log-level")) - r = set_log_level(bus, argv+optind+1); - else if (streq(argv[optind], "set-log-target")) - r = set_log_target(bus, argv+optind+1); - else - log_error("Unknown operation '%s'.", argv[optind]); - } - -finish: - pager_close(); - - strv_free(arg_dot_from_patterns); - strv_free(arg_dot_to_patterns); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/ask-password/Makefile b/src/ask-password/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/ask-password/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c deleted file mode 100644 index adc9286612..0000000000 --- a/src/ask-password/ask-password.c +++ /dev/null @@ -1,179 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "ask-password-api.h" -#include "def.h" -#include "log.h" -#include "macro.h" -#include "strv.h" - -static const char *arg_icon = NULL; -static const char *arg_id = NULL; -static const char *arg_keyname = NULL; -static char *arg_message = NULL; -static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC; -static bool arg_multiple = false; -static AskPasswordFlags arg_flags = ASK_PASSWORD_PUSH_CACHE; - -static void help(void) { - printf("%s [OPTIONS...] MESSAGE\n\n" - "Query the user for a system passphrase, via the TTY or an UI agent.\n\n" - " -h --help Show this help\n" - " --icon=NAME Icon name\n" - " --id=ID Query identifier (e.g. \"cryptsetup:/dev/sda5\")\n" - " --keyname=NAME Kernel key name for caching passwords (e.g. \"cryptsetup\")\n" - " --timeout=SEC Timeout in seconds\n" - " --echo Do not mask input (useful for usernames)\n" - " --no-tty Ask question via agent even on TTY\n" - " --accept-cached Accept cached passwords\n" - " --multiple List multiple passwords if available\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_ICON = 0x100, - ARG_TIMEOUT, - ARG_ECHO, - ARG_NO_TTY, - ARG_ACCEPT_CACHED, - ARG_MULTIPLE, - ARG_ID, - ARG_KEYNAME, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "icon", required_argument, NULL, ARG_ICON }, - { "timeout", required_argument, NULL, ARG_TIMEOUT }, - { "echo", no_argument, NULL, ARG_ECHO }, - { "no-tty", no_argument, NULL, ARG_NO_TTY }, - { "accept-cached", no_argument, NULL, ARG_ACCEPT_CACHED }, - { "multiple", no_argument, NULL, ARG_MULTIPLE }, - { "id", required_argument, NULL, ARG_ID }, - { "keyname", required_argument, NULL, ARG_KEYNAME }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_ICON: - arg_icon = optarg; - break; - - case ARG_TIMEOUT: - if (parse_sec(optarg, &arg_timeout) < 0) { - log_error("Failed to parse --timeout parameter %s", optarg); - return -EINVAL; - } - break; - - case ARG_ECHO: - arg_flags |= ASK_PASSWORD_ECHO; - break; - - case ARG_NO_TTY: - arg_flags |= ASK_PASSWORD_NO_TTY; - break; - - case ARG_ACCEPT_CACHED: - arg_flags |= ASK_PASSWORD_ACCEPT_CACHED; - break; - - case ARG_MULTIPLE: - arg_multiple = true; - break; - - case ARG_ID: - arg_id = optarg; - break; - - case ARG_KEYNAME: - arg_keyname = optarg; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (argc > optind) { - arg_message = strv_join(argv + optind, " "); - if (!arg_message) - return log_oom(); - } - - return 1; -} - -int main(int argc, char *argv[]) { - _cleanup_strv_free_erase_ char **l = NULL; - usec_t timeout; - char **p; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (arg_timeout > 0) - timeout = now(CLOCK_MONOTONIC) + arg_timeout; - else - timeout = 0; - - r = ask_password_auto(arg_message, arg_icon, arg_id, arg_keyname, timeout, arg_flags, &l); - if (r < 0) { - log_error_errno(r, "Failed to query password: %m"); - goto finish; - } - - STRV_FOREACH(p, l) { - puts(*p); - - if (!arg_multiple) - break; - } - -finish: - free(arg_message); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/backlight/Makefile b/src/backlight/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/backlight/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/backlight/backlight.c b/src/backlight/backlight.c deleted file mode 100644 index 45be135a23..0000000000 --- a/src/backlight/backlight.c +++ /dev/null @@ -1,434 +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 . -***/ - -#include "libudev.h" - -#include "alloc-util.h" -#include "def.h" -#include "escape.h" -#include "fileio.h" -#include "mkdir.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "string-util.h" -#include "udev-util.h" -#include "util.h" - -static struct udev_device *find_pci_or_platform_parent(struct udev_device *device) { - struct udev_device *parent; - const char *subsystem, *sysname; - - assert(device); - - parent = udev_device_get_parent(device); - if (!parent) - return NULL; - - subsystem = udev_device_get_subsystem(parent); - if (!subsystem) - return NULL; - - sysname = udev_device_get_sysname(parent); - if (!sysname) - return NULL; - - if (streq(subsystem, "drm")) { - const char *c; - - c = startswith(sysname, "card"); - if (!c) - return NULL; - - c += strspn(c, DIGITS); - if (*c == '-') { - /* A connector DRM device, let's ignore all but LVDS and eDP! */ - - if (!startswith(c, "-LVDS-") && - !startswith(c, "-Embedded DisplayPort-")) - return NULL; - } - - } else if (streq(subsystem, "pci")) { - const char *value; - - value = udev_device_get_sysattr_value(parent, "class"); - if (value) { - unsigned long class = 0; - - if (safe_atolu(value, &class) < 0) { - log_warning("Cannot parse PCI class %s of device %s:%s.", - value, subsystem, sysname); - return NULL; - } - - /* Graphics card */ - if (class == 0x30000) - return parent; - } - - } else if (streq(subsystem, "platform")) - return parent; - - return find_pci_or_platform_parent(parent); -} - -static bool same_device(struct udev_device *a, struct udev_device *b) { - assert(a); - assert(b); - - if (!streq_ptr(udev_device_get_subsystem(a), udev_device_get_subsystem(b))) - return false; - - if (!streq_ptr(udev_device_get_sysname(a), udev_device_get_sysname(b))) - return false; - - return true; -} - -static bool validate_device(struct udev *udev, struct udev_device *device) { - _cleanup_udev_enumerate_unref_ struct udev_enumerate *enumerate = NULL; - struct udev_list_entry *item = NULL, *first = NULL; - struct udev_device *parent; - const char *v, *subsystem; - int r; - - assert(udev); - assert(device); - - /* Verify whether we should actually care for a specific - * backlight device. For backlight devices there might be - * multiple ways to access the same control: "firmware" - * (i.e. ACPI), "platform" (i.e. via the machine's EC) and - * "raw" (via the graphics card). In general we should prefer - * "firmware" (i.e. ACPI) or "platform" access over "raw" - * access, in order not to confuse the BIOS/EC, and - * compatibility with possible low-level hotkey handling of - * screen brightness. The kernel will already make sure to - * expose only one of "firmware" and "platform" for the same - * device to userspace. However, we still need to make sure - * that we use "raw" only if no "firmware" or "platform" - * device for the same device exists. */ - - subsystem = udev_device_get_subsystem(device); - if (!streq_ptr(subsystem, "backlight")) - return true; - - v = udev_device_get_sysattr_value(device, "type"); - if (!streq_ptr(v, "raw")) - return true; - - parent = find_pci_or_platform_parent(device); - if (!parent) - return true; - - subsystem = udev_device_get_subsystem(parent); - if (!subsystem) - return true; - - enumerate = udev_enumerate_new(udev); - if (!enumerate) - return true; - - r = udev_enumerate_add_match_subsystem(enumerate, "backlight"); - if (r < 0) - return true; - - r = udev_enumerate_scan_devices(enumerate); - if (r < 0) - return true; - - first = udev_enumerate_get_list_entry(enumerate); - udev_list_entry_foreach(item, first) { - _cleanup_udev_device_unref_ struct udev_device *other; - struct udev_device *other_parent; - const char *other_subsystem; - - other = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); - if (!other) - return true; - - if (same_device(device, other)) - continue; - - v = udev_device_get_sysattr_value(other, "type"); - if (!streq_ptr(v, "platform") && !streq_ptr(v, "firmware")) - continue; - - /* OK, so there's another backlight device, and it's a - * platform or firmware device, so, let's see if we - * can verify it belongs to the same device as - * ours. */ - other_parent = find_pci_or_platform_parent(other); - if (!other_parent) - continue; - - if (same_device(parent, other_parent)) { - /* Both have the same PCI parent, that means - * we are out. */ - log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.", - udev_device_get_sysname(device), - udev_device_get_sysname(other)); - return false; - } - - other_subsystem = udev_device_get_subsystem(other_parent); - if (streq_ptr(other_subsystem, "platform") && streq_ptr(subsystem, "pci")) { - /* The other is connected to the platform bus - * and we are a PCI device, that also means we - * are out. */ - log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.", - udev_device_get_sysname(device), - udev_device_get_sysname(other)); - return false; - } - } - - return true; -} - -static unsigned get_max_brightness(struct udev_device *device) { - int r; - const char *max_brightness_str; - unsigned max_brightness; - - max_brightness_str = udev_device_get_sysattr_value(device, "max_brightness"); - if (!max_brightness_str) { - log_warning("Failed to read 'max_brightness' attribute."); - return 0; - } - - r = safe_atou(max_brightness_str, &max_brightness); - if (r < 0) { - log_warning_errno(r, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str); - return 0; - } - - if (max_brightness <= 0) { - log_warning("Maximum brightness is 0, ignoring device."); - return 0; - } - - return max_brightness; -} - -/* Some systems turn the backlight all the way off at the lowest levels. - * clamp_brightness clamps the saved brightness to at least 1 or 5% of - * max_brightness in case of 'backlight' subsystem. This avoids preserving - * an unreadably dim screen, which would otherwise force the user to - * disable state restoration. */ -static void clamp_brightness(struct udev_device *device, char **value, unsigned max_brightness) { - int r; - unsigned brightness, new_brightness, min_brightness; - const char *subsystem; - - r = safe_atou(*value, &brightness); - if (r < 0) { - log_warning_errno(r, "Failed to parse brightness \"%s\": %m", *value); - return; - } - - subsystem = udev_device_get_subsystem(device); - if (streq_ptr(subsystem, "backlight")) - min_brightness = MAX(1U, max_brightness/20); - else - min_brightness = 0; - - new_brightness = CLAMP(brightness, min_brightness, max_brightness); - if (new_brightness != brightness) { - char *old_value = *value; - - r = asprintf(value, "%u", new_brightness); - if (r < 0) { - log_oom(); - return; - } - - log_info("Saved brightness %s %s to %s.", old_value, - new_brightness > brightness ? - "too low; increasing" : "too high; decreasing", - *value); - - free(old_value); - } -} - -int main(int argc, char *argv[]) { - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_udev_device_unref_ struct udev_device *device = NULL; - _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL; - const char *sysname, *path_id; - unsigned max_brightness; - int r; - - if (argc != 3) { - log_error("This program requires two arguments."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - r = mkdir_p("/var/lib/systemd/backlight", 0755); - if (r < 0) { - log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m"); - return EXIT_FAILURE; - } - - udev = udev_new(); - if (!udev) { - log_oom(); - return EXIT_FAILURE; - } - - sysname = strchr(argv[2], ':'); - if (!sysname) { - log_error("Requires a subsystem and sysname pair specifying a backlight device."); - return EXIT_FAILURE; - } - - ss = strndup(argv[2], sysname - argv[2]); - if (!ss) { - log_oom(); - return EXIT_FAILURE; - } - - sysname++; - - if (!streq(ss, "backlight") && !streq(ss, "leds")) { - log_error("Not a backlight or LED device: '%s:%s'", ss, sysname); - return EXIT_FAILURE; - } - - errno = 0; - device = udev_device_new_from_subsystem_sysname(udev, ss, sysname); - if (!device) { - if (errno > 0) - log_error_errno(errno, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname); - else - log_oom(); - - return EXIT_FAILURE; - } - - /* If max_brightness is 0, then there is no actual backlight - * device. This happens on desktops with Asus mainboards - * that load the eeepc-wmi module. - */ - max_brightness = get_max_brightness(device); - if (max_brightness == 0) - return EXIT_SUCCESS; - - escaped_ss = cescape(ss); - if (!escaped_ss) { - log_oom(); - return EXIT_FAILURE; - } - - escaped_sysname = cescape(sysname); - if (!escaped_sysname) { - log_oom(); - return EXIT_FAILURE; - } - - path_id = udev_device_get_property_value(device, "ID_PATH"); - if (path_id) { - escaped_path_id = cescape(path_id); - if (!escaped_path_id) { - log_oom(); - return EXIT_FAILURE; - } - - saved = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname, NULL); - } else - saved = strjoin("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname, NULL); - - if (!saved) { - log_oom(); - return EXIT_FAILURE; - } - - /* If there are multiple conflicting backlight devices, then - * their probing at boot-time might happen in any order. This - * means the validity checking of the device then is not - * reliable, since it might not see other devices conflicting - * with a specific backlight. To deal with this, we will - * actively delete backlight state files at shutdown (where - * device probing should be complete), so that the validity - * check at boot time doesn't have to be reliable. */ - - if (streq(argv[1], "load")) { - _cleanup_free_ char *value = NULL; - const char *clamp; - - if (shall_restore_state() == 0) - return EXIT_SUCCESS; - - if (!validate_device(udev, device)) - return EXIT_SUCCESS; - - r = read_one_line_file(saved, &value); - if (r < 0) { - - if (r == -ENOENT) - return EXIT_SUCCESS; - - log_error_errno(r, "Failed to read %s: %m", saved); - return EXIT_FAILURE; - } - - clamp = udev_device_get_property_value(device, "ID_BACKLIGHT_CLAMP"); - if (!clamp || parse_boolean(clamp) != 0) /* default to clamping */ - clamp_brightness(device, &value, max_brightness); - - r = udev_device_set_sysattr_value(device, "brightness", value); - if (r < 0) { - log_error_errno(r, "Failed to write system 'brightness' attribute: %m"); - return EXIT_FAILURE; - } - - } else if (streq(argv[1], "save")) { - const char *value; - - if (!validate_device(udev, device)) { - unlink(saved); - return EXIT_SUCCESS; - } - - value = udev_device_get_sysattr_value(device, "brightness"); - if (!value) { - log_error("Failed to read system 'brightness' attribute"); - return EXIT_FAILURE; - } - - r = write_string_file(saved, value, WRITE_STRING_FILE_CREATE); - if (r < 0) { - log_error_errno(r, "Failed to write %s: %m", saved); - return EXIT_FAILURE; - } - - } else { - log_error("Unknown verb %s.", argv[1]); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/src/basic/.gitignore b/src/basic/.gitignore deleted file mode 100644 index e22411e484..0000000000 --- a/src/basic/.gitignore +++ /dev/null @@ -1,16 +0,0 @@ -/cap-from-name.gperf -/cap-from-name.h -/cap-list.txt -/cap-to-name.h -/errno-from-name.gperf -/errno-from-name.h -/errno-list.txt -/errno-to-name.h -/af-from-name.gperf -/af-from-name.h -/af-list.txt -/af-to-name.h -/arphrd-from-name.gperf -/arphrd-from-name.h -/arphrd-list.txt -/arphrd-to-name.h diff --git a/src/basic/Makefile b/src/basic/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/basic/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/basic/MurmurHash2.c b/src/basic/MurmurHash2.c deleted file mode 100644 index 2f4149dbe9..0000000000 --- a/src/basic/MurmurHash2.c +++ /dev/null @@ -1,86 +0,0 @@ -//----------------------------------------------------------------------------- -// MurmurHash2 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. - -// Note - This code makes a few assumptions about how your machine behaves - - -// 1. We can read a 4-byte value from any address without crashing -// 2. sizeof(int) == 4 - -// And it has a few limitations - - -// 1. It will not work incrementally. -// 2. It will not produce the same results on little-endian and big-endian -// machines. - -#include "MurmurHash2.h" - -//----------------------------------------------------------------------------- -// Platform-specific functions and macros - -// Microsoft Visual Studio - -#if defined(_MSC_VER) - -#define BIG_CONSTANT(x) (x) - -// Other compilers - -#else // defined(_MSC_VER) - -#define BIG_CONSTANT(x) (x##LLU) - -#endif // !defined(_MSC_VER) - -//----------------------------------------------------------------------------- - -uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ) -{ - // 'm' and 'r' are mixing constants generated offline. - // They're not really 'magic', they just happen to work well. - - const uint32_t m = 0x5bd1e995; - const int r = 24; - - // Initialize the hash to a 'random' value - - uint32_t h = seed ^ len; - - // Mix 4 bytes at a time into the hash - - const unsigned char * data = (const unsigned char *)key; - - while(len >= 4) - { - uint32_t k = *(uint32_t*)data; - - k *= m; - k ^= k >> r; - k *= m; - - h *= m; - h ^= k; - - data += 4; - len -= 4; - } - - // Handle the last few bytes of the input array - - switch(len) - { - case 3: h ^= data[2] << 16; - case 2: h ^= data[1] << 8; - case 1: h ^= data[0]; - h *= m; - }; - - // Do a few final mixes of the hash to ensure the last few - // bytes are well-incorporated. - - h ^= h >> 13; - h *= m; - h ^= h >> 15; - - return h; -} diff --git a/src/basic/MurmurHash2.h b/src/basic/MurmurHash2.h deleted file mode 100644 index 93362dd485..0000000000 --- a/src/basic/MurmurHash2.h +++ /dev/null @@ -1,33 +0,0 @@ -//----------------------------------------------------------------------------- -// MurmurHash2 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. - -#ifndef _MURMURHASH2_H_ -#define _MURMURHASH2_H_ - -//----------------------------------------------------------------------------- -// Platform-specific functions and macros - -// Microsoft Visual Studio - -#if defined(_MSC_VER) - -typedef unsigned char uint8_t; -typedef unsigned long uint32_t; -typedef unsigned __int64 uint64_t; - -// Other compilers - -#else // defined(_MSC_VER) - -#include - -#endif // !defined(_MSC_VER) - -//----------------------------------------------------------------------------- - -uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ); - -//----------------------------------------------------------------------------- - -#endif // _MURMURHASH2_H_ diff --git a/src/basic/af-list.c b/src/basic/af-list.c deleted file mode 100644 index 3fac9c508b..0000000000 --- a/src/basic/af-list.c +++ /dev/null @@ -1,56 +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 . -***/ - -#include -#include - -#include "af-list.h" -#include "macro.h" - -static const struct af_name* lookup_af(register const char *str, register unsigned int len); - -#include "af-from-name.h" -#include "af-to-name.h" - -const char *af_to_name(int id) { - - if (id <= 0) - return NULL; - - if (id >= (int) ELEMENTSOF(af_names)) - return NULL; - - return af_names[id]; -} - -int af_from_name(const char *name) { - const struct af_name *sc; - - assert(name); - - sc = lookup_af(name, strlen(name)); - if (!sc) - return AF_UNSPEC; - - return sc->id; -} - -int af_max(void) { - return ELEMENTSOF(af_names); -} diff --git a/src/basic/af-list.h b/src/basic/af-list.h deleted file mode 100644 index 135248dc64..0000000000 --- a/src/basic/af-list.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -const char *af_to_name(int id); -int af_from_name(const char *name); - -int af_max(void); diff --git a/src/basic/alloc-util.c b/src/basic/alloc-util.c deleted file mode 100644 index b540dcddf5..0000000000 --- a/src/basic/alloc-util.c +++ /dev/null @@ -1,83 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "alloc-util.h" -#include "macro.h" -#include "util.h" - -void* memdup(const void *p, size_t l) { - void *r; - - assert(p); - - r = malloc(l); - if (!r) - return NULL; - - memcpy(r, p, l); - return r; -} - -void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) { - size_t a, newalloc; - void *q; - - assert(p); - assert(allocated); - - if (*allocated >= need) - return *p; - - newalloc = MAX(need * 2, 64u / size); - a = newalloc * size; - - /* check for overflows */ - if (a < size * need) - return NULL; - - q = realloc(*p, a); - if (!q) - return NULL; - - *p = q; - *allocated = newalloc; - return q; -} - -void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size) { - size_t prev; - uint8_t *q; - - assert(p); - assert(allocated); - - prev = *allocated; - - q = greedy_realloc(p, allocated, need, size); - if (!q) - return NULL; - - if (*allocated > prev) - memzero(q + prev * size, (*allocated - prev) * size); - - return q; -} diff --git a/src/basic/alloc-util.h b/src/basic/alloc-util.h deleted file mode 100644 index 679ba7f398..0000000000 --- a/src/basic/alloc-util.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "macro.h" - -#define new(t, n) ((t*) malloc_multiply(sizeof(t), (n))) - -#define new0(t, n) ((t*) calloc((n), sizeof(t))) - -#define newa(t, n) ((t*) alloca(sizeof(t)*(n))) - -#define newa0(t, n) ((t*) alloca0(sizeof(t)*(n))) - -#define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n))) - -#define malloc0(n) (calloc(1, (n))) - -static inline void *mfree(void *memory) { - free(memory); - return NULL; -} - -void* memdup(const void *p, size_t l) _alloc_(2); - -static inline void freep(void *p) { - free(*(void**) p); -} - -#define _cleanup_free_ _cleanup_(freep) - -_malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t a, size_t b) { - if (_unlikely_(b != 0 && a > ((size_t) -1) / b)) - return NULL; - - return malloc(a * b); -} - -_alloc_(2, 3) static inline void *realloc_multiply(void *p, size_t a, size_t b) { - if (_unlikely_(b != 0 && a > ((size_t) -1) / b)) - return NULL; - - return realloc(p, a * b); -} - -_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t a, size_t b) { - if (_unlikely_(b != 0 && a > ((size_t) -1) / b)) - return NULL; - - return memdup(p, a * b); -} - -void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size); -void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size); - -#define GREEDY_REALLOC(array, allocated, need) \ - greedy_realloc((void**) &(array), &(allocated), (need), sizeof((array)[0])) - -#define GREEDY_REALLOC0(array, allocated, need) \ - greedy_realloc0((void**) &(array), &(allocated), (need), sizeof((array)[0])) - -#define alloca0(n) \ - ({ \ - char *_new_; \ - size_t _len_ = n; \ - _new_ = alloca(_len_); \ - (void *) memset(_new_, 0, _len_); \ - }) - -/* It's not clear what alignment glibc/gcc alloca() guarantee, hence provide a guaranteed safe version */ -#define alloca_align(size, align) \ - ({ \ - void *_ptr_; \ - size_t _mask_ = (align) - 1; \ - _ptr_ = alloca((size) + _mask_); \ - (void*)(((uintptr_t)_ptr_ + _mask_) & ~_mask_); \ - }) - -#define alloca0_align(size, align) \ - ({ \ - void *_new_; \ - size_t _size_ = (size); \ - _new_ = alloca_align(_size_, (align)); \ - (void*)memset(_new_, 0, _size_); \ - }) diff --git a/src/basic/arphrd-list.c b/src/basic/arphrd-list.c deleted file mode 100644 index 6792d1ee3f..0000000000 --- a/src/basic/arphrd-list.c +++ /dev/null @@ -1,56 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include - -#include "arphrd-list.h" -#include "macro.h" - -static const struct arphrd_name* lookup_arphrd(register const char *str, register unsigned int len); - -#include "arphrd-from-name.h" -#include "arphrd-to-name.h" - -const char *arphrd_to_name(int id) { - - if (id <= 0) - return NULL; - - if (id >= (int) ELEMENTSOF(arphrd_names)) - return NULL; - - return arphrd_names[id]; -} - -int arphrd_from_name(const char *name) { - const struct arphrd_name *sc; - - assert(name); - - sc = lookup_arphrd(name, strlen(name)); - if (!sc) - return 0; - - return sc->id; -} - -int arphrd_max(void) { - return ELEMENTSOF(arphrd_names); -} diff --git a/src/basic/arphrd-list.h b/src/basic/arphrd-list.h deleted file mode 100644 index c0f8758dbe..0000000000 --- a/src/basic/arphrd-list.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -const char *arphrd_to_name(int id); -int arphrd_from_name(const char *name); - -int arphrd_max(void); diff --git a/src/basic/async.c b/src/basic/async.c deleted file mode 100644 index a1f163f27b..0000000000 --- a/src/basic/async.c +++ /dev/null @@ -1,94 +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 . -***/ - -#include -#include -#include -#include - -#include "async.h" -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "util.h" - -int asynchronous_job(void* (*func)(void *p), void *arg) { - pthread_attr_t a; - pthread_t t; - int r; - - /* It kinda sucks that we have to resort to threads to - * implement an asynchronous sync(), but well, such is - * life. - * - * Note that issuing this command right before exiting a - * process will cause the process to wait for the sync() to - * complete. This function hence is nicely asynchronous really - * only in long running processes. */ - - r = pthread_attr_init(&a); - if (r > 0) - return -r; - - r = pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED); - if (r > 0) - goto finish; - - r = pthread_create(&t, &a, func, arg); - -finish: - pthread_attr_destroy(&a); - return -r; -} - -static void *sync_thread(void *p) { - sync(); - return NULL; -} - -int asynchronous_sync(void) { - log_debug("Spawning new thread for sync"); - - return asynchronous_job(sync_thread, NULL); -} - -static void *close_thread(void *p) { - assert_se(close_nointr(PTR_TO_FD(p)) != -EBADF); - return NULL; -} - -int asynchronous_close(int fd) { - int r; - - /* This is supposed to behave similar to safe_close(), but - * actually invoke close() asynchronously, so that it will - * never block. Ideally the kernel would have an API for this, - * but it doesn't, so we work around it, and hide this as a - * far away as we can. */ - - if (fd >= 0) { - PROTECT_ERRNO; - - r = asynchronous_job(close_thread, FD_TO_PTR(fd)); - if (r < 0) - assert_se(close_nointr(fd) != -EBADF); - } - - return -1; -} diff --git a/src/basic/async.h b/src/basic/async.h deleted file mode 100644 index 9bd13ff6e0..0000000000 --- a/src/basic/async.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -int asynchronous_job(void* (*func)(void *p), void *arg); - -int asynchronous_sync(void); -int asynchronous_close(int fd); diff --git a/src/basic/audit-util.c b/src/basic/audit-util.c deleted file mode 100644 index 5741fecdd6..0000000000 --- a/src/basic/audit-util.c +++ /dev/null @@ -1,104 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "alloc-util.h" -#include "audit-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "macro.h" -#include "parse-util.h" -#include "process-util.h" -#include "user-util.h" - -int audit_session_from_pid(pid_t pid, uint32_t *id) { - _cleanup_free_ char *s = NULL; - const char *p; - uint32_t u; - int r; - - assert(id); - - /* We don't convert ENOENT to ESRCH here, since we can't - * really distuingish between "audit is not available in the - * kernel" and "the process does not exist", both which will - * result in ENOENT. */ - - p = procfs_file_alloca(pid, "sessionid"); - - r = read_one_line_file(p, &s); - if (r < 0) - return r; - - r = safe_atou32(s, &u); - if (r < 0) - return r; - - if (u == AUDIT_SESSION_INVALID || u <= 0) - return -ENODATA; - - *id = u; - return 0; -} - -int audit_loginuid_from_pid(pid_t pid, uid_t *uid) { - _cleanup_free_ char *s = NULL; - const char *p; - uid_t u; - int r; - - assert(uid); - - p = procfs_file_alloca(pid, "loginuid"); - - r = read_one_line_file(p, &s); - if (r < 0) - return r; - - r = parse_uid(s, &u); - if (r == -ENXIO) /* the UID was -1 */ - return -ENODATA; - if (r < 0) - return r; - - *uid = (uid_t) u; - return 0; -} - -bool use_audit(void) { - static int cached_use = -1; - - if (cached_use < 0) { - int fd; - - fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT); - if (fd < 0) - cached_use = errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT; - else { - cached_use = true; - safe_close(fd); - } - } - - return cached_use; -} diff --git a/src/basic/audit-util.h b/src/basic/audit-util.h deleted file mode 100644 index e048503991..0000000000 --- a/src/basic/audit-util.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#define AUDIT_SESSION_INVALID ((uint32_t) -1) - -int audit_session_from_pid(pid_t pid, uint32_t *id); -int audit_loginuid_from_pid(pid_t pid, uid_t *uid); - -bool use_audit(void); diff --git a/src/basic/barrier.c b/src/basic/barrier.c deleted file mode 100644 index 2da633b311..0000000000 --- a/src/basic/barrier.c +++ /dev/null @@ -1,415 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann - - 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "barrier.h" -#include "fd-util.h" -#include "macro.h" - -/** - * Barriers - * This barrier implementation provides a simple synchronization method based - * on file-descriptors that can safely be used between threads and processes. A - * barrier object contains 2 shared counters based on eventfd. Both processes - * can now place barriers and wait for the other end to reach a random or - * specific barrier. - * Barriers are numbered, so you can either wait for the other end to reach any - * barrier or the last barrier that you placed. This way, you can use barriers - * for one-way *and* full synchronization. Note that even-though barriers are - * numbered, these numbers are internal and recycled once both sides reached the - * same barrier (implemented as a simple signed counter). It is thus not - * possible to address barriers by their ID. - * - * Barrier-API: Both ends can place as many barriers via barrier_place() as - * they want and each pair of barriers on both sides will be implicitly linked. - * Each side can use the barrier_wait/sync_*() family of calls to wait for the - * other side to place a specific barrier. barrier_wait_next() waits until the - * other side calls barrier_place(). No links between the barriers are - * considered and this simply serves as most basic asynchronous barrier. - * barrier_sync_next() is like barrier_wait_next() and waits for the other side - * to place their next barrier via barrier_place(). However, it only waits for - * barriers that are linked to a barrier we already placed. If the other side - * already placed more barriers than we did, barrier_sync_next() returns - * immediately. - * barrier_sync() extends barrier_sync_next() and waits until the other end - * placed as many barriers via barrier_place() as we did. If they already placed - * as many as we did (or more), it returns immediately. - * - * Additionally to basic barriers, an abortion event is available. - * barrier_abort() places an abortion event that cannot be undone. An abortion - * immediately cancels all placed barriers and replaces them. Any running and - * following wait/sync call besides barrier_wait_abortion() will immediately - * return false on both sides (otherwise, they always return true). - * barrier_abort() can be called multiple times on both ends and will be a - * no-op if already called on this side. - * barrier_wait_abortion() can be used to wait for the other side to call - * barrier_abort() and is the only wait/sync call that does not return - * immediately if we aborted outself. It only returns once the other side - * called barrier_abort(). - * - * Barriers can be used for in-process and inter-process synchronization. - * However, for in-process synchronization you could just use mutexes. - * Therefore, main target is IPC and we require both sides to *not* share the FD - * table. If that's given, barriers provide target tracking: If the remote side - * exit()s, an abortion event is implicitly queued on the other side. This way, - * a sync/wait call will be woken up if the remote side crashed or exited - * unexpectedly. However, note that these abortion events are only queued if the - * barrier-queue has been drained. Therefore, it is safe to place a barrier and - * exit. The other side can safely wait on the barrier even though the exit - * queued an abortion event. Usually, the abortion event would overwrite the - * barrier, however, that's not true for exit-abortion events. Those are only - * queued if the barrier-queue is drained (thus, the receiving side has placed - * more barriers than the remote side). - */ - -/** - * barrier_create() - Initialize a barrier object - * @obj: barrier to initialize - * - * This initializes a barrier object. The caller is responsible of allocating - * the memory and keeping it valid. The memory does not have to be zeroed - * beforehand. - * Two eventfd objects are allocated for each barrier. If allocation fails, an - * error is returned. - * - * If this function fails, the barrier is reset to an invalid state so it is - * safe to call barrier_destroy() on the object regardless whether the - * initialization succeeded or not. - * - * The caller is responsible to destroy the object via barrier_destroy() before - * releasing the underlying memory. - * - * Returns: 0 on success, negative error code on failure. - */ -int barrier_create(Barrier *b) { - _cleanup_(barrier_destroyp) Barrier *staging = b; - int r; - - assert(b); - - b->me = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); - if (b->me < 0) - return -errno; - - b->them = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); - if (b->them < 0) - return -errno; - - r = pipe2(b->pipe, O_CLOEXEC | O_NONBLOCK); - if (r < 0) - return -errno; - - staging = NULL; - return 0; -} - -/** - * barrier_destroy() - Destroy a barrier object - * @b: barrier to destroy or NULL - * - * This destroys a barrier object that has previously been passed to - * barrier_create(). The object is released and reset to invalid - * state. Therefore, it is safe to call barrier_destroy() multiple - * times or even if barrier_create() failed. However, barrier must be - * always initialized with BARRIER_NULL. - * - * If @b is NULL, this is a no-op. - */ -void barrier_destroy(Barrier *b) { - if (!b) - return; - - b->me = safe_close(b->me); - b->them = safe_close(b->them); - safe_close_pair(b->pipe); - b->barriers = 0; -} - -/** - * barrier_set_role() - Set the local role of the barrier - * @b: barrier to operate on - * @role: role to set on the barrier - * - * This sets the roles on a barrier object. This is needed to know - * which side of the barrier you're on. Usually, the parent creates - * the barrier via barrier_create() and then calls fork() or clone(). - * Therefore, the FDs are duplicated and the child retains the same - * barrier object. - * - * Both sides need to call barrier_set_role() after fork() or clone() - * are done. If this is not done, barriers will not work correctly. - * - * Note that barriers could be supported without fork() or clone(). However, - * this is currently not needed so it hasn't been implemented. - */ -void barrier_set_role(Barrier *b, unsigned int role) { - int fd; - - assert(b); - assert(role == BARRIER_PARENT || role == BARRIER_CHILD); - /* make sure this is only called once */ - assert(b->pipe[0] >= 0 && b->pipe[1] >= 0); - - if (role == BARRIER_PARENT) - b->pipe[1] = safe_close(b->pipe[1]); - else { - b->pipe[0] = safe_close(b->pipe[0]); - - /* swap me/them for children */ - fd = b->me; - b->me = b->them; - b->them = fd; - } -} - -/* places barrier; returns false if we aborted, otherwise true */ -static bool barrier_write(Barrier *b, uint64_t buf) { - ssize_t len; - - /* prevent new sync-points if we already aborted */ - if (barrier_i_aborted(b)) - return false; - - assert(b->me >= 0); - do { - len = write(b->me, &buf, sizeof(buf)); - } while (len < 0 && IN_SET(errno, EAGAIN, EINTR)); - - if (len != sizeof(buf)) - goto error; - - /* lock if we aborted */ - if (buf >= (uint64_t)BARRIER_ABORTION) { - if (barrier_they_aborted(b)) - b->barriers = BARRIER_WE_ABORTED; - else - b->barriers = BARRIER_I_ABORTED; - } else if (!barrier_is_aborted(b)) - b->barriers += buf; - - return !barrier_i_aborted(b); - -error: - /* If there is an unexpected error, we have to make this fatal. There - * is no way we can recover from sync-errors. Therefore, we close the - * pipe-ends and treat this as abortion. The other end will notice the - * pipe-close and treat it as abortion, too. */ - - safe_close_pair(b->pipe); - b->barriers = BARRIER_WE_ABORTED; - return false; -} - -/* waits for barriers; returns false if they aborted, otherwise true */ -static bool barrier_read(Barrier *b, int64_t comp) { - if (barrier_they_aborted(b)) - return false; - - while (b->barriers > comp) { - struct pollfd pfd[2] = { - { .fd = b->pipe[0] >= 0 ? b->pipe[0] : b->pipe[1], - .events = POLLHUP }, - { .fd = b->them, - .events = POLLIN }}; - uint64_t buf; - int r; - - r = poll(pfd, 2, -1); - if (r < 0 && IN_SET(errno, EAGAIN, EINTR)) - continue; - else if (r < 0) - goto error; - - if (pfd[1].revents) { - ssize_t len; - - /* events on @them signal new data for us */ - len = read(b->them, &buf, sizeof(buf)); - if (len < 0 && IN_SET(errno, EAGAIN, EINTR)) - continue; - - if (len != sizeof(buf)) - goto error; - } else if (pfd[0].revents & (POLLHUP | POLLERR | POLLNVAL)) - /* POLLHUP on the pipe tells us the other side exited. - * We treat this as implicit abortion. But we only - * handle it if there's no event on the eventfd. This - * guarantees that exit-abortions do not overwrite real - * barriers. */ - buf = BARRIER_ABORTION; - else - continue; - - /* lock if they aborted */ - if (buf >= (uint64_t)BARRIER_ABORTION) { - if (barrier_i_aborted(b)) - b->barriers = BARRIER_WE_ABORTED; - else - b->barriers = BARRIER_THEY_ABORTED; - } else if (!barrier_is_aborted(b)) - b->barriers -= buf; - } - - return !barrier_they_aborted(b); - -error: - /* If there is an unexpected error, we have to make this fatal. There - * is no way we can recover from sync-errors. Therefore, we close the - * pipe-ends and treat this as abortion. The other end will notice the - * pipe-close and treat it as abortion, too. */ - - safe_close_pair(b->pipe); - b->barriers = BARRIER_WE_ABORTED; - return false; -} - -/** - * barrier_place() - Place a new barrier - * @b: barrier object - * - * This places a new barrier on the barrier object. If either side already - * aborted, this is a no-op and returns "false". Otherwise, the barrier is - * placed and this returns "true". - * - * Returns: true if barrier was placed, false if either side aborted. - */ -bool barrier_place(Barrier *b) { - assert(b); - - if (barrier_is_aborted(b)) - return false; - - barrier_write(b, BARRIER_SINGLE); - return true; -} - -/** - * barrier_abort() - Abort the synchronization - * @b: barrier object to abort - * - * This aborts the barrier-synchronization. If barrier_abort() was already - * called on this side, this is a no-op. Otherwise, the barrier is put into the - * ABORT-state and will stay there. The other side is notified about the - * abortion. Any following attempt to place normal barriers or to wait on normal - * barriers will return immediately as "false". - * - * You can wait for the other side to call barrier_abort(), too. Use - * barrier_wait_abortion() for that. - * - * Returns: false if the other side already aborted, true otherwise. - */ -bool barrier_abort(Barrier *b) { - assert(b); - - barrier_write(b, BARRIER_ABORTION); - return !barrier_they_aborted(b); -} - -/** - * barrier_wait_next() - Wait for the next barrier of the other side - * @b: barrier to operate on - * - * This waits until the other side places its next barrier. This is independent - * of any barrier-links and just waits for any next barrier of the other side. - * - * If either side aborted, this returns false. - * - * Returns: false if either side aborted, true otherwise. - */ -bool barrier_wait_next(Barrier *b) { - assert(b); - - if (barrier_is_aborted(b)) - return false; - - barrier_read(b, b->barriers - 1); - return !barrier_is_aborted(b); -} - -/** - * barrier_wait_abortion() - Wait for the other side to abort - * @b: barrier to operate on - * - * This waits until the other side called barrier_abort(). This can be called - * regardless whether the local side already called barrier_abort() or not. - * - * If the other side has already aborted, this returns immediately. - * - * Returns: false if the local side aborted, true otherwise. - */ -bool barrier_wait_abortion(Barrier *b) { - assert(b); - - barrier_read(b, BARRIER_THEY_ABORTED); - return !barrier_i_aborted(b); -} - -/** - * barrier_sync_next() - Wait for the other side to place a next linked barrier - * @b: barrier to operate on - * - * This is like barrier_wait_next() and waits for the other side to call - * barrier_place(). However, this only waits for linked barriers. That means, if - * the other side already placed more barriers than (or as much as) we did, this - * returns immediately instead of waiting. - * - * If either side aborted, this returns false. - * - * Returns: false if either side aborted, true otherwise. - */ -bool barrier_sync_next(Barrier *b) { - assert(b); - - if (barrier_is_aborted(b)) - return false; - - barrier_read(b, MAX((int64_t)0, b->barriers - 1)); - return !barrier_is_aborted(b); -} - -/** - * barrier_sync() - Wait for the other side to place as many barriers as we did - * @b: barrier to operate on - * - * This is like barrier_sync_next() but waits for the other side to call - * barrier_place() as often as we did (in total). If they already placed as much - * as we did (or more), this returns immediately instead of waiting. - * - * If either side aborted, this returns false. - * - * Returns: false if either side aborted, true otherwise. - */ -bool barrier_sync(Barrier *b) { - assert(b); - - if (barrier_is_aborted(b)) - return false; - - barrier_read(b, 0); - return !barrier_is_aborted(b); -} diff --git a/src/basic/barrier.h b/src/basic/barrier.h deleted file mode 100644 index 6347fddc4d..0000000000 --- a/src/basic/barrier.h +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 David Herrmann - - 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 . -***/ - -#include -#include -#include - -#include "macro.h" - -/* See source file for an API description. */ - -typedef struct Barrier Barrier; - -enum { - BARRIER_SINGLE = 1LL, - BARRIER_ABORTION = INT64_MAX, - - /* bias values to store state; keep @WE < @THEY < @I */ - BARRIER_BIAS = INT64_MIN, - BARRIER_WE_ABORTED = BARRIER_BIAS + 1LL, - BARRIER_THEY_ABORTED = BARRIER_BIAS + 2LL, - BARRIER_I_ABORTED = BARRIER_BIAS + 3LL, -}; - -enum { - BARRIER_PARENT, - BARRIER_CHILD, -}; - -struct Barrier { - int me; - int them; - int pipe[2]; - int64_t barriers; -}; - -#define BARRIER_NULL {-1, -1, {-1, -1}, 0} - -int barrier_create(Barrier *obj); -void barrier_destroy(Barrier *b); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Barrier*, barrier_destroy); - -void barrier_set_role(Barrier *b, unsigned int role); - -bool barrier_place(Barrier *b); -bool barrier_abort(Barrier *b); - -bool barrier_wait_next(Barrier *b); -bool barrier_wait_abortion(Barrier *b); -bool barrier_sync_next(Barrier *b); -bool barrier_sync(Barrier *b); - -static inline bool barrier_i_aborted(Barrier *b) { - return b->barriers == BARRIER_I_ABORTED || b->barriers == BARRIER_WE_ABORTED; -} - -static inline bool barrier_they_aborted(Barrier *b) { - return b->barriers == BARRIER_THEY_ABORTED || b->barriers == BARRIER_WE_ABORTED; -} - -static inline bool barrier_we_aborted(Barrier *b) { - return b->barriers == BARRIER_WE_ABORTED; -} - -static inline bool barrier_is_aborted(Barrier *b) { - return b->barriers == BARRIER_I_ABORTED || b->barriers == BARRIER_THEY_ABORTED || b->barriers == BARRIER_WE_ABORTED; -} - -static inline bool barrier_place_and_sync(Barrier *b) { - (void) barrier_place(b); - return barrier_sync(b); -} diff --git a/src/basic/bitmap.c b/src/basic/bitmap.c deleted file mode 100644 index ad1fda0198..0000000000 --- a/src/basic/bitmap.c +++ /dev/null @@ -1,220 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 Tom Gundersen - - 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "bitmap.h" -#include "hashmap.h" -#include "macro.h" - -struct Bitmap { - uint64_t *bitmaps; - size_t n_bitmaps; - size_t bitmaps_allocated; -}; - -/* Bitmaps are only meant to store relatively small numbers - * (corresponding to, say, an enum), so it is ok to limit - * the max entry. 64k should be plenty. */ -#define BITMAPS_MAX_ENTRY 0xffff - -/* This indicates that we reached the end of the bitmap */ -#define BITMAP_END ((unsigned) -1) - -#define BITMAP_NUM_TO_OFFSET(n) ((n) / (sizeof(uint64_t) * 8)) -#define BITMAP_NUM_TO_REM(n) ((n) % (sizeof(uint64_t) * 8)) -#define BITMAP_OFFSET_TO_NUM(offset, rem) ((offset) * sizeof(uint64_t) * 8 + (rem)) - -Bitmap *bitmap_new(void) { - return new0(Bitmap, 1); -} - -void bitmap_free(Bitmap *b) { - if (!b) - return; - - free(b->bitmaps); - free(b); -} - -int bitmap_ensure_allocated(Bitmap **b) { - Bitmap *a; - - assert(b); - - if (*b) - return 0; - - a = bitmap_new(); - if (!a) - return -ENOMEM; - - *b = a; - - return 0; -} - -int bitmap_set(Bitmap *b, unsigned n) { - uint64_t bitmask; - unsigned offset; - - assert(b); - - /* we refuse to allocate huge bitmaps */ - if (n > BITMAPS_MAX_ENTRY) - return -ERANGE; - - offset = BITMAP_NUM_TO_OFFSET(n); - - if (offset >= b->n_bitmaps) { - if (!GREEDY_REALLOC0(b->bitmaps, b->bitmaps_allocated, offset + 1)) - return -ENOMEM; - - b->n_bitmaps = offset + 1; - } - - bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); - - b->bitmaps[offset] |= bitmask; - - return 0; -} - -void bitmap_unset(Bitmap *b, unsigned n) { - uint64_t bitmask; - unsigned offset; - - if (!b) - return; - - offset = BITMAP_NUM_TO_OFFSET(n); - - if (offset >= b->n_bitmaps) - return; - - bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); - - b->bitmaps[offset] &= ~bitmask; -} - -bool bitmap_isset(Bitmap *b, unsigned n) { - uint64_t bitmask; - unsigned offset; - - if (!b) - return false; - - offset = BITMAP_NUM_TO_OFFSET(n); - - if (offset >= b->n_bitmaps) - return false; - - bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); - - return !!(b->bitmaps[offset] & bitmask); -} - -bool bitmap_isclear(Bitmap *b) { - unsigned i; - - if (!b) - return true; - - for (i = 0; i < b->n_bitmaps; i++) - if (b->bitmaps[i] != 0) - return false; - - return true; -} - -void bitmap_clear(Bitmap *b) { - - if (!b) - return; - - b->bitmaps = mfree(b->bitmaps); - b->n_bitmaps = 0; - b->bitmaps_allocated = 0; -} - -bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n) { - uint64_t bitmask; - unsigned offset, rem; - - assert(i); - assert(n); - - if (!b || i->idx == BITMAP_END) - return false; - - offset = BITMAP_NUM_TO_OFFSET(i->idx); - rem = BITMAP_NUM_TO_REM(i->idx); - bitmask = UINT64_C(1) << rem; - - for (; offset < b->n_bitmaps; offset ++) { - if (b->bitmaps[offset]) { - for (; bitmask; bitmask <<= 1, rem ++) { - if (b->bitmaps[offset] & bitmask) { - *n = BITMAP_OFFSET_TO_NUM(offset, rem); - i->idx = *n + 1; - - return true; - } - } - } - - rem = 0; - bitmask = 1; - } - - i->idx = BITMAP_END; - - return false; -} - -bool bitmap_equal(Bitmap *a, Bitmap *b) { - size_t common_n_bitmaps; - Bitmap *c; - unsigned i; - - if (a == b) - return true; - - if (!a != !b) - return false; - - if (!a) - return true; - - common_n_bitmaps = MIN(a->n_bitmaps, b->n_bitmaps); - if (memcmp(a->bitmaps, b->bitmaps, sizeof(uint64_t) * common_n_bitmaps) != 0) - return false; - - c = a->n_bitmaps > b->n_bitmaps ? a : b; - for (i = common_n_bitmaps; i < c->n_bitmaps; i++) - if (c->bitmaps[i] != 0) - return false; - - return true; -} diff --git a/src/basic/bitmap.h b/src/basic/bitmap.h deleted file mode 100644 index f5f8f2f018..0000000000 --- a/src/basic/bitmap.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Tom Gundersen - - 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 . -***/ - -#include - -#include "hashmap.h" -#include "macro.h" - -typedef struct Bitmap Bitmap; - -Bitmap *bitmap_new(void); - -void bitmap_free(Bitmap *b); - -int bitmap_ensure_allocated(Bitmap **b); - -int bitmap_set(Bitmap *b, unsigned n); -void bitmap_unset(Bitmap *b, unsigned n); -bool bitmap_isset(Bitmap *b, unsigned n); -bool bitmap_isclear(Bitmap *b); -void bitmap_clear(Bitmap *b); - -bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n); - -bool bitmap_equal(Bitmap *a, Bitmap *b); - -#define BITMAP_FOREACH(n, b, i) \ - for ((i).idx = 0; bitmap_iterate((b), &(i), (unsigned*)&(n)); ) - -DEFINE_TRIVIAL_CLEANUP_FUNC(Bitmap*, bitmap_free); - -#define _cleanup_bitmap_free_ _cleanup_(bitmap_freep) diff --git a/src/basic/blkid-util.h b/src/basic/blkid-util.h deleted file mode 100644 index 7aa75eb091..0000000000 --- a/src/basic/blkid-util.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#ifdef HAVE_BLKID -#include -#endif - -#include "util.h" - -#ifdef HAVE_BLKID -DEFINE_TRIVIAL_CLEANUP_FUNC(blkid_probe, blkid_free_probe); -#define _cleanup_blkid_free_probe_ _cleanup_(blkid_free_probep) -#endif diff --git a/src/basic/btrfs-ctree.h b/src/basic/btrfs-ctree.h deleted file mode 100644 index 66bdf9736e..0000000000 --- a/src/basic/btrfs-ctree.h +++ /dev/null @@ -1,96 +0,0 @@ -#pragma once - -#include "macro.h" -#include "sparse-endian.h" - -/* Stolen from btrfs' ctree.h */ - -struct btrfs_timespec { - le64_t sec; - le32_t nsec; -} _packed_; - -struct btrfs_disk_key { - le64_t objectid; - uint8_t type; - le64_t offset; -} _packed_; - -struct btrfs_inode_item { - le64_t generation; - le64_t transid; - le64_t size; - le64_t nbytes; - le64_t block_group; - le32_t nlink; - le32_t uid; - le32_t gid; - le32_t mode; - le64_t rdev; - le64_t flags; - le64_t sequence; - le64_t reserved[4]; - struct btrfs_timespec atime; - struct btrfs_timespec ctime; - struct btrfs_timespec mtime; - struct btrfs_timespec otime; -} _packed_; - -struct btrfs_root_item { - struct btrfs_inode_item inode; - le64_t generation; - le64_t root_dirid; - le64_t bytenr; - le64_t byte_limit; - le64_t bytes_used; - le64_t last_snapshot; - le64_t flags; - le32_t refs; - struct btrfs_disk_key drop_progress; - uint8_t drop_level; - uint8_t level; - le64_t generation_v2; - uint8_t uuid[BTRFS_UUID_SIZE]; - uint8_t parent_uuid[BTRFS_UUID_SIZE]; - uint8_t received_uuid[BTRFS_UUID_SIZE]; - le64_t ctransid; - le64_t otransid; - le64_t stransid; - le64_t rtransid; - struct btrfs_timespec ctime; - struct btrfs_timespec otime; - struct btrfs_timespec stime; - struct btrfs_timespec rtime; - le64_t reserved[8]; -} _packed_; - -#define BTRFS_ROOT_SUBVOL_RDONLY (1ULL << 0) - -struct btrfs_qgroup_info_item { - le64_t generation; - le64_t rfer; - le64_t rfer_cmpr; - le64_t excl; - le64_t excl_cmpr; -} _packed_; - -#define BTRFS_QGROUP_LIMIT_MAX_RFER (1ULL << 0) -#define BTRFS_QGROUP_LIMIT_MAX_EXCL (1ULL << 1) -#define BTRFS_QGROUP_LIMIT_RSV_RFER (1ULL << 2) -#define BTRFS_QGROUP_LIMIT_RSV_EXCL (1ULL << 3) -#define BTRFS_QGROUP_LIMIT_RFER_CMPR (1ULL << 4) -#define BTRFS_QGROUP_LIMIT_EXCL_CMPR (1ULL << 5) - -struct btrfs_qgroup_limit_item { - le64_t flags; - le64_t max_rfer; - le64_t max_excl; - le64_t rsv_rfer; - le64_t rsv_excl; -} _packed_; - -struct btrfs_root_ref { - le64_t dirid; - le64_t sequence; - le16_t name_len; -} _packed_; diff --git a/src/basic/btrfs-util.c b/src/basic/btrfs-util.c deleted file mode 100644 index 359d85f2e8..0000000000 --- a/src/basic/btrfs-util.c +++ /dev/null @@ -1,2075 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_LINUX_BTRFS_H -#include -#endif - -#include "alloc-util.h" -#include "btrfs-ctree.h" -#include "btrfs-util.h" -#include "copy.h" -#include "fd-util.h" -#include "fileio.h" -#include "io-util.h" -#include "macro.h" -#include "missing.h" -#include "path-util.h" -#include "selinux-util.h" -#include "smack-util.h" -#include "sparse-endian.h" -#include "stat-util.h" -#include "string-util.h" -#include "time-util.h" -#include "util.h" - -/* WARNING: Be careful with file system ioctls! When we get an fd, we - * need to make sure it either refers to only a regular file or - * directory, or that it is located on btrfs, before invoking any - * btrfs ioctls. The ioctl numbers are reused by some device drivers - * (such as DRM), and hence might have bad effects when invoked on - * device nodes (that reference drivers) rather than fds to normal - * files or directories. */ - -static int validate_subvolume_name(const char *name) { - - if (!filename_is_valid(name)) - return -EINVAL; - - if (strlen(name) > BTRFS_SUBVOL_NAME_MAX) - return -E2BIG; - - return 0; -} - -static int open_parent(const char *path, int flags) { - _cleanup_free_ char *parent = NULL; - int fd; - - assert(path); - - parent = dirname_malloc(path); - if (!parent) - return -ENOMEM; - - fd = open(parent, flags); - if (fd < 0) - return -errno; - - return fd; -} - -static int extract_subvolume_name(const char *path, const char **subvolume) { - const char *fn; - int r; - - assert(path); - assert(subvolume); - - fn = basename(path); - - r = validate_subvolume_name(fn); - if (r < 0) - return r; - - *subvolume = fn; - return 0; -} - -int btrfs_is_filesystem(int fd) { - struct statfs sfs; - - assert(fd >= 0); - - if (fstatfs(fd, &sfs) < 0) - return -errno; - - return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC); -} - -int btrfs_is_subvol_fd(int fd) { - struct stat st; - - assert(fd >= 0); - - /* On btrfs subvolumes always have the inode 256 */ - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode) || st.st_ino != 256) - return 0; - - return btrfs_is_filesystem(fd); -} - -int btrfs_is_subvol(const char *path) { - _cleanup_close_ int fd = -1; - - assert(path); - - fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return -errno; - - return btrfs_is_subvol_fd(fd); -} - -int btrfs_subvol_make(const char *path) { - struct btrfs_ioctl_vol_args args = {}; - _cleanup_close_ int fd = -1; - const char *subvolume; - int r; - - assert(path); - - r = extract_subvolume_name(path, &subvolume); - if (r < 0) - return r; - - fd = open_parent(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return fd; - - strncpy(args.name, subvolume, sizeof(args.name)-1); - - if (ioctl(fd, BTRFS_IOC_SUBVOL_CREATE, &args) < 0) - return -errno; - - return 0; -} - -int btrfs_subvol_make_label(const char *path) { - int r; - - assert(path); - - r = mac_selinux_create_file_prepare(path, S_IFDIR); - if (r < 0) - return r; - - r = btrfs_subvol_make(path); - mac_selinux_create_file_clear(); - - if (r < 0) - return r; - - return mac_smack_fix(path, false, false); -} - -int btrfs_subvol_set_read_only_fd(int fd, bool b) { - uint64_t flags, nflags; - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode) || st.st_ino != 256) - return -EINVAL; - - if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0) - return -errno; - - if (b) - nflags = flags | BTRFS_SUBVOL_RDONLY; - else - nflags = flags & ~BTRFS_SUBVOL_RDONLY; - - if (flags == nflags) - return 0; - - if (ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &nflags) < 0) - return -errno; - - return 0; -} - -int btrfs_subvol_set_read_only(const char *path, bool b) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return -errno; - - return btrfs_subvol_set_read_only_fd(fd, b); -} - -int btrfs_subvol_get_read_only_fd(int fd) { - uint64_t flags; - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode) || st.st_ino != 256) - return -EINVAL; - - if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0) - return -errno; - - return !!(flags & BTRFS_SUBVOL_RDONLY); -} - -int btrfs_reflink(int infd, int outfd) { - struct stat st; - int r; - - assert(infd >= 0); - assert(outfd >= 0); - - /* Make sure we invoke the ioctl on a regular file, so that no - * device driver accidentally gets it. */ - - if (fstat(outfd, &st) < 0) - return -errno; - - if (!S_ISREG(st.st_mode)) - return -EINVAL; - - r = ioctl(outfd, BTRFS_IOC_CLONE, infd); - if (r < 0) - return -errno; - - return 0; -} - -int btrfs_clone_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offset, uint64_t sz) { - struct btrfs_ioctl_clone_range_args args = { - .src_fd = infd, - .src_offset = in_offset, - .src_length = sz, - .dest_offset = out_offset, - }; - struct stat st; - int r; - - assert(infd >= 0); - assert(outfd >= 0); - assert(sz > 0); - - if (fstat(outfd, &st) < 0) - return -errno; - - if (!S_ISREG(st.st_mode)) - return -EINVAL; - - r = ioctl(outfd, BTRFS_IOC_CLONE_RANGE, &args); - if (r < 0) - return -errno; - - return 0; -} - -int btrfs_get_block_device_fd(int fd, dev_t *dev) { - struct btrfs_ioctl_fs_info_args fsi = {}; - uint64_t id; - int r; - - assert(fd >= 0); - assert(dev); - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - - if (ioctl(fd, BTRFS_IOC_FS_INFO, &fsi) < 0) - return -errno; - - /* We won't do this for btrfs RAID */ - if (fsi.num_devices != 1) - return 0; - - for (id = 1; id <= fsi.max_id; id++) { - struct btrfs_ioctl_dev_info_args di = { - .devid = id, - }; - struct stat st; - - if (ioctl(fd, BTRFS_IOC_DEV_INFO, &di) < 0) { - if (errno == ENODEV) - continue; - - return -errno; - } - - if (stat((char*) di.path, &st) < 0) - return -errno; - - if (!S_ISBLK(st.st_mode)) - return -ENODEV; - - if (major(st.st_rdev) == 0) - return -ENODEV; - - *dev = st.st_rdev; - return 1; - } - - return -ENODEV; -} - -int btrfs_get_block_device(const char *path, dev_t *dev) { - _cleanup_close_ int fd = -1; - - assert(path); - assert(dev); - - fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return -errno; - - return btrfs_get_block_device_fd(fd, dev); -} - -int btrfs_subvol_get_id_fd(int fd, uint64_t *ret) { - struct btrfs_ioctl_ino_lookup_args args = { - .objectid = BTRFS_FIRST_FREE_OBJECTID - }; - int r; - - assert(fd >= 0); - assert(ret); - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - - if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args) < 0) - return -errno; - - *ret = args.treeid; - return 0; -} - -int btrfs_subvol_get_id(int fd, const char *subvol, uint64_t *ret) { - _cleanup_close_ int subvol_fd = -1; - - assert(fd >= 0); - assert(ret); - - subvol_fd = openat(fd, subvol, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (subvol_fd < 0) - return -errno; - - return btrfs_subvol_get_id_fd(subvol_fd, ret); -} - -static bool btrfs_ioctl_search_args_inc(struct btrfs_ioctl_search_args *args) { - assert(args); - - /* the objectid, type, offset together make up the btrfs key, - * which is considered a single 136byte integer when - * comparing. This call increases the counter by one, dealing - * with the overflow between the overflows */ - - if (args->key.min_offset < (uint64_t) -1) { - args->key.min_offset++; - return true; - } - - if (args->key.min_type < (uint8_t) -1) { - args->key.min_type++; - args->key.min_offset = 0; - return true; - } - - if (args->key.min_objectid < (uint64_t) -1) { - args->key.min_objectid++; - args->key.min_offset = 0; - args->key.min_type = 0; - return true; - } - - return 0; -} - -static void btrfs_ioctl_search_args_set(struct btrfs_ioctl_search_args *args, const struct btrfs_ioctl_search_header *h) { - assert(args); - assert(h); - - args->key.min_objectid = h->objectid; - args->key.min_type = h->type; - args->key.min_offset = h->offset; -} - -static int btrfs_ioctl_search_args_compare(const struct btrfs_ioctl_search_args *args) { - assert(args); - - /* Compare min and max */ - - if (args->key.min_objectid < args->key.max_objectid) - return -1; - if (args->key.min_objectid > args->key.max_objectid) - return 1; - - if (args->key.min_type < args->key.max_type) - return -1; - if (args->key.min_type > args->key.max_type) - return 1; - - if (args->key.min_offset < args->key.max_offset) - return -1; - if (args->key.min_offset > args->key.max_offset) - return 1; - - return 0; -} - -#define FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) \ - for ((i) = 0, \ - (sh) = (const struct btrfs_ioctl_search_header*) (args).buf; \ - (i) < (args).key.nr_items; \ - (i)++, \ - (sh) = (const struct btrfs_ioctl_search_header*) ((uint8_t*) (sh) + sizeof(struct btrfs_ioctl_search_header) + (sh)->len)) - -#define BTRFS_IOCTL_SEARCH_HEADER_BODY(sh) \ - ((void*) ((uint8_t*) sh + sizeof(struct btrfs_ioctl_search_header))) - -int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *ret) { - struct btrfs_ioctl_search_args args = { - /* Tree of tree roots */ - .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, - - /* Look precisely for the subvolume items */ - .key.min_type = BTRFS_ROOT_ITEM_KEY, - .key.max_type = BTRFS_ROOT_ITEM_KEY, - - .key.min_offset = 0, - .key.max_offset = (uint64_t) -1, - - /* No restrictions on the other components */ - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - - bool found = false; - int r; - - assert(fd >= 0); - assert(ret); - - if (subvol_id == 0) { - r = btrfs_subvol_get_id_fd(fd, &subvol_id); - if (r < 0) - return r; - } else { - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - } - - args.key.min_objectid = args.key.max_objectid = subvol_id; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) - return -errno; - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - - const struct btrfs_root_item *ri; - - /* Make sure we start the next search at least from this entry */ - btrfs_ioctl_search_args_set(&args, sh); - - if (sh->objectid != subvol_id) - continue; - if (sh->type != BTRFS_ROOT_ITEM_KEY) - continue; - - /* Older versions of the struct lacked the otime setting */ - if (sh->len < offsetof(struct btrfs_root_item, otime) + sizeof(struct btrfs_timespec)) - continue; - - ri = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); - - ret->otime = (usec_t) le64toh(ri->otime.sec) * USEC_PER_SEC + - (usec_t) le32toh(ri->otime.nsec) / NSEC_PER_USEC; - - ret->subvol_id = subvol_id; - ret->read_only = !!(le64toh(ri->flags) & BTRFS_ROOT_SUBVOL_RDONLY); - - assert_cc(sizeof(ri->uuid) == sizeof(ret->uuid)); - memcpy(&ret->uuid, ri->uuid, sizeof(ret->uuid)); - memcpy(&ret->parent_uuid, ri->parent_uuid, sizeof(ret->parent_uuid)); - - found = true; - goto finish; - } - - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) - break; - } - -finish: - if (!found) - return -ENODATA; - - return 0; -} - -int btrfs_qgroup_get_quota_fd(int fd, uint64_t qgroupid, BtrfsQuotaInfo *ret) { - - struct btrfs_ioctl_search_args args = { - /* Tree of quota items */ - .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, - - /* The object ID is always 0 */ - .key.min_objectid = 0, - .key.max_objectid = 0, - - /* Look precisely for the quota items */ - .key.min_type = BTRFS_QGROUP_STATUS_KEY, - .key.max_type = BTRFS_QGROUP_LIMIT_KEY, - - /* No restrictions on the other components */ - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - - bool found_info = false, found_limit = false; - int r; - - assert(fd >= 0); - assert(ret); - - if (qgroupid == 0) { - r = btrfs_subvol_get_id_fd(fd, &qgroupid); - if (r < 0) - return r; - } else { - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - } - - args.key.min_offset = args.key.max_offset = qgroupid; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) { - if (errno == ENOENT) /* quota tree is missing: quota disabled */ - break; - - return -errno; - } - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - - /* Make sure we start the next search at least from this entry */ - btrfs_ioctl_search_args_set(&args, sh); - - if (sh->objectid != 0) - continue; - if (sh->offset != qgroupid) - continue; - - if (sh->type == BTRFS_QGROUP_INFO_KEY) { - const struct btrfs_qgroup_info_item *qii = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); - - ret->referenced = le64toh(qii->rfer); - ret->exclusive = le64toh(qii->excl); - - found_info = true; - - } else if (sh->type == BTRFS_QGROUP_LIMIT_KEY) { - const struct btrfs_qgroup_limit_item *qli = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); - - if (le64toh(qli->flags) & BTRFS_QGROUP_LIMIT_MAX_RFER) - ret->referenced_max = le64toh(qli->max_rfer); - else - ret->referenced_max = (uint64_t) -1; - - if (le64toh(qli->flags) & BTRFS_QGROUP_LIMIT_MAX_EXCL) - ret->exclusive_max = le64toh(qli->max_excl); - else - ret->exclusive_max = (uint64_t) -1; - - found_limit = true; - } - - if (found_info && found_limit) - goto finish; - } - - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) - break; - } - -finish: - if (!found_limit && !found_info) - return -ENODATA; - - if (!found_info) { - ret->referenced = (uint64_t) -1; - ret->exclusive = (uint64_t) -1; - } - - if (!found_limit) { - ret->referenced_max = (uint64_t) -1; - ret->exclusive_max = (uint64_t) -1; - } - - return 0; -} - -int btrfs_qgroup_get_quota(const char *path, uint64_t qgroupid, BtrfsQuotaInfo *ret) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return btrfs_qgroup_get_quota_fd(fd, qgroupid, ret); -} - -int btrfs_subvol_find_subtree_qgroup(int fd, uint64_t subvol_id, uint64_t *ret) { - uint64_t level, lowest = (uint64_t) -1, lowest_qgroupid = 0; - _cleanup_free_ uint64_t *qgroups = NULL; - int r, n, i; - - assert(fd >= 0); - assert(ret); - - /* This finds the "subtree" qgroup for a specific - * subvolume. This only works for subvolumes that have been - * prepared with btrfs_subvol_auto_qgroup_fd() with - * insert_intermediary_qgroup=true (or equivalent). For others - * it will return the leaf qgroup instead. The two cases may - * be distuingished via the return value, which is 1 in case - * an appropriate "subtree" qgroup was found, and 0 - * otherwise. */ - - if (subvol_id == 0) { - r = btrfs_subvol_get_id_fd(fd, &subvol_id); - if (r < 0) - return r; - } - - r = btrfs_qgroupid_split(subvol_id, &level, NULL); - if (r < 0) - return r; - if (level != 0) /* Input must be a leaf qgroup */ - return -EINVAL; - - n = btrfs_qgroup_find_parents(fd, subvol_id, &qgroups); - if (n < 0) - return n; - - for (i = 0; i < n; i++) { - uint64_t id; - - r = btrfs_qgroupid_split(qgroups[i], &level, &id); - if (r < 0) - return r; - - if (id != subvol_id) - continue; - - if (lowest == (uint64_t) -1 || level < lowest) { - lowest_qgroupid = qgroups[i]; - lowest = level; - } - } - - if (lowest == (uint64_t) -1) { - /* No suitable higher-level qgroup found, let's return - * the leaf qgroup instead, and indicate that with the - * return value. */ - - *ret = subvol_id; - return 0; - } - - *ret = lowest_qgroupid; - return 1; -} - -int btrfs_subvol_get_subtree_quota_fd(int fd, uint64_t subvol_id, BtrfsQuotaInfo *ret) { - uint64_t qgroupid; - int r; - - assert(fd >= 0); - assert(ret); - - /* This determines the quota data of the qgroup with the - * lowest level, that shares the id part with the specified - * subvolume. This is useful for determining the quota data - * for entire subvolume subtrees, as long as the subtrees have - * been set up with btrfs_qgroup_subvol_auto_fd() or in a - * compatible way */ - - r = btrfs_subvol_find_subtree_qgroup(fd, subvol_id, &qgroupid); - if (r < 0) - return r; - - return btrfs_qgroup_get_quota_fd(fd, qgroupid, ret); -} - -int btrfs_subvol_get_subtree_quota(const char *path, uint64_t subvol_id, BtrfsQuotaInfo *ret) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return btrfs_subvol_get_subtree_quota_fd(fd, subvol_id, ret); -} - -int btrfs_defrag_fd(int fd) { - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISREG(st.st_mode)) - return -EINVAL; - - if (ioctl(fd, BTRFS_IOC_DEFRAG, NULL) < 0) - return -errno; - - return 0; -} - -int btrfs_defrag(const char *p) { - _cleanup_close_ int fd = -1; - - fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return btrfs_defrag_fd(fd); -} - -int btrfs_quota_enable_fd(int fd, bool b) { - struct btrfs_ioctl_quota_ctl_args args = { - .cmd = b ? BTRFS_QUOTA_CTL_ENABLE : BTRFS_QUOTA_CTL_DISABLE, - }; - int r; - - assert(fd >= 0); - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - - if (ioctl(fd, BTRFS_IOC_QUOTA_CTL, &args) < 0) - return -errno; - - return 0; -} - -int btrfs_quota_enable(const char *path, bool b) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return btrfs_quota_enable_fd(fd, b); -} - -int btrfs_qgroup_set_limit_fd(int fd, uint64_t qgroupid, uint64_t referenced_max) { - - struct btrfs_ioctl_qgroup_limit_args args = { - .lim.max_rfer = referenced_max, - .lim.flags = BTRFS_QGROUP_LIMIT_MAX_RFER, - }; - unsigned c; - int r; - - assert(fd >= 0); - - if (qgroupid == 0) { - r = btrfs_subvol_get_id_fd(fd, &qgroupid); - if (r < 0) - return r; - } else { - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - } - - args.qgroupid = qgroupid; - - for (c = 0;; c++) { - if (ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &args) < 0) { - - if (errno == EBUSY && c < 10) { - (void) btrfs_quota_scan_wait(fd); - continue; - } - - return -errno; - } - - break; - } - - return 0; -} - -int btrfs_qgroup_set_limit(const char *path, uint64_t qgroupid, uint64_t referenced_max) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return btrfs_qgroup_set_limit_fd(fd, qgroupid, referenced_max); -} - -int btrfs_subvol_set_subtree_quota_limit_fd(int fd, uint64_t subvol_id, uint64_t referenced_max) { - uint64_t qgroupid; - int r; - - assert(fd >= 0); - - r = btrfs_subvol_find_subtree_qgroup(fd, subvol_id, &qgroupid); - if (r < 0) - return r; - - return btrfs_qgroup_set_limit_fd(fd, qgroupid, referenced_max); -} - -int btrfs_subvol_set_subtree_quota_limit(const char *path, uint64_t subvol_id, uint64_t referenced_max) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return btrfs_subvol_set_subtree_quota_limit_fd(fd, subvol_id, referenced_max); -} - -int btrfs_resize_loopback_fd(int fd, uint64_t new_size, bool grow_only) { - struct btrfs_ioctl_vol_args args = {}; - _cleanup_free_ char *p = NULL, *loop = NULL, *backing = NULL; - _cleanup_close_ int loop_fd = -1, backing_fd = -1; - struct stat st; - dev_t dev = 0; - int r; - - /* In contrast to btrfs quota ioctls ftruncate() cannot make sense of "infinity" or file sizes > 2^31 */ - if (!FILE_SIZE_VALID(new_size)) - return -EINVAL; - - /* btrfs cannot handle file systems < 16M, hence use this as minimum */ - if (new_size < 16*1024*1024) - new_size = 16*1024*1024; - - r = btrfs_get_block_device_fd(fd, &dev); - if (r < 0) - return r; - if (r == 0) - return -ENODEV; - - if (asprintf(&p, "/sys/dev/block/%u:%u/loop/backing_file", major(dev), minor(dev)) < 0) - return -ENOMEM; - r = read_one_line_file(p, &backing); - if (r == -ENOENT) - return -ENODEV; - if (r < 0) - return r; - if (isempty(backing) || !path_is_absolute(backing)) - return -ENODEV; - - backing_fd = open(backing, O_RDWR|O_CLOEXEC|O_NOCTTY); - if (backing_fd < 0) - return -errno; - - if (fstat(backing_fd, &st) < 0) - return -errno; - if (!S_ISREG(st.st_mode)) - return -ENODEV; - - if (new_size == (uint64_t) st.st_size) - return 0; - - if (grow_only && new_size < (uint64_t) st.st_size) - return -EINVAL; - - if (asprintf(&loop, "/dev/block/%u:%u", major(dev), minor(dev)) < 0) - return -ENOMEM; - loop_fd = open(loop, O_RDWR|O_CLOEXEC|O_NOCTTY); - if (loop_fd < 0) - return -errno; - - if (snprintf(args.name, sizeof(args.name), "%" PRIu64, new_size) >= (int) sizeof(args.name)) - return -EINVAL; - - if (new_size < (uint64_t) st.st_size) { - /* Decrease size: first decrease btrfs size, then shorten loopback */ - if (ioctl(fd, BTRFS_IOC_RESIZE, &args) < 0) - return -errno; - } - - if (ftruncate(backing_fd, new_size) < 0) - return -errno; - - if (ioctl(loop_fd, LOOP_SET_CAPACITY, 0) < 0) - return -errno; - - if (new_size > (uint64_t) st.st_size) { - /* Increase size: first enlarge loopback, then increase btrfs size */ - if (ioctl(fd, BTRFS_IOC_RESIZE, &args) < 0) - return -errno; - } - - /* Make sure the free disk space is correctly updated for both file systems */ - (void) fsync(fd); - (void) fsync(backing_fd); - - return 1; -} - -int btrfs_resize_loopback(const char *p, uint64_t new_size, bool grow_only) { - _cleanup_close_ int fd = -1; - - fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return -errno; - - return btrfs_resize_loopback_fd(fd, new_size, grow_only); -} - -int btrfs_qgroupid_make(uint64_t level, uint64_t id, uint64_t *ret) { - assert(ret); - - if (level >= (UINT64_C(1) << (64 - BTRFS_QGROUP_LEVEL_SHIFT))) - return -EINVAL; - - if (id >= (UINT64_C(1) << BTRFS_QGROUP_LEVEL_SHIFT)) - return -EINVAL; - - *ret = (level << BTRFS_QGROUP_LEVEL_SHIFT) | id; - return 0; -} - -int btrfs_qgroupid_split(uint64_t qgroupid, uint64_t *level, uint64_t *id) { - assert(level || id); - - if (level) - *level = qgroupid >> BTRFS_QGROUP_LEVEL_SHIFT; - - if (id) - *id = qgroupid & ((UINT64_C(1) << BTRFS_QGROUP_LEVEL_SHIFT) - 1); - - return 0; -} - -static int qgroup_create_or_destroy(int fd, bool b, uint64_t qgroupid) { - - struct btrfs_ioctl_qgroup_create_args args = { - .create = b, - .qgroupid = qgroupid, - }; - unsigned c; - int r; - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (r == 0) - return -ENOTTY; - - for (c = 0;; c++) { - if (ioctl(fd, BTRFS_IOC_QGROUP_CREATE, &args) < 0) { - - /* If quota is not enabled, we get EINVAL. Turn this into a recognizable error */ - if (errno == EINVAL) - return -ENOPROTOOPT; - - if (errno == EBUSY && c < 10) { - (void) btrfs_quota_scan_wait(fd); - continue; - } - - return -errno; - } - - break; - } - - return 0; -} - -int btrfs_qgroup_create(int fd, uint64_t qgroupid) { - return qgroup_create_or_destroy(fd, true, qgroupid); -} - -int btrfs_qgroup_destroy(int fd, uint64_t qgroupid) { - return qgroup_create_or_destroy(fd, false, qgroupid); -} - -int btrfs_qgroup_destroy_recursive(int fd, uint64_t qgroupid) { - _cleanup_free_ uint64_t *qgroups = NULL; - uint64_t subvol_id; - int i, n, r; - - /* Destroys the specified qgroup, but unassigns it from all - * its parents first. Also, it recursively destroys all - * qgroups it is assgined to that have the same id part of the - * qgroupid as the specified group. */ - - r = btrfs_qgroupid_split(qgroupid, NULL, &subvol_id); - if (r < 0) - return r; - - n = btrfs_qgroup_find_parents(fd, qgroupid, &qgroups); - if (n < 0) - return n; - - for (i = 0; i < n; i++) { - uint64_t id; - - r = btrfs_qgroupid_split(qgroups[i], NULL, &id); - if (r < 0) - return r; - - r = btrfs_qgroup_unassign(fd, qgroupid, qgroups[i]); - if (r < 0) - return r; - - if (id != subvol_id) - continue; - - /* The parent qgroupid shares the same id part with - * us? If so, destroy it too. */ - - (void) btrfs_qgroup_destroy_recursive(fd, qgroups[i]); - } - - return btrfs_qgroup_destroy(fd, qgroupid); -} - -int btrfs_quota_scan_start(int fd) { - struct btrfs_ioctl_quota_rescan_args args = {}; - - assert(fd >= 0); - - if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN, &args) < 0) - return -errno; - - return 0; -} - -int btrfs_quota_scan_wait(int fd) { - assert(fd >= 0); - - if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN_WAIT) < 0) - return -errno; - - return 0; -} - -int btrfs_quota_scan_ongoing(int fd) { - struct btrfs_ioctl_quota_rescan_args args = {}; - - assert(fd >= 0); - - if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN_STATUS, &args) < 0) - return -errno; - - return !!args.flags; -} - -static int qgroup_assign_or_unassign(int fd, bool b, uint64_t child, uint64_t parent) { - struct btrfs_ioctl_qgroup_assign_args args = { - .assign = b, - .src = child, - .dst = parent, - }; - unsigned c; - int r; - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (r == 0) - return -ENOTTY; - - for (c = 0;; c++) { - r = ioctl(fd, BTRFS_IOC_QGROUP_ASSIGN, &args); - if (r < 0) { - if (errno == EBUSY && c < 10) { - (void) btrfs_quota_scan_wait(fd); - continue; - } - - return -errno; - } - - if (r == 0) - return 0; - - /* If the return value is > 0, we need to request a rescan */ - - (void) btrfs_quota_scan_start(fd); - return 1; - } -} - -int btrfs_qgroup_assign(int fd, uint64_t child, uint64_t parent) { - return qgroup_assign_or_unassign(fd, true, child, parent); -} - -int btrfs_qgroup_unassign(int fd, uint64_t child, uint64_t parent) { - return qgroup_assign_or_unassign(fd, false, child, parent); -} - -static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol_id, BtrfsRemoveFlags flags) { - struct btrfs_ioctl_search_args args = { - .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, - - .key.min_objectid = BTRFS_FIRST_FREE_OBJECTID, - .key.max_objectid = BTRFS_LAST_FREE_OBJECTID, - - .key.min_type = BTRFS_ROOT_BACKREF_KEY, - .key.max_type = BTRFS_ROOT_BACKREF_KEY, - - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - - struct btrfs_ioctl_vol_args vol_args = {}; - _cleanup_close_ int subvol_fd = -1; - struct stat st; - bool made_writable = false; - int r; - - assert(fd >= 0); - assert(subvolume); - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode)) - return -EINVAL; - - subvol_fd = openat(fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (subvol_fd < 0) - return -errno; - - if (subvol_id == 0) { - r = btrfs_subvol_get_id_fd(subvol_fd, &subvol_id); - if (r < 0) - return r; - } - - /* First, try to remove the subvolume. If it happens to be - * already empty, this will just work. */ - strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1); - if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) >= 0) { - (void) btrfs_qgroup_destroy_recursive(fd, subvol_id); /* for the leaf subvolumes, the qgroup id is identical to the subvol id */ - return 0; - } - if (!(flags & BTRFS_REMOVE_RECURSIVE) || errno != ENOTEMPTY) - return -errno; - - /* OK, the subvolume is not empty, let's look for child - * subvolumes, and remove them, first */ - - args.key.min_offset = args.key.max_offset = subvol_id; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) - return -errno; - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - _cleanup_free_ char *p = NULL; - const struct btrfs_root_ref *ref; - struct btrfs_ioctl_ino_lookup_args ino_args; - - btrfs_ioctl_search_args_set(&args, sh); - - if (sh->type != BTRFS_ROOT_BACKREF_KEY) - continue; - if (sh->offset != subvol_id) - continue; - - ref = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); - - p = strndup((char*) ref + sizeof(struct btrfs_root_ref), le64toh(ref->name_len)); - if (!p) - return -ENOMEM; - - zero(ino_args); - ino_args.treeid = subvol_id; - ino_args.objectid = htole64(ref->dirid); - - if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0) - return -errno; - - if (!made_writable) { - r = btrfs_subvol_set_read_only_fd(subvol_fd, false); - if (r < 0) - return r; - - made_writable = true; - } - - if (isempty(ino_args.name)) - /* Subvolume is in the top-level - * directory of the subvolume. */ - r = subvol_remove_children(subvol_fd, p, sh->objectid, flags); - else { - _cleanup_close_ int child_fd = -1; - - /* Subvolume is somewhere further down, - * hence we need to open the - * containing directory first */ - - child_fd = openat(subvol_fd, ino_args.name, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (child_fd < 0) - return -errno; - - r = subvol_remove_children(child_fd, p, sh->objectid, flags); - } - if (r < 0) - return r; - } - - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) - break; - } - - /* OK, the child subvolumes should all be gone now, let's try - * again to remove the subvolume */ - if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) < 0) - return -errno; - - (void) btrfs_qgroup_destroy_recursive(fd, subvol_id); - return 0; -} - -int btrfs_subvol_remove(const char *path, BtrfsRemoveFlags flags) { - _cleanup_close_ int fd = -1; - const char *subvolume; - int r; - - assert(path); - - r = extract_subvolume_name(path, &subvolume); - if (r < 0) - return r; - - fd = open_parent(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return fd; - - return subvol_remove_children(fd, subvolume, 0, flags); -} - -int btrfs_subvol_remove_fd(int fd, const char *subvolume, BtrfsRemoveFlags flags) { - return subvol_remove_children(fd, subvolume, 0, flags); -} - -int btrfs_qgroup_copy_limits(int fd, uint64_t old_qgroupid, uint64_t new_qgroupid) { - - struct btrfs_ioctl_search_args args = { - /* Tree of quota items */ - .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, - - /* The object ID is always 0 */ - .key.min_objectid = 0, - .key.max_objectid = 0, - - /* Look precisely for the quota items */ - .key.min_type = BTRFS_QGROUP_LIMIT_KEY, - .key.max_type = BTRFS_QGROUP_LIMIT_KEY, - - /* For our qgroup */ - .key.min_offset = old_qgroupid, - .key.max_offset = old_qgroupid, - - /* No restrictions on the other components */ - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - - int r; - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) { - if (errno == ENOENT) /* quota tree missing: quota is not enabled, hence nothing to copy */ - break; - - return -errno; - } - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - const struct btrfs_qgroup_limit_item *qli = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); - struct btrfs_ioctl_qgroup_limit_args qargs; - unsigned c; - - /* Make sure we start the next search at least from this entry */ - btrfs_ioctl_search_args_set(&args, sh); - - if (sh->objectid != 0) - continue; - if (sh->type != BTRFS_QGROUP_LIMIT_KEY) - continue; - if (sh->offset != old_qgroupid) - continue; - - /* We found the entry, now copy things over. */ - - qargs = (struct btrfs_ioctl_qgroup_limit_args) { - .qgroupid = new_qgroupid, - - .lim.max_rfer = le64toh(qli->max_rfer), - .lim.max_excl = le64toh(qli->max_excl), - .lim.rsv_rfer = le64toh(qli->rsv_rfer), - .lim.rsv_excl = le64toh(qli->rsv_excl), - - .lim.flags = le64toh(qli->flags) & (BTRFS_QGROUP_LIMIT_MAX_RFER| - BTRFS_QGROUP_LIMIT_MAX_EXCL| - BTRFS_QGROUP_LIMIT_RSV_RFER| - BTRFS_QGROUP_LIMIT_RSV_EXCL), - }; - - for (c = 0;; c++) { - if (ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &qargs) < 0) { - if (errno == EBUSY && c < 10) { - (void) btrfs_quota_scan_wait(fd); - continue; - } - return -errno; - } - - break; - } - - return 1; - } - - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) - break; - } - - return 0; -} - -static int copy_quota_hierarchy(int fd, uint64_t old_subvol_id, uint64_t new_subvol_id) { - _cleanup_free_ uint64_t *old_qgroups = NULL, *old_parent_qgroups = NULL; - bool copy_from_parent = false, insert_intermediary_qgroup = false; - int n_old_qgroups, n_old_parent_qgroups, r, i; - uint64_t old_parent_id; - - assert(fd >= 0); - - /* Copies a reduced form of quota information from the old to - * the new subvolume. */ - - n_old_qgroups = btrfs_qgroup_find_parents(fd, old_subvol_id, &old_qgroups); - if (n_old_qgroups <= 0) /* Nothing to copy */ - return n_old_qgroups; - - r = btrfs_subvol_get_parent(fd, old_subvol_id, &old_parent_id); - if (r == -ENXIO) - /* We have no parent, hence nothing to copy. */ - n_old_parent_qgroups = 0; - else if (r < 0) - return r; - else { - n_old_parent_qgroups = btrfs_qgroup_find_parents(fd, old_parent_id, &old_parent_qgroups); - if (n_old_parent_qgroups < 0) - return n_old_parent_qgroups; - } - - for (i = 0; i < n_old_qgroups; i++) { - uint64_t id; - int j; - - r = btrfs_qgroupid_split(old_qgroups[i], NULL, &id); - if (r < 0) - return r; - - if (id == old_subvol_id) { - /* The old subvolume was member of a qgroup - * that had the same id, but a different level - * as it self. Let's set up something similar - * in the destination. */ - insert_intermediary_qgroup = true; - break; - } - - for (j = 0; j < n_old_parent_qgroups; j++) - if (old_parent_qgroups[j] == old_qgroups[i]) { - /* The old subvolume shared a common - * parent qgroup with its parent - * subvolume. Let's set up something - * similar in the destination. */ - copy_from_parent = true; - } - } - - if (!insert_intermediary_qgroup && !copy_from_parent) - return 0; - - return btrfs_subvol_auto_qgroup_fd(fd, new_subvol_id, insert_intermediary_qgroup); -} - -static int copy_subtree_quota_limits(int fd, uint64_t old_subvol, uint64_t new_subvol) { - uint64_t old_subtree_qgroup, new_subtree_qgroup; - bool changed; - int r; - - /* First copy the leaf limits */ - r = btrfs_qgroup_copy_limits(fd, old_subvol, new_subvol); - if (r < 0) - return r; - changed = r > 0; - - /* Then, try to copy the subtree limits, if there are any. */ - r = btrfs_subvol_find_subtree_qgroup(fd, old_subvol, &old_subtree_qgroup); - if (r < 0) - return r; - if (r == 0) - return changed; - - r = btrfs_subvol_find_subtree_qgroup(fd, new_subvol, &new_subtree_qgroup); - if (r < 0) - return r; - if (r == 0) - return changed; - - r = btrfs_qgroup_copy_limits(fd, old_subtree_qgroup, new_subtree_qgroup); - if (r != 0) - return r; - - return changed; -} - -static int subvol_snapshot_children(int old_fd, int new_fd, const char *subvolume, uint64_t old_subvol_id, BtrfsSnapshotFlags flags) { - - struct btrfs_ioctl_search_args args = { - .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, - - .key.min_objectid = BTRFS_FIRST_FREE_OBJECTID, - .key.max_objectid = BTRFS_LAST_FREE_OBJECTID, - - .key.min_type = BTRFS_ROOT_BACKREF_KEY, - .key.max_type = BTRFS_ROOT_BACKREF_KEY, - - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - - struct btrfs_ioctl_vol_args_v2 vol_args = { - .flags = flags & BTRFS_SNAPSHOT_READ_ONLY ? BTRFS_SUBVOL_RDONLY : 0, - .fd = old_fd, - }; - _cleanup_close_ int subvolume_fd = -1; - uint64_t new_subvol_id; - int r; - - assert(old_fd >= 0); - assert(new_fd >= 0); - assert(subvolume); - - strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1); - - if (ioctl(new_fd, BTRFS_IOC_SNAP_CREATE_V2, &vol_args) < 0) - return -errno; - - if (!(flags & BTRFS_SNAPSHOT_RECURSIVE) && - !(flags & BTRFS_SNAPSHOT_QUOTA)) - return 0; - - if (old_subvol_id == 0) { - r = btrfs_subvol_get_id_fd(old_fd, &old_subvol_id); - if (r < 0) - return r; - } - - r = btrfs_subvol_get_id(new_fd, vol_args.name, &new_subvol_id); - if (r < 0) - return r; - - if (flags & BTRFS_SNAPSHOT_QUOTA) - (void) copy_quota_hierarchy(new_fd, old_subvol_id, new_subvol_id); - - if (!(flags & BTRFS_SNAPSHOT_RECURSIVE)) { - - if (flags & BTRFS_SNAPSHOT_QUOTA) - (void) copy_subtree_quota_limits(new_fd, old_subvol_id, new_subvol_id); - - return 0; - } - - args.key.min_offset = args.key.max_offset = old_subvol_id; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(old_fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) - return -errno; - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - _cleanup_free_ char *p = NULL, *c = NULL, *np = NULL; - struct btrfs_ioctl_ino_lookup_args ino_args; - const struct btrfs_root_ref *ref; - _cleanup_close_ int old_child_fd = -1, new_child_fd = -1; - - btrfs_ioctl_search_args_set(&args, sh); - - if (sh->type != BTRFS_ROOT_BACKREF_KEY) - continue; - - /* Avoid finding the source subvolume a second - * time */ - if (sh->offset != old_subvol_id) - continue; - - /* Avoid running into loops if the new - * subvolume is below the old one. */ - if (sh->objectid == new_subvol_id) - continue; - - ref = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); - p = strndup((char*) ref + sizeof(struct btrfs_root_ref), le64toh(ref->name_len)); - if (!p) - return -ENOMEM; - - zero(ino_args); - ino_args.treeid = old_subvol_id; - ino_args.objectid = htole64(ref->dirid); - - if (ioctl(old_fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0) - return -errno; - - /* The kernel returns an empty name if the - * subvolume is in the top-level directory, - * and otherwise appends a slash, so that we - * can just concatenate easily here, without - * adding a slash. */ - c = strappend(ino_args.name, p); - if (!c) - return -ENOMEM; - - old_child_fd = openat(old_fd, c, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (old_child_fd < 0) - return -errno; - - np = strjoin(subvolume, "/", ino_args.name, NULL); - if (!np) - return -ENOMEM; - - new_child_fd = openat(new_fd, np, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (new_child_fd < 0) - return -errno; - - if (flags & BTRFS_SNAPSHOT_READ_ONLY) { - /* If the snapshot is read-only we - * need to mark it writable - * temporarily, to put the subsnapshot - * into place. */ - - if (subvolume_fd < 0) { - subvolume_fd = openat(new_fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (subvolume_fd < 0) - return -errno; - } - - r = btrfs_subvol_set_read_only_fd(subvolume_fd, false); - if (r < 0) - return r; - } - - /* When btrfs clones the subvolumes, child - * subvolumes appear as empty directories. Remove - * them, so that we can create a new snapshot - * in their place */ - if (unlinkat(new_child_fd, p, AT_REMOVEDIR) < 0) { - int k = -errno; - - if (flags & BTRFS_SNAPSHOT_READ_ONLY) - (void) btrfs_subvol_set_read_only_fd(subvolume_fd, true); - - return k; - } - - r = subvol_snapshot_children(old_child_fd, new_child_fd, p, sh->objectid, flags & ~BTRFS_SNAPSHOT_FALLBACK_COPY); - - /* Restore the readonly flag */ - if (flags & BTRFS_SNAPSHOT_READ_ONLY) { - int k; - - k = btrfs_subvol_set_read_only_fd(subvolume_fd, true); - if (r >= 0 && k < 0) - return k; - } - - if (r < 0) - return r; - } - - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) - break; - } - - if (flags & BTRFS_SNAPSHOT_QUOTA) - (void) copy_subtree_quota_limits(new_fd, old_subvol_id, new_subvol_id); - - return 0; -} - -int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags) { - _cleanup_close_ int new_fd = -1; - const char *subvolume; - int r; - - assert(old_fd >= 0); - assert(new_path); - - r = btrfs_is_subvol_fd(old_fd); - if (r < 0) - return r; - if (r == 0) { - if (!(flags & BTRFS_SNAPSHOT_FALLBACK_COPY)) - return -EISDIR; - - r = btrfs_subvol_make(new_path); - if (r < 0) - return r; - - r = copy_directory_fd(old_fd, new_path, true); - if (r < 0) { - (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA); - return r; - } - - if (flags & BTRFS_SNAPSHOT_READ_ONLY) { - r = btrfs_subvol_set_read_only(new_path, true); - if (r < 0) { - (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA); - return r; - } - } - - return 0; - } - - r = extract_subvolume_name(new_path, &subvolume); - if (r < 0) - return r; - - new_fd = open_parent(new_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (new_fd < 0) - return new_fd; - - return subvol_snapshot_children(old_fd, new_fd, subvolume, 0, flags); -} - -int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags) { - _cleanup_close_ int old_fd = -1; - - assert(old_path); - assert(new_path); - - old_fd = open(old_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (old_fd < 0) - return -errno; - - return btrfs_subvol_snapshot_fd(old_fd, new_path, flags); -} - -int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret) { - - struct btrfs_ioctl_search_args args = { - /* Tree of quota items */ - .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, - - /* Look precisely for the quota relation items */ - .key.min_type = BTRFS_QGROUP_RELATION_KEY, - .key.max_type = BTRFS_QGROUP_RELATION_KEY, - - /* No restrictions on the other components */ - .key.min_offset = 0, - .key.max_offset = (uint64_t) -1, - - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - - _cleanup_free_ uint64_t *items = NULL; - size_t n_items = 0, n_allocated = 0; - int r; - - assert(fd >= 0); - assert(ret); - - if (qgroupid == 0) { - r = btrfs_subvol_get_id_fd(fd, &qgroupid); - if (r < 0) - return r; - } else { - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - } - - args.key.min_objectid = args.key.max_objectid = qgroupid; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) { - if (errno == ENOENT) /* quota tree missing: quota is disabled */ - break; - - return -errno; - } - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - - /* Make sure we start the next search at least from this entry */ - btrfs_ioctl_search_args_set(&args, sh); - - if (sh->type != BTRFS_QGROUP_RELATION_KEY) - continue; - if (sh->offset < sh->objectid) - continue; - if (sh->objectid != qgroupid) - continue; - - if (!GREEDY_REALLOC(items, n_allocated, n_items+1)) - return -ENOMEM; - - items[n_items++] = sh->offset; - } - - /* Increase search key by one, to read the next item, if we can. */ - if (!btrfs_ioctl_search_args_inc(&args)) - break; - } - - if (n_items <= 0) { - *ret = NULL; - return 0; - } - - *ret = items; - items = NULL; - - return (int) n_items; -} - -int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool insert_intermediary_qgroup) { - _cleanup_free_ uint64_t *qgroups = NULL; - uint64_t parent_subvol; - bool changed = false; - int n = 0, r; - - assert(fd >= 0); - - /* - * Sets up the specified subvolume's qgroup automatically in - * one of two ways: - * - * If insert_intermediary_qgroup is false, the subvolume's - * leaf qgroup will be assigned to the same parent qgroups as - * the subvolume's parent subvolume. - * - * If insert_intermediary_qgroup is true a new intermediary - * higher-level qgroup is created, with a higher level number, - * but reusing the id of the subvolume. The level number is - * picked as one smaller than the lowest level qgroup the - * parent subvolume is a member of. If the parent subvolume's - * leaf qgroup is assigned to no higher-level qgroup a new - * qgroup of level 255 is created instead. Either way, the new - * qgroup is then assigned to the parent's higher-level - * qgroup, and the subvolume itself is assigned to it. - * - * If the subvolume is already assigned to a higher level - * qgroup, no operation is executed. - * - * Effectively this means: regardless if - * insert_intermediary_qgroup is true or not, after this - * function is invoked the subvolume will be accounted within - * the same qgroups as the parent. However, if it is true, it - * will also get its own higher-level qgroup, which may in - * turn be used by subvolumes created beneath this subvolume - * later on. - * - * This hence defines a simple default qgroup setup for - * subvolumes, as long as this function is invoked on each - * created subvolume: each subvolume is always accounting - * together with its immediate parents. Optionally, if - * insert_intermediary_qgroup is true, it will also get a - * qgroup that then includes all its own child subvolumes. - */ - - if (subvol_id == 0) { - r = btrfs_is_subvol_fd(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - - r = btrfs_subvol_get_id_fd(fd, &subvol_id); - if (r < 0) - return r; - } - - n = btrfs_qgroup_find_parents(fd, subvol_id, &qgroups); - if (n < 0) - return n; - if (n > 0) /* already parent qgroups set up, let's bail */ - return 0; - - qgroups = mfree(qgroups); - - r = btrfs_subvol_get_parent(fd, subvol_id, &parent_subvol); - if (r == -ENXIO) - /* No parent, hence no qgroup memberships */ - n = 0; - else if (r < 0) - return r; - else { - n = btrfs_qgroup_find_parents(fd, parent_subvol, &qgroups); - if (n < 0) - return n; - } - - if (insert_intermediary_qgroup) { - uint64_t lowest = 256, new_qgroupid; - bool created = false; - int i; - - /* Determine the lowest qgroup that the parent - * subvolume is assigned to. */ - - for (i = 0; i < n; i++) { - uint64_t level; - - r = btrfs_qgroupid_split(qgroups[i], &level, NULL); - if (r < 0) - return r; - - if (level < lowest) - lowest = level; - } - - if (lowest <= 1) /* There are no levels left we could use insert an intermediary qgroup at */ - return -EBUSY; - - r = btrfs_qgroupid_make(lowest - 1, subvol_id, &new_qgroupid); - if (r < 0) - return r; - - /* Create the new intermediary group, unless it already exists */ - r = btrfs_qgroup_create(fd, new_qgroupid); - if (r < 0 && r != -EEXIST) - return r; - if (r >= 0) - changed = created = true; - - for (i = 0; i < n; i++) { - r = btrfs_qgroup_assign(fd, new_qgroupid, qgroups[i]); - if (r < 0 && r != -EEXIST) { - if (created) - (void) btrfs_qgroup_destroy_recursive(fd, new_qgroupid); - - return r; - } - if (r >= 0) - changed = true; - } - - r = btrfs_qgroup_assign(fd, subvol_id, new_qgroupid); - if (r < 0 && r != -EEXIST) { - if (created) - (void) btrfs_qgroup_destroy_recursive(fd, new_qgroupid); - return r; - } - if (r >= 0) - changed = true; - - } else { - int i; - - /* Assign our subvolume to all the same qgroups as the parent */ - - for (i = 0; i < n; i++) { - r = btrfs_qgroup_assign(fd, subvol_id, qgroups[i]); - if (r < 0 && r != -EEXIST) - return r; - if (r >= 0) - changed = true; - } - } - - return changed; -} - -int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_intermediary_qgroup) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return -errno; - - return btrfs_subvol_auto_qgroup_fd(fd, subvol_id, create_intermediary_qgroup); -} - -int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret) { - - struct btrfs_ioctl_search_args args = { - /* Tree of tree roots */ - .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, - - /* Look precisely for the subvolume items */ - .key.min_type = BTRFS_ROOT_BACKREF_KEY, - .key.max_type = BTRFS_ROOT_BACKREF_KEY, - - /* No restrictions on the other components */ - .key.min_offset = 0, - .key.max_offset = (uint64_t) -1, - - .key.min_transid = 0, - .key.max_transid = (uint64_t) -1, - }; - int r; - - assert(fd >= 0); - assert(ret); - - if (subvol_id == 0) { - r = btrfs_subvol_get_id_fd(fd, &subvol_id); - if (r < 0) - return r; - } else { - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; - } - - args.key.min_objectid = args.key.max_objectid = subvol_id; - - while (btrfs_ioctl_search_args_compare(&args) <= 0) { - const struct btrfs_ioctl_search_header *sh; - unsigned i; - - args.key.nr_items = 256; - if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) - return negative_errno(); - - if (args.key.nr_items <= 0) - break; - - FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { - - if (sh->type != BTRFS_ROOT_BACKREF_KEY) - continue; - if (sh->objectid != subvol_id) - continue; - - *ret = sh->offset; - return 0; - } - } - - return -ENXIO; -} diff --git a/src/basic/btrfs-util.h b/src/basic/btrfs-util.h deleted file mode 100644 index 37802c2565..0000000000 --- a/src/basic/btrfs-util.h +++ /dev/null @@ -1,131 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#pragma once - -#include -#include -#include - -#include "sd-id128.h" - -#include "time-util.h" - -typedef struct BtrfsSubvolInfo { - uint64_t subvol_id; - usec_t otime; - - sd_id128_t uuid; - sd_id128_t parent_uuid; - - bool read_only; -} BtrfsSubvolInfo; - -typedef struct BtrfsQuotaInfo { - uint64_t referenced; - uint64_t exclusive; - uint64_t referenced_max; - uint64_t exclusive_max; -} BtrfsQuotaInfo; - -typedef enum BtrfsSnapshotFlags { - BTRFS_SNAPSHOT_FALLBACK_COPY = 1, - BTRFS_SNAPSHOT_READ_ONLY = 2, - BTRFS_SNAPSHOT_RECURSIVE = 4, - BTRFS_SNAPSHOT_QUOTA = 8, -} BtrfsSnapshotFlags; - -typedef enum BtrfsRemoveFlags { - BTRFS_REMOVE_RECURSIVE = 1, - BTRFS_REMOVE_QUOTA = 2, -} BtrfsRemoveFlags; - -int btrfs_is_filesystem(int fd); - -int btrfs_is_subvol_fd(int fd); -int btrfs_is_subvol(const char *path); - -int btrfs_reflink(int infd, int outfd); -int btrfs_clone_range(int infd, uint64_t in_offset, int ofd, uint64_t out_offset, uint64_t sz); - -int btrfs_get_block_device_fd(int fd, dev_t *dev); -int btrfs_get_block_device(const char *path, dev_t *dev); - -int btrfs_defrag_fd(int fd); -int btrfs_defrag(const char *p); - -int btrfs_quota_enable_fd(int fd, bool b); -int btrfs_quota_enable(const char *path, bool b); - -int btrfs_quota_scan_start(int fd); -int btrfs_quota_scan_wait(int fd); -int btrfs_quota_scan_ongoing(int fd); - -int btrfs_resize_loopback_fd(int fd, uint64_t size, bool grow_only); -int btrfs_resize_loopback(const char *path, uint64_t size, bool grow_only); - -int btrfs_subvol_make(const char *path); -int btrfs_subvol_make_label(const char *path); - -int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags); -int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags); - -int btrfs_subvol_remove(const char *path, BtrfsRemoveFlags flags); -int btrfs_subvol_remove_fd(int fd, const char *subvolume, BtrfsRemoveFlags flags); - -int btrfs_subvol_set_read_only_fd(int fd, bool b); -int btrfs_subvol_set_read_only(const char *path, bool b); -int btrfs_subvol_get_read_only_fd(int fd); - -int btrfs_subvol_get_id(int fd, const char *subvolume, uint64_t *ret); -int btrfs_subvol_get_id_fd(int fd, uint64_t *ret); -int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret); - -int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *info); - -int btrfs_subvol_find_subtree_qgroup(int fd, uint64_t subvol_id, uint64_t *ret); - -int btrfs_subvol_get_subtree_quota(const char *path, uint64_t subvol_id, BtrfsQuotaInfo *quota); -int btrfs_subvol_get_subtree_quota_fd(int fd, uint64_t subvol_id, BtrfsQuotaInfo *quota); - -int btrfs_subvol_set_subtree_quota_limit(const char *path, uint64_t subvol_id, uint64_t referenced_max); -int btrfs_subvol_set_subtree_quota_limit_fd(int fd, uint64_t subvol_id, uint64_t referenced_max); - -int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool new_qgroup); -int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_intermediary_qgroup); - -int btrfs_qgroupid_make(uint64_t level, uint64_t id, uint64_t *ret); -int btrfs_qgroupid_split(uint64_t qgroupid, uint64_t *level, uint64_t *id); - -int btrfs_qgroup_create(int fd, uint64_t qgroupid); -int btrfs_qgroup_destroy(int fd, uint64_t qgroupid); -int btrfs_qgroup_destroy_recursive(int fd, uint64_t qgroupid); - -int btrfs_qgroup_set_limit_fd(int fd, uint64_t qgroupid, uint64_t referenced_max); -int btrfs_qgroup_set_limit(const char *path, uint64_t qgroupid, uint64_t referenced_max); - -int btrfs_qgroup_copy_limits(int fd, uint64_t old_qgroupid, uint64_t new_qgroupid); - -int btrfs_qgroup_assign(int fd, uint64_t child, uint64_t parent); -int btrfs_qgroup_unassign(int fd, uint64_t child, uint64_t parent); - -int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret); - -int btrfs_qgroup_get_quota_fd(int fd, uint64_t qgroupid, BtrfsQuotaInfo *quota); -int btrfs_qgroup_get_quota(const char *path, uint64_t qgroupid, BtrfsQuotaInfo *quota); diff --git a/src/basic/build.h b/src/basic/build.h deleted file mode 100644 index 633c2aaccb..0000000000 --- a/src/basic/build.h +++ /dev/null @@ -1,155 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#ifdef HAVE_PAM -#define _PAM_FEATURE_ "+PAM" -#else -#define _PAM_FEATURE_ "-PAM" -#endif - -#ifdef HAVE_AUDIT -#define _AUDIT_FEATURE_ "+AUDIT" -#else -#define _AUDIT_FEATURE_ "-AUDIT" -#endif - -#ifdef HAVE_SELINUX -#define _SELINUX_FEATURE_ "+SELINUX" -#else -#define _SELINUX_FEATURE_ "-SELINUX" -#endif - -#ifdef HAVE_APPARMOR -#define _APPARMOR_FEATURE_ "+APPARMOR" -#else -#define _APPARMOR_FEATURE_ "-APPARMOR" -#endif - -#ifdef HAVE_IMA -#define _IMA_FEATURE_ "+IMA" -#else -#define _IMA_FEATURE_ "-IMA" -#endif - -#ifdef HAVE_SMACK -#define _SMACK_FEATURE_ "+SMACK" -#else -#define _SMACK_FEATURE_ "-SMACK" -#endif - -#ifdef HAVE_SYSV_COMPAT -#define _SYSVINIT_FEATURE_ "+SYSVINIT" -#else -#define _SYSVINIT_FEATURE_ "-SYSVINIT" -#endif - -#ifdef HAVE_UTMP -#define _UTMP_FEATURE_ "+UTMP" -#else -#define _UTMP_FEATURE_ "-UTMP" -#endif - -#ifdef HAVE_LIBCRYPTSETUP -#define _LIBCRYPTSETUP_FEATURE_ "+LIBCRYPTSETUP" -#else -#define _LIBCRYPTSETUP_FEATURE_ "-LIBCRYPTSETUP" -#endif - -#ifdef HAVE_GCRYPT -#define _GCRYPT_FEATURE_ "+GCRYPT" -#else -#define _GCRYPT_FEATURE_ "-GCRYPT" -#endif - -#ifdef HAVE_GNUTLS -#define _GNUTLS_FEATURE_ "+GNUTLS" -#else -#define _GNUTLS_FEATURE_ "-GNUTLS" -#endif - -#ifdef HAVE_ACL -#define _ACL_FEATURE_ "+ACL" -#else -#define _ACL_FEATURE_ "-ACL" -#endif - -#ifdef HAVE_XZ -#define _XZ_FEATURE_ "+XZ" -#else -#define _XZ_FEATURE_ "-XZ" -#endif - -#ifdef HAVE_LZ4 -#define _LZ4_FEATURE_ "+LZ4" -#else -#define _LZ4_FEATURE_ "-LZ4" -#endif - -#ifdef HAVE_SECCOMP -#define _SECCOMP_FEATURE_ "+SECCOMP" -#else -#define _SECCOMP_FEATURE_ "-SECCOMP" -#endif - -#ifdef HAVE_BLKID -#define _BLKID_FEATURE_ "+BLKID" -#else -#define _BLKID_FEATURE_ "-BLKID" -#endif - -#ifdef HAVE_ELFUTILS -#define _ELFUTILS_FEATURE_ "+ELFUTILS" -#else -#define _ELFUTILS_FEATURE_ "-ELFUTILS" -#endif - -#ifdef HAVE_KMOD -#define _KMOD_FEATURE_ "+KMOD" -#else -#define _KMOD_FEATURE_ "-KMOD" -#endif - -#ifdef HAVE_LIBIDN -#define _IDN_FEATURE_ "+IDN" -#else -#define _IDN_FEATURE_ "-IDN" -#endif - -#define SYSTEMD_FEATURES \ - _PAM_FEATURE_ " " \ - _AUDIT_FEATURE_ " " \ - _SELINUX_FEATURE_ " " \ - _IMA_FEATURE_ " " \ - _APPARMOR_FEATURE_ " " \ - _SMACK_FEATURE_ " " \ - _SYSVINIT_FEATURE_ " " \ - _UTMP_FEATURE_ " " \ - _LIBCRYPTSETUP_FEATURE_ " " \ - _GCRYPT_FEATURE_ " " \ - _GNUTLS_FEATURE_ " " \ - _ACL_FEATURE_ " " \ - _XZ_FEATURE_ " " \ - _LZ4_FEATURE_ " " \ - _SECCOMP_FEATURE_ " " \ - _BLKID_FEATURE_ " " \ - _ELFUTILS_FEATURE_ " " \ - _KMOD_FEATURE_ " " \ - _IDN_FEATURE_ diff --git a/src/basic/bus-label.c b/src/basic/bus-label.c deleted file mode 100644 index d4531c7947..0000000000 --- a/src/basic/bus-label.c +++ /dev/null @@ -1,98 +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 . -***/ - -#include - -#include "alloc-util.h" -#include "bus-label.h" -#include "hexdecoct.h" -#include "macro.h" - -char *bus_label_escape(const char *s) { - char *r, *t; - const char *f; - - assert_return(s, NULL); - - /* Escapes all chars that D-Bus' object path cannot deal - * with. Can be reversed with bus_path_unescape(). We special - * case the empty string. */ - - if (*s == 0) - return strdup("_"); - - r = new(char, strlen(s)*3 + 1); - if (!r) - return NULL; - - for (f = s, t = r; *f; f++) { - - /* Escape everything that is not a-zA-Z0-9. We also - * escape 0-9 if it's the first character */ - - if (!(*f >= 'A' && *f <= 'Z') && - !(*f >= 'a' && *f <= 'z') && - !(f > s && *f >= '0' && *f <= '9')) { - *(t++) = '_'; - *(t++) = hexchar(*f >> 4); - *(t++) = hexchar(*f); - } else - *(t++) = *f; - } - - *t = 0; - - return r; -} - -char *bus_label_unescape_n(const char *f, size_t l) { - char *r, *t; - size_t i; - - assert_return(f, NULL); - - /* Special case for the empty string */ - if (l == 1 && *f == '_') - return strdup(""); - - r = new(char, l + 1); - if (!r) - return NULL; - - for (i = 0, t = r; i < l; ++i) { - if (f[i] == '_') { - int a, b; - - if (l - i < 3 || - (a = unhexchar(f[i + 1])) < 0 || - (b = unhexchar(f[i + 2])) < 0) { - /* Invalid escape code, let's take it literal then */ - *(t++) = '_'; - } else { - *(t++) = (char) ((a << 4) | b); - i += 2; - } - } else - *(t++) = f[i]; - } - - *t = 0; - - return r; -} diff --git a/src/basic/bus-label.h b/src/basic/bus-label.h deleted file mode 100644 index 62fb2c450c..0000000000 --- a/src/basic/bus-label.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include -#include -#include - -char *bus_label_escape(const char *s); -char *bus_label_unescape_n(const char *f, size_t l); - -static inline char *bus_label_unescape(const char *f) { - return bus_label_unescape_n(f, f ? strlen(f) : 0); -} diff --git a/src/basic/c-rbtree.c b/src/basic/c-rbtree.c deleted file mode 100644 index 914d7e5229..0000000000 --- a/src/basic/c-rbtree.c +++ /dev/null @@ -1,679 +0,0 @@ -/*** - This file is part of systemd. See COPYING for details. - - 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 . -***/ - -/* - * RB-Tree Implementation - * This implements the insertion/removal of elements in RB-Trees. You're highly - * recommended to have an RB-Tree documentation at hand when reading this. Both - * insertion and removal can be split into a handful of situations that can - * occur. Those situations are enumerated as "Case 1" to "Case n" here, and - * follow closely the cases described in most RB-Tree documentations. This file - * does not explain why it is enough to handle just those cases, nor does it - * provide a proof of correctness. Dig out your algorithm 101 handbook if - * you're interested. - * - * This implementation is *not* straightforward. Usually, a handful of - * rotation, reparent, swap and link helpers can be used to implement the - * rebalance operations. However, those often perform unnecessary writes. - * Therefore, this implementation hard-codes all the operations. You're highly - * recommended to look at the two basic helpers before reading the code: - * c_rbtree_swap_child() - * c_rbtree_set_parent_and_color() - * Those are the only helpers used, hence, you should really know what they do - * before digging into the code. - * - * For a highlevel documentation of the API, see the header file and docbook - * comments. - */ - -#include -#include -#include "c-rbtree.h" - -enum { - C_RBNODE_RED = 0, - C_RBNODE_BLACK = 1, -}; - -static inline unsigned long c_rbnode_color(CRBNode *n) { - return (unsigned long)n->__parent_and_color & 1UL; -} - -static inline _Bool c_rbnode_is_red(CRBNode *n) { - return c_rbnode_color(n) == C_RBNODE_RED; -} - -static inline _Bool c_rbnode_is_black(CRBNode *n) { - return c_rbnode_color(n) == C_RBNODE_BLACK; -} - -/** - * c_rbnode_leftmost() - return leftmost child - * @n: current node, or NULL - * - * This returns the leftmost child of @n. If @n is NULL, this will return NULL. - * In all other cases, this function returns a valid pointer. That is, if @n - * does not have any left children, this returns @n. - * - * Worst case runtime (n: number of elements in tree): O(log(n)) - * - * Return: Pointer to leftmost child, or NULL. - */ -CRBNode *c_rbnode_leftmost(CRBNode *n) { - if (n) - while (n->left) - n = n->left; - return n; -} - -/** - * c_rbnode_rightmost() - return rightmost child - * @n: current node, or NULL - * - * This returns the rightmost child of @n. If @n is NULL, this will return - * NULL. In all other cases, this function returns a valid pointer. That is, if - * @n does not have any right children, this returns @n. - * - * Worst case runtime (n: number of elements in tree): O(log(n)) - * - * Return: Pointer to rightmost child, or NULL. - */ -CRBNode *c_rbnode_rightmost(CRBNode *n) { - if (n) - while (n->right) - n = n->right; - return n; -} - -/** - * c_rbnode_next() - return next node - * @n: current node, or NULL - * - * An RB-Tree always defines a linear order of its elements. This function - * returns the logically next node to @n. If @n is NULL, the last node or - * unlinked, this returns NULL. - * - * Worst case runtime (n: number of elements in tree): O(log(n)) - * - * Return: Pointer to next node, or NULL. - */ -CRBNode *c_rbnode_next(CRBNode *n) { - CRBNode *p; - - if (!c_rbnode_is_linked(n)) - return NULL; - if (n->right) - return c_rbnode_leftmost(n->right); - - while ((p = c_rbnode_parent(n)) && n == p->right) - n = p; - - return p; -} - -/** - * c_rbnode_prev() - return previous node - * @n: current node, or NULL - * - * An RB-Tree always defines a linear order of its elements. This function - * returns the logically previous node to @n. If @n is NULL, the first node or - * unlinked, this returns NULL. - * - * Worst case runtime (n: number of elements in tree): O(log(n)) - * - * Return: Pointer to previous node, or NULL. - */ -CRBNode *c_rbnode_prev(CRBNode *n) { - CRBNode *p; - - if (!c_rbnode_is_linked(n)) - return NULL; - if (n->left) - return c_rbnode_rightmost(n->left); - - while ((p = c_rbnode_parent(n)) && n == p->left) - n = p; - - return p; -} - -/** - * c_rbtree_first() - return first node - * @t: tree to operate on - * - * An RB-Tree always defines a linear order of its elements. This function - * returns the logically first node in @t. If @t is empty, NULL is returned. - * - * Fixed runtime (n: number of elements in tree): O(log(n)) - * - * Return: Pointer to first node, or NULL. - */ -CRBNode *c_rbtree_first(CRBTree *t) { - assert(t); - return c_rbnode_leftmost(t->root); -} - -/** - * c_rbtree_last() - return last node - * @t: tree to operate on - * - * An RB-Tree always defines a linear order of its elements. This function - * returns the logically last node in @t. If @t is empty, NULL is returned. - * - * Fixed runtime (n: number of elements in tree): O(log(n)) - * - * Return: Pointer to last node, or NULL. - */ -CRBNode *c_rbtree_last(CRBTree *t) { - assert(t); - return c_rbnode_rightmost(t->root); -} - -/* - * Set the color and parent of a node. This should be treated as a simple - * assignment of the 'color' and 'parent' fields of the node. No other magic is - * applied. But since both fields share its backing memory, this helper - * function is provided. - */ -static inline void c_rbnode_set_parent_and_color(CRBNode *n, CRBNode *p, unsigned long c) { - assert(!((unsigned long)p & 1)); - assert(c < 2); - n->__parent_and_color = (CRBNode*)((unsigned long)p | c); -} - -/* same as c_rbnode_set_parent_and_color(), but keeps the current parent */ -static inline void c_rbnode_set_color(CRBNode *n, unsigned long c) { - c_rbnode_set_parent_and_color(n, c_rbnode_parent(n), c); -} - -/* same as c_rbnode_set_parent_and_color(), but keeps the current color */ -static inline void c_rbnode_set_parent(CRBNode *n, CRBNode *p) { - c_rbnode_set_parent_and_color(n, p, c_rbnode_color(n)); -} - -/* - * This function partially replaces an existing child pointer to a new one. The - * existing child must be given as @old, the new child as @new. @p must be the - * parent of @old (or NULL if it has no parent). - * This function ensures that the parent of @old now points to @new. However, - * it does *NOT* change the parent pointer of @new. The caller must ensure - * this. - * If @p is NULL, this function ensures that the root-pointer is adjusted - * instead (given as @t). - */ -static inline void c_rbtree_swap_child(CRBTree *t, CRBNode *p, CRBNode *old, CRBNode *new) { - if (p) { - if (p->left == old) - p->left = new; - else - p->right = new; - } else { - t->root = new; - } -} - -static inline CRBNode *c_rbtree_paint_one(CRBTree *t, CRBNode *n) { - CRBNode *p, *g, *gg, *u, *x; - - /* - * Paint a single node according to RB-Tree rules. The node must - * already be linked into the tree and painted red. - * We repaint the node or rotate the tree, if required. In case a - * recursive repaint is required, the next node to be re-painted - * is returned. - * p: parent - * g: grandparent - * gg: grandgrandparent - * u: uncle - * x: temporary - */ - - /* node is red, so we can access the parent directly */ - p = n->__parent_and_color; - - if (!p) { - /* Case 1: - * We reached the root. Mark it black and be done. As all - * leaf-paths share the root, the ratio of black nodes on each - * path stays the same. */ - c_rbnode_set_parent_and_color(n, p, C_RBNODE_BLACK); - n = NULL; - } else if (c_rbnode_is_black(p)) { - /* Case 2: - * The parent is already black. As our node is red, we did not - * change the number of black nodes on any path, nor do we have - * multiple consecutive red nodes. */ - n = NULL; - } else if (p == p->__parent_and_color->left) { /* parent is red, so grandparent exists */ - g = p->__parent_and_color; - gg = c_rbnode_parent(g); - u = g->right; - - if (u && c_rbnode_is_red(u)) { - /* Case 3: - * Parent and uncle are both red. We know the - * grandparent must be black then. Repaint parent and - * uncle black, the grandparent red and recurse into - * the grandparent. */ - c_rbnode_set_parent_and_color(p, g, C_RBNODE_BLACK); - c_rbnode_set_parent_and_color(u, g, C_RBNODE_BLACK); - c_rbnode_set_parent_and_color(g, gg, C_RBNODE_RED); - n = g; - } else { - /* parent is red, uncle is black */ - - if (n == p->right) { - /* Case 4: - * We're the right child. Rotate on parent to - * become left child, so we can handle it the - * same as case 5. */ - x = n->left; - p->right = n->left; - n->left = p; - if (x) - c_rbnode_set_parent_and_color(x, p, C_RBNODE_BLACK); - c_rbnode_set_parent_and_color(p, n, C_RBNODE_RED); - p = n; - } - - /* 'n' is invalid from here on! */ - n = NULL; - - /* Case 5: - * We're the red left child or a red parent, black - * grandparent and uncle. Rotate on grandparent and - * switch color with parent. Number of black nodes on - * each path stays the same, but we got rid of the - * double red path. As the grandparent is still black, - * we're done. */ - x = p->right; - g->left = x; - p->right = g; - if (x) - c_rbnode_set_parent_and_color(x, g, C_RBNODE_BLACK); - c_rbnode_set_parent_and_color(p, gg, C_RBNODE_BLACK); - c_rbnode_set_parent_and_color(g, p, C_RBNODE_RED); - c_rbtree_swap_child(t, gg, g, p); - } - } else /* if (p == p->__parent_and_color->left) */ { /* same as above, but mirrored */ - g = p->__parent_and_color; - gg = c_rbnode_parent(g); - u = g->left; - - if (u && c_rbnode_is_red(u)) { - c_rbnode_set_parent_and_color(p, g, C_RBNODE_BLACK); - c_rbnode_set_parent_and_color(u, g, C_RBNODE_BLACK); - c_rbnode_set_parent_and_color(g, gg, C_RBNODE_RED); - n = g; - } else { - if (n == p->left) { - x = n->right; - p->left = n->right; - n->right = p; - if (x) - c_rbnode_set_parent_and_color(x, p, C_RBNODE_BLACK); - c_rbnode_set_parent_and_color(p, n, C_RBNODE_RED); - p = n; - } - - n = NULL; - - x = p->left; - g->right = x; - p->left = g; - if (x) - c_rbnode_set_parent_and_color(x, g, C_RBNODE_BLACK); - c_rbnode_set_parent_and_color(p, gg, C_RBNODE_BLACK); - c_rbnode_set_parent_and_color(g, p, C_RBNODE_RED); - c_rbtree_swap_child(t, gg, g, p); - } - } - - return n; -} - -static inline void c_rbtree_paint(CRBTree *t, CRBNode *n) { - assert(t); - assert(n); - - while (n) - n = c_rbtree_paint_one(t, n); -} - -/** - * c_rbtree_add() - add node to tree - * @t: tree to operate one - * @p: parent node to link under, or NULL - * @l: left/right slot of @p (or root) to link at - * @n: node to add - * - * This links @n into the tree given as @t. The caller must provide the exact - * spot where to link the node. That is, the caller must traverse the tree - * based on their search order. Once they hit a leaf where to insert the node, - * call this function to link it and rebalance the tree. - * - * A typical insertion would look like this (@t is your tree, @n is your node): - * - * CRBNode **i, *p; - * - * i = &t->root; - * p = NULL; - * while (*i) { - * p = *i; - * if (compare(n, *i) < 0) - * i = &(*i)->left; - * else - * i = &(*i)->right; - * } - * - * c_rbtree_add(t, p, i, n); - * - * Once the node is linked into the tree, a simple lookup on the same tree can - * be coded like this: - * - * CRBNode *i; - * - * i = t->root; - * while (i) { - * int v = compare(n, i); - * if (v < 0) - * i = (*i)->left; - * else if (v > 0) - * i = (*i)->right; - * else - * break; - * } - * - * When you add nodes to a tree, the memory contents of the node do not matter. - * That is, there is no need to initialize the node via c_rbnode_init(). - * However, if you relink nodes multiple times during their lifetime, it is - * usually very convenient to use c_rbnode_init() and c_rbtree_remove_init(). - * In those cases, you should validate that a node is unlinked before you call - * c_rbtree_add(). - */ -void c_rbtree_add(CRBTree *t, CRBNode *p, CRBNode **l, CRBNode *n) { - assert(t); - assert(l); - assert(n); - assert(!p || l == &p->left || l == &p->right); - assert(p || l == &t->root); - - c_rbnode_set_parent_and_color(n, p, C_RBNODE_RED); - n->left = n->right = NULL; - *l = n; - - c_rbtree_paint(t, n); -} - -static inline CRBNode *c_rbtree_rebalance_one(CRBTree *t, CRBNode *p, CRBNode *n) { - CRBNode *s, *x, *y, *g; - - /* - * Rebalance tree after a node was removed. This happens only if you - * remove a black node and one path is now left with an unbalanced - * number or black nodes. - * This function assumes all paths through p and n have one black node - * less than all other paths. If recursive fixup is required, the - * current node is returned. - */ - - if (n == p->left) { - s = p->right; - if (c_rbnode_is_red(s)) { - /* Case 3: - * We have a red node as sibling. Rotate it onto our - * side so we can later on turn it black. This way, we - * gain the additional black node in our path. */ - g = c_rbnode_parent(p); - x = s->left; - p->right = x; - s->left = p; - c_rbnode_set_parent_and_color(x, p, C_RBNODE_BLACK); - c_rbnode_set_parent_and_color(s, g, c_rbnode_color(p)); - c_rbnode_set_parent_and_color(p, s, C_RBNODE_RED); - c_rbtree_swap_child(t, g, p, s); - s = x; - } - - x = s->right; - if (!x || c_rbnode_is_black(x)) { - y = s->left; - if (!y || c_rbnode_is_black(y)) { - /* Case 4: - * Our sibling is black and has only black - * children. Flip it red and turn parent black. - * This way we gained a black node in our path, - * or we fix it recursively one layer up, which - * will rotate the red sibling as parent. */ - c_rbnode_set_parent_and_color(s, p, C_RBNODE_RED); - if (c_rbnode_is_black(p)) - return p; - - c_rbnode_set_parent_and_color(p, c_rbnode_parent(p), C_RBNODE_BLACK); - return NULL; - } - - /* Case 5: - * Left child of our sibling is red, right one is black. - * Rotate on parent so the right child of our sibling is - * now red, and we can fall through to case 6. */ - x = y->right; - s->left = y->right; - y->right = s; - p->right = y; - if (x) - c_rbnode_set_parent_and_color(x, s, C_RBNODE_BLACK); - x = s; - s = y; - } - - /* Case 6: - * The right child of our sibling is red. Rotate left and flip - * colors, which gains us an additional black node in our path, - * that was previously on our sibling. */ - g = c_rbnode_parent(p); - y = s->left; - p->right = y; - s->left = p; - c_rbnode_set_parent_and_color(x, s, C_RBNODE_BLACK); - if (y) - c_rbnode_set_parent_and_color(y, p, c_rbnode_color(y)); - c_rbnode_set_parent_and_color(s, g, c_rbnode_color(p)); - c_rbnode_set_parent_and_color(p, s, C_RBNODE_BLACK); - c_rbtree_swap_child(t, g, p, s); - } else /* if (!n || n == p->right) */ { /* same as above, but mirrored */ - s = p->left; - if (c_rbnode_is_red(s)) { - g = c_rbnode_parent(p); - x = s->right; - p->left = x; - s->right = p; - c_rbnode_set_parent_and_color(x, p, C_RBNODE_BLACK); - c_rbnode_set_parent_and_color(s, g, C_RBNODE_BLACK); - c_rbnode_set_parent_and_color(p, s, C_RBNODE_RED); - c_rbtree_swap_child(t, g, p, s); - s = x; - } - - x = s->left; - if (!x || c_rbnode_is_black(x)) { - y = s->right; - if (!y || c_rbnode_is_black(y)) { - c_rbnode_set_parent_and_color(s, p, C_RBNODE_RED); - if (c_rbnode_is_black(p)) - return p; - - c_rbnode_set_parent_and_color(p, c_rbnode_parent(p), C_RBNODE_BLACK); - return NULL; - } - - x = y->left; - s->right = y->left; - y->left = s; - p->left = y; - if (x) - c_rbnode_set_parent_and_color(x, s, C_RBNODE_BLACK); - x = s; - s = y; - } - - g = c_rbnode_parent(p); - y = s->right; - p->left = y; - s->right = p; - c_rbnode_set_parent_and_color(x, s, C_RBNODE_BLACK); - if (y) - c_rbnode_set_parent_and_color(y, p, c_rbnode_color(y)); - c_rbnode_set_parent_and_color(s, g, c_rbnode_color(p)); - c_rbnode_set_parent_and_color(p, s, C_RBNODE_BLACK); - c_rbtree_swap_child(t, g, p, s); - } - - return NULL; -} - -static inline void c_rbtree_rebalance(CRBTree *t, CRBNode *p) { - CRBNode *n = NULL; - - assert(t); - assert(p); - - do { - n = c_rbtree_rebalance_one(t, p, n); - p = n ? c_rbnode_parent(n) : NULL; - } while (p); -} - -/** - * c_rbtree_remove() - remove node from tree - * @t: tree to operate one - * @n: node to remove - * - * This removes the given node from its tree. Once unlinked, the tree is - * rebalanced. - * The caller *must* ensure that the given tree is actually the tree it is - * linked on. Otherwise, behavior is undefined. - * - * This does *NOT* reset @n to being unlinked (for performance reason, this - * function *never* modifies @n at all). If you need this, use - * c_rbtree_remove_init(). - */ -void c_rbtree_remove(CRBTree *t, CRBNode *n) { - CRBNode *p, *s, *gc, *x, *next = NULL; - unsigned long c; - - assert(t); - assert(n); - assert(c_rbnode_is_linked(n)); - - /* - * There are three distinct cases during node removal of a tree: - * * The node has no children, in which case it can simply be removed. - * * The node has exactly one child, in which case the child displaces - * its parent. - * * The node has two children, in which case there is guaranteed to - * be a successor to the node (successor being the node ordered - * directly after it). This successor cannot have two children by - * itself (two interior nodes can never be successive). Therefore, - * we can simply swap the node with its successor (including color) - * and have reduced this case to either of the first two. - * - * Whenever the node we removed was black, we have to rebalance the - * tree. Note that this affects the actual node we _remove_, not @n (in - * case we swap it). - * - * p: parent - * s: successor - * gc: grand-...-child - * x: temporary - * next: next node to rebalance on - */ - - if (!n->left) { - /* - * Case 1: - * The node has no left child. If it neither has a right child, - * it is a leaf-node and we can simply unlink it. If it also - * was black, we have to rebalance, as always if we remove a - * black node. - * But if the node has a right child, the child *must* be red - * (otherwise, the right path has more black nodes as the - * non-existing left path), and the node to be removed must - * hence be black. We simply replace the node with its child, - * turning the red child black, and thus no rebalancing is - * required. - */ - p = c_rbnode_parent(n); - c = c_rbnode_color(n); - c_rbtree_swap_child(t, p, n, n->right); - if (n->right) - c_rbnode_set_parent_and_color(n->right, p, c); - else - next = (c == C_RBNODE_BLACK) ? p : NULL; - } else if (!n->right) { - /* - * Case 1.1: - * The node has exactly one child, and it is on the left. Treat - * it as mirrored case of Case 1 (i.e., replace the node by its - * child). - */ - p = c_rbnode_parent(n); - c = c_rbnode_color(n); - c_rbtree_swap_child(t, p, n, n->left); - c_rbnode_set_parent_and_color(n->left, p, c); - } else { - /* - * Case 2: - * We are dealing with a full interior node with a child not on - * both sides. Find its successor and swap it. Then remove the - * node similar to Case 1. For performance reasons we don't - * perform the full swap, but skip links that are about to be - * removed, anyway. - */ - s = n->right; - if (!s->left) { - /* right child is next, no need to touch grandchild */ - p = s; - gc = s->right; - } else { - /* find successor and swap partially */ - s = c_rbnode_leftmost(s); - p = c_rbnode_parent(s); - - gc = s->right; - p->left = s->right; - s->right = n->right; - c_rbnode_set_parent(n->right, s); - } - - /* node is partially swapped, now remove as in Case 1 */ - s->left = n->left; - c_rbnode_set_parent(n->left, s); - - x = c_rbnode_parent(n); - c = c_rbnode_color(n); - c_rbtree_swap_child(t, x, n, s); - if (gc) - c_rbnode_set_parent_and_color(gc, p, C_RBNODE_BLACK); - else - next = c_rbnode_is_black(s) ? p : NULL; - c_rbnode_set_parent_and_color(s, x, c); - } - - if (next) - c_rbtree_rebalance(t, next); -} diff --git a/src/basic/c-rbtree.h b/src/basic/c-rbtree.h deleted file mode 100644 index 20c5515ca1..0000000000 --- a/src/basic/c-rbtree.h +++ /dev/null @@ -1,297 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. See COPYING for details. - - 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 . -***/ - -/* - * Standalone Red-Black-Tree Implementation in Standard ISO-C11 - * - * This header provides an RB-Tree API, that is fully implemented in ISO-C11 - * and has no external dependencies. Furthermore, tree traversal, memory - * allocations, and key comparisons a fully in control of the API user. The - * implementation only provides the RB-Tree specific rebalancing and coloring. - * - * A tree is represented by the "CRBTree" structure. It contains a *singly* - * field, which is a pointer to the root node. If NULL, the tree is empty. If - * non-NULL, there is at least a single element in the tree. - * - * Each node of the tree is represented by the "CRBNode" structure. It has - * three fields. The @left and @right members can be accessed by the API user - * directly to traverse the tree. The third member is an implementation detail - * and encodes the parent pointer and color of the node. - * API users are required to embed the CRBNode object into their own objects - * and then use offsetof() (i.e., container_of() and friends) to turn CRBNode - * pointers into pointers to their own structure. - */ - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct CRBNode CRBNode; -typedef struct CRBTree CRBTree; - -/** - * struct CRBNode - Node of a Red-Black Tree - * @__parent_and_color: internal state - * @left: left child, or NULL - * @right: right child, or NULL - * - * Each node in an RB-Tree must embed an CRBNode object. This object contains - * pointers to its left and right child, which can be freely accessed by the - * API user at any time. They are NULL, if the node does not have a left/right - * child. - * - * The @__parent_and_color field must never be accessed directly. It encodes - * the pointer to the parent node, and the color of the node. Use the accessor - * functions instead. - * - * There is no reason to initialize a CRBNode object before linking it. - * However, if you need a boolean state that tells you whether the node is - * linked or not, you should initialize the node via c_rbnode_init() or - * C_RBNODE_INIT. - */ -struct CRBNode { - CRBNode *__parent_and_color; - CRBNode *left; - CRBNode *right; -}; - -#define C_RBNODE_INIT(_var) { .__parent_and_color = &(_var) } - -CRBNode *c_rbnode_leftmost(CRBNode *n); -CRBNode *c_rbnode_rightmost(CRBNode *n); -CRBNode *c_rbnode_next(CRBNode *n); -CRBNode *c_rbnode_prev(CRBNode *n); - -/** - * struct CRBTree - Red-Black Tree - * @root: pointer to the root node, or NULL - * - * Each Red-Black Tree is rooted in an CRBTree object. This object contains a - * pointer to the root node of the tree. The API user is free to access the - * @root member at any time, and use it to traverse the tree. - * - * To initialize an RB-Tree, set it to NULL / all zero. - */ -struct CRBTree { - CRBNode *root; -}; - -CRBNode *c_rbtree_first(CRBTree *t); -CRBNode *c_rbtree_last(CRBTree *t); - -void c_rbtree_add(CRBTree *t, CRBNode *p, CRBNode **l, CRBNode *n); -void c_rbtree_remove(CRBTree *t, CRBNode *n); - -/** - * c_rbnode_init() - mark a node as unlinked - * @n: node to operate on - * - * This marks the node @n as unlinked. The node will be set to a valid state - * that can never happen if the node is linked in a tree. Furthermore, this - * state is fully known to the implementation, and as such handled gracefully - * in all cases. - * - * You are *NOT* required to call this on your node. c_rbtree_add() can handle - * uninitialized nodes just fine. However, calling this allows to use - * c_rbnode_is_linked() to check for the state of a node. Furthermore, - * iterators and accessors can be called on initialized (yet unlinked) nodes. - * - * Use the C_RBNODE_INIT macro if you want to initialize static variables. - */ -static inline void c_rbnode_init(CRBNode *n) { - *n = (CRBNode)C_RBNODE_INIT(*n); -} - -/** - * c_rbnode_is_linked() - check whether a node is linked - * @n: node to check, or NULL - * - * This checks whether the passed node is linked. If you pass NULL, or if the - * node is not linked into a tree, this will return false. Otherwise, this - * returns true. - * - * Note that you must have either linked the node or initialized it, before - * calling this function. Never call this function on uninitialized nodes. - * Furthermore, removing a node via c_rbtree_remove() does *NOT* mark the node - * as unlinked. You have to call c_rbnode_init() yourself after removal, or use - * the c_rbtree_remove_init() helper. - * - * Return: true if the node is linked, false if not. - */ -static inline _Bool c_rbnode_is_linked(CRBNode *n) { - return n && n->__parent_and_color != n; -} - -/** - * c_rbnode_parent() - return parent pointer - * @n node to access - * - * This returns a pointer to the parent of the given node @n. If @n does not - * have a parent, NULL is returned. If @n is not linked, @n itself is returned. - * - * You should not call this on unlinked or uninitialized nodes! If you do, you - * better know how its semantics. - * - * Return: Pointer to parent. - */ -static inline CRBNode *c_rbnode_parent(CRBNode *n) { - return (CRBNode*)((unsigned long)n->__parent_and_color & ~1UL); -} - -/** - * c_rbtree_remove_init() - safely remove node from tree and reinitialize it - * @t: tree to operate on - * @n: node to remove, or NULL - * - * This is almost the same as c_rbtree_remove(), but extends it slightly, to be - * more convenient to use in many cases: - * - if @n is unlinked or NULL, this is a no-op - * - @n is reinitialized after being removed - */ -static inline void c_rbtree_remove_init(CRBTree *t, CRBNode *n) { - if (c_rbnode_is_linked(n)) { - c_rbtree_remove(t, n); - c_rbnode_init(n); - } -} - -/** - * CRBCompareFunc - compare a node to a key - * @t: tree where the node is linked to - * @k: key to compare - * @n: node to compare - * - * If you use the tree-traversal helpers (which are optional), you need to - * provide this callback so they can compare nodes in a tree to the key you - * look for. - * - * The tree @t is provided as optional context to this callback. The key you - * look for is provided as @k, the current node that should be compared to is - * provided as @n. This function should work like strcmp(), that is, return -1 - * if @key orders before @n, 0 if both compare equal, and 1 if it orders after - * @n. - */ -typedef int (*CRBCompareFunc) (CRBTree *t, void *k, CRBNode *n); - -/** - * c_rbtree_find_node() - find node - * @t: tree to search through - * @f: comparison function - * @k: key to search for - * - * This searches through @t for a node that compares equal to @k. The function - * @f must be provided by the caller, which is used to compare nodes to @k. See - * the documentation of CRBCompareFunc for details. - * - * If there are multiple entries that compare equal to @k, this will return a - * pseudo-randomly picked node. If you need stable lookup functions for trees - * where duplicate entries are allowed, you better code your own lookup. - * - * Return: Pointer to matching node, or NULL. - */ -static inline CRBNode *c_rbtree_find_node(CRBTree *t, CRBCompareFunc f, const void *k) { - CRBNode *i; - - assert(t); - assert(f); - - i = t->root; - while (i) { - int v = f(t, (void *)k, i); - if (v < 0) - i = i->left; - else if (v > 0) - i = i->right; - else - return i; - } - - return NULL; -} - -/** - * c_rbtree_find_entry() - find entry - * @_t: tree to search through - * @_f: comparison function - * @_k: key to search for - * @_t: type of the structure that embeds the nodes - * @_o: name of the node-member in type @_t - * - * This is very similar to c_rbtree_find_node(), but instead of returning a - * pointer to the CRBNode, it returns a pointer to the surrounding object. This - * object must embed the CRBNode object. The type of the surrounding object - * must be given as @_t, and the name of the embedded CRBNode member as @_o. - * - * See c_rbtree_find_node() for more details. - * - * Return: Pointer to found entry, NULL if not found. - */ -#define c_rbtree_find_entry(_m, _f, _k, _t, _o) \ - ((_t *)(((char *)c_rbtree_find_node((_m), (_f), (_k)) ?: \ - (char *)NULL + offsetof(_t, _o)) - offsetof(_t, _o))) - -/** - * c_rbtree_find_slot() - find slot to insert new node - * @t: tree to search through - * @f: comparison function - * @k: key to search for - * @p: output storage for parent pointer - * - * This searches through @t just like c_rbtree_find_node() does. However, - * instead of returning a pointer to a node that compares equal to @k, this - * searches for a slot to insert a node with key @k. A pointer to the slot is - * returned, and a pointer to the parent of the slot is stored in @p. Both - * can be passed directly to c_rbtree_add(), together with your node to insert. - * - * If there already is a node in the tree, that compares equal to @k, this will - * return NULL and store the conflicting node in @p. In all other cases, - * this will return a pointer (non-NULL) to the empty slot to insert the node - * at. @p will point to the parent node of that slot. - * - * If you want trees that allow duplicate nodes, you better code your own - * insertion function. - * - * Return: Pointer to slot to insert node, or NULL on conflicts. - */ -static inline CRBNode **c_rbtree_find_slot(CRBTree *t, CRBCompareFunc f, const void *k, CRBNode **p) { - CRBNode **i; - - assert(t); - assert(f); - assert(p); - - i = &t->root; - *p = NULL; - while (*i) { - int v = f(t, (void *)k, *i); - *p = *i; - if (v < 0) - i = &(*i)->left; - else if (v > 0) - i = &(*i)->right; - else - return NULL; - } - - return i; -} - -#ifdef __cplusplus -} -#endif diff --git a/src/basic/calendarspec.c b/src/basic/calendarspec.c deleted file mode 100644 index 775879076d..0000000000 --- a/src/basic/calendarspec.c +++ /dev/null @@ -1,1087 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "calendarspec.h" -#include "fileio.h" -#include "macro.h" -#include "parse-util.h" -#include "string-util.h" - -#define BITS_WEEKDAYS 127 - -static void free_chain(CalendarComponent *c) { - CalendarComponent *n; - - while (c) { - n = c->next; - free(c); - c = n; - } -} - -void calendar_spec_free(CalendarSpec *c) { - - if (!c) - return; - - free_chain(c->year); - free_chain(c->month); - free_chain(c->day); - free_chain(c->hour); - free_chain(c->minute); - free_chain(c->microsecond); - - free(c); -} - -static int component_compare(const void *_a, const void *_b) { - CalendarComponent * const *a = _a, * const *b = _b; - - if ((*a)->value < (*b)->value) - return -1; - if ((*a)->value > (*b)->value) - return 1; - - if ((*a)->repeat < (*b)->repeat) - return -1; - if ((*a)->repeat > (*b)->repeat) - return 1; - - return 0; -} - -static void sort_chain(CalendarComponent **c) { - unsigned n = 0, k; - CalendarComponent **b, *i, **j, *next; - - assert(c); - - for (i = *c; i; i = i->next) - n++; - - if (n <= 1) - return; - - j = b = alloca(sizeof(CalendarComponent*) * n); - for (i = *c; i; i = i->next) - *(j++) = i; - - qsort(b, n, sizeof(CalendarComponent*), component_compare); - - b[n-1]->next = NULL; - next = b[n-1]; - - /* Drop non-unique entries */ - for (k = n-1; k > 0; k--) { - if (b[k-1]->value == next->value && - b[k-1]->repeat == next->repeat) { - free(b[k-1]); - continue; - } - - b[k-1]->next = next; - next = b[k-1]; - } - - *c = next; -} - -static void fix_year(CalendarComponent *c) { - /* Turns 12 → 2012, 89 → 1989 */ - - while(c) { - CalendarComponent *n = c->next; - - if (c->value >= 0 && c->value < 70) - c->value += 2000; - - if (c->value >= 70 && c->value < 100) - c->value += 1900; - - c = n; - } -} - -int calendar_spec_normalize(CalendarSpec *c) { - assert(c); - - if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS) - c->weekdays_bits = -1; - - fix_year(c->year); - - sort_chain(&c->year); - sort_chain(&c->month); - sort_chain(&c->day); - sort_chain(&c->hour); - sort_chain(&c->minute); - sort_chain(&c->microsecond); - - return 0; -} - -_pure_ static bool chain_valid(CalendarComponent *c, int from, int to) { - if (!c) - return true; - - if (c->value < from || c->value > to) - return false; - - if (c->value + c->repeat > to) - return false; - - if (c->next) - return chain_valid(c->next, from, to); - - return true; -} - -_pure_ bool calendar_spec_valid(CalendarSpec *c) { - assert(c); - - if (c->weekdays_bits > BITS_WEEKDAYS) - return false; - - if (!chain_valid(c->year, 1970, 2199)) - return false; - - if (!chain_valid(c->month, 1, 12)) - return false; - - if (!chain_valid(c->day, 1, 31)) - return false; - - if (!chain_valid(c->hour, 0, 23)) - return false; - - if (!chain_valid(c->minute, 0, 59)) - return false; - - if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1)) - return false; - - return true; -} - -static void format_weekdays(FILE *f, const CalendarSpec *c) { - static const char *const days[] = { - "Mon", - "Tue", - "Wed", - "Thu", - "Fri", - "Sat", - "Sun" - }; - - int l, x; - bool need_colon = false; - - assert(f); - assert(c); - assert(c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS); - - for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) { - - if (c->weekdays_bits & (1 << x)) { - - if (l < 0) { - if (need_colon) - fputc(',', f); - else - need_colon = true; - - fputs(days[x], f); - l = x; - } - - } else if (l >= 0) { - - if (x > l + 1) { - fputc(x > l + 2 ? '-' : ',', f); - fputs(days[x-1], f); - } - - l = -1; - } - } - - if (l >= 0 && x > l + 1) { - fputc(x > l + 2 ? '-' : ',', f); - fputs(days[x-1], f); - } -} - -static void format_chain(FILE *f, int space, const CalendarComponent *c, bool usec) { - assert(f); - - if (!c) { - fputc('*', f); - return; - } - - assert(c->value >= 0); - if (!usec) - fprintf(f, "%0*i", space, c->value); - else if (c->value % USEC_PER_SEC == 0) - fprintf(f, "%0*i", space, (int) (c->value / USEC_PER_SEC)); - else - fprintf(f, "%0*i.%06i", space, (int) (c->value / USEC_PER_SEC), (int) (c->value % USEC_PER_SEC)); - - if (c->repeat > 0) { - if (!usec) - fprintf(f, "/%i", c->repeat); - else if (c->repeat % USEC_PER_SEC == 0) - fprintf(f, "/%i", (int) (c->repeat / USEC_PER_SEC)); - else - fprintf(f, "/%i.%06i", (int) (c->repeat / USEC_PER_SEC), (int) (c->repeat % USEC_PER_SEC)); - } - - if (c->next) { - fputc(',', f); - format_chain(f, space, c->next, usec); - } -} - -int calendar_spec_to_string(const CalendarSpec *c, char **p) { - char *buf = NULL; - size_t sz = 0; - FILE *f; - int r; - - assert(c); - assert(p); - - f = open_memstream(&buf, &sz); - if (!f) - return -ENOMEM; - - if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) { - format_weekdays(f, c); - fputc(' ', f); - } - - format_chain(f, 4, c->year, false); - fputc('-', f); - format_chain(f, 2, c->month, false); - fputc('-', f); - format_chain(f, 2, c->day, false); - fputc(' ', f); - format_chain(f, 2, c->hour, false); - fputc(':', f); - format_chain(f, 2, c->minute, false); - fputc(':', f); - format_chain(f, 2, c->microsecond, true); - - if (c->utc) - fputs(" UTC", f); - - r = fflush_and_check(f); - if (r < 0) { - free(buf); - fclose(f); - return r; - } - - fclose(f); - - *p = buf; - return 0; -} - -static int parse_weekdays(const char **p, CalendarSpec *c) { - static const struct { - const char *name; - const int nr; - } day_nr[] = { - { "Monday", 0 }, - { "Mon", 0 }, - { "Tuesday", 1 }, - { "Tue", 1 }, - { "Wednesday", 2 }, - { "Wed", 2 }, - { "Thursday", 3 }, - { "Thu", 3 }, - { "Friday", 4 }, - { "Fri", 4 }, - { "Saturday", 5 }, - { "Sat", 5 }, - { "Sunday", 6 }, - { "Sun", 6 } - }; - - int l = -1; - bool first = true; - - assert(p); - assert(*p); - assert(c); - - for (;;) { - unsigned i; - - if (!first && **p == ' ') - return 0; - - for (i = 0; i < ELEMENTSOF(day_nr); i++) { - size_t skip; - - if (!startswith_no_case(*p, day_nr[i].name)) - continue; - - skip = strlen(day_nr[i].name); - - if ((*p)[skip] != '-' && - (*p)[skip] != ',' && - (*p)[skip] != ' ' && - (*p)[skip] != 0) - return -EINVAL; - - c->weekdays_bits |= 1 << day_nr[i].nr; - - if (l >= 0) { - int j; - - if (l > day_nr[i].nr) - return -EINVAL; - - for (j = l + 1; j < day_nr[i].nr; j++) - c->weekdays_bits |= 1 << j; - } - - *p += skip; - break; - } - - /* Couldn't find this prefix, so let's assume the - weekday was not specified and let's continue with - the date */ - if (i >= ELEMENTSOF(day_nr)) - return first ? 0 : -EINVAL; - - /* We reached the end of the string */ - if (**p == 0) - return 0; - - /* We reached the end of the weekday spec part */ - if (**p == ' ') { - *p += strspn(*p, " "); - return 0; - } - - if (**p == '-') { - if (l >= 0) - return -EINVAL; - - l = day_nr[i].nr; - } else - l = -1; - - *p += 1; - first = false; - } -} - -static int parse_component_decimal(const char **p, bool usec, unsigned long *res) { - unsigned long value; - const char *e = NULL; - char *ee = NULL; - int r; - - errno = 0; - value = strtoul(*p, &ee, 10); - if (errno > 0) - return -errno; - if (ee == *p) - return -EINVAL; - if ((unsigned long) (int) value != value) - return -ERANGE; - e = ee; - - if (usec) { - if (value * USEC_PER_SEC / USEC_PER_SEC != value) - return -ERANGE; - - value *= USEC_PER_SEC; - if (*e == '.') { - unsigned add; - - e++; - r = parse_fractional_part_u(&e, 6, &add); - if (r < 0) - return r; - - if (add + value < value) - return -ERANGE; - value += add; - } - } - - *p = e; - *res = value; - - return 0; -} - -static int prepend_component(const char **p, bool usec, CalendarComponent **c) { - unsigned long value, repeat = 0; - CalendarComponent *cc; - int r; - const char *e; - - assert(p); - assert(c); - - e = *p; - - r = parse_component_decimal(&e, usec, &value); - if (r < 0) - return r; - - if (*e == '/') { - e++; - r = parse_component_decimal(&e, usec, &repeat); - if (r < 0) - return r; - - if (repeat == 0) - return -ERANGE; - } - - if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':') - return -EINVAL; - - cc = new0(CalendarComponent, 1); - if (!cc) - return -ENOMEM; - - cc->value = value; - cc->repeat = repeat; - cc->next = *c; - - *p = e; - *c = cc; - - if (*e ==',') { - *p += 1; - return prepend_component(p, usec, 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; - int r; - - assert(p); - assert(c); - - t = *p; - - if (t[0] == '*') { - if (usec) { - r = const_chain(0, c); - if (r < 0) - return r; - (*c)->repeat = USEC_PER_SEC; - } else - *c = NULL; - - *p = t + 1; - return 0; - } - - r = prepend_component(&t, usec, &cc); - if (r < 0) { - free_chain(cc); - return r; - } - - *p = t; - *c = cc; - return 0; -} - -static int parse_date(const char **p, CalendarSpec *c) { - const char *t; - int r; - CalendarComponent *first, *second, *third; - - assert(p); - assert(*p); - assert(c); - - t = *p; - - if (*t == 0) - return 0; - - r = parse_chain(&t, false, &first); - if (r < 0) - return r; - - /* Already the end? A ':' as separator? In that case this was a time, not a date */ - if (*t == 0 || *t == ':') { - free_chain(first); - return 0; - } - - if (*t != '-') { - free_chain(first); - return -EINVAL; - } - - t++; - r = parse_chain(&t, false, &second); - if (r < 0) { - free_chain(first); - return r; - } - - /* Got two parts, hence it's month and day */ - if (*t == ' ' || *t == 0) { - *p = t + strspn(t, " "); - c->month = first; - c->day = second; - return 0; - } - - if (*t != '-') { - free_chain(first); - free_chain(second); - return -EINVAL; - } - - t++; - r = parse_chain(&t, false, &third); - if (r < 0) { - free_chain(first); - free_chain(second); - return r; - } - - /* Got tree parts, hence it is year, month and day */ - if (*t == ' ' || *t == 0) { - *p = t + strspn(t, " "); - c->year = first; - c->month = second; - c->day = third; - return 0; - } - - free_chain(first); - free_chain(second); - free_chain(third); - return -EINVAL; -} - -static int parse_calendar_time(const char **p, CalendarSpec *c) { - CalendarComponent *h = NULL, *m = NULL, *s = NULL; - const char *t; - int r; - - assert(p); - assert(*p); - assert(c); - - t = *p; - - if (*t == 0) { - /* If no time is specified at all, but a date of some - * kind, then this means 00:00:00 */ - if (c->day || c->weekdays_bits > 0) - goto null_hour; - - goto finish; - } - - r = parse_chain(&t, false, &h); - if (r < 0) - goto fail; - - if (*t != ':') { - r = -EINVAL; - goto fail; - } - - t++; - r = parse_chain(&t, false, &m); - if (r < 0) - goto fail; - - /* Already at the end? Then it's hours and minutes, and seconds are 0 */ - if (*t == 0) { - if (m != NULL) - goto null_second; - - goto finish; - } - - if (*t != ':') { - r = -EINVAL; - goto fail; - } - - t++; - r = parse_chain(&t, true, &s); - if (r < 0) - goto fail; - - /* At the end? Then it's hours, minutes and seconds */ - if (*t == 0) - goto finish; - - r = -EINVAL; - goto fail; - -null_hour: - r = const_chain(0, &h); - if (r < 0) - goto fail; - - r = const_chain(0, &m); - if (r < 0) - goto fail; - -null_second: - r = const_chain(0, &s); - if (r < 0) - goto fail; - -finish: - *p = t; - c->hour = h; - c->minute = m; - c->microsecond = s; - - return 0; - -fail: - free_chain(h); - free_chain(m); - free_chain(s); - return r; -} - -int calendar_spec_from_string(const char *p, CalendarSpec **spec) { - CalendarSpec *c; - int r; - const char *utc; - - assert(p); - assert(spec); - - if (isempty(p)) - return -EINVAL; - - c = new0(CalendarSpec, 1); - if (!c) - return -ENOMEM; - - utc = endswith_no_case(p, " UTC"); - if (utc) { - c->utc = true; - p = strndupa(p, utc - p); - } - - if (strcaseeq(p, "minutely")) { - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "hourly")) { - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "daily")) { - r = const_chain(0, &c->hour); - if (r < 0) - goto fail; - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "monthly")) { - r = const_chain(1, &c->day); - if (r < 0) - goto fail; - r = const_chain(0, &c->hour); - if (r < 0) - goto fail; - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "annually") || - strcaseeq(p, "yearly") || - strcaseeq(p, "anually") /* backwards compatibility */ ) { - - r = const_chain(1, &c->month); - if (r < 0) - goto fail; - r = const_chain(1, &c->day); - if (r < 0) - goto fail; - r = const_chain(0, &c->hour); - if (r < 0) - goto fail; - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "weekly")) { - - c->weekdays_bits = 1; - - r = const_chain(0, &c->hour); - if (r < 0) - goto fail; - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "quarterly")) { - - r = const_chain(1, &c->month); - if (r < 0) - goto fail; - r = const_chain(4, &c->month); - if (r < 0) - goto fail; - r = const_chain(7, &c->month); - if (r < 0) - goto fail; - r = const_chain(10, &c->month); - if (r < 0) - goto fail; - r = const_chain(1, &c->day); - if (r < 0) - goto fail; - r = const_chain(0, &c->hour); - if (r < 0) - goto fail; - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else if (strcaseeq(p, "biannually") || - strcaseeq(p, "bi-annually") || - strcaseeq(p, "semiannually") || - strcaseeq(p, "semi-annually")) { - - r = const_chain(1, &c->month); - if (r < 0) - goto fail; - r = const_chain(7, &c->month); - if (r < 0) - goto fail; - r = const_chain(1, &c->day); - if (r < 0) - goto fail; - r = const_chain(0, &c->hour); - if (r < 0) - goto fail; - r = const_chain(0, &c->minute); - if (r < 0) - goto fail; - r = const_chain(0, &c->microsecond); - if (r < 0) - goto fail; - - } else { - r = parse_weekdays(&p, c); - if (r < 0) - goto fail; - - r = parse_date(&p, c); - if (r < 0) - goto fail; - - r = parse_calendar_time(&p, c); - if (r < 0) - goto fail; - - if (*p != 0) { - r = -EINVAL; - goto fail; - } - } - - r = calendar_spec_normalize(c); - if (r < 0) - goto fail; - - if (!calendar_spec_valid(c)) { - r = -EINVAL; - goto fail; - } - - *spec = c; - return 0; - -fail: - calendar_spec_free(c); - return r; -} - -static int find_matching_component(const CalendarComponent *c, int *val) { - const CalendarComponent *n; - int d = -1; - bool d_set = false; - int r; - - assert(val); - - if (!c) - return 0; - - while (c) { - n = c->next; - - if (c->value >= *val) { - - if (!d_set || c->value < d) { - d = c->value; - d_set = true; - } - - } else if (c->repeat > 0) { - int k; - - k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat); - - if (!d_set || k < d) { - d = k; - d_set = true; - } - } - - c = n; - } - - if (!d_set) - return -ENOENT; - - r = *val != d; - *val = d; - return r; -} - -static bool tm_out_of_bounds(const struct tm *tm, bool utc) { - struct tm t; - assert(tm); - - t = *tm; - - if (mktime_or_timegm(&t, utc) == (time_t) -1) - return true; - - /* Did any normalization take place? If so, it was out of bounds before */ - return - t.tm_year != tm->tm_year || - t.tm_mon != tm->tm_mon || - t.tm_mday != tm->tm_mday || - t.tm_hour != tm->tm_hour || - t.tm_min != tm->tm_min || - t.tm_sec != tm->tm_sec; -} - -static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) { - struct tm t; - int k; - - if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS) - return true; - - t = *tm; - if (mktime_or_timegm(&t, utc) == (time_t) -1) - return false; - - k = t.tm_wday == 0 ? 6 : t.tm_wday - 1; - return (weekdays_bits & (1 << k)); -} - -static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { - struct tm c; - int tm_usec; - int r; - - assert(spec); - assert(tm); - - c = *tm; - tm_usec = *usec; - - for (;;) { - /* Normalize the current date */ - mktime_or_timegm(&c, spec->utc); - c.tm_isdst = -1; - - c.tm_year += 1900; - r = find_matching_component(spec->year, &c.tm_year); - c.tm_year -= 1900; - - if (r > 0) { - c.tm_mon = 0; - c.tm_mday = 1; - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - } - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) - return r; - - c.tm_mon += 1; - r = find_matching_component(spec->month, &c.tm_mon); - c.tm_mon -= 1; - - if (r > 0) { - c.tm_mday = 1; - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - } - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { - c.tm_year ++; - c.tm_mon = 0; - c.tm_mday = 1; - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - continue; - } - - r = find_matching_component(spec->day, &c.tm_mday); - if (r > 0) - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { - c.tm_mon ++; - c.tm_mday = 1; - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - continue; - } - - if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) { - c.tm_mday++; - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - continue; - } - - r = find_matching_component(spec->hour, &c.tm_hour); - if (r > 0) - c.tm_min = c.tm_sec = 0; - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { - c.tm_mday ++; - c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; - continue; - } - - r = find_matching_component(spec->minute, &c.tm_min); - if (r > 0) - c.tm_sec = 0; - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { - c.tm_hour ++; - c.tm_min = c.tm_sec = tm_usec = 0; - continue; - } - - c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec; - r = find_matching_component(spec->microsecond, &c.tm_sec); - tm_usec = c.tm_sec % USEC_PER_SEC; - c.tm_sec /= USEC_PER_SEC; - - if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { - c.tm_min ++; - c.tm_sec = tm_usec = 0; - continue; - } - - *tm = c; - *usec = tm_usec; - return 0; - } -} - -int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) { - struct tm tm; - time_t t; - int r; - usec_t tm_usec; - - assert(spec); - assert(next); - - usec++; - t = (time_t) (usec / USEC_PER_SEC); - assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc)); - tm_usec = usec % USEC_PER_SEC; - - r = find_next(spec, &tm, &tm_usec); - if (r < 0) - return r; - - t = mktime_or_timegm(&tm, spec->utc); - if (t == (time_t) -1) - return -EINVAL; - - *next = (usec_t) t * USEC_PER_SEC + tm_usec; - return 0; -} diff --git a/src/basic/calendarspec.h b/src/basic/calendarspec.h deleted file mode 100644 index f6472c1244..0000000000 --- a/src/basic/calendarspec.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -/* A structure for specifying (possibly repetitive) points in calendar - * time, a la cron */ - -#include - -#include "time-util.h" -#include "util.h" - -typedef struct CalendarComponent { - int value; - int repeat; - - struct CalendarComponent *next; -} CalendarComponent; - -typedef struct CalendarSpec { - int weekdays_bits; - bool utc; - - CalendarComponent *year; - CalendarComponent *month; - CalendarComponent *day; - - CalendarComponent *hour; - CalendarComponent *minute; - CalendarComponent *microsecond; -} CalendarSpec; - -void calendar_spec_free(CalendarSpec *c); - -int calendar_spec_normalize(CalendarSpec *spec); -bool calendar_spec_valid(CalendarSpec *spec); - -int calendar_spec_to_string(const CalendarSpec *spec, char **p); -int calendar_spec_from_string(const char *p, CalendarSpec **spec); - -int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next); diff --git a/src/basic/cap-list.c b/src/basic/cap-list.c deleted file mode 100644 index 3e773a06f5..0000000000 --- a/src/basic/cap-list.c +++ /dev/null @@ -1,66 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include - -#include "cap-list.h" -#include "macro.h" -#include "missing.h" -#include "parse-util.h" -#include "util.h" - -static const struct capability_name* lookup_capability(register const char *str, register unsigned int len); - -#include "cap-from-name.h" -#include "cap-to-name.h" - -const char *capability_to_name(int id) { - - if (id < 0) - return NULL; - - if (id >= (int) ELEMENTSOF(capability_names)) - return NULL; - - return capability_names[id]; -} - -int capability_from_name(const char *name) { - const struct capability_name *sc; - int r, i; - - assert(name); - - /* Try to parse numeric capability */ - r = safe_atoi(name, &i); - if (r >= 0 && i >= 0) - return i; - - /* Try to parse string capability */ - sc = lookup_capability(name, strlen(name)); - if (!sc) - return -EINVAL; - - return sc->id; -} - -int capability_list_length(void) { - return (int) ELEMENTSOF(capability_names); -} diff --git a/src/basic/cap-list.h b/src/basic/cap-list.h deleted file mode 100644 index c1f6b94ad3..0000000000 --- a/src/basic/cap-list.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -const char *capability_to_name(int id); -int capability_from_name(const char *name); -int capability_list_length(void); diff --git a/src/basic/capability-util.c b/src/basic/capability-util.c deleted file mode 100644 index d4c5bd6937..0000000000 --- a/src/basic/capability-util.c +++ /dev/null @@ -1,361 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "capability-util.h" -#include "fileio.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "util.h" - -int have_effective_cap(int value) { - _cleanup_cap_free_ cap_t cap; - cap_flag_value_t fv; - - cap = cap_get_proc(); - if (!cap) - return -errno; - - if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0) - return -errno; - else - return fv == CAP_SET; -} - -unsigned long cap_last_cap(void) { - static thread_local unsigned long saved; - static thread_local bool valid = false; - _cleanup_free_ char *content = NULL; - unsigned long p = 0; - int r; - - if (valid) - return saved; - - /* available since linux-3.2 */ - r = read_one_line_file("/proc/sys/kernel/cap_last_cap", &content); - if (r >= 0) { - r = safe_atolu(content, &p); - if (r >= 0) { - saved = p; - valid = true; - return p; - } - } - - /* fall back to syscall-probing for pre linux-3.2 */ - p = (unsigned long) CAP_LAST_CAP; - - if (prctl(PR_CAPBSET_READ, p) < 0) { - - /* Hmm, look downwards, until we find one that - * works */ - for (p--; p > 0; p --) - if (prctl(PR_CAPBSET_READ, p) >= 0) - break; - - } else { - - /* Hmm, look upwards, until we find one that doesn't - * work */ - for (;; p++) - if (prctl(PR_CAPBSET_READ, p+1) < 0) - break; - } - - saved = p; - valid = true; - - return p; -} - -int capability_update_inherited_set(cap_t caps, uint64_t set) { - unsigned long i; - - /* Add capabilities in the set to the inherited caps. Do not apply - * them yet. */ - - for (i = 0; i < cap_last_cap(); i++) { - - if (set & (UINT64_C(1) << i)) { - cap_value_t v; - - v = (cap_value_t) i; - - /* Make the capability inheritable. */ - if (cap_set_flag(caps, CAP_INHERITABLE, 1, &v, CAP_SET) < 0) - return -errno; - } - } - - return 0; -} - -int capability_ambient_set_apply(uint64_t set, bool also_inherit) { - unsigned long i; - _cleanup_cap_free_ cap_t caps = NULL; - - /* Add the capabilities to the ambient set. */ - - if (also_inherit) { - int r; - caps = cap_get_proc(); - if (!caps) - return -errno; - - r = capability_update_inherited_set(caps, set); - if (r < 0) - return -errno; - - if (cap_set_proc(caps) < 0) - return -errno; - } - - for (i = 0; i < cap_last_cap(); i++) { - - if (set & (UINT64_C(1) << i)) { - - /* Add the capability to the ambient set. */ - if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0) < 0) - return -errno; - } - } - - return 0; -} - -int capability_bounding_set_drop(uint64_t keep, bool right_now) { - _cleanup_cap_free_ cap_t after_cap = NULL; - cap_flag_value_t fv; - unsigned long i; - int r; - - /* If we are run as PID 1 we will lack CAP_SETPCAP by default - * in the effective set (yes, the kernel drops that when - * executing init!), so get it back temporarily so that we can - * call PR_CAPBSET_DROP. */ - - after_cap = cap_get_proc(); - if (!after_cap) - return -errno; - - if (cap_get_flag(after_cap, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0) - return -errno; - - if (fv != CAP_SET) { - _cleanup_cap_free_ cap_t temp_cap = NULL; - static const cap_value_t v = CAP_SETPCAP; - - temp_cap = cap_dup(after_cap); - if (!temp_cap) { - r = -errno; - goto finish; - } - - if (cap_set_flag(temp_cap, CAP_EFFECTIVE, 1, &v, CAP_SET) < 0) { - r = -errno; - goto finish; - } - - if (cap_set_proc(temp_cap) < 0) { - r = -errno; - goto finish; - } - } - - for (i = 0; i <= cap_last_cap(); i++) { - - if (!(keep & (UINT64_C(1) << i))) { - cap_value_t v; - - /* Drop it from the bounding set */ - if (prctl(PR_CAPBSET_DROP, i) < 0) { - r = -errno; - goto finish; - } - v = (cap_value_t) i; - - /* Also drop it from the inheritable set, so - * that anything we exec() loses the - * capability for good. */ - if (cap_set_flag(after_cap, CAP_INHERITABLE, 1, &v, CAP_CLEAR) < 0) { - r = -errno; - goto finish; - } - - /* If we shall apply this right now drop it - * also from our own capability sets. */ - if (right_now) { - if (cap_set_flag(after_cap, CAP_PERMITTED, 1, &v, CAP_CLEAR) < 0 || - cap_set_flag(after_cap, CAP_EFFECTIVE, 1, &v, CAP_CLEAR) < 0) { - r = -errno; - goto finish; - } - } - } - } - - r = 0; - -finish: - if (cap_set_proc(after_cap) < 0) - return -errno; - - return r; -} - -static int drop_from_file(const char *fn, uint64_t keep) { - int r, k; - uint32_t hi, lo; - uint64_t current, after; - char *p; - - r = read_one_line_file(fn, &p); - if (r < 0) - return r; - - assert_cc(sizeof(hi) == sizeof(unsigned)); - assert_cc(sizeof(lo) == sizeof(unsigned)); - - k = sscanf(p, "%u %u", &lo, &hi); - free(p); - - if (k != 2) - return -EIO; - - current = (uint64_t) lo | ((uint64_t) hi << 32ULL); - after = current & keep; - - if (current == after) - return 0; - - lo = (unsigned) (after & 0xFFFFFFFFULL); - hi = (unsigned) ((after >> 32ULL) & 0xFFFFFFFFULL); - - if (asprintf(&p, "%u %u", lo, hi) < 0) - return -ENOMEM; - - r = write_string_file(fn, p, WRITE_STRING_FILE_CREATE); - free(p); - - return r; -} - -int capability_bounding_set_drop_usermode(uint64_t keep) { - int r; - - r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", keep); - if (r < 0) - return r; - - r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", keep); - if (r < 0) - return r; - - return r; -} - -int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) { - _cleanup_cap_free_ cap_t d = NULL; - unsigned i, j = 0; - int r; - - /* Unfortunately we cannot leave privilege dropping to PID 1 - * here, since we want to run as user but want to keep some - * capabilities. Since file capabilities have been introduced - * this cannot be done across exec() anymore, unless our - * binary has the capability configured in the file system, - * which we want to avoid. */ - - if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "Failed to change group ID: %m"); - - if (setgroups(0, NULL) < 0) - return log_error_errno(errno, "Failed to drop auxiliary groups list: %m"); - - /* Ensure we keep the permitted caps across the setresuid() */ - if (prctl(PR_SET_KEEPCAPS, 1) < 0) - return log_error_errno(errno, "Failed to enable keep capabilities flag: %m"); - - r = setresuid(uid, uid, uid); - if (r < 0) - return log_error_errno(errno, "Failed to change user ID: %m"); - - if (prctl(PR_SET_KEEPCAPS, 0) < 0) - return log_error_errno(errno, "Failed to disable keep capabilities flag: %m"); - - /* Drop all caps from the bounding set, except the ones we want */ - r = capability_bounding_set_drop(keep_capabilities, true); - if (r < 0) - return log_error_errno(r, "Failed to drop capabilities: %m"); - - /* Now upgrade the permitted caps we still kept to effective caps */ - d = cap_init(); - if (!d) - return log_oom(); - - if (keep_capabilities) { - cap_value_t bits[u64log2(keep_capabilities) + 1]; - - for (i = 0; i < ELEMENTSOF(bits); i++) - if (keep_capabilities & (1ULL << i)) - bits[j++] = i; - - /* use enough bits */ - assert(i == 64 || (keep_capabilities >> i) == 0); - /* don't use too many bits */ - assert(keep_capabilities & (1ULL << (i - 1))); - - if (cap_set_flag(d, CAP_EFFECTIVE, j, bits, CAP_SET) < 0 || - cap_set_flag(d, CAP_PERMITTED, j, bits, CAP_SET) < 0) - return log_error_errno(errno, "Failed to enable capabilities bits: %m"); - - if (cap_set_proc(d) < 0) - return log_error_errno(errno, "Failed to increase capabilities: %m"); - } - - return 0; -} - -int drop_capability(cap_value_t cv) { - _cleanup_cap_free_ cap_t tmp_cap = NULL; - - tmp_cap = cap_get_proc(); - if (!tmp_cap) - return -errno; - - if ((cap_set_flag(tmp_cap, CAP_INHERITABLE, 1, &cv, CAP_CLEAR) < 0) || - (cap_set_flag(tmp_cap, CAP_PERMITTED, 1, &cv, CAP_CLEAR) < 0) || - (cap_set_flag(tmp_cap, CAP_EFFECTIVE, 1, &cv, CAP_CLEAR) < 0)) - return -errno; - - if (cap_set_proc(tmp_cap) < 0) - return -errno; - - return 0; -} diff --git a/src/basic/capability-util.h b/src/basic/capability-util.h deleted file mode 100644 index 35a896e229..0000000000 --- a/src/basic/capability-util.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "macro.h" -#include "util.h" - -#define CAP_ALL (uint64_t) -1 - -unsigned long cap_last_cap(void); -int have_effective_cap(int value); -int capability_bounding_set_drop(uint64_t keep, bool right_now); -int capability_bounding_set_drop_usermode(uint64_t keep); - -int capability_ambient_set_apply(uint64_t set, bool also_inherit); -int capability_update_inherited_set(cap_t caps, uint64_t ambient_set); - -int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities); - -int drop_capability(cap_value_t cv); - -DEFINE_TRIVIAL_CLEANUP_FUNC(cap_t, cap_free); -#define _cleanup_cap_free_ _cleanup_(cap_freep) - -static inline void cap_free_charpp(char **p) { - if (*p) - cap_free(*p); -} -#define _cleanup_cap_free_charp_ _cleanup_(cap_free_charpp) - -static inline bool cap_test_all(uint64_t caps) { - uint64_t m; - m = (UINT64_C(1) << (cap_last_cap() + 1)) - 1; - return (caps & m) == m; -} diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c deleted file mode 100644 index 6ef00d51df..0000000000 --- a/src/basic/cgroup-util.c +++ /dev/null @@ -1,2274 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "cgroup-util.h" -#include "def.h" -#include "dirent-util.h" -#include "extract-word.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "log.h" -#include "login-util.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-util.h" -#include "proc-cmdline.h" -#include "process-util.h" -#include "set.h" -#include "special.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" -#include "unit-name.h" -#include "user-util.h" - -int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) { - _cleanup_free_ char *fs = NULL; - FILE *f; - int r; - - assert(_f); - - r = cg_get_path(controller, path, "cgroup.procs", &fs); - if (r < 0) - return r; - - f = fopen(fs, "re"); - if (!f) - return -errno; - - *_f = f; - return 0; -} - -int cg_read_pid(FILE *f, pid_t *_pid) { - unsigned long ul; - - /* Note that the cgroup.procs might contain duplicates! See - * cgroups.txt for details. */ - - assert(f); - assert(_pid); - - errno = 0; - if (fscanf(f, "%lu", &ul) != 1) { - - if (feof(f)) - return 0; - - return errno > 0 ? -errno : -EIO; - } - - if (ul <= 0) - return -EIO; - - *_pid = (pid_t) ul; - return 1; -} - -int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) { - _cleanup_free_ char *fs = NULL; - int r; - DIR *d; - - assert(_d); - - /* This is not recursive! */ - - r = cg_get_path(controller, path, NULL, &fs); - if (r < 0) - return r; - - d = opendir(fs); - if (!d) - return -errno; - - *_d = d; - return 0; -} - -int cg_read_subgroup(DIR *d, char **fn) { - struct dirent *de; - - assert(d); - assert(fn); - - FOREACH_DIRENT_ALL(de, d, return -errno) { - char *b; - - if (de->d_type != DT_DIR) - continue; - - if (streq(de->d_name, ".") || - streq(de->d_name, "..")) - continue; - - b = strdup(de->d_name); - if (!b) - return -ENOMEM; - - *fn = b; - return 1; - } - - return 0; -} - -int cg_rmdir(const char *controller, const char *path) { - _cleanup_free_ char *p = NULL; - int r; - - r = cg_get_path(controller, path, NULL, &p); - if (r < 0) - return r; - - r = rmdir(p); - if (r < 0 && errno != ENOENT) - return -errno; - - return 0; -} - -int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s) { - _cleanup_set_free_ Set *allocated_set = NULL; - bool done = false; - int r, ret = 0; - pid_t my_pid; - - assert(sig >= 0); - - /* 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 */ - - if (!s) { - s = allocated_set = set_new(NULL); - if (!s) - return -ENOMEM; - } - - my_pid = getpid(); - - do { - _cleanup_fclose_ FILE *f = NULL; - pid_t pid = 0; - done = true; - - r = cg_enumerate_processes(controller, path, &f); - if (r < 0) { - if (ret >= 0 && r != -ENOENT) - return r; - - return ret; - } - - while ((r = cg_read_pid(f, &pid)) > 0) { - - if (ignore_self && pid == my_pid) - continue; - - if (set_get(s, PID_TO_PTR(pid)) == PID_TO_PTR(pid)) - continue; - - /* 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) - (void) kill(pid, SIGCONT); - - if (ret == 0) - ret = 1; - } - - done = false; - - r = set_put(s, PID_TO_PTR(pid)); - if (r < 0) { - if (ret >= 0) - return r; - - return ret; - } - } - - if (r < 0) { - if (ret >= 0) - return r; - - return ret; - } - - /* To avoid racing against processes which fork - * quicker than we can kill them we repeat this until - * no new pids need to be killed. */ - - } while (!done); - - return ret; -} - -int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool rem, Set *s) { - _cleanup_set_free_ Set *allocated_set = NULL; - _cleanup_closedir_ DIR *d = NULL; - int r, ret; - char *fn; - - assert(path); - assert(sig >= 0); - - if (!s) { - s = allocated_set = set_new(NULL); - if (!s) - return -ENOMEM; - } - - ret = cg_kill(controller, path, sig, sigcont, ignore_self, s); - - r = cg_enumerate_subgroups(controller, path, &d); - if (r < 0) { - if (ret >= 0 && r != -ENOENT) - return r; - - return ret; - } - - while ((r = cg_read_subgroup(d, &fn)) > 0) { - _cleanup_free_ char *p = NULL; - - p = strjoin(path, "/", fn, NULL); - free(fn); - if (!p) - return -ENOMEM; - - r = cg_kill_recursive(controller, p, sig, sigcont, ignore_self, rem, s); - if (r != 0 && ret >= 0) - ret = r; - } - - if (ret >= 0 && r < 0) - ret = r; - - if (rem) { - r = cg_rmdir(controller, path); - if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) - return r; - } - - return ret; -} - -int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self) { - bool done = false; - _cleanup_set_free_ Set *s = NULL; - int r, ret = 0; - pid_t my_pid; - - assert(cfrom); - assert(pfrom); - assert(cto); - assert(pto); - - s = set_new(NULL); - if (!s) - return -ENOMEM; - - my_pid = getpid(); - - do { - _cleanup_fclose_ FILE *f = NULL; - pid_t pid = 0; - done = true; - - r = cg_enumerate_processes(cfrom, pfrom, &f); - if (r < 0) { - if (ret >= 0 && r != -ENOENT) - return r; - - return ret; - } - - while ((r = cg_read_pid(f, &pid)) > 0) { - - /* 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) - continue; - - if (set_get(s, PID_TO_PTR(pid)) == PID_TO_PTR(pid)) - continue; - - /* Ignore kernel threads. Since they can only - * exist in the root cgroup, we only check for - * them there. */ - if (cfrom && - (isempty(pfrom) || path_equal(pfrom, "/")) && - is_kernel_thread(pid) > 0) - continue; - - r = cg_attach(cto, pto, pid); - if (r < 0) { - if (ret >= 0 && r != -ESRCH) - ret = r; - } else if (ret == 0) - ret = 1; - - done = false; - - r = set_put(s, PID_TO_PTR(pid)); - if (r < 0) { - if (ret >= 0) - return r; - - return ret; - } - } - - if (r < 0) { - if (ret >= 0) - return r; - - return ret; - } - } while (!done); - - return ret; -} - -int cg_migrate_recursive( - const char *cfrom, - const char *pfrom, - const char *cto, - const char *pto, - bool ignore_self, - bool rem) { - - _cleanup_closedir_ DIR *d = NULL; - int r, ret = 0; - char *fn; - - assert(cfrom); - assert(pfrom); - assert(cto); - assert(pto); - - ret = cg_migrate(cfrom, pfrom, cto, pto, ignore_self); - - r = cg_enumerate_subgroups(cfrom, pfrom, &d); - if (r < 0) { - if (ret >= 0 && r != -ENOENT) - return r; - - return ret; - } - - while ((r = cg_read_subgroup(d, &fn)) > 0) { - _cleanup_free_ char *p = NULL; - - p = strjoin(pfrom, "/", fn, NULL); - free(fn); - if (!p) - return -ENOMEM; - - r = cg_migrate_recursive(cfrom, p, cto, pto, ignore_self, rem); - if (r != 0 && ret >= 0) - ret = r; - } - - if (r < 0 && ret >= 0) - ret = r; - - if (rem) { - r = cg_rmdir(cfrom, pfrom); - if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) - return r; - } - - return ret; -} - -int cg_migrate_recursive_fallback( - const char *cfrom, - const char *pfrom, - const char *cto, - const char *pto, - bool ignore_self, - bool rem) { - - int r; - - assert(cfrom); - assert(pfrom); - assert(cto); - assert(pto); - - r = cg_migrate_recursive(cfrom, pfrom, cto, pto, ignore_self, rem); - if (r < 0) { - char prefix[strlen(pto) + 1]; - - /* This didn't work? Then let's try all prefixes of the destination */ - - PATH_FOREACH_PREFIX(prefix, pto) { - int q; - - q = cg_migrate_recursive(cfrom, pfrom, cto, prefix, ignore_self, rem); - if (q >= 0) - return q; - } - } - - return r; -} - -static const char *controller_to_dirname(const char *controller) { - const char *e; - - assert(controller); - - /* Converts a controller name to the directory name below - * /sys/fs/cgroup/ we want to mount it to. Effectively, this - * just cuts off the name= prefixed used for named - * hierarchies, if it is specified. */ - - e = startswith(controller, "name="); - if (e) - return e; - - return controller; -} - -static int join_path_legacy(const char *controller, const char *path, const char *suffix, char **fs) { - const char *dn; - char *t = NULL; - - assert(fs); - assert(controller); - - dn = controller_to_dirname(controller); - - if (isempty(path) && isempty(suffix)) - t = strappend("/sys/fs/cgroup/", dn); - else if (isempty(path)) - t = strjoin("/sys/fs/cgroup/", dn, "/", suffix, NULL); - else if (isempty(suffix)) - t = strjoin("/sys/fs/cgroup/", dn, "/", path, NULL); - else - t = strjoin("/sys/fs/cgroup/", dn, "/", path, "/", suffix, NULL); - if (!t) - return -ENOMEM; - - *fs = t; - return 0; -} - -static int join_path_unified(const char *path, const char *suffix, char **fs) { - char *t; - - assert(fs); - - if (isempty(path) && isempty(suffix)) - t = strdup("/sys/fs/cgroup"); - else if (isempty(path)) - t = strappend("/sys/fs/cgroup/", suffix); - else if (isempty(suffix)) - t = strappend("/sys/fs/cgroup/", path); - else - t = strjoin("/sys/fs/cgroup/", path, "/", suffix, NULL); - if (!t) - return -ENOMEM; - - *fs = t; - return 0; -} - -int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) { - int unified, r; - - assert(fs); - - if (!controller) { - char *t; - - /* If no controller is specified, we return the path - * *below* the controllers, without any prefix. */ - - if (!path && !suffix) - return -EINVAL; - - if (!suffix) - t = strdup(path); - else if (!path) - t = strdup(suffix); - else - t = strjoin(path, "/", suffix, NULL); - if (!t) - return -ENOMEM; - - *fs = path_kill_slashes(t); - return 0; - } - - if (!cg_controller_is_valid(controller)) - return -EINVAL; - - unified = cg_unified(); - if (unified < 0) - return unified; - - if (unified > 0) - r = join_path_unified(path, suffix, fs); - else - r = join_path_legacy(controller, path, suffix, fs); - if (r < 0) - return r; - - path_kill_slashes(*fs); - return 0; -} - -static int controller_is_accessible(const char *controller) { - int unified; - - assert(controller); - - /* Checks whether a specific controller is accessible, - * i.e. its hierarchy mounted. In the unified hierarchy all - * controllers are considered accessible, except for the named - * hierarchies */ - - if (!cg_controller_is_valid(controller)) - return -EINVAL; - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified > 0) { - /* We don't support named hierarchies if we are using - * the unified hierarchy. */ - - if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) - return 0; - - if (startswith(controller, "name=")) - return -EOPNOTSUPP; - - } else { - const char *cc, *dn; - - dn = controller_to_dirname(controller); - cc = strjoina("/sys/fs/cgroup/", dn); - - if (laccess(cc, F_OK) < 0) - return -errno; - } - - return 0; -} - -int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs) { - int r; - - assert(controller); - assert(fs); - - /* Check if the specified controller is actually accessible */ - r = controller_is_accessible(controller); - if (r < 0) - return r; - - return cg_get_path(controller, path, suffix, fs); -} - -static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { - assert(path); - assert(sb); - assert(ftwbuf); - - if (typeflag != FTW_DP) - return 0; - - if (ftwbuf->level < 1) - return 0; - - (void) rmdir(path); - return 0; -} - -int cg_trim(const char *controller, const char *path, bool delete_root) { - _cleanup_free_ char *fs = NULL; - int r = 0; - - assert(path); - - r = cg_get_path(controller, path, NULL, &fs); - if (r < 0) - return r; - - errno = 0; - if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) != 0) { - if (errno == ENOENT) - r = 0; - else if (errno > 0) - r = -errno; - else - r = -EIO; - } - - if (delete_root) { - if (rmdir(fs) < 0 && errno != ENOENT) - return -errno; - } - - return r; -} - -int cg_create(const char *controller, const char *path) { - _cleanup_free_ char *fs = NULL; - int r; - - r = cg_get_path_and_check(controller, path, NULL, &fs); - if (r < 0) - return r; - - r = mkdir_parents(fs, 0755); - if (r < 0) - return r; - - if (mkdir(fs, 0755) < 0) { - - if (errno == EEXIST) - return 0; - - return -errno; - } - - return 1; -} - -int cg_create_and_attach(const char *controller, const char *path, pid_t pid) { - int r, q; - - assert(pid >= 0); - - r = cg_create(controller, path); - if (r < 0) - return r; - - q = cg_attach(controller, path, pid); - if (q < 0) - return q; - - /* This does not remove the cgroup on failure */ - return r; -} - -int cg_attach(const char *controller, const char *path, pid_t pid) { - _cleanup_free_ char *fs = NULL; - char c[DECIMAL_STR_MAX(pid_t) + 2]; - int r; - - assert(path); - assert(pid >= 0); - - r = cg_get_path_and_check(controller, path, "cgroup.procs", &fs); - if (r < 0) - return r; - - if (pid == 0) - pid = getpid(); - - xsprintf(c, PID_FMT "\n", pid); - - return write_string_file(fs, c, 0); -} - -int cg_attach_fallback(const char *controller, const char *path, pid_t pid) { - int r; - - assert(controller); - assert(path); - assert(pid >= 0); - - r = cg_attach(controller, path, pid); - if (r < 0) { - char prefix[strlen(path) + 1]; - - /* This didn't work? Then let's try all prefixes of - * the destination */ - - PATH_FOREACH_PREFIX(prefix, path) { - int q; - - q = cg_attach(controller, prefix, pid); - if (q >= 0) - return q; - } - } - - return r; -} - -int cg_set_group_access( - const char *controller, - const char *path, - mode_t mode, - uid_t uid, - gid_t gid) { - - _cleanup_free_ char *fs = NULL; - int r; - - if (mode == MODE_INVALID && uid == UID_INVALID && gid == GID_INVALID) - return 0; - - if (mode != MODE_INVALID) - mode &= 0777; - - r = cg_get_path(controller, path, NULL, &fs); - if (r < 0) - return r; - - return chmod_and_chown(fs, mode, uid, gid); -} - -int cg_set_task_access( - const char *controller, - const char *path, - mode_t mode, - uid_t uid, - gid_t gid) { - - _cleanup_free_ char *fs = NULL, *procs = NULL; - int r, unified; - - assert(path); - - if (mode == MODE_INVALID && uid == UID_INVALID && gid == GID_INVALID) - return 0; - - if (mode != MODE_INVALID) - mode &= 0666; - - r = cg_get_path(controller, path, "cgroup.procs", &fs); - if (r < 0) - return r; - - r = chmod_and_chown(fs, mode, uid, gid); - if (r < 0) - return r; - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified) - return 0; - - /* Compatibility, Always keep values for "tasks" in sync with - * "cgroup.procs" */ - if (cg_get_path(controller, path, "tasks", &procs) >= 0) - (void) chmod_and_chown(procs, mode, uid, gid); - - return 0; -} - -int cg_pid_get_path(const char *controller, pid_t pid, char **path) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - const char *fs; - size_t cs = 0; - int unified; - - assert(path); - assert(pid >= 0); - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified == 0) { - if (controller) { - if (!cg_controller_is_valid(controller)) - return -EINVAL; - } else - controller = SYSTEMD_CGROUP_CONTROLLER; - - cs = strlen(controller); - } - - fs = procfs_file_alloca(pid, "cgroup"); - f = fopen(fs, "re"); - if (!f) - return errno == ENOENT ? -ESRCH : -errno; - - FOREACH_LINE(line, f, return -errno) { - char *e, *p; - - truncate_nl(line); - - if (unified) { - e = startswith(line, "0:"); - if (!e) - continue; - - e = strchr(e, ':'); - if (!e) - continue; - } else { - char *l; - size_t k; - const char *word, *state; - bool found = false; - - l = strchr(line, ':'); - if (!l) - continue; - - l++; - e = strchr(l, ':'); - if (!e) - continue; - - *e = 0; - FOREACH_WORD_SEPARATOR(word, k, l, ",", state) { - if (k == cs && memcmp(word, controller, cs) == 0) { - found = true; - break; - } - } - - if (!found) - continue; - } - - p = strdup(e + 1); - if (!p) - return -ENOMEM; - - *path = p; - return 0; - } - - return -ENODATA; -} - -int cg_install_release_agent(const char *controller, const char *agent) { - _cleanup_free_ char *fs = NULL, *contents = NULL; - const char *sc; - int r, unified; - - assert(agent); - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified) /* doesn't apply to unified hierarchy */ - return -EOPNOTSUPP; - - r = cg_get_path(controller, NULL, "release_agent", &fs); - if (r < 0) - return r; - - r = read_one_line_file(fs, &contents); - if (r < 0) - return r; - - sc = strstrip(contents); - if (isempty(sc)) { - r = write_string_file(fs, agent, 0); - if (r < 0) - return r; - } else if (!path_equal(sc, agent)) - return -EEXIST; - - fs = mfree(fs); - r = cg_get_path(controller, NULL, "notify_on_release", &fs); - if (r < 0) - return r; - - contents = mfree(contents); - r = read_one_line_file(fs, &contents); - if (r < 0) - return r; - - sc = strstrip(contents); - if (streq(sc, "0")) { - r = write_string_file(fs, "1", 0); - if (r < 0) - return r; - - return 1; - } - - if (!streq(sc, "1")) - return -EIO; - - return 0; -} - -int cg_uninstall_release_agent(const char *controller) { - _cleanup_free_ char *fs = NULL; - int r, unified; - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified) /* Doesn't apply to unified hierarchy */ - return -EOPNOTSUPP; - - r = cg_get_path(controller, NULL, "notify_on_release", &fs); - if (r < 0) - return r; - - r = write_string_file(fs, "0", 0); - if (r < 0) - return r; - - fs = mfree(fs); - - r = cg_get_path(controller, NULL, "release_agent", &fs); - if (r < 0) - return r; - - r = write_string_file(fs, "", 0); - if (r < 0) - return r; - - return 0; -} - -int cg_is_empty(const char *controller, const char *path) { - _cleanup_fclose_ FILE *f = NULL; - pid_t pid; - int r; - - assert(path); - - r = cg_enumerate_processes(controller, path, &f); - if (r == -ENOENT) - return 1; - if (r < 0) - return r; - - r = cg_read_pid(f, &pid); - if (r < 0) - return r; - - return r == 0; -} - -int cg_is_empty_recursive(const char *controller, const char *path) { - int unified, r; - - assert(path); - - /* The root cgroup is always populated */ - if (controller && (isempty(path) || path_equal(path, "/"))) - return false; - - unified = cg_unified(); - if (unified < 0) - return unified; - - if (unified > 0) { - _cleanup_free_ char *populated = NULL, *t = NULL; - - /* On the unified hierarchy we can check empty state - * via the "cgroup.populated" attribute. */ - - r = cg_get_path(controller, path, "cgroup.populated", &populated); - if (r < 0) - return r; - - r = read_one_line_file(populated, &t); - if (r == -ENOENT) - return 1; - if (r < 0) - return r; - - return streq(t, "0"); - } else { - _cleanup_closedir_ DIR *d = NULL; - char *fn; - - r = cg_is_empty(controller, path); - if (r <= 0) - return r; - - r = cg_enumerate_subgroups(controller, path, &d); - if (r == -ENOENT) - return 1; - if (r < 0) - return r; - - while ((r = cg_read_subgroup(d, &fn)) > 0) { - _cleanup_free_ char *p = NULL; - - p = strjoin(path, "/", fn, NULL); - free(fn); - if (!p) - return -ENOMEM; - - r = cg_is_empty_recursive(controller, p); - if (r <= 0) - return r; - } - if (r < 0) - return r; - - return true; - } -} - -int cg_split_spec(const char *spec, char **controller, char **path) { - char *t = NULL, *u = NULL; - const char *e; - - assert(spec); - - if (*spec == '/') { - if (!path_is_safe(spec)) - return -EINVAL; - - if (path) { - t = strdup(spec); - if (!t) - return -ENOMEM; - - *path = path_kill_slashes(t); - } - - if (controller) - *controller = NULL; - - return 0; - } - - e = strchr(spec, ':'); - if (!e) { - if (!cg_controller_is_valid(spec)) - return -EINVAL; - - if (controller) { - t = strdup(spec); - if (!t) - return -ENOMEM; - - *controller = t; - } - - if (path) - *path = NULL; - - return 0; - } - - t = strndup(spec, e-spec); - if (!t) - return -ENOMEM; - if (!cg_controller_is_valid(t)) { - free(t); - return -EINVAL; - } - - if (isempty(e+1)) - u = NULL; - else { - u = strdup(e+1); - if (!u) { - free(t); - return -ENOMEM; - } - - if (!path_is_safe(u) || - !path_is_absolute(u)) { - free(t); - free(u); - return -EINVAL; - } - - path_kill_slashes(u); - } - - if (controller) - *controller = t; - else - free(t); - - if (path) - *path = u; - else - free(u); - - return 0; -} - -int cg_mangle_path(const char *path, char **result) { - _cleanup_free_ char *c = NULL, *p = NULL; - char *t; - int r; - - assert(path); - assert(result); - - /* First, check if it already is a filesystem path */ - if (path_startswith(path, "/sys/fs/cgroup")) { - - t = strdup(path); - if (!t) - return -ENOMEM; - - *result = path_kill_slashes(t); - return 0; - } - - /* Otherwise, treat it as cg spec */ - r = cg_split_spec(path, &c, &p); - if (r < 0) - return r; - - return cg_get_path(c ?: SYSTEMD_CGROUP_CONTROLLER, p ?: "/", NULL, result); -} - -int cg_get_root_path(char **path) { - char *p, *e; - int r; - - assert(path); - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p); - if (r < 0) - return r; - - e = endswith(p, "/" SPECIAL_INIT_SCOPE); - if (!e) - e = endswith(p, "/" SPECIAL_SYSTEM_SLICE); /* legacy */ - if (!e) - e = endswith(p, "/system"); /* even more legacy */ - if (e) - *e = 0; - - *path = p; - return 0; -} - -int cg_shift_path(const char *cgroup, const char *root, const char **shifted) { - _cleanup_free_ char *rt = NULL; - char *p; - int r; - - assert(cgroup); - assert(shifted); - - if (!root) { - /* If the root was specified let's use that, otherwise - * let's determine it from PID 1 */ - - r = cg_get_root_path(&rt); - if (r < 0) - return r; - - root = rt; - } - - p = path_startswith(cgroup, root); - if (p && p > cgroup) - *shifted = p - 1; - else - *shifted = cgroup; - - return 0; -} - -int cg_pid_get_path_shifted(pid_t pid, const char *root, char **cgroup) { - _cleanup_free_ char *raw = NULL; - const char *c; - int r; - - assert(pid >= 0); - assert(cgroup); - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &raw); - if (r < 0) - return r; - - r = cg_shift_path(raw, root, &c); - if (r < 0) - return r; - - if (c == raw) { - *cgroup = raw; - raw = NULL; - } else { - char *n; - - n = strdup(c); - if (!n) - return -ENOMEM; - - *cgroup = n; - } - - return 0; -} - -int cg_path_decode_unit(const char *cgroup, char **unit){ - char *c, *s; - size_t n; - - assert(cgroup); - assert(unit); - - n = strcspn(cgroup, "/"); - if (n < 3) - return -ENXIO; - - c = strndupa(cgroup, n); - c = cg_unescape(c); - - if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) - return -ENXIO; - - s = strdup(c); - if (!s) - return -ENOMEM; - - *unit = s; - return 0; -} - -static bool valid_slice_name(const char *p, size_t n) { - - if (!p) - return false; - - if (n < strlen("x.slice")) - return false; - - if (memcmp(p + n - 6, ".slice", 6) == 0) { - char buf[n+1], *c; - - memcpy(buf, p, n); - buf[n] = 0; - - c = cg_unescape(buf); - - return unit_name_is_valid(c, UNIT_NAME_PLAIN); - } - - return false; -} - -static const char *skip_slices(const char *p) { - assert(p); - - /* Skips over all slice assignments */ - - for (;;) { - size_t n; - - p += strspn(p, "/"); - - n = strcspn(p, "/"); - if (!valid_slice_name(p, n)) - return p; - - p += n; - } -} - -int cg_path_get_unit(const char *path, char **ret) { - const char *e; - char *unit; - int r; - - assert(path); - assert(ret); - - e = skip_slices(path); - - r = cg_path_decode_unit(e, &unit); - if (r < 0) - return r; - - /* We skipped over the slices, don't accept any now */ - if (endswith(unit, ".slice")) { - free(unit); - return -ENXIO; - } - - *ret = unit; - return 0; -} - -int cg_pid_get_unit(pid_t pid, char **unit) { - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(unit); - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_unit(cgroup, unit); -} - -/** - * Skip session-*.scope, but require it to be there. - */ -static const char *skip_session(const char *p) { - size_t n; - - if (isempty(p)) - return NULL; - - p += strspn(p, "/"); - - n = strcspn(p, "/"); - if (n < strlen("session-x.scope")) - return NULL; - - if (memcmp(p, "session-", 8) == 0 && memcmp(p + n - 6, ".scope", 6) == 0) { - char buf[n - 8 - 6 + 1]; - - memcpy(buf, p + 8, n - 8 - 6); - buf[n - 8 - 6] = 0; - - /* Note that session scopes never need unescaping, - * since they cannot conflict with the kernel's own - * names, hence we don't need to call cg_unescape() - * here. */ - - if (!session_id_valid(buf)) - return false; - - p += n; - p += strspn(p, "/"); - return p; - } - - return NULL; -} - -/** - * Skip user@*.service, but require it to be there. - */ -static const char *skip_user_manager(const char *p) { - size_t n; - - if (isempty(p)) - return NULL; - - p += strspn(p, "/"); - - n = strcspn(p, "/"); - if (n < strlen("user@x.service")) - return NULL; - - if (memcmp(p, "user@", 5) == 0 && memcmp(p + n - 8, ".service", 8) == 0) { - char buf[n - 5 - 8 + 1]; - - memcpy(buf, p + 5, n - 5 - 8); - buf[n - 5 - 8] = 0; - - /* Note that user manager services never need unescaping, - * since they cannot conflict with the kernel's own - * names, hence we don't need to call cg_unescape() - * here. */ - - if (parse_uid(buf, NULL) < 0) - return NULL; - - p += n; - p += strspn(p, "/"); - - return p; - } - - return NULL; -} - -static const char *skip_user_prefix(const char *path) { - const char *e, *t; - - assert(path); - - /* Skip slices, if there are any */ - e = skip_slices(path); - - /* Skip the user manager, if it's in the path now... */ - t = skip_user_manager(e); - if (t) - return t; - - /* Alternatively skip the user session if it is in the path... */ - return skip_session(e); -} - -int cg_path_get_user_unit(const char *path, char **ret) { - const char *t; - - assert(path); - assert(ret); - - t = skip_user_prefix(path); - if (!t) - return -ENXIO; - - /* And from here on it looks pretty much the same as for a - * system unit, hence let's use the same parser from here - * on. */ - return cg_path_get_unit(t, ret); -} - -int cg_pid_get_user_unit(pid_t pid, char **unit) { - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(unit); - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_user_unit(cgroup, unit); -} - -int cg_path_get_machine_name(const char *path, char **machine) { - _cleanup_free_ char *u = NULL; - const char *sl; - int r; - - r = cg_path_get_unit(path, &u); - if (r < 0) - return r; - - sl = strjoina("/run/systemd/machines/unit:", u); - return readlink_malloc(sl, machine); -} - -int cg_pid_get_machine_name(pid_t pid, char **machine) { - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(machine); - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_machine_name(cgroup, machine); -} - -int cg_path_get_session(const char *path, char **session) { - _cleanup_free_ char *unit = NULL; - char *start, *end; - int r; - - assert(path); - - r = cg_path_get_unit(path, &unit); - if (r < 0) - return r; - - start = startswith(unit, "session-"); - if (!start) - return -ENXIO; - end = endswith(start, ".scope"); - if (!end) - return -ENXIO; - - *end = 0; - if (!session_id_valid(start)) - return -ENXIO; - - if (session) { - char *rr; - - rr = strdup(start); - if (!rr) - return -ENOMEM; - - *session = rr; - } - - return 0; -} - -int cg_pid_get_session(pid_t pid, char **session) { - _cleanup_free_ char *cgroup = NULL; - int r; - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_session(cgroup, session); -} - -int cg_path_get_owner_uid(const char *path, uid_t *uid) { - _cleanup_free_ char *slice = NULL; - char *start, *end; - int r; - - assert(path); - - r = cg_path_get_slice(path, &slice); - if (r < 0) - return r; - - start = startswith(slice, "user-"); - if (!start) - return -ENXIO; - end = endswith(start, ".slice"); - if (!end) - return -ENXIO; - - *end = 0; - if (parse_uid(start, uid) < 0) - return -ENXIO; - - return 0; -} - -int cg_pid_get_owner_uid(pid_t pid, uid_t *uid) { - _cleanup_free_ char *cgroup = NULL; - int r; - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_owner_uid(cgroup, uid); -} - -int cg_path_get_slice(const char *p, char **slice) { - const char *e = NULL; - - assert(p); - assert(slice); - - /* Finds the right-most slice unit from the beginning, but - * stops before we come to the first non-slice unit. */ - - for (;;) { - size_t n; - - p += strspn(p, "/"); - - n = strcspn(p, "/"); - if (!valid_slice_name(p, n)) { - - if (!e) { - char *s; - - s = strdup("-.slice"); - if (!s) - return -ENOMEM; - - *slice = s; - return 0; - } - - return cg_path_decode_unit(e, slice); - } - - e = p; - p += n; - } -} - -int cg_pid_get_slice(pid_t pid, char **slice) { - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(slice); - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_slice(cgroup, slice); -} - -int cg_path_get_user_slice(const char *p, char **slice) { - const char *t; - assert(p); - assert(slice); - - t = skip_user_prefix(p); - if (!t) - return -ENXIO; - - /* And now it looks pretty much the same as for a system - * slice, so let's just use the same parser from here on. */ - return cg_path_get_slice(t, slice); -} - -int cg_pid_get_user_slice(pid_t pid, char **slice) { - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(slice); - - r = cg_pid_get_path_shifted(pid, NULL, &cgroup); - if (r < 0) - return r; - - return cg_path_get_user_slice(cgroup, slice); -} - -char *cg_escape(const char *p) { - bool need_prefix = false; - - /* This implements very minimal escaping for names to be used - * as file names in the cgroup tree: any name which might - * conflict with a kernel name or is prefixed with '_' is - * prefixed with a '_'. That way, when reading cgroup names it - * is sufficient to remove a single prefixing underscore if - * there is one. */ - - /* The return value of this function (unlike cg_unescape()) - * needs free()! */ - - if (p[0] == 0 || - p[0] == '_' || - p[0] == '.' || - streq(p, "notify_on_release") || - streq(p, "release_agent") || - streq(p, "tasks") || - startswith(p, "cgroup.")) - need_prefix = true; - else { - const char *dot; - - dot = strrchr(p, '.'); - if (dot) { - CGroupController c; - size_t l = dot - p; - - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - const char *n; - - n = cgroup_controller_to_string(c); - - if (l != strlen(n)) - continue; - - if (memcmp(p, n, l) != 0) - continue; - - need_prefix = true; - break; - } - } - } - - if (need_prefix) - return strappend("_", p); - - return strdup(p); -} - -char *cg_unescape(const char *p) { - assert(p); - - /* The return value of this function (unlike cg_escape()) - * doesn't need free()! */ - - if (p[0] == '_') - return (char*) p+1; - - return (char*) p; -} - -#define CONTROLLER_VALID \ - DIGITS LETTERS \ - "_" - -bool cg_controller_is_valid(const char *p) { - const char *t, *s; - - if (!p) - return false; - - s = startswith(p, "name="); - if (s) - p = s; - - if (*p == 0 || *p == '_') - return false; - - for (t = p; *t; t++) - if (!strchr(CONTROLLER_VALID, *t)) - return false; - - if (t - p > FILENAME_MAX) - return false; - - return true; -} - -int cg_slice_to_path(const char *unit, char **ret) { - _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL; - const char *dash; - int r; - - assert(unit); - assert(ret); - - if (streq(unit, "-.slice")) { - char *x; - - x = strdup(""); - if (!x) - return -ENOMEM; - *ret = x; - return 0; - } - - if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN)) - return -EINVAL; - - if (!endswith(unit, ".slice")) - return -EINVAL; - - r = unit_name_to_prefix(unit, &p); - if (r < 0) - return r; - - dash = strchr(p, '-'); - - /* Don't allow initial dashes */ - if (dash == p) - return -EINVAL; - - while (dash) { - _cleanup_free_ char *escaped = NULL; - char n[dash - p + sizeof(".slice")]; - - /* Don't allow trailing or double dashes */ - if (dash[1] == 0 || dash[1] == '-') - return -EINVAL; - - strcpy(stpncpy(n, p, dash - p), ".slice"); - if (!unit_name_is_valid(n, UNIT_NAME_PLAIN)) - return -EINVAL; - - escaped = cg_escape(n); - if (!escaped) - return -ENOMEM; - - if (!strextend(&s, escaped, "/", NULL)) - return -ENOMEM; - - dash = strchr(dash+1, '-'); - } - - e = cg_escape(unit); - if (!e) - return -ENOMEM; - - if (!strextend(&s, e, NULL)) - return -ENOMEM; - - *ret = s; - s = NULL; - - return 0; -} - -int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value) { - _cleanup_free_ char *p = NULL; - int r; - - r = cg_get_path(controller, path, attribute, &p); - if (r < 0) - return r; - - return write_string_file(p, value, 0); -} - -int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret) { - _cleanup_free_ char *p = NULL; - int r; - - r = cg_get_path(controller, path, attribute, &p); - if (r < 0) - return r; - - return read_one_line_file(p, ret); -} - -int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path) { - CGroupController c; - int r, unified; - - /* This one will create a cgroup in our private tree, but also - * duplicate it in the trees specified in mask, and remove it - * in all others */ - - /* First create the cgroup in our own hierarchy. */ - r = cg_create(SYSTEMD_CGROUP_CONTROLLER, path); - if (r < 0) - return r; - - /* If we are in the unified hierarchy, we are done now */ - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified > 0) - return 0; - - /* Otherwise, do the same in the other hierarchies */ - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); - const char *n; - - n = cgroup_controller_to_string(c); - - if (mask & bit) - (void) cg_create(n, path); - else if (supported & bit) - (void) cg_trim(n, path, true); - } - - return 0; -} - -int cg_attach_everywhere(CGroupMask supported, const char *path, pid_t pid, cg_migrate_callback_t path_callback, void *userdata) { - CGroupController c; - int r, unified; - - r = cg_attach(SYSTEMD_CGROUP_CONTROLLER, path, pid); - if (r < 0) - return r; - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified > 0) - return 0; - - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); - const char *p = NULL; - - if (!(supported & bit)) - continue; - - if (path_callback) - p = path_callback(bit, userdata); - - if (!p) - p = path; - - (void) cg_attach_fallback(cgroup_controller_to_string(c), p, pid); - } - - return 0; -} - -int cg_attach_many_everywhere(CGroupMask supported, const char *path, Set* pids, cg_migrate_callback_t path_callback, void *userdata) { - Iterator i; - void *pidp; - int r = 0; - - SET_FOREACH(pidp, pids, i) { - pid_t pid = PTR_TO_PID(pidp); - int q; - - q = cg_attach_everywhere(supported, path, pid, path_callback, userdata); - if (q < 0 && r >= 0) - r = q; - } - - return r; -} - -int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to, cg_migrate_callback_t to_callback, void *userdata) { - CGroupController c; - int r = 0, unified; - - if (!path_equal(from, to)) { - r = cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, from, SYSTEMD_CGROUP_CONTROLLER, to, false, true); - if (r < 0) - return r; - } - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified > 0) - return r; - - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); - const char *p = NULL; - - if (!(supported & bit)) - continue; - - if (to_callback) - p = to_callback(bit, userdata); - - if (!p) - p = to; - - (void) cg_migrate_recursive_fallback(SYSTEMD_CGROUP_CONTROLLER, to, cgroup_controller_to_string(c), p, false, false); - } - - return 0; -} - -int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root) { - CGroupController c; - int r, unified; - - r = cg_trim(SYSTEMD_CGROUP_CONTROLLER, path, delete_root); - if (r < 0) - return r; - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified > 0) - return r; - - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); - - if (!(supported & bit)) - continue; - - (void) cg_trim(cgroup_controller_to_string(c), path, delete_root); - } - - return 0; -} - -int cg_mask_supported(CGroupMask *ret) { - CGroupMask mask = 0; - int r, unified; - - /* Determines the mask of supported cgroup controllers. Only - * includes controllers we can make sense of and that are - * actually accessible. */ - - unified = cg_unified(); - if (unified < 0) - return unified; - if (unified > 0) { - _cleanup_free_ char *root = NULL, *controllers = NULL, *path = NULL; - const char *c; - - /* In the unified hierarchy we can read the supported - * and accessible controllers from a the top-level - * cgroup attribute */ - - r = cg_get_root_path(&root); - if (r < 0) - return r; - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, root, "cgroup.controllers", &path); - if (r < 0) - return r; - - r = read_one_line_file(path, &controllers); - if (r < 0) - return r; - - c = controllers; - for (;;) { - _cleanup_free_ char *n = NULL; - CGroupController v; - - r = extract_first_word(&c, &n, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - v = cgroup_controller_from_string(n); - if (v < 0) - continue; - - mask |= CGROUP_CONTROLLER_TO_MASK(v); - } - - /* Currently, we only support the memory and pids - * controller in the unified hierarchy, mask - * everything else off. */ - mask &= CGROUP_MASK_MEMORY | CGROUP_MASK_PIDS; - - } else { - CGroupController c; - - /* In the legacy hierarchy, we check whether which - * hierarchies are mounted. */ - - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - const char *n; - - n = cgroup_controller_to_string(c); - if (controller_is_accessible(n) >= 0) - mask |= CGROUP_CONTROLLER_TO_MASK(c); - } - } - - *ret = mask; - return 0; -} - -int cg_kernel_controllers(Set *controllers) { - _cleanup_fclose_ FILE *f = NULL; - char buf[LINE_MAX]; - int r; - - assert(controllers); - - /* Determines the full list of kernel-known controllers. Might - * include controllers we don't actually support, arbitrary - * named hierarchies and controllers that aren't currently - * accessible (because not mounted). */ - - f = fopen("/proc/cgroups", "re"); - if (!f) { - if (errno == ENOENT) - return 0; - return -errno; - } - - /* Ignore the header line */ - (void) fgets(buf, sizeof(buf), f); - - for (;;) { - char *controller; - int enabled = 0; - - errno = 0; - if (fscanf(f, "%ms %*i %*i %i", &controller, &enabled) != 2) { - - if (feof(f)) - break; - - if (ferror(f) && errno > 0) - return -errno; - - return -EBADMSG; - } - - if (!enabled) { - free(controller); - continue; - } - - if (!cg_controller_is_valid(controller)) { - free(controller); - return -EBADMSG; - } - - r = set_consume(controllers, controller); - if (r < 0) - return r; - } - - return 0; -} - -static thread_local int unified_cache = -1; - -int cg_unified(void) { - struct statfs fs; - - /* Checks if we support the unified hierarchy. Returns an - * error when the cgroup hierarchies aren't mounted yet or we - * have any other trouble determining if the unified hierarchy - * is supported. */ - - if (unified_cache >= 0) - return unified_cache; - - if (statfs("/sys/fs/cgroup/", &fs) < 0) - return -errno; - - if (F_TYPE_EQUAL(fs.f_type, CGROUP_SUPER_MAGIC)) - unified_cache = true; - else if (F_TYPE_EQUAL(fs.f_type, TMPFS_MAGIC)) - unified_cache = false; - else - return -ENOMEDIUM; - - return unified_cache; -} - -void cg_unified_flush(void) { - unified_cache = -1; -} - -int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p) { - _cleanup_free_ char *fs = NULL; - CGroupController c; - int r, unified; - - assert(p); - - if (supported == 0) - return 0; - - unified = cg_unified(); - if (unified < 0) - return unified; - if (!unified) /* on the legacy hiearchy there's no joining of controllers defined */ - return 0; - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, p, "cgroup.subtree_control", &fs); - if (r < 0) - return r; - - for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { - CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); - const char *n; - - if (!(supported & bit)) - continue; - - n = cgroup_controller_to_string(c); - { - char s[1 + strlen(n) + 1]; - - s[0] = mask & bit ? '+' : '-'; - strcpy(s + 1, n); - - r = write_string_file(fs, s, 0); - if (r < 0) - log_debug_errno(r, "Failed to enable controller %s for %s (%s): %m", n, p, fs); - } - } - - return 0; -} - -bool cg_is_unified_wanted(void) { - static thread_local int wanted = -1; - int r, unified; - - /* If the hierarchy is already mounted, then follow whatever - * was chosen for it. */ - unified = cg_unified(); - if (unified >= 0) - return unified; - - /* Otherwise, let's see what the kernel command line has to - * say. Since checking that is expensive, let's cache the - * result. */ - if (wanted >= 0) - return wanted; - - r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy", NULL); - if (r > 0) - return (wanted = true); - else { - _cleanup_free_ char *value = NULL; - - r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy=", &value); - if (r < 0) - return false; - if (r == 0) - return (wanted = false); - - return (wanted = parse_boolean(value) > 0); - } -} - -bool cg_is_legacy_wanted(void) { - return !cg_is_unified_wanted(); -} - -int cg_cpu_shares_parse(const char *s, uint64_t *ret) { - uint64_t u; - int r; - - if (isempty(s)) { - *ret = CGROUP_CPU_SHARES_INVALID; - return 0; - } - - r = safe_atou64(s, &u); - if (r < 0) - return r; - - if (u < CGROUP_CPU_SHARES_MIN || u > CGROUP_CPU_SHARES_MAX) - return -ERANGE; - - *ret = u; - return 0; -} - -int cg_blkio_weight_parse(const char *s, uint64_t *ret) { - uint64_t u; - int r; - - if (isempty(s)) { - *ret = CGROUP_BLKIO_WEIGHT_INVALID; - return 0; - } - - r = safe_atou64(s, &u); - if (r < 0) - return r; - - if (u < CGROUP_BLKIO_WEIGHT_MIN || u > CGROUP_BLKIO_WEIGHT_MAX) - return -ERANGE; - - *ret = u; - return 0; -} - -static const char *cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = { - [CGROUP_CONTROLLER_CPU] = "cpu", - [CGROUP_CONTROLLER_CPUACCT] = "cpuacct", - [CGROUP_CONTROLLER_BLKIO] = "blkio", - [CGROUP_CONTROLLER_MEMORY] = "memory", - [CGROUP_CONTROLLER_DEVICES] = "devices", - [CGROUP_CONTROLLER_PIDS] = "pids", -}; - -DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController); diff --git a/src/basic/cgroup-util.h b/src/basic/cgroup-util.h deleted file mode 100644 index ad1edd9cdb..0000000000 --- a/src/basic/cgroup-util.h +++ /dev/null @@ -1,192 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "def.h" -#include "hashmap.h" -#include "macro.h" -#include "set.h" - -/* An enum of well known cgroup controllers */ -typedef enum CGroupController { - CGROUP_CONTROLLER_CPU, - CGROUP_CONTROLLER_CPUACCT, - CGROUP_CONTROLLER_BLKIO, - CGROUP_CONTROLLER_MEMORY, - CGROUP_CONTROLLER_DEVICES, - CGROUP_CONTROLLER_PIDS, - _CGROUP_CONTROLLER_MAX, - _CGROUP_CONTROLLER_INVALID = -1, -} CGroupController; - -#define CGROUP_CONTROLLER_TO_MASK(c) (1 << (c)) - -/* A bit mask of well known cgroup controllers */ -typedef enum CGroupMask { - CGROUP_MASK_CPU = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPU), - CGROUP_MASK_CPUACCT = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPUACCT), - CGROUP_MASK_BLKIO = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BLKIO), - CGROUP_MASK_MEMORY = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_MEMORY), - CGROUP_MASK_DEVICES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_DEVICES), - CGROUP_MASK_PIDS = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_PIDS), - _CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1 -} CGroupMask; - -/* Special values for the cpu.shares attribute */ -#define CGROUP_CPU_SHARES_INVALID ((uint64_t) -1) -#define CGROUP_CPU_SHARES_MIN UINT64_C(2) -#define CGROUP_CPU_SHARES_MAX UINT64_C(262144) -#define CGROUP_CPU_SHARES_DEFAULT UINT64_C(1024) - -static inline bool CGROUP_CPU_SHARES_IS_OK(uint64_t x) { - return - x == CGROUP_CPU_SHARES_INVALID || - (x >= CGROUP_CPU_SHARES_MIN && x <= CGROUP_CPU_SHARES_MAX); -} - -/* Special values for the blkio.weight attribute */ -#define CGROUP_BLKIO_WEIGHT_INVALID ((uint64_t) -1) -#define CGROUP_BLKIO_WEIGHT_MIN UINT64_C(10) -#define CGROUP_BLKIO_WEIGHT_MAX UINT64_C(1000) -#define CGROUP_BLKIO_WEIGHT_DEFAULT UINT64_C(500) - -static inline bool CGROUP_BLKIO_WEIGHT_IS_OK(uint64_t x) { - return - x == CGROUP_BLKIO_WEIGHT_INVALID || - (x >= CGROUP_BLKIO_WEIGHT_MIN && x <= CGROUP_BLKIO_WEIGHT_MAX); -} - -/* - * General rules: - * - * We accept named hierarchies in the syntax "foo" and "name=foo". - * - * We expect that named hierarchies do not conflict in name with a - * kernel hierarchy, modulo the "name=" prefix. - * - * We always generate "normalized" controller names, i.e. without the - * "name=" prefix. - * - * We require absolute cgroup paths. When returning, we will always - * generate paths with multiple adjacent / removed. - */ - -int cg_enumerate_processes(const char *controller, const char *path, FILE **_f); -int cg_read_pid(FILE *f, pid_t *_pid); - -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); - -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); - -int cg_split_spec(const char *spec, char **controller, char **path); -int cg_mangle_path(const char *path, char **result); - -int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs); -int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs); - -int cg_pid_get_path(const char *controller, pid_t pid, char **path); - -int cg_trim(const char *controller, const char *path, bool delete_root); - -int cg_rmdir(const char *controller, const char *path); - -int cg_create(const char *controller, const char *path); -int cg_attach(const char *controller, const char *path, pid_t pid); -int cg_attach_fallback(const char *controller, const char *path, pid_t pid); -int cg_create_and_attach(const char *controller, const char *path, pid_t pid); - -int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value); -int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret); - -int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); -int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); - -int cg_install_release_agent(const char *controller, const char *agent); -int cg_uninstall_release_agent(const char *controller); - -int cg_is_empty(const char *controller, const char *path); -int cg_is_empty_recursive(const char *controller, const char *path); - -int cg_get_root_path(char **path); - -int cg_path_get_session(const char *path, char **session); -int cg_path_get_owner_uid(const char *path, uid_t *uid); -int cg_path_get_unit(const char *path, char **unit); -int cg_path_get_user_unit(const char *path, char **unit); -int cg_path_get_machine_name(const char *path, char **machine); -int cg_path_get_slice(const char *path, char **slice); -int cg_path_get_user_slice(const char *path, char **slice); - -int cg_shift_path(const char *cgroup, const char *cached_root, const char **shifted); -int cg_pid_get_path_shifted(pid_t pid, const char *cached_root, char **cgroup); - -int cg_pid_get_session(pid_t pid, char **session); -int cg_pid_get_owner_uid(pid_t pid, uid_t *uid); -int cg_pid_get_unit(pid_t pid, char **unit); -int cg_pid_get_user_unit(pid_t pid, char **unit); -int cg_pid_get_machine_name(pid_t pid, char **machine); -int cg_pid_get_slice(pid_t pid, char **slice); -int cg_pid_get_user_slice(pid_t pid, char **slice); - -int cg_path_decode_unit(const char *cgroup, char **unit); - -char *cg_escape(const char *p); -char *cg_unescape(const char *p) _pure_; - -bool cg_controller_is_valid(const char *p); - -int cg_slice_to_path(const char *unit, char **ret); - -typedef const char* (*cg_migrate_callback_t)(CGroupMask mask, void *userdata); - -int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path); -int cg_attach_everywhere(CGroupMask supported, const char *path, pid_t pid, cg_migrate_callback_t callback, void *userdata); -int cg_attach_many_everywhere(CGroupMask supported, const char *path, Set* pids, cg_migrate_callback_t callback, void *userdata); -int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to, cg_migrate_callback_t callback, void *userdata); -int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root); -int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p); - -int cg_mask_supported(CGroupMask *ret); - -int cg_kernel_controllers(Set *controllers); - -int cg_unified(void); -void cg_unified_flush(void); - -bool cg_is_unified_wanted(void); -bool cg_is_legacy_wanted(void); - -const char* cgroup_controller_to_string(CGroupController c) _const_; -CGroupController cgroup_controller_from_string(const char *s) _pure_; - -int cg_cpu_shares_parse(const char *s, uint64_t *ret); -int cg_blkio_weight_parse(const char *s, uint64_t *ret); diff --git a/src/basic/chattr-util.c b/src/basic/chattr-util.c deleted file mode 100644 index 2896a729af..0000000000 --- a/src/basic/chattr-util.c +++ /dev/null @@ -1,107 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "chattr-util.h" -#include "fd-util.h" -#include "macro.h" - -int chattr_fd(int fd, unsigned value, unsigned mask) { - unsigned old_attr, new_attr; - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - /* Explicitly check whether this is a regular file or - * directory. If it is anything else (such as a device node or - * fifo), then the ioctl will not hit the file systems but - * possibly drivers, where the ioctl might have different - * effects. Notably, DRM is using the same ioctl() number. */ - - if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) - return -ENOTTY; - - if (mask == 0) - return 0; - - if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0) - return -errno; - - new_attr = (old_attr & ~mask) | (value & mask); - if (new_attr == old_attr) - return 0; - - if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) < 0) - return -errno; - - return 1; -} - -int chattr_path(const char *p, unsigned value, unsigned mask) { - _cleanup_close_ int fd = -1; - - assert(p); - - if (mask == 0) - return 0; - - fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return chattr_fd(fd, value, mask); -} - -int read_attr_fd(int fd, unsigned *ret) { - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) - return -ENOTTY; - - if (ioctl(fd, FS_IOC_GETFLAGS, ret) < 0) - return -errno; - - return 0; -} - -int read_attr_path(const char *p, unsigned *ret) { - _cleanup_close_ int fd = -1; - - assert(p); - assert(ret); - - fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - - return read_attr_fd(fd, ret); -} diff --git a/src/basic/chattr-util.h b/src/basic/chattr-util.h deleted file mode 100644 index 960cf6d5b3..0000000000 --- a/src/basic/chattr-util.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int chattr_fd(int fd, unsigned value, unsigned mask); -int chattr_path(const char *p, unsigned value, unsigned mask); - -int read_attr_fd(int fd, unsigned *ret); -int read_attr_path(const char *p, unsigned *ret); diff --git a/src/basic/clock-util.c b/src/basic/clock-util.c deleted file mode 100644 index 507e757ff0..0000000000 --- a/src/basic/clock-util.c +++ /dev/null @@ -1,160 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2012 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "clock-util.h" -#include "fd-util.h" -#include "macro.h" -#include "string-util.h" -#include "util.h" - -int clock_get_hwclock(struct tm *tm) { - _cleanup_close_ int fd = -1; - - assert(tm); - - fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC); - if (fd < 0) - return -errno; - - /* This leaves the timezone fields of struct tm - * uninitialized! */ - if (ioctl(fd, RTC_RD_TIME, tm) < 0) - return -errno; - - /* We don't know daylight saving, so we reset this in order not - * to confuse mktime(). */ - tm->tm_isdst = -1; - - return 0; -} - -int clock_set_hwclock(const struct tm *tm) { - _cleanup_close_ int fd = -1; - - assert(tm); - - fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC); - if (fd < 0) - return -errno; - - if (ioctl(fd, RTC_SET_TIME, tm) < 0) - return -errno; - - return 0; -} - -int clock_is_localtime(void) { - _cleanup_fclose_ FILE *f; - - /* - * The third line of adjtime is "UTC" or "LOCAL" or nothing. - * # /etc/adjtime - * 0.0 0 0 - * 0 - * UTC - */ - f = fopen("/etc/adjtime", "re"); - if (f) { - char line[LINE_MAX]; - bool b; - - b = fgets(line, sizeof(line), f) && - fgets(line, sizeof(line), f) && - fgets(line, sizeof(line), f); - if (!b) - return -EIO; - - truncate_nl(line); - return streq(line, "LOCAL"); - - } else if (errno != ENOENT) - return -errno; - - return 0; -} - -int clock_set_timezone(int *min) { - const struct timeval *tv_null = NULL; - struct timespec ts; - struct tm *tm; - int minutesdelta; - struct timezone tz; - - assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); - assert_se(tm = localtime(&ts.tv_sec)); - minutesdelta = tm->tm_gmtoff / 60; - - tz.tz_minuteswest = -minutesdelta; - tz.tz_dsttime = 0; /* DST_NONE */ - - /* - * If the RTC does not run in UTC but in local time, the very first - * call to settimeofday() will set the kernel's timezone and will warp the - * system clock, so that it runs in UTC instead of the local time we - * have read from the RTC. - */ - if (settimeofday(tv_null, &tz) < 0) - return negative_errno(); - - if (min) - *min = minutesdelta; - return 0; -} - -int clock_reset_timewarp(void) { - const struct timeval *tv_null = NULL; - struct timezone tz; - - tz.tz_minuteswest = 0; - tz.tz_dsttime = 0; /* DST_NONE */ - - /* - * The very first call to settimeofday() does time warp magic. Do a - * dummy call here, so the time warping is sealed and all later calls - * behave as expected. - */ - if (settimeofday(tv_null, &tz) < 0) - return -errno; - - return 0; -} - -#define TIME_EPOCH_USEC ((usec_t) TIME_EPOCH * USEC_PER_SEC) - -int clock_apply_epoch(void) { - struct timespec ts; - - if (now(CLOCK_REALTIME) >= TIME_EPOCH_USEC) - return 0; - - if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, TIME_EPOCH_USEC)) < 0) - return -errno; - - return 1; -} diff --git a/src/basic/clock-util.h b/src/basic/clock-util.h deleted file mode 100644 index f471f2abcf..0000000000 --- a/src/basic/clock-util.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2012 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 . -***/ - -#include - -int clock_is_localtime(void); -int clock_set_timezone(int *min); -int clock_reset_timewarp(void); -int clock_get_hwclock(struct tm *tm); -int clock_set_hwclock(const struct tm *tm); -int clock_apply_epoch(void); diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c deleted file mode 100644 index c781610e14..0000000000 --- a/src/basic/conf-files.c +++ /dev/null @@ -1,166 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "conf-files.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "hashmap.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "path-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -static int files_add(Hashmap *h, const char *root, const char *path, const char *suffix) { - _cleanup_closedir_ DIR *dir = NULL; - const char *dirpath; - struct dirent *de; - int r; - - assert(path); - assert(suffix); - - dirpath = prefix_roota(root, path); - - dir = opendir(dirpath); - if (!dir) { - if (errno == ENOENT) - return 0; - return -errno; - } - - FOREACH_DIRENT(de, dir, return -errno) { - char *p; - - if (!dirent_is_file_with_suffix(de, suffix)) - continue; - - p = strjoin(dirpath, "/", de->d_name, NULL); - if (!p) - return -ENOMEM; - - r = hashmap_put(h, basename(p), p); - if (r == -EEXIST) { - log_debug("Skipping overridden file: %s.", p); - free(p); - } else if (r < 0) { - free(p); - return r; - } else if (r == 0) { - log_debug("Duplicate file %s", p); - free(p); - } - } - - return 0; -} - -static int base_cmp(const void *a, const void *b) { - const char *s1, *s2; - - s1 = *(char * const *)a; - s2 = *(char * const *)b; - return strcmp(basename(s1), basename(s2)); -} - -static int conf_files_list_strv_internal(char ***strv, const char *suffix, const char *root, char **dirs) { - _cleanup_hashmap_free_ Hashmap *fh = NULL; - char **files, **p; - int r; - - assert(strv); - assert(suffix); - - /* This alters the dirs string array */ - if (!path_strv_resolve_uniq(dirs, root)) - return -ENOMEM; - - fh = hashmap_new(&string_hash_ops); - if (!fh) - return -ENOMEM; - - STRV_FOREACH(p, dirs) { - r = files_add(fh, root, *p, suffix); - if (r == -ENOMEM) - return r; - if (r < 0) - log_debug_errno(r, "Failed to search for files in %s, ignoring: %m", *p); - } - - files = hashmap_get_strv(fh); - if (!files) - return -ENOMEM; - - qsort_safe(files, hashmap_size(fh), sizeof(char *), base_cmp); - *strv = files; - - return 0; -} - -int conf_files_list_strv(char ***strv, const char *suffix, const char *root, const char* const* dirs) { - _cleanup_strv_free_ char **copy = NULL; - - assert(strv); - assert(suffix); - - copy = strv_copy((char**) dirs); - if (!copy) - return -ENOMEM; - - return conf_files_list_strv_internal(strv, suffix, root, copy); -} - -int conf_files_list(char ***strv, const char *suffix, const char *root, const char *dir, ...) { - _cleanup_strv_free_ char **dirs = NULL; - va_list ap; - - assert(strv); - assert(suffix); - - va_start(ap, dir); - dirs = strv_new_ap(dir, ap); - va_end(ap); - - if (!dirs) - return -ENOMEM; - - return conf_files_list_strv_internal(strv, suffix, root, dirs); -} - -int conf_files_list_nulstr(char ***strv, const char *suffix, const char *root, const char *d) { - _cleanup_strv_free_ char **dirs = NULL; - - assert(strv); - assert(suffix); - - dirs = strv_split_nulstr(d); - if (!dirs) - return -ENOMEM; - - return conf_files_list_strv_internal(strv, suffix, root, dirs); -} diff --git a/src/basic/conf-files.h b/src/basic/conf-files.h deleted file mode 100644 index e00e0e81fb..0000000000 --- a/src/basic/conf-files.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2012 Lennart Poettering - Copyright 2010-2012 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 . -***/ - -int conf_files_list(char ***ret, const char *suffix, const char *root, const char *dir, ...); -int conf_files_list_strv(char ***ret, const char *suffix, const char *root, const char* const* dirs); -int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, const char *dirs); diff --git a/src/basic/copy.c b/src/basic/copy.c deleted file mode 100644 index 519b412941..0000000000 --- a/src/basic/copy.c +++ /dev/null @@ -1,548 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "btrfs-util.h" -#include "chattr-util.h" -#include "copy.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "io-util.h" -#include "macro.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "umask-util.h" -#include "xattr-util.h" - -#define COPY_BUFFER_SIZE (16*1024) - -int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink) { - bool try_sendfile = true, try_splice = true; - int r; - - assert(fdf >= 0); - assert(fdt >= 0); - - /* Try btrfs reflinks first. */ - if (try_reflink && - max_bytes == (uint64_t) -1 && - lseek(fdf, 0, SEEK_CUR) == 0 && - lseek(fdt, 0, SEEK_CUR) == 0) { - - r = btrfs_reflink(fdf, fdt); - if (r >= 0) - return 0; /* we copied the whole thing, hence hit EOF, return 0 */ - } - - for (;;) { - size_t m = COPY_BUFFER_SIZE; - ssize_t n; - - if (max_bytes != (uint64_t) -1) { - - if (max_bytes <= 0) - return 1; /* return > 0 if we hit the max_bytes limit */ - - if ((uint64_t) m > max_bytes) - m = (size_t) max_bytes; - } - - /* First try sendfile(), unless we already tried */ - if (try_sendfile) { - - n = sendfile(fdt, fdf, NULL, m); - if (n < 0) { - if (errno != EINVAL && errno != ENOSYS) - return -errno; - - try_sendfile = false; - /* use fallback below */ - } else if (n == 0) /* EOF */ - break; - else if (n > 0) - /* Success! */ - goto next; - } - - /* The try splice, unless we already tried */ - if (try_splice) { - n = splice(fdf, NULL, fdt, NULL, m, 0); - if (n < 0) { - if (errno != EINVAL && errno != ENOSYS) - return -errno; - - try_splice = false; - /* use fallback below */ - } else if (n == 0) /* EOF */ - break; - else if (n > 0) - /* Success! */ - goto next; - } - - /* As a fallback just copy bits by hand */ - { - uint8_t buf[m]; - - n = read(fdf, buf, m); - if (n < 0) - return -errno; - if (n == 0) /* EOF */ - break; - - r = loop_write(fdt, buf, (size_t) n, false); - if (r < 0) - return r; - } - - next: - if (max_bytes != (uint64_t) -1) { - assert(max_bytes >= (uint64_t) n); - max_bytes -= n; - } - } - - return 0; /* return 0 if we hit EOF earlier than the size limit */ -} - -static int fd_copy_symlink(int df, const char *from, const struct stat *st, int dt, const char *to) { - _cleanup_free_ char *target = NULL; - int r; - - assert(from); - assert(st); - assert(to); - - r = readlinkat_malloc(df, from, &target); - if (r < 0) - return r; - - if (symlinkat(target, dt, to) < 0) - return -errno; - - if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) - return -errno; - - return 0; -} - -static int fd_copy_regular(int df, const char *from, const struct stat *st, int dt, const char *to) { - _cleanup_close_ int fdf = -1, fdt = -1; - struct timespec ts[2]; - int r, q; - - assert(from); - assert(st); - assert(to); - - fdf = openat(df, from, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fdf < 0) - return -errno; - - fdt = openat(dt, to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, st->st_mode & 07777); - if (fdt < 0) - return -errno; - - r = copy_bytes(fdf, fdt, (uint64_t) -1, true); - if (r < 0) { - unlinkat(dt, to, 0); - return r; - } - - if (fchown(fdt, st->st_uid, st->st_gid) < 0) - r = -errno; - - if (fchmod(fdt, st->st_mode & 07777) < 0) - r = -errno; - - ts[0] = st->st_atim; - ts[1] = st->st_mtim; - (void) futimens(fdt, ts); - - (void) copy_xattr(fdf, fdt); - - q = close(fdt); - fdt = -1; - - if (q < 0) { - r = -errno; - unlinkat(dt, to, 0); - } - - return r; -} - -static int fd_copy_fifo(int df, const char *from, const struct stat *st, int dt, const char *to) { - int r; - - assert(from); - assert(st); - assert(to); - - r = mkfifoat(dt, to, st->st_mode & 07777); - if (r < 0) - return -errno; - - if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) - r = -errno; - - if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0) - r = -errno; - - return r; -} - -static int fd_copy_node(int df, const char *from, const struct stat *st, int dt, const char *to) { - int r; - - assert(from); - assert(st); - assert(to); - - r = mknodat(dt, to, st->st_mode, st->st_rdev); - if (r < 0) - return -errno; - - if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) - r = -errno; - - if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0) - r = -errno; - - return r; -} - -static int fd_copy_directory( - int df, - const char *from, - const struct stat *st, - int dt, - const char *to, - dev_t original_device, - bool merge) { - - _cleanup_close_ int fdf = -1, fdt = -1; - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - bool created; - int r; - - assert(st); - assert(to); - - if (from) - fdf = openat(df, from, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - else - fdf = fcntl(df, F_DUPFD_CLOEXEC, 3); - - d = fdopendir(fdf); - if (!d) - return -errno; - fdf = -1; - - r = mkdirat(dt, to, st->st_mode & 07777); - if (r >= 0) - created = true; - else if (errno == EEXIST && merge) - created = false; - else - return -errno; - - fdt = openat(dt, to, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fdt < 0) - return -errno; - - r = 0; - - if (created) { - struct timespec ut[2] = { - st->st_atim, - st->st_mtim - }; - - if (fchown(fdt, st->st_uid, st->st_gid) < 0) - r = -errno; - - if (fchmod(fdt, st->st_mode & 07777) < 0) - r = -errno; - - (void) futimens(fdt, ut); - (void) copy_xattr(dirfd(d), fdt); - } - - FOREACH_DIRENT_ALL(de, d, return -errno) { - struct stat buf; - int q; - - if (STR_IN_SET(de->d_name, ".", "..")) - continue; - - if (fstatat(dirfd(d), de->d_name, &buf, AT_SYMLINK_NOFOLLOW) < 0) { - r = -errno; - continue; - } - - if (buf.st_dev != original_device) - continue; - - if (S_ISREG(buf.st_mode)) - q = fd_copy_regular(dirfd(d), de->d_name, &buf, fdt, de->d_name); - else if (S_ISDIR(buf.st_mode)) - q = fd_copy_directory(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device, merge); - else if (S_ISLNK(buf.st_mode)) - q = fd_copy_symlink(dirfd(d), de->d_name, &buf, fdt, de->d_name); - else if (S_ISFIFO(buf.st_mode)) - q = fd_copy_fifo(dirfd(d), de->d_name, &buf, fdt, de->d_name); - else if (S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode)) - q = fd_copy_node(dirfd(d), de->d_name, &buf, fdt, de->d_name); - else - q = -EOPNOTSUPP; - - if (q == -EEXIST && merge) - q = 0; - - if (q < 0) - r = q; - } - - return r; -} - -int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge) { - struct stat st; - - assert(from); - assert(to); - - if (fstatat(fdf, from, &st, AT_SYMLINK_NOFOLLOW) < 0) - return -errno; - - if (S_ISREG(st.st_mode)) - return fd_copy_regular(fdf, from, &st, fdt, to); - else if (S_ISDIR(st.st_mode)) - return fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, merge); - else if (S_ISLNK(st.st_mode)) - return fd_copy_symlink(fdf, from, &st, fdt, to); - else if (S_ISFIFO(st.st_mode)) - return fd_copy_fifo(fdf, from, &st, fdt, to); - else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) - return fd_copy_node(fdf, from, &st, fdt, to); - else - return -EOPNOTSUPP; -} - -int copy_tree(const char *from, const char *to, bool merge) { - return copy_tree_at(AT_FDCWD, from, AT_FDCWD, to, merge); -} - -int copy_directory_fd(int dirfd, const char *to, bool merge) { - - struct stat st; - - assert(dirfd >= 0); - assert(to); - - if (fstat(dirfd, &st) < 0) - return -errno; - - if (!S_ISDIR(st.st_mode)) - return -ENOTDIR; - - return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, merge); -} - -int copy_file_fd(const char *from, int fdt, bool try_reflink) { - _cleanup_close_ int fdf = -1; - int r; - - assert(from); - assert(fdt >= 0); - - fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fdf < 0) - return -errno; - - r = copy_bytes(fdf, fdt, (uint64_t) -1, try_reflink); - - (void) copy_times(fdf, fdt); - (void) copy_xattr(fdf, fdt); - - return r; -} - -int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned chattr_flags) { - int fdt = -1, r; - - assert(from); - assert(to); - - RUN_WITH_UMASK(0000) { - fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode); - if (fdt < 0) - return -errno; - } - - if (chattr_flags != 0) - (void) chattr_fd(fdt, chattr_flags, (unsigned) -1); - - r = copy_file_fd(from, fdt, true); - if (r < 0) { - close(fdt); - unlink(to); - return r; - } - - if (close(fdt) < 0) { - unlink_noerrno(to); - return -errno; - } - - return 0; -} - -int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace, unsigned chattr_flags) { - _cleanup_free_ char *t = NULL; - int r; - - assert(from); - assert(to); - - r = tempfn_random(to, NULL, &t); - if (r < 0) - return r; - - r = copy_file(from, t, O_NOFOLLOW|O_EXCL, mode, chattr_flags); - if (r < 0) - return r; - - if (replace) { - r = renameat(AT_FDCWD, t, AT_FDCWD, to); - if (r < 0) - r = -errno; - } else - r = rename_noreplace(AT_FDCWD, t, AT_FDCWD, to); - if (r < 0) { - (void) unlink_noerrno(t); - return r; - } - - return 0; -} - -int copy_times(int fdf, int fdt) { - struct timespec ut[2]; - struct stat st; - usec_t crtime = 0; - - assert(fdf >= 0); - assert(fdt >= 0); - - if (fstat(fdf, &st) < 0) - return -errno; - - ut[0] = st.st_atim; - ut[1] = st.st_mtim; - - if (futimens(fdt, ut) < 0) - return -errno; - - if (fd_getcrtime(fdf, &crtime) >= 0) - (void) fd_setcrtime(fdt, crtime); - - return 0; -} - -int copy_xattr(int fdf, int fdt) { - _cleanup_free_ char *bufa = NULL, *bufb = NULL; - size_t sza = 100, szb = 100; - ssize_t n; - int ret = 0; - const char *p; - - for (;;) { - bufa = malloc(sza); - if (!bufa) - return -ENOMEM; - - n = flistxattr(fdf, bufa, sza); - if (n == 0) - return 0; - if (n > 0) - break; - if (errno != ERANGE) - return -errno; - - sza *= 2; - - bufa = mfree(bufa); - } - - p = bufa; - while (n > 0) { - size_t l; - - l = strlen(p); - assert(l < (size_t) n); - - if (startswith(p, "user.")) { - ssize_t m; - - if (!bufb) { - bufb = malloc(szb); - if (!bufb) - return -ENOMEM; - } - - m = fgetxattr(fdf, p, bufb, szb); - if (m < 0) { - if (errno == ERANGE) { - szb *= 2; - bufb = mfree(bufb); - continue; - } - - return -errno; - } - - if (fsetxattr(fdt, p, bufb, m, 0) < 0) - ret = -errno; - } - - p += l + 1; - n -= l + 1; - } - - return ret; -} diff --git a/src/basic/copy.h b/src/basic/copy.h deleted file mode 100644 index 3e5eb52506..0000000000 --- a/src/basic/copy.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include - -int copy_file_fd(const char *from, int to, bool try_reflink); -int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned chattr_flags); -int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace, unsigned chattr_flags); -int copy_tree(const char *from, const char *to, bool merge); -int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge); -int copy_directory_fd(int dirfd, const char *to, bool merge); -int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink); -int copy_times(int fdf, int fdt); -int copy_xattr(int fdf, int fdt); diff --git a/src/basic/cpu-set-util.c b/src/basic/cpu-set-util.c deleted file mode 100644 index 95ed6928ff..0000000000 --- a/src/basic/cpu-set-util.c +++ /dev/null @@ -1,114 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2015 Lennart Poettering - Copyright 2015 Filipe Brandenburger - - 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 . -***/ - -#include -#include -#include - -#include "alloc-util.h" -#include "cpu-set-util.h" -#include "extract-word.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "string-util.h" - -cpu_set_t* cpu_set_malloc(unsigned *ncpus) { - cpu_set_t *c; - unsigned n = 1024; - - /* Allocates the cpuset in the right size */ - - for (;;) { - c = CPU_ALLOC(n); - if (!c) - return NULL; - - if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), c) >= 0) { - CPU_ZERO_S(CPU_ALLOC_SIZE(n), c); - - if (ncpus) - *ncpus = n; - - return c; - } - - CPU_FREE(c); - - if (errno != EINVAL) - return NULL; - - n *= 2; - } -} - -int parse_cpu_set_and_warn( - const char *rvalue, - cpu_set_t **cpu_set, - const char *unit, - const char *filename, - unsigned line, - const char *lvalue) { - - const char *whole_rvalue = rvalue; - _cleanup_cpu_free_ cpu_set_t *c = NULL; - unsigned ncpus = 0; - - assert(lvalue); - assert(rvalue); - - for (;;) { - _cleanup_free_ char *word = NULL; - unsigned cpu, cpu_lower, cpu_upper; - int r; - - r = extract_first_word(&rvalue, &word, WHITESPACE ",", EXTRACT_QUOTES); - if (r < 0) - return log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, whole_rvalue); - if (r == 0) - break; - - if (!c) { - c = cpu_set_malloc(&ncpus); - if (!c) - return log_oom(); - } - - r = parse_range(word, &cpu_lower, &cpu_upper); - if (r < 0) - return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CPU affinity '%s'", word); - if (cpu_lower >= ncpus || cpu_upper >= ncpus) - return log_syntax(unit, LOG_ERR, filename, line, EINVAL, "CPU out of range '%s' ncpus is %u", word, ncpus); - - if (cpu_lower > cpu_upper) - log_syntax(unit, LOG_WARNING, filename, line, 0, "Range '%s' is invalid, %u > %u", word, cpu_lower, cpu_upper); - else - for (cpu = cpu_lower; cpu <= cpu_upper; cpu++) - CPU_SET_S(cpu, CPU_ALLOC_SIZE(ncpus), c); - } - - /* On success, sets *cpu_set and returns ncpus for the system. */ - if (c) { - *cpu_set = c; - c = NULL; - } - - return (int) ncpus; -} diff --git a/src/basic/cpu-set-util.h b/src/basic/cpu-set-util.h deleted file mode 100644 index 6f49d9afb0..0000000000 --- a/src/basic/cpu-set-util.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2015 Lennart Poettering - Copyright 2015 Filipe Brandenburger - - 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 . -***/ - -#include - -#include "macro.h" - -DEFINE_TRIVIAL_CLEANUP_FUNC(cpu_set_t*, CPU_FREE); -#define _cleanup_cpu_free_ _cleanup_(CPU_FREEp) - -cpu_set_t* cpu_set_malloc(unsigned *ncpus); - -int parse_cpu_set_and_warn(const char *rvalue, cpu_set_t **cpu_set, const char *unit, const char *filename, unsigned line, const char *lvalue); diff --git a/src/basic/def.h b/src/basic/def.h deleted file mode 100644 index 963343eb7d..0000000000 --- a/src/basic/def.h +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "util.h" - -#define DEFAULT_TIMEOUT_USEC (90*USEC_PER_SEC) -#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC) -#define DEFAULT_CONFIRM_USEC (30*USEC_PER_SEC) - -#define DEFAULT_START_LIMIT_INTERVAL (10*USEC_PER_SEC) -#define DEFAULT_START_LIMIT_BURST 5 - -/* The default time after which exit-on-idle services exit. This - * should be kept lower than the watchdog timeout, because otherwise - * the watchdog pings will keep the loop busy. */ -#define DEFAULT_EXIT_USEC (30*USEC_PER_SEC) - -/* The default value for the net.unix.max_dgram_qlen sysctl */ -#define DEFAULT_UNIX_MAX_DGRAM_QLEN 512UL - -#define SYSTEMD_CGROUP_CONTROLLER "name=systemd" - -#define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT -#define SIGNALS_IGNORE SIGPIPE - -#define REBOOT_PARAM_FILE "/run/systemd/reboot-param" - -#ifdef HAVE_SPLIT_USR -#define KBD_KEYMAP_DIRS \ - "/usr/share/keymaps/\0" \ - "/usr/share/kbd/keymaps/\0" \ - "/usr/lib/kbd/keymaps/\0" \ - "/lib/kbd/keymaps/\0" -#else -#define KBD_KEYMAP_DIRS \ - "/usr/share/keymaps/\0" \ - "/usr/share/kbd/keymaps/\0" \ - "/usr/lib/kbd/keymaps/\0" -#endif - -#define UNIX_SYSTEM_BUS_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" -#define KERNEL_SYSTEM_BUS_ADDRESS "kernel:path=/sys/fs/kdbus/0-system/bus" -#define DEFAULT_SYSTEM_BUS_ADDRESS KERNEL_SYSTEM_BUS_ADDRESS ";" UNIX_SYSTEM_BUS_ADDRESS -#define UNIX_USER_BUS_ADDRESS_FMT "unix:path=%s/bus" -#define KERNEL_USER_BUS_ADDRESS_FMT "kernel:path=/sys/fs/kdbus/"UID_FMT"-user/bus" - -#define PLYMOUTH_SOCKET { \ - .un.sun_family = AF_UNIX, \ - .un.sun_path = "\0/org/freedesktop/plymouthd", \ - } - -#ifndef TTY_GID -#define TTY_GID 5 -#endif - -#define NOTIFY_FD_MAX 768 -#define NOTIFY_BUFFER_MAX PIPE_BUF - -#ifdef HAVE_SPLIT_USR -#define _CONF_PATHS_SPLIT_USR(n) "/lib/" n "\0" -#else -#define _CONF_PATHS_SPLIT_USR(n) -#endif - -/* Return a nulstr for a standard cascade of configuration paths, - * suitable to pass to conf_files_list_nulstr() or config_parse_many() - * to implement drop-in directories for extending configuration - * files. */ -#define CONF_PATHS_NULSTR(n) \ - "/etc/" n "\0" \ - "/run/" n "\0" \ - "/usr/local/lib/" n "\0" \ - "/usr/lib/" n "\0" \ - _CONF_PATHS_SPLIT_USR(n) diff --git a/src/basic/device-nodes.c b/src/basic/device-nodes.c deleted file mode 100644 index 38c0628a90..0000000000 --- a/src/basic/device-nodes.c +++ /dev/null @@ -1,80 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2011 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 . -***/ - -#include -#include -#include - -#include "device-nodes.h" -#include "utf8.h" - -int whitelisted_char_for_devnode(char c, const char *white) { - - if ((c >= '0' && c <= '9') || - (c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - strchr("#+-.:=@_", c) != NULL || - (white != NULL && strchr(white, c) != NULL)) - return 1; - - return 0; -} - -int encode_devnode_name(const char *str, char *str_enc, size_t len) { - size_t i, j; - - if (str == NULL || str_enc == NULL) - return -EINVAL; - - for (i = 0, j = 0; str[i] != '\0'; i++) { - int seqlen; - - seqlen = utf8_encoded_valid_unichar(&str[i]); - if (seqlen > 1) { - - if (len-j < (size_t)seqlen) - return -EINVAL; - - memcpy(&str_enc[j], &str[i], seqlen); - j += seqlen; - i += (seqlen-1); - - } else if (str[i] == '\\' || !whitelisted_char_for_devnode(str[i], NULL)) { - - if (len-j < 4) - return -EINVAL; - - sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]); - j += 4; - - } else { - if (len-j < 1) - return -EINVAL; - - str_enc[j] = str[i]; - j++; - } - } - - if (len-j < 1) - return -EINVAL; - - str_enc[j] = '\0'; - return 0; -} diff --git a/src/basic/device-nodes.h b/src/basic/device-nodes.h deleted file mode 100644 index 94f385abcb..0000000000 --- a/src/basic/device-nodes.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include -#include - -int encode_devnode_name(const char *str, char *str_enc, size_t len); -int whitelisted_char_for_devnode(char c, const char *additional); diff --git a/src/basic/dirent-util.c b/src/basic/dirent-util.c deleted file mode 100644 index 5fb535cb13..0000000000 --- a/src/basic/dirent-util.c +++ /dev/null @@ -1,78 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2012 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 . -***/ - -#include -#include - -#include "dirent-util.h" -#include "path-util.h" -#include "string-util.h" - -int dirent_ensure_type(DIR *d, struct dirent *de) { - struct stat st; - - assert(d); - assert(de); - - if (de->d_type != DT_UNKNOWN) - return 0; - - if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) - return -errno; - - de->d_type = - S_ISREG(st.st_mode) ? DT_REG : - S_ISDIR(st.st_mode) ? DT_DIR : - S_ISLNK(st.st_mode) ? DT_LNK : - S_ISFIFO(st.st_mode) ? DT_FIFO : - S_ISSOCK(st.st_mode) ? DT_SOCK : - S_ISCHR(st.st_mode) ? DT_CHR : - S_ISBLK(st.st_mode) ? DT_BLK : - DT_UNKNOWN; - - return 0; -} - -bool dirent_is_file(const struct dirent *de) { - assert(de); - - if (hidden_file(de->d_name)) - return false; - - if (de->d_type != DT_REG && - de->d_type != DT_LNK && - de->d_type != DT_UNKNOWN) - return false; - - return true; -} - -bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { - assert(de); - - if (de->d_type != DT_REG && - de->d_type != DT_LNK && - de->d_type != DT_UNKNOWN) - return false; - - if (hidden_file_allow_backup(de->d_name)) - return false; - - return endswith(de->d_name, suffix); -} diff --git a/src/basic/dirent-util.h b/src/basic/dirent-util.h deleted file mode 100644 index 6bf099b46c..0000000000 --- a/src/basic/dirent-util.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#include "macro.h" -#include "path-util.h" - -int dirent_ensure_type(DIR *d, struct dirent *de); - -bool dirent_is_file(const struct dirent *de) _pure_; -bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) _pure_; - -#define FOREACH_DIRENT(de, d, on_error) \ - for (errno = 0, de = readdir(d);; errno = 0, de = readdir(d)) \ - if (!de) { \ - if (errno > 0) { \ - on_error; \ - } \ - break; \ - } else if (hidden_file((de)->d_name)) \ - continue; \ - else - -#define FOREACH_DIRENT_ALL(de, d, on_error) \ - for (errno = 0, de = readdir(d);; errno = 0, de = readdir(d)) \ - if (!de) { \ - if (errno > 0) { \ - on_error; \ - } \ - break; \ - } else diff --git a/src/basic/env-util.c b/src/basic/env-util.c deleted file mode 100644 index 7f5fddb700..0000000000 --- a/src/basic/env-util.c +++ /dev/null @@ -1,624 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "env-util.h" -#include "extract-word.h" -#include "macro.h" -#include "parse-util.h" -#include "string-util.h" -#include "strv.h" -#include "utf8.h" - -#define VALID_CHARS_ENV_NAME \ - DIGITS LETTERS \ - "_" - -#ifndef ARG_MAX -#define ARG_MAX ((size_t) sysconf(_SC_ARG_MAX)) -#endif - -static bool env_name_is_valid_n(const char *e, size_t n) { - const char *p; - - if (!e) - return false; - - if (n <= 0) - return false; - - if (e[0] >= '0' && e[0] <= '9') - return false; - - /* POSIX says the overall size of the environment block cannot - * be > ARG_MAX, an individual assignment hence cannot be - * either. Discounting the equal sign and trailing NUL this - * hence leaves ARG_MAX-2 as longest possible variable - * name. */ - if (n > ARG_MAX - 2) - return false; - - for (p = e; p < e + n; p++) - if (!strchr(VALID_CHARS_ENV_NAME, *p)) - return false; - - return true; -} - -bool env_name_is_valid(const char *e) { - if (!e) - return false; - - return env_name_is_valid_n(e, strlen(e)); -} - -bool env_value_is_valid(const char *e) { - if (!e) - return false; - - if (!utf8_is_valid(e)) - return false; - - /* bash allows tabs in environment variables, and so should - * we */ - if (string_has_cc(e, "\t")) - return false; - - /* POSIX says the overall size of the environment block cannot - * be > ARG_MAX, an individual assignment hence cannot be - * either. Discounting the shortest possible variable name of - * length 1, the equal sign and trailing NUL this hence leaves - * ARG_MAX-3 as longest possible variable value. */ - if (strlen(e) > ARG_MAX - 3) - return false; - - return true; -} - -bool env_assignment_is_valid(const char *e) { - const char *eq; - - eq = strchr(e, '='); - if (!eq) - return false; - - if (!env_name_is_valid_n(e, eq - e)) - return false; - - if (!env_value_is_valid(eq + 1)) - return false; - - /* POSIX says the overall size of the environment block cannot - * be > ARG_MAX, hence the individual variable assignments - * cannot be either, but let's leave room for one trailing NUL - * byte. */ - if (strlen(e) > ARG_MAX - 1) - return false; - - return true; -} - -bool strv_env_is_valid(char **e) { - char **p, **q; - - STRV_FOREACH(p, e) { - size_t k; - - if (!env_assignment_is_valid(*p)) - return false; - - /* Check if there are duplicate assginments */ - k = strcspn(*p, "="); - STRV_FOREACH(q, p + 1) - if (strneq(*p, *q, k) && (*q)[k] == '=') - return false; - } - - return true; -} - -bool strv_env_name_is_valid(char **l) { - char **p, **q; - - STRV_FOREACH(p, l) { - if (!env_name_is_valid(*p)) - return false; - - STRV_FOREACH(q, p + 1) - if (streq(*p, *q)) - return false; - } - - return true; -} - -bool strv_env_name_or_assignment_is_valid(char **l) { - char **p, **q; - - STRV_FOREACH(p, l) { - if (!env_assignment_is_valid(*p) && !env_name_is_valid(*p)) - return false; - - STRV_FOREACH(q, p + 1) - if (streq(*p, *q)) - return false; - } - - return true; -} - -static int env_append(char **r, char ***k, char **a) { - assert(r); - assert(k); - - if (!a) - return 0; - - /* Add the entries of a to *k unless they already exist in *r - * in which case they are overridden instead. This assumes - * there is enough space in the r array. */ - - for (; *a; a++) { - char **j; - size_t n; - - n = strcspn(*a, "="); - - if ((*a)[n] == '=') - n++; - - for (j = r; j < *k; j++) - if (strneq(*j, *a, n)) - break; - - if (j >= *k) - (*k)++; - else - free(*j); - - *j = strdup(*a); - if (!*j) - return -ENOMEM; - } - - return 0; -} - -char **strv_env_merge(unsigned n_lists, ...) { - size_t n = 0; - char **l, **k, **r; - va_list ap; - unsigned i; - - /* Merges an arbitrary number of environment sets */ - - va_start(ap, n_lists); - for (i = 0; i < n_lists; i++) { - l = va_arg(ap, char**); - n += strv_length(l); - } - va_end(ap); - - r = new(char*, n+1); - if (!r) - return NULL; - - k = r; - - va_start(ap, n_lists); - for (i = 0; i < n_lists; i++) { - l = va_arg(ap, char**); - if (env_append(r, &k, l) < 0) - goto fail; - } - va_end(ap); - - *k = NULL; - - return r; - -fail: - va_end(ap); - strv_free(r); - - return NULL; -} - -_pure_ static bool env_match(const char *t, const char *pattern) { - assert(t); - assert(pattern); - - /* pattern a matches string a - * a matches a= - * a matches a=b - * a= matches a= - * a=b matches a=b - * a= does not match a - * a=b does not match a= - * a=b does not match a - * a=b does not match a=c */ - - if (streq(t, pattern)) - return true; - - if (!strchr(pattern, '=')) { - size_t l = strlen(pattern); - - return strneq(t, pattern, l) && t[l] == '='; - } - - return false; -} - -char **strv_env_delete(char **x, unsigned n_lists, ...) { - size_t n, i = 0; - char **k, **r; - va_list ap; - - /* Deletes every entry from x that is mentioned in the other - * string lists */ - - n = strv_length(x); - - r = new(char*, n+1); - if (!r) - return NULL; - - STRV_FOREACH(k, x) { - unsigned v; - - va_start(ap, n_lists); - for (v = 0; v < n_lists; v++) { - char **l, **j; - - l = va_arg(ap, char**); - STRV_FOREACH(j, l) - if (env_match(*k, *j)) - goto skip; - } - va_end(ap); - - r[i] = strdup(*k); - if (!r[i]) { - strv_free(r); - return NULL; - } - - i++; - continue; - - skip: - va_end(ap); - } - - r[i] = NULL; - - assert(i <= n); - - return r; -} - -char **strv_env_unset(char **l, const char *p) { - - char **f, **t; - - if (!l) - return NULL; - - assert(p); - - /* Drops every occurrence of the env var setting p in the - * string list. Edits in-place. */ - - for (f = t = l; *f; f++) { - - if (env_match(*f, p)) { - free(*f); - continue; - } - - *(t++) = *f; - } - - *t = NULL; - return l; -} - -char **strv_env_unset_many(char **l, ...) { - - char **f, **t; - - if (!l) - return NULL; - - /* Like strv_env_unset() but applies many at once. Edits in-place. */ - - for (f = t = l; *f; f++) { - bool found = false; - const char *p; - va_list ap; - - va_start(ap, l); - - while ((p = va_arg(ap, const char*))) { - if (env_match(*f, p)) { - found = true; - break; - } - } - - va_end(ap); - - if (found) { - free(*f); - continue; - } - - *(t++) = *f; - } - - *t = NULL; - return l; -} - -char **strv_env_set(char **x, const char *p) { - - char **k, **r; - char* m[2] = { (char*) p, NULL }; - - /* Overrides the env var setting of p, returns a new copy */ - - r = new(char*, strv_length(x)+2); - if (!r) - return NULL; - - k = r; - if (env_append(r, &k, x) < 0) - goto fail; - - if (env_append(r, &k, m) < 0) - goto fail; - - *k = NULL; - - return r; - -fail: - strv_free(r); - return NULL; -} - -char *strv_env_get_n(char **l, const char *name, size_t k) { - char **i; - - assert(name); - - if (k <= 0) - return NULL; - - STRV_FOREACH(i, l) - if (strneq(*i, name, k) && - (*i)[k] == '=') - return *i + k + 1; - - return NULL; -} - -char *strv_env_get(char **l, const char *name) { - assert(name); - - return strv_env_get_n(l, name, strlen(name)); -} - -char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) { - char **p, **q; - int k = 0; - - STRV_FOREACH(p, e) { - size_t n; - bool duplicate = false; - - if (!env_assignment_is_valid(*p)) { - if (invalid_callback) - invalid_callback(*p, userdata); - free(*p); - continue; - } - - n = strcspn(*p, "="); - STRV_FOREACH(q, p + 1) - if (strneq(*p, *q, n) && (*q)[n] == '=') { - duplicate = true; - break; - } - - if (duplicate) { - free(*p); - continue; - } - - e[k++] = *p; - } - - if (e) - e[k] = NULL; - - return e; -} - -char *replace_env(const char *format, char **env) { - enum { - WORD, - CURLY, - VARIABLE - } state = WORD; - - const char *e, *word = format; - char *r = NULL, *k; - - assert(format); - - for (e = format; *e; e ++) { - - switch (state) { - - case WORD: - if (*e == '$') - state = CURLY; - break; - - case CURLY: - if (*e == '{') { - k = strnappend(r, word, e-word-1); - if (!k) - goto fail; - - free(r); - r = k; - - word = e-1; - state = VARIABLE; - - } else if (*e == '$') { - k = strnappend(r, word, e-word); - if (!k) - goto fail; - - free(r); - r = k; - - word = e+1; - state = WORD; - } else - state = WORD; - break; - - case VARIABLE: - if (*e == '}') { - const char *t; - - t = strempty(strv_env_get_n(env, word+2, e-word-2)); - - k = strappend(r, t); - if (!k) - goto fail; - - free(r); - r = k; - - word = e+1; - state = WORD; - } - break; - } - } - - k = strnappend(r, word, e-word); - if (!k) - goto fail; - - free(r); - return k; - -fail: - free(r); - return NULL; -} - -char **replace_env_argv(char **argv, char **env) { - char **ret, **i; - unsigned k = 0, l = 0; - - l = strv_length(argv); - - ret = new(char*, l+1); - if (!ret) - return NULL; - - STRV_FOREACH(i, argv) { - - /* If $FOO appears as single word, replace it by the split up variable */ - if ((*i)[0] == '$' && (*i)[1] != '{' && (*i)[1] != '$') { - char *e; - char **w, **m = NULL; - unsigned q; - - e = strv_env_get(env, *i+1); - if (e) { - int r; - - r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_QUOTES); - if (r < 0) { - ret[k] = NULL; - strv_free(ret); - return NULL; - } - } else - m = NULL; - - q = strv_length(m); - l = l + q - 1; - - w = realloc(ret, sizeof(char*) * (l+1)); - if (!w) { - ret[k] = NULL; - strv_free(ret); - strv_free(m); - return NULL; - } - - ret = w; - if (m) { - memcpy(ret + k, m, q * sizeof(char*)); - free(m); - } - - k += q; - continue; - } - - /* If ${FOO} appears as part of a word, replace it by the variable as-is */ - ret[k] = replace_env(*i, env); - if (!ret[k]) { - strv_free(ret); - return NULL; - } - k++; - } - - ret[k] = NULL; - return ret; -} - -int getenv_bool(const char *p) { - const char *e; - - e = getenv(p); - if (!e) - return -ENXIO; - - return parse_boolean(e); -} diff --git a/src/basic/env-util.h b/src/basic/env-util.h deleted file mode 100644 index b1fef704c2..0000000000 --- a/src/basic/env-util.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include -#include - -#include "macro.h" - -bool env_name_is_valid(const char *e); -bool env_value_is_valid(const char *e); -bool env_assignment_is_valid(const char *e); - -char *replace_env(const char *format, char **env); -char **replace_env_argv(char **argv, char **env); - -bool strv_env_is_valid(char **e); -#define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL) -char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata); - -bool strv_env_name_is_valid(char **l); -bool strv_env_name_or_assignment_is_valid(char **l); - -char **strv_env_merge(unsigned n_lists, ...); -char **strv_env_delete(char **x, unsigned n_lists, ...); /* New copy */ - -char **strv_env_set(char **x, const char *p); /* New copy ... */ -char **strv_env_unset(char **l, const char *p); /* In place ... */ -char **strv_env_unset_many(char **l, ...) _sentinel_; - -char *strv_env_get_n(char **l, const char *name, size_t k) _pure_; -char *strv_env_get(char **x, const char *n) _pure_; - -int getenv_bool(const char *p); diff --git a/src/basic/errno-list.c b/src/basic/errno-list.c deleted file mode 100644 index 31b66bad5e..0000000000 --- a/src/basic/errno-list.c +++ /dev/null @@ -1,57 +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 . -***/ - -#include - -#include "errno-list.h" -#include "macro.h" - -static const struct errno_name* lookup_errno(register const char *str, - register unsigned int len); - -#include "errno-from-name.h" -#include "errno-to-name.h" - -const char *errno_to_name(int id) { - - if (id < 0) - id = -id; - - if (id >= (int) ELEMENTSOF(errno_names)) - return NULL; - - return errno_names[id]; -} - -int errno_from_name(const char *name) { - const struct errno_name *sc; - - assert(name); - - sc = lookup_errno(name, strlen(name)); - if (!sc) - return -EINVAL; - - assert(sc->id > 0); - return sc->id; -} - -int errno_max(void) { - return ELEMENTSOF(errno_names); -} diff --git a/src/basic/errno-list.h b/src/basic/errno-list.h deleted file mode 100644 index 4eec0cc786..0000000000 --- a/src/basic/errno-list.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -const char *errno_to_name(int id); -int errno_from_name(const char *name); - -int errno_max(void); diff --git a/src/basic/escape.c b/src/basic/escape.c deleted file mode 100644 index 2e483880c8..0000000000 --- a/src/basic/escape.c +++ /dev/null @@ -1,474 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#include "alloc-util.h" -#include "escape.h" -#include "hexdecoct.h" -#include "macro.h" -#include "utf8.h" - -size_t cescape_char(char c, char *buf) { - char * buf_old = buf; - - switch (c) { - - case '\a': - *(buf++) = '\\'; - *(buf++) = 'a'; - break; - case '\b': - *(buf++) = '\\'; - *(buf++) = 'b'; - break; - case '\f': - *(buf++) = '\\'; - *(buf++) = 'f'; - break; - case '\n': - *(buf++) = '\\'; - *(buf++) = 'n'; - break; - case '\r': - *(buf++) = '\\'; - *(buf++) = 'r'; - break; - case '\t': - *(buf++) = '\\'; - *(buf++) = 't'; - break; - case '\v': - *(buf++) = '\\'; - *(buf++) = 'v'; - break; - case '\\': - *(buf++) = '\\'; - *(buf++) = '\\'; - break; - case '"': - *(buf++) = '\\'; - *(buf++) = '"'; - break; - case '\'': - *(buf++) = '\\'; - *(buf++) = '\''; - break; - - default: - /* For special chars we prefer octal over - * hexadecimal encoding, simply because glib's - * g_strescape() does the same */ - if ((c < ' ') || (c >= 127)) { - *(buf++) = '\\'; - *(buf++) = octchar((unsigned char) c >> 6); - *(buf++) = octchar((unsigned char) c >> 3); - *(buf++) = octchar((unsigned char) c); - } else - *(buf++) = c; - break; - } - - return buf - buf_old; -} - -char *cescape_length(const char *s, size_t n) { - const char *f; - char *r, *t; - - assert(s || n == 0); - - /* Does C style string escaping. May be reversed with - * cunescape(). */ - - r = new(char, n*4 + 1); - if (!r) - return NULL; - - for (f = s, t = r; f < s + n; f++) - t += cescape_char(*f, t); - - *t = 0; - - return r; -} - -char *cescape(const char *s) { - assert(s); - - return cescape_length(s, strlen(s)); -} - -int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) { - int r = 1; - - assert(p); - assert(*p); - assert(ret); - - /* Unescapes C style. Returns the unescaped character in ret. - * Sets *eight_bit to true if the escaped sequence either fits in - * one byte in UTF-8 or is a non-unicode literal byte and should - * instead be copied directly. - */ - - if (length != (size_t) -1 && length < 1) - return -EINVAL; - - switch (p[0]) { - - case 'a': - *ret = '\a'; - break; - case 'b': - *ret = '\b'; - break; - case 'f': - *ret = '\f'; - break; - case 'n': - *ret = '\n'; - break; - case 'r': - *ret = '\r'; - break; - case 't': - *ret = '\t'; - break; - case 'v': - *ret = '\v'; - break; - case '\\': - *ret = '\\'; - break; - case '"': - *ret = '"'; - break; - case '\'': - *ret = '\''; - break; - - case 's': - /* This is an extension of the XDG syntax files */ - *ret = ' '; - break; - - case 'x': { - /* hexadecimal encoding */ - int a, b; - - if (length != (size_t) -1 && length < 3) - return -EINVAL; - - a = unhexchar(p[1]); - if (a < 0) - return -EINVAL; - - b = unhexchar(p[2]); - if (b < 0) - return -EINVAL; - - /* Don't allow NUL bytes */ - if (a == 0 && b == 0) - return -EINVAL; - - *ret = (a << 4U) | b; - *eight_bit = true; - r = 3; - break; - } - - case 'u': { - /* C++11 style 16bit unicode */ - - int a[4]; - unsigned i; - uint32_t c; - - if (length != (size_t) -1 && length < 5) - return -EINVAL; - - for (i = 0; i < 4; i++) { - a[i] = unhexchar(p[1 + i]); - if (a[i] < 0) - return a[i]; - } - - c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3]; - - /* Don't allow 0 chars */ - if (c == 0) - return -EINVAL; - - *ret = c; - r = 5; - break; - } - - case 'U': { - /* C++11 style 32bit unicode */ - - int a[8]; - unsigned i; - char32_t c; - - if (length != (size_t) -1 && length < 9) - return -EINVAL; - - for (i = 0; i < 8; i++) { - a[i] = unhexchar(p[1 + i]); - if (a[i] < 0) - return a[i]; - } - - c = ((uint32_t) a[0] << 28U) | ((uint32_t) a[1] << 24U) | ((uint32_t) a[2] << 20U) | ((uint32_t) a[3] << 16U) | - ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] << 8U) | ((uint32_t) a[6] << 4U) | (uint32_t) a[7]; - - /* Don't allow 0 chars */ - if (c == 0) - return -EINVAL; - - /* Don't allow invalid code points */ - if (!unichar_is_valid(c)) - return -EINVAL; - - *ret = c; - r = 9; - break; - } - - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': { - /* octal encoding */ - int a, b, c; - char32_t m; - - if (length != (size_t) -1 && length < 3) - return -EINVAL; - - a = unoctchar(p[0]); - if (a < 0) - return -EINVAL; - - b = unoctchar(p[1]); - if (b < 0) - return -EINVAL; - - c = unoctchar(p[2]); - if (c < 0) - return -EINVAL; - - /* don't allow NUL bytes */ - if (a == 0 && b == 0 && c == 0) - return -EINVAL; - - /* Don't allow bytes above 255 */ - m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c; - if (m > 255) - return -EINVAL; - - *ret = m; - *eight_bit = true; - r = 3; - break; - } - - default: - return -EINVAL; - } - - return r; -} - -int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) { - char *r, *t; - const char *f; - size_t pl; - - assert(s); - assert(ret); - - /* Undoes C style string escaping, and optionally prefixes it. */ - - pl = prefix ? strlen(prefix) : 0; - - r = new(char, pl+length+1); - if (!r) - return -ENOMEM; - - if (prefix) - memcpy(r, prefix, pl); - - for (f = s, t = r + pl; f < s + length; f++) { - size_t remaining; - bool eight_bit = false; - char32_t u; - int k; - - remaining = s + length - f; - assert(remaining > 0); - - if (*f != '\\') { - /* A literal literal, copy verbatim */ - *(t++) = *f; - continue; - } - - if (remaining == 1) { - if (flags & UNESCAPE_RELAX) { - /* A trailing backslash, copy verbatim */ - *(t++) = *f; - continue; - } - - free(r); - return -EINVAL; - } - - k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit); - if (k < 0) { - if (flags & UNESCAPE_RELAX) { - /* Invalid escape code, let's take it literal then */ - *(t++) = '\\'; - continue; - } - - free(r); - return k; - } - - f += k; - if (eight_bit) - /* One byte? Set directly as specified */ - *(t++) = u; - else - /* Otherwise encode as multi-byte UTF-8 */ - t += utf8_encode_unichar(t, u); - } - - *t = 0; - - *ret = r; - return t - r; -} - -int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) { - return cunescape_length_with_prefix(s, length, NULL, flags, ret); -} - -int cunescape(const char *s, UnescapeFlags flags, char **ret) { - return cunescape_length(s, strlen(s), flags, ret); -} - -char *xescape(const char *s, const char *bad) { - char *r, *t; - const char *f; - - /* Escapes all chars in bad, in addition to \ and all special - * chars, in \xFF style escaping. May be reversed with - * cunescape(). */ - - r = new(char, strlen(s) * 4 + 1); - if (!r) - return NULL; - - for (f = s, t = r; *f; f++) { - - if ((*f < ' ') || (*f >= 127) || - (*f == '\\') || strchr(bad, *f)) { - *(t++) = '\\'; - *(t++) = 'x'; - *(t++) = hexchar(*f >> 4); - *(t++) = hexchar(*f); - } else - *(t++) = *f; - } - - *t = 0; - - return r; -} - -static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad) { - assert(bad); - - for (; *s; s++) { - if (*s == '\\' || strchr(bad, *s)) - *(t++) = '\\'; - - *(t++) = *s; - } - - return t; -} - -char *shell_escape(const char *s, const char *bad) { - char *r, *t; - - r = new(char, strlen(s)*2+1); - if (!r) - return NULL; - - t = strcpy_backslash_escaped(r, s, bad); - *t = 0; - - return r; -} - -char *shell_maybe_quote(const char *s) { - const char *p; - char *r, *t; - - assert(s); - - /* Encloses a string in double quotes if necessary to make it - * OK as shell string. */ - - for (p = s; *p; p++) - if (*p <= ' ' || - *p >= 127 || - strchr(SHELL_NEED_QUOTES, *p)) - break; - - if (!*p) - return strdup(s); - - r = new(char, 1+strlen(s)*2+1+1); - if (!r) - return NULL; - - t = r; - *(t++) = '"'; - t = mempcpy(t, s, p - s); - - t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE); - - *(t++)= '"'; - *t = 0; - - return r; -} diff --git a/src/basic/escape.h b/src/basic/escape.h deleted file mode 100644 index 1b28bd10af..0000000000 --- a/src/basic/escape.h +++ /dev/null @@ -1,53 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "string-util.h" -#include "missing.h" - -/* What characters are special in the shell? */ -/* must be escaped outside and inside double-quotes */ -#define SHELL_NEED_ESCAPE "\"\\`$" -/* can be escaped or double-quoted */ -#define SHELL_NEED_QUOTES SHELL_NEED_ESCAPE GLOB_CHARS "'()<>|&;" - -typedef enum UnescapeFlags { - UNESCAPE_RELAX = 1, -} UnescapeFlags; - -char *cescape(const char *s); -char *cescape_length(const char *s, size_t n); -size_t cescape_char(char c, char *buf); - -int cunescape(const char *s, UnescapeFlags flags, char **ret); -int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret); -int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret); -int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit); - -char *xescape(const char *s, const char *bad); - -char *shell_escape(const char *s, const char *bad); -char *shell_maybe_quote(const char *s); diff --git a/src/basic/ether-addr-util.c b/src/basic/ether-addr-util.c deleted file mode 100644 index ded6d31f4b..0000000000 --- a/src/basic/ether-addr-util.c +++ /dev/null @@ -1,44 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include -#include -#include - -#include "ether-addr-util.h" -#include "macro.h" - -char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]) { - assert(addr); - assert(buffer); - - /* Like ether_ntoa() but uses %02x instead of %x to print - * ethernet addresses, which makes them look less funny. Also, - * doesn't use a static buffer. */ - - sprintf(buffer, "%02x:%02x:%02x:%02x:%02x:%02x", - addr->ether_addr_octet[0], - addr->ether_addr_octet[1], - addr->ether_addr_octet[2], - addr->ether_addr_octet[3], - addr->ether_addr_octet[4], - addr->ether_addr_octet[5]); - - return buffer; -} diff --git a/src/basic/ether-addr-util.h b/src/basic/ether-addr-util.h deleted file mode 100644 index 4487149efd..0000000000 --- a/src/basic/ether-addr-util.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include - -#define ETHER_ADDR_FORMAT_STR "%02X%02X%02X%02X%02X%02X" -#define ETHER_ADDR_FORMAT_VAL(x) (x).ether_addr_octet[0], (x).ether_addr_octet[1], (x).ether_addr_octet[2], (x).ether_addr_octet[3], (x).ether_addr_octet[4], (x).ether_addr_octet[5] - -#define ETHER_ADDR_TO_STRING_MAX (3*6) - -char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]); diff --git a/src/basic/exit-status.c b/src/basic/exit-status.c deleted file mode 100644 index 5e0bc415c8..0000000000 --- a/src/basic/exit-status.c +++ /dev/null @@ -1,243 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "exit-status.h" -#include "macro.h" -#include "set.h" - -const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { - - /* We cast to int here, so that -Wenum doesn't complain that - * EXIT_SUCCESS/EXIT_FAILURE aren't in the enum */ - - switch ((int) status) { - - case EXIT_SUCCESS: - return "SUCCESS"; - - case EXIT_FAILURE: - return "FAILURE"; - } - - - if (level == EXIT_STATUS_SYSTEMD || level == EXIT_STATUS_LSB) { - switch ((int) status) { - - case EXIT_CHDIR: - return "CHDIR"; - - case EXIT_NICE: - return "NICE"; - - case EXIT_FDS: - return "FDS"; - - case EXIT_EXEC: - return "EXEC"; - - case EXIT_MEMORY: - return "MEMORY"; - - case EXIT_LIMITS: - return "LIMITS"; - - case EXIT_OOM_ADJUST: - return "OOM_ADJUST"; - - case EXIT_SIGNAL_MASK: - return "SIGNAL_MASK"; - - case EXIT_STDIN: - return "STDIN"; - - case EXIT_STDOUT: - return "STDOUT"; - - case EXIT_CHROOT: - return "CHROOT"; - - case EXIT_IOPRIO: - return "IOPRIO"; - - case EXIT_TIMERSLACK: - return "TIMERSLACK"; - - case EXIT_SECUREBITS: - return "SECUREBITS"; - - case EXIT_SETSCHEDULER: - return "SETSCHEDULER"; - - case EXIT_CPUAFFINITY: - return "CPUAFFINITY"; - - case EXIT_GROUP: - return "GROUP"; - - case EXIT_USER: - return "USER"; - - case EXIT_CAPABILITIES: - return "CAPABILITIES"; - - case EXIT_CGROUP: - return "CGROUP"; - - case EXIT_SETSID: - return "SETSID"; - - case EXIT_CONFIRM: - return "CONFIRM"; - - case EXIT_STDERR: - return "STDERR"; - - case EXIT_PAM: - return "PAM"; - - case EXIT_NETWORK: - return "NETWORK"; - - case EXIT_NAMESPACE: - return "NAMESPACE"; - - case EXIT_NO_NEW_PRIVILEGES: - return "NO_NEW_PRIVILEGES"; - - case EXIT_SECCOMP: - return "SECCOMP"; - - case EXIT_SELINUX_CONTEXT: - return "SELINUX_CONTEXT"; - - case EXIT_PERSONALITY: - return "PERSONALITY"; - - case EXIT_APPARMOR_PROFILE: - return "APPARMOR"; - - case EXIT_ADDRESS_FAMILIES: - return "ADDRESS_FAMILIES"; - - case EXIT_RUNTIME_DIRECTORY: - return "RUNTIME_DIRECTORY"; - - case EXIT_CHOWN: - return "CHOWN"; - - case EXIT_MAKE_STARTER: - return "MAKE_STARTER"; - - case EXIT_BUS_ENDPOINT: - return "BUS_ENDPOINT"; - - case EXIT_SMACK_PROCESS_LABEL: - return "SMACK_PROCESS_LABEL"; - } - } - - if (level == EXIT_STATUS_LSB) { - switch ((int) status) { - - case EXIT_INVALIDARGUMENT: - return "INVALIDARGUMENT"; - - case EXIT_NOTIMPLEMENTED: - return "NOTIMPLEMENTED"; - - case EXIT_NOPERMISSION: - return "NOPERMISSION"; - - case EXIT_NOTINSTALLED: - return "NOTINSTALLED"; - - case EXIT_NOTCONFIGURED: - return "NOTCONFIGURED"; - - case EXIT_NOTRUNNING: - return "NOTRUNNING"; - } - } - - return NULL; -} - - -bool is_clean_exit(int code, int status, ExitStatusSet *success_status) { - - if (code == CLD_EXITED) - return status == 0 || - (success_status && - set_contains(success_status->status, INT_TO_PTR(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 || - (success_status && - set_contains(success_status->signal, INT_TO_PTR(status))); - - return false; -} - -bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status) { - - if (is_clean_exit(code, status, success_status)) - return true; - - return - code == CLD_EXITED && - (status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED); -} - -void exit_status_set_free(ExitStatusSet *x) { - assert(x); - - set_free(x->status); - set_free(x->signal); - x->status = x->signal = NULL; -} - -bool exit_status_set_is_empty(ExitStatusSet *x) { - if (!x) - return true; - - return set_isempty(x->status) && set_isempty(x->signal); -} - -bool exit_status_set_test(ExitStatusSet *x, int code, int status) { - - if (exit_status_set_is_empty(x)) - return false; - - if (code == CLD_EXITED && set_contains(x->status, INT_TO_PTR(status))) - return true; - - if (IN_SET(code, CLD_KILLED, CLD_DUMPED) && set_contains(x->signal, INT_TO_PTR(status))) - return true; - - return false; -} diff --git a/src/basic/exit-status.h b/src/basic/exit-status.h deleted file mode 100644 index 79525d30ee..0000000000 --- a/src/basic/exit-status.h +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "hashmap.h" -#include "macro.h" -#include "set.h" - -typedef enum ExitStatus { - /* EXIT_SUCCESS defined by libc */ - /* EXIT_FAILURE defined by libc */ - EXIT_INVALIDARGUMENT = 2, - EXIT_NOTIMPLEMENTED = 3, - EXIT_NOPERMISSION = 4, - EXIT_NOTINSTALLED = 5, - EXIT_NOTCONFIGURED = 6, - EXIT_NOTRUNNING = 7, - - /* 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 */ - - EXIT_CHDIR = 200, - EXIT_NICE, - EXIT_FDS, - EXIT_EXEC, - EXIT_MEMORY, - EXIT_LIMITS, - EXIT_OOM_ADJUST, - EXIT_SIGNAL_MASK, - EXIT_STDIN, - EXIT_STDOUT, - EXIT_CHROOT, /* 210 */ - EXIT_IOPRIO, - EXIT_TIMERSLACK, - EXIT_SECUREBITS, - EXIT_SETSCHEDULER, - EXIT_CPUAFFINITY, - EXIT_GROUP, - EXIT_USER, - EXIT_CAPABILITIES, - EXIT_CGROUP, - EXIT_SETSID, /* 220 */ - EXIT_CONFIRM, - EXIT_STDERR, - _EXIT_RESERVED, /* used to be tcpwrap, don't reuse! */ - EXIT_PAM, - EXIT_NETWORK, - EXIT_NAMESPACE, - EXIT_NO_NEW_PRIVILEGES, - EXIT_SECCOMP, - EXIT_SELINUX_CONTEXT, - EXIT_PERSONALITY, /* 230 */ - EXIT_APPARMOR_PROFILE, - EXIT_ADDRESS_FAMILIES, - EXIT_RUNTIME_DIRECTORY, - EXIT_MAKE_STARTER, - EXIT_CHOWN, - EXIT_BUS_ENDPOINT, - EXIT_SMACK_PROCESS_LABEL, -} ExitStatus; - -typedef enum ExitStatusLevel { - EXIT_STATUS_MINIMAL, - EXIT_STATUS_SYSTEMD, - EXIT_STATUS_LSB, - EXIT_STATUS_FULL = EXIT_STATUS_LSB -} ExitStatusLevel; - -typedef struct ExitStatusSet { - Set *status; - Set *signal; -} ExitStatusSet; - -const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) _const_; - -bool is_clean_exit(int code, int status, ExitStatusSet *success_status); -bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status); - -void exit_status_set_free(ExitStatusSet *x); -bool exit_status_set_is_empty(ExitStatusSet *x); -bool exit_status_set_test(ExitStatusSet *x, int code, int status); diff --git a/src/basic/extract-word.c b/src/basic/extract-word.c deleted file mode 100644 index ee35d2a0ec..0000000000 --- a/src/basic/extract-word.c +++ /dev/null @@ -1,298 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "escape.h" -#include "extract-word.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "utf8.h" - -int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) { - _cleanup_free_ char *s = NULL; - size_t allocated = 0, sz = 0; - char c; - int r; - - char quote = 0; /* 0 or ' or " */ - bool backslash = false; /* whether we've just seen a backslash */ - - assert(p); - assert(ret); - - /* Bail early if called after last value or with no input */ - if (!*p) - goto finish_force_terminate; - c = **p; - - if (!separators) - separators = WHITESPACE; - - /* Parses the first word of a string, and returns it in - * *ret. Removes all quotes in the process. When parsing fails - * (because of an uneven number of quotes or similar), leaves - * the pointer *p at the first invalid character. */ - - if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) - if (!GREEDY_REALLOC(s, allocated, sz+1)) - return -ENOMEM; - - for (;; (*p) ++, c = **p) { - if (c == 0) - goto finish_force_terminate; - else if (strchr(separators, c)) { - if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { - (*p) ++; - goto finish_force_next; - } - } else { - /* We found a non-blank character, so we will always - * want to return a string (even if it is empty), - * allocate it here. */ - if (!GREEDY_REALLOC(s, allocated, sz+1)) - return -ENOMEM; - break; - } - } - - for (;; (*p) ++, c = **p) { - if (backslash) { - if (!GREEDY_REALLOC(s, allocated, sz+7)) - return -ENOMEM; - - if (c == 0) { - if ((flags & EXTRACT_CUNESCAPE_RELAX) && - (!quote || flags & EXTRACT_RELAX)) { - /* If we find an unquoted trailing backslash and we're in - * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the - * output. - * - * Unbalanced quotes will only be allowed in EXTRACT_RELAX - * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them. - */ - s[sz++] = '\\'; - goto finish_force_terminate; - } - if (flags & EXTRACT_RELAX) - goto finish_force_terminate; - return -EINVAL; - } - - if (flags & EXTRACT_CUNESCAPE) { - bool eight_bit = false; - char32_t u; - - r = cunescape_one(*p, (size_t) -1, &u, &eight_bit); - if (r < 0) { - if (flags & EXTRACT_CUNESCAPE_RELAX) { - s[sz++] = '\\'; - s[sz++] = c; - } else - return -EINVAL; - } else { - (*p) += r - 1; - - if (eight_bit) - s[sz++] = u; - else - sz += utf8_encode_unichar(s + sz, u); - } - } else - s[sz++] = c; - - backslash = false; - - } else if (quote) { /* inside either single or double quotes */ - for (;; (*p) ++, c = **p) { - if (c == 0) { - if (flags & EXTRACT_RELAX) - goto finish_force_terminate; - return -EINVAL; - } else if (c == quote) { /* found the end quote */ - quote = 0; - break; - } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { - backslash = true; - break; - } else { - if (!GREEDY_REALLOC(s, allocated, sz+2)) - return -ENOMEM; - - s[sz++] = c; - } - } - - } else { - for (;; (*p) ++, c = **p) { - if (c == 0) - goto finish_force_terminate; - else if ((c == '\'' || c == '"') && (flags & EXTRACT_QUOTES)) { - quote = c; - break; - } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { - backslash = true; - break; - } else if (strchr(separators, c)) { - if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { - (*p) ++; - goto finish_force_next; - } - /* Skip additional coalesced separators. */ - for (;; (*p) ++, c = **p) { - if (c == 0) - goto finish_force_terminate; - if (!strchr(separators, c)) - break; - } - goto finish; - - } else { - if (!GREEDY_REALLOC(s, allocated, sz+2)) - return -ENOMEM; - - s[sz++] = c; - } - } - } - } - -finish_force_terminate: - *p = NULL; -finish: - if (!s) { - *p = NULL; - *ret = NULL; - return 0; - } - -finish_force_next: - s[sz] = 0; - *ret = s; - s = NULL; - - return 1; -} - -int extract_first_word_and_warn( - const char **p, - char **ret, - const char *separators, - ExtractFlags flags, - const char *unit, - const char *filename, - unsigned line, - const char *rvalue) { - - /* Try to unquote it, if it fails, warn about it and try again - * but this time using EXTRACT_CUNESCAPE_RELAX to keep the - * backslashes verbatim in invalid escape sequences. */ - - const char *save; - int r; - - save = *p; - r = extract_first_word(p, ret, separators, flags); - if (r >= 0) - return r; - - if (r == -EINVAL && !(flags & EXTRACT_CUNESCAPE_RELAX)) { - - /* Retry it with EXTRACT_CUNESCAPE_RELAX. */ - *p = save; - r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX); - if (r >= 0) { - /* It worked this time, hence it must have been an invalid escape sequence we could correct. */ - log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Invalid escape sequences in line, correcting: \"%s\"", rvalue); - return r; - } - - /* If it's still EINVAL; then it must be unbalanced quoting, report this. */ - if (r == -EINVAL) - return log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting, ignoring: \"%s\"", rvalue); - } - - /* Can be any error, report it */ - return log_syntax(unit, LOG_ERR, filename, line, r, "Unable to decode word \"%s\", ignoring: %m", rvalue); -} - -int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) { - va_list ap; - char **l; - int n = 0, i, c, r; - - /* Parses a number of words from a string, stripping any - * quotes if necessary. */ - - assert(p); - - /* Count how many words are expected */ - va_start(ap, flags); - for (;;) { - if (!va_arg(ap, char **)) - break; - n++; - } - va_end(ap); - - if (n <= 0) - return 0; - - /* Read all words into a temporary array */ - l = newa0(char*, n); - for (c = 0; c < n; c++) { - - r = extract_first_word(p, &l[c], separators, flags); - if (r < 0) { - int j; - - for (j = 0; j < c; j++) - free(l[j]); - - return r; - } - - if (r == 0) - break; - } - - /* If we managed to parse all words, return them in the passed - * in parameters */ - va_start(ap, flags); - for (i = 0; i < n; i++) { - char **v; - - v = va_arg(ap, char **); - assert(v); - - *v = l[i]; - } - va_end(ap); - - return c; -} diff --git a/src/basic/extract-word.h b/src/basic/extract-word.h deleted file mode 100644 index 21db5ef33f..0000000000 --- a/src/basic/extract-word.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "macro.h" - -typedef enum ExtractFlags { - EXTRACT_RELAX = 1, - EXTRACT_CUNESCAPE = 2, - EXTRACT_CUNESCAPE_RELAX = 4, - EXTRACT_QUOTES = 8, - EXTRACT_DONT_COALESCE_SEPARATORS = 16, - EXTRACT_RETAIN_ESCAPE = 32, -} ExtractFlags; - -int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags); -int extract_first_word_and_warn(const char **p, char **ret, const char *separators, ExtractFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue); -int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) _sentinel_; diff --git a/src/basic/fd-util.c b/src/basic/fd-util.c deleted file mode 100644 index ec9560cd07..0000000000 --- a/src/basic/fd-util.c +++ /dev/null @@ -1,358 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "fd-util.h" -#include "macro.h" -#include "missing.h" -#include "parse-util.h" -#include "path-util.h" -#include "socket-util.h" -#include "util.h" - -int close_nointr(int fd) { - assert(fd >= 0); - - if (close(fd) >= 0) - return 0; - - /* - * Just ignore EINTR; a retry loop is the wrong thing to do on - * Linux. - * - * http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html - * https://bugzilla.gnome.org/show_bug.cgi?id=682819 - * http://utcc.utoronto.ca/~cks/space/blog/unix/CloseEINTR - * https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain - */ - if (errno == EINTR) - return 0; - - return -errno; -} - -int safe_close(int fd) { - - /* - * Like close_nointr() but cannot fail. Guarantees errno is - * unchanged. Is a NOP with negative fds passed, and returns - * -1, so that it can be used in this syntax: - * - * fd = safe_close(fd); - */ - - if (fd >= 0) { - PROTECT_ERRNO; - - /* The kernel might return pretty much any error code - * via close(), but the fd will be closed anyway. The - * only condition we want to check for here is whether - * the fd was invalid at all... */ - - assert_se(close_nointr(fd) != -EBADF); - } - - return -1; -} - -void safe_close_pair(int p[]) { - assert(p); - - if (p[0] == p[1]) { - /* Special case pairs which use the same fd in both - * directions... */ - p[0] = p[1] = safe_close(p[0]); - return; - } - - p[0] = safe_close(p[0]); - p[1] = safe_close(p[1]); -} - -void close_many(const int fds[], unsigned n_fd) { - unsigned i; - - assert(fds || n_fd <= 0); - - for (i = 0; i < n_fd; i++) - safe_close(fds[i]); -} - -int fclose_nointr(FILE *f) { - assert(f); - - /* Same as close_nointr(), but for fclose() */ - - if (fclose(f) == 0) - return 0; - - if (errno == EINTR) - return 0; - - return -errno; -} - -FILE* safe_fclose(FILE *f) { - - /* Same as safe_close(), but for fclose() */ - - if (f) { - PROTECT_ERRNO; - - assert_se(fclose_nointr(f) != EBADF); - } - - return NULL; -} - -DIR* safe_closedir(DIR *d) { - - if (d) { - PROTECT_ERRNO; - - assert_se(closedir(d) >= 0 || errno != EBADF); - } - - return NULL; -} - -int fd_nonblock(int fd, bool nonblock) { - int flags, nflags; - - assert(fd >= 0); - - flags = fcntl(fd, F_GETFL, 0); - if (flags < 0) - return -errno; - - if (nonblock) - nflags = flags | O_NONBLOCK; - else - nflags = flags & ~O_NONBLOCK; - - if (nflags == flags) - return 0; - - if (fcntl(fd, F_SETFL, nflags) < 0) - return -errno; - - return 0; -} - -int fd_cloexec(int fd, bool cloexec) { - int flags, nflags; - - assert(fd >= 0); - - flags = fcntl(fd, F_GETFD, 0); - if (flags < 0) - return -errno; - - if (cloexec) - nflags = flags | FD_CLOEXEC; - else - nflags = flags & ~FD_CLOEXEC; - - if (nflags == flags) - return 0; - - if (fcntl(fd, F_SETFD, nflags) < 0) - return -errno; - - return 0; -} - -_pure_ static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) { - unsigned i; - - assert(n_fdset == 0 || fdset); - - for (i = 0; i < n_fdset; i++) - if (fdset[i] == fd) - return true; - - return false; -} - -int close_all_fds(const int except[], unsigned n_except) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r = 0; - - assert(n_except == 0 || except); - - d = opendir("/proc/self/fd"); - if (!d) { - int fd; - struct rlimit rl; - - /* When /proc isn't available (for example in chroots) - * the fallback is brute forcing through the fd - * table */ - - assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0); - for (fd = 3; fd < (int) rl.rlim_max; fd ++) { - - if (fd_in_set(fd, except, n_except)) - continue; - - if (close_nointr(fd) < 0) - if (errno != EBADF && r == 0) - r = -errno; - } - - return r; - } - - while ((de = readdir(d))) { - int fd = -1; - - if (hidden_file(de->d_name)) - continue; - - if (safe_atoi(de->d_name, &fd) < 0) - /* Let's better ignore this, just in case */ - continue; - - if (fd < 3) - continue; - - if (fd == dirfd(d)) - continue; - - if (fd_in_set(fd, except, n_except)) - continue; - - if (close_nointr(fd) < 0) { - /* Valgrind has its own FD and doesn't want to have it closed */ - if (errno != EBADF && r == 0) - r = -errno; - } - } - - return r; -} - -int same_fd(int a, int b) { - struct stat sta, stb; - pid_t pid; - int r, fa, fb; - - assert(a >= 0); - assert(b >= 0); - - /* Compares two file descriptors. Note that semantics are - * quite different depending on whether we have kcmp() or we - * don't. If we have kcmp() this will only return true for - * dup()ed file descriptors, but not otherwise. If we don't - * have kcmp() this will also return true for two fds of the same - * file, created by separate open() calls. Since we use this - * call mostly for filtering out duplicates in the fd store - * this difference hopefully doesn't matter too much. */ - - if (a == b) - return true; - - /* Try to use kcmp() if we have it. */ - pid = getpid(); - r = kcmp(pid, pid, KCMP_FILE, a, b); - if (r == 0) - return true; - if (r > 0) - return false; - if (errno != ENOSYS) - return -errno; - - /* We don't have kcmp(), use fstat() instead. */ - if (fstat(a, &sta) < 0) - return -errno; - - if (fstat(b, &stb) < 0) - return -errno; - - if ((sta.st_mode & S_IFMT) != (stb.st_mode & S_IFMT)) - return false; - - /* We consider all device fds different, since two device fds - * might refer to quite different device contexts even though - * they share the same inode and backing dev_t. */ - - if (S_ISCHR(sta.st_mode) || S_ISBLK(sta.st_mode)) - return false; - - if (sta.st_dev != stb.st_dev || sta.st_ino != stb.st_ino) - return false; - - /* The fds refer to the same inode on disk, let's also check - * if they have the same fd flags. This is useful to - * distinguish the read and write side of a pipe created with - * pipe(). */ - fa = fcntl(a, F_GETFL); - if (fa < 0) - return -errno; - - fb = fcntl(b, F_GETFL); - if (fb < 0) - return -errno; - - return fa == fb; -} - -void cmsg_close_all(struct msghdr *mh) { - struct cmsghdr *cmsg; - - assert(mh); - - CMSG_FOREACH(cmsg, mh) - if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) - close_many((int*) CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int)); -} - -bool fdname_is_valid(const char *s) { - const char *p; - - /* Validates a name for $LISTEN_FDNAMES. We basically allow - * everything ASCII that's not a control character. Also, as - * special exception the ":" character is not allowed, as we - * use that as field separator in $LISTEN_FDNAMES. - * - * Note that the empty string is explicitly allowed - * here. However, we limit the length of the names to 255 - * characters. */ - - if (!s) - return false; - - for (p = s; *p; p++) { - if (*p < ' ') - return false; - if (*p >= 127) - return false; - if (*p == ':') - return false; - } - - return p - s < 256; -} diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h deleted file mode 100644 index 44528c6e35..0000000000 --- a/src/basic/fd-util.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "macro.h" - -/* Make sure we can distinguish fd 0 and NULL */ -#define FD_TO_PTR(fd) INT_TO_PTR((fd)+1) -#define PTR_TO_FD(p) (PTR_TO_INT(p)-1) - -int close_nointr(int fd); -int safe_close(int fd); -void safe_close_pair(int p[]); - -void close_many(const int fds[], unsigned n_fd); - -int fclose_nointr(FILE *f); -FILE* safe_fclose(FILE *f); -DIR* safe_closedir(DIR *f); - -static inline void closep(int *fd) { - safe_close(*fd); -} - -static inline void close_pairp(int (*p)[2]) { - safe_close_pair(*p); -} - -static inline void fclosep(FILE **f) { - safe_fclose(*f); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, pclose); -DEFINE_TRIVIAL_CLEANUP_FUNC(DIR*, closedir); - -#define _cleanup_close_ _cleanup_(closep) -#define _cleanup_fclose_ _cleanup_(fclosep) -#define _cleanup_pclose_ _cleanup_(pclosep) -#define _cleanup_closedir_ _cleanup_(closedirp) -#define _cleanup_close_pair_ _cleanup_(close_pairp) - -int fd_nonblock(int fd, bool nonblock); -int fd_cloexec(int fd, bool cloexec); - -int close_all_fds(const int except[], unsigned n_except); - -int same_fd(int a, int b); - -void cmsg_close_all(struct msghdr *mh); - -bool fdname_is_valid(const char *s); - -/* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */ -#define ERRNO_IS_DISCONNECT(r) \ - IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH) diff --git a/src/basic/fdset.c b/src/basic/fdset.c deleted file mode 100644 index 3674d3ed9d..0000000000 --- a/src/basic/fdset.c +++ /dev/null @@ -1,286 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "sd-daemon.h" - -#include "fd-util.h" -#include "fdset.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "set.h" - -#define MAKE_SET(s) ((Set*) s) -#define MAKE_FDSET(s) ((FDSet*) s) - -FDSet *fdset_new(void) { - return MAKE_FDSET(set_new(NULL)); -} - -int fdset_new_array(FDSet **ret, const int *fds, unsigned n_fds) { - unsigned i; - FDSet *s; - int r; - - assert(ret); - - s = fdset_new(); - if (!s) - return -ENOMEM; - - for (i = 0; i < n_fds; i++) { - - r = fdset_put(s, fds[i]); - if (r < 0) { - set_free(MAKE_SET(s)); - return r; - } - } - - *ret = s; - return 0; -} - -FDSet* fdset_free(FDSet *s) { - void *p; - - while ((p = set_steal_first(MAKE_SET(s)))) { - /* Valgrind's fd might have ended up in this set here, - * due to fdset_new_fill(). We'll ignore all failures - * here, so that the EBADFD that valgrind will return - * us on close() doesn't influence us */ - - /* When reloading duplicates of the private bus - * connection fds and suchlike are closed here, which - * has no effect at all, since they are only - * duplicates. So don't be surprised about these log - * messages. */ - - log_debug("Closing left-over fd %i", PTR_TO_FD(p)); - close_nointr(PTR_TO_FD(p)); - } - - set_free(MAKE_SET(s)); - return NULL; -} - -int fdset_put(FDSet *s, int fd) { - assert(s); - assert(fd >= 0); - - return set_put(MAKE_SET(s), FD_TO_PTR(fd)); -} - -int fdset_consume(FDSet *s, int fd) { - int r; - - assert(s); - assert(fd >= 0); - - r = fdset_put(s, fd); - if (r <= 0) - safe_close(fd); - - return r; -} - -int fdset_put_dup(FDSet *s, int fd) { - int copy, r; - - assert(s); - assert(fd >= 0); - - copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (copy < 0) - return -errno; - - r = fdset_put(s, copy); - if (r < 0) { - safe_close(copy); - return r; - } - - return copy; -} - -bool fdset_contains(FDSet *s, int fd) { - assert(s); - assert(fd >= 0); - - return !!set_get(MAKE_SET(s), FD_TO_PTR(fd)); -} - -int fdset_remove(FDSet *s, int fd) { - assert(s); - assert(fd >= 0); - - return set_remove(MAKE_SET(s), FD_TO_PTR(fd)) ? fd : -ENOENT; -} - -int fdset_new_fill(FDSet **_s) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r = 0; - FDSet *s; - - assert(_s); - - /* Creates an fdset and fills in all currently open file - * descriptors. */ - - d = opendir("/proc/self/fd"); - if (!d) - return -errno; - - s = fdset_new(); - if (!s) { - r = -ENOMEM; - goto finish; - } - - while ((de = readdir(d))) { - int fd = -1; - - if (hidden_file(de->d_name)) - continue; - - r = safe_atoi(de->d_name, &fd); - if (r < 0) - goto finish; - - if (fd < 3) - continue; - - if (fd == dirfd(d)) - continue; - - r = fdset_put(s, fd); - if (r < 0) - goto finish; - } - - r = 0; - *_s = s; - s = NULL; - -finish: - /* We won't close the fds here! */ - if (s) - set_free(MAKE_SET(s)); - - return r; -} - -int fdset_cloexec(FDSet *fds, bool b) { - Iterator i; - void *p; - int r; - - assert(fds); - - SET_FOREACH(p, MAKE_SET(fds), i) { - r = fd_cloexec(PTR_TO_FD(p), b); - if (r < 0) - return r; - } - - return 0; -} - -int fdset_new_listen_fds(FDSet **_s, bool unset) { - int n, fd, r; - FDSet *s; - - assert(_s); - - /* Creates an fdset and fills in all passed file descriptors */ - - s = fdset_new(); - if (!s) { - r = -ENOMEM; - goto fail; - } - - n = sd_listen_fds(unset); - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { - r = fdset_put(s, fd); - if (r < 0) - goto fail; - } - - *_s = s; - return 0; - - -fail: - if (s) - set_free(MAKE_SET(s)); - - return r; -} - -int fdset_close_others(FDSet *fds) { - void *e; - Iterator i; - int *a; - unsigned j, m; - - j = 0, m = fdset_size(fds); - a = alloca(sizeof(int) * m); - SET_FOREACH(e, MAKE_SET(fds), i) - a[j++] = PTR_TO_FD(e); - - assert(j == m); - - return close_all_fds(a, j); -} - -unsigned fdset_size(FDSet *fds) { - return set_size(MAKE_SET(fds)); -} - -bool fdset_isempty(FDSet *fds) { - return set_isempty(MAKE_SET(fds)); -} - -int fdset_iterate(FDSet *s, Iterator *i) { - void *p; - - if (!set_iterate(MAKE_SET(s), i, &p)) - return -ENOENT; - - return PTR_TO_FD(p); -} - -int fdset_steal_first(FDSet *fds) { - void *p; - - p = set_steal_first(MAKE_SET(fds)); - if (!p) - return -ENOENT; - - return PTR_TO_FD(p); -} diff --git a/src/basic/fdset.h b/src/basic/fdset.h deleted file mode 100644 index 12d0cef761..0000000000 --- a/src/basic/fdset.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "hashmap.h" -#include "macro.h" -#include "set.h" - -typedef struct FDSet FDSet; - -FDSet* fdset_new(void); -FDSet* fdset_free(FDSet *s); - -int fdset_put(FDSet *s, int fd); -int fdset_put_dup(FDSet *s, int fd); -int fdset_consume(FDSet *s, int fd); - -bool fdset_contains(FDSet *s, int fd); -int fdset_remove(FDSet *s, int fd); - -int fdset_new_array(FDSet **ret, const int *fds, unsigned n_fds); -int fdset_new_fill(FDSet **ret); -int fdset_new_listen_fds(FDSet **ret, bool unset); - -int fdset_cloexec(FDSet *fds, bool b); - -int fdset_close_others(FDSet *fds); - -unsigned fdset_size(FDSet *fds); -bool fdset_isempty(FDSet *fds); - -int fdset_iterate(FDSet *s, Iterator *i); - -int fdset_steal_first(FDSet *fds); - -#define FDSET_FOREACH(fd, fds, i) \ - for ((i) = ITERATOR_FIRST, (fd) = fdset_iterate((fds), &(i)); (fd) >= 0; (fd) = fdset_iterate((fds), &(i))) - -DEFINE_TRIVIAL_CLEANUP_FUNC(FDSet*, fdset_free); -#define _cleanup_fdset_free_ _cleanup_(fdset_freep) diff --git a/src/basic/fileio-label.c b/src/basic/fileio-label.c deleted file mode 100644 index 66dbc0fe1e..0000000000 --- a/src/basic/fileio-label.c +++ /dev/null @@ -1,68 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2010 Harald Hoyer - - 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 . -***/ - -#include - -#include "fileio-label.h" -#include "fileio.h" -#include "selinux-util.h" - -int write_string_file_atomic_label(const char *fn, const char *line) { - int r; - - r = mac_selinux_create_file_prepare(fn, S_IFREG); - if (r < 0) - return r; - - r = write_string_file(fn, line, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); - - mac_selinux_create_file_clear(); - - return r; -} - -int write_env_file_label(const char *fname, char **l) { - int r; - - r = mac_selinux_create_file_prepare(fname, S_IFREG); - if (r < 0) - return r; - - r = write_env_file(fname, l); - - mac_selinux_create_file_clear(); - - return r; -} - -int fopen_temporary_label(const char *target, - const char *path, FILE **f, char **temp_path) { - int r; - - r = mac_selinux_create_file_prepare(target, S_IFREG); - if (r < 0) - return r; - - r = fopen_temporary(path, f, temp_path); - - mac_selinux_create_file_clear(); - - return r; -} diff --git a/src/basic/fileio-label.h b/src/basic/fileio-label.h deleted file mode 100644 index fe7543013d..0000000000 --- a/src/basic/fileio-label.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2010 Harald Hoyer - - 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 . -***/ - -#include - -#include "fileio.h" - -int write_string_file_atomic_label(const char *fn, const char *line); -int write_env_file_label(const char *fname, char **l); -int fopen_temporary_label(const char *target, - const char *path, FILE **f, char **temp_path); diff --git a/src/basic/fileio.c b/src/basic/fileio.c deleted file mode 100644 index e43ca6d29e..0000000000 --- a/src/basic/fileio.c +++ /dev/null @@ -1,1280 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "ctype.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hexdecoct.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "random-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "umask-util.h" -#include "utf8.h" - -int write_string_stream(FILE *f, const char *line, bool enforce_newline) { - - assert(f); - assert(line); - - fputs(line, f); - if (enforce_newline && !endswith(line, "\n")) - fputc('\n', f); - - return fflush_and_check(f); -} - -static int write_string_file_atomic(const char *fn, const char *line, bool enforce_newline) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *p = NULL; - int r; - - assert(fn); - assert(line); - - r = fopen_temporary(fn, &f, &p); - if (r < 0) - return r; - - (void) fchmod_umask(fileno(f), 0644); - - r = write_string_stream(f, line, enforce_newline); - if (r >= 0) { - if (rename(p, fn) < 0) - r = -errno; - } - - if (r < 0) - (void) unlink(p); - - return r; -} - -int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) { - _cleanup_fclose_ FILE *f = NULL; - int q, r; - - assert(fn); - assert(line); - - if (flags & WRITE_STRING_FILE_ATOMIC) { - assert(flags & WRITE_STRING_FILE_CREATE); - - r = write_string_file_atomic(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); - if (r < 0) - goto fail; - - return r; - } - - if (flags & WRITE_STRING_FILE_CREATE) { - f = fopen(fn, "we"); - if (!f) { - r = -errno; - goto fail; - } - } else { - int fd; - - /* We manually build our own version of fopen(..., "we") that - * works without O_CREAT */ - fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - r = -errno; - goto fail; - } - - f = fdopen(fd, "we"); - if (!f) { - r = -errno; - safe_close(fd); - goto fail; - } - } - - r = write_string_stream(f, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); - if (r < 0) - goto fail; - - return 0; - -fail: - if (!(flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE)) - return r; - - f = safe_fclose(f); - - /* OK, the operation failed, but let's see if the right - * contents in place already. If so, eat up the error. */ - - q = verify_file(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); - if (q <= 0) - return r; - - return 0; -} - -int read_one_line_file(const char *fn, char **line) { - _cleanup_fclose_ FILE *f = NULL; - char t[LINE_MAX], *c; - - assert(fn); - assert(line); - - f = fopen(fn, "re"); - if (!f) - return -errno; - - if (!fgets(t, sizeof(t), f)) { - - if (ferror(f)) - return errno > 0 ? -errno : -EIO; - - t[0] = 0; - } - - c = strdup(t); - if (!c) - return -ENOMEM; - truncate_nl(c); - - *line = c; - return 0; -} - -int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *buf = NULL; - size_t l, k; - - assert(fn); - assert(blob); - - l = strlen(blob); - - if (accept_extra_nl && endswith(blob, "\n")) - accept_extra_nl = false; - - buf = malloc(l + accept_extra_nl + 1); - if (!buf) - return -ENOMEM; - - f = fopen(fn, "re"); - if (!f) - return -errno; - - /* We try to read one byte more than we need, so that we know whether we hit eof */ - errno = 0; - k = fread(buf, 1, l + accept_extra_nl + 1, f); - if (ferror(f)) - return errno > 0 ? -errno : -EIO; - - if (k != l && k != l + accept_extra_nl) - return 0; - if (memcmp(buf, blob, l) != 0) - return 0; - if (k > l && buf[l] != '\n') - return 0; - - return 1; -} - -int read_full_stream(FILE *f, char **contents, size_t *size) { - size_t n, l; - _cleanup_free_ char *buf = NULL; - struct stat st; - - assert(f); - assert(contents); - - if (fstat(fileno(f), &st) < 0) - return -errno; - - n = LINE_MAX; - - if (S_ISREG(st.st_mode)) { - - /* Safety check */ - if (st.st_size > 4*1024*1024) - return -E2BIG; - - /* Start with the right file size, but be prepared for - * files from /proc which generally report a file size - * of 0 */ - if (st.st_size > 0) - n = st.st_size; - } - - l = 0; - for (;;) { - char *t; - size_t k; - - t = realloc(buf, n+1); - if (!t) - return -ENOMEM; - - buf = t; - k = fread(buf + l, 1, n - l, f); - - if (k <= 0) { - if (ferror(f)) - return -errno; - - break; - } - - l += k; - n *= 2; - - /* Safety check */ - if (n > 4*1024*1024) - return -E2BIG; - } - - buf[l] = 0; - *contents = buf; - buf = NULL; /* do not free */ - - if (size) - *size = l; - - return 0; -} - -int read_full_file(const char *fn, char **contents, size_t *size) { - _cleanup_fclose_ FILE *f = NULL; - - assert(fn); - assert(contents); - - f = fopen(fn, "re"); - if (!f) - return -errno; - - return read_full_stream(f, contents, size); -} - -static int parse_env_file_internal( - FILE *f, - const char *fname, - const char *newline, - int (*push) (const char *filename, unsigned line, - const char *key, char *value, void *userdata, int *n_pushed), - void *userdata, - int *n_pushed) { - - _cleanup_free_ char *contents = NULL, *key = NULL; - size_t key_alloc = 0, n_key = 0, value_alloc = 0, n_value = 0, last_value_whitespace = (size_t) -1, last_key_whitespace = (size_t) -1; - char *p, *value = NULL; - int r; - unsigned line = 1; - - enum { - PRE_KEY, - KEY, - PRE_VALUE, - VALUE, - VALUE_ESCAPE, - SINGLE_QUOTE_VALUE, - SINGLE_QUOTE_VALUE_ESCAPE, - DOUBLE_QUOTE_VALUE, - DOUBLE_QUOTE_VALUE_ESCAPE, - COMMENT, - COMMENT_ESCAPE - } state = PRE_KEY; - - assert(newline); - - if (f) - r = read_full_stream(f, &contents, NULL); - else - r = read_full_file(fname, &contents, NULL); - if (r < 0) - return r; - - for (p = contents; *p; p++) { - char c = *p; - - switch (state) { - - case PRE_KEY: - if (strchr(COMMENTS, c)) - state = COMMENT; - else if (!strchr(WHITESPACE, c)) { - state = KEY; - last_key_whitespace = (size_t) -1; - - if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) { - r = -ENOMEM; - goto fail; - } - - key[n_key++] = c; - } - break; - - case KEY: - if (strchr(newline, c)) { - state = PRE_KEY; - line ++; - n_key = 0; - } else if (c == '=') { - state = PRE_VALUE; - last_value_whitespace = (size_t) -1; - } else { - if (!strchr(WHITESPACE, c)) - last_key_whitespace = (size_t) -1; - else if (last_key_whitespace == (size_t) -1) - last_key_whitespace = n_key; - - if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) { - r = -ENOMEM; - goto fail; - } - - key[n_key++] = c; - } - - break; - - case PRE_VALUE: - if (strchr(newline, c)) { - state = PRE_KEY; - line ++; - key[n_key] = 0; - - if (value) - value[n_value] = 0; - - /* strip trailing whitespace from key */ - if (last_key_whitespace != (size_t) -1) - key[last_key_whitespace] = 0; - - r = push(fname, line, key, value, userdata, n_pushed); - if (r < 0) - goto fail; - - n_key = 0; - value = NULL; - value_alloc = n_value = 0; - - } else if (c == '\'') - state = SINGLE_QUOTE_VALUE; - else if (c == '\"') - state = DOUBLE_QUOTE_VALUE; - else if (c == '\\') - state = VALUE_ESCAPE; - else if (!strchr(WHITESPACE, c)) { - state = VALUE; - - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - - break; - - case VALUE: - if (strchr(newline, c)) { - state = PRE_KEY; - line ++; - - key[n_key] = 0; - - if (value) - value[n_value] = 0; - - /* Chomp off trailing whitespace from value */ - if (last_value_whitespace != (size_t) -1) - value[last_value_whitespace] = 0; - - /* strip trailing whitespace from key */ - if (last_key_whitespace != (size_t) -1) - key[last_key_whitespace] = 0; - - r = push(fname, line, key, value, userdata, n_pushed); - if (r < 0) - goto fail; - - n_key = 0; - value = NULL; - value_alloc = n_value = 0; - - } else if (c == '\\') { - state = VALUE_ESCAPE; - last_value_whitespace = (size_t) -1; - } else { - if (!strchr(WHITESPACE, c)) - last_value_whitespace = (size_t) -1; - else if (last_value_whitespace == (size_t) -1) - last_value_whitespace = n_value; - - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - - break; - - case VALUE_ESCAPE: - state = VALUE; - - if (!strchr(newline, c)) { - /* Escaped newlines we eat up entirely */ - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - break; - - case SINGLE_QUOTE_VALUE: - if (c == '\'') - state = PRE_VALUE; - else if (c == '\\') - state = SINGLE_QUOTE_VALUE_ESCAPE; - else { - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - - break; - - case SINGLE_QUOTE_VALUE_ESCAPE: - state = SINGLE_QUOTE_VALUE; - - if (!strchr(newline, c)) { - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - break; - - case DOUBLE_QUOTE_VALUE: - if (c == '\"') - state = PRE_VALUE; - else if (c == '\\') - state = DOUBLE_QUOTE_VALUE_ESCAPE; - else { - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - - break; - - case DOUBLE_QUOTE_VALUE_ESCAPE: - state = DOUBLE_QUOTE_VALUE; - - if (!strchr(newline, c)) { - if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { - r = -ENOMEM; - goto fail; - } - - value[n_value++] = c; - } - break; - - case COMMENT: - if (c == '\\') - state = COMMENT_ESCAPE; - else if (strchr(newline, c)) { - state = PRE_KEY; - line ++; - } - break; - - case COMMENT_ESCAPE: - state = COMMENT; - break; - } - } - - if (state == PRE_VALUE || - state == VALUE || - state == VALUE_ESCAPE || - state == SINGLE_QUOTE_VALUE || - state == SINGLE_QUOTE_VALUE_ESCAPE || - state == DOUBLE_QUOTE_VALUE || - state == DOUBLE_QUOTE_VALUE_ESCAPE) { - - key[n_key] = 0; - - if (value) - value[n_value] = 0; - - if (state == VALUE) - if (last_value_whitespace != (size_t) -1) - value[last_value_whitespace] = 0; - - /* strip trailing whitespace from key */ - if (last_key_whitespace != (size_t) -1) - key[last_key_whitespace] = 0; - - r = push(fname, line, key, value, userdata, n_pushed); - if (r < 0) - goto fail; - } - - return 0; - -fail: - free(value); - return r; -} - -static int parse_env_file_push( - const char *filename, unsigned line, - const char *key, char *value, - void *userdata, - int *n_pushed) { - - const char *k; - va_list aq, *ap = userdata; - - if (!utf8_is_valid(key)) { - _cleanup_free_ char *p; - - p = utf8_escape_invalid(key); - log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.", strna(filename), line, p); - return -EINVAL; - } - - if (value && !utf8_is_valid(value)) { - _cleanup_free_ char *p; - - p = utf8_escape_invalid(value); - log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, p); - return -EINVAL; - } - - va_copy(aq, *ap); - - while ((k = va_arg(aq, const char *))) { - char **v; - - v = va_arg(aq, char **); - - if (streq(key, k)) { - va_end(aq); - free(*v); - *v = value; - - if (n_pushed) - (*n_pushed)++; - - return 1; - } - } - - va_end(aq); - free(value); - - return 0; -} - -int parse_env_file( - const char *fname, - const char *newline, ...) { - - va_list ap; - int r, n_pushed = 0; - - if (!newline) - newline = NEWLINE; - - va_start(ap, newline); - r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap, &n_pushed); - va_end(ap); - - return r < 0 ? r : n_pushed; -} - -static int load_env_file_push( - const char *filename, unsigned line, - const char *key, char *value, - void *userdata, - int *n_pushed) { - char ***m = userdata; - char *p; - int r; - - if (!utf8_is_valid(key)) { - _cleanup_free_ char *t = utf8_escape_invalid(key); - - log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t); - return -EINVAL; - } - - if (value && !utf8_is_valid(value)) { - _cleanup_free_ char *t = utf8_escape_invalid(value); - - log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t); - return -EINVAL; - } - - p = strjoin(key, "=", strempty(value), NULL); - if (!p) - return -ENOMEM; - - r = strv_consume(m, p); - if (r < 0) - return r; - - if (n_pushed) - (*n_pushed)++; - - free(value); - return 0; -} - -int load_env_file(FILE *f, const char *fname, const char *newline, char ***rl) { - char **m = NULL; - int r; - - if (!newline) - newline = NEWLINE; - - r = parse_env_file_internal(f, fname, newline, load_env_file_push, &m, NULL); - if (r < 0) { - strv_free(m); - return r; - } - - *rl = m; - return 0; -} - -static int load_env_file_push_pairs( - const char *filename, unsigned line, - const char *key, char *value, - void *userdata, - int *n_pushed) { - char ***m = userdata; - int r; - - if (!utf8_is_valid(key)) { - _cleanup_free_ char *t = utf8_escape_invalid(key); - - log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t); - return -EINVAL; - } - - if (value && !utf8_is_valid(value)) { - _cleanup_free_ char *t = utf8_escape_invalid(value); - - log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t); - return -EINVAL; - } - - r = strv_extend(m, key); - if (r < 0) - return -ENOMEM; - - if (!value) { - r = strv_extend(m, ""); - if (r < 0) - return -ENOMEM; - } else { - r = strv_push(m, value); - if (r < 0) - return r; - } - - if (n_pushed) - (*n_pushed)++; - - return 0; -} - -int load_env_file_pairs(FILE *f, const char *fname, const char *newline, char ***rl) { - char **m = NULL; - int r; - - if (!newline) - newline = NEWLINE; - - r = parse_env_file_internal(f, fname, newline, load_env_file_push_pairs, &m, NULL); - if (r < 0) { - strv_free(m); - return r; - } - - *rl = m; - return 0; -} - -static void write_env_var(FILE *f, const char *v) { - const char *p; - - p = strchr(v, '='); - if (!p) { - /* Fallback */ - fputs(v, f); - fputc('\n', f); - return; - } - - p++; - fwrite(v, 1, p-v, f); - - if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) { - fputc('\"', f); - - for (; *p; p++) { - if (strchr(SHELL_NEED_ESCAPE, *p)) - fputc('\\', f); - - fputc(*p, f); - } - - fputc('\"', f); - } else - fputs(p, f); - - fputc('\n', f); -} - -int write_env_file(const char *fname, char **l) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *p = NULL; - char **i; - int r; - - assert(fname); - - r = fopen_temporary(fname, &f, &p); - if (r < 0) - return r; - - fchmod_umask(fileno(f), 0644); - - STRV_FOREACH(i, l) - write_env_var(f, *i); - - r = fflush_and_check(f); - if (r >= 0) { - if (rename(p, fname) >= 0) - return 0; - - r = -errno; - } - - unlink(p); - return r; -} - -int executable_is_script(const char *path, char **interpreter) { - int r; - _cleanup_free_ char *line = NULL; - int len; - char *ans; - - assert(path); - - r = read_one_line_file(path, &line); - if (r < 0) - return r; - - if (!startswith(line, "#!")) - return 0; - - ans = strstrip(line + 2); - len = strcspn(ans, " \t"); - - if (len == 0) - return 0; - - ans = strndup(ans, len); - if (!ans) - return -ENOMEM; - - *interpreter = ans; - return 1; -} - -/** - * Retrieve one field from a file like /proc/self/status. pattern - * should not include whitespace or the delimiter (':'). pattern matches only - * the beginning of a line. Whitespace before ':' is skipped. Whitespace and - * zeros after the ':' will be skipped. field must be freed afterwards. - * terminator specifies the terminating characters of the field value (not - * included in the value). - */ -int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field) { - _cleanup_free_ char *status = NULL; - char *t, *f; - size_t len; - int r; - - assert(terminator); - assert(filename); - assert(pattern); - assert(field); - - r = read_full_file(filename, &status, NULL); - if (r < 0) - return r; - - t = status; - - do { - bool pattern_ok; - - do { - t = strstr(t, pattern); - if (!t) - return -ENOENT; - - /* Check that pattern occurs in beginning of line. */ - pattern_ok = (t == status || t[-1] == '\n'); - - t += strlen(pattern); - - } while (!pattern_ok); - - t += strspn(t, " \t"); - if (!*t) - return -ENOENT; - - } while (*t != ':'); - - t++; - - if (*t) { - t += strspn(t, " \t"); - - /* Also skip zeros, because when this is used for - * capabilities, we don't want the zeros. This way the - * same capability set always maps to the same string, - * irrespective of the total capability set size. For - * other numbers it shouldn't matter. */ - t += strspn(t, "0"); - /* Back off one char if there's nothing but whitespace - and zeros */ - if (!*t || isspace(*t)) - t --; - } - - len = strcspn(t, terminator); - - f = strndup(t, len); - if (!f) - return -ENOMEM; - - *field = f; - return 0; -} - -DIR *xopendirat(int fd, const char *name, int flags) { - int nfd; - DIR *d; - - assert(!(flags & O_CREAT)); - - nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags, 0); - if (nfd < 0) - return NULL; - - d = fdopendir(nfd); - if (!d) { - safe_close(nfd); - return NULL; - } - - return d; -} - -static int search_and_fopen_internal(const char *path, const char *mode, const char *root, char **search, FILE **_f) { - char **i; - - assert(path); - assert(mode); - assert(_f); - - if (!path_strv_resolve_uniq(search, root)) - return -ENOMEM; - - STRV_FOREACH(i, search) { - _cleanup_free_ char *p = NULL; - FILE *f; - - if (root) - p = strjoin(root, *i, "/", path, NULL); - else - p = strjoin(*i, "/", path, NULL); - if (!p) - return -ENOMEM; - - f = fopen(p, mode); - if (f) { - *_f = f; - return 0; - } - - if (errno != ENOENT) - return -errno; - } - - return -ENOENT; -} - -int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f) { - _cleanup_strv_free_ char **copy = NULL; - - assert(path); - assert(mode); - assert(_f); - - if (path_is_absolute(path)) { - FILE *f; - - f = fopen(path, mode); - if (f) { - *_f = f; - return 0; - } - - return -errno; - } - - copy = strv_copy((char**) search); - if (!copy) - return -ENOMEM; - - return search_and_fopen_internal(path, mode, root, copy, _f); -} - -int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f) { - _cleanup_strv_free_ char **s = NULL; - - if (path_is_absolute(path)) { - FILE *f; - - f = fopen(path, mode); - if (f) { - *_f = f; - return 0; - } - - return -errno; - } - - s = strv_split_nulstr(search); - if (!s) - return -ENOMEM; - - return search_and_fopen_internal(path, mode, root, s, _f); -} - -int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { - FILE *f; - char *t; - int r, fd; - - assert(path); - assert(_f); - assert(_temp_path); - - r = tempfn_xxxxxx(path, NULL, &t); - if (r < 0) - return r; - - fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC); - if (fd < 0) { - free(t); - return -errno; - } - - f = fdopen(fd, "we"); - if (!f) { - unlink_noerrno(t); - free(t); - safe_close(fd); - return -errno; - } - - *_f = f; - *_temp_path = t; - - return 0; -} - -int fflush_and_check(FILE *f) { - assert(f); - - errno = 0; - fflush(f); - - if (ferror(f)) - return errno > 0 ? -errno : -EIO; - - return 0; -} - -/* This is much like like mkostemp() but is subject to umask(). */ -int mkostemp_safe(char *pattern, int flags) { - _cleanup_umask_ mode_t u; - int fd; - - assert(pattern); - - u = umask(077); - - fd = mkostemp(pattern, flags); - if (fd < 0) - return -errno; - - return fd; -} - -int open_tmpfile(const char *path, int flags) { - char *p; - int fd; - - assert(path); - -#ifdef O_TMPFILE - /* Try O_TMPFILE first, if it is supported */ - fd = open(path, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR); - if (fd >= 0) - return fd; -#endif - - /* Fall back to unguessable name + unlinking */ - p = strjoina(path, "/systemd-tmp-XXXXXX"); - - fd = mkostemp_safe(p, flags); - if (fd < 0) - return fd; - - unlink(p); - return fd; -} - -int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { - const char *fn; - char *t; - - assert(p); - assert(ret); - - /* - * Turns this: - * /foo/bar/waldo - * - * Into this: - * /foo/bar/.#waldoXXXXXX - */ - - fn = basename(p); - if (!filename_is_valid(fn)) - return -EINVAL; - - if (extra == NULL) - extra = ""; - - t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1); - if (!t) - return -ENOMEM; - - strcpy(stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn), "XXXXXX"); - - *ret = path_kill_slashes(t); - return 0; -} - -int tempfn_random(const char *p, const char *extra, char **ret) { - const char *fn; - char *t, *x; - uint64_t u; - unsigned i; - - assert(p); - assert(ret); - - /* - * Turns this: - * /foo/bar/waldo - * - * Into this: - * /foo/bar/.#waldobaa2a261115984a9 - */ - - fn = basename(p); - if (!filename_is_valid(fn)) - return -EINVAL; - - if (!extra) - extra = ""; - - t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1); - if (!t) - return -ENOMEM; - - x = stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn); - - u = random_u64(); - for (i = 0; i < 16; i++) { - *(x++) = hexchar(u & 0xF); - u >>= 4; - } - - *x = 0; - - *ret = path_kill_slashes(t); - return 0; -} - -int tempfn_random_child(const char *p, const char *extra, char **ret) { - char *t, *x; - uint64_t u; - unsigned i; - - assert(p); - assert(ret); - - /* Turns this: - * /foo/bar/waldo - * Into this: - * /foo/bar/waldo/.#3c2b6219aa75d7d0 - */ - - if (!extra) - extra = ""; - - t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1); - if (!t) - return -ENOMEM; - - x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra); - - u = random_u64(); - for (i = 0; i < 16; i++) { - *(x++) = hexchar(u & 0xF); - u >>= 4; - } - - *x = 0; - - *ret = path_kill_slashes(t); - return 0; -} - -int write_timestamp_file_atomic(const char *fn, usec_t n) { - char ln[DECIMAL_STR_MAX(n)+2]; - - /* Creates a "timestamp" file, that contains nothing but a - * usec_t timestamp, formatted in ASCII. */ - - if (n <= 0 || n >= USEC_INFINITY) - return -ERANGE; - - xsprintf(ln, USEC_FMT "\n", n); - - return write_string_file(fn, ln, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); -} - -int read_timestamp_file(const char *fn, usec_t *ret) { - _cleanup_free_ char *ln = NULL; - uint64_t t; - int r; - - r = read_one_line_file(fn, &ln); - if (r < 0) - return r; - - r = safe_atou64(ln, &t); - if (r < 0) - return r; - - if (t <= 0 || t >= (uint64_t) USEC_INFINITY) - return -ERANGE; - - *ret = (usec_t) t; - return 0; -} - -int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) { - int r; - - assert(s); - - /* Outputs the specified string with fputs(), but optionally prefixes it with a separator. The *space parameter - * when specified shall initially point to a boolean variable initialized to false. It is set to true after the - * first invocation. This call is supposed to be use in loops, where a separator shall be inserted between each - * element, but not before the first one. */ - - if (!f) - f = stdout; - - if (space) { - if (!separator) - separator = " "; - - if (*space) { - r = fputs(separator, f); - if (r < 0) - return r; - } - - *space = true; - } - - return fputs(s, f); -} diff --git a/src/basic/fileio.h b/src/basic/fileio.h deleted file mode 100644 index 8084895ff3..0000000000 --- a/src/basic/fileio.h +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "macro.h" -#include "time-util.h" - -typedef enum { - WRITE_STRING_FILE_CREATE = 1, - WRITE_STRING_FILE_ATOMIC = 2, - WRITE_STRING_FILE_AVOID_NEWLINE = 4, - WRITE_STRING_FILE_VERIFY_ON_FAILURE = 8, -} WriteStringFileFlags; - -int write_string_stream(FILE *f, const char *line, bool enforce_newline); -int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags); - -int read_one_line_file(const char *fn, char **line); -int read_full_file(const char *fn, char **contents, size_t *size); -int read_full_stream(FILE *f, char **contents, size_t *size); - -int verify_file(const char *fn, const char *blob, bool accept_extra_nl); - -int parse_env_file(const char *fname, const char *separator, ...) _sentinel_; -int load_env_file(FILE *f, const char *fname, const char *separator, char ***l); -int load_env_file_pairs(FILE *f, const char *fname, const char *separator, char ***l); - -int write_env_file(const char *fname, char **l); - -int executable_is_script(const char *path, char **interpreter); - -int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field); - -DIR *xopendirat(int dirfd, const char *name, int flags); - -int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f); -int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f); - -#define FOREACH_LINE(line, f, on_error) \ - for (;;) \ - if (!fgets(line, sizeof(line), f)) { \ - if (ferror(f)) { \ - on_error; \ - } \ - break; \ - } else - -int fflush_and_check(FILE *f); - -int fopen_temporary(const char *path, FILE **_f, char **_temp_path); -int mkostemp_safe(char *pattern, int flags); -int open_tmpfile(const char *path, int flags); - -int tempfn_xxxxxx(const char *p, const char *extra, char **ret); -int tempfn_random(const char *p, const char *extra, char **ret); -int tempfn_random_child(const char *p, const char *extra, char **ret); - -int write_timestamp_file_atomic(const char *fn, usec_t n); -int read_timestamp_file(const char *fn, usec_t *ret); - -int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space); diff --git a/src/basic/formats-util.h b/src/basic/formats-util.h deleted file mode 100644 index ce516b117d..0000000000 --- a/src/basic/formats-util.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Ronny Chevalier - - 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 . -***/ - -#include - -#if SIZEOF_PID_T == 4 -# define PID_PRI PRIi32 -#elif SIZEOF_PID_T == 2 -# define PID_PRI PRIi16 -#else -# error Unknown pid_t size -#endif -#define PID_FMT "%" PID_PRI - -#if SIZEOF_UID_T == 4 -# define UID_FMT "%" PRIu32 -#elif SIZEOF_UID_T == 2 -# define UID_FMT "%" PRIu16 -#else -# error Unknown uid_t size -#endif - -#if SIZEOF_GID_T == 4 -# define GID_FMT "%" PRIu32 -#elif SIZEOF_GID_T == 2 -# define GID_FMT "%" PRIu16 -#else -# error Unknown gid_t size -#endif - -#if SIZEOF_TIME_T == 8 -# define PRI_TIME PRIi64 -#elif SIZEOF_TIME_T == 4 -# define PRI_TIME PRIu32 -#else -# error Unknown time_t size -#endif - -#if SIZEOF_RLIM_T == 8 -# define RLIM_FMT "%" PRIu64 -#elif SIZEOF_RLIM_T == 4 -# define RLIM_FMT "%" PRIu32 -#else -# error Unknown rlim_t size -#endif diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c deleted file mode 100644 index 3ef1b90edd..0000000000 --- a/src/basic/fs-util.c +++ /dev/null @@ -1,513 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-util.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "user-util.h" -#include "util.h" - -int unlink_noerrno(const char *path) { - PROTECT_ERRNO; - int r; - - r = unlink(path); - if (r < 0) - return -errno; - - return 0; -} - -int rmdir_parents(const char *path, const char *stop) { - size_t l; - int r = 0; - - assert(path); - assert(stop); - - l = strlen(path); - - /* Skip trailing slashes */ - while (l > 0 && path[l-1] == '/') - l--; - - while (l > 0) { - char *t; - - /* Skip last component */ - while (l > 0 && path[l-1] != '/') - l--; - - /* Skip trailing slashes */ - while (l > 0 && path[l-1] == '/') - l--; - - if (l <= 0) - break; - - t = strndup(path, l); - if (!t) - return -ENOMEM; - - if (path_startswith(stop, t)) { - free(t); - return 0; - } - - r = rmdir(t); - free(t); - - if (r < 0) - if (errno != ENOENT) - return -errno; - } - - return 0; -} - - -int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { - struct stat buf; - int ret; - - ret = renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE); - if (ret >= 0) - return 0; - - /* renameat2() exists since Linux 3.15, btrfs added support for it later. - * If it is not implemented, fallback to another method. */ - if (!IN_SET(errno, EINVAL, ENOSYS)) - return -errno; - - /* The link()/unlink() fallback does not work on directories. But - * renameat() without RENAME_NOREPLACE gives the same semantics on - * directories, except when newpath is an *empty* directory. This is - * good enough. */ - ret = fstatat(olddirfd, oldpath, &buf, AT_SYMLINK_NOFOLLOW); - if (ret >= 0 && S_ISDIR(buf.st_mode)) { - ret = renameat(olddirfd, oldpath, newdirfd, newpath); - return ret >= 0 ? 0 : -errno; - } - - /* If it is not a directory, use the link()/unlink() fallback. */ - ret = linkat(olddirfd, oldpath, newdirfd, newpath, 0); - if (ret < 0) - return -errno; - - ret = unlinkat(olddirfd, oldpath, 0); - if (ret < 0) { - /* backup errno before the following unlinkat() alters it */ - ret = errno; - (void) unlinkat(newdirfd, newpath, 0); - errno = ret; - return -errno; - } - - return 0; -} - -int readlinkat_malloc(int fd, const char *p, char **ret) { - size_t l = 100; - int r; - - assert(p); - assert(ret); - - for (;;) { - char *c; - ssize_t n; - - c = new(char, l); - if (!c) - return -ENOMEM; - - n = readlinkat(fd, p, c, l-1); - if (n < 0) { - r = -errno; - free(c); - return r; - } - - if ((size_t) n < l-1) { - c[n] = 0; - *ret = c; - return 0; - } - - free(c); - l *= 2; - } -} - -int readlink_malloc(const char *p, char **ret) { - return readlinkat_malloc(AT_FDCWD, p, ret); -} - -int readlink_value(const char *p, char **ret) { - _cleanup_free_ char *link = NULL; - char *value; - int r; - - r = readlink_malloc(p, &link); - if (r < 0) - return r; - - value = basename(link); - if (!value) - return -ENOENT; - - value = strdup(value); - if (!value) - return -ENOMEM; - - *ret = value; - - return 0; -} - -int readlink_and_make_absolute(const char *p, char **r) { - _cleanup_free_ char *target = NULL; - char *k; - int j; - - assert(p); - assert(r); - - j = readlink_malloc(p, &target); - if (j < 0) - return j; - - k = file_in_same_dir(p, target); - if (!k) - return -ENOMEM; - - *r = k; - return 0; -} - -int readlink_and_canonicalize(const char *p, char **r) { - char *t, *s; - int j; - - assert(p); - assert(r); - - j = readlink_and_make_absolute(p, &t); - if (j < 0) - return j; - - s = canonicalize_file_name(t); - if (s) { - free(t); - *r = s; - } else - *r = t; - - path_kill_slashes(*r); - - return 0; -} - -int readlink_and_make_absolute_root(const char *root, const char *path, char **ret) { - _cleanup_free_ char *target = NULL, *t = NULL; - const char *full; - int r; - - full = prefix_roota(root, path); - r = readlink_malloc(full, &target); - if (r < 0) - return r; - - t = file_in_same_dir(path, target); - if (!t) - return -ENOMEM; - - *ret = t; - t = NULL; - - return 0; -} - -int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { - assert(path); - - /* Under the assumption that we are running privileged we - * first change the access mode and only then hand out - * ownership to avoid a window where access is too open. */ - - if (mode != MODE_INVALID) - if (chmod(path, mode) < 0) - return -errno; - - if (uid != UID_INVALID || gid != GID_INVALID) - if (chown(path, uid, gid) < 0) - return -errno; - - return 0; -} - -int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid) { - assert(fd >= 0); - - /* Under the assumption that we are running privileged we - * first change the access mode and only then hand out - * ownership to avoid a window where access is too open. */ - - if (mode != MODE_INVALID) - if (fchmod(fd, mode) < 0) - return -errno; - - if (uid != UID_INVALID || gid != GID_INVALID) - if (fchown(fd, uid, gid) < 0) - return -errno; - - return 0; -} - -int fchmod_umask(int fd, mode_t m) { - mode_t u; - int r; - - u = umask(0777); - r = fchmod(fd, m & (~u)) < 0 ? -errno : 0; - umask(u); - - return r; -} - -int fd_warn_permissions(const char *path, int fd) { - struct stat st; - - if (fstat(fd, &st) < 0) - return -errno; - - if (st.st_mode & 0111) - log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path); - - if (st.st_mode & 0002) - log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path); - - if (getpid() == 1 && (st.st_mode & 0044) != 0044) - log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path); - - return 0; -} - -int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) { - _cleanup_close_ int fd; - int r; - - assert(path); - - if (parents) - mkdir_parents(path, 0755); - - fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, - (mode == 0 || mode == MODE_INVALID) ? 0644 : mode); - if (fd < 0) - return -errno; - - if (mode != MODE_INVALID) { - r = fchmod(fd, mode); - if (r < 0) - return -errno; - } - - if (uid != UID_INVALID || gid != GID_INVALID) { - r = fchown(fd, uid, gid); - if (r < 0) - return -errno; - } - - if (stamp != USEC_INFINITY) { - struct timespec ts[2]; - - timespec_store(&ts[0], stamp); - ts[1] = ts[0]; - r = futimens(fd, ts); - } else - r = futimens(fd, NULL); - if (r < 0) - return -errno; - - return 0; -} - -int touch(const char *path) { - return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID); -} - -int symlink_idempotent(const char *from, const char *to) { - _cleanup_free_ char *p = NULL; - int r; - - assert(from); - assert(to); - - if (symlink(from, to) < 0) { - if (errno != EEXIST) - return -errno; - - r = readlink_malloc(to, &p); - if (r < 0) - return r; - - if (!streq(p, from)) - return -EINVAL; - } - - return 0; -} - -int symlink_atomic(const char *from, const char *to) { - _cleanup_free_ char *t = NULL; - int r; - - assert(from); - assert(to); - - r = tempfn_random(to, NULL, &t); - if (r < 0) - return r; - - if (symlink(from, t) < 0) - return -errno; - - if (rename(t, to) < 0) { - unlink_noerrno(t); - return -errno; - } - - return 0; -} - -int mknod_atomic(const char *path, mode_t mode, dev_t dev) { - _cleanup_free_ char *t = NULL; - int r; - - assert(path); - - r = tempfn_random(path, NULL, &t); - if (r < 0) - return r; - - if (mknod(t, mode, dev) < 0) - return -errno; - - if (rename(t, path) < 0) { - unlink_noerrno(t); - return -errno; - } - - return 0; -} - -int mkfifo_atomic(const char *path, mode_t mode) { - _cleanup_free_ char *t = NULL; - int r; - - assert(path); - - r = tempfn_random(path, NULL, &t); - if (r < 0) - return r; - - if (mkfifo(t, mode) < 0) - return -errno; - - if (rename(t, path) < 0) { - unlink_noerrno(t); - return -errno; - } - - return 0; -} - -int get_files_in_directory(const char *path, char ***list) { - _cleanup_closedir_ DIR *d = NULL; - size_t bufsize = 0, n = 0; - _cleanup_strv_free_ char **l = NULL; - - assert(path); - - /* Returns all files in a directory in *list, and the number - * of files as return value. If list is NULL returns only the - * number. */ - - d = opendir(path); - if (!d) - return -errno; - - for (;;) { - struct dirent *de; - - errno = 0; - de = readdir(d); - if (!de && errno > 0) - return -errno; - if (!de) - break; - - dirent_ensure_type(d, de); - - if (!dirent_is_file(de)) - continue; - - if (list) { - /* one extra slot is needed for the terminating NULL */ - if (!GREEDY_REALLOC(l, bufsize, n + 2)) - return -ENOMEM; - - l[n] = strdup(de->d_name); - if (!l[n]) - return -ENOMEM; - - l[++n] = NULL; - } else - n++; - } - - if (list) { - *list = l; - l = NULL; /* avoid freeing */ - } - - return n; -} diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h deleted file mode 100644 index 0e2fcb21b9..0000000000 --- a/src/basic/fs-util.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "time-util.h" - -int unlink_noerrno(const char *path); - -int rmdir_parents(const char *path, const char *stop); - -int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); - -int readlinkat_malloc(int fd, const char *p, char **ret); -int readlink_malloc(const char *p, char **r); -int readlink_value(const char *p, char **ret); -int readlink_and_make_absolute(const char *p, char **r); -int readlink_and_canonicalize(const char *p, char **r); -int readlink_and_make_absolute_root(const char *root, const char *path, char **ret); - -int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); -int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid); - -int fchmod_umask(int fd, mode_t mode); - -int fd_warn_permissions(const char *path, int fd); - -#define laccess(path, mode) faccessat(AT_FDCWD, (path), (mode), AT_SYMLINK_NOFOLLOW) - -int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode); -int touch(const char *path); - -int symlink_idempotent(const char *from, const char *to); - -int symlink_atomic(const char *from, const char *to); -int mknod_atomic(const char *path, mode_t mode, dev_t dev); -int mkfifo_atomic(const char *path, mode_t mode); - -int get_files_in_directory(const char *path, char ***list); - -#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1) - -#define FOREACH_INOTIFY_EVENT(e, buffer, sz) \ - for ((e) = &buffer.ev; \ - (uint8_t*) (e) < (uint8_t*) (buffer.raw) + (sz); \ - (e) = (struct inotify_event*) ((uint8_t*) (e) + sizeof(struct inotify_event) + (e)->len)) - -union inotify_event_buffer { - struct inotify_event ev; - uint8_t raw[INOTIFY_EVENT_MAX]; -}; diff --git a/src/basic/glob-util.c b/src/basic/glob-util.c deleted file mode 100644 index 007198c269..0000000000 --- a/src/basic/glob-util.c +++ /dev/null @@ -1,70 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "glob-util.h" -#include "macro.h" -#include "strv.h" - -int glob_exists(const char *path) { - _cleanup_globfree_ glob_t g = {}; - int k; - - assert(path); - - errno = 0; - k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); - - if (k == GLOB_NOMATCH) - return 0; - if (k == GLOB_NOSPACE) - return -ENOMEM; - if (k != 0) - return errno > 0 ? -errno : -EIO; - - return !strv_isempty(g.gl_pathv); -} - -int glob_extend(char ***strv, const char *path) { - _cleanup_globfree_ glob_t g = {}; - int k; - char **p; - - errno = 0; - k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); - - if (k == GLOB_NOMATCH) - return -ENOENT; - if (k == GLOB_NOSPACE) - return -ENOMEM; - if (k != 0) - return errno > 0 ? -errno : -EIO; - if (strv_isempty(g.gl_pathv)) - return -ENOENT; - - STRV_FOREACH(p, g.gl_pathv) { - k = strv_extend(strv, *p); - if (k < 0) - return k; - } - - return 0; -} diff --git a/src/basic/glob-util.h b/src/basic/glob-util.h deleted file mode 100644 index 5d8fb47a26..0000000000 --- a/src/basic/glob-util.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "macro.h" -#include "string-util.h" - -int glob_exists(const char *path); -int glob_extend(char ***strv, const char *path); - -#define _cleanup_globfree_ _cleanup_(globfree) - -_pure_ static inline bool string_is_glob(const char *p) { - /* Check if a string contains any glob patterns. */ - return !!strpbrk(p, GLOB_CHARS); -} diff --git a/src/basic/gunicode.c b/src/basic/gunicode.c deleted file mode 100644 index 542110503f..0000000000 --- a/src/basic/gunicode.c +++ /dev/null @@ -1,112 +0,0 @@ -/* gunicode.c - Unicode manipulation functions - * - * Copyright (C) 1999, 2000 Tom Tromey - * Copyright 2000, 2005 Red Hat, Inc. - */ - -#include - -#include "gunicode.h" - -#define unichar uint32_t - -/** - * g_utf8_prev_char: - * @p: a pointer to a position within a UTF-8 encoded string - * - * Finds the previous UTF-8 character in the string before @p. - * - * @p does not have to be at the beginning of a UTF-8 character. No check - * is made to see if the character found is actually valid other than - * it starts with an appropriate byte. If @p might be the first - * character of the string, you must use g_utf8_find_prev_char() instead. - * - * Return value: a pointer to the found character. - **/ -char * -utf8_prev_char (const char *p) -{ - while (1) - { - p--; - if ((*p & 0xc0) != 0x80) - return (char *)p; - } -} - -struct Interval -{ - unichar start, end; -}; - -static int -interval_compare (const void *key, const void *elt) -{ - unichar c = (unichar) (long) (key); - struct Interval *interval = (struct Interval *)elt; - - if (c < interval->start) - return -1; - if (c > interval->end) - return +1; - - return 0; -} - -/* - * NOTE: - * - * The tables for g_unichar_iswide() and g_unichar_iswide_cjk() are - * generated from the Unicode Character Database's file - * extracted/DerivedEastAsianWidth.txt using the gen-iswide-table.py - * in this way: - * - * ./gen-iswide-table.py < path/to/ucd/extracted/DerivedEastAsianWidth.txt | fmt - * - * Last update for Unicode 6.0. - */ - -/** - * g_unichar_iswide: - * @c: a Unicode character - * - * Determines if a character is typically rendered in a double-width - * cell. - * - * Return value: %TRUE if the character is wide - **/ -bool -unichar_iswide (unichar c) -{ - /* See NOTE earlier for how to update this table. */ - static const struct Interval wide[] = { - {0x1100, 0x115F}, {0x2329, 0x232A}, {0x2E80, 0x2E99}, {0x2E9B, 0x2EF3}, - {0x2F00, 0x2FD5}, {0x2FF0, 0x2FFB}, {0x3000, 0x303E}, {0x3041, 0x3096}, - {0x3099, 0x30FF}, {0x3105, 0x312D}, {0x3131, 0x318E}, {0x3190, 0x31BA}, - {0x31C0, 0x31E3}, {0x31F0, 0x321E}, {0x3220, 0x3247}, {0x3250, 0x32FE}, - {0x3300, 0x4DBF}, {0x4E00, 0xA48C}, {0xA490, 0xA4C6}, {0xA960, 0xA97C}, - {0xAC00, 0xD7A3}, {0xF900, 0xFAFF}, {0xFE10, 0xFE19}, {0xFE30, 0xFE52}, - {0xFE54, 0xFE66}, {0xFE68, 0xFE6B}, {0xFF01, 0xFF60}, {0xFFE0, 0xFFE6}, - {0x1B000, 0x1B001}, {0x1F200, 0x1F202}, {0x1F210, 0x1F23A}, - {0x1F240, 0x1F248}, {0x1F250, 0x1F251}, - {0x1F300, 0x1F567}, /* Miscellaneous Symbols and Pictographs */ - {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD}, - }; - - if (bsearch ((void *)(uintptr_t)c, wide, (sizeof (wide) / sizeof ((wide)[0])), sizeof wide[0], - interval_compare)) - return true; - - return false; -} - -const char utf8_skip_data[256] = { - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, - 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1 -}; diff --git a/src/basic/gunicode.h b/src/basic/gunicode.h deleted file mode 100644 index b03aa43160..0000000000 --- a/src/basic/gunicode.h +++ /dev/null @@ -1,30 +0,0 @@ -/* gunicode.h - Unicode manipulation functions - * - * Copyright (C) 1999, 2000 Tom Tromey - * Copyright 2000, 2005 Red Hat, Inc. - */ - -#pragma once - -#include -#include -#include - -char *utf8_prev_char (const char *p); - -extern const char utf8_skip_data[256]; - -/** - * g_utf8_next_char: - * @p: Pointer to the start of a valid UTF-8 character - * - * Skips to the next character in a UTF-8 string. The string must be - * valid; this macro is as fast as possible, and has no error-checking. - * You would use this macro to iterate over a string character by - * character. The macro returns the start of the next UTF-8 character. - * Before using this macro, use g_utf8_validate() to validate strings - * that may contain invalid UTF-8. - */ -#define utf8_next_char(p) (char *)((p) + utf8_skip_data[*(const unsigned char *)(p)]) - -bool unichar_iswide (uint32_t c); diff --git a/src/basic/hash-funcs.c b/src/basic/hash-funcs.c deleted file mode 100644 index c3a4a011b5..0000000000 --- a/src/basic/hash-funcs.c +++ /dev/null @@ -1,81 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2014 Michal Schmidt - - 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 . -***/ - -#include "hash-funcs.h" - -void string_hash_func(const void *p, struct siphash *state) { - siphash24_compress(p, strlen(p) + 1, state); -} - -int string_compare_func(const void *a, const void *b) { - return strcmp(a, b); -} - -const struct hash_ops string_hash_ops = { - .hash = string_hash_func, - .compare = string_compare_func -}; - -void trivial_hash_func(const void *p, struct siphash *state) { - siphash24_compress(&p, sizeof(p), state); -} - -int trivial_compare_func(const void *a, const void *b) { - return a < b ? -1 : (a > b ? 1 : 0); -} - -const struct hash_ops trivial_hash_ops = { - .hash = trivial_hash_func, - .compare = trivial_compare_func -}; - -void uint64_hash_func(const void *p, struct siphash *state) { - siphash24_compress(p, sizeof(uint64_t), state); -} - -int uint64_compare_func(const void *_a, const void *_b) { - uint64_t a, b; - a = *(const uint64_t*) _a; - b = *(const uint64_t*) _b; - return a < b ? -1 : (a > b ? 1 : 0); -} - -const struct hash_ops uint64_hash_ops = { - .hash = uint64_hash_func, - .compare = uint64_compare_func -}; - -#if SIZEOF_DEV_T != 8 -void devt_hash_func(const void *p, struct siphash *state) { - siphash24_compress(p, sizeof(dev_t), state); -} - -int devt_compare_func(const void *_a, const void *_b) { - dev_t a, b; - a = *(const dev_t*) _a; - b = *(const dev_t*) _b; - return a < b ? -1 : (a > b ? 1 : 0); -} - -const struct hash_ops devt_hash_ops = { - .hash = devt_hash_func, - .compare = devt_compare_func -}; -#endif diff --git a/src/basic/hash-funcs.h b/src/basic/hash-funcs.h deleted file mode 100644 index 299189d143..0000000000 --- a/src/basic/hash-funcs.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2014 Michal Schmidt - - 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 . -***/ - -#include "macro.h" -#include "siphash24.h" - -typedef void (*hash_func_t)(const void *p, struct siphash *state); -typedef int (*compare_func_t)(const void *a, const void *b); - -struct hash_ops { - hash_func_t hash; - compare_func_t compare; -}; - -void string_hash_func(const void *p, struct siphash *state); -int string_compare_func(const void *a, const void *b) _pure_; -extern const struct hash_ops string_hash_ops; - -/* This will compare the passed pointers directly, and will not - * dereference them. This is hence not useful for strings or - * suchlike. */ -void trivial_hash_func(const void *p, struct siphash *state); -int trivial_compare_func(const void *a, const void *b) _const_; -extern const struct hash_ops trivial_hash_ops; - -/* 32bit values we can always just embed in the pointer itself, but - * in order to support 32bit archs we need store 64bit values - * indirectly, since they don't fit in a pointer. */ -void uint64_hash_func(const void *p, struct siphash *state); -int uint64_compare_func(const void *a, const void *b) _pure_; -extern const struct hash_ops uint64_hash_ops; - -/* On some archs dev_t is 32bit, and on others 64bit. And sometimes - * it's 64bit on 32bit archs, and sometimes 32bit on 64bit archs. Yuck! */ -#if SIZEOF_DEV_T != 8 -void devt_hash_func(const void *p, struct siphash *state) _pure_; -int devt_compare_func(const void *a, const void *b) _pure_; -extern const struct hash_ops devt_hash_ops = { - .hash = devt_hash_func, - .compare = devt_compare_func -}; -#else -#define devt_hash_func uint64_hash_func -#define devt_compare_func uint64_compare_func -#define devt_hash_ops uint64_hash_ops -#endif diff --git a/src/basic/hashmap.c b/src/basic/hashmap.c deleted file mode 100644 index 6f1a049d47..0000000000 --- a/src/basic/hashmap.c +++ /dev/null @@ -1,1805 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2014 Michal Schmidt - - 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 . -***/ - -#include -#include -#include -#include - -#include "alloc-util.h" -#include "hashmap.h" -#include "macro.h" -#include "mempool.h" -#include "process-util.h" -#include "random-util.h" -#include "set.h" -#include "siphash24.h" -#include "strv.h" -#include "util.h" - -#ifdef ENABLE_DEBUG_HASHMAP -#include -#include "list.h" -#endif - -/* - * Implementation of hashmaps. - * Addressing: open - * - uses less RAM compared to closed addressing (chaining), because - * our entries are small (especially in Sets, which tend to contain - * the majority of entries in systemd). - * Collision resolution: Robin Hood - * - tends to equalize displacement of entries from their optimal buckets. - * Probe sequence: linear - * - though theoretically worse than random probing/uniform hashing/double - * hashing, it is good for cache locality. - * - * References: - * Celis, P. 1986. Robin Hood Hashing. - * Ph.D. Dissertation. University of Waterloo, Waterloo, Ont., Canada, Canada. - * https://cs.uwaterloo.ca/research/tr/1986/CS-86-14.pdf - * - The results are derived for random probing. Suggests deletion with - * tombstones and two mean-centered search methods. None of that works - * well for linear probing. - * - * Janson, S. 2005. Individual displacements for linear probing hashing with different insertion policies. - * ACM Trans. Algorithms 1, 2 (October 2005), 177-213. - * DOI=10.1145/1103963.1103964 http://doi.acm.org/10.1145/1103963.1103964 - * http://www.math.uu.se/~svante/papers/sj157.pdf - * - Applies to Robin Hood with linear probing. Contains remarks on - * the unsuitability of mean-centered search with linear probing. - * - * Viola, A. 2005. Exact distribution of individual displacements in linear probing hashing. - * ACM Trans. Algorithms 1, 2 (October 2005), 214-242. - * DOI=10.1145/1103963.1103965 http://doi.acm.org/10.1145/1103963.1103965 - * - Similar to Janson. Note that Viola writes about C_{m,n} (number of probes - * in a successful search), and Janson writes about displacement. C = d + 1. - * - * Goossaert, E. 2013. Robin Hood hashing: backward shift deletion. - * http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/ - * - Explanation of backward shift deletion with pictures. - * - * Khuong, P. 2013. The Other Robin Hood Hashing. - * http://www.pvk.ca/Blog/2013/11/26/the-other-robin-hood-hashing/ - * - Short summary of random vs. linear probing, and tombstones vs. backward shift. - */ - -/* - * XXX Ideas for improvement: - * For unordered hashmaps, randomize iteration order, similarly to Perl: - * http://blog.booking.com/hardening-perls-hash-function.html - */ - -/* INV_KEEP_FREE = 1 / (1 - max_load_factor) - * e.g. 1 / (1 - 0.8) = 5 ... keep one fifth of the buckets free. */ -#define INV_KEEP_FREE 5U - -/* Fields common to entries of all hashmap/set types */ -struct hashmap_base_entry { - const void *key; -}; - -/* Entry types for specific hashmap/set types - * hashmap_base_entry must be at the beginning of each entry struct. */ - -struct plain_hashmap_entry { - struct hashmap_base_entry b; - void *value; -}; - -struct ordered_hashmap_entry { - struct plain_hashmap_entry p; - unsigned iterate_next, iterate_previous; -}; - -struct set_entry { - struct hashmap_base_entry b; -}; - -/* In several functions it is advantageous to have the hash table extended - * virtually by a couple of additional buckets. We reserve special index values - * for these "swap" buckets. */ -#define _IDX_SWAP_BEGIN (UINT_MAX - 3) -#define IDX_PUT (_IDX_SWAP_BEGIN + 0) -#define IDX_TMP (_IDX_SWAP_BEGIN + 1) -#define _IDX_SWAP_END (_IDX_SWAP_BEGIN + 2) - -#define IDX_FIRST (UINT_MAX - 1) /* special index for freshly initialized iterators */ -#define IDX_NIL UINT_MAX /* special index value meaning "none" or "end" */ - -assert_cc(IDX_FIRST == _IDX_SWAP_END); -assert_cc(IDX_FIRST == _IDX_ITERATOR_FIRST); - -/* Storage space for the "swap" buckets. - * All entry types can fit into a ordered_hashmap_entry. */ -struct swap_entries { - struct ordered_hashmap_entry e[_IDX_SWAP_END - _IDX_SWAP_BEGIN]; -}; - -/* Distance from Initial Bucket */ -typedef uint8_t dib_raw_t; -#define DIB_RAW_OVERFLOW ((dib_raw_t)0xfdU) /* indicates DIB value is greater than representable */ -#define DIB_RAW_REHASH ((dib_raw_t)0xfeU) /* entry yet to be rehashed during in-place resize */ -#define DIB_RAW_FREE ((dib_raw_t)0xffU) /* a free bucket */ -#define DIB_RAW_INIT ((char)DIB_RAW_FREE) /* a byte to memset a DIB store with when initializing */ - -#define DIB_FREE UINT_MAX - -#ifdef ENABLE_DEBUG_HASHMAP -struct hashmap_debug_info { - LIST_FIELDS(struct hashmap_debug_info, debug_list); - unsigned max_entries; /* high watermark of n_entries */ - - /* who allocated this hashmap */ - int line; - const char *file; - const char *func; - - /* fields to detect modification while iterating */ - unsigned put_count; /* counts puts into the hashmap */ - unsigned rem_count; /* counts removals from hashmap */ - unsigned last_rem_idx; /* remembers last removal index */ -}; - -/* Tracks all existing hashmaps. Get at it from gdb. See sd_dump_hashmaps.py */ -static LIST_HEAD(struct hashmap_debug_info, hashmap_debug_list); -static pthread_mutex_t hashmap_debug_list_mutex = PTHREAD_MUTEX_INITIALIZER; - -#define HASHMAP_DEBUG_FIELDS struct hashmap_debug_info debug; - -#else /* !ENABLE_DEBUG_HASHMAP */ -#define HASHMAP_DEBUG_FIELDS -#endif /* ENABLE_DEBUG_HASHMAP */ - -enum HashmapType { - HASHMAP_TYPE_PLAIN, - HASHMAP_TYPE_ORDERED, - HASHMAP_TYPE_SET, - _HASHMAP_TYPE_MAX -}; - -struct _packed_ indirect_storage { - char *storage; /* where buckets and DIBs are stored */ - uint8_t hash_key[HASH_KEY_SIZE]; /* hash key; changes during resize */ - - unsigned n_entries; /* number of stored entries */ - unsigned n_buckets; /* number of buckets */ - - unsigned idx_lowest_entry; /* Index below which all buckets are free. - Makes "while(hashmap_steal_first())" loops - O(n) instead of O(n^2) for unordered hashmaps. */ - uint8_t _pad[3]; /* padding for the whole HashmapBase */ - /* The bitfields in HashmapBase complete the alignment of the whole thing. */ -}; - -struct direct_storage { - /* This gives us 39 bytes on 64bit, or 35 bytes on 32bit. - * That's room for 4 set_entries + 4 DIB bytes + 3 unused bytes on 64bit, - * or 7 set_entries + 7 DIB bytes + 0 unused bytes on 32bit. */ - char storage[sizeof(struct indirect_storage)]; -}; - -#define DIRECT_BUCKETS(entry_t) \ - (sizeof(struct direct_storage) / (sizeof(entry_t) + sizeof(dib_raw_t))) - -/* We should be able to store at least one entry directly. */ -assert_cc(DIRECT_BUCKETS(struct ordered_hashmap_entry) >= 1); - -/* We have 3 bits for n_direct_entries. */ -assert_cc(DIRECT_BUCKETS(struct set_entry) < (1 << 3)); - -/* Hashmaps with directly stored entries all use this shared hash key. - * It's no big deal if the key is guessed, because there can be only - * a handful of directly stored entries in a hashmap. When a hashmap - * outgrows direct storage, it gets its own key for indirect storage. */ -static uint8_t shared_hash_key[HASH_KEY_SIZE]; -static bool shared_hash_key_initialized; - -/* Fields that all hashmap/set types must have */ -struct HashmapBase { - const struct hash_ops *hash_ops; /* hash and compare ops to use */ - - union _packed_ { - struct indirect_storage indirect; /* if has_indirect */ - struct direct_storage direct; /* if !has_indirect */ - }; - - enum HashmapType type:2; /* HASHMAP_TYPE_* */ - bool has_indirect:1; /* whether indirect storage is used */ - unsigned n_direct_entries:3; /* Number of entries in direct storage. - * Only valid if !has_indirect. */ - bool from_pool:1; /* whether was allocated from mempool */ - HASHMAP_DEBUG_FIELDS /* optional hashmap_debug_info */ -}; - -/* Specific hash types - * HashmapBase must be at the beginning of each hashmap struct. */ - -struct Hashmap { - struct HashmapBase b; -}; - -struct OrderedHashmap { - struct HashmapBase b; - unsigned iterate_list_head, iterate_list_tail; -}; - -struct Set { - struct HashmapBase b; -}; - -DEFINE_MEMPOOL(hashmap_pool, Hashmap, 8); -DEFINE_MEMPOOL(ordered_hashmap_pool, OrderedHashmap, 8); -/* No need for a separate Set pool */ -assert_cc(sizeof(Hashmap) == sizeof(Set)); - -struct hashmap_type_info { - size_t head_size; - size_t entry_size; - struct mempool *mempool; - unsigned n_direct_buckets; -}; - -static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = { - [HASHMAP_TYPE_PLAIN] = { - .head_size = sizeof(Hashmap), - .entry_size = sizeof(struct plain_hashmap_entry), - .mempool = &hashmap_pool, - .n_direct_buckets = DIRECT_BUCKETS(struct plain_hashmap_entry), - }, - [HASHMAP_TYPE_ORDERED] = { - .head_size = sizeof(OrderedHashmap), - .entry_size = sizeof(struct ordered_hashmap_entry), - .mempool = &ordered_hashmap_pool, - .n_direct_buckets = DIRECT_BUCKETS(struct ordered_hashmap_entry), - }, - [HASHMAP_TYPE_SET] = { - .head_size = sizeof(Set), - .entry_size = sizeof(struct set_entry), - .mempool = &hashmap_pool, - .n_direct_buckets = DIRECT_BUCKETS(struct set_entry), - }, -}; - -static unsigned n_buckets(HashmapBase *h) { - return h->has_indirect ? h->indirect.n_buckets - : hashmap_type_info[h->type].n_direct_buckets; -} - -static unsigned n_entries(HashmapBase *h) { - return h->has_indirect ? h->indirect.n_entries - : h->n_direct_entries; -} - -static void n_entries_inc(HashmapBase *h) { - if (h->has_indirect) - h->indirect.n_entries++; - else - h->n_direct_entries++; -} - -static void n_entries_dec(HashmapBase *h) { - if (h->has_indirect) - h->indirect.n_entries--; - else - h->n_direct_entries--; -} - -static char *storage_ptr(HashmapBase *h) { - return h->has_indirect ? h->indirect.storage - : h->direct.storage; -} - -static uint8_t *hash_key(HashmapBase *h) { - return h->has_indirect ? h->indirect.hash_key - : shared_hash_key; -} - -static unsigned base_bucket_hash(HashmapBase *h, const void *p) { - struct siphash state; - uint64_t hash; - - siphash24_init(&state, hash_key(h)); - - h->hash_ops->hash(p, &state); - - hash = siphash24_finalize(&state); - - return (unsigned) (hash % n_buckets(h)); -} -#define bucket_hash(h, p) base_bucket_hash(HASHMAP_BASE(h), p) - -static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) { - static uint8_t current[HASH_KEY_SIZE]; - static bool current_initialized = false; - - /* Returns a hash function key to use. In order to keep things - * fast we will not generate a new key each time we allocate a - * new hash table. Instead, we'll just reuse the most recently - * generated one, except if we never generated one or when we - * are rehashing an entire hash table because we reached a - * fill level */ - - if (!current_initialized || !reuse_is_ok) { - random_bytes(current, sizeof(current)); - current_initialized = true; - } - - memcpy(hash_key, current, sizeof(current)); -} - -static struct hashmap_base_entry *bucket_at(HashmapBase *h, unsigned idx) { - return (struct hashmap_base_entry*) - (storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size); -} - -static struct plain_hashmap_entry *plain_bucket_at(Hashmap *h, unsigned idx) { - return (struct plain_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx); -} - -static struct ordered_hashmap_entry *ordered_bucket_at(OrderedHashmap *h, unsigned idx) { - return (struct ordered_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx); -} - -static struct set_entry *set_bucket_at(Set *h, unsigned idx) { - return (struct set_entry*) bucket_at(HASHMAP_BASE(h), idx); -} - -static struct ordered_hashmap_entry *bucket_at_swap(struct swap_entries *swap, unsigned idx) { - return &swap->e[idx - _IDX_SWAP_BEGIN]; -} - -/* Returns a pointer to the bucket at index idx. - * Understands real indexes and swap indexes, hence "_virtual". */ -static struct hashmap_base_entry *bucket_at_virtual(HashmapBase *h, struct swap_entries *swap, - unsigned idx) { - if (idx < _IDX_SWAP_BEGIN) - return bucket_at(h, idx); - - if (idx < _IDX_SWAP_END) - return &bucket_at_swap(swap, idx)->p.b; - - assert_not_reached("Invalid index"); -} - -static dib_raw_t *dib_raw_ptr(HashmapBase *h) { - return (dib_raw_t*) - (storage_ptr(h) + hashmap_type_info[h->type].entry_size * n_buckets(h)); -} - -static unsigned bucket_distance(HashmapBase *h, unsigned idx, unsigned from) { - return idx >= from ? idx - from - : n_buckets(h) + idx - from; -} - -static unsigned bucket_calculate_dib(HashmapBase *h, unsigned idx, dib_raw_t raw_dib) { - unsigned initial_bucket; - - if (raw_dib == DIB_RAW_FREE) - return DIB_FREE; - - if (_likely_(raw_dib < DIB_RAW_OVERFLOW)) - return raw_dib; - - /* - * Having an overflow DIB value is very unlikely. The hash function - * would have to be bad. For example, in a table of size 2^24 filled - * to load factor 0.9 the maximum observed DIB is only about 60. - * In theory (assuming I used Maxima correctly), for an infinite size - * hash table with load factor 0.8 the probability of a given entry - * having DIB > 40 is 1.9e-8. - * This returns the correct DIB value by recomputing the hash value in - * the unlikely case. XXX Hitting this case could be a hint to rehash. - */ - initial_bucket = bucket_hash(h, bucket_at(h, idx)->key); - return bucket_distance(h, idx, initial_bucket); -} - -static void bucket_set_dib(HashmapBase *h, unsigned idx, unsigned dib) { - dib_raw_ptr(h)[idx] = dib != DIB_FREE ? MIN(dib, DIB_RAW_OVERFLOW) : DIB_RAW_FREE; -} - -static unsigned skip_free_buckets(HashmapBase *h, unsigned idx) { - dib_raw_t *dibs; - - dibs = dib_raw_ptr(h); - - for ( ; idx < n_buckets(h); idx++) - if (dibs[idx] != DIB_RAW_FREE) - return idx; - - return IDX_NIL; -} - -static void bucket_mark_free(HashmapBase *h, unsigned idx) { - memzero(bucket_at(h, idx), hashmap_type_info[h->type].entry_size); - bucket_set_dib(h, idx, DIB_FREE); -} - -static void bucket_move_entry(HashmapBase *h, struct swap_entries *swap, - unsigned from, unsigned to) { - struct hashmap_base_entry *e_from, *e_to; - - assert(from != to); - - e_from = bucket_at_virtual(h, swap, from); - e_to = bucket_at_virtual(h, swap, to); - - memcpy(e_to, e_from, hashmap_type_info[h->type].entry_size); - - if (h->type == HASHMAP_TYPE_ORDERED) { - OrderedHashmap *lh = (OrderedHashmap*) h; - struct ordered_hashmap_entry *le, *le_to; - - le_to = (struct ordered_hashmap_entry*) e_to; - - if (le_to->iterate_next != IDX_NIL) { - le = (struct ordered_hashmap_entry*) - bucket_at_virtual(h, swap, le_to->iterate_next); - le->iterate_previous = to; - } - - if (le_to->iterate_previous != IDX_NIL) { - le = (struct ordered_hashmap_entry*) - bucket_at_virtual(h, swap, le_to->iterate_previous); - le->iterate_next = to; - } - - if (lh->iterate_list_head == from) - lh->iterate_list_head = to; - if (lh->iterate_list_tail == from) - lh->iterate_list_tail = to; - } -} - -static unsigned next_idx(HashmapBase *h, unsigned idx) { - return (idx + 1U) % n_buckets(h); -} - -static unsigned prev_idx(HashmapBase *h, unsigned idx) { - return (n_buckets(h) + idx - 1U) % n_buckets(h); -} - -static void *entry_value(HashmapBase *h, struct hashmap_base_entry *e) { - switch (h->type) { - - case HASHMAP_TYPE_PLAIN: - case HASHMAP_TYPE_ORDERED: - return ((struct plain_hashmap_entry*)e)->value; - - case HASHMAP_TYPE_SET: - return (void*) e->key; - - default: - assert_not_reached("Unknown hashmap type"); - } -} - -static void base_remove_entry(HashmapBase *h, unsigned idx) { - unsigned left, right, prev, dib; - dib_raw_t raw_dib, *dibs; - - dibs = dib_raw_ptr(h); - assert(dibs[idx] != DIB_RAW_FREE); - -#ifdef ENABLE_DEBUG_HASHMAP - h->debug.rem_count++; - h->debug.last_rem_idx = idx; -#endif - - left = idx; - /* Find the stop bucket ("right"). It is either free or has DIB == 0. */ - for (right = next_idx(h, left); ; right = next_idx(h, right)) { - raw_dib = dibs[right]; - if (raw_dib == 0 || raw_dib == DIB_RAW_FREE) - break; - - /* The buckets are not supposed to be all occupied and with DIB > 0. - * That would mean we could make everyone better off by shifting them - * backward. This scenario is impossible. */ - assert(left != right); - } - - if (h->type == HASHMAP_TYPE_ORDERED) { - OrderedHashmap *lh = (OrderedHashmap*) h; - struct ordered_hashmap_entry *le = ordered_bucket_at(lh, idx); - - if (le->iterate_next != IDX_NIL) - ordered_bucket_at(lh, le->iterate_next)->iterate_previous = le->iterate_previous; - else - lh->iterate_list_tail = le->iterate_previous; - - if (le->iterate_previous != IDX_NIL) - ordered_bucket_at(lh, le->iterate_previous)->iterate_next = le->iterate_next; - else - lh->iterate_list_head = le->iterate_next; - } - - /* Now shift all buckets in the interval (left, right) one step backwards */ - for (prev = left, left = next_idx(h, left); left != right; - prev = left, left = next_idx(h, left)) { - dib = bucket_calculate_dib(h, left, dibs[left]); - assert(dib != 0); - bucket_move_entry(h, NULL, left, prev); - bucket_set_dib(h, prev, dib - 1); - } - - bucket_mark_free(h, prev); - n_entries_dec(h); -} -#define remove_entry(h, idx) base_remove_entry(HASHMAP_BASE(h), idx) - -static unsigned hashmap_iterate_in_insertion_order(OrderedHashmap *h, Iterator *i) { - struct ordered_hashmap_entry *e; - unsigned idx; - - assert(h); - assert(i); - - if (i->idx == IDX_NIL) - goto at_end; - - if (i->idx == IDX_FIRST && h->iterate_list_head == IDX_NIL) - goto at_end; - - if (i->idx == IDX_FIRST) { - idx = h->iterate_list_head; - e = ordered_bucket_at(h, idx); - } else { - idx = i->idx; - e = ordered_bucket_at(h, idx); - /* - * We allow removing the current entry while iterating, but removal may cause - * a backward shift. The next entry may thus move one bucket to the left. - * To detect when it happens, we remember the key pointer of the entry we were - * going to iterate next. If it does not match, there was a backward shift. - */ - if (e->p.b.key != i->next_key) { - idx = prev_idx(HASHMAP_BASE(h), idx); - e = ordered_bucket_at(h, idx); - } - assert(e->p.b.key == i->next_key); - } - -#ifdef ENABLE_DEBUG_HASHMAP - i->prev_idx = idx; -#endif - - if (e->iterate_next != IDX_NIL) { - struct ordered_hashmap_entry *n; - i->idx = e->iterate_next; - n = ordered_bucket_at(h, i->idx); - i->next_key = n->p.b.key; - } else - i->idx = IDX_NIL; - - return idx; - -at_end: - i->idx = IDX_NIL; - return IDX_NIL; -} - -static unsigned hashmap_iterate_in_internal_order(HashmapBase *h, Iterator *i) { - unsigned idx; - - assert(h); - assert(i); - - if (i->idx == IDX_NIL) - goto at_end; - - if (i->idx == IDX_FIRST) { - /* fast forward to the first occupied bucket */ - if (h->has_indirect) { - i->idx = skip_free_buckets(h, h->indirect.idx_lowest_entry); - h->indirect.idx_lowest_entry = i->idx; - } else - i->idx = skip_free_buckets(h, 0); - - if (i->idx == IDX_NIL) - goto at_end; - } else { - struct hashmap_base_entry *e; - - assert(i->idx > 0); - - e = bucket_at(h, i->idx); - /* - * We allow removing the current entry while iterating, but removal may cause - * a backward shift. The next entry may thus move one bucket to the left. - * To detect when it happens, we remember the key pointer of the entry we were - * going to iterate next. If it does not match, there was a backward shift. - */ - if (e->key != i->next_key) - e = bucket_at(h, --i->idx); - - assert(e->key == i->next_key); - } - - idx = i->idx; -#ifdef ENABLE_DEBUG_HASHMAP - i->prev_idx = idx; -#endif - - i->idx = skip_free_buckets(h, i->idx + 1); - if (i->idx != IDX_NIL) - i->next_key = bucket_at(h, i->idx)->key; - else - i->idx = IDX_NIL; - - return idx; - -at_end: - i->idx = IDX_NIL; - return IDX_NIL; -} - -static unsigned hashmap_iterate_entry(HashmapBase *h, Iterator *i) { - if (!h) { - i->idx = IDX_NIL; - return IDX_NIL; - } - -#ifdef ENABLE_DEBUG_HASHMAP - if (i->idx == IDX_FIRST) { - i->put_count = h->debug.put_count; - i->rem_count = h->debug.rem_count; - } else { - /* While iterating, must not add any new entries */ - assert(i->put_count == h->debug.put_count); - /* ... or remove entries other than the current one */ - assert(i->rem_count == h->debug.rem_count || - (i->rem_count == h->debug.rem_count - 1 && - i->prev_idx == h->debug.last_rem_idx)); - /* Reset our removals counter */ - i->rem_count = h->debug.rem_count; - } -#endif - - return h->type == HASHMAP_TYPE_ORDERED ? hashmap_iterate_in_insertion_order((OrderedHashmap*) h, i) - : hashmap_iterate_in_internal_order(h, i); -} - -bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key) { - struct hashmap_base_entry *e; - void *data; - unsigned idx; - - idx = hashmap_iterate_entry(h, i); - if (idx == IDX_NIL) { - if (value) - *value = NULL; - if (key) - *key = NULL; - - return false; - } - - e = bucket_at(h, idx); - data = entry_value(h, e); - if (value) - *value = data; - if (key) - *key = e->key; - - return true; -} - -bool set_iterate(Set *s, Iterator *i, void **value) { - return internal_hashmap_iterate(HASHMAP_BASE(s), i, value, NULL); -} - -#define HASHMAP_FOREACH_IDX(idx, h, i) \ - for ((i) = ITERATOR_FIRST, (idx) = hashmap_iterate_entry((h), &(i)); \ - (idx != IDX_NIL); \ - (idx) = hashmap_iterate_entry((h), &(i))) - -static void reset_direct_storage(HashmapBase *h) { - const struct hashmap_type_info *hi = &hashmap_type_info[h->type]; - void *p; - - assert(!h->has_indirect); - - p = mempset(h->direct.storage, 0, hi->entry_size * hi->n_direct_buckets); - memset(p, DIB_RAW_INIT, sizeof(dib_raw_t) * hi->n_direct_buckets); -} - -static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enum HashmapType type HASHMAP_DEBUG_PARAMS) { - HashmapBase *h; - const struct hashmap_type_info *hi = &hashmap_type_info[type]; - bool use_pool; - - use_pool = is_main_thread(); - - h = use_pool ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size); - - if (!h) - return NULL; - - h->type = type; - h->from_pool = use_pool; - h->hash_ops = hash_ops ? hash_ops : &trivial_hash_ops; - - if (type == HASHMAP_TYPE_ORDERED) { - OrderedHashmap *lh = (OrderedHashmap*)h; - lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL; - } - - reset_direct_storage(h); - - if (!shared_hash_key_initialized) { - random_bytes(shared_hash_key, sizeof(shared_hash_key)); - shared_hash_key_initialized= true; - } - -#ifdef ENABLE_DEBUG_HASHMAP - h->debug.func = func; - h->debug.file = file; - h->debug.line = line; - assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0); - LIST_PREPEND(debug_list, hashmap_debug_list, &h->debug); - assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0); -#endif - - return h; -} - -Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { - return (Hashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS); -} - -OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { - return (OrderedHashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS); -} - -Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { - return (Set*) hashmap_base_new(hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS); -} - -static int hashmap_base_ensure_allocated(HashmapBase **h, const struct hash_ops *hash_ops, - enum HashmapType type HASHMAP_DEBUG_PARAMS) { - HashmapBase *q; - - assert(h); - - if (*h) - return 0; - - q = hashmap_base_new(hash_ops, type HASHMAP_DEBUG_PASS_ARGS); - if (!q) - return -ENOMEM; - - *h = q; - return 0; -} - -int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { - return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS); -} - -int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { - return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS); -} - -int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { - return hashmap_base_ensure_allocated((HashmapBase**)s, hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS); -} - -static void hashmap_free_no_clear(HashmapBase *h) { - assert(!h->has_indirect); - assert(!h->n_direct_entries); - -#ifdef ENABLE_DEBUG_HASHMAP - assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0); - LIST_REMOVE(debug_list, hashmap_debug_list, &h->debug); - assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0); -#endif - - if (h->from_pool) - mempool_free_tile(hashmap_type_info[h->type].mempool, h); - else - free(h); -} - -HashmapBase *internal_hashmap_free(HashmapBase *h) { - - /* Free the hashmap, but nothing in it */ - - if (h) { - internal_hashmap_clear(h); - hashmap_free_no_clear(h); - } - - return NULL; -} - -HashmapBase *internal_hashmap_free_free(HashmapBase *h) { - - /* Free the hashmap and all data objects in it, but not the - * keys */ - - if (h) { - internal_hashmap_clear_free(h); - hashmap_free_no_clear(h); - } - - return NULL; -} - -Hashmap *hashmap_free_free_free(Hashmap *h) { - - /* Free the hashmap and all data and key objects in it */ - - if (h) { - hashmap_clear_free_free(h); - hashmap_free_no_clear(HASHMAP_BASE(h)); - } - - return NULL; -} - -void internal_hashmap_clear(HashmapBase *h) { - if (!h) - return; - - if (h->has_indirect) { - free(h->indirect.storage); - h->has_indirect = false; - } - - h->n_direct_entries = 0; - reset_direct_storage(h); - - if (h->type == HASHMAP_TYPE_ORDERED) { - OrderedHashmap *lh = (OrderedHashmap*) h; - lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL; - } -} - -void internal_hashmap_clear_free(HashmapBase *h) { - unsigned idx; - - if (!h) - return; - - for (idx = skip_free_buckets(h, 0); idx != IDX_NIL; - idx = skip_free_buckets(h, idx + 1)) - free(entry_value(h, bucket_at(h, idx))); - - internal_hashmap_clear(h); -} - -void hashmap_clear_free_free(Hashmap *h) { - unsigned idx; - - if (!h) - return; - - for (idx = skip_free_buckets(HASHMAP_BASE(h), 0); idx != IDX_NIL; - idx = skip_free_buckets(HASHMAP_BASE(h), idx + 1)) { - struct plain_hashmap_entry *e = plain_bucket_at(h, idx); - free((void*)e->b.key); - free(e->value); - } - - internal_hashmap_clear(HASHMAP_BASE(h)); -} - -static int resize_buckets(HashmapBase *h, unsigned entries_add); - -/* - * Finds an empty bucket to put an entry into, starting the scan at 'idx'. - * Performs Robin Hood swaps as it goes. The entry to put must be placed - * by the caller into swap slot IDX_PUT. - * If used for in-place resizing, may leave a displaced entry in swap slot - * IDX_PUT. Caller must rehash it next. - * Returns: true if it left a displaced entry to rehash next in IDX_PUT, - * false otherwise. - */ -static bool hashmap_put_robin_hood(HashmapBase *h, unsigned idx, - struct swap_entries *swap) { - dib_raw_t raw_dib, *dibs; - unsigned dib, distance; - -#ifdef ENABLE_DEBUG_HASHMAP - h->debug.put_count++; -#endif - - dibs = dib_raw_ptr(h); - - for (distance = 0; ; distance++) { - raw_dib = dibs[idx]; - if (raw_dib == DIB_RAW_FREE || raw_dib == DIB_RAW_REHASH) { - if (raw_dib == DIB_RAW_REHASH) - bucket_move_entry(h, swap, idx, IDX_TMP); - - if (h->has_indirect && h->indirect.idx_lowest_entry > idx) - h->indirect.idx_lowest_entry = idx; - - bucket_set_dib(h, idx, distance); - bucket_move_entry(h, swap, IDX_PUT, idx); - if (raw_dib == DIB_RAW_REHASH) { - bucket_move_entry(h, swap, IDX_TMP, IDX_PUT); - return true; - } - - return false; - } - - dib = bucket_calculate_dib(h, idx, raw_dib); - - if (dib < distance) { - /* Found a wealthier entry. Go Robin Hood! */ - bucket_set_dib(h, idx, distance); - - /* swap the entries */ - bucket_move_entry(h, swap, idx, IDX_TMP); - bucket_move_entry(h, swap, IDX_PUT, idx); - bucket_move_entry(h, swap, IDX_TMP, IDX_PUT); - - distance = dib; - } - - idx = next_idx(h, idx); - } -} - -/* - * Puts an entry into a hashmap, boldly - no check whether key already exists. - * The caller must place the entry (only its key and value, not link indexes) - * in swap slot IDX_PUT. - * Caller must ensure: the key does not exist yet in the hashmap. - * that resize is not needed if !may_resize. - * Returns: 1 if entry was put successfully. - * -ENOMEM if may_resize==true and resize failed with -ENOMEM. - * Cannot return -ENOMEM if !may_resize. - */ -static int hashmap_base_put_boldly(HashmapBase *h, unsigned idx, - struct swap_entries *swap, bool may_resize) { - struct ordered_hashmap_entry *new_entry; - int r; - - assert(idx < n_buckets(h)); - - new_entry = bucket_at_swap(swap, IDX_PUT); - - if (may_resize) { - r = resize_buckets(h, 1); - if (r < 0) - return r; - if (r > 0) - idx = bucket_hash(h, new_entry->p.b.key); - } - assert(n_entries(h) < n_buckets(h)); - - if (h->type == HASHMAP_TYPE_ORDERED) { - OrderedHashmap *lh = (OrderedHashmap*) h; - - new_entry->iterate_next = IDX_NIL; - new_entry->iterate_previous = lh->iterate_list_tail; - - if (lh->iterate_list_tail != IDX_NIL) { - struct ordered_hashmap_entry *old_tail; - - old_tail = ordered_bucket_at(lh, lh->iterate_list_tail); - assert(old_tail->iterate_next == IDX_NIL); - old_tail->iterate_next = IDX_PUT; - } - - lh->iterate_list_tail = IDX_PUT; - if (lh->iterate_list_head == IDX_NIL) - lh->iterate_list_head = IDX_PUT; - } - - assert_se(hashmap_put_robin_hood(h, idx, swap) == false); - - n_entries_inc(h); -#ifdef ENABLE_DEBUG_HASHMAP - h->debug.max_entries = MAX(h->debug.max_entries, n_entries(h)); -#endif - - return 1; -} -#define hashmap_put_boldly(h, idx, swap, may_resize) \ - hashmap_base_put_boldly(HASHMAP_BASE(h), idx, swap, may_resize) - -/* - * Returns 0 if resize is not needed. - * 1 if successfully resized. - * -ENOMEM on allocation failure. - */ -static int resize_buckets(HashmapBase *h, unsigned entries_add) { - struct swap_entries swap; - char *new_storage; - dib_raw_t *old_dibs, *new_dibs; - const struct hashmap_type_info *hi; - unsigned idx, optimal_idx; - unsigned old_n_buckets, new_n_buckets, n_rehashed, new_n_entries; - uint8_t new_shift; - bool rehash_next; - - assert(h); - - hi = &hashmap_type_info[h->type]; - new_n_entries = n_entries(h) + entries_add; - - /* overflow? */ - if (_unlikely_(new_n_entries < entries_add)) - return -ENOMEM; - - /* For direct storage we allow 100% load, because it's tiny. */ - if (!h->has_indirect && new_n_entries <= hi->n_direct_buckets) - return 0; - - /* - * Load factor = n/m = 1 - (1/INV_KEEP_FREE). - * From it follows: m = n + n/(INV_KEEP_FREE - 1) - */ - new_n_buckets = new_n_entries + new_n_entries / (INV_KEEP_FREE - 1); - /* overflow? */ - if (_unlikely_(new_n_buckets < new_n_entries)) - return -ENOMEM; - - if (_unlikely_(new_n_buckets > UINT_MAX / (hi->entry_size + sizeof(dib_raw_t)))) - return -ENOMEM; - - old_n_buckets = n_buckets(h); - - if (_likely_(new_n_buckets <= old_n_buckets)) - return 0; - - new_shift = log2u_round_up(MAX( - new_n_buckets * (hi->entry_size + sizeof(dib_raw_t)), - 2 * sizeof(struct direct_storage))); - - /* Realloc storage (buckets and DIB array). */ - new_storage = realloc(h->has_indirect ? h->indirect.storage : NULL, - 1U << new_shift); - if (!new_storage) - return -ENOMEM; - - /* Must upgrade direct to indirect storage. */ - if (!h->has_indirect) { - memcpy(new_storage, h->direct.storage, - old_n_buckets * (hi->entry_size + sizeof(dib_raw_t))); - h->indirect.n_entries = h->n_direct_entries; - h->indirect.idx_lowest_entry = 0; - h->n_direct_entries = 0; - } - - /* Get a new hash key. If we've just upgraded to indirect storage, - * allow reusing a previously generated key. It's still a different key - * from the shared one that we used for direct storage. */ - get_hash_key(h->indirect.hash_key, !h->has_indirect); - - h->has_indirect = true; - h->indirect.storage = new_storage; - h->indirect.n_buckets = (1U << new_shift) / - (hi->entry_size + sizeof(dib_raw_t)); - - old_dibs = (dib_raw_t*)(new_storage + hi->entry_size * old_n_buckets); - new_dibs = dib_raw_ptr(h); - - /* - * Move the DIB array to the new place, replacing valid DIB values with - * DIB_RAW_REHASH to indicate all of the used buckets need rehashing. - * Note: Overlap is not possible, because we have at least doubled the - * number of buckets and dib_raw_t is smaller than any entry type. - */ - for (idx = 0; idx < old_n_buckets; idx++) { - assert(old_dibs[idx] != DIB_RAW_REHASH); - new_dibs[idx] = old_dibs[idx] == DIB_RAW_FREE ? DIB_RAW_FREE - : DIB_RAW_REHASH; - } - - /* Zero the area of newly added entries (including the old DIB area) */ - memzero(bucket_at(h, old_n_buckets), - (n_buckets(h) - old_n_buckets) * hi->entry_size); - - /* The upper half of the new DIB array needs initialization */ - memset(&new_dibs[old_n_buckets], DIB_RAW_INIT, - (n_buckets(h) - old_n_buckets) * sizeof(dib_raw_t)); - - /* Rehash entries that need it */ - n_rehashed = 0; - for (idx = 0; idx < old_n_buckets; idx++) { - if (new_dibs[idx] != DIB_RAW_REHASH) - continue; - - optimal_idx = bucket_hash(h, bucket_at(h, idx)->key); - - /* - * Not much to do if by luck the entry hashes to its current - * location. Just set its DIB. - */ - if (optimal_idx == idx) { - new_dibs[idx] = 0; - n_rehashed++; - continue; - } - - new_dibs[idx] = DIB_RAW_FREE; - bucket_move_entry(h, &swap, idx, IDX_PUT); - /* bucket_move_entry does not clear the source */ - memzero(bucket_at(h, idx), hi->entry_size); - - do { - /* - * Find the new bucket for the current entry. This may make - * another entry homeless and load it into IDX_PUT. - */ - rehash_next = hashmap_put_robin_hood(h, optimal_idx, &swap); - n_rehashed++; - - /* Did the current entry displace another one? */ - if (rehash_next) - optimal_idx = bucket_hash(h, bucket_at_swap(&swap, IDX_PUT)->p.b.key); - } while (rehash_next); - } - - assert(n_rehashed == n_entries(h)); - - return 1; -} - -/* - * Finds an entry with a matching key - * Returns: index of the found entry, or IDX_NIL if not found. - */ -static unsigned base_bucket_scan(HashmapBase *h, unsigned idx, const void *key) { - struct hashmap_base_entry *e; - unsigned dib, distance; - dib_raw_t *dibs = dib_raw_ptr(h); - - assert(idx < n_buckets(h)); - - for (distance = 0; ; distance++) { - if (dibs[idx] == DIB_RAW_FREE) - return IDX_NIL; - - dib = bucket_calculate_dib(h, idx, dibs[idx]); - - if (dib < distance) - return IDX_NIL; - if (dib == distance) { - e = bucket_at(h, idx); - if (h->hash_ops->compare(e->key, key) == 0) - return idx; - } - - idx = next_idx(h, idx); - } -} -#define bucket_scan(h, idx, key) base_bucket_scan(HASHMAP_BASE(h), idx, key) - -int hashmap_put(Hashmap *h, const void *key, void *value) { - struct swap_entries swap; - struct plain_hashmap_entry *e; - unsigned hash, idx; - - assert(h); - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx != IDX_NIL) { - e = plain_bucket_at(h, idx); - if (e->value == value) - return 0; - return -EEXIST; - } - - e = &bucket_at_swap(&swap, IDX_PUT)->p; - e->b.key = key; - e->value = value; - return hashmap_put_boldly(h, hash, &swap, true); -} - -int set_put(Set *s, const void *key) { - struct swap_entries swap; - struct hashmap_base_entry *e; - unsigned hash, idx; - - assert(s); - - hash = bucket_hash(s, key); - idx = bucket_scan(s, hash, key); - if (idx != IDX_NIL) - return 0; - - e = &bucket_at_swap(&swap, IDX_PUT)->p.b; - e->key = key; - return hashmap_put_boldly(s, hash, &swap, true); -} - -int hashmap_replace(Hashmap *h, const void *key, void *value) { - struct swap_entries swap; - struct plain_hashmap_entry *e; - unsigned hash, idx; - - assert(h); - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx != IDX_NIL) { - e = plain_bucket_at(h, idx); -#ifdef ENABLE_DEBUG_HASHMAP - /* Although the key is equal, the key pointer may have changed, - * and this would break our assumption for iterating. So count - * this operation as incompatible with iteration. */ - if (e->b.key != key) { - h->b.debug.put_count++; - h->b.debug.rem_count++; - h->b.debug.last_rem_idx = idx; - } -#endif - e->b.key = key; - e->value = value; - return 0; - } - - e = &bucket_at_swap(&swap, IDX_PUT)->p; - e->b.key = key; - e->value = value; - return hashmap_put_boldly(h, hash, &swap, true); -} - -int hashmap_update(Hashmap *h, const void *key, void *value) { - struct plain_hashmap_entry *e; - unsigned hash, idx; - - assert(h); - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) - return -ENOENT; - - e = plain_bucket_at(h, idx); - e->value = value; - return 0; -} - -void *internal_hashmap_get(HashmapBase *h, const void *key) { - struct hashmap_base_entry *e; - unsigned hash, idx; - - if (!h) - return NULL; - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) - return NULL; - - e = bucket_at(h, idx); - return entry_value(h, e); -} - -void *hashmap_get2(Hashmap *h, const void *key, void **key2) { - struct plain_hashmap_entry *e; - unsigned hash, idx; - - if (!h) - return NULL; - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) - return NULL; - - e = plain_bucket_at(h, idx); - if (key2) - *key2 = (void*) e->b.key; - - return e->value; -} - -bool internal_hashmap_contains(HashmapBase *h, const void *key) { - unsigned hash; - - if (!h) - return false; - - hash = bucket_hash(h, key); - return bucket_scan(h, hash, key) != IDX_NIL; -} - -void *internal_hashmap_remove(HashmapBase *h, const void *key) { - struct hashmap_base_entry *e; - unsigned hash, idx; - void *data; - - if (!h) - return NULL; - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) - return NULL; - - e = bucket_at(h, idx); - data = entry_value(h, e); - remove_entry(h, idx); - - return data; -} - -void *hashmap_remove2(Hashmap *h, const void *key, void **rkey) { - struct plain_hashmap_entry *e; - unsigned hash, idx; - void *data; - - if (!h) { - if (rkey) - *rkey = NULL; - return NULL; - } - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) { - if (rkey) - *rkey = NULL; - return NULL; - } - - e = plain_bucket_at(h, idx); - data = e->value; - if (rkey) - *rkey = (void*) e->b.key; - - remove_entry(h, idx); - - return data; -} - -int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) { - struct swap_entries swap; - struct plain_hashmap_entry *e; - unsigned old_hash, new_hash, idx; - - if (!h) - return -ENOENT; - - old_hash = bucket_hash(h, old_key); - idx = bucket_scan(h, old_hash, old_key); - if (idx == IDX_NIL) - return -ENOENT; - - new_hash = bucket_hash(h, new_key); - if (bucket_scan(h, new_hash, new_key) != IDX_NIL) - return -EEXIST; - - remove_entry(h, idx); - - e = &bucket_at_swap(&swap, IDX_PUT)->p; - e->b.key = new_key; - e->value = value; - assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1); - - return 0; -} - -int set_remove_and_put(Set *s, const void *old_key, const void *new_key) { - struct swap_entries swap; - struct hashmap_base_entry *e; - unsigned old_hash, new_hash, idx; - - if (!s) - return -ENOENT; - - old_hash = bucket_hash(s, old_key); - idx = bucket_scan(s, old_hash, old_key); - if (idx == IDX_NIL) - return -ENOENT; - - new_hash = bucket_hash(s, new_key); - if (bucket_scan(s, new_hash, new_key) != IDX_NIL) - return -EEXIST; - - remove_entry(s, idx); - - e = &bucket_at_swap(&swap, IDX_PUT)->p.b; - e->key = new_key; - assert_se(hashmap_put_boldly(s, new_hash, &swap, false) == 1); - - return 0; -} - -int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) { - struct swap_entries swap; - struct plain_hashmap_entry *e; - unsigned old_hash, new_hash, idx_old, idx_new; - - if (!h) - return -ENOENT; - - old_hash = bucket_hash(h, old_key); - idx_old = bucket_scan(h, old_hash, old_key); - if (idx_old == IDX_NIL) - return -ENOENT; - - old_key = bucket_at(HASHMAP_BASE(h), idx_old)->key; - - new_hash = bucket_hash(h, new_key); - idx_new = bucket_scan(h, new_hash, new_key); - if (idx_new != IDX_NIL) - if (idx_old != idx_new) { - remove_entry(h, idx_new); - /* Compensate for a possible backward shift. */ - if (old_key != bucket_at(HASHMAP_BASE(h), idx_old)->key) - idx_old = prev_idx(HASHMAP_BASE(h), idx_old); - assert(old_key == bucket_at(HASHMAP_BASE(h), idx_old)->key); - } - - remove_entry(h, idx_old); - - e = &bucket_at_swap(&swap, IDX_PUT)->p; - e->b.key = new_key; - e->value = value; - assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1); - - return 0; -} - -void *hashmap_remove_value(Hashmap *h, const void *key, void *value) { - struct plain_hashmap_entry *e; - unsigned hash, idx; - - if (!h) - return NULL; - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) - return NULL; - - e = plain_bucket_at(h, idx); - if (e->value != value) - return NULL; - - remove_entry(h, idx); - - return value; -} - -static unsigned find_first_entry(HashmapBase *h) { - Iterator i = ITERATOR_FIRST; - - if (!h || !n_entries(h)) - return IDX_NIL; - - return hashmap_iterate_entry(h, &i); -} - -void *internal_hashmap_first(HashmapBase *h) { - unsigned idx; - - idx = find_first_entry(h); - if (idx == IDX_NIL) - return NULL; - - return entry_value(h, bucket_at(h, idx)); -} - -void *internal_hashmap_first_key(HashmapBase *h) { - struct hashmap_base_entry *e; - unsigned idx; - - idx = find_first_entry(h); - if (idx == IDX_NIL) - return NULL; - - e = bucket_at(h, idx); - return (void*) e->key; -} - -void *internal_hashmap_steal_first(HashmapBase *h) { - struct hashmap_base_entry *e; - void *data; - unsigned idx; - - idx = find_first_entry(h); - if (idx == IDX_NIL) - return NULL; - - e = bucket_at(h, idx); - data = entry_value(h, e); - remove_entry(h, idx); - - return data; -} - -void *internal_hashmap_steal_first_key(HashmapBase *h) { - struct hashmap_base_entry *e; - void *key; - unsigned idx; - - idx = find_first_entry(h); - if (idx == IDX_NIL) - return NULL; - - e = bucket_at(h, idx); - key = (void*) e->key; - remove_entry(h, idx); - - return key; -} - -unsigned internal_hashmap_size(HashmapBase *h) { - - if (!h) - return 0; - - return n_entries(h); -} - -unsigned internal_hashmap_buckets(HashmapBase *h) { - - if (!h) - return 0; - - return n_buckets(h); -} - -int internal_hashmap_merge(Hashmap *h, Hashmap *other) { - Iterator i; - unsigned idx; - - assert(h); - - HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) { - struct plain_hashmap_entry *pe = plain_bucket_at(other, idx); - int r; - - r = hashmap_put(h, pe->b.key, pe->value); - if (r < 0 && r != -EEXIST) - return r; - } - - return 0; -} - -int set_merge(Set *s, Set *other) { - Iterator i; - unsigned idx; - - assert(s); - - HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) { - struct set_entry *se = set_bucket_at(other, idx); - int r; - - r = set_put(s, se->b.key); - if (r < 0) - return r; - } - - return 0; -} - -int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add) { - int r; - - assert(h); - - r = resize_buckets(h, entries_add); - if (r < 0) - return r; - - return 0; -} - -/* - * The same as hashmap_merge(), but every new item from other is moved to h. - * Keys already in h are skipped and stay in other. - * Returns: 0 on success. - * -ENOMEM on alloc failure, in which case no move has been done. - */ -int internal_hashmap_move(HashmapBase *h, HashmapBase *other) { - struct swap_entries swap; - struct hashmap_base_entry *e, *n; - Iterator i; - unsigned idx; - int r; - - assert(h); - - if (!other) - return 0; - - assert(other->type == h->type); - - /* - * This reserves buckets for the worst case, where none of other's - * entries are yet present in h. This is preferable to risking - * an allocation failure in the middle of the moving and having to - * rollback or return a partial result. - */ - r = resize_buckets(h, n_entries(other)); - if (r < 0) - return r; - - HASHMAP_FOREACH_IDX(idx, other, i) { - unsigned h_hash; - - e = bucket_at(other, idx); - h_hash = bucket_hash(h, e->key); - if (bucket_scan(h, h_hash, e->key) != IDX_NIL) - continue; - - n = &bucket_at_swap(&swap, IDX_PUT)->p.b; - n->key = e->key; - if (h->type != HASHMAP_TYPE_SET) - ((struct plain_hashmap_entry*) n)->value = - ((struct plain_hashmap_entry*) e)->value; - assert_se(hashmap_put_boldly(h, h_hash, &swap, false) == 1); - - remove_entry(other, idx); - } - - return 0; -} - -int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key) { - struct swap_entries swap; - unsigned h_hash, other_hash, idx; - struct hashmap_base_entry *e, *n; - int r; - - assert(h); - - h_hash = bucket_hash(h, key); - if (bucket_scan(h, h_hash, key) != IDX_NIL) - return -EEXIST; - - if (!other) - return -ENOENT; - - assert(other->type == h->type); - - other_hash = bucket_hash(other, key); - idx = bucket_scan(other, other_hash, key); - if (idx == IDX_NIL) - return -ENOENT; - - e = bucket_at(other, idx); - - n = &bucket_at_swap(&swap, IDX_PUT)->p.b; - n->key = e->key; - if (h->type != HASHMAP_TYPE_SET) - ((struct plain_hashmap_entry*) n)->value = - ((struct plain_hashmap_entry*) e)->value; - r = hashmap_put_boldly(h, h_hash, &swap, true); - if (r < 0) - return r; - - remove_entry(other, idx); - return 0; -} - -HashmapBase *internal_hashmap_copy(HashmapBase *h) { - HashmapBase *copy; - int r; - - assert(h); - - copy = hashmap_base_new(h->hash_ops, h->type HASHMAP_DEBUG_SRC_ARGS); - if (!copy) - return NULL; - - switch (h->type) { - case HASHMAP_TYPE_PLAIN: - case HASHMAP_TYPE_ORDERED: - r = hashmap_merge((Hashmap*)copy, (Hashmap*)h); - break; - case HASHMAP_TYPE_SET: - r = set_merge((Set*)copy, (Set*)h); - break; - default: - assert_not_reached("Unknown hashmap type"); - } - - if (r < 0) { - internal_hashmap_free(copy); - return NULL; - } - - return copy; -} - -char **internal_hashmap_get_strv(HashmapBase *h) { - char **sv; - Iterator i; - unsigned idx, n; - - sv = new(char*, n_entries(h)+1); - if (!sv) - return NULL; - - n = 0; - HASHMAP_FOREACH_IDX(idx, h, i) - sv[n++] = entry_value(h, bucket_at(h, idx)); - sv[n] = NULL; - - return sv; -} - -void *ordered_hashmap_next(OrderedHashmap *h, const void *key) { - struct ordered_hashmap_entry *e; - unsigned hash, idx; - - if (!h) - return NULL; - - hash = bucket_hash(h, key); - idx = bucket_scan(h, hash, key); - if (idx == IDX_NIL) - return NULL; - - e = ordered_bucket_at(h, idx); - if (e->iterate_next == IDX_NIL) - return NULL; - return ordered_bucket_at(h, e->iterate_next)->p.value; -} - -int set_consume(Set *s, void *value) { - int r; - - r = set_put(s, value); - if (r <= 0) - free(value); - - return r; -} - -int set_put_strdup(Set *s, const char *p) { - char *c; - int r; - - assert(s); - assert(p); - - c = strdup(p); - if (!c) - return -ENOMEM; - - r = set_consume(s, c); - if (r == -EEXIST) - return 0; - - return r; -} - -int set_put_strdupv(Set *s, char **l) { - int n = 0, r; - char **i; - - STRV_FOREACH(i, l) { - r = set_put_strdup(s, *i); - if (r < 0) - return r; - - n += r; - } - - return n; -} diff --git a/src/basic/hashmap.h b/src/basic/hashmap.h deleted file mode 100644 index 6d1ae48b21..0000000000 --- a/src/basic/hashmap.h +++ /dev/null @@ -1,372 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2014 Michal Schmidt - - 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 . -***/ - -#include -#include -#include - -#include "hash-funcs.h" -#include "macro.h" -#include "util.h" - -/* - * A hash table implementation. As a minor optimization a NULL hashmap object - * will be treated as empty hashmap for all read operations. That way it is not - * necessary to instantiate an object for each Hashmap use. - * - * If ENABLE_DEBUG_HASHMAP is defined (by configuring with --enable-debug=hashmap), - * the implemention will: - * - store extra data for debugging and statistics (see tools/gdb-sd_dump_hashmaps.py) - * - perform extra checks for invalid use of iterators - */ - -#define HASH_KEY_SIZE 16 - -/* The base type for all hashmap and set types. Many functions in the - * implementation take (HashmapBase*) parameters and are run-time polymorphic, - * though the API is not meant to be polymorphic (do not call functions - * internal_*() directly). */ -typedef struct HashmapBase HashmapBase; - -/* Specific hashmap/set types */ -typedef struct Hashmap Hashmap; /* Maps keys to values */ -typedef struct OrderedHashmap OrderedHashmap; /* Like Hashmap, but also remembers entry insertion order */ -typedef struct Set Set; /* Stores just keys */ - -/* Ideally the Iterator would be an opaque struct, but it is instantiated - * by hashmap users, so the definition has to be here. Do not use its fields - * directly. */ -typedef struct { - unsigned idx; /* index of an entry to be iterated next */ - const void *next_key; /* expected value of that entry's key pointer */ -#ifdef ENABLE_DEBUG_HASHMAP - unsigned put_count; /* hashmap's put_count recorded at start of iteration */ - unsigned rem_count; /* hashmap's rem_count in previous iteration */ - unsigned prev_idx; /* idx in previous iteration */ -#endif -} Iterator; - -#define _IDX_ITERATOR_FIRST (UINT_MAX - 1) -#define ITERATOR_FIRST ((Iterator) { .idx = _IDX_ITERATOR_FIRST, .next_key = NULL }) - -/* Macros for type checking */ -#define PTR_COMPATIBLE_WITH_HASHMAP_BASE(h) \ - (__builtin_types_compatible_p(typeof(h), HashmapBase*) || \ - __builtin_types_compatible_p(typeof(h), Hashmap*) || \ - __builtin_types_compatible_p(typeof(h), OrderedHashmap*) || \ - __builtin_types_compatible_p(typeof(h), Set*)) - -#define PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h) \ - (__builtin_types_compatible_p(typeof(h), Hashmap*) || \ - __builtin_types_compatible_p(typeof(h), OrderedHashmap*)) \ - -#define HASHMAP_BASE(h) \ - __builtin_choose_expr(PTR_COMPATIBLE_WITH_HASHMAP_BASE(h), \ - (HashmapBase*)(h), \ - (void)0) - -#define PLAIN_HASHMAP(h) \ - __builtin_choose_expr(PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h), \ - (Hashmap*)(h), \ - (void)0) - -#ifdef ENABLE_DEBUG_HASHMAP -# define HASHMAP_DEBUG_PARAMS , const char *func, const char *file, int line -# define HASHMAP_DEBUG_SRC_ARGS , __func__, __FILE__, __LINE__ -# define HASHMAP_DEBUG_PASS_ARGS , func, file, line -#else -# define HASHMAP_DEBUG_PARAMS -# define HASHMAP_DEBUG_SRC_ARGS -# define HASHMAP_DEBUG_PASS_ARGS -#endif - -Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -#define hashmap_new(ops) internal_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) -#define ordered_hashmap_new(ops) internal_ordered_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) - -HashmapBase *internal_hashmap_free(HashmapBase *h); -static inline Hashmap *hashmap_free(Hashmap *h) { - return (void*)internal_hashmap_free(HASHMAP_BASE(h)); -} -static inline OrderedHashmap *ordered_hashmap_free(OrderedHashmap *h) { - return (void*)internal_hashmap_free(HASHMAP_BASE(h)); -} - -HashmapBase *internal_hashmap_free_free(HashmapBase *h); -static inline Hashmap *hashmap_free_free(Hashmap *h) { - return (void*)internal_hashmap_free_free(HASHMAP_BASE(h)); -} -static inline OrderedHashmap *ordered_hashmap_free_free(OrderedHashmap *h) { - return (void*)internal_hashmap_free_free(HASHMAP_BASE(h)); -} - -Hashmap *hashmap_free_free_free(Hashmap *h); -static inline OrderedHashmap *ordered_hashmap_free_free_free(OrderedHashmap *h) { - return (void*)hashmap_free_free_free(PLAIN_HASHMAP(h)); -} - -HashmapBase *internal_hashmap_copy(HashmapBase *h); -static inline Hashmap *hashmap_copy(Hashmap *h) { - return (Hashmap*) internal_hashmap_copy(HASHMAP_BASE(h)); -} -static inline OrderedHashmap *ordered_hashmap_copy(OrderedHashmap *h) { - return (OrderedHashmap*) internal_hashmap_copy(HASHMAP_BASE(h)); -} - -int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -#define hashmap_ensure_allocated(h, ops) internal_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) -#define ordered_hashmap_ensure_allocated(h, ops) internal_ordered_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) - -int hashmap_put(Hashmap *h, const void *key, void *value); -static inline int ordered_hashmap_put(OrderedHashmap *h, const void *key, void *value) { - return hashmap_put(PLAIN_HASHMAP(h), key, value); -} - -int hashmap_update(Hashmap *h, const void *key, void *value); -static inline int ordered_hashmap_update(OrderedHashmap *h, const void *key, void *value) { - return hashmap_update(PLAIN_HASHMAP(h), key, value); -} - -int hashmap_replace(Hashmap *h, const void *key, void *value); -static inline int ordered_hashmap_replace(OrderedHashmap *h, const void *key, void *value) { - return hashmap_replace(PLAIN_HASHMAP(h), key, value); -} - -void *internal_hashmap_get(HashmapBase *h, const void *key); -static inline void *hashmap_get(Hashmap *h, const void *key) { - return internal_hashmap_get(HASHMAP_BASE(h), key); -} -static inline void *ordered_hashmap_get(OrderedHashmap *h, const void *key) { - return internal_hashmap_get(HASHMAP_BASE(h), key); -} - -void *hashmap_get2(Hashmap *h, const void *key, void **rkey); -static inline void *ordered_hashmap_get2(OrderedHashmap *h, const void *key, void **rkey) { - return hashmap_get2(PLAIN_HASHMAP(h), key, rkey); -} - -bool internal_hashmap_contains(HashmapBase *h, const void *key); -static inline bool hashmap_contains(Hashmap *h, const void *key) { - return internal_hashmap_contains(HASHMAP_BASE(h), key); -} -static inline bool ordered_hashmap_contains(OrderedHashmap *h, const void *key) { - return internal_hashmap_contains(HASHMAP_BASE(h), key); -} - -void *internal_hashmap_remove(HashmapBase *h, const void *key); -static inline void *hashmap_remove(Hashmap *h, const void *key) { - return internal_hashmap_remove(HASHMAP_BASE(h), key); -} -static inline void *ordered_hashmap_remove(OrderedHashmap *h, const void *key) { - return internal_hashmap_remove(HASHMAP_BASE(h), key); -} - -void *hashmap_remove2(Hashmap *h, const void *key, void **rkey); -static inline void *ordered_hashmap_remove2(OrderedHashmap *h, const void *key, void **rkey) { - return hashmap_remove2(PLAIN_HASHMAP(h), key, rkey); -} - -void *hashmap_remove_value(Hashmap *h, const void *key, void *value); -static inline void *ordered_hashmap_remove_value(OrderedHashmap *h, const void *key, void *value) { - return hashmap_remove_value(PLAIN_HASHMAP(h), key, value); -} - -int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value); -static inline int ordered_hashmap_remove_and_put(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) { - return hashmap_remove_and_put(PLAIN_HASHMAP(h), old_key, new_key, value); -} - -int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value); -static inline int ordered_hashmap_remove_and_replace(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) { - return hashmap_remove_and_replace(PLAIN_HASHMAP(h), old_key, new_key, value); -} - -/* Since merging data from a OrderedHashmap into a Hashmap or vice-versa - * should just work, allow this by having looser type-checking here. */ -int internal_hashmap_merge(Hashmap *h, Hashmap *other); -#define hashmap_merge(h, other) internal_hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other)) -#define ordered_hashmap_merge(h, other) hashmap_merge(h, other) - -int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add); -static inline int hashmap_reserve(Hashmap *h, unsigned entries_add) { - return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); -} -static inline int ordered_hashmap_reserve(OrderedHashmap *h, unsigned entries_add) { - return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); -} - -int internal_hashmap_move(HashmapBase *h, HashmapBase *other); -/* Unlike hashmap_merge, hashmap_move does not allow mixing the types. */ -static inline int hashmap_move(Hashmap *h, Hashmap *other) { - return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other)); -} -static inline int ordered_hashmap_move(OrderedHashmap *h, OrderedHashmap *other) { - return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other)); -} - -int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key); -static inline int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) { - return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key); -} -static inline int ordered_hashmap_move_one(OrderedHashmap *h, OrderedHashmap *other, const void *key) { - return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key); -} - -unsigned internal_hashmap_size(HashmapBase *h) _pure_; -static inline unsigned hashmap_size(Hashmap *h) { - return internal_hashmap_size(HASHMAP_BASE(h)); -} -static inline unsigned ordered_hashmap_size(OrderedHashmap *h) { - return internal_hashmap_size(HASHMAP_BASE(h)); -} - -static inline bool hashmap_isempty(Hashmap *h) { - return hashmap_size(h) == 0; -} -static inline bool ordered_hashmap_isempty(OrderedHashmap *h) { - return ordered_hashmap_size(h) == 0; -} - -unsigned internal_hashmap_buckets(HashmapBase *h) _pure_; -static inline unsigned hashmap_buckets(Hashmap *h) { - return internal_hashmap_buckets(HASHMAP_BASE(h)); -} -static inline unsigned ordered_hashmap_buckets(OrderedHashmap *h) { - return internal_hashmap_buckets(HASHMAP_BASE(h)); -} - -bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key); -static inline bool hashmap_iterate(Hashmap *h, Iterator *i, void **value, const void **key) { - return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key); -} -static inline bool ordered_hashmap_iterate(OrderedHashmap *h, Iterator *i, void **value, const void **key) { - return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key); -} - -void internal_hashmap_clear(HashmapBase *h); -static inline void hashmap_clear(Hashmap *h) { - internal_hashmap_clear(HASHMAP_BASE(h)); -} -static inline void ordered_hashmap_clear(OrderedHashmap *h) { - internal_hashmap_clear(HASHMAP_BASE(h)); -} - -void internal_hashmap_clear_free(HashmapBase *h); -static inline void hashmap_clear_free(Hashmap *h) { - internal_hashmap_clear_free(HASHMAP_BASE(h)); -} -static inline void ordered_hashmap_clear_free(OrderedHashmap *h) { - internal_hashmap_clear_free(HASHMAP_BASE(h)); -} - -void hashmap_clear_free_free(Hashmap *h); -static inline void ordered_hashmap_clear_free_free(OrderedHashmap *h) { - hashmap_clear_free_free(PLAIN_HASHMAP(h)); -} - -/* - * Note about all *_first*() functions - * - * For plain Hashmaps and Sets the order of entries is undefined. - * The functions find whatever entry is first in the implementation - * internal order. - * - * Only for OrderedHashmaps the order is well defined and finding - * the first entry is O(1). - */ - -void *internal_hashmap_steal_first(HashmapBase *h); -static inline void *hashmap_steal_first(Hashmap *h) { - return internal_hashmap_steal_first(HASHMAP_BASE(h)); -} -static inline void *ordered_hashmap_steal_first(OrderedHashmap *h) { - return internal_hashmap_steal_first(HASHMAP_BASE(h)); -} - -void *internal_hashmap_steal_first_key(HashmapBase *h); -static inline void *hashmap_steal_first_key(Hashmap *h) { - return internal_hashmap_steal_first_key(HASHMAP_BASE(h)); -} -static inline void *ordered_hashmap_steal_first_key(OrderedHashmap *h) { - return internal_hashmap_steal_first_key(HASHMAP_BASE(h)); -} - -void *internal_hashmap_first_key(HashmapBase *h) _pure_; -static inline void *hashmap_first_key(Hashmap *h) { - return internal_hashmap_first_key(HASHMAP_BASE(h)); -} -static inline void *ordered_hashmap_first_key(OrderedHashmap *h) { - return internal_hashmap_first_key(HASHMAP_BASE(h)); -} - -void *internal_hashmap_first(HashmapBase *h) _pure_; -static inline void *hashmap_first(Hashmap *h) { - return internal_hashmap_first(HASHMAP_BASE(h)); -} -static inline void *ordered_hashmap_first(OrderedHashmap *h) { - return internal_hashmap_first(HASHMAP_BASE(h)); -} - -/* no hashmap_next */ -void *ordered_hashmap_next(OrderedHashmap *h, const void *key); - -char **internal_hashmap_get_strv(HashmapBase *h); -static inline char **hashmap_get_strv(Hashmap *h) { - return internal_hashmap_get_strv(HASHMAP_BASE(h)); -} -static inline char **ordered_hashmap_get_strv(OrderedHashmap *h) { - return internal_hashmap_get_strv(HASHMAP_BASE(h)); -} - -/* - * Hashmaps are iterated in unpredictable order. - * OrderedHashmaps are an exception to this. They are iterated in the order - * the entries were inserted. - * It is safe to remove the current entry. - */ -#define HASHMAP_FOREACH(e, h, i) \ - for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), NULL); ) - -#define ORDERED_HASHMAP_FOREACH(e, h, i) \ - for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), NULL); ) - -#define HASHMAP_FOREACH_KEY(e, k, h, i) \ - for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); ) - -#define ORDERED_HASHMAP_FOREACH_KEY(e, k, h, i) \ - for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); ) - -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free); - -#define _cleanup_hashmap_free_ _cleanup_(hashmap_freep) -#define _cleanup_hashmap_free_free_ _cleanup_(hashmap_free_freep) -#define _cleanup_hashmap_free_free_free_ _cleanup_(hashmap_free_free_freep) -#define _cleanup_ordered_hashmap_free_ _cleanup_(ordered_hashmap_freep) -#define _cleanup_ordered_hashmap_free_free_ _cleanup_(ordered_hashmap_free_freep) -#define _cleanup_ordered_hashmap_free_free_free_ _cleanup_(ordered_hashmap_free_free_freep) diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c deleted file mode 100644 index 592df53cb5..0000000000 --- a/src/basic/hexdecoct.c +++ /dev/null @@ -1,753 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "alloc-util.h" -#include "hexdecoct.h" -#include "macro.h" - -char octchar(int x) { - return '0' + (x & 7); -} - -int unoctchar(char c) { - - if (c >= '0' && c <= '7') - return c - '0'; - - return -EINVAL; -} - -char decchar(int x) { - return '0' + (x % 10); -} - -int undecchar(char c) { - - if (c >= '0' && c <= '9') - return c - '0'; - - return -EINVAL; -} - -char hexchar(int x) { - static const char table[16] = "0123456789abcdef"; - - return table[x & 15]; -} - -int unhexchar(char c) { - - if (c >= '0' && c <= '9') - return c - '0'; - - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - - return -EINVAL; -} - -char *hexmem(const void *p, size_t l) { - char *r, *z; - const uint8_t *x; - - z = r = malloc(l * 2 + 1); - if (!r) - return NULL; - - for (x = p; x < (const uint8_t*) p + l; x++) { - *(z++) = hexchar(*x >> 4); - *(z++) = hexchar(*x & 15); - } - - *z = 0; - return r; -} - -int unhexmem(const char *p, size_t l, void **mem, size_t *len) { - _cleanup_free_ uint8_t *r = NULL; - uint8_t *z; - const char *x; - - assert(mem); - assert(len); - assert(p); - - z = r = malloc((l + 1) / 2 + 1); - if (!r) - return -ENOMEM; - - for (x = p; x < p + l; x += 2) { - int a, b; - - a = unhexchar(x[0]); - if (a < 0) - return a; - else if (x+1 < p + l) { - b = unhexchar(x[1]); - if (b < 0) - return b; - } else - b = 0; - - *(z++) = (uint8_t) a << 4 | (uint8_t) b; - } - - *z = 0; - - *mem = r; - r = NULL; - *len = (l + 1) / 2; - - return 0; -} - -/* https://tools.ietf.org/html/rfc4648#section-6 - * Notice that base32hex differs from base32 in the alphabet it uses. - * The distinction is that the base32hex representation preserves the - * order of the underlying data when compared as bytestrings, this is - * useful when representing NSEC3 hashes, as one can then verify the - * order of hashes directly from their representation. */ -char base32hexchar(int x) { - static const char table[32] = "0123456789" - "ABCDEFGHIJKLMNOPQRSTUV"; - - return table[x & 31]; -} - -int unbase32hexchar(char c) { - unsigned offset; - - if (c >= '0' && c <= '9') - return c - '0'; - - offset = '9' - '0' + 1; - - if (c >= 'A' && c <= 'V') - return c - 'A' + offset; - - return -EINVAL; -} - -char *base32hexmem(const void *p, size_t l, bool padding) { - char *r, *z; - const uint8_t *x; - size_t len; - - if (padding) - /* five input bytes makes eight output bytes, padding is added so we must round up */ - len = 8 * (l + 4) / 5; - else { - /* same, but round down as there is no padding */ - len = 8 * l / 5; - - switch (l % 5) { - case 4: - len += 7; - break; - case 3: - len += 5; - break; - case 2: - len += 4; - break; - case 1: - len += 2; - break; - } - } - - z = r = malloc(len + 1); - if (!r) - return NULL; - - for (x = p; x < (const uint8_t*) p + (l / 5) * 5; x += 5) { - /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ - x[3] == QQQQQQQQ; x[4] == WWWWWWWW */ - *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ - *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ - *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ - *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ - *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */ - *(z++) = base32hexchar((x[3] & 127) >> 2); /* 000QQQQQ */ - *(z++) = base32hexchar((x[3] & 3) << 3 | x[4] >> 5); /* 000QQWWW */ - *(z++) = base32hexchar((x[4] & 31)); /* 000WWWWW */ - } - - switch (l % 5) { - case 4: - *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ - *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ - *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ - *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ - *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */ - *(z++) = base32hexchar((x[3] & 127) >> 2); /* 000QQQQQ */ - *(z++) = base32hexchar((x[3] & 3) << 3); /* 000QQ000 */ - if (padding) - *(z++) = '='; - - break; - - case 3: - *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ - *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ - *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ - *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ - *(z++) = base32hexchar((x[2] & 15) << 1); /* 000ZZZZ0 */ - if (padding) { - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - } - - break; - - case 2: - *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ - *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ - *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ - *(z++) = base32hexchar((x[1] & 1) << 4); /* 000Y0000 */ - if (padding) { - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - } - - break; - - case 1: - *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ - *(z++) = base32hexchar((x[0] & 7) << 2); /* 000XXX00 */ - if (padding) { - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - *(z++) = '='; - } - - break; - } - - *z = 0; - return r; -} - -int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *_len) { - _cleanup_free_ uint8_t *r = NULL; - int a, b, c, d, e, f, g, h; - uint8_t *z; - const char *x; - size_t len; - unsigned pad = 0; - - assert(p); - - /* padding ensures any base32hex input has input divisible by 8 */ - if (padding && l % 8 != 0) - return -EINVAL; - - if (padding) { - /* strip the padding */ - while (l > 0 && p[l - 1] == '=' && pad < 7) { - pad ++; - l --; - } - } - - /* a group of eight input bytes needs five output bytes, in case of - padding we need to add some extra bytes */ - len = (l / 8) * 5; - - switch (l % 8) { - case 7: - len += 4; - break; - case 5: - len += 3; - break; - case 4: - len += 2; - break; - case 2: - len += 1; - break; - case 0: - break; - default: - return -EINVAL; - } - - z = r = malloc(len + 1); - if (!r) - return -ENOMEM; - - for (x = p; x < p + (l / 8) * 8; x += 8) { - /* a == 000XXXXX; b == 000YYYYY; c == 000ZZZZZ; d == 000WWWWW - e == 000SSSSS; f == 000QQQQQ; g == 000VVVVV; h == 000RRRRR */ - a = unbase32hexchar(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase32hexchar(x[1]); - if (b < 0) - return -EINVAL; - - c = unbase32hexchar(x[2]); - if (c < 0) - return -EINVAL; - - d = unbase32hexchar(x[3]); - if (d < 0) - return -EINVAL; - - e = unbase32hexchar(x[4]); - if (e < 0) - return -EINVAL; - - f = unbase32hexchar(x[5]); - if (f < 0) - return -EINVAL; - - g = unbase32hexchar(x[6]); - if (g < 0) - return -EINVAL; - - h = unbase32hexchar(x[7]); - if (h < 0) - return -EINVAL; - - *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ - *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ - *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ - *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */ - *(z++) = (uint8_t) g << 5 | (uint8_t) h; /* VVVRRRRR */ - } - - switch (l % 8) { - case 7: - a = unbase32hexchar(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase32hexchar(x[1]); - if (b < 0) - return -EINVAL; - - c = unbase32hexchar(x[2]); - if (c < 0) - return -EINVAL; - - d = unbase32hexchar(x[3]); - if (d < 0) - return -EINVAL; - - e = unbase32hexchar(x[4]); - if (e < 0) - return -EINVAL; - - f = unbase32hexchar(x[5]); - if (f < 0) - return -EINVAL; - - g = unbase32hexchar(x[6]); - if (g < 0) - return -EINVAL; - - /* g == 000VV000 */ - if (g & 7) - return -EINVAL; - - *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ - *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ - *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ - *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */ - - break; - case 5: - a = unbase32hexchar(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase32hexchar(x[1]); - if (b < 0) - return -EINVAL; - - c = unbase32hexchar(x[2]); - if (c < 0) - return -EINVAL; - - d = unbase32hexchar(x[3]); - if (d < 0) - return -EINVAL; - - e = unbase32hexchar(x[4]); - if (e < 0) - return -EINVAL; - - /* e == 000SSSS0 */ - if (e & 1) - return -EINVAL; - - *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ - *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ - *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ - - break; - case 4: - a = unbase32hexchar(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase32hexchar(x[1]); - if (b < 0) - return -EINVAL; - - c = unbase32hexchar(x[2]); - if (c < 0) - return -EINVAL; - - d = unbase32hexchar(x[3]); - if (d < 0) - return -EINVAL; - - /* d == 000W0000 */ - if (d & 15) - return -EINVAL; - - *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ - *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ - - break; - case 2: - a = unbase32hexchar(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase32hexchar(x[1]); - if (b < 0) - return -EINVAL; - - /* b == 000YYY00 */ - if (b & 3) - return -EINVAL; - - *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ - - break; - case 0: - break; - default: - return -EINVAL; - } - - *z = 0; - - *mem = r; - r = NULL; - *_len = len; - - return 0; -} - -/* https://tools.ietf.org/html/rfc4648#section-4 */ -char base64char(int x) { - static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "abcdefghijklmnopqrstuvwxyz" - "0123456789+/"; - return table[x & 63]; -} - -int unbase64char(char c) { - unsigned offset; - - if (c >= 'A' && c <= 'Z') - return c - 'A'; - - offset = 'Z' - 'A' + 1; - - if (c >= 'a' && c <= 'z') - return c - 'a' + offset; - - offset += 'z' - 'a' + 1; - - if (c >= '0' && c <= '9') - return c - '0' + offset; - - offset += '9' - '0' + 1; - - if (c == '+') - return offset; - - offset ++; - - if (c == '/') - return offset; - - return -EINVAL; -} - -ssize_t base64mem(const void *p, size_t l, char **out) { - char *r, *z; - const uint8_t *x; - - /* three input bytes makes four output bytes, padding is added so we must round up */ - z = r = malloc(4 * (l + 2) / 3 + 1); - if (!r) - return -ENOMEM; - - for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) { - /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */ - *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ - *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ - *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */ - *(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */ - } - - switch (l % 3) { - case 2: - *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ - *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ - *(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */ - *(z++) = '='; - - break; - case 1: - *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ - *(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */ - *(z++) = '='; - *(z++) = '='; - - break; - } - - *z = 0; - *out = r; - return z - r; -} - -static int base64_append_width(char **prefix, int plen, - const char *sep, int indent, - const void *p, size_t l, - int width) { - - _cleanup_free_ char *x = NULL; - char *t, *s; - ssize_t slen, len, avail; - int line, lines; - - len = base64mem(p, l, &x); - if (len <= 0) - return len; - - lines = (len + width - 1) / width; - - slen = sep ? strlen(sep) : 0; - t = realloc(*prefix, plen + 1 + slen + (indent + width + 1) * lines); - if (!t) - return -ENOMEM; - - memcpy(t + plen, sep, slen); - - for (line = 0, s = t + plen + slen, avail = len; line < lines; line++) { - int act = MIN(width, avail); - - if (line > 0 || sep) { - memset(s, ' ', indent); - s += indent; - } - - memcpy(s, x + width * line, act); - s += act; - *(s++) = line < lines - 1 ? '\n' : '\0'; - avail -= act; - } - assert(avail == 0); - - *prefix = t; - return 0; -} - -int base64_append(char **prefix, int plen, - const void *p, size_t l, - int indent, int width) { - if (plen > width / 2 || plen + indent > width) - /* leave indent on the left, keep last column free */ - return base64_append_width(prefix, plen, "\n", indent, p, l, width - indent - 1); - else - /* leave plen on the left, keep last column free */ - return base64_append_width(prefix, plen, NULL, plen, p, l, width - plen - 1); -}; - - -int unbase64mem(const char *p, size_t l, void **mem, size_t *_len) { - _cleanup_free_ uint8_t *r = NULL; - int a, b, c, d; - uint8_t *z; - const char *x; - size_t len; - - assert(p); - - /* padding ensures any base63 input has input divisible by 4 */ - if (l % 4 != 0) - return -EINVAL; - - /* strip the padding */ - if (l > 0 && p[l - 1] == '=') - l --; - if (l > 0 && p[l - 1] == '=') - l --; - - /* a group of four input bytes needs three output bytes, in case of - padding we need to add two or three extra bytes */ - len = (l / 4) * 3 + (l % 4 ? (l % 4) - 1 : 0); - - z = r = malloc(len + 1); - if (!r) - return -ENOMEM; - - for (x = p; x < p + (l / 4) * 4; x += 4) { - /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */ - a = unbase64char(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase64char(x[1]); - if (b < 0) - return -EINVAL; - - c = unbase64char(x[2]); - if (c < 0) - return -EINVAL; - - d = unbase64char(x[3]); - if (d < 0) - return -EINVAL; - - *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ - *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ - *(z++) = (uint8_t) c << 6 | (uint8_t) d; /* ZZWWWWWW */ - } - - switch (l % 4) { - case 3: - a = unbase64char(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase64char(x[1]); - if (b < 0) - return -EINVAL; - - c = unbase64char(x[2]); - if (c < 0) - return -EINVAL; - - /* c == 00ZZZZ00 */ - if (c & 3) - return -EINVAL; - - *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ - *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ - - break; - case 2: - a = unbase64char(x[0]); - if (a < 0) - return -EINVAL; - - b = unbase64char(x[1]); - if (b < 0) - return -EINVAL; - - /* b == 00YY0000 */ - if (b & 15) - return -EINVAL; - - *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */ - - break; - case 0: - - break; - default: - return -EINVAL; - } - - *z = 0; - - *mem = r; - r = NULL; - *_len = len; - - return 0; -} - -void hexdump(FILE *f, const void *p, size_t s) { - const uint8_t *b = p; - unsigned n = 0; - - assert(s == 0 || b); - - while (s > 0) { - size_t i; - - fprintf(f, "%04x ", n); - - for (i = 0; i < 16; i++) { - - if (i >= s) - fputs(" ", f); - else - fprintf(f, "%02x ", b[i]); - - if (i == 7) - fputc(' ', f); - } - - fputc(' ', f); - - for (i = 0; i < 16; i++) { - - if (i >= s) - fputc(' ', f); - else - fputc(isprint(b[i]) ? (char) b[i] : '.', f); - } - - fputc('\n', f); - - if (s < 16) - break; - - n += 16; - b += 16; - s -= 16; - } -} diff --git a/src/basic/hexdecoct.h b/src/basic/hexdecoct.h deleted file mode 100644 index 1ba2f69ebd..0000000000 --- a/src/basic/hexdecoct.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "macro.h" - -char octchar(int x) _const_; -int unoctchar(char c) _const_; - -char decchar(int x) _const_; -int undecchar(char c) _const_; - -char hexchar(int x) _const_; -int unhexchar(char c) _const_; - -char *hexmem(const void *p, size_t l); -int unhexmem(const char *p, size_t l, void **mem, size_t *len); - -char base32hexchar(int x) _const_; -int unbase32hexchar(char c) _const_; - -char base64char(int x) _const_; -int unbase64char(char c) _const_; - -char *base32hexmem(const void *p, size_t l, bool padding); -int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len); - -ssize_t base64mem(const void *p, size_t l, char **out); -int base64_append(char **prefix, int plen, - const void *p, size_t l, - int margin, int width); -int unbase64mem(const char *p, size_t l, void **mem, size_t *len); - -void hexdump(FILE *f, const void *p, size_t s); diff --git a/src/basic/hostname-util.c b/src/basic/hostname-util.c deleted file mode 100644 index 7bb23448ed..0000000000 --- a/src/basic/hostname-util.c +++ /dev/null @@ -1,224 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "fd-util.h" -#include "fileio.h" -#include "hostname-util.h" -#include "macro.h" -#include "string-util.h" - -bool hostname_is_set(void) { - struct utsname u; - - assert_se(uname(&u) >= 0); - - if (isempty(u.nodename)) - return false; - - /* This is the built-in kernel default host name */ - if (streq(u.nodename, "(none)")) - return false; - - return true; -} - -char* gethostname_malloc(void) { - struct utsname u; - - assert_se(uname(&u) >= 0); - - if (isempty(u.nodename) || streq(u.nodename, "(none)")) - return strdup(u.sysname); - - return strdup(u.nodename); -} - -static bool hostname_valid_char(char c) { - return - (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || - c == '-' || - c == '_' || - c == '.'; -} - -/** - * Check if s looks like a valid host name or FQDN. This does not do - * full DNS validation, but only checks if the name is composed of - * allowed characters and the length is not above the maximum allowed - * by Linux (c.f. dns_name_is_valid()). Trailing dot is allowed if - * allow_trailing_dot is true and at least two components are present - * in the name. Note that due to the restricted charset and length - * this call is substantially more conservative than - * dns_name_is_valid(). - */ -bool hostname_is_valid(const char *s, bool allow_trailing_dot) { - unsigned n_dots = 0; - const char *p; - bool dot; - - if (isempty(s)) - return false; - - /* Doesn't accept empty hostnames, hostnames with - * leading dots, and hostnames with multiple dots in a - * sequence. Also ensures that the length stays below - * HOST_NAME_MAX. */ - - for (p = s, dot = true; *p; p++) { - if (*p == '.') { - if (dot) - return false; - - dot = true; - n_dots ++; - } else { - if (!hostname_valid_char(*p)) - return false; - - dot = false; - } - } - - if (dot && (n_dots < 2 || !allow_trailing_dot)) - return false; - - if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on - * Linux, but DNS allows domain names - * up to 255 characters */ - return false; - - return true; -} - -char* hostname_cleanup(char *s) { - char *p, *d; - bool dot; - - assert(s); - - for (p = s, d = s, dot = true; *p; p++) { - if (*p == '.') { - if (dot) - continue; - - *(d++) = '.'; - dot = true; - } else if (hostname_valid_char(*p)) { - *(d++) = *p; - dot = false; - } - - } - - if (dot && d > s) - d[-1] = 0; - else - *d = 0; - - strshorten(s, HOST_NAME_MAX); - - return s; -} - -bool is_localhost(const char *hostname) { - assert(hostname); - - /* This tries to identify local host and domain names - * described in RFC6761 plus the redhatism of .localdomain */ - - return strcaseeq(hostname, "localhost") || - strcaseeq(hostname, "localhost.") || - strcaseeq(hostname, "localdomain.") || - strcaseeq(hostname, "localdomain") || - endswith_no_case(hostname, ".localhost") || - endswith_no_case(hostname, ".localhost.") || - endswith_no_case(hostname, ".localdomain") || - endswith_no_case(hostname, ".localdomain."); -} - -bool is_gateway_hostname(const char *hostname) { - assert(hostname); - - /* This tries to identify the valid syntaxes for the our - * synthetic "gateway" host. */ - - return - strcaseeq(hostname, "gateway") || - strcaseeq(hostname, "gateway."); -} - -int sethostname_idempotent(const char *s) { - char buf[HOST_NAME_MAX + 1] = {}; - - assert(s); - - if (gethostname(buf, sizeof(buf)) < 0) - return -errno; - - if (streq(buf, s)) - return 0; - - if (sethostname(s, strlen(s)) < 0) - return -errno; - - return 1; -} - -int read_hostname_config(const char *path, char **hostname) { - _cleanup_fclose_ FILE *f = NULL; - char l[LINE_MAX]; - char *name = NULL; - - assert(path); - assert(hostname); - - f = fopen(path, "re"); - if (!f) - return -errno; - - /* may have comments, ignore them */ - FOREACH_LINE(l, f, return -errno) { - truncate_nl(l); - if (l[0] != '\0' && l[0] != '#') { - /* found line with value */ - name = hostname_cleanup(l); - name = strdup(name); - if (!name) - return -ENOMEM; - break; - } - } - - if (!name) - /* no non-empty line found */ - return -ENOENT; - - *hostname = name; - return 0; -} diff --git a/src/basic/hostname-util.h b/src/basic/hostname-util.h deleted file mode 100644 index d062eddea1..0000000000 --- a/src/basic/hostname-util.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2015 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 . -***/ - -#include - -#include "macro.h" - -bool hostname_is_set(void); - -char* gethostname_malloc(void); - -bool hostname_is_valid(const char *s, bool allow_trailing_dot) _pure_; -char* hostname_cleanup(char *s); - -#define machine_name_is_valid(s) hostname_is_valid(s, false) - -bool is_localhost(const char *hostname); -bool is_gateway_hostname(const char *hostname); - -int sethostname_idempotent(const char *s); - -int read_hostname_config(const char *path, char **hostname); diff --git a/src/basic/in-addr-util.c b/src/basic/in-addr-util.c deleted file mode 100644 index 245107ebb8..0000000000 --- a/src/basic/in-addr-util.c +++ /dev/null @@ -1,356 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "in-addr-util.h" -#include "macro.h" -#include "util.h" - -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; - - 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 -EAFNOSUPPORT; -} - -int in_addr_is_link_local(int family, const union in_addr_union *u) { - assert(u); - - if (family == AF_INET) - return (be32toh(u->in.s_addr) & UINT32_C(0xFFFF0000)) == (UINT32_C(169) << 24 | UINT32_C(254) << 16); - - if (family == AF_INET6) - return IN6_IS_ADDR_LINKLOCAL(&u->in6); - - return -EAFNOSUPPORT; -} - -int in_addr_is_localhost(int family, const union in_addr_union *u) { - assert(u); - - if (family == AF_INET) - /* All of 127.x.x.x is localhost. */ - return (be32toh(u->in.s_addr) & UINT32_C(0xFF000000)) == UINT32_C(127) << 24; - - if (family == AF_INET6) - return IN6_IS_ADDR_LOOPBACK(&u->in6); - - return -EAFNOSUPPORT; -} - -int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b) { - assert(a); - assert(b); - - if (family == AF_INET) - return a->in.s_addr == b->in.s_addr; - - if (family == AF_INET6) - return - a->in6.s6_addr32[0] == b->in6.s6_addr32[0] && - a->in6.s6_addr32[1] == b->in6.s6_addr32[1] && - a->in6.s6_addr32[2] == b->in6.s6_addr32[2] && - a->in6.s6_addr32[3] == b->in6.s6_addr32[3]; - - return -EAFNOSUPPORT; -} - -int in_addr_prefix_intersect( - int family, - const union in_addr_union *a, - unsigned aprefixlen, - const union in_addr_union *b, - unsigned bprefixlen) { - - unsigned m; - - assert(a); - assert(b); - - /* Checks whether there are any addresses that are in both - * networks */ - - m = MIN(aprefixlen, bprefixlen); - - if (family == AF_INET) { - uint32_t x, nm; - - x = be32toh(a->in.s_addr ^ b->in.s_addr); - nm = (m == 0) ? 0 : 0xFFFFFFFFUL << (32 - m); - - return (x & nm) == 0; - } - - if (family == AF_INET6) { - unsigned i; - - if (m > 128) - m = 128; - - for (i = 0; i < 16; i++) { - uint8_t x, nm; - - x = a->in6.s6_addr[i] ^ b->in6.s6_addr[i]; - - if (m < 8) - nm = 0xFF << (8 - m); - else - nm = 0xFF; - - if ((x & nm) != 0) - return 0; - - if (m > 8) - m -= 8; - else - m = 0; - } - - return 1; - } - - return -EAFNOSUPPORT; -} - -int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen) { - assert(u); - - /* Increases the network part of an address by one. Returns - * positive it that succeeds, or 0 if this overflows. */ - - if (prefixlen <= 0) - return 0; - - if (family == AF_INET) { - uint32_t c, n; - - if (prefixlen > 32) - prefixlen = 32; - - c = be32toh(u->in.s_addr); - n = c + (1UL << (32 - prefixlen)); - if (n < c) - return 0; - n &= 0xFFFFFFFFUL << (32 - prefixlen); - - u->in.s_addr = htobe32(n); - return 1; - } - - if (family == AF_INET6) { - struct in6_addr add = {}, result; - uint8_t overflow = 0; - unsigned i; - - if (prefixlen > 128) - prefixlen = 128; - - /* First calculate what we have to add */ - add.s6_addr[(prefixlen-1) / 8] = 1 << (7 - (prefixlen-1) % 8); - - for (i = 16; i > 0; i--) { - unsigned j = i - 1; - - result.s6_addr[j] = u->in6.s6_addr[j] + add.s6_addr[j] + overflow; - overflow = (result.s6_addr[j] < u->in6.s6_addr[j]); - } - - if (overflow) - return 0; - - u->in6 = result; - return 1; - } - - return -EAFNOSUPPORT; -} - -int in_addr_to_string(int family, const union in_addr_union *u, char **ret) { - char *x; - size_t l; - - assert(u); - assert(ret); - - if (family == AF_INET) - l = INET_ADDRSTRLEN; - else if (family == AF_INET6) - l = INET6_ADDRSTRLEN; - else - return -EAFNOSUPPORT; - - x = new(char, l); - if (!x) - return -ENOMEM; - - errno = 0; - if (!inet_ntop(family, u, x, l)) { - free(x); - return errno > 0 ? -errno : -EINVAL; - } - - *ret = x; - return 0; -} - -int in_addr_from_string(int family, const char *s, union in_addr_union *ret) { - - assert(s); - assert(ret); - - if (!IN_SET(family, AF_INET, AF_INET6)) - return -EAFNOSUPPORT; - - errno = 0; - if (inet_pton(family, s, ret) <= 0) - return errno > 0 ? -errno : -EINVAL; - - return 0; -} - -int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *ret) { - int r; - - assert(s); - assert(family); - assert(ret); - - r = in_addr_from_string(AF_INET, s, ret); - if (r >= 0) { - *family = AF_INET; - return 0; - } - - r = in_addr_from_string(AF_INET6, s, ret); - if (r >= 0) { - *family = AF_INET6; - return 0; - } - - return -EINVAL; -} - -unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr) { - assert(addr); - - return 32 - u32ctz(be32toh(addr->s_addr)); -} - -struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen) { - assert(addr); - assert(prefixlen <= 32); - - /* Shifting beyond 32 is not defined, handle this specially. */ - if (prefixlen == 0) - addr->s_addr = 0; - else - addr->s_addr = htobe32((0xffffffff << (32 - prefixlen)) & 0xffffffff); - - return addr; -} - -int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen) { - uint8_t msb_octet = *(uint8_t*) addr; - - /* addr may not be aligned, so make sure we only access it byte-wise */ - - assert(addr); - assert(prefixlen); - - if (msb_octet < 128) - /* class A, leading bits: 0 */ - *prefixlen = 8; - else if (msb_octet < 192) - /* class B, leading bits 10 */ - *prefixlen = 16; - else if (msb_octet < 224) - /* class C, leading bits 110 */ - *prefixlen = 24; - else - /* class D or E, no default prefixlen */ - return -ERANGE; - - return 0; -} - -int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask) { - unsigned char prefixlen; - int r; - - assert(addr); - assert(mask); - - r = in_addr_default_prefixlen(addr, &prefixlen); - if (r < 0) - return r; - - in_addr_prefixlen_to_netmask(mask, prefixlen); - return 0; -} - -int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen) { - assert(addr); - - if (family == AF_INET) { - struct in_addr mask; - - if (!in_addr_prefixlen_to_netmask(&mask, prefixlen)) - return -EINVAL; - - addr->in.s_addr &= mask.s_addr; - return 0; - } - - if (family == AF_INET6) { - unsigned i; - - for (i = 0; i < 16; i++) { - uint8_t mask; - - if (prefixlen >= 8) { - mask = 0xFF; - prefixlen -= 8; - } else { - mask = 0xFF << (8 - prefixlen); - prefixlen = 0; - } - - addr->in6.s6_addr[i] &= mask; - } - - return 0; - } - - return -EAFNOSUPPORT; -} diff --git a/src/basic/in-addr-util.h b/src/basic/in-addr-util.h deleted file mode 100644 index 17798ce816..0000000000 --- a/src/basic/in-addr-util.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include - -#include "macro.h" -#include "util.h" - -union in_addr_union { - struct in_addr in; - struct in6_addr in6; -}; - -struct in_addr_data { - int family; - union in_addr_union address; -}; - -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); -int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b); -int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen); -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_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); -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); -int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask); -int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen); - -static inline size_t FAMILY_ADDRESS_SIZE(int family) { - assert(family == AF_INET || family == AF_INET6); - return family == AF_INET6 ? 16 : 4; -} - -#define IN_ADDR_NULL ((union in_addr_union) {}) diff --git a/src/basic/io-util.c b/src/basic/io-util.c deleted file mode 100644 index 3ec8d61236..0000000000 --- a/src/basic/io-util.c +++ /dev/null @@ -1,264 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "io-util.h" -#include "time-util.h" - -int flush_fd(int fd) { - struct pollfd pollfd = { - .fd = fd, - .events = POLLIN, - }; - - for (;;) { - char buf[LINE_MAX]; - ssize_t l; - int r; - - r = poll(&pollfd, 1, 0); - if (r < 0) { - if (errno == EINTR) - continue; - - return -errno; - - } else if (r == 0) - return 0; - - l = read(fd, buf, sizeof(buf)); - if (l < 0) { - - if (errno == EINTR) - continue; - - if (errno == EAGAIN) - return 0; - - return -errno; - } else if (l == 0) - return 0; - } -} - -ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { - uint8_t *p = buf; - ssize_t n = 0; - - assert(fd >= 0); - assert(buf); - - /* If called with nbytes == 0, let's call read() at least - * once, to validate the operation */ - - if (nbytes > (size_t) SSIZE_MAX) - return -EINVAL; - - do { - ssize_t k; - - k = read(fd, p, nbytes); - if (k < 0) { - if (errno == EINTR) - continue; - - if (errno == EAGAIN && do_poll) { - - /* We knowingly ignore any return value here, - * and expect that any error/EOF is reported - * via read() */ - - (void) fd_wait_for_event(fd, POLLIN, USEC_INFINITY); - continue; - } - - return n > 0 ? n : -errno; - } - - if (k == 0) - return n; - - assert((size_t) k <= nbytes); - - p += k; - nbytes -= k; - n += k; - } while (nbytes > 0); - - return n; -} - -int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll) { - ssize_t n; - - n = loop_read(fd, buf, nbytes, do_poll); - if (n < 0) - return (int) n; - if ((size_t) n != nbytes) - return -EIO; - - return 0; -} - -int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { - const uint8_t *p = buf; - - assert(fd >= 0); - assert(buf); - - if (nbytes > (size_t) SSIZE_MAX) - return -EINVAL; - - do { - ssize_t k; - - k = write(fd, p, nbytes); - if (k < 0) { - if (errno == EINTR) - continue; - - if (errno == EAGAIN && do_poll) { - /* We knowingly ignore any return value here, - * and expect that any error/EOF is reported - * via write() */ - - (void) fd_wait_for_event(fd, POLLOUT, USEC_INFINITY); - continue; - } - - return -errno; - } - - if (_unlikely_(nbytes > 0 && k == 0)) /* Can't really happen */ - return -EIO; - - assert((size_t) k <= nbytes); - - p += k; - nbytes -= k; - } while (nbytes > 0); - - return 0; -} - -int pipe_eof(int fd) { - struct pollfd pollfd = { - .fd = fd, - .events = POLLIN|POLLHUP, - }; - - int r; - - r = poll(&pollfd, 1, 0); - if (r < 0) - return -errno; - - if (r == 0) - return 0; - - return pollfd.revents & POLLHUP; -} - -int fd_wait_for_event(int fd, int event, usec_t t) { - - struct pollfd pollfd = { - .fd = fd, - .events = event, - }; - - struct timespec ts; - int r; - - r = ppoll(&pollfd, 1, t == USEC_INFINITY ? NULL : timespec_store(&ts, t), NULL); - if (r < 0) - return -errno; - - if (r == 0) - return 0; - - return pollfd.revents; -} - -static size_t nul_length(const uint8_t *p, size_t sz) { - size_t n = 0; - - while (sz > 0) { - if (*p != 0) - break; - - n++; - p++; - sz--; - } - - return n; -} - -ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) { - const uint8_t *q, *w, *e; - ssize_t l; - - q = w = p; - e = q + sz; - while (q < e) { - size_t n; - - n = nul_length(q, e - q); - - /* If there are more than the specified run length of - * NUL bytes, or if this is the beginning or the end - * of the buffer, then seek instead of write */ - if ((n > run_length) || - (n > 0 && q == p) || - (n > 0 && q + n >= e)) { - if (q > w) { - l = write(fd, w, q - w); - if (l < 0) - return -errno; - if (l != q -w) - return -EIO; - } - - if (lseek(fd, n, SEEK_CUR) == (off_t) -1) - return -errno; - - q += n; - w = q; - } else if (n > 0) - q += n; - else - q ++; - } - - if (q > w) { - l = write(fd, w, q - w); - if (l < 0) - return -errno; - if (l != q - w) - return -EIO; - } - - return q - (const uint8_t*) p; -} diff --git a/src/basic/io-util.h b/src/basic/io-util.h deleted file mode 100644 index 142c940d92..0000000000 --- a/src/basic/io-util.h +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "macro.h" -#include "time-util.h" - -int flush_fd(int fd); - -ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll); -int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll); -int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll); - -int pipe_eof(int fd); - -int fd_wait_for_event(int fd, int event, usec_t timeout); - -ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length); - -#define IOVEC_SET_STRING(i, s) \ - do { \ - struct iovec *_i = &(i); \ - char *_s = (char *)(s); \ - _i->iov_base = _s; \ - _i->iov_len = strlen(_s); \ - } while(false) - -static inline size_t IOVEC_TOTAL_SIZE(const struct iovec *i, unsigned n) { - unsigned j; - size_t r = 0; - - for (j = 0; j < n; j++) - r += i[j].iov_len; - - return r; -} - -static inline size_t IOVEC_INCREMENT(struct iovec *i, unsigned n, size_t k) { - unsigned j; - - for (j = 0; j < n; j++) { - size_t sub; - - if (_unlikely_(k <= 0)) - break; - - sub = MIN(i[j].iov_len, k); - i[j].iov_len -= sub; - i[j].iov_base = (uint8_t*) i[j].iov_base + sub; - k -= sub; - } - - return k; -} - -static inline bool FILE_SIZE_VALID(uint64_t l) { - /* ftruncate() and friends take an unsigned file size, but actually cannot deal with file sizes larger than - * 2^63 since the kernel internally handles it as signed value. This call allows checking for this early. */ - - return (l >> 63) == 0; -} - -static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) { - - /* Same as above, but allows one extra value: -1 as indication for infinity. */ - - if (l == (uint64_t) -1) - return true; - - return FILE_SIZE_VALID(l); - -} diff --git a/src/basic/ioprio.h b/src/basic/ioprio.h deleted file mode 100644 index d8bb6eb497..0000000000 --- a/src/basic/ioprio.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef IOPRIO_H -#define IOPRIO_H - -/* This is minimal version of Linux' linux/ioprio.h header file, which - * is licensed GPL2 */ - -#include -#include - -/* - * Gives us 8 prio classes with 13-bits of data for each class - */ -#define IOPRIO_BITS (16) -#define IOPRIO_CLASS_SHIFT (13) -#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) - -#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) -#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) -#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) - -#define ioprio_valid(mask) (IOPRIO_PRIO_CLASS((mask)) != IOPRIO_CLASS_NONE) - -/* - * These are the io priority groups as implemented by CFQ. RT is the realtime - * class, it always gets premium service. BE is the best-effort scheduling - * class, the default for any process. IDLE is the idle scheduling class, it - * is only served when no one else is using the disk. - */ -enum { - IOPRIO_CLASS_NONE, - IOPRIO_CLASS_RT, - IOPRIO_CLASS_BE, - IOPRIO_CLASS_IDLE, -}; - -/* - * 8 best effort priority levels are supported - */ -#define IOPRIO_BE_NR (8) - -enum { - IOPRIO_WHO_PROCESS = 1, - IOPRIO_WHO_PGRP, - IOPRIO_WHO_USER, -}; - -static inline int ioprio_set(int which, int who, int ioprio) { - return syscall(__NR_ioprio_set, which, who, ioprio); -} - -static inline int ioprio_get(int which, int who) { - return syscall(__NR_ioprio_get, which, who); -} - -#endif diff --git a/src/basic/json.c b/src/basic/json.c deleted file mode 100644 index daa98fc815..0000000000 --- a/src/basic/json.c +++ /dev/null @@ -1,871 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "hexdecoct.h" -#include "json.h" -#include "macro.h" -#include "string-util.h" -#include "utf8.h" - -int json_variant_new(JsonVariant **ret, JsonVariantType type) { - JsonVariant *v; - - v = new0(JsonVariant, 1); - if (!v) - return -ENOMEM; - v->type = type; - *ret = v; - return 0; -} - -static int json_variant_deep_copy(JsonVariant *ret, JsonVariant *variant) { - int r; - - assert(ret); - assert(variant); - - ret->type = variant->type; - ret->size = variant->size; - - if (variant->type == JSON_VARIANT_STRING) { - ret->string = memdup(variant->string, variant->size+1); - if (!ret->string) - return -ENOMEM; - } else if (variant->type == JSON_VARIANT_ARRAY || variant->type == JSON_VARIANT_OBJECT) { - size_t i; - - ret->objects = new0(JsonVariant, variant->size); - if (!ret->objects) - return -ENOMEM; - - for (i = 0; i < variant->size; ++i) { - r = json_variant_deep_copy(&ret->objects[i], &variant->objects[i]); - if (r < 0) - return r; - } - } else - ret->value = variant->value; - - return 0; -} - -static JsonVariant *json_object_unref(JsonVariant *variant); - -static JsonVariant *json_variant_unref_inner(JsonVariant *variant) { - if (!variant) - return NULL; - - if (variant->type == JSON_VARIANT_ARRAY || variant->type == JSON_VARIANT_OBJECT) - return json_object_unref(variant); - else if (variant->type == JSON_VARIANT_STRING) - free(variant->string); - - return NULL; -} - -static JsonVariant *json_raw_unref(JsonVariant *variant, size_t size) { - if (!variant) - return NULL; - - for (size_t i = 0; i < size; ++i) - json_variant_unref_inner(&variant[i]); - - free(variant); - return NULL; -} - -static JsonVariant *json_object_unref(JsonVariant *variant) { - size_t i; - - assert(variant); - - if (!variant->objects) - return NULL; - - for (i = 0; i < variant->size; ++i) - json_variant_unref_inner(&variant->objects[i]); - - free(variant->objects); - return NULL; -} - -static JsonVariant **json_variant_array_unref(JsonVariant **variant) { - size_t i = 0; - JsonVariant *p = NULL; - - if (!variant) - return NULL; - - while((p = (variant[i++])) != NULL) { - if (p->type == JSON_VARIANT_STRING) - free(p->string); - free(p); - } - - free(variant); - - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(JsonVariant **, json_variant_array_unref); - -JsonVariant *json_variant_unref(JsonVariant *variant) { - if (!variant) - return NULL; - - if (variant->type == JSON_VARIANT_ARRAY || variant->type == JSON_VARIANT_OBJECT) - json_object_unref(variant); - else if (variant->type == JSON_VARIANT_STRING) - free(variant->string); - - free(variant); - - return NULL; -} - -char *json_variant_string(JsonVariant *variant){ - assert(variant); - assert(variant->type == JSON_VARIANT_STRING); - - return variant->string; -} - -bool json_variant_bool(JsonVariant *variant) { - assert(variant); - assert(variant->type == JSON_VARIANT_BOOLEAN); - - return variant->value.boolean; -} - -intmax_t json_variant_integer(JsonVariant *variant) { - assert(variant); - assert(variant->type == JSON_VARIANT_INTEGER); - - return variant->value.integer; -} - -double json_variant_real(JsonVariant *variant) { - assert(variant); - assert(variant->type == JSON_VARIANT_REAL); - - return variant->value.real; -} - -JsonVariant *json_variant_element(JsonVariant *variant, unsigned index) { - assert(variant); - assert(variant->type == JSON_VARIANT_ARRAY || variant->type == JSON_VARIANT_OBJECT); - assert(index < variant->size); - assert(variant->objects); - - return &variant->objects[index]; -} - -JsonVariant *json_variant_value(JsonVariant *variant, const char *key) { - size_t i; - - assert(variant); - assert(variant->type == JSON_VARIANT_OBJECT); - assert(variant->objects); - - for (i = 0; i < variant->size; i += 2) { - JsonVariant *p = &variant->objects[i]; - if (p->type == JSON_VARIANT_STRING && streq(key, p->string)) - return &variant->objects[i + 1]; - } - - return NULL; -} - -static void inc_lines(unsigned *line, const char *s, size_t n) { - const char *p = s; - - if (!line) - return; - - for (;;) { - const char *f; - - f = memchr(p, '\n', n); - if (!f) - return; - - n -= (f - p) + 1; - p = f + 1; - (*line)++; - } -} - -static int unhex_ucs2(const char *c, uint16_t *ret) { - int aa, bb, cc, dd; - uint16_t x; - - assert(c); - assert(ret); - - aa = unhexchar(c[0]); - if (aa < 0) - return -EINVAL; - - bb = unhexchar(c[1]); - if (bb < 0) - return -EINVAL; - - cc = unhexchar(c[2]); - if (cc < 0) - return -EINVAL; - - dd = unhexchar(c[3]); - if (dd < 0) - return -EINVAL; - - x = ((uint16_t) aa << 12) | - ((uint16_t) bb << 8) | - ((uint16_t) cc << 4) | - ((uint16_t) dd); - - if (x <= 0) - return -EINVAL; - - *ret = x; - - return 0; -} - -static int json_parse_string(const char **p, char **ret) { - _cleanup_free_ char *s = NULL; - size_t n = 0, allocated = 0; - const char *c; - - assert(p); - assert(*p); - assert(ret); - - c = *p; - - if (*c != '"') - return -EINVAL; - - c++; - - for (;;) { - int len; - - /* Check for EOF */ - if (*c == 0) - return -EINVAL; - - /* Check for control characters 0x00..0x1f */ - if (*c > 0 && *c < ' ') - return -EINVAL; - - /* Check for control character 0x7f */ - if (*c == 0x7f) - return -EINVAL; - - if (*c == '"') { - if (!s) { - s = strdup(""); - if (!s) - return -ENOMEM; - } else - s[n] = 0; - - *p = c + 1; - - *ret = s; - s = NULL; - return JSON_STRING; - } - - if (*c == '\\') { - char ch = 0; - c++; - - if (*c == 0) - return -EINVAL; - - if (IN_SET(*c, '"', '\\', '/')) - ch = *c; - else if (*c == 'b') - ch = '\b'; - else if (*c == 'f') - ch = '\f'; - else if (*c == 'n') - ch = '\n'; - else if (*c == 'r') - ch = '\r'; - else if (*c == 't') - ch = '\t'; - else if (*c == 'u') { - char16_t x; - int r; - - r = unhex_ucs2(c + 1, &x); - if (r < 0) - return r; - - c += 5; - - if (!GREEDY_REALLOC(s, allocated, n + 4)) - return -ENOMEM; - - if (!utf16_is_surrogate(x)) - n += utf8_encode_unichar(s + n, (char32_t) x); - else if (utf16_is_trailing_surrogate(x)) - return -EINVAL; - else { - char16_t y; - - if (c[0] != '\\' || c[1] != 'u') - return -EINVAL; - - r = unhex_ucs2(c + 2, &y); - if (r < 0) - return r; - - c += 6; - - if (!utf16_is_trailing_surrogate(y)) - return -EINVAL; - - n += utf8_encode_unichar(s + n, utf16_surrogate_pair_to_unichar(x, y)); - } - - continue; - } else - return -EINVAL; - - if (!GREEDY_REALLOC(s, allocated, n + 2)) - return -ENOMEM; - - s[n++] = ch; - c ++; - continue; - } - - len = utf8_encoded_valid_unichar(c); - if (len < 0) - return len; - - if (!GREEDY_REALLOC(s, allocated, n + len + 1)) - return -ENOMEM; - - memcpy(s + n, c, len); - n += len; - c += len; - } -} - -static int json_parse_number(const char **p, union json_value *ret) { - bool negative = false, exponent_negative = false, is_double = false; - double x = 0.0, y = 0.0, exponent = 0.0, shift = 1.0; - intmax_t i = 0; - const char *c; - - assert(p); - assert(*p); - assert(ret); - - c = *p; - - if (*c == '-') { - negative = true; - c++; - } - - if (*c == '0') - c++; - else { - if (!strchr("123456789", *c) || *c == 0) - return -EINVAL; - - do { - if (!is_double) { - int64_t t; - - t = 10 * i + (*c - '0'); - if (t < i) /* overflow */ - is_double = false; - else - i = t; - } - - x = 10.0 * x + (*c - '0'); - c++; - } while (strchr("0123456789", *c) && *c != 0); - } - - if (*c == '.') { - is_double = true; - c++; - - if (!strchr("0123456789", *c) || *c == 0) - return -EINVAL; - - do { - y = 10.0 * y + (*c - '0'); - shift = 10.0 * shift; - c++; - } while (strchr("0123456789", *c) && *c != 0); - } - - if (*c == 'e' || *c == 'E') { - is_double = true; - c++; - - if (*c == '-') { - exponent_negative = true; - c++; - } else if (*c == '+') - c++; - - if (!strchr("0123456789", *c) || *c == 0) - return -EINVAL; - - do { - exponent = 10.0 * exponent + (*c - '0'); - c++; - } while (strchr("0123456789", *c) && *c != 0); - } - - *p = c; - - if (is_double) { - ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * exp10((exponent_negative ? -1.0 : 1.0) * exponent); - return JSON_REAL; - } else { - ret->integer = negative ? -i : i; - return JSON_INTEGER; - } -} - -int json_tokenize( - const char **p, - char **ret_string, - union json_value *ret_value, - void **state, - unsigned *line) { - - const char *c; - int t; - int r; - - enum { - STATE_NULL, - STATE_VALUE, - STATE_VALUE_POST, - }; - - assert(p); - assert(*p); - assert(ret_string); - assert(ret_value); - assert(state); - - t = PTR_TO_INT(*state); - c = *p; - - if (t == STATE_NULL) { - if (line) - *line = 1; - t = STATE_VALUE; - } - - for (;;) { - const char *b; - - b = c + strspn(c, WHITESPACE); - if (*b == 0) - return JSON_END; - - inc_lines(line, c, b - c); - c = b; - - switch (t) { - - case STATE_VALUE: - - if (*c == '{') { - *ret_string = NULL; - *ret_value = JSON_VALUE_NULL; - *p = c + 1; - *state = INT_TO_PTR(STATE_VALUE); - return JSON_OBJECT_OPEN; - - } else if (*c == '}') { - *ret_string = NULL; - *ret_value = JSON_VALUE_NULL; - *p = c + 1; - *state = INT_TO_PTR(STATE_VALUE_POST); - return JSON_OBJECT_CLOSE; - - } else if (*c == '[') { - *ret_string = NULL; - *ret_value = JSON_VALUE_NULL; - *p = c + 1; - *state = INT_TO_PTR(STATE_VALUE); - return JSON_ARRAY_OPEN; - - } else if (*c == ']') { - *ret_string = NULL; - *ret_value = JSON_VALUE_NULL; - *p = c + 1; - *state = INT_TO_PTR(STATE_VALUE_POST); - return JSON_ARRAY_CLOSE; - - } else if (*c == '"') { - r = json_parse_string(&c, ret_string); - if (r < 0) - return r; - - *ret_value = JSON_VALUE_NULL; - *p = c; - *state = INT_TO_PTR(STATE_VALUE_POST); - return r; - - } else if (strchr("-0123456789", *c)) { - r = json_parse_number(&c, ret_value); - if (r < 0) - return r; - - *ret_string = NULL; - *p = c; - *state = INT_TO_PTR(STATE_VALUE_POST); - return r; - - } else if (startswith(c, "true")) { - *ret_string = NULL; - ret_value->boolean = true; - *p = c + 4; - *state = INT_TO_PTR(STATE_VALUE_POST); - return JSON_BOOLEAN; - - } else if (startswith(c, "false")) { - *ret_string = NULL; - ret_value->boolean = false; - *p = c + 5; - *state = INT_TO_PTR(STATE_VALUE_POST); - return JSON_BOOLEAN; - - } else if (startswith(c, "null")) { - *ret_string = NULL; - *ret_value = JSON_VALUE_NULL; - *p = c + 4; - *state = INT_TO_PTR(STATE_VALUE_POST); - return JSON_NULL; - - } else - return -EINVAL; - - case STATE_VALUE_POST: - - if (*c == ':') { - *ret_string = NULL; - *ret_value = JSON_VALUE_NULL; - *p = c + 1; - *state = INT_TO_PTR(STATE_VALUE); - return JSON_COLON; - } else if (*c == ',') { - *ret_string = NULL; - *ret_value = JSON_VALUE_NULL; - *p = c + 1; - *state = INT_TO_PTR(STATE_VALUE); - return JSON_COMMA; - } else if (*c == '}') { - *ret_string = NULL; - *ret_value = JSON_VALUE_NULL; - *p = c + 1; - *state = INT_TO_PTR(STATE_VALUE_POST); - return JSON_OBJECT_CLOSE; - } else if (*c == ']') { - *ret_string = NULL; - *ret_value = JSON_VALUE_NULL; - *p = c + 1; - *state = INT_TO_PTR(STATE_VALUE_POST); - return JSON_ARRAY_CLOSE; - } else - return -EINVAL; - } - - } -} - -static bool json_is_value(JsonVariant *var) { - assert(var); - - return var->type != JSON_VARIANT_CONTROL; -} - -static int json_scoped_parse(JsonVariant **tokens, size_t *i, size_t n, JsonVariant *scope) { - bool arr = scope->type == JSON_VARIANT_ARRAY; - int terminator = arr ? JSON_ARRAY_CLOSE : JSON_OBJECT_CLOSE; - size_t allocated = 0, size = 0; - JsonVariant *key = NULL, *value = NULL, *var = NULL, *items = NULL; - enum { - STATE_KEY, - STATE_COLON, - STATE_COMMA, - STATE_VALUE - } state = arr ? STATE_VALUE : STATE_KEY; - - assert(tokens); - assert(i); - assert(scope); - - while((var = *i < n ? tokens[(*i)++] : NULL) != NULL) { - bool stopper; - int r; - - stopper = !json_is_value(var) && var->value.integer == terminator; - - if (stopper) { - if (state != STATE_COMMA && size > 0) - goto error; - - goto out; - } - - if (state == STATE_KEY) { - if (var->type != JSON_VARIANT_STRING) - goto error; - else { - key = var; - state = STATE_COLON; - } - } - else if (state == STATE_COLON) { - if (key == NULL) - goto error; - - if (json_is_value(var)) - goto error; - - if (var->value.integer != JSON_COLON) - goto error; - - state = STATE_VALUE; - } - else if (state == STATE_VALUE) { - _cleanup_json_variant_unref_ JsonVariant *v = NULL; - size_t toadd = arr ? 1 : 2; - - if (!json_is_value(var)) { - int type = (var->value.integer == JSON_ARRAY_OPEN) ? JSON_VARIANT_ARRAY : JSON_VARIANT_OBJECT; - - r = json_variant_new(&v, type); - if (r < 0) - goto error; - - r = json_scoped_parse(tokens, i, n, v); - if (r < 0) - goto error; - - value = v; - } - else - value = var; - - if(!GREEDY_REALLOC(items, allocated, size + toadd)) - goto error; - - if (arr) { - r = json_variant_deep_copy(&items[size], value); - if (r < 0) - goto error; - } else { - r = json_variant_deep_copy(&items[size], key); - if (r < 0) - goto error; - - r = json_variant_deep_copy(&items[size+1], value); - if (r < 0) - goto error; - } - - size += toadd; - state = STATE_COMMA; - } - else if (state == STATE_COMMA) { - if (json_is_value(var)) - goto error; - - if (var->value.integer != JSON_COMMA) - goto error; - - key = NULL; - value = NULL; - - state = arr ? STATE_VALUE : STATE_KEY; - } - } - -error: - json_raw_unref(items, size); - return -EBADMSG; - -out: - scope->size = size; - scope->objects = items; - - return scope->type; -} - -static int json_parse_tokens(JsonVariant **tokens, size_t ntokens, JsonVariant **rv) { - size_t it = 0; - int r; - JsonVariant *e; - _cleanup_json_variant_unref_ JsonVariant *p = NULL; - - assert(tokens); - assert(ntokens); - - e = tokens[it++]; - r = json_variant_new(&p, JSON_VARIANT_OBJECT); - if (r < 0) - return r; - - if (e->type != JSON_VARIANT_CONTROL && e->value.integer != JSON_OBJECT_OPEN) - return -EBADMSG; - - r = json_scoped_parse(tokens, &it, ntokens, p); - if (r < 0) - return r; - - *rv = p; - p = NULL; - - return 0; -} - -static int json_tokens(const char *string, size_t size, JsonVariant ***tokens, size_t *n) { - _cleanup_free_ char *buf = NULL; - _cleanup_(json_variant_array_unrefp) JsonVariant **items = NULL; - union json_value v = {}; - void *json_state = NULL; - const char *p; - int t, r; - size_t allocated = 0, s = 0; - - assert(string); - assert(n); - - if (size <= 0) - return -EBADMSG; - - buf = strndup(string, size); - if (!buf) - return -ENOMEM; - - p = buf; - for (;;) { - _cleanup_json_variant_unref_ JsonVariant *var = NULL; - _cleanup_free_ char *rstr = NULL; - - t = json_tokenize(&p, &rstr, &v, &json_state, NULL); - - if (t < 0) - return t; - else if (t == JSON_END) - break; - - if (t <= JSON_ARRAY_CLOSE) { - r = json_variant_new(&var, JSON_VARIANT_CONTROL); - if (r < 0) - return r; - var->value.integer = t; - } else { - switch (t) { - case JSON_STRING: - r = json_variant_new(&var, JSON_VARIANT_STRING); - if (r < 0) - return r; - var->size = strlen(rstr); - var->string = strdup(rstr); - if (!var->string) { - return -ENOMEM; - } - break; - case JSON_INTEGER: - r = json_variant_new(&var, JSON_VARIANT_INTEGER); - if (r < 0) - return r; - var->value = v; - break; - case JSON_REAL: - r = json_variant_new(&var, JSON_VARIANT_REAL); - if (r < 0) - return r; - var->value = v; - break; - case JSON_BOOLEAN: - r = json_variant_new(&var, JSON_VARIANT_BOOLEAN); - if (r < 0) - return r; - var->value = v; - break; - case JSON_NULL: - r = json_variant_new(&var, JSON_VARIANT_NULL); - if (r < 0) - return r; - break; - } - } - - if (!GREEDY_REALLOC(items, allocated, s+2)) - return -ENOMEM; - - items[s++] = var; - items[s] = NULL; - var = NULL; - } - - *n = s; - *tokens = items; - items = NULL; - - return 0; -} - -int json_parse(const char *string, JsonVariant **rv) { - _cleanup_(json_variant_array_unrefp) JsonVariant **s = NULL; - JsonVariant *v = NULL; - size_t n = 0; - int r; - - assert(string); - assert(rv); - - r = json_tokens(string, strlen(string), &s, &n); - if (r < 0) - return r; - - r = json_parse_tokens(s, n, &v); - if (r < 0) - return r; - - *rv = v; - return 0; -} diff --git a/src/basic/json.h b/src/basic/json.h deleted file mode 100644 index a4509f680f..0000000000 --- a/src/basic/json.h +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include - -#include "macro.h" -#include "util.h" - -enum { - JSON_END, - JSON_COLON, - JSON_COMMA, - JSON_OBJECT_OPEN, - JSON_OBJECT_CLOSE, - JSON_ARRAY_OPEN, - JSON_ARRAY_CLOSE, - JSON_STRING, - JSON_REAL, - JSON_INTEGER, - JSON_BOOLEAN, - JSON_NULL, -}; - -typedef enum { - JSON_VARIANT_CONTROL, - JSON_VARIANT_STRING, - JSON_VARIANT_INTEGER, - JSON_VARIANT_BOOLEAN, - JSON_VARIANT_REAL, - JSON_VARIANT_ARRAY, - JSON_VARIANT_OBJECT, - JSON_VARIANT_NULL -} JsonVariantType; - -union json_value { - bool boolean; - double real; - intmax_t integer; -}; - -typedef struct JsonVariant { - JsonVariantType type; - size_t size; - union { - char *string; - struct JsonVariant *objects; - union json_value value; - }; -} JsonVariant; - -int json_variant_new(JsonVariant **ret, JsonVariantType type); -JsonVariant *json_variant_unref(JsonVariant *v); - -DEFINE_TRIVIAL_CLEANUP_FUNC(JsonVariant *, json_variant_unref); -#define _cleanup_json_variant_unref_ _cleanup_(json_variant_unrefp) - -char *json_variant_string(JsonVariant *v); -bool json_variant_bool(JsonVariant *v); -intmax_t json_variant_integer(JsonVariant *v); -double json_variant_real(JsonVariant *v); - -JsonVariant *json_variant_element(JsonVariant *v, unsigned index); -JsonVariant *json_variant_value(JsonVariant *v, const char *key); - -#define JSON_VALUE_NULL ((union json_value) {}) - -int json_tokenize(const char **p, char **ret_string, union json_value *ret_value, void **state, unsigned *line); - -int json_parse(const char *string, JsonVariant **rv); -int json_parse_measure(const char *string, size_t *size); diff --git a/src/basic/label.c b/src/basic/label.c deleted file mode 100644 index f5ab855d32..0000000000 --- a/src/basic/label.c +++ /dev/null @@ -1,82 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#include "label.h" -#include "macro.h" -#include "selinux-util.h" -#include "smack-util.h" - -int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { - int r, q; - - r = mac_selinux_fix(path, ignore_enoent, ignore_erofs); - q = mac_smack_fix(path, ignore_enoent, ignore_erofs); - - if (r < 0) - return r; - if (q < 0) - return q; - - return 0; -} - -int mkdir_label(const char *path, mode_t mode) { - int r; - - assert(path); - - r = mac_selinux_create_file_prepare(path, S_IFDIR); - if (r < 0) - return r; - - if (mkdir(path, mode) < 0) - r = -errno; - - mac_selinux_create_file_clear(); - - if (r < 0) - return r; - - return mac_smack_fix(path, false, false); -} - -int symlink_label(const char *old_path, const char *new_path) { - int r; - - assert(old_path); - assert(new_path); - - r = mac_selinux_create_file_prepare(new_path, S_IFLNK); - if (r < 0) - return r; - - if (symlink(old_path, new_path) < 0) - r = -errno; - - mac_selinux_create_file_clear(); - - if (r < 0) - return r; - - return mac_smack_fix(new_path, false, false); -} diff --git a/src/basic/label.h b/src/basic/label.h deleted file mode 100644 index 3e9251aa71..0000000000 --- a/src/basic/label.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs); - -int mkdir_label(const char *path, mode_t mode); -int symlink_label(const char *old_path, const char *new_path); diff --git a/src/basic/linux/Makefile b/src/basic/linux/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/basic/linux/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/basic/list.h b/src/basic/list.h deleted file mode 100644 index c68185f587..0000000000 --- a/src/basic/list.h +++ /dev/null @@ -1,182 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -/* The head of the linked list. Use this in the structure that shall - * contain the head of the linked list */ -#define LIST_HEAD(t,name) \ - t *name - -/* The pointers in the linked list's items. Use this in the item structure */ -#define LIST_FIELDS(t,name) \ - t *name##_next, *name##_prev - -/* Initialize the list's head */ -#define LIST_HEAD_INIT(head) \ - do { \ - (head) = NULL; } \ - while(false) - -/* Initialize a list item */ -#define LIST_INIT(name,item) \ - do { \ - typeof(*(item)) *_item = (item); \ - assert(_item); \ - _item->name##_prev = _item->name##_next = NULL; \ - } while(false) - -/* Prepend an item to the list */ -#define LIST_PREPEND(name,head,item) \ - do { \ - typeof(*(head)) **_head = &(head), *_item = (item); \ - assert(_item); \ - if ((_item->name##_next = *_head)) \ - _item->name##_next->name##_prev = _item; \ - _item->name##_prev = NULL; \ - *_head = _item; \ - } while(false) - -/* Append an item to the list */ -#define LIST_APPEND(name,head,item) \ - do { \ - typeof(*(head)) *_tail; \ - LIST_FIND_TAIL(name,head,_tail); \ - LIST_INSERT_AFTER(name,head,_tail,item); \ - } while(false) - -/* Remove an item from the list */ -#define LIST_REMOVE(name,head,item) \ - do { \ - typeof(*(head)) **_head = &(head), *_item = (item); \ - assert(_item); \ - if (_item->name##_next) \ - _item->name##_next->name##_prev = _item->name##_prev; \ - if (_item->name##_prev) \ - _item->name##_prev->name##_next = _item->name##_next; \ - else { \ - assert(*_head == _item); \ - *_head = _item->name##_next; \ - } \ - _item->name##_next = _item->name##_prev = NULL; \ - } while(false) - -/* Find the head of the list */ -#define LIST_FIND_HEAD(name,item,head) \ - do { \ - typeof(*(item)) *_item = (item); \ - if (!_item) \ - (head) = NULL; \ - else { \ - while (_item->name##_prev) \ - _item = _item->name##_prev; \ - (head) = _item; \ - } \ - } while (false) - -/* Find the tail of the list */ -#define LIST_FIND_TAIL(name,item,tail) \ - do { \ - typeof(*(item)) *_item = (item); \ - if (!_item) \ - (tail) = NULL; \ - else { \ - while (_item->name##_next) \ - _item = _item->name##_next; \ - (tail) = _item; \ - } \ - } while (false) - -/* Insert an item after another one (a = where, b = what) */ -#define LIST_INSERT_AFTER(name,head,a,b) \ - do { \ - typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \ - assert(_b); \ - if (!_a) { \ - if ((_b->name##_next = *_head)) \ - _b->name##_next->name##_prev = _b; \ - _b->name##_prev = NULL; \ - *_head = _b; \ - } else { \ - if ((_b->name##_next = _a->name##_next)) \ - _b->name##_next->name##_prev = _b; \ - _b->name##_prev = _a; \ - _a->name##_next = _b; \ - } \ - } while(false) - -/* Insert an item before another one (a = where, b = what) */ -#define LIST_INSERT_BEFORE(name,head,a,b) \ - do { \ - typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \ - assert(_b); \ - if (!_a) { \ - if (!*_head) { \ - _b->name##_next = NULL; \ - _b->name##_prev = NULL; \ - *_head = _b; \ - } else { \ - typeof(*(head)) *_tail = (head); \ - while (_tail->name##_next) \ - _tail = _tail->name##_next; \ - _b->name##_next = NULL; \ - _b->name##_prev = _tail; \ - _tail->name##_next = _b; \ - } \ - } else { \ - if ((_b->name##_prev = _a->name##_prev)) \ - _b->name##_prev->name##_next = _b; \ - _b->name##_next = _a; \ - _a->name##_prev = _b; \ - } \ - } while(false) - -#define LIST_JUST_US(name,item) \ - (!(item)->name##_prev && !(item)->name##_next) \ - -#define LIST_FOREACH(name,i,head) \ - for ((i) = (head); (i); (i) = (i)->name##_next) - -#define LIST_FOREACH_SAFE(name,i,n,head) \ - for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n)) - -#define LIST_FOREACH_BEFORE(name,i,p) \ - for ((i) = (p)->name##_prev; (i); (i) = (i)->name##_prev) - -#define LIST_FOREACH_AFTER(name,i,p) \ - for ((i) = (p)->name##_next; (i); (i) = (i)->name##_next) - -/* Iterate through all the members of the list p is included in, but skip over p */ -#define LIST_FOREACH_OTHERS(name,i,p) \ - for (({ \ - (i) = (p); \ - while ((i) && (i)->name##_prev) \ - (i) = (i)->name##_prev; \ - if ((i) == (p)) \ - (i) = (p)->name##_next; \ - }); \ - (i); \ - (i) = (i)->name##_next == (p) ? (p)->name##_next : (i)->name##_next) - -/* Loop starting from p->next until p->prev. - p can be adjusted meanwhile. */ -#define LIST_LOOP_BUT_ONE(name,i,head,p) \ - for ((i) = (p)->name##_next ? (p)->name##_next : (head); \ - (i) != (p); \ - (i) = (i)->name##_next ? (i)->name##_next : (head)) diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c deleted file mode 100644 index cda6b2895d..0000000000 --- a/src/basic/locale-util.c +++ /dev/null @@ -1,319 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dirent-util.h" -#include "fd-util.h" -#include "hashmap.h" -#include "locale-util.h" -#include "path-util.h" -#include "set.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "utf8.h" - -static int add_locales_from_archive(Set *locales) { - /* Stolen from glibc... */ - - struct locarhead { - uint32_t magic; - /* Serial number. */ - uint32_t serial; - /* Name hash table. */ - uint32_t namehash_offset; - uint32_t namehash_used; - uint32_t namehash_size; - /* String table. */ - uint32_t string_offset; - uint32_t string_used; - uint32_t string_size; - /* Table with locale records. */ - uint32_t locrectab_offset; - uint32_t locrectab_used; - uint32_t locrectab_size; - /* MD5 sum hash table. */ - uint32_t sumhash_offset; - uint32_t sumhash_used; - uint32_t sumhash_size; - }; - - struct namehashent { - /* Hash value of the name. */ - uint32_t hashval; - /* Offset of the name in the string table. */ - uint32_t name_offset; - /* Offset of the locale record. */ - uint32_t locrec_offset; - }; - - const struct locarhead *h; - const struct namehashent *e; - const void *p = MAP_FAILED; - _cleanup_close_ int fd = -1; - size_t sz = 0; - struct stat st; - unsigned i; - int r; - - fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return errno == ENOENT ? 0 : -errno; - - if (fstat(fd, &st) < 0) - return -errno; - - if (!S_ISREG(st.st_mode)) - return -EBADMSG; - - if (st.st_size < (off_t) sizeof(struct locarhead)) - return -EBADMSG; - - p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); - if (p == MAP_FAILED) - return -errno; - - h = (const struct locarhead *) p; - if (h->magic != 0xde020109 || - h->namehash_offset + h->namehash_size > st.st_size || - h->string_offset + h->string_size > st.st_size || - h->locrectab_offset + h->locrectab_size > st.st_size || - h->sumhash_offset + h->sumhash_size > st.st_size) { - r = -EBADMSG; - goto finish; - } - - e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset); - for (i = 0; i < h->namehash_size; i++) { - char *z; - - if (e[i].locrec_offset == 0) - continue; - - if (!utf8_is_valid((char*) p + e[i].name_offset)) - continue; - - z = strdup((char*) p + e[i].name_offset); - if (!z) { - r = -ENOMEM; - goto finish; - } - - r = set_consume(locales, z); - if (r < 0) - goto finish; - } - - r = 0; - - finish: - if (p != MAP_FAILED) - munmap((void*) p, sz); - - return r; -} - -static int add_locales_from_libdir (Set *locales) { - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *entry; - int r; - - dir = opendir("/usr/lib/locale"); - if (!dir) - return errno == ENOENT ? 0 : -errno; - - FOREACH_DIRENT(entry, dir, return -errno) { - char *z; - - if (entry->d_type != DT_DIR) - continue; - - z = strdup(entry->d_name); - if (!z) - return -ENOMEM; - - r = set_consume(locales, z); - if (r < 0 && r != -EEXIST) - return r; - } - - return 0; -} - -int get_locales(char ***ret) { - _cleanup_set_free_ Set *locales = NULL; - _cleanup_strv_free_ char **l = NULL; - int r; - - locales = set_new(&string_hash_ops); - if (!locales) - return -ENOMEM; - - r = add_locales_from_archive(locales); - if (r < 0 && r != -ENOENT) - return r; - - r = add_locales_from_libdir(locales); - if (r < 0) - return r; - - l = set_get_strv(locales); - if (!l) - return -ENOMEM; - - strv_sort(l); - - *ret = l; - l = NULL; - - return 0; -} - -bool locale_is_valid(const char *name) { - - if (isempty(name)) - return false; - - if (strlen(name) >= 128) - return false; - - if (!utf8_is_valid(name)) - return false; - - if (!filename_is_valid(name)) - return false; - - if (!string_is_safe(name)) - return false; - - return true; -} - -void init_gettext(void) { - setlocale(LC_ALL, ""); - textdomain(GETTEXT_PACKAGE); -} - -bool is_locale_utf8(void) { - const char *set; - static int cached_answer = -1; - - /* Note that we default to 'true' here, since today UTF8 is - * pretty much supported everywhere. */ - - if (cached_answer >= 0) - goto out; - - if (!setlocale(LC_ALL, "")) { - cached_answer = true; - goto out; - } - - set = nl_langinfo(CODESET); - if (!set) { - cached_answer = true; - goto out; - } - - if (streq(set, "UTF-8")) { - cached_answer = true; - goto out; - } - - /* For LC_CTYPE=="C" return true, because CTYPE is effectly - * unset and everything can do to UTF-8 nowadays. */ - set = setlocale(LC_CTYPE, NULL); - if (!set) { - cached_answer = true; - goto out; - } - - /* Check result, but ignore the result if C was set - * explicitly. */ - cached_answer = - STR_IN_SET(set, "C", "POSIX") && - !getenv("LC_ALL") && - !getenv("LC_CTYPE") && - !getenv("LANG"); - -out: - return (bool) cached_answer; -} - - -const char *draw_special_char(DrawSpecialChar ch) { - - static const char *draw_table[2][_DRAW_SPECIAL_CHAR_MAX] = { - - /* UTF-8 */ { - [DRAW_TREE_VERTICAL] = "\342\224\202 ", /* │ */ - [DRAW_TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */ - [DRAW_TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */ - [DRAW_TREE_SPACE] = " ", /* */ - [DRAW_TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */ - [DRAW_BLACK_CIRCLE] = "\342\227\217", /* ● */ - [DRAW_ARROW] = "\342\206\222", /* → */ - [DRAW_DASH] = "\342\200\223", /* – */ - }, - - /* ASCII fallback */ { - [DRAW_TREE_VERTICAL] = "| ", - [DRAW_TREE_BRANCH] = "|-", - [DRAW_TREE_RIGHT] = "`-", - [DRAW_TREE_SPACE] = " ", - [DRAW_TRIANGULAR_BULLET] = ">", - [DRAW_BLACK_CIRCLE] = "*", - [DRAW_ARROW] = "->", - [DRAW_DASH] = "-", - } - }; - - return draw_table[!is_locale_utf8()][ch]; -} - -static const char * const locale_variable_table[_VARIABLE_LC_MAX] = { - [VARIABLE_LANG] = "LANG", - [VARIABLE_LANGUAGE] = "LANGUAGE", - [VARIABLE_LC_CTYPE] = "LC_CTYPE", - [VARIABLE_LC_NUMERIC] = "LC_NUMERIC", - [VARIABLE_LC_TIME] = "LC_TIME", - [VARIABLE_LC_COLLATE] = "LC_COLLATE", - [VARIABLE_LC_MONETARY] = "LC_MONETARY", - [VARIABLE_LC_MESSAGES] = "LC_MESSAGES", - [VARIABLE_LC_PAPER] = "LC_PAPER", - [VARIABLE_LC_NAME] = "LC_NAME", - [VARIABLE_LC_ADDRESS] = "LC_ADDRESS", - [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE", - [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT", - [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION" -}; - -DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable); diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h deleted file mode 100644 index b0f9679286..0000000000 --- a/src/basic/locale-util.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include - -#include "macro.h" - -typedef enum LocaleVariable { - /* We don't list LC_ALL here on purpose. People should be - * using LANG instead. */ - - VARIABLE_LANG, - VARIABLE_LANGUAGE, - VARIABLE_LC_CTYPE, - VARIABLE_LC_NUMERIC, - VARIABLE_LC_TIME, - VARIABLE_LC_COLLATE, - VARIABLE_LC_MONETARY, - VARIABLE_LC_MESSAGES, - VARIABLE_LC_PAPER, - VARIABLE_LC_NAME, - VARIABLE_LC_ADDRESS, - VARIABLE_LC_TELEPHONE, - VARIABLE_LC_MEASUREMENT, - VARIABLE_LC_IDENTIFICATION, - _VARIABLE_LC_MAX, - _VARIABLE_LC_INVALID = -1 -} LocaleVariable; - -int get_locales(char ***l); -bool locale_is_valid(const char *name); - -#define _(String) gettext(String) -#define N_(String) String -void init_gettext(void); - -bool is_locale_utf8(void); - -typedef enum DrawSpecialChar { - DRAW_TREE_VERTICAL, - DRAW_TREE_BRANCH, - DRAW_TREE_RIGHT, - DRAW_TREE_SPACE, - DRAW_TRIANGULAR_BULLET, - DRAW_BLACK_CIRCLE, - DRAW_ARROW, - DRAW_DASH, - _DRAW_SPECIAL_CHAR_MAX -} DrawSpecialChar; - -const char *draw_special_char(DrawSpecialChar ch); - -const char* locale_variable_to_string(LocaleVariable i) _const_; -LocaleVariable locale_variable_from_string(const char *s) _pure_; diff --git a/src/basic/lockfile-util.c b/src/basic/lockfile-util.c deleted file mode 100644 index 3ee4191e4d..0000000000 --- a/src/basic/lockfile-util.c +++ /dev/null @@ -1,153 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "lockfile-util.h" -#include "macro.h" -#include "path-util.h" - -int make_lock_file(const char *p, int operation, LockFile *ret) { - _cleanup_close_ int fd = -1; - _cleanup_free_ char *t = NULL; - int r; - - /* - * We use UNPOSIX locks if they are available. They have nice - * semantics, and are mostly compatible with NFS. However, - * they are only available on new kernels. When we detect we - * are running on an older kernel, then we fall back to good - * old BSD locks. They also have nice semantics, but are - * slightly problematic on NFS, where they are upgraded to - * POSIX locks, even though locally they are orthogonal to - * POSIX locks. - */ - - t = strdup(p); - if (!t) - return -ENOMEM; - - for (;;) { - struct flock fl = { - .l_type = (operation & ~LOCK_NB) == LOCK_EX ? F_WRLCK : F_RDLCK, - .l_whence = SEEK_SET, - }; - struct stat st; - - fd = open(p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600); - if (fd < 0) - return -errno; - - r = fcntl(fd, (operation & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl); - if (r < 0) { - - /* If the kernel is too old, use good old BSD locks */ - if (errno == EINVAL) - r = flock(fd, operation); - - if (r < 0) - return errno == EAGAIN ? -EBUSY : -errno; - } - - /* If we acquired the lock, let's check if the file - * still exists in the file system. If not, then the - * previous exclusive owner removed it and then closed - * it. In such a case our acquired lock is worthless, - * hence try again. */ - - r = fstat(fd, &st); - if (r < 0) - return -errno; - if (st.st_nlink > 0) - break; - - fd = safe_close(fd); - } - - ret->path = t; - ret->fd = fd; - ret->operation = operation; - - fd = -1; - t = NULL; - - return r; -} - -int make_lock_file_for(const char *p, int operation, LockFile *ret) { - const char *fn; - char *t; - - assert(p); - assert(ret); - - fn = basename(p); - if (!filename_is_valid(fn)) - return -EINVAL; - - t = newa(char, strlen(p) + 2 + 4 + 1); - stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), ".lck"); - - return make_lock_file(t, operation, ret); -} - -void release_lock_file(LockFile *f) { - int r; - - if (!f) - return; - - if (f->path) { - - /* If we are the exclusive owner we can safely delete - * the lock file itself. If we are not the exclusive - * owner, we can try becoming it. */ - - if (f->fd >= 0 && - (f->operation & ~LOCK_NB) == LOCK_SH) { - static const struct flock fl = { - .l_type = F_WRLCK, - .l_whence = SEEK_SET, - }; - - r = fcntl(f->fd, F_OFD_SETLK, &fl); - if (r < 0 && errno == EINVAL) - r = flock(f->fd, LOCK_EX|LOCK_NB); - - if (r >= 0) - f->operation = LOCK_EX|LOCK_NB; - } - - if ((f->operation & ~LOCK_NB) == LOCK_EX) - unlink_noerrno(f->path); - - f->path = mfree(f->path); - } - - f->fd = safe_close(f->fd); - f->operation = 0; -} diff --git a/src/basic/lockfile-util.h b/src/basic/lockfile-util.h deleted file mode 100644 index 22491ee8e1..0000000000 --- a/src/basic/lockfile-util.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include - -#include "macro.h" -#include "missing.h" - -typedef struct LockFile { - char *path; - int fd; - int operation; -} LockFile; - -int make_lock_file(const char *p, int operation, LockFile *ret); -int make_lock_file_for(const char *p, int operation, LockFile *ret); -void release_lock_file(LockFile *f); - -#define _cleanup_release_lock_file_ _cleanup_(release_lock_file) - -#define LOCK_FILE_INIT { .fd = -1, .path = NULL } diff --git a/src/basic/log.c b/src/basic/log.c deleted file mode 100644 index d89e6f7274..0000000000 --- a/src/basic/log.c +++ /dev/null @@ -1,1170 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-messages.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "io-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "process-util.h" -#include "signal-util.h" -#include "socket-util.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" -#include "syslog-util.h" -#include "terminal-util.h" -#include "time-util.h" -#include "util.h" - -#define SNDBUF_SIZE (8*1024*1024) - -static LogTarget log_target = LOG_TARGET_CONSOLE; -static int log_max_level = LOG_INFO; -static int log_facility = LOG_DAEMON; - -static int console_fd = STDERR_FILENO; -static int syslog_fd = -1; -static int kmsg_fd = -1; -static int journal_fd = -1; - -static bool syslog_is_stream = false; - -static bool show_color = false; -static bool show_location = false; - -static bool upgrade_syslog_to_journal = false; - -/* Akin to glibc's __abort_msg; which is private and we hence cannot - * use here. */ -static char *log_abort_msg = NULL; - -void log_close_console(void) { - - if (console_fd < 0) - return; - - if (getpid() == 1) { - if (console_fd >= 3) - safe_close(console_fd); - - console_fd = -1; - } -} - -static int log_open_console(void) { - - if (console_fd >= 0) - return 0; - - if (getpid() == 1) { - console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (console_fd < 0) - return console_fd; - } else - console_fd = STDERR_FILENO; - - return 0; -} - -void log_close_kmsg(void) { - kmsg_fd = safe_close(kmsg_fd); -} - -static int log_open_kmsg(void) { - - if (kmsg_fd >= 0) - return 0; - - kmsg_fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (kmsg_fd < 0) - return -errno; - - return 0; -} - -void log_close_syslog(void) { - syslog_fd = safe_close(syslog_fd); -} - -static int create_log_socket(int type) { - struct timeval tv; - int fd; - - fd = socket(AF_UNIX, type|SOCK_CLOEXEC, 0); - if (fd < 0) - return -errno; - - fd_inc_sndbuf(fd, SNDBUF_SIZE); - - /* We need a blocking fd here since we'd otherwise lose - messages way too early. However, let's not hang forever in the - unlikely case of a deadlock. */ - if (getpid() == 1) - timeval_store(&tv, 10 * USEC_PER_MSEC); - else - timeval_store(&tv, 10 * USEC_PER_SEC); - (void) setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); - - return fd; -} - -static int log_open_syslog(void) { - - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/dev/log", - }; - - int r; - - if (syslog_fd >= 0) - return 0; - - syslog_fd = create_log_socket(SOCK_DGRAM); - if (syslog_fd < 0) { - r = syslog_fd; - goto fail; - } - - if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { - safe_close(syslog_fd); - - /* Some legacy syslog systems still use stream - * sockets. They really shouldn't. But what can we - * do... */ - syslog_fd = create_log_socket(SOCK_STREAM); - if (syslog_fd < 0) { - r = syslog_fd; - goto fail; - } - - if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { - r = -errno; - goto fail; - } - - syslog_is_stream = true; - } else - syslog_is_stream = false; - - return 0; - -fail: - log_close_syslog(); - return r; -} - -void log_close_journal(void) { - journal_fd = safe_close(journal_fd); -} - -static int log_open_journal(void) { - - static const union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/socket", - }; - - int r; - - if (journal_fd >= 0) - return 0; - - journal_fd = create_log_socket(SOCK_DGRAM); - if (journal_fd < 0) { - r = journal_fd; - goto fail; - } - - if (connect(journal_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { - r = -errno; - goto fail; - } - - return 0; - -fail: - log_close_journal(); - return r; -} - -int log_open(void) { - int r; - - /* If we don't use the console we close it here, to not get - * killed by SAK. If we don't use syslog we close it here so - * that we are not confused by somebody deleting the socket in - * the fs. If we don't use /dev/kmsg we still keep it open, - * because there is no reason to close it. */ - - if (log_target == LOG_TARGET_NULL) { - log_close_journal(); - log_close_syslog(); - log_close_console(); - return 0; - } - - if ((log_target != LOG_TARGET_AUTO && log_target != LOG_TARGET_SAFE) || - getpid() == 1 || - isatty(STDERR_FILENO) <= 0) { - - if (log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_JOURNAL) { - r = log_open_journal(); - if (r >= 0) { - log_close_syslog(); - log_close_console(); - return r; - } - } - - if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || - log_target == LOG_TARGET_SYSLOG) { - r = log_open_syslog(); - if (r >= 0) { - log_close_journal(); - log_close_console(); - return r; - } - } - - if (log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_SAFE || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_SYSLOG_OR_KMSG || - log_target == LOG_TARGET_KMSG) { - r = log_open_kmsg(); - if (r >= 0) { - log_close_journal(); - log_close_syslog(); - log_close_console(); - return r; - } - } - } - - log_close_journal(); - log_close_syslog(); - - return log_open_console(); -} - -void log_set_target(LogTarget target) { - assert(target >= 0); - assert(target < _LOG_TARGET_MAX); - - if (upgrade_syslog_to_journal) { - if (target == LOG_TARGET_SYSLOG) - target = LOG_TARGET_JOURNAL; - else if (target == LOG_TARGET_SYSLOG_OR_KMSG) - target = LOG_TARGET_JOURNAL_OR_KMSG; - } - - log_target = target; -} - -void log_close(void) { - log_close_journal(); - log_close_syslog(); - log_close_kmsg(); - log_close_console(); -} - -void log_forget_fds(void) { - console_fd = kmsg_fd = syslog_fd = journal_fd = -1; -} - -void log_set_max_level(int level) { - assert((level & LOG_PRIMASK) == level); - - log_max_level = level; -} - -void log_set_facility(int facility) { - log_facility = facility; -} - -static int write_to_console( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *buffer) { - - char location[64], prefix[1 + DECIMAL_STR_MAX(int) + 2]; - struct iovec iovec[6] = {}; - unsigned n = 0; - bool highlight; - - if (console_fd < 0) - return 0; - - if (log_target == LOG_TARGET_CONSOLE_PREFIXED) { - sprintf(prefix, "<%i>", level); - IOVEC_SET_STRING(iovec[n++], prefix); - } - - highlight = LOG_PRI(level) <= LOG_ERR && show_color; - - if (show_location) { - xsprintf(location, "(%s:%i) ", file, line); - IOVEC_SET_STRING(iovec[n++], location); - } - - if (highlight) - IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_RED); - IOVEC_SET_STRING(iovec[n++], buffer); - if (highlight) - IOVEC_SET_STRING(iovec[n++], ANSI_NORMAL); - IOVEC_SET_STRING(iovec[n++], "\n"); - - if (writev(console_fd, iovec, n) < 0) { - - if (errno == EIO && getpid() == 1) { - - /* If somebody tried to kick us from our - * console tty (via vhangup() or suchlike), - * try to reconnect */ - - log_close_console(); - log_open_console(); - - if (console_fd < 0) - return 0; - - if (writev(console_fd, iovec, n) < 0) - return -errno; - } else - return -errno; - } - - return 1; -} - -static int write_to_syslog( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *buffer) { - - char header_priority[2 + DECIMAL_STR_MAX(int) + 1], - header_time[64], - header_pid[4 + DECIMAL_STR_MAX(pid_t) + 1]; - struct iovec iovec[5] = {}; - struct msghdr msghdr = { - .msg_iov = iovec, - .msg_iovlen = ELEMENTSOF(iovec), - }; - time_t t; - struct tm *tm; - - if (syslog_fd < 0) - return 0; - - xsprintf(header_priority, "<%i>", level); - - t = (time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC); - tm = localtime(&t); - if (!tm) - return -EINVAL; - - if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0) - return -EINVAL; - - xsprintf(header_pid, "["PID_FMT"]: ", getpid()); - - IOVEC_SET_STRING(iovec[0], header_priority); - IOVEC_SET_STRING(iovec[1], header_time); - IOVEC_SET_STRING(iovec[2], program_invocation_short_name); - IOVEC_SET_STRING(iovec[3], header_pid); - IOVEC_SET_STRING(iovec[4], buffer); - - /* When using syslog via SOCK_STREAM separate the messages by NUL chars */ - if (syslog_is_stream) - iovec[4].iov_len++; - - for (;;) { - ssize_t n; - - n = sendmsg(syslog_fd, &msghdr, MSG_NOSIGNAL); - if (n < 0) - return -errno; - - if (!syslog_is_stream || - (size_t) n >= IOVEC_TOTAL_SIZE(iovec, ELEMENTSOF(iovec))) - break; - - IOVEC_INCREMENT(iovec, ELEMENTSOF(iovec), n); - } - - return 1; -} - -static int write_to_kmsg( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *buffer) { - - char header_priority[2 + DECIMAL_STR_MAX(int) + 1], - header_pid[4 + DECIMAL_STR_MAX(pid_t) + 1]; - struct iovec iovec[5] = {}; - - if (kmsg_fd < 0) - return 0; - - xsprintf(header_priority, "<%i>", level); - xsprintf(header_pid, "["PID_FMT"]: ", getpid()); - - IOVEC_SET_STRING(iovec[0], header_priority); - IOVEC_SET_STRING(iovec[1], program_invocation_short_name); - IOVEC_SET_STRING(iovec[2], header_pid); - IOVEC_SET_STRING(iovec[3], buffer); - IOVEC_SET_STRING(iovec[4], "\n"); - - if (writev(kmsg_fd, iovec, ELEMENTSOF(iovec)) < 0) - return -errno; - - return 1; -} - -static int log_do_header( - char *header, - size_t size, - int level, - int error, - const char *file, int line, const char *func, - const char *object_field, const char *object) { - - snprintf(header, size, - "PRIORITY=%i\n" - "SYSLOG_FACILITY=%i\n" - "%s%s%s" - "%s%.*i%s" - "%s%s%s" - "%s%.*i%s" - "%s%s%s" - "SYSLOG_IDENTIFIER=%s\n", - LOG_PRI(level), - LOG_FAC(level), - isempty(file) ? "" : "CODE_FILE=", - isempty(file) ? "" : file, - isempty(file) ? "" : "\n", - line ? "CODE_LINE=" : "", - line ? 1 : 0, line, /* %.0d means no output too, special case for 0 */ - line ? "\n" : "", - isempty(func) ? "" : "CODE_FUNCTION=", - isempty(func) ? "" : func, - isempty(func) ? "" : "\n", - error ? "ERRNO=" : "", - error ? 1 : 0, error, - error ? "\n" : "", - isempty(object) ? "" : object_field, - isempty(object) ? "" : object, - isempty(object) ? "" : "\n", - program_invocation_short_name); - - return 0; -} - -static int write_to_journal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *buffer) { - - char header[LINE_MAX]; - struct iovec iovec[4] = {}; - struct msghdr mh = {}; - - if (journal_fd < 0) - return 0; - - log_do_header(header, sizeof(header), level, error, file, line, func, object_field, object); - - IOVEC_SET_STRING(iovec[0], header); - IOVEC_SET_STRING(iovec[1], "MESSAGE="); - IOVEC_SET_STRING(iovec[2], buffer); - IOVEC_SET_STRING(iovec[3], "\n"); - - mh.msg_iov = iovec; - mh.msg_iovlen = ELEMENTSOF(iovec); - - if (sendmsg(journal_fd, &mh, MSG_NOSIGNAL) < 0) - return -errno; - - return 1; -} - -static int log_dispatch( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - char *buffer) { - - assert(buffer); - - if (log_target == LOG_TARGET_NULL) - return -error; - - /* Patch in LOG_DAEMON facility if necessary */ - if ((level & LOG_FACMASK) == 0) - level = log_facility | LOG_PRI(level); - - if (error < 0) - error = -error; - - do { - char *e; - int k = 0; - - buffer += strspn(buffer, NEWLINE); - - if (buffer[0] == 0) - break; - - if ((e = strpbrk(buffer, NEWLINE))) - *(e++) = 0; - - if (log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_JOURNAL) { - - k = write_to_journal(level, error, file, line, func, object_field, object, buffer); - if (k < 0) { - if (k != -EAGAIN) - log_close_journal(); - log_open_kmsg(); - } - } - - if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || - log_target == LOG_TARGET_SYSLOG) { - - k = write_to_syslog(level, error, file, line, func, object_field, object, buffer); - if (k < 0) { - if (k != -EAGAIN) - log_close_syslog(); - log_open_kmsg(); - } - } - - if (k <= 0 && - (log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_SAFE || - log_target == LOG_TARGET_SYSLOG_OR_KMSG || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_KMSG)) { - - k = write_to_kmsg(level, error, file, line, func, object_field, object, buffer); - if (k < 0) { - log_close_kmsg(); - log_open_console(); - } - } - - if (k <= 0) - (void) write_to_console(level, error, file, line, func, object_field, object, buffer); - - buffer = e; - } while (buffer); - - return -error; -} - -int log_dump_internal( - int level, - int error, - const char *file, - int line, - const char *func, - char *buffer) { - - PROTECT_ERRNO; - - /* This modifies the buffer... */ - - if (error < 0) - error = -error; - - if (_likely_(LOG_PRI(level) > log_max_level)) - return -error; - - return log_dispatch(level, error, file, line, func, NULL, NULL, buffer); -} - -int log_internalv( - int level, - int error, - const char *file, - int line, - const char *func, - const char *format, - va_list ap) { - - PROTECT_ERRNO; - char buffer[LINE_MAX]; - - if (error < 0) - error = -error; - - if (_likely_(LOG_PRI(level) > log_max_level)) - return -error; - - /* Make sure that %m maps to the specified error */ - if (error != 0) - errno = error; - - vsnprintf(buffer, sizeof(buffer), format, ap); - - return log_dispatch(level, error, file, line, func, NULL, NULL, buffer); -} - -int log_internal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *format, ...) { - - va_list ap; - int r; - - va_start(ap, format); - r = log_internalv(level, error, file, line, func, format, ap); - va_end(ap); - - return r; -} - -int log_object_internalv( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *format, - va_list ap) { - - PROTECT_ERRNO; - char *buffer, *b; - size_t l; - - if (error < 0) - error = -error; - - if (_likely_(LOG_PRI(level) > log_max_level)) - return -error; - - /* Make sure that %m maps to the specified error */ - if (error != 0) - errno = error; - - /* Prepend the object name before the message */ - if (object) { - size_t n; - - n = strlen(object); - l = n + 2 + LINE_MAX; - - buffer = newa(char, l); - b = stpcpy(stpcpy(buffer, object), ": "); - } else { - l = LINE_MAX; - b = buffer = newa(char, l); - } - - vsnprintf(b, l, format, ap); - - return log_dispatch(level, error, file, line, func, object_field, object, buffer); -} - -int log_object_internal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *format, ...) { - - va_list ap; - int r; - - va_start(ap, format); - r = log_object_internalv(level, error, file, line, func, object_field, object, format, ap); - va_end(ap); - - return r; -} - -static void log_assert( - int level, - const char *text, - const char *file, - int line, - const char *func, - const char *format) { - - static char buffer[LINE_MAX]; - - if (_likely_(LOG_PRI(level) > log_max_level)) - return; - - DISABLE_WARNING_FORMAT_NONLITERAL; - xsprintf(buffer, format, text, file, line, func); - REENABLE_WARNING; - - log_abort_msg = buffer; - - log_dispatch(level, 0, file, line, func, NULL, NULL, buffer); -} - -noreturn void log_assert_failed(const char *text, const char *file, int line, const char *func) { - log_assert(LOG_CRIT, text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting."); - abort(); -} - -noreturn void log_assert_failed_unreachable(const char *text, const char *file, int line, const char *func) { - log_assert(LOG_CRIT, text, file, line, func, "Code should not be reached '%s' at %s:%u, function %s(). Aborting."); - abort(); -} - -void log_assert_failed_return(const char *text, const char *file, int line, const char *func) { - PROTECT_ERRNO; - log_assert(LOG_DEBUG, text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Ignoring."); -} - -int log_oom_internal(const char *file, int line, const char *func) { - log_internal(LOG_ERR, ENOMEM, file, line, func, "Out of memory."); - return -ENOMEM; -} - -int log_format_iovec( - struct iovec *iovec, - unsigned iovec_len, - unsigned *n, - bool newline_separator, - int error, - const char *format, - va_list ap) { - - static const char nl = '\n'; - - while (format && *n + 1 < iovec_len) { - va_list aq; - char *m; - int r; - - /* We need to copy the va_list structure, - * since vasprintf() leaves it afterwards at - * an undefined location */ - - if (error != 0) - errno = error; - - va_copy(aq, ap); - r = vasprintf(&m, format, aq); - va_end(aq); - if (r < 0) - return -EINVAL; - - /* Now, jump enough ahead, so that we point to - * the next format string */ - VA_FORMAT_ADVANCE(format, ap); - - IOVEC_SET_STRING(iovec[(*n)++], m); - - if (newline_separator) { - iovec[*n].iov_base = (char*) &nl; - iovec[*n].iov_len = 1; - (*n)++; - } - - format = va_arg(ap, char *); - } - return 0; -} - -int log_struct_internal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *format, ...) { - - char buf[LINE_MAX]; - bool found = false; - PROTECT_ERRNO; - va_list ap; - - if (error < 0) - error = -error; - - if (_likely_(LOG_PRI(level) > log_max_level)) - return -error; - - if (log_target == LOG_TARGET_NULL) - return -error; - - if ((level & LOG_FACMASK) == 0) - level = log_facility | LOG_PRI(level); - - if ((log_target == LOG_TARGET_AUTO || - log_target == LOG_TARGET_JOURNAL_OR_KMSG || - log_target == LOG_TARGET_JOURNAL) && - journal_fd >= 0) { - char header[LINE_MAX]; - struct iovec iovec[17] = {}; - unsigned n = 0, i; - int r; - struct msghdr mh = { - .msg_iov = iovec, - }; - bool fallback = false; - - /* If the journal is available do structured logging */ - log_do_header(header, sizeof(header), level, error, file, line, func, NULL, NULL); - IOVEC_SET_STRING(iovec[n++], header); - - va_start(ap, format); - r = log_format_iovec(iovec, ELEMENTSOF(iovec), &n, true, error, format, ap); - if (r < 0) - fallback = true; - else { - mh.msg_iovlen = n; - (void) sendmsg(journal_fd, &mh, MSG_NOSIGNAL); - } - - va_end(ap); - for (i = 1; i < n; i += 2) - free(iovec[i].iov_base); - - if (!fallback) - return -error; - } - - /* Fallback if journal logging is not available or didn't work. */ - - va_start(ap, format); - while (format) { - va_list aq; - - if (error != 0) - errno = error; - - va_copy(aq, ap); - vsnprintf(buf, sizeof(buf), format, aq); - va_end(aq); - - if (startswith(buf, "MESSAGE=")) { - found = true; - break; - } - - VA_FORMAT_ADVANCE(format, ap); - - format = va_arg(ap, char *); - } - va_end(ap); - - if (!found) - return -error; - - return log_dispatch(level, error, file, line, func, NULL, NULL, buf + 8); -} - -int log_set_target_from_string(const char *e) { - LogTarget t; - - t = log_target_from_string(e); - if (t < 0) - return -EINVAL; - - log_set_target(t); - return 0; -} - -int log_set_max_level_from_string(const char *e) { - int t; - - t = log_level_from_string(e); - if (t < 0) - return -EINVAL; - - log_set_max_level(t); - return 0; -} - -static int parse_proc_cmdline_item(const char *key, const char *value) { - - /* - * The systemd.log_xyz= settings are parsed by all tools, and - * so is "debug". - * - * However, "quiet" is only parsed by PID 1, and only turns of - * status output to /dev/console, but does not alter the log - * level. - */ - - if (streq(key, "debug") && !value) - log_set_max_level(LOG_DEBUG); - - else if (streq(key, "systemd.log_target") && value) { - - if (log_set_target_from_string(value) < 0) - log_warning("Failed to parse log target '%s'. Ignoring.", value); - - } else if (streq(key, "systemd.log_level") && value) { - - if (log_set_max_level_from_string(value) < 0) - log_warning("Failed to parse log level '%s'. Ignoring.", value); - - } else if (streq(key, "systemd.log_color") && value) { - - if (log_show_color_from_string(value) < 0) - log_warning("Failed to parse log color setting '%s'. Ignoring.", value); - - } else if (streq(key, "systemd.log_location") && value) { - - if (log_show_location_from_string(value) < 0) - log_warning("Failed to parse log location setting '%s'. Ignoring.", value); - } - - return 0; -} - -void log_parse_environment(void) { - const char *e; - - if (get_ctty_devnr(0, NULL) < 0) - /* Only try to read the command line in daemons. - We assume that anything that has a controlling - tty is user stuff. */ - (void) parse_proc_cmdline(parse_proc_cmdline_item); - - e = secure_getenv("SYSTEMD_LOG_TARGET"); - if (e && log_set_target_from_string(e) < 0) - log_warning("Failed to parse log target '%s'. Ignoring.", e); - - e = secure_getenv("SYSTEMD_LOG_LEVEL"); - if (e && log_set_max_level_from_string(e) < 0) - log_warning("Failed to parse log level '%s'. Ignoring.", e); - - e = secure_getenv("SYSTEMD_LOG_COLOR"); - if (e && log_show_color_from_string(e) < 0) - log_warning("Failed to parse bool '%s'. Ignoring.", e); - - e = secure_getenv("SYSTEMD_LOG_LOCATION"); - if (e && log_show_location_from_string(e) < 0) - log_warning("Failed to parse bool '%s'. Ignoring.", e); -} - -LogTarget log_get_target(void) { - return log_target; -} - -int log_get_max_level(void) { - return log_max_level; -} - -void log_show_color(bool b) { - show_color = b; -} - -bool log_get_show_color(void) { - return show_color; -} - -void log_show_location(bool b) { - show_location = b; -} - -bool log_get_show_location(void) { - return show_location; -} - -int log_show_color_from_string(const char *e) { - int t; - - t = parse_boolean(e); - if (t < 0) - return t; - - log_show_color(t); - return 0; -} - -int log_show_location_from_string(const char *e) { - int t; - - t = parse_boolean(e); - if (t < 0) - return t; - - log_show_location(t); - return 0; -} - -bool log_on_console(void) { - if (log_target == LOG_TARGET_CONSOLE || - log_target == LOG_TARGET_CONSOLE_PREFIXED) - return true; - - return syslog_fd < 0 && kmsg_fd < 0 && journal_fd < 0; -} - -static const char *const log_target_table[_LOG_TARGET_MAX] = { - [LOG_TARGET_CONSOLE] = "console", - [LOG_TARGET_CONSOLE_PREFIXED] = "console-prefixed", - [LOG_TARGET_KMSG] = "kmsg", - [LOG_TARGET_JOURNAL] = "journal", - [LOG_TARGET_JOURNAL_OR_KMSG] = "journal-or-kmsg", - [LOG_TARGET_SYSLOG] = "syslog", - [LOG_TARGET_SYSLOG_OR_KMSG] = "syslog-or-kmsg", - [LOG_TARGET_AUTO] = "auto", - [LOG_TARGET_SAFE] = "safe", - [LOG_TARGET_NULL] = "null" -}; - -DEFINE_STRING_TABLE_LOOKUP(log_target, LogTarget); - -void log_received_signal(int level, const struct signalfd_siginfo *si) { - if (si->ssi_pid > 0) { - _cleanup_free_ char *p = NULL; - - get_process_comm(si->ssi_pid, &p); - - log_full(level, - "Received SIG%s from PID %"PRIu32" (%s).", - signal_to_string(si->ssi_signo), - si->ssi_pid, strna(p)); - } else - log_full(level, - "Received SIG%s.", - signal_to_string(si->ssi_signo)); - -} - -void log_set_upgrade_syslog_to_journal(bool b) { - upgrade_syslog_to_journal = b; -} - -int log_syntax_internal( - const char *unit, - int level, - const char *config_file, - unsigned config_line, - int error, - const char *file, - int line, - const char *func, - const char *format, ...) { - - PROTECT_ERRNO; - char buffer[LINE_MAX]; - int r; - va_list ap; - - if (error < 0) - error = -error; - - if (_likely_(LOG_PRI(level) > log_max_level)) - return -error; - - if (log_target == LOG_TARGET_NULL) - return -error; - - if (error != 0) - errno = error; - - va_start(ap, format); - vsnprintf(buffer, sizeof(buffer), format, ap); - va_end(ap); - - if (unit) - r = log_struct_internal( - level, error, - file, line, func, - getpid() == 1 ? "UNIT=%s" : "USER_UNIT=%s", unit, - LOG_MESSAGE_ID(SD_MESSAGE_INVALID_CONFIGURATION), - "CONFIG_FILE=%s", config_file, - "CONFIG_LINE=%u", config_line, - LOG_MESSAGE("[%s:%u] %s", config_file, config_line, buffer), - NULL); - else - r = log_struct_internal( - level, error, - file, line, func, - LOG_MESSAGE_ID(SD_MESSAGE_INVALID_CONFIGURATION), - "CONFIG_FILE=%s", config_file, - "CONFIG_LINE=%u", config_line, - LOG_MESSAGE("[%s:%u] %s", config_file, config_line, buffer), - NULL); - - return r; -} diff --git a/src/basic/log.h b/src/basic/log.h deleted file mode 100644 index 60ddead74c..0000000000 --- a/src/basic/log.h +++ /dev/null @@ -1,250 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "sd-id128.h" - -#include "macro.h" - -typedef enum LogTarget{ - LOG_TARGET_CONSOLE, - LOG_TARGET_CONSOLE_PREFIXED, - LOG_TARGET_KMSG, - LOG_TARGET_JOURNAL, - LOG_TARGET_JOURNAL_OR_KMSG, - LOG_TARGET_SYSLOG, - LOG_TARGET_SYSLOG_OR_KMSG, - LOG_TARGET_AUTO, /* console if stderr is tty, JOURNAL_OR_KMSG otherwise */ - LOG_TARGET_SAFE, /* console if stderr is tty, KMSG otherwise */ - LOG_TARGET_NULL, - _LOG_TARGET_MAX, - _LOG_TARGET_INVALID = -1 -} LogTarget; - -void log_set_target(LogTarget target); -void log_set_max_level(int level); -void log_set_facility(int facility); - -int log_set_target_from_string(const char *e); -int log_set_max_level_from_string(const char *e); - -void log_show_color(bool b); -bool log_get_show_color(void) _pure_; -void log_show_location(bool b); -bool log_get_show_location(void) _pure_; - -int log_show_color_from_string(const char *e); -int log_show_location_from_string(const char *e); - -LogTarget log_get_target(void) _pure_; -int log_get_max_level(void) _pure_; - -int log_open(void); -void log_close(void); -void log_forget_fds(void); - -void log_close_syslog(void); -void log_close_journal(void); -void log_close_kmsg(void); -void log_close_console(void); - -void log_parse_environment(void); - -int log_internal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *format, ...) _printf_(6,7); - -int log_internalv( - int level, - int error, - const char *file, - int line, - const char *func, - const char *format, - va_list ap) _printf_(6,0); - -int log_object_internal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *format, ...) _printf_(8,9); - -int log_object_internalv( - int level, - int error, - const char*file, - int line, - const char *func, - const char *object_field, - const char *object, - const char *format, - va_list ap) _printf_(8,0); - -int log_struct_internal( - int level, - int error, - const char *file, - int line, - const char *func, - const char *format, ...) _printf_(6,0) _sentinel_; - -int log_oom_internal( - const char *file, - int line, - const char *func); - -int log_format_iovec( - struct iovec *iovec, - unsigned iovec_len, - unsigned *n, - bool newline_separator, - int error, - const char *format, - va_list ap); - -/* This modifies the buffer passed! */ -int log_dump_internal( - int level, - int error, - const char *file, - int line, - const char *func, - char *buffer); - -/* Logging for various assertions */ -noreturn void log_assert_failed( - const char *text, - const char *file, - int line, - const char *func); - -noreturn void log_assert_failed_unreachable( - const char *text, - const char *file, - int line, - const char *func); - -void log_assert_failed_return( - const char *text, - const char *file, - int line, - const char *func); - -/* Logging with level */ -#define log_full_errno(level, error, ...) \ - ({ \ - int _level = (level), _e = (error); \ - (log_get_max_level() >= LOG_PRI(_level)) \ - ? log_internal(_level, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \ - : -abs(_e); \ - }) - -#define log_full(level, ...) log_full_errno(level, 0, __VA_ARGS__) - -/* Normal logging */ -#define log_debug(...) log_full(LOG_DEBUG, __VA_ARGS__) -#define log_info(...) log_full(LOG_INFO, __VA_ARGS__) -#define log_notice(...) log_full(LOG_NOTICE, __VA_ARGS__) -#define log_warning(...) log_full(LOG_WARNING, __VA_ARGS__) -#define log_error(...) log_full(LOG_ERR, __VA_ARGS__) -#define log_emergency(...) log_full(getpid() == 1 ? LOG_EMERG : LOG_ERR, __VA_ARGS__) - -/* Logging triggered by an errno-like error */ -#define log_debug_errno(error, ...) log_full_errno(LOG_DEBUG, error, __VA_ARGS__) -#define log_info_errno(error, ...) log_full_errno(LOG_INFO, error, __VA_ARGS__) -#define log_notice_errno(error, ...) log_full_errno(LOG_NOTICE, error, __VA_ARGS__) -#define log_warning_errno(error, ...) log_full_errno(LOG_WARNING, error, __VA_ARGS__) -#define log_error_errno(error, ...) log_full_errno(LOG_ERR, error, __VA_ARGS__) -#define log_emergency_errno(error, ...) log_full_errno(getpid() == 1 ? LOG_EMERG : LOG_ERR, error, __VA_ARGS__) - -#ifdef LOG_TRACE -# define log_trace(...) log_debug(__VA_ARGS__) -#else -# define log_trace(...) do {} while(0) -#endif - -/* Structured logging */ -#define log_struct(level, ...) log_struct_internal(level, 0, __FILE__, __LINE__, __func__, __VA_ARGS__) -#define log_struct_errno(level, error, ...) log_struct_internal(level, error, __FILE__, __LINE__, __func__, __VA_ARGS__) - -/* This modifies the buffer passed! */ -#define log_dump(level, buffer) log_dump_internal(level, 0, __FILE__, __LINE__, __func__, buffer) - -#define log_oom() log_oom_internal(__FILE__, __LINE__, __func__) - -bool log_on_console(void) _pure_; - -const char *log_target_to_string(LogTarget target) _const_; -LogTarget log_target_from_string(const char *s) _pure_; - -/* Helpers to prepare various fields for structured logging */ -#define LOG_MESSAGE(fmt, ...) "MESSAGE=" fmt, ##__VA_ARGS__ -#define LOG_MESSAGE_ID(x) "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(x) - -void log_received_signal(int level, const struct signalfd_siginfo *si); - -void log_set_upgrade_syslog_to_journal(bool b); - -int log_syntax_internal( - const char *unit, - int level, - const char *config_file, - unsigned config_line, - int error, - const char *file, - int line, - const char *func, - const char *format, ...) _printf_(9, 10); - -#define log_syntax(unit, level, config_file, config_line, error, ...) \ - ({ \ - int _level = (level), _e = (error); \ - (log_get_max_level() >= LOG_PRI(_level)) \ - ? log_syntax_internal(unit, _level, config_file, config_line, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \ - : -abs(_e); \ - }) - -#define log_syntax_invalid_utf8(unit, level, config_file, config_line, rvalue) \ - ({ \ - int _level = (level); \ - if (log_get_max_level() >= LOG_PRI(_level)) { \ - _cleanup_free_ char *_p = NULL; \ - _p = utf8_escape_invalid(rvalue); \ - log_syntax_internal(unit, _level, config_file, config_line, 0, __FILE__, __LINE__, __func__, \ - "String is not UTF-8 clean, ignoring assignment: %s", strna(_p)); \ - } \ - -EINVAL; \ - }) diff --git a/src/basic/login-util.c b/src/basic/login-util.c deleted file mode 100644 index 339e94f12d..0000000000 --- a/src/basic/login-util.c +++ /dev/null @@ -1,31 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#include - -#include "login-util.h" -#include "string-util.h" - -bool session_id_valid(const char *id) { - - if (isempty(id)) - return false; - - return id[strspn(id, LETTERS DIGITS)] == '\0'; -} diff --git a/src/basic/login-util.h b/src/basic/login-util.h deleted file mode 100644 index 89a337d7c1..0000000000 --- a/src/basic/login-util.h +++ /dev/null @@ -1,29 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#pragma once - -#include -#include - -bool session_id_valid(const char *id); - -static inline bool logind_running(void) { - return access("/run/systemd/seats/", F_OK) >= 0; -} diff --git a/src/basic/macro.h b/src/basic/macro.h deleted file mode 100644 index 2695d0edb7..0000000000 --- a/src/basic/macro.h +++ /dev/null @@ -1,395 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#define _printf_(a,b) __attribute__ ((format (printf, a, b))) -#define _alloc_(...) __attribute__ ((alloc_size(__VA_ARGS__))) -#define _sentinel_ __attribute__ ((sentinel)) -#define _unused_ __attribute__ ((unused)) -#define _destructor_ __attribute__ ((destructor)) -#define _pure_ __attribute__ ((pure)) -#define _const_ __attribute__ ((const)) -#define _deprecated_ __attribute__ ((deprecated)) -#define _packed_ __attribute__ ((packed)) -#define _malloc_ __attribute__ ((malloc)) -#define _weak_ __attribute__ ((weak)) -#define _likely_(x) (__builtin_expect(!!(x),1)) -#define _unlikely_(x) (__builtin_expect(!!(x),0)) -#define _public_ __attribute__ ((visibility("default"))) -#define _hidden_ __attribute__ ((visibility("hidden"))) -#define _weakref_(x) __attribute__((weakref(#x))) -#define _alignas_(x) __attribute__((aligned(__alignof(x)))) -#define _cleanup_(x) __attribute__((cleanup(x))) - -/* Temporarily disable some warnings */ -#define DISABLE_WARNING_DECLARATION_AFTER_STATEMENT \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic ignored \"-Wdeclaration-after-statement\"") - -#define DISABLE_WARNING_FORMAT_NONLITERAL \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"") - -#define DISABLE_WARNING_MISSING_PROTOTYPES \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic ignored \"-Wmissing-prototypes\"") - -#define DISABLE_WARNING_NONNULL \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic ignored \"-Wnonnull\"") - -#define DISABLE_WARNING_SHADOW \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic ignored \"-Wshadow\"") - -#define DISABLE_WARNING_INCOMPATIBLE_POINTER_TYPES \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"") - -#define REENABLE_WARNING \ - _Pragma("GCC diagnostic pop") - -/* automake test harness */ -#define EXIT_TEST_SKIP 77 - -#define XSTRINGIFY(x) #x -#define STRINGIFY(x) XSTRINGIFY(x) - -#define XCONCATENATE(x, y) x ## y -#define CONCATENATE(x, y) XCONCATENATE(x, y) - -#define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq)) -#define UNIQ __COUNTER__ - -/* Rounds up */ - -#define ALIGN4(l) (((l) + 3) & ~3) -#define ALIGN8(l) (((l) + 7) & ~7) - -#if __SIZEOF_POINTER__ == 8 -#define ALIGN(l) ALIGN8(l) -#elif __SIZEOF_POINTER__ == 4 -#define ALIGN(l) ALIGN4(l) -#else -#error "Wut? Pointers are neither 4 nor 8 bytes long?" -#endif - -#define ALIGN_PTR(p) ((void*) ALIGN((unsigned long) (p))) -#define ALIGN4_PTR(p) ((void*) ALIGN4((unsigned long) (p))) -#define ALIGN8_PTR(p) ((void*) ALIGN8((unsigned long) (p))) - -static inline size_t ALIGN_TO(size_t l, size_t ali) { - return ((l + ali - 1) & ~(ali - 1)); -} - -#define ALIGN_TO_PTR(p, ali) ((void*) ALIGN_TO((unsigned long) (p), (ali))) - -/* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */ -static inline unsigned long ALIGN_POWER2(unsigned long u) { - /* clz(0) is undefined */ - if (u == 1) - return 1; - - /* left-shift overflow is undefined */ - if (__builtin_clzl(u - 1UL) < 1) - return 0; - - return 1UL << (sizeof(u) * 8 - __builtin_clzl(u - 1UL)); -} - -#define ELEMENTSOF(x) \ - __extension__ (__builtin_choose_expr( \ - !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \ - sizeof(x)/sizeof((x)[0]), \ - (void)0)) -/* - * container_of - cast a member of a structure out to the containing structure - * @ptr: the pointer to the member. - * @type: the type of the container struct this is embedded in. - * @member: the name of the member within the struct. - */ -#define container_of(ptr, type, member) __container_of(UNIQ, (ptr), type, member) -#define __container_of(uniq, ptr, type, member) \ - __extension__ ({ \ - const typeof( ((type*)0)->member ) *UNIQ_T(A, uniq) = (ptr); \ - (type*)( (char *)UNIQ_T(A, uniq) - offsetof(type,member) ); \ - }) - -#undef MAX -#define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b)) -#define __MAX(aq, a, bq, b) \ - __extension__ ({ \ - const typeof(a) UNIQ_T(A, aq) = (a); \ - const typeof(b) UNIQ_T(B, bq) = (b); \ - UNIQ_T(A,aq) > UNIQ_T(B,bq) ? UNIQ_T(A,aq) : UNIQ_T(B,bq); \ - }) - -/* evaluates to (void) if _A or _B are not constant or of different types */ -#define CONST_MAX(_A, _B) \ - __extension__ (__builtin_choose_expr( \ - __builtin_constant_p(_A) && \ - __builtin_constant_p(_B) && \ - __builtin_types_compatible_p(typeof(_A), typeof(_B)), \ - ((_A) > (_B)) ? (_A) : (_B), \ - (void)0)) - -/* takes two types and returns the size of the larger one */ -#define MAXSIZE(A, B) (sizeof(union _packed_ { typeof(A) a; typeof(B) b; })) - -#define MAX3(x,y,z) \ - __extension__ ({ \ - const typeof(x) _c = MAX(x,y); \ - MAX(_c, z); \ - }) - -#undef MIN -#define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b)) -#define __MIN(aq, a, bq, b) \ - __extension__ ({ \ - const typeof(a) UNIQ_T(A, aq) = (a); \ - const typeof(b) UNIQ_T(B, bq) = (b); \ - UNIQ_T(A,aq) < UNIQ_T(B,bq) ? UNIQ_T(A,aq) : UNIQ_T(B,bq); \ - }) - -#define MIN3(x,y,z) \ - __extension__ ({ \ - const typeof(x) _c = MIN(x,y); \ - MIN(_c, z); \ - }) - -#define LESS_BY(a, b) __LESS_BY(UNIQ, (a), UNIQ, (b)) -#define __LESS_BY(aq, a, bq, b) \ - __extension__ ({ \ - const typeof(a) UNIQ_T(A, aq) = (a); \ - const typeof(b) UNIQ_T(B, bq) = (b); \ - UNIQ_T(A,aq) > UNIQ_T(B,bq) ? UNIQ_T(A,aq) - UNIQ_T(B,bq) : 0; \ - }) - -#undef CLAMP -#define CLAMP(x, low, high) __CLAMP(UNIQ, (x), UNIQ, (low), UNIQ, (high)) -#define __CLAMP(xq, x, lowq, low, highq, high) \ - __extension__ ({ \ - const typeof(x) UNIQ_T(X,xq) = (x); \ - const typeof(low) UNIQ_T(LOW,lowq) = (low); \ - const typeof(high) UNIQ_T(HIGH,highq) = (high); \ - UNIQ_T(X,xq) > UNIQ_T(HIGH,highq) ? \ - UNIQ_T(HIGH,highq) : \ - UNIQ_T(X,xq) < UNIQ_T(LOW,lowq) ? \ - UNIQ_T(LOW,lowq) : \ - UNIQ_T(X,xq); \ - }) - -/* [(x + y - 1) / y] suffers from an integer overflow, even though the - * computation should be possible in the given type. Therefore, we use - * [x / y + !!(x % y)]. Note that on "Real CPUs" a division returns both the - * quotient and the remainder, so both should be equally fast. */ -#define DIV_ROUND_UP(_x, _y) \ - __extension__ ({ \ - const typeof(_x) __x = (_x); \ - const typeof(_y) __y = (_y); \ - (__x / __y + !!(__x % __y)); \ - }) - -#define assert_message_se(expr, message) \ - do { \ - if (_unlikely_(!(expr))) \ - log_assert_failed(message, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ - } while (false) - -#define assert_se(expr) assert_message_se(expr, #expr) - -/* We override the glibc assert() here. */ -#undef assert -#ifdef NDEBUG -#define assert(expr) do {} while(false) -#else -#define assert(expr) assert_message_se(expr, #expr) -#endif - -#define assert_not_reached(t) \ - do { \ - log_assert_failed_unreachable(t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ - } while (false) - -#if defined(static_assert) -/* static_assert() is sometimes defined in a way that trips up - * -Wdeclaration-after-statement, hence let's temporarily turn off - * this warning around it. */ -#define assert_cc(expr) \ - DISABLE_WARNING_DECLARATION_AFTER_STATEMENT; \ - static_assert(expr, #expr); \ - REENABLE_WARNING -#else -#define assert_cc(expr) \ - DISABLE_WARNING_DECLARATION_AFTER_STATEMENT; \ - struct CONCATENATE(_assert_struct_, __COUNTER__) { \ - char x[(expr) ? 0 : -1]; \ - }; \ - REENABLE_WARNING -#endif - -#define assert_log(expr, message) ((_likely_(expr)) \ - ? (true) \ - : (log_assert_failed_return(message, __FILE__, __LINE__, __PRETTY_FUNCTION__), false)) - -#define assert_return(expr, r) \ - do { \ - if (!assert_log(expr, #expr)) \ - return (r); \ - } while (false) - -#define assert_return_errno(expr, r, err) \ - do { \ - if (!assert_log(expr, #expr)) { \ - errno = err; \ - return (r); \ - } \ - } while (false) - -#define PTR_TO_INT(p) ((int) ((intptr_t) (p))) -#define INT_TO_PTR(u) ((void *) ((intptr_t) (u))) -#define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) -#define UINT_TO_PTR(u) ((void *) ((uintptr_t) (u))) - -#define PTR_TO_LONG(p) ((long) ((intptr_t) (p))) -#define LONG_TO_PTR(u) ((void *) ((intptr_t) (u))) -#define PTR_TO_ULONG(p) ((unsigned long) ((uintptr_t) (p))) -#define ULONG_TO_PTR(u) ((void *) ((uintptr_t) (u))) - -#define PTR_TO_INT32(p) ((int32_t) ((intptr_t) (p))) -#define INT32_TO_PTR(u) ((void *) ((intptr_t) (u))) -#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p))) -#define UINT32_TO_PTR(u) ((void *) ((uintptr_t) (u))) - -#define PTR_TO_INT64(p) ((int64_t) ((intptr_t) (p))) -#define INT64_TO_PTR(u) ((void *) ((intptr_t) (u))) -#define PTR_TO_UINT64(p) ((uint64_t) ((uintptr_t) (p))) -#define UINT64_TO_PTR(u) ((void *) ((uintptr_t) (u))) - -#define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p))) -#define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u))) - -#define CHAR_TO_STR(x) ((char[2]) { x, 0 }) - -#define char_array_0(x) x[sizeof(x)-1] = 0; - -/* Returns the number of chars needed to format variables of the - * specified type as a decimal string. Adds in extra space for a - * negative '-' prefix (hence works correctly on signed - * types). Includes space for the trailing NUL. */ -#define DECIMAL_STR_MAX(type) \ - (2+(sizeof(type) <= 1 ? 3 : \ - sizeof(type) <= 2 ? 5 : \ - sizeof(type) <= 4 ? 10 : \ - sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)]))) - -#define DECIMAL_STR_WIDTH(x) \ - ({ \ - typeof(x) _x_ = (x); \ - unsigned ans = 1; \ - while (_x_ /= 10) \ - ans++; \ - ans; \ - }) - -#define SET_FLAG(v, flag, b) \ - (v) = (b) ? ((v) | (flag)) : ((v) & ~(flag)) - -#define CASE_F(X) case X: -#define CASE_F_1(CASE, X) CASE_F(X) -#define CASE_F_2(CASE, X, ...) CASE(X) CASE_F_1(CASE, __VA_ARGS__) -#define CASE_F_3(CASE, X, ...) CASE(X) CASE_F_2(CASE, __VA_ARGS__) -#define CASE_F_4(CASE, X, ...) CASE(X) CASE_F_3(CASE, __VA_ARGS__) -#define CASE_F_5(CASE, X, ...) CASE(X) CASE_F_4(CASE, __VA_ARGS__) -#define CASE_F_6(CASE, X, ...) CASE(X) CASE_F_5(CASE, __VA_ARGS__) -#define CASE_F_7(CASE, X, ...) CASE(X) CASE_F_6(CASE, __VA_ARGS__) -#define CASE_F_8(CASE, X, ...) CASE(X) CASE_F_7(CASE, __VA_ARGS__) -#define CASE_F_9(CASE, X, ...) CASE(X) CASE_F_8(CASE, __VA_ARGS__) -#define CASE_F_10(CASE, X, ...) CASE(X) CASE_F_9(CASE, __VA_ARGS__) -#define CASE_F_11(CASE, X, ...) CASE(X) CASE_F_10(CASE, __VA_ARGS__) -#define CASE_F_12(CASE, X, ...) CASE(X) CASE_F_11(CASE, __VA_ARGS__) -#define CASE_F_13(CASE, X, ...) CASE(X) CASE_F_12(CASE, __VA_ARGS__) -#define CASE_F_14(CASE, X, ...) CASE(X) CASE_F_13(CASE, __VA_ARGS__) -#define CASE_F_15(CASE, X, ...) CASE(X) CASE_F_14(CASE, __VA_ARGS__) -#define CASE_F_16(CASE, X, ...) CASE(X) CASE_F_15(CASE, __VA_ARGS__) -#define CASE_F_17(CASE, X, ...) CASE(X) CASE_F_16(CASE, __VA_ARGS__) -#define CASE_F_18(CASE, X, ...) CASE(X) CASE_F_17(CASE, __VA_ARGS__) -#define CASE_F_19(CASE, X, ...) CASE(X) CASE_F_18(CASE, __VA_ARGS__) -#define CASE_F_20(CASE, X, ...) CASE(X) CASE_F_19(CASE, __VA_ARGS__) - -#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME -#define FOR_EACH_MAKE_CASE(...) \ - GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \ - CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \ - (CASE_F,__VA_ARGS__) - -#define IN_SET(x, ...) \ - ({ \ - bool _found = false; \ - /* If the build breaks in the line below, you need to extend the case macros */ \ - static _unused_ char _static_assert__macros_need_to_be_extended[20 - sizeof((int[]){__VA_ARGS__})/sizeof(int)]; \ - switch(x) { \ - FOR_EACH_MAKE_CASE(__VA_ARGS__) \ - _found = true; \ - break; \ - default: \ - break; \ - } \ - _found; \ - }) - -/* Define C11 thread_local attribute even on older gcc compiler - * version */ -#ifndef thread_local -/* - * Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__ - * see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769 - */ -#if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16)) -#define thread_local _Thread_local -#else -#define thread_local __thread -#endif -#endif - -/* Define C11 noreturn without and even on older gcc - * compiler versions */ -#ifndef noreturn -#if __STDC_VERSION__ >= 201112L -#define noreturn _Noreturn -#else -#define noreturn __attribute__((noreturn)) -#endif -#endif - -#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ - static inline void func##p(type *p) { \ - if (*p) \ - func(*p); \ - } \ - struct __useless_struct_to_allow_trailing_semicolon__ - -#include "log.h" diff --git a/src/basic/memfd-util.c b/src/basic/memfd-util.c deleted file mode 100644 index 8c8cc78ebf..0000000000 --- a/src/basic/memfd-util.c +++ /dev/null @@ -1,174 +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 . -***/ - -#include -#include -#include -#include -#ifdef HAVE_LINUX_MEMFD_H -#include -#endif -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "macro.h" -#include "memfd-util.h" -#include "missing.h" -#include "string-util.h" -#include "utf8.h" - -int memfd_new(const char *name) { - _cleanup_free_ char *g = NULL; - int fd; - - if (!name) { - char pr[17] = {}; - - /* If no name is specified we generate one. We include - * a hint indicating our library implementation, and - * add the thread name to it */ - - assert_se(prctl(PR_GET_NAME, (unsigned long) pr) >= 0); - - if (isempty(pr)) - name = "sd"; - else { - _cleanup_free_ char *e = NULL; - - e = utf8_escape_invalid(pr); - if (!e) - return -ENOMEM; - - g = strappend("sd-", e); - if (!g) - return -ENOMEM; - - name = g; - } - } - - fd = memfd_create(name, MFD_ALLOW_SEALING | MFD_CLOEXEC); - if (fd < 0) - return -errno; - - return fd; -} - -int memfd_map(int fd, uint64_t offset, size_t size, void **p) { - void *q; - int sealed; - - assert(fd >= 0); - assert(size > 0); - assert(p); - - sealed = memfd_get_sealed(fd); - if (sealed < 0) - return sealed; - - if (sealed) - q = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, offset); - else - q = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset); - - if (q == MAP_FAILED) - return -errno; - - *p = q; - return 0; -} - -int memfd_set_sealed(int fd) { - int r; - - assert(fd >= 0); - - r = fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL); - if (r < 0) - return -errno; - - return 0; -} - -int memfd_get_sealed(int fd) { - int r; - - assert(fd >= 0); - - r = fcntl(fd, F_GET_SEALS); - if (r < 0) - return -errno; - - return r == (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL); -} - -int memfd_get_size(int fd, uint64_t *sz) { - struct stat stat; - int r; - - assert(fd >= 0); - assert(sz); - - r = fstat(fd, &stat); - if (r < 0) - return -errno; - - *sz = stat.st_size; - return 0; -} - -int memfd_set_size(int fd, uint64_t sz) { - int r; - - assert(fd >= 0); - - r = ftruncate(fd, sz); - if (r < 0) - return -errno; - - return 0; -} - -int memfd_new_and_map(const char *name, size_t sz, void **p) { - _cleanup_close_ int fd = -1; - int r; - - assert(sz > 0); - assert(p); - - fd = memfd_new(name); - if (fd < 0) - return fd; - - r = memfd_set_size(fd, sz); - if (r < 0) - return r; - - r = memfd_map(fd, 0, sz, p); - if (r < 0) - return r; - - r = fd; - fd = -1; - - return r; -} diff --git a/src/basic/memfd-util.h b/src/basic/memfd-util.h deleted file mode 100644 index 46d4989e4c..0000000000 --- a/src/basic/memfd-util.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include -#include -#include -#include - -int memfd_new(const char *name); -int memfd_new_and_map(const char *name, size_t sz, void **p); - -int memfd_map(int fd, uint64_t offset, size_t size, void **p); - -int memfd_set_sealed(int fd); -int memfd_get_sealed(int fd); - -int memfd_get_size(int fd, uint64_t *sz); -int memfd_set_size(int fd, uint64_t sz); diff --git a/src/basic/mempool.c b/src/basic/mempool.c deleted file mode 100644 index f95e2beb0f..0000000000 --- a/src/basic/mempool.c +++ /dev/null @@ -1,104 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2014 Lennart Poettering - Copyright 2014 Michal Schmidt - - 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 . -***/ - -#include -#include - -#include "macro.h" -#include "mempool.h" -#include "util.h" - -struct pool { - struct pool *next; - unsigned n_tiles; - unsigned n_used; -}; - -void* mempool_alloc_tile(struct mempool *mp) { - unsigned i; - - /* When a tile is released we add it to the list and simply - * place the next pointer at its offset 0. */ - - assert(mp->tile_size >= sizeof(void*)); - assert(mp->at_least > 0); - - if (mp->freelist) { - void *r; - - r = mp->freelist; - mp->freelist = * (void**) mp->freelist; - return r; - } - - if (_unlikely_(!mp->first_pool) || - _unlikely_(mp->first_pool->n_used >= mp->first_pool->n_tiles)) { - unsigned n; - size_t size; - struct pool *p; - - n = mp->first_pool ? mp->first_pool->n_tiles : 0; - n = MAX(mp->at_least, n * 2); - size = PAGE_ALIGN(ALIGN(sizeof(struct pool)) + n*mp->tile_size); - n = (size - ALIGN(sizeof(struct pool))) / mp->tile_size; - - p = malloc(size); - if (!p) - return NULL; - - p->next = mp->first_pool; - p->n_tiles = n; - p->n_used = 0; - - mp->first_pool = p; - } - - i = mp->first_pool->n_used++; - - return ((uint8_t*) mp->first_pool) + ALIGN(sizeof(struct pool)) + i*mp->tile_size; -} - -void* mempool_alloc0_tile(struct mempool *mp) { - void *p; - - p = mempool_alloc_tile(mp); - if (p) - memzero(p, mp->tile_size); - return p; -} - -void mempool_free_tile(struct mempool *mp, void *p) { - * (void**) p = mp->freelist; - mp->freelist = p; -} - -#ifdef VALGRIND - -void mempool_drop(struct mempool *mp) { - struct pool *p = mp->first_pool; - while (p) { - struct pool *n; - n = p->next; - free(p); - p = n; - } -} - -#endif diff --git a/src/basic/mempool.h b/src/basic/mempool.h deleted file mode 100644 index fea7841bcf..0000000000 --- a/src/basic/mempool.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011-2014 Lennart Poettering - Copyright 2014 Michal Schmidt - - 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 . -***/ - -#include - -struct pool; - -struct mempool { - struct pool *first_pool; - void *freelist; - size_t tile_size; - unsigned at_least; -}; - -void* mempool_alloc_tile(struct mempool *mp); -void* mempool_alloc0_tile(struct mempool *mp); -void mempool_free_tile(struct mempool *mp, void *p); - -#define DEFINE_MEMPOOL(pool_name, tile_type, alloc_at_least) \ -struct mempool pool_name = { \ - .tile_size = sizeof(tile_type), \ - .at_least = alloc_at_least, \ -} - - -#ifdef VALGRIND -void mempool_drop(struct mempool *mp); -#endif diff --git a/src/basic/missing.h b/src/basic/missing.h deleted file mode 100644 index 36b060496a..0000000000 --- a/src/basic/missing.h +++ /dev/null @@ -1,1170 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -/* Missing glibc definitions to access certain kernel APIs */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_AUDIT -#include -#endif - -#ifdef ARCH_MIPS -#include -#endif - -#ifdef HAVE_LINUX_BTRFS_H -#include -#endif - -#include "macro.h" - -#ifndef RLIMIT_RTTIME -#define RLIMIT_RTTIME 15 -#endif - -/* If RLIMIT_RTTIME is not defined, then we cannot use RLIMIT_NLIMITS as is */ -#define _RLIMIT_MAX (RLIMIT_RTTIME+1 > RLIMIT_NLIMITS ? RLIMIT_RTTIME+1 : RLIMIT_NLIMITS) - -#ifndef F_LINUX_SPECIFIC_BASE -#define F_LINUX_SPECIFIC_BASE 1024 -#endif - -#ifndef F_SETPIPE_SZ -#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7) -#endif - -#ifndef F_GETPIPE_SZ -#define F_GETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 8) -#endif - -#ifndef F_ADD_SEALS -#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) -#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) - -#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ -#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ -#define F_SEAL_GROW 0x0004 /* prevent file from growing */ -#define F_SEAL_WRITE 0x0008 /* prevent writes */ -#endif - -#ifndef F_OFD_GETLK -#define F_OFD_GETLK 36 -#define F_OFD_SETLK 37 -#define F_OFD_SETLKW 38 -#endif - -#ifndef MFD_ALLOW_SEALING -#define MFD_ALLOW_SEALING 0x0002U -#endif - -#ifndef MFD_CLOEXEC -#define MFD_CLOEXEC 0x0001U -#endif - -#ifndef IP_FREEBIND -#define IP_FREEBIND 15 -#endif - -#ifndef OOM_SCORE_ADJ_MIN -#define OOM_SCORE_ADJ_MIN (-1000) -#endif - -#ifndef OOM_SCORE_ADJ_MAX -#define OOM_SCORE_ADJ_MAX 1000 -#endif - -#ifndef AUDIT_SERVICE_START -#define AUDIT_SERVICE_START 1130 /* Service (daemon) start */ -#endif - -#ifndef AUDIT_SERVICE_STOP -#define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */ -#endif - -#ifndef TIOCVHANGUP -#define TIOCVHANGUP 0x5437 -#endif - -#ifndef IP_TRANSPARENT -#define IP_TRANSPARENT 19 -#endif - -#ifndef SOL_NETLINK -#define SOL_NETLINK 270 -#endif - -#ifndef NETLINK_LIST_MEMBERSHIPS -#define NETLINK_LIST_MEMBERSHIPS 9 -#endif - -#ifndef SOL_SCTP -#define SOL_SCTP 132 -#endif - -#if !HAVE_DECL_PIVOT_ROOT -static inline int pivot_root(const char *new_root, const char *put_old) { - return syscall(SYS_pivot_root, new_root, put_old); -} -#endif - -#ifndef __NR_memfd_create -# if defined __x86_64__ -# define __NR_memfd_create 319 -# elif defined __arm__ -# define __NR_memfd_create 385 -# elif defined __aarch64__ -# define __NR_memfd_create 279 -# elif defined __s390__ -# define __NR_memfd_create 350 -# elif defined _MIPS_SIM -# if _MIPS_SIM == _MIPS_SIM_ABI32 -# define __NR_memfd_create 4354 -# endif -# if _MIPS_SIM == _MIPS_SIM_NABI32 -# define __NR_memfd_create 6318 -# endif -# if _MIPS_SIM == _MIPS_SIM_ABI64 -# define __NR_memfd_create 5314 -# endif -# elif defined __i386__ -# define __NR_memfd_create 356 -# else -# warning "__NR_memfd_create unknown for your architecture" -# define __NR_memfd_create 0xffffffff -# endif -#endif - -#ifndef HAVE_MEMFD_CREATE -static inline int memfd_create(const char *name, unsigned int flags) { - return syscall(__NR_memfd_create, name, flags); -} -#endif - -#ifndef __NR_getrandom -# if defined __x86_64__ -# define __NR_getrandom 318 -# elif defined(__i386__) -# define __NR_getrandom 355 -# elif defined(__arm__) -# define __NR_getrandom 384 -# elif defined(__aarch64__) -# define __NR_getrandom 278 -# elif defined(__ia64__) -# define __NR_getrandom 1339 -# elif defined(__m68k__) -# define __NR_getrandom 352 -# elif defined(__s390x__) -# define __NR_getrandom 349 -# elif defined(__powerpc__) -# define __NR_getrandom 359 -# elif defined _MIPS_SIM -# if _MIPS_SIM == _MIPS_SIM_ABI32 -# define __NR_getrandom 4353 -# endif -# if _MIPS_SIM == _MIPS_SIM_NABI32 -# define __NR_getrandom 6317 -# endif -# if _MIPS_SIM == _MIPS_SIM_ABI64 -# define __NR_getrandom 5313 -# endif -# else -# warning "__NR_getrandom unknown for your architecture" -# define __NR_getrandom 0xffffffff -# endif -#endif - -#if !HAVE_DECL_GETRANDOM -static inline int getrandom(void *buffer, size_t count, unsigned flags) { - return syscall(__NR_getrandom, buffer, count, flags); -} -#endif - -#ifndef GRND_NONBLOCK -#define GRND_NONBLOCK 0x0001 -#endif - -#ifndef GRND_RANDOM -#define GRND_RANDOM 0x0002 -#endif - -#ifndef BTRFS_IOCTL_MAGIC -#define BTRFS_IOCTL_MAGIC 0x94 -#endif - -#ifndef BTRFS_PATH_NAME_MAX -#define BTRFS_PATH_NAME_MAX 4087 -#endif - -#ifndef BTRFS_DEVICE_PATH_NAME_MAX -#define BTRFS_DEVICE_PATH_NAME_MAX 1024 -#endif - -#ifndef BTRFS_FSID_SIZE -#define BTRFS_FSID_SIZE 16 -#endif - -#ifndef BTRFS_UUID_SIZE -#define BTRFS_UUID_SIZE 16 -#endif - -#ifndef BTRFS_SUBVOL_RDONLY -#define BTRFS_SUBVOL_RDONLY (1ULL << 1) -#endif - -#ifndef BTRFS_SUBVOL_NAME_MAX -#define BTRFS_SUBVOL_NAME_MAX 4039 -#endif - -#ifndef BTRFS_INO_LOOKUP_PATH_MAX -#define BTRFS_INO_LOOKUP_PATH_MAX 4080 -#endif - -#ifndef BTRFS_SEARCH_ARGS_BUFSIZE -#define BTRFS_SEARCH_ARGS_BUFSIZE (4096 - sizeof(struct btrfs_ioctl_search_key)) -#endif - -#ifndef BTRFS_QGROUP_LEVEL_SHIFT -#define BTRFS_QGROUP_LEVEL_SHIFT 48 -#endif - -#ifndef HAVE_LINUX_BTRFS_H -struct btrfs_ioctl_vol_args { - int64_t fd; - char name[BTRFS_PATH_NAME_MAX + 1]; -}; - -struct btrfs_qgroup_limit { - __u64 flags; - __u64 max_rfer; - __u64 max_excl; - __u64 rsv_rfer; - __u64 rsv_excl; -}; - -struct btrfs_qgroup_inherit { - __u64 flags; - __u64 num_qgroups; - __u64 num_ref_copies; - __u64 num_excl_copies; - struct btrfs_qgroup_limit lim; - __u64 qgroups[0]; -}; - -struct btrfs_ioctl_qgroup_limit_args { - __u64 qgroupid; - struct btrfs_qgroup_limit lim; -}; - -struct btrfs_ioctl_vol_args_v2 { - __s64 fd; - __u64 transid; - __u64 flags; - union { - struct { - __u64 size; - struct btrfs_qgroup_inherit *qgroup_inherit; - }; - __u64 unused[4]; - }; - char name[BTRFS_SUBVOL_NAME_MAX + 1]; -}; - -struct btrfs_ioctl_dev_info_args { - uint64_t devid; /* in/out */ - uint8_t uuid[BTRFS_UUID_SIZE]; /* in/out */ - uint64_t bytes_used; /* out */ - uint64_t total_bytes; /* out */ - uint64_t unused[379]; /* pad to 4k */ - char path[BTRFS_DEVICE_PATH_NAME_MAX]; /* out */ -}; - -struct btrfs_ioctl_fs_info_args { - uint64_t max_id; /* out */ - uint64_t num_devices; /* out */ - uint8_t fsid[BTRFS_FSID_SIZE]; /* out */ - uint64_t reserved[124]; /* pad to 1k */ -}; - -struct btrfs_ioctl_ino_lookup_args { - __u64 treeid; - __u64 objectid; - char name[BTRFS_INO_LOOKUP_PATH_MAX]; -}; - -struct btrfs_ioctl_search_key { - /* which root are we searching. 0 is the tree of tree roots */ - __u64 tree_id; - - /* keys returned will be >= min and <= max */ - __u64 min_objectid; - __u64 max_objectid; - - /* keys returned will be >= min and <= max */ - __u64 min_offset; - __u64 max_offset; - - /* max and min transids to search for */ - __u64 min_transid; - __u64 max_transid; - - /* keys returned will be >= min and <= max */ - __u32 min_type; - __u32 max_type; - - /* - * how many items did userland ask for, and how many are we - * returning - */ - __u32 nr_items; - - /* align to 64 bits */ - __u32 unused; - - /* some extra for later */ - __u64 unused1; - __u64 unused2; - __u64 unused3; - __u64 unused4; -}; - -struct btrfs_ioctl_search_header { - __u64 transid; - __u64 objectid; - __u64 offset; - __u32 type; - __u32 len; -}; - - -struct btrfs_ioctl_search_args { - struct btrfs_ioctl_search_key key; - char buf[BTRFS_SEARCH_ARGS_BUFSIZE]; -}; - -struct btrfs_ioctl_clone_range_args { - __s64 src_fd; - __u64 src_offset, src_length; - __u64 dest_offset; -}; - -#define BTRFS_QUOTA_CTL_ENABLE 1 -#define BTRFS_QUOTA_CTL_DISABLE 2 -#define BTRFS_QUOTA_CTL_RESCAN__NOTUSED 3 -struct btrfs_ioctl_quota_ctl_args { - __u64 cmd; - __u64 status; -}; -#endif - -#ifndef BTRFS_IOC_DEFRAG -#define BTRFS_IOC_DEFRAG _IOW(BTRFS_IOCTL_MAGIC, 2, \ - struct btrfs_ioctl_vol_args) -#endif - -#ifndef BTRFS_IOC_RESIZE -#define BTRFS_IOC_RESIZE _IOW(BTRFS_IOCTL_MAGIC, 3, \ - struct btrfs_ioctl_vol_args) -#endif - -#ifndef BTRFS_IOC_CLONE -#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int) -#endif - -#ifndef BTRFS_IOC_CLONE_RANGE -#define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \ - struct btrfs_ioctl_clone_range_args) -#endif - -#ifndef BTRFS_IOC_SUBVOL_CREATE -#define BTRFS_IOC_SUBVOL_CREATE _IOW(BTRFS_IOCTL_MAGIC, 14, \ - struct btrfs_ioctl_vol_args) -#endif - -#ifndef BTRFS_IOC_SNAP_DESTROY -#define BTRFS_IOC_SNAP_DESTROY _IOW(BTRFS_IOCTL_MAGIC, 15, \ - struct btrfs_ioctl_vol_args) -#endif - -#ifndef BTRFS_IOC_TREE_SEARCH -#define BTRFS_IOC_TREE_SEARCH _IOWR(BTRFS_IOCTL_MAGIC, 17, \ - struct btrfs_ioctl_search_args) -#endif - -#ifndef BTRFS_IOC_INO_LOOKUP -#define BTRFS_IOC_INO_LOOKUP _IOWR(BTRFS_IOCTL_MAGIC, 18, \ - struct btrfs_ioctl_ino_lookup_args) -#endif - -#ifndef BTRFS_IOC_SNAP_CREATE_V2 -#define BTRFS_IOC_SNAP_CREATE_V2 _IOW(BTRFS_IOCTL_MAGIC, 23, \ - struct btrfs_ioctl_vol_args_v2) -#endif - -#ifndef BTRFS_IOC_SUBVOL_GETFLAGS -#define BTRFS_IOC_SUBVOL_GETFLAGS _IOR(BTRFS_IOCTL_MAGIC, 25, __u64) -#endif - -#ifndef BTRFS_IOC_SUBVOL_SETFLAGS -#define BTRFS_IOC_SUBVOL_SETFLAGS _IOW(BTRFS_IOCTL_MAGIC, 26, __u64) -#endif - -#ifndef BTRFS_IOC_DEV_INFO -#define BTRFS_IOC_DEV_INFO _IOWR(BTRFS_IOCTL_MAGIC, 30, \ - struct btrfs_ioctl_dev_info_args) -#endif - -#ifndef BTRFS_IOC_FS_INFO -#define BTRFS_IOC_FS_INFO _IOR(BTRFS_IOCTL_MAGIC, 31, \ - struct btrfs_ioctl_fs_info_args) -#endif - -#ifndef BTRFS_IOC_DEVICES_READY -#define BTRFS_IOC_DEVICES_READY _IOR(BTRFS_IOCTL_MAGIC, 39, \ - struct btrfs_ioctl_vol_args) -#endif - -#ifndef BTRFS_IOC_QUOTA_CTL -#define BTRFS_IOC_QUOTA_CTL _IOWR(BTRFS_IOCTL_MAGIC, 40, \ - struct btrfs_ioctl_quota_ctl_args) -#endif - -#ifndef BTRFS_IOC_QGROUP_LIMIT -#define BTRFS_IOC_QGROUP_LIMIT _IOR(BTRFS_IOCTL_MAGIC, 43, \ - struct btrfs_ioctl_qgroup_limit_args) -#endif - -#ifndef BTRFS_FIRST_FREE_OBJECTID -#define BTRFS_FIRST_FREE_OBJECTID 256 -#endif - -#ifndef BTRFS_LAST_FREE_OBJECTID -#define BTRFS_LAST_FREE_OBJECTID -256ULL -#endif - -#ifndef BTRFS_ROOT_TREE_OBJECTID -#define BTRFS_ROOT_TREE_OBJECTID 1 -#endif - -#ifndef BTRFS_QUOTA_TREE_OBJECTID -#define BTRFS_QUOTA_TREE_OBJECTID 8ULL -#endif - -#ifndef BTRFS_ROOT_ITEM_KEY -#define BTRFS_ROOT_ITEM_KEY 132 -#endif - -#ifndef BTRFS_QGROUP_STATUS_KEY -#define BTRFS_QGROUP_STATUS_KEY 240 -#endif - -#ifndef BTRFS_QGROUP_INFO_KEY -#define BTRFS_QGROUP_INFO_KEY 242 -#endif - -#ifndef BTRFS_QGROUP_LIMIT_KEY -#define BTRFS_QGROUP_LIMIT_KEY 244 -#endif - -#ifndef BTRFS_QGROUP_RELATION_KEY -#define BTRFS_QGROUP_RELATION_KEY 246 -#endif - -#ifndef BTRFS_ROOT_BACKREF_KEY -#define BTRFS_ROOT_BACKREF_KEY 144 -#endif - -#ifndef BTRFS_SUPER_MAGIC -#define BTRFS_SUPER_MAGIC 0x9123683E -#endif - -#ifndef CGROUP_SUPER_MAGIC -#define CGROUP_SUPER_MAGIC 0x27e0eb -#endif - -#ifndef TMPFS_MAGIC -#define TMPFS_MAGIC 0x01021994 -#endif - -#ifndef MS_MOVE -#define MS_MOVE 8192 -#endif - -#ifndef MS_PRIVATE -#define MS_PRIVATE (1 << 18) -#endif - -#if !HAVE_DECL_GETTID -static inline pid_t gettid(void) { - return (pid_t) syscall(SYS_gettid); -} -#endif - -#ifndef SCM_SECURITY -#define SCM_SECURITY 0x03 -#endif - -#ifndef MS_STRICTATIME -#define MS_STRICTATIME (1<<24) -#endif - -#ifndef MS_REC -#define MS_REC 16384 -#endif - -#ifndef MS_SHARED -#define MS_SHARED (1<<20) -#endif - -#ifndef PR_SET_NO_NEW_PRIVS -#define PR_SET_NO_NEW_PRIVS 38 -#endif - -#ifndef PR_SET_CHILD_SUBREAPER -#define PR_SET_CHILD_SUBREAPER 36 -#endif - -#ifndef MAX_HANDLE_SZ -#define MAX_HANDLE_SZ 128 -#endif - -#ifndef __NR_name_to_handle_at -# if defined(__x86_64__) -# define __NR_name_to_handle_at 303 -# elif defined(__i386__) -# define __NR_name_to_handle_at 341 -# elif defined(__arm__) -# define __NR_name_to_handle_at 370 -# elif defined(__powerpc__) -# define __NR_name_to_handle_at 345 -# else -# error "__NR_name_to_handle_at is not defined" -# endif -#endif - -#if !HAVE_DECL_NAME_TO_HANDLE_AT -struct file_handle { - unsigned int handle_bytes; - int handle_type; - unsigned char f_handle[0]; -}; - -static inline int name_to_handle_at(int fd, const char *name, struct file_handle *handle, int *mnt_id, int flags) { - return syscall(__NR_name_to_handle_at, fd, name, handle, mnt_id, flags); -} -#endif - -#ifndef HAVE_SECURE_GETENV -# ifdef HAVE___SECURE_GETENV -# define secure_getenv __secure_getenv -# else -# error "neither secure_getenv nor __secure_getenv are available" -# endif -#endif - -#ifndef CIFS_MAGIC_NUMBER -# define CIFS_MAGIC_NUMBER 0xFF534D42 -#endif - -#ifndef TFD_TIMER_CANCEL_ON_SET -# define TFD_TIMER_CANCEL_ON_SET (1 << 1) -#endif - -#ifndef SO_REUSEPORT -# define SO_REUSEPORT 15 -#endif - -#ifndef EVIOCREVOKE -# define EVIOCREVOKE _IOW('E', 0x91, int) -#endif - -#ifndef DRM_IOCTL_SET_MASTER -# define DRM_IOCTL_SET_MASTER _IO('d', 0x1e) -#endif - -#ifndef DRM_IOCTL_DROP_MASTER -# define DRM_IOCTL_DROP_MASTER _IO('d', 0x1f) -#endif - -#if defined(__i386__) || defined(__x86_64__) - -/* The precise definition of __O_TMPFILE is arch specific, so let's - * just define this on x86 where we know the value. */ - -#ifndef __O_TMPFILE -#define __O_TMPFILE 020000000 -#endif - -/* a horrid kludge trying to make sure that this will fail on old kernels */ -#ifndef O_TMPFILE -#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) -#endif - -#endif - -#ifndef __NR_setns -# if defined(__x86_64__) -# define __NR_setns 308 -# elif defined(__i386__) -# define __NR_setns 346 -# else -# error "__NR_setns is not defined" -# endif -#endif - -#if !HAVE_DECL_SETNS -static inline int setns(int fd, int nstype) { - return syscall(__NR_setns, fd, nstype); -} -#endif - -#if !HAVE_DECL_LO_FLAGS_PARTSCAN -#define LO_FLAGS_PARTSCAN 8 -#endif - -#ifndef LOOP_CTL_REMOVE -#define LOOP_CTL_REMOVE 0x4C81 -#endif - -#ifndef LOOP_CTL_GET_FREE -#define LOOP_CTL_GET_FREE 0x4C82 -#endif - -#if !HAVE_DECL_IFLA_INET6_ADDR_GEN_MODE -#define IFLA_INET6_UNSPEC 0 -#define IFLA_INET6_FLAGS 1 -#define IFLA_INET6_CONF 2 -#define IFLA_INET6_STATS 3 -#define IFLA_INET6_MCAST 4 -#define IFLA_INET6_CACHEINFO 5 -#define IFLA_INET6_ICMP6STATS 6 -#define IFLA_INET6_TOKEN 7 -#define IFLA_INET6_ADDR_GEN_MODE 8 -#define __IFLA_INET6_MAX 9 - -#define IFLA_INET6_MAX (__IFLA_INET6_MAX - 1) - -#define IN6_ADDR_GEN_MODE_EUI64 0 -#define IN6_ADDR_GEN_MODE_NONE 1 -#endif - -#if !HAVE_DECL_IFLA_MACVLAN_FLAGS -#define IFLA_MACVLAN_UNSPEC 0 -#define IFLA_MACVLAN_MODE 1 -#define IFLA_MACVLAN_FLAGS 2 -#define __IFLA_MACVLAN_MAX 3 - -#define IFLA_MACVLAN_MAX (__IFLA_MACVLAN_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_IPVLAN_MODE -#define IFLA_IPVLAN_UNSPEC 0 -#define IFLA_IPVLAN_MODE 1 -#define __IFLA_IPVLAN_MAX 2 - -#define IFLA_IPVLAN_MAX (__IFLA_IPVLAN_MAX - 1) - -#define IPVLAN_MODE_L2 0 -#define IPVLAN_MODE_L3 1 -#define IPVLAN_MAX 2 -#endif - -#if !HAVE_DECL_IFLA_VTI_REMOTE -#define IFLA_VTI_UNSPEC 0 -#define IFLA_VTI_LINK 1 -#define IFLA_VTI_IKEY 2 -#define IFLA_VTI_OKEY 3 -#define IFLA_VTI_LOCAL 4 -#define IFLA_VTI_REMOTE 5 -#define __IFLA_VTI_MAX 6 - -#define IFLA_VTI_MAX (__IFLA_VTI_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_PHYS_PORT_ID -#undef IFLA_PROMISCUITY -#define IFLA_PROMISCUITY 30 -#define IFLA_NUM_TX_QUEUES 31 -#define IFLA_NUM_RX_QUEUES 32 -#define IFLA_CARRIER 33 -#define IFLA_PHYS_PORT_ID 34 -#define __IFLA_MAX 35 - -#define IFLA_MAX (__IFLA_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_BOND_AD_INFO -#define IFLA_BOND_UNSPEC 0 -#define IFLA_BOND_MODE 1 -#define IFLA_BOND_ACTIVE_SLAVE 2 -#define IFLA_BOND_MIIMON 3 -#define IFLA_BOND_UPDELAY 4 -#define IFLA_BOND_DOWNDELAY 5 -#define IFLA_BOND_USE_CARRIER 6 -#define IFLA_BOND_ARP_INTERVAL 7 -#define IFLA_BOND_ARP_IP_TARGET 8 -#define IFLA_BOND_ARP_VALIDATE 9 -#define IFLA_BOND_ARP_ALL_TARGETS 10 -#define IFLA_BOND_PRIMARY 11 -#define IFLA_BOND_PRIMARY_RESELECT 12 -#define IFLA_BOND_FAIL_OVER_MAC 13 -#define IFLA_BOND_XMIT_HASH_POLICY 14 -#define IFLA_BOND_RESEND_IGMP 15 -#define IFLA_BOND_NUM_PEER_NOTIF 16 -#define IFLA_BOND_ALL_SLAVES_ACTIVE 17 -#define IFLA_BOND_MIN_LINKS 18 -#define IFLA_BOND_LP_INTERVAL 19 -#define IFLA_BOND_PACKETS_PER_SLAVE 20 -#define IFLA_BOND_AD_LACP_RATE 21 -#define IFLA_BOND_AD_SELECT 22 -#define IFLA_BOND_AD_INFO 23 -#define __IFLA_BOND_MAX 24 - -#define IFLA_BOND_MAX (__IFLA_BOND_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_VLAN_PROTOCOL -#define IFLA_VLAN_UNSPEC 0 -#define IFLA_VLAN_ID 1 -#define IFLA_VLAN_FLAGS 2 -#define IFLA_VLAN_EGRESS_QOS 3 -#define IFLA_VLAN_INGRESS_QOS 4 -#define IFLA_VLAN_PROTOCOL 5 -#define __IFLA_VLAN_MAX 6 - -#define IFLA_VLAN_MAX (__IFLA_VLAN_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_VXLAN_REMCSUM_NOPARTIAL -#define IFLA_VXLAN_UNSPEC 0 -#define IFLA_VXLAN_ID 1 -#define IFLA_VXLAN_GROUP 2 -#define IFLA_VXLAN_LINK 3 -#define IFLA_VXLAN_LOCAL 4 -#define IFLA_VXLAN_TTL 5 -#define IFLA_VXLAN_TOS 6 -#define IFLA_VXLAN_LEARNING 7 -#define IFLA_VXLAN_AGEING 8 -#define IFLA_VXLAN_LIMIT 9 -#define IFLA_VXLAN_PORT_RANGE 10 -#define IFLA_VXLAN_PROXY 11 -#define IFLA_VXLAN_RSC 12 -#define IFLA_VXLAN_L2MISS 13 -#define IFLA_VXLAN_L3MISS 14 -#define IFLA_VXLAN_PORT 15 -#define IFLA_VXLAN_GROUP6 16 -#define IFLA_VXLAN_LOCAL6 17 -#define IFLA_VXLAN_UDP_CSUM 18 -#define IFLA_VXLAN_UDP_ZERO_CSUM6_TX 19 -#define IFLA_VXLAN_UDP_ZERO_CSUM6_RX 20 -#define IFLA_VXLAN_REMCSUM_TX 21 -#define IFLA_VXLAN_REMCSUM_RX 22 -#define IFLA_VXLAN_GBP 23 -#define IFLA_VXLAN_REMCSUM_NOPARTIAL 24 -#define __IFLA_VXLAN_MAX 25 - -#define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_IPTUN_ENCAP_DPORT -#define IFLA_IPTUN_UNSPEC 0 -#define IFLA_IPTUN_LINK 1 -#define IFLA_IPTUN_LOCAL 2 -#define IFLA_IPTUN_REMOTE 3 -#define IFLA_IPTUN_TTL 4 -#define IFLA_IPTUN_TOS 5 -#define IFLA_IPTUN_ENCAP_LIMIT 6 -#define IFLA_IPTUN_FLOWINFO 7 -#define IFLA_IPTUN_FLAGS 8 -#define IFLA_IPTUN_PROTO 9 -#define IFLA_IPTUN_PMTUDISC 10 -#define IFLA_IPTUN_6RD_PREFIX 11 -#define IFLA_IPTUN_6RD_RELAY_PREFIX 12 -#define IFLA_IPTUN_6RD_PREFIXLEN 13 -#define IFLA_IPTUN_6RD_RELAY_PREFIXLEN 14 -#define IFLA_IPTUN_ENCAP_TYPE 15 -#define IFLA_IPTUN_ENCAP_FLAGS 16 -#define IFLA_IPTUN_ENCAP_SPORT 17 -#define IFLA_IPTUN_ENCAP_DPORT 18 - -#define __IFLA_IPTUN_MAX 19 - -#define IFLA_IPTUN_MAX (__IFLA_IPTUN_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_GRE_ENCAP_DPORT -#define IFLA_GRE_UNSPEC 0 -#define IFLA_GRE_LINK 1 -#define IFLA_GRE_IFLAGS 2 -#define IFLA_GRE_OFLAGS 3 -#define IFLA_GRE_IKEY 4 -#define IFLA_GRE_OKEY 5 -#define IFLA_GRE_LOCAL 6 -#define IFLA_GRE_REMOTE 7 -#define IFLA_GRE_TTL 8 -#define IFLA_GRE_TOS 9 -#define IFLA_GRE_PMTUDISC 10 -#define IFLA_GRE_ENCAP_LIMIT 11 -#define IFLA_GRE_FLOWINFO 12 -#define IFLA_GRE_FLAGS 13 -#define IFLA_GRE_ENCAP_TYPE 14 -#define IFLA_GRE_ENCAP_FLAGS 15 -#define IFLA_GRE_ENCAP_SPORT 16 -#define IFLA_GRE_ENCAP_DPORT 17 - -#define __IFLA_GRE_MAX 18 - -#define IFLA_GRE_MAX (__IFLA_GRE_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_BRIDGE_VLAN_INFO -#define IFLA_BRIDGE_FLAGS 0 -#define IFLA_BRIDGE_MODE 1 -#define IFLA_BRIDGE_VLAN_INFO 2 -#define __IFLA_BRIDGE_MAX 3 - -#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_BR_PRIORITY -#define IFLA_BR_UNSPEC 0 -#define IFLA_BR_FORWARD_DELAY 1 -#define IFLA_BR_HELLO_TIME 2 -#define IFLA_BR_MAX_AGE 3 -#define IFLA_BR_AGEING_TIME 4 -#define IFLA_BR_STP_STATE 5 -#define IFLA_BR_PRIORITY 6 -#define __IFLA_BR_MAX 7 - -#define IFLA_BR_MAX (__IFLA_BR_MAX - 1) -#endif - -#if !HAVE_DECL_IFLA_BRPORT_LEARNING_SYNC -#define IFLA_BRPORT_UNSPEC 0 -#define IFLA_BRPORT_STATE 1 -#define IFLA_BRPORT_PRIORITY 2 -#define IFLA_BRPORT_COST 3 -#define IFLA_BRPORT_MODE 4 -#define IFLA_BRPORT_GUARD 5 -#define IFLA_BRPORT_PROTECT 6 -#define IFLA_BRPORT_FAST_LEAVE 7 -#define IFLA_BRPORT_LEARNING 8 -#define IFLA_BRPORT_UNICAST_FLOOD 9 -#define IFLA_BRPORT_PROXYARP 10 -#define IFLA_BRPORT_LEARNING_SYNC 11 -#define __IFLA_BRPORT_MAX 12 - -#define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) -#endif - -#if !HAVE_DECL_NDA_IFINDEX -#define NDA_UNSPEC 0 -#define NDA_DST 1 -#define NDA_LLADDR 2 -#define NDA_CACHEINFO 3 -#define NDA_PROBES 4 -#define NDA_VLAN 5 -#define NDA_PORT 6 -#define NDA_VNI 7 -#define NDA_IFINDEX 8 -#define __NDA_MAX 9 - -#define NDA_MAX (__NDA_MAX - 1) -#endif - -#ifndef RTA_PREF -#define RTA_PREF 20 -#endif - -#ifndef IPV6_UNICAST_IF -#define IPV6_UNICAST_IF 76 -#endif - -#ifndef IFF_MULTI_QUEUE -#define IFF_MULTI_QUEUE 0x100 -#endif - -#ifndef IFF_LOWER_UP -#define IFF_LOWER_UP 0x10000 -#endif - -#ifndef IFF_DORMANT -#define IFF_DORMANT 0x20000 -#endif - -#ifndef BOND_XMIT_POLICY_ENCAP23 -#define BOND_XMIT_POLICY_ENCAP23 3 -#endif - -#ifndef BOND_XMIT_POLICY_ENCAP34 -#define BOND_XMIT_POLICY_ENCAP34 4 -#endif - -#ifndef NET_ADDR_RANDOM -# define NET_ADDR_RANDOM 1 -#endif - -#ifndef NET_NAME_UNKNOWN -# define NET_NAME_UNKNOWN 0 -#endif - -#ifndef NET_NAME_ENUM -# define NET_NAME_ENUM 1 -#endif - -#ifndef NET_NAME_PREDICTABLE -# define NET_NAME_PREDICTABLE 2 -#endif - -#ifndef NET_NAME_USER -# define NET_NAME_USER 3 -#endif - -#ifndef NET_NAME_RENAMED -# define NET_NAME_RENAMED 4 -#endif - -#ifndef BPF_XOR -# define BPF_XOR 0xa0 -#endif - -/* Note that LOOPBACK_IFINDEX is currently not exported by the - * kernel/glibc, but hardcoded internally by the kernel. However, as - * it is exported to userspace indirectly via rtnetlink and the - * ioctls, and made use of widely we define it here too, in a way that - * is compatible with the kernel's internal definition. */ -#ifndef LOOPBACK_IFINDEX -#define LOOPBACK_IFINDEX 1 -#endif - -#if !HAVE_DECL_IFA_FLAGS -#define IFA_FLAGS 8 -#endif - -#ifndef IFA_F_MANAGETEMPADDR -#define IFA_F_MANAGETEMPADDR 0x100 -#endif - -#ifndef IFA_F_NOPREFIXROUTE -#define IFA_F_NOPREFIXROUTE 0x200 -#endif - -#ifndef MAX_AUDIT_MESSAGE_LENGTH -#define MAX_AUDIT_MESSAGE_LENGTH 8970 -#endif - -#ifndef AUDIT_NLGRP_MAX -#define AUDIT_NLGRP_READLOG 1 -#endif - -#ifndef CAP_MAC_OVERRIDE -#define CAP_MAC_OVERRIDE 32 -#endif - -#ifndef CAP_MAC_ADMIN -#define CAP_MAC_ADMIN 33 -#endif - -#ifndef CAP_SYSLOG -#define CAP_SYSLOG 34 -#endif - -#ifndef CAP_WAKE_ALARM -#define CAP_WAKE_ALARM 35 -#endif - -#ifndef CAP_BLOCK_SUSPEND -#define CAP_BLOCK_SUSPEND 36 -#endif - -#ifndef CAP_AUDIT_READ -#define CAP_AUDIT_READ 37 -#endif - -static inline int raw_clone(unsigned long flags, void *child_stack) { -#if defined(__s390__) || defined(__CRIS__) - /* On s390 and cris the order of the first and second arguments - * of the raw clone() system call is reversed. */ - return (int) syscall(__NR_clone, child_stack, flags); -#else - return (int) syscall(__NR_clone, flags, child_stack); -#endif -} - -static inline pid_t raw_getpid(void) { -#if defined(__alpha__) - return (pid_t) syscall(__NR_getxpid); -#else - return (pid_t) syscall(__NR_getpid); -#endif -} - -#if !HAVE_DECL_RENAMEAT2 - -#ifndef __NR_renameat2 -# if defined __x86_64__ -# define __NR_renameat2 316 -# elif defined __arm__ -# define __NR_renameat2 382 -# elif defined _MIPS_SIM -# if _MIPS_SIM == _MIPS_SIM_ABI32 -# define __NR_renameat2 4351 -# endif -# if _MIPS_SIM == _MIPS_SIM_NABI32 -# define __NR_renameat2 6315 -# endif -# if _MIPS_SIM == _MIPS_SIM_ABI64 -# define __NR_renameat2 5311 -# endif -# elif defined __i386__ -# define __NR_renameat2 353 -# else -# warning "__NR_renameat2 unknown for your architecture" -# define __NR_renameat2 0xffffffff -# endif -#endif - -static inline int renameat2(int oldfd, const char *oldname, int newfd, const char *newname, unsigned flags) { - return syscall(__NR_renameat2, oldfd, oldname, newfd, newname, flags); -} -#endif - -#ifndef RENAME_NOREPLACE -#define RENAME_NOREPLACE (1 << 0) -#endif - -#if !HAVE_DECL_KCMP -static inline int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) { -#if defined(__NR_kcmp) - return syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2); -#else - errno = ENOSYS; - return -1; -#endif -} -#endif - -#ifndef KCMP_FILE -#define KCMP_FILE 0 -#endif - -#ifndef INPUT_PROP_POINTING_STICK -#define INPUT_PROP_POINTING_STICK 0x05 -#endif - -#ifndef INPUT_PROP_ACCELEROMETER -#define INPUT_PROP_ACCELEROMETER 0x06 -#endif - -#if !HAVE_DECL_KEY_SERIAL_T -typedef int32_t key_serial_t; -#endif - -#if !HAVE_DECL_KEYCTL -static inline long keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4,unsigned long arg5) { -#if defined(__NR_keyctl) - return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); -#else - errno = ENOSYS; - return -1; -#endif -} - -static inline key_serial_t add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) { -#if defined (__NR_add_key) - return syscall(__NR_add_key, type, description, payload, plen, ringid); -#else - errno = ENOSYS; - return -1; -#endif -} - -static inline key_serial_t request_key(const char *type, const char *description, const char * callout_info, key_serial_t destringid) { -#if defined (__NR_request_key) - return syscall(__NR_request_key, type, description, callout_info, destringid); -#else - errno = ENOSYS; - return -1; -#endif -} -#endif - -#ifndef KEYCTL_READ -#define KEYCTL_READ 11 -#endif - -#ifndef KEYCTL_SET_TIMEOUT -#define KEYCTL_SET_TIMEOUT 15 -#endif - -#ifndef KEY_SPEC_USER_KEYRING -#define KEY_SPEC_USER_KEYRING -4 -#endif - -#ifndef PR_CAP_AMBIENT -#define PR_CAP_AMBIENT 47 -#endif - -#ifndef PR_CAP_AMBIENT_IS_SET -#define PR_CAP_AMBIENT_IS_SET 1 -#endif - -#ifndef PR_CAP_AMBIENT_RAISE -#define PR_CAP_AMBIENT_RAISE 2 -#endif - -#ifndef PR_CAP_AMBIENT_CLEAR_ALL -#define PR_CAP_AMBIENT_CLEAR_ALL 4 -#endif - -/* The following two defines are actually available in the kernel headers for longer, but we define them here anyway, - * since that makes it easier to use them in conjunction with the glibc net/if.h header which conflicts with - * linux/if.h. */ -#ifndef IF_OPER_UNKNOWN -#define IF_OPER_UNKNOWN 0 -#endif - -#ifndef IF_OPER_UP -#define IF_OPER_UP 6 - -#ifndef HAVE_DECL_CHAR32_T -#define char32_t uint32_t -#endif - -#ifndef HAVE_DECL_CHAR16_T -#define char16_t uint16_t -#endif - -#endif diff --git a/src/basic/mkdir-label.c b/src/basic/mkdir-label.c deleted file mode 100644 index aa6878cdf0..0000000000 --- a/src/basic/mkdir-label.c +++ /dev/null @@ -1,38 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#include "label.h" -#include "mkdir.h" - -int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid) { - return mkdir_safe_internal(path, mode, uid, gid, mkdir_label); -} - -int mkdir_parents_label(const char *path, mode_t mode) { - return mkdir_parents_internal(NULL, path, mode, mkdir_label); -} - -int mkdir_p_label(const char *path, mode_t mode) { - return mkdir_p_internal(NULL, path, mode, mkdir_label); -} diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c deleted file mode 100644 index 6b1a98402c..0000000000 --- a/src/basic/mkdir.c +++ /dev/null @@ -1,128 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "fs-util.h" -#include "macro.h" -#include "mkdir.h" -#include "path-util.h" -#include "stat-util.h" -#include "user-util.h" - -int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, mkdir_func_t _mkdir) { - struct stat st; - - if (_mkdir(path, mode) >= 0) - if (chmod_and_chown(path, mode, uid, gid) < 0) - return -errno; - - if (lstat(path, &st) < 0) - return -errno; - - if ((st.st_mode & 0007) > (mode & 0007) || - (st.st_mode & 0070) > (mode & 0070) || - (st.st_mode & 0700) > (mode & 0700) || - (uid != UID_INVALID && st.st_uid != uid) || - (gid != GID_INVALID && st.st_gid != gid) || - !S_ISDIR(st.st_mode)) - return -EEXIST; - - return 0; -} - -int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid) { - return mkdir_safe_internal(path, mode, uid, gid, mkdir); -} - -int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { - const char *p, *e; - int r; - - assert(path); - - if (prefix && !path_startswith(path, prefix)) - return -ENOTDIR; - - /* return immediately if directory exists */ - e = strrchr(path, '/'); - if (!e) - return -EINVAL; - - if (e == path) - return 0; - - p = strndupa(path, e - path); - r = is_dir(p, true); - if (r > 0) - return 0; - if (r == 0) - return -ENOTDIR; - - /* create every parent directory in the path, except the last component */ - p = path + strspn(path, "/"); - for (;;) { - char t[strlen(path) + 1]; - - e = p + strcspn(p, "/"); - p = e + strspn(e, "/"); - - /* Is this the last component? If so, then we're - * done */ - if (*p == 0) - return 0; - - memcpy(t, path, e - path); - t[e-path] = 0; - - if (prefix && path_startswith(prefix, t)) - continue; - - r = _mkdir(t, mode); - if (r < 0 && errno != EEXIST) - return -errno; - } -} - -int mkdir_parents(const char *path, mode_t mode) { - return mkdir_parents_internal(NULL, path, mode, mkdir); -} - -int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { - int r; - - /* Like mkdir -p */ - - r = mkdir_parents_internal(prefix, path, mode, _mkdir); - if (r < 0) - return r; - - r = _mkdir(path, mode); - if (r < 0 && (errno != EEXIST || is_dir(path, true) <= 0)) - return -errno; - - return 0; -} - -int mkdir_p(const char *path, mode_t mode) { - return mkdir_p_internal(NULL, path, mode, mkdir); -} diff --git a/src/basic/mkdir.h b/src/basic/mkdir.h deleted file mode 100644 index d564a3547f..0000000000 --- a/src/basic/mkdir.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid); -int mkdir_parents(const char *path, mode_t mode); -int mkdir_p(const char *path, mode_t mode); - -/* mandatory access control(MAC) versions */ -int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid); -int mkdir_parents_label(const char *path, mode_t mode); -int mkdir_p_label(const char *path, mode_t mode); - -/* internally used */ -typedef int (*mkdir_func_t)(const char *pathname, mode_t mode); -int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, mkdir_func_t _mkdir); -int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir); -int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir); diff --git a/src/basic/mount-util.c b/src/basic/mount-util.c deleted file mode 100644 index 33f2ee96d8..0000000000 --- a/src/basic/mount-util.c +++ /dev/null @@ -1,531 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "hashmap.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "set.h" -#include "stdio-util.h" -#include "string-util.h" - -static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id) { - char path[strlen("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; - _cleanup_free_ char *fdinfo = NULL; - _cleanup_close_ int subfd = -1; - char *p; - int r; - - if ((flags & AT_EMPTY_PATH) && isempty(filename)) - xsprintf(path, "/proc/self/fdinfo/%i", fd); - else { - subfd = openat(fd, filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH); - if (subfd < 0) - return -errno; - - xsprintf(path, "/proc/self/fdinfo/%i", subfd); - } - - r = read_full_file(path, &fdinfo, NULL); - if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */ - return -EOPNOTSUPP; - if (r < 0) - return -errno; - - p = startswith(fdinfo, "mnt_id:"); - if (!p) { - p = strstr(fdinfo, "\nmnt_id:"); - if (!p) /* The mnt_id field is a relatively new addition */ - return -EOPNOTSUPP; - - p += 8; - } - - p += strspn(p, WHITESPACE); - p[strcspn(p, WHITESPACE)] = 0; - - return safe_atoi(p, mnt_id); -} - - -int fd_is_mount_point(int fd, const char *filename, int flags) { - union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT; - int mount_id = -1, mount_id_parent = -1; - bool nosupp = false, check_st_dev = true; - struct stat a, b; - int r; - - assert(fd >= 0); - assert(filename); - - /* First we will try the name_to_handle_at() syscall, which - * tells us the mount id and an opaque file "handle". It is - * not supported everywhere though (kernel compile-time - * option, not all file systems are hooked up). If it works - * the mount id is usually good enough to tell us whether - * something is a mount point. - * - * If that didn't work we will try to read the mount id from - * /proc/self/fdinfo/. This is almost as good as - * name_to_handle_at(), however, does not return the - * opaque file handle. The opaque file handle is pretty useful - * to detect the root directory, which we should always - * consider a mount point. Hence we use this only as - * fallback. Exporting the mnt_id in fdinfo is a pretty recent - * kernel addition. - * - * 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 - * 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. */ - - r = name_to_handle_at(fd, filename, &h.handle, &mount_id, flags); - if (r < 0) { - if (errno == ENOSYS) - /* This kernel does not support name_to_handle_at() - * fall back to simpler logic. */ - goto fallback_fdinfo; - else if (errno == EOPNOTSUPP) - /* This kernel or file system does not support - * name_to_handle_at(), hence let's see if the - * upper fs supports it (in which case it is a - * mount point), otherwise fallback to the - * traditional stat() logic */ - nosupp = true; - else - return -errno; - } - - r = name_to_handle_at(fd, "", &h_parent.handle, &mount_id_parent, AT_EMPTY_PATH); - if (r < 0) { - if (errno == EOPNOTSUPP) { - if (nosupp) - /* Neither parent nor child do name_to_handle_at()? - We have no choice but to fall back. */ - goto fallback_fdinfo; - else - /* The parent can't do name_to_handle_at() but the - * directory we are interested in can? - * If so, it must be a mount point. */ - return 1; - } else - return -errno; - } - - /* The parent can do name_to_handle_at() but the - * directory we are interested in can't? If so, it - * must be a mount point. */ - if (nosupp) - return 1; - - /* If the file handle for the directory we are - * interested in and its parent are identical, we - * assume this is the root directory, which is a mount - * point. */ - - if (h.handle.handle_bytes == h_parent.handle.handle_bytes && - h.handle.handle_type == h_parent.handle.handle_type && - memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0) - return 1; - - return mount_id != mount_id_parent; - -fallback_fdinfo: - r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id); - if (r == -EOPNOTSUPP) - goto fallback_fstat; - if (r < 0) - return r; - - r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent); - if (r < 0) - return r; - - if (mount_id != mount_id_parent) - return 1; - - /* Hmm, so, the mount ids are the same. This leaves one - * special case though for the root file system. For that, - * let's see if the parent directory has the same inode as we - * are interested in. Hence, let's also do fstat() checks now, - * too, but avoid the st_dev comparisons, since they aren't - * that useful on unionfs mounts. */ - check_st_dev = false; - -fallback_fstat: - /* yay for fstatat() taking a different set of flags than the other - * _at() above */ - if (flags & AT_SYMLINK_FOLLOW) - flags &= ~AT_SYMLINK_FOLLOW; - else - flags |= AT_SYMLINK_NOFOLLOW; - if (fstatat(fd, filename, &a, flags) < 0) - return -errno; - - if (fstatat(fd, "", &b, AT_EMPTY_PATH) < 0) - return -errno; - - /* A directory with same device and inode as its parent? Must - * be the root directory */ - if (a.st_dev == b.st_dev && - a.st_ino == b.st_ino) - return 1; - - return check_st_dev && (a.st_dev != b.st_dev); -} - -/* flags can be AT_SYMLINK_FOLLOW or 0 */ -int path_is_mount_point(const char *t, int flags) { - _cleanup_close_ int fd = -1; - _cleanup_free_ char *canonical = NULL, *parent = NULL; - - assert(t); - - if (path_equal(t, "/")) - return 1; - - /* we need to resolve symlinks manually, we can't just rely on - * fd_is_mount_point() to do that for us; if we have a structure like - * /bin -> /usr/bin/ and /usr is a mount point, then the parent that we - * look at needs to be /usr, not /. */ - if (flags & AT_SYMLINK_FOLLOW) { - canonical = canonicalize_file_name(t); - if (!canonical) - return -errno; - - t = canonical; - } - - parent = dirname_malloc(t); - if (!parent) - return -ENOMEM; - - fd = openat(AT_FDCWD, parent, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_PATH); - if (fd < 0) - return -errno; - - return fd_is_mount_point(fd, basename(t), flags); -} - -int umount_recursive(const char *prefix, int flags) { - bool again; - int n = 0, r; - - /* Try to umount everything recursively below a - * directory. Also, take care of stacked mounts, and keep - * unmounting them until they are gone. */ - - do { - _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; - - again = false; - r = 0; - - proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); - if (!proc_self_mountinfo) - return -errno; - - for (;;) { - _cleanup_free_ char *path = NULL, *p = NULL; - int k; - - k = fscanf(proc_self_mountinfo, - "%*s " /* (1) mount id */ - "%*s " /* (2) parent id */ - "%*s " /* (3) major:minor */ - "%*s " /* (4) root */ - "%ms " /* (5) mount point */ - "%*s" /* (6) mount options */ - "%*[^-]" /* (7) optional fields */ - "- " /* (8) separator */ - "%*s " /* (9) file system type */ - "%*s" /* (10) mount source */ - "%*s" /* (11) mount options 2 */ - "%*[^\n]", /* some rubbish at the end */ - &path); - if (k != 1) { - if (k == EOF) - break; - - continue; - } - - r = cunescape(path, UNESCAPE_RELAX, &p); - if (r < 0) - return r; - - if (!path_startswith(p, prefix)) - continue; - - if (umount2(p, flags) < 0) { - r = -errno; - continue; - } - - again = true; - n++; - - break; - } - - } while (again); - - return r ? r : n; -} - -static int get_mount_flags(const char *path, unsigned long *flags) { - struct statvfs buf; - - if (statvfs(path, &buf) < 0) - return -errno; - *flags = buf.f_flag; - return 0; -} - -int bind_remount_recursive(const char *prefix, bool ro) { - _cleanup_set_free_free_ Set *done = NULL; - _cleanup_free_ char *cleaned = NULL; - int r; - - /* Recursively remount a directory (and all its submounts) - * read-only or read-write. If the directory is already - * mounted, we reuse the mount and simply mark it - * MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write - * operation). If it isn't we first make it one. Afterwards we - * apply MS_BIND|MS_RDONLY (or remove MS_RDONLY) to all - * submounts we can access, too. When mounts are stacked on - * the same mount point we only care for each individual - * "top-level" mount on each point, as we cannot - * influence/access the underlying mounts anyway. We do not - * have any effect on future submounts that might get - * propagated, they migt be writable. This includes future - * submounts that have been triggered via autofs. */ - - cleaned = strdup(prefix); - if (!cleaned) - return -ENOMEM; - - path_kill_slashes(cleaned); - - done = set_new(&string_hash_ops); - if (!done) - return -ENOMEM; - - for (;;) { - _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; - _cleanup_set_free_free_ Set *todo = NULL; - bool top_autofs = false; - char *x; - unsigned long orig_flags; - - todo = set_new(&string_hash_ops); - if (!todo) - return -ENOMEM; - - proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); - if (!proc_self_mountinfo) - return -errno; - - for (;;) { - _cleanup_free_ char *path = NULL, *p = NULL, *type = NULL; - int k; - - k = fscanf(proc_self_mountinfo, - "%*s " /* (1) mount id */ - "%*s " /* (2) parent id */ - "%*s " /* (3) major:minor */ - "%*s " /* (4) root */ - "%ms " /* (5) mount point */ - "%*s" /* (6) mount options (superblock) */ - "%*[^-]" /* (7) optional fields */ - "- " /* (8) separator */ - "%ms " /* (9) file system type */ - "%*s" /* (10) mount source */ - "%*s" /* (11) mount options (bind mount) */ - "%*[^\n]", /* some rubbish at the end */ - &path, - &type); - if (k != 2) { - if (k == EOF) - break; - - continue; - } - - r = cunescape(path, UNESCAPE_RELAX, &p); - if (r < 0) - return r; - - /* Let's ignore autofs mounts. If they aren't - * triggered yet, we want to avoid triggering - * them, as we don't make any guarantees for - * future submounts anyway. If they are - * already triggered, then we will find - * another entry for this. */ - if (streq(type, "autofs")) { - top_autofs = top_autofs || path_equal(cleaned, p); - continue; - } - - if (path_startswith(p, cleaned) && - !set_contains(done, p)) { - - r = set_consume(todo, p); - p = NULL; - - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - /* If we have no submounts to process anymore and if - * the root is either already done, or an autofs, we - * are done */ - if (set_isempty(todo) && - (top_autofs || set_contains(done, cleaned))) - return 0; - - if (!set_contains(done, cleaned) && - !set_contains(todo, cleaned)) { - /* The prefix directory itself is not yet a - * mount, make it one. */ - if (mount(cleaned, cleaned, NULL, MS_BIND|MS_REC, NULL) < 0) - return -errno; - - orig_flags = 0; - (void) get_mount_flags(cleaned, &orig_flags); - orig_flags &= ~MS_RDONLY; - - if (mount(NULL, prefix, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) - return -errno; - - x = strdup(cleaned); - if (!x) - return -ENOMEM; - - r = set_consume(done, x); - if (r < 0) - return r; - } - - while ((x = set_steal_first(todo))) { - - r = set_consume(done, x); - if (r == -EEXIST || r == 0) - continue; - if (r < 0) - return r; - - /* Try to reuse the original flag set, but - * don't care for errors, in case of - * obstructed mounts */ - 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; - } - - } - } -} - -int mount_move_root(const char *path) { - assert(path); - - if (chdir(path) < 0) - return -errno; - - if (mount(path, "/", NULL, MS_MOVE, NULL) < 0) - return -errno; - - if (chroot(".") < 0) - return -errno; - - if (chdir("/") < 0) - return -errno; - - return 0; -} - -bool fstype_is_network(const char *fstype) { - static const char table[] = - "afs\0" - "cifs\0" - "smbfs\0" - "sshfs\0" - "ncpfs\0" - "ncp\0" - "nfs\0" - "nfs4\0" - "gfs\0" - "gfs2\0" - "glusterfs\0"; - - const char *x; - - x = startswith(fstype, "fuse."); - if (x) - fstype = x; - - return nulstr_contains(table, fstype); -} - -int repeat_unmount(const char *path, int flags) { - bool done = false; - - assert(path); - - /* If there are multiple mounts on a mount point, this - * removes them all */ - - for (;;) { - if (umount2(path, flags) < 0) { - - if (errno == EINVAL) - return done; - - return -errno; - } - - done = true; - } -} diff --git a/src/basic/mount-util.h b/src/basic/mount-util.h deleted file mode 100644 index bdb525d6b0..0000000000 --- a/src/basic/mount-util.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "macro.h" -#include "missing.h" - -int fd_is_mount_point(int fd, const char *filename, int flags); -int path_is_mount_point(const char *path, int flags); - -int repeat_unmount(const char *path, int flags); - -int umount_recursive(const char *target, int flags); -int bind_remount_recursive(const char *prefix, bool ro); - -int mount_move_root(const char *path); - -DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, endmntent); -#define _cleanup_endmntent_ _cleanup_(endmntentp) - -bool fstype_is_network(const char *fstype); - -union file_handle_union { - struct file_handle handle; - char padding[sizeof(struct file_handle) + MAX_HANDLE_SZ]; -}; - -#define FILE_HANDLE_INIT { .handle.handle_bytes = MAX_HANDLE_SZ } diff --git a/src/basic/nss-util.h b/src/basic/nss-util.h deleted file mode 100644 index df565a3593..0000000000 --- a/src/basic/nss-util.h +++ /dev/null @@ -1,156 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include -#include - -#define NSS_SIGNALS_BLOCK SIGALRM,SIGVTALRM,SIGPIPE,SIGCHLD,SIGTSTP,SIGIO,SIGHUP,SIGUSR1,SIGUSR2,SIGPROF,SIGURG,SIGWINCH - -#define NSS_GETHOSTBYNAME_PROTOTYPES(module) \ -enum nss_status _nss_##module##_gethostbyname4_r( \ - const char *name, \ - struct gaih_addrtuple **pat, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop, \ - int32_t *ttlp) _public_; \ -enum nss_status _nss_##module##_gethostbyname3_r( \ - const char *name, \ - int af, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop, \ - int32_t *ttlp, \ - char **canonp) _public_; \ -enum nss_status _nss_##module##_gethostbyname2_r( \ - const char *name, \ - int af, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop) _public_; \ -enum nss_status _nss_##module##_gethostbyname_r( \ - const char *name, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop) _public_ - -#define NSS_GETHOSTBYADDR_PROTOTYPES(module) \ -enum nss_status _nss_##module##_gethostbyaddr2_r( \ - const void* addr, socklen_t len, \ - int af, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop, \ - int32_t *ttlp) _public_; \ -enum nss_status _nss_##module##_gethostbyaddr_r( \ - const void* addr, socklen_t len, \ - int af, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop) _public_ - -#define NSS_GETHOSTBYNAME_FALLBACKS(module) \ -enum nss_status _nss_##module##_gethostbyname2_r( \ - const char *name, \ - int af, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop) { \ - return _nss_##module##_gethostbyname3_r( \ - name, \ - af, \ - host, \ - buffer, buflen, \ - errnop, h_errnop, \ - NULL, \ - NULL); \ -} \ -enum nss_status _nss_##module##_gethostbyname_r( \ - const char *name, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop) { \ - enum nss_status ret = NSS_STATUS_NOTFOUND; \ - \ - if (_res.options & RES_USE_INET6) \ - ret = _nss_##module##_gethostbyname3_r( \ - name, \ - AF_INET6, \ - host, \ - buffer, buflen, \ - errnop, h_errnop, \ - NULL, \ - NULL); \ - if (ret == NSS_STATUS_NOTFOUND) \ - ret = _nss_##module##_gethostbyname3_r( \ - name, \ - AF_INET, \ - host, \ - buffer, buflen, \ - errnop, h_errnop, \ - NULL, \ - NULL); \ - return ret; \ -} \ -struct __useless_struct_to_allow_trailing_semicolon__ - -#define NSS_GETHOSTBYADDR_FALLBACKS(module) \ -enum nss_status _nss_##module##_gethostbyaddr_r( \ - const void* addr, socklen_t len, \ - int af, \ - struct hostent *host, \ - char *buffer, size_t buflen, \ - int *errnop, int *h_errnop) { \ - return _nss_##module##_gethostbyaddr2_r( \ - addr, len, \ - af, \ - host, \ - buffer, buflen, \ - errnop, h_errnop, \ - NULL); \ -} \ -struct __useless_struct_to_allow_trailing_semicolon__ - -#define NSS_GETPW_PROTOTYPES(module) \ -enum nss_status _nss_##module##_getpwnam_r( \ - const char *name, \ - struct passwd *pwd, \ - char *buffer, size_t buflen, \ - int *errnop) _public_; \ -enum nss_status _nss_mymachines_getpwuid_r( \ - uid_t uid, \ - struct passwd *pwd, \ - char *buffer, size_t buflen, \ - int *errnop) _public_ - -#define NSS_GETGR_PROTOTYPES(module) \ -enum nss_status _nss_##module##_getgrnam_r( \ - const char *name, \ - struct group *gr, \ - char *buffer, size_t buflen, \ - int *errnop) _public_; \ -enum nss_status _nss_##module##_getgrgid_r( \ - gid_t gid, \ - struct group *gr, \ - char *buffer, size_t buflen, \ - int *errnop) _public_ diff --git a/src/basic/ordered-set.c b/src/basic/ordered-set.c deleted file mode 100644 index 2e0bdf6488..0000000000 --- a/src/basic/ordered-set.c +++ /dev/null @@ -1,64 +0,0 @@ -/*** - 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 . -***/ - -#include "ordered-set.h" -#include "strv.h" - -int ordered_set_consume(OrderedSet *s, void *p) { - int r; - - r = ordered_set_put(s, p); - if (r <= 0) - free(p); - - return r; -} - -int ordered_set_put_strdup(OrderedSet *s, const char *p) { - char *c; - int r; - - assert(s); - assert(p); - - c = strdup(p); - if (!c) - return -ENOMEM; - - r = ordered_set_consume(s, c); - if (r == -EEXIST) - return 0; - - return r; -} - -int ordered_set_put_strdupv(OrderedSet *s, char **l) { - int n = 0, r; - char **i; - - STRV_FOREACH(i, l) { - r = ordered_set_put_strdup(s, *i); - if (r < 0) - return r; - - n += r; - } - - return n; -} diff --git a/src/basic/ordered-set.h b/src/basic/ordered-set.h deleted file mode 100644 index e1dfc86380..0000000000 --- a/src/basic/ordered-set.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include "hashmap.h" - -typedef struct OrderedSet OrderedSet; - -static inline OrderedSet* ordered_set_new(const struct hash_ops *ops) { - return (OrderedSet*) ordered_hashmap_new(ops); -} - -static inline int ordered_set_ensure_allocated(OrderedSet **s, const struct hash_ops *ops) { - if (*s) - return 0; - - *s = ordered_set_new(ops); - if (!*s) - return -ENOMEM; - - return 0; -} - -static inline OrderedSet* ordered_set_free(OrderedSet *s) { - ordered_hashmap_free((OrderedHashmap*) s); - return NULL; -} - -static inline OrderedSet* ordered_set_free_free(OrderedSet *s) { - ordered_hashmap_free_free((OrderedHashmap*) s); - return NULL; -} - -static inline int ordered_set_put(OrderedSet *s, void *p) { - return ordered_hashmap_put((OrderedHashmap*) s, p, p); -} - -static inline bool ordered_set_isempty(OrderedSet *s) { - return ordered_hashmap_isempty((OrderedHashmap*) s); -} - -static inline bool ordered_set_iterate(OrderedSet *s, Iterator *i, void **value) { - return ordered_hashmap_iterate((OrderedHashmap*) s, i, value, NULL); -} - -int ordered_set_consume(OrderedSet *s, void *p); -int ordered_set_put_strdup(OrderedSet *s, const char *p); -int ordered_set_put_strdupv(OrderedSet *s, char **l); - -#define ORDERED_SET_FOREACH(e, s, i) \ - for ((i) = ITERATOR_FIRST; ordered_set_iterate((s), &(i), (void**)&(e)); ) - -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free_free); - -#define _cleanup_ordered_set_free_ _cleanup_(ordered_set_freep) -#define _cleanup_ordered_set_free_free_ _cleanup_(ordered_set_free_freep) diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c deleted file mode 100644 index a3cb81b040..0000000000 --- a/src/basic/parse-util.c +++ /dev/null @@ -1,534 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "extract-word.h" -#include "macro.h" -#include "parse-util.h" -#include "string-util.h" - -int parse_boolean(const char *v) { - assert(v); - - if (streq(v, "1") || strcaseeq(v, "yes") || strcaseeq(v, "y") || strcaseeq(v, "true") || strcaseeq(v, "t") || strcaseeq(v, "on")) - return 1; - else if (streq(v, "0") || strcaseeq(v, "no") || strcaseeq(v, "n") || strcaseeq(v, "false") || strcaseeq(v, "f") || strcaseeq(v, "off")) - return 0; - - return -EINVAL; -} - -int parse_pid(const char *s, pid_t* ret_pid) { - unsigned long ul = 0; - pid_t pid; - int r; - - assert(s); - assert(ret_pid); - - r = safe_atolu(s, &ul); - if (r < 0) - return r; - - pid = (pid_t) ul; - - if ((unsigned long) pid != ul) - return -ERANGE; - - if (pid <= 0) - return -ERANGE; - - *ret_pid = pid; - return 0; -} - -int parse_mode(const char *s, mode_t *ret) { - char *x; - long l; - - assert(s); - assert(ret); - - s += strspn(s, WHITESPACE); - if (s[0] == '-') - return -ERANGE; - - errno = 0; - l = strtol(s, &x, 8); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if (l < 0 || l > 07777) - return -ERANGE; - - *ret = (mode_t) l; - return 0; -} - -int parse_ifindex(const char *s, int *ret) { - int ifi, r; - - r = safe_atoi(s, &ifi); - if (r < 0) - return r; - if (ifi <= 0) - return -EINVAL; - - *ret = ifi; - return 0; -} - -int parse_size(const char *t, uint64_t base, uint64_t *size) { - - /* Soo, sometimes we want to parse IEC binary suffixes, and - * sometimes SI decimal suffixes. This function can parse - * both. Which one is the right way depends on the - * context. Wikipedia suggests that SI is customary for - * hardware metrics and network speeds, while IEC is - * customary for most data sizes used by software and volatile - * (RAM) memory. Hence be careful which one you pick! - * - * In either case we use just K, M, G as suffix, and not Ki, - * Mi, Gi or so (as IEC would suggest). That's because that's - * frickin' ugly. But this means you really need to make sure - * to document which base you are parsing when you use this - * call. */ - - struct table { - const char *suffix; - unsigned long long factor; - }; - - static const struct table iec[] = { - { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, - { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, - { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, - { "G", 1024ULL*1024ULL*1024ULL }, - { "M", 1024ULL*1024ULL }, - { "K", 1024ULL }, - { "B", 1ULL }, - { "", 1ULL }, - }; - - static const struct table si[] = { - { "E", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL*1000ULL }, - { "P", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL }, - { "T", 1000ULL*1000ULL*1000ULL*1000ULL }, - { "G", 1000ULL*1000ULL*1000ULL }, - { "M", 1000ULL*1000ULL }, - { "K", 1000ULL }, - { "B", 1ULL }, - { "", 1ULL }, - }; - - const struct table *table; - const char *p; - unsigned long long r = 0; - unsigned n_entries, start_pos = 0; - - assert(t); - assert(base == 1000 || base == 1024); - assert(size); - - if (base == 1000) { - table = si; - n_entries = ELEMENTSOF(si); - } else { - table = iec; - n_entries = ELEMENTSOF(iec); - } - - p = t; - do { - unsigned long long l, tmp; - double frac = 0; - char *e; - unsigned i; - - p += strspn(p, WHITESPACE); - - errno = 0; - l = strtoull(p, &e, 10); - if (errno > 0) - return -errno; - if (e == p) - return -EINVAL; - if (*p == '-') - return -ERANGE; - - if (*e == '.') { - e++; - - /* strtoull() itself would accept space/+/- */ - if (*e >= '0' && *e <= '9') { - unsigned long long l2; - char *e2; - - l2 = strtoull(e, &e2, 10); - if (errno > 0) - return -errno; - - /* Ignore failure. E.g. 10.M is valid */ - frac = l2; - for (; e < e2; e++) - frac /= 10; - } - } - - e += strspn(e, WHITESPACE); - - for (i = start_pos; i < n_entries; i++) - if (startswith(e, table[i].suffix)) - break; - - if (i >= n_entries) - return -EINVAL; - - if (l + (frac > 0) > ULLONG_MAX / table[i].factor) - return -ERANGE; - - tmp = l * table[i].factor + (unsigned long long) (frac * table[i].factor); - if (tmp > ULLONG_MAX - r) - return -ERANGE; - - r += tmp; - if ((unsigned long long) (uint64_t) r != r) - return -ERANGE; - - p = e + strlen(table[i].suffix); - - start_pos = i + 1; - - } while (*p); - - *size = r; - - return 0; -} - -int parse_range(const char *t, unsigned *lower, unsigned *upper) { - _cleanup_free_ char *word = NULL; - unsigned l, u; - int r; - - assert(lower); - assert(upper); - - /* Extract the lower bound. */ - r = extract_first_word(&t, &word, "-", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - r = safe_atou(word, &l); - if (r < 0) - return r; - - /* Check for the upper bound and extract it if needed */ - if (!t) - /* Single number with no dashes. */ - u = l; - else if (!*t) - /* Trailing dash is an error. */ - return -EINVAL; - else { - r = safe_atou(t, &u); - if (r < 0) - return r; - } - - *lower = l; - *upper = u; - return 0; -} - -char *format_bytes(char *buf, size_t l, uint64_t t) { - unsigned i; - - /* This only does IEC units so far */ - - static const struct { - const char *suffix; - uint64_t factor; - } table[] = { - { "E", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, - { "P", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, - { "T", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, - { "G", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, - { "M", UINT64_C(1024)*UINT64_C(1024) }, - { "K", UINT64_C(1024) }, - }; - - if (t == (uint64_t) -1) - return NULL; - - for (i = 0; i < ELEMENTSOF(table); i++) { - - if (t >= table[i].factor) { - snprintf(buf, l, - "%" PRIu64 ".%" PRIu64 "%s", - t / table[i].factor, - ((t*UINT64_C(10)) / table[i].factor) % UINT64_C(10), - table[i].suffix); - - goto finish; - } - } - - snprintf(buf, l, "%" PRIu64 "B", t); - -finish: - buf[l-1] = 0; - return buf; - -} - -int safe_atou(const char *s, unsigned *ret_u) { - char *x = NULL; - unsigned long l; - - assert(s); - assert(ret_u); - - /* strtoul() is happy to parse negative values, and silently - * converts them to unsigned values without generating an - * error. We want a clean error, hence let's look for the "-" - * prefix on our own, and generate an error. But let's do so - * only after strtoul() validated that the string is clean - * otherwise, so that we return EINVAL preferably over - * ERANGE. */ - - s += strspn(s, WHITESPACE); - - errno = 0; - l = strtoul(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if (s[0] == '-') - return -ERANGE; - if ((unsigned long) (unsigned) l != l) - return -ERANGE; - - *ret_u = (unsigned) l; - return 0; -} - -int safe_atoi(const char *s, int *ret_i) { - char *x = NULL; - long l; - - assert(s); - assert(ret_i); - - errno = 0; - l = strtol(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if ((long) (int) l != l) - return -ERANGE; - - *ret_i = (int) l; - return 0; -} - -int safe_atollu(const char *s, long long unsigned *ret_llu) { - char *x = NULL; - unsigned long long l; - - assert(s); - assert(ret_llu); - - s += strspn(s, WHITESPACE); - - errno = 0; - l = strtoull(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if (*s == '-') - return -ERANGE; - - *ret_llu = l; - return 0; -} - -int safe_atolli(const char *s, long long int *ret_lli) { - char *x = NULL; - long long l; - - assert(s); - assert(ret_lli); - - errno = 0; - l = strtoll(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - - *ret_lli = l; - return 0; -} - -int safe_atou8(const char *s, uint8_t *ret) { - char *x = NULL; - unsigned long l; - - assert(s); - assert(ret); - - s += strspn(s, WHITESPACE); - - errno = 0; - l = strtoul(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if (s[0] == '-') - return -ERANGE; - if ((unsigned long) (uint8_t) l != l) - return -ERANGE; - - *ret = (uint8_t) l; - return 0; -} - -int safe_atou16(const char *s, uint16_t *ret) { - char *x = NULL; - unsigned long l; - - assert(s); - assert(ret); - - s += strspn(s, WHITESPACE); - - errno = 0; - l = strtoul(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if (s[0] == '-') - return -ERANGE; - if ((unsigned long) (uint16_t) l != l) - return -ERANGE; - - *ret = (uint16_t) l; - return 0; -} - -int safe_atoi16(const char *s, int16_t *ret) { - char *x = NULL; - long l; - - assert(s); - assert(ret); - - errno = 0; - l = strtol(s, &x, 0); - if (errno > 0) - return -errno; - if (!x || x == s || *x) - return -EINVAL; - if ((long) (int16_t) l != l) - return -ERANGE; - - *ret = (int16_t) l; - return 0; -} - -int safe_atod(const char *s, double *ret_d) { - char *x = NULL; - double d = 0; - locale_t loc; - - assert(s); - assert(ret_d); - - loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); - if (loc == (locale_t) 0) - return -errno; - - errno = 0; - d = strtod_l(s, &x, loc); - if (errno > 0) { - freelocale(loc); - return -errno; - } - if (!x || x == s || *x) { - freelocale(loc); - return -EINVAL; - } - - freelocale(loc); - *ret_d = (double) d; - return 0; -} - -int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) { - size_t i; - unsigned val = 0; - const char *s; - - s = *p; - - /* accept any number of digits, strtoull is limted to 19 */ - for(i=0; i < digits; i++,s++) { - if (*s < '0' || *s > '9') { - if (i == 0) - return -EINVAL; - - /* too few digits, pad with 0 */ - for (; i < digits; i++) - val *= 10; - - break; - } - - val *= 10; - val += *s - '0'; - } - - /* maybe round up */ - if (*s >= '5' && *s <= '9') - val++; - - s += strspn(s, DIGITS); - - *p = s; - *res = val; - - return 0; -} diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h deleted file mode 100644 index d8dc26a36e..0000000000 --- a/src/basic/parse-util.h +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "macro.h" - -#define MODE_INVALID ((mode_t) -1) - -int parse_boolean(const char *v) _pure_; -int parse_pid(const char *s, pid_t* ret_pid); -int parse_mode(const char *s, mode_t *ret); -int parse_ifindex(const char *s, int *ret); - -int parse_size(const char *t, uint64_t base, uint64_t *size); -int parse_range(const char *t, unsigned *lower, unsigned *upper); - -#define FORMAT_BYTES_MAX 8 -char *format_bytes(char *buf, size_t l, uint64_t t); - -int safe_atou(const char *s, unsigned *ret_u); -int safe_atoi(const char *s, int *ret_i); -int safe_atollu(const char *s, unsigned long long *ret_u); -int safe_atolli(const char *s, long long int *ret_i); - -int safe_atou8(const char *s, uint8_t *ret); - -int safe_atou16(const char *s, uint16_t *ret); -int safe_atoi16(const char *s, int16_t *ret); - -static inline int safe_atou32(const char *s, uint32_t *ret_u) { - assert_cc(sizeof(uint32_t) == sizeof(unsigned)); - return safe_atou(s, (unsigned*) ret_u); -} - -static inline int safe_atoi32(const char *s, int32_t *ret_i) { - assert_cc(sizeof(int32_t) == sizeof(int)); - return safe_atoi(s, (int*) ret_i); -} - -static inline int safe_atou64(const char *s, uint64_t *ret_u) { - assert_cc(sizeof(uint64_t) == sizeof(unsigned long long)); - return safe_atollu(s, (unsigned long long*) ret_u); -} - -static inline int safe_atoi64(const char *s, int64_t *ret_i) { - assert_cc(sizeof(int64_t) == sizeof(long long int)); - return safe_atolli(s, (long long int*) ret_i); -} - -#if LONG_MAX == INT_MAX -static inline int safe_atolu(const char *s, unsigned long *ret_u) { - assert_cc(sizeof(unsigned long) == sizeof(unsigned)); - return safe_atou(s, (unsigned*) ret_u); -} -static inline int safe_atoli(const char *s, long int *ret_u) { - assert_cc(sizeof(long int) == sizeof(int)); - return safe_atoi(s, (int*) ret_u); -} -#else -static inline int safe_atolu(const char *s, unsigned long *ret_u) { - assert_cc(sizeof(unsigned long) == sizeof(unsigned long long)); - return safe_atollu(s, (unsigned long long*) ret_u); -} -static inline int safe_atoli(const char *s, long int *ret_u) { - assert_cc(sizeof(long int) == sizeof(long long int)); - return safe_atolli(s, (long long int*) ret_u); -} -#endif - -int safe_atod(const char *s, double *ret_d); - -int parse_fractional_part_u(const char **s, size_t digits, unsigned *res); diff --git a/src/basic/path-util.c b/src/basic/path-util.c deleted file mode 100644 index 822c09bfba..0000000000 --- a/src/basic/path-util.c +++ /dev/null @@ -1,797 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2012 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -/* When we include libgen.h because we need dirname() we immediately - * undefine basename() since libgen.h defines it as a macro to the - * POSIX version which is really broken. We prefer GNU basename(). */ -#include -#undef basename - -#include "alloc-util.h" -#include "extract-word.h" -#include "fs-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "path-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" - -bool path_is_absolute(const char *p) { - return p[0] == '/'; -} - -bool is_path(const char *p) { - return !!strchr(p, '/'); -} - -int path_split_and_make_absolute(const char *p, char ***ret) { - char **l; - int r; - - assert(p); - assert(ret); - - l = strv_split(p, ":"); - if (!l) - return -ENOMEM; - - r = path_strv_make_absolute_cwd(l); - if (r < 0) { - strv_free(l); - return r; - } - - *ret = l; - return r; -} - -char *path_make_absolute(const char *p, const char *prefix) { - assert(p); - - /* Makes every item in the list an absolute path by prepending - * the prefix, if specified and necessary */ - - if (path_is_absolute(p) || !prefix) - return strdup(p); - - return strjoin(prefix, "/", p, NULL); -} - -int path_make_absolute_cwd(const char *p, char **ret) { - char *c; - - assert(p); - assert(ret); - - /* Similar to path_make_absolute(), but prefixes with the - * current working directory. */ - - if (path_is_absolute(p)) - c = strdup(p); - else { - _cleanup_free_ char *cwd = NULL; - - cwd = get_current_dir_name(); - if (!cwd) - return negative_errno(); - - c = strjoin(cwd, "/", p, NULL); - } - if (!c) - return -ENOMEM; - - *ret = c; - return 0; -} - -int path_make_relative(const char *from_dir, const char *to_path, char **_r) { - char *r, *p; - unsigned n_parents; - - assert(from_dir); - assert(to_path); - assert(_r); - - /* Strips the common part, and adds ".." elements as necessary. */ - - if (!path_is_absolute(from_dir)) - return -EINVAL; - - if (!path_is_absolute(to_path)) - return -EINVAL; - - /* Skip the common part. */ - for (;;) { - size_t a; - size_t b; - - from_dir += strspn(from_dir, "/"); - to_path += strspn(to_path, "/"); - - if (!*from_dir) { - if (!*to_path) - /* from_dir equals to_path. */ - r = strdup("."); - else - /* from_dir is a parent directory of to_path. */ - r = strdup(to_path); - - if (!r) - return -ENOMEM; - - path_kill_slashes(r); - - *_r = r; - return 0; - } - - if (!*to_path) - break; - - a = strcspn(from_dir, "/"); - b = strcspn(to_path, "/"); - - if (a != b) - break; - - if (memcmp(from_dir, to_path, a) != 0) - break; - - from_dir += a; - to_path += b; - } - - /* If we're here, then "from_dir" has one or more elements that need to - * be replaced with "..". */ - - /* Count the number of necessary ".." elements. */ - for (n_parents = 0;;) { - from_dir += strspn(from_dir, "/"); - - if (!*from_dir) - break; - - from_dir += strcspn(from_dir, "/"); - n_parents++; - } - - r = malloc(n_parents * 3 + strlen(to_path) + 1); - if (!r) - return -ENOMEM; - - for (p = r; n_parents > 0; n_parents--, p += 3) - memcpy(p, "../", 3); - - strcpy(p, to_path); - path_kill_slashes(r); - - *_r = r; - return 0; -} - -int path_strv_make_absolute_cwd(char **l) { - char **s; - int r; - - /* Goes through every item in the string list and makes it - * absolute. This works in place and won't rollback any - * changes on failure. */ - - STRV_FOREACH(s, l) { - char *t; - - r = path_make_absolute_cwd(*s, &t); - if (r < 0) - return r; - - free(*s); - *s = t; - } - - return 0; -} - -char **path_strv_resolve(char **l, const char *prefix) { - char **s; - unsigned k = 0; - bool enomem = false; - - if (strv_isempty(l)) - return l; - - /* Goes through every item in the string list and canonicalize - * the path. This works in place and won't rollback any - * changes on failure. */ - - STRV_FOREACH(s, l) { - char *t, *u; - _cleanup_free_ char *orig = NULL; - - if (!path_is_absolute(*s)) { - free(*s); - continue; - } - - if (prefix) { - orig = *s; - t = strappend(prefix, orig); - if (!t) { - enomem = true; - continue; - } - } else - t = *s; - - errno = 0; - u = canonicalize_file_name(t); - if (!u) { - if (errno == ENOENT) { - if (prefix) { - u = orig; - orig = NULL; - free(t); - } else - u = t; - } else { - free(t); - if (errno == ENOMEM || errno == 0) - enomem = true; - - continue; - } - } else if (prefix) { - char *x; - - free(t); - x = path_startswith(u, prefix); - if (x) { - /* restore the slash if it was lost */ - if (!startswith(x, "/")) - *(--x) = '/'; - - t = strdup(x); - free(u); - if (!t) { - enomem = true; - continue; - } - u = t; - } else { - /* canonicalized path goes outside of - * prefix, keep the original path instead */ - free(u); - u = orig; - orig = NULL; - } - } else - free(t); - - l[k++] = u; - } - - l[k] = NULL; - - if (enomem) - return NULL; - - return l; -} - -char **path_strv_resolve_uniq(char **l, const char *prefix) { - - if (strv_isempty(l)) - return l; - - if (!path_strv_resolve(l, prefix)) - return NULL; - - return strv_uniq(l); -} - -char *path_kill_slashes(char *path) { - char *f, *t; - bool slash = false; - - /* Removes redundant inner and trailing slashes. Modifies the - * passed string in-place. - * - * ///foo///bar/ becomes /foo/bar - */ - - for (f = path, t = path; *f; f++) { - - if (*f == '/') { - slash = true; - continue; - } - - if (slash) { - slash = false; - *(t++) = '/'; - } - - *(t++) = *f; - } - - /* Special rule, if we are talking of the root directory, a - trailing slash is good */ - - if (t == path && slash) - *(t++) = '/'; - - *t = 0; - return path; -} - -char* path_startswith(const char *path, const char *prefix) { - assert(path); - assert(prefix); - - if ((path[0] == '/') != (prefix[0] == '/')) - return NULL; - - for (;;) { - size_t a, b; - - path += strspn(path, "/"); - prefix += strspn(prefix, "/"); - - if (*prefix == 0) - return (char*) path; - - if (*path == 0) - return NULL; - - a = strcspn(path, "/"); - b = strcspn(prefix, "/"); - - if (a != b) - return NULL; - - if (memcmp(path, prefix, a) != 0) - return NULL; - - path += a; - prefix += b; - } -} - -int path_compare(const char *a, const char *b) { - int d; - - assert(a); - assert(b); - - /* A relative path and an abolute path must not compare as equal. - * Which one is sorted before the other does not really matter. - * Here a relative path is ordered before an absolute path. */ - d = (a[0] == '/') - (b[0] == '/'); - if (d != 0) - return d; - - for (;;) { - size_t j, k; - - a += strspn(a, "/"); - b += strspn(b, "/"); - - if (*a == 0 && *b == 0) - return 0; - - /* Order prefixes first: "/foo" before "/foo/bar" */ - if (*a == 0) - return -1; - if (*b == 0) - return 1; - - j = strcspn(a, "/"); - k = strcspn(b, "/"); - - /* Alphabetical sort: "/foo/aaa" before "/foo/b" */ - d = memcmp(a, b, MIN(j, k)); - if (d != 0) - return (d > 0) - (d < 0); /* sign of d */ - - /* Sort "/foo/a" before "/foo/aaa" */ - d = (j > k) - (j < k); /* sign of (j - k) */ - if (d != 0) - return d; - - a += j; - b += k; - } -} - -bool path_equal(const char *a, const char *b) { - return path_compare(a, b) == 0; -} - -bool path_equal_or_files_same(const char *a, const char *b) { - return path_equal(a, b) || files_same(a, b) > 0; -} - -char* path_join(const char *root, const char *path, const char *rest) { - assert(path); - - if (!isempty(root)) - return strjoin(root, endswith(root, "/") ? "" : "/", - path[0] == '/' ? path+1 : path, - rest ? (endswith(path, "/") ? "" : "/") : NULL, - rest && rest[0] == '/' ? rest+1 : rest, - NULL); - else - return strjoin(path, - rest ? (endswith(path, "/") ? "" : "/") : NULL, - rest && rest[0] == '/' ? rest+1 : rest, - NULL); -} - -int find_binary(const char *name, char **ret) { - int last_error, r; - const char *p; - - assert(name); - - if (is_path(name)) { - if (access(name, X_OK) < 0) - return -errno; - - if (ret) { - r = path_make_absolute_cwd(name, ret); - if (r < 0) - return r; - } - - return 0; - } - - /** - * Plain getenv, not secure_getenv, because we want - * to actually allow the user to pick the binary. - */ - p = getenv("PATH"); - if (!p) - p = DEFAULT_PATH; - - last_error = -ENOENT; - - for (;;) { - _cleanup_free_ char *j = NULL, *element = NULL; - - r = extract_first_word(&p, &element, ":", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (r == 0) - break; - - if (!path_is_absolute(element)) - continue; - - j = strjoin(element, "/", name, NULL); - if (!j) - return -ENOMEM; - - if (access(j, X_OK) >= 0) { - /* Found it! */ - - if (ret) { - *ret = path_kill_slashes(j); - j = NULL; - } - - return 0; - } - - last_error = -errno; - } - - return last_error; -} - -bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) { - bool changed = false; - const char* const* i; - - assert(timestamp); - - if (paths == NULL) - return false; - - STRV_FOREACH(i, paths) { - struct stat stats; - usec_t u; - - if (stat(*i, &stats) < 0) - continue; - - u = timespec_load(&stats.st_mtim); - - /* first check */ - if (*timestamp >= u) - continue; - - log_debug("timestamp of '%s' changed", *i); - - /* update timestamp */ - if (update) { - *timestamp = u; - changed = true; - } else - return true; - } - - return changed; -} - -static int binary_is_good(const char *binary) { - _cleanup_free_ char *p = NULL, *d = NULL; - int r; - - r = find_binary(binary, &p); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - /* An fsck that is linked to /bin/true is a non-existent - * fsck */ - - r = readlink_malloc(p, &d); - if (r == -EINVAL) /* not a symlink */ - return 1; - if (r < 0) - return r; - - return !path_equal(d, "true") && - !path_equal(d, "/bin/true") && - !path_equal(d, "/usr/bin/true") && - !path_equal(d, "/dev/null"); -} - -int fsck_exists(const char *fstype) { - const char *checker; - - assert(fstype); - - if (streq(fstype, "auto")) - return -EINVAL; - - checker = strjoina("fsck.", fstype); - return binary_is_good(checker); -} - -int mkfs_exists(const char *fstype) { - const char *mkfs; - - assert(fstype); - - if (streq(fstype, "auto")) - return -EINVAL; - - mkfs = strjoina("mkfs.", fstype); - return binary_is_good(mkfs); -} - -char *prefix_root(const char *root, const char *path) { - char *n, *p; - size_t l; - - /* If root is passed, prefixes path with it. Otherwise returns - * it as is. */ - - assert(path); - - /* First, drop duplicate prefixing slashes from the path */ - while (path[0] == '/' && path[1] == '/') - path++; - - if (isempty(root) || path_equal(root, "/")) - return strdup(path); - - l = strlen(root) + 1 + strlen(path) + 1; - - n = new(char, l); - if (!n) - return NULL; - - p = stpcpy(n, root); - - while (p > n && p[-1] == '/') - p--; - - if (path[0] != '/') - *(p++) = '/'; - - strcpy(p, path); - return n; -} - -int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) { - char *p; - int r; - - /* - * This function is intended to be used in command line - * parsers, to handle paths that are passed in. It makes the - * path absolute, and reduces it to NULL if omitted or - * root (the latter optionally). - * - * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON - * SUCCESS! Hence, do not pass in uninitialized pointers. - */ - - if (isempty(path)) { - *arg = mfree(*arg); - return 0; - } - - r = path_make_absolute_cwd(path, &p); - if (r < 0) - return log_error_errno(r, "Failed to parse path \"%s\" and make it absolute: %m", path); - - path_kill_slashes(p); - if (suppress_root && path_equal(p, "/")) - p = mfree(p); - - free(*arg); - *arg = p; - return 0; -} - -char* dirname_malloc(const char *path) { - char *d, *dir, *dir2; - - assert(path); - - d = strdup(path); - if (!d) - return NULL; - - dir = dirname(d); - assert(dir); - - if (dir == d) - return d; - - dir2 = strdup(dir); - free(d); - - return dir2; -} - -bool filename_is_valid(const char *p) { - const char *e; - - if (isempty(p)) - return false; - - if (streq(p, ".")) - return false; - - if (streq(p, "..")) - return false; - - e = strchrnul(p, '/'); - if (*e != 0) - return false; - - if (e - p > FILENAME_MAX) - return false; - - return true; -} - -bool path_is_safe(const char *p) { - - if (isempty(p)) - return false; - - if (streq(p, "..") || startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../")) - return false; - - if (strlen(p)+1 > PATH_MAX) - return false; - - /* The following two checks are not really dangerous, but hey, they still are confusing */ - if (streq(p, ".") || startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./")) - return false; - - if (strstr(p, "//")) - return false; - - return true; -} - -char *file_in_same_dir(const char *path, const char *filename) { - char *e, *ret; - size_t k; - - assert(path); - assert(filename); - - /* This removes the last component of path and appends - * filename, unless the latter is absolute anyway or the - * former isn't */ - - if (path_is_absolute(filename)) - return strdup(filename); - - e = strrchr(path, '/'); - if (!e) - return strdup(filename); - - k = strlen(filename); - ret = new(char, (e + 1 - path) + k + 1); - if (!ret) - return NULL; - - memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1); - return ret; -} - -bool hidden_file_allow_backup(const char *filename) { - assert(filename); - - return - filename[0] == '.' || - streq(filename, "lost+found") || - streq(filename, "aquota.user") || - streq(filename, "aquota.group") || - endswith(filename, ".rpmnew") || - endswith(filename, ".rpmsave") || - endswith(filename, ".rpmorig") || - endswith(filename, ".dpkg-old") || - endswith(filename, ".dpkg-new") || - endswith(filename, ".dpkg-tmp") || - endswith(filename, ".dpkg-dist") || - endswith(filename, ".dpkg-bak") || - endswith(filename, ".dpkg-backup") || - endswith(filename, ".dpkg-remove") || - endswith(filename, ".swp"); -} - -bool hidden_file(const char *filename) { - assert(filename); - - if (endswith(filename, "~")) - return true; - - return hidden_file_allow_backup(filename); -} - -bool is_device_path(const char *path) { - - /* Returns true on paths that refer to a device, either in - * sysfs or in /dev */ - - return - path_startswith(path, "/dev/") || - path_startswith(path, "/sys/"); -} diff --git a/src/basic/path-util.h b/src/basic/path-util.h deleted file mode 100644 index 2c2f87a9f2..0000000000 --- a/src/basic/path-util.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2012 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 . -***/ - -#include -#include -#include - -#include "macro.h" -#include "time-util.h" - -#define DEFAULT_PATH_NORMAL "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin" -#define DEFAULT_PATH_SPLIT_USR DEFAULT_PATH_NORMAL ":/sbin:/bin" - -#ifdef HAVE_SPLIT_USR -# define DEFAULT_PATH DEFAULT_PATH_SPLIT_USR -#else -# define DEFAULT_PATH DEFAULT_PATH_NORMAL -#endif - -bool is_path(const char *p) _pure_; -int path_split_and_make_absolute(const char *p, char ***ret); -bool path_is_absolute(const char *p) _pure_; -char* path_make_absolute(const char *p, const char *prefix); -int path_make_absolute_cwd(const char *p, char **ret); -int path_make_relative(const char *from_dir, const char *to_path, char **_r); -char* path_kill_slashes(char *path); -char* path_startswith(const char *path, const char *prefix) _pure_; -int path_compare(const char *a, const char *b) _pure_; -bool path_equal(const char *a, const char *b) _pure_; -bool path_equal_or_files_same(const char *a, const char *b); -char* path_join(const char *root, const char *path, const char *rest); - -int path_strv_make_absolute_cwd(char **l); -char** path_strv_resolve(char **l, const char *prefix); -char** path_strv_resolve_uniq(char **l, const char *prefix); - -int find_binary(const char *name, char **filename); - -bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update); - -int fsck_exists(const char *fstype); -int mkfs_exists(const char *fstype); - -/* Iterates through the path prefixes of the specified path, going up - * the tree, to root. Also returns "" (and not "/"!) for the root - * directory. Excludes the specified directory itself */ -#define PATH_FOREACH_PREFIX(prefix, path) \ - for (char *_slash = ({ path_kill_slashes(strcpy(prefix, path)); streq(prefix, "/") ? NULL : strrchr(prefix, '/'); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/')) - -/* Same as PATH_FOREACH_PREFIX but also includes the specified path itself */ -#define PATH_FOREACH_PREFIX_MORE(prefix, path) \ - for (char *_slash = ({ path_kill_slashes(strcpy(prefix, path)); if (streq(prefix, "/")) prefix[0] = 0; strrchr(prefix, 0); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/')) - -char *prefix_root(const char *root, const char *path); - -/* Similar to prefix_root(), but returns an alloca() buffer, or - * possibly a const pointer into the path parameter */ -#define prefix_roota(root, path) \ - ({ \ - const char* _path = (path), *_root = (root), *_ret; \ - char *_p, *_n; \ - size_t _l; \ - while (_path[0] == '/' && _path[1] == '/') \ - _path ++; \ - if (isempty(_root) || path_equal(_root, "/")) \ - _ret = _path; \ - else { \ - _l = strlen(_root) + 1 + strlen(_path) + 1; \ - _n = alloca(_l); \ - _p = stpcpy(_n, _root); \ - while (_p > _n && _p[-1] == '/') \ - _p--; \ - if (_path[0] != '/') \ - *(_p++) = '/'; \ - strcpy(_p, _path); \ - _ret = _n; \ - } \ - _ret; \ - }) - -int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg); - -char* dirname_malloc(const char *path); - -bool filename_is_valid(const char *p) _pure_; -bool path_is_safe(const char *p) _pure_; - -char *file_in_same_dir(const char *path, const char *filename); - -bool hidden_file_allow_backup(const char *filename); -bool hidden_file(const char *filename) _pure_; - -bool is_device_path(const char *path); diff --git a/src/basic/prioq.c b/src/basic/prioq.c deleted file mode 100644 index d2ec516d29..0000000000 --- a/src/basic/prioq.c +++ /dev/null @@ -1,320 +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 . -***/ - -/* - * Priority Queue - * The prioq object implements a priority queue. That is, it orders objects by - * their priority and allows O(1) access to the object with the highest - * priority. Insertion and removal are Θ(log n). Optionally, the caller can - * provide a pointer to an index which will be kept up-to-date by the prioq. - * - * The underlying algorithm used in this implementation is a Heap. - */ - -#include -#include - -#include "alloc-util.h" -#include "hashmap.h" -#include "prioq.h" - -struct prioq_item { - void *data; - unsigned *idx; -}; - -struct Prioq { - compare_func_t compare_func; - unsigned n_items, n_allocated; - - struct prioq_item *items; -}; - -Prioq *prioq_new(compare_func_t compare_func) { - Prioq *q; - - q = new0(Prioq, 1); - if (!q) - return q; - - q->compare_func = compare_func; - return q; -} - -Prioq* prioq_free(Prioq *q) { - if (!q) - return NULL; - - free(q->items); - free(q); - - return NULL; -} - -int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func) { - assert(q); - - if (*q) - return 0; - - *q = prioq_new(compare_func); - if (!*q) - return -ENOMEM; - - return 0; -} - -static void swap(Prioq *q, unsigned j, unsigned k) { - void *saved_data; - unsigned *saved_idx; - - assert(q); - assert(j < q->n_items); - assert(k < q->n_items); - - assert(!q->items[j].idx || *(q->items[j].idx) == j); - assert(!q->items[k].idx || *(q->items[k].idx) == k); - - saved_data = q->items[j].data; - saved_idx = q->items[j].idx; - q->items[j].data = q->items[k].data; - q->items[j].idx = q->items[k].idx; - q->items[k].data = saved_data; - q->items[k].idx = saved_idx; - - if (q->items[j].idx) - *q->items[j].idx = j; - - if (q->items[k].idx) - *q->items[k].idx = k; -} - -static unsigned shuffle_up(Prioq *q, unsigned idx) { - assert(q); - - while (idx > 0) { - unsigned k; - - k = (idx-1)/2; - - if (q->compare_func(q->items[k].data, q->items[idx].data) <= 0) - break; - - swap(q, idx, k); - idx = k; - } - - return idx; -} - -static unsigned shuffle_down(Prioq *q, unsigned idx) { - assert(q); - - for (;;) { - unsigned j, k, s; - - k = (idx+1)*2; /* right child */ - j = k-1; /* left child */ - - if (j >= q->n_items) - break; - - if (q->compare_func(q->items[j].data, q->items[idx].data) < 0) - - /* So our left child is smaller than we are, let's - * remember this fact */ - s = j; - else - s = idx; - - if (k < q->n_items && - q->compare_func(q->items[k].data, q->items[s].data) < 0) - - /* So our right child is smaller than we are, let's - * remember this fact */ - s = k; - - /* s now points to the smallest of the three items */ - - if (s == idx) - /* No swap necessary, we're done */ - break; - - swap(q, idx, s); - idx = s; - } - - return idx; -} - -int prioq_put(Prioq *q, void *data, unsigned *idx) { - struct prioq_item *i; - unsigned k; - - assert(q); - - if (q->n_items >= q->n_allocated) { - unsigned n; - struct prioq_item *j; - - n = MAX((q->n_items+1) * 2, 16u); - j = realloc(q->items, sizeof(struct prioq_item) * n); - if (!j) - return -ENOMEM; - - q->items = j; - q->n_allocated = n; - } - - k = q->n_items++; - i = q->items + k; - i->data = data; - i->idx = idx; - - if (idx) - *idx = k; - - shuffle_up(q, k); - - return 0; -} - -static void remove_item(Prioq *q, struct prioq_item *i) { - struct prioq_item *l; - - assert(q); - assert(i); - - l = q->items + q->n_items - 1; - - if (i == l) - /* Last entry, let's just remove it */ - q->n_items--; - else { - unsigned k; - - /* Not last entry, let's replace the last entry with - * this one, and reshuffle */ - - k = i - q->items; - - i->data = l->data; - i->idx = l->idx; - if (i->idx) - *i->idx = k; - q->n_items--; - - k = shuffle_down(q, k); - shuffle_up(q, k); - } -} - -_pure_ static struct prioq_item* find_item(Prioq *q, void *data, unsigned *idx) { - struct prioq_item *i; - - assert(q); - - if (idx) { - if (*idx == PRIOQ_IDX_NULL || - *idx > q->n_items) - return NULL; - - i = q->items + *idx; - if (i->data != data) - return NULL; - - return i; - } else { - for (i = q->items; i < q->items + q->n_items; i++) - if (i->data == data) - return i; - return NULL; - } -} - -int prioq_remove(Prioq *q, void *data, unsigned *idx) { - struct prioq_item *i; - - if (!q) - return 0; - - i = find_item(q, data, idx); - if (!i) - return 0; - - remove_item(q, i); - return 1; -} - -int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) { - struct prioq_item *i; - unsigned k; - - assert(q); - - i = find_item(q, data, idx); - if (!i) - return 0; - - k = i - q->items; - k = shuffle_down(q, k); - shuffle_up(q, k); - return 1; -} - -void *prioq_peek(Prioq *q) { - - if (!q) - return NULL; - - if (q->n_items <= 0) - return NULL; - - return q->items[0].data; -} - -void *prioq_pop(Prioq *q) { - void *data; - - if (!q) - return NULL; - - if (q->n_items <= 0) - return NULL; - - data = q->items[0].data; - remove_item(q, q->items); - return data; -} - -unsigned prioq_size(Prioq *q) { - - if (!q) - return 0; - - return q->n_items; -} - -bool prioq_isempty(Prioq *q) { - - if (!q) - return true; - - return q->n_items <= 0; -} diff --git a/src/basic/prioq.h b/src/basic/prioq.h deleted file mode 100644 index 113c73d040..0000000000 --- a/src/basic/prioq.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -#include "hashmap.h" -#include "macro.h" - -typedef struct Prioq Prioq; - -#define PRIOQ_IDX_NULL ((unsigned) -1) - -Prioq *prioq_new(compare_func_t compare); -Prioq *prioq_free(Prioq *q); -int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func); - -int prioq_put(Prioq *q, void *data, unsigned *idx); -int prioq_remove(Prioq *q, void *data, unsigned *idx); -int prioq_reshuffle(Prioq *q, void *data, unsigned *idx); - -void *prioq_peek(Prioq *q) _pure_; -void *prioq_pop(Prioq *q); - -unsigned prioq_size(Prioq *q) _pure_; -bool prioq_isempty(Prioq *q) _pure_; diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c deleted file mode 100644 index 3505fa9c9a..0000000000 --- a/src/basic/proc-cmdline.c +++ /dev/null @@ -1,176 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#include "alloc-util.h" -#include "extract-word.h" -#include "fileio.h" -#include "macro.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "process-util.h" -#include "special.h" -#include "string-util.h" -#include "util.h" -#include "virt.h" - -int proc_cmdline(char **ret) { - assert(ret); - - if (detect_container() > 0) - return get_process_cmdline(1, 0, false, ret); - else - return read_one_line_file("/proc/cmdline", ret); -} - -int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) { - _cleanup_free_ char *line = NULL; - const char *p; - int r; - - assert(parse_item); - - r = proc_cmdline(&line); - if (r < 0) - return r; - - p = line; - for (;;) { - _cleanup_free_ char *word = NULL; - char *value = NULL; - - r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); - if (r < 0) - return r; - if (r == 0) - break; - - /* Filter out arguments that are intended only for the - * initrd */ - if (!in_initrd() && startswith(word, "rd.")) - continue; - - value = strchr(word, '='); - if (value) - *(value++) = 0; - - r = parse_item(word, value); - if (r < 0) - return r; - } - - return 0; -} - -int get_proc_cmdline_key(const char *key, char **value) { - _cleanup_free_ char *line = NULL, *ret = NULL; - bool found = false; - const char *p; - int r; - - assert(key); - - r = proc_cmdline(&line); - if (r < 0) - return r; - - p = line; - for (;;) { - _cleanup_free_ char *word = NULL; - const char *e; - - r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); - if (r < 0) - return r; - if (r == 0) - break; - - /* Filter out arguments that are intended only for the - * initrd */ - if (!in_initrd() && startswith(word, "rd.")) - continue; - - if (value) { - e = startswith(word, key); - if (!e) - continue; - - r = free_and_strdup(&ret, e); - if (r < 0) - return r; - - found = true; - } else { - if (streq(word, key)) - found = true; - } - } - - if (value) { - *value = ret; - ret = NULL; - } - - return found; - -} - -int shall_restore_state(void) { - _cleanup_free_ char *value = NULL; - int r; - - r = get_proc_cmdline_key("systemd.restore_state=", &value); - if (r < 0) - return r; - if (r == 0) - return true; - - return parse_boolean(value); -} - -static const char * const rlmap[] = { - "emergency", SPECIAL_EMERGENCY_TARGET, - "-b", SPECIAL_EMERGENCY_TARGET, - "rescue", SPECIAL_RESCUE_TARGET, - "single", SPECIAL_RESCUE_TARGET, - "-s", SPECIAL_RESCUE_TARGET, - "s", SPECIAL_RESCUE_TARGET, - "S", SPECIAL_RESCUE_TARGET, - "1", SPECIAL_RESCUE_TARGET, - "2", SPECIAL_MULTI_USER_TARGET, - "3", SPECIAL_MULTI_USER_TARGET, - "4", SPECIAL_MULTI_USER_TARGET, - "5", SPECIAL_GRAPHICAL_TARGET, -}; - -const char* runlevel_to_target(const char *word) { - size_t i; - - if (!word) - return NULL; - - for (i = 0; i < ELEMENTSOF(rlmap); i += 2) - if (streq(word, rlmap[i])) - return rlmap[i+1]; - - return NULL; -} diff --git a/src/basic/proc-cmdline.h b/src/basic/proc-cmdline.h deleted file mode 100644 index 452642a2f5..0000000000 --- a/src/basic/proc-cmdline.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int proc_cmdline(char **ret); -int parse_proc_cmdline(int (*parse_word)(const char *key, const char *value)); -int get_proc_cmdline_key(const char *parameter, char **value); - -int shall_restore_state(void); -const char* runlevel_to_target(const char *rl); diff --git a/src/basic/process-util.c b/src/basic/process-util.c deleted file mode 100644 index 189ef9ab60..0000000000 --- a/src/basic/process-util.c +++ /dev/null @@ -1,793 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_VALGRIND_VALGRIND_H -#include -#endif - -#include "alloc-util.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "ioprio.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "process-util.h" -#include "signal-util.h" -#include "stat-util.h" -#include "string-table.h" -#include "string-util.h" -#include "user-util.h" -#include "util.h" - -int get_process_state(pid_t pid) { - const char *p; - char state; - int r; - _cleanup_free_ char *line = NULL; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "stat"); - - r = read_one_line_file(p, &line); - if (r == -ENOENT) - return -ESRCH; - if (r < 0) - return r; - - p = strrchr(line, ')'); - if (!p) - return -EIO; - - p++; - - if (sscanf(p, " %c", &state) != 1) - return -EIO; - - return (unsigned char) state; -} - -int get_process_comm(pid_t pid, char **name) { - const char *p; - int r; - - assert(name); - assert(pid >= 0); - - p = procfs_file_alloca(pid, "comm"); - - r = read_one_line_file(p, name); - if (r == -ENOENT) - return -ESRCH; - - return r; -} - -int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { - _cleanup_fclose_ FILE *f = NULL; - char *r = NULL, *k; - const char *p; - int c; - - assert(line); - assert(pid >= 0); - - p = procfs_file_alloca(pid, "cmdline"); - - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - return -errno; - } - - if (max_length == 0) { - size_t len = 0, allocated = 0; - - while ((c = getc(f)) != EOF) { - - if (!GREEDY_REALLOC(r, allocated, len+2)) { - free(r); - return -ENOMEM; - } - - r[len++] = isprint(c) ? c : ' '; - } - - if (len > 0) - r[len-1] = 0; - - } else { - bool space = false; - size_t left; - - r = new(char, max_length); - if (!r) - return -ENOMEM; - - k = r; - left = max_length; - while ((c = getc(f)) != EOF) { - - if (isprint(c)) { - if (space) { - if (left <= 4) - break; - - *(k++) = ' '; - left--; - space = false; - } - - if (left <= 4) - break; - - *(k++) = (char) c; - left--; - } else - space = true; - } - - if (left <= 4) { - size_t n = MIN(left-1, 3U); - memcpy(k, "...", n); - k[n] = 0; - } else - *k = 0; - } - - /* Kernel threads have no argv[] */ - if (isempty(r)) { - _cleanup_free_ char *t = NULL; - int h; - - free(r); - - if (!comm_fallback) - return -ENOENT; - - h = get_process_comm(pid, &t); - if (h < 0) - return h; - - r = strjoin("[", t, "]", NULL); - if (!r) - return -ENOMEM; - } - - *line = r; - return 0; -} - -void rename_process(const char name[8]) { - assert(name); - - /* This is a like a poor man's setproctitle(). It changes the - * comm field, argv[0], and also the glibc's internally used - * name of the process. For the first one a limit of 16 chars - * applies, to the second one usually one of 10 (i.e. length - * of "/sbin/init"), to the third one one of 7 (i.e. length of - * "systemd"). If you pass a longer string it will be - * truncated */ - - prctl(PR_SET_NAME, name); - - if (program_invocation_name) - strncpy(program_invocation_name, name, strlen(program_invocation_name)); - - if (saved_argc > 0) { - int i; - - if (saved_argv[0]) - strncpy(saved_argv[0], name, strlen(saved_argv[0])); - - for (i = 1; i < saved_argc; i++) { - if (!saved_argv[i]) - break; - - memzero(saved_argv[i], strlen(saved_argv[i])); - } - } -} - -int is_kernel_thread(pid_t pid) { - const char *p; - size_t count; - char c; - bool eof; - FILE *f; - - if (pid == 0 || pid == 1) /* pid 1, and we ourselves certainly aren't a kernel thread */ - return 0; - - assert(pid > 1); - - p = procfs_file_alloca(pid, "cmdline"); - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - return -errno; - } - - count = fread(&c, 1, 1, f); - eof = feof(f); - fclose(f); - - /* Kernel threads have an empty cmdline */ - - if (count <= 0) - return eof ? 1 : -errno; - - return 0; -} - -int get_process_capeff(pid_t pid, char **capeff) { - const char *p; - int r; - - assert(capeff); - assert(pid >= 0); - - p = procfs_file_alloca(pid, "status"); - - r = get_proc_field(p, "CapEff", WHITESPACE, capeff); - if (r == -ENOENT) - return -ESRCH; - - return r; -} - -static int get_process_link_contents(const char *proc_file, char **name) { - int r; - - assert(proc_file); - assert(name); - - r = readlink_malloc(proc_file, name); - if (r == -ENOENT) - return -ESRCH; - if (r < 0) - return r; - - return 0; -} - -int get_process_exe(pid_t pid, char **name) { - const char *p; - char *d; - int r; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "exe"); - r = get_process_link_contents(p, name); - if (r < 0) - return r; - - d = endswith(*name, " (deleted)"); - if (d) - *d = '\0'; - - return 0; -} - -static int get_process_id(pid_t pid, const char *field, uid_t *uid) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - const char *p; - - assert(field); - assert(uid); - - if (pid == 0) - return getuid(); - - p = procfs_file_alloca(pid, "status"); - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - return -errno; - } - - FOREACH_LINE(line, f, return -errno) { - char *l; - - l = strstrip(line); - - if (startswith(l, field)) { - l += strlen(field); - l += strspn(l, WHITESPACE); - - l[strcspn(l, WHITESPACE)] = 0; - - return parse_uid(l, uid); - } - } - - return -EIO; -} - -int get_process_uid(pid_t pid, uid_t *uid) { - return get_process_id(pid, "Uid:", uid); -} - -int get_process_gid(pid_t pid, gid_t *gid) { - assert_cc(sizeof(uid_t) == sizeof(gid_t)); - return get_process_id(pid, "Gid:", gid); -} - -int get_process_cwd(pid_t pid, char **cwd) { - const char *p; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "cwd"); - - return get_process_link_contents(p, cwd); -} - -int get_process_root(pid_t pid, char **root) { - const char *p; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "root"); - - return get_process_link_contents(p, root); -} - -int get_process_environ(pid_t pid, char **env) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *outcome = NULL; - int c; - const char *p; - size_t allocated = 0, sz = 0; - - assert(pid >= 0); - assert(env); - - p = procfs_file_alloca(pid, "environ"); - - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - return -errno; - } - - while ((c = fgetc(f)) != EOF) { - if (!GREEDY_REALLOC(outcome, allocated, sz + 5)) - return -ENOMEM; - - if (c == '\0') - outcome[sz++] = '\n'; - else - sz += cescape_char(c, outcome + sz); - } - - if (!outcome) { - outcome = strdup(""); - if (!outcome) - return -ENOMEM; - } else - outcome[sz] = '\0'; - - *env = outcome; - outcome = NULL; - - return 0; -} - -int get_process_ppid(pid_t pid, pid_t *_ppid) { - int r; - _cleanup_free_ char *line = NULL; - long unsigned ppid; - const char *p; - - assert(pid >= 0); - assert(_ppid); - - if (pid == 0) { - *_ppid = getppid(); - return 0; - } - - p = procfs_file_alloca(pid, "stat"); - r = read_one_line_file(p, &line); - if (r == -ENOENT) - return -ESRCH; - if (r < 0) - return r; - - /* Let's skip the pid and comm fields. The latter is enclosed - * in () but does not escape any () in its value, so let's - * skip over it manually */ - - p = strrchr(line, ')'); - if (!p) - return -EIO; - - p++; - - if (sscanf(p, " " - "%*c " /* state */ - "%lu ", /* ppid */ - &ppid) != 1) - return -EIO; - - if ((long unsigned) (pid_t) ppid != ppid) - return -ERANGE; - - *_ppid = (pid_t) ppid; - - return 0; -} - -int wait_for_terminate(pid_t pid, siginfo_t *status) { - siginfo_t dummy; - - assert(pid >= 1); - - if (!status) - status = &dummy; - - for (;;) { - zero(*status); - - if (waitid(P_PID, pid, status, WEXITED) < 0) { - - if (errno == EINTR) - continue; - - return -errno; - } - - return 0; - } -} - -/* - * Return values: - * < 0 : wait_for_terminate() failed to get the state of the - * process, the process was terminated by a signal, or - * failed for an unknown reason. - * >=0 : The process terminated normally, and its exit code is - * returned. - * - * That is, success is indicated by a return value of zero, and an - * error is indicated by a non-zero value. - * - * A warning is emitted if the process terminates abnormally, - * and also if it returns non-zero unless check_exit_code is true. - */ -int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code) { - int r; - siginfo_t status; - - assert(name); - assert(pid > 1); - - r = wait_for_terminate(pid, &status); - if (r < 0) - return log_warning_errno(r, "Failed to wait for %s: %m", name); - - if (status.si_code == CLD_EXITED) { - if (status.si_status != 0) - log_full(check_exit_code ? LOG_WARNING : LOG_DEBUG, - "%s failed with error code %i.", name, status.si_status); - else - log_debug("%s succeeded.", name); - - return status.si_status; - } else if (status.si_code == CLD_KILLED || - status.si_code == CLD_DUMPED) { - - log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status)); - return -EPROTO; - } - - log_warning("%s failed due to unknown reason.", name); - return -EPROTO; -} - -void sigkill_wait(pid_t *pid) { - if (!pid) - return; - if (*pid <= 1) - return; - - if (kill(*pid, SIGKILL) > 0) - (void) wait_for_terminate(*pid, NULL); -} - -int kill_and_sigcont(pid_t pid, int sig) { - int r; - - r = kill(pid, sig) < 0 ? -errno : 0; - - if (r >= 0) - kill(pid, SIGCONT); - - return r; -} - -int getenv_for_pid(pid_t pid, const char *field, char **_value) { - _cleanup_fclose_ FILE *f = NULL; - char *value = NULL; - int r; - bool done = false; - size_t l; - const char *path; - - assert(pid >= 0); - assert(field); - assert(_value); - - path = procfs_file_alloca(pid, "environ"); - - f = fopen(path, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - return -errno; - } - - l = strlen(field); - r = 0; - - do { - char line[LINE_MAX]; - unsigned i; - - for (i = 0; i < sizeof(line)-1; i++) { - int c; - - c = getc(f); - if (_unlikely_(c == EOF)) { - done = true; - break; - } else if (c == 0) - break; - - line[i] = c; - } - line[i] = 0; - - if (memcmp(line, field, l) == 0 && line[l] == '=') { - value = strdup(line + l + 1); - if (!value) - return -ENOMEM; - - r = 1; - break; - } - - } while (!done); - - *_value = value; - return r; -} - -bool pid_is_unwaited(pid_t pid) { - /* Checks whether a PID is still valid at all, including a zombie */ - - if (pid < 0) - return false; - - if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */ - return true; - - if (kill(pid, 0) >= 0) - return true; - - return errno != ESRCH; -} - -bool pid_is_alive(pid_t pid) { - int r; - - /* Checks whether a PID is still valid and not a zombie */ - - if (pid < 0) - return false; - - if (pid <= 1) /* If we or PID 1 would be a zombie, this code would not be running */ - return true; - - r = get_process_state(pid); - if (r == -ESRCH || r == 'Z') - return false; - - return true; -} - -int pid_from_same_root_fs(pid_t pid) { - const char *root; - - if (pid < 0) - return 0; - - root = procfs_file_alloca(pid, "root"); - - return files_same(root, "/proc/1/root"); -} - -bool is_main_thread(void) { - static thread_local int cached = 0; - - if (_unlikely_(cached == 0)) - cached = getpid() == gettid() ? 1 : -1; - - return cached > 0; -} - -noreturn void freeze(void) { - - /* Make sure nobody waits for us on a socket anymore */ - close_all_fds(NULL, 0); - - sync(); - - for (;;) - pause(); -} - -bool oom_score_adjust_is_valid(int oa) { - return oa >= OOM_SCORE_ADJ_MIN && oa <= OOM_SCORE_ADJ_MAX; -} - -unsigned long personality_from_string(const char *p) { - - /* Parse a personality specifier. We introduce our own - * identifiers that indicate specific ABIs, rather than just - * hints regarding the register size, since we want to keep - * things open for multiple locally supported ABIs for the - * same register size. We try to reuse the ABI identifiers - * used by libseccomp. */ - -#if defined(__x86_64__) - - if (streq(p, "x86")) - return PER_LINUX32; - - if (streq(p, "x86-64")) - return PER_LINUX; - -#elif defined(__i386__) - - if (streq(p, "x86")) - return PER_LINUX; - -#elif defined(__s390x__) - - if (streq(p, "s390")) - return PER_LINUX32; - - if (streq(p, "s390x")) - return PER_LINUX; - -#elif defined(__s390__) - - if (streq(p, "s390")) - return PER_LINUX; -#endif - - return PERSONALITY_INVALID; -} - -const char* personality_to_string(unsigned long p) { - -#if defined(__x86_64__) - - if (p == PER_LINUX32) - return "x86"; - - if (p == PER_LINUX) - return "x86-64"; - -#elif defined(__i386__) - - if (p == PER_LINUX) - return "x86"; - -#elif defined(__s390x__) - - if (p == PER_LINUX) - return "s390x"; - - if (p == PER_LINUX32) - return "s390"; - -#elif defined(__s390__) - - if (p == PER_LINUX) - return "s390"; - -#endif - - return NULL; -} - -void valgrind_summary_hack(void) { -#ifdef HAVE_VALGRIND_VALGRIND_H - if (getpid() == 1 && RUNNING_ON_VALGRIND) { - pid_t pid; - pid = raw_clone(SIGCHLD, NULL); - if (pid < 0) - log_emergency_errno(errno, "Failed to fork off valgrind helper: %m"); - else if (pid == 0) - exit(EXIT_SUCCESS); - else { - log_info("Spawned valgrind helper as PID "PID_FMT".", pid); - (void) wait_for_terminate(pid, NULL); - } - } -#endif -} - -static const char *const ioprio_class_table[] = { - [IOPRIO_CLASS_NONE] = "none", - [IOPRIO_CLASS_RT] = "realtime", - [IOPRIO_CLASS_BE] = "best-effort", - [IOPRIO_CLASS_IDLE] = "idle" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, INT_MAX); - -static const char *const sigchld_code_table[] = { - [CLD_EXITED] = "exited", - [CLD_KILLED] = "killed", - [CLD_DUMPED] = "dumped", - [CLD_TRAPPED] = "trapped", - [CLD_STOPPED] = "stopped", - [CLD_CONTINUED] = "continued", -}; - -DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int); - -static const char* const sched_policy_table[] = { - [SCHED_OTHER] = "other", - [SCHED_BATCH] = "batch", - [SCHED_IDLE] = "idle", - [SCHED_FIFO] = "fifo", - [SCHED_RR] = "rr" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX); diff --git a/src/basic/process-util.h b/src/basic/process-util.h deleted file mode 100644 index f5d193e762..0000000000 --- a/src/basic/process-util.h +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "formats-util.h" -#include "macro.h" - -#define procfs_file_alloca(pid, field) \ - ({ \ - pid_t _pid_ = (pid); \ - const char *_r_; \ - if (_pid_ == 0) { \ - _r_ = ("/proc/self/" field); \ - } else { \ - _r_ = alloca(strlen("/proc/") + DECIMAL_STR_MAX(pid_t) + 1 + sizeof(field)); \ - sprintf((char*) _r_, "/proc/"PID_FMT"/" field, _pid_); \ - } \ - _r_; \ - }) - -int get_process_state(pid_t pid); -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); -int get_process_exe(pid_t pid, char **name); -int get_process_uid(pid_t pid, uid_t *uid); -int get_process_gid(pid_t pid, gid_t *gid); -int get_process_capeff(pid_t pid, char **capeff); -int get_process_cwd(pid_t pid, char **cwd); -int get_process_root(pid_t pid, char **root); -int get_process_environ(pid_t pid, char **environ); -int get_process_ppid(pid_t pid, pid_t *ppid); - -int wait_for_terminate(pid_t pid, siginfo_t *status); -int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code); - -void sigkill_wait(pid_t *pid); -#define _cleanup_sigkill_wait_ _cleanup_(sigkill_wait) - -int kill_and_sigcont(pid_t pid, int sig); - -void rename_process(const char name[8]); -int is_kernel_thread(pid_t pid); - -int getenv_for_pid(pid_t pid, const char *field, char **_value); - -bool pid_is_alive(pid_t pid); -bool pid_is_unwaited(pid_t pid); -int pid_from_same_root_fs(pid_t pid); - -bool is_main_thread(void); - -noreturn void freeze(void); - -bool oom_score_adjust_is_valid(int oa); - -#ifndef PERSONALITY_INVALID -/* personality(7) documents that 0xffffffffUL is used for querying the - * current personality, hence let's use that here as error - * indicator. */ -#define PERSONALITY_INVALID 0xffffffffLU -#endif - -unsigned long personality_from_string(const char *p); -const char *personality_to_string(unsigned long); - -int ioprio_class_to_string_alloc(int i, char **s); -int ioprio_class_from_string(const char *s); - -const char *sigchld_code_to_string(int i) _const_; -int sigchld_code_from_string(const char *s) _pure_; - -int sched_policy_to_string_alloc(int i, char **s); -int sched_policy_from_string(const char *s); - -#define PTR_TO_PID(p) ((pid_t) ((uintptr_t) p)) -#define PID_TO_PTR(p) ((void*) ((uintptr_t) p)) - -void valgrind_summary_hack(void); diff --git a/src/basic/random-util.c b/src/basic/random-util.c deleted file mode 100644 index 2f468db770..0000000000 --- a/src/basic/random-util.c +++ /dev/null @@ -1,133 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_SYS_AUXV_H -#include -#endif - -#include "fd-util.h" -#include "io-util.h" -#include "missing.h" -#include "random-util.h" -#include "time-util.h" - -int dev_urandom(void *p, size_t n) { - static int have_syscall = -1; - - _cleanup_close_ int fd = -1; - int r; - - /* Gathers some randomness from the kernel. This call will - * 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 - * of seeding the hash functions for hashtable */ - - /* Use the getrandom() syscall unless we know we don't have - * it, or when the requested size is too large for it. */ - if (have_syscall != 0 || (size_t) (int) n != n) { - r = getrandom(p, n, GRND_NONBLOCK); - if (r == (int) n) { - have_syscall = true; - return 0; - } - - if (r < 0) { - if (errno == ENOSYS) - /* we lack the syscall, continue with - * reading from /dev/urandom */ - have_syscall = false; - else if (errno == EAGAIN) - /* not enough entropy for now. Let's - * remember to use the syscall the - * next time, again, but also read - * from /dev/urandom for now, which - * doesn't care about the current - * amount of entropy. */ - have_syscall = true; - else - return -errno; - } else - /* too short read? */ - return -ENODATA; - } - - fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return errno == ENOENT ? -ENOSYS : -errno; - - return loop_read_exact(fd, p, n, true); -} - -void initialize_srand(void) { - static bool srand_called = false; - unsigned x; -#ifdef HAVE_SYS_AUXV_H - void *auxv; -#endif - - if (srand_called) - return; - -#ifdef HAVE_SYS_AUXV_H - /* The kernel provides us with 16 bytes of entropy in auxv, so let's try to make use of that to seed the - * pseudo-random generator. It's better than nothing... */ - - auxv = (void*) getauxval(AT_RANDOM); - if (auxv) { - assert_cc(sizeof(x) < 16); - memcpy(&x, auxv, sizeof(x)); - } else -#endif - x = 0; - - - x ^= (unsigned) now(CLOCK_REALTIME); - x ^= (unsigned) gettid(); - - srand(x); - srand_called = true; -} - -void random_bytes(void *p, size_t n) { - uint8_t *q; - int r; - - r = dev_urandom(p, n); - if (r >= 0) - return; - - /* If some idiot made /dev/urandom unavailable to us, he'll - * get a PRNG instead. */ - - initialize_srand(); - - for (q = p; q < (uint8_t*) p + n; q ++) - *q = rand(); -} diff --git a/src/basic/random-util.h b/src/basic/random-util.h deleted file mode 100644 index 3cee4c5014..0000000000 --- a/src/basic/random-util.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -int dev_urandom(void *p, size_t n); -void random_bytes(void *p, size_t n); -void initialize_srand(void); - -static inline uint64_t random_u64(void) { - uint64_t u; - random_bytes(&u, sizeof(u)); - return u; -} - -static inline uint32_t random_u32(void) { - uint32_t u; - random_bytes(&u, sizeof(u)); - return u; -} diff --git a/src/basic/ratelimit.c b/src/basic/ratelimit.c deleted file mode 100644 index 3ca5625e4d..0000000000 --- a/src/basic/ratelimit.c +++ /dev/null @@ -1,56 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - - -#include - -#include "macro.h" -#include "ratelimit.h" - -/* Modelled after Linux' lib/ratelimit.c by Dave Young - * , which is licensed GPLv2. */ - -bool ratelimit_test(RateLimit *r) { - usec_t ts; - - assert(r); - - if (r->interval <= 0 || r->burst <= 0) - return true; - - ts = now(CLOCK_MONOTONIC); - - if (r->begin <= 0 || - r->begin + r->interval < ts) { - r->begin = ts; - - /* Reset counter */ - r->num = 0; - goto good; - } - - if (r->num < r->burst) - goto good; - - return false; - -good: - r->num++; - return true; -} diff --git a/src/basic/ratelimit.h b/src/basic/ratelimit.h deleted file mode 100644 index 9c8dddf5ad..0000000000 --- a/src/basic/ratelimit.h +++ /dev/null @@ -1,58 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "time-util.h" -#include "util.h" - -typedef struct RateLimit { - usec_t interval; - usec_t begin; - unsigned burst; - unsigned num; -} RateLimit; - -#define RATELIMIT_DEFINE(_name, _interval, _burst) \ - RateLimit _name = { \ - .interval = (_interval), \ - .burst = (_burst), \ - .num = 0, \ - .begin = 0 \ - } - -#define RATELIMIT_INIT(v, _interval, _burst) \ - do { \ - RateLimit *_r = &(v); \ - _r->interval = (_interval); \ - _r->burst = (_burst); \ - _r->num = 0; \ - _r->begin = 0; \ - } while (false) - -#define RATELIMIT_RESET(v) \ - do { \ - RateLimit *_r = &(v); \ - _r->num = 0; \ - _r->begin = 0; \ - } while (false) - -bool ratelimit_test(RateLimit *r); diff --git a/src/basic/refcnt.h b/src/basic/refcnt.h deleted file mode 100644 index 1d77a6445a..0000000000 --- a/src/basic/refcnt.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -/* A type-safe atomic refcounter. - * - * DO NOT USE THIS UNLESS YOU ACTUALLY CARE ABOUT THREAD SAFETY! */ - -typedef struct { - volatile unsigned _value; -} RefCount; - -#define REFCNT_GET(r) ((r)._value) -#define REFCNT_INC(r) (__sync_add_and_fetch(&(r)._value, 1)) -#define REFCNT_DEC(r) (__sync_sub_and_fetch(&(r)._value, 1)) - -#define REFCNT_INIT ((RefCount) { ._value = 1 }) diff --git a/src/basic/replace-var.c b/src/basic/replace-var.c deleted file mode 100644 index 6a204b9ec3..0000000000 --- a/src/basic/replace-var.c +++ /dev/null @@ -1,112 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include -#include -#include -#include - -#include "alloc-util.h" -#include "macro.h" -#include "replace-var.h" -#include "string-util.h" - -/* - * Generic infrastructure for replacing @FOO@ style variables in - * strings. Will call a callback for each replacement. - */ - -static int get_variable(const char *b, char **r) { - size_t k; - char *t; - - assert(b); - assert(r); - - if (*b != '@') - return 0; - - k = strspn(b + 1, UPPERCASE_LETTERS "_"); - if (k <= 0 || b[k+1] != '@') - return 0; - - t = strndup(b + 1, k); - if (!t) - return -ENOMEM; - - *r = t; - return 1; -} - -char *replace_var(const char *text, char *(*lookup)(const char *variable, void*userdata), void *userdata) { - char *r, *t; - const char *f; - size_t l; - - assert(text); - assert(lookup); - - l = strlen(text); - r = new(char, l+1); - if (!r) - return NULL; - - f = text; - t = r; - while (*f) { - _cleanup_free_ char *v = NULL, *n = NULL; - char *a; - int k; - size_t skip, d, nl; - - k = get_variable(f, &v); - if (k < 0) - goto oom; - if (k == 0) { - *(t++) = *(f++); - continue; - } - - n = lookup(v, userdata); - if (!n) - goto oom; - - skip = strlen(v) + 2; - - d = t - r; - nl = l - skip + strlen(n); - a = realloc(r, nl + 1); - if (!a) - goto oom; - - l = nl; - r = a; - t = r + d; - - t = stpcpy(t, n); - f += skip; - } - - *t = 0; - return r; - -oom: - free(r); - return NULL; -} diff --git a/src/basic/replace-var.h b/src/basic/replace-var.h deleted file mode 100644 index 78412910b2..0000000000 --- a/src/basic/replace-var.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -char *replace_var(const char *text, char *(*lookup)(const char *variable, void*userdata), void *userdata); diff --git a/src/basic/rlimit-util.c b/src/basic/rlimit-util.c deleted file mode 100644 index 7540b43215..0000000000 --- a/src/basic/rlimit-util.c +++ /dev/null @@ -1,271 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "alloc-util.h" -#include "extract-word.h" -#include "formats-util.h" -#include "macro.h" -#include "missing.h" -#include "rlimit-util.h" -#include "string-table.h" -#include "time-util.h" - -int setrlimit_closest(int resource, const struct rlimit *rlim) { - struct rlimit highest, fixed; - - assert(rlim); - - if (setrlimit(resource, rlim) >= 0) - return 0; - - if (errno != EPERM) - return -errno; - - /* So we failed to set the desired setrlimit, then let's try - * to get as close as we can */ - assert_se(getrlimit(resource, &highest) == 0); - - fixed.rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max); - fixed.rlim_max = MIN(rlim->rlim_max, highest.rlim_max); - - if (setrlimit(resource, &fixed) < 0) - return -errno; - - return 0; -} - -static int rlimit_parse_u64(const char *val, rlim_t *ret) { - uint64_t u; - int r; - - assert(val); - assert(ret); - - if (streq(val, "infinity")) { - *ret = RLIM_INFINITY; - return 0; - } - - /* setrlimit(2) suggests rlim_t is always 64bit on Linux. */ - assert_cc(sizeof(rlim_t) == sizeof(uint64_t)); - - r = safe_atou64(val, &u); - if (r < 0) - return r; - if (u >= (uint64_t) RLIM_INFINITY) - return -ERANGE; - - *ret = (rlim_t) u; - return 0; -} - -static int rlimit_parse_size(const char *val, rlim_t *ret) { - uint64_t u; - int r; - - assert(val); - assert(ret); - - if (streq(val, "infinity")) { - *ret = RLIM_INFINITY; - return 0; - } - - r = parse_size(val, 1024, &u); - if (r < 0) - return r; - if (u >= (uint64_t) RLIM_INFINITY) - return -ERANGE; - - *ret = (rlim_t) u; - return 0; -} - -static int rlimit_parse_sec(const char *val, rlim_t *ret) { - uint64_t u; - usec_t t; - int r; - - assert(val); - assert(ret); - - if (streq(val, "infinity")) { - *ret = RLIM_INFINITY; - return 0; - } - - r = parse_sec(val, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) { - *ret = RLIM_INFINITY; - return 0; - } - - u = (uint64_t) DIV_ROUND_UP(t, USEC_PER_SEC); - if (u >= (uint64_t) RLIM_INFINITY) - return -ERANGE; - - *ret = (rlim_t) u; - return 0; -} - -static int rlimit_parse_usec(const char *val, rlim_t *ret) { - usec_t t; - int r; - - assert(val); - assert(ret); - - if (streq(val, "infinity")) { - *ret = RLIM_INFINITY; - return 0; - } - - r = parse_time(val, &t, 1); - if (r < 0) - return r; - if (t == USEC_INFINITY) { - *ret = RLIM_INFINITY; - return 0; - } - - *ret = (rlim_t) t; - return 0; -} - -static int (*const rlimit_parse_table[_RLIMIT_MAX])(const char *val, rlim_t *ret) = { - [RLIMIT_CPU] = rlimit_parse_sec, - [RLIMIT_FSIZE] = rlimit_parse_size, - [RLIMIT_DATA] = rlimit_parse_size, - [RLIMIT_STACK] = rlimit_parse_size, - [RLIMIT_CORE] = rlimit_parse_size, - [RLIMIT_RSS] = rlimit_parse_size, - [RLIMIT_NOFILE] = rlimit_parse_u64, - [RLIMIT_AS] = rlimit_parse_size, - [RLIMIT_NPROC] = rlimit_parse_u64, - [RLIMIT_MEMLOCK] = rlimit_parse_size, - [RLIMIT_LOCKS] = rlimit_parse_u64, - [RLIMIT_SIGPENDING] = rlimit_parse_u64, - [RLIMIT_MSGQUEUE] = rlimit_parse_size, - [RLIMIT_NICE] = rlimit_parse_u64, - [RLIMIT_RTPRIO] = rlimit_parse_u64, - [RLIMIT_RTTIME] = rlimit_parse_usec, -}; - -int rlimit_parse_one(int resource, const char *val, rlim_t *ret) { - assert(val); - assert(ret); - - if (resource < 0) - return -EINVAL; - if (resource >= _RLIMIT_MAX) - return -EINVAL; - - return rlimit_parse_table[resource](val, ret); -} - -int rlimit_parse(int resource, const char *val, struct rlimit *ret) { - _cleanup_free_ char *hard = NULL, *soft = NULL; - rlim_t hl, sl; - int r; - - assert(val); - assert(ret); - - r = extract_first_word(&val, &soft, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - r = rlimit_parse_one(resource, soft, &sl); - if (r < 0) - return r; - - r = extract_first_word(&val, &hard, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (!isempty(val)) - return -EINVAL; - if (r == 0) - hl = sl; - else { - r = rlimit_parse_one(resource, hard, &hl); - if (r < 0) - return r; - if (sl > hl) - return -EILSEQ; - } - - *ret = (struct rlimit) { - .rlim_cur = sl, - .rlim_max = hl, - }; - - return 0; -} - -int rlimit_format(const struct rlimit *rl, char **ret) { - char *s = NULL; - - assert(rl); - assert(ret); - - if (rl->rlim_cur >= RLIM_INFINITY && rl->rlim_max >= RLIM_INFINITY) - s = strdup("infinity"); - else if (rl->rlim_cur >= RLIM_INFINITY) - (void) asprintf(&s, "infinity:" RLIM_FMT, rl->rlim_max); - else if (rl->rlim_max >= RLIM_INFINITY) - (void) asprintf(&s, RLIM_FMT ":infinity", rl->rlim_cur); - else if (rl->rlim_cur == rl->rlim_max) - (void) asprintf(&s, RLIM_FMT, rl->rlim_cur); - else - (void) asprintf(&s, RLIM_FMT ":" RLIM_FMT, rl->rlim_cur, rl->rlim_max); - - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -static const char* const rlimit_table[_RLIMIT_MAX] = { - [RLIMIT_CPU] = "LimitCPU", - [RLIMIT_FSIZE] = "LimitFSIZE", - [RLIMIT_DATA] = "LimitDATA", - [RLIMIT_STACK] = "LimitSTACK", - [RLIMIT_CORE] = "LimitCORE", - [RLIMIT_RSS] = "LimitRSS", - [RLIMIT_NOFILE] = "LimitNOFILE", - [RLIMIT_AS] = "LimitAS", - [RLIMIT_NPROC] = "LimitNPROC", - [RLIMIT_MEMLOCK] = "LimitMEMLOCK", - [RLIMIT_LOCKS] = "LimitLOCKS", - [RLIMIT_SIGPENDING] = "LimitSIGPENDING", - [RLIMIT_MSGQUEUE] = "LimitMSGQUEUE", - [RLIMIT_NICE] = "LimitNICE", - [RLIMIT_RTPRIO] = "LimitRTPRIO", - [RLIMIT_RTTIME] = "LimitRTTIME" -}; - -DEFINE_STRING_TABLE_LOOKUP(rlimit, int); diff --git a/src/basic/rlimit-util.h b/src/basic/rlimit-util.h deleted file mode 100644 index d4594eccd6..0000000000 --- a/src/basic/rlimit-util.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "macro.h" - -const char *rlimit_to_string(int i) _const_; -int rlimit_from_string(const char *s) _pure_; - -int setrlimit_closest(int resource, const struct rlimit *rlim); - -int rlimit_parse_one(int resource, const char *val, rlim_t *ret); -int rlimit_parse(int resource, const char *val, struct rlimit *ret); - -int rlimit_format(const struct rlimit *rl, char **ret); - -#define RLIMIT_MAKE_CONST(lim) ((struct rlimit) { lim, lim }) diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c deleted file mode 100644 index 43816fd1bb..0000000000 --- a/src/basic/rm-rf.c +++ /dev/null @@ -1,236 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "btrfs-util.h" -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "mount-util.h" -#include "path-util.h" -#include "rm-rf.h" -#include "stat-util.h" -#include "string-util.h" - -int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { - _cleanup_closedir_ DIR *d = NULL; - int ret = 0, r; - - assert(fd >= 0); - - /* This returns the first error we run into, but nevertheless - * tries to go on. This closes the passed fd. */ - - if (!(flags & REMOVE_PHYSICAL)) { - - r = fd_is_temporary_fs(fd); - if (r < 0) { - safe_close(fd); - return r; - } - - if (!r) { - /* We refuse to clean physical file systems - * with this call, unless explicitly - * requested. This is extra paranoia just to - * be sure we never ever remove non-state - * data */ - - log_error("Attempted to remove disk file system, and we can't allow that."); - safe_close(fd); - return -EPERM; - } - } - - d = fdopendir(fd); - if (!d) { - safe_close(fd); - return errno == ENOENT ? 0 : -errno; - } - - for (;;) { - struct dirent *de; - bool is_dir; - struct stat st; - - errno = 0; - de = readdir(d); - if (!de) { - if (errno > 0 && ret == 0) - ret = -errno; - return ret; - } - - if (streq(de->d_name, ".") || streq(de->d_name, "..")) - continue; - - if (de->d_type == DT_UNKNOWN || - (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) { - if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - continue; - } - - is_dir = S_ISDIR(st.st_mode); - } else - is_dir = de->d_type == DT_DIR; - - if (is_dir) { - int subdir_fd; - - /* if root_dev is set, remove subdirectories only if device is same */ - if (root_dev && st.st_dev != root_dev->st_dev) - continue; - - subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); - if (subdir_fd < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - continue; - } - - /* Stop at mount points */ - r = fd_is_mount_point(fd, de->d_name, 0); - if (r < 0) { - if (ret == 0 && r != -ENOENT) - ret = r; - - safe_close(subdir_fd); - continue; - } - if (r) { - safe_close(subdir_fd); - continue; - } - - if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) { - - /* This could be a subvolume, try to remove it */ - - r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); - if (r < 0) { - if (r != -ENOTTY && r != -EINVAL) { - if (ret == 0) - ret = r; - - safe_close(subdir_fd); - continue; - } - - /* ENOTTY, then it wasn't a - * btrfs subvolume, continue - * below. */ - } else { - /* It was a subvolume, continue. */ - safe_close(subdir_fd); - continue; - } - } - - /* We pass REMOVE_PHYSICAL here, to avoid - * doing the fstatfs() to check the file - * system type again for each directory */ - r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev); - if (r < 0 && ret == 0) - ret = r; - - if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - } - - } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { - - if (unlinkat(fd, de->d_name, 0) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - } - } - } -} - -int rm_rf(const char *path, RemoveFlags flags) { - int fd, r; - struct statfs s; - - assert(path); - - /* We refuse to clean the root file system with this - * call. This is extra paranoia to never cause a really - * seriously broken system. */ - if (path_equal(path, "/")) { - log_error("Attempted to remove entire root file system, and we can't allow that."); - return -EPERM; - } - - if ((flags & (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) == (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) { - /* Try to remove as subvolume first */ - r = btrfs_subvol_remove(path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); - if (r >= 0) - return r; - - if (r != -ENOTTY && r != -EINVAL && r != -ENOTDIR) - return r; - - /* Not btrfs or not a subvolume */ - } - - fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); - if (fd < 0) { - - if (errno != ENOTDIR && errno != ELOOP) - return -errno; - - if (!(flags & REMOVE_PHYSICAL)) { - if (statfs(path, &s) < 0) - return -errno; - - if (!is_temporary_fs(&s)) { - log_error("Attempted to remove disk file system, and we can't allow that."); - return -EPERM; - } - } - - if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES)) - if (unlink(path) < 0 && errno != ENOENT) - return -errno; - - return 0; - } - - r = rm_rf_children(fd, flags, NULL); - - if (flags & REMOVE_ROOT) { - if (rmdir(path) < 0) { - if (r == 0 && errno != ENOENT) - r = -errno; - } - } - - return r; -} diff --git a/src/basic/rm-rf.h b/src/basic/rm-rf.h deleted file mode 100644 index 6d03268919..0000000000 --- a/src/basic/rm-rf.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include - -typedef enum RemoveFlags { - REMOVE_ONLY_DIRECTORIES = 1, - REMOVE_ROOT = 2, - REMOVE_PHYSICAL = 4, /* if not set, only removes files on tmpfs, never physical file systems */ - REMOVE_SUBVOLUME = 8, -} RemoveFlags; - -int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev); -int rm_rf(const char *path, RemoveFlags flags); diff --git a/src/basic/securebits.h b/src/basic/securebits.h deleted file mode 100644 index 98fbe0d433..0000000000 --- a/src/basic/securebits.h +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef _LINUX_SECUREBITS_H -#define _LINUX_SECUREBITS_H 1 - -/* This is minimal version of Linux' linux/securebits.h header file, - * which is licensed GPL2 */ - -#define SECUREBITS_DEFAULT 0x00000000 - -/* When set UID 0 has no special privileges. When unset, we support - inheritance of root-permissions and suid-root executable under - compatibility mode. We raise the effective and inheritable bitmasks - *of the executable file* if the effective uid of the new process is - 0. If the real uid is 0, we raise the effective (legacy) bit of the - executable file. */ -#define SECURE_NOROOT 0 -#define SECURE_NOROOT_LOCKED 1 /* make bit-0 immutable */ - -/* When set, setuid to/from uid 0 does not trigger capability-"fixup". - When unset, to provide compatibility with old programs relying on - set*uid to gain/lose privilege, transitions to/from uid 0 cause - capabilities to be gained/lost. */ -#define SECURE_NO_SETUID_FIXUP 2 -#define SECURE_NO_SETUID_FIXUP_LOCKED 3 /* make bit-2 immutable */ - -/* When set, a process can retain its capabilities even after - transitioning to a non-root user (the set-uid fixup suppressed by - bit 2). Bit-4 is cleared when a process calls exec(); setting both - bit 4 and 5 will create a barrier through exec that no exec()'d - child can use this feature again. */ -#define SECURE_KEEP_CAPS 4 -#define SECURE_KEEP_CAPS_LOCKED 5 /* make bit-4 immutable */ - -/* Each securesetting is implemented using two bits. One bit specifies - whether the setting is on or off. The other bit specify whether the - setting is locked or not. A setting which is locked cannot be - changed from user-level. */ -#define issecure_mask(X) (1 << (X)) -#define issecure(X) (issecure_mask(X) & current_cred_xxx(securebits)) - -#define SECURE_ALL_BITS (issecure_mask(SECURE_NOROOT) | \ - issecure_mask(SECURE_NO_SETUID_FIXUP) | \ - issecure_mask(SECURE_KEEP_CAPS)) -#define SECURE_ALL_LOCKS (SECURE_ALL_BITS << 1) - -#endif /* !_LINUX_SECUREBITS_H */ diff --git a/src/basic/selinux-util.c b/src/basic/selinux-util.c deleted file mode 100644 index 6c63b9d652..0000000000 --- a/src/basic/selinux-util.c +++ /dev/null @@ -1,493 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_SELINUX -#include -#include -#include -#endif - -#include "alloc-util.h" -#include "log.h" -#include "macro.h" -#include "path-util.h" -#include "selinux-util.h" -#include "time-util.h" -#include "util.h" - -#ifdef HAVE_SELINUX -DEFINE_TRIVIAL_CLEANUP_FUNC(security_context_t, freecon); -DEFINE_TRIVIAL_CLEANUP_FUNC(context_t, context_free); - -#define _cleanup_security_context_free_ _cleanup_(freeconp) -#define _cleanup_context_free_ _cleanup_(context_freep) - -static int cached_use = -1; -static struct selabel_handle *label_hnd = NULL; - -#define log_enforcing(...) log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG, __VA_ARGS__) -#endif - -bool mac_selinux_have(void) { -#ifdef HAVE_SELINUX - if (cached_use < 0) - cached_use = is_selinux_enabled() > 0; - - return cached_use; -#else - return false; -#endif -} - -bool mac_selinux_use(void) { - if (!mac_selinux_have()) - return false; - - /* Never try to configure SELinux features if we aren't - * root */ - - return getuid() == 0; -} - -void mac_selinux_retest(void) { -#ifdef HAVE_SELINUX - cached_use = -1; -#endif -} - -int mac_selinux_init(const char *prefix) { - int r = 0; - -#ifdef HAVE_SELINUX - usec_t before_timestamp, after_timestamp; - struct mallinfo before_mallinfo, after_mallinfo; - - if (!mac_selinux_use()) - return 0; - - if (label_hnd) - return 0; - - before_mallinfo = mallinfo(); - before_timestamp = now(CLOCK_MONOTONIC); - - if (prefix) { - struct selinux_opt options[] = { - { .type = SELABEL_OPT_SUBSET, .value = prefix }, - }; - - label_hnd = selabel_open(SELABEL_CTX_FILE, options, ELEMENTSOF(options)); - } else - label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0); - - if (!label_hnd) { - log_enforcing("Failed to initialize SELinux context: %m"); - r = security_getenforce() == 1 ? -errno : 0; - } else { - char timespan[FORMAT_TIMESPAN_MAX]; - int l; - - after_timestamp = now(CLOCK_MONOTONIC); - after_mallinfo = mallinfo(); - - l = after_mallinfo.uordblks > before_mallinfo.uordblks ? after_mallinfo.uordblks - before_mallinfo.uordblks : 0; - - log_debug("Successfully loaded SELinux database in %s, size on heap is %iK.", - format_timespan(timespan, sizeof(timespan), after_timestamp - before_timestamp, 0), - (l+1023)/1024); - } -#endif - - return r; -} - -void mac_selinux_finish(void) { - -#ifdef HAVE_SELINUX - if (!label_hnd) - return; - - selabel_close(label_hnd); - label_hnd = NULL; -#endif -} - -int mac_selinux_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { - -#ifdef HAVE_SELINUX - struct stat st; - int r; - - assert(path); - - /* if mac_selinux_init() wasn't called before we are a NOOP */ - if (!label_hnd) - return 0; - - r = lstat(path, &st); - if (r >= 0) { - _cleanup_security_context_free_ security_context_t fcon = NULL; - - r = selabel_lookup_raw(label_hnd, &fcon, path, st.st_mode); - - /* If there's no label to set, then exit without warning */ - if (r < 0 && errno == ENOENT) - return 0; - - if (r >= 0) { - r = lsetfilecon(path, fcon); - - /* If the FS doesn't support labels, then exit without warning */ - if (r < 0 && errno == EOPNOTSUPP) - return 0; - } - } - - if (r < 0) { - /* Ignore ENOENT in some cases */ - if (ignore_enoent && errno == ENOENT) - return 0; - - if (ignore_erofs && errno == EROFS) - return 0; - - log_enforcing("Unable to fix SELinux security context of %s: %m", path); - if (security_getenforce() == 1) - return -errno; - } -#endif - - return 0; -} - -int mac_selinux_apply(const char *path, const char *label) { - -#ifdef HAVE_SELINUX - if (!mac_selinux_use()) - return 0; - - assert(path); - assert(label); - - if (setfilecon(path, (security_context_t) label) < 0) { - log_enforcing("Failed to set SELinux security context %s on path %s: %m", label, path); - if (security_getenforce() > 0) - return -errno; - } -#endif - return 0; -} - -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; - security_class_t sclass; - - assert(exe); - assert(label); - - if (!mac_selinux_have()) - return -EOPNOTSUPP; - - r = getcon_raw(&mycon); - if (r < 0) - return -errno; - - r = getfilecon_raw(exe, &fcon); - if (r < 0) - return -errno; - - sclass = string_to_security_class("process"); - r = security_compute_create(mycon, fcon, sclass, (security_context_t *) label); - if (r < 0) - return -errno; -#endif - - return r; -} - -int mac_selinux_get_our_label(char **label) { - int r = -EOPNOTSUPP; - - assert(label); - -#ifdef HAVE_SELINUX - if (!mac_selinux_have()) - return -EOPNOTSUPP; - - r = getcon_raw(label); - if (r < 0) - return -errno; -#endif - - return r; -} - -int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *exec_label, char **label) { - int r = -EOPNOTSUPP; - -#ifdef HAVE_SELINUX - _cleanup_security_context_free_ security_context_t mycon = NULL, peercon = NULL, fcon = NULL; - _cleanup_context_free_ context_t pcon = NULL, bcon = NULL; - security_class_t sclass; - const char *range = NULL; - - assert(socket_fd >= 0); - assert(exe); - assert(label); - - if (!mac_selinux_have()) - return -EOPNOTSUPP; - - r = getcon_raw(&mycon); - if (r < 0) - return -errno; - - r = getpeercon(socket_fd, &peercon); - if (r < 0) - return -errno; - - if (!exec_label) { - /* If there is no context set for next exec let's use context - of target executable */ - r = getfilecon_raw(exe, &fcon); - if (r < 0) - return -errno; - } - - bcon = context_new(mycon); - if (!bcon) - return -ENOMEM; - - pcon = context_new(peercon); - if (!pcon) - return -ENOMEM; - - range = context_range_get(pcon); - if (!range) - return -errno; - - r = context_range_set(bcon, range); - if (r) - return -errno; - - freecon(mycon); - mycon = strdup(context_str(bcon)); - if (!mycon) - return -ENOMEM; - - sclass = string_to_security_class("process"); - r = security_compute_create(mycon, fcon, sclass, (security_context_t *) label); - if (r < 0) - return -errno; -#endif - - return r; -} - -char* mac_selinux_free(char *label) { - -#ifdef HAVE_SELINUX - if (!label) - return NULL; - - if (!mac_selinux_have()) - return NULL; - - - freecon((security_context_t) label); -#endif - - return NULL; -} - -int mac_selinux_create_file_prepare(const char *path, mode_t mode) { - -#ifdef HAVE_SELINUX - _cleanup_security_context_free_ security_context_t filecon = NULL; - int r; - - assert(path); - - if (!label_hnd) - return 0; - - if (path_is_absolute(path)) - r = selabel_lookup_raw(label_hnd, &filecon, path, mode); - else { - _cleanup_free_ char *newpath = NULL; - - r = path_make_absolute_cwd(path, &newpath); - if (r < 0) - return r; - - r = selabel_lookup_raw(label_hnd, &filecon, newpath, mode); - } - - if (r < 0) { - /* No context specified by the policy? Proceed without setting it. */ - if (errno == ENOENT) - return 0; - - log_enforcing("Failed to determine SELinux security context for %s: %m", path); - } else { - if (setfscreatecon(filecon) >= 0) - return 0; /* Success! */ - - log_enforcing("Failed to set SELinux security context %s for %s: %m", filecon, path); - } - - if (security_getenforce() > 0) - return -errno; - -#endif - return 0; -} - -void mac_selinux_create_file_clear(void) { - -#ifdef HAVE_SELINUX - PROTECT_ERRNO; - - if (!mac_selinux_use()) - return; - - setfscreatecon(NULL); -#endif -} - -int mac_selinux_create_socket_prepare(const char *label) { - -#ifdef HAVE_SELINUX - if (!mac_selinux_use()) - return 0; - - assert(label); - - if (setsockcreatecon((security_context_t) label) < 0) { - log_enforcing("Failed to set SELinux security context %s for sockets: %m", label); - - if (security_getenforce() == 1) - return -errno; - } -#endif - - return 0; -} - -void mac_selinux_create_socket_clear(void) { - -#ifdef HAVE_SELINUX - PROTECT_ERRNO; - - if (!mac_selinux_use()) - return; - - setsockcreatecon(NULL); -#endif -} - -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; - const struct sockaddr_un *un; - bool context_changed = false; - char *path; - int r; - - assert(fd >= 0); - assert(addr); - assert(addrlen >= sizeof(sa_family_t)); - - if (!label_hnd) - goto skipped; - - /* Filter out non-local sockets */ - if (addr->sa_family != AF_UNIX) - goto skipped; - - /* Filter out anonymous sockets */ - if (addrlen < offsetof(struct sockaddr_un, sun_path) + 1) - goto skipped; - - /* Filter out abstract namespace sockets */ - un = (const struct sockaddr_un*) addr; - if (un->sun_path[0] == 0) - goto skipped; - - path = strndupa(un->sun_path, addrlen - offsetof(struct sockaddr_un, sun_path)); - - if (path_is_absolute(path)) - r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFSOCK); - else { - _cleanup_free_ char *newpath = NULL; - - r = path_make_absolute_cwd(path, &newpath); - if (r < 0) - return r; - - r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFSOCK); - } - - if (r < 0) { - /* No context specified by the policy? Proceed without setting it */ - if (errno == ENOENT) - goto skipped; - - log_enforcing("Failed to determine SELinux security context for %s: %m", path); - if (security_getenforce() > 0) - return -errno; - - } else { - if (setfscreatecon(fcon) < 0) { - log_enforcing("Failed to set SELinux security context %s for %s: %m", fcon, path); - if (security_getenforce() > 0) - return -errno; - } else - context_changed = true; - } - - r = bind(fd, addr, addrlen) < 0 ? -errno : 0; - - if (context_changed) - setfscreatecon(NULL); - - return r; - -skipped: -#endif - if (bind(fd, addr, addrlen) < 0) - return -errno; - - return 0; -} diff --git a/src/basic/selinux-util.h b/src/basic/selinux-util.h deleted file mode 100644 index 27e8edb41b..0000000000 --- a/src/basic/selinux-util.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#include "macro.h" - -bool mac_selinux_use(void); -bool mac_selinux_have(void); -void mac_selinux_retest(void); - -int mac_selinux_init(const char *prefix); -void mac_selinux_finish(void); - -int mac_selinux_fix(const char *path, bool ignore_enoent, bool ignore_erofs); -int mac_selinux_apply(const char *path, const char *label); - -int mac_selinux_get_create_label_from_exe(const char *exe, char **label); -int mac_selinux_get_our_label(char **label); -int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *exec_label, char **label); -char* mac_selinux_free(char *label); - -int mac_selinux_create_file_prepare(const char *path, mode_t mode); -void mac_selinux_create_file_clear(void); - -int mac_selinux_create_socket_prepare(const char *label); -void mac_selinux_create_socket_clear(void); - -int mac_selinux_bind(int fd, const struct sockaddr *addr, socklen_t addrlen); - -DEFINE_TRIVIAL_CLEANUP_FUNC(char*, mac_selinux_free); diff --git a/src/basic/set.h b/src/basic/set.h deleted file mode 100644 index 2bff5062da..0000000000 --- a/src/basic/set.h +++ /dev/null @@ -1,133 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "hashmap.h" -#include "macro.h" - -Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -#define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS) - -static inline Set *set_free(Set *s) { - internal_hashmap_free(HASHMAP_BASE(s)); - return NULL; -} - -static inline Set *set_free_free(Set *s) { - internal_hashmap_free_free(HASHMAP_BASE(s)); - return NULL; -} - -/* no set_free_free_free */ - -static inline Set *set_copy(Set *s) { - return (Set*) internal_hashmap_copy(HASHMAP_BASE(s)); -} - -int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); -#define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) - -int set_put(Set *s, const void *key); -/* no set_update */ -/* no set_replace */ -static inline void *set_get(Set *s, void *key) { - return internal_hashmap_get(HASHMAP_BASE(s), key); -} -/* no set_get2 */ - -static inline bool set_contains(Set *s, const void *key) { - return internal_hashmap_contains(HASHMAP_BASE(s), key); -} - -static inline void *set_remove(Set *s, const void *key) { - return internal_hashmap_remove(HASHMAP_BASE(s), key); -} - -/* no set_remove2 */ -/* no set_remove_value */ -int set_remove_and_put(Set *s, const void *old_key, const void *new_key); -/* no set_remove_and_replace */ -int set_merge(Set *s, Set *other); - -static inline int set_reserve(Set *h, unsigned entries_add) { - return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); -} - -static inline int set_move(Set *s, Set *other) { - return internal_hashmap_move(HASHMAP_BASE(s), HASHMAP_BASE(other)); -} - -static inline int set_move_one(Set *s, Set *other, const void *key) { - return internal_hashmap_move_one(HASHMAP_BASE(s), HASHMAP_BASE(other), key); -} - -static inline unsigned set_size(Set *s) { - return internal_hashmap_size(HASHMAP_BASE(s)); -} - -static inline bool set_isempty(Set *s) { - return set_size(s) == 0; -} - -static inline unsigned set_buckets(Set *s) { - return internal_hashmap_buckets(HASHMAP_BASE(s)); -} - -bool set_iterate(Set *s, Iterator *i, void **value); - -static inline void set_clear(Set *s) { - internal_hashmap_clear(HASHMAP_BASE(s)); -} - -static inline void set_clear_free(Set *s) { - internal_hashmap_clear_free(HASHMAP_BASE(s)); -} - -/* no set_clear_free_free */ - -static inline void *set_steal_first(Set *s) { - return internal_hashmap_steal_first(HASHMAP_BASE(s)); -} - -/* no set_steal_first_key */ -/* no set_first_key */ - -static inline void *set_first(Set *s) { - return internal_hashmap_first(HASHMAP_BASE(s)); -} - -/* no set_next */ - -static inline char **set_get_strv(Set *s) { - return internal_hashmap_get_strv(HASHMAP_BASE(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); - -#define SET_FOREACH(e, s, i) \ - for ((i) = ITERATOR_FIRST; set_iterate((s), &(i), (void**)&(e)); ) - -DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free); -DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free); - -#define _cleanup_set_free_ _cleanup_(set_freep) -#define _cleanup_set_free_free_ _cleanup_(set_free_freep) diff --git a/src/basic/sigbus.c b/src/basic/sigbus.c deleted file mode 100644 index 0ce4f75684..0000000000 --- a/src/basic/sigbus.c +++ /dev/null @@ -1,152 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include - -#include "macro.h" -#include "sigbus.h" -#include "util.h" - -#define SIGBUS_QUEUE_MAX 64 - -static struct sigaction old_sigaction; -static unsigned n_installed = 0; - -/* We maintain a fixed size list of page addresses that triggered a - SIGBUS. We access with list with atomic operations, so that we - don't have to deal with locks between signal handler and main - programs in possibly multiple threads. */ - -static void* volatile sigbus_queue[SIGBUS_QUEUE_MAX]; -static volatile sig_atomic_t n_sigbus_queue = 0; - -static void sigbus_push(void *addr) { - unsigned u; - - assert(addr); - - /* Find a free place, increase the number of entries and leave, if we can */ - for (u = 0; u < SIGBUS_QUEUE_MAX; u++) - if (__sync_bool_compare_and_swap(&sigbus_queue[u], NULL, addr)) { - __sync_fetch_and_add(&n_sigbus_queue, 1); - return; - } - - /* If we can't, make sure the queue size is out of bounds, to - * mark it as overflow */ - for (;;) { - unsigned c; - - __sync_synchronize(); - c = n_sigbus_queue; - - if (c > SIGBUS_QUEUE_MAX) /* already overflow */ - return; - - if (__sync_bool_compare_and_swap(&n_sigbus_queue, c, c + SIGBUS_QUEUE_MAX)) - return; - } -} - -int sigbus_pop(void **ret) { - assert(ret); - - for (;;) { - unsigned u, c; - - __sync_synchronize(); - c = n_sigbus_queue; - - if (_likely_(c == 0)) - return 0; - - if (_unlikely_(c >= SIGBUS_QUEUE_MAX)) - return -EOVERFLOW; - - for (u = 0; u < SIGBUS_QUEUE_MAX; u++) { - void *addr; - - addr = sigbus_queue[u]; - if (!addr) - continue; - - if (__sync_bool_compare_and_swap(&sigbus_queue[u], addr, NULL)) { - __sync_fetch_and_sub(&n_sigbus_queue, 1); - *ret = addr; - return 1; - } - } - } -} - -static void sigbus_handler(int sn, siginfo_t *si, void *data) { - unsigned long ul; - void *aligned; - - assert(sn == SIGBUS); - assert(si); - - if (si->si_code != BUS_ADRERR || !si->si_addr) { - assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0); - raise(SIGBUS); - return; - } - - ul = (unsigned long) si->si_addr; - ul = ul / page_size(); - ul = ul * page_size(); - aligned = (void*) ul; - - /* Let's remember which address failed */ - sigbus_push(aligned); - - /* Replace mapping with an anonymous page, so that the - * execution can continue, however with a zeroed out page */ - assert_se(mmap(aligned, page_size(), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) == aligned); -} - -void sigbus_install(void) { - struct sigaction sa = { - .sa_sigaction = sigbus_handler, - .sa_flags = SA_SIGINFO, - }; - - n_installed++; - - if (n_installed == 1) - assert_se(sigaction(SIGBUS, &sa, &old_sigaction) == 0); - - return; -} - -void sigbus_reset(void) { - - if (n_installed <= 0) - return; - - n_installed--; - - if (n_installed == 0) - assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0); - - return; -} diff --git a/src/basic/sigbus.h b/src/basic/sigbus.h deleted file mode 100644 index cce9eb201b..0000000000 --- a/src/basic/sigbus.h +++ /dev/null @@ -1,25 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#pragma once - -void sigbus_install(void); -void sigbus_reset(void); - -int sigbus_pop(void **ret); diff --git a/src/basic/signal-util.c b/src/basic/signal-util.c deleted file mode 100644 index e3047b209b..0000000000 --- a/src/basic/signal-util.c +++ /dev/null @@ -1,278 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include -#include - -#include "macro.h" -#include "parse-util.h" -#include "signal-util.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" - -int reset_all_signal_handlers(void) { - static const struct sigaction sa = { - .sa_handler = SIG_DFL, - .sa_flags = SA_RESTART, - }; - int sig, r = 0; - - for (sig = 1; sig < _NSIG; sig++) { - - /* These two cannot be caught... */ - if (sig == SIGKILL || sig == SIGSTOP) - continue; - - /* On Linux the first two RT signals are reserved by - * glibc, and sigaction() will return EINVAL for them. */ - if ((sigaction(sig, &sa, NULL) < 0)) - if (errno != EINVAL && r >= 0) - r = -errno; - } - - return r; -} - -int reset_signal_mask(void) { - sigset_t ss; - - if (sigemptyset(&ss) < 0) - return -errno; - - if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0) - return -errno; - - return 0; -} - -static int sigaction_many_ap(const struct sigaction *sa, int sig, va_list ap) { - int r = 0; - - /* negative signal ends the list. 0 signal is skipped. */ - - if (sig < 0) - return 0; - - if (sig > 0) { - if (sigaction(sig, sa, NULL) < 0) - r = -errno; - } - - while ((sig = va_arg(ap, int)) >= 0) { - - if (sig == 0) - continue; - - if (sigaction(sig, sa, NULL) < 0) { - if (r >= 0) - r = -errno; - } - } - - return r; -} - -int sigaction_many(const struct sigaction *sa, ...) { - va_list ap; - int r; - - va_start(ap, sa); - r = sigaction_many_ap(sa, 0, ap); - va_end(ap); - - return r; -} - -int ignore_signals(int sig, ...) { - - static const struct sigaction sa = { - .sa_handler = SIG_IGN, - .sa_flags = SA_RESTART, - }; - - va_list ap; - int r; - - va_start(ap, sig); - r = sigaction_many_ap(&sa, sig, ap); - va_end(ap); - - return r; -} - -int default_signals(int sig, ...) { - - static const struct sigaction sa = { - .sa_handler = SIG_DFL, - .sa_flags = SA_RESTART, - }; - - va_list ap; - int r; - - va_start(ap, sig); - r = sigaction_many_ap(&sa, sig, ap); - va_end(ap); - - return r; -} - -static int sigset_add_many_ap(sigset_t *ss, va_list ap) { - int sig, r = 0; - - assert(ss); - - while ((sig = va_arg(ap, int)) >= 0) { - - if (sig == 0) - continue; - - if (sigaddset(ss, sig) < 0) { - if (r >= 0) - r = -errno; - } - } - - return r; -} - -int sigset_add_many(sigset_t *ss, ...) { - va_list ap; - int r; - - va_start(ap, ss); - r = sigset_add_many_ap(ss, ap); - va_end(ap); - - return r; -} - -int sigprocmask_many(int how, sigset_t *old, ...) { - va_list ap; - sigset_t ss; - int r; - - if (sigemptyset(&ss) < 0) - return -errno; - - va_start(ap, old); - r = sigset_add_many_ap(&ss, ap); - va_end(ap); - - if (r < 0) - return r; - - if (sigprocmask(how, &ss, old) < 0) - return -errno; - - return 0; -} - -static const char *const __signal_table[] = { - [SIGHUP] = "HUP", - [SIGINT] = "INT", - [SIGQUIT] = "QUIT", - [SIGILL] = "ILL", - [SIGTRAP] = "TRAP", - [SIGABRT] = "ABRT", - [SIGBUS] = "BUS", - [SIGFPE] = "FPE", - [SIGKILL] = "KILL", - [SIGUSR1] = "USR1", - [SIGSEGV] = "SEGV", - [SIGUSR2] = "USR2", - [SIGPIPE] = "PIPE", - [SIGALRM] = "ALRM", - [SIGTERM] = "TERM", -#ifdef SIGSTKFLT - [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ -#endif - [SIGCHLD] = "CHLD", - [SIGCONT] = "CONT", - [SIGSTOP] = "STOP", - [SIGTSTP] = "TSTP", - [SIGTTIN] = "TTIN", - [SIGTTOU] = "TTOU", - [SIGURG] = "URG", - [SIGXCPU] = "XCPU", - [SIGXFSZ] = "XFSZ", - [SIGVTALRM] = "VTALRM", - [SIGPROF] = "PROF", - [SIGWINCH] = "WINCH", - [SIGIO] = "IO", - [SIGPWR] = "PWR", - [SIGSYS] = "SYS" -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int); - -const char *signal_to_string(int signo) { - static thread_local char buf[sizeof("RTMIN+")-1 + DECIMAL_STR_MAX(int) + 1]; - const char *name; - - name = __signal_to_string(signo); - if (name) - return name; - - if (signo >= SIGRTMIN && signo <= SIGRTMAX) - xsprintf(buf, "RTMIN+%d", signo - SIGRTMIN); - else - xsprintf(buf, "%d", signo); - - return buf; -} - -int signal_from_string(const char *s) { - int signo; - int offset = 0; - unsigned u; - - signo = __signal_from_string(s); - if (signo > 0) - return signo; - - if (startswith(s, "RTMIN+")) { - s += 6; - offset = SIGRTMIN; - } - if (safe_atou(s, &u) >= 0) { - signo = (int) u + offset; - if (signo > 0 && signo < _NSIG) - return signo; - } - return -EINVAL; -} - -int signal_from_string_try_harder(const char *s) { - int signo; - assert(s); - - signo = signal_from_string(s); - if (signo <= 0) - if (startswith(s, "SIG")) - return signal_from_string(s+3); - - return signo; -} - -void nop_signal_handler(int sig) { - /* nothing here */ -} diff --git a/src/basic/signal-util.h b/src/basic/signal-util.h deleted file mode 100644 index 72b10e8712..0000000000 --- a/src/basic/signal-util.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2015 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 . -***/ - -#include - -#include "macro.h" - -int reset_all_signal_handlers(void); -int reset_signal_mask(void); - -int ignore_signals(int sig, ...); -int default_signals(int sig, ...); -int sigaction_many(const struct sigaction *sa, ...); - -int sigset_add_many(sigset_t *ss, ...); -int sigprocmask_many(int how, sigset_t *old, ...); - -const char *signal_to_string(int i) _const_; -int signal_from_string(const char *s) _pure_; - -int signal_from_string_try_harder(const char *s); - -void nop_signal_handler(int sig); - -static inline void block_signals_reset(sigset_t *ss) { - assert_se(sigprocmask(SIG_SETMASK, ss, NULL) >= 0); -} - -#define BLOCK_SIGNALS(...) \ - _cleanup_(block_signals_reset) sigset_t _saved_sigset = ({ \ - sigset_t t; \ - assert_se(sigprocmask_many(SIG_BLOCK, &t, __VA_ARGS__, -1) >= 0); \ - t; \ - }) diff --git a/src/basic/siphash24.c b/src/basic/siphash24.c deleted file mode 100644 index 060e8ba387..0000000000 --- a/src/basic/siphash24.c +++ /dev/null @@ -1,191 +0,0 @@ -/* - SipHash reference C implementation - - Written in 2012 by - Jean-Philippe Aumasson - Daniel J. Bernstein - - To the extent possible under law, the author(s) have dedicated all copyright - and related and neighboring rights to this software to the public domain - worldwide. This software is distributed without any warranty. - - You should have received a copy of the CC0 Public Domain Dedication along with - this software. If not, see . - - (Minimal changes made by Lennart Poettering, to make clean for inclusion in systemd) - (Refactored by Tom Gundersen to split up in several functions and follow systemd - coding style) -*/ - -#include "macro.h" -#include "siphash24.h" -#include "unaligned.h" - -static inline uint64_t rotate_left(uint64_t x, uint8_t b) { - assert(b < 64); - - return (x << b) | (x >> (64 - b)); -} - -static inline void sipround(struct siphash *state) { - assert(state); - - state->v0 += state->v1; - state->v1 = rotate_left(state->v1, 13); - state->v1 ^= state->v0; - state->v0 = rotate_left(state->v0, 32); - state->v2 += state->v3; - state->v3 = rotate_left(state->v3, 16); - state->v3 ^= state->v2; - state->v0 += state->v3; - state->v3 = rotate_left(state->v3, 21); - state->v3 ^= state->v0; - state->v2 += state->v1; - state->v1 = rotate_left(state->v1, 17); - state->v1 ^= state->v2; - state->v2 = rotate_left(state->v2, 32); -} - -void siphash24_init(struct siphash *state, const uint8_t k[16]) { - uint64_t k0, k1; - - assert(state); - assert(k); - - k0 = unaligned_read_le64(k); - k1 = unaligned_read_le64(k + 8); - - *state = (struct siphash) { - /* "somepseudorandomlygeneratedbytes" */ - .v0 = 0x736f6d6570736575ULL ^ k0, - .v1 = 0x646f72616e646f6dULL ^ k1, - .v2 = 0x6c7967656e657261ULL ^ k0, - .v3 = 0x7465646279746573ULL ^ k1, - .padding = 0, - .inlen = 0, - }; -} - -void siphash24_compress(const void *_in, size_t inlen, struct siphash *state) { - - const uint8_t *in = _in; - const uint8_t *end = in + inlen; - size_t left = state->inlen & 7; - uint64_t m; - - assert(in); - assert(state); - - /* Update total length */ - state->inlen += inlen; - - /* If padding exists, fill it out */ - if (left > 0) { - for ( ; in < end && left < 8; in ++, left ++) - state->padding |= ((uint64_t) *in) << (left * 8); - - if (in == end && left < 8) - /* We did not have enough input to fill out the padding completely */ - return; - -#ifdef DEBUG - printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); - printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); - printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); - printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); - printf("(%3zu) compress padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t)state->padding); -#endif - - state->v3 ^= state->padding; - sipround(state); - sipround(state); - state->v0 ^= state->padding; - - state->padding = 0; - } - - end -= (state->inlen % sizeof(uint64_t)); - - for ( ; in < end; in += 8) { - m = unaligned_read_le64(in); -#ifdef DEBUG - printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); - printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); - printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); - printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); - printf("(%3zu) compress %08x %08x\n", state->inlen, (uint32_t) (m >> 32), (uint32_t) m); -#endif - state->v3 ^= m; - sipround(state); - sipround(state); - state->v0 ^= m; - } - - left = state->inlen & 7; - switch (left) { - case 7: - state->padding |= ((uint64_t) in[6]) << 48; - case 6: - state->padding |= ((uint64_t) in[5]) << 40; - case 5: - state->padding |= ((uint64_t) in[4]) << 32; - case 4: - state->padding |= ((uint64_t) in[3]) << 24; - case 3: - state->padding |= ((uint64_t) in[2]) << 16; - case 2: - state->padding |= ((uint64_t) in[1]) << 8; - case 1: - state->padding |= ((uint64_t) in[0]); - case 0: - break; - } -} - -uint64_t siphash24_finalize(struct siphash *state) { - uint64_t b; - - assert(state); - - b = state->padding | (((uint64_t) state->inlen) << 56); - -#ifdef DEBUG - printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); - printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); - printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); - printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); - printf("(%3zu) padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t) state->padding); -#endif - - state->v3 ^= b; - sipround(state); - sipround(state); - state->v0 ^= b; - -#ifdef DEBUG - printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); - printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); - printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); - printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); -#endif - state->v2 ^= 0xff; - - sipround(state); - sipround(state); - sipround(state); - sipround(state); - - return state->v0 ^ state->v1 ^ state->v2 ^ state->v3; -} - -uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]) { - struct siphash state; - - assert(in); - assert(k); - - siphash24_init(&state, k); - siphash24_compress(in, inlen, &state); - - return siphash24_finalize(&state); -} diff --git a/src/basic/siphash24.h b/src/basic/siphash24.h deleted file mode 100644 index 54e2420cc6..0000000000 --- a/src/basic/siphash24.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -struct siphash { - uint64_t v0; - uint64_t v1; - uint64_t v2; - uint64_t v3; - uint64_t padding; - size_t inlen; -}; - -void siphash24_init(struct siphash *state, const uint8_t k[16]); -void siphash24_compress(const void *in, size_t inlen, struct siphash *state); -#define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state)) - -uint64_t siphash24_finalize(struct siphash *state); - -uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]); diff --git a/src/basic/smack-util.c b/src/basic/smack-util.c deleted file mode 100644 index 3a3df987df..0000000000 --- a/src/basic/smack-util.c +++ /dev/null @@ -1,241 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Intel Corporation - - Author: Auke Kok - - 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fileio.h" -#include "log.h" -#include "macro.h" -#include "path-util.h" -#include "process-util.h" -#include "smack-util.h" -#include "string-table.h" -#include "xattr-util.h" - -#ifdef HAVE_SMACK -bool mac_smack_use(void) { - static int cached_use = -1; - - if (cached_use < 0) - cached_use = access("/sys/fs/smackfs/", F_OK) >= 0; - - return cached_use; -} - -static const char* const smack_attr_table[_SMACK_ATTR_MAX] = { - [SMACK_ATTR_ACCESS] = "security.SMACK64", - [SMACK_ATTR_EXEC] = "security.SMACK64EXEC", - [SMACK_ATTR_MMAP] = "security.SMACK64MMAP", - [SMACK_ATTR_TRANSMUTE] = "security.SMACK64TRANSMUTE", - [SMACK_ATTR_IPIN] = "security.SMACK64IPIN", - [SMACK_ATTR_IPOUT] = "security.SMACK64IPOUT", -}; - -DEFINE_STRING_TABLE_LOOKUP(smack_attr, SmackAttr); - -int mac_smack_read(const char *path, SmackAttr attr, char **label) { - assert(path); - assert(attr >= 0 && attr < _SMACK_ATTR_MAX); - assert(label); - - if (!mac_smack_use()) - return 0; - - return getxattr_malloc(path, smack_attr_to_string(attr), label, true); -} - -int mac_smack_read_fd(int fd, SmackAttr attr, char **label) { - assert(fd >= 0); - assert(attr >= 0 && attr < _SMACK_ATTR_MAX); - assert(label); - - if (!mac_smack_use()) - return 0; - - return fgetxattr_malloc(fd, smack_attr_to_string(attr), label); -} - -int mac_smack_apply(const char *path, SmackAttr attr, const char *label) { - int r; - - assert(path); - assert(attr >= 0 && attr < _SMACK_ATTR_MAX); - - if (!mac_smack_use()) - return 0; - - if (label) - r = lsetxattr(path, smack_attr_to_string(attr), label, strlen(label), 0); - else - r = lremovexattr(path, smack_attr_to_string(attr)); - if (r < 0) - return -errno; - - return 0; -} - -int mac_smack_apply_fd(int fd, SmackAttr attr, const char *label) { - int r; - - assert(fd >= 0); - assert(attr >= 0 && attr < _SMACK_ATTR_MAX); - - if (!mac_smack_use()) - return 0; - - if (label) - r = fsetxattr(fd, smack_attr_to_string(attr), label, strlen(label), 0); - else - r = fremovexattr(fd, smack_attr_to_string(attr)); - if (r < 0) - return -errno; - - return 0; -} - -int mac_smack_apply_pid(pid_t pid, const char *label) { - const char *p; - int r = 0; - - assert(label); - - if (!mac_smack_use()) - return 0; - - p = procfs_file_alloca(pid, "attr/current"); - r = write_string_file(p, label, 0); - if (r < 0) - return r; - - return r; -} - -int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { - struct stat st; - int r = 0; - - assert(path); - - if (!mac_smack_use()) - return 0; - - /* - * Path must be in /dev and must exist - */ - if (!path_startswith(path, "/dev")) - return 0; - - r = lstat(path, &st); - if (r >= 0) { - const char *label; - - /* - * Label directories and character devices "*". - * Label symlinks "_". - * Don't change anything else. - */ - - if (S_ISDIR(st.st_mode)) - label = SMACK_STAR_LABEL; - else if (S_ISLNK(st.st_mode)) - label = SMACK_FLOOR_LABEL; - else if (S_ISCHR(st.st_mode)) - label = SMACK_STAR_LABEL; - else - return 0; - - r = lsetxattr(path, "security.SMACK64", label, strlen(label), 0); - - /* If the FS doesn't support labels, then exit without warning */ - if (r < 0 && errno == EOPNOTSUPP) - return 0; - } - - if (r < 0) { - /* Ignore ENOENT in some cases */ - if (ignore_enoent && errno == ENOENT) - return 0; - - if (ignore_erofs && errno == EROFS) - return 0; - - r = log_debug_errno(errno, "Unable to fix SMACK label of %s: %m", path); - } - - return r; -} - -int mac_smack_copy(const char *dest, const char *src) { - int r = 0; - _cleanup_free_ char *label = NULL; - - assert(dest); - assert(src); - - r = mac_smack_read(src, SMACK_ATTR_ACCESS, &label); - if (r < 0) - return r; - - r = mac_smack_apply(dest, SMACK_ATTR_ACCESS, label); - if (r < 0) - return r; - - return r; -} - -#else -bool mac_smack_use(void) { - return false; -} - -int mac_smack_read(const char *path, SmackAttr attr, char **label) { - return -EOPNOTSUPP; -} - -int mac_smack_read_fd(int fd, SmackAttr attr, char **label) { - return -EOPNOTSUPP; -} - -int mac_smack_apply(const char *path, SmackAttr attr, const char *label) { - return 0; -} - -int mac_smack_apply_fd(int fd, SmackAttr attr, const char *label) { - return 0; -} - -int mac_smack_apply_pid(pid_t pid, const char *label) { - return 0; -} - -int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { - return 0; -} - -int mac_smack_copy(const char *dest, const char *src) { - return 0; -} -#endif diff --git a/src/basic/smack-util.h b/src/basic/smack-util.h deleted file mode 100644 index f90ba0a027..0000000000 --- a/src/basic/smack-util.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 Intel Corporation - - Author: Auke Kok - - 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 . -***/ - -#include -#include - -#include "macro.h" - -#define SMACK_FLOOR_LABEL "_" -#define SMACK_STAR_LABEL "*" - -typedef enum SmackAttr { - SMACK_ATTR_ACCESS = 0, - SMACK_ATTR_EXEC = 1, - SMACK_ATTR_MMAP = 2, - SMACK_ATTR_TRANSMUTE = 3, - SMACK_ATTR_IPIN = 4, - SMACK_ATTR_IPOUT = 5, - _SMACK_ATTR_MAX, - _SMACK_ATTR_INVALID = -1, -} SmackAttr; - -bool mac_smack_use(void); - -int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs); - -const char* smack_attr_to_string(SmackAttr i) _const_; -SmackAttr smack_attr_from_string(const char *s) _pure_; -int mac_smack_read(const char *path, SmackAttr attr, char **label); -int mac_smack_read_fd(int fd, SmackAttr attr, char **label); -int mac_smack_apply(const char *path, SmackAttr attr, const char *label); -int mac_smack_apply_fd(int fd, SmackAttr attr, const char *label); -int mac_smack_apply_pid(pid_t pid, const char *label); -int mac_smack_copy(const char *dest, const char *src); diff --git a/src/basic/socket-label.c b/src/basic/socket-label.c deleted file mode 100644 index 35e9573aa4..0000000000 --- a/src/basic/socket-label.c +++ /dev/null @@ -1,174 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "selinux-util.h" -#include "socket-util.h" - -int socket_address_listen( - const SocketAddress *a, - int flags, - int backlog, - SocketAddressBindIPv6Only only, - const char *bind_to_device, - bool reuse_port, - bool free_bind, - bool transparent, - mode_t directory_mode, - mode_t socket_mode, - const char *label) { - - _cleanup_close_ int fd = -1; - int r, one; - - assert(a); - - r = socket_address_verify(a); - if (r < 0) - return r; - - if (socket_address_family(a) == AF_INET6 && !socket_ipv6_is_supported()) - return -EAFNOSUPPORT; - - if (label) { - r = mac_selinux_create_socket_prepare(label); - if (r < 0) - return r; - } - - fd = socket(socket_address_family(a), a->type | flags, a->protocol); - r = fd < 0 ? -errno : 0; - - if (label) - mac_selinux_create_socket_clear(); - - if (r < 0) - return r; - - if (socket_address_family(a) == AF_INET6 && only != SOCKET_ADDRESS_DEFAULT) { - int flag = only == SOCKET_ADDRESS_IPV6_ONLY; - - if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)) < 0) - return -errno; - } - - if (socket_address_family(a) == AF_INET || socket_address_family(a) == AF_INET6) { - if (bind_to_device) - if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, bind_to_device, strlen(bind_to_device)+1) < 0) - return -errno; - - if (reuse_port) { - one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) < 0) - log_warning_errno(errno, "SO_REUSEPORT failed: %m"); - } - - if (free_bind) { - one = 1; - if (setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0) - log_warning_errno(errno, "IP_FREEBIND failed: %m"); - } - - if (transparent) { - one = 1; - if (setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &one, sizeof(one)) < 0) - log_warning_errno(errno, "IP_TRANSPARENT failed: %m"); - } - } - - one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) - return -errno; - - if (socket_address_family(a) == AF_UNIX && a->sockaddr.un.sun_path[0] != 0) { - mode_t old_mask; - - /* Create parents */ - mkdir_parents_label(a->sockaddr.un.sun_path, directory_mode); - - /* Enforce the right access mode for the socket */ - old_mask = umask(~ socket_mode); - - r = mac_selinux_bind(fd, &a->sockaddr.sa, a->size); - - if (r < 0 && errno == EADDRINUSE) { - /* Unlink and try again */ - unlink(a->sockaddr.un.sun_path); - r = bind(fd, &a->sockaddr.sa, a->size); - } - - umask(old_mask); - } else - r = bind(fd, &a->sockaddr.sa, a->size); - - if (r < 0) - return -errno; - - if (socket_address_can_accept(a)) - if (listen(fd, backlog) < 0) - return -errno; - - r = fd; - fd = -1; - - return r; -} - -int make_socket_fd(int log_level, const char* address, int type, int flags) { - SocketAddress a; - int fd, r; - - r = socket_address_parse(&a, address); - if (r < 0) - return log_error_errno(r, "Failed to parse socket address \"%s\": %m", address); - - a.type = type; - - fd = socket_address_listen(&a, type | flags, SOMAXCONN, SOCKET_ADDRESS_DEFAULT, - NULL, false, false, false, 0755, 0644, NULL); - if (fd < 0 || log_get_max_level() >= log_level) { - _cleanup_free_ char *p = NULL; - - r = socket_address_print(&a, &p); - if (r < 0) - return log_error_errno(r, "socket_address_print(): %m"); - - if (fd < 0) - log_error_errno(fd, "Failed to listen on %s: %m", p); - else - log_full(log_level, "Listening on %s", p); - } - - return fd; -} diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c deleted file mode 100644 index 58512686e3..0000000000 --- a/src/basic/socket-util.c +++ /dev/null @@ -1,972 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "parse-util.h" -#include "path-util.h" -#include "socket-util.h" -#include "string-table.h" -#include "string-util.h" -#include "user-util.h" -#include "util.h" - -int socket_address_parse(SocketAddress *a, const char *s) { - char *e, *n; - unsigned u; - int r; - - assert(a); - assert(s); - - zero(*a); - a->type = SOCK_STREAM; - - if (*s == '[') { - /* IPv6 in [x:.....:z]:p notation */ - - e = strchr(s+1, ']'); - if (!e) - return -EINVAL; - - n = strndupa(s+1, e-s-1); - - errno = 0; - if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0) - return errno > 0 ? -errno : -EINVAL; - - e++; - if (*e != ':') - return -EINVAL; - - e++; - r = safe_atou(e, &u); - if (r < 0) - return r; - - if (u <= 0 || u > 0xFFFF) - return -EINVAL; - - a->sockaddr.in6.sin6_family = AF_INET6; - a->sockaddr.in6.sin6_port = htons((uint16_t) u); - a->size = sizeof(struct sockaddr_in6); - - } else if (*s == '/') { - /* AF_UNIX socket */ - - size_t l; - - l = strlen(s); - if (l >= sizeof(a->sockaddr.un.sun_path)) - return -EINVAL; - - a->sockaddr.un.sun_family = AF_UNIX; - memcpy(a->sockaddr.un.sun_path, s, l); - a->size = offsetof(struct sockaddr_un, sun_path) + l + 1; - - } else if (*s == '@') { - /* Abstract AF_UNIX socket */ - size_t l; - - l = strlen(s+1); - if (l >= sizeof(a->sockaddr.un.sun_path) - 1) - return -EINVAL; - - a->sockaddr.un.sun_family = AF_UNIX; - memcpy(a->sockaddr.un.sun_path+1, s+1, l); - a->size = offsetof(struct sockaddr_un, sun_path) + 1 + l; - - } else { - e = strchr(s, ':'); - if (e) { - r = safe_atou(e+1, &u); - if (r < 0) - return r; - - if (u <= 0 || u > 0xFFFF) - return -EINVAL; - - n = strndupa(s, e-s); - - /* IPv4 in w.x.y.z:p notation? */ - r = inet_pton(AF_INET, n, &a->sockaddr.in.sin_addr); - if (r < 0) - return -errno; - - 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->size = sizeof(struct sockaddr_in); - } else { - unsigned idx; - - if (strlen(n) > IF_NAMESIZE-1) - return -EINVAL; - - /* Uh, our last resort, an interface name */ - idx = if_nametoindex(n); - if (idx == 0) - return -EINVAL; - - a->sockaddr.in6.sin6_family = AF_INET6; - a->sockaddr.in6.sin6_port = htons((uint16_t) u); - a->sockaddr.in6.sin6_scope_id = idx; - a->sockaddr.in6.sin6_addr = in6addr_any; - a->size = sizeof(struct sockaddr_in6); - } - } else { - - /* Just a port */ - r = safe_atou(s, &u); - if (r < 0) - return r; - - if (u <= 0 || u > 0xFFFF) - return -EINVAL; - - 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_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_addr.s_addr = INADDR_ANY; - a->size = sizeof(struct sockaddr_in); - } - } - } - - return 0; -} - -int socket_address_parse_and_warn(SocketAddress *a, const char *s) { - SocketAddress b; - int r; - - /* Similar to socket_address_parse() but warns for IPv6 sockets when we don't support them. */ - - r = socket_address_parse(&b, s); - if (r < 0) - return r; - - if (!socket_ipv6_is_supported() && b.sockaddr.sa.sa_family == AF_INET6) { - log_warning("Binding to IPv6 address not available since kernel does not support IPv6."); - return -EAFNOSUPPORT; - } - - *a = b; - return 0; -} - -int socket_address_parse_netlink(SocketAddress *a, const char *s) { - int family; - unsigned group = 0; - _cleanup_free_ char *sfamily = NULL; - assert(a); - assert(s); - - zero(*a); - a->type = SOCK_RAW; - - errno = 0; - if (sscanf(s, "%ms %u", &sfamily, &group) < 1) - return errno > 0 ? -errno : -EINVAL; - - family = netlink_family_from_string(sfamily); - if (family < 0) - return -EINVAL; - - a->sockaddr.nl.nl_family = AF_NETLINK; - a->sockaddr.nl.nl_groups = group; - - a->type = SOCK_RAW; - a->size = sizeof(struct sockaddr_nl); - a->protocol = family; - - return 0; -} - -int socket_address_verify(const SocketAddress *a) { - assert(a); - - switch (socket_address_family(a)) { - - case AF_INET: - if (a->size != sizeof(struct sockaddr_in)) - return -EINVAL; - - if (a->sockaddr.in.sin_port == 0) - return -EINVAL; - - if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM) - return -EINVAL; - - return 0; - - case AF_INET6: - if (a->size != sizeof(struct sockaddr_in6)) - return -EINVAL; - - if (a->sockaddr.in6.sin6_port == 0) - return -EINVAL; - - if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM) - return -EINVAL; - - return 0; - - case AF_UNIX: - if (a->size < offsetof(struct sockaddr_un, sun_path)) - return -EINVAL; - - if (a->size > offsetof(struct sockaddr_un, sun_path)) { - - if (a->sockaddr.un.sun_path[0] != 0) { - char *e; - - /* path */ - e = memchr(a->sockaddr.un.sun_path, 0, sizeof(a->sockaddr.un.sun_path)); - if (!e) - return -EINVAL; - - if (a->size != offsetof(struct sockaddr_un, sun_path) + (e - a->sockaddr.un.sun_path) + 1) - return -EINVAL; - } - } - - if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM && a->type != SOCK_SEQPACKET) - return -EINVAL; - - return 0; - - case AF_NETLINK: - - if (a->size != sizeof(struct sockaddr_nl)) - return -EINVAL; - - if (a->type != SOCK_RAW && a->type != SOCK_DGRAM) - return -EINVAL; - - return 0; - - default: - return -EAFNOSUPPORT; - } -} - -int socket_address_print(const SocketAddress *a, char **ret) { - int r; - - assert(a); - assert(ret); - - r = socket_address_verify(a); - if (r < 0) - return r; - - if (socket_address_family(a) == AF_NETLINK) { - _cleanup_free_ char *sfamily = NULL; - - r = netlink_family_to_string_alloc(a->protocol, &sfamily); - if (r < 0) - return r; - - r = asprintf(ret, "%s %u", sfamily, a->sockaddr.nl.nl_groups); - if (r < 0) - return -ENOMEM; - - return 0; - } - - return sockaddr_pretty(&a->sockaddr.sa, a->size, false, true, ret); -} - -bool socket_address_can_accept(const SocketAddress *a) { - assert(a); - - return - a->type == SOCK_STREAM || - a->type == SOCK_SEQPACKET; -} - -bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) { - assert(a); - assert(b); - - /* Invalid addresses are unequal to all */ - if (socket_address_verify(a) < 0 || - socket_address_verify(b) < 0) - return false; - - if (a->type != b->type) - return false; - - if (socket_address_family(a) != socket_address_family(b)) - return false; - - switch (socket_address_family(a)) { - - case AF_INET: - if (a->sockaddr.in.sin_addr.s_addr != b->sockaddr.in.sin_addr.s_addr) - return false; - - if (a->sockaddr.in.sin_port != b->sockaddr.in.sin_port) - return false; - - break; - - case AF_INET6: - if (memcmp(&a->sockaddr.in6.sin6_addr, &b->sockaddr.in6.sin6_addr, sizeof(a->sockaddr.in6.sin6_addr)) != 0) - return false; - - if (a->sockaddr.in6.sin6_port != b->sockaddr.in6.sin6_port) - return false; - - break; - - case AF_UNIX: - if (a->size <= offsetof(struct sockaddr_un, sun_path) || - b->size <= offsetof(struct sockaddr_un, sun_path)) - return false; - - if ((a->sockaddr.un.sun_path[0] == 0) != (b->sockaddr.un.sun_path[0] == 0)) - return false; - - if (a->sockaddr.un.sun_path[0]) { - if (!path_equal_or_files_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path)) - return false; - } else { - if (a->size != b->size) - return false; - - if (memcmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, a->size) != 0) - return false; - } - - break; - - case AF_NETLINK: - if (a->protocol != b->protocol) - return false; - - if (a->sockaddr.nl.nl_groups != b->sockaddr.nl.nl_groups) - return false; - - break; - - default: - /* Cannot compare, so we assume the addresses are different */ - return false; - } - - return true; -} - -bool socket_address_is(const SocketAddress *a, const char *s, int type) { - struct SocketAddress b; - - assert(a); - assert(s); - - if (socket_address_parse(&b, s) < 0) - return false; - - b.type = type; - - return socket_address_equal(a, &b); -} - -bool socket_address_is_netlink(const SocketAddress *a, const char *s) { - struct SocketAddress b; - - assert(a); - assert(s); - - if (socket_address_parse_netlink(&b, s) < 0) - return false; - - return socket_address_equal(a, &b); -} - -const char* socket_address_get_path(const SocketAddress *a) { - assert(a); - - if (socket_address_family(a) != AF_UNIX) - return NULL; - - if (a->sockaddr.un.sun_path[0] == 0) - return NULL; - - return a->sockaddr.un.sun_path; -} - -bool socket_ipv6_is_supported(void) { - if (access("/proc/net/sockstat6", F_OK) != 0) - return false; - - return true; -} - -bool socket_address_matches_fd(const SocketAddress *a, int fd) { - SocketAddress b; - socklen_t solen; - - assert(a); - assert(fd >= 0); - - b.size = sizeof(b.sockaddr); - if (getsockname(fd, &b.sockaddr.sa, &b.size) < 0) - return false; - - if (b.sockaddr.sa.sa_family != a->sockaddr.sa.sa_family) - return false; - - solen = sizeof(b.type); - if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &b.type, &solen) < 0) - return false; - - if (b.type != a->type) - return false; - - if (a->protocol != 0) { - solen = sizeof(b.protocol); - if (getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &b.protocol, &solen) < 0) - return false; - - if (b.protocol != a->protocol) - return false; - } - - return socket_address_equal(a, &b); -} - -int sockaddr_port(const struct sockaddr *_sa) { - union sockaddr_union *sa = (union sockaddr_union*) _sa; - - assert(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); -} - -int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret) { - union sockaddr_union *sa = (union sockaddr_union*) _sa; - char *p; - int r; - - assert(sa); - assert(salen >= sizeof(sa->sa.sa_family)); - - switch (sa->sa.sa_family) { - - case AF_INET: { - uint32_t a; - - a = ntohl(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)); - else - r = asprintf(&p, - "%u.%u.%u.%u", - a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF); - if (r < 0) - return -ENOMEM; - break; - } - - case AF_INET6: { - static const unsigned char ipv4_prefix[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF - }; - - if (translate_ipv6 && - memcmp(&sa->in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0) { - const uint8_t *a = sa->in6.sin6_addr.s6_addr+12; - if (include_port) - r = asprintf(&p, - "%u.%u.%u.%u:%u", - a[0], a[1], a[2], a[3], - ntohs(sa->in6.sin6_port)); - else - r = asprintf(&p, - "%u.%u.%u.%u", - a[0], a[1], a[2], a[3]); - if (r < 0) - return -ENOMEM; - } else { - char a[INET6_ADDRSTRLEN]; - - inet_ntop(AF_INET6, &sa->in6.sin6_addr, a, sizeof(a)); - - if (include_port) { - r = asprintf(&p, - "[%s]:%u", - a, - ntohs(sa->in6.sin6_port)); - if (r < 0) - return -ENOMEM; - } else { - p = strdup(a); - if (!p) - return -ENOMEM; - } - } - - break; - } - - case AF_UNIX: - if (salen <= offsetof(struct sockaddr_un, sun_path)) { - p = strdup(""); - if (!p) - return -ENOMEM; - - } else if (sa->un.sun_path[0] == 0) { - /* abstract */ - - /* FIXME: We assume we can print the - * socket path here and that it hasn't - * more than one NUL byte. That is - * actually an invalid assumption */ - - p = new(char, sizeof(sa->un.sun_path)+1); - if (!p) - return -ENOMEM; - - p[0] = '@'; - memcpy(p+1, sa->un.sun_path+1, sizeof(sa->un.sun_path)-1); - p[sizeof(sa->un.sun_path)] = 0; - - } else { - p = strndup(sa->un.sun_path, sizeof(sa->un.sun_path)); - if (!p) - return -ENOMEM; - } - - break; - - default: - return -EOPNOTSUPP; - } - - - *ret = p; - return 0; -} - -int getpeername_pretty(int fd, bool include_port, char **ret) { - union sockaddr_union sa; - socklen_t salen = sizeof(sa); - int r; - - assert(fd >= 0); - assert(ret); - - if (getpeername(fd, &sa.sa, &salen) < 0) - return -errno; - - if (sa.sa.sa_family == AF_UNIX) { - struct ucred ucred = {}; - - /* UNIX connection sockets are anonymous, so let's use - * PID/UID as pretty credentials instead */ - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - if (asprintf(ret, "PID "PID_FMT"/UID "UID_FMT, ucred.pid, ucred.uid) < 0) - return -ENOMEM; - - return 0; - } - - /* For remote sockets we translate IPv6 addresses back to IPv4 - * if applicable, since that's nicer. */ - - return sockaddr_pretty(&sa.sa, salen, true, include_port, ret); -} - -int getsockname_pretty(int fd, char **ret) { - union sockaddr_union sa; - socklen_t salen = sizeof(sa); - - assert(fd >= 0); - assert(ret); - - if (getsockname(fd, &sa.sa, &salen) < 0) - return -errno; - - /* For local sockets we do not translate IPv6 addresses back - * to IPv6 if applicable, since this is usually used for - * listening sockets where the difference between IPv4 and - * IPv6 matters. */ - - return sockaddr_pretty(&sa.sa, salen, false, true, ret); -} - -int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret) { - int r; - char host[NI_MAXHOST], *ret; - - assert(_ret); - - r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0, - NI_IDN|NI_IDN_USE_STD3_ASCII_RULES); - if (r != 0) { - int saved_errno = errno; - - r = sockaddr_pretty(&sa->sa, salen, true, true, &ret); - if (r < 0) - return r; - - log_debug_errno(saved_errno, "getnameinfo(%s) failed: %m", ret); - } else { - ret = strdup(host); - if (!ret) - return -ENOMEM; - } - - *_ret = ret; - return 0; -} - -int getnameinfo_pretty(int fd, char **ret) { - union sockaddr_union sa; - socklen_t salen = sizeof(sa); - - assert(fd >= 0); - assert(ret); - - if (getsockname(fd, &sa.sa, &salen) < 0) - return -errno; - - return socknameinfo_pretty(&sa, salen, ret); -} - -int socket_address_unlink(SocketAddress *a) { - assert(a); - - if (socket_address_family(a) != AF_UNIX) - return 0; - - if (a->sockaddr.un.sun_path[0] == 0) - return 0; - - if (unlink(a->sockaddr.un.sun_path) < 0) - return -errno; - - return 1; -} - -static const char* const netlink_family_table[] = { - [NETLINK_ROUTE] = "route", - [NETLINK_FIREWALL] = "firewall", - [NETLINK_INET_DIAG] = "inet-diag", - [NETLINK_NFLOG] = "nflog", - [NETLINK_XFRM] = "xfrm", - [NETLINK_SELINUX] = "selinux", - [NETLINK_ISCSI] = "iscsi", - [NETLINK_AUDIT] = "audit", - [NETLINK_FIB_LOOKUP] = "fib-lookup", - [NETLINK_CONNECTOR] = "connector", - [NETLINK_NETFILTER] = "netfilter", - [NETLINK_IP6_FW] = "ip6-fw", - [NETLINK_DNRTMSG] = "dnrtmsg", - [NETLINK_KOBJECT_UEVENT] = "kobject-uevent", - [NETLINK_GENERIC] = "generic", - [NETLINK_SCSITRANSPORT] = "scsitransport", - [NETLINK_ECRYPTFS] = "ecryptfs" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(netlink_family, int, INT_MAX); - -static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX] = { - [SOCKET_ADDRESS_DEFAULT] = "default", - [SOCKET_ADDRESS_BOTH] = "both", - [SOCKET_ADDRESS_IPV6_ONLY] = "ipv6-only" -}; - -DEFINE_STRING_TABLE_LOOKUP(socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); - -bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b) { - assert(a); - assert(b); - - if (a->sa.sa_family != b->sa.sa_family) - return false; - - if (a->sa.sa_family == AF_INET) - return a->in.sin_addr.s_addr == b->in.sin_addr.s_addr; - - if (a->sa.sa_family == AF_INET6) - return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr)) == 0; - - return false; -} - -int fd_inc_sndbuf(int fd, size_t n) { - int r, value; - socklen_t l = sizeof(value); - - r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l); - if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) - return 0; - - /* If we have the privileges we will ignore the kernel limit. */ - - value = (int) n; - if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0) - if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0) - return -errno; - - return 1; -} - -int fd_inc_rcvbuf(int fd, size_t n) { - int r, value; - socklen_t l = sizeof(value); - - r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l); - if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) - return 0; - - /* If we have the privileges we will ignore the kernel limit. */ - - value = (int) n; - if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0) - if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0) - return -errno; - return 1; -} - -static const char* const ip_tos_table[] = { - [IPTOS_LOWDELAY] = "low-delay", - [IPTOS_THROUGHPUT] = "throughput", - [IPTOS_RELIABILITY] = "reliability", - [IPTOS_LOWCOST] = "low-cost", -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff); - -int getpeercred(int fd, struct ucred *ucred) { - socklen_t n = sizeof(struct ucred); - struct ucred u; - int r; - - assert(fd >= 0); - assert(ucred); - - r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n); - if (r < 0) - return -errno; - - if (n != sizeof(struct ucred)) - return -EIO; - - /* Check if the data is actually useful and not suppressed due - * to namespacing issues */ - if (u.pid <= 0) - return -ENODATA; - if (u.uid == UID_INVALID) - return -ENODATA; - if (u.gid == GID_INVALID) - return -ENODATA; - - *ucred = u; - return 0; -} - -int getpeersec(int fd, char **ret) { - socklen_t n = 64; - char *s; - int r; - - assert(fd >= 0); - assert(ret); - - s = new0(char, n); - if (!s) - return -ENOMEM; - - r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); - if (r < 0) { - free(s); - - if (errno != ERANGE) - return -errno; - - s = new0(char, n); - if (!s) - return -ENOMEM; - - r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); - if (r < 0) { - free(s); - return -errno; - } - } - - if (isempty(s)) { - free(s); - return -EOPNOTSUPP; - } - - *ret = s; - return 0; -} - -int send_one_fd_sa( - int transport_fd, - int fd, - const struct sockaddr *sa, socklen_t len, - int flags) { - - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int))]; - } control = {}; - struct msghdr mh = { - .msg_name = (struct sockaddr*) sa, - .msg_namelen = len, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - struct cmsghdr *cmsg; - - assert(transport_fd >= 0); - assert(fd >= 0); - - 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), &fd, sizeof(int)); - - mh.msg_controllen = CMSG_SPACE(sizeof(int)); - if (sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags) < 0) - return -errno; - - return 0; -} - -int receive_one_fd(int transport_fd, int flags) { - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int))]; - } control = {}; - struct msghdr mh = { - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - struct cmsghdr *cmsg, *found = NULL; - - assert(transport_fd >= 0); - - /* - * Receive a single FD via @transport_fd. We don't care for - * the transport-type. We retrieve a single FD at most, so for - * packet-based transports, the caller must ensure to send - * only a single FD per packet. This is best used in - * combination with send_one_fd(). - */ - - if (recvmsg(transport_fd, &mh, MSG_NOSIGNAL | MSG_CMSG_CLOEXEC | flags) < 0) - return -errno; - - CMSG_FOREACH(cmsg, &mh) { - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS && - cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { - assert(!found); - found = cmsg; - break; - } - } - - if (!found) { - cmsg_close_all(&mh); - return -EIO; - } - - return *(int*) CMSG_DATA(found); -} - -ssize_t next_datagram_size_fd(int fd) { - ssize_t l; - int k; - - /* This is a bit like FIONREAD/SIOCINQ, however a bit more powerful. The difference being: recv(MSG_PEEK) will - * actually cause the next datagram in the queue to be validated regarding checksums, which FIONREAD dosn't - * do. This difference is actually of major importance as we need to be sure that the size returned here - * actually matches what we will read with recvmsg() next, as otherwise we might end up allocating a buffer of - * the wrong size. */ - - l = recv(fd, NULL, 0, MSG_PEEK|MSG_TRUNC); - if (l < 0) { - if (errno == EOPNOTSUPP) - goto fallback; - - return -errno; - } - if (l == 0) - goto fallback; - - return l; - -fallback: - k = 0; - - /* Some sockets (AF_PACKET) do not support null-sized recv() with MSG_TRUNC set, let's fall back to FIONREAD - * for them. Checksums don't matter for raw sockets anyway, hence this should be fine. */ - - if (ioctl(fd, FIONREAD, &k) < 0) - return -errno; - - return (ssize_t) k; -} diff --git a/src/basic/socket-util.h b/src/basic/socket-util.h deleted file mode 100644 index d17a2f35f8..0000000000 --- a/src/basic/socket-util.h +++ /dev/null @@ -1,139 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "macro.h" -#include "util.h" - -union sockaddr_union { - struct sockaddr sa; - struct sockaddr_in in; - struct sockaddr_in6 in6; - struct sockaddr_un un; - struct sockaddr_nl nl; - struct sockaddr_storage storage; - struct sockaddr_ll ll; -}; - -typedef struct SocketAddress { - union sockaddr_union sockaddr; - - /* We store the size here explicitly due to the weird - * sockaddr_un semantics for abstract sockets */ - socklen_t size; - - /* Socket type, i.e. SOCK_STREAM, SOCK_DGRAM, ... */ - int type; - - /* Socket protocol, IPPROTO_xxx, usually 0, except for netlink */ - int protocol; -} SocketAddress; - -typedef enum SocketAddressBindIPv6Only { - SOCKET_ADDRESS_DEFAULT, - SOCKET_ADDRESS_BOTH, - SOCKET_ADDRESS_IPV6_ONLY, - _SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX, - _SOCKET_ADDRESS_BIND_IPV6_ONLY_INVALID = -1 -} SocketAddressBindIPv6Only; - -#define socket_address_family(a) ((a)->sockaddr.sa.sa_family) - -int socket_address_parse(SocketAddress *a, const char *s); -int socket_address_parse_and_warn(SocketAddress *a, const char *s); -int socket_address_parse_netlink(SocketAddress *a, const char *s); -int socket_address_print(const SocketAddress *a, char **p); -int socket_address_verify(const SocketAddress *a) _pure_; -int socket_address_unlink(SocketAddress *a); - -bool socket_address_can_accept(const SocketAddress *a) _pure_; - -int socket_address_listen( - const SocketAddress *a, - int flags, - int backlog, - SocketAddressBindIPv6Only only, - const char *bind_to_device, - bool reuse_port, - bool free_bind, - bool transparent, - mode_t directory_mode, - mode_t socket_mode, - const char *label); -int make_socket_fd(int log_level, const char* address, int type, int flags); - -bool socket_address_is(const SocketAddress *a, const char *s, int type); -bool socket_address_is_netlink(const SocketAddress *a, const char *s); - -bool socket_address_matches_fd(const SocketAddress *a, int fd); - -bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) _pure_; - -const char* socket_address_get_path(const SocketAddress *a); - -bool socket_ipv6_is_supported(void); - -int sockaddr_port(const struct sockaddr *_sa) _pure_; - -int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret); -int getpeername_pretty(int fd, bool include_port, char **ret); -int getsockname_pretty(int fd, char **ret); - -int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret); -int getnameinfo_pretty(int fd, char **ret); - -const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_; -SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_; - -int netlink_family_to_string_alloc(int b, char **s); -int netlink_family_from_string(const char *s) _pure_; - -bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b); - -int fd_inc_sndbuf(int fd, size_t n); -int fd_inc_rcvbuf(int fd, size_t n); - -int ip_tos_to_string_alloc(int i, char **s); -int ip_tos_from_string(const char *s); - -int getpeercred(int fd, struct ucred *ucred); -int getpeersec(int fd, char **ret); - -int send_one_fd_sa(int transport_fd, - int fd, - const struct sockaddr *sa, socklen_t len, - int flags); -#define send_one_fd(transport_fd, fd, flags) send_one_fd_sa(transport_fd, fd, NULL, 0, flags) -int receive_one_fd(int transport_fd, int flags); - -ssize_t next_datagram_size_fd(int fd); - -#define CMSG_FOREACH(cmsg, mh) \ - for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg))) diff --git a/src/basic/sparse-endian.h b/src/basic/sparse-endian.h deleted file mode 100644 index c913fda8c5..0000000000 --- a/src/basic/sparse-endian.h +++ /dev/null @@ -1,88 +0,0 @@ -/* Copyright (c) 2012 Josh Triplett - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ -#ifndef SPARSE_ENDIAN_H -#define SPARSE_ENDIAN_H - -#include -#include -#include - -#ifdef __CHECKER__ -#define __bitwise __attribute__((bitwise)) -#define __force __attribute__((force)) -#else -#define __bitwise -#define __force -#endif - -typedef uint16_t __bitwise le16_t; -typedef uint16_t __bitwise be16_t; -typedef uint32_t __bitwise le32_t; -typedef uint32_t __bitwise be32_t; -typedef uint64_t __bitwise le64_t; -typedef uint64_t __bitwise be64_t; - -#undef htobe16 -#undef htole16 -#undef be16toh -#undef le16toh -#undef htobe32 -#undef htole32 -#undef be32toh -#undef le32toh -#undef htobe64 -#undef htole64 -#undef be64toh -#undef le64toh - -#if __BYTE_ORDER == __LITTLE_ENDIAN -#define bswap_16_on_le(x) __bswap_16(x) -#define bswap_32_on_le(x) __bswap_32(x) -#define bswap_64_on_le(x) __bswap_64(x) -#define bswap_16_on_be(x) (x) -#define bswap_32_on_be(x) (x) -#define bswap_64_on_be(x) (x) -#elif __BYTE_ORDER == __BIG_ENDIAN -#define bswap_16_on_le(x) (x) -#define bswap_32_on_le(x) (x) -#define bswap_64_on_le(x) (x) -#define bswap_16_on_be(x) __bswap_16(x) -#define bswap_32_on_be(x) __bswap_32(x) -#define bswap_64_on_be(x) __bswap_64(x) -#endif - -static inline le16_t htole16(uint16_t value) { return (le16_t __force) bswap_16_on_be(value); } -static inline le32_t htole32(uint32_t value) { return (le32_t __force) bswap_32_on_be(value); } -static inline le64_t htole64(uint64_t value) { return (le64_t __force) bswap_64_on_be(value); } - -static inline be16_t htobe16(uint16_t value) { return (be16_t __force) bswap_16_on_le(value); } -static inline be32_t htobe32(uint32_t value) { return (be32_t __force) bswap_32_on_le(value); } -static inline be64_t htobe64(uint64_t value) { return (be64_t __force) bswap_64_on_le(value); } - -static inline uint16_t le16toh(le16_t value) { return bswap_16_on_be((uint16_t __force)value); } -static inline uint32_t le32toh(le32_t value) { return bswap_32_on_be((uint32_t __force)value); } -static inline uint64_t le64toh(le64_t value) { return bswap_64_on_be((uint64_t __force)value); } - -static inline uint16_t be16toh(be16_t value) { return bswap_16_on_le((uint16_t __force)value); } -static inline uint32_t be32toh(be32_t value) { return bswap_32_on_le((uint32_t __force)value); } -static inline uint64_t be64toh(be64_t value) { return bswap_64_on_le((uint64_t __force)value); } - -#endif /* SPARSE_ENDIAN_H */ diff --git a/src/basic/special.h b/src/basic/special.h deleted file mode 100644 index 2fd03d9f75..0000000000 --- a/src/basic/special.h +++ /dev/null @@ -1,118 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#define SPECIAL_DEFAULT_TARGET "default.target" - -/* Shutdown targets */ -#define SPECIAL_UMOUNT_TARGET "umount.target" -/* This is not really intended to be started by directly. This is - * mostly so that other targets (reboot/halt/poweroff) can depend on - * it to bring all services down that want to be brought down on - * system shutdown. */ -#define SPECIAL_SHUTDOWN_TARGET "shutdown.target" -#define SPECIAL_HALT_TARGET "halt.target" -#define SPECIAL_POWEROFF_TARGET "poweroff.target" -#define SPECIAL_REBOOT_TARGET "reboot.target" -#define SPECIAL_KEXEC_TARGET "kexec.target" -#define SPECIAL_EXIT_TARGET "exit.target" -#define SPECIAL_SUSPEND_TARGET "suspend.target" -#define SPECIAL_HIBERNATE_TARGET "hibernate.target" -#define SPECIAL_HYBRID_SLEEP_TARGET "hybrid-sleep.target" - -/* Special boot targets */ -#define SPECIAL_RESCUE_TARGET "rescue.target" -#define SPECIAL_EMERGENCY_TARGET "emergency.target" -#define SPECIAL_MULTI_USER_TARGET "multi-user.target" -#define SPECIAL_GRAPHICAL_TARGET "graphical.target" - -/* Early boot targets */ -#define SPECIAL_SYSINIT_TARGET "sysinit.target" -#define SPECIAL_SOCKETS_TARGET "sockets.target" -#define SPECIAL_BUSNAMES_TARGET "busnames.target" -#define SPECIAL_TIMERS_TARGET "timers.target" -#define SPECIAL_PATHS_TARGET "paths.target" -#define SPECIAL_LOCAL_FS_TARGET "local-fs.target" -#define SPECIAL_LOCAL_FS_PRE_TARGET "local-fs-pre.target" -#define SPECIAL_INITRD_FS_TARGET "initrd-fs.target" -#define SPECIAL_INITRD_ROOT_FS_TARGET "initrd-root-fs.target" -#define SPECIAL_REMOTE_FS_TARGET "remote-fs.target" /* LSB's $remote_fs */ -#define SPECIAL_REMOTE_FS_PRE_TARGET "remote-fs-pre.target" -#define SPECIAL_SWAP_TARGET "swap.target" -#define SPECIAL_NETWORK_ONLINE_TARGET "network-online.target" -#define SPECIAL_TIME_SYNC_TARGET "time-sync.target" /* LSB's $time */ -#define SPECIAL_BASIC_TARGET "basic.target" - -/* LSB compatibility */ -#define SPECIAL_NETWORK_TARGET "network.target" /* LSB's $network */ -#define SPECIAL_NSS_LOOKUP_TARGET "nss-lookup.target" /* LSB's $named */ -#define SPECIAL_RPCBIND_TARGET "rpcbind.target" /* LSB's $portmap */ - -/* - * Rules regarding adding further high level targets like the above: - * - * - Be conservative, only add more of these when we really need - * them. We need strong usecases for further additions. - * - * - When there can be multiple implementations running side-by-side, - * it needs to be a .target unit which can pull in all - * implementations. - * - * - If something can be implemented with socket activation, and - * without, it needs to be a .target unit, so that it can pull in - * the appropriate unit. - * - * - Otherwise, it should be a .service unit. - * - * - In some cases it is OK to have both a .service and a .target - * unit, i.e. if there can be multiple parallel implementations, but - * only one is the "system" one. Example: syslog. - * - * Or to put this in other words: .service symlinks can be used to - * arbitrate between multiple implementations if there can be only one - * of a kind. .target units can be used to support multiple - * implementations that can run side-by-side. - */ - -/* Magic early boot services */ -#define SPECIAL_FSCK_SERVICE "systemd-fsck@.service" -#define SPECIAL_QUOTACHECK_SERVICE "systemd-quotacheck.service" -#define SPECIAL_QUOTAON_SERVICE "quotaon.service" -#define SPECIAL_REMOUNT_FS_SERVICE "systemd-remount-fs.service" - -/* Services systemd relies on */ -#define SPECIAL_DBUS_SERVICE "dbus.service" -#define SPECIAL_DBUS_SOCKET "dbus.socket" -#define SPECIAL_JOURNALD_SOCKET "systemd-journald.socket" -#define SPECIAL_JOURNALD_SERVICE "systemd-journald.service" - -/* Magic init signals */ -#define SPECIAL_KBREQUEST_TARGET "kbrequest.target" -#define SPECIAL_SIGPWR_TARGET "sigpwr.target" -#define SPECIAL_CTRL_ALT_DEL_TARGET "ctrl-alt-del.target" - -/* Where we add all our system units, users and machines by default */ -#define SPECIAL_SYSTEM_SLICE "system.slice" -#define SPECIAL_USER_SLICE "user.slice" -#define SPECIAL_MACHINE_SLICE "machine.slice" -#define SPECIAL_ROOT_SLICE "-.slice" - -/* The scope unit systemd itself lives in. */ -#define SPECIAL_INIT_SCOPE "init.scope" diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c deleted file mode 100644 index 309e84b93d..0000000000 --- a/src/basic/stat-util.c +++ /dev/null @@ -1,218 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2012 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "dirent-util.h" -#include "fd-util.h" -#include "macro.h" -#include "missing.h" -#include "stat-util.h" -#include "string-util.h" - -int is_symlink(const char *path) { - struct stat info; - - assert(path); - - if (lstat(path, &info) < 0) - return -errno; - - return !!S_ISLNK(info.st_mode); -} - -int is_dir(const char* path, bool follow) { - struct stat st; - int r; - - assert(path); - - if (follow) - r = stat(path, &st); - else - r = lstat(path, &st); - if (r < 0) - return -errno; - - return !!S_ISDIR(st.st_mode); -} - -int is_device_node(const char *path) { - struct stat info; - - assert(path); - - if (lstat(path, &info) < 0) - return -errno; - - return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode)); -} - -int dir_is_empty(const char *path) { - _cleanup_closedir_ DIR *d; - struct dirent *de; - - d = opendir(path); - if (!d) - return -errno; - - FOREACH_DIRENT(de, d, return -errno) - return 0; - - return 1; -} - -bool null_or_empty(struct stat *st) { - assert(st); - - if (S_ISREG(st->st_mode) && st->st_size <= 0) - return true; - - /* We don't want to hardcode the major/minor of /dev/null, - * hence we do a simpler "is this a device node?" check. */ - - if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) - return true; - - return false; -} - -int null_or_empty_path(const char *fn) { - struct stat st; - - assert(fn); - - if (stat(fn, &st) < 0) - return -errno; - - return null_or_empty(&st); -} - -int null_or_empty_fd(int fd) { - struct stat st; - - assert(fd >= 0); - - if (fstat(fd, &st) < 0) - return -errno; - - return null_or_empty(&st); -} - -int path_is_read_only_fs(const char *path) { - struct statvfs st; - - assert(path); - - if (statvfs(path, &st) < 0) - return -errno; - - if (st.f_flag & ST_RDONLY) - return true; - - /* On NFS, statvfs() might not reflect whether we can actually - * write to the remote share. Let's try again with - * access(W_OK) which is more reliable, at least sometimes. */ - if (access(path, W_OK) < 0 && errno == EROFS) - return true; - - return false; -} - -int path_is_os_tree(const char *path) { - char *p; - int r; - - assert(path); - - /* We use /usr/lib/os-release as flag file if something is an OS */ - p = strjoina(path, "/usr/lib/os-release"); - r = access(p, F_OK); - if (r >= 0) - return 1; - - /* Also check for the old location in /etc, just in case. */ - p = strjoina(path, "/etc/os-release"); - r = access(p, F_OK); - - return r >= 0; -} - -int files_same(const char *filea, const char *fileb) { - struct stat a, b; - - assert(filea); - assert(fileb); - - if (stat(filea, &a) < 0) - return -errno; - - if (stat(fileb, &b) < 0) - return -errno; - - return a.st_dev == b.st_dev && - a.st_ino == b.st_ino; -} - -bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) { - assert(s); - assert_cc(sizeof(statfs_f_type_t) >= sizeof(s->f_type)); - - return F_TYPE_EQUAL(s->f_type, magic_value); -} - -int fd_check_fstype(int fd, statfs_f_type_t magic_value) { - struct statfs s; - - if (fstatfs(fd, &s) < 0) - return -errno; - - return is_fs_type(&s, magic_value); -} - -int path_check_fstype(const char *path, statfs_f_type_t magic_value) { - _cleanup_close_ int fd = -1; - - fd = open(path, O_RDONLY); - if (fd < 0) - return -errno; - - return fd_check_fstype(fd, magic_value); -} - -bool is_temporary_fs(const struct statfs *s) { - return is_fs_type(s, TMPFS_MAGIC) || - is_fs_type(s, RAMFS_MAGIC); -} - -int fd_is_temporary_fs(int fd) { - struct statfs s; - - if (fstatfs(fd, &s) < 0) - return -errno; - - return is_temporary_fs(&s); -} diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h deleted file mode 100644 index 56d28f791e..0000000000 --- a/src/basic/stat-util.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2012 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "macro.h" - -int is_symlink(const char *path); -int is_dir(const char *path, bool follow); -int is_device_node(const char *path); - -int dir_is_empty(const char *path); - -static inline int dir_is_populated(const char *path) { - int r; - r = dir_is_empty(path); - if (r < 0) - return r; - return !r; -} - -bool null_or_empty(struct stat *st) _pure_; -int null_or_empty_path(const char *fn); -int null_or_empty_fd(int fd); - -int path_is_read_only_fs(const char *path); -int path_is_os_tree(const char *path); - -int files_same(const char *filea, const char *fileb); - -/* The .f_type field of struct statfs is really weird defined on - * different archs. Let's give its type a name. */ -typedef typeof(((struct statfs*)NULL)->f_type) statfs_f_type_t; - -bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) _pure_; -int fd_check_fstype(int fd, statfs_f_type_t magic_value); -int path_check_fstype(const char *path, statfs_f_type_t magic_value); - -bool is_temporary_fs(const struct statfs *s) _pure_; -int fd_is_temporary_fs(int fd); - -/* Because statfs.t_type can be int on some architectures, we have to cast - * the const magic to the type, otherwise the compiler warns about - * signed/unsigned comparison, because the magic can be 32 bit unsigned. - */ -#define F_TYPE_EQUAL(a, b) (a == (typeof(a)) b) diff --git a/src/basic/stdio-util.h b/src/basic/stdio-util.h deleted file mode 100644 index 0a675571ff..0000000000 --- a/src/basic/stdio-util.h +++ /dev/null @@ -1,76 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "macro.h" - -#define xsprintf(buf, fmt, ...) \ - assert_message_se((size_t) snprintf(buf, ELEMENTSOF(buf), fmt, __VA_ARGS__) < ELEMENTSOF(buf), "xsprintf: " #buf "[] must be big enough") - - -#define VA_FORMAT_ADVANCE(format, ap) \ -do { \ - int _argtypes[128]; \ - size_t _i, _k; \ - _k = parse_printf_format((format), ELEMENTSOF(_argtypes), _argtypes); \ - assert(_k < ELEMENTSOF(_argtypes)); \ - for (_i = 0; _i < _k; _i++) { \ - if (_argtypes[_i] & PA_FLAG_PTR) { \ - (void) va_arg(ap, void*); \ - continue; \ - } \ - \ - switch (_argtypes[_i]) { \ - case PA_INT: \ - case PA_INT|PA_FLAG_SHORT: \ - case PA_CHAR: \ - (void) va_arg(ap, int); \ - break; \ - case PA_INT|PA_FLAG_LONG: \ - (void) va_arg(ap, long int); \ - break; \ - case PA_INT|PA_FLAG_LONG_LONG: \ - (void) va_arg(ap, long long int); \ - break; \ - case PA_WCHAR: \ - (void) va_arg(ap, wchar_t); \ - break; \ - case PA_WSTRING: \ - case PA_STRING: \ - case PA_POINTER: \ - (void) va_arg(ap, void*); \ - break; \ - case PA_FLOAT: \ - case PA_DOUBLE: \ - (void) va_arg(ap, double); \ - break; \ - case PA_DOUBLE|PA_FLAG_LONG_DOUBLE: \ - (void) va_arg(ap, long double); \ - break; \ - default: \ - assert_not_reached("Unknown format string argument."); \ - } \ - } \ -} while(false) diff --git a/src/basic/strbuf.c b/src/basic/strbuf.c deleted file mode 100644 index 77220c0251..0000000000 --- a/src/basic/strbuf.c +++ /dev/null @@ -1,200 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include -#include -#include - -#include "alloc-util.h" -#include "strbuf.h" - -/* - * Strbuf stores given strings in a single continuous allocated memory - * area. Identical strings are de-duplicated and return the same offset - * as the first string stored. If the tail of a string already exists - * in the buffer, the tail is returned. - * - * A trie (http://en.wikipedia.org/wiki/Trie) is used to maintain the - * information about the stored strings. - * - * Example of udev rules: - * $ ./udevadm test . - * ... - * read rules file: /usr/lib/udev/rules.d/99-systemd.rules - * rules contain 196608 bytes tokens (16384 * 12 bytes), 39742 bytes strings - * 23939 strings (207859 bytes), 20404 de-duplicated (171653 bytes), 3536 trie nodes used - * ... - */ - -struct strbuf *strbuf_new(void) { - struct strbuf *str; - - str = new0(struct strbuf, 1); - if (!str) - return NULL; - - str->buf = new0(char, 1); - if (!str->buf) - goto err; - str->len = 1; - - str->root = new0(struct strbuf_node, 1); - if (!str->root) - goto err; - str->nodes_count = 1; - return str; -err: - free(str->buf); - free(str->root); - free(str); - return NULL; -} - -static void strbuf_node_cleanup(struct strbuf_node *node) { - size_t i; - - for (i = 0; i < node->children_count; i++) - strbuf_node_cleanup(node->children[i].child); - free(node->children); - free(node); -} - -/* clean up trie data, leave only the string buffer */ -void strbuf_complete(struct strbuf *str) { - if (!str) - return; - if (str->root) - strbuf_node_cleanup(str->root); - str->root = NULL; -} - -/* clean up everything */ -void strbuf_cleanup(struct strbuf *str) { - if (!str) - return; - if (str->root) - strbuf_node_cleanup(str->root); - free(str->buf); - free(str); -} - -static int strbuf_children_cmp(const struct strbuf_child_entry *n1, - const struct strbuf_child_entry *n2) { - return n1->c - n2->c; -} - -static void bubbleinsert(struct strbuf_node *node, - uint8_t c, - struct strbuf_node *node_child) { - - struct strbuf_child_entry new = { - .c = c, - .child = node_child, - }; - int left = 0, right = node->children_count; - - while (right > left) { - int middle = (right + left) / 2 ; - if (strbuf_children_cmp(&node->children[middle], &new) <= 0) - left = middle + 1; - else - right = middle; - } - - memmove(node->children + left + 1, node->children + left, - sizeof(struct strbuf_child_entry) * (node->children_count - left)); - node->children[left] = new; - - node->children_count ++; -} - -/* add string, return the index/offset into the buffer */ -ssize_t strbuf_add_string(struct strbuf *str, const char *s, size_t len) { - uint8_t c; - struct strbuf_node *node; - size_t depth; - char *buf_new; - struct strbuf_child_entry *child; - struct strbuf_node *node_child; - ssize_t off; - - if (!str->root) - return -EINVAL; - - /* search string; start from last character to find possibly matching tails */ - if (len == 0) - return 0; - str->in_count++; - str->in_len += len; - - node = str->root; - c = s[len-1]; - for (depth = 0; depth <= len; depth++) { - struct strbuf_child_entry search; - - /* match against current node */ - off = node->value_off + node->value_len - len; - if (depth == len || (node->value_len >= len && memcmp(str->buf + off, s, len) == 0)) { - str->dedup_len += len; - str->dedup_count++; - return off; - } - - /* lookup child node */ - c = s[len - 1 - depth]; - search.c = c; - child = bsearch(&search, node->children, node->children_count, - sizeof(struct strbuf_child_entry), - (__compar_fn_t) strbuf_children_cmp); - if (!child) - break; - node = child->child; - } - - /* add new string */ - buf_new = realloc(str->buf, str->len + len+1); - if (!buf_new) - return -ENOMEM; - str->buf = buf_new; - off = str->len; - memcpy(str->buf + off, s, len); - str->len += len; - str->buf[str->len++] = '\0'; - - /* new node */ - node_child = new0(struct strbuf_node, 1); - if (!node_child) - return -ENOMEM; - node_child->value_off = off; - node_child->value_len = len; - - /* extend array, add new entry, sort for bisection */ - child = realloc(node->children, (node->children_count + 1) * sizeof(struct strbuf_child_entry)); - if (!child) { - free(node_child); - return -ENOMEM; - } - - str->nodes_count++; - - node->children = child; - bubbleinsert(node, c, node_child); - - return off; -} diff --git a/src/basic/strbuf.h b/src/basic/strbuf.h deleted file mode 100644 index a1632da0e8..0000000000 --- a/src/basic/strbuf.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include -#include -#include - -struct strbuf { - char *buf; - size_t len; - struct strbuf_node *root; - - size_t nodes_count; - size_t in_count; - size_t in_len; - size_t dedup_len; - size_t dedup_count; -}; - -struct strbuf_node { - size_t value_off; - size_t value_len; - - struct strbuf_child_entry *children; - uint8_t children_count; -}; - -struct strbuf_child_entry { - uint8_t c; - struct strbuf_node *child; -}; - -struct strbuf *strbuf_new(void); -ssize_t strbuf_add_string(struct strbuf *str, const char *s, size_t len); -void strbuf_complete(struct strbuf *str); -void strbuf_cleanup(struct strbuf *str); diff --git a/src/basic/string-table.c b/src/basic/string-table.c deleted file mode 100644 index a1499ab126..0000000000 --- a/src/basic/string-table.c +++ /dev/null @@ -1,34 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "string-table.h" -#include "string-util.h" - -ssize_t string_table_lookup(const char * const *table, size_t len, const char *key) { - size_t i; - - if (!key) - return -1; - - for (i = 0; i < len; ++i) - if (streq_ptr(table[i], key)) - return (ssize_t) i; - - return -1; -} diff --git a/src/basic/string-table.h b/src/basic/string-table.h deleted file mode 100644 index b180488fe8..0000000000 --- a/src/basic/string-table.h +++ /dev/null @@ -1,105 +0,0 @@ - -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "macro.h" -#include "parse-util.h" -#include "string-util.h" - -ssize_t string_table_lookup(const char * const *table, size_t len, const char *key); - -/* For basic lookup tables with strictly enumerated entries */ -#define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ - scope const char *name##_to_string(type i) { \ - if (i < 0 || i >= (type) ELEMENTSOF(name##_table)) \ - return NULL; \ - return name##_table[i]; \ - } - -#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope) \ - scope type name##_from_string(const char *s) { \ - return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \ - } - -#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \ - scope type name##_from_string(const char *s) { \ - int b; \ - b = parse_boolean(s); \ - if (b == 0) \ - return (type) 0; \ - else if (b > 0) \ - return yes; \ - return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \ - } - -#define _DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \ - _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ - _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope) \ - struct __useless_struct_to_allow_trailing_semicolon__ - -#define _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,scope) \ - _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ - _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \ - struct __useless_struct_to_allow_trailing_semicolon__ - -#define DEFINE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,) -#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,static) -#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static) -#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,static) - -#define DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,) - -/* For string conversions where numbers are also acceptable */ -#define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \ - int name##_to_string_alloc(type i, char **str) { \ - char *s; \ - if (i < 0 || i > max) \ - return -ERANGE; \ - if (i < (type) ELEMENTSOF(name##_table)) { \ - s = strdup(name##_table[i]); \ - if (!s) \ - return -ENOMEM; \ - } else { \ - if (asprintf(&s, "%i", i) < 0) \ - return -ENOMEM; \ - } \ - *str = s; \ - return 0; \ - } \ - type name##_from_string(const char *s) { \ - type i; \ - unsigned u = 0; \ - if (!s) \ - return (type) -1; \ - for (i = 0; i < (type) ELEMENTSOF(name##_table); i++) \ - if (streq_ptr(name##_table[i], s)) \ - return i; \ - if (safe_atou(s, &u) >= 0 && u <= max) \ - return (type) u; \ - return (type) -1; \ - } \ - struct __useless_struct_to_allow_trailing_semicolon__ diff --git a/src/basic/string-util.c b/src/basic/string-util.c deleted file mode 100644 index 0bde55f9d5..0000000000 --- a/src/basic/string-util.c +++ /dev/null @@ -1,855 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "gunicode.h" -#include "macro.h" -#include "string-util.h" -#include "utf8.h" -#include "util.h" - -int strcmp_ptr(const char *a, const char *b) { - - /* Like strcmp(), but tries to make sense of NULL pointers */ - if (a && b) - return strcmp(a, b); - - if (!a && b) - return -1; - - if (a && !b) - return 1; - - return 0; -} - -char* endswith(const char *s, const char *postfix) { - size_t sl, pl; - - assert(s); - assert(postfix); - - sl = strlen(s); - pl = strlen(postfix); - - if (pl == 0) - return (char*) s + sl; - - if (sl < pl) - return NULL; - - if (memcmp(s + sl - pl, postfix, pl) != 0) - return NULL; - - return (char*) s + sl - pl; -} - -char* endswith_no_case(const char *s, const char *postfix) { - size_t sl, pl; - - assert(s); - assert(postfix); - - sl = strlen(s); - pl = strlen(postfix); - - if (pl == 0) - return (char*) s + sl; - - if (sl < pl) - return NULL; - - if (strcasecmp(s + sl - pl, postfix) != 0) - return NULL; - - return (char*) s + sl - pl; -} - -char* first_word(const char *s, const char *word) { - size_t sl, wl; - const char *p; - - assert(s); - assert(word); - - /* Checks if the string starts with the specified word, either - * followed by NUL or by whitespace. Returns a pointer to the - * NUL or the first character after the whitespace. */ - - sl = strlen(s); - wl = strlen(word); - - if (sl < wl) - return NULL; - - if (wl == 0) - return (char*) s; - - if (memcmp(s, word, wl) != 0) - return NULL; - - p = s + wl; - if (*p == 0) - return (char*) p; - - if (!strchr(WHITESPACE, *p)) - return NULL; - - p += strspn(p, WHITESPACE); - return (char*) p; -} - -static size_t strcspn_escaped(const char *s, const char *reject) { - bool escaped = false; - int n; - - for (n=0; s[n]; n++) { - if (escaped) - escaped = false; - else if (s[n] == '\\') - escaped = true; - else if (strchr(reject, s[n])) - break; - } - - /* if s ends in \, return index of previous char */ - return n - escaped; -} - -/* Split a string into words. */ -const char* split(const char **state, size_t *l, const char *separator, bool quoted) { - const char *current; - - current = *state; - - if (!*current) { - assert(**state == '\0'); - return NULL; - } - - current += strspn(current, separator); - if (!*current) { - *state = current; - return NULL; - } - - if (quoted && strchr("\'\"", *current)) { - char quotechars[2] = {*current, '\0'}; - - *l = strcspn_escaped(current + 1, quotechars); - if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] || - (current[*l + 2] && !strchr(separator, current[*l + 2]))) { - /* right quote missing or garbage at the end */ - *state = current; - return NULL; - } - *state = current++ + *l + 2; - } else if (quoted) { - *l = strcspn_escaped(current, separator); - if (current[*l] && !strchr(separator, current[*l])) { - /* unfinished escape */ - *state = current; - return NULL; - } - *state = current + *l; - } else { - *l = strcspn(current, separator); - *state = current + *l; - } - - return current; -} - -char *strnappend(const char *s, const char *suffix, size_t b) { - size_t a; - char *r; - - if (!s && !suffix) - return strdup(""); - - if (!s) - return strndup(suffix, b); - - if (!suffix) - return strdup(s); - - assert(s); - assert(suffix); - - a = strlen(s); - if (b > ((size_t) -1) - a) - return NULL; - - r = new(char, a+b+1); - if (!r) - return NULL; - - memcpy(r, s, a); - memcpy(r+a, suffix, b); - r[a+b] = 0; - - return r; -} - -char *strappend(const char *s, const char *suffix) { - return strnappend(s, suffix, suffix ? strlen(suffix) : 0); -} - -char *strjoin(const char *x, ...) { - va_list ap; - size_t l; - char *r, *p; - - va_start(ap, x); - - if (x) { - l = strlen(x); - - for (;;) { - const char *t; - size_t n; - - t = va_arg(ap, const char *); - if (!t) - break; - - n = strlen(t); - if (n > ((size_t) -1) - l) { - va_end(ap); - return NULL; - } - - l += n; - } - } else - l = 0; - - va_end(ap); - - r = new(char, l+1); - if (!r) - return NULL; - - if (x) { - p = stpcpy(r, x); - - va_start(ap, x); - - for (;;) { - const char *t; - - t = va_arg(ap, const char *); - if (!t) - break; - - p = stpcpy(p, t); - } - - va_end(ap); - } else - r[0] = 0; - - return r; -} - -char *strstrip(char *s) { - char *e; - - /* Drops trailing whitespace. Modifies the string in - * place. Returns pointer to first non-space character */ - - s += strspn(s, WHITESPACE); - - for (e = strchr(s, 0); e > s; e --) - if (!strchr(WHITESPACE, e[-1])) - break; - - *e = 0; - - return s; -} - -char *delete_chars(char *s, const char *bad) { - char *f, *t; - - /* Drops all whitespace, regardless where in the string */ - - for (f = s, t = s; *f; f++) { - if (strchr(bad, *f)) - continue; - - *(t++) = *f; - } - - *t = 0; - - return s; -} - -char *truncate_nl(char *s) { - assert(s); - - s[strcspn(s, NEWLINE)] = 0; - return s; -} - -char ascii_tolower(char x) { - - if (x >= 'A' && x <= 'Z') - return x - 'A' + 'a'; - - return x; -} - -char *ascii_strlower(char *t) { - char *p; - - assert(t); - - for (p = t; *p; p++) - *p = ascii_tolower(*p); - - return t; -} - -char *ascii_strlower_n(char *t, size_t n) { - size_t i; - - if (n <= 0) - return t; - - for (i = 0; i < n; i++) - t[i] = ascii_tolower(t[i]); - - return t; -} - -int ascii_strcasecmp_n(const char *a, const char *b, size_t n) { - - for (; n > 0; a++, b++, n--) { - int x, y; - - x = (int) (uint8_t) ascii_tolower(*a); - y = (int) (uint8_t) ascii_tolower(*b); - - if (x != y) - return x - y; - } - - return 0; -} - -int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m) { - int r; - - r = ascii_strcasecmp_n(a, b, MIN(n, m)); - if (r != 0) - return r; - - if (n < m) - return -1; - else if (n > m) - return 1; - else - return 0; -} - -bool chars_intersect(const char *a, const char *b) { - const char *p; - - /* Returns true if any of the chars in a are in b. */ - for (p = a; *p; p++) - if (strchr(b, *p)) - return true; - - return false; -} - -bool string_has_cc(const char *p, const char *ok) { - const char *t; - - assert(p); - - /* - * Check if a string contains control characters. If 'ok' is - * non-NULL it may be a string containing additional CCs to be - * considered OK. - */ - - for (t = p; *t; t++) { - if (ok && strchr(ok, *t)) - continue; - - if (*t > 0 && *t < ' ') - return true; - - if (*t == 127) - return true; - } - - return false; -} - -static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { - size_t x; - char *r; - - assert(s); - assert(percent <= 100); - assert(new_length >= 3); - - if (old_length <= 3 || old_length <= new_length) - return strndup(s, old_length); - - r = new0(char, new_length+1); - if (!r) - return NULL; - - x = (new_length * percent) / 100; - - if (x > new_length - 3) - x = new_length - 3; - - memcpy(r, s, x); - r[x] = '.'; - r[x+1] = '.'; - r[x+2] = '.'; - memcpy(r + x + 3, - s + old_length - (new_length - x - 3), - new_length - x - 3); - - return r; -} - -char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { - size_t x; - char *e; - const char *i, *j; - unsigned k, len, len2; - int r; - - assert(s); - assert(percent <= 100); - assert(new_length >= 3); - - /* if no multibyte characters use ascii_ellipsize_mem for speed */ - if (ascii_is_valid(s)) - return ascii_ellipsize_mem(s, old_length, new_length, percent); - - if (old_length <= 3 || old_length <= new_length) - return strndup(s, old_length); - - x = (new_length * percent) / 100; - - if (x > new_length - 3) - x = new_length - 3; - - k = 0; - for (i = s; k < x && i < s + old_length; i = utf8_next_char(i)) { - char32_t c; - - r = utf8_encoded_to_unichar(i, &c); - if (r < 0) - return NULL; - k += unichar_iswide(c) ? 2 : 1; - } - - if (k > x) /* last character was wide and went over quota */ - x ++; - - for (j = s + old_length; k < new_length && j > i; ) { - char32_t c; - - j = utf8_prev_char(j); - r = utf8_encoded_to_unichar(j, &c); - if (r < 0) - return NULL; - k += unichar_iswide(c) ? 2 : 1; - } - assert(i <= j); - - /* we don't actually need to ellipsize */ - if (i == j) - return memdup(s, old_length + 1); - - /* make space for ellipsis */ - j = utf8_next_char(j); - - len = i - s; - len2 = s + old_length - j; - e = new(char, len + 3 + len2 + 1); - if (!e) - return NULL; - - /* - printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n", - old_length, new_length, x, len, len2, k); - */ - - memcpy(e, s, len); - e[len] = 0xe2; /* tri-dot ellipsis: … */ - e[len + 1] = 0x80; - e[len + 2] = 0xa6; - - memcpy(e + len + 3, j, len2 + 1); - - return e; -} - -char *ellipsize(const char *s, size_t length, unsigned percent) { - return ellipsize_mem(s, strlen(s), length, percent); -} - -bool nulstr_contains(const char*nulstr, const char *needle) { - const char *i; - - if (!nulstr) - return false; - - NULSTR_FOREACH(i, nulstr) - if (streq(i, needle)) - return true; - - return false; -} - -char* strshorten(char *s, size_t l) { - assert(s); - - if (l < strlen(s)) - s[l] = 0; - - return s; -} - -char *strreplace(const char *text, const char *old_string, const char *new_string) { - const char *f; - char *t, *r; - size_t l, old_len, new_len; - - assert(text); - assert(old_string); - assert(new_string); - - old_len = strlen(old_string); - new_len = strlen(new_string); - - l = strlen(text); - r = new(char, l+1); - if (!r) - return NULL; - - f = text; - t = r; - while (*f) { - char *a; - size_t d, nl; - - if (!startswith(f, old_string)) { - *(t++) = *(f++); - continue; - } - - d = t - r; - nl = l - old_len + new_len; - a = realloc(r, nl + 1); - if (!a) - goto oom; - - l = nl; - r = a; - t = r + d; - - t = stpcpy(t, new_string); - f += old_len; - } - - *t = 0; - return r; - -oom: - free(r); - return NULL; -} - -char *strip_tab_ansi(char **ibuf, size_t *_isz) { - const char *i, *begin = NULL; - enum { - STATE_OTHER, - STATE_ESCAPE, - STATE_BRACKET - } state = STATE_OTHER; - char *obuf = NULL; - size_t osz = 0, isz; - FILE *f; - - assert(ibuf); - assert(*ibuf); - - /* Strips ANSI color and replaces TABs by 8 spaces */ - - isz = _isz ? *_isz : strlen(*ibuf); - - f = open_memstream(&obuf, &osz); - if (!f) - return NULL; - - for (i = *ibuf; i < *ibuf + isz + 1; i++) { - - switch (state) { - - case STATE_OTHER: - if (i >= *ibuf + isz) /* EOT */ - break; - else if (*i == '\x1B') - state = STATE_ESCAPE; - else if (*i == '\t') - fputs(" ", f); - else - fputc(*i, f); - break; - - case STATE_ESCAPE: - if (i >= *ibuf + isz) { /* EOT */ - fputc('\x1B', f); - break; - } else if (*i == '[') { - state = STATE_BRACKET; - begin = i + 1; - } else { - fputc('\x1B', f); - fputc(*i, f); - state = STATE_OTHER; - } - - break; - - case STATE_BRACKET: - - if (i >= *ibuf + isz || /* EOT */ - (!(*i >= '0' && *i <= '9') && *i != ';' && *i != 'm')) { - fputc('\x1B', f); - fputc('[', f); - state = STATE_OTHER; - i = begin-1; - } else if (*i == 'm') - state = STATE_OTHER; - break; - } - } - - if (ferror(f)) { - fclose(f); - free(obuf); - return NULL; - } - - fclose(f); - - free(*ibuf); - *ibuf = obuf; - - if (_isz) - *_isz = osz; - - return obuf; -} - -char *strextend(char **x, ...) { - va_list ap; - size_t f, l; - char *r, *p; - - assert(x); - - l = f = *x ? strlen(*x) : 0; - - va_start(ap, x); - for (;;) { - const char *t; - size_t n; - - t = va_arg(ap, const char *); - if (!t) - break; - - n = strlen(t); - if (n > ((size_t) -1) - l) { - va_end(ap); - return NULL; - } - - l += n; - } - va_end(ap); - - r = realloc(*x, l+1); - if (!r) - return NULL; - - p = r + f; - - va_start(ap, x); - for (;;) { - const char *t; - - t = va_arg(ap, const char *); - if (!t) - break; - - p = stpcpy(p, t); - } - va_end(ap); - - *p = 0; - *x = r; - - return r + l; -} - -char *strrep(const char *s, unsigned n) { - size_t l; - char *r, *p; - unsigned i; - - assert(s); - - l = strlen(s); - p = r = malloc(l * n + 1); - if (!r) - return NULL; - - for (i = 0; i < n; i++) - p = stpcpy(p, s); - - *p = 0; - return r; -} - -int split_pair(const char *s, const char *sep, char **l, char **r) { - char *x, *a, *b; - - assert(s); - assert(sep); - assert(l); - assert(r); - - if (isempty(sep)) - return -EINVAL; - - x = strstr(s, sep); - if (!x) - return -EINVAL; - - a = strndup(s, x - s); - if (!a) - return -ENOMEM; - - b = strdup(x + strlen(sep)); - if (!b) { - free(a); - return -ENOMEM; - } - - *l = a; - *r = b; - - return 0; -} - -int free_and_strdup(char **p, const char *s) { - char *t; - - assert(p); - - /* Replaces a string pointer with an strdup()ed new string, - * possibly freeing the old one. */ - - if (streq_ptr(*p, s)) - return 0; - - if (s) { - t = strdup(s); - if (!t) - return -ENOMEM; - } else - t = NULL; - - free(*p); - *p = t; - - return 1; -} - -#pragma GCC push_options -#pragma GCC optimize("O0") - -void* memory_erase(void *p, size_t l) { - volatile uint8_t* x = (volatile uint8_t*) p; - - /* 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; -} - -#pragma GCC pop_options - -char* string_erase(char *x) { - - if (!x) - return NULL; - - /* A delicious drop of snake-oil! To be called on memory where - * we stored passphrases or so, after we used them. */ - - return memory_erase(x, strlen(x)); -} - -char *string_free_erase(char *s) { - return mfree(string_erase(s)); -} - -bool string_is_safe(const char *p) { - const char *t; - - if (!p) - return false; - - for (t = p; *t; t++) { - if (*t > 0 && *t < ' ') /* no control characters */ - return false; - - if (strchr(QUOTES "\\\x7f", *t)) - return false; - } - - return true; -} diff --git a/src/basic/string-util.h b/src/basic/string-util.h deleted file mode 100644 index ad0c813761..0000000000 --- a/src/basic/string-util.h +++ /dev/null @@ -1,189 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "macro.h" - -/* What is interpreted as whitespace? */ -#define WHITESPACE " \t\n\r" -#define NEWLINE "\n\r" -#define QUOTES "\"\'" -#define COMMENTS "#;" -#define GLOB_CHARS "*?[" -#define DIGITS "0123456789" -#define LOWERCASE_LETTERS "abcdefghijklmnopqrstuvwxyz" -#define UPPERCASE_LETTERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ" -#define LETTERS LOWERCASE_LETTERS UPPERCASE_LETTERS -#define ALPHANUMERICAL LETTERS DIGITS - -#define streq(a,b) (strcmp((a),(b)) == 0) -#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0) -#define strcaseeq(a,b) (strcasecmp((a),(b)) == 0) -#define strncaseeq(a, b, n) (strncasecmp((a), (b), (n)) == 0) - -int strcmp_ptr(const char *a, const char *b) _pure_; - -static inline bool streq_ptr(const char *a, const char *b) { - return strcmp_ptr(a, b) == 0; -} - -static inline const char* strempty(const char *s) { - return s ? s : ""; -} - -static inline const char* strnull(const char *s) { - return s ? s : "(null)"; -} - -static inline const char *strna(const char *s) { - return s ? s : "n/a"; -} - -static inline bool isempty(const char *p) { - return !p || !p[0]; -} - -static inline char *startswith(const char *s, const char *prefix) { - size_t l; - - l = strlen(prefix); - if (strncmp(s, prefix, l) == 0) - return (char*) s + l; - - return NULL; -} - -static inline char *startswith_no_case(const char *s, const char *prefix) { - size_t l; - - l = strlen(prefix); - if (strncasecmp(s, prefix, l) == 0) - return (char*) s + l; - - return NULL; -} - -char *endswith(const char *s, const char *postfix) _pure_; -char *endswith_no_case(const char *s, const char *postfix) _pure_; - -char *first_word(const char *s, const char *word) _pure_; - -const char* split(const char **state, size_t *l, const char *separator, bool quoted); - -#define FOREACH_WORD(word, length, s, state) \ - _FOREACH_WORD(word, length, s, WHITESPACE, false, state) - -#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \ - _FOREACH_WORD(word, length, s, separator, false, state) - -#define FOREACH_WORD_QUOTED(word, length, s, state) \ - _FOREACH_WORD(word, length, s, WHITESPACE, true, state) - -#define _FOREACH_WORD(word, length, s, separator, quoted, state) \ - for ((state) = (s), (word) = split(&(state), &(length), (separator), (quoted)); (word); (word) = split(&(state), &(length), (separator), (quoted))) - -char *strappend(const char *s, const char *suffix); -char *strnappend(const char *s, const char *suffix, size_t length); - -char *strjoin(const char *x, ...) _sentinel_; - -#define strjoina(a, ...) \ - ({ \ - const char *_appendees_[] = { a, __VA_ARGS__ }; \ - char *_d_, *_p_; \ - int _len_ = 0; \ - unsigned _i_; \ - for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \ - _len_ += strlen(_appendees_[_i_]); \ - _p_ = _d_ = alloca(_len_ + 1); \ - for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \ - _p_ = stpcpy(_p_, _appendees_[_i_]); \ - *_p_ = 0; \ - _d_; \ - }) - -char *strstrip(char *s); -char *delete_chars(char *s, const char *bad); -char *truncate_nl(char *s); - -char ascii_tolower(char x); -char *ascii_strlower(char *s); -char *ascii_strlower_n(char *s, size_t n); - -int ascii_strcasecmp_n(const char *a, const char *b, size_t n); -int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m); - -bool chars_intersect(const char *a, const char *b) _pure_; - -static inline bool _pure_ in_charset(const char *s, const char* charset) { - assert(s); - assert(charset); - return s[strspn(s, charset)] == '\0'; -} - -bool string_has_cc(const char *p, const char *ok) _pure_; - -char *ellipsize_mem(const char *s, size_t old_length_bytes, size_t new_length_columns, unsigned percent); -char *ellipsize(const char *s, size_t length, unsigned percent); - -bool nulstr_contains(const char*nulstr, const char *needle); - -char* strshorten(char *s, size_t l); - -char *strreplace(const char *text, const char *old_string, const char *new_string); - -char *strip_tab_ansi(char **p, size_t *l); - -char *strextend(char **x, ...) _sentinel_; - -char *strrep(const char *s, unsigned n); - -int split_pair(const char *s, const char *sep, char **l, char **r); - -int free_and_strdup(char **p, const char *s); - -/* Normal memmem() requires haystack to be nonnull, which is annoying for zero-length buffers */ -static inline void *memmem_safe(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { - - if (needlelen <= 0) - return (void*) haystack; - - if (haystacklen < needlelen) - return NULL; - - assert(haystack); - assert(needle); - - return memmem(haystack, haystacklen, needle, needlelen); -} - -void* memory_erase(void *p, size_t l); -char *string_erase(char *x); - -char *string_free_erase(char *s); -DEFINE_TRIVIAL_CLEANUP_FUNC(char *, string_free_erase); -#define _cleanup_string_free_erase_ _cleanup_(string_free_erasep) - -bool string_is_safe(const char *p) _pure_; diff --git a/src/basic/strv.c b/src/basic/strv.c deleted file mode 100644 index b5d4d8191b..0000000000 --- a/src/basic/strv.c +++ /dev/null @@ -1,891 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "escape.h" -#include "extract-word.h" -#include "fileio.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -char *strv_find(char **l, const char *name) { - char **i; - - assert(name); - - STRV_FOREACH(i, l) - if (streq(*i, name)) - return *i; - - return NULL; -} - -char *strv_find_prefix(char **l, const char *name) { - char **i; - - assert(name); - - STRV_FOREACH(i, l) - if (startswith(*i, name)) - return *i; - - return NULL; -} - -char *strv_find_startswith(char **l, const char *name) { - char **i, *e; - - assert(name); - - /* Like strv_find_prefix, but actually returns only the - * suffix, not the whole item */ - - STRV_FOREACH(i, l) { - e = startswith(*i, name); - if (e) - return e; - } - - return NULL; -} - -void strv_clear(char **l) { - char **k; - - if (!l) - return; - - for (k = l; *k; k++) - free(*k); - - *l = NULL; -} - -char **strv_free(char **l) { - strv_clear(l); - free(l); - return NULL; -} - -char **strv_free_erase(char **l) { - char **i; - - STRV_FOREACH(i, l) - string_erase(*i); - - return strv_free(l); -} - -char **strv_copy(char * const *l) { - char **r, **k; - - k = r = new(char*, strv_length(l) + 1); - if (!r) - return NULL; - - if (l) - for (; *l; k++, l++) { - *k = strdup(*l); - if (!*k) { - strv_free(r); - return NULL; - } - } - - *k = NULL; - return r; -} - -unsigned strv_length(char * const *l) { - unsigned n = 0; - - if (!l) - return 0; - - for (; *l; l++) - n++; - - return n; -} - -char **strv_new_ap(const char *x, va_list ap) { - const char *s; - char **a; - unsigned n = 0, i = 0; - 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_IFNOTNULL() macro to include possibly NULL strings in - * the string list. */ - - if (x) { - n = x == (const char*) -1 ? 0 : 1; - - va_copy(aq, ap); - while ((s = va_arg(aq, const char*))) { - if (s == (const char*) -1) - continue; - - n++; - } - - va_end(aq); - } - - a = new(char*, n+1); - if (!a) - return NULL; - - if (x) { - if (x != (const char*) -1) { - a[i] = strdup(x); - if (!a[i]) - goto fail; - i++; - } - - while ((s = va_arg(ap, const char*))) { - - if (s == (const char*) -1) - continue; - - a[i] = strdup(s); - if (!a[i]) - goto fail; - - i++; - } - } - - a[i] = NULL; - - return a; - -fail: - strv_free(a); - return NULL; -} - -char **strv_new(const char *x, ...) { - char **r; - va_list ap; - - va_start(ap, x); - r = strv_new_ap(x, ap); - va_end(ap); - - return r; -} - -int strv_extend_strv(char ***a, char **b, bool filter_duplicates) { - char **s, **t; - size_t p, q, i = 0, j; - - assert(a); - - if (strv_isempty(b)) - return 0; - - p = strv_length(*a); - q = strv_length(b); - - t = realloc(*a, sizeof(char*) * (p + q + 1)); - if (!t) - return -ENOMEM; - - t[p] = NULL; - *a = t; - - STRV_FOREACH(s, b) { - - if (filter_duplicates && strv_contains(t, *s)) - continue; - - t[p+i] = strdup(*s); - if (!t[p+i]) - goto rollback; - - i++; - t[p+i] = NULL; - } - - assert(i <= q); - - return (int) i; - -rollback: - for (j = 0; j < i; j++) - free(t[p + j]); - - t[p] = NULL; - return -ENOMEM; -} - -int strv_extend_strv_concat(char ***a, char **b, const char *suffix) { - int r; - char **s; - - STRV_FOREACH(s, b) { - char *v; - - v = strappend(*s, suffix); - if (!v) - return -ENOMEM; - - r = strv_push(a, v); - if (r < 0) { - free(v); - return r; - } - } - - return 0; -} - -char **strv_split(const char *s, const char *separator) { - const char *word, *state; - size_t l; - unsigned n, i; - char **r; - - assert(s); - - n = 0; - FOREACH_WORD_SEPARATOR(word, l, s, separator, state) - n++; - - r = new(char*, n+1); - if (!r) - return NULL; - - i = 0; - FOREACH_WORD_SEPARATOR(word, l, s, separator, state) { - r[i] = strndup(word, l); - if (!r[i]) { - strv_free(r); - return NULL; - } - - i++; - } - - r[i] = NULL; - return r; -} - -char **strv_split_newlines(const char *s) { - char **l; - unsigned n; - - assert(s); - - /* Special version of strv_split() that splits on newlines and - * suppresses an empty string at the end */ - - l = strv_split(s, NEWLINE); - if (!l) - return NULL; - - n = strv_length(l); - if (n <= 0) - return l; - - if (isempty(l[n - 1])) - l[n - 1] = mfree(l[n - 1]); - - return l; -} - -int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags) { - _cleanup_strv_free_ char **l = NULL; - size_t n = 0, allocated = 0; - int r; - - assert(t); - assert(s); - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&s, &word, separators, flags); - if (r < 0) - return r; - if (r == 0) - break; - - if (!GREEDY_REALLOC(l, allocated, n + 2)) - return -ENOMEM; - - l[n++] = word; - word = NULL; - - l[n] = NULL; - } - - if (!l) { - l = new0(char*, 1); - if (!l) - return -ENOMEM; - } - - *t = l; - l = NULL; - - return (int) n; -} - -char *strv_join(char **l, const char *separator) { - char *r, *e; - char **s; - size_t n, k; - - if (!separator) - separator = " "; - - k = strlen(separator); - - n = 0; - STRV_FOREACH(s, l) { - if (n != 0) - n += k; - n += strlen(*s); - } - - r = new(char, n+1); - if (!r) - return NULL; - - e = r; - STRV_FOREACH(s, l) { - if (e != r) - e = stpcpy(e, separator); - - e = stpcpy(e, *s); - } - - *e = 0; - - return r; -} - -char *strv_join_quoted(char **l) { - char *buf = NULL; - char **s; - size_t allocated = 0, len = 0; - - STRV_FOREACH(s, l) { - /* assuming here that escaped string cannot be more - * than twice as long, and reserving space for the - * separator and quotes. - */ - _cleanup_free_ char *esc = NULL; - size_t needed; - - if (!GREEDY_REALLOC(buf, allocated, - len + strlen(*s) * 2 + 3)) - goto oom; - - esc = cescape(*s); - if (!esc) - goto oom; - - needed = snprintf(buf + len, allocated - len, "%s\"%s\"", - len > 0 ? " " : "", esc); - assert(needed < allocated - len); - len += needed; - } - - if (!buf) - buf = malloc0(1); - - return buf; - - oom: - free(buf); - return NULL; -} - -int strv_push(char ***l, char *value) { - char **c; - unsigned n, m; - - if (!value) - return 0; - - n = strv_length(*l); - - /* Increase and check for overflow */ - m = n + 2; - if (m < n) - return -ENOMEM; - - c = realloc_multiply(*l, sizeof(char*), m); - if (!c) - return -ENOMEM; - - c[n] = value; - c[n+1] = NULL; - - *l = c; - return 0; -} - -int strv_push_pair(char ***l, char *a, char *b) { - char **c; - unsigned n, m; - - if (!a && !b) - return 0; - - n = strv_length(*l); - - /* increase and check for overflow */ - m = n + !!a + !!b + 1; - if (m < n) - return -ENOMEM; - - c = realloc_multiply(*l, sizeof(char*), m); - if (!c) - return -ENOMEM; - - if (a) - c[n++] = a; - if (b) - c[n++] = b; - c[n] = NULL; - - *l = c; - return 0; -} - -int strv_push_prepend(char ***l, char *value) { - char **c; - unsigned n, m, i; - - if (!value) - return 0; - - n = strv_length(*l); - - /* increase and check for overflow */ - m = n + 2; - if (m < n) - return -ENOMEM; - - c = new(char*, m); - if (!c) - return -ENOMEM; - - for (i = 0; i < n; i++) - c[i+1] = (*l)[i]; - - c[0] = value; - c[n+1] = NULL; - - free(*l); - *l = c; - - return 0; -} - -int strv_consume(char ***l, char *value) { - int r; - - r = strv_push(l, value); - if (r < 0) - free(value); - - return r; -} - -int strv_consume_pair(char ***l, char *a, char *b) { - int r; - - r = strv_push_pair(l, a, b); - if (r < 0) { - free(a); - free(b); - } - - return r; -} - -int strv_consume_prepend(char ***l, char *value) { - int r; - - r = strv_push_prepend(l, value); - if (r < 0) - free(value); - - return r; -} - -int strv_extend(char ***l, const char *value) { - char *v; - - if (!value) - return 0; - - v = strdup(value); - if (!v) - return -ENOMEM; - - return strv_consume(l, v); -} - -char **strv_uniq(char **l) { - char **i; - - /* Drops duplicate entries. The first identical string will be - * kept, the others dropped */ - - STRV_FOREACH(i, l) - strv_remove(i+1, *i); - - return l; -} - -bool strv_is_uniq(char **l) { - char **i; - - STRV_FOREACH(i, l) - if (strv_find(i+1, *i)) - return false; - - return true; -} - -char **strv_remove(char **l, const char *s) { - char **f, **t; - - if (!l) - return NULL; - - assert(s); - - /* Drops every occurrence of s in the string list, edits - * in-place. */ - - for (f = t = l; *f; f++) - if (streq(*f, s)) - free(*f); - else - *(t++) = *f; - - *t = NULL; - return l; -} - -char **strv_parse_nulstr(const char *s, size_t l) { - const char *p; - unsigned c = 0, i = 0; - char **v; - - assert(s || l <= 0); - - if (l <= 0) - return new0(char*, 1); - - for (p = s; p < s + l; p++) - if (*p == 0) - c++; - - if (s[l-1] != 0) - c++; - - v = new0(char*, c+1); - if (!v) - return NULL; - - p = s; - while (p < s + l) { - const char *e; - - e = memchr(p, 0, s + l - p); - - v[i] = strndup(p, e ? e - p : s + l - p); - if (!v[i]) { - strv_free(v); - return NULL; - } - - i++; - - if (!e) - break; - - p = e + 1; - } - - assert(i == c); - - return v; -} - -char **strv_split_nulstr(const char *s) { - const char *i; - char **r = NULL; - - NULSTR_FOREACH(i, s) - if (strv_extend(&r, i) < 0) { - strv_free(r); - return NULL; - } - - if (!r) - return strv_new(NULL, NULL); - - return r; -} - -int strv_make_nulstr(char **l, char **p, size_t *q) { - size_t n_allocated = 0, n = 0; - _cleanup_free_ char *m = NULL; - char **i; - - assert(p); - assert(q); - - STRV_FOREACH(i, l) { - size_t z; - - z = strlen(*i); - - if (!GREEDY_REALLOC(m, n_allocated, n + z + 1)) - return -ENOMEM; - - memcpy(m + n, *i, z + 1); - n += z + 1; - } - - if (!m) { - m = new0(char, 1); - if (!m) - return -ENOMEM; - n = 0; - } - - *p = m; - *q = n; - - m = NULL; - - return 0; -} - -bool strv_overlap(char **a, char **b) { - char **i; - - STRV_FOREACH(i, a) - if (strv_contains(b, *i)) - return true; - - return false; -} - -static int str_compare(const void *_a, const void *_b) { - const char **a = (const char**) _a, **b = (const char**) _b; - - return strcmp(*a, *b); -} - -char **strv_sort(char **l) { - - if (strv_isempty(l)) - return l; - - qsort(l, strv_length(l), sizeof(char*), str_compare); - return l; -} - -bool strv_equal(char **a, char **b) { - - if (strv_isempty(a)) - return strv_isempty(b); - - if (strv_isempty(b)) - return false; - - for ( ; *a || *b; ++a, ++b) - if (!streq_ptr(*a, *b)) - return false; - - return true; -} - -void strv_print(char **l) { - char **s; - - STRV_FOREACH(s, l) - puts(*s); -} - -int strv_extendf(char ***l, const char *format, ...) { - va_list ap; - char *x; - int r; - - va_start(ap, format); - r = vasprintf(&x, format, ap); - va_end(ap); - - if (r < 0) - return -ENOMEM; - - return strv_consume(l, x); -} - -char **strv_reverse(char **l) { - unsigned n, i; - - n = strv_length(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; - } - - return l; -} - -char **strv_shell_escape(char **l, const char *bad) { - char **s; - - /* Escapes every character in every string in l that is in bad, - * edits in-place, does not roll-back on error. */ - - STRV_FOREACH(s, l) { - char *v; - - v = shell_escape(*s, bad); - if (!v) - return NULL; - - free(*s); - *s = v; - } - - return l; -} - -bool strv_fnmatch(char* const* patterns, const char *s, int flags) { - char* const* p; - - STRV_FOREACH(p, patterns) - if (fnmatch(*p, s, 0) == 0) - return true; - - return false; -} - -char ***strv_free_free(char ***l) { - char ***i; - - if (!l) - return NULL; - - for (i = l; *i; i++) - strv_free(*i); - - free(l); - return NULL; -} - -char **strv_skip(char **l, size_t n) { - - while (n > 0) { - if (strv_isempty(l)) - return l; - - l++, n--; - } - - return l; -} - -int strv_extend_n(char ***l, const char *value, size_t n) { - size_t i, j, k; - char **nl; - - assert(l); - - if (!value) - return 0; - if (n == 0) - return 0; - - /* Adds the value value n times to l */ - - k = strv_length(*l); - - nl = realloc(*l, sizeof(char*) * (k + n + 1)); - if (!nl) - return -ENOMEM; - - *l = nl; - - for (i = k; i < k + n; i++) { - nl[i] = strdup(value); - if (!nl[i]) - goto rollback; - } - - nl[i] = NULL; - return 0; - -rollback: - for (j = k; j < i; j++) - free(nl[j]); - - nl[k] = NULL; - return -ENOMEM; -} - -int fputstrv(FILE *f, char **l, const char *separator, bool *space) { - bool b = false; - char **s; - int r; - - /* Like fputs(), but for strv, and with a less stupid argument order */ - - if (!space) - space = &b; - - STRV_FOREACH(s, l) { - r = fputs_with_space(f, *s, separator, space); - if (r < 0) - return r; - } - - return 0; -} diff --git a/src/basic/strv.h b/src/basic/strv.h deleted file mode 100644 index 7bfa54408d..0000000000 --- a/src/basic/strv.h +++ /dev/null @@ -1,171 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "alloc-util.h" -#include "extract-word.h" -#include "macro.h" -#include "util.h" - -char *strv_find(char **l, const char *name) _pure_; -char *strv_find_prefix(char **l, const char *name) _pure_; -char *strv_find_startswith(char **l, const char *name) _pure_; - -char **strv_free(char **l); -DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free); -#define _cleanup_strv_free_ _cleanup_(strv_freep) - -char **strv_free_erase(char **l); -DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase); -#define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep) - -void strv_clear(char **l); - -char **strv_copy(char * const *l); -unsigned strv_length(char * const *l) _pure_; - -int strv_extend_strv(char ***a, char **b, bool filter_duplicates); -int strv_extend_strv_concat(char ***a, char **b, const char *suffix); -int strv_extend(char ***l, const char *value); -int strv_extendf(char ***l, const char *format, ...) _printf_(2,0); -int strv_push(char ***l, char *value); -int strv_push_pair(char ***l, char *a, char *b); -int strv_push_prepend(char ***l, char *value); -int strv_consume(char ***l, char *value); -int strv_consume_pair(char ***l, char *a, char *b); -int strv_consume_prepend(char ***l, char *value); - -char **strv_remove(char **l, const char *s); -char **strv_uniq(char **l); -bool strv_is_uniq(char **l); - -bool strv_equal(char **a, char **b); - -#define strv_contains(l, s) (!!strv_find((l), (s))) - -char **strv_new(const char *x, ...) _sentinel_; -char **strv_new_ap(const char *x, va_list ap); - -static inline const char* STRV_IFNOTNULL(const char *x) { - return x ? x : (const char *) -1; -} - -static inline bool strv_isempty(char * const *l) { - return !l || !*l; -} - -char **strv_split(const char *s, const char *separator); -char **strv_split_newlines(const char *s); - -int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags); - -char *strv_join(char **l, const char *separator); -char *strv_join_quoted(char **l); - -char **strv_parse_nulstr(const char *s, size_t l); -char **strv_split_nulstr(const char *s); -int strv_make_nulstr(char **l, char **p, size_t *n); - -bool strv_overlap(char **a, char **b) _pure_; - -#define STRV_FOREACH(s, l) \ - for ((s) = (l); (s) && *(s); (s)++) - -#define STRV_FOREACH_BACKWARDS(s, l) \ - STRV_FOREACH(s, l) \ - ; \ - for ((s)--; (l) && ((s) >= (l)); (s)--) - -#define STRV_FOREACH_PAIR(x, y, l) \ - for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1)) - -char **strv_sort(char **l); -void strv_print(char **l); - -#define STRV_MAKE(...) ((char**) ((const char*[]) { __VA_ARGS__, NULL })) - -#define STRV_MAKE_EMPTY ((char*[1]) { NULL }) - -#define strv_from_stdarg_alloca(first) \ - ({ \ - char **_l; \ - \ - if (!first) \ - _l = (char**) &first; \ - else { \ - unsigned _n; \ - va_list _ap; \ - \ - _n = 1; \ - va_start(_ap, first); \ - while (va_arg(_ap, char*)) \ - _n++; \ - va_end(_ap); \ - \ - _l = newa(char*, _n+1); \ - _l[_n = 0] = (char*) first; \ - va_start(_ap, first); \ - for (;;) { \ - _l[++_n] = va_arg(_ap, char*); \ - if (!_l[_n]) \ - break; \ - } \ - va_end(_ap); \ - } \ - _l; \ - }) - -#define STR_IN_SET(x, ...) strv_contains(STRV_MAKE(__VA_ARGS__), x) - -#define FOREACH_STRING(x, ...) \ - for (char **_l = ({ \ - char **_ll = STRV_MAKE(__VA_ARGS__); \ - x = _ll ? _ll[0] : NULL; \ - _ll; \ - }); \ - _l && *_l; \ - x = ({ \ - _l ++; \ - _l[0]; \ - })) - -char **strv_reverse(char **l); -char **strv_shell_escape(char **l, const char *bad); - -bool strv_fnmatch(char* const* patterns, const char *s, int flags); - -static inline bool strv_fnmatch_or_empty(char* const* patterns, const char *s, int flags) { - assert(s); - return strv_isempty(patterns) || - strv_fnmatch(patterns, s, flags); -} - -char ***strv_free_free(char ***l); - -char **strv_skip(char **l, size_t n); - -int strv_extend_n(char ***l, const char *value, size_t n); - -int fputstrv(FILE *f, char **l, const char *separator, bool *space); diff --git a/src/basic/strxcpyx.c b/src/basic/strxcpyx.c deleted file mode 100644 index aaf11d21f6..0000000000 --- a/src/basic/strxcpyx.c +++ /dev/null @@ -1,100 +0,0 @@ -/*** - This file is part of systemd. - - 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 . -***/ - -/* - * Concatenates/copies strings. In any case, terminates in all cases - * with '\0' * and moves the @dest pointer forward to the added '\0'. - * Returns the * remaining size, and 0 if the string was truncated. - */ - -#include -#include -#include - -#include "strxcpyx.h" - -size_t strpcpy(char **dest, size_t size, const char *src) { - size_t len; - - len = strlen(src); - if (len >= size) { - if (size > 1) - *dest = mempcpy(*dest, src, size-1); - size = 0; - } else { - if (len > 0) { - *dest = mempcpy(*dest, src, len); - size -= len; - } - } - *dest[0] = '\0'; - return size; -} - -size_t strpcpyf(char **dest, size_t size, const char *src, ...) { - va_list va; - int i; - - va_start(va, src); - i = vsnprintf(*dest, size, src, va); - if (i < (int)size) { - *dest += i; - size -= i; - } else { - *dest += size; - size = 0; - } - va_end(va); - *dest[0] = '\0'; - return size; -} - -size_t strpcpyl(char **dest, size_t size, const char *src, ...) { - va_list va; - - va_start(va, src); - do { - size = strpcpy(dest, size, src); - src = va_arg(va, char *); - } while (src != NULL); - va_end(va); - return size; -} - -size_t strscpy(char *dest, size_t size, const char *src) { - char *s; - - s = dest; - return strpcpy(&s, size, src); -} - -size_t strscpyl(char *dest, size_t size, const char *src, ...) { - va_list va; - char *s; - - va_start(va, src); - s = dest; - do { - size = strpcpy(&s, size, src); - src = va_arg(va, char *); - } while (src != NULL); - va_end(va); - - return size; -} diff --git a/src/basic/strxcpyx.h b/src/basic/strxcpyx.h deleted file mode 100644 index 80ff58726b..0000000000 --- a/src/basic/strxcpyx.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - 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 . -***/ - - -#include - -#include "macro.h" - -size_t strpcpy(char **dest, size_t size, const char *src); -size_t strpcpyf(char **dest, size_t size, const char *src, ...) _printf_(3, 4); -size_t strpcpyl(char **dest, size_t size, const char *src, ...) _sentinel_; -size_t strscpy(char *dest, size_t size, const char *src); -size_t strscpyl(char *dest, size_t size, const char *src, ...) _sentinel_; diff --git a/src/basic/syslog-util.c b/src/basic/syslog-util.c deleted file mode 100644 index db3405154e..0000000000 --- a/src/basic/syslog-util.c +++ /dev/null @@ -1,114 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "hexdecoct.h" -#include "macro.h" -#include "string-table.h" -#include "syslog-util.h" - -int syslog_parse_priority(const char **p, int *priority, bool with_facility) { - int a = 0, b = 0, c = 0; - int k; - - assert(p); - assert(*p); - assert(priority); - - if ((*p)[0] != '<') - return 0; - - if (!strchr(*p, '>')) - return 0; - - if ((*p)[2] == '>') { - c = undecchar((*p)[1]); - k = 3; - } else if ((*p)[3] == '>') { - b = undecchar((*p)[1]); - c = undecchar((*p)[2]); - k = 4; - } else if ((*p)[4] == '>') { - a = undecchar((*p)[1]); - b = undecchar((*p)[2]); - c = undecchar((*p)[3]); - k = 5; - } else - return 0; - - if (a < 0 || b < 0 || c < 0 || - (!with_facility && (a || b || c > 7))) - return 0; - - if (with_facility) - *priority = a*100 + b*10 + c; - else - *priority = (*priority & LOG_FACMASK) | c; - - *p += k; - return 1; -} - -static const char *const log_facility_unshifted_table[LOG_NFACILITIES] = { - [LOG_FAC(LOG_KERN)] = "kern", - [LOG_FAC(LOG_USER)] = "user", - [LOG_FAC(LOG_MAIL)] = "mail", - [LOG_FAC(LOG_DAEMON)] = "daemon", - [LOG_FAC(LOG_AUTH)] = "auth", - [LOG_FAC(LOG_SYSLOG)] = "syslog", - [LOG_FAC(LOG_LPR)] = "lpr", - [LOG_FAC(LOG_NEWS)] = "news", - [LOG_FAC(LOG_UUCP)] = "uucp", - [LOG_FAC(LOG_CRON)] = "cron", - [LOG_FAC(LOG_AUTHPRIV)] = "authpriv", - [LOG_FAC(LOG_FTP)] = "ftp", - [LOG_FAC(LOG_LOCAL0)] = "local0", - [LOG_FAC(LOG_LOCAL1)] = "local1", - [LOG_FAC(LOG_LOCAL2)] = "local2", - [LOG_FAC(LOG_LOCAL3)] = "local3", - [LOG_FAC(LOG_LOCAL4)] = "local4", - [LOG_FAC(LOG_LOCAL5)] = "local5", - [LOG_FAC(LOG_LOCAL6)] = "local6", - [LOG_FAC(LOG_LOCAL7)] = "local7" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_facility_unshifted, int, LOG_FAC(~0)); - -bool log_facility_unshifted_is_valid(int facility) { - return facility >= 0 && facility <= LOG_FAC(~0); -} - -static const char *const log_level_table[] = { - [LOG_EMERG] = "emerg", - [LOG_ALERT] = "alert", - [LOG_CRIT] = "crit", - [LOG_ERR] = "err", - [LOG_WARNING] = "warning", - [LOG_NOTICE] = "notice", - [LOG_INFO] = "info", - [LOG_DEBUG] = "debug" -}; - -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_level, int, LOG_DEBUG); - -bool log_level_is_valid(int level) { - return level >= 0 && level <= LOG_DEBUG; -} diff --git a/src/basic/syslog-util.h b/src/basic/syslog-util.h deleted file mode 100644 index 5cb606a1bf..0000000000 --- a/src/basic/syslog-util.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -int log_facility_unshifted_to_string_alloc(int i, char **s); -int log_facility_unshifted_from_string(const char *s); -bool log_facility_unshifted_is_valid(int faciliy); - -int log_level_to_string_alloc(int i, char **s); -int log_level_from_string(const char *s); -bool log_level_is_valid(int level); - -int syslog_parse_priority(const char **p, int *priority, bool with_facility); diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c deleted file mode 100644 index 0a9d2bbdef..0000000000 --- a/src/basic/terminal-util.c +++ /dev/null @@ -1,1148 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "io-util.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "process-util.h" -#include "socket-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "terminal-util.h" -#include "time-util.h" -#include "util.h" - -static volatile unsigned cached_columns = 0; -static volatile unsigned cached_lines = 0; - -int chvt(int vt) { - _cleanup_close_ int fd; - - fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (fd < 0) - return -errno; - - if (vt <= 0) { - int tiocl[2] = { - TIOCL_GETKMSGREDIRECT, - 0 - }; - - if (ioctl(fd, TIOCLINUX, tiocl) < 0) - return -errno; - - vt = tiocl[0] <= 0 ? 1 : tiocl[0]; - } - - if (ioctl(fd, VT_ACTIVATE, vt) < 0) - return -errno; - - return 0; -} - -int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) { - struct termios old_termios, new_termios; - char c, line[LINE_MAX]; - - assert(f); - assert(ret); - - if (tcgetattr(fileno(f), &old_termios) >= 0) { - new_termios = old_termios; - - new_termios.c_lflag &= ~ICANON; - new_termios.c_cc[VMIN] = 1; - new_termios.c_cc[VTIME] = 0; - - if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) { - size_t k; - - if (t != USEC_INFINITY) { - if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) { - tcsetattr(fileno(f), TCSADRAIN, &old_termios); - return -ETIMEDOUT; - } - } - - k = fread(&c, 1, 1, f); - - tcsetattr(fileno(f), TCSADRAIN, &old_termios); - - if (k <= 0) - return -EIO; - - if (need_nl) - *need_nl = c != '\n'; - - *ret = c; - return 0; - } - } - - if (t != USEC_INFINITY) { - if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) - return -ETIMEDOUT; - } - - errno = 0; - if (!fgets(line, sizeof(line), f)) - return errno > 0 ? -errno : -EIO; - - truncate_nl(line); - - if (strlen(line) != 1) - return -EBADMSG; - - if (need_nl) - *need_nl = false; - - *ret = line[0]; - return 0; -} - -int ask_char(char *ret, const char *replies, const char *text, ...) { - int r; - - assert(ret); - assert(replies); - assert(text); - - for (;;) { - va_list ap; - char c; - bool need_nl = true; - - if (on_tty()) - fputs(ANSI_HIGHLIGHT, stdout); - - va_start(ap, text); - vprintf(text, ap); - va_end(ap); - - if (on_tty()) - fputs(ANSI_NORMAL, stdout); - - fflush(stdout); - - r = read_one_char(stdin, &c, USEC_INFINITY, &need_nl); - if (r < 0) { - - if (r == -EBADMSG) { - puts("Bad input, please try again."); - continue; - } - - putchar('\n'); - return r; - } - - if (need_nl) - putchar('\n'); - - if (strchr(replies, c)) { - *ret = c; - return 0; - } - - puts("Read unexpected character, please try again."); - } -} - -int ask_string(char **ret, const char *text, ...) { - assert(ret); - assert(text); - - for (;;) { - char line[LINE_MAX]; - va_list ap; - - if (on_tty()) - fputs(ANSI_HIGHLIGHT, stdout); - - va_start(ap, text); - vprintf(text, ap); - va_end(ap); - - if (on_tty()) - fputs(ANSI_NORMAL, stdout); - - fflush(stdout); - - errno = 0; - if (!fgets(line, sizeof(line), stdin)) - return errno > 0 ? -errno : -EIO; - - if (!endswith(line, "\n")) - putchar('\n'); - else { - char *s; - - if (isempty(line)) - continue; - - truncate_nl(line); - s = strdup(line); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; - } - } -} - -int reset_terminal_fd(int fd, bool switch_to_text) { - struct termios termios; - int r = 0; - - /* Set terminal to some sane defaults */ - - assert(fd >= 0); - - /* We leave locked terminal attributes untouched, so that - * Plymouth may set whatever it wants to set, and we don't - * interfere with that. */ - - /* Disable exclusive mode, just in case */ - (void) ioctl(fd, TIOCNXCL); - - /* Switch to text mode */ - if (switch_to_text) - (void) ioctl(fd, KDSETMODE, KD_TEXT); - - /* Enable console unicode mode */ - (void) ioctl(fd, KDSKBMODE, K_UNICODE); - - if (tcgetattr(fd, &termios) < 0) { - r = -errno; - goto finish; - } - - /* We only reset the stuff that matters to the software. How - * hardware is set up we don't touch assuming that somebody - * else will do that for us */ - - termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC); - termios.c_iflag |= ICRNL | IMAXBEL | IUTF8; - termios.c_oflag |= ONLCR; - termios.c_cflag |= CREAD; - termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE; - - termios.c_cc[VINTR] = 03; /* ^C */ - termios.c_cc[VQUIT] = 034; /* ^\ */ - termios.c_cc[VERASE] = 0177; - termios.c_cc[VKILL] = 025; /* ^X */ - termios.c_cc[VEOF] = 04; /* ^D */ - termios.c_cc[VSTART] = 021; /* ^Q */ - termios.c_cc[VSTOP] = 023; /* ^S */ - termios.c_cc[VSUSP] = 032; /* ^Z */ - termios.c_cc[VLNEXT] = 026; /* ^V */ - termios.c_cc[VWERASE] = 027; /* ^W */ - termios.c_cc[VREPRINT] = 022; /* ^R */ - termios.c_cc[VEOL] = 0; - termios.c_cc[VEOL2] = 0; - - termios.c_cc[VTIME] = 0; - termios.c_cc[VMIN] = 1; - - if (tcsetattr(fd, TCSANOW, &termios) < 0) - r = -errno; - -finish: - /* Just in case, flush all crap out */ - (void) tcflush(fd, TCIOFLUSH); - - return r; -} - -int reset_terminal(const char *name) { - _cleanup_close_ int fd = -1; - - /* We open the terminal with O_NONBLOCK here, to ensure we - * don't block on carrier if this is a terminal with carrier - * configured. */ - - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (fd < 0) - return fd; - - return reset_terminal_fd(fd, true); -} - -int open_terminal(const char *name, int mode) { - int fd, r; - unsigned c = 0; - - /* - * If a TTY is in the process of being closed opening it might - * cause EIO. This is horribly awful, but unlikely to be - * changed in the kernel. Hence we work around this problem by - * retrying a couple of times. - * - * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 - */ - - if (mode & O_CREAT) - return -EINVAL; - - for (;;) { - fd = open(name, mode, 0); - if (fd >= 0) - break; - - if (errno != EIO) - return -errno; - - /* Max 1s in total */ - if (c >= 20) - return -errno; - - usleep(50 * USEC_PER_MSEC); - c++; - } - - r = isatty(fd); - if (r < 0) { - safe_close(fd); - return -errno; - } - - if (!r) { - safe_close(fd); - return -ENOTTY; - } - - return fd; -} - -int acquire_terminal( - const char *name, - bool fail, - bool force, - bool ignore_tiocstty_eperm, - usec_t timeout) { - - int fd = -1, notify = -1, r = 0, wd = -1; - usec_t ts = 0; - - assert(name); - - /* We use inotify to be notified when the tty is closed. We - * create the watch before checking if we can actually acquire - * it, so that we don't lose any event. - * - * Note: strictly speaking this actually watches for the - * device being closed, it does *not* really watch whether a - * tty loses its controlling process. However, unless some - * rogue process uses TIOCNOTTY on /dev/tty *after* closing - * its tty otherwise this will not become a problem. As long - * as the administrator makes sure not configure any service - * on the same tty as an untrusted user this should not be a - * problem. (Which he probably should not do anyway.) */ - - if (timeout != USEC_INFINITY) - ts = now(CLOCK_MONOTONIC); - - if (!fail && !force) { - notify = inotify_init1(IN_CLOEXEC | (timeout != USEC_INFINITY ? IN_NONBLOCK : 0)); - if (notify < 0) { - r = -errno; - goto fail; - } - - wd = inotify_add_watch(notify, name, IN_CLOSE); - if (wd < 0) { - r = -errno; - goto fail; - } - } - - for (;;) { - struct sigaction sa_old, sa_new = { - .sa_handler = SIG_IGN, - .sa_flags = SA_RESTART, - }; - - if (notify >= 0) { - r = flush_fd(notify); - if (r < 0) - goto fail; - } - - /* We pass here O_NOCTTY only so that we can check the return - * value TIOCSCTTY and have a reliable way to figure out if we - * successfully became the controlling process of the tty */ - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed - * if we already own the tty. */ - assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); - - /* First, try to get the tty */ - if (ioctl(fd, TIOCSCTTY, force) < 0) - r = -errno; - - assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); - - /* Sometimes, it makes sense to ignore TIOCSCTTY - * returning EPERM, i.e. when very likely we already - * are have this controlling terminal. */ - if (r < 0 && r == -EPERM && ignore_tiocstty_eperm) - r = 0; - - if (r < 0 && (force || fail || r != -EPERM)) - goto fail; - - if (r >= 0) - break; - - assert(!fail); - assert(!force); - assert(notify >= 0); - - for (;;) { - union inotify_event_buffer buffer; - struct inotify_event *e; - ssize_t l; - - if (timeout != USEC_INFINITY) { - usec_t n; - - n = now(CLOCK_MONOTONIC); - if (ts + timeout < n) { - r = -ETIMEDOUT; - goto fail; - } - - r = fd_wait_for_event(fd, POLLIN, ts + timeout - n); - if (r < 0) - goto fail; - - if (r == 0) { - r = -ETIMEDOUT; - goto fail; - } - } - - l = read(notify, &buffer, sizeof(buffer)); - if (l < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - - r = -errno; - goto fail; - } - - FOREACH_INOTIFY_EVENT(e, buffer, l) { - if (e->wd != wd || !(e->mask & IN_CLOSE)) { - r = -EIO; - goto fail; - } - } - - break; - } - - /* We close the tty fd here since if the old session - * ended our handle will be dead. It's important that - * we do this after sleeping, so that we don't enter - * an endless loop. */ - fd = safe_close(fd); - } - - safe_close(notify); - - return fd; - -fail: - safe_close(fd); - safe_close(notify); - - return r; -} - -int release_terminal(void) { - static const struct sigaction sa_new = { - .sa_handler = SIG_IGN, - .sa_flags = SA_RESTART, - }; - - _cleanup_close_ int fd = -1; - struct sigaction sa_old; - int r = 0; - - fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (fd < 0) - return -errno; - - /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed - * by our own TIOCNOTTY */ - assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); - - if (ioctl(fd, TIOCNOTTY) < 0) - r = -errno; - - assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); - - return r; -} - -int terminal_vhangup_fd(int fd) { - assert(fd >= 0); - - if (ioctl(fd, TIOCVHANGUP) < 0) - return -errno; - - return 0; -} - -int terminal_vhangup(const char *name) { - _cleanup_close_ int fd; - - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (fd < 0) - return fd; - - return terminal_vhangup_fd(fd); -} - -int vt_disallocate(const char *name) { - _cleanup_close_ int fd = -1; - unsigned u; - int r; - - /* Deallocate the VT if possible. If not possible - * (i.e. because it is the active one), at least clear it - * entirely (including the scrollback buffer) */ - - if (!startswith(name, "/dev/")) - return -EINVAL; - - if (!tty_is_vc(name)) { - /* So this is not a VT. I guess we cannot deallocate - * it then. But let's at least clear the screen */ - - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - loop_write(fd, - "\033[r" /* clear scrolling region */ - "\033[H" /* move home */ - "\033[2J", /* clear screen */ - 10, false); - return 0; - } - - if (!startswith(name, "/dev/tty")) - return -EINVAL; - - r = safe_atou(name+8, &u); - if (r < 0) - return r; - - if (u <= 0) - return -EINVAL; - - /* Try to deallocate */ - fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); - if (fd < 0) - return fd; - - r = ioctl(fd, VT_DISALLOCATE, u); - fd = safe_close(fd); - - if (r >= 0) - return 0; - - if (errno != EBUSY) - return -errno; - - /* Couldn't deallocate, so let's clear it fully with - * scrollback */ - fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - loop_write(fd, - "\033[r" /* clear scrolling region */ - "\033[H" /* move home */ - "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */ - 10, false); - return 0; -} - -int make_console_stdio(void) { - int fd, r; - - /* Make /dev/console the controlling terminal and stdin/stdout/stderr */ - - fd = acquire_terminal("/dev/console", false, true, true, USEC_INFINITY); - if (fd < 0) - return log_error_errno(fd, "Failed to acquire terminal: %m"); - - r = reset_terminal_fd(fd, true); - if (r < 0) - log_warning_errno(r, "Failed to reset terminal, ignoring: %m"); - - r = make_stdio(fd); - if (r < 0) - return log_error_errno(r, "Failed to duplicate terminal fd: %m"); - - return 0; -} - -bool tty_is_vc(const char *tty) { - assert(tty); - - return vtnr_from_tty(tty) >= 0; -} - -bool tty_is_console(const char *tty) { - assert(tty); - - if (startswith(tty, "/dev/")) - tty += 5; - - return streq(tty, "console"); -} - -int vtnr_from_tty(const char *tty) { - int i, r; - - assert(tty); - - if (startswith(tty, "/dev/")) - tty += 5; - - if (!startswith(tty, "tty") ) - return -EINVAL; - - if (tty[3] < '0' || tty[3] > '9') - return -EINVAL; - - r = safe_atoi(tty+3, &i); - if (r < 0) - return r; - - if (i < 0 || i > 63) - return -EINVAL; - - return i; -} - -char *resolve_dev_console(char **active) { - char *tty; - - /* Resolve where /dev/console is pointing to, if /sys is actually ours - * (i.e. not read-only-mounted which is a sign for container setups) */ - - if (path_is_read_only_fs("/sys") > 0) - return NULL; - - if (read_one_line_file("/sys/class/tty/console/active", active) < 0) - return NULL; - - /* If multiple log outputs are configured the last one is what - * /dev/console points to */ - tty = strrchr(*active, ' '); - if (tty) - tty++; - else - tty = *active; - - if (streq(tty, "tty0")) { - char *tmp; - - /* Get the active VC (e.g. tty1) */ - if (read_one_line_file("/sys/class/tty/tty0/active", &tmp) >= 0) { - free(*active); - tty = *active = tmp; - } - } - - return tty; -} - -bool tty_is_vc_resolve(const char *tty) { - _cleanup_free_ char *active = NULL; - - assert(tty); - - if (startswith(tty, "/dev/")) - tty += 5; - - if (streq(tty, "console")) { - tty = resolve_dev_console(&active); - if (!tty) - return false; - } - - return tty_is_vc(tty); -} - -const char *default_term_for_tty(const char *tty) { - return tty && tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220"; -} - -int fd_columns(int fd) { - struct winsize ws = {}; - - if (ioctl(fd, TIOCGWINSZ, &ws) < 0) - return -errno; - - if (ws.ws_col <= 0) - return -EIO; - - return ws.ws_col; -} - -unsigned columns(void) { - const char *e; - int c; - - if (_likely_(cached_columns > 0)) - return cached_columns; - - c = 0; - e = getenv("COLUMNS"); - if (e) - (void) safe_atoi(e, &c); - - if (c <= 0) - c = fd_columns(STDOUT_FILENO); - - if (c <= 0) - c = 80; - - cached_columns = c; - return cached_columns; -} - -int fd_lines(int fd) { - struct winsize ws = {}; - - if (ioctl(fd, TIOCGWINSZ, &ws) < 0) - return -errno; - - if (ws.ws_row <= 0) - return -EIO; - - return ws.ws_row; -} - -unsigned lines(void) { - const char *e; - int l; - - if (_likely_(cached_lines > 0)) - return cached_lines; - - l = 0; - e = getenv("LINES"); - if (e) - (void) safe_atoi(e, &l); - - if (l <= 0) - l = fd_lines(STDOUT_FILENO); - - if (l <= 0) - l = 24; - - cached_lines = l; - return cached_lines; -} - -/* intended to be used as a SIGWINCH sighandler */ -void columns_lines_cache_reset(int signum) { - cached_columns = 0; - cached_lines = 0; -} - -bool on_tty(void) { - static int cached_on_tty = -1; - - if (_unlikely_(cached_on_tty < 0)) - cached_on_tty = isatty(STDOUT_FILENO) > 0; - - return cached_on_tty; -} - -int make_stdio(int fd) { - int r, s, t; - - assert(fd >= 0); - - r = dup2(fd, STDIN_FILENO); - s = dup2(fd, STDOUT_FILENO); - t = dup2(fd, STDERR_FILENO); - - if (fd >= 3) - safe_close(fd); - - if (r < 0 || s < 0 || t < 0) - return -errno; - - /* 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); - - return 0; -} - -int make_null_stdio(void) { - int null_fd; - - null_fd = open("/dev/null", O_RDWR|O_NOCTTY); - if (null_fd < 0) - return -errno; - - return make_stdio(null_fd); -} - -int getttyname_malloc(int fd, char **ret) { - size_t l = 100; - int r; - - assert(fd >= 0); - assert(ret); - - for (;;) { - char path[l]; - - r = ttyname_r(fd, path, sizeof(path)); - if (r == 0) { - const char *p; - char *c; - - p = startswith(path, "/dev/"); - c = strdup(p ?: path); - if (!c) - return -ENOMEM; - - *ret = c; - return 0; - } - - if (r != ERANGE) - return -r; - - l *= 2; - } - - return 0; -} - -int getttyname_harder(int fd, char **r) { - int k; - char *s = NULL; - - k = getttyname_malloc(fd, &s); - if (k < 0) - return k; - - if (streq(s, "tty")) { - free(s); - return get_ctty(0, NULL, r); - } - - *r = s; - return 0; -} - -int get_ctty_devnr(pid_t pid, dev_t *d) { - int r; - _cleanup_free_ char *line = NULL; - const char *p; - unsigned long ttynr; - - assert(pid >= 0); - - p = procfs_file_alloca(pid, "stat"); - r = read_one_line_file(p, &line); - if (r < 0) - return r; - - p = strrchr(line, ')'); - if (!p) - return -EIO; - - p++; - - if (sscanf(p, " " - "%*c " /* state */ - "%*d " /* ppid */ - "%*d " /* pgrp */ - "%*d " /* session */ - "%lu ", /* ttynr */ - &ttynr) != 1) - return -EIO; - - if (major(ttynr) == 0 && minor(ttynr) == 0) - return -ENXIO; - - if (d) - *d = (dev_t) ttynr; - - return 0; -} - -int get_ctty(pid_t pid, dev_t *_devnr, char **r) { - char fn[sizeof("/dev/char/")-1 + 2*DECIMAL_STR_MAX(unsigned) + 1 + 1], *b = NULL; - _cleanup_free_ char *s = NULL; - const char *p; - dev_t devnr; - int k; - - assert(r); - - k = get_ctty_devnr(pid, &devnr); - if (k < 0) - return k; - - sprintf(fn, "/dev/char/%u:%u", major(devnr), minor(devnr)); - - k = readlink_malloc(fn, &s); - if (k < 0) { - - if (k != -ENOENT) - return k; - - /* This is an ugly hack */ - if (major(devnr) == 136) { - if (asprintf(&b, "pts/%u", minor(devnr)) < 0) - return -ENOMEM; - } else { - /* Probably something like the ptys which have no - * symlink in /dev/char. Let's return something - * vaguely useful. */ - - b = strdup(fn + 5); - if (!b) - return -ENOMEM; - } - } else { - if (startswith(s, "/dev/")) - p = s + 5; - else if (startswith(s, "../")) - p = s + 3; - else - p = s; - - b = strdup(p); - if (!b) - return -ENOMEM; - } - - *r = b; - if (_devnr) - *_devnr = devnr; - - return 0; -} - -int ptsname_malloc(int fd, char **ret) { - size_t l = 100; - - assert(fd >= 0); - assert(ret); - - for (;;) { - char *c; - - c = new(char, l); - if (!c) - return -ENOMEM; - - if (ptsname_r(fd, c, l) == 0) { - *ret = c; - return 0; - } - if (errno != ERANGE) { - free(c); - return -errno; - } - - free(c); - l *= 2; - } -} - -int ptsname_namespace(int pty, char **ret) { - int no = -1, r; - - /* Like ptsname(), but doesn't assume that the path is - * accessible in the local namespace. */ - - r = ioctl(pty, TIOCGPTN, &no); - if (r < 0) - return -errno; - - if (no < 0) - return -EIO; - - if (asprintf(ret, "/dev/pts/%i", no) < 0) - return -ENOMEM; - - return 0; -} - -int openpt_in_namespace(pid_t pid, int flags) { - _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - siginfo_t si; - pid_t child; - int r; - - assert(pid > 0); - - r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return -errno; - - if (child == 0) { - int master; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); - if (r < 0) - _exit(EXIT_FAILURE); - - master = posix_openpt(flags|O_NOCTTY|O_CLOEXEC); - if (master < 0) - _exit(EXIT_FAILURE); - - if (unlockpt(master) < 0) - _exit(EXIT_FAILURE); - - if (send_one_fd(pair[1], master, 0) < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate(child, &si); - if (r < 0) - return r; - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return -EIO; - - return receive_one_fd(pair[0], 0); -} - -int open_terminal_in_namespace(pid_t pid, const char *name, int mode) { - _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - siginfo_t si; - pid_t child; - int r; - - r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return -errno; - - if (child == 0) { - int master; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); - if (r < 0) - _exit(EXIT_FAILURE); - - master = open_terminal(name, mode|O_NOCTTY|O_CLOEXEC); - if (master < 0) - _exit(EXIT_FAILURE); - - if (send_one_fd(pair[1], master, 0) < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate(child, &si); - if (r < 0) - return r; - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return -EIO; - - return receive_one_fd(pair[0], 0); -} - -bool colors_enabled(void) { - const char *colors; - - colors = getenv("SYSTEMD_COLORS"); - if (!colors) { - if (streq_ptr(getenv("TERM"), "dumb")) - return false; - return on_tty(); - } - - return parse_boolean(colors) != 0; -} diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h deleted file mode 100644 index a7c96a77cb..0000000000 --- a/src/basic/terminal-util.h +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "macro.h" -#include "time-util.h" - -#define ANSI_RED "\x1B[0;31m" -#define ANSI_GREEN "\x1B[0;32m" -#define ANSI_UNDERLINE "\x1B[0;4m" -#define ANSI_HIGHLIGHT "\x1B[0;1;39m" -#define ANSI_HIGHLIGHT_RED "\x1B[0;1;31m" -#define ANSI_HIGHLIGHT_GREEN "\x1B[0;1;32m" -#define ANSI_HIGHLIGHT_YELLOW "\x1B[0;1;33m" -#define ANSI_HIGHLIGHT_BLUE "\x1B[0;1;34m" -#define ANSI_HIGHLIGHT_UNDERLINE "\x1B[0;1;4m" -#define ANSI_NORMAL "\x1B[0m" - -#define ANSI_ERASE_TO_END_OF_LINE "\x1B[K" - -/* Set cursor to top left corner and clear screen */ -#define ANSI_HOME_CLEAR "\x1B[H\x1B[2J" - -int reset_terminal_fd(int fd, bool switch_to_text); -int reset_terminal(const char *name); - -int open_terminal(const char *name, int mode); -int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm, usec_t timeout); -int release_terminal(void); - -int terminal_vhangup_fd(int fd); -int terminal_vhangup(const char *name); - -int chvt(int vt); - -int read_one_char(FILE *f, char *ret, usec_t timeout, bool *need_nl); -int ask_char(char *ret, const char *replies, const char *text, ...) _printf_(3, 4); -int ask_string(char **ret, const char *text, ...) _printf_(2, 3); - -int vt_disallocate(const char *name); - -char *resolve_dev_console(char **active); -bool tty_is_vc(const char *tty); -bool tty_is_vc_resolve(const char *tty); -bool tty_is_console(const char *tty) _pure_; -int vtnr_from_tty(const char *tty); -const char *default_term_for_tty(const char *tty); - -int make_stdio(int fd); -int make_null_stdio(void); -int make_console_stdio(void); - -int fd_columns(int fd); -unsigned columns(void); -int fd_lines(int fd); -unsigned lines(void); -void columns_lines_cache_reset(int _unused_ signum); - -bool on_tty(void); -bool colors_enabled(void); - -static inline const char *ansi_underline(void) { - return colors_enabled() ? ANSI_UNDERLINE : ""; -} - -static inline const char *ansi_highlight(void) { - return colors_enabled() ? ANSI_HIGHLIGHT : ""; -} - -static inline const char *ansi_highlight_underline(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_UNDERLINE : ""; -} - -static inline const char *ansi_highlight_red(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_RED : ""; -} - -static inline const char *ansi_highlight_green(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_GREEN : ""; -} - -static inline const char *ansi_highlight_yellow(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_YELLOW : ""; -} - -static inline const char *ansi_highlight_blue(void) { - return colors_enabled() ? ANSI_HIGHLIGHT_BLUE : ""; -} - -static inline const char *ansi_normal(void) { - return colors_enabled() ? ANSI_NORMAL : ""; -} - -int get_ctty_devnr(pid_t pid, dev_t *d); -int get_ctty(pid_t, dev_t *_devnr, char **r); - -int getttyname_malloc(int fd, char **r); -int getttyname_harder(int fd, char **r); - -int ptsname_malloc(int fd, char **ret); -int ptsname_namespace(int pty, char **ret); - -int openpt_in_namespace(pid_t pid, int flags); -int open_terminal_in_namespace(pid_t pid, const char *name, int mode); diff --git a/src/basic/time-util.c b/src/basic/time-util.c deleted file mode 100644 index 3973850b44..0000000000 --- a/src/basic/time-util.c +++ /dev/null @@ -1,1116 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" - -static nsec_t timespec_load_nsec(const struct timespec *ts); - -usec_t now(clockid_t clock_id) { - struct timespec ts; - - assert_se(clock_gettime(clock_id, &ts) == 0); - - return timespec_load(&ts); -} - -nsec_t now_nsec(clockid_t clock_id) { - struct timespec ts; - - assert_se(clock_gettime(clock_id, &ts) == 0); - - return timespec_load_nsec(&ts); -} - -dual_timestamp* dual_timestamp_get(dual_timestamp *ts) { - assert(ts); - - ts->realtime = now(CLOCK_REALTIME); - ts->monotonic = now(CLOCK_MONOTONIC); - - return ts; -} - -dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) { - int64_t delta; - assert(ts); - - if (u == USEC_INFINITY || u <= 0) { - ts->realtime = ts->monotonic = u; - return ts; - } - - ts->realtime = u; - - delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u; - ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta); - - return ts; -} - -dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) { - int64_t delta; - assert(ts); - - if (u == USEC_INFINITY) { - ts->realtime = ts->monotonic = USEC_INFINITY; - return ts; - } - - ts->monotonic = u; - delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u; - ts->realtime = usec_sub(now(CLOCK_REALTIME), delta); - - return ts; -} - -dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u) { - int64_t delta; - - if (u == USEC_INFINITY) { - ts->realtime = ts->monotonic = USEC_INFINITY; - return ts; - } - - dual_timestamp_get(ts); - delta = (int64_t) now(clock_boottime_or_monotonic()) - (int64_t) u; - ts->realtime = usec_sub(ts->realtime, delta); - ts->monotonic = usec_sub(ts->monotonic, delta); - - return ts; -} - -usec_t timespec_load(const struct timespec *ts) { - assert(ts); - - if (ts->tv_sec == (time_t) -1 && - ts->tv_nsec == (long) -1) - return USEC_INFINITY; - - if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC) - return USEC_INFINITY; - - return - (usec_t) ts->tv_sec * USEC_PER_SEC + - (usec_t) ts->tv_nsec / NSEC_PER_USEC; -} - -static nsec_t timespec_load_nsec(const struct timespec *ts) { - assert(ts); - - if (ts->tv_sec == (time_t) -1 && - ts->tv_nsec == (long) -1) - return NSEC_INFINITY; - - return - (nsec_t) ts->tv_sec * NSEC_PER_SEC + - (nsec_t) ts->tv_nsec; -} - -struct timespec *timespec_store(struct timespec *ts, usec_t u) { - assert(ts); - - if (u == USEC_INFINITY) { - ts->tv_sec = (time_t) -1; - ts->tv_nsec = (long) -1; - return ts; - } - - ts->tv_sec = (time_t) (u / USEC_PER_SEC); - ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC); - - return ts; -} - -usec_t timeval_load(const struct timeval *tv) { - assert(tv); - - if (tv->tv_sec == (time_t) -1 && - tv->tv_usec == (suseconds_t) -1) - return USEC_INFINITY; - - if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC) - return USEC_INFINITY; - - return - (usec_t) tv->tv_sec * USEC_PER_SEC + - (usec_t) tv->tv_usec; -} - -struct timeval *timeval_store(struct timeval *tv, usec_t u) { - assert(tv); - - if (u == USEC_INFINITY) { - tv->tv_sec = (time_t) -1; - tv->tv_usec = (suseconds_t) -1; - } else { - tv->tv_sec = (time_t) (u / USEC_PER_SEC); - tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC); - } - - return tv; -} - -static char *format_timestamp_internal(char *buf, size_t l, usec_t t, - bool utc, bool us) { - struct tm tm; - time_t sec; - int k; - - assert(buf); - assert(l > 0); - - if (t <= 0 || t == USEC_INFINITY) - return NULL; - - sec = (time_t) (t / USEC_PER_SEC); - localtime_or_gmtime_r(&sec, &tm, utc); - - 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); - - if (k <= 0) - return NULL; - 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; - } - - return buf; -} - -char *format_timestamp(char *buf, size_t l, usec_t t) { - return format_timestamp_internal(buf, l, t, false, false); -} - -char *format_timestamp_utc(char *buf, size_t l, usec_t t) { - return format_timestamp_internal(buf, l, t, true, false); -} - -char *format_timestamp_us(char *buf, size_t l, usec_t t) { - return format_timestamp_internal(buf, l, t, false, true); -} - -char *format_timestamp_us_utc(char *buf, size_t l, usec_t t) { - return format_timestamp_internal(buf, l, t, true, true); -} - -char *format_timestamp_relative(char *buf, size_t l, usec_t t) { - const char *s; - usec_t n, d; - - if (t <= 0 || t == USEC_INFINITY) - return NULL; - - n = now(CLOCK_REALTIME); - if (n > t) { - d = n - t; - s = "ago"; - } else { - d = t - n; - s = "left"; - } - - if (d >= USEC_PER_YEAR) - snprintf(buf, l, USEC_FMT " years " USEC_FMT " months %s", - d / USEC_PER_YEAR, - (d % USEC_PER_YEAR) / USEC_PER_MONTH, s); - else if (d >= USEC_PER_MONTH) - snprintf(buf, l, USEC_FMT " months " USEC_FMT " days %s", - d / USEC_PER_MONTH, - (d % USEC_PER_MONTH) / USEC_PER_DAY, s); - else if (d >= USEC_PER_WEEK) - snprintf(buf, l, USEC_FMT " weeks " USEC_FMT " days %s", - d / USEC_PER_WEEK, - (d % USEC_PER_WEEK) / USEC_PER_DAY, s); - else if (d >= 2*USEC_PER_DAY) - snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s); - else if (d >= 25*USEC_PER_HOUR) - snprintf(buf, l, "1 day " USEC_FMT "h %s", - (d - USEC_PER_DAY) / USEC_PER_HOUR, s); - else if (d >= 6*USEC_PER_HOUR) - snprintf(buf, l, USEC_FMT "h %s", - d / USEC_PER_HOUR, s); - else if (d >= USEC_PER_HOUR) - snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %s", - d / USEC_PER_HOUR, - (d % USEC_PER_HOUR) / USEC_PER_MINUTE, s); - else if (d >= 5*USEC_PER_MINUTE) - snprintf(buf, l, USEC_FMT "min %s", - d / USEC_PER_MINUTE, s); - else if (d >= USEC_PER_MINUTE) - snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %s", - d / USEC_PER_MINUTE, - (d % USEC_PER_MINUTE) / USEC_PER_SEC, s); - else if (d >= USEC_PER_SEC) - snprintf(buf, l, USEC_FMT "s %s", - d / USEC_PER_SEC, s); - else if (d >= USEC_PER_MSEC) - snprintf(buf, l, USEC_FMT "ms %s", - d / USEC_PER_MSEC, s); - else if (d > 0) - snprintf(buf, l, USEC_FMT"us %s", - d, s); - else - snprintf(buf, l, "now"); - - buf[l-1] = 0; - return buf; -} - -char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { - static const struct { - const char *suffix; - usec_t usec; - } table[] = { - { "y", USEC_PER_YEAR }, - { "month", USEC_PER_MONTH }, - { "w", USEC_PER_WEEK }, - { "d", USEC_PER_DAY }, - { "h", USEC_PER_HOUR }, - { "min", USEC_PER_MINUTE }, - { "s", USEC_PER_SEC }, - { "ms", USEC_PER_MSEC }, - { "us", 1 }, - }; - - unsigned i; - char *p = buf; - bool something = false; - - assert(buf); - assert(l > 0); - - if (t == USEC_INFINITY) { - strncpy(p, "infinity", l-1); - p[l-1] = 0; - return p; - } - - if (t <= 0) { - strncpy(p, "0", l-1); - p[l-1] = 0; - return p; - } - - /* The result of this function can be parsed with parse_sec */ - - for (i = 0; i < ELEMENTSOF(table); i++) { - int k = 0; - size_t n; - bool done = false; - usec_t a, b; - - if (t <= 0) - break; - - if (t < accuracy && something) - break; - - if (t < table[i].usec) - continue; - - if (l <= 1) - break; - - a = t / table[i].usec; - b = t % table[i].usec; - - /* Let's see if we should shows this in dot notation */ - if (t < USEC_PER_MINUTE && b > 0) { - usec_t cc; - int j; - - j = 0; - for (cc = table[i].usec; cc > 1; cc /= 10) - j++; - - for (cc = accuracy; cc > 1; cc /= 10) { - b /= 10; - j--; - } - - if (j > 0) { - k = snprintf(p, l, - "%s"USEC_FMT".%0*llu%s", - p > buf ? " " : "", - a, - j, - (unsigned long long) b, - table[i].suffix); - - t = 0; - done = true; - } - } - - /* No? Then let's show it normally */ - if (!done) { - k = snprintf(p, l, - "%s"USEC_FMT"%s", - p > buf ? " " : "", - a, - table[i].suffix); - - t = b; - } - - n = MIN((size_t) k, l); - - l -= n; - p += n; - - something = true; - } - - *p = 0; - - return buf; -} - -void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) { - - assert(f); - assert(name); - assert(t); - - if (!dual_timestamp_is_set(t)) - return; - - fprintf(f, "%s="USEC_FMT" "USEC_FMT"\n", - name, - t->realtime, - t->monotonic); -} - -int dual_timestamp_deserialize(const char *value, dual_timestamp *t) { - unsigned long long a, b; - - assert(value); - assert(t); - - if (sscanf(value, "%llu %llu", &a, &b) != 2) { - log_debug("Failed to parse finish timestamp value %s.", value); - return -EINVAL; - } - - t->realtime = a; - t->monotonic = b; - - return 0; -} - -int parse_timestamp(const char *t, usec_t *usec) { - static const struct { - const char *name; - const int nr; - } day_nr[] = { - { "Sunday", 0 }, - { "Sun", 0 }, - { "Monday", 1 }, - { "Mon", 1 }, - { "Tuesday", 2 }, - { "Tue", 2 }, - { "Wednesday", 3 }, - { "Wed", 3 }, - { "Thursday", 4 }, - { "Thu", 4 }, - { "Friday", 5 }, - { "Fri", 5 }, - { "Saturday", 6 }, - { "Sat", 6 }, - }; - - const char *k; - const char *utc; - struct tm tm, copy; - time_t x; - usec_t x_usec, plus = 0, minus = 0, ret; - int r, weekday = -1; - unsigned i; - - /* - * Allowed syntaxes: - * - * 2012-09-22 16:34:22 - * 2012-09-22 16:34 (seconds will be set to 0) - * 2012-09-22 (time will be set to 00:00:00) - * 16:34:22 (date will be set to today) - * 16:34 (date will be set to today, seconds to 0) - * now - * yesterday (time is set to 00:00:00) - * today (time is set to 00:00:00) - * tomorrow (time is set to 00:00:00) - * +5min - * -5days - * @2147483647 (seconds since epoch) - * - */ - - assert(t); - assert(usec); - - if (t[0] == '@') - return parse_sec(t + 1, usec); - - ret = now(CLOCK_REALTIME); - - if (streq(t, "now")) - goto finish; - - else if (t[0] == '+') { - r = parse_sec(t+1, &plus); - if (r < 0) - return r; - - goto finish; - - } else if (t[0] == '-') { - r = parse_sec(t+1, &minus); - if (r < 0) - return r; - - goto finish; - - } else if ((k = endswith(t, " ago"))) { - t = strndupa(t, k - t); - - r = parse_sec(t, &minus); - if (r < 0) - return r; - - goto finish; - - } else if ((k = endswith(t, " left"))) { - t = strndupa(t, k - t); - - r = parse_sec(t, &plus); - if (r < 0) - return r; - - goto finish; - } - - utc = endswith_no_case(t, " UTC"); - if (utc) - t = strndupa(t, utc - t); - - x = ret / USEC_PER_SEC; - x_usec = 0; - - assert_se(localtime_or_gmtime_r(&x, &tm, utc)); - tm.tm_isdst = -1; - - if (streq(t, "today")) { - tm.tm_sec = tm.tm_min = tm.tm_hour = 0; - goto from_tm; - - } else if (streq(t, "yesterday")) { - tm.tm_mday --; - tm.tm_sec = tm.tm_min = tm.tm_hour = 0; - goto from_tm; - - } else if (streq(t, "tomorrow")) { - tm.tm_mday ++; - tm.tm_sec = tm.tm_min = tm.tm_hour = 0; - goto from_tm; - } - - - for (i = 0; i < ELEMENTSOF(day_nr); i++) { - size_t skip; - - if (!startswith_no_case(t, day_nr[i].name)) - continue; - - skip = strlen(day_nr[i].name); - if (t[skip] != ' ') - continue; - - weekday = day_nr[i].nr; - t += skip + 1; - break; - } - - copy = tm; - k = strptime(t, "%y-%m-%d %H:%M:%S", &tm); - if (k) { - if (*k == '.') - goto parse_usec; - else if (*k == 0) - goto from_tm; - } - - tm = copy; - k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm); - if (k) { - if (*k == '.') - goto parse_usec; - else if (*k == 0) - goto from_tm; - } - - tm = copy; - k = strptime(t, "%y-%m-%d %H:%M", &tm); - if (k && *k == 0) { - tm.tm_sec = 0; - goto from_tm; - } - - tm = copy; - k = strptime(t, "%Y-%m-%d %H:%M", &tm); - if (k && *k == 0) { - tm.tm_sec = 0; - goto from_tm; - } - - tm = copy; - k = strptime(t, "%y-%m-%d", &tm); - if (k && *k == 0) { - tm.tm_sec = tm.tm_min = tm.tm_hour = 0; - goto from_tm; - } - - tm = copy; - k = strptime(t, "%Y-%m-%d", &tm); - if (k && *k == 0) { - tm.tm_sec = tm.tm_min = tm.tm_hour = 0; - goto from_tm; - } - - tm = copy; - k = strptime(t, "%H:%M:%S", &tm); - if (k) { - if (*k == '.') - goto parse_usec; - else if (*k == 0) - goto from_tm; - } - - tm = copy; - k = strptime(t, "%H:%M", &tm); - if (k && *k == 0) { - tm.tm_sec = 0; - goto from_tm; - } - - return -EINVAL; - -parse_usec: - { - unsigned add; - - k++; - r = parse_fractional_part_u(&k, 6, &add); - if (r < 0) - return -EINVAL; - - if (*k) - return -EINVAL; - - x_usec = add; - - } - -from_tm: - x = mktime_or_timegm(&tm, utc); - if (x == (time_t) -1) - return -EINVAL; - - if (weekday >= 0 && tm.tm_wday != weekday) - return -EINVAL; - - ret = (usec_t) x * USEC_PER_SEC + x_usec; - -finish: - ret += plus; - if (ret > minus) - ret -= minus; - else - ret = 0; - - *usec = ret; - - return 0; -} - -int parse_time(const char *t, usec_t *usec, usec_t default_unit) { - - static const struct { - const char *suffix; - usec_t usec; - } table[] = { - { "seconds", USEC_PER_SEC }, - { "second", USEC_PER_SEC }, - { "sec", USEC_PER_SEC }, - { "s", USEC_PER_SEC }, - { "minutes", USEC_PER_MINUTE }, - { "minute", USEC_PER_MINUTE }, - { "min", USEC_PER_MINUTE }, - { "months", USEC_PER_MONTH }, - { "month", USEC_PER_MONTH }, - { "M", USEC_PER_MONTH }, - { "msec", USEC_PER_MSEC }, - { "ms", USEC_PER_MSEC }, - { "m", USEC_PER_MINUTE }, - { "hours", USEC_PER_HOUR }, - { "hour", USEC_PER_HOUR }, - { "hr", USEC_PER_HOUR }, - { "h", USEC_PER_HOUR }, - { "days", USEC_PER_DAY }, - { "day", USEC_PER_DAY }, - { "d", USEC_PER_DAY }, - { "weeks", USEC_PER_WEEK }, - { "week", USEC_PER_WEEK }, - { "w", USEC_PER_WEEK }, - { "years", USEC_PER_YEAR }, - { "year", USEC_PER_YEAR }, - { "y", USEC_PER_YEAR }, - { "usec", 1ULL }, - { "us", 1ULL }, - }; - - const char *p, *s; - usec_t r = 0; - bool something = false; - - assert(t); - assert(usec); - assert(default_unit > 0); - - p = t; - - p += strspn(p, WHITESPACE); - s = startswith(p, "infinity"); - if (s) { - s += strspn(s, WHITESPACE); - if (*s != 0) - return -EINVAL; - - *usec = USEC_INFINITY; - return 0; - } - - for (;;) { - long long l, z = 0; - char *e; - unsigned i, n = 0; - usec_t multiplier, k; - - p += strspn(p, WHITESPACE); - - if (*p == 0) { - if (!something) - return -EINVAL; - - break; - } - - errno = 0; - l = strtoll(p, &e, 10); - - if (errno > 0) - return -errno; - - if (l < 0) - return -ERANGE; - - if (*e == '.') { - char *b = e + 1; - - errno = 0; - z = strtoll(b, &e, 10); - if (errno > 0) - return -errno; - - if (z < 0) - return -ERANGE; - - if (e == b) - return -EINVAL; - - n = e - b; - - } else if (e == p) - return -EINVAL; - - e += strspn(e, WHITESPACE); - - for (i = 0; i < ELEMENTSOF(table); i++) - if (startswith(e, table[i].suffix)) { - multiplier = table[i].usec; - p = e + strlen(table[i].suffix); - break; - } - - if (i >= ELEMENTSOF(table)) { - multiplier = default_unit; - p = e; - } - - something = true; - - k = (usec_t) z * multiplier; - - for (; n > 0; n--) - k /= 10; - - r += (usec_t) l * multiplier + k; - } - - *usec = r; - - return 0; -} - -int parse_sec(const char *t, usec_t *usec) { - return parse_time(t, usec, USEC_PER_SEC); -} - -int parse_nsec(const char *t, nsec_t *nsec) { - static const struct { - const char *suffix; - nsec_t nsec; - } table[] = { - { "seconds", NSEC_PER_SEC }, - { "second", NSEC_PER_SEC }, - { "sec", NSEC_PER_SEC }, - { "s", NSEC_PER_SEC }, - { "minutes", NSEC_PER_MINUTE }, - { "minute", NSEC_PER_MINUTE }, - { "min", NSEC_PER_MINUTE }, - { "months", NSEC_PER_MONTH }, - { "month", NSEC_PER_MONTH }, - { "msec", NSEC_PER_MSEC }, - { "ms", NSEC_PER_MSEC }, - { "m", NSEC_PER_MINUTE }, - { "hours", NSEC_PER_HOUR }, - { "hour", NSEC_PER_HOUR }, - { "hr", NSEC_PER_HOUR }, - { "h", NSEC_PER_HOUR }, - { "days", NSEC_PER_DAY }, - { "day", NSEC_PER_DAY }, - { "d", NSEC_PER_DAY }, - { "weeks", NSEC_PER_WEEK }, - { "week", NSEC_PER_WEEK }, - { "w", NSEC_PER_WEEK }, - { "years", NSEC_PER_YEAR }, - { "year", NSEC_PER_YEAR }, - { "y", NSEC_PER_YEAR }, - { "usec", NSEC_PER_USEC }, - { "us", NSEC_PER_USEC }, - { "nsec", 1ULL }, - { "ns", 1ULL }, - { "", 1ULL }, /* default is nsec */ - }; - - const char *p, *s; - nsec_t r = 0; - bool something = false; - - assert(t); - assert(nsec); - - p = t; - - p += strspn(p, WHITESPACE); - s = startswith(p, "infinity"); - if (s) { - s += strspn(s, WHITESPACE); - if (*s != 0) - return -EINVAL; - - *nsec = NSEC_INFINITY; - return 0; - } - - for (;;) { - long long l, z = 0; - char *e; - unsigned i, n = 0; - - p += strspn(p, WHITESPACE); - - if (*p == 0) { - if (!something) - return -EINVAL; - - break; - } - - errno = 0; - l = strtoll(p, &e, 10); - - if (errno > 0) - return -errno; - - if (l < 0) - return -ERANGE; - - if (*e == '.') { - char *b = e + 1; - - errno = 0; - z = strtoll(b, &e, 10); - if (errno > 0) - return -errno; - - if (z < 0) - return -ERANGE; - - if (e == b) - return -EINVAL; - - n = e - b; - - } else if (e == p) - return -EINVAL; - - e += strspn(e, WHITESPACE); - - for (i = 0; i < ELEMENTSOF(table); i++) - if (startswith(e, table[i].suffix)) { - nsec_t k = (nsec_t) z * table[i].nsec; - - for (; n > 0; n--) - k /= 10; - - r += (nsec_t) l * table[i].nsec + k; - p = e + strlen(table[i].suffix); - - something = true; - break; - } - - if (i >= ELEMENTSOF(table)) - return -EINVAL; - - } - - *nsec = r; - - return 0; -} - -bool ntp_synced(void) { - struct timex txc = {}; - - if (adjtimex(&txc) < 0) - return false; - - if (txc.status & STA_UNSYNC) - return false; - - return true; -} - -int get_timezones(char ***ret) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_strv_free_ char **zones = NULL; - size_t n_zones = 0, n_allocated = 0; - - assert(ret); - - zones = strv_new("UTC", NULL); - if (!zones) - return -ENOMEM; - - n_allocated = 2; - n_zones = 1; - - f = fopen("/usr/share/zoneinfo/zone.tab", "re"); - if (f) { - char l[LINE_MAX]; - - FOREACH_LINE(l, f, return -errno) { - char *p, *w; - size_t k; - - p = strstrip(l); - - if (isempty(p) || *p == '#') - continue; - - /* Skip over country code */ - p += strcspn(p, WHITESPACE); - p += strspn(p, WHITESPACE); - - /* Skip over coordinates */ - p += strcspn(p, WHITESPACE); - p += strspn(p, WHITESPACE); - - /* Found timezone name */ - k = strcspn(p, WHITESPACE); - if (k <= 0) - continue; - - w = strndup(p, k); - if (!w) - return -ENOMEM; - - if (!GREEDY_REALLOC(zones, n_allocated, n_zones + 2)) { - free(w); - return -ENOMEM; - } - - zones[n_zones++] = w; - zones[n_zones] = NULL; - } - - strv_sort(zones); - - } else if (errno != ENOENT) - return -errno; - - *ret = zones; - zones = NULL; - - return 0; -} - -bool timezone_is_valid(const char *name) { - bool slash = false; - const char *p, *t; - struct stat st; - - if (isempty(name)) - return false; - - if (name[0] == '/') - return false; - - for (p = name; *p; p++) { - if (!(*p >= '0' && *p <= '9') && - !(*p >= 'a' && *p <= 'z') && - !(*p >= 'A' && *p <= 'Z') && - !(*p == '-' || *p == '_' || *p == '+' || *p == '/')) - return false; - - if (*p == '/') { - - if (slash) - return false; - - slash = true; - } else - slash = false; - } - - if (slash) - return false; - - t = strjoina("/usr/share/zoneinfo/", name); - if (stat(t, &st) < 0) - return false; - - if (!S_ISREG(st.st_mode)) - return false; - - return true; -} - -clockid_t clock_boottime_or_monotonic(void) { - static clockid_t clock = -1; - int fd; - - if (clock != -1) - return clock; - - fd = timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK|TFD_CLOEXEC); - if (fd < 0) - clock = CLOCK_MONOTONIC; - else { - safe_close(fd); - clock = CLOCK_BOOTTIME; - } - - return clock; -} - -int get_timezone(char **tz) { - _cleanup_free_ char *t = NULL; - const char *e; - char *z; - int r; - - r = readlink_malloc("/etc/localtime", &t); - if (r < 0) - return r; /* returns EINVAL if not a symlink */ - - e = path_startswith(t, "/usr/share/zoneinfo/"); - if (!e) - e = path_startswith(t, "../usr/share/zoneinfo/"); - if (!e) - return -EINVAL; - - if (!timezone_is_valid(e)) - return -EINVAL; - - z = strdup(e); - if (!z) - return -ENOMEM; - - *tz = z; - return 0; -} - -time_t mktime_or_timegm(struct tm *tm, bool utc) { - return utc ? timegm(tm) : mktime(tm); -} - -struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) { - return utc ? gmtime_r(t, tm) : localtime_r(t, tm); -} - -unsigned long usec_to_jiffies(usec_t u) { - static thread_local unsigned long hz = 0; - long r; - - if (hz == 0) { - r = sysconf(_SC_CLK_TCK); - - assert(r > 0); - hz = (unsigned long) r; - } - - return DIV_ROUND_UP(u , USEC_PER_SEC / hz); -} diff --git a/src/basic/time-util.h b/src/basic/time-util.h deleted file mode 100644 index 9894e626c5..0000000000 --- a/src/basic/time-util.h +++ /dev/null @@ -1,151 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include - -typedef uint64_t usec_t; -typedef uint64_t nsec_t; - -#define NSEC_FMT "%" PRIu64 -#define USEC_FMT "%" PRIu64 - -#include "macro.h" - -typedef struct dual_timestamp { - usec_t realtime; - usec_t monotonic; -} dual_timestamp; - -#define USEC_INFINITY ((usec_t) -1) -#define NSEC_INFINITY ((nsec_t) -1) - -#define MSEC_PER_SEC 1000ULL -#define USEC_PER_SEC ((usec_t) 1000000ULL) -#define USEC_PER_MSEC ((usec_t) 1000ULL) -#define NSEC_PER_SEC ((nsec_t) 1000000000ULL) -#define NSEC_PER_MSEC ((nsec_t) 1000000ULL) -#define NSEC_PER_USEC ((nsec_t) 1000ULL) - -#define USEC_PER_MINUTE ((usec_t) (60ULL*USEC_PER_SEC)) -#define NSEC_PER_MINUTE ((nsec_t) (60ULL*NSEC_PER_SEC)) -#define USEC_PER_HOUR ((usec_t) (60ULL*USEC_PER_MINUTE)) -#define NSEC_PER_HOUR ((nsec_t) (60ULL*NSEC_PER_MINUTE)) -#define USEC_PER_DAY ((usec_t) (24ULL*USEC_PER_HOUR)) -#define NSEC_PER_DAY ((nsec_t) (24ULL*NSEC_PER_HOUR)) -#define USEC_PER_WEEK ((usec_t) (7ULL*USEC_PER_DAY)) -#define NSEC_PER_WEEK ((nsec_t) (7ULL*NSEC_PER_DAY)) -#define USEC_PER_MONTH ((usec_t) (2629800ULL*USEC_PER_SEC)) -#define NSEC_PER_MONTH ((nsec_t) (2629800ULL*NSEC_PER_SEC)) -#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 */ -#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 }) - -usec_t now(clockid_t clock); -nsec_t now_nsec(clockid_t clock); - -dual_timestamp* dual_timestamp_get(dual_timestamp *ts); -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); - -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)); -} - -usec_t timespec_load(const struct timespec *ts) _pure_; -struct timespec *timespec_store(struct timespec *ts, usec_t u); - -usec_t timeval_load(const struct timeval *tv) _pure_; -struct timeval *timeval_store(struct timeval *tv, usec_t u); - -char *format_timestamp(char *buf, size_t l, usec_t t); -char *format_timestamp_utc(char *buf, size_t l, usec_t t); -char *format_timestamp_us(char *buf, size_t l, usec_t t); -char *format_timestamp_us_utc(char *buf, size_t l, usec_t t); -char *format_timestamp_relative(char *buf, size_t l, usec_t t); -char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy); - -void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t); -int dual_timestamp_deserialize(const char *value, dual_timestamp *t); - -int parse_timestamp(const char *t, usec_t *usec); - -int parse_sec(const char *t, usec_t *usec); -int parse_time(const char *t, usec_t *usec, usec_t default_unit); -int parse_nsec(const char *t, nsec_t *nsec); - -bool ntp_synced(void); - -int get_timezones(char ***l); -bool timezone_is_valid(const char *name); - -clockid_t clock_boottime_or_monotonic(void); - -#define xstrftime(buf, fmt, tm) \ - assert_message_se(strftime(buf, ELEMENTSOF(buf), fmt, tm) > 0, \ - "xstrftime: " #buf "[] must be big enough") - -int get_timezone(char **timezone); - -time_t mktime_or_timegm(struct tm *tm, bool utc); -struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc); - -unsigned long usec_to_jiffies(usec_t usec); - -static inline usec_t usec_add(usec_t a, usec_t b) { - usec_t c; - - /* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output, and doesn't - * overflow. */ - - c = a + b; - if (c < a || c < b) /* overflow check */ - return USEC_INFINITY; - - return c; -} - -static inline usec_t usec_sub(usec_t timestamp, int64_t delta) { - if (delta < 0) - return usec_add(timestamp, (usec_t) (-delta)); - - if (timestamp == USEC_INFINITY) /* Make sure infinity doesn't degrade */ - return USEC_INFINITY; - - if (timestamp < (usec_t) delta) - return 0; - - return timestamp - delta; -} diff --git a/src/basic/umask-util.h b/src/basic/umask-util.h deleted file mode 100644 index 359d87d27c..0000000000 --- a/src/basic/umask-util.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#include "macro.h" - -static inline void umaskp(mode_t *u) { - umask(*u); -} - -#define _cleanup_umask_ _cleanup_(umaskp) - -struct _umask_struct_ { - mode_t mask; - bool quit; -}; - -static inline void _reset_umask_(struct _umask_struct_ *s) { - umask(s->mask); -}; - -#define RUN_WITH_UMASK(mask) \ - for (_cleanup_(_reset_umask_) struct _umask_struct_ _saved_umask_ = { umask(mask), false }; \ - !_saved_umask_.quit ; \ - _saved_umask_.quit = true) diff --git a/src/basic/unaligned.h b/src/basic/unaligned.h deleted file mode 100644 index 79be645bed..0000000000 --- a/src/basic/unaligned.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include -#include - -/* BE */ - -static inline uint16_t unaligned_read_be16(const void *_u) { - const uint8_t *u = _u; - - return (((uint16_t) u[0]) << 8) | - ((uint16_t) u[1]); -} - -static inline uint32_t unaligned_read_be32(const void *_u) { - const uint8_t *u = _u; - - return (((uint32_t) unaligned_read_be16(u)) << 16) | - ((uint32_t) unaligned_read_be16(u + 2)); -} - -static inline uint64_t unaligned_read_be64(const void *_u) { - const uint8_t *u = _u; - - return (((uint64_t) unaligned_read_be32(u)) << 32) | - ((uint64_t) unaligned_read_be32(u + 4)); -} - -static inline void unaligned_write_be16(void *_u, uint16_t a) { - uint8_t *u = _u; - - u[0] = (uint8_t) (a >> 8); - u[1] = (uint8_t) a; -} - -static inline void unaligned_write_be32(void *_u, uint32_t a) { - uint8_t *u = _u; - - unaligned_write_be16(u, (uint16_t) (a >> 16)); - unaligned_write_be16(u + 2, (uint16_t) a); -} - -static inline void unaligned_write_be64(void *_u, uint64_t a) { - uint8_t *u = _u; - - unaligned_write_be32(u, (uint32_t) (a >> 32)); - unaligned_write_be32(u + 4, (uint32_t) a); -} - -/* LE */ - -static inline uint16_t unaligned_read_le16(const void *_u) { - const uint8_t *u = _u; - - return (((uint16_t) u[1]) << 8) | - ((uint16_t) u[0]); -} - -static inline uint32_t unaligned_read_le32(const void *_u) { - const uint8_t *u = _u; - - return (((uint32_t) unaligned_read_le16(u + 2)) << 16) | - ((uint32_t) unaligned_read_le16(u)); -} - -static inline uint64_t unaligned_read_le64(const void *_u) { - const uint8_t *u = _u; - - return (((uint64_t) unaligned_read_le32(u + 4)) << 32) | - ((uint64_t) unaligned_read_le32(u)); -} - -static inline void unaligned_write_le16(void *_u, uint16_t a) { - uint8_t *u = _u; - - u[0] = (uint8_t) a; - u[1] = (uint8_t) (a >> 8); -} - -static inline void unaligned_write_le32(void *_u, uint32_t a) { - uint8_t *u = _u; - - unaligned_write_le16(u, (uint16_t) a); - unaligned_write_le16(u + 2, (uint16_t) (a >> 16)); -} - -static inline void unaligned_write_le64(void *_u, uint64_t a) { - uint8_t *u = _u; - - unaligned_write_le32(u, (uint32_t) a); - unaligned_write_le32(u + 4, (uint32_t) (a >> 32)); -} diff --git a/src/basic/unit-name.c b/src/basic/unit-name.c deleted file mode 100644 index fe883b95c7..0000000000 --- a/src/basic/unit-name.c +++ /dev/null @@ -1,1049 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "bus-label.h" -#include "glob-util.h" -#include "hexdecoct.h" -#include "macro.h" -#include "path-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" - -/* Characters valid in a unit name. */ -#define VALID_CHARS \ - DIGITS \ - LETTERS \ - ":-_.\\" - -/* The same, but also permits the single @ character that may appear */ -#define VALID_CHARS_WITH_AT \ - "@" \ - VALID_CHARS - -/* All chars valid in a unit name glob */ -#define VALID_CHARS_GLOB \ - VALID_CHARS_WITH_AT \ - "[]!-*?" - -bool unit_name_is_valid(const char *n, UnitNameFlags flags) { - const char *e, *i, *at; - - assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0); - - if (_unlikely_(flags == 0)) - return false; - - if (isempty(n)) - return false; - - if (strlen(n) >= UNIT_NAME_MAX) - return false; - - e = strrchr(n, '.'); - if (!e || e == n) - return false; - - if (unit_type_from_string(e + 1) < 0) - return false; - - for (i = n, at = NULL; i < e; i++) { - - if (*i == '@' && !at) - at = i; - - if (!strchr("@" VALID_CHARS, *i)) - return false; - } - - if (at == n) - return false; - - if (flags & UNIT_NAME_PLAIN) - if (!at) - return true; - - if (flags & UNIT_NAME_INSTANCE) - if (at && e > at + 1) - return true; - - if (flags & UNIT_NAME_TEMPLATE) - if (at && e == at + 1) - return true; - - return false; -} - -bool unit_prefix_is_valid(const char *p) { - - /* We don't allow additional @ in the prefix string */ - - if (isempty(p)) - return false; - - return in_charset(p, VALID_CHARS); -} - -bool unit_instance_is_valid(const char *i) { - - /* The max length depends on the length of the string, so we - * don't really check this here. */ - - if (isempty(i)) - return false; - - /* We allow additional @ in the instance string, we do not - * allow them in the prefix! */ - - return in_charset(i, "@" VALID_CHARS); -} - -bool unit_suffix_is_valid(const char *s) { - if (isempty(s)) - return false; - - if (s[0] != '.') - return false; - - if (unit_type_from_string(s + 1) < 0) - return false; - - return true; -} - -int unit_name_to_prefix(const char *n, char **ret) { - const char *p; - char *s; - - assert(n); - assert(ret); - - if (!unit_name_is_valid(n, UNIT_NAME_ANY)) - return -EINVAL; - - p = strchr(n, '@'); - if (!p) - p = strrchr(n, '.'); - - assert_se(p); - - s = strndup(n, p - n); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -int unit_name_to_instance(const char *n, char **instance) { - const char *p, *d; - char *i; - - assert(n); - assert(instance); - - if (!unit_name_is_valid(n, UNIT_NAME_ANY)) - return -EINVAL; - - /* Everything past the first @ and before the last . is the instance */ - p = strchr(n, '@'); - if (!p) { - *instance = NULL; - return 0; - } - - p++; - - d = strrchr(p, '.'); - if (!d) - return -EINVAL; - - i = strndup(p, d-p); - if (!i) - return -ENOMEM; - - *instance = i; - return 1; -} - -int unit_name_to_prefix_and_instance(const char *n, char **ret) { - const char *d; - char *s; - - assert(n); - assert(ret); - - if (!unit_name_is_valid(n, UNIT_NAME_ANY)) - return -EINVAL; - - d = strrchr(n, '.'); - if (!d) - return -EINVAL; - - s = strndup(n, d - n); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -UnitType unit_name_to_type(const char *n) { - const char *e; - - assert(n); - - if (!unit_name_is_valid(n, UNIT_NAME_ANY)) - return _UNIT_TYPE_INVALID; - - assert_se(e = strrchr(n, '.')); - - return unit_type_from_string(e + 1); -} - -int unit_name_change_suffix(const char *n, const char *suffix, char **ret) { - char *e, *s; - size_t a, b; - - assert(n); - assert(suffix); - assert(ret); - - if (!unit_name_is_valid(n, UNIT_NAME_ANY)) - return -EINVAL; - - if (!unit_suffix_is_valid(suffix)) - return -EINVAL; - - assert_se(e = strrchr(n, '.')); - - a = e - n; - b = strlen(suffix); - - s = new(char, a + b + 1); - if (!s) - return -ENOMEM; - - strcpy(mempcpy(s, n, a), suffix); - *ret = s; - - return 0; -} - -int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) { - char *s; - - assert(prefix); - assert(suffix); - assert(ret); - - if (!unit_prefix_is_valid(prefix)) - return -EINVAL; - - if (instance && !unit_instance_is_valid(instance)) - return -EINVAL; - - if (!unit_suffix_is_valid(suffix)) - return -EINVAL; - - if (!instance) - s = strappend(prefix, suffix); - else - s = strjoin(prefix, "@", instance, suffix, NULL); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -static char *do_escape_char(char c, char *t) { - assert(t); - - *(t++) = '\\'; - *(t++) = 'x'; - *(t++) = hexchar(c >> 4); - *(t++) = hexchar(c); - - return t; -} - -static char *do_escape(const char *f, char *t) { - assert(f); - assert(t); - - /* do not create units with a leading '.', like for "/.dotdir" mount points */ - if (*f == '.') { - t = do_escape_char(*f, t); - f++; - } - - for (; *f; f++) { - if (*f == '/') - *(t++) = '-'; - else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f)) - t = do_escape_char(*f, t); - else - *(t++) = *f; - } - - return t; -} - -char *unit_name_escape(const char *f) { - char *r, *t; - - assert(f); - - r = new(char, strlen(f)*4+1); - if (!r) - return NULL; - - t = do_escape(f, r); - *t = 0; - - return r; -} - -int unit_name_unescape(const char *f, char **ret) { - _cleanup_free_ char *r = NULL; - char *t; - - assert(f); - - r = strdup(f); - if (!r) - return -ENOMEM; - - for (t = r; *f; f++) { - if (*f == '-') - *(t++) = '/'; - else if (*f == '\\') { - int a, b; - - if (f[1] != 'x') - return -EINVAL; - - a = unhexchar(f[2]); - if (a < 0) - return -EINVAL; - - b = unhexchar(f[3]); - if (b < 0) - return -EINVAL; - - *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b); - f += 3; - } else - *(t++) = *f; - } - - *t = 0; - - *ret = r; - r = NULL; - - return 0; -} - -int unit_name_path_escape(const char *f, char **ret) { - char *p, *s; - - assert(f); - assert(ret); - - p = strdupa(f); - if (!p) - return -ENOMEM; - - path_kill_slashes(p); - - if (STR_IN_SET(p, "/", "")) - s = strdup("-"); - else { - char *e; - - if (!path_is_safe(p)) - return -EINVAL; - - /* Truncate trailing slashes */ - e = endswith(p, "/"); - if (e) - *e = 0; - - /* Truncate leading slashes */ - if (p[0] == '/') - p++; - - s = unit_name_escape(p); - } - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -int unit_name_path_unescape(const char *f, char **ret) { - char *s; - int r; - - assert(f); - - if (isempty(f)) - return -EINVAL; - - if (streq(f, "-")) { - s = strdup("/"); - if (!s) - return -ENOMEM; - } else { - char *w; - - r = unit_name_unescape(f, &w); - if (r < 0) - return r; - - /* Don't accept trailing or leading slashes */ - if (startswith(w, "/") || endswith(w, "/")) { - free(w); - return -EINVAL; - } - - /* Prefix a slash again */ - s = strappend("/", w); - free(w); - if (!s) - return -ENOMEM; - - if (!path_is_safe(s)) { - free(s); - return -EINVAL; - } - } - - if (ret) - *ret = s; - else - free(s); - - return 0; -} - -int unit_name_replace_instance(const char *f, const char *i, char **ret) { - const char *p, *e; - char *s; - size_t a, b; - - assert(f); - assert(i); - assert(ret); - - if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) - return -EINVAL; - if (!unit_instance_is_valid(i)) - return -EINVAL; - - assert_se(p = strchr(f, '@')); - assert_se(e = strrchr(f, '.')); - - a = p - f; - b = strlen(i); - - s = new(char, a + 1 + b + strlen(e) + 1); - if (!s) - return -ENOMEM; - - strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e); - - *ret = s; - return 0; -} - -int unit_name_template(const char *f, char **ret) { - const char *p, *e; - char *s; - size_t a; - - assert(f); - assert(ret); - - if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) - return -EINVAL; - - assert_se(p = strchr(f, '@')); - assert_se(e = strrchr(f, '.')); - - a = p - f; - - s = new(char, a + 1 + strlen(e) + 1); - if (!s) - return -ENOMEM; - - strcpy(mempcpy(s, f, a + 1), e); - - *ret = s; - return 0; -} - -int unit_name_from_path(const char *path, const char *suffix, char **ret) { - _cleanup_free_ char *p = NULL; - char *s = NULL; - int r; - - assert(path); - assert(suffix); - assert(ret); - - if (!unit_suffix_is_valid(suffix)) - return -EINVAL; - - r = unit_name_path_escape(path, &p); - if (r < 0) - return r; - - s = strappend(p, suffix); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) { - _cleanup_free_ char *p = NULL; - char *s; - int r; - - assert(prefix); - assert(path); - assert(suffix); - assert(ret); - - if (!unit_prefix_is_valid(prefix)) - return -EINVAL; - - if (!unit_suffix_is_valid(suffix)) - return -EINVAL; - - r = unit_name_path_escape(path, &p); - if (r < 0) - return r; - - s = strjoin(prefix, "@", p, suffix, NULL); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -int unit_name_to_path(const char *name, char **ret) { - _cleanup_free_ char *prefix = NULL; - int r; - - assert(name); - - r = unit_name_to_prefix(name, &prefix); - if (r < 0) - return r; - - return unit_name_path_unescape(prefix, ret); -} - -char *unit_dbus_path_from_name(const char *name) { - _cleanup_free_ char *e = NULL; - - assert(name); - - e = bus_label_escape(name); - if (!e) - return NULL; - - return strappend("/org/freedesktop/systemd1/unit/", e); -} - -int unit_name_from_dbus_path(const char *path, char **name) { - const char *e; - char *n; - - e = startswith(path, "/org/freedesktop/systemd1/unit/"); - if (!e) - return -EINVAL; - - n = bus_label_unescape(e); - if (!n) - return -ENOMEM; - - *name = n; - return 0; -} - -const char* unit_dbus_interface_from_type(UnitType t) { - - static const char *const table[_UNIT_TYPE_MAX] = { - [UNIT_SERVICE] = "org.freedesktop.systemd1.Service", - [UNIT_SOCKET] = "org.freedesktop.systemd1.Socket", - [UNIT_BUSNAME] = "org.freedesktop.systemd1.BusName", - [UNIT_TARGET] = "org.freedesktop.systemd1.Target", - [UNIT_DEVICE] = "org.freedesktop.systemd1.Device", - [UNIT_MOUNT] = "org.freedesktop.systemd1.Mount", - [UNIT_AUTOMOUNT] = "org.freedesktop.systemd1.Automount", - [UNIT_SWAP] = "org.freedesktop.systemd1.Swap", - [UNIT_TIMER] = "org.freedesktop.systemd1.Timer", - [UNIT_PATH] = "org.freedesktop.systemd1.Path", - [UNIT_SLICE] = "org.freedesktop.systemd1.Slice", - [UNIT_SCOPE] = "org.freedesktop.systemd1.Scope", - }; - - if (t < 0) - return NULL; - if (t >= _UNIT_TYPE_MAX) - return NULL; - - return table[t]; -} - -const char *unit_dbus_interface_from_name(const char *name) { - UnitType t; - - t = unit_name_to_type(name); - if (t < 0) - return NULL; - - return unit_dbus_interface_from_type(t); -} - -static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) { - const char *valid_chars; - - assert(f); - assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB)); - assert(t); - - /* We'll only escape the obvious characters here, to play - * safe. */ - - valid_chars = allow_globs == UNIT_NAME_GLOB ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT; - - for (; *f; f++) { - if (*f == '/') - *(t++) = '-'; - else if (!strchr(valid_chars, *f)) - t = do_escape_char(*f, t); - else - *(t++) = *f; - } - - return t; -} - -/** - * Convert a string to a unit name. /dev/blah is converted to dev-blah.device, - * /blah/blah is converted to blah-blah.mount, anything else is left alone, - * except that @suffix is appended if a valid unit suffix is not present. - * - * If @allow_globs, globs characters are preserved. Otherwise, they are escaped. - */ -int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) { - char *s, *t; - int r; - - assert(name); - assert(suffix); - assert(ret); - - if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */ - return -EINVAL; - - if (!unit_suffix_is_valid(suffix)) - return -EINVAL; - - /* Already a fully valid unit name? If so, no mangling is necessary... */ - if (unit_name_is_valid(name, UNIT_NAME_ANY)) - goto good; - - /* Already a fully valid globbing expression? If so, no mangling is necessary either... */ - if (allow_globs == UNIT_NAME_GLOB && - string_is_glob(name) && - in_charset(name, VALID_CHARS_GLOB)) - goto good; - - if (is_device_path(name)) { - r = unit_name_from_path(name, ".device", ret); - if (r >= 0) - return 1; - if (r != -EINVAL) - return r; - } - - if (path_is_absolute(name)) { - r = unit_name_from_path(name, ".mount", ret); - if (r >= 0) - return 1; - if (r != -EINVAL) - return r; - } - - s = new(char, strlen(name) * 4 + strlen(suffix) + 1); - if (!s) - return -ENOMEM; - - t = do_escape_mangle(name, allow_globs, s); - *t = 0; - - /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow "foo.*" as a - * valid glob. */ - if ((allow_globs != UNIT_NAME_GLOB || !string_is_glob(s)) && unit_name_to_type(s) < 0) - strcpy(t, suffix); - - *ret = s; - return 1; - -good: - s = strdup(name); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -int slice_build_parent_slice(const char *slice, char **ret) { - char *s, *dash; - int r; - - assert(slice); - assert(ret); - - if (!slice_name_is_valid(slice)) - return -EINVAL; - - if (streq(slice, "-.slice")) { - *ret = NULL; - return 0; - } - - s = strdup(slice); - if (!s) - return -ENOMEM; - - dash = strrchr(s, '-'); - if (dash) - strcpy(dash, ".slice"); - else { - r = free_and_strdup(&s, "-.slice"); - if (r < 0) { - free(s); - return r; - } - } - - *ret = s; - return 1; -} - -int slice_build_subslice(const char *slice, const char*name, char **ret) { - char *subslice; - - assert(slice); - assert(name); - assert(ret); - - if (!slice_name_is_valid(slice)) - return -EINVAL; - - if (!unit_prefix_is_valid(name)) - return -EINVAL; - - if (streq(slice, "-.slice")) - subslice = strappend(name, ".slice"); - else { - char *e; - - assert_se(e = endswith(slice, ".slice")); - - subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1); - if (!subslice) - return -ENOMEM; - - stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice"); - } - - *ret = subslice; - return 0; -} - -bool slice_name_is_valid(const char *name) { - const char *p, *e; - bool dash = false; - - if (!unit_name_is_valid(name, UNIT_NAME_PLAIN)) - return false; - - if (streq(name, "-.slice")) - return true; - - e = endswith(name, ".slice"); - if (!e) - return false; - - for (p = name; p < e; p++) { - - if (*p == '-') { - - /* Don't allow initial dash */ - if (p == name) - return false; - - /* Don't allow multiple dashes */ - if (dash) - return false; - - dash = true; - } else - dash = false; - } - - /* Don't allow trailing hash */ - if (dash) - return false; - - return true; -} - -static const char* const unit_type_table[_UNIT_TYPE_MAX] = { - [UNIT_SERVICE] = "service", - [UNIT_SOCKET] = "socket", - [UNIT_BUSNAME] = "busname", - [UNIT_TARGET] = "target", - [UNIT_DEVICE] = "device", - [UNIT_MOUNT] = "mount", - [UNIT_AUTOMOUNT] = "automount", - [UNIT_SWAP] = "swap", - [UNIT_TIMER] = "timer", - [UNIT_PATH] = "path", - [UNIT_SLICE] = "slice", - [UNIT_SCOPE] = "scope", -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType); - -static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { - [UNIT_STUB] = "stub", - [UNIT_LOADED] = "loaded", - [UNIT_NOT_FOUND] = "not-found", - [UNIT_ERROR] = "error", - [UNIT_MERGED] = "merged", - [UNIT_MASKED] = "masked" -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); - -static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { - [UNIT_ACTIVE] = "active", - [UNIT_RELOADING] = "reloading", - [UNIT_INACTIVE] = "inactive", - [UNIT_FAILED] = "failed", - [UNIT_ACTIVATING] = "activating", - [UNIT_DEACTIVATING] = "deactivating" -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); - -static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = { - [AUTOMOUNT_DEAD] = "dead", - [AUTOMOUNT_WAITING] = "waiting", - [AUTOMOUNT_RUNNING] = "running", - [AUTOMOUNT_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(automount_state, AutomountState); - -static const char* const busname_state_table[_BUSNAME_STATE_MAX] = { - [BUSNAME_DEAD] = "dead", - [BUSNAME_MAKING] = "making", - [BUSNAME_REGISTERED] = "registered", - [BUSNAME_LISTENING] = "listening", - [BUSNAME_RUNNING] = "running", - [BUSNAME_SIGTERM] = "sigterm", - [BUSNAME_SIGKILL] = "sigkill", - [BUSNAME_FAILED] = "failed", -}; - -DEFINE_STRING_TABLE_LOOKUP(busname_state, BusNameState); - -static const char* const device_state_table[_DEVICE_STATE_MAX] = { - [DEVICE_DEAD] = "dead", - [DEVICE_TENTATIVE] = "tentative", - [DEVICE_PLUGGED] = "plugged", -}; - -DEFINE_STRING_TABLE_LOOKUP(device_state, DeviceState); - -static const char* const mount_state_table[_MOUNT_STATE_MAX] = { - [MOUNT_DEAD] = "dead", - [MOUNT_MOUNTING] = "mounting", - [MOUNT_MOUNTING_DONE] = "mounting-done", - [MOUNT_MOUNTED] = "mounted", - [MOUNT_REMOUNTING] = "remounting", - [MOUNT_UNMOUNTING] = "unmounting", - [MOUNT_MOUNTING_SIGTERM] = "mounting-sigterm", - [MOUNT_MOUNTING_SIGKILL] = "mounting-sigkill", - [MOUNT_REMOUNTING_SIGTERM] = "remounting-sigterm", - [MOUNT_REMOUNTING_SIGKILL] = "remounting-sigkill", - [MOUNT_UNMOUNTING_SIGTERM] = "unmounting-sigterm", - [MOUNT_UNMOUNTING_SIGKILL] = "unmounting-sigkill", - [MOUNT_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(mount_state, MountState); - -static const char* const path_state_table[_PATH_STATE_MAX] = { - [PATH_DEAD] = "dead", - [PATH_WAITING] = "waiting", - [PATH_RUNNING] = "running", - [PATH_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(path_state, PathState); - -static const char* const scope_state_table[_SCOPE_STATE_MAX] = { - [SCOPE_DEAD] = "dead", - [SCOPE_RUNNING] = "running", - [SCOPE_ABANDONED] = "abandoned", - [SCOPE_STOP_SIGTERM] = "stop-sigterm", - [SCOPE_STOP_SIGKILL] = "stop-sigkill", - [SCOPE_FAILED] = "failed", -}; - -DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState); - -static const char* const service_state_table[_SERVICE_STATE_MAX] = { - [SERVICE_DEAD] = "dead", - [SERVICE_START_PRE] = "start-pre", - [SERVICE_START] = "start", - [SERVICE_START_POST] = "start-post", - [SERVICE_RUNNING] = "running", - [SERVICE_EXITED] = "exited", - [SERVICE_RELOAD] = "reload", - [SERVICE_STOP] = "stop", - [SERVICE_STOP_SIGABRT] = "stop-sigabrt", - [SERVICE_STOP_SIGTERM] = "stop-sigterm", - [SERVICE_STOP_SIGKILL] = "stop-sigkill", - [SERVICE_STOP_POST] = "stop-post", - [SERVICE_FINAL_SIGTERM] = "final-sigterm", - [SERVICE_FINAL_SIGKILL] = "final-sigkill", - [SERVICE_FAILED] = "failed", - [SERVICE_AUTO_RESTART] = "auto-restart", -}; - -DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState); - -static const char* const slice_state_table[_SLICE_STATE_MAX] = { - [SLICE_DEAD] = "dead", - [SLICE_ACTIVE] = "active" -}; - -DEFINE_STRING_TABLE_LOOKUP(slice_state, SliceState); - -static const char* const socket_state_table[_SOCKET_STATE_MAX] = { - [SOCKET_DEAD] = "dead", - [SOCKET_START_PRE] = "start-pre", - [SOCKET_START_CHOWN] = "start-chown", - [SOCKET_START_POST] = "start-post", - [SOCKET_LISTENING] = "listening", - [SOCKET_RUNNING] = "running", - [SOCKET_STOP_PRE] = "stop-pre", - [SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm", - [SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill", - [SOCKET_STOP_POST] = "stop-post", - [SOCKET_FINAL_SIGTERM] = "final-sigterm", - [SOCKET_FINAL_SIGKILL] = "final-sigkill", - [SOCKET_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(socket_state, SocketState); - -static const char* const swap_state_table[_SWAP_STATE_MAX] = { - [SWAP_DEAD] = "dead", - [SWAP_ACTIVATING] = "activating", - [SWAP_ACTIVATING_DONE] = "activating-done", - [SWAP_ACTIVE] = "active", - [SWAP_DEACTIVATING] = "deactivating", - [SWAP_ACTIVATING_SIGTERM] = "activating-sigterm", - [SWAP_ACTIVATING_SIGKILL] = "activating-sigkill", - [SWAP_DEACTIVATING_SIGTERM] = "deactivating-sigterm", - [SWAP_DEACTIVATING_SIGKILL] = "deactivating-sigkill", - [SWAP_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState); - -static const char* const target_state_table[_TARGET_STATE_MAX] = { - [TARGET_DEAD] = "dead", - [TARGET_ACTIVE] = "active" -}; - -DEFINE_STRING_TABLE_LOOKUP(target_state, TargetState); - -static const char* const timer_state_table[_TIMER_STATE_MAX] = { - [TIMER_DEAD] = "dead", - [TIMER_WAITING] = "waiting", - [TIMER_RUNNING] = "running", - [TIMER_ELAPSED] = "elapsed", - [TIMER_FAILED] = "failed" -}; - -DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState); - -static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = { - [UNIT_REQUIRES] = "Requires", - [UNIT_REQUISITE] = "Requisite", - [UNIT_WANTS] = "Wants", - [UNIT_BINDS_TO] = "BindsTo", - [UNIT_PART_OF] = "PartOf", - [UNIT_REQUIRED_BY] = "RequiredBy", - [UNIT_REQUISITE_OF] = "RequisiteOf", - [UNIT_WANTED_BY] = "WantedBy", - [UNIT_BOUND_BY] = "BoundBy", - [UNIT_CONSISTS_OF] = "ConsistsOf", - [UNIT_CONFLICTS] = "Conflicts", - [UNIT_CONFLICTED_BY] = "ConflictedBy", - [UNIT_BEFORE] = "Before", - [UNIT_AFTER] = "After", - [UNIT_ON_FAILURE] = "OnFailure", - [UNIT_TRIGGERS] = "Triggers", - [UNIT_TRIGGERED_BY] = "TriggeredBy", - [UNIT_PROPAGATES_RELOAD_TO] = "PropagatesReloadTo", - [UNIT_RELOAD_PROPAGATED_FROM] = "ReloadPropagatedFrom", - [UNIT_JOINS_NAMESPACE_OF] = "JoinsNamespaceOf", - [UNIT_REFERENCES] = "References", - [UNIT_REFERENCED_BY] = "ReferencedBy", -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency); diff --git a/src/basic/unit-name.h b/src/basic/unit-name.h deleted file mode 100644 index f209a84634..0000000000 --- a/src/basic/unit-name.h +++ /dev/null @@ -1,368 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "macro.h" - -#define UNIT_NAME_MAX 256 - -typedef enum UnitType { - UNIT_SERVICE = 0, - UNIT_SOCKET, - UNIT_BUSNAME, - UNIT_TARGET, - UNIT_DEVICE, - UNIT_MOUNT, - UNIT_AUTOMOUNT, - UNIT_SWAP, - UNIT_TIMER, - UNIT_PATH, - UNIT_SLICE, - UNIT_SCOPE, - _UNIT_TYPE_MAX, - _UNIT_TYPE_INVALID = -1 -} UnitType; - -typedef enum UnitLoadState { - UNIT_STUB = 0, - UNIT_LOADED, - UNIT_NOT_FOUND, - UNIT_ERROR, - UNIT_MERGED, - UNIT_MASKED, - _UNIT_LOAD_STATE_MAX, - _UNIT_LOAD_STATE_INVALID = -1 -} UnitLoadState; - -typedef enum UnitActiveState { - UNIT_ACTIVE, - UNIT_RELOADING, - UNIT_INACTIVE, - UNIT_FAILED, - UNIT_ACTIVATING, - UNIT_DEACTIVATING, - _UNIT_ACTIVE_STATE_MAX, - _UNIT_ACTIVE_STATE_INVALID = -1 -} UnitActiveState; - -typedef enum AutomountState { - AUTOMOUNT_DEAD, - AUTOMOUNT_WAITING, - AUTOMOUNT_RUNNING, - AUTOMOUNT_FAILED, - _AUTOMOUNT_STATE_MAX, - _AUTOMOUNT_STATE_INVALID = -1 -} AutomountState; - -typedef enum BusNameState { - BUSNAME_DEAD, - BUSNAME_MAKING, - BUSNAME_REGISTERED, - BUSNAME_LISTENING, - BUSNAME_RUNNING, - BUSNAME_SIGTERM, - BUSNAME_SIGKILL, - BUSNAME_FAILED, - _BUSNAME_STATE_MAX, - _BUSNAME_STATE_INVALID = -1 -} BusNameState; - -/* We simply watch devices, we cannot plug/unplug them. That - * simplifies the state engine greatly */ -typedef enum DeviceState { - DEVICE_DEAD, - DEVICE_TENTATIVE, /* mounted or swapped, but not (yet) announced by udev */ - DEVICE_PLUGGED, /* announced by udev */ - _DEVICE_STATE_MAX, - _DEVICE_STATE_INVALID = -1 -} DeviceState; - -typedef enum MountState { - MOUNT_DEAD, - MOUNT_MOUNTING, /* /usr/bin/mount is running, but the mount is not done yet. */ - MOUNT_MOUNTING_DONE, /* /usr/bin/mount is running, and the mount is done. */ - MOUNT_MOUNTED, - MOUNT_REMOUNTING, - MOUNT_UNMOUNTING, - MOUNT_MOUNTING_SIGTERM, - MOUNT_MOUNTING_SIGKILL, - MOUNT_REMOUNTING_SIGTERM, - MOUNT_REMOUNTING_SIGKILL, - MOUNT_UNMOUNTING_SIGTERM, - MOUNT_UNMOUNTING_SIGKILL, - MOUNT_FAILED, - _MOUNT_STATE_MAX, - _MOUNT_STATE_INVALID = -1 -} MountState; - -typedef enum PathState { - PATH_DEAD, - PATH_WAITING, - PATH_RUNNING, - PATH_FAILED, - _PATH_STATE_MAX, - _PATH_STATE_INVALID = -1 -} PathState; - -typedef enum ScopeState { - SCOPE_DEAD, - SCOPE_RUNNING, - SCOPE_ABANDONED, - SCOPE_STOP_SIGTERM, - SCOPE_STOP_SIGKILL, - SCOPE_FAILED, - _SCOPE_STATE_MAX, - _SCOPE_STATE_INVALID = -1 -} ScopeState; - -typedef enum ServiceState { - SERVICE_DEAD, - SERVICE_START_PRE, - SERVICE_START, - SERVICE_START_POST, - SERVICE_RUNNING, - SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */ - SERVICE_RELOAD, - SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */ - SERVICE_STOP_SIGABRT, /* Watchdog timeout */ - SERVICE_STOP_SIGTERM, - SERVICE_STOP_SIGKILL, - SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */ - SERVICE_FINAL_SIGKILL, - SERVICE_FAILED, - SERVICE_AUTO_RESTART, - _SERVICE_STATE_MAX, - _SERVICE_STATE_INVALID = -1 -} ServiceState; - -typedef enum SliceState { - SLICE_DEAD, - SLICE_ACTIVE, - _SLICE_STATE_MAX, - _SLICE_STATE_INVALID = -1 -} SliceState; - -typedef enum SocketState { - SOCKET_DEAD, - SOCKET_START_PRE, - SOCKET_START_CHOWN, - SOCKET_START_POST, - SOCKET_LISTENING, - SOCKET_RUNNING, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL, - SOCKET_STOP_POST, - SOCKET_FINAL_SIGTERM, - SOCKET_FINAL_SIGKILL, - SOCKET_FAILED, - _SOCKET_STATE_MAX, - _SOCKET_STATE_INVALID = -1 -} SocketState; - -typedef enum SwapState { - SWAP_DEAD, - SWAP_ACTIVATING, /* /sbin/swapon is running, but the swap not yet enabled. */ - SWAP_ACTIVATING_DONE, /* /sbin/swapon is running, and the swap is done. */ - SWAP_ACTIVE, - SWAP_DEACTIVATING, - SWAP_ACTIVATING_SIGTERM, - SWAP_ACTIVATING_SIGKILL, - SWAP_DEACTIVATING_SIGTERM, - SWAP_DEACTIVATING_SIGKILL, - SWAP_FAILED, - _SWAP_STATE_MAX, - _SWAP_STATE_INVALID = -1 -} SwapState; - - -typedef enum TargetState { - TARGET_DEAD, - TARGET_ACTIVE, - _TARGET_STATE_MAX, - _TARGET_STATE_INVALID = -1 -} TargetState; - -typedef enum TimerState { - TIMER_DEAD, - TIMER_WAITING, - TIMER_RUNNING, - TIMER_ELAPSED, - TIMER_FAILED, - _TIMER_STATE_MAX, - _TIMER_STATE_INVALID = -1 -} TimerState; - -typedef enum UnitDependency { - /* Positive dependencies */ - UNIT_REQUIRES, - UNIT_REQUISITE, - UNIT_WANTS, - UNIT_BINDS_TO, - UNIT_PART_OF, - - /* Inverse of the above */ - UNIT_REQUIRED_BY, /* inverse of 'requires' is 'required_by' */ - UNIT_REQUISITE_OF, /* inverse of 'requisite' is 'requisite_of' */ - UNIT_WANTED_BY, /* inverse of 'wants' */ - UNIT_BOUND_BY, /* inverse of 'binds_to' */ - UNIT_CONSISTS_OF, /* inverse of 'part_of' */ - - /* Negative dependencies */ - UNIT_CONFLICTS, /* inverse of 'conflicts' is 'conflicted_by' */ - UNIT_CONFLICTED_BY, - - /* Order */ - UNIT_BEFORE, /* inverse of 'before' is 'after' and vice versa */ - UNIT_AFTER, - - /* On Failure */ - UNIT_ON_FAILURE, - - /* Triggers (i.e. a socket triggers a service) */ - UNIT_TRIGGERS, - UNIT_TRIGGERED_BY, - - /* Propagate reloads */ - UNIT_PROPAGATES_RELOAD_TO, - UNIT_RELOAD_PROPAGATED_FROM, - - /* Joins namespace of */ - UNIT_JOINS_NAMESPACE_OF, - - /* Reference information for GC logic */ - UNIT_REFERENCES, /* Inverse of 'references' is 'referenced_by' */ - UNIT_REFERENCED_BY, - - _UNIT_DEPENDENCY_MAX, - _UNIT_DEPENDENCY_INVALID = -1 -} UnitDependency; - -typedef enum UnitNameFlags { - UNIT_NAME_PLAIN = 1, /* Allow foo.service */ - UNIT_NAME_INSTANCE = 2, /* Allow foo@bar.service */ - UNIT_NAME_TEMPLATE = 4, /* Allow foo@.service */ - UNIT_NAME_ANY = UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE, -} UnitNameFlags; - -bool unit_name_is_valid(const char *n, UnitNameFlags flags) _pure_; -bool unit_prefix_is_valid(const char *p) _pure_; -bool unit_instance_is_valid(const char *i) _pure_; -bool unit_suffix_is_valid(const char *s) _pure_; - -static inline int unit_prefix_and_instance_is_valid(const char *p) { - /* For prefix+instance and instance the same rules apply */ - return unit_instance_is_valid(p); -} - -int unit_name_to_prefix(const char *n, char **prefix); -int unit_name_to_instance(const char *n, char **instance); -int unit_name_to_prefix_and_instance(const char *n, char **ret); - -UnitType unit_name_to_type(const char *n) _pure_; - -int unit_name_change_suffix(const char *n, const char *suffix, char **ret); - -int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret); - -char *unit_name_escape(const char *f); -int unit_name_unescape(const char *f, char **ret); -int unit_name_path_escape(const char *f, char **ret); -int unit_name_path_unescape(const char *f, char **ret); - -int unit_name_replace_instance(const char *f, const char *i, char **ret); - -int unit_name_template(const char *f, char **ret); - -int unit_name_from_path(const char *path, const char *suffix, char **ret); -int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret); -int unit_name_to_path(const char *name, char **ret); - -char *unit_dbus_path_from_name(const char *name); -int unit_name_from_dbus_path(const char *path, char **name); - -const char* unit_dbus_interface_from_type(UnitType t); -const char *unit_dbus_interface_from_name(const char *name); - -typedef enum UnitNameMangle { - UNIT_NAME_NOGLOB, - UNIT_NAME_GLOB, -} UnitNameMangle; - -int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret); - -static inline int unit_name_mangle(const char *name, UnitNameMangle allow_globs, char **ret) { - return unit_name_mangle_with_suffix(name, allow_globs, ".service", ret); -} - -int slice_build_parent_slice(const char *slice, char **ret); -int slice_build_subslice(const char *slice, const char*name, char **subslice); -bool slice_name_is_valid(const char *name); - -const char *unit_type_to_string(UnitType i) _const_; -UnitType unit_type_from_string(const char *s) _pure_; - -const char *unit_load_state_to_string(UnitLoadState i) _const_; -UnitLoadState unit_load_state_from_string(const char *s) _pure_; - -const char *unit_active_state_to_string(UnitActiveState i) _const_; -UnitActiveState unit_active_state_from_string(const char *s) _pure_; - -const char* automount_state_to_string(AutomountState i) _const_; -AutomountState automount_state_from_string(const char *s) _pure_; - -const char* busname_state_to_string(BusNameState i) _const_; -BusNameState busname_state_from_string(const char *s) _pure_; - -const char* device_state_to_string(DeviceState i) _const_; -DeviceState device_state_from_string(const char *s) _pure_; - -const char* mount_state_to_string(MountState i) _const_; -MountState mount_state_from_string(const char *s) _pure_; - -const char* path_state_to_string(PathState i) _const_; -PathState path_state_from_string(const char *s) _pure_; - -const char* scope_state_to_string(ScopeState i) _const_; -ScopeState scope_state_from_string(const char *s) _pure_; - -const char* service_state_to_string(ServiceState i) _const_; -ServiceState service_state_from_string(const char *s) _pure_; - -const char* slice_state_to_string(SliceState i) _const_; -SliceState slice_state_from_string(const char *s) _pure_; - -const char* socket_state_to_string(SocketState i) _const_; -SocketState socket_state_from_string(const char *s) _pure_; - -const char* swap_state_to_string(SwapState i) _const_; -SwapState swap_state_from_string(const char *s) _pure_; - -const char* target_state_to_string(TargetState i) _const_; -TargetState target_state_from_string(const char *s) _pure_; - -const char *timer_state_to_string(TimerState i) _const_; -TimerState timer_state_from_string(const char *s) _pure_; - -const char *unit_dependency_to_string(UnitDependency i) _const_; -UnitDependency unit_dependency_from_string(const char *s) _pure_; diff --git a/src/basic/user-util.c b/src/basic/user-util.c deleted file mode 100644 index 19155bce53..0000000000 --- a/src/basic/user-util.c +++ /dev/null @@ -1,480 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "string-util.h" -#include "user-util.h" - -bool uid_is_valid(uid_t uid) { - - /* Some libc APIs use UID_INVALID as special placeholder */ - if (uid == (uid_t) UINT32_C(0xFFFFFFFF)) - return false; - - /* A long time ago UIDs where 16bit, hence explicitly avoid the 16bit -1 too */ - if (uid == (uid_t) UINT32_C(0xFFFF)) - return false; - - return true; -} - -int parse_uid(const char *s, uid_t *ret) { - uint32_t uid = 0; - int r; - - assert(s); - - assert_cc(sizeof(uid_t) == sizeof(uint32_t)); - r = safe_atou32(s, &uid); - if (r < 0) - return r; - - if (!uid_is_valid(uid)) - return -ENXIO; /* we return ENXIO instead of EINVAL - * here, to make it easy to distuingish - * invalid numeric uids from invalid - * strings. */ - - if (ret) - *ret = uid; - - return 0; -} - -char* getlogname_malloc(void) { - uid_t uid; - struct stat st; - - if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0) - uid = st.st_uid; - else - uid = getuid(); - - return uid_to_name(uid); -} - -char *getusername_malloc(void) { - const char *e; - - e = getenv("USER"); - if (e) - return strdup(e); - - return uid_to_name(getuid()); -} - -int get_user_creds( - const char **username, - uid_t *uid, gid_t *gid, - const char **home, - const char **shell) { - - struct passwd *p; - uid_t u; - - assert(username); - assert(*username); - - /* We enforce some special rules for uid=0: in order to avoid - * NSS lookups for root we hardcode its data. */ - - if (streq(*username, "root") || streq(*username, "0")) { - *username = "root"; - - if (uid) - *uid = 0; - - if (gid) - *gid = 0; - - if (home) - *home = "/root"; - - if (shell) - *shell = "/bin/sh"; - - return 0; - } - - if (parse_uid(*username, &u) >= 0) { - errno = 0; - p = getpwuid(u); - - /* If there are multiple users with the same id, make - * sure to leave $USER to the configured value instead - * of the first occurrence in the database. However if - * the uid was configured by a numeric uid, then let's - * pick the real username from /etc/passwd. */ - if (p) - *username = p->pw_name; - } else { - errno = 0; - p = getpwnam(*username); - } - - if (!p) - return errno > 0 ? -errno : -ESRCH; - - if (uid) { - if (!uid_is_valid(p->pw_uid)) - return -EBADMSG; - - *uid = p->pw_uid; - } - - if (gid) { - if (!gid_is_valid(p->pw_gid)) - return -EBADMSG; - - *gid = p->pw_gid; - } - - if (home) - *home = p->pw_dir; - - if (shell) - *shell = p->pw_shell; - - return 0; -} - -int get_group_creds(const char **groupname, gid_t *gid) { - struct group *g; - gid_t id; - - assert(groupname); - - /* We enforce some special rules for gid=0: in order to avoid - * NSS lookups for root we hardcode its data. */ - - if (streq(*groupname, "root") || streq(*groupname, "0")) { - *groupname = "root"; - - if (gid) - *gid = 0; - - return 0; - } - - if (parse_gid(*groupname, &id) >= 0) { - errno = 0; - g = getgrgid(id); - - if (g) - *groupname = g->gr_name; - } else { - errno = 0; - g = getgrnam(*groupname); - } - - if (!g) - return errno > 0 ? -errno : -ESRCH; - - if (gid) { - if (!gid_is_valid(g->gr_gid)) - return -EBADMSG; - - *gid = g->gr_gid; - } - - return 0; -} - -char* uid_to_name(uid_t uid) { - char *ret; - int r; - - /* Shortcut things to avoid NSS lookups */ - if (uid == 0) - return strdup("root"); - - if (uid_is_valid(uid)) { - long bufsize; - - bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); - if (bufsize <= 0) - bufsize = 4096; - - for (;;) { - struct passwd pwbuf, *pw = NULL; - _cleanup_free_ char *buf = NULL; - - buf = malloc(bufsize); - if (!buf) - return NULL; - - r = getpwuid_r(uid, &pwbuf, buf, (size_t) bufsize, &pw); - if (r == 0 && pw) - return strdup(pw->pw_name); - if (r != ERANGE) - break; - - bufsize *= 2; - } - } - - if (asprintf(&ret, UID_FMT, uid) < 0) - return NULL; - - return ret; -} - -char* gid_to_name(gid_t gid) { - char *ret; - int r; - - if (gid == 0) - return strdup("root"); - - if (gid_is_valid(gid)) { - long bufsize; - - bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); - if (bufsize <= 0) - bufsize = 4096; - - for (;;) { - struct group grbuf, *gr = NULL; - _cleanup_free_ char *buf = NULL; - - buf = malloc(bufsize); - if (!buf) - return NULL; - - r = getgrgid_r(gid, &grbuf, buf, (size_t) bufsize, &gr); - if (r == 0 && gr) - return strdup(gr->gr_name); - if (r != ERANGE) - break; - - bufsize *= 2; - } - } - - if (asprintf(&ret, GID_FMT, gid) < 0) - return NULL; - - return ret; -} - -int in_gid(gid_t gid) { - gid_t *gids; - int ngroups_max, r, i; - - if (getgid() == gid) - return 1; - - if (getegid() == gid) - return 1; - - if (!gid_is_valid(gid)) - return -EINVAL; - - ngroups_max = sysconf(_SC_NGROUPS_MAX); - assert(ngroups_max > 0); - - gids = alloca(sizeof(gid_t) * ngroups_max); - - r = getgroups(ngroups_max, gids); - if (r < 0) - return -errno; - - for (i = 0; i < r; i++) - if (gids[i] == gid) - return 1; - - return 0; -} - -int in_group(const char *name) { - int r; - gid_t gid; - - r = get_group_creds(&name, &gid); - if (r < 0) - return r; - - return in_gid(gid); -} - -int get_home_dir(char **_h) { - struct passwd *p; - const char *e; - char *h; - uid_t u; - - assert(_h); - - /* Take the user specified one */ - e = secure_getenv("HOME"); - if (e && path_is_absolute(e)) { - h = strdup(e); - if (!h) - return -ENOMEM; - - *_h = h; - return 0; - } - - /* Hardcode home directory for root to avoid NSS */ - u = getuid(); - if (u == 0) { - h = strdup("/root"); - if (!h) - return -ENOMEM; - - *_h = h; - return 0; - } - - /* Check the database... */ - errno = 0; - p = getpwuid(u); - if (!p) - return errno > 0 ? -errno : -ESRCH; - - if (!path_is_absolute(p->pw_dir)) - return -EINVAL; - - h = strdup(p->pw_dir); - if (!h) - return -ENOMEM; - - *_h = h; - return 0; -} - -int get_shell(char **_s) { - struct passwd *p; - const char *e; - char *s; - uid_t u; - - assert(_s); - - /* Take the user specified one */ - e = getenv("SHELL"); - if (e) { - s = strdup(e); - if (!s) - return -ENOMEM; - - *_s = s; - return 0; - } - - /* Hardcode home directory for root to avoid NSS */ - u = getuid(); - if (u == 0) { - s = strdup("/bin/sh"); - if (!s) - return -ENOMEM; - - *_s = s; - return 0; - } - - /* Check the database... */ - errno = 0; - p = getpwuid(u); - if (!p) - return errno > 0 ? -errno : -ESRCH; - - if (!path_is_absolute(p->pw_shell)) - return -EINVAL; - - s = strdup(p->pw_shell); - if (!s) - return -ENOMEM; - - *_s = s; - return 0; -} - -int reset_uid_gid(void) { - - if (setgroups(0, NULL) < 0) - return -errno; - - if (setresgid(0, 0, 0) < 0) - return -errno; - - if (setresuid(0, 0, 0) < 0) - return -errno; - - return 0; -} - -int take_etc_passwd_lock(const char *root) { - - struct flock flock = { - .l_type = F_WRLCK, - .l_whence = SEEK_SET, - .l_start = 0, - .l_len = 0, - }; - - const char *path; - int fd, r; - - /* This is roughly the same as lckpwdf(), but not as awful. We - * don't want to use alarm() and signals, hence we implement - * our own trivial version of this. - * - * Note that shadow-utils also takes per-database locks in - * addition to lckpwdf(). However, we don't given that they - * are redundant as they 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. */ - - if (root) - path = prefix_roota(root, "/etc/.pwd.lock"); - else - path = "/etc/.pwd.lock"; - - fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600); - if (fd < 0) - return -errno; - - r = fcntl(fd, F_SETLKW, &flock); - if (r < 0) { - safe_close(fd); - return -errno; - } - - return fd; -} diff --git a/src/basic/user-util.h b/src/basic/user-util.h deleted file mode 100644 index c23f1d485d..0000000000 --- a/src/basic/user-util.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -bool uid_is_valid(uid_t uid); - -static inline bool gid_is_valid(gid_t gid) { - return uid_is_valid((uid_t) gid); -} - -int parse_uid(const char *s, uid_t* ret_uid); - -static inline int parse_gid(const char *s, gid_t *ret_gid) { - return parse_uid(s, (uid_t*) ret_gid); -} - -char* getlogname_malloc(void); -char* getusername_malloc(void); - -int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home, const char **shell); -int get_group_creds(const char **groupname, gid_t *gid); - -char* uid_to_name(uid_t uid); -char* gid_to_name(gid_t gid); - -int in_gid(gid_t gid); -int in_group(const char *name); - -int get_home_dir(char **ret); -int get_shell(char **_ret); - -int reset_uid_gid(void); - -int take_etc_passwd_lock(const char *root); - -#define UID_INVALID ((uid_t) -1) -#define GID_INVALID ((gid_t) -1) - -/* The following macros add 1 when converting things, since UID 0 is a - * valid UID, while the pointer NULL is special */ -#define PTR_TO_UID(p) ((uid_t) (((uintptr_t) (p))-1)) -#define UID_TO_PTR(u) ((void*) (((uintptr_t) (u))+1)) - -#define PTR_TO_GID(p) ((gid_t) (((uintptr_t) (p))-1)) -#define GID_TO_PTR(u) ((void*) (((uintptr_t) (u))+1)) diff --git a/src/basic/utf8.c b/src/basic/utf8.c deleted file mode 100644 index 629db123cd..0000000000 --- a/src/basic/utf8.c +++ /dev/null @@ -1,409 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2011 Kay Sievers - Copyright 2012 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 . -***/ - -/* Parts of this file are based on the GLIB utf8 validation functions. The - * original license text follows. */ - -/* gutf8.c - Operations on UTF-8 strings. - * - * Copyright (C) 1999 Tom Tromey - * Copyright (C) 2000 Red Hat, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Library General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Library General Public License for more details. - * - * You should have received a copy of the GNU Library General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include -#include -#include -#include - -#include "alloc-util.h" -#include "hexdecoct.h" -#include "macro.h" -#include "utf8.h" - -bool unichar_is_valid(char32_t ch) { - - if (ch >= 0x110000) /* End of unicode space */ - return false; - if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */ - return false; - if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */ - return false; - if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */ - return false; - - return true; -} - -static bool unichar_is_control(char32_t ch) { - - /* - 0 to ' '-1 is the C0 range. - DEL=0x7F, and DEL+1 to 0x9F is C1 range. - '\t' is in C0 range, but more or less harmless and commonly used. - */ - - return (ch < ' ' && ch != '\t' && ch != '\n') || - (0x7F <= ch && ch <= 0x9F); -} - -/* count of characters used to encode one unicode char */ -static int utf8_encoded_expected_len(const char *str) { - unsigned char c; - - assert(str); - - c = (unsigned char) str[0]; - if (c < 0x80) - return 1; - if ((c & 0xe0) == 0xc0) - return 2; - if ((c & 0xf0) == 0xe0) - return 3; - if ((c & 0xf8) == 0xf0) - return 4; - if ((c & 0xfc) == 0xf8) - return 5; - if ((c & 0xfe) == 0xfc) - return 6; - - return 0; -} - -/* decode one unicode char */ -int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar) { - char32_t unichar; - int len, i; - - assert(str); - - len = utf8_encoded_expected_len(str); - - switch (len) { - case 1: - *ret_unichar = (char32_t)str[0]; - return 0; - case 2: - unichar = str[0] & 0x1f; - break; - case 3: - unichar = (char32_t)str[0] & 0x0f; - break; - case 4: - unichar = (char32_t)str[0] & 0x07; - break; - case 5: - unichar = (char32_t)str[0] & 0x03; - break; - case 6: - unichar = (char32_t)str[0] & 0x01; - break; - default: - return -EINVAL; - } - - for (i = 1; i < len; i++) { - if (((char32_t)str[i] & 0xc0) != 0x80) - return -EINVAL; - unichar <<= 6; - unichar |= (char32_t)str[i] & 0x3f; - } - - *ret_unichar = unichar; - - return 0; -} - -bool utf8_is_printable_newline(const char* str, size_t length, bool newline) { - const char *p; - - assert(str); - - for (p = str; length;) { - int encoded_len, r; - char32_t val; - - encoded_len = utf8_encoded_valid_unichar(p); - if (encoded_len < 0 || - (size_t) encoded_len > length) - return false; - - r = utf8_encoded_to_unichar(p, &val); - if (r < 0 || - unichar_is_control(val) || - (!newline && val == '\n')) - return false; - - length -= encoded_len; - p += encoded_len; - } - - return true; -} - -const char *utf8_is_valid(const char *str) { - const uint8_t *p; - - assert(str); - - for (p = (const uint8_t*) str; *p; ) { - int len; - - len = utf8_encoded_valid_unichar((const char *)p); - if (len < 0) - return NULL; - - p += len; - } - - return str; -} - -char *utf8_escape_invalid(const char *str) { - char *p, *s; - - assert(str); - - p = s = malloc(strlen(str) * 4 + 1); - if (!p) - return NULL; - - while (*str) { - int len; - - len = utf8_encoded_valid_unichar(str); - if (len > 0) { - s = mempcpy(s, str, len); - str += len; - } else { - s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER); - str += 1; - } - } - - *s = '\0'; - - return p; -} - -char *utf8_escape_non_printable(const char *str) { - char *p, *s; - - assert(str); - - p = s = malloc(strlen(str) * 4 + 1); - if (!p) - return NULL; - - while (*str) { - int len; - - len = utf8_encoded_valid_unichar(str); - if (len > 0) { - if (utf8_is_printable(str, len)) { - s = mempcpy(s, str, len); - str += len; - } else { - while (len > 0) { - *(s++) = '\\'; - *(s++) = 'x'; - *(s++) = hexchar((int) *str >> 4); - *(s++) = hexchar((int) *str); - - str += 1; - len --; - } - } - } else { - s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER); - str += 1; - } - } - - *s = '\0'; - - return p; -} - -char *ascii_is_valid(const char *str) { - const char *p; - - assert(str); - - for (p = str; *p; p++) - if ((unsigned char) *p >= 128) - return NULL; - - return (char*) str; -} - -/** - * utf8_encode_unichar() - Encode single UCS-4 character as UTF-8 - * @out_utf8: output buffer of at least 4 bytes or NULL - * @g: UCS-4 character to encode - * - * This encodes a single UCS-4 character as UTF-8 and writes it into @out_utf8. - * The length of the character is returned. It is not zero-terminated! If the - * output buffer is NULL, only the length is returned. - * - * Returns: The length in bytes that the UTF-8 representation does or would - * occupy. - */ -size_t utf8_encode_unichar(char *out_utf8, char32_t g) { - - if (g < (1 << 7)) { - if (out_utf8) - out_utf8[0] = g & 0x7f; - return 1; - } else if (g < (1 << 11)) { - if (out_utf8) { - out_utf8[0] = 0xc0 | ((g >> 6) & 0x1f); - out_utf8[1] = 0x80 | (g & 0x3f); - } - return 2; - } else if (g < (1 << 16)) { - if (out_utf8) { - out_utf8[0] = 0xe0 | ((g >> 12) & 0x0f); - out_utf8[1] = 0x80 | ((g >> 6) & 0x3f); - out_utf8[2] = 0x80 | (g & 0x3f); - } - return 3; - } else if (g < (1 << 21)) { - if (out_utf8) { - out_utf8[0] = 0xf0 | ((g >> 18) & 0x07); - out_utf8[1] = 0x80 | ((g >> 12) & 0x3f); - out_utf8[2] = 0x80 | ((g >> 6) & 0x3f); - out_utf8[3] = 0x80 | (g & 0x3f); - } - return 4; - } - - return 0; -} - -char *utf16_to_utf8(const void *s, size_t length) { - const uint8_t *f; - char *r, *t; - - r = new(char, (length * 4 + 1) / 2 + 1); - if (!r) - return NULL; - - f = s; - t = r; - - while (f < (const uint8_t*) s + length) { - char16_t w1, w2; - - /* see RFC 2781 section 2.2 */ - - w1 = f[1] << 8 | f[0]; - f += 2; - - if (!utf16_is_surrogate(w1)) { - t += utf8_encode_unichar(t, w1); - - continue; - } - - if (utf16_is_trailing_surrogate(w1)) - continue; - else if (f >= (const uint8_t*) s + length) - break; - - w2 = f[1] << 8 | f[0]; - f += 2; - - if (!utf16_is_trailing_surrogate(w2)) { - f -= 2; - continue; - } - - t += utf8_encode_unichar(t, utf16_surrogate_pair_to_unichar(w1, w2)); - } - - *t = 0; - return r; -} - -/* expected size used to encode one unicode char */ -static int utf8_unichar_to_encoded_len(char32_t unichar) { - - if (unichar < 0x80) - return 1; - if (unichar < 0x800) - return 2; - if (unichar < 0x10000) - return 3; - if (unichar < 0x200000) - return 4; - if (unichar < 0x4000000) - return 5; - - return 6; -} - -/* validate one encoded unicode char and return its length */ -int utf8_encoded_valid_unichar(const char *str) { - int len, i, r; - char32_t unichar; - - assert(str); - - len = utf8_encoded_expected_len(str); - if (len == 0) - return -EINVAL; - - /* ascii is valid */ - if (len == 1) - return 1; - - /* check if expected encoded chars are available */ - for (i = 0; i < len; i++) - if ((str[i] & 0x80) != 0x80) - return -EINVAL; - - r = utf8_encoded_to_unichar(str, &unichar); - if (r < 0) - return r; - - /* check if encoded length matches encoded value */ - if (utf8_unichar_to_encoded_len(unichar) != len) - return -EINVAL; - - /* check if value has valid range */ - if (!unichar_is_valid(unichar)) - return -EINVAL; - - return len; -} diff --git a/src/basic/utf8.h b/src/basic/utf8.h deleted file mode 100644 index 12c272d66e..0000000000 --- a/src/basic/utf8.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include -#include -#include -#include - -#include "macro.h" -#include "missing.h" - -#define UTF8_REPLACEMENT_CHARACTER "\xef\xbf\xbd" - -bool unichar_is_valid(char32_t c); - -const char *utf8_is_valid(const char *s) _pure_; -char *ascii_is_valid(const char *s) _pure_; - -bool utf8_is_printable_newline(const char* str, size_t length, bool newline) _pure_; -#define utf8_is_printable(str, length) utf8_is_printable_newline(str, length, true) - -char *utf8_escape_invalid(const char *s); -char *utf8_escape_non_printable(const char *str); - -size_t utf8_encode_unichar(char *out_utf8, char32_t g); -char *utf16_to_utf8(const void *s, size_t length); - -int utf8_encoded_valid_unichar(const char *str); -int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar); - -static inline bool utf16_is_surrogate(char16_t c) { - return (0xd800 <= c && c <= 0xdfff); -} - -static inline bool utf16_is_trailing_surrogate(char16_t c) { - return (0xdc00 <= c && c <= 0xdfff); -} - -static inline char32_t utf16_surrogate_pair_to_unichar(char16_t lead, char16_t trail) { - return ((lead - 0xd800) << 10) + (trail - 0xdc00) + 0x10000; -} diff --git a/src/basic/util.c b/src/basic/util.c deleted file mode 100644 index ea1bed7ceb..0000000000 --- a/src/basic/util.c +++ /dev/null @@ -1,793 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "build.h" -#include "def.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "hashmap.h" -#include "hostname-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "set.h" -#include "signal-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "user-util.h" -#include "util.h" - -/* Put this test here for a lack of better place */ -assert_cc(EAGAIN == EWOULDBLOCK); - -int saved_argc = 0; -char **saved_argv = NULL; - -size_t page_size(void) { - static thread_local size_t pgsz = 0; - long r; - - if (_likely_(pgsz > 0)) - return pgsz; - - r = sysconf(_SC_PAGESIZE); - assert(r > 0); - - pgsz = (size_t) r; - return pgsz; -} - -static int do_execute(char **directories, usec_t timeout, char *argv[]) { - _cleanup_hashmap_free_free_ Hashmap *pids = NULL; - _cleanup_set_free_free_ Set *seen = NULL; - char **directory; - - /* We fork this all off from a child process so that we can - * somewhat cleanly make use of SIGALRM to set a time limit */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - pids = hashmap_new(NULL); - if (!pids) - return log_oom(); - - seen = set_new(&string_hash_ops); - if (!seen) - return log_oom(); - - STRV_FOREACH(directory, directories) { - _cleanup_closedir_ DIR *d; - struct dirent *de; - - d = opendir(*directory); - if (!d) { - if (errno == ENOENT) - continue; - - return log_error_errno(errno, "Failed to open directory %s: %m", *directory); - } - - FOREACH_DIRENT(de, d, break) { - _cleanup_free_ char *path = NULL; - pid_t pid; - int r; - - if (!dirent_is_file(de)) - continue; - - if (set_contains(seen, de->d_name)) { - log_debug("%1$s/%2$s skipped (%2$s was already seen).", *directory, de->d_name); - continue; - } - - r = set_put_strdup(seen, de->d_name); - if (r < 0) - return log_oom(); - - path = strjoin(*directory, "/", de->d_name, NULL); - if (!path) - return log_oom(); - - if (null_or_empty_path(path)) { - log_debug("%s is empty (a mask).", path); - continue; - } - - pid = fork(); - if (pid < 0) { - log_error_errno(errno, "Failed to fork: %m"); - continue; - } else if (pid == 0) { - char *_argv[2]; - - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - if (!argv) { - _argv[0] = path; - _argv[1] = NULL; - argv = _argv; - } else - argv[0] = path; - - execv(path, argv); - return log_error_errno(errno, "Failed to execute %s: %m", path); - } - - log_debug("Spawned %s as " PID_FMT ".", path, pid); - - r = hashmap_put(pids, PID_TO_PTR(pid), path); - if (r < 0) - return log_oom(); - path = NULL; - } - } - - /* Abort execution of this process after the timout. We simply - * rely on SIGALRM as default action terminating the process, - * and turn on alarm(). */ - - if (timeout != USEC_INFINITY) - alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); - - while (!hashmap_isempty(pids)) { - _cleanup_free_ char *path = NULL; - pid_t pid; - - pid = PTR_TO_PID(hashmap_first_key(pids)); - assert(pid > 0); - - path = hashmap_remove(pids, PID_TO_PTR(pid)); - assert(path); - - wait_for_terminate_and_warn(path, pid, true); - } - - return 0; -} - -void execute_directories(const char* const* directories, usec_t timeout, char *argv[]) { - pid_t executor_pid; - int r; - char *name; - char **dirs = (char**) directories; - - assert(!strv_isempty(dirs)); - - name = basename(dirs[0]); - assert(!isempty(name)); - - /* Executes all binaries in the directories in parallel and waits - * for them to finish. Optionally a timeout is applied. If a file - * with the same name exists in more than one directory, the - * earliest one wins. */ - - executor_pid = fork(); - if (executor_pid < 0) { - log_error_errno(errno, "Failed to fork: %m"); - return; - - } else if (executor_pid == 0) { - r = do_execute(dirs, timeout, argv); - _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); - } - - wait_for_terminate_and_warn(name, executor_pid, true); -} - -bool plymouth_running(void) { - return access("/run/plymouth/pid", F_OK) >= 0; -} - -bool display_is_local(const char *display) { - assert(display); - - return - display[0] == ':' && - display[1] >= '0' && - display[1] <= '9'; -} - -int socket_from_display(const char *display, char **path) { - size_t k; - char *f, *c; - - assert(display); - assert(path); - - if (!display_is_local(display)) - return -EINVAL; - - k = strspn(display+1, "0123456789"); - - f = new(char, strlen("/tmp/.X11-unix/X") + k + 1); - if (!f) - return -ENOMEM; - - c = stpcpy(f, "/tmp/.X11-unix/X"); - memcpy(c, display+1, k); - c[k] = 0; - - *path = f; - - return 0; -} - -int block_get_whole_disk(dev_t d, dev_t *ret) { - char *p, *s; - int r; - unsigned n, m; - - assert(ret); - - /* If it has a queue this is good enough for us */ - if (asprintf(&p, "/sys/dev/block/%u:%u/queue", major(d), minor(d)) < 0) - return -ENOMEM; - - r = access(p, F_OK); - free(p); - - if (r >= 0) { - *ret = d; - return 0; - } - - /* If it is a partition find the originating device */ - if (asprintf(&p, "/sys/dev/block/%u:%u/partition", major(d), minor(d)) < 0) - return -ENOMEM; - - r = access(p, F_OK); - free(p); - - if (r < 0) - return -ENOENT; - - /* Get parent dev_t */ - if (asprintf(&p, "/sys/dev/block/%u:%u/../dev", major(d), minor(d)) < 0) - return -ENOMEM; - - r = read_one_line_file(p, &s); - free(p); - - if (r < 0) - return r; - - r = sscanf(s, "%u:%u", &m, &n); - free(s); - - if (r != 2) - return -EINVAL; - - /* Only return this if it is really good enough for us. */ - if (asprintf(&p, "/sys/dev/block/%u:%u/queue", m, n) < 0) - return -ENOMEM; - - r = access(p, F_OK); - free(p); - - if (r >= 0) { - *ret = makedev(m, n); - return 0; - } - - return -ENOENT; -} - -bool kexec_loaded(void) { - bool loaded = false; - char *s; - - if (read_one_line_file("/sys/kernel/kexec_loaded", &s) >= 0) { - if (s[0] == '1') - loaded = true; - free(s); - } - return loaded; -} - -int prot_from_flags(int flags) { - - switch (flags & O_ACCMODE) { - - case O_RDONLY: - return PROT_READ; - - case O_WRONLY: - return PROT_WRITE; - - case O_RDWR: - return PROT_READ|PROT_WRITE; - - default: - return -EINVAL; - } -} - -int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...) { - bool stdout_is_tty, stderr_is_tty; - pid_t parent_pid, agent_pid; - sigset_t ss, saved_ss; - unsigned n, i; - va_list ap; - char **l; - - assert(pid); - assert(path); - - /* Spawns a temporary TTY agent, making sure it goes away when - * we go away */ - - parent_pid = getpid(); - - /* First we temporarily block all signals, so that the new - * child has them blocked initially. This way, we can be sure - * that SIGTERMs are not lost we might send to the agent. */ - assert_se(sigfillset(&ss) >= 0); - assert_se(sigprocmask(SIG_SETMASK, &ss, &saved_ss) >= 0); - - agent_pid = fork(); - if (agent_pid < 0) { - assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); - return -errno; - } - - if (agent_pid != 0) { - assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); - *pid = agent_pid; - return 0; - } - - /* In the child: - * - * Make sure the agent goes away when the parent dies */ - if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) - _exit(EXIT_FAILURE); - - /* Make sure we actually can kill the agent, if we need to, in - * case somebody invoked us from a shell script that trapped - * SIGTERM or so... */ - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - /* Check whether our parent died before we were able - * to set the death signal and unblock the signals */ - if (getppid() != parent_pid) - _exit(EXIT_SUCCESS); - - /* Don't leak fds to the agent */ - close_all_fds(except, n_except); - - stdout_is_tty = isatty(STDOUT_FILENO); - stderr_is_tty = isatty(STDERR_FILENO); - - if (!stdout_is_tty || !stderr_is_tty) { - int fd; - - /* Detach from stdout/stderr. and reopen - * /dev/tty for them. This is important to - * ensure that when systemctl is started via - * popen() or a similar call that expects to - * read EOF we actually do generate EOF and - * not delay this indefinitely by because we - * keep an unused copy of stdin around. */ - fd = open("/dev/tty", O_WRONLY); - if (fd < 0) { - log_error_errno(errno, "Failed to open /dev/tty: %m"); - _exit(EXIT_FAILURE); - } - - if (!stdout_is_tty) - dup2(fd, STDOUT_FILENO); - - if (!stderr_is_tty) - dup2(fd, STDERR_FILENO); - - if (fd > 2) - close(fd); - } - - /* Count arguments */ - va_start(ap, path); - for (n = 0; va_arg(ap, char*); n++) - ; - va_end(ap); - - /* Allocate strv */ - l = alloca(sizeof(char *) * (n + 1)); - - /* Fill in arguments */ - va_start(ap, path); - for (i = 0; i <= n; i++) - l[i] = va_arg(ap, char*); - va_end(ap); - - execv(path, l); - _exit(EXIT_FAILURE); -} - -bool in_initrd(void) { - static int saved = -1; - struct statfs s; - - if (saved >= 0) - return saved; - - /* We make two checks here: - * - * 1. the flag file /etc/initrd-release must exist - * 2. the root file system must be a memory file system - * - * The second check is extra paranoia, since misdetecting an - * initrd can have bad bad consequences due the initrd - * emptying when transititioning to the main systemd. - */ - - saved = access("/etc/initrd-release", F_OK) >= 0 && - statfs("/", &s) >= 0 && - is_temporary_fs(&s); - - return saved; -} - -/* hey glibc, APIs with callbacks without a user pointer are so useless */ -void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, - int (*compar) (const void *, const void *, void *), void *arg) { - size_t l, u, idx; - const void *p; - int comparison; - - l = 0; - u = nmemb; - while (l < u) { - idx = (l + u) / 2; - p = (void *)(((const char *) base) + (idx * size)); - comparison = compar(key, p, arg); - if (comparison < 0) - u = idx; - else if (comparison > 0) - l = idx + 1; - else - return (void *)p; - } - return NULL; -} - -int on_ac_power(void) { - bool found_offline = false, found_online = false; - _cleanup_closedir_ DIR *d = NULL; - - d = opendir("/sys/class/power_supply"); - if (!d) - return errno == ENOENT ? true : -errno; - - for (;;) { - struct dirent *de; - _cleanup_close_ int fd = -1, device = -1; - char contents[6]; - ssize_t n; - - errno = 0; - de = readdir(d); - if (!de && errno > 0) - return -errno; - - if (!de) - break; - - if (hidden_file(de->d_name)) - continue; - - device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (device < 0) { - if (errno == ENOENT || errno == ENOTDIR) - continue; - - return -errno; - } - - fd = openat(device, "type", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - if (errno == ENOENT) - continue; - - return -errno; - } - - n = read(fd, contents, sizeof(contents)); - if (n < 0) - return -errno; - - if (n != 6 || memcmp(contents, "Mains\n", 6)) - continue; - - safe_close(fd); - fd = openat(device, "online", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - if (errno == ENOENT) - continue; - - return -errno; - } - - n = read(fd, contents, sizeof(contents)); - if (n < 0) - return -errno; - - if (n != 2 || contents[1] != '\n') - return -EIO; - - if (contents[0] == '1') { - found_online = true; - break; - } else if (contents[0] == '0') - found_offline = true; - else - return -EIO; - } - - 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; - pid_t leader; - int r; - - assert(machine); - assert(pid); - - if (!machine_name_is_valid(machine)) - return -EINVAL; - - p = strjoina("/run/systemd/machines/", machine); - r = parse_env_file(p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL); - if (r == -ENOENT) - return -EHOSTDOWN; - if (r < 0) - return r; - if (!s) - return -EIO; - - if (!streq_ptr(class, "container")) - return -EIO; - - r = parse_pid(s, &leader); - if (r < 0) - return r; - if (leader <= 1) - return -EIO; - - *pid = leader; - return 0; -} - -int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd) { - _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, netnsfd = -1, usernsfd = -1; - int rfd = -1; - - assert(pid >= 0); - - if (mntns_fd) { - const char *mntns; - - mntns = procfs_file_alloca(pid, "ns/mnt"); - mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (mntnsfd < 0) - return -errno; - } - - if (pidns_fd) { - const char *pidns; - - pidns = procfs_file_alloca(pid, "ns/pid"); - pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (pidnsfd < 0) - return -errno; - } - - if (netns_fd) { - const char *netns; - - netns = procfs_file_alloca(pid, "ns/net"); - netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (netnsfd < 0) - return -errno; - } - - if (userns_fd) { - const char *userns; - - userns = procfs_file_alloca(pid, "ns/user"); - usernsfd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (usernsfd < 0 && errno != ENOENT) - return -errno; - } - - if (root_fd) { - const char *root; - - root = procfs_file_alloca(pid, "root"); - rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); - if (rfd < 0) - return -errno; - } - - if (pidns_fd) - *pidns_fd = pidnsfd; - - if (mntns_fd) - *mntns_fd = mntnsfd; - - if (netns_fd) - *netns_fd = netnsfd; - - if (userns_fd) - *userns_fd = usernsfd; - - if (root_fd) - *root_fd = rfd; - - pidnsfd = mntnsfd = netnsfd = usernsfd = -1; - - return 0; -} - -int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) { - if (userns_fd >= 0) { - /* Can't setns to your own userns, since then you could - * escalate from non-root to root in your own namespace, so - * check if namespaces equal before attempting to enter. */ - _cleanup_free_ char *userns_fd_path = NULL; - int r; - if (asprintf(&userns_fd_path, "/proc/self/fd/%d", userns_fd) < 0) - return -ENOMEM; - - r = files_same(userns_fd_path, "/proc/self/ns/user"); - if (r < 0) - return r; - if (r) - userns_fd = -1; - } - - if (pidns_fd >= 0) - if (setns(pidns_fd, CLONE_NEWPID) < 0) - return -errno; - - if (mntns_fd >= 0) - if (setns(mntns_fd, CLONE_NEWNS) < 0) - return -errno; - - if (netns_fd >= 0) - if (setns(netns_fd, CLONE_NEWNET) < 0) - return -errno; - - if (userns_fd >= 0) - if (setns(userns_fd, CLONE_NEWUSER) < 0) - return -errno; - - if (root_fd >= 0) { - if (fchdir(root_fd) < 0) - return -errno; - - if (chroot(".") < 0) - return -errno; - } - - return reset_uid_gid(); -} - -uint64_t physical_memory(void) { - long mem; - - /* We return this as uint64_t in case we are running as 32bit - * process on a 64bit kernel with huge amounts of memory */ - - mem = sysconf(_SC_PHYS_PAGES); - assert(mem > 0); - - return (uint64_t) mem * (uint64_t) page_size(); -} - -int update_reboot_param_file(const char *param) { - int r = 0; - - if (param) { - r = write_string_file(REBOOT_PARAM_FILE, param, WRITE_STRING_FILE_CREATE); - if (r < 0) - return log_error_errno(r, "Failed to write reboot param to "REBOOT_PARAM_FILE": %m"); - } else - (void) unlink(REBOOT_PARAM_FILE); - - return 0; -} - -int version(void) { - puts(PACKAGE_STRING "\n" - SYSTEMD_FEATURES); - return 0; -} diff --git a/src/basic/util.h b/src/basic/util.h deleted file mode 100644 index 6f42c85a33..0000000000 --- a/src/basic/util.h +++ /dev/null @@ -1,178 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "formats-util.h" -#include "macro.h" -#include "missing.h" -#include "time-util.h" - -size_t page_size(void) _pure_; -#define PAGE_ALIGN(l) ALIGN_TO((l), page_size()) - -static inline const char* yes_no(bool b) { - return b ? "yes" : "no"; -} - -static inline const char* true_false(bool b) { - return b ? "true" : "false"; -} - -static inline const char* one_zero(bool b) { - return b ? "1" : "0"; -} - -void execute_directories(const char* const* directories, usec_t timeout, char *argv[]); - -bool plymouth_running(void); - -bool display_is_local(const char *display) _pure_; -int socket_from_display(const char *display, char **path); - -int block_get_whole_disk(dev_t d, dev_t *ret); - -#define NULSTR_FOREACH(i, l) \ - for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1) - -#define NULSTR_FOREACH_PAIR(i, j, l) \ - for ((i) = (l), (j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i)) - -extern int saved_argc; -extern char **saved_argv; - -bool kexec_loaded(void); - -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 *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, - int (*compar) (const void *, const void *, void *), - void *arg); - -/** - * Normal qsort requires base to be nonnull. Here were require - * that only if nmemb > 0. - */ -static inline void qsort_safe(void *base, size_t nmemb, size_t size, comparison_fn_t compar) { - if (nmemb <= 1) - return; - - assert(base); - qsort(base, nmemb, size, compar); -} - -int on_ac_power(void); - -#define memzero(x,l) (memset((x), 0, (l))) -#define zero(x) (memzero(&(x), sizeof(x))) - -static inline void *mempset(void *s, int c, size_t n) { - memset(s, c, n); - return (uint8_t*)s + n; -} - -static inline void _reset_errno_(int *saved_errno) { - errno = *saved_errno; -} - -#define PROTECT_ERRNO _cleanup_(_reset_errno_) __attribute__((unused)) int _saved_errno_ = errno - -static inline int negative_errno(void) { - /* This helper should be used to shut up gcc if you know 'errno' is - * negative. Instead of "return -errno;", use "return negative_errno();" - * It will suppress bogus gcc warnings in case it assumes 'errno' might - * be 0 and thus the caller's error-handling might not be triggered. */ - assert_return(errno > 0, -EINVAL); - return -errno; -} - -static inline unsigned u64log2(uint64_t n) { -#if __SIZEOF_LONG_LONG__ == 8 - return (n > 1) ? (unsigned) __builtin_clzll(n) ^ 63U : 0; -#else -#error "Wut?" -#endif -} - -static inline unsigned u32ctz(uint32_t n) { -#if __SIZEOF_INT__ == 4 - return __builtin_ctz(n); -#else -#error "Wut?" -#endif -} - -static inline unsigned log2i(int x) { - assert(x > 0); - - return __SIZEOF_INT__ * 8 - __builtin_clz(x) - 1; -} - -static inline unsigned log2u(unsigned x) { - assert(x > 0); - - return sizeof(unsigned) * 8 - __builtin_clz(x) - 1; -} - -static inline unsigned log2u_round_up(unsigned x) { - assert(x > 0); - - if (x == 1) - return 0; - - 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); - -int update_reboot_param_file(const char *param); - -int version(void); diff --git a/src/basic/verbs.c b/src/basic/verbs.c deleted file mode 100644 index d9cdb38d65..0000000000 --- a/src/basic/verbs.c +++ /dev/null @@ -1,101 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include - -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "verbs.h" -#include "virt.h" - -int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { - const Verb *verb; - const char *name; - unsigned i; - int left; - - assert(verbs); - assert(verbs[0].dispatch); - assert(argc >= 0); - assert(argv); - assert(argc >= optind); - - left = argc - optind; - name = argv[optind]; - - for (i = 0;; i++) { - bool found; - - /* At the end of the list? */ - if (!verbs[i].dispatch) { - if (name) - log_error("Unknown operation %s.", name); - else - log_error("Requires operation parameter."); - return -EINVAL; - } - - if (name) - found = streq(name, verbs[i].verb); - else - found = !!(verbs[i].flags & VERB_DEFAULT); - - if (found) { - verb = &verbs[i]; - break; - } - } - - assert(verb); - - if (!name) - left = 1; - - if (verb->min_args != VERB_ANY && - (unsigned) left < verb->min_args) { - log_error("Too few arguments."); - return -EINVAL; - } - - if (verb->max_args != VERB_ANY && - (unsigned) left > verb->max_args) { - log_error("Too many arguments."); - return -EINVAL; - } - - if ((verb->flags & VERB_NOCHROOT) && running_in_chroot() > 0) { - log_info("Running in chroot, ignoring request."); - return 0; - } - - if (name) - return verb->dispatch(left, argv + optind, userdata); - else { - char* fake[2] = { - (char*) verb->verb, - NULL - }; - - return verb->dispatch(1, fake, userdata); - } -} diff --git a/src/basic/verbs.h b/src/basic/verbs.h deleted file mode 100644 index 7b5e18510f..0000000000 --- a/src/basic/verbs.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#define VERB_ANY ((unsigned) -1) -#define VERB_DEFAULT 1U -#define VERB_NOCHROOT 2U - -typedef struct { - const char *verb; - unsigned min_args, max_args; - unsigned flags; - int (* const dispatch)(int argc, char *argv[], void *userdata); -} Verb; - -int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata); diff --git a/src/basic/virt.c b/src/basic/virt.c deleted file mode 100644 index 19b6318e3d..0000000000 --- a/src/basic/virt.c +++ /dev/null @@ -1,462 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "macro.h" -#include "process-util.h" -#include "stat-util.h" -#include "string-table.h" -#include "string-util.h" -#include "virt.h" - -static int detect_vm_cpuid(void) { - - /* CPUID is an x86 specific interface. */ -#if defined(__i386__) || defined(__x86_64__) - - static const struct { - const char *cpuid; - int id; - } cpuid_vendor_table[] = { - { "XenVMMXenVMM", VIRTUALIZATION_XEN }, - { "KVMKVMKVM", VIRTUALIZATION_KVM }, - /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ - { "VMwareVMware", VIRTUALIZATION_VMWARE }, - /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */ - { "Microsoft Hv", VIRTUALIZATION_MICROSOFT }, - }; - - uint32_t eax, ecx; - bool hypervisor; - - /* http://lwn.net/Articles/301888/ */ - -#if defined (__i386__) -#define REG_a "eax" -#define REG_b "ebx" -#elif defined (__amd64__) -#define REG_a "rax" -#define REG_b "rbx" -#endif - - /* First detect whether there is a hypervisor */ - eax = 1; - __asm__ __volatile__ ( - /* ebx/rbx is being used for PIC! */ - " push %%"REG_b" \n\t" - " cpuid \n\t" - " pop %%"REG_b" \n\t" - - : "=a" (eax), "=c" (ecx) - : "0" (eax) - ); - - hypervisor = !!(ecx & 0x80000000U); - - if (hypervisor) { - union { - uint32_t sig32[3]; - char text[13]; - } sig = {}; - unsigned j; - - /* There is a hypervisor, see what it is */ - eax = 0x40000000U; - __asm__ __volatile__ ( - /* ebx/rbx is being used for PIC! */ - " push %%"REG_b" \n\t" - " cpuid \n\t" - " mov %%ebx, %1 \n\t" - " pop %%"REG_b" \n\t" - - : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2]) - : "0" (eax) - ); - - for (j = 0; j < ELEMENTSOF(cpuid_vendor_table); j ++) - if (streq(sig.text, cpuid_vendor_table[j].cpuid)) - return cpuid_vendor_table[j].id; - - return VIRTUALIZATION_VM_OTHER; - } -#endif - - return VIRTUALIZATION_NONE; -} - -static int detect_vm_device_tree(void) { -#if defined(__arm__) || defined(__aarch64__) || defined(__powerpc__) || defined(__powerpc64__) - _cleanup_free_ char *hvtype = NULL; - int r; - - r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype); - if (r == -ENOENT) { - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *dent; - - dir = opendir("/proc/device-tree"); - if (!dir) { - if (errno == ENOENT) - return VIRTUALIZATION_NONE; - return -errno; - } - - FOREACH_DIRENT(dent, dir, return -errno) - if (strstr(dent->d_name, "fw-cfg")) - return VIRTUALIZATION_QEMU; - - return VIRTUALIZATION_NONE; - } else if (r < 0) - return r; - - if (streq(hvtype, "linux,kvm")) - return VIRTUALIZATION_KVM; - else if (strstr(hvtype, "xen")) - return VIRTUALIZATION_XEN; - else - return VIRTUALIZATION_VM_OTHER; -#else - return VIRTUALIZATION_NONE; -#endif -} - -static int detect_vm_dmi(void) { -#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) - - static const char *const dmi_vendors[] = { - "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */ - "/sys/class/dmi/id/sys_vendor", - "/sys/class/dmi/id/board_vendor", - "/sys/class/dmi/id/bios_vendor" - }; - - static const struct { - const char *vendor; - int id; - } dmi_vendor_table[] = { - { "KVM", VIRTUALIZATION_KVM }, - { "QEMU", VIRTUALIZATION_QEMU }, - /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ - { "VMware", VIRTUALIZATION_VMWARE }, - { "VMW", VIRTUALIZATION_VMWARE }, - { "innotek GmbH", VIRTUALIZATION_ORACLE }, - { "Xen", VIRTUALIZATION_XEN }, - { "Bochs", VIRTUALIZATION_BOCHS }, - { "Parallels", VIRTUALIZATION_PARALLELS }, - }; - unsigned i; - int r; - - for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) { - _cleanup_free_ char *s = NULL; - unsigned j; - - r = read_one_line_file(dmi_vendors[i], &s); - if (r < 0) { - if (r == -ENOENT) - continue; - - return r; - } - - for (j = 0; j < ELEMENTSOF(dmi_vendor_table); j++) - if (startswith(s, dmi_vendor_table[j].vendor)) - return dmi_vendor_table[j].id; - } -#endif - - return VIRTUALIZATION_NONE; -} - -static int detect_vm_xen(void) { - _cleanup_free_ char *domcap = NULL; - char *cap, *i; - int r; - - r = read_one_line_file("/proc/xen/capabilities", &domcap); - if (r == -ENOENT) - return VIRTUALIZATION_NONE; - - i = domcap; - while ((cap = strsep(&i, ","))) - if (streq(cap, "control_d")) - break; - - return cap ? VIRTUALIZATION_NONE : VIRTUALIZATION_XEN; -} - -static int detect_vm_hypervisor(void) { - _cleanup_free_ char *hvtype = NULL; - int r; - - r = read_one_line_file("/sys/hypervisor/type", &hvtype); - if (r == -ENOENT) - return VIRTUALIZATION_NONE; - if (r < 0) - return r; - - if (streq(hvtype, "xen")) - return VIRTUALIZATION_XEN; - else - return VIRTUALIZATION_VM_OTHER; -} - -static int detect_vm_uml(void) { - _cleanup_free_ char *cpuinfo_contents = NULL; - int r; - - /* Detect User-Mode Linux by reading /proc/cpuinfo */ - r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL); - if (r < 0) - return r; - if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) - return VIRTUALIZATION_UML; - - return VIRTUALIZATION_NONE; -} - -static int detect_vm_zvm(void) { - -#if defined(__s390__) - _cleanup_free_ char *t = NULL; - int r; - - r = get_proc_field("/proc/sysinfo", "VM00 Control Program", WHITESPACE, &t); - if (r == -ENOENT) - return VIRTUALIZATION_NONE; - if (r < 0) - return r; - - if (streq(t, "z/VM")) - return VIRTUALIZATION_ZVM; - else - return VIRTUALIZATION_KVM; -#else - return VIRTUALIZATION_NONE; -#endif -} - -/* Returns a short identifier for the various VM implementations */ -int detect_vm(void) { - static thread_local int cached_found = _VIRTUALIZATION_INVALID; - int r; - - if (cached_found >= 0) - return cached_found; - - /* We have to use the correct order here: - * Some virtualization technologies do use KVM hypervisor but are - * expected to be detected as something else. So detect DMI first. - * - * An example is Virtualbox since version 5.0, which uses KVM backend. - * Detection via DMI works corretly, the CPU ID would find KVM - * only. */ - r = detect_vm_dmi(); - if (r < 0) - return r; - if (r != VIRTUALIZATION_NONE) - goto finish; - - r = detect_vm_cpuid(); - if (r < 0) - return r; - if (r != VIRTUALIZATION_NONE) - goto finish; - - /* x86 xen will most likely be detected by cpuid. If not (most likely - * because we're not an x86 guest), then we should try the xen capabilities - * file next. If that's not found, then we check for the high-level - * hypervisor sysfs file: - * - * https://bugs.freedesktop.org/show_bug.cgi?id=77271 */ - - r = detect_vm_xen(); - if (r < 0) - return r; - if (r != VIRTUALIZATION_NONE) - goto finish; - - r = detect_vm_hypervisor(); - if (r < 0) - return r; - if (r != VIRTUALIZATION_NONE) - goto finish; - - r = detect_vm_device_tree(); - if (r < 0) - return r; - if (r != VIRTUALIZATION_NONE) - goto finish; - - r = detect_vm_uml(); - if (r < 0) - return r; - if (r != VIRTUALIZATION_NONE) - goto finish; - - r = detect_vm_zvm(); - if (r < 0) - return r; - -finish: - cached_found = r; - return r; -} - -int detect_container(void) { - - static const struct { - const char *value; - int id; - } value_table[] = { - { "lxc", VIRTUALIZATION_LXC }, - { "lxc-libvirt", VIRTUALIZATION_LXC_LIBVIRT }, - { "systemd-nspawn", VIRTUALIZATION_SYSTEMD_NSPAWN }, - { "docker", VIRTUALIZATION_DOCKER }, - { "rkt", VIRTUALIZATION_RKT }, - }; - - static thread_local int cached_found = _VIRTUALIZATION_INVALID; - _cleanup_free_ char *m = NULL; - const char *e = NULL; - unsigned j; - int r; - - if (cached_found >= 0) - return cached_found; - - /* /proc/vz exists in container and outside of the container, - * /proc/bc only outside of the container. */ - if (access("/proc/vz", F_OK) >= 0 && - access("/proc/bc", F_OK) < 0) { - r = VIRTUALIZATION_OPENVZ; - goto finish; - } - - if (getpid() == 1) { - /* If we are PID 1 we can just check our own - * environment variable */ - - e = getenv("container"); - if (isempty(e)) { - r = VIRTUALIZATION_NONE; - goto finish; - } - } else { - - /* Otherwise, PID 1 dropped this information into a - * file in /run. This is better than accessing - * /proc/1/environ, since we don't need CAP_SYS_PTRACE - * for that. */ - - r = read_one_line_file("/run/systemd/container", &m); - if (r == -ENOENT) { - - /* Fallback for cases where PID 1 was not - * systemd (for example, cases where - * init=/bin/sh is used. */ - - r = getenv_for_pid(1, "container", &m); - if (r <= 0) { - - /* If that didn't work, give up, - * assume no container manager. - * - * Note: This means we still cannot - * detect containers if init=/bin/sh - * is passed but privileges dropped, - * as /proc/1/environ is only readable - * with privileges. */ - - r = VIRTUALIZATION_NONE; - goto finish; - } - } - if (r < 0) - return r; - - e = m; - } - - for (j = 0; j < ELEMENTSOF(value_table); j++) - if (streq(e, value_table[j].value)) { - r = value_table[j].id; - goto finish; - } - - r = VIRTUALIZATION_CONTAINER_OTHER; - -finish: - cached_found = r; - return r; -} - -int detect_virtualization(void) { - int r; - - r = detect_container(); - if (r != 0) - return r; - - return detect_vm(); -} - -int running_in_chroot(void) { - int ret; - - ret = files_same("/proc/1/root", "/"); - if (ret < 0) - return ret; - - return ret == 0; -} - -static const char *const virtualization_table[_VIRTUALIZATION_MAX] = { - [VIRTUALIZATION_NONE] = "none", - [VIRTUALIZATION_KVM] = "kvm", - [VIRTUALIZATION_QEMU] = "qemu", - [VIRTUALIZATION_BOCHS] = "bochs", - [VIRTUALIZATION_XEN] = "xen", - [VIRTUALIZATION_UML] = "uml", - [VIRTUALIZATION_VMWARE] = "vmware", - [VIRTUALIZATION_ORACLE] = "oracle", - [VIRTUALIZATION_MICROSOFT] = "microsoft", - [VIRTUALIZATION_ZVM] = "zvm", - [VIRTUALIZATION_PARALLELS] = "parallels", - [VIRTUALIZATION_VM_OTHER] = "vm-other", - - [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn", - [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt", - [VIRTUALIZATION_LXC] = "lxc", - [VIRTUALIZATION_OPENVZ] = "openvz", - [VIRTUALIZATION_DOCKER] = "docker", - [VIRTUALIZATION_RKT] = "rkt", - [VIRTUALIZATION_CONTAINER_OTHER] = "container-other", -}; - -DEFINE_STRING_TABLE_LOOKUP(virtualization, int); diff --git a/src/basic/virt.h b/src/basic/virt.h deleted file mode 100644 index a538f07f6b..0000000000 --- a/src/basic/virt.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include - -#include "macro.h" - -enum { - VIRTUALIZATION_NONE = 0, - - VIRTUALIZATION_VM_FIRST, - VIRTUALIZATION_KVM = VIRTUALIZATION_VM_FIRST, - VIRTUALIZATION_QEMU, - VIRTUALIZATION_BOCHS, - VIRTUALIZATION_XEN, - VIRTUALIZATION_UML, - VIRTUALIZATION_VMWARE, - VIRTUALIZATION_ORACLE, - VIRTUALIZATION_MICROSOFT, - VIRTUALIZATION_ZVM, - VIRTUALIZATION_PARALLELS, - VIRTUALIZATION_VM_OTHER, - VIRTUALIZATION_VM_LAST = VIRTUALIZATION_VM_OTHER, - - VIRTUALIZATION_CONTAINER_FIRST, - VIRTUALIZATION_SYSTEMD_NSPAWN = VIRTUALIZATION_CONTAINER_FIRST, - VIRTUALIZATION_LXC_LIBVIRT, - VIRTUALIZATION_LXC, - VIRTUALIZATION_OPENVZ, - VIRTUALIZATION_DOCKER, - VIRTUALIZATION_RKT, - VIRTUALIZATION_CONTAINER_OTHER, - VIRTUALIZATION_CONTAINER_LAST = VIRTUALIZATION_CONTAINER_OTHER, - - _VIRTUALIZATION_MAX, - _VIRTUALIZATION_INVALID = -1 -}; - -static inline bool VIRTUALIZATION_IS_VM(int x) { - return x >= VIRTUALIZATION_VM_FIRST && x <= VIRTUALIZATION_VM_LAST; -} - -static inline bool VIRTUALIZATION_IS_CONTAINER(int x) { - return x >= VIRTUALIZATION_CONTAINER_FIRST && x <= VIRTUALIZATION_CONTAINER_LAST; -} - -int detect_vm(void); -int detect_container(void); -int detect_virtualization(void); - -int running_in_chroot(void); - -const char *virtualization_to_string(int v) _const_; -int virtualization_from_string(const char *s) _pure_; diff --git a/src/basic/web-util.c b/src/basic/web-util.c deleted file mode 100644 index 595688ed93..0000000000 --- a/src/basic/web-util.c +++ /dev/null @@ -1,76 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "string-util.h" -#include "utf8.h" -#include "web-util.h" - -bool http_etag_is_valid(const char *etag) { - if (isempty(etag)) - return false; - - if (!endswith(etag, "\"")) - return false; - - if (!startswith(etag, "\"") && !startswith(etag, "W/\"")) - return false; - - return true; -} - -bool http_url_is_valid(const char *url) { - const char *p; - - if (isempty(url)) - return false; - - p = startswith(url, "http://"); - if (!p) - p = startswith(url, "https://"); - if (!p) - return false; - - if (isempty(p)) - return false; - - return ascii_is_valid(p); -} - -bool documentation_url_is_valid(const char *url) { - const char *p; - - if (isempty(url)) - return false; - - if (http_url_is_valid(url)) - return true; - - p = startswith(url, "file:/"); - if (!p) - p = startswith(url, "info:"); - if (!p) - p = startswith(url, "man:"); - - if (isempty(p)) - return false; - - return ascii_is_valid(p); -} diff --git a/src/basic/web-util.h b/src/basic/web-util.h deleted file mode 100644 index e6bb6b53f5..0000000000 --- a/src/basic/web-util.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "macro.h" - -bool http_url_is_valid(const char *url) _pure_; - -bool documentation_url_is_valid(const char *url) _pure_; - -bool http_etag_is_valid(const char *etag); diff --git a/src/basic/xattr-util.c b/src/basic/xattr-util.c deleted file mode 100644 index 8d7f14f382..0000000000 --- a/src/basic/xattr-util.c +++ /dev/null @@ -1,200 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "macro.h" -#include "sparse-endian.h" -#include "stdio-util.h" -#include "time-util.h" -#include "xattr-util.h" - -int getxattr_malloc(const char *path, const char *name, char **value, bool allow_symlink) { - char *v; - size_t l; - ssize_t n; - - assert(path); - assert(name); - assert(value); - - for (l = 100; ; l = (size_t) n + 1) { - v = new0(char, l); - if (!v) - return -ENOMEM; - - if (allow_symlink) - n = lgetxattr(path, name, v, l); - else - n = getxattr(path, name, v, l); - - if (n >= 0 && (size_t) n < l) { - *value = v; - return n; - } - - free(v); - - if (n < 0 && errno != ERANGE) - return -errno; - - if (allow_symlink) - n = lgetxattr(path, name, NULL, 0); - else - n = getxattr(path, name, NULL, 0); - if (n < 0) - return -errno; - } -} - -int fgetxattr_malloc(int fd, const char *name, char **value) { - char *v; - size_t l; - ssize_t n; - - assert(fd >= 0); - assert(name); - assert(value); - - for (l = 100; ; l = (size_t) n + 1) { - v = new0(char, l); - if (!v) - return -ENOMEM; - - n = fgetxattr(fd, name, v, l); - - if (n >= 0 && (size_t) n < l) { - *value = v; - return n; - } - - free(v); - - if (n < 0 && errno != ERANGE) - return -errno; - - n = fgetxattr(fd, name, NULL, 0); - if (n < 0) - return -errno; - } -} - -ssize_t fgetxattrat_fake(int dirfd, const char *filename, const char *attribute, void *value, size_t size, int flags) { - char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; - _cleanup_close_ int fd = -1; - ssize_t l; - - /* The kernel doesn't have a fgetxattrat() command, hence let's emulate one */ - - fd = openat(dirfd, filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH|(flags & AT_SYMLINK_NOFOLLOW ? O_NOFOLLOW : 0)); - if (fd < 0) - return -errno; - - xsprintf(fn, "/proc/self/fd/%i", fd); - - l = getxattr(fn, attribute, value, size); - if (l < 0) - return -errno; - - return l; -} - -static int parse_crtime(le64_t le, usec_t *usec) { - uint64_t u; - - assert(usec); - - u = le64toh(le); - if (u == 0 || u == (uint64_t) -1) - return -EIO; - - *usec = (usec_t) u; - return 0; -} - -int fd_getcrtime(int fd, usec_t *usec) { - le64_t le; - ssize_t n; - - assert(fd >= 0); - assert(usec); - - /* Until Linux gets a real concept of birthtime/creation time, - * let's fake one with xattrs */ - - n = fgetxattr(fd, "user.crtime_usec", &le, sizeof(le)); - if (n < 0) - return -errno; - if (n != sizeof(le)) - return -EIO; - - return parse_crtime(le, usec); -} - -int fd_getcrtime_at(int dirfd, const char *name, usec_t *usec, int flags) { - le64_t le; - ssize_t n; - - n = fgetxattrat_fake(dirfd, name, "user.crtime_usec", &le, sizeof(le), flags); - if (n < 0) - return -errno; - if (n != sizeof(le)) - return -EIO; - - return parse_crtime(le, usec); -} - -int path_getcrtime(const char *p, usec_t *usec) { - le64_t le; - ssize_t n; - - assert(p); - assert(usec); - - n = getxattr(p, "user.crtime_usec", &le, sizeof(le)); - if (n < 0) - return -errno; - if (n != sizeof(le)) - return -EIO; - - return parse_crtime(le, usec); -} - -int fd_setcrtime(int fd, usec_t usec) { - le64_t le; - - assert(fd >= 0); - - if (usec <= 0) - usec = now(CLOCK_REALTIME); - - le = htole64((uint64_t) usec); - if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0) < 0) - return -errno; - - return 0; -} diff --git a/src/basic/xattr-util.h b/src/basic/xattr-util.h deleted file mode 100644 index 6fa097bf7e..0000000000 --- a/src/basic/xattr-util.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#include "time-util.h" - -int getxattr_malloc(const char *path, const char *name, char **value, bool allow_symlink); -int fgetxattr_malloc(int fd, const char *name, char **value); - -ssize_t fgetxattrat_fake(int dirfd, const char *filename, const char *attribute, void *value, size_t size, int flags); - -int fd_setcrtime(int fd, usec_t usec); - -int fd_getcrtime(int fd, usec_t *usec); -int path_getcrtime(const char *p, usec_t *usec); -int fd_getcrtime_at(int dirfd, const char *name, usec_t *usec, int flags); diff --git a/src/basic/xml.c b/src/basic/xml.c deleted file mode 100644 index 1dbeac7324..0000000000 --- a/src/basic/xml.c +++ /dev/null @@ -1,255 +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 . -***/ - -#include -#include -#include - -#include "macro.h" -#include "string-util.h" -#include "xml.h" - -enum { - STATE_NULL, - STATE_TEXT, - STATE_TAG, - STATE_ATTRIBUTE, -}; - -static void inc_lines(unsigned *line, const char *s, size_t n) { - const char *p = s; - - if (!line) - return; - - for (;;) { - const char *f; - - f = memchr(p, '\n', n); - if (!f) - return; - - n -= (f - p) + 1; - p = f + 1; - (*line)++; - } -} - -/* We don't actually do real XML here. We only read a simplistic - * subset, that is a bit less strict that XML and lacks all the more - * complex features, like entities, or namespaces. However, we do - * support some HTML5-like simplifications */ - -int xml_tokenize(const char **p, char **name, void **state, unsigned *line) { - const char *c, *e, *b; - char *ret; - int t; - - assert(p); - assert(*p); - assert(name); - assert(state); - - t = PTR_TO_INT(*state); - c = *p; - - if (t == STATE_NULL) { - if (line) - *line = 1; - t = STATE_TEXT; - } - - for (;;) { - if (*c == 0) - return XML_END; - - switch (t) { - - case STATE_TEXT: { - int x; - - e = strchrnul(c, '<'); - if (e > c) { - /* More text... */ - ret = strndup(c, e - c); - if (!ret) - return -ENOMEM; - - inc_lines(line, c, e - c); - - *name = ret; - *p = e; - *state = INT_TO_PTR(STATE_TEXT); - - return XML_TEXT; - } - - assert(*e == '<'); - b = c + 1; - - if (startswith(b, "!--")) { - /* A comment */ - e = strstr(b + 3, "-->"); - if (!e) - return -EINVAL; - - inc_lines(line, b, e + 3 - b); - - c = e + 3; - continue; - } - - if (*b == '?') { - /* Processing instruction */ - - e = strstr(b + 1, "?>"); - if (!e) - return -EINVAL; - - inc_lines(line, b, e + 2 - b); - - c = e + 2; - continue; - } - - if (*b == '!') { - /* DTD */ - - e = strchr(b + 1, '>'); - if (!e) - return -EINVAL; - - inc_lines(line, b, e + 1 - b); - - c = e + 1; - continue; - } - - if (*b == '/') { - /* A closing tag */ - x = XML_TAG_CLOSE; - b++; - } else - x = XML_TAG_OPEN; - - e = strpbrk(b, WHITESPACE "/>"); - if (!e) - return -EINVAL; - - ret = strndup(b, e - b); - if (!ret) - return -ENOMEM; - - *name = ret; - *p = e; - *state = INT_TO_PTR(STATE_TAG); - - return x; - } - - case STATE_TAG: - - b = c + strspn(c, WHITESPACE); - if (*b == 0) - return -EINVAL; - - inc_lines(line, c, b - c); - - e = b + strcspn(b, WHITESPACE "=/>"); - if (e > b) { - /* An attribute */ - - ret = strndup(b, e - b); - if (!ret) - return -ENOMEM; - - *name = ret; - *p = e; - *state = INT_TO_PTR(STATE_ATTRIBUTE); - - return XML_ATTRIBUTE_NAME; - } - - if (startswith(b, "/>")) { - /* An empty tag */ - - *name = NULL; /* For empty tags we return a NULL name, the caller must be prepared for that */ - *p = b + 2; - *state = INT_TO_PTR(STATE_TEXT); - - return XML_TAG_CLOSE_EMPTY; - } - - if (*b != '>') - return -EINVAL; - - c = b + 1; - t = STATE_TEXT; - continue; - - case STATE_ATTRIBUTE: - - if (*c == '=') { - c++; - - if (*c == '\'' || *c == '\"') { - /* Tag with a quoted value */ - - e = strchr(c+1, *c); - if (!e) - return -EINVAL; - - inc_lines(line, c, e - c); - - ret = strndup(c+1, e - c - 1); - if (!ret) - return -ENOMEM; - - *name = ret; - *p = e + 1; - *state = INT_TO_PTR(STATE_TAG); - - return XML_ATTRIBUTE_VALUE; - - } - - /* Tag with a value without quotes */ - - b = strpbrk(c, WHITESPACE ">"); - if (!b) - b = c; - - ret = strndup(c, b - c); - if (!ret) - return -ENOMEM; - - *name = ret; - *p = b; - *state = INT_TO_PTR(STATE_TAG); - return XML_ATTRIBUTE_VALUE; - } - - t = STATE_TAG; - continue; - } - - } - - assert_not_reached("Bad state"); -} diff --git a/src/basic/xml.h b/src/basic/xml.h deleted file mode 100644 index 41cb69f0dc..0000000000 --- a/src/basic/xml.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -enum { - XML_END, - XML_TEXT, - XML_TAG_OPEN, - XML_TAG_CLOSE, - XML_TAG_CLOSE_EMPTY, - XML_ATTRIBUTE_NAME, - XML_ATTRIBUTE_VALUE, -}; - -int xml_tokenize(const char **p, char **name, void **state, unsigned *line); diff --git a/src/binfmt/Makefile b/src/binfmt/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/binfmt/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/binfmt/binfmt.c b/src/binfmt/binfmt.c deleted file mode 100644 index eeef04fb1c..0000000000 --- a/src/binfmt/binfmt.c +++ /dev/null @@ -1,203 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "conf-files.h" -#include "def.h" -#include "fd-util.h" -#include "fileio.h" -#include "log.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -static const char conf_file_dirs[] = CONF_PATHS_NULSTR("binfmt.d"); - -static int delete_rule(const char *rule) { - _cleanup_free_ char *x = NULL, *fn = NULL; - char *e; - - assert(rule[0]); - - x = strdup(rule); - if (!x) - return log_oom(); - - e = strchrnul(x+1, x[0]); - *e = 0; - - fn = strappend("/proc/sys/fs/binfmt_misc/", x+1); - if (!fn) - return log_oom(); - - return write_string_file(fn, "-1", 0); -} - -static int apply_rule(const char *rule) { - int r; - - delete_rule(rule); - - r = write_string_file("/proc/sys/fs/binfmt_misc/register", rule, 0); - if (r < 0) - return log_error_errno(r, "Failed to add binary format: %m"); - - return 0; -} - -static int apply_file(const char *path, bool ignore_enoent) { - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(path); - - r = search_and_fopen_nulstr(path, "re", NULL, conf_file_dirs, &f); - if (r < 0) { - if (ignore_enoent && r == -ENOENT) - return 0; - - return log_error_errno(r, "Failed to open file '%s', ignoring: %m", path); - } - - log_debug("apply: %s", path); - for (;;) { - char l[LINE_MAX], *p; - int k; - - if (!fgets(l, sizeof(l), f)) { - if (feof(f)) - break; - - return log_error_errno(errno, "Failed to read file '%s', ignoring: %m", path); - } - - p = strstrip(l); - if (!*p) - continue; - if (strchr(COMMENTS "\n", *p)) - continue; - - k = apply_rule(p); - if (k < 0 && r == 0) - r = k; - } - - return r; -} - -static void help(void) { - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" - "Registers binary formats.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -int main(int argc, char *argv[]) { - int r, k; - - r = parse_argv(argc, argv); - if (r <= 0) - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - r = 0; - - if (argc > optind) { - int i; - - for (i = optind; i < argc; i++) { - k = apply_file(argv[i], false); - if (k < 0 && r == 0) - r = k; - } - } else { - _cleanup_strv_free_ char **files = NULL; - char **f; - - r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs); - if (r < 0) { - log_error_errno(r, "Failed to enumerate binfmt.d files: %m"); - goto finish; - } - - /* Flush out all rules */ - write_string_file("/proc/sys/fs/binfmt_misc/status", "-1", 0); - - STRV_FOREACH(f, files) { - k = apply_file(*f, true); - if (k < 0 && r == 0) - r = k; - } - } - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/boot/Makefile b/src/boot/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/boot/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/boot/Makefile b/src/boot/Makefile new file mode 100644 index 0000000000..cce11f7208 --- /dev/null +++ b/src/boot/Makefile @@ -0,0 +1,54 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_EFI),) +ifneq ($(HAVE_BLKID),) +bootctl_SOURCES = \ + src/boot/bootctl.c + +bootctl_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DEFI_MACHINE_TYPE_NAME=\"$(EFI_MACHINE_TYPE_NAME)\" \ + -DBOOTLIBDIR=\"$(bootlibdir)\" + +bootctl_CFLAGS = \ + $(AM_CFLAGS) \ + $(BLKID_CFLAGS) + +bootctl_LDADD = \ + libshared.la \ + $(BLKID_LIBS) + +bin_PROGRAMS += \ + bootctl + +dist_bashcompletion_data += \ + shell-completion/bash/bootctl + +dist_zshcompletion_data += \ + shell-completion/zsh/_bootctl +endif + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/boot/efi/Makefile b/src/boot/efi/Makefile new file mode 100644 index 0000000000..9b86554977 --- /dev/null +++ b/src/boot/efi/Makefile @@ -0,0 +1,128 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_boot_headers = \ + src/boot/efi/util.h \ + src/boot/efi/console.h \ + src/boot/efi/graphics.h \ + src/boot/efi/pefile.h \ + src/boot/efi/disk.h + +systemd_boot_sources = \ + src/boot/efi/util.c \ + src/boot/efi/console.c \ + src/boot/efi/graphics.c \ + src/boot/efi/pefile.c \ + src/boot/efi/disk.c \ + src/boot/efi/boot.c + +EXTRA_DIST += $(systemd_boot_sources) $(systemd_boot_headers) + +systemd_boot_objects = $(addprefix $(top_builddir)/,$(systemd_boot_sources:.c=.o)) +systemd_boot_solib = $(top_builddir)/src/boot/efi/systemd_boot.so +systemd_boot = systemd-boot$(EFI_MACHINE_TYPE_NAME).efi + +ifneq ($(ENABLE_EFI),) +ifneq ($(HAVE_GNUEFI),) +bootlib_DATA = $(systemd_boot) + +$(outdir)/%.o: $(top_srcdir)/src/boot/efi/%.c $(addprefix $(top_srcdir)/,$(systemd_boot_headers)) + @$(MKDIR_P) $(top_builddir)/src/boot/efi/ + $(AM_V_CC)$(EFI_CC) $(efi_cppflags) $(efi_cflags) -c $< -o $@ + +$(systemd_boot_solib): $(systemd_boot_objects) + $(AM_V_CCLD)$(LD) $(efi_ldflags) $(systemd_boot_objects) \ + -o $@ -lefi -lgnuefi $(shell $(CC) -print-libgcc-file-name); \ + nm -D -u $@ | grep ' U ' && exit 1 || : + +$(systemd_boot): $(systemd_boot_solib) + $(AM_V_GEN)$(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic \ + -j .dynsym -j .rel -j .rela -j .reloc $(EFI_FORMAT) $< $@ +endif +endif + +CLEANFILES += $(systemd_boot_objects) $(systemd_boot_solib) $(systemd_boot) + +# ------------------------------------------------------------------------------ +stub_headers = \ + src/boot/efi/util.h \ + src/boot/efi/pefile.h \ + src/boot/efi/disk.h \ + src/boot/efi/graphics.h \ + src/boot/efi/splash.h \ + src/boot/efi/linux.h + +stub_sources = \ + src/boot/efi/util.c \ + src/boot/efi/pefile.c \ + src/boot/efi/disk.c \ + src/boot/efi/graphics.c \ + src/boot/efi/splash.c \ + src/boot/efi/linux.c \ + src/boot/efi/stub.c + +EXTRA_DIST += \ + $(stub_sources) \ + $(stub_headers) \ + test/splash.bmp + +stub_objects = $(addprefix $(top_builddir)/,$(stub_sources:.c=.o)) +stub_solib = $(top_builddir)/src/boot/efi/stub.so +stub = linux$(EFI_MACHINE_TYPE_NAME).efi.stub + +ifneq ($(ENABLE_EFI),) +ifneq ($(HAVE_GNUEFI),) +bootlib_DATA += $(stub) + +$(outdir)/%.o: $(top_srcdir)/src/boot/efi/%.c $(addprefix $(top_srcdir)/,$(stub_headers)) + @$(MKDIR_P) $(top_builddir)/src/boot/efi/ + $(AM_V_CC)$(EFI_CC) $(efi_cppflags) $(efi_cflags) -c $< -o $@ + +$(stub_solib): $(stub_objects) + $(AM_V_CCLD)$(LD) $(efi_ldflags) $(stub_objects) \ + -o $@ -lefi -lgnuefi $(shell $(CC) -print-libgcc-file-name); \ + nm -D -u $@ | grep ' U ' && exit 1 || : + +$(stub): $(stub_solib) + $(AM_V_GEN)$(OBJCOPY) -j .text -j .sdata -j .data -j .dynamic \ + -j .dynsym -j .rel -j .rela -j .reloc $(EFI_FORMAT) $< $@ +endif +endif + +CLEANFILES += $(stub_objects) $(stub_solib) $(stub) + + +# ------------------------------------------------------------------------------ +CLEANFILES += test-efi-disk.img + +test-efi-disk.img: $(systemd_boot) $(stub) test/test-efi-create-disk.sh + $(AM_V_GEN)test/test-efi-create-disk.sh + +test-efi: test-efi-disk.img + $(QEMU) -machine accel=kvm -m 1024 -bios $(QEMU_BIOS) -snapshot test-efi-disk.img + +EXTRA_DIST += test/test-efi-create-disk.sh + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/bootchart/Makefile b/src/bootchart/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/bootchart/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/bootchart/bootchart.c b/src/bootchart/bootchart.c deleted file mode 100644 index 77d158f5f9..0000000000 --- a/src/bootchart/bootchart.c +++ /dev/null @@ -1,531 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2009-2013 Intel Corporation - - Authors: - Auke Kok - - 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 . - ***/ - -/*** - - Many thanks to those who contributed ideas and code: - - Ziga Mahkovec - Original bootchart author - - Anders Norgaard - PyBootchartgui - - Michael Meeks - bootchart2 - - Scott James Remnant - Ubuntu C-based logger - - Arjan van der Ven - for the idea to merge bootgraph.pl functionality - - ***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-journal.h" - -#include "alloc-util.h" -#include "bootchart.h" -#include "conf-parser.h" -#include "def.h" -#include "fd-util.h" -#include "fileio.h" -#include "io-util.h" -#include "list.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "store.h" -#include "string-util.h" -#include "strxcpyx.h" -#include "svg.h" -#include "util.h" - -static int exiting = 0; - -#define DEFAULT_SAMPLES_LEN 500 -#define DEFAULT_HZ 25.0 -#define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */ -#define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */ -#define DEFAULT_INIT ROOTLIBEXECDIR "/systemd" -#define DEFAULT_OUTPUT "/run/log" - -/* graph defaults */ -bool arg_entropy = false; -bool arg_initcall = true; -bool arg_relative = false; -bool arg_filter = true; -bool arg_show_cmdline = false; -bool arg_show_cgroup = false; -bool arg_pss = false; -bool arg_percpu = false; -int arg_samples_len = DEFAULT_SAMPLES_LEN; /* we record len+1 (1 start sample) */ -double arg_hz = DEFAULT_HZ; -double arg_scale_x = DEFAULT_SCALE_X; -double arg_scale_y = DEFAULT_SCALE_Y; - -char arg_init_path[PATH_MAX] = DEFAULT_INIT; -char arg_output_path[PATH_MAX] = DEFAULT_OUTPUT; - -static void signal_handler(int sig) { - exiting = 1; -} - -#define BOOTCHART_MAX (16*1024*1024) - -static void parse_conf(void) { - char *init = NULL, *output = NULL; - const ConfigTableItem items[] = { - { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len }, - { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz }, - { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative }, - { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter }, - { "Bootchart", "Output", config_parse_path, 0, &output }, - { "Bootchart", "Init", config_parse_path, 0, &init }, - { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss }, - { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy }, - { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x }, - { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y }, - { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup }, - { "Bootchart", "PerCPU", config_parse_bool, 0, &arg_percpu }, - { NULL, NULL, NULL, 0, NULL } - }; - - config_parse_many(PKGSYSCONFDIR "/bootchart.conf", - CONF_PATHS_NULSTR("systemd/bootchart.conf.d"), - NULL, config_item_table_lookup, items, true, NULL); - - if (init != NULL) - strscpy(arg_init_path, sizeof(arg_init_path), init); - if (output != NULL) - strscpy(arg_output_path, sizeof(arg_output_path), output); -} - -static void help(void) { - printf("Usage: %s [OPTIONS]\n\n" - "Options:\n" - " -r --rel Record time relative to recording\n" - " -f --freq=FREQ Sample frequency [%g]\n" - " -n --samples=N Stop sampling at [%d] samples\n" - " -x --scale-x=N Scale the graph horizontally [%g] \n" - " -y --scale-y=N Scale the graph vertically [%g] \n" - " -p --pss Enable PSS graph (CPU intensive)\n" - " -e --entropy Enable the entropy_avail graph\n" - " -o --output=PATH Path to output files [%s]\n" - " -i --init=PATH Path to init executable [%s]\n" - " -F --no-filter Disable filtering of unimportant or ephemeral processes\n" - " -C --cmdline Display full command lines with arguments\n" - " -c --control-group Display process control group\n" - " --per-cpu Draw each CPU utilization and wait bar also\n" - " -h --help Display this message\n\n" - "See bootchart.conf for more information.\n", - program_invocation_short_name, - DEFAULT_HZ, - DEFAULT_SAMPLES_LEN, - DEFAULT_SCALE_X, - DEFAULT_SCALE_Y, - DEFAULT_OUTPUT, - DEFAULT_INIT); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_PERCPU = 0x100, - }; - - static const struct option options[] = { - {"rel", no_argument, NULL, 'r' }, - {"freq", required_argument, NULL, 'f' }, - {"samples", required_argument, NULL, 'n' }, - {"pss", no_argument, NULL, 'p' }, - {"output", required_argument, NULL, 'o' }, - {"init", required_argument, NULL, 'i' }, - {"no-filter", no_argument, NULL, 'F' }, - {"cmdline", no_argument, NULL, 'C' }, - {"control-group", no_argument, NULL, 'c' }, - {"help", no_argument, NULL, 'h' }, - {"scale-x", required_argument, NULL, 'x' }, - {"scale-y", required_argument, NULL, 'y' }, - {"entropy", no_argument, NULL, 'e' }, - {"per-cpu", no_argument, NULL, ARG_PERCPU}, - {} - }; - int c, r; - - if (getpid() == 1) - opterr = 0; - - while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0) - switch (c) { - - case 'r': - arg_relative = true; - break; - case 'f': - r = safe_atod(optarg, &arg_hz); - if (r < 0) - log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m", - optarg); - break; - case 'F': - arg_filter = false; - break; - case 'C': - arg_show_cmdline = true; - break; - case 'c': - arg_show_cgroup = true; - break; - case 'n': - r = safe_atoi(optarg, &arg_samples_len); - if (r < 0) - log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m", - optarg); - break; - case 'o': - path_kill_slashes(optarg); - strscpy(arg_output_path, sizeof(arg_output_path), optarg); - break; - case 'i': - path_kill_slashes(optarg); - strscpy(arg_init_path, sizeof(arg_init_path), optarg); - break; - case 'p': - arg_pss = true; - break; - case 'x': - r = safe_atod(optarg, &arg_scale_x); - if (r < 0) - log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m", - optarg); - break; - case 'y': - r = safe_atod(optarg, &arg_scale_y); - if (r < 0) - log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m", - optarg); - break; - case 'e': - arg_entropy = true; - break; - case ARG_PERCPU: - arg_percpu = true; - break; - case 'h': - help(); - return 0; - case '?': - if (getpid() != 1) - return -EINVAL; - else - return 0; - default: - assert_not_reached("Unhandled option code."); - } - - if (arg_hz <= 0) { - log_error("Frequency needs to be > 0"); - return -EINVAL; - } - - return 1; -} - -static int do_journal_append(char *file) { - _cleanup_free_ char *bootchart_message = NULL; - _cleanup_free_ char *bootchart_file = NULL; - _cleanup_free_ char *p = NULL; - _cleanup_close_ int fd = -1; - struct iovec iovec[5]; - int r, j = 0; - ssize_t n; - - bootchart_file = strappend("BOOTCHART_FILE=", file); - if (!bootchart_file) - return log_oom(); - - IOVEC_SET_STRING(iovec[j++], bootchart_file); - IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518"); - IOVEC_SET_STRING(iovec[j++], "PRIORITY=7"); - bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL); - if (!bootchart_message) - return log_oom(); - - IOVEC_SET_STRING(iovec[j++], bootchart_message); - - p = malloc(10 + BOOTCHART_MAX); - if (!p) - return log_oom(); - - memcpy(p, "BOOTCHART=", 10); - - fd = open(file, O_RDONLY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open bootchart data \"%s\": %m", file); - - n = loop_read(fd, p + 10, BOOTCHART_MAX, false); - if (n < 0) - return log_error_errno(n, "Failed to read bootchart data: %m"); - - iovec[j].iov_base = p; - iovec[j].iov_len = 10 + n; - j++; - - r = sd_journal_sendv(iovec, j); - if (r < 0) - log_error_errno(r, "Failed to send bootchart: %m"); - - return 0; -} - -int main(int argc, char *argv[]) { - static struct list_sample_data *sampledata; - _cleanup_closedir_ DIR *proc = NULL; - _cleanup_free_ char *build = NULL; - _cleanup_fclose_ FILE *of = NULL; - _cleanup_close_ int sysfd = -1; - struct ps_struct *ps_first; - double graph_start; - double log_start; - double interval; - char output_file[PATH_MAX]; - char datestr[200]; - int pscount = 0; - int n_cpus = 0; - int overrun = 0; - time_t t = 0; - int r, samples; - struct ps_struct *ps; - struct rlimit rlim; - struct list_sample_data *head; - struct sigaction sig = { - .sa_handler = signal_handler, - }; - - parse_conf(); - - r = parse_argv(argc, argv); - if (r < 0) - return EXIT_FAILURE; - - if (r == 0) - return EXIT_SUCCESS; - - /* - * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then - * fork: - * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1 - * - child logs data - */ - if (getpid() == 1) { - if (fork()) - /* parent */ - execl(arg_init_path, arg_init_path, NULL); - } - argv[0][0] = '@'; - - rlim.rlim_cur = 4096; - rlim.rlim_max = 4096; - (void) setrlimit(RLIMIT_NOFILE, &rlim); - - /* start with empty ps LL */ - ps_first = new0(struct ps_struct, 1); - if (!ps_first) { - log_oom(); - return EXIT_FAILURE; - } - - /* handle TERM/INT nicely */ - sigaction(SIGHUP, &sig, NULL); - - interval = (1.0 / arg_hz) * 1000000000.0; - - if (arg_relative) - graph_start = log_start = gettime_ns(); - else { - struct timespec n; - double uptime; - - clock_gettime(clock_boottime_or_monotonic(), &n); - uptime = (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC)); - - log_start = gettime_ns(); - graph_start = log_start - uptime; - } - - if (graph_start < 0.0) { - log_error("Failed to setup graph start time.\n\n" - "The system uptime probably includes time that the system was suspended. " - "Use --rel to bypass this issue."); - return EXIT_FAILURE; - } - - LIST_HEAD_INIT(head); - - /* main program loop */ - for (samples = 0; !exiting && samples < arg_samples_len; samples++) { - int res; - double sample_stop; - double elapsed; - double timeleft; - - sampledata = new0(struct list_sample_data, 1); - if (sampledata == NULL) { - log_oom(); - return EXIT_FAILURE; - } - - sampledata->sampletime = gettime_ns(); - sampledata->counter = samples; - - if (sysfd < 0) - sysfd = open("/sys", O_RDONLY|O_CLOEXEC); - - if (!build) { - if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT) - parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL); - } - - if (proc) - rewinddir(proc); - else - proc = opendir("/proc"); - - /* wait for /proc to become available, discarding samples */ - if (proc) { - r = log_sample(proc, samples, ps_first, &sampledata, &pscount, &n_cpus); - if (r < 0) - return EXIT_FAILURE; - } - - sample_stop = gettime_ns(); - - elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0; - timeleft = interval - elapsed; - - /* - * check if we have not consumed our entire timeslice. If we - * do, don't sleep and take a new sample right away. - * we'll lose all the missed samples and overrun our total - * time - */ - if (timeleft > 0) { - struct timespec req; - - req.tv_sec = (time_t)(timeleft / 1000000000.0); - req.tv_nsec = (long)(timeleft - (req.tv_sec * 1000000000.0)); - - res = nanosleep(&req, NULL); - if (res) { - if (errno == EINTR) - /* caught signal, probably HUP! */ - break; - log_error_errno(errno, "nanosleep() failed: %m"); - return EXIT_FAILURE; - } - } else { - overrun++; - /* calculate how many samples we lost and scrap them */ - arg_samples_len -= (int)(-timeleft / interval); - } - LIST_PREPEND(link, head, sampledata); - } - - /* do some cleanup, close fd's */ - ps = ps_first; - while (ps->next_ps) { - ps = ps->next_ps; - ps->schedstat = safe_close(ps->schedstat); - ps->sched = safe_close(ps->sched); - ps->smaps = safe_fclose(ps->smaps); - } - - if (!of) { - t = time(NULL); - r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t)); - assert_se(r > 0); - - snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr); - of = fopen(output_file, "we"); - } - - if (!of) { - log_error("Error opening output file '%s': %m\n", output_file); - return EXIT_FAILURE; - } - - r = svg_do(of, strna(build), head, ps_first, - samples, pscount, n_cpus, graph_start, - log_start, interval, overrun); - - if (r < 0) { - log_error_errno(r, "Error generating svg file: %m"); - return EXIT_FAILURE; - } - - log_info("systemd-bootchart wrote %s\n", output_file); - - r = do_journal_append(output_file); - if (r < 0) - return EXIT_FAILURE; - - /* nitpic cleanups */ - ps = ps_first->next_ps; - while (ps->next_ps) { - struct ps_struct *old; - - old = ps; - old->sample = ps->first; - ps = ps->next_ps; - while (old->sample->next) { - struct ps_sched_struct *oldsample = old->sample; - - old->sample = old->sample->next; - free(oldsample); - } - free(old->cgroup); - free(old->sample); - free(old); - } - - free(ps->cgroup); - free(ps->sample); - free(ps); - - sampledata = head; - while (sampledata->link_prev) { - struct list_sample_data *old_sampledata = sampledata; - sampledata = sampledata->link_prev; - free(old_sampledata); - } - free(sampledata); - - /* don't complain when overrun once, happens most commonly on 1st sample */ - if (overrun > 1) - log_warning("systemd-bootchart: sample time overrun %i times\n", overrun); - - return 0; -} diff --git a/src/bootchart/bootchart.conf b/src/bootchart/bootchart.conf deleted file mode 100644 index 4f5e50936e..0000000000 --- a/src/bootchart/bootchart.conf +++ /dev/null @@ -1,26 +0,0 @@ -# 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. -# -# Entries in this file show the compile time defaults. -# You can change settings by editing this file. -# Defaults can be restored by simply deleting this file. -# -# See bootchart.conf(5) for details. - -[Bootchart] -#Samples=500 -#Frequency=25 -#Relative=no -#Filter=yes -#Output= -#Init=/path/to/init-binary -#PlotMemoryUsage=no -#PlotEntropyGraph=no -#ScaleX=100 -#ScaleY=20 -#ControlGroup=no -#PerCPU=no diff --git a/src/bootchart/bootchart.h b/src/bootchart/bootchart.h deleted file mode 100644 index 1b445b954b..0000000000 --- a/src/bootchart/bootchart.h +++ /dev/null @@ -1,119 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright (C) 2009-2013 Intel Corporation - - Authors: - Auke Kok - - 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 . -***/ - -#include - -#include "list.h" - -#define MAXCPUS 16 -#define MAXPIDS 65535 - -struct block_stat_struct { - /* /proc/vmstat pgpgin & pgpgout */ - int bi; - int bo; -}; - -struct cpu_stat_sample_struct { - /* /proc/schedstat fields 10 & 11 (after name) */ - double runtime; - double waittime; -}; - -/* per process, per sample data we will log */ -struct ps_sched_struct { - /* /proc//schedstat fields 1 & 2 */ - double runtime; - double waittime; - int pss; - struct list_sample_data *sampledata; - struct ps_sched_struct *next; - struct ps_sched_struct *prev; - struct ps_sched_struct *cross; /* cross pointer */ - struct ps_struct *ps_new; -}; - -struct list_sample_data { - double runtime[MAXCPUS]; - double waittime[MAXCPUS]; - double sampletime; - int entropy_avail; - struct block_stat_struct blockstat; - LIST_FIELDS(struct list_sample_data, link); /* DLL */ - int counter; -}; - -/* process info */ -struct ps_struct { - struct ps_struct *next_ps; /* SLL pointer */ - struct ps_struct *parent; /* ppid ref */ - struct ps_struct *children; /* children */ - struct ps_struct *next; /* siblings */ - - /* must match - otherwise it's a new process with same PID */ - char name[256]; - int pid; - int ppid; - char *cgroup; - - /* cache fd's */ - int sched; - int schedstat; - FILE *smaps; - - /* pointers to first/last seen timestamps */ - struct ps_sched_struct *first; - struct ps_sched_struct *last; - - /* records actual start time, may be way before bootchart runs */ - double starttime; - - /* record human readable total cpu time */ - double total; - - /* largest PSS size found */ - int pss_max; - - /* for drawing connection lines later */ - double pos_x; - double pos_y; - - struct ps_sched_struct *sample; -}; - -extern bool arg_relative; -extern bool arg_filter; -extern bool arg_show_cmdline; -extern bool arg_show_cgroup; -extern bool arg_pss; -extern bool arg_entropy; -extern bool arg_percpu; -extern bool arg_initcall; -extern int arg_samples_len; -extern double arg_hz; -extern double arg_scale_x; -extern double arg_scale_y; - -extern char arg_output_path[PATH_MAX]; -extern char arg_init_path[PATH_MAX]; diff --git a/src/bootchart/store.c b/src/bootchart/store.c deleted file mode 100644 index 42cb8043ce..0000000000 --- a/src/bootchart/store.c +++ /dev/null @@ -1,555 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2009-2013 Intel Corporation - - Authors: - Auke Kok - - 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 . - ***/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "bootchart.h" -#include "cgroup-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "parse-util.h" -#include "store.h" -#include "string-util.h" -#include "strxcpyx.h" -#include "time-util.h" -#include "util.h" - -/* - * Alloc a static 4k buffer for stdio - primarily used to increase - * PSS buffering from the default 1k stdin buffer to reduce - * read() overhead. - */ -static char smaps_buf[4096]; -static int skip = 0; - -double gettime_ns(void) { - struct timespec n; - - clock_gettime(CLOCK_MONOTONIC, &n); - - return (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC)); -} - -static char *bufgetline(char *buf) { - char *c; - - if (!buf) - return NULL; - - c = strchr(buf, '\n'); - if (c) - c++; - - return c; -} - -static int pid_cmdline_strscpy(int procfd, char *buffer, size_t buf_len, int pid) { - char filename[PATH_MAX]; - _cleanup_close_ int fd = -1; - ssize_t n; - - sprintf(filename, "%d/cmdline", pid); - fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC); - if (fd < 0) - return -errno; - - n = read(fd, buffer, buf_len-1); - if (n > 0) { - int i; - for (i = 0; i < n; i++) - if (buffer[i] == '\0') - buffer[i] = ' '; - buffer[n] = '\0'; - } - - return 0; -} - -int log_sample(DIR *proc, - int sample, - struct ps_struct *ps_first, - struct list_sample_data **ptr, - int *pscount, - int *cpus) { - - static int vmstat = -1; - _cleanup_free_ char *buf_schedstat = NULL; - char buf[4096]; - char key[256]; - char val[256]; - char rt[256]; - char wt[256]; - char *m; - int r; - int c; - int p; - int mod; - static int e_fd = -1; - ssize_t s; - ssize_t n; - struct dirent *ent; - int fd; - struct list_sample_data *sampledata; - struct ps_sched_struct *ps_prev = NULL; - int procfd; - int taskfd = -1; - - sampledata = *ptr; - - procfd = dirfd(proc); - if (procfd < 0) - return -errno; - - if (vmstat < 0) { - /* block stuff */ - vmstat = openat(procfd, "vmstat", O_RDONLY|O_CLOEXEC); - if (vmstat < 0) - return log_error_errno(errno, "Failed to open /proc/vmstat: %m"); - } - - n = pread(vmstat, buf, sizeof(buf) - 1, 0); - if (n <= 0) { - vmstat = safe_close(vmstat); - if (n < 0) - return -errno; - return -ENODATA; - } - - buf[n] = '\0'; - - m = buf; - while (m) { - if (sscanf(m, "%s %s", key, val) < 2) - goto vmstat_next; - if (streq(key, "pgpgin")) - sampledata->blockstat.bi = atoi(val); - if (streq(key, "pgpgout")) { - sampledata->blockstat.bo = atoi(val); - break; - } -vmstat_next: - m = bufgetline(m); - if (!m) - break; - } - - /* Parse "/proc/schedstat" for overall CPU utilization */ - r = read_full_file("/proc/schedstat", &buf_schedstat, NULL); - if (r < 0) - return log_error_errno(r, "Unable to read schedstat: %m"); - - m = buf_schedstat; - while (m) { - if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3) - goto schedstat_next; - - if (strstr(key, "cpu")) { - r = safe_atoi((const char*)(key+3), &c); - if (r < 0 || c > MAXCPUS -1) - /* Oops, we only have room for MAXCPUS data */ - break; - sampledata->runtime[c] = atoll(rt); - sampledata->waittime[c] = atoll(wt); - - if (c == *cpus) - *cpus = c + 1; - } -schedstat_next: - m = bufgetline(m); - if (!m) - break; - } - - if (arg_entropy) { - if (e_fd < 0) { - e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY|O_CLOEXEC); - if (e_fd < 0) - return log_error_errno(errno, "Failed to open /proc/sys/kernel/random/entropy_avail: %m"); - } - - n = pread(e_fd, buf, sizeof(buf) - 1, 0); - if (n <= 0) { - e_fd = safe_close(e_fd); - } else { - buf[n] = '\0'; - sampledata->entropy_avail = atoi(buf); - } - } - - while ((ent = readdir(proc)) != NULL) { - char filename[PATH_MAX]; - int pid; - struct ps_struct *ps; - - if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9')) - continue; - - pid = atoi(ent->d_name); - - if (pid >= MAXPIDS) - continue; - - ps = ps_first; - while (ps->next_ps) { - ps = ps->next_ps; - if (ps->pid == pid) - break; - } - - /* end of our LL? then append a new record */ - if (ps->pid != pid) { - _cleanup_fclose_ FILE *st = NULL; - char t[32]; - struct ps_struct *parent; - - ps->next_ps = new0(struct ps_struct, 1); - if (!ps->next_ps) - return log_oom(); - - ps = ps->next_ps; - ps->pid = pid; - ps->sched = -1; - ps->schedstat = -1; - - ps->sample = new0(struct ps_sched_struct, 1); - if (!ps->sample) - return log_oom(); - - ps->sample->sampledata = sampledata; - - (*pscount)++; - - /* mark our first sample */ - ps->first = ps->last = ps->sample; - ps->sample->runtime = atoll(rt); - ps->sample->waittime = atoll(wt); - - /* get name, start time */ - if (ps->sched < 0) { - sprintf(filename, "%d/sched", pid); - ps->sched = openat(procfd, filename, O_RDONLY|O_CLOEXEC); - if (ps->sched < 0) - continue; - } - - s = pread(ps->sched, buf, sizeof(buf) - 1, 0); - if (s <= 0) { - ps->sched = safe_close(ps->sched); - continue; - } - buf[s] = '\0'; - - if (!sscanf(buf, "%s %*s %*s", key)) - continue; - - strscpy(ps->name, sizeof(ps->name), key); - - /* cmdline */ - if (arg_show_cmdline) - pid_cmdline_strscpy(procfd, ps->name, sizeof(ps->name), pid); - - /* discard line 2 */ - m = bufgetline(buf); - if (!m) - continue; - - m = bufgetline(m); - if (!m) - continue; - - if (!sscanf(m, "%*s %*s %s", t)) - continue; - - r = safe_atod(t, &ps->starttime); - if (r < 0) - continue; - - ps->starttime /= 1000.0; - - if (arg_show_cgroup) - /* if this fails, that's OK */ - cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, - ps->pid, &ps->cgroup); - - /* ppid */ - sprintf(filename, "%d/stat", pid); - fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC); - if (fd < 0) - continue; - - st = fdopen(fd, "re"); - if (!st) { - close(fd); - continue; - } - - if (!fscanf(st, "%*s %*s %*s %i", &p)) - continue; - - ps->ppid = p; - - /* - * setup child pointers - * - * these are used to paint the tree coherently later - * each parent has a LL of children, and a LL of siblings - */ - if (pid == 1) - continue; /* nothing to do for init atm */ - - /* kthreadd has ppid=0, which breaks our tree ordering */ - if (ps->ppid == 0) - ps->ppid = 1; - - parent = ps_first; - while ((parent->next_ps && parent->pid != ps->ppid)) - parent = parent->next_ps; - - if (parent->pid != ps->ppid) { - /* orphan */ - ps->ppid = 1; - parent = ps_first->next_ps; - } - - ps->parent = parent; - - if (!parent->children) { - /* it's the first child */ - parent->children = ps; - } else { - /* walk all children and append */ - struct ps_struct *children; - children = parent->children; - while (children->next) - children = children->next; - - children->next = ps; - } - } - - /* else -> found pid, append data in ps */ - - /* below here is all continuous logging parts - we get here on every - * iteration */ - - /* rt, wt */ - if (ps->schedstat < 0) { - sprintf(filename, "%d/schedstat", pid); - ps->schedstat = openat(procfd, filename, O_RDONLY|O_CLOEXEC); - if (ps->schedstat < 0) - continue; - } - - s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0); - if (s <= 0) { - /* clean up our file descriptors - assume that the process exited */ - close(ps->schedstat); - ps->schedstat = -1; - ps->sched = safe_close(ps->sched); - continue; - } - - buf[s] = '\0'; - - if (!sscanf(buf, "%s %s %*s", rt, wt)) - continue; - - ps->sample->next = new0(struct ps_sched_struct, 1); - if (!ps->sample->next) - return log_oom(); - - ps->sample->next->prev = ps->sample; - ps->sample = ps->sample->next; - ps->last = ps->sample; - ps->sample->runtime = atoll(rt); - ps->sample->waittime = atoll(wt); - ps->sample->sampledata = sampledata; - ps->sample->ps_new = ps; - if (ps_prev) - ps_prev->cross = ps->sample; - - ps_prev = ps->sample; - ps->total = (ps->last->runtime - ps->first->runtime) - / 1000000000.0; - - /* Take into account CPU runtime/waittime spent in non-main threads of the process - * by parsing "/proc/[pid]/task/[tid]/schedstat" for all [tid] != [pid] - * See https://github.com/systemd/systemd/issues/139 - */ - - /* Browse directory "/proc/[pid]/task" to know the thread ids of process [pid] */ - snprintf(filename, sizeof(filename), PID_FMT "/task", pid); - taskfd = openat(procfd, filename, O_RDONLY|O_DIRECTORY|O_CLOEXEC); - if (taskfd >= 0) { - _cleanup_closedir_ DIR *taskdir = NULL; - - taskdir = fdopendir(taskfd); - if (!taskdir) { - safe_close(taskfd); - return -errno; - } - FOREACH_DIRENT(ent, taskdir, break) { - int tid = -1; - _cleanup_close_ int tid_schedstat = -1; - long long delta_rt; - long long delta_wt; - - if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9')) - continue; - - /* Skip main thread as it was already accounted */ - r = safe_atoi(ent->d_name, &tid); - if (r < 0 || tid == pid) - continue; - - /* Parse "/proc/[pid]/task/[tid]/schedstat" */ - snprintf(filename, sizeof(filename), PID_FMT "/schedstat", tid); - tid_schedstat = openat(taskfd, filename, O_RDONLY|O_CLOEXEC); - - if (tid_schedstat == -1) - continue; - - s = pread(tid_schedstat, buf, sizeof(buf) - 1, 0); - if (s <= 0) - continue; - buf[s] = '\0'; - - if (!sscanf(buf, "%s %s %*s", rt, wt)) - continue; - - r = safe_atolli(rt, &delta_rt); - if (r < 0) - continue; - r = safe_atolli(rt, &delta_wt); - if (r < 0) - continue; - ps->sample->runtime += delta_rt; - ps->sample->waittime += delta_wt; - } - } - - if (!arg_pss) - goto catch_rename; - - /* Pss */ - if (!ps->smaps) { - sprintf(filename, "%d/smaps", pid); - fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC); - if (fd < 0) - continue; - ps->smaps = fdopen(fd, "re"); - if (!ps->smaps) { - close(fd); - continue; - } - setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf)); - } else { - rewind(ps->smaps); - } - - /* test to see if we need to skip another field */ - if (skip == 0) { - if (fgets(buf, sizeof(buf), ps->smaps) == NULL) { - continue; - } - if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) { - continue; - } - if (buf[392] == 'V') { - skip = 2; - } - else { - skip = 1; - } - rewind(ps->smaps); - } - - while (1) { - int pss_kb; - - /* skip one line, this contains the object mapped. */ - if (fgets(buf, sizeof(buf), ps->smaps) == NULL) { - break; - } - /* then there's a 28 char 14 line block */ - if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) { - break; - } - pss_kb = atoi(&buf[61]); - ps->sample->pss += pss_kb; - - /* skip one more line if this is a newer kernel */ - if (skip == 2) { - if (fgets(buf, sizeof(buf), ps->smaps) == NULL) - break; - } - } - - if (ps->sample->pss > ps->pss_max) - ps->pss_max = ps->sample->pss; - -catch_rename: - /* catch process rename, try to randomize time */ - mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0); - if (((sample - ps->pid) + pid) % (int)(mod) == 0) { - - /* re-fetch name */ - /* get name, start time */ - if (ps->sched < 0) { - sprintf(filename, "%d/sched", pid); - ps->sched = openat(procfd, filename, O_RDONLY|O_CLOEXEC); - if (ps->sched < 0) - continue; - } - - s = pread(ps->sched, buf, sizeof(buf) - 1, 0); - if (s <= 0) { - /* clean up file descriptors */ - ps->sched = safe_close(ps->sched); - ps->schedstat = safe_close(ps->schedstat); - continue; - } - - buf[s] = '\0'; - - if (!sscanf(buf, "%s %*s %*s", key)) - continue; - - strscpy(ps->name, sizeof(ps->name), key); - - /* cmdline */ - if (arg_show_cmdline) - pid_cmdline_strscpy(procfd, ps->name, sizeof(ps->name), pid); - } - } - - return 0; -} diff --git a/src/bootchart/store.h b/src/bootchart/store.h deleted file mode 100644 index 6e9acf2a6f..0000000000 --- a/src/bootchart/store.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright (C) 2009-2013 Intel Corporation - - Authors: - Auke Kok - - 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 . -***/ - -#include - -#include "bootchart.h" - -double gettime_ns(void); -void log_uptime(void); -int log_sample(DIR *proc, - int sample, - struct ps_struct *ps_first, - struct list_sample_data **ptr, - int *pscount, - int *cpus); diff --git a/src/bootchart/svg.c b/src/bootchart/svg.c deleted file mode 100644 index f2af535061..0000000000 --- a/src/bootchart/svg.c +++ /dev/null @@ -1,1375 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2009-2013 Intel Corporation - - Authors: - Auke Kok - - 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 . - ***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "architecture.h" -#include "bootchart.h" -#include "fd-util.h" -#include "fileio.h" -#include "list.h" -#include "macro.h" -#include "stdio-util.h" -#include "store.h" -#include "svg.h" -#include "utf8.h" -#include "util.h" - -#define time_to_graph(t) ((t) * arg_scale_x) -#define ps_to_graph(n) ((n) * arg_scale_y) -#define kb_to_graph(m) ((m) * arg_scale_y * 0.0001) -#define to_color(n) (192.0 - ((n) * 192.0)) - -static const char * const colorwheel[12] = { - "rgb(255,32,32)", // red - "rgb(32,192,192)", // cyan - "rgb(255,128,32)", // orange - "rgb(128,32,192)", // blue-violet - "rgb(255,255,32)", // yellow - "rgb(192,32,128)", // red-violet - "rgb(32,255,32)", // green - "rgb(255,64,32)", // red-orange - "rgb(32,32,255)", // blue - "rgb(255,192,32)", // yellow-orange - "rgb(192,32,192)", // violet - "rgb(32,192,32)" // yellow-green -}; - -static double idletime = -1.0; -static int pfiltered = 0; -static int pcount = 0; -static int kcount = 0; -static double psize = 0; -static double ksize = 0; -static double esize = 0; -static struct list_sample_data *sampledata; -static struct list_sample_data *prev_sampledata; - -static void svg_header(FILE *of, struct list_sample_data *head, double graph_start, int n_cpus) { - double w; - double h; - struct list_sample_data *sampledata_last; - - assert(head); - - sampledata_last = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - sampledata_last = sampledata; - } - - /* min width is about 1600px due to the label */ - w = 150.0 + 10.0 + time_to_graph(sampledata_last->sampletime - graph_start); - w = ((w < 1600.0) ? 1600.0 : w); - - /* height is variable based on pss, psize, ksize */ - h = 400.0 + (arg_scale_y * 30.0) /* base graphs and title */ - + (arg_pss ? (100.0 * arg_scale_y) + (arg_scale_y * 7.0) : 0.0) /* pss estimate */ - + psize + ksize + esize + (n_cpus * 15 * arg_scale_y); - - fprintf(of, "\n"); - fprintf(of, "\n"); - - //fprintf(of, "\n", 1000 + 150 + (pcount * 20)); - fprintf(of, "\n\n"); - - /* write some basic info as a comment, including some help */ - fprintf(of, "\n"); - fprintf(of, "\n"); - fprintf(of, "\n"); - fprintf(of, "\n"); - fprintf(of, "\n\n"); - - fprintf(of, "\n", VERSION); - fprintf(of, "\n", arg_hz, arg_samples_len); - fprintf(of, "\n", arg_scale_x, arg_scale_y); - fprintf(of, "\n", arg_relative, arg_filter); - fprintf(of, "\n", arg_pss, arg_entropy); - fprintf(of, "\n\n", arg_output_path, arg_init_path); - - /* style sheet */ - fprintf(of, "\n \n\n\n"); -} - -static int svg_title(FILE *of, const char *build, int pscount, double log_start, int overrun) { - _cleanup_free_ char *cmdline = NULL; - _cleanup_free_ char *model = NULL; - _cleanup_free_ char *buf = NULL; - char date[256] = "Unknown"; - const char *cpu; - char *c; - time_t t; - int r; - struct utsname uts; - - r = read_one_line_file("/proc/cmdline", &cmdline); - if (r < 0) { - log_error_errno(r, "Unable to read cmdline: %m"); - return r; - } - - /* extract root fs so we can find disk model name in sysfs */ - /* FIXME: this works only in the simple case */ - c = strstr(cmdline, "root=/dev/"); - if (c) { - char rootbdev[4]; - char filename[32]; - - strncpy(rootbdev, &c[10], sizeof(rootbdev) - 1); - rootbdev[3] = '\0'; - xsprintf(filename, "/sys/block/%s/device/model", rootbdev); - - r = read_one_line_file(filename, &model); - if (r < 0) - log_info("Error reading disk model for %s: %m\n", rootbdev); - } - - /* various utsname parameters */ - r = uname(&uts); - if (r < 0) { - log_error("Error getting uname info\n"); - return -errno; - } - - /* date */ - t = time(NULL); - r = strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&t)); - assert_se(r > 0); - - /* CPU type */ - r = get_proc_field("/proc/cpuinfo", PROC_CPUINFO_MODEL, "\n", &buf); - if (r < 0) - cpu = "Unknown"; - else - cpu = buf; - - fprintf(of, "Bootchart for %s - %s\n", - uts.nodename, date); - fprintf(of, "System: %s %s %s %s\n", - uts.sysname, uts.release, uts.version, uts.machine); - fprintf(of, "CPU: %s\n", cpu); - if (model) - fprintf(of, "Disk: %s\n", model); - fprintf(of, "Boot options: %s\n", cmdline); - fprintf(of, "Build: %s\n", build); - fprintf(of, "Log start time: %.03fs\n", log_start); - fprintf(of, "Idle time: "); - - if (idletime >= 0.0) - fprintf(of, "%.03fs", idletime); - else - fprintf(of, "Not detected"); - - fprintf(of, "\n"); - fprintf(of, "Graph data: %.03f samples/sec, recorded %i total, dropped %i samples, %i processes, %i filtered\n", - arg_hz, arg_samples_len, overrun, pscount, pfiltered); - - return 0; -} - -static void svg_graph_box(FILE *of, struct list_sample_data *head, int height, double graph_start) { - double d = 0.0; - int i = 0; - double finalsample = 0.0; - struct list_sample_data *sampledata_last; - - sampledata_last = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - sampledata_last = sampledata; - } - - finalsample = sampledata_last->sampletime; - - /* outside box, fill */ - fprintf(of, "\n", - time_to_graph(0.0), - time_to_graph(finalsample - graph_start), - ps_to_graph(height)); - - for (d = graph_start; d <= finalsample; - d += (arg_scale_x < 2.0 ? 60.0 : arg_scale_x < 10.0 ? 1.0 : 0.1)) { - /* lines for each second */ - if (i % 50 == 0) - fprintf(of, " \n", - time_to_graph(d - graph_start), - time_to_graph(d - graph_start), - ps_to_graph(height)); - else if (i % 10 == 0) - fprintf(of, " \n", - time_to_graph(d - graph_start), - time_to_graph(d - graph_start), - ps_to_graph(height)); - else - fprintf(of, " \n", - time_to_graph(d - graph_start), - time_to_graph(d - graph_start), - ps_to_graph(height)); - - /* time label */ - if (i % 10 == 0) - fprintf(of, " %.01fs\n", - time_to_graph(d - graph_start), - -5.0, d - graph_start); - - i++; - } -} - -/* xml comments must not contain "--" */ -static char* xml_comment_encode(const char* name) { - char *enc_name, *p; - - enc_name = strdup(name); - if (!enc_name) - return NULL; - - for (p = enc_name; *p; p++) - if (p[0] == '-' && p[1] == '-') - p[1] = '_'; - - return enc_name; -} - -static void svg_pss_graph(FILE *of, - struct list_sample_data *head, - struct ps_struct *ps_first, - double graph_start) { - struct ps_struct *ps; - int i; - struct list_sample_data *sampledata_last; - - sampledata_last = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - sampledata_last = sampledata; - } - - - fprintf(of, "\n\n\n"); - - fprintf(of, "\n Memory allocation - Pss\n"); - - /* vsize 1000 == 1000mb */ - svg_graph_box(of, head, 100, graph_start); - /* draw some hlines for usable memory sizes */ - for (i = 100000; i < 1000000; i += 100000) { - fprintf(of, " \n", - time_to_graph(.0), - kb_to_graph(i), - time_to_graph(sampledata_last->sampletime - graph_start), - kb_to_graph(i)); - fprintf(of, " %dM\n", - time_to_graph(sampledata_last->sampletime - graph_start) + 5, - kb_to_graph(i), (1000000 - i) / 1000); - } - fprintf(of, "\n"); - - /* now plot the graph itself */ - i = 1; - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int bottom; - int top; - struct ps_sched_struct *cross_place; - - bottom = 0; - top = 0; - - /* put all the small pss blocks into the bottom */ - ps = ps_first; - while (ps->next_ps) { - ps = ps->next_ps; - if (!ps) - continue; - ps->sample = ps->first; - while (ps->sample->next) { - ps->sample = ps->sample->next; - if (ps->sample->sampledata == sampledata) - break; - } - if (ps->sample->sampledata == sampledata) { - if (ps->sample->pss <= (100 * arg_scale_y)) - top += ps->sample->pss; - break; - } - } - while (ps->sample->cross) { - cross_place = ps->sample->cross; - ps = ps->sample->cross->ps_new; - ps->sample = cross_place; - if (ps->sample->pss <= (100 * arg_scale_y)) - top += ps->sample->pss; - } - - fprintf(of, " \n", - "rgb(64,64,64)", - time_to_graph(prev_sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - top), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - kb_to_graph(top - bottom)); - bottom = top; - - /* now plot the ones that are of significant size */ - ps = ps_first; - while (ps->next_ps) { - ps = ps->next_ps; - if (!ps) - continue; - ps->sample = ps->first; - while (ps->sample->next) { - ps->sample = ps->sample->next; - if (ps->sample->sampledata == sampledata) - break; - } - /* don't draw anything smaller than 2mb */ - if (ps->sample->sampledata != sampledata) - continue; - if (ps->sample->pss > (100 * arg_scale_y)) { - top = bottom + ps->sample->pss; - fprintf(of, " \n", - colorwheel[ps->pid % 12], - time_to_graph(prev_sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - top), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - kb_to_graph(top - bottom)); - bottom = top; - } - break; - } - - while ((cross_place = ps->sample->cross)) { - ps = ps->sample->cross->ps_new; - ps->sample = cross_place; - if (ps->sample->pss > (100 * arg_scale_y)) { - top = bottom + ps->sample->pss; - fprintf(of, " \n", - colorwheel[ps->pid % 12], - time_to_graph(prev_sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - top), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - kb_to_graph(top - bottom)); - bottom = top; - } - } - - prev_sampledata = sampledata; - i++; - } - - /* overlay all the text labels */ - i = 1; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int bottom; - int top = 0; - struct ps_sched_struct *prev_sample; - struct ps_sched_struct *cross_place; - - /* put all the small pss blocks into the bottom */ - ps = ps_first->next_ps; - while (ps->next_ps) { - ps = ps->next_ps; - if (!ps) - continue; - - ps->sample = ps->first; - while (ps->sample->next) { - ps->sample = ps->sample->next; - if (ps->sample->sampledata == sampledata) - break; - } - - if (ps->sample->sampledata == sampledata) { - if (ps->sample->pss <= (100 * arg_scale_y)) - top += ps->sample->pss; - - break; - } - } - - while ((cross_place = ps->sample->cross)) { - ps = ps->sample->cross->ps_new; - ps->sample = cross_place; - if (ps->sample->pss <= (100 * arg_scale_y)) - top += ps->sample->pss; - } - bottom = top; - - /* now plot the ones that are of significant size */ - ps = ps_first; - while (ps->next_ps) { - prev_sample = ps->sample; - ps = ps->next_ps; - if (!ps) - continue; - ps->sample = ps->first; - while (ps->sample->next) { - prev_sample = ps->sample; - ps->sample = ps->sample->next; - if (ps->sample->sampledata == sampledata) - break; - } - /* don't draw anything smaller than 2mb */ - if (ps->sample->sampledata == sampledata) { - if (ps->sample->pss > (100 * arg_scale_y)) { - top = bottom + ps->sample->pss; - /* draw a label with the process / PID */ - if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y))) - fprintf(of, " %s [%i]\n", - time_to_graph(sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)), - ps->name, ps->pid); - bottom = top; - } - break; - } - } - while ((cross_place = ps->sample->cross)) { - ps = ps->sample->cross->ps_new; - ps->sample = cross_place; - prev_sample = ps->sample->prev; - if (ps->sample->pss > (100 * arg_scale_y)) { - top = bottom + ps->sample->pss; - /* draw a label with the process / PID */ - if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y))) - fprintf(of, " %s [%i]\n", - time_to_graph(sampledata->sampletime - graph_start), - kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)), - ps->name, ps->pid); - bottom = top; - } - } - - i++; - } - - /* debug output - full data dump */ - fprintf(of, "\n\n\n"); - ps = ps_first; - while (ps->next_ps) { - _cleanup_free_ char *enc_name = NULL; - ps = ps->next_ps; - if (!ps) - continue; - - enc_name = xml_comment_encode(ps->name); - if (!enc_name) - continue; - - fprintf(of, "\n"); - } - -} - -static void svg_io_bi_bar(FILE *of, - struct list_sample_data *head, - int n_samples, - double graph_start, - double interval) { - - double max = 0.0; - double range; - int max_here = 0; - int i; - int k; - struct list_sample_data *start_sampledata; - struct list_sample_data *stop_sampledata; - - fprintf(of, "\n"); - fprintf(of, "IO utilization - read\n"); - - /* - * calculate rounding range - * - * We need to round IO data since IO block data is not updated on - * each poll. Applying a smoothing function loses some burst data, - * so keep the smoothing range short. - */ - range = 0.25 / (1.0 / arg_hz); - if (range < 2.0) - range = 2.0; /* no smoothing */ - - /* surrounding box */ - svg_graph_box(of, head, 5, graph_start); - - /* find the max IO first */ - i = 1; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int start; - int stop; - int diff; - double tot; - - start = MAX(i - ((range / 2) - 1), 0); - stop = MIN(i + (range / 2), n_samples - 1); - diff = (stop - start); - - start_sampledata = sampledata; - stop_sampledata = sampledata; - - for (k = 0; k < ((range/2) - 1) && start_sampledata->link_next; k++) - start_sampledata = start_sampledata->link_next; - - for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) - stop_sampledata = stop_sampledata->link_prev; - - tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; - - if (tot > max) { - max = tot; - max_here = i; - } - - tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) / diff; - - if (tot > max) - max = tot; - - i++; - } - - /* plot bi */ - i = 1; - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int start; - int stop; - int diff; - double tot; - double pbi = 0; - - start = MAX(i - ((range / 2) - 1), 0); - stop = MIN(i + (range / 2), n_samples); - diff = (stop - start); - - start_sampledata = sampledata; - stop_sampledata = sampledata; - - for (k = 0; k < ((range/2)-1) && start_sampledata->link_next; k++) - start_sampledata = start_sampledata->link_next; - - for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) - stop_sampledata = stop_sampledata->link_prev; - - tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; - - if (max > 0) - pbi = tot / max; - - if (pbi > 0.001) - fprintf(of, "\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - (arg_scale_y * 5) - (pbi * (arg_scale_y * 5)), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - pbi * (arg_scale_y * 5)); - - /* labels around highest value */ - if (i == max_here) - fprintf(of, " %0.2fmb/sec\n", - time_to_graph(sampledata->sampletime - graph_start) + 5, - ((arg_scale_y * 5) - (pbi * (arg_scale_y * 5))) + 15, - max / 1024.0 / (interval / 1000000000.0)); - - i++; - prev_sampledata = sampledata; - } -} - -static void svg_io_bo_bar(FILE *of, - struct list_sample_data *head, - int n_samples, - double graph_start, - double interval) { - double max = 0.0; - double range; - int max_here = 0; - int i; - int k; - struct list_sample_data *start_sampledata; - struct list_sample_data *stop_sampledata; - - fprintf(of, "\n"); - fprintf(of, "IO utilization - write\n"); - - /* - * calculate rounding range - * - * We need to round IO data since IO block data is not updated on - * each poll. Applying a smoothing function loses some burst data, - * so keep the smoothing range short. - */ - range = 0.25 / (1.0 / arg_hz); - if (range < 2.0) - range = 2.0; /* no smoothing */ - - /* surrounding box */ - svg_graph_box(of, head, 5, graph_start); - - /* find the max IO first */ - i = 0; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int start; - int stop; - int diff; - double tot; - - start = MAX(i - ((range / 2) - 1), 0); - stop = MIN(i + (range / 2), n_samples - 1); - diff = (stop - start); - - start_sampledata = sampledata; - stop_sampledata = sampledata; - - for (k = 0; k < (range/2) - 1 && start_sampledata->link_next; k++) - start_sampledata = start_sampledata->link_next; - - for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) - stop_sampledata = stop_sampledata->link_prev; - - tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; - if (tot > max) - max = tot; - - tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) / diff; - if (tot > max) { - max = tot; - max_here = i; - } - - i++; - } - - /* plot bo */ - prev_sampledata = head; - i = 1; - - LIST_FOREACH_BEFORE(link, sampledata, head) { - int start, stop, diff; - double tot, pbo; - - pbo = 0; - - start = MAX(i - ((range / 2) - 1), 0); - stop = MIN(i + (range / 2), n_samples); - diff = (stop - start); - - start_sampledata = sampledata; - stop_sampledata = sampledata; - - for (k = 0; k < ((range/2)-1) && start_sampledata->link_next; k++) - start_sampledata = start_sampledata->link_next; - - for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) - stop_sampledata = stop_sampledata->link_prev; - - tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) - / diff; - - if (max > 0) - pbo = tot / max; - - if (pbo > 0.001) - fprintf(of, "\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - (arg_scale_y * 5) - (pbo * (arg_scale_y * 5)), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - pbo * (arg_scale_y * 5)); - - /* labels around highest bo value */ - if (i == max_here) - fprintf(of, " %0.2fmb/sec\n", - time_to_graph(sampledata->sampletime - graph_start) + 5, - ((arg_scale_y * 5) - (pbo * (arg_scale_y * 5))), - max / 1024.0 / (interval / 1000000000.0)); - - i++; - prev_sampledata = sampledata; - } -} - -static void svg_cpu_bar(FILE *of, struct list_sample_data *head, int n_cpus, int cpu_num, double graph_start) { - - fprintf(of, "\n"); - - if (cpu_num < 0) - fprintf(of, "CPU[overall] utilization\n"); - else - fprintf(of, "CPU[%d] utilization\n", cpu_num); - - /* surrounding box */ - svg_graph_box(of, head, 5, graph_start); - - /* bars for each sample, proportional to the CPU util. */ - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int c; - double trt; - double ptrt; - - ptrt = trt = 0.0; - - if (cpu_num < 0) - for (c = 0; c < n_cpus; c++) - trt += sampledata->runtime[c] - prev_sampledata->runtime[c]; - else - trt = sampledata->runtime[cpu_num] - prev_sampledata->runtime[cpu_num]; - - trt = trt / 1000000000.0; - - if (cpu_num < 0) - trt = trt / (double)n_cpus; - - if (trt > 0.0) - ptrt = trt / (sampledata->sampletime - prev_sampledata->sampletime); - - if (ptrt > 1.0) - ptrt = 1.0; - - if (ptrt > 0.001) - fprintf(of, "\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - (arg_scale_y * 5) - (ptrt * (arg_scale_y * 5)), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - ptrt * (arg_scale_y * 5)); - - prev_sampledata = sampledata; - } -} - -static void svg_wait_bar(FILE *of, struct list_sample_data *head, int n_cpus, int cpu_num, double graph_start) { - - fprintf(of, "\n"); - - if (cpu_num < 0) - fprintf(of, "CPU[overall] wait\n"); - else - fprintf(of, "CPU[%d] wait\n", cpu_num); - - /* surrounding box */ - svg_graph_box(of, head, 5, graph_start); - - /* bars for each sample, proportional to the CPU util. */ - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - int c; - double twt; - double ptwt; - - ptwt = twt = 0.0; - - if (cpu_num < 0) - for (c = 0; c < n_cpus; c++) - twt += sampledata->waittime[c] - prev_sampledata->waittime[c]; - else - twt = sampledata->waittime[cpu_num] - prev_sampledata->waittime[cpu_num]; - - twt = twt / 1000000000.0; - - if (cpu_num < 0) - twt = twt / (double)n_cpus; - - if (twt > 0.0) - ptwt = twt / (sampledata->sampletime - prev_sampledata->sampletime); - - if (ptwt > 1.0) - ptwt = 1.0; - - if (ptwt > 0.001) - fprintf(of, "\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - ((arg_scale_y * 5) - (ptwt * (arg_scale_y * 5))), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - ptwt * (arg_scale_y * 5)); - - prev_sampledata = sampledata; - } -} - -static void svg_entropy_bar(FILE *of, struct list_sample_data *head, double graph_start) { - - fprintf(of, "\n"); - - fprintf(of, "Entropy pool size\n"); - /* surrounding box */ - svg_graph_box(of, head, 5, graph_start); - - /* bars for each sample, scale 0-4096 */ - prev_sampledata = head; - LIST_FOREACH_BEFORE(link, sampledata, head) { - fprintf(of, "\n", - time_to_graph(prev_sampledata->sampletime - graph_start), - ((arg_scale_y * 5) - ((sampledata->entropy_avail / 4096.) * (arg_scale_y * 5))), - time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), - (sampledata->entropy_avail / 4096.) * (arg_scale_y * 5)); - prev_sampledata = sampledata; - } -} - -static struct ps_struct *get_next_ps(struct ps_struct *ps, struct ps_struct *ps_first) { - /* - * walk the list of processes and return the next one to be - * painted - */ - if (ps == ps_first) - return ps->next_ps; - - /* go deep */ - if (ps->children) - return ps->children; - - /* find siblings */ - if (ps->next) - return ps->next; - - /* go back for parent siblings */ - for (;;) { - if (ps->parent && ps->parent->next) - return ps->parent->next; - - ps = ps->parent; - if (!ps) - return ps; - } - - return NULL; -} - -static bool ps_filter(struct ps_struct *ps) { - if (!arg_filter) - return false; - - /* can't draw data when there is only 1 sample (need start + stop) */ - if (ps->first == ps->last) - return true; - - /* don't filter kthreadd */ - if (ps->pid == 2) - return false; - - /* drop stuff that doesn't use any real CPU time */ - if (ps->total <= 0.001) - return true; - - return 0; -} - -static void svg_do_initcall(FILE *of, struct list_sample_data *head, int count_only, double graph_start) { - _cleanup_pclose_ FILE *f = NULL; - double t; - char func[256]; - int ret; - int usecs; - - /* can't plot initcall when disabled or in relative mode */ - if (!arg_initcall || arg_relative) { - kcount = 0; - return; - } - - if (!count_only) { - fprintf(of, "\n"); - fprintf(of, "Kernel init threads\n"); - /* surrounding box */ - svg_graph_box(of, head, kcount, graph_start); - } - - kcount = 0; - - /* - * Initcall graphing - parses dmesg buffer and displays kernel threads - * This somewhat uses the same methods and scaling to show processes - * but looks a lot simpler. It's overlaid entirely onto the PS graph - * when appropriate. - */ - - f = popen("dmesg", "r"); - if (!f) - return; - - while (!feof(f)) { - int c; - int z = 0; - char l[256]; - - if (fgets(l, sizeof(l) - 1, f) == NULL) - continue; - - c = sscanf(l, "[%lf] initcall %s %*s %d %*s %d %*s", - &t, func, &ret, &usecs); - if (c != 4) { - /* also parse initcalls done by module loading */ - c = sscanf(l, "[%lf] initcall %s %*s %*s %d %*s %d %*s", - &t, func, &ret, &usecs); - if (c != 4) - continue; - } - - /* chop the +0xXX/0xXX stuff */ - while(func[z] != '+') - z++; - func[z] = 0; - - if (count_only) { - /* filter out irrelevant stuff */ - if (usecs >= 1000) - kcount++; - continue; - } - - fprintf(of, "\n", - func, t, usecs, ret); - - if (usecs < 1000) - continue; - - /* rect */ - fprintf(of, " \n", - time_to_graph(t - (usecs / 1000000.0)), - ps_to_graph(kcount), - time_to_graph(usecs / 1000000.0), - ps_to_graph(1)); - - /* label */ - fprintf(of, " %s %.03fs\n", - time_to_graph(t - (usecs / 1000000.0)) + 5, - ps_to_graph(kcount) + 15, - func, usecs / 1000000.0); - - kcount++; - } -} - -static void svg_ps_bars(FILE *of, - struct list_sample_data *head, - int n_samples, - int n_cpus, - struct ps_struct *ps_first, - double graph_start, - double interval) { - - struct ps_struct *ps; - int i = 0; - int j = 0; - int pid; - double w = 0.0; - - fprintf(of, "\n"); - fprintf(of, "Processes\n"); - - /* surrounding box */ - svg_graph_box(of, head, pcount, graph_start); - - /* pass 2 - ps boxes */ - ps = ps_first; - while ((ps = get_next_ps(ps, ps_first))) { - _cleanup_free_ char *enc_name = NULL, *escaped = NULL; - double endtime; - double starttime; - int t; - - if (!utf8_is_printable(ps->name, strlen(ps->name))) - escaped = utf8_escape_non_printable(ps->name); - - enc_name = xml_comment_encode(escaped ? escaped : ps->name); - if (!enc_name) - continue; - - /* leave some trace of what we actually filtered etc. */ - fprintf(of, "\n", enc_name, ps->pid, - ps->ppid, ps->total); - - starttime = ps->first->sampledata->sampletime; - - if (!ps_filter(ps)) { - /* remember where _to_ our children need to draw a line */ - ps->pos_x = time_to_graph(starttime - graph_start); - ps->pos_y = ps_to_graph(j+1); /* bottom left corner */ - } else if (ps->parent){ - /* hook children to our parent coords instead */ - ps->pos_x = ps->parent->pos_x; - ps->pos_y = ps->parent->pos_y; - - /* if this is the last child, we might still need to draw a connecting line */ - if ((!ps->next) && (ps->parent)) - fprintf(of, " \n", - ps->parent->pos_x, - ps_to_graph(j-1) + 10.0, /* whee, use the last value here */ - ps->parent->pos_x, - ps->parent->pos_y); - continue; - } - - endtime = ps->last->sampledata->sampletime; - fprintf(of, " \n", - time_to_graph(starttime - graph_start), - ps_to_graph(j), - time_to_graph(ps->last->sampledata->sampletime - starttime), - ps_to_graph(1)); - - /* paint cpu load over these */ - ps->sample = ps->first; - t = 1; - while (ps->sample->next) { - double rt, prt; - double wt, wrt; - struct ps_sched_struct *prev; - - prev = ps->sample; - ps->sample = ps->sample->next; - - /* calculate over interval */ - rt = ps->sample->runtime - prev->runtime; - wt = ps->sample->waittime - prev->waittime; - - prt = (rt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime); - wrt = (wt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime); - - /* this can happen if timekeeping isn't accurate enough */ - if (prt > 1.0) - prt = 1.0; - if (wrt > 1.0) - wrt = 1.0; - - if ((prt < 0.1) && (wrt < 0.1)) /* =~ 26 (color threshold) */ - continue; - - fprintf(of, " \n", - time_to_graph(prev->sampledata->sampletime - graph_start), - ps_to_graph(j), - time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime), - ps_to_graph(wrt)); - - /* draw cpu over wait - TODO figure out how/why run + wait > interval */ - fprintf(of, " \n", - time_to_graph(prev->sampledata->sampletime - graph_start), - ps_to_graph(j + (1.0 - prt)), - time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime), - ps_to_graph(prt)); - t++; - } - - /* determine where to display the process name */ - if ((endtime - starttime) < 1.5) - /* too small to fit label inside the box */ - w = endtime; - else - w = starttime; - - /* text label of process name */ - fprintf(of, " %s [%i]%.03fs %s\n", - time_to_graph(w - graph_start) + 5.0, - ps_to_graph(j) + 14.0, - escaped ? escaped : ps->name, - ps->pid, - (ps->last->runtime - ps->first->runtime) / 1000000000.0, - arg_show_cgroup ? ps->cgroup : ""); - /* paint lines to the parent process */ - if (ps->parent) { - /* horizontal part */ - fprintf(of, " \n", - time_to_graph(starttime - graph_start), - ps_to_graph(j) + 10.0, - ps->parent->pos_x, - ps_to_graph(j) + 10.0); - - /* one vertical line connecting all the horizontal ones up */ - if (!ps->next) - fprintf(of, " \n", - ps->parent->pos_x, - ps_to_graph(j) + 10.0, - ps->parent->pos_x, - ps->parent->pos_y); - } - - j++; /* count boxes */ - - fprintf(of, "\n"); - } - - /* last pass - determine when idle */ - pid = getpid(); - /* make sure we start counting from the point where we actually have - * data: assume that bootchart's first sample is when data started - */ - - ps = ps_first; - while (ps->next_ps) { - ps = ps->next_ps; - if (ps->pid == pid) - break; - } - - /* need to know last node first */ - ps->sample = ps->first; - i = ps->sample->next->sampledata->counter; - - while (ps->sample->next && i<(n_samples-(arg_hz/2))) { - double crt; - double brt; - int c; - int ii; - struct ps_sched_struct *sample_hz; - - ps->sample = ps->sample->next; - sample_hz = ps->sample; - for (ii = 0; (ii < (int)arg_hz/2) && sample_hz->next; ii++) - sample_hz = sample_hz->next; - - /* subtract bootchart cpu utilization from total */ - crt = 0.0; - for (c = 0; c < n_cpus; c++) - crt += sample_hz->sampledata->runtime[c] - ps->sample->sampledata->runtime[c]; - - brt = sample_hz->runtime - ps->sample->runtime; - /* - * our definition of "idle": - * - * if for (hz / 2) we've used less CPU than (interval / 2) ... - * defaults to 4.0%, which experimentally, is where atom idles - */ - if ((crt - brt) < (interval / 2.0)) { - idletime = ps->sample->sampledata->sampletime - graph_start; - fprintf(of, "\n\n", idletime); - fprintf(of, "\n", - time_to_graph(idletime), - -arg_scale_y, - time_to_graph(idletime), - ps_to_graph(pcount) + arg_scale_y); - fprintf(of, "%.01fs\n", - time_to_graph(idletime) + 5.0, - ps_to_graph(pcount) + arg_scale_y, - idletime); - break; - } - - i++; - } -} - -static void svg_top_ten_cpu(FILE *of, struct ps_struct *ps_first) { - struct ps_struct *top[10]; - struct ps_struct emptyps = {}; - struct ps_struct *ps; - int n, m; - - for (n = 0; n < (int) ELEMENTSOF(top); n++) - top[n] = &emptyps; - - /* walk all ps's and setup ptrs */ - ps = ps_first; - while ((ps = get_next_ps(ps, ps_first))) { - for (n = 0; n < 10; n++) { - if (ps->total <= top[n]->total) - continue; - /* cascade insert */ - for (m = 9; m > n; m--) - top[m] = top[m-1]; - top[n] = ps; - break; - } - } - - fprintf(of, "Top CPU consumers:\n"); - for (n = 0; n < 10; n++) - fprintf(of, "%3.03fs - %s [%d]\n", - 20 + (n * 13), - top[n]->total, - top[n]->name, - top[n]->pid); -} - -static void svg_top_ten_pss(FILE *of, struct ps_struct *ps_first) { - struct ps_struct *top[10]; - struct ps_struct emptyps = {}; - struct ps_struct *ps; - int n, m; - - for (n = 0; n < (int) ELEMENTSOF(top); n++) - top[n] = &emptyps; - - /* walk all ps's and setup ptrs */ - ps = ps_first; - while ((ps = get_next_ps(ps, ps_first))) { - for (n = 0; n < 10; n++) { - if (ps->pss_max <= top[n]->pss_max) - continue; - - /* cascade insert */ - for (m = 9; m > n; m--) - top[m] = top[m-1]; - top[n] = ps; - break; - } - } - - fprintf(of, "Top PSS consumers:\n"); - for (n = 0; n < 10; n++) - fprintf(of, "%dK - %s [%d]\n", - 20 + (n * 13), - top[n]->pss_max, - top[n]->name, - top[n]->pid); -} - -int svg_do(FILE *of, - const char *build, - struct list_sample_data *head, - struct ps_struct *ps_first, - int n_samples, - int pscount, - int n_cpus, - double graph_start, - double log_start, - double interval, - int overrun) { - - struct ps_struct *ps; - double offset = 7; - int r, c; - - sampledata = head; - LIST_FIND_TAIL(link, sampledata, head); - ps = ps_first; - - /* count initcall thread count first */ - svg_do_initcall(of, head, 1, graph_start); - ksize = kcount ? ps_to_graph(kcount) + (arg_scale_y * 2) : 0; - - /* then count processes */ - while ((ps = get_next_ps(ps, ps_first))) { - if (!ps_filter(ps)) - pcount++; - else - pfiltered++; - } - psize = ps_to_graph(pcount) + (arg_scale_y * 2); - - esize = (arg_entropy ? arg_scale_y * 7 : 0); - - /* after this, we can draw the header with proper sizing */ - svg_header(of, head, graph_start, arg_percpu ? n_cpus : 0); - fprintf(of, "\n\n"); - - fprintf(of, "\n"); - svg_io_bi_bar(of, head, n_samples, graph_start, interval); - fprintf(of, "\n\n"); - - fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); - svg_io_bo_bar(of, head, n_samples, graph_start, interval); - fprintf(of, "\n\n"); - - for (c = -1; c < (arg_percpu ? n_cpus : 0); c++) { - offset += 7; - fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); - svg_cpu_bar(of, head, n_cpus, c, graph_start); - fprintf(of, "\n\n"); - - offset += 7; - fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); - svg_wait_bar(of, head, n_cpus, c, graph_start); - fprintf(of, "\n\n"); - } - - if (kcount) { - offset += 7; - fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); - svg_do_initcall(of, head, 0, graph_start); - fprintf(of, "\n\n"); - } - - offset += 7; - fprintf(of, "\n", 400.0 + (arg_scale_y * offset) + ksize); - svg_ps_bars(of, head, n_samples, n_cpus, ps_first, graph_start, interval); - fprintf(of, "\n\n"); - - fprintf(of, "\n"); - r = svg_title(of, build, pscount, log_start, overrun); - fprintf(of, "\n\n"); - - if (r < 0) - return r; - - fprintf(of, "\n"); - svg_top_ten_cpu(of, ps_first); - fprintf(of, "\n\n"); - - if (arg_entropy) { - fprintf(of, "\n", 400.0 + (arg_scale_y * offset) + ksize + psize); - svg_entropy_bar(of, head, graph_start); - fprintf(of, "\n\n"); - } - - if (arg_pss) { - fprintf(of, "\n", 400.0 + (arg_scale_y * offset) + ksize + psize + esize); - svg_pss_graph(of, head, ps_first, graph_start); - fprintf(of, "\n\n"); - - fprintf(of, "\n"); - svg_top_ten_pss(of, ps_first); - fprintf(of, "\n\n"); - } - - /* fprintf footer */ - fprintf(of, "\n\n"); - - return 0; -} diff --git a/src/bootchart/svg.h b/src/bootchart/svg.h deleted file mode 100644 index 6e06b5ad97..0000000000 --- a/src/bootchart/svg.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright (C) 2009-2013 Intel Corporation - - Authors: - Auke Kok - - 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 . -***/ - -int svg_do(FILE *of, - const char *build, - struct list_sample_data *head, - struct ps_struct *ps_first, - int n_samples, - int pscount, - int n_cpus, - double graph_start, - double log_start, - double interval, - int overrun); diff --git a/src/bus-proxyd/Makefile b/src/bus-proxyd/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/bus-proxyd/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/bus-proxyd/Makefile b/src/bus-proxyd/Makefile new file mode 100644 index 0000000000..fb8e2826f6 --- /dev/null +++ b/src/bus-proxyd/Makefile @@ -0,0 +1,79 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +noinst_LTLIBRARIES += \ + libbus-proxy-core.la + +libbus_proxy_core_la_SOURCES = \ + src/bus-proxyd/bus-xml-policy.c \ + src/bus-proxyd/bus-xml-policy.h \ + src/bus-proxyd/driver.c \ + src/bus-proxyd/driver.h \ + src/bus-proxyd/proxy.c \ + src/bus-proxyd/proxy.h \ + src/bus-proxyd/synthesize.c \ + src/bus-proxyd/synthesize.h + +libbus_proxy_core_la_LIBADD = \ + libshared.la + +systemd_bus_proxyd_SOURCES = \ + src/bus-proxyd/bus-proxyd.c + +systemd_bus_proxyd_LDADD = \ + libbus-proxy-core.la \ + libshared.la + +systemd_stdio_bridge_SOURCES = \ + src/bus-proxyd/stdio-bridge.c + +systemd_stdio_bridge_LDADD = \ + libbus-proxy-core.la \ + libshared.la + +nodist_systemunit_DATA += \ + units/systemd-bus-proxyd.service + +dist_systemunit_DATA += \ + units/systemd-bus-proxyd.socket + +nodist_userunit_DATA += \ + units/user/systemd-bus-proxyd.service + +dist_userunit_DATA += \ + units/user/systemd-bus-proxyd.socket + +EXTRA_DIST += \ + units/systemd-bus-proxyd.service.m4.in \ + units/user/systemd-bus-proxyd.service.in + +ifneq ($(HAVE_SMACK),) +bus-proxyd-set-cap-hook: + -$(SETCAP) cap_mac_admin+ei $(DESTDIR)$(rootlibexecdir)/systemd-bus-proxyd + +INSTALL_EXEC_HOOKS += bus-proxyd-set-cap-hook +endif + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/bus-proxyd/bus-proxyd.c b/src/bus-proxyd/bus-proxyd.c index b6bd6eb390..17b80c3e9a 100644 --- a/src/bus-proxyd/bus-proxyd.c +++ b/src/bus-proxyd/bus-proxyd.c @@ -29,7 +29,7 @@ #include #include -#include "sd-daemon.h" +#include #include "alloc-util.h" #include "bus-internal.h" diff --git a/src/bus-proxyd/bus-xml-policy.c b/src/bus-proxyd/bus-xml-policy.c index 8943e0dc12..86a8362142 100644 --- a/src/bus-proxyd/bus-xml-policy.c +++ b/src/bus-proxyd/bus-xml-policy.c @@ -17,7 +17,7 @@ along with systemd; If not, see . ***/ -#include "sd-login.h" +#include #include "alloc-util.h" #include "bus-internal.h" diff --git a/src/bus-proxyd/driver.c b/src/bus-proxyd/driver.c index 1af5c310ea..133b2e0f39 100644 --- a/src/bus-proxyd/driver.c +++ b/src/bus-proxyd/driver.c @@ -23,7 +23,7 @@ #include #include -#include "sd-bus.h" +#include #include "alloc-util.h" #include "bus-internal.h" diff --git a/src/bus-proxyd/driver.h b/src/bus-proxyd/driver.h index 1630cdc7b9..cad7aa7851 100644 --- a/src/bus-proxyd/driver.h +++ b/src/bus-proxyd/driver.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-bus.h" +#include #include "bus-xml-policy.h" #include "proxy.h" diff --git a/src/bus-proxyd/proxy.c b/src/bus-proxyd/proxy.c index 907d93d4c7..bcfbab0dad 100644 --- a/src/bus-proxyd/proxy.c +++ b/src/bus-proxyd/proxy.c @@ -26,8 +26,8 @@ #include #include -#include "sd-bus.h" -#include "sd-daemon.h" +#include +#include #include "alloc-util.h" #include "bus-control.h" diff --git a/src/bus-proxyd/proxy.h b/src/bus-proxyd/proxy.h index d9e75cf73b..0e3ed8a87a 100644 --- a/src/bus-proxyd/proxy.h +++ b/src/bus-proxyd/proxy.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-bus.h" +#include #include "bus-xml-policy.h" diff --git a/src/bus-proxyd/stdio-bridge.c b/src/bus-proxyd/stdio-bridge.c index 291c1b09e3..60c4a08325 100644 --- a/src/bus-proxyd/stdio-bridge.c +++ b/src/bus-proxyd/stdio-bridge.c @@ -25,8 +25,8 @@ #include #include -#include "sd-bus.h" -#include "sd-daemon.h" +#include +#include #include "alloc-util.h" #include "bus-internal.h" diff --git a/src/bus-proxyd/synthesize.c b/src/bus-proxyd/synthesize.c index 8eea7dc5b9..7562f29ecb 100644 --- a/src/bus-proxyd/synthesize.c +++ b/src/bus-proxyd/synthesize.c @@ -21,7 +21,7 @@ #include -#include "sd-bus.h" +#include #include "bus-internal.h" #include "bus-match.h" diff --git a/src/bus-proxyd/synthesize.h b/src/bus-proxyd/synthesize.h index 1b7197f8ec..cb8c21cdb7 100644 --- a/src/bus-proxyd/synthesize.h +++ b/src/bus-proxyd/synthesize.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-bus.h" +#include #include "proxy.h" diff --git a/src/bus-proxyd/test-bus-xml-policy.c b/src/bus-proxyd/test-bus-xml-policy.c index af7c9128a2..6f32c09789 100644 --- a/src/bus-proxyd/test-bus-xml-policy.c +++ b/src/bus-proxyd/test-bus-xml-policy.c @@ -21,7 +21,7 @@ #include #include -#include "sd-bus.h" +#include #include "alloc-util.h" #include "bus-xml-policy.h" diff --git a/src/cgls/Makefile b/src/cgls/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/cgls/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/cgls/cgls.c b/src/cgls/cgls.c deleted file mode 100644 index b839fadd04..0000000000 --- a/src/cgls/cgls.c +++ /dev/null @@ -1,287 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "cgroup-show.h" -#include "cgroup-util.h" -#include "fileio.h" -#include "log.h" -#include "output-mode.h" -#include "pager.h" -#include "path-util.h" -#include "unit-name.h" -#include "util.h" - -static bool arg_no_pager = false; -static bool arg_kernel_threads = false; -static bool arg_all = false; -static int arg_full = -1; -static char* arg_machine = NULL; - -static void help(void) { - printf("%s [OPTIONS...] [CGROUP...]\n\n" - "Recursively show control group contents.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " -a --all Show all groups, including empty\n" - " -l --full Do not ellipsize output\n" - " -k Include kernel threads in output\n" - " -M --machine= Show container\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_NO_PAGER = 0x100, - ARG_VERSION, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, 'l' }, - { "machine", required_argument, NULL, 'M' }, - {} - }; - - int c; - - assert(argc >= 1); - assert(argv); - - while ((c = getopt_long(argc, argv, "hkalM:", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case 'a': - arg_all = true; - break; - - case 'l': - arg_full = true; - break; - - case 'k': - arg_kernel_threads = true; - break; - - case 'M': - arg_machine = optarg; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -static int get_cgroup_root(char **ret) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ char *unit = NULL, *path = NULL; - const char *m; - int r; - - if (!arg_machine) { - r = cg_get_root_path(ret); - if (r == -ENOMEDIUM) - return log_error_errno(r, "Failed to get root control group path: No cgroup filesystem mounted on /sys/fs/cgroup"); - else if (r < 0) - return log_error_errno(r, "Failed to get root control group path: %m"); - - return 0; - } - - m = strjoina("/run/systemd/machines/", arg_machine); - r = parse_env_file(m, NEWLINE, "SCOPE", &unit, NULL); - if (r < 0) - return log_error_errno(r, "Failed to load machine data: %m"); - - path = unit_dbus_path_from_name(unit); - if (!path) - return log_oom(); - - r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, false, &bus); - if (r < 0) - return log_error_errno(r, "Failed to create bus connection: %m"); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - path, - unit_dbus_interface_from_name(unit), - "ControlGroup", - &error, - ret); - if (r < 0) - return log_error_errno(r, "Failed to query unit control group path: %s", bus_error_message(&error, r)); - - return 0; -} - -static void show_cg_info(const char *controller, const char *path) { - - if (cg_unified() <= 0 && controller && !streq(controller, SYSTEMD_CGROUP_CONTROLLER)) - printf("Controller %s; ", controller); - - printf("Control group %s:\n", isempty(path) ? "/" : path); - fflush(stdout); -} - -int main(int argc, char *argv[]) { - int r, output_flags; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (!arg_no_pager) { - r = pager_open(false); - if (r > 0 && arg_full < 0) - arg_full = true; - } - - output_flags = - arg_all * OUTPUT_SHOW_ALL | - (arg_full > 0) * OUTPUT_FULL_WIDTH; - - if (optind < argc) { - _cleanup_free_ char *root = NULL; - int i; - - r = get_cgroup_root(&root); - if (r < 0) - goto finish; - - for (i = optind; i < argc; i++) { - int q; - - if (path_startswith(argv[i], "/sys/fs/cgroup")) { - - printf("Directory %s:\n", argv[i]); - fflush(stdout); - - q = show_cgroup_by_path(argv[i], NULL, 0, arg_kernel_threads, output_flags); - } else { - _cleanup_free_ char *c = NULL, *p = NULL, *j = NULL; - const char *controller, *path; - - r = cg_split_spec(argv[i], &c, &p); - if (r < 0) { - log_error_errno(r, "Failed to split argument %s: %m", argv[i]); - goto finish; - } - - controller = c ?: SYSTEMD_CGROUP_CONTROLLER; - if (p) { - j = strjoin(root, "/", p, NULL); - if (!j) { - r = log_oom(); - goto finish; - } - - path_kill_slashes(j); - path = j; - } else - path = root; - - show_cg_info(controller, path); - - q = show_cgroup(controller, path, NULL, 0, arg_kernel_threads, output_flags); - } - - if (q < 0) - r = q; - } - - } else { - bool done = false; - - if (!arg_machine) { - _cleanup_free_ char *cwd = NULL; - - cwd = get_current_dir_name(); - if (!cwd) { - r = log_error_errno(errno, "Cannot determine current working directory: %m"); - goto finish; - } - - if (path_startswith(cwd, "/sys/fs/cgroup")) { - printf("Working directory %s:\n", cwd); - fflush(stdout); - - r = show_cgroup_by_path(cwd, NULL, 0, arg_kernel_threads, output_flags); - done = true; - } - } - - if (!done) { - _cleanup_free_ char *root = NULL; - - r = get_cgroup_root(&root); - if (r < 0) - goto finish; - - show_cg_info(SYSTEMD_CGROUP_CONTROLLER, root); - - printf("-.slice\n"); - r = show_cgroup(SYSTEMD_CGROUP_CONTROLLER, root, NULL, 0, arg_kernel_threads, output_flags); - } - } - - if (r < 0) - log_error_errno(r, "Failed to list cgroup tree: %m"); - -finish: - pager_close(); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/cgroups-agent/Makefile b/src/cgroups-agent/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/cgroups-agent/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/cgroups-agent/cgroups-agent.c b/src/cgroups-agent/cgroups-agent.c deleted file mode 100644 index aadfba0707..0000000000 --- a/src/cgroups-agent/cgroups-agent.c +++ /dev/null @@ -1,65 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "sd-bus.h" - -#include "bus-util.h" -#include "log.h" - -int main(int argc, char *argv[]) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - if (argc != 2) { - log_error("Incorrect number of arguments."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - /* We send this event to the private D-Bus socket and then the - * system instance will forward this to the system bus. We do - * this to avoid an activation loop when we start dbus when we - * are called when the dbus service is shut down. */ - - r = bus_connect_system_systemd(&bus); - if (r < 0) { - /* If we couldn't connect we assume this was triggered - * while systemd got restarted/transitioned from - * initrd to the system, so let's ignore this */ - log_debug_errno(r, "Failed to get D-Bus connection: %m"); - return EXIT_FAILURE; - } - - r = sd_bus_emit_signal(bus, - "/org/freedesktop/systemd1/agent", - "org.freedesktop.systemd1.Agent", - "Released", - "s", argv[1]); - if (r < 0) { - log_debug_errno(r, "Failed to send signal message on private connection: %m"); - return EXIT_FAILURE; - } - - return EXIT_SUCCESS; -} diff --git a/src/cgtop/Makefile b/src/cgtop/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/cgtop/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/cgtop/cgtop.c b/src/cgtop/cgtop.c deleted file mode 100644 index 60d6da3246..0000000000 --- a/src/cgtop/cgtop.c +++ /dev/null @@ -1,1097 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "hashmap.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "stdio-util.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "util.h" - -typedef struct Group { - char *path; - - bool n_tasks_valid:1; - bool cpu_valid:1; - bool memory_valid:1; - bool io_valid:1; - - uint64_t n_tasks; - - unsigned cpu_iteration; - nsec_t cpu_usage; - nsec_t cpu_timestamp; - double cpu_fraction; - - uint64_t memory; - - unsigned io_iteration; - uint64_t io_input, io_output; - nsec_t io_timestamp; - uint64_t io_input_bps, io_output_bps; -} Group; - -static unsigned arg_depth = 3; -static unsigned arg_iterations = (unsigned) -1; -static bool arg_batch = false; -static bool arg_raw = false; -static usec_t arg_delay = 1*USEC_PER_SEC; -static char* arg_machine = NULL; - -enum { - COUNT_PIDS, - COUNT_USERSPACE_PROCESSES, - COUNT_ALL_PROCESSES, -} arg_count = COUNT_PIDS; -static bool arg_recursive = true; - -static enum { - ORDER_PATH, - ORDER_TASKS, - ORDER_CPU, - ORDER_MEMORY, - ORDER_IO, -} arg_order = ORDER_CPU; - -static enum { - CPU_PERCENT, - CPU_TIME, -} arg_cpu_type = CPU_PERCENT; - -static void group_free(Group *g) { - assert(g); - - free(g->path); - free(g); -} - -static void group_hashmap_clear(Hashmap *h) { - Group *g; - - while ((g = hashmap_steal_first(h))) - group_free(g); -} - -static void group_hashmap_free(Hashmap *h) { - group_hashmap_clear(h); - hashmap_free(h); -} - -static const char *maybe_format_bytes(char *buf, size_t l, bool is_valid, uint64_t t) { - if (!is_valid) - return "-"; - if (arg_raw) { - snprintf(buf, l, "%jd", t); - return buf; - } - return format_bytes(buf, l, t); -} - -static int process( - const char *controller, - const char *path, - Hashmap *a, - Hashmap *b, - unsigned iteration, - Group **ret) { - - Group *g; - int r; - - assert(controller); - assert(path); - assert(a); - - g = hashmap_get(a, path); - if (!g) { - g = hashmap_get(b, path); - if (!g) { - g = new0(Group, 1); - if (!g) - return -ENOMEM; - - g->path = strdup(path); - if (!g->path) { - group_free(g); - return -ENOMEM; - } - - r = hashmap_put(a, g->path, g); - if (r < 0) { - group_free(g); - return r; - } - } else { - r = hashmap_move_one(a, b, path); - if (r < 0) - return r; - - g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false; - } - } - - if (streq(controller, SYSTEMD_CGROUP_CONTROLLER) && IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES)) { - _cleanup_fclose_ FILE *f = NULL; - pid_t pid; - - r = cg_enumerate_processes(controller, path, &f); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - g->n_tasks = 0; - while (cg_read_pid(f, &pid) > 0) { - - if (arg_count == COUNT_USERSPACE_PROCESSES && is_kernel_thread(pid) > 0) - continue; - - g->n_tasks++; - } - - if (g->n_tasks > 0) - g->n_tasks_valid = true; - - } else if (streq(controller, "pids") && arg_count == COUNT_PIDS) { - _cleanup_free_ char *p = NULL, *v = NULL; - - r = cg_get_path(controller, path, "pids.current", &p); - if (r < 0) - return r; - - r = read_one_line_file(p, &v); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - r = safe_atou64(v, &g->n_tasks); - if (r < 0) - return r; - - if (g->n_tasks > 0) - g->n_tasks_valid = true; - - } else if (streq(controller, "cpuacct") && cg_unified() <= 0) { - _cleanup_free_ char *p = NULL, *v = NULL; - uint64_t new_usage; - nsec_t timestamp; - - r = cg_get_path(controller, path, "cpuacct.usage", &p); - if (r < 0) - return r; - - r = read_one_line_file(p, &v); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - r = safe_atou64(v, &new_usage); - if (r < 0) - return r; - - timestamp = now_nsec(CLOCK_MONOTONIC); - - if (g->cpu_iteration == iteration - 1 && - (nsec_t) new_usage > g->cpu_usage) { - - nsec_t x, y; - - x = timestamp - g->cpu_timestamp; - if (x < 1) - x = 1; - - y = (nsec_t) new_usage - g->cpu_usage; - g->cpu_fraction = (double) y / (double) x; - g->cpu_valid = true; - } - - g->cpu_usage = (nsec_t) new_usage; - g->cpu_timestamp = timestamp; - g->cpu_iteration = iteration; - - } else if (streq(controller, "memory")) { - _cleanup_free_ char *p = NULL, *v = NULL; - - if (cg_unified() <= 0) - r = cg_get_path(controller, path, "memory.usage_in_bytes", &p); - else - r = cg_get_path(controller, path, "memory.current", &p); - if (r < 0) - return r; - - r = read_one_line_file(p, &v); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - r = safe_atou64(v, &g->memory); - if (r < 0) - return r; - - if (g->memory > 0) - g->memory_valid = true; - - } else if (streq(controller, "blkio") && cg_unified() <= 0) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *p = NULL; - uint64_t wr = 0, rd = 0; - nsec_t timestamp; - - r = cg_get_path(controller, path, "blkio.io_service_bytes", &p); - if (r < 0) - return r; - - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return 0; - return -errno; - } - - for (;;) { - char line[LINE_MAX], *l; - uint64_t k, *q; - - if (!fgets(line, sizeof(line), f)) - break; - - l = strstrip(line); - l += strcspn(l, WHITESPACE); - l += strspn(l, WHITESPACE); - - if (first_word(l, "Read")) { - l += 4; - q = &rd; - } else if (first_word(l, "Write")) { - l += 5; - q = ≀ - } else - continue; - - l += strspn(l, WHITESPACE); - r = safe_atou64(l, &k); - if (r < 0) - continue; - - *q += k; - } - - timestamp = now_nsec(CLOCK_MONOTONIC); - - if (g->io_iteration == iteration - 1) { - uint64_t x, yr, yw; - - x = (uint64_t) (timestamp - g->io_timestamp); - if (x < 1) - x = 1; - - if (rd > g->io_input) - yr = rd - g->io_input; - else - yr = 0; - - if (wr > g->io_output) - yw = wr - g->io_output; - else - yw = 0; - - if (yr > 0 || yw > 0) { - g->io_input_bps = (yr * 1000000000ULL) / x; - g->io_output_bps = (yw * 1000000000ULL) / x; - g->io_valid = true; - } - } - - g->io_input = rd; - g->io_output = wr; - g->io_timestamp = timestamp; - g->io_iteration = iteration; - } - - if (ret) - *ret = g; - - return 0; -} - -static int refresh_one( - const char *controller, - const char *path, - Hashmap *a, - Hashmap *b, - unsigned iteration, - unsigned depth, - Group **ret) { - - _cleanup_closedir_ DIR *d = NULL; - Group *ours; - int r; - - assert(controller); - assert(path); - assert(a); - - if (depth > arg_depth) - return 0; - - r = process(controller, path, a, b, iteration, &ours); - if (r < 0) - return r; - - r = cg_enumerate_subgroups(controller, path, &d); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - for (;;) { - _cleanup_free_ char *fn = NULL, *p = NULL; - Group *child = NULL; - - r = cg_read_subgroup(d, &fn); - if (r < 0) - return r; - if (r == 0) - break; - - p = strjoin(path, "/", fn, NULL); - if (!p) - return -ENOMEM; - - path_kill_slashes(p); - - r = refresh_one(controller, p, a, b, iteration, depth + 1, &child); - if (r < 0) - return r; - - if (arg_recursive && - IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES) && - child && - child->n_tasks_valid && - streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { - - /* Recursively sum up processes */ - - if (ours->n_tasks_valid) - ours->n_tasks += child->n_tasks; - else { - ours->n_tasks = child->n_tasks; - ours->n_tasks_valid = true; - } - } - } - - if (ret) - *ret = ours; - - return 1; -} - -static int refresh(const char *root, Hashmap *a, Hashmap *b, unsigned iteration) { - int r; - - assert(a); - - r = refresh_one(SYSTEMD_CGROUP_CONTROLLER, root, a, b, iteration, 0, NULL); - if (r < 0) - return r; - r = refresh_one("cpuacct", root, a, b, iteration, 0, NULL); - if (r < 0) - return r; - r = refresh_one("memory", root, a, b, iteration, 0, NULL); - if (r < 0) - return r; - r = refresh_one("blkio", root, a, b, iteration, 0, NULL); - if (r < 0) - return r; - r = refresh_one("pids", root, a, b, iteration, 0, NULL); - if (r < 0) - return r; - - return 0; -} - -static int group_compare(const void*a, const void *b) { - const Group *x = *(Group**)a, *y = *(Group**)b; - - if (arg_order != ORDER_TASKS || arg_recursive) { - /* Let's make sure that the parent is always before - * the child. Except when ordering by tasks and - * recursive summing is off, since that is actually - * not accumulative for all children. */ - - if (path_startswith(y->path, x->path)) - return -1; - if (path_startswith(x->path, y->path)) - return 1; - } - - switch (arg_order) { - - case ORDER_PATH: - break; - - case ORDER_CPU: - if (arg_cpu_type == CPU_PERCENT) { - if (x->cpu_valid && y->cpu_valid) { - if (x->cpu_fraction > y->cpu_fraction) - return -1; - else if (x->cpu_fraction < y->cpu_fraction) - return 1; - } else if (x->cpu_valid) - return -1; - else if (y->cpu_valid) - return 1; - } else { - if (x->cpu_usage > y->cpu_usage) - return -1; - else if (x->cpu_usage < y->cpu_usage) - return 1; - } - - break; - - case ORDER_TASKS: - if (x->n_tasks_valid && y->n_tasks_valid) { - if (x->n_tasks > y->n_tasks) - return -1; - else if (x->n_tasks < y->n_tasks) - return 1; - } else if (x->n_tasks_valid) - return -1; - else if (y->n_tasks_valid) - return 1; - - break; - - case ORDER_MEMORY: - if (x->memory_valid && y->memory_valid) { - if (x->memory > y->memory) - return -1; - else if (x->memory < y->memory) - return 1; - } else if (x->memory_valid) - return -1; - else if (y->memory_valid) - return 1; - - break; - - case ORDER_IO: - if (x->io_valid && y->io_valid) { - if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps) - return -1; - else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps) - return 1; - } else if (x->io_valid) - return -1; - else if (y->io_valid) - return 1; - } - - return path_compare(x->path, y->path); -} - -static void display(Hashmap *a) { - Iterator i; - Group *g; - Group **array; - signed path_columns; - unsigned rows, n = 0, j, maxtcpu = 0, maxtpath = 3; /* 3 for ellipsize() to work properly */ - char buffer[MAX3(21, FORMAT_BYTES_MAX, FORMAT_TIMESPAN_MAX)]; - - assert(a); - - if (on_tty()) - fputs(ANSI_HOME_CLEAR, stdout); - - array = alloca(sizeof(Group*) * hashmap_size(a)); - - HASHMAP_FOREACH(g, a, i) - if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid) - array[n++] = g; - - qsort_safe(array, n, sizeof(Group*), group_compare); - - /* Find the longest names in one run */ - for (j = 0; j < n; j++) { - unsigned cputlen, pathtlen; - - format_timespan(buffer, sizeof(buffer), (usec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0); - cputlen = strlen(buffer); - maxtcpu = MAX(maxtcpu, cputlen); - - pathtlen = strlen(array[j]->path); - maxtpath = MAX(maxtpath, pathtlen); - } - - if (arg_cpu_type == CPU_PERCENT) - xsprintf(buffer, "%6s", "%CPU"); - else - xsprintf(buffer, "%*s", maxtcpu, "CPU Time"); - - rows = lines(); - if (rows <= 10) - rows = 10; - - if (on_tty()) { - const char *on, *off; - - path_columns = columns() - 36 - strlen(buffer); - if (path_columns < 10) - path_columns = 10; - - on = ansi_highlight_underline(); - off = ansi_underline(); - - printf("%s%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s%s\n", - ansi_underline(), - arg_order == ORDER_PATH ? on : "", path_columns, "Control Group", - arg_order == ORDER_PATH ? off : "", - arg_order == ORDER_TASKS ? on : "", arg_count == COUNT_PIDS ? "Tasks" : arg_count == COUNT_USERSPACE_PROCESSES ? "Procs" : "Proc+", - arg_order == ORDER_TASKS ? off : "", - arg_order == ORDER_CPU ? on : "", buffer, - arg_order == ORDER_CPU ? off : "", - arg_order == ORDER_MEMORY ? on : "", "Memory", - arg_order == ORDER_MEMORY ? off : "", - arg_order == ORDER_IO ? on : "", "Input/s", - arg_order == ORDER_IO ? off : "", - arg_order == ORDER_IO ? on : "", "Output/s", - arg_order == ORDER_IO ? off : "", - ansi_normal()); - } else - path_columns = maxtpath; - - for (j = 0; j < n; j++) { - _cleanup_free_ char *ellipsized = NULL; - const char *path; - - if (on_tty() && j + 6 > rows) - break; - - g = array[j]; - - path = isempty(g->path) ? "/" : g->path; - ellipsized = ellipsize(path, path_columns, 33); - printf("%-*s", path_columns, ellipsized ?: path); - - if (g->n_tasks_valid) - printf(" %7" PRIu64, g->n_tasks); - else - fputs(" -", stdout); - - if (arg_cpu_type == CPU_PERCENT) { - if (g->cpu_valid) - printf(" %6.1f", g->cpu_fraction*100); - else - fputs(" -", stdout); - } else - printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (usec_t) (g->cpu_usage / NSEC_PER_USEC), 0)); - - printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->memory_valid, g->memory)); - printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_input_bps)); - printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_output_bps)); - - putchar('\n'); - } -} - -static void help(void) { - printf("%s [OPTIONS...]\n\n" - "Show top control groups by their resource usage.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -p --order=path Order by path\n" - " -t --order=tasks Order by number of tasks/processes\n" - " -c --order=cpu Order by CPU load (default)\n" - " -m --order=memory Order by memory load\n" - " -i --order=io Order by IO load\n" - " -r --raw Provide raw (not human-readable) numbers\n" - " --cpu=percentage Show CPU usage as percentage (default)\n" - " --cpu=time Show CPU usage as time\n" - " -P Count userspace processes instead of tasks (excl. kernel)\n" - " -k Count all processes instead of tasks (incl. kernel)\n" - " --recursive=BOOL Sum up process count recursively\n" - " -d --delay=DELAY Delay between updates\n" - " -n --iterations=N Run for N iterations before exiting\n" - " -b --batch Run in batch mode, accepting no input\n" - " --depth=DEPTH Maximum traversal depth (default: %u)\n" - " -M --machine= Show container\n" - , program_invocation_short_name, arg_depth); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_DEPTH, - ARG_CPU_TYPE, - ARG_ORDER, - ARG_RECURSIVE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "delay", required_argument, NULL, 'd' }, - { "iterations", required_argument, NULL, 'n' }, - { "batch", no_argument, NULL, 'b' }, - { "raw", no_argument, NULL, 'r' }, - { "depth", required_argument, NULL, ARG_DEPTH }, - { "cpu", optional_argument, NULL, ARG_CPU_TYPE }, - { "order", required_argument, NULL, ARG_ORDER }, - { "recursive", required_argument, NULL, ARG_RECURSIVE }, - { "machine", required_argument, NULL, 'M' }, - {} - }; - - bool recursive_unset = false; - int c, r; - - assert(argc >= 1); - assert(argv); - - while ((c = getopt_long(argc, argv, "hptcmin:brd:kPM:", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_CPU_TYPE: - if (optarg) { - if (streq(optarg, "time")) - arg_cpu_type = CPU_TIME; - else if (streq(optarg, "percentage")) - arg_cpu_type = CPU_PERCENT; - else { - log_error("Unknown argument to --cpu=: %s", optarg); - return -EINVAL; - } - } else - arg_cpu_type = CPU_TIME; - - break; - - case ARG_DEPTH: - r = safe_atou(optarg, &arg_depth); - if (r < 0) { - log_error("Failed to parse depth parameter."); - return -EINVAL; - } - - break; - - case 'd': - r = parse_sec(optarg, &arg_delay); - if (r < 0 || arg_delay <= 0) { - log_error("Failed to parse delay parameter."); - return -EINVAL; - } - - break; - - case 'n': - r = safe_atou(optarg, &arg_iterations); - if (r < 0) { - log_error("Failed to parse iterations parameter."); - return -EINVAL; - } - - break; - - case 'b': - arg_batch = true; - break; - - case 'r': - arg_raw = true; - break; - - case 'p': - arg_order = ORDER_PATH; - break; - - case 't': - arg_order = ORDER_TASKS; - break; - - case 'c': - arg_order = ORDER_CPU; - break; - - case 'm': - arg_order = ORDER_MEMORY; - break; - - case 'i': - arg_order = ORDER_IO; - break; - - case ARG_ORDER: - if (streq(optarg, "path")) - arg_order = ORDER_PATH; - else if (streq(optarg, "tasks")) - arg_order = ORDER_TASKS; - else if (streq(optarg, "cpu")) - arg_order = ORDER_CPU; - else if (streq(optarg, "memory")) - arg_order = ORDER_MEMORY; - else if (streq(optarg, "io")) - arg_order = ORDER_IO; - else { - log_error("Invalid argument to --order=: %s", optarg); - return -EINVAL; - } - break; - - case 'k': - arg_count = COUNT_ALL_PROCESSES; - break; - - case 'P': - arg_count = COUNT_USERSPACE_PROCESSES; - break; - - case ARG_RECURSIVE: - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --recursive= argument: %s", optarg); - return r; - } - - arg_recursive = r; - recursive_unset = r == 0; - break; - - case 'M': - arg_machine = optarg; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind < argc) { - log_error("Too many arguments."); - return -EINVAL; - } - - if (recursive_unset && arg_count == COUNT_PIDS) { - log_error("Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k."); - return -EINVAL; - } - - return 1; -} - -static const char* counting_what(void) { - if (arg_count == COUNT_PIDS) - return "tasks"; - else if (arg_count == COUNT_ALL_PROCESSES) - return "all processes (incl. kernel)"; - else - return "userspace processes (excl. kernel)"; -} - -static int get_cgroup_root(char **ret) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ char *unit = NULL, *path = NULL; - const char *m; - int r; - - if (!arg_machine) { - r = cg_get_root_path(ret); - if (r < 0) - return log_error_errno(r, "Failed to get root control group path: %m"); - - return 0; - } - - m = strjoina("/run/systemd/machines/", arg_machine); - r = parse_env_file(m, NEWLINE, "SCOPE", &unit, NULL); - if (r < 0) - return log_error_errno(r, "Failed to load machine data: %m"); - - path = unit_dbus_path_from_name(unit); - if (!path) - return log_oom(); - - r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, false, &bus); - if (r < 0) - return log_error_errno(r, "Failed to create bus connection: %m"); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - path, - unit_dbus_interface_from_name(unit), - "ControlGroup", - &error, - ret); - if (r < 0) - return log_error_errno(r, "Failed to query unit control group path: %s", bus_error_message(&error, r)); - - return 0; -} - -int main(int argc, char *argv[]) { - int r; - Hashmap *a = NULL, *b = NULL; - unsigned iteration = 0; - usec_t last_refresh = 0; - bool quit = false, immediate_refresh = false; - _cleanup_free_ char *root = NULL; - CGroupMask mask; - - log_parse_environment(); - log_open(); - - r = cg_mask_supported(&mask); - if (r < 0) { - log_error_errno(r, "Failed to determine supported controllers: %m"); - goto finish; - } - - arg_count = (mask & CGROUP_MASK_PIDS) ? COUNT_PIDS : COUNT_USERSPACE_PROCESSES; - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = get_cgroup_root(&root); - if (r < 0) { - log_error_errno(r, "Failed to get root control group path: %m"); - goto finish; - } - - a = hashmap_new(&string_hash_ops); - b = hashmap_new(&string_hash_ops); - if (!a || !b) { - r = log_oom(); - goto finish; - } - - signal(SIGWINCH, columns_lines_cache_reset); - - if (arg_iterations == (unsigned) -1) - arg_iterations = on_tty() ? 0 : 1; - - while (!quit) { - Hashmap *c; - usec_t t; - char key; - char h[FORMAT_TIMESPAN_MAX]; - - t = now(CLOCK_MONOTONIC); - - if (t >= last_refresh + arg_delay || immediate_refresh) { - - r = refresh(root, a, b, iteration++); - if (r < 0) { - log_error_errno(r, "Failed to refresh: %m"); - goto finish; - } - - group_hashmap_clear(b); - - c = a; - a = b; - b = c; - - last_refresh = t; - immediate_refresh = false; - } - - display(b); - - if (arg_iterations && iteration >= arg_iterations) - break; - - if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */ - fputs("\n", stdout); - fflush(stdout); - - if (arg_batch) - (void) usleep(last_refresh + arg_delay - t); - else { - r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL); - if (r == -ETIMEDOUT) - continue; - if (r < 0) { - log_error_errno(r, "Couldn't read key: %m"); - goto finish; - } - } - - if (on_tty()) { /* TTY: Clear any user keystroke */ - fputs("\r \r", stdout); - fflush(stdout); - } - - if (arg_batch) - continue; - - switch (key) { - - case ' ': - immediate_refresh = true; - break; - - case 'q': - quit = true; - break; - - case 'p': - arg_order = ORDER_PATH; - break; - - case 't': - arg_order = ORDER_TASKS; - break; - - case 'c': - arg_order = ORDER_CPU; - break; - - case 'm': - arg_order = ORDER_MEMORY; - break; - - case 'i': - arg_order = ORDER_IO; - break; - - case '%': - arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME; - break; - - case 'k': - arg_count = arg_count != COUNT_ALL_PROCESSES ? COUNT_ALL_PROCESSES : COUNT_PIDS; - fprintf(stdout, "\nCounting: %s.", counting_what()); - fflush(stdout); - sleep(1); - break; - - case 'P': - arg_count = arg_count != COUNT_USERSPACE_PROCESSES ? COUNT_USERSPACE_PROCESSES : COUNT_PIDS; - fprintf(stdout, "\nCounting: %s.", counting_what()); - fflush(stdout); - sleep(1); - break; - - case 'r': - if (arg_count == COUNT_PIDS) - fprintf(stdout, "\n\aCannot toggle recursive counting, not available in task counting mode."); - else { - arg_recursive = !arg_recursive; - fprintf(stdout, "\nRecursive process counting: %s", yes_no(arg_recursive)); - } - fflush(stdout); - sleep(1); - break; - - case '+': - if (arg_delay < USEC_PER_SEC) - arg_delay += USEC_PER_MSEC*250; - else - arg_delay += USEC_PER_SEC; - - fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0)); - fflush(stdout); - sleep(1); - break; - - case '-': - if (arg_delay <= USEC_PER_MSEC*500) - arg_delay = USEC_PER_MSEC*250; - else if (arg_delay < USEC_PER_MSEC*1250) - arg_delay -= USEC_PER_MSEC*250; - else - arg_delay -= USEC_PER_SEC; - - fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0)); - fflush(stdout); - sleep(1); - break; - - case '?': - case 'h': - -#define ON ANSI_HIGHLIGHT -#define OFF ANSI_NORMAL - - fprintf(stdout, - "\t<" ON "p" OFF "> By path; <" ON "t" OFF "> By tasks/procs; <" ON "c" OFF "> By CPU; <" ON "m" OFF "> By memory; <" ON "i" OFF "> By I/O\n" - "\t<" ON "+" OFF "> Inc. delay; <" ON "-" OFF "> Dec. delay; <" ON "%%" OFF "> Toggle time; <" ON "SPACE" OFF "> Refresh\n" - "\t<" ON "P" OFF "> Toggle count userspace processes; <" ON "k" OFF "> Toggle count all processes\n" - "\t<" ON "r" OFF "> Count processes recursively; <" ON "q" OFF "> Quit"); - fflush(stdout); - sleep(3); - break; - - default: - if (key < ' ') - fprintf(stdout, "\nUnknown key '\\x%x'. Ignoring.", key); - else - fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key); - fflush(stdout); - sleep(1); - break; - } - } - - r = 0; - -finish: - group_hashmap_free(a); - group_hashmap_free(b); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/compat-libs/.gitignore b/src/compat-libs/.gitignore deleted file mode 100644 index 662c154cdd..0000000000 --- a/src/compat-libs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/libsystemd-*.pc diff --git a/src/compat-libs/libsystemd-daemon.pc.in b/src/compat-libs/libsystemd-daemon.pc.in deleted file mode 100644 index 847afc9d60..0000000000 --- a/src/compat-libs/libsystemd-daemon.pc.in +++ /dev/null @@ -1,19 +0,0 @@ -# Permission is hereby granted, free of charge, to any person -# obtaining a copy of this software and associated documentation files -# (the "Software"), to deal in the Software without restriction, -# including without limitation the rights to use, copy, modify, merge, -# publish, distribute, sublicense, and/or sell copies of the Software, -# and to permit persons to whom the Software is furnished to do so, -# subject to the following conditions: - -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: systemd -Description: systemd Daemon Utility Library - deprecated -URL: @PACKAGE_URL@ -Version: @PACKAGE_VERSION@ -Libs: -L${libdir} -lsystemd -Cflags: -I${includedir} diff --git a/src/compat-libs/libsystemd-daemon.sym b/src/compat-libs/libsystemd-daemon.sym deleted file mode 100644 index f440238931..0000000000 --- a/src/compat-libs/libsystemd-daemon.sym +++ /dev/null @@ -1,27 +0,0 @@ -/*** - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation files - (the "Software"), to deal in the Software without restriction, - including without limitation the rights to use, copy, modify, merge, - publish, distribute, sublicense, and/or sell copies of the Software, - and to permit persons to whom the Software is furnished to do so, - subject to the following conditions: -***/ - -/* Original symbols from systemd v31 */ - -LIBSYSTEMD_DAEMON_31 { -global: - sd_booted; - sd_is_fifo; - sd_is_mq; - sd_is_socket; - sd_is_socket_inet; - sd_is_socket_unix; - sd_is_special; - sd_listen_fds; - sd_notify; - sd_notifyf; -local: - *; -}; diff --git a/src/compat-libs/libsystemd-id128.pc.in b/src/compat-libs/libsystemd-id128.pc.in deleted file mode 100644 index 80f8fee6c3..0000000000 --- a/src/compat-libs/libsystemd-id128.pc.in +++ /dev/null @@ -1,18 +0,0 @@ -# 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. - -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: systemd -Description: systemd 128 Bit ID Utility Library - deprecated -URL: @PACKAGE_URL@ -Version: @PACKAGE_VERSION@ -Libs: -L${libdir} -lsystemd -Cflags: -I${includedir} diff --git a/src/compat-libs/libsystemd-id128.sym b/src/compat-libs/libsystemd-id128.sym deleted file mode 100644 index 604c0026c6..0000000000 --- a/src/compat-libs/libsystemd-id128.sym +++ /dev/null @@ -1,21 +0,0 @@ -/*** - 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. -***/ - -/* Original symbols from systemd v38 */ - -LIBSYSTEMD_ID128_38 { -global: - sd_id128_to_string; - sd_id128_from_string; - sd_id128_randomize; - sd_id128_get_machine; - sd_id128_get_boot; -local: - *; -}; diff --git a/src/compat-libs/libsystemd-journal.pc.in b/src/compat-libs/libsystemd-journal.pc.in deleted file mode 100644 index 395f71005b..0000000000 --- a/src/compat-libs/libsystemd-journal.pc.in +++ /dev/null @@ -1,19 +0,0 @@ -# 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. - -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: systemd -Description: systemd Journal Utility Library - deprecated -URL: @PACKAGE_URL@ -Version: @PACKAGE_VERSION@ -Requires: libsystemd = @PACKAGE_VERSION@ -Libs: -L${libdir} -lsystemd -Cflags: -I${includedir} diff --git a/src/compat-libs/libsystemd-journal.sym b/src/compat-libs/libsystemd-journal.sym deleted file mode 100644 index 4eb15910d2..0000000000 --- a/src/compat-libs/libsystemd-journal.sym +++ /dev/null @@ -1,111 +0,0 @@ -/*** - 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. -***/ - -/* Original symbols from systemd v38 */ - -LIBSYSTEMD_JOURNAL_38 { -global: - sd_journal_print; - sd_journal_printv; - sd_journal_send; - sd_journal_sendv; - sd_journal_stream_fd; - sd_journal_open; - sd_journal_close; - sd_journal_previous; - sd_journal_next; - sd_journal_previous_skip; - sd_journal_next_skip; - sd_journal_get_realtime_usec; - sd_journal_get_monotonic_usec; - sd_journal_get_data; - sd_journal_enumerate_data; - sd_journal_restart_data; - sd_journal_add_match; - sd_journal_flush_matches; - sd_journal_seek_head; - sd_journal_seek_tail; - sd_journal_seek_monotonic_usec; - sd_journal_seek_realtime_usec; - sd_journal_seek_cursor; - sd_journal_get_cursor; - sd_journal_get_fd; - sd_journal_process; -local: - *; -}; - -LIBSYSTEMD_JOURNAL_183 { -global: - sd_journal_print_with_location; - sd_journal_printv_with_location; - sd_journal_send_with_location; - sd_journal_sendv_with_location; -} LIBSYSTEMD_JOURNAL_38; - -LIBSYSTEMD_JOURNAL_184 { -global: - sd_journal_get_cutoff_realtime_usec; - sd_journal_get_cutoff_monotonic_usec; -} LIBSYSTEMD_JOURNAL_183; - -LIBSYSTEMD_JOURNAL_187 { -global: - sd_journal_wait; - sd_journal_open_directory; - sd_journal_add_disjunction; -} LIBSYSTEMD_JOURNAL_184; - -LIBSYSTEMD_JOURNAL_188 { -global: - sd_journal_perror; - sd_journal_perror_with_location; -} LIBSYSTEMD_JOURNAL_187; - -LIBSYSTEMD_JOURNAL_190 { -global: - sd_journal_get_usage; -} LIBSYSTEMD_JOURNAL_188; - -LIBSYSTEMD_JOURNAL_195 { -global: - sd_journal_test_cursor; - sd_journal_query_unique; - sd_journal_enumerate_unique; - sd_journal_restart_unique; -} LIBSYSTEMD_JOURNAL_190; - -LIBSYSTEMD_JOURNAL_196 { -global: - sd_journal_get_catalog; - sd_journal_get_catalog_for_message_id; - sd_journal_set_data_threshold; - sd_journal_get_data_threshold; -} LIBSYSTEMD_JOURNAL_195; - -LIBSYSTEMD_JOURNAL_198 { -global: - sd_journal_reliable_fd; -} LIBSYSTEMD_JOURNAL_196; - -LIBSYSTEMD_JOURNAL_201 { -global: - sd_journal_get_events; - sd_journal_get_timeout; -} LIBSYSTEMD_JOURNAL_198; - -LIBSYSTEMD_JOURNAL_202 { -global: - sd_journal_add_conjunction; -} LIBSYSTEMD_JOURNAL_201; - -LIBSYSTEMD_JOURNAL_205 { -global: - sd_journal_open_files; -} LIBSYSTEMD_JOURNAL_202; diff --git a/src/compat-libs/libsystemd-login.pc.in b/src/compat-libs/libsystemd-login.pc.in deleted file mode 100644 index db3f79c99a..0000000000 --- a/src/compat-libs/libsystemd-login.pc.in +++ /dev/null @@ -1,18 +0,0 @@ -# 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. - -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: systemd -Description: systemd Login Utility Library - deprecated -URL: @PACKAGE_URL@ -Version: @PACKAGE_VERSION@ -Libs: -L${libdir} -lsystemd -Cflags: -I${includedir} diff --git a/src/compat-libs/libsystemd-login.sym b/src/compat-libs/libsystemd-login.sym deleted file mode 100644 index 54aa91c609..0000000000 --- a/src/compat-libs/libsystemd-login.sym +++ /dev/null @@ -1,87 +0,0 @@ -/*** - 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. -***/ - -/* Original symbols from systemd v31 */ - -LIBSYSTEMD_LOGIN_31 { -global: - sd_get_seats; - sd_get_sessions; - sd_get_uids; - sd_login_monitor_flush; - sd_login_monitor_get_fd; - sd_login_monitor_new; - sd_login_monitor_unref; - sd_pid_get_owner_uid; - sd_pid_get_session; - sd_seat_can_multi_session; - sd_seat_get_active; - sd_seat_get_sessions; - sd_session_get_seat; - sd_session_get_uid; - sd_session_is_active; - sd_uid_get_seats; - sd_uid_get_sessions; - sd_uid_get_state; - sd_uid_is_on_seat; -local: - *; -}; - -LIBSYSTEMD_LOGIN_38 { -global: - sd_pid_get_unit; - sd_session_get_service; -} LIBSYSTEMD_LOGIN_31; - -LIBSYSTEMD_LOGIN_43 { -global: - sd_session_get_type; - sd_session_get_class; - sd_session_get_display; -} LIBSYSTEMD_LOGIN_38; - -LIBSYSTEMD_LOGIN_186 { -global: - sd_session_get_state; - sd_seat_can_tty; - sd_seat_can_graphical; -} LIBSYSTEMD_LOGIN_43; - -LIBSYSTEMD_LOGIN_198 { -global: - sd_session_get_tty; -} LIBSYSTEMD_LOGIN_186; - -LIBSYSTEMD_LOGIN_201 { -global: - sd_login_monitor_get_events; - sd_login_monitor_get_timeout; -} LIBSYSTEMD_LOGIN_198; - -LIBSYSTEMD_LOGIN_202 { -global: - sd_pid_get_user_unit; - sd_pid_get_machine_name; -} LIBSYSTEMD_LOGIN_201; - -LIBSYSTEMD_LOGIN_203 { -global: - sd_get_machine_names; -} LIBSYSTEMD_LOGIN_202; - -LIBSYSTEMD_LOGIN_205 { -global: - sd_pid_get_slice; -} LIBSYSTEMD_LOGIN_203; - -LIBSYSTEMD_LOGIN_207 { -global: - sd_session_get_vt; -} LIBSYSTEMD_LOGIN_205; diff --git a/src/compat-libs/linkwarning.h b/src/compat-libs/linkwarning.h deleted file mode 100644 index 79ece9e7d1..0000000000 --- a/src/compat-libs/linkwarning.h +++ /dev/null @@ -1,35 +0,0 @@ -/*** - This file is part of systemd, but is heavily based on - glibc's libc-symbols.h. - - Copyright (C) 1995-1998,2000-2006,2008,2009 Free Software Foundation, Inc - - 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 . -***/ - -#pragma once - -#define __make_section_unallocated(section_string) \ - asm (".section " section_string "\n\t.previous"); - -#define __sec_comment "\n#APP\n\t#" - -#define link_warning(symbol, msg) \ - __make_section_unallocated (".gnu.warning." #symbol) \ - static const char __evoke_link_warning_##symbol[] \ - __attribute__ ((used, section (".gnu.warning." #symbol __sec_comment))) \ - = msg - -#define obsolete_lib(name, lib) \ - link_warning(name, #name " was moved to libsystemd. Do not use " #lib ".") diff --git a/src/core/.gitignore b/src/core/.gitignore deleted file mode 100644 index 465b4fcc20..0000000000 --- a/src/core/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/macros.systemd -/triggers.systemd -/systemd.pc diff --git a/src/core/Makefile b/src/core/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/core/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/core/audit-fd.c b/src/core/audit-fd.c deleted file mode 100644 index 76afe3fe15..0000000000 --- a/src/core/audit-fd.c +++ /dev/null @@ -1,73 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - - -#include - -#include "audit-fd.h" - -#ifdef HAVE_AUDIT - -#include -#include - -#include "fd-util.h" -#include "log.h" -#include "util.h" - -static bool initialized = false; -static int audit_fd; - -int get_audit_fd(void) { - - if (!initialized) { - audit_fd = audit_open(); - - if (audit_fd < 0) { - if (errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT) - log_error_errno(errno, "Failed to connect to audit log: %m"); - - audit_fd = errno ? -errno : -EINVAL; - } - - initialized = true; - } - - return audit_fd; -} - -void close_audit_fd(void) { - - if (initialized && audit_fd >= 0) - safe_close(audit_fd); - - initialized = true; - audit_fd = -ECONNRESET; -} - -#else - -int get_audit_fd(void) { - return -EAFNOSUPPORT; -} - -void close_audit_fd(void) { -} - -#endif diff --git a/src/core/audit-fd.h b/src/core/audit-fd.h deleted file mode 100644 index 0eccb59210..0000000000 --- a/src/core/audit-fd.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -int get_audit_fd(void); -void close_audit_fd(void); diff --git a/src/core/automount.c b/src/core/automount.c deleted file mode 100644 index 5dc6fd98e7..0000000000 --- a/src/core/automount.c +++ /dev/null @@ -1,1092 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "async.h" -#include "automount.h" -#include "bus-error.h" -#include "bus-util.h" -#include "dbus-automount.h" -#include "fd-util.h" -#include "formats-util.h" -#include "io-util.h" -#include "label.h" -#include "mkdir.h" -#include "mount-util.h" -#include "mount.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "special.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" -#include "unit-name.h" -#include "unit.h" - -static const UnitActiveState state_translation_table[_AUTOMOUNT_STATE_MAX] = { - [AUTOMOUNT_DEAD] = UNIT_INACTIVE, - [AUTOMOUNT_WAITING] = UNIT_ACTIVE, - [AUTOMOUNT_RUNNING] = UNIT_ACTIVE, - [AUTOMOUNT_FAILED] = UNIT_FAILED -}; - -struct expire_data { - int dev_autofs_fd; - int ioctl_fd; -}; - -static inline void expire_data_free(struct expire_data *data) { - if (!data) - return; - - safe_close(data->dev_autofs_fd); - safe_close(data->ioctl_fd); - free(data); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct expire_data*, expire_data_free); - -static int open_dev_autofs(Manager *m); -static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, void *userdata); - -static void automount_init(Unit *u) { - Automount *a = AUTOMOUNT(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - a->pipe_fd = -1; - a->directory_mode = 0755; - UNIT(a)->ignore_on_isolate = true; -} - -static int automount_send_ready(Automount *a, Set *tokens, int status); - -static void unmount_autofs(Automount *a) { - int r; - - assert(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); - - /* If we reload/reexecute things we keep the mount point - * around */ - if (a->where && - (UNIT(a)->manager->exit_code != MANAGER_RELOAD && - UNIT(a)->manager->exit_code != MANAGER_REEXECUTE)) { - r = repeat_unmount(a->where, MNT_DETACH); - if (r < 0) - log_error_errno(r, "Failed to unmount: %m"); - } -} - -static void automount_done(Unit *u) { - Automount *a = AUTOMOUNT(u); - - assert(a); - - unmount_autofs(a); - - a->where = mfree(a->where); - - a->tokens = set_free(a->tokens); - a->expire_tokens = set_free(a->expire_tokens); - - a->expire_event_source = sd_event_source_unref(a->expire_event_source); -} - -static int automount_add_mount_links(Automount *a) { - _cleanup_free_ char *parent = NULL; - - assert(a); - - parent = dirname_malloc(a->where); - if (!parent) - return -ENOMEM; - - return unit_require_mounts_for(UNIT(a), parent); -} - -static int automount_add_default_dependencies(Automount *a) { - int r; - - assert(a); - - if (!UNIT(a)->default_dependencies) - return 0; - - if (UNIT(a)->manager->running_as != MANAGER_SYSTEM) - return 0; - - r = unit_add_two_dependencies_by_name(UNIT(a), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); - if (r < 0) - return r; - - return 0; -} - -static int automount_verify(Automount *a) { - _cleanup_free_ char *e = NULL; - int r; - - assert(a); - - if (UNIT(a)->load_state != UNIT_LOADED) - return 0; - - if (path_equal(a->where, "/")) { - log_unit_error(UNIT(a), "Cannot have an automount unit for the root directory. Refusing."); - return -EINVAL; - } - - r = unit_name_from_path(a->where, ".automount", &e); - if (r < 0) - return log_unit_error(UNIT(a), "Failed to generate unit name from path: %m"); - - if (!unit_has_name(UNIT(a), e)) { - log_unit_error(UNIT(a), "Where= setting doesn't match unit name. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int automount_load(Unit *u) { - Automount *a = AUTOMOUNT(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - /* Load a .automount file */ - r = unit_load_fragment_and_dropin_optional(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - Unit *x; - - if (!a->where) { - r = unit_name_to_path(u->id, &a->where); - if (r < 0) - return r; - } - - path_kill_slashes(a->where); - - r = unit_load_related_unit(u, ".mount", &x); - if (r < 0) - return r; - - r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, x, true); - if (r < 0) - return r; - - r = automount_add_mount_links(a); - if (r < 0) - return r; - - r = automount_add_default_dependencies(a); - if (r < 0) - return r; - } - - return automount_verify(a); -} - -static void automount_set_state(Automount *a, AutomountState state) { - AutomountState old_state; - assert(a); - - old_state = a->state; - a->state = state; - - if (state != AUTOMOUNT_WAITING && - state != AUTOMOUNT_RUNNING) - unmount_autofs(a); - - if (state != old_state) - log_unit_debug(UNIT(a), "Changed %s -> %s", automount_state_to_string(old_state), automount_state_to_string(state)); - - unit_notify(UNIT(a), state_translation_table[old_state], state_translation_table[state], true); -} - -static int automount_coldplug(Unit *u) { - Automount *a = AUTOMOUNT(u); - int r; - - assert(a); - assert(a->state == AUTOMOUNT_DEAD); - - if (a->deserialized_state != a->state) { - - r = open_dev_autofs(u->manager); - if (r < 0) - return r; - - if (a->deserialized_state == AUTOMOUNT_WAITING || - a->deserialized_state == AUTOMOUNT_RUNNING) { - assert(a->pipe_fd >= 0); - - r = sd_event_add_io(u->manager->event, &a->pipe_event_source, a->pipe_fd, EPOLLIN, automount_dispatch_io, u); - if (r < 0) - return r; - - (void) sd_event_source_set_description(a->pipe_event_source, "automount-io"); - } - - automount_set_state(a, a->deserialized_state); - } - - return 0; -} - -static void automount_dump(Unit *u, FILE *f, const char *prefix) { - char time_string[FORMAT_TIMESPAN_MAX]; - Automount *a = AUTOMOUNT(u); - - assert(a); - - fprintf(f, - "%sAutomount State: %s\n" - "%sResult: %s\n" - "%sWhere: %s\n" - "%sDirectoryMode: %04o\n" - "%sTimeoutIdleUSec: %s\n", - prefix, automount_state_to_string(a->state), - prefix, automount_result_to_string(a->result), - prefix, a->where, - prefix, a->directory_mode, - prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, a->timeout_idle_usec, USEC_PER_SEC)); -} - -static void automount_enter_dead(Automount *a, AutomountResult f) { - assert(a); - - if (f != AUTOMOUNT_SUCCESS) - a->result = f; - - automount_set_state(a, a->result != AUTOMOUNT_SUCCESS ? AUTOMOUNT_FAILED : AUTOMOUNT_DEAD); -} - -static int open_dev_autofs(Manager *m) { - struct autofs_dev_ioctl param; - - assert(m); - - if (m->dev_autofs_fd >= 0) - return m->dev_autofs_fd; - - label_fix("/dev/autofs", false, false); - - m->dev_autofs_fd = open("/dev/autofs", O_CLOEXEC|O_RDONLY); - if (m->dev_autofs_fd < 0) - return log_error_errno(errno, "Failed to open /dev/autofs: %m"); - - init_autofs_dev_ioctl(¶m); - if (ioctl(m->dev_autofs_fd, AUTOFS_DEV_IOCTL_VERSION, ¶m) < 0) { - m->dev_autofs_fd = safe_close(m->dev_autofs_fd); - return -errno; - } - - log_debug("Autofs kernel version %i.%i", param.ver_major, param.ver_minor); - - return m->dev_autofs_fd; -} - -static int open_ioctl_fd(int dev_autofs_fd, const char *where, dev_t devid) { - struct autofs_dev_ioctl *param; - size_t l; - - assert(dev_autofs_fd >= 0); - assert(where); - - l = sizeof(struct autofs_dev_ioctl) + strlen(where) + 1; - param = alloca(l); - - init_autofs_dev_ioctl(param); - param->size = l; - param->ioctlfd = -1; - param->openmount.devid = devid; - strcpy(param->path, where); - - if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_OPENMOUNT, param) < 0) - return -errno; - - if (param->ioctlfd < 0) - return -EIO; - - (void) fd_cloexec(param->ioctlfd, true); - return param->ioctlfd; -} - -static int autofs_protocol(int dev_autofs_fd, int ioctl_fd) { - uint32_t major, minor; - struct autofs_dev_ioctl param; - - assert(dev_autofs_fd >= 0); - assert(ioctl_fd >= 0); - - init_autofs_dev_ioctl(¶m); - param.ioctlfd = ioctl_fd; - - if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOVER, ¶m) < 0) - return -errno; - - major = param.protover.version; - - init_autofs_dev_ioctl(¶m); - param.ioctlfd = ioctl_fd; - - if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOSUBVER, ¶m) < 0) - return -errno; - - minor = param.protosubver.sub_version; - - log_debug("Autofs protocol version %i.%i", major, minor); - return 0; -} - -static int autofs_set_timeout(int dev_autofs_fd, int ioctl_fd, usec_t usec) { - struct autofs_dev_ioctl param; - - assert(dev_autofs_fd >= 0); - assert(ioctl_fd >= 0); - - init_autofs_dev_ioctl(¶m); - param.ioctlfd = ioctl_fd; - - /* Convert to seconds, rounding up. */ - param.timeout.timeout = (usec + USEC_PER_SEC - 1) / USEC_PER_SEC; - - if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_TIMEOUT, ¶m) < 0) - return -errno; - - return 0; -} - -static int autofs_send_ready(int dev_autofs_fd, int ioctl_fd, uint32_t token, int status) { - struct autofs_dev_ioctl param; - - assert(dev_autofs_fd >= 0); - assert(ioctl_fd >= 0); - - init_autofs_dev_ioctl(¶m); - param.ioctlfd = ioctl_fd; - - if (status) { - param.fail.token = token; - param.fail.status = status; - } else - param.ready.token = token; - - if (ioctl(dev_autofs_fd, status ? AUTOFS_DEV_IOCTL_FAIL : AUTOFS_DEV_IOCTL_READY, ¶m) < 0) - return -errno; - - return 0; -} - -static int automount_send_ready(Automount *a, Set *tokens, int status) { - _cleanup_close_ int ioctl_fd = -1; - unsigned token; - int r; - - assert(a); - assert(status <= 0); - - if (set_isempty(tokens)) - return 0; - - ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id); - if (ioctl_fd < 0) - return ioctl_fd; - - if (status) - log_unit_debug_errno(UNIT(a), status, "Sending failure: %m"); - else - log_unit_debug(UNIT(a), "Sending success."); - - r = 0; - - /* Autofs thankfully does not hand out 0 as a token */ - while ((token = PTR_TO_UINT(set_steal_first(tokens)))) { - int k; - - /* Autofs fun fact II: - * - * if you pass a positive status code here, the kernel will - * freeze! Yay! */ - - k = autofs_send_ready(UNIT(a)->manager->dev_autofs_fd, - ioctl_fd, - token, - status); - if (k < 0) - r = k; - } - - return r; -} - -static int automount_start_expire(Automount *a); - -int automount_update_mount(Automount *a, MountState old_state, MountState state) { - int r; - - assert(a); - - switch (state) { - case MOUNT_MOUNTED: - case MOUNT_REMOUNTING: - automount_send_ready(a, a->tokens, 0); - r = automount_start_expire(a); - if (r < 0) - log_unit_warning_errno(UNIT(a), r, "Failed to start expiration timer, ignoring: %m"); - break; - case MOUNT_DEAD: - case MOUNT_UNMOUNTING: - case MOUNT_MOUNTING_SIGTERM: - case MOUNT_MOUNTING_SIGKILL: - case MOUNT_REMOUNTING_SIGTERM: - case MOUNT_REMOUNTING_SIGKILL: - case MOUNT_UNMOUNTING_SIGTERM: - case MOUNT_UNMOUNTING_SIGKILL: - case MOUNT_FAILED: - if (old_state != state) - automount_send_ready(a, a->tokens, -ENODEV); - (void) sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_OFF); - break; - default: - break; - } - - switch (state) { - case MOUNT_DEAD: - automount_send_ready(a, a->expire_tokens, 0); - break; - case MOUNT_MOUNTING: - case MOUNT_MOUNTING_DONE: - case MOUNT_MOUNTING_SIGTERM: - case MOUNT_MOUNTING_SIGKILL: - case MOUNT_REMOUNTING_SIGTERM: - case MOUNT_REMOUNTING_SIGKILL: - case MOUNT_UNMOUNTING_SIGTERM: - case MOUNT_UNMOUNTING_SIGKILL: - case MOUNT_FAILED: - if (old_state != state) - automount_send_ready(a, a->expire_tokens, -ENODEV); - break; - default: - break; - } - - return 0; -} - -static void automount_enter_waiting(Automount *a) { - _cleanup_close_ int ioctl_fd = -1; - int p[2] = { -1, -1 }; - char name[sizeof("systemd-")-1 + DECIMAL_STR_MAX(pid_t) + 1]; - char options[sizeof("fd=,pgrp=,minproto=5,maxproto=5,direct")-1 - + DECIMAL_STR_MAX(int) + DECIMAL_STR_MAX(gid_t) + 1]; - bool mounted = false; - int r, dev_autofs_fd; - struct stat st; - - assert(a); - assert(a->pipe_fd < 0); - assert(a->where); - - set_clear(a->tokens); - - r = unit_fail_if_symlink(UNIT(a), a->where); - if (r < 0) - goto fail; - - (void) mkdir_p_label(a->where, 0555); - - unit_warn_if_dir_nonempty(UNIT(a), a->where); - - dev_autofs_fd = open_dev_autofs(UNIT(a)->manager); - if (dev_autofs_fd < 0) { - r = dev_autofs_fd; - goto fail; - } - - if (pipe2(p, O_NONBLOCK|O_CLOEXEC) < 0) { - r = -errno; - goto fail; - } - - xsprintf(options, "fd=%i,pgrp="PID_FMT",minproto=5,maxproto=5,direct", p[1], getpgrp()); - xsprintf(name, "systemd-"PID_FMT, getpid()); - if (mount(name, a->where, "autofs", 0, options) < 0) { - r = -errno; - goto fail; - } - - mounted = true; - - p[1] = safe_close(p[1]); - - if (stat(a->where, &st) < 0) { - r = -errno; - goto fail; - } - - ioctl_fd = open_ioctl_fd(dev_autofs_fd, a->where, st.st_dev); - if (ioctl_fd < 0) { - r = ioctl_fd; - goto fail; - } - - r = autofs_protocol(dev_autofs_fd, ioctl_fd); - if (r < 0) - goto fail; - - r = autofs_set_timeout(dev_autofs_fd, ioctl_fd, a->timeout_idle_usec); - if (r < 0) - goto fail; - - /* Autofs fun fact: - * - * Unless we close the ioctl fd here, for some weird reason - * the direct mount will not receive events from the - * kernel. */ - - r = sd_event_add_io(UNIT(a)->manager->event, &a->pipe_event_source, p[0], EPOLLIN, automount_dispatch_io, a); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(a->pipe_event_source, "automount-io"); - - a->pipe_fd = p[0]; - a->dev_id = st.st_dev; - - automount_set_state(a, AUTOMOUNT_WAITING); - - return; - -fail: - log_unit_error_errno(UNIT(a), r, "Failed to initialize automounter: %m"); - - safe_close_pair(p); - - if (mounted) { - r = repeat_unmount(a->where, MNT_DETACH); - if (r < 0) - log_error_errno(r, "Failed to unmount, ignoring: %m"); - } - - automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); -} - -static void *expire_thread(void *p) { - struct autofs_dev_ioctl param; - _cleanup_(expire_data_freep) struct expire_data *data = (struct expire_data*)p; - int r; - - assert(data->dev_autofs_fd >= 0); - assert(data->ioctl_fd >= 0); - - init_autofs_dev_ioctl(¶m); - param.ioctlfd = data->ioctl_fd; - - do { - r = ioctl(data->dev_autofs_fd, AUTOFS_DEV_IOCTL_EXPIRE, ¶m); - } while (r >= 0); - - if (errno != EAGAIN) - log_warning_errno(errno, "Failed to expire automount, ignoring: %m"); - - return NULL; -} - -static int automount_dispatch_expire(sd_event_source *source, usec_t usec, void *userdata) { - Automount *a = AUTOMOUNT(userdata); - _cleanup_(expire_data_freep) struct expire_data *data = NULL; - int r; - - assert(a); - assert(source == a->expire_event_source); - - data = new0(struct expire_data, 1); - if (!data) - return log_oom(); - - data->ioctl_fd = -1; - - data->dev_autofs_fd = fcntl(UNIT(a)->manager->dev_autofs_fd, F_DUPFD_CLOEXEC, 3); - if (data->dev_autofs_fd < 0) - return log_unit_error_errno(UNIT(a), errno, "Failed to duplicate autofs fd: %m"); - - data->ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id); - if (data->ioctl_fd < 0) - return log_unit_error_errno(UNIT(a), data->ioctl_fd, "Couldn't open autofs ioctl fd: %m"); - - r = asynchronous_job(expire_thread, data); - if (r < 0) - return log_unit_error_errno(UNIT(a), r, "Failed to start expire job: %m"); - - data = NULL; - - return automount_start_expire(a); -} - -static int automount_start_expire(Automount *a) { - int r; - usec_t timeout; - - assert(a); - - if (a->timeout_idle_usec == 0) - return 0; - - timeout = now(CLOCK_MONOTONIC) + MAX(a->timeout_idle_usec/3, USEC_PER_SEC); - - if (a->expire_event_source) { - r = sd_event_source_set_time(a->expire_event_source, timeout); - if (r < 0) - return r; - - return sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_ONESHOT); - } - - r = sd_event_add_time( - UNIT(a)->manager->event, - &a->expire_event_source, - CLOCK_MONOTONIC, timeout, 0, - automount_dispatch_expire, a); - if (r < 0) - return r; - - (void) sd_event_source_set_description(a->expire_event_source, "automount-expire"); - - return 0; -} - -static void automount_enter_runnning(Automount *a) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - struct stat st; - int r; - - assert(a); - - /* We don't take mount requests anymore if we are supposed to - * shut down anyway */ - if (unit_stop_pending(UNIT(a))) { - log_unit_debug(UNIT(a), "Suppressing automount request since unit stop is scheduled."); - automount_send_ready(a, a->tokens, -EHOSTDOWN); - automount_send_ready(a, a->expire_tokens, -EHOSTDOWN); - return; - } - - mkdir_p_label(a->where, a->directory_mode); - - /* Before we do anything, let's see if somebody is playing games with us? */ - if (lstat(a->where, &st) < 0) { - log_unit_warning_errno(UNIT(a), errno, "Failed to stat automount point: %m"); - goto fail; - } - - if (!S_ISDIR(st.st_mode) || st.st_dev != a->dev_id) - log_unit_info(UNIT(a), "Automount point already active?"); - else { - Unit *trigger; - - trigger = UNIT_TRIGGER(UNIT(a)); - if (!trigger) { - log_unit_error(UNIT(a), "Unit to trigger vanished."); - goto fail; - } - - r = manager_add_job(UNIT(a)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL); - if (r < 0) { - log_unit_warning(UNIT(a), "Failed to queue mount startup job: %s", bus_error_message(&error, r)); - goto fail; - } - } - - automount_set_state(a, AUTOMOUNT_RUNNING); - return; - -fail: - automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); -} - -static int automount_start(Unit *u) { - Automount *a = AUTOMOUNT(u); - Unit *trigger; - - assert(a); - assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_FAILED); - - if (path_is_mount_point(a->where, 0) > 0) { - log_unit_error(u, "Path %s is already a mount point, refusing start.", a->where); - return -EEXIST; - } - - trigger = UNIT_TRIGGER(u); - if (!trigger || trigger->load_state != UNIT_LOADED) { - log_unit_error(u, "Refusing to start, unit to trigger not loaded."); - return -ENOENT; - } - - a->result = AUTOMOUNT_SUCCESS; - automount_enter_waiting(a); - return 1; -} - -static int automount_stop(Unit *u) { - Automount *a = AUTOMOUNT(u); - - assert(a); - assert(a->state == AUTOMOUNT_WAITING || a->state == AUTOMOUNT_RUNNING); - - automount_enter_dead(a, AUTOMOUNT_SUCCESS); - return 1; -} - -static int automount_serialize(Unit *u, FILE *f, FDSet *fds) { - Automount *a = AUTOMOUNT(u); - Iterator i; - void *p; - int r; - - assert(a); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", automount_state_to_string(a->state)); - unit_serialize_item(u, f, "result", automount_result_to_string(a->result)); - unit_serialize_item_format(u, f, "dev-id", "%u", (unsigned) a->dev_id); - - SET_FOREACH(p, a->tokens, i) - unit_serialize_item_format(u, f, "token", "%u", PTR_TO_UINT(p)); - SET_FOREACH(p, a->expire_tokens, i) - unit_serialize_item_format(u, f, "expire-token", "%u", PTR_TO_UINT(p)); - - r = unit_serialize_item_fd(u, f, fds, "pipe-fd", a->pipe_fd); - if (r < 0) - return r; - - return 0; -} - -static int automount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Automount *a = AUTOMOUNT(u); - int r; - - assert(a); - assert(fds); - - if (streq(key, "state")) { - AutomountState state; - - state = automount_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - a->deserialized_state = state; - } else if (streq(key, "result")) { - AutomountResult f; - - f = automount_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != AUTOMOUNT_SUCCESS) - a->result = f; - - } else if (streq(key, "dev-id")) { - unsigned d; - - if (safe_atou(value, &d) < 0) - log_unit_debug(u, "Failed to parse dev-id value: %s", value); - else - a->dev_id = (unsigned) d; - } else if (streq(key, "token")) { - unsigned token; - - if (safe_atou(value, &token) < 0) - log_unit_debug(u, "Failed to parse token value: %s", value); - else { - r = set_ensure_allocated(&a->tokens, NULL); - if (r < 0) { - log_oom(); - return 0; - } - - r = set_put(a->tokens, UINT_TO_PTR(token)); - if (r < 0) - log_unit_error_errno(u, r, "Failed to add token to set: %m"); - } - } else if (streq(key, "expire-token")) { - unsigned token; - - if (safe_atou(value, &token) < 0) - log_unit_debug(u, "Failed to parse token value: %s", value); - else { - r = set_ensure_allocated(&a->expire_tokens, NULL); - if (r < 0) { - log_oom(); - return 0; - } - - r = set_put(a->expire_tokens, UINT_TO_PTR(token)); - if (r < 0) - log_unit_error_errno(u, r, "Failed to add expire token to set: %m"); - } - } else if (streq(key, "pipe-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse pipe-fd value: %s", value); - else { - safe_close(a->pipe_fd); - a->pipe_fd = fdset_remove(fds, fd); - } - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -static UnitActiveState automount_active_state(Unit *u) { - assert(u); - - return state_translation_table[AUTOMOUNT(u)->state]; -} - -static const char *automount_sub_state_to_string(Unit *u) { - assert(u); - - return automount_state_to_string(AUTOMOUNT(u)->state); -} - -static bool automount_check_gc(Unit *u) { - assert(u); - - if (!UNIT_TRIGGER(u)) - return false; - - return UNIT_VTABLE(UNIT_TRIGGER(u))->check_gc(UNIT_TRIGGER(u)); -} - -static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - union autofs_v5_packet_union packet; - Automount *a = AUTOMOUNT(userdata); - struct stat st; - Unit *trigger; - int r; - - assert(a); - assert(fd == a->pipe_fd); - - if (events != EPOLLIN) { - log_unit_error(UNIT(a), "Got invalid poll event %"PRIu32" on pipe (fd=%d)", events, fd); - goto fail; - } - - r = loop_read_exact(a->pipe_fd, &packet, sizeof(packet), true); - if (r < 0) { - log_unit_error_errno(UNIT(a), r, "Invalid read from pipe: %m"); - goto fail; - } - - switch (packet.hdr.type) { - - case autofs_ptype_missing_direct: - - if (packet.v5_packet.pid > 0) { - _cleanup_free_ char *p = NULL; - - get_process_comm(packet.v5_packet.pid, &p); - log_unit_info(UNIT(a), "Got automount request for %s, triggered by %"PRIu32" (%s)", a->where, packet.v5_packet.pid, strna(p)); - } else - log_unit_debug(UNIT(a), "Got direct mount request on %s", a->where); - - r = set_ensure_allocated(&a->tokens, NULL); - if (r < 0) { - log_unit_error(UNIT(a), "Failed to allocate token set."); - goto fail; - } - - r = set_put(a->tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token)); - if (r < 0) { - log_unit_error_errno(UNIT(a), r, "Failed to remember token: %m"); - goto fail; - } - - automount_enter_runnning(a); - break; - - case autofs_ptype_expire_direct: - log_unit_debug(UNIT(a), "Got direct umount request on %s", a->where); - - (void) sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_OFF); - - r = set_ensure_allocated(&a->expire_tokens, NULL); - if (r < 0) { - log_unit_error(UNIT(a), "Failed to allocate token set."); - goto fail; - } - - r = set_put(a->expire_tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token)); - if (r < 0) { - log_unit_error_errno(UNIT(a), r, "Failed to remember token: %m"); - goto fail; - } - - /* Before we do anything, let's see if somebody is playing games with us? */ - if (lstat(a->where, &st) < 0) { - log_unit_warning_errno(UNIT(a), errno, "Failed to stat automount point: %m"); - goto fail; - } - - if (!S_ISDIR(st.st_mode) || st.st_dev == a->dev_id) { - log_unit_info(UNIT(a), "Automount point already unmounted?"); - automount_send_ready(a, a->expire_tokens, 0); - break; - } - - trigger = UNIT_TRIGGER(UNIT(a)); - if (!trigger) { - log_unit_error(UNIT(a), "Unit to trigger vanished."); - goto fail; - } - - r = manager_add_job(UNIT(a)->manager, JOB_STOP, trigger, JOB_REPLACE, &error, NULL); - if (r < 0) { - log_unit_warning(UNIT(a), "Failed to queue umount startup job: %s", bus_error_message(&error, r)); - goto fail; - } - break; - - default: - log_unit_error(UNIT(a), "Received unknown automount request %i", packet.hdr.type); - break; - } - - return 0; - -fail: - automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); - return 0; -} - -static void automount_shutdown(Manager *m) { - assert(m); - - m->dev_autofs_fd = safe_close(m->dev_autofs_fd); -} - -static void automount_reset_failed(Unit *u) { - Automount *a = AUTOMOUNT(u); - - assert(a); - - if (a->state == AUTOMOUNT_FAILED) - automount_set_state(a, AUTOMOUNT_DEAD); - - a->result = AUTOMOUNT_SUCCESS; -} - -static bool automount_supported(void) { - static int supported = -1; - - if (supported < 0) - supported = access("/dev/autofs", F_OK) >= 0; - - return supported; -} - -static const char* const automount_result_table[_AUTOMOUNT_RESULT_MAX] = { - [AUTOMOUNT_SUCCESS] = "success", - [AUTOMOUNT_FAILURE_RESOURCES] = "resources" -}; - -DEFINE_STRING_TABLE_LOOKUP(automount_result, AutomountResult); - -const UnitVTable automount_vtable = { - .object_size = sizeof(Automount), - - .sections = - "Unit\0" - "Automount\0" - "Install\0", - - .no_alias = true, - .no_instances = true, - - .init = automount_init, - .load = automount_load, - .done = automount_done, - - .coldplug = automount_coldplug, - - .dump = automount_dump, - - .start = automount_start, - .stop = automount_stop, - - .serialize = automount_serialize, - .deserialize_item = automount_deserialize_item, - - .active_state = automount_active_state, - .sub_state_to_string = automount_sub_state_to_string, - - .check_gc = automount_check_gc, - - .reset_failed = automount_reset_failed, - - .bus_vtable = bus_automount_vtable, - - .shutdown = automount_shutdown, - .supported = automount_supported, - - .status_message_formats = { - .finished_start_job = { - [JOB_DONE] = "Set up automount %s.", - [JOB_FAILED] = "Failed to set up automount %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Unset automount %s.", - [JOB_FAILED] = "Failed to unset automount %s.", - }, - }, -}; diff --git a/src/core/automount.h b/src/core/automount.h deleted file mode 100644 index cf5b1cf994..0000000000 --- a/src/core/automount.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Automount Automount; - -#include "unit.h" - -typedef enum AutomountResult { - AUTOMOUNT_SUCCESS, - AUTOMOUNT_FAILURE_RESOURCES, - _AUTOMOUNT_RESULT_MAX, - _AUTOMOUNT_RESULT_INVALID = -1 -} AutomountResult; - -struct Automount { - Unit meta; - - AutomountState state, deserialized_state; - - char *where; - usec_t timeout_idle_usec; - - int pipe_fd; - sd_event_source *pipe_event_source; - mode_t directory_mode; - dev_t dev_id; - - Set *tokens; - Set *expire_tokens; - - sd_event_source *expire_event_source; - - AutomountResult result; -}; - -extern const UnitVTable automount_vtable; - -int automount_update_mount(Automount *a, MountState old_state, MountState state); - -const char* automount_result_to_string(AutomountResult i) _const_; -AutomountResult automount_result_from_string(const char *s) _pure_; diff --git a/src/core/bus-endpoint.c b/src/core/bus-endpoint.c deleted file mode 100644 index d22a80c91f..0000000000 --- a/src/core/bus-endpoint.c +++ /dev/null @@ -1,135 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Daniel Mack - - 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 . -***/ - -#include - -#include "alloc-util.h" -#include "bus-endpoint.h" -#include "bus-kernel.h" -#include "bus-policy.h" -#include "kdbus.h" - -int bus_kernel_set_endpoint_policy(int fd, uid_t uid, BusEndpoint *ep) { - - struct kdbus_cmd *update; - struct kdbus_item *n; - BusEndpointPolicy *po; - Iterator i; - size_t size; - int r; - - size = ALIGN8(offsetof(struct kdbus_cmd, items)); - - HASHMAP_FOREACH(po, ep->policy_hash, i) { - size += ALIGN8(offsetof(struct kdbus_item, str) + strlen(po->name) + 1); - size += ALIGN8(offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access)); - } - - update = alloca0_align(size, 8); - update->size = size; - - n = update->items; - - HASHMAP_FOREACH(po, ep->policy_hash, i) { - n->type = KDBUS_ITEM_NAME; - n->size = offsetof(struct kdbus_item, str) + strlen(po->name) + 1; - strcpy(n->str, po->name); - n = KDBUS_ITEM_NEXT(n); - - n->type = KDBUS_ITEM_POLICY_ACCESS; - n->size = offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access); - - n->policy_access.type = KDBUS_POLICY_ACCESS_USER; - n->policy_access.access = bus_kernel_translate_access(po->access); - n->policy_access.id = uid; - - n = KDBUS_ITEM_NEXT(n); - } - - r = ioctl(fd, KDBUS_CMD_ENDPOINT_UPDATE, update); - if (r < 0) - return -errno; - - return 0; -} - -int bus_endpoint_new(BusEndpoint **ep) { - assert(ep); - - *ep = new0(BusEndpoint, 1); - if (!*ep) - return -ENOMEM; - - return 0; -} - -int bus_endpoint_add_policy(BusEndpoint *ep, const char *name, BusPolicyAccess access) { - _cleanup_free_ BusEndpointPolicy *po = NULL; - _cleanup_free_ char *key = NULL; - int r; - - assert(ep); - assert(name); - assert(access > _BUS_POLICY_ACCESS_INVALID && access < _BUS_POLICY_ACCESS_MAX); - - /* check if we already have this name in the policy list. If we do, see if the new access level - * is higher than the exising one, and upgrade the entry in that case. Otherwise, do nothing. - */ - - if (ep->policy_hash) { - po = hashmap_get(ep->policy_hash, name); - if (po) { - if (po->access < access) - po->access = access; - - return 0; - } - } else { - ep->policy_hash = hashmap_new(&string_hash_ops); - if (!ep->policy_hash) - return -ENOMEM; - } - - po = new0(BusEndpointPolicy, 1); - if (!po) - return -ENOMEM; - - key = strdup(name); - if (!key) - return -ENOMEM; - - po->name = key; - po->access = access; - - r = hashmap_put(ep->policy_hash, key, po); - if (r < 0) - return r; - - po = NULL; - key = NULL; - return 0; -} - -void bus_endpoint_free(BusEndpoint *endpoint) { - if (!endpoint) - return; - - hashmap_free_free_free(endpoint->policy_hash); - free(endpoint); -} diff --git a/src/core/bus-endpoint.h b/src/core/bus-endpoint.h deleted file mode 100644 index f2fbc4701c..0000000000 --- a/src/core/bus-endpoint.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Daniel Mack - - 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 . -***/ - -typedef struct BusEndpoint BusEndpoint; -typedef struct BusEndpointPolicy BusEndpointPolicy; - -#include "bus-policy.h" -#include "hashmap.h" - -struct BusEndpointPolicy { - char *name; - BusPolicyAccess access; -}; - -struct BusEndpoint { - Hashmap *policy_hash; -}; - -int bus_endpoint_new(BusEndpoint **ep); -void bus_endpoint_free(BusEndpoint *endpoint); - -int bus_endpoint_add_policy(BusEndpoint *ep, const char *name, BusPolicyAccess access); - -int bus_kernel_set_endpoint_policy(int fd, uid_t uid, BusEndpoint *ep); diff --git a/src/core/bus-policy.c b/src/core/bus-policy.c deleted file mode 100644 index 4907c268e8..0000000000 --- a/src/core/bus-policy.c +++ /dev/null @@ -1,180 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Daniel Mack - - 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 . -***/ - -#include - -#include "alloc-util.h" -#include "bus-kernel.h" -#include "bus-policy.h" -#include "kdbus.h" -#include "string-table.h" -#include "user-util.h" -#include "util.h" - -int bus_kernel_translate_access(BusPolicyAccess access) { - assert(access >= 0); - assert(access < _BUS_POLICY_ACCESS_MAX); - - switch (access) { - - case BUS_POLICY_ACCESS_SEE: - return KDBUS_POLICY_SEE; - - case BUS_POLICY_ACCESS_TALK: - return KDBUS_POLICY_TALK; - - case BUS_POLICY_ACCESS_OWN: - return KDBUS_POLICY_OWN; - - default: - assert_not_reached("Unknown policy access"); - } -} - -int bus_kernel_translate_policy(const BusNamePolicy *policy, struct kdbus_item *item) { - int r; - - assert(policy); - assert(item); - - switch (policy->type) { - - case BUSNAME_POLICY_TYPE_USER: { - const char *user = policy->name; - uid_t uid; - - r = get_user_creds(&user, &uid, NULL, NULL, NULL); - if (r < 0) - return r; - - item->policy_access.type = KDBUS_POLICY_ACCESS_USER; - item->policy_access.id = uid; - break; - } - - case BUSNAME_POLICY_TYPE_GROUP: { - const char *group = policy->name; - gid_t gid; - - r = get_group_creds(&group, &gid); - if (r < 0) - return r; - - item->policy_access.type = KDBUS_POLICY_ACCESS_GROUP; - item->policy_access.id = gid; - break; - } - - default: - assert_not_reached("Unknown policy type"); - } - - item->policy_access.access = bus_kernel_translate_access(policy->access); - - return 0; -} - -int bus_kernel_make_starter( - int fd, - const char *name, - bool activating, - bool accept_fd, - BusNamePolicy *policy, - BusPolicyAccess world_policy) { - - struct kdbus_cmd_free cmd_free = { .size = sizeof(cmd_free) }; - struct kdbus_cmd_hello *hello; - struct kdbus_item *n; - size_t policy_cnt = 0; - BusNamePolicy *po; - size_t size; - int r; - - assert(fd >= 0); - assert(name); - - LIST_FOREACH(policy, po, policy) - policy_cnt++; - - if (world_policy >= 0) - policy_cnt++; - - size = offsetof(struct kdbus_cmd_hello, items) + - ALIGN8(offsetof(struct kdbus_item, str) + strlen(name) + 1) + - policy_cnt * ALIGN8(offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access)); - - hello = alloca0_align(size, 8); - - n = hello->items; - strcpy(n->str, name); - n->size = offsetof(struct kdbus_item, str) + strlen(n->str) + 1; - n->type = KDBUS_ITEM_NAME; - n = KDBUS_ITEM_NEXT(n); - - LIST_FOREACH(policy, po, policy) { - n->type = KDBUS_ITEM_POLICY_ACCESS; - n->size = offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access); - - r = bus_kernel_translate_policy(po, n); - if (r < 0) - return r; - - n = KDBUS_ITEM_NEXT(n); - } - - if (world_policy >= 0) { - n->type = KDBUS_ITEM_POLICY_ACCESS; - n->size = offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access); - n->policy_access.type = KDBUS_POLICY_ACCESS_WORLD; - n->policy_access.access = bus_kernel_translate_access(world_policy); - } - - hello->size = size; - hello->flags = - (activating ? KDBUS_HELLO_ACTIVATOR : KDBUS_HELLO_POLICY_HOLDER) | - (accept_fd ? KDBUS_HELLO_ACCEPT_FD : 0); - hello->pool_size = KDBUS_POOL_SIZE; - hello->attach_flags_send = _KDBUS_ATTACH_ANY; - hello->attach_flags_recv = _KDBUS_ATTACH_ANY; - - if (ioctl(fd, KDBUS_CMD_HELLO, hello) < 0) { - if (errno == ENOTTY) /* Major API change */ - return -ESOCKTNOSUPPORT; - return -errno; - } - - /* not interested in any output values */ - cmd_free.offset = hello->offset; - (void) ioctl(fd, KDBUS_CMD_FREE, &cmd_free); - - /* The higher 32bit of the bus_flags fields are considered - * 'incompatible flags'. Refuse them all for now. */ - if (hello->bus_flags > 0xFFFFFFFFULL) - return -ESOCKTNOSUPPORT; - - return fd; -} - -static const char* const bus_policy_access_table[_BUS_POLICY_ACCESS_MAX] = { - [BUS_POLICY_ACCESS_SEE] = "see", - [BUS_POLICY_ACCESS_TALK] = "talk", - [BUS_POLICY_ACCESS_OWN] = "own", -}; - -DEFINE_STRING_TABLE_LOOKUP(bus_policy_access, BusPolicyAccess); diff --git a/src/core/bus-policy.h b/src/core/bus-policy.h deleted file mode 100644 index 5b2c4d5953..0000000000 --- a/src/core/bus-policy.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Daniel Mack - - 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 . -***/ - -#include "kdbus.h" -#include "list.h" -#include "macro.h" - -typedef struct BusNamePolicy BusNamePolicy; - -typedef enum BusPolicyAccess { - BUS_POLICY_ACCESS_SEE, - BUS_POLICY_ACCESS_TALK, - BUS_POLICY_ACCESS_OWN, - _BUS_POLICY_ACCESS_MAX, - _BUS_POLICY_ACCESS_INVALID = -1 -} BusPolicyAccess; - -typedef enum BusNamePolicyType { - BUSNAME_POLICY_TYPE_USER, - BUSNAME_POLICY_TYPE_GROUP, - _BUSNAME_POLICY_TYPE_MAX, - _BUSNAME_POLICY_TYPE_INVALID = -1 -} BusNamePolicyType; - -struct BusNamePolicy { - BusNamePolicyType type; - BusPolicyAccess access; - - char *name; - - LIST_FIELDS(BusNamePolicy, policy); -}; - -int bus_kernel_translate_access(BusPolicyAccess access); -int bus_kernel_translate_policy(const BusNamePolicy *policy, struct kdbus_item *item); - -const char* bus_policy_access_to_string(BusPolicyAccess i) _const_; -BusPolicyAccess bus_policy_access_from_string(const char *s) _pure_; - -int bus_kernel_make_starter( - int fd, - const char *name, - bool activating, - bool accept_fd, - BusNamePolicy *policy, - BusPolicyAccess world_policy); diff --git a/src/core/busname.c b/src/core/busname.c deleted file mode 100644 index de2a21ccde..0000000000 --- a/src/core/busname.c +++ /dev/null @@ -1,1067 +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 . -***/ - -#include - -#include "alloc-util.h" -#include "bus-internal.h" -#include "bus-kernel.h" -#include "bus-policy.h" -#include "bus-util.h" -#include "busname.h" -#include "dbus-busname.h" -#include "fd-util.h" -#include "formats-util.h" -#include "kdbus.h" -#include "parse-util.h" -#include "process-util.h" -#include "service.h" -#include "signal-util.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" - -static const UnitActiveState state_translation_table[_BUSNAME_STATE_MAX] = { - [BUSNAME_DEAD] = UNIT_INACTIVE, - [BUSNAME_MAKING] = UNIT_ACTIVATING, - [BUSNAME_REGISTERED] = UNIT_ACTIVE, - [BUSNAME_LISTENING] = UNIT_ACTIVE, - [BUSNAME_RUNNING] = UNIT_ACTIVE, - [BUSNAME_SIGTERM] = UNIT_DEACTIVATING, - [BUSNAME_SIGKILL] = UNIT_DEACTIVATING, - [BUSNAME_FAILED] = UNIT_FAILED -}; - -static int busname_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); -static int busname_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); - -static void busname_init(Unit *u) { - BusName *n = BUSNAME(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - n->starter_fd = -1; - n->accept_fd = true; - n->activating = true; - - n->timeout_usec = u->manager->default_timeout_start_usec; -} - -static void busname_unwatch_control_pid(BusName *n) { - assert(n); - - if (n->control_pid <= 0) - return; - - unit_unwatch_pid(UNIT(n), n->control_pid); - n->control_pid = 0; -} - -static void busname_free_policy(BusName *n) { - BusNamePolicy *p; - - assert(n); - - while ((p = n->policy)) { - LIST_REMOVE(policy, n->policy, p); - - free(p->name); - free(p); - } -} - -static void busname_close_fd(BusName *n) { - assert(n); - - n->starter_event_source = sd_event_source_unref(n->starter_event_source); - n->starter_fd = safe_close(n->starter_fd); -} - -static void busname_done(Unit *u) { - BusName *n = BUSNAME(u); - - assert(n); - - n->name = mfree(n->name); - - busname_free_policy(n); - busname_unwatch_control_pid(n); - busname_close_fd(n); - - unit_ref_unset(&n->service); - - n->timer_event_source = sd_event_source_unref(n->timer_event_source); -} - -static int busname_arm_timer(BusName *n, usec_t usec) { - int r; - - assert(n); - - if (n->timer_event_source) { - r = sd_event_source_set_time(n->timer_event_source, usec); - if (r < 0) - return r; - - return sd_event_source_set_enabled(n->timer_event_source, SD_EVENT_ONESHOT); - } - - if (usec == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - UNIT(n)->manager->event, - &n->timer_event_source, - CLOCK_MONOTONIC, - usec, 0, - busname_dispatch_timer, n); - if (r < 0) - return r; - - (void) sd_event_source_set_description(n->timer_event_source, "busname-timer"); - - return 0; -} - -static int busname_add_default_default_dependencies(BusName *n) { - int r; - - assert(n); - - r = unit_add_dependency_by_name(UNIT(n), UNIT_BEFORE, SPECIAL_BUSNAMES_TARGET, NULL, true); - if (r < 0) - return r; - - if (UNIT(n)->manager->running_as == MANAGER_SYSTEM) { - r = unit_add_two_dependencies_by_name(UNIT(n), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); - if (r < 0) - return r; - } - - return unit_add_two_dependencies_by_name(UNIT(n), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -static int busname_add_extras(BusName *n) { - Unit *u = UNIT(n); - int r; - - assert(n); - - if (!n->name) { - r = unit_name_to_prefix(u->id, &n->name); - if (r < 0) - return r; - } - - if (!u->description) { - r = unit_set_description(u, n->name); - if (r < 0) - return r; - } - - if (n->activating) { - if (!UNIT_DEREF(n->service)) { - Unit *x; - - r = unit_load_related_unit(u, ".service", &x); - if (r < 0) - return r; - - unit_ref_set(&n->service, x); - } - - r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(n->service), true); - if (r < 0) - return r; - } - - if (u->default_dependencies) { - r = busname_add_default_default_dependencies(n); - if (r < 0) - return r; - } - - return 0; -} - -static int busname_verify(BusName *n) { - char *e; - - assert(n); - - if (UNIT(n)->load_state != UNIT_LOADED) - return 0; - - if (!service_name_is_valid(n->name)) { - log_unit_error(UNIT(n), "Name= setting is not a valid service name Refusing."); - return -EINVAL; - } - - e = strjoina(n->name, ".busname"); - if (!unit_has_name(UNIT(n), e)) { - log_unit_error(UNIT(n), "Name= setting doesn't match unit name. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int busname_load(Unit *u) { - BusName *n = BUSNAME(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - r = unit_load_fragment_and_dropin(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - /* This is a new unit? Then let's add in some extras */ - r = busname_add_extras(n); - if (r < 0) - return r; - } - - return busname_verify(n); -} - -static void busname_dump(Unit *u, FILE *f, const char *prefix) { - BusName *n = BUSNAME(u); - - assert(n); - assert(f); - - fprintf(f, - "%sBus Name State: %s\n" - "%sResult: %s\n" - "%sName: %s\n" - "%sActivating: %s\n" - "%sAccept FD: %s\n", - prefix, busname_state_to_string(n->state), - prefix, busname_result_to_string(n->result), - prefix, n->name, - prefix, yes_no(n->activating), - prefix, yes_no(n->accept_fd)); - - if (n->control_pid > 0) - fprintf(f, - "%sControl PID: "PID_FMT"\n", - prefix, n->control_pid); -} - -static void busname_unwatch_fd(BusName *n) { - int r; - - assert(n); - - if (!n->starter_event_source) - return; - - r = sd_event_source_set_enabled(n->starter_event_source, SD_EVENT_OFF); - if (r < 0) - log_unit_debug_errno(UNIT(n), r, "Failed to disable event source: %m"); -} - -static int busname_watch_fd(BusName *n) { - int r; - - assert(n); - - if (n->starter_fd < 0) - return 0; - - if (n->starter_event_source) { - r = sd_event_source_set_enabled(n->starter_event_source, SD_EVENT_ON); - if (r < 0) - goto fail; - } else { - r = sd_event_add_io(UNIT(n)->manager->event, &n->starter_event_source, n->starter_fd, EPOLLIN, busname_dispatch_io, n); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(n->starter_event_source, "busname-starter"); - } - - return 0; - -fail: - log_unit_warning_errno(UNIT(n), r, "Failed to watch starter fd: %m"); - busname_unwatch_fd(n); - return r; -} - -static int busname_open_fd(BusName *n) { - _cleanup_free_ char *path = NULL; - const char *mode; - - assert(n); - - if (n->starter_fd >= 0) - return 0; - - mode = UNIT(n)->manager->running_as == MANAGER_SYSTEM ? "system" : "user"; - n->starter_fd = bus_kernel_open_bus_fd(mode, &path); - if (n->starter_fd < 0) - return log_unit_warning_errno(UNIT(n), n->starter_fd, "Failed to open %s: %m", path ?: "kdbus"); - - return 0; -} - -static void busname_set_state(BusName *n, BusNameState state) { - BusNameState old_state; - assert(n); - - old_state = n->state; - n->state = state; - - if (!IN_SET(state, BUSNAME_MAKING, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) { - n->timer_event_source = sd_event_source_unref(n->timer_event_source); - busname_unwatch_control_pid(n); - } - - if (state != BUSNAME_LISTENING) - busname_unwatch_fd(n); - - if (!IN_SET(state, BUSNAME_LISTENING, BUSNAME_MAKING, BUSNAME_REGISTERED, BUSNAME_RUNNING)) - busname_close_fd(n); - - if (state != old_state) - log_unit_debug(UNIT(n), "Changed %s -> %s", busname_state_to_string(old_state), busname_state_to_string(state)); - - unit_notify(UNIT(n), state_translation_table[old_state], state_translation_table[state], true); -} - -static int busname_coldplug(Unit *u) { - BusName *n = BUSNAME(u); - int r; - - assert(n); - assert(n->state == BUSNAME_DEAD); - - if (n->deserialized_state == n->state) - return 0; - - if (n->control_pid > 0 && - pid_is_unwaited(n->control_pid) && - IN_SET(n->deserialized_state, BUSNAME_MAKING, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) { - - r = unit_watch_pid(UNIT(n), n->control_pid); - if (r < 0) - return r; - - r = busname_arm_timer(n, usec_add(u->state_change_timestamp.monotonic, n->timeout_usec)); - if (r < 0) - return r; - } - - if (IN_SET(n->deserialized_state, BUSNAME_MAKING, BUSNAME_LISTENING, BUSNAME_REGISTERED, BUSNAME_RUNNING)) { - r = busname_open_fd(n); - if (r < 0) - return r; - } - - if (n->deserialized_state == BUSNAME_LISTENING) { - r = busname_watch_fd(n); - if (r < 0) - return r; - } - - busname_set_state(n, n->deserialized_state); - return 0; -} - -static int busname_make_starter(BusName *n, pid_t *_pid) { - pid_t pid; - int r; - - r = busname_arm_timer(n, usec_add(now(CLOCK_MONOTONIC), n->timeout_usec)); - if (r < 0) - goto fail; - - /* We have to resolve the user/group names out-of-process, - * hence let's fork here. It's messy, but well, what can we - * do? */ - - pid = fork(); - if (pid < 0) - return -errno; - - if (pid == 0) { - int ret; - - (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE, -1); - (void) ignore_signals(SIGPIPE, -1); - log_forget_fds(); - - r = bus_kernel_make_starter(n->starter_fd, n->name, n->activating, n->accept_fd, n->policy, n->policy_world); - if (r < 0) { - ret = EXIT_MAKE_STARTER; - goto fail_child; - } - - _exit(0); - - fail_child: - log_open(); - log_error_errno(r, "Failed to create starter connection at step %s: %m", exit_status_to_string(ret, EXIT_STATUS_SYSTEMD)); - - _exit(ret); - } - - r = unit_watch_pid(UNIT(n), pid); - if (r < 0) - goto fail; - - *_pid = pid; - return 0; - -fail: - n->timer_event_source = sd_event_source_unref(n->timer_event_source); - return r; -} - -static void busname_enter_dead(BusName *n, BusNameResult f) { - assert(n); - - if (f != BUSNAME_SUCCESS) - n->result = f; - - busname_set_state(n, n->result != BUSNAME_SUCCESS ? BUSNAME_FAILED : BUSNAME_DEAD); -} - -static void busname_enter_signal(BusName *n, BusNameState state, BusNameResult f) { - KillContext kill_context = {}; - int r; - - assert(n); - - if (f != BUSNAME_SUCCESS) - n->result = f; - - kill_context_init(&kill_context); - - r = unit_kill_context(UNIT(n), - &kill_context, - state != BUSNAME_SIGTERM ? KILL_KILL : KILL_TERMINATE, - -1, - n->control_pid, - false); - if (r < 0) { - log_unit_warning_errno(UNIT(n), r, "Failed to kill control process: %m"); - goto fail; - } - - if (r > 0) { - r = busname_arm_timer(n, usec_add(now(CLOCK_MONOTONIC), n->timeout_usec)); - if (r < 0) { - log_unit_warning_errno(UNIT(n), r, "Failed to arm timer: %m"); - goto fail; - } - - busname_set_state(n, state); - } else if (state == BUSNAME_SIGTERM) - busname_enter_signal(n, BUSNAME_SIGKILL, BUSNAME_SUCCESS); - else - busname_enter_dead(n, BUSNAME_SUCCESS); - - return; - -fail: - busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); -} - -static void busname_enter_listening(BusName *n) { - int r; - - assert(n); - - if (n->activating) { - r = busname_watch_fd(n); - if (r < 0) { - log_unit_warning_errno(UNIT(n), r, "Failed to watch names: %m"); - goto fail; - } - - busname_set_state(n, BUSNAME_LISTENING); - } else - busname_set_state(n, BUSNAME_REGISTERED); - - return; - -fail: - busname_enter_signal(n, BUSNAME_SIGTERM, BUSNAME_FAILURE_RESOURCES); -} - -static void busname_enter_making(BusName *n) { - int r; - - assert(n); - - r = busname_open_fd(n); - if (r < 0) - goto fail; - - if (n->policy) { - /* If there is a policy, we need to resolve user/group - * names, which we can't do from PID1, hence let's - * fork. */ - busname_unwatch_control_pid(n); - - r = busname_make_starter(n, &n->control_pid); - if (r < 0) { - log_unit_warning_errno(UNIT(n), r, "Failed to fork 'making' task: %m"); - goto fail; - } - - busname_set_state(n, BUSNAME_MAKING); - } else { - /* If there is no policy, we can do everything - * directly from PID 1, hence do so. */ - - r = bus_kernel_make_starter(n->starter_fd, n->name, n->activating, n->accept_fd, NULL, n->policy_world); - if (r < 0) { - log_unit_warning_errno(UNIT(n), r, "Failed to make starter: %m"); - goto fail; - } - - busname_enter_listening(n); - } - - return; - -fail: - busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); -} - -static void busname_enter_running(BusName *n) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool pending = false; - Unit *other; - Iterator i; - int r; - - assert(n); - - if (!n->activating) - return; - - /* We don't take connections anymore if we are supposed to - * shut down anyway */ - - if (unit_stop_pending(UNIT(n))) { - log_unit_debug(UNIT(n), "Suppressing activation request since unit stop is scheduled."); - - /* Flush all queued activation reqeuest by closing and reopening the connection */ - bus_kernel_drop_one(n->starter_fd); - - busname_enter_listening(n); - return; - } - - /* If there's already a start pending don't bother to do - * anything */ - SET_FOREACH(other, UNIT(n)->dependencies[UNIT_TRIGGERS], i) - if (unit_active_or_pending(other)) { - pending = true; - break; - } - - if (!pending) { - if (!UNIT_ISSET(n->service)) { - log_unit_error(UNIT(n), "Service to activate vanished, refusing activation."); - r = -ENOENT; - goto fail; - } - - r = manager_add_job(UNIT(n)->manager, JOB_START, UNIT_DEREF(n->service), JOB_REPLACE, &error, NULL); - if (r < 0) - goto fail; - } - - busname_set_state(n, BUSNAME_RUNNING); - return; - -fail: - log_unit_warning(UNIT(n), "Failed to queue service startup job: %s", bus_error_message(&error, r)); - busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); -} - -static int busname_start(Unit *u) { - BusName *n = BUSNAME(u); - - assert(n); - - /* We cannot fulfill this request right now, try again later - * please! */ - if (IN_SET(n->state, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) - return -EAGAIN; - - /* Already on it! */ - if (n->state == BUSNAME_MAKING) - return 0; - - if (n->activating && UNIT_ISSET(n->service)) { - Service *service; - - service = SERVICE(UNIT_DEREF(n->service)); - - if (UNIT(service)->load_state != UNIT_LOADED) { - log_unit_error(u, "Bus service %s not loaded, refusing.", UNIT(service)->id); - return -ENOENT; - } - } - - assert(IN_SET(n->state, BUSNAME_DEAD, BUSNAME_FAILED)); - - n->result = BUSNAME_SUCCESS; - busname_enter_making(n); - - return 1; -} - -static int busname_stop(Unit *u) { - BusName *n = BUSNAME(u); - - assert(n); - - /* Already on it */ - if (IN_SET(n->state, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) - return 0; - - /* If there's already something running, we go directly into - * kill mode. */ - - if (n->state == BUSNAME_MAKING) { - busname_enter_signal(n, BUSNAME_SIGTERM, BUSNAME_SUCCESS); - return -EAGAIN; - } - - assert(IN_SET(n->state, BUSNAME_REGISTERED, BUSNAME_LISTENING, BUSNAME_RUNNING)); - - busname_enter_dead(n, BUSNAME_SUCCESS); - return 1; -} - -static int busname_serialize(Unit *u, FILE *f, FDSet *fds) { - BusName *n = BUSNAME(u); - int r; - - assert(n); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", busname_state_to_string(n->state)); - unit_serialize_item(u, f, "result", busname_result_to_string(n->result)); - - if (n->control_pid > 0) - unit_serialize_item_format(u, f, "control-pid", PID_FMT, n->control_pid); - - r = unit_serialize_item_fd(u, f, fds, "starter-fd", n->starter_fd); - if (r < 0) - return r; - - return 0; -} - -static int busname_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - BusName *n = BUSNAME(u); - - assert(n); - assert(key); - assert(value); - - if (streq(key, "state")) { - BusNameState state; - - state = busname_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - n->deserialized_state = state; - - } else if (streq(key, "result")) { - BusNameResult f; - - f = busname_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != BUSNAME_SUCCESS) - n->result = f; - - } else if (streq(key, "control-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse control-pid value: %s", value); - else - n->control_pid = pid; - } else if (streq(key, "starter-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse starter fd value: %s", value); - else { - safe_close(n->starter_fd); - n->starter_fd = fdset_remove(fds, fd); - } - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -_pure_ static UnitActiveState busname_active_state(Unit *u) { - assert(u); - - return state_translation_table[BUSNAME(u)->state]; -} - -_pure_ static const char *busname_sub_state_to_string(Unit *u) { - assert(u); - - return busname_state_to_string(BUSNAME(u)->state); -} - -static int busname_peek_message(BusName *n) { - struct kdbus_cmd_recv cmd_recv = { - .size = sizeof(cmd_recv), - .flags = KDBUS_RECV_PEEK, - }; - struct kdbus_cmd_free cmd_free = { - .size = sizeof(cmd_free), - }; - const char *comm = NULL; - struct kdbus_item *d; - struct kdbus_msg *k; - size_t start, ps, sz, delta; - void *p = NULL; - pid_t pid = 0; - int r; - - /* Generate a friendly debug log message about which process - * caused triggering of this bus name. This simply peeks the - * metadata of the first queued message and logs it. */ - - assert(n); - - /* Let's shortcut things a bit, if debug logging is turned off - * anyway. */ - - if (log_get_max_level() < LOG_DEBUG) - return 0; - - r = ioctl(n->starter_fd, KDBUS_CMD_RECV, &cmd_recv); - if (r < 0) { - if (errno == EINTR || errno == EAGAIN) - return 0; - - return log_unit_error_errno(UNIT(n), errno, "Failed to query activation message: %m"); - } - - /* We map as late as possible, and unmap imemdiately after - * use. On 32bit address space is scarce and we want to be - * able to handle a lot of activator connections at the same - * time, and hence shouldn't keep the mmap()s around for - * longer than necessary. */ - - ps = page_size(); - start = (cmd_recv.msg.offset / ps) * ps; - delta = cmd_recv.msg.offset - start; - sz = PAGE_ALIGN(delta + cmd_recv.msg.msg_size); - - p = mmap(NULL, sz, PROT_READ, MAP_SHARED, n->starter_fd, start); - if (p == MAP_FAILED) { - r = log_unit_error_errno(UNIT(n), errno, "Failed to map activation message: %m"); - goto finish; - } - - k = (struct kdbus_msg *) ((uint8_t *) p + delta); - KDBUS_ITEM_FOREACH(d, k, items) { - switch (d->type) { - - case KDBUS_ITEM_PIDS: - pid = d->pids.pid; - break; - - case KDBUS_ITEM_PID_COMM: - comm = d->str; - break; - } - } - - if (pid > 0) - log_unit_debug(UNIT(n), "Activation triggered by process " PID_FMT " (%s)", pid, strna(comm)); - - r = 0; - -finish: - if (p) - (void) munmap(p, sz); - - cmd_free.offset = cmd_recv.msg.offset; - if (ioctl(n->starter_fd, KDBUS_CMD_FREE, &cmd_free) < 0) - log_unit_warning(UNIT(n), "Failed to free peeked message, ignoring: %m"); - - return r; -} - -static int busname_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - BusName *n = userdata; - - assert(n); - assert(fd >= 0); - - if (n->state != BUSNAME_LISTENING) - return 0; - - log_unit_debug(UNIT(n), "Activation request"); - - if (revents != EPOLLIN) { - log_unit_error(UNIT(n), "Got unexpected poll event (0x%x) on starter fd.", revents); - goto fail; - } - - busname_peek_message(n); - busname_enter_running(n); - return 0; -fail: - - busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); - return 0; -} - -static void busname_sigchld_event(Unit *u, pid_t pid, int code, int status) { - BusName *n = BUSNAME(u); - BusNameResult f; - - assert(n); - assert(pid >= 0); - - if (pid != n->control_pid) - return; - - n->control_pid = 0; - - if (is_clean_exit(code, status, NULL)) - f = BUSNAME_SUCCESS; - else if (code == CLD_EXITED) - f = BUSNAME_FAILURE_EXIT_CODE; - else if (code == CLD_KILLED) - f = BUSNAME_FAILURE_SIGNAL; - else if (code == CLD_DUMPED) - f = BUSNAME_FAILURE_CORE_DUMP; - else - assert_not_reached("Unknown sigchld code"); - - log_unit_full(u, f == BUSNAME_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, - "Control process exited, code=%s status=%i", sigchld_code_to_string(code), status); - - if (f != BUSNAME_SUCCESS) - n->result = f; - - switch (n->state) { - - case BUSNAME_MAKING: - if (f == BUSNAME_SUCCESS) - busname_enter_listening(n); - else - busname_enter_signal(n, BUSNAME_SIGTERM, f); - break; - - case BUSNAME_SIGTERM: - case BUSNAME_SIGKILL: - busname_enter_dead(n, f); - break; - - default: - assert_not_reached("Uh, control process died at wrong time."); - } - - /* Notify clients about changed exit status */ - unit_add_to_dbus_queue(u); -} - -static int busname_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - BusName *n = BUSNAME(userdata); - - assert(n); - assert(n->timer_event_source == source); - - switch (n->state) { - - case BUSNAME_MAKING: - log_unit_warning(UNIT(n), "Making timed out. Terminating."); - busname_enter_signal(n, BUSNAME_SIGTERM, BUSNAME_FAILURE_TIMEOUT); - break; - - case BUSNAME_SIGTERM: - log_unit_warning(UNIT(n), "Stopping timed out. Killing."); - busname_enter_signal(n, BUSNAME_SIGKILL, BUSNAME_FAILURE_TIMEOUT); - break; - - case BUSNAME_SIGKILL: - log_unit_warning(UNIT(n), "Processes still around after SIGKILL. Ignoring."); - busname_enter_dead(n, BUSNAME_FAILURE_TIMEOUT); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } - - return 0; -} - -static void busname_reset_failed(Unit *u) { - BusName *n = BUSNAME(u); - - assert(n); - - if (n->state == BUSNAME_FAILED) - busname_set_state(n, BUSNAME_DEAD); - - n->result = BUSNAME_SUCCESS; -} - -static void busname_trigger_notify(Unit *u, Unit *other) { - BusName *n = BUSNAME(u); - - assert(n); - assert(other); - - if (!IN_SET(n->state, BUSNAME_RUNNING, BUSNAME_LISTENING)) - return; - - if (other->start_limit_hit) { - busname_enter_dead(n, BUSNAME_FAILURE_SERVICE_START_LIMIT_HIT); - return; - } - - if (other->load_state != UNIT_LOADED || other->type != UNIT_SERVICE) - return; - - if (IN_SET(SERVICE(other)->state, - SERVICE_DEAD, SERVICE_FAILED, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, - SERVICE_AUTO_RESTART)) - busname_enter_listening(n); - - if (SERVICE(other)->state == SERVICE_RUNNING) - busname_set_state(n, BUSNAME_RUNNING); -} - -static int busname_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - return unit_kill_common(u, who, signo, -1, BUSNAME(u)->control_pid, error); -} - -static int busname_get_timeout(Unit *u, usec_t *timeout) { - BusName *n = BUSNAME(u); - usec_t t; - int r; - - if (!n->timer_event_source) - return 0; - - r = sd_event_source_get_time(n->timer_event_source, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) - return 0; - - *timeout = t; - return 1; -} - -static bool busname_supported(void) { - static int supported = -1; - - if (supported < 0) - supported = is_kdbus_available(); - - return supported; -} - -static const char* const busname_result_table[_BUSNAME_RESULT_MAX] = { - [BUSNAME_SUCCESS] = "success", - [BUSNAME_FAILURE_RESOURCES] = "resources", - [BUSNAME_FAILURE_TIMEOUT] = "timeout", - [BUSNAME_FAILURE_EXIT_CODE] = "exit-code", - [BUSNAME_FAILURE_SIGNAL] = "signal", - [BUSNAME_FAILURE_CORE_DUMP] = "core-dump", - [BUSNAME_FAILURE_SERVICE_START_LIMIT_HIT] = "service-start-limit-hit", -}; - -DEFINE_STRING_TABLE_LOOKUP(busname_result, BusNameResult); - -const UnitVTable busname_vtable = { - .object_size = sizeof(BusName), - - .sections = - "Unit\0" - "BusName\0" - "Install\0", - .private_section = "BusName", - - .no_alias = true, - .no_instances = true, - - .init = busname_init, - .done = busname_done, - .load = busname_load, - - .coldplug = busname_coldplug, - - .dump = busname_dump, - - .start = busname_start, - .stop = busname_stop, - - .kill = busname_kill, - - .get_timeout = busname_get_timeout, - - .serialize = busname_serialize, - .deserialize_item = busname_deserialize_item, - - .active_state = busname_active_state, - .sub_state_to_string = busname_sub_state_to_string, - - .sigchld_event = busname_sigchld_event, - - .trigger_notify = busname_trigger_notify, - - .reset_failed = busname_reset_failed, - - .supported = busname_supported, - - .bus_vtable = bus_busname_vtable, - - .status_message_formats = { - .finished_start_job = { - [JOB_DONE] = "Listening on %s.", - [JOB_FAILED] = "Failed to listen on %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Closed %s.", - [JOB_FAILED] = "Failed stopping %s.", - }, - }, -}; diff --git a/src/core/busname.h b/src/core/busname.h deleted file mode 100644 index 6b6f6c62d4..0000000000 --- a/src/core/busname.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -typedef struct BusName BusName; -typedef struct BusNamePolicy BusNamePolicy; - -#include "unit.h" - -typedef enum BusNameResult { - BUSNAME_SUCCESS, - BUSNAME_FAILURE_RESOURCES, - BUSNAME_FAILURE_TIMEOUT, - BUSNAME_FAILURE_EXIT_CODE, - BUSNAME_FAILURE_SIGNAL, - BUSNAME_FAILURE_CORE_DUMP, - BUSNAME_FAILURE_SERVICE_START_LIMIT_HIT, - _BUSNAME_RESULT_MAX, - _BUSNAME_RESULT_INVALID = -1 -} BusNameResult; - -struct BusName { - Unit meta; - - char *name; - int starter_fd; - - bool activating; - bool accept_fd; - - UnitRef service; - - BusNameState state, deserialized_state; - BusNameResult result; - - usec_t timeout_usec; - - sd_event_source *starter_event_source; - sd_event_source *timer_event_source; - - pid_t control_pid; - - LIST_HEAD(BusNamePolicy, policy); - BusPolicyAccess policy_world; -}; - -extern const UnitVTable busname_vtable; - -const char* busname_result_to_string(BusNameResult i) _const_; -BusNameResult busname_result_from_string(const char *s) _pure_; diff --git a/src/core/cgroup.c b/src/core/cgroup.c deleted file mode 100644 index 39235a95f6..0000000000 --- a/src/core/cgroup.c +++ /dev/null @@ -1,1620 +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 . -***/ - -#include -#include - -#include "alloc-util.h" -#include "cgroup-util.h" -#include "cgroup.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" - -#define CGROUP_CPU_QUOTA_PERIOD_USEC ((usec_t) 100 * USEC_PER_MSEC) - -void cgroup_context_init(CGroupContext *c) { - assert(c); - - /* Initialize everything to the kernel defaults, assuming the - * structure is preinitialized to 0 */ - - c->cpu_shares = CGROUP_CPU_SHARES_INVALID; - c->startup_cpu_shares = CGROUP_CPU_SHARES_INVALID; - c->cpu_quota_per_sec_usec = USEC_INFINITY; - - c->memory_limit = (uint64_t) -1; - - c->blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID; - c->startup_blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID; - - c->tasks_max = (uint64_t) -1; -} - -void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a) { - assert(c); - assert(a); - - LIST_REMOVE(device_allow, c->device_allow, a); - free(a->path); - free(a); -} - -void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w) { - assert(c); - assert(w); - - LIST_REMOVE(device_weights, c->blockio_device_weights, w); - free(w->path); - free(w); -} - -void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b) { - assert(c); - assert(b); - - LIST_REMOVE(device_bandwidths, c->blockio_device_bandwidths, b); - free(b->path); - free(b); -} - -void cgroup_context_done(CGroupContext *c) { - assert(c); - - while (c->blockio_device_weights) - cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); - - while (c->blockio_device_bandwidths) - cgroup_context_free_blockio_device_bandwidth(c, c->blockio_device_bandwidths); - - while (c->device_allow) - cgroup_context_free_device_allow(c, c->device_allow); -} - -void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { - CGroupBlockIODeviceBandwidth *b; - CGroupBlockIODeviceWeight *w; - CGroupDeviceAllow *a; - char u[FORMAT_TIMESPAN_MAX]; - - assert(c); - assert(f); - - prefix = strempty(prefix); - - fprintf(f, - "%sCPUAccounting=%s\n" - "%sBlockIOAccounting=%s\n" - "%sMemoryAccounting=%s\n" - "%sTasksAccounting=%s\n" - "%sCPUShares=%" PRIu64 "\n" - "%sStartupCPUShares=%" PRIu64 "\n" - "%sCPUQuotaPerSecSec=%s\n" - "%sBlockIOWeight=%" PRIu64 "\n" - "%sStartupBlockIOWeight=%" PRIu64 "\n" - "%sMemoryLimit=%" PRIu64 "\n" - "%sTasksMax=%" PRIu64 "\n" - "%sDevicePolicy=%s\n" - "%sDelegate=%s\n", - prefix, yes_no(c->cpu_accounting), - prefix, yes_no(c->blockio_accounting), - prefix, yes_no(c->memory_accounting), - prefix, yes_no(c->tasks_accounting), - prefix, c->cpu_shares, - prefix, c->startup_cpu_shares, - prefix, format_timespan(u, sizeof(u), c->cpu_quota_per_sec_usec, 1), - prefix, c->blockio_weight, - prefix, c->startup_blockio_weight, - prefix, c->memory_limit, - prefix, c->tasks_max, - prefix, cgroup_device_policy_to_string(c->device_policy), - prefix, yes_no(c->delegate)); - - LIST_FOREACH(device_allow, a, c->device_allow) - fprintf(f, - "%sDeviceAllow=%s %s%s%s\n", - prefix, - a->path, - a->r ? "r" : "", a->w ? "w" : "", a->m ? "m" : ""); - - LIST_FOREACH(device_weights, w, c->blockio_device_weights) - fprintf(f, - "%sBlockIODeviceWeight=%s %" PRIu64, - prefix, - w->path, - w->weight); - - LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { - char buf[FORMAT_BYTES_MAX]; - - fprintf(f, - "%s%s=%s %s\n", - prefix, - b->read ? "BlockIOReadBandwidth" : "BlockIOWriteBandwidth", - b->path, - format_bytes(buf, sizeof(buf), b->bandwidth)); - } -} - -static int lookup_blkio_device(const char *p, dev_t *dev) { - struct stat st; - int r; - - assert(p); - assert(dev); - - r = stat(p, &st); - if (r < 0) - return log_warning_errno(errno, "Couldn't stat device %s: %m", p); - - if (S_ISBLK(st.st_mode)) - *dev = st.st_rdev; - else if (major(st.st_dev) != 0) { - /* If this is not a device node then find the block - * device this file is stored on */ - *dev = st.st_dev; - - /* If this is a partition, try to get the originating - * block device */ - block_get_whole_disk(*dev, dev); - } else { - log_warning("%s is not a block device and file system block device cannot be determined or is not local.", p); - return -ENODEV; - } - - return 0; -} - -static int whitelist_device(const char *path, const char *node, const char *acc) { - char buf[2+DECIMAL_STR_MAX(dev_t)*2+2+4]; - struct stat st; - int r; - - assert(path); - assert(acc); - - if (stat(node, &st) < 0) { - log_warning("Couldn't stat device %s", node); - return -errno; - } - - if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { - log_warning("%s is not a device.", node); - return -ENODEV; - } - - sprintf(buf, - "%c %u:%u %s", - S_ISCHR(st.st_mode) ? 'c' : 'b', - major(st.st_rdev), minor(st.st_rdev), - acc); - - r = cg_set_attribute("devices", path, "devices.allow", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set devices.allow on %s: %m", path); - - return r; -} - -static int whitelist_major(const char *path, const char *name, char type, const char *acc) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - bool good = false; - int r; - - assert(path); - assert(acc); - assert(type == 'b' || type == 'c'); - - f = fopen("/proc/devices", "re"); - if (!f) - return log_warning_errno(errno, "Cannot open /proc/devices to resolve %s (%c): %m", name, type); - - FOREACH_LINE(line, f, goto fail) { - char buf[2+DECIMAL_STR_MAX(unsigned)+3+4], *p, *w; - unsigned maj; - - truncate_nl(line); - - if (type == 'c' && streq(line, "Character devices:")) { - good = true; - continue; - } - - if (type == 'b' && streq(line, "Block devices:")) { - good = true; - continue; - } - - if (isempty(line)) { - good = false; - continue; - } - - if (!good) - continue; - - p = strstrip(line); - - w = strpbrk(p, WHITESPACE); - if (!w) - continue; - *w = 0; - - r = safe_atou(p, &maj); - if (r < 0) - continue; - if (maj <= 0) - continue; - - w++; - w += strspn(w, WHITESPACE); - - if (fnmatch(name, w, 0) != 0) - continue; - - sprintf(buf, - "%c %u:* %s", - type, - maj, - acc); - - r = cg_set_attribute("devices", path, "devices.allow", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set devices.allow on %s: %m", path); - } - - return 0; - -fail: - log_warning_errno(errno, "Failed to read /proc/devices: %m"); - return -errno; -} - -void cgroup_context_apply(CGroupContext *c, CGroupMask mask, const char *path, ManagerState state) { - bool is_root; - int r; - - assert(c); - assert(path); - - if (mask == 0) - return; - - /* Some cgroup attributes are not supported on the root cgroup, - * hence silently ignore */ - is_root = isempty(path) || path_equal(path, "/"); - if (is_root) - /* Make sure we don't try to display messages with an empty path. */ - path = "/"; - - /* We generally ignore errors caused by read-only mounted - * cgroup trees (assuming we are running in a container then), - * and missing cgroups, i.e. EROFS and ENOENT. */ - - if ((mask & CGROUP_MASK_CPU) && !is_root) { - char buf[MAX(DECIMAL_STR_MAX(uint64_t), DECIMAL_STR_MAX(usec_t)) + 1]; - - sprintf(buf, "%" PRIu64 "\n", - IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && c->startup_cpu_shares != CGROUP_CPU_SHARES_INVALID ? c->startup_cpu_shares : - c->cpu_shares != CGROUP_CPU_SHARES_INVALID ? c->cpu_shares : CGROUP_CPU_SHARES_DEFAULT); - r = cg_set_attribute("cpu", path, "cpu.shares", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set cpu.shares on %s: %m", path); - - sprintf(buf, USEC_FMT "\n", CGROUP_CPU_QUOTA_PERIOD_USEC); - r = cg_set_attribute("cpu", path, "cpu.cfs_period_us", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set cpu.cfs_period_us on %s: %m", path); - - if (c->cpu_quota_per_sec_usec != USEC_INFINITY) { - sprintf(buf, USEC_FMT "\n", c->cpu_quota_per_sec_usec * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC); - r = cg_set_attribute("cpu", path, "cpu.cfs_quota_us", buf); - } else - r = cg_set_attribute("cpu", path, "cpu.cfs_quota_us", "-1"); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set cpu.cfs_quota_us on %s: %m", path); - } - - if (mask & CGROUP_MASK_BLKIO) { - char buf[MAX(DECIMAL_STR_MAX(uint64_t)+1, - DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1)]; - CGroupBlockIODeviceWeight *w; - CGroupBlockIODeviceBandwidth *b; - - if (!is_root) { - sprintf(buf, "%" PRIu64 "\n", - IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && c->startup_blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID ? c->startup_blockio_weight : - c->blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID ? c->blockio_weight : CGROUP_BLKIO_WEIGHT_DEFAULT); - r = cg_set_attribute("blkio", path, "blkio.weight", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set blkio.weight on %s: %m", path); - - /* FIXME: no way to reset this list */ - LIST_FOREACH(device_weights, w, c->blockio_device_weights) { - dev_t dev; - - r = lookup_blkio_device(w->path, &dev); - if (r < 0) - continue; - - sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), w->weight); - r = cg_set_attribute("blkio", path, "blkio.weight_device", buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set blkio.weight_device on %s: %m", path); - } - } - - /* FIXME: no way to reset this list */ - LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { - const char *a; - dev_t dev; - - r = lookup_blkio_device(b->path, &dev); - if (r < 0) - continue; - - a = b->read ? "blkio.throttle.read_bps_device" : "blkio.throttle.write_bps_device"; - - sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), b->bandwidth); - r = cg_set_attribute("blkio", path, a, buf); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set %s on %s: %m", a, path); - } - } - - if ((mask & CGROUP_MASK_MEMORY) && !is_root) { - if (c->memory_limit != (uint64_t) -1) { - char buf[DECIMAL_STR_MAX(uint64_t) + 1]; - - sprintf(buf, "%" PRIu64 "\n", c->memory_limit); - - if (cg_unified() <= 0) - r = cg_set_attribute("memory", path, "memory.limit_in_bytes", buf); - else - r = cg_set_attribute("memory", path, "memory.max", buf); - - } else { - if (cg_unified() <= 0) - r = cg_set_attribute("memory", path, "memory.limit_in_bytes", "-1"); - else - r = cg_set_attribute("memory", path, "memory.max", "max"); - } - - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set memory.limit_in_bytes/memory.max on %s: %m", path); - } - - if ((mask & CGROUP_MASK_DEVICES) && !is_root) { - CGroupDeviceAllow *a; - - /* Changing the devices list of a populated cgroup - * might result in EINVAL, hence ignore EINVAL - * here. */ - - if (c->device_allow || c->device_policy != CGROUP_AUTO) - r = cg_set_attribute("devices", path, "devices.deny", "a"); - else - r = cg_set_attribute("devices", path, "devices.allow", "a"); - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to reset devices.list on %s: %m", path); - - if (c->device_policy == CGROUP_CLOSED || - (c->device_policy == CGROUP_AUTO && c->device_allow)) { - static const char auto_devices[] = - "/dev/null\0" "rwm\0" - "/dev/zero\0" "rwm\0" - "/dev/full\0" "rwm\0" - "/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 */ - - const char *x, *y; - - NULSTR_FOREACH_PAIR(x, y, auto_devices) - whitelist_device(path, x, y); - - whitelist_major(path, "pts", 'c', "rw"); - whitelist_major(path, "kdbus", 'c', "rw"); - whitelist_major(path, "kdbus/*", 'c', "rw"); - } - - LIST_FOREACH(device_allow, a, c->device_allow) { - char acc[4]; - unsigned k = 0; - - if (a->r) - acc[k++] = 'r'; - if (a->w) - acc[k++] = 'w'; - if (a->m) - acc[k++] = 'm'; - - if (k == 0) - continue; - - acc[k++] = 0; - - if (startswith(a->path, "/dev/")) - whitelist_device(path, a->path, acc); - else if (startswith(a->path, "block-")) - whitelist_major(path, a->path + 6, 'b', acc); - else if (startswith(a->path, "char-")) - whitelist_major(path, a->path + 5, 'c', acc); - else - log_debug("Ignoring device %s while writing cgroup attribute.", a->path); - } - } - - if ((mask & CGROUP_MASK_PIDS) && !is_root) { - - if (c->tasks_max != (uint64_t) -1) { - char buf[DECIMAL_STR_MAX(uint64_t) + 2]; - - sprintf(buf, "%" PRIu64 "\n", c->tasks_max); - r = cg_set_attribute("pids", path, "pids.max", buf); - } else - r = cg_set_attribute("pids", path, "pids.max", "max"); - - if (r < 0) - log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to set pids.max on %s: %m", path); - } -} - -CGroupMask cgroup_context_get_mask(CGroupContext *c) { - CGroupMask mask = 0; - - /* Figure out which controllers we need */ - - if (c->cpu_accounting || - c->cpu_shares != CGROUP_CPU_SHARES_INVALID || - c->startup_cpu_shares != CGROUP_CPU_SHARES_INVALID || - c->cpu_quota_per_sec_usec != USEC_INFINITY) - mask |= CGROUP_MASK_CPUACCT | CGROUP_MASK_CPU; - - if (c->blockio_accounting || - c->blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID || - c->startup_blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID || - c->blockio_device_weights || - c->blockio_device_bandwidths) - mask |= CGROUP_MASK_BLKIO; - - if (c->memory_accounting || - c->memory_limit != (uint64_t) -1) - mask |= CGROUP_MASK_MEMORY; - - if (c->device_allow || - c->device_policy != CGROUP_AUTO) - mask |= CGROUP_MASK_DEVICES; - - if (c->tasks_accounting || - c->tasks_max != (uint64_t) -1) - mask |= CGROUP_MASK_PIDS; - - return mask; -} - -CGroupMask unit_get_own_mask(Unit *u) { - CGroupContext *c; - - /* Returns the mask of controllers the unit needs for itself */ - - c = unit_get_cgroup_context(u); - if (!c) - return 0; - - /* If delegation is turned on, then turn on all cgroups, - * unless we are on the legacy hierarchy and the process we - * fork into it is known to drop privileges, and hence - * shouldn't get access to the controllers. - * - * Note that on the unified hierarchy it is safe to delegate - * controllers to unprivileged services. */ - - if (c->delegate) { - ExecContext *e; - - e = unit_get_exec_context(u); - if (!e || - exec_context_maintains_privileges(e) || - cg_unified() > 0) - return _CGROUP_MASK_ALL; - } - - return cgroup_context_get_mask(c); -} - -CGroupMask unit_get_members_mask(Unit *u) { - assert(u); - - /* Returns the mask of controllers all of the unit's children - * require, merged */ - - if (u->cgroup_members_mask_valid) - return u->cgroup_members_mask; - - u->cgroup_members_mask = 0; - - if (u->type == UNIT_SLICE) { - Unit *member; - Iterator i; - - SET_FOREACH(member, u->dependencies[UNIT_BEFORE], i) { - - if (member == u) - continue; - - if (UNIT_DEREF(member->slice) != u) - continue; - - u->cgroup_members_mask |= - unit_get_own_mask(member) | - unit_get_members_mask(member); - } - } - - u->cgroup_members_mask_valid = true; - return u->cgroup_members_mask; -} - -CGroupMask unit_get_siblings_mask(Unit *u) { - assert(u); - - /* Returns the mask of controllers all of the unit's siblings - * require, i.e. the members mask of the unit's parent slice - * if there is one. */ - - if (UNIT_ISSET(u->slice)) - return unit_get_members_mask(UNIT_DEREF(u->slice)); - - return unit_get_own_mask(u) | unit_get_members_mask(u); -} - -CGroupMask unit_get_subtree_mask(Unit *u) { - - /* Returns the mask of this subtree, meaning of the group - * itself and its children. */ - - return unit_get_own_mask(u) | unit_get_members_mask(u); -} - -CGroupMask unit_get_target_mask(Unit *u) { - CGroupMask mask; - - /* This returns the cgroup mask of all controllers to enable - * for a specific cgroup, i.e. everything it needs itself, - * plus all that its children need, plus all that its siblings - * need. This is primarily useful on the legacy cgroup - * hierarchy, where we need to duplicate each cgroup in each - * hierarchy that shall be enabled for it. */ - - mask = unit_get_own_mask(u) | unit_get_members_mask(u) | unit_get_siblings_mask(u); - mask &= u->manager->cgroup_supported; - - return mask; -} - -CGroupMask unit_get_enable_mask(Unit *u) { - CGroupMask mask; - - /* This returns the cgroup mask of all controllers to enable - * for the children of a specific cgroup. This is primarily - * useful for the unified cgroup hierarchy, where each cgroup - * controls which controllers are enabled for its children. */ - - mask = unit_get_members_mask(u); - mask &= u->manager->cgroup_supported; - - return mask; -} - -/* Recurse from a unit up through its containing slices, propagating - * mask bits upward. A unit is also member of itself. */ -void unit_update_cgroup_members_masks(Unit *u) { - CGroupMask m; - bool more; - - assert(u); - - /* Calculate subtree mask */ - m = unit_get_subtree_mask(u); - - /* See if anything changed from the previous invocation. If - * not, we're done. */ - if (u->cgroup_subtree_mask_valid && m == u->cgroup_subtree_mask) - return; - - more = - u->cgroup_subtree_mask_valid && - ((m & ~u->cgroup_subtree_mask) != 0) && - ((~m & u->cgroup_subtree_mask) == 0); - - u->cgroup_subtree_mask = m; - u->cgroup_subtree_mask_valid = true; - - if (UNIT_ISSET(u->slice)) { - Unit *s = UNIT_DEREF(u->slice); - - if (more) - /* There's more set now than before. We - * propagate the new mask to the parent's mask - * (not caring if it actually was valid or - * not). */ - - s->cgroup_members_mask |= m; - - else - /* There's less set now than before (or we - * don't know), we need to recalculate - * everything, so let's invalidate the - * parent's members mask */ - - s->cgroup_members_mask_valid = false; - - /* And now make sure that this change also hits our - * grandparents */ - unit_update_cgroup_members_masks(s); - } -} - -static const char *migrate_callback(CGroupMask mask, void *userdata) { - Unit *u = userdata; - - assert(mask != 0); - assert(u); - - while (u) { - if (u->cgroup_path && - u->cgroup_realized && - (u->cgroup_realized_mask & mask) == mask) - return u->cgroup_path; - - u = UNIT_DEREF(u->slice); - } - - return NULL; -} - -char *unit_default_cgroup_path(Unit *u) { - _cleanup_free_ char *escaped = NULL, *slice = NULL; - int r; - - assert(u); - - if (unit_has_name(u, SPECIAL_ROOT_SLICE)) - return strdup(u->manager->cgroup_root); - - if (UNIT_ISSET(u->slice) && !unit_has_name(UNIT_DEREF(u->slice), SPECIAL_ROOT_SLICE)) { - r = cg_slice_to_path(UNIT_DEREF(u->slice)->id, &slice); - if (r < 0) - return NULL; - } - - escaped = cg_escape(u->id); - if (!escaped) - return NULL; - - if (slice) - return strjoin(u->manager->cgroup_root, "/", slice, "/", escaped, NULL); - else - return strjoin(u->manager->cgroup_root, "/", escaped, NULL); -} - -int unit_set_cgroup_path(Unit *u, const char *path) { - _cleanup_free_ char *p = NULL; - int r; - - assert(u); - - if (path) { - p = strdup(path); - if (!p) - return -ENOMEM; - } else - p = NULL; - - if (streq_ptr(u->cgroup_path, p)) - return 0; - - if (p) { - r = hashmap_put(u->manager->cgroup_unit, p, u); - if (r < 0) - return r; - } - - unit_release_cgroup(u); - - u->cgroup_path = p; - p = NULL; - - return 1; -} - -int unit_watch_cgroup(Unit *u) { - _cleanup_free_ char *populated = NULL; - int r; - - assert(u); - - if (!u->cgroup_path) - return 0; - - if (u->cgroup_inotify_wd >= 0) - return 0; - - /* 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"); - if (r == 0) - return 0; - - /* Don't watch the root slice, it's pointless. */ - if (unit_has_name(u, SPECIAL_ROOT_SLICE)) - return 0; - - r = hashmap_ensure_allocated(&u->manager->cgroup_inotify_wd_unit, &trivial_hash_ops); - if (r < 0) - return log_oom(); - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.populated", &populated); - if (r < 0) - return log_oom(); - - u->cgroup_inotify_wd = inotify_add_watch(u->manager->cgroup_inotify_fd, populated, IN_MODIFY); - if (u->cgroup_inotify_wd < 0) { - - if (errno == ENOENT) /* If the directory is already - * gone we don't need to track - * it, so this is not an error */ - return 0; - - return log_unit_error_errno(u, errno, "Failed to add inotify watch descriptor for control group %s: %m", u->cgroup_path); - } - - r = hashmap_put(u->manager->cgroup_inotify_wd_unit, INT_TO_PTR(u->cgroup_inotify_wd), u); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to add inotify watch descriptor to hash map: %m"); - - return 0; -} - -static int unit_create_cgroup( - Unit *u, - CGroupMask target_mask, - CGroupMask enable_mask) { - - CGroupContext *c; - int r; - - assert(u); - - c = unit_get_cgroup_context(u); - if (!c) - return 0; - - if (!u->cgroup_path) { - _cleanup_free_ char *path = NULL; - - path = unit_default_cgroup_path(u); - if (!path) - return log_oom(); - - r = unit_set_cgroup_path(u, path); - if (r == -EEXIST) - return log_unit_error_errno(u, r, "Control group %s exists already.", path); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to set unit's control group path to %s: %m", path); - } - - /* First, create our own group */ - r = cg_create_everywhere(u->manager->cgroup_supported, target_mask, u->cgroup_path); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to create cgroup %s: %m", u->cgroup_path); - - /* Start watching it */ - (void) unit_watch_cgroup(u); - - /* Enable all controllers we need */ - r = cg_enable_everywhere(u->manager->cgroup_supported, enable_mask, u->cgroup_path); - if (r < 0) - log_unit_warning_errno(u, r, "Failed to enable controllers on cgroup %s, ignoring: %m", u->cgroup_path); - - /* Keep track that this is now realized */ - u->cgroup_realized = true; - u->cgroup_realized_mask = target_mask; - - if (u->type != UNIT_SLICE && !c->delegate) { - - /* Then, possibly move things over, but not if - * subgroups may contain processes, which is the case - * for slice and delegation units. */ - r = cg_migrate_everywhere(u->manager->cgroup_supported, u->cgroup_path, u->cgroup_path, migrate_callback, u); - if (r < 0) - log_unit_warning_errno(u, r, "Failed to migrate cgroup from to %s, ignoring: %m", u->cgroup_path); - } - - return 0; -} - -int unit_attach_pids_to_cgroup(Unit *u) { - int r; - assert(u); - - r = unit_realize_cgroup(u); - if (r < 0) - return r; - - r = cg_attach_many_everywhere(u->manager->cgroup_supported, u->cgroup_path, u->pids, migrate_callback, u); - if (r < 0) - return r; - - return 0; -} - -static bool unit_has_mask_realized(Unit *u, CGroupMask target_mask) { - assert(u); - - return u->cgroup_realized && u->cgroup_realized_mask == target_mask; -} - -/* Check if necessary controllers and attributes for a unit are in place. - * - * If so, do nothing. - * If not, create paths, move processes over, and set attributes. - * - * Returns 0 on success and < 0 on failure. */ -static int unit_realize_cgroup_now(Unit *u, ManagerState state) { - CGroupMask target_mask, enable_mask; - int r; - - assert(u); - - if (u->in_cgroup_queue) { - LIST_REMOVE(cgroup_queue, u->manager->cgroup_queue, u); - u->in_cgroup_queue = false; - } - - target_mask = unit_get_target_mask(u); - if (unit_has_mask_realized(u, target_mask)) - return 0; - - /* First, realize parents */ - if (UNIT_ISSET(u->slice)) { - r = unit_realize_cgroup_now(UNIT_DEREF(u->slice), state); - if (r < 0) - return r; - } - - /* And then do the real work */ - enable_mask = unit_get_enable_mask(u); - r = unit_create_cgroup(u, target_mask, enable_mask); - if (r < 0) - return r; - - /* Finally, apply the necessary attributes. */ - cgroup_context_apply(unit_get_cgroup_context(u), target_mask, u->cgroup_path, state); - - return 0; -} - -static void unit_add_to_cgroup_queue(Unit *u) { - - if (u->in_cgroup_queue) - return; - - LIST_PREPEND(cgroup_queue, u->manager->cgroup_queue, u); - u->in_cgroup_queue = true; -} - -unsigned manager_dispatch_cgroup_queue(Manager *m) { - ManagerState state; - unsigned n = 0; - Unit *i; - int r; - - state = manager_state(m); - - while ((i = m->cgroup_queue)) { - assert(i->in_cgroup_queue); - - r = unit_realize_cgroup_now(i, state); - if (r < 0) - log_warning_errno(r, "Failed to realize cgroups for queued unit %s, ignoring: %m", i->id); - - n++; - } - - return n; -} - -static void unit_queue_siblings(Unit *u) { - Unit *slice; - - /* This adds the siblings of the specified unit and the - * siblings of all parent units to the cgroup queue. (But - * neither the specified unit itself nor the parents.) */ - - while ((slice = UNIT_DEREF(u->slice))) { - Iterator i; - Unit *m; - - SET_FOREACH(m, slice->dependencies[UNIT_BEFORE], i) { - if (m == u) - continue; - - /* Skip units that have a dependency on the slice - * but aren't actually in it. */ - if (UNIT_DEREF(m->slice) != slice) - continue; - - /* No point in doing cgroup application for units - * without active processes. */ - if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(m))) - continue; - - /* If the unit doesn't need any new controllers - * and has current ones realized, it doesn't need - * any changes. */ - if (unit_has_mask_realized(m, unit_get_target_mask(m))) - continue; - - unit_add_to_cgroup_queue(m); - } - - u = slice; - } -} - -int unit_realize_cgroup(Unit *u) { - assert(u); - - if (!UNIT_HAS_CGROUP_CONTEXT(u)) - return 0; - - /* So, here's the deal: when realizing the cgroups for this - * unit, we need to first create all parents, but there's more - * actually: for the weight-based controllers we also need to - * make sure that all our siblings (i.e. units that are in the - * same slice as we are) have cgroups, too. Otherwise, things - * would become very uneven as each of their processes would - * get as much resources as all our group together. This call - * will synchronously create the parent cgroups, but will - * defer work on the siblings to the next event loop - * iteration. */ - - /* Add all sibling slices to the cgroup queue. */ - unit_queue_siblings(u); - - /* And realize this one now (and apply the values) */ - return unit_realize_cgroup_now(u, manager_state(u->manager)); -} - -void unit_release_cgroup(Unit *u) { - assert(u); - - /* Forgets all cgroup details for this cgroup */ - - if (u->cgroup_path) { - (void) hashmap_remove(u->manager->cgroup_unit, u->cgroup_path); - u->cgroup_path = mfree(u->cgroup_path); - } - - if (u->cgroup_inotify_wd >= 0) { - if (inotify_rm_watch(u->manager->cgroup_inotify_fd, u->cgroup_inotify_wd) < 0) - log_unit_debug_errno(u, errno, "Failed to remove cgroup inotify watch %i for %s, ignoring", u->cgroup_inotify_wd, u->id); - - (void) hashmap_remove(u->manager->cgroup_inotify_wd_unit, INT_TO_PTR(u->cgroup_inotify_wd)); - u->cgroup_inotify_wd = -1; - } -} - -void unit_prune_cgroup(Unit *u) { - int r; - bool is_root_slice; - - assert(u); - - /* Removes the cgroup, if empty and possible, and stops watching it. */ - - if (!u->cgroup_path) - return; - - is_root_slice = unit_has_name(u, SPECIAL_ROOT_SLICE); - - r = cg_trim_everywhere(u->manager->cgroup_supported, u->cgroup_path, !is_root_slice); - if (r < 0) { - log_debug_errno(r, "Failed to destroy cgroup %s, ignoring: %m", u->cgroup_path); - return; - } - - if (is_root_slice) - return; - - unit_release_cgroup(u); - - u->cgroup_realized = false; - u->cgroup_realized_mask = 0; -} - -int unit_search_main_pid(Unit *u, pid_t *ret) { - _cleanup_fclose_ FILE *f = NULL; - pid_t pid = 0, npid, mypid; - int r; - - assert(u); - assert(ret); - - if (!u->cgroup_path) - return -ENXIO; - - r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, &f); - if (r < 0) - return r; - - mypid = getpid(); - while (cg_read_pid(f, &npid) > 0) { - pid_t ppid; - - if (npid == pid) - continue; - - /* Ignore processes that aren't our kids */ - if (get_process_ppid(npid, &ppid) >= 0 && ppid != mypid) - continue; - - if (pid != 0) - /* Dang, there's more than one daemonized PID - in this group, so we don't know what process - is the main process. */ - - return -ENODATA; - - pid = npid; - } - - *ret = pid; - return 0; -} - -static int unit_watch_pids_in_path(Unit *u, const char *path) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_fclose_ FILE *f = NULL; - int ret = 0, r; - - assert(u); - assert(path); - - r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, path, &f); - if (r < 0) - ret = r; - else { - pid_t pid; - - while ((r = cg_read_pid(f, &pid)) > 0) { - r = unit_watch_pid(u, pid); - if (r < 0 && ret >= 0) - ret = r; - } - - if (r < 0 && ret >= 0) - ret = r; - } - - r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, path, &d); - if (r < 0) { - if (ret >= 0) - ret = r; - } else { - char *fn; - - while ((r = cg_read_subgroup(d, &fn)) > 0) { - _cleanup_free_ char *p = NULL; - - p = strjoin(path, "/", fn, NULL); - free(fn); - - if (!p) - return -ENOMEM; - - r = unit_watch_pids_in_path(u, p); - if (r < 0 && ret >= 0) - ret = r; - } - - if (r < 0 && ret >= 0) - ret = r; - } - - return ret; -} - -int unit_watch_all_pids(Unit *u) { - assert(u); - - /* Adds all PIDs from our cgroup to the set of PIDs we - * watch. This is a fallback logic for cases where we do not - * get reliable cgroup empty notifications: we try to use - * SIGCHLD as replacement. */ - - if (!u->cgroup_path) - return -ENOENT; - - if (cg_unified() > 0) /* On unified we can use proper notifications */ - return 0; - - return unit_watch_pids_in_path(u, u->cgroup_path); -} - -int unit_notify_cgroup_empty(Unit *u) { - int r; - - assert(u); - - if (!u->cgroup_path) - return 0; - - r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path); - if (r <= 0) - return r; - - unit_add_to_gc_queue(u); - - if (UNIT_VTABLE(u)->notify_cgroup_empty) - UNIT_VTABLE(u)->notify_cgroup_empty(u); - - return 0; -} - -static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - - assert(s); - assert(fd >= 0); - assert(m); - - for (;;) { - union inotify_event_buffer buffer; - struct inotify_event *e; - ssize_t l; - - l = read(fd, &buffer, sizeof(buffer)); - if (l < 0) { - if (errno == EINTR || errno == EAGAIN) - return 0; - - return log_error_errno(errno, "Failed to read control group inotify events: %m"); - } - - FOREACH_INOTIFY_EVENT(e, buffer, l) { - Unit *u; - - if (e->wd < 0) - /* Queue overflow has no watch descriptor */ - continue; - - if (e->mask & IN_IGNORED) - /* The watch was just removed */ - continue; - - u = hashmap_get(m->cgroup_inotify_wd_unit, INT_TO_PTR(e->wd)); - if (!u) /* Not that inotify might deliver - * events for a watch even after it - * was removed, because it was queued - * before the removal. Let's ignore - * this here safely. */ - continue; - - (void) unit_notify_cgroup_empty(u); - } - } -} - -int manager_setup_cgroup(Manager *m) { - _cleanup_free_ char *path = NULL; - CGroupController c; - int r, unified; - char *e; - - assert(m); - - /* 1. Determine hierarchy */ - m->cgroup_root = mfree(m->cgroup_root); - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &m->cgroup_root); - if (r < 0) - return log_error_errno(r, "Cannot determine cgroup we are running in: %m"); - - /* Chop off the init scope, if we are already located in it */ - e = endswith(m->cgroup_root, "/" SPECIAL_INIT_SCOPE); - - /* LEGACY: Also chop off the system slice if we are in - * it. This is to support live upgrades from older systemd - * versions where PID 1 was moved there. Also see - * cg_get_root_path(). */ - if (!e && m->running_as == MANAGER_SYSTEM) { - e = endswith(m->cgroup_root, "/" SPECIAL_SYSTEM_SLICE); - if (!e) - e = endswith(m->cgroup_root, "/system"); /* even more legacy */ - } - if (e) - *e = 0; - - /* And make sure to store away the root value without trailing - * slash, even for the root dir, so that we can easily prepend - * it everywhere. */ - while ((e = endswith(m->cgroup_root, "/"))) - *e = 0; - - /* 2. Show data */ - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, NULL, &path); - if (r < 0) - return log_error_errno(r, "Cannot find cgroup mount point: %m"); - - unified = cg_unified(); - if (unified < 0) - return log_error_errno(r, "Couldn't determine if we are running in the unified hierarchy: %m"); - if (unified > 0) - log_debug("Unified cgroup hierarchy is located at %s.", path); - else - log_debug("Using cgroup controller " SYSTEMD_CGROUP_CONTROLLER ". File system hierarchy is at %s.", path); - - if (!m->test_run) { - const char *scope_path; - - /* 3. Install agent */ - if (unified) { - - /* In the unified hierarchy we can can get - * cgroup empty notifications via inotify. */ - - m->cgroup_inotify_event_source = sd_event_source_unref(m->cgroup_inotify_event_source); - safe_close(m->cgroup_inotify_fd); - - m->cgroup_inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (m->cgroup_inotify_fd < 0) - return log_error_errno(errno, "Failed to create control group inotify object: %m"); - - r = sd_event_add_io(m->event, &m->cgroup_inotify_event_source, m->cgroup_inotify_fd, EPOLLIN, on_cgroup_inotify_event, m); - if (r < 0) - return log_error_errno(r, "Failed to watch control group inotify object: %m"); - - r = sd_event_source_set_priority(m->cgroup_inotify_event_source, SD_EVENT_PRIORITY_IDLE - 5); - if (r < 0) - return log_error_errno(r, "Failed to set priority of inotify event source: %m"); - - (void) sd_event_source_set_description(m->cgroup_inotify_event_source, "cgroup-inotify"); - - } else if (m->running_as == MANAGER_SYSTEM) { - - /* On the legacy hierarchy we only get - * notifications via cgroup agents. (Which - * isn't really reliable, since it does not - * generate events when control groups with - * children run empty. */ - - r = cg_install_release_agent(SYSTEMD_CGROUP_CONTROLLER, SYSTEMD_CGROUP_AGENT_PATH); - if (r < 0) - log_warning_errno(r, "Failed to install release agent, ignoring: %m"); - else if (r > 0) - log_debug("Installed release agent."); - else if (r == 0) - log_debug("Release agent already installed."); - } - - /* 4. Make sure we are in the special "init.scope" unit in the root slice. */ - scope_path = strjoina(m->cgroup_root, "/" SPECIAL_INIT_SCOPE); - r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, scope_path, 0); - if (r < 0) - return log_error_errno(r, "Failed to create %s control group: %m", scope_path); - - /* 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); - if (r < 0) - log_warning_errno(r, "Couldn't move remaining userspace processes, ignoring: %m"); - - /* 5. And pin it, so that it cannot be unmounted */ - safe_close(m->pin_cgroupfs_fd); - m->pin_cgroupfs_fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY|O_NONBLOCK); - if (m->pin_cgroupfs_fd < 0) - return log_error_errno(errno, "Failed to open pin file: %m"); - - /* 6. Always enable hierarchical support if it exists... */ - if (!unified) - (void) cg_set_attribute("memory", "/", "memory.use_hierarchy", "1"); - } - - /* 7. Figure out which controllers are supported */ - r = cg_mask_supported(&m->cgroup_supported); - if (r < 0) - 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)); - - return 0; -} - -void manager_shutdown_cgroup(Manager *m, bool delete) { - assert(m); - - /* We can't really delete the group, since we are in it. But - * let's trim it. */ - if (delete && m->cgroup_root) - (void) cg_trim(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, false); - - m->cgroup_inotify_wd_unit = hashmap_free(m->cgroup_inotify_wd_unit); - - m->cgroup_inotify_event_source = sd_event_source_unref(m->cgroup_inotify_event_source); - m->cgroup_inotify_fd = safe_close(m->cgroup_inotify_fd); - - m->pin_cgroupfs_fd = safe_close(m->pin_cgroupfs_fd); - - m->cgroup_root = mfree(m->cgroup_root); -} - -Unit* manager_get_unit_by_cgroup(Manager *m, const char *cgroup) { - char *p; - Unit *u; - - assert(m); - assert(cgroup); - - u = hashmap_get(m->cgroup_unit, cgroup); - if (u) - return u; - - p = strdupa(cgroup); - for (;;) { - char *e; - - e = strrchr(p, '/'); - if (!e || e == p) - return hashmap_get(m->cgroup_unit, SPECIAL_ROOT_SLICE); - - *e = 0; - - u = hashmap_get(m->cgroup_unit, p); - if (u) - return u; - } -} - -Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid) { - _cleanup_free_ char *cgroup = NULL; - int r; - - assert(m); - - if (pid <= 0) - return NULL; - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup); - if (r < 0) - return NULL; - - return manager_get_unit_by_cgroup(m, cgroup); -} - -Unit *manager_get_unit_by_pid(Manager *m, pid_t pid) { - Unit *u; - - assert(m); - - if (pid <= 0) - return NULL; - - if (pid == 1) - return hashmap_get(m->units, SPECIAL_INIT_SCOPE); - - u = hashmap_get(m->watch_pids1, PID_TO_PTR(pid)); - if (u) - return u; - - u = hashmap_get(m->watch_pids2, PID_TO_PTR(pid)); - if (u) - return u; - - return manager_get_unit_by_pid_cgroup(m, pid); -} - -int manager_notify_cgroup_empty(Manager *m, const char *cgroup) { - Unit *u; - - assert(m); - assert(cgroup); - - u = manager_get_unit_by_cgroup(m, cgroup); - if (!u) - return 0; - - return unit_notify_cgroup_empty(u); -} - -int unit_get_memory_current(Unit *u, uint64_t *ret) { - _cleanup_free_ char *v = NULL; - int r; - - assert(u); - assert(ret); - - if (!u->cgroup_path) - return -ENODATA; - - if ((u->cgroup_realized_mask & CGROUP_MASK_MEMORY) == 0) - return -ENODATA; - - if (cg_unified() <= 0) - r = cg_get_attribute("memory", u->cgroup_path, "memory.usage_in_bytes", &v); - else - r = cg_get_attribute("memory", u->cgroup_path, "memory.current", &v); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - - return safe_atou64(v, ret); -} - -int unit_get_tasks_current(Unit *u, uint64_t *ret) { - _cleanup_free_ char *v = NULL; - int r; - - assert(u); - assert(ret); - - if (!u->cgroup_path) - return -ENODATA; - - if ((u->cgroup_realized_mask & CGROUP_MASK_PIDS) == 0) - return -ENODATA; - - r = cg_get_attribute("pids", u->cgroup_path, "pids.current", &v); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - - return safe_atou64(v, ret); -} - -static int unit_get_cpu_usage_raw(Unit *u, nsec_t *ret) { - _cleanup_free_ char *v = NULL; - uint64_t ns; - int r; - - assert(u); - assert(ret); - - if (!u->cgroup_path) - return -ENODATA; - - if ((u->cgroup_realized_mask & CGROUP_MASK_CPUACCT) == 0) - return -ENODATA; - - r = cg_get_attribute("cpuacct", u->cgroup_path, "cpuacct.usage", &v); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - - r = safe_atou64(v, &ns); - if (r < 0) - return r; - - *ret = ns; - return 0; -} - -int unit_get_cpu_usage(Unit *u, nsec_t *ret) { - nsec_t ns; - int r; - - r = unit_get_cpu_usage_raw(u, &ns); - if (r < 0) - return r; - - if (ns > u->cpuacct_usage_base) - ns -= u->cpuacct_usage_base; - else - ns = 0; - - *ret = ns; - return 0; -} - -int unit_reset_cpu_usage(Unit *u) { - nsec_t ns; - int r; - - assert(u); - - r = unit_get_cpu_usage_raw(u, &ns); - if (r < 0) { - u->cpuacct_usage_base = 0; - return r; - } - - u->cpuacct_usage_base = ns; - return 0; -} - -bool unit_cgroup_delegate(Unit *u) { - CGroupContext *c; - - assert(u); - - c = unit_get_cgroup_context(u); - if (!c) - return false; - - return c->delegate; -} - -void unit_invalidate_cgroup(Unit *u, CGroupMask m) { - assert(u); - - if (!UNIT_HAS_CGROUP_CONTEXT(u)) - return; - - if (m == 0) - return; - - if ((u->cgroup_realized_mask & m) == 0) - return; - - u->cgroup_realized_mask &= ~m; - unit_add_to_cgroup_queue(u); -} - -void manager_invalidate_startup_units(Manager *m) { - Iterator i; - Unit *u; - - assert(m); - - SET_FOREACH(u, m->startup_units, i) - unit_invalidate_cgroup(u, CGROUP_MASK_CPU|CGROUP_MASK_BLKIO); -} - -static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = { - [CGROUP_AUTO] = "auto", - [CGROUP_CLOSED] = "closed", - [CGROUP_STRICT] = "strict", -}; - -DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy); diff --git a/src/core/cgroup.h b/src/core/cgroup.h deleted file mode 100644 index 360bbca30f..0000000000 --- a/src/core/cgroup.h +++ /dev/null @@ -1,155 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -#include "list.h" -#include "time-util.h" - -typedef struct CGroupContext CGroupContext; -typedef struct CGroupDeviceAllow CGroupDeviceAllow; -typedef struct CGroupBlockIODeviceWeight CGroupBlockIODeviceWeight; -typedef struct CGroupBlockIODeviceBandwidth CGroupBlockIODeviceBandwidth; - -typedef enum CGroupDevicePolicy { - - /* When devices listed, will allow those, plus built-in ones, - if none are listed will allow everything. */ - CGROUP_AUTO, - - /* Everything forbidden, except built-in ones and listed ones. */ - CGROUP_CLOSED, - - /* Everythings forbidden, except for the listed devices */ - CGROUP_STRICT, - - _CGROUP_DEVICE_POLICY_MAX, - _CGROUP_DEVICE_POLICY_INVALID = -1 -} CGroupDevicePolicy; - -struct CGroupDeviceAllow { - LIST_FIELDS(CGroupDeviceAllow, device_allow); - char *path; - bool r:1; - bool w:1; - bool m:1; -}; - -struct CGroupBlockIODeviceWeight { - LIST_FIELDS(CGroupBlockIODeviceWeight, device_weights); - char *path; - uint64_t weight; -}; - -struct CGroupBlockIODeviceBandwidth { - LIST_FIELDS(CGroupBlockIODeviceBandwidth, device_bandwidths); - char *path; - uint64_t bandwidth; - bool read; -}; - -struct CGroupContext { - bool cpu_accounting; - bool blockio_accounting; - bool memory_accounting; - bool tasks_accounting; - - uint64_t cpu_shares; - uint64_t startup_cpu_shares; - usec_t cpu_quota_per_sec_usec; - - uint64_t blockio_weight; - uint64_t startup_blockio_weight; - LIST_HEAD(CGroupBlockIODeviceWeight, blockio_device_weights); - LIST_HEAD(CGroupBlockIODeviceBandwidth, blockio_device_bandwidths); - - uint64_t memory_limit; - - CGroupDevicePolicy device_policy; - LIST_HEAD(CGroupDeviceAllow, device_allow); - - uint64_t tasks_max; - - bool delegate; -}; - -#include "cgroup-util.h" -#include "unit.h" - -void cgroup_context_init(CGroupContext *c); -void cgroup_context_done(CGroupContext *c); -void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix); -void cgroup_context_apply(CGroupContext *c, CGroupMask mask, const char *path, ManagerState state); - -CGroupMask cgroup_context_get_mask(CGroupContext *c); - -void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a); -void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w); -void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b); - -CGroupMask unit_get_own_mask(Unit *u); -CGroupMask unit_get_siblings_mask(Unit *u); -CGroupMask unit_get_members_mask(Unit *u); -CGroupMask unit_get_subtree_mask(Unit *u); - -CGroupMask unit_get_target_mask(Unit *u); -CGroupMask unit_get_enable_mask(Unit *u); - -void unit_update_cgroup_members_masks(Unit *u); - -char *unit_default_cgroup_path(Unit *u); -int unit_set_cgroup_path(Unit *u, const char *path); - -int unit_realize_cgroup(Unit *u); -void unit_release_cgroup(Unit *u); -void unit_prune_cgroup(Unit *u); -int unit_watch_cgroup(Unit *u); - -int unit_attach_pids_to_cgroup(Unit *u); - -int manager_setup_cgroup(Manager *m); -void manager_shutdown_cgroup(Manager *m, bool delete); - -unsigned manager_dispatch_cgroup_queue(Manager *m); - -Unit *manager_get_unit_by_cgroup(Manager *m, const char *cgroup); -Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid); -Unit* manager_get_unit_by_pid(Manager *m, pid_t pid); - -int unit_search_main_pid(Unit *u, pid_t *ret); -int unit_watch_all_pids(Unit *u); - -int unit_get_memory_current(Unit *u, uint64_t *ret); -int unit_get_tasks_current(Unit *u, uint64_t *ret); -int unit_get_cpu_usage(Unit *u, nsec_t *ret); -int unit_reset_cpu_usage(Unit *u); - -bool unit_cgroup_delegate(Unit *u); - -int unit_notify_cgroup_empty(Unit *u); -int manager_notify_cgroup_empty(Manager *m, const char *group); - -void unit_invalidate_cgroup(Unit *u, CGroupMask m); - -void manager_invalidate_startup_units(Manager *m); - -const char* cgroup_device_policy_to_string(CGroupDevicePolicy i) _const_; -CGroupDevicePolicy cgroup_device_policy_from_string(const char *s) _pure_; diff --git a/src/core/dbus-automount.c b/src/core/dbus-automount.c deleted file mode 100644 index b2806ad86f..0000000000 --- a/src/core/dbus-automount.c +++ /dev/null @@ -1,34 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "automount.h" -#include "bus-util.h" -#include "dbus-automount.h" -#include "string-util.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, automount_result, AutomountResult); - -const sd_bus_vtable bus_automount_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Where", "s", NULL, offsetof(Automount, where), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Automount, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Automount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("TimeoutIdleUSec", "t", bus_property_get_usec, offsetof(Automount, timeout_idle_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_VTABLE_END -}; diff --git a/src/core/dbus-automount.h b/src/core/dbus-automount.h deleted file mode 100644 index 7b51eb973a..0000000000 --- a/src/core/dbus-automount.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - - -extern const sd_bus_vtable bus_automount_vtable[]; diff --git a/src/core/dbus-busname.c b/src/core/dbus-busname.c deleted file mode 100644 index cf816ba15b..0000000000 --- a/src/core/dbus-busname.c +++ /dev/null @@ -1,37 +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 . -***/ - -#include "bus-util.h" -#include "busname.h" -#include "dbus-busname.h" -#include "string-util.h" -#include "unit.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, busname_result, BusNameResult); - -const sd_bus_vtable bus_busname_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Name", "s", NULL, offsetof(BusName, name), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(BusName, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(BusName, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(BusName, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Activating", "b", bus_property_get_bool, offsetof(BusName, activating), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("AcceptFileDescriptors", "b", bus_property_get_bool, offsetof(BusName, accept_fd), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_VTABLE_END -}; diff --git a/src/core/dbus-busname.h b/src/core/dbus-busname.h deleted file mode 100644 index 8643d1a404..0000000000 --- a/src/core/dbus-busname.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - - -extern const sd_bus_vtable bus_busname_vtable[]; diff --git a/src/core/dbus-cgroup.c b/src/core/dbus-cgroup.c deleted file mode 100644 index 859d155ec1..0000000000 --- a/src/core/dbus-cgroup.c +++ /dev/null @@ -1,699 +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 . -***/ - -#include "alloc-util.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "cgroup.h" -#include "dbus-cgroup.h" -#include "fd-util.h" -#include "fileio.h" -#include "path-util.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cgroup_device_policy, cgroup_device_policy, CGroupDevicePolicy); - -static int property_get_blockio_device_weight( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - CGroupContext *c = userdata; - CGroupBlockIODeviceWeight *w; - int r; - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'a', "(st)"); - if (r < 0) - return r; - - LIST_FOREACH(device_weights, w, c->blockio_device_weights) { - r = sd_bus_message_append(reply, "(st)", w->path, w->weight); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_blockio_device_bandwidths( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - CGroupContext *c = userdata; - CGroupBlockIODeviceBandwidth *b; - int r; - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'a', "(st)"); - if (r < 0) - return r; - - LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { - - if (streq(property, "BlockIOReadBandwidth") != b->read) - continue; - - r = sd_bus_message_append(reply, "(st)", b->path, b->bandwidth); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_device_allow( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - CGroupContext *c = userdata; - CGroupDeviceAllow *a; - int r; - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'a', "(ss)"); - if (r < 0) - return r; - - LIST_FOREACH(device_allow, a, c->device_allow) { - unsigned k = 0; - char rwm[4]; - - if (a->r) - rwm[k++] = 'r'; - if (a->w) - rwm[k++] = 'w'; - if (a->m) - rwm[k++] = 'm'; - - rwm[k] = 0; - - r = sd_bus_message_append(reply, "(ss)", a->path, rwm); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -const sd_bus_vtable bus_cgroup_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Delegate", "b", bus_property_get_bool, offsetof(CGroupContext, delegate), 0), - SD_BUS_PROPERTY("CPUAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, cpu_accounting), 0), - SD_BUS_PROPERTY("CPUShares", "t", NULL, offsetof(CGroupContext, cpu_shares), 0), - SD_BUS_PROPERTY("StartupCPUShares", "t", NULL, offsetof(CGroupContext, startup_cpu_shares), 0), - SD_BUS_PROPERTY("CPUQuotaPerSecUSec", "t", bus_property_get_usec, offsetof(CGroupContext, cpu_quota_per_sec_usec), 0), - SD_BUS_PROPERTY("BlockIOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, blockio_accounting), 0), - SD_BUS_PROPERTY("BlockIOWeight", "t", NULL, offsetof(CGroupContext, blockio_weight), 0), - SD_BUS_PROPERTY("StartupBlockIOWeight", "t", NULL, offsetof(CGroupContext, startup_blockio_weight), 0), - SD_BUS_PROPERTY("BlockIODeviceWeight", "a(st)", property_get_blockio_device_weight, 0, 0), - SD_BUS_PROPERTY("BlockIOReadBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0), - SD_BUS_PROPERTY("BlockIOWriteBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0), - SD_BUS_PROPERTY("MemoryAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, memory_accounting), 0), - SD_BUS_PROPERTY("MemoryLimit", "t", NULL, offsetof(CGroupContext, memory_limit), 0), - SD_BUS_PROPERTY("DevicePolicy", "s", property_get_cgroup_device_policy, offsetof(CGroupContext, device_policy), 0), - SD_BUS_PROPERTY("DeviceAllow", "a(ss)", property_get_device_allow, 0, 0), - SD_BUS_PROPERTY("TasksAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, tasks_accounting), 0), - SD_BUS_PROPERTY("TasksMax", "t", NULL, offsetof(CGroupContext, tasks_max), 0), - SD_BUS_VTABLE_END -}; - -static int bus_cgroup_set_transient_property( - Unit *u, - CGroupContext *c, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - int r; - - assert(u); - assert(c); - assert(name); - assert(message); - - if (streq(name, "Delegate")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->delegate = b; - unit_write_drop_in_private(u, mode, name, b ? "Delegate=yes" : "Delegate=no"); - } - - return 1; - } - - return 0; -} - -int bus_cgroup_set_property( - Unit *u, - CGroupContext *c, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - int r; - - assert(u); - assert(c); - assert(name); - assert(message); - - if (streq(name, "CPUAccounting")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->cpu_accounting = b; - unit_invalidate_cgroup(u, CGROUP_MASK_CPUACCT|CGROUP_MASK_CPU); - unit_write_drop_in_private(u, mode, name, b ? "CPUAccounting=yes" : "CPUAccounting=no"); - } - - return 1; - - } else if (streq(name, "CPUShares")) { - uint64_t shares; - - r = sd_bus_message_read(message, "t", &shares); - if (r < 0) - return r; - - if (!CGROUP_CPU_SHARES_IS_OK(shares)) - return sd_bus_error_set_errnof(error, EINVAL, "CPUShares value out of range"); - - if (mode != UNIT_CHECK) { - c->cpu_shares = shares; - unit_invalidate_cgroup(u, CGROUP_MASK_CPU); - - if (shares == CGROUP_CPU_SHARES_INVALID) - unit_write_drop_in_private(u, mode, name, "CPUShares="); - else - unit_write_drop_in_private_format(u, mode, name, "CPUShares=%" PRIu64, shares); - } - - return 1; - - } else if (streq(name, "StartupCPUShares")) { - uint64_t shares; - - r = sd_bus_message_read(message, "t", &shares); - if (r < 0) - return r; - - if (!CGROUP_CPU_SHARES_IS_OK(shares)) - return sd_bus_error_set_errnof(error, EINVAL, "StartupCPUShares value out of range"); - - if (mode != UNIT_CHECK) { - c->startup_cpu_shares = shares; - unit_invalidate_cgroup(u, CGROUP_MASK_CPU); - - if (shares == CGROUP_CPU_SHARES_INVALID) - unit_write_drop_in_private(u, mode, name, "StartupCPUShares="); - else - unit_write_drop_in_private_format(u, mode, name, "StartupCPUShares=%" PRIu64, shares); - } - - return 1; - - } else if (streq(name, "CPUQuotaPerSecUSec")) { - uint64_t u64; - - r = sd_bus_message_read(message, "t", &u64); - if (r < 0) - return r; - - if (u64 <= 0) - return sd_bus_error_set_errnof(error, EINVAL, "CPUQuotaPerSecUSec value out of range"); - - if (mode != UNIT_CHECK) { - c->cpu_quota_per_sec_usec = u64; - unit_invalidate_cgroup(u, CGROUP_MASK_CPU); - unit_write_drop_in_private_format(u, mode, "CPUQuota", "CPUQuota=%0.f%%", (double) (c->cpu_quota_per_sec_usec / 10000)); - } - - return 1; - - } else if (streq(name, "BlockIOAccounting")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->blockio_accounting = b; - unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); - unit_write_drop_in_private(u, mode, name, b ? "BlockIOAccounting=yes" : "BlockIOAccounting=no"); - } - - return 1; - - } else if (streq(name, "BlockIOWeight")) { - uint64_t weight; - - r = sd_bus_message_read(message, "t", &weight); - if (r < 0) - return r; - - if (!CGROUP_BLKIO_WEIGHT_IS_OK(weight)) - return sd_bus_error_set_errnof(error, EINVAL, "BlockIOWeight value out of range"); - - if (mode != UNIT_CHECK) { - c->blockio_weight = weight; - unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); - - if (weight == CGROUP_BLKIO_WEIGHT_INVALID) - unit_write_drop_in_private(u, mode, name, "BlockIOWeight="); - else - unit_write_drop_in_private_format(u, mode, name, "BlockIOWeight=%" PRIu64, weight); - } - - return 1; - - } else if (streq(name, "StartupBlockIOWeight")) { - uint64_t weight; - - r = sd_bus_message_read(message, "t", &weight); - if (r < 0) - return r; - - if (CGROUP_BLKIO_WEIGHT_IS_OK(weight)) - return sd_bus_error_set_errnof(error, EINVAL, "StartupBlockIOWeight value out of range"); - - if (mode != UNIT_CHECK) { - c->startup_blockio_weight = weight; - unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); - - if (weight == CGROUP_BLKIO_WEIGHT_INVALID) - unit_write_drop_in_private(u, mode, name, "StartupBlockIOWeight="); - else - unit_write_drop_in_private_format(u, mode, name, "StartupBlockIOWeight=%" PRIu64, weight); - } - - return 1; - - } else if (streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth")) { - const char *path; - bool read = true; - unsigned n = 0; - uint64_t u64; - - if (streq(name, "BlockIOWriteBandwidth")) - read = false; - - r = sd_bus_message_enter_container(message, 'a', "(st)"); - if (r < 0) - return r; - - while ((r = sd_bus_message_read(message, "(st)", &path, &u64)) > 0) { - - if (mode != UNIT_CHECK) { - CGroupBlockIODeviceBandwidth *a = NULL, *b; - - LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { - if (path_equal(path, b->path) && read == b->read) { - a = b; - break; - } - } - - if (!a) { - a = new0(CGroupBlockIODeviceBandwidth, 1); - if (!a) - return -ENOMEM; - - a->read = read; - a->path = strdup(path); - if (!a->path) { - free(a); - return -ENOMEM; - } - - LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, a); - } - - a->bandwidth = u64; - } - - n++; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - CGroupBlockIODeviceBandwidth *a, *next; - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *f = NULL; - size_t size = 0; - - if (n == 0) { - LIST_FOREACH_SAFE(device_bandwidths, a, next, c->blockio_device_bandwidths) - if (a->read == read) - cgroup_context_free_blockio_device_bandwidth(c, a); - } - - unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); - - f = open_memstream(&buf, &size); - if (!f) - return -ENOMEM; - - if (read) { - fputs("BlockIOReadBandwidth=\n", f); - LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) - if (a->read) - fprintf(f, "BlockIOReadBandwidth=%s %" PRIu64 "\n", a->path, a->bandwidth); - } else { - fputs("BlockIOWriteBandwidth=\n", f); - LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) - if (!a->read) - fprintf(f, "BlockIOWriteBandwidth=%s %" PRIu64 "\n", a->path, a->bandwidth); - } - - r = fflush_and_check(f); - if (r < 0) - return r; - unit_write_drop_in_private(u, mode, name, buf); - } - - return 1; - - } else if (streq(name, "BlockIODeviceWeight")) { - const char *path; - uint64_t weight; - unsigned n = 0; - - r = sd_bus_message_enter_container(message, 'a', "(st)"); - if (r < 0) - return r; - - while ((r = sd_bus_message_read(message, "(st)", &path, &weight)) > 0) { - - if (!CGROUP_BLKIO_WEIGHT_IS_OK(weight) || weight == CGROUP_BLKIO_WEIGHT_INVALID) - return sd_bus_error_set_errnof(error, EINVAL, "BlockIODeviceWeight out of range"); - - if (mode != UNIT_CHECK) { - CGroupBlockIODeviceWeight *a = NULL, *b; - - LIST_FOREACH(device_weights, b, c->blockio_device_weights) { - if (path_equal(b->path, path)) { - a = b; - break; - } - } - - if (!a) { - a = new0(CGroupBlockIODeviceWeight, 1); - if (!a) - return -ENOMEM; - - a->path = strdup(path); - if (!a->path) { - free(a); - return -ENOMEM; - } - LIST_PREPEND(device_weights,c->blockio_device_weights, a); - } - - a->weight = weight; - } - - n++; - } - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *f = NULL; - CGroupBlockIODeviceWeight *a; - size_t size = 0; - - if (n == 0) { - while (c->blockio_device_weights) - cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); - } - - unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); - - f = open_memstream(&buf, &size); - if (!f) - return -ENOMEM; - - fputs("BlockIODeviceWeight=\n", f); - LIST_FOREACH(device_weights, a, c->blockio_device_weights) - fprintf(f, "BlockIODeviceWeight=%s %" PRIu64 "\n", a->path, a->weight); - - r = fflush_and_check(f); - if (r < 0) - return r; - unit_write_drop_in_private(u, mode, name, buf); - } - - return 1; - - } else if (streq(name, "MemoryAccounting")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->memory_accounting = b; - unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY); - unit_write_drop_in_private(u, mode, name, b ? "MemoryAccounting=yes" : "MemoryAccounting=no"); - } - - return 1; - - } else if (streq(name, "MemoryLimit")) { - uint64_t limit; - - r = sd_bus_message_read(message, "t", &limit); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->memory_limit = limit; - unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY); - - if (limit == (uint64_t) -1) - unit_write_drop_in_private(u, mode, name, "MemoryLimit=infinity"); - else - unit_write_drop_in_private_format(u, mode, name, "MemoryLimit=%" PRIu64, limit); - } - - return 1; - - } else if (streq(name, "DevicePolicy")) { - const char *policy; - CGroupDevicePolicy p; - - r = sd_bus_message_read(message, "s", &policy); - if (r < 0) - return r; - - p = cgroup_device_policy_from_string(policy); - if (p < 0) - return -EINVAL; - - if (mode != UNIT_CHECK) { - char *buf; - - c->device_policy = p; - unit_invalidate_cgroup(u, CGROUP_MASK_DEVICES); - - buf = strjoina("DevicePolicy=", policy); - unit_write_drop_in_private(u, mode, name, buf); - } - - return 1; - - } else if (streq(name, "DeviceAllow")) { - const char *path, *rwm; - unsigned n = 0; - - r = sd_bus_message_enter_container(message, 'a', "(ss)"); - if (r < 0) - return r; - - while ((r = sd_bus_message_read(message, "(ss)", &path, &rwm)) > 0) { - - if ((!startswith(path, "/dev/") && - !startswith(path, "block-") && - !startswith(path, "char-")) || - strpbrk(path, WHITESPACE)) - return sd_bus_error_set_errnof(error, EINVAL, "DeviceAllow= requires device node"); - - if (isempty(rwm)) - rwm = "rwm"; - - if (!in_charset(rwm, "rwm")) - return sd_bus_error_set_errnof(error, EINVAL, "DeviceAllow= requires combination of rwm flags"); - - if (mode != UNIT_CHECK) { - CGroupDeviceAllow *a = NULL, *b; - - LIST_FOREACH(device_allow, b, c->device_allow) { - if (path_equal(b->path, path)) { - a = b; - break; - } - } - - if (!a) { - a = new0(CGroupDeviceAllow, 1); - if (!a) - return -ENOMEM; - - a->path = strdup(path); - if (!a->path) { - free(a); - return -ENOMEM; - } - - LIST_PREPEND(device_allow, c->device_allow, a); - } - - a->r = !!strchr(rwm, 'r'); - a->w = !!strchr(rwm, 'w'); - a->m = !!strchr(rwm, 'm'); - } - - n++; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *f = NULL; - CGroupDeviceAllow *a; - size_t size = 0; - - if (n == 0) { - while (c->device_allow) - cgroup_context_free_device_allow(c, c->device_allow); - } - - unit_invalidate_cgroup(u, CGROUP_MASK_DEVICES); - - f = open_memstream(&buf, &size); - if (!f) - return -ENOMEM; - - fputs("DeviceAllow=\n", f); - LIST_FOREACH(device_allow, a, c->device_allow) - fprintf(f, "DeviceAllow=%s %s%s%s\n", a->path, a->r ? "r" : "", a->w ? "w" : "", a->m ? "m" : ""); - - r = fflush_and_check(f); - if (r < 0) - return r; - unit_write_drop_in_private(u, mode, name, buf); - } - - return 1; - - } else if (streq(name, "TasksAccounting")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->tasks_accounting = b; - unit_invalidate_cgroup(u, CGROUP_MASK_PIDS); - unit_write_drop_in_private(u, mode, name, b ? "TasksAccounting=yes" : "TasksAccounting=no"); - } - - return 1; - - } else if (streq(name, "TasksMax")) { - uint64_t limit; - - r = sd_bus_message_read(message, "t", &limit); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->tasks_max = limit; - unit_invalidate_cgroup(u, CGROUP_MASK_PIDS); - - if (limit == (uint64_t) -1) - unit_write_drop_in_private(u, mode, name, "TasksMax=infinity"); - else - unit_write_drop_in_private_format(u, mode, name, "TasksMax=%" PRIu64, limit); - } - - return 1; - } - - if (u->transient && u->load_state == UNIT_STUB) { - r = bus_cgroup_set_transient_property(u, c, name, message, mode, error); - if (r != 0) - return r; - - } - - return 0; -} diff --git a/src/core/dbus-cgroup.h b/src/core/dbus-cgroup.h deleted file mode 100644 index b2212fe44e..0000000000 --- a/src/core/dbus-cgroup.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "sd-bus.h" - -#include "cgroup.h" - -extern const sd_bus_vtable bus_cgroup_vtable[]; - -int bus_cgroup_set_property(Unit *u, CGroupContext *c, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/core/dbus-device.c b/src/core/dbus-device.c deleted file mode 100644 index e1a12224d3..0000000000 --- a/src/core/dbus-device.c +++ /dev/null @@ -1,28 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "dbus-device.h" -#include "device.h" -#include "unit.h" - -const sd_bus_vtable bus_device_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("SysFSPath", "s", NULL, offsetof(Device, sysfs), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_VTABLE_END -}; diff --git a/src/core/dbus-device.h b/src/core/dbus-device.h deleted file mode 100644 index eb1d8c3278..0000000000 --- a/src/core/dbus-device.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "unit.h" - -extern const sd_bus_vtable bus_device_vtable[]; diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c deleted file mode 100644 index f2fc301f8e..0000000000 --- a/src/core/dbus-execute.c +++ /dev/null @@ -1,1564 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#ifdef HAVE_SECCOMP -#include -#endif - -#include "af-list.h" -#include "alloc-util.h" -#include "bus-util.h" -#include "capability-util.h" -#include "dbus-execute.h" -#include "env-util.h" -#include "execute.h" -#include "fd-util.h" -#include "fileio.h" -#include "ioprio.h" -#include "missing.h" -#include "namespace.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "rlimit-util.h" -#ifdef HAVE_SECCOMP -#include "seccomp-util.h" -#endif -#include "strv.h" -#include "syslog-util.h" -#include "utf8.h" - -BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput); - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_input, exec_input, ExecInput); - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode); - -static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_home, protect_home, ProtectHome); -static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_system, protect_system, ProtectSystem); - -static int property_get_environment_files( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - char **j; - int r; - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'a', "(sb)"); - if (r < 0) - return r; - - STRV_FOREACH(j, c->environment_files) { - const char *fn = *j; - - r = sd_bus_message_append(reply, "(sb)", fn[0] == '-' ? fn + 1 : fn, fn[0] == '-'); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_oom_score_adjust( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - - ExecContext *c = userdata; - int32_t n; - - assert(bus); - assert(reply); - assert(c); - - if (c->oom_score_adjust_set) - n = c->oom_score_adjust; - else { - _cleanup_free_ char *t = NULL; - - n = 0; - if (read_one_line_file("/proc/self/oom_score_adj", &t) >= 0) - safe_atoi32(t, &n); - } - - return sd_bus_message_append(reply, "i", n); -} - -static int property_get_nice( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - - ExecContext *c = userdata; - int32_t n; - - assert(bus); - assert(reply); - assert(c); - - if (c->nice_set) - n = c->nice; - else { - errno = 0; - n = getpriority(PRIO_PROCESS, 0); - if (errno > 0) - n = 0; - } - - return sd_bus_message_append(reply, "i", n); -} - -static int property_get_ioprio( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - - ExecContext *c = userdata; - int32_t n; - - assert(bus); - assert(reply); - assert(c); - - if (c->ioprio_set) - n = c->ioprio; - else { - n = ioprio_get(IOPRIO_WHO_PROCESS, 0); - if (n < 0) - n = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 4); - } - - return sd_bus_message_append(reply, "i", n); -} - -static int property_get_cpu_sched_policy( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - int32_t n; - - assert(bus); - assert(reply); - assert(c); - - if (c->cpu_sched_set) - n = c->cpu_sched_policy; - else { - n = sched_getscheduler(0); - if (n < 0) - n = SCHED_OTHER; - } - - return sd_bus_message_append(reply, "i", n); -} - -static int property_get_cpu_sched_priority( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - int32_t n; - - assert(bus); - assert(reply); - assert(c); - - if (c->cpu_sched_set) - n = c->cpu_sched_priority; - else { - struct sched_param p = {}; - - if (sched_getparam(0, &p) >= 0) - n = p.sched_priority; - else - n = 0; - } - - return sd_bus_message_append(reply, "i", n); -} - -static int property_get_cpu_affinity( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - if (c->cpuset) - return sd_bus_message_append_array(reply, 'y', c->cpuset, CPU_ALLOC_SIZE(c->cpuset_ncpus)); - else - return sd_bus_message_append_array(reply, 'y', NULL, 0); -} - -static int property_get_timer_slack_nsec( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - uint64_t u; - - assert(bus); - assert(reply); - assert(c); - - if (c->timer_slack_nsec != NSEC_INFINITY) - u = (uint64_t) c->timer_slack_nsec; - else - u = (uint64_t) prctl(PR_GET_TIMERSLACK); - - return sd_bus_message_append(reply, "t", u); -} - -static int property_get_capability_bounding_set( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "t", c->capability_bounding_set); -} - -static int property_get_ambient_capabilities( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "t", c->capability_ambient_set); -} - -static int property_get_capabilities( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - _cleanup_cap_free_charp_ char *t = NULL; - const char *s; - - assert(bus); - assert(reply); - assert(c); - - if (c->capabilities) - s = t = cap_to_text(c->capabilities, NULL); - else - s = ""; - - if (!s) - return -ENOMEM; - - return sd_bus_message_append(reply, "s", s); -} - -static int property_get_syscall_filter( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - _cleanup_strv_free_ char **l = NULL; - int r; - -#ifdef HAVE_SECCOMP - Iterator i; - void *id; -#endif - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'r', "bas"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "b", c->syscall_whitelist); - if (r < 0) - return r; - -#ifdef HAVE_SECCOMP - SET_FOREACH(id, c->syscall_filter, i) { - char *name; - - name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1); - if (!name) - continue; - - r = strv_consume(&l, name); - if (r < 0) - return r; - } -#endif - - strv_sort(l); - - r = sd_bus_message_append_strv(reply, l); - if (r < 0) - return r; - - return sd_bus_message_close_container(reply); -} - -static int property_get_syscall_archs( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - _cleanup_strv_free_ char **l = NULL; - int r; - -#ifdef HAVE_SECCOMP - Iterator i; - void *id; -#endif - - assert(bus); - assert(reply); - assert(c); - -#ifdef HAVE_SECCOMP - SET_FOREACH(id, c->syscall_archs, i) { - const char *name; - - name = seccomp_arch_to_string(PTR_TO_UINT32(id) - 1); - if (!name) - continue; - - r = strv_extend(&l, name); - if (r < 0) - return -ENOMEM; - } -#endif - - strv_sort(l); - - r = sd_bus_message_append_strv(reply, l); - if (r < 0) - return r; - - return 0; -} - -static int property_get_syscall_errno( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "i", (int32_t) c->syscall_errno); -} - -static int property_get_selinux_context( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "(bs)", c->selinux_context_ignore, c->selinux_context); -} - -static int property_get_apparmor_profile( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "(bs)", c->apparmor_profile_ignore, c->apparmor_profile); -} - -static int property_get_smack_process_label( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "(bs)", c->smack_process_label_ignore, c->smack_process_label); -} - -static int property_get_personality( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "s", personality_to_string(c->personality)); -} - -static int property_get_address_families( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - _cleanup_strv_free_ char **l = NULL; - Iterator i; - void *af; - int r; - - assert(bus); - assert(reply); - assert(c); - - r = sd_bus_message_open_container(reply, 'r', "bas"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "b", c->address_families_whitelist); - if (r < 0) - return r; - - SET_FOREACH(af, c->address_families, i) { - const char *name; - - name = af_to_name(PTR_TO_INT(af)); - if (!name) - continue; - - r = strv_extend(&l, name); - if (r < 0) - return -ENOMEM; - } - - strv_sort(l); - - r = sd_bus_message_append_strv(reply, l); - if (r < 0) - return r; - - return sd_bus_message_close_container(reply); -} - -static int property_get_working_directory( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - const char *wd; - - assert(bus); - assert(reply); - assert(c); - - if (c->working_directory_home) - wd = "~"; - else - wd = c->working_directory; - - if (c->working_directory_missing_ok) - wd = strjoina("!", wd); - - return sd_bus_message_append(reply, "s", wd); -} - -static int property_get_syslog_level( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "i", LOG_PRI(c->syslog_priority)); -} - -static int property_get_syslog_facility( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - ExecContext *c = userdata; - - assert(bus); - assert(reply); - assert(c); - - return sd_bus_message_append(reply, "i", LOG_FAC(c->syslog_priority)); -} - -const sd_bus_vtable bus_exec_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("EnvironmentFiles", "a(sb)", property_get_environment_files, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PassEnvironment", "as", NULL, offsetof(ExecContext, pass_environment), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("UMask", "u", bus_property_get_mode, offsetof(ExecContext, umask), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitCPU", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitCPUSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitFSIZE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitFSIZESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitDATA", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitDATASoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitSTACK", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitSTACKSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitCORE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitCORESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitRSS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitRSSSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitNOFILE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitNOFILESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitAS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitASSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitNPROC", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitNPROCSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitMEMLOCK", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitMEMLOCKSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitLOCKS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitLOCKSSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitSIGPENDING", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitSIGPENDINGSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitMSGQUEUE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitMSGQUEUESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitNICE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitNICESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitRTPRIO", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitRTPRIOSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitRTTIME", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LimitRTTIMESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("WorkingDirectory", "s", property_get_working_directory, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(ExecContext, root_directory), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("OOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Nice", "i", property_get_nice, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("IOScheduling", "i", property_get_ioprio, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CPUSchedulingPolicy", "i", property_get_cpu_sched_policy, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CPUSchedulingPriority", "i", property_get_cpu_sched_priority, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CPUAffinity", "ay", property_get_cpu_affinity, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CPUSchedulingResetOnFork", "b", bus_property_get_bool, offsetof(ExecContext, cpu_sched_reset_on_fork), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NonBlocking", "b", bus_property_get_bool, offsetof(ExecContext, non_blocking), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StandardInput", "s", property_get_exec_input, offsetof(ExecContext, std_input), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StandardOutput", "s", bus_property_get_exec_output, offsetof(ExecContext, std_output), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StandardError", "s", bus_property_get_exec_output, offsetof(ExecContext, std_error), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TTYPath", "s", NULL, offsetof(ExecContext, tty_path), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TTYReset", "b", bus_property_get_bool, offsetof(ExecContext, tty_reset), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TTYVHangup", "b", bus_property_get_bool, offsetof(ExecContext, tty_vhangup), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TTYVTDisallocate", "b", bus_property_get_bool, offsetof(ExecContext, tty_vt_disallocate), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SyslogPriority", "i", bus_property_get_int, offsetof(ExecContext, syslog_priority), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SyslogIdentifier", "s", NULL, offsetof(ExecContext, syslog_identifier), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SyslogLevelPrefix", "b", bus_property_get_bool, offsetof(ExecContext, syslog_level_prefix), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SyslogLevel", "i", property_get_syslog_level, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SyslogFacility", "i", property_get_syslog_facility, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Capabilities", "s", property_get_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CapabilityBoundingSet", "t", property_get_capability_bounding_set, 0, SD_BUS_VTABLE_PROPERTY_CONST), - 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("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("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), - SD_BUS_PROPERTY("PrivateDevices", "b", bus_property_get_bool, offsetof(ExecContext, private_devices), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ProtectHome", "s", bus_property_get_protect_home, offsetof(ExecContext, protect_home), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ProtectSystem", "s", bus_property_get_protect_system, offsetof(ExecContext, protect_system), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SameProcessGroup", "b", bus_property_get_bool, offsetof(ExecContext, same_pgrp), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("UtmpIdentifier", "s", NULL, offsetof(ExecContext, utmp_id), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("UtmpMode", "s", property_get_exec_utmp_mode, offsetof(ExecContext, utmp_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SELinuxContext", "(bs)", property_get_selinux_context, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("AppArmorProfile", "(bs)", property_get_apparmor_profile, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SmackProcessLabel", "(bs)", property_get_smack_process_label, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("IgnoreSIGPIPE", "b", bus_property_get_bool, offsetof(ExecContext, ignore_sigpipe), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NoNewPrivileges", "b", bus_property_get_bool, offsetof(ExecContext, no_new_privileges), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SystemCallFilter", "(bas)", property_get_syscall_filter, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SystemCallArchitectures", "as", property_get_syscall_archs, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SystemCallErrorNumber", "i", property_get_syscall_errno, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Personality", "s", property_get_personality, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST), - 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_VTABLE_END -}; - -static int append_exec_command(sd_bus_message *reply, ExecCommand *c) { - int r; - - assert(reply); - assert(c); - - if (!c->path) - return 0; - - r = sd_bus_message_open_container(reply, 'r', "sasbttttuii"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "s", c->path); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(reply, c->argv); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "bttttuii", - c->ignore, - c->exec_status.start_timestamp.realtime, - c->exec_status.start_timestamp.monotonic, - c->exec_status.exit_timestamp.realtime, - c->exec_status.exit_timestamp.monotonic, - (uint32_t) c->exec_status.pid, - (int32_t) c->exec_status.code, - (int32_t) c->exec_status.status); - if (r < 0) - return r; - - return sd_bus_message_close_container(reply); -} - -int bus_property_get_exec_command( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - - ExecCommand *c = (ExecCommand*) userdata; - int r; - - assert(bus); - assert(reply); - - r = sd_bus_message_open_container(reply, 'a', "(sasbttttuii)"); - if (r < 0) - return r; - - r = append_exec_command(reply, c); - if (r < 0) - return r; - - return sd_bus_message_close_container(reply); -} - -int bus_property_get_exec_command_list( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *ret_error) { - - ExecCommand *c = *(ExecCommand**) userdata; - int r; - - assert(bus); - assert(reply); - - r = sd_bus_message_open_container(reply, 'a', "(sasbttttuii)"); - if (r < 0) - return r; - - LIST_FOREACH(command, c, c) { - r = append_exec_command(reply, c); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -int bus_exec_context_set_transient_property( - Unit *u, - ExecContext *c, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - const char *soft = NULL; - int r, ri; - - assert(u); - assert(c); - assert(name); - assert(message); - - if (streq(name, "User")) { - const char *uu; - - r = sd_bus_message_read(message, "s", &uu); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - - if (isempty(uu)) { - c->user = mfree(c->user); - } else { - char *t; - - t = strdup(uu); - if (!t) - return -ENOMEM; - - free(c->user); - c->user = t; - } - - unit_write_drop_in_private_format(u, mode, name, "User=%s\n", uu); - } - - return 1; - - } else if (streq(name, "Group")) { - const char *gg; - - r = sd_bus_message_read(message, "s", &gg); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - - if (isempty(gg)) { - c->group = mfree(c->group); - } else { - char *t; - - t = strdup(gg); - if (!t) - return -ENOMEM; - - free(c->group); - c->group = t; - } - - unit_write_drop_in_private_format(u, mode, name, "Group=%s\n", gg); - } - - return 1; - } else if (streq(name, "SyslogIdentifier")) { - const char *id; - - r = sd_bus_message_read(message, "s", &id); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - - if (isempty(id)) { - c->syslog_identifier = mfree(c->syslog_identifier); - } else { - char *t; - - t = strdup(id); - if (!t) - return -ENOMEM; - - free(c->syslog_identifier); - c->syslog_identifier = t; - } - - unit_write_drop_in_private_format(u, mode, name, "SyslogIdentifier=%s\n", id); - } - - return 1; - } else if (streq(name, "SyslogLevel")) { - int level; - - r = sd_bus_message_read(message, "i", &level); - if (r < 0) - return r; - - if (!log_level_is_valid(level)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Log level value out of range"); - - if (mode != UNIT_CHECK) { - c->syslog_priority = (c->syslog_priority & LOG_FACMASK) | level; - unit_write_drop_in_private_format(u, mode, name, "SyslogLevel=%i\n", level); - } - - return 1; - } else if (streq(name, "SyslogFacility")) { - int facility; - - r = sd_bus_message_read(message, "i", &facility); - if (r < 0) - return r; - - if (!log_facility_unshifted_is_valid(facility)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Log facility value out of range"); - - if (mode != UNIT_CHECK) { - c->syslog_priority = (facility << 3) | LOG_PRI(c->syslog_priority); - unit_write_drop_in_private_format(u, mode, name, "SyslogFacility=%i\n", facility); - } - - return 1; - } else if (streq(name, "Nice")) { - int n; - - r = sd_bus_message_read(message, "i", &n); - if (r < 0) - return r; - - if (n < PRIO_MIN || n >= PRIO_MAX) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Nice value out of range"); - - if (mode != UNIT_CHECK) { - c->nice = n; - unit_write_drop_in_private_format(u, mode, name, "Nice=%i\n", n); - } - - return 1; - - } else if (STR_IN_SET(name, "TTYPath", "RootDirectory")) { - const char *s; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - if (!path_is_absolute(s)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s takes an absolute path", name); - - if (mode != UNIT_CHECK) { - if (streq(name, "TTYPath")) - r = free_and_strdup(&c->tty_path, s); - else { - assert(streq(name, "RootDirectory")); - r = free_and_strdup(&c->root_directory, s); - } - if (r < 0) - return r; - - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, s); - } - - return 1; - - } else if (streq(name, "WorkingDirectory")) { - const char *s; - bool missing_ok; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - if (s[0] == '-') { - missing_ok = true; - s++; - } else - missing_ok = false; - - if (!streq(s, "~") && !path_is_absolute(s)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "WorkingDirectory= expects an absolute path or '~'"); - - if (mode != UNIT_CHECK) { - if (streq(s, "~")) { - c->working_directory = mfree(c->working_directory); - c->working_directory_home = true; - } else { - r = free_and_strdup(&c->working_directory, s); - if (r < 0) - return r; - - c->working_directory_home = false; - } - - c->working_directory_missing_ok = missing_ok; - unit_write_drop_in_private_format(u, mode, name, "WorkingDirectory=%s%s", missing_ok ? "-" : "", s); - } - - return 1; - - } else if (streq(name, "StandardInput")) { - const char *s; - ExecInput p; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - p = exec_input_from_string(s); - if (p < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard input name"); - - if (mode != UNIT_CHECK) { - c->std_input = p; - - unit_write_drop_in_private_format(u, mode, name, "StandardInput=%s\n", exec_input_to_string(p)); - } - - return 1; - - - } else if (streq(name, "StandardOutput")) { - const char *s; - ExecOutput p; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - p = exec_output_from_string(s); - if (p < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard output name"); - - if (mode != UNIT_CHECK) { - c->std_output = p; - - unit_write_drop_in_private_format(u, mode, name, "StandardOutput=%s\n", exec_output_to_string(p)); - } - - return 1; - - } else if (streq(name, "StandardError")) { - const char *s; - ExecOutput p; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - p = exec_output_from_string(s); - if (p < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard error name"); - - if (mode != UNIT_CHECK) { - c->std_error = p; - - unit_write_drop_in_private_format(u, mode, name, "StandardError=%s\n", exec_output_to_string(p)); - } - - return 1; - - } else if (STR_IN_SET(name, - "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", - "PrivateTmp", "PrivateDevices", "PrivateNetwork", - "NoNewPrivileges", "SyslogLevelPrefix")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - if (streq(name, "IgnoreSIGPIPE")) - c->ignore_sigpipe = b; - else if (streq(name, "TTYVHangup")) - c->tty_vhangup = b; - else if (streq(name, "TTYReset")) - c->tty_reset = b; - else if (streq(name, "PrivateTmp")) - c->private_tmp = b; - else if (streq(name, "PrivateDevices")) - c->private_devices = b; - else if (streq(name, "PrivateNetwork")) - c->private_network = b; - else if (streq(name, "NoNewPrivileges")) - c->no_new_privileges = b; - else if (streq(name, "SyslogLevelPrefix")) - c->syslog_level_prefix = b; - - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, yes_no(b)); - } - - return 1; - - } else if (streq(name, "UtmpIdentifier")) { - const char *id; - - r = sd_bus_message_read(message, "s", &id); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - if (isempty(id)) - c->utmp_id = mfree(c->utmp_id); - else if (free_and_strdup(&c->utmp_id, id) < 0) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "UtmpIdentifier=%s\n", strempty(id)); - } - - return 1; - - } else if (streq(name, "UtmpMode")) { - const char *s; - ExecUtmpMode m; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - m = exec_utmp_mode_from_string(s); - if (m < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid utmp mode"); - - if (mode != UNIT_CHECK) { - c->utmp_mode = m; - - unit_write_drop_in_private_format(u, mode, name, "UtmpMode=%s\n", exec_utmp_mode_to_string(m)); - } - - return 1; - - } else if (streq(name, "PAMName")) { - const char *n; - - r = sd_bus_message_read(message, "s", &n); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - if (isempty(n)) - c->pam_name = mfree(c->pam_name); - else if (free_and_strdup(&c->pam_name, n) < 0) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "PAMName=%s\n", strempty(n)); - } - - return 1; - - } else if (streq(name, "Environment")) { - - _cleanup_strv_free_ char **l = NULL; - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - if (!strv_env_is_valid(l)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment block."); - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *joined = NULL; - char **e; - - if (strv_length(l) == 0) { - c->environment = strv_free(c->environment); - unit_write_drop_in_private_format(u, mode, name, "Environment=\n"); - } else { - e = strv_env_merge(2, c->environment, l); - if (!e) - return -ENOMEM; - - strv_free(c->environment); - c->environment = e; - - joined = strv_join_quoted(c->environment); - if (!joined) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "Environment=%s\n", joined); - } - } - - return 1; - - } else if (streq(name, "TimerSlackNSec")) { - - nsec_t n; - - r = sd_bus_message_read(message, "t", &n); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->timer_slack_nsec = n; - unit_write_drop_in_private_format(u, mode, name, "TimerSlackNSec=" NSEC_FMT "\n", n); - } - - return 1; - - } else if (streq(name, "OOMScoreAdjust")) { - int oa; - - r = sd_bus_message_read(message, "i", &oa); - if (r < 0) - return r; - - if (!oom_score_adjust_is_valid(oa)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "OOM score adjust value out of range"); - - if (mode != UNIT_CHECK) { - c->oom_score_adjust = oa; - c->oom_score_adjust_set = true; - unit_write_drop_in_private_format(u, mode, name, "OOMScoreAdjust=%i\n", oa); - } - - return 1; - - } else if (streq(name, "EnvironmentFiles")) { - - _cleanup_free_ char *joined = NULL; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char **l = NULL; - size_t size = 0; - char **i; - - r = sd_bus_message_enter_container(message, 'a', "(sb)"); - if (r < 0) - return r; - - f = open_memstream(&joined, &size); - if (!f) - return -ENOMEM; - - STRV_FOREACH(i, c->environment_files) - fprintf(f, "EnvironmentFile=%s\n", *i); - - while ((r = sd_bus_message_enter_container(message, 'r', "sb")) > 0) { - const char *path; - int b; - - r = sd_bus_message_read(message, "sb", &path, &b); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (!isempty(path) && !path_is_absolute(path)) - return sd_bus_error_set_errnof(error, EINVAL, "Path %s is not absolute.", path); - - if (mode != UNIT_CHECK) { - char *buf = NULL; - - buf = strjoin(b ? "-" : "", path, NULL); - if (!buf) - return -ENOMEM; - - fprintf(f, "EnvironmentFile=%s\n", buf); - - r = strv_consume(&l, buf); - if (r < 0) - return r; - } - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - r = fflush_and_check(f); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - if (strv_isempty(l)) { - c->environment_files = strv_free(c->environment_files); - unit_write_drop_in_private(u, mode, name, "EnvironmentFile=\n"); - } else { - r = strv_extend_strv(&c->environment_files, l, true); - if (r < 0) - return r; - - unit_write_drop_in_private(u, mode, name, joined); - } - } - - return 1; - - } else if (streq(name, "PassEnvironment")) { - - _cleanup_strv_free_ char **l = NULL; - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - if (!strv_env_name_is_valid(l)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PassEnvironment block."); - - if (mode != UNIT_CHECK) { - if (strv_isempty(l)) { - c->pass_environment = strv_free(c->pass_environment); - unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=\n"); - } else { - _cleanup_free_ char *joined = NULL; - - r = strv_extend_strv(&c->pass_environment, l, true); - if (r < 0) - return r; - - joined = strv_join_quoted(c->pass_environment); - if (!joined) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=%s\n", joined); - } - } - - return 1; - - } else if (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) { - - _cleanup_strv_free_ char **l = NULL; - char ***dirs; - char **p; - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - STRV_FOREACH(p, l) { - int offset; - if (!utf8_is_valid(*p)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s", name); - - offset = **p == '-'; - if (!path_is_absolute(*p + offset)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s", name); - } - - 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 (strv_length(l) == 0) { - *dirs = strv_free(*dirs); - unit_write_drop_in_private_format(u, mode, name, "%s=\n", name); - } else { - r = strv_extend_strv(dirs, l, true); - - if (r < 0) - return -ENOMEM; - - joined = strv_join_quoted(*dirs); - if (!joined) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, joined); - } - - } - - return 1; - - } else if (streq(name, "ProtectSystem")) { - const char *s; - ProtectSystem ps; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - r = parse_boolean(s); - if (r > 0) - ps = PROTECT_SYSTEM_YES; - else if (r == 0) - ps = PROTECT_SYSTEM_NO; - else { - ps = protect_system_from_string(s); - if (ps < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse protect system value"); - } - - if (mode != UNIT_CHECK) { - c->protect_system = ps; - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, s); - } - - return 1; - - } else if (streq(name, "ProtectHome")) { - const char *s; - ProtectHome ph; - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - r = parse_boolean(s); - if (r > 0) - ph = PROTECT_HOME_YES; - else if (r == 0) - ph = PROTECT_HOME_NO; - else { - ph = protect_home_from_string(s); - if (ph < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse protect home value"); - } - - if (mode != UNIT_CHECK) { - c->protect_home = ph; - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, s); - } - - return 1; - - } else if (streq(name, "RuntimeDirectory")) { - _cleanup_strv_free_ char **l = NULL; - char **p; - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - STRV_FOREACH(p, l) { - if (!filename_is_valid(*p)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Runtime directory is not valid %s", *p); - } - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *joined = NULL; - - if (strv_isempty(l)) { - c->runtime_directory = strv_free(c->runtime_directory); - unit_write_drop_in_private_format(u, mode, name, "%s=\n", name); - } else { - r = strv_extend_strv(&c->runtime_directory, l, true); - - if (r < 0) - return -ENOMEM; - - joined = strv_join_quoted(c->runtime_directory); - if (!joined) - return -ENOMEM; - - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, joined); - } - } - - return 1; - - } - - ri = rlimit_from_string(name); - if (ri < 0) { - soft = endswith(name, "Soft"); - if (soft) { - const char *n; - - n = strndupa(name, soft - name); - ri = rlimit_from_string(n); - if (ri >= 0) - name = n; - - } - } - - if (ri >= 0) { - uint64_t rl; - rlim_t x; - - r = sd_bus_message_read(message, "t", &rl); - if (r < 0) - return r; - - if (rl == (uint64_t) -1) - x = RLIM_INFINITY; - else { - x = (rlim_t) rl; - - if ((uint64_t) x != rl) - return -ERANGE; - } - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *f = NULL; - struct rlimit nl; - - if (c->rlimit[ri]) { - nl = *c->rlimit[ri]; - - if (soft) - nl.rlim_cur = x; - else - nl.rlim_max = x; - } else - /* When the resource limit is not initialized yet, then assign the value to both fields */ - nl = (struct rlimit) { - .rlim_cur = x, - .rlim_max = x, - }; - - r = rlimit_format(&nl, &f); - if (r < 0) - return r; - - if (c->rlimit[ri]) - *c->rlimit[ri] = nl; - else { - c->rlimit[ri] = newdup(struct rlimit, &nl, 1); - if (!c->rlimit[ri]) - return -ENOMEM; - } - - unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, f); - } - - return 1; - } - - return 0; -} diff --git a/src/core/dbus-execute.h b/src/core/dbus-execute.h deleted file mode 100644 index d0aa8e1dd5..0000000000 --- a/src/core/dbus-execute.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "sd-bus.h" - -#include "execute.h" - -#define BUS_EXEC_STATUS_VTABLE(prefix, offset, flags) \ - BUS_PROPERTY_DUAL_TIMESTAMP(prefix "StartTimestamp", (offset) + offsetof(ExecStatus, start_timestamp), flags), \ - BUS_PROPERTY_DUAL_TIMESTAMP(prefix "ExitTimestamp", (offset) + offsetof(ExecStatus, exit_timestamp), flags), \ - SD_BUS_PROPERTY(prefix "PID", "u", bus_property_get_pid, (offset) + offsetof(ExecStatus, pid), flags), \ - SD_BUS_PROPERTY(prefix "Code", "i", bus_property_get_int, (offset) + offsetof(ExecStatus, code), flags), \ - SD_BUS_PROPERTY(prefix "Status", "i", bus_property_get_int, (offset) + offsetof(ExecStatus, status), flags) - -#define BUS_EXEC_COMMAND_VTABLE(name, offset, flags) \ - SD_BUS_PROPERTY(name, "a(sasbttttuii)", bus_property_get_exec_command, offset, flags) - -#define BUS_EXEC_COMMAND_LIST_VTABLE(name, offset, flags) \ - SD_BUS_PROPERTY(name, "a(sasbttttuii)", bus_property_get_exec_command_list, offset, flags) - -extern const sd_bus_vtable bus_exec_vtable[]; - -int bus_property_get_exec_output(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); -int bus_property_get_exec_command(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); -int bus_property_get_exec_command_list(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); - -int bus_exec_context_set_transient_property(Unit *u, ExecContext *c, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/core/dbus-job.c b/src/core/dbus-job.c deleted file mode 100644 index 97a93fb2f1..0000000000 --- a/src/core/dbus-job.c +++ /dev/null @@ -1,193 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "dbus-job.h" -#include "dbus.h" -#include "job.h" -#include "log.h" -#include "selinux-access.h" -#include "string-util.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, job_type, JobType); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_state, job_state, JobState); - -static int property_get_unit( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_free_ char *p = NULL; - Job *j = userdata; - - assert(bus); - assert(reply); - assert(j); - - p = unit_dbus_path(j->unit); - if (!p) - return -ENOMEM; - - return sd_bus_message_append(reply, "(so)", j->unit->id, p); -} - -int bus_job_method_cancel(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Job *j = userdata; - int r; - - assert(message); - assert(j); - - r = mac_selinux_unit_access_check(j->unit, message, "stop", error); - if (r < 0) - return r; - - /* Access is granted to the job owner */ - if (!sd_bus_track_contains(j->clients, sd_bus_message_get_sender(message))) { - - /* And for everybody else consult PolicyKit */ - r = bus_verify_manage_units_async(j->unit->manager, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - } - - job_finish_and_invalidate(j, JOB_CANCELED, true); - - return sd_bus_reply_method_return(message, NULL); -} - -const sd_bus_vtable bus_job_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("Cancel", NULL, NULL, bus_job_method_cancel, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_PROPERTY("Id", "u", NULL, offsetof(Job, id), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Unit", "(so)", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("JobType", "s", property_get_type, offsetof(Job, type), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("State", "s", property_get_state, offsetof(Job, state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_VTABLE_END -}; - -static int send_new_signal(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *p = NULL; - Job *j = userdata; - int r; - - assert(bus); - assert(j); - - p = job_dbus_path(j); - if (!p) - return -ENOMEM; - - r = sd_bus_message_new_signal( - bus, - &m, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "JobNew"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "uos", j->id, p, j->unit->id); - if (r < 0) - return r; - - return sd_bus_send(bus, m, NULL); -} - -static int send_changed_signal(sd_bus *bus, void *userdata) { - _cleanup_free_ char *p = NULL; - Job *j = userdata; - - assert(bus); - assert(j); - - p = job_dbus_path(j); - if (!p) - return -ENOMEM; - - return sd_bus_emit_properties_changed(bus, p, "org.freedesktop.systemd1.Job", "State", NULL); -} - -void bus_job_send_change_signal(Job *j) { - int r; - - assert(j); - - if (j->in_dbus_queue) { - LIST_REMOVE(dbus_queue, j->manager->dbus_job_queue, j); - j->in_dbus_queue = false; - } - - r = bus_foreach_bus(j->manager, j->clients, j->sent_dbus_new_signal ? send_changed_signal : send_new_signal, j); - if (r < 0) - log_debug_errno(r, "Failed to send job change signal for %u: %m", j->id); - - j->sent_dbus_new_signal = true; -} - -static int send_removed_signal(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *p = NULL; - Job *j = userdata; - int r; - - assert(bus); - assert(j); - - p = job_dbus_path(j); - if (!p) - return -ENOMEM; - - r = sd_bus_message_new_signal( - bus, - &m, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "JobRemoved"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "uoss", j->id, p, j->unit->id, job_result_to_string(j->result)); - if (r < 0) - return r; - - return sd_bus_send(bus, m, NULL); -} - -void bus_job_send_removed_signal(Job *j) { - int r; - - assert(j); - - if (!j->sent_dbus_new_signal) - bus_job_send_change_signal(j); - - r = bus_foreach_bus(j->manager, j->clients, send_removed_signal, j); - if (r < 0) - log_debug_errno(r, "Failed to send job remove signal for %u: %m", j->id); -} diff --git a/src/core/dbus-job.h b/src/core/dbus-job.h deleted file mode 100644 index 024d06719e..0000000000 --- a/src/core/dbus-job.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "sd-bus.h" - -#include "job.h" - -extern const sd_bus_vtable bus_job_vtable[]; - -int bus_job_method_cancel(sd_bus_message *message, void *job, sd_bus_error *error); - -void bus_job_send_change_signal(Job *j); -void bus_job_send_removed_signal(Job *j); diff --git a/src/core/dbus-kill.c b/src/core/dbus-kill.c deleted file mode 100644 index fc50fafaad..0000000000 --- a/src/core/dbus-kill.c +++ /dev/null @@ -1,122 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include "bus-util.h" -#include "dbus-kill.h" -#include "kill.h" -#include "signal-util.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_kill_mode, kill_mode, KillMode); - -const sd_bus_vtable bus_kill_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("KillMode", "s", property_get_kill_mode, offsetof(KillContext, kill_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("KillSignal", "i", bus_property_get_int, offsetof(KillContext, kill_signal), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SendSIGKILL", "b", bus_property_get_bool, offsetof(KillContext, send_sigkill), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SendSIGHUP", "b", bus_property_get_bool, offsetof(KillContext, send_sighup), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_VTABLE_END -}; - -int bus_kill_context_set_transient_property( - Unit *u, - KillContext *c, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - int r; - - assert(u); - assert(c); - assert(name); - assert(message); - - if (streq(name, "KillMode")) { - const char *m; - KillMode k; - - r = sd_bus_message_read(message, "s", &m); - if (r < 0) - return r; - - k = kill_mode_from_string(m); - if (k < 0) - return -EINVAL; - - if (mode != UNIT_CHECK) { - c->kill_mode = k; - - unit_write_drop_in_private_format(u, mode, name, "KillMode=%s\n", kill_mode_to_string(k)); - } - - return 1; - - } else if (streq(name, "KillSignal")) { - int sig; - - r = sd_bus_message_read(message, "i", &sig); - if (r < 0) - return r; - - if (sig <= 0 || sig >= _NSIG) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Signal %i out of range", sig); - - if (mode != UNIT_CHECK) { - c->kill_signal = sig; - - unit_write_drop_in_private_format(u, mode, name, "KillSignal=%s\n", signal_to_string(sig)); - } - - return 1; - - } else if (streq(name, "SendSIGHUP")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->send_sighup = b; - - unit_write_drop_in_private_format(u, mode, name, "SendSIGHUP=%s\n", yes_no(b)); - } - - return 1; - - } else if (streq(name, "SendSIGKILL")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - c->send_sigkill = b; - - unit_write_drop_in_private_format(u, mode, name, "SendSIGKILL=%s\n", yes_no(b)); - } - - return 1; - - } - - return 0; -} diff --git a/src/core/dbus-kill.h b/src/core/dbus-kill.h deleted file mode 100644 index b9b18811e3..0000000000 --- a/src/core/dbus-kill.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include "sd-bus.h" - -#include "kill.h" -#include "unit.h" - -extern const sd_bus_vtable bus_kill_vtable[]; - -int bus_kill_context_set_transient_property(Unit *u, KillContext *c, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c deleted file mode 100644 index f939196397..0000000000 --- a/src/core/dbus-manager.c +++ /dev/null @@ -1,2137 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#include "alloc-util.h" -#include "architecture.h" -#include "build.h" -#include "bus-common-errors.h" -#include "clock-util.h" -#include "dbus-execute.h" -#include "dbus-job.h" -#include "dbus-manager.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "env-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "install.h" -#include "log.h" -#include "path-util.h" -#include "selinux-access.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "syslog-util.h" -#include "virt.h" -#include "watchdog.h" - -static int property_get_version( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "s", PACKAGE_VERSION); -} - -static int property_get_features( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "s", SYSTEMD_FEATURES); -} - -static int property_get_virtualization( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - int v; - - assert(bus); - assert(reply); - - v = detect_virtualization(); - - /* Make sure to return the empty string when we detect no virtualization, as that is the API. - * - * https://github.com/systemd/systemd/issues/1423 - */ - - return sd_bus_message_append( - reply, "s", - v == VIRTUALIZATION_NONE ? "" : virtualization_to_string(v)); -} - -static int property_get_architecture( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "s", architecture_to_string(uname_architecture())); -} - -static int property_get_tainted( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - char buf[sizeof("split-usr:cgroups-missing:local-hwclock:")] = "", *e = buf; - Manager *m = userdata; - - assert(bus); - assert(reply); - assert(m); - - if (m->taint_usr) - e = stpcpy(e, "split-usr:"); - - if (access("/proc/cgroups", F_OK) < 0) - e = stpcpy(e, "cgroups-missing:"); - - if (clock_is_localtime() > 0) - e = stpcpy(e, "local-hwclock:"); - - /* remove the last ':' */ - if (e != buf) - e[-1] = 0; - - return sd_bus_message_append(reply, "s", buf); -} - -static int property_get_log_target( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "s", log_target_to_string(log_get_target())); -} - -static int property_set_log_target( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *value, - void *userdata, - sd_bus_error *error) { - - const char *t; - int r; - - assert(bus); - assert(value); - - r = sd_bus_message_read(value, "s", &t); - if (r < 0) - return r; - - return log_set_target_from_string(t); -} - -static int property_get_log_level( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_free_ char *t = NULL; - int r; - - assert(bus); - assert(reply); - - r = log_level_to_string_alloc(log_get_max_level(), &t); - if (r < 0) - return r; - - return sd_bus_message_append(reply, "s", t); -} - -static int property_set_log_level( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *value, - void *userdata, - sd_bus_error *error) { - - const char *t; - int r; - - assert(bus); - assert(value); - - r = sd_bus_message_read(value, "s", &t); - if (r < 0) - return r; - - r = log_set_max_level_from_string(t); - if (r == 0) - log_info("Setting log level to %s.", t); - return r; -} - -static int property_get_n_names( - 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; - - assert(bus); - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "u", (uint32_t) hashmap_size(m->units)); -} - -static int property_get_n_failed_units( - 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; - - assert(bus); - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "u", (uint32_t) set_size(m->failed_units)); -} - -static int property_get_n_jobs( - 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; - - assert(bus); - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "u", (uint32_t) hashmap_size(m->jobs)); -} - -static int property_get_progress( - 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; - double d; - - assert(bus); - assert(reply); - assert(m); - - if (dual_timestamp_is_set(&m->finish_timestamp)) - d = 1.0; - else - d = 1.0 - ((double) hashmap_size(m->jobs) / (double) m->n_installed_jobs); - - return sd_bus_message_append(reply, "d", d); -} - -static int property_get_system_state( - 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; - - assert(bus); - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "s", manager_state_to_string(manager_state(m))); -} - -static int property_set_runtime_watchdog( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *value, - void *userdata, - sd_bus_error *error) { - - usec_t *t = userdata; - int r; - - assert(bus); - assert(value); - - assert_cc(sizeof(usec_t) == sizeof(uint64_t)); - - r = sd_bus_message_read(value, "t", t); - if (r < 0) - return r; - - return watchdog_set_timeout(t); -} - -static int property_get_timer_slack_nsec( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "t", (uint64_t) prctl(PR_GET_TIMERSLACK)); -} - -static int method_get_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *path = NULL; - Manager *m = userdata; - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (isempty(name)) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t pid; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - - u = manager_get_unit_by_pid(m, pid); - if (!u) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Client not member of any unit."); - } else { - u = manager_get_unit(m, name); - if (!u) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not loaded.", name); - } - - r = mac_selinux_unit_access_check(u, message, "status", error); - if (r < 0) - return r; - - path = unit_dbus_path(u); - if (!path) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", path); -} - -static int method_get_unit_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *path = NULL; - Manager *m = userdata; - pid_t pid; - Unit *u; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(pid_t) == sizeof(uint32_t)); - - /* Anyone can call this method */ - - r = sd_bus_message_read(message, "u", &pid); - if (r < 0) - return r; - if (pid < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PID " PID_FMT, pid); - - if (pid == 0) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - } - - u = manager_get_unit_by_pid(m, pid); - if (!u) - return sd_bus_error_setf(error, BUS_ERROR_NO_UNIT_FOR_PID, "PID "PID_FMT" does not belong to any loaded unit.", pid); - - r = mac_selinux_unit_access_check(u, message, "status", error); - if (r < 0) - return r; - - path = unit_dbus_path(u); - if (!path) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", path); -} - -static int method_load_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *path = NULL; - Manager *m = userdata; - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (isempty(name)) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t pid; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - - u = manager_get_unit_by_pid(m, pid); - if (!u) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Client not member of any unit."); - } else { - r = manager_load_unit(m, name, NULL, error, &u); - if (r < 0) - return r; - } - - r = mac_selinux_unit_access_check(u, message, "status", error); - if (r < 0) - return r; - - path = unit_dbus_path(u); - if (!path) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", path); -} - -static int method_start_unit_generic(sd_bus_message *message, Manager *m, JobType job_type, bool reload_if_possible, sd_bus_error *error) { - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - r = manager_load_unit(m, name, NULL, error, &u); - if (r < 0) - return r; - - return bus_unit_method_start_generic(message, u, job_type, reload_if_possible, error); -} - -static int method_start_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_START, false, error); -} - -static int method_stop_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_STOP, false, error); -} - -static int method_reload_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_RELOAD, false, error); -} - -static int method_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_RESTART, false, error); -} - -static int method_try_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_TRY_RESTART, false, error); -} - -static int method_reload_or_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_RESTART, true, error); -} - -static int method_reload_or_try_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_start_unit_generic(message, userdata, JOB_TRY_RESTART, true, error); -} - -static int method_start_unit_replace(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - const char *old_name; - Unit *u; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &old_name); - if (r < 0) - return r; - - u = manager_get_unit(m, old_name); - if (!u || !u->job || u->job->type != JOB_START) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_JOB, "No job queued for unit %s", old_name); - - return method_start_unit_generic(message, m, JOB_START, false, error); -} - -static int method_kill_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - u = manager_get_unit(m, name); - if (!u) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); - - return bus_unit_method_kill(message, u, error); -} - -static int method_reset_failed_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - u = manager_get_unit(m, name); - if (!u) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); - - return bus_unit_method_reset_failed(message, u, error); -} - -static int method_set_unit_properties(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - r = manager_load_unit(m, name, NULL, error, &u); - if (r < 0) - return r; - - r = bus_unit_check_load_state(u, error); - if (r < 0) - return r; - - return bus_unit_method_set_properties(message, u, error); -} - -static int transient_unit_from_message( - Manager *m, - sd_bus_message *message, - const char *name, - Unit **unit, - sd_bus_error *error) { - - UnitType t; - Unit *u; - int r; - - assert(m); - assert(message); - assert(name); - - t = unit_name_to_type(name); - if (t < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit name or type."); - - if (!unit_vtable[t]->can_transient) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit type %s does not support transient units.", unit_type_to_string(t)); - - r = manager_load_unit(m, name, NULL, error, &u); - if (r < 0) - return r; - - if (!unit_is_pristine(u)) - return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, "Unit %s already exists.", name); - - /* OK, the unit failed to load and is unreferenced, now let's - * fill in the transient data instead */ - r = unit_make_transient(u); - if (r < 0) - return r; - - /* Set our properties */ - r = bus_unit_set_properties(u, message, UNIT_RUNTIME, false, error); - if (r < 0) - return r; - - /* Now load the missing bits of the unit we just created */ - manager_dispatch_load_queue(m); - - *unit = u; - - return 0; -} - -static int transient_aux_units_from_message( - Manager *m, - sd_bus_message *message, - sd_bus_error *error) { - - int r; - - assert(m); - assert(message); - - r = sd_bus_message_enter_container(message, 'a', "(sa(sv))"); - if (r < 0) - return r; - - while ((r = sd_bus_message_enter_container(message, 'r', "sa(sv)")) > 0) { - const char *name = NULL; - Unit *u; - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - r = transient_unit_from_message(m, message, name, &u, error); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - return 0; -} - -static int method_start_transient_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *name, *smode; - Manager *m = userdata; - JobMode mode; - Unit *u; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "start", error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "ss", &name, &smode); - if (r < 0) - return r; - - mode = job_mode_from_string(smode); - if (mode < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Job mode %s is invalid.", smode); - - r = bus_verify_manage_units_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = transient_unit_from_message(m, message, name, &u, error); - if (r < 0) - return r; - - r = transient_aux_units_from_message(m, message, error); - if (r < 0) - return r; - - /* Finally, start it */ - return bus_unit_queue_job(message, u, JOB_START, mode, false, error); -} - -static int method_get_job(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *path = NULL; - Manager *m = userdata; - uint32_t id; - Job *j; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = sd_bus_message_read(message, "u", &id); - if (r < 0) - return r; - - j = manager_get_job(m, id); - if (!j) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_JOB, "Job %u does not exist.", (unsigned) id); - - r = mac_selinux_unit_access_check(j->unit, message, "status", error); - if (r < 0) - return r; - - path = job_dbus_path(j); - if (!path) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", path); -} - -static int method_cancel_job(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - uint32_t id; - Job *j; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "u", &id); - if (r < 0) - return r; - - j = manager_get_job(m, id); - if (!j) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_JOB, "Job %u does not exist.", (unsigned) id); - - return bus_job_method_cancel(message, j, error); -} - -static int method_clear_jobs(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = bus_verify_manage_units_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - manager_clear_jobs(m); - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = bus_verify_manage_units_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - manager_reset_failed(m); - - return sd_bus_reply_method_return(message, NULL); -} - -static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - const char *k; - Iterator i; - Unit *u; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(ssssssouso)"); - if (r < 0) - return r; - - HASHMAP_FOREACH_KEY(u, k, m->units, i) { - _cleanup_free_ char *unit_path = NULL, *job_path = NULL; - Unit *following; - - if (k != u->id) - continue; - - following = unit_following(u); - - if (!strv_isempty(states) && - !strv_contains(states, unit_load_state_to_string(u->load_state)) && - !strv_contains(states, unit_active_state_to_string(unit_active_state(u))) && - !strv_contains(states, unit_sub_state_to_string(u))) - continue; - - unit_path = unit_dbus_path(u); - if (!unit_path) - return -ENOMEM; - - if (u->job) { - job_path = job_dbus_path(u->job); - if (!job_path) - return -ENOMEM; - } - - r = sd_bus_message_append( - reply, "(ssssssouso)", - u->id, - unit_description(u), - unit_load_state_to_string(u->load_state), - unit_active_state_to_string(unit_active_state(u)), - unit_sub_state_to_string(u), - following ? following->id : "", - unit_path, - u->job ? u->job->id : 0, - u->job ? job_type_to_string(u->job->type) : "", - job_path ? job_path : "/"); - 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_list_units(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return list_units_filtered(message, userdata, error, NULL); -} - -static int method_list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **states = NULL; - int r; - - r = sd_bus_message_read_strv(message, &states); - if (r < 0) - return r; - - return list_units_filtered(message, userdata, error, states); -} - -static int method_list_jobs(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - Iterator i; - Job *j; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(usssoo)"); - if (r < 0) - return r; - - HASHMAP_FOREACH(j, m->jobs, i) { - _cleanup_free_ char *unit_path = NULL, *job_path = NULL; - - job_path = job_dbus_path(j); - if (!job_path) - return -ENOMEM; - - unit_path = unit_dbus_path(j->unit); - if (!unit_path) - return -ENOMEM; - - r = sd_bus_message_append( - reply, "(usssoo)", - j->id, - j->unit->id, - job_type_to_string(j->type), - job_state_to_string(j->state), - job_path, - unit_path); - 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_subscribe(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - if (sd_bus_message_get_bus(message) == m->api_bus) { - - /* Note that direct bus connection subscribe by - * default, we only track peers on the API bus here */ - - if (!m->subscribed) { - r = sd_bus_track_new(sd_bus_message_get_bus(message), &m->subscribed, NULL, NULL); - if (r < 0) - return r; - } - - r = sd_bus_track_add_sender(m->subscribed, message); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_ALREADY_SUBSCRIBED, "Client is already subscribed."); - } - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_unsubscribe(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - if (sd_bus_message_get_bus(message) == m->api_bus) { - r = sd_bus_track_remove_sender(m->subscribed, message); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NOT_SUBSCRIBED, "Client is not subscribed."); - } - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_dump(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *dump = NULL; - _cleanup_fclose_ FILE *f = NULL; - Manager *m = userdata; - size_t size; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - f = open_memstream(&dump, &size); - if (!f) - return -ENOMEM; - - manager_dump_units(m, f, NULL); - manager_dump_jobs(m, f, NULL); - - r = fflush_and_check(f); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, "s", dump); -} - -static int method_refuse_snapshot(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Support for snapshots has been removed."); -} - -static int method_reload(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = bus_verify_reload_daemon_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - /* Instead of sending the reply back right away, we just - * remember that we need to and then send it after the reload - * is finished. That way the caller knows when the reload - * finished. */ - - assert(!m->queued_message); - r = sd_bus_message_new_method_return(message, &m->queued_message); - if (r < 0) - return r; - - m->exit_code = MANAGER_RELOAD; - - return 1; -} - -static int method_reexecute(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = bus_verify_reload_daemon_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - /* We don't send a reply back here, the client should - * just wait for us disconnecting. */ - - m->exit_code = MANAGER_REEXECUTE; - return 1; -} - -static int method_exit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "halt", error); - if (r < 0) - return r; - - /* Exit() (in contrast to SetExitCode()) is actually allowed even if - * we are running on the host. It will fall back on reboot() in - * systemd-shutdown if it cannot do the exit() because it isn't a - * container. */ - - m->exit_code = MANAGER_EXIT; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reboot", error); - if (r < 0) - return r; - - if (m->running_as != MANAGER_SYSTEM) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Reboot is only supported for system managers."); - - m->exit_code = MANAGER_REBOOT; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "halt", error); - if (r < 0) - return r; - - if (m->running_as != MANAGER_SYSTEM) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Powering off is only supported for system managers."); - - m->exit_code = MANAGER_POWEROFF; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_halt(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "halt", error); - if (r < 0) - return r; - - if (m->running_as != MANAGER_SYSTEM) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Halt is only supported for system managers."); - - m->exit_code = MANAGER_HALT; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_kexec(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reboot", error); - if (r < 0) - return r; - - if (m->running_as != MANAGER_SYSTEM) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "KExec is only supported for system managers."); - - m->exit_code = MANAGER_KEXEC; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_switch_root(sd_bus_message *message, void *userdata, sd_bus_error *error) { - char *ri = NULL, *rt = NULL; - const char *root, *init; - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reboot", error); - if (r < 0) - return r; - - if (m->running_as != MANAGER_SYSTEM) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Root switching is only supported by system manager."); - - r = sd_bus_message_read(message, "ss", &root, &init); - if (r < 0) - return r; - - if (path_equal(root, "/") || !path_is_absolute(root)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid switch root path %s", root); - - /* Safety check */ - if (isempty(init)) { - if (!path_is_os_tree(root)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified switch root path %s does not seem to be an OS tree. os-release file is missing.", root); - } else { - _cleanup_free_ char *p = NULL; - - if (!path_is_absolute(init)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid init path %s", init); - - p = strappend(root, init); - if (!p) - return -ENOMEM; - - if (access(p, X_OK) < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified init binary %s does not exist.", p); - } - - rt = strdup(root); - if (!rt) - return -ENOMEM; - - if (!isempty(init)) { - ri = strdup(init); - if (!ri) { - free(rt); - return -ENOMEM; - } - } - - free(m->switch_root); - m->switch_root = rt; - - free(m->switch_root_init); - m->switch_root_init = ri; - - m->exit_code = MANAGER_SWITCH_ROOT; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_set_environment(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **plus = NULL; - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(message, &plus); - if (r < 0) - return r; - if (!strv_env_is_valid(plus)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); - - r = bus_verify_set_environment_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = manager_environment_add(m, NULL, plus); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_unset_environment(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **minus = NULL; - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(message, &minus); - if (r < 0) - return r; - - if (!strv_env_name_or_assignment_is_valid(minus)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment variable names or assignments"); - - r = bus_verify_set_environment_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = manager_environment_add(m, minus, NULL); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_unset_and_set_environment(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **minus = NULL, **plus = NULL; - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "reload", error); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(message, &minus); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(message, &plus); - if (r < 0) - return r; - - if (!strv_env_name_or_assignment_is_valid(minus)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment variable names or assignments"); - if (!strv_env_is_valid(plus)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); - - r = bus_verify_set_environment_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = manager_environment_add(m, minus, plus); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_error *error) { - uint8_t code; - Manager *m = userdata; - int r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "exit", error); - if (r < 0) - return r; - - r = sd_bus_message_read_basic(message, 'y', &code); - if (r < 0) - return r; - - if (m->running_as == MANAGER_SYSTEM && detect_container() <= 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "ExitCode can only be set for user service managers or in containers."); - - m->return_value = code; - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_list_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - UnitFileList *item; - Hashmap *h; - Iterator i; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - h = hashmap_new(&string_hash_ops); - if (!h) - return -ENOMEM; - - r = unit_file_get_list(m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, NULL, h); - if (r < 0) - goto fail; - - r = sd_bus_message_open_container(reply, 'a', "(ss)"); - if (r < 0) - goto fail; - - HASHMAP_FOREACH(item, h, i) { - - r = sd_bus_message_append(reply, "(ss)", item->path, unit_file_state_to_string(item->state)); - if (r < 0) - goto fail; - } - - unit_file_list_free(h); - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); - -fail: - unit_file_list_free(h); - return r; -} - -static int method_get_unit_file_state(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - const char *name; - UnitFileState state; - UnitFileScope scope; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; - - r = unit_file_get_state(scope, NULL, name, &state); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, "s", unit_file_state_to_string(state)); -} - -static int method_get_default_target(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *default_target = NULL; - Manager *m = userdata; - UnitFileScope scope; - int r; - - assert(message); - assert(m); - - /* Anyone can call this method */ - - r = mac_selinux_access_check(message, "status", error); - if (r < 0) - return r; - - scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; - - r = unit_file_get_default(scope, NULL, &default_target); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, "s", default_target); -} - -static int send_unit_files_changed(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; - int r; - - assert(bus); - - r = sd_bus_message_new_signal(bus, &message, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitFilesChanged"); - if (r < 0) - return r; - - return sd_bus_send(bus, message, NULL); -} - -static int reply_unit_file_changes_and_free( - Manager *m, - sd_bus_message *message, - int carries_install_info, - UnitFileChange *changes, - unsigned n_changes) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - unsigned i; - int r; - - if (n_changes > 0) { - r = bus_foreach_bus(m, NULL, send_unit_files_changed, NULL); - if (r < 0) - log_debug_errno(r, "Failed to send UnitFilesChanged signal: %m"); - } - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - goto fail; - - if (carries_install_info >= 0) { - r = sd_bus_message_append(reply, "b", carries_install_info); - if (r < 0) - goto fail; - } - - r = sd_bus_message_open_container(reply, 'a', "(sss)"); - if (r < 0) - goto fail; - - for (i = 0; i < n_changes; i++) { - r = sd_bus_message_append( - reply, "(sss)", - unit_file_change_type_to_string(changes[i].type), - changes[i].path, - changes[i].source); - if (r < 0) - goto fail; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto fail; - - unit_file_changes_free(changes, n_changes); - return sd_bus_send(NULL, reply, NULL); - -fail: - unit_file_changes_free(changes, n_changes); - return r; -} - -static int method_enable_unit_files_generic( - sd_bus_message *message, - Manager *m, - const char *verb, - int (*call)(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes), - bool carries_install_info, - sd_bus_error *error) { - - _cleanup_strv_free_ char **l = NULL; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - UnitFileScope scope; - int runtime, force, r; - - assert(message); - assert(m); - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "bb", &runtime, &force); - if (r < 0) - return r; - - r = bus_verify_manage_unit_files_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; - - r = call(scope, runtime, NULL, l, force, &changes, &n_changes); - if (r == -ESHUTDOWN) - return sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED, "Unit file is masked"); - if (r < 0) - return r; - - return reply_unit_file_changes_and_free(m, message, carries_install_info ? r : -1, changes, n_changes); -} - -static int method_enable_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_enable_unit_files_generic(message, userdata, "enable", unit_file_enable, true, error); -} - -static int method_reenable_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_enable_unit_files_generic(message, userdata, "enable", unit_file_reenable, true, error); -} - -static int method_link_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_enable_unit_files_generic(message, userdata, "enable", unit_file_link, false, error); -} - -static int unit_file_preset_without_mode(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes) { - return unit_file_preset(scope, runtime, root_dir, files, UNIT_FILE_PRESET_FULL, force, changes, n_changes); -} - -static int method_preset_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_enable_unit_files_generic(message, userdata, "enable", unit_file_preset_without_mode, true, error); -} - -static int method_mask_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_enable_unit_files_generic(message, userdata, "disable", unit_file_mask, false, error); -} - -static int method_preset_unit_files_with_mode(sd_bus_message *message, void *userdata, sd_bus_error *error) { - - _cleanup_strv_free_ char **l = NULL; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - Manager *m = userdata; - UnitFilePresetMode mm; - UnitFileScope scope; - int runtime, force, r; - const char *mode; - - assert(message); - assert(m); - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "sbb", &mode, &runtime, &force); - if (r < 0) - return r; - - if (isempty(mode)) - mm = UNIT_FILE_PRESET_FULL; - else { - mm = unit_file_preset_mode_from_string(mode); - if (mm < 0) - return -EINVAL; - } - - r = bus_verify_manage_unit_files_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; - - r = unit_file_preset(scope, runtime, NULL, l, mm, force, &changes, &n_changes); - if (r < 0) - return r; - - return reply_unit_file_changes_and_free(m, message, r, changes, n_changes); -} - -static int method_disable_unit_files_generic( - sd_bus_message *message, - Manager *m, const - char *verb, - int (*call)(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes), - sd_bus_error *error) { - - _cleanup_strv_free_ char **l = NULL; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - UnitFileScope scope; - int r, runtime; - - assert(message); - assert(m); - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "b", &runtime); - if (r < 0) - return r; - - scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; - - r = bus_verify_manage_unit_files_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = call(scope, runtime, NULL, l, &changes, &n_changes); - if (r < 0) - return r; - - return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); -} - -static int method_disable_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_disable_unit_files_generic(message, userdata, "disable", unit_file_disable, error); -} - -static int method_unmask_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_disable_unit_files_generic(message, userdata, "enable", unit_file_unmask, error); -} - -static int method_set_default_target(sd_bus_message *message, void *userdata, sd_bus_error *error) { - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - Manager *m = userdata; - UnitFileScope scope; - const char *name; - int force, r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "enable", error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "sb", &name, &force); - if (r < 0) - return r; - - r = bus_verify_manage_unit_files_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; - - r = unit_file_set_default(scope, NULL, name, force, &changes, &n_changes); - if (r < 0) - return r; - - return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); -} - -static int method_preset_all_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - Manager *m = userdata; - UnitFilePresetMode mm; - UnitFileScope scope; - const char *mode; - int force, runtime, r; - - assert(message); - assert(m); - - r = mac_selinux_access_check(message, "enable", error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "sbb", &mode, &runtime, &force); - if (r < 0) - return r; - - if (isempty(mode)) - mm = UNIT_FILE_PRESET_FULL; - else { - mm = unit_file_preset_mode_from_string(mode); - if (mm < 0) - return -EINVAL; - } - - r = bus_verify_manage_unit_files_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; - - r = unit_file_preset_all(scope, runtime, NULL, mm, force, &changes, &n_changes); - if (r < 0) { - unit_file_changes_free(changes, n_changes); - return r; - } - - return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); -} - -static int method_add_dependency_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_strv_free_ char **l = NULL; - Manager *m = userdata; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - UnitFileScope scope; - int runtime, force, r; - char *target; - char *type; - UnitDependency dep; - - assert(message); - assert(m); - - r = bus_verify_manage_unit_files_async(m, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = sd_bus_message_read_strv(message, &l); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "ssbb", &target, &type, &runtime, &force); - if (r < 0) - return r; - - dep = unit_dependency_from_string(type); - if (dep < 0) - return -EINVAL; - - scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; - - r = unit_file_add_dependency(scope, runtime, NULL, l, target, dep, force, &changes, &n_changes); - if (r == -ESHUTDOWN) - return sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED, "Unit file is masked"); - if (r < 0) - return r; - - return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); -} - -const sd_bus_vtable bus_manager_vtable[] = { - SD_BUS_VTABLE_START(0), - - SD_BUS_PROPERTY("Version", "s", property_get_version, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Features", "s", property_get_features, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Virtualization", "s", property_get_virtualization, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Architecture", "s", property_get_architecture, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Tainted", "s", property_get_tainted, 0, SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("FirmwareTimestamp", offsetof(Manager, firmware_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("LoaderTimestamp", offsetof(Manager, loader_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("KernelTimestamp", offsetof(Manager, kernel_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("InitRDTimestamp", offsetof(Manager, initrd_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("UserspaceTimestamp", offsetof(Manager, userspace_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("FinishTimestamp", offsetof(Manager, finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("SecurityStartTimestamp", offsetof(Manager, security_start_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("SecurityFinishTimestamp", offsetof(Manager, security_finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("GeneratorsStartTimestamp", offsetof(Manager, generators_start_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("GeneratorsFinishTimestamp", offsetof(Manager, generators_finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("UnitsLoadStartTimestamp", offsetof(Manager, units_load_start_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("UnitsLoadFinishTimestamp", offsetof(Manager, units_load_finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_WRITABLE_PROPERTY("LogLevel", "s", property_get_log_level, property_set_log_level, 0, 0), - SD_BUS_WRITABLE_PROPERTY("LogTarget", "s", property_get_log_target, property_set_log_target, 0, 0), - SD_BUS_PROPERTY("NNames", "u", property_get_n_names, 0, 0), - SD_BUS_PROPERTY("NFailedUnits", "u", property_get_n_failed_units, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("NJobs", "u", property_get_n_jobs, 0, 0), - SD_BUS_PROPERTY("NInstalledJobs", "u", bus_property_get_unsigned, offsetof(Manager, n_installed_jobs), 0), - SD_BUS_PROPERTY("NFailedJobs", "u", bus_property_get_unsigned, offsetof(Manager, n_failed_jobs), 0), - SD_BUS_PROPERTY("Progress", "d", property_get_progress, 0, 0), - SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(Manager, environment), 0), - SD_BUS_PROPERTY("ConfirmSpawn", "b", bus_property_get_bool, offsetof(Manager, confirm_spawn), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ShowStatus", "b", bus_property_get_bool, offsetof(Manager, show_status), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("UnitPath", "as", NULL, offsetof(Manager, lookup_paths.unit_path), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultStandardOutput", "s", bus_property_get_exec_output, offsetof(Manager, default_std_output), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultStandardError", "s", bus_property_get_exec_output, offsetof(Manager, default_std_output), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_WRITABLE_PROPERTY("RuntimeWatchdogUSec", "t", bus_property_get_usec, property_set_runtime_watchdog, offsetof(Manager, runtime_watchdog), 0), - SD_BUS_WRITABLE_PROPERTY("ShutdownWatchdogUSec", "t", bus_property_get_usec, bus_property_set_usec, offsetof(Manager, shutdown_watchdog), 0), - SD_BUS_PROPERTY("ControlGroup", "s", NULL, offsetof(Manager, cgroup_root), 0), - SD_BUS_PROPERTY("SystemState", "s", property_get_system_state, 0, 0), - SD_BUS_PROPERTY("ExitCode", "y", bus_property_get_unsigned, offsetof(Manager, return_value), 0), - SD_BUS_PROPERTY("DefaultTimerAccuracyUSec", "t", bus_property_get_usec, offsetof(Manager, default_timer_accuracy_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultTimeoutStartUSec", "t", bus_property_get_usec, offsetof(Manager, default_timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultTimeoutStopUSec", "t", bus_property_get_usec, offsetof(Manager, default_timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultRestartUSec", "t", bus_property_get_usec, offsetof(Manager, default_restart_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultStartLimitInterval", "t", bus_property_get_usec, offsetof(Manager, default_start_limit_interval), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultStartLimitBurst", "u", bus_property_get_unsigned, offsetof(Manager, default_start_limit_burst), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultCPUAccounting", "b", bus_property_get_bool, offsetof(Manager, default_cpu_accounting), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultBlockIOAccounting", "b", bus_property_get_bool, offsetof(Manager, default_blockio_accounting), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultMemoryAccounting", "b", bus_property_get_bool, offsetof(Manager, default_memory_accounting), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultTasksAccounting", "b", bus_property_get_bool, offsetof(Manager, default_tasks_accounting), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitCPU", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitCPUSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitFSIZE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitFSIZESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitDATA", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitDATASoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitSTACK", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitSTACKSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitCORE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitCORESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitRSS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitRSSSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitNOFILE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitNOFILESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitAS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitASSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitNPROC", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitNPROCSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitMEMLOCK", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitMEMLOCKSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitLOCKS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitLOCKSSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitSIGPENDING", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitSIGPENDINGSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitMSGQUEUE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitMSGQUEUESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitNICE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitNICESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitRTPRIO", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitRTPRIOSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitRTTIME", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultLimitRTTIMESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultTasksMax", "t", NULL, offsetof(Manager, default_tasks_max), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST), - - SD_BUS_METHOD("GetUnit", "s", "o", method_get_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetUnitByPID", "u", "o", method_get_unit_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("LoadUnit", "s", "o", method_load_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("StartUnit", "ss", "o", method_start_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("StartUnitReplace", "sss", "o", method_start_unit_replace, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("StopUnit", "ss", "o", method_stop_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ReloadUnit", "ss", "o", method_reload_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("RestartUnit", "ss", "o", method_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("TryRestartUnit", "ss", "o", method_try_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ReloadOrRestartUnit", "ss", "o", method_reload_or_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ReloadOrTryRestartUnit", "ss", "o", method_reload_or_try_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("KillUnit", "ssi", NULL, method_kill_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResetFailedUnit", "s", NULL, method_reset_failed_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetUnitProperties", "sba(sv)", NULL, method_set_unit_properties, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("StartTransientUnit", "ssa(sv)a(sa(sv))", "o", method_start_transient_unit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetJob", "u", "o", method_get_job, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CancelJob", "u", NULL, method_cancel_job, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ClearJobs", NULL, NULL, method_clear_jobs, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResetFailed", NULL, NULL, method_reset_failed, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListUnits", NULL, "a(ssssssouso)", method_list_units, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListUnitsFiltered", "as", "a(ssssssouso)", method_list_units_filtered, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListJobs", NULL, "a(usssoo)", method_list_jobs, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Subscribe", NULL, NULL, method_subscribe, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Unsubscribe", NULL, NULL, method_unsubscribe, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Dump", NULL, "s", method_dump, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CreateSnapshot", "sb", "o", method_refuse_snapshot, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("RemoveSnapshot", "s", NULL, method_refuse_snapshot, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Reload", NULL, NULL, method_reload, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Reexecute", NULL, NULL, method_reexecute, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Exit", NULL, NULL, method_exit, 0), - SD_BUS_METHOD("Reboot", NULL, NULL, method_reboot, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), - SD_BUS_METHOD("PowerOff", NULL, NULL, method_poweroff, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), - SD_BUS_METHOD("Halt", NULL, NULL, method_halt, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), - SD_BUS_METHOD("KExec", NULL, NULL, method_kexec, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), - SD_BUS_METHOD("SwitchRoot", "ss", NULL, method_switch_root, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), - SD_BUS_METHOD("SetEnvironment", "as", NULL, method_set_environment, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("UnsetEnvironment", "as", NULL, method_unset_environment, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("UnsetAndSetEnvironment", "asas", NULL, method_unset_and_set_environment, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListUnitFiles", NULL, "a(ss)", method_list_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetUnitFileState", "s", "s", method_get_unit_file_state, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("EnableUnitFiles", "asbb", "ba(sss)", method_enable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("DisableUnitFiles", "asb", "a(sss)", method_disable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ReenableUnitFiles", "asbb", "ba(sss)", method_reenable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("LinkUnitFiles", "asbb", "a(sss)", method_link_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("PresetUnitFiles", "asbb", "ba(sss)", method_preset_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("PresetUnitFilesWithMode", "assbb", "ba(sss)", method_preset_unit_files_with_mode, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MaskUnitFiles", "asbb", "a(sss)", method_mask_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("UnmaskUnitFiles", "asb", "a(sss)", method_unmask_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetDefaultTarget", "sb", "a(sss)", method_set_default_target, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetDefaultTarget", NULL, "s", method_get_default_target, SD_BUS_VTABLE_UNPRIVILEGED), - 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_SIGNAL("UnitNew", "so", 0), - SD_BUS_SIGNAL("UnitRemoved", "so", 0), - SD_BUS_SIGNAL("JobNew", "uos", 0), - SD_BUS_SIGNAL("JobRemoved", "uoss", 0), - SD_BUS_SIGNAL("StartupFinished", "tttttt", 0), - SD_BUS_SIGNAL("UnitFilesChanged", NULL, 0), - SD_BUS_SIGNAL("Reloading", "b", 0), - - SD_BUS_VTABLE_END -}; - -static int send_finished(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; - usec_t *times = userdata; - int r; - - assert(bus); - assert(times); - - r = sd_bus_message_new_signal(bus, &message, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartupFinished"); - if (r < 0) - return r; - - r = sd_bus_message_append(message, "tttttt", times[0], times[1], times[2], times[3], times[4], times[5]); - if (r < 0) - return r; - - return sd_bus_send(bus, message, NULL); -} - -void bus_manager_send_finished( - Manager *m, - usec_t firmware_usec, - usec_t loader_usec, - usec_t kernel_usec, - usec_t initrd_usec, - usec_t userspace_usec, - usec_t total_usec) { - - int r; - - assert(m); - - r = bus_foreach_bus( - m, - NULL, - send_finished, - (usec_t[6]) { - firmware_usec, - loader_usec, - kernel_usec, - initrd_usec, - userspace_usec, - total_usec - }); - if (r < 0) - log_debug_errno(r, "Failed to send finished signal: %m"); -} - -static int send_reloading(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; - int r; - - assert(bus); - - r = sd_bus_message_new_signal(bus, &message, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "Reloading"); - if (r < 0) - return r; - - r = sd_bus_message_append(message, "b", PTR_TO_INT(userdata)); - if (r < 0) - return r; - - return sd_bus_send(bus, message, NULL); -} - -void bus_manager_send_reloading(Manager *m, bool active) { - int r; - - assert(m); - - r = bus_foreach_bus(m, NULL, send_reloading, INT_TO_PTR(active)); - if (r < 0) - log_debug_errno(r, "Failed to send reloading signal: %m"); -} - -static int send_changed_signal(sd_bus *bus, void *userdata) { - assert(bus); - - return sd_bus_emit_properties_changed_strv(bus, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - NULL); -} - -void bus_manager_send_change_signal(Manager *m) { - int r; - - assert(m); - - r = bus_foreach_bus(m, NULL, send_changed_signal, NULL); - if (r < 0) - log_debug_errno(r, "Failed to send manager change signal: %m"); -} diff --git a/src/core/dbus-manager.h b/src/core/dbus-manager.h deleted file mode 100644 index 36a2e9481b..0000000000 --- a/src/core/dbus-manager.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "manager.h" - -extern const sd_bus_vtable bus_manager_vtable[]; - -void bus_manager_send_finished(Manager *m, usec_t firmware_usec, usec_t loader_usec, usec_t kernel_usec, usec_t initrd_usec, usec_t userspace_usec, usec_t total_usec); -void bus_manager_send_reloading(Manager *m, bool active); -void bus_manager_send_change_signal(Manager *m); diff --git a/src/core/dbus-mount.c b/src/core/dbus-mount.c deleted file mode 100644 index 935db7c48b..0000000000 --- a/src/core/dbus-mount.c +++ /dev/null @@ -1,211 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-kill.h" -#include "dbus-mount.h" -#include "mount.h" -#include "string-util.h" -#include "unit.h" - -static int property_get_what( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Mount *m = userdata; - const char *d; - - assert(bus); - assert(reply); - assert(m); - - if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.what) - d = m->parameters_proc_self_mountinfo.what; - else if (m->from_fragment && m->parameters_fragment.what) - d = m->parameters_fragment.what; - else - d = ""; - - return sd_bus_message_append(reply, "s", d); -} - -static int property_get_options( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Mount *m = userdata; - const char *d; - - assert(bus); - assert(reply); - assert(m); - - if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.options) - d = m->parameters_proc_self_mountinfo.options; - else if (m->from_fragment && m->parameters_fragment.options) - d = m->parameters_fragment.options; - else - d = ""; - - return sd_bus_message_append(reply, "s", d); -} - -static int property_get_type( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Mount *m = userdata; - const char *d; - - assert(bus); - assert(reply); - assert(m); - - if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.fstype) - d = m->parameters_proc_self_mountinfo.fstype; - else if (m->from_fragment && m->parameters_fragment.fstype) - d = m->parameters_fragment.fstype; - else - d = ""; - - return sd_bus_message_append(reply, "s", d); -} - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, mount_result, MountResult); - -const sd_bus_vtable bus_mount_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Where", "s", NULL, offsetof(Mount, where), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("What", "s", property_get_what, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Options","s", property_get_options, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Type", "s", property_get_type, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Mount, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Mount, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Mount, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SloppyOptions", "b", bus_property_get_bool, offsetof(Mount, sloppy_options), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Mount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_EXEC_COMMAND_VTABLE("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_VTABLE("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_VTABLE("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_VTABLE_END -}; - -static int bus_mount_set_transient_property( - Mount *m, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - const char *new_property; - char **property; - char *p; - int r; - - assert(m); - assert(name); - assert(message); - - if (streq(name, "What")) - property = &m->parameters_fragment.what; - else if (streq(name, "Options")) - property = &m->parameters_fragment.options; - else if (streq(name, "Type")) - property = &m->parameters_fragment.fstype; - else - return 0; - - r = sd_bus_message_read(message, "s", &new_property); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - p = strdup(new_property); - if (!p) - return -ENOMEM; - - free(*property); - *property = p; - } - - return 1; -} - -int bus_mount_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Mount *m = MOUNT(u); - int r; - - assert(m); - assert(name); - assert(message); - - r = bus_cgroup_set_property(u, &m->cgroup_context, name, message, mode, error); - if (r != 0) - return r; - - if (u->transient && u->load_state == UNIT_STUB) { - /* This is a transient unit, let's load a little more */ - - r = bus_mount_set_transient_property(m, name, message, mode, error); - if (r != 0) - return r; - - r = bus_exec_context_set_transient_property(u, &m->exec_context, name, message, mode, error); - if (r != 0) - return r; - - r = bus_kill_context_set_transient_property(u, &m->kill_context, name, message, mode, error); - if (r != 0) - return r; - } - - return 0; -} - -int bus_mount_commit_properties(Unit *u) { - assert(u); - - unit_update_cgroup_members_masks(u); - unit_realize_cgroup(u); - - return 0; -} diff --git a/src/core/dbus-mount.h b/src/core/dbus-mount.h deleted file mode 100644 index ec16166d36..0000000000 --- a/src/core/dbus-mount.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_mount_vtable[]; - -int bus_mount_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); -int bus_mount_commit_properties(Unit *u); diff --git a/src/core/dbus-path.c b/src/core/dbus-path.c deleted file mode 100644 index 1e153e503f..0000000000 --- a/src/core/dbus-path.c +++ /dev/null @@ -1,86 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "bus-util.h" -#include "dbus-path.h" -#include "path.h" -#include "string-util.h" -#include "unit.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, path_result, PathResult); - -static int property_get_paths( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Path *p = userdata; - PathSpec *k; - int r; - - assert(bus); - assert(reply); - assert(p); - - r = sd_bus_message_open_container(reply, 'a', "(ss)"); - if (r < 0) - return r; - - LIST_FOREACH(spec, k, p->specs) { - r = sd_bus_message_append(reply, "(ss)", path_type_to_string(k->type), k->path); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_unit( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *p = userdata, *trigger; - - assert(bus); - assert(reply); - assert(p); - - trigger = UNIT_TRIGGER(p); - - return sd_bus_message_append(reply, "s", trigger ? trigger->id : ""); -} - -const sd_bus_vtable bus_path_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Unit", "s", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Paths", "a(ss)", property_get_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("MakeDirectory", "b", bus_property_get_bool, offsetof(Path, make_directory), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Path, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Path, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_VTABLE_END -}; diff --git a/src/core/dbus-path.h b/src/core/dbus-path.h deleted file mode 100644 index d3c19e0c2b..0000000000 --- a/src/core/dbus-path.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - - - -extern const sd_bus_vtable bus_path_vtable[]; diff --git a/src/core/dbus-scope.c b/src/core/dbus-scope.c deleted file mode 100644 index 34ee9a8fa9..0000000000 --- a/src/core/dbus-scope.c +++ /dev/null @@ -1,229 +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 . -***/ - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-internal.h" -#include "bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-kill.h" -#include "dbus-scope.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "scope.h" -#include "selinux-access.h" -#include "unit.h" - -static int bus_scope_abandon(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Scope *s = userdata; - int r; - - assert(message); - assert(s); - - r = mac_selinux_unit_access_check(UNIT(s), message, "stop", error); - if (r < 0) - return r; - - r = bus_verify_manage_units_async(UNIT(s)->manager, message, error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = scope_abandon(s); - if (r == -ESTALE) - return sd_bus_error_setf(error, BUS_ERROR_SCOPE_NOT_RUNNING, "Scope %s is not running, cannot abandon.", UNIT(s)->id); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, scope_result, ScopeResult); - -const sd_bus_vtable bus_scope_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Controller", "s", NULL, offsetof(Scope, controller), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Scope, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Scope, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_SIGNAL("RequestStop", NULL, 0), - SD_BUS_METHOD("Abandon", NULL, NULL, bus_scope_abandon, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END -}; - -static int bus_scope_set_transient_property( - Scope *s, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - int r; - - assert(s); - assert(name); - assert(message); - - if (streq(name, "PIDs")) { - unsigned n = 0; - uint32_t pid; - - r = sd_bus_message_enter_container(message, 'a', "u"); - if (r < 0) - return r; - - while ((r = sd_bus_message_read(message, "u", &pid)) > 0) { - - if (pid <= 1) - return -EINVAL; - - if (mode != UNIT_CHECK) { - r = unit_watch_pid(UNIT(s), pid); - if (r < 0 && r != -EEXIST) - return r; - } - - n++; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (n <= 0) - return -EINVAL; - - return 1; - - } else if (streq(name, "Controller")) { - const char *controller; - char *c; - - r = sd_bus_message_read(message, "s", &controller); - if (r < 0) - return r; - - if (!isempty(controller) && !service_name_is_valid(controller)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Controller '%s' is not a valid bus name.", controller); - - if (mode != UNIT_CHECK) { - if (isempty(controller)) - c = NULL; - else { - c = strdup(controller); - if (!c) - return -ENOMEM; - } - - free(s->controller); - s->controller = c; - } - - return 1; - - } else if (streq(name, "TimeoutStopUSec")) { - - if (mode != UNIT_CHECK) { - r = sd_bus_message_read(message, "t", &s->timeout_stop_usec); - if (r < 0) - return r; - - unit_write_drop_in_format(UNIT(s), mode, name, "[Scope]\nTimeoutStopSec="USEC_FMT"us\n", s->timeout_stop_usec); - } else { - r = sd_bus_message_skip(message, "t"); - if (r < 0) - return r; - } - - return 1; - } - - return 0; -} - -int bus_scope_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Scope *s = SCOPE(u); - int r; - - assert(s); - assert(name); - assert(message); - - r = bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); - if (r != 0) - return r; - - if (u->load_state == UNIT_STUB) { - /* While we are created we still accept PIDs */ - - r = bus_scope_set_transient_property(s, name, message, mode, error); - if (r != 0) - return r; - - r = bus_kill_context_set_transient_property(u, &s->kill_context, name, message, mode, error); - if (r != 0) - return r; - } - - return 0; -} - -int bus_scope_commit_properties(Unit *u) { - assert(u); - - unit_update_cgroup_members_masks(u); - unit_realize_cgroup(u); - - return 0; -} - -int bus_scope_send_request_stop(Scope *s) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *p = NULL; - int r; - - assert(s); - - if (!s->controller) - return 0; - - p = unit_dbus_path(UNIT(s)); - if (!p) - return -ENOMEM; - - r = sd_bus_message_new_signal( - UNIT(s)->manager->api_bus, - &m, - p, - "org.freedesktop.systemd1.Scope", - "RequestStop"); - if (r < 0) - return r; - - return sd_bus_send_to(UNIT(s)->manager->api_bus, m, /* s->controller */ NULL, NULL); -} diff --git a/src/core/dbus-scope.h b/src/core/dbus-scope.h deleted file mode 100644 index 270306f508..0000000000 --- a/src/core/dbus-scope.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_scope_vtable[]; - -int bus_scope_set_property(Unit *u, const char *name, sd_bus_message *i, UnitSetPropertiesMode mode, sd_bus_error *error); -int bus_scope_commit_properties(Unit *u); - -int bus_scope_send_request_stop(Scope *s); diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c deleted file mode 100644 index 03eecca911..0000000000 --- a/src/core/dbus-service.c +++ /dev/null @@ -1,322 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "alloc-util.h" -#include "async.h" -#include "bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-kill.h" -#include "dbus-service.h" -#include "fd-util.h" -#include "fileio.h" -#include "path-util.h" -#include "service.h" -#include "string-util.h" -#include "strv.h" -#include "unit.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, service_type, ServiceType); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, service_result, ServiceResult); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_restart, service_restart, ServiceRestart); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_notify_access, notify_access, NotifyAccess); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_failure_action, failure_action, FailureAction); - -const sd_bus_vtable bus_service_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Restart", "s", property_get_restart, offsetof(Service, restart), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PIDFile", "s", NULL, offsetof(Service, pid_file), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NotifyAccess", "s", property_get_notify_access, offsetof(Service, notify_access), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RestartUSec", "t", bus_property_get_usec, offsetof(Service, restart_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimeoutStartUSec", "t", bus_property_get_usec, offsetof(Service, timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Service, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RuntimeMaxUSec", "t", bus_property_get_usec, offsetof(Service, runtime_max_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("WatchdogUSec", "t", bus_property_get_usec, offsetof(Service, watchdog_usec), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("WatchdogTimestamp", offsetof(Service, watchdog_timestamp), 0), - /* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */ - SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("StartLimitAction", "s", property_get_failure_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("FailureAction", "s", property_get_failure_action, offsetof(Service, failure_action), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PermissionsStartOnly", "b", bus_property_get_bool, offsetof(Service, permissions_start_only), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RootDirectoryStartOnly", "b", bus_property_get_bool, offsetof(Service, root_directory_start_only), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RemainAfterExit", "b", bus_property_get_bool, offsetof(Service, remain_after_exit), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("GuessMainPID", "b", bus_property_get_bool, offsetof(Service, guess_main_pid), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("MainPID", "u", bus_property_get_pid, offsetof(Service, main_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Service, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("BusName", "s", NULL, offsetof(Service, bus_name), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("FileDescriptorStoreMax", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store_max), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NFileDescriptorStore", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store), 0), - SD_BUS_PROPERTY("StatusText", "s", NULL, offsetof(Service, status_text), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("StatusErrno", "i", NULL, offsetof(Service, status_errno), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Service, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("USBFunctionDescriptors", "s", NULL, offsetof(Service, usb_function_descriptors), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("USBFunctionStrings", "s", NULL, offsetof(Service, usb_function_strings), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Service, exec_command[SERVICE_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStart", offsetof(Service, exec_command[SERVICE_EXEC_START]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Service, exec_command[SERVICE_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecReload", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStop", offsetof(Service, exec_command[SERVICE_EXEC_STOP]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_VTABLE_END -}; - -static int bus_service_set_transient_property( - Service *s, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - int r; - - assert(s); - assert(name); - assert(message); - - if (streq(name, "RemainAfterExit")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - s->remain_after_exit = b; - unit_write_drop_in_private_format(UNIT(s), mode, name, "RemainAfterExit=%s\n", yes_no(b)); - } - - return 1; - - } else if (streq(name, "Type")) { - const char *t; - ServiceType k; - - r = sd_bus_message_read(message, "s", &t); - if (r < 0) - return r; - - k = service_type_from_string(t); - if (k < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service type %s", t); - - if (mode != UNIT_CHECK) { - s->type = k; - unit_write_drop_in_private_format(UNIT(s), mode, name, "Type=%s\n", service_type_to_string(s->type)); - } - - return 1; - } else if (streq(name, "RuntimeMaxUSec")) { - usec_t u; - - r = sd_bus_message_read(message, "t", &u); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - s->runtime_max_usec = u; - unit_write_drop_in_private_format(UNIT(s), mode, name, "RuntimeMaxSec=" USEC_FMT "us\n", u); - } - - return 1; - - } else if (STR_IN_SET(name, - "StandardInputFileDescriptor", - "StandardOutputFileDescriptor", - "StandardErrorFileDescriptor")) { - int fd; - - r = sd_bus_message_read(message, "h", &fd); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - int copy; - - copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (copy < 0) - return -errno; - - if (streq(name, "StandardInputFileDescriptor")) { - asynchronous_close(s->stdin_fd); - s->stdin_fd = copy; - } else if (streq(name, "StandardOutputFileDescriptor")) { - asynchronous_close(s->stdout_fd); - s->stdout_fd = copy; - } else { - asynchronous_close(s->stderr_fd); - s->stderr_fd = copy; - } - - s->exec_context.stdio_as_fds = true; - } - - return 1; - - } else if (streq(name, "ExecStart")) { - unsigned n = 0; - - r = sd_bus_message_enter_container(message, 'a', "(sasb)"); - if (r < 0) - return r; - - while ((r = sd_bus_message_enter_container(message, 'r', "sasb")) > 0) { - _cleanup_strv_free_ char **argv = NULL; - const char *path; - int b; - - r = sd_bus_message_read(message, "s", &path); - if (r < 0) - return r; - - if (!path_is_absolute(path)) - return sd_bus_error_set_errnof(error, EINVAL, "Path %s is not absolute.", path); - - r = sd_bus_message_read_strv(message, &argv); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - ExecCommand *c; - - c = new0(ExecCommand, 1); - if (!c) - return -ENOMEM; - - c->path = strdup(path); - if (!c->path) { - free(c); - return -ENOMEM; - } - - c->argv = argv; - argv = NULL; - - c->ignore = b; - - path_kill_slashes(c->path); - exec_command_append_list(&s->exec_command[SERVICE_EXEC_START], c); - } - - n++; - } - - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *f = NULL; - ExecCommand *c; - size_t size = 0; - - if (n == 0) - s->exec_command[SERVICE_EXEC_START] = exec_command_free_list(s->exec_command[SERVICE_EXEC_START]); - - f = open_memstream(&buf, &size); - if (!f) - return -ENOMEM; - - fputs("ExecStart=\n", f); - - LIST_FOREACH(command, c, s->exec_command[SERVICE_EXEC_START]) { - _cleanup_free_ char *a; - - a = strv_join_quoted(c->argv); - if (!a) - return -ENOMEM; - - fprintf(f, "ExecStart=%s@%s %s\n", - c->ignore ? "-" : "", - c->path, - a); - } - - r = fflush_and_check(f); - if (r < 0) - return r; - unit_write_drop_in_private(UNIT(s), mode, name, buf); - } - - return 1; - } - - return 0; -} - -int bus_service_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Service *s = SERVICE(u); - int r; - - assert(s); - assert(name); - assert(message); - - r = bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); - if (r != 0) - return r; - - if (u->transient && u->load_state == UNIT_STUB) { - /* This is a transient unit, let's load a little more */ - - r = bus_service_set_transient_property(s, name, message, mode, error); - if (r != 0) - return r; - - r = bus_exec_context_set_transient_property(u, &s->exec_context, name, message, mode, error); - if (r != 0) - return r; - - r = bus_kill_context_set_transient_property(u, &s->kill_context, name, message, mode, error); - if (r != 0) - return r; - } - - return 0; -} - -int bus_service_commit_properties(Unit *u) { - assert(u); - - unit_update_cgroup_members_masks(u); - unit_realize_cgroup(u); - - return 0; -} diff --git a/src/core/dbus-service.h b/src/core/dbus-service.h deleted file mode 100644 index 769a53769e..0000000000 --- a/src/core/dbus-service.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_service_vtable[]; - -int bus_service_set_property(Unit *u, const char *name, sd_bus_message *i, UnitSetPropertiesMode mode, sd_bus_error *error); -int bus_service_commit_properties(Unit *u); diff --git a/src/core/dbus-slice.c b/src/core/dbus-slice.c deleted file mode 100644 index e37f50b283..0000000000 --- a/src/core/dbus-slice.c +++ /dev/null @@ -1,52 +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 . -***/ - -#include "dbus-cgroup.h" -#include "dbus-slice.h" -#include "slice.h" -#include "unit.h" - -const sd_bus_vtable bus_slice_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_VTABLE_END -}; - -int bus_slice_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Slice *s = SLICE(u); - - assert(name); - assert(u); - - return bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); -} - -int bus_slice_commit_properties(Unit *u) { - assert(u); - - unit_update_cgroup_members_masks(u); - unit_realize_cgroup(u); - - return 0; -} diff --git a/src/core/dbus-slice.h b/src/core/dbus-slice.h deleted file mode 100644 index 52ceebb135..0000000000 --- a/src/core/dbus-slice.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_slice_vtable[]; - -int bus_slice_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); -int bus_slice_commit_properties(Unit *u); diff --git a/src/core/dbus-socket.c b/src/core/dbus-socket.c deleted file mode 100644 index d33e494f6b..0000000000 --- a/src/core/dbus-socket.c +++ /dev/null @@ -1,182 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "alloc-util.h" -#include "bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-socket.h" -#include "socket.h" -#include "string-util.h" -#include "unit.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, socket_result, SocketResult); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_bind_ipv6_only, socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); - -static int property_get_listen( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - - Socket *s = SOCKET(userdata); - SocketPort *p; - int r; - - assert(bus); - assert(reply); - assert(s); - - r = sd_bus_message_open_container(reply, 'a', "(ss)"); - if (r < 0) - return r; - - LIST_FOREACH(port, p, s->ports) { - _cleanup_free_ char *address = NULL; - const char *a; - - switch (p->type) { - case SOCKET_SOCKET: { - r = socket_address_print(&p->address, &address); - if (r) - return r; - - a = address; - break; - } - - case SOCKET_SPECIAL: - case SOCKET_MQUEUE: - case SOCKET_FIFO: - case SOCKET_USB_FUNCTION: - a = p->path; - break; - - default: - assert_not_reached("Unknown socket type"); - } - - r = sd_bus_message_append(reply, "(ss)", socket_port_type_to_string(p), a); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - - -static int property_get_fdname( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Socket *s = SOCKET(userdata); - - assert(bus); - assert(reply); - assert(s); - - return sd_bus_message_append(reply, "s", socket_fdname(s)); -} - -const sd_bus_vtable bus_socket_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("BindIPv6Only", "s", property_get_bind_ipv6_only, offsetof(Socket, bind_ipv6_only), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Backlog", "u", bus_property_get_unsigned, offsetof(Socket, backlog), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Socket, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("BindToDevice", "s", NULL, offsetof(Socket, bind_to_device), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SocketUser", "s", NULL, offsetof(Socket, user), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SocketGroup", "s", NULL, offsetof(Socket, group), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SocketMode", "u", bus_property_get_mode, offsetof(Socket, socket_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Socket, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Accept", "b", bus_property_get_bool, offsetof(Socket, accept), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Writable", "b", bus_property_get_bool, offsetof(Socket, writable), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("KeepAlive", "b", bus_property_get_bool, offsetof(Socket, keep_alive), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("KeepAliveTimeUSec", "t", bus_property_get_usec, offsetof(Socket, keep_alive_time), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("KeepAliveIntervalUSec", "t", bus_property_get_usec, offsetof(Socket, keep_alive_interval), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("KeepAliveProbes", "u", bus_property_get_unsigned, offsetof(Socket, keep_alive_cnt), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DeferAcceptUSec" , "t", bus_property_get_usec, offsetof(Socket, defer_accept), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NoDelay", "b", bus_property_get_bool, offsetof(Socket, no_delay), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Priority", "i", bus_property_get_int, offsetof(Socket, priority), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ReceiveBuffer", "t", bus_property_get_size, offsetof(Socket, receive_buffer), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SendBuffer", "t", bus_property_get_size, offsetof(Socket, send_buffer), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("IPTOS", "i", bus_property_get_int, offsetof(Socket, ip_tos), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("IPTTL", "i", bus_property_get_int, offsetof(Socket, ip_ttl), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PipeSize", "t", bus_property_get_size, offsetof(Socket, pipe_size), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("FreeBind", "b", bus_property_get_bool, offsetof(Socket, free_bind), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Transparent", "b", bus_property_get_bool, offsetof(Socket, transparent), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Broadcast", "b", bus_property_get_bool, offsetof(Socket, broadcast), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PassCredentials", "b", bus_property_get_bool, offsetof(Socket, pass_cred), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PassSecurity", "b", bus_property_get_bool, offsetof(Socket, pass_sec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RemoveOnStop", "b", bus_property_get_bool, offsetof(Socket, remove_on_stop), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Listen", "a(ss)", property_get_listen, 0, SD_BUS_VTABLE_PROPERTY_CONST), - 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("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), - SD_BUS_PROPERTY("SmackLabel", "s", NULL, offsetof(Socket, smack), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SmackLabelIPIn", "s", NULL, offsetof(Socket, smack_ip_in), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SmackLabelIPOut", "s", NULL, offsetof(Socket, smack_ip_out), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Socket, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Socket, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("NConnections", "u", bus_property_get_unsigned, offsetof(Socket, n_connections), 0), - SD_BUS_PROPERTY("NAccepted", "u", bus_property_get_unsigned, offsetof(Socket, n_accepted), 0), - SD_BUS_PROPERTY("FileDescriptorName", "s", property_get_fdname, 0, 0), - SD_BUS_PROPERTY("SocketProtocol", "i", bus_property_get_int, offsetof(Socket, socket_protocol), SD_BUS_VTABLE_PROPERTY_CONST), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_VTABLE_END -}; - -int bus_socket_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Socket *s = SOCKET(u); - - assert(s); - assert(name); - assert(message); - - return bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); -} - -int bus_socket_commit_properties(Unit *u) { - assert(u); - - unit_update_cgroup_members_masks(u); - unit_realize_cgroup(u); - - return 0; -} diff --git a/src/core/dbus-socket.h b/src/core/dbus-socket.h deleted file mode 100644 index 7a792c7a89..0000000000 --- a/src/core/dbus-socket.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_socket_vtable[]; - -int bus_socket_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); -int bus_socket_commit_properties(Unit *u); diff --git a/src/core/dbus-swap.c b/src/core/dbus-swap.c deleted file mode 100644 index 292f8738c6..0000000000 --- a/src/core/dbus-swap.c +++ /dev/null @@ -1,115 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2010 Maarten Lankhorst - - 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 . -***/ - -#include "bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-swap.h" -#include "string-util.h" -#include "swap.h" -#include "unit.h" - -static int property_get_priority( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Swap *s = SWAP(userdata); - int p; - - assert(bus); - assert(reply); - assert(s); - - if (s->from_proc_swaps) - p = s->parameters_proc_swaps.priority; - else if (s->from_fragment) - p = s->parameters_fragment.priority; - else - p = -1; - - return sd_bus_message_append(reply, "i", p); -} - -static int property_get_options( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Swap *s = SWAP(userdata); - const char *options = NULL; - - assert(bus); - assert(reply); - assert(s); - - if (s->from_fragment) - options = s->parameters_fragment.options; - - return sd_bus_message_append(reply, "s", options); -} - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, swap_result, SwapResult); - -const sd_bus_vtable bus_swap_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("What", "s", NULL, offsetof(Swap, what), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Priority", "i", property_get_priority, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Options", "s", property_get_options, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Swap, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Swap, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Swap, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_EXEC_COMMAND_VTABLE("ExecActivate", offsetof(Swap, exec_command[SWAP_EXEC_ACTIVATE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - BUS_EXEC_COMMAND_VTABLE("ExecDeactivate", offsetof(Swap, exec_command[SWAP_EXEC_DEACTIVATE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_VTABLE_END -}; - -int bus_swap_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Swap *s = SWAP(u); - - assert(s); - assert(name); - assert(message); - - return bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); -} - -int bus_swap_commit_properties(Unit *u) { - assert(u); - - unit_update_cgroup_members_masks(u); - unit_realize_cgroup(u); - - return 0; -} diff --git a/src/core/dbus-swap.h b/src/core/dbus-swap.h deleted file mode 100644 index 5238471f98..0000000000 --- a/src/core/dbus-swap.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2010 Maarten Lankhorst - - 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 . -***/ - -#include "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_swap_vtable[]; - -int bus_swap_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); -int bus_swap_commit_properties(Unit *u); diff --git a/src/core/dbus-target.c b/src/core/dbus-target.c deleted file mode 100644 index 6858b1ce72..0000000000 --- a/src/core/dbus-target.c +++ /dev/null @@ -1,26 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "dbus-target.h" -#include "unit.h" - -const sd_bus_vtable bus_target_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_VTABLE_END -}; diff --git a/src/core/dbus-target.h b/src/core/dbus-target.h deleted file mode 100644 index 9be5ce06b7..0000000000 --- a/src/core/dbus-target.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "sd-bus.h" - -extern const sd_bus_vtable bus_target_vtable[]; diff --git a/src/core/dbus-timer.c b/src/core/dbus-timer.c deleted file mode 100644 index bc121b83a2..0000000000 --- a/src/core/dbus-timer.c +++ /dev/null @@ -1,352 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "alloc-util.h" -#include "bus-util.h" -#include "dbus-timer.h" -#include "strv.h" -#include "timer.h" -#include "unit.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, timer_result, TimerResult); - -static int property_get_monotonic_timers( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Timer *t = userdata; - TimerValue *v; - int r; - - assert(bus); - assert(reply); - assert(t); - - r = sd_bus_message_open_container(reply, 'a', "(stt)"); - if (r < 0) - return r; - - LIST_FOREACH(value, v, t->values) { - _cleanup_free_ char *buf = NULL; - const char *s; - size_t l; - - if (v->base == TIMER_CALENDAR) - continue; - - s = timer_base_to_string(v->base); - assert(endswith(s, "Sec")); - - /* s/Sec/USec/ */ - l = strlen(s); - buf = new(char, l+2); - if (!buf) - return -ENOMEM; - - memcpy(buf, s, l-3); - memcpy(buf+l-3, "USec", 5); - - r = sd_bus_message_append(reply, "(stt)", buf, v->value, v->next_elapse); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_calendar_timers( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Timer *t = userdata; - TimerValue *v; - int r; - - assert(bus); - assert(reply); - assert(t); - - r = sd_bus_message_open_container(reply, 'a', "(sst)"); - if (r < 0) - return r; - - LIST_FOREACH(value, v, t->values) { - _cleanup_free_ char *buf = NULL; - - if (v->base != TIMER_CALENDAR) - continue; - - r = calendar_spec_to_string(v->calendar_spec, &buf); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "(sst)", timer_base_to_string(v->base), buf, v->next_elapse); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_unit( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata, *trigger; - - assert(bus); - assert(reply); - assert(u); - - trigger = UNIT_TRIGGER(u); - - return sd_bus_message_append(reply, "s", trigger ? trigger->id : ""); -} - -static int property_get_next_elapse_monotonic( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Timer *t = userdata; - usec_t x; - - assert(bus); - assert(reply); - assert(t); - - if (t->next_elapse_monotonic_or_boottime <= 0) - x = 0; - else if (t->wake_system) { - usec_t a, b; - - a = now(CLOCK_MONOTONIC); - b = now(CLOCK_BOOTTIME); - - if (t->next_elapse_monotonic_or_boottime + a > b) - x = t->next_elapse_monotonic_or_boottime + a - b; - else - x = 0; - } else - x = t->next_elapse_monotonic_or_boottime; - - return sd_bus_message_append(reply, "t", x); -} - -const sd_bus_vtable bus_timer_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Unit", "s", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TimersMonotonic", "a(stt)", property_get_monotonic_timers, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_PROPERTY("TimersCalendar", "a(sst)", property_get_calendar_timers, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_PROPERTY("NextElapseUSecRealtime", "t", bus_property_get_usec, offsetof(Timer, next_elapse_realtime), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("NextElapseUSecMonotonic", "t", property_get_next_elapse_monotonic, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("LastTriggerUSec", offsetof(Timer, last_trigger), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Timer, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("AccuracyUSec", "t", bus_property_get_usec, offsetof(Timer, accuracy_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RandomizedDelayUSec", "t", bus_property_get_usec, offsetof(Timer, random_usec), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Persistent", "b", bus_property_get_bool, offsetof(Timer, persistent), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("WakeSystem", "b", bus_property_get_bool, offsetof(Timer, wake_system), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RemainAfterElapse", "b", bus_property_get_bool, offsetof(Timer, remain_after_elapse), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_VTABLE_END -}; - -static int bus_timer_set_transient_property( - Timer *t, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - int r; - - assert(t); - assert(name); - assert(message); - - if (STR_IN_SET(name, - "OnActiveSec", - "OnBootSec", - "OnStartupSec", - "OnUnitActiveSec", - "OnUnitInactiveSec")) { - - TimerValue *v; - TimerBase b = _TIMER_BASE_INVALID; - usec_t u = 0; - - b = timer_base_from_string(name); - if (b < 0) - return -EINVAL; - - r = sd_bus_message_read(message, "t", &u); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - char time[FORMAT_TIMESPAN_MAX]; - - unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, format_timespan(time, sizeof(time), u, USEC_PER_MSEC)); - - v = new0(TimerValue, 1); - if (!v) - return -ENOMEM; - - v->base = b; - v->value = u; - - LIST_PREPEND(value, t->values, v); - } - - return 1; - - } else if (streq(name, "OnCalendar")) { - - TimerValue *v; - CalendarSpec *c = NULL; - const char *str; - - r = sd_bus_message_read(message, "s", &str); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - r = calendar_spec_from_string(str, &c); - if (r < 0) - return r; - - unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, str); - - v = new0(TimerValue, 1); - if (!v) { - calendar_spec_free(c); - return -ENOMEM; - } - - v->base = TIMER_CALENDAR; - v->calendar_spec = c; - - LIST_PREPEND(value, t->values, v); - } - - return 1; - - } else if (STR_IN_SET(name, "AccuracyUSec", "AccuracySec")) { - usec_t u = 0; - - if (streq(name, "AccuracySec")) - log_notice("Client is using obsolete AccuracySec= transient property, please use AccuracyUSec= instead."); - - r = sd_bus_message_read(message, "t", &u); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - t->accuracy_usec = u; - unit_write_drop_in_private_format(UNIT(t), mode, name, "AccuracySec=" USEC_FMT "us\n", u); - } - - return 1; - - } else if (streq(name, "RandomizedDelayUSec")) { - usec_t u = 0; - - r = sd_bus_message_read(message, "t", &u); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - t->random_usec = u; - unit_write_drop_in_private_format(UNIT(t), mode, name, "RandomizedDelaySec=" USEC_FMT "us\n", u); - } - - return 1; - - } else if (streq(name, "WakeSystem")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - t->wake_system = b; - unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, yes_no(b)); - } - - return 1; - - } else if (streq(name, "RemainAfterElapse")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - t->remain_after_elapse = b; - unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, yes_no(b)); - } - - return 1; - } - - return 0; -} - -int bus_timer_set_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - Timer *t = TIMER(u); - int r; - - assert(t); - assert(name); - assert(message); - - if (u->transient && u->load_state == UNIT_STUB) { - r = bus_timer_set_transient_property(t, name, message, mode, error); - if (r != 0) - return r; - } - - return 0; -} diff --git a/src/core/dbus-timer.h b/src/core/dbus-timer.h deleted file mode 100644 index 39053dc4a2..0000000000 --- a/src/core/dbus-timer.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_timer_vtable[]; - -int bus_timer_set_property(Unit *u, const char *name, sd_bus_message *i, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c deleted file mode 100644 index b351f6a2c2..0000000000 --- a/src/core/dbus-unit.c +++ /dev/null @@ -1,1276 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "cgroup-util.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "locale-util.h" -#include "log.h" -#include "selinux-access.h" -#include "special.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_load_state, unit_load_state, UnitLoadState); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_job_mode, job_mode, JobMode); -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_failure_action, failure_action, FailureAction); - -static int property_get_names( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - Iterator i; - const char *t; - int r; - - assert(bus); - assert(reply); - assert(u); - - r = sd_bus_message_open_container(reply, 'a', "s"); - if (r < 0) - return r; - - SET_FOREACH(t, u->names, i) { - r = sd_bus_message_append(reply, "s", t); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_following( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata, *f; - - assert(bus); - assert(reply); - assert(u); - - f = unit_following(u); - return sd_bus_message_append(reply, "s", f ? f->id : ""); -} - -static int property_get_dependencies( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Set *s = *(Set**) userdata; - Iterator j; - Unit *u; - int r; - - assert(bus); - assert(reply); - - r = sd_bus_message_open_container(reply, 'a', "s"); - if (r < 0) - return r; - - SET_FOREACH(u, s, j) { - r = sd_bus_message_append(reply, "s", u->id); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_obsolete_dependencies( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - /* For dependency types we don't support anymore always return an empty array */ - return sd_bus_message_append(reply, "as", 0); -} - -static int property_get_description( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "s", unit_description(u)); -} - -static int property_get_active_state( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "s", unit_active_state_to_string(unit_active_state(u))); -} - -static int property_get_sub_state( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "s", unit_sub_state_to_string(u)); -} - -static int property_get_unit_file_preset( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - int r; - - assert(bus); - assert(reply); - assert(u); - - r = unit_get_unit_file_preset(u); - - return sd_bus_message_append(reply, "s", - r < 0 ? "": - r > 0 ? "enabled" : "disabled"); -} - -static int property_get_unit_file_state( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "s", unit_file_state_to_string(unit_get_unit_file_state(u))); -} - -static int property_get_can_start( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "b", unit_can_start(u) && !u->refuse_manual_start); -} - -static int property_get_can_stop( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - /* On the lower levels we assume that every unit we can start - * we can also stop */ - - return sd_bus_message_append(reply, "b", unit_can_start(u) && !u->refuse_manual_stop); -} - -static int property_get_can_reload( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "b", unit_can_reload(u)); -} - -static int property_get_can_isolate( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "b", unit_can_isolate(u) && !u->refuse_manual_start); -} - -static int property_get_job( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_free_ char *p = NULL; - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - if (!u->job) - return sd_bus_message_append(reply, "(uo)", 0, "/"); - - p = job_dbus_path(u->job); - if (!p) - return -ENOMEM; - - return sd_bus_message_append(reply, "(uo)", u->job->id, p); -} - -static int property_get_need_daemon_reload( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "b", unit_need_daemon_reload(u)); -} - -static int property_get_conditions( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - const char *(*to_string)(ConditionType type) = NULL; - Condition **list = userdata, *c; - int r; - - assert(bus); - assert(reply); - assert(list); - - to_string = streq(property, "Asserts") ? assert_type_to_string : condition_type_to_string; - - r = sd_bus_message_open_container(reply, 'a', "(sbbsi)"); - if (r < 0) - return r; - - LIST_FOREACH(conditions, c, *list) { - int tristate; - - tristate = - c->result == CONDITION_UNTESTED ? 0 : - c->result == CONDITION_SUCCEEDED ? 1 : -1; - - r = sd_bus_message_append(reply, "(sbbsi)", - to_string(c->type), - c->trigger, c->negate, - c->parameter, tristate); - if (r < 0) - return r; - - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_load_error( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - if (u->load_error != 0) - sd_bus_error_set_errno(&e, u->load_error); - - return sd_bus_message_append(reply, "(ss)", e.name, e.message); -} - -static int bus_verify_manage_units_async_full( - Unit *u, - const char *verb, - int capability, - const char *polkit_message, - sd_bus_message *call, - sd_bus_error *error) { - - const char *details[9] = { - "unit", u->id, - "verb", verb, - }; - - if (polkit_message) { - details[4] = "polkit.message"; - details[5] = polkit_message; - details[6] = "polkit.gettext_domain"; - details[7] = GETTEXT_PACKAGE; - } - - return bus_verify_polkit_async(call, capability, "org.freedesktop.systemd1.manage-units", details, false, UID_INVALID, &u->manager->polkit_registry, error); -} - -int bus_unit_method_start_generic( - sd_bus_message *message, - Unit *u, - JobType job_type, - bool reload_if_possible, - sd_bus_error *error) { - - const char *smode; - JobMode mode; - _cleanup_free_ char *verb = NULL; - static const char *const polkit_message_for_job[_JOB_TYPE_MAX] = { - [JOB_START] = N_("Authentication is required to start '$(unit)'."), - [JOB_STOP] = N_("Authentication is required to stop '$(unit)'."), - [JOB_RELOAD] = N_("Authentication is required to reload '$(unit)'."), - [JOB_RESTART] = N_("Authentication is required to restart '$(unit)'."), - [JOB_TRY_RESTART] = N_("Authentication is required to restart '$(unit)'."), - }; - int r; - - assert(message); - assert(u); - assert(job_type >= 0 && job_type < _JOB_TYPE_MAX); - - r = mac_selinux_unit_access_check( - u, message, - job_type_to_access_method(job_type), - error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "s", &smode); - if (r < 0) - return r; - - mode = job_mode_from_string(smode); - if (mode < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Job mode %s invalid", smode); - - if (reload_if_possible) - verb = strjoin("reload-or-", job_type_to_string(job_type), NULL); - else - verb = strdup(job_type_to_string(job_type)); - if (!verb) - return -ENOMEM; - - r = bus_verify_manage_units_async_full( - u, - verb, - CAP_SYS_ADMIN, - job_type < _JOB_TYPE_MAX ? polkit_message_for_job[job_type] : NULL, - message, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - return bus_unit_queue_job(message, u, job_type, mode, reload_if_possible, error); -} - -static int method_start(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_START, false, error); -} - -static int method_stop(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_STOP, false, error); -} - -static int method_reload(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_RELOAD, false, error); -} - -static int method_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_RESTART, false, error); -} - -static int method_try_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_TRY_RESTART, false, error); -} - -static int method_reload_or_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_RESTART, true, error); -} - -static int method_reload_or_try_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return bus_unit_method_start_generic(message, userdata, JOB_TRY_RESTART, true, error); -} - -int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Unit *u = userdata; - const char *swho; - int32_t signo; - KillWho who; - int r; - - assert(message); - assert(u); - - r = mac_selinux_unit_access_check(u, message, "stop", error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "si", &swho, &signo); - if (r < 0) - return r; - - if (isempty(swho)) - who = KILL_ALL; - else { - who = kill_who_from_string(swho); - if (who < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid who argument %s", swho); - } - - if (signo <= 0 || signo >= _NSIG) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Signal number out of range."); - - r = bus_verify_manage_units_async_full( - u, - "kill", - CAP_KILL, - N_("Authentication is required to kill '$(unit)'."), - message, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = unit_kill(u, who, signo, error); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Unit *u = userdata; - int r; - - assert(message); - assert(u); - - r = mac_selinux_unit_access_check(u, message, "reload", error); - if (r < 0) - return r; - - r = bus_verify_manage_units_async_full( - u, - "reset-failed", - CAP_SYS_ADMIN, - N_("Authentication is required to reset the \"failed\" state of '$(unit)'."), - message, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - unit_reset_failed(u); - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Unit *u = userdata; - int runtime, r; - - assert(message); - assert(u); - - r = mac_selinux_unit_access_check(u, message, "start", error); - if (r < 0) - return r; - - r = sd_bus_message_read(message, "b", &runtime); - if (r < 0) - return r; - - r = bus_verify_manage_units_async_full( - u, - "set-property", - CAP_SYS_ADMIN, - N_("Authentication is required to set properties on '$(unit)'."), - message, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - r = bus_unit_set_properties(u, message, runtime ? UNIT_RUNTIME : UNIT_PERSISTENT, true, error); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -const sd_bus_vtable bus_unit_vtable[] = { - SD_BUS_VTABLE_START(0), - - SD_BUS_PROPERTY("Id", "s", NULL, offsetof(Unit, id), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Names", "as", property_get_names, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Following", "s", property_get_following, 0, 0), - SD_BUS_PROPERTY("Requires", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUIRES]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Requisite", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUISITE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Wants", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_WANTS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("BindsTo", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BINDS_TO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PartOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_PART_OF]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RequiredBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUIRED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RequisiteOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUISITE_OF]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("WantedBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_WANTED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("BoundBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BOUND_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ConsistsOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONSISTS_OF]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Conflicts", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONFLICTS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ConflictedBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Before", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BEFORE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("After", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_AFTER]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("OnFailure", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_ON_FAILURE]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Triggers", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_TRIGGERS]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("TriggeredBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("PropagatesReloadTo", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_PROPAGATES_RELOAD_TO]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ReloadPropagatedFrom", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_RELOAD_PROPAGATED_FROM]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("JoinsNamespaceOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_JOINS_NAMESPACE_OF]), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RequiresOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RequisiteOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RequiredByOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RequisiteOfOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("RequiresMountsFor", "as", NULL, offsetof(Unit, requires_mounts_for), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Documentation", "as", NULL, offsetof(Unit, documentation), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Description", "s", property_get_description, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("LoadState", "s", property_get_load_state, offsetof(Unit, load_state), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ActiveState", "s", property_get_active_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("SubState", "s", property_get_sub_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("FragmentPath", "s", NULL, offsetof(Unit, fragment_path), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("SourcePath", "s", NULL, offsetof(Unit, source_path), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DropInPaths", "as", NULL, offsetof(Unit, dropin_paths), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("UnitFileState", "s", property_get_unit_file_state, 0, 0), - SD_BUS_PROPERTY("UnitFilePreset", "s", property_get_unit_file_preset, 0, 0), - BUS_PROPERTY_DUAL_TIMESTAMP("StateChangeTimestamp", offsetof(Unit, state_change_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("InactiveExitTimestamp", offsetof(Unit, inactive_exit_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("ActiveEnterTimestamp", offsetof(Unit, active_enter_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("ActiveExitTimestamp", offsetof(Unit, active_exit_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("InactiveEnterTimestamp", offsetof(Unit, inactive_enter_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("CanStart", "b", property_get_can_start, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CanStop", "b", property_get_can_stop, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CanReload", "b", property_get_can_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("CanIsolate", "b", property_get_can_isolate, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Job", "(uo)", property_get_job, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("StopWhenUnneeded", "b", bus_property_get_bool, offsetof(Unit, stop_when_unneeded), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RefuseManualStart", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_start), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RefuseManualStop", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_stop), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("AllowIsolate", "b", bus_property_get_bool, offsetof(Unit, allow_isolate), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("DefaultDependencies", "b", bus_property_get_bool, offsetof(Unit, default_dependencies), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("OnFailureJobMode", "s", property_get_job_mode, offsetof(Unit, on_failure_job_mode), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("IgnoreOnIsolate", "b", bus_property_get_bool, offsetof(Unit, ignore_on_isolate), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NeedDaemonReload", "b", property_get_need_daemon_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("JobTimeoutUSec", "t", bus_property_get_usec, offsetof(Unit, job_timeout), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("JobTimeoutAction", "s", property_get_failure_action, offsetof(Unit, job_timeout_action), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("JobTimeoutRebootArgument", "s", NULL, offsetof(Unit, job_timeout_reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("ConditionResult", "b", bus_property_get_bool, offsetof(Unit, condition_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("AssertResult", "b", bus_property_get_bool, offsetof(Unit, assert_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("ConditionTimestamp", offsetof(Unit, condition_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - BUS_PROPERTY_DUAL_TIMESTAMP("AssertTimestamp", offsetof(Unit, assert_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Conditions", "a(sbbsi)", property_get_conditions, offsetof(Unit, conditions), 0), - SD_BUS_PROPERTY("Asserts", "a(sbbsi)", property_get_conditions, offsetof(Unit, asserts), 0), - SD_BUS_PROPERTY("LoadError", "(ss)", property_get_load_error, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Transient", "b", bus_property_get_bool, offsetof(Unit, transient), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("StartLimitAction", "s", property_get_failure_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST), - - SD_BUS_METHOD("Start", "s", "o", method_start, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Stop", "s", "o", method_stop, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Reload", "s", "o", method_reload, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Restart", "s", "o", method_restart, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("TryRestart", "s", "o", method_try_restart, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ReloadOrRestart", "s", "o", method_reload_or_restart, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ReloadOrTryRestart", "s", "o", method_reload_or_try_restart, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Kill", "si", NULL, bus_unit_method_kill, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ResetFailed", NULL, NULL, bus_unit_method_reset_failed, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetProperties", "ba(sv)", NULL, bus_unit_method_set_properties, SD_BUS_VTABLE_UNPRIVILEGED), - - SD_BUS_VTABLE_END -}; - -static int property_get_slice( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - - assert(bus); - assert(reply); - assert(u); - - return sd_bus_message_append(reply, "s", unit_slice_name(u)); -} - -static int property_get_current_memory( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - uint64_t sz = (uint64_t) -1; - Unit *u = userdata; - int r; - - assert(bus); - assert(reply); - assert(u); - - r = unit_get_memory_current(u, &sz); - if (r < 0 && r != -ENODATA) - log_unit_warning_errno(u, r, "Failed to get memory.usage_in_bytes attribute: %m"); - - return sd_bus_message_append(reply, "t", sz); -} - -static int property_get_current_tasks( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - uint64_t cn = (uint64_t) -1; - Unit *u = userdata; - int r; - - assert(bus); - assert(reply); - assert(u); - - r = unit_get_tasks_current(u, &cn); - if (r < 0 && r != -ENODATA) - log_unit_warning_errno(u, r, "Failed to get pids.current attribute: %m"); - - return sd_bus_message_append(reply, "t", cn); -} - -static int property_get_cpu_usage( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - nsec_t ns = (nsec_t) -1; - Unit *u = userdata; - int r; - - assert(bus); - assert(reply); - assert(u); - - r = unit_get_cpu_usage(u, &ns); - if (r < 0 && r != -ENODATA) - log_unit_warning_errno(u, r, "Failed to get cpuacct.usage attribute: %m"); - - return sd_bus_message_append(reply, "t", ns); -} - -static int property_get_cgroup( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Unit *u = userdata; - const char *t; - - assert(bus); - assert(reply); - assert(u); - - /* Three cases: a) u->cgroup_path is NULL, in which case the - * unit has no control group, which we report as the empty - * string. b) u->cgroup_path is the empty string, which - * indicates the root cgroup, which we report as "/". c) all - * other cases we report as-is. */ - - if (u->cgroup_path) - t = isempty(u->cgroup_path) ? "/" : u->cgroup_path; - else - t = ""; - - return sd_bus_message_append(reply, "s", t); -} - -const sd_bus_vtable bus_unit_cgroup_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Slice", "s", property_get_slice, 0, 0), - SD_BUS_PROPERTY("ControlGroup", "s", property_get_cgroup, 0, 0), - SD_BUS_PROPERTY("MemoryCurrent", "t", property_get_current_memory, 0, 0), - SD_BUS_PROPERTY("CPUUsageNSec", "t", property_get_cpu_usage, 0, 0), - SD_BUS_PROPERTY("TasksCurrent", "t", property_get_current_tasks, 0, 0), - SD_BUS_VTABLE_END -}; - -static int send_new_signal(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *p = NULL; - Unit *u = userdata; - int r; - - assert(bus); - assert(u); - - p = unit_dbus_path(u); - if (!p) - return -ENOMEM; - - r = sd_bus_message_new_signal( - bus, - &m, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UnitNew"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "so", u->id, p); - if (r < 0) - return r; - - return sd_bus_send(bus, m, NULL); -} - -static int send_changed_signal(sd_bus *bus, void *userdata) { - _cleanup_free_ char *p = NULL; - Unit *u = userdata; - int r; - - assert(bus); - assert(u); - - p = unit_dbus_path(u); - if (!p) - return -ENOMEM; - - /* Send a properties changed signal. First for the specific - * type, then for the generic unit. The clients may rely on - * this order to get atomic behavior if needed. */ - - r = sd_bus_emit_properties_changed_strv( - bus, p, - unit_dbus_interface_from_type(u->type), - NULL); - if (r < 0) - return r; - - return sd_bus_emit_properties_changed_strv( - bus, p, - "org.freedesktop.systemd1.Unit", - NULL); -} - -void bus_unit_send_change_signal(Unit *u) { - int r; - assert(u); - - if (u->in_dbus_queue) { - LIST_REMOVE(dbus_queue, u->manager->dbus_unit_queue, u); - u->in_dbus_queue = false; - } - - if (!u->id) - return; - - r = bus_foreach_bus(u->manager, NULL, u->sent_dbus_new_signal ? send_changed_signal : send_new_signal, u); - if (r < 0) - log_unit_debug_errno(u, r, "Failed to send unit change signal for %s: %m", u->id); - - u->sent_dbus_new_signal = true; -} - -static int send_removed_signal(sd_bus *bus, void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *p = NULL; - Unit *u = userdata; - int r; - - assert(bus); - assert(u); - - p = unit_dbus_path(u); - if (!p) - return -ENOMEM; - - r = sd_bus_message_new_signal( - bus, - &m, - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UnitRemoved"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "so", u->id, p); - if (r < 0) - return r; - - return sd_bus_send(bus, m, NULL); -} - -void bus_unit_send_removed_signal(Unit *u) { - int r; - assert(u); - - if (!u->sent_dbus_new_signal) - bus_unit_send_change_signal(u); - - if (!u->id) - return; - - r = bus_foreach_bus(u->manager, NULL, send_removed_signal, u); - if (r < 0) - log_unit_debug_errno(u, r, "Failed to send unit remove signal for %s: %m", u->id); -} - -int bus_unit_queue_job( - sd_bus_message *message, - Unit *u, - JobType type, - JobMode mode, - bool reload_if_possible, - sd_bus_error *error) { - - _cleanup_free_ char *path = NULL; - Job *j; - int r; - - assert(message); - assert(u); - assert(type >= 0 && type < _JOB_TYPE_MAX); - assert(mode >= 0 && mode < _JOB_MODE_MAX); - - r = mac_selinux_unit_access_check( - u, message, - job_type_to_access_method(type), - error); - if (r < 0) - return r; - - if (reload_if_possible && unit_can_reload(u)) { - if (type == JOB_RESTART) - type = JOB_RELOAD_OR_START; - else if (type == JOB_TRY_RESTART) - type = JOB_TRY_RELOAD; - } - - - if (type == JOB_STOP && - (u->load_state == UNIT_NOT_FOUND || u->load_state == UNIT_ERROR) && - unit_active_state(u) == UNIT_INACTIVE) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not loaded.", u->id); - - if ((type == JOB_START && u->refuse_manual_start) || - (type == JOB_STOP && u->refuse_manual_stop) || - ((type == JOB_RESTART || type == JOB_TRY_RESTART) && (u->refuse_manual_start || u->refuse_manual_stop)) || - (type == JOB_RELOAD_OR_START && job_type_collapse(type, u) == JOB_START && u->refuse_manual_start)) - return sd_bus_error_setf(error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, unit %s may be requested by dependency only.", u->id); - - r = manager_add_job(u->manager, type, u, mode, error, &j); - if (r < 0) - return r; - - if (sd_bus_message_get_bus(message) == u->manager->api_bus) { - if (!j->clients) { - r = sd_bus_track_new(sd_bus_message_get_bus(message), &j->clients, NULL, NULL); - if (r < 0) - return r; - } - - r = sd_bus_track_add_sender(j->clients, message); - if (r < 0) - return r; - } - - path = job_dbus_path(j); - if (!path) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", path); -} - -static int bus_unit_set_transient_property( - Unit *u, - const char *name, - sd_bus_message *message, - UnitSetPropertiesMode mode, - sd_bus_error *error) { - - int r; - - assert(u); - assert(name); - assert(message); - - if (streq(name, "Description")) { - const char *d; - - r = sd_bus_message_read(message, "s", &d); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - r = unit_set_description(u, d); - if (r < 0) - return r; - - unit_write_drop_in_format(u, mode, name, "[Unit]\nDescription=%s\n", d); - } - - return 1; - - } else if (streq(name, "DefaultDependencies")) { - int b; - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) - return r; - - if (mode != UNIT_CHECK) { - u->default_dependencies = b; - unit_write_drop_in_format(u, mode, name, "[Unit]\nDefaultDependencies=%s\n", yes_no(b)); - } - - return 1; - - } else if (streq(name, "Slice")) { - Unit *slice; - const char *s; - - if (!UNIT_HAS_CGROUP_CONTEXT(u)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "The slice property is only available for units with control groups."); - if (u->type == UNIT_SLICE) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Slice may not be set for slice units."); - if (unit_has_name(u, SPECIAL_INIT_SCOPE)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot set slice for init.scope"); - - r = sd_bus_message_read(message, "s", &s); - if (r < 0) - return r; - - if (!unit_name_is_valid(s, UNIT_NAME_PLAIN)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit name '%s'", s); - - r = manager_load_unit(u->manager, s, NULL, error, &slice); - if (r < 0) - return r; - - if (slice->type != UNIT_SLICE) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit name '%s' is not a slice", s); - - if (mode != UNIT_CHECK) { - r = unit_set_slice(u, slice); - if (r < 0) - return r; - - unit_write_drop_in_private_format(u, mode, name, "Slice=%s\n", s); - } - - return 1; - - } else if (STR_IN_SET(name, - "Requires", "RequiresOverridable", - "Requisite", "RequisiteOverridable", - "Wants", - "BindsTo", - "Conflicts", - "Before", "After", - "OnFailure", - "PropagatesReloadTo", "ReloadPropagatedFrom", - "PartOf")) { - - UnitDependency d; - const char *other; - - if (streq(name, "RequiresOverridable")) - d = UNIT_REQUIRES; /* redirect for obsolete unit dependency type */ - else if (streq(name, "RequisiteOverridable")) - d = UNIT_REQUISITE; /* same here */ - else { - d = unit_dependency_from_string(name); - if (d < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit dependency: %s", name); - } - - r = sd_bus_message_enter_container(message, 'a', "s"); - if (r < 0) - return r; - - while ((r = sd_bus_message_read(message, "s", &other)) > 0) { - if (!unit_name_is_valid(other, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit name %s", other); - - if (mode != UNIT_CHECK) { - _cleanup_free_ char *label = NULL; - - r = unit_add_dependency_by_name(u, d, other, NULL, true); - if (r < 0) - return r; - - label = strjoin(name, "-", other, NULL); - if (!label) - return -ENOMEM; - - unit_write_drop_in_format(u, mode, label, "[Unit]\n%s=%s\n", name, other); - } - - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - return 1; - } - - return 0; -} - -int bus_unit_set_properties( - Unit *u, - sd_bus_message *message, - UnitSetPropertiesMode mode, - bool commit, - sd_bus_error *error) { - - bool for_real = false; - unsigned n = 0; - int r; - - assert(u); - assert(message); - - /* We iterate through the array twice. First run we just check - * if all passed data is valid, second run actually applies - * it. This is to implement transaction-like behaviour without - * actually providing full transactions. */ - - r = sd_bus_message_enter_container(message, 'a', "(sv)"); - if (r < 0) - return r; - - for (;;) { - const char *name; - - r = sd_bus_message_enter_container(message, 'r', "sv"); - if (r < 0) - return r; - if (r == 0) { - if (for_real || mode == UNIT_CHECK) - break; - - /* Reached EOF. Let's try again, and this time for realz... */ - r = sd_bus_message_rewind(message, false); - if (r < 0) - return r; - - for_real = true; - continue; - } - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (!UNIT_VTABLE(u)->bus_set_property) - return sd_bus_error_setf(error, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Objects of this type do not support setting properties."); - - r = sd_bus_message_enter_container(message, 'v', NULL); - if (r < 0) - return r; - - r = UNIT_VTABLE(u)->bus_set_property(u, name, message, for_real ? mode : UNIT_CHECK, error); - if (r == 0 && u->transient && u->load_state == UNIT_STUB) - r = bus_unit_set_transient_property(u, name, message, for_real ? mode : UNIT_CHECK, error); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Cannot set property %s, or unknown property.", name); - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - n += for_real; - } - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (commit && n > 0 && UNIT_VTABLE(u)->bus_commit_properties) - UNIT_VTABLE(u)->bus_commit_properties(u); - - return n; -} - -int bus_unit_check_load_state(Unit *u, sd_bus_error *error) { - - if (u->load_state == UNIT_LOADED) - return 0; - - /* Give a better description of the unit error when - * possible. Note that in the case of UNIT_MASKED, load_error - * is not set. */ - if (u->load_state == UNIT_MASKED) - return sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED, "Unit %s is masked.", u->id); - - if (u->load_state == UNIT_NOT_FOUND) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not found.", u->id); - - return sd_bus_error_set_errnof(error, u->load_error, "Unit %s is not loaded properly: %m.", u->id); -} diff --git a/src/core/dbus-unit.h b/src/core/dbus-unit.h deleted file mode 100644 index 07948b9cd0..0000000000 --- a/src/core/dbus-unit.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "sd-bus.h" - -#include "unit.h" - -extern const sd_bus_vtable bus_unit_vtable[]; -extern const sd_bus_vtable bus_unit_cgroup_vtable[]; - -void bus_unit_send_change_signal(Unit *u); -void bus_unit_send_removed_signal(Unit *u); - -int bus_unit_method_start_generic(sd_bus_message *message, Unit *u, JobType job_type, bool reload_if_possible, sd_bus_error *error); -int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error); - -int bus_unit_queue_job(sd_bus_message *message, Unit *u, JobType type, JobMode mode, bool reload_if_possible, sd_bus_error *error); -int bus_unit_set_properties(Unit *u, sd_bus_message *message, UnitSetPropertiesMode mode, bool commit, sd_bus_error *error); -int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_bus_error *error); - -int bus_unit_check_load_state(Unit *u, sd_bus_error *error); diff --git a/src/core/dbus.c b/src/core/dbus.c deleted file mode 100644 index 413489373f..0000000000 --- a/src/core/dbus.c +++ /dev/null @@ -1,1258 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-internal.h" -#include "bus-util.h" -#include "dbus-cgroup.h" -#include "dbus-execute.h" -#include "dbus-job.h" -#include "dbus-kill.h" -#include "dbus-manager.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "fd-util.h" -#include "log.h" -#include "missing.h" -#include "mkdir.h" -#include "selinux-access.h" -#include "special.h" -#include "string-util.h" -#include "strv.h" -#include "strxcpyx.h" -#include "user-util.h" - -#define CONNECTIONS_MAX 4096 - -static void destroy_bus(Manager *m, sd_bus **bus); - -int bus_send_queued_message(Manager *m) { - int r; - - assert(m); - - if (!m->queued_message) - return 0; - - /* If we cannot get rid of this message we won't dispatch any - * D-Bus messages, so that we won't end up wanting to queue - * another message. */ - - r = sd_bus_send(NULL, m->queued_message, NULL); - if (r < 0) - log_warning_errno(r, "Failed to send queued message: %m"); - - m->queued_message = sd_bus_message_unref(m->queued_message); - - return 0; -} - -static int signal_agent_released(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - const char *cgroup, *me; - Manager *m = userdata; - uid_t sender_uid; - sd_bus *bus; - int r; - - assert(message); - assert(m); - - /* ignore recursive events sent by us on the system/user bus */ - bus = sd_bus_message_get_bus(message); - if (!sd_bus_is_server(bus)) { - r = sd_bus_get_unique_name(bus, &me); - if (r < 0) - return r; - - if (streq_ptr(sd_bus_message_get_sender(message), me)) - return 0; - } - - /* only accept org.freedesktop.systemd1.Agent from UID=0 */ - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_euid(creds, &sender_uid); - if (r < 0 || sender_uid != 0) - return 0; - - /* parse 'cgroup-empty' notification */ - r = sd_bus_message_read(message, "s", &cgroup); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - manager_notify_cgroup_empty(m, cgroup); - - /* if running as system-instance, forward under our name */ - if (m->running_as == MANAGER_SYSTEM && m->system_bus) { - r = sd_bus_message_rewind(message, 1); - if (r >= 0) - r = sd_bus_send(m->system_bus, message, NULL); - if (r < 0) - log_warning_errno(r, "Failed to forward Released message: %m"); - } - - return 0; -} - -static int signal_disconnected(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - sd_bus *bus; - - assert(message); - assert(m); - assert_se(bus = sd_bus_message_get_bus(message)); - - if (bus == m->api_bus) - destroy_bus(m, &m->api_bus); - if (bus == m->system_bus) - destroy_bus(m, &m->system_bus); - if (set_remove(m->private_buses, bus)) { - log_debug("Got disconnect on private connection."); - destroy_bus(m, &bus); - } - - return 0; -} - -static int signal_activation_request(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - const char *name; - Unit *u; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - if (manager_unit_inactive_or_pending(m, SPECIAL_DBUS_SERVICE) || - manager_unit_inactive_or_pending(m, SPECIAL_DBUS_SOCKET)) { - r = sd_bus_error_setf(&error, BUS_ERROR_SHUTTING_DOWN, "Refusing activation, D-Bus is shutting down."); - goto failed; - } - - r = manager_load_unit(m, name, NULL, &error, &u); - if (r < 0) - goto failed; - - if (u->refuse_manual_start) { - r = sd_bus_error_setf(&error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, %s may be requested by dependency only.", u->id); - goto failed; - } - - r = manager_add_job(m, JOB_START, u, JOB_REPLACE, &error, NULL); - if (r < 0) - goto failed; - - /* Successfully queued, that's it for us */ - return 0; - -failed: - if (!sd_bus_error_is_set(&error)) - sd_bus_error_set_errno(&error, r); - - log_debug("D-Bus activation failed for %s: %s", name, bus_error_message(&error, r)); - - r = sd_bus_message_new_signal(sd_bus_message_get_bus(message), &reply, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Activator", "ActivationFailure"); - if (r < 0) { - bus_log_create_error(r); - return 0; - } - - r = sd_bus_message_append(reply, "sss", name, error.name, error.message); - if (r < 0) { - bus_log_create_error(r); - return 0; - } - - r = sd_bus_send_to(NULL, reply, "org.freedesktop.DBus", NULL); - if (r < 0) - return log_error_errno(r, "Failed to respond with to bus activation request: %m"); - - return 0; -} - -#ifdef HAVE_SELINUX -static int mac_selinux_filter(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - const char *verb, *path; - Unit *u = NULL; - Job *j; - int r; - - assert(message); - - /* Our own method calls are all protected individually with - * selinux checks, but the built-in interfaces need to be - * protected too. */ - - if (sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Set")) - verb = "reload"; - else if (sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", NULL) || - sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Properties", NULL) || - sd_bus_message_is_method_call(message, "org.freedesktop.DBus.ObjectManager", NULL) || - sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Peer", NULL)) - verb = "status"; - else - return 0; - - path = sd_bus_message_get_path(message); - - if (object_path_startswith("/org/freedesktop/systemd1", path)) { - - r = mac_selinux_access_check(message, verb, error); - if (r < 0) - return r; - - return 0; - } - - if (streq_ptr(path, "/org/freedesktop/systemd1/unit/self")) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t pid; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return 0; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return 0; - - u = manager_get_unit_by_pid(m, pid); - } else { - r = manager_get_job_from_dbus_path(m, path, &j); - if (r >= 0) - u = j->unit; - else - manager_load_unit_from_dbus_path(m, path, NULL, &u); - } - - if (!u) - return 0; - - r = mac_selinux_unit_access_check(u, message, verb, error); - if (r < 0) - return r; - - return 0; -} -#endif - -static int bus_job_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - Job *j; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = manager_get_job_from_dbus_path(m, path, &j); - if (r < 0) - return 0; - - *found = j; - return 1; -} - -static int find_unit(Manager *m, sd_bus *bus, const char *path, Unit **unit, sd_bus_error *error) { - Unit *u; - int r; - - assert(m); - assert(bus); - assert(path); - - if (streq_ptr(path, "/org/freedesktop/systemd1/unit/self")) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - sd_bus_message *message; - pid_t pid; - - message = sd_bus_get_current_message(bus); - if (!message) - return 0; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - - u = manager_get_unit_by_pid(m, pid); - } else { - r = manager_load_unit_from_dbus_path(m, path, error, &u); - if (r < 0) - return 0; - } - - if (!u) - return 0; - - *unit = u; - return 1; -} - -static int bus_unit_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - return find_unit(m, bus, path, (Unit**) found, error); -} - -static int bus_unit_interface_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - Unit *u; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = find_unit(m, bus, path, &u, error); - if (r <= 0) - return r; - - if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) - return 0; - - *found = u; - return 1; -} - -static int bus_unit_cgroup_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - Unit *u; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = find_unit(m, bus, path, &u, error); - if (r <= 0) - return r; - - if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) - return 0; - - if (!UNIT_HAS_CGROUP_CONTEXT(u)) - return 0; - - *found = u; - return 1; -} - -static int bus_cgroup_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - CGroupContext *c; - Unit *u; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = find_unit(m, bus, path, &u, error); - if (r <= 0) - return r; - - if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) - return 0; - - c = unit_get_cgroup_context(u); - if (!c) - return 0; - - *found = c; - return 1; -} - -static int bus_exec_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - ExecContext *c; - Unit *u; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = find_unit(m, bus, path, &u, error); - if (r <= 0) - return r; - - if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) - return 0; - - c = unit_get_exec_context(u); - if (!c) - return 0; - - *found = c; - return 1; -} - -static int bus_kill_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - KillContext *c; - Unit *u; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = find_unit(m, bus, path, &u, error); - if (r <= 0) - return r; - - if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) - return 0; - - c = unit_get_kill_context(u); - if (!c) - return 0; - - *found = c; - return 1; -} - -static int bus_job_enumerate(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - _cleanup_free_ char **l = NULL; - Manager *m = userdata; - unsigned k = 0; - Iterator i; - Job *j; - - l = new0(char*, hashmap_size(m->jobs)+1); - if (!l) - return -ENOMEM; - - HASHMAP_FOREACH(j, m->jobs, i) { - l[k] = job_dbus_path(j); - if (!l[k]) - return -ENOMEM; - - k++; - } - - assert(hashmap_size(m->jobs) == k); - - *nodes = l; - l = NULL; - - return k; -} - -static int bus_unit_enumerate(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - _cleanup_free_ char **l = NULL; - Manager *m = userdata; - unsigned k = 0; - Iterator i; - Unit *u; - - l = new0(char*, hashmap_size(m->units)+1); - if (!l) - return -ENOMEM; - - HASHMAP_FOREACH(u, m->units, i) { - l[k] = unit_dbus_path(u); - if (!l[k]) - return -ENOMEM; - - k++; - } - - *nodes = l; - l = NULL; - - return k; -} - -static int bus_setup_api_vtables(Manager *m, sd_bus *bus) { - UnitType t; - int r; - - assert(m); - assert(bus); - -#ifdef HAVE_SELINUX - r = sd_bus_add_filter(bus, NULL, mac_selinux_filter, m); - if (r < 0) - return log_error_errno(r, "Failed to add SELinux access filter: %m"); -#endif - - r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", bus_manager_vtable, m); - if (r < 0) - return log_error_errno(r, "Failed to register Manager vtable: %m"); - - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/job", "org.freedesktop.systemd1.Job", bus_job_vtable, bus_job_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register Job vtable: %m"); - - r = sd_bus_add_node_enumerator(bus, NULL, "/org/freedesktop/systemd1/job", bus_job_enumerate, m); - if (r < 0) - return log_error_errno(r, "Failed to add job enumerator: %m"); - - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", "org.freedesktop.systemd1.Unit", bus_unit_vtable, bus_unit_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register Unit vtable: %m"); - - r = sd_bus_add_node_enumerator(bus, NULL, "/org/freedesktop/systemd1/unit", bus_unit_enumerate, m); - if (r < 0) - return log_error_errno(r, "Failed to add job enumerator: %m"); - - for (t = 0; t < _UNIT_TYPE_MAX; t++) { - const char *interface; - - assert_se(interface = unit_dbus_interface_from_type(t)); - - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, unit_vtable[t]->bus_vtable, bus_unit_interface_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register type specific vtable for %s: %m", interface); - - if (unit_vtable[t]->cgroup_context_offset > 0) { - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_unit_cgroup_vtable, bus_unit_cgroup_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register control group unit vtable for %s: %m", interface); - - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_cgroup_vtable, bus_cgroup_context_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register control group vtable for %s: %m", interface); - } - - if (unit_vtable[t]->exec_context_offset > 0) { - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_exec_vtable, bus_exec_context_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register execute vtable for %s: %m", interface); - } - - if (unit_vtable[t]->kill_context_offset > 0) { - r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_kill_vtable, bus_kill_context_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register kill vtable for %s: %m", interface); - } - } - - return 0; -} - -static int bus_setup_disconnected_match(Manager *m, sd_bus *bus) { - int r; - - assert(m); - assert(bus); - - r = sd_bus_add_match( - bus, - NULL, - "sender='org.freedesktop.DBus.Local'," - "type='signal'," - "path='/org/freedesktop/DBus/Local'," - "interface='org.freedesktop.DBus.Local'," - "member='Disconnected'", - signal_disconnected, m); - - if (r < 0) - return log_error_errno(r, "Failed to register match for Disconnected message: %m"); - - return 0; -} - -static int bus_on_connection(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - _cleanup_close_ int nfd = -1; - Manager *m = userdata; - sd_id128_t id; - int r; - - assert(s); - assert(m); - - nfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); - if (nfd < 0) { - log_warning_errno(errno, "Failed to accept private connection, ignoring: %m"); - return 0; - } - - if (set_size(m->private_buses) >= CONNECTIONS_MAX) { - log_warning("Too many concurrent connections, refusing"); - return 0; - } - - r = set_ensure_allocated(&m->private_buses, NULL); - if (r < 0) { - log_oom(); - return 0; - } - - r = sd_bus_new(&bus); - if (r < 0) { - log_warning_errno(r, "Failed to allocate new private connection bus: %m"); - return 0; - } - - r = sd_bus_set_fd(bus, nfd, nfd); - if (r < 0) { - log_warning_errno(r, "Failed to set fd on new connection bus: %m"); - return 0; - } - - nfd = -1; - - r = bus_check_peercred(bus); - if (r < 0) { - log_warning_errno(r, "Incoming private connection from unprivileged client, refusing: %m"); - return 0; - } - - assert_se(sd_id128_randomize(&id) >= 0); - - r = sd_bus_set_server(bus, 1, id); - if (r < 0) { - log_warning_errno(r, "Failed to enable server support for new connection bus: %m"); - return 0; - } - - r = sd_bus_negotiate_creds(bus, 1, - SD_BUS_CREDS_PID|SD_BUS_CREDS_UID| - SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS| - SD_BUS_CREDS_SELINUX_CONTEXT); - if (r < 0) { - log_warning_errno(r, "Failed to enable credentials for new connection: %m"); - return 0; - } - - r = sd_bus_start(bus); - if (r < 0) { - log_warning_errno(r, "Failed to start new connection bus: %m"); - return 0; - } - - r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) { - log_warning_errno(r, "Failed to attach new connection bus to event loop: %m"); - return 0; - } - - if (m->running_as == MANAGER_SYSTEM) { - /* When we run as system instance we get the Released - * signal via a direct connection */ - - r = sd_bus_add_match( - bus, - NULL, - "type='signal'," - "interface='org.freedesktop.systemd1.Agent'," - "member='Released'," - "path='/org/freedesktop/systemd1/agent'", - signal_agent_released, m); - - if (r < 0) { - log_warning_errno(r, "Failed to register Released match on new connection bus: %m"); - return 0; - } - } - - r = bus_setup_disconnected_match(m, bus); - if (r < 0) - return 0; - - r = bus_setup_api_vtables(m, bus); - if (r < 0) { - log_warning_errno(r, "Failed to set up API vtables on new connection bus: %m"); - return 0; - } - - r = set_put(m->private_buses, bus); - if (r < 0) { - log_warning_errno(r, "Failed to add new connection bus to set: %m"); - return 0; - } - - bus = NULL; - - log_debug("Accepted new private connection."); - - return 0; -} - -int manager_sync_bus_names(Manager *m, sd_bus *bus) { - _cleanup_strv_free_ char **names = NULL; - const char *name; - Iterator i; - Unit *u; - int r; - - assert(m); - assert(bus); - - r = sd_bus_list_names(bus, &names, NULL); - if (r < 0) - return log_error_errno(r, "Failed to get initial list of names: %m"); - - /* We have to synchronize the current bus names with the - * list of active services. To do this, walk the list of - * all units with bus names. */ - HASHMAP_FOREACH_KEY(u, name, m->watch_bus, i) { - Service *s = SERVICE(u); - - assert(s); - - if (!streq_ptr(s->bus_name, name)) { - log_unit_warning(u, "Bus name has changed from %s → %s, ignoring.", s->bus_name, name); - continue; - } - - /* Check if a service's bus name is in the list of currently - * active names */ - if (strv_contains(names, name)) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - const char *unique; - - /* If it is, determine its current owner */ - r = sd_bus_get_name_creds(bus, name, SD_BUS_CREDS_UNIQUE_NAME, &creds); - if (r < 0) { - log_error_errno(r, "Failed to get bus name owner %s: %m", name); - continue; - } - - r = sd_bus_creds_get_unique_name(creds, &unique); - if (r < 0) { - log_error_errno(r, "Failed to get unique name for %s: %m", name); - continue; - } - - /* Now, let's compare that to the previous bus owner, and - * if it's still the same, all is fine, so just don't - * bother the service. Otherwise, the name has apparently - * changed, so synthesize a name owner changed signal. */ - - if (!streq_ptr(unique, s->bus_name_owner)) - UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, unique); - } else { - /* So, the name we're watching is not on the bus. - * This either means it simply hasn't appeared yet, - * or it was lost during the daemon reload. - * Check if the service has a stored name owner, - * and synthesize a name loss signal in this case. */ - - if (s->bus_name_owner) - UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, NULL); - } - } - - return 0; -} - -static int bus_setup_api(Manager *m, sd_bus *bus) { - Iterator i; - char *name; - Unit *u; - int r; - - assert(m); - assert(bus); - - /* Let's make sure we have enough credential bits so that we can make security and selinux decisions */ - r = sd_bus_negotiate_creds(bus, 1, - SD_BUS_CREDS_PID|SD_BUS_CREDS_UID| - SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS| - SD_BUS_CREDS_SELINUX_CONTEXT); - if (r < 0) - log_warning_errno(r, "Failed to enable credential passing, ignoring: %m"); - - r = bus_setup_api_vtables(m, bus); - if (r < 0) - return r; - - HASHMAP_FOREACH_KEY(u, name, m->watch_bus, i) { - r = unit_install_bus_match(u, bus, name); - if (r < 0) - log_error_errno(r, "Failed to subscribe to NameOwnerChanged signal for '%s': %m", name); - } - - r = sd_bus_add_match( - bus, - NULL, - "type='signal'," - "sender='org.freedesktop.DBus'," - "path='/org/freedesktop/DBus'," - "interface='org.freedesktop.systemd1.Activator'," - "member='ActivationRequest'", - signal_activation_request, m); - if (r < 0) - log_warning_errno(r, "Failed to subscribe to activation signal: %m"); - - /* Allow replacing of our name, to ease implementation of - * reexecution, where we keep the old connection open until - * after the new connection is set up and the name installed - * to allow clients to synchronously wait for reexecution to - * finish */ - r = sd_bus_request_name(bus,"org.freedesktop.systemd1", SD_BUS_NAME_REPLACE_EXISTING|SD_BUS_NAME_ALLOW_REPLACEMENT); - if (r < 0) - return log_error_errno(r, "Failed to register name: %m"); - - r = manager_sync_bus_names(m, bus); - if (r < 0) - return r; - - log_debug("Successfully connected to API bus."); - return 0; -} - -static int bus_init_api(Manager *m) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - int r; - - if (m->api_bus) - return 0; - - /* The API and system bus is the same if we are running in system mode */ - if (m->running_as == MANAGER_SYSTEM && m->system_bus) - bus = sd_bus_ref(m->system_bus); - else { - if (m->running_as == MANAGER_SYSTEM) - r = sd_bus_open_system(&bus); - else - r = sd_bus_open_user(&bus); - - if (r < 0) { - log_debug("Failed to connect to API bus, retrying later..."); - return 0; - } - - r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) { - log_error_errno(r, "Failed to attach API bus to event loop: %m"); - return 0; - } - - r = bus_setup_disconnected_match(m, bus); - if (r < 0) - return 0; - } - - r = bus_setup_api(m, bus); - if (r < 0) { - log_error_errno(r, "Failed to set up API bus: %m"); - return 0; - } - - m->api_bus = bus; - bus = NULL; - - return 0; -} - -static int bus_setup_system(Manager *m, sd_bus *bus) { - int r; - - assert(m); - assert(bus); - - /* On kdbus or if we are a user instance we get the Released message via the system bus */ - if (m->running_as == MANAGER_USER || m->kdbus_fd >= 0) { - r = sd_bus_add_match( - bus, - NULL, - "type='signal'," - "interface='org.freedesktop.systemd1.Agent'," - "member='Released'," - "path='/org/freedesktop/systemd1/agent'", - signal_agent_released, m); - if (r < 0) - log_warning_errno(r, "Failed to register Released match on system bus: %m"); - } - - log_debug("Successfully connected to system bus."); - return 0; -} - -static int bus_init_system(Manager *m) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - int r; - - if (m->system_bus) - return 0; - - /* The API and system bus is the same if we are running in system mode */ - if (m->running_as == MANAGER_SYSTEM && m->api_bus) { - m->system_bus = sd_bus_ref(m->api_bus); - return 0; - } - - r = sd_bus_open_system(&bus); - if (r < 0) { - log_debug("Failed to connect to system bus, retrying later..."); - return 0; - } - - r = bus_setup_disconnected_match(m, bus); - if (r < 0) - return 0; - - r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); - if (r < 0) { - log_error_errno(r, "Failed to attach system bus to event loop: %m"); - return 0; - } - - r = bus_setup_system(m, bus); - if (r < 0) { - log_error_errno(r, "Failed to set up system bus: %m"); - return 0; - } - - m->system_bus = bus; - bus = NULL; - - return 0; -} - -static int bus_init_private(Manager *m) { - _cleanup_close_ int fd = -1; - union sockaddr_union sa = { - .un.sun_family = AF_UNIX - }; - sd_event_source *s; - socklen_t salen; - int r; - - assert(m); - - if (m->private_listen_fd >= 0) - return 0; - - /* We don't need the private socket if we have kdbus */ - if (m->kdbus_fd >= 0) - return 0; - - if (m->running_as == MANAGER_SYSTEM) { - - /* We want the private bus only when running as init */ - if (getpid() != 1) - return 0; - - strcpy(sa.un.sun_path, "/run/systemd/private"); - salen = offsetof(union sockaddr_union, un.sun_path) + strlen("/run/systemd/private"); - } else { - size_t left = sizeof(sa.un.sun_path); - char *p = sa.un.sun_path; - const char *e; - - e = secure_getenv("XDG_RUNTIME_DIR"); - if (!e) { - log_error("Failed to determine XDG_RUNTIME_DIR"); - return -EHOSTDOWN; - } - - left = strpcpy(&p, left, e); - left = strpcpy(&p, left, "/systemd/private"); - - salen = sizeof(sa.un) - left; - } - - (void) mkdir_parents_label(sa.un.sun_path, 0755); - (void) unlink(sa.un.sun_path); - - fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return log_error_errno(errno, "Failed to allocate private socket: %m"); - - r = bind(fd, &sa.sa, salen); - if (r < 0) - return log_error_errno(errno, "Failed to bind private socket: %m"); - - r = listen(fd, SOMAXCONN); - if (r < 0) - return log_error_errno(errno, "Failed to make private socket listening: %m"); - - r = sd_event_add_io(m->event, &s, fd, EPOLLIN, bus_on_connection, m); - if (r < 0) - return log_error_errno(r, "Failed to allocate event source: %m"); - - (void) sd_event_source_set_description(s, "bus-connection"); - - m->private_listen_fd = fd; - m->private_listen_event_source = s; - fd = -1; - - log_debug("Successfully created private D-Bus server."); - - return 0; -} - -int bus_init(Manager *m, bool try_bus_connect) { - int r; - - if (try_bus_connect) { - r = bus_init_system(m); - if (r < 0) - return r; - - r = bus_init_api(m); - if (r < 0) - return r; - } - - r = bus_init_private(m); - if (r < 0) - return r; - - return 0; -} - -static void destroy_bus(Manager *m, sd_bus **bus) { - Iterator i; - Job *j; - - assert(m); - assert(bus); - - if (!*bus) - return; - - /* Get rid of tracked clients on this bus */ - if (m->subscribed && sd_bus_track_get_bus(m->subscribed) == *bus) - m->subscribed = sd_bus_track_unref(m->subscribed); - - HASHMAP_FOREACH(j, m->jobs, i) - if (j->clients && sd_bus_track_get_bus(j->clients) == *bus) - j->clients = sd_bus_track_unref(j->clients); - - /* Get rid of queued message on this bus */ - if (m->queued_message && sd_bus_message_get_bus(m->queued_message) == *bus) - m->queued_message = sd_bus_message_unref(m->queued_message); - - /* Possibly flush unwritten data, but only if we are - * unprivileged, since we don't want to sync here */ - if (m->running_as != MANAGER_SYSTEM) - sd_bus_flush(*bus); - - /* And destroy the object */ - sd_bus_close(*bus); - *bus = sd_bus_unref(*bus); -} - -void bus_done(Manager *m) { - sd_bus *b; - - assert(m); - - if (m->api_bus) - destroy_bus(m, &m->api_bus); - if (m->system_bus) - destroy_bus(m, &m->system_bus); - while ((b = set_steal_first(m->private_buses))) - destroy_bus(m, &b); - - m->private_buses = set_free(m->private_buses); - - m->subscribed = sd_bus_track_unref(m->subscribed); - m->deserialized_subscribed = strv_free(m->deserialized_subscribed); - - if (m->private_listen_event_source) - m->private_listen_event_source = sd_event_source_unref(m->private_listen_event_source); - - m->private_listen_fd = safe_close(m->private_listen_fd); - - bus_verify_polkit_async_registry_free(m->polkit_registry); -} - -int bus_fdset_add_all(Manager *m, FDSet *fds) { - Iterator i; - sd_bus *b; - int fd; - - assert(m); - assert(fds); - - /* When we are about to reexecute we add all D-Bus fds to the - * set to pass over to the newly executed systemd. They won't - * be used there however, except thatt they are closed at the - * very end of deserialization, those making it possible for - * clients to synchronously wait for systemd to reexec by - * simply waiting for disconnection */ - - if (m->api_bus) { - fd = sd_bus_get_fd(m->api_bus); - if (fd >= 0) { - fd = fdset_put_dup(fds, fd); - if (fd < 0) - return fd; - } - } - - SET_FOREACH(b, m->private_buses, i) { - fd = sd_bus_get_fd(b); - if (fd >= 0) { - fd = fdset_put_dup(fds, fd); - if (fd < 0) - return fd; - } - } - - /* We don't offer any APIs on the system bus (well, unless it - * is the same as the API bus) hence we don't bother with it - * here */ - - return 0; -} - -int bus_foreach_bus( - Manager *m, - sd_bus_track *subscribed2, - int (*send_message)(sd_bus *bus, void *userdata), - void *userdata) { - - Iterator i; - sd_bus *b; - int r, ret = 0; - - /* Send to all direct buses, unconditionally */ - SET_FOREACH(b, m->private_buses, i) { - r = send_message(b, userdata); - if (r < 0) - ret = r; - } - - /* Send to API bus, but only if somebody is subscribed */ - if (sd_bus_track_count(m->subscribed) > 0 || - sd_bus_track_count(subscribed2) > 0) { - r = send_message(m->api_bus, userdata); - if (r < 0) - ret = r; - } - - return ret; -} - -void bus_track_serialize(sd_bus_track *t, FILE *f) { - const char *n; - - assert(f); - - for (n = sd_bus_track_first(t); n; n = sd_bus_track_next(t)) - fprintf(f, "subscribed=%s\n", n); -} - -int bus_track_deserialize_item(char ***l, const char *line) { - const char *e; - int r; - - assert(l); - assert(line); - - e = startswith(line, "subscribed="); - if (!e) - return 0; - - r = strv_extend(l, e); - if (r < 0) - return r; - - return 1; -} - -int bus_track_coldplug(Manager *m, sd_bus_track **t, char ***l) { - int r = 0; - - assert(m); - assert(t); - assert(l); - - if (!strv_isempty(*l) && m->api_bus) { - char **i; - - if (!*t) { - r = sd_bus_track_new(m->api_bus, t, NULL, NULL); - if (r < 0) - return r; - } - - r = 0; - STRV_FOREACH(i, *l) { - int k; - - k = sd_bus_track_add_name(*t, *i); - if (k < 0) - r = k; - } - } - - *l = strv_free(*l); - - return r; -} - -int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { - return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.manage-units", NULL, false, UID_INVALID, &m->polkit_registry, error); -} - -int bus_verify_manage_unit_files_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { - return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.manage-unit-files", NULL, false, UID_INVALID, &m->polkit_registry, error); -} - -int bus_verify_reload_daemon_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { - return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.reload-daemon", NULL, false, UID_INVALID, &m->polkit_registry, error); -} - -int bus_verify_set_environment_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { - return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.set-environment", NULL, false, UID_INVALID, &m->polkit_registry, error); -} diff --git a/src/core/dbus.h b/src/core/dbus.h deleted file mode 100644 index e16a84fbb8..0000000000 --- a/src/core/dbus.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "manager.h" - -int bus_send_queued_message(Manager *m); - -int bus_init(Manager *m, bool try_bus_connect); -void bus_done(Manager *m); - -int bus_fdset_add_all(Manager *m, FDSet *fds); - -void bus_track_serialize(sd_bus_track *t, FILE *f); -int bus_track_deserialize_item(char ***l, const char *line); -int bus_track_coldplug(Manager *m, sd_bus_track **t, char ***l); - -int manager_sync_bus_names(Manager *m, sd_bus *bus); - -int bus_foreach_bus(Manager *m, sd_bus_track *subscribed2, int (*send_message)(sd_bus *bus, void *userdata), void *userdata); - -int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error *error); -int bus_verify_manage_unit_files_async(Manager *m, sd_bus_message *call, sd_bus_error *error); -int bus_verify_reload_daemon_async(Manager *m, sd_bus_message *call, sd_bus_error *error); -int bus_verify_set_environment_async(Manager *m, sd_bus_message *call, sd_bus_error *error); diff --git a/src/core/device.c b/src/core/device.c deleted file mode 100644 index d201dc5e4b..0000000000 --- a/src/core/device.c +++ /dev/null @@ -1,878 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "libudev.h" - -#include "alloc-util.h" -#include "dbus-device.h" -#include "device.h" -#include "log.h" -#include "parse-util.h" -#include "path-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "swap.h" -#include "udev-util.h" -#include "unit-name.h" -#include "unit.h" - -static const UnitActiveState state_translation_table[_DEVICE_STATE_MAX] = { - [DEVICE_DEAD] = UNIT_INACTIVE, - [DEVICE_TENTATIVE] = UNIT_ACTIVATING, - [DEVICE_PLUGGED] = UNIT_ACTIVE, -}; - -static int device_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); - -static void device_unset_sysfs(Device *d) { - Hashmap *devices; - Device *first; - - assert(d); - - if (!d->sysfs) - return; - - /* Remove this unit from the chain of devices which share the - * same sysfs path. */ - devices = UNIT(d)->manager->devices_by_sysfs; - first = hashmap_get(devices, d->sysfs); - LIST_REMOVE(same_sysfs, first, d); - - if (first) - hashmap_remove_and_replace(devices, d->sysfs, first->sysfs, first); - else - hashmap_remove(devices, d->sysfs); - - d->sysfs = mfree(d->sysfs); -} - -static int device_set_sysfs(Device *d, const char *sysfs) { - Device *first; - char *copy; - int r; - - assert(d); - - if (streq_ptr(d->sysfs, sysfs)) - return 0; - - r = hashmap_ensure_allocated(&UNIT(d)->manager->devices_by_sysfs, &string_hash_ops); - if (r < 0) - return r; - - copy = strdup(sysfs); - if (!copy) - return -ENOMEM; - - device_unset_sysfs(d); - - first = hashmap_get(UNIT(d)->manager->devices_by_sysfs, sysfs); - LIST_PREPEND(same_sysfs, first, d); - - r = hashmap_replace(UNIT(d)->manager->devices_by_sysfs, copy, first); - if (r < 0) { - LIST_REMOVE(same_sysfs, first, d); - free(copy); - return r; - } - - d->sysfs = copy; - - return 0; -} - -static void device_init(Unit *u) { - Device *d = DEVICE(u); - - assert(d); - assert(UNIT(d)->load_state == UNIT_STUB); - - /* In contrast to all other unit types we timeout jobs waiting - * for devices by default. This is because they otherwise wait - * indefinitely for plugged in devices, something which cannot - * happen for the other units since their operations time out - * anyway. */ - u->job_timeout = u->manager->default_timeout_start_usec; - - u->ignore_on_isolate = true; -} - -static void device_done(Unit *u) { - Device *d = DEVICE(u); - - assert(d); - - device_unset_sysfs(d); -} - -static void device_set_state(Device *d, DeviceState state) { - DeviceState old_state; - assert(d); - - old_state = d->state; - d->state = state; - - if (state != old_state) - log_unit_debug(UNIT(d), "Changed %s -> %s", device_state_to_string(old_state), device_state_to_string(state)); - - unit_notify(UNIT(d), state_translation_table[old_state], state_translation_table[state], true); -} - -static int device_coldplug(Unit *u) { - Device *d = DEVICE(u); - - assert(d); - assert(d->state == DEVICE_DEAD); - - if (d->found & DEVICE_FOUND_UDEV) - /* If udev says the device is around, it's around */ - device_set_state(d, DEVICE_PLUGGED); - else if (d->found != DEVICE_NOT_FOUND && d->deserialized_state != DEVICE_PLUGGED) - /* If a device is found in /proc/self/mountinfo or - * /proc/swaps, and was not yet announced via udev, - * it's "tentatively" around. */ - device_set_state(d, DEVICE_TENTATIVE); - - return 0; -} - -static int device_serialize(Unit *u, FILE *f, FDSet *fds) { - Device *d = DEVICE(u); - - assert(u); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", device_state_to_string(d->state)); - - return 0; -} - -static int device_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Device *d = DEVICE(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - DeviceState state; - - state = device_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - d->deserialized_state = state; - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -static void device_dump(Unit *u, FILE *f, const char *prefix) { - Device *d = DEVICE(u); - - assert(d); - - fprintf(f, - "%sDevice State: %s\n" - "%sSysfs Path: %s\n", - prefix, device_state_to_string(d->state), - prefix, strna(d->sysfs)); -} - -_pure_ static UnitActiveState device_active_state(Unit *u) { - assert(u); - - return state_translation_table[DEVICE(u)->state]; -} - -_pure_ static const char *device_sub_state_to_string(Unit *u) { - assert(u); - - return device_state_to_string(DEVICE(u)->state); -} - -static int device_update_description(Unit *u, struct udev_device *dev, const char *path) { - const char *model; - int r; - - assert(u); - assert(dev); - assert(path); - - model = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE"); - if (!model) - model = udev_device_get_property_value(dev, "ID_MODEL"); - - if (model) { - const char *label; - - /* Try to concatenate the device model string with a label, if there is one */ - label = udev_device_get_property_value(dev, "ID_FS_LABEL"); - if (!label) - label = udev_device_get_property_value(dev, "ID_PART_ENTRY_NAME"); - if (!label) - label = udev_device_get_property_value(dev, "ID_PART_ENTRY_NUMBER"); - - if (label) { - _cleanup_free_ char *j; - - j = strjoin(model, " ", label, NULL); - if (j) - r = unit_set_description(u, j); - else - r = -ENOMEM; - } else - r = unit_set_description(u, model); - } else - r = unit_set_description(u, path); - - if (r < 0) - log_unit_error_errno(u, r, "Failed to set device description: %m"); - - return r; -} - -static int device_add_udev_wants(Unit *u, struct udev_device *dev) { - const char *wants; - const char *word, *state; - size_t l; - int r; - const char *property; - - assert(u); - assert(dev); - - property = u->manager->running_as == MANAGER_USER ? "SYSTEMD_USER_WANTS" : "SYSTEMD_WANTS"; - wants = udev_device_get_property_value(dev, property); - if (!wants) - return 0; - - FOREACH_WORD_QUOTED(word, l, wants, state) { - _cleanup_free_ char *n = NULL; - char e[l+1]; - - memcpy(e, word, l); - e[l] = 0; - - r = unit_name_mangle(e, UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to mangle unit name: %m"); - - r = unit_add_dependency_by_name(u, UNIT_WANTS, n, NULL, true); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to add wants dependency: %m"); - } - if (!isempty(state)) - log_unit_warning(u, "Property %s on %s has trailing garbage, ignoring.", property, strna(udev_device_get_syspath(dev))); - - return 0; -} - -static int device_setup_unit(Manager *m, struct udev_device *dev, const char *path, bool main) { - _cleanup_free_ char *e = NULL; - const char *sysfs = NULL; - Unit *u = NULL; - bool delete; - int r; - - assert(m); - assert(path); - - if (dev) { - sysfs = udev_device_get_syspath(dev); - if (!sysfs) - return 0; - } - - r = unit_name_from_path(path, ".device", &e); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name from device path: %m"); - - u = manager_get_unit(m, e); - - /* The device unit can still be present even if the device was - * unplugged: a mount unit can reference it hence preventing - * the GC to have garbaged it. That's desired since the device - * unit may have a dependency on the mount unit which was - * added during the loading of the later. */ - if (u && DEVICE(u)->state == DEVICE_PLUGGED) { - /* This unit is in plugged state: we're sure it's - * attached to a device. */ - if (!path_equal(DEVICE(u)->sysfs, sysfs)) { - log_unit_error(u, "Dev %s appeared twice with different sysfs paths %s and %s", - e, DEVICE(u)->sysfs, sysfs); - return -EEXIST; - } - } - - if (!u) { - delete = true; - - u = unit_new(m, sizeof(Device)); - if (!u) - return log_oom(); - - r = unit_add_name(u, e); - if (r < 0) - goto fail; - - unit_add_to_load_queue(u); - } else - delete = false; - - /* If this was created via some dependency and has not - * actually been seen yet ->sysfs will not be - * initialized. Hence initialize it if necessary. */ - if (sysfs) { - r = device_set_sysfs(DEVICE(u), sysfs); - if (r < 0) - goto fail; - - (void) device_update_description(u, dev, path); - - /* The additional systemd udev properties we only interpret - * for the main object */ - if (main) - (void) device_add_udev_wants(u, dev); - } - - - /* Note that this won't dispatch the load queue, the caller - * has to do that if needed and appropriate */ - - unit_add_to_dbus_queue(u); - return 0; - -fail: - log_unit_warning_errno(u, r, "Failed to set up device unit: %m"); - - if (delete) - unit_free(u); - - return r; -} - -static int device_process_new(Manager *m, struct udev_device *dev) { - const char *sysfs, *dn, *alias; - struct udev_list_entry *item = NULL, *first = NULL; - int r; - - assert(m); - - sysfs = udev_device_get_syspath(dev); - if (!sysfs) - return 0; - - /* Add the main unit named after the sysfs path */ - r = device_setup_unit(m, dev, sysfs, true); - if (r < 0) - return r; - - /* Add an additional unit for the device node */ - dn = udev_device_get_devnode(dev); - if (dn) - (void) device_setup_unit(m, dev, dn, false); - - /* Add additional units for all symlinks */ - first = udev_device_get_devlinks_list_entry(dev); - udev_list_entry_foreach(item, first) { - const char *p; - struct stat st; - - /* Don't bother with the /dev/block links */ - p = udev_list_entry_get_name(item); - - if (path_startswith(p, "/dev/block/") || - path_startswith(p, "/dev/char/")) - continue; - - /* Verify that the symlink in the FS actually belongs - * to this device. This is useful to deal with - * conflicting devices, e.g. when two disks want the - * same /dev/disk/by-label/xxx link because they have - * the same label. We want to make sure that the same - * device that won the symlink wins in systemd, so we - * check the device node major/minor */ - if (stat(p, &st) >= 0) - if ((!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) || - st.st_rdev != udev_device_get_devnum(dev)) - continue; - - (void) device_setup_unit(m, dev, p, false); - } - - /* Add additional units for all explicitly configured - * aliases */ - alias = udev_device_get_property_value(dev, "SYSTEMD_ALIAS"); - if (alias) { - const char *word, *state; - size_t l; - - FOREACH_WORD_QUOTED(word, l, alias, state) { - char e[l+1]; - - memcpy(e, word, l); - e[l] = 0; - - if (path_is_absolute(e)) - (void) device_setup_unit(m, dev, e, false); - else - log_warning("SYSTEMD_ALIAS for %s is not an absolute path, ignoring: %s", sysfs, e); - } - if (!isempty(state)) - log_warning("SYSTEMD_ALIAS for %s has trailing garbage, ignoring.", sysfs); - } - - return 0; -} - -static void device_update_found_one(Device *d, bool add, DeviceFound found, bool now) { - DeviceFound n, previous; - - assert(d); - - n = add ? (d->found | found) : (d->found & ~found); - if (n == d->found) - return; - - previous = d->found; - d->found = n; - - if (!now) - return; - - if (d->found & DEVICE_FOUND_UDEV) - /* When the device is known to udev we consider it - * plugged. */ - device_set_state(d, DEVICE_PLUGGED); - else if (d->found != DEVICE_NOT_FOUND && (previous & DEVICE_FOUND_UDEV) == 0) - /* If the device has not been seen by udev yet, but is - * now referenced by the kernel, then we assume the - * kernel knows it now, and udev might soon too. */ - device_set_state(d, DEVICE_TENTATIVE); - else - /* If nobody sees the device, or if the device was - * previously seen by udev and now is only referenced - * from the kernel, then we consider the device is - * gone, the kernel just hasn't noticed it yet. */ - device_set_state(d, DEVICE_DEAD); -} - -static int device_update_found_by_sysfs(Manager *m, const char *sysfs, bool add, DeviceFound found, bool now) { - Device *d, *l; - - assert(m); - assert(sysfs); - - if (found == DEVICE_NOT_FOUND) - return 0; - - l = hashmap_get(m->devices_by_sysfs, sysfs); - LIST_FOREACH(same_sysfs, d, l) - device_update_found_one(d, add, found, now); - - return 0; -} - -static int device_update_found_by_name(Manager *m, const char *path, bool add, DeviceFound found, bool now) { - _cleanup_free_ char *e = NULL; - Unit *u; - int r; - - assert(m); - assert(path); - - if (found == DEVICE_NOT_FOUND) - return 0; - - r = unit_name_from_path(path, ".device", &e); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name from device path: %m"); - - u = manager_get_unit(m, e); - if (!u) - return 0; - - device_update_found_one(DEVICE(u), add, found, now); - return 0; -} - -static bool device_is_ready(struct udev_device *dev) { - const char *ready; - - assert(dev); - - ready = udev_device_get_property_value(dev, "SYSTEMD_READY"); - if (!ready) - return true; - - return parse_boolean(ready) != 0; -} - -static Unit *device_following(Unit *u) { - Device *d = DEVICE(u); - Device *other, *first = NULL; - - assert(d); - - if (startswith(u->id, "sys-")) - return NULL; - - /* Make everybody follow the unit that's named after the sysfs path */ - for (other = d->same_sysfs_next; other; other = other->same_sysfs_next) - if (startswith(UNIT(other)->id, "sys-")) - return UNIT(other); - - for (other = d->same_sysfs_prev; other; other = other->same_sysfs_prev) { - if (startswith(UNIT(other)->id, "sys-")) - return UNIT(other); - - first = other; - } - - return UNIT(first); -} - -static int device_following_set(Unit *u, Set **_set) { - Device *d = DEVICE(u), *other; - Set *set; - int r; - - assert(d); - assert(_set); - - if (LIST_JUST_US(same_sysfs, d)) { - *_set = NULL; - return 0; - } - - set = set_new(NULL); - if (!set) - return -ENOMEM; - - LIST_FOREACH_AFTER(same_sysfs, other, d) { - r = set_put(set, other); - if (r < 0) - goto fail; - } - - LIST_FOREACH_BEFORE(same_sysfs, other, d) { - r = set_put(set, other); - if (r < 0) - goto fail; - } - - *_set = set; - return 1; - -fail: - set_free(set); - return r; -} - -static void device_shutdown(Manager *m) { - assert(m); - - m->udev_event_source = sd_event_source_unref(m->udev_event_source); - - if (m->udev_monitor) { - udev_monitor_unref(m->udev_monitor); - m->udev_monitor = NULL; - } - - m->devices_by_sysfs = hashmap_free(m->devices_by_sysfs); -} - -static void device_enumerate(Manager *m) { - _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; - struct udev_list_entry *item = NULL, *first = NULL; - int r; - - assert(m); - - if (!m->udev_monitor) { - m->udev_monitor = udev_monitor_new_from_netlink(m->udev, "udev"); - if (!m->udev_monitor) { - log_oom(); - goto fail; - } - - /* This will fail if we are unprivileged, but that - * should not matter much, as user instances won't run - * during boot. */ - (void) udev_monitor_set_receive_buffer_size(m->udev_monitor, 128*1024*1024); - - r = udev_monitor_filter_add_match_tag(m->udev_monitor, "systemd"); - if (r < 0) { - log_error_errno(r, "Failed to add udev tag match: %m"); - goto fail; - } - - r = udev_monitor_enable_receiving(m->udev_monitor); - if (r < 0) { - log_error_errno(r, "Failed to enable udev event reception: %m"); - goto fail; - } - - r = sd_event_add_io(m->event, &m->udev_event_source, udev_monitor_get_fd(m->udev_monitor), EPOLLIN, device_dispatch_io, m); - if (r < 0) { - log_error_errno(r, "Failed to watch udev file descriptor: %m"); - goto fail; - } - - (void) sd_event_source_set_description(m->udev_event_source, "device"); - } - - e = udev_enumerate_new(m->udev); - if (!e) { - log_oom(); - goto fail; - } - - r = udev_enumerate_add_match_tag(e, "systemd"); - if (r < 0) { - log_error_errno(r, "Failed to create udev tag enumeration: %m"); - goto fail; - } - - r = udev_enumerate_add_match_is_initialized(e); - if (r < 0) { - log_error_errno(r, "Failed to install initialization match into enumeration: %m"); - goto fail; - } - - r = udev_enumerate_scan_devices(e); - if (r < 0) { - log_error_errno(r, "Failed to enumerate devices: %m"); - goto fail; - } - - first = udev_enumerate_get_list_entry(e); - udev_list_entry_foreach(item, first) { - _cleanup_udev_device_unref_ struct udev_device *dev = NULL; - const char *sysfs; - - sysfs = udev_list_entry_get_name(item); - - dev = udev_device_new_from_syspath(m->udev, sysfs); - if (!dev) { - log_oom(); - continue; - } - - if (!device_is_ready(dev)) - continue; - - (void) device_process_new(m, dev); - - device_update_found_by_sysfs(m, sysfs, true, DEVICE_FOUND_UDEV, false); - } - - return; - -fail: - device_shutdown(m); -} - -static int device_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - _cleanup_udev_device_unref_ struct udev_device *dev = NULL; - Manager *m = userdata; - const char *action, *sysfs; - int r; - - assert(m); - - if (revents != EPOLLIN) { - static RATELIMIT_DEFINE(limit, 10*USEC_PER_SEC, 5); - - if (!ratelimit_test(&limit)) - log_error_errno(errno, "Failed to get udev event: %m"); - if (!(revents & EPOLLIN)) - return 0; - } - - /* - * libudev might filter-out devices which pass the bloom - * filter, so getting NULL here is not necessarily an error. - */ - dev = udev_monitor_receive_device(m->udev_monitor); - if (!dev) - return 0; - - sysfs = udev_device_get_syspath(dev); - if (!sysfs) { - log_error("Failed to get udev sys path."); - return 0; - } - - action = udev_device_get_action(dev); - if (!action) { - log_error("Failed to get udev action string."); - return 0; - } - - if (streq(action, "remove")) { - r = swap_process_device_remove(m, dev); - if (r < 0) - log_error_errno(r, "Failed to process swap device remove event: %m"); - - /* If we get notified that a device was removed by - * udev, then it's completely gone, hence unset all - * found bits */ - device_update_found_by_sysfs(m, sysfs, false, DEVICE_FOUND_UDEV|DEVICE_FOUND_MOUNT|DEVICE_FOUND_SWAP, true); - - } else if (device_is_ready(dev)) { - - (void) device_process_new(m, dev); - - r = swap_process_device_new(m, dev); - if (r < 0) - log_error_errno(r, "Failed to process swap device new event: %m"); - - manager_dispatch_load_queue(m); - - /* The device is found now, set the udev found bit */ - device_update_found_by_sysfs(m, sysfs, true, DEVICE_FOUND_UDEV, true); - - } else { - /* The device is nominally around, but not ready for - * us. Hence unset the udev bit, but leave the rest - * around. */ - - device_update_found_by_sysfs(m, sysfs, false, DEVICE_FOUND_UDEV, true); - } - - return 0; -} - -static bool device_supported(void) { - static int read_only = -1; - - /* If /sys is read-only we don't support device units, and any - * attempts to start one should fail immediately. */ - - if (read_only < 0) - read_only = path_is_read_only_fs("/sys"); - - return read_only <= 0; -} - -int device_found_node(Manager *m, const char *node, bool add, DeviceFound found, bool now) { - _cleanup_udev_device_unref_ struct udev_device *dev = NULL; - struct stat st; - - assert(m); - assert(node); - - if (!device_supported()) - return 0; - - /* This is called whenever we find a device referenced in - * /proc/swaps or /proc/self/mounts. Such a device might be - * mounted/enabled at a time where udev has not finished - * probing it yet, and we thus haven't learned about it - * yet. In this case we will set the device unit to - * "tentative" state. */ - - if (add) { - if (!path_startswith(node, "/dev")) - return 0; - - /* We make an extra check here, if the device node - * actually exists. If it's missing, then this is an - * indication that device was unplugged but is still - * referenced in /proc/swaps or - * /proc/self/mountinfo. Note that this check doesn't - * really cover all cases where a device might be gone - * away, since drives that can have a medium inserted - * will still have a device node even when the medium - * is not there... */ - - if (stat(node, &st) >= 0) { - if (!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) - return 0; - - dev = udev_device_new_from_devnum(m->udev, S_ISBLK(st.st_mode) ? 'b' : 'c', st.st_rdev); - if (!dev && errno != ENOENT) - return log_error_errno(errno, "Failed to get udev device from devnum %u:%u: %m", major(st.st_rdev), minor(st.st_rdev)); - - } else if (errno != ENOENT) - return log_error_errno(errno, "Failed to stat device node file %s: %m", node); - - /* If the device is known in the kernel and newly - * appeared, then we'll create a device unit for it, - * under the name referenced in /proc/swaps or - * /proc/self/mountinfo. */ - - (void) device_setup_unit(m, dev, node, false); - } - - /* Update the device unit's state, should it exist */ - return device_update_found_by_name(m, node, add, found, now); -} - -const UnitVTable device_vtable = { - .object_size = sizeof(Device), - .sections = - "Unit\0" - "Device\0" - "Install\0", - - .no_instances = true, - - .init = device_init, - .done = device_done, - .load = unit_load_fragment_and_dropin_optional, - - .coldplug = device_coldplug, - - .serialize = device_serialize, - .deserialize_item = device_deserialize_item, - - .dump = device_dump, - - .active_state = device_active_state, - .sub_state_to_string = device_sub_state_to_string, - - .bus_vtable = bus_device_vtable, - - .following = device_following, - .following_set = device_following_set, - - .enumerate = device_enumerate, - .shutdown = device_shutdown, - .supported = device_supported, - - .status_message_formats = { - .starting_stopping = { - [0] = "Expecting device %s...", - }, - .finished_start_job = { - [JOB_DONE] = "Found device %s.", - [JOB_TIMEOUT] = "Timed out waiting for device %s.", - }, - }, -}; diff --git a/src/core/device.h b/src/core/device.h deleted file mode 100644 index 184a1a349b..0000000000 --- a/src/core/device.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Device Device; - -typedef enum DeviceFound { - DEVICE_NOT_FOUND = 0, - DEVICE_FOUND_UDEV = 1, - DEVICE_FOUND_MOUNT = 2, - DEVICE_FOUND_SWAP = 4, -} DeviceFound; - -struct Device { - Unit meta; - - char *sysfs; - DeviceFound found; - - /* In order to be able to distinguish dependencies on - different device nodes we might end up creating multiple - devices for the same sysfs path. We chain them up here. */ - LIST_FIELDS(struct Device, same_sysfs); - - DeviceState state, deserialized_state; -}; - -extern const UnitVTable device_vtable; - -int device_found_node(Manager *m, const char *node, bool add, DeviceFound found, bool now); diff --git a/src/core/execute.c b/src/core/execute.c deleted file mode 100644 index 1e4630182d..0000000000 --- a/src/core/execute.c +++ /dev/null @@ -1,3165 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_PAM -#include -#endif - -#ifdef HAVE_SELINUX -#include -#endif - -#ifdef HAVE_SECCOMP -#include -#endif - -#ifdef HAVE_APPARMOR -#include -#endif - -#include "sd-messages.h" - -#include "af-list.h" -#include "alloc-util.h" -#ifdef HAVE_APPARMOR -#include "apparmor-util.h" -#endif -#include "async.h" -#include "barrier.h" -#include "bus-endpoint.h" -#include "cap-list.h" -#include "capability-util.h" -#include "def.h" -#include "env-util.h" -#include "errno-list.h" -#include "execute.h" -#include "exit-status.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "glob-util.h" -#include "io-util.h" -#include "ioprio.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "namespace.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "rlimit-util.h" -#include "rm-rf.h" -#ifdef HAVE_SECCOMP -#include "seccomp-util.h" -#endif -#include "securebits.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "smack-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "syslog-util.h" -#include "terminal-util.h" -#include "unit.h" -#include "user-util.h" -#include "util.h" -#include "utmp-wtmp.h" - -#define IDLE_TIMEOUT_USEC (5*USEC_PER_SEC) -#define IDLE_TIMEOUT2_USEC (1*USEC_PER_SEC) - -/* This assumes there is a 'tty' group */ -#define TTY_MODE 0620 - -#define SNDBUF_SIZE (8*1024*1024) - -static int shift_fds(int fds[], unsigned n_fds) { - int start, restart_from; - - if (n_fds <= 0) - return 0; - - /* Modifies the fds array! (sorts it) */ - - assert(fds); - - start = 0; - for (;;) { - int i; - - restart_from = -1; - - for (i = start; i < (int) n_fds; i++) { - int nfd; - - /* Already at right index? */ - if (fds[i] == i+3) - continue; - - nfd = fcntl(fds[i], F_DUPFD, i + 3); - if (nfd < 0) - return -errno; - - safe_close(fds[i]); - fds[i] = nfd; - - /* Hmm, the fd we wanted isn't free? Then - * let's remember that and try again from here */ - if (nfd != i+3 && restart_from < 0) - restart_from = i; - } - - if (restart_from < 0) - break; - - start = restart_from; - } - - return 0; -} - -static int flags_fds(const int fds[], unsigned n_fds, bool nonblock) { - unsigned i; - int r; - - if (n_fds <= 0) - return 0; - - assert(fds); - - /* Drops/Sets O_NONBLOCK and FD_CLOEXEC from the file flags */ - - for (i = 0; i < n_fds; i++) { - - r = fd_nonblock(fds[i], nonblock); - if (r < 0) - return r; - - /* We unconditionally drop FD_CLOEXEC from the fds, - * since after all we want to pass these fds to our - * children */ - - r = fd_cloexec(fds[i], false); - if (r < 0) - return r; - } - - return 0; -} - -static const char *exec_context_tty_path(const ExecContext *context) { - assert(context); - - if (context->stdio_as_fds) - return NULL; - - if (context->tty_path) - return context->tty_path; - - return "/dev/console"; -} - -static void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) { - const char *path; - - assert(context); - - path = exec_context_tty_path(context); - - if (context->tty_vhangup) { - if (p && p->stdin_fd >= 0) - (void) terminal_vhangup_fd(p->stdin_fd); - else if (path) - (void) terminal_vhangup(path); - } - - if (context->tty_reset) { - if (p && p->stdin_fd >= 0) - (void) reset_terminal_fd(p->stdin_fd, true); - else if (path) - (void) reset_terminal(path); - } - - if (context->tty_vt_disallocate && path) - (void) vt_disallocate(path); -} - -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; -} - -static int open_null_as(int flags, int nfd) { - int fd, r; - - assert(nfd >= 0); - - fd = open("/dev/null", flags|O_NOCTTY); - if (fd < 0) - return -errno; - - if (fd != nfd) { - r = dup2(fd, nfd) < 0 ? -errno : nfd; - safe_close(fd); - } else - r = nfd; - - return r; -} - -static int connect_journal_socket(int fd, uid_t uid, gid_t gid) { - union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - .un.sun_path = "/run/systemd/journal/stdout", - }; - uid_t olduid = UID_INVALID; - gid_t oldgid = GID_INVALID; - int r; - - if (gid != GID_INVALID) { - oldgid = getgid(); - - r = setegid(gid); - if (r < 0) - return -errno; - } - - if (uid != UID_INVALID) { - olduid = getuid(); - - r = seteuid(uid); - if (r < 0) { - r = -errno; - goto restore_gid; - } - } - - r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)); - if (r < 0) - r = -errno; - - /* If we fail to restore the uid or gid, things will likely - fail later on. This should only happen if an LSM interferes. */ - - if (uid != UID_INVALID) - (void) seteuid(olduid); - - restore_gid: - if (gid != GID_INVALID) - (void) setegid(oldgid); - - 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) { - int fd, r; - - assert(context); - assert(output < _EXEC_OUTPUT_MAX); - assert(ident); - assert(nfd >= 0); - - fd = socket(AF_UNIX, SOCK_STREAM, 0); - if (fd < 0) - return -errno; - - r = connect_journal_socket(fd, uid, gid); - if (r < 0) - return r; - - if (shutdown(fd, SHUT_RD) < 0) { - safe_close(fd); - return -errno; - } - - fd_inc_sndbuf(fd, SNDBUF_SIZE); - - dprintf(fd, - "%s\n" - "%s\n" - "%i\n" - "%i\n" - "%i\n" - "%i\n" - "%i\n", - context->syslog_identifier ? context->syslog_identifier : ident, - 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; - - return r; -} -static int open_terminal_as(const char *path, mode_t mode, int nfd) { - int fd, r; - - assert(path); - assert(nfd >= 0); - - fd = open_terminal(path, mode | O_NOCTTY); - if (fd < 0) - return fd; - - if (fd != nfd) { - r = dup2(fd, nfd) < 0 ? -errno : nfd; - safe_close(fd); - } else - r = 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) - return EXEC_INPUT_NULL; - - if (std_input == EXEC_INPUT_SOCKET && socket_fd < 0) - return EXEC_INPUT_NULL; - - return std_input; -} - -static int fixup_output(ExecOutput std_output, int socket_fd) { - - if (std_output == EXEC_OUTPUT_SOCKET && socket_fd < 0) - return EXEC_OUTPUT_INHERIT; - - return std_output; -} - -static int setup_input( - const ExecContext *context, - const ExecParameters *params, - int socket_fd) { - - ExecInput i; - - assert(context); - assert(params); - - if (params->stdin_fd >= 0) { - if (dup2(params->stdin_fd, STDIN_FILENO) < 0) - return -errno; - - /* Try to make this the controlling tty, if it is a tty, and reset it */ - (void) ioctl(STDIN_FILENO, TIOCSCTTY, context->std_input == EXEC_INPUT_TTY_FORCE); - (void) reset_terminal_fd(STDIN_FILENO, true); - - return STDIN_FILENO; - } - - i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin); - - switch (i) { - - case EXEC_INPUT_NULL: - return open_null_as(O_RDONLY, STDIN_FILENO); - - case EXEC_INPUT_TTY: - case EXEC_INPUT_TTY_FORCE: - case EXEC_INPUT_TTY_FAIL: { - int fd, r; - - fd = acquire_terminal(exec_context_tty_path(context), - i == EXEC_INPUT_TTY_FAIL, - i == EXEC_INPUT_TTY_FORCE, - false, - USEC_INFINITY); - if (fd < 0) - return fd; - - if (fd != STDIN_FILENO) { - r = dup2(fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; - safe_close(fd); - } else - r = STDIN_FILENO; - - return r; - } - - case EXEC_INPUT_SOCKET: - return dup2(socket_fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; - - default: - assert_not_reached("Unknown input type"); - } -} - -static int setup_output( - Unit *unit, - const ExecContext *context, - const ExecParameters *params, - int fileno, - int socket_fd, - const char *ident, - uid_t uid, gid_t gid) { - - ExecOutput o; - ExecInput i; - int r; - - assert(unit); - assert(context); - assert(params); - assert(ident); - - if (fileno == STDOUT_FILENO && params->stdout_fd >= 0) { - - if (dup2(params->stdout_fd, STDOUT_FILENO) < 0) - return -errno; - - return STDOUT_FILENO; - } - - if (fileno == STDERR_FILENO && params->stderr_fd >= 0) { - if (dup2(params->stderr_fd, STDERR_FILENO) < 0) - return -errno; - - return STDERR_FILENO; - } - - i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin); - o = fixup_output(context->std_output, socket_fd); - - if (fileno == STDERR_FILENO) { - ExecOutput e; - e = fixup_output(context->std_error, socket_fd); - - /* This expects the input and output are already set up */ - - /* Don't change the stderr file descriptor if we inherit all - * the way and are not on a tty */ - if (e == EXEC_OUTPUT_INHERIT && - o == EXEC_OUTPUT_INHERIT && - i == EXEC_INPUT_NULL && - !is_terminal_input(context->std_input) && - getppid () != 1) - return fileno; - - /* Duplicate from stdout if possible */ - if (e == o || e == EXEC_OUTPUT_INHERIT) - return dup2(STDOUT_FILENO, fileno) < 0 ? -errno : fileno; - - o = e; - - } else if (o == EXEC_OUTPUT_INHERIT) { - /* If input got downgraded, inherit the original value */ - if (i == EXEC_INPUT_NULL && is_terminal_input(context->std_input)) - return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); - - /* If the input is connected to anything that's not a /dev/null, inherit that... */ - if (i != EXEC_INPUT_NULL) - return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno; - - /* If we are not started from PID 1 we just inherit STDOUT from our parent process. */ - if (getppid() != 1) - return fileno; - - /* We need to open /dev/null here anew, to get the right access mode. */ - return open_null_as(O_WRONLY, fileno); - } - - switch (o) { - - case EXEC_OUTPUT_NULL: - return open_null_as(O_WRONLY, fileno); - - case EXEC_OUTPUT_TTY: - if (is_terminal_input(i)) - return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno; - - /* We don't reset the terminal if this is just about output */ - return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); - - case EXEC_OUTPUT_SYSLOG: - case EXEC_OUTPUT_SYSLOG_AND_CONSOLE: - case EXEC_OUTPUT_KMSG: - 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); - 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); - } - return r; - - case EXEC_OUTPUT_SOCKET: - assert(socket_fd >= 0); - return dup2(socket_fd, fileno) < 0 ? -errno : fileno; - - default: - assert_not_reached("Unknown error type"); - } -} - -static int chown_terminal(int fd, uid_t uid) { - struct stat st; - - assert(fd >= 0); - - /* This might fail. What matters are the results. */ - (void) fchown(fd, uid, -1); - (void) fchmod(fd, TTY_MODE); - - if (fstat(fd, &st) < 0) - return -errno; - - if (st.st_uid != uid || (st.st_mode & 0777) != TTY_MODE) - return -EPERM; - - return 0; -} - -static int setup_confirm_stdio(int *_saved_stdin, int *_saved_stdout) { - _cleanup_close_ int fd = -1, saved_stdin = -1, saved_stdout = -1; - int r; - - assert(_saved_stdin); - assert(_saved_stdout); - - saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3); - if (saved_stdin < 0) - return -errno; - - saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3); - if (saved_stdout < 0) - return -errno; - - fd = acquire_terminal( - "/dev/console", - false, - false, - false, - DEFAULT_CONFIRM_USEC); - if (fd < 0) - return fd; - - r = chown_terminal(fd, getuid()); - if (r < 0) - return r; - - r = reset_terminal_fd(fd, true); - if (r < 0) - return r; - - if (dup2(fd, STDIN_FILENO) < 0) - return -errno; - - if (dup2(fd, STDOUT_FILENO) < 0) - return -errno; - - if (fd >= 2) - safe_close(fd); - fd = -1; - - *_saved_stdin = saved_stdin; - *_saved_stdout = saved_stdout; - - saved_stdin = saved_stdout = -1; - - return 0; -} - -_printf_(1, 2) static int write_confirm_message(const char *format, ...) { - _cleanup_close_ int fd = -1; - va_list ap; - - assert(format); - - fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - va_start(ap, format); - vdprintf(fd, format, ap); - va_end(ap); - - return 0; -} - -static int restore_confirm_stdio(int *saved_stdin, int *saved_stdout) { - int r = 0; - - assert(saved_stdin); - assert(saved_stdout); - - release_terminal(); - - if (*saved_stdin >= 0) - if (dup2(*saved_stdin, STDIN_FILENO) < 0) - r = -errno; - - if (*saved_stdout >= 0) - if (dup2(*saved_stdout, STDOUT_FILENO) < 0) - r = -errno; - - *saved_stdin = safe_close(*saved_stdin); - *saved_stdout = safe_close(*saved_stdout); - - return r; -} - -static int ask_for_confirmation(char *response, char **argv) { - int saved_stdout = -1, saved_stdin = -1, r; - _cleanup_free_ char *line = NULL; - - r = setup_confirm_stdio(&saved_stdin, &saved_stdout); - if (r < 0) - return r; - - line = exec_command_line(argv); - if (!line) - return -ENOMEM; - - r = ask_char(response, "yns", "Execute %s? [Yes, No, Skip] ", line); - - restore_confirm_stdio(&saved_stdin, &saved_stdout); - - return r; -} - -static int enforce_groups(const ExecContext *context, const char *username, gid_t gid) { - bool keep_groups = false; - int r; - - assert(context); - - /* Lookup and set GID and supplementary group list. Here too - * we avoid NSS lookups for gid=0. */ - - if (context->group || username) { - /* First step, initialize groups from /etc/groups */ - if (username && gid != 0) { - if (initgroups(username, gid) < 0) - return -errno; - - keep_groups = true; - } - - /* Second step, set our gids */ - if (setresgid(gid, gid, gid) < 0) - return -errno; - } - - if (context->supplementary_groups) { - int ngroups_max, k; - gid_t *gids; - char **i; - - /* Final step, initialize any manually set supplementary groups */ - assert_se((ngroups_max = (int) sysconf(_SC_NGROUPS_MAX)) > 0); - - if (!(gids = new(gid_t, ngroups_max))) - return -ENOMEM; - - if (keep_groups) { - k = getgroups(ngroups_max, gids); - if (k < 0) { - free(gids); - return -errno; - } - } else - k = 0; - - STRV_FOREACH(i, context->supplementary_groups) { - const char *g; - - if (k >= ngroups_max) { - free(gids); - return -E2BIG; - } - - g = *i; - r = get_group_creds(&g, gids+k); - if (r < 0) { - free(gids); - return r; - } - - k++; - } - - if (setgroups(k, gids) < 0) { - free(gids); - return -errno; - } - - free(gids); - } - - return 0; -} - -static int enforce_user(const ExecContext *context, uid_t uid) { - assert(context); - - /* Sets (but doesn't lookup) the uid and make sure we keep the - * capabilities while doing so. */ - - if (context->capabilities || context->capability_ambient_set != 0) { - - /* First step: If we need to keep capabilities but - * drop privileges we need to make sure we keep our - * caps, while we drop privileges. */ - if (uid != 0) { - int sb = context->secure_bits | 1<capabilities) { - _cleanup_cap_free_ cap_t d = NULL; - static const cap_value_t bits[] = { - CAP_SETUID, /* Necessary so that we can run setresuid() below */ - CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */ - }; - - d = cap_dup(context->capabilities); - if (!d) - return -errno; - - if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 || - cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0) - return -errno; - - if (cap_set_proc(d) < 0) - return -errno; - } - } - - /* Third step: actually set the uids */ - if (setresuid(uid, uid, uid) < 0) - return -errno; - - /* At this point we should have all necessary capabilities but - are otherwise a normal user. However, the caps might got - corrupted due to the setresuid() so we need clean them up - later. This is done outside of this call. */ - - return 0; -} - -#ifdef HAVE_PAM - -static int null_conv( - int num_msg, - const struct pam_message **msg, - struct pam_response **resp, - void *appdata_ptr) { - - /* We don't support conversations */ - - return PAM_CONV_ERR; -} - -static int setup_pam( - const char *name, - const char *user, - uid_t uid, - const char *tty, - char ***pam_env, - int fds[], unsigned n_fds) { - - static const struct pam_conv conv = { - .conv = null_conv, - .appdata_ptr = NULL - }; - - _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; - pam_handle_t *handle = NULL; - sigset_t old_ss; - int pam_code = PAM_SUCCESS, r; - char **e = NULL; - bool close_session = false; - pid_t pam_pid = 0, parent_pid; - int flags = 0; - - assert(name); - assert(user); - assert(pam_env); - - /* We set up PAM in the parent process, then fork. The child - * will then stay around until killed via PR_GET_PDEATHSIG or - * systemd via the cgroup logic. It will then remove the PAM - * session again. The parent process will exec() the actual - * daemon. We do things this way to ensure that the main PID - * of the daemon is the one we initially fork()ed. */ - - r = barrier_create(&barrier); - if (r < 0) - goto fail; - - if (log_get_max_level() < LOG_DEBUG) - flags |= PAM_SILENT; - - pam_code = pam_start(name, user, &conv, &handle); - if (pam_code != PAM_SUCCESS) { - handle = NULL; - goto fail; - } - - if (tty) { - pam_code = pam_set_item(handle, PAM_TTY, tty); - if (pam_code != PAM_SUCCESS) - goto fail; - } - - pam_code = pam_acct_mgmt(handle, flags); - if (pam_code != PAM_SUCCESS) - goto fail; - - pam_code = pam_open_session(handle, flags); - if (pam_code != PAM_SUCCESS) - goto fail; - - close_session = true; - - e = pam_getenvlist(handle); - if (!e) { - pam_code = PAM_BUF_ERR; - goto fail; - } - - /* Block SIGTERM, so that we know that it won't get lost in - * the child */ - - assert_se(sigprocmask_many(SIG_BLOCK, &old_ss, SIGTERM, -1) >= 0); - - parent_pid = getpid(); - - pam_pid = fork(); - if (pam_pid < 0) { - r = -errno; - goto fail; - } - - if (pam_pid == 0) { - int sig, ret = EXIT_PAM; - - /* The child's job is to reset the PAM session on - * termination */ - barrier_set_role(&barrier, BARRIER_CHILD); - - /* This string must fit in 10 chars (i.e. the length - * of "/sbin/init"), to look pretty in /bin/ps */ - rename_process("(sd-pam)"); - - /* Make sure we don't keep open the passed fds in this - child. We assume that otherwise only those fds are - open here that have been opened by PAM. */ - close_many(fds, n_fds); - - /* Drop privileges - we don't need any to pam_close_session - * and this will make PR_SET_PDEATHSIG work in most cases. - * If this fails, ignore the error - but expect sd-pam threads - * to fail to exit normally */ - if (setresuid(uid, uid, uid) < 0) - log_error_errno(r, "Error: Failed to setresuid() in sd-pam: %m"); - - (void) ignore_signals(SIGPIPE, -1); - - /* Wait until our parent died. This will only work if - * the above setresuid() succeeds, otherwise the kernel - * will not allow unprivileged parents kill their privileged - * children this way. We rely on the control groups kill logic - * to do the rest for us. */ - if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) - goto child_finish; - - /* Tell the parent that our setup is done. This is especially - * important regarding dropping privileges. Otherwise, unit - * setup might race against our setresuid(2) call. */ - barrier_place(&barrier); - - /* Check if our parent process might already have - * died? */ - if (getppid() == parent_pid) { - sigset_t ss; - - assert_se(sigemptyset(&ss) >= 0); - assert_se(sigaddset(&ss, SIGTERM) >= 0); - - for (;;) { - if (sigwait(&ss, &sig) < 0) { - if (errno == EINTR) - continue; - - goto child_finish; - } - - assert(sig == SIGTERM); - break; - } - } - - /* If our parent died we'll end the session */ - if (getppid() != parent_pid) { - pam_code = pam_close_session(handle, flags); - if (pam_code != PAM_SUCCESS) - goto child_finish; - } - - ret = 0; - - child_finish: - pam_end(handle, pam_code | flags); - _exit(ret); - } - - barrier_set_role(&barrier, BARRIER_PARENT); - - /* If the child was forked off successfully it will do all the - * cleanups, so forget about the handle here. */ - handle = NULL; - - /* Unblock SIGTERM again in the parent */ - assert_se(sigprocmask(SIG_SETMASK, &old_ss, NULL) >= 0); - - /* We close the log explicitly here, since the PAM modules - * might have opened it, but we don't want this fd around. */ - closelog(); - - /* Synchronously wait for the child to initialize. We don't care for - * errors as we cannot recover. However, warn loudly if it happens. */ - if (!barrier_place_and_sync(&barrier)) - log_error("PAM initialization failed"); - - *pam_env = e; - e = NULL; - - return 0; - -fail: - if (pam_code != PAM_SUCCESS) { - log_error("PAM failed: %s", pam_strerror(handle, pam_code)); - r = -EPERM; /* PAM errors do not map to errno */ - } else - log_error_errno(r, "PAM failed: %m"); - - if (handle) { - if (close_session) - pam_code = pam_close_session(handle, flags); - - pam_end(handle, pam_code | flags); - } - - strv_free(e); - closelog(); - - return r; -} -#endif - -static void rename_process_from_path(const char *path) { - char process_name[11]; - const char *p; - size_t l; - - /* This resulting string must fit in 10 chars (i.e. the length - * of "/sbin/init") to look pretty in /bin/ps */ - - p = basename(path); - if (isempty(p)) { - rename_process("(...)"); - return; - } - - l = strlen(p); - if (l > 8) { - /* The end of the process name is usually more - * interesting, since the first bit might just be - * "systemd-" */ - p = p + l - 8; - l = 8; - } - - process_name[0] = '('; - memcpy(process_name+1, p, l); - process_name[1+l] = ')'; - process_name[1+l+1] = 0; - - rename_process(process_name); -} - -#ifdef HAVE_SECCOMP - -static int apply_seccomp(const ExecContext *c) { - uint32_t negative_action, action; - scmp_filter_ctx *seccomp; - Iterator i; - void *id; - int r; - - assert(c); - - negative_action = c->syscall_errno == 0 ? SCMP_ACT_KILL : SCMP_ACT_ERRNO(c->syscall_errno); - - seccomp = seccomp_init(c->syscall_whitelist ? negative_action : SCMP_ACT_ALLOW); - if (!seccomp) - return -ENOMEM; - - if (c->syscall_archs) { - - SET_FOREACH(id, c->syscall_archs, i) { - r = seccomp_arch_add(seccomp, PTR_TO_UINT32(id) - 1); - if (r == -EEXIST) - continue; - if (r < 0) - goto finish; - } - - } else { - r = seccomp_add_secondary_archs(seccomp); - if (r < 0) - goto finish; - } - - action = c->syscall_whitelist ? SCMP_ACT_ALLOW : negative_action; - SET_FOREACH(id, c->syscall_filter, i) { - r = seccomp_rule_add(seccomp, action, PTR_TO_INT(id) - 1, 0); - 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; -} - -static int apply_address_families(const ExecContext *c) { - scmp_filter_ctx *seccomp; - Iterator i; - int r; - - assert(c); - - seccomp = seccomp_init(SCMP_ACT_ALLOW); - if (!seccomp) - return -ENOMEM; - - r = seccomp_add_secondary_archs(seccomp); - if (r < 0) - goto finish; - - if (c->address_families_whitelist) { - int af, first = 0, last = 0; - void *afp; - - /* If this is a whitelist, we first block the address - * families that are out of range and then everything - * that is not in the set. First, we find the lowest - * and highest address family in the set. */ - - SET_FOREACH(afp, c->address_families, i) { - af = PTR_TO_INT(afp); - - if (af <= 0 || af >= af_max()) - continue; - - if (first == 0 || af < first) - first = af; - - if (last == 0 || af > last) - last = af; - } - - assert((first == 0) == (last == 0)); - - if (first == 0) { - - /* No entries in the valid range, block everything */ - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 0); - if (r < 0) - goto finish; - - } else { - - /* Block everything below the first entry */ - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 1, - SCMP_A0(SCMP_CMP_LT, first)); - if (r < 0) - goto finish; - - /* Block everything above the last entry */ - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 1, - SCMP_A0(SCMP_CMP_GT, last)); - if (r < 0) - goto finish; - - /* Block everything between the first and last - * entry */ - for (af = 1; af < af_max(); af++) { - - if (set_contains(c->address_families, INT_TO_PTR(af))) - continue; - - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 1, - SCMP_A0(SCMP_CMP_EQ, af)); - if (r < 0) - goto finish; - } - } - - } else { - void *af; - - /* If this is a blacklist, then generate one rule for - * each address family that are then combined in OR - * checks. */ - - SET_FOREACH(af, c->address_families, i) { - - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EPROTONOSUPPORT), - SCMP_SYS(socket), - 1, - SCMP_A0(SCMP_CMP_EQ, PTR_TO_INT(af))); - 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]) { - assert(idle_pipe); - - - idle_pipe[1] = safe_close(idle_pipe[1]); - idle_pipe[2] = safe_close(idle_pipe[2]); - - if (idle_pipe[0] >= 0) { - int r; - - r = fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT_USEC); - - if (idle_pipe[3] >= 0 && r == 0 /* timeout */) { - ssize_t n; - - /* Signal systemd that we are bored and want to continue. */ - n = write(idle_pipe[3], "x", 1); - if (n > 0) - /* Wait for systemd to react to the signal above. */ - fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT2_USEC); - } - - idle_pipe[0] = safe_close(idle_pipe[0]); - - } - - idle_pipe[3] = safe_close(idle_pipe[3]); -} - -static int build_environment( - const ExecContext *c, - const ExecParameters *p, - unsigned n_fds, - const char *home, - const char *username, - const char *shell, - char ***ret) { - - _cleanup_strv_free_ char **our_env = NULL; - unsigned n_env = 0; - char *x; - - assert(c); - assert(ret); - - our_env = new0(char*, 11); - if (!our_env) - return -ENOMEM; - - if (n_fds > 0) { - _cleanup_free_ char *joined = NULL; - - if (asprintf(&x, "LISTEN_PID="PID_FMT, getpid()) < 0) - return -ENOMEM; - our_env[n_env++] = x; - - if (asprintf(&x, "LISTEN_FDS=%u", n_fds) < 0) - return -ENOMEM; - our_env[n_env++] = x; - - joined = strv_join(p->fd_names, ":"); - if (!joined) - return -ENOMEM; - - x = strjoin("LISTEN_FDNAMES=", joined, NULL); - if (!x) - return -ENOMEM; - our_env[n_env++] = x; - } - - if (p->watchdog_usec > 0) { - if (asprintf(&x, "WATCHDOG_PID="PID_FMT, getpid()) < 0) - return -ENOMEM; - our_env[n_env++] = x; - - if (asprintf(&x, "WATCHDOG_USEC="USEC_FMT, p->watchdog_usec) < 0) - return -ENOMEM; - our_env[n_env++] = x; - } - - if (home) { - x = strappend("HOME=", home); - if (!x) - return -ENOMEM; - our_env[n_env++] = x; - } - - if (username) { - x = strappend("LOGNAME=", username); - if (!x) - return -ENOMEM; - our_env[n_env++] = x; - - x = strappend("USER=", username); - if (!x) - return -ENOMEM; - our_env[n_env++] = x; - } - - if (shell) { - x = strappend("SHELL=", shell); - if (!x) - return -ENOMEM; - 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) { - - x = strdup(default_term_for_tty(exec_context_tty_path(c))); - if (!x) - return -ENOMEM; - our_env[n_env++] = x; - } - - our_env[n_env++] = NULL; - assert(n_env <= 11); - - *ret = our_env; - our_env = NULL; - - return 0; -} - -static int build_pass_environment(const ExecContext *c, char ***ret) { - _cleanup_strv_free_ char **pass_env = NULL; - size_t n_env = 0, n_bufsize = 0; - char **i; - - STRV_FOREACH(i, c->pass_environment) { - _cleanup_free_ char *x = NULL; - char *v; - - v = getenv(*i); - if (!v) - continue; - x = strjoin(*i, "=", v, NULL); - if (!x) - return -ENOMEM; - if (!GREEDY_REALLOC(pass_env, n_bufsize, n_env + 2)) - return -ENOMEM; - pass_env[n_env++] = x; - pass_env[n_env] = NULL; - x = NULL; - } - - *ret = pass_env; - pass_env = NULL; - - return 0; -} - -static bool exec_needs_mount_namespace( - const ExecContext *context, - const ExecParameters *params, - ExecRuntime *runtime) { - - assert(context); - assert(params); - - if (!strv_isempty(context->read_write_dirs) || - !strv_isempty(context->read_only_dirs) || - !strv_isempty(context->inaccessible_dirs)) - return true; - - if (context->mount_flags != 0) - return true; - - if (context->private_tmp && runtime && (runtime->tmp_dir || runtime->var_tmp_dir)) - return true; - - if (params->bus_endpoint_path) - return true; - - if (context->private_devices || - context->protect_system != PROTECT_SYSTEM_NO || - context->protect_home != PROTECT_HOME_NO) - return true; - - return false; -} - -static int close_remaining_fds( - const ExecParameters *params, - ExecRuntime *runtime, - int socket_fd, - int *fds, unsigned n_fds) { - - unsigned n_dont_close = 0; - int dont_close[n_fds + 7]; - - assert(params); - - if (params->stdin_fd >= 0) - dont_close[n_dont_close++] = params->stdin_fd; - if (params->stdout_fd >= 0) - dont_close[n_dont_close++] = params->stdout_fd; - if (params->stderr_fd >= 0) - dont_close[n_dont_close++] = params->stderr_fd; - - if (socket_fd >= 0) - dont_close[n_dont_close++] = socket_fd; - if (n_fds > 0) { - memcpy(dont_close + n_dont_close, fds, sizeof(int) * n_fds); - n_dont_close += n_fds; - } - - if (params->bus_endpoint_fd >= 0) - dont_close[n_dont_close++] = params->bus_endpoint_fd; - - 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]; - } - - return close_all_fds(dont_close, n_dont_close); -} - -static int exec_child( - Unit *unit, - ExecCommand *command, - const ExecContext *context, - const ExecParameters *params, - ExecRuntime *runtime, - 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_free_ char *mac_selinux_context_net = NULL; - const char *username = NULL, *home = NULL, *shell = NULL, *wd; - uid_t uid = UID_INVALID; - gid_t gid = GID_INVALID; - int i, r; - bool needs_mount_namespace; - - assert(unit); - assert(command); - assert(context); - assert(params); - assert(exit_status); - - rename_process_from_path(command->path); - - /* We reset exactly these signals, since they are the - * only ones we set to SIG_IGN in the main daemon. All - * others we leave untouched because we set them to - * SIG_DFL or a valid handler initially, both of which - * will be demoted to SIG_DFL. */ - (void) default_signals(SIGNALS_CRASH_HANDLER, - SIGNALS_IGNORE, -1); - - if (context->ignore_sigpipe) - (void) ignore_signals(SIGPIPE, -1); - - r = reset_signal_mask(); - if (r < 0) { - *exit_status = EXIT_SIGNAL_MASK; - return r; - } - - if (params->idle_pipe) - do_idle_pipe_dance(params->idle_pipe); - - /* Close sockets very early to make sure we don't - * block init reexecution because it cannot bind its - * sockets */ - - log_forget_fds(); - - r = close_remaining_fds(params, runtime, socket_fd, fds, n_fds); - if (r < 0) { - *exit_status = EXIT_FDS; - return r; - } - - if (!context->same_pgrp) - if (setsid() < 0) { - *exit_status = EXIT_SETSID; - return -errno; - } - - exec_context_tty_reset(context, params); - - if (params->confirm_spawn) { - char response; - - r = ask_for_confirmation(&response, argv); - if (r == -ETIMEDOUT) - write_confirm_message("Confirmation question timed out, assuming positive response.\n"); - else if (r < 0) - write_confirm_message("Couldn't ask confirmation question, assuming positive response: %s\n", strerror(-r)); - else if (response == 's') { - write_confirm_message("Skipping execution.\n"); - *exit_status = EXIT_CONFIRM; - return -ECANCELED; - } else if (response == 'n') { - write_confirm_message("Failing execution.\n"); - *exit_status = 0; - return 0; - } - } - - if (context->user) { - username = context->user; - r = get_user_creds(&username, &uid, &gid, &home, &shell); - if (r < 0) { - *exit_status = EXIT_USER; - return r; - } - } - - 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 */ - if (socket_fd >= 0) - (void) fd_nonblock(socket_fd, false); - - r = setup_input(context, params, socket_fd); - if (r < 0) { - *exit_status = EXIT_STDIN; - return r; - } - - r = setup_output(unit, context, params, STDOUT_FILENO, socket_fd, basename(command->path), uid, gid); - if (r < 0) { - *exit_status = EXIT_STDOUT; - return r; - } - - r = setup_output(unit, context, params, STDERR_FILENO, socket_fd, basename(command->path), uid, gid); - if (r < 0) { - *exit_status = EXIT_STDERR; - return r; - } - - if (params->cgroup_path) { - r = cg_attach_everywhere(params->cgroup_supported, params->cgroup_path, 0, NULL, NULL); - if (r < 0) { - *exit_status = EXIT_CGROUP; - return r; - } - } - - if (context->oom_score_adjust_set) { - char t[DECIMAL_STR_MAX(context->oom_score_adjust)]; - - /* When we can't make this change due to EPERM, then - * let's silently skip over it. User namespaces - * prohibit write access to this file, and we - * shouldn't trip up over that. */ - - sprintf(t, "%i", context->oom_score_adjust); - r = write_string_file("/proc/self/oom_score_adj", t, 0); - if (r == -EPERM || r == -EACCES) { - log_open(); - log_unit_debug_errno(unit, r, "Failed to adjust OOM setting, assuming containerized execution, ignoring: %m"); - log_close(); - } else if (r < 0) { - *exit_status = EXIT_OOM_ADJUST; - return -errno; - } - } - - if (context->nice_set) - if (setpriority(PRIO_PROCESS, 0, context->nice) < 0) { - *exit_status = EXIT_NICE; - return -errno; - } - - if (context->cpu_sched_set) { - struct sched_param param = { - .sched_priority = context->cpu_sched_priority, - }; - - r = sched_setscheduler(0, - context->cpu_sched_policy | - (context->cpu_sched_reset_on_fork ? - SCHED_RESET_ON_FORK : 0), - ¶m); - if (r < 0) { - *exit_status = EXIT_SETSCHEDULER; - return -errno; - } - } - - if (context->cpuset) - if (sched_setaffinity(0, CPU_ALLOC_SIZE(context->cpuset_ncpus), context->cpuset) < 0) { - *exit_status = EXIT_CPUAFFINITY; - return -errno; - } - - if (context->ioprio_set) - if (ioprio_set(IOPRIO_WHO_PROCESS, 0, context->ioprio) < 0) { - *exit_status = EXIT_IOPRIO; - return -errno; - } - - if (context->timer_slack_nsec != NSEC_INFINITY) - if (prctl(PR_SET_TIMERSLACK, context->timer_slack_nsec) < 0) { - *exit_status = EXIT_TIMERSLACK; - return -errno; - } - - if (context->personality != PERSONALITY_INVALID) - if (personality(context->personality) < 0) { - *exit_status = EXIT_PERSONALITY; - return -errno; - } - - if (context->utmp_id) - utmp_put_init_process(context->utmp_id, getpid(), getsid(0), context->tty_path, - context->utmp_mode == EXEC_UTMP_INIT ? INIT_PROCESS : - context->utmp_mode == EXEC_UTMP_LOGIN ? LOGIN_PROCESS : - USER_PROCESS, - username ? "root" : context->user); - - if (context->user && is_terminal_input(context->std_input)) { - r = chown_terminal(STDIN_FILENO, uid); - if (r < 0) { - *exit_status = EXIT_STDIN; - return r; - } - } - - if (params->bus_endpoint_fd >= 0 && context->bus_endpoint) { - uid_t ep_uid = (uid == UID_INVALID) ? 0 : uid; - - r = bus_kernel_set_endpoint_policy(params->bus_endpoint_fd, ep_uid, context->bus_endpoint); - if (r < 0) { - *exit_status = EXIT_BUS_ENDPOINT; - return r; - } - } - - /* If delegation is enabled we'll pass ownership of the cgroup - * (but only in systemd's own controller hierarchy!) to the - * user of the new process. */ - if (params->cgroup_path && context->user && params->cgroup_delegate) { - r = cg_set_task_access(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, 0644, uid, gid); - if (r < 0) { - *exit_status = EXIT_CGROUP; - return r; - } - - - r = cg_set_group_access(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, 0755, uid, gid); - if (r < 0) { - *exit_status = EXIT_CGROUP; - return r; - } - } - - if (!strv_isempty(context->runtime_directory) && params->runtime_prefix) { - char **rt; - - STRV_FOREACH(rt, context->runtime_directory) { - _cleanup_free_ char *p; - - p = strjoin(params->runtime_prefix, "/", *rt, NULL); - if (!p) { - *exit_status = EXIT_RUNTIME_DIRECTORY; - return -ENOMEM; - } - - r = mkdir_p_label(p, context->runtime_directory_mode); - if (r < 0) { - *exit_status = EXIT_RUNTIME_DIRECTORY; - return r; - } - - r = chmod_and_chown(p, context->runtime_directory_mode, uid, gid); - if (r < 0) { - *exit_status = EXIT_RUNTIME_DIRECTORY; - return r; - } - } - } - - umask(context->umask); - - if (params->apply_permissions) { - r = enforce_groups(context, username, gid); - if (r < 0) { - *exit_status = EXIT_GROUP; - return r; - } -#ifdef HAVE_SMACK - if (context->smack_process_label) { - r = mac_smack_apply_pid(0, context->smack_process_label); - if (r < 0) { - *exit_status = EXIT_SMACK_PROCESS_LABEL; - return r; - } - } -#ifdef SMACK_DEFAULT_PROCESS_LABEL - else { - _cleanup_free_ char *exec_label = NULL; - - r = mac_smack_read(command->path, SMACK_ATTR_EXEC, &exec_label); - if (r < 0 && r != -ENODATA && r != -EOPNOTSUPP) { - *exit_status = EXIT_SMACK_PROCESS_LABEL; - return r; - } - - r = mac_smack_apply_pid(0, exec_label ? : SMACK_DEFAULT_PROCESS_LABEL); - if (r < 0) { - *exit_status = EXIT_SMACK_PROCESS_LABEL; - return r; - } - } -#endif -#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); - if (r < 0) { - *exit_status = EXIT_PAM; - return r; - } - } -#endif - } - - if (context->private_network && runtime && runtime->netns_storage_socket[0] >= 0) { - r = setup_netns(runtime->netns_storage_socket); - if (r < 0) { - *exit_status = EXIT_NETWORK; - return r; - } - } - - needs_mount_namespace = exec_needs_mount_namespace(context, params, runtime); - - if (needs_mount_namespace) { - char *tmp = NULL, *var = NULL; - - /* The runtime struct only contains the parent - * of the private /tmp, which is - * non-accessible to world users. Inside of it - * there's a /tmp that is sticky, and that's - * the one we want to use here. */ - - if (context->private_tmp && runtime) { - if (runtime->tmp_dir) - tmp = strjoina(runtime->tmp_dir, "/tmp"); - if (runtime->var_tmp_dir) - var = strjoina(runtime->var_tmp_dir, "/tmp"); - } - - r = setup_namespace( - params->apply_chroot ? context->root_directory : NULL, - context->read_write_dirs, - context->read_only_dirs, - context->inaccessible_dirs, - tmp, - var, - params->bus_endpoint_path, - context->private_devices, - context->protect_home, - context->protect_system, - context->mount_flags); - - /* If we couldn't set up the namespace this is - * probably due to a missing capability. In this case, - * silently proceeed. */ - if (r == -EPERM || r == -EACCES) { - log_open(); - log_unit_debug_errno(unit, r, "Failed to set up namespace, assuming containerized execution, ignoring: %m"); - log_close(); - } else if (r < 0) { - *exit_status = EXIT_NAMESPACE; - return r; - } - } - - if (context->working_directory_home) - wd = home; - else if (context->working_directory) - wd = context->working_directory; - else - wd = "/"; - - if (params->apply_chroot) { - if (!needs_mount_namespace && context->root_directory) - if (chroot(context->root_directory) < 0) { - *exit_status = EXIT_CHROOT; - return -errno; - } - - if (chdir(wd) < 0 && - !context->working_directory_missing_ok) { - *exit_status = EXIT_CHDIR; - return -errno; - } - } else { - const char *d; - - d = strjoina(strempty(context->root_directory), "/", strempty(wd)); - if (chdir(d) < 0 && - !context->working_directory_missing_ok) { - *exit_status = EXIT_CHDIR; - return -errno; - } - } - -#ifdef HAVE_SELINUX - if (params->apply_permissions && mac_selinux_use() && params->selinux_context_net && socket_fd >= 0) { - 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; - return r; - } - } -#endif - - /* We repeat the fd closing here, to make sure that - * nothing is leaked from the PAM modules. Note that - * we are more aggressive this time since socket_fd - * and the netns fds we don't need anymore. The custom - * endpoint fd was needed to upload the policy and can - * now be closed as well. */ - r = close_all_fds(fds, n_fds); - if (r >= 0) - r = shift_fds(fds, n_fds); - if (r >= 0) - r = flags_fds(fds, n_fds, context->non_blocking); - if (r < 0) { - *exit_status = EXIT_FDS; - return r; - } - - if (params->apply_permissions) { - - 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) { - *exit_status = EXIT_LIMITS; - return -errno; - } - } - - if (!cap_test_all(context->capability_bounding_set)) { - r = capability_bounding_set_drop(context->capability_bounding_set, false); - if (r < 0) { - *exit_status = EXIT_CAPABILITIES; - return r; - } - } - - /* This is done before enforce_user, but ambient set - * does not survive over setresuid() if keep_caps is not set. */ - if (context->capability_ambient_set != 0) { - r = capability_ambient_set_apply(context->capability_ambient_set, true); - if (r < 0) { - *exit_status = EXIT_CAPABILITIES; - return r; - } - - if (context->capabilities) { - - /* The capabilities in ambient set need to be also in the inherited - * set. If they aren't, trying to get them will fail. Add the ambient - * set inherited capabilities to the capability set in the context. - * This is needed because if capabilities are set (using "Capabilities=" - * keyword), they will override whatever we set now. */ - - r = capability_update_inherited_set(context->capabilities, context->capability_ambient_set); - if (r < 0) { - *exit_status = EXIT_CAPABILITIES; - return r; - } - } - } - - if (context->user) { - r = enforce_user(context, uid); - if (r < 0) { - *exit_status = EXIT_USER; - return r; - } - if (context->capability_ambient_set != 0) { - - /* Fix the ambient capabilities after user change. */ - r = capability_ambient_set_apply(context->capability_ambient_set, false); - if (r < 0) { - *exit_status = EXIT_CAPABILITIES; - return r; - } - - /* If we were asked to change user and ambient capabilities - * were requested, we had to add keep-caps to the securebits - * so that we would maintain the inherited capability set - * through the setresuid(). Make sure that the bit is added - * also to the context secure_bits so that we don't try to - * drop the bit away next. */ - - secure_bits |= 1<capabilities) - if (cap_set_proc(context->capabilities) < 0) { - *exit_status = EXIT_CAPABILITIES; - return -errno; - } - - if (context->no_new_privileges) - if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { - *exit_status = EXIT_NO_NEW_PRIVILEGES; - return -errno; - } - -#ifdef HAVE_SECCOMP - if (context->address_families_whitelist || - !set_isempty(context->address_families)) { - r = apply_address_families(context); - if (r < 0) { - *exit_status = EXIT_ADDRESS_FAMILIES; - return r; - } - } - - if (context->syscall_whitelist || - !set_isempty(context->syscall_filter) || - !set_isempty(context->syscall_archs)) { - r = apply_seccomp(context); - if (r < 0) { - *exit_status = EXIT_SECCOMP; - return r; - } - } -#endif - -#ifdef HAVE_SELINUX - if (mac_selinux_use()) { - char *exec_context = mac_selinux_context_net ?: context->selinux_context; - - if (exec_context) { - r = setexeccon(exec_context); - if (r < 0) { - *exit_status = EXIT_SELINUX_CONTEXT; - return r; - } - } - } -#endif - -#ifdef HAVE_APPARMOR - if (context->apparmor_profile && mac_apparmor_use()) { - r = aa_change_onexec(context->apparmor_profile); - if (r < 0 && !context->apparmor_profile_ignore) { - *exit_status = EXIT_APPARMOR_PROFILE; - return -errno; - } - } -#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); - 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; - - line = exec_command_line(final_argv); - if (line) { - log_open(); - log_struct(LOG_DEBUG, - LOG_UNIT_ID(unit), - "EXECUTABLE=%s", command->path, - LOG_UNIT_MESSAGE(unit, "Executing: %s", line), - NULL); - log_close(); - } - } - - execve(command->path, final_argv, final_env); - *exit_status = EXIT_EXEC; - return -errno; -} - -int exec_spawn(Unit *unit, - ExecCommand *command, - const ExecContext *context, - const ExecParameters *params, - ExecRuntime *runtime, - pid_t *ret) { - - _cleanup_strv_free_ char **files_env = NULL; - int *fds = NULL; unsigned n_fds = 0; - _cleanup_free_ char *line = NULL; - int socket_fd, r; - char **argv; - pid_t pid; - - assert(unit); - assert(command); - assert(context); - assert(ret); - assert(params); - assert(params->fds || params->n_fds <= 0); - - if (context->std_input == EXEC_INPUT_SOCKET || - context->std_output == EXEC_OUTPUT_SOCKET || - context->std_error == EXEC_OUTPUT_SOCKET) { - - if (params->n_fds != 1) { - log_unit_error(unit, "Got more than one socket."); - return -EINVAL; - } - - socket_fd = params->fds[0]; - } else { - socket_fd = -1; - fds = params->fds; - n_fds = params->n_fds; - } - - r = exec_context_load_environment(unit, context, &files_env); - if (r < 0) - return log_unit_error_errno(unit, r, "Failed to load environment files: %m"); - - argv = params->argv ?: command->argv; - line = exec_command_line(argv); - if (!line) - return log_oom(); - - log_struct(LOG_DEBUG, - LOG_UNIT_ID(unit), - LOG_UNIT_MESSAGE(unit, "About to execute: %s", line), - "EXECUTABLE=%s", command->path, - NULL); - pid = fork(); - if (pid < 0) - return log_unit_error_errno(unit, errno, "Failed to fork: %m"); - - if (pid == 0) { - int exit_status; - - r = exec_child(unit, - command, - context, - params, - runtime, - argv, - socket_fd, - fds, n_fds, - files_env, - &exit_status); - if (r < 0) { - log_open(); - log_struct_errno(LOG_ERR, r, - LOG_MESSAGE_ID(SD_MESSAGE_SPAWN_FAILED), - LOG_UNIT_ID(unit), - LOG_UNIT_MESSAGE(unit, "Failed at step %s spawning %s: %m", - exit_status_to_string(exit_status, EXIT_STATUS_SYSTEMD), - command->path), - "EXECUTABLE=%s", command->path, - NULL); - } - - _exit(exit_status); - } - - log_unit_debug(unit, "Forked %s as "PID_FMT, command->path, pid); - - /* We add the new process to the cgroup both in the child (so - * that we can be sure that no user code is ever executed - * outside of the cgroup) and in the parent (so that we can be - * sure that when we kill the cgroup the process will be - * killed too). */ - if (params->cgroup_path) - (void) cg_attach(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, pid); - - exec_status_start(&command->exec_status, pid); - - *ret = pid; - return 0; -} - -void exec_context_init(ExecContext *c) { - assert(c); - - c->umask = 0022; - c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 0); - c->cpu_sched_policy = SCHED_OTHER; - c->syslog_priority = LOG_DAEMON|LOG_INFO; - c->syslog_level_prefix = true; - c->ignore_sigpipe = true; - c->timer_slack_nsec = NSEC_INFINITY; - c->personality = PERSONALITY_INVALID; - c->runtime_directory_mode = 0755; - c->capability_bounding_set = CAP_ALL; -} - -void exec_context_done(ExecContext *c) { - unsigned l; - - assert(c); - - c->environment = strv_free(c->environment); - c->environment_files = strv_free(c->environment_files); - c->pass_environment = strv_free(c->pass_environment); - - for (l = 0; l < ELEMENTSOF(c->rlimit); l++) - c->rlimit[l] = mfree(c->rlimit[l]); - - c->working_directory = mfree(c->working_directory); - c->root_directory = mfree(c->root_directory); - c->tty_path = mfree(c->tty_path); - c->syslog_identifier = mfree(c->syslog_identifier); - c->user = mfree(c->user); - c->group = mfree(c->group); - - c->supplementary_groups = strv_free(c->supplementary_groups); - - c->pam_name = mfree(c->pam_name); - - if (c->capabilities) { - cap_free(c->capabilities); - c->capabilities = NULL; - } - - 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); - - if (c->cpuset) - CPU_FREE(c->cpuset); - - c->utmp_id = mfree(c->utmp_id); - c->selinux_context = mfree(c->selinux_context); - c->apparmor_profile = mfree(c->apparmor_profile); - - c->syscall_filter = set_free(c->syscall_filter); - c->syscall_archs = set_free(c->syscall_archs); - c->address_families = set_free(c->address_families); - - c->runtime_directory = strv_free(c->runtime_directory); - - bus_endpoint_free(c->bus_endpoint); - c->bus_endpoint = NULL; -} - -int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_prefix) { - char **i; - - assert(c); - - if (!runtime_prefix) - return 0; - - STRV_FOREACH(i, c->runtime_directory) { - _cleanup_free_ char *p; - - p = strjoin(runtime_prefix, "/", *i, NULL); - if (!p) - return -ENOMEM; - - /* We execute this synchronously, since we need to be - * sure this is gone when we start the service - * next. */ - (void) rm_rf(p, REMOVE_ROOT); - } - - return 0; -} - -void exec_command_done(ExecCommand *c) { - assert(c); - - c->path = mfree(c->path); - - c->argv = strv_free(c->argv); -} - -void exec_command_done_array(ExecCommand *c, unsigned n) { - unsigned i; - - for (i = 0; i < n; i++) - exec_command_done(c+i); -} - -ExecCommand* exec_command_free_list(ExecCommand *c) { - ExecCommand *i; - - while ((i = c)) { - LIST_REMOVE(command, c, i); - exec_command_done(i); - free(i); - } - - return NULL; -} - -void exec_command_free_array(ExecCommand **c, unsigned n) { - unsigned i; - - for (i = 0; i < n; i++) - c[i] = exec_command_free_list(c[i]); -} - -typedef struct InvalidEnvInfo { - Unit *unit; - const char *path; -} InvalidEnvInfo; - -static void invalid_env(const char *p, void *userdata) { - InvalidEnvInfo *info = userdata; - - log_unit_error(info->unit, "Ignoring invalid environment assignment '%s': %s", p, info->path); -} - -int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l) { - char **i, **r = NULL; - - assert(c); - assert(l); - - STRV_FOREACH(i, c->environment_files) { - char *fn; - int k; - bool ignore = false; - char **p; - _cleanup_globfree_ glob_t pglob = {}; - int count, n; - - fn = *i; - - if (fn[0] == '-') { - ignore = true; - fn ++; - } - - if (!path_is_absolute(fn)) { - if (ignore) - continue; - - strv_free(r); - return -EINVAL; - } - - /* Filename supports globbing, take all matching files */ - errno = 0; - if (glob(fn, 0, NULL, &pglob) != 0) { - if (ignore) - continue; - - strv_free(r); - return errno > 0 ? -errno : -EINVAL; - } - count = pglob.gl_pathc; - if (count == 0) { - if (ignore) - continue; - - strv_free(r); - return -EINVAL; - } - for (n = 0; n < count; n++) { - k = load_env_file(NULL, pglob.gl_pathv[n], NULL, &p); - if (k < 0) { - if (ignore) - continue; - - strv_free(r); - return k; - } - /* Log invalid environment variables with filename */ - if (p) { - InvalidEnvInfo info = { - .unit = unit, - .path = pglob.gl_pathv[n] - }; - - p = strv_env_clean_with_callback(p, invalid_env, &info); - } - - if (r == NULL) - r = p; - else { - char **m; - - m = strv_env_merge(2, r, p); - strv_free(r); - strv_free(p); - if (!m) - return -ENOMEM; - - r = m; - } - } - } - - *l = r; - - return 0; -} - -static bool tty_may_match_dev_console(const char *tty) { - _cleanup_free_ char *active = NULL; - char *console; - - if (!tty) - return true; - - if (startswith(tty, "/dev/")) - tty += 5; - - /* trivial identity? */ - if (streq(tty, "console")) - return true; - - console = resolve_dev_console(&active); - /* if we could not resolve, assume it may */ - if (!console) - return true; - - /* "tty0" means the active VC, so it may be the same sometimes */ - return streq(console, tty) || (streq(console, "tty0") && tty_is_vc(tty)); -} - -bool exec_context_may_touch_console(ExecContext *ec) { - - return (ec->tty_reset || - ec->tty_vhangup || - ec->tty_vt_disallocate || - is_terminal_input(ec->std_input) || - is_terminal_output(ec->std_output) || - is_terminal_output(ec->std_error)) && - tty_may_match_dev_console(exec_context_tty_path(ec)); -} - -static void strv_fprintf(FILE *f, char **l) { - char **g; - - assert(f); - - STRV_FOREACH(g, l) - fprintf(f, " %s", *g); -} - -void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { - char **e, **d; - unsigned i; - - assert(c); - assert(f); - - prefix = strempty(prefix); - - fprintf(f, - "%sUMask: %04o\n" - "%sWorkingDirectory: %s\n" - "%sRootDirectory: %s\n" - "%sNonBlocking: %s\n" - "%sPrivateTmp: %s\n" - "%sPrivateNetwork: %s\n" - "%sPrivateDevices: %s\n" - "%sProtectHome: %s\n" - "%sProtectSystem: %s\n" - "%sIgnoreSIGPIPE: %s\n", - prefix, c->umask, - prefix, c->working_directory ? c->working_directory : "/", - prefix, c->root_directory ? c->root_directory : "/", - prefix, yes_no(c->non_blocking), - prefix, yes_no(c->private_tmp), - prefix, yes_no(c->private_network), - prefix, yes_no(c->private_devices), - prefix, protect_home_to_string(c->protect_home), - prefix, protect_system_to_string(c->protect_system), - prefix, yes_no(c->ignore_sigpipe)); - - STRV_FOREACH(e, c->environment) - fprintf(f, "%sEnvironment: %s\n", prefix, *e); - - STRV_FOREACH(e, c->environment_files) - fprintf(f, "%sEnvironmentFile: %s\n", prefix, *e); - - STRV_FOREACH(e, c->pass_environment) - fprintf(f, "%sPassEnvironment: %s\n", prefix, *e); - - fprintf(f, "%sRuntimeDirectoryMode: %04o\n", prefix, c->runtime_directory_mode); - - STRV_FOREACH(d, c->runtime_directory) - fprintf(f, "%sRuntimeDirectory: %s\n", prefix, *d); - - if (c->nice_set) - fprintf(f, - "%sNice: %i\n", - prefix, c->nice); - - if (c->oom_score_adjust_set) - fprintf(f, - "%sOOMScoreAdjust: %i\n", - prefix, c->oom_score_adjust); - - for (i = 0; i < RLIM_NLIMITS; i++) - if (c->rlimit[i]) { - fprintf(f, "%s%s: " RLIM_FMT "\n", - prefix, rlimit_to_string(i), c->rlimit[i]->rlim_max); - fprintf(f, "%s%sSoft: " RLIM_FMT "\n", - prefix, rlimit_to_string(i), c->rlimit[i]->rlim_cur); - } - - if (c->ioprio_set) { - _cleanup_free_ char *class_str = NULL; - - ioprio_class_to_string_alloc(IOPRIO_PRIO_CLASS(c->ioprio), &class_str); - fprintf(f, - "%sIOSchedulingClass: %s\n" - "%sIOPriority: %i\n", - prefix, strna(class_str), - prefix, (int) IOPRIO_PRIO_DATA(c->ioprio)); - } - - if (c->cpu_sched_set) { - _cleanup_free_ char *policy_str = NULL; - - sched_policy_to_string_alloc(c->cpu_sched_policy, &policy_str); - fprintf(f, - "%sCPUSchedulingPolicy: %s\n" - "%sCPUSchedulingPriority: %i\n" - "%sCPUSchedulingResetOnFork: %s\n", - prefix, strna(policy_str), - prefix, c->cpu_sched_priority, - prefix, yes_no(c->cpu_sched_reset_on_fork)); - } - - if (c->cpuset) { - fprintf(f, "%sCPUAffinity:", prefix); - for (i = 0; i < c->cpuset_ncpus; i++) - if (CPU_ISSET_S(i, CPU_ALLOC_SIZE(c->cpuset_ncpus), c->cpuset)) - fprintf(f, " %u", i); - fputs("\n", f); - } - - if (c->timer_slack_nsec != NSEC_INFINITY) - fprintf(f, "%sTimerSlackNSec: "NSEC_FMT "\n", prefix, c->timer_slack_nsec); - - fprintf(f, - "%sStandardInput: %s\n" - "%sStandardOutput: %s\n" - "%sStandardError: %s\n", - prefix, exec_input_to_string(c->std_input), - prefix, exec_output_to_string(c->std_output), - prefix, exec_output_to_string(c->std_error)); - - if (c->tty_path) - fprintf(f, - "%sTTYPath: %s\n" - "%sTTYReset: %s\n" - "%sTTYVHangup: %s\n" - "%sTTYVTDisallocate: %s\n", - prefix, c->tty_path, - prefix, yes_no(c->tty_reset), - prefix, yes_no(c->tty_vhangup), - prefix, yes_no(c->tty_vt_disallocate)); - - if (c->std_output == EXEC_OUTPUT_SYSLOG || - c->std_output == EXEC_OUTPUT_KMSG || - c->std_output == EXEC_OUTPUT_JOURNAL || - c->std_output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || - c->std_output == EXEC_OUTPUT_KMSG_AND_CONSOLE || - c->std_output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE || - c->std_error == EXEC_OUTPUT_SYSLOG || - c->std_error == EXEC_OUTPUT_KMSG || - c->std_error == EXEC_OUTPUT_JOURNAL || - c->std_error == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || - c->std_error == EXEC_OUTPUT_KMSG_AND_CONSOLE || - c->std_error == EXEC_OUTPUT_JOURNAL_AND_CONSOLE) { - - _cleanup_free_ char *fac_str = NULL, *lvl_str = NULL; - - log_facility_unshifted_to_string_alloc(c->syslog_priority >> 3, &fac_str); - log_level_to_string_alloc(LOG_PRI(c->syslog_priority), &lvl_str); - - fprintf(f, - "%sSyslogFacility: %s\n" - "%sSyslogLevel: %s\n", - prefix, strna(fac_str), - prefix, strna(lvl_str)); - } - - if (c->capabilities) { - _cleanup_cap_free_charp_ char *t; - - t = cap_to_text(c->capabilities, NULL); - if (t) - fprintf(f, "%sCapabilities: %s\n", prefix, t); - } - - if (c->secure_bits) - fprintf(f, "%sSecure Bits:%s%s%s%s%s%s\n", - prefix, - (c->secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<capability_bounding_set != CAP_ALL) { - unsigned long l; - fprintf(f, "%sCapabilityBoundingSet:", prefix); - - for (l = 0; l <= cap_last_cap(); l++) - if (c->capability_bounding_set & (UINT64_C(1) << l)) - fprintf(f, " %s", strna(capability_to_name(l))); - - fputs("\n", f); - } - - if (c->capability_ambient_set != 0) { - unsigned long l; - fprintf(f, "%sAmbientCapabilities:", prefix); - - for (l = 0; l <= cap_last_cap(); l++) - if (c->capability_ambient_set & (UINT64_C(1) << l)) - fprintf(f, " %s", strna(capability_to_name(l))); - - fputs("\n", f); - } - - if (c->user) - fprintf(f, "%sUser: %s\n", prefix, c->user); - if (c->group) - fprintf(f, "%sGroup: %s\n", prefix, c->group); - - if (strv_length(c->supplementary_groups) > 0) { - fprintf(f, "%sSupplementaryGroups:", prefix); - strv_fprintf(f, c->supplementary_groups); - fputs("\n", f); - } - - 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); - fputs("\n", f); - } - - if (strv_length(c->read_only_dirs) > 0) { - fprintf(f, "%sReadOnlyDirs:", prefix); - strv_fprintf(f, c->read_only_dirs); - fputs("\n", f); - } - - if (strv_length(c->inaccessible_dirs) > 0) { - fprintf(f, "%sInaccessibleDirs:", prefix); - strv_fprintf(f, c->inaccessible_dirs); - fputs("\n", f); - } - - if (c->utmp_id) - fprintf(f, - "%sUtmpIdentifier: %s\n", - prefix, c->utmp_id); - - if (c->selinux_context) - fprintf(f, - "%sSELinuxContext: %s%s\n", - prefix, c->selinux_context_ignore ? "-" : "", c->selinux_context); - - if (c->personality != PERSONALITY_INVALID) - fprintf(f, - "%sPersonality: %s\n", - prefix, strna(personality_to_string(c->personality))); - - if (c->syscall_filter) { -#ifdef HAVE_SECCOMP - Iterator j; - void *id; - bool first = true; -#endif - - fprintf(f, - "%sSystemCallFilter: ", - prefix); - - if (!c->syscall_whitelist) - fputc('~', f); - -#ifdef HAVE_SECCOMP - SET_FOREACH(id, c->syscall_filter, j) { - _cleanup_free_ char *name = NULL; - - if (first) - first = false; - else - fputc(' ', f); - - name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1); - fputs(strna(name), f); - } -#endif - - fputc('\n', f); - } - - if (c->syscall_archs) { -#ifdef HAVE_SECCOMP - Iterator j; - void *id; -#endif - - fprintf(f, - "%sSystemCallArchitectures:", - prefix); - -#ifdef HAVE_SECCOMP - SET_FOREACH(id, c->syscall_archs, j) - fprintf(f, " %s", strna(seccomp_arch_to_string(PTR_TO_UINT32(id) - 1))); -#endif - fputc('\n', f); - } - - if (c->syscall_errno > 0) - fprintf(f, - "%sSystemCallErrorNumber: %s\n", - prefix, strna(errno_to_name(c->syscall_errno))); - - if (c->apparmor_profile) - fprintf(f, - "%sAppArmorProfile: %s%s\n", - prefix, c->apparmor_profile_ignore ? "-" : "", c->apparmor_profile); -} - -bool exec_context_maintains_privileges(ExecContext *c) { - assert(c); - - /* Returns true if the process forked off would run run under - * an unchanged UID or as root. */ - - if (!c->user) - return true; - - if (streq(c->user, "root") || streq(c->user, "0")) - return true; - - return false; -} - -void exec_status_start(ExecStatus *s, pid_t pid) { - assert(s); - - zero(*s); - s->pid = pid; - dual_timestamp_get(&s->start_timestamp); -} - -void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status) { - assert(s); - - if (s->pid && s->pid != pid) - zero(*s); - - s->pid = pid; - dual_timestamp_get(&s->exit_timestamp); - - s->code = code; - s->status = status; - - if (context) { - if (context->utmp_id) - utmp_put_dead_process(context->utmp_id, pid, code, status); - - exec_context_tty_reset(context, NULL); - } -} - -void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) { - char buf[FORMAT_TIMESTAMP_MAX]; - - assert(s); - assert(f); - - if (s->pid <= 0) - return; - - prefix = strempty(prefix); - - fprintf(f, - "%sPID: "PID_FMT"\n", - prefix, s->pid); - - if (s->start_timestamp.realtime > 0) - fprintf(f, - "%sStart Timestamp: %s\n", - prefix, format_timestamp(buf, sizeof(buf), s->start_timestamp.realtime)); - - if (s->exit_timestamp.realtime > 0) - fprintf(f, - "%sExit Timestamp: %s\n" - "%sExit Code: %s\n" - "%sExit Status: %i\n", - prefix, format_timestamp(buf, sizeof(buf), s->exit_timestamp.realtime), - prefix, sigchld_code_to_string(s->code), - prefix, s->status); -} - -char *exec_command_line(char **argv) { - size_t k; - char *n, *p, **a; - bool first = true; - - assert(argv); - - k = 1; - STRV_FOREACH(a, argv) - k += strlen(*a)+3; - - if (!(n = new(char, k))) - return NULL; - - p = n; - STRV_FOREACH(a, argv) { - - if (!first) - *(p++) = ' '; - else - first = false; - - if (strpbrk(*a, WHITESPACE)) { - *(p++) = '\''; - p = stpcpy(p, *a); - *(p++) = '\''; - } else - p = stpcpy(p, *a); - - } - - *p = 0; - - /* FIXME: this doesn't really handle arguments that have - * spaces and ticks in them */ - - return n; -} - -void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix) { - _cleanup_free_ char *cmd = NULL; - const char *prefix2; - - assert(c); - assert(f); - - prefix = strempty(prefix); - prefix2 = strjoina(prefix, "\t"); - - cmd = exec_command_line(c->argv); - fprintf(f, - "%sCommand Line: %s\n", - prefix, cmd ? cmd : strerror(ENOMEM)); - - exec_status_dump(&c->exec_status, f, prefix2); -} - -void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix) { - assert(f); - - prefix = strempty(prefix); - - LIST_FOREACH(command, c, c) - exec_command_dump(c, f, prefix); -} - -void exec_command_append_list(ExecCommand **l, ExecCommand *e) { - ExecCommand *end; - - assert(l); - assert(e); - - if (*l) { - /* It's kind of important, that we keep the order here */ - LIST_FIND_TAIL(command, *l, end); - LIST_INSERT_AFTER(command, *l, end, e); - } else - *l = e; -} - -int exec_command_set(ExecCommand *c, const char *path, ...) { - va_list ap; - char **l, *p; - - assert(c); - assert(path); - - va_start(ap, path); - l = strv_new_ap(path, ap); - va_end(ap); - - if (!l) - return -ENOMEM; - - p = strdup(path); - if (!p) { - strv_free(l); - return -ENOMEM; - } - - free(c->path); - c->path = p; - - strv_free(c->argv); - c->argv = l; - - return 0; -} - -int exec_command_append(ExecCommand *c, const char *path, ...) { - _cleanup_strv_free_ char **l = NULL; - va_list ap; - int r; - - assert(c); - assert(path); - - va_start(ap, path); - l = strv_new_ap(path, ap); - va_end(ap); - - if (!l) - return -ENOMEM; - - r = strv_extend_strv(&c->argv, l, false); - if (r < 0) - return r; - - return 0; -} - - -static int exec_runtime_allocate(ExecRuntime **rt) { - - if (*rt) - return 0; - - *rt = new0(ExecRuntime, 1); - if (!*rt) - return -ENOMEM; - - (*rt)->n_ref = 1; - (*rt)->netns_storage_socket[0] = (*rt)->netns_storage_socket[1] = -1; - - return 0; -} - -int exec_runtime_make(ExecRuntime **rt, ExecContext *c, const char *id) { - int r; - - assert(rt); - assert(c); - assert(id); - - if (*rt) - return 1; - - if (!c->private_network && !c->private_tmp) - return 0; - - r = exec_runtime_allocate(rt); - if (r < 0) - return r; - - if (c->private_network && (*rt)->netns_storage_socket[0] < 0) { - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, (*rt)->netns_storage_socket) < 0) - return -errno; - } - - if (c->private_tmp && !(*rt)->tmp_dir) { - r = setup_tmp_dirs(id, &(*rt)->tmp_dir, &(*rt)->var_tmp_dir); - if (r < 0) - return r; - } - - return 1; -} - -ExecRuntime *exec_runtime_ref(ExecRuntime *r) { - assert(r); - assert(r->n_ref > 0); - - r->n_ref++; - return r; -} - -ExecRuntime *exec_runtime_unref(ExecRuntime *r) { - - if (!r) - return NULL; - - assert(r->n_ref > 0); - - r->n_ref--; - if (r->n_ref > 0) - return NULL; - - free(r->tmp_dir); - free(r->var_tmp_dir); - safe_close_pair(r->netns_storage_socket); - free(r); - - return NULL; -} - -int exec_runtime_serialize(Unit *u, ExecRuntime *rt, FILE *f, FDSet *fds) { - assert(u); - assert(f); - assert(fds); - - if (!rt) - return 0; - - if (rt->tmp_dir) - unit_serialize_item(u, f, "tmp-dir", rt->tmp_dir); - - if (rt->var_tmp_dir) - unit_serialize_item(u, f, "var-tmp-dir", rt->var_tmp_dir); - - if (rt->netns_storage_socket[0] >= 0) { - int copy; - - copy = fdset_put_dup(fds, rt->netns_storage_socket[0]); - if (copy < 0) - return copy; - - unit_serialize_item_format(u, f, "netns-socket-0", "%i", copy); - } - - if (rt->netns_storage_socket[1] >= 0) { - int copy; - - copy = fdset_put_dup(fds, rt->netns_storage_socket[1]); - if (copy < 0) - return copy; - - unit_serialize_item_format(u, f, "netns-socket-1", "%i", copy); - } - - return 0; -} - -int exec_runtime_deserialize_item(Unit *u, ExecRuntime **rt, const char *key, const char *value, FDSet *fds) { - int r; - - assert(rt); - assert(key); - assert(value); - - if (streq(key, "tmp-dir")) { - char *copy; - - r = exec_runtime_allocate(rt); - if (r < 0) - return log_oom(); - - copy = strdup(value); - if (!copy) - return log_oom(); - - free((*rt)->tmp_dir); - (*rt)->tmp_dir = copy; - - } else if (streq(key, "var-tmp-dir")) { - char *copy; - - r = exec_runtime_allocate(rt); - if (r < 0) - return log_oom(); - - copy = strdup(value); - if (!copy) - return log_oom(); - - free((*rt)->var_tmp_dir); - (*rt)->var_tmp_dir = copy; - - } else if (streq(key, "netns-socket-0")) { - int fd; - - r = exec_runtime_allocate(rt); - if (r < 0) - return log_oom(); - - if (safe_atoi(value, &fd) < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse netns socket value: %s", value); - else { - safe_close((*rt)->netns_storage_socket[0]); - (*rt)->netns_storage_socket[0] = fdset_remove(fds, fd); - } - } else if (streq(key, "netns-socket-1")) { - int fd; - - r = exec_runtime_allocate(rt); - if (r < 0) - return log_oom(); - - if (safe_atoi(value, &fd) < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse netns socket value: %s", value); - else { - safe_close((*rt)->netns_storage_socket[1]); - (*rt)->netns_storage_socket[1] = fdset_remove(fds, fd); - } - } else - return 0; - - return 1; -} - -static void *remove_tmpdir_thread(void *p) { - _cleanup_free_ char *path = p; - - (void) rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL); - return NULL; -} - -void exec_runtime_destroy(ExecRuntime *rt) { - int r; - - if (!rt) - return; - - /* If there are multiple users of this, let's leave the stuff around */ - if (rt->n_ref > 1) - return; - - if (rt->tmp_dir) { - log_debug("Spawning thread to nuke %s", rt->tmp_dir); - - r = asynchronous_job(remove_tmpdir_thread, rt->tmp_dir); - if (r < 0) { - log_warning_errno(r, "Failed to nuke %s: %m", rt->tmp_dir); - free(rt->tmp_dir); - } - - rt->tmp_dir = NULL; - } - - if (rt->var_tmp_dir) { - log_debug("Spawning thread to nuke %s", rt->var_tmp_dir); - - r = asynchronous_job(remove_tmpdir_thread, rt->var_tmp_dir); - if (r < 0) { - log_warning_errno(r, "Failed to nuke %s: %m", rt->var_tmp_dir); - free(rt->var_tmp_dir); - } - - rt->var_tmp_dir = NULL; - } - - safe_close_pair(rt->netns_storage_socket); -} - -static const char* const exec_input_table[_EXEC_INPUT_MAX] = { - [EXEC_INPUT_NULL] = "null", - [EXEC_INPUT_TTY] = "tty", - [EXEC_INPUT_TTY_FORCE] = "tty-force", - [EXEC_INPUT_TTY_FAIL] = "tty-fail", - [EXEC_INPUT_SOCKET] = "socket" -}; - -DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput); - -static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = { - [EXEC_OUTPUT_INHERIT] = "inherit", - [EXEC_OUTPUT_NULL] = "null", - [EXEC_OUTPUT_TTY] = "tty", - [EXEC_OUTPUT_SYSLOG] = "syslog", - [EXEC_OUTPUT_SYSLOG_AND_CONSOLE] = "syslog+console", - [EXEC_OUTPUT_KMSG] = "kmsg", - [EXEC_OUTPUT_KMSG_AND_CONSOLE] = "kmsg+console", - [EXEC_OUTPUT_JOURNAL] = "journal", - [EXEC_OUTPUT_JOURNAL_AND_CONSOLE] = "journal+console", - [EXEC_OUTPUT_SOCKET] = "socket" -}; - -DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput); - -static const char* const exec_utmp_mode_table[_EXEC_UTMP_MODE_MAX] = { - [EXEC_UTMP_INIT] = "init", - [EXEC_UTMP_LOGIN] = "login", - [EXEC_UTMP_USER] = "user", -}; - -DEFINE_STRING_TABLE_LOOKUP(exec_utmp_mode, ExecUtmpMode); diff --git a/src/core/execute.h b/src/core/execute.h deleted file mode 100644 index 578f85b6bc..0000000000 --- a/src/core/execute.h +++ /dev/null @@ -1,297 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct ExecStatus ExecStatus; -typedef struct ExecCommand ExecCommand; -typedef struct ExecContext ExecContext; -typedef struct ExecRuntime ExecRuntime; -typedef struct ExecParameters ExecParameters; - -#include -#include -#include -#include - -#include "bus-endpoint.h" -#include "fdset.h" -#include "list.h" -#include "missing.h" -#include "namespace.h" - -typedef enum ExecUtmpMode { - EXEC_UTMP_INIT, - EXEC_UTMP_LOGIN, - EXEC_UTMP_USER, - _EXEC_UTMP_MODE_MAX, - _EXEC_UTMP_MODE_INVALID = -1 -} ExecUtmpMode; - -typedef enum ExecInput { - EXEC_INPUT_NULL, - EXEC_INPUT_TTY, - EXEC_INPUT_TTY_FORCE, - EXEC_INPUT_TTY_FAIL, - EXEC_INPUT_SOCKET, - _EXEC_INPUT_MAX, - _EXEC_INPUT_INVALID = -1 -} ExecInput; - -typedef enum ExecOutput { - EXEC_OUTPUT_INHERIT, - EXEC_OUTPUT_NULL, - EXEC_OUTPUT_TTY, - EXEC_OUTPUT_SYSLOG, - EXEC_OUTPUT_SYSLOG_AND_CONSOLE, - EXEC_OUTPUT_KMSG, - EXEC_OUTPUT_KMSG_AND_CONSOLE, - EXEC_OUTPUT_JOURNAL, - EXEC_OUTPUT_JOURNAL_AND_CONSOLE, - EXEC_OUTPUT_SOCKET, - _EXEC_OUTPUT_MAX, - _EXEC_OUTPUT_INVALID = -1 -} ExecOutput; - -struct ExecStatus { - dual_timestamp start_timestamp; - dual_timestamp exit_timestamp; - pid_t pid; - int code; /* as in siginfo_t::si_code */ - int status; /* as in sigingo_t::si_status */ -}; - -struct ExecCommand { - char *path; - char **argv; - ExecStatus exec_status; - LIST_FIELDS(ExecCommand, command); /* useful for chaining commands */ - bool ignore; -}; - -struct ExecRuntime { - int n_ref; - - char *tmp_dir; - char *var_tmp_dir; - - int netns_storage_socket[2]; -}; - -struct ExecContext { - char **environment; - char **environment_files; - char **pass_environment; - - struct rlimit *rlimit[_RLIMIT_MAX]; - char *working_directory, *root_directory; - bool working_directory_missing_ok; - bool working_directory_home; - - mode_t umask; - int oom_score_adjust; - int nice; - int ioprio; - int cpu_sched_policy; - int cpu_sched_priority; - - cpu_set_t *cpuset; - unsigned cpuset_ncpus; - - ExecInput std_input; - ExecOutput std_output; - ExecOutput std_error; - - nsec_t timer_slack_nsec; - - bool stdio_as_fds; - - char *tty_path; - - bool tty_reset; - bool tty_vhangup; - bool tty_vt_disallocate; - - bool ignore_sigpipe; - - /* Since resolving these names might might involve socket - * connections and we don't want to deadlock ourselves these - * names are resolved on execution only and in the child - * process. */ - char *user; - char *group; - char **supplementary_groups; - - char *pam_name; - - char *utmp_id; - ExecUtmpMode utmp_mode; - - bool selinux_context_ignore; - char *selinux_context; - - bool apparmor_profile_ignore; - char *apparmor_profile; - - bool smack_process_label_ignore; - char *smack_process_label; - - char **read_write_dirs, **read_only_dirs, **inaccessible_dirs; - unsigned long mount_flags; - - uint64_t capability_bounding_set; - - uint64_t capability_ambient_set; - - cap_t capabilities; - int secure_bits; - - int syslog_priority; - char *syslog_identifier; - bool syslog_level_prefix; - - bool cpu_sched_reset_on_fork; - bool non_blocking; - bool private_tmp; - bool private_network; - bool private_devices; - ProtectSystem protect_system; - ProtectHome protect_home; - - bool no_new_privileges; - - /* 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 - * that the autofs logic detects that it belongs to us and we - * don't enter a trigger loop. */ - bool same_pgrp; - - unsigned long personality; - - Set *syscall_filter; - Set *syscall_archs; - int syscall_errno; - bool syscall_whitelist:1; - - Set *address_families; - bool address_families_whitelist:1; - - char **runtime_directory; - mode_t runtime_directory_mode; - - 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; - - /* custom dbus enpoint */ - BusEndpoint *bus_endpoint; -}; - -#include "cgroup-util.h" -#include "cgroup.h" - -struct ExecParameters { - char **argv; - char **environment; - - int *fds; - char **fd_names; - unsigned n_fds; - - bool apply_permissions:1; - bool apply_chroot:1; - bool apply_tty_stdin:1; - - bool confirm_spawn:1; - bool selinux_context_net:1; - - bool cgroup_delegate:1; - CGroupMask cgroup_supported; - const char *cgroup_path; - - const char *runtime_prefix; - - usec_t watchdog_usec; - - int *idle_pipe; - - char *bus_endpoint_path; - int bus_endpoint_fd; - - int stdin_fd; - int stdout_fd; - int stderr_fd; -}; - -int exec_spawn(Unit *unit, - ExecCommand *command, - const ExecContext *context, - const ExecParameters *exec_params, - ExecRuntime *runtime, - pid_t *ret); - -void exec_command_done(ExecCommand *c); -void exec_command_done_array(ExecCommand *c, unsigned n); - -ExecCommand* exec_command_free_list(ExecCommand *c); -void exec_command_free_array(ExecCommand **c, unsigned n); - -char *exec_command_line(char **argv); - -void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix); -void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix); -void exec_command_append_list(ExecCommand **l, ExecCommand *e); -int exec_command_set(ExecCommand *c, const char *path, ...); -int exec_command_append(ExecCommand *c, const char *path, ...); - -void exec_context_init(ExecContext *c); -void exec_context_done(ExecContext *c); -void exec_context_dump(ExecContext *c, FILE* f, const char *prefix); - -int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_root); - -int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l); - -bool exec_context_may_touch_console(ExecContext *c); -bool exec_context_maintains_privileges(ExecContext *c); - -void exec_status_start(ExecStatus *s, pid_t pid); -void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status); -void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix); - -int exec_runtime_make(ExecRuntime **rt, ExecContext *c, const char *id); -ExecRuntime *exec_runtime_ref(ExecRuntime *r); -ExecRuntime *exec_runtime_unref(ExecRuntime *r); - -int exec_runtime_serialize(Unit *unit, ExecRuntime *rt, FILE *f, FDSet *fds); -int exec_runtime_deserialize_item(Unit *unit, ExecRuntime **rt, const char *key, const char *value, FDSet *fds); - -void exec_runtime_destroy(ExecRuntime *rt); - -const char* exec_output_to_string(ExecOutput i) _const_; -ExecOutput exec_output_from_string(const char *s) _pure_; - -const char* exec_input_to_string(ExecInput i) _const_; -ExecInput exec_input_from_string(const char *s) _pure_; - -const char* exec_utmp_mode_to_string(ExecUtmpMode i) _const_; -ExecUtmpMode exec_utmp_mode_from_string(const char *s) _pure_; diff --git a/src/core/failure-action.c b/src/core/failure-action.c deleted file mode 100644 index 39f5519ca1..0000000000 --- a/src/core/failure-action.c +++ /dev/null @@ -1,125 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - Copyright 2012 Michael Olbrich - - 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 . -***/ - -#include -#include - -#include "bus-error.h" -#include "bus-util.h" -#include "failure-action.h" -#include "special.h" -#include "string-table.h" -#include "terminal-util.h" - -static void log_and_status(Manager *m, const char *message) { - log_warning("%s", message); - manager_status_printf(m, STATUS_TYPE_EMERGENCY, - ANSI_HIGHLIGHT_RED " !! " ANSI_NORMAL, - "%s", message); -} - -int failure_action( - Manager *m, - FailureAction action, - const char *reboot_arg) { - - assert(m); - assert(action >= 0); - assert(action < _FAILURE_ACTION_MAX); - - if (action == FAILURE_ACTION_NONE) - return -ECANCELED; - - if (m->running_as == MANAGER_USER) { - /* Downgrade all options to simply exiting if we run - * in user mode */ - - log_warning("Exiting as result of failure."); - m->exit_code = MANAGER_EXIT; - return -ECANCELED; - } - - switch (action) { - - case FAILURE_ACTION_REBOOT: - log_and_status(m, "Rebooting as result of failure."); - - update_reboot_param_file(reboot_arg); - (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE, NULL); - - break; - - case FAILURE_ACTION_REBOOT_FORCE: - log_and_status(m, "Forcibly rebooting as result of failure."); - - update_reboot_param_file(reboot_arg); - m->exit_code = MANAGER_REBOOT; - break; - - case FAILURE_ACTION_REBOOT_IMMEDIATE: - log_and_status(m, "Rebooting immediately as result of failure."); - - sync(); - - if (reboot_arg) { - log_info("Rebooting with argument '%s'.", reboot_arg); - syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, reboot_arg); - } - - log_info("Rebooting."); - reboot(RB_AUTOBOOT); - break; - - case FAILURE_ACTION_POWEROFF: - log_and_status(m, "Powering off as result of failure."); - (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_POWEROFF_TARGET, JOB_REPLACE, NULL); - break; - - case FAILURE_ACTION_POWEROFF_FORCE: - log_and_status(m, "Forcibly powering off as result of failure."); - m->exit_code = MANAGER_POWEROFF; - break; - - case FAILURE_ACTION_POWEROFF_IMMEDIATE: - log_and_status(m, "Powering off immediately as result of failure."); - - sync(); - - log_info("Powering off."); - reboot(RB_POWER_OFF); - break; - - default: - assert_not_reached("Unknown failure action"); - } - - return -ECANCELED; -} - -static const char* const failure_action_table[_FAILURE_ACTION_MAX] = { - [FAILURE_ACTION_NONE] = "none", - [FAILURE_ACTION_REBOOT] = "reboot", - [FAILURE_ACTION_REBOOT_FORCE] = "reboot-force", - [FAILURE_ACTION_REBOOT_IMMEDIATE] = "reboot-immediate", - [FAILURE_ACTION_POWEROFF] = "poweroff", - [FAILURE_ACTION_POWEROFF_FORCE] = "poweroff-force", - [FAILURE_ACTION_POWEROFF_IMMEDIATE] = "poweroff-immediate" -}; -DEFINE_STRING_TABLE_LOOKUP(failure_action, FailureAction); diff --git a/src/core/failure-action.h b/src/core/failure-action.h deleted file mode 100644 index 1adac4ad5c..0000000000 --- a/src/core/failure-action.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Lennart Poettering - Copyright 2012 Michael Olbrich - - 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 . -***/ - -typedef enum FailureAction { - FAILURE_ACTION_NONE, - FAILURE_ACTION_REBOOT, - FAILURE_ACTION_REBOOT_FORCE, - FAILURE_ACTION_REBOOT_IMMEDIATE, - FAILURE_ACTION_POWEROFF, - FAILURE_ACTION_POWEROFF_FORCE, - FAILURE_ACTION_POWEROFF_IMMEDIATE, - _FAILURE_ACTION_MAX, - _FAILURE_ACTION_INVALID = -1 -} FailureAction; - -#include "macro.h" -#include "manager.h" - -int failure_action(Manager *m, FailureAction action, const char *reboot_arg); - -const char* failure_action_to_string(FailureAction i) _const_; -FailureAction failure_action_from_string(const char *s) _pure_; diff --git a/src/core/hostname-setup.c b/src/core/hostname-setup.c deleted file mode 100644 index 68be52856b..0000000000 --- a/src/core/hostname-setup.c +++ /dev/null @@ -1,68 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#include "alloc-util.h" -#include "fileio.h" -#include "hostname-setup.h" -#include "hostname-util.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "util.h" - -int hostname_setup(void) { - int r; - _cleanup_free_ char *b = NULL; - const char *hn; - bool enoent = false; - - r = read_hostname_config("/etc/hostname", &b); - if (r < 0) { - if (r == -ENOENT) - enoent = true; - else - log_warning_errno(r, "Failed to read configured hostname: %m"); - - hn = NULL; - } else - hn = b; - - if (isempty(hn)) { - /* Don't override the hostname if it is already set - * and not explicitly configured */ - if (hostname_is_set()) - return 0; - - if (enoent) - log_info("No hostname configured."); - - hn = "localhost"; - } - - r = sethostname_idempotent(hn); - if (r < 0) - return log_warning_errno(r, "Failed to set hostname to <%s>: %m", hn); - - log_info("Set hostname to <%s>.", hn); - return 0; -} diff --git a/src/core/hostname-setup.h b/src/core/hostname-setup.h deleted file mode 100644 index 73e8c75c71..0000000000 --- a/src/core/hostname-setup.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int hostname_setup(void); diff --git a/src/core/ima-setup.c b/src/core/ima-setup.c deleted file mode 100644 index ff7558d500..0000000000 --- a/src/core/ima-setup.c +++ /dev/null @@ -1,80 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy - TORSEC group -- http://security.polito.it - - 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 . -***/ - -#include -#include - -#include "fd-util.h" -#include "fileio.h" -#include "ima-setup.h" -#include "log.h" -#include "util.h" - -#define IMA_SECFS_DIR "/sys/kernel/security/ima" -#define IMA_SECFS_POLICY IMA_SECFS_DIR "/policy" -#define IMA_POLICY_PATH "/etc/ima/ima-policy" - -int ima_setup(void) { -#ifdef HAVE_IMA - _cleanup_fclose_ FILE *input = NULL; - _cleanup_close_ int imafd = -1; - unsigned lineno = 0; - char line[page_size()]; - - if (access(IMA_SECFS_DIR, F_OK) < 0) { - log_debug("IMA support is disabled in the kernel, ignoring."); - return 0; - } - - input = fopen(IMA_POLICY_PATH, "re"); - if (!input) { - log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, - "Failed to open the IMA custom policy file "IMA_POLICY_PATH", ignoring: %m"); - return 0; - } - - if (access(IMA_SECFS_POLICY, F_OK) < 0) { - log_warning("Another IMA custom policy has already been loaded, ignoring."); - return 0; - } - - imafd = open(IMA_SECFS_POLICY, O_WRONLY|O_CLOEXEC); - if (imafd < 0) { - log_error_errno(errno, "Failed to open the IMA kernel interface "IMA_SECFS_POLICY", ignoring: %m"); - return 0; - } - - FOREACH_LINE(line, input, - return log_error_errno(errno, "Failed to read the IMA custom policy file "IMA_POLICY_PATH": %m")) { - size_t len; - - len = strlen(line); - lineno++; - - if (len > 0 && write(imafd, line, len) < 0) - return log_error_errno(errno, "Failed to load the IMA custom policy file "IMA_POLICY_PATH"%u: %m", - lineno); - } - - log_info("Successfully loaded the IMA custom policy "IMA_POLICY_PATH"."); -#endif /* HAVE_IMA */ - return 0; -} diff --git a/src/core/ima-setup.h b/src/core/ima-setup.h deleted file mode 100644 index 3bad74b246..0000000000 --- a/src/core/ima-setup.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy - TORSEC group -- http://security.polito.it - - 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 . -***/ - -int ima_setup(void); diff --git a/src/core/job.c b/src/core/job.c deleted file mode 100644 index 97304c4d05..0000000000 --- a/src/core/job.c +++ /dev/null @@ -1,1249 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "sd-id128.h" -#include "sd-messages.h" - -#include "alloc-util.h" -#include "async.h" -#include "dbus-job.h" -#include "dbus.h" -#include "escape.h" -#include "job.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "set.h" -#include "special.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "unit.h" -#include "virt.h" - -Job* job_new_raw(Unit *unit) { - Job *j; - - /* used for deserialization */ - - assert(unit); - - j = new0(Job, 1); - if (!j) - return NULL; - - j->manager = unit->manager; - j->unit = unit; - j->type = _JOB_TYPE_INVALID; - - return j; -} - -Job* job_new(Unit *unit, JobType type) { - Job *j; - - assert(type < _JOB_TYPE_MAX); - - j = job_new_raw(unit); - if (!j) - return NULL; - - j->id = j->manager->current_job_id++; - j->type = type; - - /* We don't link it here, that's what job_dependency() is for */ - - return j; -} - -void job_free(Job *j) { - assert(j); - assert(!j->installed); - assert(!j->transaction_prev); - assert(!j->transaction_next); - assert(!j->subject_list); - assert(!j->object_list); - - if (j->in_run_queue) - LIST_REMOVE(run_queue, j->manager->run_queue, j); - - if (j->in_dbus_queue) - LIST_REMOVE(dbus_queue, j->manager->dbus_job_queue, j); - - sd_event_source_unref(j->timer_event_source); - - sd_bus_track_unref(j->clients); - strv_free(j->deserialized_clients); - - free(j); -} - -static void job_set_state(Job *j, JobState state) { - assert(j); - assert(state >= 0); - assert(state < _JOB_STATE_MAX); - - if (j->state == state) - return; - - j->state = state; - - if (!j->installed) - return; - - if (j->state == JOB_RUNNING) - j->unit->manager->n_running_jobs++; - else { - assert(j->state == JOB_WAITING); - assert(j->unit->manager->n_running_jobs > 0); - - j->unit->manager->n_running_jobs--; - - if (j->unit->manager->n_running_jobs <= 0) - j->unit->manager->jobs_in_progress_event_source = sd_event_source_unref(j->unit->manager->jobs_in_progress_event_source); - } -} - -void job_uninstall(Job *j) { - Job **pj; - - assert(j->installed); - - job_set_state(j, JOB_WAITING); - - pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; - assert(*pj == j); - - /* Detach from next 'bigger' objects */ - - /* daemon-reload should be transparent to job observers */ - if (j->manager->n_reloading <= 0) - bus_job_send_removed_signal(j); - - *pj = NULL; - - unit_add_to_gc_queue(j->unit); - - hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id)); - j->installed = false; -} - -static bool job_type_allows_late_merge(JobType t) { - /* Tells whether it is OK to merge a job of type 't' with an already - * running job. - * Reloads cannot be merged this way. Think of the sequence: - * 1. Reload of a daemon is in progress; the daemon has already loaded - * its config file, but hasn't completed the reload operation yet. - * 2. Edit foo's config file. - * 3. Trigger another reload to have the daemon use the new config. - * Should the second reload job be merged into the first one, the daemon - * would not know about the new config. - * JOB_RESTART jobs on the other hand can be merged, because they get - * patched into JOB_START after stopping the unit. So if we see a - * JOB_RESTART running, it means the unit hasn't stopped yet and at - * this time the merge is still allowed. */ - return t != JOB_RELOAD; -} - -static void job_merge_into_installed(Job *j, Job *other) { - assert(j->installed); - assert(j->unit == other->unit); - - if (j->type != JOB_NOP) - job_type_merge_and_collapse(&j->type, other->type, j->unit); - else - assert(other->type == JOB_NOP); - - j->irreversible = j->irreversible || other->irreversible; - j->ignore_order = j->ignore_order || other->ignore_order; -} - -Job* job_install(Job *j) { - Job **pj; - Job *uj; - - assert(!j->installed); - assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); - assert(j->state == JOB_WAITING); - - pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; - uj = *pj; - - if (uj) { - if (job_type_is_conflicting(uj->type, j->type)) - job_finish_and_invalidate(uj, JOB_CANCELED, false); - else { - /* not conflicting, i.e. mergeable */ - - if (uj->state == JOB_WAITING || - (job_type_allows_late_merge(j->type) && job_type_is_superset(uj->type, j->type))) { - job_merge_into_installed(uj, j); - log_unit_debug(uj->unit, - "Merged into installed job %s/%s as %u", - uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id); - return uj; - } else { - /* already running and not safe to merge into */ - /* Patch uj to become a merged job and re-run it. */ - /* XXX It should be safer to queue j to run after uj finishes, but it is - * not currently possible to have more than one installed job per unit. */ - job_merge_into_installed(uj, j); - log_unit_debug(uj->unit, - "Merged into running job, re-running: %s/%s as %u", - uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id); - - job_set_state(uj, JOB_WAITING); - return uj; - } - } - } - - /* Install the job */ - *pj = j; - j->installed = true; - - j->manager->n_installed_jobs ++; - log_unit_debug(j->unit, - "Installed new job %s/%s as %u", - j->unit->id, job_type_to_string(j->type), (unsigned) j->id); - return j; -} - -int job_install_deserialized(Job *j) { - Job **pj; - - assert(!j->installed); - - if (j->type < 0 || j->type >= _JOB_TYPE_MAX_IN_TRANSACTION) { - log_debug("Invalid job type %s in deserialization.", strna(job_type_to_string(j->type))); - return -EINVAL; - } - - pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; - if (*pj) { - log_unit_debug(j->unit, "Unit already has a job installed. Not installing deserialized job."); - return -EEXIST; - } - - *pj = j; - j->installed = true; - - if (j->state == JOB_RUNNING) - j->unit->manager->n_running_jobs++; - - log_unit_debug(j->unit, - "Reinstalled deserialized job %s/%s as %u", - j->unit->id, job_type_to_string(j->type), (unsigned) j->id); - return 0; -} - -JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) { - JobDependency *l; - - assert(object); - - /* Adds a new job link, which encodes that the 'subject' job - * needs the 'object' job in some way. If 'subject' is NULL - * this means the 'anchor' job (i.e. the one the user - * explicitly asked for) is the requester. */ - - if (!(l = new0(JobDependency, 1))) - return NULL; - - l->subject = subject; - l->object = object; - l->matters = matters; - l->conflicts = conflicts; - - if (subject) - LIST_PREPEND(subject, subject->subject_list, l); - - LIST_PREPEND(object, object->object_list, l); - - return l; -} - -void job_dependency_free(JobDependency *l) { - assert(l); - - if (l->subject) - LIST_REMOVE(subject, l->subject->subject_list, l); - - LIST_REMOVE(object, l->object->object_list, l); - - free(l); -} - -void job_dump(Job *j, FILE*f, const char *prefix) { - assert(j); - assert(f); - - if (!prefix) - prefix = ""; - - fprintf(f, - "%s-> Job %u:\n" - "%s\tAction: %s -> %s\n" - "%s\tState: %s\n" - "%s\tIrreversible: %s\n", - prefix, j->id, - prefix, j->unit->id, job_type_to_string(j->type), - prefix, job_state_to_string(j->state), - prefix, yes_no(j->irreversible)); -} - -/* - * Merging is commutative, so imagine the matrix as symmetric. We store only - * its lower triangle to avoid duplication. We don't store the main diagonal, - * because A merged with A is simply A. - * - * If the resulting type is collapsed immediately afterwards (to get rid of - * the JOB_RELOAD_OR_START, which lies outside the lookup function's domain), - * the following properties hold: - * - * Merging is associative! A merged with B, and then merged with C is the same - * as A merged with the result of B merged with C. - * - * Mergeability is transitive! If A can be merged with B and B with C then - * A also with C. - * - * Also, if A merged with B cannot be merged with C, then either A or B cannot - * be merged with C either. - */ -static const JobType job_merging_table[] = { -/* What \ With * JOB_START JOB_VERIFY_ACTIVE JOB_STOP JOB_RELOAD */ -/*********************************************************************************/ -/*JOB_START */ -/*JOB_VERIFY_ACTIVE */ JOB_START, -/*JOB_STOP */ -1, -1, -/*JOB_RELOAD */ JOB_RELOAD_OR_START, JOB_RELOAD, -1, -/*JOB_RESTART */ JOB_RESTART, JOB_RESTART, -1, JOB_RESTART, -}; - -JobType job_type_lookup_merge(JobType a, JobType b) { - assert_cc(ELEMENTSOF(job_merging_table) == _JOB_TYPE_MAX_MERGING * (_JOB_TYPE_MAX_MERGING - 1) / 2); - assert(a >= 0 && a < _JOB_TYPE_MAX_MERGING); - assert(b >= 0 && b < _JOB_TYPE_MAX_MERGING); - - if (a == b) - return a; - - if (a < b) { - JobType tmp = a; - a = b; - b = tmp; - } - - return job_merging_table[(a - 1) * a / 2 + b]; -} - -bool job_type_is_redundant(JobType a, UnitActiveState b) { - switch (a) { - - case JOB_START: - return - b == UNIT_ACTIVE || - b == UNIT_RELOADING; - - case JOB_STOP: - return - b == UNIT_INACTIVE || - b == UNIT_FAILED; - - case JOB_VERIFY_ACTIVE: - return - b == UNIT_ACTIVE || - b == UNIT_RELOADING; - - case JOB_RELOAD: - return - b == UNIT_RELOADING; - - case JOB_RESTART: - return - b == UNIT_ACTIVATING; - - case JOB_NOP: - return true; - - default: - assert_not_reached("Invalid job type"); - } -} - -JobType job_type_collapse(JobType t, Unit *u) { - UnitActiveState s; - - switch (t) { - - case JOB_TRY_RESTART: - s = unit_active_state(u); - if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) - return JOB_NOP; - - return JOB_RESTART; - - case JOB_TRY_RELOAD: - s = unit_active_state(u); - if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) - return JOB_NOP; - - return JOB_RELOAD; - - case JOB_RELOAD_OR_START: - s = unit_active_state(u); - if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) - return JOB_START; - - return JOB_RELOAD; - - default: - return t; - } -} - -int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u) { - JobType t; - - t = job_type_lookup_merge(*a, b); - if (t < 0) - return -EEXIST; - - *a = job_type_collapse(t, u); - return 0; -} - -static bool job_is_runnable(Job *j) { - Iterator i; - Unit *other; - - assert(j); - assert(j->installed); - - /* Checks whether there is any job running for the units this - * job needs to be running after (in the case of a 'positive' - * job type) or before (in the case of a 'negative' job - * type. */ - - /* Note that unit types have a say in what is runnable, - * too. For example, if they return -EAGAIN from - * unit_start() they can indicate they are not - * runnable yet. */ - - /* First check if there is an override */ - if (j->ignore_order) - return true; - - if (j->type == JOB_NOP) - return true; - - if (j->type == JOB_START || - j->type == JOB_VERIFY_ACTIVE || - j->type == JOB_RELOAD) { - - /* Immediate result is that the job is or might be - * started. In this case let's wait for the - * dependencies, regardless whether they are - * starting or stopping something. */ - - SET_FOREACH(other, j->unit->dependencies[UNIT_AFTER], i) - if (other->job) - return false; - } - - /* Also, if something else is being stopped and we should - * change state after it, then let's wait. */ - - SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i) - if (other->job && - (other->job->type == JOB_STOP || - other->job->type == JOB_RESTART)) - return false; - - /* This means that for a service a and a service b where b - * shall be started after a: - * - * start a + start b → 1st step start a, 2nd step start b - * start a + stop b → 1st step stop b, 2nd step start a - * stop a + start b → 1st step stop a, 2nd step start b - * stop a + stop b → 1st step stop b, 2nd step stop a - * - * This has the side effect that restarts are properly - * synchronized too. */ - - return true; -} - -static void job_change_type(Job *j, JobType newtype) { - assert(j); - - log_unit_debug(j->unit, - "Converting job %s/%s -> %s/%s", - j->unit->id, job_type_to_string(j->type), - j->unit->id, job_type_to_string(newtype)); - - j->type = newtype; -} - -static int job_perform_on_unit(Job **j) { - uint32_t id; - Manager *m; - JobType t; - Unit *u; - int r; - - /* While we execute this operation the job might go away (for - * example: because it finishes immediately or is replaced by - * a new, conflicting job.) To make sure we don't access a - * freed job later on we store the id here, so that we can - * verify the job is still valid. */ - - assert(j); - assert(*j); - - m = (*j)->manager; - u = (*j)->unit; - t = (*j)->type; - id = (*j)->id; - - switch (t) { - case JOB_START: - r = unit_start(u); - break; - - case JOB_RESTART: - t = JOB_STOP; - /* fall through */ - case JOB_STOP: - r = unit_stop(u); - break; - - case JOB_RELOAD: - r = unit_reload(u); - break; - - default: - assert_not_reached("Invalid job type"); - } - - /* Log if the job still exists and the start/stop/reload function - * actually did something. */ - *j = manager_get_job(m, id); - if (*j && r > 0) - unit_status_emit_starting_stopping_reloading(u, t); - - return r; -} - -int job_run_and_invalidate(Job *j) { - int r; - - assert(j); - assert(j->installed); - assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); - assert(j->in_run_queue); - - LIST_REMOVE(run_queue, j->manager->run_queue, j); - j->in_run_queue = false; - - if (j->state != JOB_WAITING) - return 0; - - if (!job_is_runnable(j)) - return -EAGAIN; - - job_set_state(j, JOB_RUNNING); - job_add_to_dbus_queue(j); - - - switch (j->type) { - - case JOB_VERIFY_ACTIVE: { - UnitActiveState t = unit_active_state(j->unit); - if (UNIT_IS_ACTIVE_OR_RELOADING(t)) - r = -EALREADY; - else if (t == UNIT_ACTIVATING) - r = -EAGAIN; - else - r = -EBADR; - break; - } - - case JOB_START: - case JOB_STOP: - case JOB_RESTART: - r = job_perform_on_unit(&j); - - /* If the unit type does not support starting/stopping, - * then simply wait. */ - if (r == -EBADR) - r = 0; - break; - - case JOB_RELOAD: - r = job_perform_on_unit(&j); - break; - - case JOB_NOP: - r = -EALREADY; - break; - - default: - assert_not_reached("Unknown job type"); - } - - if (j) { - if (r == -EALREADY) - r = job_finish_and_invalidate(j, JOB_DONE, true); - else if (r == -EBADR) - r = job_finish_and_invalidate(j, JOB_SKIPPED, true); - else if (r == -ENOEXEC) - r = job_finish_and_invalidate(j, JOB_INVALID, true); - else if (r == -EPROTO) - r = job_finish_and_invalidate(j, JOB_ASSERT, true); - else if (r == -EOPNOTSUPP) - r = job_finish_and_invalidate(j, JOB_UNSUPPORTED, true); - else if (r == -EAGAIN) - job_set_state(j, JOB_WAITING); - else if (r < 0) - r = job_finish_and_invalidate(j, JOB_FAILED, true); - } - - return r; -} - -_pure_ static const char *job_get_status_message_format(Unit *u, JobType t, JobResult result) { - - static const char *const generic_finished_start_job[_JOB_RESULT_MAX] = { - [JOB_DONE] = "Started %s.", - [JOB_TIMEOUT] = "Timed out starting %s.", - [JOB_FAILED] = "Failed to start %s.", - [JOB_DEPENDENCY] = "Dependency failed for %s.", - [JOB_ASSERT] = "Assertion failed for %s.", - [JOB_UNSUPPORTED] = "Starting of %s not supported.", - }; - static const char *const generic_finished_stop_job[_JOB_RESULT_MAX] = { - [JOB_DONE] = "Stopped %s.", - [JOB_FAILED] = "Stopped (with error) %s.", - [JOB_TIMEOUT] = "Timed out stoppping %s.", - }; - static const char *const generic_finished_reload_job[_JOB_RESULT_MAX] = { - [JOB_DONE] = "Reloaded %s.", - [JOB_FAILED] = "Reload failed for %s.", - [JOB_TIMEOUT] = "Timed out reloading %s.", - }; - /* When verify-active detects the unit is inactive, report it. - * Most likely a DEPEND warning from a requisiting unit will - * occur next and it's nice to see what was requisited. */ - static const char *const generic_finished_verify_active_job[_JOB_RESULT_MAX] = { - [JOB_SKIPPED] = "%s is not active.", - }; - - const UnitStatusMessageFormats *format_table; - const char *format; - - assert(u); - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); - - if (IN_SET(t, JOB_START, JOB_STOP, JOB_RESTART)) { - format_table = &UNIT_VTABLE(u)->status_message_formats; - if (format_table) { - format = t == JOB_START ? format_table->finished_start_job[result] : - format_table->finished_stop_job[result]; - if (format) - return format; - } - } - - /* Return generic strings */ - if (t == JOB_START) - return generic_finished_start_job[result]; - else if (t == JOB_STOP || t == JOB_RESTART) - return generic_finished_stop_job[result]; - else if (t == JOB_RELOAD) - return generic_finished_reload_job[result]; - else if (t == JOB_VERIFY_ACTIVE) - return generic_finished_verify_active_job[result]; - - return NULL; -} - -static void job_print_status_message(Unit *u, JobType t, JobResult result) { - static const char* const job_result_status_table[_JOB_RESULT_MAX] = { - [JOB_DONE] = ANSI_GREEN " OK " ANSI_NORMAL, - [JOB_TIMEOUT] = ANSI_HIGHLIGHT_RED " TIME " ANSI_NORMAL, - [JOB_FAILED] = ANSI_HIGHLIGHT_RED "FAILED" ANSI_NORMAL, - [JOB_DEPENDENCY] = ANSI_HIGHLIGHT_YELLOW "DEPEND" ANSI_NORMAL, - [JOB_SKIPPED] = ANSI_HIGHLIGHT " INFO " ANSI_NORMAL, - [JOB_ASSERT] = ANSI_HIGHLIGHT_YELLOW "ASSERT" ANSI_NORMAL, - [JOB_UNSUPPORTED] = ANSI_HIGHLIGHT_YELLOW "UNSUPP" ANSI_NORMAL, - }; - - const char *format; - - assert(u); - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); - - /* Reload status messages have traditionally not been printed to console. */ - if (t == JOB_RELOAD) - return; - - format = job_get_status_message_format(u, t, result); - if (!format) - return; - - if (result != JOB_DONE) - manager_flip_auto_status(u->manager, true); - - DISABLE_WARNING_FORMAT_NONLITERAL; - unit_status_printf(u, job_result_status_table[result], format); - REENABLE_WARNING; - - if (t == JOB_START && result == JOB_FAILED) { - _cleanup_free_ char *quoted; - - quoted = shell_maybe_quote(u->id); - manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL, "See 'systemctl status %s' for details.", strna(quoted)); - } -} - -static void job_log_status_message(Unit *u, JobType t, JobResult result) { - const char *format; - char buf[LINE_MAX]; - sd_id128_t mid; - static const int job_result_log_level[_JOB_RESULT_MAX] = { - [JOB_DONE] = LOG_INFO, - [JOB_CANCELED] = LOG_INFO, - [JOB_TIMEOUT] = LOG_ERR, - [JOB_FAILED] = LOG_ERR, - [JOB_DEPENDENCY] = LOG_WARNING, - [JOB_SKIPPED] = LOG_NOTICE, - [JOB_INVALID] = LOG_INFO, - [JOB_ASSERT] = LOG_WARNING, - [JOB_UNSUPPORTED] = LOG_WARNING, - }; - - assert(u); - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); - - /* Skip this if it goes to the console. since we already print - * to the console anyway... */ - - if (log_on_console()) - return; - - format = job_get_status_message_format(u, t, result); - if (!format) - return; - - DISABLE_WARNING_FORMAT_NONLITERAL; - xsprintf(buf, format, unit_description(u)); - REENABLE_WARNING; - - switch (t) { - - case JOB_START: - mid = result == JOB_DONE ? SD_MESSAGE_UNIT_STARTED : SD_MESSAGE_UNIT_FAILED; - break; - - case JOB_RELOAD: - mid = SD_MESSAGE_UNIT_RELOADED; - break; - - case JOB_STOP: - case JOB_RESTART: - mid = SD_MESSAGE_UNIT_STOPPED; - break; - - default: - log_struct(job_result_log_level[result], - LOG_UNIT_ID(u), - LOG_MESSAGE("%s", buf), - "RESULT=%s", job_result_to_string(result), - NULL); - return; - } - - log_struct(job_result_log_level[result], - LOG_MESSAGE_ID(mid), - LOG_UNIT_ID(u), - LOG_MESSAGE("%s", buf), - "RESULT=%s", job_result_to_string(result), - NULL); -} - -static void job_emit_status_message(Unit *u, JobType t, JobResult result) { - - /* No message if the job did not actually do anything due to failed condition. */ - if (t == JOB_START && result == JOB_DONE && !u->condition_result) - return; - - job_log_status_message(u, t, result); - job_print_status_message(u, t, result); -} - -static void job_fail_dependencies(Unit *u, UnitDependency d) { - Unit *other; - Iterator i; - - assert(u); - - SET_FOREACH(other, u->dependencies[d], i) { - Job *j = other->job; - - if (!j) - continue; - if (!IN_SET(j->type, JOB_START, JOB_VERIFY_ACTIVE)) - continue; - - job_finish_and_invalidate(j, JOB_DEPENDENCY, true); - } -} - -int job_finish_and_invalidate(Job *j, JobResult result, bool recursive) { - Unit *u; - Unit *other; - JobType t; - Iterator i; - - assert(j); - assert(j->installed); - assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); - - u = j->unit; - t = j->type; - - j->result = result; - - log_unit_debug(u, "Job %s/%s finished, result=%s", u->id, job_type_to_string(t), job_result_to_string(result)); - - job_emit_status_message(u, t, result); - - job_add_to_dbus_queue(j); - - /* Patch restart jobs so that they become normal start jobs */ - if (result == JOB_DONE && t == JOB_RESTART) { - - job_change_type(j, JOB_START); - job_set_state(j, JOB_WAITING); - - job_add_to_run_queue(j); - - goto finish; - } - - if (result == JOB_FAILED || result == JOB_INVALID) - j->manager->n_failed_jobs ++; - - job_uninstall(j); - job_free(j); - - /* Fail depending jobs on failure */ - if (result != JOB_DONE && recursive) { - if (IN_SET(t, JOB_START, JOB_VERIFY_ACTIVE)) { - job_fail_dependencies(u, UNIT_REQUIRED_BY); - job_fail_dependencies(u, UNIT_REQUISITE_OF); - job_fail_dependencies(u, UNIT_BOUND_BY); - } else if (t == JOB_STOP) - job_fail_dependencies(u, UNIT_CONFLICTED_BY); - } - - /* Trigger OnFailure dependencies that are not generated by - * the unit itself. We don't treat JOB_CANCELED as failure in - * this context. And JOB_FAILURE is already handled by the - * unit itself. */ - if (result == JOB_TIMEOUT || result == JOB_DEPENDENCY) { - log_struct(LOG_NOTICE, - "JOB_TYPE=%s", job_type_to_string(t), - "JOB_RESULT=%s", job_result_to_string(result), - LOG_UNIT_ID(u), - LOG_UNIT_MESSAGE(u, "Job %s/%s failed with result '%s'.", - u->id, - job_type_to_string(t), - job_result_to_string(result)), - NULL); - - unit_start_on_failure(u); - } - - unit_trigger_notify(u); - -finish: - /* Try to start the next jobs that can be started */ - SET_FOREACH(other, u->dependencies[UNIT_AFTER], i) - if (other->job) - job_add_to_run_queue(other->job); - SET_FOREACH(other, u->dependencies[UNIT_BEFORE], i) - if (other->job) - job_add_to_run_queue(other->job); - - manager_check_finished(u->manager); - - return 0; -} - -static int job_dispatch_timer(sd_event_source *s, uint64_t monotonic, void *userdata) { - Job *j = userdata; - Unit *u; - - assert(j); - assert(s == j->timer_event_source); - - log_unit_warning(j->unit, "Job %s/%s timed out.", j->unit->id, job_type_to_string(j->type)); - - u = j->unit; - job_finish_and_invalidate(j, JOB_TIMEOUT, true); - - failure_action(u->manager, u->job_timeout_action, u->job_timeout_reboot_arg); - - return 0; -} - -int job_start_timer(Job *j) { - int r; - - if (j->timer_event_source) - return 0; - - j->begin_usec = now(CLOCK_MONOTONIC); - - if (j->unit->job_timeout == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - j->manager->event, - &j->timer_event_source, - CLOCK_MONOTONIC, - usec_add(j->begin_usec, j->unit->job_timeout), 0, - job_dispatch_timer, j); - if (r < 0) - return r; - - (void) sd_event_source_set_description(j->timer_event_source, "job-start"); - - return 0; -} - -void job_add_to_run_queue(Job *j) { - assert(j); - assert(j->installed); - - if (j->in_run_queue) - return; - - if (!j->manager->run_queue) - sd_event_source_set_enabled(j->manager->run_queue_event_source, SD_EVENT_ONESHOT); - - LIST_PREPEND(run_queue, j->manager->run_queue, j); - j->in_run_queue = true; -} - -void job_add_to_dbus_queue(Job *j) { - assert(j); - assert(j->installed); - - if (j->in_dbus_queue) - return; - - /* We don't check if anybody is subscribed here, since this - * job might just have been created and not yet assigned to a - * connection/client. */ - - LIST_PREPEND(dbus_queue, j->manager->dbus_job_queue, j); - j->in_dbus_queue = true; -} - -char *job_dbus_path(Job *j) { - char *p; - - assert(j); - - if (asprintf(&p, "/org/freedesktop/systemd1/job/%"PRIu32, j->id) < 0) - return NULL; - - return p; -} - -int job_serialize(Job *j, FILE *f, FDSet *fds) { - fprintf(f, "job-id=%u\n", j->id); - fprintf(f, "job-type=%s\n", job_type_to_string(j->type)); - fprintf(f, "job-state=%s\n", job_state_to_string(j->state)); - fprintf(f, "job-irreversible=%s\n", yes_no(j->irreversible)); - fprintf(f, "job-sent-dbus-new-signal=%s\n", yes_no(j->sent_dbus_new_signal)); - fprintf(f, "job-ignore-order=%s\n", yes_no(j->ignore_order)); - - if (j->begin_usec > 0) - fprintf(f, "job-begin="USEC_FMT"\n", j->begin_usec); - - bus_track_serialize(j->clients, f); - - /* End marker */ - fputc('\n', f); - return 0; -} - -int job_deserialize(Job *j, FILE *f, FDSet *fds) { - assert(j); - - for (;;) { - char line[LINE_MAX], *l, *v; - size_t k; - - if (!fgets(line, sizeof(line), f)) { - if (feof(f)) - return 0; - return -errno; - } - - char_array_0(line); - l = strstrip(line); - - /* End marker */ - if (l[0] == 0) - return 0; - - k = strcspn(l, "="); - - if (l[k] == '=') { - l[k] = 0; - v = l+k+1; - } else - v = l+k; - - if (streq(l, "job-id")) { - - if (safe_atou32(v, &j->id) < 0) - log_debug("Failed to parse job id value %s", v); - - } else if (streq(l, "job-type")) { - JobType t; - - t = job_type_from_string(v); - if (t < 0) - log_debug("Failed to parse job type %s", v); - else if (t >= _JOB_TYPE_MAX_IN_TRANSACTION) - log_debug("Cannot deserialize job of type %s", v); - else - j->type = t; - - } else if (streq(l, "job-state")) { - JobState s; - - s = job_state_from_string(v); - if (s < 0) - log_debug("Failed to parse job state %s", v); - else - job_set_state(j, s); - - } else if (streq(l, "job-irreversible")) { - int b; - - b = parse_boolean(v); - if (b < 0) - log_debug("Failed to parse job irreversible flag %s", v); - else - j->irreversible = j->irreversible || b; - - } else if (streq(l, "job-sent-dbus-new-signal")) { - int b; - - b = parse_boolean(v); - if (b < 0) - log_debug("Failed to parse job sent_dbus_new_signal flag %s", v); - else - j->sent_dbus_new_signal = j->sent_dbus_new_signal || b; - - } else if (streq(l, "job-ignore-order")) { - int b; - - b = parse_boolean(v); - if (b < 0) - log_debug("Failed to parse job ignore_order flag %s", v); - else - j->ignore_order = j->ignore_order || b; - - } else if (streq(l, "job-begin")) { - unsigned long long ull; - - if (sscanf(v, "%llu", &ull) != 1) - log_debug("Failed to parse job-begin value %s", v); - else - j->begin_usec = ull; - - } else if (streq(l, "subscribed")) { - - if (strv_extend(&j->deserialized_clients, v) < 0) - return log_oom(); - } - } -} - -int job_coldplug(Job *j) { - int r; - - assert(j); - - /* After deserialization is complete and the bus connection - * set up again, let's start watching our subscribers again */ - r = bus_track_coldplug(j->manager, &j->clients, &j->deserialized_clients); - if (r < 0) - return r; - - if (j->state == JOB_WAITING) - job_add_to_run_queue(j); - - if (j->begin_usec == 0 || j->unit->job_timeout == USEC_INFINITY) - return 0; - - j->timer_event_source = sd_event_source_unref(j->timer_event_source); - - r = sd_event_add_time( - j->manager->event, - &j->timer_event_source, - CLOCK_MONOTONIC, - usec_add(j->begin_usec, j->unit->job_timeout), 0, - job_dispatch_timer, j); - if (r < 0) - log_debug_errno(r, "Failed to restart timeout for job: %m"); - - (void) sd_event_source_set_description(j->timer_event_source, "job-timeout"); - - return r; -} - -void job_shutdown_magic(Job *j) { - assert(j); - - /* The shutdown target gets some special treatment here: we - * tell the kernel to begin with flushing its disk caches, to - * optimize shutdown time a bit. Ideally we wouldn't hardcode - * this magic into PID 1. However all other processes aren't - * options either since they'd exit much sooner than PID 1 and - * asynchronous sync() would cause their exit to be - * delayed. */ - - if (j->type != JOB_START) - return; - - if (j->unit->manager->running_as != MANAGER_SYSTEM) - return; - - if (!unit_has_name(j->unit, SPECIAL_SHUTDOWN_TARGET)) - return; - - /* In case messages on console has been disabled on boot */ - j->unit->manager->no_console_output = false; - - if (detect_container() > 0) - return; - - asynchronous_sync(); -} - -int job_get_timeout(Job *j, usec_t *timeout) { - usec_t x = USEC_INFINITY, y = USEC_INFINITY; - Unit *u = j->unit; - int r; - - assert(u); - - if (j->timer_event_source) { - r = sd_event_source_get_time(j->timer_event_source, &x); - if (r < 0) - return r; - } - - if (UNIT_VTABLE(u)->get_timeout) { - r = UNIT_VTABLE(u)->get_timeout(u, &y); - if (r < 0) - return r; - } - - if (x == USEC_INFINITY && y == USEC_INFINITY) - return 0; - - *timeout = MIN(x, y); - return 1; -} - -static const char* const job_state_table[_JOB_STATE_MAX] = { - [JOB_WAITING] = "waiting", - [JOB_RUNNING] = "running" -}; - -DEFINE_STRING_TABLE_LOOKUP(job_state, JobState); - -static const char* const job_type_table[_JOB_TYPE_MAX] = { - [JOB_START] = "start", - [JOB_VERIFY_ACTIVE] = "verify-active", - [JOB_STOP] = "stop", - [JOB_RELOAD] = "reload", - [JOB_RELOAD_OR_START] = "reload-or-start", - [JOB_RESTART] = "restart", - [JOB_TRY_RESTART] = "try-restart", - [JOB_TRY_RELOAD] = "try-reload", - [JOB_NOP] = "nop", -}; - -DEFINE_STRING_TABLE_LOOKUP(job_type, JobType); - -static const char* const job_mode_table[_JOB_MODE_MAX] = { - [JOB_FAIL] = "fail", - [JOB_REPLACE] = "replace", - [JOB_REPLACE_IRREVERSIBLY] = "replace-irreversibly", - [JOB_ISOLATE] = "isolate", - [JOB_FLUSH] = "flush", - [JOB_IGNORE_DEPENDENCIES] = "ignore-dependencies", - [JOB_IGNORE_REQUIREMENTS] = "ignore-requirements", -}; - -DEFINE_STRING_TABLE_LOOKUP(job_mode, JobMode); - -static const char* const job_result_table[_JOB_RESULT_MAX] = { - [JOB_DONE] = "done", - [JOB_CANCELED] = "canceled", - [JOB_TIMEOUT] = "timeout", - [JOB_FAILED] = "failed", - [JOB_DEPENDENCY] = "dependency", - [JOB_SKIPPED] = "skipped", - [JOB_INVALID] = "invalid", - [JOB_ASSERT] = "assert", - [JOB_UNSUPPORTED] = "unsupported", -}; - -DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult); - -const char* job_type_to_access_method(JobType t) { - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); - - if (IN_SET(t, JOB_START, JOB_RESTART, JOB_TRY_RESTART)) - return "start"; - else if (t == JOB_STOP) - return "stop"; - else - return "reload"; -} diff --git a/src/core/job.h b/src/core/job.h deleted file mode 100644 index 856b0ce829..0000000000 --- a/src/core/job.h +++ /dev/null @@ -1,242 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "sd-event.h" - -#include "list.h" -#include "unit-name.h" - -typedef struct Job Job; -typedef struct JobDependency JobDependency; -typedef enum JobType JobType; -typedef enum JobState JobState; -typedef enum JobMode JobMode; -typedef enum JobResult JobResult; - -/* Be careful when changing the job types! Adjust job_merging_table[] accordingly! */ -enum JobType { - JOB_START, /* if a unit does not support being started, we'll just wait until it becomes active */ - JOB_VERIFY_ACTIVE, - - JOB_STOP, - - JOB_RELOAD, /* if running, reload */ - - /* Note that restarts are first treated like JOB_STOP, but - * then instead of finishing are patched to become - * JOB_START. */ - JOB_RESTART, /* If running, stop. Then start unconditionally. */ - - _JOB_TYPE_MAX_MERGING, - - /* JOB_NOP can enter into a transaction, but as it won't pull in - * any dependencies and it uses the special 'nop_job' slot in Unit, - * it won't have to merge with anything (except possibly into another - * JOB_NOP, previously installed). JOB_NOP is special-cased in - * job_type_is_*() functions so that the transaction can be - * activated. */ - JOB_NOP = _JOB_TYPE_MAX_MERGING, /* do nothing */ - - _JOB_TYPE_MAX_IN_TRANSACTION, - - /* JOB_TRY_RESTART can never appear in a transaction, because - * it always collapses into JOB_RESTART or JOB_NOP before entering. - * Thus we never need to merge it with anything. */ - JOB_TRY_RESTART = _JOB_TYPE_MAX_IN_TRANSACTION, /* if running, stop and then start */ - - /* Similar to JOB_TRY_RESTART but collapses to JOB_RELOAD or JOB_NOP */ - JOB_TRY_RELOAD, - - /* JOB_RELOAD_OR_START won't enter into a transaction and cannot result - * from transaction merging (there's no way for JOB_RELOAD and - * JOB_START to meet in one transaction). It can result from a merge - * during job installation, but then it will immediately collapse into - * one of the two simpler types. */ - JOB_RELOAD_OR_START, /* if running, reload, otherwise start */ - - _JOB_TYPE_MAX, - _JOB_TYPE_INVALID = -1 -}; - -enum JobState { - JOB_WAITING, - JOB_RUNNING, - _JOB_STATE_MAX, - _JOB_STATE_INVALID = -1 -}; - -enum JobMode { - JOB_FAIL, /* Fail if a conflicting job is already queued */ - JOB_REPLACE, /* Replace an existing conflicting job */ - JOB_REPLACE_IRREVERSIBLY,/* Like JOB_REPLACE + produce irreversible jobs */ - JOB_ISOLATE, /* Start a unit, and stop all others */ - JOB_FLUSH, /* Flush out all other queued jobs when queing this one */ - JOB_IGNORE_DEPENDENCIES, /* Ignore both requirement and ordering dependencies */ - JOB_IGNORE_REQUIREMENTS, /* Ignore requirement dependencies */ - _JOB_MODE_MAX, - _JOB_MODE_INVALID = -1 -}; - -enum JobResult { - JOB_DONE, /* Job completed successfully */ - JOB_CANCELED, /* Job canceled by a conflicting job installation or by explicit cancel request */ - JOB_TIMEOUT, /* Job timeout elapsed */ - JOB_FAILED, /* Job failed */ - JOB_DEPENDENCY, /* A required dependency job did not result in JOB_DONE */ - JOB_SKIPPED, /* Negative result of JOB_VERIFY_ACTIVE */ - JOB_INVALID, /* JOB_RELOAD of inactive unit */ - JOB_ASSERT, /* Couldn't start a unit, because an assert didn't hold */ - JOB_UNSUPPORTED, /* Couldn't start a unit, because the unit type is not supported on the system */ - _JOB_RESULT_MAX, - _JOB_RESULT_INVALID = -1 -}; - -#include "unit.h" - -struct JobDependency { - /* Encodes that the 'subject' job needs the 'object' job in - * some way. This structure is used only while building a transaction. */ - Job *subject; - Job *object; - - LIST_FIELDS(JobDependency, subject); - LIST_FIELDS(JobDependency, object); - - bool matters; - bool conflicts; -}; - -struct Job { - Manager *manager; - Unit *unit; - - LIST_FIELDS(Job, transaction); - LIST_FIELDS(Job, run_queue); - LIST_FIELDS(Job, dbus_queue); - - LIST_HEAD(JobDependency, subject_list); - LIST_HEAD(JobDependency, object_list); - - /* Used for graph algs as a "I have been here" marker */ - Job* marker; - unsigned generation; - - uint32_t id; - - JobType type; - JobState state; - - sd_event_source *timer_event_source; - usec_t begin_usec; - - /* - * This tracks where to send signals, and also which clients - * are allowed to call DBus methods on the job (other than - * root). - * - * There can be more than one client, because of job merging. - */ - sd_bus_track *clients; - char **deserialized_clients; - - JobResult result; - - bool installed:1; - bool in_run_queue:1; - bool matters_to_anchor:1; - bool in_dbus_queue:1; - bool sent_dbus_new_signal:1; - bool ignore_order:1; - bool irreversible:1; -}; - -Job* job_new(Unit *unit, JobType type); -Job* job_new_raw(Unit *unit); -void job_free(Job *job); -Job* job_install(Job *j); -int job_install_deserialized(Job *j); -void job_uninstall(Job *j); -void job_dump(Job *j, FILE*f, const char *prefix); -int job_serialize(Job *j, FILE *f, FDSet *fds); -int job_deserialize(Job *j, FILE *f, FDSet *fds); -int job_coldplug(Job *j); - -JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts); -void job_dependency_free(JobDependency *l); - -int job_merge(Job *j, Job *other); - -JobType job_type_lookup_merge(JobType a, JobType b) _pure_; - -_pure_ static inline bool job_type_is_mergeable(JobType a, JobType b) { - return job_type_lookup_merge(a, b) >= 0; -} - -_pure_ static inline bool job_type_is_conflicting(JobType a, JobType b) { - return a != JOB_NOP && b != JOB_NOP && !job_type_is_mergeable(a, b); -} - -_pure_ static inline bool job_type_is_superset(JobType a, JobType b) { - /* Checks whether operation a is a "superset" of b in its actions */ - if (b == JOB_NOP) - return true; - if (a == JOB_NOP) - return false; - return a == job_type_lookup_merge(a, b); -} - -bool job_type_is_redundant(JobType a, UnitActiveState b) _pure_; - -/* Collapses a state-dependent job type into a simpler type by observing - * the state of the unit which it is going to be applied to. */ -JobType job_type_collapse(JobType t, Unit *u); - -int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u); - -void job_add_to_run_queue(Job *j); -void job_add_to_dbus_queue(Job *j); - -int job_start_timer(Job *j); - -int job_run_and_invalidate(Job *j); -int job_finish_and_invalidate(Job *j, JobResult result, bool recursive); - -char *job_dbus_path(Job *j); - -void job_shutdown_magic(Job *j); - -int job_get_timeout(Job *j, usec_t *timeout) _pure_; - -const char* job_type_to_string(JobType t) _const_; -JobType job_type_from_string(const char *s) _pure_; - -const char* job_state_to_string(JobState t) _const_; -JobState job_state_from_string(const char *s) _pure_; - -const char* job_mode_to_string(JobMode t) _const_; -JobMode job_mode_from_string(const char *s) _pure_; - -const char* job_result_to_string(JobResult t) _const_; -JobResult job_result_from_string(const char *s) _pure_; - -const char* job_type_to_access_method(JobType t); diff --git a/src/core/kill.c b/src/core/kill.c deleted file mode 100644 index 6854587d54..0000000000 --- a/src/core/kill.c +++ /dev/null @@ -1,68 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include "kill.h" -#include "signal-util.h" -#include "string-table.h" -#include "util.h" - -void kill_context_init(KillContext *c) { - assert(c); - - c->kill_signal = SIGTERM; - c->send_sigkill = true; - c->send_sighup = false; -} - -void kill_context_dump(KillContext *c, FILE *f, const char *prefix) { - assert(c); - - if (!prefix) - prefix = ""; - - fprintf(f, - "%sKillMode: %s\n" - "%sKillSignal: SIG%s\n" - "%sSendSIGKILL: %s\n" - "%sSendSIGHUP: %s\n", - prefix, kill_mode_to_string(c->kill_mode), - prefix, signal_to_string(c->kill_signal), - prefix, yes_no(c->send_sigkill), - prefix, yes_no(c->send_sighup)); -} - -static const char* const kill_mode_table[_KILL_MODE_MAX] = { - [KILL_CONTROL_GROUP] = "control-group", - [KILL_PROCESS] = "process", - [KILL_MIXED] = "mixed", - [KILL_NONE] = "none" -}; - -DEFINE_STRING_TABLE_LOOKUP(kill_mode, KillMode); - -static const char* const kill_who_table[_KILL_WHO_MAX] = { - [KILL_MAIN] = "main", - [KILL_CONTROL] = "control", - [KILL_ALL] = "all", - [KILL_MAIN_FAIL] = "main-fail", - [KILL_CONTROL_FAIL] = "control-fail", - [KILL_ALL_FAIL] = "all-fail" -}; - -DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/core/kill.h b/src/core/kill.h deleted file mode 100644 index b3d2056cb0..0000000000 --- a/src/core/kill.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -typedef struct KillContext KillContext; - -#include -#include - -#include "macro.h" - -typedef enum KillMode { - /* The kill mode is a property of a unit. */ - KILL_CONTROL_GROUP = 0, - KILL_PROCESS, - KILL_MIXED, - KILL_NONE, - _KILL_MODE_MAX, - _KILL_MODE_INVALID = -1 -} KillMode; - -struct KillContext { - KillMode kill_mode; - int kill_signal; - bool send_sigkill; - bool send_sighup; -}; - -typedef enum KillWho { - /* Kill who is a property of an operation */ - KILL_MAIN, - KILL_CONTROL, - KILL_ALL, - KILL_MAIN_FAIL, - KILL_CONTROL_FAIL, - KILL_ALL_FAIL, - _KILL_WHO_MAX, - _KILL_WHO_INVALID = -1 -} KillWho; - -void kill_context_init(KillContext *c); -void kill_context_dump(KillContext *c, FILE *f, const char *prefix); - -const char *kill_mode_to_string(KillMode k) _const_; -KillMode kill_mode_from_string(const char *s) _pure_; - -const char *kill_who_to_string(KillWho k) _const_; -KillWho kill_who_from_string(const char *s) _pure_; diff --git a/src/core/killall.c b/src/core/killall.c deleted file mode 100644 index 09378f7085..0000000000 --- a/src/core/killall.c +++ /dev/null @@ -1,249 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 ProFUSION embedded systems - - 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 . -***/ - -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "killall.h" -#include "parse-util.h" -#include "process-util.h" -#include "set.h" -#include "string-util.h" -#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; - const char *p; - size_t count; - uid_t uid; - int r; - - /* We are PID 1, let's not commit suicide */ - if (pid == 1) - return true; - - r = get_process_uid(pid, &uid); - if (r < 0) - return true; /* not really, but better safe than sorry */ - - /* Non-root processes otherwise are always subject to be killed */ - if (uid != 0) - return false; - - p = procfs_file_alloca(pid, "cmdline"); - f = fopen(p, "re"); - if (!f) - return true; /* not really, but has the desired effect */ - - count = fread(&c, 1, 1, f); - - /* Kernel threads have an empty cmdline */ - if (count <= 0) - return true; - - /* Processes with argv[0][0] = '@' we ignore from the killing - * spree. - * - * http://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons */ - if (c == '@' && warn_rootfs) { - _cleanup_free_ char *comm = NULL; - - r = pid_from_same_root_fs(pid); - if (r < 0) - return true; - - get_process_comm(pid, &comm); - - if (r) - log_notice("Process " PID_FMT " (%s) has been 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)); - return true; - } else if (c == '@') - return true; - - return false; -} - -static void wait_for_children(Set *pids, sigset_t *mask) { - usec_t until; - - assert(mask); - - if (set_isempty(pids)) - return; - - until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC; - for (;;) { - struct timespec ts; - int k; - usec_t n; - void *p; - Iterator i; - - /* First, let the kernel inform us about killed - * children. Most processes will probably be our - * children, but some are not (might be our - * grandchildren instead...). */ - for (;;) { - pid_t pid; - - pid = waitpid(-1, NULL, WNOHANG); - if (pid == 0) - break; - if (pid < 0) { - if (errno == ECHILD) - break; - - log_error_errno(errno, "waitpid() failed: %m"); - return; - } - - (void) set_remove(pids, PID_TO_PTR(pid)); - } - - /* Now explicitly check who might be remaining, who - * might not be our child. */ - SET_FOREACH(p, pids, i) { - - /* We misuse getpgid as a check whether a - * process still exists. */ - if (getpgid(PTR_TO_PID(p)) >= 0) - continue; - - if (errno != ESRCH) - continue; - - set_remove(pids, p); - } - - if (set_isempty(pids)) - return; - - n = now(CLOCK_MONOTONIC); - if (n >= until) - return; - - timespec_store(&ts, until - n); - k = sigtimedwait(mask, NULL, &ts); - if (k != SIGCHLD) { - - if (k < 0 && errno != EAGAIN) { - log_error_errno(errno, "sigtimedwait() failed: %m"); - return; - } - - if (k >= 0) - log_warning("sigtimedwait() returned unexpected signal."); - } - } -} - -static int killall(int sig, Set *pids, bool send_sighup) { - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *d; - - dir = opendir("/proc"); - if (!dir) - return -errno; - - while ((d = readdir(dir))) { - pid_t pid; - int r; - - if (d->d_type != DT_DIR && - d->d_type != DT_UNKNOWN) - continue; - - if (parse_pid(d->d_name, &pid) < 0) - continue; - - if (ignore_proc(pid, sig == SIGKILL && !in_initrd())) - continue; - - if (sig == SIGKILL) { - _cleanup_free_ char *s = NULL; - - get_process_comm(pid, &s); - log_notice("Sending SIGKILL to PID "PID_FMT" (%s).", pid, strna(s)); - } - - if (kill(pid, sig) >= 0) { - if (pids) { - r = set_put(pids, PID_TO_PTR(pid)); - if (r < 0) - log_oom(); - } - } else if (errno != ENOENT) - log_warning_errno(errno, "Could not kill %d: %m", pid); - - if (send_sighup) { - /* Optionally, also send a SIGHUP signal, but - only if the process has a controlling - tty. This is useful to allow handling of - shells which ignore SIGTERM but react to - SIGHUP. We do not send this to processes that - have no controlling TTY since we don't want to - trigger reloads of daemon processes. Also we - make sure to only send this after SIGTERM so - that SIGTERM is always first in the queue. */ - - - if (get_ctty_devnr(pid, NULL) >= 0) - kill(pid, SIGHUP); - } - } - - return set_size(pids); -} - -void broadcast_signal(int sig, bool wait_for_exit, bool send_sighup) { - sigset_t mask, oldmask; - _cleanup_set_free_ Set *pids = NULL; - - if (wait_for_exit) - pids = set_new(NULL); - - assert_se(sigemptyset(&mask) == 0); - assert_se(sigaddset(&mask, SIGCHLD) == 0); - assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0); - - if (kill(-1, SIGSTOP) < 0 && errno != ESRCH) - log_warning_errno(errno, "kill(-1, SIGSTOP) failed: %m"); - - killall(sig, pids, send_sighup); - - if (kill(-1, SIGCONT) < 0 && errno != ESRCH) - log_warning_errno(errno, "kill(-1, SIGCONT) failed: %m"); - - if (wait_for_exit) - wait_for_children(pids, &mask); - - assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0); -} diff --git a/src/core/killall.h b/src/core/killall.h deleted file mode 100644 index acc2439f00..0000000000 --- a/src/core/killall.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -void broadcast_signal(int sig, bool wait_for_exit, bool send_sighup); diff --git a/src/core/kmod-setup.c b/src/core/kmod-setup.c deleted file mode 100644 index 3503db52ed..0000000000 --- a/src/core/kmod-setup.c +++ /dev/null @@ -1,131 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#ifdef HAVE_KMOD -#include -#endif - -#include "bus-util.h" -#include "capability-util.h" -#include "kmod-setup.h" -#include "macro.h" - -#ifdef HAVE_KMOD -static void systemd_kmod_log( - void *data, - int priority, - const char *file, int line, - const char *fn, - const char *format, - va_list args) { - - /* library logging is enabled at debug only */ - DISABLE_WARNING_FORMAT_NONLITERAL; - log_internalv(LOG_DEBUG, 0, file, line, fn, format, args); - REENABLE_WARNING; -} -#endif - -int kmod_setup(void) { -#ifdef HAVE_KMOD - - static const struct { - const char *module; - const char *path; - bool warn_if_unavailable:1; - bool warn_if_module:1; - bool (*condition_fn)(void); - } kmod_table[] = { - /* auto-loading on use doesn't work before udev is up */ - { "autofs4", "/sys/class/misc/autofs", true, false, NULL }, - - /* early configure of ::1 on the loopback device */ - { "ipv6", "/sys/module/ipv6", false, true, NULL }, - - /* 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 }, -#endif - }; - struct kmod_ctx *ctx = NULL; - unsigned int i; - int r; - - if (have_effective_cap(CAP_SYS_MODULE) == 0) - return 0; - - for (i = 0; i < ELEMENTSOF(kmod_table); i++) { - struct kmod_module *mod; - - if (kmod_table[i].path && access(kmod_table[i].path, F_OK) >= 0) - continue; - - if (kmod_table[i].condition_fn && !kmod_table[i].condition_fn()) - continue; - - if (kmod_table[i].warn_if_module) - log_debug("Your kernel apparently lacks built-in %s support. Might be " - "a good idea to compile it in. We'll now try to work around " - "this by loading the module...", kmod_table[i].module); - - if (!ctx) { - ctx = kmod_new(NULL, NULL); - if (!ctx) - return log_oom(); - - kmod_set_log_fn(ctx, systemd_kmod_log, NULL); - kmod_load_resources(ctx); - } - - r = kmod_module_new_from_name(ctx, kmod_table[i].module, &mod); - if (r < 0) { - log_error("Failed to lookup module '%s'", kmod_table[i].module); - continue; - } - - r = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL); - if (r == 0) - log_debug("Inserted module '%s'", kmod_module_get_name(mod)); - else if (r == KMOD_PROBE_APPLY_BLACKLIST) - log_info("Module '%s' is blacklisted", kmod_module_get_name(mod)); - else { - bool print_warning = kmod_table[i].warn_if_unavailable || (r < 0 && r != -ENOENT); - - log_full_errno(print_warning ? LOG_WARNING : LOG_DEBUG, r, - "Failed to insert module '%s': %m", kmod_module_get_name(mod)); - } - - kmod_module_unref(mod); - } - - if (ctx) - kmod_unref(ctx); - -#endif - return 0; -} diff --git a/src/core/kmod-setup.h b/src/core/kmod-setup.h deleted file mode 100644 index 685f4df301..0000000000 --- a/src/core/kmod-setup.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int kmod_setup(void); diff --git a/src/core/load-dropin.c b/src/core/load-dropin.c deleted file mode 100644 index 22b71b6f5e..0000000000 --- a/src/core/load-dropin.c +++ /dev/null @@ -1,81 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - - -#include "conf-parser.h" -#include "load-dropin.h" -#include "load-fragment.h" -#include "log.h" -#include "strv.h" -#include "unit-name.h" -#include "unit.h" - -static int add_dependency_consumer( - UnitDependency dependency, - const char *entry, - const char* filepath, - void *arg) { - Unit *u = arg; - int r; - - assert(u); - - r = unit_add_dependency_by_name(u, dependency, entry, filepath, true); - if (r < 0) - log_error_errno(r, "Cannot add dependency %s to %s, ignoring: %m", entry, u->id); - - return 0; -} - -int unit_load_dropin(Unit *u) { - Iterator i; - char *t, **f; - int r; - - assert(u); - - /* Load dependencies from supplementary drop-in directories */ - - SET_FOREACH(t, u->names, i) { - char **p; - - STRV_FOREACH(p, u->manager->lookup_paths.unit_path) { - unit_file_process_dir(u->manager->unit_path_cache, *p, t, ".wants", UNIT_WANTS, - add_dependency_consumer, u, NULL); - unit_file_process_dir(u->manager->unit_path_cache, *p, t, ".requires", UNIT_REQUIRES, - add_dependency_consumer, u, NULL); - } - } - - u->dropin_paths = strv_free(u->dropin_paths); - r = unit_find_dropin_paths(u, &u->dropin_paths); - if (r <= 0) - return 0; - - STRV_FOREACH(f, u->dropin_paths) { - config_parse(u->id, *f, NULL, - UNIT_VTABLE(u)->sections, - config_item_perf_lookup, load_fragment_gperf_lookup, - false, false, false, u); - } - - u->dropin_mtime = now(CLOCK_REALTIME); - - return 0; -} diff --git a/src/core/load-dropin.h b/src/core/load-dropin.h deleted file mode 100644 index d8a4aefbb3..0000000000 --- a/src/core/load-dropin.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "dropin.h" -#include "unit.h" - -/* Read service data supplementary drop-in directories */ - -static inline int unit_find_dropin_paths(Unit *u, char ***paths) { - return unit_file_find_dropin_paths(u->manager->lookup_paths.unit_path, - u->manager->unit_path_cache, - u->names, - paths); -} - -int unit_load_dropin(Unit *u); diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 deleted file mode 100644 index 5024fd19a5..0000000000 --- a/src/core/load-fragment-gperf.gperf.m4 +++ /dev/null @@ -1,378 +0,0 @@ -%{ -#include -#include "conf-parser.h" -#include "load-fragment.h" -#include "missing.h" -%} -struct ConfigPerfItem; -%null_strings -%language=ANSI-C -%define slot-name section_and_lvalue -%define hash-function-name load_fragment_gperf_hash -%define lookup-function-name load_fragment_gperf_lookup -%readonly-tables -%omit-struct-type -%struct-type -%includes -%% -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.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) -$1.IOSchedulingPriority, config_parse_exec_io_priority, 0, offsetof($1, exec_context) -$1.CPUSchedulingPolicy, config_parse_exec_cpu_sched_policy, 0, offsetof($1, exec_context) -$1.CPUSchedulingPriority, config_parse_exec_cpu_sched_prio, 0, offsetof($1, exec_context) -$1.CPUSchedulingResetOnFork, config_parse_bool, 0, offsetof($1, exec_context.cpu_sched_reset_on_fork) -$1.CPUAffinity, config_parse_exec_cpu_affinity, 0, offsetof($1, exec_context) -$1.UMask, config_parse_mode, 0, offsetof($1, exec_context.umask) -$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.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) -$1.TTYPath, config_parse_unit_path_printf, 0, offsetof($1, exec_context.tty_path) -$1.TTYReset, config_parse_bool, 0, offsetof($1, exec_context.tty_reset) -$1.TTYVHangup, config_parse_bool, 0, offsetof($1, exec_context.tty_vhangup) -$1.TTYVTDisallocate, config_parse_bool, 0, offsetof($1, exec_context.tty_vt_disallocate) -$1.SyslogIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.syslog_identifier) -$1.SyslogFacility, config_parse_log_facility, 0, offsetof($1, exec_context.syslog_priority) -$1.SyslogLevel, config_parse_log_level, 0, offsetof($1, exec_context.syslog_priority) -$1.SyslogLevelPrefix, config_parse_bool, 0, offsetof($1, exec_context.syslog_level_prefix) -$1.Capabilities, config_parse_exec_capabilities, 0, offsetof($1, exec_context) -$1.SecureBits, config_parse_exec_secure_bits, 0, offsetof($1, exec_context) -$1.CapabilityBoundingSet, config_parse_capability_set, 0, offsetof($1, exec_context.capability_bounding_set) -$1.AmbientCapabilities, config_parse_capability_set, 0, offsetof($1, exec_context.capability_ambient_set) -$1.TimerSlackNSec, config_parse_nsec, 0, offsetof($1, exec_context.timer_slack_nsec) -$1.NoNewPrivileges, config_parse_no_new_privileges, 0, offsetof($1, exec_context) -m4_ifdef(`HAVE_SECCOMP', -`$1.SystemCallFilter, config_parse_syscall_filter, 0, offsetof($1, exec_context) -$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.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.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) -$1.LimitDATA, config_parse_limit, RLIMIT_DATA, offsetof($1, exec_context.rlimit) -$1.LimitSTACK, config_parse_limit, RLIMIT_STACK, offsetof($1, exec_context.rlimit) -$1.LimitCORE, config_parse_limit, RLIMIT_CORE, offsetof($1, exec_context.rlimit) -$1.LimitRSS, config_parse_limit, RLIMIT_RSS, offsetof($1, exec_context.rlimit) -$1.LimitNOFILE, config_parse_limit, RLIMIT_NOFILE, offsetof($1, exec_context.rlimit) -$1.LimitAS, config_parse_limit, RLIMIT_AS, offsetof($1, exec_context.rlimit) -$1.LimitNPROC, config_parse_limit, RLIMIT_NPROC, offsetof($1, exec_context.rlimit) -$1.LimitMEMLOCK, config_parse_limit, RLIMIT_MEMLOCK, offsetof($1, exec_context.rlimit) -$1.LimitLOCKS, config_parse_limit, RLIMIT_LOCKS, offsetof($1, exec_context.rlimit) -$1.LimitSIGPENDING, config_parse_limit, RLIMIT_SIGPENDING, offsetof($1, exec_context.rlimit) -$1.LimitMSGQUEUE, config_parse_limit, RLIMIT_MSGQUEUE, offsetof($1, exec_context.rlimit) -$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.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) -$1.ProtectSystem, config_parse_protect_system, 0, offsetof($1, exec_context) -$1.ProtectHome, config_parse_protect_home, 0, offsetof($1, exec_context) -$1.MountFlags, config_parse_exec_mount_flags, 0, offsetof($1, exec_context) -$1.Personality, config_parse_personality, 0, offsetof($1, exec_context.personality) -$1.RuntimeDirectoryMode, config_parse_mode, 0, offsetof($1, exec_context.runtime_directory_mode) -$1.RuntimeDirectory, config_parse_runtime_directory, 0, offsetof($1, exec_context.runtime_directory) -m4_ifdef(`HAVE_PAM', -`$1.PAMName, config_parse_unit_string_printf, 0, offsetof($1, exec_context.pam_name)', -`$1.PAMName, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') -$1.IgnoreSIGPIPE, config_parse_bool, 0, offsetof($1, exec_context.ignore_sigpipe) -$1.UtmpIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.utmp_id) -$1.UtmpMode, config_parse_exec_utmp_mode, 0, offsetof($1, exec_context.utmp_mode) -m4_ifdef(`HAVE_SELINUX', -`$1.SELinuxContext, config_parse_exec_selinux_context, 0, offsetof($1, exec_context)', -`$1.SELinuxContext, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') -m4_ifdef(`HAVE_APPARMOR', -`$1.AppArmorProfile, config_parse_exec_apparmor_profile, 0, offsetof($1, exec_context)', -`$1.AppArmorProfile, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') -m4_ifdef(`HAVE_SMACK', -`$1.SmackProcessLabel, config_parse_exec_smack_process_label, 0, offsetof($1, exec_context)', -`$1.SmackProcessLabel, config_parse_warn_compat, DISABLED_CONFIGURATION, 0')' -)m4_dnl -m4_define(`KILL_CONTEXT_CONFIG_ITEMS', -`$1.SendSIGKILL, config_parse_bool, 0, offsetof($1, kill_context.send_sigkill) -$1.SendSIGHUP, config_parse_bool, 0, offsetof($1, kill_context.send_sighup) -$1.KillMode, config_parse_kill_mode, 0, offsetof($1, kill_context.kill_mode) -$1.KillSignal, config_parse_signal, 0, offsetof($1, kill_context.kill_signal)' -)m4_dnl -m4_define(`CGROUP_CONTEXT_CONFIG_ITEMS', -`$1.Slice, config_parse_unit_slice, 0, 0 -$1.CPUAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.cpu_accounting) -$1.CPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.cpu_shares) -$1.StartupCPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.startup_cpu_shares) -$1.CPUQuota, config_parse_cpu_quota, 0, offsetof($1, cgroup_context) -$1.MemoryAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.memory_accounting) -$1.MemoryLimit, config_parse_memory_limit, 0, offsetof($1, cgroup_context) -$1.DeviceAllow, config_parse_device_allow, 0, offsetof($1, cgroup_context) -$1.DevicePolicy, config_parse_device_policy, 0, offsetof($1, cgroup_context.device_policy) -$1.BlockIOAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.blockio_accounting) -$1.BlockIOWeight, config_parse_blockio_weight, 0, offsetof($1, cgroup_context.blockio_weight) -$1.StartupBlockIOWeight, config_parse_blockio_weight, 0, offsetof($1, cgroup_context.startup_blockio_weight) -$1.BlockIODeviceWeight, config_parse_blockio_device_weight, 0, offsetof($1, cgroup_context) -$1.BlockIOReadBandwidth, config_parse_blockio_bandwidth, 0, offsetof($1, cgroup_context) -$1.BlockIOWriteBandwidth, config_parse_blockio_bandwidth, 0, offsetof($1, cgroup_context) -$1.TasksAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.tasks_accounting) -$1.TasksMax, config_parse_tasks_max, 0, offsetof($1, cgroup_context.tasks_max) -$1.Delegate, config_parse_bool, 0, offsetof($1, cgroup_context.delegate) -$1.NetClass, config_parse_warn_compat, DISABLED_LEGACY, 0' -)m4_dnl -Unit.Description, config_parse_unit_string_printf, 0, offsetof(Unit, description) -Unit.Documentation, config_parse_documentation, 0, offsetof(Unit, documentation) -Unit.SourcePath, config_parse_path, 0, offsetof(Unit, source_path) -Unit.Requires, config_parse_unit_deps, UNIT_REQUIRES, 0 -Unit.Requisite, config_parse_unit_deps, UNIT_REQUISITE, 0 -Unit.Wants, config_parse_unit_deps, UNIT_WANTS, 0 -Unit.BindsTo, config_parse_unit_deps, UNIT_BINDS_TO, 0 -Unit.BindTo, config_parse_unit_deps, UNIT_BINDS_TO, 0 -Unit.Conflicts, config_parse_unit_deps, UNIT_CONFLICTS, 0 -Unit.Before, config_parse_unit_deps, UNIT_BEFORE, 0 -Unit.After, config_parse_unit_deps, UNIT_AFTER, 0 -Unit.OnFailure, config_parse_unit_deps, UNIT_ON_FAILURE, 0 -Unit.PropagatesReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0 -Unit.PropagateReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0 -Unit.ReloadPropagatedFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0 -Unit.PropagateReloadFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0 -Unit.PartOf, config_parse_unit_deps, UNIT_PART_OF, 0 -Unit.JoinsNamespaceOf, config_parse_unit_deps, UNIT_JOINS_NAMESPACE_OF, 0 -Unit.RequiresOverridable, config_parse_obsolete_unit_deps, UNIT_REQUIRES, 0 -Unit.RequisiteOverridable, config_parse_obsolete_unit_deps, UNIT_REQUISITE, 0 -Unit.RequiresMountsFor, config_parse_unit_requires_mounts_for, 0, 0 -Unit.StopWhenUnneeded, config_parse_bool, 0, offsetof(Unit, stop_when_unneeded) -Unit.RefuseManualStart, config_parse_bool, 0, offsetof(Unit, refuse_manual_start) -Unit.RefuseManualStop, config_parse_bool, 0, offsetof(Unit, refuse_manual_stop) -Unit.AllowIsolate, config_parse_bool, 0, offsetof(Unit, allow_isolate) -Unit.DefaultDependencies, config_parse_bool, 0, offsetof(Unit, default_dependencies) -Unit.OnFailureJobMode, config_parse_job_mode, 0, offsetof(Unit, on_failure_job_mode) -Unit.OnFailureIsolate, config_parse_job_mode_isolate, 0, offsetof(Unit, on_failure_job_mode) -Unit.IgnoreOnIsolate, config_parse_bool, 0, offsetof(Unit, ignore_on_isolate) -Unit.IgnoreOnSnapshot, config_parse_warn_compat, DISABLED_LEGACY, 0 -Unit.JobTimeoutSec, config_parse_sec_fix_0, 0, offsetof(Unit, job_timeout) -Unit.JobTimeoutAction, config_parse_failure_action, 0, offsetof(Unit, job_timeout_action) -Unit.JobTimeoutRebootArgument, config_parse_string, 0, offsetof(Unit, job_timeout_reboot_arg) -Unit.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) -Unit.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst) -Unit.StartLimitAction, config_parse_failure_action, 0, offsetof(Unit, start_limit_action) -Unit.RebootArgument, config_parse_string, 0, offsetof(Unit, reboot_arg) -Unit.ConditionPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, conditions) -Unit.ConditionPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, conditions) -Unit.ConditionPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, offsetof(Unit, conditions) -Unit.ConditionPathIsSymbolicLink,config_parse_unit_condition_path, CONDITION_PATH_IS_SYMBOLIC_LINK,offsetof(Unit, conditions) -Unit.ConditionPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, offsetof(Unit, conditions) -Unit.ConditionPathIsReadWrite, config_parse_unit_condition_path, CONDITION_PATH_IS_READ_WRITE, offsetof(Unit, conditions) -Unit.ConditionDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, offsetof(Unit, conditions) -Unit.ConditionFileNotEmpty, config_parse_unit_condition_path, CONDITION_FILE_NOT_EMPTY, offsetof(Unit, conditions) -Unit.ConditionFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, offsetof(Unit, conditions) -Unit.ConditionNeedsUpdate, config_parse_unit_condition_path, CONDITION_NEEDS_UPDATE, offsetof(Unit, conditions) -Unit.ConditionFirstBoot, config_parse_unit_condition_string, CONDITION_FIRST_BOOT, offsetof(Unit, conditions) -Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, conditions) -Unit.ConditionArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, conditions) -Unit.ConditionVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, conditions) -Unit.ConditionSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, offsetof(Unit, conditions) -Unit.ConditionCapability, config_parse_unit_condition_string, CONDITION_CAPABILITY, offsetof(Unit, conditions) -Unit.ConditionHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, conditions) -Unit.ConditionACPower, config_parse_unit_condition_string, CONDITION_AC_POWER, offsetof(Unit, conditions) -Unit.ConditionNull, config_parse_unit_condition_null, 0, offsetof(Unit, conditions) -Unit.AssertPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, asserts) -Unit.AssertPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, asserts) -Unit.AssertPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, offsetof(Unit, asserts) -Unit.AssertPathIsSymbolicLink, config_parse_unit_condition_path, CONDITION_PATH_IS_SYMBOLIC_LINK,offsetof(Unit, asserts) -Unit.AssertPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, offsetof(Unit, asserts) -Unit.AssertPathIsReadWrite, config_parse_unit_condition_path, CONDITION_PATH_IS_READ_WRITE, offsetof(Unit, asserts) -Unit.AssertDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, offsetof(Unit, asserts) -Unit.AssertFileNotEmpty, config_parse_unit_condition_path, CONDITION_FILE_NOT_EMPTY, offsetof(Unit, asserts) -Unit.AssertFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, offsetof(Unit, asserts) -Unit.AssertNeedsUpdate, config_parse_unit_condition_path, CONDITION_NEEDS_UPDATE, offsetof(Unit, asserts) -Unit.AssertFirstBoot, config_parse_unit_condition_string, CONDITION_FIRST_BOOT, offsetof(Unit, asserts) -Unit.AssertKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, asserts) -Unit.AssertArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, asserts) -Unit.AssertVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, asserts) -Unit.AssertSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, offsetof(Unit, asserts) -Unit.AssertCapability, config_parse_unit_condition_string, CONDITION_CAPABILITY, offsetof(Unit, asserts) -Unit.AssertHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, asserts) -Unit.AssertACPower, config_parse_unit_condition_string, CONDITION_AC_POWER, offsetof(Unit, asserts) -Unit.AssertNull, config_parse_unit_condition_null, 0, offsetof(Unit, asserts) -m4_dnl -Service.PIDFile, config_parse_unit_path_printf, 0, offsetof(Service, pid_file) -Service.ExecStartPre, config_parse_exec, SERVICE_EXEC_START_PRE, offsetof(Service, exec_command) -Service.ExecStart, config_parse_exec, SERVICE_EXEC_START, offsetof(Service, exec_command) -Service.ExecStartPost, config_parse_exec, SERVICE_EXEC_START_POST, offsetof(Service, exec_command) -Service.ExecReload, config_parse_exec, SERVICE_EXEC_RELOAD, offsetof(Service, exec_command) -Service.ExecStop, config_parse_exec, SERVICE_EXEC_STOP, offsetof(Service, exec_command) -Service.ExecStopPost, config_parse_exec, SERVICE_EXEC_STOP_POST, offsetof(Service, exec_command) -Service.RestartSec, config_parse_sec, 0, offsetof(Service, restart_usec) -Service.TimeoutSec, config_parse_service_timeout, 0, 0 -Service.TimeoutStartSec, config_parse_service_timeout, 0, 0 -Service.TimeoutStopSec, config_parse_service_timeout, 0, 0 -Service.RuntimeMaxSec, config_parse_sec, 0, offsetof(Service, runtime_max_usec) -Service.WatchdogSec, config_parse_sec, 0, offsetof(Service, watchdog_usec) -Service.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) -Service.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst) -Service.StartLimitAction, config_parse_failure_action, 0, offsetof(Unit, start_limit_action) -Service.RebootArgument, config_parse_string, 0, offsetof(Unit, reboot_arg) -Service.FailureAction, config_parse_failure_action, 0, offsetof(Service, failure_action) -Service.Type, config_parse_service_type, 0, offsetof(Service, type) -Service.Restart, config_parse_service_restart, 0, offsetof(Service, restart) -Service.PermissionsStartOnly, config_parse_bool, 0, offsetof(Service, permissions_start_only) -Service.RootDirectoryStartOnly, config_parse_bool, 0, offsetof(Service, root_directory_start_only) -Service.RemainAfterExit, config_parse_bool, 0, offsetof(Service, remain_after_exit) -Service.GuessMainPID, config_parse_bool, 0, offsetof(Service, guess_main_pid) -Service.RestartPreventExitStatus, config_parse_set_status, 0, offsetof(Service, restart_prevent_status) -Service.RestartForceExitStatus, config_parse_set_status, 0, offsetof(Service, restart_force_status) -Service.SuccessExitStatus, config_parse_set_status, 0, offsetof(Service, success_status) -Service.SysVStartPriority, config_parse_warn_compat, DISABLED_LEGACY, 0 -Service.NonBlocking, config_parse_bool, 0, offsetof(Service, exec_context.non_blocking) -Service.BusName, config_parse_bus_name, 0, offsetof(Service, bus_name) -Service.FileDescriptorStoreMax, config_parse_unsigned, 0, offsetof(Service, n_fd_store_max) -Service.NotifyAccess, config_parse_notify_access, 0, offsetof(Service, notify_access) -Service.Sockets, config_parse_service_sockets, 0, 0 -Service.BusPolicy, config_parse_bus_endpoint_policy, 0, offsetof(Service, exec_context) -Service.USBFunctionDescriptors, config_parse_path, 0, offsetof(Service, usb_function_descriptors) -Service.USBFunctionStrings, config_parse_path, 0, offsetof(Service, usb_function_strings) -EXEC_CONTEXT_CONFIG_ITEMS(Service)m4_dnl -CGROUP_CONTEXT_CONFIG_ITEMS(Service)m4_dnl -KILL_CONTEXT_CONFIG_ITEMS(Service)m4_dnl -m4_dnl -Socket.ListenStream, config_parse_socket_listen, SOCKET_SOCKET, 0 -Socket.ListenDatagram, config_parse_socket_listen, SOCKET_SOCKET, 0 -Socket.ListenSequentialPacket, config_parse_socket_listen, SOCKET_SOCKET, 0 -Socket.ListenFIFO, config_parse_socket_listen, SOCKET_FIFO, 0 -Socket.ListenNetlink, config_parse_socket_listen, SOCKET_SOCKET, 0 -Socket.ListenSpecial, config_parse_socket_listen, SOCKET_SPECIAL, 0 -Socket.ListenMessageQueue, config_parse_socket_listen, SOCKET_MQUEUE, 0 -Socket.ListenUSBFunction, config_parse_socket_listen, SOCKET_USB_FUNCTION, 0 -Socket.SocketProtocol, config_parse_socket_protocol, 0, 0 -Socket.BindIPv6Only, config_parse_socket_bind, 0, 0, -Socket.Backlog, config_parse_unsigned, 0, offsetof(Socket, backlog) -Socket.BindToDevice, config_parse_socket_bindtodevice, 0, 0 -Socket.ExecStartPre, config_parse_exec, SOCKET_EXEC_START_PRE, offsetof(Socket, exec_command) -Socket.ExecStartPost, config_parse_exec, SOCKET_EXEC_START_POST, offsetof(Socket, exec_command) -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.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.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) -Socket.KeepAliveProbes, config_parse_unsigned, 0, offsetof(Socket, keep_alive_cnt) -Socket.DeferAcceptSec, config_parse_sec, 0, offsetof(Socket, defer_accept) -Socket.NoDelay, config_parse_bool, 0, offsetof(Socket, no_delay) -Socket.Priority, config_parse_int, 0, offsetof(Socket, priority) -Socket.ReceiveBuffer, config_parse_iec_size, 0, offsetof(Socket, receive_buffer) -Socket.SendBuffer, config_parse_iec_size, 0, offsetof(Socket, send_buffer) -Socket.IPTOS, config_parse_ip_tos, 0, offsetof(Socket, ip_tos) -Socket.IPTTL, config_parse_int, 0, offsetof(Socket, ip_ttl) -Socket.Mark, config_parse_int, 0, offsetof(Socket, mark) -Socket.PipeSize, config_parse_iec_size, 0, offsetof(Socket, pipe_size) -Socket.FreeBind, config_parse_bool, 0, offsetof(Socket, free_bind) -Socket.Transparent, config_parse_bool, 0, offsetof(Socket, transparent) -Socket.Broadcast, config_parse_bool, 0, offsetof(Socket, broadcast) -Socket.PassCredentials, config_parse_bool, 0, offsetof(Socket, pass_cred) -Socket.PassSecurity, config_parse_bool, 0, offsetof(Socket, pass_sec) -Socket.TCPCongestion, config_parse_string, 0, offsetof(Socket, tcp_congestion) -Socket.ReusePort, config_parse_bool, 0, offsetof(Socket, reuse_port) -Socket.MessageQueueMaxMessages, config_parse_long, 0, offsetof(Socket, mq_maxmsg) -Socket.MessageQueueMessageSize, config_parse_long, 0, offsetof(Socket, mq_msgsize) -Socket.RemoveOnStop, config_parse_bool, 0, offsetof(Socket, remove_on_stop) -Socket.Symlinks, config_parse_unit_path_strv_printf, 0, offsetof(Socket, symlinks) -Socket.FileDescriptorName, config_parse_fdname, 0, 0 -Socket.Service, config_parse_socket_service, 0, 0 -m4_ifdef(`HAVE_SMACK', -`Socket.SmackLabel, config_parse_string, 0, offsetof(Socket, smack) -Socket.SmackLabelIPIn, config_parse_string, 0, offsetof(Socket, smack_ip_in) -Socket.SmackLabelIPOut, config_parse_string, 0, offsetof(Socket, smack_ip_out)', -`Socket.SmackLabel, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 -Socket.SmackLabelIPIn, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 -Socket.SmackLabelIPOut, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') -m4_ifdef(`HAVE_SELINUX', -`Socket.SELinuxContextFromNet, config_parse_bool, 0, offsetof(Socket, selinux_context_from_net)', -`Socket.SELinuxContextFromNet, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') -EXEC_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl -CGROUP_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl -KILL_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl -m4_dnl -BusName.Name, config_parse_string, 0, offsetof(BusName, name) -BusName.Activating, config_parse_bool, 0, offsetof(BusName, activating) -BusName.Service, config_parse_busname_service, 0, 0 -BusName.AllowUser, config_parse_bus_policy, 0, 0 -BusName.AllowGroup, config_parse_bus_policy, 0, 0 -BusName.AllowWorld, config_parse_bus_policy_world, 0, offsetof(BusName, policy_world) -BusName.SELinuxContext, config_parse_exec_selinux_context, 0, 0 -BusName.AcceptFileDescriptors, config_parse_bool, 0, offsetof(BusName, accept_fd) -m4_dnl -Mount.What, config_parse_string, 0, offsetof(Mount, parameters_fragment.what) -Mount.Where, config_parse_path, 0, offsetof(Mount, where) -Mount.Options, config_parse_string, 0, offsetof(Mount, parameters_fragment.options) -Mount.Type, config_parse_string, 0, offsetof(Mount, parameters_fragment.fstype) -Mount.TimeoutSec, config_parse_sec, 0, offsetof(Mount, timeout_usec) -Mount.DirectoryMode, config_parse_mode, 0, offsetof(Mount, directory_mode) -Mount.SloppyOptions, config_parse_bool, 0, offsetof(Mount, sloppy_options) -EXEC_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl -CGROUP_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl -KILL_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl -m4_dnl -Automount.Where, config_parse_path, 0, offsetof(Automount, where) -Automount.DirectoryMode, config_parse_mode, 0, offsetof(Automount, directory_mode) -Automount.TimeoutIdleSec, config_parse_sec, 0, offsetof(Automount, timeout_idle_usec) -m4_dnl -Swap.What, config_parse_path, 0, offsetof(Swap, parameters_fragment.what) -Swap.Priority, config_parse_int, 0, offsetof(Swap, parameters_fragment.priority) -Swap.Options, config_parse_string, 0, offsetof(Swap, parameters_fragment.options) -Swap.TimeoutSec, config_parse_sec, 0, offsetof(Swap, timeout_usec) -EXEC_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl -CGROUP_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl -KILL_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl -m4_dnl -Timer.OnCalendar, config_parse_timer, 0, 0 -Timer.OnActiveSec, config_parse_timer, 0, 0 -Timer.OnBootSec, config_parse_timer, 0, 0 -Timer.OnStartupSec, config_parse_timer, 0, 0 -Timer.OnUnitActiveSec, config_parse_timer, 0, 0 -Timer.OnUnitInactiveSec, config_parse_timer, 0, 0 -Timer.Persistent, config_parse_bool, 0, offsetof(Timer, persistent) -Timer.WakeSystem, config_parse_bool, 0, offsetof(Timer, wake_system) -Timer.RemainAfterElapse, config_parse_bool, 0, offsetof(Timer, remain_after_elapse) -Timer.AccuracySec, config_parse_sec, 0, offsetof(Timer, accuracy_usec) -Timer.RandomizedDelaySec, config_parse_sec, 0, offsetof(Timer, random_usec) -Timer.Unit, config_parse_trigger_unit, 0, 0 -m4_dnl -Path.PathExists, config_parse_path_spec, 0, 0 -Path.PathExistsGlob, config_parse_path_spec, 0, 0 -Path.PathChanged, config_parse_path_spec, 0, 0 -Path.PathModified, config_parse_path_spec, 0, 0 -Path.DirectoryNotEmpty, config_parse_path_spec, 0, 0 -Path.Unit, config_parse_trigger_unit, 0, 0 -Path.MakeDirectory, config_parse_bool, 0, offsetof(Path, make_directory) -Path.DirectoryMode, config_parse_mode, 0, offsetof(Path, directory_mode) -m4_dnl -CGROUP_CONTEXT_CONFIG_ITEMS(Slice)m4_dnl -m4_dnl -CGROUP_CONTEXT_CONFIG_ITEMS(Scope)m4_dnl -KILL_CONTEXT_CONFIG_ITEMS(Scope)m4_dnl -Scope.TimeoutStopSec, config_parse_sec, 0, offsetof(Scope, timeout_stop_usec) -m4_dnl The [Install] section is ignored here. -Install.Alias, NULL, 0, 0 -Install.WantedBy, NULL, 0, 0 -Install.RequiredBy, NULL, 0, 0 -Install.Also, NULL, 0, 0 -Install.DefaultInstance, NULL, 0, 0 diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c deleted file mode 100644 index 8804b3ac41..0000000000 --- a/src/core/load-fragment.c +++ /dev/null @@ -1,3939 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2012 Holger Hans Peter Freyther - - 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 . -***/ - -#include -#include -#include -#include -#ifdef HAVE_SECCOMP -#include -#endif -#include -#include -#include -#include - -#include "af-list.h" -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-internal.h" -#include "bus-util.h" -#include "cap-list.h" -#include "capability-util.h" -#include "cgroup.h" -#include "conf-parser.h" -#include "cpu-set-util.h" -#include "env-util.h" -#include "errno-list.h" -#include "escape.h" -#include "fd-util.h" -#include "fs-util.h" -#include "ioprio.h" -#include "load-fragment.h" -#include "log.h" -#include "missing.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "rlimit-util.h" -#ifdef HAVE_SECCOMP -#include "seccomp-util.h" -#endif -#include "securebits.h" -#include "signal-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit-printf.h" -#include "unit.h" -#include "utf8.h" -#include "web-util.h" - -int config_parse_warn_compat( - 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) { - Disabled reason = ltype; - - switch(reason) { - case DISABLED_CONFIGURATION: - log_syntax(unit, LOG_DEBUG, filename, line, 0, - "Support for option %s= has been disabled at compile time and it is ignored", lvalue); - break; - case DISABLED_LEGACY: - log_syntax(unit, LOG_INFO, filename, line, 0, - "Support for option %s= has been removed and it is ignored", lvalue); - break; - case DISABLED_EXPERIMENTAL: - log_syntax(unit, LOG_INFO, filename, line, 0, - "Support for option %s= has not yet been enabled and it is ignored", lvalue); - break; - }; - - return 0; -} - -int config_parse_unit_deps( - 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) { - - UnitDependency d = ltype; - Unit *u = userdata; - const char *p; - - assert(filename); - assert(lvalue); - assert(rvalue); - - p = rvalue; - for(;;) { - _cleanup_free_ char *word = NULL, *k = NULL; - int r; - - r = extract_first_word(&p, &word, NULL, EXTRACT_RETAIN_ESCAPE); - 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_name_printf(u, word, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - continue; - } - - r = unit_add_dependency_by_name(u, d, k, NULL, true); - if (r < 0) - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add dependency on %s, ignoring: %m", k); - } - - return 0; -} - -int config_parse_obsolete_unit_deps( - 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) { - - log_syntax(unit, LOG_WARNING, filename, line, 0, - "Unit dependency type %s= is obsolete, replacing by %s=, please update your unit file", lvalue, unit_dependency_to_string(ltype)); - - return config_parse_unit_deps(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, userdata); -} - -int config_parse_unit_string_printf( - 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) { - - _cleanup_free_ char *k = NULL; - Unit *u = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - r = unit_full_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); -} - -int config_parse_unit_strv_printf( - 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) { - - Unit *u = userdata; - _cleanup_free_ char *k = NULL; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - r = unit_full_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - return config_parse_strv(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); -} - -int config_parse_unit_path_printf( - 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) { - - _cleanup_free_ char *k = NULL; - Unit *u = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - r = unit_full_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - return config_parse_path(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); -} - -int config_parse_unit_path_strv_printf( - 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 ***x = data; - const char *word, *state; - Unit *u = userdata; - size_t l; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - _cleanup_free_ char *k = NULL; - char t[l+1]; - - memcpy(t, word, l); - t[l] = 0; - - r = unit_full_printf(u, t, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", t); - return 0; - } - - if (!utf8_is_valid(k)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); - return 0; - } - - if (!path_is_absolute(k)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Symlink path %s is not absolute, ignoring: %m", k); - return 0; - } - - path_kill_slashes(k); - - r = strv_push(x, k); - if (r < 0) - return log_oom(); - - k = NULL; - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid syntax, ignoring."); - - return 0; -} - -int config_parse_socket_listen(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) { - - _cleanup_free_ SocketPort *p = NULL; - SocketPort *tail; - Socket *s; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - s = SOCKET(data); - - if (isempty(rvalue)) { - /* An empty assignment removes all ports */ - socket_free_ports(s); - return 0; - } - - p = new0(SocketPort, 1); - if (!p) - return log_oom(); - - if (ltype != SOCKET_SOCKET) { - - p->type = ltype; - r = unit_full_printf(UNIT(s), rvalue, &p->path); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - path_kill_slashes(p->path); - - } else if (streq(lvalue, "ListenNetlink")) { - _cleanup_free_ char *k = NULL; - - p->type = SOCKET_SOCKET; - r = unit_full_printf(UNIT(s), rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - r = socket_address_parse_netlink(&p->address, k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address value, ignoring: %s", rvalue); - return 0; - } - - } else { - _cleanup_free_ char *k = NULL; - - p->type = SOCKET_SOCKET; - r = unit_full_printf(UNIT(s), rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r,"Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - r = socket_address_parse_and_warn(&p->address, k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address value, ignoring: %s", rvalue); - return 0; - } - - if (streq(lvalue, "ListenStream")) - p->address.type = SOCK_STREAM; - else if (streq(lvalue, "ListenDatagram")) - p->address.type = SOCK_DGRAM; - else { - assert(streq(lvalue, "ListenSequentialPacket")); - p->address.type = SOCK_SEQPACKET; - } - - if (socket_address_family(&p->address) != AF_LOCAL && p->address.type == SOCK_SEQPACKET) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Address family not supported, ignoring: %s", rvalue); - return 0; - } - } - - p->fd = -1; - p->auxiliary_fds = NULL; - p->n_auxiliary_fds = 0; - p->socket = s; - - if (s->ports) { - LIST_FIND_TAIL(port, s->ports, tail); - LIST_INSERT_AFTER(port, s->ports, tail, p); - } else - LIST_PREPEND(port, s->ports, p); - p = NULL; - - return 0; -} - -int config_parse_socket_protocol(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) { - Socket *s; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - s = SOCKET(data); - - if (streq(rvalue, "udplite")) - s->socket_protocol = IPPROTO_UDPLITE; - else if (streq(rvalue, "sctp")) - s->socket_protocol = IPPROTO_SCTP; - else { - log_syntax(unit, LOG_ERR, filename, line, 0, "Socket protocol not supported, ignoring: %s", rvalue); - return 0; - } - - return 0; -} - -int config_parse_socket_bind(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) { - - Socket *s; - SocketAddressBindIPv6Only b; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - s = SOCKET(data); - - b = socket_address_bind_ipv6_only_from_string(rvalue); - if (b < 0) { - int r; - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse bind IPv6 only value, ignoring: %s", rvalue); - return 0; - } - - s->bind_ipv6_only = r ? SOCKET_ADDRESS_IPV6_ONLY : SOCKET_ADDRESS_BOTH; - } else - s->bind_ipv6_only = b; - - return 0; -} - -int config_parse_exec_nice(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) { - - ExecContext *c = data; - int priority, r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = safe_atoi(rvalue, &priority); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse nice priority, ignoring: %s", rvalue); - return 0; - } - - if (priority < PRIO_MIN || priority >= PRIO_MAX) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Nice priority out of range, ignoring: %s", rvalue); - return 0; - } - - c->nice = priority; - c->nice_set = true; - - return 0; -} - -int config_parse_exec_oom_score_adjust(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) { - - ExecContext *c = data; - int oa, r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = safe_atoi(rvalue, &oa); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse the OOM score adjust value, ignoring: %s", rvalue); - return 0; - } - - if (oa < OOM_SCORE_ADJ_MIN || oa > OOM_SCORE_ADJ_MAX) { - log_syntax(unit, LOG_ERR, filename, line, 0, "OOM score adjust value out of range, ignoring: %s", rvalue); - return 0; - } - - c->oom_score_adjust = oa; - c->oom_score_adjust_set = true; - - return 0; -} - -int config_parse_exec( - 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) { - - ExecCommand **e = data; - const char *p; - bool semicolon; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(e); - - e += ltype; - rvalue += strspn(rvalue, WHITESPACE); - - if (isempty(rvalue)) { - /* An empty assignment resets the list */ - *e = exec_command_free_list(*e); - return 0; - } - - p = rvalue; - do { - _cleanup_free_ char *path = NULL, *firstword = NULL; - bool separate_argv0 = false, ignore = false; - _cleanup_free_ ExecCommand *nce = NULL; - _cleanup_strv_free_ char **n = NULL; - size_t nlen = 0, nbufsize = 0; - char *f; - int i; - - semicolon = false; - - r = extract_first_word_and_warn(&p, &firstword, WHITESPACE, EXTRACT_QUOTES|EXTRACT_CUNESCAPE, unit, filename, line, rvalue); - if (r <= 0) - 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]. */ - if (*f == '-' && !ignore) - ignore = true; - else if (*f == '@' && !separate_argv0) - separate_argv0 = true; - else - break; - f ++; - } - - if (isempty(f)) { - /* First word is either "-" or "@" with no command. */ - log_syntax(unit, LOG_ERR, filename, line, 0, "Empty path in command line, ignoring: \"%s\"", rvalue); - return 0; - } - if (!string_is_safe(f)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path contains special characters, ignoring: %s", rvalue); - return 0; - } - if (!path_is_absolute(f)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path is not absolute, ignoring: %s", rvalue); - return 0; - } - if (endswith(f, "/")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path specifies a directory, ignoring: %s", rvalue); - return 0; - } - - if (f == firstword) { - path = firstword; - firstword = NULL; - } else { - path = strdup(f); - if (!path) - return log_oom(); - } - - if (!separate_argv0) { - if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) - return log_oom(); - f = strdup(path); - if (!f) - return log_oom(); - n[nlen++] = f; - n[nlen] = NULL; - } - - path_kill_slashes(path); - - while (!isempty(p)) { - _cleanup_free_ char *word = NULL; - - /* Check explicitly for an unquoted semicolon as - * command separator token. */ - if (p[0] == ';' && (!p[1] || strchr(WHITESPACE, p[1]))) { - p ++; - p += strspn(p, WHITESPACE); - semicolon = true; - break; - } - - /* Check for \; explicitly, to not confuse it with \\; - * or "\;" or "\\;" etc. extract_first_word would - * return the same for all of those. */ - if (p[0] == '\\' && p[1] == ';' && (!p[2] || strchr(WHITESPACE, p[2]))) { - p += 2; - p += strspn(p, WHITESPACE); - if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) - return log_oom(); - f = strdup(";"); - if (!f) - return log_oom(); - n[nlen++] = f; - n[nlen] = NULL; - continue; - } - - r = extract_first_word_and_warn(&p, &word, WHITESPACE, EXTRACT_QUOTES|EXTRACT_CUNESCAPE, unit, filename, line, rvalue); - if (r == 0) - break; - else if (r < 0) - return 0; - - if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) - return log_oom(); - n[nlen++] = word; - n[nlen] = NULL; - word = NULL; - } - - if (!n || !n[0]) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Empty executable name or zeroeth argument, ignoring: %s", rvalue); - return 0; - } - - nce = new0(ExecCommand, 1); - if (!nce) - return log_oom(); - - nce->argv = n; - nce->path = path; - nce->ignore = ignore; - - exec_command_append_list(e, nce); - - /* Do not _cleanup_free_ these. */ - n = NULL; - path = NULL; - nce = NULL; - - rvalue = p; - } while (semicolon); - - return 0; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier"); - -int config_parse_socket_bindtodevice(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) { - - Socket *s = data; - char *n; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (rvalue[0] && !streq(rvalue, "*")) { - n = strdup(rvalue); - if (!n) - return log_oom(); - } else - n = NULL; - - free(s->bind_to_device); - s->bind_to_device = n; - - return 0; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_output, exec_output, ExecOutput, "Failed to parse output specifier"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_input, exec_input, ExecInput, "Failed to parse input specifier"); - -int config_parse_exec_io_class(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) { - - ExecContext *c = data; - int x; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - x = ioprio_class_from_string(rvalue); - if (x < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse IO scheduling class, ignoring: %s", rvalue); - return 0; - } - - c->ioprio = IOPRIO_PRIO_VALUE(x, IOPRIO_PRIO_DATA(c->ioprio)); - c->ioprio_set = true; - - return 0; -} - -int config_parse_exec_io_priority(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) { - - ExecContext *c = data; - int i, r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = safe_atoi(rvalue, &i); - if (r < 0 || i < 0 || i >= IOPRIO_BE_NR) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse IO priority, ignoring: %s", rvalue); - return 0; - } - - c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_PRIO_CLASS(c->ioprio), i); - c->ioprio_set = true; - - return 0; -} - -int config_parse_exec_cpu_sched_policy(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) { - - - ExecContext *c = data; - int x; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - x = sched_policy_from_string(rvalue); - if (x < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse CPU scheduling policy, ignoring: %s", rvalue); - return 0; - } - - c->cpu_sched_policy = x; - /* Moving to or from real-time policy? We need to adjust the priority */ - c->cpu_sched_priority = CLAMP(c->cpu_sched_priority, sched_get_priority_min(x), sched_get_priority_max(x)); - c->cpu_sched_set = true; - - return 0; -} - -int config_parse_exec_cpu_sched_prio(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) { - - ExecContext *c = data; - int i, min, max, r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = safe_atoi(rvalue, &i); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CPU scheduling policy, ignoring: %s", rvalue); - return 0; - } - - /* On Linux RR/FIFO range from 1 to 99 and OTHER/BATCH may only be 0 */ - min = sched_get_priority_min(c->cpu_sched_policy); - max = sched_get_priority_max(c->cpu_sched_policy); - - if (i < min || i > max) { - log_syntax(unit, LOG_ERR, filename, line, 0, "CPU scheduling priority is out of range, ignoring: %s", rvalue); - return 0; - } - - c->cpu_sched_priority = i; - c->cpu_sched_set = true; - - return 0; -} - -int config_parse_exec_cpu_affinity(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) { - - ExecContext *c = data; - _cleanup_cpu_free_ cpu_set_t *cpuset = NULL; - int ncpus; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - ncpus = parse_cpu_set_and_warn(rvalue, &cpuset, unit, filename, line, lvalue); - if (ncpus < 0) - return ncpus; - - if (c->cpuset) - CPU_FREE(c->cpuset); - - if (ncpus == 0) - /* An empty assignment resets the CPU list */ - c->cpuset = NULL; - else { - c->cpuset = cpuset; - cpuset = NULL; - } - c->cpuset_ncpus = ncpus; - - return 0; -} - -int config_parse_exec_capabilities(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) { - - ExecContext *c = data; - cap_t cap; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - cap = cap_from_text(rvalue); - if (!cap) { - log_syntax(unit, LOG_ERR, filename, line, errno, "Failed to parse capabilities, ignoring: %s", rvalue); - return 0; - } - - if (c->capabilities) - cap_free(c->capabilities); - c->capabilities = cap; - - return 0; -} - -int config_parse_exec_secure_bits(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) { - - ExecContext *c = data; - size_t l; - const char *word, *state; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* An empty assignment resets the field */ - c->secure_bits = 0; - return 0; - } - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - if (first_word(word, "keep-caps")) - c->secure_bits |= 1<secure_bits |= 1<secure_bits |= 1<secure_bits |= 1<secure_bits |= 1<secure_bits |= 1< replace */ - *capability_set = sum; - else - /* previous data -> merge */ - *capability_set |= sum; - - return 0; -} - -int config_parse_limit( - 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) { - - struct rlimit **rl = data, d = {}; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = rlimit_parse(ltype, rvalue, &d); - if (r == -EILSEQ) { - log_syntax(unit, LOG_WARNING, filename, line, r, "Soft resource limit chosen higher than hard limit, ignoring: %s", rvalue); - return 0; - } - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); - return 0; - } - - if (rl[ltype]) - *rl[ltype] = d; - else { - rl[ltype] = newdup(struct rlimit, &d, 1); - if (!rl[ltype]) - return log_oom(); - } - - return 0; -} - -#ifdef HAVE_SYSV_COMPAT -int config_parse_sysv_priority(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 *priority = data; - int i, r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = safe_atoi(rvalue, &i); - if (r < 0 || i < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse SysV start priority, ignoring: %s", rvalue); - return 0; - } - - *priority = (int) i; - return 0; -} -#endif - -DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode, "Failed to parse utmp mode"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_kill_mode, kill_mode, KillMode, "Failed to parse kill mode"); - -int config_parse_exec_mount_flags(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) { - - - unsigned long flags = 0; - ExecContext *c = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (streq(rvalue, "shared")) - flags = MS_SHARED; - else if (streq(rvalue, "slave")) - flags = MS_SLAVE; - else if (streq(rvalue, "private")) - flags = MS_PRIVATE; - else { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse mount flag %s, ignoring.", rvalue); - return 0; - } - - c->mount_flags = flags; - - return 0; -} - -int config_parse_exec_selinux_context( - 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) { - - ExecContext *c = data; - Unit *u = userdata; - bool ignore; - char *k; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - c->selinux_context = mfree(c->selinux_context); - c->selinux_context_ignore = false; - return 0; - } - - if (rvalue[0] == '-') { - ignore = true; - rvalue++; - } else - ignore = false; - - r = unit_name_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - return 0; - } - - free(c->selinux_context); - c->selinux_context = k; - c->selinux_context_ignore = ignore; - - return 0; -} - -int config_parse_exec_apparmor_profile( - 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) { - - ExecContext *c = data; - Unit *u = userdata; - bool ignore; - char *k; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - c->apparmor_profile = mfree(c->apparmor_profile); - c->apparmor_profile_ignore = false; - return 0; - } - - if (rvalue[0] == '-') { - ignore = true; - rvalue++; - } else - ignore = false; - - r = unit_name_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - return 0; - } - - free(c->apparmor_profile); - c->apparmor_profile = k; - c->apparmor_profile_ignore = ignore; - - return 0; -} - -int config_parse_exec_smack_process_label( - 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) { - - ExecContext *c = data; - Unit *u = userdata; - bool ignore; - char *k; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - c->smack_process_label = mfree(c->smack_process_label); - c->smack_process_label_ignore = false; - return 0; - } - - if (rvalue[0] == '-') { - ignore = true; - rvalue++; - } else - ignore = false; - - r = unit_name_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - return 0; - } - - free(c->smack_process_label); - c->smack_process_label = k; - c->smack_process_label_ignore = ignore; - - return 0; -} - -int config_parse_timer(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) { - - Timer *t = data; - usec_t u = 0; - TimerValue *v; - TimerBase b; - CalendarSpec *c = NULL; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets list */ - timer_free_values(t); - return 0; - } - - b = timer_base_from_string(lvalue); - if (b < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse timer base, ignoring: %s", lvalue); - return 0; - } - - if (b == TIMER_CALENDAR) { - if (calendar_spec_from_string(rvalue, &c) < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse calendar specification, ignoring: %s", rvalue); - return 0; - } - } else { - if (parse_sec(rvalue, &u) < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse timer value, ignoring: %s", rvalue); - return 0; - } - } - - v = new0(TimerValue, 1); - if (!v) { - calendar_spec_free(c); - return log_oom(); - } - - v->base = b; - v->value = u; - v->calendar_spec = c; - - LIST_PREPEND(value, t->values, v); - - return 0; -} - -int config_parse_trigger_unit( - 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) { - - _cleanup_free_ char *p = NULL; - Unit *u = data; - UnitType type; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (!set_isempty(u->dependencies[UNIT_TRIGGERS])) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Multiple units to trigger specified, ignoring: %s", rvalue); - return 0; - } - - r = unit_name_printf(u, rvalue, &p); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - return 0; - } - - type = unit_name_to_type(p); - if (type < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Unit type not valid, ignoring: %s", rvalue); - return 0; - } - - if (type == u->type) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Trigger cannot be of same type, ignoring: %s", rvalue); - return 0; - } - - r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, UNIT_TRIGGERS, p, NULL, true); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add trigger on %s, ignoring: %m", p); - return 0; - } - - return 0; -} - -int config_parse_path_spec(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) { - - Path *p = data; - PathSpec *s; - PathType b; - _cleanup_free_ char *k = NULL; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment clears list */ - path_free_specs(p); - return 0; - } - - b = path_type_from_string(lvalue); - if (b < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse path type, ignoring: %s", lvalue); - return 0; - } - - r = unit_full_printf(UNIT(p), rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); - return 0; - } - - if (!path_is_absolute(k)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Path is not absolute, ignoring: %s", k); - return 0; - } - - s = new0(PathSpec, 1); - if (!s) - return log_oom(); - - s->unit = UNIT(p); - s->path = path_kill_slashes(k); - k = NULL; - s->type = b; - s->inotify_fd = -1; - - LIST_PREPEND(spec, p->specs, s); - - return 0; -} - -int config_parse_socket_service( - 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) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *p = NULL; - Socket *s = data; - Unit *x; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = unit_name_printf(UNIT(s), rvalue, &p); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - - if (!endswith(p, ".service")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type service, ignoring: %s", rvalue); - return 0; - } - - r = manager_load_unit(UNIT(s)->manager, p, NULL, &error, &x); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load unit %s, ignoring: %s", rvalue, bus_error_message(&error, r)); - return 0; - } - - unit_ref_set(&s->service, x); - - return 0; -} - -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) { - - _cleanup_free_ char *p = NULL; - Socket *s = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - s->fdname = mfree(s->fdname); - return 0; - } - - r = unit_name_printf(UNIT(s), rvalue, &p); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - - if (!fdname_is_valid(p)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name, ignoring: %s", p); - return 0; - } - - free(s->fdname); - s->fdname = p; - p = NULL; - - return 0; -} - -int config_parse_service_sockets( - 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) { - - Service *s = data; - const char *p; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - p = rvalue; - for(;;) { - _cleanup_free_ char *word = NULL, *k = NULL; - - r = extract_first_word(&p, &word, NULL, 0); - if (r == 0) - break; - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Trailing garbage in sockets, ignoring: %s", rvalue); - break; - } - - r = unit_name_printf(UNIT(s), word, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - continue; - } - - if (!endswith(k, ".socket")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type socket, ignoring: %s", k); - continue; - } - - r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_WANTS, UNIT_AFTER, k, NULL, true); - if (r < 0) - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add dependency on %s, ignoring: %m", k); - - r = unit_add_dependency_by_name(UNIT(s), UNIT_TRIGGERED_BY, k, NULL, true); - if (r < 0) - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add dependency on %s, ignoring: %m", k); - } - - return 0; -} - -int config_parse_bus_name( - 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) { - - _cleanup_free_ char *k = NULL; - Unit *u = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - r = unit_full_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); - return 0; - } - - if (!service_name_is_valid(k)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid bus name %s, ignoring.", k); - return 0; - } - - return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); -} - -int config_parse_service_timeout( - 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) { - - Service *s = userdata; - usec_t usec; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(s); - - /* This is called for three cases: TimeoutSec=, TimeoutStopSec= and TimeoutStartSec=. */ - - r = parse_sec(rvalue, &usec); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s= parameter, ignoring: %s", lvalue, rvalue); - return 0; - } - - /* Traditionally, these options accepted 0 to disable the timeouts. However, a timeout of 0 suggests it happens - * immediately, hence fix this to become USEC_INFINITY instead. This is in-line with how we internally handle - * all other timeouts. */ - if (usec <= 0) - usec = USEC_INFINITY; - - if (!streq(lvalue, "TimeoutStopSec")) { - s->start_timeout_defined = true; - s->timeout_start_usec = usec; - } - - if (!streq(lvalue, "TimeoutStartSec")) - s->timeout_stop_usec = usec; - - return 0; -} - -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) { - - usec_t *usec = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(usec); - - /* This is pretty much like config_parse_sec(), except that this treats a time of 0 as infinity, for - * compatibility with older versions of systemd where 0 instead of infinity was used as indicator to turn off a - * timeout. */ - - r = parse_sec(rvalue, usec); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s= parameter, ignoring: %s", lvalue, rvalue); - return 0; - } - - if (*usec <= 0) - *usec = USEC_INFINITY; - - return 0; -} - -int config_parse_busname_service( - 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) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - BusName *n = data; - int r; - Unit *x; - _cleanup_free_ char *p = NULL; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = unit_name_printf(UNIT(n), rvalue, &p); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - - if (!endswith(p, ".service")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type service, ignoring: %s", rvalue); - return 0; - } - - r = manager_load_unit(UNIT(n)->manager, p, NULL, &error, &x); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load unit %s, ignoring: %s", rvalue, bus_error_message(&error, r)); - return 0; - } - - unit_ref_set(&n->service, x); - - return 0; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_bus_policy_world, bus_policy_access, BusPolicyAccess, "Failed to parse bus name policy access"); - -int config_parse_bus_policy( - 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) { - - _cleanup_free_ BusNamePolicy *p = NULL; - _cleanup_free_ char *id_str = NULL; - BusName *busname = data; - char *access_str; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - p = new0(BusNamePolicy, 1); - if (!p) - return log_oom(); - - if (streq(lvalue, "AllowUser")) - p->type = BUSNAME_POLICY_TYPE_USER; - else if (streq(lvalue, "AllowGroup")) - p->type = BUSNAME_POLICY_TYPE_GROUP; - else - assert_not_reached("Unknown lvalue"); - - id_str = strdup(rvalue); - if (!id_str) - return log_oom(); - - access_str = strpbrk(id_str, WHITESPACE); - if (!access_str) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid busname policy value '%s'", rvalue); - return 0; - } - - *access_str = '\0'; - access_str++; - access_str += strspn(access_str, WHITESPACE); - - p->access = bus_policy_access_from_string(access_str); - if (p->access < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid busname policy access type '%s'", access_str); - return 0; - } - - p->name = id_str; - id_str = NULL; - - LIST_PREPEND(policy, busname->policy, p); - p = NULL; - - return 0; -} - -int config_parse_bus_endpoint_policy( - 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) { - - _cleanup_free_ char *name = NULL; - BusPolicyAccess access; - ExecContext *c = data; - char *access_str; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - name = strdup(rvalue); - if (!name) - return log_oom(); - - access_str = strpbrk(name, WHITESPACE); - if (!access_str) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid endpoint policy value '%s'", rvalue); - return 0; - } - - *access_str = '\0'; - access_str++; - access_str += strspn(access_str, WHITESPACE); - - access = bus_policy_access_from_string(access_str); - if (access <= _BUS_POLICY_ACCESS_INVALID || - access >= _BUS_POLICY_ACCESS_MAX) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid endpoint policy access type '%s'", access_str); - return 0; - } - - if (!c->bus_endpoint) { - r = bus_endpoint_new(&c->bus_endpoint); - if (r < 0) - return log_error_errno(r, "Failed to create bus endpoint object: %m"); - } - - return bus_endpoint_add_policy(c->bus_endpoint, name, access); -} - -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) { - - ExecContext *c = data; - Unit *u = userdata; - bool missing_ok; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(c); - assert(u); - - if (rvalue[0] == '-') { - missing_ok = true; - rvalue++; - } else - missing_ok = false; - - if (streq(rvalue, "~")) { - c->working_directory_home = true; - c->working_directory = mfree(c->working_directory); - } 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 working directory path '%s', ignoring: %m", rvalue); - return 0; - } - - path_kill_slashes(k); - - if (!utf8_is_valid(k)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); - return 0; - } - - if (!path_is_absolute(k)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Working directory path '%s' is not absolute, ignoring.", rvalue); - return 0; - } - - free(c->working_directory); - c->working_directory = k; - k = NULL; - - c->working_directory_home = false; - } - - c->working_directory_missing_ok = missing_ok; - return 0; -} - -int config_parse_unit_env_file(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 ***env = data; - Unit *u = userdata; - _cleanup_free_ char *n = NULL; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment frees the list */ - *env = strv_free(*env); - return 0; - } - - r = unit_full_printf(u, rvalue, &n); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - - if (!path_is_absolute(n[0] == '-' ? n + 1 : n)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Path '%s' is not absolute, ignoring.", n); - return 0; - } - - r = strv_extend(env, n); - if (r < 0) - return log_oom(); - - return 0; -} - -int config_parse_environ(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) { - - Unit *u = userdata; - char*** env = data; - const char *word, *state; - size_t l; - _cleanup_free_ char *k = NULL; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *env = strv_free(*env); - return 0; - } - - if (u) { - r = unit_full_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - } - - if (!k) { - k = strdup(rvalue); - if (!k) - return log_oom(); - } - - FOREACH_WORD_QUOTED(word, l, k, state) { - _cleanup_free_ char *n = NULL; - char **x; - - r = cunescape_length(word, l, 0, &n); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Couldn't unescape assignment, ignoring: %s", rvalue); - continue; - } - - if (!env_assignment_is_valid(n)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid environment assignment, ignoring: %s", rvalue); - continue; - } - - x = strv_env_set(*env, n); - if (!x) - return log_oom(); - - strv_free(*env); - *env = x; - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} - -int config_parse_pass_environ(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) { - - const char *whole_rvalue = rvalue; - char*** passenv = data; - _cleanup_strv_free_ char **n = NULL; - size_t nlen = 0, nbufsize = 0; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *passenv = strv_free(*passenv); - return 0; - } - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES); - if (r == 0) - break; - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, - "Trailing garbage in %s, ignoring: %s", lvalue, whole_rvalue); - break; - } - - if (!env_name_is_valid(word)) { - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Invalid environment name for %s, ignoring: %s", lvalue, word); - continue; - } - - if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) - return log_oom(); - n[nlen++] = word; - n[nlen] = NULL; - word = NULL; - } - - if (n) { - r = strv_extend_strv(passenv, n, true); - if (r < 0) - return r; - } - - return 0; -} - -int config_parse_ip_tos(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 *ip_tos = data, x; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - x = ip_tos_from_string(rvalue); - if (x < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse IP TOS value, ignoring: %s", rvalue); - return 0; - } - - *ip_tos = x; - return 0; -} - -int config_parse_unit_condition_path( - 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) { - - _cleanup_free_ char *p = NULL; - Condition **list = data, *c; - ConditionType t = ltype; - bool trigger, negate; - Unit *u = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *list = condition_free_list(*list); - return 0; - } - - trigger = rvalue[0] == '|'; - if (trigger) - rvalue++; - - negate = rvalue[0] == '!'; - if (negate) - rvalue++; - - r = unit_full_printf(u, rvalue, &p); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - - if (!path_is_absolute(p)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Path in condition not absolute, ignoring: %s", p); - return 0; - } - - c = condition_new(t, p, trigger, negate); - if (!c) - return log_oom(); - - LIST_PREPEND(conditions, *list, c); - return 0; -} - -int config_parse_unit_condition_string( - 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) { - - _cleanup_free_ char *s = NULL; - Condition **list = data, *c; - ConditionType t = ltype; - bool trigger, negate; - Unit *u = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *list = condition_free_list(*list); - return 0; - } - - trigger = rvalue[0] == '|'; - if (trigger) - rvalue++; - - negate = rvalue[0] == '!'; - if (negate) - rvalue++; - - r = unit_full_printf(u, rvalue, &s); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); - return 0; - } - - c = condition_new(t, s, trigger, negate); - if (!c) - return log_oom(); - - LIST_PREPEND(conditions, *list, c); - return 0; -} - -int config_parse_unit_condition_null( - 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) { - - Condition **list = data, *c; - bool trigger, negate; - int b; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *list = condition_free_list(*list); - return 0; - } - - trigger = rvalue[0] == '|'; - if (trigger) - rvalue++; - - negate = rvalue[0] == '!'; - if (negate) - rvalue++; - - b = parse_boolean(rvalue); - if (b < 0) { - log_syntax(unit, LOG_ERR, filename, line, b, "Failed to parse boolean value in condition, ignoring: %s", rvalue); - return 0; - } - - if (!b) - negate = !negate; - - c = condition_new(CONDITION_NULL, NULL, trigger, negate); - if (!c) - return log_oom(); - - LIST_PREPEND(conditions, *list, c); - return 0; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_failure_action, failure_action, FailureAction, "Failed to parse failure action specifier"); - -int config_parse_unit_requires_mounts_for( - 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) { - - Unit *u = userdata; - const char *word, *state; - size_t l; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - int r; - _cleanup_free_ char *n; - - n = strndup(word, l); - if (!n) - return log_oom(); - - if (!utf8_is_valid(n)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); - continue; - } - - r = unit_require_mounts_for(u, n); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add required mount for, ignoring: %s", rvalue); - continue; - } - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} - -int config_parse_documentation(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) { - - Unit *u = userdata; - int r; - char **a, **b; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - u->documentation = strv_free(u->documentation); - return 0; - } - - r = config_parse_unit_strv_printf(unit, filename, line, section, section_line, lvalue, ltype, - rvalue, data, userdata); - if (r < 0) - return r; - - for (a = b = u->documentation; a && *a; a++) { - - if (documentation_url_is_valid(*a)) - *(b++) = *a; - else { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid URL, ignoring: %s", *a); - free(*a); - } - } - if (b) - *b = NULL; - - return r; -} - -#ifdef HAVE_SECCOMP -int config_parse_syscall_filter( - 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) { - - static const char default_syscalls[] = - "execve\0" - "exit\0" - "exit_group\0" - "rt_sigreturn\0" - "sigreturn\0"; - - ExecContext *c = data; - Unit *u = userdata; - bool invert = false; - const char *word, *state; - size_t l; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - c->syscall_filter = set_free(c->syscall_filter); - c->syscall_whitelist = false; - return 0; - } - - if (rvalue[0] == '~') { - invert = true; - rvalue++; - } - - if (!c->syscall_filter) { - c->syscall_filter = set_new(NULL); - if (!c->syscall_filter) - return log_oom(); - - if (invert) - /* Allow everything but the ones listed */ - c->syscall_whitelist = false; - else { - const char *i; - - /* Allow nothing but the ones listed */ - c->syscall_whitelist = true; - - /* Accept default syscalls if we are on a whitelist */ - NULSTR_FOREACH(i, default_syscalls) { - int id; - - id = seccomp_syscall_resolve_name(i); - if (id < 0) - continue; - - r = set_put(c->syscall_filter, INT_TO_PTR(id + 1)); - if (r == 0) - continue; - if (r < 0) - return log_oom(); - } - } - } - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - _cleanup_free_ char *t = NULL; - int id; - - t = strndup(word, l); - if (!t) - return log_oom(); - - id = seccomp_syscall_resolve_name(t); - if (id < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse system call, ignoring: %s", t); - continue; - } - - /* If we previously wanted to forbid a syscall and now - * we want to allow it, then remove it from the list - */ - if (!invert == c->syscall_whitelist) { - r = set_put(c->syscall_filter, INT_TO_PTR(id + 1)); - if (r == 0) - continue; - if (r < 0) - return log_oom(); - } else - set_remove(c->syscall_filter, INT_TO_PTR(id + 1)); - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - /* Turn on NNP, but only if it wasn't configured explicitly - * before, and only if we are in user mode. */ - if (!c->no_new_privileges_set && u->manager->running_as == MANAGER_USER) - c->no_new_privileges = true; - - return 0; -} - -int config_parse_syscall_archs( - 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) { - - Set **archs = data; - const char *word, *state; - size_t l; - int r; - - if (isempty(rvalue)) { - *archs = set_free(*archs); - return 0; - } - - r = set_ensure_allocated(archs, NULL); - if (r < 0) - return log_oom(); - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - _cleanup_free_ char *t = NULL; - uint32_t a; - - t = strndup(word, l); - if (!t) - return log_oom(); - - r = seccomp_arch_from_string(t, &a); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse system call architecture, ignoring: %s", t); - continue; - } - - r = set_put(*archs, UINT32_TO_PTR(a + 1)); - if (r == 0) - continue; - if (r < 0) - return log_oom(); - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} - -int config_parse_syscall_errno( - 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) { - - ExecContext *c = data; - int e; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - /* Empty assignment resets to KILL */ - c->syscall_errno = 0; - return 0; - } - - e = errno_from_name(rvalue); - if (e < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse error number, ignoring: %s", rvalue); - return 0; - } - - c->syscall_errno = e; - return 0; -} - -int config_parse_address_families( - 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) { - - ExecContext *c = data; - bool invert = false; - const char *word, *state; - size_t l; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - c->address_families = set_free(c->address_families); - c->address_families_whitelist = false; - return 0; - } - - if (rvalue[0] == '~') { - invert = true; - rvalue++; - } - - if (!c->address_families) { - c->address_families = set_new(NULL); - if (!c->address_families) - return log_oom(); - - c->address_families_whitelist = !invert; - } - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - _cleanup_free_ char *t = NULL; - int af; - - t = strndup(word, l); - if (!t) - return log_oom(); - - af = af_from_name(t); - if (af <= 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse address family, ignoring: %s", t); - continue; - } - - /* If we previously wanted to forbid an address family and now - * we want to allow it, then remove it from the list - */ - if (!invert == c->address_families_whitelist) { - r = set_put(c->address_families, INT_TO_PTR(af)); - if (r == 0) - continue; - if (r < 0) - return log_oom(); - } else - set_remove(c->address_families, INT_TO_PTR(af)); - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} -#endif - -int config_parse_unit_slice( - 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) { - - _cleanup_free_ char *k = NULL; - Unit *u = userdata, *slice = NULL; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(u); - - r = unit_name_printf(u, rvalue, &k); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); - return 0; - } - - r = manager_load_unit(u->manager, k, NULL, NULL, &slice); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load slice unit %s. Ignoring.", k); - return 0; - } - - r = unit_set_slice(u, slice); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to assign slice %s to unit %s. Ignoring.", slice->id, u->id); - return 0; - } - - return 0; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_device_policy, cgroup_device_policy, CGroupDevicePolicy, "Failed to parse device policy"); - -int config_parse_cpu_shares( - 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 *shares = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = cg_cpu_shares_parse(rvalue, shares); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "CPU shares '%s' invalid. Ignoring.", rvalue); - return 0; - } - - return 0; -} - -int config_parse_cpu_quota( - 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) { - - CGroupContext *c = data; - double percent; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - c->cpu_quota_per_sec_usec = USEC_INFINITY; - return 0; - } - - if (!endswith(rvalue, "%")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "CPU quota '%s' not ending in '%%'. 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); - - return 0; -} - -int config_parse_memory_limit( - 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) { - - CGroupContext *c = data; - uint64_t bytes; - int r; - - if (isempty(rvalue) || streq(rvalue, "infinity")) { - c->memory_limit = (uint64_t) -1; - return 0; - } - - 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); - return 0; - } - - c->memory_limit = bytes; - return 0; -} - -int config_parse_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 *tasks_max = data, u; - int r; - - if (isempty(rvalue) || streq(rvalue, "infinity")) { - *tasks_max = (uint64_t) -1; - 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); - return 0; - } - - *tasks_max = u; - return 0; -} - -int config_parse_device_allow( - 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) { - - _cleanup_free_ char *path = NULL; - CGroupContext *c = data; - CGroupDeviceAllow *a; - const char *m; - size_t n; - - if (isempty(rvalue)) { - while (c->device_allow) - cgroup_context_free_device_allow(c, c->device_allow); - - return 0; - } - - n = strcspn(rvalue, WHITESPACE); - path = strndup(rvalue, n); - if (!path) - return log_oom(); - - if (!startswith(path, "/dev/") && - !startswith(path, "block-") && - !startswith(path, "char-")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); - return 0; - } - - m = rvalue + n + strspn(rvalue + n, WHITESPACE); - if (isempty(m)) - m = "rwm"; - - if (!in_charset(m, "rwm")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device rights '%s'. Ignoring.", m); - return 0; - } - - a = new0(CGroupDeviceAllow, 1); - if (!a) - return log_oom(); - - a->path = path; - path = NULL; - a->r = !!strchr(m, 'r'); - a->w = !!strchr(m, 'w'); - a->m = !!strchr(m, 'm'); - - LIST_PREPEND(device_allow, c->device_allow, a); - return 0; -} - -int config_parse_blockio_weight( - 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 *weight = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = cg_blkio_weight_parse(rvalue, weight); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Block IO weight '%s' invalid. Ignoring.", rvalue); - return 0; - } - - return 0; -} - -int config_parse_blockio_device_weight( - 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) { - - _cleanup_free_ char *path = NULL; - CGroupBlockIODeviceWeight *w; - CGroupContext *c = data; - const char *weight; - uint64_t u; - size_t n; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) { - while (c->blockio_device_weights) - cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); - - return 0; - } - - n = strcspn(rvalue, WHITESPACE); - weight = rvalue + n; - weight += strspn(weight, WHITESPACE); - - if (isempty(weight)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Expected block device and device weight. Ignoring."); - return 0; - } - - path = strndup(rvalue, n); - if (!path) - return log_oom(); - - if (!path_startswith(path, "/dev")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); - return 0; - } - - r = cg_blkio_weight_parse(weight, &u); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Block IO weight '%s' invalid. Ignoring.", weight); - return 0; - } - - assert(u != CGROUP_BLKIO_WEIGHT_INVALID); - - w = new0(CGroupBlockIODeviceWeight, 1); - if (!w) - return log_oom(); - - w->path = path; - path = NULL; - - w->weight = u; - - LIST_PREPEND(device_weights, c->blockio_device_weights, w); - return 0; -} - -int config_parse_blockio_bandwidth( - 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) { - - _cleanup_free_ char *path = NULL; - CGroupBlockIODeviceBandwidth *b; - CGroupContext *c = data; - const char *bandwidth; - uint64_t bytes; - bool read; - size_t n; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - read = streq("BlockIOReadBandwidth", lvalue); - - if (isempty(rvalue)) { - CGroupBlockIODeviceBandwidth *next; - - LIST_FOREACH_SAFE (device_bandwidths, b, next, c->blockio_device_bandwidths) - if (b->read == read) - cgroup_context_free_blockio_device_bandwidth(c, b); - - return 0; - } - - n = strcspn(rvalue, WHITESPACE); - bandwidth = rvalue + n; - bandwidth += strspn(bandwidth, WHITESPACE); - - if (!*bandwidth) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Expected space separated pair of device node and bandwidth. Ignoring."); - return 0; - } - - path = strndup(rvalue, n); - if (!path) - return log_oom(); - - if (!path_startswith(path, "/dev")) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); - return 0; - } - - r = parse_size(bandwidth, 1000, &bytes); - if (r < 0 || bytes <= 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Block IO Bandwidth '%s' invalid. Ignoring.", rvalue); - return 0; - } - - b = new0(CGroupBlockIODeviceBandwidth, 1); - if (!b) - return log_oom(); - - b->path = path; - path = NULL; - b->bandwidth = bytes; - b->read = read; - - LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, b); - - return 0; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_job_mode, job_mode, JobMode, "Failed to parse job mode"); - -int config_parse_job_mode_isolate( - 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) { - - JobMode *m = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse boolean, ignoring: %s", rvalue); - return 0; - } - - *m = r ? JOB_ISOLATE : JOB_REPLACE; - return 0; -} - -int config_parse_runtime_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) { - - char***rt = data; - Unit *u = userdata; - const char *word, *state; - size_t l; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *rt = strv_free(*rt); - return 0; - } - - FOREACH_WORD_QUOTED(word, l, rvalue, state) { - _cleanup_free_ char *t = NULL, *n = NULL; - - t = strndup(word, l); - if (!t) - return log_oom(); - - r = unit_name_printf(u, t, &n); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); - continue; - } - - if (!filename_is_valid(n)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Runtime directory is not valid, ignoring assignment: %s", rvalue); - continue; - } - - r = strv_push(rt, n); - if (r < 0) - return log_oom(); - - n = NULL; - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} - -int config_parse_set_status( - 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) { - - size_t l; - const char *word, *state; - int r; - ExitStatusSet *status_set = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - /* Empty assignment resets the list */ - if (isempty(rvalue)) { - exit_status_set_free(status_set); - return 0; - } - - FOREACH_WORD(word, l, rvalue, state) { - _cleanup_free_ char *temp; - int val; - Set **set; - - temp = strndup(word, l); - if (!temp) - return log_oom(); - - r = safe_atoi(temp, &val); - if (r < 0) { - val = signal_from_string_try_harder(temp); - - if (val <= 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse value, ignoring: %s", word); - continue; - } - set = &status_set->signal; - } else { - if (val < 0 || val > 255) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Value %d is outside range 0-255, ignoring", val); - continue; - } - set = &status_set->status; - } - - r = set_ensure_allocated(set, NULL); - if (r < 0) - return log_oom(); - - r = set_put(*set, INT_TO_PTR(val)); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Unable to store: %s", word); - return r; - } - } - if (!isempty(state)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} - -int config_parse_namespace_path_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*** sv = data; - const char *prev; - const char *cur; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - /* Empty assignment resets the list */ - *sv = strv_free(*sv); - return 0; - } - - prev = cur = rvalue; - for (;;) { - _cleanup_free_ char *word = NULL; - int offset; - - r = extract_first_word(&cur, &word, NULL, EXTRACT_QUOTES); - if (r == 0) - break; - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Trailing garbage, ignoring: %s", prev); - return 0; - } - - if (!utf8_is_valid(word)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, word); - prev = cur; - continue; - } - - offset = word[0] == '-'; - if (!path_is_absolute(word + offset)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute path, ignoring: %s", word); - prev = cur; - continue; - } - - path_kill_slashes(word + offset); - - r = strv_push(sv, word); - if (r < 0) - return log_oom(); - - prev = cur; - word = NULL; - } - - return 0; -} - -int config_parse_no_new_privileges( - 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) { - - ExecContext *c = data; - int k; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - k = parse_boolean(rvalue); - if (k < 0) { - log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue); - return 0; - } - - c->no_new_privileges = !!k; - c->no_new_privileges_set = true; - - return 0; -} - -int config_parse_protect_home( - 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) { - - ExecContext *c = data; - int k; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - /* Our enum shall be a superset of booleans, hence first try - * to parse as as boolean, and then as enum */ - - k = parse_boolean(rvalue); - if (k > 0) - c->protect_home = PROTECT_HOME_YES; - else if (k == 0) - c->protect_home = PROTECT_HOME_NO; - else { - ProtectHome h; - - h = protect_home_from_string(rvalue); - if (h < 0){ - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse protect home value, ignoring: %s", rvalue); - return 0; - } - - c->protect_home = h; - } - - return 0; -} - -int config_parse_protect_system( - 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) { - - ExecContext *c = data; - int k; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - /* Our enum shall be a superset of booleans, hence first try - * to parse as as boolean, and then as enum */ - - k = parse_boolean(rvalue); - if (k > 0) - c->protect_system = PROTECT_SYSTEM_YES; - else if (k == 0) - c->protect_system = PROTECT_SYSTEM_NO; - else { - ProtectSystem s; - - s = protect_system_from_string(rvalue); - if (s < 0){ - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse protect system value, ignoring: %s", rvalue); - return 0; - } - - c->protect_system = s; - } - - return 0; -} - -#define FOLLOW_MAX 8 - -static int open_follow(char **filename, FILE **_f, Set *names, char **_final) { - unsigned c = 0; - int fd, r; - FILE *f; - char *id = NULL; - - assert(filename); - assert(*filename); - assert(_f); - assert(names); - - /* This will update the filename pointer if the loaded file is - * reached by a symlink. The old string will be freed. */ - - for (;;) { - char *target, *name; - - if (c++ >= FOLLOW_MAX) - return -ELOOP; - - path_kill_slashes(*filename); - - /* Add the file name we are currently looking at to - * the names of this unit, but only if it is a valid - * unit name. */ - name = basename(*filename); - - if (unit_name_is_valid(name, UNIT_NAME_ANY)) { - - id = set_get(names, name); - if (!id) { - id = strdup(name); - if (!id) - return -ENOMEM; - - r = set_consume(names, id); - if (r < 0) - return r; - } - } - - /* Try to open the file name, but don't if its a symlink */ - fd = open(*filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd >= 0) - break; - - if (errno != ELOOP) - return -errno; - - /* Hmm, so this is a symlink. Let's read the name, and follow it manually */ - r = readlink_and_make_absolute(*filename, &target); - if (r < 0) - return r; - - free(*filename); - *filename = target; - } - - f = fdopen(fd, "re"); - if (!f) { - safe_close(fd); - return -errno; - } - - *_f = f; - *_final = id; - return 0; -} - -static int merge_by_names(Unit **u, Set *names, const char *id) { - char *k; - int r; - - assert(u); - assert(*u); - assert(names); - - /* Let's try to add in all symlink names we found */ - while ((k = set_steal_first(names))) { - - /* First try to merge in the other name into our - * unit */ - r = unit_merge_by_name(*u, k); - if (r < 0) { - Unit *other; - - /* Hmm, we couldn't merge the other unit into - * ours? Then let's try it the other way - * round */ - - other = manager_get_unit((*u)->manager, k); - free(k); - - if (other) { - r = unit_merge(other, *u); - if (r >= 0) { - *u = other; - return merge_by_names(u, names, NULL); - } - } - - return r; - } - - if (id == k) - unit_choose_id(*u, id); - - free(k); - } - - return 0; -} - -static int load_from_path(Unit *u, const char *path) { - int r; - _cleanup_set_free_free_ Set *symlink_names = NULL; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *filename = NULL; - char *id = NULL; - Unit *merged; - struct stat st; - - assert(u); - assert(path); - - symlink_names = set_new(&string_hash_ops); - if (!symlink_names) - return -ENOMEM; - - if (path_is_absolute(path)) { - - filename = strdup(path); - if (!filename) - return -ENOMEM; - - r = open_follow(&filename, &f, symlink_names, &id); - if (r < 0) { - filename = mfree(filename); - if (r != -ENOENT) - return r; - } - - } else { - char **p; - - STRV_FOREACH(p, u->manager->lookup_paths.unit_path) { - - /* Instead of opening the path right away, we manually - * follow all symlinks and add their name to our unit - * name set while doing so */ - filename = path_make_absolute(path, *p); - if (!filename) - return -ENOMEM; - - if (u->manager->unit_path_cache && - !set_get(u->manager->unit_path_cache, filename)) - r = -ENOENT; - else - r = open_follow(&filename, &f, symlink_names, &id); - - if (r < 0) { - filename = mfree(filename); - if (r != -ENOENT) - return r; - - /* Empty the symlink names for the next run */ - set_clear_free(symlink_names); - continue; - } - - break; - } - } - - if (!filename) - /* Hmm, no suitable file found? */ - return 0; - - merged = u; - r = merge_by_names(&merged, symlink_names, id); - if (r < 0) - return r; - - if (merged != u) { - u->load_state = UNIT_MERGED; - return 0; - } - - if (fstat(fileno(f), &st) < 0) - return -errno; - - if (null_or_empty(&st)) - u->load_state = UNIT_MASKED; - else { - u->load_state = UNIT_LOADED; - - /* Now, parse the file contents */ - r = config_parse(u->id, filename, f, - UNIT_VTABLE(u)->sections, - config_item_perf_lookup, load_fragment_gperf_lookup, - false, true, false, u); - if (r < 0) - return r; - } - - free(u->fragment_path); - u->fragment_path = filename; - filename = NULL; - - u->fragment_mtime = timespec_load(&st.st_mtim); - - if (u->source_path) { - if (stat(u->source_path, &st) >= 0) - u->source_mtime = timespec_load(&st.st_mtim); - else - u->source_mtime = 0; - } - - return 0; -} - -int unit_load_fragment(Unit *u) { - int r; - Iterator i; - const char *t; - - assert(u); - assert(u->load_state == UNIT_STUB); - assert(u->id); - - if (u->transient) { - u->load_state = UNIT_LOADED; - return 0; - } - - /* First, try to find the unit under its id. We always look - * for unit files in the default directories, to make it easy - * to override things by placing things in /etc/systemd/system */ - r = load_from_path(u, u->id); - if (r < 0) - return r; - - /* Try to find an alias we can load this with */ - if (u->load_state == UNIT_STUB) { - SET_FOREACH(t, u->names, i) { - - if (t == u->id) - continue; - - r = load_from_path(u, t); - if (r < 0) - return r; - - if (u->load_state != UNIT_STUB) - break; - } - } - - /* And now, try looking for it under the suggested (originally linked) path */ - if (u->load_state == UNIT_STUB && u->fragment_path) { - - r = load_from_path(u, u->fragment_path); - if (r < 0) - return r; - - if (u->load_state == UNIT_STUB) - /* Hmm, this didn't work? Then let's get rid - * of the fragment path stored for us, so that - * we don't point to an invalid location. */ - u->fragment_path = mfree(u->fragment_path); - } - - /* Look for a template */ - if (u->load_state == UNIT_STUB && u->instance) { - _cleanup_free_ char *k = NULL; - - r = unit_name_template(u->id, &k); - if (r < 0) - return r; - - r = load_from_path(u, k); - if (r < 0) - return r; - - if (u->load_state == UNIT_STUB) { - SET_FOREACH(t, u->names, i) { - _cleanup_free_ char *z = NULL; - - if (t == u->id) - continue; - - r = unit_name_template(t, &z); - if (r < 0) - return r; - - r = load_from_path(u, z); - if (r < 0) - return r; - - if (u->load_state != UNIT_STUB) - break; - } - } - } - - return 0; -} - -void unit_dump_config_items(FILE *f) { - static const struct { - const ConfigParserCallback callback; - const char *rvalue; - } table[] = { -#if !defined(HAVE_SYSV_COMPAT) || !defined(HAVE_SECCOMP) || !defined(HAVE_PAM) || !defined(HAVE_SELINUX) || !defined(HAVE_SMACK) || !defined(HAVE_APPARMOR) - { config_parse_warn_compat, "NOTSUPPORTED" }, -#endif - { config_parse_int, "INTEGER" }, - { config_parse_unsigned, "UNSIGNED" }, - { config_parse_iec_size, "SIZE" }, - { config_parse_iec_uint64, "SIZE" }, - { config_parse_si_size, "SIZE" }, - { config_parse_bool, "BOOLEAN" }, - { config_parse_string, "STRING" }, - { config_parse_path, "PATH" }, - { config_parse_unit_path_printf, "PATH" }, - { config_parse_strv, "STRING [...]" }, - { config_parse_exec_nice, "NICE" }, - { config_parse_exec_oom_score_adjust, "OOMSCOREADJUST" }, - { config_parse_exec_io_class, "IOCLASS" }, - { config_parse_exec_io_priority, "IOPRIORITY" }, - { config_parse_exec_cpu_sched_policy, "CPUSCHEDPOLICY" }, - { config_parse_exec_cpu_sched_prio, "CPUSCHEDPRIO" }, - { config_parse_exec_cpu_affinity, "CPUAFFINITY" }, - { config_parse_mode, "MODE" }, - { config_parse_unit_env_file, "FILE" }, - { config_parse_output, "OUTPUT" }, - { config_parse_input, "INPUT" }, - { config_parse_log_facility, "FACILITY" }, - { config_parse_log_level, "LEVEL" }, - { config_parse_exec_capabilities, "CAPABILITIES" }, - { config_parse_exec_secure_bits, "SECUREBITS" }, - { config_parse_capability_set, "BOUNDINGSET" }, - { config_parse_limit, "LIMIT" }, - { config_parse_unit_deps, "UNIT [...]" }, - { config_parse_exec, "PATH [ARGUMENT [...]]" }, - { config_parse_service_type, "SERVICETYPE" }, - { config_parse_service_restart, "SERVICERESTART" }, -#ifdef HAVE_SYSV_COMPAT - { config_parse_sysv_priority, "SYSVPRIORITY" }, -#endif - { config_parse_kill_mode, "KILLMODE" }, - { config_parse_signal, "SIGNAL" }, - { config_parse_socket_listen, "SOCKET [...]" }, - { config_parse_socket_bind, "SOCKETBIND" }, - { config_parse_socket_bindtodevice, "NETWORKINTERFACE" }, - { config_parse_sec, "SECONDS" }, - { config_parse_nsec, "NANOSECONDS" }, - { config_parse_namespace_path_strv, "PATH [...]" }, - { config_parse_unit_requires_mounts_for, "PATH [...]" }, - { config_parse_exec_mount_flags, "MOUNTFLAG [...]" }, - { config_parse_unit_string_printf, "STRING" }, - { config_parse_trigger_unit, "UNIT" }, - { config_parse_timer, "TIMER" }, - { config_parse_path_spec, "PATH" }, - { config_parse_notify_access, "ACCESS" }, - { config_parse_ip_tos, "TOS" }, - { config_parse_unit_condition_path, "CONDITION" }, - { config_parse_unit_condition_string, "CONDITION" }, - { config_parse_unit_condition_null, "CONDITION" }, - { config_parse_unit_slice, "SLICE" }, - { config_parse_documentation, "URL" }, - { config_parse_service_timeout, "SECONDS" }, - { config_parse_failure_action, "ACTION" }, - { config_parse_set_status, "STATUS" }, - { config_parse_service_sockets, "SOCKETS" }, - { config_parse_environ, "ENVIRON" }, -#ifdef HAVE_SECCOMP - { config_parse_syscall_filter, "SYSCALLS" }, - { config_parse_syscall_archs, "ARCHS" }, - { config_parse_syscall_errno, "ERRNO" }, - { config_parse_address_families, "FAMILIES" }, -#endif - { config_parse_cpu_shares, "SHARES" }, - { config_parse_memory_limit, "LIMIT" }, - { config_parse_device_allow, "DEVICE" }, - { config_parse_device_policy, "POLICY" }, - { config_parse_blockio_bandwidth, "BANDWIDTH" }, - { config_parse_blockio_weight, "WEIGHT" }, - { config_parse_blockio_device_weight, "DEVICEWEIGHT" }, - { config_parse_long, "LONG" }, - { config_parse_socket_service, "SERVICE" }, -#ifdef HAVE_SELINUX - { config_parse_exec_selinux_context, "LABEL" }, -#endif - { config_parse_job_mode, "MODE" }, - { config_parse_job_mode_isolate, "BOOLEAN" }, - { config_parse_personality, "PERSONALITY" }, - }; - - const char *prev = NULL; - const char *i; - - assert(f); - - NULSTR_FOREACH(i, load_fragment_gperf_nulstr) { - const char *rvalue = "OTHER", *lvalue; - unsigned j; - size_t prefix_len; - const char *dot; - const ConfigPerfItem *p; - - assert_se(p = load_fragment_gperf_lookup(i, strlen(i))); - - dot = strchr(i, '.'); - lvalue = dot ? dot + 1 : i; - prefix_len = dot-i; - - if (dot) - if (!prev || !strneq(prev, i, prefix_len+1)) { - if (prev) - fputc('\n', f); - - fprintf(f, "[%.*s]\n", (int) prefix_len, i); - } - - for (j = 0; j < ELEMENTSOF(table); j++) - if (p->parse == table[j].callback) { - rvalue = table[j].rvalue; - break; - } - - fprintf(f, "%s=%s\n", lvalue, rvalue); - prev = i; - } -} diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h deleted file mode 100644 index 5fb5910919..0000000000 --- a/src/core/load-fragment.h +++ /dev/null @@ -1,122 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "unit.h" - -/* Read service data from .desktop file style configuration fragments */ - -int unit_load_fragment(Unit *u); - -void unit_dump_config_items(FILE *f); - -int config_parse_warn_compat(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_unit_deps(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_obsolete_unit_deps(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_unit_string_printf(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_unit_strv_printf(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_unit_path_printf(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_unit_path_strv_printf(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_documentation(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_socket_listen(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_socket_protocol(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_socket_bind(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_exec_nice(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_exec_oom_score_adjust(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_exec(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_service_timeout(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_service_type(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_service_restart(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_socket_bindtodevice(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_output(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_input(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_exec_io_class(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_exec_io_priority(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_exec_cpu_sched_policy(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_exec_cpu_sched_prio(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_exec_cpu_affinity(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_exec_capabilities(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_exec_secure_bits(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_capability_set(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_limit(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_sysv_priority(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_kill_signal(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_exec_mount_flags(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_timer(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_trigger_unit(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_path_spec(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_socket_service(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_service_sockets(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_busname_service(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_bus_policy(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_bus_policy_world(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_bus_endpoint_policy(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_unit_env_file(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_ip_tos(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_unit_condition_path(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_unit_condition_string(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_unit_condition_null(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_kill_mode(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_notify_access(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_failure_action(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_unit_requires_mounts_for(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_syscall_filter(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_syscall_archs(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_syscall_errno(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_environ(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_pass_environ(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_unit_slice(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_cpu_shares(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_memory_limit(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_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 config_parse_device_policy(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_device_allow(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_blockio_weight(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_blockio_device_weight(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_blockio_bandwidth(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_netclass(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_job_mode(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_job_mode_isolate(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_exec_selinux_context(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_exec_apparmor_profile(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_exec_smack_process_label(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_address_families(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_runtime_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_set_status(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_namespace_path_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); -int config_parse_no_new_privileges(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_cpu_quota(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_protect_home(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_protect_system(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_bus_name(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_exec_utmp_mode(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_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); - -/* gperf prototypes */ -const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length); -extern const char load_fragment_gperf_nulstr[]; - -typedef enum Disabled { - DISABLED_CONFIGURATION, - DISABLED_LEGACY, - DISABLED_EXPERIMENTAL, -} Disabled; diff --git a/src/core/locale-setup.c b/src/core/locale-setup.c deleted file mode 100644 index ccf61d29fb..0000000000 --- a/src/core/locale-setup.c +++ /dev/null @@ -1,124 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "env-util.h" -#include "fileio.h" -#include "locale-setup.h" -#include "locale-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" -#include "virt.h" - -int locale_setup(char ***environment) { - char **add; - char *variables[_VARIABLE_LC_MAX] = {}; - int r = 0, i; - - if (detect_container() <= 0) { - r = parse_env_file("/proc/cmdline", WHITESPACE, - "locale.LANG", &variables[VARIABLE_LANG], - "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE], - "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE], - "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], - "locale.LC_TIME", &variables[VARIABLE_LC_TIME], - "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE], - "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY], - "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], - "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER], - "locale.LC_NAME", &variables[VARIABLE_LC_NAME], - "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], - "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], - "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], - "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], - NULL); - - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read /proc/cmdline: %m"); - } - - /* Hmm, nothing set on the kernel cmd line? Then let's - * try /etc/locale.conf */ - if (r <= 0) { - r = parse_env_file("/etc/locale.conf", NEWLINE, - "LANG", &variables[VARIABLE_LANG], - "LANGUAGE", &variables[VARIABLE_LANGUAGE], - "LC_CTYPE", &variables[VARIABLE_LC_CTYPE], - "LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], - "LC_TIME", &variables[VARIABLE_LC_TIME], - "LC_COLLATE", &variables[VARIABLE_LC_COLLATE], - "LC_MONETARY", &variables[VARIABLE_LC_MONETARY], - "LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], - "LC_PAPER", &variables[VARIABLE_LC_PAPER], - "LC_NAME", &variables[VARIABLE_LC_NAME], - "LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], - "LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], - "LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], - "LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], - NULL); - - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read /etc/locale.conf: %m"); - } - - add = NULL; - for (i = 0; i < _VARIABLE_LC_MAX; i++) { - char *s; - - if (!variables[i]) - continue; - - s = strjoin(locale_variable_to_string(i), "=", variables[i], NULL); - if (!s) { - r = -ENOMEM; - goto finish; - } - - if (strv_consume(&add, s) < 0) { - r = -ENOMEM; - goto finish; - } - } - - if (!strv_isempty(add)) { - char **e; - - e = strv_env_merge(2, *environment, add); - if (!e) { - r = -ENOMEM; - goto finish; - } - - strv_free(*environment); - *environment = e; - } - - r = 0; - -finish: - strv_free(add); - - for (i = 0; i < _VARIABLE_LC_MAX; i++) - free(variables[i]); - - return r; -} diff --git a/src/core/locale-setup.h b/src/core/locale-setup.h deleted file mode 100644 index 3b97497afe..0000000000 --- a/src/core/locale-setup.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int locale_setup(char ***environment); diff --git a/src/core/loopback-setup.c b/src/core/loopback-setup.c deleted file mode 100644 index 04062a7910..0000000000 --- a/src/core/loopback-setup.c +++ /dev/null @@ -1,90 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "sd-netlink.h" - -#include "loopback-setup.h" -#include "missing.h" -#include "netlink-util.h" - -static int start_loopback(sd_netlink *rtnl) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; - int r; - - r = sd_rtnl_message_new_link(rtnl, &req, RTM_SETLINK, LOOPBACK_IFINDEX); - if (r < 0) - return r; - - r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP); - if (r < 0) - return r; - - r = sd_netlink_call(rtnl, req, 0, NULL); - if (r < 0) - return r; - - return 0; -} - -static bool check_loopback(sd_netlink *rtnl) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; - unsigned flags; - int r; - - r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, LOOPBACK_IFINDEX); - if (r < 0) - return false; - - r = sd_netlink_call(rtnl, req, 0, &reply); - if (r < 0) - return false; - - r = sd_rtnl_message_link_get_flags(reply, &flags); - if (r < 0) - return false; - - return flags & IFF_UP; -} - -int loopback_setup(void) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int r; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return r; - - r = start_loopback(rtnl); - if (r < 0) { - - /* If we lack the permissions to configure the - * loopback device, but we find it to be already - * configured, let's exit cleanly, in order to - * supported unprivileged containers. */ - if (r == -EPERM && check_loopback(rtnl)) - return 0; - - return log_warning_errno(r, "Failed to configure loopback device: %m"); - } - - return 0; -} diff --git a/src/core/loopback-setup.h b/src/core/loopback-setup.h deleted file mode 100644 index e7547b8a26..0000000000 --- a/src/core/loopback-setup.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int loopback_setup(void); diff --git a/src/core/machine-id-setup.c b/src/core/machine-id-setup.c deleted file mode 100644 index 9e6b3d3292..0000000000 --- a/src/core/machine-id-setup.c +++ /dev/null @@ -1,375 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "sd-id128.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hexdecoct.h" -#include "io-util.h" -#include "log.h" -#include "machine-id-setup.h" -#include "macro.h" -#include "mkdir.h" -#include "mount-util.h" -#include "path-util.h" -#include "process-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "umask-util.h" -#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, char id[34]) { - assert(fd >= 0); - assert(id); - - if (lseek(fd, 0, SEEK_SET) < 0) - return -errno; - - return loop_write(fd, id, 33, false); -} - -static int generate_machine_id(char id[34], const char *root) { - int fd, r; - unsigned char *p; - sd_id128_t buf; - char *q; - const char *dbus_machine_id; - - assert(id); - - if (isempty(root)) - dbus_machine_id = "/var/lib/dbus/machine-id"; - else - dbus_machine_id = strjoina(root, "/var/lib/dbus/machine-id"); - - /* First, try reading the D-Bus machine id, unless it is a symlink */ - 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) { - log_info("Initializing machine ID from D-Bus machine ID."); - return 0; - } - } - - if (isempty(root)) { - /* If that didn't work, see if we are running in a container, - * and a machine ID was passed in via $container_uuid the way - * libvirt/LXC does it */ - - 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; - } - } - - } else if (detect_vm() == VIRTUALIZATION_KVM) { - - /* If we are not running in a container, see if we are - * 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 that didn't work, generate a random machine id */ - r = sd_id128_randomize(&buf); - 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; - - log_info("Initializing machine ID from random generator."); - - return 0; -} - -int machine_id_setup(const char *root, sd_id128_t machine_id) { - const char *etc_machine_id, *run_machine_id; - _cleanup_close_ int fd = -1; - bool writable = true; - char id[34]; /* 32 + \n + \0 */ - int r; - - if (isempty(root)) { - etc_machine_id = "/etc/machine-id"; - run_machine_id = "/run/machine-id"; - } else { - char *x; - - x = strjoina(root, "/etc/machine-id"); - etc_machine_id = path_kill_slashes(x); - - x = strjoina(root, "/run/machine-id"); - run_machine_id = path_kill_slashes(x); - } - - RUN_WITH_UMASK(0000) { - /* We create this 0444, to indicate that this isn't really - * something you should ever modify. Of course, since the file - * will be owned by root it doesn't matter much, but maybe - * people look. */ - - 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; - - fd = open(etc_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - if (old_errno == EROFS && errno == ENOENT) - log_error_errno(errno, - "System cannot boot: Missing /etc/machine-id and /etc is mounted read-only.\n" - "Booting up is supported only when:\n" - "1) /etc/machine-id exists and is populated.\n" - "2) /etc/machine-id exists and is empty.\n" - "3) /etc/machine-id is missing and /etc is writable.\n"); - else - log_error_errno(errno, "Cannot open %s: %m", etc_machine_id); - - return -errno; - } - - writable = false; - } - } - - /* 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; - - /* Hmm, so, the id currently stored is not useful, then let's - * generate one */ - - r = generate_machine_id(id, root); - if (r < 0) - return r; - } - - if (writable) - if (write_machine_id(fd, id) >= 0) - return 0; - - fd = safe_close(fd); - - /* 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); - } - - /* And now, let's mount it over */ - if (mount(run_machine_id, etc_machine_id, NULL, MS_BIND, NULL) < 0) { - (void) unlink_noerrno(run_machine_id); - return log_error_errno(errno, "Failed to mount %s: %m", etc_machine_id); - } - - log_info("Installed transient %s file.", etc_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); - - return 0; -} - -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 */ - int r; - - if (isempty(root)) - etc_machine_id = "/etc/machine-id"; - else { - char *x; - - x = strjoina(root, "/etc/machine-id"); - etc_machine_id = path_kill_slashes(x); - } - - 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); - return 0; - } - - /* Read existing machine-id */ - fd = open(etc_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY); - 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); - if (r == 0) { - log_error("%s is not on a temporary file system.", etc_machine_id); - return -EROFS; - } - - fd = safe_close(fd); - - /* Store current mount namespace */ - r = namespace_open(0, NULL, &initial_mntns_fd, NULL, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Can't fetch current mount namespace: %m"); - - /* Switch to a new mount namespace, isolate ourself and unmount etc_machine_id in our new namespace */ - if (unshare(CLONE_NEWNS) < 0) - return log_error_errno(errno, "Failed to enter new namespace: %m"); - - if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) - return log_error_errno(errno, "Couldn't make-rslave / mountpoint in our private namespace: %m"); - - if (umount(etc_machine_id) < 0) - 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); - if (r < 0) - return log_error_errno(r, "Cannot write %s: %m", etc_machine_id); - - fd = safe_close(fd); - - /* Return to initial namespace and proceed a lazy tmpfs unmount */ - r = namespace_enter(-1, initial_mntns_fd, -1, -1, -1); - if (r < 0) - return log_warning_errno(r, "Failed to switch back to initial mount namespace: %m.\nWe'll keep transient %s file until next reboot.", etc_machine_id); - - if (umount2(etc_machine_id, MNT_DETACH) < 0) - return log_warning_errno(errno, "Failed to unmount transient %s file: %m.\nWe keep that mount until next reboot.", etc_machine_id); - - return 0; -} diff --git a/src/core/machine-id-setup.h b/src/core/machine-id-setup.h deleted file mode 100644 index a7e7678ed9..0000000000 --- a/src/core/machine-id-setup.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -int machine_id_commit(const char *root); -int machine_id_setup(const char *root, sd_id128_t machine_id); diff --git a/src/core/macros.systemd.in b/src/core/macros.systemd.in deleted file mode 100644 index 2cace3d3ba..0000000000 --- a/src/core/macros.systemd.in +++ /dev/null @@ -1,105 +0,0 @@ -# -*- Mode: rpm-spec; indent-tabs-mode: nil -*- */ -# -# This file is part of systemd. -# -# Copyright 2012 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 . - -# RPM macros for packages installing systemd unit files - -%_unitdir @systemunitdir@ -%_userunitdir @userunitdir@ -%_presetdir @systempresetdir@ -%_udevhwdbdir @udevhwdbdir@ -%_udevrulesdir @udevrulesdir@ -%_journalcatalogdir @catalogdir@ -%_tmpfilesdir @tmpfilesdir@ -%_sysusersdir @sysusersdir@ -%_sysctldir @sysctldir@ -%_binfmtdir @binfmtdir@ - -%systemd_requires \ -Requires(post): systemd \ -Requires(preun): systemd \ -Requires(postun): systemd \ -%{nil} - -%systemd_post() \ -if [ $1 -eq 1 ] ; then \ - # Initial installation \ - systemctl --no-reload preset %{?*} >/dev/null 2>&1 || : \ -fi \ -%{nil} - -%systemd_user_post() %{expand:%systemd_post \\--user \\--global %%{?*}} - -%systemd_preun() \ -if [ $1 -eq 0 ] ; then \ - # Package removal, not upgrade \ - systemctl --no-reload disable --now %{?*} > /dev/null 2>&1 || : \ -fi \ -%{nil} - -%systemd_user_preun() \ -if [ $1 -eq 0 ] ; then \ - # Package removal, not upgrade \ - systemctl --no-reload --user --global disable %{?*} > /dev/null 2>&1 || : \ -fi \ -%{nil} - -%systemd_postun() %{nil} - -%systemd_user_postun() %{nil} - -%systemd_postun_with_restart() \ -if [ $1 -ge 1 ] ; then \ - # Package upgrade, not uninstall \ - systemctl try-restart %{?*} >/dev/null 2>&1 || : \ -fi \ -%{nil} - -%systemd_user_postun_with_restart() %{nil} - -%udev_hwdb_update() \ -udevadm hwdb --update >/dev/null 2>&1 || : \ -%{nil} - -%udev_rules_update() \ -udevadm control --reload >/dev/null 2>&1 || : \ -%{nil} - -%journal_catalog_update() \ -journalctl --update-catalog >/dev/null 2>&1 || : \ -%{nil} - -%tmpfiles_create() \ -systemd-tmpfiles --create %{?*} >/dev/null 2>&1 || : \ -%{nil} - -%sysusers_create() \ -systemd-sysusers %{?*} >/dev/null 2>&1 || : \ -%{nil} - -%sysusers_create_inline() \ -echo %{?*} | systemd-sysusers - >/dev/null 2>&1 || : \ -%{nil} - -%sysctl_apply() \ -@rootlibexecdir@/systemd-sysctl %{?*} >/dev/null 2>&1 || : \ -%{nil} - -%binfmt_apply() \ -@rootlibexecdir@/systemd-binfmt %{?*} >/dev/null 2>&1 || : \ -%{nil} diff --git a/src/core/main.c b/src/core/main.c deleted file mode 100644 index 5346392d73..0000000000 --- a/src/core/main.c +++ /dev/null @@ -1,2142 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#ifdef HAVE_SECCOMP -#include -#endif -#ifdef HAVE_VALGRIND_VALGRIND_H -#include -#endif - -#include "sd-bus.h" -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "architecture.h" -#include "build.h" -#include "bus-error.h" -#include "bus-util.h" -#include "capability-util.h" -#include "clock-util.h" -#include "conf-parser.h" -#include "cpu-set-util.h" -#include "dbus-manager.h" -#include "def.h" -#include "env-util.h" -#include "fd-util.h" -#include "fdset.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "hostname-setup.h" -#include "ima-setup.h" -#include "killall.h" -#include "kmod-setup.h" -#include "load-fragment.h" -#include "log.h" -#include "loopback-setup.h" -#include "machine-id-setup.h" -#include "manager.h" -#include "missing.h" -#include "mount-setup.h" -#include "pager.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "process-util.h" -#include "rlimit-util.h" -#include "selinux-setup.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "smack-setup.h" -#include "special.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "strv.h" -#include "switch-root.h" -#include "terminal-util.h" -#include "user-util.h" -#include "virt.h" -#include "watchdog.h" - -static enum { - ACTION_RUN, - ACTION_HELP, - ACTION_VERSION, - ACTION_TEST, - ACTION_DUMP_CONFIGURATION_ITEMS, - ACTION_DONE -} arg_action = ACTION_RUN; -static char *arg_default_unit = NULL; -static ManagerRunningAs arg_running_as = _MANAGER_RUNNING_AS_INVALID; -static bool arg_dump_core = true; -static int arg_crash_chvt = -1; -static bool arg_crash_shell = false; -static bool arg_crash_reboot = false; -static bool arg_confirm_spawn = false; -static ShowStatus arg_show_status = _SHOW_STATUS_UNSET; -static bool arg_switched_root = false; -static int arg_no_pager = -1; -static char ***arg_join_controllers = NULL; -static ExecOutput arg_default_std_output = EXEC_OUTPUT_JOURNAL; -static ExecOutput arg_default_std_error = EXEC_OUTPUT_INHERIT; -static usec_t arg_default_restart_usec = DEFAULT_RESTART_USEC; -static usec_t arg_default_timeout_start_usec = DEFAULT_TIMEOUT_USEC; -static usec_t arg_default_timeout_stop_usec = DEFAULT_TIMEOUT_USEC; -static usec_t arg_default_start_limit_interval = DEFAULT_START_LIMIT_INTERVAL; -static unsigned arg_default_start_limit_burst = DEFAULT_START_LIMIT_BURST; -static usec_t arg_runtime_watchdog = 0; -static usec_t arg_shutdown_watchdog = 10 * USEC_PER_MINUTE; -static char **arg_default_environment = NULL; -static struct rlimit *arg_default_rlimit[_RLIMIT_MAX] = {}; -static uint64_t arg_capability_bounding_set = CAP_ALL; -static nsec_t arg_timer_slack_nsec = NSEC_INFINITY; -static usec_t arg_default_timer_accuracy_usec = 1 * USEC_PER_MINUTE; -static Set* arg_syscall_archs = NULL; -static FILE* arg_serialization = NULL; -static bool arg_default_cpu_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 sd_id128_t arg_machine_id = {}; - -static void pager_open_if_enabled(void) { - - if (arg_no_pager <= 0) - return; - - pager_open(false); -} - -noreturn static void freeze_or_reboot(void) { - - if (arg_crash_reboot) { - log_notice("Rebooting in 10s..."); - (void) sleep(10); - - log_notice("Rebooting now..."); - (void) reboot(RB_AUTOBOOT); - log_emergency_errno(errno, "Failed to reboot: %m"); - } - - log_emergency("Freezing execution."); - freeze(); -} - -noreturn static void crash(int sig) { - struct sigaction sa; - pid_t pid; - - if (getpid() != 1) - /* Pass this on immediately, if this is not PID 1 */ - (void) raise(sig); - else if (!arg_dump_core) - log_emergency("Caught <%s>, not dumping core.", signal_to_string(sig)); - else { - sa = (struct sigaction) { - .sa_handler = nop_signal_handler, - .sa_flags = SA_NOCLDSTOP|SA_RESTART, - }; - - /* We want to wait for the core process, hence let's enable SIGCHLD */ - (void) sigaction(SIGCHLD, &sa, NULL); - - pid = raw_clone(SIGCHLD, NULL); - if (pid < 0) - log_emergency_errno(errno, "Caught <%s>, cannot fork for core dump: %m", signal_to_string(sig)); - else if (pid == 0) { - /* Enable default signal handler for core dump */ - - sa = (struct sigaction) { - .sa_handler = SIG_DFL, - }; - (void) sigaction(sig, &sa, NULL); - - /* Don't limit the coredump size */ - (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); - - /* Just to be sure... */ - (void) chdir("/"); - - /* Raise the signal again */ - pid = raw_getpid(); - (void) kill(pid, sig); /* raise() would kill the parent */ - - assert_not_reached("We shouldn't be here..."); - _exit(EXIT_FAILURE); - } else { - siginfo_t status; - int r; - - /* Order things nicely. */ - r = wait_for_terminate(pid, &status); - if (r < 0) - log_emergency_errno(r, "Caught <%s>, waitpid() failed: %m", signal_to_string(sig)); - else if (status.si_code != CLD_DUMPED) - log_emergency("Caught <%s>, core dump failed (child "PID_FMT", code=%s, status=%i/%s).", - signal_to_string(sig), - pid, sigchld_code_to_string(status.si_code), - status.si_status, - strna(status.si_code == CLD_EXITED - ? exit_status_to_string(status.si_status, EXIT_STATUS_FULL) - : signal_to_string(status.si_status))); - else - log_emergency("Caught <%s>, dumped core as pid "PID_FMT".", signal_to_string(sig), pid); - } - } - - if (arg_crash_chvt >= 0) - (void) chvt(arg_crash_chvt); - - sa = (struct sigaction) { - .sa_handler = SIG_IGN, - .sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT|SA_RESTART, - }; - - /* Let the kernel reap children for us */ - (void) sigaction(SIGCHLD, &sa, NULL); - - if (arg_crash_shell) { - log_notice("Executing crash shell in 10s..."); - (void) sleep(10); - - pid = raw_clone(SIGCHLD, NULL); - if (pid < 0) - log_emergency_errno(errno, "Failed to fork off crash shell: %m"); - else if (pid == 0) { - (void) setsid(); - (void) make_console_stdio(); - (void) execle("/bin/sh", "/bin/sh", NULL, environ); - - log_emergency_errno(errno, "execle() failed: %m"); - _exit(EXIT_FAILURE); - } else { - log_info("Spawned crash shell as PID "PID_FMT".", pid); - (void) wait_for_terminate(pid, NULL); - } - } - - freeze_or_reboot(); -} - -static void install_crash_handler(void) { - static const struct sigaction sa = { - .sa_handler = crash, - .sa_flags = SA_NODEFER, /* So that we can raise the signal again from the signal handler */ - }; - int r; - - /* We ignore the return value here, since, we don't mind if we - * cannot set up a crash handler */ - r = sigaction_many(&sa, SIGNALS_CRASH_HANDLER, -1); - if (r < 0) - log_debug_errno(r, "I had trouble setting up the crash handler, ignoring: %m"); -} - -static int console_setup(void) { - _cleanup_close_ int tty_fd = -1; - int r; - - tty_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (tty_fd < 0) - return log_error_errno(tty_fd, "Failed to open /dev/console: %m"); - - /* We don't want to force text mode. plymouth may be showing - * pictures already from initrd. */ - r = reset_terminal_fd(tty_fd, false); - if (r < 0) - return log_error_errno(r, "Failed to reset /dev/console: %m"); - - return 0; -} - -static int parse_crash_chvt(const char *value) { - int b; - - if (safe_atoi(value, &arg_crash_chvt) >= 0) - return 0; - - b = parse_boolean(value); - if (b < 0) - return b; - - if (b > 0) - arg_crash_chvt = 0; /* switch to where kmsg goes */ - else - arg_crash_chvt = -1; /* turn off switching */ - - return 0; -} - -static int set_machine_id(const char *m) { - - if (sd_id128_from_string(m, &arg_machine_id) < 0) - return -EINVAL; - - if (sd_id128_is_null(arg_machine_id)) - return -EINVAL; - - return 0; -} - -static int parse_proc_cmdline_item(const char *key, const char *value) { - - int r; - - assert(key); - - if (streq(key, "systemd.unit") && value) { - - if (!in_initrd()) - return free_and_strdup(&arg_default_unit, value); - - } else if (streq(key, "rd.systemd.unit") && value) { - - if (in_initrd()) - return free_and_strdup(&arg_default_unit, value); - - } else if (streq(key, "systemd.dump_core") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse dump core switch %s. Ignoring.", value); - else - arg_dump_core = r; - - } else if (streq(key, "systemd.crash_chvt") && value) { - - if (parse_crash_chvt(value) < 0) - log_warning("Failed to parse crash chvt switch %s. Ignoring.", value); - - } else if (streq(key, "systemd.crash_shell") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse crash shell switch %s. Ignoring.", value); - else - arg_crash_shell = r; - - } else if (streq(key, "systemd.crash_reboot") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse crash reboot switch %s. Ignoring.", value); - else - arg_crash_reboot = r; - - } else if (streq(key, "systemd.confirm_spawn") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse confirm spawn switch %s. Ignoring.", value); - else - arg_confirm_spawn = r; - - } else if (streq(key, "systemd.show_status") && value) { - - r = parse_show_status(value, &arg_show_status); - if (r < 0) - log_warning("Failed to parse show status switch %s. Ignoring.", value); - - } else if (streq(key, "systemd.default_standard_output") && value) { - - r = exec_output_from_string(value); - if (r < 0) - log_warning("Failed to parse default standard output switch %s. Ignoring.", value); - else - arg_default_std_output = r; - - } else if (streq(key, "systemd.default_standard_error") && value) { - - r = exec_output_from_string(value); - if (r < 0) - log_warning("Failed to parse default standard error switch %s. Ignoring.", value); - else - arg_default_std_error = r; - - } else if (streq(key, "systemd.setenv") && value) { - - if (env_assignment_is_valid(value)) { - char **env; - - env = strv_env_set(arg_default_environment, value); - if (env) - arg_default_environment = env; - else - log_warning_errno(ENOMEM, "Setting environment variable '%s' failed, ignoring: %m", value); - } else - log_warning("Environment variable name '%s' is not valid. Ignoring.", value); - - } else if (streq(key, "systemd.machine_id") && value) { - - r = set_machine_id(value); - if (r < 0) - log_warning("MachineID '%s' is not valid. Ignoring.", value); - - } else if (streq(key, "quiet") && !value) { - - if (arg_show_status == _SHOW_STATUS_UNSET) - arg_show_status = SHOW_STATUS_AUTO; - - } else if (streq(key, "debug") && !value) { - - /* Note that log_parse_environment() handles 'debug' - * too, and sets the log level to LOG_DEBUG. */ - - if (detect_container() > 0) - log_set_target(LOG_TARGET_CONSOLE); - - } else if (!in_initrd() && !value) { - const char *target; - - /* SysV compatibility */ - target = runlevel_to_target(key); - if (target) - return free_and_strdup(&arg_default_unit, target); - } - - return 0; -} - -#define DEFINE_SETTER(name, func, descr) \ - static int name(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 r; \ - \ - assert(filename); \ - assert(lvalue); \ - assert(rvalue); \ - \ - r = func(rvalue); \ - if (r < 0) \ - log_syntax(unit, LOG_ERR, filename, line, r, \ - "Invalid " descr "'%s': %m", \ - rvalue); \ - \ - return 0; \ - } - -DEFINE_SETTER(config_parse_level2, log_set_max_level_from_string, "log level") -DEFINE_SETTER(config_parse_target, log_set_target_from_string, "target") -DEFINE_SETTER(config_parse_color, log_show_color_from_string, "color" ) -DEFINE_SETTER(config_parse_location, log_show_location_from_string, "location") - -static int config_parse_cpu_affinity2( - 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) { - - _cleanup_cpu_free_ cpu_set_t *c = NULL; - int ncpus; - - ncpus = parse_cpu_set_and_warn(rvalue, &c, unit, filename, line, lvalue); - if (ncpus < 0) - return ncpus; - - if (sched_setaffinity(0, CPU_ALLOC_SIZE(ncpus), c) < 0) - log_warning("Failed to set CPU affinity: %m"); - - return 0; -} - -static int config_parse_show_status( - 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 k; - ShowStatus *b = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - k = parse_show_status(rvalue, b); - if (k < 0) { - log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse show status setting, ignoring: %s", rvalue); - return 0; - } - - return 0; -} - -static int config_parse_crash_chvt( - 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 r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = parse_crash_chvt(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CrashChangeVT= setting, ignoring: %s", rvalue); - return 0; - } - - return 0; -} - -static int config_parse_join_controllers(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) { - - const char *whole_rvalue = rvalue; - unsigned n = 0; - - assert(filename); - assert(lvalue); - assert(rvalue); - - arg_join_controllers = strv_free_free(arg_join_controllers); - - for (;;) { - _cleanup_free_ char *word = NULL; - char **l; - int r; - - r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, whole_rvalue); - return r; - } - if (r == 0) - break; - - l = strv_split(word, ","); - if (!l) - return log_oom(); - strv_uniq(l); - - if (strv_length(l) <= 1) { - strv_free(l); - continue; - } - - if (!arg_join_controllers) { - arg_join_controllers = new(char**, 2); - if (!arg_join_controllers) { - strv_free(l); - return log_oom(); - } - - arg_join_controllers[0] = l; - arg_join_controllers[1] = NULL; - - n = 1; - } else { - char ***a; - char ***t; - - t = new0(char**, n+2); - if (!t) { - strv_free(l); - return log_oom(); - } - - n = 0; - - for (a = arg_join_controllers; *a; a++) { - - if (strv_overlap(*a, l)) { - if (strv_extend_strv(&l, *a, false) < 0) { - strv_free(l); - strv_free_free(t); - return log_oom(); - } - - } else { - char **c; - - c = strv_copy(*a); - if (!c) { - strv_free(l); - strv_free_free(t); - return log_oom(); - } - - t[n++] = c; - } - } - - t[n++] = strv_uniq(l); - - strv_free_free(arg_join_controllers); - arg_join_controllers = t; - } - } - if (!isempty(rvalue)) - log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); - - return 0; -} - -static int parse_config_file(void) { - - const ConfigTableItem items[] = { - { "Manager", "LogLevel", config_parse_level2, 0, NULL }, - { "Manager", "LogTarget", config_parse_target, 0, NULL }, - { "Manager", "LogColor", config_parse_color, 0, NULL }, - { "Manager", "LogLocation", config_parse_location, 0, NULL }, - { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core }, - { "Manager", "CrashChVT", /* legacy */ config_parse_crash_chvt, 0, NULL }, - { "Manager", "CrashChangeVT", config_parse_crash_chvt, 0, NULL }, - { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, - { "Manager", "CrashReboot", config_parse_bool, 0, &arg_crash_reboot }, - { "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status }, - { "Manager", "CPUAffinity", config_parse_cpu_affinity2, 0, NULL }, - { "Manager", "JoinControllers", config_parse_join_controllers, 0, &arg_join_controllers }, - { "Manager", "RuntimeWatchdogSec", config_parse_sec, 0, &arg_runtime_watchdog }, - { "Manager", "ShutdownWatchdogSec", config_parse_sec, 0, &arg_shutdown_watchdog }, - { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, -#ifdef HAVE_SECCOMP - { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, -#endif - { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec }, - { "Manager", "DefaultTimerAccuracySec", config_parse_sec, 0, &arg_default_timer_accuracy_usec }, - { "Manager", "DefaultStandardOutput", config_parse_output, 0, &arg_default_std_output }, - { "Manager", "DefaultStandardError", config_parse_output, 0, &arg_default_std_error }, - { "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_default_timeout_start_usec }, - { "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_default_timeout_stop_usec }, - { "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_default_restart_usec }, - { "Manager", "DefaultStartLimitInterval", config_parse_sec, 0, &arg_default_start_limit_interval }, - { "Manager", "DefaultStartLimitBurst", config_parse_unsigned, 0, &arg_default_start_limit_burst }, - { "Manager", "DefaultEnvironment", config_parse_environ, 0, &arg_default_environment }, - { "Manager", "DefaultLimitCPU", config_parse_limit, RLIMIT_CPU, arg_default_rlimit }, - { "Manager", "DefaultLimitFSIZE", config_parse_limit, RLIMIT_FSIZE, arg_default_rlimit }, - { "Manager", "DefaultLimitDATA", config_parse_limit, RLIMIT_DATA, arg_default_rlimit }, - { "Manager", "DefaultLimitSTACK", config_parse_limit, RLIMIT_STACK, arg_default_rlimit }, - { "Manager", "DefaultLimitCORE", config_parse_limit, RLIMIT_CORE, arg_default_rlimit }, - { "Manager", "DefaultLimitRSS", config_parse_limit, RLIMIT_RSS, arg_default_rlimit }, - { "Manager", "DefaultLimitNOFILE", config_parse_limit, RLIMIT_NOFILE, arg_default_rlimit }, - { "Manager", "DefaultLimitAS", config_parse_limit, RLIMIT_AS, arg_default_rlimit }, - { "Manager", "DefaultLimitNPROC", config_parse_limit, RLIMIT_NPROC, arg_default_rlimit }, - { "Manager", "DefaultLimitMEMLOCK", config_parse_limit, RLIMIT_MEMLOCK, arg_default_rlimit }, - { "Manager", "DefaultLimitLOCKS", config_parse_limit, RLIMIT_LOCKS, arg_default_rlimit }, - { "Manager", "DefaultLimitSIGPENDING", config_parse_limit, RLIMIT_SIGPENDING, arg_default_rlimit }, - { "Manager", "DefaultLimitMSGQUEUE", config_parse_limit, RLIMIT_MSGQUEUE, arg_default_rlimit }, - { "Manager", "DefaultLimitNICE", config_parse_limit, RLIMIT_NICE, arg_default_rlimit }, - { "Manager", "DefaultLimitRTPRIO", config_parse_limit, RLIMIT_RTPRIO, arg_default_rlimit }, - { "Manager", "DefaultLimitRTTIME", config_parse_limit, RLIMIT_RTTIME, arg_default_rlimit }, - { "Manager", "DefaultCPUAccounting", config_parse_bool, 0, &arg_default_cpu_accounting }, - { "Manager", "DefaultBlockIOAccounting", config_parse_bool, 0, &arg_default_blockio_accounting }, - { "Manager", "DefaultMemoryAccounting", config_parse_bool, 0, &arg_default_memory_accounting }, - { "Manager", "DefaultTasksAccounting", config_parse_bool, 0, &arg_default_tasks_accounting }, - { "Manager", "DefaultTasksMax", config_parse_tasks_max, 0, &arg_default_tasks_max }, - {} - }; - - const char *fn, *conf_dirs_nulstr; - - fn = arg_running_as == MANAGER_SYSTEM ? - PKGSYSCONFDIR "/system.conf" : - PKGSYSCONFDIR "/user.conf"; - - conf_dirs_nulstr = arg_running_as == MANAGER_SYSTEM ? - CONF_PATHS_NULSTR("systemd/system.conf.d") : - CONF_PATHS_NULSTR("systemd/user.conf.d"); - - config_parse_many(fn, conf_dirs_nulstr, "Manager\0", config_item_table_lookup, items, false, NULL); - - /* Traditionally "0" was used to turn off the default unit timeouts. Fix this up so that we used USEC_INFINITY - * like everywhere else. */ - if (arg_default_timeout_start_usec <= 0) - arg_default_timeout_start_usec = USEC_INFINITY; - if (arg_default_timeout_stop_usec <= 0) - arg_default_timeout_stop_usec = USEC_INFINITY; - - return 0; -} - -static void manager_set_defaults(Manager *m) { - - assert(m); - - m->default_timer_accuracy_usec = arg_default_timer_accuracy_usec; - m->default_std_output = arg_default_std_output; - m->default_std_error = arg_default_std_error; - m->default_timeout_start_usec = arg_default_timeout_start_usec; - m->default_timeout_stop_usec = arg_default_timeout_stop_usec; - m->default_restart_usec = arg_default_restart_usec; - m->default_start_limit_interval = arg_default_start_limit_interval; - m->default_start_limit_burst = arg_default_start_limit_burst; - m->default_cpu_accounting = arg_default_cpu_accounting; - m->default_blockio_accounting = arg_default_blockio_accounting; - m->default_memory_accounting = arg_default_memory_accounting; - m->default_tasks_accounting = arg_default_tasks_accounting; - m->default_tasks_max = arg_default_tasks_max; - - manager_set_default_rlimits(m, arg_default_rlimit); - manager_environment_add(m, NULL, arg_default_environment); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_LOG_LEVEL = 0x100, - ARG_LOG_TARGET, - ARG_LOG_COLOR, - ARG_LOG_LOCATION, - ARG_UNIT, - ARG_SYSTEM, - ARG_USER, - ARG_TEST, - ARG_NO_PAGER, - ARG_VERSION, - ARG_DUMP_CONFIGURATION_ITEMS, - ARG_DUMP_CORE, - ARG_CRASH_CHVT, - ARG_CRASH_SHELL, - ARG_CRASH_REBOOT, - ARG_CONFIRM_SPAWN, - ARG_SHOW_STATUS, - ARG_DESERIALIZE, - ARG_SWITCHED_ROOT, - ARG_DEFAULT_STD_OUTPUT, - ARG_DEFAULT_STD_ERROR, - ARG_MACHINE_ID - }; - - static const struct option options[] = { - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, - { "log-target", required_argument, NULL, ARG_LOG_TARGET }, - { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, - { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, - { "unit", required_argument, NULL, ARG_UNIT }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "test", no_argument, NULL, ARG_TEST }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, - { "dump-core", optional_argument, NULL, ARG_DUMP_CORE }, - { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT }, - { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL }, - { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT }, - { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN }, - { "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, - { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, - { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT }, - { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, }, - { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, }, - { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, - {} - }; - - int c, r; - - assert(argc >= 1); - assert(argv); - - if (getpid() == 1) - opterr = 0; - - while ((c = getopt_long(argc, argv, "hDbsz:", options, NULL)) >= 0) - - switch (c) { - - case ARG_LOG_LEVEL: - r = log_set_max_level_from_string(optarg); - if (r < 0) { - log_error("Failed to parse log level %s.", optarg); - return r; - } - - break; - - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); - if (r < 0) { - log_error("Failed to parse log target %s.", optarg); - return r; - } - - break; - - case ARG_LOG_COLOR: - - if (optarg) { - r = log_show_color_from_string(optarg); - if (r < 0) { - log_error("Failed to parse log color setting %s.", optarg); - return r; - } - } else - log_show_color(true); - - break; - - case ARG_LOG_LOCATION: - if (optarg) { - r = log_show_location_from_string(optarg); - if (r < 0) { - log_error("Failed to parse log location setting %s.", optarg); - return r; - } - } else - log_show_location(true); - - break; - - case ARG_DEFAULT_STD_OUTPUT: - r = exec_output_from_string(optarg); - if (r < 0) { - log_error("Failed to parse default standard output setting %s.", optarg); - return r; - } else - arg_default_std_output = r; - break; - - case ARG_DEFAULT_STD_ERROR: - r = exec_output_from_string(optarg); - if (r < 0) { - log_error("Failed to parse default standard error output setting %s.", optarg); - return r; - } else - arg_default_std_error = r; - break; - - case ARG_UNIT: - - r = free_and_strdup(&arg_default_unit, optarg); - if (r < 0) - return log_error_errno(r, "Failed to set default unit %s: %m", optarg); - - break; - - case ARG_SYSTEM: - arg_running_as = MANAGER_SYSTEM; - break; - - case ARG_USER: - arg_running_as = MANAGER_USER; - break; - - case ARG_TEST: - arg_action = ACTION_TEST; - if (arg_no_pager < 0) - arg_no_pager = true; - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case ARG_VERSION: - arg_action = ACTION_VERSION; - break; - - case ARG_DUMP_CONFIGURATION_ITEMS: - arg_action = ACTION_DUMP_CONFIGURATION_ITEMS; - break; - - case ARG_DUMP_CORE: - if (!optarg) - arg_dump_core = true; - else { - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse dump core boolean: %s", optarg); - arg_dump_core = r; - } - break; - - case ARG_CRASH_CHVT: - r = parse_crash_chvt(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse crash virtual terminal index: %s", optarg); - break; - - case ARG_CRASH_SHELL: - if (!optarg) - arg_crash_shell = true; - else { - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse crash shell boolean: %s", optarg); - arg_crash_shell = r; - } - break; - - case ARG_CRASH_REBOOT: - if (!optarg) - arg_crash_reboot = true; - else { - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse crash shell boolean: %s", optarg); - arg_crash_reboot = r; - } - break; - - case ARG_CONFIRM_SPAWN: - r = optarg ? parse_boolean(optarg) : 1; - if (r < 0) { - log_error("Failed to parse confirm spawn boolean %s.", optarg); - return r; - } - arg_confirm_spawn = r; - break; - - case ARG_SHOW_STATUS: - if (optarg) { - r = parse_show_status(optarg, &arg_show_status); - if (r < 0) { - log_error("Failed to parse show status boolean %s.", optarg); - return r; - } - } else - arg_show_status = SHOW_STATUS_YES; - break; - - case ARG_DESERIALIZE: { - int fd; - FILE *f; - - r = safe_atoi(optarg, &fd); - if (r < 0 || fd < 0) { - log_error("Failed to parse deserialize option %s.", optarg); - return -EINVAL; - } - - (void) fd_cloexec(fd, true); - - f = fdopen(fd, "r"); - if (!f) - return log_error_errno(errno, "Failed to open serialization fd: %m"); - - safe_fclose(arg_serialization); - arg_serialization = f; - - break; - } - - case ARG_SWITCHED_ROOT: - arg_switched_root = true; - break; - - case ARG_MACHINE_ID: - r = set_machine_id(optarg); - if (r < 0) { - log_error("MachineID '%s' is not valid.", optarg); - return r; - } - break; - - case 'h': - arg_action = ACTION_HELP; - if (arg_no_pager < 0) - arg_no_pager = true; - break; - - case 'D': - log_set_max_level(LOG_DEBUG); - break; - - case 'b': - case 's': - case 'z': - /* Just to eat away the sysvinit kernel - * cmdline args without getopt() error - * messages that we'll parse in - * parse_proc_cmdline_word() or ignore. */ - - case '?': - if (getpid() != 1) - return -EINVAL; - else - return 0; - - default: - assert_not_reached("Unhandled option code."); - } - - if (optind < argc && getpid() != 1) { - /* Hmm, when we aren't run as init system - * let's complain about excess arguments */ - - log_error("Excess arguments."); - return -EINVAL; - } - - return 0; -} - -static int help(void) { - - printf("%s [OPTIONS...]\n\n" - "Starts up and maintains the system or user services.\n\n" - " -h --help Show this help\n" - " --test Determine startup sequence, dump it and exit\n" - " --no-pager Do not pipe output into a pager\n" - " --dump-configuration-items Dump understood unit configuration items\n" - " --unit=UNIT Set default unit\n" - " --system Run a system instance, even if PID != 1\n" - " --user Run a user instance\n" - " --dump-core[=BOOL] Dump core on crash\n" - " --crash-vt=NR Change to specified VT on crash\n" - " --crash-reboot[=BOOL] Reboot on crash\n" - " --crash-shell[=BOOL] Run shell on crash\n" - " --confirm-spawn[=BOOL] Ask for confirmation when spawning processes\n" - " --show-status[=BOOL] Show status updates on the console during bootup\n" - " --log-target=TARGET Set log target (console, journal, kmsg, journal-or-kmsg, null)\n" - " --log-level=LEVEL Set log level (debug, info, notice, warning, err, crit, alert, emerg)\n" - " --log-color[=BOOL] Highlight important log messages\n" - " --log-location[=BOOL] Include code location in log messages\n" - " --default-standard-output= Set default standard output for services\n" - " --default-standard-error= Set default standard error output for services\n", - program_invocation_short_name); - - return 0; -} - -static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds, bool switching_root) { - _cleanup_fdset_free_ FDSet *fds = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(m); - assert(_f); - assert(_fds); - - r = manager_open_serialization(m, &f); - if (r < 0) - return log_error_errno(r, "Failed to create serialization file: %m"); - - /* Make sure nothing is really destructed when we shut down */ - m->n_reloading ++; - bus_manager_send_reloading(m, true); - - fds = fdset_new(); - if (!fds) - return log_oom(); - - r = manager_serialize(m, f, fds, switching_root); - if (r < 0) - return log_error_errno(r, "Failed to serialize state: %m"); - - if (fseeko(f, 0, SEEK_SET) == (off_t) -1) - return log_error_errno(errno, "Failed to rewind serialization fd: %m"); - - r = fd_cloexec(fileno(f), false); - if (r < 0) - return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization: %m"); - - r = fdset_cloexec(fds, false); - if (r < 0) - return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization fds: %m"); - - *_f = f; - *_fds = fds; - - f = NULL; - fds = NULL; - - return 0; -} - -static int bump_rlimit_nofile(struct rlimit *saved_rlimit) { - struct rlimit nl; - int r; - - assert(saved_rlimit); - - /* Save the original RLIMIT_NOFILE so that we can reset it - * later when transitioning from the initrd to the main - * systemd or suchlike. */ - if (getrlimit(RLIMIT_NOFILE, saved_rlimit) < 0) - return log_error_errno(errno, "Reading RLIMIT_NOFILE failed: %m"); - - /* Make sure forked processes get the default kernel setting */ - if (!arg_default_rlimit[RLIMIT_NOFILE]) { - struct rlimit *rl; - - rl = newdup(struct rlimit, saved_rlimit, 1); - if (!rl) - return log_oom(); - - arg_default_rlimit[RLIMIT_NOFILE] = rl; - } - - /* Bump up the resource limit for ourselves substantially */ - nl.rlim_cur = nl.rlim_max = 64*1024; - r = setrlimit_closest(RLIMIT_NOFILE, &nl); - if (r < 0) - return log_error_errno(r, "Setting RLIMIT_NOFILE failed: %m"); - - return 0; -} - -static void test_usr(void) { - - /* Check that /usr is not a separate fs */ - - if (dir_is_empty("/usr") <= 0) - return; - - log_warning("/usr appears to be on its own filesystem and is not already mounted. This is not a supported setup. " - "Some things will probably break (sometimes even silently) in mysterious ways. " - "Consult http://freedesktop.org/wiki/Software/systemd/separate-usr-is-broken for more information."); -} - -static int initialize_join_controllers(void) { - /* By default, mount "cpu" + "cpuacct" together, and "net_cls" - * + "net_prio". We'd like to add "cpuset" to the mix, but - * "cpuset" doesn't really work for groups with no initialized - * attributes. */ - - arg_join_controllers = new(char**, 3); - if (!arg_join_controllers) - return -ENOMEM; - - arg_join_controllers[0] = strv_new("cpu", "cpuacct", NULL); - if (!arg_join_controllers[0]) - goto oom; - - arg_join_controllers[1] = strv_new("net_cls", "net_prio", NULL); - if (!arg_join_controllers[1]) - goto oom; - - arg_join_controllers[2] = NULL; - return 0; - -oom: - arg_join_controllers = strv_free_free(arg_join_controllers); - return -ENOMEM; -} - -static int enforce_syscall_archs(Set *archs) { -#ifdef HAVE_SECCOMP - scmp_filter_ctx *seccomp; - Iterator i; - void *id; - int r; - - seccomp = seccomp_init(SCMP_ACT_ALLOW); - if (!seccomp) - return log_oom(); - - SET_FOREACH(id, arg_syscall_archs, i) { - r = seccomp_arch_add(seccomp, PTR_TO_UINT32(id) - 1); - if (r == -EEXIST) - continue; - if (r < 0) { - log_error_errno(r, "Failed to add architecture to seccomp: %m"); - goto finish; - } - } - - r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); - if (r < 0) { - log_error_errno(r, "Failed to unset NO_NEW_PRIVS: %m"); - goto finish; - } - - r = seccomp_load(seccomp); - if (r < 0) - log_error_errno(r, "Failed to add install architecture seccomp: %m"); - -finish: - seccomp_release(seccomp); - return r; -#else - return 0; -#endif -} - -static int status_welcome(void) { - _cleanup_free_ char *pretty_name = NULL, *ansi_color = NULL; - int r; - - r = parse_env_file("/etc/os-release", NEWLINE, - "PRETTY_NAME", &pretty_name, - "ANSI_COLOR", &ansi_color, - NULL); - if (r == -ENOENT) - r = parse_env_file("/usr/lib/os-release", NEWLINE, - "PRETTY_NAME", &pretty_name, - "ANSI_COLOR", &ansi_color, - NULL); - - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read os-release file: %m"); - - return status_printf(NULL, false, false, - "\nWelcome to \x1B[%sm%s\x1B[0m!\n", - isempty(ansi_color) ? "1" : ansi_color, - isempty(pretty_name) ? "GNU/Linux" : pretty_name); -} - -static int write_container_id(void) { - const char *c; - int r; - - c = getenv("container"); - if (isempty(c)) - return 0; - - r = write_string_file("/run/systemd/container", c, WRITE_STRING_FILE_CREATE); - if (r < 0) - return log_warning_errno(r, "Failed to write /run/systemd/container, ignoring: %m"); - - return 1; -} - -static int bump_unix_max_dgram_qlen(void) { - _cleanup_free_ char *qlen = NULL; - unsigned long v; - int r; - - /* Let's bump the net.unix.max_dgram_qlen sysctl. The kernel - * default of 16 is simply too low. We set the value really - * really early during boot, so that it is actually applied to - * all our sockets, including the $NOTIFY_SOCKET one. */ - - r = read_one_line_file("/proc/sys/net/unix/max_dgram_qlen", &qlen); - if (r < 0) - return log_warning_errno(r, "Failed to read AF_UNIX datagram queue length, ignoring: %m"); - - r = safe_atolu(qlen, &v); - if (r < 0) - return log_warning_errno(r, "Failed to parse AF_UNIX datagram queue length, ignoring: %m"); - - if (v >= DEFAULT_UNIX_MAX_DGRAM_QLEN) - return 0; - - qlen = mfree(qlen); - if (asprintf(&qlen, "%lu\n", DEFAULT_UNIX_MAX_DGRAM_QLEN) < 0) - return log_oom(); - - r = write_string_file("/proc/sys/net/unix/max_dgram_qlen", qlen, 0); - if (r < 0) - return log_full_errno(IN_SET(r, -EROFS, -EPERM, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, - "Failed to bump AF_UNIX datagram queue length, ignoring: %m"); - - return 1; -} - -int main(int argc, char *argv[]) { - Manager *m = NULL; - int r, retval = EXIT_FAILURE; - usec_t before_startup, after_startup; - char timespan[FORMAT_TIMESPAN_MAX]; - FDSet *fds = NULL; - bool reexecute = false; - const char *shutdown_verb = NULL; - dual_timestamp initrd_timestamp = DUAL_TIMESTAMP_NULL; - dual_timestamp userspace_timestamp = DUAL_TIMESTAMP_NULL; - dual_timestamp kernel_timestamp = DUAL_TIMESTAMP_NULL; - dual_timestamp security_start_timestamp = DUAL_TIMESTAMP_NULL; - dual_timestamp security_finish_timestamp = DUAL_TIMESTAMP_NULL; - static char systemd[] = "systemd"; - bool skip_setup = false; - unsigned j; - bool loaded_policy = false; - bool arm_reboot_watchdog = false; - bool queue_default_job = false; - bool empty_etc = false; - char *switch_root_dir = NULL, *switch_root_init = NULL; - struct rlimit saved_rlimit_nofile = RLIMIT_MAKE_CONST(0); - const char *error_message = NULL; - -#ifdef HAVE_SYSV_COMPAT - if (getpid() != 1 && strstr(program_invocation_short_name, "init")) { - /* This is compatibility support for SysV, where - * calling init as a user is identical to telinit. */ - - errno = -ENOENT; - execv(SYSTEMCTL_BINARY_PATH, argv); - log_error_errno(errno, "Failed to exec " SYSTEMCTL_BINARY_PATH ": %m"); - return 1; - } -#endif - - dual_timestamp_from_monotonic(&kernel_timestamp, 0); - dual_timestamp_get(&userspace_timestamp); - - /* Determine if this is a reexecution or normal bootup. We do - * the full command line parsing much later, so let's just - * have a quick peek here. */ - if (strv_find(argv+1, "--deserialize")) - skip_setup = true; - - /* If we have switched root, do all the special setup - * things */ - if (strv_find(argv+1, "--switched-root")) - skip_setup = false; - - /* If we get started via the /sbin/init symlink then we are - called 'init'. After a subsequent reexecution we are then - called 'systemd'. That is confusing, hence let's call us - systemd right-away. */ - program_invocation_short_name = systemd; - prctl(PR_SET_NAME, systemd); - - saved_argv = argv; - saved_argc = argc; - - log_show_color(isatty(STDERR_FILENO) > 0); - log_set_upgrade_syslog_to_journal(true); - - /* Disable the umask logic */ - if (getpid() == 1) - umask(0); - - if (getpid() == 1 && detect_container() <= 0) { - - /* Running outside of a container as PID 1 */ - arg_running_as = MANAGER_SYSTEM; - make_null_stdio(); - log_set_target(LOG_TARGET_KMSG); - log_open(); - - if (in_initrd()) - initrd_timestamp = userspace_timestamp; - - if (!skip_setup) { - r = mount_setup_early(); - if (r < 0) { - error_message = "Failed to early mount API filesystems"; - goto finish; - } - dual_timestamp_get(&security_start_timestamp); - 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; - } - dual_timestamp_get(&security_finish_timestamp); - } - - if (mac_selinux_init(NULL) < 0) { - error_message = "Failed to initialize SELinux policy"; - goto finish; - } - - if (!skip_setup) { - if (clock_is_localtime() > 0) { - int min; - - /* - * The very first call of settimeofday() also does a time warp in the kernel. - * - * In the rtc-in-local time mode, we set the kernel's timezone, and rely on - * external tools to take care of maintaining the RTC and do all adjustments. - * This matches the behavior of Windows, which leaves the RTC alone if the - * registry tells that the RTC runs in UTC. - */ - r = clock_set_timezone(&min); - if (r < 0) - log_error_errno(r, "Failed to apply local time delta, ignoring: %m"); - else - log_info("RTC configured in localtime, applying delta of %i minutes to system time.", min); - } else if (!in_initrd()) { - /* - * 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 - * 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. - * - * Do no set the kernel's timezone. The concept of local time cannot - * be supported reliably, the time will jump or be incorrect at every daylight - * saving time change. All kernel local time concepts will be treated - * as UTC that way. - */ - (void) clock_reset_timewarp(); - } - - r = clock_apply_epoch(); - if (r < 0) - log_error_errno(r, "Current system time is before build time, but cannot correct: %m"); - else if (r > 0) - log_info("System time before build time, advancing clock."); - } - - /* Set the default for later on, but don't actually - * open the logs like this for now. Note that if we - * are transitioning from the initrd there might still - * be journal fd open, and we shouldn't attempt - * opening that before we parsed /proc/cmdline which - * might redirect output elsewhere. */ - log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); - - } else if (getpid() == 1) { - /* Running inside a container, as PID 1 */ - arg_running_as = MANAGER_SYSTEM; - log_set_target(LOG_TARGET_CONSOLE); - log_close_console(); /* force reopen of /dev/console */ - log_open(); - - /* For the later on, see above... */ - log_set_target(LOG_TARGET_JOURNAL); - - /* clear the kernel timestamp, - * because we are in a container */ - kernel_timestamp.monotonic = 0ULL; - kernel_timestamp.realtime = 0ULL; - - } else { - /* Running as user instance */ - arg_running_as = MANAGER_USER; - log_set_target(LOG_TARGET_AUTO); - log_open(); - - /* clear the kernel timestamp, - * because we are not PID 1 */ - kernel_timestamp = DUAL_TIMESTAMP_NULL; - } - - if (getpid() == 1) { - /* Don't limit the core dump size, so that coredump handlers such as systemd-coredump (which honour the limit) - * will process core dumps for system services by default. */ - (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); - - /* But at the same time, turn off the core_pattern logic by default, so that no coredumps are stored - * until the systemd-coredump tool is enabled via sysctl. */ - if (!skip_setup) - (void) write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", 0); - } - - /* Initialize default unit */ - r = free_and_strdup(&arg_default_unit, SPECIAL_DEFAULT_TARGET); - if (r < 0) { - log_emergency_errno(r, "Failed to set default unit %s: %m", SPECIAL_DEFAULT_TARGET); - error_message = "Failed to set default unit"; - goto finish; - } - - r = initialize_join_controllers(); - if (r < 0) { - error_message = "Failed to initialize cgroup controllers"; - goto finish; - } - - /* Mount /proc, /sys and friends, so that /proc/cmdline and - * /proc/$PID/fd is available. */ - if (getpid() == 1) { - - /* Load the kernel modules early, so that we kdbus.ko is loaded before kdbusfs shall be mounted */ - if (!skip_setup) - kmod_setup(); - - r = mount_setup(loaded_policy); - if (r < 0) { - error_message = "Failed to mount API filesystems"; - goto finish; - } - } - - /* Reset all signal handlers. */ - (void) reset_all_signal_handlers(); - (void) ignore_signals(SIGNALS_IGNORE, -1); - - if (parse_config_file() < 0) { - error_message = "Failed to parse config file"; - goto finish; - } - - if (arg_running_as == MANAGER_SYSTEM) { - r = parse_proc_cmdline(parse_proc_cmdline_item); - if (r < 0) - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - } - - /* Note that this also parses bits from the kernel command - * line, including "debug". */ - log_parse_environment(); - - if (parse_argv(argc, argv) < 0) { - error_message = "Failed to parse commandline arguments"; - goto finish; - } - - if (arg_action == ACTION_TEST && - geteuid() == 0) { - log_error("Don't run test mode as root."); - goto finish; - } - - if (arg_running_as == MANAGER_USER && - arg_action == ACTION_RUN && - sd_booted() <= 0) { - log_error("Trying to run as user instance, but the system has not been booted with systemd."); - goto finish; - } - - if (arg_running_as == MANAGER_SYSTEM && - arg_action == ACTION_RUN && - running_in_chroot() > 0) { - log_error("Cannot be run in a chroot() environment."); - goto finish; - } - - if (arg_action == ACTION_TEST) - skip_setup = true; - - pager_open_if_enabled(); - - if (arg_action == ACTION_HELP) { - retval = help(); - goto finish; - } else if (arg_action == ACTION_VERSION) { - retval = version(); - goto finish; - } else if (arg_action == ACTION_DUMP_CONFIGURATION_ITEMS) { - unit_dump_config_items(stdout); - retval = EXIT_SUCCESS; - goto finish; - } else if (arg_action == ACTION_DONE) { - retval = EXIT_SUCCESS; - goto finish; - } - - if (arg_running_as == MANAGER_USER && - !getenv("XDG_RUNTIME_DIR")) { - log_error("Trying to run as user instance, but $XDG_RUNTIME_DIR is not set."); - goto finish; - } - - assert_se(arg_action == ACTION_RUN || arg_action == ACTION_TEST); - - /* Close logging fds, in order not to confuse fdset below */ - log_close(); - - /* Remember open file descriptors for later deserialization */ - r = fdset_new_fill(&fds); - if (r < 0) { - log_emergency_errno(r, "Failed to allocate fd set: %m"); - error_message = "Failed to allocate fd set"; - goto finish; - } else - fdset_cloexec(fds, true); - - if (arg_serialization) - assert_se(fdset_remove(fds, fileno(arg_serialization)) >= 0); - - if (arg_running_as == MANAGER_SYSTEM) - /* Become a session leader if we aren't one yet. */ - setsid(); - - /* Move out of the way, so that we won't block unmounts */ - assert_se(chdir("/") == 0); - - /* Reset the console, but only if this is really init and we - * are freshly booted */ - if (arg_running_as == MANAGER_SYSTEM && arg_action == ACTION_RUN) { - - /* If we are init, we connect stdin/stdout/stderr to - * /dev/null and make sure we don't have a controlling - * tty. */ - release_terminal(); - - if (getpid() == 1 && !skip_setup) - console_setup(); - } - - /* Open the logging devices, if possible and necessary */ - log_open(); - - if (arg_show_status == _SHOW_STATUS_UNSET) - arg_show_status = SHOW_STATUS_YES; - - /* Make sure we leave a core dump without panicing the - * kernel. */ - if (getpid() == 1) { - install_crash_handler(); - - r = mount_cgroup_controllers(arg_join_controllers); - if (r < 0) - goto finish; - } - - if (arg_running_as == MANAGER_SYSTEM) { - int v; - - log_info(PACKAGE_STRING " running in %ssystem mode. (" SYSTEMD_FEATURES ")", - arg_action == ACTION_TEST ? "test " : "" ); - - v = detect_virtualization(); - if (v > 0) - log_info("Detected virtualization %s.", virtualization_to_string(v)); - - write_container_id(); - - log_info("Detected architecture %s.", architecture_to_string(uname_architecture())); - - if (in_initrd()) - log_info("Running in initial RAM disk."); - - /* Let's check whether /etc is already populated. We - * don't actually really check for that, but use - * /etc/machine-id as flag file. This allows container - * managers and installers to provision a couple of - * files already. If the container manager wants to - * provision the machine ID itself it should pass - * $container_uuid to PID 1. */ - - empty_etc = access("/etc/machine-id", F_OK) < 0; - if (empty_etc) - log_info("Running with unpopulated /etc."); - } else { - _cleanup_free_ char *t; - - t = uid_to_name(getuid()); - log_debug(PACKAGE_STRING " running in %suser mode for user "UID_FMT"/%s. (" SYSTEMD_FEATURES ")", - arg_action == ACTION_TEST ? " test" : "", getuid(), t); - } - - if (arg_running_as == MANAGER_SYSTEM && !skip_setup) { - if (arg_show_status > 0) - status_welcome(); - - hostname_setup(); - machine_id_setup(NULL, arg_machine_id); - loopback_setup(); - bump_unix_max_dgram_qlen(); - - test_usr(); - } - - if (arg_running_as == MANAGER_SYSTEM && arg_runtime_watchdog > 0) - watchdog_set_timeout(&arg_runtime_watchdog); - - if (arg_timer_slack_nsec != NSEC_INFINITY) - if (prctl(PR_SET_TIMERSLACK, arg_timer_slack_nsec) < 0) - log_error_errno(errno, "Failed to adjust timer slack: %m"); - - if (!cap_test_all(arg_capability_bounding_set)) { - r = capability_bounding_set_drop_usermode(arg_capability_bounding_set); - if (r < 0) { - log_emergency_errno(r, "Failed to drop capability bounding set of usermode helpers: %m"); - error_message = "Failed to drop capability bounding set of usermode helpers"; - goto finish; - } - r = capability_bounding_set_drop(arg_capability_bounding_set, true); - if (r < 0) { - log_emergency_errno(r, "Failed to drop capability bounding set: %m"); - error_message = "Failed to drop capability bounding set"; - goto finish; - } - } - - if (arg_syscall_archs) { - r = enforce_syscall_archs(arg_syscall_archs); - if (r < 0) { - error_message = "Failed to set syscall architectures"; - goto finish; - } - } - - if (arg_running_as == MANAGER_USER) - /* Become reaper of our children */ - if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) - log_warning_errno(errno, "Failed to make us a subreaper: %m"); - - if (arg_running_as == MANAGER_SYSTEM) { - bump_rlimit_nofile(&saved_rlimit_nofile); - - if (empty_etc) { - r = unit_file_preset_all(UNIT_FILE_SYSTEM, false, NULL, UNIT_FILE_PRESET_ENABLE_ONLY, false, NULL, 0); - if (r < 0) - log_full_errno(r == -EEXIST ? LOG_NOTICE : LOG_WARNING, r, "Failed to populate /etc with preset unit settings, ignoring: %m"); - else - log_info("Populated /etc with preset unit settings."); - } - } - - r = manager_new(arg_running_as, arg_action == ACTION_TEST, &m); - if (r < 0) { - log_emergency_errno(r, "Failed to allocate manager object: %m"); - error_message = "Failed to allocate manager object"; - goto finish; - } - - m->confirm_spawn = arg_confirm_spawn; - m->runtime_watchdog = arg_runtime_watchdog; - m->shutdown_watchdog = arg_shutdown_watchdog; - m->userspace_timestamp = userspace_timestamp; - m->kernel_timestamp = kernel_timestamp; - m->initrd_timestamp = initrd_timestamp; - m->security_start_timestamp = security_start_timestamp; - m->security_finish_timestamp = security_finish_timestamp; - - manager_set_defaults(m); - manager_set_show_status(m, arg_show_status); - manager_set_first_boot(m, empty_etc); - - /* Remember whether we should queue the default job */ - queue_default_job = !arg_serialization || arg_switched_root; - - before_startup = now(CLOCK_MONOTONIC); - - r = manager_startup(m, arg_serialization, fds); - if (r < 0) - log_error_errno(r, "Failed to fully start up daemon: %m"); - - /* This will close all file descriptors that were opened, but - * not claimed by any unit. */ - fds = fdset_free(fds); - - arg_serialization = safe_fclose(arg_serialization); - - if (queue_default_job) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - Unit *target = NULL; - Job *default_unit_job; - - log_debug("Activating default unit: %s", arg_default_unit); - - r = manager_load_unit(m, arg_default_unit, NULL, &error, &target); - if (r < 0) - log_error("Failed to load default target: %s", bus_error_message(&error, r)); - else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) - log_error_errno(target->load_error, "Failed to load default target: %m"); - else if (target->load_state == UNIT_MASKED) - log_error("Default target masked."); - - if (!target || target->load_state != UNIT_LOADED) { - log_info("Trying to load rescue target..."); - - r = manager_load_unit(m, SPECIAL_RESCUE_TARGET, NULL, &error, &target); - if (r < 0) { - log_emergency("Failed to load rescue target: %s", bus_error_message(&error, r)); - error_message = "Failed to load rescue target"; - goto finish; - } else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) { - log_emergency_errno(target->load_error, "Failed to load rescue target: %m"); - error_message = "Failed to load rescue target"; - goto finish; - } else if (target->load_state == UNIT_MASKED) { - log_emergency("Rescue target masked."); - error_message = "Rescue target masked"; - goto finish; - } - } - - assert(target->load_state == UNIT_LOADED); - - if (arg_action == ACTION_TEST) { - printf("-> By units:\n"); - manager_dump_units(m, stdout, "\t"); - } - - r = manager_add_job(m, JOB_START, target, JOB_ISOLATE, &error, &default_unit_job); - if (r == -EPERM) { - log_debug("Default target could not be isolated, starting instead: %s", bus_error_message(&error, r)); - - sd_bus_error_free(&error); - - r = manager_add_job(m, JOB_START, target, JOB_REPLACE, &error, &default_unit_job); - if (r < 0) { - log_emergency("Failed to start default target: %s", bus_error_message(&error, r)); - error_message = "Failed to start default target"; - goto finish; - } - } else if (r < 0) { - log_emergency("Failed to isolate default target: %s", bus_error_message(&error, r)); - error_message = "Failed to isolate default target"; - goto finish; - } - - m->default_unit_job_id = default_unit_job->id; - - after_startup = now(CLOCK_MONOTONIC); - log_full(arg_action == ACTION_TEST ? LOG_INFO : LOG_DEBUG, - "Loaded units and determined initial transaction in %s.", - format_timespan(timespan, sizeof(timespan), after_startup - before_startup, 100 * USEC_PER_MSEC)); - - if (arg_action == ACTION_TEST) { - printf("-> By jobs:\n"); - manager_dump_jobs(m, stdout, "\t"); - retval = EXIT_SUCCESS; - goto finish; - } - } - - for (;;) { - r = manager_loop(m); - if (r < 0) { - log_emergency_errno(r, "Failed to run main loop: %m"); - error_message = "Failed to run main loop"; - goto finish; - } - - switch (m->exit_code) { - - case MANAGER_RELOAD: - log_info("Reloading."); - - r = parse_config_file(); - if (r < 0) - log_error("Failed to parse config file."); - - manager_set_defaults(m); - - r = manager_reload(m); - if (r < 0) - log_error_errno(r, "Failed to reload: %m"); - break; - - case MANAGER_REEXECUTE: - - if (prepare_reexecute(m, &arg_serialization, &fds, false) < 0) { - error_message = "Failed to prepare for reexecution"; - goto finish; - } - - reexecute = true; - log_notice("Reexecuting."); - goto finish; - - case MANAGER_SWITCH_ROOT: - /* Steal the switch root parameters */ - switch_root_dir = m->switch_root; - switch_root_init = m->switch_root_init; - m->switch_root = m->switch_root_init = NULL; - - if (!switch_root_init) - if (prepare_reexecute(m, &arg_serialization, &fds, true) < 0) { - error_message = "Failed to prepare for reexecution"; - goto finish; - } - - reexecute = true; - log_notice("Switching root."); - goto finish; - - case MANAGER_EXIT: - retval = m->return_value; - - if (m->running_as == MANAGER_USER) { - log_debug("Exit."); - goto finish; - } - - /* fallthrough */ - case MANAGER_REBOOT: - case MANAGER_POWEROFF: - case MANAGER_HALT: - case MANAGER_KEXEC: { - static const char * const table[_MANAGER_EXIT_CODE_MAX] = { - [MANAGER_EXIT] = "exit", - [MANAGER_REBOOT] = "reboot", - [MANAGER_POWEROFF] = "poweroff", - [MANAGER_HALT] = "halt", - [MANAGER_KEXEC] = "kexec" - }; - - assert_se(shutdown_verb = table[m->exit_code]); - arm_reboot_watchdog = m->exit_code == MANAGER_REBOOT; - - log_notice("Shutting down."); - goto finish; - } - - default: - assert_not_reached("Unknown exit code."); - } - } - -finish: - pager_close(); - - if (m) - arg_shutdown_watchdog = m->shutdown_watchdog; - - m = manager_free(m); - - for (j = 0; j < ELEMENTSOF(arg_default_rlimit); j++) - arg_default_rlimit[j] = mfree(arg_default_rlimit[j]); - - arg_default_unit = mfree(arg_default_unit); - arg_join_controllers = strv_free_free(arg_join_controllers); - arg_default_environment = strv_free(arg_default_environment); - arg_syscall_archs = set_free(arg_syscall_archs); - - mac_selinux_finish(); - - if (reexecute) { - const char **args; - unsigned i, args_size; - - /* Close and disarm the watchdog, so that the new - * instance can reinitialize it, but doesn't get - * rebooted while we do that */ - watchdog_close(true); - - /* Reset the RLIMIT_NOFILE to the kernel default, so - * that the new systemd can pass the kernel default to - * its child processes */ - if (saved_rlimit_nofile.rlim_cur > 0) - (void) setrlimit(RLIMIT_NOFILE, &saved_rlimit_nofile); - - if (switch_root_dir) { - /* Kill all remaining processes from the - * initrd, but don't wait for them, so that we - * can handle the SIGCHLD for them after - * deserializing. */ - broadcast_signal(SIGTERM, false, true); - - /* And switch root with MS_MOVE, because we remove the old directory afterwards and detach it. */ - r = switch_root(switch_root_dir, "/mnt", true, MS_MOVE); - if (r < 0) - log_error_errno(r, "Failed to switch root, trying to continue: %m"); - } - - args_size = MAX(6, argc+1); - args = newa(const char*, args_size); - - if (!switch_root_init) { - char sfd[DECIMAL_STR_MAX(int) + 1]; - - /* First try to spawn ourselves with the right - * path, and with full serialization. We do - * this only if the user didn't specify an - * explicit init to spawn. */ - - assert(arg_serialization); - assert(fds); - - xsprintf(sfd, "%i", fileno(arg_serialization)); - - i = 0; - args[i++] = SYSTEMD_BINARY_PATH; - if (switch_root_dir) - args[i++] = "--switched-root"; - args[i++] = arg_running_as == MANAGER_SYSTEM ? "--system" : "--user"; - args[i++] = "--deserialize"; - 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); - - /* - * We want valgrind to print its memory usage summary before reexecution. - * Valgrind won't do this is on its own on exec(), but it will do it on exit(). - * Hence, to ensure we get a summary here, fork() off a child, let it exit() cleanly, - * so that it prints the summary, and wait() for it in the parent, before proceeding into the exec(). - */ - valgrind_summary_hack(); - - (void) execv(args[0], (char* const*) args); - } - - /* Try the fallback, if there is any, without any - * serialization. We pass the original argv[] and - * envp[]. (Well, modulo the ordering changes due to - * getopt() in argv[], and some cleanups in envp[], - * but let's hope that doesn't matter.) */ - - 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; - assert(i <= args_size); - - /* Reenable any blocked signals, especially important - * if we switch from initial ramdisk to init=... */ - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - if (switch_root_init) { - args[0] = switch_root_init; - (void) execv(args[0], (char* const*) args); - log_warning_errno(errno, "Failed to execute configured init, trying fallback: %m"); - } - - args[0] = "/sbin/init"; - (void) execv(args[0], (char* const*) args); - - if (errno == ENOENT) { - log_warning("No /sbin/init, trying fallback"); - - args[0] = "/bin/sh"; - args[1] = NULL; - (void) execv(args[0], (char* const*) args); - log_error_errno(errno, "Failed to execute /bin/sh, giving up: %m"); - } else - log_warning_errno(errno, "Failed to execute /sbin/init, giving up: %m"); - } - - arg_serialization = safe_fclose(arg_serialization); - fds = fdset_free(fds); - -#ifdef HAVE_VALGRIND_VALGRIND_H - /* If we are PID 1 and running under valgrind, then let's exit - * here explicitly. valgrind will only generate nice output on - * exit(), not on exec(), hence let's do the former not the - * latter here. */ - if (getpid() == 1 && RUNNING_ON_VALGRIND) - return 0; -#endif - - if (shutdown_verb) { - char log_level[DECIMAL_STR_MAX(int) + 1]; - char exit_code[DECIMAL_STR_MAX(uint8_t) + 1]; - const char* command_line[11] = { - SYSTEMD_SHUTDOWN_BINARY_PATH, - shutdown_verb, - "--log-level", log_level, - "--log-target", - }; - unsigned pos = 5; - _cleanup_strv_free_ char **env_block = NULL; - - assert(command_line[pos] == NULL); - env_block = strv_copy(environ); - - xsprintf(log_level, "%d", log_get_max_level()); - - switch (log_get_target()) { - - case LOG_TARGET_KMSG: - case LOG_TARGET_JOURNAL_OR_KMSG: - case LOG_TARGET_SYSLOG_OR_KMSG: - command_line[pos++] = "kmsg"; - break; - - case LOG_TARGET_NULL: - command_line[pos++] = "null"; - break; - - case LOG_TARGET_CONSOLE: - default: - command_line[pos++] = "console"; - break; - }; - - if (log_get_show_color()) - command_line[pos++] = "--log-color"; - - if (log_get_show_location()) - command_line[pos++] = "--log-location"; - - if (streq(shutdown_verb, "exit")) { - command_line[pos++] = "--exit-code"; - command_line[pos++] = exit_code; - xsprintf(exit_code, "%d", retval); - } - - assert(pos < ELEMENTSOF(command_line)); - - if (arm_reboot_watchdog && arg_shutdown_watchdog > 0) { - char *e; - - /* If we reboot let's set the shutdown - * watchdog and tell the shutdown binary to - * repeatedly ping it */ - r = watchdog_set_timeout(&arg_shutdown_watchdog); - watchdog_close(r < 0); - - /* Tell the binary how often to ping, ignore failure */ - if (asprintf(&e, "WATCHDOG_USEC="USEC_FMT, arg_shutdown_watchdog) > 0) - (void) strv_push(&env_block, e); - } else - watchdog_close(true); - - /* Avoid the creation of new processes forked by the - * kernel; at this point, we will not listen to the - * signals anyway */ - if (detect_container() <= 0) - (void) cg_uninstall_release_agent(SYSTEMD_CGROUP_CONTROLLER); - - execve(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line, env_block); - log_error_errno(errno, "Failed to execute shutdown binary, %s: %m", - getpid() == 1 ? "freezing" : "quitting"); - } - - if (getpid() == 1) { - if (error_message) - manager_status_printf(NULL, STATUS_TYPE_EMERGENCY, - ANSI_HIGHLIGHT_RED "!!!!!!" ANSI_NORMAL, - "%s, freezing.", error_message); - freeze_or_reboot(); - } - - return retval; -} diff --git a/src/core/manager.c b/src/core/manager.c deleted file mode 100644 index f36cf5e320..0000000000 --- a/src/core/manager.c +++ /dev/null @@ -1,3124 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_AUDIT -#include -#endif - -#include "sd-daemon.h" -#include "sd-messages.h" - -#include "alloc-util.h" -#include "audit-fd.h" -#include "boot-timestamps.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-kernel.h" -#include "bus-util.h" -#include "dbus-job.h" -#include "dbus-manager.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "env-util.h" -#include "escape.h" -#include "exit-status.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hashmap.h" -#include "io-util.h" -#include "locale-setup.h" -#include "log.h" -#include "macro.h" -#include "manager.h" -#include "missing.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-lookup.h" -#include "path-util.h" -#include "process-util.h" -#include "ratelimit.h" -#include "rm-rf.h" -#include "signal-util.h" -#include "special.h" -#include "stat-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "time-util.h" -#include "transaction.h" -#include "umask-util.h" -#include "unit-name.h" -#include "util.h" -#include "virt.h" -#include "watchdog.h" - -#define NOTIFY_RCVBUF_SIZE (8*1024*1024) - -/* Initial delay and the interval for printing status messages about running jobs */ -#define JOBS_IN_PROGRESS_WAIT_USEC (5*USEC_PER_SEC) -#define JOBS_IN_PROGRESS_PERIOD_USEC (USEC_PER_SEC / 3) -#define JOBS_IN_PROGRESS_PERIOD_DIVISOR 3 - -static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); -static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); -static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); -static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); -static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata); -static int manager_dispatch_run_queue(sd_event_source *source, void *userdata); -static int manager_run_generators(Manager *m); -static void manager_undo_generators(Manager *m); - -static void manager_watch_jobs_in_progress(Manager *m) { - usec_t next; - int r; - - assert(m); - - if (m->jobs_in_progress_event_source) - return; - - next = now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_WAIT_USEC; - r = sd_event_add_time( - m->event, - &m->jobs_in_progress_event_source, - CLOCK_MONOTONIC, - next, 0, - manager_dispatch_jobs_in_progress, m); - if (r < 0) - return; - - (void) sd_event_source_set_description(m->jobs_in_progress_event_source, "manager-jobs-in-progress"); -} - -#define CYLON_BUFFER_EXTRA (2*(sizeof(ANSI_RED)-1) + sizeof(ANSI_HIGHLIGHT_RED)-1 + 2*(sizeof(ANSI_NORMAL)-1)) - -static void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned pos) { - char *p = buffer; - - assert(buflen >= CYLON_BUFFER_EXTRA + width + 1); - assert(pos <= width+1); /* 0 or width+1 mean that the center light is behind the corner */ - - if (pos > 1) { - if (pos > 2) - p = mempset(p, ' ', pos-2); - p = stpcpy(p, ANSI_RED); - *p++ = '*'; - } - - if (pos > 0 && pos <= width) { - p = stpcpy(p, ANSI_HIGHLIGHT_RED); - *p++ = '*'; - } - - p = stpcpy(p, ANSI_NORMAL); - - if (pos < width) { - p = stpcpy(p, ANSI_RED); - *p++ = '*'; - if (pos < width-1) - p = mempset(p, ' ', width-1-pos); - strcpy(p, ANSI_NORMAL); - } -} - -void manager_flip_auto_status(Manager *m, bool enable) { - assert(m); - - if (enable) { - if (m->show_status == SHOW_STATUS_AUTO) - manager_set_show_status(m, SHOW_STATUS_TEMPORARY); - } else { - if (m->show_status == SHOW_STATUS_TEMPORARY) - manager_set_show_status(m, SHOW_STATUS_AUTO); - } -} - -static void manager_print_jobs_in_progress(Manager *m) { - _cleanup_free_ char *job_of_n = NULL; - Iterator i; - Job *j; - unsigned counter = 0, print_nr; - char cylon[6 + CYLON_BUFFER_EXTRA + 1]; - unsigned cylon_pos; - char time[FORMAT_TIMESPAN_MAX], limit[FORMAT_TIMESPAN_MAX] = "no limit"; - uint64_t x; - - assert(m); - assert(m->n_running_jobs > 0); - - manager_flip_auto_status(m, true); - - print_nr = (m->jobs_in_progress_iteration / JOBS_IN_PROGRESS_PERIOD_DIVISOR) % m->n_running_jobs; - - HASHMAP_FOREACH(j, m->jobs, i) - if (j->state == JOB_RUNNING && counter++ == print_nr) - break; - - /* m->n_running_jobs must be consistent with the contents of m->jobs, - * so the above loop must have succeeded in finding j. */ - assert(counter == print_nr + 1); - assert(j); - - cylon_pos = m->jobs_in_progress_iteration % 14; - if (cylon_pos >= 8) - cylon_pos = 14 - cylon_pos; - draw_cylon(cylon, sizeof(cylon), 6, cylon_pos); - - m->jobs_in_progress_iteration++; - - if (m->n_running_jobs > 1) { - if (asprintf(&job_of_n, "(%u of %u) ", counter, m->n_running_jobs) < 0) - job_of_n = NULL; - } - - format_timespan(time, sizeof(time), now(CLOCK_MONOTONIC) - j->begin_usec, 1*USEC_PER_SEC); - if (job_get_timeout(j, &x) > 0) - format_timespan(limit, sizeof(limit), x - j->begin_usec, 1*USEC_PER_SEC); - - manager_status_printf(m, STATUS_TYPE_EPHEMERAL, cylon, - "%sA %s job is running for %s (%s / %s)", - strempty(job_of_n), - job_type_to_string(j->type), - unit_description(j->unit), - time, limit); -} - -static int have_ask_password(void) { - _cleanup_closedir_ DIR *dir; - - dir = opendir("/run/systemd/ask-password"); - if (!dir) { - if (errno == ENOENT) - return false; - else - return -errno; - } - - for (;;) { - struct dirent *de; - - errno = 0; - de = readdir(dir); - if (!de && errno > 0) - return -errno; - if (!de) - return false; - - if (startswith(de->d_name, "ask.")) - return true; - } -} - -static int manager_dispatch_ask_password_fd(sd_event_source *source, - int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - - assert(m); - - flush_fd(fd); - - m->have_ask_password = have_ask_password(); - if (m->have_ask_password < 0) - /* Log error but continue. Negative have_ask_password - * is treated as unknown status. */ - log_error_errno(m->have_ask_password, "Failed to list /run/systemd/ask-password: %m"); - - return 0; -} - -static void manager_close_ask_password(Manager *m) { - assert(m); - - m->ask_password_event_source = sd_event_source_unref(m->ask_password_event_source); - m->ask_password_inotify_fd = safe_close(m->ask_password_inotify_fd); - m->have_ask_password = -EINVAL; -} - -static int manager_check_ask_password(Manager *m) { - int r; - - assert(m); - - if (!m->ask_password_event_source) { - assert(m->ask_password_inotify_fd < 0); - - mkdir_p_label("/run/systemd/ask-password", 0755); - - m->ask_password_inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (m->ask_password_inotify_fd < 0) - return log_error_errno(errno, "inotify_init1() failed: %m"); - - if (inotify_add_watch(m->ask_password_inotify_fd, "/run/systemd/ask-password", IN_CREATE|IN_DELETE|IN_MOVE) < 0) { - log_error_errno(errno, "Failed to add watch on /run/systemd/ask-password: %m"); - manager_close_ask_password(m); - return -errno; - } - - r = sd_event_add_io(m->event, &m->ask_password_event_source, - m->ask_password_inotify_fd, EPOLLIN, - manager_dispatch_ask_password_fd, m); - if (r < 0) { - log_error_errno(errno, "Failed to add event source for /run/systemd/ask-password: %m"); - manager_close_ask_password(m); - return -errno; - } - - (void) sd_event_source_set_description(m->ask_password_event_source, "manager-ask-password"); - - /* Queries might have been added meanwhile... */ - manager_dispatch_ask_password_fd(m->ask_password_event_source, - m->ask_password_inotify_fd, EPOLLIN, m); - } - - return m->have_ask_password; -} - -static int manager_watch_idle_pipe(Manager *m) { - int r; - - assert(m); - - if (m->idle_pipe_event_source) - return 0; - - if (m->idle_pipe[2] < 0) - return 0; - - r = sd_event_add_io(m->event, &m->idle_pipe_event_source, m->idle_pipe[2], EPOLLIN, manager_dispatch_idle_pipe_fd, m); - if (r < 0) - return log_error_errno(r, "Failed to watch idle pipe: %m"); - - (void) sd_event_source_set_description(m->idle_pipe_event_source, "manager-idle-pipe"); - - return 0; -} - -static void manager_close_idle_pipe(Manager *m) { - assert(m); - - m->idle_pipe_event_source = sd_event_source_unref(m->idle_pipe_event_source); - - safe_close_pair(m->idle_pipe); - safe_close_pair(m->idle_pipe + 2); -} - -static int manager_setup_time_change(Manager *m) { - int r; - - /* We only care for the cancellation event, hence we set the - * timeout to the latest possible value. */ - struct itimerspec its = { - .it_value.tv_sec = TIME_T_MAX, - }; - - assert(m); - assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX)); - - if (m->test_run) - return 0; - - /* Uses TFD_TIMER_CANCEL_ON_SET to get notifications whenever - * CLOCK_REALTIME makes a jump relative to CLOCK_MONOTONIC */ - - m->time_change_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC); - if (m->time_change_fd < 0) - return log_error_errno(errno, "Failed to create timerfd: %m"); - - if (timerfd_settime(m->time_change_fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) { - log_debug_errno(errno, "Failed to set up TFD_TIMER_CANCEL_ON_SET, ignoring: %m"); - m->time_change_fd = safe_close(m->time_change_fd); - return 0; - } - - r = sd_event_add_io(m->event, &m->time_change_event_source, m->time_change_fd, EPOLLIN, manager_dispatch_time_change_fd, m); - if (r < 0) - return log_error_errno(r, "Failed to create time change event source: %m"); - - (void) sd_event_source_set_description(m->time_change_event_source, "manager-time-change"); - - log_debug("Set up TFD_TIMER_CANCEL_ON_SET timerfd."); - - return 0; -} - -static int enable_special_signals(Manager *m) { - _cleanup_close_ int fd = -1; - - assert(m); - - if (m->test_run) - return 0; - - /* Enable that we get SIGINT on control-alt-del. In containers - * this will fail with EPERM (older) or EINVAL (newer), so - * ignore that. */ - if (reboot(RB_DISABLE_CAD) < 0 && errno != EPERM && errno != EINVAL) - log_warning_errno(errno, "Failed to enable ctrl-alt-del handling: %m"); - - fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) { - /* Support systems without virtual console */ - if (fd != -ENOENT) - log_warning_errno(errno, "Failed to open /dev/tty0: %m"); - } else { - /* Enable that we get SIGWINCH on kbrequest */ - if (ioctl(fd, KDSIGACCEPT, SIGWINCH) < 0) - log_warning_errno(errno, "Failed to enable kbrequest handling: %m"); - } - - return 0; -} - -static int manager_setup_signals(Manager *m) { - struct sigaction sa = { - .sa_handler = SIG_DFL, - .sa_flags = SA_NOCLDSTOP|SA_RESTART, - }; - sigset_t mask; - int r; - - assert(m); - - assert_se(sigaction(SIGCHLD, &sa, NULL) == 0); - - /* We make liberal use of realtime signals here. On - * Linux/glibc we have 30 of them (with the exception of Linux - * on hppa, see below), between SIGRTMIN+0 ... SIGRTMIN+30 - * (aka SIGRTMAX). */ - - assert_se(sigemptyset(&mask) == 0); - sigset_add_many(&mask, - SIGCHLD, /* Child died */ - SIGTERM, /* Reexecute daemon */ - SIGHUP, /* Reload configuration */ - SIGUSR1, /* systemd/upstart: reconnect to D-Bus */ - SIGUSR2, /* systemd: dump status */ - SIGINT, /* Kernel sends us this on control-alt-del */ - SIGWINCH, /* Kernel sends us this on kbrequest (alt-arrowup) */ - SIGPWR, /* Some kernel drivers and upsd send us this on power failure */ - - SIGRTMIN+0, /* systemd: start default.target */ - SIGRTMIN+1, /* systemd: isolate rescue.target */ - SIGRTMIN+2, /* systemd: isolate emergency.target */ - SIGRTMIN+3, /* systemd: start halt.target */ - SIGRTMIN+4, /* systemd: start poweroff.target */ - SIGRTMIN+5, /* systemd: start reboot.target */ - SIGRTMIN+6, /* systemd: start kexec.target */ - - /* ... space for more special targets ... */ - - SIGRTMIN+13, /* systemd: Immediate halt */ - SIGRTMIN+14, /* systemd: Immediate poweroff */ - SIGRTMIN+15, /* systemd: Immediate reboot */ - SIGRTMIN+16, /* systemd: Immediate kexec */ - - /* ... space for more immediate system state changes ... */ - - SIGRTMIN+20, /* systemd: enable status messages */ - SIGRTMIN+21, /* systemd: disable status messages */ - SIGRTMIN+22, /* systemd: set log level to LOG_DEBUG */ - SIGRTMIN+23, /* systemd: set log level to LOG_INFO */ - SIGRTMIN+24, /* systemd: Immediate exit (--user only) */ - - /* .. one free signal here ... */ - -#if !defined(__hppa64__) && !defined(__hppa__) - /* Apparently Linux on hppa has fewer RT - * signals (SIGRTMAX is SIGRTMIN+25 there), - * hence let's not try to make use of them - * here. Since these commands are accessible - * by different means and only really a safety - * net, the missing functionality on hppa - * shouldn't matter. */ - - SIGRTMIN+26, /* systemd: set log target to journal-or-kmsg */ - SIGRTMIN+27, /* systemd: set log target to console */ - SIGRTMIN+28, /* systemd: set log target to kmsg */ - SIGRTMIN+29, /* systemd: set log target to syslog-or-kmsg (obsolete) */ - - /* ... one free signal here SIGRTMIN+30 ... */ -#endif - -1); - assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); - - m->signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); - if (m->signal_fd < 0) - return -errno; - - r = sd_event_add_io(m->event, &m->signal_event_source, m->signal_fd, EPOLLIN, manager_dispatch_signal_fd, m); - if (r < 0) - return r; - - (void) sd_event_source_set_description(m->signal_event_source, "manager-signal"); - - /* Process signals a bit earlier than the rest of things, but - * later than notify_fd processing, so that the notify - * processing can still figure out to which process/service a - * message belongs, before we reap the process. */ - r = sd_event_source_set_priority(m->signal_event_source, SD_EVENT_PRIORITY_NORMAL-5); - if (r < 0) - return r; - - if (m->running_as == MANAGER_SYSTEM) - return enable_special_signals(m); - - return 0; -} - -static void manager_clean_environment(Manager *m) { - assert(m); - - /* Let's remove some environment variables that we - * need ourselves to communicate with our clients */ - strv_env_unset_many( - m->environment, - "NOTIFY_SOCKET", - "MAINPID", - "MANAGERPID", - "LISTEN_PID", - "LISTEN_FDS", - "LISTEN_FDNAMES", - "WATCHDOG_PID", - "WATCHDOG_USEC", - NULL); -} - -static int manager_default_environment(Manager *m) { - assert(m); - - if (m->running_as == MANAGER_SYSTEM) { - /* The system manager always starts with a clean - * environment for its children. It does not import - * the kernel or the parents exported variables. - * - * The initial passed environ is untouched to keep - * /proc/self/environ valid; it is used for tagging - * the init process inside containers. */ - m->environment = strv_new("PATH=" DEFAULT_PATH, - NULL); - - /* Import locale variables LC_*= from configuration */ - locale_setup(&m->environment); - } else { - /* The user manager passes its own environment - * along to its children. */ - m->environment = strv_copy(environ); - } - - if (!m->environment) - return -ENOMEM; - - manager_clean_environment(m); - strv_sort(m->environment); - - return 0; -} - - -int manager_new(ManagerRunningAs running_as, bool test_run, Manager **_m) { - - static const char * const unit_log_fields[_MANAGER_RUNNING_AS_MAX] = { - [MANAGER_SYSTEM] = "UNIT=", - [MANAGER_USER] = "USER_UNIT=", - }; - - static const char * const unit_log_format_strings[_MANAGER_RUNNING_AS_MAX] = { - [MANAGER_SYSTEM] = "UNIT=%s", - [MANAGER_USER] = "USER_UNIT=%s", - }; - - Manager *m; - int r; - - assert(_m); - assert(running_as >= 0); - assert(running_as < _MANAGER_RUNNING_AS_MAX); - - m = new0(Manager, 1); - if (!m) - return -ENOMEM; - -#ifdef ENABLE_EFI - if (running_as == MANAGER_SYSTEM && detect_container() <= 0) - boot_timestamps(&m->userspace_timestamp, &m->firmware_timestamp, &m->loader_timestamp); -#endif - - m->running_as = running_as; - 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); - - /* Prepare log fields we can use for structured logging */ - m->unit_log_field = unit_log_fields[running_as]; - m->unit_log_format_string = unit_log_format_strings[running_as]; - - m->idle_pipe[0] = m->idle_pipe[1] = m->idle_pipe[2] = m->idle_pipe[3] = -1; - - m->pin_cgroupfs_fd = m->notify_fd = m->signal_fd = m->time_change_fd = - m->dev_autofs_fd = m->private_listen_fd = m->kdbus_fd = m->cgroup_inotify_fd = -1; - - m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ - - m->ask_password_inotify_fd = -1; - m->have_ask_password = -EINVAL; /* we don't know */ - m->first_boot = -1; - - m->test_run = test_run; - - /* Reboot immediately if the user hits C-A-D more often than 7x per 2s */ - RATELIMIT_INIT(m->ctrl_alt_del_ratelimit, 2 * USEC_PER_SEC, 7); - - r = manager_default_environment(m); - if (r < 0) - goto fail; - - r = hashmap_ensure_allocated(&m->units, &string_hash_ops); - if (r < 0) - goto fail; - - r = hashmap_ensure_allocated(&m->jobs, NULL); - if (r < 0) - goto fail; - - r = hashmap_ensure_allocated(&m->cgroup_unit, &string_hash_ops); - if (r < 0) - goto fail; - - r = hashmap_ensure_allocated(&m->watch_bus, &string_hash_ops); - if (r < 0) - goto fail; - - r = sd_event_default(&m->event); - if (r < 0) - goto fail; - - r = sd_event_add_defer(m->event, &m->run_queue_event_source, manager_dispatch_run_queue, m); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(m->run_queue_event_source, SD_EVENT_PRIORITY_IDLE); - if (r < 0) - goto fail; - - r = sd_event_source_set_enabled(m->run_queue_event_source, SD_EVENT_OFF); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->run_queue_event_source, "manager-run-queue"); - - r = manager_setup_signals(m); - if (r < 0) - goto fail; - - r = manager_setup_cgroup(m); - if (r < 0) - goto fail; - - r = manager_setup_time_change(m); - if (r < 0) - goto fail; - - m->udev = udev_new(); - if (!m->udev) { - r = -ENOMEM; - goto fail; - } - - /* Note that we set up neither kdbus, nor the notify fd - * here. We do that after deserialization, since they might - * have gotten serialized across the reexec. */ - - m->taint_usr = dir_is_empty("/usr") > 0; - - *_m = m; - return 0; - -fail: - manager_free(m); - return r; -} - -static int manager_setup_notify(Manager *m) { - int r; - - if (m->test_run) - return 0; - - if (m->notify_fd < 0) { - _cleanup_close_ int fd = -1; - union sockaddr_union sa = { - .sa.sa_family = AF_UNIX, - }; - static const int one = 1; - - /* First free all secondary fields */ - m->notify_socket = mfree(m->notify_socket); - m->notify_event_source = sd_event_source_unref(m->notify_event_source); - - 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"); - - fd_inc_rcvbuf(fd, NOTIFY_RCVBUF_SIZE); - - if (m->running_as == MANAGER_SYSTEM) - m->notify_socket = strdup("/run/systemd/notify"); - else { - const char *e; - - e = getenv("XDG_RUNTIME_DIR"); - if (!e) { - log_error_errno(errno, "XDG_RUNTIME_DIR is not set: %m"); - return -EINVAL; - } - - m->notify_socket = strappend(e, "/systemd/notify"); - } - if (!m->notify_socket) - return log_oom(); - - (void) mkdir_parents_label(m->notify_socket, 0755); - (void) unlink(m->notify_socket); - - strncpy(sa.un.sun_path, m->notify_socket, sizeof(sa.un.sun_path)-1); - r = bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)); - if (r < 0) - 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) - return log_error_errno(errno, "SO_PASSCRED failed: %m"); - - m->notify_fd = fd; - fd = -1; - - log_debug("Using notification socket %s", m->notify_socket); - } - - if (!m->notify_event_source) { - r = sd_event_add_io(m->event, &m->notify_event_source, m->notify_fd, EPOLLIN, manager_dispatch_notify_fd, m); - if (r < 0) - return log_error_errno(r, "Failed to allocate notify event source: %m"); - - /* Process signals a bit earlier than SIGCHLD, so that we can - * still identify to which service an exit message belongs */ - r = sd_event_source_set_priority(m->notify_event_source, SD_EVENT_PRIORITY_NORMAL-7); - if (r < 0) - return log_error_errno(r, "Failed to set priority of notify event source: %m"); - - (void) sd_event_source_set_description(m->notify_event_source, "manager-notify"); - } - - 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( - m->running_as == MANAGER_SYSTEM ? "system" : "user", - m->running_as == MANAGER_SYSTEM, &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; - - assert(m); - - if (m->test_run) - return 0; - - try_bus_connect = - m->kdbus_fd >= 0 || - reexecuting || - (m->running_as == MANAGER_USER && getenv("DBUS_SESSION_BUS_ADDRESS")); - - /* Try to connect to the buses, if possible. */ - return bus_init(m, try_bus_connect); -} - -static unsigned manager_dispatch_cleanup_queue(Manager *m) { - Unit *u; - unsigned n = 0; - - assert(m); - - while ((u = m->cleanup_queue)) { - assert(u->in_cleanup_queue); - - unit_free(u); - n++; - } - - return n; -} - -enum { - GC_OFFSET_IN_PATH, /* This one is on the path we were traveling */ - GC_OFFSET_UNSURE, /* No clue */ - GC_OFFSET_GOOD, /* We still need this unit */ - GC_OFFSET_BAD, /* We don't need this unit anymore */ - _GC_OFFSET_MAX -}; - -static void unit_gc_sweep(Unit *u, unsigned gc_marker) { - Iterator i; - Unit *other; - bool is_bad; - - assert(u); - - if (u->gc_marker == gc_marker + GC_OFFSET_GOOD || - u->gc_marker == gc_marker + GC_OFFSET_BAD || - u->gc_marker == gc_marker + GC_OFFSET_IN_PATH) - return; - - if (u->in_cleanup_queue) - goto bad; - - if (unit_check_gc(u)) - goto good; - - u->gc_marker = gc_marker + GC_OFFSET_IN_PATH; - - is_bad = true; - - SET_FOREACH(other, u->dependencies[UNIT_REFERENCED_BY], i) { - unit_gc_sweep(other, gc_marker); - - if (other->gc_marker == gc_marker + GC_OFFSET_GOOD) - goto good; - - if (other->gc_marker != gc_marker + GC_OFFSET_BAD) - is_bad = false; - } - - if (is_bad) - goto bad; - - /* We were unable to find anything out about this entry, so - * let's investigate it later */ - u->gc_marker = gc_marker + GC_OFFSET_UNSURE; - unit_add_to_gc_queue(u); - return; - -bad: - /* We definitely know that this one is not useful anymore, so - * let's mark it for deletion */ - u->gc_marker = gc_marker + GC_OFFSET_BAD; - unit_add_to_cleanup_queue(u); - return; - -good: - u->gc_marker = gc_marker + GC_OFFSET_GOOD; -} - -static unsigned manager_dispatch_gc_queue(Manager *m) { - Unit *u; - unsigned n = 0; - unsigned gc_marker; - - assert(m); - - /* log_debug("Running GC..."); */ - - m->gc_marker += _GC_OFFSET_MAX; - if (m->gc_marker + _GC_OFFSET_MAX <= _GC_OFFSET_MAX) - m->gc_marker = 1; - - gc_marker = m->gc_marker; - - while ((u = m->gc_queue)) { - assert(u->in_gc_queue); - - unit_gc_sweep(u, gc_marker); - - LIST_REMOVE(gc_queue, m->gc_queue, u); - u->in_gc_queue = false; - - n++; - - if (u->gc_marker == gc_marker + GC_OFFSET_BAD || - u->gc_marker == gc_marker + GC_OFFSET_UNSURE) { - if (u->id) - log_unit_debug(u, "Collecting."); - u->gc_marker = gc_marker + GC_OFFSET_BAD; - unit_add_to_cleanup_queue(u); - } - } - - m->n_in_gc_queue = 0; - - return n; -} - -static void manager_clear_jobs_and_units(Manager *m) { - Unit *u; - - assert(m); - - while ((u = hashmap_first(m->units))) - unit_free(u); - - manager_dispatch_cleanup_queue(m); - - assert(!m->load_queue); - assert(!m->run_queue); - assert(!m->dbus_unit_queue); - assert(!m->dbus_job_queue); - assert(!m->cleanup_queue); - assert(!m->gc_queue); - - assert(hashmap_isempty(m->jobs)); - assert(hashmap_isempty(m->units)); - - m->n_on_console = 0; - m->n_running_jobs = 0; -} - -Manager* manager_free(Manager *m) { - UnitType c; - int i; - - if (!m) - return NULL; - - manager_clear_jobs_and_units(m); - - for (c = 0; c < _UNIT_TYPE_MAX; c++) - if (unit_vtable[c]->shutdown) - unit_vtable[c]->shutdown(m); - - /* If we reexecute ourselves, we keep the root cgroup - * around */ - manager_shutdown_cgroup(m, m->exit_code != MANAGER_REEXECUTE); - - manager_undo_generators(m); - - bus_done(m); - - hashmap_free(m->units); - hashmap_free(m->jobs); - hashmap_free(m->watch_pids1); - hashmap_free(m->watch_pids2); - hashmap_free(m->watch_bus); - - set_free(m->startup_units); - set_free(m->failed_units); - - sd_event_source_unref(m->signal_event_source); - sd_event_source_unref(m->notify_event_source); - sd_event_source_unref(m->time_change_event_source); - sd_event_source_unref(m->jobs_in_progress_event_source); - sd_event_source_unref(m->run_queue_event_source); - - safe_close(m->signal_fd); - safe_close(m->notify_fd); - safe_close(m->time_change_fd); - safe_close(m->kdbus_fd); - - manager_close_ask_password(m); - - manager_close_idle_pipe(m); - - udev_unref(m->udev); - sd_event_unref(m->event); - - free(m->notify_socket); - - lookup_paths_free(&m->lookup_paths); - strv_free(m->environment); - - hashmap_free(m->cgroup_unit); - set_free_free(m->unit_path_cache); - - free(m->switch_root); - free(m->switch_root_init); - - for (i = 0; i < _RLIMIT_MAX; i++) - m->rlimit[i] = mfree(m->rlimit[i]); - - assert(hashmap_isempty(m->units_requiring_mounts_for)); - hashmap_free(m->units_requiring_mounts_for); - - free(m); - return NULL; -} - -void manager_enumerate(Manager *m) { - UnitType c; - - assert(m); - - /* Let's ask every type to load all units from disk/kernel - * that it might know */ - for (c = 0; c < _UNIT_TYPE_MAX; c++) { - if (!unit_type_supported(c)) { - log_debug("Unit type .%s is not supported on this system.", unit_type_to_string(c)); - continue; - } - - if (!unit_vtable[c]->enumerate) - continue; - - unit_vtable[c]->enumerate(m); - } - - manager_dispatch_load_queue(m); -} - -static void manager_coldplug(Manager *m) { - Iterator i; - Unit *u; - char *k; - int r; - - assert(m); - - /* Then, let's set up their initial state. */ - HASHMAP_FOREACH_KEY(u, k, m->units, i) { - - /* ignore aliases */ - if (u->id != k) - continue; - - r = unit_coldplug(u); - if (r < 0) - log_warning_errno(r, "We couldn't coldplug %s, proceeding anyway: %m", u->id); - } -} - -static void manager_build_unit_path_cache(Manager *m) { - char **i; - _cleanup_closedir_ DIR *d = NULL; - int r; - - assert(m); - - set_free_free(m->unit_path_cache); - - m->unit_path_cache = set_new(&string_hash_ops); - if (!m->unit_path_cache) { - log_error("Failed to allocate unit path cache."); - return; - } - - /* This simply builds a list of files we know exist, so that - * we don't always have to go to disk */ - - STRV_FOREACH(i, m->lookup_paths.unit_path) { - struct dirent *de; - - d = opendir(*i); - if (!d) { - if (errno != ENOENT) - log_error_errno(errno, "Failed to open directory %s: %m", *i); - continue; - } - - while ((de = readdir(d))) { - char *p; - - if (hidden_file(de->d_name)) - continue; - - p = strjoin(streq(*i, "/") ? "" : *i, "/", de->d_name, NULL); - if (!p) { - r = -ENOMEM; - goto fail; - } - - r = set_consume(m->unit_path_cache, p); - if (r < 0) - goto fail; - } - - d = safe_closedir(d); - } - - return; - -fail: - log_error_errno(r, "Failed to build unit path cache: %m"); - - set_free_free(m->unit_path_cache); - m->unit_path_cache = NULL; -} - - -static void manager_distribute_fds(Manager *m, FDSet *fds) { - Iterator i; - Unit *u; - - assert(m); - - HASHMAP_FOREACH(u, m->units, i) { - - if (fdset_size(fds) <= 0) - break; - - if (!UNIT_VTABLE(u)->distribute_fds) - continue; - - UNIT_VTABLE(u)->distribute_fds(u, fds); - } -} - -int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { - int r, q; - - assert(m); - - dual_timestamp_get(&m->generators_start_timestamp); - r = manager_run_generators(m); - dual_timestamp_get(&m->generators_finish_timestamp); - if (r < 0) - return r; - - r = lookup_paths_init( - &m->lookup_paths, m->running_as, true, - NULL, - m->generator_unit_path, - m->generator_unit_path_early, - m->generator_unit_path_late); - if (r < 0) - return r; - - manager_build_unit_path_cache(m); - - /* If we will deserialize make sure that during enumeration - * this is already known, so we increase the counter here - * already */ - if (serialization) - m->n_reloading ++; - - /* First, enumerate what we can from all config files */ - dual_timestamp_get(&m->units_load_start_timestamp); - manager_enumerate(m); - dual_timestamp_get(&m->units_load_finish_timestamp); - - /* Second, deserialize if there is something to deserialize */ - if (serialization) - r = manager_deserialize(m, serialization, fds); - - /* Any fds left? Find some unit which wants them. This is - * useful to allow container managers to pass some file - * descriptors to us pre-initialized. This enables - * socket-based activation of entire containers. */ - manager_distribute_fds(m, fds); - - /* We might have deserialized the notify fd, but if we didn't - * then let's create the bus now */ - q = manager_setup_notify(m); - if (q < 0 && r == 0) - r = q; - - /* 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); - - if (serialization) { - assert(m->n_reloading > 0); - m->n_reloading --; - - /* Let's wait for the UnitNew/JobNew messages being - * sent, before we notify that the reload is - * finished */ - m->send_reloading_done = true; - } - - return r; -} - -int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_error *e, Job **_ret) { - int r; - Transaction *tr; - - assert(m); - assert(type < _JOB_TYPE_MAX); - assert(unit); - assert(mode < _JOB_MODE_MAX); - - if (mode == JOB_ISOLATE && type != JOB_START) - return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Isolate is only valid for start."); - - if (mode == JOB_ISOLATE && !unit->allow_isolate) - return sd_bus_error_setf(e, BUS_ERROR_NO_ISOLATION, "Operation refused, unit may not be isolated."); - - log_unit_debug(unit, "Trying to enqueue job %s/%s/%s", unit->id, job_type_to_string(type), job_mode_to_string(mode)); - - type = job_type_collapse(type, unit); - - tr = transaction_new(mode == JOB_REPLACE_IRREVERSIBLY); - if (!tr) - return -ENOMEM; - - r = transaction_add_job_and_dependencies(tr, type, unit, NULL, true, false, - mode == JOB_IGNORE_DEPENDENCIES || mode == JOB_IGNORE_REQUIREMENTS, - mode == JOB_IGNORE_DEPENDENCIES, e); - if (r < 0) - goto tr_abort; - - if (mode == JOB_ISOLATE) { - r = transaction_add_isolate_jobs(tr, m); - if (r < 0) - goto tr_abort; - } - - r = transaction_activate(tr, m, mode, e); - if (r < 0) - goto tr_abort; - - log_unit_debug(unit, - "Enqueued job %s/%s as %u", unit->id, - job_type_to_string(type), (unsigned) tr->anchor_job->id); - - if (_ret) - *_ret = tr->anchor_job; - - transaction_free(tr); - return 0; - -tr_abort: - transaction_abort(tr); - transaction_free(tr); - return r; -} - -int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, sd_bus_error *e, Job **ret) { - Unit *unit; - int r; - - assert(m); - assert(type < _JOB_TYPE_MAX); - assert(name); - assert(mode < _JOB_MODE_MAX); - - r = manager_load_unit(m, name, NULL, NULL, &unit); - if (r < 0) - return r; - - return manager_add_job(m, type, unit, mode, e, ret); -} - -int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Job **ret) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(m); - assert(type < _JOB_TYPE_MAX); - assert(name); - assert(mode < _JOB_MODE_MAX); - - r = manager_add_job_by_name(m, type, name, mode, &error, ret); - if (r < 0) - return log_warning_errno(r, "Failed to enqueue %s job for %s: %s", job_mode_to_string(mode), name, bus_error_message(&error, r)); - - return r; -} - -Job *manager_get_job(Manager *m, uint32_t id) { - assert(m); - - return hashmap_get(m->jobs, UINT32_TO_PTR(id)); -} - -Unit *manager_get_unit(Manager *m, const char *name) { - assert(m); - assert(name); - - return hashmap_get(m->units, name); -} - -unsigned manager_dispatch_load_queue(Manager *m) { - Unit *u; - unsigned n = 0; - - assert(m); - - /* Make sure we are not run recursively */ - if (m->dispatching_load_queue) - return 0; - - m->dispatching_load_queue = true; - - /* Dispatches the load queue. Takes a unit from the queue and - * tries to load its data until the queue is empty */ - - while ((u = m->load_queue)) { - assert(u->in_load_queue); - - unit_load(u); - n++; - } - - m->dispatching_load_queue = false; - return n; -} - -int manager_load_unit_prepare( - Manager *m, - const char *name, - const char *path, - sd_bus_error *e, - Unit **_ret) { - - Unit *ret; - UnitType t; - int r; - - assert(m); - assert(name || path); - - /* This will prepare the unit for loading, but not actually - * load anything from disk. */ - - if (path && !is_path(path)) - return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not absolute.", path); - - if (!name) - name = basename(path); - - t = unit_name_to_type(name); - - if (t == _UNIT_TYPE_INVALID || !unit_name_is_valid(name, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) - return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Unit name %s is not valid.", name); - - ret = manager_get_unit(m, name); - if (ret) { - *_ret = ret; - return 1; - } - - ret = unit_new(m, unit_vtable[t]->object_size); - if (!ret) - return -ENOMEM; - - if (path) { - ret->fragment_path = strdup(path); - if (!ret->fragment_path) { - unit_free(ret); - return -ENOMEM; - } - } - - r = unit_add_name(ret, name); - if (r < 0) { - unit_free(ret); - return r; - } - - unit_add_to_load_queue(ret); - unit_add_to_dbus_queue(ret); - unit_add_to_gc_queue(ret); - - if (_ret) - *_ret = ret; - - return 0; -} - -int manager_load_unit( - Manager *m, - const char *name, - const char *path, - sd_bus_error *e, - Unit **_ret) { - - int r; - - assert(m); - - /* This will load the service information files, but not actually - * start any services or anything. */ - - r = manager_load_unit_prepare(m, name, path, e, _ret); - if (r != 0) - return r; - - manager_dispatch_load_queue(m); - - if (_ret) - *_ret = unit_follow_merge(*_ret); - - return 0; -} - -void manager_dump_jobs(Manager *s, FILE *f, const char *prefix) { - Iterator i; - Job *j; - - assert(s); - assert(f); - - HASHMAP_FOREACH(j, s->jobs, i) - job_dump(j, f, prefix); -} - -void manager_dump_units(Manager *s, FILE *f, const char *prefix) { - Iterator i; - Unit *u; - const char *t; - - assert(s); - assert(f); - - HASHMAP_FOREACH_KEY(u, t, s->units, i) - if (u->id == t) - unit_dump(u, f, prefix); -} - -void manager_clear_jobs(Manager *m) { - Job *j; - - assert(m); - - while ((j = hashmap_first(m->jobs))) - /* No need to recurse. We're cancelling all jobs. */ - job_finish_and_invalidate(j, JOB_CANCELED, false); -} - -static int manager_dispatch_run_queue(sd_event_source *source, void *userdata) { - Manager *m = userdata; - Job *j; - - assert(source); - assert(m); - - while ((j = m->run_queue)) { - assert(j->installed); - assert(j->in_run_queue); - - job_run_and_invalidate(j); - } - - if (m->n_running_jobs > 0) - manager_watch_jobs_in_progress(m); - - if (m->n_on_console > 0) - manager_watch_idle_pipe(m); - - return 1; -} - -static unsigned manager_dispatch_dbus_queue(Manager *m) { - Job *j; - Unit *u; - unsigned n = 0; - - assert(m); - - if (m->dispatching_dbus_queue) - return 0; - - m->dispatching_dbus_queue = true; - - while ((u = m->dbus_unit_queue)) { - assert(u->in_dbus_queue); - - bus_unit_send_change_signal(u); - n++; - } - - while ((j = m->dbus_job_queue)) { - assert(j->in_dbus_queue); - - bus_job_send_change_signal(j); - n++; - } - - m->dispatching_dbus_queue = false; - - if (m->send_reloading_done) { - m->send_reloading_done = false; - - bus_manager_send_reloading(m, false); - } - - if (m->queued_message) - bus_send_queued_message(m); - - return n; -} - -static void manager_invoke_notify_message(Manager *m, Unit *u, pid_t pid, const char *buf, size_t n, FDSet *fds) { - _cleanup_strv_free_ char **tags = NULL; - - assert(m); - assert(u); - assert(buf); - assert(n > 0); - - tags = strv_split(buf, "\n\r"); - if (!tags) { - log_oom(); - return; - } - - if (UNIT_VTABLE(u)->notify_message) - UNIT_VTABLE(u)->notify_message(u, pid, tags, fds); - else - log_unit_debug(u, "Got notification message for unit. Ignoring."); -} - -static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - _cleanup_fdset_free_ FDSet *fds = NULL; - Manager *m = userdata; - - char buf[NOTIFY_BUFFER_MAX+1]; - 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; - bool found = false; - Unit *u1, *u2, *u3; - int r, *fd_array = NULL; - unsigned n_fds = 0; - ssize_t n; - - assert(m); - assert(m->notify_fd == fd); - - if (revents != EPOLLIN) { - log_warning("Got unexpected poll event for notify fd."); - return 0; - } - - n = recvmsg(m->notify_fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); - if (n < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - CMSG_FOREACH(cmsg, &msghdr) { - if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { - - fd_array = (int*) CMSG_DATA(cmsg); - n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - - } else 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 (n_fds > 0) { - assert(fd_array); - - r = fdset_new_array(&fds, fd_array, n_fds); - if (r < 0) { - close_many(fd_array, n_fds); - return log_oom(); - } - } - - if (!ucred || ucred->pid <= 0) { - 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; - - /* Notify every unit that might be interested, but try - * to avoid notifying the same one multiple times. */ - u1 = manager_get_unit_by_pid_cgroup(m, ucred->pid); - if (u1) { - manager_invoke_notify_message(m, u1, ucred->pid, buf, n, fds); - found = true; - } - - u2 = hashmap_get(m->watch_pids1, PID_TO_PTR(ucred->pid)); - if (u2 && u2 != u1) { - manager_invoke_notify_message(m, u2, ucred->pid, buf, n, fds); - found = true; - } - - u3 = hashmap_get(m->watch_pids2, PID_TO_PTR(ucred->pid)); - if (u3 && u3 != u2 && u3 != u1) { - manager_invoke_notify_message(m, u3, ucred->pid, buf, n, fds); - found = true; - } - - if (!found) - log_warning("Cannot find unit for notify message of PID "PID_FMT".", ucred->pid); - - if (fdset_size(fds) > 0) - log_warning("Got auxiliary fds with notification message, closing all."); - - return 0; -} - -static void invoke_sigchld_event(Manager *m, Unit *u, const siginfo_t *si) { - assert(m); - assert(u); - assert(si); - - log_unit_debug(u, "Child "PID_FMT" belongs to %s", si->si_pid, u->id); - - unit_unwatch_pid(u, si->si_pid); - UNIT_VTABLE(u)->sigchld_event(u, si->si_pid, si->si_code, si->si_status); -} - -static int manager_dispatch_sigchld(Manager *m) { - assert(m); - - for (;;) { - siginfo_t si = {}; - - /* First we call waitd() for a PID and do not reap the - * zombie. That way we can still access /proc/$PID for - * it while it is a zombie. */ - if (waitid(P_ALL, 0, &si, WEXITED|WNOHANG|WNOWAIT) < 0) { - - if (errno == ECHILD) - break; - - if (errno == EINTR) - continue; - - return -errno; - } - - if (si.si_pid <= 0) - break; - - if (si.si_code == CLD_EXITED || si.si_code == CLD_KILLED || si.si_code == CLD_DUMPED) { - _cleanup_free_ char *name = NULL; - Unit *u1, *u2, *u3; - - get_process_comm(si.si_pid, &name); - - log_debug("Child "PID_FMT" (%s) died (code=%s, status=%i/%s)", - si.si_pid, strna(name), - sigchld_code_to_string(si.si_code), - si.si_status, - strna(si.si_code == CLD_EXITED - ? exit_status_to_string(si.si_status, EXIT_STATUS_FULL) - : signal_to_string(si.si_status))); - - /* And now figure out the unit this belongs - * to, it might be multiple... */ - u1 = manager_get_unit_by_pid_cgroup(m, si.si_pid); - if (u1) - invoke_sigchld_event(m, u1, &si); - u2 = hashmap_get(m->watch_pids1, PID_TO_PTR(si.si_pid)); - if (u2 && u2 != u1) - invoke_sigchld_event(m, u2, &si); - u3 = hashmap_get(m->watch_pids2, PID_TO_PTR(si.si_pid)); - if (u3 && u3 != u2 && u3 != u1) - invoke_sigchld_event(m, u3, &si); - } - - /* And now, we actually reap the zombie. */ - if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) { - if (errno == EINTR) - continue; - - return -errno; - } - } - - return 0; -} - -static int manager_start_target(Manager *m, const char *name, JobMode mode) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - log_debug("Activating special unit %s", name); - - r = manager_add_job_by_name(m, JOB_START, name, mode, &error, NULL); - if (r < 0) - log_error("Failed to enqueue %s job: %s", name, bus_error_message(&error, r)); - - return r; -} - -static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - ssize_t n; - struct signalfd_siginfo sfsi; - bool sigchld = false; - int r; - - assert(m); - assert(m->signal_fd == fd); - - if (revents != EPOLLIN) { - log_warning("Got unexpected events from signal file descriptor."); - return 0; - } - - for (;;) { - n = read(m->signal_fd, &sfsi, sizeof(sfsi)); - if (n != sizeof(sfsi)) { - - if (n >= 0) - return -EIO; - - if (errno == EINTR || errno == EAGAIN) - break; - - return -errno; - } - - log_received_signal(sfsi.ssi_signo == SIGCHLD || - (sfsi.ssi_signo == SIGTERM && m->running_as == MANAGER_USER) - ? LOG_DEBUG : LOG_INFO, - &sfsi); - - switch (sfsi.ssi_signo) { - - case SIGCHLD: - sigchld = true; - break; - - case SIGTERM: - if (m->running_as == MANAGER_SYSTEM) { - /* This is for compatibility with the - * original sysvinit */ - m->exit_code = MANAGER_REEXECUTE; - break; - } - - /* Fall through */ - - case SIGINT: - if (m->running_as == MANAGER_SYSTEM) { - - /* If the user presses C-A-D more than - * 7 times within 2s, we reboot - * immediately. */ - - if (ratelimit_test(&m->ctrl_alt_del_ratelimit)) - manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE_IRREVERSIBLY); - else { - log_notice("Ctrl-Alt-Del was pressed more than 7 times within 2s, rebooting immediately."); - status_printf(NULL, true, false, "Ctrl-Alt-Del was pressed more than 7 times within 2s, rebooting immediately."); - m->exit_code = MANAGER_REBOOT; - } - - break; - } - - /* Run the exit target if there is one, if not, just exit. */ - if (manager_start_target(m, SPECIAL_EXIT_TARGET, JOB_REPLACE) < 0) { - m->exit_code = MANAGER_EXIT; - return 0; - } - - break; - - case SIGWINCH: - if (m->running_as == MANAGER_SYSTEM) - manager_start_target(m, SPECIAL_KBREQUEST_TARGET, JOB_REPLACE); - - /* This is a nop on non-init */ - break; - - case SIGPWR: - if (m->running_as == MANAGER_SYSTEM) - manager_start_target(m, SPECIAL_SIGPWR_TARGET, JOB_REPLACE); - - /* This is a nop on non-init */ - break; - - case SIGUSR1: { - Unit *u; - - u = manager_get_unit(m, SPECIAL_DBUS_SERVICE); - - if (!u || UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) { - log_info("Trying to reconnect to bus..."); - bus_init(m, true); - } - - if (!u || !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) { - log_info("Loading D-Bus service..."); - manager_start_target(m, SPECIAL_DBUS_SERVICE, JOB_REPLACE); - } - - break; - } - - case SIGUSR2: { - _cleanup_free_ char *dump = NULL; - _cleanup_fclose_ FILE *f = NULL; - size_t size; - - f = open_memstream(&dump, &size); - if (!f) { - log_warning_errno(errno, "Failed to allocate memory stream: %m"); - break; - } - - manager_dump_units(m, f, "\t"); - manager_dump_jobs(m, f, "\t"); - - r = fflush_and_check(f); - if (r < 0) { - log_warning_errno(r, "Failed to write status stream: %m"); - break; - } - - log_dump(LOG_INFO, dump); - break; - } - - case SIGHUP: - m->exit_code = MANAGER_RELOAD; - break; - - default: { - - /* Starting SIGRTMIN+0 */ - static const char * const target_table[] = { - [0] = SPECIAL_DEFAULT_TARGET, - [1] = SPECIAL_RESCUE_TARGET, - [2] = SPECIAL_EMERGENCY_TARGET, - [3] = SPECIAL_HALT_TARGET, - [4] = SPECIAL_POWEROFF_TARGET, - [5] = SPECIAL_REBOOT_TARGET, - [6] = SPECIAL_KEXEC_TARGET - }; - - /* Starting SIGRTMIN+13, so that target halt and system halt are 10 apart */ - static const ManagerExitCode code_table[] = { - [0] = MANAGER_HALT, - [1] = MANAGER_POWEROFF, - [2] = MANAGER_REBOOT, - [3] = MANAGER_KEXEC - }; - - if ((int) sfsi.ssi_signo >= SIGRTMIN+0 && - (int) sfsi.ssi_signo < SIGRTMIN+(int) ELEMENTSOF(target_table)) { - int idx = (int) sfsi.ssi_signo - SIGRTMIN; - manager_start_target(m, target_table[idx], - (idx == 1 || idx == 2) ? JOB_ISOLATE : JOB_REPLACE); - break; - } - - if ((int) sfsi.ssi_signo >= SIGRTMIN+13 && - (int) sfsi.ssi_signo < SIGRTMIN+13+(int) ELEMENTSOF(code_table)) { - m->exit_code = code_table[sfsi.ssi_signo - SIGRTMIN - 13]; - break; - } - - switch (sfsi.ssi_signo - SIGRTMIN) { - - case 20: - manager_set_show_status(m, SHOW_STATUS_YES); - break; - - case 21: - manager_set_show_status(m, SHOW_STATUS_NO); - break; - - case 22: - log_set_max_level(LOG_DEBUG); - log_info("Setting log level to debug."); - break; - - case 23: - log_set_max_level(LOG_INFO); - log_info("Setting log level to info."); - break; - - case 24: - if (m->running_as == MANAGER_USER) { - m->exit_code = MANAGER_EXIT; - return 0; - } - - /* This is a nop on init */ - break; - - case 26: - case 29: /* compatibility: used to be mapped to LOG_TARGET_SYSLOG_OR_KMSG */ - log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); - log_notice("Setting log target to journal-or-kmsg."); - break; - - case 27: - log_set_target(LOG_TARGET_CONSOLE); - log_notice("Setting log target to console."); - break; - - case 28: - log_set_target(LOG_TARGET_KMSG); - log_notice("Setting log target to kmsg."); - break; - - default: - log_warning("Got unhandled signal <%s>.", signal_to_string(sfsi.ssi_signo)); - } - } - } - } - - if (sigchld) - manager_dispatch_sigchld(m); - - return 0; -} - -static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - Iterator i; - Unit *u; - - assert(m); - assert(m->time_change_fd == fd); - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE), - LOG_MESSAGE("Time has been changed"), - NULL); - - /* Restart the watch */ - m->time_change_event_source = sd_event_source_unref(m->time_change_event_source); - m->time_change_fd = safe_close(m->time_change_fd); - - manager_setup_time_change(m); - - HASHMAP_FOREACH(u, m->units, i) - if (UNIT_VTABLE(u)->time_change) - UNIT_VTABLE(u)->time_change(u); - - return 0; -} - -static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - - assert(m); - assert(m->idle_pipe[2] == fd); - - m->no_console_output = m->n_on_console > 0; - - manager_close_idle_pipe(m); - - return 0; -} - -static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata) { - Manager *m = userdata; - int r; - uint64_t next; - - assert(m); - assert(source); - - manager_print_jobs_in_progress(m); - - next = now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_PERIOD_USEC; - r = sd_event_source_set_time(source, next); - if (r < 0) - return r; - - return sd_event_source_set_enabled(source, SD_EVENT_ONESHOT); -} - -int manager_loop(Manager *m) { - int r; - - RATELIMIT_DEFINE(rl, 1*USEC_PER_SEC, 50000); - - assert(m); - m->exit_code = MANAGER_OK; - - /* Release the path cache */ - m->unit_path_cache = set_free_free(m->unit_path_cache); - - manager_check_finished(m); - - /* There might still be some zombies hanging around from - * before we were exec()'ed. Let's reap them. */ - r = manager_dispatch_sigchld(m); - if (r < 0) - return r; - - while (m->exit_code == MANAGER_OK) { - usec_t wait_usec; - - if (m->runtime_watchdog > 0 && m->running_as == MANAGER_SYSTEM) - watchdog_ping(); - - if (!ratelimit_test(&rl)) { - /* Yay, something is going seriously wrong, pause a little */ - log_warning("Looping too fast. Throttling execution a little."); - sleep(1); - } - - if (manager_dispatch_load_queue(m) > 0) - continue; - - if (manager_dispatch_gc_queue(m) > 0) - continue; - - if (manager_dispatch_cleanup_queue(m) > 0) - continue; - - if (manager_dispatch_cgroup_queue(m) > 0) - continue; - - if (manager_dispatch_dbus_queue(m) > 0) - continue; - - /* Sleep for half the watchdog time */ - if (m->runtime_watchdog > 0 && m->running_as == MANAGER_SYSTEM) { - wait_usec = m->runtime_watchdog / 2; - if (wait_usec <= 0) - wait_usec = 1; - } else - wait_usec = USEC_INFINITY; - - r = sd_event_run(m->event, wait_usec); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - } - - return m->exit_code; -} - -int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u) { - _cleanup_free_ char *n = NULL; - Unit *u; - int r; - - assert(m); - assert(s); - assert(_u); - - r = unit_name_from_dbus_path(s, &n); - if (r < 0) - return r; - - r = manager_load_unit(m, n, NULL, e, &u); - if (r < 0) - return r; - - *_u = u; - - return 0; -} - -int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j) { - const char *p; - unsigned id; - Job *j; - int r; - - assert(m); - assert(s); - assert(_j); - - p = startswith(s, "/org/freedesktop/systemd1/job/"); - if (!p) - return -EINVAL; - - r = safe_atou(p, &id); - if (r < 0) - return r; - - j = manager_get_job(m, id); - if (!j) - return -ENOENT; - - *_j = j; - - return 0; -} - -void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) { - -#ifdef HAVE_AUDIT - _cleanup_free_ char *p = NULL; - const char *msg; - int audit_fd, r; - - if (m->running_as != MANAGER_SYSTEM) - return; - - audit_fd = get_audit_fd(); - if (audit_fd < 0) - return; - - /* Don't generate audit events if the service was already - * started and we're just deserializing */ - if (m->n_reloading > 0) - return; - - if (u->type != UNIT_SERVICE) - return; - - r = unit_name_to_prefix_and_instance(u->id, &p); - if (r < 0) { - log_error_errno(r, "Failed to extract prefix and instance of unit name: %m"); - return; - } - - msg = strjoina("unit=", p); - if (audit_log_user_comm_message(audit_fd, type, msg, "systemd", NULL, NULL, NULL, success) < 0) { - if (errno == EPERM) - /* We aren't allowed to send audit messages? - * Then let's not retry again. */ - close_audit_fd(); - else - log_warning_errno(errno, "Failed to send audit message: %m"); - } -#endif - -} - -void manager_send_unit_plymouth(Manager *m, Unit *u) { - union sockaddr_union sa = PLYMOUTH_SOCKET; - - int n = 0; - _cleanup_free_ char *message = NULL; - _cleanup_close_ int fd = -1; - - /* Don't generate plymouth events if the service was already - * started and we're just deserializing */ - if (m->n_reloading > 0) - return; - - if (m->running_as != MANAGER_SYSTEM) - return; - - if (detect_container() > 0) - return; - - if (u->type != UNIT_SERVICE && - u->type != UNIT_MOUNT && - u->type != UNIT_SWAP) - return; - - /* We set SOCK_NONBLOCK here so that we rather drop the - * message then wait for plymouth */ - fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) { - log_error_errno(errno, "socket() failed: %m"); - return; - } - - if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) { - - if (!IN_SET(errno, EPIPE, EAGAIN, ENOENT, ECONNREFUSED, ECONNRESET, ECONNABORTED)) - log_error_errno(errno, "connect() failed: %m"); - return; - } - - if (asprintf(&message, "U\002%c%s%n", (int) (strlen(u->id) + 1), u->id, &n) < 0) { - log_oom(); - return; - } - - errno = 0; - if (write(fd, message, n + 1) != n + 1) - if (!IN_SET(errno, EPIPE, EAGAIN, ENOENT, ECONNREFUSED, ECONNRESET, ECONNABORTED)) - log_error_errno(errno, "Failed to write Plymouth message: %m"); -} - -int manager_open_serialization(Manager *m, FILE **_f) { - const char *path; - int fd = -1; - FILE *f; - - assert(_f); - - path = m->running_as == MANAGER_SYSTEM ? "/run/systemd" : "/tmp"; - fd = open_tmpfile(path, O_RDWR|O_CLOEXEC); - if (fd < 0) - return -errno; - - log_debug("Serializing state to %s", path); - - f = fdopen(fd, "w+"); - if (!f) { - safe_close(fd); - return -errno; - } - - *_f = f; - - return 0; -} - -int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) { - Iterator i; - Unit *u; - const char *t; - char **e; - int r; - - assert(m); - assert(f); - assert(fds); - - m->n_reloading ++; - - fprintf(f, "current-job-id=%"PRIu32"\n", m->current_job_id); - fprintf(f, "taint-usr=%s\n", yes_no(m->taint_usr)); - fprintf(f, "n-installed-jobs=%u\n", m->n_installed_jobs); - fprintf(f, "n-failed-jobs=%u\n", m->n_failed_jobs); - - dual_timestamp_serialize(f, "firmware-timestamp", &m->firmware_timestamp); - dual_timestamp_serialize(f, "loader-timestamp", &m->loader_timestamp); - dual_timestamp_serialize(f, "kernel-timestamp", &m->kernel_timestamp); - dual_timestamp_serialize(f, "initrd-timestamp", &m->initrd_timestamp); - - if (!in_initrd()) { - dual_timestamp_serialize(f, "userspace-timestamp", &m->userspace_timestamp); - dual_timestamp_serialize(f, "finish-timestamp", &m->finish_timestamp); - dual_timestamp_serialize(f, "security-start-timestamp", &m->security_start_timestamp); - dual_timestamp_serialize(f, "security-finish-timestamp", &m->security_finish_timestamp); - dual_timestamp_serialize(f, "generators-start-timestamp", &m->generators_start_timestamp); - dual_timestamp_serialize(f, "generators-finish-timestamp", &m->generators_finish_timestamp); - dual_timestamp_serialize(f, "units-load-start-timestamp", &m->units_load_start_timestamp); - dual_timestamp_serialize(f, "units-load-finish-timestamp", &m->units_load_finish_timestamp); - } - - if (!switching_root) { - STRV_FOREACH(e, m->environment) { - _cleanup_free_ char *ce; - - ce = cescape(*e); - if (!ce) - return -ENOMEM; - - fprintf(f, "env=%s\n", *e); - } - } - - if (m->notify_fd >= 0) { - int copy; - - copy = fdset_put_dup(fds, m->notify_fd); - if (copy < 0) - return copy; - - fprintf(f, "notify-fd=%i\n", copy); - fprintf(f, "notify-socket=%s\n", m->notify_socket); - } - - if (m->kdbus_fd >= 0) { - int copy; - - copy = fdset_put_dup(fds, m->kdbus_fd); - if (copy < 0) - return copy; - - fprintf(f, "kdbus-fd=%i\n", copy); - } - - bus_track_serialize(m->subscribed, f); - - fputc('\n', f); - - HASHMAP_FOREACH_KEY(u, t, m->units, i) { - if (u->id != t) - continue; - - /* Start marker */ - fputs(u->id, f); - fputc('\n', f); - - r = unit_serialize(u, f, fds, !switching_root); - if (r < 0) { - m->n_reloading --; - return r; - } - } - - assert(m->n_reloading > 0); - m->n_reloading --; - - if (ferror(f)) - return -EIO; - - r = bus_fdset_add_all(m, fds); - if (r < 0) - return r; - - return 0; -} - -int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { - int r = 0; - - assert(m); - assert(f); - - log_debug("Deserializing state..."); - - m->n_reloading ++; - - for (;;) { - char line[LINE_MAX], *l; - - if (!fgets(line, sizeof(line), f)) { - if (feof(f)) - r = 0; - else - r = -errno; - - goto finish; - } - - char_array_0(line); - l = strstrip(line); - - if (l[0] == 0) - break; - - if (startswith(l, "current-job-id=")) { - uint32_t id; - - if (safe_atou32(l+15, &id) < 0) - log_debug("Failed to parse current job id value %s", l+15); - else - m->current_job_id = MAX(m->current_job_id, id); - - } else if (startswith(l, "n-installed-jobs=")) { - uint32_t n; - - if (safe_atou32(l+17, &n) < 0) - log_debug("Failed to parse installed jobs counter %s", l+17); - else - m->n_installed_jobs += n; - - } else if (startswith(l, "n-failed-jobs=")) { - uint32_t n; - - if (safe_atou32(l+14, &n) < 0) - log_debug("Failed to parse failed jobs counter %s", l+14); - else - m->n_failed_jobs += n; - - } else if (startswith(l, "taint-usr=")) { - int b; - - b = parse_boolean(l+10); - if (b < 0) - log_debug("Failed to parse taint /usr flag %s", l+10); - else - m->taint_usr = m->taint_usr || b; - - } else if (startswith(l, "firmware-timestamp=")) - dual_timestamp_deserialize(l+19, &m->firmware_timestamp); - else if (startswith(l, "loader-timestamp=")) - dual_timestamp_deserialize(l+17, &m->loader_timestamp); - else if (startswith(l, "kernel-timestamp=")) - dual_timestamp_deserialize(l+17, &m->kernel_timestamp); - else if (startswith(l, "initrd-timestamp=")) - dual_timestamp_deserialize(l+17, &m->initrd_timestamp); - else if (startswith(l, "userspace-timestamp=")) - dual_timestamp_deserialize(l+20, &m->userspace_timestamp); - else if (startswith(l, "finish-timestamp=")) - dual_timestamp_deserialize(l+17, &m->finish_timestamp); - else if (startswith(l, "security-start-timestamp=")) - dual_timestamp_deserialize(l+25, &m->security_start_timestamp); - else if (startswith(l, "security-finish-timestamp=")) - dual_timestamp_deserialize(l+26, &m->security_finish_timestamp); - else if (startswith(l, "generators-start-timestamp=")) - dual_timestamp_deserialize(l+27, &m->generators_start_timestamp); - else if (startswith(l, "generators-finish-timestamp=")) - dual_timestamp_deserialize(l+28, &m->generators_finish_timestamp); - else if (startswith(l, "units-load-start-timestamp=")) - dual_timestamp_deserialize(l+27, &m->units_load_start_timestamp); - else if (startswith(l, "units-load-finish-timestamp=")) - dual_timestamp_deserialize(l+28, &m->units_load_finish_timestamp); - else if (startswith(l, "env=")) { - _cleanup_free_ char *uce = NULL; - char **e; - - r = cunescape(l + 4, UNESCAPE_RELAX, &uce); - if (r < 0) - goto finish; - - e = strv_env_set(m->environment, uce); - if (!e) { - r = -ENOMEM; - goto finish; - } - - strv_free(m->environment); - m->environment = e; - - } else if (startswith(l, "notify-fd=")) { - int fd; - - if (safe_atoi(l + 10, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_debug("Failed to parse notify fd: %s", l + 10); - else { - m->notify_event_source = sd_event_source_unref(m->notify_event_source); - safe_close(m->notify_fd); - m->notify_fd = fdset_remove(fds, fd); - } - - } else if (startswith(l, "notify-socket=")) { - char *n; - - n = strdup(l+14); - if (!n) { - r = -ENOMEM; - goto finish; - } - - free(m->notify_socket); - m->notify_socket = n; - - } else if (startswith(l, "kdbus-fd=")) { - int fd; - - if (safe_atoi(l + 9, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_debug("Failed to parse kdbus fd: %s", l + 9); - else { - safe_close(m->kdbus_fd); - m->kdbus_fd = fdset_remove(fds, fd); - } - - } else { - int k; - - k = bus_track_deserialize_item(&m->deserialized_subscribed, l); - if (k < 0) - log_debug_errno(k, "Failed to deserialize bus tracker object: %m"); - else if (k == 0) - log_debug("Unknown serialization item '%s'", l); - } - } - - for (;;) { - Unit *u; - char name[UNIT_NAME_MAX+2]; - - /* Start marker */ - if (!fgets(name, sizeof(name), f)) { - if (feof(f)) - r = 0; - else - r = -errno; - - goto finish; - } - - char_array_0(name); - - r = manager_load_unit(m, strstrip(name), NULL, NULL, &u); - if (r < 0) - goto finish; - - r = unit_deserialize(u, f, fds); - if (r < 0) - goto finish; - } - -finish: - if (ferror(f)) - r = -EIO; - - assert(m->n_reloading > 0); - m->n_reloading --; - - return r; -} - -int manager_reload(Manager *m) { - int r, q; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_fdset_free_ FDSet *fds = NULL; - - assert(m); - - r = manager_open_serialization(m, &f); - if (r < 0) - return r; - - m->n_reloading ++; - bus_manager_send_reloading(m, true); - - fds = fdset_new(); - if (!fds) { - m->n_reloading --; - return -ENOMEM; - } - - r = manager_serialize(m, f, fds, false); - if (r < 0) { - m->n_reloading --; - return r; - } - - if (fseeko(f, 0, SEEK_SET) < 0) { - m->n_reloading --; - return -errno; - } - - /* From here on there is no way back. */ - manager_clear_jobs_and_units(m); - manager_undo_generators(m); - lookup_paths_free(&m->lookup_paths); - - /* Find new unit paths */ - q = manager_run_generators(m); - if (q < 0 && r >= 0) - r = q; - - q = lookup_paths_init( - &m->lookup_paths, m->running_as, true, - NULL, - m->generator_unit_path, - m->generator_unit_path_early, - m->generator_unit_path_late); - if (q < 0 && r >= 0) - r = q; - - manager_build_unit_path_cache(m); - - /* First, enumerate what we can from all config files */ - manager_enumerate(m); - - /* Second, deserialize our stored data */ - q = manager_deserialize(m, f, fds); - if (q < 0 && r >= 0) - r = q; - - fclose(f); - f = NULL; - - /* Re-register notify_fd as event source */ - q = manager_setup_notify(m); - if (q < 0 && r >= 0) - r = q; - - /* Third, fire things up! */ - manager_coldplug(m); - - /* Sync current state of bus names with our set of listening units */ - if (m->api_bus) - manager_sync_bus_names(m, m->api_bus); - - assert(m->n_reloading > 0); - m->n_reloading--; - - m->send_reloading_done = true; - - return r; -} - -bool manager_is_reloading_or_reexecuting(Manager *m) { - assert(m); - - return m->n_reloading != 0; -} - -void manager_reset_failed(Manager *m) { - Unit *u; - Iterator i; - - assert(m); - - HASHMAP_FOREACH(u, m->units, i) - unit_reset_failed(u); -} - -bool manager_unit_inactive_or_pending(Manager *m, const char *name) { - Unit *u; - - assert(m); - assert(name); - - /* Returns true if the unit is inactive or going down */ - u = manager_get_unit(m, name); - if (!u) - return true; - - return unit_inactive_or_pending(u); -} - -static void manager_notify_finished(Manager *m) { - char userspace[FORMAT_TIMESPAN_MAX], initrd[FORMAT_TIMESPAN_MAX], kernel[FORMAT_TIMESPAN_MAX], sum[FORMAT_TIMESPAN_MAX]; - usec_t firmware_usec, loader_usec, kernel_usec, initrd_usec, userspace_usec, total_usec; - - if (m->test_run) - return; - - if (m->running_as == MANAGER_SYSTEM && detect_container() <= 0) { - - /* Note that m->kernel_usec.monotonic is always at 0, - * and m->firmware_usec.monotonic and - * m->loader_usec.monotonic should be considered - * negative values. */ - - firmware_usec = m->firmware_timestamp.monotonic - m->loader_timestamp.monotonic; - loader_usec = m->loader_timestamp.monotonic - m->kernel_timestamp.monotonic; - userspace_usec = m->finish_timestamp.monotonic - m->userspace_timestamp.monotonic; - total_usec = m->firmware_timestamp.monotonic + m->finish_timestamp.monotonic; - - if (dual_timestamp_is_set(&m->initrd_timestamp)) { - - kernel_usec = m->initrd_timestamp.monotonic - m->kernel_timestamp.monotonic; - initrd_usec = m->userspace_timestamp.monotonic - m->initrd_timestamp.monotonic; - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED), - "KERNEL_USEC="USEC_FMT, kernel_usec, - "INITRD_USEC="USEC_FMT, initrd_usec, - "USERSPACE_USEC="USEC_FMT, userspace_usec, - LOG_MESSAGE("Startup finished in %s (kernel) + %s (initrd) + %s (userspace) = %s.", - format_timespan(kernel, sizeof(kernel), kernel_usec, USEC_PER_MSEC), - format_timespan(initrd, sizeof(initrd), initrd_usec, USEC_PER_MSEC), - format_timespan(userspace, sizeof(userspace), userspace_usec, USEC_PER_MSEC), - format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)), - NULL); - } else { - kernel_usec = m->userspace_timestamp.monotonic - m->kernel_timestamp.monotonic; - initrd_usec = 0; - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED), - "KERNEL_USEC="USEC_FMT, kernel_usec, - "USERSPACE_USEC="USEC_FMT, userspace_usec, - LOG_MESSAGE("Startup finished in %s (kernel) + %s (userspace) = %s.", - format_timespan(kernel, sizeof(kernel), kernel_usec, USEC_PER_MSEC), - format_timespan(userspace, sizeof(userspace), userspace_usec, USEC_PER_MSEC), - format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)), - NULL); - } - } else { - firmware_usec = loader_usec = initrd_usec = kernel_usec = 0; - total_usec = userspace_usec = m->finish_timestamp.monotonic - m->userspace_timestamp.monotonic; - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED), - "USERSPACE_USEC="USEC_FMT, userspace_usec, - LOG_MESSAGE("Startup finished in %s.", - format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)), - NULL); - } - - bus_manager_send_finished(m, firmware_usec, loader_usec, kernel_usec, initrd_usec, userspace_usec, total_usec); - - sd_notifyf(false, - "READY=1\n" - "STATUS=Startup finished in %s.", - format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)); -} - -void manager_check_finished(Manager *m) { - assert(m); - - if (m->n_reloading > 0) - return; - - /* Verify that we are actually running currently. Initially - * the exit code is set to invalid, and during operation it is - * then set to MANAGER_OK */ - if (m->exit_code != MANAGER_OK) - return; - - if (hashmap_size(m->jobs) > 0) { - if (m->jobs_in_progress_event_source) - /* Ignore any failure, this is only for feedback */ - (void) sd_event_source_set_time(m->jobs_in_progress_event_source, now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_WAIT_USEC); - - return; - } - - manager_flip_auto_status(m, false); - - /* Notify Type=idle units that we are done now */ - manager_close_idle_pipe(m); - - /* Turn off confirm spawn now */ - m->confirm_spawn = false; - - /* No need to update ask password status when we're going non-interactive */ - manager_close_ask_password(m); - - /* This is no longer the first boot */ - manager_set_first_boot(m, false); - - if (dual_timestamp_is_set(&m->finish_timestamp)) - return; - - dual_timestamp_get(&m->finish_timestamp); - - manager_notify_finished(m); - - manager_invalidate_startup_units(m); -} - -static int create_generator_dir(Manager *m, char **generator, const char *name) { - char *p; - int r; - - assert(m); - assert(generator); - assert(name); - - if (*generator) - return 0; - - if (m->running_as == MANAGER_SYSTEM && getpid() == 1) { - /* systemd --system, not running --test */ - - p = strappend("/run/systemd/", name); - if (!p) - return log_oom(); - - r = mkdir_p_label(p, 0755); - if (r < 0) { - log_error_errno(r, "Failed to create generator directory %s: %m", p); - free(p); - return r; - } - } else if (m->running_as == MANAGER_USER) { - const char *s = NULL; - - s = getenv("XDG_RUNTIME_DIR"); - if (!s) - return -EINVAL; - p = strjoin(s, "/systemd/", name, NULL); - if (!p) - return log_oom(); - - r = mkdir_p_label(p, 0755); - if (r < 0) { - log_error_errno(r, "Failed to create generator directory %s: %m", p); - free(p); - return r; - } - } else { - /* systemd --system --test */ - - p = strjoin("/tmp/systemd-", name, ".XXXXXX", NULL); - if (!p) - return log_oom(); - - if (!mkdtemp(p)) { - log_error_errno(errno, "Failed to create generator directory %s: %m", p); - free(p); - return -errno; - } - } - - *generator = p; - return 0; -} - -static void trim_generator_dir(Manager *m, char **generator) { - assert(m); - assert(generator); - - if (!*generator) - return; - - if (rmdir(*generator) >= 0) - *generator = mfree(*generator); - - return; -} - -static int manager_run_generators(Manager *m) { - _cleanup_strv_free_ char **paths = NULL; - const char *argv[5]; - char **path; - int r; - - assert(m); - - if (m->test_run) - return 0; - - paths = generator_paths(m->running_as); - if (!paths) - return log_oom(); - - /* Optimize by skipping the whole process by not creating output directories - * if no generators are found. */ - STRV_FOREACH(path, paths) { - r = access(*path, F_OK); - if (r == 0) - goto found; - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open generator directory %s: %m", *path); - } - return 0; - - found: - r = create_generator_dir(m, &m->generator_unit_path, "generator"); - if (r < 0) - goto finish; - - r = create_generator_dir(m, &m->generator_unit_path_early, "generator.early"); - if (r < 0) - goto finish; - - r = create_generator_dir(m, &m->generator_unit_path_late, "generator.late"); - if (r < 0) - goto finish; - - argv[0] = NULL; /* Leave this empty, execute_directory() will fill something in */ - argv[1] = m->generator_unit_path; - argv[2] = m->generator_unit_path_early; - argv[3] = m->generator_unit_path_late; - argv[4] = NULL; - - RUN_WITH_UMASK(0022) - execute_directories((const char* const*) paths, DEFAULT_TIMEOUT_USEC, (char**) argv); - -finish: - trim_generator_dir(m, &m->generator_unit_path); - trim_generator_dir(m, &m->generator_unit_path_early); - trim_generator_dir(m, &m->generator_unit_path_late); - return r; -} - -static void remove_generator_dir(Manager *m, char **generator) { - assert(m); - assert(generator); - - if (!*generator) - return; - - strv_remove(m->lookup_paths.unit_path, *generator); - (void) rm_rf(*generator, REMOVE_ROOT); - - *generator = mfree(*generator); -} - -static void manager_undo_generators(Manager *m) { - assert(m); - - remove_generator_dir(m, &m->generator_unit_path); - remove_generator_dir(m, &m->generator_unit_path_early); - remove_generator_dir(m, &m->generator_unit_path_late); -} - -int manager_environment_add(Manager *m, char **minus, char **plus) { - char **a = NULL, **b = NULL, **l; - assert(m); - - l = m->environment; - - if (!strv_isempty(minus)) { - a = strv_env_delete(l, 1, minus); - if (!a) - return -ENOMEM; - - l = a; - } - - if (!strv_isempty(plus)) { - b = strv_env_merge(2, l, plus); - if (!b) { - strv_free(a); - return -ENOMEM; - } - - l = b; - } - - if (m->environment != l) - strv_free(m->environment); - if (a != l) - strv_free(a); - if (b != l) - strv_free(b); - - m->environment = l; - manager_clean_environment(m); - strv_sort(m->environment); - - return 0; -} - -int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit) { - int i; - - assert(m); - - for (i = 0; i < _RLIMIT_MAX; i++) { - m->rlimit[i] = mfree(m->rlimit[i]); - - if (!default_rlimit[i]) - continue; - - m->rlimit[i] = newdup(struct rlimit, default_rlimit[i], 1); - if (!m->rlimit[i]) - return -ENOMEM; - } - - return 0; -} - -void manager_recheck_journal(Manager *m) { - Unit *u; - - assert(m); - - if (m->running_as != MANAGER_SYSTEM) - return; - - u = manager_get_unit(m, SPECIAL_JOURNALD_SOCKET); - if (u && SOCKET(u)->state != SOCKET_RUNNING) { - log_close_journal(); - return; - } - - u = manager_get_unit(m, SPECIAL_JOURNALD_SERVICE); - if (u && SERVICE(u)->state != SERVICE_RUNNING) { - log_close_journal(); - return; - } - - /* Hmm, OK, so the socket is fully up and the service is up - * too, then let's make use of the thing. */ - log_open(); -} - -void manager_set_show_status(Manager *m, ShowStatus mode) { - assert(m); - assert(IN_SET(mode, SHOW_STATUS_AUTO, SHOW_STATUS_NO, SHOW_STATUS_YES, SHOW_STATUS_TEMPORARY)); - - if (m->running_as != MANAGER_SYSTEM) - return; - - if (m->show_status != mode) - log_debug("%s showing of status.", - mode == SHOW_STATUS_NO ? "Disabling" : "Enabling"); - m->show_status = mode; - - if (mode > 0) - (void) touch("/run/systemd/show-status"); - else - (void) unlink("/run/systemd/show-status"); -} - -static bool manager_get_show_status(Manager *m, StatusType type) { - assert(m); - - if (m->running_as != MANAGER_SYSTEM) - return false; - - if (m->no_console_output) - return false; - - if (!IN_SET(manager_state(m), MANAGER_INITIALIZING, MANAGER_STARTING, MANAGER_STOPPING)) - return false; - - /* If we cannot find out the status properly, just proceed. */ - if (type != STATUS_TYPE_EMERGENCY && manager_check_ask_password(m) > 0) - return false; - - if (m->show_status > 0) - return true; - - return false; -} - -void manager_set_first_boot(Manager *m, bool b) { - assert(m); - - if (m->running_as != MANAGER_SYSTEM) - return; - - if (m->first_boot != (int) b) { - if (b) - (void) touch("/run/systemd/first-boot"); - else - (void) unlink("/run/systemd/first-boot"); - } - - m->first_boot = b; -} - -void manager_status_printf(Manager *m, StatusType type, const char *status, const char *format, ...) { - va_list ap; - - /* If m is NULL, assume we're after shutdown and let the messages through. */ - - if (m && !manager_get_show_status(m, type)) - return; - - /* XXX We should totally drop the check for ephemeral here - * and thus effectively make 'Type=idle' pointless. */ - if (type == STATUS_TYPE_EPHEMERAL && m && m->n_on_console > 0) - return; - - va_start(ap, format); - status_vprintf(status, true, type == STATUS_TYPE_EPHEMERAL, format, ap); - va_end(ap); -} - -Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path) { - char p[strlen(path)+1]; - - assert(m); - assert(path); - - strcpy(p, path); - path_kill_slashes(p); - - return hashmap_get(m->units_requiring_mounts_for, streq(p, "/") ? "" : p); -} - -const char *manager_get_runtime_prefix(Manager *m) { - assert(m); - - return m->running_as == MANAGER_SYSTEM ? - "/run" : - getenv("XDG_RUNTIME_DIR"); -} - -int manager_update_failed_units(Manager *m, Unit *u, bool failed) { - unsigned size; - int r; - - assert(m); - assert(u->manager == m); - - size = set_size(m->failed_units); - - if (failed) { - r = set_ensure_allocated(&m->failed_units, NULL); - if (r < 0) - return log_oom(); - - if (set_put(m->failed_units, u) < 0) - return log_oom(); - } else - (void) set_remove(m->failed_units, u); - - if (set_size(m->failed_units) != size) - bus_manager_send_change_signal(m); - - return 0; -} - -ManagerState manager_state(Manager *m) { - Unit *u; - - assert(m); - - /* Did we ever finish booting? If not then we are still starting up */ - if (!dual_timestamp_is_set(&m->finish_timestamp)) { - - u = manager_get_unit(m, SPECIAL_BASIC_TARGET); - if (!u || !UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) - return MANAGER_INITIALIZING; - - return MANAGER_STARTING; - } - - /* Is the special shutdown target queued? If so, we are in shutdown state */ - u = manager_get_unit(m, SPECIAL_SHUTDOWN_TARGET); - if (u && u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)) - return MANAGER_STOPPING; - - /* Are the rescue or emergency targets active or queued? If so we are in maintenance state */ - u = manager_get_unit(m, SPECIAL_RESCUE_TARGET); - if (u && (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)) || - (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)))) - return MANAGER_MAINTENANCE; - - u = manager_get_unit(m, SPECIAL_EMERGENCY_TARGET); - if (u && (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)) || - (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)))) - return MANAGER_MAINTENANCE; - - /* Are there any failed units? If so, we are in degraded mode */ - if (set_size(m->failed_units) > 0) - return MANAGER_DEGRADED; - - return MANAGER_RUNNING; -} - -static const char *const manager_state_table[_MANAGER_STATE_MAX] = { - [MANAGER_INITIALIZING] = "initializing", - [MANAGER_STARTING] = "starting", - [MANAGER_RUNNING] = "running", - [MANAGER_DEGRADED] = "degraded", - [MANAGER_MAINTENANCE] = "maintenance", - [MANAGER_STOPPING] = "stopping", -}; - -DEFINE_STRING_TABLE_LOOKUP(manager_state, ManagerState); diff --git a/src/core/manager.h b/src/core/manager.h deleted file mode 100644 index 9803f73129..0000000000 --- a/src/core/manager.h +++ /dev/null @@ -1,376 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#include "sd-bus.h" -#include "sd-event.h" - -#include "cgroup-util.h" -#include "fdset.h" -#include "hashmap.h" -#include "list.h" -#include "ratelimit.h" - -/* Enforce upper limit how many names we allow */ -#define MANAGER_MAX_NAMES 131072 /* 128K */ - -typedef struct Manager Manager; - -typedef enum ManagerState { - MANAGER_INITIALIZING, - MANAGER_STARTING, - MANAGER_RUNNING, - MANAGER_DEGRADED, - MANAGER_MAINTENANCE, - MANAGER_STOPPING, - _MANAGER_STATE_MAX, - _MANAGER_STATE_INVALID = -1 -} ManagerState; - -typedef enum ManagerExitCode { - MANAGER_OK, - MANAGER_EXIT, - MANAGER_RELOAD, - MANAGER_REEXECUTE, - MANAGER_REBOOT, - MANAGER_POWEROFF, - MANAGER_HALT, - MANAGER_KEXEC, - MANAGER_SWITCH_ROOT, - _MANAGER_EXIT_CODE_MAX, - _MANAGER_EXIT_CODE_INVALID = -1 -} ManagerExitCode; - -typedef enum StatusType { - STATUS_TYPE_EPHEMERAL, - STATUS_TYPE_NORMAL, - STATUS_TYPE_EMERGENCY, -} StatusType; - -#include "execute.h" -#include "job.h" -#include "path-lookup.h" -#include "show-status.h" -#include "unit-name.h" - -struct Manager { - /* Note that the set of units we know of is allowed to be - * inconsistent. However the subset of it that is loaded may - * not, and the list of jobs may neither. */ - - /* Active jobs and units */ - Hashmap *units; /* name string => Unit object n:1 */ - Hashmap *jobs; /* job id => Job object 1:1 */ - - /* To make it easy to iterate through the units of a specific - * type we maintain a per type linked list */ - LIST_HEAD(Unit, units_by_type[_UNIT_TYPE_MAX]); - - /* Units that need to be loaded */ - LIST_HEAD(Unit, load_queue); /* this is actually more a stack than a queue, but uh. */ - - /* Jobs that need to be run */ - LIST_HEAD(Job, run_queue); /* more a stack than a queue, too */ - - /* Units and jobs that have not yet been announced via - * D-Bus. When something about a job changes it is added here - * if it is not in there yet. This allows easy coalescing of - * D-Bus change signals. */ - LIST_HEAD(Unit, dbus_unit_queue); - LIST_HEAD(Job, dbus_job_queue); - - /* Units to remove */ - LIST_HEAD(Unit, cleanup_queue); - - /* Units to check when doing GC */ - LIST_HEAD(Unit, gc_queue); - - /* Units that should be realized */ - LIST_HEAD(Unit, cgroup_queue); - - sd_event *event; - - /* We use two hash tables here, since the same PID might be - * watched by two different units: once the unit that forked - * it off, and possibly a different unit to which it was - * joined as cgroup member. Since we know that it is either - * one or two units for each PID we just use to hashmaps - * here. */ - Hashmap *watch_pids1; /* pid => Unit object n:1 */ - Hashmap *watch_pids2; /* pid => Unit object n:1 */ - - /* A set contains all units which cgroup should be refreshed after startup */ - Set *startup_units; - - /* A set which contains all currently failed units */ - Set *failed_units; - - sd_event_source *run_queue_event_source; - - char *notify_socket; - int notify_fd; - sd_event_source *notify_event_source; - - int signal_fd; - sd_event_source *signal_event_source; - - int time_change_fd; - sd_event_source *time_change_event_source; - - sd_event_source *jobs_in_progress_event_source; - - LookupPaths lookup_paths; - Set *unit_path_cache; - - char **environment; - - usec_t runtime_watchdog; - usec_t shutdown_watchdog; - - dual_timestamp firmware_timestamp; - dual_timestamp loader_timestamp; - dual_timestamp kernel_timestamp; - dual_timestamp initrd_timestamp; - dual_timestamp userspace_timestamp; - dual_timestamp finish_timestamp; - - dual_timestamp security_start_timestamp; - dual_timestamp security_finish_timestamp; - dual_timestamp generators_start_timestamp; - dual_timestamp generators_finish_timestamp; - dual_timestamp units_load_start_timestamp; - dual_timestamp units_load_finish_timestamp; - - char *generator_unit_path; - char *generator_unit_path_early; - char *generator_unit_path_late; - - struct udev* udev; - - /* Data specific to the device subsystem */ - struct udev_monitor* udev_monitor; - sd_event_source *udev_event_source; - Hashmap *devices_by_sysfs; - - /* Data specific to the mount subsystem */ - struct libmnt_monitor *mount_monitor; - sd_event_source *mount_event_source; - - /* Data specific to the swap filesystem */ - FILE *proc_swaps; - sd_event_source *swap_event_source; - Hashmap *swaps_by_devnode; - - /* Data specific to the D-Bus subsystem */ - sd_bus *api_bus, *system_bus; - Set *private_buses; - int private_listen_fd; - sd_event_source *private_listen_event_source; - - /* Contains all the clients that are subscribed to signals via - the API bus. Note that private bus connections are always - considered subscribes, since they last for very short only, - and it is much simpler that way. */ - sd_bus_track *subscribed; - char **deserialized_subscribed; - - /* This is used during reloading: before the reload we queue - * the reply message here, and afterwards we send it */ - sd_bus_message *queued_message; - - Hashmap *watch_bus; /* D-Bus names => Unit object n:1 */ - - bool send_reloading_done; - - uint32_t current_job_id; - uint32_t default_unit_job_id; - - /* Data specific to the Automount subsystem */ - int dev_autofs_fd; - - /* Data specific to the cgroup subsystem */ - Hashmap *cgroup_unit; - CGroupMask cgroup_supported; - char *cgroup_root; - - /* Notifications from cgroups, when the unified hierarchy is - * used is done via inotify. */ - int cgroup_inotify_fd; - sd_event_source *cgroup_inotify_event_source; - Hashmap *cgroup_inotify_wd_unit; - - /* Make sure the user cannot accidentally unmount our cgroup - * file system */ - int pin_cgroupfs_fd; - - int gc_marker; - unsigned n_in_gc_queue; - - /* Flags */ - ManagerRunningAs running_as; - ManagerExitCode exit_code:5; - - bool dispatching_load_queue:1; - bool dispatching_dbus_queue:1; - - bool taint_usr:1; - - bool test_run:1; - - /* If non-zero, exit with the following value when the systemd - * process terminate. Useful for containers: systemd-nspawn could get - * the return value. */ - uint8_t return_value; - - ShowStatus show_status; - bool confirm_spawn; - bool no_console_output; - - ExecOutput default_std_output, default_std_error; - - usec_t default_restart_usec, default_timeout_start_usec, default_timeout_stop_usec; - - usec_t default_start_limit_interval; - unsigned default_start_limit_burst; - - bool default_cpu_accounting; - bool default_memory_accounting; - bool default_blockio_accounting; - bool default_tasks_accounting; - - uint64_t default_tasks_max; - usec_t default_timer_accuracy_usec; - - struct rlimit *rlimit[_RLIMIT_MAX]; - - /* non-zero if we are reloading or reexecuting, */ - int n_reloading; - - unsigned n_installed_jobs; - unsigned n_failed_jobs; - - /* Jobs in progress watching */ - unsigned n_running_jobs; - unsigned n_on_console; - unsigned jobs_in_progress_iteration; - - /* Do we have any outstanding password prompts? */ - int have_ask_password; - int ask_password_inotify_fd; - sd_event_source *ask_password_event_source; - - /* Type=idle pipes */ - int idle_pipe[4]; - sd_event_source *idle_pipe_event_source; - - char *switch_root; - char *switch_root_init; - - /* This maps all possible path prefixes to the units needing - * them. It's a hashmap with a path string as key and a Set as - * value where Unit objects are contained. */ - Hashmap *units_requiring_mounts_for; - - /* Reference to the kdbus bus control fd */ - int kdbus_fd; - - /* Used for processing polkit authorization responses */ - Hashmap *polkit_registry; - - /* When the user hits C-A-D more than 7 times per 2s, reboot immediately... */ - RateLimit ctrl_alt_del_ratelimit; - - const char *unit_log_field; - const char *unit_log_format_string; - - int first_boot; -}; - -int manager_new(ManagerRunningAs running_as, bool test_run, Manager **m); -Manager* manager_free(Manager *m); - -void manager_enumerate(Manager *m); -int manager_startup(Manager *m, FILE *serialization, FDSet *fds); - -Job *manager_get_job(Manager *m, uint32_t id); -Unit *manager_get_unit(Manager *m, const char *name); - -int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j); - -int manager_load_unit_prepare(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **_ret); -int manager_load_unit(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **_ret); -int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u); - -int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_error *e, Job **_ret); -int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, sd_bus_error *e, Job **_ret); -int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Job **ret); - -void manager_dump_units(Manager *s, FILE *f, const char *prefix); -void manager_dump_jobs(Manager *s, FILE *f, const char *prefix); - -void manager_clear_jobs(Manager *m); - -unsigned manager_dispatch_load_queue(Manager *m); - -int manager_environment_add(Manager *m, char **minus, char **plus); -int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit); - -int manager_loop(Manager *m); - -int manager_open_serialization(Manager *m, FILE **_f); - -int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root); -int manager_deserialize(Manager *m, FILE *f, FDSet *fds); - -int manager_reload(Manager *m); - -bool manager_is_reloading_or_reexecuting(Manager *m) _pure_; - -void manager_reset_failed(Manager *m); - -void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success); -void manager_send_unit_plymouth(Manager *m, Unit *u); - -bool manager_unit_inactive_or_pending(Manager *m, const char *name); - -void manager_check_finished(Manager *m); - -void manager_recheck_journal(Manager *m); - -void manager_set_show_status(Manager *m, ShowStatus mode); -void manager_set_first_boot(Manager *m, bool b); - -void manager_status_printf(Manager *m, StatusType type, const char *status, const char *format, ...) _printf_(4,5); -void manager_flip_auto_status(Manager *m, bool enable); - -Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path); - -const char *manager_get_runtime_prefix(Manager *m); - -ManagerState manager_state(Manager *m); - -int manager_update_failed_units(Manager *m, Unit *u, bool failed); - -const char *manager_state_to_string(ManagerState m) _const_; -ManagerState manager_state_from_string(const char *s) _pure_; diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c deleted file mode 100644 index de1a361cc4..0000000000 --- a/src/core/mount-setup.c +++ /dev/null @@ -1,412 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "dev-setup.h" -#include "efivars.h" -#include "label.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "mount-setup.h" -#include "mount-util.h" -#include "path-util.h" -#include "set.h" -#include "smack-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" -#include "virt.h" - -typedef enum MountMode { - MNT_NONE = 0, - MNT_FATAL = 1 << 0, - MNT_IN_CONTAINER = 1 << 1, -} MountMode; - -typedef struct MountPoint { - const char *what; - const char *where; - const char *type; - const char *options; - unsigned long flags; - bool (*condition_fn)(void); - MountMode mode; -} MountPoint; - -/* The first three entries we might need before SELinux is up. The - * fourth (securityfs) is needed by IMA to load a custom policy. The - * other ones we can delay until SELinux and IMA are loaded. When - * SMACK is enabled we need smackfs, too, so it's a fifth one. */ -#ifdef HAVE_SMACK -#define N_EARLY_MOUNT 5 -#else -#define N_EARLY_MOUNT 4 -#endif - -static const MountPoint mount_table[] = { - { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - NULL, MNT_FATAL|MNT_IN_CONTAINER }, - { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - NULL, MNT_FATAL|MNT_IN_CONTAINER }, - { "devtmpfs", "/dev", "devtmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, - NULL, MNT_FATAL|MNT_IN_CONTAINER }, - { "securityfs", "/sys/kernel/security", "securityfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - NULL, MNT_NONE }, -#ifdef HAVE_SMACK - { "smackfs", "/sys/fs/smackfs", "smackfs", "smackfsdef=*", MS_NOSUID|MS_NOEXEC|MS_NODEV, - mac_smack_use, MNT_FATAL }, - { "tmpfs", "/dev/shm", "tmpfs", "mode=1777,smackfsroot=*", MS_NOSUID|MS_NODEV|MS_STRICTATIME, - mac_smack_use, MNT_FATAL }, -#endif - { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, - NULL, MNT_FATAL|MNT_IN_CONTAINER }, - { "devpts", "/dev/pts", "devpts", "mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, - NULL, MNT_IN_CONTAINER }, -#ifdef HAVE_SMACK - { "tmpfs", "/run", "tmpfs", "mode=755,smackfsroot=*", MS_NOSUID|MS_NODEV|MS_STRICTATIME, - mac_smack_use, MNT_FATAL }, -#endif - { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, - NULL, MNT_FATAL|MNT_IN_CONTAINER }, - { "cgroup", "/sys/fs/cgroup", "cgroup", "__DEVEL__sane_behavior", MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_unified_wanted, MNT_FATAL|MNT_IN_CONTAINER }, - { "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, - cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, - { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd,xattr", MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_legacy_wanted, MNT_IN_CONTAINER }, - { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, - cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, - { "pstore", "/sys/fs/pstore", "pstore", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, - NULL, MNT_NONE }, -#ifdef ENABLE_EFI - { "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, - * we just list them here so that we know that we should ignore them */ - -static const char ignore_paths[] = - /* SELinux file systems */ - "/sys/fs/selinux\0" - /* Container bind mounts */ - "/proc/sys\0" - "/dev/console\0" - "/proc/kmsg\0"; - -bool mount_point_is_api(const char *path) { - unsigned i; - - /* Checks if this mount point is considered "API", and hence - * should be ignored */ - - for (i = 0; i < ELEMENTSOF(mount_table); i ++) - if (path_equal(path, mount_table[i].where)) - return true; - - return path_startswith(path, "/sys/fs/cgroup/"); -} - -bool mount_point_ignore(const char *path) { - const char *i; - - NULSTR_FOREACH(i, ignore_paths) - if (path_equal(path, i)) - return true; - - return false; -} - -static int mount_one(const MountPoint *p, bool relabel) { - int r; - - assert(p); - - if (p->condition_fn && !p->condition_fn()) - return 0; - - /* Relabel first, just in case */ - if (relabel) - (void) label_fix(p->where, true, true); - - r = path_is_mount_point(p->where, AT_SYMLINK_FOLLOW); - if (r < 0 && r != -ENOENT) { - log_full_errno((p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG, r, "Failed to determine whether %s is a mount point: %m", p->where); - return (p->mode & MNT_FATAL) ? r : 0; - } - if (r > 0) - return 0; - - /* Skip securityfs in a container */ - if (!(p->mode & MNT_IN_CONTAINER) && detect_container() > 0) - return 0; - - /* The access mode here doesn't really matter too much, since - * the mounted file system will take precedence anyway. */ - if (relabel) - (void) mkdir_p_label(p->where, 0755); - else - (void) mkdir_p(p->where, 0755); - - log_debug("Mounting %s to %s of type %s with options %s.", - p->what, - p->where, - p->type, - strna(p->options)); - - if (mount(p->what, - p->where, - p->type, - p->flags, - p->options) < 0) { - log_full_errno((p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG, errno, "Failed to mount %s at %s: %m", p->type, p->where); - return (p->mode & MNT_FATAL) ? -errno : 0; - } - - /* Relabel again, since we now mounted something fresh here */ - if (relabel) - (void) label_fix(p->where, false, false); - - return 1; -} - -static int mount_points_setup(unsigned n, bool loaded_policy) { - unsigned i; - int r = 0; - - for (i = 0; i < n; i ++) { - int j; - - j = mount_one(mount_table + i, loaded_policy); - if (j != 0 && r >= 0) - r = j; - } - - return r; -} - -int mount_setup_early(void) { - assert_cc(N_EARLY_MOUNT <= ELEMENTSOF(mount_table)); - - /* Do a minimal mount of /proc and friends to enable the most - * basic stuff, such as SELinux */ - return mount_points_setup(N_EARLY_MOUNT, false); -} - -int mount_cgroup_controllers(char ***join_controllers) { - _cleanup_set_free_free_ Set *controllers = NULL; - int r; - - if (!cg_is_legacy_wanted()) - return 0; - - /* Mount all available cgroup controllers that are built into the kernel. */ - - controllers = set_new(&string_hash_ops); - if (!controllers) - return log_oom(); - - r = cg_kernel_controllers(controllers); - if (r < 0) - return log_error_errno(r, "Failed to enumerate cgroup controllers: %m"); - - for (;;) { - _cleanup_free_ char *options = NULL, *controller = NULL, *where = NULL; - MountPoint p = { - .what = "cgroup", - .type = "cgroup", - .flags = MS_NOSUID|MS_NOEXEC|MS_NODEV, - .mode = MNT_IN_CONTAINER, - }; - char ***k = NULL; - - controller = set_steal_first(controllers); - if (!controller) - break; - - if (join_controllers) - for (k = join_controllers; *k; k++) - if (strv_find(*k, controller)) - break; - - if (k && *k) { - char **i, **j; - - for (i = *k, j = *k; *i; i++) { - - if (!streq(*i, controller)) { - _cleanup_free_ char *t; - - t = set_remove(controllers, *i); - if (!t) { - free(*i); - continue; - } - } - - *(j++) = *i; - } - - *j = NULL; - - options = strv_join(*k, ","); - if (!options) - return log_oom(); - } else { - options = controller; - controller = NULL; - } - - where = strappend("/sys/fs/cgroup/", options); - if (!where) - return log_oom(); - - p.where = where; - p.options = options; - - r = mount_one(&p, true); - if (r < 0) - return r; - - if (r > 0 && k && *k) { - char **i; - - for (i = *k; *i; i++) { - _cleanup_free_ char *t = NULL; - - t = strappend("/sys/fs/cgroup/", *i); - if (!t) - return log_oom(); - - r = symlink(options, t); - if (r >= 0) { -#ifdef SMACK_RUN_LABEL - _cleanup_free_ char *src; - src = strappend("/sys/fs/cgroup/", options); - if (!src) - return log_oom(); - r = mac_smack_copy(t, src); - if (r < 0 && r != -EOPNOTSUPP) - return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", src, t); -#endif - } else if (errno != EEXIST) - return log_error_errno(errno, "Failed to create symlink %s: %m", t); - } - } - } - - /* Now that we mounted everything, let's make the tmpfs the - * cgroup file systems are mounted into read-only. */ - (void) mount("tmpfs", "/sys/fs/cgroup", "tmpfs", MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755"); - - return 0; -} - -#if defined(HAVE_SELINUX) || defined(HAVE_SMACK) -static int nftw_cb( - const char *fpath, - const struct stat *sb, - int tflag, - struct FTW *ftwbuf) { - - /* No need to label /dev twice in a row... */ - if (_unlikely_(ftwbuf->level == 0)) - return FTW_CONTINUE; - - label_fix(fpath, false, false); - - /* /run/initramfs is static data and big, no need to - * dynamically relabel its contents at boot... */ - if (_unlikely_(ftwbuf->level == 1 && - tflag == FTW_D && - streq(fpath, "/run/initramfs"))) - return FTW_SKIP_SUBTREE; - - return FTW_CONTINUE; -}; -#endif - -int mount_setup(bool loaded_policy) { - int r = 0; - - r = mount_points_setup(ELEMENTSOF(mount_table), loaded_policy); - - if (r < 0) - return r; - -#if defined(HAVE_SELINUX) || defined(HAVE_SMACK) - /* Nodes in devtmpfs and /run need to be manually updated for - * the appropriate labels, after mounting. The other virtual - * API file systems like /sys and /proc do not need that, they - * use the same label for all their files. */ - if (loaded_policy) { - usec_t before_relabel, after_relabel; - char timespan[FORMAT_TIMESPAN_MAX]; - - before_relabel = now(CLOCK_MONOTONIC); - - nftw("/dev", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); - nftw("/run", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); - - after_relabel = now(CLOCK_MONOTONIC); - - log_info("Relabelled /dev and /run in %s.", - format_timespan(timespan, sizeof(timespan), after_relabel - before_relabel, 0)); - } -#endif - - /* Create a few default symlinks, which are normally created - * by udevd, but some scripts might need them before we start - * udevd. */ - dev_setup(NULL, UID_INVALID, GID_INVALID); - - /* Mark the root directory as shared in regards to mount - * propagation. The kernel defaults to "private", but we think - * it makes more sense to have a default of "shared" so that - * nspawn and the container tools work out of the box. If - * specific setups need other settings they can reset the - * propagation mode to private if needed. */ - if (detect_container() <= 0) - if (mount(NULL, "/", NULL, MS_REC|MS_SHARED, NULL) < 0) - log_warning_errno(errno, "Failed to set up the root directory for shared mount propagation: %m"); - - /* Create a few directories we always want around, Note that - * sd_booted() checks for /run/systemd/system, so this mkdir - * 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); - - return 0; -} diff --git a/src/core/mount-setup.h b/src/core/mount-setup.h deleted file mode 100644 index 647bd770ae..0000000000 --- a/src/core/mount-setup.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -int mount_setup_early(void); -int mount_setup(bool loaded_policy); - -int mount_cgroup_controllers(char ***join_controllers); - -bool mount_point_is_api(const char *path); -bool mount_point_ignore(const char *path); diff --git a/src/core/mount.c b/src/core/mount.c deleted file mode 100644 index de1075d3a0..0000000000 --- a/src/core/mount.c +++ /dev/null @@ -1,1873 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "sd-messages.h" - -#include "alloc-util.h" -#include "dbus-mount.h" -#include "escape.h" -#include "exit-status.h" -#include "formats-util.h" -#include "fstab-util.h" -#include "log.h" -#include "manager.h" -#include "mkdir.h" -#include "mount-setup.h" -#include "mount-util.h" -#include "mount.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit.h" - -#define RETRY_UMOUNT_MAX 32 - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct libmnt_table*, mnt_free_table); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct libmnt_iter*, mnt_free_iter); - -static const UnitActiveState state_translation_table[_MOUNT_STATE_MAX] = { - [MOUNT_DEAD] = UNIT_INACTIVE, - [MOUNT_MOUNTING] = UNIT_ACTIVATING, - [MOUNT_MOUNTING_DONE] = UNIT_ACTIVE, - [MOUNT_MOUNTED] = UNIT_ACTIVE, - [MOUNT_REMOUNTING] = UNIT_RELOADING, - [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING, - [MOUNT_MOUNTING_SIGTERM] = UNIT_DEACTIVATING, - [MOUNT_MOUNTING_SIGKILL] = UNIT_DEACTIVATING, - [MOUNT_REMOUNTING_SIGTERM] = UNIT_RELOADING, - [MOUNT_REMOUNTING_SIGKILL] = UNIT_RELOADING, - [MOUNT_UNMOUNTING_SIGTERM] = UNIT_DEACTIVATING, - [MOUNT_UNMOUNTING_SIGKILL] = UNIT_DEACTIVATING, - [MOUNT_FAILED] = UNIT_FAILED -}; - -static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); -static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); - -static bool mount_needs_network(const char *options, const char *fstype) { - if (fstab_test_option(options, "_netdev\0")) - return true; - - if (fstype && fstype_is_network(fstype)) - return true; - - return false; -} - -static bool mount_is_network(const MountParameters *p) { - assert(p); - - return mount_needs_network(p->options, p->fstype); -} - -static bool mount_is_bind(const MountParameters *p) { - assert(p); - - if (fstab_test_option(p->options, "bind\0" "rbind\0")) - return true; - - if (p->fstype && STR_IN_SET(p->fstype, "bind", "rbind")) - return true; - - return false; -} - -static bool mount_is_auto(const MountParameters *p) { - assert(p); - - return !fstab_test_option(p->options, "noauto\0"); -} - -static bool needs_quota(const MountParameters *p) { - assert(p); - - /* Quotas are not enabled on network filesystems, - * but we want them, for example, on storage connected via iscsi */ - if (p->fstype && fstype_is_network(p->fstype)) - return false; - - if (mount_is_bind(p)) - return false; - - return fstab_test_option(p->options, - "usrquota\0" "grpquota\0" "quota\0" "usrjquota\0" "grpjquota\0"); -} - -static void mount_init(Unit *u) { - Mount *m = MOUNT(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - m->timeout_usec = u->manager->default_timeout_start_usec; - m->directory_mode = 0755; - - if (unit_has_name(u, "-.mount")) { - /* Don't allow start/stop for root directory */ - u->refuse_manual_start = true; - u->refuse_manual_stop = true; - } else { - /* The stdio/kmsg bridge socket is on /, in order to avoid a - * dep loop, don't use kmsg logging for -.mount */ - m->exec_context.std_output = u->manager->default_std_output; - m->exec_context.std_error = u->manager->default_std_error; - } - - /* We need to make sure that /usr/bin/mount is always called - * in the same process group as us, so that the autofs kernel - * side doesn't send us another mount request while we are - * already trying to comply its last one. */ - m->exec_context.same_pgrp = true; - - m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; - - u->ignore_on_isolate = true; -} - -static int mount_arm_timer(Mount *m, usec_t usec) { - int r; - - assert(m); - - if (m->timer_event_source) { - r = sd_event_source_set_time(m->timer_event_source, usec); - if (r < 0) - return r; - - return sd_event_source_set_enabled(m->timer_event_source, SD_EVENT_ONESHOT); - } - - if (usec == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - UNIT(m)->manager->event, - &m->timer_event_source, - CLOCK_MONOTONIC, - usec, 0, - mount_dispatch_timer, m); - if (r < 0) - return r; - - (void) sd_event_source_set_description(m->timer_event_source, "mount-timer"); - - return 0; -} - -static void mount_unwatch_control_pid(Mount *m) { - assert(m); - - if (m->control_pid <= 0) - return; - - unit_unwatch_pid(UNIT(m), m->control_pid); - m->control_pid = 0; -} - -static void mount_parameters_done(MountParameters *p) { - assert(p); - - free(p->what); - free(p->options); - free(p->fstype); - - p->what = p->options = p->fstype = NULL; -} - -static void mount_done(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - m->where = mfree(m->where); - - mount_parameters_done(&m->parameters_proc_self_mountinfo); - mount_parameters_done(&m->parameters_fragment); - - m->exec_runtime = exec_runtime_unref(m->exec_runtime); - exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX); - m->control_command = NULL; - - mount_unwatch_control_pid(m); - - m->timer_event_source = sd_event_source_unref(m->timer_event_source); -} - -_pure_ static MountParameters* get_mount_parameters_fragment(Mount *m) { - assert(m); - - if (m->from_fragment) - return &m->parameters_fragment; - - return NULL; -} - -_pure_ static MountParameters* get_mount_parameters(Mount *m) { - assert(m); - - if (m->from_proc_self_mountinfo) - return &m->parameters_proc_self_mountinfo; - - return get_mount_parameters_fragment(m); -} - -static int mount_add_mount_links(Mount *m) { - _cleanup_free_ char *parent = NULL; - MountParameters *pm; - Unit *other; - Iterator i; - Set *s; - int r; - - assert(m); - - if (!path_equal(m->where, "/")) { - /* Adds in links to other mount points that might lie further - * up in the hierarchy */ - - parent = dirname_malloc(m->where); - if (!parent) - return -ENOMEM; - - r = unit_require_mounts_for(UNIT(m), parent); - if (r < 0) - return r; - } - - /* Adds in links to other mount points that might be needed - * for the source path (if this is a bind mount) to be - * available. */ - pm = get_mount_parameters_fragment(m); - if (pm && pm->what && - path_is_absolute(pm->what) && - !mount_is_network(pm)) { - - r = unit_require_mounts_for(UNIT(m), pm->what); - if (r < 0) - return r; - } - - /* Adds in links to other units that use this path or paths - * further down in the hierarchy */ - s = manager_get_units_requiring_mounts_for(UNIT(m)->manager, m->where); - SET_FOREACH(other, s, i) { - - if (other->load_state != UNIT_LOADED) - continue; - - if (other == UNIT(m)) - continue; - - r = unit_add_dependency(other, UNIT_AFTER, UNIT(m), true); - if (r < 0) - return r; - - if (UNIT(m)->fragment_path) { - /* If we have fragment configuration, then make this dependency required */ - r = unit_add_dependency(other, UNIT_REQUIRES, UNIT(m), true); - if (r < 0) - return r; - } - } - - return 0; -} - -static int mount_add_device_links(Mount *m) { - MountParameters *p; - bool device_wants_mount = false; - int r; - - assert(m); - - p = get_mount_parameters(m); - if (!p) - return 0; - - if (!p->what) - return 0; - - if (mount_is_bind(p)) - return 0; - - if (!is_device_path(p->what)) - return 0; - - /* /dev/root is a really weird thing, it's not a real device, - * but just a path the kernel exports for the root file system - * specified on the kernel command line. Ignore it here. */ - if (path_equal(p->what, "/dev/root")) - return 0; - - if (path_equal(m->where, "/")) - return 0; - - if (mount_is_auto(p) && UNIT(m)->manager->running_as == MANAGER_SYSTEM) - device_wants_mount = true; - - r = unit_add_node_link(UNIT(m), p->what, device_wants_mount, m->from_fragment ? UNIT_BINDS_TO : UNIT_REQUIRES); - if (r < 0) - return r; - - return 0; -} - -static int mount_add_quota_links(Mount *m) { - int r; - MountParameters *p; - - assert(m); - - if (UNIT(m)->manager->running_as != MANAGER_SYSTEM) - return 0; - - p = get_mount_parameters_fragment(m); - if (!p) - return 0; - - if (!needs_quota(p)) - return 0; - - r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTACHECK_SERVICE, NULL, true); - if (r < 0) - return r; - - r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTAON_SERVICE, NULL, true); - if (r < 0) - return r; - - return 0; -} - -static bool should_umount(Mount *m) { - MountParameters *p; - - if (path_equal(m->where, "/") || - path_equal(m->where, "/usr")) - return false; - - p = get_mount_parameters(m); - if (p && fstab_test_option(p->options, "x-initrd.mount\0") && - !in_initrd()) - return false; - - return true; -} - -static int mount_add_default_dependencies(Mount *m) { - MountParameters *p; - const char *after; - int r; - - assert(m); - - if (!UNIT(m)->default_dependencies) - return 0; - - if (UNIT(m)->manager->running_as != MANAGER_SYSTEM) - return 0; - - /* We do not add any default dependencies to / and /usr, since - * they are guaranteed to stay mounted the whole time, since - * our system is on it. Also, don't bother with anything - * mounted below virtual file systems, it's also going to be - * virtual, and hence not worth the effort. */ - if (path_equal(m->where, "/") || - path_equal(m->where, "/usr") || - path_startswith(m->where, "/proc") || - path_startswith(m->where, "/sys") || - path_startswith(m->where, "/dev")) - return 0; - - p = get_mount_parameters(m); - if (!p) - return 0; - - if (mount_is_network(p)) { - /* We order ourselves after network.target. This is - * primarily useful at shutdown: services that take - * down the network should order themselves before - * network.target, so that they are shut down only - * after this mount unit is stopped. */ - - r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, SPECIAL_NETWORK_TARGET, NULL, true); - if (r < 0) - return r; - - /* We pull in network-online.target, and order - * ourselves after it. This is useful at start-up to - * actively pull in tools that want to be started - * before we start mounting network file systems, and - * whose purpose it is to delay this until the network - * is "up". */ - - r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_WANTS, UNIT_AFTER, SPECIAL_NETWORK_ONLINE_TARGET, NULL, true); - if (r < 0) - return r; - - after = SPECIAL_REMOTE_FS_PRE_TARGET; - } else - after = SPECIAL_LOCAL_FS_PRE_TARGET; - - r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, after, NULL, true); - if (r < 0) - return r; - - if (should_umount(m)) { - r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); - if (r < 0) - return r; - } - - return 0; -} - -static int mount_verify(Mount *m) { - _cleanup_free_ char *e = NULL; - int r; - - assert(m); - - if (UNIT(m)->load_state != UNIT_LOADED) - return 0; - - if (!m->from_fragment && !m->from_proc_self_mountinfo) - return -ENOENT; - - r = unit_name_from_path(m->where, ".mount", &e); - if (r < 0) - return log_unit_error_errno(UNIT(m), r, "Failed to generate unit name from mount path: %m"); - - if (!unit_has_name(UNIT(m), e)) { - log_unit_error(UNIT(m), "Where= setting doesn't match unit name. Refusing."); - return -EINVAL; - } - - if (mount_point_is_api(m->where) || mount_point_ignore(m->where)) { - log_unit_error(UNIT(m), "Cannot create mount unit for API file system %s. Refusing.", m->where); - return -EINVAL; - } - - if (UNIT(m)->fragment_path && !m->parameters_fragment.what) { - log_unit_error(UNIT(m), "What= setting is missing. Refusing."); - return -EBADMSG; - } - - if (m->exec_context.pam_name && m->kill_context.kill_mode != KILL_CONTROL_GROUP) { - log_unit_error(UNIT(m), "Unit has PAM enabled. Kill mode must be set to control-group'. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int mount_add_extras(Mount *m) { - Unit *u = UNIT(m); - int r; - - assert(m); - - if (u->fragment_path) - m->from_fragment = true; - - if (!m->where) { - r = unit_name_to_path(u->id, &m->where); - if (r < 0) - return r; - } - - path_kill_slashes(m->where); - - if (!u->description) { - r = unit_set_description(u, m->where); - if (r < 0) - return r; - } - - r = mount_add_device_links(m); - if (r < 0) - return r; - - r = mount_add_mount_links(m); - if (r < 0) - return r; - - r = mount_add_quota_links(m); - if (r < 0) - return r; - - r = unit_patch_contexts(u); - if (r < 0) - return r; - - r = unit_add_exec_dependencies(u, &m->exec_context); - if (r < 0) - return r; - - r = unit_set_default_slice(u); - if (r < 0) - return r; - - r = mount_add_default_dependencies(m); - if (r < 0) - return r; - - return 0; -} - -static int mount_load(Unit *u) { - Mount *m = MOUNT(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - if (m->from_proc_self_mountinfo) - r = unit_load_fragment_and_dropin_optional(u); - else - r = unit_load_fragment_and_dropin(u); - - if (r < 0) - return r; - - /* This is a new unit? Then let's add in some extras */ - if (u->load_state == UNIT_LOADED) { - r = mount_add_extras(m); - if (r < 0) - return r; - } - - return mount_verify(m); -} - -static int mount_notify_automount(Mount *m, MountState old_state, MountState state) { - Unit *p; - int r; - Iterator i; - - assert(m); - - SET_FOREACH(p, UNIT(m)->dependencies[UNIT_TRIGGERED_BY], i) - if (p->type == UNIT_AUTOMOUNT) { - r = automount_update_mount(AUTOMOUNT(p), old_state, state); - if (r < 0) - return r; - } - - return 0; -} - -static void mount_set_state(Mount *m, MountState state) { - MountState old_state; - assert(m); - - old_state = m->state; - m->state = state; - - if (state != MOUNT_MOUNTING && - state != MOUNT_MOUNTING_DONE && - state != MOUNT_REMOUNTING && - state != MOUNT_UNMOUNTING && - state != MOUNT_MOUNTING_SIGTERM && - state != MOUNT_MOUNTING_SIGKILL && - state != MOUNT_UNMOUNTING_SIGTERM && - state != MOUNT_UNMOUNTING_SIGKILL && - state != MOUNT_REMOUNTING_SIGTERM && - state != MOUNT_REMOUNTING_SIGKILL) { - m->timer_event_source = sd_event_source_unref(m->timer_event_source); - mount_unwatch_control_pid(m); - m->control_command = NULL; - m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; - } - - mount_notify_automount(m, old_state, state); - - if (state != old_state) - log_unit_debug(UNIT(m), "Changed %s -> %s", mount_state_to_string(old_state), mount_state_to_string(state)); - - unit_notify(UNIT(m), state_translation_table[old_state], state_translation_table[state], m->reload_result == MOUNT_SUCCESS); - m->reload_result = MOUNT_SUCCESS; -} - -static int mount_coldplug(Unit *u) { - Mount *m = MOUNT(u); - MountState new_state = MOUNT_DEAD; - int r; - - assert(m); - assert(m->state == MOUNT_DEAD); - - if (m->deserialized_state != m->state) - new_state = m->deserialized_state; - else if (m->from_proc_self_mountinfo) - new_state = MOUNT_MOUNTED; - - if (new_state == m->state) - return 0; - - if (m->control_pid > 0 && - pid_is_unwaited(m->control_pid) && - IN_SET(new_state, - MOUNT_MOUNTING, - MOUNT_MOUNTING_DONE, - MOUNT_REMOUNTING, - MOUNT_UNMOUNTING, - MOUNT_MOUNTING_SIGTERM, - MOUNT_MOUNTING_SIGKILL, - MOUNT_UNMOUNTING_SIGTERM, - MOUNT_UNMOUNTING_SIGKILL, - MOUNT_REMOUNTING_SIGTERM, - MOUNT_REMOUNTING_SIGKILL)) { - - r = unit_watch_pid(UNIT(m), m->control_pid); - if (r < 0) - return r; - - r = mount_arm_timer(m, usec_add(u->state_change_timestamp.monotonic, m->timeout_usec)); - if (r < 0) - return r; - } - - mount_set_state(m, new_state); - return 0; -} - -static void mount_dump(Unit *u, FILE *f, const char *prefix) { - Mount *m = MOUNT(u); - MountParameters *p; - - assert(m); - assert(f); - - p = get_mount_parameters(m); - - fprintf(f, - "%sMount State: %s\n" - "%sResult: %s\n" - "%sWhere: %s\n" - "%sWhat: %s\n" - "%sFile System Type: %s\n" - "%sOptions: %s\n" - "%sFrom /proc/self/mountinfo: %s\n" - "%sFrom fragment: %s\n" - "%sDirectoryMode: %04o\n", - prefix, mount_state_to_string(m->state), - prefix, mount_result_to_string(m->result), - prefix, m->where, - prefix, p ? strna(p->what) : "n/a", - prefix, p ? strna(p->fstype) : "n/a", - prefix, p ? strna(p->options) : "n/a", - prefix, yes_no(m->from_proc_self_mountinfo), - prefix, yes_no(m->from_fragment), - prefix, m->directory_mode); - - if (m->control_pid > 0) - fprintf(f, - "%sControl PID: "PID_FMT"\n", - prefix, m->control_pid); - - exec_context_dump(&m->exec_context, f, prefix); - kill_context_dump(&m->kill_context, f, prefix); -} - -static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { - pid_t pid; - int r; - ExecParameters exec_params = { - .apply_permissions = true, - .apply_chroot = true, - .apply_tty_stdin = true, - .bus_endpoint_fd = -1, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, - }; - - assert(m); - assert(c); - assert(_pid); - - (void) unit_realize_cgroup(UNIT(m)); - if (m->reset_cpu_usage) { - (void) unit_reset_cpu_usage(UNIT(m)); - m->reset_cpu_usage = false; - } - - r = unit_setup_exec_runtime(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; - - exec_params.environment = UNIT(m)->manager->environment; - exec_params.confirm_spawn = UNIT(m)->manager->confirm_spawn; - exec_params.cgroup_supported = UNIT(m)->manager->cgroup_supported; - exec_params.cgroup_path = UNIT(m)->cgroup_path; - exec_params.cgroup_delegate = m->cgroup_context.delegate; - exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(m)->manager); - - r = exec_spawn(UNIT(m), - c, - &m->exec_context, - &exec_params, - m->exec_runtime, - &pid); - if (r < 0) - return r; - - r = unit_watch_pid(UNIT(m), pid); - if (r < 0) - /* FIXME: we need to do something here */ - return r; - - *_pid = pid; - - return 0; -} - -static void mount_enter_dead(Mount *m, MountResult f) { - assert(m); - - if (f != MOUNT_SUCCESS) - m->result = f; - - 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); -} - -static void mount_enter_mounted(Mount *m, MountResult f) { - assert(m); - - if (f != MOUNT_SUCCESS) - m->result = f; - - mount_set_state(m, MOUNT_MOUNTED); -} - -static void mount_enter_signal(Mount *m, MountState state, MountResult f) { - int r; - - assert(m); - - if (f != MOUNT_SUCCESS) - m->result = f; - - r = unit_kill_context( - UNIT(m), - &m->kill_context, - (state != MOUNT_MOUNTING_SIGTERM && state != MOUNT_UNMOUNTING_SIGTERM && state != MOUNT_REMOUNTING_SIGTERM) ? - KILL_KILL : KILL_TERMINATE, - -1, - m->control_pid, - false); - if (r < 0) - goto fail; - - if (r > 0) { - r = mount_arm_timer(m, usec_add(now(CLOCK_MONOTONIC), m->timeout_usec)); - if (r < 0) - goto fail; - - mount_set_state(m, state); - } else if (state == MOUNT_REMOUNTING_SIGTERM) - mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, MOUNT_SUCCESS); - else if (state == MOUNT_REMOUNTING_SIGKILL) - mount_enter_mounted(m, MOUNT_SUCCESS); - else if (state == MOUNT_MOUNTING_SIGTERM) - mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, MOUNT_SUCCESS); - else if (state == MOUNT_UNMOUNTING_SIGTERM) - mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_SUCCESS); - else - mount_enter_dead(m, MOUNT_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(m), r, "Failed to kill processes: %m"); - - if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL) - mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); - else - mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); -} - -static void mount_enter_unmounting(Mount *m) { - int r; - - assert(m); - - /* Start counting our attempts */ - if (!IN_SET(m->state, - MOUNT_UNMOUNTING, - MOUNT_UNMOUNTING_SIGTERM, - MOUNT_UNMOUNTING_SIGKILL)) - m->n_retry_umount = 0; - - m->control_command_id = MOUNT_EXEC_UNMOUNT; - m->control_command = m->exec_command + MOUNT_EXEC_UNMOUNT; - - r = exec_command_set(m->control_command, UMOUNT_PATH, m->where, NULL); - if (r < 0) - goto fail; - - mount_unwatch_control_pid(m); - - r = mount_spawn(m, m->control_command, &m->control_pid); - if (r < 0) - goto fail; - - mount_set_state(m, MOUNT_UNMOUNTING); - - return; - -fail: - log_unit_warning_errno(UNIT(m), r, "Failed to run 'umount' task: %m"); - mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); -} - -static int mount_get_opts(Mount *m, char **ret) { - return fstab_filter_options(m->parameters_fragment.options, - "nofail\0" "noauto\0" "auto\0", NULL, NULL, ret); -} - -static void mount_enter_mounting(Mount *m) { - int r; - MountParameters *p; - - assert(m); - - m->control_command_id = MOUNT_EXEC_MOUNT; - m->control_command = m->exec_command + MOUNT_EXEC_MOUNT; - - r = unit_fail_if_symlink(UNIT(m), m->where); - if (r < 0) - goto fail; - - (void) mkdir_p_label(m->where, m->directory_mode); - - unit_warn_if_dir_nonempty(UNIT(m), m->where); - - /* Create the source directory for bind-mounts if needed */ - p = get_mount_parameters_fragment(m); - if (p && mount_is_bind(p)) - (void) mkdir_p_label(p->what, m->directory_mode); - - if (m->from_fragment) { - _cleanup_free_ char *opts = NULL; - - r = mount_get_opts(m, &opts); - if (r < 0) - goto fail; - - r = exec_command_set(m->control_command, MOUNT_PATH, - m->parameters_fragment.what, m->where, NULL); - if (r >= 0 && m->sloppy_options) - r = exec_command_append(m->control_command, "-s", NULL); - if (r >= 0 && m->parameters_fragment.fstype) - r = exec_command_append(m->control_command, "-t", m->parameters_fragment.fstype, NULL); - if (r >= 0 && !isempty(opts)) - r = exec_command_append(m->control_command, "-o", opts, NULL); - } else - r = -ENOENT; - - if (r < 0) - goto fail; - - mount_unwatch_control_pid(m); - - r = mount_spawn(m, m->control_command, &m->control_pid); - if (r < 0) - goto fail; - - mount_set_state(m, MOUNT_MOUNTING); - - return; - -fail: - log_unit_warning_errno(UNIT(m), r, "Failed to run 'mount' task: %m"); - mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); -} - -static void mount_enter_remounting(Mount *m) { - int r; - - assert(m); - - m->control_command_id = MOUNT_EXEC_REMOUNT; - m->control_command = m->exec_command + MOUNT_EXEC_REMOUNT; - - if (m->from_fragment) { - const char *o; - - if (m->parameters_fragment.options) - o = strjoina("remount,", m->parameters_fragment.options); - else - o = "remount"; - - r = exec_command_set(m->control_command, MOUNT_PATH, - m->parameters_fragment.what, m->where, - "-o", o, NULL); - if (r >= 0 && m->sloppy_options) - r = exec_command_append(m->control_command, "-s", NULL); - if (r >= 0 && m->parameters_fragment.fstype) - r = exec_command_append(m->control_command, "-t", m->parameters_fragment.fstype, NULL); - } else - r = -ENOENT; - - if (r < 0) - goto fail; - - mount_unwatch_control_pid(m); - - r = mount_spawn(m, m->control_command, &m->control_pid); - if (r < 0) - goto fail; - - mount_set_state(m, MOUNT_REMOUNTING); - - return; - -fail: - log_unit_warning_errno(UNIT(m), r, "Failed to run 'remount' task: %m"); - m->reload_result = MOUNT_FAILURE_RESOURCES; - mount_enter_mounted(m, MOUNT_SUCCESS); -} - -static int mount_start(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - /* We cannot fulfill this request right now, try again later - * please! */ - if (m->state == MOUNT_UNMOUNTING || - m->state == MOUNT_UNMOUNTING_SIGTERM || - m->state == MOUNT_UNMOUNTING_SIGKILL || - m->state == MOUNT_MOUNTING_SIGTERM || - m->state == MOUNT_MOUNTING_SIGKILL) - return -EAGAIN; - - /* Already on it! */ - if (m->state == MOUNT_MOUNTING) - return 0; - - assert(m->state == MOUNT_DEAD || m->state == MOUNT_FAILED); - - m->result = MOUNT_SUCCESS; - m->reload_result = MOUNT_SUCCESS; - m->reset_cpu_usage = true; - - mount_enter_mounting(m); - return 1; -} - -static int mount_stop(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - /* Already on it */ - if (m->state == MOUNT_UNMOUNTING || - m->state == MOUNT_UNMOUNTING_SIGKILL || - m->state == MOUNT_UNMOUNTING_SIGTERM || - m->state == MOUNT_MOUNTING_SIGTERM || - m->state == MOUNT_MOUNTING_SIGKILL) - return 0; - - assert(m->state == MOUNT_MOUNTING || - m->state == MOUNT_MOUNTING_DONE || - m->state == MOUNT_MOUNTED || - m->state == MOUNT_REMOUNTING || - m->state == MOUNT_REMOUNTING_SIGTERM || - m->state == MOUNT_REMOUNTING_SIGKILL); - - mount_enter_unmounting(m); - return 1; -} - -static int mount_reload(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - if (m->state == MOUNT_MOUNTING_DONE) - return -EAGAIN; - - assert(m->state == MOUNT_MOUNTED); - - mount_enter_remounting(m); - return 1; -} - -static int mount_serialize(Unit *u, FILE *f, FDSet *fds) { - Mount *m = MOUNT(u); - - assert(m); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", mount_state_to_string(m->state)); - unit_serialize_item(u, f, "result", mount_result_to_string(m->result)); - unit_serialize_item(u, f, "reload-result", mount_result_to_string(m->reload_result)); - - if (m->control_pid > 0) - unit_serialize_item_format(u, f, "control-pid", PID_FMT, m->control_pid); - - if (m->control_command_id >= 0) - unit_serialize_item(u, f, "control-command", mount_exec_command_to_string(m->control_command_id)); - - return 0; -} - -static int mount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Mount *m = MOUNT(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - MountState state; - - if ((state = mount_state_from_string(value)) < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - m->deserialized_state = state; - } else if (streq(key, "result")) { - MountResult f; - - f = mount_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != MOUNT_SUCCESS) - m->result = f; - - } else if (streq(key, "reload-result")) { - MountResult f; - - f = mount_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse reload result value: %s", value); - else if (f != MOUNT_SUCCESS) - m->reload_result = f; - - } else if (streq(key, "control-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse control-pid value: %s", value); - else - m->control_pid = pid; - } else if (streq(key, "control-command")) { - MountExecCommand id; - - id = mount_exec_command_from_string(value); - if (id < 0) - log_unit_debug(u, "Failed to parse exec-command value: %s", value); - else { - m->control_command_id = id; - m->control_command = m->exec_command + id; - } - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -_pure_ static UnitActiveState mount_active_state(Unit *u) { - assert(u); - - return state_translation_table[MOUNT(u)->state]; -} - -_pure_ static const char *mount_sub_state_to_string(Unit *u) { - assert(u); - - return mount_state_to_string(MOUNT(u)->state); -} - -_pure_ static bool mount_check_gc(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - return m->from_proc_self_mountinfo; -} - -static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { - Mount *m = MOUNT(u); - MountResult f; - - assert(m); - assert(pid >= 0); - - if (pid != m->control_pid) - return; - - m->control_pid = 0; - - if (is_clean_exit(code, status, NULL)) - f = MOUNT_SUCCESS; - else if (code == CLD_EXITED) - f = MOUNT_FAILURE_EXIT_CODE; - else if (code == CLD_KILLED) - f = MOUNT_FAILURE_SIGNAL; - else if (code == CLD_DUMPED) - f = MOUNT_FAILURE_CORE_DUMP; - else - assert_not_reached("Unknown code"); - - if (f != MOUNT_SUCCESS) - m->result = f; - - if (m->control_command) { - exec_status_exit(&m->control_command->exec_status, &m->exec_context, pid, code, status); - - m->control_command = NULL; - m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; - } - - log_unit_full(u, f == MOUNT_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, - "Mount process exited, code=%s status=%i", sigchld_code_to_string(code), status); - - /* Note that mount(8) returning and the kernel sending us a - * mount table change event might happen out-of-order. If an - * operation succeed we assume the kernel will follow soon too - * and already change into the resulting state. If it fails - * we check if the kernel still knows about the mount. and - * change state accordingly. */ - - switch (m->state) { - - case MOUNT_MOUNTING: - case MOUNT_MOUNTING_DONE: - case MOUNT_MOUNTING_SIGKILL: - case MOUNT_MOUNTING_SIGTERM: - - if (f == MOUNT_SUCCESS) - mount_enter_mounted(m, f); - else if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, f); - else - mount_enter_dead(m, f); - break; - - case MOUNT_REMOUNTING: - case MOUNT_REMOUNTING_SIGKILL: - case MOUNT_REMOUNTING_SIGTERM: - - m->reload_result = f; - if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, MOUNT_SUCCESS); - else - mount_enter_dead(m, MOUNT_SUCCESS); - - break; - - case MOUNT_UNMOUNTING: - case MOUNT_UNMOUNTING_SIGKILL: - case MOUNT_UNMOUNTING_SIGTERM: - - if (f == MOUNT_SUCCESS) { - - if (m->from_proc_self_mountinfo) { - - /* Still a mount point? If so, let's - * try again. Most likely there were - * multiple mount points stacked on - * top of each other. Note that due to - * the io event priority logic we can - * be sure the new mountinfo is loaded - * before we process the SIGCHLD for - * the mount command. */ - - if (m->n_retry_umount < RETRY_UMOUNT_MAX) { - log_unit_debug(u, "Mount still present, trying again."); - m->n_retry_umount++; - mount_enter_unmounting(m); - } else { - log_unit_debug(u, "Mount still present after %u attempts to unmount, giving up.", m->n_retry_umount); - mount_enter_mounted(m, f); - } - } else - mount_enter_dead(m, f); - - } else if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, f); - else - mount_enter_dead(m, f); - break; - - default: - assert_not_reached("Uh, control process died at wrong time."); - } - - /* Notify clients about changed exit status */ - unit_add_to_dbus_queue(u); -} - -static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Mount *m = MOUNT(userdata); - - assert(m); - assert(m->timer_event_source == source); - - switch (m->state) { - - case MOUNT_MOUNTING: - case MOUNT_MOUNTING_DONE: - log_unit_warning(UNIT(m), "Mounting timed out. Stopping."); - mount_enter_signal(m, MOUNT_MOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); - break; - - case MOUNT_REMOUNTING: - log_unit_warning(UNIT(m), "Remounting timed out. Stopping."); - m->reload_result = MOUNT_FAILURE_TIMEOUT; - mount_enter_mounted(m, MOUNT_SUCCESS); - break; - - case MOUNT_UNMOUNTING: - log_unit_warning(UNIT(m), "Unmounting timed out. Stopping."); - mount_enter_signal(m, MOUNT_UNMOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); - break; - - case MOUNT_MOUNTING_SIGTERM: - if (m->kill_context.send_sigkill) { - log_unit_warning(UNIT(m), "Mounting timed out. Killing."); - mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(m), "Mounting timed out. Skipping SIGKILL. Ignoring."); - - if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); - else - mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); - } - break; - - case MOUNT_REMOUNTING_SIGTERM: - if (m->kill_context.send_sigkill) { - log_unit_warning(UNIT(m), "Remounting timed out. Killing."); - mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(m), "Remounting timed out. Skipping SIGKILL. Ignoring."); - - if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); - else - mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); - } - break; - - case MOUNT_UNMOUNTING_SIGTERM: - if (m->kill_context.send_sigkill) { - log_unit_warning(UNIT(m), "Unmounting timed out. Killing."); - mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(m), "Unmounting timed out. Skipping SIGKILL. Ignoring."); - - if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); - else - mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); - } - break; - - case MOUNT_MOUNTING_SIGKILL: - case MOUNT_REMOUNTING_SIGKILL: - case MOUNT_UNMOUNTING_SIGKILL: - log_unit_warning(UNIT(m),"Mount process still around after SIGKILL. Ignoring."); - - if (m->from_proc_self_mountinfo) - mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); - else - mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } - - return 0; -} - -static int mount_setup_unit( - Manager *m, - const char *what, - const char *where, - const char *options, - const char *fstype, - bool set_flags) { - - _cleanup_free_ char *e = NULL, *w = NULL, *o = NULL, *f = NULL; - bool load_extras = false; - MountParameters *p; - bool delete, changed = false; - Unit *u; - int r; - - assert(m); - assert(what); - assert(where); - assert(options); - assert(fstype); - - /* Ignore API mount points. They should never be referenced in - * dependencies ever. */ - if (mount_point_is_api(where) || mount_point_ignore(where)) - return 0; - - if (streq(fstype, "autofs")) - return 0; - - /* probably some kind of swap, ignore */ - if (!is_path(where)) - return 0; - - r = unit_name_from_path(where, ".mount", &e); - if (r < 0) - return r; - - u = manager_get_unit(m, e); - if (!u) { - delete = true; - - u = unit_new(m, sizeof(Mount)); - if (!u) - return log_oom(); - - r = unit_add_name(u, e); - if (r < 0) - goto fail; - - MOUNT(u)->where = strdup(where); - if (!MOUNT(u)->where) { - r = -ENOMEM; - goto fail; - } - - u->source_path = strdup("/proc/self/mountinfo"); - if (!u->source_path) { - r = -ENOMEM; - goto fail; - } - - if (m->running_as == MANAGER_SYSTEM) { - const char* target; - - target = mount_needs_network(options, fstype) ? SPECIAL_REMOTE_FS_TARGET : SPECIAL_LOCAL_FS_TARGET; - r = unit_add_dependency_by_name(u, UNIT_BEFORE, target, NULL, true); - if (r < 0) - goto fail; - - if (should_umount(MOUNT(u))) { - r = unit_add_dependency_by_name(u, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); - if (r < 0) - goto fail; - } - } - - unit_add_to_load_queue(u); - changed = true; - } else { - delete = false; - - if (!MOUNT(u)->where) { - MOUNT(u)->where = strdup(where); - if (!MOUNT(u)->where) { - r = -ENOMEM; - goto fail; - } - } - - if (m->running_as == MANAGER_SYSTEM && - mount_needs_network(options, fstype)) { - /* _netdev option may have shown up late, or on a - * remount. Add remote-fs dependencies, even though - * local-fs ones may already be there. */ - unit_add_dependency_by_name(u, UNIT_BEFORE, SPECIAL_REMOTE_FS_TARGET, NULL, true); - load_extras = true; - } - - if (u->load_state == UNIT_NOT_FOUND) { - u->load_state = UNIT_LOADED; - u->load_error = 0; - - /* Load in the extras later on, after we - * finished initialization of the unit */ - load_extras = true; - changed = true; - } - } - - w = strdup(what); - o = strdup(options); - f = strdup(fstype); - if (!w || !o || !f) { - r = -ENOMEM; - goto fail; - } - - p = &MOUNT(u)->parameters_proc_self_mountinfo; - - changed = changed || - !streq_ptr(p->options, options) || - !streq_ptr(p->what, what) || - !streq_ptr(p->fstype, fstype); - - if (set_flags) { - MOUNT(u)->is_mounted = true; - MOUNT(u)->just_mounted = !MOUNT(u)->from_proc_self_mountinfo; - MOUNT(u)->just_changed = changed; - } - - MOUNT(u)->from_proc_self_mountinfo = true; - - free(p->what); - p->what = w; - w = NULL; - - free(p->options); - p->options = o; - o = NULL; - - free(p->fstype); - p->fstype = f; - f = NULL; - - if (load_extras) { - r = mount_add_extras(MOUNT(u)); - if (r < 0) - goto fail; - } - - if (changed) - unit_add_to_dbus_queue(u); - - return 0; - -fail: - log_warning_errno(r, "Failed to set up mount unit: %m"); - - if (delete && u) - unit_free(u); - - return r; -} - -static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) { - _cleanup_(mnt_free_tablep) struct libmnt_table *t = NULL; - _cleanup_(mnt_free_iterp) struct libmnt_iter *i = NULL; - int r = 0; - - assert(m); - - t = mnt_new_table(); - if (!t) - return log_oom(); - - i = mnt_new_iter(MNT_ITER_FORWARD); - if (!i) - return log_oom(); - - r = mnt_table_parse_mtab(t, NULL); - if (r < 0) - return log_error_errno(r, "Failed to parse /proc/self/mountinfo: %m"); - - r = 0; - for (;;) { - const char *device, *path, *options, *fstype; - _cleanup_free_ char *d = NULL, *p = NULL; - struct libmnt_fs *fs; - int k; - - k = mnt_table_next_fs(t, i, &fs); - if (k == 1) - break; - if (k < 0) - return log_error_errno(k, "Failed to get next entry from /proc/self/mountinfo: %m"); - - device = mnt_fs_get_source(fs); - path = mnt_fs_get_target(fs); - options = mnt_fs_get_options(fs); - fstype = mnt_fs_get_fstype(fs); - - if (!device || !path) - continue; - - if (cunescape(device, UNESCAPE_RELAX, &d) < 0) - return log_oom(); - - if (cunescape(path, UNESCAPE_RELAX, &p) < 0) - return log_oom(); - - (void) device_found_node(m, d, true, DEVICE_FOUND_MOUNT, set_flags); - - k = mount_setup_unit(m, d, p, options, fstype, set_flags); - if (r == 0 && k < 0) - r = k; - } - - return r; -} - -static void mount_shutdown(Manager *m) { - - assert(m); - - m->mount_event_source = sd_event_source_unref(m->mount_event_source); - - mnt_unref_monitor(m->mount_monitor); - m->mount_monitor = NULL; -} - -static int mount_get_timeout(Unit *u, usec_t *timeout) { - Mount *m = MOUNT(u); - usec_t t; - int r; - - if (!m->timer_event_source) - return 0; - - r = sd_event_source_get_time(m->timer_event_source, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) - return 0; - - *timeout = t; - return 1; -} - -static void mount_enumerate(Manager *m) { - int r; - - assert(m); - - mnt_init_debug(0); - - if (!m->mount_monitor) { - int fd; - - m->mount_monitor = mnt_new_monitor(); - if (!m->mount_monitor) { - log_oom(); - goto fail; - } - - r = mnt_monitor_enable_kernel(m->mount_monitor, 1); - if (r < 0) { - log_error_errno(r, "Failed to enable watching of kernel mount events: %m"); - goto fail; - } - - r = mnt_monitor_enable_userspace(m->mount_monitor, 1, NULL); - if (r < 0) { - log_error_errno(r, "Failed to enable watching of userspace mount events: %m"); - goto fail; - } - - /* mnt_unref_monitor() will close the fd */ - fd = r = mnt_monitor_get_fd(m->mount_monitor); - if (r < 0) { - log_error_errno(r, "Failed to acquire watch file descriptor: %m"); - goto fail; - } - - r = sd_event_add_io(m->event, &m->mount_event_source, fd, EPOLLIN, mount_dispatch_io, m); - if (r < 0) { - log_error_errno(r, "Failed to watch mount file descriptor: %m"); - goto fail; - } - - r = sd_event_source_set_priority(m->mount_event_source, -10); - if (r < 0) { - log_error_errno(r, "Failed to adjust mount watch priority: %m"); - goto fail; - } - - (void) sd_event_source_set_description(m->mount_event_source, "mount-monitor-dispatch"); - } - - r = mount_load_proc_self_mountinfo(m, false); - if (r < 0) - goto fail; - - return; - -fail: - mount_shutdown(m); -} - -static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - _cleanup_set_free_ Set *around = NULL, *gone = NULL; - Manager *m = userdata; - const char *what; - Iterator i; - Unit *u; - int r; - - assert(m); - assert(revents & EPOLLIN); - - if (fd == mnt_monitor_get_fd(m->mount_monitor)) { - bool rescan = false; - - /* Drain all events and verify that the event is valid. - * - * Note that libmount also monitors /run/mount mkdir if the - * directory does not exist yet. The mkdir may generate event - * which is irrelevant for us. - * - * error: r < 0; valid: r == 0, false positive: rc == 1 */ - do { - r = mnt_monitor_next_change(m->mount_monitor, NULL, NULL); - if (r == 0) - rescan = true; - else if (r < 0) - return log_error_errno(r, "Failed to drain libmount events"); - } while (r == 0); - - log_debug("libmount event [rescan: %s]", yes_no(rescan)); - if (!rescan) - return 0; - } - - r = mount_load_proc_self_mountinfo(m, true); - if (r < 0) { - /* Reset flags, just in case, for later calls */ - LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_MOUNT]) { - Mount *mount = MOUNT(u); - - mount->is_mounted = mount->just_mounted = mount->just_changed = false; - } - - return 0; - } - - manager_dispatch_load_queue(m); - - LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_MOUNT]) { - Mount *mount = MOUNT(u); - - if (!mount->is_mounted) { - - /* A mount point is not around right now. It - * might be gone, or might never have - * existed. */ - - if (mount->from_proc_self_mountinfo && - mount->parameters_proc_self_mountinfo.what) { - - /* Remember that this device might just have disappeared */ - if (set_ensure_allocated(&gone, &string_hash_ops) < 0 || - set_put(gone, mount->parameters_proc_self_mountinfo.what) < 0) - log_oom(); /* we don't care too much about OOM here... */ - } - - mount->from_proc_self_mountinfo = false; - - switch (mount->state) { - - case MOUNT_MOUNTED: - /* This has just been unmounted by - * somebody else, follow the state - * change. */ - mount_enter_dead(mount, MOUNT_SUCCESS); - break; - - default: - break; - } - - } else if (mount->just_mounted || mount->just_changed) { - - /* A mount point was added or changed */ - - switch (mount->state) { - - case MOUNT_DEAD: - case MOUNT_FAILED: - /* This has just been mounted by - * somebody else, follow the state - * change. */ - mount_enter_mounted(mount, MOUNT_SUCCESS); - break; - - case MOUNT_MOUNTING: - mount_set_state(mount, MOUNT_MOUNTING_DONE); - break; - - default: - /* Nothing really changed, but let's - * issue an notification call - * nonetheless, in case somebody is - * waiting for this. (e.g. file system - * ro/rw remounts.) */ - mount_set_state(mount, mount->state); - break; - } - } - - if (mount->is_mounted && - mount->from_proc_self_mountinfo && - mount->parameters_proc_self_mountinfo.what) { - - if (set_ensure_allocated(&around, &string_hash_ops) < 0 || - set_put(around, mount->parameters_proc_self_mountinfo.what) < 0) - log_oom(); - } - - /* Reset the flags for later calls */ - mount->is_mounted = mount->just_mounted = mount->just_changed = false; - } - - SET_FOREACH(what, gone, i) { - if (set_contains(around, what)) - continue; - - /* Let the device units know that the device is no longer mounted */ - (void) device_found_node(m, what, false, DEVICE_FOUND_MOUNT, true); - } - - return 0; -} - -static void mount_reset_failed(Unit *u) { - Mount *m = MOUNT(u); - - assert(m); - - if (m->state == MOUNT_FAILED) - mount_set_state(m, MOUNT_DEAD); - - m->result = MOUNT_SUCCESS; - m->reload_result = MOUNT_SUCCESS; -} - -static int mount_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - return unit_kill_common(u, who, signo, -1, MOUNT(u)->control_pid, error); -} - -static const char* const mount_exec_command_table[_MOUNT_EXEC_COMMAND_MAX] = { - [MOUNT_EXEC_MOUNT] = "ExecMount", - [MOUNT_EXEC_UNMOUNT] = "ExecUnmount", - [MOUNT_EXEC_REMOUNT] = "ExecRemount", -}; - -DEFINE_STRING_TABLE_LOOKUP(mount_exec_command, MountExecCommand); - -static const char* const mount_result_table[_MOUNT_RESULT_MAX] = { - [MOUNT_SUCCESS] = "success", - [MOUNT_FAILURE_RESOURCES] = "resources", - [MOUNT_FAILURE_TIMEOUT] = "timeout", - [MOUNT_FAILURE_EXIT_CODE] = "exit-code", - [MOUNT_FAILURE_SIGNAL] = "signal", - [MOUNT_FAILURE_CORE_DUMP] = "core-dump" -}; - -DEFINE_STRING_TABLE_LOOKUP(mount_result, MountResult); - -const UnitVTable mount_vtable = { - .object_size = sizeof(Mount), - .exec_context_offset = offsetof(Mount, exec_context), - .cgroup_context_offset = offsetof(Mount, cgroup_context), - .kill_context_offset = offsetof(Mount, kill_context), - .exec_runtime_offset = offsetof(Mount, exec_runtime), - - .sections = - "Unit\0" - "Mount\0" - "Install\0", - .private_section = "Mount", - - .no_alias = true, - .no_instances = true, - - .init = mount_init, - .load = mount_load, - .done = mount_done, - - .coldplug = mount_coldplug, - - .dump = mount_dump, - - .start = mount_start, - .stop = mount_stop, - .reload = mount_reload, - - .kill = mount_kill, - - .serialize = mount_serialize, - .deserialize_item = mount_deserialize_item, - - .active_state = mount_active_state, - .sub_state_to_string = mount_sub_state_to_string, - - .check_gc = mount_check_gc, - - .sigchld_event = mount_sigchld_event, - - .reset_failed = mount_reset_failed, - - .bus_vtable = bus_mount_vtable, - .bus_set_property = bus_mount_set_property, - .bus_commit_properties = bus_mount_commit_properties, - - .get_timeout = mount_get_timeout, - - .can_transient = true, - - .enumerate = mount_enumerate, - .shutdown = mount_shutdown, - - .status_message_formats = { - .starting_stopping = { - [0] = "Mounting %s...", - [1] = "Unmounting %s...", - }, - .finished_start_job = { - [JOB_DONE] = "Mounted %s.", - [JOB_FAILED] = "Failed to mount %s.", - [JOB_TIMEOUT] = "Timed out mounting %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Unmounted %s.", - [JOB_FAILED] = "Failed unmounting %s.", - [JOB_TIMEOUT] = "Timed out unmounting %s.", - }, - }, -}; diff --git a/src/core/mount.h b/src/core/mount.h deleted file mode 100644 index 3b343c6b1f..0000000000 --- a/src/core/mount.h +++ /dev/null @@ -1,107 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Mount Mount; - -#include "execute.h" -#include "kill.h" - -typedef enum MountExecCommand { - MOUNT_EXEC_MOUNT, - MOUNT_EXEC_UNMOUNT, - MOUNT_EXEC_REMOUNT, - _MOUNT_EXEC_COMMAND_MAX, - _MOUNT_EXEC_COMMAND_INVALID = -1 -} MountExecCommand; - -typedef enum MountResult { - MOUNT_SUCCESS, - MOUNT_FAILURE_RESOURCES, - MOUNT_FAILURE_TIMEOUT, - MOUNT_FAILURE_EXIT_CODE, - MOUNT_FAILURE_SIGNAL, - MOUNT_FAILURE_CORE_DUMP, - _MOUNT_RESULT_MAX, - _MOUNT_RESULT_INVALID = -1 -} MountResult; - -typedef struct MountParameters { - char *what; - char *options; - char *fstype; -} MountParameters; - -struct Mount { - Unit meta; - - char *where; - - MountParameters parameters_proc_self_mountinfo; - MountParameters parameters_fragment; - - bool from_proc_self_mountinfo:1; - bool from_fragment:1; - - /* Used while looking for mount points that vanished or got - * added from/to /proc/self/mountinfo */ - bool is_mounted:1; - bool just_mounted:1; - bool just_changed:1; - - bool reset_cpu_usage:1; - - bool sloppy_options; - - MountResult result; - MountResult reload_result; - - mode_t directory_mode; - - usec_t timeout_usec; - - ExecCommand exec_command[_MOUNT_EXEC_COMMAND_MAX]; - - ExecContext exec_context; - KillContext kill_context; - CGroupContext cgroup_context; - - ExecRuntime *exec_runtime; - - MountState state, deserialized_state; - - ExecCommand* control_command; - MountExecCommand control_command_id; - pid_t control_pid; - - sd_event_source *timer_event_source; - - unsigned n_retry_umount; -}; - -extern const UnitVTable mount_vtable; - -void mount_fd_event(Manager *m, int events); - -const char* mount_exec_command_to_string(MountExecCommand i) _const_; -MountExecCommand mount_exec_command_from_string(const char *s) _pure_; - -const char* mount_result_to_string(MountResult i) _const_; -MountResult mount_result_from_string(const char *s) _pure_; diff --git a/src/core/namespace.c b/src/core/namespace.c deleted file mode 100644 index 4fa381db5b..0000000000 --- a/src/core/namespace.c +++ /dev/null @@ -1,727 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "dev-setup.h" -#include "fd-util.h" -#include "loopback-setup.h" -#include "missing.h" -#include "mkdir.h" -#include "mount-util.h" -#include "namespace.h" -#include "path-util.h" -#include "selinux-util.h" -#include "socket-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "umask-util.h" -#include "user-util.h" -#include "util.h" - -typedef enum MountMode { - /* This is ordered by priority! */ - INACCESSIBLE, - READONLY, - PRIVATE_TMP, - PRIVATE_VAR_TMP, - PRIVATE_DEV, - PRIVATE_BUS_ENDPOINT, - READWRITE -} MountMode; - -typedef struct BindMount { - const char *path; - MountMode mode; - bool done; - bool ignore; -} BindMount; - -static int append_mounts(BindMount **p, char **strv, MountMode mode) { - char **i; - - assert(p); - - STRV_FOREACH(i, strv) { - - (*p)->ignore = false; - (*p)->done = false; - - if ((mode == INACCESSIBLE || mode == READONLY || mode == READWRITE) && (*i)[0] == '-') { - (*p)->ignore = true; - (*i)++; - } - - if (!path_is_absolute(*i)) - return -EINVAL; - - (*p)->path = *i; - (*p)->mode = mode; - (*p)++; - } - - return 0; -} - -static int mount_path_compare(const void *a, const void *b) { - const BindMount *p = a, *q = b; - int d; - - d = path_compare(p->path, q->path); - - if (d == 0) { - /* If the paths are equal, check the mode */ - if (p->mode < q->mode) - return -1; - - if (p->mode > q->mode) - return 1; - - return 0; - } - - /* If the paths are not equal, then order prefixes first */ - return d; -} - -static void drop_duplicates(BindMount *m, unsigned *n) { - BindMount *f, *t, *previous; - - assert(m); - assert(n); - - for (f = m, t = m, previous = NULL; f < m+*n; f++) { - - /* The first one wins */ - if (previous && path_equal(f->path, previous->path)) - continue; - - *t = *f; - - previous = t; - - t++; - } - - *n = t - m; -} - -static int mount_dev(BindMount *m) { - static const char devnodes[] = - "/dev/null\0" - "/dev/zero\0" - "/dev/full\0" - "/dev/random\0" - "/dev/urandom\0" - "/dev/tty\0"; - - char temporary_mount[] = "/tmp/namespace-dev-XXXXXX"; - const char *d, *dev = NULL, *devpts = NULL, *devshm = NULL, *devhugepages = NULL, *devmqueue = NULL, *devlog = NULL, *devptmx = NULL; - _cleanup_umask_ mode_t u; - int r; - - assert(m); - - u = umask(0000); - - if (!mkdtemp(temporary_mount)) - return -errno; - - dev = strjoina(temporary_mount, "/dev"); - (void) mkdir(dev, 0755); - if (mount("tmpfs", dev, "tmpfs", MS_NOSUID|MS_STRICTATIME, "mode=755") < 0) { - r = -errno; - goto fail; - } - - devpts = strjoina(temporary_mount, "/dev/pts"); - (void) mkdir(devpts, 0755); - if (mount("/dev/pts", devpts, NULL, MS_BIND, NULL) < 0) { - r = -errno; - goto fail; - } - - devptmx = strjoina(temporary_mount, "/dev/ptmx"); - if (symlink("pts/ptmx", devptmx) < 0) { - r = -errno; - goto fail; - } - - devshm = strjoina(temporary_mount, "/dev/shm"); - (void) mkdir(devshm, 01777); - r = mount("/dev/shm", devshm, NULL, MS_BIND, NULL); - if (r < 0) { - r = -errno; - goto fail; - } - - devmqueue = strjoina(temporary_mount, "/dev/mqueue"); - (void) mkdir(devmqueue, 0755); - (void) mount("/dev/mqueue", devmqueue, NULL, MS_BIND, NULL); - - devhugepages = strjoina(temporary_mount, "/dev/hugepages"); - (void) mkdir(devhugepages, 0755); - (void) mount("/dev/hugepages", devhugepages, NULL, MS_BIND, NULL); - - devlog = strjoina(temporary_mount, "/dev/log"); - (void) symlink("/run/systemd/journal/dev-log", devlog); - - NULSTR_FOREACH(d, devnodes) { - _cleanup_free_ char *dn = NULL; - struct stat st; - - r = stat(d, &st); - if (r < 0) { - - if (errno == ENOENT) - continue; - - r = -errno; - goto fail; - } - - if (!S_ISBLK(st.st_mode) && - !S_ISCHR(st.st_mode)) { - r = -EINVAL; - goto fail; - } - - if (st.st_rdev == 0) - continue; - - dn = strappend(temporary_mount, d); - if (!dn) { - r = -ENOMEM; - goto fail; - } - - mac_selinux_create_file_prepare(d, st.st_mode); - r = mknod(dn, st.st_mode, st.st_rdev); - mac_selinux_create_file_clear(); - - if (r < 0) { - r = -errno; - goto fail; - } - } - - dev_setup(temporary_mount, UID_INVALID, GID_INVALID); - - /* Create the /dev directory if missing. It is more likely to be - * missing when the service is started with RootDirectory. This is - * consistent with mount units creating the mount points when missing. - */ - (void) mkdir_p_label(m->path, 0755); - - if (mount(dev, m->path, NULL, MS_MOVE, NULL) < 0) { - r = -errno; - goto fail; - } - - rmdir(dev); - rmdir(temporary_mount); - - return 0; - -fail: - if (devpts) - umount(devpts); - - if (devshm) - umount(devshm); - - if (devhugepages) - umount(devhugepages); - - if (devmqueue) - umount(devmqueue); - - umount(dev); - rmdir(dev); - rmdir(temporary_mount); - - return r; -} - -static int mount_kdbus(BindMount *m) { - - char temporary_mount[] = "/tmp/kdbus-dev-XXXXXX"; - _cleanup_free_ char *basepath = NULL; - _cleanup_umask_ mode_t u; - char *busnode = NULL, *root; - struct stat st; - int r; - - assert(m); - - u = umask(0000); - - if (!mkdtemp(temporary_mount)) - return log_error_errno(errno, "Failed create temp dir: %m"); - - root = strjoina(temporary_mount, "/kdbus"); - (void) mkdir(root, 0755); - if (mount("tmpfs", root, "tmpfs", MS_NOSUID|MS_STRICTATIME, "mode=777") < 0) { - r = -errno; - goto fail; - } - - /* create a new /dev/null dev node copy so we have some fodder to - * bind-mount the custom endpoint over. */ - if (stat("/dev/null", &st) < 0) { - r = log_error_errno(errno, "Failed to stat /dev/null: %m"); - goto fail; - } - - busnode = strjoina(root, "/bus"); - if (mknod(busnode, (st.st_mode & ~07777) | 0600, st.st_rdev) < 0) { - r = log_error_errno(errno, "mknod() for %s failed: %m", - busnode); - goto fail; - } - - r = mount(m->path, busnode, NULL, MS_BIND, NULL); - if (r < 0) { - r = log_error_errno(errno, "bind mount of %s failed: %m", - m->path); - goto fail; - } - - basepath = dirname_malloc(m->path); - if (!basepath) { - r = -ENOMEM; - goto fail; - } - - if (mount(root, basepath, NULL, MS_MOVE, NULL) < 0) { - r = log_error_errno(errno, "bind mount of %s failed: %m", - basepath); - goto fail; - } - - rmdir(temporary_mount); - return 0; - -fail: - if (busnode) { - umount(busnode); - unlink(busnode); - } - - umount(root); - rmdir(root); - rmdir(temporary_mount); - - return r; -} - -static int apply_mount( - BindMount *m, - const char *tmp_dir, - const char *var_tmp_dir) { - - const char *what; - int r; - - assert(m); - - switch (m->mode) { - - case INACCESSIBLE: - - /* First, get rid of everything that is below if there - * is anything... Then, overmount it with an - * inaccessible directory. */ - umount_recursive(m->path, 0); - - what = "/run/systemd/inaccessible"; - break; - - case READONLY: - case READWRITE: - /* Nothing to mount here, we just later toggle the - * MS_RDONLY bit for the mount point */ - return 0; - - case PRIVATE_TMP: - what = tmp_dir; - break; - - case PRIVATE_VAR_TMP: - what = var_tmp_dir; - break; - - case PRIVATE_DEV: - return mount_dev(m); - - case PRIVATE_BUS_ENDPOINT: - return mount_kdbus(m); - - default: - assert_not_reached("Unknown mode"); - } - - assert(what); - - r = mount(what, m->path, NULL, MS_BIND|MS_REC, NULL); - if (r >= 0) - log_debug("Successfully mounted %s to %s", what, m->path); - else if (m->ignore && errno == ENOENT) - return 0; - - return r; -} - -static int make_read_only(BindMount *m) { - int r; - - assert(m); - - if (IN_SET(m->mode, INACCESSIBLE, READONLY)) - r = bind_remount_recursive(m->path, true); - else if (IN_SET(m->mode, READWRITE, PRIVATE_TMP, PRIVATE_VAR_TMP, PRIVATE_DEV)) - r = bind_remount_recursive(m->path, false); - else - r = 0; - - if (m->ignore && r == -ENOENT) - return 0; - - return r; -} - -int setup_namespace( - const char* root_directory, - char** read_write_dirs, - char** read_only_dirs, - char** inaccessible_dirs, - const char* tmp_dir, - const char* var_tmp_dir, - const char* bus_endpoint_path, - bool private_dev, - ProtectHome protect_home, - ProtectSystem protect_system, - unsigned long mount_flags) { - - BindMount *m, *mounts = NULL; - unsigned n; - int r = 0; - - if (mount_flags == 0) - mount_flags = MS_SHARED; - - if (unshare(CLONE_NEWNS) < 0) - return -errno; - - n = !!tmp_dir + !!var_tmp_dir + !!bus_endpoint_path + - strv_length(read_write_dirs) + - strv_length(read_only_dirs) + - strv_length(inaccessible_dirs) + - private_dev + - (protect_home != PROTECT_HOME_NO ? 3 : 0) + - (protect_system != PROTECT_SYSTEM_NO ? 2 : 0) + - (protect_system == PROTECT_SYSTEM_FULL ? 1 : 0); - - if (n > 0) { - m = mounts = (BindMount *) alloca0(n * sizeof(BindMount)); - r = append_mounts(&m, read_write_dirs, READWRITE); - if (r < 0) - return r; - - r = append_mounts(&m, read_only_dirs, READONLY); - if (r < 0) - return r; - - r = append_mounts(&m, inaccessible_dirs, INACCESSIBLE); - if (r < 0) - return r; - - if (tmp_dir) { - m->path = prefix_roota(root_directory, "/tmp"); - m->mode = PRIVATE_TMP; - m++; - } - - if (var_tmp_dir) { - m->path = prefix_roota(root_directory, "/var/tmp"); - m->mode = PRIVATE_VAR_TMP; - m++; - } - - if (private_dev) { - m->path = prefix_roota(root_directory, "/dev"); - m->mode = PRIVATE_DEV; - m++; - } - - if (bus_endpoint_path) { - m->path = prefix_roota(root_directory, bus_endpoint_path); - m->mode = PRIVATE_BUS_ENDPOINT; - m++; - } - - if (protect_home != PROTECT_HOME_NO) { - const char *home_dir, *run_user_dir, *root_dir; - - home_dir = prefix_roota(root_directory, "/home"); - home_dir = strjoina("-", home_dir); - run_user_dir = prefix_roota(root_directory, "/run/user"); - run_user_dir = strjoina("-", run_user_dir); - root_dir = prefix_roota(root_directory, "/root"); - root_dir = strjoina("-", root_dir); - - r = append_mounts(&m, STRV_MAKE(home_dir, run_user_dir, root_dir), - protect_home == PROTECT_HOME_READ_ONLY ? READONLY : INACCESSIBLE); - if (r < 0) - return r; - } - - if (protect_system != PROTECT_SYSTEM_NO) { - const char *usr_dir, *boot_dir, *etc_dir; - - usr_dir = prefix_roota(root_directory, "/usr"); - boot_dir = prefix_roota(root_directory, "/boot"); - boot_dir = strjoina("-", boot_dir); - etc_dir = prefix_roota(root_directory, "/etc"); - - r = append_mounts(&m, protect_system == PROTECT_SYSTEM_FULL - ? STRV_MAKE(usr_dir, boot_dir, etc_dir) - : STRV_MAKE(usr_dir, boot_dir), READONLY); - if (r < 0) - return r; - } - - assert(mounts + n == m); - - qsort(mounts, n, sizeof(BindMount), mount_path_compare); - drop_duplicates(mounts, &n); - } - - if (n > 0 || root_directory) { - /* Remount / as SLAVE so that nothing now mounted in the namespace - shows up in the parent */ - if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) - return -errno; - } - - if (root_directory) { - /* Turn directory into bind mount */ - if (mount(root_directory, root_directory, NULL, MS_BIND|MS_REC, NULL) < 0) - return -errno; - } - - if (n > 0) { - for (m = mounts; m < mounts + n; ++m) { - r = apply_mount(m, tmp_dir, var_tmp_dir); - if (r < 0) - goto fail; - } - - for (m = mounts; m < mounts + n; ++m) { - r = make_read_only(m); - if (r < 0) - goto fail; - } - } - - if (root_directory) { - /* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */ - r = mount_move_root(root_directory); - - /* at this point, we cannot rollback */ - if (r < 0) - return r; - } - - /* Remount / as the desired mode. Not that this will not - * reestablish propagation from our side to the host, since - * what's disconnected is disconnected. */ - if (mount(NULL, "/", NULL, mount_flags | MS_REC, NULL) < 0) - /* at this point, we cannot rollback */ - return -errno; - - return 0; - -fail: - if (n > 0) { - for (m = mounts; m < mounts + n; ++m) - if (m->done) - (void) umount2(m->path, MNT_DETACH); - } - - return r; -} - -static int setup_one_tmp_dir(const char *id, const char *prefix, char **path) { - _cleanup_free_ char *x = NULL; - char bid[SD_ID128_STRING_MAX]; - sd_id128_t boot_id; - int r; - - assert(id); - assert(prefix); - assert(path); - - /* We include the boot id in the directory so that after a - * reboot we can easily identify obsolete directories. */ - - r = sd_id128_get_boot(&boot_id); - if (r < 0) - return r; - - x = strjoin(prefix, "/systemd-private-", sd_id128_to_string(boot_id, bid), "-", id, "-XXXXXX", NULL); - if (!x) - return -ENOMEM; - - RUN_WITH_UMASK(0077) - if (!mkdtemp(x)) - return -errno; - - RUN_WITH_UMASK(0000) { - char *y; - - y = strjoina(x, "/tmp"); - - if (mkdir(y, 0777 | S_ISVTX) < 0) - return -errno; - } - - *path = x; - x = NULL; - - return 0; -} - -int setup_tmp_dirs(const char *id, char **tmp_dir, char **var_tmp_dir) { - char *a, *b; - int r; - - assert(id); - assert(tmp_dir); - assert(var_tmp_dir); - - r = setup_one_tmp_dir(id, "/tmp", &a); - if (r < 0) - return r; - - r = setup_one_tmp_dir(id, "/var/tmp", &b); - if (r < 0) { - char *t; - - t = strjoina(a, "/tmp"); - rmdir(t); - rmdir(a); - - free(a); - return r; - } - - *tmp_dir = a; - *var_tmp_dir = b; - - return 0; -} - -int setup_netns(int netns_storage_socket[2]) { - _cleanup_close_ int netns = -1; - int r, q; - - assert(netns_storage_socket); - assert(netns_storage_socket[0] >= 0); - assert(netns_storage_socket[1] >= 0); - - /* We use the passed socketpair as a storage buffer for our - * namespace reference fd. Whatever process runs this first - * shall create a new namespace, all others should just join - * it. To serialize that we use a file lock on the socket - * pair. - * - * It's a bit crazy, but hey, works great! */ - - if (lockf(netns_storage_socket[0], F_LOCK, 0) < 0) - return -errno; - - netns = receive_one_fd(netns_storage_socket[0], MSG_DONTWAIT); - if (netns == -EAGAIN) { - /* Nothing stored yet, so let's create a new namespace */ - - if (unshare(CLONE_NEWNET) < 0) { - r = -errno; - goto fail; - } - - loopback_setup(); - - netns = open("/proc/self/ns/net", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (netns < 0) { - r = -errno; - goto fail; - } - - r = 1; - - } else if (netns < 0) { - r = netns; - goto fail; - - } else { - /* Yay, found something, so let's join the namespace */ - if (setns(netns, CLONE_NEWNET) < 0) { - r = -errno; - goto fail; - } - - r = 0; - } - - q = send_one_fd(netns_storage_socket[1], netns, MSG_DONTWAIT); - if (q < 0) { - r = q; - goto fail; - } - -fail: - lockf(netns_storage_socket[0], F_ULOCK, 0); - return r; -} - -static const char *const protect_home_table[_PROTECT_HOME_MAX] = { - [PROTECT_HOME_NO] = "no", - [PROTECT_HOME_YES] = "yes", - [PROTECT_HOME_READ_ONLY] = "read-only", -}; - -DEFINE_STRING_TABLE_LOOKUP(protect_home, ProtectHome); - -static const char *const protect_system_table[_PROTECT_SYSTEM_MAX] = { - [PROTECT_SYSTEM_NO] = "no", - [PROTECT_SYSTEM_YES] = "yes", - [PROTECT_SYSTEM_FULL] = "full", -}; - -DEFINE_STRING_TABLE_LOOKUP(protect_system, ProtectSystem); diff --git a/src/core/namespace.h b/src/core/namespace.h deleted file mode 100644 index 40bee74e2c..0000000000 --- a/src/core/namespace.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "macro.h" - -typedef enum ProtectHome { - PROTECT_HOME_NO, - PROTECT_HOME_YES, - PROTECT_HOME_READ_ONLY, - _PROTECT_HOME_MAX, - _PROTECT_HOME_INVALID = -1 -} ProtectHome; - -typedef enum ProtectSystem { - PROTECT_SYSTEM_NO, - PROTECT_SYSTEM_YES, - PROTECT_SYSTEM_FULL, - _PROTECT_SYSTEM_MAX, - _PROTECT_SYSTEM_INVALID = -1 -} ProtectSystem; - -int setup_namespace(const char *chroot, - char **read_write_dirs, - char **read_only_dirs, - char **inaccessible_dirs, - const char *tmp_dir, - const char *var_tmp_dir, - const char *endpoint_path, - bool private_dev, - ProtectHome protect_home, - ProtectSystem protect_system, - unsigned long mount_flags); - -int setup_tmp_dirs(const char *id, - char **tmp_dir, - char **var_tmp_dir); - -int setup_netns(int netns_storage_socket[2]); - -const char* protect_home_to_string(ProtectHome p) _const_; -ProtectHome protect_home_from_string(const char *s) _pure_; - -const char* protect_system_to_string(ProtectSystem p) _const_; -ProtectSystem protect_system_from_string(const char *s) _pure_; diff --git a/src/core/org.freedesktop.systemd1.conf b/src/core/org.freedesktop.systemd1.conf deleted file mode 100644 index 6a7a37ee92..0000000000 --- a/src/core/org.freedesktop.systemd1.conf +++ /dev/null @@ -1,212 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/core/org.freedesktop.systemd1.policy.in.in b/src/core/org.freedesktop.systemd1.policy.in.in deleted file mode 100644 index cc39a9e1c3..0000000000 --- a/src/core/org.freedesktop.systemd1.policy.in.in +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - The systemd Project - http://www.freedesktop.org/wiki/Software/systemd - - - <_description>Send passphrase back to system - <_message>Authentication is required to send the entered passphrase back to the system. - - no - no - auth_admin_keep - - @rootlibexecdir@/systemd-reply-password - - - - <_description>Manage system services or other units - <_message>Authentication is required to manage system services or other units. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Manage system service or unit files - <_message>Authentication is required to manage system service or unit files. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Set or unset system and service manager environment variables - <_message>Authentication is required to set or unset system and service manager environment variables. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Reload the systemd state - <_message>Authentication is required to reload the systemd state. - - auth_admin - auth_admin - auth_admin_keep - - - - diff --git a/src/core/org.freedesktop.systemd1.service b/src/core/org.freedesktop.systemd1.service deleted file mode 100644 index d4df3e93a2..0000000000 --- a/src/core/org.freedesktop.systemd1.service +++ /dev/null @@ -1,11 +0,0 @@ -# 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. - -[D-BUS Service] -Name=org.freedesktop.systemd1 -Exec=/bin/false -User=root diff --git a/src/core/path.c b/src/core/path.c deleted file mode 100644 index 460c1d3bf2..0000000000 --- a/src/core/path.c +++ /dev/null @@ -1,778 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "bus-error.h" -#include "bus-util.h" -#include "dbus-path.h" -#include "fd-util.h" -#include "fs-util.h" -#include "glob-util.h" -#include "macro.h" -#include "mkdir.h" -#include "path.h" -#include "special.h" -#include "stat-util.h" -#include "string-table.h" -#include "string-util.h" -#include "unit-name.h" -#include "unit.h" - -static const UnitActiveState state_translation_table[_PATH_STATE_MAX] = { - [PATH_DEAD] = UNIT_INACTIVE, - [PATH_WAITING] = UNIT_ACTIVE, - [PATH_RUNNING] = UNIT_ACTIVE, - [PATH_FAILED] = UNIT_FAILED -}; - -static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); - -int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler) { - - static const int flags_table[_PATH_TYPE_MAX] = { - [PATH_EXISTS] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB, - [PATH_EXISTS_GLOB] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB, - [PATH_CHANGED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO, - [PATH_MODIFIED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO|IN_MODIFY, - [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE|IN_MOVED_TO - }; - - bool exists = false; - char *slash, *oldslash = NULL; - int r; - - assert(s); - assert(s->unit); - assert(handler); - - path_spec_unwatch(s); - - s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (s->inotify_fd < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(s->unit->manager->event, &s->event_source, s->inotify_fd, EPOLLIN, handler, s); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(s->event_source, "path"); - - /* This assumes the path was passed through path_kill_slashes()! */ - - for (slash = strchr(s->path, '/'); ; slash = strchr(slash+1, '/')) { - char *cut = NULL; - int flags; - char tmp; - - if (slash) { - cut = slash + (slash == s->path); - tmp = *cut; - *cut = '\0'; - - flags = IN_MOVE_SELF | IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO; - } else - flags = flags_table[s->type]; - - r = inotify_add_watch(s->inotify_fd, s->path, flags); - if (r < 0) { - if (errno == EACCES || errno == ENOENT) { - if (cut) - *cut = tmp; - break; - } - - r = log_warning_errno(errno, "Failed to add watch on %s: %s", s->path, errno == ENOSPC ? "too many watches" : strerror(-r)); - if (cut) - *cut = tmp; - goto fail; - } else { - exists = true; - - /* Path exists, we don't need to watch parent - too closely. */ - if (oldslash) { - char *cut2 = oldslash + (oldslash == s->path); - char tmp2 = *cut2; - *cut2 = '\0'; - - inotify_add_watch(s->inotify_fd, s->path, IN_MOVE_SELF); - /* Error is ignored, the worst can happen is - we get spurious events. */ - - *cut2 = tmp2; - } - } - - if (cut) - *cut = tmp; - - if (slash) - oldslash = slash; - else { - /* whole path has been iterated over */ - s->primary_wd = r; - break; - } - } - - if (!exists) { - r = log_error_errno(errno, "Failed to add watch on any of the components of %s: %m", s->path); - /* either EACCESS or ENOENT */ - goto fail; - } - - return 0; - -fail: - path_spec_unwatch(s); - return r; -} - -void path_spec_unwatch(PathSpec *s) { - assert(s); - - s->event_source = sd_event_source_unref(s->event_source); - s->inotify_fd = safe_close(s->inotify_fd); -} - -int path_spec_fd_event(PathSpec *s, uint32_t revents) { - union inotify_event_buffer buffer; - struct inotify_event *e; - ssize_t l; - int r = 0; - - if (revents != EPOLLIN) { - log_error("Got invalid poll event on inotify."); - return -EINVAL; - } - - l = read(s->inotify_fd, &buffer, sizeof(buffer)); - if (l < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return log_error_errno(errno, "Failed to read inotify event: %m"); - } - - FOREACH_INOTIFY_EVENT(e, buffer, l) { - if ((s->type == PATH_CHANGED || s->type == PATH_MODIFIED) && - s->primary_wd == e->wd) - r = 1; - } - - return r; -} - -static bool path_spec_check_good(PathSpec *s, bool initial) { - bool good = false; - - switch (s->type) { - - case PATH_EXISTS: - good = access(s->path, F_OK) >= 0; - break; - - case PATH_EXISTS_GLOB: - good = glob_exists(s->path) > 0; - break; - - case PATH_DIRECTORY_NOT_EMPTY: { - int k; - - k = dir_is_empty(s->path); - good = !(k == -ENOENT || k > 0); - break; - } - - case PATH_CHANGED: - case PATH_MODIFIED: { - bool b; - - b = access(s->path, F_OK) >= 0; - good = !initial && b != s->previous_exists; - s->previous_exists = b; - break; - } - - default: - ; - } - - return good; -} - -static void path_spec_mkdir(PathSpec *s, mode_t mode) { - int r; - - if (s->type == PATH_EXISTS || s->type == PATH_EXISTS_GLOB) - return; - - r = mkdir_p_label(s->path, mode); - if (r < 0) - log_warning_errno(r, "mkdir(%s) failed: %m", s->path); -} - -static void path_spec_dump(PathSpec *s, FILE *f, const char *prefix) { - fprintf(f, - "%s%s: %s\n", - prefix, - path_type_to_string(s->type), - s->path); -} - -void path_spec_done(PathSpec *s) { - assert(s); - assert(s->inotify_fd == -1); - - free(s->path); -} - -static void path_init(Unit *u) { - Path *p = PATH(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - p->directory_mode = 0755; -} - -void path_free_specs(Path *p) { - PathSpec *s; - - assert(p); - - while ((s = p->specs)) { - path_spec_unwatch(s); - LIST_REMOVE(spec, p->specs, s); - path_spec_done(s); - free(s); - } -} - -static void path_done(Unit *u) { - Path *p = PATH(u); - - assert(p); - - path_free_specs(p); -} - -static int path_add_mount_links(Path *p) { - PathSpec *s; - int r; - - assert(p); - - LIST_FOREACH(spec, s, p->specs) { - r = unit_require_mounts_for(UNIT(p), s->path); - if (r < 0) - return r; - } - - return 0; -} - -static int path_verify(Path *p) { - assert(p); - - if (UNIT(p)->load_state != UNIT_LOADED) - return 0; - - if (!p->specs) { - log_unit_error(UNIT(p), "Path unit lacks path setting. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int path_add_default_dependencies(Path *p) { - int r; - - assert(p); - - if (!UNIT(p)->default_dependencies) - return 0; - - r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_PATHS_TARGET, NULL, true); - if (r < 0) - return r; - - if (UNIT(p)->manager->running_as == MANAGER_SYSTEM) { - r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); - if (r < 0) - return r; - } - - return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -static int path_load(Unit *u) { - Path *p = PATH(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - r = unit_load_fragment_and_dropin(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - - if (set_isempty(u->dependencies[UNIT_TRIGGERS])) { - Unit *x; - - r = unit_load_related_unit(u, ".service", &x); - if (r < 0) - return r; - - r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, x, true); - if (r < 0) - return r; - } - - r = path_add_mount_links(p); - if (r < 0) - return r; - - r = path_add_default_dependencies(p); - if (r < 0) - return r; - } - - return path_verify(p); -} - -static void path_dump(Unit *u, FILE *f, const char *prefix) { - Path *p = PATH(u); - Unit *trigger; - PathSpec *s; - - assert(p); - assert(f); - - trigger = UNIT_TRIGGER(u); - - fprintf(f, - "%sPath State: %s\n" - "%sResult: %s\n" - "%sUnit: %s\n" - "%sMakeDirectory: %s\n" - "%sDirectoryMode: %04o\n", - prefix, path_state_to_string(p->state), - prefix, path_result_to_string(p->result), - prefix, trigger ? trigger->id : "n/a", - prefix, yes_no(p->make_directory), - prefix, p->directory_mode); - - LIST_FOREACH(spec, s, p->specs) - path_spec_dump(s, f, prefix); -} - -static void path_unwatch(Path *p) { - PathSpec *s; - - assert(p); - - LIST_FOREACH(spec, s, p->specs) - path_spec_unwatch(s); -} - -static int path_watch(Path *p) { - int r; - PathSpec *s; - - assert(p); - - LIST_FOREACH(spec, s, p->specs) { - r = path_spec_watch(s, path_dispatch_io); - if (r < 0) - return r; - } - - return 0; -} - -static void path_set_state(Path *p, PathState state) { - PathState old_state; - assert(p); - - old_state = p->state; - p->state = state; - - if (state != PATH_WAITING && - (state != PATH_RUNNING || p->inotify_triggered)) - path_unwatch(p); - - if (state != old_state) - log_unit_debug(UNIT(p), "Changed %s -> %s", path_state_to_string(old_state), path_state_to_string(state)); - - unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], true); -} - -static void path_enter_waiting(Path *p, bool initial, bool recheck); - -static int path_coldplug(Unit *u) { - Path *p = PATH(u); - - assert(p); - assert(p->state == PATH_DEAD); - - if (p->deserialized_state != p->state) { - - if (p->deserialized_state == PATH_WAITING || - p->deserialized_state == PATH_RUNNING) - path_enter_waiting(p, true, true); - else - path_set_state(p, p->deserialized_state); - } - - return 0; -} - -static void path_enter_dead(Path *p, PathResult f) { - assert(p); - - if (f != PATH_SUCCESS) - p->result = f; - - path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD); -} - -static void path_enter_running(Path *p) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - Unit *trigger; - int r; - - assert(p); - - /* Don't start job if we are supposed to go down */ - if (unit_stop_pending(UNIT(p))) - return; - - trigger = UNIT_TRIGGER(UNIT(p)); - if (!trigger) { - log_unit_error(UNIT(p), "Unit to trigger vanished."); - path_enter_dead(p, TIMER_FAILURE_RESOURCES); - return; - } - - r = manager_add_job(UNIT(p)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL); - if (r < 0) - goto fail; - - p->inotify_triggered = false; - - r = path_watch(p); - if (r < 0) - goto fail; - - path_set_state(p, PATH_RUNNING); - return; - -fail: - log_unit_warning(UNIT(p), "Failed to queue unit startup job: %s", bus_error_message(&error, r)); - path_enter_dead(p, PATH_FAILURE_RESOURCES); -} - -static bool path_check_good(Path *p, bool initial) { - PathSpec *s; - bool good = false; - - assert(p); - - LIST_FOREACH(spec, s, p->specs) { - good = path_spec_check_good(s, initial); - - if (good) - break; - } - - return good; -} - -static void path_enter_waiting(Path *p, bool initial, bool recheck) { - int r; - - if (recheck) - if (path_check_good(p, initial)) { - log_unit_debug(UNIT(p), "Got triggered."); - path_enter_running(p); - return; - } - - r = path_watch(p); - if (r < 0) - goto fail; - - /* Hmm, so now we have created inotify watches, but the file - * might have appeared/been removed by now, so we must - * recheck */ - - if (recheck) - if (path_check_good(p, false)) { - log_unit_debug(UNIT(p), "Got triggered."); - path_enter_running(p); - return; - } - - path_set_state(p, PATH_WAITING); - return; - -fail: - log_unit_warning_errno(UNIT(p), r, "Failed to enter waiting state: %m"); - path_enter_dead(p, PATH_FAILURE_RESOURCES); -} - -static void path_mkdir(Path *p) { - PathSpec *s; - - assert(p); - - if (!p->make_directory) - return; - - LIST_FOREACH(spec, s, p->specs) - path_spec_mkdir(s, p->directory_mode); -} - -static int path_start(Unit *u) { - Path *p = PATH(u); - Unit *trigger; - - assert(p); - assert(p->state == PATH_DEAD || p->state == PATH_FAILED); - - trigger = UNIT_TRIGGER(u); - if (!trigger || trigger->load_state != UNIT_LOADED) { - log_unit_error(u, "Refusing to start, unit to trigger not loaded."); - return -ENOENT; - } - - path_mkdir(p); - - p->result = PATH_SUCCESS; - path_enter_waiting(p, true, true); - - return 1; -} - -static int path_stop(Unit *u) { - Path *p = PATH(u); - - assert(p); - assert(p->state == PATH_WAITING || p->state == PATH_RUNNING); - - path_enter_dead(p, PATH_SUCCESS); - return 1; -} - -static int path_serialize(Unit *u, FILE *f, FDSet *fds) { - Path *p = PATH(u); - - assert(u); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", path_state_to_string(p->state)); - unit_serialize_item(u, f, "result", path_result_to_string(p->result)); - - return 0; -} - -static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Path *p = PATH(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - PathState state; - - state = path_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - p->deserialized_state = state; - - } else if (streq(key, "result")) { - PathResult f; - - f = path_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != PATH_SUCCESS) - p->result = f; - - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -_pure_ static UnitActiveState path_active_state(Unit *u) { - assert(u); - - return state_translation_table[PATH(u)->state]; -} - -_pure_ static const char *path_sub_state_to_string(Unit *u) { - assert(u); - - return path_state_to_string(PATH(u)->state); -} - -static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - PathSpec *s = userdata; - Path *p; - int changed; - - assert(s); - assert(s->unit); - assert(fd >= 0); - - p = PATH(s->unit); - - if (p->state != PATH_WAITING && - p->state != PATH_RUNNING) - return 0; - - /* log_debug("inotify wakeup on %s.", u->id); */ - - LIST_FOREACH(spec, s, p->specs) - if (path_spec_owns_inotify_fd(s, fd)) - break; - - if (!s) { - log_error("Got event on unknown fd."); - goto fail; - } - - changed = path_spec_fd_event(s, revents); - if (changed < 0) - goto fail; - - /* If we are already running, then remember that one event was - * dispatched so that we restart the service only if something - * actually changed on disk */ - p->inotify_triggered = true; - - if (changed) - path_enter_running(p); - else - path_enter_waiting(p, false, true); - - return 0; - -fail: - path_enter_dead(p, PATH_FAILURE_RESOURCES); - return 0; -} - -static void path_trigger_notify(Unit *u, Unit *other) { - Path *p = PATH(u); - - assert(u); - assert(other); - - /* Invoked whenever the unit we trigger changes state or gains - * or loses a job */ - - if (other->load_state != UNIT_LOADED) - return; - - if (p->state == PATH_RUNNING && - UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) { - log_unit_debug(UNIT(p), "Got notified about unit deactivation."); - - /* Hmm, so inotify was triggered since the - * last activation, so I guess we need to - * recheck what is going on. */ - path_enter_waiting(p, false, p->inotify_triggered); - } -} - -static void path_reset_failed(Unit *u) { - Path *p = PATH(u); - - assert(p); - - if (p->state == PATH_FAILED) - path_set_state(p, PATH_DEAD); - - p->result = PATH_SUCCESS; -} - -static const char* const path_type_table[_PATH_TYPE_MAX] = { - [PATH_EXISTS] = "PathExists", - [PATH_EXISTS_GLOB] = "PathExistsGlob", - [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty", - [PATH_CHANGED] = "PathChanged", - [PATH_MODIFIED] = "PathModified", -}; - -DEFINE_STRING_TABLE_LOOKUP(path_type, PathType); - -static const char* const path_result_table[_PATH_RESULT_MAX] = { - [PATH_SUCCESS] = "success", - [PATH_FAILURE_RESOURCES] = "resources", -}; - -DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult); - -const UnitVTable path_vtable = { - .object_size = sizeof(Path), - - .sections = - "Unit\0" - "Path\0" - "Install\0", - - .init = path_init, - .done = path_done, - .load = path_load, - - .coldplug = path_coldplug, - - .dump = path_dump, - - .start = path_start, - .stop = path_stop, - - .serialize = path_serialize, - .deserialize_item = path_deserialize_item, - - .active_state = path_active_state, - .sub_state_to_string = path_sub_state_to_string, - - .trigger_notify = path_trigger_notify, - - .reset_failed = path_reset_failed, - - .bus_vtable = bus_path_vtable -}; diff --git a/src/core/path.h b/src/core/path.h deleted file mode 100644 index bbbcebd78e..0000000000 --- a/src/core/path.h +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Path Path; -typedef struct PathSpec PathSpec; - -#include "unit.h" - -typedef enum PathType { - PATH_EXISTS, - PATH_EXISTS_GLOB, - PATH_DIRECTORY_NOT_EMPTY, - PATH_CHANGED, - PATH_MODIFIED, - _PATH_TYPE_MAX, - _PATH_TYPE_INVALID = -1 -} PathType; - -typedef struct PathSpec { - Unit *unit; - - char *path; - - sd_event_source *event_source; - - LIST_FIELDS(struct PathSpec, spec); - - PathType type; - int inotify_fd; - int primary_wd; - - bool previous_exists; -} PathSpec; - -int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler); -void path_spec_unwatch(PathSpec *s); -int path_spec_fd_event(PathSpec *s, uint32_t events); -void path_spec_done(PathSpec *s); - -static inline bool path_spec_owns_inotify_fd(PathSpec *s, int fd) { - return s->inotify_fd == fd; -} - -typedef enum PathResult { - PATH_SUCCESS, - PATH_FAILURE_RESOURCES, - _PATH_RESULT_MAX, - _PATH_RESULT_INVALID = -1 -} PathResult; - -struct Path { - Unit meta; - - LIST_HEAD(PathSpec, specs); - - PathState state, deserialized_state; - - bool inotify_triggered; - - bool make_directory; - mode_t directory_mode; - - PathResult result; -}; - -void path_free_specs(Path *p); - -extern const UnitVTable path_vtable; - -const char* path_type_to_string(PathType i) _const_; -PathType path_type_from_string(const char *s) _pure_; - -const char* path_result_to_string(PathResult i) _const_; -PathResult path_result_from_string(const char *s) _pure_; diff --git a/src/core/scope.c b/src/core/scope.c deleted file mode 100644 index c5d0ecef04..0000000000 --- a/src/core/scope.c +++ /dev/null @@ -1,610 +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 . -***/ - -#include -#include - -#include "alloc-util.h" -#include "dbus-scope.h" -#include "load-dropin.h" -#include "log.h" -#include "scope.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit.h" - -static const UnitActiveState state_translation_table[_SCOPE_STATE_MAX] = { - [SCOPE_DEAD] = UNIT_INACTIVE, - [SCOPE_RUNNING] = UNIT_ACTIVE, - [SCOPE_ABANDONED] = UNIT_ACTIVE, - [SCOPE_STOP_SIGTERM] = UNIT_DEACTIVATING, - [SCOPE_STOP_SIGKILL] = UNIT_DEACTIVATING, - [SCOPE_FAILED] = UNIT_FAILED -}; - -static int scope_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); - -static void scope_init(Unit *u) { - Scope *s = SCOPE(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - s->timeout_stop_usec = u->manager->default_timeout_stop_usec; - - UNIT(s)->ignore_on_isolate = true; -} - -static void scope_done(Unit *u) { - Scope *s = SCOPE(u); - - assert(u); - - free(s->controller); - - s->timer_event_source = sd_event_source_unref(s->timer_event_source); -} - -static int scope_arm_timer(Scope *s, usec_t usec) { - int r; - - assert(s); - - if (s->timer_event_source) { - r = sd_event_source_set_time(s->timer_event_source, usec); - if (r < 0) - return r; - - return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); - } - - if (usec == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - UNIT(s)->manager->event, - &s->timer_event_source, - CLOCK_MONOTONIC, - usec, 0, - scope_dispatch_timer, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->timer_event_source, "scope-timer"); - - return 0; -} - -static void scope_set_state(Scope *s, ScopeState state) { - ScopeState old_state; - assert(s); - - old_state = s->state; - s->state = state; - - if (!IN_SET(state, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL)) - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - - if (IN_SET(state, SCOPE_DEAD, SCOPE_FAILED)) - unit_unwatch_all_pids(UNIT(s)); - - if (state != old_state) - log_debug("%s changed %s -> %s", UNIT(s)->id, scope_state_to_string(old_state), scope_state_to_string(state)); - - unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); -} - -static int scope_add_default_dependencies(Scope *s) { - int r; - - assert(s); - - if (!UNIT(s)->default_dependencies) - return 0; - - /* Make sure scopes are unloaded on shutdown */ - r = unit_add_two_dependencies_by_name( - UNIT(s), - UNIT_BEFORE, UNIT_CONFLICTS, - SPECIAL_SHUTDOWN_TARGET, NULL, true); - if (r < 0) - return r; - - return 0; -} - -static int scope_verify(Scope *s) { - assert(s); - - if (UNIT(s)->load_state != UNIT_LOADED) - return 0; - - if (set_isempty(UNIT(s)->pids) && - !manager_is_reloading_or_reexecuting(UNIT(s)->manager) && - !unit_has_name(UNIT(s), SPECIAL_INIT_SCOPE)) { - log_unit_error(UNIT(s), "Scope has no PIDs. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int scope_load(Unit *u) { - Scope *s = SCOPE(u); - int r; - - assert(s); - assert(u->load_state == UNIT_STUB); - - if (!u->transient && !manager_is_reloading_or_reexecuting(u->manager)) - return -ENOENT; - - u->load_state = UNIT_LOADED; - - r = unit_load_dropin(u); - if (r < 0) - return r; - - r = unit_patch_contexts(u); - if (r < 0) - return r; - - r = unit_set_default_slice(u); - if (r < 0) - return r; - - r = scope_add_default_dependencies(s); - if (r < 0) - return r; - - return scope_verify(s); -} - -static int scope_coldplug(Unit *u) { - Scope *s = SCOPE(u); - int r; - - assert(s); - assert(s->state == SCOPE_DEAD); - - if (s->deserialized_state == s->state) - return 0; - - if (IN_SET(s->deserialized_state, SCOPE_STOP_SIGKILL, SCOPE_STOP_SIGTERM)) { - r = scope_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_stop_usec)); - if (r < 0) - return r; - } - - if (!IN_SET(s->deserialized_state, SCOPE_DEAD, SCOPE_FAILED)) - unit_watch_all_pids(UNIT(s)); - - scope_set_state(s, s->deserialized_state); - return 0; -} - -static void scope_dump(Unit *u, FILE *f, const char *prefix) { - Scope *s = SCOPE(u); - - assert(s); - assert(f); - - fprintf(f, - "%sScope State: %s\n" - "%sResult: %s\n", - prefix, scope_state_to_string(s->state), - prefix, scope_result_to_string(s->result)); - - cgroup_context_dump(&s->cgroup_context, f, prefix); - kill_context_dump(&s->kill_context, f, prefix); -} - -static void scope_enter_dead(Scope *s, ScopeResult f) { - assert(s); - - if (f != SCOPE_SUCCESS) - s->result = f; - - scope_set_state(s, s->result != SCOPE_SUCCESS ? SCOPE_FAILED : SCOPE_DEAD); -} - -static void scope_enter_signal(Scope *s, ScopeState state, ScopeResult f) { - bool skip_signal = false; - int r; - - assert(s); - - if (f != SCOPE_SUCCESS) - s->result = f; - - unit_watch_all_pids(UNIT(s)); - - /* 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 */ - if (state == SCOPE_STOP_SIGTERM) - skip_signal = bus_scope_send_request_stop(s) > 0; - - if (!skip_signal) { - r = unit_kill_context( - UNIT(s), - &s->kill_context, - state != SCOPE_STOP_SIGTERM ? KILL_KILL : KILL_TERMINATE, - -1, -1, false); - if (r < 0) - goto fail; - } else - r = 1; - - if (r > 0) { - r = scope_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); - if (r < 0) - goto fail; - - scope_set_state(s, state); - } else if (state == SCOPE_STOP_SIGTERM) - scope_enter_signal(s, SCOPE_STOP_SIGKILL, SCOPE_SUCCESS); - else - scope_enter_dead(s, SCOPE_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); - - scope_enter_dead(s, SCOPE_FAILURE_RESOURCES); -} - -static int scope_start(Unit *u) { - Scope *s = SCOPE(u); - int r; - - assert(s); - - if (unit_has_name(u, SPECIAL_INIT_SCOPE)) - return -EPERM; - - if (s->state == SCOPE_FAILED) - return -EPERM; - - /* We can't fulfill this right now, please try again later */ - if (s->state == SCOPE_STOP_SIGTERM || - s->state == SCOPE_STOP_SIGKILL) - return -EAGAIN; - - assert(s->state == SCOPE_DEAD); - - if (!u->transient && !manager_is_reloading_or_reexecuting(u->manager)) - return -ENOENT; - - (void) unit_realize_cgroup(u); - (void) unit_reset_cpu_usage(u); - - r = unit_attach_pids_to_cgroup(u); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to add PIDs to scope's control group: %m"); - scope_enter_dead(s, SCOPE_FAILURE_RESOURCES); - return r; - } - - s->result = SCOPE_SUCCESS; - - scope_set_state(s, SCOPE_RUNNING); - return 1; -} - -static int scope_stop(Unit *u) { - Scope *s = SCOPE(u); - - assert(s); - - if (s->state == SCOPE_STOP_SIGTERM || - s->state == SCOPE_STOP_SIGKILL) - return 0; - - assert(s->state == SCOPE_RUNNING || - s->state == SCOPE_ABANDONED); - - scope_enter_signal(s, SCOPE_STOP_SIGTERM, SCOPE_SUCCESS); - return 1; -} - -static void scope_reset_failed(Unit *u) { - Scope *s = SCOPE(u); - - assert(s); - - if (s->state == SCOPE_FAILED) - scope_set_state(s, SCOPE_DEAD); - - s->result = SCOPE_SUCCESS; -} - -static int scope_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - return unit_kill_common(u, who, signo, -1, -1, error); -} - -static int scope_get_timeout(Unit *u, usec_t *timeout) { - Scope *s = SCOPE(u); - usec_t t; - int r; - - if (!s->timer_event_source) - return 0; - - r = sd_event_source_get_time(s->timer_event_source, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) - return 0; - - *timeout = t; - return 1; -} - -static int scope_serialize(Unit *u, FILE *f, FDSet *fds) { - Scope *s = SCOPE(u); - - assert(s); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", scope_state_to_string(s->state)); - return 0; -} - -static int scope_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Scope *s = SCOPE(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - ScopeState state; - - state = scope_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - s->deserialized_state = state; - - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -static bool scope_check_gc(Unit *u) { - assert(u); - - /* Never clean up scopes that still have a process around, - * even if the scope is formally dead. */ - - if (!u->cgroup_path) - return false; - - return cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path) <= 0; -} - -static void scope_notify_cgroup_empty_event(Unit *u) { - Scope *s = SCOPE(u); - assert(u); - - log_unit_debug(u, "cgroup is empty"); - - if (IN_SET(s->state, SCOPE_RUNNING, SCOPE_ABANDONED, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL)) - scope_enter_dead(s, SCOPE_SUCCESS); -} - -static void scope_sigchld_event(Unit *u, pid_t pid, int code, int status) { - - /* If we get a SIGCHLD event for one of the processes we were - interested in, then we look for others to watch, under the - assumption that we'll sooner or later get a SIGCHLD for - them, as the original process we watched was probably the - parent of them, and they are hence now our children. */ - - 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)) - scope_notify_cgroup_empty_event(u); -} - -static int scope_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Scope *s = SCOPE(userdata); - - assert(s); - assert(s->timer_event_source == source); - - switch (s->state) { - - case SCOPE_STOP_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "Stopping timed out. Killing."); - scope_enter_signal(s, SCOPE_STOP_SIGKILL, SCOPE_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "Stopping timed out. Skipping SIGKILL."); - scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); - } - - break; - - case SCOPE_STOP_SIGKILL: - log_unit_warning(UNIT(s), "Still around after SIGKILL. Ignoring."); - scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } - - return 0; -} - -int scope_abandon(Scope *s) { - assert(s); - - if (unit_has_name(UNIT(s), SPECIAL_INIT_SCOPE)) - return -EPERM; - - if (!IN_SET(s->state, SCOPE_RUNNING, SCOPE_ABANDONED)) - return -ESTALE; - - s->controller = mfree(s->controller); - - /* The client is no longer watching the remaining processes, - * so let's step in here, under the assumption that the - * remaining processes will be sooner or later reassigned to - * us as parent. */ - - unit_tidy_watch_pids(UNIT(s), 0, 0); - unit_watch_all_pids(UNIT(s)); - - /* If the PID set is empty now, then let's finish this off */ - if (set_isempty(UNIT(s)->pids)) - scope_notify_cgroup_empty_event(UNIT(s)); - else - scope_set_state(s, SCOPE_ABANDONED); - - return 0; -} - -_pure_ static UnitActiveState scope_active_state(Unit *u) { - assert(u); - - return state_translation_table[SCOPE(u)->state]; -} - -_pure_ static const char *scope_sub_state_to_string(Unit *u) { - assert(u); - - return scope_state_to_string(SCOPE(u)->state); -} - -static void scope_enumerate(Manager *m) { - Unit *u; - int r; - - assert(m); - - /* Let's unconditionally add the "init.scope" special unit - * that encapsulates PID 1. Note that PID 1 already is in the - * cgroup for this, we hence just need to allocate the object - * for it and that's it. */ - - u = manager_get_unit(m, SPECIAL_INIT_SCOPE); - if (!u) { - u = unit_new(m, sizeof(Scope)); - if (!u) { - log_oom(); - return; - } - - r = unit_add_name(u, SPECIAL_INIT_SCOPE); - if (r < 0) { - unit_free(u); - log_error_errno(r, "Failed to add init.scope name"); - return; - } - } - - u->transient = true; - u->default_dependencies = false; - u->no_gc = true; - u->ignore_on_isolate = true; - u->refuse_manual_start = true; - u->refuse_manual_stop = true; - SCOPE(u)->deserialized_state = SCOPE_RUNNING; - SCOPE(u)->kill_context.kill_signal = SIGRTMIN+14; - - /* Prettify things, if we can. */ - if (!u->description) - u->description = strdup("System and Service Manager"); - if (!u->documentation) - (void) strv_extend(&u->documentation, "man:systemd(1)"); - - unit_add_to_load_queue(u); - unit_add_to_dbus_queue(u); -} - -static const char* const scope_result_table[_SCOPE_RESULT_MAX] = { - [SCOPE_SUCCESS] = "success", - [SCOPE_FAILURE_RESOURCES] = "resources", - [SCOPE_FAILURE_TIMEOUT] = "timeout", -}; - -DEFINE_STRING_TABLE_LOOKUP(scope_result, ScopeResult); - -const UnitVTable scope_vtable = { - .object_size = sizeof(Scope), - .cgroup_context_offset = offsetof(Scope, cgroup_context), - .kill_context_offset = offsetof(Scope, kill_context), - - .sections = - "Unit\0" - "Scope\0" - "Install\0", - .private_section = "Scope", - - .no_alias = true, - .no_instances = true, - .can_transient = true, - - .init = scope_init, - .load = scope_load, - .done = scope_done, - - .coldplug = scope_coldplug, - - .dump = scope_dump, - - .start = scope_start, - .stop = scope_stop, - - .kill = scope_kill, - - .get_timeout = scope_get_timeout, - - .serialize = scope_serialize, - .deserialize_item = scope_deserialize_item, - - .active_state = scope_active_state, - .sub_state_to_string = scope_sub_state_to_string, - - .check_gc = scope_check_gc, - - .sigchld_event = scope_sigchld_event, - - .reset_failed = scope_reset_failed, - - .notify_cgroup_empty = scope_notify_cgroup_empty_event, - - .bus_vtable = bus_scope_vtable, - .bus_set_property = bus_scope_set_property, - .bus_commit_properties = bus_scope_commit_properties, - - .enumerate = scope_enumerate, -}; diff --git a/src/core/scope.h b/src/core/scope.h deleted file mode 100644 index 2dc86325c5..0000000000 --- a/src/core/scope.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -typedef struct Scope Scope; - -#include "kill.h" - -typedef enum ScopeResult { - SCOPE_SUCCESS, - SCOPE_FAILURE_RESOURCES, - SCOPE_FAILURE_TIMEOUT, - _SCOPE_RESULT_MAX, - _SCOPE_RESULT_INVALID = -1 -} ScopeResult; - -struct Scope { - Unit meta; - - CGroupContext cgroup_context; - KillContext kill_context; - - ScopeState state, deserialized_state; - ScopeResult result; - - usec_t timeout_stop_usec; - - char *controller; - - sd_event_source *timer_event_source; -}; - -extern const UnitVTable scope_vtable; - -int scope_abandon(Scope *s); - -const char* scope_result_to_string(ScopeResult i) _const_; -ScopeResult scope_result_from_string(const char *s) _pure_; diff --git a/src/core/selinux-access.c b/src/core/selinux-access.c deleted file mode 100644 index 2cdfcf7b5d..0000000000 --- a/src/core/selinux-access.c +++ /dev/null @@ -1,280 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Dan Walsh - - 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 . -***/ - -#include "selinux-access.h" - -#ifdef HAVE_SELINUX - -#include -#include -#include -#include -#ifdef HAVE_AUDIT -#include -#endif - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "audit-fd.h" -#include "bus-util.h" -#include "log.h" -#include "path-util.h" -#include "selinux-util.h" -#include "stdio-util.h" -#include "strv.h" -#include "util.h" - -static bool initialized = false; - -struct audit_info { - sd_bus_creds *creds; - const char *path; - const char *cmdline; -}; - -/* - Any time an access gets denied this callback will be called - with the audit data. We then need to just copy the audit data into the msgbuf. -*/ -static int audit_callback( - void *auditdata, - security_class_t cls, - char *msgbuf, - size_t msgbufsize) { - - const struct audit_info *audit = auditdata; - uid_t uid = 0, login_uid = 0; - gid_t gid = 0; - char login_uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a"; - char uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a"; - char gid_buf[DECIMAL_STR_MAX(gid_t) + 1] = "n/a"; - - if (sd_bus_creds_get_audit_login_uid(audit->creds, &login_uid) >= 0) - xsprintf(login_uid_buf, UID_FMT, login_uid); - if (sd_bus_creds_get_euid(audit->creds, &uid) >= 0) - xsprintf(uid_buf, UID_FMT, uid); - if (sd_bus_creds_get_egid(audit->creds, &gid) >= 0) - xsprintf(gid_buf, GID_FMT, gid); - - snprintf(msgbuf, msgbufsize, - "auid=%s uid=%s gid=%s%s%s%s%s%s%s", - login_uid_buf, uid_buf, gid_buf, - audit->path ? " path=\"" : "", strempty(audit->path), audit->path ? "\"" : "", - audit->cmdline ? " cmdline=\"" : "", strempty(audit->cmdline), audit->cmdline ? "\"" : ""); - - return 0; -} - -static int callback_type_to_priority(int type) { - switch(type) { - - case SELINUX_ERROR: - return LOG_ERR; - - case SELINUX_WARNING: - return LOG_WARNING; - - case SELINUX_INFO: - return LOG_INFO; - - case SELINUX_AVC: - default: - return LOG_NOTICE; - } -} - -/* - libselinux uses this callback when access gets denied or other - events happen. If audit is turned on, messages will be reported - using audit netlink, otherwise they will be logged using the usual - channels. - - Code copied from dbus and modified. -*/ -_printf_(2, 3) static int log_callback(int type, const char *fmt, ...) { - va_list ap; - -#ifdef HAVE_AUDIT - int fd; - - fd = get_audit_fd(); - - if (fd >= 0) { - _cleanup_free_ char *buf = NULL; - int r; - - va_start(ap, fmt); - r = vasprintf(&buf, fmt, ap); - va_end(ap); - - if (r >= 0) { - audit_log_user_avc_message(fd, AUDIT_USER_AVC, buf, NULL, NULL, NULL, 0); - return 0; - } - } -#endif - - va_start(ap, fmt); - log_internalv(LOG_AUTH | callback_type_to_priority(type), 0, __FILE__, __LINE__, __FUNCTION__, fmt, ap); - va_end(ap); - - return 0; -} - -static int access_init(sd_bus_error *error) { - - if (!mac_selinux_use()) - return 0; - - if (initialized) - return 1; - - if (avc_open(NULL, 0) != 0) { - int enforce, saved_errno = errno; - - enforce = security_getenforce(); - log_full_errno(enforce != 0 ? LOG_ERR : LOG_WARNING, saved_errno, "Failed to open the SELinux AVC: %m"); - - /* If enforcement isn't on, then let's suppress this - * error, and just don't do any AVC checks. The - * warning we printed is hence all the admin will - * see. */ - if (enforce == 0) - return 0; - - /* Return an access denied error, if we couldn't load - * the AVC but enforcing mode was on, or we couldn't - * determine whether it is one. */ - return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to open the SELinux AVC: %s", strerror(saved_errno)); - } - - selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback); - selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback); - - initialized = true; - return 1; -} - -/* - This function communicates with the kernel to check whether or not it should - allow the access. - If the machine is in permissive mode it will return ok. Audit messages will - still be generated if the access would be denied in enforcing mode. -*/ -int mac_selinux_generic_access_check( - sd_bus_message *message, - const char *path, - const char *permission, - sd_bus_error *error) { - - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - const char *tclass = NULL, *scon = NULL; - struct audit_info audit_info = {}; - _cleanup_free_ char *cl = NULL; - security_context_t fcon = NULL; - char **cmdline = NULL; - int r = 0; - - assert(message); - assert(permission); - assert(error); - - r = access_init(error); - if (r <= 0) - return r; - - r = sd_bus_query_sender_creds( - message, - SD_BUS_CREDS_PID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID| - SD_BUS_CREDS_CMDLINE|SD_BUS_CREDS_AUDIT_LOGIN_UID| - SD_BUS_CREDS_SELINUX_CONTEXT| - SD_BUS_CREDS_AUGMENT /* get more bits from /proc */, - &creds); - if (r < 0) - goto finish; - - /* The SELinux context is something we really should have - * gotten directly from the message or sender, and not be an - * augmented field. If it was augmented we cannot use it for - * authorization, since this is racy and vulnerable. Let's add - * an extra check, just in case, even though this really - * shouldn't be possible. */ - assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_SELINUX_CONTEXT) == 0, -EPERM); - - r = sd_bus_creds_get_selinux_context(creds, &scon); - if (r < 0) - goto finish; - - if (path) { - /* Get the file context of the unit file */ - - r = getfilecon_raw(path, &fcon); - if (r < 0) { - r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path); - goto finish; - } - - tclass = "service"; - } else { - r = getcon_raw(&fcon); - if (r < 0) { - r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get current context."); - goto finish; - } - - tclass = "system"; - } - - sd_bus_creds_get_cmdline(creds, &cmdline); - cl = strv_join(cmdline, " "); - - audit_info.creds = creds; - audit_info.path = path; - audit_info.cmdline = cl; - - r = selinux_check_access(scon, fcon, tclass, permission, &audit_info); - if (r < 0) - r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access."); - - log_debug("SELinux access check scon=%s tcon=%s tclass=%s perm=%s path=%s cmdline=%s: %i", scon, fcon, tclass, permission, path, cl, r); - -finish: - freecon(fcon); - - if (r < 0 && security_getenforce() != 1) { - sd_bus_error_free(error); - r = 0; - } - - return r; -} - -#else - -int mac_selinux_generic_access_check( - sd_bus_message *message, - const char *path, - const char *permission, - sd_bus_error *error) { - - return 0; -} - -#endif diff --git a/src/core/selinux-access.h b/src/core/selinux-access.h deleted file mode 100644 index 8f1f058a32..0000000000 --- a/src/core/selinux-access.h +++ /dev/null @@ -1,45 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 Dan Walsh - - 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 . -***/ - -#include "sd-bus.h" - -#include "bus-util.h" -#include "manager.h" - -int mac_selinux_generic_access_check(sd_bus_message *message, const char *path, const char *permission, sd_bus_error *error); - -#ifdef HAVE_SELINUX - -#define mac_selinux_access_check(message, permission, error) \ - mac_selinux_generic_access_check((message), NULL, (permission), (error)) - -#define mac_selinux_unit_access_check(unit, message, permission, error) \ - ({ \ - Unit *_unit = (unit); \ - mac_selinux_generic_access_check((message), _unit->source_path ?: _unit->fragment_path, (permission), (error)); \ - }) - -#else - -#define mac_selinux_access_check(message, permission, error) 0 -#define mac_selinux_unit_access_check(unit, message, permission, error) 0 - -#endif diff --git a/src/core/selinux-setup.c b/src/core/selinux-setup.c deleted file mode 100644 index 9a115a4387..0000000000 --- a/src/core/selinux-setup.c +++ /dev/null @@ -1,121 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#ifdef HAVE_SELINUX -#include -#endif - -#include "log.h" -#include "macro.h" -#include "selinux-setup.h" -#include "selinux-util.h" -#include "string-util.h" -#include "util.h" - -#ifdef HAVE_SELINUX -_printf_(2,3) -static int null_log(int type, const char *fmt, ...) { - return 0; -} -#endif - -int mac_selinux_setup(bool *loaded_policy) { - -#ifdef HAVE_SELINUX - int enforce = 0; - usec_t before_load, after_load; - security_context_t con; - int r; - union selinux_callback cb; - bool initialized = false; - - assert(loaded_policy); - - /* Turn off all of SELinux' own logging, we want to do that */ - cb.func_log = null_log; - selinux_set_callback(SELINUX_CB_LOG, cb); - - /* Don't load policy in the initrd if we don't appear to have - * it. For the real root, we check below if we've already - * loaded policy, and return gracefully. - */ - if (in_initrd() && access(selinux_path(), F_OK) < 0) - return 0; - - /* Already initialized by somebody else? */ - r = getcon_raw(&con); - if (r == 0) { - initialized = !streq(con, "kernel"); - freecon(con); - } - - /* Make sure we have no fds open while loading the policy and - * transitioning */ - log_close(); - - /* Now load the policy */ - before_load = now(CLOCK_MONOTONIC); - r = selinux_init_load_policy(&enforce); - if (r == 0) { - _cleanup_(mac_selinux_freep) char *label = NULL; - char timespan[FORMAT_TIMESPAN_MAX]; - - mac_selinux_retest(); - - /* Transition to the new context */ - r = mac_selinux_get_create_label_from_exe(SYSTEMD_BINARY_PATH, &label); - if (r < 0 || !label) { - log_open(); - log_error("Failed to compute init label, ignoring."); - } else { - r = setcon(label); - - log_open(); - if (r < 0) - log_error("Failed to transition into init label '%s', ignoring.", label); - } - - after_load = now(CLOCK_MONOTONIC); - - log_info("Successfully loaded SELinux policy in %s.", - format_timespan(timespan, sizeof(timespan), after_load - before_load, 0)); - - *loaded_policy = true; - - } else { - log_open(); - - if (enforce > 0) { - if (!initialized) { - log_emergency("Failed to load SELinux policy."); - return -EIO; - } - - log_warning("Failed to load new SELinux policy. Continuing with old policy."); - } else - log_debug("Unable to load SELinux policy. Ignoring."); - } -#endif - - return 0; -} diff --git a/src/core/selinux-setup.h b/src/core/selinux-setup.h deleted file mode 100644 index 7b613249b0..0000000000 --- a/src/core/selinux-setup.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -int mac_selinux_setup(bool *loaded_policy); diff --git a/src/core/service.c b/src/core/service.c deleted file mode 100644 index ac7e41d777..0000000000 --- a/src/core/service.c +++ /dev/null @@ -1,3366 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#include "alloc-util.h" -#include "async.h" -#include "bus-error.h" -#include "bus-kernel.h" -#include "bus-util.h" -#include "dbus-service.h" -#include "def.h" -#include "env-util.h" -#include "escape.h" -#include "exit-status.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "load-dropin.h" -#include "load-fragment.h" -#include "log.h" -#include "manager.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "service.h" -#include "signal-util.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit-printf.h" -#include "unit.h" -#include "utf8.h" -#include "util.h" - -static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { - [SERVICE_DEAD] = UNIT_INACTIVE, - [SERVICE_START_PRE] = UNIT_ACTIVATING, - [SERVICE_START] = UNIT_ACTIVATING, - [SERVICE_START_POST] = UNIT_ACTIVATING, - [SERVICE_RUNNING] = UNIT_ACTIVE, - [SERVICE_EXITED] = UNIT_ACTIVE, - [SERVICE_RELOAD] = UNIT_RELOADING, - [SERVICE_STOP] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGABRT] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, - [SERVICE_STOP_POST] = UNIT_DEACTIVATING, - [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, - [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, - [SERVICE_FAILED] = UNIT_FAILED, - [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING -}; - -/* For Type=idle we never want to delay any other jobs, hence we - * consider idle jobs active as soon as we start working on them */ -static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = { - [SERVICE_DEAD] = UNIT_INACTIVE, - [SERVICE_START_PRE] = UNIT_ACTIVE, - [SERVICE_START] = UNIT_ACTIVE, - [SERVICE_START_POST] = UNIT_ACTIVE, - [SERVICE_RUNNING] = UNIT_ACTIVE, - [SERVICE_EXITED] = UNIT_ACTIVE, - [SERVICE_RELOAD] = UNIT_RELOADING, - [SERVICE_STOP] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGABRT] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, - [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, - [SERVICE_STOP_POST] = UNIT_DEACTIVATING, - [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, - [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, - [SERVICE_FAILED] = UNIT_FAILED, - [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING -}; - -static int service_dispatch_io(sd_event_source *source, int fd, uint32_t events, void *userdata); -static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); -static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata); - -static void service_enter_signal(Service *s, ServiceState state, ServiceResult f); -static void service_enter_reload_by_notify(Service *s); - -static void service_init(Unit *u) { - Service *s = SERVICE(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - s->timeout_start_usec = u->manager->default_timeout_start_usec; - s->timeout_stop_usec = u->manager->default_timeout_stop_usec; - s->restart_usec = u->manager->default_restart_usec; - s->runtime_max_usec = USEC_INFINITY; - s->type = _SERVICE_TYPE_INVALID; - s->socket_fd = -1; - s->bus_endpoint_fd = -1; - s->stdin_fd = s->stdout_fd = s->stderr_fd = -1; - s->guess_main_pid = true; - - s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; -} - -static void service_unwatch_control_pid(Service *s) { - assert(s); - - if (s->control_pid <= 0) - return; - - unit_unwatch_pid(UNIT(s), s->control_pid); - s->control_pid = 0; -} - -static void service_unwatch_main_pid(Service *s) { - assert(s); - - if (s->main_pid <= 0) - return; - - unit_unwatch_pid(UNIT(s), s->main_pid); - s->main_pid = 0; -} - -static void service_unwatch_pid_file(Service *s) { - if (!s->pid_file_pathspec) - return; - - log_unit_debug(UNIT(s), "Stopping watch for PID file %s", s->pid_file_pathspec->path); - path_spec_unwatch(s->pid_file_pathspec); - path_spec_done(s->pid_file_pathspec); - s->pid_file_pathspec = mfree(s->pid_file_pathspec); -} - -static int service_set_main_pid(Service *s, pid_t pid) { - pid_t ppid; - - assert(s); - - if (pid <= 1) - return -EINVAL; - - if (pid == getpid()) - return -EINVAL; - - if (s->main_pid == pid && s->main_pid_known) - return 0; - - if (s->main_pid != pid) { - service_unwatch_main_pid(s); - exec_status_start(&s->main_exec_status, pid); - } - - s->main_pid = pid; - s->main_pid_known = true; - - if (get_process_ppid(pid, &ppid) >= 0 && ppid != getpid()) { - log_unit_warning(UNIT(s), "Supervising process "PID_FMT" which is not our child. We'll most likely not notice when it exits.", pid); - s->main_pid_alien = true; - } else - s->main_pid_alien = false; - - return 0; -} - -static void service_close_socket_fd(Service *s) { - assert(s); - - s->socket_fd = asynchronous_close(s->socket_fd); -} - -static void service_connection_unref(Service *s) { - assert(s); - - if (!UNIT_ISSET(s->accept_socket)) - return; - - socket_connection_unref(SOCKET(UNIT_DEREF(s->accept_socket))); - unit_ref_unset(&s->accept_socket); -} - -static void service_stop_watchdog(Service *s) { - assert(s); - - s->watchdog_event_source = sd_event_source_unref(s->watchdog_event_source); - s->watchdog_timestamp = DUAL_TIMESTAMP_NULL; -} - -static void service_start_watchdog(Service *s) { - int r; - - assert(s); - - if (s->watchdog_usec <= 0) - 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)); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to reset watchdog timer: %m"); - return; - } - - r = sd_event_source_set_enabled(s->watchdog_event_source, SD_EVENT_ONESHOT); - } else { - r = sd_event_add_time( - UNIT(s)->manager->event, - &s->watchdog_event_source, - CLOCK_MONOTONIC, - usec_add(s->watchdog_timestamp.monotonic, s->watchdog_usec), 0, - service_dispatch_watchdog, s); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to add watchdog timer: %m"); - return; - } - - (void) sd_event_source_set_description(s->watchdog_event_source, "service-watchdog"); - - /* Let's process everything else which might be a sign - * of living before we consider a service died. */ - r = sd_event_source_set_priority(s->watchdog_event_source, SD_EVENT_PRIORITY_IDLE); - } - - if (r < 0) - log_unit_warning_errno(UNIT(s), r, "Failed to install watchdog timer: %m"); -} - -static void service_reset_watchdog(Service *s) { - assert(s); - - dual_timestamp_get(&s->watchdog_timestamp); - service_start_watchdog(s); -} - -static void service_fd_store_unlink(ServiceFDStore *fs) { - - if (!fs) - return; - - if (fs->service) { - assert(fs->service->n_fd_store > 0); - LIST_REMOVE(fd_store, fs->service->fd_store, fs); - fs->service->n_fd_store--; - } - - if (fs->event_source) { - sd_event_source_set_enabled(fs->event_source, SD_EVENT_OFF); - sd_event_source_unref(fs->event_source); - } - - free(fs->fdname); - safe_close(fs->fd); - free(fs); -} - -static void service_release_resources(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0) - return; - - log_unit_debug(u, "Releasing all resources."); - - s->stdin_fd = safe_close(s->stdin_fd); - s->stdout_fd = safe_close(s->stdout_fd); - s->stderr_fd = safe_close(s->stderr_fd); - - while (s->fd_store) - service_fd_store_unlink(s->fd_store); - - assert(s->n_fd_store == 0); -} - -static void service_done(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - s->pid_file = mfree(s->pid_file); - s->status_text = mfree(s->status_text); - - s->exec_runtime = exec_runtime_unref(s->exec_runtime); - exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX); - s->control_command = NULL; - s->main_command = NULL; - - exit_status_set_free(&s->restart_prevent_status); - exit_status_set_free(&s->restart_force_status); - exit_status_set_free(&s->success_status); - - /* This will leak a process, but at least no memory or any of - * our resources */ - service_unwatch_main_pid(s); - service_unwatch_control_pid(s); - service_unwatch_pid_file(s); - - if (s->bus_name) { - unit_unwatch_bus_name(u, s->bus_name); - s->bus_name = mfree(s->bus_name); - } - - s->bus_name_owner = mfree(s->bus_name_owner); - - s->bus_endpoint_fd = safe_close(s->bus_endpoint_fd); - service_close_socket_fd(s); - service_connection_unref(s); - - unit_ref_unset(&s->accept_socket); - - service_stop_watchdog(s); - - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - - service_release_resources(u); -} - -static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) { - ServiceFDStore *fs = userdata; - - assert(e); - assert(fs); - - /* If we get either EPOLLHUP or EPOLLERR, it's time to remove this entry from the fd store */ - service_fd_store_unlink(fs); - return 0; -} - -static int service_add_fd_store(Service *s, int fd, const char *name) { - ServiceFDStore *fs; - int r; - - assert(s); - assert(fd >= 0); - - if (s->n_fd_store >= s->n_fd_store_max) - return 0; - - LIST_FOREACH(fd_store, fs, s->fd_store) { - r = same_fd(fs->fd, fd); - if (r < 0) - return r; - if (r > 0) { - /* Already included */ - safe_close(fd); - return 1; - } - } - - fs = new0(ServiceFDStore, 1); - if (!fs) - return -ENOMEM; - - fs->fd = fd; - fs->service = s; - fs->fdname = strdup(name ?: "stored"); - if (!fs->fdname) { - free(fs); - return -ENOMEM; - } - - r = sd_event_add_io(UNIT(s)->manager->event, &fs->event_source, fd, 0, on_fd_store_io, fs); - if (r < 0) { - free(fs->fdname); - free(fs); - return r; - } - - (void) sd_event_source_set_description(fs->event_source, "service-fd-store"); - - LIST_PREPEND(fd_store, s->fd_store, fs); - s->n_fd_store++; - - return 1; -} - -static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) { - int r; - - assert(s); - - if (fdset_size(fds) <= 0) - return 0; - - while (s->n_fd_store < s->n_fd_store_max) { - _cleanup_close_ int fd = -1; - - fd = fdset_steal_first(fds); - if (fd < 0) - break; - - r = service_add_fd_store(s, fd, name); - if (r < 0) - return log_unit_error_errno(UNIT(s), r, "Couldn't add fd to fd store: %m"); - if (r > 0) { - log_unit_debug(UNIT(s), "Added fd to fd store."); - fd = -1; - } - } - - if (fdset_size(fds) > 0) - log_unit_warning(UNIT(s), "Tried to store more fds than FileDescriptorStoreMax=%u allows, closing remaining.", s->n_fd_store_max); - - return 0; -} - -static int service_arm_timer(Service *s, usec_t usec) { - int r; - - assert(s); - - if (s->timer_event_source) { - r = sd_event_source_set_time(s->timer_event_source, usec); - if (r < 0) - return r; - - return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); - } - - if (usec == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - UNIT(s)->manager->event, - &s->timer_event_source, - CLOCK_MONOTONIC, - usec, 0, - service_dispatch_timer, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->timer_event_source, "service-timer"); - - return 0; -} - -static int service_verify(Service *s) { - assert(s); - - if (UNIT(s)->load_state != UNIT_LOADED) - return 0; - - if (!s->exec_command[SERVICE_EXEC_START] && !s->exec_command[SERVICE_EXEC_STOP]) { - log_unit_error(UNIT(s), "Service lacks both ExecStart= and ExecStop= setting. Refusing."); - return -EINVAL; - } - - if (s->type != SERVICE_ONESHOT && !s->exec_command[SERVICE_EXEC_START]) { - log_unit_error(UNIT(s), "Service has no ExecStart= setting, which is only allowed for Type=oneshot services. Refusing."); - return -EINVAL; - } - - if (!s->remain_after_exit && !s->exec_command[SERVICE_EXEC_START]) { - log_unit_error(UNIT(s), "Service has no ExecStart= setting, which is only allowed for RemainAfterExit=yes services. Refusing."); - return -EINVAL; - } - - if (s->type != SERVICE_ONESHOT && s->exec_command[SERVICE_EXEC_START]->command_next) { - log_unit_error(UNIT(s), "Service has more than one ExecStart= setting, which is only allowed for Type=oneshot services. Refusing."); - return -EINVAL; - } - - if (s->type == SERVICE_ONESHOT && s->restart != SERVICE_RESTART_NO) { - log_unit_error(UNIT(s), "Service has Restart= setting other than no, which isn't allowed for Type=oneshot services. Refusing."); - return -EINVAL; - } - - if (s->type == SERVICE_ONESHOT && !exit_status_set_is_empty(&s->restart_force_status)) { - log_unit_error(UNIT(s), "Service has RestartForceStatus= set, which isn't allowed for Type=oneshot services. Refusing."); - return -EINVAL; - } - - if (s->type == SERVICE_DBUS && !s->bus_name) { - log_unit_error(UNIT(s), "Service is of type D-Bus but no D-Bus service name has been specified. Refusing."); - return -EINVAL; - } - - if (s->bus_name && s->type != SERVICE_DBUS) - log_unit_warning(UNIT(s), "Service has a D-Bus service name specified, but is not of type dbus. Ignoring."); - - if (s->exec_context.pam_name && !(s->kill_context.kill_mode == KILL_CONTROL_GROUP || s->kill_context.kill_mode == KILL_MIXED)) { - log_unit_error(UNIT(s), "Service has PAM enabled. Kill mode must be set to 'control-group' or 'mixed'. Refusing."); - return -EINVAL; - } - - if (s->usb_function_descriptors && !s->usb_function_strings) - log_unit_warning(UNIT(s), "Service has USBFunctionDescriptors= setting, but no USBFunctionStrings=. Ignoring."); - - if (!s->usb_function_descriptors && s->usb_function_strings) - log_unit_warning(UNIT(s), "Service has USBFunctionStrings= setting, but no USBFunctionDescriptors=. Ignoring."); - - if (s->runtime_max_usec != USEC_INFINITY && s->type == SERVICE_ONESHOT) - log_unit_warning(UNIT(s), "MaxRuntimeSec= has no effect in combination with Type=oneshot. Ignoring."); - - return 0; -} - -static int service_add_default_dependencies(Service *s) { - int r; - - assert(s); - - if (!UNIT(s)->default_dependencies) - return 0; - - /* Add a number of automatic dependencies useful for the - * majority of services. */ - - if (UNIT(s)->manager->running_as == MANAGER_SYSTEM) { - /* First, pull in the really early boot stuff, and - * require it, so that we fail if we can't acquire - * it. */ - - r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); - if (r < 0) - return r; - } else { - - /* In the --user instance there's no sysinit.target, - * in that case require basic.target instead. */ - - r = unit_add_dependency_by_name(UNIT(s), UNIT_REQUIRES, SPECIAL_BASIC_TARGET, NULL, true); - if (r < 0) - return r; - } - - /* Second, if the rest of the base system is in the same - * transaction, order us after it, but do not pull it in or - * even require it. */ - r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_BASIC_TARGET, NULL, true); - if (r < 0) - return r; - - /* Third, add us in for normal shutdown. */ - return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -static void service_fix_output(Service *s) { - assert(s); - - /* If nothing has been explicitly configured, patch default - * output in. If input is socket/tty we avoid this however, - * since in that case we want output to default to the same - * place as we read input from. */ - - if (s->exec_context.std_error == EXEC_OUTPUT_INHERIT && - s->exec_context.std_output == EXEC_OUTPUT_INHERIT && - s->exec_context.std_input == EXEC_INPUT_NULL) - s->exec_context.std_error = UNIT(s)->manager->default_std_error; - - if (s->exec_context.std_output == EXEC_OUTPUT_INHERIT && - s->exec_context.std_input == EXEC_INPUT_NULL) - s->exec_context.std_output = UNIT(s)->manager->default_std_output; -} - -static int service_setup_bus_name(Service *s) { - int r; - - assert(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"); - } - - /* 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); - if (r < 0) - return log_unit_error_errno(UNIT(s), r, "Failed to add dependency on " SPECIAL_DBUS_SOCKET ": %m"); - - r = unit_watch_bus_name(UNIT(s), s->bus_name); - if (r == -EEXIST) - return log_unit_error_errno(UNIT(s), r, "Two services allocated for the same bus name %s, refusing operation.", s->bus_name); - if (r < 0) - return log_unit_error_errno(UNIT(s), r, "Cannot watch bus name %s: %m", s->bus_name); - - return 0; -} - -static int service_add_extras(Service *s) { - int r; - - assert(s); - - if (s->type == _SERVICE_TYPE_INVALID) { - /* Figure out a type automatically */ - if (s->bus_name) - s->type = SERVICE_DBUS; - else if (s->exec_command[SERVICE_EXEC_START]) - s->type = SERVICE_SIMPLE; - else - s->type = SERVICE_ONESHOT; - } - - /* Oneshot services have disabled start timeout by default */ - if (s->type == SERVICE_ONESHOT && !s->start_timeout_defined) - s->timeout_start_usec = USEC_INFINITY; - - service_fix_output(s); - - r = unit_patch_contexts(UNIT(s)); - if (r < 0) - return r; - - r = unit_add_exec_dependencies(UNIT(s), &s->exec_context); - if (r < 0) - return r; - - r = unit_set_default_slice(UNIT(s)); - if (r < 0) - return r; - - if (s->type == SERVICE_NOTIFY && s->notify_access == NOTIFY_NONE) - s->notify_access = NOTIFY_MAIN; - - if (s->watchdog_usec > 0 && s->notify_access == NOTIFY_NONE) - s->notify_access = NOTIFY_MAIN; - - r = service_add_default_dependencies(s); - if (r < 0) - return r; - - r = service_setup_bus_name(s); - if (r < 0) - return r; - - return 0; -} - -static int service_load(Unit *u) { - Service *s = SERVICE(u); - int r; - - assert(s); - - /* Load a .service file */ - r = unit_load_fragment(u); - if (r < 0) - return r; - - /* Still nothing found? Then let's give up */ - if (u->load_state == UNIT_STUB) - return -ENOENT; - - /* This is a new unit? Then let's add in some extras */ - if (u->load_state == UNIT_LOADED) { - - /* We were able to load something, then let's add in - * the dropin directories. */ - r = unit_load_dropin(u); - if (r < 0) - return r; - - /* This is a new unit? Then let's add in some - * extras */ - r = service_add_extras(s); - if (r < 0) - return r; - } - - return service_verify(s); -} - -static void service_dump(Unit *u, FILE *f, const char *prefix) { - ServiceExecCommand c; - Service *s = SERVICE(u); - const char *prefix2; - - assert(s); - - prefix = strempty(prefix); - prefix2 = strjoina(prefix, "\t"); - - fprintf(f, - "%sService State: %s\n" - "%sResult: %s\n" - "%sReload Result: %s\n" - "%sPermissionsStartOnly: %s\n" - "%sRootDirectoryStartOnly: %s\n" - "%sRemainAfterExit: %s\n" - "%sGuessMainPID: %s\n" - "%sType: %s\n" - "%sRestart: %s\n" - "%sNotifyAccess: %s\n" - "%sNotifyState: %s\n", - prefix, service_state_to_string(s->state), - prefix, service_result_to_string(s->result), - prefix, service_result_to_string(s->reload_result), - prefix, yes_no(s->permissions_start_only), - prefix, yes_no(s->root_directory_start_only), - prefix, yes_no(s->remain_after_exit), - prefix, yes_no(s->guess_main_pid), - prefix, service_type_to_string(s->type), - prefix, service_restart_to_string(s->restart), - prefix, notify_access_to_string(s->notify_access), - prefix, notify_state_to_string(s->notify_state)); - - if (s->control_pid > 0) - fprintf(f, - "%sControl PID: "PID_FMT"\n", - prefix, s->control_pid); - - if (s->main_pid > 0) - fprintf(f, - "%sMain PID: "PID_FMT"\n" - "%sMain PID Known: %s\n" - "%sMain PID Alien: %s\n", - prefix, s->main_pid, - prefix, yes_no(s->main_pid_known), - prefix, yes_no(s->main_pid_alien)); - - if (s->pid_file) - fprintf(f, - "%sPIDFile: %s\n", - prefix, s->pid_file); - - if (s->bus_name) - fprintf(f, - "%sBusName: %s\n" - "%sBus Name Good: %s\n", - prefix, s->bus_name, - prefix, yes_no(s->bus_name_good)); - - kill_context_dump(&s->kill_context, f, prefix); - exec_context_dump(&s->exec_context, f, prefix); - - for (c = 0; c < _SERVICE_EXEC_COMMAND_MAX; c++) { - - if (!s->exec_command[c]) - continue; - - fprintf(f, "%s-> %s:\n", - prefix, service_exec_command_to_string(c)); - - exec_command_dump_list(s->exec_command[c], f, prefix2); - } - - if (s->status_text) - fprintf(f, "%sStatus Text: %s\n", - prefix, s->status_text); - - if (s->n_fd_store_max > 0) - fprintf(f, - "%sFile Descriptor Store Max: %u\n" - "%sFile Descriptor Store Current: %u\n", - prefix, s->n_fd_store_max, - prefix, s->n_fd_store); -} - -static int service_load_pid_file(Service *s, bool may_warn) { - _cleanup_free_ char *k = NULL; - int r; - pid_t pid; - - assert(s); - - if (!s->pid_file) - return -ENOENT; - - r = read_one_line_file(s->pid_file, &k); - if (r < 0) { - if (may_warn) - log_unit_info_errno(UNIT(s), r, "PID file %s not readable (yet?) after %s: %m", s->pid_file, service_state_to_string(s->state)); - return r; - } - - r = parse_pid(k, &pid); - if (r < 0) { - if (may_warn) - log_unit_info_errno(UNIT(s), r, "Failed to read PID from file %s: %m", s->pid_file); - return r; - } - - if (!pid_is_alive(pid)) { - if (may_warn) - log_unit_info(UNIT(s), "PID "PID_FMT" read from file %s does not exist or is a zombie.", pid, s->pid_file); - return -ESRCH; - } - - if (s->main_pid_known) { - if (pid == s->main_pid) - return 0; - - log_unit_debug(UNIT(s), "Main PID changing: "PID_FMT" -> "PID_FMT, s->main_pid, pid); - - service_unwatch_main_pid(s); - s->main_pid_known = false; - } else - log_unit_debug(UNIT(s), "Main PID loaded: "PID_FMT, pid); - - r = service_set_main_pid(s, pid); - if (r < 0) - return r; - - r = unit_watch_pid(UNIT(s), pid); - if (r < 0) { - /* FIXME: we need to do something here */ - log_unit_warning_errno(UNIT(s), r, "Failed to watch PID "PID_FMT" for service: %m", pid); - return r; - } - - return 0; -} - -static int service_search_main_pid(Service *s) { - pid_t pid = 0; - int r; - - assert(s); - - /* If we know it anyway, don't ever fallback to unreliable - * heuristics */ - if (s->main_pid_known) - return 0; - - if (!s->guess_main_pid) - return 0; - - assert(s->main_pid <= 0); - - r = unit_search_main_pid(UNIT(s), &pid); - if (r < 0) - return r; - - log_unit_debug(UNIT(s), "Main PID guessed: "PID_FMT, pid); - r = service_set_main_pid(s, pid); - if (r < 0) - return r; - - r = unit_watch_pid(UNIT(s), pid); - if (r < 0) { - /* FIXME: we need to do something here */ - log_unit_warning_errno(UNIT(s), r, "Failed to watch PID "PID_FMT" from: %m", pid); - return r; - } - - return 0; -} - -static void service_set_state(Service *s, ServiceState state) { - ServiceState old_state; - const UnitActiveState *table; - - assert(s); - - table = s->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table; - - old_state = s->state; - s->state = state; - - service_unwatch_pid_file(s); - - if (!IN_SET(state, - SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - SERVICE_RUNNING, - SERVICE_RELOAD, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, - SERVICE_AUTO_RESTART)) - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - - if (!IN_SET(state, - SERVICE_START, SERVICE_START_POST, - SERVICE_RUNNING, SERVICE_RELOAD, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { - service_unwatch_main_pid(s); - s->main_command = NULL; - } - - if (!IN_SET(state, - SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - SERVICE_RELOAD, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { - service_unwatch_control_pid(s); - s->control_command = NULL; - s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; - } - - if (IN_SET(state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) - unit_unwatch_all_pids(UNIT(s)); - - if (!IN_SET(state, - SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - SERVICE_RUNNING, SERVICE_RELOAD, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) && - !(state == SERVICE_DEAD && UNIT(s)->job)) { - service_close_socket_fd(s); - service_connection_unref(s); - } - - if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) - service_stop_watchdog(s); - - /* For the inactive states unit_notify() will trim the cgroup, - * but for exit we have to do that ourselves... */ - if (state == SERVICE_EXITED && UNIT(s)->manager->n_reloading <= 0) - unit_prune_cgroup(UNIT(s)); - - /* For remain_after_exit services, let's see if we can "release" the - * hold on the console, since unit_notify() only does that in case of - * change of state */ - if (state == SERVICE_EXITED && - s->remain_after_exit && - UNIT(s)->manager->n_on_console > 0) { - - ExecContext *ec; - - ec = unit_get_exec_context(UNIT(s)); - if (ec && exec_context_may_touch_console(ec)) { - Manager *m = UNIT(s)->manager; - - m->n_on_console --; - if (m->n_on_console == 0) - /* unset no_console_output flag, since the console is free */ - m->no_console_output = false; - } - } - - if (old_state != state) - log_unit_debug(UNIT(s), "Changed %s -> %s", service_state_to_string(old_state), service_state_to_string(state)); - - unit_notify(UNIT(s), table[old_state], table[state], s->reload_result == SERVICE_SUCCESS); -} - -static usec_t service_coldplug_timeout(Service *s) { - assert(s); - - switch (s->deserialized_state) { - - case SERVICE_START_PRE: - case SERVICE_START: - case SERVICE_START_POST: - case SERVICE_RELOAD: - return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_start_usec); - - case SERVICE_RUNNING: - return usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec); - - case SERVICE_STOP: - case SERVICE_STOP_SIGABRT: - case SERVICE_STOP_SIGTERM: - case SERVICE_STOP_SIGKILL: - case SERVICE_STOP_POST: - case SERVICE_FINAL_SIGTERM: - case SERVICE_FINAL_SIGKILL: - return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_stop_usec); - - case SERVICE_AUTO_RESTART: - return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, s->restart_usec); - - default: - return USEC_INFINITY; - } -} - -static int service_coldplug(Unit *u) { - Service *s = SERVICE(u); - int r; - - assert(s); - assert(s->state == SERVICE_DEAD); - - if (s->deserialized_state == s->state) - return 0; - - r = service_arm_timer(s, service_coldplug_timeout(s)); - if (r < 0) - return r; - - if (s->main_pid > 0 && - pid_is_unwaited(s->main_pid) && - ((s->deserialized_state == SERVICE_START && IN_SET(s->type, SERVICE_FORKING, SERVICE_DBUS, SERVICE_ONESHOT, SERVICE_NOTIFY)) || - IN_SET(s->deserialized_state, - SERVICE_START, SERVICE_START_POST, - SERVICE_RUNNING, SERVICE_RELOAD, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))) { - r = unit_watch_pid(UNIT(s), s->main_pid); - if (r < 0) - return r; - } - - if (s->control_pid > 0 && - pid_is_unwaited(s->control_pid) && - IN_SET(s->deserialized_state, - SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, - SERVICE_RELOAD, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { - r = unit_watch_pid(UNIT(s), s->control_pid); - if (r < 0) - return r; - } - - if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) - unit_watch_all_pids(UNIT(s)); - - if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) - service_start_watchdog(s); - - service_set_state(s, s->deserialized_state); - return 0; -} - -static int service_collect_fds(Service *s, int **fds, char ***fd_names) { - _cleanup_strv_free_ char **rfd_names = NULL; - _cleanup_free_ int *rfds = NULL; - int rn_fds = 0, r; - - assert(s); - assert(fds); - assert(fd_names); - - if (s->socket_fd >= 0) { - - /* Pass the per-connection socket */ - - rfds = new(int, 1); - if (!rfds) - return -ENOMEM; - rfds[0] = s->socket_fd; - - rfd_names = strv_new("connection", NULL); - if (!rfd_names) - return -ENOMEM; - - rn_fds = 1; - } else { - Iterator i; - Unit *u; - - /* Pass all our configured sockets for singleton services */ - - SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) { - _cleanup_free_ int *cfds = NULL; - Socket *sock; - int cn_fds; - - if (u->type != UNIT_SOCKET) - continue; - - sock = SOCKET(u); - - cn_fds = socket_collect_fds(sock, &cfds); - if (cn_fds < 0) - return cn_fds; - - if (cn_fds <= 0) - continue; - - if (!rfds) { - rfds = cfds; - rn_fds = cn_fds; - - cfds = NULL; - } else { - int *t; - - t = realloc(rfds, (rn_fds + cn_fds) * sizeof(int)); - if (!t) - return -ENOMEM; - - memcpy(t + rn_fds, cfds, cn_fds * sizeof(int)); - - rfds = t; - rn_fds += cn_fds; - } - - r = strv_extend_n(&rfd_names, socket_fdname(sock), cn_fds); - if (r < 0) - return r; - } - } - - if (s->n_fd_store > 0) { - ServiceFDStore *fs; - char **nl; - int *t; - - t = realloc(rfds, (rn_fds + s->n_fd_store) * sizeof(int)); - if (!t) - return -ENOMEM; - - rfds = t; - - nl = realloc(rfd_names, (rn_fds + s->n_fd_store + 1) * sizeof(char*)); - if (!nl) - return -ENOMEM; - - rfd_names = nl; - - LIST_FOREACH(fd_store, fs, s->fd_store) { - rfds[rn_fds] = fs->fd; - rfd_names[rn_fds] = strdup(strempty(fs->fdname)); - if (!rfd_names[rn_fds]) - return -ENOMEM; - - rn_fds++; - } - - rfd_names[rn_fds] = NULL; - } - - *fds = rfds; - *fd_names = rfd_names; - - rfds = NULL; - rfd_names = NULL; - - return rn_fds; -} - -static int service_spawn( - Service *s, - ExecCommand *c, - usec_t timeout, - bool pass_fds, - bool apply_permissions, - bool apply_chroot, - bool apply_tty_stdin, - bool is_control, - pid_t *_pid) { - - _cleanup_strv_free_ char **argv = NULL, **final_env = NULL, **our_env = NULL, **fd_names = NULL; - _cleanup_free_ char *bus_endpoint_path = NULL; - _cleanup_free_ int *fds = NULL; - unsigned n_fds = 0, n_env = 0; - const char *path; - pid_t pid; - - ExecParameters exec_params = { - .apply_permissions = apply_permissions, - .apply_chroot = apply_chroot, - .apply_tty_stdin = apply_tty_stdin, - .bus_endpoint_fd = -1, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, - }; - - int r; - - assert(s); - assert(c); - assert(_pid); - - (void) unit_realize_cgroup(UNIT(s)); - if (s->reset_cpu_usage) { - (void) unit_reset_cpu_usage(UNIT(s)); - s->reset_cpu_usage = false; - } - - r = unit_setup_exec_runtime(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 || - s->exec_context.std_error == EXEC_OUTPUT_SOCKET) { - - r = service_collect_fds(s, &fds, &fd_names); - if (r < 0) - return r; - - n_fds = r; - } - - r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), timeout)); - if (r < 0) - return r; - - r = unit_full_printf_strv(UNIT(s), c->argv, &argv); - if (r < 0) - return r; - - our_env = new0(char*, 6); - if (!our_env) - return -ENOMEM; - - if (is_control ? s->notify_access == NOTIFY_ALL : s->notify_access != NOTIFY_NONE) - if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0) - return -ENOMEM; - - if (s->main_pid > 0) - if (asprintf(our_env + n_env++, "MAINPID="PID_FMT, s->main_pid) < 0) - return -ENOMEM; - - if (UNIT(s)->manager->running_as != MANAGER_SYSTEM) - if (asprintf(our_env + n_env++, "MANAGERPID="PID_FMT, getpid()) < 0) - return -ENOMEM; - - if (s->socket_fd >= 0) { - union sockaddr_union sa; - socklen_t salen = sizeof(sa); - - r = getpeername(s->socket_fd, &sa.sa, &salen); - if (r < 0) - return -errno; - - if (IN_SET(sa.sa.sa_family, AF_INET, AF_INET6)) { - _cleanup_free_ char *addr = NULL; - char *t; - int port; - - r = sockaddr_pretty(&sa.sa, salen, true, false, &addr); - if (r < 0) - return r; - - t = strappend("REMOTE_ADDR=", addr); - if (!t) - return -ENOMEM; - our_env[n_env++] = t; - - port = sockaddr_port(&sa.sa); - if (port < 0) - return port; - - if (asprintf(&t, "REMOTE_PORT=%u", port) < 0) - return -ENOMEM; - our_env[n_env++] = t; - } - } - - final_env = strv_env_merge(2, UNIT(s)->manager->environment, our_env, NULL); - if (!final_env) - return -ENOMEM; - - if (is_control && UNIT(s)->cgroup_path) { - path = strjoina(UNIT(s)->cgroup_path, "/control"); - (void) cg_create(SYSTEMD_CGROUP_CONTROLLER, path); - } else - path = UNIT(s)->cgroup_path; - - if (s->exec_context.bus_endpoint) { - r = bus_kernel_create_endpoint(UNIT(s)->manager->running_as == MANAGER_SYSTEM ? "system" : "user", - UNIT(s)->id, &bus_endpoint_path); - if (r < 0) - return r; - - /* Pass the fd to the exec_params so that the child process can upload the policy. - * Keep a reference to the fd in the service, so the endpoint is kept alive as long - * as the service is running. */ - exec_params.bus_endpoint_fd = s->bus_endpoint_fd = r; - } - - exec_params.argv = argv; - exec_params.fds = fds; - exec_params.fd_names = fd_names; - exec_params.n_fds = n_fds; - exec_params.environment = final_env; - exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; - exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; - exec_params.cgroup_path = path; - exec_params.cgroup_delegate = s->cgroup_context.delegate; - exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(s)->manager); - exec_params.watchdog_usec = s->watchdog_usec; - exec_params.bus_endpoint_path = bus_endpoint_path; - exec_params.selinux_context_net = s->socket_fd_selinux_context_net; - if (s->type == SERVICE_IDLE) - exec_params.idle_pipe = UNIT(s)->manager->idle_pipe; - exec_params.stdin_fd = s->stdin_fd; - exec_params.stdout_fd = s->stdout_fd; - exec_params.stderr_fd = s->stderr_fd; - - r = exec_spawn(UNIT(s), - c, - &s->exec_context, - &exec_params, - s->exec_runtime, - &pid); - if (r < 0) - return r; - - r = unit_watch_pid(UNIT(s), pid); - if (r < 0) - /* FIXME: we need to do something here */ - return r; - - *_pid = pid; - - return 0; -} - -static int main_pid_good(Service *s) { - assert(s); - - /* Returns 0 if the pid is dead, 1 if it is good, -1 if we - * don't know */ - - /* If we know the pid file, then let's just check if it is - * still valid */ - if (s->main_pid_known) { - - /* If it's an alien child let's check if it is still - * alive ... */ - if (s->main_pid_alien && s->main_pid > 0) - return pid_is_alive(s->main_pid); - - /* .. otherwise assume we'll get a SIGCHLD for it, - * which we really should wait for to collect exit - * status and code */ - return s->main_pid > 0; - } - - /* We don't know the pid */ - return -EAGAIN; -} - -_pure_ static int control_pid_good(Service *s) { - assert(s); - - return s->control_pid > 0; -} - -static int cgroup_good(Service *s) { - int r; - - assert(s); - - if (!UNIT(s)->cgroup_path) - return 0; - - r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, UNIT(s)->cgroup_path); - if (r < 0) - return r; - - return !r; -} - -static bool service_shall_restart(Service *s) { - assert(s); - - /* Don't restart after manual stops */ - if (s->forbid_restart) - return false; - - /* Never restart if this is configured as special exception */ - if (exit_status_set_test(&s->restart_prevent_status, s->main_exec_status.code, s->main_exec_status.status)) - return false; - - /* Restart if the exit code/status are configured as restart triggers */ - if (exit_status_set_test(&s->restart_force_status, s->main_exec_status.code, s->main_exec_status.status)) - return true; - - switch (s->restart) { - - case SERVICE_RESTART_NO: - return false; - - case SERVICE_RESTART_ALWAYS: - return true; - - case SERVICE_RESTART_ON_SUCCESS: - return s->result == SERVICE_SUCCESS; - - case SERVICE_RESTART_ON_FAILURE: - return s->result != SERVICE_SUCCESS; - - case SERVICE_RESTART_ON_ABNORMAL: - return !IN_SET(s->result, SERVICE_SUCCESS, SERVICE_FAILURE_EXIT_CODE); - - case SERVICE_RESTART_ON_WATCHDOG: - return s->result == SERVICE_FAILURE_WATCHDOG; - - case SERVICE_RESTART_ON_ABORT: - return IN_SET(s->result, SERVICE_FAILURE_SIGNAL, SERVICE_FAILURE_CORE_DUMP); - - default: - assert_not_reached("unknown restart setting"); - } -} - -static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) { - int r; - assert(s); - - if (f != SERVICE_SUCCESS) - s->result = f; - - service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD); - - if (s->result != SERVICE_SUCCESS) { - log_unit_warning(UNIT(s), "Failed with result '%s'.", service_result_to_string(s->result)); - failure_action(UNIT(s)->manager, s->failure_action, UNIT(s)->reboot_arg); - } - - if (allow_restart && service_shall_restart(s)) { - - r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->restart_usec)); - if (r < 0) - goto fail; - - service_set_state(s, SERVICE_AUTO_RESTART); - } - - /* The next restart might not be a manual stop, hence reset the flag indicating manual stops */ - s->forbid_restart = false; - - /* We want fresh tmpdirs in case service is started again immediately */ - exec_runtime_destroy(s->exec_runtime); - s->exec_runtime = exec_runtime_unref(s->exec_runtime); - - /* Also, remove the runtime directory in */ - exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); - - /* 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. */ - if (s->pid_file) - (void) unlink(s->pid_file); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run install restart timer: %m"); - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); -} - -static void service_enter_stop_post(Service *s, ServiceResult f) { - int r; - assert(s); - - if (f != SERVICE_SUCCESS) - s->result = f; - - service_unwatch_control_pid(s); - unit_watch_all_pids(UNIT(s)); - - s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST]; - if (s->control_command) { - s->control_command_id = SERVICE_EXEC_STOP_POST; - - r = service_spawn(s, - s->control_command, - s->timeout_stop_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - true, - true, - &s->control_pid); - if (r < 0) - goto fail; - - service_set_state(s, SERVICE_STOP_POST); - } else - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop-post' task: %m"); - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); -} - -static int state_to_kill_operation(ServiceState state) { - switch (state) { - - case SERVICE_STOP_SIGABRT: - return KILL_ABORT; - - case SERVICE_STOP_SIGTERM: - case SERVICE_FINAL_SIGTERM: - return KILL_TERMINATE; - - case SERVICE_STOP_SIGKILL: - case SERVICE_FINAL_SIGKILL: - return KILL_KILL; - - default: - return _KILL_OPERATION_INVALID; - } -} - -static void service_enter_signal(Service *s, ServiceState state, ServiceResult f) { - int r; - - assert(s); - - if (f != SERVICE_SUCCESS) - s->result = f; - - unit_watch_all_pids(UNIT(s)); - - r = unit_kill_context( - UNIT(s), - &s->kill_context, - state_to_kill_operation(state), - s->main_pid, - s->control_pid, - s->main_pid_alien); - - if (r < 0) - goto fail; - - if (r > 0) { - r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); - if (r < 0) - goto fail; - - service_set_state(s, state); - } else if (IN_SET(state, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM) && s->kill_context.send_sigkill) - service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_SUCCESS); - else if (IN_SET(state, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL)) - service_enter_stop_post(s, SERVICE_SUCCESS); - else if (state == SERVICE_FINAL_SIGTERM && s->kill_context.send_sigkill) - service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_SUCCESS); - else - service_enter_dead(s, SERVICE_SUCCESS, true); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); - - if (IN_SET(state, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL)) - service_enter_stop_post(s, SERVICE_FAILURE_RESOURCES); - else - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); -} - -static void service_enter_stop_by_notify(Service *s) { - assert(s); - - unit_watch_all_pids(UNIT(s)); - - service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); - - /* The service told us it's stopping, so it's as if we SIGTERM'd it. */ - service_set_state(s, SERVICE_STOP_SIGTERM); -} - -static void service_enter_stop(Service *s, ServiceResult f) { - int r; - - assert(s); - - if (f != SERVICE_SUCCESS) - s->result = f; - - service_unwatch_control_pid(s); - unit_watch_all_pids(UNIT(s)); - - s->control_command = s->exec_command[SERVICE_EXEC_STOP]; - if (s->control_command) { - s->control_command_id = SERVICE_EXEC_STOP; - - r = service_spawn(s, - s->control_command, - s->timeout_stop_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - true, - &s->control_pid); - if (r < 0) - goto fail; - - service_set_state(s, SERVICE_STOP); - } else - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop' task: %m"); - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); -} - -static bool service_good(Service *s) { - int main_pid_ok; - assert(s); - - if (s->type == SERVICE_DBUS && !s->bus_name_good) - return false; - - main_pid_ok = main_pid_good(s); - if (main_pid_ok > 0) /* It's alive */ - return true; - if (main_pid_ok == 0) /* It's dead */ - return false; - - /* OK, we don't know anything about the main PID, maybe - * because there is none. Let's check the control group - * instead. */ - - return cgroup_good(s) != 0; -} - -static void service_enter_running(Service *s, ServiceResult f) { - assert(s); - - if (f != SERVICE_SUCCESS) - s->result = f; - - service_unwatch_control_pid(s); - - if (service_good(s)) { - - /* If there are any queued up sd_notify() - * notifications, process them now */ - if (s->notify_state == NOTIFY_RELOADING) - service_enter_reload_by_notify(s); - else if (s->notify_state == NOTIFY_STOPPING) - service_enter_stop_by_notify(s); - else { - service_set_state(s, SERVICE_RUNNING); - service_arm_timer(s, usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec)); - } - - } else if (s->remain_after_exit) - service_set_state(s, SERVICE_EXITED); - else - service_enter_stop(s, SERVICE_SUCCESS); -} - -static void service_enter_start_post(Service *s) { - int r; - assert(s); - - service_unwatch_control_pid(s); - service_reset_watchdog(s); - - s->control_command = s->exec_command[SERVICE_EXEC_START_POST]; - if (s->control_command) { - s->control_command_id = SERVICE_EXEC_START_POST; - - r = service_spawn(s, - s->control_command, - s->timeout_start_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - true, - &s->control_pid); - if (r < 0) - goto fail; - - service_set_state(s, SERVICE_START_POST); - } else - service_enter_running(s, SERVICE_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-post' task: %m"); - service_enter_stop(s, SERVICE_FAILURE_RESOURCES); -} - -static void service_kill_control_processes(Service *s) { - char *p; - - if (!UNIT(s)->cgroup_path) - return; - - p = strjoina(UNIT(s)->cgroup_path, "/control"); - cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, p, SIGKILL, true, true, true, NULL); -} - -static void service_enter_start(Service *s) { - ExecCommand *c; - usec_t timeout; - pid_t pid; - int r; - - assert(s); - - service_unwatch_control_pid(s); - service_unwatch_main_pid(s); - - /* We want to ensure that nobody leaks processes from - * START_PRE here, so let's go on a killing spree, People - * should not spawn long running processes from START_PRE. */ - service_kill_control_processes(s); - - if (s->type == SERVICE_FORKING) { - s->control_command_id = SERVICE_EXEC_START; - c = s->control_command = s->exec_command[SERVICE_EXEC_START]; - - s->main_command = NULL; - } else { - s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; - s->control_command = NULL; - - c = s->main_command = s->exec_command[SERVICE_EXEC_START]; - } - - if (!c) { - assert(s->type == SERVICE_ONESHOT); - service_enter_start_post(s); - return; - } - - if (IN_SET(s->type, SERVICE_SIMPLE, SERVICE_IDLE)) - /* For simple + idle this is the main process. We don't apply any timeout here, but - * service_enter_running() will later apply the .runtime_max_usec timeout. */ - timeout = USEC_INFINITY; - else - timeout = s->timeout_start_usec; - - r = service_spawn(s, - c, - timeout, - true, - true, - true, - true, - false, - &pid); - if (r < 0) - goto fail; - - if (IN_SET(s->type, SERVICE_SIMPLE, SERVICE_IDLE)) { - /* For simple services we immediately start - * the START_POST binaries. */ - - service_set_main_pid(s, pid); - service_enter_start_post(s); - - } else if (s->type == SERVICE_FORKING) { - - /* For forking services we wait until the start - * process exited. */ - - s->control_pid = pid; - service_set_state(s, SERVICE_START); - - } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY)) { - - /* For oneshot services we wait until the start - * process exited, too, but it is our main process. */ - - /* For D-Bus services we know the main pid right away, - * but wait for the bus name to appear on the - * bus. Notify services are similar. */ - - service_set_main_pid(s, pid); - service_set_state(s, SERVICE_START); - } else - assert_not_reached("Unknown service type"); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'start' task: %m"); - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); -} - -static void service_enter_start_pre(Service *s) { - int r; - - assert(s); - - service_unwatch_control_pid(s); - - s->control_command = s->exec_command[SERVICE_EXEC_START_PRE]; - if (s->control_command) { - /* Before we start anything, let's clear up what might - * be left from previous runs. */ - service_kill_control_processes(s); - - s->control_command_id = SERVICE_EXEC_START_PRE; - - r = service_spawn(s, - s->control_command, - s->timeout_start_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - true, - true, - &s->control_pid); - if (r < 0) - goto fail; - - service_set_state(s, SERVICE_START_PRE); - } else - service_enter_start(s); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-pre' task: %m"); - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); -} - -static void service_enter_restart(Service *s) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(s); - - if (UNIT(s)->job && UNIT(s)->job->type == JOB_STOP) { - /* Don't restart things if we are going down anyway */ - log_unit_info(UNIT(s), "Stop job pending for unit, delaying automatic restart."); - - r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->restart_usec)); - if (r < 0) - goto fail; - - return; - } - - /* Any units that are bound to this service must also be - * restarted. We use JOB_RESTART (instead of the more obvious - * JOB_START) here so that those dependency jobs will be added - * as well. */ - r = manager_add_job(UNIT(s)->manager, JOB_RESTART, UNIT(s), JOB_FAIL, &error, NULL); - if (r < 0) - goto fail; - - /* Note that we stay in the SERVICE_AUTO_RESTART state here, - * it will be canceled as part of the service_stop() call that - * is executed as part of JOB_RESTART. */ - - log_unit_debug(UNIT(s), "Scheduled restart job."); - return; - -fail: - log_unit_warning(UNIT(s), "Failed to schedule restart job: %s", bus_error_message(&error, -r)); - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); -} - -static void service_enter_reload_by_notify(Service *s) { - assert(s); - - service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_start_usec)); - service_set_state(s, SERVICE_RELOAD); -} - -static void service_enter_reload(Service *s) { - int r; - - assert(s); - - service_unwatch_control_pid(s); - s->reload_result = SERVICE_SUCCESS; - - s->control_command = s->exec_command[SERVICE_EXEC_RELOAD]; - if (s->control_command) { - s->control_command_id = SERVICE_EXEC_RELOAD; - - r = service_spawn(s, - s->control_command, - s->timeout_start_usec, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - false, - true, - &s->control_pid); - if (r < 0) - goto fail; - - service_set_state(s, SERVICE_RELOAD); - } else - service_enter_running(s, SERVICE_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'reload' task: %m"); - s->reload_result = SERVICE_FAILURE_RESOURCES; - service_enter_running(s, SERVICE_SUCCESS); -} - -static void service_run_next_control(Service *s) { - usec_t timeout; - int r; - - assert(s); - assert(s->control_command); - assert(s->control_command->command_next); - - assert(s->control_command_id != SERVICE_EXEC_START); - - s->control_command = s->control_command->command_next; - service_unwatch_control_pid(s); - - if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) - timeout = s->timeout_start_usec; - else - timeout = s->timeout_stop_usec; - - r = service_spawn(s, - s->control_command, - timeout, - false, - !s->permissions_start_only, - !s->root_directory_start_only, - s->control_command_id == SERVICE_EXEC_START_PRE || - s->control_command_id == SERVICE_EXEC_STOP_POST, - true, - &s->control_pid); - if (r < 0) - goto fail; - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run next control task: %m"); - - if (s->state == SERVICE_START_PRE) - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); - else if (s->state == SERVICE_STOP) - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); - else if (s->state == SERVICE_STOP_POST) - service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); - else if (s->state == SERVICE_RELOAD) { - s->reload_result = SERVICE_FAILURE_RESOURCES; - service_enter_running(s, SERVICE_SUCCESS); - } else - service_enter_stop(s, SERVICE_FAILURE_RESOURCES); -} - -static void service_run_next_main(Service *s) { - pid_t pid; - int r; - - assert(s); - assert(s->main_command); - assert(s->main_command->command_next); - assert(s->type == SERVICE_ONESHOT); - - s->main_command = s->main_command->command_next; - service_unwatch_main_pid(s); - - r = service_spawn(s, - s->main_command, - s->timeout_start_usec, - true, - true, - true, - true, - false, - &pid); - if (r < 0) - goto fail; - - service_set_main_pid(s, pid); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run next main task: %m"); - service_enter_stop(s, SERVICE_FAILURE_RESOURCES); -} - -static int service_start(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - /* We cannot fulfill this request right now, try again later - * please! */ - if (IN_SET(s->state, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) - return -EAGAIN; - - /* Already on it! */ - if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST)) - return 0; - - /* A service that will be restarted must be stopped first to - * trigger BindsTo and/or OnFailure dependencies. If a user - * does not want to wait for the holdoff time to elapse, the - * service should be manually restarted, not started. We - * simply return EAGAIN here, so that any start jobs stay - * queued, and assume that the auto restart timer will - * eventually trigger the restart. */ - if (s->state == SERVICE_AUTO_RESTART) - return -EAGAIN; - - assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED)); - - s->result = SERVICE_SUCCESS; - s->reload_result = SERVICE_SUCCESS; - s->main_pid_known = false; - s->main_pid_alien = false; - s->forbid_restart = false; - s->reset_cpu_usage = true; - - s->status_text = mfree(s->status_text); - s->status_errno = 0; - - s->notify_state = NOTIFY_UNKNOWN; - - service_enter_start_pre(s); - return 1; -} - -static int service_stop(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - /* Don't create restart jobs from manual stops. */ - s->forbid_restart = true; - - /* Already on it */ - if (IN_SET(s->state, - SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) - return 0; - - /* A restart will be scheduled or is in progress. */ - if (s->state == SERVICE_AUTO_RESTART) { - service_set_state(s, SERVICE_DEAD); - return 0; - } - - /* If there's already something running we go directly into - * kill mode. */ - if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD)) { - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); - return 0; - } - - assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED)); - - service_enter_stop(s, SERVICE_SUCCESS); - return 1; -} - -static int service_reload(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - assert(s->state == SERVICE_RUNNING || s->state == SERVICE_EXITED); - - service_enter_reload(s); - return 1; -} - -_pure_ static bool service_can_reload(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - return !!s->exec_command[SERVICE_EXEC_RELOAD]; -} - -static int service_serialize(Unit *u, FILE *f, FDSet *fds) { - Service *s = SERVICE(u); - ServiceFDStore *fs; - int r; - - assert(u); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", service_state_to_string(s->state)); - unit_serialize_item(u, f, "result", service_result_to_string(s->result)); - unit_serialize_item(u, f, "reload-result", service_result_to_string(s->reload_result)); - - if (s->control_pid > 0) - unit_serialize_item_format(u, f, "control-pid", PID_FMT, s->control_pid); - - if (s->main_pid_known && s->main_pid > 0) - unit_serialize_item_format(u, f, "main-pid", PID_FMT, s->main_pid); - - unit_serialize_item(u, f, "main-pid-known", yes_no(s->main_pid_known)); - unit_serialize_item(u, f, "bus-name-good", yes_no(s->bus_name_good)); - unit_serialize_item(u, f, "bus-name-owner", s->bus_name_owner); - - r = unit_serialize_item_escaped(u, f, "status-text", s->status_text); - if (r < 0) - return r; - - /* FIXME: There's a minor uncleanliness here: if there are - * multiple commands attached here, we will start from the - * first one again */ - if (s->control_command_id >= 0) - unit_serialize_item(u, f, "control-command", service_exec_command_to_string(s->control_command_id)); - - r = unit_serialize_item_fd(u, f, fds, "stdin-fd", s->stdin_fd); - if (r < 0) - return r; - r = unit_serialize_item_fd(u, f, fds, "stdout-fd", s->stdout_fd); - if (r < 0) - return r; - r = unit_serialize_item_fd(u, f, fds, "stderr-fd", s->stderr_fd); - if (r < 0) - return r; - - r = unit_serialize_item_fd(u, f, fds, "socket-fd", s->socket_fd); - if (r < 0) - return r; - r = unit_serialize_item_fd(u, f, fds, "endpoint-fd", s->bus_endpoint_fd); - if (r < 0) - return r; - - LIST_FOREACH(fd_store, fs, s->fd_store) { - _cleanup_free_ char *c = NULL; - int copy; - - copy = fdset_put_dup(fds, fs->fd); - if (copy < 0) - return copy; - - c = cescape(fs->fdname); - - unit_serialize_item_format(u, f, "fd-store-fd", "%i %s", copy, strempty(c)); - } - - if (s->main_exec_status.pid > 0) { - unit_serialize_item_format(u, f, "main-exec-status-pid", PID_FMT, s->main_exec_status.pid); - dual_timestamp_serialize(f, "main-exec-status-start", &s->main_exec_status.start_timestamp); - dual_timestamp_serialize(f, "main-exec-status-exit", &s->main_exec_status.exit_timestamp); - - if (dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) { - unit_serialize_item_format(u, f, "main-exec-status-code", "%i", s->main_exec_status.code); - unit_serialize_item_format(u, f, "main-exec-status-status", "%i", s->main_exec_status.status); - } - } - - if (dual_timestamp_is_set(&s->watchdog_timestamp)) - dual_timestamp_serialize(f, "watchdog-timestamp", &s->watchdog_timestamp); - - unit_serialize_item(u, f, "forbid-restart", yes_no(s->forbid_restart)); - - return 0; -} - -static int service_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Service *s = SERVICE(u); - int r; - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - ServiceState state; - - state = service_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - s->deserialized_state = state; - } else if (streq(key, "result")) { - ServiceResult f; - - f = service_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != SERVICE_SUCCESS) - s->result = f; - - } else if (streq(key, "reload-result")) { - ServiceResult f; - - f = service_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse reload result value: %s", value); - else if (f != SERVICE_SUCCESS) - s->reload_result = f; - - } else if (streq(key, "control-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse control-pid value: %s", value); - else - s->control_pid = pid; - } else if (streq(key, "main-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse main-pid value: %s", value); - else { - service_set_main_pid(s, pid); - unit_watch_pid(UNIT(s), pid); - } - } else if (streq(key, "main-pid-known")) { - int b; - - b = parse_boolean(value); - if (b < 0) - log_unit_debug(u, "Failed to parse main-pid-known value: %s", value); - else - s->main_pid_known = b; - } else if (streq(key, "bus-name-good")) { - int b; - - b = parse_boolean(value); - if (b < 0) - log_unit_debug(u, "Failed to parse bus-name-good value: %s", value); - else - s->bus_name_good = b; - } else if (streq(key, "bus-name-owner")) { - r = free_and_strdup(&s->bus_name_owner, value); - if (r < 0) - log_unit_error_errno(u, r, "Unable to deserialize current bus owner %s: %m", value); - } else if (streq(key, "status-text")) { - char *t; - - r = cunescape(value, 0, &t); - if (r < 0) - log_unit_debug_errno(u, r, "Failed to unescape status text: %s", value); - else { - free(s->status_text); - s->status_text = t; - } - - } else if (streq(key, "control-command")) { - ServiceExecCommand id; - - id = service_exec_command_from_string(value); - if (id < 0) - log_unit_debug(u, "Failed to parse exec-command value: %s", value); - else { - s->control_command_id = id; - s->control_command = s->exec_command[id]; - } - } else if (streq(key, "socket-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse socket-fd value: %s", value); - else { - asynchronous_close(s->socket_fd); - s->socket_fd = fdset_remove(fds, fd); - } - } else if (streq(key, "endpoint-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse endpoint-fd value: %s", value); - else { - safe_close(s->bus_endpoint_fd); - s->bus_endpoint_fd = fdset_remove(fds, fd); - } - } else if (streq(key, "fd-store-fd")) { - const char *fdv; - size_t pf; - int fd; - - pf = strcspn(value, WHITESPACE); - fdv = strndupa(value, pf); - - if (safe_atoi(fdv, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse fd-store-fd value: %s", value); - else { - _cleanup_free_ char *t = NULL; - const char *fdn; - - fdn = value + pf; - fdn += strspn(fdn, WHITESPACE); - (void) cunescape(fdn, 0, &t); - - r = service_add_fd_store(s, fd, t); - if (r < 0) - log_unit_error_errno(u, r, "Failed to add fd to store: %m"); - else if (r > 0) - fdset_remove(fds, fd); - } - - } else if (streq(key, "main-exec-status-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse main-exec-status-pid value: %s", value); - else - s->main_exec_status.pid = pid; - } else if (streq(key, "main-exec-status-code")) { - int i; - - if (safe_atoi(value, &i) < 0) - log_unit_debug(u, "Failed to parse main-exec-status-code value: %s", value); - else - s->main_exec_status.code = i; - } else if (streq(key, "main-exec-status-status")) { - int i; - - if (safe_atoi(value, &i) < 0) - log_unit_debug(u, "Failed to parse main-exec-status-status value: %s", value); - else - s->main_exec_status.status = i; - } else if (streq(key, "main-exec-status-start")) - dual_timestamp_deserialize(value, &s->main_exec_status.start_timestamp); - else if (streq(key, "main-exec-status-exit")) - dual_timestamp_deserialize(value, &s->main_exec_status.exit_timestamp); - else if (streq(key, "watchdog-timestamp")) - dual_timestamp_deserialize(value, &s->watchdog_timestamp); - else if (streq(key, "forbid-restart")) { - int b; - - b = parse_boolean(value); - if (b < 0) - log_unit_debug(u, "Failed to parse forbid-restart value: %s", value); - else - s->forbid_restart = b; - } else if (streq(key, "stdin-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse stdin-fd value: %s", value); - else { - asynchronous_close(s->stdin_fd); - s->stdin_fd = fdset_remove(fds, fd); - s->exec_context.stdio_as_fds = true; - } - } else if (streq(key, "stdout-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse stdout-fd value: %s", value); - else { - asynchronous_close(s->stdout_fd); - s->stdout_fd = fdset_remove(fds, fd); - s->exec_context.stdio_as_fds = true; - } - } else if (streq(key, "stderr-fd")) { - int fd; - - if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse stderr-fd value: %s", value); - else { - asynchronous_close(s->stderr_fd); - s->stderr_fd = fdset_remove(fds, fd); - s->exec_context.stdio_as_fds = true; - } - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -_pure_ static UnitActiveState service_active_state(Unit *u) { - const UnitActiveState *table; - - assert(u); - - table = SERVICE(u)->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table; - - return table[SERVICE(u)->state]; -} - -static const char *service_sub_state_to_string(Unit *u) { - assert(u); - - return service_state_to_string(SERVICE(u)->state); -} - -static bool service_check_gc(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - /* Never clean up services that still have a process around, - * even if the service is formally dead. */ - if (cgroup_good(s) > 0 || - main_pid_good(s) > 0 || - control_pid_good(s) > 0) - return true; - - return false; -} - -static int service_retry_pid_file(Service *s) { - int r; - - assert(s->pid_file); - assert(s->state == SERVICE_START || s->state == SERVICE_START_POST); - - r = service_load_pid_file(s, false); - if (r < 0) - return r; - - service_unwatch_pid_file(s); - - service_enter_running(s, SERVICE_SUCCESS); - return 0; -} - -static int service_watch_pid_file(Service *s) { - int r; - - log_unit_debug(UNIT(s), "Setting watch for PID file %s", s->pid_file_pathspec->path); - - r = path_spec_watch(s->pid_file_pathspec, service_dispatch_io); - if (r < 0) - goto fail; - - /* the pidfile might have appeared just before we set the watch */ - log_unit_debug(UNIT(s), "Trying to read PID file %s in case it changed", s->pid_file_pathspec->path); - service_retry_pid_file(s); - - return 0; -fail: - log_unit_error_errno(UNIT(s), r, "Failed to set a watch for PID file %s: %m", s->pid_file_pathspec->path); - service_unwatch_pid_file(s); - return r; -} - -static int service_demand_pid_file(Service *s) { - PathSpec *ps; - - assert(s->pid_file); - assert(!s->pid_file_pathspec); - - ps = new0(PathSpec, 1); - if (!ps) - return -ENOMEM; - - ps->unit = UNIT(s); - ps->path = strdup(s->pid_file); - if (!ps->path) { - free(ps); - return -ENOMEM; - } - - path_kill_slashes(ps->path); - - /* PATH_CHANGED would not be enough. There are daemons (sendmail) that - * keep their PID file open all the time. */ - ps->type = PATH_MODIFIED; - ps->inotify_fd = -1; - - s->pid_file_pathspec = ps; - - return service_watch_pid_file(s); -} - -static int service_dispatch_io(sd_event_source *source, int fd, uint32_t events, void *userdata) { - PathSpec *p = userdata; - Service *s; - - assert(p); - - s = SERVICE(p->unit); - - assert(s); - assert(fd >= 0); - assert(s->state == SERVICE_START || s->state == SERVICE_START_POST); - assert(s->pid_file_pathspec); - assert(path_spec_owns_inotify_fd(s->pid_file_pathspec, fd)); - - log_unit_debug(UNIT(s), "inotify event"); - - if (path_spec_fd_event(p, events) < 0) - goto fail; - - if (service_retry_pid_file(s) == 0) - return 0; - - if (service_watch_pid_file(s) < 0) - goto fail; - - return 0; - -fail: - service_unwatch_pid_file(s); - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); - return 0; -} - -static void service_notify_cgroup_empty_event(Unit *u) { - Service *s = SERVICE(u); - - assert(u); - - log_unit_debug(u, "cgroup is empty"); - - switch (s->state) { - - /* Waiting for SIGCHLD is usually more interesting, - * because it includes return codes/signals. Which is - * why we ignore the cgroup events for most cases, - * except when we don't know pid which to expect the - * SIGCHLD for. */ - - case SERVICE_START: - case SERVICE_START_POST: - /* If we were hoping for the daemon to write its PID file, - * we can give up now. */ - if (s->pid_file_pathspec) { - log_unit_warning(u, "Daemon never wrote its PID file. Failing."); - - service_unwatch_pid_file(s); - if (s->state == SERVICE_START) - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); - else - service_enter_stop(s, SERVICE_FAILURE_RESOURCES); - } - break; - - case SERVICE_RUNNING: - /* service_enter_running() will figure out what to do */ - service_enter_running(s, SERVICE_SUCCESS); - break; - - case SERVICE_STOP_SIGABRT: - case SERVICE_STOP_SIGTERM: - case SERVICE_STOP_SIGKILL: - - if (main_pid_good(s) <= 0 && !control_pid_good(s)) - service_enter_stop_post(s, SERVICE_SUCCESS); - - break; - - case SERVICE_STOP_POST: - case SERVICE_FINAL_SIGTERM: - case SERVICE_FINAL_SIGKILL: - if (main_pid_good(s) <= 0 && !control_pid_good(s)) - service_enter_dead(s, SERVICE_SUCCESS, true); - - break; - - default: - ; - } -} - -static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { - Service *s = SERVICE(u); - ServiceResult f; - - assert(s); - assert(pid >= 0); - - if (UNIT(s)->fragment_path ? is_clean_exit(code, status, &s->success_status) : - is_clean_exit_lsb(code, status, &s->success_status)) - f = SERVICE_SUCCESS; - else if (code == CLD_EXITED) - f = SERVICE_FAILURE_EXIT_CODE; - else if (code == CLD_KILLED) - f = SERVICE_FAILURE_SIGNAL; - else if (code == CLD_DUMPED) - f = SERVICE_FAILURE_CORE_DUMP; - else - assert_not_reached("Unknown code"); - - if (s->main_pid == pid) { - /* Forking services may occasionally move to a new PID. - * As long as they update the PID file before exiting the old - * PID, they're fine. */ - if (service_load_pid_file(s, false) == 0) - return; - - s->main_pid = 0; - exec_status_exit(&s->main_exec_status, &s->exec_context, pid, code, status); - - if (s->main_command) { - /* If this is not a forking service than the - * main process got started and hence we copy - * the exit status so that it is recorded both - * as main and as control process exit - * status */ - - s->main_command->exec_status = s->main_exec_status; - - if (s->main_command->ignore) - f = SERVICE_SUCCESS; - } else if (s->exec_command[SERVICE_EXEC_START]) { - - /* If this is a forked process, then we should - * ignore the return value if this was - * configured for the starter process */ - - if (s->exec_command[SERVICE_EXEC_START]->ignore) - f = SERVICE_SUCCESS; - } - - log_struct(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, - LOG_UNIT_ID(u), - LOG_UNIT_MESSAGE(u, "Main process exited, code=%s, status=%i/%s", - sigchld_code_to_string(code), status, - strna(code == CLD_EXITED - ? exit_status_to_string(status, EXIT_STATUS_FULL) - : signal_to_string(status))), - "EXIT_CODE=%s", sigchld_code_to_string(code), - "EXIT_STATUS=%i", status, - NULL); - - if (f != SERVICE_SUCCESS) - s->result = f; - - if (s->main_command && - s->main_command->command_next && - f == SERVICE_SUCCESS) { - - /* There is another command to * - * execute, so let's do that. */ - - log_unit_debug(u, "Running next main command for state %s.", service_state_to_string(s->state)); - service_run_next_main(s); - - } else { - - /* The service exited, so the service is officially - * gone. */ - s->main_command = NULL; - - switch (s->state) { - - case SERVICE_START_POST: - case SERVICE_RELOAD: - case SERVICE_STOP: - /* Need to wait until the operation is - * done */ - break; - - case SERVICE_START: - if (s->type == SERVICE_ONESHOT) { - /* This was our main goal, so let's go on */ - if (f == SERVICE_SUCCESS) - service_enter_start_post(s); - else - service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); - break; - } - - /* Fall through */ - - case SERVICE_RUNNING: - service_enter_running(s, f); - break; - - case SERVICE_STOP_SIGABRT: - case SERVICE_STOP_SIGTERM: - case SERVICE_STOP_SIGKILL: - - if (!control_pid_good(s)) - service_enter_stop_post(s, f); - - /* If there is still a control process, wait for that first */ - break; - - case SERVICE_STOP_POST: - case SERVICE_FINAL_SIGTERM: - case SERVICE_FINAL_SIGKILL: - - if (!control_pid_good(s)) - service_enter_dead(s, f, true); - break; - - default: - assert_not_reached("Uh, main process died at wrong time."); - } - } - - } else if (s->control_pid == pid) { - s->control_pid = 0; - - if (s->control_command) { - exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); - - if (s->control_command->ignore) - f = SERVICE_SUCCESS; - } - - log_unit_full(u, f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, - "Control process exited, code=%s status=%i", - sigchld_code_to_string(code), status); - - if (f != SERVICE_SUCCESS) - s->result = f; - - /* Immediately get rid of the cgroup, so that the - * kernel doesn't delay the cgroup empty messages for - * the service cgroup any longer than necessary */ - service_kill_control_processes(s); - - if (s->control_command && - s->control_command->command_next && - f == SERVICE_SUCCESS) { - - /* There is another command to * - * execute, so let's do that. */ - - log_unit_debug(u, "Running next control command for state %s.", service_state_to_string(s->state)); - service_run_next_control(s); - - } else { - /* No further commands for this step, so let's - * figure out what to do next */ - - s->control_command = NULL; - s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; - - log_unit_debug(u, "Got final SIGCHLD for state %s.", service_state_to_string(s->state)); - - switch (s->state) { - - case SERVICE_START_PRE: - if (f == SERVICE_SUCCESS) - service_enter_start(s); - else - service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); - break; - - case SERVICE_START: - if (s->type != SERVICE_FORKING) - /* Maybe spurious event due to a reload that changed the type? */ - break; - - if (f != SERVICE_SUCCESS) { - service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); - break; - } - - if (s->pid_file) { - bool has_start_post; - int r; - - /* Let's try to load the pid file here if we can. - * The PID file might actually be created by a START_POST - * script. In that case don't worry if the loading fails. */ - - has_start_post = !!s->exec_command[SERVICE_EXEC_START_POST]; - r = service_load_pid_file(s, !has_start_post); - if (!has_start_post && r < 0) { - r = service_demand_pid_file(s); - if (r < 0 || !cgroup_good(s)) - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); - break; - } - } else - (void) service_search_main_pid(s); - - service_enter_start_post(s); - break; - - case SERVICE_START_POST: - if (f != SERVICE_SUCCESS) { - service_enter_signal(s, SERVICE_STOP_SIGTERM, f); - break; - } - - if (s->pid_file) { - int r; - - r = service_load_pid_file(s, true); - if (r < 0) { - r = service_demand_pid_file(s); - if (r < 0 || !cgroup_good(s)) - service_enter_stop(s, SERVICE_FAILURE_RESOURCES); - break; - } - } else - (void) service_search_main_pid(s); - - service_enter_running(s, SERVICE_SUCCESS); - break; - - case SERVICE_RELOAD: - if (f == SERVICE_SUCCESS) { - service_load_pid_file(s, true); - (void) service_search_main_pid(s); - } - - s->reload_result = f; - service_enter_running(s, SERVICE_SUCCESS); - break; - - case SERVICE_STOP: - service_enter_signal(s, SERVICE_STOP_SIGTERM, f); - break; - - case SERVICE_STOP_SIGABRT: - case SERVICE_STOP_SIGTERM: - case SERVICE_STOP_SIGKILL: - if (main_pid_good(s) <= 0) - service_enter_stop_post(s, f); - - /* If there is still a service - * process around, wait until - * that one quit, too */ - break; - - case SERVICE_STOP_POST: - case SERVICE_FINAL_SIGTERM: - case SERVICE_FINAL_SIGKILL: - if (main_pid_good(s) <= 0) - service_enter_dead(s, f, true); - break; - - default: - assert_not_reached("Uh, control process died at wrong time."); - } - } - } - - /* Notify clients about changed exit status */ - unit_add_to_dbus_queue(u); - - /* We got one SIGCHLD for the service, let's watch all - * processes that are now running of the service, and watch - * that. Among the PIDs we then watch will be children - * reassigned to us, which hopefully allows us to identify - * when all children are gone */ - 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)) - service_notify_cgroup_empty_event(u); -} - -static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Service *s = SERVICE(userdata); - - assert(s); - assert(source == s->timer_event_source); - - switch (s->state) { - - case SERVICE_START_PRE: - case SERVICE_START: - log_unit_warning(UNIT(s), "%s operation timed out. Terminating.", s->state == SERVICE_START ? "Start" : "Start-pre"); - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_START_POST: - log_unit_warning(UNIT(s), "Start-post operation timed out. Stopping."); - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_RUNNING: - log_unit_warning(UNIT(s), "Service reached runtime time limit. Stopping."); - service_enter_stop(s, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_RELOAD: - log_unit_warning(UNIT(s), "Reload operation timed out. Killing reload process."); - service_kill_control_processes(s); - s->reload_result = SERVICE_FAILURE_TIMEOUT; - service_enter_running(s, SERVICE_SUCCESS); - break; - - case SERVICE_STOP: - log_unit_warning(UNIT(s), "Stopping timed out. Terminating."); - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_STOP_SIGABRT: - log_unit_warning(UNIT(s), "State 'stop-sigabrt' timed out. Terminating."); - service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_STOP_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "State 'stop-sigterm' timed out. Killing."); - service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "State 'stop-sigterm' timed out. Skipping SIGKILL."); - service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT); - } - - break; - - case SERVICE_STOP_SIGKILL: - /* Uh, we sent a SIGKILL and it is still not gone? - * Must be something we cannot kill, so let's just be - * weirded out and continue */ - - log_unit_warning(UNIT(s), "Processes still around after SIGKILL. Ignoring."); - service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_STOP_POST: - log_unit_warning(UNIT(s), "State 'stop-post' timed out. Terminating."); - service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); - break; - - case SERVICE_FINAL_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "State 'stop-final-sigterm' timed out. Killing."); - service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "State 'stop-final-sigterm' timed out. Skipping SIGKILL. Entering failed mode."); - service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, false); - } - - break; - - case SERVICE_FINAL_SIGKILL: - log_unit_warning(UNIT(s), "Processes still around after final SIGKILL. Entering failed mode."); - service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, true); - break; - - case SERVICE_AUTO_RESTART: - log_unit_info(UNIT(s), - s->restart_usec > 0 ? - "Service hold-off time over, scheduling restart." : - "Service has no hold-off time, scheduling restart."); - service_enter_restart(s); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } - - return 0; -} - -static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata) { - Service *s = SERVICE(userdata); - char t[FORMAT_TIMESPAN_MAX]; - - assert(s); - assert(source == s->watchdog_event_source); - - log_unit_error(UNIT(s), "Watchdog timeout (limit %s)!", - format_timespan(t, sizeof(t), s->watchdog_usec, 1)); - - service_enter_signal(s, SERVICE_STOP_SIGABRT, SERVICE_FAILURE_WATCHDOG); - - return 0; -} - -static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds) { - Service *s = SERVICE(u); - _cleanup_free_ char *cc = NULL; - bool notify_dbus = false; - const char *e; - - assert(u); - - cc = strv_join(tags, ", "); - - if (s->notify_access == NOTIFY_NONE) { - log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception is disabled.", pid); - return; - } else if (s->notify_access == NOTIFY_MAIN && pid != s->main_pid) { - if (s->main_pid != 0) - log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid); - else - log_unit_debug(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid); - return; - } else - log_unit_debug(u, "Got notification message from PID "PID_FMT" (%s)", pid, isempty(cc) ? "n/a" : cc); - - /* Interpret MAINPID= */ - e = strv_find_startswith(tags, "MAINPID="); - if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) { - if (parse_pid(e, &pid) < 0) - log_unit_warning(u, "Failed to parse MAINPID= field in notification message: %s", e); - else { - service_set_main_pid(s, pid); - unit_watch_pid(UNIT(s), pid); - notify_dbus = true; - } - } - - /* Interpret RELOADING= */ - if (strv_find(tags, "RELOADING=1")) { - - s->notify_state = NOTIFY_RELOADING; - - if (s->state == SERVICE_RUNNING) - service_enter_reload_by_notify(s); - - notify_dbus = true; - } - - /* Interpret READY= */ - if (strv_find(tags, "READY=1")) { - - s->notify_state = NOTIFY_READY; - - /* Type=notify services inform us about completed - * initialization with READY=1 */ - if (s->type == SERVICE_NOTIFY && s->state == SERVICE_START) - service_enter_start_post(s); - - /* Sending READY=1 while we are reloading informs us - * that the reloading is complete */ - if (s->state == SERVICE_RELOAD && s->control_pid == 0) - service_enter_running(s, SERVICE_SUCCESS); - - notify_dbus = true; - } - - /* Interpret STOPPING= */ - if (strv_find(tags, "STOPPING=1")) { - - s->notify_state = NOTIFY_STOPPING; - - if (s->state == SERVICE_RUNNING) - service_enter_stop_by_notify(s); - - notify_dbus = true; - } - - /* Interpret STATUS= */ - e = strv_find_startswith(tags, "STATUS="); - if (e) { - _cleanup_free_ char *t = NULL; - - if (!isempty(e)) { - if (!utf8_is_valid(e)) - log_unit_warning(u, "Status message in notification message is not UTF-8 clean."); - else { - t = strdup(e); - if (!t) - log_oom(); - } - } - - if (!streq_ptr(s->status_text, t)) { - - free(s->status_text); - s->status_text = t; - t = NULL; - - notify_dbus = true; - } - } - - /* Interpret ERRNO= */ - e = strv_find_startswith(tags, "ERRNO="); - if (e) { - int status_errno; - - if (safe_atoi(e, &status_errno) < 0 || status_errno < 0) - log_unit_warning(u, "Failed to parse ERRNO= field in notification message: %s", e); - else { - if (s->status_errno != status_errno) { - s->status_errno = status_errno; - notify_dbus = true; - } - } - } - - /* Interpret WATCHDOG= */ - if (strv_find(tags, "WATCHDOG=1")) - service_reset_watchdog(s); - - if (strv_find(tags, "FDSTORE=1")) { - const char *name; - - name = strv_find_startswith(tags, "FDNAME="); - if (name && !fdname_is_valid(name)) { - log_unit_warning(u, "Passed FDNAME= name is invalid, ignoring."); - name = NULL; - } - - service_add_fd_store_set(s, fds, name); - } - - /* Notify clients about changed status or main pid */ - if (notify_dbus) - unit_add_to_dbus_queue(u); -} - -static int service_get_timeout(Unit *u, usec_t *timeout) { - Service *s = SERVICE(u); - uint64_t t; - int r; - - if (!s->timer_event_source) - return 0; - - r = sd_event_source_get_time(s->timer_event_source, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) - return 0; - - *timeout = t; - return 1; -} - -static void service_bus_name_owner_change( - Unit *u, - const char *name, - const char *old_owner, - const char *new_owner) { - - Service *s = SERVICE(u); - int r; - - assert(s); - assert(name); - - assert(streq(s->bus_name, name)); - assert(old_owner || new_owner); - - if (old_owner && new_owner) - log_unit_debug(u, "D-Bus name %s changed owner from %s to %s", name, old_owner, new_owner); - else if (old_owner) - log_unit_debug(u, "D-Bus name %s no longer registered by %s", name, old_owner); - else - log_unit_debug(u, "D-Bus name %s now registered by %s", name, new_owner); - - s->bus_name_good = !!new_owner; - - /* Track the current owner, so we can reconstruct changes after a daemon reload */ - r = free_and_strdup(&s->bus_name_owner, new_owner); - if (r < 0) { - log_unit_error_errno(u, r, "Unable to set new bus name owner %s: %m", new_owner); - return; - } - - if (s->type == SERVICE_DBUS) { - - /* service_enter_running() will figure out what to - * do */ - if (s->state == SERVICE_RUNNING) - service_enter_running(s, SERVICE_SUCCESS); - else if (s->state == SERVICE_START && new_owner) - service_enter_start_post(s); - - } else if (new_owner && - s->main_pid <= 0 && - (s->state == SERVICE_START || - s->state == SERVICE_START_POST || - s->state == SERVICE_RUNNING || - s->state == SERVICE_RELOAD)) { - - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t pid; - - /* Try to acquire PID from bus service */ - - r = sd_bus_get_name_creds(u->manager->api_bus, name, SD_BUS_CREDS_PID, &creds); - if (r >= 0) - r = sd_bus_creds_get_pid(creds, &pid); - if (r >= 0) { - log_unit_debug(u, "D-Bus name %s is now owned by process %u", name, (unsigned) pid); - - service_set_main_pid(s, pid); - unit_watch_pid(UNIT(s), pid); - } - } -} - -int service_set_socket_fd(Service *s, int fd, Socket *sock, bool selinux_context_net) { - _cleanup_free_ char *peer = NULL; - int r; - - assert(s); - assert(fd >= 0); - - /* This is called by the socket code when instantiating a new - * service for a stream socket and the socket needs to be - * configured. */ - - if (UNIT(s)->load_state != UNIT_LOADED) - return -EINVAL; - - if (s->socket_fd >= 0) - return -EBUSY; - - if (s->state != SERVICE_DEAD) - return -EAGAIN; - - if (getpeername_pretty(fd, true, &peer) >= 0) { - - if (UNIT(s)->description) { - _cleanup_free_ char *a; - - a = strjoin(UNIT(s)->description, " (", peer, ")", NULL); - if (!a) - return -ENOMEM; - - r = unit_set_description(UNIT(s), a); - } else - r = unit_set_description(UNIT(s), peer); - - if (r < 0) - return r; - } - - s->socket_fd = fd; - s->socket_fd_selinux_context_net = selinux_context_net; - - unit_ref_set(&s->accept_socket, UNIT(sock)); - - return unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false); -} - -static void service_reset_failed(Unit *u) { - Service *s = SERVICE(u); - - assert(s); - - if (s->state == SERVICE_FAILED) - service_set_state(s, SERVICE_DEAD); - - s->result = SERVICE_SUCCESS; - s->reload_result = SERVICE_SUCCESS; -} - -static int service_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - Service *s = SERVICE(u); - - return unit_kill_common(u, who, signo, s->main_pid, s->control_pid, error); -} - -static const char* const service_restart_table[_SERVICE_RESTART_MAX] = { - [SERVICE_RESTART_NO] = "no", - [SERVICE_RESTART_ON_SUCCESS] = "on-success", - [SERVICE_RESTART_ON_FAILURE] = "on-failure", - [SERVICE_RESTART_ON_ABNORMAL] = "on-abnormal", - [SERVICE_RESTART_ON_WATCHDOG] = "on-watchdog", - [SERVICE_RESTART_ON_ABORT] = "on-abort", - [SERVICE_RESTART_ALWAYS] = "always", -}; - -DEFINE_STRING_TABLE_LOOKUP(service_restart, ServiceRestart); - -static const char* const service_type_table[_SERVICE_TYPE_MAX] = { - [SERVICE_SIMPLE] = "simple", - [SERVICE_FORKING] = "forking", - [SERVICE_ONESHOT] = "oneshot", - [SERVICE_DBUS] = "dbus", - [SERVICE_NOTIFY] = "notify", - [SERVICE_IDLE] = "idle" -}; - -DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType); - -static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] = { - [SERVICE_EXEC_START_PRE] = "ExecStartPre", - [SERVICE_EXEC_START] = "ExecStart", - [SERVICE_EXEC_START_POST] = "ExecStartPost", - [SERVICE_EXEC_RELOAD] = "ExecReload", - [SERVICE_EXEC_STOP] = "ExecStop", - [SERVICE_EXEC_STOP_POST] = "ExecStopPost", -}; - -DEFINE_STRING_TABLE_LOOKUP(service_exec_command, ServiceExecCommand); - -static const char* const notify_access_table[_NOTIFY_ACCESS_MAX] = { - [NOTIFY_NONE] = "none", - [NOTIFY_MAIN] = "main", - [NOTIFY_ALL] = "all" -}; - -DEFINE_STRING_TABLE_LOOKUP(notify_access, NotifyAccess); - -static const char* const notify_state_table[_NOTIFY_STATE_MAX] = { - [NOTIFY_UNKNOWN] = "unknown", - [NOTIFY_READY] = "ready", - [NOTIFY_RELOADING] = "reloading", - [NOTIFY_STOPPING] = "stopping", -}; - -DEFINE_STRING_TABLE_LOOKUP(notify_state, NotifyState); - -static const char* const service_result_table[_SERVICE_RESULT_MAX] = { - [SERVICE_SUCCESS] = "success", - [SERVICE_FAILURE_RESOURCES] = "resources", - [SERVICE_FAILURE_TIMEOUT] = "timeout", - [SERVICE_FAILURE_EXIT_CODE] = "exit-code", - [SERVICE_FAILURE_SIGNAL] = "signal", - [SERVICE_FAILURE_CORE_DUMP] = "core-dump", - [SERVICE_FAILURE_WATCHDOG] = "watchdog", -}; - -DEFINE_STRING_TABLE_LOOKUP(service_result, ServiceResult); - -const UnitVTable service_vtable = { - .object_size = sizeof(Service), - .exec_context_offset = offsetof(Service, exec_context), - .cgroup_context_offset = offsetof(Service, cgroup_context), - .kill_context_offset = offsetof(Service, kill_context), - .exec_runtime_offset = offsetof(Service, exec_runtime), - - .sections = - "Unit\0" - "Service\0" - "Install\0", - .private_section = "Service", - - .init = service_init, - .done = service_done, - .load = service_load, - .release_resources = service_release_resources, - - .coldplug = service_coldplug, - - .dump = service_dump, - - .start = service_start, - .stop = service_stop, - .reload = service_reload, - - .can_reload = service_can_reload, - - .kill = service_kill, - - .serialize = service_serialize, - .deserialize_item = service_deserialize_item, - - .active_state = service_active_state, - .sub_state_to_string = service_sub_state_to_string, - - .check_gc = service_check_gc, - - .sigchld_event = service_sigchld_event, - - .reset_failed = service_reset_failed, - - .notify_cgroup_empty = service_notify_cgroup_empty_event, - .notify_message = service_notify_message, - - .bus_name_owner_change = service_bus_name_owner_change, - - .bus_vtable = bus_service_vtable, - .bus_set_property = bus_service_set_property, - .bus_commit_properties = bus_service_commit_properties, - - .get_timeout = service_get_timeout, - .can_transient = true, - - .status_message_formats = { - .starting_stopping = { - [0] = "Starting %s...", - [1] = "Stopping %s...", - }, - .finished_start_job = { - [JOB_DONE] = "Started %s.", - [JOB_FAILED] = "Failed to start %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Stopped %s.", - [JOB_FAILED] = "Stopped (with error) %s.", - }, - }, -}; diff --git a/src/core/service.h b/src/core/service.h deleted file mode 100644 index d342e000bb..0000000000 --- a/src/core/service.h +++ /dev/null @@ -1,220 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Service Service; -typedef struct ServiceFDStore ServiceFDStore; - -#include "exit-status.h" -#include "kill.h" -#include "path.h" -#include "ratelimit.h" - -typedef enum ServiceRestart { - SERVICE_RESTART_NO, - SERVICE_RESTART_ON_SUCCESS, - SERVICE_RESTART_ON_FAILURE, - SERVICE_RESTART_ON_ABNORMAL, - SERVICE_RESTART_ON_WATCHDOG, - SERVICE_RESTART_ON_ABORT, - SERVICE_RESTART_ALWAYS, - _SERVICE_RESTART_MAX, - _SERVICE_RESTART_INVALID = -1 -} ServiceRestart; - -typedef enum ServiceType { - SERVICE_SIMPLE, /* we fork and go on right-away (i.e. modern socket activated daemons) */ - SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */ - SERVICE_ONESHOT, /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */ - SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */ - SERVICE_NOTIFY, /* we fork and wait until a daemon sends us a ready message with sd_notify() */ - SERVICE_IDLE, /* much like simple, but delay exec() until all jobs are dispatched. */ - _SERVICE_TYPE_MAX, - _SERVICE_TYPE_INVALID = -1 -} ServiceType; - -typedef enum ServiceExecCommand { - SERVICE_EXEC_START_PRE, - SERVICE_EXEC_START, - SERVICE_EXEC_START_POST, - SERVICE_EXEC_RELOAD, - SERVICE_EXEC_STOP, - SERVICE_EXEC_STOP_POST, - _SERVICE_EXEC_COMMAND_MAX, - _SERVICE_EXEC_COMMAND_INVALID = -1 -} ServiceExecCommand; - -typedef enum NotifyAccess { - NOTIFY_NONE, - NOTIFY_ALL, - NOTIFY_MAIN, - _NOTIFY_ACCESS_MAX, - _NOTIFY_ACCESS_INVALID = -1 -} NotifyAccess; - -typedef enum NotifyState { - NOTIFY_UNKNOWN, - NOTIFY_READY, - NOTIFY_RELOADING, - NOTIFY_STOPPING, - _NOTIFY_STATE_MAX, - _NOTIFY_STATE_INVALID = -1 -} NotifyState; - -typedef enum ServiceResult { - SERVICE_SUCCESS, - SERVICE_FAILURE_RESOURCES, - SERVICE_FAILURE_TIMEOUT, - SERVICE_FAILURE_EXIT_CODE, - SERVICE_FAILURE_SIGNAL, - SERVICE_FAILURE_CORE_DUMP, - SERVICE_FAILURE_WATCHDOG, - _SERVICE_RESULT_MAX, - _SERVICE_RESULT_INVALID = -1 -} ServiceResult; - -struct ServiceFDStore { - Service *service; - - int fd; - char *fdname; - sd_event_source *event_source; - - LIST_FIELDS(ServiceFDStore, fd_store); -}; - -struct Service { - Unit meta; - - ServiceType type; - ServiceRestart restart; - ExitStatusSet restart_prevent_status; - ExitStatusSet restart_force_status; - ExitStatusSet success_status; - - /* If set we'll read the main daemon PID from this file */ - char *pid_file; - - usec_t restart_usec; - usec_t timeout_start_usec; - usec_t timeout_stop_usec; - usec_t runtime_max_usec; - - dual_timestamp watchdog_timestamp; - usec_t watchdog_usec; - sd_event_source *watchdog_event_source; - - ExecCommand* exec_command[_SERVICE_EXEC_COMMAND_MAX]; - - ExecContext exec_context; - KillContext kill_context; - CGroupContext cgroup_context; - - ServiceState state, deserialized_state; - - /* The exit status of the real main process */ - ExecStatus main_exec_status; - - /* The currently executed control process */ - ExecCommand *control_command; - - /* The currently executed main process, which may be NULL if - * the main process got started via forking mode and not by - * us */ - ExecCommand *main_command; - - /* The ID of the control command currently being executed */ - ServiceExecCommand control_command_id; - - /* Runtime data of the execution context */ - ExecRuntime *exec_runtime; - - pid_t main_pid, control_pid; - int socket_fd; - bool socket_fd_selinux_context_net; - - int bus_endpoint_fd; - - bool permissions_start_only; - bool root_directory_start_only; - bool remain_after_exit; - bool guess_main_pid; - - /* If we shut down, remember why */ - ServiceResult result; - ServiceResult reload_result; - - bool main_pid_known:1; - bool main_pid_alien:1; - bool bus_name_good:1; - bool forbid_restart:1; - bool start_timeout_defined:1; - - bool reset_cpu_usage:1; - - char *bus_name; - char *bus_name_owner; /* unique name of the current owner */ - - char *status_text; - int status_errno; - - FailureAction failure_action; - - UnitRef accept_socket; - - sd_event_source *timer_event_source; - PathSpec *pid_file_pathspec; - - NotifyAccess notify_access; - NotifyState notify_state; - - ServiceFDStore *fd_store; - unsigned n_fd_store; - unsigned n_fd_store_max; - - char *usb_function_descriptors; - char *usb_function_strings; - - int stdin_fd; - int stdout_fd; - int stderr_fd; -}; - -extern const UnitVTable service_vtable; - -int service_set_socket_fd(Service *s, int fd, struct Socket *socket, bool selinux_context_net); - -const char* service_restart_to_string(ServiceRestart i) _const_; -ServiceRestart service_restart_from_string(const char *s) _pure_; - -const char* service_type_to_string(ServiceType i) _const_; -ServiceType service_type_from_string(const char *s) _pure_; - -const char* service_exec_command_to_string(ServiceExecCommand i) _const_; -ServiceExecCommand service_exec_command_from_string(const char *s) _pure_; - -const char* notify_access_to_string(NotifyAccess i) _const_; -NotifyAccess notify_access_from_string(const char *s) _pure_; - -const char* notify_state_to_string(NotifyState i) _const_; -NotifyState notify_state_from_string(const char *s) _pure_; - -const char* service_result_to_string(ServiceResult i) _const_; -ServiceResult service_result_from_string(const char *s) _pure_; diff --git a/src/core/show-status.c b/src/core/show-status.c deleted file mode 100644 index 59ebdc7219..0000000000 --- a/src/core/show-status.c +++ /dev/null @@ -1,124 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "alloc-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "parse-util.h" -#include "show-status.h" -#include "string-util.h" -#include "terminal-util.h" -#include "util.h" - -int parse_show_status(const char *v, ShowStatus *ret) { - int r; - - assert(v); - assert(ret); - - if (streq(v, "auto")) { - *ret = SHOW_STATUS_AUTO; - return 0; - } - - r = parse_boolean(v); - if (r < 0) - return r; - - *ret = r ? SHOW_STATUS_YES : SHOW_STATUS_NO; - return 0; -} - -int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) { - static const char status_indent[] = " "; /* "[" STATUS "] " */ - _cleanup_free_ char *s = NULL; - _cleanup_close_ int fd = -1; - struct iovec iovec[6] = {}; - int n = 0; - static bool prev_ephemeral; - - assert(format); - - /* This is independent of logging, as status messages are - * optional and go exclusively to the console. */ - - if (vasprintf(&s, format, ap) < 0) - return log_oom(); - - fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return fd; - - if (ellipse) { - char *e; - size_t emax, sl; - int c; - - c = fd_columns(fd); - if (c <= 0) - c = 80; - - sl = status ? sizeof(status_indent)-1 : 0; - - emax = c - sl - 1; - if (emax < 3) - emax = 3; - - e = ellipsize(s, emax, 50); - if (e) { - free(s); - s = e; - } - } - - if (prev_ephemeral) - IOVEC_SET_STRING(iovec[n++], "\r" ANSI_ERASE_TO_END_OF_LINE); - prev_ephemeral = ephemeral; - - if (status) { - if (!isempty(status)) { - IOVEC_SET_STRING(iovec[n++], "["); - IOVEC_SET_STRING(iovec[n++], status); - IOVEC_SET_STRING(iovec[n++], "] "); - } else - IOVEC_SET_STRING(iovec[n++], status_indent); - } - - IOVEC_SET_STRING(iovec[n++], s); - if (!ephemeral) - IOVEC_SET_STRING(iovec[n++], "\n"); - - if (writev(fd, iovec, n) < 0) - return -errno; - - return 0; -} - -int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) { - va_list ap; - int r; - - assert(format); - - va_start(ap, format); - r = status_vprintf(status, ellipse, ephemeral, format, ap); - va_end(ap); - - return r; -} diff --git a/src/core/show-status.h b/src/core/show-status.h deleted file mode 100644 index 9a29e72645..0000000000 --- a/src/core/show-status.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "macro.h" - -/* Manager status */ - -typedef enum ShowStatus { - _SHOW_STATUS_UNSET = -2, - SHOW_STATUS_AUTO = -1, - SHOW_STATUS_NO = 0, - SHOW_STATUS_YES = 1, - SHOW_STATUS_TEMPORARY = 2, -} ShowStatus; - -int parse_show_status(const char *v, ShowStatus *ret); - -int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) _printf_(4,0); -int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) _printf_(4,5); diff --git a/src/core/shutdown.c b/src/core/shutdown.c deleted file mode 100644 index 6296b4c94a..0000000000 --- a/src/core/shutdown.c +++ /dev/null @@ -1,435 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 ProFUSION embedded systems - - 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "cgroup-util.h" -#include "def.h" -#include "fileio.h" -#include "killall.h" -#include "log.h" -#include "missing.h" -#include "parse-util.h" -#include "process-util.h" -#include "string-util.h" -#include "switch-root.h" -#include "terminal-util.h" -#include "umount.h" -#include "util.h" -#include "virt.h" -#include "watchdog.h" - -#define FINALIZE_ATTEMPTS 50 - -static char* arg_verb; -static uint8_t arg_exit_code; - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_LOG_LEVEL = 0x100, - ARG_LOG_TARGET, - ARG_LOG_COLOR, - ARG_LOG_LOCATION, - ARG_EXIT_CODE, - }; - - static const struct option options[] = { - { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, - { "log-target", required_argument, NULL, ARG_LOG_TARGET }, - { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, - { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, - { "exit-code", required_argument, NULL, ARG_EXIT_CODE }, - {} - }; - - int c, r; - - assert(argc >= 1); - assert(argv); - - /* "-" prevents getopt from permuting argv[] and moving the verb away - * from argv[1]. Our interface to initrd promises it'll be there. */ - while ((c = getopt_long(argc, argv, "-", options, NULL)) >= 0) - switch (c) { - - case ARG_LOG_LEVEL: - r = log_set_max_level_from_string(optarg); - if (r < 0) - log_error("Failed to parse log level %s, ignoring.", optarg); - - break; - - case ARG_LOG_TARGET: - r = log_set_target_from_string(optarg); - if (r < 0) - log_error("Failed to parse log target %s, ignoring", optarg); - - break; - - case ARG_LOG_COLOR: - - if (optarg) { - r = log_show_color_from_string(optarg); - if (r < 0) - log_error("Failed to parse log color setting %s, ignoring", optarg); - } else - log_show_color(true); - - break; - - case ARG_LOG_LOCATION: - if (optarg) { - r = log_show_location_from_string(optarg); - if (r < 0) - log_error("Failed to parse log location setting %s, ignoring", optarg); - } else - log_show_location(true); - - break; - - case ARG_EXIT_CODE: - r = safe_atou8(optarg, &arg_exit_code); - if (r < 0) - log_error("Failed to parse exit code %s, ignoring", optarg); - - break; - - case '\001': - if (!arg_verb) - arg_verb = optarg; - else - log_error("Excess arguments, ignoring"); - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option code."); - } - - if (!arg_verb) { - log_error("Verb argument missing."); - return -EINVAL; - } - - return 0; -} - -static int switch_root_initramfs(void) { - if (mount("/run/initramfs", "/run/initramfs", NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to mount bind /run/initramfs on /run/initramfs: %m"); - - if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) - return log_error_errno(errno, "Failed to make /run/initramfs private mount: %m"); - - /* switch_root with MS_BIND, because there might still be processes lurking around, which have open file descriptors. - * /run/initramfs/shutdown will take care of these. - * Also do not detach the old root, because /run/initramfs/shutdown needs to access it. - */ - 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; - _cleanup_free_ char *cgroup = NULL; - char *arguments[3]; - unsigned retries; - int cmd, r; - static const char* const dirs[] = {SYSTEM_SHUTDOWN_PATH, NULL}; - - log_parse_environment(); - r = parse_argv(argc, argv); - if (r < 0) - goto error; - - /* journald will die if not gone yet. The log target defaults - * to console, but may have been changed by command line options. */ - - log_close_console(); /* force reopen of /dev/console */ - log_open(); - - umask(0022); - - if (getpid() != 1) { - log_error("Not executed by init (PID 1)."); - r = -EPERM; - goto error; - } - - if (streq(arg_verb, "reboot")) - cmd = RB_AUTOBOOT; - else if (streq(arg_verb, "poweroff")) - cmd = RB_POWER_OFF; - else if (streq(arg_verb, "halt")) - cmd = RB_HALT_SYSTEM; - else if (streq(arg_verb, "kexec")) - cmd = LINUX_REBOOT_CMD_KEXEC; - else if (streq(arg_verb, "exit")) - cmd = 0; /* ignored, just checking that arg_verb is valid */ - else { - r = -EINVAL; - log_error("Unknown action '%s'.", arg_verb); - goto error; - } - - cg_get_root_path(&cgroup); - - use_watchdog = !!getenv("WATCHDOG_USEC"); - - /* lock us into memory */ - mlockall(MCL_CURRENT|MCL_FUTURE); - - 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; - need_dm_detach = !in_container; - - /* Unmount all mountpoints, swaps, and loopback devices */ - for (retries = 0; retries < FINALIZE_ATTEMPTS; retries++) { - bool changed = false; - - if (use_watchdog) - watchdog_ping(); - - /* Let's trim the cgroup tree on each iteration so - that we leave an empty cgroup tree around, so that - container managers get a nice notify event when we - are down */ - if (cgroup) - cg_trim(SYSTEMD_CGROUP_CONTROLLER, cgroup, false); - - if (need_umount) { - log_info("Unmounting file systems."); - r = umount_all(&changed); - if (r == 0) { - need_umount = false; - log_info("All filesystems unmounted."); - } else if (r > 0) - log_info("Not all file systems unmounted, %d left.", r); - else - log_error_errno(r, "Failed to unmount file systems: %m"); - } - - if (need_swapoff) { - log_info("Deactivating swaps."); - r = swapoff_all(&changed); - if (r == 0) { - need_swapoff = false; - log_info("All swaps deactivated."); - } else if (r > 0) - log_info("Not all swaps deactivated, %d left.", r); - else - log_error_errno(r, "Failed to deactivate swaps: %m"); - } - - if (need_loop_detach) { - log_info("Detaching loop devices."); - r = loopback_detach_all(&changed); - if (r == 0) { - need_loop_detach = false; - log_info("All loop devices detached."); - } else if (r > 0) - log_info("Not all loop devices detached, %d left.", r); - else - log_error_errno(r, "Failed to detach loop devices: %m"); - } - - if (need_dm_detach) { - log_info("Detaching DM devices."); - r = dm_detach_all(&changed); - if (r == 0) { - need_dm_detach = false; - log_info("All DM devices detached."); - } else if (r > 0) - log_info("Not all DM devices detached, %d left.", r); - else - log_error_errno(r, "Failed to detach DM devices: %m"); - } - - if (!need_umount && !need_swapoff && !need_loop_detach && !need_dm_detach) { - if (retries > 0) - log_info("All filesystems, swaps, loop devices, DM devices detached."); - /* Yay, done */ - goto initrd_jump; - } - - /* If in this iteration we didn't manage to - * unmount/deactivate anything, we simply give up */ - if (!changed) { - log_info("Cannot finalize remaining%s%s%s%s continuing.", - need_umount ? " file systems," : "", - need_swapoff ? " swap devices," : "", - need_loop_detach ? " loop devices," : "", - need_dm_detach ? " DM devices," : ""); - goto initrd_jump; - } - - log_debug("After %u retries, couldn't finalize remaining %s%s%s%s trying again.", - retries + 1, - need_umount ? " file systems," : "", - need_swapoff ? " swap devices," : "", - need_loop_detach ? " loop devices," : "", - need_dm_detach ? " DM devices," : ""); - } - - log_error("Too many iterations, giving up."); - - initrd_jump: - - arguments[0] = NULL; - arguments[1] = arg_verb; - arguments[2] = NULL; - execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments); - - if (!in_container && !in_initrd() && - access("/run/initramfs/shutdown", X_OK) == 0) { - r = switch_root_initramfs(); - if (r >= 0) { - argv[0] = (char*) "/shutdown"; - - setsid(); - make_console_stdio(); - - log_info("Successfully changed into root pivot.\n" - "Returning to initrd..."); - - execv("/shutdown", argv); - log_error_errno(errno, "Failed to execute shutdown binary: %m"); - } else - log_error_errno(r, "Failed to switch root to \"/run/initramfs\": %m"); - - } - - if (need_umount || need_swapoff || need_loop_detach || need_dm_detach) - log_error("Failed to finalize %s%s%s%s ignoring", - need_umount ? " file systems," : "", - need_swapoff ? " swap devices," : "", - 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. */ - if (!in_container) - sync(); - - if (streq(arg_verb, "exit")) { - if (in_container) - exit(arg_exit_code); - else { - /* We cannot exit() on the host, fallback on another - * method. */ - cmd = RB_POWER_OFF; - } - } - - switch (cmd) { - - case LINUX_REBOOT_CMD_KEXEC: - - if (!in_container) { - /* We cheat and exec kexec to avoid doing all its work */ - pid_t pid; - - log_info("Rebooting with kexec."); - - pid = fork(); - if (pid < 0) - log_error_errno(errno, "Failed to fork: %m"); - else if (pid == 0) { - - const char * const args[] = { - KEXEC, "-e", NULL - }; - - /* Child */ - - execv(args[0], (char * const *) args); - _exit(EXIT_FAILURE); - } else - wait_for_terminate_and_warn("kexec", pid, true); - } - - cmd = RB_AUTOBOOT; - /* Fall through */ - - case RB_AUTOBOOT: - - if (!in_container) { - _cleanup_free_ char *param = NULL; - - if (read_one_line_file(REBOOT_PARAM_FILE, ¶m) >= 0) { - log_info("Rebooting with argument '%s'.", param); - syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, param); - } - } - - log_info("Rebooting."); - break; - - case RB_POWER_OFF: - log_info("Powering off."); - break; - - case RB_HALT_SYSTEM: - log_info("Halting system."); - break; - - default: - assert_not_reached("Unknown magic"); - } - - reboot(cmd); - if (errno == EPERM && in_container) { - /* If we are in a container, and we lacked - * CAP_SYS_BOOT just exit, this will kill our - * container for good. */ - log_info("Exiting container."); - exit(0); - } - - r = log_error_errno(errno, "Failed to invoke reboot(): %m"); - - error: - log_emergency_errno(r, "Critical error while doing system shutdown: %m"); - freeze(); -} diff --git a/src/core/slice.c b/src/core/slice.c deleted file mode 100644 index d65364c6f4..0000000000 --- a/src/core/slice.c +++ /dev/null @@ -1,339 +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 . -***/ - -#include - -#include "alloc-util.h" -#include "dbus-slice.h" -#include "log.h" -#include "slice.h" -#include "special.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit.h" - -static const UnitActiveState state_translation_table[_SLICE_STATE_MAX] = { - [SLICE_DEAD] = UNIT_INACTIVE, - [SLICE_ACTIVE] = UNIT_ACTIVE -}; - -static void slice_set_state(Slice *t, SliceState state) { - SliceState old_state; - assert(t); - - old_state = t->state; - t->state = state; - - if (state != old_state) - log_debug("%s changed %s -> %s", - UNIT(t)->id, - slice_state_to_string(old_state), - slice_state_to_string(state)); - - unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); -} - -static int slice_add_parent_slice(Slice *s) { - char *a, *dash; - Unit *parent; - int r; - - assert(s); - - if (UNIT_ISSET(UNIT(s)->slice)) - return 0; - - if (unit_has_name(UNIT(s), SPECIAL_ROOT_SLICE)) - return 0; - - a = strdupa(UNIT(s)->id); - dash = strrchr(a, '-'); - if (dash) - strcpy(dash, ".slice"); - else - a = (char*) SPECIAL_ROOT_SLICE; - - r = manager_load_unit(UNIT(s)->manager, a, NULL, NULL, &parent); - if (r < 0) - return r; - - unit_ref_set(&UNIT(s)->slice, parent); - return 0; -} - -static int slice_add_default_dependencies(Slice *s) { - int r; - - assert(s); - - if (!UNIT(s)->default_dependencies) - return 0; - - /* Make sure slices are unloaded on shutdown */ - r = unit_add_two_dependencies_by_name( - UNIT(s), - UNIT_BEFORE, UNIT_CONFLICTS, - SPECIAL_SHUTDOWN_TARGET, NULL, true); - if (r < 0) - return r; - - return 0; -} - -static int slice_verify(Slice *s) { - _cleanup_free_ char *parent = NULL; - int r; - - assert(s); - - if (UNIT(s)->load_state != UNIT_LOADED) - return 0; - - if (!slice_name_is_valid(UNIT(s)->id)) { - log_unit_error(UNIT(s), "Slice name %s is not valid. Refusing.", UNIT(s)->id); - return -EINVAL; - } - - r = slice_build_parent_slice(UNIT(s)->id, &parent); - if (r < 0) - return log_unit_error_errno(UNIT(s), r, "Failed to determine parent slice: %m"); - - if (parent ? !unit_has_name(UNIT_DEREF(UNIT(s)->slice), parent) : UNIT_ISSET(UNIT(s)->slice)) { - log_unit_error(UNIT(s), "Located outside of parent slice. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int slice_load(Unit *u) { - Slice *s = SLICE(u); - int r; - - assert(s); - - r = unit_load_fragment_and_dropin_optional(u); - if (r < 0) - return r; - - /* This is a new unit? Then let's add in some extras */ - if (u->load_state == UNIT_LOADED) { - - r = unit_patch_contexts(u); - if (r < 0) - return r; - - r = slice_add_parent_slice(s); - if (r < 0) - return r; - - r = slice_add_default_dependencies(s); - if (r < 0) - return r; - } - - return slice_verify(s); -} - -static int slice_coldplug(Unit *u) { - Slice *t = SLICE(u); - - assert(t); - assert(t->state == SLICE_DEAD); - - if (t->deserialized_state != t->state) - slice_set_state(t, t->deserialized_state); - - return 0; -} - -static void slice_dump(Unit *u, FILE *f, const char *prefix) { - Slice *t = SLICE(u); - - assert(t); - assert(f); - - fprintf(f, - "%sSlice State: %s\n", - prefix, slice_state_to_string(t->state)); - - cgroup_context_dump(&t->cgroup_context, f, prefix); -} - -static int slice_start(Unit *u) { - Slice *t = SLICE(u); - - assert(t); - assert(t->state == SLICE_DEAD); - - (void) unit_realize_cgroup(u); - (void) unit_reset_cpu_usage(u); - - slice_set_state(t, SLICE_ACTIVE); - return 1; -} - -static int slice_stop(Unit *u) { - Slice *t = SLICE(u); - - assert(t); - assert(t->state == SLICE_ACTIVE); - - /* We do not need to destroy the cgroup explicitly, - * unit_notify() will do that for us anyway. */ - - slice_set_state(t, SLICE_DEAD); - return 1; -} - -static int slice_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - return unit_kill_common(u, who, signo, -1, -1, error); -} - -static int slice_serialize(Unit *u, FILE *f, FDSet *fds) { - Slice *s = SLICE(u); - - assert(s); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", slice_state_to_string(s->state)); - return 0; -} - -static int slice_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Slice *s = SLICE(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - SliceState state; - - state = slice_state_from_string(value); - if (state < 0) - log_debug("Failed to parse state value %s", value); - else - s->deserialized_state = state; - - } else - log_debug("Unknown serialization key '%s'", key); - - return 0; -} - -_pure_ static UnitActiveState slice_active_state(Unit *u) { - assert(u); - - return state_translation_table[SLICE(u)->state]; -} - -_pure_ static const char *slice_sub_state_to_string(Unit *u) { - assert(u); - - return slice_state_to_string(SLICE(u)->state); -} - -static void slice_enumerate(Manager *m) { - Unit *u; - int r; - - assert(m); - - u = manager_get_unit(m, SPECIAL_ROOT_SLICE); - if (!u) { - u = unit_new(m, sizeof(Slice)); - if (!u) { - log_oom(); - return; - } - - r = unit_add_name(u, SPECIAL_ROOT_SLICE); - if (r < 0) { - unit_free(u); - log_error_errno(r, "Failed to add -.slice name"); - return; - } - } - - u->default_dependencies = false; - u->no_gc = true; - u->ignore_on_isolate = true; - u->refuse_manual_start = true; - u->refuse_manual_stop = true; - SLICE(u)->deserialized_state = SLICE_ACTIVE; - - if (!u->description) - u->description = strdup("Root Slice"); - if (!u->documentation) - (void) strv_extend(&u->documentation, "man:systemd.special(7)"); - - unit_add_to_load_queue(u); - unit_add_to_dbus_queue(u); -} - -const UnitVTable slice_vtable = { - .object_size = sizeof(Slice), - .cgroup_context_offset = offsetof(Slice, cgroup_context), - - .sections = - "Unit\0" - "Slice\0" - "Install\0", - .private_section = "Slice", - - .no_alias = true, - .no_instances = true, - .can_transient = true, - - .load = slice_load, - - .coldplug = slice_coldplug, - - .dump = slice_dump, - - .start = slice_start, - .stop = slice_stop, - - .kill = slice_kill, - - .serialize = slice_serialize, - .deserialize_item = slice_deserialize_item, - - .active_state = slice_active_state, - .sub_state_to_string = slice_sub_state_to_string, - - .bus_vtable = bus_slice_vtable, - .bus_set_property = bus_slice_set_property, - .bus_commit_properties = bus_slice_commit_properties, - - .enumerate = slice_enumerate, - - .status_message_formats = { - .finished_start_job = { - [JOB_DONE] = "Created slice %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Removed slice %s.", - }, - }, -}; diff --git a/src/core/slice.h b/src/core/slice.h deleted file mode 100644 index c9f3f61067..0000000000 --- a/src/core/slice.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -typedef struct Slice Slice; - -struct Slice { - Unit meta; - - SliceState state, deserialized_state; - - CGroupContext cgroup_context; -}; - -extern const UnitVTable slice_vtable; diff --git a/src/core/smack-setup.c b/src/core/smack-setup.c deleted file mode 100644 index 0c26e85460..0000000000 --- a/src/core/smack-setup.c +++ /dev/null @@ -1,346 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2013 Intel Corporation - Authors: - Nathaniel Chen - - 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "log.h" -#include "macro.h" -#include "smack-setup.h" -#include "string-util.h" -#include "util.h" - -#ifdef HAVE_SMACK - -static int write_access2_rules(const char* srcdir) { - _cleanup_close_ int load2_fd = -1, change_fd = -1; - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *entry; - char buf[NAME_MAX]; - int dfd = -1; - int r = 0; - - load2_fd = open("/sys/fs/smackfs/load2", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (load2_fd < 0) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open '/sys/fs/smackfs/load2': %m"); - return -errno; /* negative error */ - } - - change_fd = open("/sys/fs/smackfs/change-rule", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (change_fd < 0) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open '/sys/fs/smackfs/change-rule': %m"); - return -errno; /* negative error */ - } - - /* write rules to load2 or change-rule from every file in the directory */ - dir = opendir(srcdir); - if (!dir) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to opendir '%s': %m", srcdir); - return errno; /* positive on purpose */ - } - - dfd = dirfd(dir); - assert(dfd >= 0); - - FOREACH_DIRENT(entry, dir, return 0) { - int fd; - _cleanup_fclose_ FILE *policy = NULL; - - if (!dirent_is_file(entry)) - continue; - - fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); - if (fd < 0) { - if (r == 0) - r = -errno; - log_warning_errno(errno, "Failed to open '%s': %m", entry->d_name); - continue; - } - - policy = fdopen(fd, "re"); - if (!policy) { - if (r == 0) - r = -errno; - safe_close(fd); - log_error_errno(errno, "Failed to open '%s': %m", entry->d_name); - continue; - } - - /* load2 write rules in the kernel require a line buffered stream */ - FOREACH_LINE(buf, policy, - log_error_errno(errno, "Failed to read line from '%s': %m", - entry->d_name)) { - - _cleanup_free_ char *sbj = NULL, *obj = NULL, *acc1 = NULL, *acc2 = NULL; - - if (isempty(truncate_nl(buf))) - continue; - - /* if 3 args -> load rule : subject object access1 */ - /* if 4 args -> change rule : subject object access1 access2 */ - if (sscanf(buf, "%ms %ms %ms %ms", &sbj, &obj, &acc1, &acc2) < 3) { - log_error_errno(errno, "Failed to parse rule '%s' in '%s', ignoring.", buf, entry->d_name); - continue; - } - - if (write(isempty(acc2) ? load2_fd : change_fd, buf, strlen(buf)) < 0) { - if (r == 0) - r = -errno; - log_error_errno(errno, "Failed to write '%s' to '%s' in '%s'", - buf, isempty(acc2) ? "/sys/fs/smackfs/load2" : "/sys/fs/smackfs/change-rule", entry->d_name); - } - } - } - - return r; -} - -static int write_cipso2_rules(const char* srcdir) { - _cleanup_close_ int cipso2_fd = -1; - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *entry; - char buf[NAME_MAX]; - int dfd = -1; - int r = 0; - - cipso2_fd = open("/sys/fs/smackfs/cipso2", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (cipso2_fd < 0) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open '/sys/fs/smackfs/cipso2': %m"); - return -errno; /* negative error */ - } - - /* write rules to cipso2 from every file in the directory */ - dir = opendir(srcdir); - if (!dir) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to opendir '%s': %m", srcdir); - return errno; /* positive on purpose */ - } - - dfd = dirfd(dir); - assert(dfd >= 0); - - FOREACH_DIRENT(entry, dir, return 0) { - int fd; - _cleanup_fclose_ FILE *policy = NULL; - - if (!dirent_is_file(entry)) - continue; - - fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); - if (fd < 0) { - if (r == 0) - r = -errno; - log_error_errno(errno, "Failed to open '%s': %m", entry->d_name); - continue; - } - - policy = fdopen(fd, "re"); - if (!policy) { - if (r == 0) - r = -errno; - safe_close(fd); - log_error_errno(errno, "Failed to open '%s': %m", entry->d_name); - continue; - } - - /* cipso2 write rules in the kernel require a line buffered stream */ - FOREACH_LINE(buf, policy, - log_error_errno(errno, "Failed to read line from '%s': %m", - entry->d_name)) { - - if (isempty(truncate_nl(buf))) - continue; - - if (write(cipso2_fd, buf, strlen(buf)) < 0) { - if (r == 0) - r = -errno; - log_error_errno(errno, "Failed to write '%s' to '/sys/fs/smackfs/cipso2' in '%s'", - buf, entry->d_name); - break; - } - } - } - - return r; -} - -static int write_netlabel_rules(const char* srcdir) { - _cleanup_fclose_ FILE *dst = NULL; - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *entry; - char buf[NAME_MAX]; - int dfd = -1; - int r = 0; - - dst = fopen("/sys/fs/smackfs/netlabel", "we"); - if (!dst) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to open /sys/fs/smackfs/netlabel: %m"); - return -errno; /* negative error */ - } - - /* write rules to dst from every file in the directory */ - dir = opendir(srcdir); - if (!dir) { - if (errno != ENOENT) - log_warning_errno(errno, "Failed to opendir %s: %m", srcdir); - return errno; /* positive on purpose */ - } - - dfd = dirfd(dir); - assert(dfd >= 0); - - FOREACH_DIRENT(entry, dir, return 0) { - int fd; - _cleanup_fclose_ FILE *policy = NULL; - - fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); - if (fd < 0) { - if (r == 0) - r = -errno; - log_warning_errno(errno, "Failed to open %s: %m", entry->d_name); - continue; - } - - policy = fdopen(fd, "re"); - if (!policy) { - if (r == 0) - r = -errno; - safe_close(fd); - log_error_errno(errno, "Failed to open %s: %m", entry->d_name); - continue; - } - - /* load2 write rules in the kernel require a line buffered stream */ - FOREACH_LINE(buf, policy, - log_error_errno(errno, "Failed to read line from %s: %m", - entry->d_name)) { - if (!fputs(buf, dst)) { - if (r == 0) - r = -EINVAL; - log_error_errno(errno, "Failed to write line to /sys/fs/smackfs/netlabel"); - break; - } - if (fflush(dst)) { - if (r == 0) - r = -errno; - log_error_errno(errno, "Failed to flush writes to /sys/fs/smackfs/netlabel: %m"); - break; - } - } - } - - return r; -} - -#endif - -int mac_smack_setup(bool *loaded_policy) { - -#ifdef HAVE_SMACK - - int r; - - assert(loaded_policy); - - r = write_access2_rules("/etc/smack/accesses.d/"); - switch(r) { - case -ENOENT: - log_debug("Smack is not enabled in the kernel."); - return 0; - case ENOENT: - log_debug("Smack access rules directory '/etc/smack/accesses.d/' not found"); - return 0; - case 0: - log_info("Successfully loaded Smack policies."); - break; - default: - log_warning_errno(r, "Failed to load Smack access rules, ignoring: %m"); - return 0; - } - -#ifdef SMACK_RUN_LABEL - r = write_string_file("/proc/self/attr/current", SMACK_RUN_LABEL, 0); - if (r < 0) - log_warning_errno(r, "Failed to set SMACK label \"" SMACK_RUN_LABEL "\" on self: %m"); - r = write_string_file("/sys/fs/smackfs/ambient", SMACK_RUN_LABEL, 0); - if (r < 0) - log_warning_errno(r, "Failed to set SMACK ambient label \"" SMACK_RUN_LABEL "\": %m"); - r = write_string_file("/sys/fs/smackfs/netlabel", - "0.0.0.0/0 " SMACK_RUN_LABEL, 0); - if (r < 0) - log_warning_errno(r, "Failed to set SMACK netlabel rule \"0.0.0.0/0 " SMACK_RUN_LABEL "\": %m"); - r = write_string_file("/sys/fs/smackfs/netlabel", "127.0.0.1 -CIPSO", 0); - if (r < 0) - log_warning_errno(r, "Failed to set SMACK netlabel rule \"127.0.0.1 -CIPSO\": %m"); -#endif - - r = write_cipso2_rules("/etc/smack/cipso.d/"); - switch(r) { - case -ENOENT: - log_debug("Smack/CIPSO is not enabled in the kernel."); - return 0; - case ENOENT: - log_debug("Smack/CIPSO access rules directory '/etc/smack/cipso.d/' not found"); - break; - case 0: - log_info("Successfully loaded Smack/CIPSO policies."); - break; - default: - log_warning_errno(r, "Failed to load Smack/CIPSO access rules, ignoring: %m"); - break; - } - - r = write_netlabel_rules("/etc/smack/netlabel.d/"); - switch(r) { - case -ENOENT: - log_debug("Smack/CIPSO is not enabled in the kernel."); - return 0; - case ENOENT: - log_debug("Smack network host rules directory '/etc/smack/netlabel.d/' not found"); - break; - case 0: - log_info("Successfully loaded Smack network host rules."); - break; - default: - log_warning_errno(r, "Failed to load Smack network host rules: %m, ignoring."); - break; - } - - *loaded_policy = true; - -#endif - - return 0; -} diff --git a/src/core/smack-setup.h b/src/core/smack-setup.h deleted file mode 100644 index 78164c85e6..0000000000 --- a/src/core/smack-setup.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright (C) 2013 Intel Corporation - Authors: - Nathaniel Chen - - 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 . -***/ - -int mac_smack_setup(bool *loaded_policy); diff --git a/src/core/socket.c b/src/core/socket.c deleted file mode 100644 index 976687af41..0000000000 --- a/src/core/socket.c +++ /dev/null @@ -1,2866 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-event.h" -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "copy.h" -#include "dbus-socket.h" -#include "def.h" -#include "exit-status.h" -#include "fd-util.h" -#include "formats-util.h" -#include "label.h" -#include "log.h" -#include "missing.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "smack-util.h" -#include "socket.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit-printf.h" -#include "unit.h" -#include "user-util.h" - -static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = { - [SOCKET_DEAD] = UNIT_INACTIVE, - [SOCKET_START_PRE] = UNIT_ACTIVATING, - [SOCKET_START_CHOWN] = UNIT_ACTIVATING, - [SOCKET_START_POST] = UNIT_ACTIVATING, - [SOCKET_LISTENING] = UNIT_ACTIVE, - [SOCKET_RUNNING] = UNIT_ACTIVE, - [SOCKET_STOP_PRE] = UNIT_DEACTIVATING, - [SOCKET_STOP_PRE_SIGTERM] = UNIT_DEACTIVATING, - [SOCKET_STOP_PRE_SIGKILL] = UNIT_DEACTIVATING, - [SOCKET_STOP_POST] = UNIT_DEACTIVATING, - [SOCKET_FINAL_SIGTERM] = UNIT_DEACTIVATING, - [SOCKET_FINAL_SIGKILL] = UNIT_DEACTIVATING, - [SOCKET_FAILED] = UNIT_FAILED -}; - -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); - -static void socket_init(Unit *u) { - Socket *s = SOCKET(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - s->backlog = SOMAXCONN; - s->timeout_usec = u->manager->default_timeout_start_usec; - s->directory_mode = 0755; - s->socket_mode = 0666; - - s->max_connections = 64; - - s->priority = -1; - s->ip_tos = -1; - s->ip_ttl = -1; - s->mark = -1; - - s->exec_context.std_output = u->manager->default_std_output; - s->exec_context.std_error = u->manager->default_std_error; - - s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; -} - -static void socket_unwatch_control_pid(Socket *s) { - assert(s); - - if (s->control_pid <= 0) - return; - - unit_unwatch_pid(UNIT(s), s->control_pid); - s->control_pid = 0; -} - -static void socket_cleanup_fd_list(SocketPort *p) { - assert(p); - - close_many(p->auxiliary_fds, p->n_auxiliary_fds); - p->auxiliary_fds = mfree(p->auxiliary_fds); - p->n_auxiliary_fds = 0; -} - -void socket_free_ports(Socket *s) { - SocketPort *p; - - assert(s); - - while ((p = s->ports)) { - LIST_REMOVE(port, s->ports, p); - - sd_event_source_unref(p->event_source); - - socket_cleanup_fd_list(p); - safe_close(p->fd); - free(p->path); - free(p); - } -} - -static void socket_done(Unit *u) { - Socket *s = SOCKET(u); - - assert(s); - - socket_free_ports(s); - - s->exec_runtime = exec_runtime_unref(s->exec_runtime); - exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX); - s->control_command = NULL; - - socket_unwatch_control_pid(s); - - unit_ref_unset(&s->service); - - s->tcp_congestion = mfree(s->tcp_congestion); - s->bind_to_device = mfree(s->bind_to_device); - - s->smack = mfree(s->smack); - s->smack_ip_in = mfree(s->smack_ip_in); - s->smack_ip_out = mfree(s->smack_ip_out); - - strv_free(s->symlinks); - - s->user = mfree(s->user); - s->group = mfree(s->group); - - s->fdname = mfree(s->fdname); - - s->timer_event_source = sd_event_source_unref(s->timer_event_source); -} - -static int socket_arm_timer(Socket *s, usec_t usec) { - int r; - - assert(s); - - if (s->timer_event_source) { - r = sd_event_source_set_time(s->timer_event_source, usec); - if (r < 0) - return r; - - return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); - } - - if (usec == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - UNIT(s)->manager->event, - &s->timer_event_source, - CLOCK_MONOTONIC, - usec, 0, - socket_dispatch_timer, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->timer_event_source, "socket-timer"); - - return 0; -} - -int socket_instantiate_service(Socket *s) { - _cleanup_free_ char *prefix = NULL, *name = NULL; - int r; - Unit *u; - - assert(s); - - /* This fills in s->service if it isn't filled in yet. For - * Accept=yes sockets we create the next connection service - * here. For Accept=no this is mostly a NOP since the service - * is figured out at load time anyway. */ - - if (UNIT_DEREF(s->service)) - return 0; - - if (!s->accept) - return 0; - - r = unit_name_to_prefix(UNIT(s)->id, &prefix); - if (r < 0) - return r; - - if (asprintf(&name, "%s@%u.service", prefix, s->n_accepted) < 0) - return -ENOMEM; - - r = manager_load_unit(UNIT(s)->manager, name, NULL, NULL, &u); - if (r < 0) - return r; - - u->no_gc = true; - unit_ref_set(&s->service, u); - - return unit_add_two_dependencies(UNIT(s), UNIT_BEFORE, UNIT_TRIGGERS, u, false); -} - -static bool have_non_accept_socket(Socket *s) { - SocketPort *p; - - assert(s); - - if (!s->accept) - return true; - - LIST_FOREACH(port, p, s->ports) { - - if (p->type != SOCKET_SOCKET) - return true; - - if (!socket_address_can_accept(&p->address)) - return true; - } - - return false; -} - -static int socket_add_mount_links(Socket *s) { - SocketPort *p; - int r; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - const char *path = NULL; - - if (p->type == SOCKET_SOCKET) - path = socket_address_get_path(&p->address); - else if (IN_SET(p->type, SOCKET_FIFO, SOCKET_SPECIAL, SOCKET_USB_FUNCTION)) - path = p->path; - - if (!path) - continue; - - r = unit_require_mounts_for(UNIT(s), path); - if (r < 0) - return r; - } - - return 0; -} - -static int socket_add_device_link(Socket *s) { - char *t; - - assert(s); - - if (!s->bind_to_device || streq(s->bind_to_device, "lo")) - return 0; - - t = strjoina("/sys/subsystem/net/devices/", s->bind_to_device); - return unit_add_node_link(UNIT(s), t, false, UNIT_BINDS_TO); -} - -static int socket_add_default_dependencies(Socket *s) { - int r; - assert(s); - - if (!UNIT(s)->default_dependencies) - return 0; - - r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SOCKETS_TARGET, NULL, true); - if (r < 0) - return r; - - if (UNIT(s)->manager->running_as == MANAGER_SYSTEM) { - r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); - if (r < 0) - return r; - } - - return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -_pure_ static bool socket_has_exec(Socket *s) { - unsigned i; - assert(s); - - for (i = 0; i < _SOCKET_EXEC_COMMAND_MAX; i++) - if (s->exec_command[i]) - return true; - - return false; -} - -static int socket_add_extras(Socket *s) { - Unit *u = UNIT(s); - int r; - - assert(s); - - if (have_non_accept_socket(s)) { - - if (!UNIT_DEREF(s->service)) { - Unit *x; - - r = unit_load_related_unit(u, ".service", &x); - if (r < 0) - return r; - - unit_ref_set(&s->service, x); - } - - r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(s->service), true); - if (r < 0) - return r; - } - - r = socket_add_mount_links(s); - if (r < 0) - return r; - - r = socket_add_device_link(s); - if (r < 0) - return r; - - r = unit_patch_contexts(u); - if (r < 0) - return r; - - if (socket_has_exec(s)) { - r = unit_add_exec_dependencies(u, &s->exec_context); - if (r < 0) - return r; - - r = unit_set_default_slice(u); - if (r < 0) - return r; - } - - r = socket_add_default_dependencies(s); - if (r < 0) - return r; - - return 0; -} - -static const char *socket_find_symlink_target(Socket *s) { - const char *found = NULL; - SocketPort *p; - - LIST_FOREACH(port, p, s->ports) { - const char *f = NULL; - - switch (p->type) { - - case SOCKET_FIFO: - f = p->path; - break; - - case SOCKET_SOCKET: - if (p->address.sockaddr.un.sun_path[0] != 0) - f = p->address.sockaddr.un.sun_path; - break; - - default: - break; - } - - if (f) { - if (found) - return NULL; - - found = f; - } - } - - return found; -} - -static int socket_verify(Socket *s) { - assert(s); - - if (UNIT(s)->load_state != UNIT_LOADED) - return 0; - - if (!s->ports) { - log_unit_error(UNIT(s), "Unit lacks Listen setting. Refusing."); - return -EINVAL; - } - - if (s->accept && have_non_accept_socket(s)) { - log_unit_error(UNIT(s), "Unit configured for accepting sockets, but sockets are non-accepting. Refusing."); - return -EINVAL; - } - - if (s->accept && s->max_connections <= 0) { - log_unit_error(UNIT(s), "MaxConnection= setting too small. Refusing."); - return -EINVAL; - } - - if (s->accept && UNIT_DEREF(s->service)) { - log_unit_error(UNIT(s), "Explicit service configuration for accepting socket units not supported. Refusing."); - return -EINVAL; - } - - if (s->exec_context.pam_name && s->kill_context.kill_mode != KILL_CONTROL_GROUP) { - log_unit_error(UNIT(s), "Unit has PAM enabled. Kill mode must be set to 'control-group'. Refusing."); - return -EINVAL; - } - - if (!strv_isempty(s->symlinks) && !socket_find_symlink_target(s)) { - log_unit_error(UNIT(s), "Unit has symlinks set but none or more than one node in the file system. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int socket_load(Unit *u) { - Socket *s = SOCKET(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - r = unit_load_fragment_and_dropin(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - /* This is a new unit? Then let's add in some extras */ - r = socket_add_extras(s); - if (r < 0) - return r; - } - - return socket_verify(s); -} - -_const_ static const char* listen_lookup(int family, int type) { - - if (family == AF_NETLINK) - return "ListenNetlink"; - - if (type == SOCK_STREAM) - return "ListenStream"; - else if (type == SOCK_DGRAM) - return "ListenDatagram"; - else if (type == SOCK_SEQPACKET) - return "ListenSequentialPacket"; - - assert_not_reached("Unknown socket type"); - return NULL; -} - -static void socket_dump(Unit *u, FILE *f, const char *prefix) { - char time_string[FORMAT_TIMESPAN_MAX]; - SocketExecCommand c; - Socket *s = SOCKET(u); - SocketPort *p; - const char *prefix2; - - assert(s); - assert(f); - - prefix = strempty(prefix); - prefix2 = strjoina(prefix, "\t"); - - fprintf(f, - "%sSocket State: %s\n" - "%sResult: %s\n" - "%sBindIPv6Only: %s\n" - "%sBacklog: %u\n" - "%sSocketMode: %04o\n" - "%sDirectoryMode: %04o\n" - "%sKeepAlive: %s\n" - "%sNoDelay: %s\n" - "%sFreeBind: %s\n" - "%sTransparent: %s\n" - "%sBroadcast: %s\n" - "%sPassCredentials: %s\n" - "%sPassSecurity: %s\n" - "%sTCPCongestion: %s\n" - "%sRemoveOnStop: %s\n" - "%sWritable: %s\n" - "%sFDName: %s\n" - "%sSELinuxContextFromNet: %s\n", - prefix, socket_state_to_string(s->state), - prefix, socket_result_to_string(s->result), - prefix, socket_address_bind_ipv6_only_to_string(s->bind_ipv6_only), - prefix, s->backlog, - prefix, s->socket_mode, - prefix, s->directory_mode, - prefix, yes_no(s->keep_alive), - prefix, yes_no(s->no_delay), - prefix, yes_no(s->free_bind), - prefix, yes_no(s->transparent), - prefix, yes_no(s->broadcast), - prefix, yes_no(s->pass_cred), - prefix, yes_no(s->pass_sec), - prefix, strna(s->tcp_congestion), - prefix, yes_no(s->remove_on_stop), - prefix, yes_no(s->writable), - prefix, socket_fdname(s), - prefix, yes_no(s->selinux_context_from_net)); - - if (s->control_pid > 0) - fprintf(f, - "%sControl PID: "PID_FMT"\n", - prefix, s->control_pid); - - if (s->bind_to_device) - fprintf(f, - "%sBindToDevice: %s\n", - prefix, s->bind_to_device); - - if (s->accept) - fprintf(f, - "%sAccepted: %u\n" - "%sNConnections: %u\n" - "%sMaxConnections: %u\n", - prefix, s->n_accepted, - prefix, s->n_connections, - prefix, s->max_connections); - - if (s->priority >= 0) - fprintf(f, - "%sPriority: %i\n", - prefix, s->priority); - - if (s->receive_buffer > 0) - fprintf(f, - "%sReceiveBuffer: %zu\n", - prefix, s->receive_buffer); - - if (s->send_buffer > 0) - fprintf(f, - "%sSendBuffer: %zu\n", - prefix, s->send_buffer); - - if (s->ip_tos >= 0) - fprintf(f, - "%sIPTOS: %i\n", - prefix, s->ip_tos); - - if (s->ip_ttl >= 0) - fprintf(f, - "%sIPTTL: %i\n", - prefix, s->ip_ttl); - - if (s->pipe_size > 0) - fprintf(f, - "%sPipeSize: %zu\n", - prefix, s->pipe_size); - - if (s->mark >= 0) - fprintf(f, - "%sMark: %i\n", - prefix, s->mark); - - if (s->mq_maxmsg > 0) - fprintf(f, - "%sMessageQueueMaxMessages: %li\n", - prefix, s->mq_maxmsg); - - if (s->mq_msgsize > 0) - fprintf(f, - "%sMessageQueueMessageSize: %li\n", - prefix, s->mq_msgsize); - - if (s->reuse_port) - fprintf(f, - "%sReusePort: %s\n", - prefix, yes_no(s->reuse_port)); - - if (s->smack) - fprintf(f, - "%sSmackLabel: %s\n", - prefix, s->smack); - - if (s->smack_ip_in) - fprintf(f, - "%sSmackLabelIPIn: %s\n", - prefix, s->smack_ip_in); - - if (s->smack_ip_out) - fprintf(f, - "%sSmackLabelIPOut: %s\n", - prefix, s->smack_ip_out); - - if (!isempty(s->user) || !isempty(s->group)) - fprintf(f, - "%sOwnerUser: %s\n" - "%sOwnerGroup: %s\n", - prefix, strna(s->user), - prefix, strna(s->group)); - - if (s->keep_alive_time > 0) - fprintf(f, - "%sKeepAliveTimeSec: %s\n", - prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->keep_alive_time, USEC_PER_SEC)); - - if (s->keep_alive_interval) - fprintf(f, - "%sKeepAliveIntervalSec: %s\n", - prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->keep_alive_interval, USEC_PER_SEC)); - - if (s->keep_alive_cnt) - fprintf(f, - "%sKeepAliveProbes: %u\n", - prefix, s->keep_alive_cnt); - - if (s->defer_accept) - fprintf(f, - "%sDeferAcceptSec: %s\n", - prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->defer_accept, USEC_PER_SEC)); - - LIST_FOREACH(port, p, s->ports) { - - if (p->type == SOCKET_SOCKET) { - const char *t; - int r; - char *k = NULL; - - r = socket_address_print(&p->address, &k); - if (r < 0) - t = strerror(-r); - else - t = k; - - fprintf(f, "%s%s: %s\n", prefix, listen_lookup(socket_address_family(&p->address), p->address.type), t); - free(k); - } else if (p->type == SOCKET_SPECIAL) - fprintf(f, "%sListenSpecial: %s\n", prefix, p->path); - else if (p->type == SOCKET_USB_FUNCTION) - fprintf(f, "%sListenUSBFunction: %s\n", prefix, p->path); - else if (p->type == SOCKET_MQUEUE) - fprintf(f, "%sListenMessageQueue: %s\n", prefix, p->path); - else - fprintf(f, "%sListenFIFO: %s\n", prefix, p->path); - } - - exec_context_dump(&s->exec_context, f, prefix); - kill_context_dump(&s->kill_context, f, prefix); - - for (c = 0; c < _SOCKET_EXEC_COMMAND_MAX; c++) { - if (!s->exec_command[c]) - continue; - - fprintf(f, "%s-> %s:\n", - prefix, socket_exec_command_to_string(c)); - - exec_command_dump_list(s->exec_command[c], f, prefix2); - } -} - -static int instance_from_socket(int fd, unsigned nr, char **instance) { - socklen_t l; - char *r; - union sockaddr_union local, remote; - - assert(fd >= 0); - assert(instance); - - l = sizeof(local); - if (getsockname(fd, &local.sa, &l) < 0) - return -errno; - - l = sizeof(remote); - if (getpeername(fd, &remote.sa, &l) < 0) - return -errno; - - switch (local.sa.sa_family) { - - case AF_INET: { - uint32_t - a = ntohl(local.in.sin_addr.s_addr), - b = ntohl(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), - b >> 24, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF, - ntohs(remote.in.sin_port)) < 0) - return -ENOMEM; - - break; - } - - case AF_INET6: { - static const unsigned char ipv4_prefix[] = { - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF - }; - - if (memcmp(&local.in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0 && - memcmp(&remote.in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0) { - const uint8_t - *a = local.in6.sin6_addr.s6_addr+12, - *b = remote.in6.sin6_addr.s6_addr+12; - - if (asprintf(&r, - "%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), - b[0], b[1], b[2], b[3], - ntohs(remote.in6.sin6_port)) < 0) - return -ENOMEM; - } else { - char a[INET6_ADDRSTRLEN], b[INET6_ADDRSTRLEN]; - - if (asprintf(&r, - "%u-%s:%u-%s:%u", - nr, - inet_ntop(AF_INET6, &local.in6.sin6_addr, a, sizeof(a)), - ntohs(local.in6.sin6_port), - inet_ntop(AF_INET6, &remote.in6.sin6_addr, b, sizeof(b)), - ntohs(remote.in6.sin6_port)) < 0) - return -ENOMEM; - } - - break; - } - - case AF_UNIX: { - struct ucred ucred; - int k; - - k = getpeercred(fd, &ucred); - if (k >= 0) { - if (asprintf(&r, - "%u-"PID_FMT"-"UID_FMT, - nr, ucred.pid, ucred.uid) < 0) - return -ENOMEM; - } else if (k == -ENODATA) { - /* This handles the case where somebody is - * connecting from another pid/uid namespace - * (e.g. from outside of our container). */ - if (asprintf(&r, - "%u-unknown", - nr) < 0) - return -ENOMEM; - } else - return k; - - break; - } - - default: - assert_not_reached("Unhandled socket type."); - } - - *instance = r; - return 0; -} - -static void socket_close_fds(Socket *s) { - SocketPort *p; - char **i; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - - p->event_source = sd_event_source_unref(p->event_source); - - if (p->fd < 0) - continue; - - p->fd = safe_close(p->fd); - socket_cleanup_fd_list(p); - - /* One little note: we should normally not delete any - * sockets in the file system here! After all some - * other process we spawned might still have a - * reference of this fd and wants to continue to use - * it. Therefore we delete sockets in the file system - * before we create a new one, not after we stopped - * using one! */ - - if (s->remove_on_stop) { - switch (p->type) { - - case SOCKET_FIFO: - unlink(p->path); - break; - - case SOCKET_MQUEUE: - mq_unlink(p->path); - break; - - case SOCKET_SOCKET: - socket_address_unlink(&p->address); - break; - - default: - break; - } - } - } - - if (s->remove_on_stop) - STRV_FOREACH(i, s->symlinks) - unlink(*i); -} - -static void socket_apply_socket_options(Socket *s, int fd) { - int r; - - assert(s); - assert(fd >= 0); - - if (s->keep_alive) { - int b = s->keep_alive; - if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &b, sizeof(b)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_KEEPALIVE failed: %m"); - } - - if (s->keep_alive_time) { - int value = s->keep_alive_time / USEC_PER_SEC; - if (setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &value, sizeof(value)) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_KEEPIDLE failed: %m"); - } - - if (s->keep_alive_interval) { - int value = s->keep_alive_interval / USEC_PER_SEC; - if (setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &value, sizeof(value)) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_KEEPINTVL failed: %m"); - } - - if (s->keep_alive_cnt) { - int value = s->keep_alive_cnt; - if (setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &value, sizeof(value)) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_KEEPCNT failed: %m"); - } - - if (s->defer_accept) { - int value = s->defer_accept / USEC_PER_SEC; - if (setsockopt(fd, SOL_TCP, TCP_DEFER_ACCEPT, &value, sizeof(value)) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_DEFER_ACCEPT failed: %m"); - } - - if (s->no_delay) { - int b = s->no_delay; - - if (s->socket_protocol == IPPROTO_SCTP) { - if (setsockopt(fd, SOL_SCTP, SCTP_NODELAY, &b, sizeof(b)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SCTP_NODELAY failed: %m"); - } else { - if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &b, sizeof(b)) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_NODELAY failed: %m"); - } - } - - if (s->broadcast) { - int one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_BROADCAST failed: %m"); - } - - if (s->pass_cred) { - int one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_PASSCRED failed: %m"); - } - - if (s->pass_sec) { - int one = 1; - if (setsockopt(fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_PASSSEC failed: %m"); - } - - if (s->priority >= 0) - if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &s->priority, sizeof(s->priority)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_PRIORITY failed: %m"); - - if (s->receive_buffer > 0) { - int value = (int) s->receive_buffer; - - /* We first try with SO_RCVBUFFORCE, in case we have the perms for that */ - - if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0) - if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_RCVBUF failed: %m"); - } - - if (s->send_buffer > 0) { - int value = (int) s->send_buffer; - if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0) - if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_SNDBUF failed: %m"); - } - - if (s->mark >= 0) - if (setsockopt(fd, SOL_SOCKET, SO_MARK, &s->mark, sizeof(s->mark)) < 0) - log_unit_warning_errno(UNIT(s), errno, "SO_MARK failed: %m"); - - if (s->ip_tos >= 0) - if (setsockopt(fd, IPPROTO_IP, IP_TOS, &s->ip_tos, sizeof(s->ip_tos)) < 0) - log_unit_warning_errno(UNIT(s), errno, "IP_TOS failed: %m"); - - if (s->ip_ttl >= 0) { - int x; - - r = setsockopt(fd, IPPROTO_IP, IP_TTL, &s->ip_ttl, sizeof(s->ip_ttl)); - - if (socket_ipv6_is_supported()) - x = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &s->ip_ttl, sizeof(s->ip_ttl)); - else { - x = -1; - errno = EAFNOSUPPORT; - } - - if (r < 0 && x < 0) - log_unit_warning_errno(UNIT(s), errno, "IP_TTL/IPV6_UNICAST_HOPS failed: %m"); - } - - if (s->tcp_congestion) - if (setsockopt(fd, SOL_TCP, TCP_CONGESTION, s->tcp_congestion, strlen(s->tcp_congestion)+1) < 0) - log_unit_warning_errno(UNIT(s), errno, "TCP_CONGESTION failed: %m"); - - if (s->smack_ip_in) { - r = mac_smack_apply_fd(fd, SMACK_ATTR_IPIN, s->smack_ip_in); - if (r < 0) - log_unit_error_errno(UNIT(s), r, "mac_smack_apply_ip_in_fd: %m"); - } - - if (s->smack_ip_out) { - r = mac_smack_apply_fd(fd, SMACK_ATTR_IPOUT, s->smack_ip_out); - if (r < 0) - log_unit_error_errno(UNIT(s), r, "mac_smack_apply_ip_out_fd: %m"); - } -} - -static void socket_apply_fifo_options(Socket *s, int fd) { - int r; - - assert(s); - assert(fd >= 0); - - if (s->pipe_size > 0) - if (fcntl(fd, F_SETPIPE_SZ, s->pipe_size) < 0) - log_unit_warning_errno(UNIT(s), errno, "Setting pipe size failed, ignoring: %m"); - - if (s->smack) { - r = mac_smack_apply_fd(fd, SMACK_ATTR_ACCESS, s->smack); - if (r < 0) - log_unit_error_errno(UNIT(s), r, "SMACK relabelling failed, ignoring: %m"); - } -} - -static int fifo_address_create( - const char *path, - mode_t directory_mode, - mode_t socket_mode) { - - _cleanup_close_ int fd = -1; - mode_t old_mask; - struct stat st; - int r; - - assert(path); - - mkdir_parents_label(path, directory_mode); - - r = mac_selinux_create_file_prepare(path, S_IFIFO); - if (r < 0) - return r; - - /* Enforce the right access mode for the fifo */ - old_mask = umask(~ socket_mode); - - /* Include the original umask in our mask */ - (void) umask(~socket_mode | old_mask); - - r = mkfifo(path, socket_mode); - (void) umask(old_mask); - - if (r < 0 && errno != EEXIST) { - r = -errno; - goto fail; - } - - fd = open(path, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK | O_NOFOLLOW); - if (fd < 0) { - r = -errno; - goto fail; - } - - mac_selinux_create_file_clear(); - - if (fstat(fd, &st) < 0) { - r = -errno; - goto fail; - } - - if (!S_ISFIFO(st.st_mode) || - (st.st_mode & 0777) != (socket_mode & ~old_mask) || - st.st_uid != getuid() || - st.st_gid != getgid()) { - r = -EEXIST; - goto fail; - } - - r = fd; - fd = -1; - - return r; - -fail: - mac_selinux_create_file_clear(); - return r; -} - -static int special_address_create(const char *path, bool writable) { - _cleanup_close_ int fd = -1; - struct stat st; - int r; - - assert(path); - - fd = open(path, (writable ? O_RDWR : O_RDONLY)|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW); - if (fd < 0) - return -errno; - - if (fstat(fd, &st) < 0) - return -errno; - - /* Check whether this is a /proc, /sys or /dev file or char device */ - if (!S_ISREG(st.st_mode) && !S_ISCHR(st.st_mode)) - return -EEXIST; - - r = fd; - fd = -1; - - return r; -} - -static int usbffs_address_create(const char *path) { - _cleanup_close_ int fd = -1; - struct stat st; - int r; - - assert(path); - - fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW); - if (fd < 0) - return -errno; - - if (fstat(fd, &st) < 0) - return -errno; - - /* Check whether this is a regular file (ffs endpoint)*/ - if (!S_ISREG(st.st_mode)) - return -EEXIST; - - r = fd; - fd = -1; - - return r; -} - -static int mq_address_create( - const char *path, - mode_t mq_mode, - long maxmsg, - long msgsize) { - - _cleanup_close_ int fd = -1; - struct stat st; - mode_t old_mask; - struct mq_attr _attr, *attr = NULL; - int r; - - assert(path); - - if (maxmsg > 0 && msgsize > 0) { - _attr = (struct mq_attr) { - .mq_flags = O_NONBLOCK, - .mq_maxmsg = maxmsg, - .mq_msgsize = msgsize, - }; - attr = &_attr; - } - - /* Enforce the right access mode for the mq */ - old_mask = umask(~ mq_mode); - - /* Include the original umask in our mask */ - (void) umask(~mq_mode | old_mask); - fd = mq_open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_CREAT, mq_mode, attr); - (void) umask(old_mask); - - if (fd < 0) - return -errno; - - if (fstat(fd, &st) < 0) - return -errno; - - if ((st.st_mode & 0777) != (mq_mode & ~old_mask) || - st.st_uid != getuid() || - st.st_gid != getgid()) - return -EEXIST; - - r = fd; - fd = -1; - - return r; -} - -static int socket_symlink(Socket *s) { - const char *p; - char **i; - - assert(s); - - p = socket_find_symlink_target(s); - if (!p) - return 0; - - STRV_FOREACH(i, s->symlinks) - symlink_label(p, *i); - - return 0; -} - -static int usbffs_write_descs(int fd, Service *s) { - int r; - - if (!s->usb_function_descriptors || !s->usb_function_strings) - return -EINVAL; - - r = copy_file_fd(s->usb_function_descriptors, fd, false); - if (r < 0) - return r; - - return copy_file_fd(s->usb_function_strings, fd, false); -} - -static int usbffs_select_ep(const struct dirent *d) { - return d->d_name[0] != '.' && !streq(d->d_name, "ep0"); -} - -static int usbffs_dispatch_eps(SocketPort *p) { - _cleanup_free_ struct dirent **ent = NULL; - _cleanup_free_ char *path = NULL; - int r, i, n, k; - - path = dirname_malloc(p->path); - if (!path) - return -ENOMEM; - - r = scandir(path, &ent, usbffs_select_ep, alphasort); - if (r < 0) - return -errno; - - n = r; - p->auxiliary_fds = new(int, n); - if (!p->auxiliary_fds) - return -ENOMEM; - - p->n_auxiliary_fds = n; - - k = 0; - for (i = 0; i < n; ++i) { - _cleanup_free_ char *ep = NULL; - - ep = path_make_absolute(ent[i]->d_name, path); - if (!ep) - return -ENOMEM; - - path_kill_slashes(ep); - - r = usbffs_address_create(ep); - if (r < 0) - goto fail; - - p->auxiliary_fds[k] = r; - - ++k; - free(ent[i]); - } - - return r; - -fail: - close_many(p->auxiliary_fds, k); - p->auxiliary_fds = mfree(p->auxiliary_fds); - p->n_auxiliary_fds = 0; - - return r; -} - -static int socket_open_fds(Socket *s) { - _cleanup_(mac_selinux_freep) char *label = NULL; - bool know_label = false; - SocketPort *p; - int r; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - - if (p->fd >= 0) - continue; - - switch (p->type) { - - case SOCKET_SOCKET: - - if (!know_label) { - /* Figure out label, if we don't it know - * yet. We do it once, for the first - * socket where we need this and - * remember it for the rest. */ - - if (s->selinux_context_from_net) { - /* Get it from the network label */ - - r = mac_selinux_get_our_label(&label); - if (r < 0 && r != -EOPNOTSUPP) - goto rollback; - - } else { - /* Get it from the executable we are about to start */ - - r = socket_instantiate_service(s); - if (r < 0) - goto rollback; - - if (UNIT_ISSET(s->service) && - SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]) { - r = mac_selinux_get_create_label_from_exe(SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]->path, &label); - if (r < 0 && r != -EPERM && r != -EOPNOTSUPP) - goto rollback; - } - } - - know_label = true; - } - - /* Apply the socket protocol */ - switch(p->address.type) { - case SOCK_STREAM: - case SOCK_SEQPACKET: - if (p->socket->socket_protocol == IPPROTO_SCTP) - p->address.protocol = p->socket->socket_protocol; - break; - case SOCK_DGRAM: - if (p->socket->socket_protocol == IPPROTO_UDPLITE) - p->address.protocol = p->socket->socket_protocol; - break; - } - - r = socket_address_listen( - &p->address, - SOCK_CLOEXEC|SOCK_NONBLOCK, - s->backlog, - s->bind_ipv6_only, - s->bind_to_device, - s->reuse_port, - s->free_bind, - s->transparent, - s->directory_mode, - s->socket_mode, - label); - if (r < 0) - goto rollback; - - p->fd = r; - socket_apply_socket_options(s, p->fd); - socket_symlink(s); - break; - - case SOCKET_SPECIAL: - - p->fd = special_address_create(p->path, s->writable); - if (p->fd < 0) { - r = p->fd; - goto rollback; - } - break; - - case SOCKET_FIFO: - - p->fd = fifo_address_create( - p->path, - s->directory_mode, - s->socket_mode); - if (p->fd < 0) { - r = p->fd; - goto rollback; - } - - socket_apply_fifo_options(s, p->fd); - socket_symlink(s); - break; - - case SOCKET_MQUEUE: - - p->fd = mq_address_create( - p->path, - s->socket_mode, - s->mq_maxmsg, - s->mq_msgsize); - if (p->fd < 0) { - r = p->fd; - goto rollback; - } - break; - - case SOCKET_USB_FUNCTION: - - p->fd = usbffs_address_create(p->path); - if (p->fd < 0) { - r = p->fd; - goto rollback; - } - - r = usbffs_write_descs(p->fd, SERVICE(UNIT_DEREF(s->service))); - if (r < 0) - goto rollback; - - r = usbffs_dispatch_eps(p); - if (r < 0) - goto rollback; - - break; - - default: - assert_not_reached("Unknown port type"); - } - } - - return 0; - -rollback: - socket_close_fds(s); - return r; -} - -static void socket_unwatch_fds(Socket *s) { - SocketPort *p; - int r; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - if (p->fd < 0) - continue; - - if (!p->event_source) - continue; - - r = sd_event_source_set_enabled(p->event_source, SD_EVENT_OFF); - if (r < 0) - log_unit_debug_errno(UNIT(s), r, "Failed to disable event source: %m"); - } -} - -static int socket_watch_fds(Socket *s) { - SocketPort *p; - int r; - - assert(s); - - LIST_FOREACH(port, p, s->ports) { - if (p->fd < 0) - continue; - - if (p->event_source) { - r = sd_event_source_set_enabled(p->event_source, SD_EVENT_ON); - if (r < 0) - goto fail; - } else { - r = sd_event_add_io(UNIT(s)->manager->event, &p->event_source, p->fd, EPOLLIN, socket_dispatch_io, p); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(p->event_source, "socket-port-io"); - } - } - - return 0; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to watch listening fds: %m"); - socket_unwatch_fds(s); - return r; -} - -static void socket_set_state(Socket *s, SocketState state) { - SocketState old_state; - assert(s); - - old_state = s->state; - s->state = state; - - if (!IN_SET(state, - SOCKET_START_PRE, - SOCKET_START_CHOWN, - SOCKET_START_POST, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL, - SOCKET_STOP_POST, - SOCKET_FINAL_SIGTERM, - SOCKET_FINAL_SIGKILL)) { - - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - socket_unwatch_control_pid(s); - s->control_command = NULL; - s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; - } - - if (state != SOCKET_LISTENING) - socket_unwatch_fds(s); - - if (!IN_SET(state, - SOCKET_START_CHOWN, - SOCKET_START_POST, - SOCKET_LISTENING, - SOCKET_RUNNING, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL)) - socket_close_fds(s); - - if (state != old_state) - log_unit_debug(UNIT(s), "Changed %s -> %s", socket_state_to_string(old_state), socket_state_to_string(state)); - - unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); -} - -static int socket_coldplug(Unit *u) { - Socket *s = SOCKET(u); - int r; - - assert(s); - assert(s->state == SOCKET_DEAD); - - if (s->deserialized_state == s->state) - return 0; - - if (s->control_pid > 0 && - pid_is_unwaited(s->control_pid) && - IN_SET(s->deserialized_state, - SOCKET_START_PRE, - SOCKET_START_CHOWN, - SOCKET_START_POST, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL, - SOCKET_STOP_POST, - SOCKET_FINAL_SIGTERM, - SOCKET_FINAL_SIGKILL)) { - - r = unit_watch_pid(UNIT(s), s->control_pid); - if (r < 0) - return r; - - r = socket_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_usec)); - if (r < 0) - return r; - } - - if (IN_SET(s->deserialized_state, - SOCKET_START_CHOWN, - SOCKET_START_POST, - SOCKET_LISTENING, - SOCKET_RUNNING, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL)) { - - r = socket_open_fds(s); - if (r < 0) - return r; - } - - if (s->deserialized_state == SOCKET_LISTENING) { - r = socket_watch_fds(s); - if (r < 0) - return r; - } - - socket_set_state(s, s->deserialized_state); - return 0; -} - -static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { - _cleanup_free_ char **argv = NULL; - pid_t pid; - int r; - ExecParameters exec_params = { - .apply_permissions = true, - .apply_chroot = true, - .apply_tty_stdin = true, - .bus_endpoint_fd = -1, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, - }; - - assert(s); - assert(c); - assert(_pid); - - (void) unit_realize_cgroup(UNIT(s)); - if (s->reset_cpu_usage) { - (void) unit_reset_cpu_usage(UNIT(s)); - s->reset_cpu_usage = false; - } - - r = unit_setup_exec_runtime(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; - - r = unit_full_printf_strv(UNIT(s), c->argv, &argv); - if (r < 0) - return r; - - exec_params.argv = argv; - exec_params.environment = UNIT(s)->manager->environment; - exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; - exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; - exec_params.cgroup_path = UNIT(s)->cgroup_path; - exec_params.cgroup_delegate = s->cgroup_context.delegate; - exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(s)->manager); - - r = exec_spawn(UNIT(s), - c, - &s->exec_context, - &exec_params, - s->exec_runtime, - &pid); - if (r < 0) - return r; - - r = unit_watch_pid(UNIT(s), pid); - if (r < 0) - /* FIXME: we need to do something here */ - return r; - - *_pid = pid; - return 0; -} - -static int socket_chown(Socket *s, pid_t *_pid) { - pid_t pid; - int r; - - r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); - if (r < 0) - goto fail; - - /* We have to resolve the user names out-of-process, hence - * let's fork here. It's messy, but well, what can we do? */ - - pid = fork(); - if (pid < 0) - return -errno; - - if (pid == 0) { - SocketPort *p; - uid_t uid = UID_INVALID; - gid_t gid = GID_INVALID; - int ret; - - (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE, -1); - (void) ignore_signals(SIGPIPE, -1); - log_forget_fds(); - - if (!isempty(s->user)) { - const char *user = s->user; - - r = get_user_creds(&user, &uid, &gid, NULL, NULL); - if (r < 0) { - ret = EXIT_USER; - goto fail_child; - } - } - - if (!isempty(s->group)) { - const char *group = s->group; - - r = get_group_creds(&group, &gid); - if (r < 0) { - ret = EXIT_GROUP; - goto fail_child; - } - } - - LIST_FOREACH(port, p, s->ports) { - const char *path = NULL; - - if (p->type == SOCKET_SOCKET) - path = socket_address_get_path(&p->address); - else if (p->type == SOCKET_FIFO) - path = p->path; - - if (!path) - continue; - - if (chown(path, uid, gid) < 0) { - r = -errno; - ret = EXIT_CHOWN; - goto fail_child; - } - } - - _exit(0); - - fail_child: - log_open(); - log_error_errno(r, "Failed to chown socket at step %s: %m", exit_status_to_string(ret, EXIT_STATUS_SYSTEMD)); - - _exit(ret); - } - - r = unit_watch_pid(UNIT(s), pid); - if (r < 0) - goto fail; - - *_pid = pid; - return 0; - -fail: - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - return r; -} - -static void socket_enter_dead(Socket *s, SocketResult f) { - assert(s); - - if (f != SOCKET_SUCCESS) - s->result = f; - - 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); -} - -static void socket_enter_signal(Socket *s, SocketState state, SocketResult f); - -static void socket_enter_stop_post(Socket *s, SocketResult f) { - int r; - assert(s); - - if (f != SOCKET_SUCCESS) - s->result = f; - - socket_unwatch_control_pid(s); - s->control_command_id = SOCKET_EXEC_STOP_POST; - s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST]; - - if (s->control_command) { - r = socket_spawn(s, s->control_command, &s->control_pid); - if (r < 0) - goto fail; - - socket_set_state(s, SOCKET_STOP_POST); - } else - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop-post' task: %m"); - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_signal(Socket *s, SocketState state, SocketResult f) { - int r; - - assert(s); - - if (f != SOCKET_SUCCESS) - s->result = f; - - r = unit_kill_context( - UNIT(s), - &s->kill_context, - (state != SOCKET_STOP_PRE_SIGTERM && state != SOCKET_FINAL_SIGTERM) ? - KILL_KILL : KILL_TERMINATE, - -1, - s->control_pid, - false); - if (r < 0) - goto fail; - - if (r > 0) { - r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); - if (r < 0) - goto fail; - - socket_set_state(s, state); - } else if (state == SOCKET_STOP_PRE_SIGTERM) - socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, SOCKET_SUCCESS); - else if (state == SOCKET_STOP_PRE_SIGKILL) - socket_enter_stop_post(s, SOCKET_SUCCESS); - else if (state == SOCKET_FINAL_SIGTERM) - socket_enter_signal(s, SOCKET_FINAL_SIGKILL, SOCKET_SUCCESS); - else - socket_enter_dead(s, SOCKET_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); - - if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL) - socket_enter_stop_post(s, SOCKET_FAILURE_RESOURCES); - else - socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_stop_pre(Socket *s, SocketResult f) { - int r; - assert(s); - - if (f != SOCKET_SUCCESS) - s->result = f; - - socket_unwatch_control_pid(s); - s->control_command_id = SOCKET_EXEC_STOP_PRE; - s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE]; - - if (s->control_command) { - r = socket_spawn(s, s->control_command, &s->control_pid); - if (r < 0) - goto fail; - - socket_set_state(s, SOCKET_STOP_PRE); - } else - socket_enter_stop_post(s, SOCKET_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop-pre' task: %m"); - socket_enter_stop_post(s, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_listening(Socket *s) { - int r; - assert(s); - - r = socket_watch_fds(s); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to watch sockets: %m"); - goto fail; - } - - socket_set_state(s, SOCKET_LISTENING); - return; - -fail: - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_start_post(Socket *s) { - int r; - assert(s); - - socket_unwatch_control_pid(s); - s->control_command_id = SOCKET_EXEC_START_POST; - s->control_command = s->exec_command[SOCKET_EXEC_START_POST]; - - if (s->control_command) { - r = socket_spawn(s, s->control_command, &s->control_pid); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-post' task: %m"); - goto fail; - } - - socket_set_state(s, SOCKET_START_POST); - } else - socket_enter_listening(s); - - return; - -fail: - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_start_chown(Socket *s) { - int r; - - assert(s); - - r = socket_open_fds(s); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to listen on sockets: %m"); - goto fail; - } - - if (!isempty(s->user) || !isempty(s->group)) { - - socket_unwatch_control_pid(s); - s->control_command_id = SOCKET_EXEC_START_CHOWN; - s->control_command = NULL; - - r = socket_chown(s, &s->control_pid); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to fork 'start-chown' task: %m"); - goto fail; - } - - socket_set_state(s, SOCKET_START_CHOWN); - } else - socket_enter_start_post(s); - - return; - -fail: - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_start_pre(Socket *s) { - int r; - assert(s); - - socket_unwatch_control_pid(s); - s->control_command_id = SOCKET_EXEC_START_PRE; - s->control_command = s->exec_command[SOCKET_EXEC_START_PRE]; - - if (s->control_command) { - r = socket_spawn(s, s->control_command, &s->control_pid); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-pre' task: %m"); - goto fail; - } - - socket_set_state(s, SOCKET_START_PRE); - } else - socket_enter_start_chown(s); - - return; - -fail: - socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); -} - -static void socket_enter_running(Socket *s, int cfd) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(s); - - /* We don't take connections anymore if we are supposed to - * shut down anyway */ - if (unit_stop_pending(UNIT(s))) { - - log_unit_debug(UNIT(s), "Suppressing connection request since unit stop is scheduled."); - - if (cfd >= 0) - safe_close(cfd); - else { - /* Flush all sockets by closing and reopening them */ - socket_close_fds(s); - - r = socket_open_fds(s); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to listen on sockets: %m"); - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); - return; - } - - r = socket_watch_fds(s); - if (r < 0) { - log_unit_warning_errno(UNIT(s), r, "Failed to watch sockets: %m"); - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); - } - } - - return; - } - - if (cfd < 0) { - Iterator i; - Unit *other; - bool pending = false; - - /* If there's already a start pending don't bother to - * do anything */ - SET_FOREACH(other, UNIT(s)->dependencies[UNIT_TRIGGERS], i) - if (unit_active_or_pending(other)) { - pending = true; - break; - } - - if (!pending) { - if (!UNIT_ISSET(s->service)) { - log_unit_error(UNIT(s), "Service to activate vanished, refusing activation."); - r = -ENOENT; - goto fail; - } - - r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT_DEREF(s->service), JOB_REPLACE, &error, NULL); - if (r < 0) - goto fail; - } - - socket_set_state(s, SOCKET_RUNNING); - } else { - _cleanup_free_ char *prefix = NULL, *instance = NULL, *name = NULL; - Service *service; - - if (s->n_connections >= s->max_connections) { - log_unit_warning(UNIT(s), "Too many incoming connections (%u)", s->n_connections); - safe_close(cfd); - return; - } - - r = socket_instantiate_service(s); - if (r < 0) - goto fail; - - r = instance_from_socket(cfd, s->n_accepted, &instance); - if (r < 0) { - if (r != -ENOTCONN) - goto fail; - - /* ENOTCONN is legitimate if TCP RST was received. - * This connection is over, but the socket unit lives on. */ - safe_close(cfd); - return; - } - - r = unit_name_to_prefix(UNIT(s)->id, &prefix); - if (r < 0) - goto fail; - - r = unit_name_build(prefix, instance, ".service", &name); - if (r < 0) - goto fail; - - r = unit_add_name(UNIT_DEREF(s->service), name); - if (r < 0) - goto fail; - - service = SERVICE(UNIT_DEREF(s->service)); - unit_ref_unset(&s->service); - s->n_accepted ++; - - UNIT(service)->no_gc = false; - - unit_choose_id(UNIT(service), name); - - r = service_set_socket_fd(service, cfd, s, s->selinux_context_from_net); - if (r < 0) - goto fail; - - cfd = -1; - s->n_connections ++; - - r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, &error, NULL); - if (r < 0) - goto fail; - - /* Notify clients about changed counters */ - unit_add_to_dbus_queue(UNIT(s)); - } - - return; - -fail: - log_unit_warning(UNIT(s), "Failed to queue service startup job (Maybe the service file is missing or not a %s unit?): %s", - cfd >= 0 ? "template" : "non-template", - bus_error_message(&error, r)); - - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); - safe_close(cfd); -} - -static void socket_run_next(Socket *s) { - int r; - - assert(s); - assert(s->control_command); - assert(s->control_command->command_next); - - socket_unwatch_control_pid(s); - - s->control_command = s->control_command->command_next; - - r = socket_spawn(s, s->control_command, &s->control_pid); - if (r < 0) - goto fail; - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run next task: %m"); - - if (s->state == SOCKET_START_POST) - socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); - else if (s->state == SOCKET_STOP_POST) - socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); - else - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); -} - -static int socket_start(Unit *u) { - Socket *s = SOCKET(u); - - assert(s); - - /* We cannot fulfill this request right now, try again later - * please! */ - if (IN_SET(s->state, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGKILL, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_POST, - SOCKET_FINAL_SIGTERM, - SOCKET_FINAL_SIGKILL)) - return -EAGAIN; - - /* Already on it! */ - if (IN_SET(s->state, - SOCKET_START_PRE, - SOCKET_START_CHOWN, - SOCKET_START_POST)) - return 0; - - /* Cannot run this without the service being around */ - if (UNIT_ISSET(s->service)) { - Service *service; - - service = SERVICE(UNIT_DEREF(s->service)); - - if (UNIT(service)->load_state != UNIT_LOADED) { - log_unit_error(u, "Socket service %s not loaded, refusing.", UNIT(service)->id); - return -ENOENT; - } - - /* If the service is already active we cannot start the - * socket */ - if (service->state != SERVICE_DEAD && - service->state != SERVICE_FAILED && - service->state != SERVICE_AUTO_RESTART) { - log_unit_error(u, "Socket service %s already active, refusing.", UNIT(service)->id); - return -EBUSY; - } - } - - assert(s->state == SOCKET_DEAD || s->state == SOCKET_FAILED); - - s->result = SOCKET_SUCCESS; - s->reset_cpu_usage = true; - - socket_enter_start_pre(s); - - return 1; -} - -static int socket_stop(Unit *u) { - Socket *s = SOCKET(u); - - assert(s); - - /* Already on it */ - if (IN_SET(s->state, - SOCKET_STOP_PRE, - SOCKET_STOP_PRE_SIGTERM, - SOCKET_STOP_PRE_SIGKILL, - SOCKET_STOP_POST, - SOCKET_FINAL_SIGTERM, - SOCKET_FINAL_SIGKILL)) - return 0; - - /* If there's already something running we go directly into - * kill mode. */ - if (IN_SET(s->state, - SOCKET_START_PRE, - SOCKET_START_CHOWN, - SOCKET_START_POST)) { - socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_SUCCESS); - return -EAGAIN; - } - - assert(s->state == SOCKET_LISTENING || s->state == SOCKET_RUNNING); - - socket_enter_stop_pre(s, SOCKET_SUCCESS); - return 1; -} - -static int socket_serialize(Unit *u, FILE *f, FDSet *fds) { - Socket *s = SOCKET(u); - SocketPort *p; - int r; - - assert(u); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", socket_state_to_string(s->state)); - unit_serialize_item(u, f, "result", socket_result_to_string(s->result)); - unit_serialize_item_format(u, f, "n-accepted", "%u", s->n_accepted); - - if (s->control_pid > 0) - unit_serialize_item_format(u, f, "control-pid", PID_FMT, s->control_pid); - - if (s->control_command_id >= 0) - unit_serialize_item(u, f, "control-command", socket_exec_command_to_string(s->control_command_id)); - - LIST_FOREACH(port, p, s->ports) { - int copy; - - if (p->fd < 0) - continue; - - copy = fdset_put_dup(fds, p->fd); - if (copy < 0) - return copy; - - if (p->type == SOCKET_SOCKET) { - _cleanup_free_ char *t = NULL; - - r = socket_address_print(&p->address, &t); - if (r < 0) - return r; - - if (socket_address_family(&p->address) == AF_NETLINK) - unit_serialize_item_format(u, f, "netlink", "%i %s", copy, t); - else - unit_serialize_item_format(u, f, "socket", "%i %i %s", copy, p->address.type, t); - - } else if (p->type == SOCKET_SPECIAL) - unit_serialize_item_format(u, f, "special", "%i %s", copy, p->path); - else if (p->type == SOCKET_MQUEUE) - unit_serialize_item_format(u, f, "mqueue", "%i %s", copy, p->path); - else if (p->type == SOCKET_USB_FUNCTION) - unit_serialize_item_format(u, f, "ffs", "%i %s", copy, p->path); - else { - assert(p->type == SOCKET_FIFO); - unit_serialize_item_format(u, f, "fifo", "%i %s", copy, p->path); - } - } - - return 0; -} - -static int socket_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Socket *s = SOCKET(u); - - assert(u); - assert(key); - assert(value); - - if (streq(key, "state")) { - SocketState state; - - state = socket_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - s->deserialized_state = state; - } else if (streq(key, "result")) { - SocketResult f; - - f = socket_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != SOCKET_SUCCESS) - s->result = f; - - } else if (streq(key, "n-accepted")) { - unsigned k; - - if (safe_atou(value, &k) < 0) - log_unit_debug(u, "Failed to parse n-accepted value: %s", value); - else - s->n_accepted += k; - } else if (streq(key, "control-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse control-pid value: %s", value); - else - s->control_pid = pid; - } else if (streq(key, "control-command")) { - SocketExecCommand id; - - id = socket_exec_command_from_string(value); - if (id < 0) - log_unit_debug(u, "Failed to parse exec-command value: %s", value); - else { - s->control_command_id = id; - s->control_command = s->exec_command[id]; - } - } else if (streq(key, "fifo")) { - int fd, skip = 0; - SocketPort *p; - - if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse fifo value: %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (p->type == SOCKET_FIFO && - path_equal_or_files_same(p->path, value+skip)) - break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } - - } else if (streq(key, "special")) { - int fd, skip = 0; - SocketPort *p; - - if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse special value: %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (p->type == SOCKET_SPECIAL && - path_equal_or_files_same(p->path, value+skip)) - break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } - - } else if (streq(key, "mqueue")) { - int fd, skip = 0; - SocketPort *p; - - if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse mqueue value: %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (p->type == SOCKET_MQUEUE && - streq(p->path, value+skip)) - break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } - - } else if (streq(key, "socket")) { - int fd, type, skip = 0; - SocketPort *p; - - if (sscanf(value, "%i %i %n", &fd, &type, &skip) < 2 || fd < 0 || type < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse socket value: %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (socket_address_is(&p->address, value+skip, type)) - break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } - - } else if (streq(key, "netlink")) { - int fd, skip = 0; - SocketPort *p; - - if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse socket value: %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (socket_address_is_netlink(&p->address, value+skip)) - break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } - - } else if (streq(key, "ffs")) { - int fd, skip = 0; - SocketPort *p; - - if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) - log_unit_debug(u, "Failed to parse ffs value: %s", value); - else { - - LIST_FOREACH(port, p, s->ports) - if (p->type == SOCKET_USB_FUNCTION && - path_equal_or_files_same(p->path, value+skip)) - break; - - if (p) { - safe_close(p->fd); - p->fd = fdset_remove(fds, fd); - } - } - - } else - log_unit_debug(UNIT(s), "Unknown serialization key: %s", key); - - return 0; -} - -static void socket_distribute_fds(Unit *u, FDSet *fds) { - Socket *s = SOCKET(u); - SocketPort *p; - - assert(u); - - LIST_FOREACH(port, p, s->ports) { - Iterator i; - int fd; - - if (p->type != SOCKET_SOCKET) - continue; - - if (p->fd >= 0) - continue; - - FDSET_FOREACH(fd, fds, i) { - if (socket_address_matches_fd(&p->address, fd)) { - p->fd = fdset_remove(fds, fd); - s->deserialized_state = SOCKET_LISTENING; - break; - } - } - } -} - -_pure_ static UnitActiveState socket_active_state(Unit *u) { - assert(u); - - return state_translation_table[SOCKET(u)->state]; -} - -_pure_ static const char *socket_sub_state_to_string(Unit *u) { - assert(u); - - return socket_state_to_string(SOCKET(u)->state); -} - -const char* socket_port_type_to_string(SocketPort *p) { - - assert(p); - - switch (p->type) { - - case SOCKET_SOCKET: - - switch (p->address.type) { - - case SOCK_STREAM: - return "Stream"; - - case SOCK_DGRAM: - return "Datagram"; - - case SOCK_SEQPACKET: - return "SequentialPacket"; - - case SOCK_RAW: - if (socket_address_family(&p->address) == AF_NETLINK) - return "Netlink"; - - default: - return NULL; - } - - case SOCKET_SPECIAL: - return "Special"; - - case SOCKET_MQUEUE: - return "MessageQueue"; - - case SOCKET_FIFO: - return "FIFO"; - - case SOCKET_USB_FUNCTION: - return "USBFunction"; - - default: - return NULL; - } -} - -_pure_ static bool socket_check_gc(Unit *u) { - Socket *s = SOCKET(u); - - assert(u); - - return s->n_connections > 0; -} - -static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - SocketPort *p = userdata; - int cfd = -1; - - assert(p); - assert(fd >= 0); - - if (p->socket->state != SOCKET_LISTENING) - return 0; - - log_unit_debug(UNIT(p->socket), "Incoming traffic"); - - if (revents != EPOLLIN) { - - if (revents & EPOLLHUP) - log_unit_error(UNIT(p->socket), "Got POLLHUP on a listening socket. The service probably invoked shutdown() on it, and should better not do that."); - else - log_unit_error(UNIT(p->socket), "Got unexpected poll event (0x%x) on socket.", revents); - goto fail; - } - - if (p->socket->accept && - p->type == SOCKET_SOCKET && - socket_address_can_accept(&p->address)) { - - for (;;) { - - cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK); - if (cfd < 0) { - - if (errno == EINTR) - continue; - - log_unit_error_errno(UNIT(p->socket), errno, "Failed to accept socket: %m"); - goto fail; - } - - break; - } - - socket_apply_socket_options(p->socket, cfd); - } - - socket_enter_running(p->socket, cfd); - return 0; - -fail: - socket_enter_stop_pre(p->socket, SOCKET_FAILURE_RESOURCES); - return 0; -} - -static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) { - Socket *s = SOCKET(u); - SocketResult f; - - assert(s); - assert(pid >= 0); - - if (pid != s->control_pid) - return; - - s->control_pid = 0; - - if (is_clean_exit(code, status, NULL)) - f = SOCKET_SUCCESS; - else if (code == CLD_EXITED) - f = SOCKET_FAILURE_EXIT_CODE; - else if (code == CLD_KILLED) - f = SOCKET_FAILURE_SIGNAL; - else if (code == CLD_DUMPED) - f = SOCKET_FAILURE_CORE_DUMP; - else - assert_not_reached("Unknown sigchld code"); - - if (s->control_command) { - exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); - - if (s->control_command->ignore) - f = SOCKET_SUCCESS; - } - - log_unit_full(u, f == SOCKET_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, - "Control process exited, code=%s status=%i", - sigchld_code_to_string(code), status); - - if (f != SOCKET_SUCCESS) - s->result = f; - - if (s->control_command && - s->control_command->command_next && - f == SOCKET_SUCCESS) { - - log_unit_debug(u, "Running next command for state %s", socket_state_to_string(s->state)); - socket_run_next(s); - } else { - s->control_command = NULL; - s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; - - /* No further commands for this step, so let's figure - * out what to do next */ - - log_unit_debug(u, "Got final SIGCHLD for state %s", socket_state_to_string(s->state)); - - switch (s->state) { - - case SOCKET_START_PRE: - if (f == SOCKET_SUCCESS) - socket_enter_start_chown(s); - else - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, f); - break; - - case SOCKET_START_CHOWN: - if (f == SOCKET_SUCCESS) - socket_enter_start_post(s); - else - socket_enter_stop_pre(s, f); - break; - - case SOCKET_START_POST: - if (f == SOCKET_SUCCESS) - socket_enter_listening(s); - else - socket_enter_stop_pre(s, f); - break; - - case SOCKET_STOP_PRE: - case SOCKET_STOP_PRE_SIGTERM: - case SOCKET_STOP_PRE_SIGKILL: - socket_enter_stop_post(s, f); - break; - - case SOCKET_STOP_POST: - case SOCKET_FINAL_SIGTERM: - case SOCKET_FINAL_SIGKILL: - socket_enter_dead(s, f); - break; - - default: - assert_not_reached("Uh, control process died at wrong time."); - } - } - - /* Notify clients about changed exit status */ - unit_add_to_dbus_queue(u); -} - -static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Socket *s = SOCKET(userdata); - - assert(s); - assert(s->timer_event_source == source); - - switch (s->state) { - - case SOCKET_START_PRE: - log_unit_warning(UNIT(s), "Starting timed out. Terminating."); - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_START_CHOWN: - case SOCKET_START_POST: - log_unit_warning(UNIT(s), "Starting timed out. Stopping."); - socket_enter_stop_pre(s, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_STOP_PRE: - log_unit_warning(UNIT(s), "Stopping timed out. Terminating."); - socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_STOP_PRE_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "Stopping timed out. Killing."); - socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, SOCKET_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "Stopping timed out. Skipping SIGKILL. Ignoring."); - socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); - } - break; - - case SOCKET_STOP_PRE_SIGKILL: - log_unit_warning(UNIT(s), "Processes still around after SIGKILL. Ignoring."); - socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_STOP_POST: - log_unit_warning(UNIT(s), "Stopping timed out (2). Terminating."); - socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); - break; - - case SOCKET_FINAL_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "Stopping timed out (2). Killing."); - socket_enter_signal(s, SOCKET_FINAL_SIGKILL, SOCKET_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "Stopping timed out (2). Skipping SIGKILL. Ignoring."); - socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); - } - break; - - case SOCKET_FINAL_SIGKILL: - log_unit_warning(UNIT(s), "Still around after SIGKILL (2). Entering failed mode."); - socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } - - return 0; -} - -int socket_collect_fds(Socket *s, int **fds) { - int *rfds, k = 0, n = 0; - SocketPort *p; - - assert(s); - assert(fds); - - /* Called from the service code for requesting our fds */ - - LIST_FOREACH(port, p, s->ports) { - if (p->fd >= 0) - n++; - n += p->n_auxiliary_fds; - } - - if (n <= 0) { - *fds = NULL; - return 0; - } - - rfds = new(int, n); - if (!rfds) - return -ENOMEM; - - LIST_FOREACH(port, p, s->ports) { - int i; - - if (p->fd >= 0) - rfds[k++] = p->fd; - for (i = 0; i < p->n_auxiliary_fds; ++i) - rfds[k++] = p->auxiliary_fds[i]; - } - - assert(k == n); - - *fds = rfds; - return n; -} - -static void socket_reset_failed(Unit *u) { - Socket *s = SOCKET(u); - - assert(s); - - if (s->state == SOCKET_FAILED) - socket_set_state(s, SOCKET_DEAD); - - s->result = SOCKET_SUCCESS; -} - -void socket_connection_unref(Socket *s) { - assert(s); - - /* The service is dead. Yay! - * - * This is strictly for one-instance-per-connection - * services. */ - - assert(s->n_connections > 0); - s->n_connections--; - - log_unit_debug(UNIT(s), "One connection closed, %u left.", s->n_connections); -} - -static void socket_trigger_notify(Unit *u, Unit *other) { - Socket *s = SOCKET(u); - - assert(u); - assert(other); - - /* Don't propagate state changes from the service if we are - already down or accepting connections */ - if (!IN_SET(s->state, SOCKET_RUNNING, SOCKET_LISTENING) || s->accept) - return; - - if (other->start_limit_hit) { - socket_enter_stop_pre(s, SOCKET_FAILURE_SERVICE_START_LIMIT_HIT); - return; - } - - if (other->load_state != UNIT_LOADED || other->type != UNIT_SERVICE) - return; - - if (IN_SET(SERVICE(other)->state, - SERVICE_DEAD, SERVICE_FAILED, - SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, - SERVICE_AUTO_RESTART)) - socket_enter_listening(s); - - if (SERVICE(other)->state == SERVICE_RUNNING) - socket_set_state(s, SOCKET_RUNNING); -} - -static int socket_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - return unit_kill_common(u, who, signo, -1, SOCKET(u)->control_pid, error); -} - -static int socket_get_timeout(Unit *u, usec_t *timeout) { - Socket *s = SOCKET(u); - usec_t t; - int r; - - if (!s->timer_event_source) - return 0; - - r = sd_event_source_get_time(s->timer_event_source, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) - return 0; - - *timeout = t; - return 1; -} - -char *socket_fdname(Socket *s) { - assert(s); - - /* Returns the name to use for $LISTEN_NAMES. If the user - * didn't specify anything specifically, use the socket unit's - * name as fallback. */ - - if (s->fdname) - return s->fdname; - - return UNIT(s)->id; -} - -static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = { - [SOCKET_EXEC_START_PRE] = "StartPre", - [SOCKET_EXEC_START_CHOWN] = "StartChown", - [SOCKET_EXEC_START_POST] = "StartPost", - [SOCKET_EXEC_STOP_PRE] = "StopPre", - [SOCKET_EXEC_STOP_POST] = "StopPost" -}; - -DEFINE_STRING_TABLE_LOOKUP(socket_exec_command, SocketExecCommand); - -static const char* const socket_result_table[_SOCKET_RESULT_MAX] = { - [SOCKET_SUCCESS] = "success", - [SOCKET_FAILURE_RESOURCES] = "resources", - [SOCKET_FAILURE_TIMEOUT] = "timeout", - [SOCKET_FAILURE_EXIT_CODE] = "exit-code", - [SOCKET_FAILURE_SIGNAL] = "signal", - [SOCKET_FAILURE_CORE_DUMP] = "core-dump", - [SOCKET_FAILURE_SERVICE_START_LIMIT_HIT] = "service-start-limit-hit" -}; - -DEFINE_STRING_TABLE_LOOKUP(socket_result, SocketResult); - -const UnitVTable socket_vtable = { - .object_size = sizeof(Socket), - .exec_context_offset = offsetof(Socket, exec_context), - .cgroup_context_offset = offsetof(Socket, cgroup_context), - .kill_context_offset = offsetof(Socket, kill_context), - .exec_runtime_offset = offsetof(Socket, exec_runtime), - - .sections = - "Unit\0" - "Socket\0" - "Install\0", - .private_section = "Socket", - - .init = socket_init, - .done = socket_done, - .load = socket_load, - - .coldplug = socket_coldplug, - - .dump = socket_dump, - - .start = socket_start, - .stop = socket_stop, - - .kill = socket_kill, - - .get_timeout = socket_get_timeout, - - .serialize = socket_serialize, - .deserialize_item = socket_deserialize_item, - .distribute_fds = socket_distribute_fds, - - .active_state = socket_active_state, - .sub_state_to_string = socket_sub_state_to_string, - - .check_gc = socket_check_gc, - - .sigchld_event = socket_sigchld_event, - - .trigger_notify = socket_trigger_notify, - - .reset_failed = socket_reset_failed, - - .bus_vtable = bus_socket_vtable, - .bus_set_property = bus_socket_set_property, - .bus_commit_properties = bus_socket_commit_properties, - - .status_message_formats = { - /*.starting_stopping = { - [0] = "Starting socket %s...", - [1] = "Stopping socket %s...", - },*/ - .finished_start_job = { - [JOB_DONE] = "Listening on %s.", - [JOB_FAILED] = "Failed to listen on %s.", - [JOB_TIMEOUT] = "Timed out starting %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Closed %s.", - [JOB_FAILED] = "Failed stopping %s.", - [JOB_TIMEOUT] = "Timed out stopping %s.", - }, - }, -}; diff --git a/src/core/socket.h b/src/core/socket.h deleted file mode 100644 index b537b026a7..0000000000 --- a/src/core/socket.h +++ /dev/null @@ -1,181 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Socket Socket; - -#include "mount.h" -#include "service.h" -#include "socket-util.h" - -typedef enum SocketExecCommand { - SOCKET_EXEC_START_PRE, - SOCKET_EXEC_START_CHOWN, - SOCKET_EXEC_START_POST, - SOCKET_EXEC_STOP_PRE, - SOCKET_EXEC_STOP_POST, - _SOCKET_EXEC_COMMAND_MAX, - _SOCKET_EXEC_COMMAND_INVALID = -1 -} SocketExecCommand; - -typedef enum SocketType { - SOCKET_SOCKET, - SOCKET_FIFO, - SOCKET_SPECIAL, - SOCKET_MQUEUE, - SOCKET_USB_FUNCTION, - _SOCKET_FIFO_MAX, - _SOCKET_FIFO_INVALID = -1 -} SocketType; - -typedef enum SocketResult { - SOCKET_SUCCESS, - SOCKET_FAILURE_RESOURCES, - SOCKET_FAILURE_TIMEOUT, - SOCKET_FAILURE_EXIT_CODE, - SOCKET_FAILURE_SIGNAL, - SOCKET_FAILURE_CORE_DUMP, - SOCKET_FAILURE_SERVICE_START_LIMIT_HIT, - _SOCKET_RESULT_MAX, - _SOCKET_RESULT_INVALID = -1 -} SocketResult; - -typedef struct SocketPort { - Socket *socket; - - SocketType type; - int fd; - int *auxiliary_fds; - int n_auxiliary_fds; - - SocketAddress address; - char *path; - sd_event_source *event_source; - - LIST_FIELDS(struct SocketPort, port); -} SocketPort; - -struct Socket { - Unit meta; - - LIST_HEAD(SocketPort, ports); - - unsigned n_accepted; - unsigned n_connections; - unsigned max_connections; - - unsigned backlog; - unsigned keep_alive_cnt; - usec_t timeout_usec; - usec_t keep_alive_time; - usec_t keep_alive_interval; - usec_t defer_accept; - - ExecCommand* exec_command[_SOCKET_EXEC_COMMAND_MAX]; - ExecContext exec_context; - KillContext kill_context; - CGroupContext cgroup_context; - ExecRuntime *exec_runtime; - - /* For Accept=no sockets refers to the one service we'll - activate. For Accept=yes sockets is either NULL, or filled - when the next service we spawn. */ - UnitRef service; - - SocketState state, deserialized_state; - - sd_event_source *timer_event_source; - - ExecCommand* control_command; - SocketExecCommand control_command_id; - pid_t control_pid; - - mode_t directory_mode; - mode_t socket_mode; - - SocketResult result; - - char **symlinks; - - bool accept; - bool remove_on_stop; - bool writable; - - int socket_protocol; - - /* Socket options */ - bool keep_alive; - bool no_delay; - bool free_bind; - bool transparent; - bool broadcast; - bool pass_cred; - bool pass_sec; - - /* Only for INET6 sockets: issue IPV6_V6ONLY sockopt */ - SocketAddressBindIPv6Only bind_ipv6_only; - - int priority; - int mark; - size_t receive_buffer; - size_t send_buffer; - int ip_tos; - int ip_ttl; - size_t pipe_size; - char *bind_to_device; - char *tcp_congestion; - bool reuse_port; - long mq_maxmsg; - long mq_msgsize; - - char *smack; - char *smack_ip_in; - char *smack_ip_out; - - bool selinux_context_from_net; - - char *user, *group; - - bool reset_cpu_usage:1; - - char *fdname; -}; - -/* Called from the service code when collecting fds */ -int socket_collect_fds(Socket *s, int **fds); - -/* Called from the service code when a per-connection service ended */ -void socket_connection_unref(Socket *s); - -void socket_free_ports(Socket *s); - -int socket_instantiate_service(Socket *s); - -char *socket_fdname(Socket *s); - -extern const UnitVTable socket_vtable; - -const char* socket_exec_command_to_string(SocketExecCommand i) _const_; -SocketExecCommand socket_exec_command_from_string(const char *s) _pure_; - -const char* socket_result_to_string(SocketResult i) _const_; -SocketResult socket_result_from_string(const char *s) _pure_; - -const char* socket_port_type_to_string(SocketPort *p) _pure_; diff --git a/src/core/swap.c b/src/core/swap.c deleted file mode 100644 index 1bf0c0a808..0000000000 --- a/src/core/swap.c +++ /dev/null @@ -1,1518 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "libudev.h" - -#include "alloc-util.h" -#include "dbus-swap.h" -#include "escape.h" -#include "exit-status.h" -#include "fd-util.h" -#include "formats-util.h" -#include "fstab-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" -#include "swap.h" -#include "udev-util.h" -#include "unit-name.h" -#include "unit.h" -#include "virt.h" - -static const UnitActiveState state_translation_table[_SWAP_STATE_MAX] = { - [SWAP_DEAD] = UNIT_INACTIVE, - [SWAP_ACTIVATING] = UNIT_ACTIVATING, - [SWAP_ACTIVATING_DONE] = UNIT_ACTIVE, - [SWAP_ACTIVE] = UNIT_ACTIVE, - [SWAP_DEACTIVATING] = UNIT_DEACTIVATING, - [SWAP_ACTIVATING_SIGTERM] = UNIT_DEACTIVATING, - [SWAP_ACTIVATING_SIGKILL] = UNIT_DEACTIVATING, - [SWAP_DEACTIVATING_SIGTERM] = UNIT_DEACTIVATING, - [SWAP_DEACTIVATING_SIGKILL] = UNIT_DEACTIVATING, - [SWAP_FAILED] = UNIT_FAILED -}; - -static int swap_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); -static int swap_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); - -static void swap_unset_proc_swaps(Swap *s) { - assert(s); - - if (!s->from_proc_swaps) - return; - - s->parameters_proc_swaps.what = mfree(s->parameters_proc_swaps.what); - - s->from_proc_swaps = false; -} - -static int swap_set_devnode(Swap *s, const char *devnode) { - Hashmap *swaps; - Swap *first; - int r; - - assert(s); - - r = hashmap_ensure_allocated(&UNIT(s)->manager->swaps_by_devnode, &string_hash_ops); - if (r < 0) - return r; - - swaps = UNIT(s)->manager->swaps_by_devnode; - - if (s->devnode) { - first = hashmap_get(swaps, s->devnode); - - LIST_REMOVE(same_devnode, first, s); - if (first) - hashmap_replace(swaps, first->devnode, first); - else - hashmap_remove(swaps, s->devnode); - - s->devnode = mfree(s->devnode); - } - - if (devnode) { - s->devnode = strdup(devnode); - if (!s->devnode) - return -ENOMEM; - - first = hashmap_get(swaps, s->devnode); - LIST_PREPEND(same_devnode, first, s); - - return hashmap_replace(swaps, first->devnode, first); - } - - return 0; -} - -static void swap_init(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - assert(UNIT(s)->load_state == UNIT_STUB); - - s->timeout_usec = u->manager->default_timeout_start_usec; - - s->exec_context.std_output = u->manager->default_std_output; - s->exec_context.std_error = u->manager->default_std_error; - - s->parameters_proc_swaps.priority = s->parameters_fragment.priority = -1; - - s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; - - u->ignore_on_isolate = true; -} - -static void swap_unwatch_control_pid(Swap *s) { - assert(s); - - if (s->control_pid <= 0) - return; - - unit_unwatch_pid(UNIT(s), s->control_pid); - s->control_pid = 0; -} - -static void swap_done(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - - swap_unset_proc_swaps(s); - swap_set_devnode(s, NULL); - - s->what = mfree(s->what); - s->parameters_fragment.what = mfree(s->parameters_fragment.what); - s->parameters_fragment.options = mfree(s->parameters_fragment.options); - - s->exec_runtime = exec_runtime_unref(s->exec_runtime); - exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX); - s->control_command = NULL; - - swap_unwatch_control_pid(s); - - s->timer_event_source = sd_event_source_unref(s->timer_event_source); -} - -static int swap_arm_timer(Swap *s, usec_t usec) { - int r; - - assert(s); - - if (s->timer_event_source) { - r = sd_event_source_set_time(s->timer_event_source, usec); - if (r < 0) - return r; - - return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); - } - - if (usec == USEC_INFINITY) - return 0; - - r = sd_event_add_time( - UNIT(s)->manager->event, - &s->timer_event_source, - CLOCK_MONOTONIC, - usec, 0, - swap_dispatch_timer, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->timer_event_source, "swap-timer"); - - return 0; -} - -static int swap_add_device_links(Swap *s) { - assert(s); - - if (!s->what) - return 0; - - if (!s->from_fragment) - return 0; - - if (is_device_path(s->what)) - return unit_add_node_link(UNIT(s), s->what, UNIT(s)->manager->running_as == MANAGER_SYSTEM, UNIT_BINDS_TO); - else - /* File based swap devices need to be ordered after - * systemd-remount-fs.service, since they might need a - * writable file system. */ - return unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_REMOUNT_FS_SERVICE, NULL, true); -} - -static int swap_add_default_dependencies(Swap *s) { - int r; - - assert(s); - - if (!UNIT(s)->default_dependencies) - return 0; - - if (UNIT(s)->manager->running_as != MANAGER_SYSTEM) - return 0; - - if (detect_container() > 0) - return 0; - - /* swap units generated for the swap dev links are missing the - * ordering dep against the swap target. */ - r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SWAP_TARGET, NULL, true); - if (r < 0) - return r; - - return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); -} - -static int swap_verify(Swap *s) { - _cleanup_free_ char *e = NULL; - int r; - - if (UNIT(s)->load_state != UNIT_LOADED) - return 0; - - r = unit_name_from_path(s->what, ".swap", &e); - if (r < 0) - return log_unit_error_errno(UNIT(s), r, "Failed to generate unit name from path: %m"); - - if (!unit_has_name(UNIT(s), e)) { - log_unit_error(UNIT(s), "Value of What= and unit name do not match, not loading."); - return -EINVAL; - } - - if (s->exec_context.pam_name && s->kill_context.kill_mode != KILL_CONTROL_GROUP) { - log_unit_error(UNIT(s), "Unit has PAM enabled. Kill mode must be set to 'control-group'. Refusing to load."); - return -EINVAL; - } - - return 0; -} - -static int swap_load_devnode(Swap *s) { - _cleanup_udev_device_unref_ struct udev_device *d = NULL; - struct stat st; - const char *p; - - assert(s); - - if (stat(s->what, &st) < 0 || !S_ISBLK(st.st_mode)) - return 0; - - d = udev_device_new_from_devnum(UNIT(s)->manager->udev, 'b', st.st_rdev); - if (!d) - return 0; - - p = udev_device_get_devnode(d); - if (!p) - return 0; - - return swap_set_devnode(s, p); -} - -static int swap_load(Unit *u) { - int r; - Swap *s = SWAP(u); - - assert(s); - assert(u->load_state == UNIT_STUB); - - /* Load a .swap file */ - r = unit_load_fragment_and_dropin_optional(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - - if (UNIT(s)->fragment_path) - s->from_fragment = true; - - if (!s->what) { - if (s->parameters_fragment.what) - s->what = strdup(s->parameters_fragment.what); - else if (s->parameters_proc_swaps.what) - s->what = strdup(s->parameters_proc_swaps.what); - else { - r = unit_name_to_path(u->id, &s->what); - if (r < 0) - return r; - } - - if (!s->what) - return -ENOMEM; - } - - path_kill_slashes(s->what); - - if (!UNIT(s)->description) { - r = unit_set_description(u, s->what); - if (r < 0) - return r; - } - - r = unit_require_mounts_for(UNIT(s), s->what); - if (r < 0) - return r; - - r = swap_add_device_links(s); - if (r < 0) - return r; - - r = swap_load_devnode(s); - if (r < 0) - return r; - - r = unit_patch_contexts(u); - if (r < 0) - return r; - - r = unit_add_exec_dependencies(u, &s->exec_context); - if (r < 0) - return r; - - r = unit_set_default_slice(u); - if (r < 0) - return r; - - r = swap_add_default_dependencies(s); - if (r < 0) - return r; - } - - return swap_verify(s); -} - -static int swap_setup_unit( - Manager *m, - const char *what, - const char *what_proc_swaps, - int priority, - bool set_flags) { - - _cleanup_free_ char *e = NULL; - bool delete = false; - Unit *u = NULL; - int r; - SwapParameters *p; - - assert(m); - assert(what); - assert(what_proc_swaps); - - r = unit_name_from_path(what, ".swap", &e); - if (r < 0) - return log_unit_error_errno(u, r, "Failed to generate unit name from path: %m"); - - u = manager_get_unit(m, e); - - if (u && - SWAP(u)->from_proc_swaps && - !path_equal(SWAP(u)->parameters_proc_swaps.what, what_proc_swaps)) { - log_error("Swap %s appeared twice with different device paths %s and %s", e, SWAP(u)->parameters_proc_swaps.what, what_proc_swaps); - return -EEXIST; - } - - if (!u) { - delete = true; - - u = unit_new(m, sizeof(Swap)); - if (!u) - return log_oom(); - - r = unit_add_name(u, e); - if (r < 0) - goto fail; - - SWAP(u)->what = strdup(what); - if (!SWAP(u)->what) { - r = -ENOMEM; - goto fail; - } - - unit_add_to_load_queue(u); - } else - delete = false; - - p = &SWAP(u)->parameters_proc_swaps; - - if (!p->what) { - p->what = strdup(what_proc_swaps); - if (!p->what) { - r = -ENOMEM; - goto fail; - } - } - - if (set_flags) { - SWAP(u)->is_active = true; - SWAP(u)->just_activated = !SWAP(u)->from_proc_swaps; - } - - SWAP(u)->from_proc_swaps = true; - - p->priority = priority; - - unit_add_to_dbus_queue(u); - return 0; - -fail: - log_unit_warning_errno(u, r, "Failed to load swap unit: %m"); - - if (delete && u) - unit_free(u); - - return r; -} - -static int swap_process_new(Manager *m, const char *device, int prio, bool set_flags) { - _cleanup_udev_device_unref_ struct udev_device *d = NULL; - struct udev_list_entry *item = NULL, *first = NULL; - const char *dn; - struct stat st; - int r; - - assert(m); - - r = swap_setup_unit(m, device, device, prio, set_flags); - if (r < 0) - return r; - - /* If this is a block device, then let's add duplicates for - * all other names of this block device */ - if (stat(device, &st) < 0 || !S_ISBLK(st.st_mode)) - return 0; - - d = udev_device_new_from_devnum(m->udev, 'b', st.st_rdev); - if (!d) - return 0; - - /* Add the main device node */ - dn = udev_device_get_devnode(d); - if (dn && !streq(dn, device)) - swap_setup_unit(m, dn, device, prio, set_flags); - - /* Add additional units for all symlinks */ - first = udev_device_get_devlinks_list_entry(d); - udev_list_entry_foreach(item, first) { - const char *p; - - /* Don't bother with the /dev/block links */ - p = udev_list_entry_get_name(item); - - if (streq(p, device)) - continue; - - if (path_startswith(p, "/dev/block/")) - continue; - - if (stat(p, &st) >= 0) - if (!S_ISBLK(st.st_mode) || - st.st_rdev != udev_device_get_devnum(d)) - continue; - - swap_setup_unit(m, p, device, prio, set_flags); - } - - return r; -} - -static void swap_set_state(Swap *s, SwapState state) { - SwapState old_state; - Swap *other; - - assert(s); - - old_state = s->state; - s->state = state; - - if (state != SWAP_ACTIVATING && - state != SWAP_ACTIVATING_SIGTERM && - state != SWAP_ACTIVATING_SIGKILL && - state != SWAP_ACTIVATING_DONE && - state != SWAP_DEACTIVATING && - state != SWAP_DEACTIVATING_SIGTERM && - state != SWAP_DEACTIVATING_SIGKILL) { - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - swap_unwatch_control_pid(s); - s->control_command = NULL; - s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; - } - - if (state != old_state) - log_unit_debug(UNIT(s), "Changed %s -> %s", swap_state_to_string(old_state), swap_state_to_string(state)); - - unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); - - /* If there other units for the same device node have a job - queued it might be worth checking again if it is runnable - now. This is necessary, since swap_start() refuses - operation with EAGAIN if there's already another job for - the same device node queued. */ - LIST_FOREACH_OTHERS(same_devnode, other, s) - if (UNIT(other)->job) - job_add_to_run_queue(UNIT(other)->job); -} - -static int swap_coldplug(Unit *u) { - Swap *s = SWAP(u); - SwapState new_state = SWAP_DEAD; - int r; - - assert(s); - assert(s->state == SWAP_DEAD); - - if (s->deserialized_state != s->state) - new_state = s->deserialized_state; - else if (s->from_proc_swaps) - new_state = SWAP_ACTIVE; - - if (new_state == s->state) - return 0; - - if (s->control_pid > 0 && - pid_is_unwaited(s->control_pid) && - IN_SET(new_state, - SWAP_ACTIVATING, - SWAP_ACTIVATING_SIGTERM, - SWAP_ACTIVATING_SIGKILL, - SWAP_ACTIVATING_DONE, - SWAP_DEACTIVATING, - SWAP_DEACTIVATING_SIGTERM, - SWAP_DEACTIVATING_SIGKILL)) { - - r = unit_watch_pid(UNIT(s), s->control_pid); - if (r < 0) - return r; - - r = swap_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_usec)); - if (r < 0) - return r; - } - - swap_set_state(s, new_state); - return 0; -} - -static void swap_dump(Unit *u, FILE *f, const char *prefix) { - Swap *s = SWAP(u); - SwapParameters *p; - - assert(s); - assert(f); - - if (s->from_proc_swaps) - p = &s->parameters_proc_swaps; - else if (s->from_fragment) - p = &s->parameters_fragment; - else - p = NULL; - - fprintf(f, - "%sSwap State: %s\n" - "%sResult: %s\n" - "%sWhat: %s\n" - "%sFrom /proc/swaps: %s\n" - "%sFrom fragment: %s\n", - prefix, swap_state_to_string(s->state), - prefix, swap_result_to_string(s->result), - prefix, s->what, - prefix, yes_no(s->from_proc_swaps), - prefix, yes_no(s->from_fragment)); - - if (s->devnode) - fprintf(f, "%sDevice Node: %s\n", prefix, s->devnode); - - if (p) - fprintf(f, - "%sPriority: %i\n" - "%sOptions: %s\n", - prefix, p->priority, - prefix, strempty(p->options)); - - if (s->control_pid > 0) - fprintf(f, - "%sControl PID: "PID_FMT"\n", - prefix, s->control_pid); - - exec_context_dump(&s->exec_context, f, prefix); - kill_context_dump(&s->kill_context, f, prefix); -} - -static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { - pid_t pid; - int r; - ExecParameters exec_params = { - .apply_permissions = true, - .apply_chroot = true, - .apply_tty_stdin = true, - .bus_endpoint_fd = -1, - .stdin_fd = -1, - .stdout_fd = -1, - .stderr_fd = -1, - }; - - assert(s); - assert(c); - assert(_pid); - - (void) unit_realize_cgroup(UNIT(s)); - if (s->reset_cpu_usage) { - (void) unit_reset_cpu_usage(UNIT(s)); - s->reset_cpu_usage = false; - } - - r = unit_setup_exec_runtime(UNIT(s)); - if (r < 0) - goto fail; - - r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); - if (r < 0) - goto fail; - - exec_params.environment = UNIT(s)->manager->environment; - exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; - exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; - exec_params.cgroup_path = UNIT(s)->cgroup_path; - exec_params.cgroup_delegate = s->cgroup_context.delegate; - exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(s)->manager); - - r = exec_spawn(UNIT(s), - c, - &s->exec_context, - &exec_params, - s->exec_runtime, - &pid); - if (r < 0) - goto fail; - - r = unit_watch_pid(UNIT(s), pid); - if (r < 0) - /* FIXME: we need to do something here */ - goto fail; - - *_pid = pid; - - return 0; - -fail: - s->timer_event_source = sd_event_source_unref(s->timer_event_source); - return r; -} - -static void swap_enter_dead(Swap *s, SwapResult f) { - assert(s); - - if (f != SWAP_SUCCESS) - s->result = f; - - 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); -} - -static void swap_enter_active(Swap *s, SwapResult f) { - assert(s); - - if (f != SWAP_SUCCESS) - s->result = f; - - swap_set_state(s, SWAP_ACTIVE); -} - -static void swap_enter_signal(Swap *s, SwapState state, SwapResult f) { - int r; - - assert(s); - - if (f != SWAP_SUCCESS) - s->result = f; - - r = unit_kill_context( - UNIT(s), - &s->kill_context, - (state != SWAP_ACTIVATING_SIGTERM && state != SWAP_DEACTIVATING_SIGTERM) ? - KILL_KILL : KILL_TERMINATE, - -1, - s->control_pid, - false); - if (r < 0) - goto fail; - - if (r > 0) { - r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); - if (r < 0) - goto fail; - - swap_set_state(s, state); - } else if (state == SWAP_ACTIVATING_SIGTERM) - swap_enter_signal(s, SWAP_ACTIVATING_SIGKILL, SWAP_SUCCESS); - else if (state == SWAP_DEACTIVATING_SIGTERM) - swap_enter_signal(s, SWAP_DEACTIVATING_SIGKILL, SWAP_SUCCESS); - else - swap_enter_dead(s, SWAP_SUCCESS); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); - swap_enter_dead(s, SWAP_FAILURE_RESOURCES); -} - -static void swap_enter_activating(Swap *s) { - _cleanup_free_ char *opts = NULL; - int r; - - assert(s); - - s->control_command_id = SWAP_EXEC_ACTIVATE; - s->control_command = s->exec_command + SWAP_EXEC_ACTIVATE; - - if (s->from_fragment) { - int priority = -1; - - r = fstab_find_pri(s->parameters_fragment.options, &priority); - if (r < 0) - log_warning_errno(r, "Failed to parse swap priority \"%s\", ignoring: %m", s->parameters_fragment.options); - else if (r == 1 && s->parameters_fragment.priority >= 0) - log_warning("Duplicate swap priority configuration by Priority and Options fields."); - - if (r <= 0 && s->parameters_fragment.priority >= 0) { - if (s->parameters_fragment.options) - r = asprintf(&opts, "%s,pri=%i", s->parameters_fragment.options, s->parameters_fragment.priority); - else - r = asprintf(&opts, "pri=%i", s->parameters_fragment.priority); - if (r < 0) - goto fail; - } - } - - r = exec_command_set(s->control_command, "/sbin/swapon", NULL); - if (r < 0) - goto fail; - - if (s->parameters_fragment.options || opts) { - r = exec_command_append(s->control_command, "-o", - opts ? : s->parameters_fragment.options, NULL); - if (r < 0) - goto fail; - } - - r = exec_command_append(s->control_command, s->what, NULL); - if (r < 0) - goto fail; - - swap_unwatch_control_pid(s); - - r = swap_spawn(s, s->control_command, &s->control_pid); - if (r < 0) - goto fail; - - swap_set_state(s, SWAP_ACTIVATING); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'swapon' task: %m"); - swap_enter_dead(s, SWAP_FAILURE_RESOURCES); -} - -static void swap_enter_deactivating(Swap *s) { - int r; - - assert(s); - - s->control_command_id = SWAP_EXEC_DEACTIVATE; - s->control_command = s->exec_command + SWAP_EXEC_DEACTIVATE; - - r = exec_command_set(s->control_command, - "/sbin/swapoff", - s->what, - NULL); - if (r < 0) - goto fail; - - swap_unwatch_control_pid(s); - - r = swap_spawn(s, s->control_command, &s->control_pid); - if (r < 0) - goto fail; - - swap_set_state(s, SWAP_DEACTIVATING); - - return; - -fail: - log_unit_warning_errno(UNIT(s), r, "Failed to run 'swapoff' task: %m"); - swap_enter_active(s, SWAP_FAILURE_RESOURCES); -} - -static int swap_start(Unit *u) { - Swap *s = SWAP(u), *other; - - assert(s); - - /* We cannot fulfill this request right now, try again later - * please! */ - - if (s->state == SWAP_DEACTIVATING || - s->state == SWAP_DEACTIVATING_SIGTERM || - s->state == SWAP_DEACTIVATING_SIGKILL || - s->state == SWAP_ACTIVATING_SIGTERM || - s->state == SWAP_ACTIVATING_SIGKILL) - return -EAGAIN; - - if (s->state == SWAP_ACTIVATING) - return 0; - - assert(s->state == SWAP_DEAD || s->state == SWAP_FAILED); - - if (detect_container() > 0) - return -EPERM; - - /* If there's a job for another swap unit for the same node - * running, then let's not dispatch this one for now, and wait - * until that other job has finished. */ - LIST_FOREACH_OTHERS(same_devnode, other, s) - if (UNIT(other)->job && UNIT(other)->job->state == JOB_RUNNING) - return -EAGAIN; - - s->result = SWAP_SUCCESS; - s->reset_cpu_usage = true; - - swap_enter_activating(s); - return 1; -} - -static int swap_stop(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - - if (s->state == SWAP_DEACTIVATING || - s->state == SWAP_DEACTIVATING_SIGTERM || - s->state == SWAP_DEACTIVATING_SIGKILL || - s->state == SWAP_ACTIVATING_SIGTERM || - s->state == SWAP_ACTIVATING_SIGKILL) - return 0; - - assert(s->state == SWAP_ACTIVATING || - s->state == SWAP_ACTIVATING_DONE || - s->state == SWAP_ACTIVE); - - if (detect_container() > 0) - return -EPERM; - - swap_enter_deactivating(s); - return 1; -} - -static int swap_serialize(Unit *u, FILE *f, FDSet *fds) { - Swap *s = SWAP(u); - - assert(s); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", swap_state_to_string(s->state)); - unit_serialize_item(u, f, "result", swap_result_to_string(s->result)); - - if (s->control_pid > 0) - unit_serialize_item_format(u, f, "control-pid", PID_FMT, s->control_pid); - - if (s->control_command_id >= 0) - unit_serialize_item(u, f, "control-command", swap_exec_command_to_string(s->control_command_id)); - - return 0; -} - -static int swap_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Swap *s = SWAP(u); - - assert(s); - assert(fds); - - if (streq(key, "state")) { - SwapState state; - - state = swap_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - s->deserialized_state = state; - } else if (streq(key, "result")) { - SwapResult f; - - f = swap_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != SWAP_SUCCESS) - s->result = f; - } else if (streq(key, "control-pid")) { - pid_t pid; - - if (parse_pid(value, &pid) < 0) - log_unit_debug(u, "Failed to parse control-pid value: %s", value); - else - s->control_pid = pid; - - } else if (streq(key, "control-command")) { - SwapExecCommand id; - - id = swap_exec_command_from_string(value); - if (id < 0) - log_unit_debug(u, "Failed to parse exec-command value: %s", value); - else { - s->control_command_id = id; - s->control_command = s->exec_command + id; - } - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -_pure_ static UnitActiveState swap_active_state(Unit *u) { - assert(u); - - return state_translation_table[SWAP(u)->state]; -} - -_pure_ static const char *swap_sub_state_to_string(Unit *u) { - assert(u); - - return swap_state_to_string(SWAP(u)->state); -} - -_pure_ static bool swap_check_gc(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - - return s->from_proc_swaps; -} - -static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) { - Swap *s = SWAP(u); - SwapResult f; - - assert(s); - assert(pid >= 0); - - if (pid != s->control_pid) - return; - - s->control_pid = 0; - - if (is_clean_exit(code, status, NULL)) - f = SWAP_SUCCESS; - else if (code == CLD_EXITED) - f = SWAP_FAILURE_EXIT_CODE; - else if (code == CLD_KILLED) - f = SWAP_FAILURE_SIGNAL; - else if (code == CLD_DUMPED) - f = SWAP_FAILURE_CORE_DUMP; - else - assert_not_reached("Unknown code"); - - if (f != SWAP_SUCCESS) - s->result = f; - - if (s->control_command) { - exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); - - s->control_command = NULL; - s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; - } - - log_unit_full(u, f == SWAP_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, - "Swap process exited, code=%s status=%i", sigchld_code_to_string(code), status); - - switch (s->state) { - - case SWAP_ACTIVATING: - case SWAP_ACTIVATING_DONE: - case SWAP_ACTIVATING_SIGTERM: - case SWAP_ACTIVATING_SIGKILL: - - if (f == SWAP_SUCCESS) - swap_enter_active(s, f); - else - swap_enter_dead(s, f); - break; - - case SWAP_DEACTIVATING: - case SWAP_DEACTIVATING_SIGKILL: - case SWAP_DEACTIVATING_SIGTERM: - - swap_enter_dead(s, f); - break; - - default: - assert_not_reached("Uh, control process died at wrong time."); - } - - /* Notify clients about changed exit status */ - unit_add_to_dbus_queue(u); -} - -static int swap_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { - Swap *s = SWAP(userdata); - - assert(s); - assert(s->timer_event_source == source); - - switch (s->state) { - - case SWAP_ACTIVATING: - case SWAP_ACTIVATING_DONE: - log_unit_warning(UNIT(s), "Activation timed out. Stopping."); - swap_enter_signal(s, SWAP_ACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); - break; - - case SWAP_DEACTIVATING: - log_unit_warning(UNIT(s), "Deactivation timed out. Stopping."); - swap_enter_signal(s, SWAP_DEACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); - break; - - case SWAP_ACTIVATING_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "Activation timed out. Killing."); - swap_enter_signal(s, SWAP_ACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "Activation timed out. Skipping SIGKILL. Ignoring."); - swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); - } - break; - - case SWAP_DEACTIVATING_SIGTERM: - if (s->kill_context.send_sigkill) { - log_unit_warning(UNIT(s), "Deactivation timed out. Killing."); - swap_enter_signal(s, SWAP_DEACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); - } else { - log_unit_warning(UNIT(s), "Deactivation timed out. Skipping SIGKILL. Ignoring."); - swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); - } - break; - - case SWAP_ACTIVATING_SIGKILL: - case SWAP_DEACTIVATING_SIGKILL: - log_unit_warning(UNIT(s), "Swap process still around after SIGKILL. Ignoring."); - swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); - break; - - default: - assert_not_reached("Timeout at wrong time."); - } - - return 0; -} - -static int swap_load_proc_swaps(Manager *m, bool set_flags) { - unsigned i; - int r = 0; - - assert(m); - - rewind(m->proc_swaps); - - (void) fscanf(m->proc_swaps, "%*s %*s %*s %*s %*s\n"); - - for (i = 1;; i++) { - _cleanup_free_ char *dev = NULL, *d = NULL; - int prio = 0, k; - - k = fscanf(m->proc_swaps, - "%ms " /* device/file */ - "%*s " /* type of swap */ - "%*s " /* swap size */ - "%*s " /* used */ - "%i\n", /* priority */ - &dev, &prio); - if (k != 2) { - if (k == EOF) - break; - - log_warning("Failed to parse /proc/swaps:%u.", i); - continue; - } - - if (cunescape(dev, UNESCAPE_RELAX, &d) < 0) - return log_oom(); - - device_found_node(m, d, true, DEVICE_FOUND_SWAP, set_flags); - - k = swap_process_new(m, d, prio, set_flags); - if (k < 0) - r = k; - } - - return r; -} - -static int swap_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - Unit *u; - int r; - - assert(m); - assert(revents & EPOLLPRI); - - r = swap_load_proc_swaps(m, true); - if (r < 0) { - log_error_errno(r, "Failed to reread /proc/swaps: %m"); - - /* Reset flags, just in case, for late calls */ - LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_SWAP]) { - Swap *swap = SWAP(u); - - swap->is_active = swap->just_activated = false; - } - - return 0; - } - - manager_dispatch_load_queue(m); - - LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_SWAP]) { - Swap *swap = SWAP(u); - - if (!swap->is_active) { - /* This has just been deactivated */ - - swap_unset_proc_swaps(swap); - - switch (swap->state) { - - case SWAP_ACTIVE: - swap_enter_dead(swap, SWAP_SUCCESS); - break; - - default: - /* Fire again */ - swap_set_state(swap, swap->state); - break; - } - - if (swap->what) - device_found_node(m, swap->what, false, DEVICE_FOUND_SWAP, true); - - } else if (swap->just_activated) { - - /* New swap entry */ - - switch (swap->state) { - - case SWAP_DEAD: - case SWAP_FAILED: - swap_enter_active(swap, SWAP_SUCCESS); - break; - - case SWAP_ACTIVATING: - swap_set_state(swap, SWAP_ACTIVATING_DONE); - break; - - default: - /* Nothing really changed, but let's - * issue an notification call - * nonetheless, in case somebody is - * waiting for this. */ - swap_set_state(swap, swap->state); - break; - } - } - - /* Reset the flags for later calls */ - swap->is_active = swap->just_activated = false; - } - - return 1; -} - -static Unit *swap_following(Unit *u) { - Swap *s = SWAP(u); - Swap *other, *first = NULL; - - assert(s); - - /* If the user configured the swap through /etc/fstab or - * a device unit, follow that. */ - - if (s->from_fragment) - return NULL; - - LIST_FOREACH_OTHERS(same_devnode, other, s) - if (other->from_fragment) - return UNIT(other); - - /* Otherwise, make everybody follow the unit that's named after - * the swap device in the kernel */ - - if (streq_ptr(s->what, s->devnode)) - return NULL; - - LIST_FOREACH_AFTER(same_devnode, other, s) - if (streq_ptr(other->what, other->devnode)) - return UNIT(other); - - LIST_FOREACH_BEFORE(same_devnode, other, s) { - if (streq_ptr(other->what, other->devnode)) - return UNIT(other); - - first = other; - } - - /* Fall back to the first on the list */ - return UNIT(first); -} - -static int swap_following_set(Unit *u, Set **_set) { - Swap *s = SWAP(u), *other; - Set *set; - int r; - - assert(s); - assert(_set); - - if (LIST_JUST_US(same_devnode, s)) { - *_set = NULL; - return 0; - } - - set = set_new(NULL); - if (!set) - return -ENOMEM; - - LIST_FOREACH_OTHERS(same_devnode, other, s) { - r = set_put(set, other); - if (r < 0) - goto fail; - } - - *_set = set; - return 1; - -fail: - set_free(set); - return r; -} - -static void swap_shutdown(Manager *m) { - assert(m); - - m->swap_event_source = sd_event_source_unref(m->swap_event_source); - - m->proc_swaps = safe_fclose(m->proc_swaps); - - m->swaps_by_devnode = hashmap_free(m->swaps_by_devnode); -} - -static void swap_enumerate(Manager *m) { - int r; - - assert(m); - - if (!m->proc_swaps) { - m->proc_swaps = fopen("/proc/swaps", "re"); - if (!m->proc_swaps) { - if (errno == ENOENT) - log_debug("Not swap enabled, skipping enumeration"); - else - log_error_errno(errno, "Failed to open /proc/swaps: %m"); - - return; - } - - r = sd_event_add_io(m->event, &m->swap_event_source, fileno(m->proc_swaps), EPOLLPRI, swap_dispatch_io, m); - if (r < 0) { - log_error_errno(r, "Failed to watch /proc/swaps: %m"); - goto fail; - } - - /* Dispatch this before we dispatch SIGCHLD, so that - * we always get the events from /proc/swaps before - * the SIGCHLD of /sbin/swapon. */ - r = sd_event_source_set_priority(m->swap_event_source, -10); - if (r < 0) { - log_error_errno(r, "Failed to change /proc/swaps priority: %m"); - goto fail; - } - - (void) sd_event_source_set_description(m->swap_event_source, "swap-proc"); - } - - r = swap_load_proc_swaps(m, false); - if (r < 0) - goto fail; - - return; - -fail: - swap_shutdown(m); -} - -int swap_process_device_new(Manager *m, struct udev_device *dev) { - struct udev_list_entry *item = NULL, *first = NULL; - _cleanup_free_ char *e = NULL; - const char *dn; - Swap *s; - int r = 0; - - assert(m); - assert(dev); - - dn = udev_device_get_devnode(dev); - if (!dn) - return 0; - - r = unit_name_from_path(dn, ".swap", &e); - if (r < 0) - return r; - - s = hashmap_get(m->units, e); - if (s) - r = swap_set_devnode(s, dn); - - first = udev_device_get_devlinks_list_entry(dev); - udev_list_entry_foreach(item, first) { - _cleanup_free_ char *n = NULL; - int q; - - q = unit_name_from_path(udev_list_entry_get_name(item), ".swap", &n); - if (q < 0) - return q; - - s = hashmap_get(m->units, n); - if (s) { - q = swap_set_devnode(s, dn); - if (q < 0) - r = q; - } - } - - return r; -} - -int swap_process_device_remove(Manager *m, struct udev_device *dev) { - const char *dn; - int r = 0; - Swap *s; - - dn = udev_device_get_devnode(dev); - if (!dn) - return 0; - - while ((s = hashmap_get(m->swaps_by_devnode, dn))) { - int q; - - q = swap_set_devnode(s, NULL); - if (q < 0) - r = q; - } - - return r; -} - -static void swap_reset_failed(Unit *u) { - Swap *s = SWAP(u); - - assert(s); - - if (s->state == SWAP_FAILED) - swap_set_state(s, SWAP_DEAD); - - s->result = SWAP_SUCCESS; -} - -static int swap_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { - return unit_kill_common(u, who, signo, -1, SWAP(u)->control_pid, error); -} - -static int swap_get_timeout(Unit *u, usec_t *timeout) { - Swap *s = SWAP(u); - usec_t t; - int r; - - if (!s->timer_event_source) - return 0; - - r = sd_event_source_get_time(s->timer_event_source, &t); - if (r < 0) - return r; - if (t == USEC_INFINITY) - return 0; - - *timeout = t; - return 1; -} - -static bool swap_supported(void) { - static int supported = -1; - - /* If swap support is not available in the kernel, or we are - * running in a container we don't support swap units, and any - * attempts to starting one should fail immediately. */ - - if (supported < 0) - supported = - access("/proc/swaps", F_OK) >= 0 && - detect_container() <= 0; - - return supported; -} - -static const char* const swap_exec_command_table[_SWAP_EXEC_COMMAND_MAX] = { - [SWAP_EXEC_ACTIVATE] = "ExecActivate", - [SWAP_EXEC_DEACTIVATE] = "ExecDeactivate", -}; - -DEFINE_STRING_TABLE_LOOKUP(swap_exec_command, SwapExecCommand); - -static const char* const swap_result_table[_SWAP_RESULT_MAX] = { - [SWAP_SUCCESS] = "success", - [SWAP_FAILURE_RESOURCES] = "resources", - [SWAP_FAILURE_TIMEOUT] = "timeout", - [SWAP_FAILURE_EXIT_CODE] = "exit-code", - [SWAP_FAILURE_SIGNAL] = "signal", - [SWAP_FAILURE_CORE_DUMP] = "core-dump" -}; - -DEFINE_STRING_TABLE_LOOKUP(swap_result, SwapResult); - -const UnitVTable swap_vtable = { - .object_size = sizeof(Swap), - .exec_context_offset = offsetof(Swap, exec_context), - .cgroup_context_offset = offsetof(Swap, cgroup_context), - .kill_context_offset = offsetof(Swap, kill_context), - .exec_runtime_offset = offsetof(Swap, exec_runtime), - - .sections = - "Unit\0" - "Swap\0" - "Install\0", - .private_section = "Swap", - - .no_alias = true, - .no_instances = true, - - .init = swap_init, - .load = swap_load, - .done = swap_done, - - .coldplug = swap_coldplug, - - .dump = swap_dump, - - .start = swap_start, - .stop = swap_stop, - - .kill = swap_kill, - - .get_timeout = swap_get_timeout, - - .serialize = swap_serialize, - .deserialize_item = swap_deserialize_item, - - .active_state = swap_active_state, - .sub_state_to_string = swap_sub_state_to_string, - - .check_gc = swap_check_gc, - - .sigchld_event = swap_sigchld_event, - - .reset_failed = swap_reset_failed, - - .bus_vtable = bus_swap_vtable, - .bus_set_property = bus_swap_set_property, - .bus_commit_properties = bus_swap_commit_properties, - - .following = swap_following, - .following_set = swap_following_set, - - .enumerate = swap_enumerate, - .shutdown = swap_shutdown, - .supported = swap_supported, - - .status_message_formats = { - .starting_stopping = { - [0] = "Activating swap %s...", - [1] = "Deactivating swap %s...", - }, - .finished_start_job = { - [JOB_DONE] = "Activated swap %s.", - [JOB_FAILED] = "Failed to activate swap %s.", - [JOB_TIMEOUT] = "Timed out activating swap %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Deactivated swap %s.", - [JOB_FAILED] = "Failed deactivating swap %s.", - [JOB_TIMEOUT] = "Timed out deactivating swap %s.", - }, - }, -}; diff --git a/src/core/swap.h b/src/core/swap.h deleted file mode 100644 index ac7a63d81b..0000000000 --- a/src/core/swap.h +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2010 Maarten Lankhorst - - 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 . -***/ - -#include "libudev.h" - -typedef struct Swap Swap; - -typedef enum SwapExecCommand { - SWAP_EXEC_ACTIVATE, - SWAP_EXEC_DEACTIVATE, - _SWAP_EXEC_COMMAND_MAX, - _SWAP_EXEC_COMMAND_INVALID = -1 -} SwapExecCommand; - -typedef enum SwapResult { - SWAP_SUCCESS, - SWAP_FAILURE_RESOURCES, - SWAP_FAILURE_TIMEOUT, - SWAP_FAILURE_EXIT_CODE, - SWAP_FAILURE_SIGNAL, - SWAP_FAILURE_CORE_DUMP, - _SWAP_RESULT_MAX, - _SWAP_RESULT_INVALID = -1 -} SwapResult; - -typedef struct SwapParameters { - char *what; - char *options; - int priority; -} SwapParameters; - -struct Swap { - Unit meta; - - char *what; - - /* If the device has already shown up, this is the device - * node, which might be different from what, due to - * symlinks */ - char *devnode; - - SwapParameters parameters_proc_swaps; - SwapParameters parameters_fragment; - - bool from_proc_swaps:1; - bool from_fragment:1; - - /* Used while looking for swaps that vanished or got added - * from/to /proc/swaps */ - bool is_active:1; - bool just_activated:1; - - bool reset_cpu_usage:1; - - SwapResult result; - - usec_t timeout_usec; - - ExecCommand exec_command[_SWAP_EXEC_COMMAND_MAX]; - ExecContext exec_context; - KillContext kill_context; - CGroupContext cgroup_context; - - ExecRuntime *exec_runtime; - - SwapState state, deserialized_state; - - ExecCommand* control_command; - SwapExecCommand control_command_id; - pid_t control_pid; - - sd_event_source *timer_event_source; - - /* In order to be able to distinguish dependencies on - different device nodes we might end up creating multiple - devices for the same swap. We chain them up here. */ - - LIST_FIELDS(struct Swap, same_devnode); -}; - -extern const UnitVTable swap_vtable; - -int swap_process_device_new(Manager *m, struct udev_device *dev); -int swap_process_device_remove(Manager *m, struct udev_device *dev); - -const char* swap_exec_command_to_string(SwapExecCommand i) _const_; -SwapExecCommand swap_exec_command_from_string(const char *s) _pure_; - -const char* swap_result_to_string(SwapResult i) _const_; -SwapResult swap_result_from_string(const char *s) _pure_; diff --git a/src/core/system.conf b/src/core/system.conf deleted file mode 100644 index e2ded27333..0000000000 --- a/src/core/system.conf +++ /dev/null @@ -1,60 +0,0 @@ -# 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. -# -# Entries in this file show the compile time defaults. -# You can change settings by editing this file. -# Defaults can be restored by simply deleting this file. -# -# See systemd-system.conf(5) for details. - -[Manager] -#LogLevel=info -#LogTarget=journal-or-kmsg -#LogColor=yes -#LogLocation=no -#DumpCore=yes -#ShowStatus=yes -#CrashChangeVT=no -#CrashShell=no -#CrashReboot=no -#CPUAffinity=1 2 -#JoinControllers=cpu,cpuacct net_cls,net_prio -#RuntimeWatchdogSec=0 -#ShutdownWatchdogSec=10min -#CapabilityBoundingSet= -#SystemCallArchitectures= -#TimerSlackNSec= -#DefaultTimerAccuracySec=1min -#DefaultStandardOutput=journal -#DefaultStandardError=inherit -#DefaultTimeoutStartSec=90s -#DefaultTimeoutStopSec=90s -#DefaultRestartSec=100ms -#DefaultStartLimitInterval=10s -#DefaultStartLimitBurst=5 -#DefaultEnvironment= -#DefaultCPUAccounting=no -#DefaultBlockIOAccounting=no -#DefaultMemoryAccounting=no -#DefaultTasksAccounting=yes -#DefaultTasksMax=512 -#DefaultLimitCPU= -#DefaultLimitFSIZE= -#DefaultLimitDATA= -#DefaultLimitSTACK= -#DefaultLimitCORE= -#DefaultLimitRSS= -#DefaultLimitNOFILE= -#DefaultLimitAS= -#DefaultLimitNPROC= -#DefaultLimitMEMLOCK= -#DefaultLimitLOCKS= -#DefaultLimitSIGPENDING= -#DefaultLimitMSGQUEUE= -#DefaultLimitNICE= -#DefaultLimitRTPRIO= -#DefaultLimitRTTIME= diff --git a/src/core/systemd.pc.in b/src/core/systemd.pc.in deleted file mode 100644 index ac52b30dd3..0000000000 --- a/src/core/systemd.pc.in +++ /dev/null @@ -1,34 +0,0 @@ -# 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. - -prefix=@prefix@ -systemdutildir=@rootlibexecdir@ -systemdsystemunitdir=@systemunitdir@ -systemdsystempresetdir=@systempresetdir@ -systemduserunitdir=@userunitdir@ -systemduserpresetdir=@userpresetdir@ -systemdsystemconfdir=@pkgsysconfdir@/system -systemduserconfdir=@pkgsysconfdir@/user -systemdsystemunitpath=${systemdsystemconfdir}:/etc/systemd/system:/run/systemd/system:/usr/local/lib/systemd/system:${systemdsystemunitdir}:/usr/lib/systemd/system:/lib/systemd/system -systemduserunitpath=${systemduserconfdir}:/etc/systemd/user:/run/systemd/user:/usr/local/lib/systemd/user:/usr/local/share/systemd/user:${systemduserunitdir}:/usr/lib/systemd/user:/usr/share/systemd/user -systemdsystemgeneratordir=@systemgeneratordir@ -systemdusergeneratordir=@usergeneratordir@ -systemdsleepdir=@systemsleepdir@ -systemdshutdowndir=@systemshutdowndir@ -tmpfilesdir=@tmpfilesdir@ -sysusersdir=@sysusersdir@ -sysctldir=@sysctldir@ -binfmtdir=@binfmtdir@ -modulesloaddir=@modulesloaddir@ -catalogdir=@catalogdir@ -systemuidmax=@systemuidmax@ -systemgidmax=@systemgidmax@ - -Name: systemd -Description: systemd System and Service Manager -URL: @PACKAGE_URL@ -Version: @PACKAGE_VERSION@ diff --git a/src/core/target.c b/src/core/target.c deleted file mode 100644 index 61a91aad07..0000000000 --- a/src/core/target.c +++ /dev/null @@ -1,223 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "dbus-target.h" -#include "log.h" -#include "special.h" -#include "string-util.h" -#include "unit-name.h" -#include "unit.h" -#include "target.h" - -static const UnitActiveState state_translation_table[_TARGET_STATE_MAX] = { - [TARGET_DEAD] = UNIT_INACTIVE, - [TARGET_ACTIVE] = UNIT_ACTIVE -}; - -static void target_set_state(Target *t, TargetState state) { - TargetState old_state; - assert(t); - - old_state = t->state; - t->state = state; - - if (state != old_state) - log_debug("%s changed %s -> %s", - UNIT(t)->id, - target_state_to_string(old_state), - target_state_to_string(state)); - - unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); -} - -static int target_add_default_dependencies(Target *t) { - - static const UnitDependency deps[] = { - UNIT_REQUIRES, - UNIT_REQUISITE, - UNIT_WANTS, - UNIT_BINDS_TO, - UNIT_PART_OF - }; - - Iterator i; - Unit *other; - int r; - unsigned k; - - assert(t); - - /* Imply ordering for requirement dependencies on target - * units. Note that when the user created a contradicting - * ordering manually we won't add anything in here to make - * sure we don't create a loop. */ - - for (k = 0; k < ELEMENTSOF(deps); k++) - SET_FOREACH(other, UNIT(t)->dependencies[deps[k]], i) { - r = unit_add_default_target_dependency(other, UNIT(t)); - if (r < 0) - return r; - } - - /* Make sure targets are unloaded on shutdown */ - return unit_add_dependency_by_name(UNIT(t), UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -static int target_load(Unit *u) { - Target *t = TARGET(u); - int r; - - assert(t); - - r = unit_load_fragment_and_dropin(u); - if (r < 0) - return r; - - /* This is a new unit? Then let's add in some extras */ - if (u->load_state == UNIT_LOADED && u->default_dependencies) { - r = target_add_default_dependencies(t); - if (r < 0) - return r; - } - - return 0; -} - -static int target_coldplug(Unit *u) { - Target *t = TARGET(u); - - assert(t); - assert(t->state == TARGET_DEAD); - - if (t->deserialized_state != t->state) - target_set_state(t, t->deserialized_state); - - return 0; -} - -static void target_dump(Unit *u, FILE *f, const char *prefix) { - Target *t = TARGET(u); - - assert(t); - assert(f); - - fprintf(f, - "%sTarget State: %s\n", - prefix, target_state_to_string(t->state)); -} - -static int target_start(Unit *u) { - Target *t = TARGET(u); - - assert(t); - assert(t->state == TARGET_DEAD); - - target_set_state(t, TARGET_ACTIVE); - return 1; -} - -static int target_stop(Unit *u) { - Target *t = TARGET(u); - - assert(t); - assert(t->state == TARGET_ACTIVE); - - target_set_state(t, TARGET_DEAD); - return 1; -} - -static int target_serialize(Unit *u, FILE *f, FDSet *fds) { - Target *s = TARGET(u); - - assert(s); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", target_state_to_string(s->state)); - return 0; -} - -static int target_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Target *s = TARGET(u); - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - TargetState state; - - state = target_state_from_string(value); - if (state < 0) - log_debug("Failed to parse state value %s", value); - else - s->deserialized_state = state; - - } else - log_debug("Unknown serialization key '%s'", key); - - return 0; -} - -_pure_ static UnitActiveState target_active_state(Unit *u) { - assert(u); - - return state_translation_table[TARGET(u)->state]; -} - -_pure_ static const char *target_sub_state_to_string(Unit *u) { - assert(u); - - return target_state_to_string(TARGET(u)->state); -} - -const UnitVTable target_vtable = { - .object_size = sizeof(Target), - - .sections = - "Unit\0" - "Target\0" - "Install\0", - - .load = target_load, - .coldplug = target_coldplug, - - .dump = target_dump, - - .start = target_start, - .stop = target_stop, - - .serialize = target_serialize, - .deserialize_item = target_deserialize_item, - - .active_state = target_active_state, - .sub_state_to_string = target_sub_state_to_string, - - .bus_vtable = bus_target_vtable, - - .status_message_formats = { - .finished_start_job = { - [JOB_DONE] = "Reached target %s.", - }, - .finished_stop_job = { - [JOB_DONE] = "Stopped target %s.", - }, - }, -}; diff --git a/src/core/target.h b/src/core/target.h deleted file mode 100644 index 339aea154e..0000000000 --- a/src/core/target.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Target Target; - -struct Target { - Unit meta; - - TargetState state, deserialized_state; -}; - -extern const UnitVTable target_vtable; diff --git a/src/core/timer.c b/src/core/timer.c deleted file mode 100644 index 3d0bae16e5..0000000000 --- a/src/core/timer.c +++ /dev/null @@ -1,851 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "dbus-timer.h" -#include "fs-util.h" -#include "parse-util.h" -#include "random-util.h" -#include "special.h" -#include "string-table.h" -#include "string-util.h" -#include "timer.h" -#include "unit-name.h" -#include "unit.h" -#include "user-util.h" -#include "virt.h" - -static const UnitActiveState state_translation_table[_TIMER_STATE_MAX] = { - [TIMER_DEAD] = UNIT_INACTIVE, - [TIMER_WAITING] = UNIT_ACTIVE, - [TIMER_RUNNING] = UNIT_ACTIVE, - [TIMER_ELAPSED] = UNIT_ACTIVE, - [TIMER_FAILED] = UNIT_FAILED -}; - -static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata); - -static void timer_init(Unit *u) { - Timer *t = TIMER(u); - - assert(u); - assert(u->load_state == UNIT_STUB); - - t->next_elapse_monotonic_or_boottime = USEC_INFINITY; - t->next_elapse_realtime = USEC_INFINITY; - t->accuracy_usec = u->manager->default_timer_accuracy_usec; - t->remain_after_elapse = true; -} - -void timer_free_values(Timer *t) { - TimerValue *v; - - assert(t); - - while ((v = t->values)) { - LIST_REMOVE(value, t->values, v); - calendar_spec_free(v->calendar_spec); - free(v); - } -} - -static void timer_done(Unit *u) { - Timer *t = TIMER(u); - - assert(t); - - timer_free_values(t); - - t->monotonic_event_source = sd_event_source_unref(t->monotonic_event_source); - t->realtime_event_source = sd_event_source_unref(t->realtime_event_source); - - free(t->stamp_path); -} - -static int timer_verify(Timer *t) { - assert(t); - - if (UNIT(t)->load_state != UNIT_LOADED) - return 0; - - if (!t->values) { - log_unit_error(UNIT(t), "Timer unit lacks value setting. Refusing."); - return -EINVAL; - } - - return 0; -} - -static int timer_add_default_dependencies(Timer *t) { - int r; - TimerValue *v; - - assert(t); - - if (!UNIT(t)->default_dependencies) - return 0; - - r = unit_add_dependency_by_name(UNIT(t), UNIT_BEFORE, SPECIAL_TIMERS_TARGET, NULL, true); - if (r < 0) - return r; - - if (UNIT(t)->manager->running_as == MANAGER_SYSTEM) { - r = unit_add_two_dependencies_by_name(UNIT(t), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); - if (r < 0) - return r; - - LIST_FOREACH(value, v, t->values) { - if (v->base == TIMER_CALENDAR) { - r = unit_add_dependency_by_name(UNIT(t), UNIT_AFTER, SPECIAL_TIME_SYNC_TARGET, NULL, true); - if (r < 0) - return r; - break; - } - } - } - - return unit_add_two_dependencies_by_name(UNIT(t), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); -} - -static int timer_setup_persistent(Timer *t) { - int r; - - assert(t); - - if (!t->persistent) - return 0; - - if (UNIT(t)->manager->running_as == MANAGER_SYSTEM) { - - r = unit_require_mounts_for(UNIT(t), "/var/lib/systemd/timers"); - if (r < 0) - return r; - - t->stamp_path = strappend("/var/lib/systemd/timers/stamp-", UNIT(t)->id); - } else { - const char *e; - - e = getenv("XDG_DATA_HOME"); - if (e) - t->stamp_path = strjoin(e, "/systemd/timers/stamp-", UNIT(t)->id, NULL); - else { - - _cleanup_free_ char *h = NULL; - - r = get_home_dir(&h); - if (r < 0) - return log_unit_error_errno(UNIT(t), r, "Failed to determine home directory: %m"); - - t->stamp_path = strjoin(h, "/.local/share/systemd/timers/stamp-", UNIT(t)->id, NULL); - } - } - - if (!t->stamp_path) - return log_oom(); - - return 0; -} - -static int timer_load(Unit *u) { - Timer *t = TIMER(u); - int r; - - assert(u); - assert(u->load_state == UNIT_STUB); - - r = unit_load_fragment_and_dropin(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_LOADED) { - - if (set_isempty(u->dependencies[UNIT_TRIGGERS])) { - Unit *x; - - r = unit_load_related_unit(u, ".service", &x); - if (r < 0) - return r; - - r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, x, true); - if (r < 0) - return r; - } - - r = timer_setup_persistent(t); - if (r < 0) - return r; - - r = timer_add_default_dependencies(t); - if (r < 0) - return r; - } - - return timer_verify(t); -} - -static void timer_dump(Unit *u, FILE *f, const char *prefix) { - char buf[FORMAT_TIMESPAN_MAX]; - Timer *t = TIMER(u); - Unit *trigger; - TimerValue *v; - - trigger = UNIT_TRIGGER(u); - - fprintf(f, - "%sTimer State: %s\n" - "%sResult: %s\n" - "%sUnit: %s\n" - "%sPersistent: %s\n" - "%sWakeSystem: %s\n" - "%sAccuracy: %s\n" - "%sRemainAfterElapse: %s\n", - prefix, timer_state_to_string(t->state), - prefix, timer_result_to_string(t->result), - prefix, trigger ? trigger->id : "n/a", - prefix, yes_no(t->persistent), - prefix, yes_no(t->wake_system), - prefix, format_timespan(buf, sizeof(buf), t->accuracy_usec, 1), - prefix, yes_no(t->remain_after_elapse)); - - LIST_FOREACH(value, v, t->values) { - - if (v->base == TIMER_CALENDAR) { - _cleanup_free_ char *p = NULL; - - calendar_spec_to_string(v->calendar_spec, &p); - - fprintf(f, - "%s%s: %s\n", - prefix, - timer_base_to_string(v->base), - strna(p)); - } else { - char timespan1[FORMAT_TIMESPAN_MAX]; - - fprintf(f, - "%s%s: %s\n", - prefix, - timer_base_to_string(v->base), - format_timespan(timespan1, sizeof(timespan1), v->value, 0)); - } - } -} - -static void timer_set_state(Timer *t, TimerState state) { - TimerState old_state; - assert(t); - - old_state = t->state; - t->state = state; - - if (state != TIMER_WAITING) { - t->monotonic_event_source = sd_event_source_unref(t->monotonic_event_source); - t->realtime_event_source = sd_event_source_unref(t->realtime_event_source); - } - - if (state != old_state) - log_unit_debug(UNIT(t), "Changed %s -> %s", timer_state_to_string(old_state), timer_state_to_string(state)); - - unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); -} - -static void timer_enter_waiting(Timer *t, bool initial); - -static int timer_coldplug(Unit *u) { - Timer *t = TIMER(u); - - assert(t); - assert(t->state == TIMER_DEAD); - - if (t->deserialized_state == t->state) - return 0; - - if (t->deserialized_state == TIMER_WAITING) - timer_enter_waiting(t, false); - else - timer_set_state(t, t->deserialized_state); - - return 0; -} - -static void timer_enter_dead(Timer *t, TimerResult f) { - assert(t); - - if (f != TIMER_SUCCESS) - t->result = f; - - timer_set_state(t, t->result != TIMER_SUCCESS ? TIMER_FAILED : TIMER_DEAD); -} - -static void timer_enter_elapsed(Timer *t, bool leave_around) { - assert(t); - - /* If a unit is marked with RemainAfterElapse=yes we leave it - * around even after it elapsed once, so that starting it - * later again does not necessarily mean immediate - * retriggering. We unconditionally leave units with - * TIMER_UNIT_ACTIVE or TIMER_UNIT_INACTIVE triggers around, - * since they might be restarted automatically at any time - * later on. */ - - if (t->remain_after_elapse || leave_around) - timer_set_state(t, TIMER_ELAPSED); - else - timer_enter_dead(t, TIMER_SUCCESS); -} - -static usec_t monotonic_to_boottime(usec_t t) { - usec_t a, b; - - if (t <= 0) - return 0; - - a = now(CLOCK_BOOTTIME); - b = now(CLOCK_MONOTONIC); - - if (t + a > b) - return t + a - b; - else - return 0; -} - -static void add_random(Timer *t, usec_t *v) { - char s[FORMAT_TIMESPAN_MAX]; - usec_t add; - - assert(t); - assert(v); - - if (t->random_usec == 0) - return; - if (*v == USEC_INFINITY) - return; - - add = random_u64() % t->random_usec; - - if (*v + add < *v) /* overflow */ - *v = (usec_t) -2; /* Highest possible value, that is not USEC_INFINITY */ - else - *v += add; - - log_unit_info(UNIT(t), "Adding %s random time.", format_timespan(s, sizeof(s), add, 0)); -} - -static void timer_enter_waiting(Timer *t, bool initial) { - bool found_monotonic = false, found_realtime = false; - usec_t ts_realtime, ts_monotonic; - usec_t base = 0; - bool leave_around = false; - TimerValue *v; - Unit *trigger; - int r; - - assert(t); - - trigger = UNIT_TRIGGER(UNIT(t)); - if (!trigger) { - log_unit_error(UNIT(t), "Unit to trigger vanished."); - timer_enter_dead(t, TIMER_FAILURE_RESOURCES); - return; - } - - /* If we shall wake the system we use the boottime clock - * rather than the monotonic clock. */ - - ts_realtime = now(CLOCK_REALTIME); - ts_monotonic = now(t->wake_system ? CLOCK_BOOTTIME : CLOCK_MONOTONIC); - t->next_elapse_monotonic_or_boottime = t->next_elapse_realtime = 0; - - LIST_FOREACH(value, v, t->values) { - - if (v->disabled) - continue; - - if (v->base == TIMER_CALENDAR) { - usec_t b; - - /* If we know the last time this was - * triggered, schedule the job based relative - * to that. If we don't just start from - * now. */ - - b = t->last_trigger.realtime > 0 ? t->last_trigger.realtime : ts_realtime; - - r = calendar_spec_next_usec(v->calendar_spec, b, &v->next_elapse); - if (r < 0) - continue; - - if (!found_realtime) - t->next_elapse_realtime = v->next_elapse; - else - t->next_elapse_realtime = MIN(t->next_elapse_realtime, v->next_elapse); - - found_realtime = true; - - } else { - switch (v->base) { - - case TIMER_ACTIVE: - if (state_translation_table[t->state] == UNIT_ACTIVE) - base = UNIT(t)->inactive_exit_timestamp.monotonic; - else - base = ts_monotonic; - break; - - case TIMER_BOOT: - if (detect_container() <= 0) { - /* CLOCK_MONOTONIC equals the uptime on Linux */ - base = 0; - break; - } - /* In a container we don't want to include the time the host - * was already up when the container started, so count from - * our own startup. Fall through. */ - case TIMER_STARTUP: - base = UNIT(t)->manager->userspace_timestamp.monotonic; - break; - - case TIMER_UNIT_ACTIVE: - leave_around = true; - base = trigger->inactive_exit_timestamp.monotonic; - - if (base <= 0) - base = t->last_trigger.monotonic; - - if (base <= 0) - continue; - - break; - - case TIMER_UNIT_INACTIVE: - leave_around = true; - base = trigger->inactive_enter_timestamp.monotonic; - - if (base <= 0) - base = t->last_trigger.monotonic; - - if (base <= 0) - continue; - - break; - - default: - assert_not_reached("Unknown timer base"); - } - - if (t->wake_system) - base = monotonic_to_boottime(base); - - v->next_elapse = base + v->value; - - if (!initial && v->next_elapse < ts_monotonic && IN_SET(v->base, TIMER_ACTIVE, TIMER_BOOT, TIMER_STARTUP)) { - /* This is a one time trigger, disable it now */ - v->disabled = true; - continue; - } - - if (!found_monotonic) - t->next_elapse_monotonic_or_boottime = v->next_elapse; - else - t->next_elapse_monotonic_or_boottime = MIN(t->next_elapse_monotonic_or_boottime, v->next_elapse); - - found_monotonic = true; - } - } - - if (!found_monotonic && !found_realtime) { - log_unit_debug(UNIT(t), "Timer is elapsed."); - timer_enter_elapsed(t, leave_around); - return; - } - - if (found_monotonic) { - char buf[FORMAT_TIMESPAN_MAX]; - usec_t left; - - add_random(t, &t->next_elapse_monotonic_or_boottime); - - left = t->next_elapse_monotonic_or_boottime > ts_monotonic ? t->next_elapse_monotonic_or_boottime - ts_monotonic : 0; - log_unit_debug(UNIT(t), "Monotonic timer elapses in %s.", format_timespan(buf, sizeof(buf), left, 0)); - - if (t->monotonic_event_source) { - r = sd_event_source_set_time(t->monotonic_event_source, t->next_elapse_monotonic_or_boottime); - if (r < 0) - goto fail; - - r = sd_event_source_set_enabled(t->monotonic_event_source, SD_EVENT_ONESHOT); - if (r < 0) - goto fail; - } else { - - r = sd_event_add_time( - UNIT(t)->manager->event, - &t->monotonic_event_source, - t->wake_system ? CLOCK_BOOTTIME_ALARM : CLOCK_MONOTONIC, - t->next_elapse_monotonic_or_boottime, t->accuracy_usec, - timer_dispatch, t); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(t->monotonic_event_source, "timer-monotonic"); - } - - } else if (t->monotonic_event_source) { - - r = sd_event_source_set_enabled(t->monotonic_event_source, SD_EVENT_OFF); - if (r < 0) - goto fail; - } - - if (found_realtime) { - char buf[FORMAT_TIMESTAMP_MAX]; - - add_random(t, &t->next_elapse_realtime); - - log_unit_debug(UNIT(t), "Realtime timer elapses at %s.", format_timestamp(buf, sizeof(buf), t->next_elapse_realtime)); - - if (t->realtime_event_source) { - r = sd_event_source_set_time(t->realtime_event_source, t->next_elapse_realtime); - if (r < 0) - goto fail; - - r = sd_event_source_set_enabled(t->realtime_event_source, SD_EVENT_ONESHOT); - if (r < 0) - goto fail; - } else { - r = sd_event_add_time( - UNIT(t)->manager->event, - &t->realtime_event_source, - t->wake_system ? CLOCK_REALTIME_ALARM : CLOCK_REALTIME, - t->next_elapse_realtime, t->accuracy_usec, - timer_dispatch, t); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(t->realtime_event_source, "timer-realtime"); - } - - } else if (t->realtime_event_source) { - - r = sd_event_source_set_enabled(t->realtime_event_source, SD_EVENT_OFF); - if (r < 0) - goto fail; - } - - timer_set_state(t, TIMER_WAITING); - return; - -fail: - log_unit_warning_errno(UNIT(t), r, "Failed to enter waiting state: %m"); - timer_enter_dead(t, TIMER_FAILURE_RESOURCES); -} - -static void timer_enter_running(Timer *t) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - Unit *trigger; - int r; - - assert(t); - - /* Don't start job if we are supposed to go down */ - if (unit_stop_pending(UNIT(t))) - return; - - trigger = UNIT_TRIGGER(UNIT(t)); - if (!trigger) { - log_unit_error(UNIT(t), "Unit to trigger vanished."); - timer_enter_dead(t, TIMER_FAILURE_RESOURCES); - return; - } - - r = manager_add_job(UNIT(t)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL); - if (r < 0) - goto fail; - - dual_timestamp_get(&t->last_trigger); - - if (t->stamp_path) - touch_file(t->stamp_path, true, t->last_trigger.realtime, UID_INVALID, GID_INVALID, MODE_INVALID); - - timer_set_state(t, TIMER_RUNNING); - return; - -fail: - log_unit_warning(UNIT(t), "Failed to queue unit startup job: %s", bus_error_message(&error, r)); - timer_enter_dead(t, TIMER_FAILURE_RESOURCES); -} - -static int timer_start(Unit *u) { - Timer *t = TIMER(u); - TimerValue *v; - Unit *trigger; - - assert(t); - assert(t->state == TIMER_DEAD || t->state == TIMER_FAILED); - - trigger = UNIT_TRIGGER(u); - if (!trigger || trigger->load_state != UNIT_LOADED) { - log_unit_error(u, "Refusing to start, unit to trigger not loaded."); - return -ENOENT; - } - - t->last_trigger = DUAL_TIMESTAMP_NULL; - - /* Reenable all timers that depend on unit activation time */ - LIST_FOREACH(value, v, t->values) - if (v->base == TIMER_ACTIVE) - v->disabled = false; - - if (t->stamp_path) { - struct stat st; - - if (stat(t->stamp_path, &st) >= 0) - t->last_trigger.realtime = timespec_load(&st.st_atim); - else if (errno == ENOENT) - /* The timer has never run before, - * make sure a stamp file exists. - */ - touch_file(t->stamp_path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID); - } - - t->result = TIMER_SUCCESS; - timer_enter_waiting(t, true); - return 1; -} - -static int timer_stop(Unit *u) { - Timer *t = TIMER(u); - - assert(t); - assert(t->state == TIMER_WAITING || t->state == TIMER_RUNNING || t->state == TIMER_ELAPSED); - - timer_enter_dead(t, TIMER_SUCCESS); - return 1; -} - -static int timer_serialize(Unit *u, FILE *f, FDSet *fds) { - Timer *t = TIMER(u); - - assert(u); - assert(f); - assert(fds); - - unit_serialize_item(u, f, "state", timer_state_to_string(t->state)); - unit_serialize_item(u, f, "result", timer_result_to_string(t->result)); - - if (t->last_trigger.realtime > 0) - unit_serialize_item_format(u, f, "last-trigger-realtime", "%" PRIu64, t->last_trigger.realtime); - - if (t->last_trigger.monotonic > 0) - unit_serialize_item_format(u, f, "last-trigger-monotonic", "%" PRIu64, t->last_trigger.monotonic); - - return 0; -} - -static int timer_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { - Timer *t = TIMER(u); - int r; - - assert(u); - assert(key); - assert(value); - assert(fds); - - if (streq(key, "state")) { - TimerState state; - - state = timer_state_from_string(value); - if (state < 0) - log_unit_debug(u, "Failed to parse state value: %s", value); - else - t->deserialized_state = state; - } else if (streq(key, "result")) { - TimerResult f; - - f = timer_result_from_string(value); - if (f < 0) - log_unit_debug(u, "Failed to parse result value: %s", value); - else if (f != TIMER_SUCCESS) - t->result = f; - } else if (streq(key, "last-trigger-realtime")) { - - r = safe_atou64(value, &t->last_trigger.realtime); - if (r < 0) - log_unit_debug(u, "Failed to parse last-trigger-realtime value: %s", value); - - } else if (streq(key, "last-trigger-monotonic")) { - - r = safe_atou64(value, &t->last_trigger.monotonic); - if (r < 0) - log_unit_debug(u, "Failed to parse last-trigger-monotonic value: %s", value); - - } else - log_unit_debug(u, "Unknown serialization key: %s", key); - - return 0; -} - -_pure_ static UnitActiveState timer_active_state(Unit *u) { - assert(u); - - return state_translation_table[TIMER(u)->state]; -} - -_pure_ static const char *timer_sub_state_to_string(Unit *u) { - assert(u); - - return timer_state_to_string(TIMER(u)->state); -} - -static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata) { - Timer *t = TIMER(userdata); - - assert(t); - - if (t->state != TIMER_WAITING) - return 0; - - log_unit_debug(UNIT(t), "Timer elapsed."); - timer_enter_running(t); - return 0; -} - -static void timer_trigger_notify(Unit *u, Unit *other) { - Timer *t = TIMER(u); - TimerValue *v; - - assert(u); - assert(other); - - if (other->load_state != UNIT_LOADED) - return; - - /* Reenable all timers that depend on unit state */ - LIST_FOREACH(value, v, t->values) - if (v->base == TIMER_UNIT_ACTIVE || - v->base == TIMER_UNIT_INACTIVE) - v->disabled = false; - - switch (t->state) { - - case TIMER_WAITING: - case TIMER_ELAPSED: - - /* Recalculate sleep time */ - timer_enter_waiting(t, false); - break; - - case TIMER_RUNNING: - - if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) { - log_unit_debug(UNIT(t), "Got notified about unit deactivation."); - timer_enter_waiting(t, false); - } - break; - - case TIMER_DEAD: - case TIMER_FAILED: - break; - - default: - assert_not_reached("Unknown timer state"); - } -} - -static void timer_reset_failed(Unit *u) { - Timer *t = TIMER(u); - - assert(t); - - if (t->state == TIMER_FAILED) - timer_set_state(t, TIMER_DEAD); - - t->result = TIMER_SUCCESS; -} - -static void timer_time_change(Unit *u) { - Timer *t = TIMER(u); - - assert(u); - - if (t->state != TIMER_WAITING) - return; - - log_unit_debug(u, "Time change, recalculating next elapse."); - timer_enter_waiting(t, false); -} - -static const char* const timer_base_table[_TIMER_BASE_MAX] = { - [TIMER_ACTIVE] = "OnActiveSec", - [TIMER_BOOT] = "OnBootSec", - [TIMER_STARTUP] = "OnStartupSec", - [TIMER_UNIT_ACTIVE] = "OnUnitActiveSec", - [TIMER_UNIT_INACTIVE] = "OnUnitInactiveSec", - [TIMER_CALENDAR] = "OnCalendar" -}; - -DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase); - -static const char* const timer_result_table[_TIMER_RESULT_MAX] = { - [TIMER_SUCCESS] = "success", - [TIMER_FAILURE_RESOURCES] = "resources" -}; - -DEFINE_STRING_TABLE_LOOKUP(timer_result, TimerResult); - -const UnitVTable timer_vtable = { - .object_size = sizeof(Timer), - - .sections = - "Unit\0" - "Timer\0" - "Install\0", - .private_section = "Timer", - - .init = timer_init, - .done = timer_done, - .load = timer_load, - - .coldplug = timer_coldplug, - - .dump = timer_dump, - - .start = timer_start, - .stop = timer_stop, - - .serialize = timer_serialize, - .deserialize_item = timer_deserialize_item, - - .active_state = timer_active_state, - .sub_state_to_string = timer_sub_state_to_string, - - .trigger_notify = timer_trigger_notify, - - .reset_failed = timer_reset_failed, - .time_change = timer_time_change, - - .bus_vtable = bus_timer_vtable, - .bus_set_property = bus_timer_set_property, - - .can_transient = true, -}; diff --git a/src/core/timer.h b/src/core/timer.h deleted file mode 100644 index 698e6da2f5..0000000000 --- a/src/core/timer.h +++ /dev/null @@ -1,88 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Timer Timer; - -#include "calendarspec.h" - -typedef enum TimerBase { - TIMER_ACTIVE, - TIMER_BOOT, - TIMER_STARTUP, - TIMER_UNIT_ACTIVE, - TIMER_UNIT_INACTIVE, - TIMER_CALENDAR, - _TIMER_BASE_MAX, - _TIMER_BASE_INVALID = -1 -} TimerBase; - -typedef struct TimerValue { - TimerBase base; - bool disabled; - - usec_t value; /* only for monotonic events */ - CalendarSpec *calendar_spec; /* only for calendar events */ - usec_t next_elapse; - - LIST_FIELDS(struct TimerValue, value); -} TimerValue; - -typedef enum TimerResult { - TIMER_SUCCESS, - TIMER_FAILURE_RESOURCES, - _TIMER_RESULT_MAX, - _TIMER_RESULT_INVALID = -1 -} TimerResult; - -struct Timer { - Unit meta; - - usec_t accuracy_usec; - usec_t random_usec; - - LIST_HEAD(TimerValue, values); - usec_t next_elapse_realtime; - usec_t next_elapse_monotonic_or_boottime; - dual_timestamp last_trigger; - - TimerState state, deserialized_state; - - sd_event_source *monotonic_event_source; - sd_event_source *realtime_event_source; - - TimerResult result; - - bool persistent; - bool wake_system; - bool remain_after_elapse; - - char *stamp_path; -}; - -void timer_free_values(Timer *t); - -extern const UnitVTable timer_vtable; - -const char *timer_base_to_string(TimerBase i) _const_; -TimerBase timer_base_from_string(const char *s) _pure_; - -const char* timer_result_to_string(TimerResult i) _const_; -TimerResult timer_result_from_string(const char *s) _pure_; diff --git a/src/core/transaction.c b/src/core/transaction.c deleted file mode 100644 index b28fc76785..0000000000 --- a/src/core/transaction.c +++ /dev/null @@ -1,1092 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "terminal-util.h" -#include "transaction.h" -#include "dbus-unit.h" - -static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies); - -static void transaction_delete_job(Transaction *tr, Job *j, bool delete_dependencies) { - assert(tr); - assert(j); - - /* Deletes one job from the transaction */ - - transaction_unlink_job(tr, j, delete_dependencies); - - job_free(j); -} - -static void transaction_delete_unit(Transaction *tr, Unit *u) { - Job *j; - - /* Deletes all jobs associated with a certain unit from the - * transaction */ - - while ((j = hashmap_get(tr->jobs, u))) - transaction_delete_job(tr, j, true); -} - -void transaction_abort(Transaction *tr) { - Job *j; - - assert(tr); - - while ((j = hashmap_first(tr->jobs))) - transaction_delete_job(tr, j, false); - - assert(hashmap_isempty(tr->jobs)); -} - -static void transaction_find_jobs_that_matter_to_anchor(Job *j, unsigned generation) { - JobDependency *l; - - /* A recursive sweep through the graph that marks all units - * that matter to the anchor job, i.e. are directly or - * indirectly a dependency of the anchor job via paths that - * are fully marked as mattering. */ - - j->matters_to_anchor = true; - j->generation = generation; - - LIST_FOREACH(subject, l, j->subject_list) { - - /* This link does not matter */ - if (!l->matters) - continue; - - /* This unit has already been marked */ - if (l->object->generation == generation) - continue; - - transaction_find_jobs_that_matter_to_anchor(l->object, generation); - } -} - -static void transaction_merge_and_delete_job(Transaction *tr, Job *j, Job *other, JobType t) { - JobDependency *l, *last; - - assert(j); - assert(other); - assert(j->unit == other->unit); - assert(!j->installed); - - /* Merges 'other' into 'j' and then deletes 'other'. */ - - j->type = t; - j->state = JOB_WAITING; - j->irreversible = j->irreversible || other->irreversible; - j->matters_to_anchor = j->matters_to_anchor || other->matters_to_anchor; - - /* Patch us in as new owner of the JobDependency objects */ - last = NULL; - LIST_FOREACH(subject, l, other->subject_list) { - assert(l->subject == other); - l->subject = j; - last = l; - } - - /* Merge both lists */ - if (last) { - last->subject_next = j->subject_list; - if (j->subject_list) - j->subject_list->subject_prev = last; - j->subject_list = other->subject_list; - } - - /* Patch us in as new owner of the JobDependency objects */ - last = NULL; - LIST_FOREACH(object, l, other->object_list) { - assert(l->object == other); - l->object = j; - last = l; - } - - /* Merge both lists */ - if (last) { - last->object_next = j->object_list; - if (j->object_list) - j->object_list->object_prev = last; - j->object_list = other->object_list; - } - - /* Kill the other job */ - other->subject_list = NULL; - other->object_list = NULL; - transaction_delete_job(tr, other, true); -} - -_pure_ static bool job_is_conflicted_by(Job *j) { - JobDependency *l; - - assert(j); - - /* Returns true if this job is pulled in by a least one - * ConflictedBy dependency. */ - - LIST_FOREACH(object, l, j->object_list) - if (l->conflicts) - return true; - - return false; -} - -static int delete_one_unmergeable_job(Transaction *tr, Job *j) { - Job *k; - - assert(j); - - /* Tries to delete one item in the linked list - * j->transaction_next->transaction_next->... that conflicts - * with another one, in an attempt to make an inconsistent - * transaction work. */ - - /* We rely here on the fact that if a merged with b does not - * merge with c, either a or b merge with c neither */ - LIST_FOREACH(transaction, j, j) - LIST_FOREACH(transaction, k, j->transaction_next) { - Job *d; - - /* Is this one mergeable? Then skip it */ - if (job_type_is_mergeable(j->type, k->type)) - continue; - - /* Ok, we found two that conflict, let's see if we can - * drop one of them */ - if (!j->matters_to_anchor && !k->matters_to_anchor) { - - /* Both jobs don't matter, so let's - * find the one that is smarter to - * remove. Let's think positive and - * rather remove stops then starts -- - * except if something is being - * stopped because it is conflicted by - * another unit in which case we - * rather remove the start. */ - - log_unit_debug(j->unit, - "Looking at job %s/%s conflicted_by=%s", - j->unit->id, job_type_to_string(j->type), - yes_no(j->type == JOB_STOP && job_is_conflicted_by(j))); - log_unit_debug(k->unit, - "Looking at job %s/%s conflicted_by=%s", - k->unit->id, job_type_to_string(k->type), - yes_no(k->type == JOB_STOP && job_is_conflicted_by(k))); - - if (j->type == JOB_STOP) { - - if (job_is_conflicted_by(j)) - d = k; - else - d = j; - - } else if (k->type == JOB_STOP) { - - if (job_is_conflicted_by(k)) - d = j; - else - d = k; - } else - d = j; - - } else if (!j->matters_to_anchor) - d = j; - else if (!k->matters_to_anchor) - d = k; - else - return -ENOEXEC; - - /* Ok, we can drop one, so let's do so. */ - log_unit_debug(d->unit, - "Fixing conflicting jobs %s/%s,%s/%s by deleting job %s/%s", - j->unit->id, job_type_to_string(j->type), - k->unit->id, job_type_to_string(k->type), - d->unit->id, job_type_to_string(d->type)); - transaction_delete_job(tr, d, true); - return 0; - } - - return -EINVAL; -} - -static int transaction_merge_jobs(Transaction *tr, sd_bus_error *e) { - Job *j; - Iterator i; - int r; - - assert(tr); - - /* First step, check whether any of the jobs for one specific - * task conflict. If so, try to drop one of them. */ - HASHMAP_FOREACH(j, tr->jobs, i) { - JobType t; - Job *k; - - t = j->type; - LIST_FOREACH(transaction, k, j->transaction_next) { - if (job_type_merge_and_collapse(&t, k->type, j->unit) >= 0) - continue; - - /* OK, we could not merge all jobs for this - * action. Let's see if we can get rid of one - * of them */ - - r = delete_one_unmergeable_job(tr, j); - if (r >= 0) - /* Ok, we managed to drop one, now - * let's ask our callers to call us - * again after garbage collecting */ - return -EAGAIN; - - /* We couldn't merge anything. Failure */ - return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_JOBS_CONFLICTING, - "Transaction contains conflicting jobs '%s' and '%s' for %s. " - "Probably contradicting requirement dependencies configured.", - job_type_to_string(t), - job_type_to_string(k->type), - k->unit->id); - } - } - - /* Second step, merge the jobs. */ - HASHMAP_FOREACH(j, tr->jobs, i) { - JobType t = j->type; - Job *k; - - /* Merge all transaction jobs for j->unit */ - LIST_FOREACH(transaction, k, j->transaction_next) - assert_se(job_type_merge_and_collapse(&t, k->type, j->unit) == 0); - - while ((k = j->transaction_next)) { - if (tr->anchor_job == k) { - transaction_merge_and_delete_job(tr, k, j, t); - j = k; - } else - transaction_merge_and_delete_job(tr, j, k, t); - } - - assert(!j->transaction_next); - assert(!j->transaction_prev); - } - - return 0; -} - -static void transaction_drop_redundant(Transaction *tr) { - Job *j; - Iterator i; - - /* Goes through the transaction and removes all jobs of the units - * whose jobs are all noops. If not all of a unit's jobs are - * redundant, they are kept. */ - - assert(tr); - -rescan: - HASHMAP_FOREACH(j, tr->jobs, i) { - Job *k; - - LIST_FOREACH(transaction, k, j) { - - if (tr->anchor_job == k || - !job_type_is_redundant(k->type, unit_active_state(k->unit)) || - (k->unit->job && job_type_is_conflicting(k->type, k->unit->job->type))) - goto next_unit; - } - - /* log_debug("Found redundant job %s/%s, dropping.", j->unit->id, job_type_to_string(j->type)); */ - transaction_delete_job(tr, j, false); - goto rescan; - next_unit:; - } -} - -_pure_ static bool unit_matters_to_anchor(Unit *u, Job *j) { - assert(u); - assert(!j->transaction_prev); - - /* Checks whether at least one of the jobs for this unit - * matters to the anchor. */ - - LIST_FOREACH(transaction, j, j) - if (j->matters_to_anchor) - return true; - - return false; -} - -static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsigned generation, sd_bus_error *e) { - Iterator i; - Unit *u; - int r; - - assert(tr); - assert(j); - assert(!j->transaction_prev); - - /* Does a recursive sweep through the ordering graph, looking - * for a cycle. If we find a cycle we try to break it. */ - - /* Have we seen this before? */ - if (j->generation == generation) { - Job *k, *delete; - - /* If the marker is NULL we have been here already and - * decided the job was loop-free from here. Hence - * shortcut things and return right-away. */ - if (!j->marker) - return 0; - - /* So, the marker is not NULL and we already have been - * here. We have a cycle. Let's try to break it. We go - * backwards in our path and try to find a suitable - * job to remove. We use the marker to find our way - * back, since smart how we are we stored our way back - * in there. */ - log_unit_warning(j->unit, - "Found ordering cycle on %s/%s", - j->unit->id, job_type_to_string(j->type)); - - 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 */ - log_unit_warning(j->unit, - "Found dependency on %s/%s", - k->unit->id, job_type_to_string(k->type)); - - if (!delete && hashmap_get(tr->jobs, k->unit) && !unit_matters_to_anchor(k->unit, k)) - /* Ok, we can drop this one, so let's - * do so. */ - delete = k; - - /* Check if this in fact was the beginning of - * the cycle */ - if (k == j) - break; - } - - - if (delete) { - /* logging for j not k here 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)); - log_unit_error(delete->unit, - "Job %s/%s deleted to break ordering cycle starting with %s/%s", - delete->unit->id, job_type_to_string(delete->type), - j->unit->id, job_type_to_string(j->type)); - unit_status_printf(delete->unit, ANSI_HIGHLIGHT_RED " SKIP " ANSI_NORMAL, - "Ordering cycle found, skipping %s"); - transaction_delete_unit(tr, delete->unit); - return -EAGAIN; - } - - log_error("Unable to break cycle"); - - return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, - "Transaction order is cyclic. See system logs for details."); - } - - /* Make the marker point to where we come from, so that we can - * find our way backwards if we want to break a cycle. We use - * a special marker for the beginning: we point to - * ourselves. */ - j->marker = from ? from : j; - j->generation = generation; - - /* We assume that the dependencies are bidirectional, and - * hence can ignore UNIT_AFTER */ - SET_FOREACH(u, j->unit->dependencies[UNIT_BEFORE], i) { - Job *o; - - /* Is there a job for this unit? */ - o = hashmap_get(tr->jobs, u); - if (!o) { - /* Ok, there is no job for this in the - * transaction, but maybe there is already one - * running? */ - o = u->job; - if (!o) - continue; - } - - r = transaction_verify_order_one(tr, o, j, generation, e); - if (r < 0) - return r; - } - - /* Ok, let's backtrack, and remember that this entry is not on - * our path anymore. */ - j->marker = NULL; - - return 0; -} - -static int transaction_verify_order(Transaction *tr, unsigned *generation, sd_bus_error *e) { - Job *j; - int r; - Iterator i; - unsigned g; - - assert(tr); - assert(generation); - - /* Check if the ordering graph is cyclic. If it is, try to fix - * that up by dropping one of the jobs. */ - - g = (*generation)++; - - HASHMAP_FOREACH(j, tr->jobs, i) { - r = transaction_verify_order_one(tr, j, NULL, g, e); - if (r < 0) - return r; - } - - return 0; -} - -static void transaction_collect_garbage(Transaction *tr) { - Iterator i; - Job *j; - - assert(tr); - - /* Drop jobs that are not required by any other job */ - -rescan: - HASHMAP_FOREACH(j, tr->jobs, i) { - if (tr->anchor_job == j || j->object_list) { - /* log_debug("Keeping job %s/%s because of %s/%s", */ - /* j->unit->id, job_type_to_string(j->type), */ - /* j->object_list->subject ? j->object_list->subject->unit->id : "root", */ - /* j->object_list->subject ? job_type_to_string(j->object_list->subject->type) : "root"); */ - continue; - } - - /* log_debug("Garbage collecting job %s/%s", j->unit->id, job_type_to_string(j->type)); */ - transaction_delete_job(tr, j, true); - goto rescan; - } -} - -static int transaction_is_destructive(Transaction *tr, JobMode mode, sd_bus_error *e) { - Iterator i; - Job *j; - - assert(tr); - - /* Checks whether applying this transaction means that - * existing jobs would be replaced */ - - HASHMAP_FOREACH(j, tr->jobs, i) { - - /* Assume merged */ - assert(!j->transaction_prev); - assert(!j->transaction_next); - - if (j->unit->job && (mode == JOB_FAIL || j->unit->job->irreversible) && - job_type_is_conflicting(j->unit->job->type, j->type)) - return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, - "Transaction is destructive."); - } - - return 0; -} - -static void transaction_minimize_impact(Transaction *tr) { - Job *j; - Iterator i; - - assert(tr); - - /* Drops all unnecessary jobs that reverse already active jobs - * or that stop a running service. */ - -rescan: - HASHMAP_FOREACH(j, tr->jobs, i) { - LIST_FOREACH(transaction, j, j) { - bool stops_running_service, changes_existing_job; - - /* If it matters, we shouldn't drop it */ - if (j->matters_to_anchor) - continue; - - /* Would this stop a running service? - * Would this change an existing job? - * If so, let's drop this entry */ - - stops_running_service = - j->type == JOB_STOP && UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(j->unit)); - - changes_existing_job = - j->unit->job && - job_type_is_conflicting(j->type, j->unit->job->type); - - if (!stops_running_service && !changes_existing_job) - continue; - - if (stops_running_service) - log_unit_debug(j->unit, - "%s/%s would stop a running service.", - j->unit->id, job_type_to_string(j->type)); - - if (changes_existing_job) - log_unit_debug(j->unit, - "%s/%s would change existing job.", - j->unit->id, job_type_to_string(j->type)); - - /* Ok, let's get rid of this */ - log_unit_debug(j->unit, - "Deleting %s/%s to minimize impact.", - j->unit->id, job_type_to_string(j->type)); - - transaction_delete_job(tr, j, true); - goto rescan; - } - } -} - -static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) { - Iterator i; - Job *j; - int r; - - /* Moves the transaction jobs to the set of active jobs */ - - if (mode == JOB_ISOLATE || mode == JOB_FLUSH) { - - /* When isolating first kill all installed jobs which - * aren't part of the new transaction */ - HASHMAP_FOREACH(j, m->jobs, i) { - assert(j->installed); - - if (hashmap_get(tr->jobs, j->unit)) - continue; - - /* Not invalidating recursively. Avoids triggering - * OnFailure= actions of dependent jobs. Also avoids - * invalidating our iterator. */ - job_finish_and_invalidate(j, JOB_CANCELED, false); - } - } - - HASHMAP_FOREACH(j, tr->jobs, i) { - /* Assume merged */ - assert(!j->transaction_prev); - assert(!j->transaction_next); - - r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j); - if (r < 0) - goto rollback; - } - - while ((j = hashmap_steal_first(tr->jobs))) { - Job *installed_job; - - /* Clean the job dependencies */ - transaction_unlink_job(tr, j, false); - - installed_job = job_install(j); - if (installed_job != j) { - /* j has been merged into a previously installed job */ - if (tr->anchor_job == j) - tr->anchor_job = installed_job; - hashmap_remove(m->jobs, UINT32_TO_PTR(j->id)); - job_free(j); - j = installed_job; - } - - job_add_to_run_queue(j); - job_add_to_dbus_queue(j); - job_start_timer(j); - job_shutdown_magic(j); - } - - return 0; - -rollback: - - HASHMAP_FOREACH(j, tr->jobs, i) - hashmap_remove(m->jobs, UINT32_TO_PTR(j->id)); - - return r; -} - -int transaction_activate(Transaction *tr, Manager *m, JobMode mode, sd_bus_error *e) { - Iterator i; - Job *j; - int r; - unsigned generation = 1; - - assert(tr); - - /* This applies the changes recorded in tr->jobs to - * the actual list of jobs, if possible. */ - - /* Reset the generation counter of all installed jobs. The detection of cycles - * looks at installed jobs. If they had a non-zero generation from some previous - * walk of the graph, the algorithm would break. */ - HASHMAP_FOREACH(j, m->jobs, i) - j->generation = 0; - - /* First step: figure out which jobs matter */ - transaction_find_jobs_that_matter_to_anchor(tr->anchor_job, generation++); - - /* Second step: Try not to stop any running services if - * we don't have to. Don't try to reverse running - * jobs if we don't have to. */ - if (mode == JOB_FAIL) - transaction_minimize_impact(tr); - - /* Third step: Drop redundant jobs */ - transaction_drop_redundant(tr); - - for (;;) { - /* Fourth step: Let's remove unneeded jobs that might - * be lurking. */ - if (mode != JOB_ISOLATE) - transaction_collect_garbage(tr); - - /* Fifth step: verify order makes sense and correct - * cycles if necessary and possible */ - r = transaction_verify_order(tr, &generation, e); - if (r >= 0) - break; - - if (r != -EAGAIN) { - log_warning("Requested transaction contains an unfixable cyclic ordering dependency: %s", bus_error_message(e, r)); - return r; - } - - /* Let's see if the resulting transaction ordering - * graph is still cyclic... */ - } - - for (;;) { - /* Sixth step: let's drop unmergeable entries if - * necessary and possible, merge entries we can - * merge */ - r = transaction_merge_jobs(tr, e); - if (r >= 0) - break; - - if (r != -EAGAIN) { - log_warning("Requested transaction contains unmergeable jobs: %s", bus_error_message(e, r)); - return r; - } - - /* Seventh step: an entry got dropped, let's garbage - * collect its dependencies. */ - if (mode != JOB_ISOLATE) - transaction_collect_garbage(tr); - - /* Let's see if the resulting transaction still has - * unmergeable entries ... */ - } - - /* Eights step: Drop redundant jobs again, if the merging now allows us to drop more. */ - transaction_drop_redundant(tr); - - /* Ninth step: check whether we can actually apply this */ - r = transaction_is_destructive(tr, mode, e); - if (r < 0) { - log_notice("Requested transaction contradicts existing jobs: %s", bus_error_message(e, r)); - return r; - } - - /* Tenth step: apply changes */ - r = transaction_apply(tr, m, mode); - if (r < 0) - return log_warning_errno(r, "Failed to apply transaction: %m"); - - assert(hashmap_isempty(tr->jobs)); - - if (!hashmap_isempty(m->jobs)) { - /* Are there any jobs now? Then make sure we have the - * idle pipe around. We don't really care too much - * whether this works or not, as the idle pipe is a - * feature for cosmetics, not actually useful for - * anything beyond that. */ - - if (m->idle_pipe[0] < 0 && m->idle_pipe[1] < 0 && - m->idle_pipe[2] < 0 && m->idle_pipe[3] < 0) { - (void) pipe2(m->idle_pipe, O_NONBLOCK|O_CLOEXEC); - (void) pipe2(m->idle_pipe + 2, O_NONBLOCK|O_CLOEXEC); - } - } - - return 0; -} - -static Job* transaction_add_one_job(Transaction *tr, JobType type, Unit *unit, bool *is_new) { - Job *j, *f; - - assert(tr); - assert(unit); - - /* Looks for an existing prospective job and returns that. If - * it doesn't exist it is created and added to the prospective - * jobs list. */ - - f = hashmap_get(tr->jobs, unit); - - LIST_FOREACH(transaction, j, f) { - assert(j->unit == unit); - - if (j->type == type) { - if (is_new) - *is_new = false; - return j; - } - } - - j = job_new(unit, type); - if (!j) - return NULL; - - j->generation = 0; - j->marker = NULL; - j->matters_to_anchor = false; - j->irreversible = tr->irreversible; - - LIST_PREPEND(transaction, f, j); - - if (hashmap_replace(tr->jobs, unit, f) < 0) { - LIST_REMOVE(transaction, f, j); - job_free(j); - return NULL; - } - - if (is_new) - *is_new = true; - - /* log_debug("Added job %s/%s to transaction.", unit->id, job_type_to_string(type)); */ - - return j; -} - -static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies) { - assert(tr); - assert(j); - - if (j->transaction_prev) - j->transaction_prev->transaction_next = j->transaction_next; - else if (j->transaction_next) - hashmap_replace(tr->jobs, j->unit, j->transaction_next); - else - hashmap_remove_value(tr->jobs, j->unit, j); - - if (j->transaction_next) - j->transaction_next->transaction_prev = j->transaction_prev; - - j->transaction_prev = j->transaction_next = NULL; - - while (j->subject_list) - job_dependency_free(j->subject_list); - - while (j->object_list) { - Job *other = j->object_list->matters ? j->object_list->subject : NULL; - - job_dependency_free(j->object_list); - - if (other && delete_dependencies) { - log_unit_debug(other->unit, - "Deleting job %s/%s as dependency of job %s/%s", - other->unit->id, job_type_to_string(other->type), - j->unit->id, job_type_to_string(j->type)); - transaction_delete_job(tr, other, delete_dependencies); - } - } -} - -int transaction_add_job_and_dependencies( - Transaction *tr, - JobType type, - Unit *unit, - Job *by, - bool matters, - bool conflicts, - bool ignore_requirements, - bool ignore_order, - sd_bus_error *e) { - Job *ret; - Iterator i; - Unit *dep; - int r; - bool is_new; - - assert(tr); - assert(type < _JOB_TYPE_MAX); - assert(type < _JOB_TYPE_MAX_IN_TRANSACTION); - assert(unit); - - /* Before adding jobs for this unit, let's ensure that its state has been loaded - * This matters when jobs are spawned as part of coldplugging itself (see e. g. path_coldplug()). - * This way, we "recursively" coldplug units, ensuring that we do not look at state of - * not-yet-coldplugged units. */ - if (unit->manager->n_reloading > 0) - unit_coldplug(unit); - - /* log_debug("Pulling in %s/%s from %s/%s", */ - /* unit->id, job_type_to_string(type), */ - /* by ? by->unit->id : "NA", */ - /* by ? job_type_to_string(by->type) : "NA"); */ - - if (!IN_SET(unit->load_state, UNIT_LOADED, UNIT_ERROR, UNIT_NOT_FOUND, UNIT_MASKED)) - return sd_bus_error_setf(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id); - - if (type != JOB_STOP) { - r = bus_unit_check_load_state(unit, e); - if (r < 0) - return r; - } - - if (!unit_job_is_applicable(unit, type)) - return sd_bus_error_setf(e, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, - "Job type %s is not applicable for unit %s.", - job_type_to_string(type), unit->id); - - - /* First add the job. */ - ret = transaction_add_one_job(tr, type, unit, &is_new); - if (!ret) - return -ENOMEM; - - ret->ignore_order = ret->ignore_order || ignore_order; - - /* Then, add a link to the job. */ - if (by) { - if (!job_dependency_new(by, ret, matters, conflicts)) - return -ENOMEM; - } else { - /* If the job has no parent job, it is the anchor job. */ - assert(!tr->anchor_job); - tr->anchor_job = ret; - } - - if (is_new && !ignore_requirements && type != JOB_NOP) { - Set *following; - - /* If we are following some other unit, make sure we - * add all dependencies of everybody following. */ - if (unit_following_set(ret->unit, &following) > 0) { - SET_FOREACH(dep, following, i) { - r = transaction_add_job_and_dependencies(tr, type, dep, ret, false, false, false, ignore_order, e); - if (r < 0) { - log_unit_warning(dep, "Cannot add dependency job for, ignoring: %s", bus_error_message(e, r)); - sd_bus_error_free(e); - } - } - - set_free(following); - } - - /* Finally, recursively add in all dependencies. */ - if (type == JOB_START || type == JOB_RESTART) { - SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i) { - r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, false, false, ignore_order, e); - if (r < 0) { - if (r != -EBADR) /* job type not applicable */ - goto fail; - - sd_bus_error_free(e); - } - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_BINDS_TO], i) { - r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, false, false, ignore_order, e); - if (r < 0) { - if (r != -EBADR) /* job type not applicable */ - goto fail; - - sd_bus_error_free(e); - } - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) { - r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, ignore_order, e); - if (r < 0) { - /* unit masked, job type not applicable and unit not found are not considered as errors. */ - log_unit_full(dep, - IN_SET(r, -ESHUTDOWN, -EBADR, -ENOENT) ? LOG_DEBUG : LOG_WARNING, - r, "Cannot add dependency job, ignoring: %s", - bus_error_message(e, r)); - sd_bus_error_free(e); - } - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE], i) { - r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, true, false, false, ignore_order, e); - if (r < 0) { - if (r != -EBADR) /* job type not applicable */ - goto fail; - - sd_bus_error_free(e); - } - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTS], i) { - r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, true, false, ignore_order, e); - if (r < 0) { - if (r != -EBADR) /* job type not applicable */ - goto fail; - - sd_bus_error_free(e); - } - } - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTED_BY], i) { - r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, false, false, false, ignore_order, e); - if (r < 0) { - log_unit_warning(dep, - "Cannot add dependency job, ignoring: %s", - bus_error_message(e, r)); - sd_bus_error_free(e); - } - } - - } - - if (type == JOB_STOP || type == JOB_RESTART) { - static const UnitDependency propagate_deps[] = { - UNIT_REQUIRED_BY, - UNIT_REQUISITE_OF, - UNIT_BOUND_BY, - UNIT_CONSISTS_OF, - }; - - JobType ptype; - unsigned j; - - /* We propagate STOP as STOP, but RESTART only - * as TRY_RESTART, in order not to start - * dependencies that are not around. */ - ptype = type == JOB_RESTART ? JOB_TRY_RESTART : type; - - for (j = 0; j < ELEMENTSOF(propagate_deps); j++) - SET_FOREACH(dep, ret->unit->dependencies[propagate_deps[j]], i) { - JobType nt; - - nt = job_type_collapse(ptype, dep); - if (nt == JOB_NOP) - continue; - - r = transaction_add_job_and_dependencies(tr, nt, dep, ret, true, false, false, ignore_order, e); - if (r < 0) { - if (r != -EBADR) /* job type not applicable */ - goto fail; - - sd_bus_error_free(e); - } - } - } - - if (type == JOB_RELOAD) { - - SET_FOREACH(dep, ret->unit->dependencies[UNIT_PROPAGATES_RELOAD_TO], i) { - JobType nt; - - nt = job_type_collapse(JOB_TRY_RELOAD, dep); - if (nt == JOB_NOP) - continue; - - r = transaction_add_job_and_dependencies(tr, nt, dep, ret, false, false, false, ignore_order, e); - if (r < 0) { - log_unit_warning(dep, - "Cannot add dependency reload job, ignoring: %s", - bus_error_message(e, r)); - sd_bus_error_free(e); - } - } - } - - /* JOB_VERIFY_STARTED require no dependency handling */ - } - - return 0; - -fail: - return r; -} - -int transaction_add_isolate_jobs(Transaction *tr, Manager *m) { - Iterator i; - Unit *u; - char *k; - int r; - - assert(tr); - assert(m); - - HASHMAP_FOREACH_KEY(u, k, m->units, i) { - - /* ignore aliases */ - if (u->id != k) - continue; - - if (u->ignore_on_isolate) - continue; - - /* No need to stop inactive jobs */ - if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u)) && !u->job) - continue; - - /* Is there already something listed for this? */ - if (hashmap_get(tr->jobs, u)) - continue; - - r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, tr->anchor_job, true, false, false, false, NULL); - if (r < 0) - log_unit_warning_errno(u, r, "Cannot add isolate job, ignoring: %m"); - } - - return 0; -} - -Transaction *transaction_new(bool irreversible) { - Transaction *tr; - - tr = new0(Transaction, 1); - if (!tr) - return NULL; - - tr->jobs = hashmap_new(NULL); - if (!tr->jobs) { - free(tr); - return NULL; - } - - tr->irreversible = irreversible; - - return tr; -} - -void transaction_free(Transaction *tr) { - assert(hashmap_isempty(tr->jobs)); - hashmap_free(tr->jobs); - free(tr); -} diff --git a/src/core/transaction.h b/src/core/transaction.h deleted file mode 100644 index 6a3f927b0f..0000000000 --- a/src/core/transaction.h +++ /dev/null @@ -1,51 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef struct Transaction Transaction; - -#include "hashmap.h" -#include "job.h" -#include "manager.h" -#include "unit.h" - -struct Transaction { - /* Jobs to be added */ - Hashmap *jobs; /* Unit object => Job object list 1:1 */ - Job *anchor_job; /* the job the user asked for */ - bool irreversible; -}; - -Transaction *transaction_new(bool irreversible); -void transaction_free(Transaction *tr); - -int transaction_add_job_and_dependencies( - Transaction *tr, - JobType type, - Unit *unit, - Job *by, - bool matters, - bool conflicts, - bool ignore_requirements, - bool ignore_order, - sd_bus_error *e); -int transaction_activate(Transaction *tr, Manager *m, JobMode mode, sd_bus_error *e); -int transaction_add_isolate_jobs(Transaction *tr, Manager *m); -void transaction_abort(Transaction *tr); diff --git a/src/core/triggers.systemd.in b/src/core/triggers.systemd.in deleted file mode 100644 index 9e18a39a67..0000000000 --- a/src/core/triggers.systemd.in +++ /dev/null @@ -1,64 +0,0 @@ -# -*- Mode: rpm-spec; indent-tabs-mode: nil -*- */ -# -# This file is part of systemd. -# -# Copyright 2015 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 . - -# The contents of this are an example to be copied into systemd.spec. - -%transfiletriggerin -P 900900 -p -- @systemunitdir@ /etc/systemd/system --- This script will run after any package is initially installed or --- upgraded. We care about the case where a package is initially --- installed, because other cases are covered by the *un scriptlets, --- so sometimes we will reload needlessly. - -pid = posix.fork() -if pid == 0 then - assert(posix.exec("%{_bindir}/systemctl", "daemon-reload")) -elseif pid > 0 then - posix.wait(pid) -end - -%transfiletriggerun -p -- @systemunitdir@ /etc/systemd/system --- On removal, we need to run daemon-reload after any units have been --- removed. %transfiletriggerpostun would be ideal, but it does not get --- executed for some reason. --- On upgrade, we need to run daemon-reload after any new unit files --- have been installed, but before %postun scripts in packages get --- executed. %transfiletriggerun gets the right list of files --- but it is invoked too early (before changes happen). --- %filetriggerpostun happens at the right time, but it fires for --- every package. --- To execute the reload at the right time, we create a state --- file in %transfiletriggerun and execute the daemon-reload in --- the first %filetriggerpostun. - -posix.mkdir("%{_localstatedir}/lib") -posix.mkdir("%{_localstatedir}/lib/rpm-state") -posix.mkdir("%{_localstatedir}/lib/rpm-state/systemd") -io.open("%{_localstatedir}/lib/rpm-state/systemd/needs-reload", "w") - -%filetriggerpostun -P 1000100 -p -- @systemunitdir@ /etc/systemd/system -if posix.access("%{_localstatedir}/lib/rpm-state/systemd/needs-reload") then - posix.unlink("%{_localstatedir}/lib/rpm-state/systemd/needs-reload") - posix.rmdir("%{_localstatedir}/lib/rpm-state/systemd") - pid = posix.fork() - if pid == 0 then - assert(posix.exec("%{_bindir}/systemctl", "daemon-reload")) - elseif pid > 0 then - posix.wait(pid) - end -end diff --git a/src/core/umount.c b/src/core/umount.c deleted file mode 100644 index a458768e7d..0000000000 --- a/src/core/umount.c +++ /dev/null @@ -1,613 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 ProFUSION embedded systems - - 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "libudev.h" - -#include "alloc-util.h" -#include "escape.h" -#include "fd-util.h" -#include "fstab-util.h" -#include "list.h" -#include "mount-setup.h" -#include "path-util.h" -#include "string-util.h" -#include "udev-util.h" -#include "umount.h" -#include "util.h" -#include "virt.h" - -typedef struct MountPoint { - char *path; - char *options; - dev_t devnum; - LIST_FIELDS(struct MountPoint, mount_point); -} MountPoint; - -static void mount_point_free(MountPoint **head, MountPoint *m) { - assert(head); - assert(m); - - LIST_REMOVE(mount_point, *head, m); - - free(m->path); - free(m); -} - -static void mount_points_list_free(MountPoint **head) { - assert(head); - - while (*head) - mount_point_free(head, *head); -} - -static int mount_points_list_get(MountPoint **head) { - _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; - unsigned int i; - int r; - - assert(head); - - proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); - if (!proc_self_mountinfo) - return -errno; - - for (i = 1;; i++) { - _cleanup_free_ char *path = NULL, *options = NULL; - char *p = NULL; - MountPoint *m; - int k; - - k = fscanf(proc_self_mountinfo, - "%*s " /* (1) mount id */ - "%*s " /* (2) parent id */ - "%*s " /* (3) major:minor */ - "%*s " /* (4) root */ - "%ms " /* (5) mount point */ - "%*s" /* (6) mount flags */ - "%*[^-]" /* (7) optional fields */ - "- " /* (8) separator */ - "%*s " /* (9) file system type */ - "%*s" /* (10) mount source */ - "%ms" /* (11) mount options */ - "%*[^\n]", /* some rubbish at the end */ - &path, &options); - if (k != 2) { - if (k == EOF) - break; - - log_warning("Failed to parse /proc/self/mountinfo:%u.", i); - continue; - } - - r = cunescape(path, UNESCAPE_RELAX, &p); - if (r < 0) - return r; - - /* Ignore mount points we can't unmount because they - * are API or because we are keeping them open (like - * /dev/console). Also, ignore all mounts below API - * file systems, since they are likely virtual too, - * and hence not worth spending time on. Also, in - * unprivileged containers we might lack the rights to - * unmount these things, hence don't bother. */ - if (mount_point_is_api(p) || - mount_point_ignore(p) || - path_startswith(p, "/dev") || - path_startswith(p, "/sys") || - path_startswith(p, "/proc")) { - free(p); - continue; - } - - m = new0(MountPoint, 1); - if (!m) { - free(p); - return -ENOMEM; - } - - m->path = p; - m->options = options; - options = NULL; - - LIST_PREPEND(mount_point, *head, m); - } - - return 0; -} - -static int swap_list_get(MountPoint **head) { - _cleanup_fclose_ FILE *proc_swaps = NULL; - unsigned int i; - int r; - - assert(head); - - proc_swaps = fopen("/proc/swaps", "re"); - if (!proc_swaps) - return (errno == ENOENT) ? 0 : -errno; - - (void) fscanf(proc_swaps, "%*s %*s %*s %*s %*s\n"); - - for (i = 2;; i++) { - MountPoint *swap; - char *dev = NULL, *d; - int k; - - k = fscanf(proc_swaps, - "%ms " /* device/file */ - "%*s " /* type of swap */ - "%*s " /* swap size */ - "%*s " /* used */ - "%*s\n", /* priority */ - &dev); - - if (k != 1) { - if (k == EOF) - break; - - log_warning("Failed to parse /proc/swaps:%u.", i); - free(dev); - continue; - } - - if (endswith(dev, " (deleted)")) { - free(dev); - continue; - } - - r = cunescape(dev, UNESCAPE_RELAX, &d); - free(dev); - if (r < 0) - return r; - - swap = new0(MountPoint, 1); - if (!swap) { - free(d); - return -ENOMEM; - } - - swap->path = d; - LIST_PREPEND(mount_point, *head, swap); - } - - return 0; -} - -static int loopback_list_get(MountPoint **head) { - _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; - struct udev_list_entry *item = NULL, *first = NULL; - _cleanup_udev_unref_ struct udev *udev = NULL; - int r; - - assert(head); - - udev = udev_new(); - if (!udev) - return -ENOMEM; - - e = udev_enumerate_new(udev); - if (!e) - return -ENOMEM; - - r = udev_enumerate_add_match_subsystem(e, "block"); - if (r < 0) - return r; - - r = udev_enumerate_add_match_sysname(e, "loop*"); - if (r < 0) - return r; - - r = udev_enumerate_add_match_sysattr(e, "loop/backing_file", NULL); - if (r < 0) - return r; - - r = udev_enumerate_scan_devices(e); - if (r < 0) - return r; - - first = udev_enumerate_get_list_entry(e); - udev_list_entry_foreach(item, first) { - MountPoint *lb; - _cleanup_udev_device_unref_ struct udev_device *d; - char *loop; - const char *dn; - - d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); - if (!d) - return -ENOMEM; - - dn = udev_device_get_devnode(d); - if (!dn) - continue; - - loop = strdup(dn); - if (!loop) - return -ENOMEM; - - lb = new0(MountPoint, 1); - if (!lb) { - free(loop); - return -ENOMEM; - } - - lb->path = loop; - LIST_PREPEND(mount_point, *head, lb); - } - - return 0; -} - -static int dm_list_get(MountPoint **head) { - _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; - struct udev_list_entry *item = NULL, *first = NULL; - _cleanup_udev_unref_ struct udev *udev = NULL; - int r; - - assert(head); - - udev = udev_new(); - if (!udev) - return -ENOMEM; - - e = udev_enumerate_new(udev); - if (!e) - return -ENOMEM; - - r = udev_enumerate_add_match_subsystem(e, "block"); - if (r < 0) - return r; - - r = udev_enumerate_add_match_sysname(e, "dm-*"); - if (r < 0) - return r; - - r = udev_enumerate_scan_devices(e); - if (r < 0) - return r; - - first = udev_enumerate_get_list_entry(e); - udev_list_entry_foreach(item, first) { - MountPoint *m; - _cleanup_udev_device_unref_ struct udev_device *d; - dev_t devnum; - char *node; - const char *dn; - - d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); - if (!d) - return -ENOMEM; - - devnum = udev_device_get_devnum(d); - dn = udev_device_get_devnode(d); - if (major(devnum) == 0 || !dn) - continue; - - node = strdup(dn); - if (!node) - return -ENOMEM; - - m = new(MountPoint, 1); - if (!m) { - free(node); - return -ENOMEM; - } - - m->path = node; - m->devnum = devnum; - LIST_PREPEND(mount_point, *head, m); - } - - return 0; -} - -static int delete_loopback(const char *device) { - _cleanup_close_ int fd = -1; - int r; - - fd = open(device, O_RDONLY|O_CLOEXEC); - if (fd < 0) - return errno == ENOENT ? 0 : -errno; - - r = ioctl(fd, LOOP_CLR_FD, 0); - if (r >= 0) - return 1; - - /* ENXIO: not bound, so no error */ - if (errno == ENXIO) - return 0; - - return -errno; -} - -static int delete_dm(dev_t devnum) { - _cleanup_close_ int fd = -1; - int r; - struct dm_ioctl dm = { - .version = {DM_VERSION_MAJOR, - DM_VERSION_MINOR, - DM_VERSION_PATCHLEVEL}, - .data_size = sizeof(dm), - .dev = devnum, - }; - - assert(major(devnum) != 0); - - fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC); - if (fd < 0) - return -errno; - - r = ioctl(fd, DM_DEV_REMOVE, &dm); - return r >= 0 ? 0 : -errno; -} - -static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_error) { - MountPoint *m, *n; - int n_failed = 0; - - assert(head); - - LIST_FOREACH_SAFE(mount_point, m, n, *head) { - - /* If we are in a container, don't attempt to - read-only mount anything as that brings no real - benefits, but might confuse the host, as we remount - the superblock here, not the bind mound. */ - if (detect_container() <= 0) { - _cleanup_free_ char *options = NULL; - /* MS_REMOUNT requires that the data parameter - * should be the same from the original mount - * except for the desired changes. Since we want - * to remount read-only, we should filter out - * rw (and ro too, because it confuses the kernel) */ - (void) fstab_filter_options(m->options, "rw\0ro\0", NULL, NULL, &options); - - /* We always try to remount directories - * read-only first, before we go on and umount - * them. - * - * Mount points can be stacked. If a mount - * point is stacked below / or /usr, we - * cannot umount or remount it directly, - * since there is no way to refer to the - * underlying mount. There's nothing we can do - * about it for the general case, but we can - * do something about it if it is aliased - * somehwere else via a bind mount. If we - * explicitly remount the super block of that - * alias read-only we hence should be - * relatively safe regarding keeping the fs we - * can otherwise not see dirty. */ - log_info("Remounting '%s' read-only with options '%s'.", m->path, options); - (void) mount(NULL, m->path, NULL, MS_REMOUNT|MS_RDONLY, options); - } - - /* Skip / and /usr since we cannot unmount that - * anyway, since we are running from it. They have - * already been remounted ro. */ - if (path_equal(m->path, "/") -#ifndef HAVE_SPLIT_USR - || path_equal(m->path, "/usr") -#endif - ) - continue; - - /* Trying to umount. We don't force here since we rely - * on busy NFS and FUSE file systems to return EBUSY - * until we closed everything on top of them. */ - log_info("Unmounting %s.", m->path); - if (umount2(m->path, 0) == 0) { - if (changed) - *changed = true; - - mount_point_free(head, m); - } else if (log_error) { - log_warning_errno(errno, "Could not unmount %s: %m", m->path); - n_failed++; - } - } - - return n_failed; -} - -static int swap_points_list_off(MountPoint **head, bool *changed) { - MountPoint *m, *n; - int n_failed = 0; - - assert(head); - - LIST_FOREACH_SAFE(mount_point, m, n, *head) { - log_info("Deactivating swap %s.", m->path); - if (swapoff(m->path) == 0) { - if (changed) - *changed = true; - - mount_point_free(head, m); - } else { - log_warning_errno(errno, "Could not deactivate swap %s: %m", m->path); - n_failed++; - } - } - - return n_failed; -} - -static int loopback_points_list_detach(MountPoint **head, bool *changed) { - MountPoint *m, *n; - int n_failed = 0, k; - struct stat root_st; - - assert(head); - - k = lstat("/", &root_st); - - LIST_FOREACH_SAFE(mount_point, m, n, *head) { - int r; - struct stat loopback_st; - - if (k >= 0 && - major(root_st.st_dev) != 0 && - lstat(m->path, &loopback_st) >= 0 && - root_st.st_dev == loopback_st.st_rdev) { - n_failed ++; - continue; - } - - log_info("Detaching loopback %s.", m->path); - r = delete_loopback(m->path); - if (r >= 0) { - if (r > 0 && changed) - *changed = true; - - mount_point_free(head, m); - } else { - log_warning_errno(errno, "Could not detach loopback %s: %m", m->path); - n_failed++; - } - } - - return n_failed; -} - -static int dm_points_list_detach(MountPoint **head, bool *changed) { - MountPoint *m, *n; - int n_failed = 0, k; - struct stat root_st; - - assert(head); - - k = lstat("/", &root_st); - - LIST_FOREACH_SAFE(mount_point, m, n, *head) { - int r; - - if (k >= 0 && - major(root_st.st_dev) != 0 && - root_st.st_dev == m->devnum) { - n_failed ++; - continue; - } - - log_info("Detaching DM %u:%u.", major(m->devnum), minor(m->devnum)); - r = delete_dm(m->devnum); - if (r >= 0) { - if (changed) - *changed = true; - - mount_point_free(head, m); - } else { - log_warning_errno(errno, "Could not detach DM %s: %m", m->path); - n_failed++; - } - } - - return n_failed; -} - -int umount_all(bool *changed) { - int r; - bool umount_changed; - LIST_HEAD(MountPoint, mp_list_head); - - LIST_HEAD_INIT(mp_list_head); - r = mount_points_list_get(&mp_list_head); - if (r < 0) - goto end; - - /* retry umount, until nothing can be umounted anymore */ - do { - umount_changed = false; - - mount_points_list_umount(&mp_list_head, &umount_changed, false); - if (umount_changed) - *changed = true; - - } while (umount_changed); - - /* umount one more time with logging enabled */ - r = mount_points_list_umount(&mp_list_head, &umount_changed, true); - if (r <= 0) - goto end; - - end: - mount_points_list_free(&mp_list_head); - - return r; -} - -int swapoff_all(bool *changed) { - int r; - LIST_HEAD(MountPoint, swap_list_head); - - LIST_HEAD_INIT(swap_list_head); - - r = swap_list_get(&swap_list_head); - if (r < 0) - goto end; - - r = swap_points_list_off(&swap_list_head, changed); - - end: - mount_points_list_free(&swap_list_head); - - return r; -} - -int loopback_detach_all(bool *changed) { - int r; - LIST_HEAD(MountPoint, loopback_list_head); - - LIST_HEAD_INIT(loopback_list_head); - - r = loopback_list_get(&loopback_list_head); - if (r < 0) - goto end; - - r = loopback_points_list_detach(&loopback_list_head, changed); - - end: - mount_points_list_free(&loopback_list_head); - - return r; -} - -int dm_detach_all(bool *changed) { - int r; - LIST_HEAD(MountPoint, dm_list_head); - - LIST_HEAD_INIT(dm_list_head); - - r = dm_list_get(&dm_list_head); - if (r < 0) - goto end; - - r = dm_points_list_detach(&dm_list_head, changed); - - end: - mount_points_list_free(&dm_list_head); - - return r; -} diff --git a/src/core/umount.h b/src/core/umount.h deleted file mode 100644 index 4e2215a47d..0000000000 --- a/src/core/umount.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 ProFUSION embedded systems - - 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 . -***/ - -int umount_all(bool *changed); - -int swapoff_all(bool *changed); - -int loopback_detach_all(bool *changed); - -int dm_detach_all(bool *changed); diff --git a/src/core/unit-printf.c b/src/core/unit-printf.c deleted file mode 100644 index fc057d965c..0000000000 --- a/src/core/unit-printf.c +++ /dev/null @@ -1,309 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "alloc-util.h" -#include "cgroup-util.h" -#include "formats-util.h" -#include "macro.h" -#include "specifier.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit-printf.h" -#include "unit.h" -#include "user-util.h" - -static int specifier_prefix_and_instance(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - - assert(u); - - return unit_name_to_prefix_and_instance(u->id, ret); -} - -static int specifier_prefix(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - - assert(u); - - return unit_name_to_prefix(u->id, ret); -} - -static int specifier_prefix_unescaped(char specifier, void *data, void *userdata, char **ret) { - _cleanup_free_ char *p = NULL; - Unit *u = userdata; - int r; - - assert(u); - - r = unit_name_to_prefix(u->id, &p); - if (r < 0) - return r; - - return unit_name_unescape(p, ret); -} - -static int specifier_instance_unescaped(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - - assert(u); - - return unit_name_unescape(strempty(u->instance), ret); -} - -static int specifier_filename(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - - assert(u); - - if (u->instance) - return unit_name_path_unescape(u->instance, ret); - else - return unit_name_to_path(u->id, ret); -} - -static int specifier_cgroup(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - char *n; - - assert(u); - - if (u->cgroup_path) - n = strdup(u->cgroup_path); - else - n = unit_default_cgroup_path(u); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} - -static int specifier_cgroup_root(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - char *n; - - assert(u); - - n = strdup(u->manager->cgroup_root); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} - -static int specifier_cgroup_slice(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - char *n; - - assert(u); - - if (UNIT_ISSET(u->slice)) { - Unit *slice; - - slice = UNIT_DEREF(u->slice); - - if (slice->cgroup_path) - n = strdup(slice->cgroup_path); - else - n = unit_default_cgroup_path(slice); - } else - n = strdup(u->manager->cgroup_root); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} - -static int specifier_runtime(char specifier, void *data, void *userdata, char **ret) { - Unit *u = userdata; - const char *e; - char *n = NULL; - - assert(u); - - if (u->manager->running_as == MANAGER_SYSTEM) - e = "/run"; - else { - e = getenv("XDG_RUNTIME_DIR"); - if (!e) - return -EOPNOTSUPP; - } - - n = strdup(e); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} - -static int specifier_user_name(char specifier, void *data, void *userdata, char **ret) { - char *t; - - /* If we are UID 0 (root), this will not result in NSS, - * otherwise it might. This is good, as we want to be able to - * run this in PID 1, where our user ID is 0, but where NSS - * lookups are not allowed. */ - - t = getusername_malloc(); - if (!t) - return -ENOMEM; - - *ret = t; - return 0; -} - -static int specifier_user_id(char specifier, void *data, void *userdata, char **ret) { - - if (asprintf(ret, UID_FMT, getuid()) < 0) - return -ENOMEM; - - return 0; -} - -static int specifier_user_home(char specifier, void *data, void *userdata, char **ret) { - - /* On PID 1 (which runs as root) this will not result in NSS, - * which is good. See above */ - - return get_home_dir(ret); -} - -static int specifier_user_shell(char specifier, void *data, void *userdata, char **ret) { - - /* On PID 1 (which runs as root) this will not result in NSS, - * which is good. See above */ - - return get_shell(ret); -} - -int unit_name_printf(Unit *u, const char* format, char **ret) { - - /* - * This will use the passed string as format string and - * replace the following specifiers: - * - * %n: the full id of the unit (foo@bar.waldo) - * %N: the id of the unit without the suffix (foo@bar) - * %p: the prefix (foo) - * %i: the instance (bar) - */ - - const Specifier table[] = { - { 'n', specifier_string, u->id }, - { 'N', specifier_prefix_and_instance, NULL }, - { 'p', specifier_prefix, NULL }, - { 'i', specifier_string, u->instance }, - { 0, NULL, NULL } - }; - - assert(u); - assert(format); - assert(ret); - - return specifier_printf(format, table, u, ret); -} - -int unit_full_printf(Unit *u, const char *format, char **ret) { - - /* This is similar to unit_name_printf() but also supports - * unescaping. Also, adds a couple of additional codes: - * - * %f the instance if set, otherwise the id - * %c cgroup path of unit - * %r where units in this slice are placed in the cgroup tree - * %R the root of this systemd's instance tree - * %t the runtime directory to place sockets in (e.g. "/run" or $XDG_RUNTIME_DIR) - * %U the UID of the running user - * %u the username of the running user - * %h the homedir of the running user - * %s the shell of the running user - * %m the machine ID of the running system - * %H the host name of the running system - * %b the boot ID of the running system - * %v `uname -r` of the running system - */ - - const Specifier table[] = { - { 'n', specifier_string, u->id }, - { 'N', specifier_prefix_and_instance, NULL }, - { 'p', specifier_prefix, NULL }, - { 'P', specifier_prefix_unescaped, NULL }, - { 'i', specifier_string, u->instance }, - { 'I', specifier_instance_unescaped, NULL }, - - { 'f', specifier_filename, NULL }, - { 'c', specifier_cgroup, NULL }, - { 'r', specifier_cgroup_slice, NULL }, - { 'R', specifier_cgroup_root, NULL }, - { 't', specifier_runtime, NULL }, - - { 'U', specifier_user_id, NULL }, - { 'u', specifier_user_name, NULL }, - { 'h', specifier_user_home, NULL }, - { 's', specifier_user_shell, NULL }, - - { 'm', specifier_machine_id, NULL }, - { 'H', specifier_host_name, NULL }, - { 'b', specifier_boot_id, NULL }, - { 'v', specifier_kernel_release, NULL }, - {} - }; - - assert(u); - assert(format); - assert(ret); - - return specifier_printf(format, table, u, ret); -} - -int unit_full_printf_strv(Unit *u, char **l, char ***ret) { - size_t n; - char **r, **i, **j; - int q; - - /* Applies unit_full_printf to every entry in l */ - - assert(u); - - n = strv_length(l); - r = new(char*, n+1); - if (!r) - return -ENOMEM; - - for (i = l, j = r; *i; i++, j++) { - q = unit_full_printf(u, *i, j); - if (q < 0) - goto fail; - } - - *j = NULL; - *ret = r; - return 0; - -fail: - for (j--; j >= r; j--) - free(*j); - - free(r); - return q; -} diff --git a/src/core/unit-printf.h b/src/core/unit-printf.h deleted file mode 100644 index 4fc8531228..0000000000 --- a/src/core/unit-printf.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include "unit.h" - -int unit_name_printf(Unit *u, const char* text, char **ret); -int unit_full_printf(Unit *u, const char *text, char **ret); -int unit_full_printf_strv(Unit *u, char **l, char ***ret); diff --git a/src/core/unit.c b/src/core/unit.c deleted file mode 100644 index d39e3dcaeb..0000000000 --- a/src/core/unit.c +++ /dev/null @@ -1,3756 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "sd-id128.h" -#include "sd-messages.h" - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "dbus-unit.h" -#include "dbus.h" -#include "dropin.h" -#include "escape.h" -#include "execute.h" -#include "fileio-label.h" -#include "formats-util.h" -#include "load-dropin.h" -#include "load-fragment.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "set.h" -#include "special.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "unit.h" -#include "user-util.h" -#include "virt.h" - -const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { - [UNIT_SERVICE] = &service_vtable, - [UNIT_SOCKET] = &socket_vtable, - [UNIT_BUSNAME] = &busname_vtable, - [UNIT_TARGET] = &target_vtable, - [UNIT_DEVICE] = &device_vtable, - [UNIT_MOUNT] = &mount_vtable, - [UNIT_AUTOMOUNT] = &automount_vtable, - [UNIT_SWAP] = &swap_vtable, - [UNIT_TIMER] = &timer_vtable, - [UNIT_PATH] = &path_vtable, - [UNIT_SLICE] = &slice_vtable, - [UNIT_SCOPE] = &scope_vtable -}; - -static void maybe_warn_about_dependency(Unit *u, const char *other, UnitDependency dependency); - -Unit *unit_new(Manager *m, size_t size) { - Unit *u; - - assert(m); - assert(size >= sizeof(Unit)); - - u = malloc0(size); - if (!u) - return NULL; - - u->names = set_new(&string_hash_ops); - if (!u->names) { - free(u); - return NULL; - } - - u->manager = m; - u->type = _UNIT_TYPE_INVALID; - u->default_dependencies = true; - u->unit_file_state = _UNIT_FILE_STATE_INVALID; - u->unit_file_preset = -1; - u->on_failure_job_mode = JOB_REPLACE; - u->cgroup_inotify_wd = -1; - u->job_timeout = USEC_INFINITY; - - 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); - - return u; -} - -bool unit_has_name(Unit *u, const char *name) { - assert(u); - assert(name); - - return !!set_get(u->names, (char*) name); -} - -static void unit_init(Unit *u) { - CGroupContext *cc; - ExecContext *ec; - KillContext *kc; - - assert(u); - assert(u->manager); - assert(u->type >= 0); - - cc = unit_get_cgroup_context(u); - if (cc) { - cgroup_context_init(cc); - - /* Copy in the manager defaults into the cgroup - * context, _before_ the rest of the settings have - * been initialized */ - - cc->cpu_accounting = u->manager->default_cpu_accounting; - cc->blockio_accounting = u->manager->default_blockio_accounting; - cc->memory_accounting = u->manager->default_memory_accounting; - cc->tasks_accounting = u->manager->default_tasks_accounting; - - if (u->type != UNIT_SLICE) - cc->tasks_max = u->manager->default_tasks_max; - } - - ec = unit_get_exec_context(u); - if (ec) - exec_context_init(ec); - - kc = unit_get_kill_context(u); - if (kc) - kill_context_init(kc); - - if (UNIT_VTABLE(u)->init) - UNIT_VTABLE(u)->init(u); -} - -int unit_add_name(Unit *u, const char *text) { - _cleanup_free_ char *s = NULL, *i = NULL; - UnitType t; - int r; - - assert(u); - assert(text); - - if (unit_name_is_valid(text, UNIT_NAME_TEMPLATE)) { - - if (!u->instance) - return -EINVAL; - - r = unit_name_replace_instance(text, u->instance, &s); - if (r < 0) - return r; - } else { - s = strdup(text); - if (!s) - return -ENOMEM; - } - - if (set_contains(u->names, s)) - return 0; - if (hashmap_contains(u->manager->units, s)) - return -EEXIST; - - if (!unit_name_is_valid(s, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) - return -EINVAL; - - t = unit_name_to_type(s); - if (t < 0) - return -EINVAL; - - if (u->type != _UNIT_TYPE_INVALID && t != u->type) - return -EINVAL; - - r = unit_name_to_instance(s, &i); - if (r < 0) - return r; - - if (i && unit_vtable[t]->no_instances) - return -EINVAL; - - /* Ensure that this unit is either instanced or not instanced, - * but not both. Note that we do allow names with different - * instance names however! */ - if (u->type != _UNIT_TYPE_INVALID && !u->instance != !i) - return -EINVAL; - - if (unit_vtable[t]->no_alias && !set_isempty(u->names)) - return -EEXIST; - - if (hashmap_size(u->manager->units) >= MANAGER_MAX_NAMES) - return -E2BIG; - - r = set_put(u->names, s); - if (r < 0) - return r; - assert(r > 0); - - r = hashmap_put(u->manager->units, s, u); - if (r < 0) { - (void) set_remove(u->names, s); - return r; - } - - if (u->type == _UNIT_TYPE_INVALID) { - u->type = t; - u->id = s; - u->instance = i; - - LIST_PREPEND(units_by_type, u->manager->units_by_type[t], u); - - unit_init(u); - - i = NULL; - } - - s = NULL; - - unit_add_to_dbus_queue(u); - return 0; -} - -int unit_choose_id(Unit *u, const char *name) { - _cleanup_free_ char *t = NULL; - char *s, *i; - int r; - - assert(u); - assert(name); - - if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { - - if (!u->instance) - return -EINVAL; - - r = unit_name_replace_instance(name, u->instance, &t); - if (r < 0) - return r; - - name = t; - } - - /* Selects one of the names of this unit as the id */ - s = set_get(u->names, (char*) name); - if (!s) - return -ENOENT; - - /* Determine the new instance from the new id */ - r = unit_name_to_instance(s, &i); - if (r < 0) - return r; - - u->id = s; - - free(u->instance); - u->instance = i; - - unit_add_to_dbus_queue(u); - - return 0; -} - -int unit_set_description(Unit *u, const char *description) { - char *s; - - assert(u); - - if (isempty(description)) - s = NULL; - else { - s = strdup(description); - if (!s) - return -ENOMEM; - } - - free(u->description); - u->description = s; - - unit_add_to_dbus_queue(u); - return 0; -} - -bool unit_check_gc(Unit *u) { - UnitActiveState state; - assert(u); - - if (u->job) - return true; - - if (u->nop_job) - return true; - - state = unit_active_state(u); - - /* If the unit is inactive and failed and no job is queued for - * it, then release its runtime resources */ - if (UNIT_IS_INACTIVE_OR_FAILED(state) && - UNIT_VTABLE(u)->release_resources) - UNIT_VTABLE(u)->release_resources(u); - - /* But we keep the unit object around for longer when it is - * referenced or configured to not be gc'ed */ - if (state != UNIT_INACTIVE) - return true; - - if (u->no_gc) - return true; - - if (u->refs) - return true; - - if (UNIT_VTABLE(u)->check_gc) - if (UNIT_VTABLE(u)->check_gc(u)) - return true; - - return false; -} - -void unit_add_to_load_queue(Unit *u) { - assert(u); - assert(u->type != _UNIT_TYPE_INVALID); - - if (u->load_state != UNIT_STUB || u->in_load_queue) - return; - - LIST_PREPEND(load_queue, u->manager->load_queue, u); - u->in_load_queue = true; -} - -void unit_add_to_cleanup_queue(Unit *u) { - assert(u); - - if (u->in_cleanup_queue) - return; - - LIST_PREPEND(cleanup_queue, u->manager->cleanup_queue, u); - u->in_cleanup_queue = true; -} - -void unit_add_to_gc_queue(Unit *u) { - assert(u); - - if (u->in_gc_queue || u->in_cleanup_queue) - return; - - if (unit_check_gc(u)) - return; - - LIST_PREPEND(gc_queue, u->manager->gc_queue, u); - u->in_gc_queue = true; - - u->manager->n_in_gc_queue ++; -} - -void unit_add_to_dbus_queue(Unit *u) { - assert(u); - assert(u->type != _UNIT_TYPE_INVALID); - - if (u->load_state == UNIT_STUB || u->in_dbus_queue) - return; - - /* Shortcut things if nobody cares */ - if (sd_bus_track_count(u->manager->subscribed) <= 0 && - set_isempty(u->manager->private_buses)) { - u->sent_dbus_new_signal = true; - return; - } - - LIST_PREPEND(dbus_queue, u->manager->dbus_unit_queue, u); - u->in_dbus_queue = true; -} - -static void bidi_set_free(Unit *u, Set *s) { - Iterator i; - Unit *other; - - assert(u); - - /* Frees the set and makes sure we are dropped from the - * inverse pointers */ - - SET_FOREACH(other, s, i) { - UnitDependency d; - - for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) - set_remove(other->dependencies[d], u); - - unit_add_to_gc_queue(other); - } - - set_free(s); -} - -static void unit_remove_transient(Unit *u) { - char **i; - - assert(u); - - if (!u->transient) - return; - - if (u->fragment_path) - (void) unlink(u->fragment_path); - - STRV_FOREACH(i, u->dropin_paths) { - _cleanup_free_ char *p = NULL; - - (void) unlink(*i); - - p = dirname_malloc(*i); - if (p) - (void) rmdir(p); - } -} - -static void unit_free_requires_mounts_for(Unit *u) { - char **j; - - STRV_FOREACH(j, u->requires_mounts_for) { - char s[strlen(*j) + 1]; - - PATH_FOREACH_PREFIX_MORE(s, *j) { - char *y; - Set *x; - - x = hashmap_get2(u->manager->units_requiring_mounts_for, s, (void**) &y); - if (!x) - continue; - - set_remove(x, u); - - if (set_isempty(x)) { - hashmap_remove(u->manager->units_requiring_mounts_for, y); - free(y); - set_free(x); - } - } - } - - u->requires_mounts_for = strv_free(u->requires_mounts_for); -} - -static void unit_done(Unit *u) { - ExecContext *ec; - CGroupContext *cc; - - assert(u); - - if (u->type < 0) - return; - - if (UNIT_VTABLE(u)->done) - UNIT_VTABLE(u)->done(u); - - ec = unit_get_exec_context(u); - if (ec) - exec_context_done(ec); - - cc = unit_get_cgroup_context(u); - if (cc) - cgroup_context_done(cc); -} - -void unit_free(Unit *u) { - UnitDependency d; - Iterator i; - char *t; - - assert(u); - - if (u->manager->n_reloading <= 0) - unit_remove_transient(u); - - bus_unit_send_removed_signal(u); - - unit_done(u); - - sd_bus_slot_unref(u->match_bus_slot); - - unit_free_requires_mounts_for(u); - - SET_FOREACH(t, u->names, i) - hashmap_remove_value(u->manager->units, t, u); - - if (u->job) { - Job *j = u->job; - job_uninstall(j); - job_free(j); - } - - if (u->nop_job) { - Job *j = u->nop_job; - job_uninstall(j); - job_free(j); - } - - for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) - bidi_set_free(u, u->dependencies[d]); - - if (u->type != _UNIT_TYPE_INVALID) - LIST_REMOVE(units_by_type, u->manager->units_by_type[u->type], u); - - if (u->in_load_queue) - LIST_REMOVE(load_queue, u->manager->load_queue, u); - - if (u->in_dbus_queue) - LIST_REMOVE(dbus_queue, u->manager->dbus_unit_queue, u); - - if (u->in_cleanup_queue) - LIST_REMOVE(cleanup_queue, u->manager->cleanup_queue, u); - - if (u->in_gc_queue) { - LIST_REMOVE(gc_queue, u->manager->gc_queue, u); - u->manager->n_in_gc_queue--; - } - - if (u->in_cgroup_queue) - LIST_REMOVE(cgroup_queue, u->manager->cgroup_queue, u); - - unit_release_cgroup(u); - - (void) manager_update_failed_units(u->manager, u, false); - set_remove(u->manager->startup_units, u); - - free(u->description); - strv_free(u->documentation); - free(u->fragment_path); - free(u->source_path); - strv_free(u->dropin_paths); - free(u->instance); - - free(u->job_timeout_reboot_arg); - - set_free_free(u->names); - - unit_unwatch_all_pids(u); - - condition_free_list(u->conditions); - condition_free_list(u->asserts); - - free(u->reboot_arg); - - unit_ref_unset(&u->slice); - - while (u->refs) - unit_ref_unset(u->refs); - - free(u); -} - -UnitActiveState unit_active_state(Unit *u) { - assert(u); - - if (u->load_state == UNIT_MERGED) - return unit_active_state(unit_follow_merge(u)); - - /* After a reload it might happen that a unit is not correctly - * loaded but still has a process around. That's why we won't - * shortcut failed loading to UNIT_INACTIVE_FAILED. */ - - return UNIT_VTABLE(u)->active_state(u); -} - -const char* unit_sub_state_to_string(Unit *u) { - assert(u); - - return UNIT_VTABLE(u)->sub_state_to_string(u); -} - -static int complete_move(Set **s, Set **other) { - int r; - - assert(s); - assert(other); - - if (!*other) - return 0; - - if (*s) { - r = set_move(*s, *other); - if (r < 0) - return r; - } else { - *s = *other; - *other = NULL; - } - - return 0; -} - -static int merge_names(Unit *u, Unit *other) { - char *t; - Iterator i; - int r; - - assert(u); - assert(other); - - r = complete_move(&u->names, &other->names); - if (r < 0) - return r; - - set_free_free(other->names); - other->names = NULL; - other->id = NULL; - - SET_FOREACH(t, u->names, i) - assert_se(hashmap_replace(u->manager->units, t, u) == 0); - - return 0; -} - -static int reserve_dependencies(Unit *u, Unit *other, UnitDependency d) { - unsigned n_reserve; - - assert(u); - assert(other); - assert(d < _UNIT_DEPENDENCY_MAX); - - /* - * If u does not have this dependency set allocated, there is no need - * to reserve anything. In that case other's set will be transferred - * as a whole to u by complete_move(). - */ - if (!u->dependencies[d]) - return 0; - - /* merge_dependencies() will skip a u-on-u dependency */ - n_reserve = set_size(other->dependencies[d]) - !!set_get(other->dependencies[d], u); - - return set_reserve(u->dependencies[d], n_reserve); -} - -static void merge_dependencies(Unit *u, Unit *other, const char *other_id, UnitDependency d) { - Iterator i; - Unit *back; - int r; - - assert(u); - assert(other); - assert(d < _UNIT_DEPENDENCY_MAX); - - /* Fix backwards pointers */ - SET_FOREACH(back, other->dependencies[d], i) { - UnitDependency k; - - for (k = 0; k < _UNIT_DEPENDENCY_MAX; k++) { - /* Do not add dependencies between u and itself */ - if (back == u) { - if (set_remove(back->dependencies[k], other)) - maybe_warn_about_dependency(u, other_id, k); - } else { - r = set_remove_and_put(back->dependencies[k], other, u); - if (r == -EEXIST) - set_remove(back->dependencies[k], other); - else - assert(r >= 0 || r == -ENOENT); - } - } - } - - /* Also do not move dependencies on u to itself */ - back = set_remove(other->dependencies[d], u); - if (back) - maybe_warn_about_dependency(u, other_id, d); - - /* The move cannot fail. The caller must have performed a reservation. */ - assert_se(complete_move(&u->dependencies[d], &other->dependencies[d]) == 0); - - other->dependencies[d] = set_free(other->dependencies[d]); -} - -int unit_merge(Unit *u, Unit *other) { - UnitDependency d; - const char *other_id = NULL; - int r; - - assert(u); - assert(other); - assert(u->manager == other->manager); - assert(u->type != _UNIT_TYPE_INVALID); - - other = unit_follow_merge(other); - - if (other == u) - return 0; - - if (u->type != other->type) - return -EINVAL; - - if (!u->instance != !other->instance) - return -EINVAL; - - if (other->load_state != UNIT_STUB && - other->load_state != UNIT_NOT_FOUND) - return -EEXIST; - - if (other->job) - return -EEXIST; - - if (other->nop_job) - return -EEXIST; - - if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) - return -EEXIST; - - if (other->id) - other_id = strdupa(other->id); - - /* Make reservations to ensure merge_dependencies() won't fail */ - for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { - r = reserve_dependencies(u, other, d); - /* - * We don't rollback reservations if we fail. We don't have - * a way to undo reservations. A reservation is not a leak. - */ - if (r < 0) - return r; - } - - /* Merge names */ - r = merge_names(u, other); - if (r < 0) - return r; - - /* Redirect all references */ - while (other->refs) - unit_ref_set(other->refs, u); - - /* Merge dependencies */ - for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) - merge_dependencies(u, other, other_id, d); - - other->load_state = UNIT_MERGED; - other->merged_into = u; - - /* If there is still some data attached to the other node, we - * don't need it anymore, and can free it. */ - if (other->load_state != UNIT_STUB) - if (UNIT_VTABLE(other)->done) - UNIT_VTABLE(other)->done(other); - - unit_add_to_dbus_queue(u); - unit_add_to_cleanup_queue(other); - - return 0; -} - -int unit_merge_by_name(Unit *u, const char *name) { - Unit *other; - int r; - _cleanup_free_ char *s = NULL; - - assert(u); - assert(name); - - if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { - if (!u->instance) - return -EINVAL; - - r = unit_name_replace_instance(name, u->instance, &s); - if (r < 0) - return r; - - name = s; - } - - other = manager_get_unit(u->manager, name); - if (other) - return unit_merge(u, other); - - return unit_add_name(u, name); -} - -Unit* unit_follow_merge(Unit *u) { - assert(u); - - while (u->load_state == UNIT_MERGED) - assert_se(u = u->merged_into); - - return u; -} - -int unit_add_exec_dependencies(Unit *u, ExecContext *c) { - int r; - - assert(u); - assert(c); - - if (c->working_directory) { - r = unit_require_mounts_for(u, c->working_directory); - if (r < 0) - return r; - } - - if (c->root_directory) { - r = unit_require_mounts_for(u, c->root_directory); - if (r < 0) - return r; - } - - if (u->manager->running_as != MANAGER_SYSTEM) - return 0; - - if (c->private_tmp) { - r = unit_require_mounts_for(u, "/tmp"); - if (r < 0) - return r; - - r = unit_require_mounts_for(u, "/var/tmp"); - if (r < 0) - return r; - } - - if (c->std_output != EXEC_OUTPUT_KMSG && - c->std_output != EXEC_OUTPUT_SYSLOG && - c->std_output != EXEC_OUTPUT_JOURNAL && - c->std_output != EXEC_OUTPUT_KMSG_AND_CONSOLE && - c->std_output != EXEC_OUTPUT_SYSLOG_AND_CONSOLE && - c->std_output != EXEC_OUTPUT_JOURNAL_AND_CONSOLE && - c->std_error != EXEC_OUTPUT_KMSG && - c->std_error != EXEC_OUTPUT_SYSLOG && - c->std_error != EXEC_OUTPUT_JOURNAL && - c->std_error != EXEC_OUTPUT_KMSG_AND_CONSOLE && - c->std_error != EXEC_OUTPUT_JOURNAL_AND_CONSOLE && - c->std_error != EXEC_OUTPUT_SYSLOG_AND_CONSOLE) - return 0; - - /* If syslog or kernel logging is requested, make sure our own - * logging daemon is run first. */ - - r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true); - if (r < 0) - return r; - - return 0; -} - -const char *unit_description(Unit *u) { - assert(u); - - if (u->description) - return u->description; - - return strna(u->id); -} - -void unit_dump(Unit *u, FILE *f, const char *prefix) { - char *t, **j; - UnitDependency d; - Iterator i; - const char *prefix2; - char - timestamp0[FORMAT_TIMESTAMP_MAX], - timestamp1[FORMAT_TIMESTAMP_MAX], - timestamp2[FORMAT_TIMESTAMP_MAX], - timestamp3[FORMAT_TIMESTAMP_MAX], - timestamp4[FORMAT_TIMESTAMP_MAX], - timespan[FORMAT_TIMESPAN_MAX]; - Unit *following; - _cleanup_set_free_ Set *following_set = NULL; - int r; - - assert(u); - assert(u->type >= 0); - - prefix = strempty(prefix); - prefix2 = strjoina(prefix, "\t"); - - fprintf(f, - "%s-> Unit %s:\n" - "%s\tDescription: %s\n" - "%s\tInstance: %s\n" - "%s\tUnit Load State: %s\n" - "%s\tUnit Active State: %s\n" - "%s\nState Change Timestamp: %s\n" - "%s\tInactive Exit Timestamp: %s\n" - "%s\tActive Enter Timestamp: %s\n" - "%s\tActive Exit Timestamp: %s\n" - "%s\tInactive Enter Timestamp: %s\n" - "%s\tGC Check Good: %s\n" - "%s\tNeed Daemon Reload: %s\n" - "%s\tTransient: %s\n" - "%s\tSlice: %s\n" - "%s\tCGroup: %s\n" - "%s\tCGroup realized: %s\n" - "%s\tCGroup mask: 0x%x\n" - "%s\tCGroup members mask: 0x%x\n", - prefix, u->id, - prefix, unit_description(u), - prefix, strna(u->instance), - prefix, unit_load_state_to_string(u->load_state), - prefix, unit_active_state_to_string(unit_active_state(u)), - prefix, strna(format_timestamp(timestamp0, sizeof(timestamp0), u->state_change_timestamp.realtime)), - prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->inactive_exit_timestamp.realtime)), - prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->active_enter_timestamp.realtime)), - prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->active_exit_timestamp.realtime)), - prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->inactive_enter_timestamp.realtime)), - prefix, yes_no(unit_check_gc(u)), - prefix, yes_no(unit_need_daemon_reload(u)), - prefix, yes_no(u->transient), - prefix, strna(unit_slice_name(u)), - prefix, strna(u->cgroup_path), - prefix, yes_no(u->cgroup_realized), - prefix, u->cgroup_realized_mask, - prefix, u->cgroup_members_mask); - - SET_FOREACH(t, u->names, i) - fprintf(f, "%s\tName: %s\n", prefix, t); - - STRV_FOREACH(j, u->documentation) - fprintf(f, "%s\tDocumentation: %s\n", prefix, *j); - - following = unit_following(u); - if (following) - fprintf(f, "%s\tFollowing: %s\n", prefix, following->id); - - r = unit_following_set(u, &following_set); - if (r >= 0) { - Unit *other; - - SET_FOREACH(other, following_set, i) - fprintf(f, "%s\tFollowing Set Member: %s\n", prefix, other->id); - } - - if (u->fragment_path) - fprintf(f, "%s\tFragment Path: %s\n", prefix, u->fragment_path); - - if (u->source_path) - fprintf(f, "%s\tSource Path: %s\n", prefix, u->source_path); - - STRV_FOREACH(j, u->dropin_paths) - fprintf(f, "%s\tDropIn Path: %s\n", prefix, *j); - - if (u->job_timeout != USEC_INFINITY) - fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout, 0)); - - if (u->job_timeout_action != FAILURE_ACTION_NONE) - fprintf(f, "%s\tJob Timeout Action: %s\n", prefix, failure_action_to_string(u->job_timeout_action)); - - if (u->job_timeout_reboot_arg) - fprintf(f, "%s\tJob Timeout Reboot Argument: %s\n", prefix, u->job_timeout_reboot_arg); - - condition_dump_list(u->conditions, f, prefix, condition_type_to_string); - condition_dump_list(u->asserts, f, prefix, assert_type_to_string); - - if (dual_timestamp_is_set(&u->condition_timestamp)) - fprintf(f, - "%s\tCondition Timestamp: %s\n" - "%s\tCondition Result: %s\n", - prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->condition_timestamp.realtime)), - prefix, yes_no(u->condition_result)); - - if (dual_timestamp_is_set(&u->assert_timestamp)) - fprintf(f, - "%s\tAssert Timestamp: %s\n" - "%s\tAssert Result: %s\n", - prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->assert_timestamp.realtime)), - prefix, yes_no(u->assert_result)); - - for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { - Unit *other; - - SET_FOREACH(other, u->dependencies[d], i) - fprintf(f, "%s\t%s: %s\n", prefix, unit_dependency_to_string(d), other->id); - } - - if (!strv_isempty(u->requires_mounts_for)) { - fprintf(f, - "%s\tRequiresMountsFor:", prefix); - - STRV_FOREACH(j, u->requires_mounts_for) - fprintf(f, " %s", *j); - - fputs("\n", f); - } - - if (u->load_state == UNIT_LOADED) { - - fprintf(f, - "%s\tStopWhenUnneeded: %s\n" - "%s\tRefuseManualStart: %s\n" - "%s\tRefuseManualStop: %s\n" - "%s\tDefaultDependencies: %s\n" - "%s\tOnFailureJobMode: %s\n" - "%s\tIgnoreOnIsolate: %s\n", - prefix, yes_no(u->stop_when_unneeded), - prefix, yes_no(u->refuse_manual_start), - prefix, yes_no(u->refuse_manual_stop), - prefix, yes_no(u->default_dependencies), - prefix, job_mode_to_string(u->on_failure_job_mode), - prefix, yes_no(u->ignore_on_isolate)); - - if (UNIT_VTABLE(u)->dump) - UNIT_VTABLE(u)->dump(u, f, prefix2); - - } else if (u->load_state == UNIT_MERGED) - fprintf(f, - "%s\tMerged into: %s\n", - prefix, u->merged_into->id); - else if (u->load_state == UNIT_ERROR) - fprintf(f, "%s\tLoad Error Code: %s\n", prefix, strerror(-u->load_error)); - - - if (u->job) - job_dump(u->job, f, prefix2); - - if (u->nop_job) - job_dump(u->nop_job, f, prefix2); - -} - -/* Common implementation for multiple backends */ -int unit_load_fragment_and_dropin(Unit *u) { - int r; - - assert(u); - - /* Load a .{service,socket,...} file */ - r = unit_load_fragment(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_STUB) - return -ENOENT; - - /* Load drop-in directory data */ - r = unit_load_dropin(unit_follow_merge(u)); - if (r < 0) - return r; - - return 0; -} - -/* Common implementation for multiple backends */ -int unit_load_fragment_and_dropin_optional(Unit *u) { - int r; - - assert(u); - - /* Same as unit_load_fragment_and_dropin(), but whether - * something can be loaded or not doesn't matter. */ - - /* Load a .service file */ - r = unit_load_fragment(u); - if (r < 0) - return r; - - if (u->load_state == UNIT_STUB) - u->load_state = UNIT_LOADED; - - /* Load drop-in directory data */ - r = unit_load_dropin(unit_follow_merge(u)); - if (r < 0) - return r; - - return 0; -} - -int unit_add_default_target_dependency(Unit *u, Unit *target) { - assert(u); - assert(target); - - if (target->type != UNIT_TARGET) - return 0; - - /* Only add the dependency if both units are loaded, so that - * that loop check below is reliable */ - if (u->load_state != UNIT_LOADED || - target->load_state != UNIT_LOADED) - return 0; - - /* If either side wants no automatic dependencies, then let's - * skip this */ - if (!u->default_dependencies || - !target->default_dependencies) - return 0; - - /* Don't create loops */ - if (set_get(target->dependencies[UNIT_BEFORE], u)) - return 0; - - return unit_add_dependency(target, UNIT_AFTER, u, true); -} - -static int unit_add_target_dependencies(Unit *u) { - - static const UnitDependency deps[] = { - UNIT_REQUIRED_BY, - UNIT_REQUISITE_OF, - UNIT_WANTED_BY, - UNIT_BOUND_BY - }; - - Unit *target; - Iterator i; - unsigned k; - int r = 0; - - assert(u); - - for (k = 0; k < ELEMENTSOF(deps); k++) - SET_FOREACH(target, u->dependencies[deps[k]], i) { - r = unit_add_default_target_dependency(u, target); - if (r < 0) - return r; - } - - return r; -} - -static int unit_add_slice_dependencies(Unit *u) { - assert(u); - - if (!UNIT_HAS_CGROUP_CONTEXT(u)) - return 0; - - if (UNIT_ISSET(u->slice)) - return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, UNIT_DEREF(u->slice), true); - - if (unit_has_name(u, SPECIAL_ROOT_SLICE)) - return 0; - - return unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, SPECIAL_ROOT_SLICE, NULL, true); -} - -static int unit_add_mount_dependencies(Unit *u) { - char **i; - int r; - - assert(u); - - STRV_FOREACH(i, u->requires_mounts_for) { - char prefix[strlen(*i) + 1]; - - PATH_FOREACH_PREFIX_MORE(prefix, *i) { - _cleanup_free_ char *p = NULL; - Unit *m; - - r = unit_name_from_path(prefix, ".mount", &p); - if (r < 0) - return r; - - m = manager_get_unit(u->manager, p); - if (!m) { - /* Make sure to load the mount unit if - * it exists. If so the dependencies - * on this unit will be added later - * during the loading of the mount - * unit. */ - (void) manager_load_unit_prepare(u->manager, p, NULL, NULL, &m); - continue; - } - if (m == u) - continue; - - if (m->load_state != UNIT_LOADED) - continue; - - r = unit_add_dependency(u, UNIT_AFTER, m, true); - if (r < 0) - return r; - - if (m->fragment_path) { - r = unit_add_dependency(u, UNIT_REQUIRES, m, true); - if (r < 0) - return r; - } - } - } - - return 0; -} - -static int unit_add_startup_units(Unit *u) { - CGroupContext *c; - int r; - - c = unit_get_cgroup_context(u); - if (!c) - return 0; - - if (c->startup_cpu_shares == CGROUP_CPU_SHARES_INVALID && - c->startup_blockio_weight == CGROUP_BLKIO_WEIGHT_INVALID) - return 0; - - r = set_ensure_allocated(&u->manager->startup_units, NULL); - if (r < 0) - return r; - - return set_put(u->manager->startup_units, u); -} - -int unit_load(Unit *u) { - int r; - - assert(u); - - if (u->in_load_queue) { - LIST_REMOVE(load_queue, u->manager->load_queue, u); - u->in_load_queue = false; - } - - if (u->type == _UNIT_TYPE_INVALID) - return -EINVAL; - - if (u->load_state != UNIT_STUB) - return 0; - - if (UNIT_VTABLE(u)->load) { - r = UNIT_VTABLE(u)->load(u); - if (r < 0) - goto fail; - } - - if (u->load_state == UNIT_STUB) { - r = -ENOENT; - goto fail; - } - - if (u->load_state == UNIT_LOADED) { - - r = unit_add_target_dependencies(u); - if (r < 0) - goto fail; - - r = unit_add_slice_dependencies(u); - if (r < 0) - goto fail; - - r = unit_add_mount_dependencies(u); - if (r < 0) - goto fail; - - r = unit_add_startup_units(u); - if (r < 0) - goto fail; - - if (u->on_failure_job_mode == JOB_ISOLATE && set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { - log_unit_error(u, "More than one OnFailure= dependencies specified but OnFailureJobMode=isolate set. Refusing."); - r = -EINVAL; - goto fail; - } - - unit_update_cgroup_members_masks(u); - } - - assert((u->load_state != UNIT_MERGED) == !u->merged_into); - - unit_add_to_dbus_queue(unit_follow_merge(u)); - unit_add_to_gc_queue(u); - - return 0; - -fail: - u->load_state = u->load_state == UNIT_STUB ? UNIT_NOT_FOUND : UNIT_ERROR; - u->load_error = r; - unit_add_to_dbus_queue(u); - unit_add_to_gc_queue(u); - - log_unit_debug_errno(u, r, "Failed to load configuration: %m"); - - return r; -} - -static bool unit_condition_test_list(Unit *u, Condition *first, const char *(*to_string)(ConditionType t)) { - Condition *c; - int triggered = -1; - - assert(u); - assert(to_string); - - /* If the condition list is empty, then it is true */ - if (!first) - return true; - - /* Otherwise, if all of the non-trigger conditions apply and - * if any of the trigger conditions apply (unless there are - * none) we return true */ - LIST_FOREACH(conditions, c, first) { - int r; - - r = condition_test(c); - if (r < 0) - log_unit_warning(u, - "Couldn't determine result for %s=%s%s%s, assuming failed: %m", - to_string(c->type), - c->trigger ? "|" : "", - c->negate ? "!" : "", - c->parameter); - else - log_unit_debug(u, - "%s=%s%s%s %s.", - to_string(c->type), - c->trigger ? "|" : "", - c->negate ? "!" : "", - c->parameter, - condition_result_to_string(c->result)); - - if (!c->trigger && r <= 0) - return false; - - if (c->trigger && triggered <= 0) - triggered = r > 0; - } - - return triggered != 0; -} - -static bool unit_condition_test(Unit *u) { - assert(u); - - dual_timestamp_get(&u->condition_timestamp); - u->condition_result = unit_condition_test_list(u, u->conditions, condition_type_to_string); - - return u->condition_result; -} - -static bool unit_assert_test(Unit *u) { - assert(u); - - dual_timestamp_get(&u->assert_timestamp); - u->assert_result = unit_condition_test_list(u, u->asserts, assert_type_to_string); - - return u->assert_result; -} - -void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) { - DISABLE_WARNING_FORMAT_NONLITERAL; - manager_status_printf(u->manager, STATUS_TYPE_NORMAL, status, unit_status_msg_format, unit_description(u)); - REENABLE_WARNING; -} - -_pure_ static const char* unit_get_status_message_format(Unit *u, JobType t) { - const char *format; - const UnitStatusMessageFormats *format_table; - - assert(u); - assert(IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)); - - if (t != JOB_RELOAD) { - format_table = &UNIT_VTABLE(u)->status_message_formats; - if (format_table) { - format = format_table->starting_stopping[t == JOB_STOP]; - if (format) - return format; - } - } - - /* Return generic strings */ - if (t == JOB_START) - return "Starting %s."; - else if (t == JOB_STOP) - return "Stopping %s."; - else - return "Reloading %s."; -} - -static void unit_status_print_starting_stopping(Unit *u, JobType t) { - const char *format; - - assert(u); - - /* Reload status messages have traditionally not been printed to console. */ - if (!IN_SET(t, JOB_START, JOB_STOP)) - return; - - format = unit_get_status_message_format(u, t); - - DISABLE_WARNING_FORMAT_NONLITERAL; - unit_status_printf(u, "", format); - REENABLE_WARNING; -} - -static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { - const char *format; - char buf[LINE_MAX]; - sd_id128_t mid; - - assert(u); - - if (!IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)) - return; - - if (log_on_console()) - return; - - /* We log status messages for all units and all operations. */ - - format = unit_get_status_message_format(u, t); - - DISABLE_WARNING_FORMAT_NONLITERAL; - xsprintf(buf, format, unit_description(u)); - REENABLE_WARNING; - - mid = t == JOB_START ? SD_MESSAGE_UNIT_STARTING : - t == JOB_STOP ? SD_MESSAGE_UNIT_STOPPING : - SD_MESSAGE_UNIT_RELOADING; - - /* Note that we deliberately use LOG_MESSAGE() instead of - * LOG_UNIT_MESSAGE() here, since this is supposed to mimic - * closely what is written to screen using the status output, - * which is supposed the highest level, friendliest output - * possible, which means we should avoid the low-level unit - * name. */ - log_struct(LOG_INFO, - LOG_MESSAGE_ID(mid), - LOG_UNIT_ID(u), - LOG_MESSAGE("%s", buf), - NULL); -} - -void unit_status_emit_starting_stopping_reloading(Unit *u, JobType t) { - assert(u); - assert(t >= 0); - assert(t < _JOB_TYPE_MAX); - - unit_status_log_starting_stopping_reloading(u, t); - unit_status_print_starting_stopping(u, t); -} - -static int unit_start_limit_test(Unit *u) { - assert(u); - - if (ratelimit_test(&u->start_limit)) { - u->start_limit_hit = false; - return 0; - } - - log_unit_warning(u, "Start request repeated too quickly."); - u->start_limit_hit = true; - - return failure_action(u->manager, u->start_limit_action, u->reboot_arg); -} - -/* Errors: - * -EBADR: This unit type does not support starting. - * -EALREADY: Unit is already started. - * -EAGAIN: An operation is already in progress. Retry later. - * -ECANCELED: Too many requests for now. - * -EPROTO: Assert failed - * -EINVAL: Unit not loaded - * -EOPNOTSUPP: Unit type not supported - */ -int unit_start(Unit *u) { - UnitActiveState state; - Unit *following; - int r; - - assert(u); - - /* If this is already started, then this will succeed. Note - * that this will even succeed if this unit is not startable - * by the user. This is relied on to detect when we need to - * wait for units and when waiting is finished. */ - state = unit_active_state(u); - if (UNIT_IS_ACTIVE_OR_RELOADING(state)) - return -EALREADY; - - /* Make sure we don't enter a busy loop of some kind. */ - r = unit_start_limit_test(u); - if (r < 0) - return r; - - /* Units that aren't loaded cannot be started */ - if (u->load_state != UNIT_LOADED) - return -EINVAL; - - /* If the conditions failed, don't do anything at all. If we - * already are activating this call might still be useful to - * speed up activation in case there is some hold-off time, - * but we don't want to recheck the condition in that case. */ - if (state != UNIT_ACTIVATING && - !unit_condition_test(u)) { - log_unit_debug(u, "Starting requested but condition failed. Not starting unit."); - return -EALREADY; - } - - /* If the asserts failed, fail the entire job */ - if (state != UNIT_ACTIVATING && - !unit_assert_test(u)) { - log_unit_notice(u, "Starting requested but asserts failed."); - return -EPROTO; - } - - /* Units of types that aren't supported cannot be - * started. Note that we do this test only after the condition - * checks, so that we rather return condition check errors - * (which are usually not considered a true failure) than "not - * supported" errors (which are considered a failure). - */ - if (!unit_supported(u)) - return -EOPNOTSUPP; - - /* Forward to the main object, if we aren't it. */ - following = unit_following(u); - if (following) { - log_unit_debug(u, "Redirecting start request from %s to %s.", u->id, following->id); - return unit_start(following); - } - - /* If it is stopped, but we cannot start it, then fail */ - if (!UNIT_VTABLE(u)->start) - return -EBADR; - - /* We don't suppress calls to ->start() here when we are - * already starting, to allow this request to be used as a - * "hurry up" call, for example when the unit is in some "auto - * restart" state where it waits for a holdoff timer to elapse - * before it will start again. */ - - unit_add_to_dbus_queue(u); - - return UNIT_VTABLE(u)->start(u); -} - -bool unit_can_start(Unit *u) { - assert(u); - - if (u->load_state != UNIT_LOADED) - return false; - - if (!unit_supported(u)) - return false; - - return !!UNIT_VTABLE(u)->start; -} - -bool unit_can_isolate(Unit *u) { - assert(u); - - return unit_can_start(u) && - u->allow_isolate; -} - -/* Errors: - * -EBADR: This unit type does not support stopping. - * -EALREADY: Unit is already stopped. - * -EAGAIN: An operation is already in progress. Retry later. - */ -int unit_stop(Unit *u) { - UnitActiveState state; - Unit *following; - - assert(u); - - state = unit_active_state(u); - if (UNIT_IS_INACTIVE_OR_FAILED(state)) - return -EALREADY; - - following = unit_following(u); - if (following) { - log_unit_debug(u, "Redirecting stop request from %s to %s.", u->id, following->id); - return unit_stop(following); - } - - if (!UNIT_VTABLE(u)->stop) - return -EBADR; - - unit_add_to_dbus_queue(u); - - return UNIT_VTABLE(u)->stop(u); -} - -/* Errors: - * -EBADR: This unit type does not support reloading. - * -ENOEXEC: Unit is not started. - * -EAGAIN: An operation is already in progress. Retry later. - */ -int unit_reload(Unit *u) { - UnitActiveState state; - Unit *following; - - assert(u); - - if (u->load_state != UNIT_LOADED) - return -EINVAL; - - if (!unit_can_reload(u)) - return -EBADR; - - state = unit_active_state(u); - if (state == UNIT_RELOADING) - return -EALREADY; - - if (state != UNIT_ACTIVE) { - log_unit_warning(u, "Unit cannot be reloaded because it is inactive."); - return -ENOEXEC; - } - - following = unit_following(u); - if (following) { - log_unit_debug(u, "Redirecting reload request from %s to %s.", u->id, following->id); - return unit_reload(following); - } - - unit_add_to_dbus_queue(u); - - return UNIT_VTABLE(u)->reload(u); -} - -bool unit_can_reload(Unit *u) { - assert(u); - - if (!UNIT_VTABLE(u)->reload) - return false; - - if (!UNIT_VTABLE(u)->can_reload) - return true; - - return UNIT_VTABLE(u)->can_reload(u); -} - -static void unit_check_unneeded(Unit *u) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - static const UnitDependency needed_dependencies[] = { - UNIT_REQUIRED_BY, - UNIT_REQUISITE_OF, - UNIT_WANTED_BY, - UNIT_BOUND_BY, - }; - - Unit *other; - Iterator i; - unsigned j; - int r; - - assert(u); - - /* If this service shall be shut down when unneeded then do - * so. */ - - if (!u->stop_when_unneeded) - return; - - if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) - return; - - for (j = 0; j < ELEMENTSOF(needed_dependencies); j++) - SET_FOREACH(other, u->dependencies[needed_dependencies[j]], i) - if (unit_active_or_pending(other)) - return; - - /* If stopping a unit fails continously 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)) { - log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently."); - return; - } - - log_unit_info(u, "Unit not needed anymore. Stopping."); - - /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */ - r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL); - if (r < 0) - log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); -} - -static void unit_check_binds_to(Unit *u) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool stop = false; - Unit *other; - Iterator i; - int r; - - assert(u); - - if (u->job) - return; - - if (unit_active_state(u) != UNIT_ACTIVE) - return; - - SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) { - if (other->job) - continue; - - if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) - continue; - - stop = true; - break; - } - - if (!stop) - return; - - /* If stopping a unit fails continously 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)) { - log_unit_warning(u, "Unit is bound to inactive unit %s, but not stopping since we tried this too often recently.", other->id); - return; - } - - assert(other); - log_unit_info(u, "Unit is bound to inactive unit %s. Stopping, too.", other->id); - - /* A unit we need to run is gone. Sniff. Let's stop this. */ - r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL); - if (r < 0) - log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); -} - -static void retroactively_start_dependencies(Unit *u) { - Iterator i; - Unit *other; - - assert(u); - assert(UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))); - - SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i) - if (!set_get(u->dependencies[UNIT_AFTER], other) && - !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL); - - SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) - if (!set_get(u->dependencies[UNIT_AFTER], other) && - !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL); - - SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) - if (!set_get(u->dependencies[UNIT_AFTER], other) && - !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_START, other, JOB_FAIL, NULL, NULL); - - SET_FOREACH(other, u->dependencies[UNIT_CONFLICTS], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); - - SET_FOREACH(other, u->dependencies[UNIT_CONFLICTED_BY], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); -} - -static void retroactively_stop_dependencies(Unit *u) { - Iterator i; - Unit *other; - - assert(u); - assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); - - /* Pull down units which are bound to us recursively if enabled */ - SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); -} - -static void check_unneeded_dependencies(Unit *u) { - Iterator i; - Unit *other; - - assert(u); - assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); - - /* Garbage collect services that might not be needed anymore, if enabled */ - SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - unit_check_unneeded(other); - SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - unit_check_unneeded(other); - SET_FOREACH(other, u->dependencies[UNIT_REQUISITE], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - unit_check_unneeded(other); - SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) - if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) - unit_check_unneeded(other); -} - -void unit_start_on_failure(Unit *u) { - Unit *other; - Iterator i; - - assert(u); - - if (set_size(u->dependencies[UNIT_ON_FAILURE]) <= 0) - return; - - log_unit_info(u, "Triggering OnFailure= dependencies."); - - SET_FOREACH(other, u->dependencies[UNIT_ON_FAILURE], i) { - int r; - - r = manager_add_job(u->manager, JOB_START, other, u->on_failure_job_mode, NULL, NULL); - if (r < 0) - log_unit_error_errno(u, r, "Failed to enqueue OnFailure= job: %m"); - } -} - -void unit_trigger_notify(Unit *u) { - Unit *other; - Iterator i; - - assert(u); - - SET_FOREACH(other, u->dependencies[UNIT_TRIGGERED_BY], i) - if (UNIT_VTABLE(other)->trigger_notify) - UNIT_VTABLE(other)->trigger_notify(other, u); -} - -void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) { - Manager *m; - bool unexpected; - - assert(u); - assert(os < _UNIT_ACTIVE_STATE_MAX); - assert(ns < _UNIT_ACTIVE_STATE_MAX); - - /* Note that this is called for all low-level state changes, - * even if they might map to the same high-level - * UnitActiveState! That means that ns == os is an expected - * behavior here. For example: if a mount point is remounted - * this function will be called too! */ - - m = u->manager; - - /* Update timestamps for state changes */ - if (m->n_reloading <= 0) { - dual_timestamp_get(&u->state_change_timestamp); - - if (UNIT_IS_INACTIVE_OR_FAILED(os) && !UNIT_IS_INACTIVE_OR_FAILED(ns)) - u->inactive_exit_timestamp = u->state_change_timestamp; - else if (!UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_INACTIVE_OR_FAILED(ns)) - u->inactive_enter_timestamp = u->state_change_timestamp; - - if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns)) - u->active_enter_timestamp = u->state_change_timestamp; - else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns)) - u->active_exit_timestamp = u->state_change_timestamp; - } - - /* Keep track of failed units */ - (void) manager_update_failed_units(u->manager, u, ns == UNIT_FAILED); - - /* Make sure the cgroup is always removed when we become inactive */ - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - unit_prune_cgroup(u); - - /* Note that this doesn't apply to RemainAfterExit services exiting - * successfully, since there's no change of state in that case. Which is - * why it is handled in service_set_state() */ - if (UNIT_IS_INACTIVE_OR_FAILED(os) != UNIT_IS_INACTIVE_OR_FAILED(ns)) { - ExecContext *ec; - - ec = unit_get_exec_context(u); - if (ec && exec_context_may_touch_console(ec)) { - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) { - m->n_on_console --; - - if (m->n_on_console == 0) - /* unset no_console_output flag, since the console is free */ - m->no_console_output = false; - } else - m->n_on_console ++; - } - } - - if (u->job) { - unexpected = false; - - if (u->job->state == JOB_WAITING) - - /* So we reached a different state for this - * job. Let's see if we can run it now if it - * failed previously due to EAGAIN. */ - job_add_to_run_queue(u->job); - - /* Let's check whether this state change constitutes a - * finished job, or maybe contradicts a running job and - * hence needs to invalidate jobs. */ - - switch (u->job->type) { - - case JOB_START: - case JOB_VERIFY_ACTIVE: - - if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) - job_finish_and_invalidate(u->job, JOB_DONE, true); - else if (u->job->state == JOB_RUNNING && ns != UNIT_ACTIVATING) { - unexpected = true; - - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true); - } - - break; - - case JOB_RELOAD: - case JOB_RELOAD_OR_START: - case JOB_TRY_RELOAD: - - if (u->job->state == JOB_RUNNING) { - if (ns == UNIT_ACTIVE) - job_finish_and_invalidate(u->job, reload_success ? JOB_DONE : JOB_FAILED, true); - else if (ns != UNIT_ACTIVATING && ns != UNIT_RELOADING) { - unexpected = true; - - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true); - } - } - - break; - - case JOB_STOP: - case JOB_RESTART: - case JOB_TRY_RESTART: - - if (UNIT_IS_INACTIVE_OR_FAILED(ns)) - job_finish_and_invalidate(u->job, JOB_DONE, true); - else if (u->job->state == JOB_RUNNING && ns != UNIT_DEACTIVATING) { - unexpected = true; - job_finish_and_invalidate(u->job, JOB_FAILED, true); - } - - break; - - default: - assert_not_reached("Job type unknown"); - } - - } else - unexpected = true; - - if (m->n_reloading <= 0) { - - /* If this state change happened without being - * requested by a job, then let's retroactively start - * or stop dependencies. We skip that step when - * deserializing, since we don't want to create any - * additional jobs just because something is already - * activated. */ - - if (unexpected) { - if (UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_ACTIVE_OR_ACTIVATING(ns)) - retroactively_start_dependencies(u); - else if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) - retroactively_stop_dependencies(u); - } - - /* stop unneeded units regardless if going down was expected or not */ - if (UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) - check_unneeded_dependencies(u); - - if (ns != os && ns == UNIT_FAILED) { - log_unit_notice(u, "Unit entered failed state."); - unit_start_on_failure(u); - } - } - - /* Some names are special */ - if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) { - - if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) - /* The bus might have just become available, - * hence try to connect to it, if we aren't - * yet connected. */ - bus_init(m, true); - - if (u->type == UNIT_SERVICE && - !UNIT_IS_ACTIVE_OR_RELOADING(os) && - m->n_reloading <= 0) { - /* Write audit record if we have just finished starting up */ - manager_send_unit_audit(m, u, AUDIT_SERVICE_START, true); - u->in_audit = true; - } - - if (!UNIT_IS_ACTIVE_OR_RELOADING(os)) - manager_send_unit_plymouth(m, u); - - } else { - - /* We don't care about D-Bus here, since we'll get an - * asynchronous notification for it anyway. */ - - if (u->type == UNIT_SERVICE && - UNIT_IS_INACTIVE_OR_FAILED(ns) && - !UNIT_IS_INACTIVE_OR_FAILED(os) && - m->n_reloading <= 0) { - - /* Hmm, if there was no start record written - * write it now, so that we always have a nice - * pair */ - if (!u->in_audit) { - manager_send_unit_audit(m, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE); - - if (ns == UNIT_INACTIVE) - manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, true); - } else - /* Write audit record if we have just finished shutting down */ - manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); - - u->in_audit = false; - } - } - - manager_recheck_journal(m); - unit_trigger_notify(u); - - if (u->manager->n_reloading <= 0) { - /* Maybe we finished startup and are now ready for - * being stopped because unneeded? */ - unit_check_unneeded(u); - - /* Maybe we finished startup, but something we needed - * has vanished? Let's die then. (This happens when - * something BindsTo= to a Type=oneshot unit, as these - * units go directly from starting to inactive, - * without ever entering started.) */ - unit_check_binds_to(u); - } - - unit_add_to_dbus_queue(u); - unit_add_to_gc_queue(u); -} - -int unit_watch_pid(Unit *u, pid_t pid) { - int q, r; - - assert(u); - assert(pid >= 1); - - /* Watch a specific PID. We only support one or two units - * watching each PID for now, not more. */ - - r = set_ensure_allocated(&u->pids, NULL); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&u->manager->watch_pids1, NULL); - if (r < 0) - return r; - - r = hashmap_put(u->manager->watch_pids1, PID_TO_PTR(pid), u); - if (r == -EEXIST) { - r = hashmap_ensure_allocated(&u->manager->watch_pids2, NULL); - if (r < 0) - return r; - - r = hashmap_put(u->manager->watch_pids2, PID_TO_PTR(pid), u); - } - - q = set_put(u->pids, PID_TO_PTR(pid)); - if (q < 0) - return q; - - return r; -} - -void unit_unwatch_pid(Unit *u, pid_t pid) { - assert(u); - assert(pid >= 1); - - (void) hashmap_remove_value(u->manager->watch_pids1, PID_TO_PTR(pid), u); - (void) hashmap_remove_value(u->manager->watch_pids2, PID_TO_PTR(pid), u); - (void) set_remove(u->pids, PID_TO_PTR(pid)); -} - -void unit_unwatch_all_pids(Unit *u) { - assert(u); - - while (!set_isempty(u->pids)) - unit_unwatch_pid(u, PTR_TO_PID(set_first(u->pids))); - - u->pids = set_free(u->pids); -} - -void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2) { - Iterator i; - void *e; - - assert(u); - - /* Cleans dead PIDs from our list */ - - SET_FOREACH(e, u->pids, i) { - pid_t pid = PTR_TO_PID(e); - - if (pid == except1 || pid == except2) - continue; - - if (!pid_is_unwaited(pid)) - unit_unwatch_pid(u, pid); - } -} - -bool unit_job_is_applicable(Unit *u, JobType j) { - assert(u); - assert(j >= 0 && j < _JOB_TYPE_MAX); - - switch (j) { - - case JOB_VERIFY_ACTIVE: - case JOB_START: - case JOB_STOP: - case JOB_NOP: - return true; - - case JOB_RESTART: - case JOB_TRY_RESTART: - return unit_can_start(u); - - case JOB_RELOAD: - case JOB_TRY_RELOAD: - return unit_can_reload(u); - - case JOB_RELOAD_OR_START: - return unit_can_reload(u) && unit_can_start(u); - - default: - assert_not_reached("Invalid job type"); - } -} - -static void maybe_warn_about_dependency(Unit *u, const char *other, UnitDependency dependency) { - assert(u); - - /* Only warn about some unit types */ - if (!IN_SET(dependency, UNIT_CONFLICTS, UNIT_CONFLICTED_BY, UNIT_BEFORE, UNIT_AFTER, UNIT_ON_FAILURE, UNIT_TRIGGERS, UNIT_TRIGGERED_BY)) - return; - - if (streq_ptr(u->id, other)) - log_unit_warning(u, "Dependency %s=%s dropped", unit_dependency_to_string(dependency), u->id); - else - log_unit_warning(u, "Dependency %s=%s dropped, merged into %s", unit_dependency_to_string(dependency), strna(other), u->id); -} - -int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference) { - - static const UnitDependency inverse_table[_UNIT_DEPENDENCY_MAX] = { - [UNIT_REQUIRES] = UNIT_REQUIRED_BY, - [UNIT_WANTS] = UNIT_WANTED_BY, - [UNIT_REQUISITE] = UNIT_REQUISITE_OF, - [UNIT_BINDS_TO] = UNIT_BOUND_BY, - [UNIT_PART_OF] = UNIT_CONSISTS_OF, - [UNIT_REQUIRED_BY] = UNIT_REQUIRES, - [UNIT_REQUISITE_OF] = UNIT_REQUISITE, - [UNIT_WANTED_BY] = UNIT_WANTS, - [UNIT_BOUND_BY] = UNIT_BINDS_TO, - [UNIT_CONSISTS_OF] = UNIT_PART_OF, - [UNIT_CONFLICTS] = UNIT_CONFLICTED_BY, - [UNIT_CONFLICTED_BY] = UNIT_CONFLICTS, - [UNIT_BEFORE] = UNIT_AFTER, - [UNIT_AFTER] = UNIT_BEFORE, - [UNIT_ON_FAILURE] = _UNIT_DEPENDENCY_INVALID, - [UNIT_REFERENCES] = UNIT_REFERENCED_BY, - [UNIT_REFERENCED_BY] = UNIT_REFERENCES, - [UNIT_TRIGGERS] = UNIT_TRIGGERED_BY, - [UNIT_TRIGGERED_BY] = UNIT_TRIGGERS, - [UNIT_PROPAGATES_RELOAD_TO] = UNIT_RELOAD_PROPAGATED_FROM, - [UNIT_RELOAD_PROPAGATED_FROM] = UNIT_PROPAGATES_RELOAD_TO, - [UNIT_JOINS_NAMESPACE_OF] = UNIT_JOINS_NAMESPACE_OF, - }; - int r, q = 0, v = 0, w = 0; - Unit *orig_u = u, *orig_other = other; - - assert(u); - assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX); - assert(other); - - u = unit_follow_merge(u); - other = unit_follow_merge(other); - - /* We won't allow dependencies on ourselves. We will not - * consider them an error however. */ - if (u == other) { - maybe_warn_about_dependency(orig_u, orig_other->id, d); - return 0; - } - - r = set_ensure_allocated(&u->dependencies[d], NULL); - if (r < 0) - return r; - - if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) { - r = set_ensure_allocated(&other->dependencies[inverse_table[d]], NULL); - if (r < 0) - return r; - } - - if (add_reference) { - r = set_ensure_allocated(&u->dependencies[UNIT_REFERENCES], NULL); - if (r < 0) - return r; - - r = set_ensure_allocated(&other->dependencies[UNIT_REFERENCED_BY], NULL); - if (r < 0) - return r; - } - - q = set_put(u->dependencies[d], other); - if (q < 0) - return q; - - if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID && inverse_table[d] != d) { - v = set_put(other->dependencies[inverse_table[d]], u); - if (v < 0) { - r = v; - goto fail; - } - } - - if (add_reference) { - w = set_put(u->dependencies[UNIT_REFERENCES], other); - if (w < 0) { - r = w; - goto fail; - } - - r = set_put(other->dependencies[UNIT_REFERENCED_BY], u); - if (r < 0) - goto fail; - } - - unit_add_to_dbus_queue(u); - return 0; - -fail: - if (q > 0) - set_remove(u->dependencies[d], other); - - if (v > 0) - set_remove(other->dependencies[inverse_table[d]], u); - - if (w > 0) - set_remove(u->dependencies[UNIT_REFERENCES], other); - - return r; -} - -int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference) { - int r; - - assert(u); - - r = unit_add_dependency(u, d, other, add_reference); - if (r < 0) - return r; - - return unit_add_dependency(u, e, other, add_reference); -} - -static int resolve_template(Unit *u, const char *name, const char*path, char **buf, const char **ret) { - int r; - - assert(u); - assert(name || path); - assert(buf); - assert(ret); - - if (!name) - name = basename(path); - - if (!unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { - *buf = NULL; - *ret = name; - return 0; - } - - if (u->instance) - r = unit_name_replace_instance(name, u->instance, buf); - else { - _cleanup_free_ char *i = NULL; - - r = unit_name_to_prefix(u->id, &i); - if (r < 0) - return r; - - r = unit_name_replace_instance(name, i, buf); - } - if (r < 0) - return r; - - *ret = *buf; - return 0; -} - -int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { - _cleanup_free_ char *buf = NULL; - Unit *other; - int r; - - assert(u); - assert(name || path); - - r = resolve_template(u, name, path, &buf, &name); - if (r < 0) - return r; - - r = manager_load_unit(u->manager, name, path, NULL, &other); - if (r < 0) - return r; - - return unit_add_dependency(u, d, other, add_reference); -} - -int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { - _cleanup_free_ char *buf = NULL; - Unit *other; - int r; - - assert(u); - assert(name || path); - - r = resolve_template(u, name, path, &buf, &name); - if (r < 0) - return r; - - r = manager_load_unit(u->manager, name, path, NULL, &other); - if (r < 0) - return r; - - return unit_add_two_dependencies(u, d, e, other, add_reference); -} - -int set_unit_path(const char *p) { - /* This is mostly for debug purposes */ - if (setenv("SYSTEMD_UNIT_PATH", p, 1) < 0) - return -errno; - - return 0; -} - -char *unit_dbus_path(Unit *u) { - assert(u); - - if (!u->id) - return NULL; - - return unit_dbus_path_from_name(u->id); -} - -int unit_set_slice(Unit *u, Unit *slice) { - assert(u); - assert(slice); - - /* Sets the unit slice if it has not been set before. Is extra - * careful, to only allow this for units that actually have a - * cgroup context. Also, we don't allow to set this for slices - * (since the parent slice is derived from the name). Make - * sure the unit we set is actually a slice. */ - - if (!UNIT_HAS_CGROUP_CONTEXT(u)) - return -EOPNOTSUPP; - - if (u->type == UNIT_SLICE) - return -EINVAL; - - if (unit_active_state(u) != UNIT_INACTIVE) - return -EBUSY; - - if (slice->type != UNIT_SLICE) - return -EINVAL; - - if (unit_has_name(u, SPECIAL_INIT_SCOPE) && - !unit_has_name(slice, SPECIAL_ROOT_SLICE)) - return -EPERM; - - if (UNIT_DEREF(u->slice) == slice) - return 0; - - if (UNIT_ISSET(u->slice)) - return -EBUSY; - - unit_ref_set(&u->slice, slice); - return 1; -} - -int unit_set_default_slice(Unit *u) { - _cleanup_free_ char *b = NULL; - const char *slice_name; - Unit *slice; - int r; - - assert(u); - - if (UNIT_ISSET(u->slice)) - return 0; - - if (u->instance) { - _cleanup_free_ char *prefix = NULL, *escaped = NULL; - - /* Implicitly place all instantiated units in their - * own per-template slice */ - - r = unit_name_to_prefix(u->id, &prefix); - if (r < 0) - return r; - - /* The prefix is already escaped, but it might include - * "-" which has a special meaning for slice units, - * hence escape it here extra. */ - escaped = unit_name_escape(prefix); - if (!escaped) - return -ENOMEM; - - if (u->manager->running_as == MANAGER_SYSTEM) - b = strjoin("system-", escaped, ".slice", NULL); - else - b = strappend(escaped, ".slice"); - if (!b) - return -ENOMEM; - - slice_name = b; - } else - slice_name = - u->manager->running_as == MANAGER_SYSTEM && !unit_has_name(u, SPECIAL_INIT_SCOPE) - ? SPECIAL_SYSTEM_SLICE - : SPECIAL_ROOT_SLICE; - - r = manager_load_unit(u->manager, slice_name, NULL, NULL, &slice); - if (r < 0) - return r; - - return unit_set_slice(u, slice); -} - -const char *unit_slice_name(Unit *u) { - assert(u); - - if (!UNIT_ISSET(u->slice)) - return NULL; - - return UNIT_DEREF(u->slice)->id; -} - -int unit_load_related_unit(Unit *u, const char *type, Unit **_found) { - _cleanup_free_ char *t = NULL; - int r; - - assert(u); - assert(type); - assert(_found); - - r = unit_name_change_suffix(u->id, type, &t); - if (r < 0) - return r; - if (unit_has_name(u, t)) - return -EINVAL; - - r = manager_load_unit(u->manager, t, NULL, NULL, _found); - assert(r < 0 || *_found != u); - return r; -} - -static int signal_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *name, *old_owner, *new_owner; - Unit *u = userdata; - int r; - - assert(message); - assert(u); - - r = sd_bus_message_read(message, "sss", &name, &old_owner, &new_owner); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - if (UNIT_VTABLE(u)->bus_name_owner_change) - UNIT_VTABLE(u)->bus_name_owner_change(u, name, old_owner, new_owner); - - return 0; -} - -int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name) { - const char *match; - - assert(u); - assert(bus); - assert(name); - - if (u->match_bus_slot) - return -EBUSY; - - match = strjoina("type='signal'," - "sender='org.freedesktop.DBus'," - "path='/org/freedesktop/DBus'," - "interface='org.freedesktop.DBus'," - "member='NameOwnerChanged'," - "arg0='", name, "'", - NULL); - - return sd_bus_add_match(bus, &u->match_bus_slot, match, signal_name_owner_changed, u); -} - -int unit_watch_bus_name(Unit *u, const char *name) { - int r; - - assert(u); - assert(name); - - /* Watch a specific name on the bus. We only support one unit - * watching each name for now. */ - - if (u->manager->api_bus) { - /* If the bus is already available, install the match directly. - * Otherwise, just put the name in the list. bus_setup_api() will take care later. */ - r = unit_install_bus_match(u, u->manager->api_bus, name); - if (r < 0) - return log_warning_errno(r, "Failed to subscribe to NameOwnerChanged signal for '%s': %m", name); - } - - r = hashmap_put(u->manager->watch_bus, name, u); - if (r < 0) { - u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot); - return log_warning_errno(r, "Failed to put bus name to hashmap: %m"); - } - - return 0; -} - -void unit_unwatch_bus_name(Unit *u, const char *name) { - assert(u); - assert(name); - - hashmap_remove_value(u->manager->watch_bus, name, u); - u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot); -} - -bool unit_can_serialize(Unit *u) { - assert(u); - - return UNIT_VTABLE(u)->serialize && UNIT_VTABLE(u)->deserialize_item; -} - -int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { - int r; - - assert(u); - assert(f); - assert(fds); - - if (unit_can_serialize(u)) { - ExecRuntime *rt; - - r = UNIT_VTABLE(u)->serialize(u, f, fds); - if (r < 0) - return r; - - rt = unit_get_exec_runtime(u); - if (rt) { - r = exec_runtime_serialize(u, rt, f, fds); - if (r < 0) - return r; - } - } - - dual_timestamp_serialize(f, "state-change-timestamp", &u->state_change_timestamp); - - dual_timestamp_serialize(f, "inactive-exit-timestamp", &u->inactive_exit_timestamp); - dual_timestamp_serialize(f, "active-enter-timestamp", &u->active_enter_timestamp); - dual_timestamp_serialize(f, "active-exit-timestamp", &u->active_exit_timestamp); - dual_timestamp_serialize(f, "inactive-enter-timestamp", &u->inactive_enter_timestamp); - - dual_timestamp_serialize(f, "condition-timestamp", &u->condition_timestamp); - dual_timestamp_serialize(f, "assert-timestamp", &u->assert_timestamp); - - if (dual_timestamp_is_set(&u->condition_timestamp)) - unit_serialize_item(u, f, "condition-result", yes_no(u->condition_result)); - - if (dual_timestamp_is_set(&u->assert_timestamp)) - unit_serialize_item(u, f, "assert-result", yes_no(u->assert_result)); - - unit_serialize_item(u, f, "transient", yes_no(u->transient)); - unit_serialize_item_format(u, f, "cpuacct-usage-base", "%" PRIu64, u->cpuacct_usage_base); - - if (u->cgroup_path) - unit_serialize_item(u, f, "cgroup", u->cgroup_path); - unit_serialize_item(u, f, "cgroup-realized", yes_no(u->cgroup_realized)); - - if (serialize_jobs) { - if (u->job) { - fprintf(f, "job\n"); - job_serialize(u->job, f, fds); - } - - if (u->nop_job) { - fprintf(f, "job\n"); - job_serialize(u->nop_job, f, fds); - } - } - - /* End marker */ - fputc('\n', f); - return 0; -} - -int unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) { - assert(u); - assert(f); - assert(key); - - if (!value) - return 0; - - fputs(key, f); - fputc('=', f); - fputs(value, f); - fputc('\n', f); - - return 1; -} - -int unit_serialize_item_escaped(Unit *u, FILE *f, const char *key, const char *value) { - _cleanup_free_ char *c = NULL; - - assert(u); - assert(f); - assert(key); - - if (!value) - return 0; - - c = cescape(value); - if (!c) - return -ENOMEM; - - fputs(key, f); - fputc('=', f); - fputs(c, f); - fputc('\n', f); - - return 1; -} - -int unit_serialize_item_fd(Unit *u, FILE *f, FDSet *fds, const char *key, int fd) { - int copy; - - assert(u); - assert(f); - assert(key); - - if (fd < 0) - return 0; - - copy = fdset_put_dup(fds, fd); - if (copy < 0) - return copy; - - fprintf(f, "%s=%i\n", key, copy); - return 1; -} - -void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *format, ...) { - va_list ap; - - assert(u); - assert(f); - assert(key); - assert(format); - - fputs(key, f); - fputc('=', f); - - va_start(ap, format); - vfprintf(f, format, ap); - va_end(ap); - - fputc('\n', f); -} - -int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { - ExecRuntime **rt = NULL; - size_t offset; - int r; - - assert(u); - assert(f); - assert(fds); - - offset = UNIT_VTABLE(u)->exec_runtime_offset; - if (offset > 0) - rt = (ExecRuntime**) ((uint8_t*) u + offset); - - for (;;) { - char line[LINE_MAX], *l, *v; - size_t k; - - if (!fgets(line, sizeof(line), f)) { - if (feof(f)) - return 0; - return -errno; - } - - char_array_0(line); - l = strstrip(line); - - /* End marker */ - if (isempty(l)) - break; - - k = strcspn(l, "="); - - if (l[k] == '=') { - l[k] = 0; - v = l+k+1; - } else - v = l+k; - - if (streq(l, "job")) { - if (v[0] == '\0') { - /* new-style serialized job */ - Job *j; - - j = job_new_raw(u); - if (!j) - return log_oom(); - - r = job_deserialize(j, f, fds); - if (r < 0) { - job_free(j); - return r; - } - - r = hashmap_put(u->manager->jobs, UINT32_TO_PTR(j->id), j); - if (r < 0) { - job_free(j); - return r; - } - - r = job_install_deserialized(j); - if (r < 0) { - hashmap_remove(u->manager->jobs, UINT32_TO_PTR(j->id)); - job_free(j); - return r; - } - } else /* legacy for pre-44 */ - log_unit_warning(u, "Update from too old systemd versions are unsupported, cannot deserialize job: %s", v); - continue; - } else if (streq(l, "state-change-timestamp")) { - dual_timestamp_deserialize(v, &u->state_change_timestamp); - continue; - } else if (streq(l, "inactive-exit-timestamp")) { - dual_timestamp_deserialize(v, &u->inactive_exit_timestamp); - continue; - } else if (streq(l, "active-enter-timestamp")) { - dual_timestamp_deserialize(v, &u->active_enter_timestamp); - continue; - } else if (streq(l, "active-exit-timestamp")) { - dual_timestamp_deserialize(v, &u->active_exit_timestamp); - continue; - } else if (streq(l, "inactive-enter-timestamp")) { - dual_timestamp_deserialize(v, &u->inactive_enter_timestamp); - continue; - } else if (streq(l, "condition-timestamp")) { - dual_timestamp_deserialize(v, &u->condition_timestamp); - continue; - } else if (streq(l, "assert-timestamp")) { - dual_timestamp_deserialize(v, &u->assert_timestamp); - continue; - } else if (streq(l, "condition-result")) { - - r = parse_boolean(v); - if (r < 0) - log_unit_debug(u, "Failed to parse condition result value %s, ignoring.", v); - else - u->condition_result = r; - - continue; - - } else if (streq(l, "assert-result")) { - - r = parse_boolean(v); - if (r < 0) - log_unit_debug(u, "Failed to parse assert result value %s, ignoring.", v); - else - u->assert_result = r; - - continue; - - } else if (streq(l, "transient")) { - - r = parse_boolean(v); - if (r < 0) - log_unit_debug(u, "Failed to parse transient bool %s, ignoring.", v); - else - u->transient = r; - - continue; - - } else if (streq(l, "cpuacct-usage-base")) { - - r = safe_atou64(v, &u->cpuacct_usage_base); - if (r < 0) - log_unit_debug(u, "Failed to parse CPU usage %s, ignoring.", v); - - continue; - - } else if (streq(l, "cgroup")) { - - r = unit_set_cgroup_path(u, v); - if (r < 0) - log_unit_debug_errno(u, r, "Failed to set cgroup path %s, ignoring: %m", v); - - (void) unit_watch_cgroup(u); - - continue; - } else if (streq(l, "cgroup-realized")) { - int b; - - b = parse_boolean(v); - if (b < 0) - log_unit_debug(u, "Failed to parse cgroup-realized bool %s, ignoring.", v); - else - u->cgroup_realized = b; - - continue; - } - - if (unit_can_serialize(u)) { - if (rt) { - r = exec_runtime_deserialize_item(u, rt, l, v, fds); - if (r < 0) { - log_unit_warning(u, "Failed to deserialize runtime parameter '%s', ignoring.", l); - continue; - } - - /* Returns positive if key was handled by the call */ - if (r > 0) - continue; - } - - r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds); - if (r < 0) - log_unit_warning(u, "Failed to deserialize unit parameter '%s', ignoring.", l); - } - } - - /* Versions before 228 did not carry a state change timestamp. In this case, take the current time. This is - * useful, so that timeouts based on this timestamp don't trigger too early, and is in-line with the logic from - * before 228 where the base for timeouts was not persistent across reboots. */ - - if (!dual_timestamp_is_set(&u->state_change_timestamp)) - dual_timestamp_get(&u->state_change_timestamp); - - return 0; -} - -int unit_add_node_link(Unit *u, const char *what, bool wants, UnitDependency dep) { - Unit *device; - _cleanup_free_ char *e = NULL; - int r; - - assert(u); - - /* Adds in links to the device node that this unit is based on */ - if (isempty(what)) - return 0; - - if (!is_device_path(what)) - return 0; - - /* When device units aren't supported (such as in a - * container), don't create dependencies on them. */ - if (!unit_type_supported(UNIT_DEVICE)) - return 0; - - r = unit_name_from_path(what, ".device", &e); - if (r < 0) - return r; - - r = manager_load_unit(u->manager, e, NULL, NULL, &device); - if (r < 0) - return r; - - r = unit_add_two_dependencies(u, UNIT_AFTER, - u->manager->running_as == MANAGER_SYSTEM ? dep : UNIT_WANTS, - device, true); - if (r < 0) - return r; - - if (wants) { - r = unit_add_dependency(device, UNIT_WANTS, u, false); - if (r < 0) - return r; - } - - return 0; -} - -int unit_coldplug(Unit *u) { - int r = 0, q = 0; - - assert(u); - - /* Make sure we don't enter a loop, when coldplugging - * recursively. */ - if (u->coldplugged) - return 0; - - u->coldplugged = true; - - if (UNIT_VTABLE(u)->coldplug) - r = UNIT_VTABLE(u)->coldplug(u); - - if (u->job) - q = job_coldplug(u->job); - - if (r < 0) - return r; - if (q < 0) - return q; - - return 0; -} - -bool unit_need_daemon_reload(Unit *u) { - _cleanup_strv_free_ char **t = NULL; - char **path; - struct stat st; - unsigned loaded_cnt, current_cnt; - - assert(u); - - if (u->fragment_path) { - zero(st); - if (stat(u->fragment_path, &st) < 0) - /* What, cannot access this anymore? */ - return true; - - if (u->fragment_mtime > 0 && - timespec_load(&st.st_mtim) != u->fragment_mtime) - return true; - } - - if (u->source_path) { - zero(st); - if (stat(u->source_path, &st) < 0) - return true; - - if (u->source_mtime > 0 && - timespec_load(&st.st_mtim) != u->source_mtime) - return true; - } - - (void) unit_find_dropin_paths(u, &t); - loaded_cnt = strv_length(t); - current_cnt = strv_length(u->dropin_paths); - - if (loaded_cnt == current_cnt) { - if (loaded_cnt == 0) - return false; - - if (strv_overlap(u->dropin_paths, t)) { - STRV_FOREACH(path, u->dropin_paths) { - zero(st); - if (stat(*path, &st) < 0) - return true; - - if (u->dropin_mtime > 0 && - timespec_load(&st.st_mtim) > u->dropin_mtime) - return true; - } - - return false; - } else - return true; - } else - return true; -} - -void unit_reset_failed(Unit *u) { - assert(u); - - if (UNIT_VTABLE(u)->reset_failed) - UNIT_VTABLE(u)->reset_failed(u); - - RATELIMIT_RESET(u->start_limit); - u->start_limit_hit = false; -} - -Unit *unit_following(Unit *u) { - assert(u); - - if (UNIT_VTABLE(u)->following) - return UNIT_VTABLE(u)->following(u); - - return NULL; -} - -bool unit_stop_pending(Unit *u) { - assert(u); - - /* This call does check the current state of the unit. It's - * hence useful to be called from state change calls of the - * unit itself, where the state isn't updated yet. This is - * different from unit_inactive_or_pending() which checks both - * the current state and for a queued job. */ - - return u->job && u->job->type == JOB_STOP; -} - -bool unit_inactive_or_pending(Unit *u) { - assert(u); - - /* Returns true if the unit is inactive or going down */ - - if (UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))) - return true; - - if (unit_stop_pending(u)) - return true; - - return false; -} - -bool unit_active_or_pending(Unit *u) { - assert(u); - - /* Returns true if the unit is active or going up */ - - if (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) - return true; - - if (u->job && - (u->job->type == JOB_START || - u->job->type == JOB_RELOAD_OR_START || - u->job->type == JOB_RESTART)) - return true; - - return false; -} - -int unit_kill(Unit *u, KillWho w, int signo, sd_bus_error *error) { - assert(u); - assert(w >= 0 && w < _KILL_WHO_MAX); - assert(signo > 0); - assert(signo < _NSIG); - - if (!UNIT_VTABLE(u)->kill) - return -EOPNOTSUPP; - - return UNIT_VTABLE(u)->kill(u, w, signo, error); -} - -static Set *unit_pid_set(pid_t main_pid, pid_t control_pid) { - Set *pid_set; - int r; - - pid_set = set_new(NULL); - if (!pid_set) - return NULL; - - /* Exclude the main/control pids from being killed via the cgroup */ - if (main_pid > 0) { - r = set_put(pid_set, PID_TO_PTR(main_pid)); - if (r < 0) - goto fail; - } - - if (control_pid > 0) { - r = set_put(pid_set, PID_TO_PTR(control_pid)); - if (r < 0) - goto fail; - } - - return pid_set; - -fail: - set_free(pid_set); - return NULL; -} - -int unit_kill_common( - Unit *u, - KillWho who, - int signo, - pid_t main_pid, - pid_t control_pid, - sd_bus_error *error) { - - int r = 0; - bool killed = false; - - if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL)) { - if (main_pid < 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no main processes", unit_type_to_string(u->type)); - else if (main_pid == 0) - return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill"); - } - - if (IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL)) { - if (control_pid < 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no control processes", unit_type_to_string(u->type)); - else if (control_pid == 0) - return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); - } - - if (IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL, KILL_ALL, KILL_ALL_FAIL)) - if (control_pid > 0) { - if (kill(control_pid, signo) < 0) - r = -errno; - else - killed = true; - } - - if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL, KILL_ALL, KILL_ALL_FAIL)) - if (main_pid > 0) { - if (kill(main_pid, signo) < 0) - r = -errno; - else - killed = true; - } - - if (IN_SET(who, KILL_ALL, KILL_ALL_FAIL) && u->cgroup_path) { - _cleanup_set_free_ Set *pid_set = NULL; - int q; - - /* Exclude the main/control pids from being killed via the cgroup */ - pid_set = unit_pid_set(main_pid, control_pid); - if (!pid_set) - return -ENOMEM; - - q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, false, false, pid_set); - if (q < 0 && q != -EAGAIN && q != -ESRCH && q != -ENOENT) - r = q; - else - killed = true; - } - - if (r == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL)) - return -ESRCH; - - return r; -} - -int unit_following_set(Unit *u, Set **s) { - assert(u); - assert(s); - - if (UNIT_VTABLE(u)->following_set) - return UNIT_VTABLE(u)->following_set(u, s); - - *s = NULL; - return 0; -} - -UnitFileState unit_get_unit_file_state(Unit *u) { - int r; - - assert(u); - - if (u->unit_file_state < 0 && u->fragment_path) { - r = unit_file_get_state( - u->manager->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, - NULL, - basename(u->fragment_path), - &u->unit_file_state); - if (r < 0) - u->unit_file_state = UNIT_FILE_BAD; - } - - return u->unit_file_state; -} - -int unit_get_unit_file_preset(Unit *u) { - assert(u); - - if (u->unit_file_preset < 0 && u->fragment_path) - u->unit_file_preset = unit_file_query_preset( - u->manager->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, - NULL, - basename(u->fragment_path)); - - return u->unit_file_preset; -} - -Unit* unit_ref_set(UnitRef *ref, Unit *u) { - assert(ref); - assert(u); - - if (ref->unit) - unit_ref_unset(ref); - - ref->unit = u; - LIST_PREPEND(refs, u->refs, ref); - return u; -} - -void unit_ref_unset(UnitRef *ref) { - assert(ref); - - if (!ref->unit) - return; - - LIST_REMOVE(refs, ref->unit->refs, ref); - ref->unit = NULL; -} - -int unit_patch_contexts(Unit *u) { - CGroupContext *cc; - ExecContext *ec; - unsigned i; - int r; - - assert(u); - - /* Patch in the manager defaults into the exec and cgroup - * contexts, _after_ the rest of the settings have been - * initialized */ - - ec = unit_get_exec_context(u); - if (ec) { - /* This only copies in the ones that need memory */ - for (i = 0; i < _RLIMIT_MAX; i++) - if (u->manager->rlimit[i] && !ec->rlimit[i]) { - ec->rlimit[i] = newdup(struct rlimit, u->manager->rlimit[i], 1); - if (!ec->rlimit[i]) - return -ENOMEM; - } - - if (u->manager->running_as == MANAGER_USER && - !ec->working_directory) { - - r = get_home_dir(&ec->working_directory); - if (r < 0) - return r; - - /* Allow user services to run, even if the - * home directory is missing */ - ec->working_directory_missing_ok = true; - } - - if (u->manager->running_as == MANAGER_USER && - (ec->syscall_whitelist || - !set_isempty(ec->syscall_filter) || - !set_isempty(ec->syscall_archs) || - ec->address_families_whitelist || - !set_isempty(ec->address_families))) - ec->no_new_privileges = true; - - if (ec->private_devices) - ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD); - } - - cc = unit_get_cgroup_context(u); - if (cc) { - - if (ec && - ec->private_devices && - cc->device_policy == CGROUP_AUTO) - cc->device_policy = CGROUP_CLOSED; - } - - return 0; -} - -ExecContext *unit_get_exec_context(Unit *u) { - size_t offset; - assert(u); - - if (u->type < 0) - return NULL; - - offset = UNIT_VTABLE(u)->exec_context_offset; - if (offset <= 0) - return NULL; - - return (ExecContext*) ((uint8_t*) u + offset); -} - -KillContext *unit_get_kill_context(Unit *u) { - size_t offset; - assert(u); - - if (u->type < 0) - return NULL; - - offset = UNIT_VTABLE(u)->kill_context_offset; - if (offset <= 0) - return NULL; - - return (KillContext*) ((uint8_t*) u + offset); -} - -CGroupContext *unit_get_cgroup_context(Unit *u) { - size_t offset; - - if (u->type < 0) - return NULL; - - offset = UNIT_VTABLE(u)->cgroup_context_offset; - if (offset <= 0) - return NULL; - - return (CGroupContext*) ((uint8_t*) u + offset); -} - -ExecRuntime *unit_get_exec_runtime(Unit *u) { - size_t offset; - - if (u->type < 0) - return NULL; - - offset = UNIT_VTABLE(u)->exec_runtime_offset; - if (offset <= 0) - return NULL; - - return *(ExecRuntime**) ((uint8_t*) u + offset); -} - -static int unit_drop_in_dir(Unit *u, UnitSetPropertiesMode mode, bool transient, char **dir) { - assert(u); - - if (u->manager->running_as == MANAGER_USER) { - int r; - - if (mode == UNIT_PERSISTENT && !transient) - r = user_config_home(dir); - else - r = user_runtime_dir(dir); - if (r == 0) - return -ENOENT; - - return r; - } - - if (mode == UNIT_PERSISTENT && !transient) - *dir = strdup("/etc/systemd/system"); - else - *dir = strdup("/run/systemd/system"); - if (!*dir) - return -ENOMEM; - - return 0; -} - -int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { - - _cleanup_free_ char *dir = NULL, *p = NULL, *q = NULL; - int r; - - assert(u); - - if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) - return 0; - - r = unit_drop_in_dir(u, mode, u->transient, &dir); - if (r < 0) - return r; - - r = write_drop_in(dir, u->id, 50, name, data); - if (r < 0) - return r; - - r = drop_in_file(dir, u->id, 50, name, &p, &q); - if (r < 0) - return r; - - r = strv_extend(&u->dropin_paths, q); - if (r < 0) - return r; - - strv_sort(u->dropin_paths); - strv_uniq(u->dropin_paths); - - u->dropin_mtime = now(CLOCK_REALTIME); - - return 0; -} - -int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { - _cleanup_free_ char *p = NULL; - va_list ap; - int r; - - assert(u); - assert(name); - assert(format); - - if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) - return 0; - - va_start(ap, format); - r = vasprintf(&p, format, ap); - va_end(ap); - - if (r < 0) - return -ENOMEM; - - return unit_write_drop_in(u, mode, name, p); -} - -int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { - _cleanup_free_ char *ndata = NULL; - - assert(u); - assert(name); - assert(data); - - if (!UNIT_VTABLE(u)->private_section) - return -EINVAL; - - if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) - return 0; - - ndata = strjoin("[", UNIT_VTABLE(u)->private_section, "]\n", data, NULL); - if (!ndata) - return -ENOMEM; - - return unit_write_drop_in(u, mode, name, ndata); -} - -int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { - _cleanup_free_ char *p = NULL; - va_list ap; - int r; - - assert(u); - assert(name); - assert(format); - - if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) - return 0; - - va_start(ap, format); - r = vasprintf(&p, format, ap); - va_end(ap); - - if (r < 0) - return -ENOMEM; - - return unit_write_drop_in_private(u, mode, name, p); -} - -int unit_make_transient(Unit *u) { - assert(u); - - if (!UNIT_VTABLE(u)->can_transient) - return -EOPNOTSUPP; - - u->load_state = UNIT_STUB; - u->load_error = 0; - u->transient = true; - - u->fragment_path = mfree(u->fragment_path); - u->source_path = mfree(u->source_path); - u->dropin_paths = strv_free(u->dropin_paths); - u->fragment_mtime = u->source_mtime = u->dropin_mtime = 0; - - unit_add_to_dbus_queue(u); - unit_add_to_gc_queue(u); - unit_add_to_load_queue(u); - - return 0; -} - -int unit_kill_context( - Unit *u, - KillContext *c, - KillOperation k, - pid_t main_pid, - pid_t control_pid, - bool main_pid_alien) { - - bool wait_for_exit = false; - int sig, r; - - assert(u); - assert(c); - - 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"); - } - - if (main_pid > 0) { - r = kill_and_sigcont(main_pid, sig); - - if (r < 0 && r != -ESRCH) { - _cleanup_free_ char *comm = NULL; - 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) - (void) kill(main_pid, SIGHUP); - } - } - - if (control_pid > 0) { - r = kill_and_sigcont(control_pid, sig); - - if (r < 0 && r != -ESRCH) { - _cleanup_free_ char *comm = NULL; - 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) - (void) kill(control_pid, SIGHUP); - } - } - - if (u->cgroup_path && - (c->kill_mode == KILL_CONTROL_GROUP || (c->kill_mode == KILL_MIXED && k == KILL_KILL))) { - _cleanup_set_free_ Set *pid_set = NULL; - - /* Exclude the main/control pids from being killed via the cgroup */ - pid_set = unit_pid_set(main_pid, control_pid); - if (!pid_set) - return -ENOMEM; - - r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, sig, true, k != KILL_TERMINATE, false, pid_set); - 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); - - } else if (r > 0) { - - /* FIXME: For now, on the legacy hierarchy, we - * will not wait for the cgroup members to die - * if we are running in a container or if this - * is a delegation unit, simply because cgroup - * notification is unreliable in these - * cases. It doesn't work at all in - * containers, and outside of containers it - * can be confused easily by left-over - * directories in the cgroup -- which however - * should not exist in non-delegated units. On - * the unified hierarchy that's different, - * there we get proper events. Hence rely on - * them.*/ - - if (cg_unified() > 0 || - (detect_container() == 0 && !unit_cgroup_delegate(u))) - wait_for_exit = true; - - if (c->send_sighup && k != KILL_KILL) { - 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); - } - } - } - - return wait_for_exit; -} - -int unit_require_mounts_for(Unit *u, const char *path) { - char prefix[strlen(path) + 1], *p; - int r; - - assert(u); - assert(path); - - /* Registers a unit for requiring a certain path and all its - * prefixes. We keep a simple array of these paths in the - * unit, since its usually short. However, we build a prefix - * table for all possible prefixes so that new appearing mount - * units can easily determine which units to make themselves a - * dependency of. */ - - if (!path_is_absolute(path)) - return -EINVAL; - - p = strdup(path); - if (!p) - return -ENOMEM; - - path_kill_slashes(p); - - if (!path_is_safe(p)) { - free(p); - return -EPERM; - } - - if (strv_contains(u->requires_mounts_for, p)) { - free(p); - return 0; - } - - r = strv_consume(&u->requires_mounts_for, p); - if (r < 0) - return r; - - PATH_FOREACH_PREFIX_MORE(prefix, p) { - Set *x; - - x = hashmap_get(u->manager->units_requiring_mounts_for, prefix); - if (!x) { - char *q; - - r = hashmap_ensure_allocated(&u->manager->units_requiring_mounts_for, &string_hash_ops); - if (r < 0) - return r; - - q = strdup(prefix); - if (!q) - return -ENOMEM; - - x = set_new(NULL); - if (!x) { - free(q); - return -ENOMEM; - } - - r = hashmap_put(u->manager->units_requiring_mounts_for, q, x); - if (r < 0) { - free(q); - set_free(x); - return r; - } - } - - r = set_put(x, u); - if (r < 0) - return r; - } - - return 0; -} - -int unit_setup_exec_runtime(Unit *u) { - ExecRuntime **rt; - size_t offset; - Iterator i; - Unit *other; - - offset = UNIT_VTABLE(u)->exec_runtime_offset; - assert(offset > 0); - - /* Check if there already is an ExecRuntime for this unit? */ - rt = (ExecRuntime**) ((uint8_t*) u + offset); - if (*rt) - return 0; - - /* Try to get it from somebody else */ - SET_FOREACH(other, u->dependencies[UNIT_JOINS_NAMESPACE_OF], i) { - - *rt = unit_get_exec_runtime(other); - if (*rt) { - exec_runtime_ref(*rt); - return 0; - } - } - - return exec_runtime_make(rt, unit_get_exec_context(u), u->id); -} - -bool unit_type_supported(UnitType t) { - if (_unlikely_(t < 0)) - return false; - if (_unlikely_(t >= _UNIT_TYPE_MAX)) - return false; - - if (!unit_vtable[t]->supported) - return true; - - return unit_vtable[t]->supported(); -} - -void unit_warn_if_dir_nonempty(Unit *u, const char* where) { - int r; - - assert(u); - assert(where); - - r = dir_is_empty(where); - if (r > 0) - return; - if (r < 0) { - log_unit_warning_errno(u, r, "Failed to check directory %s: %m", where); - return; - } - - log_struct(LOG_NOTICE, - LOG_MESSAGE_ID(SD_MESSAGE_OVERMOUNTING), - LOG_UNIT_ID(u), - LOG_UNIT_MESSAGE(u, "Directory %s to mount over is not empty, mounting anyway.", where), - "WHERE=%s", where, - NULL); -} - -int unit_fail_if_symlink(Unit *u, const char* where) { - int r; - - assert(u); - assert(where); - - r = is_symlink(where); - if (r < 0) { - log_unit_debug_errno(u, r, "Failed to check symlink %s, ignoring: %m", where); - return 0; - } - if (r == 0) - return 0; - - log_struct(LOG_ERR, - LOG_MESSAGE_ID(SD_MESSAGE_OVERMOUNTING), - LOG_UNIT_ID(u), - LOG_UNIT_MESSAGE(u, "Mount on symlink %s not allowed.", where), - "WHERE=%s", where, - NULL); - - return -ELOOP; -} - -bool unit_is_pristine(Unit *u) { - assert(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 - * actually loaded, as those unit types don't require a file - * on disk to validly load. */ - - return !(!IN_SET(u->load_state, UNIT_NOT_FOUND, UNIT_LOADED) || - u->fragment_path || - u->source_path || - !strv_isempty(u->dropin_paths) || - u->job || - u->merged_into); -} diff --git a/src/core/unit.h b/src/core/unit.h deleted file mode 100644 index 601e763ce2..0000000000 --- a/src/core/unit.h +++ /dev/null @@ -1,630 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -typedef struct Unit Unit; -typedef struct UnitVTable UnitVTable; -typedef struct UnitRef UnitRef; -typedef struct UnitStatusMessageFormats UnitStatusMessageFormats; - -#include "condition.h" -#include "failure-action.h" -#include "install.h" -#include "list.h" -#include "unit-name.h" - -typedef enum KillOperation { - KILL_TERMINATE, - KILL_KILL, - KILL_ABORT, - _KILL_OPERATION_MAX, - _KILL_OPERATION_INVALID = -1 -} KillOperation; - -static inline bool UNIT_IS_ACTIVE_OR_RELOADING(UnitActiveState t) { - return t == UNIT_ACTIVE || t == UNIT_RELOADING; -} - -static inline bool UNIT_IS_ACTIVE_OR_ACTIVATING(UnitActiveState t) { - return t == UNIT_ACTIVE || t == UNIT_ACTIVATING || t == UNIT_RELOADING; -} - -static inline bool UNIT_IS_INACTIVE_OR_DEACTIVATING(UnitActiveState t) { - return t == UNIT_INACTIVE || t == UNIT_FAILED || t == UNIT_DEACTIVATING; -} - -static inline bool UNIT_IS_INACTIVE_OR_FAILED(UnitActiveState t) { - return t == UNIT_INACTIVE || t == UNIT_FAILED; -} - -#include "job.h" - -struct UnitRef { - /* Keeps tracks of references to a unit. This is useful so - * that we can merge two units if necessary and correct all - * references to them */ - - Unit* unit; - LIST_FIELDS(UnitRef, refs); -}; - -struct Unit { - Manager *manager; - - UnitType type; - UnitLoadState load_state; - Unit *merged_into; - - char *id; /* One name is special because we use it for identification. Points to an entry in the names set */ - char *instance; - - Set *names; - Set *dependencies[_UNIT_DEPENDENCY_MAX]; - - char **requires_mounts_for; - - char *description; - char **documentation; - - char *fragment_path; /* if loaded from a config file this is the primary path to it */ - char *source_path; /* if converted, the source file */ - char **dropin_paths; - - usec_t fragment_mtime; - usec_t source_mtime; - usec_t dropin_mtime; - - /* If there is something to do with this unit, then this is the installed job for it */ - Job *job; - - /* JOB_NOP jobs are special and can be installed without disturbing the real job. */ - Job *nop_job; - - /* The slot used for watching NameOwnerChanged signals */ - sd_bus_slot *match_bus_slot; - - /* Job timeout and action to take */ - usec_t job_timeout; - FailureAction job_timeout_action; - char *job_timeout_reboot_arg; - - /* References to this */ - LIST_HEAD(UnitRef, refs); - - /* Conditions to check */ - LIST_HEAD(Condition, conditions); - LIST_HEAD(Condition, asserts); - - dual_timestamp condition_timestamp; - dual_timestamp assert_timestamp; - - /* Updated whenever the low-level state changes */ - dual_timestamp state_change_timestamp; - - /* Updated whenever the (high-level) active state enters or leaves the active or inactive states */ - dual_timestamp inactive_exit_timestamp; - dual_timestamp active_enter_timestamp; - dual_timestamp active_exit_timestamp; - dual_timestamp inactive_enter_timestamp; - - UnitRef slice; - - /* Per type list */ - LIST_FIELDS(Unit, units_by_type); - - /* All units which have requires_mounts_for set */ - LIST_FIELDS(Unit, has_requires_mounts_for); - - /* Load queue */ - LIST_FIELDS(Unit, load_queue); - - /* D-Bus queue */ - LIST_FIELDS(Unit, dbus_queue); - - /* Cleanup queue */ - LIST_FIELDS(Unit, cleanup_queue); - - /* GC queue */ - LIST_FIELDS(Unit, gc_queue); - - /* CGroup realize members queue */ - LIST_FIELDS(Unit, cgroup_queue); - - /* Units with the same CGroup netclass */ - LIST_FIELDS(Unit, cgroup_netclass); - - /* PIDs we keep an eye on. Note that a unit might have many - * more, but these are the ones we care enough about to - * process SIGCHLD for */ - Set *pids; - - /* Used during GC sweeps */ - unsigned gc_marker; - - /* Error code when we didn't manage to load the unit (negative) */ - int load_error; - - /* Put a ratelimit on unit starting */ - RateLimit start_limit; - FailureAction start_limit_action; - char *reboot_arg; - - /* Make sure we never enter endless loops with the check unneeded logic, or the BindsTo= logic */ - RateLimit auto_stop_ratelimit; - - /* Cached unit file state and preset */ - UnitFileState unit_file_state; - int unit_file_preset; - - /* Where the cpuacct.usage cgroup counter was at the time the unit was started */ - nsec_t cpuacct_usage_base; - - /* Counterparts in the cgroup filesystem */ - char *cgroup_path; - CGroupMask cgroup_realized_mask; - CGroupMask cgroup_subtree_mask; - CGroupMask cgroup_members_mask; - int cgroup_inotify_wd; - - uint32_t cgroup_netclass_id; - - /* How to start OnFailure units */ - JobMode on_failure_job_mode; - - /* Garbage collect us we nobody wants or requires us anymore */ - bool stop_when_unneeded; - - /* Create default dependencies */ - bool default_dependencies; - - /* Refuse manual starting, allow starting only indirectly via dependency. */ - bool refuse_manual_start; - - /* Don't allow the user to stop this unit manually, allow stopping only indirectly via dependency. */ - bool refuse_manual_stop; - - /* Allow isolation requests */ - bool allow_isolate; - - /* Ignore this unit when isolating */ - bool ignore_on_isolate; - - /* Did the last condition check succeed? */ - bool condition_result; - bool assert_result; - - /* Is this a transient unit? */ - bool transient; - - bool in_load_queue:1; - bool in_dbus_queue:1; - bool in_cleanup_queue:1; - bool in_gc_queue:1; - bool in_cgroup_queue:1; - - bool sent_dbus_new_signal:1; - - bool no_gc:1; - - bool in_audit:1; - - bool cgroup_realized:1; - bool cgroup_members_mask_valid:1; - bool cgroup_subtree_mask_valid:1; - - bool start_limit_hit:1; - - /* Did we already invoke unit_coldplug() for this unit? */ - bool coldplugged:1; -}; - -struct UnitStatusMessageFormats { - const char *starting_stopping[2]; - const char *finished_start_job[_JOB_RESULT_MAX]; - const char *finished_stop_job[_JOB_RESULT_MAX]; -}; - -typedef enum UnitSetPropertiesMode { - UNIT_CHECK = 0, - UNIT_RUNTIME = 1, - UNIT_PERSISTENT = 2, -} UnitSetPropertiesMode; - -#include "automount.h" -#include "busname.h" -#include "device.h" -#include "path.h" -#include "scope.h" -#include "slice.h" -#include "socket.h" -#include "swap.h" -#include "target.h" -#include "timer.h" - -struct UnitVTable { - /* How much memory does an object of this unit type need */ - size_t object_size; - - /* If greater than 0, the offset into the object where - * ExecContext is found, if the unit type has that */ - size_t exec_context_offset; - - /* If greater than 0, the offset into the object where - * CGroupContext is found, if the unit type has that */ - size_t cgroup_context_offset; - - /* If greater than 0, the offset into the object where - * KillContext is found, if the unit type has that */ - size_t kill_context_offset; - - /* If greater than 0, the offset into the object where the - * pointer to ExecRuntime is found, if the unit type has - * that */ - size_t exec_runtime_offset; - - /* The name of the configuration file section with the private settings of this unit */ - const char *private_section; - - /* Config file sections this unit type understands, separated - * by NUL chars */ - const char *sections; - - /* This should reset all type-specific variables. This should - * not allocate memory, and is called with zero-initialized - * data. It should hence only initialize variables that need - * to be set != 0. */ - void (*init)(Unit *u); - - /* This should free all type-specific variables. It should be - * idempotent. */ - void (*done)(Unit *u); - - /* Actually load data from disk. This may fail, and should set - * load_state to UNIT_LOADED, UNIT_MERGED or leave it at - * UNIT_STUB if no configuration could be found. */ - int (*load)(Unit *u); - - /* If a lot of units got created via enumerate(), this is - * where to actually set the state and call unit_notify(). */ - int (*coldplug)(Unit *u); - - void (*dump)(Unit *u, FILE *f, const char *prefix); - - int (*start)(Unit *u); - int (*stop)(Unit *u); - int (*reload)(Unit *u); - - int (*kill)(Unit *u, KillWho w, int signo, sd_bus_error *error); - - bool (*can_reload)(Unit *u); - - /* Write all data that cannot be restored from other sources - * away using unit_serialize_item() */ - int (*serialize)(Unit *u, FILE *f, FDSet *fds); - - /* Restore one item from the serialization */ - int (*deserialize_item)(Unit *u, const char *key, const char *data, FDSet *fds); - - /* Try to match up fds with what we need for this unit */ - void (*distribute_fds)(Unit *u, FDSet *fds); - - /* Boils down the more complex internal state of this unit to - * a simpler one that the engine can understand */ - UnitActiveState (*active_state)(Unit *u); - - /* Returns the substate specific to this unit type as - * string. This is purely information so that we can give the - * user a more fine grained explanation in which actual state a - * unit is in. */ - const char* (*sub_state_to_string)(Unit *u); - - /* Return true when there is reason to keep this entry around - * even nothing references it and it isn't active in any - * way */ - bool (*check_gc)(Unit *u); - - /* When the unit is not running and no job for it queued we - * shall release its runtime resources */ - void (*release_resources)(Unit *u); - - /* Invoked on every child that died */ - void (*sigchld_event)(Unit *u, pid_t pid, int code, int status); - - /* Reset failed state if we are in failed state */ - void (*reset_failed)(Unit *u); - - /* Called whenever any of the cgroups this unit watches for - * ran empty */ - void (*notify_cgroup_empty)(Unit *u); - - /* Called whenever a process of this unit sends us a message */ - void (*notify_message)(Unit *u, pid_t pid, char **tags, FDSet *fds); - - /* Called whenever a name this Unit registered for comes or - * goes away. */ - void (*bus_name_owner_change)(Unit *u, const char *name, const char *old_owner, const char *new_owner); - - /* Called for each property that is being set */ - int (*bus_set_property)(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); - - /* Called after at least one property got changed to apply the necessary change */ - int (*bus_commit_properties)(Unit *u); - - /* Return the unit this unit is following */ - Unit *(*following)(Unit *u); - - /* Return the set of units that are following each other */ - int (*following_set)(Unit *u, Set **s); - - /* Invoked each time a unit this unit is triggering changes - * state or gains/loses a job */ - void (*trigger_notify)(Unit *u, Unit *trigger); - - /* Called whenever CLOCK_REALTIME made a jump */ - void (*time_change)(Unit *u); - - /* Returns the next timeout of a unit */ - int (*get_timeout)(Unit *u, usec_t *timeout); - - /* This is called for each unit type and should be used to - * enumerate existing devices and load them. However, - * everything that is loaded here should still stay in - * inactive state. It is the job of the coldplug() call above - * to put the units into the initial state. */ - void (*enumerate)(Manager *m); - - /* Type specific cleanups. */ - void (*shutdown)(Manager *m); - - /* If this function is set and return false all jobs for units - * of this type will immediately fail. */ - bool (*supported)(void); - - /* The bus vtable */ - const sd_bus_vtable *bus_vtable; - - /* The strings to print in status messages */ - UnitStatusMessageFormats status_message_formats; - - /* Can units of this type have multiple names? */ - bool no_alias:1; - - /* Instances make no sense for this type */ - bool no_instances:1; - - /* True if transient units of this type are OK */ - bool can_transient:1; -}; - -extern const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX]; - -#define UNIT_VTABLE(u) unit_vtable[(u)->type] - -/* For casting a unit into the various unit types */ -#define DEFINE_CAST(UPPERCASE, MixedCase) \ - static inline MixedCase* UPPERCASE(Unit *u) { \ - if (_unlikely_(!u || u->type != UNIT_##UPPERCASE)) \ - return NULL; \ - \ - return (MixedCase*) u; \ - } - -/* For casting the various unit types into a unit */ -#define UNIT(u) (&(u)->meta) - -#define UNIT_HAS_EXEC_CONTEXT(u) (UNIT_VTABLE(u)->exec_context_offset > 0) -#define UNIT_HAS_CGROUP_CONTEXT(u) (UNIT_VTABLE(u)->cgroup_context_offset > 0) -#define UNIT_HAS_KILL_CONTEXT(u) (UNIT_VTABLE(u)->kill_context_offset > 0) - -#define UNIT_TRIGGER(u) ((Unit*) set_first((u)->dependencies[UNIT_TRIGGERS])) - -DEFINE_CAST(SERVICE, Service); -DEFINE_CAST(SOCKET, Socket); -DEFINE_CAST(BUSNAME, BusName); -DEFINE_CAST(TARGET, Target); -DEFINE_CAST(DEVICE, Device); -DEFINE_CAST(MOUNT, Mount); -DEFINE_CAST(AUTOMOUNT, Automount); -DEFINE_CAST(SWAP, Swap); -DEFINE_CAST(TIMER, Timer); -DEFINE_CAST(PATH, Path); -DEFINE_CAST(SLICE, Slice); -DEFINE_CAST(SCOPE, Scope); - -Unit *unit_new(Manager *m, size_t size); -void unit_free(Unit *u); - -int unit_add_name(Unit *u, const char *name); - -int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference); -int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference); - -int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *filename, bool add_reference); -int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference); - -int unit_add_exec_dependencies(Unit *u, ExecContext *c); - -int unit_choose_id(Unit *u, const char *name); -int unit_set_description(Unit *u, const char *description); - -bool unit_check_gc(Unit *u); - -void unit_add_to_load_queue(Unit *u); -void unit_add_to_dbus_queue(Unit *u); -void unit_add_to_cleanup_queue(Unit *u); -void unit_add_to_gc_queue(Unit *u); - -int unit_merge(Unit *u, Unit *other); -int unit_merge_by_name(Unit *u, const char *other); - -Unit *unit_follow_merge(Unit *u) _pure_; - -int unit_load_fragment_and_dropin(Unit *u); -int unit_load_fragment_and_dropin_optional(Unit *u); -int unit_load(Unit *unit); - -int unit_set_slice(Unit *u, Unit *slice); -int unit_set_default_slice(Unit *u); - -const char *unit_description(Unit *u) _pure_; - -bool unit_has_name(Unit *u, const char *name); - -UnitActiveState unit_active_state(Unit *u); - -const char* unit_sub_state_to_string(Unit *u); - -void unit_dump(Unit *u, FILE *f, const char *prefix); - -bool unit_can_reload(Unit *u) _pure_; -bool unit_can_start(Unit *u) _pure_; -bool unit_can_isolate(Unit *u) _pure_; - -int unit_start(Unit *u); -int unit_stop(Unit *u); -int unit_reload(Unit *u); - -int unit_kill(Unit *u, KillWho w, int signo, sd_bus_error *error); -int unit_kill_common(Unit *u, KillWho who, int signo, pid_t main_pid, pid_t control_pid, sd_bus_error *error); - -void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success); - -int unit_watch_pid(Unit *u, pid_t pid); -void unit_unwatch_pid(Unit *u, pid_t pid); -void unit_unwatch_all_pids(Unit *u); - -void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2); - -int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name); -int unit_watch_bus_name(Unit *u, const char *name); -void unit_unwatch_bus_name(Unit *u, const char *name); - -bool unit_job_is_applicable(Unit *u, JobType j); - -int set_unit_path(const char *p); - -char *unit_dbus_path(Unit *u); - -int unit_load_related_unit(Unit *u, const char *type, Unit **_found); - -bool unit_can_serialize(Unit *u) _pure_; - -int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs); -int unit_deserialize(Unit *u, FILE *f, FDSet *fds); - -int unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value); -int unit_serialize_item_escaped(Unit *u, FILE *f, const char *key, const char *value); -int unit_serialize_item_fd(Unit *u, FILE *f, FDSet *fds, const char *key, int fd); -void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *value, ...) _printf_(4,5); - -int unit_add_node_link(Unit *u, const char *what, bool wants, UnitDependency d); - -int unit_coldplug(Unit *u); - -void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) _printf_(3, 0); -void unit_status_emit_starting_stopping_reloading(Unit *u, JobType t); - -bool unit_need_daemon_reload(Unit *u); - -void unit_reset_failed(Unit *u); - -Unit *unit_following(Unit *u); -int unit_following_set(Unit *u, Set **s); - -const char *unit_slice_name(Unit *u); - -bool unit_stop_pending(Unit *u) _pure_; -bool unit_inactive_or_pending(Unit *u) _pure_; -bool unit_active_or_pending(Unit *u); - -int unit_add_default_target_dependency(Unit *u, Unit *target); - -void unit_start_on_failure(Unit *u); -void unit_trigger_notify(Unit *u); - -UnitFileState unit_get_unit_file_state(Unit *u); -int unit_get_unit_file_preset(Unit *u); - -Unit* unit_ref_set(UnitRef *ref, Unit *u); -void unit_ref_unset(UnitRef *ref); - -#define UNIT_DEREF(ref) ((ref).unit) -#define UNIT_ISSET(ref) (!!(ref).unit) - -int unit_patch_contexts(Unit *u); - -ExecContext *unit_get_exec_context(Unit *u) _pure_; -KillContext *unit_get_kill_context(Unit *u) _pure_; -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_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); - -int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data); -int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) _printf_(4,5); - -int unit_kill_context(Unit *u, KillContext *c, KillOperation k, pid_t main_pid, pid_t control_pid, bool main_pid_alien); - -int unit_make_transient(Unit *u); - -int unit_require_mounts_for(Unit *u, const char *path); - -bool unit_type_supported(UnitType t); - -bool unit_is_pristine(Unit *u); - -static inline bool unit_supported(Unit *u) { - return unit_type_supported(u->type); -} - -void unit_warn_if_dir_nonempty(Unit *u, const char* where); -int unit_fail_if_symlink(Unit *u, const char* where); - -/* Macros which append UNIT= or USER_UNIT= to the message */ - -#define log_unit_full(unit, level, error, ...) \ - ({ \ - Unit *_u = (unit); \ - _u ? log_object_internal(level, error, __FILE__, __LINE__, __func__, _u->manager->unit_log_field, _u->id, ##__VA_ARGS__) : \ - log_internal(level, error, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \ - }) - -#define log_unit_debug(unit, ...) log_unit_full(unit, LOG_DEBUG, 0, ##__VA_ARGS__) -#define log_unit_info(unit, ...) log_unit_full(unit, LOG_INFO, 0, ##__VA_ARGS__) -#define log_unit_notice(unit, ...) log_unit_full(unit, LOG_NOTICE, 0, ##__VA_ARGS__) -#define log_unit_warning(unit, ...) log_unit_full(unit, LOG_WARNING, 0, ##__VA_ARGS__) -#define log_unit_error(unit, ...) log_unit_full(unit, LOG_ERR, 0, ##__VA_ARGS__) - -#define log_unit_debug_errno(unit, error, ...) log_unit_full(unit, LOG_DEBUG, error, ##__VA_ARGS__) -#define log_unit_info_errno(unit, error, ...) log_unit_full(unit, LOG_INFO, error, ##__VA_ARGS__) -#define log_unit_notice_errno(unit, error, ...) log_unit_full(unit, LOG_NOTICE, error, ##__VA_ARGS__) -#define log_unit_warning_errno(unit, error, ...) log_unit_full(unit, LOG_WARNING, error, ##__VA_ARGS__) -#define log_unit_error_errno(unit, error, ...) log_unit_full(unit, LOG_ERR, error, ##__VA_ARGS__) - -#define LOG_UNIT_MESSAGE(unit, fmt, ...) "MESSAGE=%s: " fmt, (unit)->id, ##__VA_ARGS__ -#define LOG_UNIT_ID(unit) (unit)->manager->unit_log_format_string, (unit)->id diff --git a/src/core/user.conf b/src/core/user.conf deleted file mode 100644 index 87c8164378..0000000000 --- a/src/core/user.conf +++ /dev/null @@ -1,44 +0,0 @@ -# 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. -# -# You can override the directives in this file by creating files in -# /etc/systemd/user.conf.d/*.conf. -# -# See systemd-user.conf(5) for details - -[Manager] -#LogLevel=info -#LogTarget=console -#LogColor=yes -#LogLocation=no -#SystemCallArchitectures= -#TimerSlackNSec= -#DefaultTimerAccuracySec=1min -#DefaultStandardOutput=inherit -#DefaultStandardError=inherit -#DefaultTimeoutStartSec=90s -#DefaultTimeoutStopSec=90s -#DefaultRestartSec=100ms -#DefaultStartLimitInterval=10s -#DefaultStartLimitBurst=5 -#DefaultEnvironment= -#DefaultLimitCPU= -#DefaultLimitFSIZE= -#DefaultLimitDATA= -#DefaultLimitSTACK= -#DefaultLimitCORE= -#DefaultLimitRSS= -#DefaultLimitNOFILE= -#DefaultLimitAS= -#DefaultLimitNPROC= -#DefaultLimitMEMLOCK= -#DefaultLimitLOCKS= -#DefaultLimitSIGPENDING= -#DefaultLimitMSGQUEUE= -#DefaultLimitNICE= -#DefaultLimitRTPRIO= -#DefaultLimitRTTIME= diff --git a/src/coredump/Makefile b/src/coredump/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/coredump/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/coredump/Makefile b/src/coredump/Makefile new file mode 100644 index 0000000000..5d7cd67de0 --- /dev/null +++ b/src/coredump/Makefile @@ -0,0 +1,96 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_COREDUMP),) +systemd_coredump_SOURCES = \ + src/coredump/coredump.c \ + src/coredump/coredump-vacuum.c \ + src/coredump/coredump-vacuum.h + +systemd_coredump_LDADD = \ + libshared.la + +ifneq ($(HAVE_ELFUTILS),) +systemd_coredump_SOURCES += \ + src/coredump/stacktrace.c \ + src/coredump/stacktrace.h + +systemd_coredump_LDADD += \ + $(ELFUTILS_LIBS) +endif + +nodist_systemunit_DATA += \ + units/systemd-coredump@.service + +dist_systemunit_DATA += \ + units/systemd-coredump.socket + +SOCKETS_TARGET_WANTS += \ + systemd-coredump.socket + +rootlibexec_PROGRAMS += \ + systemd-coredump + +dist_pkgsysconf_DATA += \ + src/coredump/coredump.conf + +coredumpctl_SOURCES = \ + src/coredump/coredumpctl.c + +coredumpctl_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + coredumpctl + +manual_tests += \ + test-coredump-vacuum + +test_coredump_vacuum_SOURCES = \ + src/coredump/test-coredump-vacuum.c \ + src/coredump/coredump-vacuum.c \ + src/coredump/coredump-vacuum.h + +test_coredump_vacuum_LDADD = \ + libshared.la + +dist_bashcompletion_data += \ + shell-completion/bash/coredumpctl + +dist_zshcompletion_data += \ + shell-completion/zsh/_coredumpctl + +nodist_sysctl_DATA = \ + sysctl.d/50-coredump.conf + +CLEANFILES += \ + sysctl.d/50-coredump.conf +endif + +EXTRA_DIST += \ + sysctl.d/50-coredump.conf.in \ + units/systemd-coredump@.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/coredump/coredump.c b/src/coredump/coredump.c index 085909c20c..64ce54d97f 100644 --- a/src/coredump/coredump.c +++ b/src/coredump/coredump.c @@ -28,9 +28,9 @@ #include #endif -#include "sd-journal.h" -#include "sd-login.h" -#include "sd-daemon.h" +#include +#include +#include #include "acl-util.h" #include "alloc-util.h" diff --git a/src/coredump/coredumpctl.c b/src/coredump/coredumpctl.c index 0034a1a0ac..5f4f710a0c 100644 --- a/src/coredump/coredumpctl.c +++ b/src/coredump/coredumpctl.c @@ -24,7 +24,7 @@ #include #include -#include "sd-journal.h" +#include #include "alloc-util.h" #include "compress.h" diff --git a/src/cryptsetup/Makefile b/src/cryptsetup/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/cryptsetup/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c deleted file mode 100644 index 8ac5ab730a..0000000000 --- a/src/cryptsetup/cryptsetup-generator.c +++ /dev/null @@ -1,509 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "alloc-util.h" -#include "dropin.h" -#include "fd-util.h" -#include "fileio.h" -#include "fstab-util.h" -#include "generator.h" -#include "hashmap.h" -#include "log.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-util.h" -#include "proc-cmdline.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" -#include "util.h" - -typedef struct crypto_device { - char *uuid; - char *keyfile; - char *name; - char *options; - bool create; -} crypto_device; - -static const char *arg_dest = "/tmp"; -static bool arg_enabled = true; -static bool arg_read_crypttab = true; -static bool arg_whitelist = false; -static Hashmap *arg_disks = NULL; -static char *arg_default_options = NULL; -static char *arg_default_keyfile = NULL; - -static int create_disk( - const char *name, - const char *device, - const char *password, - const char *options) { - - _cleanup_free_ char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *to = NULL, *e = NULL, - *filtered = NULL; - _cleanup_fclose_ FILE *f = NULL; - bool noauto, nofail, tmp, swap; - char *from; - int r; - - assert(name); - assert(device); - - noauto = fstab_test_yes_no_option(options, "noauto\0" "auto\0"); - nofail = fstab_test_yes_no_option(options, "nofail\0" "fail\0"); - tmp = fstab_test_option(options, "tmp\0"); - swap = fstab_test_option(options, "swap\0"); - - if (tmp && swap) { - log_error("Device '%s' cannot be both 'tmp' and 'swap'. Ignoring.", name); - return -EINVAL; - } - - e = unit_name_escape(name); - if (!e) - return log_oom(); - - r = unit_name_build("systemd-cryptsetup", e, ".service", &n); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - p = strjoin(arg_dest, "/", n, NULL); - if (!p) - return log_oom(); - - u = fstab_node_to_udev_node(device); - if (!u) - return log_oom(); - - r = unit_name_from_path(u, ".device", &d); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - f = fopen(p, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", p); - - fputs( - "# Automatically generated by systemd-cryptsetup-generator\n\n" - "[Unit]\n" - "Description=Cryptography Setup for %I\n" - "Documentation=man:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)\n" - "SourcePath=/etc/crypttab\n" - "DefaultDependencies=no\n" - "Conflicts=umount.target\n" - "BindsTo=dev-mapper-%i.device\n" - "IgnoreOnIsolate=true\n" - "After=cryptsetup-pre.target\n", - f); - - if (!nofail) - fprintf(f, - "Before=cryptsetup.target\n"); - - if (password) { - if (STR_IN_SET(password, "/dev/urandom", "/dev/random", "/dev/hw_random")) - fputs("After=systemd-random-seed.service\n", f); - else if (!streq(password, "-") && !streq(password, "none")) { - _cleanup_free_ char *uu; - - uu = fstab_node_to_udev_node(password); - if (!uu) - return log_oom(); - - if (!path_equal(uu, "/dev/null")) { - - if (is_device_path(uu)) { - _cleanup_free_ char *dd = NULL; - - r = unit_name_from_path(uu, ".device", &dd); - if (r < 0) - return log_error_errno(r, "Failed to generate unit name: %m"); - - fprintf(f, "After=%1$s\nRequires=%1$s\n", dd); - } else - fprintf(f, "RequiresMountsFor=%s\n", password); - } - } - } - - if (is_device_path(u)) - fprintf(f, - "BindsTo=%s\n" - "After=%s\n" - "Before=umount.target\n", - d, d); - else - fprintf(f, - "RequiresMountsFor=%s\n", - u); - - r = generator_write_timeouts(arg_dest, device, name, options, &filtered); - if (r < 0) - return r; - - fprintf(f, - "\n[Service]\n" - "Type=oneshot\n" - "RemainAfterExit=yes\n" - "TimeoutSec=0\n" /* the binary handles timeouts anyway */ - "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n" - "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n", - name, u, strempty(password), strempty(filtered), - name); - - if (tmp) - fprintf(f, - "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n", - name); - - if (swap) - fprintf(f, - "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n", - name); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write file %s: %m", p); - - from = strjoina("../", n); - - if (!noauto) { - - to = strjoin(arg_dest, "/", d, ".wants/", n, NULL); - if (!to) - return log_oom(); - - mkdir_parents_label(to, 0755); - if (symlink(from, to) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", to); - - free(to); - if (!nofail) - to = strjoin(arg_dest, "/cryptsetup.target.requires/", n, NULL); - else - to = strjoin(arg_dest, "/cryptsetup.target.wants/", n, NULL); - if (!to) - return log_oom(); - - mkdir_parents_label(to, 0755); - if (symlink(from, to) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", to); - } - - free(to); - to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL); - if (!to) - return log_oom(); - - mkdir_parents_label(to, 0755); - if (symlink(from, to) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", to); - - if (!noauto && !nofail) { - _cleanup_free_ char *dmname; - dmname = strjoin("dev-mapper-", e, ".device", NULL); - if (!dmname) - return log_oom(); - - r = write_drop_in(arg_dest, dmname, 90, "device-timeout", - "# Automatically generated by systemd-cryptsetup-generator \n\n" - "[Unit]\nJobTimeoutSec=0"); - if (r < 0) - return log_error_errno(r, "Failed to write device drop-in: %m"); - } - - return 0; -} - -static void free_arg_disks(void) { - crypto_device *d; - - while ((d = hashmap_steal_first(arg_disks))) { - free(d->uuid); - free(d->keyfile); - free(d->name); - free(d->options); - free(d); - } - - hashmap_free(arg_disks); -} - -static crypto_device *get_crypto_device(const char *uuid) { - int r; - crypto_device *d; - - assert(uuid); - - d = hashmap_get(arg_disks, uuid); - if (!d) { - d = new0(struct crypto_device, 1); - if (!d) - return NULL; - - d->create = false; - d->keyfile = d->options = d->name = NULL; - - d->uuid = strdup(uuid); - if (!d->uuid) { - free(d); - return NULL; - } - - r = hashmap_put(arg_disks, d->uuid, d); - if (r < 0) { - free(d->uuid); - free(d); - return NULL; - } - } - - return d; -} - -static int parse_proc_cmdline_item(const char *key, const char *value) { - int r; - crypto_device *d; - _cleanup_free_ char *uuid = NULL, *uuid_value = NULL; - - if (STR_IN_SET(key, "luks", "rd.luks") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse luks switch %s. Ignoring.", value); - else - arg_enabled = r; - - } else if (STR_IN_SET(key, "luks.crypttab", "rd.luks.crypttab") && value) { - - r = parse_boolean(value); - if (r < 0) - log_warning("Failed to parse luks crypttab switch %s. Ignoring.", value); - else - arg_read_crypttab = r; - - } else if (STR_IN_SET(key, "luks.uuid", "rd.luks.uuid") && value) { - - d = get_crypto_device(startswith(value, "luks-") ? value+5 : value); - if (!d) - return log_oom(); - - d->create = arg_whitelist = true; - - } else if (STR_IN_SET(key, "luks.options", "rd.luks.options") && value) { - - r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); - if (r == 2) { - d = get_crypto_device(uuid); - if (!d) - return log_oom(); - - free(d->options); - d->options = uuid_value; - uuid_value = NULL; - } else if (free_and_strdup(&arg_default_options, value) < 0) - return log_oom(); - - } else if (STR_IN_SET(key, "luks.key", "rd.luks.key") && value) { - - r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); - if (r == 2) { - d = get_crypto_device(uuid); - if (!d) - return log_oom(); - - free(d->keyfile); - d->keyfile = uuid_value; - uuid_value = NULL; - } else if (free_and_strdup(&arg_default_keyfile, value) < 0) - return log_oom(); - - } else if (STR_IN_SET(key, "luks.name", "rd.luks.name") && value) { - - r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); - if (r == 2) { - d = get_crypto_device(uuid); - if (!d) - return log_oom(); - - d->create = arg_whitelist = true; - - free(d->name); - d->name = uuid_value; - uuid_value = NULL; - } else - log_warning("Failed to parse luks name switch %s. Ignoring.", value); - - } - - return 0; -} - -static int add_crypttab_devices(void) { - struct stat st; - unsigned crypttab_line = 0; - _cleanup_fclose_ FILE *f = NULL; - - if (!arg_read_crypttab) - return 0; - - f = fopen("/etc/crypttab", "re"); - if (!f) { - if (errno != ENOENT) - log_error_errno(errno, "Failed to open /etc/crypttab: %m"); - return 0; - } - - if (fstat(fileno(f), &st) < 0) { - log_error_errno(errno, "Failed to stat /etc/crypttab: %m"); - return 0; - } - - for (;;) { - int r, k; - char line[LINE_MAX], *l, *uuid; - crypto_device *d = NULL; - _cleanup_free_ char *name = NULL, *device = NULL, *keyfile = NULL, *options = NULL; - - if (!fgets(line, sizeof(line), f)) - break; - - crypttab_line++; - - l = strstrip(line); - if (*l == '#' || *l == 0) - continue; - - k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &keyfile, &options); - if (k < 2 || k > 4) { - log_error("Failed to parse /etc/crypttab:%u, ignoring.", crypttab_line); - continue; - } - - uuid = startswith(device, "UUID="); - if (!uuid) - uuid = path_startswith(device, "/dev/disk/by-uuid/"); - if (!uuid) - uuid = startswith(name, "luks-"); - if (uuid) - d = hashmap_get(arg_disks, uuid); - - if (arg_whitelist && !d) { - log_info("Not creating device '%s' because it was not specified on the kernel command line.", name); - continue; - } - - r = create_disk(name, device, keyfile, (d && d->options) ? d->options : options); - if (r < 0) - return r; - - if (d) - d->create = false; - } - - return 0; -} - -static int add_proc_cmdline_devices(void) { - int r; - Iterator i; - crypto_device *d; - - HASHMAP_FOREACH(d, arg_disks, i) { - const char *options; - _cleanup_free_ char *device = NULL; - - if (!d->create) - continue; - - if (!d->name) { - d->name = strappend("luks-", d->uuid); - if (!d->name) - return log_oom(); - } - - device = strappend("UUID=", d->uuid); - if (!device) - return log_oom(); - - if (d->options) - options = d->options; - else if (arg_default_options) - options = arg_default_options; - else - options = "timeout=0"; - - r = create_disk(d->name, device, d->keyfile ?: arg_default_keyfile, options); - if (r < 0) - return r; - } - - return 0; -} - -int main(int argc, char *argv[]) { - int r = EXIT_FAILURE; - - if (argc > 1 && argc != 4) { - log_error("This program takes three or no arguments."); - return EXIT_FAILURE; - } - - if (argc > 1) - arg_dest = argv[1]; - - log_set_target(LOG_TARGET_SAFE); - log_parse_environment(); - log_open(); - - umask(0022); - - arg_disks = hashmap_new(&string_hash_ops); - if (!arg_disks) - goto cleanup; - - r = parse_proc_cmdline(parse_proc_cmdline_item); - if (r < 0) { - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - r = EXIT_FAILURE; - } - - if (!arg_enabled) { - r = EXIT_SUCCESS; - goto cleanup; - } - - if (add_crypttab_devices() < 0) - goto cleanup; - - if (add_proc_cmdline_devices() < 0) - goto cleanup; - - r = EXIT_SUCCESS; - -cleanup: - free_arg_disks(); - free(arg_default_options); - free(arg_default_keyfile); - - return r; -} diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c deleted file mode 100644 index 2ef966257a..0000000000 --- a/src/cryptsetup/cryptsetup.c +++ /dev/null @@ -1,753 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "sd-device.h" - -#include "alloc-util.h" -#include "ask-password-api.h" -#include "device-util.h" -#include "escape.h" -#include "fileio.h" -#include "log.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -static const char *arg_type = NULL; /* CRYPT_LUKS1, CRYPT_TCRYPT or CRYPT_PLAIN */ -static char *arg_cipher = NULL; -static unsigned arg_key_size = 0; -static int arg_key_slot = CRYPT_ANY_SLOT; -static unsigned arg_keyfile_size = 0; -static unsigned arg_keyfile_offset = 0; -static char *arg_hash = NULL; -static char *arg_header = NULL; -static unsigned arg_tries = 3; -static bool arg_readonly = false; -static bool arg_verify = false; -static bool arg_discards = false; -static bool arg_tcrypt_hidden = false; -static bool arg_tcrypt_system = false; -static char **arg_tcrypt_keyfiles = NULL; -static uint64_t arg_offset = 0; -static uint64_t arg_skip = 0; -static usec_t arg_timeout = 0; - -/* Options Debian's crypttab knows we don't: - - precheck= - check= - checkargs= - noearly= - loud= - keyscript= -*/ - -static int parse_one_option(const char *option) { - assert(option); - - /* Handled outside of this tool */ - if (STR_IN_SET(option, "noauto", "auto", "nofail", "fail")) - return 0; - - if (startswith(option, "cipher=")) { - char *t; - - t = strdup(option+7); - if (!t) - return log_oom(); - - free(arg_cipher); - arg_cipher = t; - - } else if (startswith(option, "size=")) { - - if (safe_atou(option+5, &arg_key_size) < 0) { - log_error("size= parse failure, ignoring."); - return 0; - } - - if (arg_key_size % 8) { - log_error("size= not a multiple of 8, ignoring."); - return 0; - } - - arg_key_size /= 8; - - } else if (startswith(option, "key-slot=")) { - - arg_type = CRYPT_LUKS1; - if (safe_atoi(option+9, &arg_key_slot) < 0) { - log_error("key-slot= parse failure, ignoring."); - return 0; - } - - } else if (startswith(option, "tcrypt-keyfile=")) { - - arg_type = CRYPT_TCRYPT; - if (path_is_absolute(option+15)) { - if (strv_extend(&arg_tcrypt_keyfiles, option + 15) < 0) - return log_oom(); - } else - log_error("Key file path '%s' is not absolute. Ignoring.", option+15); - - } else if (startswith(option, "keyfile-size=")) { - - if (safe_atou(option+13, &arg_keyfile_size) < 0) { - log_error("keyfile-size= parse failure, ignoring."); - return 0; - } - - } else if (startswith(option, "keyfile-offset=")) { - - if (safe_atou(option+15, &arg_keyfile_offset) < 0) { - log_error("keyfile-offset= parse failure, ignoring."); - return 0; - } - - } else if (startswith(option, "hash=")) { - char *t; - - t = strdup(option+5); - if (!t) - return log_oom(); - - free(arg_hash); - arg_hash = t; - - } else if (startswith(option, "header=")) { - arg_type = CRYPT_LUKS1; - - if (!path_is_absolute(option+7)) { - log_error("Header path '%s' is not absolute, refusing.", option+7); - return -EINVAL; - } - - if (arg_header) { - log_error("Duplicate header= options, refusing."); - return -EINVAL; - } - - arg_header = strdup(option+7); - if (!arg_header) - return log_oom(); - - } else if (startswith(option, "tries=")) { - - if (safe_atou(option+6, &arg_tries) < 0) { - log_error("tries= parse failure, ignoring."); - return 0; - } - - } else if (STR_IN_SET(option, "readonly", "read-only")) - arg_readonly = true; - else if (streq(option, "verify")) - arg_verify = true; - else if (STR_IN_SET(option, "allow-discards", "discard")) - arg_discards = true; - else if (streq(option, "luks")) - arg_type = CRYPT_LUKS1; - else if (streq(option, "tcrypt")) - arg_type = CRYPT_TCRYPT; - else if (streq(option, "tcrypt-hidden")) { - arg_type = CRYPT_TCRYPT; - arg_tcrypt_hidden = true; - } else if (streq(option, "tcrypt-system")) { - arg_type = CRYPT_TCRYPT; - arg_tcrypt_system = true; - } else if (STR_IN_SET(option, "plain", "swap", "tmp")) - arg_type = CRYPT_PLAIN; - else if (startswith(option, "timeout=")) { - - if (parse_sec(option+8, &arg_timeout) < 0) { - log_error("timeout= parse failure, ignoring."); - return 0; - } - - } else if (startswith(option, "offset=")) { - - if (safe_atou64(option+7, &arg_offset) < 0) { - log_error("offset= parse failure, refusing."); - return -EINVAL; - } - - } else if (startswith(option, "skip=")) { - - if (safe_atou64(option+5, &arg_skip) < 0) { - log_error("skip= parse failure, refusing."); - return -EINVAL; - } - - } else if (!streq(option, "none")) - log_error("Encountered unknown /etc/crypttab option '%s', ignoring.", option); - - return 0; -} - -static int parse_options(const char *options) { - const char *word, *state; - size_t l; - int r; - - assert(options); - - FOREACH_WORD_SEPARATOR(word, l, options, ",", state) { - _cleanup_free_ char *o; - - o = strndup(word, l); - if (!o) - return -ENOMEM; - r = parse_one_option(o); - if (r < 0) - return r; - } - - /* sanity-check options */ - if (arg_type != NULL && !streq(arg_type, CRYPT_PLAIN)) { - if (arg_offset) - log_warning("offset= ignored with type %s", arg_type); - if (arg_skip) - log_warning("skip= ignored with type %s", arg_type); - } - - return 0; -} - -static void log_glue(int level, const char *msg, void *usrptr) { - log_debug("%s", msg); -} - -static int disk_major_minor(const char *path, char **ret) { - struct stat st; - - assert(path); - - if (stat(path, &st) < 0) - return -errno; - - if (!S_ISBLK(st.st_mode)) - return -EINVAL; - - if (asprintf(ret, "/dev/block/%d:%d", major(st.st_rdev), minor(st.st_rdev)) < 0) - return -errno; - - return 0; -} - -static char* disk_description(const char *path) { - - static const char name_fields[] = - "ID_PART_ENTRY_NAME\0" - "DM_NAME\0" - "ID_MODEL_FROM_DATABASE\0" - "ID_MODEL\0"; - - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - struct stat st; - const char *i; - int r; - - assert(path); - - if (stat(path, &st) < 0) - return NULL; - - if (!S_ISBLK(st.st_mode)) - return NULL; - - r = sd_device_new_from_devnum(&device, 'b', st.st_rdev); - if (r < 0) - return NULL; - - NULSTR_FOREACH(i, name_fields) { - const char *name; - - r = sd_device_get_property_value(device, i, &name); - if (r >= 0 && !isempty(name)) - return strdup(name); - } - - return NULL; -} - -static char *disk_mount_point(const char *label) { - _cleanup_free_ char *device = NULL; - _cleanup_endmntent_ FILE *f = NULL; - struct mntent *m; - - /* Yeah, we don't support native systemd unit files here for now */ - - if (asprintf(&device, "/dev/mapper/%s", label) < 0) - return NULL; - - f = setmntent("/etc/fstab", "r"); - if (!f) - return NULL; - - while ((m = getmntent(f))) - if (path_equal(m->mnt_fsname, device)) - return strdup(m->mnt_dir); - - return NULL; -} - -static int get_password(const char *vol, const char *src, usec_t until, bool accept_cached, char ***ret) { - _cleanup_free_ char *description = NULL, *name_buffer = NULL, *mount_point = NULL, *maj_min = NULL, *text = NULL, *escaped_name = NULL; - _cleanup_strv_free_erase_ char **passwords = NULL; - const char *name = NULL; - char **p, *id; - int r = 0; - - assert(vol); - assert(src); - assert(ret); - - description = disk_description(src); - mount_point = disk_mount_point(vol); - - if (description && streq(vol, description)) - /* If the description string is simply the - * volume name, then let's not show this - * twice */ - description = mfree(description); - - if (mount_point && description) - r = asprintf(&name_buffer, "%s (%s) on %s", description, vol, mount_point); - else if (mount_point) - r = asprintf(&name_buffer, "%s on %s", vol, mount_point); - else if (description) - r = asprintf(&name_buffer, "%s (%s)", description, vol); - - if (r < 0) - return log_oom(); - - name = name_buffer ? name_buffer : vol; - - if (asprintf(&text, "Please enter passphrase for disk %s!", name) < 0) - return log_oom(); - - if (src) - (void) disk_major_minor(src, &maj_min); - - if (maj_min) { - escaped_name = maj_min; - maj_min = NULL; - } else - escaped_name = cescape(name); - - if (!escaped_name) - return log_oom(); - - id = strjoina("cryptsetup:", escaped_name); - - r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until, - ASK_PASSWORD_PUSH_CACHE | (accept_cached*ASK_PASSWORD_ACCEPT_CACHED), - &passwords); - if (r < 0) - return log_error_errno(r, "Failed to query password: %m"); - - if (arg_verify) { - _cleanup_strv_free_erase_ char **passwords2 = NULL; - - assert(strv_length(passwords) == 1); - - if (asprintf(&text, "Please enter passphrase for disk %s! (verification)", name) < 0) - return log_oom(); - - id = strjoina("cryptsetup-verification:", escaped_name); - - r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until, ASK_PASSWORD_PUSH_CACHE, &passwords2); - if (r < 0) - return log_error_errno(r, "Failed to query verification password: %m"); - - assert(strv_length(passwords2) == 1); - - if (!streq(passwords[0], passwords2[0])) { - log_warning("Passwords did not match, retrying."); - return -EAGAIN; - } - } - - strv_uniq(passwords); - - STRV_FOREACH(p, passwords) { - char *c; - - if (strlen(*p)+1 >= arg_key_size) - continue; - - /* Pad password if necessary */ - c = new(char, arg_key_size); - if (!c) - return log_oom(); - - strncpy(c, *p, arg_key_size); - free(*p); - *p = c; - } - - *ret = passwords; - passwords = NULL; - - return 0; -} - -static int attach_tcrypt( - struct crypt_device *cd, - const char *name, - const char *key_file, - char **passwords, - uint32_t flags) { - - int r = 0; - _cleanup_free_ char *passphrase = NULL; - struct crypt_params_tcrypt params = { - .flags = CRYPT_TCRYPT_LEGACY_MODES, - .keyfiles = (const char **)arg_tcrypt_keyfiles, - .keyfiles_count = strv_length(arg_tcrypt_keyfiles) - }; - - assert(cd); - assert(name); - assert(key_file || (passwords && passwords[0])); - - if (arg_tcrypt_hidden) - params.flags |= CRYPT_TCRYPT_HIDDEN_HEADER; - - if (arg_tcrypt_system) - params.flags |= CRYPT_TCRYPT_SYSTEM_HEADER; - - if (key_file) { - r = read_one_line_file(key_file, &passphrase); - if (r < 0) { - log_error_errno(r, "Failed to read password file '%s': %m", key_file); - return -EAGAIN; - } - - params.passphrase = passphrase; - } else - params.passphrase = passwords[0]; - params.passphrase_size = strlen(params.passphrase); - - r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); - if (r < 0) { - if (key_file && r == -EPERM) { - log_error("Failed to activate using password file '%s'.", key_file); - return -EAGAIN; - } - return r; - } - - return crypt_activate_by_volume_key(cd, name, NULL, 0, flags); -} - -static int attach_luks_or_plain(struct crypt_device *cd, - const char *name, - const char *key_file, - const char *data_device, - char **passwords, - uint32_t flags) { - int r = 0; - bool pass_volume_key = false; - - assert(cd); - assert(name); - assert(key_file || passwords); - - if (!arg_type || streq(arg_type, CRYPT_LUKS1)) { - r = crypt_load(cd, CRYPT_LUKS1, NULL); - if (r < 0) { - log_error("crypt_load() failed on device %s.\n", crypt_get_device_name(cd)); - return r; - } - - if (data_device) - r = crypt_set_data_device(cd, data_device); - } - - if ((!arg_type && r < 0) || streq_ptr(arg_type, CRYPT_PLAIN)) { - struct crypt_params_plain params = { - .offset = arg_offset, - .skip = arg_skip, - }; - const char *cipher, *cipher_mode; - _cleanup_free_ char *truncated_cipher = NULL; - - if (arg_hash) { - /* plain isn't a real hash type. it just means "use no hash" */ - if (!streq(arg_hash, "plain")) - params.hash = arg_hash; - } else if (!key_file) - /* for CRYPT_PLAIN, the behaviour of cryptsetup - * package is to not hash when a key file is provided */ - params.hash = "ripemd160"; - - if (arg_cipher) { - size_t l; - - l = strcspn(arg_cipher, "-"); - truncated_cipher = strndup(arg_cipher, l); - if (!truncated_cipher) - return log_oom(); - - cipher = truncated_cipher; - cipher_mode = arg_cipher[l] ? arg_cipher+l+1 : "plain"; - } else { - cipher = "aes"; - cipher_mode = "cbc-essiv:sha256"; - } - - /* for CRYPT_PLAIN limit reads - * from keyfile to key length, and - * ignore keyfile-size */ - arg_keyfile_size = arg_key_size; - - /* In contrast to what the name - * crypt_setup() might suggest this - * doesn't actually format anything, - * it just configures encryption - * parameters when used for plain - * mode. */ - r = crypt_format(cd, CRYPT_PLAIN, cipher, cipher_mode, NULL, NULL, arg_keyfile_size, ¶ms); - - /* hash == NULL implies the user passed "plain" */ - pass_volume_key = (params.hash == NULL); - } - - if (r < 0) - return log_error_errno(r, "Loading of cryptographic parameters failed: %m"); - - log_info("Set cipher %s, mode %s, key size %i bits for device %s.", - crypt_get_cipher(cd), - crypt_get_cipher_mode(cd), - crypt_get_volume_key_size(cd)*8, - crypt_get_device_name(cd)); - - if (key_file) { - r = crypt_activate_by_keyfile_offset(cd, name, arg_key_slot, key_file, arg_keyfile_size, arg_keyfile_offset, flags); - if (r < 0) { - log_error_errno(r, "Failed to activate with key file '%s': %m", key_file); - return -EAGAIN; - } - } else { - char **p; - - STRV_FOREACH(p, passwords) { - if (pass_volume_key) - r = crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags); - else - r = crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags); - - if (r >= 0) - break; - } - } - - return r; -} - -static int help(void) { - - printf("%s attach VOLUME SOURCEDEVICE [PASSWORD] [OPTIONS]\n" - "%s detach VOLUME\n\n" - "Attaches or detaches an encrypted block device.\n", - program_invocation_short_name, - program_invocation_short_name); - - return 0; -} - -int main(int argc, char *argv[]) { - int r = EXIT_FAILURE; - struct crypt_device *cd = NULL; - - if (argc <= 1) { - help(); - return EXIT_SUCCESS; - } - - if (argc < 3) { - log_error("This program requires at least two arguments."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - if (streq(argv[1], "attach")) { - uint32_t flags = 0; - int k; - unsigned tries; - usec_t until; - crypt_status_info status; - const char *key_file = NULL; - - /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [PASSWORD] [OPTIONS] */ - - if (argc < 4) { - log_error("attach requires at least two arguments."); - goto finish; - } - - if (argc >= 5 && - argv[4][0] && - !streq(argv[4], "-") && - !streq(argv[4], "none")) { - - if (!path_is_absolute(argv[4])) - log_error("Password file path '%s' is not absolute. Ignoring.", argv[4]); - else - key_file = argv[4]; - } - - if (argc >= 6 && argv[5][0] && !streq(argv[5], "-")) { - if (parse_options(argv[5]) < 0) - goto finish; - } - - /* A delicious drop of snake oil */ - mlockall(MCL_FUTURE); - - if (arg_header) { - log_debug("LUKS header: %s", arg_header); - k = crypt_init(&cd, arg_header); - } else - k = crypt_init(&cd, argv[3]); - if (k) { - log_error_errno(k, "crypt_init() failed: %m"); - goto finish; - } - - crypt_set_log_callback(cd, log_glue, NULL); - - status = crypt_status(cd, argv[2]); - if (status == CRYPT_ACTIVE || status == CRYPT_BUSY) { - log_info("Volume %s already active.", argv[2]); - r = EXIT_SUCCESS; - goto finish; - } - - if (arg_readonly) - flags |= CRYPT_ACTIVATE_READONLY; - - if (arg_discards) - flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS; - - if (arg_timeout > 0) - until = now(CLOCK_MONOTONIC) + arg_timeout; - else - until = 0; - - arg_key_size = (arg_key_size > 0 ? arg_key_size : (256 / 8)); - - if (key_file) { - struct stat st; - - /* Ideally we'd do this on the open fd, but since this is just a - * warning it's OK to do this in two steps. */ - if (stat(key_file, &st) >= 0 && S_ISREG(st.st_mode) && (st.st_mode & 0005)) - log_warning("Key file %s is world-readable. This is not a good idea!", key_file); - } - - for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) { - _cleanup_strv_free_erase_ char **passwords = NULL; - - if (!key_file) { - k = get_password(argv[2], argv[3], until, tries == 0 && !arg_verify, &passwords); - if (k == -EAGAIN) - continue; - else if (k < 0) - goto finish; - } - - if (streq_ptr(arg_type, CRYPT_TCRYPT)) - k = attach_tcrypt(cd, argv[2], key_file, passwords, flags); - else - k = attach_luks_or_plain(cd, - argv[2], - key_file, - arg_header ? argv[3] : NULL, - passwords, - flags); - if (k >= 0) - break; - else if (k == -EAGAIN) { - key_file = NULL; - continue; - } else if (k != -EPERM) { - log_error_errno(k, "Failed to activate: %m"); - goto finish; - } - - log_warning("Invalid passphrase."); - } - - if (arg_tries != 0 && tries >= arg_tries) { - log_error("Too many attempts; giving up."); - r = EXIT_FAILURE; - goto finish; - } - - } else if (streq(argv[1], "detach")) { - int k; - - k = crypt_init_by_name(&cd, argv[2]); - if (k) { - log_error_errno(k, "crypt_init() failed: %m"); - goto finish; - } - - crypt_set_log_callback(cd, log_glue, NULL); - - k = crypt_deactivate(cd, argv[2]); - if (k < 0) { - log_error_errno(k, "Failed to deactivate: %m"); - goto finish; - } - - } else { - log_error("Unknown verb %s.", argv[1]); - goto finish; - } - - r = EXIT_SUCCESS; - -finish: - - if (cd) - crypt_free(cd); - - free(arg_cipher); - free(arg_hash); - free(arg_header); - strv_free(arg_tcrypt_keyfiles); - - return r; -} diff --git a/src/dbus1-generator/Makefile b/src/dbus1-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/dbus1-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/dbus1-generator/Makefile b/src/dbus1-generator/Makefile new file mode 100644 index 0000000000..1d7f0d1a81 --- /dev/null +++ b/src/dbus1-generator/Makefile @@ -0,0 +1,49 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemgenerator_PROGRAMS += \ + systemd-dbus1-generator + +systemd_dbus1_generator_SOURCES = \ + src/dbus1-generator/dbus1-generator.c + +systemd_dbus1_generator_LDADD = \ + libshared.la + +dbus1-generator-install-hook: + $(AM_V_at)$(MKDIR_P) $(DESTDIR)$(usergeneratordir) + $(AM_V_RM)rm -f $(DESTDIR)$(usergeneratordir)/systemd-dbus1-generator + $(AM_V_LN)$(LN_S) --relative -f $(DESTDIR)$(systemgeneratordir)/systemd-dbus1-generator $(DESTDIR)$(usergeneratordir)/systemd-dbus1-generator + +dbus1-generator-uninstall-hook: + rm -f $(DESTDIR)$(usergeneratordir)/systemd-dbus1-generator + +dist_xinitrc_SCRIPTS = \ + xorg/50-systemd-user.sh + +INSTALL_EXEC_HOOKS += dbus1-generator-install-hook +UNINSTALL_EXEC_HOOKS += dbus1-generator-uninstall-hook + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/debug-generator/Makefile b/src/debug-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/debug-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/debug-generator/Makefile b/src/debug-generator/Makefile new file mode 100644 index 0000000000..79684a8a7a --- /dev/null +++ b/src/debug-generator/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_debug_generator_SOURCES = \ + src/debug-generator/debug-generator.c + +systemd_debug_generator_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/delta/Makefile b/src/delta/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/delta/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/delta/delta.c b/src/delta/delta.c deleted file mode 100644 index a54fc89de6..0000000000 --- a/src/delta/delta.c +++ /dev/null @@ -1,643 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Lennart Poettering - Copyright 2013 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "hashmap.h" -#include "locale-util.h" -#include "log.h" -#include "pager.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "signal-util.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "util.h" - -static const char prefixes[] = - "/etc\0" - "/run\0" - "/usr/local/lib\0" - "/usr/local/share\0" - "/usr/lib\0" - "/usr/share\0" -#ifdef HAVE_SPLIT_USR - "/lib\0" -#endif - ; - -static const char suffixes[] = - "sysctl.d\0" - "tmpfiles.d\0" - "modules-load.d\0" - "binfmt.d\0" - "systemd/system\0" - "systemd/user\0" - "systemd/system-preset\0" - "systemd/user-preset\0" - "udev/rules.d\0" - "modprobe.d\0"; - -static const char have_dropins[] = - "systemd/system\0" - "systemd/user\0"; - -static bool arg_no_pager = false; -static int arg_diff = -1; - -static enum { - SHOW_MASKED = 1 << 0, - SHOW_EQUIVALENT = 1 << 1, - SHOW_REDIRECTED = 1 << 2, - SHOW_OVERRIDDEN = 1 << 3, - SHOW_UNCHANGED = 1 << 4, - SHOW_EXTENDED = 1 << 5, - - SHOW_DEFAULTS = - (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN | SHOW_EXTENDED) -} arg_flags = 0; - -static void pager_open_if_enabled(void) { - - if (arg_no_pager) - return; - - pager_open(false); -} - -static int equivalent(const char *a, const char *b) { - _cleanup_free_ char *x = NULL, *y = NULL; - - x = canonicalize_file_name(a); - if (!x) - return -errno; - - y = canonicalize_file_name(b); - if (!y) - return -errno; - - return path_equal(x, y); -} - -static int notify_override_masked(const char *top, const char *bottom) { - if (!(arg_flags & SHOW_MASKED)) - return 0; - - printf("%s%s%s %s %s %s\n", - ansi_highlight_red(), "[MASKED]", ansi_normal(), - top, draw_special_char(DRAW_ARROW), bottom); - return 1; -} - -static int notify_override_equivalent(const char *top, const char *bottom) { - if (!(arg_flags & SHOW_EQUIVALENT)) - return 0; - - printf("%s%s%s %s %s %s\n", - ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(), - top, draw_special_char(DRAW_ARROW), bottom); - return 1; -} - -static int notify_override_redirected(const char *top, const char *bottom) { - if (!(arg_flags & SHOW_REDIRECTED)) - return 0; - - printf("%s%s%s %s %s %s\n", - ansi_highlight(), "[REDIRECTED]", ansi_normal(), - top, draw_special_char(DRAW_ARROW), bottom); - return 1; -} - -static int notify_override_overridden(const char *top, const char *bottom) { - if (!(arg_flags & SHOW_OVERRIDDEN)) - return 0; - - printf("%s%s%s %s %s %s\n", - ansi_highlight(), "[OVERRIDDEN]", ansi_normal(), - top, draw_special_char(DRAW_ARROW), bottom); - return 1; -} - -static int notify_override_extended(const char *top, const char *bottom) { - if (!(arg_flags & SHOW_EXTENDED)) - return 0; - - printf("%s%s%s %s %s %s\n", - ansi_highlight(), "[EXTENDED]", ansi_normal(), - top, draw_special_char(DRAW_ARROW), bottom); - return 1; -} - -static int notify_override_unchanged(const char *f) { - if (!(arg_flags & SHOW_UNCHANGED)) - return 0; - - printf("[UNCHANGED] %s\n", f); - return 1; -} - -static int found_override(const char *top, const char *bottom) { - _cleanup_free_ char *dest = NULL; - int k; - pid_t pid; - - assert(top); - assert(bottom); - - if (null_or_empty_path(top) > 0) - return notify_override_masked(top, bottom); - - k = readlink_malloc(top, &dest); - if (k >= 0) { - if (equivalent(dest, bottom) > 0) - return notify_override_equivalent(top, bottom); - else - return notify_override_redirected(top, bottom); - } - - k = notify_override_overridden(top, bottom); - if (!arg_diff) - return k; - - putchar('\n'); - - fflush(stdout); - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork off diff: %m"); - else if (pid == 0) { - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - execlp("diff", "diff", "-us", "--", bottom, top, NULL); - log_error_errno(errno, "Failed to execute diff: %m"); - _exit(EXIT_FAILURE); - } - - wait_for_terminate_and_warn("diff", pid, false); - putchar('\n'); - - return k; -} - -static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) { - _cleanup_free_ char *unit = NULL; - _cleanup_free_ char *path = NULL; - _cleanup_strv_free_ char **list = NULL; - char **file; - char *c; - int r; - - assert(!endswith(drop, "/")); - - path = strjoin(toppath, "/", drop, NULL); - if (!path) - return -ENOMEM; - - log_debug("Looking at %s", path); - - unit = strdup(drop); - if (!unit) - return -ENOMEM; - - c = strrchr(unit, '.'); - if (!c) - return -EINVAL; - *c = 0; - - r = get_files_in_directory(path, &list); - if (r < 0) - return log_error_errno(r, "Failed to enumerate %s: %m", path); - - STRV_FOREACH(file, list) { - Hashmap *h; - int k; - char *p; - char *d; - - if (!endswith(*file, ".conf")) - continue; - - p = strjoin(path, "/", *file, NULL); - if (!p) - return -ENOMEM; - d = p + strlen(toppath) + 1; - - log_debug("Adding at top: %s %s %s", d, draw_special_char(DRAW_ARROW), p); - k = hashmap_put(top, d, p); - if (k >= 0) { - p = strdup(p); - if (!p) - return -ENOMEM; - d = p + strlen(toppath) + 1; - } else if (k != -EEXIST) { - free(p); - return k; - } - - log_debug("Adding at bottom: %s %s %s", d, draw_special_char(DRAW_ARROW), p); - free(hashmap_remove(bottom, d)); - k = hashmap_put(bottom, d, p); - if (k < 0) { - free(p); - return k; - } - - h = hashmap_get(drops, unit); - if (!h) { - h = hashmap_new(&string_hash_ops); - if (!h) - return -ENOMEM; - hashmap_put(drops, unit, h); - unit = strdup(unit); - if (!unit) - return -ENOMEM; - } - - p = strdup(p); - if (!p) - return -ENOMEM; - - log_debug("Adding to drops: %s %s %s %s %s", - unit, draw_special_char(DRAW_ARROW), basename(p), draw_special_char(DRAW_ARROW), p); - k = hashmap_put(h, basename(p), p); - if (k < 0) { - free(p); - if (k != -EEXIST) - return k; - } - } - return 0; -} - -static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) { - _cleanup_closedir_ DIR *d; - - assert(top); - assert(bottom); - assert(drops); - assert(path); - - log_debug("Looking at %s", path); - - d = opendir(path); - if (!d) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - for (;;) { - struct dirent *de; - int k; - char *p; - - errno = 0; - de = readdir(d); - if (!de) - return -errno; - - dirent_ensure_type(d, de); - - if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d")) - enumerate_dir_d(top, bottom, drops, path, de->d_name); - - if (!dirent_is_file(de)) - continue; - - p = strjoin(path, "/", de->d_name, NULL); - if (!p) - return -ENOMEM; - - log_debug("Adding at top: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p); - k = hashmap_put(top, basename(p), p); - if (k >= 0) { - p = strdup(p); - if (!p) - return -ENOMEM; - } else if (k != -EEXIST) { - free(p); - return k; - } - - log_debug("Adding at bottom: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p); - free(hashmap_remove(bottom, basename(p))); - k = hashmap_put(bottom, basename(p), p); - if (k < 0) { - free(p); - return k; - } - } -} - -static int process_suffix(const char *suffix, const char *onlyprefix) { - const char *p; - char *f; - Hashmap *top, *bottom, *drops; - Hashmap *h; - char *key; - int r = 0, k; - Iterator i, j; - int n_found = 0; - bool dropins; - - assert(suffix); - assert(!startswith(suffix, "/")); - assert(!strstr(suffix, "//")); - - dropins = nulstr_contains(have_dropins, suffix); - - top = hashmap_new(&string_hash_ops); - bottom = hashmap_new(&string_hash_ops); - drops = hashmap_new(&string_hash_ops); - if (!top || !bottom || !drops) { - r = -ENOMEM; - goto finish; - } - - NULSTR_FOREACH(p, prefixes) { - _cleanup_free_ char *t = NULL; - - t = strjoin(p, "/", suffix, NULL); - if (!t) { - r = -ENOMEM; - goto finish; - } - - k = enumerate_dir(top, bottom, drops, t, dropins); - if (r == 0) - r = k; - } - - HASHMAP_FOREACH_KEY(f, key, top, i) { - char *o; - - o = hashmap_get(bottom, key); - assert(o); - - if (!onlyprefix || startswith(o, onlyprefix)) { - if (path_equal(o, f)) { - notify_override_unchanged(f); - } else { - k = found_override(f, o); - if (k < 0) - r = k; - else - n_found += k; - } - } - - h = hashmap_get(drops, key); - if (h) - HASHMAP_FOREACH(o, h, j) - if (!onlyprefix || startswith(o, onlyprefix)) - n_found += notify_override_extended(f, o); - } - -finish: - hashmap_free_free(top); - hashmap_free_free(bottom); - - HASHMAP_FOREACH_KEY(h, key, drops, i){ - hashmap_free_free(hashmap_remove(drops, key)); - hashmap_remove(drops, key); - free(key); - } - hashmap_free(drops); - - return r < 0 ? r : n_found; -} - -static int process_suffixes(const char *onlyprefix) { - const char *n; - int n_found = 0, r; - - NULSTR_FOREACH(n, suffixes) { - r = process_suffix(n, onlyprefix); - if (r < 0) - return r; - - n_found += r; - } - - return n_found; -} - -static int process_suffix_chop(const char *arg) { - const char *p; - - assert(arg); - - if (!path_is_absolute(arg)) - return process_suffix(arg, NULL); - - /* Strip prefix from the suffix */ - NULSTR_FOREACH(p, prefixes) { - const char *suffix; - - suffix = startswith(arg, p); - if (suffix) { - suffix += strspn(suffix, "/"); - if (*suffix) - return process_suffix(suffix, NULL); - else - return process_suffixes(arg); - } - } - - log_error("Invalid suffix specification %s.", arg); - return -EINVAL; -} - -static void help(void) { - printf("%s [OPTIONS...] [SUFFIX...]\n\n" - "Find overridden configuration files.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --diff[=1|0] Show a diff when overridden files differ\n" - " -t --type=LIST... Only display a selected set of override types\n" - , program_invocation_short_name); -} - -static int parse_flags(const char *flag_str, int flags) { - const char *word, *state; - size_t l; - - FOREACH_WORD_SEPARATOR(word, l, flag_str, ",", state) { - if (strneq("masked", word, l)) - flags |= SHOW_MASKED; - else if (strneq ("equivalent", word, l)) - flags |= SHOW_EQUIVALENT; - else if (strneq("redirected", word, l)) - flags |= SHOW_REDIRECTED; - else if (strneq("overridden", word, l)) - flags |= SHOW_OVERRIDDEN; - else if (strneq("unchanged", word, l)) - flags |= SHOW_UNCHANGED; - else if (strneq("extended", word, l)) - flags |= SHOW_EXTENDED; - else if (strneq("default", word, l)) - flags |= SHOW_DEFAULTS; - else - return -EINVAL; - } - return flags; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_NO_PAGER = 0x100, - ARG_DIFF, - ARG_VERSION - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "diff", optional_argument, NULL, ARG_DIFF }, - { "type", required_argument, NULL, 't' }, - {} - }; - - int c; - - assert(argc >= 1); - assert(argv); - - while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case 't': { - int f; - f = parse_flags(optarg, arg_flags); - if (f < 0) { - log_error("Failed to parse flags field."); - return -EINVAL; - } - arg_flags = f; - break; - } - - case ARG_DIFF: - if (!optarg) - arg_diff = 1; - else { - int b; - - b = parse_boolean(optarg); - if (b < 0) { - log_error("Failed to parse diff boolean."); - return -EINVAL; - } - - arg_diff = b; - } - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -int main(int argc, char *argv[]) { - int r, k, n_found = 0; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (arg_flags == 0) - arg_flags = SHOW_DEFAULTS; - - if (arg_diff < 0) - arg_diff = !!(arg_flags & SHOW_OVERRIDDEN); - else if (arg_diff) - arg_flags |= SHOW_OVERRIDDEN; - - pager_open_if_enabled(); - - if (optind < argc) { - int i; - - for (i = optind; i < argc; i++) { - path_kill_slashes(argv[i]); - - k = process_suffix_chop(argv[i]); - if (k < 0) - r = k; - else - n_found += k; - } - - } else { - k = process_suffixes(NULL); - if (k < 0) - r = k; - else - n_found += k; - } - - if (r >= 0) - printf("%s%i overridden configuration files found.\n", n_found ? "\n" : "", n_found); - -finish: - pager_close(); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/detect-virt/Makefile b/src/detect-virt/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/detect-virt/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/detect-virt/Makefile b/src/detect-virt/Makefile new file mode 100644 index 0000000000..529a3a2561 --- /dev/null +++ b/src/detect-virt/Makefile @@ -0,0 +1,35 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_detect_virt_SOURCES = \ + src/detect-virt/detect-virt.c + +systemd_detect_virt_LDADD = \ + libshared.la + +INSTALL_EXEC_HOOKS += \ + systemd-detect-virt-install-hook + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/escape/Makefile b/src/escape/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/escape/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/escape/escape.c b/src/escape/escape.c deleted file mode 100644 index 9f39049577..0000000000 --- a/src/escape/escape.c +++ /dev/null @@ -1,237 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Michael Biebl - - 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 . -***/ - -#include -#include -#include - -#include "alloc-util.h" -#include "log.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" - -static enum { - ACTION_ESCAPE, - ACTION_UNESCAPE, - ACTION_MANGLE -} arg_action = ACTION_ESCAPE; -static const char *arg_suffix = NULL; -static const char *arg_template = NULL; -static bool arg_path = false; - -static void help(void) { - printf("%s [OPTIONS...] [NAME...]\n\n" - "Show system and user paths.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --suffix=SUFFIX Unit suffix to append to escaped strings\n" - " --template=TEMPLATE Insert strings as instance into template\n" - " -u --unescape Unescape strings\n" - " -m --mangle Mangle strings\n" - " -p --path When escaping/unescaping assume the string is a path\n" - , program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_SUFFIX, - ARG_TEMPLATE - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "suffix", required_argument, NULL, ARG_SUFFIX }, - { "template", required_argument, NULL, ARG_TEMPLATE }, - { "unescape", no_argument, NULL, 'u' }, - { "mangle", no_argument, NULL, 'm' }, - { "path", no_argument, NULL, 'p' }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hump", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_SUFFIX: - - if (unit_type_from_string(optarg) < 0) { - log_error("Invalid unit suffix type %s.", optarg); - return -EINVAL; - } - - arg_suffix = optarg; - break; - - case ARG_TEMPLATE: - - if (!unit_name_is_valid(optarg, UNIT_NAME_TEMPLATE)) { - log_error("Template name %s is not valid.", optarg); - return -EINVAL; - } - - arg_template = optarg; - break; - - case 'u': - arg_action = ACTION_UNESCAPE; - break; - - case 'm': - arg_action = ACTION_MANGLE; - break; - - case 'p': - arg_path = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind >= argc) { - log_error("Not enough arguments."); - return -EINVAL; - } - - if (arg_template && arg_suffix) { - log_error("--suffix= and --template= may not be combined."); - return -EINVAL; - } - - if ((arg_template || arg_suffix) && arg_action != ACTION_ESCAPE) { - log_error("--suffix= and --template= are not compatible with --unescape or --mangle."); - return -EINVAL; - } - - if (arg_path && !IN_SET(arg_action, ACTION_ESCAPE, ACTION_UNESCAPE)) { - log_error("--path may not be combined with --mangle."); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char *argv[]) { - char **i; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - STRV_FOREACH(i, argv + optind) { - _cleanup_free_ char *e = NULL; - - switch (arg_action) { - - case ACTION_ESCAPE: - if (arg_path) { - r = unit_name_path_escape(*i, &e); - if (r < 0) { - log_error_errno(r, "Failed to escape string: %m"); - goto finish; - } - } else { - e = unit_name_escape(*i); - if (!e) { - r = log_oom(); - goto finish; - } - } - - if (arg_template) { - char *x; - - r = unit_name_replace_instance(arg_template, e, &x); - if (r < 0) { - log_error_errno(r, "Failed to replace instance: %m"); - goto finish; - } - - free(e); - e = x; - } else if (arg_suffix) { - char *x; - - x = strjoin(e, ".", arg_suffix, NULL); - if (!x) { - r = log_oom(); - goto finish; - } - - free(e); - e = x; - } - - break; - - case ACTION_UNESCAPE: - if (arg_path) - r = unit_name_path_unescape(*i, &e); - else - r = unit_name_unescape(*i, &e); - - if (r < 0) { - log_error_errno(r, "Failed to unescape string: %m"); - goto finish; - } - break; - - case ACTION_MANGLE: - r = unit_name_mangle(*i, UNIT_NAME_NOGLOB, &e); - if (r < 0) { - log_error_errno(r, "Failed to mangle name: %m"); - goto finish; - } - break; - } - - if (i != argv+optind) - fputc(' ', stdout); - - fputs(e, stdout); - } - - fputc('\n', stdout); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/firstboot/Makefile b/src/firstboot/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/firstboot/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/firstboot/Makefile b/src/firstboot/Makefile new file mode 100644 index 0000000000..97f06890c5 --- /dev/null +++ b/src/firstboot/Makefile @@ -0,0 +1,47 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_FIRSTBOOT),) +systemd_firstboot_SOURCES = \ + src/firstboot/firstboot.c + +systemd_firstboot_LDADD = \ + libshared.la \ + -lcrypt + +rootbin_PROGRAMS += \ + systemd-firstboot + +nodist_systemunit_DATA += \ + units/systemd-firstboot.service + +SYSINIT_TARGET_WANTS += \ + systemd-firstboot.service +endif + +EXTRA_DIST += \ + units/systemd-firstboot.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/fsck/Makefile b/src/fsck/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/fsck/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/fsck/Makefile b/src/fsck/Makefile new file mode 100644 index 0000000000..9c981837f5 --- /dev/null +++ b/src/fsck/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_fsck_SOURCES = \ + src/fsck/fsck.c + +systemd_fsck_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/fsck/fsck.c b/src/fsck/fsck.c index 6f56066da8..f21433c0ad 100644 --- a/src/fsck/fsck.c +++ b/src/fsck/fsck.c @@ -27,8 +27,8 @@ #include #include -#include "sd-bus.h" -#include "sd-device.h" +#include +#include #include "alloc-util.h" #include "bus-common-errors.h" diff --git a/src/fstab-generator/Makefile b/src/fstab-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/fstab-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/fstab-generator/Makefile b/src/fstab-generator/Makefile new file mode 100644 index 0000000000..4d201612b8 --- /dev/null +++ b/src/fstab-generator/Makefile @@ -0,0 +1,33 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_fstab_generator_SOURCES = \ + src/fstab-generator/fstab-generator.c \ + src/core/mount-setup.c + +systemd_fstab_generator_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/getty-generator/Makefile b/src/getty-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/getty-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/getty-generator/Makefile b/src/getty-generator/Makefile new file mode 100644 index 0000000000..a070f1c424 --- /dev/null +++ b/src/getty-generator/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_getty_generator_SOURCES = \ + src/getty-generator/getty-generator.c + +systemd_getty_generator_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/gpt-auto-generator/Makefile b/src/gpt-auto-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/gpt-auto-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/gpt-auto-generator/Makefile b/src/gpt-auto-generator/Makefile new file mode 100644 index 0000000000..1c82e8fae8 --- /dev/null +++ b/src/gpt-auto-generator/Makefile @@ -0,0 +1,43 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_BLKID),) +systemgenerator_PROGRAMS += \ + systemd-gpt-auto-generator + +systemd_gpt_auto_generator_SOURCES = \ + src/gpt-auto-generator/gpt-auto-generator.c \ + src/basic/blkid-util.h + +systemd_gpt_auto_generator_LDADD = \ + libshared.la \ + $(BLKID_LIBS) + +systemd_gpt_auto_generator_CFLAGS = \ + $(AM_CFLAGS) \ + $(BLKID_CFLAGS) +endif + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index af96adec06..a8149b8756 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -23,7 +23,7 @@ #include #include "libudev.h" -#include "sd-id128.h" +#include #include "alloc-util.h" #include "blkid-util.h" diff --git a/src/hibernate-resume/Makefile b/src/hibernate-resume/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/hibernate-resume/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/hibernate-resume/Makefile b/src/hibernate-resume/Makefile new file mode 100644 index 0000000000..70f7ec7cca --- /dev/null +++ b/src/hibernate-resume/Makefile @@ -0,0 +1,60 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_HIBERNATE),) +systemgenerator_PROGRAMS += \ + systemd-hibernate-resume-generator + +rootlibexec_PROGRAMS += \ + systemd-hibernate-resume + +systemd_hibernate_resume_SOURCES = \ + src/hibernate-resume/hibernate-resume.c + +systemd_hibernate_resume_LDADD = \ + libshared.la + +systemd_hibernate_resume_generator_SOURCES = \ + src/hibernate-resume/hibernate-resume-generator.c + +systemd_hibernate_resume_generator_LDADD = \ + libshared.la + +dist_systemunit_DATA += \ + units/hibernate.target \ + units/hybrid-sleep.target + +nodist_systemunit_DATA += \ + units/systemd-hibernate.service \ + units/systemd-hibernate-resume@.service \ + units/systemd-hybrid-sleep.service +endif + +EXTRA_DIST += \ + units/systemd-hibernate.service.in \ + units/systemd-hibernate-resume@.service.in \ + units/systemd-hybrid-sleep.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/hostname/Makefile b/src/hostname/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/hostname/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/hostname/Makefile b/src/hostname/Makefile new file mode 100644 index 0000000000..12b0628cb4 --- /dev/null +++ b/src/hostname/Makefile @@ -0,0 +1,79 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_HOSTNAMED),) +systemd_hostnamed_SOURCES = \ + src/hostname/hostnamed.c + +systemd_hostnamed_LDADD = \ + libshared.la + +rootlibexec_PROGRAMS += \ + systemd-hostnamed + +nodist_systemunit_DATA += \ + units/systemd-hostnamed.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.hostname1.busname + +dist_dbuspolicy_DATA += \ + src/hostname/org.freedesktop.hostname1.conf + +dist_dbussystemservice_DATA += \ + src/hostname/org.freedesktop.hostname1.service + +polkitpolicy_files += \ + src/hostname/org.freedesktop.hostname1.policy + +SYSTEM_UNIT_ALIASES += \ + systemd-hostnamed.service dbus-org.freedesktop.hostname1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.hostname1.busname + +hostnamectl_SOURCES = \ + src/hostname/hostnamectl.c + +hostnamectl_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + hostnamectl + +dist_bashcompletion_data += \ + shell-completion/bash/hostnamectl + +dist_zshcompletion_data += \ + shell-completion/zsh/_hostnamectl +endif + +polkitpolicy_in_files += \ + src/hostname/org.freedesktop.hostname1.policy.in + +EXTRA_DIST += \ + units/systemd-hostnamed.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/hostname/hostnamectl.c b/src/hostname/hostnamectl.c index c16a324232..aabc3b30c0 100644 --- a/src/hostname/hostnamectl.c +++ b/src/hostname/hostnamectl.c @@ -23,8 +23,8 @@ #include #include -#include "sd-bus.h" -#include "sd-id128.h" +#include +#include #include "alloc-util.h" #include "architecture.h" diff --git a/src/hwdb/Makefile b/src/hwdb/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/hwdb/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/import/Makefile b/src/import/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/import/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/import/Makefile b/src/import/Makefile new file mode 100644 index 0000000000..aefe6d615b --- /dev/null +++ b/src/import/Makefile @@ -0,0 +1,191 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_IMPORTD),) + +ifneq ($(HAVE_LIBCURL),) +ifneq ($(HAVE_XZ),) +ifneq ($(HAVE_ZLIB),) +ifneq ($(HAVE_BZIP2),) +ifneq ($(HAVE_GCRYPT),) + +rootlibexec_PROGRAMS += \ + systemd-importd \ + systemd-pull \ + systemd-import \ + systemd-export + +systemd_importd_SOURCES = \ + src/import/importd.c + +systemd_importd_CFLAGS = \ + $(AM_CFLAGS) \ + -D SYSTEMD_PULL_PATH=\"$(rootlibexecdir)/systemd-pull\" \ + -D SYSTEMD_IMPORT_PATH=\"$(rootlibexecdir)/systemd-import\" \ + -D SYSTEMD_EXPORT_PATH=\"$(rootlibexecdir)/systemd-export\" + +systemd_importd_LDADD = \ + libshared.la + +systemd_pull_SOURCES = \ + src/import/pull.c \ + src/import/pull-raw.c \ + src/import/pull-raw.h \ + src/import/pull-tar.c \ + src/import/pull-tar.h \ + src/import/pull-job.c \ + src/import/pull-job.h \ + src/import/pull-common.c \ + src/import/pull-common.h \ + src/import/import-common.c \ + src/import/import-common.h \ + src/import/import-compress.c \ + src/import/import-compress.h \ + src/import/curl-util.c \ + src/import/curl-util.h \ + src/import/aufs-util.c \ + src/import/aufs-util.h \ + src/import/qcow2-util.c \ + src/import/qcow2-util.h + +systemd_pull_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBCURL_CFLAGS) \ + $(XZ_CFLAGS) \ + $(ZLIB_CFLAGS) \ + $(GCRYPT_CFLAGS) \ + -D VENDOR_KEYRING_PATH=\"$(rootlibexecdir)/import-pubring.gpg\" \ + -D USER_KEYRING_PATH=\"$(pkgsysconfdir)/import-pubring.gpg\" + +systemd_pull_LDADD = \ + libshared.la \ + $(LIBCURL_LIBS) \ + $(XZ_LIBS) \ + $(ZLIB_LIBS) \ + -lbz2 \ + $(GCRYPT_LIBS) + +systemd_import_SOURCES = \ + src/import/import.c \ + src/import/import-raw.c \ + src/import/import-raw.h \ + src/import/import-tar.c \ + src/import/import-tar.h \ + src/import/import-common.c \ + src/import/import-common.h \ + src/import/import-compress.c \ + src/import/import-compress.h \ + src/import/qcow2-util.c \ + src/import/qcow2-util.h + +systemd_import_CFLAGS = \ + $(AM_CFLAGS) \ + $(XZ_CFLAGS) \ + $(ZLIB_CFLAGS) + +systemd_import_LDADD = \ + libshared.la \ + $(XZ_LIBS) \ + $(ZLIB_LIBS) \ + -lbz2 + +systemd_export_SOURCES = \ + src/import/export.c \ + src/import/export-tar.c \ + src/import/export-tar.h \ + src/import/export-raw.c \ + src/import/export-raw.h \ + src/import/import-common.c \ + src/import/import-common.h \ + src/import/import-compress.c \ + src/import/import-compress.h + +systemd_export_CFLAGS = \ + $(AM_CFLAGS) \ + $(XZ_CFLAGS) \ + $(ZLIB_CFLAGS) + +systemd_export_LDADD = \ + libshared.la \ + $(XZ_LIBS) \ + $(ZLIB_LIBS) \ + -lbz2 + +dist_rootlibexec_DATA = \ + src/import/import-pubring.gpg + +nodist_systemunit_DATA += \ + units/systemd-importd.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.import1.busname + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.import1.busname + +SYSTEM_UNIT_ALIASES += \ + systemd-importd.service dbus-org.freedesktop.import1.service + +dist_dbussystemservice_DATA += \ + src/import/org.freedesktop.import1.service + +dist_dbuspolicy_DATA += \ + src/import/org.freedesktop.import1.conf + +polkitpolicy_files += \ + src/import/org.freedesktop.import1.policy + +manual_tests += \ + test-qcow2 + +test_qcow2_SOURCES = \ + src/import/test-qcow2.c \ + src/import/qcow2-util.c \ + src/import/qcow2-util.h + +test_qcow2_CFLAGS = \ + $(AM_CFLAGS) \ + $(ZLIB_CFLAGS) + +test_qcow2_LDADD = \ + libshared.la \ + $(ZLIB_LIBS) + +endif +endif +endif +endif +endif + +endif + +polkitpolicy_in_files += \ + src/import/org.freedesktop.import1.policy.in + +EXTRA_DIST += \ + units/systemd-importd.service.in + + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/import/curl-util.h b/src/import/curl-util.h index a758cc5640..2e71bd3b5d 100644 --- a/src/import/curl-util.h +++ b/src/import/curl-util.h @@ -22,7 +22,7 @@ #include #include -#include "sd-event.h" +#include #include "hashmap.h" diff --git a/src/import/export-raw.c b/src/import/export-raw.c index db06e11b87..658f835132 100644 --- a/src/import/export-raw.c +++ b/src/import/export-raw.c @@ -25,7 +25,7 @@ #include #undef basename -#include "sd-daemon.h" +#include #include "alloc-util.h" #include "btrfs-util.h" diff --git a/src/import/export-raw.h b/src/import/export-raw.h index 8e723d4908..c7ac134603 100644 --- a/src/import/export-raw.h +++ b/src/import/export-raw.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" +#include #include "import-compress.h" #include "macro.h" diff --git a/src/import/export-tar.c b/src/import/export-tar.c index d79c27f2d0..9c511984c1 100644 --- a/src/import/export-tar.c +++ b/src/import/export-tar.c @@ -17,7 +17,7 @@ along with systemd; If not, see . ***/ -#include "sd-daemon.h" +#include #include "alloc-util.h" #include "btrfs-util.h" diff --git a/src/import/export-tar.h b/src/import/export-tar.h index 1e3c8bb80c..50206cabb3 100644 --- a/src/import/export-tar.h +++ b/src/import/export-tar.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" +#include #include "import-compress.h" #include "macro.h" diff --git a/src/import/export.c b/src/import/export.c index cc98c33ef6..0a5efe5476 100644 --- a/src/import/export.c +++ b/src/import/export.c @@ -19,7 +19,7 @@ #include -#include "sd-event.h" +#include #include "alloc-util.h" #include "export-raw.h" diff --git a/src/import/import-raw.c b/src/import/import-raw.c index fd6b9f7703..ce37392707 100644 --- a/src/import/import-raw.c +++ b/src/import/import-raw.c @@ -19,8 +19,8 @@ #include -#include "sd-daemon.h" -#include "sd-event.h" +#include +#include #include "alloc-util.h" #include "btrfs-util.h" diff --git a/src/import/import-raw.h b/src/import/import-raw.h index 4f543e0883..f0a315c088 100644 --- a/src/import/import-raw.h +++ b/src/import/import-raw.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" +#include #include "import-util.h" #include "macro.h" diff --git a/src/import/import-tar.c b/src/import/import-tar.c index 8b81324fde..016d05e77d 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -19,8 +19,8 @@ #include -#include "sd-daemon.h" -#include "sd-event.h" +#include +#include #include "alloc-util.h" #include "btrfs-util.h" diff --git a/src/import/import-tar.h b/src/import/import-tar.h index 24abe06c8f..b66b00ddfd 100644 --- a/src/import/import-tar.h +++ b/src/import/import-tar.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" +#include #include "import-util.h" #include "macro.h" diff --git a/src/import/import.c b/src/import/import.c index 4e442ee84a..338847dbc2 100644 --- a/src/import/import.c +++ b/src/import/import.c @@ -19,7 +19,7 @@ #include -#include "sd-event.h" +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/import/importd.c b/src/import/importd.c index d2a5867a6e..b44d0525ae 100644 --- a/src/import/importd.c +++ b/src/import/importd.c @@ -19,7 +19,7 @@ #include -#include "sd-bus.h" +#include #include "alloc-util.h" #include "bus-common-errors.h" diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index 8a16602c3e..ca49f0c7d7 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -21,7 +21,7 @@ #include #include -#include "sd-daemon.h" +#include #include "alloc-util.h" #include "btrfs-util.h" diff --git a/src/import/pull-raw.h b/src/import/pull-raw.h index 8f6d16eb3a..6bafa6dafd 100644 --- a/src/import/pull-raw.h +++ b/src/import/pull-raw.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" +#include #include "import-util.h" #include "macro.h" diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index afb13366f0..1fed468b7e 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -20,7 +20,7 @@ #include #include -#include "sd-daemon.h" +#include #include "alloc-util.h" #include "btrfs-util.h" diff --git a/src/import/pull-tar.h b/src/import/pull-tar.h index 7e63e496d8..9ff5bd5953 100644 --- a/src/import/pull-tar.h +++ b/src/import/pull-tar.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-event.h" +#include #include "import-util.h" #include "macro.h" diff --git a/src/import/pull.c b/src/import/pull.c index 72604a6a74..74df24f993 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -19,7 +19,7 @@ #include -#include "sd-event.h" +#include #include "alloc-util.h" #include "hostname-util.h" diff --git a/src/initctl/Makefile b/src/initctl/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/initctl/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/initctl/Makefile b/src/initctl/Makefile new file mode 100644 index 0000000000..b63a2e47d9 --- /dev/null +++ b/src/initctl/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_initctl_SOURCES = \ + src/initctl/initctl.c + +systemd_initctl_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/initctl/initctl.c b/src/initctl/initctl.c index 3e57afb997..27f1fd04a1 100644 --- a/src/initctl/initctl.c +++ b/src/initctl/initctl.c @@ -23,8 +23,8 @@ #include #include -#include "sd-bus.h" -#include "sd-daemon.h" +#include +#include #include "alloc-util.h" #include "bus-error.h" diff --git a/src/journal-remote/Makefile b/src/journal-remote/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/journal-remote/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/journal-remote/Makefile b/src/journal-remote/Makefile new file mode 100644 index 0000000000..302f326303 --- /dev/null +++ b/src/journal-remote/Makefile @@ -0,0 +1,113 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_MICROHTTPD),) +rootlibexec_PROGRAMS += \ + systemd-journal-remote + +systemd_journal_remote_SOURCES = \ + src/journal-remote/journal-remote-parse.h \ + src/journal-remote/journal-remote-parse.c \ + src/journal-remote/journal-remote-write.h \ + src/journal-remote/journal-remote-write.c \ + src/journal-remote/journal-remote.h \ + src/journal-remote/journal-remote.c + +systemd_journal_remote_LDADD = \ + libjournal-core.la + +systemd_journal_remote_SOURCES += \ + src/journal-remote/microhttpd-util.h \ + src/journal-remote/microhttpd-util.c + +systemd_journal_remote_CFLAGS = \ + $(AM_CFLAGS) \ + $(MICROHTTPD_CFLAGS) + +systemd_journal_remote_LDADD += \ + $(MICROHTTPD_LIBS) + +ifneq ($(ENABLE_TMPFILES),) +dist_tmpfiles_DATA += \ + tmpfiles.d/systemd-remote.conf +endif + +ifneq ($(HAVE_GNUTLS),) +systemd_journal_remote_LDADD += \ + $(GNUTLS_LIBS) +endif + +# systemd-journal-remote make sense mostly with full crypto stack +dist_systemunit_DATA += \ + units/systemd-journal-remote.socket + +nodist_systemunit_DATA += \ + units/systemd-journal-remote.service + +journal-remote-install-hook: journal-install-hook + -$(MKDIR_P) $(DESTDIR)/var/log/journal/remote + -chown 0:0 $(DESTDIR)/var/log/journal/remote + -chmod 755 $(DESTDIR)/var/log/journal/remote + +INSTALL_EXEC_HOOKS += journal-remote-install-hook + +nodist_pkgsysconf_DATA += \ + src/journal-remote/journal-remote.conf + +EXTRA_DIST += \ + units/systemd-journal-remote.service.in \ + src/journal-remote/journal-remote.conf.in \ + src/journal-remote/log-generator.py +endif + +ifneq ($(HAVE_LIBCURL),) +rootlibexec_PROGRAMS += \ + systemd-journal-upload + +systemd_journal_upload_SOURCES = \ + src/journal-remote/journal-upload.h \ + src/journal-remote/journal-upload.c \ + src/journal-remote/journal-upload-journal.c + +systemd_journal_upload_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBCURL_CFLAGS) + +systemd_journal_upload_LDADD = \ + libshared.la \ + $(LIBCURL_LIBS) + +nodist_systemunit_DATA += \ + units/systemd-journal-upload.service + +nodist_pkgsysconf_DATA += \ + src/journal-remote/journal-upload.conf +endif + +EXTRA_DIST += \ + units/systemd-journal-upload.service.in \ + src/journal-remote/journal-upload.conf.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c index 3b9adb53e1..0e27a80cf2 100644 --- a/src/journal-remote/journal-gatewayd.c +++ b/src/journal-remote/journal-gatewayd.c @@ -27,9 +27,9 @@ #include #include -#include "sd-bus.h" -#include "sd-daemon.h" -#include "sd-journal.h" +#include +#include +#include #include "alloc-util.h" #include "bus-util.h" diff --git a/src/journal-remote/journal-remote-parse.h b/src/journal-remote/journal-remote-parse.h index 0b8b6af736..91c5650798 100644 --- a/src/journal-remote/journal-remote-parse.h +++ b/src/journal-remote/journal-remote-parse.h @@ -19,7 +19,7 @@ #pragma once -#include "sd-event.h" +#include #include "journal-remote-write.h" diff --git a/src/journal-remote/journal-remote.c b/src/journal-remote/journal-remote.c index 3ce6fe27b3..db8772d3e6 100644 --- a/src/journal-remote/journal-remote.c +++ b/src/journal-remote/journal-remote.c @@ -31,7 +31,7 @@ #include #endif -#include "sd-daemon.h" +#include #include "alloc-util.h" #include "conf-parser.h" diff --git a/src/journal-remote/journal-remote.h b/src/journal-remote/journal-remote.h index 6466a1c101..de83388eef 100644 --- a/src/journal-remote/journal-remote.h +++ b/src/journal-remote/journal-remote.h @@ -20,7 +20,7 @@ #pragma once -#include "sd-event.h" +#include #include "hashmap.h" #include "journal-remote-parse.h" diff --git a/src/journal-remote/journal-upload.c b/src/journal-remote/journal-upload.c index 440563e7d3..be3012209c 100644 --- a/src/journal-remote/journal-upload.c +++ b/src/journal-remote/journal-upload.c @@ -23,7 +23,7 @@ #include #include -#include "sd-daemon.h" +#include #include "alloc-util.h" #include "conf-parser.h" diff --git a/src/journal-remote/journal-upload.h b/src/journal-remote/journal-upload.h index b8cd04d527..40f64dfc60 100644 --- a/src/journal-remote/journal-upload.h +++ b/src/journal-remote/journal-upload.h @@ -2,8 +2,8 @@ #include -#include "sd-event.h" -#include "sd-journal.h" +#include +#include typedef enum { ENTRY_CURSOR = 0, /* Nothing actually written yet. */ diff --git a/src/journal/Makefile b/src/journal/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/journal/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/journal/Makefile b/src/journal/Makefile new file mode 100644 index 0000000000..de004d78e9 --- /dev/null +++ b/src/journal/Makefile @@ -0,0 +1,361 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +audit_list_includes = -include linux/audit.h -include missing.h +ifneq ($(HAVE_AUDIT),) +audit_list_includes += -include libaudit.h +endif + +$(outdir)/audit_type-list.txt: + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(CPP) $(CFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) -dM $(audit_list_includes) - $@ + +$(outdir)/audit_type-to-name.h: src/journal/audit_type-list.txt + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(AWK) 'BEGIN{ print "const char *audit_type_to_string(int type) {\n\tswitch(type) {" } {printf " case AUDIT_%s: return \"%s\";\n", $$1, $$1 } END{ print " default: return NULL;\n\t}\n}\n" }' <$< >$@ + + +$(outdir)/dns_type-list.txt: src/resolve/dns-type.h + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(SED) -n -r 's/.* DNS_TYPE_(\w+).*/\1/p' <$< >$@ + +$(outdir)/dns_type-to-name.h: src/resolve/dns_type-list.txt + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(AWK) 'BEGIN{ print "const char *dns_type_to_string(int type) {\n\tswitch(type) {" } {printf " case DNS_TYPE_%s: return ", $$1; sub(/_/, "-"); printf "\"%s\";\n", $$1 } END{ print " default: return NULL;\n\t}\n}\n" }' <$< >$@ + +$(outdir)/dns_type-from-name.gperf: src/resolve/dns_type-list.txt + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct dns_type_name { const char* name; int id; };"; print "%null-strings"; print "%%";} { s=$$1; sub(/_/, "-", s); printf "%s, ", $$s; printf "DNS_TYPE_%s\n", $$1 }' <$< >$@ + +systemd_journald_SOURCES = \ + src/journal/journald.c \ + src/journal/journald-server.h + +systemd_journald_LDADD = \ + libjournal-core.la \ + libshared.la + +systemd_cat_SOURCES = \ + src/journal/cat.c + +systemd_cat_LDADD = \ + libjournal-core.la + +# using _CFLAGS = in the conditional below would suppress AM_CFLAGS +journalctl_CFLAGS = \ + $(AM_CFLAGS) + +journalctl_SOURCES = \ + src/journal/journalctl.c + +journalctl_LDADD = \ + libshared.la \ + libudev-core.la + +ifneq ($(HAVE_QRENCODE),) +journalctl_SOURCES += \ + src/journal/journal-qrcode.c \ + src/journal/journal-qrcode.h + +journalctl_CFLAGS += \ + $(QRENCODE_CFLAGS) + +journalctl_LDADD += \ + $(QRENCODE_LIBS) +endif + +test_journal_SOURCES = \ + src/journal/test-journal.c + +test_journal_LDADD = \ + libjournal-core.la + +test_journal_send_SOURCES = \ + src/journal/test-journal-send.c + +test_journal_send_LDADD = \ + libjournal-core.la + +test_journal_syslog_SOURCES = \ + src/journal/test-journal-syslog.c + +test_journal_syslog_LDADD = \ + libjournal-core.la + +test_journal_match_SOURCES = \ + src/journal/test-journal-match.c + +test_journal_match_LDADD = \ + libjournal-core.la + +test_journal_enum_SOURCES = \ + src/journal/test-journal-enum.c + +test_journal_enum_LDADD = \ + libjournal-core.la + +test_journal_stream_SOURCES = \ + src/journal/test-journal-stream.c + +test_journal_stream_LDADD = \ + libjournal-core.la + +test_journal_flush_SOURCES = \ + src/journal/test-journal-flush.c + +test_journal_flush_LDADD = \ + libjournal-core.la + +test_journal_init_SOURCES = \ + src/journal/test-journal-init.c + +test_journal_init_LDADD = \ + libjournal-core.la + +test_journal_verify_SOURCES = \ + src/journal/test-journal-verify.c + +test_journal_verify_LDADD = \ + libjournal-core.la + +test_journal_interleaving_SOURCES = \ + src/journal/test-journal-interleaving.c + +test_journal_interleaving_LDADD = \ + libjournal-core.la + +test_mmap_cache_SOURCES = \ + src/journal/test-mmap-cache.c + +test_mmap_cache_LDADD = \ + libjournal-core.la + +test_catalog_SOURCES = \ + src/journal/test-catalog.c + +test_catalog_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DCATALOG_DIR=\"$(abs_top_srcdir)/catalog\" + +test_catalog_LDADD = \ + libjournal-core.la + +test_compress_SOURCES = \ + src/journal/test-compress.c + +test_compress_LDADD = \ + libshared.la + +test_compress_benchmark_SOURCES = \ + src/journal/test-compress-benchmark.c + +test_compress_benchmark_LDADD = \ + libshared.la + +test_audit_type_SOURCES = \ + src/journal/test-audit-type.c + +test_audit_type_LDADD = \ + libjournal-core.la + +libjournal_core_la_SOURCES = \ + src/journal/journald-kmsg.c \ + src/journal/journald-kmsg.h \ + src/journal/journald-syslog.c \ + src/journal/journald-syslog.h \ + src/journal/journald-stream.c \ + src/journal/journald-stream.h \ + src/journal/journald-server.c \ + src/journal/journald-server.h \ + src/journal/journald-console.c \ + src/journal/journald-console.h \ + src/journal/journald-wall.c \ + src/journal/journald-wall.h \ + src/journal/journald-native.c \ + src/journal/journald-native.h \ + src/journal/journald-audit.c \ + src/journal/journald-audit.h \ + src/journal/journald-rate-limit.c \ + src/journal/journald-rate-limit.h \ + src/journal/journal-internal.h + +nodist_libjournal_core_la_SOURCES = \ + src/journal/journald-gperf.c + +libjournal_core_la_LIBADD = \ + libshared.la + +noinst_LTLIBRARIES += \ + libjournal-core.la + +journal-install-hook: + -$(MKDIR_P) $(DESTDIR)/var/log/journal + -chown 0:0 $(DESTDIR)/var/log/journal + -chmod 755 $(DESTDIR)/var/log/journal + -setfacl -nm g:adm:rx,d:g:adm:rx $(DESTDIR)/var/log/journal/ + -setfacl -nm g:wheel:rx,d:g:wheel:rx $(DESTDIR)/var/log/journal/ + +journal-uninstall-hook: + -rmdir $(DESTDIR)/var/log/journal/remote + -rmdir $(DESTDIR)/var/log/journal/ + +INSTALL_EXEC_HOOKS += journal-install-hook +UNINSTALL_EXEC_HOOKS += journal-uninstall-hook + +# ------------------------------------------------------------------------------ +# Update catalog on installation. Do not bother if installing +# in DESTDIR, since this is likely for packaging purposes. +catalog-update-hook: + -test -n "$(DESTDIR)" || $(rootbindir)/journalctl --update-catalog + +INSTALL_DATA_HOOKS += \ + catalog-update-hook + +catalog-remove-hook: + -test -n "$(DESTDIR)" || rm -f $(catalogstatedir)/database + +UNINSTALL_DATA_HOOKS += \ + catalog-remove-hook + +manual_tests += \ + test-journal-enum + +tests += \ + test-journal \ + test-journal-send \ + test-journal-syslog \ + test-journal-match \ + test-journal-stream \ + test-journal-init \ + test-journal-verify \ + test-journal-interleaving \ + test-journal-flush \ + test-mmap-cache \ + test-catalog \ + test-audit-type + +ifneq ($(HAVE_COMPRESSION),) +tests += \ + test-compress \ + test-compress-benchmark +endif + + +rootlibexec_PROGRAMS += \ + systemd-journald + +rootbin_PROGRAMS += \ + journalctl + +bin_PROGRAMS += \ + systemd-cat + +dist_systemunit_DATA += \ + units/systemd-journald.socket \ + units/systemd-journald-dev-log.socket \ + units/systemd-journald-audit.socket + +nodist_systemunit_DATA += \ + units/systemd-journald.service \ + units/systemd-journal-flush.service \ + units/systemd-journal-catalog-update.service + +dist_pkgsysconf_DATA += \ + src/journal/journald.conf + +dist_catalog_DATA = \ + catalog/systemd.be.catalog \ + catalog/systemd.be@latin.catalog \ + catalog/systemd.fr.catalog \ + catalog/systemd.it.catalog \ + catalog/systemd.pl.catalog \ + catalog/systemd.pt_BR.catalog \ + catalog/systemd.ru.catalog \ + catalog/systemd.zh_CN.catalog \ + catalog/systemd.zh_TW.catalog \ + catalog/systemd.catalog + +SOCKETS_TARGET_WANTS += \ + systemd-journald.socket \ + systemd-journald-dev-log.socket \ + systemd-journald-audit.socket + +SYSINIT_TARGET_WANTS += \ + systemd-journald.service \ + systemd-journal-flush.service \ + systemd-journal-catalog-update.service + +EXTRA_DIST += \ + units/systemd-journald.service.in \ + units/systemd-journal-flush.service.in \ + units/systemd-journal-catalog-update.service.in + +gperf_gperf_sources += \ + src/journal/journald-gperf.gperf + +# ------------------------------------------------------------------------------ +ifneq ($(HAVE_MICROHTTPD),) +gatewayddocumentrootdir=$(pkgdatadir)/gatewayd + +rootlibexec_PROGRAMS += \ + systemd-journal-gatewayd + +systemd_journal_gatewayd_SOURCES = \ + src/journal-remote/journal-gatewayd.c \ + src/journal-remote/microhttpd-util.h \ + src/journal-remote/microhttpd-util.c + +systemd_journal_gatewayd_LDADD = \ + libshared.la \ + $(MICROHTTPD_LIBS) + +ifneq ($(HAVE_GNUTLS),) +systemd_journal_gatewayd_LDADD += \ + $(GNUTLS_LIBS) +endif + +systemd_journal_gatewayd_CFLAGS = \ + $(AM_CFLAGS) \ + $(MICROHTTPD_CFLAGS) + +systemd_journal_gatewayd_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -DDOCUMENT_ROOT=\"$(gatewayddocumentrootdir)\" + +dist_systemunit_DATA += \ + units/systemd-journal-gatewayd.socket + +nodist_systemunit_DATA += \ + units/systemd-journal-gatewayd.service + +dist_gatewayddocumentroot_DATA = \ + src/journal-remote/browse.html + +endif + +EXTRA_DIST += \ + units/systemd-journal-gatewayd.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/journal/cat.c b/src/journal/cat.c index 08c844d44f..93ab6e7f96 100644 --- a/src/journal/cat.c +++ b/src/journal/cat.c @@ -24,7 +24,7 @@ #include #include -#include "sd-journal.h" +#include #include "fd-util.h" #include "parse-util.h" diff --git a/src/journal/catalog.c b/src/journal/catalog.c index 164a3a15f2..962ca238b9 100644 --- a/src/journal/catalog.c +++ b/src/journal/catalog.c @@ -25,7 +25,7 @@ #include #include -#include "sd-id128.h" +#include #include "alloc-util.h" #include "catalog.h" diff --git a/src/journal/catalog.h b/src/journal/catalog.h index 1b1014b335..b621de3068 100644 --- a/src/journal/catalog.h +++ b/src/journal/catalog.h @@ -21,7 +21,7 @@ #include -#include "sd-id128.h" +#include #include "hashmap.h" #include "strbuf.h" diff --git a/src/journal/catalog/systemd.be.catalog b/src/journal/catalog/systemd.be.catalog new file mode 100644 index 0000000000..be081d6efc --- /dev/null +++ b/src/journal/catalog/systemd.be.catalog @@ -0,0 +1,260 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2015 Viktar Vaŭčkievič +# +# 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 . + +# Message catalog for systemd's own messages +# Belarusian translation + +# The catalog format is documented on +# Фармат каталога апісаны на старонцы +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: Сэрвіс журналявання запусціўся +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Працэс сістэмнага журналявання запусціўся, адкрыў файлы для +запісу і гатовы апрацоўваць запыты. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Сэрвіс журналявання спыніўся +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Працэс сістэмнага журналявання спыніўся і закрыў усе файлы. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Паведамленні з сэрвісу адкінуты +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Сэрвіс адправіў занадта штат паведамленняў за кароткі прамежак часу. +Частка паведамленняў была адкінута. + +Майце на ўвазе, што былі адкінуты паведамлення толькі гэтага сэрвісу. +Паведамленні іншых сэрвісаў засталіся. + +Мяжа, пасля якой паведамленні будуць адкінуты, наладжваецца з +дапамогай RateLimitInterval= і RateLimitBurst= у файле +/etc/systemd/journald.conf. Глядзіце journald.conf(5) для дэталей. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Паведамленні страчаны +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Паведамленні ядра былі страчаны, так як сістэма журналявання не паспела +іх апрацаваць. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Працэс @COREDUMP_PID@ (@COREDUMP_COMM@) скінуў дамп памяці +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Працэс @COREDUMP_PID@ (@COREDUMP_COMM@) разбіўся і скінуў дамп памяці. + +Звычайна гэта сведчыць аб памылцы ў праграмным кодзе. +Рэкамендуецца паведаміць аб гэтым распрацоўнікам. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Новая сесія № @SESSION_ID@ створана для карыстальніка @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Новая сесія з № @SESSION_ID@ створана для карыстальніка @USER_ID@. + +Лідар гэтай сесіі пад № @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Сесія № @SESSION_ID@ спынена +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Сесія № @SESSION_ID@ спынена. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Даступна новае працоўнае месца № @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Новае працоўнае месца № @SEAT_ID@ наладжана і даступна для выкарыстання. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Працоўнае месца № @SEAT_ID@ выдалена +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Працоўнае месца № @SEAT_ID@ выдалена і больш не даступна. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Час зменены +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Сістэмны гадзіннік зменены на @REALTIME@ мікрасекунд ад 1 студзеня 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Часавы пояс зменены на @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Сістэмны часавы пояс зменены на @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Запуск сістэмы завяршыўся +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Усе сістэмныя сэрвісы, неабходныя для загрузкі сістэмы, паспяхова +запусціліся. Майце на ўвазе, што гэта не значыць, што машына нічога не +робіць. Магчыма, некаторыя сэрвісы яшчэ ініцыялізіруюцца. + +На запуск ядра спатрэбілася @KERNEL_USEC@ мікрасекунд. + +На запуск пачатковага RAM-дыска спатрэбілася @INITRD_USEC@ мікрасекунд. + +На запуск сістэмных сэрвісаў спатрэбілася @USERSPACE_USEC@ мікрасекунд. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Сістэма перайшла ў стан сну @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Цяпер сістэма перайшла у стан сну @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Сістэма выйшла са стана сну @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Цяпер сістэма выйшла са стана сну @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Сістэма завяршае работу +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Пачаўся працэс выключэння сістэмы. +Спыняюцца ўсе сістэмныя сэрвісы і дэмантуюцца файлавыя сістэмы. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Юніт @UNIT@ запускаецца +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Пачаўся працэс запуску юніта @UNIT@. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Юніт @UNIT@ запусціўся +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Працэс запуску юніта @UNIT@ завершаны. + +Вынік: @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Юніт @UNIT@ спыняецца +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Пачаўся працэс спынення юніта @UNIT@. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Юніт @UNIT@ спынены +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Працэс спынення юніта @UNIT@ завершаны. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Збой юніта @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Збой юніта @UNIT@. + +Вынік: @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: Юніт @UNIT@ перачытвае сваю канфігурацыю +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Юніт @UNIT@ пачаў перачытваць сваю канфігурацыю. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Юніт @UNIT@ перачытаў сваю канфігурацыю +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Юніт @UNIT@ перачытаў сваю канфігурацыю. + +Вынік: @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Працэс @EXECUTABLE@ не можа быць выкананы +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Працэс @EXECUTABLE@ не можа быць выкананы ў выніку збою. + +Ён вярнуў памылку нумар @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Sibject: Адно ці больш паведамленняў не былі накіраваны ў syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Адно ці больш паведамленняў не былі накіраваны ў syslog сэрвіс, які +выконваецца паралельна з journald. Звычайна гэта значыць, што +рэалізацыя syslog не паспявае апрацаваць паведамленні з неабходнай +хуткасцю. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Кропка мантавання не пустая +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Каталог @WHERE@ указаны як кропка мантавання (другое поле ў /etc/fstab +ці Where= поле ў файле юніта systemd) і не пусты. Гэта не перашкаджае +мантаванню, але існуючыя ў ім файлы будуць недаступны. Для доступу да +іх, калі ласка, змантуйце гэтую файлавую сістэму ў іншае месца. + +-- 24d8d4452573402496068381a6312df2 +Subject: Віртуальная машына або кантэйнер запусціўся +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Віртуальная машына @NAME@ з лідарам № @LEADER@ запусцілася і +гатова для выкарыстання. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Віртуальная машына або кантэйнер спынены +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Віртуальная машына @NAME@ з лідарам № @LEADER@ спынена. diff --git a/src/journal/catalog/systemd.be@latin.catalog b/src/journal/catalog/systemd.be@latin.catalog new file mode 100644 index 0000000000..382fdb8b04 --- /dev/null +++ b/src/journal/catalog/systemd.be@latin.catalog @@ -0,0 +1,260 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2015 Viktar Vaŭčkievič +# +# 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 . + +# Message catalog for systemd's own messages +# Belarusian Latin translation + +# The catalog format is documented on +# Farmat kataloha apisany na staroncy +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: Servis žurnaliavannia zapusciŭsia +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Praces sistemnaha žurnaliavannia zapusciŭsia, adkryŭ fajly dlia +zapisu i hatovy apracoŭvać zapyty. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Servis žurnaliavannia spyniŭsia +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Praces sistemnaha žurnaliavannia spyniŭsia i zakryŭ usie fajly. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Paviedamlienni z servisu adkinuty +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Servis adpraviŭ zanadta štat paviedamlienniaŭ za karotki pramiežak času. +Častka paviedamlienniaŭ byla adkinuta. + +Majcie na ŭvazie, što byli adkinuty paviedamliennia toĺki hetaha servisu. +Paviedamlienni inšych servisaŭ zastalisia. + +Miaža, paslia jakoj paviedamlienni buduć adkinuty, naladžvajecca z +dapamohaj RateLimitInterval= i RateLimitBurst= u fajlie +/etc/systemd/journald.conf. Hliadzicie journald.conf(5) dlia detaliej. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Paviedamlienni stračany +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Paviedamlienni jadra byli stračany, tak jak sistema žurnaliavannia nie paspiela +ich apracavać. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Praces @COREDUMP_PID@ (@COREDUMP_COMM@) skinuŭ damp pamiaci +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Praces @COREDUMP_PID@ (@COREDUMP_COMM@) razbiŭsia i skinuŭ damp pamiaci. + +Zvyčajna heta sviedčyć ab pamylcy ŭ prahramnym kodzie. +Rekamiendujecca paviedamić ab hetym raspracoŭnikam. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Novaja siesija № @SESSION_ID@ stvorana dlia karystaĺnika @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Novaja siesija z № @SESSION_ID@ stvorana dlia karystaĺnika @USER_ID@. + +Lidar hetaj siesii pad № @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Siesija № @SESSION_ID@ spyniena +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Siesija № @SESSION_ID@ spyniena. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Dastupna novaje pracoŭnaje miesca № @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Novaje pracoŭnaje miesca № @SEAT_ID@ naladžana i dastupna dlia vykarystannia. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Pracoŭnaje miesca № @SEAT_ID@ vydaliena +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Pracoŭnaje miesca № @SEAT_ID@ vydaliena i boĺš nie dastupna. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Čas zmienieny +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Sistemny hadzinnik zmienieny na @REALTIME@ mikrasiekund ad 1 studzienia 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Časavy pojas zmienieny na @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Sistemny časavy pojas zmienieny na @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Zapusk sistemy zaviaršyŭsia +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Usie sistemnyja servisy, nieabchodnyja dlia zahruzki sistemy, paspiachova +zapuscilisia. Majcie na ŭvazie, što heta nie značyć, što mašyna ničoha nie +robić. Mahčyma, niekatoryja servisy jašče inicyjalizirujucca. + +Na zapusk jadra spatrebilasia @KERNEL_USEC@ mikrasiekund. + +Na zapusk pačatkovaha RAM-dyska spatrebilasia @INITRD_USEC@ mikrasiekund. + +Na zapusk sistemnych servisaŭ spatrebilasia @USERSPACE_USEC@ mikrasiekund. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Sistema pierajšla ŭ stan snu @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Ciapier sistema pierajšla u stan snu @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Sistema vyjšla sa stana snu @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Ciapier sistema vyjšla sa stana snu @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Sistema zaviaršaje rabotu +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Pačaŭsia praces vykliučennia sistemy. +Spyniajucca ŭsie sistemnyja servisy i demantujucca fajlavyja sistemy. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Junit @UNIT@ zapuskajecca +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Pačaŭsia praces zapusku junita @UNIT@. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Junit @UNIT@ zapusciŭsia +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Praces zapusku junita @UNIT@ zavieršany. + +Vynik: @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Junit @UNIT@ spyniajecca +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Pačaŭsia praces spyniennia junita @UNIT@. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Junit @UNIT@ spynieny +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Praces spyniennia junita @UNIT@ zavieršany. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Zboj junita @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Zboj junita @UNIT@. + +Vynik: @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: Junit @UNIT@ pieračytvaje svaju kanfihuracyju +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Junit @UNIT@ pačaŭ pieračytvać svaju kanfihuracyju. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Junit @UNIT@ pieračytaŭ svaju kanfihuracyju +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Junit @UNIT@ pieračytaŭ svaju kanfihuracyju. + +Vynik: @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Praces @EXECUTABLE@ nie moža być vykanany +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Praces @EXECUTABLE@ nie moža być vykanany ŭ vyniku zboju. + +Jon viarnuŭ pamylku numar @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Sibject: Adno ci boĺš paviedamlienniaŭ nie byli nakiravany ŭ syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Adno ci boĺš paviedamlienniaŭ nie byli nakiravany ŭ syslog servis, jaki +vykonvajecca paralieĺna z journald. Zvyčajna heta značyć, što +realizacyja syslog nie paspiavaje apracavać paviedamlienni z nieabchodnaj +chutkasciu. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Kropka mantavannia nie pustaja +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Kataloh @WHERE@ ukazany jak kropka mantavannia (druhoje polie ŭ /etc/fstab +ci Where= polie ŭ fajlie junita systemd) i nie pusty. Heta nie pieraškadžaje +mantavanniu, alie isnujučyja ŭ im fajly buduć niedastupny. Dlia dostupu da +ich, kali laska, zmantujcie hetuju fajlavuju sistemu ŭ inšaje miesca. + +-- 24d8d4452573402496068381a6312df2 +Subject: Virtuaĺnaja mašyna abo kantejnier zapusciŭsia +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Virtuaĺnaja mašyna @NAME@ z lidaram № @LEADER@ zapuscilasia i +hatova dlia vykarystannia. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Virtuaĺnaja mašyna abo kantejnier spynieny +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Virtuaĺnaja mašyna @NAME@ z lidaram № @LEADER@ spyniena. diff --git a/src/journal/catalog/systemd.catalog b/src/journal/catalog/systemd.catalog new file mode 100644 index 0000000000..077f182a5a --- /dev/null +++ b/src/journal/catalog/systemd.catalog @@ -0,0 +1,334 @@ +# This file is part of systemd. +# +# Copyright 2012 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 . + +# Message catalog for systemd's own messages + +# The catalog format is documented on +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: The journal has been started +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The system journal process has started up, opened the journal +files for writing and is now ready to process requests. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: The journal has been stopped +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The system journal process has shut down and closed all currently +active journal files. + +-- ec387f577b844b8fa948f33cad9a75e6 +Subject: Disk space used by the journal +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@JOURNAL_NAME@ (@JOURNAL_PATH@) is currently using @CURRENT_USE_PRETTY@. +Maximum allowed usage is set to @MAX_USE_PRETTY@. +Leaving at least @DISK_KEEP_FREE_PRETTY@ free (of currently available @DISK_AVAILABLE_PRETTY@ of disk space). +Enforced usage limit is thus @LIMIT_PRETTY@, of which @AVAILABLE_PRETTY@ are still available. + +The limits controlling how much disk space is used by the journal may +be configured with SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=, +RuntimeMaxUse=, RuntimeKeepFree=, RuntimeMaxFileSize= settings in +/etc/systemd/journald.conf. See journald.conf(5) for details. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Messages from a service have been suppressed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +A service has logged too many messages within a time period. Messages +from the service have been dropped. + +Note that only messages from the service in question have been +dropped, other services' messages are unaffected. + +The limits controlling when messages are dropped may be configured +with RateLimitInterval= and RateLimitBurst= in +/etc/systemd/journald.conf. See journald.conf(5) for details. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Journal messages have been missed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Kernel messages have been lost as the journal system has been unable +to process them quickly enough. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Process @COREDUMP_PID@ (@COREDUMP_COMM@) dumped core +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Process @COREDUMP_PID@ (@COREDUMP_COMM@) crashed and dumped core. + +This usually indicates a programming error in the crashing program and +should be reported to its vendor as a bug. + +-- fc2e22bc6ee647b6b90729ab34a250b1 de +Subject: Speicherabbild für Prozess @COREDUMP_PID@ (@COREDUMP_COMM) generiert +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Prozess @COREDUMP_PID@ (@COREDUMP_COMM@) ist abgebrochen worden und +ein Speicherabbild wurde generiert. + +Üblicherweise ist dies ein Hinweis auf einen Programmfehler und sollte +als Fehler dem jeweiligen Hersteller gemeldet werden. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: A new session @SESSION_ID@ has been created for user @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A new session with the ID @SESSION_ID@ has been created for the user @USER_ID@. + +The leading process of the session is @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Session @SESSION_ID@ has been terminated +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A session with the ID @SESSION_ID@ has been terminated. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: A new seat @SEAT_ID@ is now available +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A new seat @SEAT_ID@ has been configured and is now available. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Seat @SEAT_ID@ has now been removed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A seat @SEAT_ID@ has been removed and is no longer available. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Time change +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The system clock has been changed to @REALTIME@ microseconds after January 1st, 1970. + +-- c7a787079b354eaaa9e77b371893cd27 de +Subject: Zeitänderung +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Die System-Zeit wurde geändert auf @REALTIME@ Mikrosekunden nach dem 1. Januar 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Time zone change to @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The system timezone has been changed to @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: System start-up is now complete +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +All system services necessary queued for starting at boot have been +successfully started. Note that this does not mean that the machine is +now idle as services might still be busy with completing start-up. + +Kernel start-up required @KERNEL_USEC@ microseconds. + +Initial RAM disk start-up required @INITRD_USEC@ microseconds. + +Userspace start-up required @USERSPACE_USEC@ microseconds. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: System sleep state @SLEEP@ entered +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The system has now entered the @SLEEP@ sleep state. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: System sleep state @SLEEP@ left +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The system has now left the @SLEEP@ sleep state. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: System shutdown initiated +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemd shutdown has been initiated. The shutdown has now begun and +all system services are terminated and all file systems unmounted. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Unit @UNIT@ has begun start-up +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has begun starting up. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Unit @UNIT@ has finished start-up +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has finished starting up. + +The start-up result is @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Unit @UNIT@ has begun shutting down +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has begun shutting down. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Unit @UNIT@ has finished shutting down +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has finished shutting down. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Unit @UNIT@ has failed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has failed. + +The result is @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: Unit @UNIT@ has begun reloading its configuration +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has begun reloading its configuration + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Unit @UNIT@ has finished reloading its configuration +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Unit @UNIT@ has finished reloading its configuration + +The result is @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Process @EXECUTABLE@ could not be executed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The process @EXECUTABLE@ could not be executed and failed. + +The error number returned by this process is @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: One or more messages could not be forwarded to syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +One or more messages could not be forwarded to the syslog service +running side-by-side with journald. This usually indicates that the +syslog implementation has not been able to keep up with the speed of +messages queued. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Mount point is not empty +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The directory @WHERE@ is specified as the mount point (second field in +/etc/fstab or Where= field in systemd unit file) and is not empty. +This does not interfere with mounting, but the pre-exisiting files in +this directory become inaccessible. To see those over-mounted files, +please manually mount the underlying file system to a secondary +location. + +-- 24d8d4452573402496068381a6312df2 +Subject: A virtual machine or container has been started +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The virtual machine @NAME@ with its leader PID @LEADER@ has been +started is now ready to use. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: A virtual machine or container has been terminated +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +The virtual machine @NAME@ with its leader PID @LEADER@ has been +shut down. + +-- 36db2dfa5a9045e1bd4af5f93e1cf057 +Subject: DNSSEC mode has been turned off, as server doesn't support it +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) resolved.conf(5) + +The resolver service (systemd-resolved.service) has detected that the +configured DNS server does not support DNSSEC, and DNSSEC validation has been +turned off as result. + +This event will take place if DNSSEC=allow-downgrade is configured in +resolved.conf and the configured DNS server is incompatible with DNSSEC. Note +that using this mode permits DNSSEC downgrade attacks, as an attacker might be +able turn off DNSSEC validation on the system by inserting DNS replies in the +communication channel that result in a downgrade like this. + +This event might be indication that the DNS server is indeed incompatible with +DNSSEC or that an attacker has successfully managed to stage such a downgrade +attack. + +-- 1675d7f172174098b1108bf8c7dc8f5d +Subject: DNSSEC validation failed +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +A DNS query or resource record set failed DNSSEC validation. This is usually +indication that the communication channel used was tampered with. + +-- 4d4408cfd0d144859184d1e65d7c8a65 +Subject: A DNSSEC trust anchor has been revoked +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +A DNSSEC trust anchor has been revoked. A new trust anchor has to be +configured, or the operating system needs to be updated, to provide an updated +DNSSEC trust anchor. diff --git a/src/journal/catalog/systemd.da.catalog b/src/journal/catalog/systemd.da.catalog new file mode 100644 index 0000000000..bd4d742d8a --- /dev/null +++ b/src/journal/catalog/systemd.da.catalog @@ -0,0 +1,261 @@ +# This file is part of systemd. +# +# Copyright 2012 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 . + +# Message catalog for systemd's own messages +# Danish translation + +# The catalog format is documented on +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: Journalen er blevet startet +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +System-journal processen har startet op, åbnet journal filerne for +tilskrivning og er nu klar til at modtage anmodninger. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Journalen er blevet stoppet +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +System-journal processen er stoppet og har lukket alle aktive journal +filer. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Beskeder fra en service er blevet undertrykt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +En service har logget for mange beskeder inden for en given tidsperiode. +Beskeder fra omtalte service er blevet smidt væk. + +Kun beskeder fra omtalte service er smidt væk. Beskeder fra andre +services er ikke påvirket. + +Grænsen for hvornår beskeder bliver smidt væk kan konfigureres +med RateLimitInterval= og RateLimitBurst= i +/etc/systemd/journald.conf. Se journald.conf(5) for detaljer herom. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Journal beskeder er gået tabt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Kernel beskeder er gået tabt da journal systemet ikke har været i stand +til at håndtere dem hurtigt nok. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Fejl-fil genereret for process @COREDUMP_PID@ (@COREDUMP_COMM@) +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Process @COREDUMP_PID@ (@COREDUMP_COMM@) har lukket ned og genereret en +fejl-fil. + +Dette indikerer som regel en programmeringsfejl i det nedlukkede program +og burde blive reporteret som en bug til folkene bag + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: En ny session @SESSION_ID@ er blevet lavet for bruger @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +En ny session med ID @SESSION_ID@ er blevet lavet for brugeren @USER_ID@. + +Den ledende process for sessionen er @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Session @SESSION_ID@ er blevet lukket ned +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +En session med ID @SESSION_ID@ er blevet lukket ned. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: En ny arbejdsstation $SEAT_ID@ er nu tilgængelig +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +En ny arbejdsstation @SEAT_ID@ er blevet konfigureret og er nu tilgængelig. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Arbejdsstation @SEAT_ID@ er nu blevet fjernet +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +En arbejdsstation @SEAT_ID@ er blevet fjernet og er ikke længere tilgængelig. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Tidsændring +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemtiden er blevet ændret til @REALTIME@ mikrosekunder efter d. 1. Januar 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Tidszoneændring til @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Tidszonen for systemet er blevet ændret til @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Opstart af systemet er nu fuldført +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Alle system services i kø til at køre ved opstart, er blevet startet +med success. Bemærk at dette ikke betyder at maskinen er i dvale, da +services stadig kan være i gang med at færdiggøre deres opstart. + +Opstart af kernel tog @KERNEL_USEC@ mikrosekunder. + +Opstart af initrd tog @INITRD_USEC@ mikrosekunder. + +Opstart af userspace tog @USERSPACE_USEC@ mikrosekunder. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: System slumretilstand @SLEEP@ trådt i kraft +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +System er nu gået i @SLEEP@ slumretilstand. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: System slumretilstand @SLEEP@ forladt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemet har nu forladt @SLEEP@ slumretilstand. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Systemnedlukning påbegyndt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemnedlukning er blevet påbegyndt. Nedlukningen er nu begyndt og +alle system services er blevet afbrudt og alle filsystemer afmonteret. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Enhed @UNIT@ har påbegyndt opstart +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ er begyndt at starte op. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Enhed @UNIT har færdiggjort opstart +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ er færdig med at starte op. + +Resultat for opstart er @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Enhed @UNIT@ har påbegyndt nedlukning +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ har påbegyndt nedlukning. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Enhed @UNIT@ har færdiggjort nedlukning +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ har færdiggjort nedlukning. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Enhed @UNIT@ har fejlet +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ har fejlet. + +Resultatet er @RESULT@ + +-- d34d037fff1847e6ae669a370e694725 +Subject: Enhed @UNIT@ har påbegyndt genindlæsning af sin konfiguration +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ er begyndt at genindlæse sin konfiguration + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Enhed @UNIT@ har færdiggjort genindlæsning af sin konfiguration +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Enhed @UNIT@ er færdig med at genindlæse sin konfiguration + +Resultatet er: @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Process @EXECUTABLE@ kunne ikke eksekveres +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Processen @EXECUTABLE@ kunne ikke eksekveres og fejlede. + +Processens returnerede fejlkode er @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Èn eller flere beskeder kunne ikke videresendes til syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Èn eller flere beskeder kunne ikke videresendes til syslog servicen +der kører side-om-side med journald. Dette indikerer typisk at syslog +implementationen ikke har kunnet følge med mængden af ventende beskeder. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Monteringspunkt er ikke tomt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Folderen @WHERE@ er specificeret som monteringspunkt (andet felt i +/etc/fstab eller Where= feltet i systemd enhedsfil) men er ikke tom. +Dette forstyrrer ikke monteringen, men de pre-eksisterende filer i folderen +bliver utilgængelige. For at se de over-monterede filer; montér det +underlæggende filsystem til en anden lokation. + +-- 24d8d4452573402496068381a6312df2 +Subject: En virtuel maskine eller container er blevet startet +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Den virtuelle maskine @NAME@ med dens leder PID @LEADER@ er blevet +startet og er klar til brug. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: En virtuel maskine eller container er blevet afbrudt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Den virtuelle maskine @NAME@ med dens leder PID @LEADER@ er blevet +nedlukket. diff --git a/src/journal/catalog/systemd.fr.catalog b/src/journal/catalog/systemd.fr.catalog new file mode 100644 index 0000000000..03a457786f --- /dev/null +++ b/src/journal/catalog/systemd.fr.catalog @@ -0,0 +1,260 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2013-2015 Sylvain Plantefève +# +# 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 . + +# Message catalog for systemd's own messages +# French translation + +# Le format du catalogue de messages est décrit (en anglais) içi : +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +-- f77379a8490b408bbe5f6940505a777b +Subject: Le journal a été démarré +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le processus du journal système a démarré, ouvert ses fichiers en écriture +et est prêt à traiter les requêtes. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Le journal a été arrêté +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le processus du journal système a été arrêté et tous ses fichiers actifs +ont été fermés. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Des messages d'un service ont été supprimés +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Un service a essayé d'enregistrer un trop grand nombre de messages sur un +intervalle de temps donné. Des messages de ce service ont été évincés. + +Notez que seuls des messages de ce service ont été évincés, les messages des +autres services ne sont pas affectés. + +Les limites définissant ce comportement peuvent être configurées avec les +paramètres RateLimitInterval= et RateLimitBurst= dans le fichier +/etc/systemd/journald.conf. Voir journald.conf(5) pour plus de détails. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Des messages du journal ont été manqués +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Des messages du noyau ont été manqués car le journal système n'a pas été +capable de les traiter suffisamment vite. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Le processus @COREDUMP_PID@ (@COREDUMP_COMM@) a généré un fichier « core » +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Le processus @COREDUMP_PID@ (@COREDUMP_COMM@) a planté et généré un fichier « core ». + +Cela indique généralement une erreur de programmation dans le programme +incriminé, et cela devrait être notifié à son concepteur comme un défaut (bug). + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Une nouvelle session @SESSION_ID@ a été créée pour l'utilisateur @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Une nouvelle session a été créée pour l'utilisateur @USER_ID@ avec +l'identifiant (ID) @SESSION_ID@. + +Le processus maître de la session est @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: La session @SESSION_ID@ s'est terminée +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +La session d'identifiant (ID) @SESSION_ID@ s'est terminée. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Un nouveau poste (seat) @SEAT_ID@ est disponible +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Un nouveau poste (seat) @SEAT_ID@ a été configuré et est maintenant disponible. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Le poste (seat) @SEAT_ID@ a été retiré +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Le poste (seat) @SEAT_ID@ a été retiré et n'est plus disponible. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Changement d'heure +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'horloge système a été modifiée et positionnée à @REALTIME@ microsecondes +après le 1er janvier 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Fuseau horaire modifié en @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le fuseau horaire du système a été modifié et positionné à @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Le démarrage du système est terminé +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Tous les services nécessaires au démarrage du système ont été lancés avec +succès. Notez que cela ne signifie pas que le système est maintenant au +repos, car des services peuvent encore être en train de terminer leur +démarrage. + +Le chargement du noyau a nécessité @KERNEL_USEC@ microsecondes. + +Le chargement du « RAM disk » initial a nécessité @INITRD_USEC@ microsecondes. + +Le chargement de l'espace utilisateur a nécessité @USERSPACE_USEC@ microsecondes. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Le système entre dans l'état de repos (sleep state) @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le système est maintenant à l'état de repos (sleep state) @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Le système sorti de l'état de repos (sleep state) @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le système est maintenant sorti de l'état de repos (sleep state) @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Arrêt du système amorcé +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'arrêt du système a été amorcé. L'arrêt a maintenant commencé, tous les +services du système sont terminés et tous les systèmes de fichiers sont +démontés. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: L'unité (unit) @UNIT@ a commencé à démarrer +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a commencé à démarrer. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: L'unité (unit) @UNIT@ a terminé son démarrage +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a terminé son démarrage, avec le résultat @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: L'unité (unit) @UNIT@ a commencé à s'arrêter +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a commencé à s'arrêter. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: L'unité (unit) @UNIT@ a terminé son arrêt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a terminé son arrêt. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: L'unité (unit) @UNIT@ a échoué +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a échoué, avec le résultat @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: L'unité (unit) @UNIT@ a commencé à recharger sa configuration +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a commencé à recharger sa configuration. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: L'unité (unit) @UNIT@ a terminé de recharger configuration +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unité (unit) @UNIT@ a terminé de recharger configuration, +avec le résultat @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Le processus @EXECUTABLE@ n'a pas pu être exécuté +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le processus @EXECUTABLE@ n'a pas pu être exécuté, et a donc échoué. + +Le code d'erreur renvoyé est @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Un ou plusieurs messages n'ont pas pu être transmis à syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Un ou plusieurs messages n'ont pas pu être transmis au service syslog +s'exécutant conjointement avec journald. Cela indique généralement que +l'implémentation de syslog utilisée n'a pas été capable de suivre la cadence +du flux de messages. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Le point de montage n'est pas vide +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Le répertoire @WHERE@ est spécifié comme point de montage (second champ du +fichier /etc/fstab, ou champ Where= dans une unité (unit) systemd) et n'est pas +vide. +Cela ne perturbe pas le montage du système de fichiers, mais les fichiers +préalablement présents dans ce répertoire sont devenus inaccessibles. +Pour atteindre ces fichiers, veuillez monter manuellement le système de +fichiers sous-jacent à un autre emplacement. + +-- 24d8d4452573402496068381a6312df2 +Subject: Une machine virtuelle ou un conteneur (container) a été démarré +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +La machine virtuelle @NAME@ a été démarrée avec le PID maître @LEADER@, +et est maintenant prête à l'emploi. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Une machine virtuelle ou un conteneur (container) a été arrêté +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +La machine virtuelle @NAME@ avec le PID maître @LEADER@ a été arrêtée. diff --git a/src/journal/catalog/systemd.hu.catalog b/src/journal/catalog/systemd.hu.catalog new file mode 100644 index 0000000000..30d76916cc --- /dev/null +++ b/src/journal/catalog/systemd.hu.catalog @@ -0,0 +1,262 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2016 Gabor Kelemen +# +# 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 . + +# Message catalog for systemd's own messages + +# The catalog format is documented on +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: A napló elindult +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszernapló folyamat elindult, megnyitotta írásra a naplófájlokat, +és most készen áll kérések feldolgozására. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: A napló leállt +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszernapló folyamat leállt, és bezárt minden jelenleg aktív naplófájlt. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Egy szolgáltatás üzenetei elnémítva +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Egy szolgáltatás túl sok üzenetet naplózott adott idő alatt. A +szolgáltatástól származó üzenetek eldobásra kerültek. + +Ne feledje, hogy csak a kérdéses szolgáltatás üzenetei kerültek eldobásra, + más szolgáltatások üzeneteit ez nem befolyásolja. + +Az üzenetek eldobását vezérlő korlátok az /etc/systemd/journald.conf +RateLimitInterval= és RateLimitBurst= beállításaival adhatók meg. +Részletekért lásd a journald.conf(5) man oldalt. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Naplóüzenetek vesztek el +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Kernelüzenetek vesztek el, mert a naplózó rendszer nem tudta elég gyorsan +feldolgozni azokat. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Egy folyamat összeomlott: @COREDUMP_PID@ (@COREDUMP_COMM@) +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Ez a folyamat: @COREDUMP_PID@ (@COREDUMP_COMM@) összeomlott, és core fájlt + írt ki. + +Ez általában programozási hibát jelez az összeomló programban, és +a szállítója felé kell bejelenteni. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Új munkamenet (@SESSION_ID@) létrehozva, felhasználója: @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Létrejött egy új munkamenet @SESSION_ID@ azonosítóval ezen felhasználóhoz: +@USER_ID@. + +A munkamenet vezető folyamata: @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Munkamenet (@SESSION_ID@) befejezve +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A következő azonosítójú munkamenet befejeződött: @SESSION_ID@. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Elérhető egy új munkaállomás: @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Beállításra kerül és használható egy új munkaállomás: @SEAT_ID@. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: A munkaállomás eltávolítva: @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +A munkaállomás el lett távolítva, és már nem érhető el: @SEAT_ID@ + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Időmódosítás +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszeróra beállítva @REALTIME@ ezredmásodpercre 1970. január 1. után. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Időzóna-módosítás erre: @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszer időzónája módosítva lett erre: @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: A rendszer indítása kész +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszerindításkor szükséges indításhoz sorba állított összes +rendszerszolgáltatás elindult. Ne feledje, hogy ez nem jelenti, hogy a +gép üresjáratban van, mivel egyes szolgáltatások még az indítás +befejezésével lehetnek elfoglalva. + +A kernel indítása @KERNEL_USEC@ ezredmásodpercet igényelt. + +A kiinduló RAM lemez indítása @INITRD_USEC@ ezredmásodpercet igényelt. + +A felhasználói programok indítása @USERSPACE_USEC@ ezredmásodpercet igényelt. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: A rendszer „@SLEEP@” alvási állapotba lépett +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszer belépett ebbe az alvási állapotba: @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: A rendszer „@SLEEP@” alvási állapotból kilépett +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A rendszer kilépett ebből az alvási állapotból: @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Rendszer leállítása kezdeményezve +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A systemd leállítása kezdeményezve. A leállítás megkezdődött, minden +rendszerszolgáltatás befejeződik, minden fájlrendszer leválasztásra kerül. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: A(z) @UNIT@ egység indítása megkezdődött +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység megkezdte az indulást. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: A(z) @UNIT@ egység befejezte az indulást +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység befejezte az indulást + +Az indítás eredménye: @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: A(z) @UNIT@ egység megkezdte a leállást +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység megkezdte a leállást. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: A(z) @UNIT@ egység befejezte a leállást +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység befejezte a leállást. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: A(z) @UNIT@ egység hibát jelzett +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység hibát jelzett. + +Az eredmény: @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: A(z) @UNIT@ egység megkezdte a beállításainak újratöltését +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység megkezdte a beállításainak újratöltését. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: A(z) @UNIT@ egység befejezte a beállításainak újratöltését +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @UNIT@ egység befejezte a beállításainak újratöltését. + +Az eredmény: @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: A folyamat végrehajtása sikertelen: @EXECUTABLE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A folyamat végrehajtása sikertelen volt, és hibát jelzett: @EXECUTABLE@. + +A folyamat által visszaadott hibaszám: @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Legalább egy üzenet nem továbbítható a rendszernaplónak +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Legalább egy üzenet nem volt továbbítható a journald-vel párhuzamosan futó +syslog szolgáltatásnak. Ez általában azt jelenti, hogy a syslog +megvalósítás nem volt képes lépést tartani a sorba állított +üzenetek sebességével. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: A csatolási pont nem üres +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A csatolási pontként megadott @WHERE@ könyvtár (második mező az /etc/fstab +fájlban, vagy a Where= sor a systemd egységfájlban) nem üres. Ez nem +akadályozza meg a csatolást, de a könyvtárban már meglévő fájlok +elérhetetlenné válnak. A fájlok láthatóvá tételéhez csatolja +az azokat tartalmazó fájlrendszert egy másodlagos helyre. + +-- 24d8d4452573402496068381a6312df2 +Subject: Egy virtuális gép vagy konténer elindult +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @NAME@ nevű virtuális gép (vezető PID: @LEADER@) elindult, és +használatra kész. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Egy virtuális gép vagy konténer befejeződött +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A(z) @NAME@ nevű virtuális gép (vezető PID: @LEADER@) leállt. diff --git a/src/journal/catalog/systemd.it.catalog b/src/journal/catalog/systemd.it.catalog new file mode 100644 index 0000000000..861b92b74a --- /dev/null +++ b/src/journal/catalog/systemd.it.catalog @@ -0,0 +1,254 @@ +# This file is part of systemd. +# +# Copyright 2013 Daniele Medri +# +# 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 . + +# Message catalog for systemd's own messages + +-- f77379a8490b408bbe5f6940505a777b +Subject: Il registro è stato avviato +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Il processo relativo al registro di sistema è stato avviato, ha aperto i +file in scrittura ed è ora pronto a gestire richieste. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Il registro è stato terminato +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Il processo relativo al registro di sistema è stato terminato e ha chiuso +tutti i file attivi. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: I messaggi di un servizio sono stati soppressi +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Un servizio ha registrato troppi messaggi in un dato periodo di tempo. +I messaggi del servizio sono stati eliminati. + +Solo i messaggi del servizio indicato sono stati +eliminati, i messaggi degli altri servizi rimangono invariati. + +I limiti oltre i quali i messaggi si eliminano si configurano +con RateLimitInterval= e RateLimitBurst= in +/etc/systemd/journald.conf. Vedi journald.conf(5) per maggiori informazioni. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: I messaggi di un servizio sono stati perduti +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +I messaggi del kernel sono stati perduti perché, il registro di sistema +non è stato in grado di gestirli abbastanza velocemente. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Il processo @COREDUMP_PID@ (@COREDUMP_COMM@) ha generato un dump. +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Il processo @COREDUMP_PID@ (@COREDUMP_COMM@) si è bloccato generando un dump. + +Questo di solito capita per un errore di programmazione nell'applicazione e +dovrebbe essere segnalato al vendor come un bug. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: La nuova sessione @SESSION_ID@ è stata creata per l'utente @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Una nuova sessione con ID @SESSION_ID@ è stata creata per l'utente @USER_ID@. + +Il processo primario della sessione è @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: La sessione @SESSION_ID@ è terminata +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +La sessione con ID @SESSION_ID@ è terminata. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: La nuova postazione @SEAT_ID@ è ora disponibile +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +La nuova postazione @SEAT_ID@ è stata configurata ed è ora disponibile. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: La postazione @SEAT_ID@ è stata rimossa +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +La postazione @SEAT_ID@ è stata rimossa e non è più disponibile. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Cambio d'orario +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'orologio di sistema è cambiato in @REALTIME@ microsecondi dal 1 gennaio, 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Il fuso orario è cambiato in @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Il fuso orario di sistema è cambiato in @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Avvio del sistema completato. +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Tutti i servizi di sistema richiesti per la fase di avvio sono stati eseguiti +con successo. Nota che la macchina potrebbe non essere ancora pronta in quanto +i servizi attivati sono in fase di completamento. + +L'avvio del kernel ha richiesto @KERNEL_USEC@ microsecondi. + +L'avvio del disco RAM ha richiesto @INITRD_USEC@ microsecondi. + +L'avvio dello userspace ha richiesto @USERSPACE_USEC@ microsecondi. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Il sistema è entrato in fase di pausa @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Il sistema è entrato nello stato di pausa @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Il sistema è uscito dalla fase di pausa @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Il sistema è uscito dallo stato di pausa @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Il sistema è in fase di spegnimento +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemd è in fase di spegnimento. Tutti i servizi di sistema +saranno terminati e tutti i file systems smontati. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: L'unità @UNIT@ inizia la fase di avvio +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ ha iniziato la fase di avvio. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: L'unità @UNIT@ termina la fase di avvio +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ ha terminato la fase di avvio. + +La fase di avvio è @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: L'unità @UNIT@ inizia la fase di spegnimento +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ ha iniziato la fase di spegnimento. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: L'unità @UNIT@ termina la fase di spegnimento +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ ha terminato la fase di spegnimento. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: L'unità @UNIT@ è fallita +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ è fallita. + +Il risultato è @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: L'unità @UNIT@ inizia a caricare la propria configurazione +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ è iniziata ricaricando la propria configurazione + +-- 7b05ebc668384222baa8881179cfda54 +Subject: L'unità @UNIT@ termina il caricamento della propria configurazione +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +L'unità @UNIT@ è terminata ricaricando la propria configurazione + +Il risultato è @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Il processo @EXECUTABLE@ non può essere eseguito +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Il processo @EXECUTABLE@ non può essere eseguito e termina. + +Il numero di errore restituito durante l'esecuzione del processo è @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Uno o più messaggi non possono essere inoltrati a syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Uno o più messaggi non possono essere inviati al servizio syslog +eseguito in parallelo a journald. Questo di solito capita perché, +l'implementazione di syslog non sta al passo con la +velocità dei messaggi accodati. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Il punto di montaggio non è vuoto +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +La directory @WHERE@ è specificata come punto di montaggio (secondo campo +in /etc/fstab o nel campo Where= del file unità di systemd) e non è vuoto. +Questo non interferisce con il montaggio, ma i file pre-esistenti in questa +directory diventano inaccessibili. Per visualizzare i file, si suggerisce +di montare manualmente il file system indicato in una posizione secondaria. + +-- 24d8d4452573402496068381a6312df2 +Subject: Avviata macchina virtuale o container +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +La macchina virtuale @NAME@ con PID primario @LEADER@ è stata +avviata ed è pronta all'uso. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Terminata macchina virtuale o container +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +La macchina virtuale @NAME@ con PID primario @LEADER@ è stata spenta. diff --git a/src/journal/catalog/systemd.ko.catalog b/src/journal/catalog/systemd.ko.catalog new file mode 100644 index 0000000000..3c3535a94c --- /dev/null +++ b/src/journal/catalog/systemd.ko.catalog @@ -0,0 +1,264 @@ +# This file is part of systemd. +# +# Copyright 2012 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 . + +# Message catalog for systemd's own messages +# Korean translation + +# The catalog format is documented on +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ +# +# Translator : +# Seong-ho Cho , 2015. + +-- f77379a8490b408bbe5f6940505a777b +Subject: 저널 시작 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +시스템 저널 프로세스를 시작했고 기록목적으로 저널 파일을 열었으며, +프로세스 요청을 기다리고 있습니다. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: 저널 멈춤 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +시스템 저널 프로세스를 껐고 현재 활성화 중인 저널 파일을 모두 +닫았습니다. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: 서비스의 메시지를 거절함 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +일정 시간동안 서비스에서 너무 많은 메시지를 기록했습니다. +서비스에서 오는 메시지를 거절했습니다. + +의문점이 있는 서비스로부터 오는 메시지만 거절했음을 참고하십시오 +다른 서비스의 메시지에는 영향을 주지 않습니다. + +메시지 거절 제어 제한 값은 /etc/systemd/journald.conf 의 +RateLimitInterval= 변수와 RateLimitBurst= 변수로 설정합니다. +자세한 내용은 ournald.conf(5)를 살펴보십시오. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: 저널 메시지 놓침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +저널 시스템에서 커널 메시지를 충분히 빠르게 처리할 수 없어 커널 + 메시지를 잃었습니다. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: 프로세스 @COREDUMP_PID@번 코어 덤프(@COREDUMP_COMM@) 생성함 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +프로세스 @COREDUMP_PID@번 (@COREDUMP_COMM@)이 비정상적으로 끝나 +코어 덤프를 생성했습니다. + +보통 비정상 종료 관리 프로그램에서 프로그래밍 오류를 나타내며, +제작자에게 버그로 보고해야합니다. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: @USER_ID@ 사용자의 새 @SESSION_ID@ 세션 만듦 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +@USER_ID@ 사용자의 새 @SESSION_ID@ 세션을 만들었습니다. + +이 세션의 관리 프로세스는 @LEADER@ 입니다. + +-- 3354939424b4456d9802ca8333ed424a +Subject: @SESSION_ID@ 세션 마침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +@SESSION_ID@ 세션을 끝냈습니다. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: 새 @SEAT_ID@ 시트 사용할 수 있음 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +새 @SEAT_ID@ 시트를 설정했고 사용할 수 있습니다. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: @SEAT_ID@ 시트 제거함 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +@SEAT_ID@ 시트를 제거했으며 더이상 사용할 수 없습니다. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: 시간 바꿈 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +시스템 시계를 1970년 1월 1일 이후로 @REALTIME@ 마이크로초 지난 값으로 +설정했습니다. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: @TIMEZONE@ 시간대로 시간대 바꿈 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +시스템 시간대를 @TIMEZONE@ 시간대로 바꾸었습니다. + +-- b07a249cd024414a82dd00cd181378ff +Subject: 시스템 시동 마침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +부팅 과정에 시작하려고 준비한 모든 시스템 서비스를 성공적으로 + 시작했습니다. 머신이 서비스처럼 대기중이라는 의미는 아니며 +지동을 완전히 마칠 때까지 사용중일 수도 있는 점 참고하십시오. + +커널 시동에 @KERNEL_USEC@ 마이크로초가 걸립니다. + +초기 램 디스크 시동에 @INITRD_USEC@ 마이크로초가 걸립니다. + +사용자 영역 시동에 @USERSPACE_USEC@ 마이크로초가 걸립니다. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: @SLEEP@ 대기 상태 진입 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@SLEEP@ 대기 상태로 진입했습니다. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: @SLEEP@ 대기 상태 마침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@SLEEP@ 대기 상태를 마쳤습니다. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: 컴퓨터 끄기 시작 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +컴퓨터 끄기 동작을 시작했습니다. 모든 시스템 동작을 멈추고 +모든 파일 시스템의 마운트를 해제합니다. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: @UNIT@ 유닛 시작 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛을 시작했습니다. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: @UNIT@ 유닛 시동 마침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛 시동을 마쳤습니다. + +시동 결과는 @RESULT@ 입니다. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: @UNIT@ 유닛 끝내기 동작 시작 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛 끝내기 동작을 시작했습니다. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: @UNIT@ 유닛 끝내기 동작 마침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛 끝내기 동작을 마쳤습니다. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: @UNIT@ 유닛 동작 실패 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛 동작에 실패했습니다. + +결과는 @RESULT@ 입니다. + +-- d34d037fff1847e6ae669a370e694725 +Subject: @UNIT@ 유닛 설정 다시 읽기 시작 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛의 설정 다시 읽기를 시작했습니다 + +-- 7b05ebc668384222baa8881179cfda54 +Subject: @UNIT@ 유닛 설정 다시 읽기 완료 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 유닛의 설정 다시 읽기 동작을 끝냈습니다. + +결과는 @RESULT@ 입니다. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: @EXECUTABLE@ 프로세스 시작할 수 없음 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@EXECUTABLE@ 프로세스를 시작할 수 없어 실행에 실패했습니다. + +이 프로세스에서 반환한 오류 번호는 @ERRNO@번 입니다. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: 하나 이상의 메시지를 syslog에 전달할 수 없음 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +journald 서비스와 동시에 실행중인 syslog 서비스에 하나 이상의 메시지를 +전달할 수 없습니다. 보통 순차적으로 오는 메시지의 속도를 syslog 구현체가 +따라가지 못함을 의미합니다. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: 마운트 지점 비어있지 않음 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@WHERE@ 디렉터리를 마운트 지점으로 지정했으며 (/etc/fstab 파일의 + 두번째 필드 또는 systemd 유닛 파일의 Where= 필드) 비어있지 않습니다. +마운트 과정에 방해가 되진 않지만 이전에 이 디렉터리에 존재하는 파일에 + 접근할 수 없게 됩니다. 중복으로 마운트한 파일을 보려면, 근본 파일 +시스템의 다음 위치에 직접 마운트하십시오. + +-- 24d8d4452573402496068381a6312df2 +Subject: 가상 머신 또는 컨테이너 시작 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@LEADER@ 프로세스 ID로 동작하는 @NAME@ 가상 머신을 시작했으며, +이제부터 사용할 수 있습니다. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: 가상 머신 또는 컨테이너 마침 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@LEADER@ 프로세스 ID로 동작하는 @NAME@ 가상 머신을 껐습니다. diff --git a/src/journal/catalog/systemd.pl.catalog b/src/journal/catalog/systemd.pl.catalog new file mode 100644 index 0000000000..6b8a31d8c4 --- /dev/null +++ b/src/journal/catalog/systemd.pl.catalog @@ -0,0 +1,261 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2014, 2015 Piotr Drąg +# +# 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 . + +# Message catalog for systemd's own messages +# Polish translation + +# The catalog format is documented on +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: Uruchomiono dziennik +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemowy proces dziennika został uruchomiony, otworzył pliki dziennika do +zapisu i jest gotowy do przetwarzania żądań. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Zatrzymano dziennik +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemowy proces dziennika został wyłączony i zamknął wszystkie obecnie +aktywne pliki dziennika. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Ograniczono komunikaty z usługi +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Usługa zapisała za dużo komunikatów w określonym czasie. Komunikaty z usługi +zostały pominięte. + +Proszę zauważyć, że tylko komunikaty z danej usługi zostały pominięte. Nie ma +to wpływu na komunikaty innych usług. + +Ograniczenia kontrolujące pomijanie komunikatów mogą być konfigurowane +za pomocą opcji RateLimitInterval= i RateLimitBurst= w pliku +/etc/systemd/journald.conf. Strona journald.conf(5) zawiera więcej informacji. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Utracono komunikaty dziennika +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Komunikaty jądra zostały utracone, ponieważ system dziennika nie mógł +przetworzyć ich odpowiednio szybko. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Proces @COREDUMP_PID@ (@COREDUMP_COMM@) zrzucił plik core +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Proces @COREDUMP_PID@ (@COREDUMP_COMM@) uległ awarii i zrzucił plik core. + +Zwykle wskazuje to na błąd programistyczny w danym programie i powinno zostać zgłoszone jego producentowi jako błąd. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Utworzono nową sesję @SESSION_ID@ dla użytkownika @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Nowa sesja o identyfikatorze @SESSION_ID@ została utworzona dla użytkownika +@USER_ID@. + +Proces prowadzący sesji: @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Zakończono sesję @SESSION_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Sesja o identyfikatorze @SESSION_ID@ została zakończona. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Dostępne jest nowe stanowisko @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Nowe stanowisko @SEAT_ID@ zostało skonfigurowane i jest teraz dostępne. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Usunięto stanowisko @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Stanowisko @SEAT_ID@ zostało usunięte i nie jest już dostępne. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Zmiana czasu +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Zegar systemowy został zmieniony na @REALTIME@ μs po 1 stycznia 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Zmiana strefy czasowej na @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemowa strefa czasowa została zmieniona na @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Ukończono uruchamianie systemu +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Wszystkie usługi systemowe obowiązkowo zakolejkowane do włączenia podczas +uruchamiania systemu zostały pomyślnie uruchomione. Proszę zauważyć, że nie +oznacza to, że komputer jest bezczynny, jako że usługi mogą wciąż kończyć +proces uruchamiania. + +Uruchamianie jądra zajęło @KERNEL_USEC@ μs. + +Uruchamianie początkowego dysku RAM zajęło @INITRD_USEC@ μs. + +Uruchamianie przestrzeni użytkownika zajęło @USERSPACE_USEC@ μs. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Przejście do stanu uśpienia @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +System przeszedł do stanu uśpienia @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Wyjście ze stanu uśpienia @SLEEP@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +System wyszedł ze stanu uśpienia @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Zainicjowano wyłączenie systemu +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Zainicjowano wyłączenie systemd. Wyłączenie zostało rozpoczęte i wszystkie +usługi systemowe zostały zakończone, a wszystkie systemy plików odmontowane. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Rozpoczęto uruchamianie jednostki @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ rozpoczęła uruchamianie. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Ukończono uruchamianie jednostki @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ ukończyła uruchamianie. + +Wynik uruchamiania: @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Rozpoczęto wyłączanie jednostki @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ rozpoczęła wyłączanie. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Ukończono wyłączanie jednostki @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ ukończyła wyłączanie. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Jednostka @UNIT@ się nie powiodła +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ się nie powiodła. + +Wynik: @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: Rozpoczęto ponowne wczytywanie konfiguracji jednostki @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ rozpoczęła ponowne wczytywanie swojej konfiguracji. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Ukończono ponowne wczytywanie konfiguracji jednostki @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jednostka @UNIT@ ukończyła ponowne wczytywanie swojej konfiguracji. + +Wynik: @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Nie można wykonać procesu @EXECUTABLE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Proces @EXECUTABLE@ nie mógł zostać wykonany i się nie powiódł. + +Numer błędu zwrócony przez ten proces: @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Nie można przekazać jednego lub więcej komunikatów do syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Jeden lub więcej komunikatów nie może zostać przekazanych do usługi syslog +uruchomionej obok journald. Zwykle oznacza to, że implementacja syslog nie +jest w stanie nadążyć za prędkością kolejki komunikatów. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Punkt montowania nie jest pusty +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Katalog @WHERE@ został podany jako punkt montowania (drugie pole w pliku +/etc/fstab lub pole Where= w pliku jednostki systemd) i nie jest pusty. Nie +wpływa to na montowanie, ale wcześniej istniejące pliki w tym katalogu stają +się niedostępne. Aby zobaczyć te pliki, proszę ręcznie zamontować system +plików w innym położeniu. + +-- 24d8d4452573402496068381a6312df2 +Subject: Uruchomiono maszynę wirtualną lub kontener +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Maszyna wirtualna @NAME@ (PID prowadzący @LEADER@) została uruchomiona i jest +gotowa do użycia. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Zakończono maszynę wirtualną lub kontener +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Maszyna wirtualna @NAME@ (PID prowadzący @LEADER@) została wyłączona. diff --git a/src/journal/catalog/systemd.pt_BR.catalog b/src/journal/catalog/systemd.pt_BR.catalog new file mode 100644 index 0000000000..d9716e30f7 --- /dev/null +++ b/src/journal/catalog/systemd.pt_BR.catalog @@ -0,0 +1,264 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2015 Rafael Ferreira (translation) +# +# 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 . + +# Catálogo de mensagens para as mensagens do próprio systemd + +# O formato do catálogo está documentado em +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# Para uma explicação do porquê de fazermos tudo isso, veja +# https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: O jornal foi inciado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O processo jornal do sistema foi iniciado, arquivos foram abertos e está +pronto para processar requisições. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: O jornal foi interrompido +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O processo do jornal do sistema foi desligado e todos os arquivos de jornal +do sistema foram fechados. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Mensagens de um serviço foram suprimidas +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Um serviço registrou no log um número excessivo de mensagens dentro de um +período de tempo. Mensagens do serviço foram descartadas. + +Note que apenas mensagens de um serviço em questão foram descartadas; outras +mensagens dos serviços não foram afetadas. + +Os controles de limites de quando as mensagens são descartadas pode ser +configurado com RateLimitInterval= e RateLimitBurst= no +/etc/systemd/journald.conf. Veja journald.conf(5) para detalhes. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Mensagens do jornal foram perdidas +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Mensagens do kernel foram perdidas pois o sistema do jornal não pôde +processá-las em velocidade suficiente para a demanda. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Processo @COREDUMP_PID@ (@COREDUMP_COMM@) despejou núcleo +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Processo @COREDUMP_PID@ (@COREDUMP_COMM@) travou e despejou o núcleo. + +Isso normalmente indica um erro de programação no programa que travou e +deveria ser relatado para seu fabricante como um erro. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: A nova sessão @SESSION_ID@ foi criada para usuário o @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Uma nova sessão com o ID @SESSION_ID@ foi criada para o usuário @USER_ID@. + +O processo originador da sessão é @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Sessão @SESSION_ID@ foi terminada +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Um sessão com o ID @SESSION_ID@ foi terminada. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Um novo seat @SEAT_ID@ está disponível +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Um novo seat @SEAT_ID@ foi configurado e está disponível. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Seat @SEAT_ID@ foi removido agora +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Um seat @SEAT_ID@ foi removido e não está mais disponível. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Time change +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O relógio do sistema foi alterado para @REALTIME@ microssegundos após 1º de +janeiro de 1970. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Fuso horário alterado para @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O fuso horário do sistema foi alterado para @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Inicialização do sistema foi concluída +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Todos os serviços do sistema necessários que estão enfileirados para +executar na inicialização do sistema, foram iniciados com sucesso. Note +que isso não significa que a máquina está ociosa, pois os serviços podem +ainda estar ocupados com a inicialização completa. + +Inicialização do kernel precisou @KERNEL_USEC@ microssegundos. + +Disco de RAM inicial precisou de @INITRD_USEC@ microssegundos. + +Inicialização do espaço do usuário precisou de @USERSPACE_USEC@ microssegundos. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Estado de suspensão do sistema @SLEEP@ iniciado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O sistema entrou agora no estado de suspensão @SLEEP@. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Estado de suspensão do sistema @SLEEP@ finalizado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O sistema saiu agora do estado de suspensão @SLEEP@. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Desligamento do sistema iniciado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Desligamento do sistema foi inicializado. O desligamento se iniciou e todos +os serviços do sistema foram terminados e todos os sistemas desmontados. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Unidade @UNIT@ sendo iniciado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ está sendo iniciada. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Unidade @UNIT@ concluiu a inicialização +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ concluiu a inicialização. + +The start-up result is @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Unidade @UNIT@ sendo desligado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ está sendo desligada. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: A unidade @UNIT@ concluiu o desligamento +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ concluiu o desligamento. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: A unidade @UNIT@ falhou +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ falhou. + +O resultado é @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: Unidade @UNIT@ iniciou recarregamento de sua configuração +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ iniciou o recarregamento de sua configuração. + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Unidade @UNIT@ concluiu recarregamento de sua configuração +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A unidade @UNIT@ concluiu o recarregamento de sua configuração. + +O resultado é @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Processo @EXECUTABLE@ não pôde ser executado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O processo @EXECUTABLE@ não pôde ser executado e falhou. + +O número de erro retornado por este processo é @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Uma ou mais mensagens não puderam ser encaminhadas para o syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Uma ou mais mensagens não puderam ser encaminhadas para o serviço do syslog +em execução paralela ao journald. Isso normalmente indica que a implementação +do syslog não foi capaz de se manter com a velocidade das mensagens +enfileiradas. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Ponto de montagem não está vazio +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +O diretório @WHERE@ está especificado como ponto de montagem (o segundo campo +no /etc/fstab ou campo Where= no arquivo de unidade do systemd) e não está +vazio. Isso não interfere com a montagem, mas os arquivos pré-existentes +neste diretório se tornaram inacessívels. Para ver aqueles arquivos, sobre os +quais foi realizada a montagem, por favor monte manualmente o sistema de +arquivos subjacente para uma localização secundária. + +-- 24d8d4452573402496068381a6312df2 +Subject: Uma máquina virtual ou contêiner foi iniciado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A máquina virtual @NAME@ com seu PID @LEADER@ incial foi iniciada e está +pronto para ser usad. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Uma máquina virtual ou contêiner foi terminado +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +A máquina virtual @NAME@ com seu PID @LEADER@ incial foi desligada. diff --git a/src/journal/catalog/systemd.ru.catalog b/src/journal/catalog/systemd.ru.catalog new file mode 100644 index 0000000000..eedbb8aa9c --- /dev/null +++ b/src/journal/catalog/systemd.ru.catalog @@ -0,0 +1,354 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2013-2016 Sergey Ptashnick +# +# 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 . + +# Message catalog for systemd's own messages +# Russian translation + +# Формат каталога сообщений описан по ссылке +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# Перед каждым элементом в комментарии указан Subject исходного +# сообщения (на английском). + +# Subject: The Journal has been started +-- f77379a8490b408bbe5f6940505a777b +Subject: Запущена служба журналирования +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Процесс, отвечающий за журналирование системных событий, успешно запустился, +открыл для записи файлы журнала, и готов обрабатывать запросы. + +# Subject: The Journal has been stopped +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Служба журналирования остановлена +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Процесс, отвечающий за журналирование системных событий, завершил работу и +закрыл все свои файлы. + +# Subject: Disk space used by the journal +-- ec387f577b844b8fa948f33cad9a75e6 +Subject: Место на диске, занятое журналом +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@JOURNAL_NAME@ (@JOURNAL_PATH@) сейчас занимает @CURRENT_USE_PRETTY@. +Максимальный разрешенный размер составляет @MAX_USE_PRETTY@. +Оставляем свободными как минимум @DISK_KEEP_FREE_PRETTY@ (сейчас на диске +свободно @DISK_AVAILABLE_PRETTY@). +Таким образом, предел использования составляет @LIMIT_PRETTY@, из которых +@AVAILABLE_PRETTY@ пока свободно. + +Ограничения на размер журнала настраиваются при помощи параметров +SystemMaxUse=, SystemKeepFree=, SystemMaxFileSize=, RuntimeMaxUse=, +RuntimeKeepFree=, RuntimeMaxFileSize= в файле /etc/systemd/journald.conf. +Более подробные сведения вы можете получить на справочной странице +journald.conf(5). + +# Subject: Messages from a service have been suppressed +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Часть сообщений от службы пропущена +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Служба отправила слишком много сообщений за короткий промежуток времени. +Часть сообщений была пропущена. + +Обратите внимание, что были пропущены сообщения только от этой службы, +сообщения других служб не затронуты. + +Предел, после которого служба журнала начинает игнорировать сообщения, +настраивается параметрами RateLimitInterval= и RateLimitBurst= в файле +/etc/systemd/journald.conf. Подробности смотрите на странице руководства +journald.conf(5). + +# Subject: Journal messages have been missed +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Часть сообщений ядра пропущена +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Часть сообщений, поступивших от ядра, была потеряна, так как служба +журналирования не успела их обработать. + +# Subject: Process @COREDUMP_PID@ (@COREDUMP_COMM@) dumped core +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Процесс @COREDUMP_PID@ (@COREDUMP_COMM@) сбросил дамп памяти +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Процесс @COREDUMP_PID@ (@COREDUMP_COMM@) завершился из-за критической ошибки. +Записан дамп памяти. + +Вероятно, это произошло из-за ошибки, допущенной в коде программы. +Рекомендуется сообщить её разработчикам о возникшей проблеме. + +# Subject: A new session @SESSION_ID@ has been created for user @USER_ID@ +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Для пользователя @USER_ID@ создан новый сеанс @SESSION_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Для пользователя @USER_ID@ создан новый сеанс с идентификатором @SESSION_ID@. + +Главный процесс нового сеанса имеет индентификатор @LEADER@. + +# Subject: A session @SESSION_ID@ has been terminated +-- 3354939424b4456d9802ca8333ed424a +Subject: Сеанс @SESSION_ID@ завершен +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Сеанс с идентификатором @SESSION_ID@ завершился. + +# Subject: A new seat @SEAT_ID@ is now available +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Добавлено новое рабочее место @SEAT_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Новое рабочее место (seat) @SEAT_ID@ полностью настроено и готово к +использованию. + +# Subject: A seat @SEAT_ID@ has now been removed +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Рабочее место @SEAT_ID@ отключено +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Рабочее место (seat) @SEAT_ID@ было отключено. + +# Subject: Time change +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Переведены системные часы +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Системные часы были переведены. Сейчас они показывают @REALTIME@ микросекунд +с момента 00:00:00 1 января 1970 года. + +# Subject: Time zone change to @TIMEZONE@ +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Часовой пояс изменен на @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Системный часовой пояс был изменен. Новое значение: @TIMEZONE@. + +# Subject: System start-up is now complete +-- b07a249cd024414a82dd00cd181378ff +Subject: Запуск системы завершен +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Все системные службы, запуск которых предписан настройками, были запущены. +Впрочем, это ещё не означает, что система в данный момент ничем не занята, +так как некоторые службы могут продолжать инициализацию даже после того, как +отчитались о своем запуске. + +Запуск ядра занял @KERNEL_USEC@ микросекунд. + +Процессы начального RAM-диска (initrd) отработали за @INITRD_USEC@ микросекунд. + +Запуск системных служб занял @USERSPACE_USEC@ микросекунд. + +# Subject: System sleep state @SLEEP@ entered +-- 6bbd95ee977941e497c48be27c254128 +Subject: Система перешла в состояние сна (@SLEEP@) +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Система была переведена в состояние сна (@SLEEP@). + +# Subject: System sleep state @SLEEP@ left +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Система вышла из состояния сна (@SLEEP@) +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Система была выведена из состояния сна (@SLEEP@). + +# Subject: System shutdown initiated +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Подготовка системы к выключению +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Начат процесс подготовки к выключению компьютера. Останавливаются все системные +службы, отмонтируются все файловые системы. + +# Subject: Unit @UNIT@ has begun with start-up +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Начинается запуск юнита @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Начат процесс запуска юнита @UNIT@. + +# Subject: Unit @UNIT@ has finished start-up +-- 39f53479d3a045ac8e11786248231fbf +Subject: Запуск юнита @UNIT@ завершен +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Процесс запуска юнита @UNIT@ был завершен. + +Результат: @RESULT@. + +# Subject: Unit @UNIT@ has begun shutting down +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Начинается остановка юнита @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Начат процесс остановки юнита @UNIT@. + +# Subject: Unit @UNIT@ has finished shutting down +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Завершена остановка юнита @UNIT@. +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Процесс остановки юнита @UNIT@ был завершен. + +# Subject: Unit @UNIT@ has failed +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Ошибка юнита @UNIT@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Произошел сбой юнита @UNIT@. + +Результат: @RESULT@. + +# Subject: Unit @UNIT@ has begun with reloading its configuration +-- d34d037fff1847e6ae669a370e694725 +Subject: Юнит @UNIT@ начал перечитывать свои настройки +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Юнит @UNIT@ начал процесс перечитывания своей конфигурации. + +# Subject: Unit @UNIT@ has finished reloading its configuration +-- 7b05ebc668384222baa8881179cfda54 +Subject: Юнит @UNIT@ завершил перечитывание своих настроек +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Юнит @UNIT@ завершил процесс перечитывания своей конфигурации. + +Результат: @RESULT@. + +# Subject: Process @EXECUTABLE@ could not be executed +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Не удалось запустить процесс @EXECUTABLE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Сбой: не удалось запустить процесс @EXECUTABLE@. + +Код ошибки: @ERRNO@. + +# Subject: One or more messages could not be forwarded to syslog +-- 0027229ca0644181a76c4e92458afa2e +Subject: Часть сообщений не удалось передать процессу syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Не удалось передать некоторые сообщения демону системного лога (syslog), +дублирующему работу службы системного журнала. Скорее всего, причина в том, что +используемая реализация syslog не успевает обрабатывать сообщения с достаточной +скоростью. + +# Subject: Mount point is not empty +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Каталог, являющийся точкой монтирования, не пуст +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Каталог @WHERE@, который был указан в качестве точки монтирования (во втором +столбце файла /etc/fstab, либо в параметре Where= файла конфигурации юнита), +не является пустым. Это никак не мешает монтированию, однако ранее находившиеся +в нем файлы будут недоступны. Чтобы получить к ним доступ, вы можете вручную +перемонтировать эту файловую систему в другую точку. + +# Subject: A virtual machine or container has been started +-- 24d8d4452573402496068381a6312df2 +Subject: Запущена виртуальная машина/контейнер +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Виртуальная машина @NAME@ (идентификатор главного процесса: @LEADER@) запущена и +готова к работе. + +# Subject: A virtual machine or container has been terminated +-- 58432bd3bace477cb514b56381b8a758 +Subject: Остановлена виртуальная машина/контейнер +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Виртуальная машина @NAME@ (идентификатор главного процесса: @LEADER@) выключена. + +# Subject: DNSSEC mode has been turned off, as server doesn't support it +-- 36db2dfa5a9045e1bd4af5f93e1cf057 +Subject: Механизм DNSSEC был отключен, так как DNS-сервер его не поддерживает +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) resolved.conf(5) + +Служба разрешения имен хостов (systemd-resolved.service) определила, что +указанный в настойках DNS-сервер не поддерживает технологию DNSSEC, и +автоматически отключила DNSSEC-проверки. + +Данное событие возникает, если в файле resolved.conf указан параметр +DNSSEC=allow-downgrade, и вышестоящий DNS-сервер не поддерживает DNSSEC. +Обратите внимание, что режим allow-downgrade допускает возможность атаки +"DNSSEC downgrade", в ходе которой атакующий хакер блокирует проверки DNSSEC +путем отправки ложных сообщений от имени DNS-сервера. + +Возникновение данного события может свидетельствовать как о том, что ваш +DNS-сервер не поддерживает DNSSEC, так и о том, что некий хакер успешно провел +против вас атаку, направленную на блокировку DNSSEC-проверок. + +# Subject: DNSSEC validation failed +-- 1675d7f172174098b1108bf8c7dc8f5d +Subject: Проверка DNSSEC провалена +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +DNS-запрос или отдельная ресурсная запись не прошла проверку DNSSEC. +Как правило, это свидетельствует о постороннем вмешательстве в канал связи. + +# Subject: A DNSSEC trust anchor has been revoked +-- 4d4408cfd0d144859184d1e65d7c8a65 +Subject: Открытый ключ DNSSEC был отозван +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:systemd-resolved.service(8) + +Открытый ключ (trust ahcnor) DNSSEC был отозван. Необходимо настроить новый +открытый ключ, либо обновить систему, чтобы получить обновленный открытый ключ. diff --git a/src/journal/catalog/systemd.sr.catalog b/src/journal/catalog/systemd.sr.catalog new file mode 100644 index 0000000000..cf700c477b --- /dev/null +++ b/src/journal/catalog/systemd.sr.catalog @@ -0,0 +1,262 @@ +# This file is part of systemd. +# +# Copyright 2012 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 . + +# Message catalog for systemd's own messages +# Serbian translation + +# Формат каталога је документован на +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# Да бисте видели зашто ово радимо, погледајте https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: Журнал је покренут +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Системски журналски процес се покренуо, отворио журналске +датотеке за упис и спреман је за обраду захтева. + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: Журнал је заустављен +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Системски журналски процес се зауставио и затворио све тренутно +отворене журналске датотеке. + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: Поруке од услуге су утишане +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +Услуга је уписала сувише порука за једно време. Поруке +од услуге су одбачене. + +Знајте да су само поруке од ове услуге одбачене, друге +услуге нису захваћене овим. + +Ограничења која подешавају начин на који се поруке одбацују се могу подесити +помоћу „RateLimitInterval=“ и „RateLimitBurst=“ параметара унутар датотеке +/etc/systemd/journald.conf. Погледајте journald.conf(5) за појединости. + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: Журналске поруке су изгубљене +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Поруке кернела су изгубљене јер журналски систем није могао да их +обради довољно брзо. + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: Процес @COREDUMP_PID@ (@COREDUMP_COMM@) је избацио своје језгро +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +Процес @COREDUMP_PID@ (@COREDUMP_COMM@) је пао и избацио своје језгро. + +Ово обично значи да постоји грешка у програму који је пао и ова +грешка треба да се пријави продавцу. + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: Нова сесија @SESSION_ID@ је направљена за корисника @USER_ID@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Нова сесија са ИБ-ом @SESSION_ID@ је направљена за корисника @USER_ID@. + +Водећи процес сесије је @LEADER@. + +-- 3354939424b4456d9802ca8333ed424a +Subject: Сесија @SESSION_ID@ је окончана +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Сесија са ИБ-ом @SESSION_ID@ је окончана. + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: Ново седиште @SEAT_ID@ је сада доступно +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Ново седиште @SEAT_ID@ је исподешавано и сада је доступно. + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: Седиште @SEAT_ID@ је сада уклоњено +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +Седиште @SEAT_ID@ је сада уклоњено и више није доступно. + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: Време је промењено +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Системски сат је сада подешен на @REALTIME@ микросекунде након 1. јануара 1970. године. + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: Временска зона је промењена на @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Временска зона је промењена на @TIMEZONE@. + +-- b07a249cd024414a82dd00cd181378ff +Subject: Подизање система је сада готово +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Све системске услуге које су заказане за подизање су успешно покренуте. +Знајте да ово не значи да је машина сада беспослена јер услуге могу +и даље бити заузете завршавањем покретања система. + +Подизање кернела је трајало @KERNEL_USEC@ микросекунде. + +Подизање почетног РАМ диска је трајало @INITRD_USEC@ микросекунде. + +Подизање корисничких програма је трајало @USERSPACE_USEC@ микросекунде. + +-- 6bbd95ee977941e497c48be27c254128 +Subject: Системско стање спавања @SLEEP@ започето +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Систем је сада ушао у @SLEEP@ стање спавања. + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: Системско стање спавања @SLEEP@ напуштено +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Систем је изашао из @SLEEP@ стања спавања. + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: Гашење система започето +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Систем-де гашење је започето. Гашење је сада почело и све +системске услуге су окончане и сви системи датотека откачени. + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: Јединица @UNIT@ је почела са покретањем +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је почела са покретањем. + +-- 39f53479d3a045ac8e11786248231fbf +Subject: Јединица @UNIT@ је завршила са покретањем +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је завршила са покретањем. + +Исход покретања је @RESULT@. + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: Јединица @UNIT@ је почела са гашењем +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је почела са гашењем. + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: Јединица @UNIT@ је завршила са гашењем +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је завршила са гашењем. + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: Јединица @UNIT@ је пукла +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је пукла. + +Исход је @RESULT@. + +-- d34d037fff1847e6ae669a370e694725 +Subject: Јединица @UNIT@ је почела са поновним учитавањем свог подешавања +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је почела са поновним учитавањем свог подешавања + +-- 7b05ebc668384222baa8881179cfda54 +Subject: Јединица @UNIT@ је завршила са поновним учитавањем свог подешавања +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Јединица @UNIT@ је завршила са поновним учитавањем свог подешавања + +Исход је @RESULT@. + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: Процес @EXECUTABLE@ није могао бити извршен +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Процес @EXECUTABLE@ није могао бити извршен и пукао је. + +Овај процес је вратио број грешке @ERRNO@. + +-- 0027229ca0644181a76c4e92458afa2e +Subject: Једна или више порука није могло бити прослеђено системском записнику +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Једна или више порука није могло бити прослеђено „syslog“ услузи +која ради упоредно са журнал-деом. Ово обично значи да спроведена +„syslog“ услуга није могла да издржи брзину свих надолазећих +порука у реду. + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: Тачка качења није празна +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Директоријум @WHERE@ је наведен као тачка качења (друго поље у +/etc/fstab датотеци или у „Where=“ пољу систем-де јединичне датотеке) +и он није празан. Ово не утиче на качење али ће већ постојеће датотеке у +овом директоријуму постати недоступне. Да бисте видели ове недоступне +датотеке, ручно прикачите основни систем датотека у другу +путању. + +-- 24d8d4452573402496068381a6312df2 +Subject: Виртуелна машина или контејнер је покренут(а) +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Виртуелна машина @NAME@ са водећим ПИБ-ом @LEADER@ је +покренута и сада је спремна за коришћење. + +-- 58432bd3bace477cb514b56381b8a758 +Subject: Виртуелна машина или контејнер је окончан(а) +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Виртуелна машина @NAME@ са водећим ПИБ-ом @LEADER@ је +угашена. diff --git a/src/journal/catalog/systemd.zh_CN.catalog b/src/journal/catalog/systemd.zh_CN.catalog new file mode 100644 index 0000000000..38639109e4 --- /dev/null +++ b/src/journal/catalog/systemd.zh_CN.catalog @@ -0,0 +1,253 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2015 Boyuan Yang +# +# 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 . + +# Message catalog for systemd's own messages +# Simplified Chinese translation + +# 本 catalog 文档格式被记载在 +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# 如需了解我们为什么做这些工作,请见 https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: 日志已开始 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系统日志进程已启动,已打开供写入的日志文件并准备好处理请求。 + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: 日志已停止 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系统日志进程已终止,并已关闭所有当前活动的日志文件。 + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: 由某个服务而来的消息已被抑制 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +某个服务在一个时间周期内记录了太多消息。 +从该服务而来的消息已被丢弃。 + +请注意只有由有问题的服务传来的消息被丢弃, +其它服务的消息不受影响。 + +可以在 /etc/systemd/journald.conf 中设定 RateLimitInterval= +以及 RateLimitBurst = 的值以控制丢弃信息的限制。 +请参见 journald.conf(5) 以了解详情。 + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: 日志消息已遗失 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +因日志系统对内核消息的处理速度不够快, +部分信息已经遗失。 + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: 进程 @COREDUMP_PID@ (@COREDUMP_COMM@) 核心已转储 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +进程 @COREDUMP_PID@ (@COREDUMP_COMM@) 已崩溃并进行核心转储。 + +这通常意味着崩溃程序中存在编程错误,并应当将此错误向其开发者报告。 + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: 一个新会话 @SESSION_ID@ 已为用户 @USER_ID@ 建立 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +一个 ID 为 @SESSION_ID@ 的新会话已为用户 @USER_ID@ 建立。 + +该会话的首进程为 @LEADER@。 + +-- 3354939424b4456d9802ca8333ed424a +Subject: 会话 @SESSION_ID@ 已终止 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +一个 ID 为 @SESSION_ID@ 的会话已终止。 + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: 一个新的座位 @SEAT_ID@ 可用 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +一个新的座位 @SEAT_ID@ 已被配置并已可用。 + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: 座位 @SEAT_ID@ 已被移除 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +座位 @SEAT_ID@ 已被移除并不再可用。 + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: 时间已变更 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系统时钟已变更为1970年1月1日后 @REALTIME@ 微秒。 + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: 时区变更为 @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系统时区已变更为 @TIMEZONE@。 + +-- b07a249cd024414a82dd00cd181378ff +Subject: 系统启动已完成 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +所有系统启动时需要的系统服务均已成功启动。 +请注意这并不代表现在机器已经空闲,因为某些服务可能仍处于完成启动的过程中。 + +内核启动使用了 @KERNEL_USEC@ 毫秒。 + +初始内存盘启动使用了 @INITRD_USEC@ 毫秒。 + +用户空间启动使用了 @USERSPACE_USEC@ 毫秒。 + +-- 6bbd95ee977941e497c48be27c254128 +Subject: 系统已进入 @SLEEP@ 睡眠状态 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-deve + +系统现已进入 @SLEEP@ 睡眠状态。 + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: 系统已离开 @SLEEP@ 睡眠状态 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系统现已离开 @SLEEP@ 睡眠状态。 + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: 系统关机已开始 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系统关机操作已初始化。 +关机已开始,所有系统服务均已结束,所有文件系统已卸载。 + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: @UNIT@ 单元已开始启动 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已开始启动。 + +-- 39f53479d3a045ac8e11786248231fbf +Subject: @UNIT@ 单元已结束启动 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已结束启动。 + +启动结果为“@RESULT@”。 + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: @UNIT@ 单元已开始停止操作 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已开始停止操作。 + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: @UNIT@ 单元已结束停止操作 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已结束停止操作。 + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: @UNIT@ 单元已失败 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已失败。 + +结果为“@RESULT@”。 + +-- d34d037fff1847e6ae669a370e694725 +Subject: @UNIT@ 单元已开始重新载入其配置 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已开始重新载入其配置。 + +-- 7b05ebc668384222baa8881179cfda54 +Subject: @UNIT@ 单元已结束配置重载入 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +@UNIT@ 单元已结束配置重载入操作。 + +结果为“@RESULT@”。 + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: 进程 @EXECUTABLE@ 无法执行 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +进程 @EXECUTABLE@ 无法被执行并已失败。 + +该进程返回的错误代码为 @ERRNO@。 + +-- 0027229ca0644181a76c4e92458afa2e +Subject: 一个或更多消息无法被转发至 syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +有一条或更多的消息无法被转发至与 journald 同时运行的 syslog 服务。 +这通常意味着 syslog 实现无法跟上队列中消息进入的速度。 + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: 挂载点不为空 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +目录 @WHERE@ 被指定为挂载点(即 /etc/fstab 文件的第二栏,或 systemd 单元 +文件的 Where= 字段),且该目录非空。 +这并不会影响挂载行为,但该目录中先前已存在的文件将无法被访问。 +如需查看这些文件,请手动将其下的文件系统挂载到另一个位置。 + +-- 24d8d4452573402496068381a6312df2 +Subject: 一个虚拟机或容器已启动 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +虚拟机 @NAME@,以及其首进程 PID @LEADER@,已被启动并可被使用。 + +-- 58432bd3bace477cb514b56381b8a758 +Subject: 一个虚拟机或容器已被终止 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +虚拟机 @NAME@,以及其首进程 PID @LEADER@,已被关闭并停止。 diff --git a/src/journal/catalog/systemd.zh_TW.catalog b/src/journal/catalog/systemd.zh_TW.catalog new file mode 100644 index 0000000000..027ffe44e5 --- /dev/null +++ b/src/journal/catalog/systemd.zh_TW.catalog @@ -0,0 +1,263 @@ +# This file is part of systemd. +# +# Copyright 2012 Lennart Poettering +# Copyright 2015 Jeff Huang +# +# 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 . + +# Message catalog for systemd's own messages +# Traditional Chinese translation + +# Catalog 的格式記錄於 +# http://www.freedesktop.org/wiki/Software/systemd/catalog + +# For an explanation why we do all this, see https://xkcd.com/1024/ + +-- f77379a8490b408bbe5f6940505a777b +Subject: 日誌已開始 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系統日誌行程已啟動,已開啟日誌 +檔案供寫入並準備好對行程的要求做出回應。 + +-- d93fb3c9c24d451a97cea615ce59c00b +Subject: 日誌已停止 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系統日誌行程已關閉,且關閉所有目前 +活躍的日誌檔案。 + +-- a596d6fe7bfa4994828e72309e95d61e +Subject: 從服務而來的訊息已被抑制 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:journald.conf(5) + +有一個服務在一個時間週期內記錄了太多訊息。 +從該服務而來的訊息已被丟棄。 + +注意,只有有問題的服務之訊息被丟棄, +其他服務的訊息則不受影響。 + +可以在 /etc/systemd/journald.conf 中設定 +RateLimitInterval= 以及 RateLimitBurst= +來控制當訊息要開始被丟棄時的限制。參見 journald.conf(5) 以獲得更多資訊。 + +-- e9bf28e6e834481bb6f48f548ad13606 +Subject: 日誌訊息已遺失 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +因日誌系統對核心訊息的處理不夠快速, +部份訊息已遺失。 + +-- fc2e22bc6ee647b6b90729ab34a250b1 +Subject: 行程 @COREDUMP_PID@ (@COREDUMP_COMM@) 核心傾印 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: man:core(5) + +行程 @COREDUMP_PID@ (@COREDUMP_COMM@) 當掉並核心傾印。 + +這通常代表了在當掉的程式中的一個程式錯誤 +並需要回報錯誤給其開發者。 + +-- 8d45620c1a4348dbb17410da57c60c66 +Subject: 新的工作階段 @SESSION_ID@ 已為使用者 @USER_ID@ 建立 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +一個新的工作階段,ID @SESSION_ID@ 已為使用者 @USER_ID@ 建立。 + +這個工作階段的領導行程為 @LEADER@。 + +-- 3354939424b4456d9802ca8333ed424a +Subject: 工作階段 @SESSION_ID@ 已結束 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +一個工作階段,ID @SESSION_ID@ 已結束。 + +-- fcbefc5da23d428093f97c82a9290f7b +Subject: 新的座位 @SEAT_ID@ 可用 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +一個新的座位 @SEAT_ID@ 已被設定且現在可用。 + +-- e7852bfe46784ed0accde04bc864c2d5 +Subject: 座位 @SEAT_ID@ 已被移除 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel +Documentation: http://www.freedesktop.org/wiki/Software/systemd/multiseat + +座位 @SEAT_ID@ 已被移除且不再可用。 + +-- c7a787079b354eaaa9e77b371893cd27 +Subject: 時間變更 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系統時間已變更為1970年1月1日後 @REALTIME@ 微秒。 + +-- 45f82f4aef7a4bbf942ce861d1f20990 +Subject: 時區變更為 @TIMEZONE@ +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系統時區已變更為 @TIMEZONE@。 + +-- b07a249cd024414a82dd00cd181378ff +Subject: 系統啟動已完成 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +所有開機所必要的系統服務都已成功啟動。 +注意這並不代表這臺機器有空閒的時間 +可以服務,可能仍忙於完成啟動。 + +核心啟動需要 @KERNEL_USEC@ 微秒。 + +初始 RAM 磁碟啟動需要 @INITRD_USEC@ 微秒。 + +使用者空間啟動需要 @USERSPACE_USEC@ 微秒。 + +-- 6bbd95ee977941e497c48be27c254128 +Subject: 系統進入 @SLEEP@ 睡眠狀態 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系統現在已進入 @SLEEP@ 睡眠狀態。 + +-- 8811e6df2a8e40f58a94cea26f8ebf14 +Subject: 系統離開 @SLEEP@ 睡眠狀態 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +系統現在已離開 @SLEEP@ 睡眠狀態。 + +-- 98268866d1d54a499c4e98921d93bc40 +Subject: 系統關機開始 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +Systemd 關閉已經開始。關閉已開始且所有系統服務 +都已結束,所有的檔案系統也都已被卸載。 + +-- 7d4958e842da4a758f6c1cdc7b36dcc5 +Subject: 單位 @UNIT@ 已開始啟動 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 已開始啟動。 + +-- 39f53479d3a045ac8e11786248231fbf +Subject: 單位 @UNIT@ 啟動已結束 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 啟動已結束。 + +啟動結果為 @RESULT@。 + +-- de5b426a63be47a7b6ac3eaac82e2f6f +Subject: 單位 @UNIT@ 已開始關閉 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 已開始關閉。 + +-- 9d1aaa27d60140bd96365438aad20286 +Subject: 單位 @UNIT@ 已關閉結束 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 已關閉結束。 + +-- be02cf6855d2428ba40df7e9d022f03d +Subject: 單位 @UNIT@ 已失敗 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 已失敗。 + +結果為 @RESULT@。 + +-- d34d037fff1847e6ae669a370e694725 +Subject: 單位 @UNIT@ 已開始重新載入其設定 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 已開始重新載入其設定 + +-- 7b05ebc668384222baa8881179cfda54 +Subject: 單位 @UNIT@ 已結束重新載入其設定 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +單位 @UNIT@ 已結束重新載入其設定 + +結果為 @RESULT@。 + +-- 641257651c1b4ec9a8624d7a40a9e1e7 +Subject: 行程 @EXECUTABLE@ 無法執行 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +行程 @EXECUTABLE@ 無法執行且失敗。 + +由該行程所回傳的錯誤碼為 @ERRNO@。 + +-- 0027229ca0644181a76c4e92458afa2e +Subject: 一個或更多訊息無法被轉發到 syslog +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +一個或更多訊息無法被轉發到 syslog 服務 +以及並行執行的 journald。這通常代表著 +syslog 實作並無未跟上佇列中訊息 +的速度。 + +-- 1dee0369c7fc4736b7099b38ecb46ee7 +Subject: 掛載點不為空 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +目錄 @WHERE@ 被指定為掛載點(在 /etc/fstab 中的 +第二欄或是在 systemd 單位檔案中的 Where= 欄位)且其不為空。 +這並不會干擾掛載,但在此目錄中已存在的檔案 +會變成無法存取的狀態。要檢視這些 over-mounted 的檔案, +請手動掛載下面的檔案系統到次要 +位置。 + +-- 24d8d4452573402496068381a6312df2 +Subject: 虛擬機器或容器已啟動 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +虛擬機器 @NAME@ 包含它的領導 PID @LEADER@ 現在 +已經開始並已經可以使用。 + +-- 58432bd3bace477cb514b56381b8a758 +Subject: 虛擬機器或容器已結束 +Defined-By: systemd +Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel + +虛擬機器 @NAME@ 包含它的領導 PID @LEADER@ 已經 +關閉。 diff --git a/src/journal/journal-def.h b/src/journal/journal-def.h index 67edb43960..a0a052ce4f 100644 --- a/src/journal/journal-def.h +++ b/src/journal/journal-def.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-id128.h" +#include #include "macro.h" #include "sparse-endian.h" diff --git a/src/journal/journal-file.c b/src/journal/journal-file.c index 912eb94d0a..4ea7013a53 100644 --- a/src/journal/journal-file.c +++ b/src/journal/journal-file.c @@ -37,7 +37,7 @@ #include "lookup3.h" #include "parse-util.h" #include "random-util.h" -#include "sd-event.h" +#include #include "string-util.h" #include "xattr-util.h" diff --git a/src/journal/journal-file.h b/src/journal/journal-file.h index 07b9561b8a..0414117d98 100644 --- a/src/journal/journal-file.h +++ b/src/journal/journal-file.h @@ -25,13 +25,13 @@ #include #endif -#include "sd-id128.h" +#include #include "hashmap.h" #include "journal-def.h" #include "macro.h" #include "mmap-cache.h" -#include "sd-event.h" +#include #include "sparse-endian.h" typedef struct JournalMetrics { diff --git a/src/journal/journal-internal.h b/src/journal/journal-internal.h index 7639325acf..8f21544ae1 100644 --- a/src/journal/journal-internal.h +++ b/src/journal/journal-internal.h @@ -23,8 +23,8 @@ #include #include -#include "sd-id128.h" -#include "sd-journal.h" +#include +#include #include "hashmap.h" #include "journal-def.h" diff --git a/src/journal/journal-qrcode.h b/src/journal/journal-qrcode.h index ef39085561..34a779d5be 100644 --- a/src/journal/journal-qrcode.h +++ b/src/journal/journal-qrcode.h @@ -22,6 +22,6 @@ #include #include -#include "sd-id128.h" +#include int print_qr_code(FILE *f, const void *seed, size_t seed_size, uint64_t start, uint64_t interval, const char *hn, sd_id128_t machine); diff --git a/src/journal/journal-send.c b/src/journal/journal-send.c index c7d670f4ff..b85d9d3c61 100644 --- a/src/journal/journal-send.c +++ b/src/journal/journal-send.c @@ -27,7 +27,7 @@ #define SD_JOURNAL_SUPPRESS_LOCATION -#include "sd-journal.h" +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/journal/journal-vacuum.c b/src/journal/journal-vacuum.c index 05e97620ae..3650ab3865 100644 --- a/src/journal/journal-vacuum.c +++ b/src/journal/journal-vacuum.c @@ -21,7 +21,7 @@ #include #include -#include "sd-id128.h" +#include #include "alloc-util.h" #include "dirent-util.h" diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 273242bea6..1670978466 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -33,8 +33,8 @@ #include #include -#include "sd-bus.h" -#include "sd-journal.h" +#include +#include #include "acl-util.h" #include "alloc-util.h" diff --git a/src/journal/journald-kmsg.c b/src/journal/journald-kmsg.c index eb1ac90e98..1ac57bb361 100644 --- a/src/journal/journald-kmsg.c +++ b/src/journal/journald-kmsg.c @@ -24,7 +24,7 @@ #include #include "libudev.h" -#include "sd-messages.h" +#include #include "escape.h" #include "fd-util.h" diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index ee2db8d29f..4f53bf01c3 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -27,9 +27,9 @@ #include #include "libudev.h" -#include "sd-daemon.h" -#include "sd-journal.h" -#include "sd-messages.h" +#include +#include +#include #include "acl-util.h" #include "alloc-util.h" diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h index b9551dda1b..cb8a5bbe9b 100644 --- a/src/journal/journald-server.h +++ b/src/journal/journald-server.h @@ -22,7 +22,7 @@ #include #include -#include "sd-event.h" +#include typedef struct Server Server; diff --git a/src/journal/journald-stream.c b/src/journal/journald-stream.c index 6e8b405b53..583b1305ae 100644 --- a/src/journal/journald-stream.c +++ b/src/journal/journald-stream.c @@ -24,8 +24,8 @@ #include #endif -#include "sd-daemon.h" -#include "sd-event.h" +#include +#include #include "alloc-util.h" #include "dirent-util.h" diff --git a/src/journal/journald-syslog.c b/src/journal/journald-syslog.c index 5153fd0cce..480a1cc405 100644 --- a/src/journal/journald-syslog.c +++ b/src/journal/journald-syslog.c @@ -21,7 +21,7 @@ #include #include -#include "sd-messages.h" +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/journal/journald.c b/src/journal/journald.c index 272acb71c4..1afe44fa8e 100644 --- a/src/journal/journald.c +++ b/src/journal/journald.c @@ -19,8 +19,8 @@ #include -#include "sd-daemon.h" -#include "sd-messages.h" +#include +#include #include "formats-util.h" #include "journal-authenticate.h" diff --git a/src/journal/sd-journal.c b/src/journal/sd-journal.c index 5a2a28a8d4..1493f0348d 100644 --- a/src/journal/sd-journal.c +++ b/src/journal/sd-journal.c @@ -26,7 +26,7 @@ #include #include -#include "sd-journal.h" +#include #include "alloc-util.h" #include "catalog.h" diff --git a/src/journal/test-catalog.c b/src/journal/test-catalog.c index da6fcbca4d..6f2f9f5561 100644 --- a/src/journal/test-catalog.c +++ b/src/journal/test-catalog.c @@ -23,7 +23,7 @@ #include #include -#include "sd-messages.h" +#include #include "alloc-util.h" #include "catalog.h" diff --git a/src/journal/test-journal-enum.c b/src/journal/test-journal-enum.c index e5e9d9dcb3..ffdc317e1d 100644 --- a/src/journal/test-journal-enum.c +++ b/src/journal/test-journal-enum.c @@ -19,7 +19,7 @@ #include -#include "sd-journal.h" +#include #include "journal-internal.h" #include "log.h" diff --git a/src/journal/test-journal-flush.c b/src/journal/test-journal-flush.c index 7bd9c40366..3dee462e74 100644 --- a/src/journal/test-journal-flush.c +++ b/src/journal/test-journal-flush.c @@ -19,7 +19,7 @@ #include -#include "sd-journal.h" +#include #include "alloc-util.h" #include "journal-file.h" diff --git a/src/journal/test-journal-init.c b/src/journal/test-journal-init.c index ef21e2d05f..e6713034dd 100644 --- a/src/journal/test-journal-init.c +++ b/src/journal/test-journal-init.c @@ -17,7 +17,7 @@ along with systemd; If not, see . ***/ -#include "sd-journal.h" +#include #include "log.h" #include "parse-util.h" diff --git a/src/journal/test-journal-interleaving.c b/src/journal/test-journal-interleaving.c index 7f94990888..b47182dbfd 100644 --- a/src/journal/test-journal-interleaving.c +++ b/src/journal/test-journal-interleaving.c @@ -21,7 +21,7 @@ #include #include -#include "sd-journal.h" +#include #include "alloc-util.h" #include "journal-file.h" diff --git a/src/journal/test-journal-match.c b/src/journal/test-journal-match.c index 3ab554b9b0..5ee2adb827 100644 --- a/src/journal/test-journal-match.c +++ b/src/journal/test-journal-match.c @@ -19,7 +19,7 @@ #include -#include "sd-journal.h" +#include #include "alloc-util.h" #include "journal-internal.h" diff --git a/src/journal/test-journal-send.c b/src/journal/test-journal-send.c index d70f0b0bc8..169082f9a4 100644 --- a/src/journal/test-journal-send.c +++ b/src/journal/test-journal-send.c @@ -21,7 +21,7 @@ #include #include -#include "sd-journal.h" +#include #include "macro.h" diff --git a/src/journal/test-journal-stream.c b/src/journal/test-journal-stream.c index 4e6f8c0f7b..57c6c0872d 100644 --- a/src/journal/test-journal-stream.c +++ b/src/journal/test-journal-stream.c @@ -20,7 +20,7 @@ #include #include -#include "sd-journal.h" +#include #include "alloc-util.h" #include "journal-file.h" diff --git a/src/kernel-install/Makefile b/src/kernel-install/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/kernel-install/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/kernel-install/Makefile b/src/kernel-install/Makefile new file mode 100644 index 0000000000..d50b684d01 --- /dev/null +++ b/src/kernel-install/Makefile @@ -0,0 +1,33 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +dist_bin_SCRIPTS = \ + src/kernel-install/kernel-install + +dist_kernelinstall_SCRIPTS = \ + src/kernel-install/50-depmod.install \ + src/kernel-install/90-loaderentry.install + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/kernel-install/bash-completion_kernel-install b/src/kernel-install/bash-completion_kernel-install new file mode 100644 index 0000000000..7cd2494cf7 --- /dev/null +++ b/src/kernel-install/bash-completion_kernel-install @@ -0,0 +1,50 @@ +# kernel-install(8) completion -*- shell-script -*- +# +# This file is part of systemd. +# +# Copyright 2013 Kay Sievers +# Copyright 2013 Harald Hoyer +# +# 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 +# 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 . + +_kernel_install() { + local comps + local MACHINE_ID + local cur=${COMP_WORDS[COMP_CWORD]} + + case $COMP_CWORD in + 1) + comps="add remove" + ;; + 2) + comps=$(cd /lib/modules; echo [0-9]*) + if [[ ${COMP_WORDS[1]} == "remove" ]] && [[ -f /etc/machine-id ]]; then + read MACHINE_ID < /etc/machine-id + if [[ $MACHINE_ID ]] && ( [[ -d /boot/$MACHINE_ID ]] || [[ -L /boot/$MACHINE_ID ]] ); then + comps=$(cd "/boot/$MACHINE_ID"; echo [0-9]*) + fi + fi + ;; + 3) + [[ "$cur" ]] || cur=/boot/vmlinuz-${COMP_WORDS[2]} + comps=$(compgen -f -- "$cur") + compopt -o filenames + ;; + esac + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) + return 0 +} + +complete -F _kernel_install kernel-install diff --git a/src/kernel-install/kernel-install.xml b/src/kernel-install/kernel-install.xml new file mode 100644 index 0000000000..eb519188a6 --- /dev/null +++ b/src/kernel-install/kernel-install.xml @@ -0,0 +1,192 @@ + + + + + + + + + kernel-install + systemd + + + + Developer + Harald + Hoyer + harald@redhat.com + + + + + + kernel-install + 8 + + + + kernel-install + Add and remove kernel and initramfs images to and from /boot + + + + + kernel-install + COMMAND + KERNEL-VERSION + KERNEL-IMAGE + + + + + Description + + kernel-install is used to install and remove kernel and + initramfs images to and from /boot. + + + kernel-install will execute the files + located in the directory /usr/lib/kernel/install.d/ + and the local administration directory /etc/kernel/install.d/. + All files are collectively sorted and executed in lexical order, regardless of the directory in + which they live. However, files with identical filenames replace each other. + Files in /etc/kernel/install.d/ take precedence over files with the same name + in /usr/lib/kernel/install.d/. This can be used to override a system-supplied + executables with a local file if needed; a symbolic link in /etc/kernel/install.d/ + with the same name as an executable in /usr/lib/kernel/install.d/, + pointing to /dev/null, disables the executable entirely. Executables must have the + extension .install; other extensions are ignored. + + + + + Commands + The following commands are understood: + + + add KERNEL-VERSION KERNEL-IMAGE + + kernel-install creates the directory + /boot/MACHINE-ID/KERNEL-VERSION/ + and calls every executable + /usr/lib/kernel/install.d/*.install and + /etc/kernel/install.d/*.install with + the arguments + add KERNEL-VERSION /boot/MACHINE-ID/KERNEL-VERSION/ + + + The kernel-install plugin 50-depmod.install runs depmod for the KERNEL-VERSION. + + The kernel-install plugin + 90-loaderentry.install copies + KERNEL-IMAGE to + /boot/MACHINE-ID/KERNEL-VERSION/linux. + It also creates a boot loader entry according to the boot + loader specification in + /boot/loader/entries/MACHINE-ID-KERNEL-VERSION.conf. + The title of the entry is the + PRETTY_NAME parameter specified + in /etc/os-release or + /usr/lib/os-release (if the former is + missing), or "GNU/Linux + KERNEL-VERSION", if unset. If + the file initrd is found next to the + linux file, the initrd will be added to + the configuration. + + + + remove KERNEL-VERSION + + Calls every executable /usr/lib/kernel/install.d/*.install + and /etc/kernel/install.d/*.install with the arguments + remove KERNEL-VERSION /boot/MACHINE-ID/KERNEL-VERSION/ + + + kernel-install removes the entire directory + /boot/MACHINE-ID/KERNEL-VERSION/ afterwards. + + The kernel-install plugin 90-loaderentry.install removes the file + /boot/loader/entries/MACHINE-ID-KERNEL-VERSION.conf. + + + + + + + + + Exit status + If every executable returns with 0, 0 is returned, a non-zero failure code otherwise. + + + + Files + + + + /usr/lib/kernel/install.d/*.install + /etc/kernel/install.d/*.install + + + Drop-in files which are executed by kernel-install. + + + + + /etc/kernel/cmdline + /proc/cmdline + + + The content of the file /etc/kernel/cmdline specifies the kernel command line to use. + If that file does not exist, /proc/cmdline is used. + + + + + /etc/machine-id + + + The content of the file specifies the machine identification MACHINE-ID. + + + + + /etc/os-release + /usr/lib/os-release + + + The content of the file specifies the operating system title PRETTY_NAME. + + + + + + + See Also + + machine-id5, + os-release5, + Boot loader specification + + + + diff --git a/src/kernel-install/zsh-completion_kernel-install b/src/kernel-install/zsh-completion_kernel-install new file mode 100644 index 0000000000..4fdd3a4ae7 --- /dev/null +++ b/src/kernel-install/zsh-completion_kernel-install @@ -0,0 +1,26 @@ +#compdef kernel-install + +_images(){ + if [[ "$words[2]" == "remove" ]]; then + _message 'No more options' + else + _path_files -W /boot/ -P /boot/ -g "vmlinuz-*" + fi +} + +_kernels(){ + read _MACHINE_ID < /etc/machine-id + _kernel=( /lib/modules/[0-9]* ) + if [[ "$cmd" == "remove" && -n "$_MACHINE_ID" ]]; then + _kernel=( "/boot/$_MACHINE_ID"/[0-9]* ) + fi + _kernel=( ${_kernel##*/} ) + _describe "installed kernels" _kernel +} + +_arguments \ + '1::add or remove:(add remove)' \ + '2::kernel versions:_kernels' \ + '3::kernel images:_images' + +#vim: set ft=zsh sw=4 ts=4 et diff --git a/src/libbasic/.gitignore b/src/libbasic/.gitignore new file mode 100644 index 0000000000..e22411e484 --- /dev/null +++ b/src/libbasic/.gitignore @@ -0,0 +1,16 @@ +/cap-from-name.gperf +/cap-from-name.h +/cap-list.txt +/cap-to-name.h +/errno-from-name.gperf +/errno-from-name.h +/errno-list.txt +/errno-to-name.h +/af-from-name.gperf +/af-from-name.h +/af-list.txt +/af-to-name.h +/arphrd-from-name.gperf +/arphrd-from-name.h +/arphrd-list.txt +/arphrd-to-name.h diff --git a/src/libbasic/Makefile b/src/libbasic/Makefile new file mode 100644 index 0000000000..f93f8607ea --- /dev/null +++ b/src/libbasic/Makefile @@ -0,0 +1,280 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +noinst_LTLIBRARIES += \ + libbasic.la + +libbasic_la_SOURCES = \ + src/basic/missing.h \ + src/basic/capability-util.c \ + src/basic/capability-util.h \ + src/basic/c-rbtree.c \ + src/basic/c-rbtree.h \ + src/basic/conf-files.c \ + src/basic/conf-files.h \ + src/basic/stdio-util.h \ + src/basic/hostname-util.h \ + src/basic/hostname-util.c \ + src/basic/unit-name.c \ + src/basic/unit-name.h \ + src/basic/ioprio.h \ + src/basic/securebits.h \ + src/basic/special.h \ + src/basic/list.h \ + src/basic/unaligned.h \ + src/basic/macro.h \ + src/basic/def.h \ + src/basic/sparse-endian.h \ + src/basic/refcnt.h \ + src/basic/util.c \ + src/basic/util.h \ + src/basic/io-util.c \ + src/basic/io-util.h \ + src/basic/string-util.c \ + src/basic/string-util.h \ + src/basic/fd-util.c \ + src/basic/fd-util.h \ + src/basic/parse-util.c \ + src/basic/parse-util.h \ + src/basic/user-util.c \ + src/basic/user-util.h \ + src/basic/rlimit-util.c \ + src/basic/rlimit-util.h \ + src/basic/dirent-util.c \ + src/basic/dirent-util.h \ + src/basic/xattr-util.c \ + src/basic/xattr-util.h \ + src/basic/chattr-util.c \ + src/basic/chattr-util.h \ + src/basic/proc-cmdline.c \ + src/basic/proc-cmdline.h \ + src/basic/fs-util.c \ + src/basic/fs-util.h \ + src/basic/syslog-util.c \ + src/basic/syslog-util.h \ + src/basic/stat-util.c \ + src/basic/stat-util.h \ + src/basic/mount-util.c \ + src/basic/mount-util.h \ + src/basic/hexdecoct.c \ + src/basic/hexdecoct.h \ + src/basic/glob-util.h \ + src/basic/glob-util.c \ + src/basic/extract-word.c \ + src/basic/extract-word.h \ + src/basic/escape.c \ + src/basic/escape.h \ + src/basic/cpu-set-util.c \ + src/basic/cpu-set-util.h \ + src/basic/lockfile-util.c \ + src/basic/lockfile-util.h \ + src/basic/path-util.c \ + src/basic/path-util.h \ + src/basic/time-util.c \ + src/basic/time-util.h \ + src/basic/locale-util.c \ + src/basic/locale-util.h \ + src/basic/umask-util.h \ + src/basic/signal-util.c \ + src/basic/signal-util.h \ + src/basic/string-table.c \ + src/basic/string-table.h \ + src/basic/mempool.c \ + src/basic/mempool.h \ + src/basic/hashmap.c \ + src/basic/hashmap.h \ + src/basic/hash-funcs.c \ + src/basic/hash-funcs.h \ + src/basic/siphash24.c \ + src/basic/siphash24.h \ + src/basic/set.h \ + src/basic/ordered-set.h \ + src/basic/ordered-set.c \ + src/basic/bitmap.c \ + src/basic/bitmap.h \ + src/basic/fdset.c \ + src/basic/fdset.h \ + src/basic/prioq.c \ + src/basic/prioq.h \ + src/basic/web-util.c \ + src/basic/web-util.h \ + src/basic/strv.c \ + src/basic/strv.h \ + src/basic/env-util.c \ + src/basic/env-util.h \ + src/basic/strbuf.c \ + src/basic/strbuf.h \ + src/basic/strxcpyx.c \ + src/basic/strxcpyx.h \ + src/basic/log.c \ + src/basic/log.h \ + src/basic/bus-label.c \ + src/basic/bus-label.h \ + src/basic/ratelimit.h \ + src/basic/ratelimit.c \ + src/basic/exit-status.c \ + src/basic/exit-status.h \ + src/basic/virt.c \ + src/basic/virt.h \ + src/basic/smack-util.c \ + src/basic/smack-util.h \ + src/basic/device-nodes.c \ + src/basic/device-nodes.h \ + src/basic/utf8.c \ + src/basic/utf8.h \ + src/basic/gunicode.c \ + src/basic/gunicode.h \ + src/basic/socket-util.c \ + src/basic/socket-util.h \ + src/basic/in-addr-util.c \ + src/basic/in-addr-util.h \ + src/basic/ether-addr-util.h \ + src/basic/ether-addr-util.c \ + src/basic/replace-var.c \ + src/basic/replace-var.h \ + src/basic/clock-util.c \ + src/basic/clock-util.h \ + src/basic/calendarspec.c \ + src/basic/calendarspec.h \ + src/basic/fileio.c \ + src/basic/fileio.h \ + src/basic/MurmurHash2.c \ + src/basic/MurmurHash2.h \ + src/basic/mkdir.c \ + src/basic/mkdir.h \ + src/basic/cgroup-util.c \ + src/basic/cgroup-util.h \ + src/basic/errno-list.c \ + src/basic/errno-list.h \ + src/basic/af-list.c \ + src/basic/af-list.h \ + src/basic/arphrd-list.c \ + src/basic/arphrd-list.h \ + src/basic/terminal-util.c \ + src/basic/terminal-util.h \ + src/basic/login-util.h \ + src/basic/login-util.c \ + src/basic/cap-list.c \ + src/basic/cap-list.h \ + src/basic/audit-util.c \ + src/basic/audit-util.h \ + src/basic/xml.c \ + src/basic/xml.h \ + src/basic/json.c \ + src/basic/json.h \ + src/basic/barrier.c \ + src/basic/barrier.h \ + src/basic/async.c \ + src/basic/async.h \ + src/basic/memfd-util.c \ + src/basic/memfd-util.h \ + src/basic/process-util.c \ + src/basic/process-util.h \ + src/basic/random-util.c \ + src/basic/random-util.h \ + src/basic/verbs.c \ + src/basic/verbs.h \ + src/basic/sigbus.c \ + src/basic/sigbus.h \ + src/basic/build.h \ + src/basic/socket-label.c \ + src/basic/label.c \ + src/basic/label.h \ + src/basic/btrfs-util.c \ + src/basic/btrfs-util.h \ + src/basic/btrfs-ctree.h \ + src/basic/selinux-util.c \ + src/basic/selinux-util.h \ + src/basic/mkdir-label.c \ + src/basic/fileio-label.c \ + src/basic/fileio-label.h \ + src/basic/rm-rf.c \ + src/basic/rm-rf.h \ + src/basic/copy.c \ + src/basic/copy.h \ + src/basic/alloc-util.h \ + src/basic/alloc-util.c \ + src/basic/formats-util.h \ + src/basic/nss-util.h + +nodist_libbasic_la_SOURCES = \ + src/basic/errno-from-name.h \ + src/basic/errno-to-name.h \ + src/basic/af-from-name.h \ + src/basic/af-to-name.h \ + src/basic/arphrd-from-name.h \ + src/basic/arphrd-to-name.h \ + src/basic/cap-from-name.h \ + src/basic/cap-to-name.h + +libbasic_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(SELINUX_CFLAGS) \ + $(CAP_CFLAGS) \ + -pthread + +libbasic_la_LIBADD = \ + $(SELINUX_LIBS) \ + $(CAP_LIBS) \ + -lrt \ + -lm + +$(outdir)/errno-list.txt: + $(AM_V_GEN)$(CPP) $(AM_CPPFLAGS) -dM -include errno.h - $@ + +$(outdir)/errno-to-name.h: src/basic/errno-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "static const char* const errno_names[] = { "} !/EDEADLOCK/ && !/EWOULDBLOCK/ && !/ENOTSUP/ { printf "[%s] = \"%s\",\n", $$1, $$1 } END{print "};"}' <$< >$@ + + +$(outdir)/af-list.txt: + $(AM_V_GEN)$(CPP) $(AM_CPPFLAGS) -dM -include sys/socket.h - $@ + +$(outdir)/af-to-name.h: src/basic/af-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "static const char* const af_names[] = { "} !/AF_FILE/ && !/AF_ROUTE/ && !/AF_LOCAL/ { printf "[%s] = \"%s\",\n", $$1, $$1 } END{print "};"}' <$< >$@ + + +$(outdir)/arphrd-list.txt: + $(AM_V_GEN)$(CPP) $(AM_CPPFLAGS) -dM -include net/if_arp.h - $@ + +$(outdir)/arphrd-to-name.h: src/basic/arphrd-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "static const char* const arphrd_names[] = { "} !/CISCO/ { printf "[ARPHRD_%s] = \"%s\",\n", $$1, $$1 } END{print "};"}' <$< >$@ + +$(outdir)/arphrd-from-name.gperf: src/basic/arphrd-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct arphrd_name { const char* name; int id; };"; print "%null-strings"; print "%%";} { printf "%s, ARPHRD_%s\n", $$1, $$1 }' <$< >$@ + + +$(outdir)/cap-list.txt: + $(AM_V_GEN)$(CPP) $(AM_CPPFLAGS) -dM -include linux/capability.h -include missing.h - $@ + +$(outdir)/cap-to-name.h: src/basic/cap-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "static const char* const capability_names[] = { "} { printf "[%s] = \"%s\",\n", $$1, tolower($$1) } END{print "};"}' <$< >$@ + +$(outdir)/cap-from-name.gperf: src/basic/cap-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct capability_name { const char* name; int id; };"; print "%null-strings"; print "%%";} { printf "%s, %s\n", $$1, $$1 }' <$< >$@ + +$(outdir)/cap-from-name.h: src/basic/cap-from-name.gperf + $(AM_V_GPERF)$(GPERF) -L ANSI-C -t --ignore-case -N lookup_capability -H hash_capability_name -p -C <$< >$@ + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libbasic/MurmurHash2.c b/src/libbasic/MurmurHash2.c new file mode 100644 index 0000000000..2f4149dbe9 --- /dev/null +++ b/src/libbasic/MurmurHash2.c @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------------- +// MurmurHash2 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +// Note - This code makes a few assumptions about how your machine behaves - + +// 1. We can read a 4-byte value from any address without crashing +// 2. sizeof(int) == 4 + +// And it has a few limitations - + +// 1. It will not work incrementally. +// 2. It will not produce the same results on little-endian and big-endian +// machines. + +#include "MurmurHash2.h" + +//----------------------------------------------------------------------------- +// Platform-specific functions and macros + +// Microsoft Visual Studio + +#if defined(_MSC_VER) + +#define BIG_CONSTANT(x) (x) + +// Other compilers + +#else // defined(_MSC_VER) + +#define BIG_CONSTANT(x) (x##LLU) + +#endif // !defined(_MSC_VER) + +//----------------------------------------------------------------------------- + +uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ) +{ + // 'm' and 'r' are mixing constants generated offline. + // They're not really 'magic', they just happen to work well. + + const uint32_t m = 0x5bd1e995; + const int r = 24; + + // Initialize the hash to a 'random' value + + uint32_t h = seed ^ len; + + // Mix 4 bytes at a time into the hash + + const unsigned char * data = (const unsigned char *)key; + + while(len >= 4) + { + uint32_t k = *(uint32_t*)data; + + k *= m; + k ^= k >> r; + k *= m; + + h *= m; + h ^= k; + + data += 4; + len -= 4; + } + + // Handle the last few bytes of the input array + + switch(len) + { + case 3: h ^= data[2] << 16; + case 2: h ^= data[1] << 8; + case 1: h ^= data[0]; + h *= m; + }; + + // Do a few final mixes of the hash to ensure the last few + // bytes are well-incorporated. + + h ^= h >> 13; + h *= m; + h ^= h >> 15; + + return h; +} diff --git a/src/libbasic/MurmurHash2.h b/src/libbasic/MurmurHash2.h new file mode 100644 index 0000000000..93362dd485 --- /dev/null +++ b/src/libbasic/MurmurHash2.h @@ -0,0 +1,33 @@ +//----------------------------------------------------------------------------- +// MurmurHash2 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +#ifndef _MURMURHASH2_H_ +#define _MURMURHASH2_H_ + +//----------------------------------------------------------------------------- +// Platform-specific functions and macros + +// Microsoft Visual Studio + +#if defined(_MSC_VER) + +typedef unsigned char uint8_t; +typedef unsigned long uint32_t; +typedef unsigned __int64 uint64_t; + +// Other compilers + +#else // defined(_MSC_VER) + +#include + +#endif // !defined(_MSC_VER) + +//----------------------------------------------------------------------------- + +uint32_t MurmurHash2 ( const void * key, int len, uint32_t seed ); + +//----------------------------------------------------------------------------- + +#endif // _MURMURHASH2_H_ diff --git a/src/libbasic/af-list.c b/src/libbasic/af-list.c new file mode 100644 index 0000000000..3fac9c508b --- /dev/null +++ b/src/libbasic/af-list.c @@ -0,0 +1,56 @@ +/*** + 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 . +***/ + +#include +#include + +#include "af-list.h" +#include "macro.h" + +static const struct af_name* lookup_af(register const char *str, register unsigned int len); + +#include "af-from-name.h" +#include "af-to-name.h" + +const char *af_to_name(int id) { + + if (id <= 0) + return NULL; + + if (id >= (int) ELEMENTSOF(af_names)) + return NULL; + + return af_names[id]; +} + +int af_from_name(const char *name) { + const struct af_name *sc; + + assert(name); + + sc = lookup_af(name, strlen(name)); + if (!sc) + return AF_UNSPEC; + + return sc->id; +} + +int af_max(void) { + return ELEMENTSOF(af_names); +} diff --git a/src/libbasic/af-list.h b/src/libbasic/af-list.h new file mode 100644 index 0000000000..135248dc64 --- /dev/null +++ b/src/libbasic/af-list.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +const char *af_to_name(int id); +int af_from_name(const char *name); + +int af_max(void); diff --git a/src/libbasic/alloc-util.c b/src/libbasic/alloc-util.c new file mode 100644 index 0000000000..b540dcddf5 --- /dev/null +++ b/src/libbasic/alloc-util.c @@ -0,0 +1,83 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "alloc-util.h" +#include "macro.h" +#include "util.h" + +void* memdup(const void *p, size_t l) { + void *r; + + assert(p); + + r = malloc(l); + if (!r) + return NULL; + + memcpy(r, p, l); + return r; +} + +void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size) { + size_t a, newalloc; + void *q; + + assert(p); + assert(allocated); + + if (*allocated >= need) + return *p; + + newalloc = MAX(need * 2, 64u / size); + a = newalloc * size; + + /* check for overflows */ + if (a < size * need) + return NULL; + + q = realloc(*p, a); + if (!q) + return NULL; + + *p = q; + *allocated = newalloc; + return q; +} + +void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size) { + size_t prev; + uint8_t *q; + + assert(p); + assert(allocated); + + prev = *allocated; + + q = greedy_realloc(p, allocated, need, size); + if (!q) + return NULL; + + if (*allocated > prev) + memzero(q + prev * size, (*allocated - prev) * size); + + return q; +} diff --git a/src/libbasic/alloc-util.h b/src/libbasic/alloc-util.h new file mode 100644 index 0000000000..679ba7f398 --- /dev/null +++ b/src/libbasic/alloc-util.h @@ -0,0 +1,107 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "macro.h" + +#define new(t, n) ((t*) malloc_multiply(sizeof(t), (n))) + +#define new0(t, n) ((t*) calloc((n), sizeof(t))) + +#define newa(t, n) ((t*) alloca(sizeof(t)*(n))) + +#define newa0(t, n) ((t*) alloca0(sizeof(t)*(n))) + +#define newdup(t, p, n) ((t*) memdup_multiply(p, sizeof(t), (n))) + +#define malloc0(n) (calloc(1, (n))) + +static inline void *mfree(void *memory) { + free(memory); + return NULL; +} + +void* memdup(const void *p, size_t l) _alloc_(2); + +static inline void freep(void *p) { + free(*(void**) p); +} + +#define _cleanup_free_ _cleanup_(freep) + +_malloc_ _alloc_(1, 2) static inline void *malloc_multiply(size_t a, size_t b) { + if (_unlikely_(b != 0 && a > ((size_t) -1) / b)) + return NULL; + + return malloc(a * b); +} + +_alloc_(2, 3) static inline void *realloc_multiply(void *p, size_t a, size_t b) { + if (_unlikely_(b != 0 && a > ((size_t) -1) / b)) + return NULL; + + return realloc(p, a * b); +} + +_alloc_(2, 3) static inline void *memdup_multiply(const void *p, size_t a, size_t b) { + if (_unlikely_(b != 0 && a > ((size_t) -1) / b)) + return NULL; + + return memdup(p, a * b); +} + +void* greedy_realloc(void **p, size_t *allocated, size_t need, size_t size); +void* greedy_realloc0(void **p, size_t *allocated, size_t need, size_t size); + +#define GREEDY_REALLOC(array, allocated, need) \ + greedy_realloc((void**) &(array), &(allocated), (need), sizeof((array)[0])) + +#define GREEDY_REALLOC0(array, allocated, need) \ + greedy_realloc0((void**) &(array), &(allocated), (need), sizeof((array)[0])) + +#define alloca0(n) \ + ({ \ + char *_new_; \ + size_t _len_ = n; \ + _new_ = alloca(_len_); \ + (void *) memset(_new_, 0, _len_); \ + }) + +/* It's not clear what alignment glibc/gcc alloca() guarantee, hence provide a guaranteed safe version */ +#define alloca_align(size, align) \ + ({ \ + void *_ptr_; \ + size_t _mask_ = (align) - 1; \ + _ptr_ = alloca((size) + _mask_); \ + (void*)(((uintptr_t)_ptr_ + _mask_) & ~_mask_); \ + }) + +#define alloca0_align(size, align) \ + ({ \ + void *_new_; \ + size_t _size_ = (size); \ + _new_ = alloca_align(_size_, (align)); \ + (void*)memset(_new_, 0, _size_); \ + }) diff --git a/src/libbasic/arphrd-list.c b/src/libbasic/arphrd-list.c new file mode 100644 index 0000000000..6792d1ee3f --- /dev/null +++ b/src/libbasic/arphrd-list.c @@ -0,0 +1,56 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include + +#include "arphrd-list.h" +#include "macro.h" + +static const struct arphrd_name* lookup_arphrd(register const char *str, register unsigned int len); + +#include "arphrd-from-name.h" +#include "arphrd-to-name.h" + +const char *arphrd_to_name(int id) { + + if (id <= 0) + return NULL; + + if (id >= (int) ELEMENTSOF(arphrd_names)) + return NULL; + + return arphrd_names[id]; +} + +int arphrd_from_name(const char *name) { + const struct arphrd_name *sc; + + assert(name); + + sc = lookup_arphrd(name, strlen(name)); + if (!sc) + return 0; + + return sc->id; +} + +int arphrd_max(void) { + return ELEMENTSOF(arphrd_names); +} diff --git a/src/libbasic/arphrd-list.h b/src/libbasic/arphrd-list.h new file mode 100644 index 0000000000..c0f8758dbe --- /dev/null +++ b/src/libbasic/arphrd-list.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +const char *arphrd_to_name(int id); +int arphrd_from_name(const char *name); + +int arphrd_max(void); diff --git a/src/libbasic/async.c b/src/libbasic/async.c new file mode 100644 index 0000000000..a1f163f27b --- /dev/null +++ b/src/libbasic/async.c @@ -0,0 +1,94 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include + +#include "async.h" +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "util.h" + +int asynchronous_job(void* (*func)(void *p), void *arg) { + pthread_attr_t a; + pthread_t t; + int r; + + /* It kinda sucks that we have to resort to threads to + * implement an asynchronous sync(), but well, such is + * life. + * + * Note that issuing this command right before exiting a + * process will cause the process to wait for the sync() to + * complete. This function hence is nicely asynchronous really + * only in long running processes. */ + + r = pthread_attr_init(&a); + if (r > 0) + return -r; + + r = pthread_attr_setdetachstate(&a, PTHREAD_CREATE_DETACHED); + if (r > 0) + goto finish; + + r = pthread_create(&t, &a, func, arg); + +finish: + pthread_attr_destroy(&a); + return -r; +} + +static void *sync_thread(void *p) { + sync(); + return NULL; +} + +int asynchronous_sync(void) { + log_debug("Spawning new thread for sync"); + + return asynchronous_job(sync_thread, NULL); +} + +static void *close_thread(void *p) { + assert_se(close_nointr(PTR_TO_FD(p)) != -EBADF); + return NULL; +} + +int asynchronous_close(int fd) { + int r; + + /* This is supposed to behave similar to safe_close(), but + * actually invoke close() asynchronously, so that it will + * never block. Ideally the kernel would have an API for this, + * but it doesn't, so we work around it, and hide this as a + * far away as we can. */ + + if (fd >= 0) { + PROTECT_ERRNO; + + r = asynchronous_job(close_thread, FD_TO_PTR(fd)); + if (r < 0) + assert_se(close_nointr(fd) != -EBADF); + } + + return -1; +} diff --git a/src/libbasic/async.h b/src/libbasic/async.h new file mode 100644 index 0000000000..9bd13ff6e0 --- /dev/null +++ b/src/libbasic/async.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + 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 . +***/ + +int asynchronous_job(void* (*func)(void *p), void *arg); + +int asynchronous_sync(void); +int asynchronous_close(int fd); diff --git a/src/libbasic/audit-util.c b/src/libbasic/audit-util.c new file mode 100644 index 0000000000..5741fecdd6 --- /dev/null +++ b/src/libbasic/audit-util.c @@ -0,0 +1,104 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "audit-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "macro.h" +#include "parse-util.h" +#include "process-util.h" +#include "user-util.h" + +int audit_session_from_pid(pid_t pid, uint32_t *id) { + _cleanup_free_ char *s = NULL; + const char *p; + uint32_t u; + int r; + + assert(id); + + /* We don't convert ENOENT to ESRCH here, since we can't + * really distuingish between "audit is not available in the + * kernel" and "the process does not exist", both which will + * result in ENOENT. */ + + p = procfs_file_alloca(pid, "sessionid"); + + r = read_one_line_file(p, &s); + if (r < 0) + return r; + + r = safe_atou32(s, &u); + if (r < 0) + return r; + + if (u == AUDIT_SESSION_INVALID || u <= 0) + return -ENODATA; + + *id = u; + return 0; +} + +int audit_loginuid_from_pid(pid_t pid, uid_t *uid) { + _cleanup_free_ char *s = NULL; + const char *p; + uid_t u; + int r; + + assert(uid); + + p = procfs_file_alloca(pid, "loginuid"); + + r = read_one_line_file(p, &s); + if (r < 0) + return r; + + r = parse_uid(s, &u); + if (r == -ENXIO) /* the UID was -1 */ + return -ENODATA; + if (r < 0) + return r; + + *uid = (uid_t) u; + return 0; +} + +bool use_audit(void) { + static int cached_use = -1; + + if (cached_use < 0) { + int fd; + + fd = socket(AF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_AUDIT); + if (fd < 0) + cached_use = errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT; + else { + cached_use = true; + safe_close(fd); + } + } + + return cached_use; +} diff --git a/src/libbasic/audit-util.h b/src/libbasic/audit-util.h new file mode 100644 index 0000000000..e048503991 --- /dev/null +++ b/src/libbasic/audit-util.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#define AUDIT_SESSION_INVALID ((uint32_t) -1) + +int audit_session_from_pid(pid_t pid, uint32_t *id); +int audit_loginuid_from_pid(pid_t pid, uid_t *uid); + +bool use_audit(void); diff --git a/src/libbasic/barrier.c b/src/libbasic/barrier.c new file mode 100644 index 0000000000..2da633b311 --- /dev/null +++ b/src/libbasic/barrier.c @@ -0,0 +1,415 @@ +/*** + This file is part of systemd. + + Copyright 2014 David Herrmann + + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "barrier.h" +#include "fd-util.h" +#include "macro.h" + +/** + * Barriers + * This barrier implementation provides a simple synchronization method based + * on file-descriptors that can safely be used between threads and processes. A + * barrier object contains 2 shared counters based on eventfd. Both processes + * can now place barriers and wait for the other end to reach a random or + * specific barrier. + * Barriers are numbered, so you can either wait for the other end to reach any + * barrier or the last barrier that you placed. This way, you can use barriers + * for one-way *and* full synchronization. Note that even-though barriers are + * numbered, these numbers are internal and recycled once both sides reached the + * same barrier (implemented as a simple signed counter). It is thus not + * possible to address barriers by their ID. + * + * Barrier-API: Both ends can place as many barriers via barrier_place() as + * they want and each pair of barriers on both sides will be implicitly linked. + * Each side can use the barrier_wait/sync_*() family of calls to wait for the + * other side to place a specific barrier. barrier_wait_next() waits until the + * other side calls barrier_place(). No links between the barriers are + * considered and this simply serves as most basic asynchronous barrier. + * barrier_sync_next() is like barrier_wait_next() and waits for the other side + * to place their next barrier via barrier_place(). However, it only waits for + * barriers that are linked to a barrier we already placed. If the other side + * already placed more barriers than we did, barrier_sync_next() returns + * immediately. + * barrier_sync() extends barrier_sync_next() and waits until the other end + * placed as many barriers via barrier_place() as we did. If they already placed + * as many as we did (or more), it returns immediately. + * + * Additionally to basic barriers, an abortion event is available. + * barrier_abort() places an abortion event that cannot be undone. An abortion + * immediately cancels all placed barriers and replaces them. Any running and + * following wait/sync call besides barrier_wait_abortion() will immediately + * return false on both sides (otherwise, they always return true). + * barrier_abort() can be called multiple times on both ends and will be a + * no-op if already called on this side. + * barrier_wait_abortion() can be used to wait for the other side to call + * barrier_abort() and is the only wait/sync call that does not return + * immediately if we aborted outself. It only returns once the other side + * called barrier_abort(). + * + * Barriers can be used for in-process and inter-process synchronization. + * However, for in-process synchronization you could just use mutexes. + * Therefore, main target is IPC and we require both sides to *not* share the FD + * table. If that's given, barriers provide target tracking: If the remote side + * exit()s, an abortion event is implicitly queued on the other side. This way, + * a sync/wait call will be woken up if the remote side crashed or exited + * unexpectedly. However, note that these abortion events are only queued if the + * barrier-queue has been drained. Therefore, it is safe to place a barrier and + * exit. The other side can safely wait on the barrier even though the exit + * queued an abortion event. Usually, the abortion event would overwrite the + * barrier, however, that's not true for exit-abortion events. Those are only + * queued if the barrier-queue is drained (thus, the receiving side has placed + * more barriers than the remote side). + */ + +/** + * barrier_create() - Initialize a barrier object + * @obj: barrier to initialize + * + * This initializes a barrier object. The caller is responsible of allocating + * the memory and keeping it valid. The memory does not have to be zeroed + * beforehand. + * Two eventfd objects are allocated for each barrier. If allocation fails, an + * error is returned. + * + * If this function fails, the barrier is reset to an invalid state so it is + * safe to call barrier_destroy() on the object regardless whether the + * initialization succeeded or not. + * + * The caller is responsible to destroy the object via barrier_destroy() before + * releasing the underlying memory. + * + * Returns: 0 on success, negative error code on failure. + */ +int barrier_create(Barrier *b) { + _cleanup_(barrier_destroyp) Barrier *staging = b; + int r; + + assert(b); + + b->me = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (b->me < 0) + return -errno; + + b->them = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK); + if (b->them < 0) + return -errno; + + r = pipe2(b->pipe, O_CLOEXEC | O_NONBLOCK); + if (r < 0) + return -errno; + + staging = NULL; + return 0; +} + +/** + * barrier_destroy() - Destroy a barrier object + * @b: barrier to destroy or NULL + * + * This destroys a barrier object that has previously been passed to + * barrier_create(). The object is released and reset to invalid + * state. Therefore, it is safe to call barrier_destroy() multiple + * times or even if barrier_create() failed. However, barrier must be + * always initialized with BARRIER_NULL. + * + * If @b is NULL, this is a no-op. + */ +void barrier_destroy(Barrier *b) { + if (!b) + return; + + b->me = safe_close(b->me); + b->them = safe_close(b->them); + safe_close_pair(b->pipe); + b->barriers = 0; +} + +/** + * barrier_set_role() - Set the local role of the barrier + * @b: barrier to operate on + * @role: role to set on the barrier + * + * This sets the roles on a barrier object. This is needed to know + * which side of the barrier you're on. Usually, the parent creates + * the barrier via barrier_create() and then calls fork() or clone(). + * Therefore, the FDs are duplicated and the child retains the same + * barrier object. + * + * Both sides need to call barrier_set_role() after fork() or clone() + * are done. If this is not done, barriers will not work correctly. + * + * Note that barriers could be supported without fork() or clone(). However, + * this is currently not needed so it hasn't been implemented. + */ +void barrier_set_role(Barrier *b, unsigned int role) { + int fd; + + assert(b); + assert(role == BARRIER_PARENT || role == BARRIER_CHILD); + /* make sure this is only called once */ + assert(b->pipe[0] >= 0 && b->pipe[1] >= 0); + + if (role == BARRIER_PARENT) + b->pipe[1] = safe_close(b->pipe[1]); + else { + b->pipe[0] = safe_close(b->pipe[0]); + + /* swap me/them for children */ + fd = b->me; + b->me = b->them; + b->them = fd; + } +} + +/* places barrier; returns false if we aborted, otherwise true */ +static bool barrier_write(Barrier *b, uint64_t buf) { + ssize_t len; + + /* prevent new sync-points if we already aborted */ + if (barrier_i_aborted(b)) + return false; + + assert(b->me >= 0); + do { + len = write(b->me, &buf, sizeof(buf)); + } while (len < 0 && IN_SET(errno, EAGAIN, EINTR)); + + if (len != sizeof(buf)) + goto error; + + /* lock if we aborted */ + if (buf >= (uint64_t)BARRIER_ABORTION) { + if (barrier_they_aborted(b)) + b->barriers = BARRIER_WE_ABORTED; + else + b->barriers = BARRIER_I_ABORTED; + } else if (!barrier_is_aborted(b)) + b->barriers += buf; + + return !barrier_i_aborted(b); + +error: + /* If there is an unexpected error, we have to make this fatal. There + * is no way we can recover from sync-errors. Therefore, we close the + * pipe-ends and treat this as abortion. The other end will notice the + * pipe-close and treat it as abortion, too. */ + + safe_close_pair(b->pipe); + b->barriers = BARRIER_WE_ABORTED; + return false; +} + +/* waits for barriers; returns false if they aborted, otherwise true */ +static bool barrier_read(Barrier *b, int64_t comp) { + if (barrier_they_aborted(b)) + return false; + + while (b->barriers > comp) { + struct pollfd pfd[2] = { + { .fd = b->pipe[0] >= 0 ? b->pipe[0] : b->pipe[1], + .events = POLLHUP }, + { .fd = b->them, + .events = POLLIN }}; + uint64_t buf; + int r; + + r = poll(pfd, 2, -1); + if (r < 0 && IN_SET(errno, EAGAIN, EINTR)) + continue; + else if (r < 0) + goto error; + + if (pfd[1].revents) { + ssize_t len; + + /* events on @them signal new data for us */ + len = read(b->them, &buf, sizeof(buf)); + if (len < 0 && IN_SET(errno, EAGAIN, EINTR)) + continue; + + if (len != sizeof(buf)) + goto error; + } else if (pfd[0].revents & (POLLHUP | POLLERR | POLLNVAL)) + /* POLLHUP on the pipe tells us the other side exited. + * We treat this as implicit abortion. But we only + * handle it if there's no event on the eventfd. This + * guarantees that exit-abortions do not overwrite real + * barriers. */ + buf = BARRIER_ABORTION; + else + continue; + + /* lock if they aborted */ + if (buf >= (uint64_t)BARRIER_ABORTION) { + if (barrier_i_aborted(b)) + b->barriers = BARRIER_WE_ABORTED; + else + b->barriers = BARRIER_THEY_ABORTED; + } else if (!barrier_is_aborted(b)) + b->barriers -= buf; + } + + return !barrier_they_aborted(b); + +error: + /* If there is an unexpected error, we have to make this fatal. There + * is no way we can recover from sync-errors. Therefore, we close the + * pipe-ends and treat this as abortion. The other end will notice the + * pipe-close and treat it as abortion, too. */ + + safe_close_pair(b->pipe); + b->barriers = BARRIER_WE_ABORTED; + return false; +} + +/** + * barrier_place() - Place a new barrier + * @b: barrier object + * + * This places a new barrier on the barrier object. If either side already + * aborted, this is a no-op and returns "false". Otherwise, the barrier is + * placed and this returns "true". + * + * Returns: true if barrier was placed, false if either side aborted. + */ +bool barrier_place(Barrier *b) { + assert(b); + + if (barrier_is_aborted(b)) + return false; + + barrier_write(b, BARRIER_SINGLE); + return true; +} + +/** + * barrier_abort() - Abort the synchronization + * @b: barrier object to abort + * + * This aborts the barrier-synchronization. If barrier_abort() was already + * called on this side, this is a no-op. Otherwise, the barrier is put into the + * ABORT-state and will stay there. The other side is notified about the + * abortion. Any following attempt to place normal barriers or to wait on normal + * barriers will return immediately as "false". + * + * You can wait for the other side to call barrier_abort(), too. Use + * barrier_wait_abortion() for that. + * + * Returns: false if the other side already aborted, true otherwise. + */ +bool barrier_abort(Barrier *b) { + assert(b); + + barrier_write(b, BARRIER_ABORTION); + return !barrier_they_aborted(b); +} + +/** + * barrier_wait_next() - Wait for the next barrier of the other side + * @b: barrier to operate on + * + * This waits until the other side places its next barrier. This is independent + * of any barrier-links and just waits for any next barrier of the other side. + * + * If either side aborted, this returns false. + * + * Returns: false if either side aborted, true otherwise. + */ +bool barrier_wait_next(Barrier *b) { + assert(b); + + if (barrier_is_aborted(b)) + return false; + + barrier_read(b, b->barriers - 1); + return !barrier_is_aborted(b); +} + +/** + * barrier_wait_abortion() - Wait for the other side to abort + * @b: barrier to operate on + * + * This waits until the other side called barrier_abort(). This can be called + * regardless whether the local side already called barrier_abort() or not. + * + * If the other side has already aborted, this returns immediately. + * + * Returns: false if the local side aborted, true otherwise. + */ +bool barrier_wait_abortion(Barrier *b) { + assert(b); + + barrier_read(b, BARRIER_THEY_ABORTED); + return !barrier_i_aborted(b); +} + +/** + * barrier_sync_next() - Wait for the other side to place a next linked barrier + * @b: barrier to operate on + * + * This is like barrier_wait_next() and waits for the other side to call + * barrier_place(). However, this only waits for linked barriers. That means, if + * the other side already placed more barriers than (or as much as) we did, this + * returns immediately instead of waiting. + * + * If either side aborted, this returns false. + * + * Returns: false if either side aborted, true otherwise. + */ +bool barrier_sync_next(Barrier *b) { + assert(b); + + if (barrier_is_aborted(b)) + return false; + + barrier_read(b, MAX((int64_t)0, b->barriers - 1)); + return !barrier_is_aborted(b); +} + +/** + * barrier_sync() - Wait for the other side to place as many barriers as we did + * @b: barrier to operate on + * + * This is like barrier_sync_next() but waits for the other side to call + * barrier_place() as often as we did (in total). If they already placed as much + * as we did (or more), this returns immediately instead of waiting. + * + * If either side aborted, this returns false. + * + * Returns: false if either side aborted, true otherwise. + */ +bool barrier_sync(Barrier *b) { + assert(b); + + if (barrier_is_aborted(b)) + return false; + + barrier_read(b, 0); + return !barrier_is_aborted(b); +} diff --git a/src/libbasic/barrier.h b/src/libbasic/barrier.h new file mode 100644 index 0000000000..6347fddc4d --- /dev/null +++ b/src/libbasic/barrier.h @@ -0,0 +1,91 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 David Herrmann + + 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 . +***/ + +#include +#include +#include + +#include "macro.h" + +/* See source file for an API description. */ + +typedef struct Barrier Barrier; + +enum { + BARRIER_SINGLE = 1LL, + BARRIER_ABORTION = INT64_MAX, + + /* bias values to store state; keep @WE < @THEY < @I */ + BARRIER_BIAS = INT64_MIN, + BARRIER_WE_ABORTED = BARRIER_BIAS + 1LL, + BARRIER_THEY_ABORTED = BARRIER_BIAS + 2LL, + BARRIER_I_ABORTED = BARRIER_BIAS + 3LL, +}; + +enum { + BARRIER_PARENT, + BARRIER_CHILD, +}; + +struct Barrier { + int me; + int them; + int pipe[2]; + int64_t barriers; +}; + +#define BARRIER_NULL {-1, -1, {-1, -1}, 0} + +int barrier_create(Barrier *obj); +void barrier_destroy(Barrier *b); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Barrier*, barrier_destroy); + +void barrier_set_role(Barrier *b, unsigned int role); + +bool barrier_place(Barrier *b); +bool barrier_abort(Barrier *b); + +bool barrier_wait_next(Barrier *b); +bool barrier_wait_abortion(Barrier *b); +bool barrier_sync_next(Barrier *b); +bool barrier_sync(Barrier *b); + +static inline bool barrier_i_aborted(Barrier *b) { + return b->barriers == BARRIER_I_ABORTED || b->barriers == BARRIER_WE_ABORTED; +} + +static inline bool barrier_they_aborted(Barrier *b) { + return b->barriers == BARRIER_THEY_ABORTED || b->barriers == BARRIER_WE_ABORTED; +} + +static inline bool barrier_we_aborted(Barrier *b) { + return b->barriers == BARRIER_WE_ABORTED; +} + +static inline bool barrier_is_aborted(Barrier *b) { + return b->barriers == BARRIER_I_ABORTED || b->barriers == BARRIER_THEY_ABORTED || b->barriers == BARRIER_WE_ABORTED; +} + +static inline bool barrier_place_and_sync(Barrier *b) { + (void) barrier_place(b); + return barrier_sync(b); +} diff --git a/src/libbasic/bitmap.c b/src/libbasic/bitmap.c new file mode 100644 index 0000000000..ad1fda0198 --- /dev/null +++ b/src/libbasic/bitmap.c @@ -0,0 +1,220 @@ +/*** + This file is part of systemd. + + Copyright 2015 Tom Gundersen + + 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "bitmap.h" +#include "hashmap.h" +#include "macro.h" + +struct Bitmap { + uint64_t *bitmaps; + size_t n_bitmaps; + size_t bitmaps_allocated; +}; + +/* Bitmaps are only meant to store relatively small numbers + * (corresponding to, say, an enum), so it is ok to limit + * the max entry. 64k should be plenty. */ +#define BITMAPS_MAX_ENTRY 0xffff + +/* This indicates that we reached the end of the bitmap */ +#define BITMAP_END ((unsigned) -1) + +#define BITMAP_NUM_TO_OFFSET(n) ((n) / (sizeof(uint64_t) * 8)) +#define BITMAP_NUM_TO_REM(n) ((n) % (sizeof(uint64_t) * 8)) +#define BITMAP_OFFSET_TO_NUM(offset, rem) ((offset) * sizeof(uint64_t) * 8 + (rem)) + +Bitmap *bitmap_new(void) { + return new0(Bitmap, 1); +} + +void bitmap_free(Bitmap *b) { + if (!b) + return; + + free(b->bitmaps); + free(b); +} + +int bitmap_ensure_allocated(Bitmap **b) { + Bitmap *a; + + assert(b); + + if (*b) + return 0; + + a = bitmap_new(); + if (!a) + return -ENOMEM; + + *b = a; + + return 0; +} + +int bitmap_set(Bitmap *b, unsigned n) { + uint64_t bitmask; + unsigned offset; + + assert(b); + + /* we refuse to allocate huge bitmaps */ + if (n > BITMAPS_MAX_ENTRY) + return -ERANGE; + + offset = BITMAP_NUM_TO_OFFSET(n); + + if (offset >= b->n_bitmaps) { + if (!GREEDY_REALLOC0(b->bitmaps, b->bitmaps_allocated, offset + 1)) + return -ENOMEM; + + b->n_bitmaps = offset + 1; + } + + bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); + + b->bitmaps[offset] |= bitmask; + + return 0; +} + +void bitmap_unset(Bitmap *b, unsigned n) { + uint64_t bitmask; + unsigned offset; + + if (!b) + return; + + offset = BITMAP_NUM_TO_OFFSET(n); + + if (offset >= b->n_bitmaps) + return; + + bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); + + b->bitmaps[offset] &= ~bitmask; +} + +bool bitmap_isset(Bitmap *b, unsigned n) { + uint64_t bitmask; + unsigned offset; + + if (!b) + return false; + + offset = BITMAP_NUM_TO_OFFSET(n); + + if (offset >= b->n_bitmaps) + return false; + + bitmask = UINT64_C(1) << BITMAP_NUM_TO_REM(n); + + return !!(b->bitmaps[offset] & bitmask); +} + +bool bitmap_isclear(Bitmap *b) { + unsigned i; + + if (!b) + return true; + + for (i = 0; i < b->n_bitmaps; i++) + if (b->bitmaps[i] != 0) + return false; + + return true; +} + +void bitmap_clear(Bitmap *b) { + + if (!b) + return; + + b->bitmaps = mfree(b->bitmaps); + b->n_bitmaps = 0; + b->bitmaps_allocated = 0; +} + +bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n) { + uint64_t bitmask; + unsigned offset, rem; + + assert(i); + assert(n); + + if (!b || i->idx == BITMAP_END) + return false; + + offset = BITMAP_NUM_TO_OFFSET(i->idx); + rem = BITMAP_NUM_TO_REM(i->idx); + bitmask = UINT64_C(1) << rem; + + for (; offset < b->n_bitmaps; offset ++) { + if (b->bitmaps[offset]) { + for (; bitmask; bitmask <<= 1, rem ++) { + if (b->bitmaps[offset] & bitmask) { + *n = BITMAP_OFFSET_TO_NUM(offset, rem); + i->idx = *n + 1; + + return true; + } + } + } + + rem = 0; + bitmask = 1; + } + + i->idx = BITMAP_END; + + return false; +} + +bool bitmap_equal(Bitmap *a, Bitmap *b) { + size_t common_n_bitmaps; + Bitmap *c; + unsigned i; + + if (a == b) + return true; + + if (!a != !b) + return false; + + if (!a) + return true; + + common_n_bitmaps = MIN(a->n_bitmaps, b->n_bitmaps); + if (memcmp(a->bitmaps, b->bitmaps, sizeof(uint64_t) * common_n_bitmaps) != 0) + return false; + + c = a->n_bitmaps > b->n_bitmaps ? a : b; + for (i = common_n_bitmaps; i < c->n_bitmaps; i++) + if (c->bitmaps[i] != 0) + return false; + + return true; +} diff --git a/src/libbasic/bitmap.h b/src/libbasic/bitmap.h new file mode 100644 index 0000000000..f5f8f2f018 --- /dev/null +++ b/src/libbasic/bitmap.h @@ -0,0 +1,50 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Tom Gundersen + + 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 . +***/ + +#include + +#include "hashmap.h" +#include "macro.h" + +typedef struct Bitmap Bitmap; + +Bitmap *bitmap_new(void); + +void bitmap_free(Bitmap *b); + +int bitmap_ensure_allocated(Bitmap **b); + +int bitmap_set(Bitmap *b, unsigned n); +void bitmap_unset(Bitmap *b, unsigned n); +bool bitmap_isset(Bitmap *b, unsigned n); +bool bitmap_isclear(Bitmap *b); +void bitmap_clear(Bitmap *b); + +bool bitmap_iterate(Bitmap *b, Iterator *i, unsigned *n); + +bool bitmap_equal(Bitmap *a, Bitmap *b); + +#define BITMAP_FOREACH(n, b, i) \ + for ((i).idx = 0; bitmap_iterate((b), &(i), (unsigned*)&(n)); ) + +DEFINE_TRIVIAL_CLEANUP_FUNC(Bitmap*, bitmap_free); + +#define _cleanup_bitmap_free_ _cleanup_(bitmap_freep) diff --git a/src/libbasic/blkid-util.h b/src/libbasic/blkid-util.h new file mode 100644 index 0000000000..7aa75eb091 --- /dev/null +++ b/src/libbasic/blkid-util.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#ifdef HAVE_BLKID +#include +#endif + +#include "util.h" + +#ifdef HAVE_BLKID +DEFINE_TRIVIAL_CLEANUP_FUNC(blkid_probe, blkid_free_probe); +#define _cleanup_blkid_free_probe_ _cleanup_(blkid_free_probep) +#endif diff --git a/src/libbasic/btrfs-ctree.h b/src/libbasic/btrfs-ctree.h new file mode 100644 index 0000000000..66bdf9736e --- /dev/null +++ b/src/libbasic/btrfs-ctree.h @@ -0,0 +1,96 @@ +#pragma once + +#include "macro.h" +#include "sparse-endian.h" + +/* Stolen from btrfs' ctree.h */ + +struct btrfs_timespec { + le64_t sec; + le32_t nsec; +} _packed_; + +struct btrfs_disk_key { + le64_t objectid; + uint8_t type; + le64_t offset; +} _packed_; + +struct btrfs_inode_item { + le64_t generation; + le64_t transid; + le64_t size; + le64_t nbytes; + le64_t block_group; + le32_t nlink; + le32_t uid; + le32_t gid; + le32_t mode; + le64_t rdev; + le64_t flags; + le64_t sequence; + le64_t reserved[4]; + struct btrfs_timespec atime; + struct btrfs_timespec ctime; + struct btrfs_timespec mtime; + struct btrfs_timespec otime; +} _packed_; + +struct btrfs_root_item { + struct btrfs_inode_item inode; + le64_t generation; + le64_t root_dirid; + le64_t bytenr; + le64_t byte_limit; + le64_t bytes_used; + le64_t last_snapshot; + le64_t flags; + le32_t refs; + struct btrfs_disk_key drop_progress; + uint8_t drop_level; + uint8_t level; + le64_t generation_v2; + uint8_t uuid[BTRFS_UUID_SIZE]; + uint8_t parent_uuid[BTRFS_UUID_SIZE]; + uint8_t received_uuid[BTRFS_UUID_SIZE]; + le64_t ctransid; + le64_t otransid; + le64_t stransid; + le64_t rtransid; + struct btrfs_timespec ctime; + struct btrfs_timespec otime; + struct btrfs_timespec stime; + struct btrfs_timespec rtime; + le64_t reserved[8]; +} _packed_; + +#define BTRFS_ROOT_SUBVOL_RDONLY (1ULL << 0) + +struct btrfs_qgroup_info_item { + le64_t generation; + le64_t rfer; + le64_t rfer_cmpr; + le64_t excl; + le64_t excl_cmpr; +} _packed_; + +#define BTRFS_QGROUP_LIMIT_MAX_RFER (1ULL << 0) +#define BTRFS_QGROUP_LIMIT_MAX_EXCL (1ULL << 1) +#define BTRFS_QGROUP_LIMIT_RSV_RFER (1ULL << 2) +#define BTRFS_QGROUP_LIMIT_RSV_EXCL (1ULL << 3) +#define BTRFS_QGROUP_LIMIT_RFER_CMPR (1ULL << 4) +#define BTRFS_QGROUP_LIMIT_EXCL_CMPR (1ULL << 5) + +struct btrfs_qgroup_limit_item { + le64_t flags; + le64_t max_rfer; + le64_t max_excl; + le64_t rsv_rfer; + le64_t rsv_excl; +} _packed_; + +struct btrfs_root_ref { + le64_t dirid; + le64_t sequence; + le16_t name_len; +} _packed_; diff --git a/src/libbasic/btrfs-util.c b/src/libbasic/btrfs-util.c new file mode 100644 index 0000000000..359d85f2e8 --- /dev/null +++ b/src/libbasic/btrfs-util.c @@ -0,0 +1,2075 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LINUX_BTRFS_H +#include +#endif + +#include "alloc-util.h" +#include "btrfs-ctree.h" +#include "btrfs-util.h" +#include "copy.h" +#include "fd-util.h" +#include "fileio.h" +#include "io-util.h" +#include "macro.h" +#include "missing.h" +#include "path-util.h" +#include "selinux-util.h" +#include "smack-util.h" +#include "sparse-endian.h" +#include "stat-util.h" +#include "string-util.h" +#include "time-util.h" +#include "util.h" + +/* WARNING: Be careful with file system ioctls! When we get an fd, we + * need to make sure it either refers to only a regular file or + * directory, or that it is located on btrfs, before invoking any + * btrfs ioctls. The ioctl numbers are reused by some device drivers + * (such as DRM), and hence might have bad effects when invoked on + * device nodes (that reference drivers) rather than fds to normal + * files or directories. */ + +static int validate_subvolume_name(const char *name) { + + if (!filename_is_valid(name)) + return -EINVAL; + + if (strlen(name) > BTRFS_SUBVOL_NAME_MAX) + return -E2BIG; + + return 0; +} + +static int open_parent(const char *path, int flags) { + _cleanup_free_ char *parent = NULL; + int fd; + + assert(path); + + parent = dirname_malloc(path); + if (!parent) + return -ENOMEM; + + fd = open(parent, flags); + if (fd < 0) + return -errno; + + return fd; +} + +static int extract_subvolume_name(const char *path, const char **subvolume) { + const char *fn; + int r; + + assert(path); + assert(subvolume); + + fn = basename(path); + + r = validate_subvolume_name(fn); + if (r < 0) + return r; + + *subvolume = fn; + return 0; +} + +int btrfs_is_filesystem(int fd) { + struct statfs sfs; + + assert(fd >= 0); + + if (fstatfs(fd, &sfs) < 0) + return -errno; + + return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC); +} + +int btrfs_is_subvol_fd(int fd) { + struct stat st; + + assert(fd >= 0); + + /* On btrfs subvolumes always have the inode 256 */ + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode) || st.st_ino != 256) + return 0; + + return btrfs_is_filesystem(fd); +} + +int btrfs_is_subvol(const char *path) { + _cleanup_close_ int fd = -1; + + assert(path); + + fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return -errno; + + return btrfs_is_subvol_fd(fd); +} + +int btrfs_subvol_make(const char *path) { + struct btrfs_ioctl_vol_args args = {}; + _cleanup_close_ int fd = -1; + const char *subvolume; + int r; + + assert(path); + + r = extract_subvolume_name(path, &subvolume); + if (r < 0) + return r; + + fd = open_parent(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return fd; + + strncpy(args.name, subvolume, sizeof(args.name)-1); + + if (ioctl(fd, BTRFS_IOC_SUBVOL_CREATE, &args) < 0) + return -errno; + + return 0; +} + +int btrfs_subvol_make_label(const char *path) { + int r; + + assert(path); + + r = mac_selinux_create_file_prepare(path, S_IFDIR); + if (r < 0) + return r; + + r = btrfs_subvol_make(path); + mac_selinux_create_file_clear(); + + if (r < 0) + return r; + + return mac_smack_fix(path, false, false); +} + +int btrfs_subvol_set_read_only_fd(int fd, bool b) { + uint64_t flags, nflags; + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode) || st.st_ino != 256) + return -EINVAL; + + if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0) + return -errno; + + if (b) + nflags = flags | BTRFS_SUBVOL_RDONLY; + else + nflags = flags & ~BTRFS_SUBVOL_RDONLY; + + if (flags == nflags) + return 0; + + if (ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &nflags) < 0) + return -errno; + + return 0; +} + +int btrfs_subvol_set_read_only(const char *path, bool b) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return -errno; + + return btrfs_subvol_set_read_only_fd(fd, b); +} + +int btrfs_subvol_get_read_only_fd(int fd) { + uint64_t flags; + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode) || st.st_ino != 256) + return -EINVAL; + + if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0) + return -errno; + + return !!(flags & BTRFS_SUBVOL_RDONLY); +} + +int btrfs_reflink(int infd, int outfd) { + struct stat st; + int r; + + assert(infd >= 0); + assert(outfd >= 0); + + /* Make sure we invoke the ioctl on a regular file, so that no + * device driver accidentally gets it. */ + + if (fstat(outfd, &st) < 0) + return -errno; + + if (!S_ISREG(st.st_mode)) + return -EINVAL; + + r = ioctl(outfd, BTRFS_IOC_CLONE, infd); + if (r < 0) + return -errno; + + return 0; +} + +int btrfs_clone_range(int infd, uint64_t in_offset, int outfd, uint64_t out_offset, uint64_t sz) { + struct btrfs_ioctl_clone_range_args args = { + .src_fd = infd, + .src_offset = in_offset, + .src_length = sz, + .dest_offset = out_offset, + }; + struct stat st; + int r; + + assert(infd >= 0); + assert(outfd >= 0); + assert(sz > 0); + + if (fstat(outfd, &st) < 0) + return -errno; + + if (!S_ISREG(st.st_mode)) + return -EINVAL; + + r = ioctl(outfd, BTRFS_IOC_CLONE_RANGE, &args); + if (r < 0) + return -errno; + + return 0; +} + +int btrfs_get_block_device_fd(int fd, dev_t *dev) { + struct btrfs_ioctl_fs_info_args fsi = {}; + uint64_t id; + int r; + + assert(fd >= 0); + assert(dev); + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + + if (ioctl(fd, BTRFS_IOC_FS_INFO, &fsi) < 0) + return -errno; + + /* We won't do this for btrfs RAID */ + if (fsi.num_devices != 1) + return 0; + + for (id = 1; id <= fsi.max_id; id++) { + struct btrfs_ioctl_dev_info_args di = { + .devid = id, + }; + struct stat st; + + if (ioctl(fd, BTRFS_IOC_DEV_INFO, &di) < 0) { + if (errno == ENODEV) + continue; + + return -errno; + } + + if (stat((char*) di.path, &st) < 0) + return -errno; + + if (!S_ISBLK(st.st_mode)) + return -ENODEV; + + if (major(st.st_rdev) == 0) + return -ENODEV; + + *dev = st.st_rdev; + return 1; + } + + return -ENODEV; +} + +int btrfs_get_block_device(const char *path, dev_t *dev) { + _cleanup_close_ int fd = -1; + + assert(path); + assert(dev); + + fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + return btrfs_get_block_device_fd(fd, dev); +} + +int btrfs_subvol_get_id_fd(int fd, uint64_t *ret) { + struct btrfs_ioctl_ino_lookup_args args = { + .objectid = BTRFS_FIRST_FREE_OBJECTID + }; + int r; + + assert(fd >= 0); + assert(ret); + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + + if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &args) < 0) + return -errno; + + *ret = args.treeid; + return 0; +} + +int btrfs_subvol_get_id(int fd, const char *subvol, uint64_t *ret) { + _cleanup_close_ int subvol_fd = -1; + + assert(fd >= 0); + assert(ret); + + subvol_fd = openat(fd, subvol, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (subvol_fd < 0) + return -errno; + + return btrfs_subvol_get_id_fd(subvol_fd, ret); +} + +static bool btrfs_ioctl_search_args_inc(struct btrfs_ioctl_search_args *args) { + assert(args); + + /* the objectid, type, offset together make up the btrfs key, + * which is considered a single 136byte integer when + * comparing. This call increases the counter by one, dealing + * with the overflow between the overflows */ + + if (args->key.min_offset < (uint64_t) -1) { + args->key.min_offset++; + return true; + } + + if (args->key.min_type < (uint8_t) -1) { + args->key.min_type++; + args->key.min_offset = 0; + return true; + } + + if (args->key.min_objectid < (uint64_t) -1) { + args->key.min_objectid++; + args->key.min_offset = 0; + args->key.min_type = 0; + return true; + } + + return 0; +} + +static void btrfs_ioctl_search_args_set(struct btrfs_ioctl_search_args *args, const struct btrfs_ioctl_search_header *h) { + assert(args); + assert(h); + + args->key.min_objectid = h->objectid; + args->key.min_type = h->type; + args->key.min_offset = h->offset; +} + +static int btrfs_ioctl_search_args_compare(const struct btrfs_ioctl_search_args *args) { + assert(args); + + /* Compare min and max */ + + if (args->key.min_objectid < args->key.max_objectid) + return -1; + if (args->key.min_objectid > args->key.max_objectid) + return 1; + + if (args->key.min_type < args->key.max_type) + return -1; + if (args->key.min_type > args->key.max_type) + return 1; + + if (args->key.min_offset < args->key.max_offset) + return -1; + if (args->key.min_offset > args->key.max_offset) + return 1; + + return 0; +} + +#define FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) \ + for ((i) = 0, \ + (sh) = (const struct btrfs_ioctl_search_header*) (args).buf; \ + (i) < (args).key.nr_items; \ + (i)++, \ + (sh) = (const struct btrfs_ioctl_search_header*) ((uint8_t*) (sh) + sizeof(struct btrfs_ioctl_search_header) + (sh)->len)) + +#define BTRFS_IOCTL_SEARCH_HEADER_BODY(sh) \ + ((void*) ((uint8_t*) sh + sizeof(struct btrfs_ioctl_search_header))) + +int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *ret) { + struct btrfs_ioctl_search_args args = { + /* Tree of tree roots */ + .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, + + /* Look precisely for the subvolume items */ + .key.min_type = BTRFS_ROOT_ITEM_KEY, + .key.max_type = BTRFS_ROOT_ITEM_KEY, + + .key.min_offset = 0, + .key.max_offset = (uint64_t) -1, + + /* No restrictions on the other components */ + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + bool found = false; + int r; + + assert(fd >= 0); + assert(ret); + + if (subvol_id == 0) { + r = btrfs_subvol_get_id_fd(fd, &subvol_id); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } + + args.key.min_objectid = args.key.max_objectid = subvol_id; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + return -errno; + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + + const struct btrfs_root_item *ri; + + /* Make sure we start the next search at least from this entry */ + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->objectid != subvol_id) + continue; + if (sh->type != BTRFS_ROOT_ITEM_KEY) + continue; + + /* Older versions of the struct lacked the otime setting */ + if (sh->len < offsetof(struct btrfs_root_item, otime) + sizeof(struct btrfs_timespec)) + continue; + + ri = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); + + ret->otime = (usec_t) le64toh(ri->otime.sec) * USEC_PER_SEC + + (usec_t) le32toh(ri->otime.nsec) / NSEC_PER_USEC; + + ret->subvol_id = subvol_id; + ret->read_only = !!(le64toh(ri->flags) & BTRFS_ROOT_SUBVOL_RDONLY); + + assert_cc(sizeof(ri->uuid) == sizeof(ret->uuid)); + memcpy(&ret->uuid, ri->uuid, sizeof(ret->uuid)); + memcpy(&ret->parent_uuid, ri->parent_uuid, sizeof(ret->parent_uuid)); + + found = true; + goto finish; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + +finish: + if (!found) + return -ENODATA; + + return 0; +} + +int btrfs_qgroup_get_quota_fd(int fd, uint64_t qgroupid, BtrfsQuotaInfo *ret) { + + struct btrfs_ioctl_search_args args = { + /* Tree of quota items */ + .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, + + /* The object ID is always 0 */ + .key.min_objectid = 0, + .key.max_objectid = 0, + + /* Look precisely for the quota items */ + .key.min_type = BTRFS_QGROUP_STATUS_KEY, + .key.max_type = BTRFS_QGROUP_LIMIT_KEY, + + /* No restrictions on the other components */ + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + bool found_info = false, found_limit = false; + int r; + + assert(fd >= 0); + assert(ret); + + if (qgroupid == 0) { + r = btrfs_subvol_get_id_fd(fd, &qgroupid); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } + + args.key.min_offset = args.key.max_offset = qgroupid; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) { + if (errno == ENOENT) /* quota tree is missing: quota disabled */ + break; + + return -errno; + } + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + + /* Make sure we start the next search at least from this entry */ + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->objectid != 0) + continue; + if (sh->offset != qgroupid) + continue; + + if (sh->type == BTRFS_QGROUP_INFO_KEY) { + const struct btrfs_qgroup_info_item *qii = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); + + ret->referenced = le64toh(qii->rfer); + ret->exclusive = le64toh(qii->excl); + + found_info = true; + + } else if (sh->type == BTRFS_QGROUP_LIMIT_KEY) { + const struct btrfs_qgroup_limit_item *qli = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); + + if (le64toh(qli->flags) & BTRFS_QGROUP_LIMIT_MAX_RFER) + ret->referenced_max = le64toh(qli->max_rfer); + else + ret->referenced_max = (uint64_t) -1; + + if (le64toh(qli->flags) & BTRFS_QGROUP_LIMIT_MAX_EXCL) + ret->exclusive_max = le64toh(qli->max_excl); + else + ret->exclusive_max = (uint64_t) -1; + + found_limit = true; + } + + if (found_info && found_limit) + goto finish; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + +finish: + if (!found_limit && !found_info) + return -ENODATA; + + if (!found_info) { + ret->referenced = (uint64_t) -1; + ret->exclusive = (uint64_t) -1; + } + + if (!found_limit) { + ret->referenced_max = (uint64_t) -1; + ret->exclusive_max = (uint64_t) -1; + } + + return 0; +} + +int btrfs_qgroup_get_quota(const char *path, uint64_t qgroupid, BtrfsQuotaInfo *ret) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_qgroup_get_quota_fd(fd, qgroupid, ret); +} + +int btrfs_subvol_find_subtree_qgroup(int fd, uint64_t subvol_id, uint64_t *ret) { + uint64_t level, lowest = (uint64_t) -1, lowest_qgroupid = 0; + _cleanup_free_ uint64_t *qgroups = NULL; + int r, n, i; + + assert(fd >= 0); + assert(ret); + + /* This finds the "subtree" qgroup for a specific + * subvolume. This only works for subvolumes that have been + * prepared with btrfs_subvol_auto_qgroup_fd() with + * insert_intermediary_qgroup=true (or equivalent). For others + * it will return the leaf qgroup instead. The two cases may + * be distuingished via the return value, which is 1 in case + * an appropriate "subtree" qgroup was found, and 0 + * otherwise. */ + + if (subvol_id == 0) { + r = btrfs_subvol_get_id_fd(fd, &subvol_id); + if (r < 0) + return r; + } + + r = btrfs_qgroupid_split(subvol_id, &level, NULL); + if (r < 0) + return r; + if (level != 0) /* Input must be a leaf qgroup */ + return -EINVAL; + + n = btrfs_qgroup_find_parents(fd, subvol_id, &qgroups); + if (n < 0) + return n; + + for (i = 0; i < n; i++) { + uint64_t id; + + r = btrfs_qgroupid_split(qgroups[i], &level, &id); + if (r < 0) + return r; + + if (id != subvol_id) + continue; + + if (lowest == (uint64_t) -1 || level < lowest) { + lowest_qgroupid = qgroups[i]; + lowest = level; + } + } + + if (lowest == (uint64_t) -1) { + /* No suitable higher-level qgroup found, let's return + * the leaf qgroup instead, and indicate that with the + * return value. */ + + *ret = subvol_id; + return 0; + } + + *ret = lowest_qgroupid; + return 1; +} + +int btrfs_subvol_get_subtree_quota_fd(int fd, uint64_t subvol_id, BtrfsQuotaInfo *ret) { + uint64_t qgroupid; + int r; + + assert(fd >= 0); + assert(ret); + + /* This determines the quota data of the qgroup with the + * lowest level, that shares the id part with the specified + * subvolume. This is useful for determining the quota data + * for entire subvolume subtrees, as long as the subtrees have + * been set up with btrfs_qgroup_subvol_auto_fd() or in a + * compatible way */ + + r = btrfs_subvol_find_subtree_qgroup(fd, subvol_id, &qgroupid); + if (r < 0) + return r; + + return btrfs_qgroup_get_quota_fd(fd, qgroupid, ret); +} + +int btrfs_subvol_get_subtree_quota(const char *path, uint64_t subvol_id, BtrfsQuotaInfo *ret) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_subvol_get_subtree_quota_fd(fd, subvol_id, ret); +} + +int btrfs_defrag_fd(int fd) { + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISREG(st.st_mode)) + return -EINVAL; + + if (ioctl(fd, BTRFS_IOC_DEFRAG, NULL) < 0) + return -errno; + + return 0; +} + +int btrfs_defrag(const char *p) { + _cleanup_close_ int fd = -1; + + fd = open(p, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_defrag_fd(fd); +} + +int btrfs_quota_enable_fd(int fd, bool b) { + struct btrfs_ioctl_quota_ctl_args args = { + .cmd = b ? BTRFS_QUOTA_CTL_ENABLE : BTRFS_QUOTA_CTL_DISABLE, + }; + int r; + + assert(fd >= 0); + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + + if (ioctl(fd, BTRFS_IOC_QUOTA_CTL, &args) < 0) + return -errno; + + return 0; +} + +int btrfs_quota_enable(const char *path, bool b) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_quota_enable_fd(fd, b); +} + +int btrfs_qgroup_set_limit_fd(int fd, uint64_t qgroupid, uint64_t referenced_max) { + + struct btrfs_ioctl_qgroup_limit_args args = { + .lim.max_rfer = referenced_max, + .lim.flags = BTRFS_QGROUP_LIMIT_MAX_RFER, + }; + unsigned c; + int r; + + assert(fd >= 0); + + if (qgroupid == 0) { + r = btrfs_subvol_get_id_fd(fd, &qgroupid); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } + + args.qgroupid = qgroupid; + + for (c = 0;; c++) { + if (ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &args) < 0) { + + if (errno == EBUSY && c < 10) { + (void) btrfs_quota_scan_wait(fd); + continue; + } + + return -errno; + } + + break; + } + + return 0; +} + +int btrfs_qgroup_set_limit(const char *path, uint64_t qgroupid, uint64_t referenced_max) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_qgroup_set_limit_fd(fd, qgroupid, referenced_max); +} + +int btrfs_subvol_set_subtree_quota_limit_fd(int fd, uint64_t subvol_id, uint64_t referenced_max) { + uint64_t qgroupid; + int r; + + assert(fd >= 0); + + r = btrfs_subvol_find_subtree_qgroup(fd, subvol_id, &qgroupid); + if (r < 0) + return r; + + return btrfs_qgroup_set_limit_fd(fd, qgroupid, referenced_max); +} + +int btrfs_subvol_set_subtree_quota_limit(const char *path, uint64_t subvol_id, uint64_t referenced_max) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_subvol_set_subtree_quota_limit_fd(fd, subvol_id, referenced_max); +} + +int btrfs_resize_loopback_fd(int fd, uint64_t new_size, bool grow_only) { + struct btrfs_ioctl_vol_args args = {}; + _cleanup_free_ char *p = NULL, *loop = NULL, *backing = NULL; + _cleanup_close_ int loop_fd = -1, backing_fd = -1; + struct stat st; + dev_t dev = 0; + int r; + + /* In contrast to btrfs quota ioctls ftruncate() cannot make sense of "infinity" or file sizes > 2^31 */ + if (!FILE_SIZE_VALID(new_size)) + return -EINVAL; + + /* btrfs cannot handle file systems < 16M, hence use this as minimum */ + if (new_size < 16*1024*1024) + new_size = 16*1024*1024; + + r = btrfs_get_block_device_fd(fd, &dev); + if (r < 0) + return r; + if (r == 0) + return -ENODEV; + + if (asprintf(&p, "/sys/dev/block/%u:%u/loop/backing_file", major(dev), minor(dev)) < 0) + return -ENOMEM; + r = read_one_line_file(p, &backing); + if (r == -ENOENT) + return -ENODEV; + if (r < 0) + return r; + if (isempty(backing) || !path_is_absolute(backing)) + return -ENODEV; + + backing_fd = open(backing, O_RDWR|O_CLOEXEC|O_NOCTTY); + if (backing_fd < 0) + return -errno; + + if (fstat(backing_fd, &st) < 0) + return -errno; + if (!S_ISREG(st.st_mode)) + return -ENODEV; + + if (new_size == (uint64_t) st.st_size) + return 0; + + if (grow_only && new_size < (uint64_t) st.st_size) + return -EINVAL; + + if (asprintf(&loop, "/dev/block/%u:%u", major(dev), minor(dev)) < 0) + return -ENOMEM; + loop_fd = open(loop, O_RDWR|O_CLOEXEC|O_NOCTTY); + if (loop_fd < 0) + return -errno; + + if (snprintf(args.name, sizeof(args.name), "%" PRIu64, new_size) >= (int) sizeof(args.name)) + return -EINVAL; + + if (new_size < (uint64_t) st.st_size) { + /* Decrease size: first decrease btrfs size, then shorten loopback */ + if (ioctl(fd, BTRFS_IOC_RESIZE, &args) < 0) + return -errno; + } + + if (ftruncate(backing_fd, new_size) < 0) + return -errno; + + if (ioctl(loop_fd, LOOP_SET_CAPACITY, 0) < 0) + return -errno; + + if (new_size > (uint64_t) st.st_size) { + /* Increase size: first enlarge loopback, then increase btrfs size */ + if (ioctl(fd, BTRFS_IOC_RESIZE, &args) < 0) + return -errno; + } + + /* Make sure the free disk space is correctly updated for both file systems */ + (void) fsync(fd); + (void) fsync(backing_fd); + + return 1; +} + +int btrfs_resize_loopback(const char *p, uint64_t new_size, bool grow_only) { + _cleanup_close_ int fd = -1; + + fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + return btrfs_resize_loopback_fd(fd, new_size, grow_only); +} + +int btrfs_qgroupid_make(uint64_t level, uint64_t id, uint64_t *ret) { + assert(ret); + + if (level >= (UINT64_C(1) << (64 - BTRFS_QGROUP_LEVEL_SHIFT))) + return -EINVAL; + + if (id >= (UINT64_C(1) << BTRFS_QGROUP_LEVEL_SHIFT)) + return -EINVAL; + + *ret = (level << BTRFS_QGROUP_LEVEL_SHIFT) | id; + return 0; +} + +int btrfs_qgroupid_split(uint64_t qgroupid, uint64_t *level, uint64_t *id) { + assert(level || id); + + if (level) + *level = qgroupid >> BTRFS_QGROUP_LEVEL_SHIFT; + + if (id) + *id = qgroupid & ((UINT64_C(1) << BTRFS_QGROUP_LEVEL_SHIFT) - 1); + + return 0; +} + +static int qgroup_create_or_destroy(int fd, bool b, uint64_t qgroupid) { + + struct btrfs_ioctl_qgroup_create_args args = { + .create = b, + .qgroupid = qgroupid, + }; + unsigned c; + int r; + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (r == 0) + return -ENOTTY; + + for (c = 0;; c++) { + if (ioctl(fd, BTRFS_IOC_QGROUP_CREATE, &args) < 0) { + + /* If quota is not enabled, we get EINVAL. Turn this into a recognizable error */ + if (errno == EINVAL) + return -ENOPROTOOPT; + + if (errno == EBUSY && c < 10) { + (void) btrfs_quota_scan_wait(fd); + continue; + } + + return -errno; + } + + break; + } + + return 0; +} + +int btrfs_qgroup_create(int fd, uint64_t qgroupid) { + return qgroup_create_or_destroy(fd, true, qgroupid); +} + +int btrfs_qgroup_destroy(int fd, uint64_t qgroupid) { + return qgroup_create_or_destroy(fd, false, qgroupid); +} + +int btrfs_qgroup_destroy_recursive(int fd, uint64_t qgroupid) { + _cleanup_free_ uint64_t *qgroups = NULL; + uint64_t subvol_id; + int i, n, r; + + /* Destroys the specified qgroup, but unassigns it from all + * its parents first. Also, it recursively destroys all + * qgroups it is assgined to that have the same id part of the + * qgroupid as the specified group. */ + + r = btrfs_qgroupid_split(qgroupid, NULL, &subvol_id); + if (r < 0) + return r; + + n = btrfs_qgroup_find_parents(fd, qgroupid, &qgroups); + if (n < 0) + return n; + + for (i = 0; i < n; i++) { + uint64_t id; + + r = btrfs_qgroupid_split(qgroups[i], NULL, &id); + if (r < 0) + return r; + + r = btrfs_qgroup_unassign(fd, qgroupid, qgroups[i]); + if (r < 0) + return r; + + if (id != subvol_id) + continue; + + /* The parent qgroupid shares the same id part with + * us? If so, destroy it too. */ + + (void) btrfs_qgroup_destroy_recursive(fd, qgroups[i]); + } + + return btrfs_qgroup_destroy(fd, qgroupid); +} + +int btrfs_quota_scan_start(int fd) { + struct btrfs_ioctl_quota_rescan_args args = {}; + + assert(fd >= 0); + + if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN, &args) < 0) + return -errno; + + return 0; +} + +int btrfs_quota_scan_wait(int fd) { + assert(fd >= 0); + + if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN_WAIT) < 0) + return -errno; + + return 0; +} + +int btrfs_quota_scan_ongoing(int fd) { + struct btrfs_ioctl_quota_rescan_args args = {}; + + assert(fd >= 0); + + if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN_STATUS, &args) < 0) + return -errno; + + return !!args.flags; +} + +static int qgroup_assign_or_unassign(int fd, bool b, uint64_t child, uint64_t parent) { + struct btrfs_ioctl_qgroup_assign_args args = { + .assign = b, + .src = child, + .dst = parent, + }; + unsigned c; + int r; + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (r == 0) + return -ENOTTY; + + for (c = 0;; c++) { + r = ioctl(fd, BTRFS_IOC_QGROUP_ASSIGN, &args); + if (r < 0) { + if (errno == EBUSY && c < 10) { + (void) btrfs_quota_scan_wait(fd); + continue; + } + + return -errno; + } + + if (r == 0) + return 0; + + /* If the return value is > 0, we need to request a rescan */ + + (void) btrfs_quota_scan_start(fd); + return 1; + } +} + +int btrfs_qgroup_assign(int fd, uint64_t child, uint64_t parent) { + return qgroup_assign_or_unassign(fd, true, child, parent); +} + +int btrfs_qgroup_unassign(int fd, uint64_t child, uint64_t parent) { + return qgroup_assign_or_unassign(fd, false, child, parent); +} + +static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol_id, BtrfsRemoveFlags flags) { + struct btrfs_ioctl_search_args args = { + .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, + + .key.min_objectid = BTRFS_FIRST_FREE_OBJECTID, + .key.max_objectid = BTRFS_LAST_FREE_OBJECTID, + + .key.min_type = BTRFS_ROOT_BACKREF_KEY, + .key.max_type = BTRFS_ROOT_BACKREF_KEY, + + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + struct btrfs_ioctl_vol_args vol_args = {}; + _cleanup_close_ int subvol_fd = -1; + struct stat st; + bool made_writable = false; + int r; + + assert(fd >= 0); + assert(subvolume); + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode)) + return -EINVAL; + + subvol_fd = openat(fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (subvol_fd < 0) + return -errno; + + if (subvol_id == 0) { + r = btrfs_subvol_get_id_fd(subvol_fd, &subvol_id); + if (r < 0) + return r; + } + + /* First, try to remove the subvolume. If it happens to be + * already empty, this will just work. */ + strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1); + if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) >= 0) { + (void) btrfs_qgroup_destroy_recursive(fd, subvol_id); /* for the leaf subvolumes, the qgroup id is identical to the subvol id */ + return 0; + } + if (!(flags & BTRFS_REMOVE_RECURSIVE) || errno != ENOTEMPTY) + return -errno; + + /* OK, the subvolume is not empty, let's look for child + * subvolumes, and remove them, first */ + + args.key.min_offset = args.key.max_offset = subvol_id; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + return -errno; + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + _cleanup_free_ char *p = NULL; + const struct btrfs_root_ref *ref; + struct btrfs_ioctl_ino_lookup_args ino_args; + + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->type != BTRFS_ROOT_BACKREF_KEY) + continue; + if (sh->offset != subvol_id) + continue; + + ref = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); + + p = strndup((char*) ref + sizeof(struct btrfs_root_ref), le64toh(ref->name_len)); + if (!p) + return -ENOMEM; + + zero(ino_args); + ino_args.treeid = subvol_id; + ino_args.objectid = htole64(ref->dirid); + + if (ioctl(fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0) + return -errno; + + if (!made_writable) { + r = btrfs_subvol_set_read_only_fd(subvol_fd, false); + if (r < 0) + return r; + + made_writable = true; + } + + if (isempty(ino_args.name)) + /* Subvolume is in the top-level + * directory of the subvolume. */ + r = subvol_remove_children(subvol_fd, p, sh->objectid, flags); + else { + _cleanup_close_ int child_fd = -1; + + /* Subvolume is somewhere further down, + * hence we need to open the + * containing directory first */ + + child_fd = openat(subvol_fd, ino_args.name, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (child_fd < 0) + return -errno; + + r = subvol_remove_children(child_fd, p, sh->objectid, flags); + } + if (r < 0) + return r; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + + /* OK, the child subvolumes should all be gone now, let's try + * again to remove the subvolume */ + if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) < 0) + return -errno; + + (void) btrfs_qgroup_destroy_recursive(fd, subvol_id); + return 0; +} + +int btrfs_subvol_remove(const char *path, BtrfsRemoveFlags flags) { + _cleanup_close_ int fd = -1; + const char *subvolume; + int r; + + assert(path); + + r = extract_subvolume_name(path, &subvolume); + if (r < 0) + return r; + + fd = open_parent(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return fd; + + return subvol_remove_children(fd, subvolume, 0, flags); +} + +int btrfs_subvol_remove_fd(int fd, const char *subvolume, BtrfsRemoveFlags flags) { + return subvol_remove_children(fd, subvolume, 0, flags); +} + +int btrfs_qgroup_copy_limits(int fd, uint64_t old_qgroupid, uint64_t new_qgroupid) { + + struct btrfs_ioctl_search_args args = { + /* Tree of quota items */ + .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, + + /* The object ID is always 0 */ + .key.min_objectid = 0, + .key.max_objectid = 0, + + /* Look precisely for the quota items */ + .key.min_type = BTRFS_QGROUP_LIMIT_KEY, + .key.max_type = BTRFS_QGROUP_LIMIT_KEY, + + /* For our qgroup */ + .key.min_offset = old_qgroupid, + .key.max_offset = old_qgroupid, + + /* No restrictions on the other components */ + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + int r; + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) { + if (errno == ENOENT) /* quota tree missing: quota is not enabled, hence nothing to copy */ + break; + + return -errno; + } + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + const struct btrfs_qgroup_limit_item *qli = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); + struct btrfs_ioctl_qgroup_limit_args qargs; + unsigned c; + + /* Make sure we start the next search at least from this entry */ + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->objectid != 0) + continue; + if (sh->type != BTRFS_QGROUP_LIMIT_KEY) + continue; + if (sh->offset != old_qgroupid) + continue; + + /* We found the entry, now copy things over. */ + + qargs = (struct btrfs_ioctl_qgroup_limit_args) { + .qgroupid = new_qgroupid, + + .lim.max_rfer = le64toh(qli->max_rfer), + .lim.max_excl = le64toh(qli->max_excl), + .lim.rsv_rfer = le64toh(qli->rsv_rfer), + .lim.rsv_excl = le64toh(qli->rsv_excl), + + .lim.flags = le64toh(qli->flags) & (BTRFS_QGROUP_LIMIT_MAX_RFER| + BTRFS_QGROUP_LIMIT_MAX_EXCL| + BTRFS_QGROUP_LIMIT_RSV_RFER| + BTRFS_QGROUP_LIMIT_RSV_EXCL), + }; + + for (c = 0;; c++) { + if (ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &qargs) < 0) { + if (errno == EBUSY && c < 10) { + (void) btrfs_quota_scan_wait(fd); + continue; + } + return -errno; + } + + break; + } + + return 1; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + + return 0; +} + +static int copy_quota_hierarchy(int fd, uint64_t old_subvol_id, uint64_t new_subvol_id) { + _cleanup_free_ uint64_t *old_qgroups = NULL, *old_parent_qgroups = NULL; + bool copy_from_parent = false, insert_intermediary_qgroup = false; + int n_old_qgroups, n_old_parent_qgroups, r, i; + uint64_t old_parent_id; + + assert(fd >= 0); + + /* Copies a reduced form of quota information from the old to + * the new subvolume. */ + + n_old_qgroups = btrfs_qgroup_find_parents(fd, old_subvol_id, &old_qgroups); + if (n_old_qgroups <= 0) /* Nothing to copy */ + return n_old_qgroups; + + r = btrfs_subvol_get_parent(fd, old_subvol_id, &old_parent_id); + if (r == -ENXIO) + /* We have no parent, hence nothing to copy. */ + n_old_parent_qgroups = 0; + else if (r < 0) + return r; + else { + n_old_parent_qgroups = btrfs_qgroup_find_parents(fd, old_parent_id, &old_parent_qgroups); + if (n_old_parent_qgroups < 0) + return n_old_parent_qgroups; + } + + for (i = 0; i < n_old_qgroups; i++) { + uint64_t id; + int j; + + r = btrfs_qgroupid_split(old_qgroups[i], NULL, &id); + if (r < 0) + return r; + + if (id == old_subvol_id) { + /* The old subvolume was member of a qgroup + * that had the same id, but a different level + * as it self. Let's set up something similar + * in the destination. */ + insert_intermediary_qgroup = true; + break; + } + + for (j = 0; j < n_old_parent_qgroups; j++) + if (old_parent_qgroups[j] == old_qgroups[i]) { + /* The old subvolume shared a common + * parent qgroup with its parent + * subvolume. Let's set up something + * similar in the destination. */ + copy_from_parent = true; + } + } + + if (!insert_intermediary_qgroup && !copy_from_parent) + return 0; + + return btrfs_subvol_auto_qgroup_fd(fd, new_subvol_id, insert_intermediary_qgroup); +} + +static int copy_subtree_quota_limits(int fd, uint64_t old_subvol, uint64_t new_subvol) { + uint64_t old_subtree_qgroup, new_subtree_qgroup; + bool changed; + int r; + + /* First copy the leaf limits */ + r = btrfs_qgroup_copy_limits(fd, old_subvol, new_subvol); + if (r < 0) + return r; + changed = r > 0; + + /* Then, try to copy the subtree limits, if there are any. */ + r = btrfs_subvol_find_subtree_qgroup(fd, old_subvol, &old_subtree_qgroup); + if (r < 0) + return r; + if (r == 0) + return changed; + + r = btrfs_subvol_find_subtree_qgroup(fd, new_subvol, &new_subtree_qgroup); + if (r < 0) + return r; + if (r == 0) + return changed; + + r = btrfs_qgroup_copy_limits(fd, old_subtree_qgroup, new_subtree_qgroup); + if (r != 0) + return r; + + return changed; +} + +static int subvol_snapshot_children(int old_fd, int new_fd, const char *subvolume, uint64_t old_subvol_id, BtrfsSnapshotFlags flags) { + + struct btrfs_ioctl_search_args args = { + .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, + + .key.min_objectid = BTRFS_FIRST_FREE_OBJECTID, + .key.max_objectid = BTRFS_LAST_FREE_OBJECTID, + + .key.min_type = BTRFS_ROOT_BACKREF_KEY, + .key.max_type = BTRFS_ROOT_BACKREF_KEY, + + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + struct btrfs_ioctl_vol_args_v2 vol_args = { + .flags = flags & BTRFS_SNAPSHOT_READ_ONLY ? BTRFS_SUBVOL_RDONLY : 0, + .fd = old_fd, + }; + _cleanup_close_ int subvolume_fd = -1; + uint64_t new_subvol_id; + int r; + + assert(old_fd >= 0); + assert(new_fd >= 0); + assert(subvolume); + + strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1); + + if (ioctl(new_fd, BTRFS_IOC_SNAP_CREATE_V2, &vol_args) < 0) + return -errno; + + if (!(flags & BTRFS_SNAPSHOT_RECURSIVE) && + !(flags & BTRFS_SNAPSHOT_QUOTA)) + return 0; + + if (old_subvol_id == 0) { + r = btrfs_subvol_get_id_fd(old_fd, &old_subvol_id); + if (r < 0) + return r; + } + + r = btrfs_subvol_get_id(new_fd, vol_args.name, &new_subvol_id); + if (r < 0) + return r; + + if (flags & BTRFS_SNAPSHOT_QUOTA) + (void) copy_quota_hierarchy(new_fd, old_subvol_id, new_subvol_id); + + if (!(flags & BTRFS_SNAPSHOT_RECURSIVE)) { + + if (flags & BTRFS_SNAPSHOT_QUOTA) + (void) copy_subtree_quota_limits(new_fd, old_subvol_id, new_subvol_id); + + return 0; + } + + args.key.min_offset = args.key.max_offset = old_subvol_id; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(old_fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + return -errno; + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + _cleanup_free_ char *p = NULL, *c = NULL, *np = NULL; + struct btrfs_ioctl_ino_lookup_args ino_args; + const struct btrfs_root_ref *ref; + _cleanup_close_ int old_child_fd = -1, new_child_fd = -1; + + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->type != BTRFS_ROOT_BACKREF_KEY) + continue; + + /* Avoid finding the source subvolume a second + * time */ + if (sh->offset != old_subvol_id) + continue; + + /* Avoid running into loops if the new + * subvolume is below the old one. */ + if (sh->objectid == new_subvol_id) + continue; + + ref = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); + p = strndup((char*) ref + sizeof(struct btrfs_root_ref), le64toh(ref->name_len)); + if (!p) + return -ENOMEM; + + zero(ino_args); + ino_args.treeid = old_subvol_id; + ino_args.objectid = htole64(ref->dirid); + + if (ioctl(old_fd, BTRFS_IOC_INO_LOOKUP, &ino_args) < 0) + return -errno; + + /* The kernel returns an empty name if the + * subvolume is in the top-level directory, + * and otherwise appends a slash, so that we + * can just concatenate easily here, without + * adding a slash. */ + c = strappend(ino_args.name, p); + if (!c) + return -ENOMEM; + + old_child_fd = openat(old_fd, c, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (old_child_fd < 0) + return -errno; + + np = strjoin(subvolume, "/", ino_args.name, NULL); + if (!np) + return -ENOMEM; + + new_child_fd = openat(new_fd, np, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (new_child_fd < 0) + return -errno; + + if (flags & BTRFS_SNAPSHOT_READ_ONLY) { + /* If the snapshot is read-only we + * need to mark it writable + * temporarily, to put the subsnapshot + * into place. */ + + if (subvolume_fd < 0) { + subvolume_fd = openat(new_fd, subvolume, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (subvolume_fd < 0) + return -errno; + } + + r = btrfs_subvol_set_read_only_fd(subvolume_fd, false); + if (r < 0) + return r; + } + + /* When btrfs clones the subvolumes, child + * subvolumes appear as empty directories. Remove + * them, so that we can create a new snapshot + * in their place */ + if (unlinkat(new_child_fd, p, AT_REMOVEDIR) < 0) { + int k = -errno; + + if (flags & BTRFS_SNAPSHOT_READ_ONLY) + (void) btrfs_subvol_set_read_only_fd(subvolume_fd, true); + + return k; + } + + r = subvol_snapshot_children(old_child_fd, new_child_fd, p, sh->objectid, flags & ~BTRFS_SNAPSHOT_FALLBACK_COPY); + + /* Restore the readonly flag */ + if (flags & BTRFS_SNAPSHOT_READ_ONLY) { + int k; + + k = btrfs_subvol_set_read_only_fd(subvolume_fd, true); + if (r >= 0 && k < 0) + return k; + } + + if (r < 0) + return r; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + + if (flags & BTRFS_SNAPSHOT_QUOTA) + (void) copy_subtree_quota_limits(new_fd, old_subvol_id, new_subvol_id); + + return 0; +} + +int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags) { + _cleanup_close_ int new_fd = -1; + const char *subvolume; + int r; + + assert(old_fd >= 0); + assert(new_path); + + r = btrfs_is_subvol_fd(old_fd); + if (r < 0) + return r; + if (r == 0) { + if (!(flags & BTRFS_SNAPSHOT_FALLBACK_COPY)) + return -EISDIR; + + r = btrfs_subvol_make(new_path); + if (r < 0) + return r; + + r = copy_directory_fd(old_fd, new_path, true); + if (r < 0) { + (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA); + return r; + } + + if (flags & BTRFS_SNAPSHOT_READ_ONLY) { + r = btrfs_subvol_set_read_only(new_path, true); + if (r < 0) { + (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA); + return r; + } + } + + return 0; + } + + r = extract_subvolume_name(new_path, &subvolume); + if (r < 0) + return r; + + new_fd = open_parent(new_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (new_fd < 0) + return new_fd; + + return subvol_snapshot_children(old_fd, new_fd, subvolume, 0, flags); +} + +int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags) { + _cleanup_close_ int old_fd = -1; + + assert(old_path); + assert(new_path); + + old_fd = open(old_path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (old_fd < 0) + return -errno; + + return btrfs_subvol_snapshot_fd(old_fd, new_path, flags); +} + +int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret) { + + struct btrfs_ioctl_search_args args = { + /* Tree of quota items */ + .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, + + /* Look precisely for the quota relation items */ + .key.min_type = BTRFS_QGROUP_RELATION_KEY, + .key.max_type = BTRFS_QGROUP_RELATION_KEY, + + /* No restrictions on the other components */ + .key.min_offset = 0, + .key.max_offset = (uint64_t) -1, + + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + _cleanup_free_ uint64_t *items = NULL; + size_t n_items = 0, n_allocated = 0; + int r; + + assert(fd >= 0); + assert(ret); + + if (qgroupid == 0) { + r = btrfs_subvol_get_id_fd(fd, &qgroupid); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } + + args.key.min_objectid = args.key.max_objectid = qgroupid; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) { + if (errno == ENOENT) /* quota tree missing: quota is disabled */ + break; + + return -errno; + } + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + + /* Make sure we start the next search at least from this entry */ + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->type != BTRFS_QGROUP_RELATION_KEY) + continue; + if (sh->offset < sh->objectid) + continue; + if (sh->objectid != qgroupid) + continue; + + if (!GREEDY_REALLOC(items, n_allocated, n_items+1)) + return -ENOMEM; + + items[n_items++] = sh->offset; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + + if (n_items <= 0) { + *ret = NULL; + return 0; + } + + *ret = items; + items = NULL; + + return (int) n_items; +} + +int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool insert_intermediary_qgroup) { + _cleanup_free_ uint64_t *qgroups = NULL; + uint64_t parent_subvol; + bool changed = false; + int n = 0, r; + + assert(fd >= 0); + + /* + * Sets up the specified subvolume's qgroup automatically in + * one of two ways: + * + * If insert_intermediary_qgroup is false, the subvolume's + * leaf qgroup will be assigned to the same parent qgroups as + * the subvolume's parent subvolume. + * + * If insert_intermediary_qgroup is true a new intermediary + * higher-level qgroup is created, with a higher level number, + * but reusing the id of the subvolume. The level number is + * picked as one smaller than the lowest level qgroup the + * parent subvolume is a member of. If the parent subvolume's + * leaf qgroup is assigned to no higher-level qgroup a new + * qgroup of level 255 is created instead. Either way, the new + * qgroup is then assigned to the parent's higher-level + * qgroup, and the subvolume itself is assigned to it. + * + * If the subvolume is already assigned to a higher level + * qgroup, no operation is executed. + * + * Effectively this means: regardless if + * insert_intermediary_qgroup is true or not, after this + * function is invoked the subvolume will be accounted within + * the same qgroups as the parent. However, if it is true, it + * will also get its own higher-level qgroup, which may in + * turn be used by subvolumes created beneath this subvolume + * later on. + * + * This hence defines a simple default qgroup setup for + * subvolumes, as long as this function is invoked on each + * created subvolume: each subvolume is always accounting + * together with its immediate parents. Optionally, if + * insert_intermediary_qgroup is true, it will also get a + * qgroup that then includes all its own child subvolumes. + */ + + if (subvol_id == 0) { + r = btrfs_is_subvol_fd(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + + r = btrfs_subvol_get_id_fd(fd, &subvol_id); + if (r < 0) + return r; + } + + n = btrfs_qgroup_find_parents(fd, subvol_id, &qgroups); + if (n < 0) + return n; + if (n > 0) /* already parent qgroups set up, let's bail */ + return 0; + + qgroups = mfree(qgroups); + + r = btrfs_subvol_get_parent(fd, subvol_id, &parent_subvol); + if (r == -ENXIO) + /* No parent, hence no qgroup memberships */ + n = 0; + else if (r < 0) + return r; + else { + n = btrfs_qgroup_find_parents(fd, parent_subvol, &qgroups); + if (n < 0) + return n; + } + + if (insert_intermediary_qgroup) { + uint64_t lowest = 256, new_qgroupid; + bool created = false; + int i; + + /* Determine the lowest qgroup that the parent + * subvolume is assigned to. */ + + for (i = 0; i < n; i++) { + uint64_t level; + + r = btrfs_qgroupid_split(qgroups[i], &level, NULL); + if (r < 0) + return r; + + if (level < lowest) + lowest = level; + } + + if (lowest <= 1) /* There are no levels left we could use insert an intermediary qgroup at */ + return -EBUSY; + + r = btrfs_qgroupid_make(lowest - 1, subvol_id, &new_qgroupid); + if (r < 0) + return r; + + /* Create the new intermediary group, unless it already exists */ + r = btrfs_qgroup_create(fd, new_qgroupid); + if (r < 0 && r != -EEXIST) + return r; + if (r >= 0) + changed = created = true; + + for (i = 0; i < n; i++) { + r = btrfs_qgroup_assign(fd, new_qgroupid, qgroups[i]); + if (r < 0 && r != -EEXIST) { + if (created) + (void) btrfs_qgroup_destroy_recursive(fd, new_qgroupid); + + return r; + } + if (r >= 0) + changed = true; + } + + r = btrfs_qgroup_assign(fd, subvol_id, new_qgroupid); + if (r < 0 && r != -EEXIST) { + if (created) + (void) btrfs_qgroup_destroy_recursive(fd, new_qgroupid); + return r; + } + if (r >= 0) + changed = true; + + } else { + int i; + + /* Assign our subvolume to all the same qgroups as the parent */ + + for (i = 0; i < n; i++) { + r = btrfs_qgroup_assign(fd, subvol_id, qgroups[i]); + if (r < 0 && r != -EEXIST) + return r; + if (r >= 0) + changed = true; + } + } + + return changed; +} + +int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_intermediary_qgroup) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return -errno; + + return btrfs_subvol_auto_qgroup_fd(fd, subvol_id, create_intermediary_qgroup); +} + +int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret) { + + struct btrfs_ioctl_search_args args = { + /* Tree of tree roots */ + .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, + + /* Look precisely for the subvolume items */ + .key.min_type = BTRFS_ROOT_BACKREF_KEY, + .key.max_type = BTRFS_ROOT_BACKREF_KEY, + + /* No restrictions on the other components */ + .key.min_offset = 0, + .key.max_offset = (uint64_t) -1, + + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + int r; + + assert(fd >= 0); + assert(ret); + + if (subvol_id == 0) { + r = btrfs_subvol_get_id_fd(fd, &subvol_id); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } + + args.key.min_objectid = args.key.max_objectid = subvol_id; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + return negative_errno(); + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + + if (sh->type != BTRFS_ROOT_BACKREF_KEY) + continue; + if (sh->objectid != subvol_id) + continue; + + *ret = sh->offset; + return 0; + } + } + + return -ENXIO; +} diff --git a/src/libbasic/btrfs-util.h b/src/libbasic/btrfs-util.h new file mode 100644 index 0000000000..29f8ba63d3 --- /dev/null +++ b/src/libbasic/btrfs-util.h @@ -0,0 +1,131 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#pragma once + +#include +#include +#include + +#include + +#include "time-util.h" + +typedef struct BtrfsSubvolInfo { + uint64_t subvol_id; + usec_t otime; + + sd_id128_t uuid; + sd_id128_t parent_uuid; + + bool read_only; +} BtrfsSubvolInfo; + +typedef struct BtrfsQuotaInfo { + uint64_t referenced; + uint64_t exclusive; + uint64_t referenced_max; + uint64_t exclusive_max; +} BtrfsQuotaInfo; + +typedef enum BtrfsSnapshotFlags { + BTRFS_SNAPSHOT_FALLBACK_COPY = 1, + BTRFS_SNAPSHOT_READ_ONLY = 2, + BTRFS_SNAPSHOT_RECURSIVE = 4, + BTRFS_SNAPSHOT_QUOTA = 8, +} BtrfsSnapshotFlags; + +typedef enum BtrfsRemoveFlags { + BTRFS_REMOVE_RECURSIVE = 1, + BTRFS_REMOVE_QUOTA = 2, +} BtrfsRemoveFlags; + +int btrfs_is_filesystem(int fd); + +int btrfs_is_subvol_fd(int fd); +int btrfs_is_subvol(const char *path); + +int btrfs_reflink(int infd, int outfd); +int btrfs_clone_range(int infd, uint64_t in_offset, int ofd, uint64_t out_offset, uint64_t sz); + +int btrfs_get_block_device_fd(int fd, dev_t *dev); +int btrfs_get_block_device(const char *path, dev_t *dev); + +int btrfs_defrag_fd(int fd); +int btrfs_defrag(const char *p); + +int btrfs_quota_enable_fd(int fd, bool b); +int btrfs_quota_enable(const char *path, bool b); + +int btrfs_quota_scan_start(int fd); +int btrfs_quota_scan_wait(int fd); +int btrfs_quota_scan_ongoing(int fd); + +int btrfs_resize_loopback_fd(int fd, uint64_t size, bool grow_only); +int btrfs_resize_loopback(const char *path, uint64_t size, bool grow_only); + +int btrfs_subvol_make(const char *path); +int btrfs_subvol_make_label(const char *path); + +int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags); +int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags); + +int btrfs_subvol_remove(const char *path, BtrfsRemoveFlags flags); +int btrfs_subvol_remove_fd(int fd, const char *subvolume, BtrfsRemoveFlags flags); + +int btrfs_subvol_set_read_only_fd(int fd, bool b); +int btrfs_subvol_set_read_only(const char *path, bool b); +int btrfs_subvol_get_read_only_fd(int fd); + +int btrfs_subvol_get_id(int fd, const char *subvolume, uint64_t *ret); +int btrfs_subvol_get_id_fd(int fd, uint64_t *ret); +int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret); + +int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *info); + +int btrfs_subvol_find_subtree_qgroup(int fd, uint64_t subvol_id, uint64_t *ret); + +int btrfs_subvol_get_subtree_quota(const char *path, uint64_t subvol_id, BtrfsQuotaInfo *quota); +int btrfs_subvol_get_subtree_quota_fd(int fd, uint64_t subvol_id, BtrfsQuotaInfo *quota); + +int btrfs_subvol_set_subtree_quota_limit(const char *path, uint64_t subvol_id, uint64_t referenced_max); +int btrfs_subvol_set_subtree_quota_limit_fd(int fd, uint64_t subvol_id, uint64_t referenced_max); + +int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool new_qgroup); +int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_intermediary_qgroup); + +int btrfs_qgroupid_make(uint64_t level, uint64_t id, uint64_t *ret); +int btrfs_qgroupid_split(uint64_t qgroupid, uint64_t *level, uint64_t *id); + +int btrfs_qgroup_create(int fd, uint64_t qgroupid); +int btrfs_qgroup_destroy(int fd, uint64_t qgroupid); +int btrfs_qgroup_destroy_recursive(int fd, uint64_t qgroupid); + +int btrfs_qgroup_set_limit_fd(int fd, uint64_t qgroupid, uint64_t referenced_max); +int btrfs_qgroup_set_limit(const char *path, uint64_t qgroupid, uint64_t referenced_max); + +int btrfs_qgroup_copy_limits(int fd, uint64_t old_qgroupid, uint64_t new_qgroupid); + +int btrfs_qgroup_assign(int fd, uint64_t child, uint64_t parent); +int btrfs_qgroup_unassign(int fd, uint64_t child, uint64_t parent); + +int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret); + +int btrfs_qgroup_get_quota_fd(int fd, uint64_t qgroupid, BtrfsQuotaInfo *quota); +int btrfs_qgroup_get_quota(const char *path, uint64_t qgroupid, BtrfsQuotaInfo *quota); diff --git a/src/libbasic/build.h b/src/libbasic/build.h new file mode 100644 index 0000000000..633c2aaccb --- /dev/null +++ b/src/libbasic/build.h @@ -0,0 +1,155 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#ifdef HAVE_PAM +#define _PAM_FEATURE_ "+PAM" +#else +#define _PAM_FEATURE_ "-PAM" +#endif + +#ifdef HAVE_AUDIT +#define _AUDIT_FEATURE_ "+AUDIT" +#else +#define _AUDIT_FEATURE_ "-AUDIT" +#endif + +#ifdef HAVE_SELINUX +#define _SELINUX_FEATURE_ "+SELINUX" +#else +#define _SELINUX_FEATURE_ "-SELINUX" +#endif + +#ifdef HAVE_APPARMOR +#define _APPARMOR_FEATURE_ "+APPARMOR" +#else +#define _APPARMOR_FEATURE_ "-APPARMOR" +#endif + +#ifdef HAVE_IMA +#define _IMA_FEATURE_ "+IMA" +#else +#define _IMA_FEATURE_ "-IMA" +#endif + +#ifdef HAVE_SMACK +#define _SMACK_FEATURE_ "+SMACK" +#else +#define _SMACK_FEATURE_ "-SMACK" +#endif + +#ifdef HAVE_SYSV_COMPAT +#define _SYSVINIT_FEATURE_ "+SYSVINIT" +#else +#define _SYSVINIT_FEATURE_ "-SYSVINIT" +#endif + +#ifdef HAVE_UTMP +#define _UTMP_FEATURE_ "+UTMP" +#else +#define _UTMP_FEATURE_ "-UTMP" +#endif + +#ifdef HAVE_LIBCRYPTSETUP +#define _LIBCRYPTSETUP_FEATURE_ "+LIBCRYPTSETUP" +#else +#define _LIBCRYPTSETUP_FEATURE_ "-LIBCRYPTSETUP" +#endif + +#ifdef HAVE_GCRYPT +#define _GCRYPT_FEATURE_ "+GCRYPT" +#else +#define _GCRYPT_FEATURE_ "-GCRYPT" +#endif + +#ifdef HAVE_GNUTLS +#define _GNUTLS_FEATURE_ "+GNUTLS" +#else +#define _GNUTLS_FEATURE_ "-GNUTLS" +#endif + +#ifdef HAVE_ACL +#define _ACL_FEATURE_ "+ACL" +#else +#define _ACL_FEATURE_ "-ACL" +#endif + +#ifdef HAVE_XZ +#define _XZ_FEATURE_ "+XZ" +#else +#define _XZ_FEATURE_ "-XZ" +#endif + +#ifdef HAVE_LZ4 +#define _LZ4_FEATURE_ "+LZ4" +#else +#define _LZ4_FEATURE_ "-LZ4" +#endif + +#ifdef HAVE_SECCOMP +#define _SECCOMP_FEATURE_ "+SECCOMP" +#else +#define _SECCOMP_FEATURE_ "-SECCOMP" +#endif + +#ifdef HAVE_BLKID +#define _BLKID_FEATURE_ "+BLKID" +#else +#define _BLKID_FEATURE_ "-BLKID" +#endif + +#ifdef HAVE_ELFUTILS +#define _ELFUTILS_FEATURE_ "+ELFUTILS" +#else +#define _ELFUTILS_FEATURE_ "-ELFUTILS" +#endif + +#ifdef HAVE_KMOD +#define _KMOD_FEATURE_ "+KMOD" +#else +#define _KMOD_FEATURE_ "-KMOD" +#endif + +#ifdef HAVE_LIBIDN +#define _IDN_FEATURE_ "+IDN" +#else +#define _IDN_FEATURE_ "-IDN" +#endif + +#define SYSTEMD_FEATURES \ + _PAM_FEATURE_ " " \ + _AUDIT_FEATURE_ " " \ + _SELINUX_FEATURE_ " " \ + _IMA_FEATURE_ " " \ + _APPARMOR_FEATURE_ " " \ + _SMACK_FEATURE_ " " \ + _SYSVINIT_FEATURE_ " " \ + _UTMP_FEATURE_ " " \ + _LIBCRYPTSETUP_FEATURE_ " " \ + _GCRYPT_FEATURE_ " " \ + _GNUTLS_FEATURE_ " " \ + _ACL_FEATURE_ " " \ + _XZ_FEATURE_ " " \ + _LZ4_FEATURE_ " " \ + _SECCOMP_FEATURE_ " " \ + _BLKID_FEATURE_ " " \ + _ELFUTILS_FEATURE_ " " \ + _KMOD_FEATURE_ " " \ + _IDN_FEATURE_ diff --git a/src/libbasic/bus-label.c b/src/libbasic/bus-label.c new file mode 100644 index 0000000000..d4531c7947 --- /dev/null +++ b/src/libbasic/bus-label.c @@ -0,0 +1,98 @@ +/*** + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "bus-label.h" +#include "hexdecoct.h" +#include "macro.h" + +char *bus_label_escape(const char *s) { + char *r, *t; + const char *f; + + assert_return(s, NULL); + + /* Escapes all chars that D-Bus' object path cannot deal + * with. Can be reversed with bus_path_unescape(). We special + * case the empty string. */ + + if (*s == 0) + return strdup("_"); + + r = new(char, strlen(s)*3 + 1); + if (!r) + return NULL; + + for (f = s, t = r; *f; f++) { + + /* Escape everything that is not a-zA-Z0-9. We also + * escape 0-9 if it's the first character */ + + if (!(*f >= 'A' && *f <= 'Z') && + !(*f >= 'a' && *f <= 'z') && + !(f > s && *f >= '0' && *f <= '9')) { + *(t++) = '_'; + *(t++) = hexchar(*f >> 4); + *(t++) = hexchar(*f); + } else + *(t++) = *f; + } + + *t = 0; + + return r; +} + +char *bus_label_unescape_n(const char *f, size_t l) { + char *r, *t; + size_t i; + + assert_return(f, NULL); + + /* Special case for the empty string */ + if (l == 1 && *f == '_') + return strdup(""); + + r = new(char, l + 1); + if (!r) + return NULL; + + for (i = 0, t = r; i < l; ++i) { + if (f[i] == '_') { + int a, b; + + if (l - i < 3 || + (a = unhexchar(f[i + 1])) < 0 || + (b = unhexchar(f[i + 2])) < 0) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '_'; + } else { + *(t++) = (char) ((a << 4) | b); + i += 2; + } + } else + *(t++) = f[i]; + } + + *t = 0; + + return r; +} diff --git a/src/libbasic/bus-label.h b/src/libbasic/bus-label.h new file mode 100644 index 0000000000..62fb2c450c --- /dev/null +++ b/src/libbasic/bus-label.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + 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 . +***/ + +#include +#include +#include + +char *bus_label_escape(const char *s); +char *bus_label_unescape_n(const char *f, size_t l); + +static inline char *bus_label_unescape(const char *f) { + return bus_label_unescape_n(f, f ? strlen(f) : 0); +} diff --git a/src/libbasic/c-rbtree.c b/src/libbasic/c-rbtree.c new file mode 100644 index 0000000000..914d7e5229 --- /dev/null +++ b/src/libbasic/c-rbtree.c @@ -0,0 +1,679 @@ +/*** + This file is part of systemd. See COPYING for details. + + 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 . +***/ + +/* + * RB-Tree Implementation + * This implements the insertion/removal of elements in RB-Trees. You're highly + * recommended to have an RB-Tree documentation at hand when reading this. Both + * insertion and removal can be split into a handful of situations that can + * occur. Those situations are enumerated as "Case 1" to "Case n" here, and + * follow closely the cases described in most RB-Tree documentations. This file + * does not explain why it is enough to handle just those cases, nor does it + * provide a proof of correctness. Dig out your algorithm 101 handbook if + * you're interested. + * + * This implementation is *not* straightforward. Usually, a handful of + * rotation, reparent, swap and link helpers can be used to implement the + * rebalance operations. However, those often perform unnecessary writes. + * Therefore, this implementation hard-codes all the operations. You're highly + * recommended to look at the two basic helpers before reading the code: + * c_rbtree_swap_child() + * c_rbtree_set_parent_and_color() + * Those are the only helpers used, hence, you should really know what they do + * before digging into the code. + * + * For a highlevel documentation of the API, see the header file and docbook + * comments. + */ + +#include +#include +#include "c-rbtree.h" + +enum { + C_RBNODE_RED = 0, + C_RBNODE_BLACK = 1, +}; + +static inline unsigned long c_rbnode_color(CRBNode *n) { + return (unsigned long)n->__parent_and_color & 1UL; +} + +static inline _Bool c_rbnode_is_red(CRBNode *n) { + return c_rbnode_color(n) == C_RBNODE_RED; +} + +static inline _Bool c_rbnode_is_black(CRBNode *n) { + return c_rbnode_color(n) == C_RBNODE_BLACK; +} + +/** + * c_rbnode_leftmost() - return leftmost child + * @n: current node, or NULL + * + * This returns the leftmost child of @n. If @n is NULL, this will return NULL. + * In all other cases, this function returns a valid pointer. That is, if @n + * does not have any left children, this returns @n. + * + * Worst case runtime (n: number of elements in tree): O(log(n)) + * + * Return: Pointer to leftmost child, or NULL. + */ +CRBNode *c_rbnode_leftmost(CRBNode *n) { + if (n) + while (n->left) + n = n->left; + return n; +} + +/** + * c_rbnode_rightmost() - return rightmost child + * @n: current node, or NULL + * + * This returns the rightmost child of @n. If @n is NULL, this will return + * NULL. In all other cases, this function returns a valid pointer. That is, if + * @n does not have any right children, this returns @n. + * + * Worst case runtime (n: number of elements in tree): O(log(n)) + * + * Return: Pointer to rightmost child, or NULL. + */ +CRBNode *c_rbnode_rightmost(CRBNode *n) { + if (n) + while (n->right) + n = n->right; + return n; +} + +/** + * c_rbnode_next() - return next node + * @n: current node, or NULL + * + * An RB-Tree always defines a linear order of its elements. This function + * returns the logically next node to @n. If @n is NULL, the last node or + * unlinked, this returns NULL. + * + * Worst case runtime (n: number of elements in tree): O(log(n)) + * + * Return: Pointer to next node, or NULL. + */ +CRBNode *c_rbnode_next(CRBNode *n) { + CRBNode *p; + + if (!c_rbnode_is_linked(n)) + return NULL; + if (n->right) + return c_rbnode_leftmost(n->right); + + while ((p = c_rbnode_parent(n)) && n == p->right) + n = p; + + return p; +} + +/** + * c_rbnode_prev() - return previous node + * @n: current node, or NULL + * + * An RB-Tree always defines a linear order of its elements. This function + * returns the logically previous node to @n. If @n is NULL, the first node or + * unlinked, this returns NULL. + * + * Worst case runtime (n: number of elements in tree): O(log(n)) + * + * Return: Pointer to previous node, or NULL. + */ +CRBNode *c_rbnode_prev(CRBNode *n) { + CRBNode *p; + + if (!c_rbnode_is_linked(n)) + return NULL; + if (n->left) + return c_rbnode_rightmost(n->left); + + while ((p = c_rbnode_parent(n)) && n == p->left) + n = p; + + return p; +} + +/** + * c_rbtree_first() - return first node + * @t: tree to operate on + * + * An RB-Tree always defines a linear order of its elements. This function + * returns the logically first node in @t. If @t is empty, NULL is returned. + * + * Fixed runtime (n: number of elements in tree): O(log(n)) + * + * Return: Pointer to first node, or NULL. + */ +CRBNode *c_rbtree_first(CRBTree *t) { + assert(t); + return c_rbnode_leftmost(t->root); +} + +/** + * c_rbtree_last() - return last node + * @t: tree to operate on + * + * An RB-Tree always defines a linear order of its elements. This function + * returns the logically last node in @t. If @t is empty, NULL is returned. + * + * Fixed runtime (n: number of elements in tree): O(log(n)) + * + * Return: Pointer to last node, or NULL. + */ +CRBNode *c_rbtree_last(CRBTree *t) { + assert(t); + return c_rbnode_rightmost(t->root); +} + +/* + * Set the color and parent of a node. This should be treated as a simple + * assignment of the 'color' and 'parent' fields of the node. No other magic is + * applied. But since both fields share its backing memory, this helper + * function is provided. + */ +static inline void c_rbnode_set_parent_and_color(CRBNode *n, CRBNode *p, unsigned long c) { + assert(!((unsigned long)p & 1)); + assert(c < 2); + n->__parent_and_color = (CRBNode*)((unsigned long)p | c); +} + +/* same as c_rbnode_set_parent_and_color(), but keeps the current parent */ +static inline void c_rbnode_set_color(CRBNode *n, unsigned long c) { + c_rbnode_set_parent_and_color(n, c_rbnode_parent(n), c); +} + +/* same as c_rbnode_set_parent_and_color(), but keeps the current color */ +static inline void c_rbnode_set_parent(CRBNode *n, CRBNode *p) { + c_rbnode_set_parent_and_color(n, p, c_rbnode_color(n)); +} + +/* + * This function partially replaces an existing child pointer to a new one. The + * existing child must be given as @old, the new child as @new. @p must be the + * parent of @old (or NULL if it has no parent). + * This function ensures that the parent of @old now points to @new. However, + * it does *NOT* change the parent pointer of @new. The caller must ensure + * this. + * If @p is NULL, this function ensures that the root-pointer is adjusted + * instead (given as @t). + */ +static inline void c_rbtree_swap_child(CRBTree *t, CRBNode *p, CRBNode *old, CRBNode *new) { + if (p) { + if (p->left == old) + p->left = new; + else + p->right = new; + } else { + t->root = new; + } +} + +static inline CRBNode *c_rbtree_paint_one(CRBTree *t, CRBNode *n) { + CRBNode *p, *g, *gg, *u, *x; + + /* + * Paint a single node according to RB-Tree rules. The node must + * already be linked into the tree and painted red. + * We repaint the node or rotate the tree, if required. In case a + * recursive repaint is required, the next node to be re-painted + * is returned. + * p: parent + * g: grandparent + * gg: grandgrandparent + * u: uncle + * x: temporary + */ + + /* node is red, so we can access the parent directly */ + p = n->__parent_and_color; + + if (!p) { + /* Case 1: + * We reached the root. Mark it black and be done. As all + * leaf-paths share the root, the ratio of black nodes on each + * path stays the same. */ + c_rbnode_set_parent_and_color(n, p, C_RBNODE_BLACK); + n = NULL; + } else if (c_rbnode_is_black(p)) { + /* Case 2: + * The parent is already black. As our node is red, we did not + * change the number of black nodes on any path, nor do we have + * multiple consecutive red nodes. */ + n = NULL; + } else if (p == p->__parent_and_color->left) { /* parent is red, so grandparent exists */ + g = p->__parent_and_color; + gg = c_rbnode_parent(g); + u = g->right; + + if (u && c_rbnode_is_red(u)) { + /* Case 3: + * Parent and uncle are both red. We know the + * grandparent must be black then. Repaint parent and + * uncle black, the grandparent red and recurse into + * the grandparent. */ + c_rbnode_set_parent_and_color(p, g, C_RBNODE_BLACK); + c_rbnode_set_parent_and_color(u, g, C_RBNODE_BLACK); + c_rbnode_set_parent_and_color(g, gg, C_RBNODE_RED); + n = g; + } else { + /* parent is red, uncle is black */ + + if (n == p->right) { + /* Case 4: + * We're the right child. Rotate on parent to + * become left child, so we can handle it the + * same as case 5. */ + x = n->left; + p->right = n->left; + n->left = p; + if (x) + c_rbnode_set_parent_and_color(x, p, C_RBNODE_BLACK); + c_rbnode_set_parent_and_color(p, n, C_RBNODE_RED); + p = n; + } + + /* 'n' is invalid from here on! */ + n = NULL; + + /* Case 5: + * We're the red left child or a red parent, black + * grandparent and uncle. Rotate on grandparent and + * switch color with parent. Number of black nodes on + * each path stays the same, but we got rid of the + * double red path. As the grandparent is still black, + * we're done. */ + x = p->right; + g->left = x; + p->right = g; + if (x) + c_rbnode_set_parent_and_color(x, g, C_RBNODE_BLACK); + c_rbnode_set_parent_and_color(p, gg, C_RBNODE_BLACK); + c_rbnode_set_parent_and_color(g, p, C_RBNODE_RED); + c_rbtree_swap_child(t, gg, g, p); + } + } else /* if (p == p->__parent_and_color->left) */ { /* same as above, but mirrored */ + g = p->__parent_and_color; + gg = c_rbnode_parent(g); + u = g->left; + + if (u && c_rbnode_is_red(u)) { + c_rbnode_set_parent_and_color(p, g, C_RBNODE_BLACK); + c_rbnode_set_parent_and_color(u, g, C_RBNODE_BLACK); + c_rbnode_set_parent_and_color(g, gg, C_RBNODE_RED); + n = g; + } else { + if (n == p->left) { + x = n->right; + p->left = n->right; + n->right = p; + if (x) + c_rbnode_set_parent_and_color(x, p, C_RBNODE_BLACK); + c_rbnode_set_parent_and_color(p, n, C_RBNODE_RED); + p = n; + } + + n = NULL; + + x = p->left; + g->right = x; + p->left = g; + if (x) + c_rbnode_set_parent_and_color(x, g, C_RBNODE_BLACK); + c_rbnode_set_parent_and_color(p, gg, C_RBNODE_BLACK); + c_rbnode_set_parent_and_color(g, p, C_RBNODE_RED); + c_rbtree_swap_child(t, gg, g, p); + } + } + + return n; +} + +static inline void c_rbtree_paint(CRBTree *t, CRBNode *n) { + assert(t); + assert(n); + + while (n) + n = c_rbtree_paint_one(t, n); +} + +/** + * c_rbtree_add() - add node to tree + * @t: tree to operate one + * @p: parent node to link under, or NULL + * @l: left/right slot of @p (or root) to link at + * @n: node to add + * + * This links @n into the tree given as @t. The caller must provide the exact + * spot where to link the node. That is, the caller must traverse the tree + * based on their search order. Once they hit a leaf where to insert the node, + * call this function to link it and rebalance the tree. + * + * A typical insertion would look like this (@t is your tree, @n is your node): + * + * CRBNode **i, *p; + * + * i = &t->root; + * p = NULL; + * while (*i) { + * p = *i; + * if (compare(n, *i) < 0) + * i = &(*i)->left; + * else + * i = &(*i)->right; + * } + * + * c_rbtree_add(t, p, i, n); + * + * Once the node is linked into the tree, a simple lookup on the same tree can + * be coded like this: + * + * CRBNode *i; + * + * i = t->root; + * while (i) { + * int v = compare(n, i); + * if (v < 0) + * i = (*i)->left; + * else if (v > 0) + * i = (*i)->right; + * else + * break; + * } + * + * When you add nodes to a tree, the memory contents of the node do not matter. + * That is, there is no need to initialize the node via c_rbnode_init(). + * However, if you relink nodes multiple times during their lifetime, it is + * usually very convenient to use c_rbnode_init() and c_rbtree_remove_init(). + * In those cases, you should validate that a node is unlinked before you call + * c_rbtree_add(). + */ +void c_rbtree_add(CRBTree *t, CRBNode *p, CRBNode **l, CRBNode *n) { + assert(t); + assert(l); + assert(n); + assert(!p || l == &p->left || l == &p->right); + assert(p || l == &t->root); + + c_rbnode_set_parent_and_color(n, p, C_RBNODE_RED); + n->left = n->right = NULL; + *l = n; + + c_rbtree_paint(t, n); +} + +static inline CRBNode *c_rbtree_rebalance_one(CRBTree *t, CRBNode *p, CRBNode *n) { + CRBNode *s, *x, *y, *g; + + /* + * Rebalance tree after a node was removed. This happens only if you + * remove a black node and one path is now left with an unbalanced + * number or black nodes. + * This function assumes all paths through p and n have one black node + * less than all other paths. If recursive fixup is required, the + * current node is returned. + */ + + if (n == p->left) { + s = p->right; + if (c_rbnode_is_red(s)) { + /* Case 3: + * We have a red node as sibling. Rotate it onto our + * side so we can later on turn it black. This way, we + * gain the additional black node in our path. */ + g = c_rbnode_parent(p); + x = s->left; + p->right = x; + s->left = p; + c_rbnode_set_parent_and_color(x, p, C_RBNODE_BLACK); + c_rbnode_set_parent_and_color(s, g, c_rbnode_color(p)); + c_rbnode_set_parent_and_color(p, s, C_RBNODE_RED); + c_rbtree_swap_child(t, g, p, s); + s = x; + } + + x = s->right; + if (!x || c_rbnode_is_black(x)) { + y = s->left; + if (!y || c_rbnode_is_black(y)) { + /* Case 4: + * Our sibling is black and has only black + * children. Flip it red and turn parent black. + * This way we gained a black node in our path, + * or we fix it recursively one layer up, which + * will rotate the red sibling as parent. */ + c_rbnode_set_parent_and_color(s, p, C_RBNODE_RED); + if (c_rbnode_is_black(p)) + return p; + + c_rbnode_set_parent_and_color(p, c_rbnode_parent(p), C_RBNODE_BLACK); + return NULL; + } + + /* Case 5: + * Left child of our sibling is red, right one is black. + * Rotate on parent so the right child of our sibling is + * now red, and we can fall through to case 6. */ + x = y->right; + s->left = y->right; + y->right = s; + p->right = y; + if (x) + c_rbnode_set_parent_and_color(x, s, C_RBNODE_BLACK); + x = s; + s = y; + } + + /* Case 6: + * The right child of our sibling is red. Rotate left and flip + * colors, which gains us an additional black node in our path, + * that was previously on our sibling. */ + g = c_rbnode_parent(p); + y = s->left; + p->right = y; + s->left = p; + c_rbnode_set_parent_and_color(x, s, C_RBNODE_BLACK); + if (y) + c_rbnode_set_parent_and_color(y, p, c_rbnode_color(y)); + c_rbnode_set_parent_and_color(s, g, c_rbnode_color(p)); + c_rbnode_set_parent_and_color(p, s, C_RBNODE_BLACK); + c_rbtree_swap_child(t, g, p, s); + } else /* if (!n || n == p->right) */ { /* same as above, but mirrored */ + s = p->left; + if (c_rbnode_is_red(s)) { + g = c_rbnode_parent(p); + x = s->right; + p->left = x; + s->right = p; + c_rbnode_set_parent_and_color(x, p, C_RBNODE_BLACK); + c_rbnode_set_parent_and_color(s, g, C_RBNODE_BLACK); + c_rbnode_set_parent_and_color(p, s, C_RBNODE_RED); + c_rbtree_swap_child(t, g, p, s); + s = x; + } + + x = s->left; + if (!x || c_rbnode_is_black(x)) { + y = s->right; + if (!y || c_rbnode_is_black(y)) { + c_rbnode_set_parent_and_color(s, p, C_RBNODE_RED); + if (c_rbnode_is_black(p)) + return p; + + c_rbnode_set_parent_and_color(p, c_rbnode_parent(p), C_RBNODE_BLACK); + return NULL; + } + + x = y->left; + s->right = y->left; + y->left = s; + p->left = y; + if (x) + c_rbnode_set_parent_and_color(x, s, C_RBNODE_BLACK); + x = s; + s = y; + } + + g = c_rbnode_parent(p); + y = s->right; + p->left = y; + s->right = p; + c_rbnode_set_parent_and_color(x, s, C_RBNODE_BLACK); + if (y) + c_rbnode_set_parent_and_color(y, p, c_rbnode_color(y)); + c_rbnode_set_parent_and_color(s, g, c_rbnode_color(p)); + c_rbnode_set_parent_and_color(p, s, C_RBNODE_BLACK); + c_rbtree_swap_child(t, g, p, s); + } + + return NULL; +} + +static inline void c_rbtree_rebalance(CRBTree *t, CRBNode *p) { + CRBNode *n = NULL; + + assert(t); + assert(p); + + do { + n = c_rbtree_rebalance_one(t, p, n); + p = n ? c_rbnode_parent(n) : NULL; + } while (p); +} + +/** + * c_rbtree_remove() - remove node from tree + * @t: tree to operate one + * @n: node to remove + * + * This removes the given node from its tree. Once unlinked, the tree is + * rebalanced. + * The caller *must* ensure that the given tree is actually the tree it is + * linked on. Otherwise, behavior is undefined. + * + * This does *NOT* reset @n to being unlinked (for performance reason, this + * function *never* modifies @n at all). If you need this, use + * c_rbtree_remove_init(). + */ +void c_rbtree_remove(CRBTree *t, CRBNode *n) { + CRBNode *p, *s, *gc, *x, *next = NULL; + unsigned long c; + + assert(t); + assert(n); + assert(c_rbnode_is_linked(n)); + + /* + * There are three distinct cases during node removal of a tree: + * * The node has no children, in which case it can simply be removed. + * * The node has exactly one child, in which case the child displaces + * its parent. + * * The node has two children, in which case there is guaranteed to + * be a successor to the node (successor being the node ordered + * directly after it). This successor cannot have two children by + * itself (two interior nodes can never be successive). Therefore, + * we can simply swap the node with its successor (including color) + * and have reduced this case to either of the first two. + * + * Whenever the node we removed was black, we have to rebalance the + * tree. Note that this affects the actual node we _remove_, not @n (in + * case we swap it). + * + * p: parent + * s: successor + * gc: grand-...-child + * x: temporary + * next: next node to rebalance on + */ + + if (!n->left) { + /* + * Case 1: + * The node has no left child. If it neither has a right child, + * it is a leaf-node and we can simply unlink it. If it also + * was black, we have to rebalance, as always if we remove a + * black node. + * But if the node has a right child, the child *must* be red + * (otherwise, the right path has more black nodes as the + * non-existing left path), and the node to be removed must + * hence be black. We simply replace the node with its child, + * turning the red child black, and thus no rebalancing is + * required. + */ + p = c_rbnode_parent(n); + c = c_rbnode_color(n); + c_rbtree_swap_child(t, p, n, n->right); + if (n->right) + c_rbnode_set_parent_and_color(n->right, p, c); + else + next = (c == C_RBNODE_BLACK) ? p : NULL; + } else if (!n->right) { + /* + * Case 1.1: + * The node has exactly one child, and it is on the left. Treat + * it as mirrored case of Case 1 (i.e., replace the node by its + * child). + */ + p = c_rbnode_parent(n); + c = c_rbnode_color(n); + c_rbtree_swap_child(t, p, n, n->left); + c_rbnode_set_parent_and_color(n->left, p, c); + } else { + /* + * Case 2: + * We are dealing with a full interior node with a child not on + * both sides. Find its successor and swap it. Then remove the + * node similar to Case 1. For performance reasons we don't + * perform the full swap, but skip links that are about to be + * removed, anyway. + */ + s = n->right; + if (!s->left) { + /* right child is next, no need to touch grandchild */ + p = s; + gc = s->right; + } else { + /* find successor and swap partially */ + s = c_rbnode_leftmost(s); + p = c_rbnode_parent(s); + + gc = s->right; + p->left = s->right; + s->right = n->right; + c_rbnode_set_parent(n->right, s); + } + + /* node is partially swapped, now remove as in Case 1 */ + s->left = n->left; + c_rbnode_set_parent(n->left, s); + + x = c_rbnode_parent(n); + c = c_rbnode_color(n); + c_rbtree_swap_child(t, x, n, s); + if (gc) + c_rbnode_set_parent_and_color(gc, p, C_RBNODE_BLACK); + else + next = c_rbnode_is_black(s) ? p : NULL; + c_rbnode_set_parent_and_color(s, x, c); + } + + if (next) + c_rbtree_rebalance(t, next); +} diff --git a/src/libbasic/c-rbtree.h b/src/libbasic/c-rbtree.h new file mode 100644 index 0000000000..20c5515ca1 --- /dev/null +++ b/src/libbasic/c-rbtree.h @@ -0,0 +1,297 @@ +#pragma once + +/*** + This file is part of systemd. See COPYING for details. + + 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 . +***/ + +/* + * Standalone Red-Black-Tree Implementation in Standard ISO-C11 + * + * This header provides an RB-Tree API, that is fully implemented in ISO-C11 + * and has no external dependencies. Furthermore, tree traversal, memory + * allocations, and key comparisons a fully in control of the API user. The + * implementation only provides the RB-Tree specific rebalancing and coloring. + * + * A tree is represented by the "CRBTree" structure. It contains a *singly* + * field, which is a pointer to the root node. If NULL, the tree is empty. If + * non-NULL, there is at least a single element in the tree. + * + * Each node of the tree is represented by the "CRBNode" structure. It has + * three fields. The @left and @right members can be accessed by the API user + * directly to traverse the tree. The third member is an implementation detail + * and encodes the parent pointer and color of the node. + * API users are required to embed the CRBNode object into their own objects + * and then use offsetof() (i.e., container_of() and friends) to turn CRBNode + * pointers into pointers to their own structure. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct CRBNode CRBNode; +typedef struct CRBTree CRBTree; + +/** + * struct CRBNode - Node of a Red-Black Tree + * @__parent_and_color: internal state + * @left: left child, or NULL + * @right: right child, or NULL + * + * Each node in an RB-Tree must embed an CRBNode object. This object contains + * pointers to its left and right child, which can be freely accessed by the + * API user at any time. They are NULL, if the node does not have a left/right + * child. + * + * The @__parent_and_color field must never be accessed directly. It encodes + * the pointer to the parent node, and the color of the node. Use the accessor + * functions instead. + * + * There is no reason to initialize a CRBNode object before linking it. + * However, if you need a boolean state that tells you whether the node is + * linked or not, you should initialize the node via c_rbnode_init() or + * C_RBNODE_INIT. + */ +struct CRBNode { + CRBNode *__parent_and_color; + CRBNode *left; + CRBNode *right; +}; + +#define C_RBNODE_INIT(_var) { .__parent_and_color = &(_var) } + +CRBNode *c_rbnode_leftmost(CRBNode *n); +CRBNode *c_rbnode_rightmost(CRBNode *n); +CRBNode *c_rbnode_next(CRBNode *n); +CRBNode *c_rbnode_prev(CRBNode *n); + +/** + * struct CRBTree - Red-Black Tree + * @root: pointer to the root node, or NULL + * + * Each Red-Black Tree is rooted in an CRBTree object. This object contains a + * pointer to the root node of the tree. The API user is free to access the + * @root member at any time, and use it to traverse the tree. + * + * To initialize an RB-Tree, set it to NULL / all zero. + */ +struct CRBTree { + CRBNode *root; +}; + +CRBNode *c_rbtree_first(CRBTree *t); +CRBNode *c_rbtree_last(CRBTree *t); + +void c_rbtree_add(CRBTree *t, CRBNode *p, CRBNode **l, CRBNode *n); +void c_rbtree_remove(CRBTree *t, CRBNode *n); + +/** + * c_rbnode_init() - mark a node as unlinked + * @n: node to operate on + * + * This marks the node @n as unlinked. The node will be set to a valid state + * that can never happen if the node is linked in a tree. Furthermore, this + * state is fully known to the implementation, and as such handled gracefully + * in all cases. + * + * You are *NOT* required to call this on your node. c_rbtree_add() can handle + * uninitialized nodes just fine. However, calling this allows to use + * c_rbnode_is_linked() to check for the state of a node. Furthermore, + * iterators and accessors can be called on initialized (yet unlinked) nodes. + * + * Use the C_RBNODE_INIT macro if you want to initialize static variables. + */ +static inline void c_rbnode_init(CRBNode *n) { + *n = (CRBNode)C_RBNODE_INIT(*n); +} + +/** + * c_rbnode_is_linked() - check whether a node is linked + * @n: node to check, or NULL + * + * This checks whether the passed node is linked. If you pass NULL, or if the + * node is not linked into a tree, this will return false. Otherwise, this + * returns true. + * + * Note that you must have either linked the node or initialized it, before + * calling this function. Never call this function on uninitialized nodes. + * Furthermore, removing a node via c_rbtree_remove() does *NOT* mark the node + * as unlinked. You have to call c_rbnode_init() yourself after removal, or use + * the c_rbtree_remove_init() helper. + * + * Return: true if the node is linked, false if not. + */ +static inline _Bool c_rbnode_is_linked(CRBNode *n) { + return n && n->__parent_and_color != n; +} + +/** + * c_rbnode_parent() - return parent pointer + * @n node to access + * + * This returns a pointer to the parent of the given node @n. If @n does not + * have a parent, NULL is returned. If @n is not linked, @n itself is returned. + * + * You should not call this on unlinked or uninitialized nodes! If you do, you + * better know how its semantics. + * + * Return: Pointer to parent. + */ +static inline CRBNode *c_rbnode_parent(CRBNode *n) { + return (CRBNode*)((unsigned long)n->__parent_and_color & ~1UL); +} + +/** + * c_rbtree_remove_init() - safely remove node from tree and reinitialize it + * @t: tree to operate on + * @n: node to remove, or NULL + * + * This is almost the same as c_rbtree_remove(), but extends it slightly, to be + * more convenient to use in many cases: + * - if @n is unlinked or NULL, this is a no-op + * - @n is reinitialized after being removed + */ +static inline void c_rbtree_remove_init(CRBTree *t, CRBNode *n) { + if (c_rbnode_is_linked(n)) { + c_rbtree_remove(t, n); + c_rbnode_init(n); + } +} + +/** + * CRBCompareFunc - compare a node to a key + * @t: tree where the node is linked to + * @k: key to compare + * @n: node to compare + * + * If you use the tree-traversal helpers (which are optional), you need to + * provide this callback so they can compare nodes in a tree to the key you + * look for. + * + * The tree @t is provided as optional context to this callback. The key you + * look for is provided as @k, the current node that should be compared to is + * provided as @n. This function should work like strcmp(), that is, return -1 + * if @key orders before @n, 0 if both compare equal, and 1 if it orders after + * @n. + */ +typedef int (*CRBCompareFunc) (CRBTree *t, void *k, CRBNode *n); + +/** + * c_rbtree_find_node() - find node + * @t: tree to search through + * @f: comparison function + * @k: key to search for + * + * This searches through @t for a node that compares equal to @k. The function + * @f must be provided by the caller, which is used to compare nodes to @k. See + * the documentation of CRBCompareFunc for details. + * + * If there are multiple entries that compare equal to @k, this will return a + * pseudo-randomly picked node. If you need stable lookup functions for trees + * where duplicate entries are allowed, you better code your own lookup. + * + * Return: Pointer to matching node, or NULL. + */ +static inline CRBNode *c_rbtree_find_node(CRBTree *t, CRBCompareFunc f, const void *k) { + CRBNode *i; + + assert(t); + assert(f); + + i = t->root; + while (i) { + int v = f(t, (void *)k, i); + if (v < 0) + i = i->left; + else if (v > 0) + i = i->right; + else + return i; + } + + return NULL; +} + +/** + * c_rbtree_find_entry() - find entry + * @_t: tree to search through + * @_f: comparison function + * @_k: key to search for + * @_t: type of the structure that embeds the nodes + * @_o: name of the node-member in type @_t + * + * This is very similar to c_rbtree_find_node(), but instead of returning a + * pointer to the CRBNode, it returns a pointer to the surrounding object. This + * object must embed the CRBNode object. The type of the surrounding object + * must be given as @_t, and the name of the embedded CRBNode member as @_o. + * + * See c_rbtree_find_node() for more details. + * + * Return: Pointer to found entry, NULL if not found. + */ +#define c_rbtree_find_entry(_m, _f, _k, _t, _o) \ + ((_t *)(((char *)c_rbtree_find_node((_m), (_f), (_k)) ?: \ + (char *)NULL + offsetof(_t, _o)) - offsetof(_t, _o))) + +/** + * c_rbtree_find_slot() - find slot to insert new node + * @t: tree to search through + * @f: comparison function + * @k: key to search for + * @p: output storage for parent pointer + * + * This searches through @t just like c_rbtree_find_node() does. However, + * instead of returning a pointer to a node that compares equal to @k, this + * searches for a slot to insert a node with key @k. A pointer to the slot is + * returned, and a pointer to the parent of the slot is stored in @p. Both + * can be passed directly to c_rbtree_add(), together with your node to insert. + * + * If there already is a node in the tree, that compares equal to @k, this will + * return NULL and store the conflicting node in @p. In all other cases, + * this will return a pointer (non-NULL) to the empty slot to insert the node + * at. @p will point to the parent node of that slot. + * + * If you want trees that allow duplicate nodes, you better code your own + * insertion function. + * + * Return: Pointer to slot to insert node, or NULL on conflicts. + */ +static inline CRBNode **c_rbtree_find_slot(CRBTree *t, CRBCompareFunc f, const void *k, CRBNode **p) { + CRBNode **i; + + assert(t); + assert(f); + assert(p); + + i = &t->root; + *p = NULL; + while (*i) { + int v = f(t, (void *)k, *i); + *p = *i; + if (v < 0) + i = &(*i)->left; + else if (v > 0) + i = &(*i)->right; + else + return NULL; + } + + return i; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/libbasic/calendarspec.c b/src/libbasic/calendarspec.c new file mode 100644 index 0000000000..775879076d --- /dev/null +++ b/src/libbasic/calendarspec.c @@ -0,0 +1,1087 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "calendarspec.h" +#include "fileio.h" +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" + +#define BITS_WEEKDAYS 127 + +static void free_chain(CalendarComponent *c) { + CalendarComponent *n; + + while (c) { + n = c->next; + free(c); + c = n; + } +} + +void calendar_spec_free(CalendarSpec *c) { + + if (!c) + return; + + free_chain(c->year); + free_chain(c->month); + free_chain(c->day); + free_chain(c->hour); + free_chain(c->minute); + free_chain(c->microsecond); + + free(c); +} + +static int component_compare(const void *_a, const void *_b) { + CalendarComponent * const *a = _a, * const *b = _b; + + if ((*a)->value < (*b)->value) + return -1; + if ((*a)->value > (*b)->value) + return 1; + + if ((*a)->repeat < (*b)->repeat) + return -1; + if ((*a)->repeat > (*b)->repeat) + return 1; + + return 0; +} + +static void sort_chain(CalendarComponent **c) { + unsigned n = 0, k; + CalendarComponent **b, *i, **j, *next; + + assert(c); + + for (i = *c; i; i = i->next) + n++; + + if (n <= 1) + return; + + j = b = alloca(sizeof(CalendarComponent*) * n); + for (i = *c; i; i = i->next) + *(j++) = i; + + qsort(b, n, sizeof(CalendarComponent*), component_compare); + + b[n-1]->next = NULL; + next = b[n-1]; + + /* Drop non-unique entries */ + for (k = n-1; k > 0; k--) { + if (b[k-1]->value == next->value && + b[k-1]->repeat == next->repeat) { + free(b[k-1]); + continue; + } + + b[k-1]->next = next; + next = b[k-1]; + } + + *c = next; +} + +static void fix_year(CalendarComponent *c) { + /* Turns 12 → 2012, 89 → 1989 */ + + while(c) { + CalendarComponent *n = c->next; + + if (c->value >= 0 && c->value < 70) + c->value += 2000; + + if (c->value >= 70 && c->value < 100) + c->value += 1900; + + c = n; + } +} + +int calendar_spec_normalize(CalendarSpec *c) { + assert(c); + + if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS) + c->weekdays_bits = -1; + + fix_year(c->year); + + sort_chain(&c->year); + sort_chain(&c->month); + sort_chain(&c->day); + sort_chain(&c->hour); + sort_chain(&c->minute); + sort_chain(&c->microsecond); + + return 0; +} + +_pure_ static bool chain_valid(CalendarComponent *c, int from, int to) { + if (!c) + return true; + + if (c->value < from || c->value > to) + return false; + + if (c->value + c->repeat > to) + return false; + + if (c->next) + return chain_valid(c->next, from, to); + + return true; +} + +_pure_ bool calendar_spec_valid(CalendarSpec *c) { + assert(c); + + if (c->weekdays_bits > BITS_WEEKDAYS) + return false; + + if (!chain_valid(c->year, 1970, 2199)) + return false; + + if (!chain_valid(c->month, 1, 12)) + return false; + + if (!chain_valid(c->day, 1, 31)) + return false; + + if (!chain_valid(c->hour, 0, 23)) + return false; + + if (!chain_valid(c->minute, 0, 59)) + return false; + + if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1)) + return false; + + return true; +} + +static void format_weekdays(FILE *f, const CalendarSpec *c) { + static const char *const days[] = { + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", + "Sun" + }; + + int l, x; + bool need_colon = false; + + assert(f); + assert(c); + assert(c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS); + + for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) { + + if (c->weekdays_bits & (1 << x)) { + + if (l < 0) { + if (need_colon) + fputc(',', f); + else + need_colon = true; + + fputs(days[x], f); + l = x; + } + + } else if (l >= 0) { + + if (x > l + 1) { + fputc(x > l + 2 ? '-' : ',', f); + fputs(days[x-1], f); + } + + l = -1; + } + } + + if (l >= 0 && x > l + 1) { + fputc(x > l + 2 ? '-' : ',', f); + fputs(days[x-1], f); + } +} + +static void format_chain(FILE *f, int space, const CalendarComponent *c, bool usec) { + assert(f); + + if (!c) { + fputc('*', f); + return; + } + + assert(c->value >= 0); + if (!usec) + fprintf(f, "%0*i", space, c->value); + else if (c->value % USEC_PER_SEC == 0) + fprintf(f, "%0*i", space, (int) (c->value / USEC_PER_SEC)); + else + fprintf(f, "%0*i.%06i", space, (int) (c->value / USEC_PER_SEC), (int) (c->value % USEC_PER_SEC)); + + if (c->repeat > 0) { + if (!usec) + fprintf(f, "/%i", c->repeat); + else if (c->repeat % USEC_PER_SEC == 0) + fprintf(f, "/%i", (int) (c->repeat / USEC_PER_SEC)); + else + fprintf(f, "/%i.%06i", (int) (c->repeat / USEC_PER_SEC), (int) (c->repeat % USEC_PER_SEC)); + } + + if (c->next) { + fputc(',', f); + format_chain(f, space, c->next, usec); + } +} + +int calendar_spec_to_string(const CalendarSpec *c, char **p) { + char *buf = NULL; + size_t sz = 0; + FILE *f; + int r; + + assert(c); + assert(p); + + f = open_memstream(&buf, &sz); + if (!f) + return -ENOMEM; + + if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) { + format_weekdays(f, c); + fputc(' ', f); + } + + format_chain(f, 4, c->year, false); + fputc('-', f); + format_chain(f, 2, c->month, false); + fputc('-', f); + format_chain(f, 2, c->day, false); + fputc(' ', f); + format_chain(f, 2, c->hour, false); + fputc(':', f); + format_chain(f, 2, c->minute, false); + fputc(':', f); + format_chain(f, 2, c->microsecond, true); + + if (c->utc) + fputs(" UTC", f); + + r = fflush_and_check(f); + if (r < 0) { + free(buf); + fclose(f); + return r; + } + + fclose(f); + + *p = buf; + return 0; +} + +static int parse_weekdays(const char **p, CalendarSpec *c) { + static const struct { + const char *name; + const int nr; + } day_nr[] = { + { "Monday", 0 }, + { "Mon", 0 }, + { "Tuesday", 1 }, + { "Tue", 1 }, + { "Wednesday", 2 }, + { "Wed", 2 }, + { "Thursday", 3 }, + { "Thu", 3 }, + { "Friday", 4 }, + { "Fri", 4 }, + { "Saturday", 5 }, + { "Sat", 5 }, + { "Sunday", 6 }, + { "Sun", 6 } + }; + + int l = -1; + bool first = true; + + assert(p); + assert(*p); + assert(c); + + for (;;) { + unsigned i; + + if (!first && **p == ' ') + return 0; + + for (i = 0; i < ELEMENTSOF(day_nr); i++) { + size_t skip; + + if (!startswith_no_case(*p, day_nr[i].name)) + continue; + + skip = strlen(day_nr[i].name); + + if ((*p)[skip] != '-' && + (*p)[skip] != ',' && + (*p)[skip] != ' ' && + (*p)[skip] != 0) + return -EINVAL; + + c->weekdays_bits |= 1 << day_nr[i].nr; + + if (l >= 0) { + int j; + + if (l > day_nr[i].nr) + return -EINVAL; + + for (j = l + 1; j < day_nr[i].nr; j++) + c->weekdays_bits |= 1 << j; + } + + *p += skip; + break; + } + + /* Couldn't find this prefix, so let's assume the + weekday was not specified and let's continue with + the date */ + if (i >= ELEMENTSOF(day_nr)) + return first ? 0 : -EINVAL; + + /* We reached the end of the string */ + if (**p == 0) + return 0; + + /* We reached the end of the weekday spec part */ + if (**p == ' ') { + *p += strspn(*p, " "); + return 0; + } + + if (**p == '-') { + if (l >= 0) + return -EINVAL; + + l = day_nr[i].nr; + } else + l = -1; + + *p += 1; + first = false; + } +} + +static int parse_component_decimal(const char **p, bool usec, unsigned long *res) { + unsigned long value; + const char *e = NULL; + char *ee = NULL; + int r; + + errno = 0; + value = strtoul(*p, &ee, 10); + if (errno > 0) + return -errno; + if (ee == *p) + return -EINVAL; + if ((unsigned long) (int) value != value) + return -ERANGE; + e = ee; + + if (usec) { + if (value * USEC_PER_SEC / USEC_PER_SEC != value) + return -ERANGE; + + value *= USEC_PER_SEC; + if (*e == '.') { + unsigned add; + + e++; + r = parse_fractional_part_u(&e, 6, &add); + if (r < 0) + return r; + + if (add + value < value) + return -ERANGE; + value += add; + } + } + + *p = e; + *res = value; + + return 0; +} + +static int prepend_component(const char **p, bool usec, CalendarComponent **c) { + unsigned long value, repeat = 0; + CalendarComponent *cc; + int r; + const char *e; + + assert(p); + assert(c); + + e = *p; + + r = parse_component_decimal(&e, usec, &value); + if (r < 0) + return r; + + if (*e == '/') { + e++; + r = parse_component_decimal(&e, usec, &repeat); + if (r < 0) + return r; + + if (repeat == 0) + return -ERANGE; + } + + if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':') + return -EINVAL; + + cc = new0(CalendarComponent, 1); + if (!cc) + return -ENOMEM; + + cc->value = value; + cc->repeat = repeat; + cc->next = *c; + + *p = e; + *c = cc; + + if (*e ==',') { + *p += 1; + return prepend_component(p, usec, 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; + int r; + + assert(p); + assert(c); + + t = *p; + + if (t[0] == '*') { + if (usec) { + r = const_chain(0, c); + if (r < 0) + return r; + (*c)->repeat = USEC_PER_SEC; + } else + *c = NULL; + + *p = t + 1; + return 0; + } + + r = prepend_component(&t, usec, &cc); + if (r < 0) { + free_chain(cc); + return r; + } + + *p = t; + *c = cc; + return 0; +} + +static int parse_date(const char **p, CalendarSpec *c) { + const char *t; + int r; + CalendarComponent *first, *second, *third; + + assert(p); + assert(*p); + assert(c); + + t = *p; + + if (*t == 0) + return 0; + + r = parse_chain(&t, false, &first); + if (r < 0) + return r; + + /* Already the end? A ':' as separator? In that case this was a time, not a date */ + if (*t == 0 || *t == ':') { + free_chain(first); + return 0; + } + + if (*t != '-') { + free_chain(first); + return -EINVAL; + } + + t++; + r = parse_chain(&t, false, &second); + if (r < 0) { + free_chain(first); + return r; + } + + /* Got two parts, hence it's month and day */ + if (*t == ' ' || *t == 0) { + *p = t + strspn(t, " "); + c->month = first; + c->day = second; + return 0; + } + + if (*t != '-') { + free_chain(first); + free_chain(second); + return -EINVAL; + } + + t++; + r = parse_chain(&t, false, &third); + if (r < 0) { + free_chain(first); + free_chain(second); + return r; + } + + /* Got tree parts, hence it is year, month and day */ + if (*t == ' ' || *t == 0) { + *p = t + strspn(t, " "); + c->year = first; + c->month = second; + c->day = third; + return 0; + } + + free_chain(first); + free_chain(second); + free_chain(third); + return -EINVAL; +} + +static int parse_calendar_time(const char **p, CalendarSpec *c) { + CalendarComponent *h = NULL, *m = NULL, *s = NULL; + const char *t; + int r; + + assert(p); + assert(*p); + assert(c); + + t = *p; + + if (*t == 0) { + /* If no time is specified at all, but a date of some + * kind, then this means 00:00:00 */ + if (c->day || c->weekdays_bits > 0) + goto null_hour; + + goto finish; + } + + r = parse_chain(&t, false, &h); + if (r < 0) + goto fail; + + if (*t != ':') { + r = -EINVAL; + goto fail; + } + + t++; + r = parse_chain(&t, false, &m); + if (r < 0) + goto fail; + + /* Already at the end? Then it's hours and minutes, and seconds are 0 */ + if (*t == 0) { + if (m != NULL) + goto null_second; + + goto finish; + } + + if (*t != ':') { + r = -EINVAL; + goto fail; + } + + t++; + r = parse_chain(&t, true, &s); + if (r < 0) + goto fail; + + /* At the end? Then it's hours, minutes and seconds */ + if (*t == 0) + goto finish; + + r = -EINVAL; + goto fail; + +null_hour: + r = const_chain(0, &h); + if (r < 0) + goto fail; + + r = const_chain(0, &m); + if (r < 0) + goto fail; + +null_second: + r = const_chain(0, &s); + if (r < 0) + goto fail; + +finish: + *p = t; + c->hour = h; + c->minute = m; + c->microsecond = s; + + return 0; + +fail: + free_chain(h); + free_chain(m); + free_chain(s); + return r; +} + +int calendar_spec_from_string(const char *p, CalendarSpec **spec) { + CalendarSpec *c; + int r; + const char *utc; + + assert(p); + assert(spec); + + if (isempty(p)) + return -EINVAL; + + c = new0(CalendarSpec, 1); + if (!c) + return -ENOMEM; + + utc = endswith_no_case(p, " UTC"); + if (utc) { + c->utc = true; + p = strndupa(p, utc - p); + } + + if (strcaseeq(p, "minutely")) { + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "hourly")) { + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "daily")) { + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "monthly")) { + r = const_chain(1, &c->day); + if (r < 0) + goto fail; + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "annually") || + strcaseeq(p, "yearly") || + strcaseeq(p, "anually") /* backwards compatibility */ ) { + + r = const_chain(1, &c->month); + if (r < 0) + goto fail; + r = const_chain(1, &c->day); + if (r < 0) + goto fail; + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "weekly")) { + + c->weekdays_bits = 1; + + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "quarterly")) { + + r = const_chain(1, &c->month); + if (r < 0) + goto fail; + r = const_chain(4, &c->month); + if (r < 0) + goto fail; + r = const_chain(7, &c->month); + if (r < 0) + goto fail; + r = const_chain(10, &c->month); + if (r < 0) + goto fail; + r = const_chain(1, &c->day); + if (r < 0) + goto fail; + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else if (strcaseeq(p, "biannually") || + strcaseeq(p, "bi-annually") || + strcaseeq(p, "semiannually") || + strcaseeq(p, "semi-annually")) { + + r = const_chain(1, &c->month); + if (r < 0) + goto fail; + r = const_chain(7, &c->month); + if (r < 0) + goto fail; + r = const_chain(1, &c->day); + if (r < 0) + goto fail; + r = const_chain(0, &c->hour); + if (r < 0) + goto fail; + r = const_chain(0, &c->minute); + if (r < 0) + goto fail; + r = const_chain(0, &c->microsecond); + if (r < 0) + goto fail; + + } else { + r = parse_weekdays(&p, c); + if (r < 0) + goto fail; + + r = parse_date(&p, c); + if (r < 0) + goto fail; + + r = parse_calendar_time(&p, c); + if (r < 0) + goto fail; + + if (*p != 0) { + r = -EINVAL; + goto fail; + } + } + + r = calendar_spec_normalize(c); + if (r < 0) + goto fail; + + if (!calendar_spec_valid(c)) { + r = -EINVAL; + goto fail; + } + + *spec = c; + return 0; + +fail: + calendar_spec_free(c); + return r; +} + +static int find_matching_component(const CalendarComponent *c, int *val) { + const CalendarComponent *n; + int d = -1; + bool d_set = false; + int r; + + assert(val); + + if (!c) + return 0; + + while (c) { + n = c->next; + + if (c->value >= *val) { + + if (!d_set || c->value < d) { + d = c->value; + d_set = true; + } + + } else if (c->repeat > 0) { + int k; + + k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat); + + if (!d_set || k < d) { + d = k; + d_set = true; + } + } + + c = n; + } + + if (!d_set) + return -ENOENT; + + r = *val != d; + *val = d; + return r; +} + +static bool tm_out_of_bounds(const struct tm *tm, bool utc) { + struct tm t; + assert(tm); + + t = *tm; + + if (mktime_or_timegm(&t, utc) == (time_t) -1) + return true; + + /* Did any normalization take place? If so, it was out of bounds before */ + return + t.tm_year != tm->tm_year || + t.tm_mon != tm->tm_mon || + t.tm_mday != tm->tm_mday || + t.tm_hour != tm->tm_hour || + t.tm_min != tm->tm_min || + t.tm_sec != tm->tm_sec; +} + +static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) { + struct tm t; + int k; + + if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS) + return true; + + t = *tm; + if (mktime_or_timegm(&t, utc) == (time_t) -1) + return false; + + k = t.tm_wday == 0 ? 6 : t.tm_wday - 1; + return (weekdays_bits & (1 << k)); +} + +static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) { + struct tm c; + int tm_usec; + int r; + + assert(spec); + assert(tm); + + c = *tm; + tm_usec = *usec; + + for (;;) { + /* Normalize the current date */ + mktime_or_timegm(&c, spec->utc); + c.tm_isdst = -1; + + c.tm_year += 1900; + r = find_matching_component(spec->year, &c.tm_year); + c.tm_year -= 1900; + + if (r > 0) { + c.tm_mon = 0; + c.tm_mday = 1; + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + } + if (r < 0 || tm_out_of_bounds(&c, spec->utc)) + return r; + + c.tm_mon += 1; + r = find_matching_component(spec->month, &c.tm_mon); + c.tm_mon -= 1; + + if (r > 0) { + c.tm_mday = 1; + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + } + if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + c.tm_year ++; + c.tm_mon = 0; + c.tm_mday = 1; + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + continue; + } + + r = find_matching_component(spec->day, &c.tm_mday); + if (r > 0) + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + c.tm_mon ++; + c.tm_mday = 1; + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + continue; + } + + if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) { + c.tm_mday++; + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + continue; + } + + r = find_matching_component(spec->hour, &c.tm_hour); + if (r > 0) + c.tm_min = c.tm_sec = 0; + if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + c.tm_mday ++; + c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0; + continue; + } + + r = find_matching_component(spec->minute, &c.tm_min); + if (r > 0) + c.tm_sec = 0; + if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + c.tm_hour ++; + c.tm_min = c.tm_sec = tm_usec = 0; + continue; + } + + c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec; + r = find_matching_component(spec->microsecond, &c.tm_sec); + tm_usec = c.tm_sec % USEC_PER_SEC; + c.tm_sec /= USEC_PER_SEC; + + if (r < 0 || tm_out_of_bounds(&c, spec->utc)) { + c.tm_min ++; + c.tm_sec = tm_usec = 0; + continue; + } + + *tm = c; + *usec = tm_usec; + return 0; + } +} + +int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) { + struct tm tm; + time_t t; + int r; + usec_t tm_usec; + + assert(spec); + assert(next); + + usec++; + t = (time_t) (usec / USEC_PER_SEC); + assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc)); + tm_usec = usec % USEC_PER_SEC; + + r = find_next(spec, &tm, &tm_usec); + if (r < 0) + return r; + + t = mktime_or_timegm(&tm, spec->utc); + if (t == (time_t) -1) + return -EINVAL; + + *next = (usec_t) t * USEC_PER_SEC + tm_usec; + return 0; +} diff --git a/src/libbasic/calendarspec.h b/src/libbasic/calendarspec.h new file mode 100644 index 0000000000..f6472c1244 --- /dev/null +++ b/src/libbasic/calendarspec.h @@ -0,0 +1,58 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +/* A structure for specifying (possibly repetitive) points in calendar + * time, a la cron */ + +#include + +#include "time-util.h" +#include "util.h" + +typedef struct CalendarComponent { + int value; + int repeat; + + struct CalendarComponent *next; +} CalendarComponent; + +typedef struct CalendarSpec { + int weekdays_bits; + bool utc; + + CalendarComponent *year; + CalendarComponent *month; + CalendarComponent *day; + + CalendarComponent *hour; + CalendarComponent *minute; + CalendarComponent *microsecond; +} CalendarSpec; + +void calendar_spec_free(CalendarSpec *c); + +int calendar_spec_normalize(CalendarSpec *spec); +bool calendar_spec_valid(CalendarSpec *spec); + +int calendar_spec_to_string(const CalendarSpec *spec, char **p); +int calendar_spec_from_string(const char *p, CalendarSpec **spec); + +int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next); diff --git a/src/libbasic/cap-list.c b/src/libbasic/cap-list.c new file mode 100644 index 0000000000..3e773a06f5 --- /dev/null +++ b/src/libbasic/cap-list.c @@ -0,0 +1,66 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include + +#include "cap-list.h" +#include "macro.h" +#include "missing.h" +#include "parse-util.h" +#include "util.h" + +static const struct capability_name* lookup_capability(register const char *str, register unsigned int len); + +#include "cap-from-name.h" +#include "cap-to-name.h" + +const char *capability_to_name(int id) { + + if (id < 0) + return NULL; + + if (id >= (int) ELEMENTSOF(capability_names)) + return NULL; + + return capability_names[id]; +} + +int capability_from_name(const char *name) { + const struct capability_name *sc; + int r, i; + + assert(name); + + /* Try to parse numeric capability */ + r = safe_atoi(name, &i); + if (r >= 0 && i >= 0) + return i; + + /* Try to parse string capability */ + sc = lookup_capability(name, strlen(name)); + if (!sc) + return -EINVAL; + + return sc->id; +} + +int capability_list_length(void) { + return (int) ELEMENTSOF(capability_names); +} diff --git a/src/libbasic/cap-list.h b/src/libbasic/cap-list.h new file mode 100644 index 0000000000..c1f6b94ad3 --- /dev/null +++ b/src/libbasic/cap-list.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +const char *capability_to_name(int id); +int capability_from_name(const char *name); +int capability_list_length(void); diff --git a/src/libbasic/capability-util.c b/src/libbasic/capability-util.c new file mode 100644 index 0000000000..d4c5bd6937 --- /dev/null +++ b/src/libbasic/capability-util.c @@ -0,0 +1,361 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "capability-util.h" +#include "fileio.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "util.h" + +int have_effective_cap(int value) { + _cleanup_cap_free_ cap_t cap; + cap_flag_value_t fv; + + cap = cap_get_proc(); + if (!cap) + return -errno; + + if (cap_get_flag(cap, value, CAP_EFFECTIVE, &fv) < 0) + return -errno; + else + return fv == CAP_SET; +} + +unsigned long cap_last_cap(void) { + static thread_local unsigned long saved; + static thread_local bool valid = false; + _cleanup_free_ char *content = NULL; + unsigned long p = 0; + int r; + + if (valid) + return saved; + + /* available since linux-3.2 */ + r = read_one_line_file("/proc/sys/kernel/cap_last_cap", &content); + if (r >= 0) { + r = safe_atolu(content, &p); + if (r >= 0) { + saved = p; + valid = true; + return p; + } + } + + /* fall back to syscall-probing for pre linux-3.2 */ + p = (unsigned long) CAP_LAST_CAP; + + if (prctl(PR_CAPBSET_READ, p) < 0) { + + /* Hmm, look downwards, until we find one that + * works */ + for (p--; p > 0; p --) + if (prctl(PR_CAPBSET_READ, p) >= 0) + break; + + } else { + + /* Hmm, look upwards, until we find one that doesn't + * work */ + for (;; p++) + if (prctl(PR_CAPBSET_READ, p+1) < 0) + break; + } + + saved = p; + valid = true; + + return p; +} + +int capability_update_inherited_set(cap_t caps, uint64_t set) { + unsigned long i; + + /* Add capabilities in the set to the inherited caps. Do not apply + * them yet. */ + + for (i = 0; i < cap_last_cap(); i++) { + + if (set & (UINT64_C(1) << i)) { + cap_value_t v; + + v = (cap_value_t) i; + + /* Make the capability inheritable. */ + if (cap_set_flag(caps, CAP_INHERITABLE, 1, &v, CAP_SET) < 0) + return -errno; + } + } + + return 0; +} + +int capability_ambient_set_apply(uint64_t set, bool also_inherit) { + unsigned long i; + _cleanup_cap_free_ cap_t caps = NULL; + + /* Add the capabilities to the ambient set. */ + + if (also_inherit) { + int r; + caps = cap_get_proc(); + if (!caps) + return -errno; + + r = capability_update_inherited_set(caps, set); + if (r < 0) + return -errno; + + if (cap_set_proc(caps) < 0) + return -errno; + } + + for (i = 0; i < cap_last_cap(); i++) { + + if (set & (UINT64_C(1) << i)) { + + /* Add the capability to the ambient set. */ + if (prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_RAISE, i, 0, 0) < 0) + return -errno; + } + } + + return 0; +} + +int capability_bounding_set_drop(uint64_t keep, bool right_now) { + _cleanup_cap_free_ cap_t after_cap = NULL; + cap_flag_value_t fv; + unsigned long i; + int r; + + /* If we are run as PID 1 we will lack CAP_SETPCAP by default + * in the effective set (yes, the kernel drops that when + * executing init!), so get it back temporarily so that we can + * call PR_CAPBSET_DROP. */ + + after_cap = cap_get_proc(); + if (!after_cap) + return -errno; + + if (cap_get_flag(after_cap, CAP_SETPCAP, CAP_EFFECTIVE, &fv) < 0) + return -errno; + + if (fv != CAP_SET) { + _cleanup_cap_free_ cap_t temp_cap = NULL; + static const cap_value_t v = CAP_SETPCAP; + + temp_cap = cap_dup(after_cap); + if (!temp_cap) { + r = -errno; + goto finish; + } + + if (cap_set_flag(temp_cap, CAP_EFFECTIVE, 1, &v, CAP_SET) < 0) { + r = -errno; + goto finish; + } + + if (cap_set_proc(temp_cap) < 0) { + r = -errno; + goto finish; + } + } + + for (i = 0; i <= cap_last_cap(); i++) { + + if (!(keep & (UINT64_C(1) << i))) { + cap_value_t v; + + /* Drop it from the bounding set */ + if (prctl(PR_CAPBSET_DROP, i) < 0) { + r = -errno; + goto finish; + } + v = (cap_value_t) i; + + /* Also drop it from the inheritable set, so + * that anything we exec() loses the + * capability for good. */ + if (cap_set_flag(after_cap, CAP_INHERITABLE, 1, &v, CAP_CLEAR) < 0) { + r = -errno; + goto finish; + } + + /* If we shall apply this right now drop it + * also from our own capability sets. */ + if (right_now) { + if (cap_set_flag(after_cap, CAP_PERMITTED, 1, &v, CAP_CLEAR) < 0 || + cap_set_flag(after_cap, CAP_EFFECTIVE, 1, &v, CAP_CLEAR) < 0) { + r = -errno; + goto finish; + } + } + } + } + + r = 0; + +finish: + if (cap_set_proc(after_cap) < 0) + return -errno; + + return r; +} + +static int drop_from_file(const char *fn, uint64_t keep) { + int r, k; + uint32_t hi, lo; + uint64_t current, after; + char *p; + + r = read_one_line_file(fn, &p); + if (r < 0) + return r; + + assert_cc(sizeof(hi) == sizeof(unsigned)); + assert_cc(sizeof(lo) == sizeof(unsigned)); + + k = sscanf(p, "%u %u", &lo, &hi); + free(p); + + if (k != 2) + return -EIO; + + current = (uint64_t) lo | ((uint64_t) hi << 32ULL); + after = current & keep; + + if (current == after) + return 0; + + lo = (unsigned) (after & 0xFFFFFFFFULL); + hi = (unsigned) ((after >> 32ULL) & 0xFFFFFFFFULL); + + if (asprintf(&p, "%u %u", lo, hi) < 0) + return -ENOMEM; + + r = write_string_file(fn, p, WRITE_STRING_FILE_CREATE); + free(p); + + return r; +} + +int capability_bounding_set_drop_usermode(uint64_t keep) { + int r; + + r = drop_from_file("/proc/sys/kernel/usermodehelper/inheritable", keep); + if (r < 0) + return r; + + r = drop_from_file("/proc/sys/kernel/usermodehelper/bset", keep); + if (r < 0) + return r; + + return r; +} + +int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities) { + _cleanup_cap_free_ cap_t d = NULL; + unsigned i, j = 0; + int r; + + /* Unfortunately we cannot leave privilege dropping to PID 1 + * here, since we want to run as user but want to keep some + * capabilities. Since file capabilities have been introduced + * this cannot be done across exec() anymore, unless our + * binary has the capability configured in the file system, + * which we want to avoid. */ + + if (setresgid(gid, gid, gid) < 0) + return log_error_errno(errno, "Failed to change group ID: %m"); + + if (setgroups(0, NULL) < 0) + return log_error_errno(errno, "Failed to drop auxiliary groups list: %m"); + + /* Ensure we keep the permitted caps across the setresuid() */ + if (prctl(PR_SET_KEEPCAPS, 1) < 0) + return log_error_errno(errno, "Failed to enable keep capabilities flag: %m"); + + r = setresuid(uid, uid, uid); + if (r < 0) + return log_error_errno(errno, "Failed to change user ID: %m"); + + if (prctl(PR_SET_KEEPCAPS, 0) < 0) + return log_error_errno(errno, "Failed to disable keep capabilities flag: %m"); + + /* Drop all caps from the bounding set, except the ones we want */ + r = capability_bounding_set_drop(keep_capabilities, true); + if (r < 0) + return log_error_errno(r, "Failed to drop capabilities: %m"); + + /* Now upgrade the permitted caps we still kept to effective caps */ + d = cap_init(); + if (!d) + return log_oom(); + + if (keep_capabilities) { + cap_value_t bits[u64log2(keep_capabilities) + 1]; + + for (i = 0; i < ELEMENTSOF(bits); i++) + if (keep_capabilities & (1ULL << i)) + bits[j++] = i; + + /* use enough bits */ + assert(i == 64 || (keep_capabilities >> i) == 0); + /* don't use too many bits */ + assert(keep_capabilities & (1ULL << (i - 1))); + + if (cap_set_flag(d, CAP_EFFECTIVE, j, bits, CAP_SET) < 0 || + cap_set_flag(d, CAP_PERMITTED, j, bits, CAP_SET) < 0) + return log_error_errno(errno, "Failed to enable capabilities bits: %m"); + + if (cap_set_proc(d) < 0) + return log_error_errno(errno, "Failed to increase capabilities: %m"); + } + + return 0; +} + +int drop_capability(cap_value_t cv) { + _cleanup_cap_free_ cap_t tmp_cap = NULL; + + tmp_cap = cap_get_proc(); + if (!tmp_cap) + return -errno; + + if ((cap_set_flag(tmp_cap, CAP_INHERITABLE, 1, &cv, CAP_CLEAR) < 0) || + (cap_set_flag(tmp_cap, CAP_PERMITTED, 1, &cv, CAP_CLEAR) < 0) || + (cap_set_flag(tmp_cap, CAP_EFFECTIVE, 1, &cv, CAP_CLEAR) < 0)) + return -errno; + + if (cap_set_proc(tmp_cap) < 0) + return -errno; + + return 0; +} diff --git a/src/libbasic/capability-util.h b/src/libbasic/capability-util.h new file mode 100644 index 0000000000..35a896e229 --- /dev/null +++ b/src/libbasic/capability-util.h @@ -0,0 +1,57 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "macro.h" +#include "util.h" + +#define CAP_ALL (uint64_t) -1 + +unsigned long cap_last_cap(void); +int have_effective_cap(int value); +int capability_bounding_set_drop(uint64_t keep, bool right_now); +int capability_bounding_set_drop_usermode(uint64_t keep); + +int capability_ambient_set_apply(uint64_t set, bool also_inherit); +int capability_update_inherited_set(cap_t caps, uint64_t ambient_set); + +int drop_privileges(uid_t uid, gid_t gid, uint64_t keep_capabilities); + +int drop_capability(cap_value_t cv); + +DEFINE_TRIVIAL_CLEANUP_FUNC(cap_t, cap_free); +#define _cleanup_cap_free_ _cleanup_(cap_freep) + +static inline void cap_free_charpp(char **p) { + if (*p) + cap_free(*p); +} +#define _cleanup_cap_free_charp_ _cleanup_(cap_free_charpp) + +static inline bool cap_test_all(uint64_t caps) { + uint64_t m; + m = (UINT64_C(1) << (cap_last_cap() + 1)) - 1; + return (caps & m) == m; +} diff --git a/src/libbasic/cgroup-util.c b/src/libbasic/cgroup-util.c new file mode 100644 index 0000000000..6ef00d51df --- /dev/null +++ b/src/libbasic/cgroup-util.c @@ -0,0 +1,2274 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "def.h" +#include "dirent-util.h" +#include "extract-word.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "log.h" +#include "login-util.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "set.h" +#include "special.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "unit-name.h" +#include "user-util.h" + +int cg_enumerate_processes(const char *controller, const char *path, FILE **_f) { + _cleanup_free_ char *fs = NULL; + FILE *f; + int r; + + assert(_f); + + r = cg_get_path(controller, path, "cgroup.procs", &fs); + if (r < 0) + return r; + + f = fopen(fs, "re"); + if (!f) + return -errno; + + *_f = f; + return 0; +} + +int cg_read_pid(FILE *f, pid_t *_pid) { + unsigned long ul; + + /* Note that the cgroup.procs might contain duplicates! See + * cgroups.txt for details. */ + + assert(f); + assert(_pid); + + errno = 0; + if (fscanf(f, "%lu", &ul) != 1) { + + if (feof(f)) + return 0; + + return errno > 0 ? -errno : -EIO; + } + + if (ul <= 0) + return -EIO; + + *_pid = (pid_t) ul; + return 1; +} + +int cg_enumerate_subgroups(const char *controller, const char *path, DIR **_d) { + _cleanup_free_ char *fs = NULL; + int r; + DIR *d; + + assert(_d); + + /* This is not recursive! */ + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) + return r; + + d = opendir(fs); + if (!d) + return -errno; + + *_d = d; + return 0; +} + +int cg_read_subgroup(DIR *d, char **fn) { + struct dirent *de; + + assert(d); + assert(fn); + + FOREACH_DIRENT_ALL(de, d, return -errno) { + char *b; + + if (de->d_type != DT_DIR) + continue; + + if (streq(de->d_name, ".") || + streq(de->d_name, "..")) + continue; + + b = strdup(de->d_name); + if (!b) + return -ENOMEM; + + *fn = b; + return 1; + } + + return 0; +} + +int cg_rmdir(const char *controller, const char *path) { + _cleanup_free_ char *p = NULL; + int r; + + r = cg_get_path(controller, path, NULL, &p); + if (r < 0) + return r; + + r = rmdir(p); + if (r < 0 && errno != ENOENT) + return -errno; + + return 0; +} + +int cg_kill(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, Set *s) { + _cleanup_set_free_ Set *allocated_set = NULL; + bool done = false; + int r, ret = 0; + pid_t my_pid; + + assert(sig >= 0); + + /* 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 */ + + if (!s) { + s = allocated_set = set_new(NULL); + if (!s) + return -ENOMEM; + } + + my_pid = getpid(); + + do { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid = 0; + done = true; + + r = cg_enumerate_processes(controller, path, &f); + if (r < 0) { + if (ret >= 0 && r != -ENOENT) + return r; + + return ret; + } + + while ((r = cg_read_pid(f, &pid)) > 0) { + + if (ignore_self && pid == my_pid) + continue; + + if (set_get(s, PID_TO_PTR(pid)) == PID_TO_PTR(pid)) + continue; + + /* 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) + (void) kill(pid, SIGCONT); + + if (ret == 0) + ret = 1; + } + + done = false; + + r = set_put(s, PID_TO_PTR(pid)); + if (r < 0) { + if (ret >= 0) + return r; + + return ret; + } + } + + if (r < 0) { + if (ret >= 0) + return r; + + return ret; + } + + /* To avoid racing against processes which fork + * quicker than we can kill them we repeat this until + * no new pids need to be killed. */ + + } while (!done); + + return ret; +} + +int cg_kill_recursive(const char *controller, const char *path, int sig, bool sigcont, bool ignore_self, bool rem, Set *s) { + _cleanup_set_free_ Set *allocated_set = NULL; + _cleanup_closedir_ DIR *d = NULL; + int r, ret; + char *fn; + + assert(path); + assert(sig >= 0); + + if (!s) { + s = allocated_set = set_new(NULL); + if (!s) + return -ENOMEM; + } + + ret = cg_kill(controller, path, sig, sigcont, ignore_self, s); + + r = cg_enumerate_subgroups(controller, path, &d); + if (r < 0) { + if (ret >= 0 && r != -ENOENT) + return r; + + return ret; + } + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + _cleanup_free_ char *p = NULL; + + p = strjoin(path, "/", fn, NULL); + free(fn); + if (!p) + return -ENOMEM; + + r = cg_kill_recursive(controller, p, sig, sigcont, ignore_self, rem, s); + if (r != 0 && ret >= 0) + ret = r; + } + + if (ret >= 0 && r < 0) + ret = r; + + if (rem) { + r = cg_rmdir(controller, path); + if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) + return r; + } + + return ret; +} + +int cg_migrate(const char *cfrom, const char *pfrom, const char *cto, const char *pto, bool ignore_self) { + bool done = false; + _cleanup_set_free_ Set *s = NULL; + int r, ret = 0; + pid_t my_pid; + + assert(cfrom); + assert(pfrom); + assert(cto); + assert(pto); + + s = set_new(NULL); + if (!s) + return -ENOMEM; + + my_pid = getpid(); + + do { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid = 0; + done = true; + + r = cg_enumerate_processes(cfrom, pfrom, &f); + if (r < 0) { + if (ret >= 0 && r != -ENOENT) + return r; + + return ret; + } + + while ((r = cg_read_pid(f, &pid)) > 0) { + + /* 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) + continue; + + if (set_get(s, PID_TO_PTR(pid)) == PID_TO_PTR(pid)) + continue; + + /* Ignore kernel threads. Since they can only + * exist in the root cgroup, we only check for + * them there. */ + if (cfrom && + (isempty(pfrom) || path_equal(pfrom, "/")) && + is_kernel_thread(pid) > 0) + continue; + + r = cg_attach(cto, pto, pid); + if (r < 0) { + if (ret >= 0 && r != -ESRCH) + ret = r; + } else if (ret == 0) + ret = 1; + + done = false; + + r = set_put(s, PID_TO_PTR(pid)); + if (r < 0) { + if (ret >= 0) + return r; + + return ret; + } + } + + if (r < 0) { + if (ret >= 0) + return r; + + return ret; + } + } while (!done); + + return ret; +} + +int cg_migrate_recursive( + const char *cfrom, + const char *pfrom, + const char *cto, + const char *pto, + bool ignore_self, + bool rem) { + + _cleanup_closedir_ DIR *d = NULL; + int r, ret = 0; + char *fn; + + assert(cfrom); + assert(pfrom); + assert(cto); + assert(pto); + + ret = cg_migrate(cfrom, pfrom, cto, pto, ignore_self); + + r = cg_enumerate_subgroups(cfrom, pfrom, &d); + if (r < 0) { + if (ret >= 0 && r != -ENOENT) + return r; + + return ret; + } + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + _cleanup_free_ char *p = NULL; + + p = strjoin(pfrom, "/", fn, NULL); + free(fn); + if (!p) + return -ENOMEM; + + r = cg_migrate_recursive(cfrom, p, cto, pto, ignore_self, rem); + if (r != 0 && ret >= 0) + ret = r; + } + + if (r < 0 && ret >= 0) + ret = r; + + if (rem) { + r = cg_rmdir(cfrom, pfrom); + if (r < 0 && ret >= 0 && r != -ENOENT && r != -EBUSY) + return r; + } + + return ret; +} + +int cg_migrate_recursive_fallback( + const char *cfrom, + const char *pfrom, + const char *cto, + const char *pto, + bool ignore_self, + bool rem) { + + int r; + + assert(cfrom); + assert(pfrom); + assert(cto); + assert(pto); + + r = cg_migrate_recursive(cfrom, pfrom, cto, pto, ignore_self, rem); + if (r < 0) { + char prefix[strlen(pto) + 1]; + + /* This didn't work? Then let's try all prefixes of the destination */ + + PATH_FOREACH_PREFIX(prefix, pto) { + int q; + + q = cg_migrate_recursive(cfrom, pfrom, cto, prefix, ignore_self, rem); + if (q >= 0) + return q; + } + } + + return r; +} + +static const char *controller_to_dirname(const char *controller) { + const char *e; + + assert(controller); + + /* Converts a controller name to the directory name below + * /sys/fs/cgroup/ we want to mount it to. Effectively, this + * just cuts off the name= prefixed used for named + * hierarchies, if it is specified. */ + + e = startswith(controller, "name="); + if (e) + return e; + + return controller; +} + +static int join_path_legacy(const char *controller, const char *path, const char *suffix, char **fs) { + const char *dn; + char *t = NULL; + + assert(fs); + assert(controller); + + dn = controller_to_dirname(controller); + + if (isempty(path) && isempty(suffix)) + t = strappend("/sys/fs/cgroup/", dn); + else if (isempty(path)) + t = strjoin("/sys/fs/cgroup/", dn, "/", suffix, NULL); + else if (isempty(suffix)) + t = strjoin("/sys/fs/cgroup/", dn, "/", path, NULL); + else + t = strjoin("/sys/fs/cgroup/", dn, "/", path, "/", suffix, NULL); + if (!t) + return -ENOMEM; + + *fs = t; + return 0; +} + +static int join_path_unified(const char *path, const char *suffix, char **fs) { + char *t; + + assert(fs); + + if (isempty(path) && isempty(suffix)) + t = strdup("/sys/fs/cgroup"); + else if (isempty(path)) + t = strappend("/sys/fs/cgroup/", suffix); + else if (isempty(suffix)) + t = strappend("/sys/fs/cgroup/", path); + else + t = strjoin("/sys/fs/cgroup/", path, "/", suffix, NULL); + if (!t) + return -ENOMEM; + + *fs = t; + return 0; +} + +int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs) { + int unified, r; + + assert(fs); + + if (!controller) { + char *t; + + /* If no controller is specified, we return the path + * *below* the controllers, without any prefix. */ + + if (!path && !suffix) + return -EINVAL; + + if (!suffix) + t = strdup(path); + else if (!path) + t = strdup(suffix); + else + t = strjoin(path, "/", suffix, NULL); + if (!t) + return -ENOMEM; + + *fs = path_kill_slashes(t); + return 0; + } + + if (!cg_controller_is_valid(controller)) + return -EINVAL; + + unified = cg_unified(); + if (unified < 0) + return unified; + + if (unified > 0) + r = join_path_unified(path, suffix, fs); + else + r = join_path_legacy(controller, path, suffix, fs); + if (r < 0) + return r; + + path_kill_slashes(*fs); + return 0; +} + +static int controller_is_accessible(const char *controller) { + int unified; + + assert(controller); + + /* Checks whether a specific controller is accessible, + * i.e. its hierarchy mounted. In the unified hierarchy all + * controllers are considered accessible, except for the named + * hierarchies */ + + if (!cg_controller_is_valid(controller)) + return -EINVAL; + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified > 0) { + /* We don't support named hierarchies if we are using + * the unified hierarchy. */ + + if (streq(controller, SYSTEMD_CGROUP_CONTROLLER)) + return 0; + + if (startswith(controller, "name=")) + return -EOPNOTSUPP; + + } else { + const char *cc, *dn; + + dn = controller_to_dirname(controller); + cc = strjoina("/sys/fs/cgroup/", dn); + + if (laccess(cc, F_OK) < 0) + return -errno; + } + + return 0; +} + +int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs) { + int r; + + assert(controller); + assert(fs); + + /* Check if the specified controller is actually accessible */ + r = controller_is_accessible(controller); + if (r < 0) + return r; + + return cg_get_path(controller, path, suffix, fs); +} + +static int trim_cb(const char *path, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { + assert(path); + assert(sb); + assert(ftwbuf); + + if (typeflag != FTW_DP) + return 0; + + if (ftwbuf->level < 1) + return 0; + + (void) rmdir(path); + return 0; +} + +int cg_trim(const char *controller, const char *path, bool delete_root) { + _cleanup_free_ char *fs = NULL; + int r = 0; + + assert(path); + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) + return r; + + errno = 0; + if (nftw(fs, trim_cb, 64, FTW_DEPTH|FTW_MOUNT|FTW_PHYS) != 0) { + if (errno == ENOENT) + r = 0; + else if (errno > 0) + r = -errno; + else + r = -EIO; + } + + if (delete_root) { + if (rmdir(fs) < 0 && errno != ENOENT) + return -errno; + } + + return r; +} + +int cg_create(const char *controller, const char *path) { + _cleanup_free_ char *fs = NULL; + int r; + + r = cg_get_path_and_check(controller, path, NULL, &fs); + if (r < 0) + return r; + + r = mkdir_parents(fs, 0755); + if (r < 0) + return r; + + if (mkdir(fs, 0755) < 0) { + + if (errno == EEXIST) + return 0; + + return -errno; + } + + return 1; +} + +int cg_create_and_attach(const char *controller, const char *path, pid_t pid) { + int r, q; + + assert(pid >= 0); + + r = cg_create(controller, path); + if (r < 0) + return r; + + q = cg_attach(controller, path, pid); + if (q < 0) + return q; + + /* This does not remove the cgroup on failure */ + return r; +} + +int cg_attach(const char *controller, const char *path, pid_t pid) { + _cleanup_free_ char *fs = NULL; + char c[DECIMAL_STR_MAX(pid_t) + 2]; + int r; + + assert(path); + assert(pid >= 0); + + r = cg_get_path_and_check(controller, path, "cgroup.procs", &fs); + if (r < 0) + return r; + + if (pid == 0) + pid = getpid(); + + xsprintf(c, PID_FMT "\n", pid); + + return write_string_file(fs, c, 0); +} + +int cg_attach_fallback(const char *controller, const char *path, pid_t pid) { + int r; + + assert(controller); + assert(path); + assert(pid >= 0); + + r = cg_attach(controller, path, pid); + if (r < 0) { + char prefix[strlen(path) + 1]; + + /* This didn't work? Then let's try all prefixes of + * the destination */ + + PATH_FOREACH_PREFIX(prefix, path) { + int q; + + q = cg_attach(controller, prefix, pid); + if (q >= 0) + return q; + } + } + + return r; +} + +int cg_set_group_access( + const char *controller, + const char *path, + mode_t mode, + uid_t uid, + gid_t gid) { + + _cleanup_free_ char *fs = NULL; + int r; + + if (mode == MODE_INVALID && uid == UID_INVALID && gid == GID_INVALID) + return 0; + + if (mode != MODE_INVALID) + mode &= 0777; + + r = cg_get_path(controller, path, NULL, &fs); + if (r < 0) + return r; + + return chmod_and_chown(fs, mode, uid, gid); +} + +int cg_set_task_access( + const char *controller, + const char *path, + mode_t mode, + uid_t uid, + gid_t gid) { + + _cleanup_free_ char *fs = NULL, *procs = NULL; + int r, unified; + + assert(path); + + if (mode == MODE_INVALID && uid == UID_INVALID && gid == GID_INVALID) + return 0; + + if (mode != MODE_INVALID) + mode &= 0666; + + r = cg_get_path(controller, path, "cgroup.procs", &fs); + if (r < 0) + return r; + + r = chmod_and_chown(fs, mode, uid, gid); + if (r < 0) + return r; + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified) + return 0; + + /* Compatibility, Always keep values for "tasks" in sync with + * "cgroup.procs" */ + if (cg_get_path(controller, path, "tasks", &procs) >= 0) + (void) chmod_and_chown(procs, mode, uid, gid); + + return 0; +} + +int cg_pid_get_path(const char *controller, pid_t pid, char **path) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + const char *fs; + size_t cs = 0; + int unified; + + assert(path); + assert(pid >= 0); + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified == 0) { + if (controller) { + if (!cg_controller_is_valid(controller)) + return -EINVAL; + } else + controller = SYSTEMD_CGROUP_CONTROLLER; + + cs = strlen(controller); + } + + fs = procfs_file_alloca(pid, "cgroup"); + f = fopen(fs, "re"); + if (!f) + return errno == ENOENT ? -ESRCH : -errno; + + FOREACH_LINE(line, f, return -errno) { + char *e, *p; + + truncate_nl(line); + + if (unified) { + e = startswith(line, "0:"); + if (!e) + continue; + + e = strchr(e, ':'); + if (!e) + continue; + } else { + char *l; + size_t k; + const char *word, *state; + bool found = false; + + l = strchr(line, ':'); + if (!l) + continue; + + l++; + e = strchr(l, ':'); + if (!e) + continue; + + *e = 0; + FOREACH_WORD_SEPARATOR(word, k, l, ",", state) { + if (k == cs && memcmp(word, controller, cs) == 0) { + found = true; + break; + } + } + + if (!found) + continue; + } + + p = strdup(e + 1); + if (!p) + return -ENOMEM; + + *path = p; + return 0; + } + + return -ENODATA; +} + +int cg_install_release_agent(const char *controller, const char *agent) { + _cleanup_free_ char *fs = NULL, *contents = NULL; + const char *sc; + int r, unified; + + assert(agent); + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified) /* doesn't apply to unified hierarchy */ + return -EOPNOTSUPP; + + r = cg_get_path(controller, NULL, "release_agent", &fs); + if (r < 0) + return r; + + r = read_one_line_file(fs, &contents); + if (r < 0) + return r; + + sc = strstrip(contents); + if (isempty(sc)) { + r = write_string_file(fs, agent, 0); + if (r < 0) + return r; + } else if (!path_equal(sc, agent)) + return -EEXIST; + + fs = mfree(fs); + r = cg_get_path(controller, NULL, "notify_on_release", &fs); + if (r < 0) + return r; + + contents = mfree(contents); + r = read_one_line_file(fs, &contents); + if (r < 0) + return r; + + sc = strstrip(contents); + if (streq(sc, "0")) { + r = write_string_file(fs, "1", 0); + if (r < 0) + return r; + + return 1; + } + + if (!streq(sc, "1")) + return -EIO; + + return 0; +} + +int cg_uninstall_release_agent(const char *controller) { + _cleanup_free_ char *fs = NULL; + int r, unified; + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified) /* Doesn't apply to unified hierarchy */ + return -EOPNOTSUPP; + + r = cg_get_path(controller, NULL, "notify_on_release", &fs); + if (r < 0) + return r; + + r = write_string_file(fs, "0", 0); + if (r < 0) + return r; + + fs = mfree(fs); + + r = cg_get_path(controller, NULL, "release_agent", &fs); + if (r < 0) + return r; + + r = write_string_file(fs, "", 0); + if (r < 0) + return r; + + return 0; +} + +int cg_is_empty(const char *controller, const char *path) { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid; + int r; + + assert(path); + + r = cg_enumerate_processes(controller, path, &f); + if (r == -ENOENT) + return 1; + if (r < 0) + return r; + + r = cg_read_pid(f, &pid); + if (r < 0) + return r; + + return r == 0; +} + +int cg_is_empty_recursive(const char *controller, const char *path) { + int unified, r; + + assert(path); + + /* The root cgroup is always populated */ + if (controller && (isempty(path) || path_equal(path, "/"))) + return false; + + unified = cg_unified(); + if (unified < 0) + return unified; + + if (unified > 0) { + _cleanup_free_ char *populated = NULL, *t = NULL; + + /* On the unified hierarchy we can check empty state + * via the "cgroup.populated" attribute. */ + + r = cg_get_path(controller, path, "cgroup.populated", &populated); + if (r < 0) + return r; + + r = read_one_line_file(populated, &t); + if (r == -ENOENT) + return 1; + if (r < 0) + return r; + + return streq(t, "0"); + } else { + _cleanup_closedir_ DIR *d = NULL; + char *fn; + + r = cg_is_empty(controller, path); + if (r <= 0) + return r; + + r = cg_enumerate_subgroups(controller, path, &d); + if (r == -ENOENT) + return 1; + if (r < 0) + return r; + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + _cleanup_free_ char *p = NULL; + + p = strjoin(path, "/", fn, NULL); + free(fn); + if (!p) + return -ENOMEM; + + r = cg_is_empty_recursive(controller, p); + if (r <= 0) + return r; + } + if (r < 0) + return r; + + return true; + } +} + +int cg_split_spec(const char *spec, char **controller, char **path) { + char *t = NULL, *u = NULL; + const char *e; + + assert(spec); + + if (*spec == '/') { + if (!path_is_safe(spec)) + return -EINVAL; + + if (path) { + t = strdup(spec); + if (!t) + return -ENOMEM; + + *path = path_kill_slashes(t); + } + + if (controller) + *controller = NULL; + + return 0; + } + + e = strchr(spec, ':'); + if (!e) { + if (!cg_controller_is_valid(spec)) + return -EINVAL; + + if (controller) { + t = strdup(spec); + if (!t) + return -ENOMEM; + + *controller = t; + } + + if (path) + *path = NULL; + + return 0; + } + + t = strndup(spec, e-spec); + if (!t) + return -ENOMEM; + if (!cg_controller_is_valid(t)) { + free(t); + return -EINVAL; + } + + if (isempty(e+1)) + u = NULL; + else { + u = strdup(e+1); + if (!u) { + free(t); + return -ENOMEM; + } + + if (!path_is_safe(u) || + !path_is_absolute(u)) { + free(t); + free(u); + return -EINVAL; + } + + path_kill_slashes(u); + } + + if (controller) + *controller = t; + else + free(t); + + if (path) + *path = u; + else + free(u); + + return 0; +} + +int cg_mangle_path(const char *path, char **result) { + _cleanup_free_ char *c = NULL, *p = NULL; + char *t; + int r; + + assert(path); + assert(result); + + /* First, check if it already is a filesystem path */ + if (path_startswith(path, "/sys/fs/cgroup")) { + + t = strdup(path); + if (!t) + return -ENOMEM; + + *result = path_kill_slashes(t); + return 0; + } + + /* Otherwise, treat it as cg spec */ + r = cg_split_spec(path, &c, &p); + if (r < 0) + return r; + + return cg_get_path(c ?: SYSTEMD_CGROUP_CONTROLLER, p ?: "/", NULL, result); +} + +int cg_get_root_path(char **path) { + char *p, *e; + int r; + + assert(path); + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 1, &p); + if (r < 0) + return r; + + e = endswith(p, "/" SPECIAL_INIT_SCOPE); + if (!e) + e = endswith(p, "/" SPECIAL_SYSTEM_SLICE); /* legacy */ + if (!e) + e = endswith(p, "/system"); /* even more legacy */ + if (e) + *e = 0; + + *path = p; + return 0; +} + +int cg_shift_path(const char *cgroup, const char *root, const char **shifted) { + _cleanup_free_ char *rt = NULL; + char *p; + int r; + + assert(cgroup); + assert(shifted); + + if (!root) { + /* If the root was specified let's use that, otherwise + * let's determine it from PID 1 */ + + r = cg_get_root_path(&rt); + if (r < 0) + return r; + + root = rt; + } + + p = path_startswith(cgroup, root); + if (p && p > cgroup) + *shifted = p - 1; + else + *shifted = cgroup; + + return 0; +} + +int cg_pid_get_path_shifted(pid_t pid, const char *root, char **cgroup) { + _cleanup_free_ char *raw = NULL; + const char *c; + int r; + + assert(pid >= 0); + assert(cgroup); + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &raw); + if (r < 0) + return r; + + r = cg_shift_path(raw, root, &c); + if (r < 0) + return r; + + if (c == raw) { + *cgroup = raw; + raw = NULL; + } else { + char *n; + + n = strdup(c); + if (!n) + return -ENOMEM; + + *cgroup = n; + } + + return 0; +} + +int cg_path_decode_unit(const char *cgroup, char **unit){ + char *c, *s; + size_t n; + + assert(cgroup); + assert(unit); + + n = strcspn(cgroup, "/"); + if (n < 3) + return -ENXIO; + + c = strndupa(cgroup, n); + c = cg_unescape(c); + + if (!unit_name_is_valid(c, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) + return -ENXIO; + + s = strdup(c); + if (!s) + return -ENOMEM; + + *unit = s; + return 0; +} + +static bool valid_slice_name(const char *p, size_t n) { + + if (!p) + return false; + + if (n < strlen("x.slice")) + return false; + + if (memcmp(p + n - 6, ".slice", 6) == 0) { + char buf[n+1], *c; + + memcpy(buf, p, n); + buf[n] = 0; + + c = cg_unescape(buf); + + return unit_name_is_valid(c, UNIT_NAME_PLAIN); + } + + return false; +} + +static const char *skip_slices(const char *p) { + assert(p); + + /* Skips over all slice assignments */ + + for (;;) { + size_t n; + + p += strspn(p, "/"); + + n = strcspn(p, "/"); + if (!valid_slice_name(p, n)) + return p; + + p += n; + } +} + +int cg_path_get_unit(const char *path, char **ret) { + const char *e; + char *unit; + int r; + + assert(path); + assert(ret); + + e = skip_slices(path); + + r = cg_path_decode_unit(e, &unit); + if (r < 0) + return r; + + /* We skipped over the slices, don't accept any now */ + if (endswith(unit, ".slice")) { + free(unit); + return -ENXIO; + } + + *ret = unit; + return 0; +} + +int cg_pid_get_unit(pid_t pid, char **unit) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(unit); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_unit(cgroup, unit); +} + +/** + * Skip session-*.scope, but require it to be there. + */ +static const char *skip_session(const char *p) { + size_t n; + + if (isempty(p)) + return NULL; + + p += strspn(p, "/"); + + n = strcspn(p, "/"); + if (n < strlen("session-x.scope")) + return NULL; + + if (memcmp(p, "session-", 8) == 0 && memcmp(p + n - 6, ".scope", 6) == 0) { + char buf[n - 8 - 6 + 1]; + + memcpy(buf, p + 8, n - 8 - 6); + buf[n - 8 - 6] = 0; + + /* Note that session scopes never need unescaping, + * since they cannot conflict with the kernel's own + * names, hence we don't need to call cg_unescape() + * here. */ + + if (!session_id_valid(buf)) + return false; + + p += n; + p += strspn(p, "/"); + return p; + } + + return NULL; +} + +/** + * Skip user@*.service, but require it to be there. + */ +static const char *skip_user_manager(const char *p) { + size_t n; + + if (isempty(p)) + return NULL; + + p += strspn(p, "/"); + + n = strcspn(p, "/"); + if (n < strlen("user@x.service")) + return NULL; + + if (memcmp(p, "user@", 5) == 0 && memcmp(p + n - 8, ".service", 8) == 0) { + char buf[n - 5 - 8 + 1]; + + memcpy(buf, p + 5, n - 5 - 8); + buf[n - 5 - 8] = 0; + + /* Note that user manager services never need unescaping, + * since they cannot conflict with the kernel's own + * names, hence we don't need to call cg_unescape() + * here. */ + + if (parse_uid(buf, NULL) < 0) + return NULL; + + p += n; + p += strspn(p, "/"); + + return p; + } + + return NULL; +} + +static const char *skip_user_prefix(const char *path) { + const char *e, *t; + + assert(path); + + /* Skip slices, if there are any */ + e = skip_slices(path); + + /* Skip the user manager, if it's in the path now... */ + t = skip_user_manager(e); + if (t) + return t; + + /* Alternatively skip the user session if it is in the path... */ + return skip_session(e); +} + +int cg_path_get_user_unit(const char *path, char **ret) { + const char *t; + + assert(path); + assert(ret); + + t = skip_user_prefix(path); + if (!t) + return -ENXIO; + + /* And from here on it looks pretty much the same as for a + * system unit, hence let's use the same parser from here + * on. */ + return cg_path_get_unit(t, ret); +} + +int cg_pid_get_user_unit(pid_t pid, char **unit) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(unit); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_user_unit(cgroup, unit); +} + +int cg_path_get_machine_name(const char *path, char **machine) { + _cleanup_free_ char *u = NULL; + const char *sl; + int r; + + r = cg_path_get_unit(path, &u); + if (r < 0) + return r; + + sl = strjoina("/run/systemd/machines/unit:", u); + return readlink_malloc(sl, machine); +} + +int cg_pid_get_machine_name(pid_t pid, char **machine) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(machine); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_machine_name(cgroup, machine); +} + +int cg_path_get_session(const char *path, char **session) { + _cleanup_free_ char *unit = NULL; + char *start, *end; + int r; + + assert(path); + + r = cg_path_get_unit(path, &unit); + if (r < 0) + return r; + + start = startswith(unit, "session-"); + if (!start) + return -ENXIO; + end = endswith(start, ".scope"); + if (!end) + return -ENXIO; + + *end = 0; + if (!session_id_valid(start)) + return -ENXIO; + + if (session) { + char *rr; + + rr = strdup(start); + if (!rr) + return -ENOMEM; + + *session = rr; + } + + return 0; +} + +int cg_pid_get_session(pid_t pid, char **session) { + _cleanup_free_ char *cgroup = NULL; + int r; + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_session(cgroup, session); +} + +int cg_path_get_owner_uid(const char *path, uid_t *uid) { + _cleanup_free_ char *slice = NULL; + char *start, *end; + int r; + + assert(path); + + r = cg_path_get_slice(path, &slice); + if (r < 0) + return r; + + start = startswith(slice, "user-"); + if (!start) + return -ENXIO; + end = endswith(start, ".slice"); + if (!end) + return -ENXIO; + + *end = 0; + if (parse_uid(start, uid) < 0) + return -ENXIO; + + return 0; +} + +int cg_pid_get_owner_uid(pid_t pid, uid_t *uid) { + _cleanup_free_ char *cgroup = NULL; + int r; + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_owner_uid(cgroup, uid); +} + +int cg_path_get_slice(const char *p, char **slice) { + const char *e = NULL; + + assert(p); + assert(slice); + + /* Finds the right-most slice unit from the beginning, but + * stops before we come to the first non-slice unit. */ + + for (;;) { + size_t n; + + p += strspn(p, "/"); + + n = strcspn(p, "/"); + if (!valid_slice_name(p, n)) { + + if (!e) { + char *s; + + s = strdup("-.slice"); + if (!s) + return -ENOMEM; + + *slice = s; + return 0; + } + + return cg_path_decode_unit(e, slice); + } + + e = p; + p += n; + } +} + +int cg_pid_get_slice(pid_t pid, char **slice) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(slice); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_slice(cgroup, slice); +} + +int cg_path_get_user_slice(const char *p, char **slice) { + const char *t; + assert(p); + assert(slice); + + t = skip_user_prefix(p); + if (!t) + return -ENXIO; + + /* And now it looks pretty much the same as for a system + * slice, so let's just use the same parser from here on. */ + return cg_path_get_slice(t, slice); +} + +int cg_pid_get_user_slice(pid_t pid, char **slice) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(slice); + + r = cg_pid_get_path_shifted(pid, NULL, &cgroup); + if (r < 0) + return r; + + return cg_path_get_user_slice(cgroup, slice); +} + +char *cg_escape(const char *p) { + bool need_prefix = false; + + /* This implements very minimal escaping for names to be used + * as file names in the cgroup tree: any name which might + * conflict with a kernel name or is prefixed with '_' is + * prefixed with a '_'. That way, when reading cgroup names it + * is sufficient to remove a single prefixing underscore if + * there is one. */ + + /* The return value of this function (unlike cg_unescape()) + * needs free()! */ + + if (p[0] == 0 || + p[0] == '_' || + p[0] == '.' || + streq(p, "notify_on_release") || + streq(p, "release_agent") || + streq(p, "tasks") || + startswith(p, "cgroup.")) + need_prefix = true; + else { + const char *dot; + + dot = strrchr(p, '.'); + if (dot) { + CGroupController c; + size_t l = dot - p; + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + const char *n; + + n = cgroup_controller_to_string(c); + + if (l != strlen(n)) + continue; + + if (memcmp(p, n, l) != 0) + continue; + + need_prefix = true; + break; + } + } + } + + if (need_prefix) + return strappend("_", p); + + return strdup(p); +} + +char *cg_unescape(const char *p) { + assert(p); + + /* The return value of this function (unlike cg_escape()) + * doesn't need free()! */ + + if (p[0] == '_') + return (char*) p+1; + + return (char*) p; +} + +#define CONTROLLER_VALID \ + DIGITS LETTERS \ + "_" + +bool cg_controller_is_valid(const char *p) { + const char *t, *s; + + if (!p) + return false; + + s = startswith(p, "name="); + if (s) + p = s; + + if (*p == 0 || *p == '_') + return false; + + for (t = p; *t; t++) + if (!strchr(CONTROLLER_VALID, *t)) + return false; + + if (t - p > FILENAME_MAX) + return false; + + return true; +} + +int cg_slice_to_path(const char *unit, char **ret) { + _cleanup_free_ char *p = NULL, *s = NULL, *e = NULL; + const char *dash; + int r; + + assert(unit); + assert(ret); + + if (streq(unit, "-.slice")) { + char *x; + + x = strdup(""); + if (!x) + return -ENOMEM; + *ret = x; + return 0; + } + + if (!unit_name_is_valid(unit, UNIT_NAME_PLAIN)) + return -EINVAL; + + if (!endswith(unit, ".slice")) + return -EINVAL; + + r = unit_name_to_prefix(unit, &p); + if (r < 0) + return r; + + dash = strchr(p, '-'); + + /* Don't allow initial dashes */ + if (dash == p) + return -EINVAL; + + while (dash) { + _cleanup_free_ char *escaped = NULL; + char n[dash - p + sizeof(".slice")]; + + /* Don't allow trailing or double dashes */ + if (dash[1] == 0 || dash[1] == '-') + return -EINVAL; + + strcpy(stpncpy(n, p, dash - p), ".slice"); + if (!unit_name_is_valid(n, UNIT_NAME_PLAIN)) + return -EINVAL; + + escaped = cg_escape(n); + if (!escaped) + return -ENOMEM; + + if (!strextend(&s, escaped, "/", NULL)) + return -ENOMEM; + + dash = strchr(dash+1, '-'); + } + + e = cg_escape(unit); + if (!e) + return -ENOMEM; + + if (!strextend(&s, e, NULL)) + return -ENOMEM; + + *ret = s; + s = NULL; + + return 0; +} + +int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value) { + _cleanup_free_ char *p = NULL; + int r; + + r = cg_get_path(controller, path, attribute, &p); + if (r < 0) + return r; + + return write_string_file(p, value, 0); +} + +int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret) { + _cleanup_free_ char *p = NULL; + int r; + + r = cg_get_path(controller, path, attribute, &p); + if (r < 0) + return r; + + return read_one_line_file(p, ret); +} + +int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path) { + CGroupController c; + int r, unified; + + /* This one will create a cgroup in our private tree, but also + * duplicate it in the trees specified in mask, and remove it + * in all others */ + + /* First create the cgroup in our own hierarchy. */ + r = cg_create(SYSTEMD_CGROUP_CONTROLLER, path); + if (r < 0) + return r; + + /* If we are in the unified hierarchy, we are done now */ + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified > 0) + return 0; + + /* Otherwise, do the same in the other hierarchies */ + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); + const char *n; + + n = cgroup_controller_to_string(c); + + if (mask & bit) + (void) cg_create(n, path); + else if (supported & bit) + (void) cg_trim(n, path, true); + } + + return 0; +} + +int cg_attach_everywhere(CGroupMask supported, const char *path, pid_t pid, cg_migrate_callback_t path_callback, void *userdata) { + CGroupController c; + int r, unified; + + r = cg_attach(SYSTEMD_CGROUP_CONTROLLER, path, pid); + if (r < 0) + return r; + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified > 0) + return 0; + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); + const char *p = NULL; + + if (!(supported & bit)) + continue; + + if (path_callback) + p = path_callback(bit, userdata); + + if (!p) + p = path; + + (void) cg_attach_fallback(cgroup_controller_to_string(c), p, pid); + } + + return 0; +} + +int cg_attach_many_everywhere(CGroupMask supported, const char *path, Set* pids, cg_migrate_callback_t path_callback, void *userdata) { + Iterator i; + void *pidp; + int r = 0; + + SET_FOREACH(pidp, pids, i) { + pid_t pid = PTR_TO_PID(pidp); + int q; + + q = cg_attach_everywhere(supported, path, pid, path_callback, userdata); + if (q < 0 && r >= 0) + r = q; + } + + return r; +} + +int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to, cg_migrate_callback_t to_callback, void *userdata) { + CGroupController c; + int r = 0, unified; + + if (!path_equal(from, to)) { + r = cg_migrate_recursive(SYSTEMD_CGROUP_CONTROLLER, from, SYSTEMD_CGROUP_CONTROLLER, to, false, true); + if (r < 0) + return r; + } + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified > 0) + return r; + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); + const char *p = NULL; + + if (!(supported & bit)) + continue; + + if (to_callback) + p = to_callback(bit, userdata); + + if (!p) + p = to; + + (void) cg_migrate_recursive_fallback(SYSTEMD_CGROUP_CONTROLLER, to, cgroup_controller_to_string(c), p, false, false); + } + + return 0; +} + +int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root) { + CGroupController c; + int r, unified; + + r = cg_trim(SYSTEMD_CGROUP_CONTROLLER, path, delete_root); + if (r < 0) + return r; + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified > 0) + return r; + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); + + if (!(supported & bit)) + continue; + + (void) cg_trim(cgroup_controller_to_string(c), path, delete_root); + } + + return 0; +} + +int cg_mask_supported(CGroupMask *ret) { + CGroupMask mask = 0; + int r, unified; + + /* Determines the mask of supported cgroup controllers. Only + * includes controllers we can make sense of and that are + * actually accessible. */ + + unified = cg_unified(); + if (unified < 0) + return unified; + if (unified > 0) { + _cleanup_free_ char *root = NULL, *controllers = NULL, *path = NULL; + const char *c; + + /* In the unified hierarchy we can read the supported + * and accessible controllers from a the top-level + * cgroup attribute */ + + r = cg_get_root_path(&root); + if (r < 0) + return r; + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, root, "cgroup.controllers", &path); + if (r < 0) + return r; + + r = read_one_line_file(path, &controllers); + if (r < 0) + return r; + + c = controllers; + for (;;) { + _cleanup_free_ char *n = NULL; + CGroupController v; + + r = extract_first_word(&c, &n, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + v = cgroup_controller_from_string(n); + if (v < 0) + continue; + + mask |= CGROUP_CONTROLLER_TO_MASK(v); + } + + /* Currently, we only support the memory and pids + * controller in the unified hierarchy, mask + * everything else off. */ + mask &= CGROUP_MASK_MEMORY | CGROUP_MASK_PIDS; + + } else { + CGroupController c; + + /* In the legacy hierarchy, we check whether which + * hierarchies are mounted. */ + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + const char *n; + + n = cgroup_controller_to_string(c); + if (controller_is_accessible(n) >= 0) + mask |= CGROUP_CONTROLLER_TO_MASK(c); + } + } + + *ret = mask; + return 0; +} + +int cg_kernel_controllers(Set *controllers) { + _cleanup_fclose_ FILE *f = NULL; + char buf[LINE_MAX]; + int r; + + assert(controllers); + + /* Determines the full list of kernel-known controllers. Might + * include controllers we don't actually support, arbitrary + * named hierarchies and controllers that aren't currently + * accessible (because not mounted). */ + + f = fopen("/proc/cgroups", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + return -errno; + } + + /* Ignore the header line */ + (void) fgets(buf, sizeof(buf), f); + + for (;;) { + char *controller; + int enabled = 0; + + errno = 0; + if (fscanf(f, "%ms %*i %*i %i", &controller, &enabled) != 2) { + + if (feof(f)) + break; + + if (ferror(f) && errno > 0) + return -errno; + + return -EBADMSG; + } + + if (!enabled) { + free(controller); + continue; + } + + if (!cg_controller_is_valid(controller)) { + free(controller); + return -EBADMSG; + } + + r = set_consume(controllers, controller); + if (r < 0) + return r; + } + + return 0; +} + +static thread_local int unified_cache = -1; + +int cg_unified(void) { + struct statfs fs; + + /* Checks if we support the unified hierarchy. Returns an + * error when the cgroup hierarchies aren't mounted yet or we + * have any other trouble determining if the unified hierarchy + * is supported. */ + + if (unified_cache >= 0) + return unified_cache; + + if (statfs("/sys/fs/cgroup/", &fs) < 0) + return -errno; + + if (F_TYPE_EQUAL(fs.f_type, CGROUP_SUPER_MAGIC)) + unified_cache = true; + else if (F_TYPE_EQUAL(fs.f_type, TMPFS_MAGIC)) + unified_cache = false; + else + return -ENOMEDIUM; + + return unified_cache; +} + +void cg_unified_flush(void) { + unified_cache = -1; +} + +int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p) { + _cleanup_free_ char *fs = NULL; + CGroupController c; + int r, unified; + + assert(p); + + if (supported == 0) + return 0; + + unified = cg_unified(); + if (unified < 0) + return unified; + if (!unified) /* on the legacy hiearchy there's no joining of controllers defined */ + return 0; + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, p, "cgroup.subtree_control", &fs); + if (r < 0) + return r; + + for (c = 0; c < _CGROUP_CONTROLLER_MAX; c++) { + CGroupMask bit = CGROUP_CONTROLLER_TO_MASK(c); + const char *n; + + if (!(supported & bit)) + continue; + + n = cgroup_controller_to_string(c); + { + char s[1 + strlen(n) + 1]; + + s[0] = mask & bit ? '+' : '-'; + strcpy(s + 1, n); + + r = write_string_file(fs, s, 0); + if (r < 0) + log_debug_errno(r, "Failed to enable controller %s for %s (%s): %m", n, p, fs); + } + } + + return 0; +} + +bool cg_is_unified_wanted(void) { + static thread_local int wanted = -1; + int r, unified; + + /* If the hierarchy is already mounted, then follow whatever + * was chosen for it. */ + unified = cg_unified(); + if (unified >= 0) + return unified; + + /* Otherwise, let's see what the kernel command line has to + * say. Since checking that is expensive, let's cache the + * result. */ + if (wanted >= 0) + return wanted; + + r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy", NULL); + if (r > 0) + return (wanted = true); + else { + _cleanup_free_ char *value = NULL; + + r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy=", &value); + if (r < 0) + return false; + if (r == 0) + return (wanted = false); + + return (wanted = parse_boolean(value) > 0); + } +} + +bool cg_is_legacy_wanted(void) { + return !cg_is_unified_wanted(); +} + +int cg_cpu_shares_parse(const char *s, uint64_t *ret) { + uint64_t u; + int r; + + if (isempty(s)) { + *ret = CGROUP_CPU_SHARES_INVALID; + return 0; + } + + r = safe_atou64(s, &u); + if (r < 0) + return r; + + if (u < CGROUP_CPU_SHARES_MIN || u > CGROUP_CPU_SHARES_MAX) + return -ERANGE; + + *ret = u; + return 0; +} + +int cg_blkio_weight_parse(const char *s, uint64_t *ret) { + uint64_t u; + int r; + + if (isempty(s)) { + *ret = CGROUP_BLKIO_WEIGHT_INVALID; + return 0; + } + + r = safe_atou64(s, &u); + if (r < 0) + return r; + + if (u < CGROUP_BLKIO_WEIGHT_MIN || u > CGROUP_BLKIO_WEIGHT_MAX) + return -ERANGE; + + *ret = u; + return 0; +} + +static const char *cgroup_controller_table[_CGROUP_CONTROLLER_MAX] = { + [CGROUP_CONTROLLER_CPU] = "cpu", + [CGROUP_CONTROLLER_CPUACCT] = "cpuacct", + [CGROUP_CONTROLLER_BLKIO] = "blkio", + [CGROUP_CONTROLLER_MEMORY] = "memory", + [CGROUP_CONTROLLER_DEVICES] = "devices", + [CGROUP_CONTROLLER_PIDS] = "pids", +}; + +DEFINE_STRING_TABLE_LOOKUP(cgroup_controller, CGroupController); diff --git a/src/libbasic/cgroup-util.h b/src/libbasic/cgroup-util.h new file mode 100644 index 0000000000..ad1edd9cdb --- /dev/null +++ b/src/libbasic/cgroup-util.h @@ -0,0 +1,192 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "def.h" +#include "hashmap.h" +#include "macro.h" +#include "set.h" + +/* An enum of well known cgroup controllers */ +typedef enum CGroupController { + CGROUP_CONTROLLER_CPU, + CGROUP_CONTROLLER_CPUACCT, + CGROUP_CONTROLLER_BLKIO, + CGROUP_CONTROLLER_MEMORY, + CGROUP_CONTROLLER_DEVICES, + CGROUP_CONTROLLER_PIDS, + _CGROUP_CONTROLLER_MAX, + _CGROUP_CONTROLLER_INVALID = -1, +} CGroupController; + +#define CGROUP_CONTROLLER_TO_MASK(c) (1 << (c)) + +/* A bit mask of well known cgroup controllers */ +typedef enum CGroupMask { + CGROUP_MASK_CPU = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPU), + CGROUP_MASK_CPUACCT = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_CPUACCT), + CGROUP_MASK_BLKIO = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_BLKIO), + CGROUP_MASK_MEMORY = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_MEMORY), + CGROUP_MASK_DEVICES = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_DEVICES), + CGROUP_MASK_PIDS = CGROUP_CONTROLLER_TO_MASK(CGROUP_CONTROLLER_PIDS), + _CGROUP_MASK_ALL = CGROUP_CONTROLLER_TO_MASK(_CGROUP_CONTROLLER_MAX) - 1 +} CGroupMask; + +/* Special values for the cpu.shares attribute */ +#define CGROUP_CPU_SHARES_INVALID ((uint64_t) -1) +#define CGROUP_CPU_SHARES_MIN UINT64_C(2) +#define CGROUP_CPU_SHARES_MAX UINT64_C(262144) +#define CGROUP_CPU_SHARES_DEFAULT UINT64_C(1024) + +static inline bool CGROUP_CPU_SHARES_IS_OK(uint64_t x) { + return + x == CGROUP_CPU_SHARES_INVALID || + (x >= CGROUP_CPU_SHARES_MIN && x <= CGROUP_CPU_SHARES_MAX); +} + +/* Special values for the blkio.weight attribute */ +#define CGROUP_BLKIO_WEIGHT_INVALID ((uint64_t) -1) +#define CGROUP_BLKIO_WEIGHT_MIN UINT64_C(10) +#define CGROUP_BLKIO_WEIGHT_MAX UINT64_C(1000) +#define CGROUP_BLKIO_WEIGHT_DEFAULT UINT64_C(500) + +static inline bool CGROUP_BLKIO_WEIGHT_IS_OK(uint64_t x) { + return + x == CGROUP_BLKIO_WEIGHT_INVALID || + (x >= CGROUP_BLKIO_WEIGHT_MIN && x <= CGROUP_BLKIO_WEIGHT_MAX); +} + +/* + * General rules: + * + * We accept named hierarchies in the syntax "foo" and "name=foo". + * + * We expect that named hierarchies do not conflict in name with a + * kernel hierarchy, modulo the "name=" prefix. + * + * We always generate "normalized" controller names, i.e. without the + * "name=" prefix. + * + * We require absolute cgroup paths. When returning, we will always + * generate paths with multiple adjacent / removed. + */ + +int cg_enumerate_processes(const char *controller, const char *path, FILE **_f); +int cg_read_pid(FILE *f, pid_t *_pid); + +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); + +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); + +int cg_split_spec(const char *spec, char **controller, char **path); +int cg_mangle_path(const char *path, char **result); + +int cg_get_path(const char *controller, const char *path, const char *suffix, char **fs); +int cg_get_path_and_check(const char *controller, const char *path, const char *suffix, char **fs); + +int cg_pid_get_path(const char *controller, pid_t pid, char **path); + +int cg_trim(const char *controller, const char *path, bool delete_root); + +int cg_rmdir(const char *controller, const char *path); + +int cg_create(const char *controller, const char *path); +int cg_attach(const char *controller, const char *path, pid_t pid); +int cg_attach_fallback(const char *controller, const char *path, pid_t pid); +int cg_create_and_attach(const char *controller, const char *path, pid_t pid); + +int cg_set_attribute(const char *controller, const char *path, const char *attribute, const char *value); +int cg_get_attribute(const char *controller, const char *path, const char *attribute, char **ret); + +int cg_set_group_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); +int cg_set_task_access(const char *controller, const char *path, mode_t mode, uid_t uid, gid_t gid); + +int cg_install_release_agent(const char *controller, const char *agent); +int cg_uninstall_release_agent(const char *controller); + +int cg_is_empty(const char *controller, const char *path); +int cg_is_empty_recursive(const char *controller, const char *path); + +int cg_get_root_path(char **path); + +int cg_path_get_session(const char *path, char **session); +int cg_path_get_owner_uid(const char *path, uid_t *uid); +int cg_path_get_unit(const char *path, char **unit); +int cg_path_get_user_unit(const char *path, char **unit); +int cg_path_get_machine_name(const char *path, char **machine); +int cg_path_get_slice(const char *path, char **slice); +int cg_path_get_user_slice(const char *path, char **slice); + +int cg_shift_path(const char *cgroup, const char *cached_root, const char **shifted); +int cg_pid_get_path_shifted(pid_t pid, const char *cached_root, char **cgroup); + +int cg_pid_get_session(pid_t pid, char **session); +int cg_pid_get_owner_uid(pid_t pid, uid_t *uid); +int cg_pid_get_unit(pid_t pid, char **unit); +int cg_pid_get_user_unit(pid_t pid, char **unit); +int cg_pid_get_machine_name(pid_t pid, char **machine); +int cg_pid_get_slice(pid_t pid, char **slice); +int cg_pid_get_user_slice(pid_t pid, char **slice); + +int cg_path_decode_unit(const char *cgroup, char **unit); + +char *cg_escape(const char *p); +char *cg_unescape(const char *p) _pure_; + +bool cg_controller_is_valid(const char *p); + +int cg_slice_to_path(const char *unit, char **ret); + +typedef const char* (*cg_migrate_callback_t)(CGroupMask mask, void *userdata); + +int cg_create_everywhere(CGroupMask supported, CGroupMask mask, const char *path); +int cg_attach_everywhere(CGroupMask supported, const char *path, pid_t pid, cg_migrate_callback_t callback, void *userdata); +int cg_attach_many_everywhere(CGroupMask supported, const char *path, Set* pids, cg_migrate_callback_t callback, void *userdata); +int cg_migrate_everywhere(CGroupMask supported, const char *from, const char *to, cg_migrate_callback_t callback, void *userdata); +int cg_trim_everywhere(CGroupMask supported, const char *path, bool delete_root); +int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p); + +int cg_mask_supported(CGroupMask *ret); + +int cg_kernel_controllers(Set *controllers); + +int cg_unified(void); +void cg_unified_flush(void); + +bool cg_is_unified_wanted(void); +bool cg_is_legacy_wanted(void); + +const char* cgroup_controller_to_string(CGroupController c) _const_; +CGroupController cgroup_controller_from_string(const char *s) _pure_; + +int cg_cpu_shares_parse(const char *s, uint64_t *ret); +int cg_blkio_weight_parse(const char *s, uint64_t *ret); diff --git a/src/libbasic/chattr-util.c b/src/libbasic/chattr-util.c new file mode 100644 index 0000000000..2896a729af --- /dev/null +++ b/src/libbasic/chattr-util.c @@ -0,0 +1,107 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "chattr-util.h" +#include "fd-util.h" +#include "macro.h" + +int chattr_fd(int fd, unsigned value, unsigned mask) { + unsigned old_attr, new_attr; + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + /* Explicitly check whether this is a regular file or + * directory. If it is anything else (such as a device node or + * fifo), then the ioctl will not hit the file systems but + * possibly drivers, where the ioctl might have different + * effects. Notably, DRM is using the same ioctl() number. */ + + if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) + return -ENOTTY; + + if (mask == 0) + return 0; + + if (ioctl(fd, FS_IOC_GETFLAGS, &old_attr) < 0) + return -errno; + + new_attr = (old_attr & ~mask) | (value & mask); + if (new_attr == old_attr) + return 0; + + if (ioctl(fd, FS_IOC_SETFLAGS, &new_attr) < 0) + return -errno; + + return 1; +} + +int chattr_path(const char *p, unsigned value, unsigned mask) { + _cleanup_close_ int fd = -1; + + assert(p); + + if (mask == 0) + return 0; + + fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return chattr_fd(fd, value, mask); +} + +int read_attr_fd(int fd, unsigned *ret) { + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode) && !S_ISREG(st.st_mode)) + return -ENOTTY; + + if (ioctl(fd, FS_IOC_GETFLAGS, ret) < 0) + return -errno; + + return 0; +} + +int read_attr_path(const char *p, unsigned *ret) { + _cleanup_close_ int fd = -1; + + assert(p); + assert(ret); + + fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return read_attr_fd(fd, ret); +} diff --git a/src/libbasic/chattr-util.h b/src/libbasic/chattr-util.h new file mode 100644 index 0000000000..960cf6d5b3 --- /dev/null +++ b/src/libbasic/chattr-util.h @@ -0,0 +1,26 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int chattr_fd(int fd, unsigned value, unsigned mask); +int chattr_path(const char *p, unsigned value, unsigned mask); + +int read_attr_fd(int fd, unsigned *ret); +int read_attr_path(const char *p, unsigned *ret); diff --git a/src/libbasic/clock-util.c b/src/libbasic/clock-util.c new file mode 100644 index 0000000000..507e757ff0 --- /dev/null +++ b/src/libbasic/clock-util.c @@ -0,0 +1,160 @@ +/*** + This file is part of systemd. + + Copyright 2010-2012 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clock-util.h" +#include "fd-util.h" +#include "macro.h" +#include "string-util.h" +#include "util.h" + +int clock_get_hwclock(struct tm *tm) { + _cleanup_close_ int fd = -1; + + assert(tm); + + fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + + /* This leaves the timezone fields of struct tm + * uninitialized! */ + if (ioctl(fd, RTC_RD_TIME, tm) < 0) + return -errno; + + /* We don't know daylight saving, so we reset this in order not + * to confuse mktime(). */ + tm->tm_isdst = -1; + + return 0; +} + +int clock_set_hwclock(const struct tm *tm) { + _cleanup_close_ int fd = -1; + + assert(tm); + + fd = open("/dev/rtc", O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + + if (ioctl(fd, RTC_SET_TIME, tm) < 0) + return -errno; + + return 0; +} + +int clock_is_localtime(void) { + _cleanup_fclose_ FILE *f; + + /* + * The third line of adjtime is "UTC" or "LOCAL" or nothing. + * # /etc/adjtime + * 0.0 0 0 + * 0 + * UTC + */ + f = fopen("/etc/adjtime", "re"); + if (f) { + char line[LINE_MAX]; + bool b; + + b = fgets(line, sizeof(line), f) && + fgets(line, sizeof(line), f) && + fgets(line, sizeof(line), f); + if (!b) + return -EIO; + + truncate_nl(line); + return streq(line, "LOCAL"); + + } else if (errno != ENOENT) + return -errno; + + return 0; +} + +int clock_set_timezone(int *min) { + const struct timeval *tv_null = NULL; + struct timespec ts; + struct tm *tm; + int minutesdelta; + struct timezone tz; + + assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + assert_se(tm = localtime(&ts.tv_sec)); + minutesdelta = tm->tm_gmtoff / 60; + + tz.tz_minuteswest = -minutesdelta; + tz.tz_dsttime = 0; /* DST_NONE */ + + /* + * If the RTC does not run in UTC but in local time, the very first + * call to settimeofday() will set the kernel's timezone and will warp the + * system clock, so that it runs in UTC instead of the local time we + * have read from the RTC. + */ + if (settimeofday(tv_null, &tz) < 0) + return negative_errno(); + + if (min) + *min = minutesdelta; + return 0; +} + +int clock_reset_timewarp(void) { + const struct timeval *tv_null = NULL; + struct timezone tz; + + tz.tz_minuteswest = 0; + tz.tz_dsttime = 0; /* DST_NONE */ + + /* + * The very first call to settimeofday() does time warp magic. Do a + * dummy call here, so the time warping is sealed and all later calls + * behave as expected. + */ + if (settimeofday(tv_null, &tz) < 0) + return -errno; + + return 0; +} + +#define TIME_EPOCH_USEC ((usec_t) TIME_EPOCH * USEC_PER_SEC) + +int clock_apply_epoch(void) { + struct timespec ts; + + if (now(CLOCK_REALTIME) >= TIME_EPOCH_USEC) + return 0; + + if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, TIME_EPOCH_USEC)) < 0) + return -errno; + + return 1; +} diff --git a/src/libbasic/clock-util.h b/src/libbasic/clock-util.h new file mode 100644 index 0000000000..f471f2abcf --- /dev/null +++ b/src/libbasic/clock-util.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2012 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 . +***/ + +#include + +int clock_is_localtime(void); +int clock_set_timezone(int *min); +int clock_reset_timewarp(void); +int clock_get_hwclock(struct tm *tm); +int clock_set_hwclock(const struct tm *tm); +int clock_apply_epoch(void); diff --git a/src/libbasic/conf-files.c b/src/libbasic/conf-files.c new file mode 100644 index 0000000000..c781610e14 --- /dev/null +++ b/src/libbasic/conf-files.c @@ -0,0 +1,166 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "conf-files.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +static int files_add(Hashmap *h, const char *root, const char *path, const char *suffix) { + _cleanup_closedir_ DIR *dir = NULL; + const char *dirpath; + struct dirent *de; + int r; + + assert(path); + assert(suffix); + + dirpath = prefix_roota(root, path); + + dir = opendir(dirpath); + if (!dir) { + if (errno == ENOENT) + return 0; + return -errno; + } + + FOREACH_DIRENT(de, dir, return -errno) { + char *p; + + if (!dirent_is_file_with_suffix(de, suffix)) + continue; + + p = strjoin(dirpath, "/", de->d_name, NULL); + if (!p) + return -ENOMEM; + + r = hashmap_put(h, basename(p), p); + if (r == -EEXIST) { + log_debug("Skipping overridden file: %s.", p); + free(p); + } else if (r < 0) { + free(p); + return r; + } else if (r == 0) { + log_debug("Duplicate file %s", p); + free(p); + } + } + + return 0; +} + +static int base_cmp(const void *a, const void *b) { + const char *s1, *s2; + + s1 = *(char * const *)a; + s2 = *(char * const *)b; + return strcmp(basename(s1), basename(s2)); +} + +static int conf_files_list_strv_internal(char ***strv, const char *suffix, const char *root, char **dirs) { + _cleanup_hashmap_free_ Hashmap *fh = NULL; + char **files, **p; + int r; + + assert(strv); + assert(suffix); + + /* This alters the dirs string array */ + if (!path_strv_resolve_uniq(dirs, root)) + return -ENOMEM; + + fh = hashmap_new(&string_hash_ops); + if (!fh) + return -ENOMEM; + + STRV_FOREACH(p, dirs) { + r = files_add(fh, root, *p, suffix); + if (r == -ENOMEM) + return r; + if (r < 0) + log_debug_errno(r, "Failed to search for files in %s, ignoring: %m", *p); + } + + files = hashmap_get_strv(fh); + if (!files) + return -ENOMEM; + + qsort_safe(files, hashmap_size(fh), sizeof(char *), base_cmp); + *strv = files; + + return 0; +} + +int conf_files_list_strv(char ***strv, const char *suffix, const char *root, const char* const* dirs) { + _cleanup_strv_free_ char **copy = NULL; + + assert(strv); + assert(suffix); + + copy = strv_copy((char**) dirs); + if (!copy) + return -ENOMEM; + + return conf_files_list_strv_internal(strv, suffix, root, copy); +} + +int conf_files_list(char ***strv, const char *suffix, const char *root, const char *dir, ...) { + _cleanup_strv_free_ char **dirs = NULL; + va_list ap; + + assert(strv); + assert(suffix); + + va_start(ap, dir); + dirs = strv_new_ap(dir, ap); + va_end(ap); + + if (!dirs) + return -ENOMEM; + + return conf_files_list_strv_internal(strv, suffix, root, dirs); +} + +int conf_files_list_nulstr(char ***strv, const char *suffix, const char *root, const char *d) { + _cleanup_strv_free_ char **dirs = NULL; + + assert(strv); + assert(suffix); + + dirs = strv_split_nulstr(d); + if (!dirs) + return -ENOMEM; + + return conf_files_list_strv_internal(strv, suffix, root, dirs); +} diff --git a/src/libbasic/conf-files.h b/src/libbasic/conf-files.h new file mode 100644 index 0000000000..e00e0e81fb --- /dev/null +++ b/src/libbasic/conf-files.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2012 Lennart Poettering + Copyright 2010-2012 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 . +***/ + +int conf_files_list(char ***ret, const char *suffix, const char *root, const char *dir, ...); +int conf_files_list_strv(char ***ret, const char *suffix, const char *root, const char* const* dirs); +int conf_files_list_nulstr(char ***ret, const char *suffix, const char *root, const char *dirs); diff --git a/src/libbasic/copy.c b/src/libbasic/copy.c new file mode 100644 index 0000000000..519b412941 --- /dev/null +++ b/src/libbasic/copy.c @@ -0,0 +1,548 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "btrfs-util.h" +#include "chattr-util.h" +#include "copy.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "io-util.h" +#include "macro.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "umask-util.h" +#include "xattr-util.h" + +#define COPY_BUFFER_SIZE (16*1024) + +int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink) { + bool try_sendfile = true, try_splice = true; + int r; + + assert(fdf >= 0); + assert(fdt >= 0); + + /* Try btrfs reflinks first. */ + if (try_reflink && + max_bytes == (uint64_t) -1 && + lseek(fdf, 0, SEEK_CUR) == 0 && + lseek(fdt, 0, SEEK_CUR) == 0) { + + r = btrfs_reflink(fdf, fdt); + if (r >= 0) + return 0; /* we copied the whole thing, hence hit EOF, return 0 */ + } + + for (;;) { + size_t m = COPY_BUFFER_SIZE; + ssize_t n; + + if (max_bytes != (uint64_t) -1) { + + if (max_bytes <= 0) + return 1; /* return > 0 if we hit the max_bytes limit */ + + if ((uint64_t) m > max_bytes) + m = (size_t) max_bytes; + } + + /* First try sendfile(), unless we already tried */ + if (try_sendfile) { + + n = sendfile(fdt, fdf, NULL, m); + if (n < 0) { + if (errno != EINVAL && errno != ENOSYS) + return -errno; + + try_sendfile = false; + /* use fallback below */ + } else if (n == 0) /* EOF */ + break; + else if (n > 0) + /* Success! */ + goto next; + } + + /* The try splice, unless we already tried */ + if (try_splice) { + n = splice(fdf, NULL, fdt, NULL, m, 0); + if (n < 0) { + if (errno != EINVAL && errno != ENOSYS) + return -errno; + + try_splice = false; + /* use fallback below */ + } else if (n == 0) /* EOF */ + break; + else if (n > 0) + /* Success! */ + goto next; + } + + /* As a fallback just copy bits by hand */ + { + uint8_t buf[m]; + + n = read(fdf, buf, m); + if (n < 0) + return -errno; + if (n == 0) /* EOF */ + break; + + r = loop_write(fdt, buf, (size_t) n, false); + if (r < 0) + return r; + } + + next: + if (max_bytes != (uint64_t) -1) { + assert(max_bytes >= (uint64_t) n); + max_bytes -= n; + } + } + + return 0; /* return 0 if we hit EOF earlier than the size limit */ +} + +static int fd_copy_symlink(int df, const char *from, const struct stat *st, int dt, const char *to) { + _cleanup_free_ char *target = NULL; + int r; + + assert(from); + assert(st); + assert(to); + + r = readlinkat_malloc(df, from, &target); + if (r < 0) + return r; + + if (symlinkat(target, dt, to) < 0) + return -errno; + + if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) + return -errno; + + return 0; +} + +static int fd_copy_regular(int df, const char *from, const struct stat *st, int dt, const char *to) { + _cleanup_close_ int fdf = -1, fdt = -1; + struct timespec ts[2]; + int r, q; + + assert(from); + assert(st); + assert(to); + + fdf = openat(df, from, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fdf < 0) + return -errno; + + fdt = openat(dt, to, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, st->st_mode & 07777); + if (fdt < 0) + return -errno; + + r = copy_bytes(fdf, fdt, (uint64_t) -1, true); + if (r < 0) { + unlinkat(dt, to, 0); + return r; + } + + if (fchown(fdt, st->st_uid, st->st_gid) < 0) + r = -errno; + + if (fchmod(fdt, st->st_mode & 07777) < 0) + r = -errno; + + ts[0] = st->st_atim; + ts[1] = st->st_mtim; + (void) futimens(fdt, ts); + + (void) copy_xattr(fdf, fdt); + + q = close(fdt); + fdt = -1; + + if (q < 0) { + r = -errno; + unlinkat(dt, to, 0); + } + + return r; +} + +static int fd_copy_fifo(int df, const char *from, const struct stat *st, int dt, const char *to) { + int r; + + assert(from); + assert(st); + assert(to); + + r = mkfifoat(dt, to, st->st_mode & 07777); + if (r < 0) + return -errno; + + if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) + r = -errno; + + if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0) + r = -errno; + + return r; +} + +static int fd_copy_node(int df, const char *from, const struct stat *st, int dt, const char *to) { + int r; + + assert(from); + assert(st); + assert(to); + + r = mknodat(dt, to, st->st_mode, st->st_rdev); + if (r < 0) + return -errno; + + if (fchownat(dt, to, st->st_uid, st->st_gid, AT_SYMLINK_NOFOLLOW) < 0) + r = -errno; + + if (fchmodat(dt, to, st->st_mode & 07777, 0) < 0) + r = -errno; + + return r; +} + +static int fd_copy_directory( + int df, + const char *from, + const struct stat *st, + int dt, + const char *to, + dev_t original_device, + bool merge) { + + _cleanup_close_ int fdf = -1, fdt = -1; + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + bool created; + int r; + + assert(st); + assert(to); + + if (from) + fdf = openat(df, from, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + else + fdf = fcntl(df, F_DUPFD_CLOEXEC, 3); + + d = fdopendir(fdf); + if (!d) + return -errno; + fdf = -1; + + r = mkdirat(dt, to, st->st_mode & 07777); + if (r >= 0) + created = true; + else if (errno == EEXIST && merge) + created = false; + else + return -errno; + + fdt = openat(dt, to, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fdt < 0) + return -errno; + + r = 0; + + if (created) { + struct timespec ut[2] = { + st->st_atim, + st->st_mtim + }; + + if (fchown(fdt, st->st_uid, st->st_gid) < 0) + r = -errno; + + if (fchmod(fdt, st->st_mode & 07777) < 0) + r = -errno; + + (void) futimens(fdt, ut); + (void) copy_xattr(dirfd(d), fdt); + } + + FOREACH_DIRENT_ALL(de, d, return -errno) { + struct stat buf; + int q; + + if (STR_IN_SET(de->d_name, ".", "..")) + continue; + + if (fstatat(dirfd(d), de->d_name, &buf, AT_SYMLINK_NOFOLLOW) < 0) { + r = -errno; + continue; + } + + if (buf.st_dev != original_device) + continue; + + if (S_ISREG(buf.st_mode)) + q = fd_copy_regular(dirfd(d), de->d_name, &buf, fdt, de->d_name); + else if (S_ISDIR(buf.st_mode)) + q = fd_copy_directory(dirfd(d), de->d_name, &buf, fdt, de->d_name, original_device, merge); + else if (S_ISLNK(buf.st_mode)) + q = fd_copy_symlink(dirfd(d), de->d_name, &buf, fdt, de->d_name); + else if (S_ISFIFO(buf.st_mode)) + q = fd_copy_fifo(dirfd(d), de->d_name, &buf, fdt, de->d_name); + else if (S_ISBLK(buf.st_mode) || S_ISCHR(buf.st_mode)) + q = fd_copy_node(dirfd(d), de->d_name, &buf, fdt, de->d_name); + else + q = -EOPNOTSUPP; + + if (q == -EEXIST && merge) + q = 0; + + if (q < 0) + r = q; + } + + return r; +} + +int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge) { + struct stat st; + + assert(from); + assert(to); + + if (fstatat(fdf, from, &st, AT_SYMLINK_NOFOLLOW) < 0) + return -errno; + + if (S_ISREG(st.st_mode)) + return fd_copy_regular(fdf, from, &st, fdt, to); + else if (S_ISDIR(st.st_mode)) + return fd_copy_directory(fdf, from, &st, fdt, to, st.st_dev, merge); + else if (S_ISLNK(st.st_mode)) + return fd_copy_symlink(fdf, from, &st, fdt, to); + else if (S_ISFIFO(st.st_mode)) + return fd_copy_fifo(fdf, from, &st, fdt, to); + else if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)) + return fd_copy_node(fdf, from, &st, fdt, to); + else + return -EOPNOTSUPP; +} + +int copy_tree(const char *from, const char *to, bool merge) { + return copy_tree_at(AT_FDCWD, from, AT_FDCWD, to, merge); +} + +int copy_directory_fd(int dirfd, const char *to, bool merge) { + + struct stat st; + + assert(dirfd >= 0); + assert(to); + + if (fstat(dirfd, &st) < 0) + return -errno; + + if (!S_ISDIR(st.st_mode)) + return -ENOTDIR; + + return fd_copy_directory(dirfd, NULL, &st, AT_FDCWD, to, st.st_dev, merge); +} + +int copy_file_fd(const char *from, int fdt, bool try_reflink) { + _cleanup_close_ int fdf = -1; + int r; + + assert(from); + assert(fdt >= 0); + + fdf = open(from, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fdf < 0) + return -errno; + + r = copy_bytes(fdf, fdt, (uint64_t) -1, try_reflink); + + (void) copy_times(fdf, fdt); + (void) copy_xattr(fdf, fdt); + + return r; +} + +int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned chattr_flags) { + int fdt = -1, r; + + assert(from); + assert(to); + + RUN_WITH_UMASK(0000) { + fdt = open(to, flags|O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, mode); + if (fdt < 0) + return -errno; + } + + if (chattr_flags != 0) + (void) chattr_fd(fdt, chattr_flags, (unsigned) -1); + + r = copy_file_fd(from, fdt, true); + if (r < 0) { + close(fdt); + unlink(to); + return r; + } + + if (close(fdt) < 0) { + unlink_noerrno(to); + return -errno; + } + + return 0; +} + +int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace, unsigned chattr_flags) { + _cleanup_free_ char *t = NULL; + int r; + + assert(from); + assert(to); + + r = tempfn_random(to, NULL, &t); + if (r < 0) + return r; + + r = copy_file(from, t, O_NOFOLLOW|O_EXCL, mode, chattr_flags); + if (r < 0) + return r; + + if (replace) { + r = renameat(AT_FDCWD, t, AT_FDCWD, to); + if (r < 0) + r = -errno; + } else + r = rename_noreplace(AT_FDCWD, t, AT_FDCWD, to); + if (r < 0) { + (void) unlink_noerrno(t); + return r; + } + + return 0; +} + +int copy_times(int fdf, int fdt) { + struct timespec ut[2]; + struct stat st; + usec_t crtime = 0; + + assert(fdf >= 0); + assert(fdt >= 0); + + if (fstat(fdf, &st) < 0) + return -errno; + + ut[0] = st.st_atim; + ut[1] = st.st_mtim; + + if (futimens(fdt, ut) < 0) + return -errno; + + if (fd_getcrtime(fdf, &crtime) >= 0) + (void) fd_setcrtime(fdt, crtime); + + return 0; +} + +int copy_xattr(int fdf, int fdt) { + _cleanup_free_ char *bufa = NULL, *bufb = NULL; + size_t sza = 100, szb = 100; + ssize_t n; + int ret = 0; + const char *p; + + for (;;) { + bufa = malloc(sza); + if (!bufa) + return -ENOMEM; + + n = flistxattr(fdf, bufa, sza); + if (n == 0) + return 0; + if (n > 0) + break; + if (errno != ERANGE) + return -errno; + + sza *= 2; + + bufa = mfree(bufa); + } + + p = bufa; + while (n > 0) { + size_t l; + + l = strlen(p); + assert(l < (size_t) n); + + if (startswith(p, "user.")) { + ssize_t m; + + if (!bufb) { + bufb = malloc(szb); + if (!bufb) + return -ENOMEM; + } + + m = fgetxattr(fdf, p, bufb, szb); + if (m < 0) { + if (errno == ERANGE) { + szb *= 2; + bufb = mfree(bufb); + continue; + } + + return -errno; + } + + if (fsetxattr(fdt, p, bufb, m, 0) < 0) + ret = -errno; + } + + p += l + 1; + n -= l + 1; + } + + return ret; +} diff --git a/src/libbasic/copy.h b/src/libbasic/copy.h new file mode 100644 index 0000000000..3e5eb52506 --- /dev/null +++ b/src/libbasic/copy.h @@ -0,0 +1,35 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include + +int copy_file_fd(const char *from, int to, bool try_reflink); +int copy_file(const char *from, const char *to, int flags, mode_t mode, unsigned chattr_flags); +int copy_file_atomic(const char *from, const char *to, mode_t mode, bool replace, unsigned chattr_flags); +int copy_tree(const char *from, const char *to, bool merge); +int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge); +int copy_directory_fd(int dirfd, const char *to, bool merge); +int copy_bytes(int fdf, int fdt, uint64_t max_bytes, bool try_reflink); +int copy_times(int fdf, int fdt); +int copy_xattr(int fdf, int fdt); diff --git a/src/libbasic/cpu-set-util.c b/src/libbasic/cpu-set-util.c new file mode 100644 index 0000000000..95ed6928ff --- /dev/null +++ b/src/libbasic/cpu-set-util.c @@ -0,0 +1,114 @@ +/*** + This file is part of systemd. + + Copyright 2010-2015 Lennart Poettering + Copyright 2015 Filipe Brandenburger + + 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 . +***/ + +#include +#include +#include + +#include "alloc-util.h" +#include "cpu-set-util.h" +#include "extract-word.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" + +cpu_set_t* cpu_set_malloc(unsigned *ncpus) { + cpu_set_t *c; + unsigned n = 1024; + + /* Allocates the cpuset in the right size */ + + for (;;) { + c = CPU_ALLOC(n); + if (!c) + return NULL; + + if (sched_getaffinity(0, CPU_ALLOC_SIZE(n), c) >= 0) { + CPU_ZERO_S(CPU_ALLOC_SIZE(n), c); + + if (ncpus) + *ncpus = n; + + return c; + } + + CPU_FREE(c); + + if (errno != EINVAL) + return NULL; + + n *= 2; + } +} + +int parse_cpu_set_and_warn( + const char *rvalue, + cpu_set_t **cpu_set, + const char *unit, + const char *filename, + unsigned line, + const char *lvalue) { + + const char *whole_rvalue = rvalue; + _cleanup_cpu_free_ cpu_set_t *c = NULL; + unsigned ncpus = 0; + + assert(lvalue); + assert(rvalue); + + for (;;) { + _cleanup_free_ char *word = NULL; + unsigned cpu, cpu_lower, cpu_upper; + int r; + + r = extract_first_word(&rvalue, &word, WHITESPACE ",", EXTRACT_QUOTES); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, whole_rvalue); + if (r == 0) + break; + + if (!c) { + c = cpu_set_malloc(&ncpus); + if (!c) + return log_oom(); + } + + r = parse_range(word, &cpu_lower, &cpu_upper); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CPU affinity '%s'", word); + if (cpu_lower >= ncpus || cpu_upper >= ncpus) + return log_syntax(unit, LOG_ERR, filename, line, EINVAL, "CPU out of range '%s' ncpus is %u", word, ncpus); + + if (cpu_lower > cpu_upper) + log_syntax(unit, LOG_WARNING, filename, line, 0, "Range '%s' is invalid, %u > %u", word, cpu_lower, cpu_upper); + else + for (cpu = cpu_lower; cpu <= cpu_upper; cpu++) + CPU_SET_S(cpu, CPU_ALLOC_SIZE(ncpus), c); + } + + /* On success, sets *cpu_set and returns ncpus for the system. */ + if (c) { + *cpu_set = c; + c = NULL; + } + + return (int) ncpus; +} diff --git a/src/libbasic/cpu-set-util.h b/src/libbasic/cpu-set-util.h new file mode 100644 index 0000000000..6f49d9afb0 --- /dev/null +++ b/src/libbasic/cpu-set-util.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2015 Lennart Poettering + Copyright 2015 Filipe Brandenburger + + 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 . +***/ + +#include + +#include "macro.h" + +DEFINE_TRIVIAL_CLEANUP_FUNC(cpu_set_t*, CPU_FREE); +#define _cleanup_cpu_free_ _cleanup_(CPU_FREEp) + +cpu_set_t* cpu_set_malloc(unsigned *ncpus); + +int parse_cpu_set_and_warn(const char *rvalue, cpu_set_t **cpu_set, const char *unit, const char *filename, unsigned line, const char *lvalue); diff --git a/src/libbasic/def.h b/src/libbasic/def.h new file mode 100644 index 0000000000..963343eb7d --- /dev/null +++ b/src/libbasic/def.h @@ -0,0 +1,92 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "util.h" + +#define DEFAULT_TIMEOUT_USEC (90*USEC_PER_SEC) +#define DEFAULT_RESTART_USEC (100*USEC_PER_MSEC) +#define DEFAULT_CONFIRM_USEC (30*USEC_PER_SEC) + +#define DEFAULT_START_LIMIT_INTERVAL (10*USEC_PER_SEC) +#define DEFAULT_START_LIMIT_BURST 5 + +/* The default time after which exit-on-idle services exit. This + * should be kept lower than the watchdog timeout, because otherwise + * the watchdog pings will keep the loop busy. */ +#define DEFAULT_EXIT_USEC (30*USEC_PER_SEC) + +/* The default value for the net.unix.max_dgram_qlen sysctl */ +#define DEFAULT_UNIX_MAX_DGRAM_QLEN 512UL + +#define SYSTEMD_CGROUP_CONTROLLER "name=systemd" + +#define SIGNALS_CRASH_HANDLER SIGSEGV,SIGILL,SIGFPE,SIGBUS,SIGQUIT,SIGABRT +#define SIGNALS_IGNORE SIGPIPE + +#define REBOOT_PARAM_FILE "/run/systemd/reboot-param" + +#ifdef HAVE_SPLIT_USR +#define KBD_KEYMAP_DIRS \ + "/usr/share/keymaps/\0" \ + "/usr/share/kbd/keymaps/\0" \ + "/usr/lib/kbd/keymaps/\0" \ + "/lib/kbd/keymaps/\0" +#else +#define KBD_KEYMAP_DIRS \ + "/usr/share/keymaps/\0" \ + "/usr/share/kbd/keymaps/\0" \ + "/usr/lib/kbd/keymaps/\0" +#endif + +#define UNIX_SYSTEM_BUS_ADDRESS "unix:path=/var/run/dbus/system_bus_socket" +#define KERNEL_SYSTEM_BUS_ADDRESS "kernel:path=/sys/fs/kdbus/0-system/bus" +#define DEFAULT_SYSTEM_BUS_ADDRESS KERNEL_SYSTEM_BUS_ADDRESS ";" UNIX_SYSTEM_BUS_ADDRESS +#define UNIX_USER_BUS_ADDRESS_FMT "unix:path=%s/bus" +#define KERNEL_USER_BUS_ADDRESS_FMT "kernel:path=/sys/fs/kdbus/"UID_FMT"-user/bus" + +#define PLYMOUTH_SOCKET { \ + .un.sun_family = AF_UNIX, \ + .un.sun_path = "\0/org/freedesktop/plymouthd", \ + } + +#ifndef TTY_GID +#define TTY_GID 5 +#endif + +#define NOTIFY_FD_MAX 768 +#define NOTIFY_BUFFER_MAX PIPE_BUF + +#ifdef HAVE_SPLIT_USR +#define _CONF_PATHS_SPLIT_USR(n) "/lib/" n "\0" +#else +#define _CONF_PATHS_SPLIT_USR(n) +#endif + +/* Return a nulstr for a standard cascade of configuration paths, + * suitable to pass to conf_files_list_nulstr() or config_parse_many() + * to implement drop-in directories for extending configuration + * files. */ +#define CONF_PATHS_NULSTR(n) \ + "/etc/" n "\0" \ + "/run/" n "\0" \ + "/usr/local/lib/" n "\0" \ + "/usr/lib/" n "\0" \ + _CONF_PATHS_SPLIT_USR(n) diff --git a/src/libbasic/device-nodes.c b/src/libbasic/device-nodes.c new file mode 100644 index 0000000000..38c0628a90 --- /dev/null +++ b/src/libbasic/device-nodes.c @@ -0,0 +1,80 @@ +/*** + This file is part of systemd. + + Copyright 2008-2011 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 . +***/ + +#include +#include +#include + +#include "device-nodes.h" +#include "utf8.h" + +int whitelisted_char_for_devnode(char c, const char *white) { + + if ((c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + strchr("#+-.:=@_", c) != NULL || + (white != NULL && strchr(white, c) != NULL)) + return 1; + + return 0; +} + +int encode_devnode_name(const char *str, char *str_enc, size_t len) { + size_t i, j; + + if (str == NULL || str_enc == NULL) + return -EINVAL; + + for (i = 0, j = 0; str[i] != '\0'; i++) { + int seqlen; + + seqlen = utf8_encoded_valid_unichar(&str[i]); + if (seqlen > 1) { + + if (len-j < (size_t)seqlen) + return -EINVAL; + + memcpy(&str_enc[j], &str[i], seqlen); + j += seqlen; + i += (seqlen-1); + + } else if (str[i] == '\\' || !whitelisted_char_for_devnode(str[i], NULL)) { + + if (len-j < 4) + return -EINVAL; + + sprintf(&str_enc[j], "\\x%02x", (unsigned char) str[i]); + j += 4; + + } else { + if (len-j < 1) + return -EINVAL; + + str_enc[j] = str[i]; + j++; + } + } + + if (len-j < 1) + return -EINVAL; + + str_enc[j] = '\0'; + return 0; +} diff --git a/src/libbasic/device-nodes.h b/src/libbasic/device-nodes.h new file mode 100644 index 0000000000..94f385abcb --- /dev/null +++ b/src/libbasic/device-nodes.h @@ -0,0 +1,26 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include +#include + +int encode_devnode_name(const char *str, char *str_enc, size_t len); +int whitelisted_char_for_devnode(char c, const char *additional); diff --git a/src/libbasic/dirent-util.c b/src/libbasic/dirent-util.c new file mode 100644 index 0000000000..5fb535cb13 --- /dev/null +++ b/src/libbasic/dirent-util.c @@ -0,0 +1,78 @@ +/*** + This file is part of systemd. + + Copyright 2010-2012 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 . +***/ + +#include +#include + +#include "dirent-util.h" +#include "path-util.h" +#include "string-util.h" + +int dirent_ensure_type(DIR *d, struct dirent *de) { + struct stat st; + + assert(d); + assert(de); + + if (de->d_type != DT_UNKNOWN) + return 0; + + if (fstatat(dirfd(d), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) + return -errno; + + de->d_type = + S_ISREG(st.st_mode) ? DT_REG : + S_ISDIR(st.st_mode) ? DT_DIR : + S_ISLNK(st.st_mode) ? DT_LNK : + S_ISFIFO(st.st_mode) ? DT_FIFO : + S_ISSOCK(st.st_mode) ? DT_SOCK : + S_ISCHR(st.st_mode) ? DT_CHR : + S_ISBLK(st.st_mode) ? DT_BLK : + DT_UNKNOWN; + + return 0; +} + +bool dirent_is_file(const struct dirent *de) { + assert(de); + + if (hidden_file(de->d_name)) + return false; + + if (de->d_type != DT_REG && + de->d_type != DT_LNK && + de->d_type != DT_UNKNOWN) + return false; + + return true; +} + +bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) { + assert(de); + + if (de->d_type != DT_REG && + de->d_type != DT_LNK && + de->d_type != DT_UNKNOWN) + return false; + + if (hidden_file_allow_backup(de->d_name)) + return false; + + return endswith(de->d_name, suffix); +} diff --git a/src/libbasic/dirent-util.h b/src/libbasic/dirent-util.h new file mode 100644 index 0000000000..6bf099b46c --- /dev/null +++ b/src/libbasic/dirent-util.h @@ -0,0 +1,52 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#include "macro.h" +#include "path-util.h" + +int dirent_ensure_type(DIR *d, struct dirent *de); + +bool dirent_is_file(const struct dirent *de) _pure_; +bool dirent_is_file_with_suffix(const struct dirent *de, const char *suffix) _pure_; + +#define FOREACH_DIRENT(de, d, on_error) \ + for (errno = 0, de = readdir(d);; errno = 0, de = readdir(d)) \ + if (!de) { \ + if (errno > 0) { \ + on_error; \ + } \ + break; \ + } else if (hidden_file((de)->d_name)) \ + continue; \ + else + +#define FOREACH_DIRENT_ALL(de, d, on_error) \ + for (errno = 0, de = readdir(d);; errno = 0, de = readdir(d)) \ + if (!de) { \ + if (errno > 0) { \ + on_error; \ + } \ + break; \ + } else diff --git a/src/libbasic/env-util.c b/src/libbasic/env-util.c new file mode 100644 index 0000000000..7f5fddb700 --- /dev/null +++ b/src/libbasic/env-util.c @@ -0,0 +1,624 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "env-util.h" +#include "extract-word.h" +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" +#include "utf8.h" + +#define VALID_CHARS_ENV_NAME \ + DIGITS LETTERS \ + "_" + +#ifndef ARG_MAX +#define ARG_MAX ((size_t) sysconf(_SC_ARG_MAX)) +#endif + +static bool env_name_is_valid_n(const char *e, size_t n) { + const char *p; + + if (!e) + return false; + + if (n <= 0) + return false; + + if (e[0] >= '0' && e[0] <= '9') + return false; + + /* POSIX says the overall size of the environment block cannot + * be > ARG_MAX, an individual assignment hence cannot be + * either. Discounting the equal sign and trailing NUL this + * hence leaves ARG_MAX-2 as longest possible variable + * name. */ + if (n > ARG_MAX - 2) + return false; + + for (p = e; p < e + n; p++) + if (!strchr(VALID_CHARS_ENV_NAME, *p)) + return false; + + return true; +} + +bool env_name_is_valid(const char *e) { + if (!e) + return false; + + return env_name_is_valid_n(e, strlen(e)); +} + +bool env_value_is_valid(const char *e) { + if (!e) + return false; + + if (!utf8_is_valid(e)) + return false; + + /* bash allows tabs in environment variables, and so should + * we */ + if (string_has_cc(e, "\t")) + return false; + + /* POSIX says the overall size of the environment block cannot + * be > ARG_MAX, an individual assignment hence cannot be + * either. Discounting the shortest possible variable name of + * length 1, the equal sign and trailing NUL this hence leaves + * ARG_MAX-3 as longest possible variable value. */ + if (strlen(e) > ARG_MAX - 3) + return false; + + return true; +} + +bool env_assignment_is_valid(const char *e) { + const char *eq; + + eq = strchr(e, '='); + if (!eq) + return false; + + if (!env_name_is_valid_n(e, eq - e)) + return false; + + if (!env_value_is_valid(eq + 1)) + return false; + + /* POSIX says the overall size of the environment block cannot + * be > ARG_MAX, hence the individual variable assignments + * cannot be either, but let's leave room for one trailing NUL + * byte. */ + if (strlen(e) > ARG_MAX - 1) + return false; + + return true; +} + +bool strv_env_is_valid(char **e) { + char **p, **q; + + STRV_FOREACH(p, e) { + size_t k; + + if (!env_assignment_is_valid(*p)) + return false; + + /* Check if there are duplicate assginments */ + k = strcspn(*p, "="); + STRV_FOREACH(q, p + 1) + if (strneq(*p, *q, k) && (*q)[k] == '=') + return false; + } + + return true; +} + +bool strv_env_name_is_valid(char **l) { + char **p, **q; + + STRV_FOREACH(p, l) { + if (!env_name_is_valid(*p)) + return false; + + STRV_FOREACH(q, p + 1) + if (streq(*p, *q)) + return false; + } + + return true; +} + +bool strv_env_name_or_assignment_is_valid(char **l) { + char **p, **q; + + STRV_FOREACH(p, l) { + if (!env_assignment_is_valid(*p) && !env_name_is_valid(*p)) + return false; + + STRV_FOREACH(q, p + 1) + if (streq(*p, *q)) + return false; + } + + return true; +} + +static int env_append(char **r, char ***k, char **a) { + assert(r); + assert(k); + + if (!a) + return 0; + + /* Add the entries of a to *k unless they already exist in *r + * in which case they are overridden instead. This assumes + * there is enough space in the r array. */ + + for (; *a; a++) { + char **j; + size_t n; + + n = strcspn(*a, "="); + + if ((*a)[n] == '=') + n++; + + for (j = r; j < *k; j++) + if (strneq(*j, *a, n)) + break; + + if (j >= *k) + (*k)++; + else + free(*j); + + *j = strdup(*a); + if (!*j) + return -ENOMEM; + } + + return 0; +} + +char **strv_env_merge(unsigned n_lists, ...) { + size_t n = 0; + char **l, **k, **r; + va_list ap; + unsigned i; + + /* Merges an arbitrary number of environment sets */ + + va_start(ap, n_lists); + for (i = 0; i < n_lists; i++) { + l = va_arg(ap, char**); + n += strv_length(l); + } + va_end(ap); + + r = new(char*, n+1); + if (!r) + return NULL; + + k = r; + + va_start(ap, n_lists); + for (i = 0; i < n_lists; i++) { + l = va_arg(ap, char**); + if (env_append(r, &k, l) < 0) + goto fail; + } + va_end(ap); + + *k = NULL; + + return r; + +fail: + va_end(ap); + strv_free(r); + + return NULL; +} + +_pure_ static bool env_match(const char *t, const char *pattern) { + assert(t); + assert(pattern); + + /* pattern a matches string a + * a matches a= + * a matches a=b + * a= matches a= + * a=b matches a=b + * a= does not match a + * a=b does not match a= + * a=b does not match a + * a=b does not match a=c */ + + if (streq(t, pattern)) + return true; + + if (!strchr(pattern, '=')) { + size_t l = strlen(pattern); + + return strneq(t, pattern, l) && t[l] == '='; + } + + return false; +} + +char **strv_env_delete(char **x, unsigned n_lists, ...) { + size_t n, i = 0; + char **k, **r; + va_list ap; + + /* Deletes every entry from x that is mentioned in the other + * string lists */ + + n = strv_length(x); + + r = new(char*, n+1); + if (!r) + return NULL; + + STRV_FOREACH(k, x) { + unsigned v; + + va_start(ap, n_lists); + for (v = 0; v < n_lists; v++) { + char **l, **j; + + l = va_arg(ap, char**); + STRV_FOREACH(j, l) + if (env_match(*k, *j)) + goto skip; + } + va_end(ap); + + r[i] = strdup(*k); + if (!r[i]) { + strv_free(r); + return NULL; + } + + i++; + continue; + + skip: + va_end(ap); + } + + r[i] = NULL; + + assert(i <= n); + + return r; +} + +char **strv_env_unset(char **l, const char *p) { + + char **f, **t; + + if (!l) + return NULL; + + assert(p); + + /* Drops every occurrence of the env var setting p in the + * string list. Edits in-place. */ + + for (f = t = l; *f; f++) { + + if (env_match(*f, p)) { + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return l; +} + +char **strv_env_unset_many(char **l, ...) { + + char **f, **t; + + if (!l) + return NULL; + + /* Like strv_env_unset() but applies many at once. Edits in-place. */ + + for (f = t = l; *f; f++) { + bool found = false; + const char *p; + va_list ap; + + va_start(ap, l); + + while ((p = va_arg(ap, const char*))) { + if (env_match(*f, p)) { + found = true; + break; + } + } + + va_end(ap); + + if (found) { + free(*f); + continue; + } + + *(t++) = *f; + } + + *t = NULL; + return l; +} + +char **strv_env_set(char **x, const char *p) { + + char **k, **r; + char* m[2] = { (char*) p, NULL }; + + /* Overrides the env var setting of p, returns a new copy */ + + r = new(char*, strv_length(x)+2); + if (!r) + return NULL; + + k = r; + if (env_append(r, &k, x) < 0) + goto fail; + + if (env_append(r, &k, m) < 0) + goto fail; + + *k = NULL; + + return r; + +fail: + strv_free(r); + return NULL; +} + +char *strv_env_get_n(char **l, const char *name, size_t k) { + char **i; + + assert(name); + + if (k <= 0) + return NULL; + + STRV_FOREACH(i, l) + if (strneq(*i, name, k) && + (*i)[k] == '=') + return *i + k + 1; + + return NULL; +} + +char *strv_env_get(char **l, const char *name) { + assert(name); + + return strv_env_get_n(l, name, strlen(name)); +} + +char **strv_env_clean_with_callback(char **e, void (*invalid_callback)(const char *p, void *userdata), void *userdata) { + char **p, **q; + int k = 0; + + STRV_FOREACH(p, e) { + size_t n; + bool duplicate = false; + + if (!env_assignment_is_valid(*p)) { + if (invalid_callback) + invalid_callback(*p, userdata); + free(*p); + continue; + } + + n = strcspn(*p, "="); + STRV_FOREACH(q, p + 1) + if (strneq(*p, *q, n) && (*q)[n] == '=') { + duplicate = true; + break; + } + + if (duplicate) { + free(*p); + continue; + } + + e[k++] = *p; + } + + if (e) + e[k] = NULL; + + return e; +} + +char *replace_env(const char *format, char **env) { + enum { + WORD, + CURLY, + VARIABLE + } state = WORD; + + const char *e, *word = format; + char *r = NULL, *k; + + assert(format); + + for (e = format; *e; e ++) { + + switch (state) { + + case WORD: + if (*e == '$') + state = CURLY; + break; + + case CURLY: + if (*e == '{') { + k = strnappend(r, word, e-word-1); + if (!k) + goto fail; + + free(r); + r = k; + + word = e-1; + state = VARIABLE; + + } else if (*e == '$') { + k = strnappend(r, word, e-word); + if (!k) + goto fail; + + free(r); + r = k; + + word = e+1; + state = WORD; + } else + state = WORD; + break; + + case VARIABLE: + if (*e == '}') { + const char *t; + + t = strempty(strv_env_get_n(env, word+2, e-word-2)); + + k = strappend(r, t); + if (!k) + goto fail; + + free(r); + r = k; + + word = e+1; + state = WORD; + } + break; + } + } + + k = strnappend(r, word, e-word); + if (!k) + goto fail; + + free(r); + return k; + +fail: + free(r); + return NULL; +} + +char **replace_env_argv(char **argv, char **env) { + char **ret, **i; + unsigned k = 0, l = 0; + + l = strv_length(argv); + + ret = new(char*, l+1); + if (!ret) + return NULL; + + STRV_FOREACH(i, argv) { + + /* If $FOO appears as single word, replace it by the split up variable */ + if ((*i)[0] == '$' && (*i)[1] != '{' && (*i)[1] != '$') { + char *e; + char **w, **m = NULL; + unsigned q; + + e = strv_env_get(env, *i+1); + if (e) { + int r; + + r = strv_split_extract(&m, e, WHITESPACE, EXTRACT_RELAX|EXTRACT_QUOTES); + if (r < 0) { + ret[k] = NULL; + strv_free(ret); + return NULL; + } + } else + m = NULL; + + q = strv_length(m); + l = l + q - 1; + + w = realloc(ret, sizeof(char*) * (l+1)); + if (!w) { + ret[k] = NULL; + strv_free(ret); + strv_free(m); + return NULL; + } + + ret = w; + if (m) { + memcpy(ret + k, m, q * sizeof(char*)); + free(m); + } + + k += q; + continue; + } + + /* If ${FOO} appears as part of a word, replace it by the variable as-is */ + ret[k] = replace_env(*i, env); + if (!ret[k]) { + strv_free(ret); + return NULL; + } + k++; + } + + ret[k] = NULL; + return ret; +} + +int getenv_bool(const char *p) { + const char *e; + + e = getenv(p); + if (!e) + return -ENXIO; + + return parse_boolean(e); +} diff --git a/src/libbasic/env-util.h b/src/libbasic/env-util.h new file mode 100644 index 0000000000..b1fef704c2 --- /dev/null +++ b/src/libbasic/env-util.h @@ -0,0 +1,51 @@ +#pragma once + +/*** + 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 . +***/ + +#include +#include + +#include "macro.h" + +bool env_name_is_valid(const char *e); +bool env_value_is_valid(const char *e); +bool env_assignment_is_valid(const char *e); + +char *replace_env(const char *format, char **env); +char **replace_env_argv(char **argv, char **env); + +bool strv_env_is_valid(char **e); +#define strv_env_clean(l) strv_env_clean_with_callback(l, NULL, NULL) +char **strv_env_clean_with_callback(char **l, void (*invalid_callback)(const char *p, void *userdata), void *userdata); + +bool strv_env_name_is_valid(char **l); +bool strv_env_name_or_assignment_is_valid(char **l); + +char **strv_env_merge(unsigned n_lists, ...); +char **strv_env_delete(char **x, unsigned n_lists, ...); /* New copy */ + +char **strv_env_set(char **x, const char *p); /* New copy ... */ +char **strv_env_unset(char **l, const char *p); /* In place ... */ +char **strv_env_unset_many(char **l, ...) _sentinel_; + +char *strv_env_get_n(char **l, const char *name, size_t k) _pure_; +char *strv_env_get(char **x, const char *n) _pure_; + +int getenv_bool(const char *p); diff --git a/src/libbasic/errno-list.c b/src/libbasic/errno-list.c new file mode 100644 index 0000000000..31b66bad5e --- /dev/null +++ b/src/libbasic/errno-list.c @@ -0,0 +1,57 @@ +/*** + 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 . +***/ + +#include + +#include "errno-list.h" +#include "macro.h" + +static const struct errno_name* lookup_errno(register const char *str, + register unsigned int len); + +#include "errno-from-name.h" +#include "errno-to-name.h" + +const char *errno_to_name(int id) { + + if (id < 0) + id = -id; + + if (id >= (int) ELEMENTSOF(errno_names)) + return NULL; + + return errno_names[id]; +} + +int errno_from_name(const char *name) { + const struct errno_name *sc; + + assert(name); + + sc = lookup_errno(name, strlen(name)); + if (!sc) + return -EINVAL; + + assert(sc->id > 0); + return sc->id; +} + +int errno_max(void) { + return ELEMENTSOF(errno_names); +} diff --git a/src/libbasic/errno-list.h b/src/libbasic/errno-list.h new file mode 100644 index 0000000000..4eec0cc786 --- /dev/null +++ b/src/libbasic/errno-list.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + 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 . +***/ + +const char *errno_to_name(int id); +int errno_from_name(const char *name); + +int errno_max(void); diff --git a/src/libbasic/escape.c b/src/libbasic/escape.c new file mode 100644 index 0000000000..2e483880c8 --- /dev/null +++ b/src/libbasic/escape.c @@ -0,0 +1,474 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#include "alloc-util.h" +#include "escape.h" +#include "hexdecoct.h" +#include "macro.h" +#include "utf8.h" + +size_t cescape_char(char c, char *buf) { + char * buf_old = buf; + + switch (c) { + + case '\a': + *(buf++) = '\\'; + *(buf++) = 'a'; + break; + case '\b': + *(buf++) = '\\'; + *(buf++) = 'b'; + break; + case '\f': + *(buf++) = '\\'; + *(buf++) = 'f'; + break; + case '\n': + *(buf++) = '\\'; + *(buf++) = 'n'; + break; + case '\r': + *(buf++) = '\\'; + *(buf++) = 'r'; + break; + case '\t': + *(buf++) = '\\'; + *(buf++) = 't'; + break; + case '\v': + *(buf++) = '\\'; + *(buf++) = 'v'; + break; + case '\\': + *(buf++) = '\\'; + *(buf++) = '\\'; + break; + case '"': + *(buf++) = '\\'; + *(buf++) = '"'; + break; + case '\'': + *(buf++) = '\\'; + *(buf++) = '\''; + break; + + default: + /* For special chars we prefer octal over + * hexadecimal encoding, simply because glib's + * g_strescape() does the same */ + if ((c < ' ') || (c >= 127)) { + *(buf++) = '\\'; + *(buf++) = octchar((unsigned char) c >> 6); + *(buf++) = octchar((unsigned char) c >> 3); + *(buf++) = octchar((unsigned char) c); + } else + *(buf++) = c; + break; + } + + return buf - buf_old; +} + +char *cescape_length(const char *s, size_t n) { + const char *f; + char *r, *t; + + assert(s || n == 0); + + /* Does C style string escaping. May be reversed with + * cunescape(). */ + + r = new(char, n*4 + 1); + if (!r) + return NULL; + + for (f = s, t = r; f < s + n; f++) + t += cescape_char(*f, t); + + *t = 0; + + return r; +} + +char *cescape(const char *s) { + assert(s); + + return cescape_length(s, strlen(s)); +} + +int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit) { + int r = 1; + + assert(p); + assert(*p); + assert(ret); + + /* Unescapes C style. Returns the unescaped character in ret. + * Sets *eight_bit to true if the escaped sequence either fits in + * one byte in UTF-8 or is a non-unicode literal byte and should + * instead be copied directly. + */ + + if (length != (size_t) -1 && length < 1) + return -EINVAL; + + switch (p[0]) { + + case 'a': + *ret = '\a'; + break; + case 'b': + *ret = '\b'; + break; + case 'f': + *ret = '\f'; + break; + case 'n': + *ret = '\n'; + break; + case 'r': + *ret = '\r'; + break; + case 't': + *ret = '\t'; + break; + case 'v': + *ret = '\v'; + break; + case '\\': + *ret = '\\'; + break; + case '"': + *ret = '"'; + break; + case '\'': + *ret = '\''; + break; + + case 's': + /* This is an extension of the XDG syntax files */ + *ret = ' '; + break; + + case 'x': { + /* hexadecimal encoding */ + int a, b; + + if (length != (size_t) -1 && length < 3) + return -EINVAL; + + a = unhexchar(p[1]); + if (a < 0) + return -EINVAL; + + b = unhexchar(p[2]); + if (b < 0) + return -EINVAL; + + /* Don't allow NUL bytes */ + if (a == 0 && b == 0) + return -EINVAL; + + *ret = (a << 4U) | b; + *eight_bit = true; + r = 3; + break; + } + + case 'u': { + /* C++11 style 16bit unicode */ + + int a[4]; + unsigned i; + uint32_t c; + + if (length != (size_t) -1 && length < 5) + return -EINVAL; + + for (i = 0; i < 4; i++) { + a[i] = unhexchar(p[1 + i]); + if (a[i] < 0) + return a[i]; + } + + c = ((uint32_t) a[0] << 12U) | ((uint32_t) a[1] << 8U) | ((uint32_t) a[2] << 4U) | (uint32_t) a[3]; + + /* Don't allow 0 chars */ + if (c == 0) + return -EINVAL; + + *ret = c; + r = 5; + break; + } + + case 'U': { + /* C++11 style 32bit unicode */ + + int a[8]; + unsigned i; + char32_t c; + + if (length != (size_t) -1 && length < 9) + return -EINVAL; + + for (i = 0; i < 8; i++) { + a[i] = unhexchar(p[1 + i]); + if (a[i] < 0) + return a[i]; + } + + c = ((uint32_t) a[0] << 28U) | ((uint32_t) a[1] << 24U) | ((uint32_t) a[2] << 20U) | ((uint32_t) a[3] << 16U) | + ((uint32_t) a[4] << 12U) | ((uint32_t) a[5] << 8U) | ((uint32_t) a[6] << 4U) | (uint32_t) a[7]; + + /* Don't allow 0 chars */ + if (c == 0) + return -EINVAL; + + /* Don't allow invalid code points */ + if (!unichar_is_valid(c)) + return -EINVAL; + + *ret = c; + r = 9; + break; + } + + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': { + /* octal encoding */ + int a, b, c; + char32_t m; + + if (length != (size_t) -1 && length < 3) + return -EINVAL; + + a = unoctchar(p[0]); + if (a < 0) + return -EINVAL; + + b = unoctchar(p[1]); + if (b < 0) + return -EINVAL; + + c = unoctchar(p[2]); + if (c < 0) + return -EINVAL; + + /* don't allow NUL bytes */ + if (a == 0 && b == 0 && c == 0) + return -EINVAL; + + /* Don't allow bytes above 255 */ + m = ((uint32_t) a << 6U) | ((uint32_t) b << 3U) | (uint32_t) c; + if (m > 255) + return -EINVAL; + + *ret = m; + *eight_bit = true; + r = 3; + break; + } + + default: + return -EINVAL; + } + + return r; +} + +int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret) { + char *r, *t; + const char *f; + size_t pl; + + assert(s); + assert(ret); + + /* Undoes C style string escaping, and optionally prefixes it. */ + + pl = prefix ? strlen(prefix) : 0; + + r = new(char, pl+length+1); + if (!r) + return -ENOMEM; + + if (prefix) + memcpy(r, prefix, pl); + + for (f = s, t = r + pl; f < s + length; f++) { + size_t remaining; + bool eight_bit = false; + char32_t u; + int k; + + remaining = s + length - f; + assert(remaining > 0); + + if (*f != '\\') { + /* A literal literal, copy verbatim */ + *(t++) = *f; + continue; + } + + if (remaining == 1) { + if (flags & UNESCAPE_RELAX) { + /* A trailing backslash, copy verbatim */ + *(t++) = *f; + continue; + } + + free(r); + return -EINVAL; + } + + k = cunescape_one(f + 1, remaining - 1, &u, &eight_bit); + if (k < 0) { + if (flags & UNESCAPE_RELAX) { + /* Invalid escape code, let's take it literal then */ + *(t++) = '\\'; + continue; + } + + free(r); + return k; + } + + f += k; + if (eight_bit) + /* One byte? Set directly as specified */ + *(t++) = u; + else + /* Otherwise encode as multi-byte UTF-8 */ + t += utf8_encode_unichar(t, u); + } + + *t = 0; + + *ret = r; + return t - r; +} + +int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret) { + return cunescape_length_with_prefix(s, length, NULL, flags, ret); +} + +int cunescape(const char *s, UnescapeFlags flags, char **ret) { + return cunescape_length(s, strlen(s), flags, ret); +} + +char *xescape(const char *s, const char *bad) { + char *r, *t; + const char *f; + + /* Escapes all chars in bad, in addition to \ and all special + * chars, in \xFF style escaping. May be reversed with + * cunescape(). */ + + r = new(char, strlen(s) * 4 + 1); + if (!r) + return NULL; + + for (f = s, t = r; *f; f++) { + + if ((*f < ' ') || (*f >= 127) || + (*f == '\\') || strchr(bad, *f)) { + *(t++) = '\\'; + *(t++) = 'x'; + *(t++) = hexchar(*f >> 4); + *(t++) = hexchar(*f); + } else + *(t++) = *f; + } + + *t = 0; + + return r; +} + +static char *strcpy_backslash_escaped(char *t, const char *s, const char *bad) { + assert(bad); + + for (; *s; s++) { + if (*s == '\\' || strchr(bad, *s)) + *(t++) = '\\'; + + *(t++) = *s; + } + + return t; +} + +char *shell_escape(const char *s, const char *bad) { + char *r, *t; + + r = new(char, strlen(s)*2+1); + if (!r) + return NULL; + + t = strcpy_backslash_escaped(r, s, bad); + *t = 0; + + return r; +} + +char *shell_maybe_quote(const char *s) { + const char *p; + char *r, *t; + + assert(s); + + /* Encloses a string in double quotes if necessary to make it + * OK as shell string. */ + + for (p = s; *p; p++) + if (*p <= ' ' || + *p >= 127 || + strchr(SHELL_NEED_QUOTES, *p)) + break; + + if (!*p) + return strdup(s); + + r = new(char, 1+strlen(s)*2+1+1); + if (!r) + return NULL; + + t = r; + *(t++) = '"'; + t = mempcpy(t, s, p - s); + + t = strcpy_backslash_escaped(t, p, SHELL_NEED_ESCAPE); + + *(t++)= '"'; + *t = 0; + + return r; +} diff --git a/src/libbasic/escape.h b/src/libbasic/escape.h new file mode 100644 index 0000000000..1b28bd10af --- /dev/null +++ b/src/libbasic/escape.h @@ -0,0 +1,53 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "string-util.h" +#include "missing.h" + +/* What characters are special in the shell? */ +/* must be escaped outside and inside double-quotes */ +#define SHELL_NEED_ESCAPE "\"\\`$" +/* can be escaped or double-quoted */ +#define SHELL_NEED_QUOTES SHELL_NEED_ESCAPE GLOB_CHARS "'()<>|&;" + +typedef enum UnescapeFlags { + UNESCAPE_RELAX = 1, +} UnescapeFlags; + +char *cescape(const char *s); +char *cescape_length(const char *s, size_t n); +size_t cescape_char(char c, char *buf); + +int cunescape(const char *s, UnescapeFlags flags, char **ret); +int cunescape_length(const char *s, size_t length, UnescapeFlags flags, char **ret); +int cunescape_length_with_prefix(const char *s, size_t length, const char *prefix, UnescapeFlags flags, char **ret); +int cunescape_one(const char *p, size_t length, char32_t *ret, bool *eight_bit); + +char *xescape(const char *s, const char *bad); + +char *shell_escape(const char *s, const char *bad); +char *shell_maybe_quote(const char *s); diff --git a/src/libbasic/ether-addr-util.c b/src/libbasic/ether-addr-util.c new file mode 100644 index 0000000000..ded6d31f4b --- /dev/null +++ b/src/libbasic/ether-addr-util.c @@ -0,0 +1,44 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include +#include +#include + +#include "ether-addr-util.h" +#include "macro.h" + +char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]) { + assert(addr); + assert(buffer); + + /* Like ether_ntoa() but uses %02x instead of %x to print + * ethernet addresses, which makes them look less funny. Also, + * doesn't use a static buffer. */ + + sprintf(buffer, "%02x:%02x:%02x:%02x:%02x:%02x", + addr->ether_addr_octet[0], + addr->ether_addr_octet[1], + addr->ether_addr_octet[2], + addr->ether_addr_octet[3], + addr->ether_addr_octet[4], + addr->ether_addr_octet[5]); + + return buffer; +} diff --git a/src/libbasic/ether-addr-util.h b/src/libbasic/ether-addr-util.h new file mode 100644 index 0000000000..4487149efd --- /dev/null +++ b/src/libbasic/ether-addr-util.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include + +#define ETHER_ADDR_FORMAT_STR "%02X%02X%02X%02X%02X%02X" +#define ETHER_ADDR_FORMAT_VAL(x) (x).ether_addr_octet[0], (x).ether_addr_octet[1], (x).ether_addr_octet[2], (x).ether_addr_octet[3], (x).ether_addr_octet[4], (x).ether_addr_octet[5] + +#define ETHER_ADDR_TO_STRING_MAX (3*6) + +char* ether_addr_to_string(const struct ether_addr *addr, char buffer[ETHER_ADDR_TO_STRING_MAX]); diff --git a/src/libbasic/exit-status.c b/src/libbasic/exit-status.c new file mode 100644 index 0000000000..5e0bc415c8 --- /dev/null +++ b/src/libbasic/exit-status.c @@ -0,0 +1,243 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "exit-status.h" +#include "macro.h" +#include "set.h" + +const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { + + /* We cast to int here, so that -Wenum doesn't complain that + * EXIT_SUCCESS/EXIT_FAILURE aren't in the enum */ + + switch ((int) status) { + + case EXIT_SUCCESS: + return "SUCCESS"; + + case EXIT_FAILURE: + return "FAILURE"; + } + + + if (level == EXIT_STATUS_SYSTEMD || level == EXIT_STATUS_LSB) { + switch ((int) status) { + + case EXIT_CHDIR: + return "CHDIR"; + + case EXIT_NICE: + return "NICE"; + + case EXIT_FDS: + return "FDS"; + + case EXIT_EXEC: + return "EXEC"; + + case EXIT_MEMORY: + return "MEMORY"; + + case EXIT_LIMITS: + return "LIMITS"; + + case EXIT_OOM_ADJUST: + return "OOM_ADJUST"; + + case EXIT_SIGNAL_MASK: + return "SIGNAL_MASK"; + + case EXIT_STDIN: + return "STDIN"; + + case EXIT_STDOUT: + return "STDOUT"; + + case EXIT_CHROOT: + return "CHROOT"; + + case EXIT_IOPRIO: + return "IOPRIO"; + + case EXIT_TIMERSLACK: + return "TIMERSLACK"; + + case EXIT_SECUREBITS: + return "SECUREBITS"; + + case EXIT_SETSCHEDULER: + return "SETSCHEDULER"; + + case EXIT_CPUAFFINITY: + return "CPUAFFINITY"; + + case EXIT_GROUP: + return "GROUP"; + + case EXIT_USER: + return "USER"; + + case EXIT_CAPABILITIES: + return "CAPABILITIES"; + + case EXIT_CGROUP: + return "CGROUP"; + + case EXIT_SETSID: + return "SETSID"; + + case EXIT_CONFIRM: + return "CONFIRM"; + + case EXIT_STDERR: + return "STDERR"; + + case EXIT_PAM: + return "PAM"; + + case EXIT_NETWORK: + return "NETWORK"; + + case EXIT_NAMESPACE: + return "NAMESPACE"; + + case EXIT_NO_NEW_PRIVILEGES: + return "NO_NEW_PRIVILEGES"; + + case EXIT_SECCOMP: + return "SECCOMP"; + + case EXIT_SELINUX_CONTEXT: + return "SELINUX_CONTEXT"; + + case EXIT_PERSONALITY: + return "PERSONALITY"; + + case EXIT_APPARMOR_PROFILE: + return "APPARMOR"; + + case EXIT_ADDRESS_FAMILIES: + return "ADDRESS_FAMILIES"; + + case EXIT_RUNTIME_DIRECTORY: + return "RUNTIME_DIRECTORY"; + + case EXIT_CHOWN: + return "CHOWN"; + + case EXIT_MAKE_STARTER: + return "MAKE_STARTER"; + + case EXIT_BUS_ENDPOINT: + return "BUS_ENDPOINT"; + + case EXIT_SMACK_PROCESS_LABEL: + return "SMACK_PROCESS_LABEL"; + } + } + + if (level == EXIT_STATUS_LSB) { + switch ((int) status) { + + case EXIT_INVALIDARGUMENT: + return "INVALIDARGUMENT"; + + case EXIT_NOTIMPLEMENTED: + return "NOTIMPLEMENTED"; + + case EXIT_NOPERMISSION: + return "NOPERMISSION"; + + case EXIT_NOTINSTALLED: + return "NOTINSTALLED"; + + case EXIT_NOTCONFIGURED: + return "NOTCONFIGURED"; + + case EXIT_NOTRUNNING: + return "NOTRUNNING"; + } + } + + return NULL; +} + + +bool is_clean_exit(int code, int status, ExitStatusSet *success_status) { + + if (code == CLD_EXITED) + return status == 0 || + (success_status && + set_contains(success_status->status, INT_TO_PTR(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 || + (success_status && + set_contains(success_status->signal, INT_TO_PTR(status))); + + return false; +} + +bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status) { + + if (is_clean_exit(code, status, success_status)) + return true; + + return + code == CLD_EXITED && + (status == EXIT_NOTINSTALLED || status == EXIT_NOTCONFIGURED); +} + +void exit_status_set_free(ExitStatusSet *x) { + assert(x); + + set_free(x->status); + set_free(x->signal); + x->status = x->signal = NULL; +} + +bool exit_status_set_is_empty(ExitStatusSet *x) { + if (!x) + return true; + + return set_isempty(x->status) && set_isempty(x->signal); +} + +bool exit_status_set_test(ExitStatusSet *x, int code, int status) { + + if (exit_status_set_is_empty(x)) + return false; + + if (code == CLD_EXITED && set_contains(x->status, INT_TO_PTR(status))) + return true; + + if (IN_SET(code, CLD_KILLED, CLD_DUMPED) && set_contains(x->signal, INT_TO_PTR(status))) + return true; + + return false; +} diff --git a/src/libbasic/exit-status.h b/src/libbasic/exit-status.h new file mode 100644 index 0000000000..79525d30ee --- /dev/null +++ b/src/libbasic/exit-status.h @@ -0,0 +1,103 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "hashmap.h" +#include "macro.h" +#include "set.h" + +typedef enum ExitStatus { + /* EXIT_SUCCESS defined by libc */ + /* EXIT_FAILURE defined by libc */ + EXIT_INVALIDARGUMENT = 2, + EXIT_NOTIMPLEMENTED = 3, + EXIT_NOPERMISSION = 4, + EXIT_NOTINSTALLED = 5, + EXIT_NOTCONFIGURED = 6, + EXIT_NOTRUNNING = 7, + + /* 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 */ + + EXIT_CHDIR = 200, + EXIT_NICE, + EXIT_FDS, + EXIT_EXEC, + EXIT_MEMORY, + EXIT_LIMITS, + EXIT_OOM_ADJUST, + EXIT_SIGNAL_MASK, + EXIT_STDIN, + EXIT_STDOUT, + EXIT_CHROOT, /* 210 */ + EXIT_IOPRIO, + EXIT_TIMERSLACK, + EXIT_SECUREBITS, + EXIT_SETSCHEDULER, + EXIT_CPUAFFINITY, + EXIT_GROUP, + EXIT_USER, + EXIT_CAPABILITIES, + EXIT_CGROUP, + EXIT_SETSID, /* 220 */ + EXIT_CONFIRM, + EXIT_STDERR, + _EXIT_RESERVED, /* used to be tcpwrap, don't reuse! */ + EXIT_PAM, + EXIT_NETWORK, + EXIT_NAMESPACE, + EXIT_NO_NEW_PRIVILEGES, + EXIT_SECCOMP, + EXIT_SELINUX_CONTEXT, + EXIT_PERSONALITY, /* 230 */ + EXIT_APPARMOR_PROFILE, + EXIT_ADDRESS_FAMILIES, + EXIT_RUNTIME_DIRECTORY, + EXIT_MAKE_STARTER, + EXIT_CHOWN, + EXIT_BUS_ENDPOINT, + EXIT_SMACK_PROCESS_LABEL, +} ExitStatus; + +typedef enum ExitStatusLevel { + EXIT_STATUS_MINIMAL, + EXIT_STATUS_SYSTEMD, + EXIT_STATUS_LSB, + EXIT_STATUS_FULL = EXIT_STATUS_LSB +} ExitStatusLevel; + +typedef struct ExitStatusSet { + Set *status; + Set *signal; +} ExitStatusSet; + +const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) _const_; + +bool is_clean_exit(int code, int status, ExitStatusSet *success_status); +bool is_clean_exit_lsb(int code, int status, ExitStatusSet *success_status); + +void exit_status_set_free(ExitStatusSet *x); +bool exit_status_set_is_empty(ExitStatusSet *x); +bool exit_status_set_test(ExitStatusSet *x, int code, int status); diff --git a/src/libbasic/extract-word.c b/src/libbasic/extract-word.c new file mode 100644 index 0000000000..ee35d2a0ec --- /dev/null +++ b/src/libbasic/extract-word.c @@ -0,0 +1,298 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "escape.h" +#include "extract-word.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "utf8.h" + +int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags) { + _cleanup_free_ char *s = NULL; + size_t allocated = 0, sz = 0; + char c; + int r; + + char quote = 0; /* 0 or ' or " */ + bool backslash = false; /* whether we've just seen a backslash */ + + assert(p); + assert(ret); + + /* Bail early if called after last value or with no input */ + if (!*p) + goto finish_force_terminate; + c = **p; + + if (!separators) + separators = WHITESPACE; + + /* Parses the first word of a string, and returns it in + * *ret. Removes all quotes in the process. When parsing fails + * (because of an uneven number of quotes or similar), leaves + * the pointer *p at the first invalid character. */ + + if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) + if (!GREEDY_REALLOC(s, allocated, sz+1)) + return -ENOMEM; + + for (;; (*p) ++, c = **p) { + if (c == 0) + goto finish_force_terminate; + else if (strchr(separators, c)) { + if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { + (*p) ++; + goto finish_force_next; + } + } else { + /* We found a non-blank character, so we will always + * want to return a string (even if it is empty), + * allocate it here. */ + if (!GREEDY_REALLOC(s, allocated, sz+1)) + return -ENOMEM; + break; + } + } + + for (;; (*p) ++, c = **p) { + if (backslash) { + if (!GREEDY_REALLOC(s, allocated, sz+7)) + return -ENOMEM; + + if (c == 0) { + if ((flags & EXTRACT_CUNESCAPE_RELAX) && + (!quote || flags & EXTRACT_RELAX)) { + /* If we find an unquoted trailing backslash and we're in + * EXTRACT_CUNESCAPE_RELAX mode, keep it verbatim in the + * output. + * + * Unbalanced quotes will only be allowed in EXTRACT_RELAX + * mode, EXTRACT_CUNESCAPE_RELAX mode does not allow them. + */ + s[sz++] = '\\'; + goto finish_force_terminate; + } + if (flags & EXTRACT_RELAX) + goto finish_force_terminate; + return -EINVAL; + } + + if (flags & EXTRACT_CUNESCAPE) { + bool eight_bit = false; + char32_t u; + + r = cunescape_one(*p, (size_t) -1, &u, &eight_bit); + if (r < 0) { + if (flags & EXTRACT_CUNESCAPE_RELAX) { + s[sz++] = '\\'; + s[sz++] = c; + } else + return -EINVAL; + } else { + (*p) += r - 1; + + if (eight_bit) + s[sz++] = u; + else + sz += utf8_encode_unichar(s + sz, u); + } + } else + s[sz++] = c; + + backslash = false; + + } else if (quote) { /* inside either single or double quotes */ + for (;; (*p) ++, c = **p) { + if (c == 0) { + if (flags & EXTRACT_RELAX) + goto finish_force_terminate; + return -EINVAL; + } else if (c == quote) { /* found the end quote */ + quote = 0; + break; + } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { + backslash = true; + break; + } else { + if (!GREEDY_REALLOC(s, allocated, sz+2)) + return -ENOMEM; + + s[sz++] = c; + } + } + + } else { + for (;; (*p) ++, c = **p) { + if (c == 0) + goto finish_force_terminate; + else if ((c == '\'' || c == '"') && (flags & EXTRACT_QUOTES)) { + quote = c; + break; + } else if (c == '\\' && !(flags & EXTRACT_RETAIN_ESCAPE)) { + backslash = true; + break; + } else if (strchr(separators, c)) { + if (flags & EXTRACT_DONT_COALESCE_SEPARATORS) { + (*p) ++; + goto finish_force_next; + } + /* Skip additional coalesced separators. */ + for (;; (*p) ++, c = **p) { + if (c == 0) + goto finish_force_terminate; + if (!strchr(separators, c)) + break; + } + goto finish; + + } else { + if (!GREEDY_REALLOC(s, allocated, sz+2)) + return -ENOMEM; + + s[sz++] = c; + } + } + } + } + +finish_force_terminate: + *p = NULL; +finish: + if (!s) { + *p = NULL; + *ret = NULL; + return 0; + } + +finish_force_next: + s[sz] = 0; + *ret = s; + s = NULL; + + return 1; +} + +int extract_first_word_and_warn( + const char **p, + char **ret, + const char *separators, + ExtractFlags flags, + const char *unit, + const char *filename, + unsigned line, + const char *rvalue) { + + /* Try to unquote it, if it fails, warn about it and try again + * but this time using EXTRACT_CUNESCAPE_RELAX to keep the + * backslashes verbatim in invalid escape sequences. */ + + const char *save; + int r; + + save = *p; + r = extract_first_word(p, ret, separators, flags); + if (r >= 0) + return r; + + if (r == -EINVAL && !(flags & EXTRACT_CUNESCAPE_RELAX)) { + + /* Retry it with EXTRACT_CUNESCAPE_RELAX. */ + *p = save; + r = extract_first_word(p, ret, separators, flags|EXTRACT_CUNESCAPE_RELAX); + if (r >= 0) { + /* It worked this time, hence it must have been an invalid escape sequence we could correct. */ + log_syntax(unit, LOG_WARNING, filename, line, EINVAL, "Invalid escape sequences in line, correcting: \"%s\"", rvalue); + return r; + } + + /* If it's still EINVAL; then it must be unbalanced quoting, report this. */ + if (r == -EINVAL) + return log_syntax(unit, LOG_ERR, filename, line, r, "Unbalanced quoting, ignoring: \"%s\"", rvalue); + } + + /* Can be any error, report it */ + return log_syntax(unit, LOG_ERR, filename, line, r, "Unable to decode word \"%s\", ignoring: %m", rvalue); +} + +int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) { + va_list ap; + char **l; + int n = 0, i, c, r; + + /* Parses a number of words from a string, stripping any + * quotes if necessary. */ + + assert(p); + + /* Count how many words are expected */ + va_start(ap, flags); + for (;;) { + if (!va_arg(ap, char **)) + break; + n++; + } + va_end(ap); + + if (n <= 0) + return 0; + + /* Read all words into a temporary array */ + l = newa0(char*, n); + for (c = 0; c < n; c++) { + + r = extract_first_word(p, &l[c], separators, flags); + if (r < 0) { + int j; + + for (j = 0; j < c; j++) + free(l[j]); + + return r; + } + + if (r == 0) + break; + } + + /* If we managed to parse all words, return them in the passed + * in parameters */ + va_start(ap, flags); + for (i = 0; i < n; i++) { + char **v; + + v = va_arg(ap, char **); + assert(v); + + *v = l[i]; + } + va_end(ap); + + return c; +} diff --git a/src/libbasic/extract-word.h b/src/libbasic/extract-word.h new file mode 100644 index 0000000000..21db5ef33f --- /dev/null +++ b/src/libbasic/extract-word.h @@ -0,0 +1,35 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "macro.h" + +typedef enum ExtractFlags { + EXTRACT_RELAX = 1, + EXTRACT_CUNESCAPE = 2, + EXTRACT_CUNESCAPE_RELAX = 4, + EXTRACT_QUOTES = 8, + EXTRACT_DONT_COALESCE_SEPARATORS = 16, + EXTRACT_RETAIN_ESCAPE = 32, +} ExtractFlags; + +int extract_first_word(const char **p, char **ret, const char *separators, ExtractFlags flags); +int extract_first_word_and_warn(const char **p, char **ret, const char *separators, ExtractFlags flags, const char *unit, const char *filename, unsigned line, const char *rvalue); +int extract_many_words(const char **p, const char *separators, ExtractFlags flags, ...) _sentinel_; diff --git a/src/libbasic/fd-util.c b/src/libbasic/fd-util.c new file mode 100644 index 0000000000..ec9560cd07 --- /dev/null +++ b/src/libbasic/fd-util.c @@ -0,0 +1,358 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "fd-util.h" +#include "macro.h" +#include "missing.h" +#include "parse-util.h" +#include "path-util.h" +#include "socket-util.h" +#include "util.h" + +int close_nointr(int fd) { + assert(fd >= 0); + + if (close(fd) >= 0) + return 0; + + /* + * Just ignore EINTR; a retry loop is the wrong thing to do on + * Linux. + * + * http://lkml.indiana.edu/hypermail/linux/kernel/0509.1/0877.html + * https://bugzilla.gnome.org/show_bug.cgi?id=682819 + * http://utcc.utoronto.ca/~cks/space/blog/unix/CloseEINTR + * https://sites.google.com/site/michaelsafyan/software-engineering/checkforeintrwheninvokingclosethinkagain + */ + if (errno == EINTR) + return 0; + + return -errno; +} + +int safe_close(int fd) { + + /* + * Like close_nointr() but cannot fail. Guarantees errno is + * unchanged. Is a NOP with negative fds passed, and returns + * -1, so that it can be used in this syntax: + * + * fd = safe_close(fd); + */ + + if (fd >= 0) { + PROTECT_ERRNO; + + /* The kernel might return pretty much any error code + * via close(), but the fd will be closed anyway. The + * only condition we want to check for here is whether + * the fd was invalid at all... */ + + assert_se(close_nointr(fd) != -EBADF); + } + + return -1; +} + +void safe_close_pair(int p[]) { + assert(p); + + if (p[0] == p[1]) { + /* Special case pairs which use the same fd in both + * directions... */ + p[0] = p[1] = safe_close(p[0]); + return; + } + + p[0] = safe_close(p[0]); + p[1] = safe_close(p[1]); +} + +void close_many(const int fds[], unsigned n_fd) { + unsigned i; + + assert(fds || n_fd <= 0); + + for (i = 0; i < n_fd; i++) + safe_close(fds[i]); +} + +int fclose_nointr(FILE *f) { + assert(f); + + /* Same as close_nointr(), but for fclose() */ + + if (fclose(f) == 0) + return 0; + + if (errno == EINTR) + return 0; + + return -errno; +} + +FILE* safe_fclose(FILE *f) { + + /* Same as safe_close(), but for fclose() */ + + if (f) { + PROTECT_ERRNO; + + assert_se(fclose_nointr(f) != EBADF); + } + + return NULL; +} + +DIR* safe_closedir(DIR *d) { + + if (d) { + PROTECT_ERRNO; + + assert_se(closedir(d) >= 0 || errno != EBADF); + } + + return NULL; +} + +int fd_nonblock(int fd, bool nonblock) { + int flags, nflags; + + assert(fd >= 0); + + flags = fcntl(fd, F_GETFL, 0); + if (flags < 0) + return -errno; + + if (nonblock) + nflags = flags | O_NONBLOCK; + else + nflags = flags & ~O_NONBLOCK; + + if (nflags == flags) + return 0; + + if (fcntl(fd, F_SETFL, nflags) < 0) + return -errno; + + return 0; +} + +int fd_cloexec(int fd, bool cloexec) { + int flags, nflags; + + assert(fd >= 0); + + flags = fcntl(fd, F_GETFD, 0); + if (flags < 0) + return -errno; + + if (cloexec) + nflags = flags | FD_CLOEXEC; + else + nflags = flags & ~FD_CLOEXEC; + + if (nflags == flags) + return 0; + + if (fcntl(fd, F_SETFD, nflags) < 0) + return -errno; + + return 0; +} + +_pure_ static bool fd_in_set(int fd, const int fdset[], unsigned n_fdset) { + unsigned i; + + assert(n_fdset == 0 || fdset); + + for (i = 0; i < n_fdset; i++) + if (fdset[i] == fd) + return true; + + return false; +} + +int close_all_fds(const int except[], unsigned n_except) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0; + + assert(n_except == 0 || except); + + d = opendir("/proc/self/fd"); + if (!d) { + int fd; + struct rlimit rl; + + /* When /proc isn't available (for example in chroots) + * the fallback is brute forcing through the fd + * table */ + + assert_se(getrlimit(RLIMIT_NOFILE, &rl) >= 0); + for (fd = 3; fd < (int) rl.rlim_max; fd ++) { + + if (fd_in_set(fd, except, n_except)) + continue; + + if (close_nointr(fd) < 0) + if (errno != EBADF && r == 0) + r = -errno; + } + + return r; + } + + while ((de = readdir(d))) { + int fd = -1; + + if (hidden_file(de->d_name)) + continue; + + if (safe_atoi(de->d_name, &fd) < 0) + /* Let's better ignore this, just in case */ + continue; + + if (fd < 3) + continue; + + if (fd == dirfd(d)) + continue; + + if (fd_in_set(fd, except, n_except)) + continue; + + if (close_nointr(fd) < 0) { + /* Valgrind has its own FD and doesn't want to have it closed */ + if (errno != EBADF && r == 0) + r = -errno; + } + } + + return r; +} + +int same_fd(int a, int b) { + struct stat sta, stb; + pid_t pid; + int r, fa, fb; + + assert(a >= 0); + assert(b >= 0); + + /* Compares two file descriptors. Note that semantics are + * quite different depending on whether we have kcmp() or we + * don't. If we have kcmp() this will only return true for + * dup()ed file descriptors, but not otherwise. If we don't + * have kcmp() this will also return true for two fds of the same + * file, created by separate open() calls. Since we use this + * call mostly for filtering out duplicates in the fd store + * this difference hopefully doesn't matter too much. */ + + if (a == b) + return true; + + /* Try to use kcmp() if we have it. */ + pid = getpid(); + r = kcmp(pid, pid, KCMP_FILE, a, b); + if (r == 0) + return true; + if (r > 0) + return false; + if (errno != ENOSYS) + return -errno; + + /* We don't have kcmp(), use fstat() instead. */ + if (fstat(a, &sta) < 0) + return -errno; + + if (fstat(b, &stb) < 0) + return -errno; + + if ((sta.st_mode & S_IFMT) != (stb.st_mode & S_IFMT)) + return false; + + /* We consider all device fds different, since two device fds + * might refer to quite different device contexts even though + * they share the same inode and backing dev_t. */ + + if (S_ISCHR(sta.st_mode) || S_ISBLK(sta.st_mode)) + return false; + + if (sta.st_dev != stb.st_dev || sta.st_ino != stb.st_ino) + return false; + + /* The fds refer to the same inode on disk, let's also check + * if they have the same fd flags. This is useful to + * distinguish the read and write side of a pipe created with + * pipe(). */ + fa = fcntl(a, F_GETFL); + if (fa < 0) + return -errno; + + fb = fcntl(b, F_GETFL); + if (fb < 0) + return -errno; + + return fa == fb; +} + +void cmsg_close_all(struct msghdr *mh) { + struct cmsghdr *cmsg; + + assert(mh); + + CMSG_FOREACH(cmsg, mh) + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) + close_many((int*) CMSG_DATA(cmsg), (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int)); +} + +bool fdname_is_valid(const char *s) { + const char *p; + + /* Validates a name for $LISTEN_FDNAMES. We basically allow + * everything ASCII that's not a control character. Also, as + * special exception the ":" character is not allowed, as we + * use that as field separator in $LISTEN_FDNAMES. + * + * Note that the empty string is explicitly allowed + * here. However, we limit the length of the names to 255 + * characters. */ + + if (!s) + return false; + + for (p = s; *p; p++) { + if (*p < ' ') + return false; + if (*p >= 127) + return false; + if (*p == ':') + return false; + } + + return p - s < 256; +} diff --git a/src/libbasic/fd-util.h b/src/libbasic/fd-util.h new file mode 100644 index 0000000000..44528c6e35 --- /dev/null +++ b/src/libbasic/fd-util.h @@ -0,0 +1,77 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "macro.h" + +/* Make sure we can distinguish fd 0 and NULL */ +#define FD_TO_PTR(fd) INT_TO_PTR((fd)+1) +#define PTR_TO_FD(p) (PTR_TO_INT(p)-1) + +int close_nointr(int fd); +int safe_close(int fd); +void safe_close_pair(int p[]); + +void close_many(const int fds[], unsigned n_fd); + +int fclose_nointr(FILE *f); +FILE* safe_fclose(FILE *f); +DIR* safe_closedir(DIR *f); + +static inline void closep(int *fd) { + safe_close(*fd); +} + +static inline void close_pairp(int (*p)[2]) { + safe_close_pair(*p); +} + +static inline void fclosep(FILE **f) { + safe_fclose(*f); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, pclose); +DEFINE_TRIVIAL_CLEANUP_FUNC(DIR*, closedir); + +#define _cleanup_close_ _cleanup_(closep) +#define _cleanup_fclose_ _cleanup_(fclosep) +#define _cleanup_pclose_ _cleanup_(pclosep) +#define _cleanup_closedir_ _cleanup_(closedirp) +#define _cleanup_close_pair_ _cleanup_(close_pairp) + +int fd_nonblock(int fd, bool nonblock); +int fd_cloexec(int fd, bool cloexec); + +int close_all_fds(const int except[], unsigned n_except); + +int same_fd(int a, int b); + +void cmsg_close_all(struct msghdr *mh); + +bool fdname_is_valid(const char *s); + +/* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */ +#define ERRNO_IS_DISCONNECT(r) \ + IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH) diff --git a/src/libbasic/fdset.c b/src/libbasic/fdset.c new file mode 100644 index 0000000000..da1162991f --- /dev/null +++ b/src/libbasic/fdset.c @@ -0,0 +1,286 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include + +#include "fd-util.h" +#include "fdset.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "set.h" + +#define MAKE_SET(s) ((Set*) s) +#define MAKE_FDSET(s) ((FDSet*) s) + +FDSet *fdset_new(void) { + return MAKE_FDSET(set_new(NULL)); +} + +int fdset_new_array(FDSet **ret, const int *fds, unsigned n_fds) { + unsigned i; + FDSet *s; + int r; + + assert(ret); + + s = fdset_new(); + if (!s) + return -ENOMEM; + + for (i = 0; i < n_fds; i++) { + + r = fdset_put(s, fds[i]); + if (r < 0) { + set_free(MAKE_SET(s)); + return r; + } + } + + *ret = s; + return 0; +} + +FDSet* fdset_free(FDSet *s) { + void *p; + + while ((p = set_steal_first(MAKE_SET(s)))) { + /* Valgrind's fd might have ended up in this set here, + * due to fdset_new_fill(). We'll ignore all failures + * here, so that the EBADFD that valgrind will return + * us on close() doesn't influence us */ + + /* When reloading duplicates of the private bus + * connection fds and suchlike are closed here, which + * has no effect at all, since they are only + * duplicates. So don't be surprised about these log + * messages. */ + + log_debug("Closing left-over fd %i", PTR_TO_FD(p)); + close_nointr(PTR_TO_FD(p)); + } + + set_free(MAKE_SET(s)); + return NULL; +} + +int fdset_put(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return set_put(MAKE_SET(s), FD_TO_PTR(fd)); +} + +int fdset_consume(FDSet *s, int fd) { + int r; + + assert(s); + assert(fd >= 0); + + r = fdset_put(s, fd); + if (r <= 0) + safe_close(fd); + + return r; +} + +int fdset_put_dup(FDSet *s, int fd) { + int copy, r; + + assert(s); + assert(fd >= 0); + + copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (copy < 0) + return -errno; + + r = fdset_put(s, copy); + if (r < 0) { + safe_close(copy); + return r; + } + + return copy; +} + +bool fdset_contains(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return !!set_get(MAKE_SET(s), FD_TO_PTR(fd)); +} + +int fdset_remove(FDSet *s, int fd) { + assert(s); + assert(fd >= 0); + + return set_remove(MAKE_SET(s), FD_TO_PTR(fd)) ? fd : -ENOENT; +} + +int fdset_new_fill(FDSet **_s) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0; + FDSet *s; + + assert(_s); + + /* Creates an fdset and fills in all currently open file + * descriptors. */ + + d = opendir("/proc/self/fd"); + if (!d) + return -errno; + + s = fdset_new(); + if (!s) { + r = -ENOMEM; + goto finish; + } + + while ((de = readdir(d))) { + int fd = -1; + + if (hidden_file(de->d_name)) + continue; + + r = safe_atoi(de->d_name, &fd); + if (r < 0) + goto finish; + + if (fd < 3) + continue; + + if (fd == dirfd(d)) + continue; + + r = fdset_put(s, fd); + if (r < 0) + goto finish; + } + + r = 0; + *_s = s; + s = NULL; + +finish: + /* We won't close the fds here! */ + if (s) + set_free(MAKE_SET(s)); + + return r; +} + +int fdset_cloexec(FDSet *fds, bool b) { + Iterator i; + void *p; + int r; + + assert(fds); + + SET_FOREACH(p, MAKE_SET(fds), i) { + r = fd_cloexec(PTR_TO_FD(p), b); + if (r < 0) + return r; + } + + return 0; +} + +int fdset_new_listen_fds(FDSet **_s, bool unset) { + int n, fd, r; + FDSet *s; + + assert(_s); + + /* Creates an fdset and fills in all passed file descriptors */ + + s = fdset_new(); + if (!s) { + r = -ENOMEM; + goto fail; + } + + n = sd_listen_fds(unset); + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { + r = fdset_put(s, fd); + if (r < 0) + goto fail; + } + + *_s = s; + return 0; + + +fail: + if (s) + set_free(MAKE_SET(s)); + + return r; +} + +int fdset_close_others(FDSet *fds) { + void *e; + Iterator i; + int *a; + unsigned j, m; + + j = 0, m = fdset_size(fds); + a = alloca(sizeof(int) * m); + SET_FOREACH(e, MAKE_SET(fds), i) + a[j++] = PTR_TO_FD(e); + + assert(j == m); + + return close_all_fds(a, j); +} + +unsigned fdset_size(FDSet *fds) { + return set_size(MAKE_SET(fds)); +} + +bool fdset_isempty(FDSet *fds) { + return set_isempty(MAKE_SET(fds)); +} + +int fdset_iterate(FDSet *s, Iterator *i) { + void *p; + + if (!set_iterate(MAKE_SET(s), i, &p)) + return -ENOENT; + + return PTR_TO_FD(p); +} + +int fdset_steal_first(FDSet *fds) { + void *p; + + p = set_steal_first(MAKE_SET(fds)); + if (!p) + return -ENOENT; + + return PTR_TO_FD(p); +} diff --git a/src/libbasic/fdset.h b/src/libbasic/fdset.h new file mode 100644 index 0000000000..12d0cef761 --- /dev/null +++ b/src/libbasic/fdset.h @@ -0,0 +1,59 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "hashmap.h" +#include "macro.h" +#include "set.h" + +typedef struct FDSet FDSet; + +FDSet* fdset_new(void); +FDSet* fdset_free(FDSet *s); + +int fdset_put(FDSet *s, int fd); +int fdset_put_dup(FDSet *s, int fd); +int fdset_consume(FDSet *s, int fd); + +bool fdset_contains(FDSet *s, int fd); +int fdset_remove(FDSet *s, int fd); + +int fdset_new_array(FDSet **ret, const int *fds, unsigned n_fds); +int fdset_new_fill(FDSet **ret); +int fdset_new_listen_fds(FDSet **ret, bool unset); + +int fdset_cloexec(FDSet *fds, bool b); + +int fdset_close_others(FDSet *fds); + +unsigned fdset_size(FDSet *fds); +bool fdset_isempty(FDSet *fds); + +int fdset_iterate(FDSet *s, Iterator *i); + +int fdset_steal_first(FDSet *fds); + +#define FDSET_FOREACH(fd, fds, i) \ + for ((i) = ITERATOR_FIRST, (fd) = fdset_iterate((fds), &(i)); (fd) >= 0; (fd) = fdset_iterate((fds), &(i))) + +DEFINE_TRIVIAL_CLEANUP_FUNC(FDSet*, fdset_free); +#define _cleanup_fdset_free_ _cleanup_(fdset_freep) diff --git a/src/libbasic/fileio-label.c b/src/libbasic/fileio-label.c new file mode 100644 index 0000000000..66dbc0fe1e --- /dev/null +++ b/src/libbasic/fileio-label.c @@ -0,0 +1,68 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Harald Hoyer + + 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 . +***/ + +#include + +#include "fileio-label.h" +#include "fileio.h" +#include "selinux-util.h" + +int write_string_file_atomic_label(const char *fn, const char *line) { + int r; + + r = mac_selinux_create_file_prepare(fn, S_IFREG); + if (r < 0) + return r; + + r = write_string_file(fn, line, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); + + mac_selinux_create_file_clear(); + + return r; +} + +int write_env_file_label(const char *fname, char **l) { + int r; + + r = mac_selinux_create_file_prepare(fname, S_IFREG); + if (r < 0) + return r; + + r = write_env_file(fname, l); + + mac_selinux_create_file_clear(); + + return r; +} + +int fopen_temporary_label(const char *target, + const char *path, FILE **f, char **temp_path) { + int r; + + r = mac_selinux_create_file_prepare(target, S_IFREG); + if (r < 0) + return r; + + r = fopen_temporary(path, f, temp_path); + + mac_selinux_create_file_clear(); + + return r; +} diff --git a/src/libbasic/fileio-label.h b/src/libbasic/fileio-label.h new file mode 100644 index 0000000000..fe7543013d --- /dev/null +++ b/src/libbasic/fileio-label.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Harald Hoyer + + 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 . +***/ + +#include + +#include "fileio.h" + +int write_string_file_atomic_label(const char *fn, const char *line); +int write_env_file_label(const char *fname, char **l); +int fopen_temporary_label(const char *target, + const char *path, FILE **f, char **temp_path); diff --git a/src/libbasic/fileio.c b/src/libbasic/fileio.c new file mode 100644 index 0000000000..e43ca6d29e --- /dev/null +++ b/src/libbasic/fileio.c @@ -0,0 +1,1280 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "ctype.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hexdecoct.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "random-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "umask-util.h" +#include "utf8.h" + +int write_string_stream(FILE *f, const char *line, bool enforce_newline) { + + assert(f); + assert(line); + + fputs(line, f); + if (enforce_newline && !endswith(line, "\n")) + fputc('\n', f); + + return fflush_and_check(f); +} + +static int write_string_file_atomic(const char *fn, const char *line, bool enforce_newline) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *p = NULL; + int r; + + assert(fn); + assert(line); + + r = fopen_temporary(fn, &f, &p); + if (r < 0) + return r; + + (void) fchmod_umask(fileno(f), 0644); + + r = write_string_stream(f, line, enforce_newline); + if (r >= 0) { + if (rename(p, fn) < 0) + r = -errno; + } + + if (r < 0) + (void) unlink(p); + + return r; +} + +int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags) { + _cleanup_fclose_ FILE *f = NULL; + int q, r; + + assert(fn); + assert(line); + + if (flags & WRITE_STRING_FILE_ATOMIC) { + assert(flags & WRITE_STRING_FILE_CREATE); + + r = write_string_file_atomic(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); + if (r < 0) + goto fail; + + return r; + } + + if (flags & WRITE_STRING_FILE_CREATE) { + f = fopen(fn, "we"); + if (!f) { + r = -errno; + goto fail; + } + } else { + int fd; + + /* We manually build our own version of fopen(..., "we") that + * works without O_CREAT */ + fd = open(fn, O_WRONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + r = -errno; + goto fail; + } + + f = fdopen(fd, "we"); + if (!f) { + r = -errno; + safe_close(fd); + goto fail; + } + } + + r = write_string_stream(f, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); + if (r < 0) + goto fail; + + return 0; + +fail: + if (!(flags & WRITE_STRING_FILE_VERIFY_ON_FAILURE)) + return r; + + f = safe_fclose(f); + + /* OK, the operation failed, but let's see if the right + * contents in place already. If so, eat up the error. */ + + q = verify_file(fn, line, !(flags & WRITE_STRING_FILE_AVOID_NEWLINE)); + if (q <= 0) + return r; + + return 0; +} + +int read_one_line_file(const char *fn, char **line) { + _cleanup_fclose_ FILE *f = NULL; + char t[LINE_MAX], *c; + + assert(fn); + assert(line); + + f = fopen(fn, "re"); + if (!f) + return -errno; + + if (!fgets(t, sizeof(t), f)) { + + if (ferror(f)) + return errno > 0 ? -errno : -EIO; + + t[0] = 0; + } + + c = strdup(t); + if (!c) + return -ENOMEM; + truncate_nl(c); + + *line = c; + return 0; +} + +int verify_file(const char *fn, const char *blob, bool accept_extra_nl) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *buf = NULL; + size_t l, k; + + assert(fn); + assert(blob); + + l = strlen(blob); + + if (accept_extra_nl && endswith(blob, "\n")) + accept_extra_nl = false; + + buf = malloc(l + accept_extra_nl + 1); + if (!buf) + return -ENOMEM; + + f = fopen(fn, "re"); + if (!f) + return -errno; + + /* We try to read one byte more than we need, so that we know whether we hit eof */ + errno = 0; + k = fread(buf, 1, l + accept_extra_nl + 1, f); + if (ferror(f)) + return errno > 0 ? -errno : -EIO; + + if (k != l && k != l + accept_extra_nl) + return 0; + if (memcmp(buf, blob, l) != 0) + return 0; + if (k > l && buf[l] != '\n') + return 0; + + return 1; +} + +int read_full_stream(FILE *f, char **contents, size_t *size) { + size_t n, l; + _cleanup_free_ char *buf = NULL; + struct stat st; + + assert(f); + assert(contents); + + if (fstat(fileno(f), &st) < 0) + return -errno; + + n = LINE_MAX; + + if (S_ISREG(st.st_mode)) { + + /* Safety check */ + if (st.st_size > 4*1024*1024) + return -E2BIG; + + /* Start with the right file size, but be prepared for + * files from /proc which generally report a file size + * of 0 */ + if (st.st_size > 0) + n = st.st_size; + } + + l = 0; + for (;;) { + char *t; + size_t k; + + t = realloc(buf, n+1); + if (!t) + return -ENOMEM; + + buf = t; + k = fread(buf + l, 1, n - l, f); + + if (k <= 0) { + if (ferror(f)) + return -errno; + + break; + } + + l += k; + n *= 2; + + /* Safety check */ + if (n > 4*1024*1024) + return -E2BIG; + } + + buf[l] = 0; + *contents = buf; + buf = NULL; /* do not free */ + + if (size) + *size = l; + + return 0; +} + +int read_full_file(const char *fn, char **contents, size_t *size) { + _cleanup_fclose_ FILE *f = NULL; + + assert(fn); + assert(contents); + + f = fopen(fn, "re"); + if (!f) + return -errno; + + return read_full_stream(f, contents, size); +} + +static int parse_env_file_internal( + FILE *f, + const char *fname, + const char *newline, + int (*push) (const char *filename, unsigned line, + const char *key, char *value, void *userdata, int *n_pushed), + void *userdata, + int *n_pushed) { + + _cleanup_free_ char *contents = NULL, *key = NULL; + size_t key_alloc = 0, n_key = 0, value_alloc = 0, n_value = 0, last_value_whitespace = (size_t) -1, last_key_whitespace = (size_t) -1; + char *p, *value = NULL; + int r; + unsigned line = 1; + + enum { + PRE_KEY, + KEY, + PRE_VALUE, + VALUE, + VALUE_ESCAPE, + SINGLE_QUOTE_VALUE, + SINGLE_QUOTE_VALUE_ESCAPE, + DOUBLE_QUOTE_VALUE, + DOUBLE_QUOTE_VALUE_ESCAPE, + COMMENT, + COMMENT_ESCAPE + } state = PRE_KEY; + + assert(newline); + + if (f) + r = read_full_stream(f, &contents, NULL); + else + r = read_full_file(fname, &contents, NULL); + if (r < 0) + return r; + + for (p = contents; *p; p++) { + char c = *p; + + switch (state) { + + case PRE_KEY: + if (strchr(COMMENTS, c)) + state = COMMENT; + else if (!strchr(WHITESPACE, c)) { + state = KEY; + last_key_whitespace = (size_t) -1; + + if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) { + r = -ENOMEM; + goto fail; + } + + key[n_key++] = c; + } + break; + + case KEY: + if (strchr(newline, c)) { + state = PRE_KEY; + line ++; + n_key = 0; + } else if (c == '=') { + state = PRE_VALUE; + last_value_whitespace = (size_t) -1; + } else { + if (!strchr(WHITESPACE, c)) + last_key_whitespace = (size_t) -1; + else if (last_key_whitespace == (size_t) -1) + last_key_whitespace = n_key; + + if (!GREEDY_REALLOC(key, key_alloc, n_key+2)) { + r = -ENOMEM; + goto fail; + } + + key[n_key++] = c; + } + + break; + + case PRE_VALUE: + if (strchr(newline, c)) { + state = PRE_KEY; + line ++; + key[n_key] = 0; + + if (value) + value[n_value] = 0; + + /* strip trailing whitespace from key */ + if (last_key_whitespace != (size_t) -1) + key[last_key_whitespace] = 0; + + r = push(fname, line, key, value, userdata, n_pushed); + if (r < 0) + goto fail; + + n_key = 0; + value = NULL; + value_alloc = n_value = 0; + + } else if (c == '\'') + state = SINGLE_QUOTE_VALUE; + else if (c == '\"') + state = DOUBLE_QUOTE_VALUE; + else if (c == '\\') + state = VALUE_ESCAPE; + else if (!strchr(WHITESPACE, c)) { + state = VALUE; + + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + + break; + + case VALUE: + if (strchr(newline, c)) { + state = PRE_KEY; + line ++; + + key[n_key] = 0; + + if (value) + value[n_value] = 0; + + /* Chomp off trailing whitespace from value */ + if (last_value_whitespace != (size_t) -1) + value[last_value_whitespace] = 0; + + /* strip trailing whitespace from key */ + if (last_key_whitespace != (size_t) -1) + key[last_key_whitespace] = 0; + + r = push(fname, line, key, value, userdata, n_pushed); + if (r < 0) + goto fail; + + n_key = 0; + value = NULL; + value_alloc = n_value = 0; + + } else if (c == '\\') { + state = VALUE_ESCAPE; + last_value_whitespace = (size_t) -1; + } else { + if (!strchr(WHITESPACE, c)) + last_value_whitespace = (size_t) -1; + else if (last_value_whitespace == (size_t) -1) + last_value_whitespace = n_value; + + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + + break; + + case VALUE_ESCAPE: + state = VALUE; + + if (!strchr(newline, c)) { + /* Escaped newlines we eat up entirely */ + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + break; + + case SINGLE_QUOTE_VALUE: + if (c == '\'') + state = PRE_VALUE; + else if (c == '\\') + state = SINGLE_QUOTE_VALUE_ESCAPE; + else { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + + break; + + case SINGLE_QUOTE_VALUE_ESCAPE: + state = SINGLE_QUOTE_VALUE; + + if (!strchr(newline, c)) { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + break; + + case DOUBLE_QUOTE_VALUE: + if (c == '\"') + state = PRE_VALUE; + else if (c == '\\') + state = DOUBLE_QUOTE_VALUE_ESCAPE; + else { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + + break; + + case DOUBLE_QUOTE_VALUE_ESCAPE: + state = DOUBLE_QUOTE_VALUE; + + if (!strchr(newline, c)) { + if (!GREEDY_REALLOC(value, value_alloc, n_value+2)) { + r = -ENOMEM; + goto fail; + } + + value[n_value++] = c; + } + break; + + case COMMENT: + if (c == '\\') + state = COMMENT_ESCAPE; + else if (strchr(newline, c)) { + state = PRE_KEY; + line ++; + } + break; + + case COMMENT_ESCAPE: + state = COMMENT; + break; + } + } + + if (state == PRE_VALUE || + state == VALUE || + state == VALUE_ESCAPE || + state == SINGLE_QUOTE_VALUE || + state == SINGLE_QUOTE_VALUE_ESCAPE || + state == DOUBLE_QUOTE_VALUE || + state == DOUBLE_QUOTE_VALUE_ESCAPE) { + + key[n_key] = 0; + + if (value) + value[n_value] = 0; + + if (state == VALUE) + if (last_value_whitespace != (size_t) -1) + value[last_value_whitespace] = 0; + + /* strip trailing whitespace from key */ + if (last_key_whitespace != (size_t) -1) + key[last_key_whitespace] = 0; + + r = push(fname, line, key, value, userdata, n_pushed); + if (r < 0) + goto fail; + } + + return 0; + +fail: + free(value); + return r; +} + +static int parse_env_file_push( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata, + int *n_pushed) { + + const char *k; + va_list aq, *ap = userdata; + + if (!utf8_is_valid(key)) { + _cleanup_free_ char *p; + + p = utf8_escape_invalid(key); + log_error("%s:%u: invalid UTF-8 in key '%s', ignoring.", strna(filename), line, p); + return -EINVAL; + } + + if (value && !utf8_is_valid(value)) { + _cleanup_free_ char *p; + + p = utf8_escape_invalid(value); + log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, p); + return -EINVAL; + } + + va_copy(aq, *ap); + + while ((k = va_arg(aq, const char *))) { + char **v; + + v = va_arg(aq, char **); + + if (streq(key, k)) { + va_end(aq); + free(*v); + *v = value; + + if (n_pushed) + (*n_pushed)++; + + return 1; + } + } + + va_end(aq); + free(value); + + return 0; +} + +int parse_env_file( + const char *fname, + const char *newline, ...) { + + va_list ap; + int r, n_pushed = 0; + + if (!newline) + newline = NEWLINE; + + va_start(ap, newline); + r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap, &n_pushed); + va_end(ap); + + return r < 0 ? r : n_pushed; +} + +static int load_env_file_push( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata, + int *n_pushed) { + char ***m = userdata; + char *p; + int r; + + if (!utf8_is_valid(key)) { + _cleanup_free_ char *t = utf8_escape_invalid(key); + + log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t); + return -EINVAL; + } + + if (value && !utf8_is_valid(value)) { + _cleanup_free_ char *t = utf8_escape_invalid(value); + + log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t); + return -EINVAL; + } + + p = strjoin(key, "=", strempty(value), NULL); + if (!p) + return -ENOMEM; + + r = strv_consume(m, p); + if (r < 0) + return r; + + if (n_pushed) + (*n_pushed)++; + + free(value); + return 0; +} + +int load_env_file(FILE *f, const char *fname, const char *newline, char ***rl) { + char **m = NULL; + int r; + + if (!newline) + newline = NEWLINE; + + r = parse_env_file_internal(f, fname, newline, load_env_file_push, &m, NULL); + if (r < 0) { + strv_free(m); + return r; + } + + *rl = m; + return 0; +} + +static int load_env_file_push_pairs( + const char *filename, unsigned line, + const char *key, char *value, + void *userdata, + int *n_pushed) { + char ***m = userdata; + int r; + + if (!utf8_is_valid(key)) { + _cleanup_free_ char *t = utf8_escape_invalid(key); + + log_error("%s:%u: invalid UTF-8 for key '%s', ignoring.", strna(filename), line, t); + return -EINVAL; + } + + if (value && !utf8_is_valid(value)) { + _cleanup_free_ char *t = utf8_escape_invalid(value); + + log_error("%s:%u: invalid UTF-8 value for key %s: '%s', ignoring.", strna(filename), line, key, t); + return -EINVAL; + } + + r = strv_extend(m, key); + if (r < 0) + return -ENOMEM; + + if (!value) { + r = strv_extend(m, ""); + if (r < 0) + return -ENOMEM; + } else { + r = strv_push(m, value); + if (r < 0) + return r; + } + + if (n_pushed) + (*n_pushed)++; + + return 0; +} + +int load_env_file_pairs(FILE *f, const char *fname, const char *newline, char ***rl) { + char **m = NULL; + int r; + + if (!newline) + newline = NEWLINE; + + r = parse_env_file_internal(f, fname, newline, load_env_file_push_pairs, &m, NULL); + if (r < 0) { + strv_free(m); + return r; + } + + *rl = m; + return 0; +} + +static void write_env_var(FILE *f, const char *v) { + const char *p; + + p = strchr(v, '='); + if (!p) { + /* Fallback */ + fputs(v, f); + fputc('\n', f); + return; + } + + p++; + fwrite(v, 1, p-v, f); + + if (string_has_cc(p, NULL) || chars_intersect(p, WHITESPACE SHELL_NEED_QUOTES)) { + fputc('\"', f); + + for (; *p; p++) { + if (strchr(SHELL_NEED_ESCAPE, *p)) + fputc('\\', f); + + fputc(*p, f); + } + + fputc('\"', f); + } else + fputs(p, f); + + fputc('\n', f); +} + +int write_env_file(const char *fname, char **l) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *p = NULL; + char **i; + int r; + + assert(fname); + + r = fopen_temporary(fname, &f, &p); + if (r < 0) + return r; + + fchmod_umask(fileno(f), 0644); + + STRV_FOREACH(i, l) + write_env_var(f, *i); + + r = fflush_and_check(f); + if (r >= 0) { + if (rename(p, fname) >= 0) + return 0; + + r = -errno; + } + + unlink(p); + return r; +} + +int executable_is_script(const char *path, char **interpreter) { + int r; + _cleanup_free_ char *line = NULL; + int len; + char *ans; + + assert(path); + + r = read_one_line_file(path, &line); + if (r < 0) + return r; + + if (!startswith(line, "#!")) + return 0; + + ans = strstrip(line + 2); + len = strcspn(ans, " \t"); + + if (len == 0) + return 0; + + ans = strndup(ans, len); + if (!ans) + return -ENOMEM; + + *interpreter = ans; + return 1; +} + +/** + * Retrieve one field from a file like /proc/self/status. pattern + * should not include whitespace or the delimiter (':'). pattern matches only + * the beginning of a line. Whitespace before ':' is skipped. Whitespace and + * zeros after the ':' will be skipped. field must be freed afterwards. + * terminator specifies the terminating characters of the field value (not + * included in the value). + */ +int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field) { + _cleanup_free_ char *status = NULL; + char *t, *f; + size_t len; + int r; + + assert(terminator); + assert(filename); + assert(pattern); + assert(field); + + r = read_full_file(filename, &status, NULL); + if (r < 0) + return r; + + t = status; + + do { + bool pattern_ok; + + do { + t = strstr(t, pattern); + if (!t) + return -ENOENT; + + /* Check that pattern occurs in beginning of line. */ + pattern_ok = (t == status || t[-1] == '\n'); + + t += strlen(pattern); + + } while (!pattern_ok); + + t += strspn(t, " \t"); + if (!*t) + return -ENOENT; + + } while (*t != ':'); + + t++; + + if (*t) { + t += strspn(t, " \t"); + + /* Also skip zeros, because when this is used for + * capabilities, we don't want the zeros. This way the + * same capability set always maps to the same string, + * irrespective of the total capability set size. For + * other numbers it shouldn't matter. */ + t += strspn(t, "0"); + /* Back off one char if there's nothing but whitespace + and zeros */ + if (!*t || isspace(*t)) + t --; + } + + len = strcspn(t, terminator); + + f = strndup(t, len); + if (!f) + return -ENOMEM; + + *field = f; + return 0; +} + +DIR *xopendirat(int fd, const char *name, int flags) { + int nfd; + DIR *d; + + assert(!(flags & O_CREAT)); + + nfd = openat(fd, name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|flags, 0); + if (nfd < 0) + return NULL; + + d = fdopendir(nfd); + if (!d) { + safe_close(nfd); + return NULL; + } + + return d; +} + +static int search_and_fopen_internal(const char *path, const char *mode, const char *root, char **search, FILE **_f) { + char **i; + + assert(path); + assert(mode); + assert(_f); + + if (!path_strv_resolve_uniq(search, root)) + return -ENOMEM; + + STRV_FOREACH(i, search) { + _cleanup_free_ char *p = NULL; + FILE *f; + + if (root) + p = strjoin(root, *i, "/", path, NULL); + else + p = strjoin(*i, "/", path, NULL); + if (!p) + return -ENOMEM; + + f = fopen(p, mode); + if (f) { + *_f = f; + return 0; + } + + if (errno != ENOENT) + return -errno; + } + + return -ENOENT; +} + +int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f) { + _cleanup_strv_free_ char **copy = NULL; + + assert(path); + assert(mode); + assert(_f); + + if (path_is_absolute(path)) { + FILE *f; + + f = fopen(path, mode); + if (f) { + *_f = f; + return 0; + } + + return -errno; + } + + copy = strv_copy((char**) search); + if (!copy) + return -ENOMEM; + + return search_and_fopen_internal(path, mode, root, copy, _f); +} + +int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f) { + _cleanup_strv_free_ char **s = NULL; + + if (path_is_absolute(path)) { + FILE *f; + + f = fopen(path, mode); + if (f) { + *_f = f; + return 0; + } + + return -errno; + } + + s = strv_split_nulstr(search); + if (!s) + return -ENOMEM; + + return search_and_fopen_internal(path, mode, root, s, _f); +} + +int fopen_temporary(const char *path, FILE **_f, char **_temp_path) { + FILE *f; + char *t; + int r, fd; + + assert(path); + assert(_f); + assert(_temp_path); + + r = tempfn_xxxxxx(path, NULL, &t); + if (r < 0) + return r; + + fd = mkostemp_safe(t, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + free(t); + return -errno; + } + + f = fdopen(fd, "we"); + if (!f) { + unlink_noerrno(t); + free(t); + safe_close(fd); + return -errno; + } + + *_f = f; + *_temp_path = t; + + return 0; +} + +int fflush_and_check(FILE *f) { + assert(f); + + errno = 0; + fflush(f); + + if (ferror(f)) + return errno > 0 ? -errno : -EIO; + + return 0; +} + +/* This is much like like mkostemp() but is subject to umask(). */ +int mkostemp_safe(char *pattern, int flags) { + _cleanup_umask_ mode_t u; + int fd; + + assert(pattern); + + u = umask(077); + + fd = mkostemp(pattern, flags); + if (fd < 0) + return -errno; + + return fd; +} + +int open_tmpfile(const char *path, int flags) { + char *p; + int fd; + + assert(path); + +#ifdef O_TMPFILE + /* Try O_TMPFILE first, if it is supported */ + fd = open(path, flags|O_TMPFILE|O_EXCL, S_IRUSR|S_IWUSR); + if (fd >= 0) + return fd; +#endif + + /* Fall back to unguessable name + unlinking */ + p = strjoina(path, "/systemd-tmp-XXXXXX"); + + fd = mkostemp_safe(p, flags); + if (fd < 0) + return fd; + + unlink(p); + return fd; +} + +int tempfn_xxxxxx(const char *p, const char *extra, char **ret) { + const char *fn; + char *t; + + assert(p); + assert(ret); + + /* + * Turns this: + * /foo/bar/waldo + * + * Into this: + * /foo/bar/.#waldoXXXXXX + */ + + fn = basename(p); + if (!filename_is_valid(fn)) + return -EINVAL; + + if (extra == NULL) + extra = ""; + + t = new(char, strlen(p) + 2 + strlen(extra) + 6 + 1); + if (!t) + return -ENOMEM; + + strcpy(stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn), "XXXXXX"); + + *ret = path_kill_slashes(t); + return 0; +} + +int tempfn_random(const char *p, const char *extra, char **ret) { + const char *fn; + char *t, *x; + uint64_t u; + unsigned i; + + assert(p); + assert(ret); + + /* + * Turns this: + * /foo/bar/waldo + * + * Into this: + * /foo/bar/.#waldobaa2a261115984a9 + */ + + fn = basename(p); + if (!filename_is_valid(fn)) + return -EINVAL; + + if (!extra) + extra = ""; + + t = new(char, strlen(p) + 2 + strlen(extra) + 16 + 1); + if (!t) + return -ENOMEM; + + x = stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), extra), fn); + + u = random_u64(); + for (i = 0; i < 16; i++) { + *(x++) = hexchar(u & 0xF); + u >>= 4; + } + + *x = 0; + + *ret = path_kill_slashes(t); + return 0; +} + +int tempfn_random_child(const char *p, const char *extra, char **ret) { + char *t, *x; + uint64_t u; + unsigned i; + + assert(p); + assert(ret); + + /* Turns this: + * /foo/bar/waldo + * Into this: + * /foo/bar/waldo/.#3c2b6219aa75d7d0 + */ + + if (!extra) + extra = ""; + + t = new(char, strlen(p) + 3 + strlen(extra) + 16 + 1); + if (!t) + return -ENOMEM; + + x = stpcpy(stpcpy(stpcpy(t, p), "/.#"), extra); + + u = random_u64(); + for (i = 0; i < 16; i++) { + *(x++) = hexchar(u & 0xF); + u >>= 4; + } + + *x = 0; + + *ret = path_kill_slashes(t); + return 0; +} + +int write_timestamp_file_atomic(const char *fn, usec_t n) { + char ln[DECIMAL_STR_MAX(n)+2]; + + /* Creates a "timestamp" file, that contains nothing but a + * usec_t timestamp, formatted in ASCII. */ + + if (n <= 0 || n >= USEC_INFINITY) + return -ERANGE; + + xsprintf(ln, USEC_FMT "\n", n); + + return write_string_file(fn, ln, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); +} + +int read_timestamp_file(const char *fn, usec_t *ret) { + _cleanup_free_ char *ln = NULL; + uint64_t t; + int r; + + r = read_one_line_file(fn, &ln); + if (r < 0) + return r; + + r = safe_atou64(ln, &t); + if (r < 0) + return r; + + if (t <= 0 || t >= (uint64_t) USEC_INFINITY) + return -ERANGE; + + *ret = (usec_t) t; + return 0; +} + +int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space) { + int r; + + assert(s); + + /* Outputs the specified string with fputs(), but optionally prefixes it with a separator. The *space parameter + * when specified shall initially point to a boolean variable initialized to false. It is set to true after the + * first invocation. This call is supposed to be use in loops, where a separator shall be inserted between each + * element, but not before the first one. */ + + if (!f) + f = stdout; + + if (space) { + if (!separator) + separator = " "; + + if (*space) { + r = fputs(separator, f); + if (r < 0) + return r; + } + + *space = true; + } + + return fputs(s, f); +} diff --git a/src/libbasic/fileio.h b/src/libbasic/fileio.h new file mode 100644 index 0000000000..8084895ff3 --- /dev/null +++ b/src/libbasic/fileio.h @@ -0,0 +1,84 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "macro.h" +#include "time-util.h" + +typedef enum { + WRITE_STRING_FILE_CREATE = 1, + WRITE_STRING_FILE_ATOMIC = 2, + WRITE_STRING_FILE_AVOID_NEWLINE = 4, + WRITE_STRING_FILE_VERIFY_ON_FAILURE = 8, +} WriteStringFileFlags; + +int write_string_stream(FILE *f, const char *line, bool enforce_newline); +int write_string_file(const char *fn, const char *line, WriteStringFileFlags flags); + +int read_one_line_file(const char *fn, char **line); +int read_full_file(const char *fn, char **contents, size_t *size); +int read_full_stream(FILE *f, char **contents, size_t *size); + +int verify_file(const char *fn, const char *blob, bool accept_extra_nl); + +int parse_env_file(const char *fname, const char *separator, ...) _sentinel_; +int load_env_file(FILE *f, const char *fname, const char *separator, char ***l); +int load_env_file_pairs(FILE *f, const char *fname, const char *separator, char ***l); + +int write_env_file(const char *fname, char **l); + +int executable_is_script(const char *path, char **interpreter); + +int get_proc_field(const char *filename, const char *pattern, const char *terminator, char **field); + +DIR *xopendirat(int dirfd, const char *name, int flags); + +int search_and_fopen(const char *path, const char *mode, const char *root, const char **search, FILE **_f); +int search_and_fopen_nulstr(const char *path, const char *mode, const char *root, const char *search, FILE **_f); + +#define FOREACH_LINE(line, f, on_error) \ + for (;;) \ + if (!fgets(line, sizeof(line), f)) { \ + if (ferror(f)) { \ + on_error; \ + } \ + break; \ + } else + +int fflush_and_check(FILE *f); + +int fopen_temporary(const char *path, FILE **_f, char **_temp_path); +int mkostemp_safe(char *pattern, int flags); +int open_tmpfile(const char *path, int flags); + +int tempfn_xxxxxx(const char *p, const char *extra, char **ret); +int tempfn_random(const char *p, const char *extra, char **ret); +int tempfn_random_child(const char *p, const char *extra, char **ret); + +int write_timestamp_file_atomic(const char *fn, usec_t n); +int read_timestamp_file(const char *fn, usec_t *ret); + +int fputs_with_space(FILE *f, const char *s, const char *separator, bool *space); diff --git a/src/libbasic/formats-util.h b/src/libbasic/formats-util.h new file mode 100644 index 0000000000..ce516b117d --- /dev/null +++ b/src/libbasic/formats-util.h @@ -0,0 +1,63 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Ronny Chevalier + + 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 . +***/ + +#include + +#if SIZEOF_PID_T == 4 +# define PID_PRI PRIi32 +#elif SIZEOF_PID_T == 2 +# define PID_PRI PRIi16 +#else +# error Unknown pid_t size +#endif +#define PID_FMT "%" PID_PRI + +#if SIZEOF_UID_T == 4 +# define UID_FMT "%" PRIu32 +#elif SIZEOF_UID_T == 2 +# define UID_FMT "%" PRIu16 +#else +# error Unknown uid_t size +#endif + +#if SIZEOF_GID_T == 4 +# define GID_FMT "%" PRIu32 +#elif SIZEOF_GID_T == 2 +# define GID_FMT "%" PRIu16 +#else +# error Unknown gid_t size +#endif + +#if SIZEOF_TIME_T == 8 +# define PRI_TIME PRIi64 +#elif SIZEOF_TIME_T == 4 +# define PRI_TIME PRIu32 +#else +# error Unknown time_t size +#endif + +#if SIZEOF_RLIM_T == 8 +# define RLIM_FMT "%" PRIu64 +#elif SIZEOF_RLIM_T == 4 +# define RLIM_FMT "%" PRIu32 +#else +# error Unknown rlim_t size +#endif diff --git a/src/libbasic/fs-util.c b/src/libbasic/fs-util.c new file mode 100644 index 0000000000..3ef1b90edd --- /dev/null +++ b/src/libbasic/fs-util.c @@ -0,0 +1,513 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "user-util.h" +#include "util.h" + +int unlink_noerrno(const char *path) { + PROTECT_ERRNO; + int r; + + r = unlink(path); + if (r < 0) + return -errno; + + return 0; +} + +int rmdir_parents(const char *path, const char *stop) { + size_t l; + int r = 0; + + assert(path); + assert(stop); + + l = strlen(path); + + /* Skip trailing slashes */ + while (l > 0 && path[l-1] == '/') + l--; + + while (l > 0) { + char *t; + + /* Skip last component */ + while (l > 0 && path[l-1] != '/') + l--; + + /* Skip trailing slashes */ + while (l > 0 && path[l-1] == '/') + l--; + + if (l <= 0) + break; + + t = strndup(path, l); + if (!t) + return -ENOMEM; + + if (path_startswith(stop, t)) { + free(t); + return 0; + } + + r = rmdir(t); + free(t); + + if (r < 0) + if (errno != ENOENT) + return -errno; + } + + return 0; +} + + +int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { + struct stat buf; + int ret; + + ret = renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE); + if (ret >= 0) + return 0; + + /* renameat2() exists since Linux 3.15, btrfs added support for it later. + * If it is not implemented, fallback to another method. */ + if (!IN_SET(errno, EINVAL, ENOSYS)) + return -errno; + + /* The link()/unlink() fallback does not work on directories. But + * renameat() without RENAME_NOREPLACE gives the same semantics on + * directories, except when newpath is an *empty* directory. This is + * good enough. */ + ret = fstatat(olddirfd, oldpath, &buf, AT_SYMLINK_NOFOLLOW); + if (ret >= 0 && S_ISDIR(buf.st_mode)) { + ret = renameat(olddirfd, oldpath, newdirfd, newpath); + return ret >= 0 ? 0 : -errno; + } + + /* If it is not a directory, use the link()/unlink() fallback. */ + ret = linkat(olddirfd, oldpath, newdirfd, newpath, 0); + if (ret < 0) + return -errno; + + ret = unlinkat(olddirfd, oldpath, 0); + if (ret < 0) { + /* backup errno before the following unlinkat() alters it */ + ret = errno; + (void) unlinkat(newdirfd, newpath, 0); + errno = ret; + return -errno; + } + + return 0; +} + +int readlinkat_malloc(int fd, const char *p, char **ret) { + size_t l = 100; + int r; + + assert(p); + assert(ret); + + for (;;) { + char *c; + ssize_t n; + + c = new(char, l); + if (!c) + return -ENOMEM; + + n = readlinkat(fd, p, c, l-1); + if (n < 0) { + r = -errno; + free(c); + return r; + } + + if ((size_t) n < l-1) { + c[n] = 0; + *ret = c; + return 0; + } + + free(c); + l *= 2; + } +} + +int readlink_malloc(const char *p, char **ret) { + return readlinkat_malloc(AT_FDCWD, p, ret); +} + +int readlink_value(const char *p, char **ret) { + _cleanup_free_ char *link = NULL; + char *value; + int r; + + r = readlink_malloc(p, &link); + if (r < 0) + return r; + + value = basename(link); + if (!value) + return -ENOENT; + + value = strdup(value); + if (!value) + return -ENOMEM; + + *ret = value; + + return 0; +} + +int readlink_and_make_absolute(const char *p, char **r) { + _cleanup_free_ char *target = NULL; + char *k; + int j; + + assert(p); + assert(r); + + j = readlink_malloc(p, &target); + if (j < 0) + return j; + + k = file_in_same_dir(p, target); + if (!k) + return -ENOMEM; + + *r = k; + return 0; +} + +int readlink_and_canonicalize(const char *p, char **r) { + char *t, *s; + int j; + + assert(p); + assert(r); + + j = readlink_and_make_absolute(p, &t); + if (j < 0) + return j; + + s = canonicalize_file_name(t); + if (s) { + free(t); + *r = s; + } else + *r = t; + + path_kill_slashes(*r); + + return 0; +} + +int readlink_and_make_absolute_root(const char *root, const char *path, char **ret) { + _cleanup_free_ char *target = NULL, *t = NULL; + const char *full; + int r; + + full = prefix_roota(root, path); + r = readlink_malloc(full, &target); + if (r < 0) + return r; + + t = file_in_same_dir(path, target); + if (!t) + return -ENOMEM; + + *ret = t; + t = NULL; + + return 0; +} + +int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { + assert(path); + + /* Under the assumption that we are running privileged we + * first change the access mode and only then hand out + * ownership to avoid a window where access is too open. */ + + if (mode != MODE_INVALID) + if (chmod(path, mode) < 0) + return -errno; + + if (uid != UID_INVALID || gid != GID_INVALID) + if (chown(path, uid, gid) < 0) + return -errno; + + return 0; +} + +int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid) { + assert(fd >= 0); + + /* Under the assumption that we are running privileged we + * first change the access mode and only then hand out + * ownership to avoid a window where access is too open. */ + + if (mode != MODE_INVALID) + if (fchmod(fd, mode) < 0) + return -errno; + + if (uid != UID_INVALID || gid != GID_INVALID) + if (fchown(fd, uid, gid) < 0) + return -errno; + + return 0; +} + +int fchmod_umask(int fd, mode_t m) { + mode_t u; + int r; + + u = umask(0777); + r = fchmod(fd, m & (~u)) < 0 ? -errno : 0; + umask(u); + + return r; +} + +int fd_warn_permissions(const char *path, int fd) { + struct stat st; + + if (fstat(fd, &st) < 0) + return -errno; + + if (st.st_mode & 0111) + log_warning("Configuration file %s is marked executable. Please remove executable permission bits. Proceeding anyway.", path); + + if (st.st_mode & 0002) + log_warning("Configuration file %s is marked world-writable. Please remove world writability permission bits. Proceeding anyway.", path); + + if (getpid() == 1 && (st.st_mode & 0044) != 0044) + log_warning("Configuration file %s is marked world-inaccessible. This has no effect as configuration data is accessible via APIs without restrictions. Proceeding anyway.", path); + + return 0; +} + +int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) { + _cleanup_close_ int fd; + int r; + + assert(path); + + if (parents) + mkdir_parents(path, 0755); + + fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, + (mode == 0 || mode == MODE_INVALID) ? 0644 : mode); + if (fd < 0) + return -errno; + + if (mode != MODE_INVALID) { + r = fchmod(fd, mode); + if (r < 0) + return -errno; + } + + if (uid != UID_INVALID || gid != GID_INVALID) { + r = fchown(fd, uid, gid); + if (r < 0) + return -errno; + } + + if (stamp != USEC_INFINITY) { + struct timespec ts[2]; + + timespec_store(&ts[0], stamp); + ts[1] = ts[0]; + r = futimens(fd, ts); + } else + r = futimens(fd, NULL); + if (r < 0) + return -errno; + + return 0; +} + +int touch(const char *path) { + return touch_file(path, false, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID); +} + +int symlink_idempotent(const char *from, const char *to) { + _cleanup_free_ char *p = NULL; + int r; + + assert(from); + assert(to); + + if (symlink(from, to) < 0) { + if (errno != EEXIST) + return -errno; + + r = readlink_malloc(to, &p); + if (r < 0) + return r; + + if (!streq(p, from)) + return -EINVAL; + } + + return 0; +} + +int symlink_atomic(const char *from, const char *to) { + _cleanup_free_ char *t = NULL; + int r; + + assert(from); + assert(to); + + r = tempfn_random(to, NULL, &t); + if (r < 0) + return r; + + if (symlink(from, t) < 0) + return -errno; + + if (rename(t, to) < 0) { + unlink_noerrno(t); + return -errno; + } + + return 0; +} + +int mknod_atomic(const char *path, mode_t mode, dev_t dev) { + _cleanup_free_ char *t = NULL; + int r; + + assert(path); + + r = tempfn_random(path, NULL, &t); + if (r < 0) + return r; + + if (mknod(t, mode, dev) < 0) + return -errno; + + if (rename(t, path) < 0) { + unlink_noerrno(t); + return -errno; + } + + return 0; +} + +int mkfifo_atomic(const char *path, mode_t mode) { + _cleanup_free_ char *t = NULL; + int r; + + assert(path); + + r = tempfn_random(path, NULL, &t); + if (r < 0) + return r; + + if (mkfifo(t, mode) < 0) + return -errno; + + if (rename(t, path) < 0) { + unlink_noerrno(t); + return -errno; + } + + return 0; +} + +int get_files_in_directory(const char *path, char ***list) { + _cleanup_closedir_ DIR *d = NULL; + size_t bufsize = 0, n = 0; + _cleanup_strv_free_ char **l = NULL; + + assert(path); + + /* Returns all files in a directory in *list, and the number + * of files as return value. If list is NULL returns only the + * number. */ + + d = opendir(path); + if (!d) + return -errno; + + for (;;) { + struct dirent *de; + + errno = 0; + de = readdir(d); + if (!de && errno > 0) + return -errno; + if (!de) + break; + + dirent_ensure_type(d, de); + + if (!dirent_is_file(de)) + continue; + + if (list) { + /* one extra slot is needed for the terminating NULL */ + if (!GREEDY_REALLOC(l, bufsize, n + 2)) + return -ENOMEM; + + l[n] = strdup(de->d_name); + if (!l[n]) + return -ENOMEM; + + l[++n] = NULL; + } else + n++; + } + + if (list) { + *list = l; + l = NULL; /* avoid freeing */ + } + + return n; +} diff --git a/src/libbasic/fs-util.h b/src/libbasic/fs-util.h new file mode 100644 index 0000000000..0e2fcb21b9 --- /dev/null +++ b/src/libbasic/fs-util.h @@ -0,0 +1,75 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "time-util.h" + +int unlink_noerrno(const char *path); + +int rmdir_parents(const char *path, const char *stop); + +int rename_noreplace(int olddirfd, const char *oldpath, int newdirfd, const char *newpath); + +int readlinkat_malloc(int fd, const char *p, char **ret); +int readlink_malloc(const char *p, char **r); +int readlink_value(const char *p, char **ret); +int readlink_and_make_absolute(const char *p, char **r); +int readlink_and_canonicalize(const char *p, char **r); +int readlink_and_make_absolute_root(const char *root, const char *path, char **ret); + +int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid); +int fchmod_and_fchown(int fd, mode_t mode, uid_t uid, gid_t gid); + +int fchmod_umask(int fd, mode_t mode); + +int fd_warn_permissions(const char *path, int fd); + +#define laccess(path, mode) faccessat(AT_FDCWD, (path), (mode), AT_SYMLINK_NOFOLLOW) + +int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode); +int touch(const char *path); + +int symlink_idempotent(const char *from, const char *to); + +int symlink_atomic(const char *from, const char *to); +int mknod_atomic(const char *path, mode_t mode, dev_t dev); +int mkfifo_atomic(const char *path, mode_t mode); + +int get_files_in_directory(const char *path, char ***list); + +#define INOTIFY_EVENT_MAX (sizeof(struct inotify_event) + NAME_MAX + 1) + +#define FOREACH_INOTIFY_EVENT(e, buffer, sz) \ + for ((e) = &buffer.ev; \ + (uint8_t*) (e) < (uint8_t*) (buffer.raw) + (sz); \ + (e) = (struct inotify_event*) ((uint8_t*) (e) + sizeof(struct inotify_event) + (e)->len)) + +union inotify_event_buffer { + struct inotify_event ev; + uint8_t raw[INOTIFY_EVENT_MAX]; +}; diff --git a/src/libbasic/glob-util.c b/src/libbasic/glob-util.c new file mode 100644 index 0000000000..007198c269 --- /dev/null +++ b/src/libbasic/glob-util.c @@ -0,0 +1,70 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "glob-util.h" +#include "macro.h" +#include "strv.h" + +int glob_exists(const char *path) { + _cleanup_globfree_ glob_t g = {}; + int k; + + assert(path); + + errno = 0; + k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); + + if (k == GLOB_NOMATCH) + return 0; + if (k == GLOB_NOSPACE) + return -ENOMEM; + if (k != 0) + return errno > 0 ? -errno : -EIO; + + return !strv_isempty(g.gl_pathv); +} + +int glob_extend(char ***strv, const char *path) { + _cleanup_globfree_ glob_t g = {}; + int k; + char **p; + + errno = 0; + k = glob(path, GLOB_NOSORT|GLOB_BRACE, NULL, &g); + + if (k == GLOB_NOMATCH) + return -ENOENT; + if (k == GLOB_NOSPACE) + return -ENOMEM; + if (k != 0) + return errno > 0 ? -errno : -EIO; + if (strv_isempty(g.gl_pathv)) + return -ENOENT; + + STRV_FOREACH(p, g.gl_pathv) { + k = strv_extend(strv, *p); + if (k < 0) + return k; + } + + return 0; +} diff --git a/src/libbasic/glob-util.h b/src/libbasic/glob-util.h new file mode 100644 index 0000000000..5d8fb47a26 --- /dev/null +++ b/src/libbasic/glob-util.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "macro.h" +#include "string-util.h" + +int glob_exists(const char *path); +int glob_extend(char ***strv, const char *path); + +#define _cleanup_globfree_ _cleanup_(globfree) + +_pure_ static inline bool string_is_glob(const char *p) { + /* Check if a string contains any glob patterns. */ + return !!strpbrk(p, GLOB_CHARS); +} diff --git a/src/libbasic/gunicode.c b/src/libbasic/gunicode.c new file mode 100644 index 0000000000..542110503f --- /dev/null +++ b/src/libbasic/gunicode.c @@ -0,0 +1,112 @@ +/* gunicode.c - Unicode manipulation functions + * + * Copyright (C) 1999, 2000 Tom Tromey + * Copyright 2000, 2005 Red Hat, Inc. + */ + +#include + +#include "gunicode.h" + +#define unichar uint32_t + +/** + * g_utf8_prev_char: + * @p: a pointer to a position within a UTF-8 encoded string + * + * Finds the previous UTF-8 character in the string before @p. + * + * @p does not have to be at the beginning of a UTF-8 character. No check + * is made to see if the character found is actually valid other than + * it starts with an appropriate byte. If @p might be the first + * character of the string, you must use g_utf8_find_prev_char() instead. + * + * Return value: a pointer to the found character. + **/ +char * +utf8_prev_char (const char *p) +{ + while (1) + { + p--; + if ((*p & 0xc0) != 0x80) + return (char *)p; + } +} + +struct Interval +{ + unichar start, end; +}; + +static int +interval_compare (const void *key, const void *elt) +{ + unichar c = (unichar) (long) (key); + struct Interval *interval = (struct Interval *)elt; + + if (c < interval->start) + return -1; + if (c > interval->end) + return +1; + + return 0; +} + +/* + * NOTE: + * + * The tables for g_unichar_iswide() and g_unichar_iswide_cjk() are + * generated from the Unicode Character Database's file + * extracted/DerivedEastAsianWidth.txt using the gen-iswide-table.py + * in this way: + * + * ./gen-iswide-table.py < path/to/ucd/extracted/DerivedEastAsianWidth.txt | fmt + * + * Last update for Unicode 6.0. + */ + +/** + * g_unichar_iswide: + * @c: a Unicode character + * + * Determines if a character is typically rendered in a double-width + * cell. + * + * Return value: %TRUE if the character is wide + **/ +bool +unichar_iswide (unichar c) +{ + /* See NOTE earlier for how to update this table. */ + static const struct Interval wide[] = { + {0x1100, 0x115F}, {0x2329, 0x232A}, {0x2E80, 0x2E99}, {0x2E9B, 0x2EF3}, + {0x2F00, 0x2FD5}, {0x2FF0, 0x2FFB}, {0x3000, 0x303E}, {0x3041, 0x3096}, + {0x3099, 0x30FF}, {0x3105, 0x312D}, {0x3131, 0x318E}, {0x3190, 0x31BA}, + {0x31C0, 0x31E3}, {0x31F0, 0x321E}, {0x3220, 0x3247}, {0x3250, 0x32FE}, + {0x3300, 0x4DBF}, {0x4E00, 0xA48C}, {0xA490, 0xA4C6}, {0xA960, 0xA97C}, + {0xAC00, 0xD7A3}, {0xF900, 0xFAFF}, {0xFE10, 0xFE19}, {0xFE30, 0xFE52}, + {0xFE54, 0xFE66}, {0xFE68, 0xFE6B}, {0xFF01, 0xFF60}, {0xFFE0, 0xFFE6}, + {0x1B000, 0x1B001}, {0x1F200, 0x1F202}, {0x1F210, 0x1F23A}, + {0x1F240, 0x1F248}, {0x1F250, 0x1F251}, + {0x1F300, 0x1F567}, /* Miscellaneous Symbols and Pictographs */ + {0x20000, 0x2FFFD}, {0x30000, 0x3FFFD}, + }; + + if (bsearch ((void *)(uintptr_t)c, wide, (sizeof (wide) / sizeof ((wide)[0])), sizeof wide[0], + interval_compare)) + return true; + + return false; +} + +const char utf8_skip_data[256] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1 +}; diff --git a/src/libbasic/gunicode.h b/src/libbasic/gunicode.h new file mode 100644 index 0000000000..b03aa43160 --- /dev/null +++ b/src/libbasic/gunicode.h @@ -0,0 +1,30 @@ +/* gunicode.h - Unicode manipulation functions + * + * Copyright (C) 1999, 2000 Tom Tromey + * Copyright 2000, 2005 Red Hat, Inc. + */ + +#pragma once + +#include +#include +#include + +char *utf8_prev_char (const char *p); + +extern const char utf8_skip_data[256]; + +/** + * g_utf8_next_char: + * @p: Pointer to the start of a valid UTF-8 character + * + * Skips to the next character in a UTF-8 string. The string must be + * valid; this macro is as fast as possible, and has no error-checking. + * You would use this macro to iterate over a string character by + * character. The macro returns the start of the next UTF-8 character. + * Before using this macro, use g_utf8_validate() to validate strings + * that may contain invalid UTF-8. + */ +#define utf8_next_char(p) (char *)((p) + utf8_skip_data[*(const unsigned char *)(p)]) + +bool unichar_iswide (uint32_t c); diff --git a/src/libbasic/hash-funcs.c b/src/libbasic/hash-funcs.c new file mode 100644 index 0000000000..c3a4a011b5 --- /dev/null +++ b/src/libbasic/hash-funcs.c @@ -0,0 +1,81 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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 . +***/ + +#include "hash-funcs.h" + +void string_hash_func(const void *p, struct siphash *state) { + siphash24_compress(p, strlen(p) + 1, state); +} + +int string_compare_func(const void *a, const void *b) { + return strcmp(a, b); +} + +const struct hash_ops string_hash_ops = { + .hash = string_hash_func, + .compare = string_compare_func +}; + +void trivial_hash_func(const void *p, struct siphash *state) { + siphash24_compress(&p, sizeof(p), state); +} + +int trivial_compare_func(const void *a, const void *b) { + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops trivial_hash_ops = { + .hash = trivial_hash_func, + .compare = trivial_compare_func +}; + +void uint64_hash_func(const void *p, struct siphash *state) { + siphash24_compress(p, sizeof(uint64_t), state); +} + +int uint64_compare_func(const void *_a, const void *_b) { + uint64_t a, b; + a = *(const uint64_t*) _a; + b = *(const uint64_t*) _b; + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops uint64_hash_ops = { + .hash = uint64_hash_func, + .compare = uint64_compare_func +}; + +#if SIZEOF_DEV_T != 8 +void devt_hash_func(const void *p, struct siphash *state) { + siphash24_compress(p, sizeof(dev_t), state); +} + +int devt_compare_func(const void *_a, const void *_b) { + dev_t a, b; + a = *(const dev_t*) _a; + b = *(const dev_t*) _b; + return a < b ? -1 : (a > b ? 1 : 0); +} + +const struct hash_ops devt_hash_ops = { + .hash = devt_hash_func, + .compare = devt_compare_func +}; +#endif diff --git a/src/libbasic/hash-funcs.h b/src/libbasic/hash-funcs.h new file mode 100644 index 0000000000..299189d143 --- /dev/null +++ b/src/libbasic/hash-funcs.h @@ -0,0 +1,65 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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 . +***/ + +#include "macro.h" +#include "siphash24.h" + +typedef void (*hash_func_t)(const void *p, struct siphash *state); +typedef int (*compare_func_t)(const void *a, const void *b); + +struct hash_ops { + hash_func_t hash; + compare_func_t compare; +}; + +void string_hash_func(const void *p, struct siphash *state); +int string_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops string_hash_ops; + +/* This will compare the passed pointers directly, and will not + * dereference them. This is hence not useful for strings or + * suchlike. */ +void trivial_hash_func(const void *p, struct siphash *state); +int trivial_compare_func(const void *a, const void *b) _const_; +extern const struct hash_ops trivial_hash_ops; + +/* 32bit values we can always just embed in the pointer itself, but + * in order to support 32bit archs we need store 64bit values + * indirectly, since they don't fit in a pointer. */ +void uint64_hash_func(const void *p, struct siphash *state); +int uint64_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops uint64_hash_ops; + +/* On some archs dev_t is 32bit, and on others 64bit. And sometimes + * it's 64bit on 32bit archs, and sometimes 32bit on 64bit archs. Yuck! */ +#if SIZEOF_DEV_T != 8 +void devt_hash_func(const void *p, struct siphash *state) _pure_; +int devt_compare_func(const void *a, const void *b) _pure_; +extern const struct hash_ops devt_hash_ops = { + .hash = devt_hash_func, + .compare = devt_compare_func +}; +#else +#define devt_hash_func uint64_hash_func +#define devt_compare_func uint64_compare_func +#define devt_hash_ops uint64_hash_ops +#endif diff --git a/src/libbasic/hashmap.c b/src/libbasic/hashmap.c new file mode 100644 index 0000000000..6f1a049d47 --- /dev/null +++ b/src/libbasic/hashmap.c @@ -0,0 +1,1805 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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 . +***/ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "hashmap.h" +#include "macro.h" +#include "mempool.h" +#include "process-util.h" +#include "random-util.h" +#include "set.h" +#include "siphash24.h" +#include "strv.h" +#include "util.h" + +#ifdef ENABLE_DEBUG_HASHMAP +#include +#include "list.h" +#endif + +/* + * Implementation of hashmaps. + * Addressing: open + * - uses less RAM compared to closed addressing (chaining), because + * our entries are small (especially in Sets, which tend to contain + * the majority of entries in systemd). + * Collision resolution: Robin Hood + * - tends to equalize displacement of entries from their optimal buckets. + * Probe sequence: linear + * - though theoretically worse than random probing/uniform hashing/double + * hashing, it is good for cache locality. + * + * References: + * Celis, P. 1986. Robin Hood Hashing. + * Ph.D. Dissertation. University of Waterloo, Waterloo, Ont., Canada, Canada. + * https://cs.uwaterloo.ca/research/tr/1986/CS-86-14.pdf + * - The results are derived for random probing. Suggests deletion with + * tombstones and two mean-centered search methods. None of that works + * well for linear probing. + * + * Janson, S. 2005. Individual displacements for linear probing hashing with different insertion policies. + * ACM Trans. Algorithms 1, 2 (October 2005), 177-213. + * DOI=10.1145/1103963.1103964 http://doi.acm.org/10.1145/1103963.1103964 + * http://www.math.uu.se/~svante/papers/sj157.pdf + * - Applies to Robin Hood with linear probing. Contains remarks on + * the unsuitability of mean-centered search with linear probing. + * + * Viola, A. 2005. Exact distribution of individual displacements in linear probing hashing. + * ACM Trans. Algorithms 1, 2 (October 2005), 214-242. + * DOI=10.1145/1103963.1103965 http://doi.acm.org/10.1145/1103963.1103965 + * - Similar to Janson. Note that Viola writes about C_{m,n} (number of probes + * in a successful search), and Janson writes about displacement. C = d + 1. + * + * Goossaert, E. 2013. Robin Hood hashing: backward shift deletion. + * http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/ + * - Explanation of backward shift deletion with pictures. + * + * Khuong, P. 2013. The Other Robin Hood Hashing. + * http://www.pvk.ca/Blog/2013/11/26/the-other-robin-hood-hashing/ + * - Short summary of random vs. linear probing, and tombstones vs. backward shift. + */ + +/* + * XXX Ideas for improvement: + * For unordered hashmaps, randomize iteration order, similarly to Perl: + * http://blog.booking.com/hardening-perls-hash-function.html + */ + +/* INV_KEEP_FREE = 1 / (1 - max_load_factor) + * e.g. 1 / (1 - 0.8) = 5 ... keep one fifth of the buckets free. */ +#define INV_KEEP_FREE 5U + +/* Fields common to entries of all hashmap/set types */ +struct hashmap_base_entry { + const void *key; +}; + +/* Entry types for specific hashmap/set types + * hashmap_base_entry must be at the beginning of each entry struct. */ + +struct plain_hashmap_entry { + struct hashmap_base_entry b; + void *value; +}; + +struct ordered_hashmap_entry { + struct plain_hashmap_entry p; + unsigned iterate_next, iterate_previous; +}; + +struct set_entry { + struct hashmap_base_entry b; +}; + +/* In several functions it is advantageous to have the hash table extended + * virtually by a couple of additional buckets. We reserve special index values + * for these "swap" buckets. */ +#define _IDX_SWAP_BEGIN (UINT_MAX - 3) +#define IDX_PUT (_IDX_SWAP_BEGIN + 0) +#define IDX_TMP (_IDX_SWAP_BEGIN + 1) +#define _IDX_SWAP_END (_IDX_SWAP_BEGIN + 2) + +#define IDX_FIRST (UINT_MAX - 1) /* special index for freshly initialized iterators */ +#define IDX_NIL UINT_MAX /* special index value meaning "none" or "end" */ + +assert_cc(IDX_FIRST == _IDX_SWAP_END); +assert_cc(IDX_FIRST == _IDX_ITERATOR_FIRST); + +/* Storage space for the "swap" buckets. + * All entry types can fit into a ordered_hashmap_entry. */ +struct swap_entries { + struct ordered_hashmap_entry e[_IDX_SWAP_END - _IDX_SWAP_BEGIN]; +}; + +/* Distance from Initial Bucket */ +typedef uint8_t dib_raw_t; +#define DIB_RAW_OVERFLOW ((dib_raw_t)0xfdU) /* indicates DIB value is greater than representable */ +#define DIB_RAW_REHASH ((dib_raw_t)0xfeU) /* entry yet to be rehashed during in-place resize */ +#define DIB_RAW_FREE ((dib_raw_t)0xffU) /* a free bucket */ +#define DIB_RAW_INIT ((char)DIB_RAW_FREE) /* a byte to memset a DIB store with when initializing */ + +#define DIB_FREE UINT_MAX + +#ifdef ENABLE_DEBUG_HASHMAP +struct hashmap_debug_info { + LIST_FIELDS(struct hashmap_debug_info, debug_list); + unsigned max_entries; /* high watermark of n_entries */ + + /* who allocated this hashmap */ + int line; + const char *file; + const char *func; + + /* fields to detect modification while iterating */ + unsigned put_count; /* counts puts into the hashmap */ + unsigned rem_count; /* counts removals from hashmap */ + unsigned last_rem_idx; /* remembers last removal index */ +}; + +/* Tracks all existing hashmaps. Get at it from gdb. See sd_dump_hashmaps.py */ +static LIST_HEAD(struct hashmap_debug_info, hashmap_debug_list); +static pthread_mutex_t hashmap_debug_list_mutex = PTHREAD_MUTEX_INITIALIZER; + +#define HASHMAP_DEBUG_FIELDS struct hashmap_debug_info debug; + +#else /* !ENABLE_DEBUG_HASHMAP */ +#define HASHMAP_DEBUG_FIELDS +#endif /* ENABLE_DEBUG_HASHMAP */ + +enum HashmapType { + HASHMAP_TYPE_PLAIN, + HASHMAP_TYPE_ORDERED, + HASHMAP_TYPE_SET, + _HASHMAP_TYPE_MAX +}; + +struct _packed_ indirect_storage { + char *storage; /* where buckets and DIBs are stored */ + uint8_t hash_key[HASH_KEY_SIZE]; /* hash key; changes during resize */ + + unsigned n_entries; /* number of stored entries */ + unsigned n_buckets; /* number of buckets */ + + unsigned idx_lowest_entry; /* Index below which all buckets are free. + Makes "while(hashmap_steal_first())" loops + O(n) instead of O(n^2) for unordered hashmaps. */ + uint8_t _pad[3]; /* padding for the whole HashmapBase */ + /* The bitfields in HashmapBase complete the alignment of the whole thing. */ +}; + +struct direct_storage { + /* This gives us 39 bytes on 64bit, or 35 bytes on 32bit. + * That's room for 4 set_entries + 4 DIB bytes + 3 unused bytes on 64bit, + * or 7 set_entries + 7 DIB bytes + 0 unused bytes on 32bit. */ + char storage[sizeof(struct indirect_storage)]; +}; + +#define DIRECT_BUCKETS(entry_t) \ + (sizeof(struct direct_storage) / (sizeof(entry_t) + sizeof(dib_raw_t))) + +/* We should be able to store at least one entry directly. */ +assert_cc(DIRECT_BUCKETS(struct ordered_hashmap_entry) >= 1); + +/* We have 3 bits for n_direct_entries. */ +assert_cc(DIRECT_BUCKETS(struct set_entry) < (1 << 3)); + +/* Hashmaps with directly stored entries all use this shared hash key. + * It's no big deal if the key is guessed, because there can be only + * a handful of directly stored entries in a hashmap. When a hashmap + * outgrows direct storage, it gets its own key for indirect storage. */ +static uint8_t shared_hash_key[HASH_KEY_SIZE]; +static bool shared_hash_key_initialized; + +/* Fields that all hashmap/set types must have */ +struct HashmapBase { + const struct hash_ops *hash_ops; /* hash and compare ops to use */ + + union _packed_ { + struct indirect_storage indirect; /* if has_indirect */ + struct direct_storage direct; /* if !has_indirect */ + }; + + enum HashmapType type:2; /* HASHMAP_TYPE_* */ + bool has_indirect:1; /* whether indirect storage is used */ + unsigned n_direct_entries:3; /* Number of entries in direct storage. + * Only valid if !has_indirect. */ + bool from_pool:1; /* whether was allocated from mempool */ + HASHMAP_DEBUG_FIELDS /* optional hashmap_debug_info */ +}; + +/* Specific hash types + * HashmapBase must be at the beginning of each hashmap struct. */ + +struct Hashmap { + struct HashmapBase b; +}; + +struct OrderedHashmap { + struct HashmapBase b; + unsigned iterate_list_head, iterate_list_tail; +}; + +struct Set { + struct HashmapBase b; +}; + +DEFINE_MEMPOOL(hashmap_pool, Hashmap, 8); +DEFINE_MEMPOOL(ordered_hashmap_pool, OrderedHashmap, 8); +/* No need for a separate Set pool */ +assert_cc(sizeof(Hashmap) == sizeof(Set)); + +struct hashmap_type_info { + size_t head_size; + size_t entry_size; + struct mempool *mempool; + unsigned n_direct_buckets; +}; + +static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = { + [HASHMAP_TYPE_PLAIN] = { + .head_size = sizeof(Hashmap), + .entry_size = sizeof(struct plain_hashmap_entry), + .mempool = &hashmap_pool, + .n_direct_buckets = DIRECT_BUCKETS(struct plain_hashmap_entry), + }, + [HASHMAP_TYPE_ORDERED] = { + .head_size = sizeof(OrderedHashmap), + .entry_size = sizeof(struct ordered_hashmap_entry), + .mempool = &ordered_hashmap_pool, + .n_direct_buckets = DIRECT_BUCKETS(struct ordered_hashmap_entry), + }, + [HASHMAP_TYPE_SET] = { + .head_size = sizeof(Set), + .entry_size = sizeof(struct set_entry), + .mempool = &hashmap_pool, + .n_direct_buckets = DIRECT_BUCKETS(struct set_entry), + }, +}; + +static unsigned n_buckets(HashmapBase *h) { + return h->has_indirect ? h->indirect.n_buckets + : hashmap_type_info[h->type].n_direct_buckets; +} + +static unsigned n_entries(HashmapBase *h) { + return h->has_indirect ? h->indirect.n_entries + : h->n_direct_entries; +} + +static void n_entries_inc(HashmapBase *h) { + if (h->has_indirect) + h->indirect.n_entries++; + else + h->n_direct_entries++; +} + +static void n_entries_dec(HashmapBase *h) { + if (h->has_indirect) + h->indirect.n_entries--; + else + h->n_direct_entries--; +} + +static char *storage_ptr(HashmapBase *h) { + return h->has_indirect ? h->indirect.storage + : h->direct.storage; +} + +static uint8_t *hash_key(HashmapBase *h) { + return h->has_indirect ? h->indirect.hash_key + : shared_hash_key; +} + +static unsigned base_bucket_hash(HashmapBase *h, const void *p) { + struct siphash state; + uint64_t hash; + + siphash24_init(&state, hash_key(h)); + + h->hash_ops->hash(p, &state); + + hash = siphash24_finalize(&state); + + return (unsigned) (hash % n_buckets(h)); +} +#define bucket_hash(h, p) base_bucket_hash(HASHMAP_BASE(h), p) + +static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) { + static uint8_t current[HASH_KEY_SIZE]; + static bool current_initialized = false; + + /* Returns a hash function key to use. In order to keep things + * fast we will not generate a new key each time we allocate a + * new hash table. Instead, we'll just reuse the most recently + * generated one, except if we never generated one or when we + * are rehashing an entire hash table because we reached a + * fill level */ + + if (!current_initialized || !reuse_is_ok) { + random_bytes(current, sizeof(current)); + current_initialized = true; + } + + memcpy(hash_key, current, sizeof(current)); +} + +static struct hashmap_base_entry *bucket_at(HashmapBase *h, unsigned idx) { + return (struct hashmap_base_entry*) + (storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size); +} + +static struct plain_hashmap_entry *plain_bucket_at(Hashmap *h, unsigned idx) { + return (struct plain_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx); +} + +static struct ordered_hashmap_entry *ordered_bucket_at(OrderedHashmap *h, unsigned idx) { + return (struct ordered_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx); +} + +static struct set_entry *set_bucket_at(Set *h, unsigned idx) { + return (struct set_entry*) bucket_at(HASHMAP_BASE(h), idx); +} + +static struct ordered_hashmap_entry *bucket_at_swap(struct swap_entries *swap, unsigned idx) { + return &swap->e[idx - _IDX_SWAP_BEGIN]; +} + +/* Returns a pointer to the bucket at index idx. + * Understands real indexes and swap indexes, hence "_virtual". */ +static struct hashmap_base_entry *bucket_at_virtual(HashmapBase *h, struct swap_entries *swap, + unsigned idx) { + if (idx < _IDX_SWAP_BEGIN) + return bucket_at(h, idx); + + if (idx < _IDX_SWAP_END) + return &bucket_at_swap(swap, idx)->p.b; + + assert_not_reached("Invalid index"); +} + +static dib_raw_t *dib_raw_ptr(HashmapBase *h) { + return (dib_raw_t*) + (storage_ptr(h) + hashmap_type_info[h->type].entry_size * n_buckets(h)); +} + +static unsigned bucket_distance(HashmapBase *h, unsigned idx, unsigned from) { + return idx >= from ? idx - from + : n_buckets(h) + idx - from; +} + +static unsigned bucket_calculate_dib(HashmapBase *h, unsigned idx, dib_raw_t raw_dib) { + unsigned initial_bucket; + + if (raw_dib == DIB_RAW_FREE) + return DIB_FREE; + + if (_likely_(raw_dib < DIB_RAW_OVERFLOW)) + return raw_dib; + + /* + * Having an overflow DIB value is very unlikely. The hash function + * would have to be bad. For example, in a table of size 2^24 filled + * to load factor 0.9 the maximum observed DIB is only about 60. + * In theory (assuming I used Maxima correctly), for an infinite size + * hash table with load factor 0.8 the probability of a given entry + * having DIB > 40 is 1.9e-8. + * This returns the correct DIB value by recomputing the hash value in + * the unlikely case. XXX Hitting this case could be a hint to rehash. + */ + initial_bucket = bucket_hash(h, bucket_at(h, idx)->key); + return bucket_distance(h, idx, initial_bucket); +} + +static void bucket_set_dib(HashmapBase *h, unsigned idx, unsigned dib) { + dib_raw_ptr(h)[idx] = dib != DIB_FREE ? MIN(dib, DIB_RAW_OVERFLOW) : DIB_RAW_FREE; +} + +static unsigned skip_free_buckets(HashmapBase *h, unsigned idx) { + dib_raw_t *dibs; + + dibs = dib_raw_ptr(h); + + for ( ; idx < n_buckets(h); idx++) + if (dibs[idx] != DIB_RAW_FREE) + return idx; + + return IDX_NIL; +} + +static void bucket_mark_free(HashmapBase *h, unsigned idx) { + memzero(bucket_at(h, idx), hashmap_type_info[h->type].entry_size); + bucket_set_dib(h, idx, DIB_FREE); +} + +static void bucket_move_entry(HashmapBase *h, struct swap_entries *swap, + unsigned from, unsigned to) { + struct hashmap_base_entry *e_from, *e_to; + + assert(from != to); + + e_from = bucket_at_virtual(h, swap, from); + e_to = bucket_at_virtual(h, swap, to); + + memcpy(e_to, e_from, hashmap_type_info[h->type].entry_size); + + if (h->type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*) h; + struct ordered_hashmap_entry *le, *le_to; + + le_to = (struct ordered_hashmap_entry*) e_to; + + if (le_to->iterate_next != IDX_NIL) { + le = (struct ordered_hashmap_entry*) + bucket_at_virtual(h, swap, le_to->iterate_next); + le->iterate_previous = to; + } + + if (le_to->iterate_previous != IDX_NIL) { + le = (struct ordered_hashmap_entry*) + bucket_at_virtual(h, swap, le_to->iterate_previous); + le->iterate_next = to; + } + + if (lh->iterate_list_head == from) + lh->iterate_list_head = to; + if (lh->iterate_list_tail == from) + lh->iterate_list_tail = to; + } +} + +static unsigned next_idx(HashmapBase *h, unsigned idx) { + return (idx + 1U) % n_buckets(h); +} + +static unsigned prev_idx(HashmapBase *h, unsigned idx) { + return (n_buckets(h) + idx - 1U) % n_buckets(h); +} + +static void *entry_value(HashmapBase *h, struct hashmap_base_entry *e) { + switch (h->type) { + + case HASHMAP_TYPE_PLAIN: + case HASHMAP_TYPE_ORDERED: + return ((struct plain_hashmap_entry*)e)->value; + + case HASHMAP_TYPE_SET: + return (void*) e->key; + + default: + assert_not_reached("Unknown hashmap type"); + } +} + +static void base_remove_entry(HashmapBase *h, unsigned idx) { + unsigned left, right, prev, dib; + dib_raw_t raw_dib, *dibs; + + dibs = dib_raw_ptr(h); + assert(dibs[idx] != DIB_RAW_FREE); + +#ifdef ENABLE_DEBUG_HASHMAP + h->debug.rem_count++; + h->debug.last_rem_idx = idx; +#endif + + left = idx; + /* Find the stop bucket ("right"). It is either free or has DIB == 0. */ + for (right = next_idx(h, left); ; right = next_idx(h, right)) { + raw_dib = dibs[right]; + if (raw_dib == 0 || raw_dib == DIB_RAW_FREE) + break; + + /* The buckets are not supposed to be all occupied and with DIB > 0. + * That would mean we could make everyone better off by shifting them + * backward. This scenario is impossible. */ + assert(left != right); + } + + if (h->type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*) h; + struct ordered_hashmap_entry *le = ordered_bucket_at(lh, idx); + + if (le->iterate_next != IDX_NIL) + ordered_bucket_at(lh, le->iterate_next)->iterate_previous = le->iterate_previous; + else + lh->iterate_list_tail = le->iterate_previous; + + if (le->iterate_previous != IDX_NIL) + ordered_bucket_at(lh, le->iterate_previous)->iterate_next = le->iterate_next; + else + lh->iterate_list_head = le->iterate_next; + } + + /* Now shift all buckets in the interval (left, right) one step backwards */ + for (prev = left, left = next_idx(h, left); left != right; + prev = left, left = next_idx(h, left)) { + dib = bucket_calculate_dib(h, left, dibs[left]); + assert(dib != 0); + bucket_move_entry(h, NULL, left, prev); + bucket_set_dib(h, prev, dib - 1); + } + + bucket_mark_free(h, prev); + n_entries_dec(h); +} +#define remove_entry(h, idx) base_remove_entry(HASHMAP_BASE(h), idx) + +static unsigned hashmap_iterate_in_insertion_order(OrderedHashmap *h, Iterator *i) { + struct ordered_hashmap_entry *e; + unsigned idx; + + assert(h); + assert(i); + + if (i->idx == IDX_NIL) + goto at_end; + + if (i->idx == IDX_FIRST && h->iterate_list_head == IDX_NIL) + goto at_end; + + if (i->idx == IDX_FIRST) { + idx = h->iterate_list_head; + e = ordered_bucket_at(h, idx); + } else { + idx = i->idx; + e = ordered_bucket_at(h, idx); + /* + * We allow removing the current entry while iterating, but removal may cause + * a backward shift. The next entry may thus move one bucket to the left. + * To detect when it happens, we remember the key pointer of the entry we were + * going to iterate next. If it does not match, there was a backward shift. + */ + if (e->p.b.key != i->next_key) { + idx = prev_idx(HASHMAP_BASE(h), idx); + e = ordered_bucket_at(h, idx); + } + assert(e->p.b.key == i->next_key); + } + +#ifdef ENABLE_DEBUG_HASHMAP + i->prev_idx = idx; +#endif + + if (e->iterate_next != IDX_NIL) { + struct ordered_hashmap_entry *n; + i->idx = e->iterate_next; + n = ordered_bucket_at(h, i->idx); + i->next_key = n->p.b.key; + } else + i->idx = IDX_NIL; + + return idx; + +at_end: + i->idx = IDX_NIL; + return IDX_NIL; +} + +static unsigned hashmap_iterate_in_internal_order(HashmapBase *h, Iterator *i) { + unsigned idx; + + assert(h); + assert(i); + + if (i->idx == IDX_NIL) + goto at_end; + + if (i->idx == IDX_FIRST) { + /* fast forward to the first occupied bucket */ + if (h->has_indirect) { + i->idx = skip_free_buckets(h, h->indirect.idx_lowest_entry); + h->indirect.idx_lowest_entry = i->idx; + } else + i->idx = skip_free_buckets(h, 0); + + if (i->idx == IDX_NIL) + goto at_end; + } else { + struct hashmap_base_entry *e; + + assert(i->idx > 0); + + e = bucket_at(h, i->idx); + /* + * We allow removing the current entry while iterating, but removal may cause + * a backward shift. The next entry may thus move one bucket to the left. + * To detect when it happens, we remember the key pointer of the entry we were + * going to iterate next. If it does not match, there was a backward shift. + */ + if (e->key != i->next_key) + e = bucket_at(h, --i->idx); + + assert(e->key == i->next_key); + } + + idx = i->idx; +#ifdef ENABLE_DEBUG_HASHMAP + i->prev_idx = idx; +#endif + + i->idx = skip_free_buckets(h, i->idx + 1); + if (i->idx != IDX_NIL) + i->next_key = bucket_at(h, i->idx)->key; + else + i->idx = IDX_NIL; + + return idx; + +at_end: + i->idx = IDX_NIL; + return IDX_NIL; +} + +static unsigned hashmap_iterate_entry(HashmapBase *h, Iterator *i) { + if (!h) { + i->idx = IDX_NIL; + return IDX_NIL; + } + +#ifdef ENABLE_DEBUG_HASHMAP + if (i->idx == IDX_FIRST) { + i->put_count = h->debug.put_count; + i->rem_count = h->debug.rem_count; + } else { + /* While iterating, must not add any new entries */ + assert(i->put_count == h->debug.put_count); + /* ... or remove entries other than the current one */ + assert(i->rem_count == h->debug.rem_count || + (i->rem_count == h->debug.rem_count - 1 && + i->prev_idx == h->debug.last_rem_idx)); + /* Reset our removals counter */ + i->rem_count = h->debug.rem_count; + } +#endif + + return h->type == HASHMAP_TYPE_ORDERED ? hashmap_iterate_in_insertion_order((OrderedHashmap*) h, i) + : hashmap_iterate_in_internal_order(h, i); +} + +bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key) { + struct hashmap_base_entry *e; + void *data; + unsigned idx; + + idx = hashmap_iterate_entry(h, i); + if (idx == IDX_NIL) { + if (value) + *value = NULL; + if (key) + *key = NULL; + + return false; + } + + e = bucket_at(h, idx); + data = entry_value(h, e); + if (value) + *value = data; + if (key) + *key = e->key; + + return true; +} + +bool set_iterate(Set *s, Iterator *i, void **value) { + return internal_hashmap_iterate(HASHMAP_BASE(s), i, value, NULL); +} + +#define HASHMAP_FOREACH_IDX(idx, h, i) \ + for ((i) = ITERATOR_FIRST, (idx) = hashmap_iterate_entry((h), &(i)); \ + (idx != IDX_NIL); \ + (idx) = hashmap_iterate_entry((h), &(i))) + +static void reset_direct_storage(HashmapBase *h) { + const struct hashmap_type_info *hi = &hashmap_type_info[h->type]; + void *p; + + assert(!h->has_indirect); + + p = mempset(h->direct.storage, 0, hi->entry_size * hi->n_direct_buckets); + memset(p, DIB_RAW_INIT, sizeof(dib_raw_t) * hi->n_direct_buckets); +} + +static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enum HashmapType type HASHMAP_DEBUG_PARAMS) { + HashmapBase *h; + const struct hashmap_type_info *hi = &hashmap_type_info[type]; + bool use_pool; + + use_pool = is_main_thread(); + + h = use_pool ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size); + + if (!h) + return NULL; + + h->type = type; + h->from_pool = use_pool; + h->hash_ops = hash_ops ? hash_ops : &trivial_hash_ops; + + if (type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*)h; + lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL; + } + + reset_direct_storage(h); + + if (!shared_hash_key_initialized) { + random_bytes(shared_hash_key, sizeof(shared_hash_key)); + shared_hash_key_initialized= true; + } + +#ifdef ENABLE_DEBUG_HASHMAP + h->debug.func = func; + h->debug.file = file; + h->debug.line = line; + assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0); + LIST_PREPEND(debug_list, hashmap_debug_list, &h->debug); + assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0); +#endif + + return h; +} + +Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return (Hashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS); +} + +OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return (OrderedHashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS); +} + +Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return (Set*) hashmap_base_new(hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS); +} + +static int hashmap_base_ensure_allocated(HashmapBase **h, const struct hash_ops *hash_ops, + enum HashmapType type HASHMAP_DEBUG_PARAMS) { + HashmapBase *q; + + assert(h); + + if (*h) + return 0; + + q = hashmap_base_new(hash_ops, type HASHMAP_DEBUG_PASS_ARGS); + if (!q) + return -ENOMEM; + + *h = q; + return 0; +} + +int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS); +} + +int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS); +} + +int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) { + return hashmap_base_ensure_allocated((HashmapBase**)s, hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS); +} + +static void hashmap_free_no_clear(HashmapBase *h) { + assert(!h->has_indirect); + assert(!h->n_direct_entries); + +#ifdef ENABLE_DEBUG_HASHMAP + assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0); + LIST_REMOVE(debug_list, hashmap_debug_list, &h->debug); + assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0); +#endif + + if (h->from_pool) + mempool_free_tile(hashmap_type_info[h->type].mempool, h); + else + free(h); +} + +HashmapBase *internal_hashmap_free(HashmapBase *h) { + + /* Free the hashmap, but nothing in it */ + + if (h) { + internal_hashmap_clear(h); + hashmap_free_no_clear(h); + } + + return NULL; +} + +HashmapBase *internal_hashmap_free_free(HashmapBase *h) { + + /* Free the hashmap and all data objects in it, but not the + * keys */ + + if (h) { + internal_hashmap_clear_free(h); + hashmap_free_no_clear(h); + } + + return NULL; +} + +Hashmap *hashmap_free_free_free(Hashmap *h) { + + /* Free the hashmap and all data and key objects in it */ + + if (h) { + hashmap_clear_free_free(h); + hashmap_free_no_clear(HASHMAP_BASE(h)); + } + + return NULL; +} + +void internal_hashmap_clear(HashmapBase *h) { + if (!h) + return; + + if (h->has_indirect) { + free(h->indirect.storage); + h->has_indirect = false; + } + + h->n_direct_entries = 0; + reset_direct_storage(h); + + if (h->type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*) h; + lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL; + } +} + +void internal_hashmap_clear_free(HashmapBase *h) { + unsigned idx; + + if (!h) + return; + + for (idx = skip_free_buckets(h, 0); idx != IDX_NIL; + idx = skip_free_buckets(h, idx + 1)) + free(entry_value(h, bucket_at(h, idx))); + + internal_hashmap_clear(h); +} + +void hashmap_clear_free_free(Hashmap *h) { + unsigned idx; + + if (!h) + return; + + for (idx = skip_free_buckets(HASHMAP_BASE(h), 0); idx != IDX_NIL; + idx = skip_free_buckets(HASHMAP_BASE(h), idx + 1)) { + struct plain_hashmap_entry *e = plain_bucket_at(h, idx); + free((void*)e->b.key); + free(e->value); + } + + internal_hashmap_clear(HASHMAP_BASE(h)); +} + +static int resize_buckets(HashmapBase *h, unsigned entries_add); + +/* + * Finds an empty bucket to put an entry into, starting the scan at 'idx'. + * Performs Robin Hood swaps as it goes. The entry to put must be placed + * by the caller into swap slot IDX_PUT. + * If used for in-place resizing, may leave a displaced entry in swap slot + * IDX_PUT. Caller must rehash it next. + * Returns: true if it left a displaced entry to rehash next in IDX_PUT, + * false otherwise. + */ +static bool hashmap_put_robin_hood(HashmapBase *h, unsigned idx, + struct swap_entries *swap) { + dib_raw_t raw_dib, *dibs; + unsigned dib, distance; + +#ifdef ENABLE_DEBUG_HASHMAP + h->debug.put_count++; +#endif + + dibs = dib_raw_ptr(h); + + for (distance = 0; ; distance++) { + raw_dib = dibs[idx]; + if (raw_dib == DIB_RAW_FREE || raw_dib == DIB_RAW_REHASH) { + if (raw_dib == DIB_RAW_REHASH) + bucket_move_entry(h, swap, idx, IDX_TMP); + + if (h->has_indirect && h->indirect.idx_lowest_entry > idx) + h->indirect.idx_lowest_entry = idx; + + bucket_set_dib(h, idx, distance); + bucket_move_entry(h, swap, IDX_PUT, idx); + if (raw_dib == DIB_RAW_REHASH) { + bucket_move_entry(h, swap, IDX_TMP, IDX_PUT); + return true; + } + + return false; + } + + dib = bucket_calculate_dib(h, idx, raw_dib); + + if (dib < distance) { + /* Found a wealthier entry. Go Robin Hood! */ + bucket_set_dib(h, idx, distance); + + /* swap the entries */ + bucket_move_entry(h, swap, idx, IDX_TMP); + bucket_move_entry(h, swap, IDX_PUT, idx); + bucket_move_entry(h, swap, IDX_TMP, IDX_PUT); + + distance = dib; + } + + idx = next_idx(h, idx); + } +} + +/* + * Puts an entry into a hashmap, boldly - no check whether key already exists. + * The caller must place the entry (only its key and value, not link indexes) + * in swap slot IDX_PUT. + * Caller must ensure: the key does not exist yet in the hashmap. + * that resize is not needed if !may_resize. + * Returns: 1 if entry was put successfully. + * -ENOMEM if may_resize==true and resize failed with -ENOMEM. + * Cannot return -ENOMEM if !may_resize. + */ +static int hashmap_base_put_boldly(HashmapBase *h, unsigned idx, + struct swap_entries *swap, bool may_resize) { + struct ordered_hashmap_entry *new_entry; + int r; + + assert(idx < n_buckets(h)); + + new_entry = bucket_at_swap(swap, IDX_PUT); + + if (may_resize) { + r = resize_buckets(h, 1); + if (r < 0) + return r; + if (r > 0) + idx = bucket_hash(h, new_entry->p.b.key); + } + assert(n_entries(h) < n_buckets(h)); + + if (h->type == HASHMAP_TYPE_ORDERED) { + OrderedHashmap *lh = (OrderedHashmap*) h; + + new_entry->iterate_next = IDX_NIL; + new_entry->iterate_previous = lh->iterate_list_tail; + + if (lh->iterate_list_tail != IDX_NIL) { + struct ordered_hashmap_entry *old_tail; + + old_tail = ordered_bucket_at(lh, lh->iterate_list_tail); + assert(old_tail->iterate_next == IDX_NIL); + old_tail->iterate_next = IDX_PUT; + } + + lh->iterate_list_tail = IDX_PUT; + if (lh->iterate_list_head == IDX_NIL) + lh->iterate_list_head = IDX_PUT; + } + + assert_se(hashmap_put_robin_hood(h, idx, swap) == false); + + n_entries_inc(h); +#ifdef ENABLE_DEBUG_HASHMAP + h->debug.max_entries = MAX(h->debug.max_entries, n_entries(h)); +#endif + + return 1; +} +#define hashmap_put_boldly(h, idx, swap, may_resize) \ + hashmap_base_put_boldly(HASHMAP_BASE(h), idx, swap, may_resize) + +/* + * Returns 0 if resize is not needed. + * 1 if successfully resized. + * -ENOMEM on allocation failure. + */ +static int resize_buckets(HashmapBase *h, unsigned entries_add) { + struct swap_entries swap; + char *new_storage; + dib_raw_t *old_dibs, *new_dibs; + const struct hashmap_type_info *hi; + unsigned idx, optimal_idx; + unsigned old_n_buckets, new_n_buckets, n_rehashed, new_n_entries; + uint8_t new_shift; + bool rehash_next; + + assert(h); + + hi = &hashmap_type_info[h->type]; + new_n_entries = n_entries(h) + entries_add; + + /* overflow? */ + if (_unlikely_(new_n_entries < entries_add)) + return -ENOMEM; + + /* For direct storage we allow 100% load, because it's tiny. */ + if (!h->has_indirect && new_n_entries <= hi->n_direct_buckets) + return 0; + + /* + * Load factor = n/m = 1 - (1/INV_KEEP_FREE). + * From it follows: m = n + n/(INV_KEEP_FREE - 1) + */ + new_n_buckets = new_n_entries + new_n_entries / (INV_KEEP_FREE - 1); + /* overflow? */ + if (_unlikely_(new_n_buckets < new_n_entries)) + return -ENOMEM; + + if (_unlikely_(new_n_buckets > UINT_MAX / (hi->entry_size + sizeof(dib_raw_t)))) + return -ENOMEM; + + old_n_buckets = n_buckets(h); + + if (_likely_(new_n_buckets <= old_n_buckets)) + return 0; + + new_shift = log2u_round_up(MAX( + new_n_buckets * (hi->entry_size + sizeof(dib_raw_t)), + 2 * sizeof(struct direct_storage))); + + /* Realloc storage (buckets and DIB array). */ + new_storage = realloc(h->has_indirect ? h->indirect.storage : NULL, + 1U << new_shift); + if (!new_storage) + return -ENOMEM; + + /* Must upgrade direct to indirect storage. */ + if (!h->has_indirect) { + memcpy(new_storage, h->direct.storage, + old_n_buckets * (hi->entry_size + sizeof(dib_raw_t))); + h->indirect.n_entries = h->n_direct_entries; + h->indirect.idx_lowest_entry = 0; + h->n_direct_entries = 0; + } + + /* Get a new hash key. If we've just upgraded to indirect storage, + * allow reusing a previously generated key. It's still a different key + * from the shared one that we used for direct storage. */ + get_hash_key(h->indirect.hash_key, !h->has_indirect); + + h->has_indirect = true; + h->indirect.storage = new_storage; + h->indirect.n_buckets = (1U << new_shift) / + (hi->entry_size + sizeof(dib_raw_t)); + + old_dibs = (dib_raw_t*)(new_storage + hi->entry_size * old_n_buckets); + new_dibs = dib_raw_ptr(h); + + /* + * Move the DIB array to the new place, replacing valid DIB values with + * DIB_RAW_REHASH to indicate all of the used buckets need rehashing. + * Note: Overlap is not possible, because we have at least doubled the + * number of buckets and dib_raw_t is smaller than any entry type. + */ + for (idx = 0; idx < old_n_buckets; idx++) { + assert(old_dibs[idx] != DIB_RAW_REHASH); + new_dibs[idx] = old_dibs[idx] == DIB_RAW_FREE ? DIB_RAW_FREE + : DIB_RAW_REHASH; + } + + /* Zero the area of newly added entries (including the old DIB area) */ + memzero(bucket_at(h, old_n_buckets), + (n_buckets(h) - old_n_buckets) * hi->entry_size); + + /* The upper half of the new DIB array needs initialization */ + memset(&new_dibs[old_n_buckets], DIB_RAW_INIT, + (n_buckets(h) - old_n_buckets) * sizeof(dib_raw_t)); + + /* Rehash entries that need it */ + n_rehashed = 0; + for (idx = 0; idx < old_n_buckets; idx++) { + if (new_dibs[idx] != DIB_RAW_REHASH) + continue; + + optimal_idx = bucket_hash(h, bucket_at(h, idx)->key); + + /* + * Not much to do if by luck the entry hashes to its current + * location. Just set its DIB. + */ + if (optimal_idx == idx) { + new_dibs[idx] = 0; + n_rehashed++; + continue; + } + + new_dibs[idx] = DIB_RAW_FREE; + bucket_move_entry(h, &swap, idx, IDX_PUT); + /* bucket_move_entry does not clear the source */ + memzero(bucket_at(h, idx), hi->entry_size); + + do { + /* + * Find the new bucket for the current entry. This may make + * another entry homeless and load it into IDX_PUT. + */ + rehash_next = hashmap_put_robin_hood(h, optimal_idx, &swap); + n_rehashed++; + + /* Did the current entry displace another one? */ + if (rehash_next) + optimal_idx = bucket_hash(h, bucket_at_swap(&swap, IDX_PUT)->p.b.key); + } while (rehash_next); + } + + assert(n_rehashed == n_entries(h)); + + return 1; +} + +/* + * Finds an entry with a matching key + * Returns: index of the found entry, or IDX_NIL if not found. + */ +static unsigned base_bucket_scan(HashmapBase *h, unsigned idx, const void *key) { + struct hashmap_base_entry *e; + unsigned dib, distance; + dib_raw_t *dibs = dib_raw_ptr(h); + + assert(idx < n_buckets(h)); + + for (distance = 0; ; distance++) { + if (dibs[idx] == DIB_RAW_FREE) + return IDX_NIL; + + dib = bucket_calculate_dib(h, idx, dibs[idx]); + + if (dib < distance) + return IDX_NIL; + if (dib == distance) { + e = bucket_at(h, idx); + if (h->hash_ops->compare(e->key, key) == 0) + return idx; + } + + idx = next_idx(h, idx); + } +} +#define bucket_scan(h, idx, key) base_bucket_scan(HASHMAP_BASE(h), idx, key) + +int hashmap_put(Hashmap *h, const void *key, void *value) { + struct swap_entries swap; + struct plain_hashmap_entry *e; + unsigned hash, idx; + + assert(h); + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx != IDX_NIL) { + e = plain_bucket_at(h, idx); + if (e->value == value) + return 0; + return -EEXIST; + } + + e = &bucket_at_swap(&swap, IDX_PUT)->p; + e->b.key = key; + e->value = value; + return hashmap_put_boldly(h, hash, &swap, true); +} + +int set_put(Set *s, const void *key) { + struct swap_entries swap; + struct hashmap_base_entry *e; + unsigned hash, idx; + + assert(s); + + hash = bucket_hash(s, key); + idx = bucket_scan(s, hash, key); + if (idx != IDX_NIL) + return 0; + + e = &bucket_at_swap(&swap, IDX_PUT)->p.b; + e->key = key; + return hashmap_put_boldly(s, hash, &swap, true); +} + +int hashmap_replace(Hashmap *h, const void *key, void *value) { + struct swap_entries swap; + struct plain_hashmap_entry *e; + unsigned hash, idx; + + assert(h); + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx != IDX_NIL) { + e = plain_bucket_at(h, idx); +#ifdef ENABLE_DEBUG_HASHMAP + /* Although the key is equal, the key pointer may have changed, + * and this would break our assumption for iterating. So count + * this operation as incompatible with iteration. */ + if (e->b.key != key) { + h->b.debug.put_count++; + h->b.debug.rem_count++; + h->b.debug.last_rem_idx = idx; + } +#endif + e->b.key = key; + e->value = value; + return 0; + } + + e = &bucket_at_swap(&swap, IDX_PUT)->p; + e->b.key = key; + e->value = value; + return hashmap_put_boldly(h, hash, &swap, true); +} + +int hashmap_update(Hashmap *h, const void *key, void *value) { + struct plain_hashmap_entry *e; + unsigned hash, idx; + + assert(h); + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return -ENOENT; + + e = plain_bucket_at(h, idx); + e->value = value; + return 0; +} + +void *internal_hashmap_get(HashmapBase *h, const void *key) { + struct hashmap_base_entry *e; + unsigned hash, idx; + + if (!h) + return NULL; + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return NULL; + + e = bucket_at(h, idx); + return entry_value(h, e); +} + +void *hashmap_get2(Hashmap *h, const void *key, void **key2) { + struct plain_hashmap_entry *e; + unsigned hash, idx; + + if (!h) + return NULL; + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return NULL; + + e = plain_bucket_at(h, idx); + if (key2) + *key2 = (void*) e->b.key; + + return e->value; +} + +bool internal_hashmap_contains(HashmapBase *h, const void *key) { + unsigned hash; + + if (!h) + return false; + + hash = bucket_hash(h, key); + return bucket_scan(h, hash, key) != IDX_NIL; +} + +void *internal_hashmap_remove(HashmapBase *h, const void *key) { + struct hashmap_base_entry *e; + unsigned hash, idx; + void *data; + + if (!h) + return NULL; + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return NULL; + + e = bucket_at(h, idx); + data = entry_value(h, e); + remove_entry(h, idx); + + return data; +} + +void *hashmap_remove2(Hashmap *h, const void *key, void **rkey) { + struct plain_hashmap_entry *e; + unsigned hash, idx; + void *data; + + if (!h) { + if (rkey) + *rkey = NULL; + return NULL; + } + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) { + if (rkey) + *rkey = NULL; + return NULL; + } + + e = plain_bucket_at(h, idx); + data = e->value; + if (rkey) + *rkey = (void*) e->b.key; + + remove_entry(h, idx); + + return data; +} + +int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) { + struct swap_entries swap; + struct plain_hashmap_entry *e; + unsigned old_hash, new_hash, idx; + + if (!h) + return -ENOENT; + + old_hash = bucket_hash(h, old_key); + idx = bucket_scan(h, old_hash, old_key); + if (idx == IDX_NIL) + return -ENOENT; + + new_hash = bucket_hash(h, new_key); + if (bucket_scan(h, new_hash, new_key) != IDX_NIL) + return -EEXIST; + + remove_entry(h, idx); + + e = &bucket_at_swap(&swap, IDX_PUT)->p; + e->b.key = new_key; + e->value = value; + assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1); + + return 0; +} + +int set_remove_and_put(Set *s, const void *old_key, const void *new_key) { + struct swap_entries swap; + struct hashmap_base_entry *e; + unsigned old_hash, new_hash, idx; + + if (!s) + return -ENOENT; + + old_hash = bucket_hash(s, old_key); + idx = bucket_scan(s, old_hash, old_key); + if (idx == IDX_NIL) + return -ENOENT; + + new_hash = bucket_hash(s, new_key); + if (bucket_scan(s, new_hash, new_key) != IDX_NIL) + return -EEXIST; + + remove_entry(s, idx); + + e = &bucket_at_swap(&swap, IDX_PUT)->p.b; + e->key = new_key; + assert_se(hashmap_put_boldly(s, new_hash, &swap, false) == 1); + + return 0; +} + +int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) { + struct swap_entries swap; + struct plain_hashmap_entry *e; + unsigned old_hash, new_hash, idx_old, idx_new; + + if (!h) + return -ENOENT; + + old_hash = bucket_hash(h, old_key); + idx_old = bucket_scan(h, old_hash, old_key); + if (idx_old == IDX_NIL) + return -ENOENT; + + old_key = bucket_at(HASHMAP_BASE(h), idx_old)->key; + + new_hash = bucket_hash(h, new_key); + idx_new = bucket_scan(h, new_hash, new_key); + if (idx_new != IDX_NIL) + if (idx_old != idx_new) { + remove_entry(h, idx_new); + /* Compensate for a possible backward shift. */ + if (old_key != bucket_at(HASHMAP_BASE(h), idx_old)->key) + idx_old = prev_idx(HASHMAP_BASE(h), idx_old); + assert(old_key == bucket_at(HASHMAP_BASE(h), idx_old)->key); + } + + remove_entry(h, idx_old); + + e = &bucket_at_swap(&swap, IDX_PUT)->p; + e->b.key = new_key; + e->value = value; + assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1); + + return 0; +} + +void *hashmap_remove_value(Hashmap *h, const void *key, void *value) { + struct plain_hashmap_entry *e; + unsigned hash, idx; + + if (!h) + return NULL; + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return NULL; + + e = plain_bucket_at(h, idx); + if (e->value != value) + return NULL; + + remove_entry(h, idx); + + return value; +} + +static unsigned find_first_entry(HashmapBase *h) { + Iterator i = ITERATOR_FIRST; + + if (!h || !n_entries(h)) + return IDX_NIL; + + return hashmap_iterate_entry(h, &i); +} + +void *internal_hashmap_first(HashmapBase *h) { + unsigned idx; + + idx = find_first_entry(h); + if (idx == IDX_NIL) + return NULL; + + return entry_value(h, bucket_at(h, idx)); +} + +void *internal_hashmap_first_key(HashmapBase *h) { + struct hashmap_base_entry *e; + unsigned idx; + + idx = find_first_entry(h); + if (idx == IDX_NIL) + return NULL; + + e = bucket_at(h, idx); + return (void*) e->key; +} + +void *internal_hashmap_steal_first(HashmapBase *h) { + struct hashmap_base_entry *e; + void *data; + unsigned idx; + + idx = find_first_entry(h); + if (idx == IDX_NIL) + return NULL; + + e = bucket_at(h, idx); + data = entry_value(h, e); + remove_entry(h, idx); + + return data; +} + +void *internal_hashmap_steal_first_key(HashmapBase *h) { + struct hashmap_base_entry *e; + void *key; + unsigned idx; + + idx = find_first_entry(h); + if (idx == IDX_NIL) + return NULL; + + e = bucket_at(h, idx); + key = (void*) e->key; + remove_entry(h, idx); + + return key; +} + +unsigned internal_hashmap_size(HashmapBase *h) { + + if (!h) + return 0; + + return n_entries(h); +} + +unsigned internal_hashmap_buckets(HashmapBase *h) { + + if (!h) + return 0; + + return n_buckets(h); +} + +int internal_hashmap_merge(Hashmap *h, Hashmap *other) { + Iterator i; + unsigned idx; + + assert(h); + + HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) { + struct plain_hashmap_entry *pe = plain_bucket_at(other, idx); + int r; + + r = hashmap_put(h, pe->b.key, pe->value); + if (r < 0 && r != -EEXIST) + return r; + } + + return 0; +} + +int set_merge(Set *s, Set *other) { + Iterator i; + unsigned idx; + + assert(s); + + HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) { + struct set_entry *se = set_bucket_at(other, idx); + int r; + + r = set_put(s, se->b.key); + if (r < 0) + return r; + } + + return 0; +} + +int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add) { + int r; + + assert(h); + + r = resize_buckets(h, entries_add); + if (r < 0) + return r; + + return 0; +} + +/* + * The same as hashmap_merge(), but every new item from other is moved to h. + * Keys already in h are skipped and stay in other. + * Returns: 0 on success. + * -ENOMEM on alloc failure, in which case no move has been done. + */ +int internal_hashmap_move(HashmapBase *h, HashmapBase *other) { + struct swap_entries swap; + struct hashmap_base_entry *e, *n; + Iterator i; + unsigned idx; + int r; + + assert(h); + + if (!other) + return 0; + + assert(other->type == h->type); + + /* + * This reserves buckets for the worst case, where none of other's + * entries are yet present in h. This is preferable to risking + * an allocation failure in the middle of the moving and having to + * rollback or return a partial result. + */ + r = resize_buckets(h, n_entries(other)); + if (r < 0) + return r; + + HASHMAP_FOREACH_IDX(idx, other, i) { + unsigned h_hash; + + e = bucket_at(other, idx); + h_hash = bucket_hash(h, e->key); + if (bucket_scan(h, h_hash, e->key) != IDX_NIL) + continue; + + n = &bucket_at_swap(&swap, IDX_PUT)->p.b; + n->key = e->key; + if (h->type != HASHMAP_TYPE_SET) + ((struct plain_hashmap_entry*) n)->value = + ((struct plain_hashmap_entry*) e)->value; + assert_se(hashmap_put_boldly(h, h_hash, &swap, false) == 1); + + remove_entry(other, idx); + } + + return 0; +} + +int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key) { + struct swap_entries swap; + unsigned h_hash, other_hash, idx; + struct hashmap_base_entry *e, *n; + int r; + + assert(h); + + h_hash = bucket_hash(h, key); + if (bucket_scan(h, h_hash, key) != IDX_NIL) + return -EEXIST; + + if (!other) + return -ENOENT; + + assert(other->type == h->type); + + other_hash = bucket_hash(other, key); + idx = bucket_scan(other, other_hash, key); + if (idx == IDX_NIL) + return -ENOENT; + + e = bucket_at(other, idx); + + n = &bucket_at_swap(&swap, IDX_PUT)->p.b; + n->key = e->key; + if (h->type != HASHMAP_TYPE_SET) + ((struct plain_hashmap_entry*) n)->value = + ((struct plain_hashmap_entry*) e)->value; + r = hashmap_put_boldly(h, h_hash, &swap, true); + if (r < 0) + return r; + + remove_entry(other, idx); + return 0; +} + +HashmapBase *internal_hashmap_copy(HashmapBase *h) { + HashmapBase *copy; + int r; + + assert(h); + + copy = hashmap_base_new(h->hash_ops, h->type HASHMAP_DEBUG_SRC_ARGS); + if (!copy) + return NULL; + + switch (h->type) { + case HASHMAP_TYPE_PLAIN: + case HASHMAP_TYPE_ORDERED: + r = hashmap_merge((Hashmap*)copy, (Hashmap*)h); + break; + case HASHMAP_TYPE_SET: + r = set_merge((Set*)copy, (Set*)h); + break; + default: + assert_not_reached("Unknown hashmap type"); + } + + if (r < 0) { + internal_hashmap_free(copy); + return NULL; + } + + return copy; +} + +char **internal_hashmap_get_strv(HashmapBase *h) { + char **sv; + Iterator i; + unsigned idx, n; + + sv = new(char*, n_entries(h)+1); + if (!sv) + return NULL; + + n = 0; + HASHMAP_FOREACH_IDX(idx, h, i) + sv[n++] = entry_value(h, bucket_at(h, idx)); + sv[n] = NULL; + + return sv; +} + +void *ordered_hashmap_next(OrderedHashmap *h, const void *key) { + struct ordered_hashmap_entry *e; + unsigned hash, idx; + + if (!h) + return NULL; + + hash = bucket_hash(h, key); + idx = bucket_scan(h, hash, key); + if (idx == IDX_NIL) + return NULL; + + e = ordered_bucket_at(h, idx); + if (e->iterate_next == IDX_NIL) + return NULL; + return ordered_bucket_at(h, e->iterate_next)->p.value; +} + +int set_consume(Set *s, void *value) { + int r; + + r = set_put(s, value); + if (r <= 0) + free(value); + + return r; +} + +int set_put_strdup(Set *s, const char *p) { + char *c; + int r; + + assert(s); + assert(p); + + c = strdup(p); + if (!c) + return -ENOMEM; + + r = set_consume(s, c); + if (r == -EEXIST) + return 0; + + return r; +} + +int set_put_strdupv(Set *s, char **l) { + int n = 0, r; + char **i; + + STRV_FOREACH(i, l) { + r = set_put_strdup(s, *i); + if (r < 0) + return r; + + n += r; + } + + return n; +} diff --git a/src/libbasic/hashmap.h b/src/libbasic/hashmap.h new file mode 100644 index 0000000000..6d1ae48b21 --- /dev/null +++ b/src/libbasic/hashmap.h @@ -0,0 +1,372 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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 . +***/ + +#include +#include +#include + +#include "hash-funcs.h" +#include "macro.h" +#include "util.h" + +/* + * A hash table implementation. As a minor optimization a NULL hashmap object + * will be treated as empty hashmap for all read operations. That way it is not + * necessary to instantiate an object for each Hashmap use. + * + * If ENABLE_DEBUG_HASHMAP is defined (by configuring with --enable-debug=hashmap), + * the implemention will: + * - store extra data for debugging and statistics (see tools/gdb-sd_dump_hashmaps.py) + * - perform extra checks for invalid use of iterators + */ + +#define HASH_KEY_SIZE 16 + +/* The base type for all hashmap and set types. Many functions in the + * implementation take (HashmapBase*) parameters and are run-time polymorphic, + * though the API is not meant to be polymorphic (do not call functions + * internal_*() directly). */ +typedef struct HashmapBase HashmapBase; + +/* Specific hashmap/set types */ +typedef struct Hashmap Hashmap; /* Maps keys to values */ +typedef struct OrderedHashmap OrderedHashmap; /* Like Hashmap, but also remembers entry insertion order */ +typedef struct Set Set; /* Stores just keys */ + +/* Ideally the Iterator would be an opaque struct, but it is instantiated + * by hashmap users, so the definition has to be here. Do not use its fields + * directly. */ +typedef struct { + unsigned idx; /* index of an entry to be iterated next */ + const void *next_key; /* expected value of that entry's key pointer */ +#ifdef ENABLE_DEBUG_HASHMAP + unsigned put_count; /* hashmap's put_count recorded at start of iteration */ + unsigned rem_count; /* hashmap's rem_count in previous iteration */ + unsigned prev_idx; /* idx in previous iteration */ +#endif +} Iterator; + +#define _IDX_ITERATOR_FIRST (UINT_MAX - 1) +#define ITERATOR_FIRST ((Iterator) { .idx = _IDX_ITERATOR_FIRST, .next_key = NULL }) + +/* Macros for type checking */ +#define PTR_COMPATIBLE_WITH_HASHMAP_BASE(h) \ + (__builtin_types_compatible_p(typeof(h), HashmapBase*) || \ + __builtin_types_compatible_p(typeof(h), Hashmap*) || \ + __builtin_types_compatible_p(typeof(h), OrderedHashmap*) || \ + __builtin_types_compatible_p(typeof(h), Set*)) + +#define PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h) \ + (__builtin_types_compatible_p(typeof(h), Hashmap*) || \ + __builtin_types_compatible_p(typeof(h), OrderedHashmap*)) \ + +#define HASHMAP_BASE(h) \ + __builtin_choose_expr(PTR_COMPATIBLE_WITH_HASHMAP_BASE(h), \ + (HashmapBase*)(h), \ + (void)0) + +#define PLAIN_HASHMAP(h) \ + __builtin_choose_expr(PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h), \ + (Hashmap*)(h), \ + (void)0) + +#ifdef ENABLE_DEBUG_HASHMAP +# define HASHMAP_DEBUG_PARAMS , const char *func, const char *file, int line +# define HASHMAP_DEBUG_SRC_ARGS , __func__, __FILE__, __LINE__ +# define HASHMAP_DEBUG_PASS_ARGS , func, file, line +#else +# define HASHMAP_DEBUG_PARAMS +# define HASHMAP_DEBUG_SRC_ARGS +# define HASHMAP_DEBUG_PASS_ARGS +#endif + +Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define hashmap_new(ops) internal_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) +#define ordered_hashmap_new(ops) internal_ordered_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS) + +HashmapBase *internal_hashmap_free(HashmapBase *h); +static inline Hashmap *hashmap_free(Hashmap *h) { + return (void*)internal_hashmap_free(HASHMAP_BASE(h)); +} +static inline OrderedHashmap *ordered_hashmap_free(OrderedHashmap *h) { + return (void*)internal_hashmap_free(HASHMAP_BASE(h)); +} + +HashmapBase *internal_hashmap_free_free(HashmapBase *h); +static inline Hashmap *hashmap_free_free(Hashmap *h) { + return (void*)internal_hashmap_free_free(HASHMAP_BASE(h)); +} +static inline OrderedHashmap *ordered_hashmap_free_free(OrderedHashmap *h) { + return (void*)internal_hashmap_free_free(HASHMAP_BASE(h)); +} + +Hashmap *hashmap_free_free_free(Hashmap *h); +static inline OrderedHashmap *ordered_hashmap_free_free_free(OrderedHashmap *h) { + return (void*)hashmap_free_free_free(PLAIN_HASHMAP(h)); +} + +HashmapBase *internal_hashmap_copy(HashmapBase *h); +static inline Hashmap *hashmap_copy(Hashmap *h) { + return (Hashmap*) internal_hashmap_copy(HASHMAP_BASE(h)); +} +static inline OrderedHashmap *ordered_hashmap_copy(OrderedHashmap *h) { + return (OrderedHashmap*) internal_hashmap_copy(HASHMAP_BASE(h)); +} + +int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define hashmap_ensure_allocated(h, ops) internal_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) +#define ordered_hashmap_ensure_allocated(h, ops) internal_ordered_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) + +int hashmap_put(Hashmap *h, const void *key, void *value); +static inline int ordered_hashmap_put(OrderedHashmap *h, const void *key, void *value) { + return hashmap_put(PLAIN_HASHMAP(h), key, value); +} + +int hashmap_update(Hashmap *h, const void *key, void *value); +static inline int ordered_hashmap_update(OrderedHashmap *h, const void *key, void *value) { + return hashmap_update(PLAIN_HASHMAP(h), key, value); +} + +int hashmap_replace(Hashmap *h, const void *key, void *value); +static inline int ordered_hashmap_replace(OrderedHashmap *h, const void *key, void *value) { + return hashmap_replace(PLAIN_HASHMAP(h), key, value); +} + +void *internal_hashmap_get(HashmapBase *h, const void *key); +static inline void *hashmap_get(Hashmap *h, const void *key) { + return internal_hashmap_get(HASHMAP_BASE(h), key); +} +static inline void *ordered_hashmap_get(OrderedHashmap *h, const void *key) { + return internal_hashmap_get(HASHMAP_BASE(h), key); +} + +void *hashmap_get2(Hashmap *h, const void *key, void **rkey); +static inline void *ordered_hashmap_get2(OrderedHashmap *h, const void *key, void **rkey) { + return hashmap_get2(PLAIN_HASHMAP(h), key, rkey); +} + +bool internal_hashmap_contains(HashmapBase *h, const void *key); +static inline bool hashmap_contains(Hashmap *h, const void *key) { + return internal_hashmap_contains(HASHMAP_BASE(h), key); +} +static inline bool ordered_hashmap_contains(OrderedHashmap *h, const void *key) { + return internal_hashmap_contains(HASHMAP_BASE(h), key); +} + +void *internal_hashmap_remove(HashmapBase *h, const void *key); +static inline void *hashmap_remove(Hashmap *h, const void *key) { + return internal_hashmap_remove(HASHMAP_BASE(h), key); +} +static inline void *ordered_hashmap_remove(OrderedHashmap *h, const void *key) { + return internal_hashmap_remove(HASHMAP_BASE(h), key); +} + +void *hashmap_remove2(Hashmap *h, const void *key, void **rkey); +static inline void *ordered_hashmap_remove2(OrderedHashmap *h, const void *key, void **rkey) { + return hashmap_remove2(PLAIN_HASHMAP(h), key, rkey); +} + +void *hashmap_remove_value(Hashmap *h, const void *key, void *value); +static inline void *ordered_hashmap_remove_value(OrderedHashmap *h, const void *key, void *value) { + return hashmap_remove_value(PLAIN_HASHMAP(h), key, value); +} + +int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value); +static inline int ordered_hashmap_remove_and_put(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) { + return hashmap_remove_and_put(PLAIN_HASHMAP(h), old_key, new_key, value); +} + +int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value); +static inline int ordered_hashmap_remove_and_replace(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) { + return hashmap_remove_and_replace(PLAIN_HASHMAP(h), old_key, new_key, value); +} + +/* Since merging data from a OrderedHashmap into a Hashmap or vice-versa + * should just work, allow this by having looser type-checking here. */ +int internal_hashmap_merge(Hashmap *h, Hashmap *other); +#define hashmap_merge(h, other) internal_hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other)) +#define ordered_hashmap_merge(h, other) hashmap_merge(h, other) + +int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add); +static inline int hashmap_reserve(Hashmap *h, unsigned entries_add) { + return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); +} +static inline int ordered_hashmap_reserve(OrderedHashmap *h, unsigned entries_add) { + return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); +} + +int internal_hashmap_move(HashmapBase *h, HashmapBase *other); +/* Unlike hashmap_merge, hashmap_move does not allow mixing the types. */ +static inline int hashmap_move(Hashmap *h, Hashmap *other) { + return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other)); +} +static inline int ordered_hashmap_move(OrderedHashmap *h, OrderedHashmap *other) { + return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other)); +} + +int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key); +static inline int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) { + return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key); +} +static inline int ordered_hashmap_move_one(OrderedHashmap *h, OrderedHashmap *other, const void *key) { + return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key); +} + +unsigned internal_hashmap_size(HashmapBase *h) _pure_; +static inline unsigned hashmap_size(Hashmap *h) { + return internal_hashmap_size(HASHMAP_BASE(h)); +} +static inline unsigned ordered_hashmap_size(OrderedHashmap *h) { + return internal_hashmap_size(HASHMAP_BASE(h)); +} + +static inline bool hashmap_isempty(Hashmap *h) { + return hashmap_size(h) == 0; +} +static inline bool ordered_hashmap_isempty(OrderedHashmap *h) { + return ordered_hashmap_size(h) == 0; +} + +unsigned internal_hashmap_buckets(HashmapBase *h) _pure_; +static inline unsigned hashmap_buckets(Hashmap *h) { + return internal_hashmap_buckets(HASHMAP_BASE(h)); +} +static inline unsigned ordered_hashmap_buckets(OrderedHashmap *h) { + return internal_hashmap_buckets(HASHMAP_BASE(h)); +} + +bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key); +static inline bool hashmap_iterate(Hashmap *h, Iterator *i, void **value, const void **key) { + return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key); +} +static inline bool ordered_hashmap_iterate(OrderedHashmap *h, Iterator *i, void **value, const void **key) { + return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key); +} + +void internal_hashmap_clear(HashmapBase *h); +static inline void hashmap_clear(Hashmap *h) { + internal_hashmap_clear(HASHMAP_BASE(h)); +} +static inline void ordered_hashmap_clear(OrderedHashmap *h) { + internal_hashmap_clear(HASHMAP_BASE(h)); +} + +void internal_hashmap_clear_free(HashmapBase *h); +static inline void hashmap_clear_free(Hashmap *h) { + internal_hashmap_clear_free(HASHMAP_BASE(h)); +} +static inline void ordered_hashmap_clear_free(OrderedHashmap *h) { + internal_hashmap_clear_free(HASHMAP_BASE(h)); +} + +void hashmap_clear_free_free(Hashmap *h); +static inline void ordered_hashmap_clear_free_free(OrderedHashmap *h) { + hashmap_clear_free_free(PLAIN_HASHMAP(h)); +} + +/* + * Note about all *_first*() functions + * + * For plain Hashmaps and Sets the order of entries is undefined. + * The functions find whatever entry is first in the implementation + * internal order. + * + * Only for OrderedHashmaps the order is well defined and finding + * the first entry is O(1). + */ + +void *internal_hashmap_steal_first(HashmapBase *h); +static inline void *hashmap_steal_first(Hashmap *h) { + return internal_hashmap_steal_first(HASHMAP_BASE(h)); +} +static inline void *ordered_hashmap_steal_first(OrderedHashmap *h) { + return internal_hashmap_steal_first(HASHMAP_BASE(h)); +} + +void *internal_hashmap_steal_first_key(HashmapBase *h); +static inline void *hashmap_steal_first_key(Hashmap *h) { + return internal_hashmap_steal_first_key(HASHMAP_BASE(h)); +} +static inline void *ordered_hashmap_steal_first_key(OrderedHashmap *h) { + return internal_hashmap_steal_first_key(HASHMAP_BASE(h)); +} + +void *internal_hashmap_first_key(HashmapBase *h) _pure_; +static inline void *hashmap_first_key(Hashmap *h) { + return internal_hashmap_first_key(HASHMAP_BASE(h)); +} +static inline void *ordered_hashmap_first_key(OrderedHashmap *h) { + return internal_hashmap_first_key(HASHMAP_BASE(h)); +} + +void *internal_hashmap_first(HashmapBase *h) _pure_; +static inline void *hashmap_first(Hashmap *h) { + return internal_hashmap_first(HASHMAP_BASE(h)); +} +static inline void *ordered_hashmap_first(OrderedHashmap *h) { + return internal_hashmap_first(HASHMAP_BASE(h)); +} + +/* no hashmap_next */ +void *ordered_hashmap_next(OrderedHashmap *h, const void *key); + +char **internal_hashmap_get_strv(HashmapBase *h); +static inline char **hashmap_get_strv(Hashmap *h) { + return internal_hashmap_get_strv(HASHMAP_BASE(h)); +} +static inline char **ordered_hashmap_get_strv(OrderedHashmap *h) { + return internal_hashmap_get_strv(HASHMAP_BASE(h)); +} + +/* + * Hashmaps are iterated in unpredictable order. + * OrderedHashmaps are an exception to this. They are iterated in the order + * the entries were inserted. + * It is safe to remove the current entry. + */ +#define HASHMAP_FOREACH(e, h, i) \ + for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), NULL); ) + +#define ORDERED_HASHMAP_FOREACH(e, h, i) \ + for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), NULL); ) + +#define HASHMAP_FOREACH_KEY(e, k, h, i) \ + for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); ) + +#define ORDERED_HASHMAP_FOREACH_KEY(e, k, h, i) \ + for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); ) + +DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free); + +#define _cleanup_hashmap_free_ _cleanup_(hashmap_freep) +#define _cleanup_hashmap_free_free_ _cleanup_(hashmap_free_freep) +#define _cleanup_hashmap_free_free_free_ _cleanup_(hashmap_free_free_freep) +#define _cleanup_ordered_hashmap_free_ _cleanup_(ordered_hashmap_freep) +#define _cleanup_ordered_hashmap_free_free_ _cleanup_(ordered_hashmap_free_freep) +#define _cleanup_ordered_hashmap_free_free_free_ _cleanup_(ordered_hashmap_free_free_freep) diff --git a/src/libbasic/hexdecoct.c b/src/libbasic/hexdecoct.c new file mode 100644 index 0000000000..592df53cb5 --- /dev/null +++ b/src/libbasic/hexdecoct.c @@ -0,0 +1,753 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "hexdecoct.h" +#include "macro.h" + +char octchar(int x) { + return '0' + (x & 7); +} + +int unoctchar(char c) { + + if (c >= '0' && c <= '7') + return c - '0'; + + return -EINVAL; +} + +char decchar(int x) { + return '0' + (x % 10); +} + +int undecchar(char c) { + + if (c >= '0' && c <= '9') + return c - '0'; + + return -EINVAL; +} + +char hexchar(int x) { + static const char table[16] = "0123456789abcdef"; + + return table[x & 15]; +} + +int unhexchar(char c) { + + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return -EINVAL; +} + +char *hexmem(const void *p, size_t l) { + char *r, *z; + const uint8_t *x; + + z = r = malloc(l * 2 + 1); + if (!r) + return NULL; + + for (x = p; x < (const uint8_t*) p + l; x++) { + *(z++) = hexchar(*x >> 4); + *(z++) = hexchar(*x & 15); + } + + *z = 0; + return r; +} + +int unhexmem(const char *p, size_t l, void **mem, size_t *len) { + _cleanup_free_ uint8_t *r = NULL; + uint8_t *z; + const char *x; + + assert(mem); + assert(len); + assert(p); + + z = r = malloc((l + 1) / 2 + 1); + if (!r) + return -ENOMEM; + + for (x = p; x < p + l; x += 2) { + int a, b; + + a = unhexchar(x[0]); + if (a < 0) + return a; + else if (x+1 < p + l) { + b = unhexchar(x[1]); + if (b < 0) + return b; + } else + b = 0; + + *(z++) = (uint8_t) a << 4 | (uint8_t) b; + } + + *z = 0; + + *mem = r; + r = NULL; + *len = (l + 1) / 2; + + return 0; +} + +/* https://tools.ietf.org/html/rfc4648#section-6 + * Notice that base32hex differs from base32 in the alphabet it uses. + * The distinction is that the base32hex representation preserves the + * order of the underlying data when compared as bytestrings, this is + * useful when representing NSEC3 hashes, as one can then verify the + * order of hashes directly from their representation. */ +char base32hexchar(int x) { + static const char table[32] = "0123456789" + "ABCDEFGHIJKLMNOPQRSTUV"; + + return table[x & 31]; +} + +int unbase32hexchar(char c) { + unsigned offset; + + if (c >= '0' && c <= '9') + return c - '0'; + + offset = '9' - '0' + 1; + + if (c >= 'A' && c <= 'V') + return c - 'A' + offset; + + return -EINVAL; +} + +char *base32hexmem(const void *p, size_t l, bool padding) { + char *r, *z; + const uint8_t *x; + size_t len; + + if (padding) + /* five input bytes makes eight output bytes, padding is added so we must round up */ + len = 8 * (l + 4) / 5; + else { + /* same, but round down as there is no padding */ + len = 8 * l / 5; + + switch (l % 5) { + case 4: + len += 7; + break; + case 3: + len += 5; + break; + case 2: + len += 4; + break; + case 1: + len += 2; + break; + } + } + + z = r = malloc(len + 1); + if (!r) + return NULL; + + for (x = p; x < (const uint8_t*) p + (l / 5) * 5; x += 5) { + /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ + x[3] == QQQQQQQQ; x[4] == WWWWWWWW */ + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ + *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ + *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ + *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */ + *(z++) = base32hexchar((x[3] & 127) >> 2); /* 000QQQQQ */ + *(z++) = base32hexchar((x[3] & 3) << 3 | x[4] >> 5); /* 000QQWWW */ + *(z++) = base32hexchar((x[4] & 31)); /* 000WWWWW */ + } + + switch (l % 5) { + case 4: + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ + *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ + *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ + *(z++) = base32hexchar((x[2] & 15) << 1 | x[3] >> 7); /* 000ZZZZQ */ + *(z++) = base32hexchar((x[3] & 127) >> 2); /* 000QQQQQ */ + *(z++) = base32hexchar((x[3] & 3) << 3); /* 000QQ000 */ + if (padding) + *(z++) = '='; + + break; + + case 3: + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ + *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ + *(z++) = base32hexchar((x[1] & 1) << 4 | x[2] >> 4); /* 000YZZZZ */ + *(z++) = base32hexchar((x[2] & 15) << 1); /* 000ZZZZ0 */ + if (padding) { + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + } + + break; + + case 2: + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2 | x[1] >> 6); /* 000XXXYY */ + *(z++) = base32hexchar((x[1] & 63) >> 1); /* 000YYYYY */ + *(z++) = base32hexchar((x[1] & 1) << 4); /* 000Y0000 */ + if (padding) { + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + } + + break; + + case 1: + *(z++) = base32hexchar(x[0] >> 3); /* 000XXXXX */ + *(z++) = base32hexchar((x[0] & 7) << 2); /* 000XXX00 */ + if (padding) { + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + *(z++) = '='; + } + + break; + } + + *z = 0; + return r; +} + +int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *_len) { + _cleanup_free_ uint8_t *r = NULL; + int a, b, c, d, e, f, g, h; + uint8_t *z; + const char *x; + size_t len; + unsigned pad = 0; + + assert(p); + + /* padding ensures any base32hex input has input divisible by 8 */ + if (padding && l % 8 != 0) + return -EINVAL; + + if (padding) { + /* strip the padding */ + while (l > 0 && p[l - 1] == '=' && pad < 7) { + pad ++; + l --; + } + } + + /* a group of eight input bytes needs five output bytes, in case of + padding we need to add some extra bytes */ + len = (l / 8) * 5; + + switch (l % 8) { + case 7: + len += 4; + break; + case 5: + len += 3; + break; + case 4: + len += 2; + break; + case 2: + len += 1; + break; + case 0: + break; + default: + return -EINVAL; + } + + z = r = malloc(len + 1); + if (!r) + return -ENOMEM; + + for (x = p; x < p + (l / 8) * 8; x += 8) { + /* a == 000XXXXX; b == 000YYYYY; c == 000ZZZZZ; d == 000WWWWW + e == 000SSSSS; f == 000QQQQQ; g == 000VVVVV; h == 000RRRRR */ + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase32hexchar(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase32hexchar(x[3]); + if (d < 0) + return -EINVAL; + + e = unbase32hexchar(x[4]); + if (e < 0) + return -EINVAL; + + f = unbase32hexchar(x[5]); + if (f < 0) + return -EINVAL; + + g = unbase32hexchar(x[6]); + if (g < 0) + return -EINVAL; + + h = unbase32hexchar(x[7]); + if (h < 0) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ + *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ + *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */ + *(z++) = (uint8_t) g << 5 | (uint8_t) h; /* VVVRRRRR */ + } + + switch (l % 8) { + case 7: + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase32hexchar(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase32hexchar(x[3]); + if (d < 0) + return -EINVAL; + + e = unbase32hexchar(x[4]); + if (e < 0) + return -EINVAL; + + f = unbase32hexchar(x[5]); + if (f < 0) + return -EINVAL; + + g = unbase32hexchar(x[6]); + if (g < 0) + return -EINVAL; + + /* g == 000VV000 */ + if (g & 7) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ + *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ + *(z++) = (uint8_t) e << 7 | (uint8_t) f << 2 | (uint8_t) g >> 3; /* SQQQQQVV */ + + break; + case 5: + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase32hexchar(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase32hexchar(x[3]); + if (d < 0) + return -EINVAL; + + e = unbase32hexchar(x[4]); + if (e < 0) + return -EINVAL; + + /* e == 000SSSS0 */ + if (e & 1) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ + *(z++) = (uint8_t) d << 4 | (uint8_t) e >> 1; /* WWWWSSSS */ + + break; + case 4: + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase32hexchar(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase32hexchar(x[3]); + if (d < 0) + return -EINVAL; + + /* d == 000W0000 */ + if (d & 15) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + *(z++) = (uint8_t) b << 6 | (uint8_t) c << 1 | (uint8_t) d >> 4; /* YYZZZZZW */ + + break; + case 2: + a = unbase32hexchar(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase32hexchar(x[1]); + if (b < 0) + return -EINVAL; + + /* b == 000YYY00 */ + if (b & 3) + return -EINVAL; + + *(z++) = (uint8_t) a << 3 | (uint8_t) b >> 2; /* XXXXXYYY */ + + break; + case 0: + break; + default: + return -EINVAL; + } + + *z = 0; + + *mem = r; + r = NULL; + *_len = len; + + return 0; +} + +/* https://tools.ietf.org/html/rfc4648#section-4 */ +char base64char(int x) { + static const char table[64] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + return table[x & 63]; +} + +int unbase64char(char c) { + unsigned offset; + + if (c >= 'A' && c <= 'Z') + return c - 'A'; + + offset = 'Z' - 'A' + 1; + + if (c >= 'a' && c <= 'z') + return c - 'a' + offset; + + offset += 'z' - 'a' + 1; + + if (c >= '0' && c <= '9') + return c - '0' + offset; + + offset += '9' - '0' + 1; + + if (c == '+') + return offset; + + offset ++; + + if (c == '/') + return offset; + + return -EINVAL; +} + +ssize_t base64mem(const void *p, size_t l, char **out) { + char *r, *z; + const uint8_t *x; + + /* three input bytes makes four output bytes, padding is added so we must round up */ + z = r = malloc(4 * (l + 2) / 3 + 1); + if (!r) + return -ENOMEM; + + for (x = p; x < (const uint8_t*) p + (l / 3) * 3; x += 3) { + /* x[0] == XXXXXXXX; x[1] == YYYYYYYY; x[2] == ZZZZZZZZ */ + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + *(z++) = base64char((x[1] & 15) << 2 | x[2] >> 6); /* 00YYYYZZ */ + *(z++) = base64char(x[2] & 63); /* 00ZZZZZZ */ + } + + switch (l % 3) { + case 2: + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4 | x[1] >> 4); /* 00XXYYYY */ + *(z++) = base64char((x[1] & 15) << 2); /* 00YYYY00 */ + *(z++) = '='; + + break; + case 1: + *(z++) = base64char(x[0] >> 2); /* 00XXXXXX */ + *(z++) = base64char((x[0] & 3) << 4); /* 00XX0000 */ + *(z++) = '='; + *(z++) = '='; + + break; + } + + *z = 0; + *out = r; + return z - r; +} + +static int base64_append_width(char **prefix, int plen, + const char *sep, int indent, + const void *p, size_t l, + int width) { + + _cleanup_free_ char *x = NULL; + char *t, *s; + ssize_t slen, len, avail; + int line, lines; + + len = base64mem(p, l, &x); + if (len <= 0) + return len; + + lines = (len + width - 1) / width; + + slen = sep ? strlen(sep) : 0; + t = realloc(*prefix, plen + 1 + slen + (indent + width + 1) * lines); + if (!t) + return -ENOMEM; + + memcpy(t + plen, sep, slen); + + for (line = 0, s = t + plen + slen, avail = len; line < lines; line++) { + int act = MIN(width, avail); + + if (line > 0 || sep) { + memset(s, ' ', indent); + s += indent; + } + + memcpy(s, x + width * line, act); + s += act; + *(s++) = line < lines - 1 ? '\n' : '\0'; + avail -= act; + } + assert(avail == 0); + + *prefix = t; + return 0; +} + +int base64_append(char **prefix, int plen, + const void *p, size_t l, + int indent, int width) { + if (plen > width / 2 || plen + indent > width) + /* leave indent on the left, keep last column free */ + return base64_append_width(prefix, plen, "\n", indent, p, l, width - indent - 1); + else + /* leave plen on the left, keep last column free */ + return base64_append_width(prefix, plen, NULL, plen, p, l, width - plen - 1); +}; + + +int unbase64mem(const char *p, size_t l, void **mem, size_t *_len) { + _cleanup_free_ uint8_t *r = NULL; + int a, b, c, d; + uint8_t *z; + const char *x; + size_t len; + + assert(p); + + /* padding ensures any base63 input has input divisible by 4 */ + if (l % 4 != 0) + return -EINVAL; + + /* strip the padding */ + if (l > 0 && p[l - 1] == '=') + l --; + if (l > 0 && p[l - 1] == '=') + l --; + + /* a group of four input bytes needs three output bytes, in case of + padding we need to add two or three extra bytes */ + len = (l / 4) * 3 + (l % 4 ? (l % 4) - 1 : 0); + + z = r = malloc(len + 1); + if (!r) + return -ENOMEM; + + for (x = p; x < p + (l / 4) * 4; x += 4) { + /* a == 00XXXXXX; b == 00YYYYYY; c == 00ZZZZZZ; d == 00WWWWWW */ + a = unbase64char(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase64char(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase64char(x[2]); + if (c < 0) + return -EINVAL; + + d = unbase64char(x[3]); + if (d < 0) + return -EINVAL; + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + *(z++) = (uint8_t) c << 6 | (uint8_t) d; /* ZZWWWWWW */ + } + + switch (l % 4) { + case 3: + a = unbase64char(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase64char(x[1]); + if (b < 0) + return -EINVAL; + + c = unbase64char(x[2]); + if (c < 0) + return -EINVAL; + + /* c == 00ZZZZ00 */ + if (c & 3) + return -EINVAL; + + *(z++) = (uint8_t) a << 2 | (uint8_t) b >> 4; /* XXXXXXYY */ + *(z++) = (uint8_t) b << 4 | (uint8_t) c >> 2; /* YYYYZZZZ */ + + break; + case 2: + a = unbase64char(x[0]); + if (a < 0) + return -EINVAL; + + b = unbase64char(x[1]); + if (b < 0) + return -EINVAL; + + /* b == 00YY0000 */ + if (b & 15) + return -EINVAL; + + *(z++) = (uint8_t) a << 2 | (uint8_t) (b >> 4); /* XXXXXXYY */ + + break; + case 0: + + break; + default: + return -EINVAL; + } + + *z = 0; + + *mem = r; + r = NULL; + *_len = len; + + return 0; +} + +void hexdump(FILE *f, const void *p, size_t s) { + const uint8_t *b = p; + unsigned n = 0; + + assert(s == 0 || b); + + while (s > 0) { + size_t i; + + fprintf(f, "%04x ", n); + + for (i = 0; i < 16; i++) { + + if (i >= s) + fputs(" ", f); + else + fprintf(f, "%02x ", b[i]); + + if (i == 7) + fputc(' ', f); + } + + fputc(' ', f); + + for (i = 0; i < 16; i++) { + + if (i >= s) + fputc(' ', f); + else + fputc(isprint(b[i]) ? (char) b[i] : '.', f); + } + + fputc('\n', f); + + if (s < 16) + break; + + n += 16; + b += 16; + s -= 16; + } +} diff --git a/src/libbasic/hexdecoct.h b/src/libbasic/hexdecoct.h new file mode 100644 index 0000000000..1ba2f69ebd --- /dev/null +++ b/src/libbasic/hexdecoct.h @@ -0,0 +1,56 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "macro.h" + +char octchar(int x) _const_; +int unoctchar(char c) _const_; + +char decchar(int x) _const_; +int undecchar(char c) _const_; + +char hexchar(int x) _const_; +int unhexchar(char c) _const_; + +char *hexmem(const void *p, size_t l); +int unhexmem(const char *p, size_t l, void **mem, size_t *len); + +char base32hexchar(int x) _const_; +int unbase32hexchar(char c) _const_; + +char base64char(int x) _const_; +int unbase64char(char c) _const_; + +char *base32hexmem(const void *p, size_t l, bool padding); +int unbase32hexmem(const char *p, size_t l, bool padding, void **mem, size_t *len); + +ssize_t base64mem(const void *p, size_t l, char **out); +int base64_append(char **prefix, int plen, + const void *p, size_t l, + int margin, int width); +int unbase64mem(const char *p, size_t l, void **mem, size_t *len); + +void hexdump(FILE *f, const void *p, size_t s); diff --git a/src/libbasic/hostname-util.c b/src/libbasic/hostname-util.c new file mode 100644 index 0000000000..7bb23448ed --- /dev/null +++ b/src/libbasic/hostname-util.c @@ -0,0 +1,224 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "fd-util.h" +#include "fileio.h" +#include "hostname-util.h" +#include "macro.h" +#include "string-util.h" + +bool hostname_is_set(void) { + struct utsname u; + + assert_se(uname(&u) >= 0); + + if (isempty(u.nodename)) + return false; + + /* This is the built-in kernel default host name */ + if (streq(u.nodename, "(none)")) + return false; + + return true; +} + +char* gethostname_malloc(void) { + struct utsname u; + + assert_se(uname(&u) >= 0); + + if (isempty(u.nodename) || streq(u.nodename, "(none)")) + return strdup(u.sysname); + + return strdup(u.nodename); +} + +static bool hostname_valid_char(char c) { + return + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '-' || + c == '_' || + c == '.'; +} + +/** + * Check if s looks like a valid host name or FQDN. This does not do + * full DNS validation, but only checks if the name is composed of + * allowed characters and the length is not above the maximum allowed + * by Linux (c.f. dns_name_is_valid()). Trailing dot is allowed if + * allow_trailing_dot is true and at least two components are present + * in the name. Note that due to the restricted charset and length + * this call is substantially more conservative than + * dns_name_is_valid(). + */ +bool hostname_is_valid(const char *s, bool allow_trailing_dot) { + unsigned n_dots = 0; + const char *p; + bool dot; + + if (isempty(s)) + return false; + + /* Doesn't accept empty hostnames, hostnames with + * leading dots, and hostnames with multiple dots in a + * sequence. Also ensures that the length stays below + * HOST_NAME_MAX. */ + + for (p = s, dot = true; *p; p++) { + if (*p == '.') { + if (dot) + return false; + + dot = true; + n_dots ++; + } else { + if (!hostname_valid_char(*p)) + return false; + + dot = false; + } + } + + if (dot && (n_dots < 2 || !allow_trailing_dot)) + return false; + + if (p-s > HOST_NAME_MAX) /* Note that HOST_NAME_MAX is 64 on + * Linux, but DNS allows domain names + * up to 255 characters */ + return false; + + return true; +} + +char* hostname_cleanup(char *s) { + char *p, *d; + bool dot; + + assert(s); + + for (p = s, d = s, dot = true; *p; p++) { + if (*p == '.') { + if (dot) + continue; + + *(d++) = '.'; + dot = true; + } else if (hostname_valid_char(*p)) { + *(d++) = *p; + dot = false; + } + + } + + if (dot && d > s) + d[-1] = 0; + else + *d = 0; + + strshorten(s, HOST_NAME_MAX); + + return s; +} + +bool is_localhost(const char *hostname) { + assert(hostname); + + /* This tries to identify local host and domain names + * described in RFC6761 plus the redhatism of .localdomain */ + + return strcaseeq(hostname, "localhost") || + strcaseeq(hostname, "localhost.") || + strcaseeq(hostname, "localdomain.") || + strcaseeq(hostname, "localdomain") || + endswith_no_case(hostname, ".localhost") || + endswith_no_case(hostname, ".localhost.") || + endswith_no_case(hostname, ".localdomain") || + endswith_no_case(hostname, ".localdomain."); +} + +bool is_gateway_hostname(const char *hostname) { + assert(hostname); + + /* This tries to identify the valid syntaxes for the our + * synthetic "gateway" host. */ + + return + strcaseeq(hostname, "gateway") || + strcaseeq(hostname, "gateway."); +} + +int sethostname_idempotent(const char *s) { + char buf[HOST_NAME_MAX + 1] = {}; + + assert(s); + + if (gethostname(buf, sizeof(buf)) < 0) + return -errno; + + if (streq(buf, s)) + return 0; + + if (sethostname(s, strlen(s)) < 0) + return -errno; + + return 1; +} + +int read_hostname_config(const char *path, char **hostname) { + _cleanup_fclose_ FILE *f = NULL; + char l[LINE_MAX]; + char *name = NULL; + + assert(path); + assert(hostname); + + f = fopen(path, "re"); + if (!f) + return -errno; + + /* may have comments, ignore them */ + FOREACH_LINE(l, f, return -errno) { + truncate_nl(l); + if (l[0] != '\0' && l[0] != '#') { + /* found line with value */ + name = hostname_cleanup(l); + name = strdup(name); + if (!name) + return -ENOMEM; + break; + } + } + + if (!name) + /* no non-empty line found */ + return -ENOENT; + + *hostname = name; + return 0; +} diff --git a/src/libbasic/hostname-util.h b/src/libbasic/hostname-util.h new file mode 100644 index 0000000000..d062eddea1 --- /dev/null +++ b/src/libbasic/hostname-util.h @@ -0,0 +1,40 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2015 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 . +***/ + +#include + +#include "macro.h" + +bool hostname_is_set(void); + +char* gethostname_malloc(void); + +bool hostname_is_valid(const char *s, bool allow_trailing_dot) _pure_; +char* hostname_cleanup(char *s); + +#define machine_name_is_valid(s) hostname_is_valid(s, false) + +bool is_localhost(const char *hostname); +bool is_gateway_hostname(const char *hostname); + +int sethostname_idempotent(const char *s); + +int read_hostname_config(const char *path, char **hostname); diff --git a/src/libbasic/in-addr-util.c b/src/libbasic/in-addr-util.c new file mode 100644 index 0000000000..245107ebb8 --- /dev/null +++ b/src/libbasic/in-addr-util.c @@ -0,0 +1,356 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "in-addr-util.h" +#include "macro.h" +#include "util.h" + +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; + + 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 -EAFNOSUPPORT; +} + +int in_addr_is_link_local(int family, const union in_addr_union *u) { + assert(u); + + if (family == AF_INET) + return (be32toh(u->in.s_addr) & UINT32_C(0xFFFF0000)) == (UINT32_C(169) << 24 | UINT32_C(254) << 16); + + if (family == AF_INET6) + return IN6_IS_ADDR_LINKLOCAL(&u->in6); + + return -EAFNOSUPPORT; +} + +int in_addr_is_localhost(int family, const union in_addr_union *u) { + assert(u); + + if (family == AF_INET) + /* All of 127.x.x.x is localhost. */ + return (be32toh(u->in.s_addr) & UINT32_C(0xFF000000)) == UINT32_C(127) << 24; + + if (family == AF_INET6) + return IN6_IS_ADDR_LOOPBACK(&u->in6); + + return -EAFNOSUPPORT; +} + +int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b) { + assert(a); + assert(b); + + if (family == AF_INET) + return a->in.s_addr == b->in.s_addr; + + if (family == AF_INET6) + return + a->in6.s6_addr32[0] == b->in6.s6_addr32[0] && + a->in6.s6_addr32[1] == b->in6.s6_addr32[1] && + a->in6.s6_addr32[2] == b->in6.s6_addr32[2] && + a->in6.s6_addr32[3] == b->in6.s6_addr32[3]; + + return -EAFNOSUPPORT; +} + +int in_addr_prefix_intersect( + int family, + const union in_addr_union *a, + unsigned aprefixlen, + const union in_addr_union *b, + unsigned bprefixlen) { + + unsigned m; + + assert(a); + assert(b); + + /* Checks whether there are any addresses that are in both + * networks */ + + m = MIN(aprefixlen, bprefixlen); + + if (family == AF_INET) { + uint32_t x, nm; + + x = be32toh(a->in.s_addr ^ b->in.s_addr); + nm = (m == 0) ? 0 : 0xFFFFFFFFUL << (32 - m); + + return (x & nm) == 0; + } + + if (family == AF_INET6) { + unsigned i; + + if (m > 128) + m = 128; + + for (i = 0; i < 16; i++) { + uint8_t x, nm; + + x = a->in6.s6_addr[i] ^ b->in6.s6_addr[i]; + + if (m < 8) + nm = 0xFF << (8 - m); + else + nm = 0xFF; + + if ((x & nm) != 0) + return 0; + + if (m > 8) + m -= 8; + else + m = 0; + } + + return 1; + } + + return -EAFNOSUPPORT; +} + +int in_addr_prefix_next(int family, union in_addr_union *u, unsigned prefixlen) { + assert(u); + + /* Increases the network part of an address by one. Returns + * positive it that succeeds, or 0 if this overflows. */ + + if (prefixlen <= 0) + return 0; + + if (family == AF_INET) { + uint32_t c, n; + + if (prefixlen > 32) + prefixlen = 32; + + c = be32toh(u->in.s_addr); + n = c + (1UL << (32 - prefixlen)); + if (n < c) + return 0; + n &= 0xFFFFFFFFUL << (32 - prefixlen); + + u->in.s_addr = htobe32(n); + return 1; + } + + if (family == AF_INET6) { + struct in6_addr add = {}, result; + uint8_t overflow = 0; + unsigned i; + + if (prefixlen > 128) + prefixlen = 128; + + /* First calculate what we have to add */ + add.s6_addr[(prefixlen-1) / 8] = 1 << (7 - (prefixlen-1) % 8); + + for (i = 16; i > 0; i--) { + unsigned j = i - 1; + + result.s6_addr[j] = u->in6.s6_addr[j] + add.s6_addr[j] + overflow; + overflow = (result.s6_addr[j] < u->in6.s6_addr[j]); + } + + if (overflow) + return 0; + + u->in6 = result; + return 1; + } + + return -EAFNOSUPPORT; +} + +int in_addr_to_string(int family, const union in_addr_union *u, char **ret) { + char *x; + size_t l; + + assert(u); + assert(ret); + + if (family == AF_INET) + l = INET_ADDRSTRLEN; + else if (family == AF_INET6) + l = INET6_ADDRSTRLEN; + else + return -EAFNOSUPPORT; + + x = new(char, l); + if (!x) + return -ENOMEM; + + errno = 0; + if (!inet_ntop(family, u, x, l)) { + free(x); + return errno > 0 ? -errno : -EINVAL; + } + + *ret = x; + return 0; +} + +int in_addr_from_string(int family, const char *s, union in_addr_union *ret) { + + assert(s); + assert(ret); + + if (!IN_SET(family, AF_INET, AF_INET6)) + return -EAFNOSUPPORT; + + errno = 0; + if (inet_pton(family, s, ret) <= 0) + return errno > 0 ? -errno : -EINVAL; + + return 0; +} + +int in_addr_from_string_auto(const char *s, int *family, union in_addr_union *ret) { + int r; + + assert(s); + assert(family); + assert(ret); + + r = in_addr_from_string(AF_INET, s, ret); + if (r >= 0) { + *family = AF_INET; + return 0; + } + + r = in_addr_from_string(AF_INET6, s, ret); + if (r >= 0) { + *family = AF_INET6; + return 0; + } + + return -EINVAL; +} + +unsigned char in_addr_netmask_to_prefixlen(const struct in_addr *addr) { + assert(addr); + + return 32 - u32ctz(be32toh(addr->s_addr)); +} + +struct in_addr* in_addr_prefixlen_to_netmask(struct in_addr *addr, unsigned char prefixlen) { + assert(addr); + assert(prefixlen <= 32); + + /* Shifting beyond 32 is not defined, handle this specially. */ + if (prefixlen == 0) + addr->s_addr = 0; + else + addr->s_addr = htobe32((0xffffffff << (32 - prefixlen)) & 0xffffffff); + + return addr; +} + +int in_addr_default_prefixlen(const struct in_addr *addr, unsigned char *prefixlen) { + uint8_t msb_octet = *(uint8_t*) addr; + + /* addr may not be aligned, so make sure we only access it byte-wise */ + + assert(addr); + assert(prefixlen); + + if (msb_octet < 128) + /* class A, leading bits: 0 */ + *prefixlen = 8; + else if (msb_octet < 192) + /* class B, leading bits 10 */ + *prefixlen = 16; + else if (msb_octet < 224) + /* class C, leading bits 110 */ + *prefixlen = 24; + else + /* class D or E, no default prefixlen */ + return -ERANGE; + + return 0; +} + +int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask) { + unsigned char prefixlen; + int r; + + assert(addr); + assert(mask); + + r = in_addr_default_prefixlen(addr, &prefixlen); + if (r < 0) + return r; + + in_addr_prefixlen_to_netmask(mask, prefixlen); + return 0; +} + +int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen) { + assert(addr); + + if (family == AF_INET) { + struct in_addr mask; + + if (!in_addr_prefixlen_to_netmask(&mask, prefixlen)) + return -EINVAL; + + addr->in.s_addr &= mask.s_addr; + return 0; + } + + if (family == AF_INET6) { + unsigned i; + + for (i = 0; i < 16; i++) { + uint8_t mask; + + if (prefixlen >= 8) { + mask = 0xFF; + prefixlen -= 8; + } else { + mask = 0xFF << (8 - prefixlen); + prefixlen = 0; + } + + addr->in6.s6_addr[i] &= mask; + } + + return 0; + } + + return -EAFNOSUPPORT; +} diff --git a/src/libbasic/in-addr-util.h b/src/libbasic/in-addr-util.h new file mode 100644 index 0000000000..17798ce816 --- /dev/null +++ b/src/libbasic/in-addr-util.h @@ -0,0 +1,59 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include + +#include "macro.h" +#include "util.h" + +union in_addr_union { + struct in_addr in; + struct in6_addr in6; +}; + +struct in_addr_data { + int family; + union in_addr_union address; +}; + +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); +int in_addr_equal(int family, const union in_addr_union *a, const union in_addr_union *b); +int in_addr_prefix_intersect(int family, const union in_addr_union *a, unsigned aprefixlen, const union in_addr_union *b, unsigned bprefixlen); +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_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); +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); +int in_addr_default_subnet_mask(const struct in_addr *addr, struct in_addr *mask); +int in_addr_mask(int family, union in_addr_union *addr, unsigned char prefixlen); + +static inline size_t FAMILY_ADDRESS_SIZE(int family) { + assert(family == AF_INET || family == AF_INET6); + return family == AF_INET6 ? 16 : 4; +} + +#define IN_ADDR_NULL ((union in_addr_union) {}) diff --git a/src/libbasic/io-util.c b/src/libbasic/io-util.c new file mode 100644 index 0000000000..3ec8d61236 --- /dev/null +++ b/src/libbasic/io-util.c @@ -0,0 +1,264 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "io-util.h" +#include "time-util.h" + +int flush_fd(int fd) { + struct pollfd pollfd = { + .fd = fd, + .events = POLLIN, + }; + + for (;;) { + char buf[LINE_MAX]; + ssize_t l; + int r; + + r = poll(&pollfd, 1, 0); + if (r < 0) { + if (errno == EINTR) + continue; + + return -errno; + + } else if (r == 0) + return 0; + + l = read(fd, buf, sizeof(buf)); + if (l < 0) { + + if (errno == EINTR) + continue; + + if (errno == EAGAIN) + return 0; + + return -errno; + } else if (l == 0) + return 0; + } +} + +ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll) { + uint8_t *p = buf; + ssize_t n = 0; + + assert(fd >= 0); + assert(buf); + + /* If called with nbytes == 0, let's call read() at least + * once, to validate the operation */ + + if (nbytes > (size_t) SSIZE_MAX) + return -EINVAL; + + do { + ssize_t k; + + k = read(fd, p, nbytes); + if (k < 0) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN && do_poll) { + + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via read() */ + + (void) fd_wait_for_event(fd, POLLIN, USEC_INFINITY); + continue; + } + + return n > 0 ? n : -errno; + } + + if (k == 0) + return n; + + assert((size_t) k <= nbytes); + + p += k; + nbytes -= k; + n += k; + } while (nbytes > 0); + + return n; +} + +int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll) { + ssize_t n; + + n = loop_read(fd, buf, nbytes, do_poll); + if (n < 0) + return (int) n; + if ((size_t) n != nbytes) + return -EIO; + + return 0; +} + +int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll) { + const uint8_t *p = buf; + + assert(fd >= 0); + assert(buf); + + if (nbytes > (size_t) SSIZE_MAX) + return -EINVAL; + + do { + ssize_t k; + + k = write(fd, p, nbytes); + if (k < 0) { + if (errno == EINTR) + continue; + + if (errno == EAGAIN && do_poll) { + /* We knowingly ignore any return value here, + * and expect that any error/EOF is reported + * via write() */ + + (void) fd_wait_for_event(fd, POLLOUT, USEC_INFINITY); + continue; + } + + return -errno; + } + + if (_unlikely_(nbytes > 0 && k == 0)) /* Can't really happen */ + return -EIO; + + assert((size_t) k <= nbytes); + + p += k; + nbytes -= k; + } while (nbytes > 0); + + return 0; +} + +int pipe_eof(int fd) { + struct pollfd pollfd = { + .fd = fd, + .events = POLLIN|POLLHUP, + }; + + int r; + + r = poll(&pollfd, 1, 0); + if (r < 0) + return -errno; + + if (r == 0) + return 0; + + return pollfd.revents & POLLHUP; +} + +int fd_wait_for_event(int fd, int event, usec_t t) { + + struct pollfd pollfd = { + .fd = fd, + .events = event, + }; + + struct timespec ts; + int r; + + r = ppoll(&pollfd, 1, t == USEC_INFINITY ? NULL : timespec_store(&ts, t), NULL); + if (r < 0) + return -errno; + + if (r == 0) + return 0; + + return pollfd.revents; +} + +static size_t nul_length(const uint8_t *p, size_t sz) { + size_t n = 0; + + while (sz > 0) { + if (*p != 0) + break; + + n++; + p++; + sz--; + } + + return n; +} + +ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length) { + const uint8_t *q, *w, *e; + ssize_t l; + + q = w = p; + e = q + sz; + while (q < e) { + size_t n; + + n = nul_length(q, e - q); + + /* If there are more than the specified run length of + * NUL bytes, or if this is the beginning or the end + * of the buffer, then seek instead of write */ + if ((n > run_length) || + (n > 0 && q == p) || + (n > 0 && q + n >= e)) { + if (q > w) { + l = write(fd, w, q - w); + if (l < 0) + return -errno; + if (l != q -w) + return -EIO; + } + + if (lseek(fd, n, SEEK_CUR) == (off_t) -1) + return -errno; + + q += n; + w = q; + } else if (n > 0) + q += n; + else + q ++; + } + + if (q > w) { + l = write(fd, w, q - w); + if (l < 0) + return -errno; + if (l != q - w) + return -EIO; + } + + return q - (const uint8_t*) p; +} diff --git a/src/libbasic/io-util.h b/src/libbasic/io-util.h new file mode 100644 index 0000000000..142c940d92 --- /dev/null +++ b/src/libbasic/io-util.h @@ -0,0 +1,95 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "macro.h" +#include "time-util.h" + +int flush_fd(int fd); + +ssize_t loop_read(int fd, void *buf, size_t nbytes, bool do_poll); +int loop_read_exact(int fd, void *buf, size_t nbytes, bool do_poll); +int loop_write(int fd, const void *buf, size_t nbytes, bool do_poll); + +int pipe_eof(int fd); + +int fd_wait_for_event(int fd, int event, usec_t timeout); + +ssize_t sparse_write(int fd, const void *p, size_t sz, size_t run_length); + +#define IOVEC_SET_STRING(i, s) \ + do { \ + struct iovec *_i = &(i); \ + char *_s = (char *)(s); \ + _i->iov_base = _s; \ + _i->iov_len = strlen(_s); \ + } while(false) + +static inline size_t IOVEC_TOTAL_SIZE(const struct iovec *i, unsigned n) { + unsigned j; + size_t r = 0; + + for (j = 0; j < n; j++) + r += i[j].iov_len; + + return r; +} + +static inline size_t IOVEC_INCREMENT(struct iovec *i, unsigned n, size_t k) { + unsigned j; + + for (j = 0; j < n; j++) { + size_t sub; + + if (_unlikely_(k <= 0)) + break; + + sub = MIN(i[j].iov_len, k); + i[j].iov_len -= sub; + i[j].iov_base = (uint8_t*) i[j].iov_base + sub; + k -= sub; + } + + return k; +} + +static inline bool FILE_SIZE_VALID(uint64_t l) { + /* ftruncate() and friends take an unsigned file size, but actually cannot deal with file sizes larger than + * 2^63 since the kernel internally handles it as signed value. This call allows checking for this early. */ + + return (l >> 63) == 0; +} + +static inline bool FILE_SIZE_VALID_OR_INFINITY(uint64_t l) { + + /* Same as above, but allows one extra value: -1 as indication for infinity. */ + + if (l == (uint64_t) -1) + return true; + + return FILE_SIZE_VALID(l); + +} diff --git a/src/libbasic/ioprio.h b/src/libbasic/ioprio.h new file mode 100644 index 0000000000..d8bb6eb497 --- /dev/null +++ b/src/libbasic/ioprio.h @@ -0,0 +1,55 @@ +#ifndef IOPRIO_H +#define IOPRIO_H + +/* This is minimal version of Linux' linux/ioprio.h header file, which + * is licensed GPL2 */ + +#include +#include + +/* + * Gives us 8 prio classes with 13-bits of data for each class + */ +#define IOPRIO_BITS (16) +#define IOPRIO_CLASS_SHIFT (13) +#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1) + +#define IOPRIO_PRIO_CLASS(mask) ((mask) >> IOPRIO_CLASS_SHIFT) +#define IOPRIO_PRIO_DATA(mask) ((mask) & IOPRIO_PRIO_MASK) +#define IOPRIO_PRIO_VALUE(class, data) (((class) << IOPRIO_CLASS_SHIFT) | data) + +#define ioprio_valid(mask) (IOPRIO_PRIO_CLASS((mask)) != IOPRIO_CLASS_NONE) + +/* + * These are the io priority groups as implemented by CFQ. RT is the realtime + * class, it always gets premium service. BE is the best-effort scheduling + * class, the default for any process. IDLE is the idle scheduling class, it + * is only served when no one else is using the disk. + */ +enum { + IOPRIO_CLASS_NONE, + IOPRIO_CLASS_RT, + IOPRIO_CLASS_BE, + IOPRIO_CLASS_IDLE, +}; + +/* + * 8 best effort priority levels are supported + */ +#define IOPRIO_BE_NR (8) + +enum { + IOPRIO_WHO_PROCESS = 1, + IOPRIO_WHO_PGRP, + IOPRIO_WHO_USER, +}; + +static inline int ioprio_set(int which, int who, int ioprio) { + return syscall(__NR_ioprio_set, which, who, ioprio); +} + +static inline int ioprio_get(int which, int who) { + return syscall(__NR_ioprio_get, which, who); +} + +#endif diff --git a/src/libbasic/json.c b/src/libbasic/json.c new file mode 100644 index 0000000000..daa98fc815 --- /dev/null +++ b/src/libbasic/json.c @@ -0,0 +1,871 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "hexdecoct.h" +#include "json.h" +#include "macro.h" +#include "string-util.h" +#include "utf8.h" + +int json_variant_new(JsonVariant **ret, JsonVariantType type) { + JsonVariant *v; + + v = new0(JsonVariant, 1); + if (!v) + return -ENOMEM; + v->type = type; + *ret = v; + return 0; +} + +static int json_variant_deep_copy(JsonVariant *ret, JsonVariant *variant) { + int r; + + assert(ret); + assert(variant); + + ret->type = variant->type; + ret->size = variant->size; + + if (variant->type == JSON_VARIANT_STRING) { + ret->string = memdup(variant->string, variant->size+1); + if (!ret->string) + return -ENOMEM; + } else if (variant->type == JSON_VARIANT_ARRAY || variant->type == JSON_VARIANT_OBJECT) { + size_t i; + + ret->objects = new0(JsonVariant, variant->size); + if (!ret->objects) + return -ENOMEM; + + for (i = 0; i < variant->size; ++i) { + r = json_variant_deep_copy(&ret->objects[i], &variant->objects[i]); + if (r < 0) + return r; + } + } else + ret->value = variant->value; + + return 0; +} + +static JsonVariant *json_object_unref(JsonVariant *variant); + +static JsonVariant *json_variant_unref_inner(JsonVariant *variant) { + if (!variant) + return NULL; + + if (variant->type == JSON_VARIANT_ARRAY || variant->type == JSON_VARIANT_OBJECT) + return json_object_unref(variant); + else if (variant->type == JSON_VARIANT_STRING) + free(variant->string); + + return NULL; +} + +static JsonVariant *json_raw_unref(JsonVariant *variant, size_t size) { + if (!variant) + return NULL; + + for (size_t i = 0; i < size; ++i) + json_variant_unref_inner(&variant[i]); + + free(variant); + return NULL; +} + +static JsonVariant *json_object_unref(JsonVariant *variant) { + size_t i; + + assert(variant); + + if (!variant->objects) + return NULL; + + for (i = 0; i < variant->size; ++i) + json_variant_unref_inner(&variant->objects[i]); + + free(variant->objects); + return NULL; +} + +static JsonVariant **json_variant_array_unref(JsonVariant **variant) { + size_t i = 0; + JsonVariant *p = NULL; + + if (!variant) + return NULL; + + while((p = (variant[i++])) != NULL) { + if (p->type == JSON_VARIANT_STRING) + free(p->string); + free(p); + } + + free(variant); + + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(JsonVariant **, json_variant_array_unref); + +JsonVariant *json_variant_unref(JsonVariant *variant) { + if (!variant) + return NULL; + + if (variant->type == JSON_VARIANT_ARRAY || variant->type == JSON_VARIANT_OBJECT) + json_object_unref(variant); + else if (variant->type == JSON_VARIANT_STRING) + free(variant->string); + + free(variant); + + return NULL; +} + +char *json_variant_string(JsonVariant *variant){ + assert(variant); + assert(variant->type == JSON_VARIANT_STRING); + + return variant->string; +} + +bool json_variant_bool(JsonVariant *variant) { + assert(variant); + assert(variant->type == JSON_VARIANT_BOOLEAN); + + return variant->value.boolean; +} + +intmax_t json_variant_integer(JsonVariant *variant) { + assert(variant); + assert(variant->type == JSON_VARIANT_INTEGER); + + return variant->value.integer; +} + +double json_variant_real(JsonVariant *variant) { + assert(variant); + assert(variant->type == JSON_VARIANT_REAL); + + return variant->value.real; +} + +JsonVariant *json_variant_element(JsonVariant *variant, unsigned index) { + assert(variant); + assert(variant->type == JSON_VARIANT_ARRAY || variant->type == JSON_VARIANT_OBJECT); + assert(index < variant->size); + assert(variant->objects); + + return &variant->objects[index]; +} + +JsonVariant *json_variant_value(JsonVariant *variant, const char *key) { + size_t i; + + assert(variant); + assert(variant->type == JSON_VARIANT_OBJECT); + assert(variant->objects); + + for (i = 0; i < variant->size; i += 2) { + JsonVariant *p = &variant->objects[i]; + if (p->type == JSON_VARIANT_STRING && streq(key, p->string)) + return &variant->objects[i + 1]; + } + + return NULL; +} + +static void inc_lines(unsigned *line, const char *s, size_t n) { + const char *p = s; + + if (!line) + return; + + for (;;) { + const char *f; + + f = memchr(p, '\n', n); + if (!f) + return; + + n -= (f - p) + 1; + p = f + 1; + (*line)++; + } +} + +static int unhex_ucs2(const char *c, uint16_t *ret) { + int aa, bb, cc, dd; + uint16_t x; + + assert(c); + assert(ret); + + aa = unhexchar(c[0]); + if (aa < 0) + return -EINVAL; + + bb = unhexchar(c[1]); + if (bb < 0) + return -EINVAL; + + cc = unhexchar(c[2]); + if (cc < 0) + return -EINVAL; + + dd = unhexchar(c[3]); + if (dd < 0) + return -EINVAL; + + x = ((uint16_t) aa << 12) | + ((uint16_t) bb << 8) | + ((uint16_t) cc << 4) | + ((uint16_t) dd); + + if (x <= 0) + return -EINVAL; + + *ret = x; + + return 0; +} + +static int json_parse_string(const char **p, char **ret) { + _cleanup_free_ char *s = NULL; + size_t n = 0, allocated = 0; + const char *c; + + assert(p); + assert(*p); + assert(ret); + + c = *p; + + if (*c != '"') + return -EINVAL; + + c++; + + for (;;) { + int len; + + /* Check for EOF */ + if (*c == 0) + return -EINVAL; + + /* Check for control characters 0x00..0x1f */ + if (*c > 0 && *c < ' ') + return -EINVAL; + + /* Check for control character 0x7f */ + if (*c == 0x7f) + return -EINVAL; + + if (*c == '"') { + if (!s) { + s = strdup(""); + if (!s) + return -ENOMEM; + } else + s[n] = 0; + + *p = c + 1; + + *ret = s; + s = NULL; + return JSON_STRING; + } + + if (*c == '\\') { + char ch = 0; + c++; + + if (*c == 0) + return -EINVAL; + + if (IN_SET(*c, '"', '\\', '/')) + ch = *c; + else if (*c == 'b') + ch = '\b'; + else if (*c == 'f') + ch = '\f'; + else if (*c == 'n') + ch = '\n'; + else if (*c == 'r') + ch = '\r'; + else if (*c == 't') + ch = '\t'; + else if (*c == 'u') { + char16_t x; + int r; + + r = unhex_ucs2(c + 1, &x); + if (r < 0) + return r; + + c += 5; + + if (!GREEDY_REALLOC(s, allocated, n + 4)) + return -ENOMEM; + + if (!utf16_is_surrogate(x)) + n += utf8_encode_unichar(s + n, (char32_t) x); + else if (utf16_is_trailing_surrogate(x)) + return -EINVAL; + else { + char16_t y; + + if (c[0] != '\\' || c[1] != 'u') + return -EINVAL; + + r = unhex_ucs2(c + 2, &y); + if (r < 0) + return r; + + c += 6; + + if (!utf16_is_trailing_surrogate(y)) + return -EINVAL; + + n += utf8_encode_unichar(s + n, utf16_surrogate_pair_to_unichar(x, y)); + } + + continue; + } else + return -EINVAL; + + if (!GREEDY_REALLOC(s, allocated, n + 2)) + return -ENOMEM; + + s[n++] = ch; + c ++; + continue; + } + + len = utf8_encoded_valid_unichar(c); + if (len < 0) + return len; + + if (!GREEDY_REALLOC(s, allocated, n + len + 1)) + return -ENOMEM; + + memcpy(s + n, c, len); + n += len; + c += len; + } +} + +static int json_parse_number(const char **p, union json_value *ret) { + bool negative = false, exponent_negative = false, is_double = false; + double x = 0.0, y = 0.0, exponent = 0.0, shift = 1.0; + intmax_t i = 0; + const char *c; + + assert(p); + assert(*p); + assert(ret); + + c = *p; + + if (*c == '-') { + negative = true; + c++; + } + + if (*c == '0') + c++; + else { + if (!strchr("123456789", *c) || *c == 0) + return -EINVAL; + + do { + if (!is_double) { + int64_t t; + + t = 10 * i + (*c - '0'); + if (t < i) /* overflow */ + is_double = false; + else + i = t; + } + + x = 10.0 * x + (*c - '0'); + c++; + } while (strchr("0123456789", *c) && *c != 0); + } + + if (*c == '.') { + is_double = true; + c++; + + if (!strchr("0123456789", *c) || *c == 0) + return -EINVAL; + + do { + y = 10.0 * y + (*c - '0'); + shift = 10.0 * shift; + c++; + } while (strchr("0123456789", *c) && *c != 0); + } + + if (*c == 'e' || *c == 'E') { + is_double = true; + c++; + + if (*c == '-') { + exponent_negative = true; + c++; + } else if (*c == '+') + c++; + + if (!strchr("0123456789", *c) || *c == 0) + return -EINVAL; + + do { + exponent = 10.0 * exponent + (*c - '0'); + c++; + } while (strchr("0123456789", *c) && *c != 0); + } + + *p = c; + + if (is_double) { + ret->real = ((negative ? -1.0 : 1.0) * (x + (y / shift))) * exp10((exponent_negative ? -1.0 : 1.0) * exponent); + return JSON_REAL; + } else { + ret->integer = negative ? -i : i; + return JSON_INTEGER; + } +} + +int json_tokenize( + const char **p, + char **ret_string, + union json_value *ret_value, + void **state, + unsigned *line) { + + const char *c; + int t; + int r; + + enum { + STATE_NULL, + STATE_VALUE, + STATE_VALUE_POST, + }; + + assert(p); + assert(*p); + assert(ret_string); + assert(ret_value); + assert(state); + + t = PTR_TO_INT(*state); + c = *p; + + if (t == STATE_NULL) { + if (line) + *line = 1; + t = STATE_VALUE; + } + + for (;;) { + const char *b; + + b = c + strspn(c, WHITESPACE); + if (*b == 0) + return JSON_END; + + inc_lines(line, c, b - c); + c = b; + + switch (t) { + + case STATE_VALUE: + + if (*c == '{') { + *ret_string = NULL; + *ret_value = JSON_VALUE_NULL; + *p = c + 1; + *state = INT_TO_PTR(STATE_VALUE); + return JSON_OBJECT_OPEN; + + } else if (*c == '}') { + *ret_string = NULL; + *ret_value = JSON_VALUE_NULL; + *p = c + 1; + *state = INT_TO_PTR(STATE_VALUE_POST); + return JSON_OBJECT_CLOSE; + + } else if (*c == '[') { + *ret_string = NULL; + *ret_value = JSON_VALUE_NULL; + *p = c + 1; + *state = INT_TO_PTR(STATE_VALUE); + return JSON_ARRAY_OPEN; + + } else if (*c == ']') { + *ret_string = NULL; + *ret_value = JSON_VALUE_NULL; + *p = c + 1; + *state = INT_TO_PTR(STATE_VALUE_POST); + return JSON_ARRAY_CLOSE; + + } else if (*c == '"') { + r = json_parse_string(&c, ret_string); + if (r < 0) + return r; + + *ret_value = JSON_VALUE_NULL; + *p = c; + *state = INT_TO_PTR(STATE_VALUE_POST); + return r; + + } else if (strchr("-0123456789", *c)) { + r = json_parse_number(&c, ret_value); + if (r < 0) + return r; + + *ret_string = NULL; + *p = c; + *state = INT_TO_PTR(STATE_VALUE_POST); + return r; + + } else if (startswith(c, "true")) { + *ret_string = NULL; + ret_value->boolean = true; + *p = c + 4; + *state = INT_TO_PTR(STATE_VALUE_POST); + return JSON_BOOLEAN; + + } else if (startswith(c, "false")) { + *ret_string = NULL; + ret_value->boolean = false; + *p = c + 5; + *state = INT_TO_PTR(STATE_VALUE_POST); + return JSON_BOOLEAN; + + } else if (startswith(c, "null")) { + *ret_string = NULL; + *ret_value = JSON_VALUE_NULL; + *p = c + 4; + *state = INT_TO_PTR(STATE_VALUE_POST); + return JSON_NULL; + + } else + return -EINVAL; + + case STATE_VALUE_POST: + + if (*c == ':') { + *ret_string = NULL; + *ret_value = JSON_VALUE_NULL; + *p = c + 1; + *state = INT_TO_PTR(STATE_VALUE); + return JSON_COLON; + } else if (*c == ',') { + *ret_string = NULL; + *ret_value = JSON_VALUE_NULL; + *p = c + 1; + *state = INT_TO_PTR(STATE_VALUE); + return JSON_COMMA; + } else if (*c == '}') { + *ret_string = NULL; + *ret_value = JSON_VALUE_NULL; + *p = c + 1; + *state = INT_TO_PTR(STATE_VALUE_POST); + return JSON_OBJECT_CLOSE; + } else if (*c == ']') { + *ret_string = NULL; + *ret_value = JSON_VALUE_NULL; + *p = c + 1; + *state = INT_TO_PTR(STATE_VALUE_POST); + return JSON_ARRAY_CLOSE; + } else + return -EINVAL; + } + + } +} + +static bool json_is_value(JsonVariant *var) { + assert(var); + + return var->type != JSON_VARIANT_CONTROL; +} + +static int json_scoped_parse(JsonVariant **tokens, size_t *i, size_t n, JsonVariant *scope) { + bool arr = scope->type == JSON_VARIANT_ARRAY; + int terminator = arr ? JSON_ARRAY_CLOSE : JSON_OBJECT_CLOSE; + size_t allocated = 0, size = 0; + JsonVariant *key = NULL, *value = NULL, *var = NULL, *items = NULL; + enum { + STATE_KEY, + STATE_COLON, + STATE_COMMA, + STATE_VALUE + } state = arr ? STATE_VALUE : STATE_KEY; + + assert(tokens); + assert(i); + assert(scope); + + while((var = *i < n ? tokens[(*i)++] : NULL) != NULL) { + bool stopper; + int r; + + stopper = !json_is_value(var) && var->value.integer == terminator; + + if (stopper) { + if (state != STATE_COMMA && size > 0) + goto error; + + goto out; + } + + if (state == STATE_KEY) { + if (var->type != JSON_VARIANT_STRING) + goto error; + else { + key = var; + state = STATE_COLON; + } + } + else if (state == STATE_COLON) { + if (key == NULL) + goto error; + + if (json_is_value(var)) + goto error; + + if (var->value.integer != JSON_COLON) + goto error; + + state = STATE_VALUE; + } + else if (state == STATE_VALUE) { + _cleanup_json_variant_unref_ JsonVariant *v = NULL; + size_t toadd = arr ? 1 : 2; + + if (!json_is_value(var)) { + int type = (var->value.integer == JSON_ARRAY_OPEN) ? JSON_VARIANT_ARRAY : JSON_VARIANT_OBJECT; + + r = json_variant_new(&v, type); + if (r < 0) + goto error; + + r = json_scoped_parse(tokens, i, n, v); + if (r < 0) + goto error; + + value = v; + } + else + value = var; + + if(!GREEDY_REALLOC(items, allocated, size + toadd)) + goto error; + + if (arr) { + r = json_variant_deep_copy(&items[size], value); + if (r < 0) + goto error; + } else { + r = json_variant_deep_copy(&items[size], key); + if (r < 0) + goto error; + + r = json_variant_deep_copy(&items[size+1], value); + if (r < 0) + goto error; + } + + size += toadd; + state = STATE_COMMA; + } + else if (state == STATE_COMMA) { + if (json_is_value(var)) + goto error; + + if (var->value.integer != JSON_COMMA) + goto error; + + key = NULL; + value = NULL; + + state = arr ? STATE_VALUE : STATE_KEY; + } + } + +error: + json_raw_unref(items, size); + return -EBADMSG; + +out: + scope->size = size; + scope->objects = items; + + return scope->type; +} + +static int json_parse_tokens(JsonVariant **tokens, size_t ntokens, JsonVariant **rv) { + size_t it = 0; + int r; + JsonVariant *e; + _cleanup_json_variant_unref_ JsonVariant *p = NULL; + + assert(tokens); + assert(ntokens); + + e = tokens[it++]; + r = json_variant_new(&p, JSON_VARIANT_OBJECT); + if (r < 0) + return r; + + if (e->type != JSON_VARIANT_CONTROL && e->value.integer != JSON_OBJECT_OPEN) + return -EBADMSG; + + r = json_scoped_parse(tokens, &it, ntokens, p); + if (r < 0) + return r; + + *rv = p; + p = NULL; + + return 0; +} + +static int json_tokens(const char *string, size_t size, JsonVariant ***tokens, size_t *n) { + _cleanup_free_ char *buf = NULL; + _cleanup_(json_variant_array_unrefp) JsonVariant **items = NULL; + union json_value v = {}; + void *json_state = NULL; + const char *p; + int t, r; + size_t allocated = 0, s = 0; + + assert(string); + assert(n); + + if (size <= 0) + return -EBADMSG; + + buf = strndup(string, size); + if (!buf) + return -ENOMEM; + + p = buf; + for (;;) { + _cleanup_json_variant_unref_ JsonVariant *var = NULL; + _cleanup_free_ char *rstr = NULL; + + t = json_tokenize(&p, &rstr, &v, &json_state, NULL); + + if (t < 0) + return t; + else if (t == JSON_END) + break; + + if (t <= JSON_ARRAY_CLOSE) { + r = json_variant_new(&var, JSON_VARIANT_CONTROL); + if (r < 0) + return r; + var->value.integer = t; + } else { + switch (t) { + case JSON_STRING: + r = json_variant_new(&var, JSON_VARIANT_STRING); + if (r < 0) + return r; + var->size = strlen(rstr); + var->string = strdup(rstr); + if (!var->string) { + return -ENOMEM; + } + break; + case JSON_INTEGER: + r = json_variant_new(&var, JSON_VARIANT_INTEGER); + if (r < 0) + return r; + var->value = v; + break; + case JSON_REAL: + r = json_variant_new(&var, JSON_VARIANT_REAL); + if (r < 0) + return r; + var->value = v; + break; + case JSON_BOOLEAN: + r = json_variant_new(&var, JSON_VARIANT_BOOLEAN); + if (r < 0) + return r; + var->value = v; + break; + case JSON_NULL: + r = json_variant_new(&var, JSON_VARIANT_NULL); + if (r < 0) + return r; + break; + } + } + + if (!GREEDY_REALLOC(items, allocated, s+2)) + return -ENOMEM; + + items[s++] = var; + items[s] = NULL; + var = NULL; + } + + *n = s; + *tokens = items; + items = NULL; + + return 0; +} + +int json_parse(const char *string, JsonVariant **rv) { + _cleanup_(json_variant_array_unrefp) JsonVariant **s = NULL; + JsonVariant *v = NULL; + size_t n = 0; + int r; + + assert(string); + assert(rv); + + r = json_tokens(string, strlen(string), &s, &n); + if (r < 0) + return r; + + r = json_parse_tokens(s, n, &v); + if (r < 0) + return r; + + *rv = v; + return 0; +} diff --git a/src/libbasic/json.h b/src/libbasic/json.h new file mode 100644 index 0000000000..a4509f680f --- /dev/null +++ b/src/libbasic/json.h @@ -0,0 +1,90 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include + +#include "macro.h" +#include "util.h" + +enum { + JSON_END, + JSON_COLON, + JSON_COMMA, + JSON_OBJECT_OPEN, + JSON_OBJECT_CLOSE, + JSON_ARRAY_OPEN, + JSON_ARRAY_CLOSE, + JSON_STRING, + JSON_REAL, + JSON_INTEGER, + JSON_BOOLEAN, + JSON_NULL, +}; + +typedef enum { + JSON_VARIANT_CONTROL, + JSON_VARIANT_STRING, + JSON_VARIANT_INTEGER, + JSON_VARIANT_BOOLEAN, + JSON_VARIANT_REAL, + JSON_VARIANT_ARRAY, + JSON_VARIANT_OBJECT, + JSON_VARIANT_NULL +} JsonVariantType; + +union json_value { + bool boolean; + double real; + intmax_t integer; +}; + +typedef struct JsonVariant { + JsonVariantType type; + size_t size; + union { + char *string; + struct JsonVariant *objects; + union json_value value; + }; +} JsonVariant; + +int json_variant_new(JsonVariant **ret, JsonVariantType type); +JsonVariant *json_variant_unref(JsonVariant *v); + +DEFINE_TRIVIAL_CLEANUP_FUNC(JsonVariant *, json_variant_unref); +#define _cleanup_json_variant_unref_ _cleanup_(json_variant_unrefp) + +char *json_variant_string(JsonVariant *v); +bool json_variant_bool(JsonVariant *v); +intmax_t json_variant_integer(JsonVariant *v); +double json_variant_real(JsonVariant *v); + +JsonVariant *json_variant_element(JsonVariant *v, unsigned index); +JsonVariant *json_variant_value(JsonVariant *v, const char *key); + +#define JSON_VALUE_NULL ((union json_value) {}) + +int json_tokenize(const char **p, char **ret_string, union json_value *ret_value, void **state, unsigned *line); + +int json_parse(const char *string, JsonVariant **rv); +int json_parse_measure(const char *string, size_t *size); diff --git a/src/libbasic/label.c b/src/libbasic/label.c new file mode 100644 index 0000000000..f5ab855d32 --- /dev/null +++ b/src/libbasic/label.c @@ -0,0 +1,82 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#include "label.h" +#include "macro.h" +#include "selinux-util.h" +#include "smack-util.h" + +int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { + int r, q; + + r = mac_selinux_fix(path, ignore_enoent, ignore_erofs); + q = mac_smack_fix(path, ignore_enoent, ignore_erofs); + + if (r < 0) + return r; + if (q < 0) + return q; + + return 0; +} + +int mkdir_label(const char *path, mode_t mode) { + int r; + + assert(path); + + r = mac_selinux_create_file_prepare(path, S_IFDIR); + if (r < 0) + return r; + + if (mkdir(path, mode) < 0) + r = -errno; + + mac_selinux_create_file_clear(); + + if (r < 0) + return r; + + return mac_smack_fix(path, false, false); +} + +int symlink_label(const char *old_path, const char *new_path) { + int r; + + assert(old_path); + assert(new_path); + + r = mac_selinux_create_file_prepare(new_path, S_IFLNK); + if (r < 0) + return r; + + if (symlink(old_path, new_path) < 0) + r = -errno; + + mac_selinux_create_file_clear(); + + if (r < 0) + return r; + + return mac_smack_fix(new_path, false, false); +} diff --git a/src/libbasic/label.h b/src/libbasic/label.h new file mode 100644 index 0000000000..3e9251aa71 --- /dev/null +++ b/src/libbasic/label.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +int label_fix(const char *path, bool ignore_enoent, bool ignore_erofs); + +int mkdir_label(const char *path, mode_t mode); +int symlink_label(const char *old_path, const char *new_path); diff --git a/src/libbasic/list.h b/src/libbasic/list.h new file mode 100644 index 0000000000..c68185f587 --- /dev/null +++ b/src/libbasic/list.h @@ -0,0 +1,182 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +/* The head of the linked list. Use this in the structure that shall + * contain the head of the linked list */ +#define LIST_HEAD(t,name) \ + t *name + +/* The pointers in the linked list's items. Use this in the item structure */ +#define LIST_FIELDS(t,name) \ + t *name##_next, *name##_prev + +/* Initialize the list's head */ +#define LIST_HEAD_INIT(head) \ + do { \ + (head) = NULL; } \ + while(false) + +/* Initialize a list item */ +#define LIST_INIT(name,item) \ + do { \ + typeof(*(item)) *_item = (item); \ + assert(_item); \ + _item->name##_prev = _item->name##_next = NULL; \ + } while(false) + +/* Prepend an item to the list */ +#define LIST_PREPEND(name,head,item) \ + do { \ + typeof(*(head)) **_head = &(head), *_item = (item); \ + assert(_item); \ + if ((_item->name##_next = *_head)) \ + _item->name##_next->name##_prev = _item; \ + _item->name##_prev = NULL; \ + *_head = _item; \ + } while(false) + +/* Append an item to the list */ +#define LIST_APPEND(name,head,item) \ + do { \ + typeof(*(head)) *_tail; \ + LIST_FIND_TAIL(name,head,_tail); \ + LIST_INSERT_AFTER(name,head,_tail,item); \ + } while(false) + +/* Remove an item from the list */ +#define LIST_REMOVE(name,head,item) \ + do { \ + typeof(*(head)) **_head = &(head), *_item = (item); \ + assert(_item); \ + if (_item->name##_next) \ + _item->name##_next->name##_prev = _item->name##_prev; \ + if (_item->name##_prev) \ + _item->name##_prev->name##_next = _item->name##_next; \ + else { \ + assert(*_head == _item); \ + *_head = _item->name##_next; \ + } \ + _item->name##_next = _item->name##_prev = NULL; \ + } while(false) + +/* Find the head of the list */ +#define LIST_FIND_HEAD(name,item,head) \ + do { \ + typeof(*(item)) *_item = (item); \ + if (!_item) \ + (head) = NULL; \ + else { \ + while (_item->name##_prev) \ + _item = _item->name##_prev; \ + (head) = _item; \ + } \ + } while (false) + +/* Find the tail of the list */ +#define LIST_FIND_TAIL(name,item,tail) \ + do { \ + typeof(*(item)) *_item = (item); \ + if (!_item) \ + (tail) = NULL; \ + else { \ + while (_item->name##_next) \ + _item = _item->name##_next; \ + (tail) = _item; \ + } \ + } while (false) + +/* Insert an item after another one (a = where, b = what) */ +#define LIST_INSERT_AFTER(name,head,a,b) \ + do { \ + typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \ + assert(_b); \ + if (!_a) { \ + if ((_b->name##_next = *_head)) \ + _b->name##_next->name##_prev = _b; \ + _b->name##_prev = NULL; \ + *_head = _b; \ + } else { \ + if ((_b->name##_next = _a->name##_next)) \ + _b->name##_next->name##_prev = _b; \ + _b->name##_prev = _a; \ + _a->name##_next = _b; \ + } \ + } while(false) + +/* Insert an item before another one (a = where, b = what) */ +#define LIST_INSERT_BEFORE(name,head,a,b) \ + do { \ + typeof(*(head)) **_head = &(head), *_a = (a), *_b = (b); \ + assert(_b); \ + if (!_a) { \ + if (!*_head) { \ + _b->name##_next = NULL; \ + _b->name##_prev = NULL; \ + *_head = _b; \ + } else { \ + typeof(*(head)) *_tail = (head); \ + while (_tail->name##_next) \ + _tail = _tail->name##_next; \ + _b->name##_next = NULL; \ + _b->name##_prev = _tail; \ + _tail->name##_next = _b; \ + } \ + } else { \ + if ((_b->name##_prev = _a->name##_prev)) \ + _b->name##_prev->name##_next = _b; \ + _b->name##_next = _a; \ + _a->name##_prev = _b; \ + } \ + } while(false) + +#define LIST_JUST_US(name,item) \ + (!(item)->name##_prev && !(item)->name##_next) \ + +#define LIST_FOREACH(name,i,head) \ + for ((i) = (head); (i); (i) = (i)->name##_next) + +#define LIST_FOREACH_SAFE(name,i,n,head) \ + for ((i) = (head); (i) && (((n) = (i)->name##_next), 1); (i) = (n)) + +#define LIST_FOREACH_BEFORE(name,i,p) \ + for ((i) = (p)->name##_prev; (i); (i) = (i)->name##_prev) + +#define LIST_FOREACH_AFTER(name,i,p) \ + for ((i) = (p)->name##_next; (i); (i) = (i)->name##_next) + +/* Iterate through all the members of the list p is included in, but skip over p */ +#define LIST_FOREACH_OTHERS(name,i,p) \ + for (({ \ + (i) = (p); \ + while ((i) && (i)->name##_prev) \ + (i) = (i)->name##_prev; \ + if ((i) == (p)) \ + (i) = (p)->name##_next; \ + }); \ + (i); \ + (i) = (i)->name##_next == (p) ? (p)->name##_next : (i)->name##_next) + +/* Loop starting from p->next until p->prev. + p can be adjusted meanwhile. */ +#define LIST_LOOP_BUT_ONE(name,i,head,p) \ + for ((i) = (p)->name##_next ? (p)->name##_next : (head); \ + (i) != (p); \ + (i) = (i)->name##_next ? (i)->name##_next : (head)) diff --git a/src/libbasic/locale-util.c b/src/libbasic/locale-util.c new file mode 100644 index 0000000000..cda6b2895d --- /dev/null +++ b/src/libbasic/locale-util.c @@ -0,0 +1,319 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dirent-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "locale-util.h" +#include "path-util.h" +#include "set.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "utf8.h" + +static int add_locales_from_archive(Set *locales) { + /* Stolen from glibc... */ + + struct locarhead { + uint32_t magic; + /* Serial number. */ + uint32_t serial; + /* Name hash table. */ + uint32_t namehash_offset; + uint32_t namehash_used; + uint32_t namehash_size; + /* String table. */ + uint32_t string_offset; + uint32_t string_used; + uint32_t string_size; + /* Table with locale records. */ + uint32_t locrectab_offset; + uint32_t locrectab_used; + uint32_t locrectab_size; + /* MD5 sum hash table. */ + uint32_t sumhash_offset; + uint32_t sumhash_used; + uint32_t sumhash_size; + }; + + struct namehashent { + /* Hash value of the name. */ + uint32_t hashval; + /* Offset of the name in the string table. */ + uint32_t name_offset; + /* Offset of the locale record. */ + uint32_t locrec_offset; + }; + + const struct locarhead *h; + const struct namehashent *e; + const void *p = MAP_FAILED; + _cleanup_close_ int fd = -1; + size_t sz = 0; + struct stat st; + unsigned i; + int r; + + fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return errno == ENOENT ? 0 : -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + if (!S_ISREG(st.st_mode)) + return -EBADMSG; + + if (st.st_size < (off_t) sizeof(struct locarhead)) + return -EBADMSG; + + p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (p == MAP_FAILED) + return -errno; + + h = (const struct locarhead *) p; + if (h->magic != 0xde020109 || + h->namehash_offset + h->namehash_size > st.st_size || + h->string_offset + h->string_size > st.st_size || + h->locrectab_offset + h->locrectab_size > st.st_size || + h->sumhash_offset + h->sumhash_size > st.st_size) { + r = -EBADMSG; + goto finish; + } + + e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset); + for (i = 0; i < h->namehash_size; i++) { + char *z; + + if (e[i].locrec_offset == 0) + continue; + + if (!utf8_is_valid((char*) p + e[i].name_offset)) + continue; + + z = strdup((char*) p + e[i].name_offset); + if (!z) { + r = -ENOMEM; + goto finish; + } + + r = set_consume(locales, z); + if (r < 0) + goto finish; + } + + r = 0; + + finish: + if (p != MAP_FAILED) + munmap((void*) p, sz); + + return r; +} + +static int add_locales_from_libdir (Set *locales) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *entry; + int r; + + dir = opendir("/usr/lib/locale"); + if (!dir) + return errno == ENOENT ? 0 : -errno; + + FOREACH_DIRENT(entry, dir, return -errno) { + char *z; + + if (entry->d_type != DT_DIR) + continue; + + z = strdup(entry->d_name); + if (!z) + return -ENOMEM; + + r = set_consume(locales, z); + if (r < 0 && r != -EEXIST) + return r; + } + + return 0; +} + +int get_locales(char ***ret) { + _cleanup_set_free_ Set *locales = NULL; + _cleanup_strv_free_ char **l = NULL; + int r; + + locales = set_new(&string_hash_ops); + if (!locales) + return -ENOMEM; + + r = add_locales_from_archive(locales); + if (r < 0 && r != -ENOENT) + return r; + + r = add_locales_from_libdir(locales); + if (r < 0) + return r; + + l = set_get_strv(locales); + if (!l) + return -ENOMEM; + + strv_sort(l); + + *ret = l; + l = NULL; + + return 0; +} + +bool locale_is_valid(const char *name) { + + if (isempty(name)) + return false; + + if (strlen(name) >= 128) + return false; + + if (!utf8_is_valid(name)) + return false; + + if (!filename_is_valid(name)) + return false; + + if (!string_is_safe(name)) + return false; + + return true; +} + +void init_gettext(void) { + setlocale(LC_ALL, ""); + textdomain(GETTEXT_PACKAGE); +} + +bool is_locale_utf8(void) { + const char *set; + static int cached_answer = -1; + + /* Note that we default to 'true' here, since today UTF8 is + * pretty much supported everywhere. */ + + if (cached_answer >= 0) + goto out; + + if (!setlocale(LC_ALL, "")) { + cached_answer = true; + goto out; + } + + set = nl_langinfo(CODESET); + if (!set) { + cached_answer = true; + goto out; + } + + if (streq(set, "UTF-8")) { + cached_answer = true; + goto out; + } + + /* For LC_CTYPE=="C" return true, because CTYPE is effectly + * unset and everything can do to UTF-8 nowadays. */ + set = setlocale(LC_CTYPE, NULL); + if (!set) { + cached_answer = true; + goto out; + } + + /* Check result, but ignore the result if C was set + * explicitly. */ + cached_answer = + STR_IN_SET(set, "C", "POSIX") && + !getenv("LC_ALL") && + !getenv("LC_CTYPE") && + !getenv("LANG"); + +out: + return (bool) cached_answer; +} + + +const char *draw_special_char(DrawSpecialChar ch) { + + static const char *draw_table[2][_DRAW_SPECIAL_CHAR_MAX] = { + + /* UTF-8 */ { + [DRAW_TREE_VERTICAL] = "\342\224\202 ", /* │ */ + [DRAW_TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */ + [DRAW_TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */ + [DRAW_TREE_SPACE] = " ", /* */ + [DRAW_TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */ + [DRAW_BLACK_CIRCLE] = "\342\227\217", /* ● */ + [DRAW_ARROW] = "\342\206\222", /* → */ + [DRAW_DASH] = "\342\200\223", /* – */ + }, + + /* ASCII fallback */ { + [DRAW_TREE_VERTICAL] = "| ", + [DRAW_TREE_BRANCH] = "|-", + [DRAW_TREE_RIGHT] = "`-", + [DRAW_TREE_SPACE] = " ", + [DRAW_TRIANGULAR_BULLET] = ">", + [DRAW_BLACK_CIRCLE] = "*", + [DRAW_ARROW] = "->", + [DRAW_DASH] = "-", + } + }; + + return draw_table[!is_locale_utf8()][ch]; +} + +static const char * const locale_variable_table[_VARIABLE_LC_MAX] = { + [VARIABLE_LANG] = "LANG", + [VARIABLE_LANGUAGE] = "LANGUAGE", + [VARIABLE_LC_CTYPE] = "LC_CTYPE", + [VARIABLE_LC_NUMERIC] = "LC_NUMERIC", + [VARIABLE_LC_TIME] = "LC_TIME", + [VARIABLE_LC_COLLATE] = "LC_COLLATE", + [VARIABLE_LC_MONETARY] = "LC_MONETARY", + [VARIABLE_LC_MESSAGES] = "LC_MESSAGES", + [VARIABLE_LC_PAPER] = "LC_PAPER", + [VARIABLE_LC_NAME] = "LC_NAME", + [VARIABLE_LC_ADDRESS] = "LC_ADDRESS", + [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE", + [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT", + [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION" +}; + +DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable); diff --git a/src/libbasic/locale-util.h b/src/libbasic/locale-util.h new file mode 100644 index 0000000000..b0f9679286 --- /dev/null +++ b/src/libbasic/locale-util.h @@ -0,0 +1,73 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include + +#include "macro.h" + +typedef enum LocaleVariable { + /* We don't list LC_ALL here on purpose. People should be + * using LANG instead. */ + + VARIABLE_LANG, + VARIABLE_LANGUAGE, + VARIABLE_LC_CTYPE, + VARIABLE_LC_NUMERIC, + VARIABLE_LC_TIME, + VARIABLE_LC_COLLATE, + VARIABLE_LC_MONETARY, + VARIABLE_LC_MESSAGES, + VARIABLE_LC_PAPER, + VARIABLE_LC_NAME, + VARIABLE_LC_ADDRESS, + VARIABLE_LC_TELEPHONE, + VARIABLE_LC_MEASUREMENT, + VARIABLE_LC_IDENTIFICATION, + _VARIABLE_LC_MAX, + _VARIABLE_LC_INVALID = -1 +} LocaleVariable; + +int get_locales(char ***l); +bool locale_is_valid(const char *name); + +#define _(String) gettext(String) +#define N_(String) String +void init_gettext(void); + +bool is_locale_utf8(void); + +typedef enum DrawSpecialChar { + DRAW_TREE_VERTICAL, + DRAW_TREE_BRANCH, + DRAW_TREE_RIGHT, + DRAW_TREE_SPACE, + DRAW_TRIANGULAR_BULLET, + DRAW_BLACK_CIRCLE, + DRAW_ARROW, + DRAW_DASH, + _DRAW_SPECIAL_CHAR_MAX +} DrawSpecialChar; + +const char *draw_special_char(DrawSpecialChar ch); + +const char* locale_variable_to_string(LocaleVariable i) _const_; +LocaleVariable locale_variable_from_string(const char *s) _pure_; diff --git a/src/libbasic/lockfile-util.c b/src/libbasic/lockfile-util.c new file mode 100644 index 0000000000..3ee4191e4d --- /dev/null +++ b/src/libbasic/lockfile-util.c @@ -0,0 +1,153 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "lockfile-util.h" +#include "macro.h" +#include "path-util.h" + +int make_lock_file(const char *p, int operation, LockFile *ret) { + _cleanup_close_ int fd = -1; + _cleanup_free_ char *t = NULL; + int r; + + /* + * We use UNPOSIX locks if they are available. They have nice + * semantics, and are mostly compatible with NFS. However, + * they are only available on new kernels. When we detect we + * are running on an older kernel, then we fall back to good + * old BSD locks. They also have nice semantics, but are + * slightly problematic on NFS, where they are upgraded to + * POSIX locks, even though locally they are orthogonal to + * POSIX locks. + */ + + t = strdup(p); + if (!t) + return -ENOMEM; + + for (;;) { + struct flock fl = { + .l_type = (operation & ~LOCK_NB) == LOCK_EX ? F_WRLCK : F_RDLCK, + .l_whence = SEEK_SET, + }; + struct stat st; + + fd = open(p, O_CREAT|O_RDWR|O_NOFOLLOW|O_CLOEXEC|O_NOCTTY, 0600); + if (fd < 0) + return -errno; + + r = fcntl(fd, (operation & LOCK_NB) ? F_OFD_SETLK : F_OFD_SETLKW, &fl); + if (r < 0) { + + /* If the kernel is too old, use good old BSD locks */ + if (errno == EINVAL) + r = flock(fd, operation); + + if (r < 0) + return errno == EAGAIN ? -EBUSY : -errno; + } + + /* If we acquired the lock, let's check if the file + * still exists in the file system. If not, then the + * previous exclusive owner removed it and then closed + * it. In such a case our acquired lock is worthless, + * hence try again. */ + + r = fstat(fd, &st); + if (r < 0) + return -errno; + if (st.st_nlink > 0) + break; + + fd = safe_close(fd); + } + + ret->path = t; + ret->fd = fd; + ret->operation = operation; + + fd = -1; + t = NULL; + + return r; +} + +int make_lock_file_for(const char *p, int operation, LockFile *ret) { + const char *fn; + char *t; + + assert(p); + assert(ret); + + fn = basename(p); + if (!filename_is_valid(fn)) + return -EINVAL; + + t = newa(char, strlen(p) + 2 + 4 + 1); + stpcpy(stpcpy(stpcpy(mempcpy(t, p, fn - p), ".#"), fn), ".lck"); + + return make_lock_file(t, operation, ret); +} + +void release_lock_file(LockFile *f) { + int r; + + if (!f) + return; + + if (f->path) { + + /* If we are the exclusive owner we can safely delete + * the lock file itself. If we are not the exclusive + * owner, we can try becoming it. */ + + if (f->fd >= 0 && + (f->operation & ~LOCK_NB) == LOCK_SH) { + static const struct flock fl = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET, + }; + + r = fcntl(f->fd, F_OFD_SETLK, &fl); + if (r < 0 && errno == EINVAL) + r = flock(f->fd, LOCK_EX|LOCK_NB); + + if (r >= 0) + f->operation = LOCK_EX|LOCK_NB; + } + + if ((f->operation & ~LOCK_NB) == LOCK_EX) + unlink_noerrno(f->path); + + f->path = mfree(f->path); + } + + f->fd = safe_close(f->fd); + f->operation = 0; +} diff --git a/src/libbasic/lockfile-util.h b/src/libbasic/lockfile-util.h new file mode 100644 index 0000000000..22491ee8e1 --- /dev/null +++ b/src/libbasic/lockfile-util.h @@ -0,0 +1,39 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include + +#include "macro.h" +#include "missing.h" + +typedef struct LockFile { + char *path; + int fd; + int operation; +} LockFile; + +int make_lock_file(const char *p, int operation, LockFile *ret); +int make_lock_file_for(const char *p, int operation, LockFile *ret); +void release_lock_file(LockFile *f); + +#define _cleanup_release_lock_file_ _cleanup_(release_lock_file) + +#define LOCK_FILE_INIT { .fd = -1, .path = NULL } diff --git a/src/libbasic/log.c b/src/libbasic/log.c new file mode 100644 index 0000000000..f625e3bd95 --- /dev/null +++ b/src/libbasic/log.c @@ -0,0 +1,1170 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "io-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "syslog-util.h" +#include "terminal-util.h" +#include "time-util.h" +#include "util.h" + +#define SNDBUF_SIZE (8*1024*1024) + +static LogTarget log_target = LOG_TARGET_CONSOLE; +static int log_max_level = LOG_INFO; +static int log_facility = LOG_DAEMON; + +static int console_fd = STDERR_FILENO; +static int syslog_fd = -1; +static int kmsg_fd = -1; +static int journal_fd = -1; + +static bool syslog_is_stream = false; + +static bool show_color = false; +static bool show_location = false; + +static bool upgrade_syslog_to_journal = false; + +/* Akin to glibc's __abort_msg; which is private and we hence cannot + * use here. */ +static char *log_abort_msg = NULL; + +void log_close_console(void) { + + if (console_fd < 0) + return; + + if (getpid() == 1) { + if (console_fd >= 3) + safe_close(console_fd); + + console_fd = -1; + } +} + +static int log_open_console(void) { + + if (console_fd >= 0) + return 0; + + if (getpid() == 1) { + console_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (console_fd < 0) + return console_fd; + } else + console_fd = STDERR_FILENO; + + return 0; +} + +void log_close_kmsg(void) { + kmsg_fd = safe_close(kmsg_fd); +} + +static int log_open_kmsg(void) { + + if (kmsg_fd >= 0) + return 0; + + kmsg_fd = open("/dev/kmsg", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (kmsg_fd < 0) + return -errno; + + return 0; +} + +void log_close_syslog(void) { + syslog_fd = safe_close(syslog_fd); +} + +static int create_log_socket(int type) { + struct timeval tv; + int fd; + + fd = socket(AF_UNIX, type|SOCK_CLOEXEC, 0); + if (fd < 0) + return -errno; + + fd_inc_sndbuf(fd, SNDBUF_SIZE); + + /* We need a blocking fd here since we'd otherwise lose + messages way too early. However, let's not hang forever in the + unlikely case of a deadlock. */ + if (getpid() == 1) + timeval_store(&tv, 10 * USEC_PER_MSEC); + else + timeval_store(&tv, 10 * USEC_PER_SEC); + (void) setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); + + return fd; +} + +static int log_open_syslog(void) { + + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/dev/log", + }; + + int r; + + if (syslog_fd >= 0) + return 0; + + syslog_fd = create_log_socket(SOCK_DGRAM); + if (syslog_fd < 0) { + r = syslog_fd; + goto fail; + } + + if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { + safe_close(syslog_fd); + + /* Some legacy syslog systems still use stream + * sockets. They really shouldn't. But what can we + * do... */ + syslog_fd = create_log_socket(SOCK_STREAM); + if (syslog_fd < 0) { + r = syslog_fd; + goto fail; + } + + if (connect(syslog_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { + r = -errno; + goto fail; + } + + syslog_is_stream = true; + } else + syslog_is_stream = false; + + return 0; + +fail: + log_close_syslog(); + return r; +} + +void log_close_journal(void) { + journal_fd = safe_close(journal_fd); +} + +static int log_open_journal(void) { + + static const union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/journal/socket", + }; + + int r; + + if (journal_fd >= 0) + return 0; + + journal_fd = create_log_socket(SOCK_DGRAM); + if (journal_fd < 0) { + r = journal_fd; + goto fail; + } + + if (connect(journal_fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + log_close_journal(); + return r; +} + +int log_open(void) { + int r; + + /* If we don't use the console we close it here, to not get + * killed by SAK. If we don't use syslog we close it here so + * that we are not confused by somebody deleting the socket in + * the fs. If we don't use /dev/kmsg we still keep it open, + * because there is no reason to close it. */ + + if (log_target == LOG_TARGET_NULL) { + log_close_journal(); + log_close_syslog(); + log_close_console(); + return 0; + } + + if ((log_target != LOG_TARGET_AUTO && log_target != LOG_TARGET_SAFE) || + getpid() == 1 || + isatty(STDERR_FILENO) <= 0) { + + if (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_JOURNAL) { + r = log_open_journal(); + if (r >= 0) { + log_close_syslog(); + log_close_console(); + return r; + } + } + + if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_SYSLOG) { + r = log_open_syslog(); + if (r >= 0) { + log_close_journal(); + log_close_console(); + return r; + } + } + + if (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_SAFE || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_KMSG) { + r = log_open_kmsg(); + if (r >= 0) { + log_close_journal(); + log_close_syslog(); + log_close_console(); + return r; + } + } + } + + log_close_journal(); + log_close_syslog(); + + return log_open_console(); +} + +void log_set_target(LogTarget target) { + assert(target >= 0); + assert(target < _LOG_TARGET_MAX); + + if (upgrade_syslog_to_journal) { + if (target == LOG_TARGET_SYSLOG) + target = LOG_TARGET_JOURNAL; + else if (target == LOG_TARGET_SYSLOG_OR_KMSG) + target = LOG_TARGET_JOURNAL_OR_KMSG; + } + + log_target = target; +} + +void log_close(void) { + log_close_journal(); + log_close_syslog(); + log_close_kmsg(); + log_close_console(); +} + +void log_forget_fds(void) { + console_fd = kmsg_fd = syslog_fd = journal_fd = -1; +} + +void log_set_max_level(int level) { + assert((level & LOG_PRIMASK) == level); + + log_max_level = level; +} + +void log_set_facility(int facility) { + log_facility = facility; +} + +static int write_to_console( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *buffer) { + + char location[64], prefix[1 + DECIMAL_STR_MAX(int) + 2]; + struct iovec iovec[6] = {}; + unsigned n = 0; + bool highlight; + + if (console_fd < 0) + return 0; + + if (log_target == LOG_TARGET_CONSOLE_PREFIXED) { + sprintf(prefix, "<%i>", level); + IOVEC_SET_STRING(iovec[n++], prefix); + } + + highlight = LOG_PRI(level) <= LOG_ERR && show_color; + + if (show_location) { + xsprintf(location, "(%s:%i) ", file, line); + IOVEC_SET_STRING(iovec[n++], location); + } + + if (highlight) + IOVEC_SET_STRING(iovec[n++], ANSI_HIGHLIGHT_RED); + IOVEC_SET_STRING(iovec[n++], buffer); + if (highlight) + IOVEC_SET_STRING(iovec[n++], ANSI_NORMAL); + IOVEC_SET_STRING(iovec[n++], "\n"); + + if (writev(console_fd, iovec, n) < 0) { + + if (errno == EIO && getpid() == 1) { + + /* If somebody tried to kick us from our + * console tty (via vhangup() or suchlike), + * try to reconnect */ + + log_close_console(); + log_open_console(); + + if (console_fd < 0) + return 0; + + if (writev(console_fd, iovec, n) < 0) + return -errno; + } else + return -errno; + } + + return 1; +} + +static int write_to_syslog( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *buffer) { + + char header_priority[2 + DECIMAL_STR_MAX(int) + 1], + header_time[64], + header_pid[4 + DECIMAL_STR_MAX(pid_t) + 1]; + struct iovec iovec[5] = {}; + struct msghdr msghdr = { + .msg_iov = iovec, + .msg_iovlen = ELEMENTSOF(iovec), + }; + time_t t; + struct tm *tm; + + if (syslog_fd < 0) + return 0; + + xsprintf(header_priority, "<%i>", level); + + t = (time_t) (now(CLOCK_REALTIME) / USEC_PER_SEC); + tm = localtime(&t); + if (!tm) + return -EINVAL; + + if (strftime(header_time, sizeof(header_time), "%h %e %T ", tm) <= 0) + return -EINVAL; + + xsprintf(header_pid, "["PID_FMT"]: ", getpid()); + + IOVEC_SET_STRING(iovec[0], header_priority); + IOVEC_SET_STRING(iovec[1], header_time); + IOVEC_SET_STRING(iovec[2], program_invocation_short_name); + IOVEC_SET_STRING(iovec[3], header_pid); + IOVEC_SET_STRING(iovec[4], buffer); + + /* When using syslog via SOCK_STREAM separate the messages by NUL chars */ + if (syslog_is_stream) + iovec[4].iov_len++; + + for (;;) { + ssize_t n; + + n = sendmsg(syslog_fd, &msghdr, MSG_NOSIGNAL); + if (n < 0) + return -errno; + + if (!syslog_is_stream || + (size_t) n >= IOVEC_TOTAL_SIZE(iovec, ELEMENTSOF(iovec))) + break; + + IOVEC_INCREMENT(iovec, ELEMENTSOF(iovec), n); + } + + return 1; +} + +static int write_to_kmsg( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *buffer) { + + char header_priority[2 + DECIMAL_STR_MAX(int) + 1], + header_pid[4 + DECIMAL_STR_MAX(pid_t) + 1]; + struct iovec iovec[5] = {}; + + if (kmsg_fd < 0) + return 0; + + xsprintf(header_priority, "<%i>", level); + xsprintf(header_pid, "["PID_FMT"]: ", getpid()); + + IOVEC_SET_STRING(iovec[0], header_priority); + IOVEC_SET_STRING(iovec[1], program_invocation_short_name); + IOVEC_SET_STRING(iovec[2], header_pid); + IOVEC_SET_STRING(iovec[3], buffer); + IOVEC_SET_STRING(iovec[4], "\n"); + + if (writev(kmsg_fd, iovec, ELEMENTSOF(iovec)) < 0) + return -errno; + + return 1; +} + +static int log_do_header( + char *header, + size_t size, + int level, + int error, + const char *file, int line, const char *func, + const char *object_field, const char *object) { + + snprintf(header, size, + "PRIORITY=%i\n" + "SYSLOG_FACILITY=%i\n" + "%s%s%s" + "%s%.*i%s" + "%s%s%s" + "%s%.*i%s" + "%s%s%s" + "SYSLOG_IDENTIFIER=%s\n", + LOG_PRI(level), + LOG_FAC(level), + isempty(file) ? "" : "CODE_FILE=", + isempty(file) ? "" : file, + isempty(file) ? "" : "\n", + line ? "CODE_LINE=" : "", + line ? 1 : 0, line, /* %.0d means no output too, special case for 0 */ + line ? "\n" : "", + isempty(func) ? "" : "CODE_FUNCTION=", + isempty(func) ? "" : func, + isempty(func) ? "" : "\n", + error ? "ERRNO=" : "", + error ? 1 : 0, error, + error ? "\n" : "", + isempty(object) ? "" : object_field, + isempty(object) ? "" : object, + isempty(object) ? "" : "\n", + program_invocation_short_name); + + return 0; +} + +static int write_to_journal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *buffer) { + + char header[LINE_MAX]; + struct iovec iovec[4] = {}; + struct msghdr mh = {}; + + if (journal_fd < 0) + return 0; + + log_do_header(header, sizeof(header), level, error, file, line, func, object_field, object); + + IOVEC_SET_STRING(iovec[0], header); + IOVEC_SET_STRING(iovec[1], "MESSAGE="); + IOVEC_SET_STRING(iovec[2], buffer); + IOVEC_SET_STRING(iovec[3], "\n"); + + mh.msg_iov = iovec; + mh.msg_iovlen = ELEMENTSOF(iovec); + + if (sendmsg(journal_fd, &mh, MSG_NOSIGNAL) < 0) + return -errno; + + return 1; +} + +static int log_dispatch( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + char *buffer) { + + assert(buffer); + + if (log_target == LOG_TARGET_NULL) + return -error; + + /* Patch in LOG_DAEMON facility if necessary */ + if ((level & LOG_FACMASK) == 0) + level = log_facility | LOG_PRI(level); + + if (error < 0) + error = -error; + + do { + char *e; + int k = 0; + + buffer += strspn(buffer, NEWLINE); + + if (buffer[0] == 0) + break; + + if ((e = strpbrk(buffer, NEWLINE))) + *(e++) = 0; + + if (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_JOURNAL) { + + k = write_to_journal(level, error, file, line, func, object_field, object, buffer); + if (k < 0) { + if (k != -EAGAIN) + log_close_journal(); + log_open_kmsg(); + } + } + + if (log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_SYSLOG) { + + k = write_to_syslog(level, error, file, line, func, object_field, object, buffer); + if (k < 0) { + if (k != -EAGAIN) + log_close_syslog(); + log_open_kmsg(); + } + } + + if (k <= 0 && + (log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_SAFE || + log_target == LOG_TARGET_SYSLOG_OR_KMSG || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_KMSG)) { + + k = write_to_kmsg(level, error, file, line, func, object_field, object, buffer); + if (k < 0) { + log_close_kmsg(); + log_open_console(); + } + } + + if (k <= 0) + (void) write_to_console(level, error, file, line, func, object_field, object, buffer); + + buffer = e; + } while (buffer); + + return -error; +} + +int log_dump_internal( + int level, + int error, + const char *file, + int line, + const char *func, + char *buffer) { + + PROTECT_ERRNO; + + /* This modifies the buffer... */ + + if (error < 0) + error = -error; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return -error; + + return log_dispatch(level, error, file, line, func, NULL, NULL, buffer); +} + +int log_internalv( + int level, + int error, + const char *file, + int line, + const char *func, + const char *format, + va_list ap) { + + PROTECT_ERRNO; + char buffer[LINE_MAX]; + + if (error < 0) + error = -error; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return -error; + + /* Make sure that %m maps to the specified error */ + if (error != 0) + errno = error; + + vsnprintf(buffer, sizeof(buffer), format, ap); + + return log_dispatch(level, error, file, line, func, NULL, NULL, buffer); +} + +int log_internal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *format, ...) { + + va_list ap; + int r; + + va_start(ap, format); + r = log_internalv(level, error, file, line, func, format, ap); + va_end(ap); + + return r; +} + +int log_object_internalv( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *format, + va_list ap) { + + PROTECT_ERRNO; + char *buffer, *b; + size_t l; + + if (error < 0) + error = -error; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return -error; + + /* Make sure that %m maps to the specified error */ + if (error != 0) + errno = error; + + /* Prepend the object name before the message */ + if (object) { + size_t n; + + n = strlen(object); + l = n + 2 + LINE_MAX; + + buffer = newa(char, l); + b = stpcpy(stpcpy(buffer, object), ": "); + } else { + l = LINE_MAX; + b = buffer = newa(char, l); + } + + vsnprintf(b, l, format, ap); + + return log_dispatch(level, error, file, line, func, object_field, object, buffer); +} + +int log_object_internal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *format, ...) { + + va_list ap; + int r; + + va_start(ap, format); + r = log_object_internalv(level, error, file, line, func, object_field, object, format, ap); + va_end(ap); + + return r; +} + +static void log_assert( + int level, + const char *text, + const char *file, + int line, + const char *func, + const char *format) { + + static char buffer[LINE_MAX]; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return; + + DISABLE_WARNING_FORMAT_NONLITERAL; + xsprintf(buffer, format, text, file, line, func); + REENABLE_WARNING; + + log_abort_msg = buffer; + + log_dispatch(level, 0, file, line, func, NULL, NULL, buffer); +} + +noreturn void log_assert_failed(const char *text, const char *file, int line, const char *func) { + log_assert(LOG_CRIT, text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting."); + abort(); +} + +noreturn void log_assert_failed_unreachable(const char *text, const char *file, int line, const char *func) { + log_assert(LOG_CRIT, text, file, line, func, "Code should not be reached '%s' at %s:%u, function %s(). Aborting."); + abort(); +} + +void log_assert_failed_return(const char *text, const char *file, int line, const char *func) { + PROTECT_ERRNO; + log_assert(LOG_DEBUG, text, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Ignoring."); +} + +int log_oom_internal(const char *file, int line, const char *func) { + log_internal(LOG_ERR, ENOMEM, file, line, func, "Out of memory."); + return -ENOMEM; +} + +int log_format_iovec( + struct iovec *iovec, + unsigned iovec_len, + unsigned *n, + bool newline_separator, + int error, + const char *format, + va_list ap) { + + static const char nl = '\n'; + + while (format && *n + 1 < iovec_len) { + va_list aq; + char *m; + int r; + + /* We need to copy the va_list structure, + * since vasprintf() leaves it afterwards at + * an undefined location */ + + if (error != 0) + errno = error; + + va_copy(aq, ap); + r = vasprintf(&m, format, aq); + va_end(aq); + if (r < 0) + return -EINVAL; + + /* Now, jump enough ahead, so that we point to + * the next format string */ + VA_FORMAT_ADVANCE(format, ap); + + IOVEC_SET_STRING(iovec[(*n)++], m); + + if (newline_separator) { + iovec[*n].iov_base = (char*) &nl; + iovec[*n].iov_len = 1; + (*n)++; + } + + format = va_arg(ap, char *); + } + return 0; +} + +int log_struct_internal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *format, ...) { + + char buf[LINE_MAX]; + bool found = false; + PROTECT_ERRNO; + va_list ap; + + if (error < 0) + error = -error; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return -error; + + if (log_target == LOG_TARGET_NULL) + return -error; + + if ((level & LOG_FACMASK) == 0) + level = log_facility | LOG_PRI(level); + + if ((log_target == LOG_TARGET_AUTO || + log_target == LOG_TARGET_JOURNAL_OR_KMSG || + log_target == LOG_TARGET_JOURNAL) && + journal_fd >= 0) { + char header[LINE_MAX]; + struct iovec iovec[17] = {}; + unsigned n = 0, i; + int r; + struct msghdr mh = { + .msg_iov = iovec, + }; + bool fallback = false; + + /* If the journal is available do structured logging */ + log_do_header(header, sizeof(header), level, error, file, line, func, NULL, NULL); + IOVEC_SET_STRING(iovec[n++], header); + + va_start(ap, format); + r = log_format_iovec(iovec, ELEMENTSOF(iovec), &n, true, error, format, ap); + if (r < 0) + fallback = true; + else { + mh.msg_iovlen = n; + (void) sendmsg(journal_fd, &mh, MSG_NOSIGNAL); + } + + va_end(ap); + for (i = 1; i < n; i += 2) + free(iovec[i].iov_base); + + if (!fallback) + return -error; + } + + /* Fallback if journal logging is not available or didn't work. */ + + va_start(ap, format); + while (format) { + va_list aq; + + if (error != 0) + errno = error; + + va_copy(aq, ap); + vsnprintf(buf, sizeof(buf), format, aq); + va_end(aq); + + if (startswith(buf, "MESSAGE=")) { + found = true; + break; + } + + VA_FORMAT_ADVANCE(format, ap); + + format = va_arg(ap, char *); + } + va_end(ap); + + if (!found) + return -error; + + return log_dispatch(level, error, file, line, func, NULL, NULL, buf + 8); +} + +int log_set_target_from_string(const char *e) { + LogTarget t; + + t = log_target_from_string(e); + if (t < 0) + return -EINVAL; + + log_set_target(t); + return 0; +} + +int log_set_max_level_from_string(const char *e) { + int t; + + t = log_level_from_string(e); + if (t < 0) + return -EINVAL; + + log_set_max_level(t); + return 0; +} + +static int parse_proc_cmdline_item(const char *key, const char *value) { + + /* + * The systemd.log_xyz= settings are parsed by all tools, and + * so is "debug". + * + * However, "quiet" is only parsed by PID 1, and only turns of + * status output to /dev/console, but does not alter the log + * level. + */ + + if (streq(key, "debug") && !value) + log_set_max_level(LOG_DEBUG); + + else if (streq(key, "systemd.log_target") && value) { + + if (log_set_target_from_string(value) < 0) + log_warning("Failed to parse log target '%s'. Ignoring.", value); + + } else if (streq(key, "systemd.log_level") && value) { + + if (log_set_max_level_from_string(value) < 0) + log_warning("Failed to parse log level '%s'. Ignoring.", value); + + } else if (streq(key, "systemd.log_color") && value) { + + if (log_show_color_from_string(value) < 0) + log_warning("Failed to parse log color setting '%s'. Ignoring.", value); + + } else if (streq(key, "systemd.log_location") && value) { + + if (log_show_location_from_string(value) < 0) + log_warning("Failed to parse log location setting '%s'. Ignoring.", value); + } + + return 0; +} + +void log_parse_environment(void) { + const char *e; + + if (get_ctty_devnr(0, NULL) < 0) + /* Only try to read the command line in daemons. + We assume that anything that has a controlling + tty is user stuff. */ + (void) parse_proc_cmdline(parse_proc_cmdline_item); + + e = secure_getenv("SYSTEMD_LOG_TARGET"); + if (e && log_set_target_from_string(e) < 0) + log_warning("Failed to parse log target '%s'. Ignoring.", e); + + e = secure_getenv("SYSTEMD_LOG_LEVEL"); + if (e && log_set_max_level_from_string(e) < 0) + log_warning("Failed to parse log level '%s'. Ignoring.", e); + + e = secure_getenv("SYSTEMD_LOG_COLOR"); + if (e && log_show_color_from_string(e) < 0) + log_warning("Failed to parse bool '%s'. Ignoring.", e); + + e = secure_getenv("SYSTEMD_LOG_LOCATION"); + if (e && log_show_location_from_string(e) < 0) + log_warning("Failed to parse bool '%s'. Ignoring.", e); +} + +LogTarget log_get_target(void) { + return log_target; +} + +int log_get_max_level(void) { + return log_max_level; +} + +void log_show_color(bool b) { + show_color = b; +} + +bool log_get_show_color(void) { + return show_color; +} + +void log_show_location(bool b) { + show_location = b; +} + +bool log_get_show_location(void) { + return show_location; +} + +int log_show_color_from_string(const char *e) { + int t; + + t = parse_boolean(e); + if (t < 0) + return t; + + log_show_color(t); + return 0; +} + +int log_show_location_from_string(const char *e) { + int t; + + t = parse_boolean(e); + if (t < 0) + return t; + + log_show_location(t); + return 0; +} + +bool log_on_console(void) { + if (log_target == LOG_TARGET_CONSOLE || + log_target == LOG_TARGET_CONSOLE_PREFIXED) + return true; + + return syslog_fd < 0 && kmsg_fd < 0 && journal_fd < 0; +} + +static const char *const log_target_table[_LOG_TARGET_MAX] = { + [LOG_TARGET_CONSOLE] = "console", + [LOG_TARGET_CONSOLE_PREFIXED] = "console-prefixed", + [LOG_TARGET_KMSG] = "kmsg", + [LOG_TARGET_JOURNAL] = "journal", + [LOG_TARGET_JOURNAL_OR_KMSG] = "journal-or-kmsg", + [LOG_TARGET_SYSLOG] = "syslog", + [LOG_TARGET_SYSLOG_OR_KMSG] = "syslog-or-kmsg", + [LOG_TARGET_AUTO] = "auto", + [LOG_TARGET_SAFE] = "safe", + [LOG_TARGET_NULL] = "null" +}; + +DEFINE_STRING_TABLE_LOOKUP(log_target, LogTarget); + +void log_received_signal(int level, const struct signalfd_siginfo *si) { + if (si->ssi_pid > 0) { + _cleanup_free_ char *p = NULL; + + get_process_comm(si->ssi_pid, &p); + + log_full(level, + "Received SIG%s from PID %"PRIu32" (%s).", + signal_to_string(si->ssi_signo), + si->ssi_pid, strna(p)); + } else + log_full(level, + "Received SIG%s.", + signal_to_string(si->ssi_signo)); + +} + +void log_set_upgrade_syslog_to_journal(bool b) { + upgrade_syslog_to_journal = b; +} + +int log_syntax_internal( + const char *unit, + int level, + const char *config_file, + unsigned config_line, + int error, + const char *file, + int line, + const char *func, + const char *format, ...) { + + PROTECT_ERRNO; + char buffer[LINE_MAX]; + int r; + va_list ap; + + if (error < 0) + error = -error; + + if (_likely_(LOG_PRI(level) > log_max_level)) + return -error; + + if (log_target == LOG_TARGET_NULL) + return -error; + + if (error != 0) + errno = error; + + va_start(ap, format); + vsnprintf(buffer, sizeof(buffer), format, ap); + va_end(ap); + + if (unit) + r = log_struct_internal( + level, error, + file, line, func, + getpid() == 1 ? "UNIT=%s" : "USER_UNIT=%s", unit, + LOG_MESSAGE_ID(SD_MESSAGE_INVALID_CONFIGURATION), + "CONFIG_FILE=%s", config_file, + "CONFIG_LINE=%u", config_line, + LOG_MESSAGE("[%s:%u] %s", config_file, config_line, buffer), + NULL); + else + r = log_struct_internal( + level, error, + file, line, func, + LOG_MESSAGE_ID(SD_MESSAGE_INVALID_CONFIGURATION), + "CONFIG_FILE=%s", config_file, + "CONFIG_LINE=%u", config_line, + LOG_MESSAGE("[%s:%u] %s", config_file, config_line, buffer), + NULL); + + return r; +} diff --git a/src/libbasic/log.h b/src/libbasic/log.h new file mode 100644 index 0000000000..8ccbf8ed70 --- /dev/null +++ b/src/libbasic/log.h @@ -0,0 +1,250 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "macro.h" + +typedef enum LogTarget{ + LOG_TARGET_CONSOLE, + LOG_TARGET_CONSOLE_PREFIXED, + LOG_TARGET_KMSG, + LOG_TARGET_JOURNAL, + LOG_TARGET_JOURNAL_OR_KMSG, + LOG_TARGET_SYSLOG, + LOG_TARGET_SYSLOG_OR_KMSG, + LOG_TARGET_AUTO, /* console if stderr is tty, JOURNAL_OR_KMSG otherwise */ + LOG_TARGET_SAFE, /* console if stderr is tty, KMSG otherwise */ + LOG_TARGET_NULL, + _LOG_TARGET_MAX, + _LOG_TARGET_INVALID = -1 +} LogTarget; + +void log_set_target(LogTarget target); +void log_set_max_level(int level); +void log_set_facility(int facility); + +int log_set_target_from_string(const char *e); +int log_set_max_level_from_string(const char *e); + +void log_show_color(bool b); +bool log_get_show_color(void) _pure_; +void log_show_location(bool b); +bool log_get_show_location(void) _pure_; + +int log_show_color_from_string(const char *e); +int log_show_location_from_string(const char *e); + +LogTarget log_get_target(void) _pure_; +int log_get_max_level(void) _pure_; + +int log_open(void); +void log_close(void); +void log_forget_fds(void); + +void log_close_syslog(void); +void log_close_journal(void); +void log_close_kmsg(void); +void log_close_console(void); + +void log_parse_environment(void); + +int log_internal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *format, ...) _printf_(6,7); + +int log_internalv( + int level, + int error, + const char *file, + int line, + const char *func, + const char *format, + va_list ap) _printf_(6,0); + +int log_object_internal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *format, ...) _printf_(8,9); + +int log_object_internalv( + int level, + int error, + const char*file, + int line, + const char *func, + const char *object_field, + const char *object, + const char *format, + va_list ap) _printf_(8,0); + +int log_struct_internal( + int level, + int error, + const char *file, + int line, + const char *func, + const char *format, ...) _printf_(6,0) _sentinel_; + +int log_oom_internal( + const char *file, + int line, + const char *func); + +int log_format_iovec( + struct iovec *iovec, + unsigned iovec_len, + unsigned *n, + bool newline_separator, + int error, + const char *format, + va_list ap); + +/* This modifies the buffer passed! */ +int log_dump_internal( + int level, + int error, + const char *file, + int line, + const char *func, + char *buffer); + +/* Logging for various assertions */ +noreturn void log_assert_failed( + const char *text, + const char *file, + int line, + const char *func); + +noreturn void log_assert_failed_unreachable( + const char *text, + const char *file, + int line, + const char *func); + +void log_assert_failed_return( + const char *text, + const char *file, + int line, + const char *func); + +/* Logging with level */ +#define log_full_errno(level, error, ...) \ + ({ \ + int _level = (level), _e = (error); \ + (log_get_max_level() >= LOG_PRI(_level)) \ + ? log_internal(_level, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \ + : -abs(_e); \ + }) + +#define log_full(level, ...) log_full_errno(level, 0, __VA_ARGS__) + +/* Normal logging */ +#define log_debug(...) log_full(LOG_DEBUG, __VA_ARGS__) +#define log_info(...) log_full(LOG_INFO, __VA_ARGS__) +#define log_notice(...) log_full(LOG_NOTICE, __VA_ARGS__) +#define log_warning(...) log_full(LOG_WARNING, __VA_ARGS__) +#define log_error(...) log_full(LOG_ERR, __VA_ARGS__) +#define log_emergency(...) log_full(getpid() == 1 ? LOG_EMERG : LOG_ERR, __VA_ARGS__) + +/* Logging triggered by an errno-like error */ +#define log_debug_errno(error, ...) log_full_errno(LOG_DEBUG, error, __VA_ARGS__) +#define log_info_errno(error, ...) log_full_errno(LOG_INFO, error, __VA_ARGS__) +#define log_notice_errno(error, ...) log_full_errno(LOG_NOTICE, error, __VA_ARGS__) +#define log_warning_errno(error, ...) log_full_errno(LOG_WARNING, error, __VA_ARGS__) +#define log_error_errno(error, ...) log_full_errno(LOG_ERR, error, __VA_ARGS__) +#define log_emergency_errno(error, ...) log_full_errno(getpid() == 1 ? LOG_EMERG : LOG_ERR, error, __VA_ARGS__) + +#ifdef LOG_TRACE +# define log_trace(...) log_debug(__VA_ARGS__) +#else +# define log_trace(...) do {} while(0) +#endif + +/* Structured logging */ +#define log_struct(level, ...) log_struct_internal(level, 0, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define log_struct_errno(level, error, ...) log_struct_internal(level, error, __FILE__, __LINE__, __func__, __VA_ARGS__) + +/* This modifies the buffer passed! */ +#define log_dump(level, buffer) log_dump_internal(level, 0, __FILE__, __LINE__, __func__, buffer) + +#define log_oom() log_oom_internal(__FILE__, __LINE__, __func__) + +bool log_on_console(void) _pure_; + +const char *log_target_to_string(LogTarget target) _const_; +LogTarget log_target_from_string(const char *s) _pure_; + +/* Helpers to prepare various fields for structured logging */ +#define LOG_MESSAGE(fmt, ...) "MESSAGE=" fmt, ##__VA_ARGS__ +#define LOG_MESSAGE_ID(x) "MESSAGE_ID=" SD_ID128_FORMAT_STR, SD_ID128_FORMAT_VAL(x) + +void log_received_signal(int level, const struct signalfd_siginfo *si); + +void log_set_upgrade_syslog_to_journal(bool b); + +int log_syntax_internal( + const char *unit, + int level, + const char *config_file, + unsigned config_line, + int error, + const char *file, + int line, + const char *func, + const char *format, ...) _printf_(9, 10); + +#define log_syntax(unit, level, config_file, config_line, error, ...) \ + ({ \ + int _level = (level), _e = (error); \ + (log_get_max_level() >= LOG_PRI(_level)) \ + ? log_syntax_internal(unit, _level, config_file, config_line, _e, __FILE__, __LINE__, __func__, __VA_ARGS__) \ + : -abs(_e); \ + }) + +#define log_syntax_invalid_utf8(unit, level, config_file, config_line, rvalue) \ + ({ \ + int _level = (level); \ + if (log_get_max_level() >= LOG_PRI(_level)) { \ + _cleanup_free_ char *_p = NULL; \ + _p = utf8_escape_invalid(rvalue); \ + log_syntax_internal(unit, _level, config_file, config_line, 0, __FILE__, __LINE__, __func__, \ + "String is not UTF-8 clean, ignoring assignment: %s", strna(_p)); \ + } \ + -EINVAL; \ + }) diff --git a/src/libbasic/login-util.c b/src/libbasic/login-util.c new file mode 100644 index 0000000000..339e94f12d --- /dev/null +++ b/src/libbasic/login-util.c @@ -0,0 +1,31 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#include + +#include "login-util.h" +#include "string-util.h" + +bool session_id_valid(const char *id) { + + if (isempty(id)) + return false; + + return id[strspn(id, LETTERS DIGITS)] == '\0'; +} diff --git a/src/libbasic/login-util.h b/src/libbasic/login-util.h new file mode 100644 index 0000000000..89a337d7c1 --- /dev/null +++ b/src/libbasic/login-util.h @@ -0,0 +1,29 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#pragma once + +#include +#include + +bool session_id_valid(const char *id); + +static inline bool logind_running(void) { + return access("/run/systemd/seats/", F_OK) >= 0; +} diff --git a/src/libbasic/macro.h b/src/libbasic/macro.h new file mode 100644 index 0000000000..2695d0edb7 --- /dev/null +++ b/src/libbasic/macro.h @@ -0,0 +1,395 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#define _printf_(a,b) __attribute__ ((format (printf, a, b))) +#define _alloc_(...) __attribute__ ((alloc_size(__VA_ARGS__))) +#define _sentinel_ __attribute__ ((sentinel)) +#define _unused_ __attribute__ ((unused)) +#define _destructor_ __attribute__ ((destructor)) +#define _pure_ __attribute__ ((pure)) +#define _const_ __attribute__ ((const)) +#define _deprecated_ __attribute__ ((deprecated)) +#define _packed_ __attribute__ ((packed)) +#define _malloc_ __attribute__ ((malloc)) +#define _weak_ __attribute__ ((weak)) +#define _likely_(x) (__builtin_expect(!!(x),1)) +#define _unlikely_(x) (__builtin_expect(!!(x),0)) +#define _public_ __attribute__ ((visibility("default"))) +#define _hidden_ __attribute__ ((visibility("hidden"))) +#define _weakref_(x) __attribute__((weakref(#x))) +#define _alignas_(x) __attribute__((aligned(__alignof(x)))) +#define _cleanup_(x) __attribute__((cleanup(x))) + +/* Temporarily disable some warnings */ +#define DISABLE_WARNING_DECLARATION_AFTER_STATEMENT \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wdeclaration-after-statement\"") + +#define DISABLE_WARNING_FORMAT_NONLITERAL \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"") + +#define DISABLE_WARNING_MISSING_PROTOTYPES \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wmissing-prototypes\"") + +#define DISABLE_WARNING_NONNULL \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wnonnull\"") + +#define DISABLE_WARNING_SHADOW \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wshadow\"") + +#define DISABLE_WARNING_INCOMPATIBLE_POINTER_TYPES \ + _Pragma("GCC diagnostic push"); \ + _Pragma("GCC diagnostic ignored \"-Wincompatible-pointer-types\"") + +#define REENABLE_WARNING \ + _Pragma("GCC diagnostic pop") + +/* automake test harness */ +#define EXIT_TEST_SKIP 77 + +#define XSTRINGIFY(x) #x +#define STRINGIFY(x) XSTRINGIFY(x) + +#define XCONCATENATE(x, y) x ## y +#define CONCATENATE(x, y) XCONCATENATE(x, y) + +#define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq)) +#define UNIQ __COUNTER__ + +/* Rounds up */ + +#define ALIGN4(l) (((l) + 3) & ~3) +#define ALIGN8(l) (((l) + 7) & ~7) + +#if __SIZEOF_POINTER__ == 8 +#define ALIGN(l) ALIGN8(l) +#elif __SIZEOF_POINTER__ == 4 +#define ALIGN(l) ALIGN4(l) +#else +#error "Wut? Pointers are neither 4 nor 8 bytes long?" +#endif + +#define ALIGN_PTR(p) ((void*) ALIGN((unsigned long) (p))) +#define ALIGN4_PTR(p) ((void*) ALIGN4((unsigned long) (p))) +#define ALIGN8_PTR(p) ((void*) ALIGN8((unsigned long) (p))) + +static inline size_t ALIGN_TO(size_t l, size_t ali) { + return ((l + ali - 1) & ~(ali - 1)); +} + +#define ALIGN_TO_PTR(p, ali) ((void*) ALIGN_TO((unsigned long) (p), (ali))) + +/* align to next higher power-of-2 (except for: 0 => 0, overflow => 0) */ +static inline unsigned long ALIGN_POWER2(unsigned long u) { + /* clz(0) is undefined */ + if (u == 1) + return 1; + + /* left-shift overflow is undefined */ + if (__builtin_clzl(u - 1UL) < 1) + return 0; + + return 1UL << (sizeof(u) * 8 - __builtin_clzl(u - 1UL)); +} + +#define ELEMENTSOF(x) \ + __extension__ (__builtin_choose_expr( \ + !__builtin_types_compatible_p(typeof(x), typeof(&*(x))), \ + sizeof(x)/sizeof((x)[0]), \ + (void)0)) +/* + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + */ +#define container_of(ptr, type, member) __container_of(UNIQ, (ptr), type, member) +#define __container_of(uniq, ptr, type, member) \ + __extension__ ({ \ + const typeof( ((type*)0)->member ) *UNIQ_T(A, uniq) = (ptr); \ + (type*)( (char *)UNIQ_T(A, uniq) - offsetof(type,member) ); \ + }) + +#undef MAX +#define MAX(a, b) __MAX(UNIQ, (a), UNIQ, (b)) +#define __MAX(aq, a, bq, b) \ + __extension__ ({ \ + const typeof(a) UNIQ_T(A, aq) = (a); \ + const typeof(b) UNIQ_T(B, bq) = (b); \ + UNIQ_T(A,aq) > UNIQ_T(B,bq) ? UNIQ_T(A,aq) : UNIQ_T(B,bq); \ + }) + +/* evaluates to (void) if _A or _B are not constant or of different types */ +#define CONST_MAX(_A, _B) \ + __extension__ (__builtin_choose_expr( \ + __builtin_constant_p(_A) && \ + __builtin_constant_p(_B) && \ + __builtin_types_compatible_p(typeof(_A), typeof(_B)), \ + ((_A) > (_B)) ? (_A) : (_B), \ + (void)0)) + +/* takes two types and returns the size of the larger one */ +#define MAXSIZE(A, B) (sizeof(union _packed_ { typeof(A) a; typeof(B) b; })) + +#define MAX3(x,y,z) \ + __extension__ ({ \ + const typeof(x) _c = MAX(x,y); \ + MAX(_c, z); \ + }) + +#undef MIN +#define MIN(a, b) __MIN(UNIQ, (a), UNIQ, (b)) +#define __MIN(aq, a, bq, b) \ + __extension__ ({ \ + const typeof(a) UNIQ_T(A, aq) = (a); \ + const typeof(b) UNIQ_T(B, bq) = (b); \ + UNIQ_T(A,aq) < UNIQ_T(B,bq) ? UNIQ_T(A,aq) : UNIQ_T(B,bq); \ + }) + +#define MIN3(x,y,z) \ + __extension__ ({ \ + const typeof(x) _c = MIN(x,y); \ + MIN(_c, z); \ + }) + +#define LESS_BY(a, b) __LESS_BY(UNIQ, (a), UNIQ, (b)) +#define __LESS_BY(aq, a, bq, b) \ + __extension__ ({ \ + const typeof(a) UNIQ_T(A, aq) = (a); \ + const typeof(b) UNIQ_T(B, bq) = (b); \ + UNIQ_T(A,aq) > UNIQ_T(B,bq) ? UNIQ_T(A,aq) - UNIQ_T(B,bq) : 0; \ + }) + +#undef CLAMP +#define CLAMP(x, low, high) __CLAMP(UNIQ, (x), UNIQ, (low), UNIQ, (high)) +#define __CLAMP(xq, x, lowq, low, highq, high) \ + __extension__ ({ \ + const typeof(x) UNIQ_T(X,xq) = (x); \ + const typeof(low) UNIQ_T(LOW,lowq) = (low); \ + const typeof(high) UNIQ_T(HIGH,highq) = (high); \ + UNIQ_T(X,xq) > UNIQ_T(HIGH,highq) ? \ + UNIQ_T(HIGH,highq) : \ + UNIQ_T(X,xq) < UNIQ_T(LOW,lowq) ? \ + UNIQ_T(LOW,lowq) : \ + UNIQ_T(X,xq); \ + }) + +/* [(x + y - 1) / y] suffers from an integer overflow, even though the + * computation should be possible in the given type. Therefore, we use + * [x / y + !!(x % y)]. Note that on "Real CPUs" a division returns both the + * quotient and the remainder, so both should be equally fast. */ +#define DIV_ROUND_UP(_x, _y) \ + __extension__ ({ \ + const typeof(_x) __x = (_x); \ + const typeof(_y) __y = (_y); \ + (__x / __y + !!(__x % __y)); \ + }) + +#define assert_message_se(expr, message) \ + do { \ + if (_unlikely_(!(expr))) \ + log_assert_failed(message, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + } while (false) + +#define assert_se(expr) assert_message_se(expr, #expr) + +/* We override the glibc assert() here. */ +#undef assert +#ifdef NDEBUG +#define assert(expr) do {} while(false) +#else +#define assert(expr) assert_message_se(expr, #expr) +#endif + +#define assert_not_reached(t) \ + do { \ + log_assert_failed_unreachable(t, __FILE__, __LINE__, __PRETTY_FUNCTION__); \ + } while (false) + +#if defined(static_assert) +/* static_assert() is sometimes defined in a way that trips up + * -Wdeclaration-after-statement, hence let's temporarily turn off + * this warning around it. */ +#define assert_cc(expr) \ + DISABLE_WARNING_DECLARATION_AFTER_STATEMENT; \ + static_assert(expr, #expr); \ + REENABLE_WARNING +#else +#define assert_cc(expr) \ + DISABLE_WARNING_DECLARATION_AFTER_STATEMENT; \ + struct CONCATENATE(_assert_struct_, __COUNTER__) { \ + char x[(expr) ? 0 : -1]; \ + }; \ + REENABLE_WARNING +#endif + +#define assert_log(expr, message) ((_likely_(expr)) \ + ? (true) \ + : (log_assert_failed_return(message, __FILE__, __LINE__, __PRETTY_FUNCTION__), false)) + +#define assert_return(expr, r) \ + do { \ + if (!assert_log(expr, #expr)) \ + return (r); \ + } while (false) + +#define assert_return_errno(expr, r, err) \ + do { \ + if (!assert_log(expr, #expr)) { \ + errno = err; \ + return (r); \ + } \ + } while (false) + +#define PTR_TO_INT(p) ((int) ((intptr_t) (p))) +#define INT_TO_PTR(u) ((void *) ((intptr_t) (u))) +#define PTR_TO_UINT(p) ((unsigned int) ((uintptr_t) (p))) +#define UINT_TO_PTR(u) ((void *) ((uintptr_t) (u))) + +#define PTR_TO_LONG(p) ((long) ((intptr_t) (p))) +#define LONG_TO_PTR(u) ((void *) ((intptr_t) (u))) +#define PTR_TO_ULONG(p) ((unsigned long) ((uintptr_t) (p))) +#define ULONG_TO_PTR(u) ((void *) ((uintptr_t) (u))) + +#define PTR_TO_INT32(p) ((int32_t) ((intptr_t) (p))) +#define INT32_TO_PTR(u) ((void *) ((intptr_t) (u))) +#define PTR_TO_UINT32(p) ((uint32_t) ((uintptr_t) (p))) +#define UINT32_TO_PTR(u) ((void *) ((uintptr_t) (u))) + +#define PTR_TO_INT64(p) ((int64_t) ((intptr_t) (p))) +#define INT64_TO_PTR(u) ((void *) ((intptr_t) (u))) +#define PTR_TO_UINT64(p) ((uint64_t) ((uintptr_t) (p))) +#define UINT64_TO_PTR(u) ((void *) ((uintptr_t) (u))) + +#define PTR_TO_SIZE(p) ((size_t) ((uintptr_t) (p))) +#define SIZE_TO_PTR(u) ((void *) ((uintptr_t) (u))) + +#define CHAR_TO_STR(x) ((char[2]) { x, 0 }) + +#define char_array_0(x) x[sizeof(x)-1] = 0; + +/* Returns the number of chars needed to format variables of the + * specified type as a decimal string. Adds in extra space for a + * negative '-' prefix (hence works correctly on signed + * types). Includes space for the trailing NUL. */ +#define DECIMAL_STR_MAX(type) \ + (2+(sizeof(type) <= 1 ? 3 : \ + sizeof(type) <= 2 ? 5 : \ + sizeof(type) <= 4 ? 10 : \ + sizeof(type) <= 8 ? 20 : sizeof(int[-2*(sizeof(type) > 8)]))) + +#define DECIMAL_STR_WIDTH(x) \ + ({ \ + typeof(x) _x_ = (x); \ + unsigned ans = 1; \ + while (_x_ /= 10) \ + ans++; \ + ans; \ + }) + +#define SET_FLAG(v, flag, b) \ + (v) = (b) ? ((v) | (flag)) : ((v) & ~(flag)) + +#define CASE_F(X) case X: +#define CASE_F_1(CASE, X) CASE_F(X) +#define CASE_F_2(CASE, X, ...) CASE(X) CASE_F_1(CASE, __VA_ARGS__) +#define CASE_F_3(CASE, X, ...) CASE(X) CASE_F_2(CASE, __VA_ARGS__) +#define CASE_F_4(CASE, X, ...) CASE(X) CASE_F_3(CASE, __VA_ARGS__) +#define CASE_F_5(CASE, X, ...) CASE(X) CASE_F_4(CASE, __VA_ARGS__) +#define CASE_F_6(CASE, X, ...) CASE(X) CASE_F_5(CASE, __VA_ARGS__) +#define CASE_F_7(CASE, X, ...) CASE(X) CASE_F_6(CASE, __VA_ARGS__) +#define CASE_F_8(CASE, X, ...) CASE(X) CASE_F_7(CASE, __VA_ARGS__) +#define CASE_F_9(CASE, X, ...) CASE(X) CASE_F_8(CASE, __VA_ARGS__) +#define CASE_F_10(CASE, X, ...) CASE(X) CASE_F_9(CASE, __VA_ARGS__) +#define CASE_F_11(CASE, X, ...) CASE(X) CASE_F_10(CASE, __VA_ARGS__) +#define CASE_F_12(CASE, X, ...) CASE(X) CASE_F_11(CASE, __VA_ARGS__) +#define CASE_F_13(CASE, X, ...) CASE(X) CASE_F_12(CASE, __VA_ARGS__) +#define CASE_F_14(CASE, X, ...) CASE(X) CASE_F_13(CASE, __VA_ARGS__) +#define CASE_F_15(CASE, X, ...) CASE(X) CASE_F_14(CASE, __VA_ARGS__) +#define CASE_F_16(CASE, X, ...) CASE(X) CASE_F_15(CASE, __VA_ARGS__) +#define CASE_F_17(CASE, X, ...) CASE(X) CASE_F_16(CASE, __VA_ARGS__) +#define CASE_F_18(CASE, X, ...) CASE(X) CASE_F_17(CASE, __VA_ARGS__) +#define CASE_F_19(CASE, X, ...) CASE(X) CASE_F_18(CASE, __VA_ARGS__) +#define CASE_F_20(CASE, X, ...) CASE(X) CASE_F_19(CASE, __VA_ARGS__) + +#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,NAME,...) NAME +#define FOR_EACH_MAKE_CASE(...) \ + GET_CASE_F(__VA_ARGS__,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12,CASE_F_11, \ + CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \ + (CASE_F,__VA_ARGS__) + +#define IN_SET(x, ...) \ + ({ \ + bool _found = false; \ + /* If the build breaks in the line below, you need to extend the case macros */ \ + static _unused_ char _static_assert__macros_need_to_be_extended[20 - sizeof((int[]){__VA_ARGS__})/sizeof(int)]; \ + switch(x) { \ + FOR_EACH_MAKE_CASE(__VA_ARGS__) \ + _found = true; \ + break; \ + default: \ + break; \ + } \ + _found; \ + }) + +/* Define C11 thread_local attribute even on older gcc compiler + * version */ +#ifndef thread_local +/* + * Don't break on glibc < 2.16 that doesn't define __STDC_NO_THREADS__ + * see http://gcc.gnu.org/bugzilla/show_bug.cgi?id=53769 + */ +#if __STDC_VERSION__ >= 201112L && !(defined(__STDC_NO_THREADS__) || (defined(__GNU_LIBRARY__) && __GLIBC__ == 2 && __GLIBC_MINOR__ < 16)) +#define thread_local _Thread_local +#else +#define thread_local __thread +#endif +#endif + +/* Define C11 noreturn without and even on older gcc + * compiler versions */ +#ifndef noreturn +#if __STDC_VERSION__ >= 201112L +#define noreturn _Noreturn +#else +#define noreturn __attribute__((noreturn)) +#endif +#endif + +#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \ + static inline void func##p(type *p) { \ + if (*p) \ + func(*p); \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +#include "log.h" diff --git a/src/libbasic/memfd-util.c b/src/libbasic/memfd-util.c new file mode 100644 index 0000000000..8c8cc78ebf --- /dev/null +++ b/src/libbasic/memfd-util.c @@ -0,0 +1,174 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#ifdef HAVE_LINUX_MEMFD_H +#include +#endif +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "macro.h" +#include "memfd-util.h" +#include "missing.h" +#include "string-util.h" +#include "utf8.h" + +int memfd_new(const char *name) { + _cleanup_free_ char *g = NULL; + int fd; + + if (!name) { + char pr[17] = {}; + + /* If no name is specified we generate one. We include + * a hint indicating our library implementation, and + * add the thread name to it */ + + assert_se(prctl(PR_GET_NAME, (unsigned long) pr) >= 0); + + if (isempty(pr)) + name = "sd"; + else { + _cleanup_free_ char *e = NULL; + + e = utf8_escape_invalid(pr); + if (!e) + return -ENOMEM; + + g = strappend("sd-", e); + if (!g) + return -ENOMEM; + + name = g; + } + } + + fd = memfd_create(name, MFD_ALLOW_SEALING | MFD_CLOEXEC); + if (fd < 0) + return -errno; + + return fd; +} + +int memfd_map(int fd, uint64_t offset, size_t size, void **p) { + void *q; + int sealed; + + assert(fd >= 0); + assert(size > 0); + assert(p); + + sealed = memfd_get_sealed(fd); + if (sealed < 0) + return sealed; + + if (sealed) + q = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, offset); + else + q = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, offset); + + if (q == MAP_FAILED) + return -errno; + + *p = q; + return 0; +} + +int memfd_set_sealed(int fd) { + int r; + + assert(fd >= 0); + + r = fcntl(fd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL); + if (r < 0) + return -errno; + + return 0; +} + +int memfd_get_sealed(int fd) { + int r; + + assert(fd >= 0); + + r = fcntl(fd, F_GET_SEALS); + if (r < 0) + return -errno; + + return r == (F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE | F_SEAL_SEAL); +} + +int memfd_get_size(int fd, uint64_t *sz) { + struct stat stat; + int r; + + assert(fd >= 0); + assert(sz); + + r = fstat(fd, &stat); + if (r < 0) + return -errno; + + *sz = stat.st_size; + return 0; +} + +int memfd_set_size(int fd, uint64_t sz) { + int r; + + assert(fd >= 0); + + r = ftruncate(fd, sz); + if (r < 0) + return -errno; + + return 0; +} + +int memfd_new_and_map(const char *name, size_t sz, void **p) { + _cleanup_close_ int fd = -1; + int r; + + assert(sz > 0); + assert(p); + + fd = memfd_new(name); + if (fd < 0) + return fd; + + r = memfd_set_size(fd, sz); + if (r < 0) + return r; + + r = memfd_map(fd, 0, sz, p); + if (r < 0) + return r; + + r = fd; + fd = -1; + + return r; +} diff --git a/src/libbasic/memfd-util.h b/src/libbasic/memfd-util.h new file mode 100644 index 0000000000..46d4989e4c --- /dev/null +++ b/src/libbasic/memfd-util.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + 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 . +***/ + +#include +#include +#include +#include + +int memfd_new(const char *name); +int memfd_new_and_map(const char *name, size_t sz, void **p); + +int memfd_map(int fd, uint64_t offset, size_t size, void **p); + +int memfd_set_sealed(int fd); +int memfd_get_sealed(int fd); + +int memfd_get_size(int fd, uint64_t *sz); +int memfd_set_size(int fd, uint64_t sz); diff --git a/src/libbasic/mempool.c b/src/libbasic/mempool.c new file mode 100644 index 0000000000..f95e2beb0f --- /dev/null +++ b/src/libbasic/mempool.c @@ -0,0 +1,104 @@ +/*** + This file is part of systemd. + + Copyright 2010-2014 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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 . +***/ + +#include +#include + +#include "macro.h" +#include "mempool.h" +#include "util.h" + +struct pool { + struct pool *next; + unsigned n_tiles; + unsigned n_used; +}; + +void* mempool_alloc_tile(struct mempool *mp) { + unsigned i; + + /* When a tile is released we add it to the list and simply + * place the next pointer at its offset 0. */ + + assert(mp->tile_size >= sizeof(void*)); + assert(mp->at_least > 0); + + if (mp->freelist) { + void *r; + + r = mp->freelist; + mp->freelist = * (void**) mp->freelist; + return r; + } + + if (_unlikely_(!mp->first_pool) || + _unlikely_(mp->first_pool->n_used >= mp->first_pool->n_tiles)) { + unsigned n; + size_t size; + struct pool *p; + + n = mp->first_pool ? mp->first_pool->n_tiles : 0; + n = MAX(mp->at_least, n * 2); + size = PAGE_ALIGN(ALIGN(sizeof(struct pool)) + n*mp->tile_size); + n = (size - ALIGN(sizeof(struct pool))) / mp->tile_size; + + p = malloc(size); + if (!p) + return NULL; + + p->next = mp->first_pool; + p->n_tiles = n; + p->n_used = 0; + + mp->first_pool = p; + } + + i = mp->first_pool->n_used++; + + return ((uint8_t*) mp->first_pool) + ALIGN(sizeof(struct pool)) + i*mp->tile_size; +} + +void* mempool_alloc0_tile(struct mempool *mp) { + void *p; + + p = mempool_alloc_tile(mp); + if (p) + memzero(p, mp->tile_size); + return p; +} + +void mempool_free_tile(struct mempool *mp, void *p) { + * (void**) p = mp->freelist; + mp->freelist = p; +} + +#ifdef VALGRIND + +void mempool_drop(struct mempool *mp) { + struct pool *p = mp->first_pool; + while (p) { + struct pool *n; + n = p->next; + free(p); + p = n; + } +} + +#endif diff --git a/src/libbasic/mempool.h b/src/libbasic/mempool.h new file mode 100644 index 0000000000..fea7841bcf --- /dev/null +++ b/src/libbasic/mempool.h @@ -0,0 +1,47 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011-2014 Lennart Poettering + Copyright 2014 Michal Schmidt + + 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 . +***/ + +#include + +struct pool; + +struct mempool { + struct pool *first_pool; + void *freelist; + size_t tile_size; + unsigned at_least; +}; + +void* mempool_alloc_tile(struct mempool *mp); +void* mempool_alloc0_tile(struct mempool *mp); +void mempool_free_tile(struct mempool *mp, void *p); + +#define DEFINE_MEMPOOL(pool_name, tile_type, alloc_at_least) \ +struct mempool pool_name = { \ + .tile_size = sizeof(tile_type), \ + .at_least = alloc_at_least, \ +} + + +#ifdef VALGRIND +void mempool_drop(struct mempool *mp); +#endif diff --git a/src/libbasic/missing.h b/src/libbasic/missing.h new file mode 100644 index 0000000000..36b060496a --- /dev/null +++ b/src/libbasic/missing.h @@ -0,0 +1,1170 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +/* Missing glibc definitions to access certain kernel APIs */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_AUDIT +#include +#endif + +#ifdef ARCH_MIPS +#include +#endif + +#ifdef HAVE_LINUX_BTRFS_H +#include +#endif + +#include "macro.h" + +#ifndef RLIMIT_RTTIME +#define RLIMIT_RTTIME 15 +#endif + +/* If RLIMIT_RTTIME is not defined, then we cannot use RLIMIT_NLIMITS as is */ +#define _RLIMIT_MAX (RLIMIT_RTTIME+1 > RLIMIT_NLIMITS ? RLIMIT_RTTIME+1 : RLIMIT_NLIMITS) + +#ifndef F_LINUX_SPECIFIC_BASE +#define F_LINUX_SPECIFIC_BASE 1024 +#endif + +#ifndef F_SETPIPE_SZ +#define F_SETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 7) +#endif + +#ifndef F_GETPIPE_SZ +#define F_GETPIPE_SZ (F_LINUX_SPECIFIC_BASE + 8) +#endif + +#ifndef F_ADD_SEALS +#define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9) +#define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10) + +#define F_SEAL_SEAL 0x0001 /* prevent further seals from being set */ +#define F_SEAL_SHRINK 0x0002 /* prevent file from shrinking */ +#define F_SEAL_GROW 0x0004 /* prevent file from growing */ +#define F_SEAL_WRITE 0x0008 /* prevent writes */ +#endif + +#ifndef F_OFD_GETLK +#define F_OFD_GETLK 36 +#define F_OFD_SETLK 37 +#define F_OFD_SETLKW 38 +#endif + +#ifndef MFD_ALLOW_SEALING +#define MFD_ALLOW_SEALING 0x0002U +#endif + +#ifndef MFD_CLOEXEC +#define MFD_CLOEXEC 0x0001U +#endif + +#ifndef IP_FREEBIND +#define IP_FREEBIND 15 +#endif + +#ifndef OOM_SCORE_ADJ_MIN +#define OOM_SCORE_ADJ_MIN (-1000) +#endif + +#ifndef OOM_SCORE_ADJ_MAX +#define OOM_SCORE_ADJ_MAX 1000 +#endif + +#ifndef AUDIT_SERVICE_START +#define AUDIT_SERVICE_START 1130 /* Service (daemon) start */ +#endif + +#ifndef AUDIT_SERVICE_STOP +#define AUDIT_SERVICE_STOP 1131 /* Service (daemon) stop */ +#endif + +#ifndef TIOCVHANGUP +#define TIOCVHANGUP 0x5437 +#endif + +#ifndef IP_TRANSPARENT +#define IP_TRANSPARENT 19 +#endif + +#ifndef SOL_NETLINK +#define SOL_NETLINK 270 +#endif + +#ifndef NETLINK_LIST_MEMBERSHIPS +#define NETLINK_LIST_MEMBERSHIPS 9 +#endif + +#ifndef SOL_SCTP +#define SOL_SCTP 132 +#endif + +#if !HAVE_DECL_PIVOT_ROOT +static inline int pivot_root(const char *new_root, const char *put_old) { + return syscall(SYS_pivot_root, new_root, put_old); +} +#endif + +#ifndef __NR_memfd_create +# if defined __x86_64__ +# define __NR_memfd_create 319 +# elif defined __arm__ +# define __NR_memfd_create 385 +# elif defined __aarch64__ +# define __NR_memfd_create 279 +# elif defined __s390__ +# define __NR_memfd_create 350 +# elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_memfd_create 4354 +# endif +# if _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_memfd_create 6318 +# endif +# if _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_memfd_create 5314 +# endif +# elif defined __i386__ +# define __NR_memfd_create 356 +# else +# warning "__NR_memfd_create unknown for your architecture" +# define __NR_memfd_create 0xffffffff +# endif +#endif + +#ifndef HAVE_MEMFD_CREATE +static inline int memfd_create(const char *name, unsigned int flags) { + return syscall(__NR_memfd_create, name, flags); +} +#endif + +#ifndef __NR_getrandom +# if defined __x86_64__ +# define __NR_getrandom 318 +# elif defined(__i386__) +# define __NR_getrandom 355 +# elif defined(__arm__) +# define __NR_getrandom 384 +# elif defined(__aarch64__) +# define __NR_getrandom 278 +# elif defined(__ia64__) +# define __NR_getrandom 1339 +# elif defined(__m68k__) +# define __NR_getrandom 352 +# elif defined(__s390x__) +# define __NR_getrandom 349 +# elif defined(__powerpc__) +# define __NR_getrandom 359 +# elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_getrandom 4353 +# endif +# if _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_getrandom 6317 +# endif +# if _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_getrandom 5313 +# endif +# else +# warning "__NR_getrandom unknown for your architecture" +# define __NR_getrandom 0xffffffff +# endif +#endif + +#if !HAVE_DECL_GETRANDOM +static inline int getrandom(void *buffer, size_t count, unsigned flags) { + return syscall(__NR_getrandom, buffer, count, flags); +} +#endif + +#ifndef GRND_NONBLOCK +#define GRND_NONBLOCK 0x0001 +#endif + +#ifndef GRND_RANDOM +#define GRND_RANDOM 0x0002 +#endif + +#ifndef BTRFS_IOCTL_MAGIC +#define BTRFS_IOCTL_MAGIC 0x94 +#endif + +#ifndef BTRFS_PATH_NAME_MAX +#define BTRFS_PATH_NAME_MAX 4087 +#endif + +#ifndef BTRFS_DEVICE_PATH_NAME_MAX +#define BTRFS_DEVICE_PATH_NAME_MAX 1024 +#endif + +#ifndef BTRFS_FSID_SIZE +#define BTRFS_FSID_SIZE 16 +#endif + +#ifndef BTRFS_UUID_SIZE +#define BTRFS_UUID_SIZE 16 +#endif + +#ifndef BTRFS_SUBVOL_RDONLY +#define BTRFS_SUBVOL_RDONLY (1ULL << 1) +#endif + +#ifndef BTRFS_SUBVOL_NAME_MAX +#define BTRFS_SUBVOL_NAME_MAX 4039 +#endif + +#ifndef BTRFS_INO_LOOKUP_PATH_MAX +#define BTRFS_INO_LOOKUP_PATH_MAX 4080 +#endif + +#ifndef BTRFS_SEARCH_ARGS_BUFSIZE +#define BTRFS_SEARCH_ARGS_BUFSIZE (4096 - sizeof(struct btrfs_ioctl_search_key)) +#endif + +#ifndef BTRFS_QGROUP_LEVEL_SHIFT +#define BTRFS_QGROUP_LEVEL_SHIFT 48 +#endif + +#ifndef HAVE_LINUX_BTRFS_H +struct btrfs_ioctl_vol_args { + int64_t fd; + char name[BTRFS_PATH_NAME_MAX + 1]; +}; + +struct btrfs_qgroup_limit { + __u64 flags; + __u64 max_rfer; + __u64 max_excl; + __u64 rsv_rfer; + __u64 rsv_excl; +}; + +struct btrfs_qgroup_inherit { + __u64 flags; + __u64 num_qgroups; + __u64 num_ref_copies; + __u64 num_excl_copies; + struct btrfs_qgroup_limit lim; + __u64 qgroups[0]; +}; + +struct btrfs_ioctl_qgroup_limit_args { + __u64 qgroupid; + struct btrfs_qgroup_limit lim; +}; + +struct btrfs_ioctl_vol_args_v2 { + __s64 fd; + __u64 transid; + __u64 flags; + union { + struct { + __u64 size; + struct btrfs_qgroup_inherit *qgroup_inherit; + }; + __u64 unused[4]; + }; + char name[BTRFS_SUBVOL_NAME_MAX + 1]; +}; + +struct btrfs_ioctl_dev_info_args { + uint64_t devid; /* in/out */ + uint8_t uuid[BTRFS_UUID_SIZE]; /* in/out */ + uint64_t bytes_used; /* out */ + uint64_t total_bytes; /* out */ + uint64_t unused[379]; /* pad to 4k */ + char path[BTRFS_DEVICE_PATH_NAME_MAX]; /* out */ +}; + +struct btrfs_ioctl_fs_info_args { + uint64_t max_id; /* out */ + uint64_t num_devices; /* out */ + uint8_t fsid[BTRFS_FSID_SIZE]; /* out */ + uint64_t reserved[124]; /* pad to 1k */ +}; + +struct btrfs_ioctl_ino_lookup_args { + __u64 treeid; + __u64 objectid; + char name[BTRFS_INO_LOOKUP_PATH_MAX]; +}; + +struct btrfs_ioctl_search_key { + /* which root are we searching. 0 is the tree of tree roots */ + __u64 tree_id; + + /* keys returned will be >= min and <= max */ + __u64 min_objectid; + __u64 max_objectid; + + /* keys returned will be >= min and <= max */ + __u64 min_offset; + __u64 max_offset; + + /* max and min transids to search for */ + __u64 min_transid; + __u64 max_transid; + + /* keys returned will be >= min and <= max */ + __u32 min_type; + __u32 max_type; + + /* + * how many items did userland ask for, and how many are we + * returning + */ + __u32 nr_items; + + /* align to 64 bits */ + __u32 unused; + + /* some extra for later */ + __u64 unused1; + __u64 unused2; + __u64 unused3; + __u64 unused4; +}; + +struct btrfs_ioctl_search_header { + __u64 transid; + __u64 objectid; + __u64 offset; + __u32 type; + __u32 len; +}; + + +struct btrfs_ioctl_search_args { + struct btrfs_ioctl_search_key key; + char buf[BTRFS_SEARCH_ARGS_BUFSIZE]; +}; + +struct btrfs_ioctl_clone_range_args { + __s64 src_fd; + __u64 src_offset, src_length; + __u64 dest_offset; +}; + +#define BTRFS_QUOTA_CTL_ENABLE 1 +#define BTRFS_QUOTA_CTL_DISABLE 2 +#define BTRFS_QUOTA_CTL_RESCAN__NOTUSED 3 +struct btrfs_ioctl_quota_ctl_args { + __u64 cmd; + __u64 status; +}; +#endif + +#ifndef BTRFS_IOC_DEFRAG +#define BTRFS_IOC_DEFRAG _IOW(BTRFS_IOCTL_MAGIC, 2, \ + struct btrfs_ioctl_vol_args) +#endif + +#ifndef BTRFS_IOC_RESIZE +#define BTRFS_IOC_RESIZE _IOW(BTRFS_IOCTL_MAGIC, 3, \ + struct btrfs_ioctl_vol_args) +#endif + +#ifndef BTRFS_IOC_CLONE +#define BTRFS_IOC_CLONE _IOW(BTRFS_IOCTL_MAGIC, 9, int) +#endif + +#ifndef BTRFS_IOC_CLONE_RANGE +#define BTRFS_IOC_CLONE_RANGE _IOW(BTRFS_IOCTL_MAGIC, 13, \ + struct btrfs_ioctl_clone_range_args) +#endif + +#ifndef BTRFS_IOC_SUBVOL_CREATE +#define BTRFS_IOC_SUBVOL_CREATE _IOW(BTRFS_IOCTL_MAGIC, 14, \ + struct btrfs_ioctl_vol_args) +#endif + +#ifndef BTRFS_IOC_SNAP_DESTROY +#define BTRFS_IOC_SNAP_DESTROY _IOW(BTRFS_IOCTL_MAGIC, 15, \ + struct btrfs_ioctl_vol_args) +#endif + +#ifndef BTRFS_IOC_TREE_SEARCH +#define BTRFS_IOC_TREE_SEARCH _IOWR(BTRFS_IOCTL_MAGIC, 17, \ + struct btrfs_ioctl_search_args) +#endif + +#ifndef BTRFS_IOC_INO_LOOKUP +#define BTRFS_IOC_INO_LOOKUP _IOWR(BTRFS_IOCTL_MAGIC, 18, \ + struct btrfs_ioctl_ino_lookup_args) +#endif + +#ifndef BTRFS_IOC_SNAP_CREATE_V2 +#define BTRFS_IOC_SNAP_CREATE_V2 _IOW(BTRFS_IOCTL_MAGIC, 23, \ + struct btrfs_ioctl_vol_args_v2) +#endif + +#ifndef BTRFS_IOC_SUBVOL_GETFLAGS +#define BTRFS_IOC_SUBVOL_GETFLAGS _IOR(BTRFS_IOCTL_MAGIC, 25, __u64) +#endif + +#ifndef BTRFS_IOC_SUBVOL_SETFLAGS +#define BTRFS_IOC_SUBVOL_SETFLAGS _IOW(BTRFS_IOCTL_MAGIC, 26, __u64) +#endif + +#ifndef BTRFS_IOC_DEV_INFO +#define BTRFS_IOC_DEV_INFO _IOWR(BTRFS_IOCTL_MAGIC, 30, \ + struct btrfs_ioctl_dev_info_args) +#endif + +#ifndef BTRFS_IOC_FS_INFO +#define BTRFS_IOC_FS_INFO _IOR(BTRFS_IOCTL_MAGIC, 31, \ + struct btrfs_ioctl_fs_info_args) +#endif + +#ifndef BTRFS_IOC_DEVICES_READY +#define BTRFS_IOC_DEVICES_READY _IOR(BTRFS_IOCTL_MAGIC, 39, \ + struct btrfs_ioctl_vol_args) +#endif + +#ifndef BTRFS_IOC_QUOTA_CTL +#define BTRFS_IOC_QUOTA_CTL _IOWR(BTRFS_IOCTL_MAGIC, 40, \ + struct btrfs_ioctl_quota_ctl_args) +#endif + +#ifndef BTRFS_IOC_QGROUP_LIMIT +#define BTRFS_IOC_QGROUP_LIMIT _IOR(BTRFS_IOCTL_MAGIC, 43, \ + struct btrfs_ioctl_qgroup_limit_args) +#endif + +#ifndef BTRFS_FIRST_FREE_OBJECTID +#define BTRFS_FIRST_FREE_OBJECTID 256 +#endif + +#ifndef BTRFS_LAST_FREE_OBJECTID +#define BTRFS_LAST_FREE_OBJECTID -256ULL +#endif + +#ifndef BTRFS_ROOT_TREE_OBJECTID +#define BTRFS_ROOT_TREE_OBJECTID 1 +#endif + +#ifndef BTRFS_QUOTA_TREE_OBJECTID +#define BTRFS_QUOTA_TREE_OBJECTID 8ULL +#endif + +#ifndef BTRFS_ROOT_ITEM_KEY +#define BTRFS_ROOT_ITEM_KEY 132 +#endif + +#ifndef BTRFS_QGROUP_STATUS_KEY +#define BTRFS_QGROUP_STATUS_KEY 240 +#endif + +#ifndef BTRFS_QGROUP_INFO_KEY +#define BTRFS_QGROUP_INFO_KEY 242 +#endif + +#ifndef BTRFS_QGROUP_LIMIT_KEY +#define BTRFS_QGROUP_LIMIT_KEY 244 +#endif + +#ifndef BTRFS_QGROUP_RELATION_KEY +#define BTRFS_QGROUP_RELATION_KEY 246 +#endif + +#ifndef BTRFS_ROOT_BACKREF_KEY +#define BTRFS_ROOT_BACKREF_KEY 144 +#endif + +#ifndef BTRFS_SUPER_MAGIC +#define BTRFS_SUPER_MAGIC 0x9123683E +#endif + +#ifndef CGROUP_SUPER_MAGIC +#define CGROUP_SUPER_MAGIC 0x27e0eb +#endif + +#ifndef TMPFS_MAGIC +#define TMPFS_MAGIC 0x01021994 +#endif + +#ifndef MS_MOVE +#define MS_MOVE 8192 +#endif + +#ifndef MS_PRIVATE +#define MS_PRIVATE (1 << 18) +#endif + +#if !HAVE_DECL_GETTID +static inline pid_t gettid(void) { + return (pid_t) syscall(SYS_gettid); +} +#endif + +#ifndef SCM_SECURITY +#define SCM_SECURITY 0x03 +#endif + +#ifndef MS_STRICTATIME +#define MS_STRICTATIME (1<<24) +#endif + +#ifndef MS_REC +#define MS_REC 16384 +#endif + +#ifndef MS_SHARED +#define MS_SHARED (1<<20) +#endif + +#ifndef PR_SET_NO_NEW_PRIVS +#define PR_SET_NO_NEW_PRIVS 38 +#endif + +#ifndef PR_SET_CHILD_SUBREAPER +#define PR_SET_CHILD_SUBREAPER 36 +#endif + +#ifndef MAX_HANDLE_SZ +#define MAX_HANDLE_SZ 128 +#endif + +#ifndef __NR_name_to_handle_at +# if defined(__x86_64__) +# define __NR_name_to_handle_at 303 +# elif defined(__i386__) +# define __NR_name_to_handle_at 341 +# elif defined(__arm__) +# define __NR_name_to_handle_at 370 +# elif defined(__powerpc__) +# define __NR_name_to_handle_at 345 +# else +# error "__NR_name_to_handle_at is not defined" +# endif +#endif + +#if !HAVE_DECL_NAME_TO_HANDLE_AT +struct file_handle { + unsigned int handle_bytes; + int handle_type; + unsigned char f_handle[0]; +}; + +static inline int name_to_handle_at(int fd, const char *name, struct file_handle *handle, int *mnt_id, int flags) { + return syscall(__NR_name_to_handle_at, fd, name, handle, mnt_id, flags); +} +#endif + +#ifndef HAVE_SECURE_GETENV +# ifdef HAVE___SECURE_GETENV +# define secure_getenv __secure_getenv +# else +# error "neither secure_getenv nor __secure_getenv are available" +# endif +#endif + +#ifndef CIFS_MAGIC_NUMBER +# define CIFS_MAGIC_NUMBER 0xFF534D42 +#endif + +#ifndef TFD_TIMER_CANCEL_ON_SET +# define TFD_TIMER_CANCEL_ON_SET (1 << 1) +#endif + +#ifndef SO_REUSEPORT +# define SO_REUSEPORT 15 +#endif + +#ifndef EVIOCREVOKE +# define EVIOCREVOKE _IOW('E', 0x91, int) +#endif + +#ifndef DRM_IOCTL_SET_MASTER +# define DRM_IOCTL_SET_MASTER _IO('d', 0x1e) +#endif + +#ifndef DRM_IOCTL_DROP_MASTER +# define DRM_IOCTL_DROP_MASTER _IO('d', 0x1f) +#endif + +#if defined(__i386__) || defined(__x86_64__) + +/* The precise definition of __O_TMPFILE is arch specific, so let's + * just define this on x86 where we know the value. */ + +#ifndef __O_TMPFILE +#define __O_TMPFILE 020000000 +#endif + +/* a horrid kludge trying to make sure that this will fail on old kernels */ +#ifndef O_TMPFILE +#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY) +#endif + +#endif + +#ifndef __NR_setns +# if defined(__x86_64__) +# define __NR_setns 308 +# elif defined(__i386__) +# define __NR_setns 346 +# else +# error "__NR_setns is not defined" +# endif +#endif + +#if !HAVE_DECL_SETNS +static inline int setns(int fd, int nstype) { + return syscall(__NR_setns, fd, nstype); +} +#endif + +#if !HAVE_DECL_LO_FLAGS_PARTSCAN +#define LO_FLAGS_PARTSCAN 8 +#endif + +#ifndef LOOP_CTL_REMOVE +#define LOOP_CTL_REMOVE 0x4C81 +#endif + +#ifndef LOOP_CTL_GET_FREE +#define LOOP_CTL_GET_FREE 0x4C82 +#endif + +#if !HAVE_DECL_IFLA_INET6_ADDR_GEN_MODE +#define IFLA_INET6_UNSPEC 0 +#define IFLA_INET6_FLAGS 1 +#define IFLA_INET6_CONF 2 +#define IFLA_INET6_STATS 3 +#define IFLA_INET6_MCAST 4 +#define IFLA_INET6_CACHEINFO 5 +#define IFLA_INET6_ICMP6STATS 6 +#define IFLA_INET6_TOKEN 7 +#define IFLA_INET6_ADDR_GEN_MODE 8 +#define __IFLA_INET6_MAX 9 + +#define IFLA_INET6_MAX (__IFLA_INET6_MAX - 1) + +#define IN6_ADDR_GEN_MODE_EUI64 0 +#define IN6_ADDR_GEN_MODE_NONE 1 +#endif + +#if !HAVE_DECL_IFLA_MACVLAN_FLAGS +#define IFLA_MACVLAN_UNSPEC 0 +#define IFLA_MACVLAN_MODE 1 +#define IFLA_MACVLAN_FLAGS 2 +#define __IFLA_MACVLAN_MAX 3 + +#define IFLA_MACVLAN_MAX (__IFLA_MACVLAN_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_IPVLAN_MODE +#define IFLA_IPVLAN_UNSPEC 0 +#define IFLA_IPVLAN_MODE 1 +#define __IFLA_IPVLAN_MAX 2 + +#define IFLA_IPVLAN_MAX (__IFLA_IPVLAN_MAX - 1) + +#define IPVLAN_MODE_L2 0 +#define IPVLAN_MODE_L3 1 +#define IPVLAN_MAX 2 +#endif + +#if !HAVE_DECL_IFLA_VTI_REMOTE +#define IFLA_VTI_UNSPEC 0 +#define IFLA_VTI_LINK 1 +#define IFLA_VTI_IKEY 2 +#define IFLA_VTI_OKEY 3 +#define IFLA_VTI_LOCAL 4 +#define IFLA_VTI_REMOTE 5 +#define __IFLA_VTI_MAX 6 + +#define IFLA_VTI_MAX (__IFLA_VTI_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_PHYS_PORT_ID +#undef IFLA_PROMISCUITY +#define IFLA_PROMISCUITY 30 +#define IFLA_NUM_TX_QUEUES 31 +#define IFLA_NUM_RX_QUEUES 32 +#define IFLA_CARRIER 33 +#define IFLA_PHYS_PORT_ID 34 +#define __IFLA_MAX 35 + +#define IFLA_MAX (__IFLA_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_BOND_AD_INFO +#define IFLA_BOND_UNSPEC 0 +#define IFLA_BOND_MODE 1 +#define IFLA_BOND_ACTIVE_SLAVE 2 +#define IFLA_BOND_MIIMON 3 +#define IFLA_BOND_UPDELAY 4 +#define IFLA_BOND_DOWNDELAY 5 +#define IFLA_BOND_USE_CARRIER 6 +#define IFLA_BOND_ARP_INTERVAL 7 +#define IFLA_BOND_ARP_IP_TARGET 8 +#define IFLA_BOND_ARP_VALIDATE 9 +#define IFLA_BOND_ARP_ALL_TARGETS 10 +#define IFLA_BOND_PRIMARY 11 +#define IFLA_BOND_PRIMARY_RESELECT 12 +#define IFLA_BOND_FAIL_OVER_MAC 13 +#define IFLA_BOND_XMIT_HASH_POLICY 14 +#define IFLA_BOND_RESEND_IGMP 15 +#define IFLA_BOND_NUM_PEER_NOTIF 16 +#define IFLA_BOND_ALL_SLAVES_ACTIVE 17 +#define IFLA_BOND_MIN_LINKS 18 +#define IFLA_BOND_LP_INTERVAL 19 +#define IFLA_BOND_PACKETS_PER_SLAVE 20 +#define IFLA_BOND_AD_LACP_RATE 21 +#define IFLA_BOND_AD_SELECT 22 +#define IFLA_BOND_AD_INFO 23 +#define __IFLA_BOND_MAX 24 + +#define IFLA_BOND_MAX (__IFLA_BOND_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_VLAN_PROTOCOL +#define IFLA_VLAN_UNSPEC 0 +#define IFLA_VLAN_ID 1 +#define IFLA_VLAN_FLAGS 2 +#define IFLA_VLAN_EGRESS_QOS 3 +#define IFLA_VLAN_INGRESS_QOS 4 +#define IFLA_VLAN_PROTOCOL 5 +#define __IFLA_VLAN_MAX 6 + +#define IFLA_VLAN_MAX (__IFLA_VLAN_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_VXLAN_REMCSUM_NOPARTIAL +#define IFLA_VXLAN_UNSPEC 0 +#define IFLA_VXLAN_ID 1 +#define IFLA_VXLAN_GROUP 2 +#define IFLA_VXLAN_LINK 3 +#define IFLA_VXLAN_LOCAL 4 +#define IFLA_VXLAN_TTL 5 +#define IFLA_VXLAN_TOS 6 +#define IFLA_VXLAN_LEARNING 7 +#define IFLA_VXLAN_AGEING 8 +#define IFLA_VXLAN_LIMIT 9 +#define IFLA_VXLAN_PORT_RANGE 10 +#define IFLA_VXLAN_PROXY 11 +#define IFLA_VXLAN_RSC 12 +#define IFLA_VXLAN_L2MISS 13 +#define IFLA_VXLAN_L3MISS 14 +#define IFLA_VXLAN_PORT 15 +#define IFLA_VXLAN_GROUP6 16 +#define IFLA_VXLAN_LOCAL6 17 +#define IFLA_VXLAN_UDP_CSUM 18 +#define IFLA_VXLAN_UDP_ZERO_CSUM6_TX 19 +#define IFLA_VXLAN_UDP_ZERO_CSUM6_RX 20 +#define IFLA_VXLAN_REMCSUM_TX 21 +#define IFLA_VXLAN_REMCSUM_RX 22 +#define IFLA_VXLAN_GBP 23 +#define IFLA_VXLAN_REMCSUM_NOPARTIAL 24 +#define __IFLA_VXLAN_MAX 25 + +#define IFLA_VXLAN_MAX (__IFLA_VXLAN_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_IPTUN_ENCAP_DPORT +#define IFLA_IPTUN_UNSPEC 0 +#define IFLA_IPTUN_LINK 1 +#define IFLA_IPTUN_LOCAL 2 +#define IFLA_IPTUN_REMOTE 3 +#define IFLA_IPTUN_TTL 4 +#define IFLA_IPTUN_TOS 5 +#define IFLA_IPTUN_ENCAP_LIMIT 6 +#define IFLA_IPTUN_FLOWINFO 7 +#define IFLA_IPTUN_FLAGS 8 +#define IFLA_IPTUN_PROTO 9 +#define IFLA_IPTUN_PMTUDISC 10 +#define IFLA_IPTUN_6RD_PREFIX 11 +#define IFLA_IPTUN_6RD_RELAY_PREFIX 12 +#define IFLA_IPTUN_6RD_PREFIXLEN 13 +#define IFLA_IPTUN_6RD_RELAY_PREFIXLEN 14 +#define IFLA_IPTUN_ENCAP_TYPE 15 +#define IFLA_IPTUN_ENCAP_FLAGS 16 +#define IFLA_IPTUN_ENCAP_SPORT 17 +#define IFLA_IPTUN_ENCAP_DPORT 18 + +#define __IFLA_IPTUN_MAX 19 + +#define IFLA_IPTUN_MAX (__IFLA_IPTUN_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_GRE_ENCAP_DPORT +#define IFLA_GRE_UNSPEC 0 +#define IFLA_GRE_LINK 1 +#define IFLA_GRE_IFLAGS 2 +#define IFLA_GRE_OFLAGS 3 +#define IFLA_GRE_IKEY 4 +#define IFLA_GRE_OKEY 5 +#define IFLA_GRE_LOCAL 6 +#define IFLA_GRE_REMOTE 7 +#define IFLA_GRE_TTL 8 +#define IFLA_GRE_TOS 9 +#define IFLA_GRE_PMTUDISC 10 +#define IFLA_GRE_ENCAP_LIMIT 11 +#define IFLA_GRE_FLOWINFO 12 +#define IFLA_GRE_FLAGS 13 +#define IFLA_GRE_ENCAP_TYPE 14 +#define IFLA_GRE_ENCAP_FLAGS 15 +#define IFLA_GRE_ENCAP_SPORT 16 +#define IFLA_GRE_ENCAP_DPORT 17 + +#define __IFLA_GRE_MAX 18 + +#define IFLA_GRE_MAX (__IFLA_GRE_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_BRIDGE_VLAN_INFO +#define IFLA_BRIDGE_FLAGS 0 +#define IFLA_BRIDGE_MODE 1 +#define IFLA_BRIDGE_VLAN_INFO 2 +#define __IFLA_BRIDGE_MAX 3 + +#define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_BR_PRIORITY +#define IFLA_BR_UNSPEC 0 +#define IFLA_BR_FORWARD_DELAY 1 +#define IFLA_BR_HELLO_TIME 2 +#define IFLA_BR_MAX_AGE 3 +#define IFLA_BR_AGEING_TIME 4 +#define IFLA_BR_STP_STATE 5 +#define IFLA_BR_PRIORITY 6 +#define __IFLA_BR_MAX 7 + +#define IFLA_BR_MAX (__IFLA_BR_MAX - 1) +#endif + +#if !HAVE_DECL_IFLA_BRPORT_LEARNING_SYNC +#define IFLA_BRPORT_UNSPEC 0 +#define IFLA_BRPORT_STATE 1 +#define IFLA_BRPORT_PRIORITY 2 +#define IFLA_BRPORT_COST 3 +#define IFLA_BRPORT_MODE 4 +#define IFLA_BRPORT_GUARD 5 +#define IFLA_BRPORT_PROTECT 6 +#define IFLA_BRPORT_FAST_LEAVE 7 +#define IFLA_BRPORT_LEARNING 8 +#define IFLA_BRPORT_UNICAST_FLOOD 9 +#define IFLA_BRPORT_PROXYARP 10 +#define IFLA_BRPORT_LEARNING_SYNC 11 +#define __IFLA_BRPORT_MAX 12 + +#define IFLA_BRPORT_MAX (__IFLA_BRPORT_MAX - 1) +#endif + +#if !HAVE_DECL_NDA_IFINDEX +#define NDA_UNSPEC 0 +#define NDA_DST 1 +#define NDA_LLADDR 2 +#define NDA_CACHEINFO 3 +#define NDA_PROBES 4 +#define NDA_VLAN 5 +#define NDA_PORT 6 +#define NDA_VNI 7 +#define NDA_IFINDEX 8 +#define __NDA_MAX 9 + +#define NDA_MAX (__NDA_MAX - 1) +#endif + +#ifndef RTA_PREF +#define RTA_PREF 20 +#endif + +#ifndef IPV6_UNICAST_IF +#define IPV6_UNICAST_IF 76 +#endif + +#ifndef IFF_MULTI_QUEUE +#define IFF_MULTI_QUEUE 0x100 +#endif + +#ifndef IFF_LOWER_UP +#define IFF_LOWER_UP 0x10000 +#endif + +#ifndef IFF_DORMANT +#define IFF_DORMANT 0x20000 +#endif + +#ifndef BOND_XMIT_POLICY_ENCAP23 +#define BOND_XMIT_POLICY_ENCAP23 3 +#endif + +#ifndef BOND_XMIT_POLICY_ENCAP34 +#define BOND_XMIT_POLICY_ENCAP34 4 +#endif + +#ifndef NET_ADDR_RANDOM +# define NET_ADDR_RANDOM 1 +#endif + +#ifndef NET_NAME_UNKNOWN +# define NET_NAME_UNKNOWN 0 +#endif + +#ifndef NET_NAME_ENUM +# define NET_NAME_ENUM 1 +#endif + +#ifndef NET_NAME_PREDICTABLE +# define NET_NAME_PREDICTABLE 2 +#endif + +#ifndef NET_NAME_USER +# define NET_NAME_USER 3 +#endif + +#ifndef NET_NAME_RENAMED +# define NET_NAME_RENAMED 4 +#endif + +#ifndef BPF_XOR +# define BPF_XOR 0xa0 +#endif + +/* Note that LOOPBACK_IFINDEX is currently not exported by the + * kernel/glibc, but hardcoded internally by the kernel. However, as + * it is exported to userspace indirectly via rtnetlink and the + * ioctls, and made use of widely we define it here too, in a way that + * is compatible with the kernel's internal definition. */ +#ifndef LOOPBACK_IFINDEX +#define LOOPBACK_IFINDEX 1 +#endif + +#if !HAVE_DECL_IFA_FLAGS +#define IFA_FLAGS 8 +#endif + +#ifndef IFA_F_MANAGETEMPADDR +#define IFA_F_MANAGETEMPADDR 0x100 +#endif + +#ifndef IFA_F_NOPREFIXROUTE +#define IFA_F_NOPREFIXROUTE 0x200 +#endif + +#ifndef MAX_AUDIT_MESSAGE_LENGTH +#define MAX_AUDIT_MESSAGE_LENGTH 8970 +#endif + +#ifndef AUDIT_NLGRP_MAX +#define AUDIT_NLGRP_READLOG 1 +#endif + +#ifndef CAP_MAC_OVERRIDE +#define CAP_MAC_OVERRIDE 32 +#endif + +#ifndef CAP_MAC_ADMIN +#define CAP_MAC_ADMIN 33 +#endif + +#ifndef CAP_SYSLOG +#define CAP_SYSLOG 34 +#endif + +#ifndef CAP_WAKE_ALARM +#define CAP_WAKE_ALARM 35 +#endif + +#ifndef CAP_BLOCK_SUSPEND +#define CAP_BLOCK_SUSPEND 36 +#endif + +#ifndef CAP_AUDIT_READ +#define CAP_AUDIT_READ 37 +#endif + +static inline int raw_clone(unsigned long flags, void *child_stack) { +#if defined(__s390__) || defined(__CRIS__) + /* On s390 and cris the order of the first and second arguments + * of the raw clone() system call is reversed. */ + return (int) syscall(__NR_clone, child_stack, flags); +#else + return (int) syscall(__NR_clone, flags, child_stack); +#endif +} + +static inline pid_t raw_getpid(void) { +#if defined(__alpha__) + return (pid_t) syscall(__NR_getxpid); +#else + return (pid_t) syscall(__NR_getpid); +#endif +} + +#if !HAVE_DECL_RENAMEAT2 + +#ifndef __NR_renameat2 +# if defined __x86_64__ +# define __NR_renameat2 316 +# elif defined __arm__ +# define __NR_renameat2 382 +# elif defined _MIPS_SIM +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_renameat2 4351 +# endif +# if _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_renameat2 6315 +# endif +# if _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_renameat2 5311 +# endif +# elif defined __i386__ +# define __NR_renameat2 353 +# else +# warning "__NR_renameat2 unknown for your architecture" +# define __NR_renameat2 0xffffffff +# endif +#endif + +static inline int renameat2(int oldfd, const char *oldname, int newfd, const char *newname, unsigned flags) { + return syscall(__NR_renameat2, oldfd, oldname, newfd, newname, flags); +} +#endif + +#ifndef RENAME_NOREPLACE +#define RENAME_NOREPLACE (1 << 0) +#endif + +#if !HAVE_DECL_KCMP +static inline int kcmp(pid_t pid1, pid_t pid2, int type, unsigned long idx1, unsigned long idx2) { +#if defined(__NR_kcmp) + return syscall(__NR_kcmp, pid1, pid2, type, idx1, idx2); +#else + errno = ENOSYS; + return -1; +#endif +} +#endif + +#ifndef KCMP_FILE +#define KCMP_FILE 0 +#endif + +#ifndef INPUT_PROP_POINTING_STICK +#define INPUT_PROP_POINTING_STICK 0x05 +#endif + +#ifndef INPUT_PROP_ACCELEROMETER +#define INPUT_PROP_ACCELEROMETER 0x06 +#endif + +#if !HAVE_DECL_KEY_SERIAL_T +typedef int32_t key_serial_t; +#endif + +#if !HAVE_DECL_KEYCTL +static inline long keyctl(int cmd, unsigned long arg2, unsigned long arg3, unsigned long arg4,unsigned long arg5) { +#if defined(__NR_keyctl) + return syscall(__NR_keyctl, cmd, arg2, arg3, arg4, arg5); +#else + errno = ENOSYS; + return -1; +#endif +} + +static inline key_serial_t add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) { +#if defined (__NR_add_key) + return syscall(__NR_add_key, type, description, payload, plen, ringid); +#else + errno = ENOSYS; + return -1; +#endif +} + +static inline key_serial_t request_key(const char *type, const char *description, const char * callout_info, key_serial_t destringid) { +#if defined (__NR_request_key) + return syscall(__NR_request_key, type, description, callout_info, destringid); +#else + errno = ENOSYS; + return -1; +#endif +} +#endif + +#ifndef KEYCTL_READ +#define KEYCTL_READ 11 +#endif + +#ifndef KEYCTL_SET_TIMEOUT +#define KEYCTL_SET_TIMEOUT 15 +#endif + +#ifndef KEY_SPEC_USER_KEYRING +#define KEY_SPEC_USER_KEYRING -4 +#endif + +#ifndef PR_CAP_AMBIENT +#define PR_CAP_AMBIENT 47 +#endif + +#ifndef PR_CAP_AMBIENT_IS_SET +#define PR_CAP_AMBIENT_IS_SET 1 +#endif + +#ifndef PR_CAP_AMBIENT_RAISE +#define PR_CAP_AMBIENT_RAISE 2 +#endif + +#ifndef PR_CAP_AMBIENT_CLEAR_ALL +#define PR_CAP_AMBIENT_CLEAR_ALL 4 +#endif + +/* The following two defines are actually available in the kernel headers for longer, but we define them here anyway, + * since that makes it easier to use them in conjunction with the glibc net/if.h header which conflicts with + * linux/if.h. */ +#ifndef IF_OPER_UNKNOWN +#define IF_OPER_UNKNOWN 0 +#endif + +#ifndef IF_OPER_UP +#define IF_OPER_UP 6 + +#ifndef HAVE_DECL_CHAR32_T +#define char32_t uint32_t +#endif + +#ifndef HAVE_DECL_CHAR16_T +#define char16_t uint16_t +#endif + +#endif diff --git a/src/libbasic/mkdir-label.c b/src/libbasic/mkdir-label.c new file mode 100644 index 0000000000..aa6878cdf0 --- /dev/null +++ b/src/libbasic/mkdir-label.c @@ -0,0 +1,38 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#include "label.h" +#include "mkdir.h" + +int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid) { + return mkdir_safe_internal(path, mode, uid, gid, mkdir_label); +} + +int mkdir_parents_label(const char *path, mode_t mode) { + return mkdir_parents_internal(NULL, path, mode, mkdir_label); +} + +int mkdir_p_label(const char *path, mode_t mode) { + return mkdir_p_internal(NULL, path, mode, mkdir_label); +} diff --git a/src/libbasic/mkdir.c b/src/libbasic/mkdir.c new file mode 100644 index 0000000000..6b1a98402c --- /dev/null +++ b/src/libbasic/mkdir.c @@ -0,0 +1,128 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "fs-util.h" +#include "macro.h" +#include "mkdir.h" +#include "path-util.h" +#include "stat-util.h" +#include "user-util.h" + +int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, mkdir_func_t _mkdir) { + struct stat st; + + if (_mkdir(path, mode) >= 0) + if (chmod_and_chown(path, mode, uid, gid) < 0) + return -errno; + + if (lstat(path, &st) < 0) + return -errno; + + if ((st.st_mode & 0007) > (mode & 0007) || + (st.st_mode & 0070) > (mode & 0070) || + (st.st_mode & 0700) > (mode & 0700) || + (uid != UID_INVALID && st.st_uid != uid) || + (gid != GID_INVALID && st.st_gid != gid) || + !S_ISDIR(st.st_mode)) + return -EEXIST; + + return 0; +} + +int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid) { + return mkdir_safe_internal(path, mode, uid, gid, mkdir); +} + +int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { + const char *p, *e; + int r; + + assert(path); + + if (prefix && !path_startswith(path, prefix)) + return -ENOTDIR; + + /* return immediately if directory exists */ + e = strrchr(path, '/'); + if (!e) + return -EINVAL; + + if (e == path) + return 0; + + p = strndupa(path, e - path); + r = is_dir(p, true); + if (r > 0) + return 0; + if (r == 0) + return -ENOTDIR; + + /* create every parent directory in the path, except the last component */ + p = path + strspn(path, "/"); + for (;;) { + char t[strlen(path) + 1]; + + e = p + strcspn(p, "/"); + p = e + strspn(e, "/"); + + /* Is this the last component? If so, then we're + * done */ + if (*p == 0) + return 0; + + memcpy(t, path, e - path); + t[e-path] = 0; + + if (prefix && path_startswith(prefix, t)) + continue; + + r = _mkdir(t, mode); + if (r < 0 && errno != EEXIST) + return -errno; + } +} + +int mkdir_parents(const char *path, mode_t mode) { + return mkdir_parents_internal(NULL, path, mode, mkdir); +} + +int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir) { + int r; + + /* Like mkdir -p */ + + r = mkdir_parents_internal(prefix, path, mode, _mkdir); + if (r < 0) + return r; + + r = _mkdir(path, mode); + if (r < 0 && (errno != EEXIST || is_dir(path, true) <= 0)) + return -errno; + + return 0; +} + +int mkdir_p(const char *path, mode_t mode) { + return mkdir_p_internal(NULL, path, mode, mkdir); +} diff --git a/src/libbasic/mkdir.h b/src/libbasic/mkdir.h new file mode 100644 index 0000000000..d564a3547f --- /dev/null +++ b/src/libbasic/mkdir.h @@ -0,0 +1,38 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid); +int mkdir_parents(const char *path, mode_t mode); +int mkdir_p(const char *path, mode_t mode); + +/* mandatory access control(MAC) versions */ +int mkdir_safe_label(const char *path, mode_t mode, uid_t uid, gid_t gid); +int mkdir_parents_label(const char *path, mode_t mode); +int mkdir_p_label(const char *path, mode_t mode); + +/* internally used */ +typedef int (*mkdir_func_t)(const char *pathname, mode_t mode); +int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, mkdir_func_t _mkdir); +int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir); +int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir); diff --git a/src/libbasic/mount-util.c b/src/libbasic/mount-util.c new file mode 100644 index 0000000000..33f2ee96d8 --- /dev/null +++ b/src/libbasic/mount-util.c @@ -0,0 +1,531 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "hashmap.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "set.h" +#include "stdio-util.h" +#include "string-util.h" + +static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id) { + char path[strlen("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)]; + _cleanup_free_ char *fdinfo = NULL; + _cleanup_close_ int subfd = -1; + char *p; + int r; + + if ((flags & AT_EMPTY_PATH) && isempty(filename)) + xsprintf(path, "/proc/self/fdinfo/%i", fd); + else { + subfd = openat(fd, filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH); + if (subfd < 0) + return -errno; + + xsprintf(path, "/proc/self/fdinfo/%i", subfd); + } + + r = read_full_file(path, &fdinfo, NULL); + if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */ + return -EOPNOTSUPP; + if (r < 0) + return -errno; + + p = startswith(fdinfo, "mnt_id:"); + if (!p) { + p = strstr(fdinfo, "\nmnt_id:"); + if (!p) /* The mnt_id field is a relatively new addition */ + return -EOPNOTSUPP; + + p += 8; + } + + p += strspn(p, WHITESPACE); + p[strcspn(p, WHITESPACE)] = 0; + + return safe_atoi(p, mnt_id); +} + + +int fd_is_mount_point(int fd, const char *filename, int flags) { + union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT; + int mount_id = -1, mount_id_parent = -1; + bool nosupp = false, check_st_dev = true; + struct stat a, b; + int r; + + assert(fd >= 0); + assert(filename); + + /* First we will try the name_to_handle_at() syscall, which + * tells us the mount id and an opaque file "handle". It is + * not supported everywhere though (kernel compile-time + * option, not all file systems are hooked up). If it works + * the mount id is usually good enough to tell us whether + * something is a mount point. + * + * If that didn't work we will try to read the mount id from + * /proc/self/fdinfo/. This is almost as good as + * name_to_handle_at(), however, does not return the + * opaque file handle. The opaque file handle is pretty useful + * to detect the root directory, which we should always + * consider a mount point. Hence we use this only as + * fallback. Exporting the mnt_id in fdinfo is a pretty recent + * kernel addition. + * + * 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 + * 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. */ + + r = name_to_handle_at(fd, filename, &h.handle, &mount_id, flags); + if (r < 0) { + if (errno == ENOSYS) + /* This kernel does not support name_to_handle_at() + * fall back to simpler logic. */ + goto fallback_fdinfo; + else if (errno == EOPNOTSUPP) + /* This kernel or file system does not support + * name_to_handle_at(), hence let's see if the + * upper fs supports it (in which case it is a + * mount point), otherwise fallback to the + * traditional stat() logic */ + nosupp = true; + else + return -errno; + } + + r = name_to_handle_at(fd, "", &h_parent.handle, &mount_id_parent, AT_EMPTY_PATH); + if (r < 0) { + if (errno == EOPNOTSUPP) { + if (nosupp) + /* Neither parent nor child do name_to_handle_at()? + We have no choice but to fall back. */ + goto fallback_fdinfo; + else + /* The parent can't do name_to_handle_at() but the + * directory we are interested in can? + * If so, it must be a mount point. */ + return 1; + } else + return -errno; + } + + /* The parent can do name_to_handle_at() but the + * directory we are interested in can't? If so, it + * must be a mount point. */ + if (nosupp) + return 1; + + /* If the file handle for the directory we are + * interested in and its parent are identical, we + * assume this is the root directory, which is a mount + * point. */ + + if (h.handle.handle_bytes == h_parent.handle.handle_bytes && + h.handle.handle_type == h_parent.handle.handle_type && + memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0) + return 1; + + return mount_id != mount_id_parent; + +fallback_fdinfo: + r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id); + if (r == -EOPNOTSUPP) + goto fallback_fstat; + if (r < 0) + return r; + + r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent); + if (r < 0) + return r; + + if (mount_id != mount_id_parent) + return 1; + + /* Hmm, so, the mount ids are the same. This leaves one + * special case though for the root file system. For that, + * let's see if the parent directory has the same inode as we + * are interested in. Hence, let's also do fstat() checks now, + * too, but avoid the st_dev comparisons, since they aren't + * that useful on unionfs mounts. */ + check_st_dev = false; + +fallback_fstat: + /* yay for fstatat() taking a different set of flags than the other + * _at() above */ + if (flags & AT_SYMLINK_FOLLOW) + flags &= ~AT_SYMLINK_FOLLOW; + else + flags |= AT_SYMLINK_NOFOLLOW; + if (fstatat(fd, filename, &a, flags) < 0) + return -errno; + + if (fstatat(fd, "", &b, AT_EMPTY_PATH) < 0) + return -errno; + + /* A directory with same device and inode as its parent? Must + * be the root directory */ + if (a.st_dev == b.st_dev && + a.st_ino == b.st_ino) + return 1; + + return check_st_dev && (a.st_dev != b.st_dev); +} + +/* flags can be AT_SYMLINK_FOLLOW or 0 */ +int path_is_mount_point(const char *t, int flags) { + _cleanup_close_ int fd = -1; + _cleanup_free_ char *canonical = NULL, *parent = NULL; + + assert(t); + + if (path_equal(t, "/")) + return 1; + + /* we need to resolve symlinks manually, we can't just rely on + * fd_is_mount_point() to do that for us; if we have a structure like + * /bin -> /usr/bin/ and /usr is a mount point, then the parent that we + * look at needs to be /usr, not /. */ + if (flags & AT_SYMLINK_FOLLOW) { + canonical = canonicalize_file_name(t); + if (!canonical) + return -errno; + + t = canonical; + } + + parent = dirname_malloc(t); + if (!parent) + return -ENOMEM; + + fd = openat(AT_FDCWD, parent, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_PATH); + if (fd < 0) + return -errno; + + return fd_is_mount_point(fd, basename(t), flags); +} + +int umount_recursive(const char *prefix, int flags) { + bool again; + int n = 0, r; + + /* Try to umount everything recursively below a + * directory. Also, take care of stacked mounts, and keep + * unmounting them until they are gone. */ + + do { + _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; + + again = false; + r = 0; + + proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); + if (!proc_self_mountinfo) + return -errno; + + for (;;) { + _cleanup_free_ char *path = NULL, *p = NULL; + int k; + + k = fscanf(proc_self_mountinfo, + "%*s " /* (1) mount id */ + "%*s " /* (2) parent id */ + "%*s " /* (3) major:minor */ + "%*s " /* (4) root */ + "%ms " /* (5) mount point */ + "%*s" /* (6) mount options */ + "%*[^-]" /* (7) optional fields */ + "- " /* (8) separator */ + "%*s " /* (9) file system type */ + "%*s" /* (10) mount source */ + "%*s" /* (11) mount options 2 */ + "%*[^\n]", /* some rubbish at the end */ + &path); + if (k != 1) { + if (k == EOF) + break; + + continue; + } + + r = cunescape(path, UNESCAPE_RELAX, &p); + if (r < 0) + return r; + + if (!path_startswith(p, prefix)) + continue; + + if (umount2(p, flags) < 0) { + r = -errno; + continue; + } + + again = true; + n++; + + break; + } + + } while (again); + + return r ? r : n; +} + +static int get_mount_flags(const char *path, unsigned long *flags) { + struct statvfs buf; + + if (statvfs(path, &buf) < 0) + return -errno; + *flags = buf.f_flag; + return 0; +} + +int bind_remount_recursive(const char *prefix, bool ro) { + _cleanup_set_free_free_ Set *done = NULL; + _cleanup_free_ char *cleaned = NULL; + int r; + + /* Recursively remount a directory (and all its submounts) + * read-only or read-write. If the directory is already + * mounted, we reuse the mount and simply mark it + * MS_BIND|MS_RDONLY (or remove the MS_RDONLY for read-write + * operation). If it isn't we first make it one. Afterwards we + * apply MS_BIND|MS_RDONLY (or remove MS_RDONLY) to all + * submounts we can access, too. When mounts are stacked on + * the same mount point we only care for each individual + * "top-level" mount on each point, as we cannot + * influence/access the underlying mounts anyway. We do not + * have any effect on future submounts that might get + * propagated, they migt be writable. This includes future + * submounts that have been triggered via autofs. */ + + cleaned = strdup(prefix); + if (!cleaned) + return -ENOMEM; + + path_kill_slashes(cleaned); + + done = set_new(&string_hash_ops); + if (!done) + return -ENOMEM; + + for (;;) { + _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; + _cleanup_set_free_free_ Set *todo = NULL; + bool top_autofs = false; + char *x; + unsigned long orig_flags; + + todo = set_new(&string_hash_ops); + if (!todo) + return -ENOMEM; + + proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); + if (!proc_self_mountinfo) + return -errno; + + for (;;) { + _cleanup_free_ char *path = NULL, *p = NULL, *type = NULL; + int k; + + k = fscanf(proc_self_mountinfo, + "%*s " /* (1) mount id */ + "%*s " /* (2) parent id */ + "%*s " /* (3) major:minor */ + "%*s " /* (4) root */ + "%ms " /* (5) mount point */ + "%*s" /* (6) mount options (superblock) */ + "%*[^-]" /* (7) optional fields */ + "- " /* (8) separator */ + "%ms " /* (9) file system type */ + "%*s" /* (10) mount source */ + "%*s" /* (11) mount options (bind mount) */ + "%*[^\n]", /* some rubbish at the end */ + &path, + &type); + if (k != 2) { + if (k == EOF) + break; + + continue; + } + + r = cunescape(path, UNESCAPE_RELAX, &p); + if (r < 0) + return r; + + /* Let's ignore autofs mounts. If they aren't + * triggered yet, we want to avoid triggering + * them, as we don't make any guarantees for + * future submounts anyway. If they are + * already triggered, then we will find + * another entry for this. */ + if (streq(type, "autofs")) { + top_autofs = top_autofs || path_equal(cleaned, p); + continue; + } + + if (path_startswith(p, cleaned) && + !set_contains(done, p)) { + + r = set_consume(todo, p); + p = NULL; + + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + /* If we have no submounts to process anymore and if + * the root is either already done, or an autofs, we + * are done */ + if (set_isempty(todo) && + (top_autofs || set_contains(done, cleaned))) + return 0; + + if (!set_contains(done, cleaned) && + !set_contains(todo, cleaned)) { + /* The prefix directory itself is not yet a + * mount, make it one. */ + if (mount(cleaned, cleaned, NULL, MS_BIND|MS_REC, NULL) < 0) + return -errno; + + orig_flags = 0; + (void) get_mount_flags(cleaned, &orig_flags); + orig_flags &= ~MS_RDONLY; + + if (mount(NULL, prefix, NULL, orig_flags|MS_BIND|MS_REMOUNT|(ro ? MS_RDONLY : 0), NULL) < 0) + return -errno; + + x = strdup(cleaned); + if (!x) + return -ENOMEM; + + r = set_consume(done, x); + if (r < 0) + return r; + } + + while ((x = set_steal_first(todo))) { + + r = set_consume(done, x); + if (r == -EEXIST || r == 0) + continue; + if (r < 0) + return r; + + /* Try to reuse the original flag set, but + * don't care for errors, in case of + * obstructed mounts */ + 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; + } + + } + } +} + +int mount_move_root(const char *path) { + assert(path); + + if (chdir(path) < 0) + return -errno; + + if (mount(path, "/", NULL, MS_MOVE, NULL) < 0) + return -errno; + + if (chroot(".") < 0) + return -errno; + + if (chdir("/") < 0) + return -errno; + + return 0; +} + +bool fstype_is_network(const char *fstype) { + static const char table[] = + "afs\0" + "cifs\0" + "smbfs\0" + "sshfs\0" + "ncpfs\0" + "ncp\0" + "nfs\0" + "nfs4\0" + "gfs\0" + "gfs2\0" + "glusterfs\0"; + + const char *x; + + x = startswith(fstype, "fuse."); + if (x) + fstype = x; + + return nulstr_contains(table, fstype); +} + +int repeat_unmount(const char *path, int flags) { + bool done = false; + + assert(path); + + /* If there are multiple mounts on a mount point, this + * removes them all */ + + for (;;) { + if (umount2(path, flags) < 0) { + + if (errno == EINVAL) + return done; + + return -errno; + } + + done = true; + } +} diff --git a/src/libbasic/mount-util.h b/src/libbasic/mount-util.h new file mode 100644 index 0000000000..bdb525d6b0 --- /dev/null +++ b/src/libbasic/mount-util.h @@ -0,0 +1,52 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "macro.h" +#include "missing.h" + +int fd_is_mount_point(int fd, const char *filename, int flags); +int path_is_mount_point(const char *path, int flags); + +int repeat_unmount(const char *path, int flags); + +int umount_recursive(const char *target, int flags); +int bind_remount_recursive(const char *prefix, bool ro); + +int mount_move_root(const char *path); + +DEFINE_TRIVIAL_CLEANUP_FUNC(FILE*, endmntent); +#define _cleanup_endmntent_ _cleanup_(endmntentp) + +bool fstype_is_network(const char *fstype); + +union file_handle_union { + struct file_handle handle; + char padding[sizeof(struct file_handle) + MAX_HANDLE_SZ]; +}; + +#define FILE_HANDLE_INIT { .handle.handle_bytes = MAX_HANDLE_SZ } diff --git a/src/libbasic/nss-util.h b/src/libbasic/nss-util.h new file mode 100644 index 0000000000..df565a3593 --- /dev/null +++ b/src/libbasic/nss-util.h @@ -0,0 +1,156 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include +#include + +#define NSS_SIGNALS_BLOCK SIGALRM,SIGVTALRM,SIGPIPE,SIGCHLD,SIGTSTP,SIGIO,SIGHUP,SIGUSR1,SIGUSR2,SIGPROF,SIGURG,SIGWINCH + +#define NSS_GETHOSTBYNAME_PROTOTYPES(module) \ +enum nss_status _nss_##module##_gethostbyname4_r( \ + const char *name, \ + struct gaih_addrtuple **pat, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop, \ + int32_t *ttlp) _public_; \ +enum nss_status _nss_##module##_gethostbyname3_r( \ + const char *name, \ + int af, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop, \ + int32_t *ttlp, \ + char **canonp) _public_; \ +enum nss_status _nss_##module##_gethostbyname2_r( \ + const char *name, \ + int af, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop) _public_; \ +enum nss_status _nss_##module##_gethostbyname_r( \ + const char *name, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop) _public_ + +#define NSS_GETHOSTBYADDR_PROTOTYPES(module) \ +enum nss_status _nss_##module##_gethostbyaddr2_r( \ + const void* addr, socklen_t len, \ + int af, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop, \ + int32_t *ttlp) _public_; \ +enum nss_status _nss_##module##_gethostbyaddr_r( \ + const void* addr, socklen_t len, \ + int af, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop) _public_ + +#define NSS_GETHOSTBYNAME_FALLBACKS(module) \ +enum nss_status _nss_##module##_gethostbyname2_r( \ + const char *name, \ + int af, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop) { \ + return _nss_##module##_gethostbyname3_r( \ + name, \ + af, \ + host, \ + buffer, buflen, \ + errnop, h_errnop, \ + NULL, \ + NULL); \ +} \ +enum nss_status _nss_##module##_gethostbyname_r( \ + const char *name, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop) { \ + enum nss_status ret = NSS_STATUS_NOTFOUND; \ + \ + if (_res.options & RES_USE_INET6) \ + ret = _nss_##module##_gethostbyname3_r( \ + name, \ + AF_INET6, \ + host, \ + buffer, buflen, \ + errnop, h_errnop, \ + NULL, \ + NULL); \ + if (ret == NSS_STATUS_NOTFOUND) \ + ret = _nss_##module##_gethostbyname3_r( \ + name, \ + AF_INET, \ + host, \ + buffer, buflen, \ + errnop, h_errnop, \ + NULL, \ + NULL); \ + return ret; \ +} \ +struct __useless_struct_to_allow_trailing_semicolon__ + +#define NSS_GETHOSTBYADDR_FALLBACKS(module) \ +enum nss_status _nss_##module##_gethostbyaddr_r( \ + const void* addr, socklen_t len, \ + int af, \ + struct hostent *host, \ + char *buffer, size_t buflen, \ + int *errnop, int *h_errnop) { \ + return _nss_##module##_gethostbyaddr2_r( \ + addr, len, \ + af, \ + host, \ + buffer, buflen, \ + errnop, h_errnop, \ + NULL); \ +} \ +struct __useless_struct_to_allow_trailing_semicolon__ + +#define NSS_GETPW_PROTOTYPES(module) \ +enum nss_status _nss_##module##_getpwnam_r( \ + const char *name, \ + struct passwd *pwd, \ + char *buffer, size_t buflen, \ + int *errnop) _public_; \ +enum nss_status _nss_mymachines_getpwuid_r( \ + uid_t uid, \ + struct passwd *pwd, \ + char *buffer, size_t buflen, \ + int *errnop) _public_ + +#define NSS_GETGR_PROTOTYPES(module) \ +enum nss_status _nss_##module##_getgrnam_r( \ + const char *name, \ + struct group *gr, \ + char *buffer, size_t buflen, \ + int *errnop) _public_; \ +enum nss_status _nss_##module##_getgrgid_r( \ + gid_t gid, \ + struct group *gr, \ + char *buffer, size_t buflen, \ + int *errnop) _public_ diff --git a/src/libbasic/ordered-set.c b/src/libbasic/ordered-set.c new file mode 100644 index 0000000000..2e0bdf6488 --- /dev/null +++ b/src/libbasic/ordered-set.c @@ -0,0 +1,64 @@ +/*** + 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 . +***/ + +#include "ordered-set.h" +#include "strv.h" + +int ordered_set_consume(OrderedSet *s, void *p) { + int r; + + r = ordered_set_put(s, p); + if (r <= 0) + free(p); + + return r; +} + +int ordered_set_put_strdup(OrderedSet *s, const char *p) { + char *c; + int r; + + assert(s); + assert(p); + + c = strdup(p); + if (!c) + return -ENOMEM; + + r = ordered_set_consume(s, c); + if (r == -EEXIST) + return 0; + + return r; +} + +int ordered_set_put_strdupv(OrderedSet *s, char **l) { + int n = 0, r; + char **i; + + STRV_FOREACH(i, l) { + r = ordered_set_put_strdup(s, *i); + if (r < 0) + return r; + + n += r; + } + + return n; +} diff --git a/src/libbasic/ordered-set.h b/src/libbasic/ordered-set.h new file mode 100644 index 0000000000..e1dfc86380 --- /dev/null +++ b/src/libbasic/ordered-set.h @@ -0,0 +1,74 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include "hashmap.h" + +typedef struct OrderedSet OrderedSet; + +static inline OrderedSet* ordered_set_new(const struct hash_ops *ops) { + return (OrderedSet*) ordered_hashmap_new(ops); +} + +static inline int ordered_set_ensure_allocated(OrderedSet **s, const struct hash_ops *ops) { + if (*s) + return 0; + + *s = ordered_set_new(ops); + if (!*s) + return -ENOMEM; + + return 0; +} + +static inline OrderedSet* ordered_set_free(OrderedSet *s) { + ordered_hashmap_free((OrderedHashmap*) s); + return NULL; +} + +static inline OrderedSet* ordered_set_free_free(OrderedSet *s) { + ordered_hashmap_free_free((OrderedHashmap*) s); + return NULL; +} + +static inline int ordered_set_put(OrderedSet *s, void *p) { + return ordered_hashmap_put((OrderedHashmap*) s, p, p); +} + +static inline bool ordered_set_isempty(OrderedSet *s) { + return ordered_hashmap_isempty((OrderedHashmap*) s); +} + +static inline bool ordered_set_iterate(OrderedSet *s, Iterator *i, void **value) { + return ordered_hashmap_iterate((OrderedHashmap*) s, i, value, NULL); +} + +int ordered_set_consume(OrderedSet *s, void *p); +int ordered_set_put_strdup(OrderedSet *s, const char *p); +int ordered_set_put_strdupv(OrderedSet *s, char **l); + +#define ORDERED_SET_FOREACH(e, s, i) \ + for ((i) = ITERATOR_FIRST; ordered_set_iterate((s), &(i), (void**)&(e)); ) + +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedSet*, ordered_set_free_free); + +#define _cleanup_ordered_set_free_ _cleanup_(ordered_set_freep) +#define _cleanup_ordered_set_free_free_ _cleanup_(ordered_set_free_freep) diff --git a/src/libbasic/parse-util.c b/src/libbasic/parse-util.c new file mode 100644 index 0000000000..a3cb81b040 --- /dev/null +++ b/src/libbasic/parse-util.c @@ -0,0 +1,534 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "extract-word.h" +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" + +int parse_boolean(const char *v) { + assert(v); + + if (streq(v, "1") || strcaseeq(v, "yes") || strcaseeq(v, "y") || strcaseeq(v, "true") || strcaseeq(v, "t") || strcaseeq(v, "on")) + return 1; + else if (streq(v, "0") || strcaseeq(v, "no") || strcaseeq(v, "n") || strcaseeq(v, "false") || strcaseeq(v, "f") || strcaseeq(v, "off")) + return 0; + + return -EINVAL; +} + +int parse_pid(const char *s, pid_t* ret_pid) { + unsigned long ul = 0; + pid_t pid; + int r; + + assert(s); + assert(ret_pid); + + r = safe_atolu(s, &ul); + if (r < 0) + return r; + + pid = (pid_t) ul; + + if ((unsigned long) pid != ul) + return -ERANGE; + + if (pid <= 0) + return -ERANGE; + + *ret_pid = pid; + return 0; +} + +int parse_mode(const char *s, mode_t *ret) { + char *x; + long l; + + assert(s); + assert(ret); + + s += strspn(s, WHITESPACE); + if (s[0] == '-') + return -ERANGE; + + errno = 0; + l = strtol(s, &x, 8); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if (l < 0 || l > 07777) + return -ERANGE; + + *ret = (mode_t) l; + return 0; +} + +int parse_ifindex(const char *s, int *ret) { + int ifi, r; + + r = safe_atoi(s, &ifi); + if (r < 0) + return r; + if (ifi <= 0) + return -EINVAL; + + *ret = ifi; + return 0; +} + +int parse_size(const char *t, uint64_t base, uint64_t *size) { + + /* Soo, sometimes we want to parse IEC binary suffixes, and + * sometimes SI decimal suffixes. This function can parse + * both. Which one is the right way depends on the + * context. Wikipedia suggests that SI is customary for + * hardware metrics and network speeds, while IEC is + * customary for most data sizes used by software and volatile + * (RAM) memory. Hence be careful which one you pick! + * + * In either case we use just K, M, G as suffix, and not Ki, + * Mi, Gi or so (as IEC would suggest). That's because that's + * frickin' ugly. But this means you really need to make sure + * to document which base you are parsing when you use this + * call. */ + + struct table { + const char *suffix; + unsigned long long factor; + }; + + static const struct table iec[] = { + { "E", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "P", 1024ULL*1024ULL*1024ULL*1024ULL*1024ULL }, + { "T", 1024ULL*1024ULL*1024ULL*1024ULL }, + { "G", 1024ULL*1024ULL*1024ULL }, + { "M", 1024ULL*1024ULL }, + { "K", 1024ULL }, + { "B", 1ULL }, + { "", 1ULL }, + }; + + static const struct table si[] = { + { "E", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL*1000ULL }, + { "P", 1000ULL*1000ULL*1000ULL*1000ULL*1000ULL }, + { "T", 1000ULL*1000ULL*1000ULL*1000ULL }, + { "G", 1000ULL*1000ULL*1000ULL }, + { "M", 1000ULL*1000ULL }, + { "K", 1000ULL }, + { "B", 1ULL }, + { "", 1ULL }, + }; + + const struct table *table; + const char *p; + unsigned long long r = 0; + unsigned n_entries, start_pos = 0; + + assert(t); + assert(base == 1000 || base == 1024); + assert(size); + + if (base == 1000) { + table = si; + n_entries = ELEMENTSOF(si); + } else { + table = iec; + n_entries = ELEMENTSOF(iec); + } + + p = t; + do { + unsigned long long l, tmp; + double frac = 0; + char *e; + unsigned i; + + p += strspn(p, WHITESPACE); + + errno = 0; + l = strtoull(p, &e, 10); + if (errno > 0) + return -errno; + if (e == p) + return -EINVAL; + if (*p == '-') + return -ERANGE; + + if (*e == '.') { + e++; + + /* strtoull() itself would accept space/+/- */ + if (*e >= '0' && *e <= '9') { + unsigned long long l2; + char *e2; + + l2 = strtoull(e, &e2, 10); + if (errno > 0) + return -errno; + + /* Ignore failure. E.g. 10.M is valid */ + frac = l2; + for (; e < e2; e++) + frac /= 10; + } + } + + e += strspn(e, WHITESPACE); + + for (i = start_pos; i < n_entries; i++) + if (startswith(e, table[i].suffix)) + break; + + if (i >= n_entries) + return -EINVAL; + + if (l + (frac > 0) > ULLONG_MAX / table[i].factor) + return -ERANGE; + + tmp = l * table[i].factor + (unsigned long long) (frac * table[i].factor); + if (tmp > ULLONG_MAX - r) + return -ERANGE; + + r += tmp; + if ((unsigned long long) (uint64_t) r != r) + return -ERANGE; + + p = e + strlen(table[i].suffix); + + start_pos = i + 1; + + } while (*p); + + *size = r; + + return 0; +} + +int parse_range(const char *t, unsigned *lower, unsigned *upper) { + _cleanup_free_ char *word = NULL; + unsigned l, u; + int r; + + assert(lower); + assert(upper); + + /* Extract the lower bound. */ + r = extract_first_word(&t, &word, "-", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + r = safe_atou(word, &l); + if (r < 0) + return r; + + /* Check for the upper bound and extract it if needed */ + if (!t) + /* Single number with no dashes. */ + u = l; + else if (!*t) + /* Trailing dash is an error. */ + return -EINVAL; + else { + r = safe_atou(t, &u); + if (r < 0) + return r; + } + + *lower = l; + *upper = u; + return 0; +} + +char *format_bytes(char *buf, size_t l, uint64_t t) { + unsigned i; + + /* This only does IEC units so far */ + + static const struct { + const char *suffix; + uint64_t factor; + } table[] = { + { "E", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, + { "P", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, + { "T", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, + { "G", UINT64_C(1024)*UINT64_C(1024)*UINT64_C(1024) }, + { "M", UINT64_C(1024)*UINT64_C(1024) }, + { "K", UINT64_C(1024) }, + }; + + if (t == (uint64_t) -1) + return NULL; + + for (i = 0; i < ELEMENTSOF(table); i++) { + + if (t >= table[i].factor) { + snprintf(buf, l, + "%" PRIu64 ".%" PRIu64 "%s", + t / table[i].factor, + ((t*UINT64_C(10)) / table[i].factor) % UINT64_C(10), + table[i].suffix); + + goto finish; + } + } + + snprintf(buf, l, "%" PRIu64 "B", t); + +finish: + buf[l-1] = 0; + return buf; + +} + +int safe_atou(const char *s, unsigned *ret_u) { + char *x = NULL; + unsigned long l; + + assert(s); + assert(ret_u); + + /* strtoul() is happy to parse negative values, and silently + * converts them to unsigned values without generating an + * error. We want a clean error, hence let's look for the "-" + * prefix on our own, and generate an error. But let's do so + * only after strtoul() validated that the string is clean + * otherwise, so that we return EINVAL preferably over + * ERANGE. */ + + s += strspn(s, WHITESPACE); + + errno = 0; + l = strtoul(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if (s[0] == '-') + return -ERANGE; + if ((unsigned long) (unsigned) l != l) + return -ERANGE; + + *ret_u = (unsigned) l; + return 0; +} + +int safe_atoi(const char *s, int *ret_i) { + char *x = NULL; + long l; + + assert(s); + assert(ret_i); + + errno = 0; + l = strtol(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if ((long) (int) l != l) + return -ERANGE; + + *ret_i = (int) l; + return 0; +} + +int safe_atollu(const char *s, long long unsigned *ret_llu) { + char *x = NULL; + unsigned long long l; + + assert(s); + assert(ret_llu); + + s += strspn(s, WHITESPACE); + + errno = 0; + l = strtoull(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if (*s == '-') + return -ERANGE; + + *ret_llu = l; + return 0; +} + +int safe_atolli(const char *s, long long int *ret_lli) { + char *x = NULL; + long long l; + + assert(s); + assert(ret_lli); + + errno = 0; + l = strtoll(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + + *ret_lli = l; + return 0; +} + +int safe_atou8(const char *s, uint8_t *ret) { + char *x = NULL; + unsigned long l; + + assert(s); + assert(ret); + + s += strspn(s, WHITESPACE); + + errno = 0; + l = strtoul(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if (s[0] == '-') + return -ERANGE; + if ((unsigned long) (uint8_t) l != l) + return -ERANGE; + + *ret = (uint8_t) l; + return 0; +} + +int safe_atou16(const char *s, uint16_t *ret) { + char *x = NULL; + unsigned long l; + + assert(s); + assert(ret); + + s += strspn(s, WHITESPACE); + + errno = 0; + l = strtoul(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if (s[0] == '-') + return -ERANGE; + if ((unsigned long) (uint16_t) l != l) + return -ERANGE; + + *ret = (uint16_t) l; + return 0; +} + +int safe_atoi16(const char *s, int16_t *ret) { + char *x = NULL; + long l; + + assert(s); + assert(ret); + + errno = 0; + l = strtol(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if ((long) (int16_t) l != l) + return -ERANGE; + + *ret = (int16_t) l; + return 0; +} + +int safe_atod(const char *s, double *ret_d) { + char *x = NULL; + double d = 0; + locale_t loc; + + assert(s); + assert(ret_d); + + loc = newlocale(LC_NUMERIC_MASK, "C", (locale_t) 0); + if (loc == (locale_t) 0) + return -errno; + + errno = 0; + d = strtod_l(s, &x, loc); + if (errno > 0) { + freelocale(loc); + return -errno; + } + if (!x || x == s || *x) { + freelocale(loc); + return -EINVAL; + } + + freelocale(loc); + *ret_d = (double) d; + return 0; +} + +int parse_fractional_part_u(const char **p, size_t digits, unsigned *res) { + size_t i; + unsigned val = 0; + const char *s; + + s = *p; + + /* accept any number of digits, strtoull is limted to 19 */ + for(i=0; i < digits; i++,s++) { + if (*s < '0' || *s > '9') { + if (i == 0) + return -EINVAL; + + /* too few digits, pad with 0 */ + for (; i < digits; i++) + val *= 10; + + break; + } + + val *= 10; + val += *s - '0'; + } + + /* maybe round up */ + if (*s >= '5' && *s <= '9') + val++; + + s += strspn(s, DIGITS); + + *p = s; + *res = val; + + return 0; +} diff --git a/src/libbasic/parse-util.h b/src/libbasic/parse-util.h new file mode 100644 index 0000000000..d8dc26a36e --- /dev/null +++ b/src/libbasic/parse-util.h @@ -0,0 +1,95 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "macro.h" + +#define MODE_INVALID ((mode_t) -1) + +int parse_boolean(const char *v) _pure_; +int parse_pid(const char *s, pid_t* ret_pid); +int parse_mode(const char *s, mode_t *ret); +int parse_ifindex(const char *s, int *ret); + +int parse_size(const char *t, uint64_t base, uint64_t *size); +int parse_range(const char *t, unsigned *lower, unsigned *upper); + +#define FORMAT_BYTES_MAX 8 +char *format_bytes(char *buf, size_t l, uint64_t t); + +int safe_atou(const char *s, unsigned *ret_u); +int safe_atoi(const char *s, int *ret_i); +int safe_atollu(const char *s, unsigned long long *ret_u); +int safe_atolli(const char *s, long long int *ret_i); + +int safe_atou8(const char *s, uint8_t *ret); + +int safe_atou16(const char *s, uint16_t *ret); +int safe_atoi16(const char *s, int16_t *ret); + +static inline int safe_atou32(const char *s, uint32_t *ret_u) { + assert_cc(sizeof(uint32_t) == sizeof(unsigned)); + return safe_atou(s, (unsigned*) ret_u); +} + +static inline int safe_atoi32(const char *s, int32_t *ret_i) { + assert_cc(sizeof(int32_t) == sizeof(int)); + return safe_atoi(s, (int*) ret_i); +} + +static inline int safe_atou64(const char *s, uint64_t *ret_u) { + assert_cc(sizeof(uint64_t) == sizeof(unsigned long long)); + return safe_atollu(s, (unsigned long long*) ret_u); +} + +static inline int safe_atoi64(const char *s, int64_t *ret_i) { + assert_cc(sizeof(int64_t) == sizeof(long long int)); + return safe_atolli(s, (long long int*) ret_i); +} + +#if LONG_MAX == INT_MAX +static inline int safe_atolu(const char *s, unsigned long *ret_u) { + assert_cc(sizeof(unsigned long) == sizeof(unsigned)); + return safe_atou(s, (unsigned*) ret_u); +} +static inline int safe_atoli(const char *s, long int *ret_u) { + assert_cc(sizeof(long int) == sizeof(int)); + return safe_atoi(s, (int*) ret_u); +} +#else +static inline int safe_atolu(const char *s, unsigned long *ret_u) { + assert_cc(sizeof(unsigned long) == sizeof(unsigned long long)); + return safe_atollu(s, (unsigned long long*) ret_u); +} +static inline int safe_atoli(const char *s, long int *ret_u) { + assert_cc(sizeof(long int) == sizeof(long long int)); + return safe_atolli(s, (long long int*) ret_u); +} +#endif + +int safe_atod(const char *s, double *ret_d); + +int parse_fractional_part_u(const char **s, size_t digits, unsigned *res); diff --git a/src/libbasic/path-util.c b/src/libbasic/path-util.c new file mode 100644 index 0000000000..822c09bfba --- /dev/null +++ b/src/libbasic/path-util.c @@ -0,0 +1,797 @@ +/*** + This file is part of systemd. + + Copyright 2010-2012 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +/* When we include libgen.h because we need dirname() we immediately + * undefine basename() since libgen.h defines it as a macro to the + * POSIX version which is really broken. We prefer GNU basename(). */ +#include +#undef basename + +#include "alloc-util.h" +#include "extract-word.h" +#include "fs-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "path-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" + +bool path_is_absolute(const char *p) { + return p[0] == '/'; +} + +bool is_path(const char *p) { + return !!strchr(p, '/'); +} + +int path_split_and_make_absolute(const char *p, char ***ret) { + char **l; + int r; + + assert(p); + assert(ret); + + l = strv_split(p, ":"); + if (!l) + return -ENOMEM; + + r = path_strv_make_absolute_cwd(l); + if (r < 0) { + strv_free(l); + return r; + } + + *ret = l; + return r; +} + +char *path_make_absolute(const char *p, const char *prefix) { + assert(p); + + /* Makes every item in the list an absolute path by prepending + * the prefix, if specified and necessary */ + + if (path_is_absolute(p) || !prefix) + return strdup(p); + + return strjoin(prefix, "/", p, NULL); +} + +int path_make_absolute_cwd(const char *p, char **ret) { + char *c; + + assert(p); + assert(ret); + + /* Similar to path_make_absolute(), but prefixes with the + * current working directory. */ + + if (path_is_absolute(p)) + c = strdup(p); + else { + _cleanup_free_ char *cwd = NULL; + + cwd = get_current_dir_name(); + if (!cwd) + return negative_errno(); + + c = strjoin(cwd, "/", p, NULL); + } + if (!c) + return -ENOMEM; + + *ret = c; + return 0; +} + +int path_make_relative(const char *from_dir, const char *to_path, char **_r) { + char *r, *p; + unsigned n_parents; + + assert(from_dir); + assert(to_path); + assert(_r); + + /* Strips the common part, and adds ".." elements as necessary. */ + + if (!path_is_absolute(from_dir)) + return -EINVAL; + + if (!path_is_absolute(to_path)) + return -EINVAL; + + /* Skip the common part. */ + for (;;) { + size_t a; + size_t b; + + from_dir += strspn(from_dir, "/"); + to_path += strspn(to_path, "/"); + + if (!*from_dir) { + if (!*to_path) + /* from_dir equals to_path. */ + r = strdup("."); + else + /* from_dir is a parent directory of to_path. */ + r = strdup(to_path); + + if (!r) + return -ENOMEM; + + path_kill_slashes(r); + + *_r = r; + return 0; + } + + if (!*to_path) + break; + + a = strcspn(from_dir, "/"); + b = strcspn(to_path, "/"); + + if (a != b) + break; + + if (memcmp(from_dir, to_path, a) != 0) + break; + + from_dir += a; + to_path += b; + } + + /* If we're here, then "from_dir" has one or more elements that need to + * be replaced with "..". */ + + /* Count the number of necessary ".." elements. */ + for (n_parents = 0;;) { + from_dir += strspn(from_dir, "/"); + + if (!*from_dir) + break; + + from_dir += strcspn(from_dir, "/"); + n_parents++; + } + + r = malloc(n_parents * 3 + strlen(to_path) + 1); + if (!r) + return -ENOMEM; + + for (p = r; n_parents > 0; n_parents--, p += 3) + memcpy(p, "../", 3); + + strcpy(p, to_path); + path_kill_slashes(r); + + *_r = r; + return 0; +} + +int path_strv_make_absolute_cwd(char **l) { + char **s; + int r; + + /* Goes through every item in the string list and makes it + * absolute. This works in place and won't rollback any + * changes on failure. */ + + STRV_FOREACH(s, l) { + char *t; + + r = path_make_absolute_cwd(*s, &t); + if (r < 0) + return r; + + free(*s); + *s = t; + } + + return 0; +} + +char **path_strv_resolve(char **l, const char *prefix) { + char **s; + unsigned k = 0; + bool enomem = false; + + if (strv_isempty(l)) + return l; + + /* Goes through every item in the string list and canonicalize + * the path. This works in place and won't rollback any + * changes on failure. */ + + STRV_FOREACH(s, l) { + char *t, *u; + _cleanup_free_ char *orig = NULL; + + if (!path_is_absolute(*s)) { + free(*s); + continue; + } + + if (prefix) { + orig = *s; + t = strappend(prefix, orig); + if (!t) { + enomem = true; + continue; + } + } else + t = *s; + + errno = 0; + u = canonicalize_file_name(t); + if (!u) { + if (errno == ENOENT) { + if (prefix) { + u = orig; + orig = NULL; + free(t); + } else + u = t; + } else { + free(t); + if (errno == ENOMEM || errno == 0) + enomem = true; + + continue; + } + } else if (prefix) { + char *x; + + free(t); + x = path_startswith(u, prefix); + if (x) { + /* restore the slash if it was lost */ + if (!startswith(x, "/")) + *(--x) = '/'; + + t = strdup(x); + free(u); + if (!t) { + enomem = true; + continue; + } + u = t; + } else { + /* canonicalized path goes outside of + * prefix, keep the original path instead */ + free(u); + u = orig; + orig = NULL; + } + } else + free(t); + + l[k++] = u; + } + + l[k] = NULL; + + if (enomem) + return NULL; + + return l; +} + +char **path_strv_resolve_uniq(char **l, const char *prefix) { + + if (strv_isempty(l)) + return l; + + if (!path_strv_resolve(l, prefix)) + return NULL; + + return strv_uniq(l); +} + +char *path_kill_slashes(char *path) { + char *f, *t; + bool slash = false; + + /* Removes redundant inner and trailing slashes. Modifies the + * passed string in-place. + * + * ///foo///bar/ becomes /foo/bar + */ + + for (f = path, t = path; *f; f++) { + + if (*f == '/') { + slash = true; + continue; + } + + if (slash) { + slash = false; + *(t++) = '/'; + } + + *(t++) = *f; + } + + /* Special rule, if we are talking of the root directory, a + trailing slash is good */ + + if (t == path && slash) + *(t++) = '/'; + + *t = 0; + return path; +} + +char* path_startswith(const char *path, const char *prefix) { + assert(path); + assert(prefix); + + if ((path[0] == '/') != (prefix[0] == '/')) + return NULL; + + for (;;) { + size_t a, b; + + path += strspn(path, "/"); + prefix += strspn(prefix, "/"); + + if (*prefix == 0) + return (char*) path; + + if (*path == 0) + return NULL; + + a = strcspn(path, "/"); + b = strcspn(prefix, "/"); + + if (a != b) + return NULL; + + if (memcmp(path, prefix, a) != 0) + return NULL; + + path += a; + prefix += b; + } +} + +int path_compare(const char *a, const char *b) { + int d; + + assert(a); + assert(b); + + /* A relative path and an abolute path must not compare as equal. + * Which one is sorted before the other does not really matter. + * Here a relative path is ordered before an absolute path. */ + d = (a[0] == '/') - (b[0] == '/'); + if (d != 0) + return d; + + for (;;) { + size_t j, k; + + a += strspn(a, "/"); + b += strspn(b, "/"); + + if (*a == 0 && *b == 0) + return 0; + + /* Order prefixes first: "/foo" before "/foo/bar" */ + if (*a == 0) + return -1; + if (*b == 0) + return 1; + + j = strcspn(a, "/"); + k = strcspn(b, "/"); + + /* Alphabetical sort: "/foo/aaa" before "/foo/b" */ + d = memcmp(a, b, MIN(j, k)); + if (d != 0) + return (d > 0) - (d < 0); /* sign of d */ + + /* Sort "/foo/a" before "/foo/aaa" */ + d = (j > k) - (j < k); /* sign of (j - k) */ + if (d != 0) + return d; + + a += j; + b += k; + } +} + +bool path_equal(const char *a, const char *b) { + return path_compare(a, b) == 0; +} + +bool path_equal_or_files_same(const char *a, const char *b) { + return path_equal(a, b) || files_same(a, b) > 0; +} + +char* path_join(const char *root, const char *path, const char *rest) { + assert(path); + + if (!isempty(root)) + return strjoin(root, endswith(root, "/") ? "" : "/", + path[0] == '/' ? path+1 : path, + rest ? (endswith(path, "/") ? "" : "/") : NULL, + rest && rest[0] == '/' ? rest+1 : rest, + NULL); + else + return strjoin(path, + rest ? (endswith(path, "/") ? "" : "/") : NULL, + rest && rest[0] == '/' ? rest+1 : rest, + NULL); +} + +int find_binary(const char *name, char **ret) { + int last_error, r; + const char *p; + + assert(name); + + if (is_path(name)) { + if (access(name, X_OK) < 0) + return -errno; + + if (ret) { + r = path_make_absolute_cwd(name, ret); + if (r < 0) + return r; + } + + return 0; + } + + /** + * Plain getenv, not secure_getenv, because we want + * to actually allow the user to pick the binary. + */ + p = getenv("PATH"); + if (!p) + p = DEFAULT_PATH; + + last_error = -ENOENT; + + for (;;) { + _cleanup_free_ char *j = NULL, *element = NULL; + + r = extract_first_word(&p, &element, ":", EXTRACT_RELAX|EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + break; + + if (!path_is_absolute(element)) + continue; + + j = strjoin(element, "/", name, NULL); + if (!j) + return -ENOMEM; + + if (access(j, X_OK) >= 0) { + /* Found it! */ + + if (ret) { + *ret = path_kill_slashes(j); + j = NULL; + } + + return 0; + } + + last_error = -errno; + } + + return last_error; +} + +bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool update) { + bool changed = false; + const char* const* i; + + assert(timestamp); + + if (paths == NULL) + return false; + + STRV_FOREACH(i, paths) { + struct stat stats; + usec_t u; + + if (stat(*i, &stats) < 0) + continue; + + u = timespec_load(&stats.st_mtim); + + /* first check */ + if (*timestamp >= u) + continue; + + log_debug("timestamp of '%s' changed", *i); + + /* update timestamp */ + if (update) { + *timestamp = u; + changed = true; + } else + return true; + } + + return changed; +} + +static int binary_is_good(const char *binary) { + _cleanup_free_ char *p = NULL, *d = NULL; + int r; + + r = find_binary(binary, &p); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + /* An fsck that is linked to /bin/true is a non-existent + * fsck */ + + r = readlink_malloc(p, &d); + if (r == -EINVAL) /* not a symlink */ + return 1; + if (r < 0) + return r; + + return !path_equal(d, "true") && + !path_equal(d, "/bin/true") && + !path_equal(d, "/usr/bin/true") && + !path_equal(d, "/dev/null"); +} + +int fsck_exists(const char *fstype) { + const char *checker; + + assert(fstype); + + if (streq(fstype, "auto")) + return -EINVAL; + + checker = strjoina("fsck.", fstype); + return binary_is_good(checker); +} + +int mkfs_exists(const char *fstype) { + const char *mkfs; + + assert(fstype); + + if (streq(fstype, "auto")) + return -EINVAL; + + mkfs = strjoina("mkfs.", fstype); + return binary_is_good(mkfs); +} + +char *prefix_root(const char *root, const char *path) { + char *n, *p; + size_t l; + + /* If root is passed, prefixes path with it. Otherwise returns + * it as is. */ + + assert(path); + + /* First, drop duplicate prefixing slashes from the path */ + while (path[0] == '/' && path[1] == '/') + path++; + + if (isempty(root) || path_equal(root, "/")) + return strdup(path); + + l = strlen(root) + 1 + strlen(path) + 1; + + n = new(char, l); + if (!n) + return NULL; + + p = stpcpy(n, root); + + while (p > n && p[-1] == '/') + p--; + + if (path[0] != '/') + *(p++) = '/'; + + strcpy(p, path); + return n; +} + +int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg) { + char *p; + int r; + + /* + * This function is intended to be used in command line + * parsers, to handle paths that are passed in. It makes the + * path absolute, and reduces it to NULL if omitted or + * root (the latter optionally). + * + * NOTE THAT THIS WILL FREE THE PREVIOUS ARGUMENT POINTER ON + * SUCCESS! Hence, do not pass in uninitialized pointers. + */ + + if (isempty(path)) { + *arg = mfree(*arg); + return 0; + } + + r = path_make_absolute_cwd(path, &p); + if (r < 0) + return log_error_errno(r, "Failed to parse path \"%s\" and make it absolute: %m", path); + + path_kill_slashes(p); + if (suppress_root && path_equal(p, "/")) + p = mfree(p); + + free(*arg); + *arg = p; + return 0; +} + +char* dirname_malloc(const char *path) { + char *d, *dir, *dir2; + + assert(path); + + d = strdup(path); + if (!d) + return NULL; + + dir = dirname(d); + assert(dir); + + if (dir == d) + return d; + + dir2 = strdup(dir); + free(d); + + return dir2; +} + +bool filename_is_valid(const char *p) { + const char *e; + + if (isempty(p)) + return false; + + if (streq(p, ".")) + return false; + + if (streq(p, "..")) + return false; + + e = strchrnul(p, '/'); + if (*e != 0) + return false; + + if (e - p > FILENAME_MAX) + return false; + + return true; +} + +bool path_is_safe(const char *p) { + + if (isempty(p)) + return false; + + if (streq(p, "..") || startswith(p, "../") || endswith(p, "/..") || strstr(p, "/../")) + return false; + + if (strlen(p)+1 > PATH_MAX) + return false; + + /* The following two checks are not really dangerous, but hey, they still are confusing */ + if (streq(p, ".") || startswith(p, "./") || endswith(p, "/.") || strstr(p, "/./")) + return false; + + if (strstr(p, "//")) + return false; + + return true; +} + +char *file_in_same_dir(const char *path, const char *filename) { + char *e, *ret; + size_t k; + + assert(path); + assert(filename); + + /* This removes the last component of path and appends + * filename, unless the latter is absolute anyway or the + * former isn't */ + + if (path_is_absolute(filename)) + return strdup(filename); + + e = strrchr(path, '/'); + if (!e) + return strdup(filename); + + k = strlen(filename); + ret = new(char, (e + 1 - path) + k + 1); + if (!ret) + return NULL; + + memcpy(mempcpy(ret, path, e + 1 - path), filename, k + 1); + return ret; +} + +bool hidden_file_allow_backup(const char *filename) { + assert(filename); + + return + filename[0] == '.' || + streq(filename, "lost+found") || + streq(filename, "aquota.user") || + streq(filename, "aquota.group") || + endswith(filename, ".rpmnew") || + endswith(filename, ".rpmsave") || + endswith(filename, ".rpmorig") || + endswith(filename, ".dpkg-old") || + endswith(filename, ".dpkg-new") || + endswith(filename, ".dpkg-tmp") || + endswith(filename, ".dpkg-dist") || + endswith(filename, ".dpkg-bak") || + endswith(filename, ".dpkg-backup") || + endswith(filename, ".dpkg-remove") || + endswith(filename, ".swp"); +} + +bool hidden_file(const char *filename) { + assert(filename); + + if (endswith(filename, "~")) + return true; + + return hidden_file_allow_backup(filename); +} + +bool is_device_path(const char *path) { + + /* Returns true on paths that refer to a device, either in + * sysfs or in /dev */ + + return + path_startswith(path, "/dev/") || + path_startswith(path, "/sys/"); +} diff --git a/src/libbasic/path-util.h b/src/libbasic/path-util.h new file mode 100644 index 0000000000..2c2f87a9f2 --- /dev/null +++ b/src/libbasic/path-util.h @@ -0,0 +1,111 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2012 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 . +***/ + +#include +#include +#include + +#include "macro.h" +#include "time-util.h" + +#define DEFAULT_PATH_NORMAL "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin" +#define DEFAULT_PATH_SPLIT_USR DEFAULT_PATH_NORMAL ":/sbin:/bin" + +#ifdef HAVE_SPLIT_USR +# define DEFAULT_PATH DEFAULT_PATH_SPLIT_USR +#else +# define DEFAULT_PATH DEFAULT_PATH_NORMAL +#endif + +bool is_path(const char *p) _pure_; +int path_split_and_make_absolute(const char *p, char ***ret); +bool path_is_absolute(const char *p) _pure_; +char* path_make_absolute(const char *p, const char *prefix); +int path_make_absolute_cwd(const char *p, char **ret); +int path_make_relative(const char *from_dir, const char *to_path, char **_r); +char* path_kill_slashes(char *path); +char* path_startswith(const char *path, const char *prefix) _pure_; +int path_compare(const char *a, const char *b) _pure_; +bool path_equal(const char *a, const char *b) _pure_; +bool path_equal_or_files_same(const char *a, const char *b); +char* path_join(const char *root, const char *path, const char *rest); + +int path_strv_make_absolute_cwd(char **l); +char** path_strv_resolve(char **l, const char *prefix); +char** path_strv_resolve_uniq(char **l, const char *prefix); + +int find_binary(const char *name, char **filename); + +bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update); + +int fsck_exists(const char *fstype); +int mkfs_exists(const char *fstype); + +/* Iterates through the path prefixes of the specified path, going up + * the tree, to root. Also returns "" (and not "/"!) for the root + * directory. Excludes the specified directory itself */ +#define PATH_FOREACH_PREFIX(prefix, path) \ + for (char *_slash = ({ path_kill_slashes(strcpy(prefix, path)); streq(prefix, "/") ? NULL : strrchr(prefix, '/'); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/')) + +/* Same as PATH_FOREACH_PREFIX but also includes the specified path itself */ +#define PATH_FOREACH_PREFIX_MORE(prefix, path) \ + for (char *_slash = ({ path_kill_slashes(strcpy(prefix, path)); if (streq(prefix, "/")) prefix[0] = 0; strrchr(prefix, 0); }); _slash && ((*_slash = 0), true); _slash = strrchr((prefix), '/')) + +char *prefix_root(const char *root, const char *path); + +/* Similar to prefix_root(), but returns an alloca() buffer, or + * possibly a const pointer into the path parameter */ +#define prefix_roota(root, path) \ + ({ \ + const char* _path = (path), *_root = (root), *_ret; \ + char *_p, *_n; \ + size_t _l; \ + while (_path[0] == '/' && _path[1] == '/') \ + _path ++; \ + if (isempty(_root) || path_equal(_root, "/")) \ + _ret = _path; \ + else { \ + _l = strlen(_root) + 1 + strlen(_path) + 1; \ + _n = alloca(_l); \ + _p = stpcpy(_n, _root); \ + while (_p > _n && _p[-1] == '/') \ + _p--; \ + if (_path[0] != '/') \ + *(_p++) = '/'; \ + strcpy(_p, _path); \ + _ret = _n; \ + } \ + _ret; \ + }) + +int parse_path_argument_and_warn(const char *path, bool suppress_root, char **arg); + +char* dirname_malloc(const char *path); + +bool filename_is_valid(const char *p) _pure_; +bool path_is_safe(const char *p) _pure_; + +char *file_in_same_dir(const char *path, const char *filename); + +bool hidden_file_allow_backup(const char *filename); +bool hidden_file(const char *filename) _pure_; + +bool is_device_path(const char *path); diff --git a/src/libbasic/prioq.c b/src/libbasic/prioq.c new file mode 100644 index 0000000000..d2ec516d29 --- /dev/null +++ b/src/libbasic/prioq.c @@ -0,0 +1,320 @@ +/*** + 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 . +***/ + +/* + * Priority Queue + * The prioq object implements a priority queue. That is, it orders objects by + * their priority and allows O(1) access to the object with the highest + * priority. Insertion and removal are Θ(log n). Optionally, the caller can + * provide a pointer to an index which will be kept up-to-date by the prioq. + * + * The underlying algorithm used in this implementation is a Heap. + */ + +#include +#include + +#include "alloc-util.h" +#include "hashmap.h" +#include "prioq.h" + +struct prioq_item { + void *data; + unsigned *idx; +}; + +struct Prioq { + compare_func_t compare_func; + unsigned n_items, n_allocated; + + struct prioq_item *items; +}; + +Prioq *prioq_new(compare_func_t compare_func) { + Prioq *q; + + q = new0(Prioq, 1); + if (!q) + return q; + + q->compare_func = compare_func; + return q; +} + +Prioq* prioq_free(Prioq *q) { + if (!q) + return NULL; + + free(q->items); + free(q); + + return NULL; +} + +int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func) { + assert(q); + + if (*q) + return 0; + + *q = prioq_new(compare_func); + if (!*q) + return -ENOMEM; + + return 0; +} + +static void swap(Prioq *q, unsigned j, unsigned k) { + void *saved_data; + unsigned *saved_idx; + + assert(q); + assert(j < q->n_items); + assert(k < q->n_items); + + assert(!q->items[j].idx || *(q->items[j].idx) == j); + assert(!q->items[k].idx || *(q->items[k].idx) == k); + + saved_data = q->items[j].data; + saved_idx = q->items[j].idx; + q->items[j].data = q->items[k].data; + q->items[j].idx = q->items[k].idx; + q->items[k].data = saved_data; + q->items[k].idx = saved_idx; + + if (q->items[j].idx) + *q->items[j].idx = j; + + if (q->items[k].idx) + *q->items[k].idx = k; +} + +static unsigned shuffle_up(Prioq *q, unsigned idx) { + assert(q); + + while (idx > 0) { + unsigned k; + + k = (idx-1)/2; + + if (q->compare_func(q->items[k].data, q->items[idx].data) <= 0) + break; + + swap(q, idx, k); + idx = k; + } + + return idx; +} + +static unsigned shuffle_down(Prioq *q, unsigned idx) { + assert(q); + + for (;;) { + unsigned j, k, s; + + k = (idx+1)*2; /* right child */ + j = k-1; /* left child */ + + if (j >= q->n_items) + break; + + if (q->compare_func(q->items[j].data, q->items[idx].data) < 0) + + /* So our left child is smaller than we are, let's + * remember this fact */ + s = j; + else + s = idx; + + if (k < q->n_items && + q->compare_func(q->items[k].data, q->items[s].data) < 0) + + /* So our right child is smaller than we are, let's + * remember this fact */ + s = k; + + /* s now points to the smallest of the three items */ + + if (s == idx) + /* No swap necessary, we're done */ + break; + + swap(q, idx, s); + idx = s; + } + + return idx; +} + +int prioq_put(Prioq *q, void *data, unsigned *idx) { + struct prioq_item *i; + unsigned k; + + assert(q); + + if (q->n_items >= q->n_allocated) { + unsigned n; + struct prioq_item *j; + + n = MAX((q->n_items+1) * 2, 16u); + j = realloc(q->items, sizeof(struct prioq_item) * n); + if (!j) + return -ENOMEM; + + q->items = j; + q->n_allocated = n; + } + + k = q->n_items++; + i = q->items + k; + i->data = data; + i->idx = idx; + + if (idx) + *idx = k; + + shuffle_up(q, k); + + return 0; +} + +static void remove_item(Prioq *q, struct prioq_item *i) { + struct prioq_item *l; + + assert(q); + assert(i); + + l = q->items + q->n_items - 1; + + if (i == l) + /* Last entry, let's just remove it */ + q->n_items--; + else { + unsigned k; + + /* Not last entry, let's replace the last entry with + * this one, and reshuffle */ + + k = i - q->items; + + i->data = l->data; + i->idx = l->idx; + if (i->idx) + *i->idx = k; + q->n_items--; + + k = shuffle_down(q, k); + shuffle_up(q, k); + } +} + +_pure_ static struct prioq_item* find_item(Prioq *q, void *data, unsigned *idx) { + struct prioq_item *i; + + assert(q); + + if (idx) { + if (*idx == PRIOQ_IDX_NULL || + *idx > q->n_items) + return NULL; + + i = q->items + *idx; + if (i->data != data) + return NULL; + + return i; + } else { + for (i = q->items; i < q->items + q->n_items; i++) + if (i->data == data) + return i; + return NULL; + } +} + +int prioq_remove(Prioq *q, void *data, unsigned *idx) { + struct prioq_item *i; + + if (!q) + return 0; + + i = find_item(q, data, idx); + if (!i) + return 0; + + remove_item(q, i); + return 1; +} + +int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) { + struct prioq_item *i; + unsigned k; + + assert(q); + + i = find_item(q, data, idx); + if (!i) + return 0; + + k = i - q->items; + k = shuffle_down(q, k); + shuffle_up(q, k); + return 1; +} + +void *prioq_peek(Prioq *q) { + + if (!q) + return NULL; + + if (q->n_items <= 0) + return NULL; + + return q->items[0].data; +} + +void *prioq_pop(Prioq *q) { + void *data; + + if (!q) + return NULL; + + if (q->n_items <= 0) + return NULL; + + data = q->items[0].data; + remove_item(q, q->items); + return data; +} + +unsigned prioq_size(Prioq *q) { + + if (!q) + return 0; + + return q->n_items; +} + +bool prioq_isempty(Prioq *q) { + + if (!q) + return true; + + return q->n_items <= 0; +} diff --git a/src/libbasic/prioq.h b/src/libbasic/prioq.h new file mode 100644 index 0000000000..113c73d040 --- /dev/null +++ b/src/libbasic/prioq.h @@ -0,0 +1,43 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include "hashmap.h" +#include "macro.h" + +typedef struct Prioq Prioq; + +#define PRIOQ_IDX_NULL ((unsigned) -1) + +Prioq *prioq_new(compare_func_t compare); +Prioq *prioq_free(Prioq *q); +int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func); + +int prioq_put(Prioq *q, void *data, unsigned *idx); +int prioq_remove(Prioq *q, void *data, unsigned *idx); +int prioq_reshuffle(Prioq *q, void *data, unsigned *idx); + +void *prioq_peek(Prioq *q) _pure_; +void *prioq_pop(Prioq *q); + +unsigned prioq_size(Prioq *q) _pure_; +bool prioq_isempty(Prioq *q) _pure_; diff --git a/src/libbasic/proc-cmdline.c b/src/libbasic/proc-cmdline.c new file mode 100644 index 0000000000..3505fa9c9a --- /dev/null +++ b/src/libbasic/proc-cmdline.c @@ -0,0 +1,176 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#include "alloc-util.h" +#include "extract-word.h" +#include "fileio.h" +#include "macro.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "special.h" +#include "string-util.h" +#include "util.h" +#include "virt.h" + +int proc_cmdline(char **ret) { + assert(ret); + + if (detect_container() > 0) + return get_process_cmdline(1, 0, false, ret); + else + return read_one_line_file("/proc/cmdline", ret); +} + +int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value)) { + _cleanup_free_ char *line = NULL; + const char *p; + int r; + + assert(parse_item); + + r = proc_cmdline(&line); + if (r < 0) + return r; + + p = line; + for (;;) { + _cleanup_free_ char *word = NULL; + char *value = NULL; + + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); + if (r < 0) + return r; + if (r == 0) + break; + + /* Filter out arguments that are intended only for the + * initrd */ + if (!in_initrd() && startswith(word, "rd.")) + continue; + + value = strchr(word, '='); + if (value) + *(value++) = 0; + + r = parse_item(word, value); + if (r < 0) + return r; + } + + return 0; +} + +int get_proc_cmdline_key(const char *key, char **value) { + _cleanup_free_ char *line = NULL, *ret = NULL; + bool found = false; + const char *p; + int r; + + assert(key); + + r = proc_cmdline(&line); + if (r < 0) + return r; + + p = line; + for (;;) { + _cleanup_free_ char *word = NULL; + const char *e; + + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); + if (r < 0) + return r; + if (r == 0) + break; + + /* Filter out arguments that are intended only for the + * initrd */ + if (!in_initrd() && startswith(word, "rd.")) + continue; + + if (value) { + e = startswith(word, key); + if (!e) + continue; + + r = free_and_strdup(&ret, e); + if (r < 0) + return r; + + found = true; + } else { + if (streq(word, key)) + found = true; + } + } + + if (value) { + *value = ret; + ret = NULL; + } + + return found; + +} + +int shall_restore_state(void) { + _cleanup_free_ char *value = NULL; + int r; + + r = get_proc_cmdline_key("systemd.restore_state=", &value); + if (r < 0) + return r; + if (r == 0) + return true; + + return parse_boolean(value); +} + +static const char * const rlmap[] = { + "emergency", SPECIAL_EMERGENCY_TARGET, + "-b", SPECIAL_EMERGENCY_TARGET, + "rescue", SPECIAL_RESCUE_TARGET, + "single", SPECIAL_RESCUE_TARGET, + "-s", SPECIAL_RESCUE_TARGET, + "s", SPECIAL_RESCUE_TARGET, + "S", SPECIAL_RESCUE_TARGET, + "1", SPECIAL_RESCUE_TARGET, + "2", SPECIAL_MULTI_USER_TARGET, + "3", SPECIAL_MULTI_USER_TARGET, + "4", SPECIAL_MULTI_USER_TARGET, + "5", SPECIAL_GRAPHICAL_TARGET, +}; + +const char* runlevel_to_target(const char *word) { + size_t i; + + if (!word) + return NULL; + + for (i = 0; i < ELEMENTSOF(rlmap); i += 2) + if (streq(word, rlmap[i])) + return rlmap[i+1]; + + return NULL; +} diff --git a/src/libbasic/proc-cmdline.h b/src/libbasic/proc-cmdline.h new file mode 100644 index 0000000000..452642a2f5 --- /dev/null +++ b/src/libbasic/proc-cmdline.h @@ -0,0 +1,27 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int proc_cmdline(char **ret); +int parse_proc_cmdline(int (*parse_word)(const char *key, const char *value)); +int get_proc_cmdline_key(const char *parameter, char **value); + +int shall_restore_state(void); +const char* runlevel_to_target(const char *rl); diff --git a/src/libbasic/process-util.c b/src/libbasic/process-util.c new file mode 100644 index 0000000000..189ef9ab60 --- /dev/null +++ b/src/libbasic/process-util.c @@ -0,0 +1,793 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_VALGRIND_VALGRIND_H +#include +#endif + +#include "alloc-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "ioprio.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "process-util.h" +#include "signal-util.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "user-util.h" +#include "util.h" + +int get_process_state(pid_t pid) { + const char *p; + char state; + int r; + _cleanup_free_ char *line = NULL; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "stat"); + + r = read_one_line_file(p, &line); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; + + p = strrchr(line, ')'); + if (!p) + return -EIO; + + p++; + + if (sscanf(p, " %c", &state) != 1) + return -EIO; + + return (unsigned char) state; +} + +int get_process_comm(pid_t pid, char **name) { + const char *p; + int r; + + assert(name); + assert(pid >= 0); + + p = procfs_file_alloca(pid, "comm"); + + r = read_one_line_file(p, name); + if (r == -ENOENT) + return -ESRCH; + + return r; +} + +int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char **line) { + _cleanup_fclose_ FILE *f = NULL; + char *r = NULL, *k; + const char *p; + int c; + + assert(line); + assert(pid >= 0); + + p = procfs_file_alloca(pid, "cmdline"); + + f = fopen(p, "re"); + if (!f) { + if (errno == ENOENT) + return -ESRCH; + return -errno; + } + + if (max_length == 0) { + size_t len = 0, allocated = 0; + + while ((c = getc(f)) != EOF) { + + if (!GREEDY_REALLOC(r, allocated, len+2)) { + free(r); + return -ENOMEM; + } + + r[len++] = isprint(c) ? c : ' '; + } + + if (len > 0) + r[len-1] = 0; + + } else { + bool space = false; + size_t left; + + r = new(char, max_length); + if (!r) + return -ENOMEM; + + k = r; + left = max_length; + while ((c = getc(f)) != EOF) { + + if (isprint(c)) { + if (space) { + if (left <= 4) + break; + + *(k++) = ' '; + left--; + space = false; + } + + if (left <= 4) + break; + + *(k++) = (char) c; + left--; + } else + space = true; + } + + if (left <= 4) { + size_t n = MIN(left-1, 3U); + memcpy(k, "...", n); + k[n] = 0; + } else + *k = 0; + } + + /* Kernel threads have no argv[] */ + if (isempty(r)) { + _cleanup_free_ char *t = NULL; + int h; + + free(r); + + if (!comm_fallback) + return -ENOENT; + + h = get_process_comm(pid, &t); + if (h < 0) + return h; + + r = strjoin("[", t, "]", NULL); + if (!r) + return -ENOMEM; + } + + *line = r; + return 0; +} + +void rename_process(const char name[8]) { + assert(name); + + /* This is a like a poor man's setproctitle(). It changes the + * comm field, argv[0], and also the glibc's internally used + * name of the process. For the first one a limit of 16 chars + * applies, to the second one usually one of 10 (i.e. length + * of "/sbin/init"), to the third one one of 7 (i.e. length of + * "systemd"). If you pass a longer string it will be + * truncated */ + + prctl(PR_SET_NAME, name); + + if (program_invocation_name) + strncpy(program_invocation_name, name, strlen(program_invocation_name)); + + if (saved_argc > 0) { + int i; + + if (saved_argv[0]) + strncpy(saved_argv[0], name, strlen(saved_argv[0])); + + for (i = 1; i < saved_argc; i++) { + if (!saved_argv[i]) + break; + + memzero(saved_argv[i], strlen(saved_argv[i])); + } + } +} + +int is_kernel_thread(pid_t pid) { + const char *p; + size_t count; + char c; + bool eof; + FILE *f; + + if (pid == 0 || pid == 1) /* pid 1, and we ourselves certainly aren't a kernel thread */ + return 0; + + assert(pid > 1); + + p = procfs_file_alloca(pid, "cmdline"); + f = fopen(p, "re"); + if (!f) { + if (errno == ENOENT) + return -ESRCH; + return -errno; + } + + count = fread(&c, 1, 1, f); + eof = feof(f); + fclose(f); + + /* Kernel threads have an empty cmdline */ + + if (count <= 0) + return eof ? 1 : -errno; + + return 0; +} + +int get_process_capeff(pid_t pid, char **capeff) { + const char *p; + int r; + + assert(capeff); + assert(pid >= 0); + + p = procfs_file_alloca(pid, "status"); + + r = get_proc_field(p, "CapEff", WHITESPACE, capeff); + if (r == -ENOENT) + return -ESRCH; + + return r; +} + +static int get_process_link_contents(const char *proc_file, char **name) { + int r; + + assert(proc_file); + assert(name); + + r = readlink_malloc(proc_file, name); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; + + return 0; +} + +int get_process_exe(pid_t pid, char **name) { + const char *p; + char *d; + int r; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "exe"); + r = get_process_link_contents(p, name); + if (r < 0) + return r; + + d = endswith(*name, " (deleted)"); + if (d) + *d = '\0'; + + return 0; +} + +static int get_process_id(pid_t pid, const char *field, uid_t *uid) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + const char *p; + + assert(field); + assert(uid); + + if (pid == 0) + return getuid(); + + p = procfs_file_alloca(pid, "status"); + f = fopen(p, "re"); + if (!f) { + if (errno == ENOENT) + return -ESRCH; + return -errno; + } + + FOREACH_LINE(line, f, return -errno) { + char *l; + + l = strstrip(line); + + if (startswith(l, field)) { + l += strlen(field); + l += strspn(l, WHITESPACE); + + l[strcspn(l, WHITESPACE)] = 0; + + return parse_uid(l, uid); + } + } + + return -EIO; +} + +int get_process_uid(pid_t pid, uid_t *uid) { + return get_process_id(pid, "Uid:", uid); +} + +int get_process_gid(pid_t pid, gid_t *gid) { + assert_cc(sizeof(uid_t) == sizeof(gid_t)); + return get_process_id(pid, "Gid:", gid); +} + +int get_process_cwd(pid_t pid, char **cwd) { + const char *p; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "cwd"); + + return get_process_link_contents(p, cwd); +} + +int get_process_root(pid_t pid, char **root) { + const char *p; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "root"); + + return get_process_link_contents(p, root); +} + +int get_process_environ(pid_t pid, char **env) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *outcome = NULL; + int c; + const char *p; + size_t allocated = 0, sz = 0; + + assert(pid >= 0); + assert(env); + + p = procfs_file_alloca(pid, "environ"); + + f = fopen(p, "re"); + if (!f) { + if (errno == ENOENT) + return -ESRCH; + return -errno; + } + + while ((c = fgetc(f)) != EOF) { + if (!GREEDY_REALLOC(outcome, allocated, sz + 5)) + return -ENOMEM; + + if (c == '\0') + outcome[sz++] = '\n'; + else + sz += cescape_char(c, outcome + sz); + } + + if (!outcome) { + outcome = strdup(""); + if (!outcome) + return -ENOMEM; + } else + outcome[sz] = '\0'; + + *env = outcome; + outcome = NULL; + + return 0; +} + +int get_process_ppid(pid_t pid, pid_t *_ppid) { + int r; + _cleanup_free_ char *line = NULL; + long unsigned ppid; + const char *p; + + assert(pid >= 0); + assert(_ppid); + + if (pid == 0) { + *_ppid = getppid(); + return 0; + } + + p = procfs_file_alloca(pid, "stat"); + r = read_one_line_file(p, &line); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) + return r; + + /* Let's skip the pid and comm fields. The latter is enclosed + * in () but does not escape any () in its value, so let's + * skip over it manually */ + + p = strrchr(line, ')'); + if (!p) + return -EIO; + + p++; + + if (sscanf(p, " " + "%*c " /* state */ + "%lu ", /* ppid */ + &ppid) != 1) + return -EIO; + + if ((long unsigned) (pid_t) ppid != ppid) + return -ERANGE; + + *_ppid = (pid_t) ppid; + + return 0; +} + +int wait_for_terminate(pid_t pid, siginfo_t *status) { + siginfo_t dummy; + + assert(pid >= 1); + + if (!status) + status = &dummy; + + for (;;) { + zero(*status); + + if (waitid(P_PID, pid, status, WEXITED) < 0) { + + if (errno == EINTR) + continue; + + return -errno; + } + + return 0; + } +} + +/* + * Return values: + * < 0 : wait_for_terminate() failed to get the state of the + * process, the process was terminated by a signal, or + * failed for an unknown reason. + * >=0 : The process terminated normally, and its exit code is + * returned. + * + * That is, success is indicated by a return value of zero, and an + * error is indicated by a non-zero value. + * + * A warning is emitted if the process terminates abnormally, + * and also if it returns non-zero unless check_exit_code is true. + */ +int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code) { + int r; + siginfo_t status; + + assert(name); + assert(pid > 1); + + r = wait_for_terminate(pid, &status); + if (r < 0) + return log_warning_errno(r, "Failed to wait for %s: %m", name); + + if (status.si_code == CLD_EXITED) { + if (status.si_status != 0) + log_full(check_exit_code ? LOG_WARNING : LOG_DEBUG, + "%s failed with error code %i.", name, status.si_status); + else + log_debug("%s succeeded.", name); + + return status.si_status; + } else if (status.si_code == CLD_KILLED || + status.si_code == CLD_DUMPED) { + + log_warning("%s terminated by signal %s.", name, signal_to_string(status.si_status)); + return -EPROTO; + } + + log_warning("%s failed due to unknown reason.", name); + return -EPROTO; +} + +void sigkill_wait(pid_t *pid) { + if (!pid) + return; + if (*pid <= 1) + return; + + if (kill(*pid, SIGKILL) > 0) + (void) wait_for_terminate(*pid, NULL); +} + +int kill_and_sigcont(pid_t pid, int sig) { + int r; + + r = kill(pid, sig) < 0 ? -errno : 0; + + if (r >= 0) + kill(pid, SIGCONT); + + return r; +} + +int getenv_for_pid(pid_t pid, const char *field, char **_value) { + _cleanup_fclose_ FILE *f = NULL; + char *value = NULL; + int r; + bool done = false; + size_t l; + const char *path; + + assert(pid >= 0); + assert(field); + assert(_value); + + path = procfs_file_alloca(pid, "environ"); + + f = fopen(path, "re"); + if (!f) { + if (errno == ENOENT) + return -ESRCH; + return -errno; + } + + l = strlen(field); + r = 0; + + do { + char line[LINE_MAX]; + unsigned i; + + for (i = 0; i < sizeof(line)-1; i++) { + int c; + + c = getc(f); + if (_unlikely_(c == EOF)) { + done = true; + break; + } else if (c == 0) + break; + + line[i] = c; + } + line[i] = 0; + + if (memcmp(line, field, l) == 0 && line[l] == '=') { + value = strdup(line + l + 1); + if (!value) + return -ENOMEM; + + r = 1; + break; + } + + } while (!done); + + *_value = value; + return r; +} + +bool pid_is_unwaited(pid_t pid) { + /* Checks whether a PID is still valid at all, including a zombie */ + + if (pid < 0) + return false; + + if (pid <= 1) /* If we or PID 1 would be dead and have been waited for, this code would not be running */ + return true; + + if (kill(pid, 0) >= 0) + return true; + + return errno != ESRCH; +} + +bool pid_is_alive(pid_t pid) { + int r; + + /* Checks whether a PID is still valid and not a zombie */ + + if (pid < 0) + return false; + + if (pid <= 1) /* If we or PID 1 would be a zombie, this code would not be running */ + return true; + + r = get_process_state(pid); + if (r == -ESRCH || r == 'Z') + return false; + + return true; +} + +int pid_from_same_root_fs(pid_t pid) { + const char *root; + + if (pid < 0) + return 0; + + root = procfs_file_alloca(pid, "root"); + + return files_same(root, "/proc/1/root"); +} + +bool is_main_thread(void) { + static thread_local int cached = 0; + + if (_unlikely_(cached == 0)) + cached = getpid() == gettid() ? 1 : -1; + + return cached > 0; +} + +noreturn void freeze(void) { + + /* Make sure nobody waits for us on a socket anymore */ + close_all_fds(NULL, 0); + + sync(); + + for (;;) + pause(); +} + +bool oom_score_adjust_is_valid(int oa) { + return oa >= OOM_SCORE_ADJ_MIN && oa <= OOM_SCORE_ADJ_MAX; +} + +unsigned long personality_from_string(const char *p) { + + /* Parse a personality specifier. We introduce our own + * identifiers that indicate specific ABIs, rather than just + * hints regarding the register size, since we want to keep + * things open for multiple locally supported ABIs for the + * same register size. We try to reuse the ABI identifiers + * used by libseccomp. */ + +#if defined(__x86_64__) + + if (streq(p, "x86")) + return PER_LINUX32; + + if (streq(p, "x86-64")) + return PER_LINUX; + +#elif defined(__i386__) + + if (streq(p, "x86")) + return PER_LINUX; + +#elif defined(__s390x__) + + if (streq(p, "s390")) + return PER_LINUX32; + + if (streq(p, "s390x")) + return PER_LINUX; + +#elif defined(__s390__) + + if (streq(p, "s390")) + return PER_LINUX; +#endif + + return PERSONALITY_INVALID; +} + +const char* personality_to_string(unsigned long p) { + +#if defined(__x86_64__) + + if (p == PER_LINUX32) + return "x86"; + + if (p == PER_LINUX) + return "x86-64"; + +#elif defined(__i386__) + + if (p == PER_LINUX) + return "x86"; + +#elif defined(__s390x__) + + if (p == PER_LINUX) + return "s390x"; + + if (p == PER_LINUX32) + return "s390"; + +#elif defined(__s390__) + + if (p == PER_LINUX) + return "s390"; + +#endif + + return NULL; +} + +void valgrind_summary_hack(void) { +#ifdef HAVE_VALGRIND_VALGRIND_H + if (getpid() == 1 && RUNNING_ON_VALGRIND) { + pid_t pid; + pid = raw_clone(SIGCHLD, NULL); + if (pid < 0) + log_emergency_errno(errno, "Failed to fork off valgrind helper: %m"); + else if (pid == 0) + exit(EXIT_SUCCESS); + else { + log_info("Spawned valgrind helper as PID "PID_FMT".", pid); + (void) wait_for_terminate(pid, NULL); + } + } +#endif +} + +static const char *const ioprio_class_table[] = { + [IOPRIO_CLASS_NONE] = "none", + [IOPRIO_CLASS_RT] = "realtime", + [IOPRIO_CLASS_BE] = "best-effort", + [IOPRIO_CLASS_IDLE] = "idle" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ioprio_class, int, INT_MAX); + +static const char *const sigchld_code_table[] = { + [CLD_EXITED] = "exited", + [CLD_KILLED] = "killed", + [CLD_DUMPED] = "dumped", + [CLD_TRAPPED] = "trapped", + [CLD_STOPPED] = "stopped", + [CLD_CONTINUED] = "continued", +}; + +DEFINE_STRING_TABLE_LOOKUP(sigchld_code, int); + +static const char* const sched_policy_table[] = { + [SCHED_OTHER] = "other", + [SCHED_BATCH] = "batch", + [SCHED_IDLE] = "idle", + [SCHED_FIFO] = "fifo", + [SCHED_RR] = "rr" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(sched_policy, int, INT_MAX); diff --git a/src/libbasic/process-util.h b/src/libbasic/process-util.h new file mode 100644 index 0000000000..f5d193e762 --- /dev/null +++ b/src/libbasic/process-util.h @@ -0,0 +1,103 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "formats-util.h" +#include "macro.h" + +#define procfs_file_alloca(pid, field) \ + ({ \ + pid_t _pid_ = (pid); \ + const char *_r_; \ + if (_pid_ == 0) { \ + _r_ = ("/proc/self/" field); \ + } else { \ + _r_ = alloca(strlen("/proc/") + DECIMAL_STR_MAX(pid_t) + 1 + sizeof(field)); \ + sprintf((char*) _r_, "/proc/"PID_FMT"/" field, _pid_); \ + } \ + _r_; \ + }) + +int get_process_state(pid_t pid); +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); +int get_process_exe(pid_t pid, char **name); +int get_process_uid(pid_t pid, uid_t *uid); +int get_process_gid(pid_t pid, gid_t *gid); +int get_process_capeff(pid_t pid, char **capeff); +int get_process_cwd(pid_t pid, char **cwd); +int get_process_root(pid_t pid, char **root); +int get_process_environ(pid_t pid, char **environ); +int get_process_ppid(pid_t pid, pid_t *ppid); + +int wait_for_terminate(pid_t pid, siginfo_t *status); +int wait_for_terminate_and_warn(const char *name, pid_t pid, bool check_exit_code); + +void sigkill_wait(pid_t *pid); +#define _cleanup_sigkill_wait_ _cleanup_(sigkill_wait) + +int kill_and_sigcont(pid_t pid, int sig); + +void rename_process(const char name[8]); +int is_kernel_thread(pid_t pid); + +int getenv_for_pid(pid_t pid, const char *field, char **_value); + +bool pid_is_alive(pid_t pid); +bool pid_is_unwaited(pid_t pid); +int pid_from_same_root_fs(pid_t pid); + +bool is_main_thread(void); + +noreturn void freeze(void); + +bool oom_score_adjust_is_valid(int oa); + +#ifndef PERSONALITY_INVALID +/* personality(7) documents that 0xffffffffUL is used for querying the + * current personality, hence let's use that here as error + * indicator. */ +#define PERSONALITY_INVALID 0xffffffffLU +#endif + +unsigned long personality_from_string(const char *p); +const char *personality_to_string(unsigned long); + +int ioprio_class_to_string_alloc(int i, char **s); +int ioprio_class_from_string(const char *s); + +const char *sigchld_code_to_string(int i) _const_; +int sigchld_code_from_string(const char *s) _pure_; + +int sched_policy_to_string_alloc(int i, char **s); +int sched_policy_from_string(const char *s); + +#define PTR_TO_PID(p) ((pid_t) ((uintptr_t) p)) +#define PID_TO_PTR(p) ((void*) ((uintptr_t) p)) + +void valgrind_summary_hack(void); diff --git a/src/libbasic/random-util.c b/src/libbasic/random-util.c new file mode 100644 index 0000000000..2f468db770 --- /dev/null +++ b/src/libbasic/random-util.c @@ -0,0 +1,133 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SYS_AUXV_H +#include +#endif + +#include "fd-util.h" +#include "io-util.h" +#include "missing.h" +#include "random-util.h" +#include "time-util.h" + +int dev_urandom(void *p, size_t n) { + static int have_syscall = -1; + + _cleanup_close_ int fd = -1; + int r; + + /* Gathers some randomness from the kernel. This call will + * 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 + * of seeding the hash functions for hashtable */ + + /* Use the getrandom() syscall unless we know we don't have + * it, or when the requested size is too large for it. */ + if (have_syscall != 0 || (size_t) (int) n != n) { + r = getrandom(p, n, GRND_NONBLOCK); + if (r == (int) n) { + have_syscall = true; + return 0; + } + + if (r < 0) { + if (errno == ENOSYS) + /* we lack the syscall, continue with + * reading from /dev/urandom */ + have_syscall = false; + else if (errno == EAGAIN) + /* not enough entropy for now. Let's + * remember to use the syscall the + * next time, again, but also read + * from /dev/urandom for now, which + * doesn't care about the current + * amount of entropy. */ + have_syscall = true; + else + return -errno; + } else + /* too short read? */ + return -ENODATA; + } + + fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return errno == ENOENT ? -ENOSYS : -errno; + + return loop_read_exact(fd, p, n, true); +} + +void initialize_srand(void) { + static bool srand_called = false; + unsigned x; +#ifdef HAVE_SYS_AUXV_H + void *auxv; +#endif + + if (srand_called) + return; + +#ifdef HAVE_SYS_AUXV_H + /* The kernel provides us with 16 bytes of entropy in auxv, so let's try to make use of that to seed the + * pseudo-random generator. It's better than nothing... */ + + auxv = (void*) getauxval(AT_RANDOM); + if (auxv) { + assert_cc(sizeof(x) < 16); + memcpy(&x, auxv, sizeof(x)); + } else +#endif + x = 0; + + + x ^= (unsigned) now(CLOCK_REALTIME); + x ^= (unsigned) gettid(); + + srand(x); + srand_called = true; +} + +void random_bytes(void *p, size_t n) { + uint8_t *q; + int r; + + r = dev_urandom(p, n); + if (r >= 0) + return; + + /* If some idiot made /dev/urandom unavailable to us, he'll + * get a PRNG instead. */ + + initialize_srand(); + + for (q = p; q < (uint8_t*) p + n; q ++) + *q = rand(); +} diff --git a/src/libbasic/random-util.h b/src/libbasic/random-util.h new file mode 100644 index 0000000000..3cee4c5014 --- /dev/null +++ b/src/libbasic/random-util.h @@ -0,0 +1,39 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +int dev_urandom(void *p, size_t n); +void random_bytes(void *p, size_t n); +void initialize_srand(void); + +static inline uint64_t random_u64(void) { + uint64_t u; + random_bytes(&u, sizeof(u)); + return u; +} + +static inline uint32_t random_u32(void) { + uint32_t u; + random_bytes(&u, sizeof(u)); + return u; +} diff --git a/src/libbasic/ratelimit.c b/src/libbasic/ratelimit.c new file mode 100644 index 0000000000..3ca5625e4d --- /dev/null +++ b/src/libbasic/ratelimit.c @@ -0,0 +1,56 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + + +#include + +#include "macro.h" +#include "ratelimit.h" + +/* Modelled after Linux' lib/ratelimit.c by Dave Young + * , which is licensed GPLv2. */ + +bool ratelimit_test(RateLimit *r) { + usec_t ts; + + assert(r); + + if (r->interval <= 0 || r->burst <= 0) + return true; + + ts = now(CLOCK_MONOTONIC); + + if (r->begin <= 0 || + r->begin + r->interval < ts) { + r->begin = ts; + + /* Reset counter */ + r->num = 0; + goto good; + } + + if (r->num < r->burst) + goto good; + + return false; + +good: + r->num++; + return true; +} diff --git a/src/libbasic/ratelimit.h b/src/libbasic/ratelimit.h new file mode 100644 index 0000000000..9c8dddf5ad --- /dev/null +++ b/src/libbasic/ratelimit.h @@ -0,0 +1,58 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "time-util.h" +#include "util.h" + +typedef struct RateLimit { + usec_t interval; + usec_t begin; + unsigned burst; + unsigned num; +} RateLimit; + +#define RATELIMIT_DEFINE(_name, _interval, _burst) \ + RateLimit _name = { \ + .interval = (_interval), \ + .burst = (_burst), \ + .num = 0, \ + .begin = 0 \ + } + +#define RATELIMIT_INIT(v, _interval, _burst) \ + do { \ + RateLimit *_r = &(v); \ + _r->interval = (_interval); \ + _r->burst = (_burst); \ + _r->num = 0; \ + _r->begin = 0; \ + } while (false) + +#define RATELIMIT_RESET(v) \ + do { \ + RateLimit *_r = &(v); \ + _r->num = 0; \ + _r->begin = 0; \ + } while (false) + +bool ratelimit_test(RateLimit *r); diff --git a/src/libbasic/refcnt.h b/src/libbasic/refcnt.h new file mode 100644 index 0000000000..1d77a6445a --- /dev/null +++ b/src/libbasic/refcnt.h @@ -0,0 +1,34 @@ +#pragma once + +/*** + 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 . +***/ + +/* A type-safe atomic refcounter. + * + * DO NOT USE THIS UNLESS YOU ACTUALLY CARE ABOUT THREAD SAFETY! */ + +typedef struct { + volatile unsigned _value; +} RefCount; + +#define REFCNT_GET(r) ((r)._value) +#define REFCNT_INC(r) (__sync_add_and_fetch(&(r)._value, 1)) +#define REFCNT_DEC(r) (__sync_sub_and_fetch(&(r)._value, 1)) + +#define REFCNT_INIT ((RefCount) { ._value = 1 }) diff --git a/src/libbasic/replace-var.c b/src/libbasic/replace-var.c new file mode 100644 index 0000000000..6a204b9ec3 --- /dev/null +++ b/src/libbasic/replace-var.c @@ -0,0 +1,112 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "macro.h" +#include "replace-var.h" +#include "string-util.h" + +/* + * Generic infrastructure for replacing @FOO@ style variables in + * strings. Will call a callback for each replacement. + */ + +static int get_variable(const char *b, char **r) { + size_t k; + char *t; + + assert(b); + assert(r); + + if (*b != '@') + return 0; + + k = strspn(b + 1, UPPERCASE_LETTERS "_"); + if (k <= 0 || b[k+1] != '@') + return 0; + + t = strndup(b + 1, k); + if (!t) + return -ENOMEM; + + *r = t; + return 1; +} + +char *replace_var(const char *text, char *(*lookup)(const char *variable, void*userdata), void *userdata) { + char *r, *t; + const char *f; + size_t l; + + assert(text); + assert(lookup); + + l = strlen(text); + r = new(char, l+1); + if (!r) + return NULL; + + f = text; + t = r; + while (*f) { + _cleanup_free_ char *v = NULL, *n = NULL; + char *a; + int k; + size_t skip, d, nl; + + k = get_variable(f, &v); + if (k < 0) + goto oom; + if (k == 0) { + *(t++) = *(f++); + continue; + } + + n = lookup(v, userdata); + if (!n) + goto oom; + + skip = strlen(v) + 2; + + d = t - r; + nl = l - skip + strlen(n); + a = realloc(r, nl + 1); + if (!a) + goto oom; + + l = nl; + r = a; + t = r + d; + + t = stpcpy(t, n); + f += skip; + } + + *t = 0; + return r; + +oom: + free(r); + return NULL; +} diff --git a/src/libbasic/replace-var.h b/src/libbasic/replace-var.h new file mode 100644 index 0000000000..78412910b2 --- /dev/null +++ b/src/libbasic/replace-var.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +char *replace_var(const char *text, char *(*lookup)(const char *variable, void*userdata), void *userdata); diff --git a/src/libbasic/rlimit-util.c b/src/libbasic/rlimit-util.c new file mode 100644 index 0000000000..7540b43215 --- /dev/null +++ b/src/libbasic/rlimit-util.c @@ -0,0 +1,271 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "alloc-util.h" +#include "extract-word.h" +#include "formats-util.h" +#include "macro.h" +#include "missing.h" +#include "rlimit-util.h" +#include "string-table.h" +#include "time-util.h" + +int setrlimit_closest(int resource, const struct rlimit *rlim) { + struct rlimit highest, fixed; + + assert(rlim); + + if (setrlimit(resource, rlim) >= 0) + return 0; + + if (errno != EPERM) + return -errno; + + /* So we failed to set the desired setrlimit, then let's try + * to get as close as we can */ + assert_se(getrlimit(resource, &highest) == 0); + + fixed.rlim_cur = MIN(rlim->rlim_cur, highest.rlim_max); + fixed.rlim_max = MIN(rlim->rlim_max, highest.rlim_max); + + if (setrlimit(resource, &fixed) < 0) + return -errno; + + return 0; +} + +static int rlimit_parse_u64(const char *val, rlim_t *ret) { + uint64_t u; + int r; + + assert(val); + assert(ret); + + if (streq(val, "infinity")) { + *ret = RLIM_INFINITY; + return 0; + } + + /* setrlimit(2) suggests rlim_t is always 64bit on Linux. */ + assert_cc(sizeof(rlim_t) == sizeof(uint64_t)); + + r = safe_atou64(val, &u); + if (r < 0) + return r; + if (u >= (uint64_t) RLIM_INFINITY) + return -ERANGE; + + *ret = (rlim_t) u; + return 0; +} + +static int rlimit_parse_size(const char *val, rlim_t *ret) { + uint64_t u; + int r; + + assert(val); + assert(ret); + + if (streq(val, "infinity")) { + *ret = RLIM_INFINITY; + return 0; + } + + r = parse_size(val, 1024, &u); + if (r < 0) + return r; + if (u >= (uint64_t) RLIM_INFINITY) + return -ERANGE; + + *ret = (rlim_t) u; + return 0; +} + +static int rlimit_parse_sec(const char *val, rlim_t *ret) { + uint64_t u; + usec_t t; + int r; + + assert(val); + assert(ret); + + if (streq(val, "infinity")) { + *ret = RLIM_INFINITY; + return 0; + } + + r = parse_sec(val, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) { + *ret = RLIM_INFINITY; + return 0; + } + + u = (uint64_t) DIV_ROUND_UP(t, USEC_PER_SEC); + if (u >= (uint64_t) RLIM_INFINITY) + return -ERANGE; + + *ret = (rlim_t) u; + return 0; +} + +static int rlimit_parse_usec(const char *val, rlim_t *ret) { + usec_t t; + int r; + + assert(val); + assert(ret); + + if (streq(val, "infinity")) { + *ret = RLIM_INFINITY; + return 0; + } + + r = parse_time(val, &t, 1); + if (r < 0) + return r; + if (t == USEC_INFINITY) { + *ret = RLIM_INFINITY; + return 0; + } + + *ret = (rlim_t) t; + return 0; +} + +static int (*const rlimit_parse_table[_RLIMIT_MAX])(const char *val, rlim_t *ret) = { + [RLIMIT_CPU] = rlimit_parse_sec, + [RLIMIT_FSIZE] = rlimit_parse_size, + [RLIMIT_DATA] = rlimit_parse_size, + [RLIMIT_STACK] = rlimit_parse_size, + [RLIMIT_CORE] = rlimit_parse_size, + [RLIMIT_RSS] = rlimit_parse_size, + [RLIMIT_NOFILE] = rlimit_parse_u64, + [RLIMIT_AS] = rlimit_parse_size, + [RLIMIT_NPROC] = rlimit_parse_u64, + [RLIMIT_MEMLOCK] = rlimit_parse_size, + [RLIMIT_LOCKS] = rlimit_parse_u64, + [RLIMIT_SIGPENDING] = rlimit_parse_u64, + [RLIMIT_MSGQUEUE] = rlimit_parse_size, + [RLIMIT_NICE] = rlimit_parse_u64, + [RLIMIT_RTPRIO] = rlimit_parse_u64, + [RLIMIT_RTTIME] = rlimit_parse_usec, +}; + +int rlimit_parse_one(int resource, const char *val, rlim_t *ret) { + assert(val); + assert(ret); + + if (resource < 0) + return -EINVAL; + if (resource >= _RLIMIT_MAX) + return -EINVAL; + + return rlimit_parse_table[resource](val, ret); +} + +int rlimit_parse(int resource, const char *val, struct rlimit *ret) { + _cleanup_free_ char *hard = NULL, *soft = NULL; + rlim_t hl, sl; + int r; + + assert(val); + assert(ret); + + r = extract_first_word(&val, &soft, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + r = rlimit_parse_one(resource, soft, &sl); + if (r < 0) + return r; + + r = extract_first_word(&val, &hard, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (!isempty(val)) + return -EINVAL; + if (r == 0) + hl = sl; + else { + r = rlimit_parse_one(resource, hard, &hl); + if (r < 0) + return r; + if (sl > hl) + return -EILSEQ; + } + + *ret = (struct rlimit) { + .rlim_cur = sl, + .rlim_max = hl, + }; + + return 0; +} + +int rlimit_format(const struct rlimit *rl, char **ret) { + char *s = NULL; + + assert(rl); + assert(ret); + + if (rl->rlim_cur >= RLIM_INFINITY && rl->rlim_max >= RLIM_INFINITY) + s = strdup("infinity"); + else if (rl->rlim_cur >= RLIM_INFINITY) + (void) asprintf(&s, "infinity:" RLIM_FMT, rl->rlim_max); + else if (rl->rlim_max >= RLIM_INFINITY) + (void) asprintf(&s, RLIM_FMT ":infinity", rl->rlim_cur); + else if (rl->rlim_cur == rl->rlim_max) + (void) asprintf(&s, RLIM_FMT, rl->rlim_cur); + else + (void) asprintf(&s, RLIM_FMT ":" RLIM_FMT, rl->rlim_cur, rl->rlim_max); + + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +static const char* const rlimit_table[_RLIMIT_MAX] = { + [RLIMIT_CPU] = "LimitCPU", + [RLIMIT_FSIZE] = "LimitFSIZE", + [RLIMIT_DATA] = "LimitDATA", + [RLIMIT_STACK] = "LimitSTACK", + [RLIMIT_CORE] = "LimitCORE", + [RLIMIT_RSS] = "LimitRSS", + [RLIMIT_NOFILE] = "LimitNOFILE", + [RLIMIT_AS] = "LimitAS", + [RLIMIT_NPROC] = "LimitNPROC", + [RLIMIT_MEMLOCK] = "LimitMEMLOCK", + [RLIMIT_LOCKS] = "LimitLOCKS", + [RLIMIT_SIGPENDING] = "LimitSIGPENDING", + [RLIMIT_MSGQUEUE] = "LimitMSGQUEUE", + [RLIMIT_NICE] = "LimitNICE", + [RLIMIT_RTPRIO] = "LimitRTPRIO", + [RLIMIT_RTTIME] = "LimitRTTIME" +}; + +DEFINE_STRING_TABLE_LOOKUP(rlimit, int); diff --git a/src/libbasic/rlimit-util.h b/src/libbasic/rlimit-util.h new file mode 100644 index 0000000000..d4594eccd6 --- /dev/null +++ b/src/libbasic/rlimit-util.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "macro.h" + +const char *rlimit_to_string(int i) _const_; +int rlimit_from_string(const char *s) _pure_; + +int setrlimit_closest(int resource, const struct rlimit *rlim); + +int rlimit_parse_one(int resource, const char *val, rlim_t *ret); +int rlimit_parse(int resource, const char *val, struct rlimit *ret); + +int rlimit_format(const struct rlimit *rl, char **ret); + +#define RLIMIT_MAKE_CONST(lim) ((struct rlimit) { lim, lim }) diff --git a/src/libbasic/rm-rf.c b/src/libbasic/rm-rf.c new file mode 100644 index 0000000000..43816fd1bb --- /dev/null +++ b/src/libbasic/rm-rf.c @@ -0,0 +1,236 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "btrfs-util.h" +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "mount-util.h" +#include "path-util.h" +#include "rm-rf.h" +#include "stat-util.h" +#include "string-util.h" + +int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { + _cleanup_closedir_ DIR *d = NULL; + int ret = 0, r; + + assert(fd >= 0); + + /* This returns the first error we run into, but nevertheless + * tries to go on. This closes the passed fd. */ + + if (!(flags & REMOVE_PHYSICAL)) { + + r = fd_is_temporary_fs(fd); + if (r < 0) { + safe_close(fd); + return r; + } + + if (!r) { + /* We refuse to clean physical file systems + * with this call, unless explicitly + * requested. This is extra paranoia just to + * be sure we never ever remove non-state + * data */ + + log_error("Attempted to remove disk file system, and we can't allow that."); + safe_close(fd); + return -EPERM; + } + } + + d = fdopendir(fd); + if (!d) { + safe_close(fd); + return errno == ENOENT ? 0 : -errno; + } + + for (;;) { + struct dirent *de; + bool is_dir; + struct stat st; + + errno = 0; + de = readdir(d); + if (!de) { + if (errno > 0 && ret == 0) + ret = -errno; + return ret; + } + + if (streq(de->d_name, ".") || streq(de->d_name, "..")) + continue; + + if (de->d_type == DT_UNKNOWN || + (de->d_type == DT_DIR && (root_dev || (flags & REMOVE_SUBVOLUME)))) { + if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + continue; + } + + is_dir = S_ISDIR(st.st_mode); + } else + is_dir = de->d_type == DT_DIR; + + if (is_dir) { + int subdir_fd; + + /* if root_dev is set, remove subdirectories only if device is same */ + if (root_dev && st.st_dev != root_dev->st_dev) + continue; + + subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (subdir_fd < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + continue; + } + + /* Stop at mount points */ + r = fd_is_mount_point(fd, de->d_name, 0); + if (r < 0) { + if (ret == 0 && r != -ENOENT) + ret = r; + + safe_close(subdir_fd); + continue; + } + if (r) { + safe_close(subdir_fd); + continue; + } + + if ((flags & REMOVE_SUBVOLUME) && st.st_ino == 256) { + + /* This could be a subvolume, try to remove it */ + + r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); + if (r < 0) { + if (r != -ENOTTY && r != -EINVAL) { + if (ret == 0) + ret = r; + + safe_close(subdir_fd); + continue; + } + + /* ENOTTY, then it wasn't a + * btrfs subvolume, continue + * below. */ + } else { + /* It was a subvolume, continue. */ + safe_close(subdir_fd); + continue; + } + } + + /* We pass REMOVE_PHYSICAL here, to avoid + * doing the fstatfs() to check the file + * system type again for each directory */ + r = rm_rf_children(subdir_fd, flags | REMOVE_PHYSICAL, root_dev); + if (r < 0 && ret == 0) + ret = r; + + if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + + } else if (!(flags & REMOVE_ONLY_DIRECTORIES)) { + + if (unlinkat(fd, de->d_name, 0) < 0) { + if (ret == 0 && errno != ENOENT) + ret = -errno; + } + } + } +} + +int rm_rf(const char *path, RemoveFlags flags) { + int fd, r; + struct statfs s; + + assert(path); + + /* We refuse to clean the root file system with this + * call. This is extra paranoia to never cause a really + * seriously broken system. */ + if (path_equal(path, "/")) { + log_error("Attempted to remove entire root file system, and we can't allow that."); + return -EPERM; + } + + if ((flags & (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) == (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) { + /* Try to remove as subvolume first */ + r = btrfs_subvol_remove(path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); + if (r >= 0) + return r; + + if (r != -ENOTTY && r != -EINVAL && r != -ENOTDIR) + return r; + + /* Not btrfs or not a subvolume */ + } + + fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + if (fd < 0) { + + if (errno != ENOTDIR && errno != ELOOP) + return -errno; + + if (!(flags & REMOVE_PHYSICAL)) { + if (statfs(path, &s) < 0) + return -errno; + + if (!is_temporary_fs(&s)) { + log_error("Attempted to remove disk file system, and we can't allow that."); + return -EPERM; + } + } + + if ((flags & REMOVE_ROOT) && !(flags & REMOVE_ONLY_DIRECTORIES)) + if (unlink(path) < 0 && errno != ENOENT) + return -errno; + + return 0; + } + + r = rm_rf_children(fd, flags, NULL); + + if (flags & REMOVE_ROOT) { + if (rmdir(path) < 0) { + if (r == 0 && errno != ENOENT) + r = -errno; + } + } + + return r; +} diff --git a/src/libbasic/rm-rf.h b/src/libbasic/rm-rf.h new file mode 100644 index 0000000000..6d03268919 --- /dev/null +++ b/src/libbasic/rm-rf.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include + +typedef enum RemoveFlags { + REMOVE_ONLY_DIRECTORIES = 1, + REMOVE_ROOT = 2, + REMOVE_PHYSICAL = 4, /* if not set, only removes files on tmpfs, never physical file systems */ + REMOVE_SUBVOLUME = 8, +} RemoveFlags; + +int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev); +int rm_rf(const char *path, RemoveFlags flags); diff --git a/src/libbasic/securebits.h b/src/libbasic/securebits.h new file mode 100644 index 0000000000..98fbe0d433 --- /dev/null +++ b/src/libbasic/securebits.h @@ -0,0 +1,45 @@ +#ifndef _LINUX_SECUREBITS_H +#define _LINUX_SECUREBITS_H 1 + +/* This is minimal version of Linux' linux/securebits.h header file, + * which is licensed GPL2 */ + +#define SECUREBITS_DEFAULT 0x00000000 + +/* When set UID 0 has no special privileges. When unset, we support + inheritance of root-permissions and suid-root executable under + compatibility mode. We raise the effective and inheritable bitmasks + *of the executable file* if the effective uid of the new process is + 0. If the real uid is 0, we raise the effective (legacy) bit of the + executable file. */ +#define SECURE_NOROOT 0 +#define SECURE_NOROOT_LOCKED 1 /* make bit-0 immutable */ + +/* When set, setuid to/from uid 0 does not trigger capability-"fixup". + When unset, to provide compatibility with old programs relying on + set*uid to gain/lose privilege, transitions to/from uid 0 cause + capabilities to be gained/lost. */ +#define SECURE_NO_SETUID_FIXUP 2 +#define SECURE_NO_SETUID_FIXUP_LOCKED 3 /* make bit-2 immutable */ + +/* When set, a process can retain its capabilities even after + transitioning to a non-root user (the set-uid fixup suppressed by + bit 2). Bit-4 is cleared when a process calls exec(); setting both + bit 4 and 5 will create a barrier through exec that no exec()'d + child can use this feature again. */ +#define SECURE_KEEP_CAPS 4 +#define SECURE_KEEP_CAPS_LOCKED 5 /* make bit-4 immutable */ + +/* Each securesetting is implemented using two bits. One bit specifies + whether the setting is on or off. The other bit specify whether the + setting is locked or not. A setting which is locked cannot be + changed from user-level. */ +#define issecure_mask(X) (1 << (X)) +#define issecure(X) (issecure_mask(X) & current_cred_xxx(securebits)) + +#define SECURE_ALL_BITS (issecure_mask(SECURE_NOROOT) | \ + issecure_mask(SECURE_NO_SETUID_FIXUP) | \ + issecure_mask(SECURE_KEEP_CAPS)) +#define SECURE_ALL_LOCKS (SECURE_ALL_BITS << 1) + +#endif /* !_LINUX_SECUREBITS_H */ diff --git a/src/libbasic/selinux-util.c b/src/libbasic/selinux-util.c new file mode 100644 index 0000000000..6c63b9d652 --- /dev/null +++ b/src/libbasic/selinux-util.c @@ -0,0 +1,493 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_SELINUX +#include +#include +#include +#endif + +#include "alloc-util.h" +#include "log.h" +#include "macro.h" +#include "path-util.h" +#include "selinux-util.h" +#include "time-util.h" +#include "util.h" + +#ifdef HAVE_SELINUX +DEFINE_TRIVIAL_CLEANUP_FUNC(security_context_t, freecon); +DEFINE_TRIVIAL_CLEANUP_FUNC(context_t, context_free); + +#define _cleanup_security_context_free_ _cleanup_(freeconp) +#define _cleanup_context_free_ _cleanup_(context_freep) + +static int cached_use = -1; +static struct selabel_handle *label_hnd = NULL; + +#define log_enforcing(...) log_full(security_getenforce() == 1 ? LOG_ERR : LOG_DEBUG, __VA_ARGS__) +#endif + +bool mac_selinux_have(void) { +#ifdef HAVE_SELINUX + if (cached_use < 0) + cached_use = is_selinux_enabled() > 0; + + return cached_use; +#else + return false; +#endif +} + +bool mac_selinux_use(void) { + if (!mac_selinux_have()) + return false; + + /* Never try to configure SELinux features if we aren't + * root */ + + return getuid() == 0; +} + +void mac_selinux_retest(void) { +#ifdef HAVE_SELINUX + cached_use = -1; +#endif +} + +int mac_selinux_init(const char *prefix) { + int r = 0; + +#ifdef HAVE_SELINUX + usec_t before_timestamp, after_timestamp; + struct mallinfo before_mallinfo, after_mallinfo; + + if (!mac_selinux_use()) + return 0; + + if (label_hnd) + return 0; + + before_mallinfo = mallinfo(); + before_timestamp = now(CLOCK_MONOTONIC); + + if (prefix) { + struct selinux_opt options[] = { + { .type = SELABEL_OPT_SUBSET, .value = prefix }, + }; + + label_hnd = selabel_open(SELABEL_CTX_FILE, options, ELEMENTSOF(options)); + } else + label_hnd = selabel_open(SELABEL_CTX_FILE, NULL, 0); + + if (!label_hnd) { + log_enforcing("Failed to initialize SELinux context: %m"); + r = security_getenforce() == 1 ? -errno : 0; + } else { + char timespan[FORMAT_TIMESPAN_MAX]; + int l; + + after_timestamp = now(CLOCK_MONOTONIC); + after_mallinfo = mallinfo(); + + l = after_mallinfo.uordblks > before_mallinfo.uordblks ? after_mallinfo.uordblks - before_mallinfo.uordblks : 0; + + log_debug("Successfully loaded SELinux database in %s, size on heap is %iK.", + format_timespan(timespan, sizeof(timespan), after_timestamp - before_timestamp, 0), + (l+1023)/1024); + } +#endif + + return r; +} + +void mac_selinux_finish(void) { + +#ifdef HAVE_SELINUX + if (!label_hnd) + return; + + selabel_close(label_hnd); + label_hnd = NULL; +#endif +} + +int mac_selinux_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { + +#ifdef HAVE_SELINUX + struct stat st; + int r; + + assert(path); + + /* if mac_selinux_init() wasn't called before we are a NOOP */ + if (!label_hnd) + return 0; + + r = lstat(path, &st); + if (r >= 0) { + _cleanup_security_context_free_ security_context_t fcon = NULL; + + r = selabel_lookup_raw(label_hnd, &fcon, path, st.st_mode); + + /* If there's no label to set, then exit without warning */ + if (r < 0 && errno == ENOENT) + return 0; + + if (r >= 0) { + r = lsetfilecon(path, fcon); + + /* If the FS doesn't support labels, then exit without warning */ + if (r < 0 && errno == EOPNOTSUPP) + return 0; + } + } + + if (r < 0) { + /* Ignore ENOENT in some cases */ + if (ignore_enoent && errno == ENOENT) + return 0; + + if (ignore_erofs && errno == EROFS) + return 0; + + log_enforcing("Unable to fix SELinux security context of %s: %m", path); + if (security_getenforce() == 1) + return -errno; + } +#endif + + return 0; +} + +int mac_selinux_apply(const char *path, const char *label) { + +#ifdef HAVE_SELINUX + if (!mac_selinux_use()) + return 0; + + assert(path); + assert(label); + + if (setfilecon(path, (security_context_t) label) < 0) { + log_enforcing("Failed to set SELinux security context %s on path %s: %m", label, path); + if (security_getenforce() > 0) + return -errno; + } +#endif + return 0; +} + +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; + security_class_t sclass; + + assert(exe); + assert(label); + + if (!mac_selinux_have()) + return -EOPNOTSUPP; + + r = getcon_raw(&mycon); + if (r < 0) + return -errno; + + r = getfilecon_raw(exe, &fcon); + if (r < 0) + return -errno; + + sclass = string_to_security_class("process"); + r = security_compute_create(mycon, fcon, sclass, (security_context_t *) label); + if (r < 0) + return -errno; +#endif + + return r; +} + +int mac_selinux_get_our_label(char **label) { + int r = -EOPNOTSUPP; + + assert(label); + +#ifdef HAVE_SELINUX + if (!mac_selinux_have()) + return -EOPNOTSUPP; + + r = getcon_raw(label); + if (r < 0) + return -errno; +#endif + + return r; +} + +int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *exec_label, char **label) { + int r = -EOPNOTSUPP; + +#ifdef HAVE_SELINUX + _cleanup_security_context_free_ security_context_t mycon = NULL, peercon = NULL, fcon = NULL; + _cleanup_context_free_ context_t pcon = NULL, bcon = NULL; + security_class_t sclass; + const char *range = NULL; + + assert(socket_fd >= 0); + assert(exe); + assert(label); + + if (!mac_selinux_have()) + return -EOPNOTSUPP; + + r = getcon_raw(&mycon); + if (r < 0) + return -errno; + + r = getpeercon(socket_fd, &peercon); + if (r < 0) + return -errno; + + if (!exec_label) { + /* If there is no context set for next exec let's use context + of target executable */ + r = getfilecon_raw(exe, &fcon); + if (r < 0) + return -errno; + } + + bcon = context_new(mycon); + if (!bcon) + return -ENOMEM; + + pcon = context_new(peercon); + if (!pcon) + return -ENOMEM; + + range = context_range_get(pcon); + if (!range) + return -errno; + + r = context_range_set(bcon, range); + if (r) + return -errno; + + freecon(mycon); + mycon = strdup(context_str(bcon)); + if (!mycon) + return -ENOMEM; + + sclass = string_to_security_class("process"); + r = security_compute_create(mycon, fcon, sclass, (security_context_t *) label); + if (r < 0) + return -errno; +#endif + + return r; +} + +char* mac_selinux_free(char *label) { + +#ifdef HAVE_SELINUX + if (!label) + return NULL; + + if (!mac_selinux_have()) + return NULL; + + + freecon((security_context_t) label); +#endif + + return NULL; +} + +int mac_selinux_create_file_prepare(const char *path, mode_t mode) { + +#ifdef HAVE_SELINUX + _cleanup_security_context_free_ security_context_t filecon = NULL; + int r; + + assert(path); + + if (!label_hnd) + return 0; + + if (path_is_absolute(path)) + r = selabel_lookup_raw(label_hnd, &filecon, path, mode); + else { + _cleanup_free_ char *newpath = NULL; + + r = path_make_absolute_cwd(path, &newpath); + if (r < 0) + return r; + + r = selabel_lookup_raw(label_hnd, &filecon, newpath, mode); + } + + if (r < 0) { + /* No context specified by the policy? Proceed without setting it. */ + if (errno == ENOENT) + return 0; + + log_enforcing("Failed to determine SELinux security context for %s: %m", path); + } else { + if (setfscreatecon(filecon) >= 0) + return 0; /* Success! */ + + log_enforcing("Failed to set SELinux security context %s for %s: %m", filecon, path); + } + + if (security_getenforce() > 0) + return -errno; + +#endif + return 0; +} + +void mac_selinux_create_file_clear(void) { + +#ifdef HAVE_SELINUX + PROTECT_ERRNO; + + if (!mac_selinux_use()) + return; + + setfscreatecon(NULL); +#endif +} + +int mac_selinux_create_socket_prepare(const char *label) { + +#ifdef HAVE_SELINUX + if (!mac_selinux_use()) + return 0; + + assert(label); + + if (setsockcreatecon((security_context_t) label) < 0) { + log_enforcing("Failed to set SELinux security context %s for sockets: %m", label); + + if (security_getenforce() == 1) + return -errno; + } +#endif + + return 0; +} + +void mac_selinux_create_socket_clear(void) { + +#ifdef HAVE_SELINUX + PROTECT_ERRNO; + + if (!mac_selinux_use()) + return; + + setsockcreatecon(NULL); +#endif +} + +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; + const struct sockaddr_un *un; + bool context_changed = false; + char *path; + int r; + + assert(fd >= 0); + assert(addr); + assert(addrlen >= sizeof(sa_family_t)); + + if (!label_hnd) + goto skipped; + + /* Filter out non-local sockets */ + if (addr->sa_family != AF_UNIX) + goto skipped; + + /* Filter out anonymous sockets */ + if (addrlen < offsetof(struct sockaddr_un, sun_path) + 1) + goto skipped; + + /* Filter out abstract namespace sockets */ + un = (const struct sockaddr_un*) addr; + if (un->sun_path[0] == 0) + goto skipped; + + path = strndupa(un->sun_path, addrlen - offsetof(struct sockaddr_un, sun_path)); + + if (path_is_absolute(path)) + r = selabel_lookup_raw(label_hnd, &fcon, path, S_IFSOCK); + else { + _cleanup_free_ char *newpath = NULL; + + r = path_make_absolute_cwd(path, &newpath); + if (r < 0) + return r; + + r = selabel_lookup_raw(label_hnd, &fcon, newpath, S_IFSOCK); + } + + if (r < 0) { + /* No context specified by the policy? Proceed without setting it */ + if (errno == ENOENT) + goto skipped; + + log_enforcing("Failed to determine SELinux security context for %s: %m", path); + if (security_getenforce() > 0) + return -errno; + + } else { + if (setfscreatecon(fcon) < 0) { + log_enforcing("Failed to set SELinux security context %s for %s: %m", fcon, path); + if (security_getenforce() > 0) + return -errno; + } else + context_changed = true; + } + + r = bind(fd, addr, addrlen) < 0 ? -errno : 0; + + if (context_changed) + setfscreatecon(NULL); + + return r; + +skipped: +#endif + if (bind(fd, addr, addrlen) < 0) + return -errno; + + return 0; +} diff --git a/src/libbasic/selinux-util.h b/src/libbasic/selinux-util.h new file mode 100644 index 0000000000..27e8edb41b --- /dev/null +++ b/src/libbasic/selinux-util.h @@ -0,0 +1,51 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#include "macro.h" + +bool mac_selinux_use(void); +bool mac_selinux_have(void); +void mac_selinux_retest(void); + +int mac_selinux_init(const char *prefix); +void mac_selinux_finish(void); + +int mac_selinux_fix(const char *path, bool ignore_enoent, bool ignore_erofs); +int mac_selinux_apply(const char *path, const char *label); + +int mac_selinux_get_create_label_from_exe(const char *exe, char **label); +int mac_selinux_get_our_label(char **label); +int mac_selinux_get_child_mls_label(int socket_fd, const char *exe, const char *exec_label, char **label); +char* mac_selinux_free(char *label); + +int mac_selinux_create_file_prepare(const char *path, mode_t mode); +void mac_selinux_create_file_clear(void); + +int mac_selinux_create_socket_prepare(const char *label); +void mac_selinux_create_socket_clear(void); + +int mac_selinux_bind(int fd, const struct sockaddr *addr, socklen_t addrlen); + +DEFINE_TRIVIAL_CLEANUP_FUNC(char*, mac_selinux_free); diff --git a/src/libbasic/set.h b/src/libbasic/set.h new file mode 100644 index 0000000000..2bff5062da --- /dev/null +++ b/src/libbasic/set.h @@ -0,0 +1,133 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "hashmap.h" +#include "macro.h" + +Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS) + +static inline Set *set_free(Set *s) { + internal_hashmap_free(HASHMAP_BASE(s)); + return NULL; +} + +static inline Set *set_free_free(Set *s) { + internal_hashmap_free_free(HASHMAP_BASE(s)); + return NULL; +} + +/* no set_free_free_free */ + +static inline Set *set_copy(Set *s) { + return (Set*) internal_hashmap_copy(HASHMAP_BASE(s)); +} + +int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); +#define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) + +int set_put(Set *s, const void *key); +/* no set_update */ +/* no set_replace */ +static inline void *set_get(Set *s, void *key) { + return internal_hashmap_get(HASHMAP_BASE(s), key); +} +/* no set_get2 */ + +static inline bool set_contains(Set *s, const void *key) { + return internal_hashmap_contains(HASHMAP_BASE(s), key); +} + +static inline void *set_remove(Set *s, const void *key) { + return internal_hashmap_remove(HASHMAP_BASE(s), key); +} + +/* no set_remove2 */ +/* no set_remove_value */ +int set_remove_and_put(Set *s, const void *old_key, const void *new_key); +/* no set_remove_and_replace */ +int set_merge(Set *s, Set *other); + +static inline int set_reserve(Set *h, unsigned entries_add) { + return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add); +} + +static inline int set_move(Set *s, Set *other) { + return internal_hashmap_move(HASHMAP_BASE(s), HASHMAP_BASE(other)); +} + +static inline int set_move_one(Set *s, Set *other, const void *key) { + return internal_hashmap_move_one(HASHMAP_BASE(s), HASHMAP_BASE(other), key); +} + +static inline unsigned set_size(Set *s) { + return internal_hashmap_size(HASHMAP_BASE(s)); +} + +static inline bool set_isempty(Set *s) { + return set_size(s) == 0; +} + +static inline unsigned set_buckets(Set *s) { + return internal_hashmap_buckets(HASHMAP_BASE(s)); +} + +bool set_iterate(Set *s, Iterator *i, void **value); + +static inline void set_clear(Set *s) { + internal_hashmap_clear(HASHMAP_BASE(s)); +} + +static inline void set_clear_free(Set *s) { + internal_hashmap_clear_free(HASHMAP_BASE(s)); +} + +/* no set_clear_free_free */ + +static inline void *set_steal_first(Set *s) { + return internal_hashmap_steal_first(HASHMAP_BASE(s)); +} + +/* no set_steal_first_key */ +/* no set_first_key */ + +static inline void *set_first(Set *s) { + return internal_hashmap_first(HASHMAP_BASE(s)); +} + +/* no set_next */ + +static inline char **set_get_strv(Set *s) { + return internal_hashmap_get_strv(HASHMAP_BASE(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); + +#define SET_FOREACH(e, s, i) \ + for ((i) = ITERATOR_FIRST; set_iterate((s), &(i), (void**)&(e)); ) + +DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free); +DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free); + +#define _cleanup_set_free_ _cleanup_(set_freep) +#define _cleanup_set_free_free_ _cleanup_(set_free_freep) diff --git a/src/libbasic/sigbus.c b/src/libbasic/sigbus.c new file mode 100644 index 0000000000..0ce4f75684 --- /dev/null +++ b/src/libbasic/sigbus.c @@ -0,0 +1,152 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include + +#include "macro.h" +#include "sigbus.h" +#include "util.h" + +#define SIGBUS_QUEUE_MAX 64 + +static struct sigaction old_sigaction; +static unsigned n_installed = 0; + +/* We maintain a fixed size list of page addresses that triggered a + SIGBUS. We access with list with atomic operations, so that we + don't have to deal with locks between signal handler and main + programs in possibly multiple threads. */ + +static void* volatile sigbus_queue[SIGBUS_QUEUE_MAX]; +static volatile sig_atomic_t n_sigbus_queue = 0; + +static void sigbus_push(void *addr) { + unsigned u; + + assert(addr); + + /* Find a free place, increase the number of entries and leave, if we can */ + for (u = 0; u < SIGBUS_QUEUE_MAX; u++) + if (__sync_bool_compare_and_swap(&sigbus_queue[u], NULL, addr)) { + __sync_fetch_and_add(&n_sigbus_queue, 1); + return; + } + + /* If we can't, make sure the queue size is out of bounds, to + * mark it as overflow */ + for (;;) { + unsigned c; + + __sync_synchronize(); + c = n_sigbus_queue; + + if (c > SIGBUS_QUEUE_MAX) /* already overflow */ + return; + + if (__sync_bool_compare_and_swap(&n_sigbus_queue, c, c + SIGBUS_QUEUE_MAX)) + return; + } +} + +int sigbus_pop(void **ret) { + assert(ret); + + for (;;) { + unsigned u, c; + + __sync_synchronize(); + c = n_sigbus_queue; + + if (_likely_(c == 0)) + return 0; + + if (_unlikely_(c >= SIGBUS_QUEUE_MAX)) + return -EOVERFLOW; + + for (u = 0; u < SIGBUS_QUEUE_MAX; u++) { + void *addr; + + addr = sigbus_queue[u]; + if (!addr) + continue; + + if (__sync_bool_compare_and_swap(&sigbus_queue[u], addr, NULL)) { + __sync_fetch_and_sub(&n_sigbus_queue, 1); + *ret = addr; + return 1; + } + } + } +} + +static void sigbus_handler(int sn, siginfo_t *si, void *data) { + unsigned long ul; + void *aligned; + + assert(sn == SIGBUS); + assert(si); + + if (si->si_code != BUS_ADRERR || !si->si_addr) { + assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0); + raise(SIGBUS); + return; + } + + ul = (unsigned long) si->si_addr; + ul = ul / page_size(); + ul = ul * page_size(); + aligned = (void*) ul; + + /* Let's remember which address failed */ + sigbus_push(aligned); + + /* Replace mapping with an anonymous page, so that the + * execution can continue, however with a zeroed out page */ + assert_se(mmap(aligned, page_size(), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0) == aligned); +} + +void sigbus_install(void) { + struct sigaction sa = { + .sa_sigaction = sigbus_handler, + .sa_flags = SA_SIGINFO, + }; + + n_installed++; + + if (n_installed == 1) + assert_se(sigaction(SIGBUS, &sa, &old_sigaction) == 0); + + return; +} + +void sigbus_reset(void) { + + if (n_installed <= 0) + return; + + n_installed--; + + if (n_installed == 0) + assert_se(sigaction(SIGBUS, &old_sigaction, NULL) == 0); + + return; +} diff --git a/src/libbasic/sigbus.h b/src/libbasic/sigbus.h new file mode 100644 index 0000000000..cce9eb201b --- /dev/null +++ b/src/libbasic/sigbus.h @@ -0,0 +1,25 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#pragma once + +void sigbus_install(void); +void sigbus_reset(void); + +int sigbus_pop(void **ret); diff --git a/src/libbasic/signal-util.c b/src/libbasic/signal-util.c new file mode 100644 index 0000000000..e3047b209b --- /dev/null +++ b/src/libbasic/signal-util.c @@ -0,0 +1,278 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include +#include + +#include "macro.h" +#include "parse-util.h" +#include "signal-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" + +int reset_all_signal_handlers(void) { + static const struct sigaction sa = { + .sa_handler = SIG_DFL, + .sa_flags = SA_RESTART, + }; + int sig, r = 0; + + for (sig = 1; sig < _NSIG; sig++) { + + /* These two cannot be caught... */ + if (sig == SIGKILL || sig == SIGSTOP) + continue; + + /* On Linux the first two RT signals are reserved by + * glibc, and sigaction() will return EINVAL for them. */ + if ((sigaction(sig, &sa, NULL) < 0)) + if (errno != EINVAL && r >= 0) + r = -errno; + } + + return r; +} + +int reset_signal_mask(void) { + sigset_t ss; + + if (sigemptyset(&ss) < 0) + return -errno; + + if (sigprocmask(SIG_SETMASK, &ss, NULL) < 0) + return -errno; + + return 0; +} + +static int sigaction_many_ap(const struct sigaction *sa, int sig, va_list ap) { + int r = 0; + + /* negative signal ends the list. 0 signal is skipped. */ + + if (sig < 0) + return 0; + + if (sig > 0) { + if (sigaction(sig, sa, NULL) < 0) + r = -errno; + } + + while ((sig = va_arg(ap, int)) >= 0) { + + if (sig == 0) + continue; + + if (sigaction(sig, sa, NULL) < 0) { + if (r >= 0) + r = -errno; + } + } + + return r; +} + +int sigaction_many(const struct sigaction *sa, ...) { + va_list ap; + int r; + + va_start(ap, sa); + r = sigaction_many_ap(sa, 0, ap); + va_end(ap); + + return r; +} + +int ignore_signals(int sig, ...) { + + static const struct sigaction sa = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + + va_list ap; + int r; + + va_start(ap, sig); + r = sigaction_many_ap(&sa, sig, ap); + va_end(ap); + + return r; +} + +int default_signals(int sig, ...) { + + static const struct sigaction sa = { + .sa_handler = SIG_DFL, + .sa_flags = SA_RESTART, + }; + + va_list ap; + int r; + + va_start(ap, sig); + r = sigaction_many_ap(&sa, sig, ap); + va_end(ap); + + return r; +} + +static int sigset_add_many_ap(sigset_t *ss, va_list ap) { + int sig, r = 0; + + assert(ss); + + while ((sig = va_arg(ap, int)) >= 0) { + + if (sig == 0) + continue; + + if (sigaddset(ss, sig) < 0) { + if (r >= 0) + r = -errno; + } + } + + return r; +} + +int sigset_add_many(sigset_t *ss, ...) { + va_list ap; + int r; + + va_start(ap, ss); + r = sigset_add_many_ap(ss, ap); + va_end(ap); + + return r; +} + +int sigprocmask_many(int how, sigset_t *old, ...) { + va_list ap; + sigset_t ss; + int r; + + if (sigemptyset(&ss) < 0) + return -errno; + + va_start(ap, old); + r = sigset_add_many_ap(&ss, ap); + va_end(ap); + + if (r < 0) + return r; + + if (sigprocmask(how, &ss, old) < 0) + return -errno; + + return 0; +} + +static const char *const __signal_table[] = { + [SIGHUP] = "HUP", + [SIGINT] = "INT", + [SIGQUIT] = "QUIT", + [SIGILL] = "ILL", + [SIGTRAP] = "TRAP", + [SIGABRT] = "ABRT", + [SIGBUS] = "BUS", + [SIGFPE] = "FPE", + [SIGKILL] = "KILL", + [SIGUSR1] = "USR1", + [SIGSEGV] = "SEGV", + [SIGUSR2] = "USR2", + [SIGPIPE] = "PIPE", + [SIGALRM] = "ALRM", + [SIGTERM] = "TERM", +#ifdef SIGSTKFLT + [SIGSTKFLT] = "STKFLT", /* Linux on SPARC doesn't know SIGSTKFLT */ +#endif + [SIGCHLD] = "CHLD", + [SIGCONT] = "CONT", + [SIGSTOP] = "STOP", + [SIGTSTP] = "TSTP", + [SIGTTIN] = "TTIN", + [SIGTTOU] = "TTOU", + [SIGURG] = "URG", + [SIGXCPU] = "XCPU", + [SIGXFSZ] = "XFSZ", + [SIGVTALRM] = "VTALRM", + [SIGPROF] = "PROF", + [SIGWINCH] = "WINCH", + [SIGIO] = "IO", + [SIGPWR] = "PWR", + [SIGSYS] = "SYS" +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(__signal, int); + +const char *signal_to_string(int signo) { + static thread_local char buf[sizeof("RTMIN+")-1 + DECIMAL_STR_MAX(int) + 1]; + const char *name; + + name = __signal_to_string(signo); + if (name) + return name; + + if (signo >= SIGRTMIN && signo <= SIGRTMAX) + xsprintf(buf, "RTMIN+%d", signo - SIGRTMIN); + else + xsprintf(buf, "%d", signo); + + return buf; +} + +int signal_from_string(const char *s) { + int signo; + int offset = 0; + unsigned u; + + signo = __signal_from_string(s); + if (signo > 0) + return signo; + + if (startswith(s, "RTMIN+")) { + s += 6; + offset = SIGRTMIN; + } + if (safe_atou(s, &u) >= 0) { + signo = (int) u + offset; + if (signo > 0 && signo < _NSIG) + return signo; + } + return -EINVAL; +} + +int signal_from_string_try_harder(const char *s) { + int signo; + assert(s); + + signo = signal_from_string(s); + if (signo <= 0) + if (startswith(s, "SIG")) + return signal_from_string(s+3); + + return signo; +} + +void nop_signal_handler(int sig) { + /* nothing here */ +} diff --git a/src/libbasic/signal-util.h b/src/libbasic/signal-util.h new file mode 100644 index 0000000000..72b10e8712 --- /dev/null +++ b/src/libbasic/signal-util.h @@ -0,0 +1,52 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2015 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 . +***/ + +#include + +#include "macro.h" + +int reset_all_signal_handlers(void); +int reset_signal_mask(void); + +int ignore_signals(int sig, ...); +int default_signals(int sig, ...); +int sigaction_many(const struct sigaction *sa, ...); + +int sigset_add_many(sigset_t *ss, ...); +int sigprocmask_many(int how, sigset_t *old, ...); + +const char *signal_to_string(int i) _const_; +int signal_from_string(const char *s) _pure_; + +int signal_from_string_try_harder(const char *s); + +void nop_signal_handler(int sig); + +static inline void block_signals_reset(sigset_t *ss) { + assert_se(sigprocmask(SIG_SETMASK, ss, NULL) >= 0); +} + +#define BLOCK_SIGNALS(...) \ + _cleanup_(block_signals_reset) sigset_t _saved_sigset = ({ \ + sigset_t t; \ + assert_se(sigprocmask_many(SIG_BLOCK, &t, __VA_ARGS__, -1) >= 0); \ + t; \ + }) diff --git a/src/libbasic/siphash24.c b/src/libbasic/siphash24.c new file mode 100644 index 0000000000..060e8ba387 --- /dev/null +++ b/src/libbasic/siphash24.c @@ -0,0 +1,191 @@ +/* + SipHash reference C implementation + + Written in 2012 by + Jean-Philippe Aumasson + Daniel J. Bernstein + + To the extent possible under law, the author(s) have dedicated all copyright + and related and neighboring rights to this software to the public domain + worldwide. This software is distributed without any warranty. + + You should have received a copy of the CC0 Public Domain Dedication along with + this software. If not, see . + + (Minimal changes made by Lennart Poettering, to make clean for inclusion in systemd) + (Refactored by Tom Gundersen to split up in several functions and follow systemd + coding style) +*/ + +#include "macro.h" +#include "siphash24.h" +#include "unaligned.h" + +static inline uint64_t rotate_left(uint64_t x, uint8_t b) { + assert(b < 64); + + return (x << b) | (x >> (64 - b)); +} + +static inline void sipround(struct siphash *state) { + assert(state); + + state->v0 += state->v1; + state->v1 = rotate_left(state->v1, 13); + state->v1 ^= state->v0; + state->v0 = rotate_left(state->v0, 32); + state->v2 += state->v3; + state->v3 = rotate_left(state->v3, 16); + state->v3 ^= state->v2; + state->v0 += state->v3; + state->v3 = rotate_left(state->v3, 21); + state->v3 ^= state->v0; + state->v2 += state->v1; + state->v1 = rotate_left(state->v1, 17); + state->v1 ^= state->v2; + state->v2 = rotate_left(state->v2, 32); +} + +void siphash24_init(struct siphash *state, const uint8_t k[16]) { + uint64_t k0, k1; + + assert(state); + assert(k); + + k0 = unaligned_read_le64(k); + k1 = unaligned_read_le64(k + 8); + + *state = (struct siphash) { + /* "somepseudorandomlygeneratedbytes" */ + .v0 = 0x736f6d6570736575ULL ^ k0, + .v1 = 0x646f72616e646f6dULL ^ k1, + .v2 = 0x6c7967656e657261ULL ^ k0, + .v3 = 0x7465646279746573ULL ^ k1, + .padding = 0, + .inlen = 0, + }; +} + +void siphash24_compress(const void *_in, size_t inlen, struct siphash *state) { + + const uint8_t *in = _in; + const uint8_t *end = in + inlen; + size_t left = state->inlen & 7; + uint64_t m; + + assert(in); + assert(state); + + /* Update total length */ + state->inlen += inlen; + + /* If padding exists, fill it out */ + if (left > 0) { + for ( ; in < end && left < 8; in ++, left ++) + state->padding |= ((uint64_t) *in) << (left * 8); + + if (in == end && left < 8) + /* We did not have enough input to fill out the padding completely */ + return; + +#ifdef DEBUG + printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); + printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); + printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); + printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); + printf("(%3zu) compress padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t)state->padding); +#endif + + state->v3 ^= state->padding; + sipround(state); + sipround(state); + state->v0 ^= state->padding; + + state->padding = 0; + } + + end -= (state->inlen % sizeof(uint64_t)); + + for ( ; in < end; in += 8) { + m = unaligned_read_le64(in); +#ifdef DEBUG + printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); + printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); + printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); + printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); + printf("(%3zu) compress %08x %08x\n", state->inlen, (uint32_t) (m >> 32), (uint32_t) m); +#endif + state->v3 ^= m; + sipround(state); + sipround(state); + state->v0 ^= m; + } + + left = state->inlen & 7; + switch (left) { + case 7: + state->padding |= ((uint64_t) in[6]) << 48; + case 6: + state->padding |= ((uint64_t) in[5]) << 40; + case 5: + state->padding |= ((uint64_t) in[4]) << 32; + case 4: + state->padding |= ((uint64_t) in[3]) << 24; + case 3: + state->padding |= ((uint64_t) in[2]) << 16; + case 2: + state->padding |= ((uint64_t) in[1]) << 8; + case 1: + state->padding |= ((uint64_t) in[0]); + case 0: + break; + } +} + +uint64_t siphash24_finalize(struct siphash *state) { + uint64_t b; + + assert(state); + + b = state->padding | (((uint64_t) state->inlen) << 56); + +#ifdef DEBUG + printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); + printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); + printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); + printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); + printf("(%3zu) padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t) state->padding); +#endif + + state->v3 ^= b; + sipround(state); + sipround(state); + state->v0 ^= b; + +#ifdef DEBUG + printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0); + printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1); + printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2); + printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3); +#endif + state->v2 ^= 0xff; + + sipround(state); + sipround(state); + sipround(state); + sipround(state); + + return state->v0 ^ state->v1 ^ state->v2 ^ state->v3; +} + +uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]) { + struct siphash state; + + assert(in); + assert(k); + + siphash24_init(&state, k); + siphash24_compress(in, inlen, &state); + + return siphash24_finalize(&state); +} diff --git a/src/libbasic/siphash24.h b/src/libbasic/siphash24.h new file mode 100644 index 0000000000..54e2420cc6 --- /dev/null +++ b/src/libbasic/siphash24.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include +#include + +struct siphash { + uint64_t v0; + uint64_t v1; + uint64_t v2; + uint64_t v3; + uint64_t padding; + size_t inlen; +}; + +void siphash24_init(struct siphash *state, const uint8_t k[16]); +void siphash24_compress(const void *in, size_t inlen, struct siphash *state); +#define siphash24_compress_byte(byte, state) siphash24_compress((const uint8_t[]) { (byte) }, 1, (state)) + +uint64_t siphash24_finalize(struct siphash *state); + +uint64_t siphash24(const void *in, size_t inlen, const uint8_t k[16]); diff --git a/src/libbasic/smack-util.c b/src/libbasic/smack-util.c new file mode 100644 index 0000000000..3a3df987df --- /dev/null +++ b/src/libbasic/smack-util.c @@ -0,0 +1,241 @@ +/*** + This file is part of systemd. + + Copyright 2013 Intel Corporation + + Author: Auke Kok + + 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fileio.h" +#include "log.h" +#include "macro.h" +#include "path-util.h" +#include "process-util.h" +#include "smack-util.h" +#include "string-table.h" +#include "xattr-util.h" + +#ifdef HAVE_SMACK +bool mac_smack_use(void) { + static int cached_use = -1; + + if (cached_use < 0) + cached_use = access("/sys/fs/smackfs/", F_OK) >= 0; + + return cached_use; +} + +static const char* const smack_attr_table[_SMACK_ATTR_MAX] = { + [SMACK_ATTR_ACCESS] = "security.SMACK64", + [SMACK_ATTR_EXEC] = "security.SMACK64EXEC", + [SMACK_ATTR_MMAP] = "security.SMACK64MMAP", + [SMACK_ATTR_TRANSMUTE] = "security.SMACK64TRANSMUTE", + [SMACK_ATTR_IPIN] = "security.SMACK64IPIN", + [SMACK_ATTR_IPOUT] = "security.SMACK64IPOUT", +}; + +DEFINE_STRING_TABLE_LOOKUP(smack_attr, SmackAttr); + +int mac_smack_read(const char *path, SmackAttr attr, char **label) { + assert(path); + assert(attr >= 0 && attr < _SMACK_ATTR_MAX); + assert(label); + + if (!mac_smack_use()) + return 0; + + return getxattr_malloc(path, smack_attr_to_string(attr), label, true); +} + +int mac_smack_read_fd(int fd, SmackAttr attr, char **label) { + assert(fd >= 0); + assert(attr >= 0 && attr < _SMACK_ATTR_MAX); + assert(label); + + if (!mac_smack_use()) + return 0; + + return fgetxattr_malloc(fd, smack_attr_to_string(attr), label); +} + +int mac_smack_apply(const char *path, SmackAttr attr, const char *label) { + int r; + + assert(path); + assert(attr >= 0 && attr < _SMACK_ATTR_MAX); + + if (!mac_smack_use()) + return 0; + + if (label) + r = lsetxattr(path, smack_attr_to_string(attr), label, strlen(label), 0); + else + r = lremovexattr(path, smack_attr_to_string(attr)); + if (r < 0) + return -errno; + + return 0; +} + +int mac_smack_apply_fd(int fd, SmackAttr attr, const char *label) { + int r; + + assert(fd >= 0); + assert(attr >= 0 && attr < _SMACK_ATTR_MAX); + + if (!mac_smack_use()) + return 0; + + if (label) + r = fsetxattr(fd, smack_attr_to_string(attr), label, strlen(label), 0); + else + r = fremovexattr(fd, smack_attr_to_string(attr)); + if (r < 0) + return -errno; + + return 0; +} + +int mac_smack_apply_pid(pid_t pid, const char *label) { + const char *p; + int r = 0; + + assert(label); + + if (!mac_smack_use()) + return 0; + + p = procfs_file_alloca(pid, "attr/current"); + r = write_string_file(p, label, 0); + if (r < 0) + return r; + + return r; +} + +int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { + struct stat st; + int r = 0; + + assert(path); + + if (!mac_smack_use()) + return 0; + + /* + * Path must be in /dev and must exist + */ + if (!path_startswith(path, "/dev")) + return 0; + + r = lstat(path, &st); + if (r >= 0) { + const char *label; + + /* + * Label directories and character devices "*". + * Label symlinks "_". + * Don't change anything else. + */ + + if (S_ISDIR(st.st_mode)) + label = SMACK_STAR_LABEL; + else if (S_ISLNK(st.st_mode)) + label = SMACK_FLOOR_LABEL; + else if (S_ISCHR(st.st_mode)) + label = SMACK_STAR_LABEL; + else + return 0; + + r = lsetxattr(path, "security.SMACK64", label, strlen(label), 0); + + /* If the FS doesn't support labels, then exit without warning */ + if (r < 0 && errno == EOPNOTSUPP) + return 0; + } + + if (r < 0) { + /* Ignore ENOENT in some cases */ + if (ignore_enoent && errno == ENOENT) + return 0; + + if (ignore_erofs && errno == EROFS) + return 0; + + r = log_debug_errno(errno, "Unable to fix SMACK label of %s: %m", path); + } + + return r; +} + +int mac_smack_copy(const char *dest, const char *src) { + int r = 0; + _cleanup_free_ char *label = NULL; + + assert(dest); + assert(src); + + r = mac_smack_read(src, SMACK_ATTR_ACCESS, &label); + if (r < 0) + return r; + + r = mac_smack_apply(dest, SMACK_ATTR_ACCESS, label); + if (r < 0) + return r; + + return r; +} + +#else +bool mac_smack_use(void) { + return false; +} + +int mac_smack_read(const char *path, SmackAttr attr, char **label) { + return -EOPNOTSUPP; +} + +int mac_smack_read_fd(int fd, SmackAttr attr, char **label) { + return -EOPNOTSUPP; +} + +int mac_smack_apply(const char *path, SmackAttr attr, const char *label) { + return 0; +} + +int mac_smack_apply_fd(int fd, SmackAttr attr, const char *label) { + return 0; +} + +int mac_smack_apply_pid(pid_t pid, const char *label) { + return 0; +} + +int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs) { + return 0; +} + +int mac_smack_copy(const char *dest, const char *src) { + return 0; +} +#endif diff --git a/src/libbasic/smack-util.h b/src/libbasic/smack-util.h new file mode 100644 index 0000000000..f90ba0a027 --- /dev/null +++ b/src/libbasic/smack-util.h @@ -0,0 +1,54 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Intel Corporation + + Author: Auke Kok + + 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 . +***/ + +#include +#include + +#include "macro.h" + +#define SMACK_FLOOR_LABEL "_" +#define SMACK_STAR_LABEL "*" + +typedef enum SmackAttr { + SMACK_ATTR_ACCESS = 0, + SMACK_ATTR_EXEC = 1, + SMACK_ATTR_MMAP = 2, + SMACK_ATTR_TRANSMUTE = 3, + SMACK_ATTR_IPIN = 4, + SMACK_ATTR_IPOUT = 5, + _SMACK_ATTR_MAX, + _SMACK_ATTR_INVALID = -1, +} SmackAttr; + +bool mac_smack_use(void); + +int mac_smack_fix(const char *path, bool ignore_enoent, bool ignore_erofs); + +const char* smack_attr_to_string(SmackAttr i) _const_; +SmackAttr smack_attr_from_string(const char *s) _pure_; +int mac_smack_read(const char *path, SmackAttr attr, char **label); +int mac_smack_read_fd(int fd, SmackAttr attr, char **label); +int mac_smack_apply(const char *path, SmackAttr attr, const char *label); +int mac_smack_apply_fd(int fd, SmackAttr attr, const char *label); +int mac_smack_apply_pid(pid_t pid, const char *label); +int mac_smack_copy(const char *dest, const char *src); diff --git a/src/libbasic/socket-label.c b/src/libbasic/socket-label.c new file mode 100644 index 0000000000..35e9573aa4 --- /dev/null +++ b/src/libbasic/socket-label.c @@ -0,0 +1,174 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "selinux-util.h" +#include "socket-util.h" + +int socket_address_listen( + const SocketAddress *a, + int flags, + int backlog, + SocketAddressBindIPv6Only only, + const char *bind_to_device, + bool reuse_port, + bool free_bind, + bool transparent, + mode_t directory_mode, + mode_t socket_mode, + const char *label) { + + _cleanup_close_ int fd = -1; + int r, one; + + assert(a); + + r = socket_address_verify(a); + if (r < 0) + return r; + + if (socket_address_family(a) == AF_INET6 && !socket_ipv6_is_supported()) + return -EAFNOSUPPORT; + + if (label) { + r = mac_selinux_create_socket_prepare(label); + if (r < 0) + return r; + } + + fd = socket(socket_address_family(a), a->type | flags, a->protocol); + r = fd < 0 ? -errno : 0; + + if (label) + mac_selinux_create_socket_clear(); + + if (r < 0) + return r; + + if (socket_address_family(a) == AF_INET6 && only != SOCKET_ADDRESS_DEFAULT) { + int flag = only == SOCKET_ADDRESS_IPV6_ONLY; + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &flag, sizeof(flag)) < 0) + return -errno; + } + + if (socket_address_family(a) == AF_INET || socket_address_family(a) == AF_INET6) { + if (bind_to_device) + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, bind_to_device, strlen(bind_to_device)+1) < 0) + return -errno; + + if (reuse_port) { + one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) < 0) + log_warning_errno(errno, "SO_REUSEPORT failed: %m"); + } + + if (free_bind) { + one = 1; + if (setsockopt(fd, IPPROTO_IP, IP_FREEBIND, &one, sizeof(one)) < 0) + log_warning_errno(errno, "IP_FREEBIND failed: %m"); + } + + if (transparent) { + one = 1; + if (setsockopt(fd, IPPROTO_IP, IP_TRANSPARENT, &one, sizeof(one)) < 0) + log_warning_errno(errno, "IP_TRANSPARENT failed: %m"); + } + } + + one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) < 0) + return -errno; + + if (socket_address_family(a) == AF_UNIX && a->sockaddr.un.sun_path[0] != 0) { + mode_t old_mask; + + /* Create parents */ + mkdir_parents_label(a->sockaddr.un.sun_path, directory_mode); + + /* Enforce the right access mode for the socket */ + old_mask = umask(~ socket_mode); + + r = mac_selinux_bind(fd, &a->sockaddr.sa, a->size); + + if (r < 0 && errno == EADDRINUSE) { + /* Unlink and try again */ + unlink(a->sockaddr.un.sun_path); + r = bind(fd, &a->sockaddr.sa, a->size); + } + + umask(old_mask); + } else + r = bind(fd, &a->sockaddr.sa, a->size); + + if (r < 0) + return -errno; + + if (socket_address_can_accept(a)) + if (listen(fd, backlog) < 0) + return -errno; + + r = fd; + fd = -1; + + return r; +} + +int make_socket_fd(int log_level, const char* address, int type, int flags) { + SocketAddress a; + int fd, r; + + r = socket_address_parse(&a, address); + if (r < 0) + return log_error_errno(r, "Failed to parse socket address \"%s\": %m", address); + + a.type = type; + + fd = socket_address_listen(&a, type | flags, SOMAXCONN, SOCKET_ADDRESS_DEFAULT, + NULL, false, false, false, 0755, 0644, NULL); + if (fd < 0 || log_get_max_level() >= log_level) { + _cleanup_free_ char *p = NULL; + + r = socket_address_print(&a, &p); + if (r < 0) + return log_error_errno(r, "socket_address_print(): %m"); + + if (fd < 0) + log_error_errno(fd, "Failed to listen on %s: %m", p); + else + log_full(log_level, "Listening on %s", p); + } + + return fd; +} diff --git a/src/libbasic/socket-util.c b/src/libbasic/socket-util.c new file mode 100644 index 0000000000..58512686e3 --- /dev/null +++ b/src/libbasic/socket-util.c @@ -0,0 +1,972 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "parse-util.h" +#include "path-util.h" +#include "socket-util.h" +#include "string-table.h" +#include "string-util.h" +#include "user-util.h" +#include "util.h" + +int socket_address_parse(SocketAddress *a, const char *s) { + char *e, *n; + unsigned u; + int r; + + assert(a); + assert(s); + + zero(*a); + a->type = SOCK_STREAM; + + if (*s == '[') { + /* IPv6 in [x:.....:z]:p notation */ + + e = strchr(s+1, ']'); + if (!e) + return -EINVAL; + + n = strndupa(s+1, e-s-1); + + errno = 0; + if (inet_pton(AF_INET6, n, &a->sockaddr.in6.sin6_addr) <= 0) + return errno > 0 ? -errno : -EINVAL; + + e++; + if (*e != ':') + return -EINVAL; + + e++; + r = safe_atou(e, &u); + if (r < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + a->sockaddr.in6.sin6_family = AF_INET6; + a->sockaddr.in6.sin6_port = htons((uint16_t) u); + a->size = sizeof(struct sockaddr_in6); + + } else if (*s == '/') { + /* AF_UNIX socket */ + + size_t l; + + l = strlen(s); + if (l >= sizeof(a->sockaddr.un.sun_path)) + return -EINVAL; + + a->sockaddr.un.sun_family = AF_UNIX; + memcpy(a->sockaddr.un.sun_path, s, l); + a->size = offsetof(struct sockaddr_un, sun_path) + l + 1; + + } else if (*s == '@') { + /* Abstract AF_UNIX socket */ + size_t l; + + l = strlen(s+1); + if (l >= sizeof(a->sockaddr.un.sun_path) - 1) + return -EINVAL; + + a->sockaddr.un.sun_family = AF_UNIX; + memcpy(a->sockaddr.un.sun_path+1, s+1, l); + a->size = offsetof(struct sockaddr_un, sun_path) + 1 + l; + + } else { + e = strchr(s, ':'); + if (e) { + r = safe_atou(e+1, &u); + if (r < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + n = strndupa(s, e-s); + + /* IPv4 in w.x.y.z:p notation? */ + r = inet_pton(AF_INET, n, &a->sockaddr.in.sin_addr); + if (r < 0) + return -errno; + + 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->size = sizeof(struct sockaddr_in); + } else { + unsigned idx; + + if (strlen(n) > IF_NAMESIZE-1) + return -EINVAL; + + /* Uh, our last resort, an interface name */ + idx = if_nametoindex(n); + if (idx == 0) + return -EINVAL; + + a->sockaddr.in6.sin6_family = AF_INET6; + a->sockaddr.in6.sin6_port = htons((uint16_t) u); + a->sockaddr.in6.sin6_scope_id = idx; + a->sockaddr.in6.sin6_addr = in6addr_any; + a->size = sizeof(struct sockaddr_in6); + } + } else { + + /* Just a port */ + r = safe_atou(s, &u); + if (r < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + 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_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_addr.s_addr = INADDR_ANY; + a->size = sizeof(struct sockaddr_in); + } + } + } + + return 0; +} + +int socket_address_parse_and_warn(SocketAddress *a, const char *s) { + SocketAddress b; + int r; + + /* Similar to socket_address_parse() but warns for IPv6 sockets when we don't support them. */ + + r = socket_address_parse(&b, s); + if (r < 0) + return r; + + if (!socket_ipv6_is_supported() && b.sockaddr.sa.sa_family == AF_INET6) { + log_warning("Binding to IPv6 address not available since kernel does not support IPv6."); + return -EAFNOSUPPORT; + } + + *a = b; + return 0; +} + +int socket_address_parse_netlink(SocketAddress *a, const char *s) { + int family; + unsigned group = 0; + _cleanup_free_ char *sfamily = NULL; + assert(a); + assert(s); + + zero(*a); + a->type = SOCK_RAW; + + errno = 0; + if (sscanf(s, "%ms %u", &sfamily, &group) < 1) + return errno > 0 ? -errno : -EINVAL; + + family = netlink_family_from_string(sfamily); + if (family < 0) + return -EINVAL; + + a->sockaddr.nl.nl_family = AF_NETLINK; + a->sockaddr.nl.nl_groups = group; + + a->type = SOCK_RAW; + a->size = sizeof(struct sockaddr_nl); + a->protocol = family; + + return 0; +} + +int socket_address_verify(const SocketAddress *a) { + assert(a); + + switch (socket_address_family(a)) { + + case AF_INET: + if (a->size != sizeof(struct sockaddr_in)) + return -EINVAL; + + if (a->sockaddr.in.sin_port == 0) + return -EINVAL; + + if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM) + return -EINVAL; + + return 0; + + case AF_INET6: + if (a->size != sizeof(struct sockaddr_in6)) + return -EINVAL; + + if (a->sockaddr.in6.sin6_port == 0) + return -EINVAL; + + if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM) + return -EINVAL; + + return 0; + + case AF_UNIX: + if (a->size < offsetof(struct sockaddr_un, sun_path)) + return -EINVAL; + + if (a->size > offsetof(struct sockaddr_un, sun_path)) { + + if (a->sockaddr.un.sun_path[0] != 0) { + char *e; + + /* path */ + e = memchr(a->sockaddr.un.sun_path, 0, sizeof(a->sockaddr.un.sun_path)); + if (!e) + return -EINVAL; + + if (a->size != offsetof(struct sockaddr_un, sun_path) + (e - a->sockaddr.un.sun_path) + 1) + return -EINVAL; + } + } + + if (a->type != SOCK_STREAM && a->type != SOCK_DGRAM && a->type != SOCK_SEQPACKET) + return -EINVAL; + + return 0; + + case AF_NETLINK: + + if (a->size != sizeof(struct sockaddr_nl)) + return -EINVAL; + + if (a->type != SOCK_RAW && a->type != SOCK_DGRAM) + return -EINVAL; + + return 0; + + default: + return -EAFNOSUPPORT; + } +} + +int socket_address_print(const SocketAddress *a, char **ret) { + int r; + + assert(a); + assert(ret); + + r = socket_address_verify(a); + if (r < 0) + return r; + + if (socket_address_family(a) == AF_NETLINK) { + _cleanup_free_ char *sfamily = NULL; + + r = netlink_family_to_string_alloc(a->protocol, &sfamily); + if (r < 0) + return r; + + r = asprintf(ret, "%s %u", sfamily, a->sockaddr.nl.nl_groups); + if (r < 0) + return -ENOMEM; + + return 0; + } + + return sockaddr_pretty(&a->sockaddr.sa, a->size, false, true, ret); +} + +bool socket_address_can_accept(const SocketAddress *a) { + assert(a); + + return + a->type == SOCK_STREAM || + a->type == SOCK_SEQPACKET; +} + +bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) { + assert(a); + assert(b); + + /* Invalid addresses are unequal to all */ + if (socket_address_verify(a) < 0 || + socket_address_verify(b) < 0) + return false; + + if (a->type != b->type) + return false; + + if (socket_address_family(a) != socket_address_family(b)) + return false; + + switch (socket_address_family(a)) { + + case AF_INET: + if (a->sockaddr.in.sin_addr.s_addr != b->sockaddr.in.sin_addr.s_addr) + return false; + + if (a->sockaddr.in.sin_port != b->sockaddr.in.sin_port) + return false; + + break; + + case AF_INET6: + if (memcmp(&a->sockaddr.in6.sin6_addr, &b->sockaddr.in6.sin6_addr, sizeof(a->sockaddr.in6.sin6_addr)) != 0) + return false; + + if (a->sockaddr.in6.sin6_port != b->sockaddr.in6.sin6_port) + return false; + + break; + + case AF_UNIX: + if (a->size <= offsetof(struct sockaddr_un, sun_path) || + b->size <= offsetof(struct sockaddr_un, sun_path)) + return false; + + if ((a->sockaddr.un.sun_path[0] == 0) != (b->sockaddr.un.sun_path[0] == 0)) + return false; + + if (a->sockaddr.un.sun_path[0]) { + if (!path_equal_or_files_same(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path)) + return false; + } else { + if (a->size != b->size) + return false; + + if (memcmp(a->sockaddr.un.sun_path, b->sockaddr.un.sun_path, a->size) != 0) + return false; + } + + break; + + case AF_NETLINK: + if (a->protocol != b->protocol) + return false; + + if (a->sockaddr.nl.nl_groups != b->sockaddr.nl.nl_groups) + return false; + + break; + + default: + /* Cannot compare, so we assume the addresses are different */ + return false; + } + + return true; +} + +bool socket_address_is(const SocketAddress *a, const char *s, int type) { + struct SocketAddress b; + + assert(a); + assert(s); + + if (socket_address_parse(&b, s) < 0) + return false; + + b.type = type; + + return socket_address_equal(a, &b); +} + +bool socket_address_is_netlink(const SocketAddress *a, const char *s) { + struct SocketAddress b; + + assert(a); + assert(s); + + if (socket_address_parse_netlink(&b, s) < 0) + return false; + + return socket_address_equal(a, &b); +} + +const char* socket_address_get_path(const SocketAddress *a) { + assert(a); + + if (socket_address_family(a) != AF_UNIX) + return NULL; + + if (a->sockaddr.un.sun_path[0] == 0) + return NULL; + + return a->sockaddr.un.sun_path; +} + +bool socket_ipv6_is_supported(void) { + if (access("/proc/net/sockstat6", F_OK) != 0) + return false; + + return true; +} + +bool socket_address_matches_fd(const SocketAddress *a, int fd) { + SocketAddress b; + socklen_t solen; + + assert(a); + assert(fd >= 0); + + b.size = sizeof(b.sockaddr); + if (getsockname(fd, &b.sockaddr.sa, &b.size) < 0) + return false; + + if (b.sockaddr.sa.sa_family != a->sockaddr.sa.sa_family) + return false; + + solen = sizeof(b.type); + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &b.type, &solen) < 0) + return false; + + if (b.type != a->type) + return false; + + if (a->protocol != 0) { + solen = sizeof(b.protocol); + if (getsockopt(fd, SOL_SOCKET, SO_PROTOCOL, &b.protocol, &solen) < 0) + return false; + + if (b.protocol != a->protocol) + return false; + } + + return socket_address_equal(a, &b); +} + +int sockaddr_port(const struct sockaddr *_sa) { + union sockaddr_union *sa = (union sockaddr_union*) _sa; + + assert(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); +} + +int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret) { + union sockaddr_union *sa = (union sockaddr_union*) _sa; + char *p; + int r; + + assert(sa); + assert(salen >= sizeof(sa->sa.sa_family)); + + switch (sa->sa.sa_family) { + + case AF_INET: { + uint32_t a; + + a = ntohl(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)); + else + r = asprintf(&p, + "%u.%u.%u.%u", + a >> 24, (a >> 16) & 0xFF, (a >> 8) & 0xFF, a & 0xFF); + if (r < 0) + return -ENOMEM; + break; + } + + case AF_INET6: { + static const unsigned char ipv4_prefix[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF + }; + + if (translate_ipv6 && + memcmp(&sa->in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0) { + const uint8_t *a = sa->in6.sin6_addr.s6_addr+12; + if (include_port) + r = asprintf(&p, + "%u.%u.%u.%u:%u", + a[0], a[1], a[2], a[3], + ntohs(sa->in6.sin6_port)); + else + r = asprintf(&p, + "%u.%u.%u.%u", + a[0], a[1], a[2], a[3]); + if (r < 0) + return -ENOMEM; + } else { + char a[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET6, &sa->in6.sin6_addr, a, sizeof(a)); + + if (include_port) { + r = asprintf(&p, + "[%s]:%u", + a, + ntohs(sa->in6.sin6_port)); + if (r < 0) + return -ENOMEM; + } else { + p = strdup(a); + if (!p) + return -ENOMEM; + } + } + + break; + } + + case AF_UNIX: + if (salen <= offsetof(struct sockaddr_un, sun_path)) { + p = strdup(""); + if (!p) + return -ENOMEM; + + } else if (sa->un.sun_path[0] == 0) { + /* abstract */ + + /* FIXME: We assume we can print the + * socket path here and that it hasn't + * more than one NUL byte. That is + * actually an invalid assumption */ + + p = new(char, sizeof(sa->un.sun_path)+1); + if (!p) + return -ENOMEM; + + p[0] = '@'; + memcpy(p+1, sa->un.sun_path+1, sizeof(sa->un.sun_path)-1); + p[sizeof(sa->un.sun_path)] = 0; + + } else { + p = strndup(sa->un.sun_path, sizeof(sa->un.sun_path)); + if (!p) + return -ENOMEM; + } + + break; + + default: + return -EOPNOTSUPP; + } + + + *ret = p; + return 0; +} + +int getpeername_pretty(int fd, bool include_port, char **ret) { + union sockaddr_union sa; + socklen_t salen = sizeof(sa); + int r; + + assert(fd >= 0); + assert(ret); + + if (getpeername(fd, &sa.sa, &salen) < 0) + return -errno; + + if (sa.sa.sa_family == AF_UNIX) { + struct ucred ucred = {}; + + /* UNIX connection sockets are anonymous, so let's use + * PID/UID as pretty credentials instead */ + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + if (asprintf(ret, "PID "PID_FMT"/UID "UID_FMT, ucred.pid, ucred.uid) < 0) + return -ENOMEM; + + return 0; + } + + /* For remote sockets we translate IPv6 addresses back to IPv4 + * if applicable, since that's nicer. */ + + return sockaddr_pretty(&sa.sa, salen, true, include_port, ret); +} + +int getsockname_pretty(int fd, char **ret) { + union sockaddr_union sa; + socklen_t salen = sizeof(sa); + + assert(fd >= 0); + assert(ret); + + if (getsockname(fd, &sa.sa, &salen) < 0) + return -errno; + + /* For local sockets we do not translate IPv6 addresses back + * to IPv6 if applicable, since this is usually used for + * listening sockets where the difference between IPv4 and + * IPv6 matters. */ + + return sockaddr_pretty(&sa.sa, salen, false, true, ret); +} + +int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret) { + int r; + char host[NI_MAXHOST], *ret; + + assert(_ret); + + r = getnameinfo(&sa->sa, salen, host, sizeof(host), NULL, 0, + NI_IDN|NI_IDN_USE_STD3_ASCII_RULES); + if (r != 0) { + int saved_errno = errno; + + r = sockaddr_pretty(&sa->sa, salen, true, true, &ret); + if (r < 0) + return r; + + log_debug_errno(saved_errno, "getnameinfo(%s) failed: %m", ret); + } else { + ret = strdup(host); + if (!ret) + return -ENOMEM; + } + + *_ret = ret; + return 0; +} + +int getnameinfo_pretty(int fd, char **ret) { + union sockaddr_union sa; + socklen_t salen = sizeof(sa); + + assert(fd >= 0); + assert(ret); + + if (getsockname(fd, &sa.sa, &salen) < 0) + return -errno; + + return socknameinfo_pretty(&sa, salen, ret); +} + +int socket_address_unlink(SocketAddress *a) { + assert(a); + + if (socket_address_family(a) != AF_UNIX) + return 0; + + if (a->sockaddr.un.sun_path[0] == 0) + return 0; + + if (unlink(a->sockaddr.un.sun_path) < 0) + return -errno; + + return 1; +} + +static const char* const netlink_family_table[] = { + [NETLINK_ROUTE] = "route", + [NETLINK_FIREWALL] = "firewall", + [NETLINK_INET_DIAG] = "inet-diag", + [NETLINK_NFLOG] = "nflog", + [NETLINK_XFRM] = "xfrm", + [NETLINK_SELINUX] = "selinux", + [NETLINK_ISCSI] = "iscsi", + [NETLINK_AUDIT] = "audit", + [NETLINK_FIB_LOOKUP] = "fib-lookup", + [NETLINK_CONNECTOR] = "connector", + [NETLINK_NETFILTER] = "netfilter", + [NETLINK_IP6_FW] = "ip6-fw", + [NETLINK_DNRTMSG] = "dnrtmsg", + [NETLINK_KOBJECT_UEVENT] = "kobject-uevent", + [NETLINK_GENERIC] = "generic", + [NETLINK_SCSITRANSPORT] = "scsitransport", + [NETLINK_ECRYPTFS] = "ecryptfs" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(netlink_family, int, INT_MAX); + +static const char* const socket_address_bind_ipv6_only_table[_SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX] = { + [SOCKET_ADDRESS_DEFAULT] = "default", + [SOCKET_ADDRESS_BOTH] = "both", + [SOCKET_ADDRESS_IPV6_ONLY] = "ipv6-only" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); + +bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b) { + assert(a); + assert(b); + + if (a->sa.sa_family != b->sa.sa_family) + return false; + + if (a->sa.sa_family == AF_INET) + return a->in.sin_addr.s_addr == b->in.sin_addr.s_addr; + + if (a->sa.sa_family == AF_INET6) + return memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr)) == 0; + + return false; +} + +int fd_inc_sndbuf(int fd, size_t n) { + int r, value; + socklen_t l = sizeof(value); + + r = getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, &l); + if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) + return 0; + + /* If we have the privileges we will ignore the kernel limit. */ + + value = (int) n; + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0) + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0) + return -errno; + + return 1; +} + +int fd_inc_rcvbuf(int fd, size_t n) { + int r, value; + socklen_t l = sizeof(value); + + r = getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, &l); + if (r >= 0 && l == sizeof(value) && (size_t) value >= n*2) + return 0; + + /* If we have the privileges we will ignore the kernel limit. */ + + value = (int) n; + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0) + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0) + return -errno; + return 1; +} + +static const char* const ip_tos_table[] = { + [IPTOS_LOWDELAY] = "low-delay", + [IPTOS_THROUGHPUT] = "throughput", + [IPTOS_RELIABILITY] = "reliability", + [IPTOS_LOWCOST] = "low-cost", +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(ip_tos, int, 0xff); + +int getpeercred(int fd, struct ucred *ucred) { + socklen_t n = sizeof(struct ucred); + struct ucred u; + int r; + + assert(fd >= 0); + assert(ucred); + + r = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &u, &n); + if (r < 0) + return -errno; + + if (n != sizeof(struct ucred)) + return -EIO; + + /* Check if the data is actually useful and not suppressed due + * to namespacing issues */ + if (u.pid <= 0) + return -ENODATA; + if (u.uid == UID_INVALID) + return -ENODATA; + if (u.gid == GID_INVALID) + return -ENODATA; + + *ucred = u; + return 0; +} + +int getpeersec(int fd, char **ret) { + socklen_t n = 64; + char *s; + int r; + + assert(fd >= 0); + assert(ret); + + s = new0(char, n); + if (!s) + return -ENOMEM; + + r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); + if (r < 0) { + free(s); + + if (errno != ERANGE) + return -errno; + + s = new0(char, n); + if (!s) + return -ENOMEM; + + r = getsockopt(fd, SOL_SOCKET, SO_PEERSEC, s, &n); + if (r < 0) { + free(s); + return -errno; + } + } + + if (isempty(s)) { + free(s); + return -EOPNOTSUPP; + } + + *ret = s; + return 0; +} + +int send_one_fd_sa( + int transport_fd, + int fd, + const struct sockaddr *sa, socklen_t len, + int flags) { + + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_name = (struct sockaddr*) sa, + .msg_namelen = len, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + + assert(transport_fd >= 0); + assert(fd >= 0); + + 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), &fd, sizeof(int)); + + mh.msg_controllen = CMSG_SPACE(sizeof(int)); + if (sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags) < 0) + return -errno; + + return 0; +} + +int receive_one_fd(int transport_fd, int flags) { + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg, *found = NULL; + + assert(transport_fd >= 0); + + /* + * Receive a single FD via @transport_fd. We don't care for + * the transport-type. We retrieve a single FD at most, so for + * packet-based transports, the caller must ensure to send + * only a single FD per packet. This is best used in + * combination with send_one_fd(). + */ + + if (recvmsg(transport_fd, &mh, MSG_NOSIGNAL | MSG_CMSG_CLOEXEC | flags) < 0) + return -errno; + + CMSG_FOREACH(cmsg, &mh) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS && + cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { + assert(!found); + found = cmsg; + break; + } + } + + if (!found) { + cmsg_close_all(&mh); + return -EIO; + } + + return *(int*) CMSG_DATA(found); +} + +ssize_t next_datagram_size_fd(int fd) { + ssize_t l; + int k; + + /* This is a bit like FIONREAD/SIOCINQ, however a bit more powerful. The difference being: recv(MSG_PEEK) will + * actually cause the next datagram in the queue to be validated regarding checksums, which FIONREAD dosn't + * do. This difference is actually of major importance as we need to be sure that the size returned here + * actually matches what we will read with recvmsg() next, as otherwise we might end up allocating a buffer of + * the wrong size. */ + + l = recv(fd, NULL, 0, MSG_PEEK|MSG_TRUNC); + if (l < 0) { + if (errno == EOPNOTSUPP) + goto fallback; + + return -errno; + } + if (l == 0) + goto fallback; + + return l; + +fallback: + k = 0; + + /* Some sockets (AF_PACKET) do not support null-sized recv() with MSG_TRUNC set, let's fall back to FIONREAD + * for them. Checksums don't matter for raw sockets anyway, hence this should be fine. */ + + if (ioctl(fd, FIONREAD, &k) < 0) + return -errno; + + return (ssize_t) k; +} diff --git a/src/libbasic/socket-util.h b/src/libbasic/socket-util.h new file mode 100644 index 0000000000..d17a2f35f8 --- /dev/null +++ b/src/libbasic/socket-util.h @@ -0,0 +1,139 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "macro.h" +#include "util.h" + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; + struct sockaddr_un un; + struct sockaddr_nl nl; + struct sockaddr_storage storage; + struct sockaddr_ll ll; +}; + +typedef struct SocketAddress { + union sockaddr_union sockaddr; + + /* We store the size here explicitly due to the weird + * sockaddr_un semantics for abstract sockets */ + socklen_t size; + + /* Socket type, i.e. SOCK_STREAM, SOCK_DGRAM, ... */ + int type; + + /* Socket protocol, IPPROTO_xxx, usually 0, except for netlink */ + int protocol; +} SocketAddress; + +typedef enum SocketAddressBindIPv6Only { + SOCKET_ADDRESS_DEFAULT, + SOCKET_ADDRESS_BOTH, + SOCKET_ADDRESS_IPV6_ONLY, + _SOCKET_ADDRESS_BIND_IPV6_ONLY_MAX, + _SOCKET_ADDRESS_BIND_IPV6_ONLY_INVALID = -1 +} SocketAddressBindIPv6Only; + +#define socket_address_family(a) ((a)->sockaddr.sa.sa_family) + +int socket_address_parse(SocketAddress *a, const char *s); +int socket_address_parse_and_warn(SocketAddress *a, const char *s); +int socket_address_parse_netlink(SocketAddress *a, const char *s); +int socket_address_print(const SocketAddress *a, char **p); +int socket_address_verify(const SocketAddress *a) _pure_; +int socket_address_unlink(SocketAddress *a); + +bool socket_address_can_accept(const SocketAddress *a) _pure_; + +int socket_address_listen( + const SocketAddress *a, + int flags, + int backlog, + SocketAddressBindIPv6Only only, + const char *bind_to_device, + bool reuse_port, + bool free_bind, + bool transparent, + mode_t directory_mode, + mode_t socket_mode, + const char *label); +int make_socket_fd(int log_level, const char* address, int type, int flags); + +bool socket_address_is(const SocketAddress *a, const char *s, int type); +bool socket_address_is_netlink(const SocketAddress *a, const char *s); + +bool socket_address_matches_fd(const SocketAddress *a, int fd); + +bool socket_address_equal(const SocketAddress *a, const SocketAddress *b) _pure_; + +const char* socket_address_get_path(const SocketAddress *a); + +bool socket_ipv6_is_supported(void); + +int sockaddr_port(const struct sockaddr *_sa) _pure_; + +int sockaddr_pretty(const struct sockaddr *_sa, socklen_t salen, bool translate_ipv6, bool include_port, char **ret); +int getpeername_pretty(int fd, bool include_port, char **ret); +int getsockname_pretty(int fd, char **ret); + +int socknameinfo_pretty(union sockaddr_union *sa, socklen_t salen, char **_ret); +int getnameinfo_pretty(int fd, char **ret); + +const char* socket_address_bind_ipv6_only_to_string(SocketAddressBindIPv6Only b) _const_; +SocketAddressBindIPv6Only socket_address_bind_ipv6_only_from_string(const char *s) _pure_; + +int netlink_family_to_string_alloc(int b, char **s); +int netlink_family_from_string(const char *s) _pure_; + +bool sockaddr_equal(const union sockaddr_union *a, const union sockaddr_union *b); + +int fd_inc_sndbuf(int fd, size_t n); +int fd_inc_rcvbuf(int fd, size_t n); + +int ip_tos_to_string_alloc(int i, char **s); +int ip_tos_from_string(const char *s); + +int getpeercred(int fd, struct ucred *ucred); +int getpeersec(int fd, char **ret); + +int send_one_fd_sa(int transport_fd, + int fd, + const struct sockaddr *sa, socklen_t len, + int flags); +#define send_one_fd(transport_fd, fd, flags) send_one_fd_sa(transport_fd, fd, NULL, 0, flags) +int receive_one_fd(int transport_fd, int flags); + +ssize_t next_datagram_size_fd(int fd); + +#define CMSG_FOREACH(cmsg, mh) \ + for ((cmsg) = CMSG_FIRSTHDR(mh); (cmsg); (cmsg) = CMSG_NXTHDR((mh), (cmsg))) diff --git a/src/libbasic/sparse-endian.h b/src/libbasic/sparse-endian.h new file mode 100644 index 0000000000..c913fda8c5 --- /dev/null +++ b/src/libbasic/sparse-endian.h @@ -0,0 +1,88 @@ +/* Copyright (c) 2012 Josh Triplett + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#ifndef SPARSE_ENDIAN_H +#define SPARSE_ENDIAN_H + +#include +#include +#include + +#ifdef __CHECKER__ +#define __bitwise __attribute__((bitwise)) +#define __force __attribute__((force)) +#else +#define __bitwise +#define __force +#endif + +typedef uint16_t __bitwise le16_t; +typedef uint16_t __bitwise be16_t; +typedef uint32_t __bitwise le32_t; +typedef uint32_t __bitwise be32_t; +typedef uint64_t __bitwise le64_t; +typedef uint64_t __bitwise be64_t; + +#undef htobe16 +#undef htole16 +#undef be16toh +#undef le16toh +#undef htobe32 +#undef htole32 +#undef be32toh +#undef le32toh +#undef htobe64 +#undef htole64 +#undef be64toh +#undef le64toh + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define bswap_16_on_le(x) __bswap_16(x) +#define bswap_32_on_le(x) __bswap_32(x) +#define bswap_64_on_le(x) __bswap_64(x) +#define bswap_16_on_be(x) (x) +#define bswap_32_on_be(x) (x) +#define bswap_64_on_be(x) (x) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define bswap_16_on_le(x) (x) +#define bswap_32_on_le(x) (x) +#define bswap_64_on_le(x) (x) +#define bswap_16_on_be(x) __bswap_16(x) +#define bswap_32_on_be(x) __bswap_32(x) +#define bswap_64_on_be(x) __bswap_64(x) +#endif + +static inline le16_t htole16(uint16_t value) { return (le16_t __force) bswap_16_on_be(value); } +static inline le32_t htole32(uint32_t value) { return (le32_t __force) bswap_32_on_be(value); } +static inline le64_t htole64(uint64_t value) { return (le64_t __force) bswap_64_on_be(value); } + +static inline be16_t htobe16(uint16_t value) { return (be16_t __force) bswap_16_on_le(value); } +static inline be32_t htobe32(uint32_t value) { return (be32_t __force) bswap_32_on_le(value); } +static inline be64_t htobe64(uint64_t value) { return (be64_t __force) bswap_64_on_le(value); } + +static inline uint16_t le16toh(le16_t value) { return bswap_16_on_be((uint16_t __force)value); } +static inline uint32_t le32toh(le32_t value) { return bswap_32_on_be((uint32_t __force)value); } +static inline uint64_t le64toh(le64_t value) { return bswap_64_on_be((uint64_t __force)value); } + +static inline uint16_t be16toh(be16_t value) { return bswap_16_on_le((uint16_t __force)value); } +static inline uint32_t be32toh(be32_t value) { return bswap_32_on_le((uint32_t __force)value); } +static inline uint64_t be64toh(be64_t value) { return bswap_64_on_le((uint64_t __force)value); } + +#endif /* SPARSE_ENDIAN_H */ diff --git a/src/libbasic/special.h b/src/libbasic/special.h new file mode 100644 index 0000000000..2fd03d9f75 --- /dev/null +++ b/src/libbasic/special.h @@ -0,0 +1,118 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#define SPECIAL_DEFAULT_TARGET "default.target" + +/* Shutdown targets */ +#define SPECIAL_UMOUNT_TARGET "umount.target" +/* This is not really intended to be started by directly. This is + * mostly so that other targets (reboot/halt/poweroff) can depend on + * it to bring all services down that want to be brought down on + * system shutdown. */ +#define SPECIAL_SHUTDOWN_TARGET "shutdown.target" +#define SPECIAL_HALT_TARGET "halt.target" +#define SPECIAL_POWEROFF_TARGET "poweroff.target" +#define SPECIAL_REBOOT_TARGET "reboot.target" +#define SPECIAL_KEXEC_TARGET "kexec.target" +#define SPECIAL_EXIT_TARGET "exit.target" +#define SPECIAL_SUSPEND_TARGET "suspend.target" +#define SPECIAL_HIBERNATE_TARGET "hibernate.target" +#define SPECIAL_HYBRID_SLEEP_TARGET "hybrid-sleep.target" + +/* Special boot targets */ +#define SPECIAL_RESCUE_TARGET "rescue.target" +#define SPECIAL_EMERGENCY_TARGET "emergency.target" +#define SPECIAL_MULTI_USER_TARGET "multi-user.target" +#define SPECIAL_GRAPHICAL_TARGET "graphical.target" + +/* Early boot targets */ +#define SPECIAL_SYSINIT_TARGET "sysinit.target" +#define SPECIAL_SOCKETS_TARGET "sockets.target" +#define SPECIAL_BUSNAMES_TARGET "busnames.target" +#define SPECIAL_TIMERS_TARGET "timers.target" +#define SPECIAL_PATHS_TARGET "paths.target" +#define SPECIAL_LOCAL_FS_TARGET "local-fs.target" +#define SPECIAL_LOCAL_FS_PRE_TARGET "local-fs-pre.target" +#define SPECIAL_INITRD_FS_TARGET "initrd-fs.target" +#define SPECIAL_INITRD_ROOT_FS_TARGET "initrd-root-fs.target" +#define SPECIAL_REMOTE_FS_TARGET "remote-fs.target" /* LSB's $remote_fs */ +#define SPECIAL_REMOTE_FS_PRE_TARGET "remote-fs-pre.target" +#define SPECIAL_SWAP_TARGET "swap.target" +#define SPECIAL_NETWORK_ONLINE_TARGET "network-online.target" +#define SPECIAL_TIME_SYNC_TARGET "time-sync.target" /* LSB's $time */ +#define SPECIAL_BASIC_TARGET "basic.target" + +/* LSB compatibility */ +#define SPECIAL_NETWORK_TARGET "network.target" /* LSB's $network */ +#define SPECIAL_NSS_LOOKUP_TARGET "nss-lookup.target" /* LSB's $named */ +#define SPECIAL_RPCBIND_TARGET "rpcbind.target" /* LSB's $portmap */ + +/* + * Rules regarding adding further high level targets like the above: + * + * - Be conservative, only add more of these when we really need + * them. We need strong usecases for further additions. + * + * - When there can be multiple implementations running side-by-side, + * it needs to be a .target unit which can pull in all + * implementations. + * + * - If something can be implemented with socket activation, and + * without, it needs to be a .target unit, so that it can pull in + * the appropriate unit. + * + * - Otherwise, it should be a .service unit. + * + * - In some cases it is OK to have both a .service and a .target + * unit, i.e. if there can be multiple parallel implementations, but + * only one is the "system" one. Example: syslog. + * + * Or to put this in other words: .service symlinks can be used to + * arbitrate between multiple implementations if there can be only one + * of a kind. .target units can be used to support multiple + * implementations that can run side-by-side. + */ + +/* Magic early boot services */ +#define SPECIAL_FSCK_SERVICE "systemd-fsck@.service" +#define SPECIAL_QUOTACHECK_SERVICE "systemd-quotacheck.service" +#define SPECIAL_QUOTAON_SERVICE "quotaon.service" +#define SPECIAL_REMOUNT_FS_SERVICE "systemd-remount-fs.service" + +/* Services systemd relies on */ +#define SPECIAL_DBUS_SERVICE "dbus.service" +#define SPECIAL_DBUS_SOCKET "dbus.socket" +#define SPECIAL_JOURNALD_SOCKET "systemd-journald.socket" +#define SPECIAL_JOURNALD_SERVICE "systemd-journald.service" + +/* Magic init signals */ +#define SPECIAL_KBREQUEST_TARGET "kbrequest.target" +#define SPECIAL_SIGPWR_TARGET "sigpwr.target" +#define SPECIAL_CTRL_ALT_DEL_TARGET "ctrl-alt-del.target" + +/* Where we add all our system units, users and machines by default */ +#define SPECIAL_SYSTEM_SLICE "system.slice" +#define SPECIAL_USER_SLICE "user.slice" +#define SPECIAL_MACHINE_SLICE "machine.slice" +#define SPECIAL_ROOT_SLICE "-.slice" + +/* The scope unit systemd itself lives in. */ +#define SPECIAL_INIT_SCOPE "init.scope" diff --git a/src/libbasic/stat-util.c b/src/libbasic/stat-util.c new file mode 100644 index 0000000000..309e84b93d --- /dev/null +++ b/src/libbasic/stat-util.c @@ -0,0 +1,218 @@ +/*** + This file is part of systemd. + + Copyright 2010-2012 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dirent-util.h" +#include "fd-util.h" +#include "macro.h" +#include "missing.h" +#include "stat-util.h" +#include "string-util.h" + +int is_symlink(const char *path) { + struct stat info; + + assert(path); + + if (lstat(path, &info) < 0) + return -errno; + + return !!S_ISLNK(info.st_mode); +} + +int is_dir(const char* path, bool follow) { + struct stat st; + int r; + + assert(path); + + if (follow) + r = stat(path, &st); + else + r = lstat(path, &st); + if (r < 0) + return -errno; + + return !!S_ISDIR(st.st_mode); +} + +int is_device_node(const char *path) { + struct stat info; + + assert(path); + + if (lstat(path, &info) < 0) + return -errno; + + return !!(S_ISBLK(info.st_mode) || S_ISCHR(info.st_mode)); +} + +int dir_is_empty(const char *path) { + _cleanup_closedir_ DIR *d; + struct dirent *de; + + d = opendir(path); + if (!d) + return -errno; + + FOREACH_DIRENT(de, d, return -errno) + return 0; + + return 1; +} + +bool null_or_empty(struct stat *st) { + assert(st); + + if (S_ISREG(st->st_mode) && st->st_size <= 0) + return true; + + /* We don't want to hardcode the major/minor of /dev/null, + * hence we do a simpler "is this a device node?" check. */ + + if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) + return true; + + return false; +} + +int null_or_empty_path(const char *fn) { + struct stat st; + + assert(fn); + + if (stat(fn, &st) < 0) + return -errno; + + return null_or_empty(&st); +} + +int null_or_empty_fd(int fd) { + struct stat st; + + assert(fd >= 0); + + if (fstat(fd, &st) < 0) + return -errno; + + return null_or_empty(&st); +} + +int path_is_read_only_fs(const char *path) { + struct statvfs st; + + assert(path); + + if (statvfs(path, &st) < 0) + return -errno; + + if (st.f_flag & ST_RDONLY) + return true; + + /* On NFS, statvfs() might not reflect whether we can actually + * write to the remote share. Let's try again with + * access(W_OK) which is more reliable, at least sometimes. */ + if (access(path, W_OK) < 0 && errno == EROFS) + return true; + + return false; +} + +int path_is_os_tree(const char *path) { + char *p; + int r; + + assert(path); + + /* We use /usr/lib/os-release as flag file if something is an OS */ + p = strjoina(path, "/usr/lib/os-release"); + r = access(p, F_OK); + if (r >= 0) + return 1; + + /* Also check for the old location in /etc, just in case. */ + p = strjoina(path, "/etc/os-release"); + r = access(p, F_OK); + + return r >= 0; +} + +int files_same(const char *filea, const char *fileb) { + struct stat a, b; + + assert(filea); + assert(fileb); + + if (stat(filea, &a) < 0) + return -errno; + + if (stat(fileb, &b) < 0) + return -errno; + + return a.st_dev == b.st_dev && + a.st_ino == b.st_ino; +} + +bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) { + assert(s); + assert_cc(sizeof(statfs_f_type_t) >= sizeof(s->f_type)); + + return F_TYPE_EQUAL(s->f_type, magic_value); +} + +int fd_check_fstype(int fd, statfs_f_type_t magic_value) { + struct statfs s; + + if (fstatfs(fd, &s) < 0) + return -errno; + + return is_fs_type(&s, magic_value); +} + +int path_check_fstype(const char *path, statfs_f_type_t magic_value) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY); + if (fd < 0) + return -errno; + + return fd_check_fstype(fd, magic_value); +} + +bool is_temporary_fs(const struct statfs *s) { + return is_fs_type(s, TMPFS_MAGIC) || + is_fs_type(s, RAMFS_MAGIC); +} + +int fd_is_temporary_fs(int fd) { + struct statfs s; + + if (fstatfs(fd, &s) < 0) + return -errno; + + return is_temporary_fs(&s); +} diff --git a/src/libbasic/stat-util.h b/src/libbasic/stat-util.h new file mode 100644 index 0000000000..56d28f791e --- /dev/null +++ b/src/libbasic/stat-util.h @@ -0,0 +1,69 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2012 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "macro.h" + +int is_symlink(const char *path); +int is_dir(const char *path, bool follow); +int is_device_node(const char *path); + +int dir_is_empty(const char *path); + +static inline int dir_is_populated(const char *path) { + int r; + r = dir_is_empty(path); + if (r < 0) + return r; + return !r; +} + +bool null_or_empty(struct stat *st) _pure_; +int null_or_empty_path(const char *fn); +int null_or_empty_fd(int fd); + +int path_is_read_only_fs(const char *path); +int path_is_os_tree(const char *path); + +int files_same(const char *filea, const char *fileb); + +/* The .f_type field of struct statfs is really weird defined on + * different archs. Let's give its type a name. */ +typedef typeof(((struct statfs*)NULL)->f_type) statfs_f_type_t; + +bool is_fs_type(const struct statfs *s, statfs_f_type_t magic_value) _pure_; +int fd_check_fstype(int fd, statfs_f_type_t magic_value); +int path_check_fstype(const char *path, statfs_f_type_t magic_value); + +bool is_temporary_fs(const struct statfs *s) _pure_; +int fd_is_temporary_fs(int fd); + +/* Because statfs.t_type can be int on some architectures, we have to cast + * the const magic to the type, otherwise the compiler warns about + * signed/unsigned comparison, because the magic can be 32 bit unsigned. + */ +#define F_TYPE_EQUAL(a, b) (a == (typeof(a)) b) diff --git a/src/libbasic/stdio-util.h b/src/libbasic/stdio-util.h new file mode 100644 index 0000000000..0a675571ff --- /dev/null +++ b/src/libbasic/stdio-util.h @@ -0,0 +1,76 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "macro.h" + +#define xsprintf(buf, fmt, ...) \ + assert_message_se((size_t) snprintf(buf, ELEMENTSOF(buf), fmt, __VA_ARGS__) < ELEMENTSOF(buf), "xsprintf: " #buf "[] must be big enough") + + +#define VA_FORMAT_ADVANCE(format, ap) \ +do { \ + int _argtypes[128]; \ + size_t _i, _k; \ + _k = parse_printf_format((format), ELEMENTSOF(_argtypes), _argtypes); \ + assert(_k < ELEMENTSOF(_argtypes)); \ + for (_i = 0; _i < _k; _i++) { \ + if (_argtypes[_i] & PA_FLAG_PTR) { \ + (void) va_arg(ap, void*); \ + continue; \ + } \ + \ + switch (_argtypes[_i]) { \ + case PA_INT: \ + case PA_INT|PA_FLAG_SHORT: \ + case PA_CHAR: \ + (void) va_arg(ap, int); \ + break; \ + case PA_INT|PA_FLAG_LONG: \ + (void) va_arg(ap, long int); \ + break; \ + case PA_INT|PA_FLAG_LONG_LONG: \ + (void) va_arg(ap, long long int); \ + break; \ + case PA_WCHAR: \ + (void) va_arg(ap, wchar_t); \ + break; \ + case PA_WSTRING: \ + case PA_STRING: \ + case PA_POINTER: \ + (void) va_arg(ap, void*); \ + break; \ + case PA_FLOAT: \ + case PA_DOUBLE: \ + (void) va_arg(ap, double); \ + break; \ + case PA_DOUBLE|PA_FLAG_LONG_DOUBLE: \ + (void) va_arg(ap, long double); \ + break; \ + default: \ + assert_not_reached("Unknown format string argument."); \ + } \ + } \ +} while(false) diff --git a/src/libbasic/strbuf.c b/src/libbasic/strbuf.c new file mode 100644 index 0000000000..77220c0251 --- /dev/null +++ b/src/libbasic/strbuf.c @@ -0,0 +1,200 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include +#include +#include + +#include "alloc-util.h" +#include "strbuf.h" + +/* + * Strbuf stores given strings in a single continuous allocated memory + * area. Identical strings are de-duplicated and return the same offset + * as the first string stored. If the tail of a string already exists + * in the buffer, the tail is returned. + * + * A trie (http://en.wikipedia.org/wiki/Trie) is used to maintain the + * information about the stored strings. + * + * Example of udev rules: + * $ ./udevadm test . + * ... + * read rules file: /usr/lib/udev/rules.d/99-systemd.rules + * rules contain 196608 bytes tokens (16384 * 12 bytes), 39742 bytes strings + * 23939 strings (207859 bytes), 20404 de-duplicated (171653 bytes), 3536 trie nodes used + * ... + */ + +struct strbuf *strbuf_new(void) { + struct strbuf *str; + + str = new0(struct strbuf, 1); + if (!str) + return NULL; + + str->buf = new0(char, 1); + if (!str->buf) + goto err; + str->len = 1; + + str->root = new0(struct strbuf_node, 1); + if (!str->root) + goto err; + str->nodes_count = 1; + return str; +err: + free(str->buf); + free(str->root); + free(str); + return NULL; +} + +static void strbuf_node_cleanup(struct strbuf_node *node) { + size_t i; + + for (i = 0; i < node->children_count; i++) + strbuf_node_cleanup(node->children[i].child); + free(node->children); + free(node); +} + +/* clean up trie data, leave only the string buffer */ +void strbuf_complete(struct strbuf *str) { + if (!str) + return; + if (str->root) + strbuf_node_cleanup(str->root); + str->root = NULL; +} + +/* clean up everything */ +void strbuf_cleanup(struct strbuf *str) { + if (!str) + return; + if (str->root) + strbuf_node_cleanup(str->root); + free(str->buf); + free(str); +} + +static int strbuf_children_cmp(const struct strbuf_child_entry *n1, + const struct strbuf_child_entry *n2) { + return n1->c - n2->c; +} + +static void bubbleinsert(struct strbuf_node *node, + uint8_t c, + struct strbuf_node *node_child) { + + struct strbuf_child_entry new = { + .c = c, + .child = node_child, + }; + int left = 0, right = node->children_count; + + while (right > left) { + int middle = (right + left) / 2 ; + if (strbuf_children_cmp(&node->children[middle], &new) <= 0) + left = middle + 1; + else + right = middle; + } + + memmove(node->children + left + 1, node->children + left, + sizeof(struct strbuf_child_entry) * (node->children_count - left)); + node->children[left] = new; + + node->children_count ++; +} + +/* add string, return the index/offset into the buffer */ +ssize_t strbuf_add_string(struct strbuf *str, const char *s, size_t len) { + uint8_t c; + struct strbuf_node *node; + size_t depth; + char *buf_new; + struct strbuf_child_entry *child; + struct strbuf_node *node_child; + ssize_t off; + + if (!str->root) + return -EINVAL; + + /* search string; start from last character to find possibly matching tails */ + if (len == 0) + return 0; + str->in_count++; + str->in_len += len; + + node = str->root; + c = s[len-1]; + for (depth = 0; depth <= len; depth++) { + struct strbuf_child_entry search; + + /* match against current node */ + off = node->value_off + node->value_len - len; + if (depth == len || (node->value_len >= len && memcmp(str->buf + off, s, len) == 0)) { + str->dedup_len += len; + str->dedup_count++; + return off; + } + + /* lookup child node */ + c = s[len - 1 - depth]; + search.c = c; + child = bsearch(&search, node->children, node->children_count, + sizeof(struct strbuf_child_entry), + (__compar_fn_t) strbuf_children_cmp); + if (!child) + break; + node = child->child; + } + + /* add new string */ + buf_new = realloc(str->buf, str->len + len+1); + if (!buf_new) + return -ENOMEM; + str->buf = buf_new; + off = str->len; + memcpy(str->buf + off, s, len); + str->len += len; + str->buf[str->len++] = '\0'; + + /* new node */ + node_child = new0(struct strbuf_node, 1); + if (!node_child) + return -ENOMEM; + node_child->value_off = off; + node_child->value_len = len; + + /* extend array, add new entry, sort for bisection */ + child = realloc(node->children, (node->children_count + 1) * sizeof(struct strbuf_child_entry)); + if (!child) { + free(node_child); + return -ENOMEM; + } + + str->nodes_count++; + + node->children = child; + bubbleinsert(node, c, node_child); + + return off; +} diff --git a/src/libbasic/strbuf.h b/src/libbasic/strbuf.h new file mode 100644 index 0000000000..a1632da0e8 --- /dev/null +++ b/src/libbasic/strbuf.h @@ -0,0 +1,54 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include +#include +#include + +struct strbuf { + char *buf; + size_t len; + struct strbuf_node *root; + + size_t nodes_count; + size_t in_count; + size_t in_len; + size_t dedup_len; + size_t dedup_count; +}; + +struct strbuf_node { + size_t value_off; + size_t value_len; + + struct strbuf_child_entry *children; + uint8_t children_count; +}; + +struct strbuf_child_entry { + uint8_t c; + struct strbuf_node *child; +}; + +struct strbuf *strbuf_new(void); +ssize_t strbuf_add_string(struct strbuf *str, const char *s, size_t len); +void strbuf_complete(struct strbuf *str); +void strbuf_cleanup(struct strbuf *str); diff --git a/src/libbasic/string-table.c b/src/libbasic/string-table.c new file mode 100644 index 0000000000..a1499ab126 --- /dev/null +++ b/src/libbasic/string-table.c @@ -0,0 +1,34 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "string-table.h" +#include "string-util.h" + +ssize_t string_table_lookup(const char * const *table, size_t len, const char *key) { + size_t i; + + if (!key) + return -1; + + for (i = 0; i < len; ++i) + if (streq_ptr(table[i], key)) + return (ssize_t) i; + + return -1; +} diff --git a/src/libbasic/string-table.h b/src/libbasic/string-table.h new file mode 100644 index 0000000000..b180488fe8 --- /dev/null +++ b/src/libbasic/string-table.h @@ -0,0 +1,105 @@ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" + +ssize_t string_table_lookup(const char * const *table, size_t len, const char *key); + +/* For basic lookup tables with strictly enumerated entries */ +#define _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ + scope const char *name##_to_string(type i) { \ + if (i < 0 || i >= (type) ELEMENTSOF(name##_table)) \ + return NULL; \ + return name##_table[i]; \ + } + +#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope) \ + scope type name##_from_string(const char *s) { \ + return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \ + } + +#define _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \ + scope type name##_from_string(const char *s) { \ + int b; \ + b = parse_boolean(s); \ + if (b == 0) \ + return (type) 0; \ + else if (b > 0) \ + return yes; \ + return (type) string_table_lookup(name##_table, ELEMENTSOF(name##_table), s); \ + } + +#define _DEFINE_STRING_TABLE_LOOKUP(name,type,scope) \ + _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ + _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,scope) \ + struct __useless_struct_to_allow_trailing_semicolon__ + +#define _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,scope) \ + _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,scope) \ + _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(name,type,yes,scope) \ + struct __useless_struct_to_allow_trailing_semicolon__ + +#define DEFINE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,) +#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP(name,type) _DEFINE_STRING_TABLE_LOOKUP(name,type,static) +#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_TO_STRING(name,type,static) +#define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(name,type) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING(name,type,static) + +#define DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes) _DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(name,type,yes,) + +/* For string conversions where numbers are also acceptable */ +#define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \ + int name##_to_string_alloc(type i, char **str) { \ + char *s; \ + if (i < 0 || i > max) \ + return -ERANGE; \ + if (i < (type) ELEMENTSOF(name##_table)) { \ + s = strdup(name##_table[i]); \ + if (!s) \ + return -ENOMEM; \ + } else { \ + if (asprintf(&s, "%i", i) < 0) \ + return -ENOMEM; \ + } \ + *str = s; \ + return 0; \ + } \ + type name##_from_string(const char *s) { \ + type i; \ + unsigned u = 0; \ + if (!s) \ + return (type) -1; \ + for (i = 0; i < (type) ELEMENTSOF(name##_table); i++) \ + if (streq_ptr(name##_table[i], s)) \ + return i; \ + if (safe_atou(s, &u) >= 0 && u <= max) \ + return (type) u; \ + return (type) -1; \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ diff --git a/src/libbasic/string-util.c b/src/libbasic/string-util.c new file mode 100644 index 0000000000..0bde55f9d5 --- /dev/null +++ b/src/libbasic/string-util.c @@ -0,0 +1,855 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "gunicode.h" +#include "macro.h" +#include "string-util.h" +#include "utf8.h" +#include "util.h" + +int strcmp_ptr(const char *a, const char *b) { + + /* Like strcmp(), but tries to make sense of NULL pointers */ + if (a && b) + return strcmp(a, b); + + if (!a && b) + return -1; + + if (a && !b) + return 1; + + return 0; +} + +char* endswith(const char *s, const char *postfix) { + size_t sl, pl; + + assert(s); + assert(postfix); + + sl = strlen(s); + pl = strlen(postfix); + + if (pl == 0) + return (char*) s + sl; + + if (sl < pl) + return NULL; + + if (memcmp(s + sl - pl, postfix, pl) != 0) + return NULL; + + return (char*) s + sl - pl; +} + +char* endswith_no_case(const char *s, const char *postfix) { + size_t sl, pl; + + assert(s); + assert(postfix); + + sl = strlen(s); + pl = strlen(postfix); + + if (pl == 0) + return (char*) s + sl; + + if (sl < pl) + return NULL; + + if (strcasecmp(s + sl - pl, postfix) != 0) + return NULL; + + return (char*) s + sl - pl; +} + +char* first_word(const char *s, const char *word) { + size_t sl, wl; + const char *p; + + assert(s); + assert(word); + + /* Checks if the string starts with the specified word, either + * followed by NUL or by whitespace. Returns a pointer to the + * NUL or the first character after the whitespace. */ + + sl = strlen(s); + wl = strlen(word); + + if (sl < wl) + return NULL; + + if (wl == 0) + return (char*) s; + + if (memcmp(s, word, wl) != 0) + return NULL; + + p = s + wl; + if (*p == 0) + return (char*) p; + + if (!strchr(WHITESPACE, *p)) + return NULL; + + p += strspn(p, WHITESPACE); + return (char*) p; +} + +static size_t strcspn_escaped(const char *s, const char *reject) { + bool escaped = false; + int n; + + for (n=0; s[n]; n++) { + if (escaped) + escaped = false; + else if (s[n] == '\\') + escaped = true; + else if (strchr(reject, s[n])) + break; + } + + /* if s ends in \, return index of previous char */ + return n - escaped; +} + +/* Split a string into words. */ +const char* split(const char **state, size_t *l, const char *separator, bool quoted) { + const char *current; + + current = *state; + + if (!*current) { + assert(**state == '\0'); + return NULL; + } + + current += strspn(current, separator); + if (!*current) { + *state = current; + return NULL; + } + + if (quoted && strchr("\'\"", *current)) { + char quotechars[2] = {*current, '\0'}; + + *l = strcspn_escaped(current + 1, quotechars); + if (current[*l + 1] == '\0' || current[*l + 1] != quotechars[0] || + (current[*l + 2] && !strchr(separator, current[*l + 2]))) { + /* right quote missing or garbage at the end */ + *state = current; + return NULL; + } + *state = current++ + *l + 2; + } else if (quoted) { + *l = strcspn_escaped(current, separator); + if (current[*l] && !strchr(separator, current[*l])) { + /* unfinished escape */ + *state = current; + return NULL; + } + *state = current + *l; + } else { + *l = strcspn(current, separator); + *state = current + *l; + } + + return current; +} + +char *strnappend(const char *s, const char *suffix, size_t b) { + size_t a; + char *r; + + if (!s && !suffix) + return strdup(""); + + if (!s) + return strndup(suffix, b); + + if (!suffix) + return strdup(s); + + assert(s); + assert(suffix); + + a = strlen(s); + if (b > ((size_t) -1) - a) + return NULL; + + r = new(char, a+b+1); + if (!r) + return NULL; + + memcpy(r, s, a); + memcpy(r+a, suffix, b); + r[a+b] = 0; + + return r; +} + +char *strappend(const char *s, const char *suffix) { + return strnappend(s, suffix, suffix ? strlen(suffix) : 0); +} + +char *strjoin(const char *x, ...) { + va_list ap; + size_t l; + char *r, *p; + + va_start(ap, x); + + if (x) { + l = strlen(x); + + for (;;) { + const char *t; + size_t n; + + t = va_arg(ap, const char *); + if (!t) + break; + + n = strlen(t); + if (n > ((size_t) -1) - l) { + va_end(ap); + return NULL; + } + + l += n; + } + } else + l = 0; + + va_end(ap); + + r = new(char, l+1); + if (!r) + return NULL; + + if (x) { + p = stpcpy(r, x); + + va_start(ap, x); + + for (;;) { + const char *t; + + t = va_arg(ap, const char *); + if (!t) + break; + + p = stpcpy(p, t); + } + + va_end(ap); + } else + r[0] = 0; + + return r; +} + +char *strstrip(char *s) { + char *e; + + /* Drops trailing whitespace. Modifies the string in + * place. Returns pointer to first non-space character */ + + s += strspn(s, WHITESPACE); + + for (e = strchr(s, 0); e > s; e --) + if (!strchr(WHITESPACE, e[-1])) + break; + + *e = 0; + + return s; +} + +char *delete_chars(char *s, const char *bad) { + char *f, *t; + + /* Drops all whitespace, regardless where in the string */ + + for (f = s, t = s; *f; f++) { + if (strchr(bad, *f)) + continue; + + *(t++) = *f; + } + + *t = 0; + + return s; +} + +char *truncate_nl(char *s) { + assert(s); + + s[strcspn(s, NEWLINE)] = 0; + return s; +} + +char ascii_tolower(char x) { + + if (x >= 'A' && x <= 'Z') + return x - 'A' + 'a'; + + return x; +} + +char *ascii_strlower(char *t) { + char *p; + + assert(t); + + for (p = t; *p; p++) + *p = ascii_tolower(*p); + + return t; +} + +char *ascii_strlower_n(char *t, size_t n) { + size_t i; + + if (n <= 0) + return t; + + for (i = 0; i < n; i++) + t[i] = ascii_tolower(t[i]); + + return t; +} + +int ascii_strcasecmp_n(const char *a, const char *b, size_t n) { + + for (; n > 0; a++, b++, n--) { + int x, y; + + x = (int) (uint8_t) ascii_tolower(*a); + y = (int) (uint8_t) ascii_tolower(*b); + + if (x != y) + return x - y; + } + + return 0; +} + +int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m) { + int r; + + r = ascii_strcasecmp_n(a, b, MIN(n, m)); + if (r != 0) + return r; + + if (n < m) + return -1; + else if (n > m) + return 1; + else + return 0; +} + +bool chars_intersect(const char *a, const char *b) { + const char *p; + + /* Returns true if any of the chars in a are in b. */ + for (p = a; *p; p++) + if (strchr(b, *p)) + return true; + + return false; +} + +bool string_has_cc(const char *p, const char *ok) { + const char *t; + + assert(p); + + /* + * Check if a string contains control characters. If 'ok' is + * non-NULL it may be a string containing additional CCs to be + * considered OK. + */ + + for (t = p; *t; t++) { + if (ok && strchr(ok, *t)) + continue; + + if (*t > 0 && *t < ' ') + return true; + + if (*t == 127) + return true; + } + + return false; +} + +static char *ascii_ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { + size_t x; + char *r; + + assert(s); + assert(percent <= 100); + assert(new_length >= 3); + + if (old_length <= 3 || old_length <= new_length) + return strndup(s, old_length); + + r = new0(char, new_length+1); + if (!r) + return NULL; + + x = (new_length * percent) / 100; + + if (x > new_length - 3) + x = new_length - 3; + + memcpy(r, s, x); + r[x] = '.'; + r[x+1] = '.'; + r[x+2] = '.'; + memcpy(r + x + 3, + s + old_length - (new_length - x - 3), + new_length - x - 3); + + return r; +} + +char *ellipsize_mem(const char *s, size_t old_length, size_t new_length, unsigned percent) { + size_t x; + char *e; + const char *i, *j; + unsigned k, len, len2; + int r; + + assert(s); + assert(percent <= 100); + assert(new_length >= 3); + + /* if no multibyte characters use ascii_ellipsize_mem for speed */ + if (ascii_is_valid(s)) + return ascii_ellipsize_mem(s, old_length, new_length, percent); + + if (old_length <= 3 || old_length <= new_length) + return strndup(s, old_length); + + x = (new_length * percent) / 100; + + if (x > new_length - 3) + x = new_length - 3; + + k = 0; + for (i = s; k < x && i < s + old_length; i = utf8_next_char(i)) { + char32_t c; + + r = utf8_encoded_to_unichar(i, &c); + if (r < 0) + return NULL; + k += unichar_iswide(c) ? 2 : 1; + } + + if (k > x) /* last character was wide and went over quota */ + x ++; + + for (j = s + old_length; k < new_length && j > i; ) { + char32_t c; + + j = utf8_prev_char(j); + r = utf8_encoded_to_unichar(j, &c); + if (r < 0) + return NULL; + k += unichar_iswide(c) ? 2 : 1; + } + assert(i <= j); + + /* we don't actually need to ellipsize */ + if (i == j) + return memdup(s, old_length + 1); + + /* make space for ellipsis */ + j = utf8_next_char(j); + + len = i - s; + len2 = s + old_length - j; + e = new(char, len + 3 + len2 + 1); + if (!e) + return NULL; + + /* + printf("old_length=%zu new_length=%zu x=%zu len=%u len2=%u k=%u\n", + old_length, new_length, x, len, len2, k); + */ + + memcpy(e, s, len); + e[len] = 0xe2; /* tri-dot ellipsis: … */ + e[len + 1] = 0x80; + e[len + 2] = 0xa6; + + memcpy(e + len + 3, j, len2 + 1); + + return e; +} + +char *ellipsize(const char *s, size_t length, unsigned percent) { + return ellipsize_mem(s, strlen(s), length, percent); +} + +bool nulstr_contains(const char*nulstr, const char *needle) { + const char *i; + + if (!nulstr) + return false; + + NULSTR_FOREACH(i, nulstr) + if (streq(i, needle)) + return true; + + return false; +} + +char* strshorten(char *s, size_t l) { + assert(s); + + if (l < strlen(s)) + s[l] = 0; + + return s; +} + +char *strreplace(const char *text, const char *old_string, const char *new_string) { + const char *f; + char *t, *r; + size_t l, old_len, new_len; + + assert(text); + assert(old_string); + assert(new_string); + + old_len = strlen(old_string); + new_len = strlen(new_string); + + l = strlen(text); + r = new(char, l+1); + if (!r) + return NULL; + + f = text; + t = r; + while (*f) { + char *a; + size_t d, nl; + + if (!startswith(f, old_string)) { + *(t++) = *(f++); + continue; + } + + d = t - r; + nl = l - old_len + new_len; + a = realloc(r, nl + 1); + if (!a) + goto oom; + + l = nl; + r = a; + t = r + d; + + t = stpcpy(t, new_string); + f += old_len; + } + + *t = 0; + return r; + +oom: + free(r); + return NULL; +} + +char *strip_tab_ansi(char **ibuf, size_t *_isz) { + const char *i, *begin = NULL; + enum { + STATE_OTHER, + STATE_ESCAPE, + STATE_BRACKET + } state = STATE_OTHER; + char *obuf = NULL; + size_t osz = 0, isz; + FILE *f; + + assert(ibuf); + assert(*ibuf); + + /* Strips ANSI color and replaces TABs by 8 spaces */ + + isz = _isz ? *_isz : strlen(*ibuf); + + f = open_memstream(&obuf, &osz); + if (!f) + return NULL; + + for (i = *ibuf; i < *ibuf + isz + 1; i++) { + + switch (state) { + + case STATE_OTHER: + if (i >= *ibuf + isz) /* EOT */ + break; + else if (*i == '\x1B') + state = STATE_ESCAPE; + else if (*i == '\t') + fputs(" ", f); + else + fputc(*i, f); + break; + + case STATE_ESCAPE: + if (i >= *ibuf + isz) { /* EOT */ + fputc('\x1B', f); + break; + } else if (*i == '[') { + state = STATE_BRACKET; + begin = i + 1; + } else { + fputc('\x1B', f); + fputc(*i, f); + state = STATE_OTHER; + } + + break; + + case STATE_BRACKET: + + if (i >= *ibuf + isz || /* EOT */ + (!(*i >= '0' && *i <= '9') && *i != ';' && *i != 'm')) { + fputc('\x1B', f); + fputc('[', f); + state = STATE_OTHER; + i = begin-1; + } else if (*i == 'm') + state = STATE_OTHER; + break; + } + } + + if (ferror(f)) { + fclose(f); + free(obuf); + return NULL; + } + + fclose(f); + + free(*ibuf); + *ibuf = obuf; + + if (_isz) + *_isz = osz; + + return obuf; +} + +char *strextend(char **x, ...) { + va_list ap; + size_t f, l; + char *r, *p; + + assert(x); + + l = f = *x ? strlen(*x) : 0; + + va_start(ap, x); + for (;;) { + const char *t; + size_t n; + + t = va_arg(ap, const char *); + if (!t) + break; + + n = strlen(t); + if (n > ((size_t) -1) - l) { + va_end(ap); + return NULL; + } + + l += n; + } + va_end(ap); + + r = realloc(*x, l+1); + if (!r) + return NULL; + + p = r + f; + + va_start(ap, x); + for (;;) { + const char *t; + + t = va_arg(ap, const char *); + if (!t) + break; + + p = stpcpy(p, t); + } + va_end(ap); + + *p = 0; + *x = r; + + return r + l; +} + +char *strrep(const char *s, unsigned n) { + size_t l; + char *r, *p; + unsigned i; + + assert(s); + + l = strlen(s); + p = r = malloc(l * n + 1); + if (!r) + return NULL; + + for (i = 0; i < n; i++) + p = stpcpy(p, s); + + *p = 0; + return r; +} + +int split_pair(const char *s, const char *sep, char **l, char **r) { + char *x, *a, *b; + + assert(s); + assert(sep); + assert(l); + assert(r); + + if (isempty(sep)) + return -EINVAL; + + x = strstr(s, sep); + if (!x) + return -EINVAL; + + a = strndup(s, x - s); + if (!a) + return -ENOMEM; + + b = strdup(x + strlen(sep)); + if (!b) { + free(a); + return -ENOMEM; + } + + *l = a; + *r = b; + + return 0; +} + +int free_and_strdup(char **p, const char *s) { + char *t; + + assert(p); + + /* Replaces a string pointer with an strdup()ed new string, + * possibly freeing the old one. */ + + if (streq_ptr(*p, s)) + return 0; + + if (s) { + t = strdup(s); + if (!t) + return -ENOMEM; + } else + t = NULL; + + free(*p); + *p = t; + + return 1; +} + +#pragma GCC push_options +#pragma GCC optimize("O0") + +void* memory_erase(void *p, size_t l) { + volatile uint8_t* x = (volatile uint8_t*) p; + + /* 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; +} + +#pragma GCC pop_options + +char* string_erase(char *x) { + + if (!x) + return NULL; + + /* A delicious drop of snake-oil! To be called on memory where + * we stored passphrases or so, after we used them. */ + + return memory_erase(x, strlen(x)); +} + +char *string_free_erase(char *s) { + return mfree(string_erase(s)); +} + +bool string_is_safe(const char *p) { + const char *t; + + if (!p) + return false; + + for (t = p; *t; t++) { + if (*t > 0 && *t < ' ') /* no control characters */ + return false; + + if (strchr(QUOTES "\\\x7f", *t)) + return false; + } + + return true; +} diff --git a/src/libbasic/string-util.h b/src/libbasic/string-util.h new file mode 100644 index 0000000000..ad0c813761 --- /dev/null +++ b/src/libbasic/string-util.h @@ -0,0 +1,189 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "macro.h" + +/* What is interpreted as whitespace? */ +#define WHITESPACE " \t\n\r" +#define NEWLINE "\n\r" +#define QUOTES "\"\'" +#define COMMENTS "#;" +#define GLOB_CHARS "*?[" +#define DIGITS "0123456789" +#define LOWERCASE_LETTERS "abcdefghijklmnopqrstuvwxyz" +#define UPPERCASE_LETTERS "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define LETTERS LOWERCASE_LETTERS UPPERCASE_LETTERS +#define ALPHANUMERICAL LETTERS DIGITS + +#define streq(a,b) (strcmp((a),(b)) == 0) +#define strneq(a, b, n) (strncmp((a), (b), (n)) == 0) +#define strcaseeq(a,b) (strcasecmp((a),(b)) == 0) +#define strncaseeq(a, b, n) (strncasecmp((a), (b), (n)) == 0) + +int strcmp_ptr(const char *a, const char *b) _pure_; + +static inline bool streq_ptr(const char *a, const char *b) { + return strcmp_ptr(a, b) == 0; +} + +static inline const char* strempty(const char *s) { + return s ? s : ""; +} + +static inline const char* strnull(const char *s) { + return s ? s : "(null)"; +} + +static inline const char *strna(const char *s) { + return s ? s : "n/a"; +} + +static inline bool isempty(const char *p) { + return !p || !p[0]; +} + +static inline char *startswith(const char *s, const char *prefix) { + size_t l; + + l = strlen(prefix); + if (strncmp(s, prefix, l) == 0) + return (char*) s + l; + + return NULL; +} + +static inline char *startswith_no_case(const char *s, const char *prefix) { + size_t l; + + l = strlen(prefix); + if (strncasecmp(s, prefix, l) == 0) + return (char*) s + l; + + return NULL; +} + +char *endswith(const char *s, const char *postfix) _pure_; +char *endswith_no_case(const char *s, const char *postfix) _pure_; + +char *first_word(const char *s, const char *word) _pure_; + +const char* split(const char **state, size_t *l, const char *separator, bool quoted); + +#define FOREACH_WORD(word, length, s, state) \ + _FOREACH_WORD(word, length, s, WHITESPACE, false, state) + +#define FOREACH_WORD_SEPARATOR(word, length, s, separator, state) \ + _FOREACH_WORD(word, length, s, separator, false, state) + +#define FOREACH_WORD_QUOTED(word, length, s, state) \ + _FOREACH_WORD(word, length, s, WHITESPACE, true, state) + +#define _FOREACH_WORD(word, length, s, separator, quoted, state) \ + for ((state) = (s), (word) = split(&(state), &(length), (separator), (quoted)); (word); (word) = split(&(state), &(length), (separator), (quoted))) + +char *strappend(const char *s, const char *suffix); +char *strnappend(const char *s, const char *suffix, size_t length); + +char *strjoin(const char *x, ...) _sentinel_; + +#define strjoina(a, ...) \ + ({ \ + const char *_appendees_[] = { a, __VA_ARGS__ }; \ + char *_d_, *_p_; \ + int _len_ = 0; \ + unsigned _i_; \ + for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \ + _len_ += strlen(_appendees_[_i_]); \ + _p_ = _d_ = alloca(_len_ + 1); \ + for (_i_ = 0; _i_ < ELEMENTSOF(_appendees_) && _appendees_[_i_]; _i_++) \ + _p_ = stpcpy(_p_, _appendees_[_i_]); \ + *_p_ = 0; \ + _d_; \ + }) + +char *strstrip(char *s); +char *delete_chars(char *s, const char *bad); +char *truncate_nl(char *s); + +char ascii_tolower(char x); +char *ascii_strlower(char *s); +char *ascii_strlower_n(char *s, size_t n); + +int ascii_strcasecmp_n(const char *a, const char *b, size_t n); +int ascii_strcasecmp_nn(const char *a, size_t n, const char *b, size_t m); + +bool chars_intersect(const char *a, const char *b) _pure_; + +static inline bool _pure_ in_charset(const char *s, const char* charset) { + assert(s); + assert(charset); + return s[strspn(s, charset)] == '\0'; +} + +bool string_has_cc(const char *p, const char *ok) _pure_; + +char *ellipsize_mem(const char *s, size_t old_length_bytes, size_t new_length_columns, unsigned percent); +char *ellipsize(const char *s, size_t length, unsigned percent); + +bool nulstr_contains(const char*nulstr, const char *needle); + +char* strshorten(char *s, size_t l); + +char *strreplace(const char *text, const char *old_string, const char *new_string); + +char *strip_tab_ansi(char **p, size_t *l); + +char *strextend(char **x, ...) _sentinel_; + +char *strrep(const char *s, unsigned n); + +int split_pair(const char *s, const char *sep, char **l, char **r); + +int free_and_strdup(char **p, const char *s); + +/* Normal memmem() requires haystack to be nonnull, which is annoying for zero-length buffers */ +static inline void *memmem_safe(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { + + if (needlelen <= 0) + return (void*) haystack; + + if (haystacklen < needlelen) + return NULL; + + assert(haystack); + assert(needle); + + return memmem(haystack, haystacklen, needle, needlelen); +} + +void* memory_erase(void *p, size_t l); +char *string_erase(char *x); + +char *string_free_erase(char *s); +DEFINE_TRIVIAL_CLEANUP_FUNC(char *, string_free_erase); +#define _cleanup_string_free_erase_ _cleanup_(string_free_erasep) + +bool string_is_safe(const char *p) _pure_; diff --git a/src/libbasic/strv.c b/src/libbasic/strv.c new file mode 100644 index 0000000000..b5d4d8191b --- /dev/null +++ b/src/libbasic/strv.c @@ -0,0 +1,891 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "escape.h" +#include "extract-word.h" +#include "fileio.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +char *strv_find(char **l, const char *name) { + char **i; + + assert(name); + + STRV_FOREACH(i, l) + if (streq(*i, name)) + return *i; + + return NULL; +} + +char *strv_find_prefix(char **l, const char *name) { + char **i; + + assert(name); + + STRV_FOREACH(i, l) + if (startswith(*i, name)) + return *i; + + return NULL; +} + +char *strv_find_startswith(char **l, const char *name) { + char **i, *e; + + assert(name); + + /* Like strv_find_prefix, but actually returns only the + * suffix, not the whole item */ + + STRV_FOREACH(i, l) { + e = startswith(*i, name); + if (e) + return e; + } + + return NULL; +} + +void strv_clear(char **l) { + char **k; + + if (!l) + return; + + for (k = l; *k; k++) + free(*k); + + *l = NULL; +} + +char **strv_free(char **l) { + strv_clear(l); + free(l); + return NULL; +} + +char **strv_free_erase(char **l) { + char **i; + + STRV_FOREACH(i, l) + string_erase(*i); + + return strv_free(l); +} + +char **strv_copy(char * const *l) { + char **r, **k; + + k = r = new(char*, strv_length(l) + 1); + if (!r) + return NULL; + + if (l) + for (; *l; k++, l++) { + *k = strdup(*l); + if (!*k) { + strv_free(r); + return NULL; + } + } + + *k = NULL; + return r; +} + +unsigned strv_length(char * const *l) { + unsigned n = 0; + + if (!l) + return 0; + + for (; *l; l++) + n++; + + return n; +} + +char **strv_new_ap(const char *x, va_list ap) { + const char *s; + char **a; + unsigned n = 0, i = 0; + 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_IFNOTNULL() macro to include possibly NULL strings in + * the string list. */ + + if (x) { + n = x == (const char*) -1 ? 0 : 1; + + va_copy(aq, ap); + while ((s = va_arg(aq, const char*))) { + if (s == (const char*) -1) + continue; + + n++; + } + + va_end(aq); + } + + a = new(char*, n+1); + if (!a) + return NULL; + + if (x) { + if (x != (const char*) -1) { + a[i] = strdup(x); + if (!a[i]) + goto fail; + i++; + } + + while ((s = va_arg(ap, const char*))) { + + if (s == (const char*) -1) + continue; + + a[i] = strdup(s); + if (!a[i]) + goto fail; + + i++; + } + } + + a[i] = NULL; + + return a; + +fail: + strv_free(a); + return NULL; +} + +char **strv_new(const char *x, ...) { + char **r; + va_list ap; + + va_start(ap, x); + r = strv_new_ap(x, ap); + va_end(ap); + + return r; +} + +int strv_extend_strv(char ***a, char **b, bool filter_duplicates) { + char **s, **t; + size_t p, q, i = 0, j; + + assert(a); + + if (strv_isempty(b)) + return 0; + + p = strv_length(*a); + q = strv_length(b); + + t = realloc(*a, sizeof(char*) * (p + q + 1)); + if (!t) + return -ENOMEM; + + t[p] = NULL; + *a = t; + + STRV_FOREACH(s, b) { + + if (filter_duplicates && strv_contains(t, *s)) + continue; + + t[p+i] = strdup(*s); + if (!t[p+i]) + goto rollback; + + i++; + t[p+i] = NULL; + } + + assert(i <= q); + + return (int) i; + +rollback: + for (j = 0; j < i; j++) + free(t[p + j]); + + t[p] = NULL; + return -ENOMEM; +} + +int strv_extend_strv_concat(char ***a, char **b, const char *suffix) { + int r; + char **s; + + STRV_FOREACH(s, b) { + char *v; + + v = strappend(*s, suffix); + if (!v) + return -ENOMEM; + + r = strv_push(a, v); + if (r < 0) { + free(v); + return r; + } + } + + return 0; +} + +char **strv_split(const char *s, const char *separator) { + const char *word, *state; + size_t l; + unsigned n, i; + char **r; + + assert(s); + + n = 0; + FOREACH_WORD_SEPARATOR(word, l, s, separator, state) + n++; + + r = new(char*, n+1); + if (!r) + return NULL; + + i = 0; + FOREACH_WORD_SEPARATOR(word, l, s, separator, state) { + r[i] = strndup(word, l); + if (!r[i]) { + strv_free(r); + return NULL; + } + + i++; + } + + r[i] = NULL; + return r; +} + +char **strv_split_newlines(const char *s) { + char **l; + unsigned n; + + assert(s); + + /* Special version of strv_split() that splits on newlines and + * suppresses an empty string at the end */ + + l = strv_split(s, NEWLINE); + if (!l) + return NULL; + + n = strv_length(l); + if (n <= 0) + return l; + + if (isempty(l[n - 1])) + l[n - 1] = mfree(l[n - 1]); + + return l; +} + +int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags) { + _cleanup_strv_free_ char **l = NULL; + size_t n = 0, allocated = 0; + int r; + + assert(t); + assert(s); + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&s, &word, separators, flags); + if (r < 0) + return r; + if (r == 0) + break; + + if (!GREEDY_REALLOC(l, allocated, n + 2)) + return -ENOMEM; + + l[n++] = word; + word = NULL; + + l[n] = NULL; + } + + if (!l) { + l = new0(char*, 1); + if (!l) + return -ENOMEM; + } + + *t = l; + l = NULL; + + return (int) n; +} + +char *strv_join(char **l, const char *separator) { + char *r, *e; + char **s; + size_t n, k; + + if (!separator) + separator = " "; + + k = strlen(separator); + + n = 0; + STRV_FOREACH(s, l) { + if (n != 0) + n += k; + n += strlen(*s); + } + + r = new(char, n+1); + if (!r) + return NULL; + + e = r; + STRV_FOREACH(s, l) { + if (e != r) + e = stpcpy(e, separator); + + e = stpcpy(e, *s); + } + + *e = 0; + + return r; +} + +char *strv_join_quoted(char **l) { + char *buf = NULL; + char **s; + size_t allocated = 0, len = 0; + + STRV_FOREACH(s, l) { + /* assuming here that escaped string cannot be more + * than twice as long, and reserving space for the + * separator and quotes. + */ + _cleanup_free_ char *esc = NULL; + size_t needed; + + if (!GREEDY_REALLOC(buf, allocated, + len + strlen(*s) * 2 + 3)) + goto oom; + + esc = cescape(*s); + if (!esc) + goto oom; + + needed = snprintf(buf + len, allocated - len, "%s\"%s\"", + len > 0 ? " " : "", esc); + assert(needed < allocated - len); + len += needed; + } + + if (!buf) + buf = malloc0(1); + + return buf; + + oom: + free(buf); + return NULL; +} + +int strv_push(char ***l, char *value) { + char **c; + unsigned n, m; + + if (!value) + return 0; + + n = strv_length(*l); + + /* Increase and check for overflow */ + m = n + 2; + if (m < n) + return -ENOMEM; + + c = realloc_multiply(*l, sizeof(char*), m); + if (!c) + return -ENOMEM; + + c[n] = value; + c[n+1] = NULL; + + *l = c; + return 0; +} + +int strv_push_pair(char ***l, char *a, char *b) { + char **c; + unsigned n, m; + + if (!a && !b) + return 0; + + n = strv_length(*l); + + /* increase and check for overflow */ + m = n + !!a + !!b + 1; + if (m < n) + return -ENOMEM; + + c = realloc_multiply(*l, sizeof(char*), m); + if (!c) + return -ENOMEM; + + if (a) + c[n++] = a; + if (b) + c[n++] = b; + c[n] = NULL; + + *l = c; + return 0; +} + +int strv_push_prepend(char ***l, char *value) { + char **c; + unsigned n, m, i; + + if (!value) + return 0; + + n = strv_length(*l); + + /* increase and check for overflow */ + m = n + 2; + if (m < n) + return -ENOMEM; + + c = new(char*, m); + if (!c) + return -ENOMEM; + + for (i = 0; i < n; i++) + c[i+1] = (*l)[i]; + + c[0] = value; + c[n+1] = NULL; + + free(*l); + *l = c; + + return 0; +} + +int strv_consume(char ***l, char *value) { + int r; + + r = strv_push(l, value); + if (r < 0) + free(value); + + return r; +} + +int strv_consume_pair(char ***l, char *a, char *b) { + int r; + + r = strv_push_pair(l, a, b); + if (r < 0) { + free(a); + free(b); + } + + return r; +} + +int strv_consume_prepend(char ***l, char *value) { + int r; + + r = strv_push_prepend(l, value); + if (r < 0) + free(value); + + return r; +} + +int strv_extend(char ***l, const char *value) { + char *v; + + if (!value) + return 0; + + v = strdup(value); + if (!v) + return -ENOMEM; + + return strv_consume(l, v); +} + +char **strv_uniq(char **l) { + char **i; + + /* Drops duplicate entries. The first identical string will be + * kept, the others dropped */ + + STRV_FOREACH(i, l) + strv_remove(i+1, *i); + + return l; +} + +bool strv_is_uniq(char **l) { + char **i; + + STRV_FOREACH(i, l) + if (strv_find(i+1, *i)) + return false; + + return true; +} + +char **strv_remove(char **l, const char *s) { + char **f, **t; + + if (!l) + return NULL; + + assert(s); + + /* Drops every occurrence of s in the string list, edits + * in-place. */ + + for (f = t = l; *f; f++) + if (streq(*f, s)) + free(*f); + else + *(t++) = *f; + + *t = NULL; + return l; +} + +char **strv_parse_nulstr(const char *s, size_t l) { + const char *p; + unsigned c = 0, i = 0; + char **v; + + assert(s || l <= 0); + + if (l <= 0) + return new0(char*, 1); + + for (p = s; p < s + l; p++) + if (*p == 0) + c++; + + if (s[l-1] != 0) + c++; + + v = new0(char*, c+1); + if (!v) + return NULL; + + p = s; + while (p < s + l) { + const char *e; + + e = memchr(p, 0, s + l - p); + + v[i] = strndup(p, e ? e - p : s + l - p); + if (!v[i]) { + strv_free(v); + return NULL; + } + + i++; + + if (!e) + break; + + p = e + 1; + } + + assert(i == c); + + return v; +} + +char **strv_split_nulstr(const char *s) { + const char *i; + char **r = NULL; + + NULSTR_FOREACH(i, s) + if (strv_extend(&r, i) < 0) { + strv_free(r); + return NULL; + } + + if (!r) + return strv_new(NULL, NULL); + + return r; +} + +int strv_make_nulstr(char **l, char **p, size_t *q) { + size_t n_allocated = 0, n = 0; + _cleanup_free_ char *m = NULL; + char **i; + + assert(p); + assert(q); + + STRV_FOREACH(i, l) { + size_t z; + + z = strlen(*i); + + if (!GREEDY_REALLOC(m, n_allocated, n + z + 1)) + return -ENOMEM; + + memcpy(m + n, *i, z + 1); + n += z + 1; + } + + if (!m) { + m = new0(char, 1); + if (!m) + return -ENOMEM; + n = 0; + } + + *p = m; + *q = n; + + m = NULL; + + return 0; +} + +bool strv_overlap(char **a, char **b) { + char **i; + + STRV_FOREACH(i, a) + if (strv_contains(b, *i)) + return true; + + return false; +} + +static int str_compare(const void *_a, const void *_b) { + const char **a = (const char**) _a, **b = (const char**) _b; + + return strcmp(*a, *b); +} + +char **strv_sort(char **l) { + + if (strv_isempty(l)) + return l; + + qsort(l, strv_length(l), sizeof(char*), str_compare); + return l; +} + +bool strv_equal(char **a, char **b) { + + if (strv_isempty(a)) + return strv_isempty(b); + + if (strv_isempty(b)) + return false; + + for ( ; *a || *b; ++a, ++b) + if (!streq_ptr(*a, *b)) + return false; + + return true; +} + +void strv_print(char **l) { + char **s; + + STRV_FOREACH(s, l) + puts(*s); +} + +int strv_extendf(char ***l, const char *format, ...) { + va_list ap; + char *x; + int r; + + va_start(ap, format); + r = vasprintf(&x, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return strv_consume(l, x); +} + +char **strv_reverse(char **l) { + unsigned n, i; + + n = strv_length(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; + } + + return l; +} + +char **strv_shell_escape(char **l, const char *bad) { + char **s; + + /* Escapes every character in every string in l that is in bad, + * edits in-place, does not roll-back on error. */ + + STRV_FOREACH(s, l) { + char *v; + + v = shell_escape(*s, bad); + if (!v) + return NULL; + + free(*s); + *s = v; + } + + return l; +} + +bool strv_fnmatch(char* const* patterns, const char *s, int flags) { + char* const* p; + + STRV_FOREACH(p, patterns) + if (fnmatch(*p, s, 0) == 0) + return true; + + return false; +} + +char ***strv_free_free(char ***l) { + char ***i; + + if (!l) + return NULL; + + for (i = l; *i; i++) + strv_free(*i); + + free(l); + return NULL; +} + +char **strv_skip(char **l, size_t n) { + + while (n > 0) { + if (strv_isempty(l)) + return l; + + l++, n--; + } + + return l; +} + +int strv_extend_n(char ***l, const char *value, size_t n) { + size_t i, j, k; + char **nl; + + assert(l); + + if (!value) + return 0; + if (n == 0) + return 0; + + /* Adds the value value n times to l */ + + k = strv_length(*l); + + nl = realloc(*l, sizeof(char*) * (k + n + 1)); + if (!nl) + return -ENOMEM; + + *l = nl; + + for (i = k; i < k + n; i++) { + nl[i] = strdup(value); + if (!nl[i]) + goto rollback; + } + + nl[i] = NULL; + return 0; + +rollback: + for (j = k; j < i; j++) + free(nl[j]); + + nl[k] = NULL; + return -ENOMEM; +} + +int fputstrv(FILE *f, char **l, const char *separator, bool *space) { + bool b = false; + char **s; + int r; + + /* Like fputs(), but for strv, and with a less stupid argument order */ + + if (!space) + space = &b; + + STRV_FOREACH(s, l) { + r = fputs_with_space(f, *s, separator, space); + if (r < 0) + return r; + } + + return 0; +} diff --git a/src/libbasic/strv.h b/src/libbasic/strv.h new file mode 100644 index 0000000000..7bfa54408d --- /dev/null +++ b/src/libbasic/strv.h @@ -0,0 +1,171 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "extract-word.h" +#include "macro.h" +#include "util.h" + +char *strv_find(char **l, const char *name) _pure_; +char *strv_find_prefix(char **l, const char *name) _pure_; +char *strv_find_startswith(char **l, const char *name) _pure_; + +char **strv_free(char **l); +DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free); +#define _cleanup_strv_free_ _cleanup_(strv_freep) + +char **strv_free_erase(char **l); +DEFINE_TRIVIAL_CLEANUP_FUNC(char**, strv_free_erase); +#define _cleanup_strv_free_erase_ _cleanup_(strv_free_erasep) + +void strv_clear(char **l); + +char **strv_copy(char * const *l); +unsigned strv_length(char * const *l) _pure_; + +int strv_extend_strv(char ***a, char **b, bool filter_duplicates); +int strv_extend_strv_concat(char ***a, char **b, const char *suffix); +int strv_extend(char ***l, const char *value); +int strv_extendf(char ***l, const char *format, ...) _printf_(2,0); +int strv_push(char ***l, char *value); +int strv_push_pair(char ***l, char *a, char *b); +int strv_push_prepend(char ***l, char *value); +int strv_consume(char ***l, char *value); +int strv_consume_pair(char ***l, char *a, char *b); +int strv_consume_prepend(char ***l, char *value); + +char **strv_remove(char **l, const char *s); +char **strv_uniq(char **l); +bool strv_is_uniq(char **l); + +bool strv_equal(char **a, char **b); + +#define strv_contains(l, s) (!!strv_find((l), (s))) + +char **strv_new(const char *x, ...) _sentinel_; +char **strv_new_ap(const char *x, va_list ap); + +static inline const char* STRV_IFNOTNULL(const char *x) { + return x ? x : (const char *) -1; +} + +static inline bool strv_isempty(char * const *l) { + return !l || !*l; +} + +char **strv_split(const char *s, const char *separator); +char **strv_split_newlines(const char *s); + +int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags); + +char *strv_join(char **l, const char *separator); +char *strv_join_quoted(char **l); + +char **strv_parse_nulstr(const char *s, size_t l); +char **strv_split_nulstr(const char *s); +int strv_make_nulstr(char **l, char **p, size_t *n); + +bool strv_overlap(char **a, char **b) _pure_; + +#define STRV_FOREACH(s, l) \ + for ((s) = (l); (s) && *(s); (s)++) + +#define STRV_FOREACH_BACKWARDS(s, l) \ + STRV_FOREACH(s, l) \ + ; \ + for ((s)--; (l) && ((s) >= (l)); (s)--) + +#define STRV_FOREACH_PAIR(x, y, l) \ + for ((x) = (l), (y) = (x+1); (x) && *(x) && *(y); (x) += 2, (y) = (x + 1)) + +char **strv_sort(char **l); +void strv_print(char **l); + +#define STRV_MAKE(...) ((char**) ((const char*[]) { __VA_ARGS__, NULL })) + +#define STRV_MAKE_EMPTY ((char*[1]) { NULL }) + +#define strv_from_stdarg_alloca(first) \ + ({ \ + char **_l; \ + \ + if (!first) \ + _l = (char**) &first; \ + else { \ + unsigned _n; \ + va_list _ap; \ + \ + _n = 1; \ + va_start(_ap, first); \ + while (va_arg(_ap, char*)) \ + _n++; \ + va_end(_ap); \ + \ + _l = newa(char*, _n+1); \ + _l[_n = 0] = (char*) first; \ + va_start(_ap, first); \ + for (;;) { \ + _l[++_n] = va_arg(_ap, char*); \ + if (!_l[_n]) \ + break; \ + } \ + va_end(_ap); \ + } \ + _l; \ + }) + +#define STR_IN_SET(x, ...) strv_contains(STRV_MAKE(__VA_ARGS__), x) + +#define FOREACH_STRING(x, ...) \ + for (char **_l = ({ \ + char **_ll = STRV_MAKE(__VA_ARGS__); \ + x = _ll ? _ll[0] : NULL; \ + _ll; \ + }); \ + _l && *_l; \ + x = ({ \ + _l ++; \ + _l[0]; \ + })) + +char **strv_reverse(char **l); +char **strv_shell_escape(char **l, const char *bad); + +bool strv_fnmatch(char* const* patterns, const char *s, int flags); + +static inline bool strv_fnmatch_or_empty(char* const* patterns, const char *s, int flags) { + assert(s); + return strv_isempty(patterns) || + strv_fnmatch(patterns, s, flags); +} + +char ***strv_free_free(char ***l); + +char **strv_skip(char **l, size_t n); + +int strv_extend_n(char ***l, const char *value, size_t n); + +int fputstrv(FILE *f, char **l, const char *separator, bool *space); diff --git a/src/libbasic/strxcpyx.c b/src/libbasic/strxcpyx.c new file mode 100644 index 0000000000..aaf11d21f6 --- /dev/null +++ b/src/libbasic/strxcpyx.c @@ -0,0 +1,100 @@ +/*** + This file is part of systemd. + + 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 . +***/ + +/* + * Concatenates/copies strings. In any case, terminates in all cases + * with '\0' * and moves the @dest pointer forward to the added '\0'. + * Returns the * remaining size, and 0 if the string was truncated. + */ + +#include +#include +#include + +#include "strxcpyx.h" + +size_t strpcpy(char **dest, size_t size, const char *src) { + size_t len; + + len = strlen(src); + if (len >= size) { + if (size > 1) + *dest = mempcpy(*dest, src, size-1); + size = 0; + } else { + if (len > 0) { + *dest = mempcpy(*dest, src, len); + size -= len; + } + } + *dest[0] = '\0'; + return size; +} + +size_t strpcpyf(char **dest, size_t size, const char *src, ...) { + va_list va; + int i; + + va_start(va, src); + i = vsnprintf(*dest, size, src, va); + if (i < (int)size) { + *dest += i; + size -= i; + } else { + *dest += size; + size = 0; + } + va_end(va); + *dest[0] = '\0'; + return size; +} + +size_t strpcpyl(char **dest, size_t size, const char *src, ...) { + va_list va; + + va_start(va, src); + do { + size = strpcpy(dest, size, src); + src = va_arg(va, char *); + } while (src != NULL); + va_end(va); + return size; +} + +size_t strscpy(char *dest, size_t size, const char *src) { + char *s; + + s = dest; + return strpcpy(&s, size, src); +} + +size_t strscpyl(char *dest, size_t size, const char *src, ...) { + va_list va; + char *s; + + va_start(va, src); + s = dest; + do { + size = strpcpy(&s, size, src); + src = va_arg(va, char *); + } while (src != NULL); + va_end(va); + + return size; +} diff --git a/src/libbasic/strxcpyx.h b/src/libbasic/strxcpyx.h new file mode 100644 index 0000000000..80ff58726b --- /dev/null +++ b/src/libbasic/strxcpyx.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + This file is part of systemd. + + 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 . +***/ + + +#include + +#include "macro.h" + +size_t strpcpy(char **dest, size_t size, const char *src); +size_t strpcpyf(char **dest, size_t size, const char *src, ...) _printf_(3, 4); +size_t strpcpyl(char **dest, size_t size, const char *src, ...) _sentinel_; +size_t strscpy(char *dest, size_t size, const char *src); +size_t strscpyl(char *dest, size_t size, const char *src, ...) _sentinel_; diff --git a/src/libbasic/syslog-util.c b/src/libbasic/syslog-util.c new file mode 100644 index 0000000000..db3405154e --- /dev/null +++ b/src/libbasic/syslog-util.c @@ -0,0 +1,114 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "hexdecoct.h" +#include "macro.h" +#include "string-table.h" +#include "syslog-util.h" + +int syslog_parse_priority(const char **p, int *priority, bool with_facility) { + int a = 0, b = 0, c = 0; + int k; + + assert(p); + assert(*p); + assert(priority); + + if ((*p)[0] != '<') + return 0; + + if (!strchr(*p, '>')) + return 0; + + if ((*p)[2] == '>') { + c = undecchar((*p)[1]); + k = 3; + } else if ((*p)[3] == '>') { + b = undecchar((*p)[1]); + c = undecchar((*p)[2]); + k = 4; + } else if ((*p)[4] == '>') { + a = undecchar((*p)[1]); + b = undecchar((*p)[2]); + c = undecchar((*p)[3]); + k = 5; + } else + return 0; + + if (a < 0 || b < 0 || c < 0 || + (!with_facility && (a || b || c > 7))) + return 0; + + if (with_facility) + *priority = a*100 + b*10 + c; + else + *priority = (*priority & LOG_FACMASK) | c; + + *p += k; + return 1; +} + +static const char *const log_facility_unshifted_table[LOG_NFACILITIES] = { + [LOG_FAC(LOG_KERN)] = "kern", + [LOG_FAC(LOG_USER)] = "user", + [LOG_FAC(LOG_MAIL)] = "mail", + [LOG_FAC(LOG_DAEMON)] = "daemon", + [LOG_FAC(LOG_AUTH)] = "auth", + [LOG_FAC(LOG_SYSLOG)] = "syslog", + [LOG_FAC(LOG_LPR)] = "lpr", + [LOG_FAC(LOG_NEWS)] = "news", + [LOG_FAC(LOG_UUCP)] = "uucp", + [LOG_FAC(LOG_CRON)] = "cron", + [LOG_FAC(LOG_AUTHPRIV)] = "authpriv", + [LOG_FAC(LOG_FTP)] = "ftp", + [LOG_FAC(LOG_LOCAL0)] = "local0", + [LOG_FAC(LOG_LOCAL1)] = "local1", + [LOG_FAC(LOG_LOCAL2)] = "local2", + [LOG_FAC(LOG_LOCAL3)] = "local3", + [LOG_FAC(LOG_LOCAL4)] = "local4", + [LOG_FAC(LOG_LOCAL5)] = "local5", + [LOG_FAC(LOG_LOCAL6)] = "local6", + [LOG_FAC(LOG_LOCAL7)] = "local7" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_facility_unshifted, int, LOG_FAC(~0)); + +bool log_facility_unshifted_is_valid(int facility) { + return facility >= 0 && facility <= LOG_FAC(~0); +} + +static const char *const log_level_table[] = { + [LOG_EMERG] = "emerg", + [LOG_ALERT] = "alert", + [LOG_CRIT] = "crit", + [LOG_ERR] = "err", + [LOG_WARNING] = "warning", + [LOG_NOTICE] = "notice", + [LOG_INFO] = "info", + [LOG_DEBUG] = "debug" +}; + +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(log_level, int, LOG_DEBUG); + +bool log_level_is_valid(int level) { + return level >= 0 && level <= LOG_DEBUG; +} diff --git a/src/libbasic/syslog-util.h b/src/libbasic/syslog-util.h new file mode 100644 index 0000000000..5cb606a1bf --- /dev/null +++ b/src/libbasic/syslog-util.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +int log_facility_unshifted_to_string_alloc(int i, char **s); +int log_facility_unshifted_from_string(const char *s); +bool log_facility_unshifted_is_valid(int faciliy); + +int log_level_to_string_alloc(int i, char **s); +int log_level_from_string(const char *s); +bool log_level_is_valid(int level); + +int syslog_parse_priority(const char **p, int *priority, bool with_facility); diff --git a/src/libbasic/terminal-util.c b/src/libbasic/terminal-util.c new file mode 100644 index 0000000000..0a9d2bbdef --- /dev/null +++ b/src/libbasic/terminal-util.c @@ -0,0 +1,1148 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "io-util.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "process-util.h" +#include "socket-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "terminal-util.h" +#include "time-util.h" +#include "util.h" + +static volatile unsigned cached_columns = 0; +static volatile unsigned cached_lines = 0; + +int chvt(int vt) { + _cleanup_close_ int fd; + + fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) + return -errno; + + if (vt <= 0) { + int tiocl[2] = { + TIOCL_GETKMSGREDIRECT, + 0 + }; + + if (ioctl(fd, TIOCLINUX, tiocl) < 0) + return -errno; + + vt = tiocl[0] <= 0 ? 1 : tiocl[0]; + } + + if (ioctl(fd, VT_ACTIVATE, vt) < 0) + return -errno; + + return 0; +} + +int read_one_char(FILE *f, char *ret, usec_t t, bool *need_nl) { + struct termios old_termios, new_termios; + char c, line[LINE_MAX]; + + assert(f); + assert(ret); + + if (tcgetattr(fileno(f), &old_termios) >= 0) { + new_termios = old_termios; + + new_termios.c_lflag &= ~ICANON; + new_termios.c_cc[VMIN] = 1; + new_termios.c_cc[VTIME] = 0; + + if (tcsetattr(fileno(f), TCSADRAIN, &new_termios) >= 0) { + size_t k; + + if (t != USEC_INFINITY) { + if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) { + tcsetattr(fileno(f), TCSADRAIN, &old_termios); + return -ETIMEDOUT; + } + } + + k = fread(&c, 1, 1, f); + + tcsetattr(fileno(f), TCSADRAIN, &old_termios); + + if (k <= 0) + return -EIO; + + if (need_nl) + *need_nl = c != '\n'; + + *ret = c; + return 0; + } + } + + if (t != USEC_INFINITY) { + if (fd_wait_for_event(fileno(f), POLLIN, t) <= 0) + return -ETIMEDOUT; + } + + errno = 0; + if (!fgets(line, sizeof(line), f)) + return errno > 0 ? -errno : -EIO; + + truncate_nl(line); + + if (strlen(line) != 1) + return -EBADMSG; + + if (need_nl) + *need_nl = false; + + *ret = line[0]; + return 0; +} + +int ask_char(char *ret, const char *replies, const char *text, ...) { + int r; + + assert(ret); + assert(replies); + assert(text); + + for (;;) { + va_list ap; + char c; + bool need_nl = true; + + if (on_tty()) + fputs(ANSI_HIGHLIGHT, stdout); + + va_start(ap, text); + vprintf(text, ap); + va_end(ap); + + if (on_tty()) + fputs(ANSI_NORMAL, stdout); + + fflush(stdout); + + r = read_one_char(stdin, &c, USEC_INFINITY, &need_nl); + if (r < 0) { + + if (r == -EBADMSG) { + puts("Bad input, please try again."); + continue; + } + + putchar('\n'); + return r; + } + + if (need_nl) + putchar('\n'); + + if (strchr(replies, c)) { + *ret = c; + return 0; + } + + puts("Read unexpected character, please try again."); + } +} + +int ask_string(char **ret, const char *text, ...) { + assert(ret); + assert(text); + + for (;;) { + char line[LINE_MAX]; + va_list ap; + + if (on_tty()) + fputs(ANSI_HIGHLIGHT, stdout); + + va_start(ap, text); + vprintf(text, ap); + va_end(ap); + + if (on_tty()) + fputs(ANSI_NORMAL, stdout); + + fflush(stdout); + + errno = 0; + if (!fgets(line, sizeof(line), stdin)) + return errno > 0 ? -errno : -EIO; + + if (!endswith(line, "\n")) + putchar('\n'); + else { + char *s; + + if (isempty(line)) + continue; + + truncate_nl(line); + s = strdup(line); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; + } + } +} + +int reset_terminal_fd(int fd, bool switch_to_text) { + struct termios termios; + int r = 0; + + /* Set terminal to some sane defaults */ + + assert(fd >= 0); + + /* We leave locked terminal attributes untouched, so that + * Plymouth may set whatever it wants to set, and we don't + * interfere with that. */ + + /* Disable exclusive mode, just in case */ + (void) ioctl(fd, TIOCNXCL); + + /* Switch to text mode */ + if (switch_to_text) + (void) ioctl(fd, KDSETMODE, KD_TEXT); + + /* Enable console unicode mode */ + (void) ioctl(fd, KDSKBMODE, K_UNICODE); + + if (tcgetattr(fd, &termios) < 0) { + r = -errno; + goto finish; + } + + /* We only reset the stuff that matters to the software. How + * hardware is set up we don't touch assuming that somebody + * else will do that for us */ + + termios.c_iflag &= ~(IGNBRK | BRKINT | ISTRIP | INLCR | IGNCR | IUCLC); + termios.c_iflag |= ICRNL | IMAXBEL | IUTF8; + termios.c_oflag |= ONLCR; + termios.c_cflag |= CREAD; + termios.c_lflag = ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOPRT | ECHOKE; + + termios.c_cc[VINTR] = 03; /* ^C */ + termios.c_cc[VQUIT] = 034; /* ^\ */ + termios.c_cc[VERASE] = 0177; + termios.c_cc[VKILL] = 025; /* ^X */ + termios.c_cc[VEOF] = 04; /* ^D */ + termios.c_cc[VSTART] = 021; /* ^Q */ + termios.c_cc[VSTOP] = 023; /* ^S */ + termios.c_cc[VSUSP] = 032; /* ^Z */ + termios.c_cc[VLNEXT] = 026; /* ^V */ + termios.c_cc[VWERASE] = 027; /* ^W */ + termios.c_cc[VREPRINT] = 022; /* ^R */ + termios.c_cc[VEOL] = 0; + termios.c_cc[VEOL2] = 0; + + termios.c_cc[VTIME] = 0; + termios.c_cc[VMIN] = 1; + + if (tcsetattr(fd, TCSANOW, &termios) < 0) + r = -errno; + +finish: + /* Just in case, flush all crap out */ + (void) tcflush(fd, TCIOFLUSH); + + return r; +} + +int reset_terminal(const char *name) { + _cleanup_close_ int fd = -1; + + /* We open the terminal with O_NONBLOCK here, to ensure we + * don't block on carrier if this is a terminal with carrier + * configured. */ + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) + return fd; + + return reset_terminal_fd(fd, true); +} + +int open_terminal(const char *name, int mode) { + int fd, r; + unsigned c = 0; + + /* + * If a TTY is in the process of being closed opening it might + * cause EIO. This is horribly awful, but unlikely to be + * changed in the kernel. Hence we work around this problem by + * retrying a couple of times. + * + * https://bugs.launchpad.net/ubuntu/+source/linux/+bug/554172/comments/245 + */ + + if (mode & O_CREAT) + return -EINVAL; + + for (;;) { + fd = open(name, mode, 0); + if (fd >= 0) + break; + + if (errno != EIO) + return -errno; + + /* Max 1s in total */ + if (c >= 20) + return -errno; + + usleep(50 * USEC_PER_MSEC); + c++; + } + + r = isatty(fd); + if (r < 0) { + safe_close(fd); + return -errno; + } + + if (!r) { + safe_close(fd); + return -ENOTTY; + } + + return fd; +} + +int acquire_terminal( + const char *name, + bool fail, + bool force, + bool ignore_tiocstty_eperm, + usec_t timeout) { + + int fd = -1, notify = -1, r = 0, wd = -1; + usec_t ts = 0; + + assert(name); + + /* We use inotify to be notified when the tty is closed. We + * create the watch before checking if we can actually acquire + * it, so that we don't lose any event. + * + * Note: strictly speaking this actually watches for the + * device being closed, it does *not* really watch whether a + * tty loses its controlling process. However, unless some + * rogue process uses TIOCNOTTY on /dev/tty *after* closing + * its tty otherwise this will not become a problem. As long + * as the administrator makes sure not configure any service + * on the same tty as an untrusted user this should not be a + * problem. (Which he probably should not do anyway.) */ + + if (timeout != USEC_INFINITY) + ts = now(CLOCK_MONOTONIC); + + if (!fail && !force) { + notify = inotify_init1(IN_CLOEXEC | (timeout != USEC_INFINITY ? IN_NONBLOCK : 0)); + if (notify < 0) { + r = -errno; + goto fail; + } + + wd = inotify_add_watch(notify, name, IN_CLOSE); + if (wd < 0) { + r = -errno; + goto fail; + } + } + + for (;;) { + struct sigaction sa_old, sa_new = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + + if (notify >= 0) { + r = flush_fd(notify); + if (r < 0) + goto fail; + } + + /* We pass here O_NOCTTY only so that we can check the return + * value TIOCSCTTY and have a reliable way to figure out if we + * successfully became the controlling process of the tty */ + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed + * if we already own the tty. */ + assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); + + /* First, try to get the tty */ + if (ioctl(fd, TIOCSCTTY, force) < 0) + r = -errno; + + assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); + + /* Sometimes, it makes sense to ignore TIOCSCTTY + * returning EPERM, i.e. when very likely we already + * are have this controlling terminal. */ + if (r < 0 && r == -EPERM && ignore_tiocstty_eperm) + r = 0; + + if (r < 0 && (force || fail || r != -EPERM)) + goto fail; + + if (r >= 0) + break; + + assert(!fail); + assert(!force); + assert(notify >= 0); + + for (;;) { + union inotify_event_buffer buffer; + struct inotify_event *e; + ssize_t l; + + if (timeout != USEC_INFINITY) { + usec_t n; + + n = now(CLOCK_MONOTONIC); + if (ts + timeout < n) { + r = -ETIMEDOUT; + goto fail; + } + + r = fd_wait_for_event(fd, POLLIN, ts + timeout - n); + if (r < 0) + goto fail; + + if (r == 0) { + r = -ETIMEDOUT; + goto fail; + } + } + + l = read(notify, &buffer, sizeof(buffer)); + if (l < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + + r = -errno; + goto fail; + } + + FOREACH_INOTIFY_EVENT(e, buffer, l) { + if (e->wd != wd || !(e->mask & IN_CLOSE)) { + r = -EIO; + goto fail; + } + } + + break; + } + + /* We close the tty fd here since if the old session + * ended our handle will be dead. It's important that + * we do this after sleeping, so that we don't enter + * an endless loop. */ + fd = safe_close(fd); + } + + safe_close(notify); + + return fd; + +fail: + safe_close(fd); + safe_close(notify); + + return r; +} + +int release_terminal(void) { + static const struct sigaction sa_new = { + .sa_handler = SIG_IGN, + .sa_flags = SA_RESTART, + }; + + _cleanup_close_ int fd = -1; + struct sigaction sa_old; + int r = 0; + + fd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) + return -errno; + + /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed + * by our own TIOCNOTTY */ + assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); + + if (ioctl(fd, TIOCNOTTY) < 0) + r = -errno; + + assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); + + return r; +} + +int terminal_vhangup_fd(int fd) { + assert(fd >= 0); + + if (ioctl(fd, TIOCVHANGUP) < 0) + return -errno; + + return 0; +} + +int terminal_vhangup(const char *name) { + _cleanup_close_ int fd; + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) + return fd; + + return terminal_vhangup_fd(fd); +} + +int vt_disallocate(const char *name) { + _cleanup_close_ int fd = -1; + unsigned u; + int r; + + /* Deallocate the VT if possible. If not possible + * (i.e. because it is the active one), at least clear it + * entirely (including the scrollback buffer) */ + + if (!startswith(name, "/dev/")) + return -EINVAL; + + if (!tty_is_vc(name)) { + /* So this is not a VT. I guess we cannot deallocate + * it then. But let's at least clear the screen */ + + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + loop_write(fd, + "\033[r" /* clear scrolling region */ + "\033[H" /* move home */ + "\033[2J", /* clear screen */ + 10, false); + return 0; + } + + if (!startswith(name, "/dev/tty")) + return -EINVAL; + + r = safe_atou(name+8, &u); + if (r < 0) + return r; + + if (u <= 0) + return -EINVAL; + + /* Try to deallocate */ + fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) + return fd; + + r = ioctl(fd, VT_DISALLOCATE, u); + fd = safe_close(fd); + + if (r >= 0) + return 0; + + if (errno != EBUSY) + return -errno; + + /* Couldn't deallocate, so let's clear it fully with + * scrollback */ + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + loop_write(fd, + "\033[r" /* clear scrolling region */ + "\033[H" /* move home */ + "\033[3J", /* clear screen including scrollback, requires Linux 2.6.40 */ + 10, false); + return 0; +} + +int make_console_stdio(void) { + int fd, r; + + /* Make /dev/console the controlling terminal and stdin/stdout/stderr */ + + fd = acquire_terminal("/dev/console", false, true, true, USEC_INFINITY); + if (fd < 0) + return log_error_errno(fd, "Failed to acquire terminal: %m"); + + r = reset_terminal_fd(fd, true); + if (r < 0) + log_warning_errno(r, "Failed to reset terminal, ignoring: %m"); + + r = make_stdio(fd); + if (r < 0) + return log_error_errno(r, "Failed to duplicate terminal fd: %m"); + + return 0; +} + +bool tty_is_vc(const char *tty) { + assert(tty); + + return vtnr_from_tty(tty) >= 0; +} + +bool tty_is_console(const char *tty) { + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + return streq(tty, "console"); +} + +int vtnr_from_tty(const char *tty) { + int i, r; + + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + if (!startswith(tty, "tty") ) + return -EINVAL; + + if (tty[3] < '0' || tty[3] > '9') + return -EINVAL; + + r = safe_atoi(tty+3, &i); + if (r < 0) + return r; + + if (i < 0 || i > 63) + return -EINVAL; + + return i; +} + +char *resolve_dev_console(char **active) { + char *tty; + + /* Resolve where /dev/console is pointing to, if /sys is actually ours + * (i.e. not read-only-mounted which is a sign for container setups) */ + + if (path_is_read_only_fs("/sys") > 0) + return NULL; + + if (read_one_line_file("/sys/class/tty/console/active", active) < 0) + return NULL; + + /* If multiple log outputs are configured the last one is what + * /dev/console points to */ + tty = strrchr(*active, ' '); + if (tty) + tty++; + else + tty = *active; + + if (streq(tty, "tty0")) { + char *tmp; + + /* Get the active VC (e.g. tty1) */ + if (read_one_line_file("/sys/class/tty/tty0/active", &tmp) >= 0) { + free(*active); + tty = *active = tmp; + } + } + + return tty; +} + +bool tty_is_vc_resolve(const char *tty) { + _cleanup_free_ char *active = NULL; + + assert(tty); + + if (startswith(tty, "/dev/")) + tty += 5; + + if (streq(tty, "console")) { + tty = resolve_dev_console(&active); + if (!tty) + return false; + } + + return tty_is_vc(tty); +} + +const char *default_term_for_tty(const char *tty) { + return tty && tty_is_vc_resolve(tty) ? "TERM=linux" : "TERM=vt220"; +} + +int fd_columns(int fd) { + struct winsize ws = {}; + + if (ioctl(fd, TIOCGWINSZ, &ws) < 0) + return -errno; + + if (ws.ws_col <= 0) + return -EIO; + + return ws.ws_col; +} + +unsigned columns(void) { + const char *e; + int c; + + if (_likely_(cached_columns > 0)) + return cached_columns; + + c = 0; + e = getenv("COLUMNS"); + if (e) + (void) safe_atoi(e, &c); + + if (c <= 0) + c = fd_columns(STDOUT_FILENO); + + if (c <= 0) + c = 80; + + cached_columns = c; + return cached_columns; +} + +int fd_lines(int fd) { + struct winsize ws = {}; + + if (ioctl(fd, TIOCGWINSZ, &ws) < 0) + return -errno; + + if (ws.ws_row <= 0) + return -EIO; + + return ws.ws_row; +} + +unsigned lines(void) { + const char *e; + int l; + + if (_likely_(cached_lines > 0)) + return cached_lines; + + l = 0; + e = getenv("LINES"); + if (e) + (void) safe_atoi(e, &l); + + if (l <= 0) + l = fd_lines(STDOUT_FILENO); + + if (l <= 0) + l = 24; + + cached_lines = l; + return cached_lines; +} + +/* intended to be used as a SIGWINCH sighandler */ +void columns_lines_cache_reset(int signum) { + cached_columns = 0; + cached_lines = 0; +} + +bool on_tty(void) { + static int cached_on_tty = -1; + + if (_unlikely_(cached_on_tty < 0)) + cached_on_tty = isatty(STDOUT_FILENO) > 0; + + return cached_on_tty; +} + +int make_stdio(int fd) { + int r, s, t; + + assert(fd >= 0); + + r = dup2(fd, STDIN_FILENO); + s = dup2(fd, STDOUT_FILENO); + t = dup2(fd, STDERR_FILENO); + + if (fd >= 3) + safe_close(fd); + + if (r < 0 || s < 0 || t < 0) + return -errno; + + /* 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); + + return 0; +} + +int make_null_stdio(void) { + int null_fd; + + null_fd = open("/dev/null", O_RDWR|O_NOCTTY); + if (null_fd < 0) + return -errno; + + return make_stdio(null_fd); +} + +int getttyname_malloc(int fd, char **ret) { + size_t l = 100; + int r; + + assert(fd >= 0); + assert(ret); + + for (;;) { + char path[l]; + + r = ttyname_r(fd, path, sizeof(path)); + if (r == 0) { + const char *p; + char *c; + + p = startswith(path, "/dev/"); + c = strdup(p ?: path); + if (!c) + return -ENOMEM; + + *ret = c; + return 0; + } + + if (r != ERANGE) + return -r; + + l *= 2; + } + + return 0; +} + +int getttyname_harder(int fd, char **r) { + int k; + char *s = NULL; + + k = getttyname_malloc(fd, &s); + if (k < 0) + return k; + + if (streq(s, "tty")) { + free(s); + return get_ctty(0, NULL, r); + } + + *r = s; + return 0; +} + +int get_ctty_devnr(pid_t pid, dev_t *d) { + int r; + _cleanup_free_ char *line = NULL; + const char *p; + unsigned long ttynr; + + assert(pid >= 0); + + p = procfs_file_alloca(pid, "stat"); + r = read_one_line_file(p, &line); + if (r < 0) + return r; + + p = strrchr(line, ')'); + if (!p) + return -EIO; + + p++; + + if (sscanf(p, " " + "%*c " /* state */ + "%*d " /* ppid */ + "%*d " /* pgrp */ + "%*d " /* session */ + "%lu ", /* ttynr */ + &ttynr) != 1) + return -EIO; + + if (major(ttynr) == 0 && minor(ttynr) == 0) + return -ENXIO; + + if (d) + *d = (dev_t) ttynr; + + return 0; +} + +int get_ctty(pid_t pid, dev_t *_devnr, char **r) { + char fn[sizeof("/dev/char/")-1 + 2*DECIMAL_STR_MAX(unsigned) + 1 + 1], *b = NULL; + _cleanup_free_ char *s = NULL; + const char *p; + dev_t devnr; + int k; + + assert(r); + + k = get_ctty_devnr(pid, &devnr); + if (k < 0) + return k; + + sprintf(fn, "/dev/char/%u:%u", major(devnr), minor(devnr)); + + k = readlink_malloc(fn, &s); + if (k < 0) { + + if (k != -ENOENT) + return k; + + /* This is an ugly hack */ + if (major(devnr) == 136) { + if (asprintf(&b, "pts/%u", minor(devnr)) < 0) + return -ENOMEM; + } else { + /* Probably something like the ptys which have no + * symlink in /dev/char. Let's return something + * vaguely useful. */ + + b = strdup(fn + 5); + if (!b) + return -ENOMEM; + } + } else { + if (startswith(s, "/dev/")) + p = s + 5; + else if (startswith(s, "../")) + p = s + 3; + else + p = s; + + b = strdup(p); + if (!b) + return -ENOMEM; + } + + *r = b; + if (_devnr) + *_devnr = devnr; + + return 0; +} + +int ptsname_malloc(int fd, char **ret) { + size_t l = 100; + + assert(fd >= 0); + assert(ret); + + for (;;) { + char *c; + + c = new(char, l); + if (!c) + return -ENOMEM; + + if (ptsname_r(fd, c, l) == 0) { + *ret = c; + return 0; + } + if (errno != ERANGE) { + free(c); + return -errno; + } + + free(c); + l *= 2; + } +} + +int ptsname_namespace(int pty, char **ret) { + int no = -1, r; + + /* Like ptsname(), but doesn't assume that the path is + * accessible in the local namespace. */ + + r = ioctl(pty, TIOCGPTN, &no); + if (r < 0) + return -errno; + + if (no < 0) + return -EIO; + + if (asprintf(ret, "/dev/pts/%i", no) < 0) + return -ENOMEM; + + return 0; +} + +int openpt_in_namespace(pid_t pid, int flags) { + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + siginfo_t si; + pid_t child; + int r; + + assert(pid > 0); + + r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return -errno; + + if (child == 0) { + int master; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); + if (r < 0) + _exit(EXIT_FAILURE); + + master = posix_openpt(flags|O_NOCTTY|O_CLOEXEC); + if (master < 0) + _exit(EXIT_FAILURE); + + if (unlockpt(master) < 0) + _exit(EXIT_FAILURE); + + if (send_one_fd(pair[1], master, 0) < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) + return r; + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return -EIO; + + return receive_one_fd(pair[0], 0); +} + +int open_terminal_in_namespace(pid_t pid, const char *name, int mode) { + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + siginfo_t si; + pid_t child; + int r; + + r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return -errno; + + if (child == 0) { + int master; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); + if (r < 0) + _exit(EXIT_FAILURE); + + master = open_terminal(name, mode|O_NOCTTY|O_CLOEXEC); + if (master < 0) + _exit(EXIT_FAILURE); + + if (send_one_fd(pair[1], master, 0) < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) + return r; + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return -EIO; + + return receive_one_fd(pair[0], 0); +} + +bool colors_enabled(void) { + const char *colors; + + colors = getenv("SYSTEMD_COLORS"); + if (!colors) { + if (streq_ptr(getenv("TERM"), "dumb")) + return false; + return on_tty(); + } + + return parse_boolean(colors) != 0; +} diff --git a/src/libbasic/terminal-util.h b/src/libbasic/terminal-util.h new file mode 100644 index 0000000000..a7c96a77cb --- /dev/null +++ b/src/libbasic/terminal-util.h @@ -0,0 +1,126 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "macro.h" +#include "time-util.h" + +#define ANSI_RED "\x1B[0;31m" +#define ANSI_GREEN "\x1B[0;32m" +#define ANSI_UNDERLINE "\x1B[0;4m" +#define ANSI_HIGHLIGHT "\x1B[0;1;39m" +#define ANSI_HIGHLIGHT_RED "\x1B[0;1;31m" +#define ANSI_HIGHLIGHT_GREEN "\x1B[0;1;32m" +#define ANSI_HIGHLIGHT_YELLOW "\x1B[0;1;33m" +#define ANSI_HIGHLIGHT_BLUE "\x1B[0;1;34m" +#define ANSI_HIGHLIGHT_UNDERLINE "\x1B[0;1;4m" +#define ANSI_NORMAL "\x1B[0m" + +#define ANSI_ERASE_TO_END_OF_LINE "\x1B[K" + +/* Set cursor to top left corner and clear screen */ +#define ANSI_HOME_CLEAR "\x1B[H\x1B[2J" + +int reset_terminal_fd(int fd, bool switch_to_text); +int reset_terminal(const char *name); + +int open_terminal(const char *name, int mode); +int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm, usec_t timeout); +int release_terminal(void); + +int terminal_vhangup_fd(int fd); +int terminal_vhangup(const char *name); + +int chvt(int vt); + +int read_one_char(FILE *f, char *ret, usec_t timeout, bool *need_nl); +int ask_char(char *ret, const char *replies, const char *text, ...) _printf_(3, 4); +int ask_string(char **ret, const char *text, ...) _printf_(2, 3); + +int vt_disallocate(const char *name); + +char *resolve_dev_console(char **active); +bool tty_is_vc(const char *tty); +bool tty_is_vc_resolve(const char *tty); +bool tty_is_console(const char *tty) _pure_; +int vtnr_from_tty(const char *tty); +const char *default_term_for_tty(const char *tty); + +int make_stdio(int fd); +int make_null_stdio(void); +int make_console_stdio(void); + +int fd_columns(int fd); +unsigned columns(void); +int fd_lines(int fd); +unsigned lines(void); +void columns_lines_cache_reset(int _unused_ signum); + +bool on_tty(void); +bool colors_enabled(void); + +static inline const char *ansi_underline(void) { + return colors_enabled() ? ANSI_UNDERLINE : ""; +} + +static inline const char *ansi_highlight(void) { + return colors_enabled() ? ANSI_HIGHLIGHT : ""; +} + +static inline const char *ansi_highlight_underline(void) { + return colors_enabled() ? ANSI_HIGHLIGHT_UNDERLINE : ""; +} + +static inline const char *ansi_highlight_red(void) { + return colors_enabled() ? ANSI_HIGHLIGHT_RED : ""; +} + +static inline const char *ansi_highlight_green(void) { + return colors_enabled() ? ANSI_HIGHLIGHT_GREEN : ""; +} + +static inline const char *ansi_highlight_yellow(void) { + return colors_enabled() ? ANSI_HIGHLIGHT_YELLOW : ""; +} + +static inline const char *ansi_highlight_blue(void) { + return colors_enabled() ? ANSI_HIGHLIGHT_BLUE : ""; +} + +static inline const char *ansi_normal(void) { + return colors_enabled() ? ANSI_NORMAL : ""; +} + +int get_ctty_devnr(pid_t pid, dev_t *d); +int get_ctty(pid_t, dev_t *_devnr, char **r); + +int getttyname_malloc(int fd, char **r); +int getttyname_harder(int fd, char **r); + +int ptsname_malloc(int fd, char **ret); +int ptsname_namespace(int pty, char **ret); + +int openpt_in_namespace(pid_t pid, int flags); +int open_terminal_in_namespace(pid_t pid, const char *name, int mode); diff --git a/src/libbasic/time-util.c b/src/libbasic/time-util.c new file mode 100644 index 0000000000..3973850b44 --- /dev/null +++ b/src/libbasic/time-util.c @@ -0,0 +1,1116 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" + +static nsec_t timespec_load_nsec(const struct timespec *ts); + +usec_t now(clockid_t clock_id) { + struct timespec ts; + + assert_se(clock_gettime(clock_id, &ts) == 0); + + return timespec_load(&ts); +} + +nsec_t now_nsec(clockid_t clock_id) { + struct timespec ts; + + assert_se(clock_gettime(clock_id, &ts) == 0); + + return timespec_load_nsec(&ts); +} + +dual_timestamp* dual_timestamp_get(dual_timestamp *ts) { + assert(ts); + + ts->realtime = now(CLOCK_REALTIME); + ts->monotonic = now(CLOCK_MONOTONIC); + + return ts; +} + +dual_timestamp* dual_timestamp_from_realtime(dual_timestamp *ts, usec_t u) { + int64_t delta; + assert(ts); + + if (u == USEC_INFINITY || u <= 0) { + ts->realtime = ts->monotonic = u; + return ts; + } + + ts->realtime = u; + + delta = (int64_t) now(CLOCK_REALTIME) - (int64_t) u; + ts->monotonic = usec_sub(now(CLOCK_MONOTONIC), delta); + + return ts; +} + +dual_timestamp* dual_timestamp_from_monotonic(dual_timestamp *ts, usec_t u) { + int64_t delta; + assert(ts); + + if (u == USEC_INFINITY) { + ts->realtime = ts->monotonic = USEC_INFINITY; + return ts; + } + + ts->monotonic = u; + delta = (int64_t) now(CLOCK_MONOTONIC) - (int64_t) u; + ts->realtime = usec_sub(now(CLOCK_REALTIME), delta); + + return ts; +} + +dual_timestamp* dual_timestamp_from_boottime_or_monotonic(dual_timestamp *ts, usec_t u) { + int64_t delta; + + if (u == USEC_INFINITY) { + ts->realtime = ts->monotonic = USEC_INFINITY; + return ts; + } + + dual_timestamp_get(ts); + delta = (int64_t) now(clock_boottime_or_monotonic()) - (int64_t) u; + ts->realtime = usec_sub(ts->realtime, delta); + ts->monotonic = usec_sub(ts->monotonic, delta); + + return ts; +} + +usec_t timespec_load(const struct timespec *ts) { + assert(ts); + + if (ts->tv_sec == (time_t) -1 && + ts->tv_nsec == (long) -1) + return USEC_INFINITY; + + if ((usec_t) ts->tv_sec > (UINT64_MAX - (ts->tv_nsec / NSEC_PER_USEC)) / USEC_PER_SEC) + return USEC_INFINITY; + + return + (usec_t) ts->tv_sec * USEC_PER_SEC + + (usec_t) ts->tv_nsec / NSEC_PER_USEC; +} + +static nsec_t timespec_load_nsec(const struct timespec *ts) { + assert(ts); + + if (ts->tv_sec == (time_t) -1 && + ts->tv_nsec == (long) -1) + return NSEC_INFINITY; + + return + (nsec_t) ts->tv_sec * NSEC_PER_SEC + + (nsec_t) ts->tv_nsec; +} + +struct timespec *timespec_store(struct timespec *ts, usec_t u) { + assert(ts); + + if (u == USEC_INFINITY) { + ts->tv_sec = (time_t) -1; + ts->tv_nsec = (long) -1; + return ts; + } + + ts->tv_sec = (time_t) (u / USEC_PER_SEC); + ts->tv_nsec = (long int) ((u % USEC_PER_SEC) * NSEC_PER_USEC); + + return ts; +} + +usec_t timeval_load(const struct timeval *tv) { + assert(tv); + + if (tv->tv_sec == (time_t) -1 && + tv->tv_usec == (suseconds_t) -1) + return USEC_INFINITY; + + if ((usec_t) tv->tv_sec > (UINT64_MAX - tv->tv_usec) / USEC_PER_SEC) + return USEC_INFINITY; + + return + (usec_t) tv->tv_sec * USEC_PER_SEC + + (usec_t) tv->tv_usec; +} + +struct timeval *timeval_store(struct timeval *tv, usec_t u) { + assert(tv); + + if (u == USEC_INFINITY) { + tv->tv_sec = (time_t) -1; + tv->tv_usec = (suseconds_t) -1; + } else { + tv->tv_sec = (time_t) (u / USEC_PER_SEC); + tv->tv_usec = (suseconds_t) (u % USEC_PER_SEC); + } + + return tv; +} + +static char *format_timestamp_internal(char *buf, size_t l, usec_t t, + bool utc, bool us) { + struct tm tm; + time_t sec; + int k; + + assert(buf); + assert(l > 0); + + if (t <= 0 || t == USEC_INFINITY) + return NULL; + + sec = (time_t) (t / USEC_PER_SEC); + localtime_or_gmtime_r(&sec, &tm, utc); + + 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); + + if (k <= 0) + return NULL; + 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; + } + + return buf; +} + +char *format_timestamp(char *buf, size_t l, usec_t t) { + return format_timestamp_internal(buf, l, t, false, false); +} + +char *format_timestamp_utc(char *buf, size_t l, usec_t t) { + return format_timestamp_internal(buf, l, t, true, false); +} + +char *format_timestamp_us(char *buf, size_t l, usec_t t) { + return format_timestamp_internal(buf, l, t, false, true); +} + +char *format_timestamp_us_utc(char *buf, size_t l, usec_t t) { + return format_timestamp_internal(buf, l, t, true, true); +} + +char *format_timestamp_relative(char *buf, size_t l, usec_t t) { + const char *s; + usec_t n, d; + + if (t <= 0 || t == USEC_INFINITY) + return NULL; + + n = now(CLOCK_REALTIME); + if (n > t) { + d = n - t; + s = "ago"; + } else { + d = t - n; + s = "left"; + } + + if (d >= USEC_PER_YEAR) + snprintf(buf, l, USEC_FMT " years " USEC_FMT " months %s", + d / USEC_PER_YEAR, + (d % USEC_PER_YEAR) / USEC_PER_MONTH, s); + else if (d >= USEC_PER_MONTH) + snprintf(buf, l, USEC_FMT " months " USEC_FMT " days %s", + d / USEC_PER_MONTH, + (d % USEC_PER_MONTH) / USEC_PER_DAY, s); + else if (d >= USEC_PER_WEEK) + snprintf(buf, l, USEC_FMT " weeks " USEC_FMT " days %s", + d / USEC_PER_WEEK, + (d % USEC_PER_WEEK) / USEC_PER_DAY, s); + else if (d >= 2*USEC_PER_DAY) + snprintf(buf, l, USEC_FMT " days %s", d / USEC_PER_DAY, s); + else if (d >= 25*USEC_PER_HOUR) + snprintf(buf, l, "1 day " USEC_FMT "h %s", + (d - USEC_PER_DAY) / USEC_PER_HOUR, s); + else if (d >= 6*USEC_PER_HOUR) + snprintf(buf, l, USEC_FMT "h %s", + d / USEC_PER_HOUR, s); + else if (d >= USEC_PER_HOUR) + snprintf(buf, l, USEC_FMT "h " USEC_FMT "min %s", + d / USEC_PER_HOUR, + (d % USEC_PER_HOUR) / USEC_PER_MINUTE, s); + else if (d >= 5*USEC_PER_MINUTE) + snprintf(buf, l, USEC_FMT "min %s", + d / USEC_PER_MINUTE, s); + else if (d >= USEC_PER_MINUTE) + snprintf(buf, l, USEC_FMT "min " USEC_FMT "s %s", + d / USEC_PER_MINUTE, + (d % USEC_PER_MINUTE) / USEC_PER_SEC, s); + else if (d >= USEC_PER_SEC) + snprintf(buf, l, USEC_FMT "s %s", + d / USEC_PER_SEC, s); + else if (d >= USEC_PER_MSEC) + snprintf(buf, l, USEC_FMT "ms %s", + d / USEC_PER_MSEC, s); + else if (d > 0) + snprintf(buf, l, USEC_FMT"us %s", + d, s); + else + snprintf(buf, l, "now"); + + buf[l-1] = 0; + return buf; +} + +char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy) { + static const struct { + const char *suffix; + usec_t usec; + } table[] = { + { "y", USEC_PER_YEAR }, + { "month", USEC_PER_MONTH }, + { "w", USEC_PER_WEEK }, + { "d", USEC_PER_DAY }, + { "h", USEC_PER_HOUR }, + { "min", USEC_PER_MINUTE }, + { "s", USEC_PER_SEC }, + { "ms", USEC_PER_MSEC }, + { "us", 1 }, + }; + + unsigned i; + char *p = buf; + bool something = false; + + assert(buf); + assert(l > 0); + + if (t == USEC_INFINITY) { + strncpy(p, "infinity", l-1); + p[l-1] = 0; + return p; + } + + if (t <= 0) { + strncpy(p, "0", l-1); + p[l-1] = 0; + return p; + } + + /* The result of this function can be parsed with parse_sec */ + + for (i = 0; i < ELEMENTSOF(table); i++) { + int k = 0; + size_t n; + bool done = false; + usec_t a, b; + + if (t <= 0) + break; + + if (t < accuracy && something) + break; + + if (t < table[i].usec) + continue; + + if (l <= 1) + break; + + a = t / table[i].usec; + b = t % table[i].usec; + + /* Let's see if we should shows this in dot notation */ + if (t < USEC_PER_MINUTE && b > 0) { + usec_t cc; + int j; + + j = 0; + for (cc = table[i].usec; cc > 1; cc /= 10) + j++; + + for (cc = accuracy; cc > 1; cc /= 10) { + b /= 10; + j--; + } + + if (j > 0) { + k = snprintf(p, l, + "%s"USEC_FMT".%0*llu%s", + p > buf ? " " : "", + a, + j, + (unsigned long long) b, + table[i].suffix); + + t = 0; + done = true; + } + } + + /* No? Then let's show it normally */ + if (!done) { + k = snprintf(p, l, + "%s"USEC_FMT"%s", + p > buf ? " " : "", + a, + table[i].suffix); + + t = b; + } + + n = MIN((size_t) k, l); + + l -= n; + p += n; + + something = true; + } + + *p = 0; + + return buf; +} + +void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t) { + + assert(f); + assert(name); + assert(t); + + if (!dual_timestamp_is_set(t)) + return; + + fprintf(f, "%s="USEC_FMT" "USEC_FMT"\n", + name, + t->realtime, + t->monotonic); +} + +int dual_timestamp_deserialize(const char *value, dual_timestamp *t) { + unsigned long long a, b; + + assert(value); + assert(t); + + if (sscanf(value, "%llu %llu", &a, &b) != 2) { + log_debug("Failed to parse finish timestamp value %s.", value); + return -EINVAL; + } + + t->realtime = a; + t->monotonic = b; + + return 0; +} + +int parse_timestamp(const char *t, usec_t *usec) { + static const struct { + const char *name; + const int nr; + } day_nr[] = { + { "Sunday", 0 }, + { "Sun", 0 }, + { "Monday", 1 }, + { "Mon", 1 }, + { "Tuesday", 2 }, + { "Tue", 2 }, + { "Wednesday", 3 }, + { "Wed", 3 }, + { "Thursday", 4 }, + { "Thu", 4 }, + { "Friday", 5 }, + { "Fri", 5 }, + { "Saturday", 6 }, + { "Sat", 6 }, + }; + + const char *k; + const char *utc; + struct tm tm, copy; + time_t x; + usec_t x_usec, plus = 0, minus = 0, ret; + int r, weekday = -1; + unsigned i; + + /* + * Allowed syntaxes: + * + * 2012-09-22 16:34:22 + * 2012-09-22 16:34 (seconds will be set to 0) + * 2012-09-22 (time will be set to 00:00:00) + * 16:34:22 (date will be set to today) + * 16:34 (date will be set to today, seconds to 0) + * now + * yesterday (time is set to 00:00:00) + * today (time is set to 00:00:00) + * tomorrow (time is set to 00:00:00) + * +5min + * -5days + * @2147483647 (seconds since epoch) + * + */ + + assert(t); + assert(usec); + + if (t[0] == '@') + return parse_sec(t + 1, usec); + + ret = now(CLOCK_REALTIME); + + if (streq(t, "now")) + goto finish; + + else if (t[0] == '+') { + r = parse_sec(t+1, &plus); + if (r < 0) + return r; + + goto finish; + + } else if (t[0] == '-') { + r = parse_sec(t+1, &minus); + if (r < 0) + return r; + + goto finish; + + } else if ((k = endswith(t, " ago"))) { + t = strndupa(t, k - t); + + r = parse_sec(t, &minus); + if (r < 0) + return r; + + goto finish; + + } else if ((k = endswith(t, " left"))) { + t = strndupa(t, k - t); + + r = parse_sec(t, &plus); + if (r < 0) + return r; + + goto finish; + } + + utc = endswith_no_case(t, " UTC"); + if (utc) + t = strndupa(t, utc - t); + + x = ret / USEC_PER_SEC; + x_usec = 0; + + assert_se(localtime_or_gmtime_r(&x, &tm, utc)); + tm.tm_isdst = -1; + + if (streq(t, "today")) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto from_tm; + + } else if (streq(t, "yesterday")) { + tm.tm_mday --; + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto from_tm; + + } else if (streq(t, "tomorrow")) { + tm.tm_mday ++; + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto from_tm; + } + + + for (i = 0; i < ELEMENTSOF(day_nr); i++) { + size_t skip; + + if (!startswith_no_case(t, day_nr[i].name)) + continue; + + skip = strlen(day_nr[i].name); + if (t[skip] != ' ') + continue; + + weekday = day_nr[i].nr; + t += skip + 1; + break; + } + + copy = tm; + k = strptime(t, "%y-%m-%d %H:%M:%S", &tm); + if (k) { + if (*k == '.') + goto parse_usec; + else if (*k == 0) + goto from_tm; + } + + tm = copy; + k = strptime(t, "%Y-%m-%d %H:%M:%S", &tm); + if (k) { + if (*k == '.') + goto parse_usec; + else if (*k == 0) + goto from_tm; + } + + tm = copy; + k = strptime(t, "%y-%m-%d %H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto from_tm; + } + + tm = copy; + k = strptime(t, "%Y-%m-%d %H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto from_tm; + } + + tm = copy; + k = strptime(t, "%y-%m-%d", &tm); + if (k && *k == 0) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto from_tm; + } + + tm = copy; + k = strptime(t, "%Y-%m-%d", &tm); + if (k && *k == 0) { + tm.tm_sec = tm.tm_min = tm.tm_hour = 0; + goto from_tm; + } + + tm = copy; + k = strptime(t, "%H:%M:%S", &tm); + if (k) { + if (*k == '.') + goto parse_usec; + else if (*k == 0) + goto from_tm; + } + + tm = copy; + k = strptime(t, "%H:%M", &tm); + if (k && *k == 0) { + tm.tm_sec = 0; + goto from_tm; + } + + return -EINVAL; + +parse_usec: + { + unsigned add; + + k++; + r = parse_fractional_part_u(&k, 6, &add); + if (r < 0) + return -EINVAL; + + if (*k) + return -EINVAL; + + x_usec = add; + + } + +from_tm: + x = mktime_or_timegm(&tm, utc); + if (x == (time_t) -1) + return -EINVAL; + + if (weekday >= 0 && tm.tm_wday != weekday) + return -EINVAL; + + ret = (usec_t) x * USEC_PER_SEC + x_usec; + +finish: + ret += plus; + if (ret > minus) + ret -= minus; + else + ret = 0; + + *usec = ret; + + return 0; +} + +int parse_time(const char *t, usec_t *usec, usec_t default_unit) { + + static const struct { + const char *suffix; + usec_t usec; + } table[] = { + { "seconds", USEC_PER_SEC }, + { "second", USEC_PER_SEC }, + { "sec", USEC_PER_SEC }, + { "s", USEC_PER_SEC }, + { "minutes", USEC_PER_MINUTE }, + { "minute", USEC_PER_MINUTE }, + { "min", USEC_PER_MINUTE }, + { "months", USEC_PER_MONTH }, + { "month", USEC_PER_MONTH }, + { "M", USEC_PER_MONTH }, + { "msec", USEC_PER_MSEC }, + { "ms", USEC_PER_MSEC }, + { "m", USEC_PER_MINUTE }, + { "hours", USEC_PER_HOUR }, + { "hour", USEC_PER_HOUR }, + { "hr", USEC_PER_HOUR }, + { "h", USEC_PER_HOUR }, + { "days", USEC_PER_DAY }, + { "day", USEC_PER_DAY }, + { "d", USEC_PER_DAY }, + { "weeks", USEC_PER_WEEK }, + { "week", USEC_PER_WEEK }, + { "w", USEC_PER_WEEK }, + { "years", USEC_PER_YEAR }, + { "year", USEC_PER_YEAR }, + { "y", USEC_PER_YEAR }, + { "usec", 1ULL }, + { "us", 1ULL }, + }; + + const char *p, *s; + usec_t r = 0; + bool something = false; + + assert(t); + assert(usec); + assert(default_unit > 0); + + p = t; + + p += strspn(p, WHITESPACE); + s = startswith(p, "infinity"); + if (s) { + s += strspn(s, WHITESPACE); + if (*s != 0) + return -EINVAL; + + *usec = USEC_INFINITY; + return 0; + } + + for (;;) { + long long l, z = 0; + char *e; + unsigned i, n = 0; + usec_t multiplier, k; + + p += strspn(p, WHITESPACE); + + if (*p == 0) { + if (!something) + return -EINVAL; + + break; + } + + errno = 0; + l = strtoll(p, &e, 10); + + if (errno > 0) + return -errno; + + if (l < 0) + return -ERANGE; + + if (*e == '.') { + char *b = e + 1; + + errno = 0; + z = strtoll(b, &e, 10); + if (errno > 0) + return -errno; + + if (z < 0) + return -ERANGE; + + if (e == b) + return -EINVAL; + + n = e - b; + + } else if (e == p) + return -EINVAL; + + e += strspn(e, WHITESPACE); + + for (i = 0; i < ELEMENTSOF(table); i++) + if (startswith(e, table[i].suffix)) { + multiplier = table[i].usec; + p = e + strlen(table[i].suffix); + break; + } + + if (i >= ELEMENTSOF(table)) { + multiplier = default_unit; + p = e; + } + + something = true; + + k = (usec_t) z * multiplier; + + for (; n > 0; n--) + k /= 10; + + r += (usec_t) l * multiplier + k; + } + + *usec = r; + + return 0; +} + +int parse_sec(const char *t, usec_t *usec) { + return parse_time(t, usec, USEC_PER_SEC); +} + +int parse_nsec(const char *t, nsec_t *nsec) { + static const struct { + const char *suffix; + nsec_t nsec; + } table[] = { + { "seconds", NSEC_PER_SEC }, + { "second", NSEC_PER_SEC }, + { "sec", NSEC_PER_SEC }, + { "s", NSEC_PER_SEC }, + { "minutes", NSEC_PER_MINUTE }, + { "minute", NSEC_PER_MINUTE }, + { "min", NSEC_PER_MINUTE }, + { "months", NSEC_PER_MONTH }, + { "month", NSEC_PER_MONTH }, + { "msec", NSEC_PER_MSEC }, + { "ms", NSEC_PER_MSEC }, + { "m", NSEC_PER_MINUTE }, + { "hours", NSEC_PER_HOUR }, + { "hour", NSEC_PER_HOUR }, + { "hr", NSEC_PER_HOUR }, + { "h", NSEC_PER_HOUR }, + { "days", NSEC_PER_DAY }, + { "day", NSEC_PER_DAY }, + { "d", NSEC_PER_DAY }, + { "weeks", NSEC_PER_WEEK }, + { "week", NSEC_PER_WEEK }, + { "w", NSEC_PER_WEEK }, + { "years", NSEC_PER_YEAR }, + { "year", NSEC_PER_YEAR }, + { "y", NSEC_PER_YEAR }, + { "usec", NSEC_PER_USEC }, + { "us", NSEC_PER_USEC }, + { "nsec", 1ULL }, + { "ns", 1ULL }, + { "", 1ULL }, /* default is nsec */ + }; + + const char *p, *s; + nsec_t r = 0; + bool something = false; + + assert(t); + assert(nsec); + + p = t; + + p += strspn(p, WHITESPACE); + s = startswith(p, "infinity"); + if (s) { + s += strspn(s, WHITESPACE); + if (*s != 0) + return -EINVAL; + + *nsec = NSEC_INFINITY; + return 0; + } + + for (;;) { + long long l, z = 0; + char *e; + unsigned i, n = 0; + + p += strspn(p, WHITESPACE); + + if (*p == 0) { + if (!something) + return -EINVAL; + + break; + } + + errno = 0; + l = strtoll(p, &e, 10); + + if (errno > 0) + return -errno; + + if (l < 0) + return -ERANGE; + + if (*e == '.') { + char *b = e + 1; + + errno = 0; + z = strtoll(b, &e, 10); + if (errno > 0) + return -errno; + + if (z < 0) + return -ERANGE; + + if (e == b) + return -EINVAL; + + n = e - b; + + } else if (e == p) + return -EINVAL; + + e += strspn(e, WHITESPACE); + + for (i = 0; i < ELEMENTSOF(table); i++) + if (startswith(e, table[i].suffix)) { + nsec_t k = (nsec_t) z * table[i].nsec; + + for (; n > 0; n--) + k /= 10; + + r += (nsec_t) l * table[i].nsec + k; + p = e + strlen(table[i].suffix); + + something = true; + break; + } + + if (i >= ELEMENTSOF(table)) + return -EINVAL; + + } + + *nsec = r; + + return 0; +} + +bool ntp_synced(void) { + struct timex txc = {}; + + if (adjtimex(&txc) < 0) + return false; + + if (txc.status & STA_UNSYNC) + return false; + + return true; +} + +int get_timezones(char ***ret) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_strv_free_ char **zones = NULL; + size_t n_zones = 0, n_allocated = 0; + + assert(ret); + + zones = strv_new("UTC", NULL); + if (!zones) + return -ENOMEM; + + n_allocated = 2; + n_zones = 1; + + f = fopen("/usr/share/zoneinfo/zone.tab", "re"); + if (f) { + char l[LINE_MAX]; + + FOREACH_LINE(l, f, return -errno) { + char *p, *w; + size_t k; + + p = strstrip(l); + + if (isempty(p) || *p == '#') + continue; + + /* Skip over country code */ + p += strcspn(p, WHITESPACE); + p += strspn(p, WHITESPACE); + + /* Skip over coordinates */ + p += strcspn(p, WHITESPACE); + p += strspn(p, WHITESPACE); + + /* Found timezone name */ + k = strcspn(p, WHITESPACE); + if (k <= 0) + continue; + + w = strndup(p, k); + if (!w) + return -ENOMEM; + + if (!GREEDY_REALLOC(zones, n_allocated, n_zones + 2)) { + free(w); + return -ENOMEM; + } + + zones[n_zones++] = w; + zones[n_zones] = NULL; + } + + strv_sort(zones); + + } else if (errno != ENOENT) + return -errno; + + *ret = zones; + zones = NULL; + + return 0; +} + +bool timezone_is_valid(const char *name) { + bool slash = false; + const char *p, *t; + struct stat st; + + if (isempty(name)) + return false; + + if (name[0] == '/') + return false; + + for (p = name; *p; p++) { + if (!(*p >= '0' && *p <= '9') && + !(*p >= 'a' && *p <= 'z') && + !(*p >= 'A' && *p <= 'Z') && + !(*p == '-' || *p == '_' || *p == '+' || *p == '/')) + return false; + + if (*p == '/') { + + if (slash) + return false; + + slash = true; + } else + slash = false; + } + + if (slash) + return false; + + t = strjoina("/usr/share/zoneinfo/", name); + if (stat(t, &st) < 0) + return false; + + if (!S_ISREG(st.st_mode)) + return false; + + return true; +} + +clockid_t clock_boottime_or_monotonic(void) { + static clockid_t clock = -1; + int fd; + + if (clock != -1) + return clock; + + fd = timerfd_create(CLOCK_BOOTTIME, TFD_NONBLOCK|TFD_CLOEXEC); + if (fd < 0) + clock = CLOCK_MONOTONIC; + else { + safe_close(fd); + clock = CLOCK_BOOTTIME; + } + + return clock; +} + +int get_timezone(char **tz) { + _cleanup_free_ char *t = NULL; + const char *e; + char *z; + int r; + + r = readlink_malloc("/etc/localtime", &t); + if (r < 0) + return r; /* returns EINVAL if not a symlink */ + + e = path_startswith(t, "/usr/share/zoneinfo/"); + if (!e) + e = path_startswith(t, "../usr/share/zoneinfo/"); + if (!e) + return -EINVAL; + + if (!timezone_is_valid(e)) + return -EINVAL; + + z = strdup(e); + if (!z) + return -ENOMEM; + + *tz = z; + return 0; +} + +time_t mktime_or_timegm(struct tm *tm, bool utc) { + return utc ? timegm(tm) : mktime(tm); +} + +struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc) { + return utc ? gmtime_r(t, tm) : localtime_r(t, tm); +} + +unsigned long usec_to_jiffies(usec_t u) { + static thread_local unsigned long hz = 0; + long r; + + if (hz == 0) { + r = sysconf(_SC_CLK_TCK); + + assert(r > 0); + hz = (unsigned long) r; + } + + return DIV_ROUND_UP(u , USEC_PER_SEC / hz); +} diff --git a/src/libbasic/time-util.h b/src/libbasic/time-util.h new file mode 100644 index 0000000000..9894e626c5 --- /dev/null +++ b/src/libbasic/time-util.h @@ -0,0 +1,151 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include + +typedef uint64_t usec_t; +typedef uint64_t nsec_t; + +#define NSEC_FMT "%" PRIu64 +#define USEC_FMT "%" PRIu64 + +#include "macro.h" + +typedef struct dual_timestamp { + usec_t realtime; + usec_t monotonic; +} dual_timestamp; + +#define USEC_INFINITY ((usec_t) -1) +#define NSEC_INFINITY ((nsec_t) -1) + +#define MSEC_PER_SEC 1000ULL +#define USEC_PER_SEC ((usec_t) 1000000ULL) +#define USEC_PER_MSEC ((usec_t) 1000ULL) +#define NSEC_PER_SEC ((nsec_t) 1000000000ULL) +#define NSEC_PER_MSEC ((nsec_t) 1000000ULL) +#define NSEC_PER_USEC ((nsec_t) 1000ULL) + +#define USEC_PER_MINUTE ((usec_t) (60ULL*USEC_PER_SEC)) +#define NSEC_PER_MINUTE ((nsec_t) (60ULL*NSEC_PER_SEC)) +#define USEC_PER_HOUR ((usec_t) (60ULL*USEC_PER_MINUTE)) +#define NSEC_PER_HOUR ((nsec_t) (60ULL*NSEC_PER_MINUTE)) +#define USEC_PER_DAY ((usec_t) (24ULL*USEC_PER_HOUR)) +#define NSEC_PER_DAY ((nsec_t) (24ULL*NSEC_PER_HOUR)) +#define USEC_PER_WEEK ((usec_t) (7ULL*USEC_PER_DAY)) +#define NSEC_PER_WEEK ((nsec_t) (7ULL*NSEC_PER_DAY)) +#define USEC_PER_MONTH ((usec_t) (2629800ULL*USEC_PER_SEC)) +#define NSEC_PER_MONTH ((nsec_t) (2629800ULL*NSEC_PER_SEC)) +#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 */ +#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 }) + +usec_t now(clockid_t clock); +nsec_t now_nsec(clockid_t clock); + +dual_timestamp* dual_timestamp_get(dual_timestamp *ts); +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); + +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)); +} + +usec_t timespec_load(const struct timespec *ts) _pure_; +struct timespec *timespec_store(struct timespec *ts, usec_t u); + +usec_t timeval_load(const struct timeval *tv) _pure_; +struct timeval *timeval_store(struct timeval *tv, usec_t u); + +char *format_timestamp(char *buf, size_t l, usec_t t); +char *format_timestamp_utc(char *buf, size_t l, usec_t t); +char *format_timestamp_us(char *buf, size_t l, usec_t t); +char *format_timestamp_us_utc(char *buf, size_t l, usec_t t); +char *format_timestamp_relative(char *buf, size_t l, usec_t t); +char *format_timespan(char *buf, size_t l, usec_t t, usec_t accuracy); + +void dual_timestamp_serialize(FILE *f, const char *name, dual_timestamp *t); +int dual_timestamp_deserialize(const char *value, dual_timestamp *t); + +int parse_timestamp(const char *t, usec_t *usec); + +int parse_sec(const char *t, usec_t *usec); +int parse_time(const char *t, usec_t *usec, usec_t default_unit); +int parse_nsec(const char *t, nsec_t *nsec); + +bool ntp_synced(void); + +int get_timezones(char ***l); +bool timezone_is_valid(const char *name); + +clockid_t clock_boottime_or_monotonic(void); + +#define xstrftime(buf, fmt, tm) \ + assert_message_se(strftime(buf, ELEMENTSOF(buf), fmt, tm) > 0, \ + "xstrftime: " #buf "[] must be big enough") + +int get_timezone(char **timezone); + +time_t mktime_or_timegm(struct tm *tm, bool utc); +struct tm *localtime_or_gmtime_r(const time_t *t, struct tm *tm, bool utc); + +unsigned long usec_to_jiffies(usec_t usec); + +static inline usec_t usec_add(usec_t a, usec_t b) { + usec_t c; + + /* Adds two time values, and makes sure USEC_INFINITY as input results as USEC_INFINITY in output, and doesn't + * overflow. */ + + c = a + b; + if (c < a || c < b) /* overflow check */ + return USEC_INFINITY; + + return c; +} + +static inline usec_t usec_sub(usec_t timestamp, int64_t delta) { + if (delta < 0) + return usec_add(timestamp, (usec_t) (-delta)); + + if (timestamp == USEC_INFINITY) /* Make sure infinity doesn't degrade */ + return USEC_INFINITY; + + if (timestamp < (usec_t) delta) + return 0; + + return timestamp - delta; +} diff --git a/src/libbasic/umask-util.h b/src/libbasic/umask-util.h new file mode 100644 index 0000000000..359d87d27c --- /dev/null +++ b/src/libbasic/umask-util.h @@ -0,0 +1,46 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#include "macro.h" + +static inline void umaskp(mode_t *u) { + umask(*u); +} + +#define _cleanup_umask_ _cleanup_(umaskp) + +struct _umask_struct_ { + mode_t mask; + bool quit; +}; + +static inline void _reset_umask_(struct _umask_struct_ *s) { + umask(s->mask); +}; + +#define RUN_WITH_UMASK(mask) \ + for (_cleanup_(_reset_umask_) struct _umask_struct_ _saved_umask_ = { umask(mask), false }; \ + !_saved_umask_.quit ; \ + _saved_umask_.quit = true) diff --git a/src/libbasic/unaligned.h b/src/libbasic/unaligned.h new file mode 100644 index 0000000000..79be645bed --- /dev/null +++ b/src/libbasic/unaligned.h @@ -0,0 +1,111 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include +#include + +/* BE */ + +static inline uint16_t unaligned_read_be16(const void *_u) { + const uint8_t *u = _u; + + return (((uint16_t) u[0]) << 8) | + ((uint16_t) u[1]); +} + +static inline uint32_t unaligned_read_be32(const void *_u) { + const uint8_t *u = _u; + + return (((uint32_t) unaligned_read_be16(u)) << 16) | + ((uint32_t) unaligned_read_be16(u + 2)); +} + +static inline uint64_t unaligned_read_be64(const void *_u) { + const uint8_t *u = _u; + + return (((uint64_t) unaligned_read_be32(u)) << 32) | + ((uint64_t) unaligned_read_be32(u + 4)); +} + +static inline void unaligned_write_be16(void *_u, uint16_t a) { + uint8_t *u = _u; + + u[0] = (uint8_t) (a >> 8); + u[1] = (uint8_t) a; +} + +static inline void unaligned_write_be32(void *_u, uint32_t a) { + uint8_t *u = _u; + + unaligned_write_be16(u, (uint16_t) (a >> 16)); + unaligned_write_be16(u + 2, (uint16_t) a); +} + +static inline void unaligned_write_be64(void *_u, uint64_t a) { + uint8_t *u = _u; + + unaligned_write_be32(u, (uint32_t) (a >> 32)); + unaligned_write_be32(u + 4, (uint32_t) a); +} + +/* LE */ + +static inline uint16_t unaligned_read_le16(const void *_u) { + const uint8_t *u = _u; + + return (((uint16_t) u[1]) << 8) | + ((uint16_t) u[0]); +} + +static inline uint32_t unaligned_read_le32(const void *_u) { + const uint8_t *u = _u; + + return (((uint32_t) unaligned_read_le16(u + 2)) << 16) | + ((uint32_t) unaligned_read_le16(u)); +} + +static inline uint64_t unaligned_read_le64(const void *_u) { + const uint8_t *u = _u; + + return (((uint64_t) unaligned_read_le32(u + 4)) << 32) | + ((uint64_t) unaligned_read_le32(u)); +} + +static inline void unaligned_write_le16(void *_u, uint16_t a) { + uint8_t *u = _u; + + u[0] = (uint8_t) a; + u[1] = (uint8_t) (a >> 8); +} + +static inline void unaligned_write_le32(void *_u, uint32_t a) { + uint8_t *u = _u; + + unaligned_write_le16(u, (uint16_t) a); + unaligned_write_le16(u + 2, (uint16_t) (a >> 16)); +} + +static inline void unaligned_write_le64(void *_u, uint64_t a) { + uint8_t *u = _u; + + unaligned_write_le32(u, (uint32_t) a); + unaligned_write_le32(u + 4, (uint32_t) (a >> 32)); +} diff --git a/src/libbasic/unit-name.c b/src/libbasic/unit-name.c new file mode 100644 index 0000000000..fe883b95c7 --- /dev/null +++ b/src/libbasic/unit-name.c @@ -0,0 +1,1049 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "bus-label.h" +#include "glob-util.h" +#include "hexdecoct.h" +#include "macro.h" +#include "path-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" + +/* Characters valid in a unit name. */ +#define VALID_CHARS \ + DIGITS \ + LETTERS \ + ":-_.\\" + +/* The same, but also permits the single @ character that may appear */ +#define VALID_CHARS_WITH_AT \ + "@" \ + VALID_CHARS + +/* All chars valid in a unit name glob */ +#define VALID_CHARS_GLOB \ + VALID_CHARS_WITH_AT \ + "[]!-*?" + +bool unit_name_is_valid(const char *n, UnitNameFlags flags) { + const char *e, *i, *at; + + assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0); + + if (_unlikely_(flags == 0)) + return false; + + if (isempty(n)) + return false; + + if (strlen(n) >= UNIT_NAME_MAX) + return false; + + e = strrchr(n, '.'); + if (!e || e == n) + return false; + + if (unit_type_from_string(e + 1) < 0) + return false; + + for (i = n, at = NULL; i < e; i++) { + + if (*i == '@' && !at) + at = i; + + if (!strchr("@" VALID_CHARS, *i)) + return false; + } + + if (at == n) + return false; + + if (flags & UNIT_NAME_PLAIN) + if (!at) + return true; + + if (flags & UNIT_NAME_INSTANCE) + if (at && e > at + 1) + return true; + + if (flags & UNIT_NAME_TEMPLATE) + if (at && e == at + 1) + return true; + + return false; +} + +bool unit_prefix_is_valid(const char *p) { + + /* We don't allow additional @ in the prefix string */ + + if (isempty(p)) + return false; + + return in_charset(p, VALID_CHARS); +} + +bool unit_instance_is_valid(const char *i) { + + /* The max length depends on the length of the string, so we + * don't really check this here. */ + + if (isempty(i)) + return false; + + /* We allow additional @ in the instance string, we do not + * allow them in the prefix! */ + + return in_charset(i, "@" VALID_CHARS); +} + +bool unit_suffix_is_valid(const char *s) { + if (isempty(s)) + return false; + + if (s[0] != '.') + return false; + + if (unit_type_from_string(s + 1) < 0) + return false; + + return true; +} + +int unit_name_to_prefix(const char *n, char **ret) { + const char *p; + char *s; + + assert(n); + assert(ret); + + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return -EINVAL; + + p = strchr(n, '@'); + if (!p) + p = strrchr(n, '.'); + + assert_se(p); + + s = strndup(n, p - n); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int unit_name_to_instance(const char *n, char **instance) { + const char *p, *d; + char *i; + + assert(n); + assert(instance); + + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return -EINVAL; + + /* Everything past the first @ and before the last . is the instance */ + p = strchr(n, '@'); + if (!p) { + *instance = NULL; + return 0; + } + + p++; + + d = strrchr(p, '.'); + if (!d) + return -EINVAL; + + i = strndup(p, d-p); + if (!i) + return -ENOMEM; + + *instance = i; + return 1; +} + +int unit_name_to_prefix_and_instance(const char *n, char **ret) { + const char *d; + char *s; + + assert(n); + assert(ret); + + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return -EINVAL; + + d = strrchr(n, '.'); + if (!d) + return -EINVAL; + + s = strndup(n, d - n); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +UnitType unit_name_to_type(const char *n) { + const char *e; + + assert(n); + + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return _UNIT_TYPE_INVALID; + + assert_se(e = strrchr(n, '.')); + + return unit_type_from_string(e + 1); +} + +int unit_name_change_suffix(const char *n, const char *suffix, char **ret) { + char *e, *s; + size_t a, b; + + assert(n); + assert(suffix); + assert(ret); + + if (!unit_name_is_valid(n, UNIT_NAME_ANY)) + return -EINVAL; + + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; + + assert_se(e = strrchr(n, '.')); + + a = e - n; + b = strlen(suffix); + + s = new(char, a + b + 1); + if (!s) + return -ENOMEM; + + strcpy(mempcpy(s, n, a), suffix); + *ret = s; + + return 0; +} + +int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) { + char *s; + + assert(prefix); + assert(suffix); + assert(ret); + + if (!unit_prefix_is_valid(prefix)) + return -EINVAL; + + if (instance && !unit_instance_is_valid(instance)) + return -EINVAL; + + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; + + if (!instance) + s = strappend(prefix, suffix); + else + s = strjoin(prefix, "@", instance, suffix, NULL); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +static char *do_escape_char(char c, char *t) { + assert(t); + + *(t++) = '\\'; + *(t++) = 'x'; + *(t++) = hexchar(c >> 4); + *(t++) = hexchar(c); + + return t; +} + +static char *do_escape(const char *f, char *t) { + assert(f); + assert(t); + + /* do not create units with a leading '.', like for "/.dotdir" mount points */ + if (*f == '.') { + t = do_escape_char(*f, t); + f++; + } + + for (; *f; f++) { + if (*f == '/') + *(t++) = '-'; + else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f)) + t = do_escape_char(*f, t); + else + *(t++) = *f; + } + + return t; +} + +char *unit_name_escape(const char *f) { + char *r, *t; + + assert(f); + + r = new(char, strlen(f)*4+1); + if (!r) + return NULL; + + t = do_escape(f, r); + *t = 0; + + return r; +} + +int unit_name_unescape(const char *f, char **ret) { + _cleanup_free_ char *r = NULL; + char *t; + + assert(f); + + r = strdup(f); + if (!r) + return -ENOMEM; + + for (t = r; *f; f++) { + if (*f == '-') + *(t++) = '/'; + else if (*f == '\\') { + int a, b; + + if (f[1] != 'x') + return -EINVAL; + + a = unhexchar(f[2]); + if (a < 0) + return -EINVAL; + + b = unhexchar(f[3]); + if (b < 0) + return -EINVAL; + + *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b); + f += 3; + } else + *(t++) = *f; + } + + *t = 0; + + *ret = r; + r = NULL; + + return 0; +} + +int unit_name_path_escape(const char *f, char **ret) { + char *p, *s; + + assert(f); + assert(ret); + + p = strdupa(f); + if (!p) + return -ENOMEM; + + path_kill_slashes(p); + + if (STR_IN_SET(p, "/", "")) + s = strdup("-"); + else { + char *e; + + if (!path_is_safe(p)) + return -EINVAL; + + /* Truncate trailing slashes */ + e = endswith(p, "/"); + if (e) + *e = 0; + + /* Truncate leading slashes */ + if (p[0] == '/') + p++; + + s = unit_name_escape(p); + } + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int unit_name_path_unescape(const char *f, char **ret) { + char *s; + int r; + + assert(f); + + if (isempty(f)) + return -EINVAL; + + if (streq(f, "-")) { + s = strdup("/"); + if (!s) + return -ENOMEM; + } else { + char *w; + + r = unit_name_unescape(f, &w); + if (r < 0) + return r; + + /* Don't accept trailing or leading slashes */ + if (startswith(w, "/") || endswith(w, "/")) { + free(w); + return -EINVAL; + } + + /* Prefix a slash again */ + s = strappend("/", w); + free(w); + if (!s) + return -ENOMEM; + + if (!path_is_safe(s)) { + free(s); + return -EINVAL; + } + } + + if (ret) + *ret = s; + else + free(s); + + return 0; +} + +int unit_name_replace_instance(const char *f, const char *i, char **ret) { + const char *p, *e; + char *s; + size_t a, b; + + assert(f); + assert(i); + assert(ret); + + if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) + return -EINVAL; + if (!unit_instance_is_valid(i)) + return -EINVAL; + + assert_se(p = strchr(f, '@')); + assert_se(e = strrchr(f, '.')); + + a = p - f; + b = strlen(i); + + s = new(char, a + 1 + b + strlen(e) + 1); + if (!s) + return -ENOMEM; + + strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e); + + *ret = s; + return 0; +} + +int unit_name_template(const char *f, char **ret) { + const char *p, *e; + char *s; + size_t a; + + assert(f); + assert(ret); + + if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) + return -EINVAL; + + assert_se(p = strchr(f, '@')); + assert_se(e = strrchr(f, '.')); + + a = p - f; + + s = new(char, a + 1 + strlen(e) + 1); + if (!s) + return -ENOMEM; + + strcpy(mempcpy(s, f, a + 1), e); + + *ret = s; + return 0; +} + +int unit_name_from_path(const char *path, const char *suffix, char **ret) { + _cleanup_free_ char *p = NULL; + char *s = NULL; + int r; + + assert(path); + assert(suffix); + assert(ret); + + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; + + r = unit_name_path_escape(path, &p); + if (r < 0) + return r; + + s = strappend(p, suffix); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) { + _cleanup_free_ char *p = NULL; + char *s; + int r; + + assert(prefix); + assert(path); + assert(suffix); + assert(ret); + + if (!unit_prefix_is_valid(prefix)) + return -EINVAL; + + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; + + r = unit_name_path_escape(path, &p); + if (r < 0) + return r; + + s = strjoin(prefix, "@", p, suffix, NULL); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int unit_name_to_path(const char *name, char **ret) { + _cleanup_free_ char *prefix = NULL; + int r; + + assert(name); + + r = unit_name_to_prefix(name, &prefix); + if (r < 0) + return r; + + return unit_name_path_unescape(prefix, ret); +} + +char *unit_dbus_path_from_name(const char *name) { + _cleanup_free_ char *e = NULL; + + assert(name); + + e = bus_label_escape(name); + if (!e) + return NULL; + + return strappend("/org/freedesktop/systemd1/unit/", e); +} + +int unit_name_from_dbus_path(const char *path, char **name) { + const char *e; + char *n; + + e = startswith(path, "/org/freedesktop/systemd1/unit/"); + if (!e) + return -EINVAL; + + n = bus_label_unescape(e); + if (!n) + return -ENOMEM; + + *name = n; + return 0; +} + +const char* unit_dbus_interface_from_type(UnitType t) { + + static const char *const table[_UNIT_TYPE_MAX] = { + [UNIT_SERVICE] = "org.freedesktop.systemd1.Service", + [UNIT_SOCKET] = "org.freedesktop.systemd1.Socket", + [UNIT_BUSNAME] = "org.freedesktop.systemd1.BusName", + [UNIT_TARGET] = "org.freedesktop.systemd1.Target", + [UNIT_DEVICE] = "org.freedesktop.systemd1.Device", + [UNIT_MOUNT] = "org.freedesktop.systemd1.Mount", + [UNIT_AUTOMOUNT] = "org.freedesktop.systemd1.Automount", + [UNIT_SWAP] = "org.freedesktop.systemd1.Swap", + [UNIT_TIMER] = "org.freedesktop.systemd1.Timer", + [UNIT_PATH] = "org.freedesktop.systemd1.Path", + [UNIT_SLICE] = "org.freedesktop.systemd1.Slice", + [UNIT_SCOPE] = "org.freedesktop.systemd1.Scope", + }; + + if (t < 0) + return NULL; + if (t >= _UNIT_TYPE_MAX) + return NULL; + + return table[t]; +} + +const char *unit_dbus_interface_from_name(const char *name) { + UnitType t; + + t = unit_name_to_type(name); + if (t < 0) + return NULL; + + return unit_dbus_interface_from_type(t); +} + +static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) { + const char *valid_chars; + + assert(f); + assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB)); + assert(t); + + /* We'll only escape the obvious characters here, to play + * safe. */ + + valid_chars = allow_globs == UNIT_NAME_GLOB ? VALID_CHARS_GLOB : VALID_CHARS_WITH_AT; + + for (; *f; f++) { + if (*f == '/') + *(t++) = '-'; + else if (!strchr(valid_chars, *f)) + t = do_escape_char(*f, t); + else + *(t++) = *f; + } + + return t; +} + +/** + * Convert a string to a unit name. /dev/blah is converted to dev-blah.device, + * /blah/blah is converted to blah-blah.mount, anything else is left alone, + * except that @suffix is appended if a valid unit suffix is not present. + * + * If @allow_globs, globs characters are preserved. Otherwise, they are escaped. + */ +int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) { + char *s, *t; + int r; + + assert(name); + assert(suffix); + assert(ret); + + if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */ + return -EINVAL; + + if (!unit_suffix_is_valid(suffix)) + return -EINVAL; + + /* Already a fully valid unit name? If so, no mangling is necessary... */ + if (unit_name_is_valid(name, UNIT_NAME_ANY)) + goto good; + + /* Already a fully valid globbing expression? If so, no mangling is necessary either... */ + if (allow_globs == UNIT_NAME_GLOB && + string_is_glob(name) && + in_charset(name, VALID_CHARS_GLOB)) + goto good; + + if (is_device_path(name)) { + r = unit_name_from_path(name, ".device", ret); + if (r >= 0) + return 1; + if (r != -EINVAL) + return r; + } + + if (path_is_absolute(name)) { + r = unit_name_from_path(name, ".mount", ret); + if (r >= 0) + return 1; + if (r != -EINVAL) + return r; + } + + s = new(char, strlen(name) * 4 + strlen(suffix) + 1); + if (!s) + return -ENOMEM; + + t = do_escape_mangle(name, allow_globs, s); + *t = 0; + + /* Append a suffix if it doesn't have any, but only if this is not a glob, so that we can allow "foo.*" as a + * valid glob. */ + if ((allow_globs != UNIT_NAME_GLOB || !string_is_glob(s)) && unit_name_to_type(s) < 0) + strcpy(t, suffix); + + *ret = s; + return 1; + +good: + s = strdup(name); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int slice_build_parent_slice(const char *slice, char **ret) { + char *s, *dash; + int r; + + assert(slice); + assert(ret); + + if (!slice_name_is_valid(slice)) + return -EINVAL; + + if (streq(slice, "-.slice")) { + *ret = NULL; + return 0; + } + + s = strdup(slice); + if (!s) + return -ENOMEM; + + dash = strrchr(s, '-'); + if (dash) + strcpy(dash, ".slice"); + else { + r = free_and_strdup(&s, "-.slice"); + if (r < 0) { + free(s); + return r; + } + } + + *ret = s; + return 1; +} + +int slice_build_subslice(const char *slice, const char*name, char **ret) { + char *subslice; + + assert(slice); + assert(name); + assert(ret); + + if (!slice_name_is_valid(slice)) + return -EINVAL; + + if (!unit_prefix_is_valid(name)) + return -EINVAL; + + if (streq(slice, "-.slice")) + subslice = strappend(name, ".slice"); + else { + char *e; + + assert_se(e = endswith(slice, ".slice")); + + subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1); + if (!subslice) + return -ENOMEM; + + stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice"); + } + + *ret = subslice; + return 0; +} + +bool slice_name_is_valid(const char *name) { + const char *p, *e; + bool dash = false; + + if (!unit_name_is_valid(name, UNIT_NAME_PLAIN)) + return false; + + if (streq(name, "-.slice")) + return true; + + e = endswith(name, ".slice"); + if (!e) + return false; + + for (p = name; p < e; p++) { + + if (*p == '-') { + + /* Don't allow initial dash */ + if (p == name) + return false; + + /* Don't allow multiple dashes */ + if (dash) + return false; + + dash = true; + } else + dash = false; + } + + /* Don't allow trailing hash */ + if (dash) + return false; + + return true; +} + +static const char* const unit_type_table[_UNIT_TYPE_MAX] = { + [UNIT_SERVICE] = "service", + [UNIT_SOCKET] = "socket", + [UNIT_BUSNAME] = "busname", + [UNIT_TARGET] = "target", + [UNIT_DEVICE] = "device", + [UNIT_MOUNT] = "mount", + [UNIT_AUTOMOUNT] = "automount", + [UNIT_SWAP] = "swap", + [UNIT_TIMER] = "timer", + [UNIT_PATH] = "path", + [UNIT_SLICE] = "slice", + [UNIT_SCOPE] = "scope", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType); + +static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { + [UNIT_STUB] = "stub", + [UNIT_LOADED] = "loaded", + [UNIT_NOT_FOUND] = "not-found", + [UNIT_ERROR] = "error", + [UNIT_MERGED] = "merged", + [UNIT_MASKED] = "masked" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); + +static const char* const unit_active_state_table[_UNIT_ACTIVE_STATE_MAX] = { + [UNIT_ACTIVE] = "active", + [UNIT_RELOADING] = "reloading", + [UNIT_INACTIVE] = "inactive", + [UNIT_FAILED] = "failed", + [UNIT_ACTIVATING] = "activating", + [UNIT_DEACTIVATING] = "deactivating" +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_active_state, UnitActiveState); + +static const char* const automount_state_table[_AUTOMOUNT_STATE_MAX] = { + [AUTOMOUNT_DEAD] = "dead", + [AUTOMOUNT_WAITING] = "waiting", + [AUTOMOUNT_RUNNING] = "running", + [AUTOMOUNT_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(automount_state, AutomountState); + +static const char* const busname_state_table[_BUSNAME_STATE_MAX] = { + [BUSNAME_DEAD] = "dead", + [BUSNAME_MAKING] = "making", + [BUSNAME_REGISTERED] = "registered", + [BUSNAME_LISTENING] = "listening", + [BUSNAME_RUNNING] = "running", + [BUSNAME_SIGTERM] = "sigterm", + [BUSNAME_SIGKILL] = "sigkill", + [BUSNAME_FAILED] = "failed", +}; + +DEFINE_STRING_TABLE_LOOKUP(busname_state, BusNameState); + +static const char* const device_state_table[_DEVICE_STATE_MAX] = { + [DEVICE_DEAD] = "dead", + [DEVICE_TENTATIVE] = "tentative", + [DEVICE_PLUGGED] = "plugged", +}; + +DEFINE_STRING_TABLE_LOOKUP(device_state, DeviceState); + +static const char* const mount_state_table[_MOUNT_STATE_MAX] = { + [MOUNT_DEAD] = "dead", + [MOUNT_MOUNTING] = "mounting", + [MOUNT_MOUNTING_DONE] = "mounting-done", + [MOUNT_MOUNTED] = "mounted", + [MOUNT_REMOUNTING] = "remounting", + [MOUNT_UNMOUNTING] = "unmounting", + [MOUNT_MOUNTING_SIGTERM] = "mounting-sigterm", + [MOUNT_MOUNTING_SIGKILL] = "mounting-sigkill", + [MOUNT_REMOUNTING_SIGTERM] = "remounting-sigterm", + [MOUNT_REMOUNTING_SIGKILL] = "remounting-sigkill", + [MOUNT_UNMOUNTING_SIGTERM] = "unmounting-sigterm", + [MOUNT_UNMOUNTING_SIGKILL] = "unmounting-sigkill", + [MOUNT_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(mount_state, MountState); + +static const char* const path_state_table[_PATH_STATE_MAX] = { + [PATH_DEAD] = "dead", + [PATH_WAITING] = "waiting", + [PATH_RUNNING] = "running", + [PATH_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(path_state, PathState); + +static const char* const scope_state_table[_SCOPE_STATE_MAX] = { + [SCOPE_DEAD] = "dead", + [SCOPE_RUNNING] = "running", + [SCOPE_ABANDONED] = "abandoned", + [SCOPE_STOP_SIGTERM] = "stop-sigterm", + [SCOPE_STOP_SIGKILL] = "stop-sigkill", + [SCOPE_FAILED] = "failed", +}; + +DEFINE_STRING_TABLE_LOOKUP(scope_state, ScopeState); + +static const char* const service_state_table[_SERVICE_STATE_MAX] = { + [SERVICE_DEAD] = "dead", + [SERVICE_START_PRE] = "start-pre", + [SERVICE_START] = "start", + [SERVICE_START_POST] = "start-post", + [SERVICE_RUNNING] = "running", + [SERVICE_EXITED] = "exited", + [SERVICE_RELOAD] = "reload", + [SERVICE_STOP] = "stop", + [SERVICE_STOP_SIGABRT] = "stop-sigabrt", + [SERVICE_STOP_SIGTERM] = "stop-sigterm", + [SERVICE_STOP_SIGKILL] = "stop-sigkill", + [SERVICE_STOP_POST] = "stop-post", + [SERVICE_FINAL_SIGTERM] = "final-sigterm", + [SERVICE_FINAL_SIGKILL] = "final-sigkill", + [SERVICE_FAILED] = "failed", + [SERVICE_AUTO_RESTART] = "auto-restart", +}; + +DEFINE_STRING_TABLE_LOOKUP(service_state, ServiceState); + +static const char* const slice_state_table[_SLICE_STATE_MAX] = { + [SLICE_DEAD] = "dead", + [SLICE_ACTIVE] = "active" +}; + +DEFINE_STRING_TABLE_LOOKUP(slice_state, SliceState); + +static const char* const socket_state_table[_SOCKET_STATE_MAX] = { + [SOCKET_DEAD] = "dead", + [SOCKET_START_PRE] = "start-pre", + [SOCKET_START_CHOWN] = "start-chown", + [SOCKET_START_POST] = "start-post", + [SOCKET_LISTENING] = "listening", + [SOCKET_RUNNING] = "running", + [SOCKET_STOP_PRE] = "stop-pre", + [SOCKET_STOP_PRE_SIGTERM] = "stop-pre-sigterm", + [SOCKET_STOP_PRE_SIGKILL] = "stop-pre-sigkill", + [SOCKET_STOP_POST] = "stop-post", + [SOCKET_FINAL_SIGTERM] = "final-sigterm", + [SOCKET_FINAL_SIGKILL] = "final-sigkill", + [SOCKET_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_state, SocketState); + +static const char* const swap_state_table[_SWAP_STATE_MAX] = { + [SWAP_DEAD] = "dead", + [SWAP_ACTIVATING] = "activating", + [SWAP_ACTIVATING_DONE] = "activating-done", + [SWAP_ACTIVE] = "active", + [SWAP_DEACTIVATING] = "deactivating", + [SWAP_ACTIVATING_SIGTERM] = "activating-sigterm", + [SWAP_ACTIVATING_SIGKILL] = "activating-sigkill", + [SWAP_DEACTIVATING_SIGTERM] = "deactivating-sigterm", + [SWAP_DEACTIVATING_SIGKILL] = "deactivating-sigkill", + [SWAP_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(swap_state, SwapState); + +static const char* const target_state_table[_TARGET_STATE_MAX] = { + [TARGET_DEAD] = "dead", + [TARGET_ACTIVE] = "active" +}; + +DEFINE_STRING_TABLE_LOOKUP(target_state, TargetState); + +static const char* const timer_state_table[_TIMER_STATE_MAX] = { + [TIMER_DEAD] = "dead", + [TIMER_WAITING] = "waiting", + [TIMER_RUNNING] = "running", + [TIMER_ELAPSED] = "elapsed", + [TIMER_FAILED] = "failed" +}; + +DEFINE_STRING_TABLE_LOOKUP(timer_state, TimerState); + +static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = { + [UNIT_REQUIRES] = "Requires", + [UNIT_REQUISITE] = "Requisite", + [UNIT_WANTS] = "Wants", + [UNIT_BINDS_TO] = "BindsTo", + [UNIT_PART_OF] = "PartOf", + [UNIT_REQUIRED_BY] = "RequiredBy", + [UNIT_REQUISITE_OF] = "RequisiteOf", + [UNIT_WANTED_BY] = "WantedBy", + [UNIT_BOUND_BY] = "BoundBy", + [UNIT_CONSISTS_OF] = "ConsistsOf", + [UNIT_CONFLICTS] = "Conflicts", + [UNIT_CONFLICTED_BY] = "ConflictedBy", + [UNIT_BEFORE] = "Before", + [UNIT_AFTER] = "After", + [UNIT_ON_FAILURE] = "OnFailure", + [UNIT_TRIGGERS] = "Triggers", + [UNIT_TRIGGERED_BY] = "TriggeredBy", + [UNIT_PROPAGATES_RELOAD_TO] = "PropagatesReloadTo", + [UNIT_RELOAD_PROPAGATED_FROM] = "ReloadPropagatedFrom", + [UNIT_JOINS_NAMESPACE_OF] = "JoinsNamespaceOf", + [UNIT_REFERENCES] = "References", + [UNIT_REFERENCED_BY] = "ReferencedBy", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency); diff --git a/src/libbasic/unit-name.h b/src/libbasic/unit-name.h new file mode 100644 index 0000000000..f209a84634 --- /dev/null +++ b/src/libbasic/unit-name.h @@ -0,0 +1,368 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "macro.h" + +#define UNIT_NAME_MAX 256 + +typedef enum UnitType { + UNIT_SERVICE = 0, + UNIT_SOCKET, + UNIT_BUSNAME, + UNIT_TARGET, + UNIT_DEVICE, + UNIT_MOUNT, + UNIT_AUTOMOUNT, + UNIT_SWAP, + UNIT_TIMER, + UNIT_PATH, + UNIT_SLICE, + UNIT_SCOPE, + _UNIT_TYPE_MAX, + _UNIT_TYPE_INVALID = -1 +} UnitType; + +typedef enum UnitLoadState { + UNIT_STUB = 0, + UNIT_LOADED, + UNIT_NOT_FOUND, + UNIT_ERROR, + UNIT_MERGED, + UNIT_MASKED, + _UNIT_LOAD_STATE_MAX, + _UNIT_LOAD_STATE_INVALID = -1 +} UnitLoadState; + +typedef enum UnitActiveState { + UNIT_ACTIVE, + UNIT_RELOADING, + UNIT_INACTIVE, + UNIT_FAILED, + UNIT_ACTIVATING, + UNIT_DEACTIVATING, + _UNIT_ACTIVE_STATE_MAX, + _UNIT_ACTIVE_STATE_INVALID = -1 +} UnitActiveState; + +typedef enum AutomountState { + AUTOMOUNT_DEAD, + AUTOMOUNT_WAITING, + AUTOMOUNT_RUNNING, + AUTOMOUNT_FAILED, + _AUTOMOUNT_STATE_MAX, + _AUTOMOUNT_STATE_INVALID = -1 +} AutomountState; + +typedef enum BusNameState { + BUSNAME_DEAD, + BUSNAME_MAKING, + BUSNAME_REGISTERED, + BUSNAME_LISTENING, + BUSNAME_RUNNING, + BUSNAME_SIGTERM, + BUSNAME_SIGKILL, + BUSNAME_FAILED, + _BUSNAME_STATE_MAX, + _BUSNAME_STATE_INVALID = -1 +} BusNameState; + +/* We simply watch devices, we cannot plug/unplug them. That + * simplifies the state engine greatly */ +typedef enum DeviceState { + DEVICE_DEAD, + DEVICE_TENTATIVE, /* mounted or swapped, but not (yet) announced by udev */ + DEVICE_PLUGGED, /* announced by udev */ + _DEVICE_STATE_MAX, + _DEVICE_STATE_INVALID = -1 +} DeviceState; + +typedef enum MountState { + MOUNT_DEAD, + MOUNT_MOUNTING, /* /usr/bin/mount is running, but the mount is not done yet. */ + MOUNT_MOUNTING_DONE, /* /usr/bin/mount is running, and the mount is done. */ + MOUNT_MOUNTED, + MOUNT_REMOUNTING, + MOUNT_UNMOUNTING, + MOUNT_MOUNTING_SIGTERM, + MOUNT_MOUNTING_SIGKILL, + MOUNT_REMOUNTING_SIGTERM, + MOUNT_REMOUNTING_SIGKILL, + MOUNT_UNMOUNTING_SIGTERM, + MOUNT_UNMOUNTING_SIGKILL, + MOUNT_FAILED, + _MOUNT_STATE_MAX, + _MOUNT_STATE_INVALID = -1 +} MountState; + +typedef enum PathState { + PATH_DEAD, + PATH_WAITING, + PATH_RUNNING, + PATH_FAILED, + _PATH_STATE_MAX, + _PATH_STATE_INVALID = -1 +} PathState; + +typedef enum ScopeState { + SCOPE_DEAD, + SCOPE_RUNNING, + SCOPE_ABANDONED, + SCOPE_STOP_SIGTERM, + SCOPE_STOP_SIGKILL, + SCOPE_FAILED, + _SCOPE_STATE_MAX, + _SCOPE_STATE_INVALID = -1 +} ScopeState; + +typedef enum ServiceState { + SERVICE_DEAD, + SERVICE_START_PRE, + SERVICE_START, + SERVICE_START_POST, + SERVICE_RUNNING, + SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */ + SERVICE_RELOAD, + SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */ + SERVICE_STOP_SIGABRT, /* Watchdog timeout */ + SERVICE_STOP_SIGTERM, + SERVICE_STOP_SIGKILL, + SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, /* In case the STOP_POST executable hangs, we shoot that down, too */ + SERVICE_FINAL_SIGKILL, + SERVICE_FAILED, + SERVICE_AUTO_RESTART, + _SERVICE_STATE_MAX, + _SERVICE_STATE_INVALID = -1 +} ServiceState; + +typedef enum SliceState { + SLICE_DEAD, + SLICE_ACTIVE, + _SLICE_STATE_MAX, + _SLICE_STATE_INVALID = -1 +} SliceState; + +typedef enum SocketState { + SOCKET_DEAD, + SOCKET_START_PRE, + SOCKET_START_CHOWN, + SOCKET_START_POST, + SOCKET_LISTENING, + SOCKET_RUNNING, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL, + SOCKET_STOP_POST, + SOCKET_FINAL_SIGTERM, + SOCKET_FINAL_SIGKILL, + SOCKET_FAILED, + _SOCKET_STATE_MAX, + _SOCKET_STATE_INVALID = -1 +} SocketState; + +typedef enum SwapState { + SWAP_DEAD, + SWAP_ACTIVATING, /* /sbin/swapon is running, but the swap not yet enabled. */ + SWAP_ACTIVATING_DONE, /* /sbin/swapon is running, and the swap is done. */ + SWAP_ACTIVE, + SWAP_DEACTIVATING, + SWAP_ACTIVATING_SIGTERM, + SWAP_ACTIVATING_SIGKILL, + SWAP_DEACTIVATING_SIGTERM, + SWAP_DEACTIVATING_SIGKILL, + SWAP_FAILED, + _SWAP_STATE_MAX, + _SWAP_STATE_INVALID = -1 +} SwapState; + + +typedef enum TargetState { + TARGET_DEAD, + TARGET_ACTIVE, + _TARGET_STATE_MAX, + _TARGET_STATE_INVALID = -1 +} TargetState; + +typedef enum TimerState { + TIMER_DEAD, + TIMER_WAITING, + TIMER_RUNNING, + TIMER_ELAPSED, + TIMER_FAILED, + _TIMER_STATE_MAX, + _TIMER_STATE_INVALID = -1 +} TimerState; + +typedef enum UnitDependency { + /* Positive dependencies */ + UNIT_REQUIRES, + UNIT_REQUISITE, + UNIT_WANTS, + UNIT_BINDS_TO, + UNIT_PART_OF, + + /* Inverse of the above */ + UNIT_REQUIRED_BY, /* inverse of 'requires' is 'required_by' */ + UNIT_REQUISITE_OF, /* inverse of 'requisite' is 'requisite_of' */ + UNIT_WANTED_BY, /* inverse of 'wants' */ + UNIT_BOUND_BY, /* inverse of 'binds_to' */ + UNIT_CONSISTS_OF, /* inverse of 'part_of' */ + + /* Negative dependencies */ + UNIT_CONFLICTS, /* inverse of 'conflicts' is 'conflicted_by' */ + UNIT_CONFLICTED_BY, + + /* Order */ + UNIT_BEFORE, /* inverse of 'before' is 'after' and vice versa */ + UNIT_AFTER, + + /* On Failure */ + UNIT_ON_FAILURE, + + /* Triggers (i.e. a socket triggers a service) */ + UNIT_TRIGGERS, + UNIT_TRIGGERED_BY, + + /* Propagate reloads */ + UNIT_PROPAGATES_RELOAD_TO, + UNIT_RELOAD_PROPAGATED_FROM, + + /* Joins namespace of */ + UNIT_JOINS_NAMESPACE_OF, + + /* Reference information for GC logic */ + UNIT_REFERENCES, /* Inverse of 'references' is 'referenced_by' */ + UNIT_REFERENCED_BY, + + _UNIT_DEPENDENCY_MAX, + _UNIT_DEPENDENCY_INVALID = -1 +} UnitDependency; + +typedef enum UnitNameFlags { + UNIT_NAME_PLAIN = 1, /* Allow foo.service */ + UNIT_NAME_INSTANCE = 2, /* Allow foo@bar.service */ + UNIT_NAME_TEMPLATE = 4, /* Allow foo@.service */ + UNIT_NAME_ANY = UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE, +} UnitNameFlags; + +bool unit_name_is_valid(const char *n, UnitNameFlags flags) _pure_; +bool unit_prefix_is_valid(const char *p) _pure_; +bool unit_instance_is_valid(const char *i) _pure_; +bool unit_suffix_is_valid(const char *s) _pure_; + +static inline int unit_prefix_and_instance_is_valid(const char *p) { + /* For prefix+instance and instance the same rules apply */ + return unit_instance_is_valid(p); +} + +int unit_name_to_prefix(const char *n, char **prefix); +int unit_name_to_instance(const char *n, char **instance); +int unit_name_to_prefix_and_instance(const char *n, char **ret); + +UnitType unit_name_to_type(const char *n) _pure_; + +int unit_name_change_suffix(const char *n, const char *suffix, char **ret); + +int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret); + +char *unit_name_escape(const char *f); +int unit_name_unescape(const char *f, char **ret); +int unit_name_path_escape(const char *f, char **ret); +int unit_name_path_unescape(const char *f, char **ret); + +int unit_name_replace_instance(const char *f, const char *i, char **ret); + +int unit_name_template(const char *f, char **ret); + +int unit_name_from_path(const char *path, const char *suffix, char **ret); +int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret); +int unit_name_to_path(const char *name, char **ret); + +char *unit_dbus_path_from_name(const char *name); +int unit_name_from_dbus_path(const char *path, char **name); + +const char* unit_dbus_interface_from_type(UnitType t); +const char *unit_dbus_interface_from_name(const char *name); + +typedef enum UnitNameMangle { + UNIT_NAME_NOGLOB, + UNIT_NAME_GLOB, +} UnitNameMangle; + +int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret); + +static inline int unit_name_mangle(const char *name, UnitNameMangle allow_globs, char **ret) { + return unit_name_mangle_with_suffix(name, allow_globs, ".service", ret); +} + +int slice_build_parent_slice(const char *slice, char **ret); +int slice_build_subslice(const char *slice, const char*name, char **subslice); +bool slice_name_is_valid(const char *name); + +const char *unit_type_to_string(UnitType i) _const_; +UnitType unit_type_from_string(const char *s) _pure_; + +const char *unit_load_state_to_string(UnitLoadState i) _const_; +UnitLoadState unit_load_state_from_string(const char *s) _pure_; + +const char *unit_active_state_to_string(UnitActiveState i) _const_; +UnitActiveState unit_active_state_from_string(const char *s) _pure_; + +const char* automount_state_to_string(AutomountState i) _const_; +AutomountState automount_state_from_string(const char *s) _pure_; + +const char* busname_state_to_string(BusNameState i) _const_; +BusNameState busname_state_from_string(const char *s) _pure_; + +const char* device_state_to_string(DeviceState i) _const_; +DeviceState device_state_from_string(const char *s) _pure_; + +const char* mount_state_to_string(MountState i) _const_; +MountState mount_state_from_string(const char *s) _pure_; + +const char* path_state_to_string(PathState i) _const_; +PathState path_state_from_string(const char *s) _pure_; + +const char* scope_state_to_string(ScopeState i) _const_; +ScopeState scope_state_from_string(const char *s) _pure_; + +const char* service_state_to_string(ServiceState i) _const_; +ServiceState service_state_from_string(const char *s) _pure_; + +const char* slice_state_to_string(SliceState i) _const_; +SliceState slice_state_from_string(const char *s) _pure_; + +const char* socket_state_to_string(SocketState i) _const_; +SocketState socket_state_from_string(const char *s) _pure_; + +const char* swap_state_to_string(SwapState i) _const_; +SwapState swap_state_from_string(const char *s) _pure_; + +const char* target_state_to_string(TargetState i) _const_; +TargetState target_state_from_string(const char *s) _pure_; + +const char *timer_state_to_string(TimerState i) _const_; +TimerState timer_state_from_string(const char *s) _pure_; + +const char *unit_dependency_to_string(UnitDependency i) _const_; +UnitDependency unit_dependency_from_string(const char *s) _pure_; diff --git a/src/libbasic/user-util.c b/src/libbasic/user-util.c new file mode 100644 index 0000000000..19155bce53 --- /dev/null +++ b/src/libbasic/user-util.c @@ -0,0 +1,480 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "string-util.h" +#include "user-util.h" + +bool uid_is_valid(uid_t uid) { + + /* Some libc APIs use UID_INVALID as special placeholder */ + if (uid == (uid_t) UINT32_C(0xFFFFFFFF)) + return false; + + /* A long time ago UIDs where 16bit, hence explicitly avoid the 16bit -1 too */ + if (uid == (uid_t) UINT32_C(0xFFFF)) + return false; + + return true; +} + +int parse_uid(const char *s, uid_t *ret) { + uint32_t uid = 0; + int r; + + assert(s); + + assert_cc(sizeof(uid_t) == sizeof(uint32_t)); + r = safe_atou32(s, &uid); + if (r < 0) + return r; + + if (!uid_is_valid(uid)) + return -ENXIO; /* we return ENXIO instead of EINVAL + * here, to make it easy to distuingish + * invalid numeric uids from invalid + * strings. */ + + if (ret) + *ret = uid; + + return 0; +} + +char* getlogname_malloc(void) { + uid_t uid; + struct stat st; + + if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0) + uid = st.st_uid; + else + uid = getuid(); + + return uid_to_name(uid); +} + +char *getusername_malloc(void) { + const char *e; + + e = getenv("USER"); + if (e) + return strdup(e); + + return uid_to_name(getuid()); +} + +int get_user_creds( + const char **username, + uid_t *uid, gid_t *gid, + const char **home, + const char **shell) { + + struct passwd *p; + uid_t u; + + assert(username); + assert(*username); + + /* We enforce some special rules for uid=0: in order to avoid + * NSS lookups for root we hardcode its data. */ + + if (streq(*username, "root") || streq(*username, "0")) { + *username = "root"; + + if (uid) + *uid = 0; + + if (gid) + *gid = 0; + + if (home) + *home = "/root"; + + if (shell) + *shell = "/bin/sh"; + + return 0; + } + + if (parse_uid(*username, &u) >= 0) { + errno = 0; + p = getpwuid(u); + + /* If there are multiple users with the same id, make + * sure to leave $USER to the configured value instead + * of the first occurrence in the database. However if + * the uid was configured by a numeric uid, then let's + * pick the real username from /etc/passwd. */ + if (p) + *username = p->pw_name; + } else { + errno = 0; + p = getpwnam(*username); + } + + if (!p) + return errno > 0 ? -errno : -ESRCH; + + if (uid) { + if (!uid_is_valid(p->pw_uid)) + return -EBADMSG; + + *uid = p->pw_uid; + } + + if (gid) { + if (!gid_is_valid(p->pw_gid)) + return -EBADMSG; + + *gid = p->pw_gid; + } + + if (home) + *home = p->pw_dir; + + if (shell) + *shell = p->pw_shell; + + return 0; +} + +int get_group_creds(const char **groupname, gid_t *gid) { + struct group *g; + gid_t id; + + assert(groupname); + + /* We enforce some special rules for gid=0: in order to avoid + * NSS lookups for root we hardcode its data. */ + + if (streq(*groupname, "root") || streq(*groupname, "0")) { + *groupname = "root"; + + if (gid) + *gid = 0; + + return 0; + } + + if (parse_gid(*groupname, &id) >= 0) { + errno = 0; + g = getgrgid(id); + + if (g) + *groupname = g->gr_name; + } else { + errno = 0; + g = getgrnam(*groupname); + } + + if (!g) + return errno > 0 ? -errno : -ESRCH; + + if (gid) { + if (!gid_is_valid(g->gr_gid)) + return -EBADMSG; + + *gid = g->gr_gid; + } + + return 0; +} + +char* uid_to_name(uid_t uid) { + char *ret; + int r; + + /* Shortcut things to avoid NSS lookups */ + if (uid == 0) + return strdup("root"); + + if (uid_is_valid(uid)) { + long bufsize; + + bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (bufsize <= 0) + bufsize = 4096; + + for (;;) { + struct passwd pwbuf, *pw = NULL; + _cleanup_free_ char *buf = NULL; + + buf = malloc(bufsize); + if (!buf) + return NULL; + + r = getpwuid_r(uid, &pwbuf, buf, (size_t) bufsize, &pw); + if (r == 0 && pw) + return strdup(pw->pw_name); + if (r != ERANGE) + break; + + bufsize *= 2; + } + } + + if (asprintf(&ret, UID_FMT, uid) < 0) + return NULL; + + return ret; +} + +char* gid_to_name(gid_t gid) { + char *ret; + int r; + + if (gid == 0) + return strdup("root"); + + if (gid_is_valid(gid)) { + long bufsize; + + bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); + if (bufsize <= 0) + bufsize = 4096; + + for (;;) { + struct group grbuf, *gr = NULL; + _cleanup_free_ char *buf = NULL; + + buf = malloc(bufsize); + if (!buf) + return NULL; + + r = getgrgid_r(gid, &grbuf, buf, (size_t) bufsize, &gr); + if (r == 0 && gr) + return strdup(gr->gr_name); + if (r != ERANGE) + break; + + bufsize *= 2; + } + } + + if (asprintf(&ret, GID_FMT, gid) < 0) + return NULL; + + return ret; +} + +int in_gid(gid_t gid) { + gid_t *gids; + int ngroups_max, r, i; + + if (getgid() == gid) + return 1; + + if (getegid() == gid) + return 1; + + if (!gid_is_valid(gid)) + return -EINVAL; + + ngroups_max = sysconf(_SC_NGROUPS_MAX); + assert(ngroups_max > 0); + + gids = alloca(sizeof(gid_t) * ngroups_max); + + r = getgroups(ngroups_max, gids); + if (r < 0) + return -errno; + + for (i = 0; i < r; i++) + if (gids[i] == gid) + return 1; + + return 0; +} + +int in_group(const char *name) { + int r; + gid_t gid; + + r = get_group_creds(&name, &gid); + if (r < 0) + return r; + + return in_gid(gid); +} + +int get_home_dir(char **_h) { + struct passwd *p; + const char *e; + char *h; + uid_t u; + + assert(_h); + + /* Take the user specified one */ + e = secure_getenv("HOME"); + if (e && path_is_absolute(e)) { + h = strdup(e); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; + } + + /* Hardcode home directory for root to avoid NSS */ + u = getuid(); + if (u == 0) { + h = strdup("/root"); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; + } + + /* Check the database... */ + errno = 0; + p = getpwuid(u); + if (!p) + return errno > 0 ? -errno : -ESRCH; + + if (!path_is_absolute(p->pw_dir)) + return -EINVAL; + + h = strdup(p->pw_dir); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; +} + +int get_shell(char **_s) { + struct passwd *p; + const char *e; + char *s; + uid_t u; + + assert(_s); + + /* Take the user specified one */ + e = getenv("SHELL"); + if (e) { + s = strdup(e); + if (!s) + return -ENOMEM; + + *_s = s; + return 0; + } + + /* Hardcode home directory for root to avoid NSS */ + u = getuid(); + if (u == 0) { + s = strdup("/bin/sh"); + if (!s) + return -ENOMEM; + + *_s = s; + return 0; + } + + /* Check the database... */ + errno = 0; + p = getpwuid(u); + if (!p) + return errno > 0 ? -errno : -ESRCH; + + if (!path_is_absolute(p->pw_shell)) + return -EINVAL; + + s = strdup(p->pw_shell); + if (!s) + return -ENOMEM; + + *_s = s; + return 0; +} + +int reset_uid_gid(void) { + + if (setgroups(0, NULL) < 0) + return -errno; + + if (setresgid(0, 0, 0) < 0) + return -errno; + + if (setresuid(0, 0, 0) < 0) + return -errno; + + return 0; +} + +int take_etc_passwd_lock(const char *root) { + + struct flock flock = { + .l_type = F_WRLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0, + }; + + const char *path; + int fd, r; + + /* This is roughly the same as lckpwdf(), but not as awful. We + * don't want to use alarm() and signals, hence we implement + * our own trivial version of this. + * + * Note that shadow-utils also takes per-database locks in + * addition to lckpwdf(). However, we don't given that they + * are redundant as they 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. */ + + if (root) + path = prefix_roota(root, "/etc/.pwd.lock"); + else + path = "/etc/.pwd.lock"; + + fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0600); + if (fd < 0) + return -errno; + + r = fcntl(fd, F_SETLKW, &flock); + if (r < 0) { + safe_close(fd); + return -errno; + } + + return fd; +} diff --git a/src/libbasic/user-util.h b/src/libbasic/user-util.h new file mode 100644 index 0000000000..c23f1d485d --- /dev/null +++ b/src/libbasic/user-util.h @@ -0,0 +1,65 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +bool uid_is_valid(uid_t uid); + +static inline bool gid_is_valid(gid_t gid) { + return uid_is_valid((uid_t) gid); +} + +int parse_uid(const char *s, uid_t* ret_uid); + +static inline int parse_gid(const char *s, gid_t *ret_gid) { + return parse_uid(s, (uid_t*) ret_gid); +} + +char* getlogname_malloc(void); +char* getusername_malloc(void); + +int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home, const char **shell); +int get_group_creds(const char **groupname, gid_t *gid); + +char* uid_to_name(uid_t uid); +char* gid_to_name(gid_t gid); + +int in_gid(gid_t gid); +int in_group(const char *name); + +int get_home_dir(char **ret); +int get_shell(char **_ret); + +int reset_uid_gid(void); + +int take_etc_passwd_lock(const char *root); + +#define UID_INVALID ((uid_t) -1) +#define GID_INVALID ((gid_t) -1) + +/* The following macros add 1 when converting things, since UID 0 is a + * valid UID, while the pointer NULL is special */ +#define PTR_TO_UID(p) ((uid_t) (((uintptr_t) (p))-1)) +#define UID_TO_PTR(u) ((void*) (((uintptr_t) (u))+1)) + +#define PTR_TO_GID(p) ((gid_t) (((uintptr_t) (p))-1)) +#define GID_TO_PTR(u) ((void*) (((uintptr_t) (u))+1)) diff --git a/src/libbasic/utf8.c b/src/libbasic/utf8.c new file mode 100644 index 0000000000..629db123cd --- /dev/null +++ b/src/libbasic/utf8.c @@ -0,0 +1,409 @@ +/*** + This file is part of systemd. + + Copyright 2008-2011 Kay Sievers + Copyright 2012 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 . +***/ + +/* Parts of this file are based on the GLIB utf8 validation functions. The + * original license text follows. */ + +/* gutf8.c - Operations on UTF-8 strings. + * + * Copyright (C) 1999 Tom Tromey + * Copyright (C) 2000 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "hexdecoct.h" +#include "macro.h" +#include "utf8.h" + +bool unichar_is_valid(char32_t ch) { + + if (ch >= 0x110000) /* End of unicode space */ + return false; + if ((ch & 0xFFFFF800) == 0xD800) /* Reserved area for UTF-16 */ + return false; + if ((ch >= 0xFDD0) && (ch <= 0xFDEF)) /* Reserved */ + return false; + if ((ch & 0xFFFE) == 0xFFFE) /* BOM (Byte Order Mark) */ + return false; + + return true; +} + +static bool unichar_is_control(char32_t ch) { + + /* + 0 to ' '-1 is the C0 range. + DEL=0x7F, and DEL+1 to 0x9F is C1 range. + '\t' is in C0 range, but more or less harmless and commonly used. + */ + + return (ch < ' ' && ch != '\t' && ch != '\n') || + (0x7F <= ch && ch <= 0x9F); +} + +/* count of characters used to encode one unicode char */ +static int utf8_encoded_expected_len(const char *str) { + unsigned char c; + + assert(str); + + c = (unsigned char) str[0]; + if (c < 0x80) + return 1; + if ((c & 0xe0) == 0xc0) + return 2; + if ((c & 0xf0) == 0xe0) + return 3; + if ((c & 0xf8) == 0xf0) + return 4; + if ((c & 0xfc) == 0xf8) + return 5; + if ((c & 0xfe) == 0xfc) + return 6; + + return 0; +} + +/* decode one unicode char */ +int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar) { + char32_t unichar; + int len, i; + + assert(str); + + len = utf8_encoded_expected_len(str); + + switch (len) { + case 1: + *ret_unichar = (char32_t)str[0]; + return 0; + case 2: + unichar = str[0] & 0x1f; + break; + case 3: + unichar = (char32_t)str[0] & 0x0f; + break; + case 4: + unichar = (char32_t)str[0] & 0x07; + break; + case 5: + unichar = (char32_t)str[0] & 0x03; + break; + case 6: + unichar = (char32_t)str[0] & 0x01; + break; + default: + return -EINVAL; + } + + for (i = 1; i < len; i++) { + if (((char32_t)str[i] & 0xc0) != 0x80) + return -EINVAL; + unichar <<= 6; + unichar |= (char32_t)str[i] & 0x3f; + } + + *ret_unichar = unichar; + + return 0; +} + +bool utf8_is_printable_newline(const char* str, size_t length, bool newline) { + const char *p; + + assert(str); + + for (p = str; length;) { + int encoded_len, r; + char32_t val; + + encoded_len = utf8_encoded_valid_unichar(p); + if (encoded_len < 0 || + (size_t) encoded_len > length) + return false; + + r = utf8_encoded_to_unichar(p, &val); + if (r < 0 || + unichar_is_control(val) || + (!newline && val == '\n')) + return false; + + length -= encoded_len; + p += encoded_len; + } + + return true; +} + +const char *utf8_is_valid(const char *str) { + const uint8_t *p; + + assert(str); + + for (p = (const uint8_t*) str; *p; ) { + int len; + + len = utf8_encoded_valid_unichar((const char *)p); + if (len < 0) + return NULL; + + p += len; + } + + return str; +} + +char *utf8_escape_invalid(const char *str) { + char *p, *s; + + assert(str); + + p = s = malloc(strlen(str) * 4 + 1); + if (!p) + return NULL; + + while (*str) { + int len; + + len = utf8_encoded_valid_unichar(str); + if (len > 0) { + s = mempcpy(s, str, len); + str += len; + } else { + s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER); + str += 1; + } + } + + *s = '\0'; + + return p; +} + +char *utf8_escape_non_printable(const char *str) { + char *p, *s; + + assert(str); + + p = s = malloc(strlen(str) * 4 + 1); + if (!p) + return NULL; + + while (*str) { + int len; + + len = utf8_encoded_valid_unichar(str); + if (len > 0) { + if (utf8_is_printable(str, len)) { + s = mempcpy(s, str, len); + str += len; + } else { + while (len > 0) { + *(s++) = '\\'; + *(s++) = 'x'; + *(s++) = hexchar((int) *str >> 4); + *(s++) = hexchar((int) *str); + + str += 1; + len --; + } + } + } else { + s = stpcpy(s, UTF8_REPLACEMENT_CHARACTER); + str += 1; + } + } + + *s = '\0'; + + return p; +} + +char *ascii_is_valid(const char *str) { + const char *p; + + assert(str); + + for (p = str; *p; p++) + if ((unsigned char) *p >= 128) + return NULL; + + return (char*) str; +} + +/** + * utf8_encode_unichar() - Encode single UCS-4 character as UTF-8 + * @out_utf8: output buffer of at least 4 bytes or NULL + * @g: UCS-4 character to encode + * + * This encodes a single UCS-4 character as UTF-8 and writes it into @out_utf8. + * The length of the character is returned. It is not zero-terminated! If the + * output buffer is NULL, only the length is returned. + * + * Returns: The length in bytes that the UTF-8 representation does or would + * occupy. + */ +size_t utf8_encode_unichar(char *out_utf8, char32_t g) { + + if (g < (1 << 7)) { + if (out_utf8) + out_utf8[0] = g & 0x7f; + return 1; + } else if (g < (1 << 11)) { + if (out_utf8) { + out_utf8[0] = 0xc0 | ((g >> 6) & 0x1f); + out_utf8[1] = 0x80 | (g & 0x3f); + } + return 2; + } else if (g < (1 << 16)) { + if (out_utf8) { + out_utf8[0] = 0xe0 | ((g >> 12) & 0x0f); + out_utf8[1] = 0x80 | ((g >> 6) & 0x3f); + out_utf8[2] = 0x80 | (g & 0x3f); + } + return 3; + } else if (g < (1 << 21)) { + if (out_utf8) { + out_utf8[0] = 0xf0 | ((g >> 18) & 0x07); + out_utf8[1] = 0x80 | ((g >> 12) & 0x3f); + out_utf8[2] = 0x80 | ((g >> 6) & 0x3f); + out_utf8[3] = 0x80 | (g & 0x3f); + } + return 4; + } + + return 0; +} + +char *utf16_to_utf8(const void *s, size_t length) { + const uint8_t *f; + char *r, *t; + + r = new(char, (length * 4 + 1) / 2 + 1); + if (!r) + return NULL; + + f = s; + t = r; + + while (f < (const uint8_t*) s + length) { + char16_t w1, w2; + + /* see RFC 2781 section 2.2 */ + + w1 = f[1] << 8 | f[0]; + f += 2; + + if (!utf16_is_surrogate(w1)) { + t += utf8_encode_unichar(t, w1); + + continue; + } + + if (utf16_is_trailing_surrogate(w1)) + continue; + else if (f >= (const uint8_t*) s + length) + break; + + w2 = f[1] << 8 | f[0]; + f += 2; + + if (!utf16_is_trailing_surrogate(w2)) { + f -= 2; + continue; + } + + t += utf8_encode_unichar(t, utf16_surrogate_pair_to_unichar(w1, w2)); + } + + *t = 0; + return r; +} + +/* expected size used to encode one unicode char */ +static int utf8_unichar_to_encoded_len(char32_t unichar) { + + if (unichar < 0x80) + return 1; + if (unichar < 0x800) + return 2; + if (unichar < 0x10000) + return 3; + if (unichar < 0x200000) + return 4; + if (unichar < 0x4000000) + return 5; + + return 6; +} + +/* validate one encoded unicode char and return its length */ +int utf8_encoded_valid_unichar(const char *str) { + int len, i, r; + char32_t unichar; + + assert(str); + + len = utf8_encoded_expected_len(str); + if (len == 0) + return -EINVAL; + + /* ascii is valid */ + if (len == 1) + return 1; + + /* check if expected encoded chars are available */ + for (i = 0; i < len; i++) + if ((str[i] & 0x80) != 0x80) + return -EINVAL; + + r = utf8_encoded_to_unichar(str, &unichar); + if (r < 0) + return r; + + /* check if encoded length matches encoded value */ + if (utf8_unichar_to_encoded_len(unichar) != len) + return -EINVAL; + + /* check if value has valid range */ + if (!unichar_is_valid(unichar)) + return -EINVAL; + + return len; +} diff --git a/src/libbasic/utf8.h b/src/libbasic/utf8.h new file mode 100644 index 0000000000..12c272d66e --- /dev/null +++ b/src/libbasic/utf8.h @@ -0,0 +1,59 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include +#include +#include +#include + +#include "macro.h" +#include "missing.h" + +#define UTF8_REPLACEMENT_CHARACTER "\xef\xbf\xbd" + +bool unichar_is_valid(char32_t c); + +const char *utf8_is_valid(const char *s) _pure_; +char *ascii_is_valid(const char *s) _pure_; + +bool utf8_is_printable_newline(const char* str, size_t length, bool newline) _pure_; +#define utf8_is_printable(str, length) utf8_is_printable_newline(str, length, true) + +char *utf8_escape_invalid(const char *s); +char *utf8_escape_non_printable(const char *str); + +size_t utf8_encode_unichar(char *out_utf8, char32_t g); +char *utf16_to_utf8(const void *s, size_t length); + +int utf8_encoded_valid_unichar(const char *str); +int utf8_encoded_to_unichar(const char *str, char32_t *ret_unichar); + +static inline bool utf16_is_surrogate(char16_t c) { + return (0xd800 <= c && c <= 0xdfff); +} + +static inline bool utf16_is_trailing_surrogate(char16_t c) { + return (0xdc00 <= c && c <= 0xdfff); +} + +static inline char32_t utf16_surrogate_pair_to_unichar(char16_t lead, char16_t trail) { + return ((lead - 0xd800) << 10) + (trail - 0xdc00) + 0x10000; +} diff --git a/src/libbasic/util.c b/src/libbasic/util.c new file mode 100644 index 0000000000..ea1bed7ceb --- /dev/null +++ b/src/libbasic/util.c @@ -0,0 +1,793 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "build.h" +#include "def.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "hashmap.h" +#include "hostname-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "set.h" +#include "signal-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "user-util.h" +#include "util.h" + +/* Put this test here for a lack of better place */ +assert_cc(EAGAIN == EWOULDBLOCK); + +int saved_argc = 0; +char **saved_argv = NULL; + +size_t page_size(void) { + static thread_local size_t pgsz = 0; + long r; + + if (_likely_(pgsz > 0)) + return pgsz; + + r = sysconf(_SC_PAGESIZE); + assert(r > 0); + + pgsz = (size_t) r; + return pgsz; +} + +static int do_execute(char **directories, usec_t timeout, char *argv[]) { + _cleanup_hashmap_free_free_ Hashmap *pids = NULL; + _cleanup_set_free_free_ Set *seen = NULL; + char **directory; + + /* We fork this all off from a child process so that we can + * somewhat cleanly make use of SIGALRM to set a time limit */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + pids = hashmap_new(NULL); + if (!pids) + return log_oom(); + + seen = set_new(&string_hash_ops); + if (!seen) + return log_oom(); + + STRV_FOREACH(directory, directories) { + _cleanup_closedir_ DIR *d; + struct dirent *de; + + d = opendir(*directory); + if (!d) { + if (errno == ENOENT) + continue; + + return log_error_errno(errno, "Failed to open directory %s: %m", *directory); + } + + FOREACH_DIRENT(de, d, break) { + _cleanup_free_ char *path = NULL; + pid_t pid; + int r; + + if (!dirent_is_file(de)) + continue; + + if (set_contains(seen, de->d_name)) { + log_debug("%1$s/%2$s skipped (%2$s was already seen).", *directory, de->d_name); + continue; + } + + r = set_put_strdup(seen, de->d_name); + if (r < 0) + return log_oom(); + + path = strjoin(*directory, "/", de->d_name, NULL); + if (!path) + return log_oom(); + + if (null_or_empty_path(path)) { + log_debug("%s is empty (a mask).", path); + continue; + } + + pid = fork(); + if (pid < 0) { + log_error_errno(errno, "Failed to fork: %m"); + continue; + } else if (pid == 0) { + char *_argv[2]; + + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + if (!argv) { + _argv[0] = path; + _argv[1] = NULL; + argv = _argv; + } else + argv[0] = path; + + execv(path, argv); + return log_error_errno(errno, "Failed to execute %s: %m", path); + } + + log_debug("Spawned %s as " PID_FMT ".", path, pid); + + r = hashmap_put(pids, PID_TO_PTR(pid), path); + if (r < 0) + return log_oom(); + path = NULL; + } + } + + /* Abort execution of this process after the timout. We simply + * rely on SIGALRM as default action terminating the process, + * and turn on alarm(). */ + + if (timeout != USEC_INFINITY) + alarm((timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); + + while (!hashmap_isempty(pids)) { + _cleanup_free_ char *path = NULL; + pid_t pid; + + pid = PTR_TO_PID(hashmap_first_key(pids)); + assert(pid > 0); + + path = hashmap_remove(pids, PID_TO_PTR(pid)); + assert(path); + + wait_for_terminate_and_warn(path, pid, true); + } + + return 0; +} + +void execute_directories(const char* const* directories, usec_t timeout, char *argv[]) { + pid_t executor_pid; + int r; + char *name; + char **dirs = (char**) directories; + + assert(!strv_isempty(dirs)); + + name = basename(dirs[0]); + assert(!isempty(name)); + + /* Executes all binaries in the directories in parallel and waits + * for them to finish. Optionally a timeout is applied. If a file + * with the same name exists in more than one directory, the + * earliest one wins. */ + + executor_pid = fork(); + if (executor_pid < 0) { + log_error_errno(errno, "Failed to fork: %m"); + return; + + } else if (executor_pid == 0) { + r = do_execute(dirs, timeout, argv); + _exit(r < 0 ? EXIT_FAILURE : EXIT_SUCCESS); + } + + wait_for_terminate_and_warn(name, executor_pid, true); +} + +bool plymouth_running(void) { + return access("/run/plymouth/pid", F_OK) >= 0; +} + +bool display_is_local(const char *display) { + assert(display); + + return + display[0] == ':' && + display[1] >= '0' && + display[1] <= '9'; +} + +int socket_from_display(const char *display, char **path) { + size_t k; + char *f, *c; + + assert(display); + assert(path); + + if (!display_is_local(display)) + return -EINVAL; + + k = strspn(display+1, "0123456789"); + + f = new(char, strlen("/tmp/.X11-unix/X") + k + 1); + if (!f) + return -ENOMEM; + + c = stpcpy(f, "/tmp/.X11-unix/X"); + memcpy(c, display+1, k); + c[k] = 0; + + *path = f; + + return 0; +} + +int block_get_whole_disk(dev_t d, dev_t *ret) { + char *p, *s; + int r; + unsigned n, m; + + assert(ret); + + /* If it has a queue this is good enough for us */ + if (asprintf(&p, "/sys/dev/block/%u:%u/queue", major(d), minor(d)) < 0) + return -ENOMEM; + + r = access(p, F_OK); + free(p); + + if (r >= 0) { + *ret = d; + return 0; + } + + /* If it is a partition find the originating device */ + if (asprintf(&p, "/sys/dev/block/%u:%u/partition", major(d), minor(d)) < 0) + return -ENOMEM; + + r = access(p, F_OK); + free(p); + + if (r < 0) + return -ENOENT; + + /* Get parent dev_t */ + if (asprintf(&p, "/sys/dev/block/%u:%u/../dev", major(d), minor(d)) < 0) + return -ENOMEM; + + r = read_one_line_file(p, &s); + free(p); + + if (r < 0) + return r; + + r = sscanf(s, "%u:%u", &m, &n); + free(s); + + if (r != 2) + return -EINVAL; + + /* Only return this if it is really good enough for us. */ + if (asprintf(&p, "/sys/dev/block/%u:%u/queue", m, n) < 0) + return -ENOMEM; + + r = access(p, F_OK); + free(p); + + if (r >= 0) { + *ret = makedev(m, n); + return 0; + } + + return -ENOENT; +} + +bool kexec_loaded(void) { + bool loaded = false; + char *s; + + if (read_one_line_file("/sys/kernel/kexec_loaded", &s) >= 0) { + if (s[0] == '1') + loaded = true; + free(s); + } + return loaded; +} + +int prot_from_flags(int flags) { + + switch (flags & O_ACCMODE) { + + case O_RDONLY: + return PROT_READ; + + case O_WRONLY: + return PROT_WRITE; + + case O_RDWR: + return PROT_READ|PROT_WRITE; + + default: + return -EINVAL; + } +} + +int fork_agent(pid_t *pid, const int except[], unsigned n_except, const char *path, ...) { + bool stdout_is_tty, stderr_is_tty; + pid_t parent_pid, agent_pid; + sigset_t ss, saved_ss; + unsigned n, i; + va_list ap; + char **l; + + assert(pid); + assert(path); + + /* Spawns a temporary TTY agent, making sure it goes away when + * we go away */ + + parent_pid = getpid(); + + /* First we temporarily block all signals, so that the new + * child has them blocked initially. This way, we can be sure + * that SIGTERMs are not lost we might send to the agent. */ + assert_se(sigfillset(&ss) >= 0); + assert_se(sigprocmask(SIG_SETMASK, &ss, &saved_ss) >= 0); + + agent_pid = fork(); + if (agent_pid < 0) { + assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); + return -errno; + } + + if (agent_pid != 0) { + assert_se(sigprocmask(SIG_SETMASK, &saved_ss, NULL) >= 0); + *pid = agent_pid; + return 0; + } + + /* In the child: + * + * Make sure the agent goes away when the parent dies */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); + + /* Make sure we actually can kill the agent, if we need to, in + * case somebody invoked us from a shell script that trapped + * SIGTERM or so... */ + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + /* Check whether our parent died before we were able + * to set the death signal and unblock the signals */ + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); + + /* Don't leak fds to the agent */ + close_all_fds(except, n_except); + + stdout_is_tty = isatty(STDOUT_FILENO); + stderr_is_tty = isatty(STDERR_FILENO); + + if (!stdout_is_tty || !stderr_is_tty) { + int fd; + + /* Detach from stdout/stderr. and reopen + * /dev/tty for them. This is important to + * ensure that when systemctl is started via + * popen() or a similar call that expects to + * read EOF we actually do generate EOF and + * not delay this indefinitely by because we + * keep an unused copy of stdin around. */ + fd = open("/dev/tty", O_WRONLY); + if (fd < 0) { + log_error_errno(errno, "Failed to open /dev/tty: %m"); + _exit(EXIT_FAILURE); + } + + if (!stdout_is_tty) + dup2(fd, STDOUT_FILENO); + + if (!stderr_is_tty) + dup2(fd, STDERR_FILENO); + + if (fd > 2) + close(fd); + } + + /* Count arguments */ + va_start(ap, path); + for (n = 0; va_arg(ap, char*); n++) + ; + va_end(ap); + + /* Allocate strv */ + l = alloca(sizeof(char *) * (n + 1)); + + /* Fill in arguments */ + va_start(ap, path); + for (i = 0; i <= n; i++) + l[i] = va_arg(ap, char*); + va_end(ap); + + execv(path, l); + _exit(EXIT_FAILURE); +} + +bool in_initrd(void) { + static int saved = -1; + struct statfs s; + + if (saved >= 0) + return saved; + + /* We make two checks here: + * + * 1. the flag file /etc/initrd-release must exist + * 2. the root file system must be a memory file system + * + * The second check is extra paranoia, since misdetecting an + * initrd can have bad bad consequences due the initrd + * emptying when transititioning to the main systemd. + */ + + saved = access("/etc/initrd-release", F_OK) >= 0 && + statfs("/", &s) >= 0 && + is_temporary_fs(&s); + + return saved; +} + +/* hey glibc, APIs with callbacks without a user pointer are so useless */ +void *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, + int (*compar) (const void *, const void *, void *), void *arg) { + size_t l, u, idx; + const void *p; + int comparison; + + l = 0; + u = nmemb; + while (l < u) { + idx = (l + u) / 2; + p = (void *)(((const char *) base) + (idx * size)); + comparison = compar(key, p, arg); + if (comparison < 0) + u = idx; + else if (comparison > 0) + l = idx + 1; + else + return (void *)p; + } + return NULL; +} + +int on_ac_power(void) { + bool found_offline = false, found_online = false; + _cleanup_closedir_ DIR *d = NULL; + + d = opendir("/sys/class/power_supply"); + if (!d) + return errno == ENOENT ? true : -errno; + + for (;;) { + struct dirent *de; + _cleanup_close_ int fd = -1, device = -1; + char contents[6]; + ssize_t n; + + errno = 0; + de = readdir(d); + if (!de && errno > 0) + return -errno; + + if (!de) + break; + + if (hidden_file(de->d_name)) + continue; + + device = openat(dirfd(d), de->d_name, O_DIRECTORY|O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (device < 0) { + if (errno == ENOENT || errno == ENOTDIR) + continue; + + return -errno; + } + + fd = openat(device, "type", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (errno == ENOENT) + continue; + + return -errno; + } + + n = read(fd, contents, sizeof(contents)); + if (n < 0) + return -errno; + + if (n != 6 || memcmp(contents, "Mains\n", 6)) + continue; + + safe_close(fd); + fd = openat(device, "online", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (errno == ENOENT) + continue; + + return -errno; + } + + n = read(fd, contents, sizeof(contents)); + if (n < 0) + return -errno; + + if (n != 2 || contents[1] != '\n') + return -EIO; + + if (contents[0] == '1') { + found_online = true; + break; + } else if (contents[0] == '0') + found_offline = true; + else + return -EIO; + } + + 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; + pid_t leader; + int r; + + assert(machine); + assert(pid); + + if (!machine_name_is_valid(machine)) + return -EINVAL; + + p = strjoina("/run/systemd/machines/", machine); + r = parse_env_file(p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL); + if (r == -ENOENT) + return -EHOSTDOWN; + if (r < 0) + return r; + if (!s) + return -EIO; + + if (!streq_ptr(class, "container")) + return -EIO; + + r = parse_pid(s, &leader); + if (r < 0) + return r; + if (leader <= 1) + return -EIO; + + *pid = leader; + return 0; +} + +int namespace_open(pid_t pid, int *pidns_fd, int *mntns_fd, int *netns_fd, int *userns_fd, int *root_fd) { + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, netnsfd = -1, usernsfd = -1; + int rfd = -1; + + assert(pid >= 0); + + if (mntns_fd) { + const char *mntns; + + mntns = procfs_file_alloca(pid, "ns/mnt"); + mntnsfd = open(mntns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (mntnsfd < 0) + return -errno; + } + + if (pidns_fd) { + const char *pidns; + + pidns = procfs_file_alloca(pid, "ns/pid"); + pidnsfd = open(pidns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (pidnsfd < 0) + return -errno; + } + + if (netns_fd) { + const char *netns; + + netns = procfs_file_alloca(pid, "ns/net"); + netnsfd = open(netns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (netnsfd < 0) + return -errno; + } + + if (userns_fd) { + const char *userns; + + userns = procfs_file_alloca(pid, "ns/user"); + usernsfd = open(userns, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (usernsfd < 0 && errno != ENOENT) + return -errno; + } + + if (root_fd) { + const char *root; + + root = procfs_file_alloca(pid, "root"); + rfd = open(root, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (rfd < 0) + return -errno; + } + + if (pidns_fd) + *pidns_fd = pidnsfd; + + if (mntns_fd) + *mntns_fd = mntnsfd; + + if (netns_fd) + *netns_fd = netnsfd; + + if (userns_fd) + *userns_fd = usernsfd; + + if (root_fd) + *root_fd = rfd; + + pidnsfd = mntnsfd = netnsfd = usernsfd = -1; + + return 0; +} + +int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd) { + if (userns_fd >= 0) { + /* Can't setns to your own userns, since then you could + * escalate from non-root to root in your own namespace, so + * check if namespaces equal before attempting to enter. */ + _cleanup_free_ char *userns_fd_path = NULL; + int r; + if (asprintf(&userns_fd_path, "/proc/self/fd/%d", userns_fd) < 0) + return -ENOMEM; + + r = files_same(userns_fd_path, "/proc/self/ns/user"); + if (r < 0) + return r; + if (r) + userns_fd = -1; + } + + if (pidns_fd >= 0) + if (setns(pidns_fd, CLONE_NEWPID) < 0) + return -errno; + + if (mntns_fd >= 0) + if (setns(mntns_fd, CLONE_NEWNS) < 0) + return -errno; + + if (netns_fd >= 0) + if (setns(netns_fd, CLONE_NEWNET) < 0) + return -errno; + + if (userns_fd >= 0) + if (setns(userns_fd, CLONE_NEWUSER) < 0) + return -errno; + + if (root_fd >= 0) { + if (fchdir(root_fd) < 0) + return -errno; + + if (chroot(".") < 0) + return -errno; + } + + return reset_uid_gid(); +} + +uint64_t physical_memory(void) { + long mem; + + /* We return this as uint64_t in case we are running as 32bit + * process on a 64bit kernel with huge amounts of memory */ + + mem = sysconf(_SC_PHYS_PAGES); + assert(mem > 0); + + return (uint64_t) mem * (uint64_t) page_size(); +} + +int update_reboot_param_file(const char *param) { + int r = 0; + + if (param) { + r = write_string_file(REBOOT_PARAM_FILE, param, WRITE_STRING_FILE_CREATE); + if (r < 0) + return log_error_errno(r, "Failed to write reboot param to "REBOOT_PARAM_FILE": %m"); + } else + (void) unlink(REBOOT_PARAM_FILE); + + return 0; +} + +int version(void) { + puts(PACKAGE_STRING "\n" + SYSTEMD_FEATURES); + return 0; +} diff --git a/src/libbasic/util.h b/src/libbasic/util.h new file mode 100644 index 0000000000..6f42c85a33 --- /dev/null +++ b/src/libbasic/util.h @@ -0,0 +1,178 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "formats-util.h" +#include "macro.h" +#include "missing.h" +#include "time-util.h" + +size_t page_size(void) _pure_; +#define PAGE_ALIGN(l) ALIGN_TO((l), page_size()) + +static inline const char* yes_no(bool b) { + return b ? "yes" : "no"; +} + +static inline const char* true_false(bool b) { + return b ? "true" : "false"; +} + +static inline const char* one_zero(bool b) { + return b ? "1" : "0"; +} + +void execute_directories(const char* const* directories, usec_t timeout, char *argv[]); + +bool plymouth_running(void); + +bool display_is_local(const char *display) _pure_; +int socket_from_display(const char *display, char **path); + +int block_get_whole_disk(dev_t d, dev_t *ret); + +#define NULSTR_FOREACH(i, l) \ + for ((i) = (l); (i) && *(i); (i) = strchr((i), 0)+1) + +#define NULSTR_FOREACH_PAIR(i, j, l) \ + for ((i) = (l), (j) = strchr((i), 0)+1; (i) && *(i); (i) = strchr((j), 0)+1, (j) = *(i) ? strchr((i), 0)+1 : (i)) + +extern int saved_argc; +extern char **saved_argv; + +bool kexec_loaded(void); + +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 *xbsearch_r(const void *key, const void *base, size_t nmemb, size_t size, + int (*compar) (const void *, const void *, void *), + void *arg); + +/** + * Normal qsort requires base to be nonnull. Here were require + * that only if nmemb > 0. + */ +static inline void qsort_safe(void *base, size_t nmemb, size_t size, comparison_fn_t compar) { + if (nmemb <= 1) + return; + + assert(base); + qsort(base, nmemb, size, compar); +} + +int on_ac_power(void); + +#define memzero(x,l) (memset((x), 0, (l))) +#define zero(x) (memzero(&(x), sizeof(x))) + +static inline void *mempset(void *s, int c, size_t n) { + memset(s, c, n); + return (uint8_t*)s + n; +} + +static inline void _reset_errno_(int *saved_errno) { + errno = *saved_errno; +} + +#define PROTECT_ERRNO _cleanup_(_reset_errno_) __attribute__((unused)) int _saved_errno_ = errno + +static inline int negative_errno(void) { + /* This helper should be used to shut up gcc if you know 'errno' is + * negative. Instead of "return -errno;", use "return negative_errno();" + * It will suppress bogus gcc warnings in case it assumes 'errno' might + * be 0 and thus the caller's error-handling might not be triggered. */ + assert_return(errno > 0, -EINVAL); + return -errno; +} + +static inline unsigned u64log2(uint64_t n) { +#if __SIZEOF_LONG_LONG__ == 8 + return (n > 1) ? (unsigned) __builtin_clzll(n) ^ 63U : 0; +#else +#error "Wut?" +#endif +} + +static inline unsigned u32ctz(uint32_t n) { +#if __SIZEOF_INT__ == 4 + return __builtin_ctz(n); +#else +#error "Wut?" +#endif +} + +static inline unsigned log2i(int x) { + assert(x > 0); + + return __SIZEOF_INT__ * 8 - __builtin_clz(x) - 1; +} + +static inline unsigned log2u(unsigned x) { + assert(x > 0); + + return sizeof(unsigned) * 8 - __builtin_clz(x) - 1; +} + +static inline unsigned log2u_round_up(unsigned x) { + assert(x > 0); + + if (x == 1) + return 0; + + 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); + +int update_reboot_param_file(const char *param); + +int version(void); diff --git a/src/libbasic/verbs.c b/src/libbasic/verbs.c new file mode 100644 index 0000000000..d9cdb38d65 --- /dev/null +++ b/src/libbasic/verbs.c @@ -0,0 +1,101 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include + +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "verbs.h" +#include "virt.h" + +int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata) { + const Verb *verb; + const char *name; + unsigned i; + int left; + + assert(verbs); + assert(verbs[0].dispatch); + assert(argc >= 0); + assert(argv); + assert(argc >= optind); + + left = argc - optind; + name = argv[optind]; + + for (i = 0;; i++) { + bool found; + + /* At the end of the list? */ + if (!verbs[i].dispatch) { + if (name) + log_error("Unknown operation %s.", name); + else + log_error("Requires operation parameter."); + return -EINVAL; + } + + if (name) + found = streq(name, verbs[i].verb); + else + found = !!(verbs[i].flags & VERB_DEFAULT); + + if (found) { + verb = &verbs[i]; + break; + } + } + + assert(verb); + + if (!name) + left = 1; + + if (verb->min_args != VERB_ANY && + (unsigned) left < verb->min_args) { + log_error("Too few arguments."); + return -EINVAL; + } + + if (verb->max_args != VERB_ANY && + (unsigned) left > verb->max_args) { + log_error("Too many arguments."); + return -EINVAL; + } + + if ((verb->flags & VERB_NOCHROOT) && running_in_chroot() > 0) { + log_info("Running in chroot, ignoring request."); + return 0; + } + + if (name) + return verb->dispatch(left, argv + optind, userdata); + else { + char* fake[2] = { + (char*) verb->verb, + NULL + }; + + return verb->dispatch(1, fake, userdata); + } +} diff --git a/src/libbasic/verbs.h b/src/libbasic/verbs.h new file mode 100644 index 0000000000..7b5e18510f --- /dev/null +++ b/src/libbasic/verbs.h @@ -0,0 +1,33 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#define VERB_ANY ((unsigned) -1) +#define VERB_DEFAULT 1U +#define VERB_NOCHROOT 2U + +typedef struct { + const char *verb; + unsigned min_args, max_args; + unsigned flags; + int (* const dispatch)(int argc, char *argv[], void *userdata); +} Verb; + +int dispatch_verb(int argc, char *argv[], const Verb verbs[], void *userdata); diff --git a/src/libbasic/virt.c b/src/libbasic/virt.c new file mode 100644 index 0000000000..19b6318e3d --- /dev/null +++ b/src/libbasic/virt.c @@ -0,0 +1,462 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "macro.h" +#include "process-util.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "virt.h" + +static int detect_vm_cpuid(void) { + + /* CPUID is an x86 specific interface. */ +#if defined(__i386__) || defined(__x86_64__) + + static const struct { + const char *cpuid; + int id; + } cpuid_vendor_table[] = { + { "XenVMMXenVMM", VIRTUALIZATION_XEN }, + { "KVMKVMKVM", VIRTUALIZATION_KVM }, + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + { "VMwareVMware", VIRTUALIZATION_VMWARE }, + /* http://msdn.microsoft.com/en-us/library/ff542428.aspx */ + { "Microsoft Hv", VIRTUALIZATION_MICROSOFT }, + }; + + uint32_t eax, ecx; + bool hypervisor; + + /* http://lwn.net/Articles/301888/ */ + +#if defined (__i386__) +#define REG_a "eax" +#define REG_b "ebx" +#elif defined (__amd64__) +#define REG_a "rax" +#define REG_b "rbx" +#endif + + /* First detect whether there is a hypervisor */ + eax = 1; + __asm__ __volatile__ ( + /* ebx/rbx is being used for PIC! */ + " push %%"REG_b" \n\t" + " cpuid \n\t" + " pop %%"REG_b" \n\t" + + : "=a" (eax), "=c" (ecx) + : "0" (eax) + ); + + hypervisor = !!(ecx & 0x80000000U); + + if (hypervisor) { + union { + uint32_t sig32[3]; + char text[13]; + } sig = {}; + unsigned j; + + /* There is a hypervisor, see what it is */ + eax = 0x40000000U; + __asm__ __volatile__ ( + /* ebx/rbx is being used for PIC! */ + " push %%"REG_b" \n\t" + " cpuid \n\t" + " mov %%ebx, %1 \n\t" + " pop %%"REG_b" \n\t" + + : "=a" (eax), "=r" (sig.sig32[0]), "=c" (sig.sig32[1]), "=d" (sig.sig32[2]) + : "0" (eax) + ); + + for (j = 0; j < ELEMENTSOF(cpuid_vendor_table); j ++) + if (streq(sig.text, cpuid_vendor_table[j].cpuid)) + return cpuid_vendor_table[j].id; + + return VIRTUALIZATION_VM_OTHER; + } +#endif + + return VIRTUALIZATION_NONE; +} + +static int detect_vm_device_tree(void) { +#if defined(__arm__) || defined(__aarch64__) || defined(__powerpc__) || defined(__powerpc64__) + _cleanup_free_ char *hvtype = NULL; + int r; + + r = read_one_line_file("/proc/device-tree/hypervisor/compatible", &hvtype); + if (r == -ENOENT) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *dent; + + dir = opendir("/proc/device-tree"); + if (!dir) { + if (errno == ENOENT) + return VIRTUALIZATION_NONE; + return -errno; + } + + FOREACH_DIRENT(dent, dir, return -errno) + if (strstr(dent->d_name, "fw-cfg")) + return VIRTUALIZATION_QEMU; + + return VIRTUALIZATION_NONE; + } else if (r < 0) + return r; + + if (streq(hvtype, "linux,kvm")) + return VIRTUALIZATION_KVM; + else if (strstr(hvtype, "xen")) + return VIRTUALIZATION_XEN; + else + return VIRTUALIZATION_VM_OTHER; +#else + return VIRTUALIZATION_NONE; +#endif +} + +static int detect_vm_dmi(void) { +#if defined(__i386__) || defined(__x86_64__) || defined(__arm__) || defined(__aarch64__) + + static const char *const dmi_vendors[] = { + "/sys/class/dmi/id/product_name", /* Test this before sys_vendor to detect KVM over QEMU */ + "/sys/class/dmi/id/sys_vendor", + "/sys/class/dmi/id/board_vendor", + "/sys/class/dmi/id/bios_vendor" + }; + + static const struct { + const char *vendor; + int id; + } dmi_vendor_table[] = { + { "KVM", VIRTUALIZATION_KVM }, + { "QEMU", VIRTUALIZATION_QEMU }, + /* http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1009458 */ + { "VMware", VIRTUALIZATION_VMWARE }, + { "VMW", VIRTUALIZATION_VMWARE }, + { "innotek GmbH", VIRTUALIZATION_ORACLE }, + { "Xen", VIRTUALIZATION_XEN }, + { "Bochs", VIRTUALIZATION_BOCHS }, + { "Parallels", VIRTUALIZATION_PARALLELS }, + }; + unsigned i; + int r; + + for (i = 0; i < ELEMENTSOF(dmi_vendors); i++) { + _cleanup_free_ char *s = NULL; + unsigned j; + + r = read_one_line_file(dmi_vendors[i], &s); + if (r < 0) { + if (r == -ENOENT) + continue; + + return r; + } + + for (j = 0; j < ELEMENTSOF(dmi_vendor_table); j++) + if (startswith(s, dmi_vendor_table[j].vendor)) + return dmi_vendor_table[j].id; + } +#endif + + return VIRTUALIZATION_NONE; +} + +static int detect_vm_xen(void) { + _cleanup_free_ char *domcap = NULL; + char *cap, *i; + int r; + + r = read_one_line_file("/proc/xen/capabilities", &domcap); + if (r == -ENOENT) + return VIRTUALIZATION_NONE; + + i = domcap; + while ((cap = strsep(&i, ","))) + if (streq(cap, "control_d")) + break; + + return cap ? VIRTUALIZATION_NONE : VIRTUALIZATION_XEN; +} + +static int detect_vm_hypervisor(void) { + _cleanup_free_ char *hvtype = NULL; + int r; + + r = read_one_line_file("/sys/hypervisor/type", &hvtype); + if (r == -ENOENT) + return VIRTUALIZATION_NONE; + if (r < 0) + return r; + + if (streq(hvtype, "xen")) + return VIRTUALIZATION_XEN; + else + return VIRTUALIZATION_VM_OTHER; +} + +static int detect_vm_uml(void) { + _cleanup_free_ char *cpuinfo_contents = NULL; + int r; + + /* Detect User-Mode Linux by reading /proc/cpuinfo */ + r = read_full_file("/proc/cpuinfo", &cpuinfo_contents, NULL); + if (r < 0) + return r; + if (strstr(cpuinfo_contents, "\nvendor_id\t: User Mode Linux\n")) + return VIRTUALIZATION_UML; + + return VIRTUALIZATION_NONE; +} + +static int detect_vm_zvm(void) { + +#if defined(__s390__) + _cleanup_free_ char *t = NULL; + int r; + + r = get_proc_field("/proc/sysinfo", "VM00 Control Program", WHITESPACE, &t); + if (r == -ENOENT) + return VIRTUALIZATION_NONE; + if (r < 0) + return r; + + if (streq(t, "z/VM")) + return VIRTUALIZATION_ZVM; + else + return VIRTUALIZATION_KVM; +#else + return VIRTUALIZATION_NONE; +#endif +} + +/* Returns a short identifier for the various VM implementations */ +int detect_vm(void) { + static thread_local int cached_found = _VIRTUALIZATION_INVALID; + int r; + + if (cached_found >= 0) + return cached_found; + + /* We have to use the correct order here: + * Some virtualization technologies do use KVM hypervisor but are + * expected to be detected as something else. So detect DMI first. + * + * An example is Virtualbox since version 5.0, which uses KVM backend. + * Detection via DMI works corretly, the CPU ID would find KVM + * only. */ + r = detect_vm_dmi(); + if (r < 0) + return r; + if (r != VIRTUALIZATION_NONE) + goto finish; + + r = detect_vm_cpuid(); + if (r < 0) + return r; + if (r != VIRTUALIZATION_NONE) + goto finish; + + /* x86 xen will most likely be detected by cpuid. If not (most likely + * because we're not an x86 guest), then we should try the xen capabilities + * file next. If that's not found, then we check for the high-level + * hypervisor sysfs file: + * + * https://bugs.freedesktop.org/show_bug.cgi?id=77271 */ + + r = detect_vm_xen(); + if (r < 0) + return r; + if (r != VIRTUALIZATION_NONE) + goto finish; + + r = detect_vm_hypervisor(); + if (r < 0) + return r; + if (r != VIRTUALIZATION_NONE) + goto finish; + + r = detect_vm_device_tree(); + if (r < 0) + return r; + if (r != VIRTUALIZATION_NONE) + goto finish; + + r = detect_vm_uml(); + if (r < 0) + return r; + if (r != VIRTUALIZATION_NONE) + goto finish; + + r = detect_vm_zvm(); + if (r < 0) + return r; + +finish: + cached_found = r; + return r; +} + +int detect_container(void) { + + static const struct { + const char *value; + int id; + } value_table[] = { + { "lxc", VIRTUALIZATION_LXC }, + { "lxc-libvirt", VIRTUALIZATION_LXC_LIBVIRT }, + { "systemd-nspawn", VIRTUALIZATION_SYSTEMD_NSPAWN }, + { "docker", VIRTUALIZATION_DOCKER }, + { "rkt", VIRTUALIZATION_RKT }, + }; + + static thread_local int cached_found = _VIRTUALIZATION_INVALID; + _cleanup_free_ char *m = NULL; + const char *e = NULL; + unsigned j; + int r; + + if (cached_found >= 0) + return cached_found; + + /* /proc/vz exists in container and outside of the container, + * /proc/bc only outside of the container. */ + if (access("/proc/vz", F_OK) >= 0 && + access("/proc/bc", F_OK) < 0) { + r = VIRTUALIZATION_OPENVZ; + goto finish; + } + + if (getpid() == 1) { + /* If we are PID 1 we can just check our own + * environment variable */ + + e = getenv("container"); + if (isempty(e)) { + r = VIRTUALIZATION_NONE; + goto finish; + } + } else { + + /* Otherwise, PID 1 dropped this information into a + * file in /run. This is better than accessing + * /proc/1/environ, since we don't need CAP_SYS_PTRACE + * for that. */ + + r = read_one_line_file("/run/systemd/container", &m); + if (r == -ENOENT) { + + /* Fallback for cases where PID 1 was not + * systemd (for example, cases where + * init=/bin/sh is used. */ + + r = getenv_for_pid(1, "container", &m); + if (r <= 0) { + + /* If that didn't work, give up, + * assume no container manager. + * + * Note: This means we still cannot + * detect containers if init=/bin/sh + * is passed but privileges dropped, + * as /proc/1/environ is only readable + * with privileges. */ + + r = VIRTUALIZATION_NONE; + goto finish; + } + } + if (r < 0) + return r; + + e = m; + } + + for (j = 0; j < ELEMENTSOF(value_table); j++) + if (streq(e, value_table[j].value)) { + r = value_table[j].id; + goto finish; + } + + r = VIRTUALIZATION_CONTAINER_OTHER; + +finish: + cached_found = r; + return r; +} + +int detect_virtualization(void) { + int r; + + r = detect_container(); + if (r != 0) + return r; + + return detect_vm(); +} + +int running_in_chroot(void) { + int ret; + + ret = files_same("/proc/1/root", "/"); + if (ret < 0) + return ret; + + return ret == 0; +} + +static const char *const virtualization_table[_VIRTUALIZATION_MAX] = { + [VIRTUALIZATION_NONE] = "none", + [VIRTUALIZATION_KVM] = "kvm", + [VIRTUALIZATION_QEMU] = "qemu", + [VIRTUALIZATION_BOCHS] = "bochs", + [VIRTUALIZATION_XEN] = "xen", + [VIRTUALIZATION_UML] = "uml", + [VIRTUALIZATION_VMWARE] = "vmware", + [VIRTUALIZATION_ORACLE] = "oracle", + [VIRTUALIZATION_MICROSOFT] = "microsoft", + [VIRTUALIZATION_ZVM] = "zvm", + [VIRTUALIZATION_PARALLELS] = "parallels", + [VIRTUALIZATION_VM_OTHER] = "vm-other", + + [VIRTUALIZATION_SYSTEMD_NSPAWN] = "systemd-nspawn", + [VIRTUALIZATION_LXC_LIBVIRT] = "lxc-libvirt", + [VIRTUALIZATION_LXC] = "lxc", + [VIRTUALIZATION_OPENVZ] = "openvz", + [VIRTUALIZATION_DOCKER] = "docker", + [VIRTUALIZATION_RKT] = "rkt", + [VIRTUALIZATION_CONTAINER_OTHER] = "container-other", +}; + +DEFINE_STRING_TABLE_LOOKUP(virtualization, int); diff --git a/src/libbasic/virt.h b/src/libbasic/virt.h new file mode 100644 index 0000000000..a538f07f6b --- /dev/null +++ b/src/libbasic/virt.h @@ -0,0 +1,72 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include + +#include "macro.h" + +enum { + VIRTUALIZATION_NONE = 0, + + VIRTUALIZATION_VM_FIRST, + VIRTUALIZATION_KVM = VIRTUALIZATION_VM_FIRST, + VIRTUALIZATION_QEMU, + VIRTUALIZATION_BOCHS, + VIRTUALIZATION_XEN, + VIRTUALIZATION_UML, + VIRTUALIZATION_VMWARE, + VIRTUALIZATION_ORACLE, + VIRTUALIZATION_MICROSOFT, + VIRTUALIZATION_ZVM, + VIRTUALIZATION_PARALLELS, + VIRTUALIZATION_VM_OTHER, + VIRTUALIZATION_VM_LAST = VIRTUALIZATION_VM_OTHER, + + VIRTUALIZATION_CONTAINER_FIRST, + VIRTUALIZATION_SYSTEMD_NSPAWN = VIRTUALIZATION_CONTAINER_FIRST, + VIRTUALIZATION_LXC_LIBVIRT, + VIRTUALIZATION_LXC, + VIRTUALIZATION_OPENVZ, + VIRTUALIZATION_DOCKER, + VIRTUALIZATION_RKT, + VIRTUALIZATION_CONTAINER_OTHER, + VIRTUALIZATION_CONTAINER_LAST = VIRTUALIZATION_CONTAINER_OTHER, + + _VIRTUALIZATION_MAX, + _VIRTUALIZATION_INVALID = -1 +}; + +static inline bool VIRTUALIZATION_IS_VM(int x) { + return x >= VIRTUALIZATION_VM_FIRST && x <= VIRTUALIZATION_VM_LAST; +} + +static inline bool VIRTUALIZATION_IS_CONTAINER(int x) { + return x >= VIRTUALIZATION_CONTAINER_FIRST && x <= VIRTUALIZATION_CONTAINER_LAST; +} + +int detect_vm(void); +int detect_container(void); +int detect_virtualization(void); + +int running_in_chroot(void); + +const char *virtualization_to_string(int v) _const_; +int virtualization_from_string(const char *s) _pure_; diff --git a/src/libbasic/web-util.c b/src/libbasic/web-util.c new file mode 100644 index 0000000000..595688ed93 --- /dev/null +++ b/src/libbasic/web-util.c @@ -0,0 +1,76 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "string-util.h" +#include "utf8.h" +#include "web-util.h" + +bool http_etag_is_valid(const char *etag) { + if (isempty(etag)) + return false; + + if (!endswith(etag, "\"")) + return false; + + if (!startswith(etag, "\"") && !startswith(etag, "W/\"")) + return false; + + return true; +} + +bool http_url_is_valid(const char *url) { + const char *p; + + if (isempty(url)) + return false; + + p = startswith(url, "http://"); + if (!p) + p = startswith(url, "https://"); + if (!p) + return false; + + if (isempty(p)) + return false; + + return ascii_is_valid(p); +} + +bool documentation_url_is_valid(const char *url) { + const char *p; + + if (isempty(url)) + return false; + + if (http_url_is_valid(url)) + return true; + + p = startswith(url, "file:/"); + if (!p) + p = startswith(url, "info:"); + if (!p) + p = startswith(url, "man:"); + + if (isempty(p)) + return false; + + return ascii_is_valid(p); +} diff --git a/src/libbasic/web-util.h b/src/libbasic/web-util.h new file mode 100644 index 0000000000..e6bb6b53f5 --- /dev/null +++ b/src/libbasic/web-util.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "macro.h" + +bool http_url_is_valid(const char *url) _pure_; + +bool documentation_url_is_valid(const char *url) _pure_; + +bool http_etag_is_valid(const char *etag); diff --git a/src/libbasic/xattr-util.c b/src/libbasic/xattr-util.c new file mode 100644 index 0000000000..8d7f14f382 --- /dev/null +++ b/src/libbasic/xattr-util.c @@ -0,0 +1,200 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "macro.h" +#include "sparse-endian.h" +#include "stdio-util.h" +#include "time-util.h" +#include "xattr-util.h" + +int getxattr_malloc(const char *path, const char *name, char **value, bool allow_symlink) { + char *v; + size_t l; + ssize_t n; + + assert(path); + assert(name); + assert(value); + + for (l = 100; ; l = (size_t) n + 1) { + v = new0(char, l); + if (!v) + return -ENOMEM; + + if (allow_symlink) + n = lgetxattr(path, name, v, l); + else + n = getxattr(path, name, v, l); + + if (n >= 0 && (size_t) n < l) { + *value = v; + return n; + } + + free(v); + + if (n < 0 && errno != ERANGE) + return -errno; + + if (allow_symlink) + n = lgetxattr(path, name, NULL, 0); + else + n = getxattr(path, name, NULL, 0); + if (n < 0) + return -errno; + } +} + +int fgetxattr_malloc(int fd, const char *name, char **value) { + char *v; + size_t l; + ssize_t n; + + assert(fd >= 0); + assert(name); + assert(value); + + for (l = 100; ; l = (size_t) n + 1) { + v = new0(char, l); + if (!v) + return -ENOMEM; + + n = fgetxattr(fd, name, v, l); + + if (n >= 0 && (size_t) n < l) { + *value = v; + return n; + } + + free(v); + + if (n < 0 && errno != ERANGE) + return -errno; + + n = fgetxattr(fd, name, NULL, 0); + if (n < 0) + return -errno; + } +} + +ssize_t fgetxattrat_fake(int dirfd, const char *filename, const char *attribute, void *value, size_t size, int flags) { + char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1]; + _cleanup_close_ int fd = -1; + ssize_t l; + + /* The kernel doesn't have a fgetxattrat() command, hence let's emulate one */ + + fd = openat(dirfd, filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH|(flags & AT_SYMLINK_NOFOLLOW ? O_NOFOLLOW : 0)); + if (fd < 0) + return -errno; + + xsprintf(fn, "/proc/self/fd/%i", fd); + + l = getxattr(fn, attribute, value, size); + if (l < 0) + return -errno; + + return l; +} + +static int parse_crtime(le64_t le, usec_t *usec) { + uint64_t u; + + assert(usec); + + u = le64toh(le); + if (u == 0 || u == (uint64_t) -1) + return -EIO; + + *usec = (usec_t) u; + return 0; +} + +int fd_getcrtime(int fd, usec_t *usec) { + le64_t le; + ssize_t n; + + assert(fd >= 0); + assert(usec); + + /* Until Linux gets a real concept of birthtime/creation time, + * let's fake one with xattrs */ + + n = fgetxattr(fd, "user.crtime_usec", &le, sizeof(le)); + if (n < 0) + return -errno; + if (n != sizeof(le)) + return -EIO; + + return parse_crtime(le, usec); +} + +int fd_getcrtime_at(int dirfd, const char *name, usec_t *usec, int flags) { + le64_t le; + ssize_t n; + + n = fgetxattrat_fake(dirfd, name, "user.crtime_usec", &le, sizeof(le), flags); + if (n < 0) + return -errno; + if (n != sizeof(le)) + return -EIO; + + return parse_crtime(le, usec); +} + +int path_getcrtime(const char *p, usec_t *usec) { + le64_t le; + ssize_t n; + + assert(p); + assert(usec); + + n = getxattr(p, "user.crtime_usec", &le, sizeof(le)); + if (n < 0) + return -errno; + if (n != sizeof(le)) + return -EIO; + + return parse_crtime(le, usec); +} + +int fd_setcrtime(int fd, usec_t usec) { + le64_t le; + + assert(fd >= 0); + + if (usec <= 0) + usec = now(CLOCK_REALTIME); + + le = htole64((uint64_t) usec); + if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0) < 0) + return -errno; + + return 0; +} diff --git a/src/libbasic/xattr-util.h b/src/libbasic/xattr-util.h new file mode 100644 index 0000000000..6fa097bf7e --- /dev/null +++ b/src/libbasic/xattr-util.h @@ -0,0 +1,37 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#include "time-util.h" + +int getxattr_malloc(const char *path, const char *name, char **value, bool allow_symlink); +int fgetxattr_malloc(int fd, const char *name, char **value); + +ssize_t fgetxattrat_fake(int dirfd, const char *filename, const char *attribute, void *value, size_t size, int flags); + +int fd_setcrtime(int fd, usec_t usec); + +int fd_getcrtime(int fd, usec_t *usec); +int path_getcrtime(const char *p, usec_t *usec); +int fd_getcrtime_at(int dirfd, const char *name, usec_t *usec, int flags); diff --git a/src/libbasic/xml.c b/src/libbasic/xml.c new file mode 100644 index 0000000000..1dbeac7324 --- /dev/null +++ b/src/libbasic/xml.c @@ -0,0 +1,255 @@ +/*** + 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 . +***/ + +#include +#include +#include + +#include "macro.h" +#include "string-util.h" +#include "xml.h" + +enum { + STATE_NULL, + STATE_TEXT, + STATE_TAG, + STATE_ATTRIBUTE, +}; + +static void inc_lines(unsigned *line, const char *s, size_t n) { + const char *p = s; + + if (!line) + return; + + for (;;) { + const char *f; + + f = memchr(p, '\n', n); + if (!f) + return; + + n -= (f - p) + 1; + p = f + 1; + (*line)++; + } +} + +/* We don't actually do real XML here. We only read a simplistic + * subset, that is a bit less strict that XML and lacks all the more + * complex features, like entities, or namespaces. However, we do + * support some HTML5-like simplifications */ + +int xml_tokenize(const char **p, char **name, void **state, unsigned *line) { + const char *c, *e, *b; + char *ret; + int t; + + assert(p); + assert(*p); + assert(name); + assert(state); + + t = PTR_TO_INT(*state); + c = *p; + + if (t == STATE_NULL) { + if (line) + *line = 1; + t = STATE_TEXT; + } + + for (;;) { + if (*c == 0) + return XML_END; + + switch (t) { + + case STATE_TEXT: { + int x; + + e = strchrnul(c, '<'); + if (e > c) { + /* More text... */ + ret = strndup(c, e - c); + if (!ret) + return -ENOMEM; + + inc_lines(line, c, e - c); + + *name = ret; + *p = e; + *state = INT_TO_PTR(STATE_TEXT); + + return XML_TEXT; + } + + assert(*e == '<'); + b = c + 1; + + if (startswith(b, "!--")) { + /* A comment */ + e = strstr(b + 3, "-->"); + if (!e) + return -EINVAL; + + inc_lines(line, b, e + 3 - b); + + c = e + 3; + continue; + } + + if (*b == '?') { + /* Processing instruction */ + + e = strstr(b + 1, "?>"); + if (!e) + return -EINVAL; + + inc_lines(line, b, e + 2 - b); + + c = e + 2; + continue; + } + + if (*b == '!') { + /* DTD */ + + e = strchr(b + 1, '>'); + if (!e) + return -EINVAL; + + inc_lines(line, b, e + 1 - b); + + c = e + 1; + continue; + } + + if (*b == '/') { + /* A closing tag */ + x = XML_TAG_CLOSE; + b++; + } else + x = XML_TAG_OPEN; + + e = strpbrk(b, WHITESPACE "/>"); + if (!e) + return -EINVAL; + + ret = strndup(b, e - b); + if (!ret) + return -ENOMEM; + + *name = ret; + *p = e; + *state = INT_TO_PTR(STATE_TAG); + + return x; + } + + case STATE_TAG: + + b = c + strspn(c, WHITESPACE); + if (*b == 0) + return -EINVAL; + + inc_lines(line, c, b - c); + + e = b + strcspn(b, WHITESPACE "=/>"); + if (e > b) { + /* An attribute */ + + ret = strndup(b, e - b); + if (!ret) + return -ENOMEM; + + *name = ret; + *p = e; + *state = INT_TO_PTR(STATE_ATTRIBUTE); + + return XML_ATTRIBUTE_NAME; + } + + if (startswith(b, "/>")) { + /* An empty tag */ + + *name = NULL; /* For empty tags we return a NULL name, the caller must be prepared for that */ + *p = b + 2; + *state = INT_TO_PTR(STATE_TEXT); + + return XML_TAG_CLOSE_EMPTY; + } + + if (*b != '>') + return -EINVAL; + + c = b + 1; + t = STATE_TEXT; + continue; + + case STATE_ATTRIBUTE: + + if (*c == '=') { + c++; + + if (*c == '\'' || *c == '\"') { + /* Tag with a quoted value */ + + e = strchr(c+1, *c); + if (!e) + return -EINVAL; + + inc_lines(line, c, e - c); + + ret = strndup(c+1, e - c - 1); + if (!ret) + return -ENOMEM; + + *name = ret; + *p = e + 1; + *state = INT_TO_PTR(STATE_TAG); + + return XML_ATTRIBUTE_VALUE; + + } + + /* Tag with a value without quotes */ + + b = strpbrk(c, WHITESPACE ">"); + if (!b) + b = c; + + ret = strndup(c, b - c); + if (!ret) + return -ENOMEM; + + *name = ret; + *p = b; + *state = INT_TO_PTR(STATE_TAG); + return XML_ATTRIBUTE_VALUE; + } + + t = STATE_TAG; + continue; + } + + } + + assert_not_reached("Bad state"); +} diff --git a/src/libbasic/xml.h b/src/libbasic/xml.h new file mode 100644 index 0000000000..41cb69f0dc --- /dev/null +++ b/src/libbasic/xml.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + 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 . +***/ + +enum { + XML_END, + XML_TEXT, + XML_TAG_OPEN, + XML_TAG_CLOSE, + XML_TAG_CLOSE_EMPTY, + XML_ATTRIBUTE_NAME, + XML_ATTRIBUTE_VALUE, +}; + +int xml_tokenize(const char **p, char **name, void **state, unsigned *line); diff --git a/src/libcore/.gitignore b/src/libcore/.gitignore new file mode 100644 index 0000000000..465b4fcc20 --- /dev/null +++ b/src/libcore/.gitignore @@ -0,0 +1,3 @@ +/macros.systemd +/triggers.systemd +/systemd.pc diff --git a/src/libcore/Makefile b/src/libcore/Makefile new file mode 100644 index 0000000000..5eef67be3d --- /dev/null +++ b/src/libcore/Makefile @@ -0,0 +1,172 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +noinst_LTLIBRARIES += \ + libcore.la + +libcore_la_SOURCES = \ + src/core/unit.c \ + src/core/unit.h \ + src/core/unit-printf.c \ + src/core/unit-printf.h \ + src/core/job.c \ + src/core/job.h \ + src/core/manager.c \ + src/core/manager.h \ + src/core/transaction.c \ + src/core/transaction.h \ + src/core/load-fragment.c \ + src/core/load-fragment.h \ + src/core/service.c \ + src/core/service.h \ + src/core/socket.c \ + src/core/socket.h \ + src/core/busname.c \ + src/core/busname.h \ + src/core/bus-endpoint.c \ + src/core/bus-endpoint.h \ + src/core/bus-policy.c \ + src/core/bus-policy.h \ + src/core/target.c \ + src/core/target.h \ + src/core/device.c \ + src/core/device.h \ + src/core/mount.c \ + src/core/mount.h \ + src/core/automount.c \ + src/core/automount.h \ + src/core/swap.c \ + src/core/swap.h \ + src/core/timer.c \ + src/core/timer.h \ + src/core/path.c \ + src/core/path.h \ + src/core/slice.c \ + src/core/slice.h \ + src/core/scope.c \ + src/core/scope.h \ + src/core/load-dropin.c \ + src/core/load-dropin.h \ + src/core/execute.c \ + src/core/execute.h \ + src/core/kill.c \ + src/core/kill.h \ + src/core/dbus.c \ + src/core/dbus.h \ + src/core/dbus-manager.c \ + src/core/dbus-manager.h \ + src/core/dbus-unit.c \ + src/core/dbus-unit.h \ + src/core/dbus-job.c \ + src/core/dbus-job.h \ + src/core/dbus-service.c \ + src/core/dbus-service.h \ + src/core/dbus-socket.c \ + src/core/dbus-socket.h \ + src/core/dbus-busname.c \ + src/core/dbus-busname.h \ + src/core/dbus-target.c \ + src/core/dbus-target.h \ + src/core/dbus-device.c \ + src/core/dbus-device.h \ + src/core/dbus-mount.c \ + src/core/dbus-mount.h \ + src/core/dbus-automount.c \ + src/core/dbus-automount.h \ + src/core/dbus-swap.c \ + src/core/dbus-swap.h \ + src/core/dbus-timer.c \ + src/core/dbus-timer.h \ + src/core/dbus-path.c \ + src/core/dbus-path.h \ + src/core/dbus-slice.c \ + src/core/dbus-slice.h \ + src/core/dbus-scope.c \ + src/core/dbus-scope.h \ + src/core/dbus-execute.c \ + src/core/dbus-execute.h \ + src/core/dbus-kill.c \ + src/core/dbus-kill.h \ + src/core/dbus-cgroup.c \ + src/core/dbus-cgroup.h \ + src/core/cgroup.c \ + src/core/cgroup.h \ + src/core/selinux-access.c \ + src/core/selinux-access.h \ + src/core/selinux-setup.c \ + src/core/selinux-setup.h \ + src/core/smack-setup.c \ + src/core/smack-setup.h \ + src/core/ima-setup.c \ + src/core/ima-setup.h \ + src/core/locale-setup.h \ + src/core/locale-setup.c \ + src/core/hostname-setup.c \ + src/core/hostname-setup.h \ + src/core/machine-id-setup.c \ + src/core/machine-id-setup.h \ + src/core/mount-setup.c \ + src/core/mount-setup.h \ + src/core/kmod-setup.c \ + src/core/kmod-setup.h \ + src/core/loopback-setup.h \ + src/core/loopback-setup.c \ + src/core/namespace.c \ + src/core/namespace.h \ + src/core/killall.h \ + src/core/killall.c \ + src/core/audit-fd.c \ + src/core/audit-fd.h \ + src/core/show-status.c \ + src/core/show-status.h \ + src/core/failure-action.c \ + src/core/failure-action.h + +nodist_libcore_la_SOURCES = \ + src/core/load-fragment-gperf.c \ + src/core/load-fragment-gperf-nulstr.c + +libcore_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(PAM_CFLAGS) \ + $(AUDIT_CFLAGS) \ + $(KMOD_CFLAGS) \ + $(APPARMOR_CFLAGS) \ + $(MOUNT_CFLAGS) \ + $(SECCOMP_CFLAGS) + +libcore_la_LIBADD = \ + libshared.la \ + $(PAM_LIBS) \ + $(AUDIT_LIBS) \ + $(KMOD_LIBS) \ + $(APPARMOR_LIBS) \ + $(MOUNT_LIBS) + +$(outdir)/load-fragment-gperf-nulstr.c: src/core/load-fragment-gperf.gperf + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(AWK) 'BEGIN{ keywords=0 ; FS="," ; print "extern const char load_fragment_gperf_nulstr[];" ; print "const char load_fragment_gperf_nulstr[] ="} ; keyword==1 { print "\"" $$1 "\\0\"" } ; /%%/ { keyword=1} ; END { print ";" }' < $< > $@ + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libcore/audit-fd.c b/src/libcore/audit-fd.c new file mode 100644 index 0000000000..76afe3fe15 --- /dev/null +++ b/src/libcore/audit-fd.c @@ -0,0 +1,73 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + + +#include + +#include "audit-fd.h" + +#ifdef HAVE_AUDIT + +#include +#include + +#include "fd-util.h" +#include "log.h" +#include "util.h" + +static bool initialized = false; +static int audit_fd; + +int get_audit_fd(void) { + + if (!initialized) { + audit_fd = audit_open(); + + if (audit_fd < 0) { + if (errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT) + log_error_errno(errno, "Failed to connect to audit log: %m"); + + audit_fd = errno ? -errno : -EINVAL; + } + + initialized = true; + } + + return audit_fd; +} + +void close_audit_fd(void) { + + if (initialized && audit_fd >= 0) + safe_close(audit_fd); + + initialized = true; + audit_fd = -ECONNRESET; +} + +#else + +int get_audit_fd(void) { + return -EAFNOSUPPORT; +} + +void close_audit_fd(void) { +} + +#endif diff --git a/src/libcore/audit-fd.h b/src/libcore/audit-fd.h new file mode 100644 index 0000000000..0eccb59210 --- /dev/null +++ b/src/libcore/audit-fd.h @@ -0,0 +1,23 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +int get_audit_fd(void); +void close_audit_fd(void); diff --git a/src/libcore/automount.c b/src/libcore/automount.c new file mode 100644 index 0000000000..5dc6fd98e7 --- /dev/null +++ b/src/libcore/automount.c @@ -0,0 +1,1092 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "async.h" +#include "automount.h" +#include "bus-error.h" +#include "bus-util.h" +#include "dbus-automount.h" +#include "fd-util.h" +#include "formats-util.h" +#include "io-util.h" +#include "label.h" +#include "mkdir.h" +#include "mount-util.h" +#include "mount.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "special.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "unit-name.h" +#include "unit.h" + +static const UnitActiveState state_translation_table[_AUTOMOUNT_STATE_MAX] = { + [AUTOMOUNT_DEAD] = UNIT_INACTIVE, + [AUTOMOUNT_WAITING] = UNIT_ACTIVE, + [AUTOMOUNT_RUNNING] = UNIT_ACTIVE, + [AUTOMOUNT_FAILED] = UNIT_FAILED +}; + +struct expire_data { + int dev_autofs_fd; + int ioctl_fd; +}; + +static inline void expire_data_free(struct expire_data *data) { + if (!data) + return; + + safe_close(data->dev_autofs_fd); + safe_close(data->ioctl_fd); + free(data); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct expire_data*, expire_data_free); + +static int open_dev_autofs(Manager *m); +static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, void *userdata); + +static void automount_init(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + a->pipe_fd = -1; + a->directory_mode = 0755; + UNIT(a)->ignore_on_isolate = true; +} + +static int automount_send_ready(Automount *a, Set *tokens, int status); + +static void unmount_autofs(Automount *a) { + int r; + + assert(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); + + /* If we reload/reexecute things we keep the mount point + * around */ + if (a->where && + (UNIT(a)->manager->exit_code != MANAGER_RELOAD && + UNIT(a)->manager->exit_code != MANAGER_REEXECUTE)) { + r = repeat_unmount(a->where, MNT_DETACH); + if (r < 0) + log_error_errno(r, "Failed to unmount: %m"); + } +} + +static void automount_done(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + unmount_autofs(a); + + a->where = mfree(a->where); + + a->tokens = set_free(a->tokens); + a->expire_tokens = set_free(a->expire_tokens); + + a->expire_event_source = sd_event_source_unref(a->expire_event_source); +} + +static int automount_add_mount_links(Automount *a) { + _cleanup_free_ char *parent = NULL; + + assert(a); + + parent = dirname_malloc(a->where); + if (!parent) + return -ENOMEM; + + return unit_require_mounts_for(UNIT(a), parent); +} + +static int automount_add_default_dependencies(Automount *a) { + int r; + + assert(a); + + if (!UNIT(a)->default_dependencies) + return 0; + + if (UNIT(a)->manager->running_as != MANAGER_SYSTEM) + return 0; + + r = unit_add_two_dependencies_by_name(UNIT(a), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); + if (r < 0) + return r; + + return 0; +} + +static int automount_verify(Automount *a) { + _cleanup_free_ char *e = NULL; + int r; + + assert(a); + + if (UNIT(a)->load_state != UNIT_LOADED) + return 0; + + if (path_equal(a->where, "/")) { + log_unit_error(UNIT(a), "Cannot have an automount unit for the root directory. Refusing."); + return -EINVAL; + } + + r = unit_name_from_path(a->where, ".automount", &e); + if (r < 0) + return log_unit_error(UNIT(a), "Failed to generate unit name from path: %m"); + + if (!unit_has_name(UNIT(a), e)) { + log_unit_error(UNIT(a), "Where= setting doesn't match unit name. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int automount_load(Unit *u) { + Automount *a = AUTOMOUNT(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + /* Load a .automount file */ + r = unit_load_fragment_and_dropin_optional(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + Unit *x; + + if (!a->where) { + r = unit_name_to_path(u->id, &a->where); + if (r < 0) + return r; + } + + path_kill_slashes(a->where); + + r = unit_load_related_unit(u, ".mount", &x); + if (r < 0) + return r; + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, x, true); + if (r < 0) + return r; + + r = automount_add_mount_links(a); + if (r < 0) + return r; + + r = automount_add_default_dependencies(a); + if (r < 0) + return r; + } + + return automount_verify(a); +} + +static void automount_set_state(Automount *a, AutomountState state) { + AutomountState old_state; + assert(a); + + old_state = a->state; + a->state = state; + + if (state != AUTOMOUNT_WAITING && + state != AUTOMOUNT_RUNNING) + unmount_autofs(a); + + if (state != old_state) + log_unit_debug(UNIT(a), "Changed %s -> %s", automount_state_to_string(old_state), automount_state_to_string(state)); + + unit_notify(UNIT(a), state_translation_table[old_state], state_translation_table[state], true); +} + +static int automount_coldplug(Unit *u) { + Automount *a = AUTOMOUNT(u); + int r; + + assert(a); + assert(a->state == AUTOMOUNT_DEAD); + + if (a->deserialized_state != a->state) { + + r = open_dev_autofs(u->manager); + if (r < 0) + return r; + + if (a->deserialized_state == AUTOMOUNT_WAITING || + a->deserialized_state == AUTOMOUNT_RUNNING) { + assert(a->pipe_fd >= 0); + + r = sd_event_add_io(u->manager->event, &a->pipe_event_source, a->pipe_fd, EPOLLIN, automount_dispatch_io, u); + if (r < 0) + return r; + + (void) sd_event_source_set_description(a->pipe_event_source, "automount-io"); + } + + automount_set_state(a, a->deserialized_state); + } + + return 0; +} + +static void automount_dump(Unit *u, FILE *f, const char *prefix) { + char time_string[FORMAT_TIMESPAN_MAX]; + Automount *a = AUTOMOUNT(u); + + assert(a); + + fprintf(f, + "%sAutomount State: %s\n" + "%sResult: %s\n" + "%sWhere: %s\n" + "%sDirectoryMode: %04o\n" + "%sTimeoutIdleUSec: %s\n", + prefix, automount_state_to_string(a->state), + prefix, automount_result_to_string(a->result), + prefix, a->where, + prefix, a->directory_mode, + prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, a->timeout_idle_usec, USEC_PER_SEC)); +} + +static void automount_enter_dead(Automount *a, AutomountResult f) { + assert(a); + + if (f != AUTOMOUNT_SUCCESS) + a->result = f; + + automount_set_state(a, a->result != AUTOMOUNT_SUCCESS ? AUTOMOUNT_FAILED : AUTOMOUNT_DEAD); +} + +static int open_dev_autofs(Manager *m) { + struct autofs_dev_ioctl param; + + assert(m); + + if (m->dev_autofs_fd >= 0) + return m->dev_autofs_fd; + + label_fix("/dev/autofs", false, false); + + m->dev_autofs_fd = open("/dev/autofs", O_CLOEXEC|O_RDONLY); + if (m->dev_autofs_fd < 0) + return log_error_errno(errno, "Failed to open /dev/autofs: %m"); + + init_autofs_dev_ioctl(¶m); + if (ioctl(m->dev_autofs_fd, AUTOFS_DEV_IOCTL_VERSION, ¶m) < 0) { + m->dev_autofs_fd = safe_close(m->dev_autofs_fd); + return -errno; + } + + log_debug("Autofs kernel version %i.%i", param.ver_major, param.ver_minor); + + return m->dev_autofs_fd; +} + +static int open_ioctl_fd(int dev_autofs_fd, const char *where, dev_t devid) { + struct autofs_dev_ioctl *param; + size_t l; + + assert(dev_autofs_fd >= 0); + assert(where); + + l = sizeof(struct autofs_dev_ioctl) + strlen(where) + 1; + param = alloca(l); + + init_autofs_dev_ioctl(param); + param->size = l; + param->ioctlfd = -1; + param->openmount.devid = devid; + strcpy(param->path, where); + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_OPENMOUNT, param) < 0) + return -errno; + + if (param->ioctlfd < 0) + return -EIO; + + (void) fd_cloexec(param->ioctlfd, true); + return param->ioctlfd; +} + +static int autofs_protocol(int dev_autofs_fd, int ioctl_fd) { + uint32_t major, minor; + struct autofs_dev_ioctl param; + + assert(dev_autofs_fd >= 0); + assert(ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOVER, ¶m) < 0) + return -errno; + + major = param.protover.version; + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_PROTOSUBVER, ¶m) < 0) + return -errno; + + minor = param.protosubver.sub_version; + + log_debug("Autofs protocol version %i.%i", major, minor); + return 0; +} + +static int autofs_set_timeout(int dev_autofs_fd, int ioctl_fd, usec_t usec) { + struct autofs_dev_ioctl param; + + assert(dev_autofs_fd >= 0); + assert(ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + + /* Convert to seconds, rounding up. */ + param.timeout.timeout = (usec + USEC_PER_SEC - 1) / USEC_PER_SEC; + + if (ioctl(dev_autofs_fd, AUTOFS_DEV_IOCTL_TIMEOUT, ¶m) < 0) + return -errno; + + return 0; +} + +static int autofs_send_ready(int dev_autofs_fd, int ioctl_fd, uint32_t token, int status) { + struct autofs_dev_ioctl param; + + assert(dev_autofs_fd >= 0); + assert(ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = ioctl_fd; + + if (status) { + param.fail.token = token; + param.fail.status = status; + } else + param.ready.token = token; + + if (ioctl(dev_autofs_fd, status ? AUTOFS_DEV_IOCTL_FAIL : AUTOFS_DEV_IOCTL_READY, ¶m) < 0) + return -errno; + + return 0; +} + +static int automount_send_ready(Automount *a, Set *tokens, int status) { + _cleanup_close_ int ioctl_fd = -1; + unsigned token; + int r; + + assert(a); + assert(status <= 0); + + if (set_isempty(tokens)) + return 0; + + ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id); + if (ioctl_fd < 0) + return ioctl_fd; + + if (status) + log_unit_debug_errno(UNIT(a), status, "Sending failure: %m"); + else + log_unit_debug(UNIT(a), "Sending success."); + + r = 0; + + /* Autofs thankfully does not hand out 0 as a token */ + while ((token = PTR_TO_UINT(set_steal_first(tokens)))) { + int k; + + /* Autofs fun fact II: + * + * if you pass a positive status code here, the kernel will + * freeze! Yay! */ + + k = autofs_send_ready(UNIT(a)->manager->dev_autofs_fd, + ioctl_fd, + token, + status); + if (k < 0) + r = k; + } + + return r; +} + +static int automount_start_expire(Automount *a); + +int automount_update_mount(Automount *a, MountState old_state, MountState state) { + int r; + + assert(a); + + switch (state) { + case MOUNT_MOUNTED: + case MOUNT_REMOUNTING: + automount_send_ready(a, a->tokens, 0); + r = automount_start_expire(a); + if (r < 0) + log_unit_warning_errno(UNIT(a), r, "Failed to start expiration timer, ignoring: %m"); + break; + case MOUNT_DEAD: + case MOUNT_UNMOUNTING: + case MOUNT_MOUNTING_SIGTERM: + case MOUNT_MOUNTING_SIGKILL: + case MOUNT_REMOUNTING_SIGTERM: + case MOUNT_REMOUNTING_SIGKILL: + case MOUNT_UNMOUNTING_SIGTERM: + case MOUNT_UNMOUNTING_SIGKILL: + case MOUNT_FAILED: + if (old_state != state) + automount_send_ready(a, a->tokens, -ENODEV); + (void) sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_OFF); + break; + default: + break; + } + + switch (state) { + case MOUNT_DEAD: + automount_send_ready(a, a->expire_tokens, 0); + break; + case MOUNT_MOUNTING: + case MOUNT_MOUNTING_DONE: + case MOUNT_MOUNTING_SIGTERM: + case MOUNT_MOUNTING_SIGKILL: + case MOUNT_REMOUNTING_SIGTERM: + case MOUNT_REMOUNTING_SIGKILL: + case MOUNT_UNMOUNTING_SIGTERM: + case MOUNT_UNMOUNTING_SIGKILL: + case MOUNT_FAILED: + if (old_state != state) + automount_send_ready(a, a->expire_tokens, -ENODEV); + break; + default: + break; + } + + return 0; +} + +static void automount_enter_waiting(Automount *a) { + _cleanup_close_ int ioctl_fd = -1; + int p[2] = { -1, -1 }; + char name[sizeof("systemd-")-1 + DECIMAL_STR_MAX(pid_t) + 1]; + char options[sizeof("fd=,pgrp=,minproto=5,maxproto=5,direct")-1 + + DECIMAL_STR_MAX(int) + DECIMAL_STR_MAX(gid_t) + 1]; + bool mounted = false; + int r, dev_autofs_fd; + struct stat st; + + assert(a); + assert(a->pipe_fd < 0); + assert(a->where); + + set_clear(a->tokens); + + r = unit_fail_if_symlink(UNIT(a), a->where); + if (r < 0) + goto fail; + + (void) mkdir_p_label(a->where, 0555); + + unit_warn_if_dir_nonempty(UNIT(a), a->where); + + dev_autofs_fd = open_dev_autofs(UNIT(a)->manager); + if (dev_autofs_fd < 0) { + r = dev_autofs_fd; + goto fail; + } + + if (pipe2(p, O_NONBLOCK|O_CLOEXEC) < 0) { + r = -errno; + goto fail; + } + + xsprintf(options, "fd=%i,pgrp="PID_FMT",minproto=5,maxproto=5,direct", p[1], getpgrp()); + xsprintf(name, "systemd-"PID_FMT, getpid()); + if (mount(name, a->where, "autofs", 0, options) < 0) { + r = -errno; + goto fail; + } + + mounted = true; + + p[1] = safe_close(p[1]); + + if (stat(a->where, &st) < 0) { + r = -errno; + goto fail; + } + + ioctl_fd = open_ioctl_fd(dev_autofs_fd, a->where, st.st_dev); + if (ioctl_fd < 0) { + r = ioctl_fd; + goto fail; + } + + r = autofs_protocol(dev_autofs_fd, ioctl_fd); + if (r < 0) + goto fail; + + r = autofs_set_timeout(dev_autofs_fd, ioctl_fd, a->timeout_idle_usec); + if (r < 0) + goto fail; + + /* Autofs fun fact: + * + * Unless we close the ioctl fd here, for some weird reason + * the direct mount will not receive events from the + * kernel. */ + + r = sd_event_add_io(UNIT(a)->manager->event, &a->pipe_event_source, p[0], EPOLLIN, automount_dispatch_io, a); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(a->pipe_event_source, "automount-io"); + + a->pipe_fd = p[0]; + a->dev_id = st.st_dev; + + automount_set_state(a, AUTOMOUNT_WAITING); + + return; + +fail: + log_unit_error_errno(UNIT(a), r, "Failed to initialize automounter: %m"); + + safe_close_pair(p); + + if (mounted) { + r = repeat_unmount(a->where, MNT_DETACH); + if (r < 0) + log_error_errno(r, "Failed to unmount, ignoring: %m"); + } + + automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); +} + +static void *expire_thread(void *p) { + struct autofs_dev_ioctl param; + _cleanup_(expire_data_freep) struct expire_data *data = (struct expire_data*)p; + int r; + + assert(data->dev_autofs_fd >= 0); + assert(data->ioctl_fd >= 0); + + init_autofs_dev_ioctl(¶m); + param.ioctlfd = data->ioctl_fd; + + do { + r = ioctl(data->dev_autofs_fd, AUTOFS_DEV_IOCTL_EXPIRE, ¶m); + } while (r >= 0); + + if (errno != EAGAIN) + log_warning_errno(errno, "Failed to expire automount, ignoring: %m"); + + return NULL; +} + +static int automount_dispatch_expire(sd_event_source *source, usec_t usec, void *userdata) { + Automount *a = AUTOMOUNT(userdata); + _cleanup_(expire_data_freep) struct expire_data *data = NULL; + int r; + + assert(a); + assert(source == a->expire_event_source); + + data = new0(struct expire_data, 1); + if (!data) + return log_oom(); + + data->ioctl_fd = -1; + + data->dev_autofs_fd = fcntl(UNIT(a)->manager->dev_autofs_fd, F_DUPFD_CLOEXEC, 3); + if (data->dev_autofs_fd < 0) + return log_unit_error_errno(UNIT(a), errno, "Failed to duplicate autofs fd: %m"); + + data->ioctl_fd = open_ioctl_fd(UNIT(a)->manager->dev_autofs_fd, a->where, a->dev_id); + if (data->ioctl_fd < 0) + return log_unit_error_errno(UNIT(a), data->ioctl_fd, "Couldn't open autofs ioctl fd: %m"); + + r = asynchronous_job(expire_thread, data); + if (r < 0) + return log_unit_error_errno(UNIT(a), r, "Failed to start expire job: %m"); + + data = NULL; + + return automount_start_expire(a); +} + +static int automount_start_expire(Automount *a) { + int r; + usec_t timeout; + + assert(a); + + if (a->timeout_idle_usec == 0) + return 0; + + timeout = now(CLOCK_MONOTONIC) + MAX(a->timeout_idle_usec/3, USEC_PER_SEC); + + if (a->expire_event_source) { + r = sd_event_source_set_time(a->expire_event_source, timeout); + if (r < 0) + return r; + + return sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_ONESHOT); + } + + r = sd_event_add_time( + UNIT(a)->manager->event, + &a->expire_event_source, + CLOCK_MONOTONIC, timeout, 0, + automount_dispatch_expire, a); + if (r < 0) + return r; + + (void) sd_event_source_set_description(a->expire_event_source, "automount-expire"); + + return 0; +} + +static void automount_enter_runnning(Automount *a) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + struct stat st; + int r; + + assert(a); + + /* We don't take mount requests anymore if we are supposed to + * shut down anyway */ + if (unit_stop_pending(UNIT(a))) { + log_unit_debug(UNIT(a), "Suppressing automount request since unit stop is scheduled."); + automount_send_ready(a, a->tokens, -EHOSTDOWN); + automount_send_ready(a, a->expire_tokens, -EHOSTDOWN); + return; + } + + mkdir_p_label(a->where, a->directory_mode); + + /* Before we do anything, let's see if somebody is playing games with us? */ + if (lstat(a->where, &st) < 0) { + log_unit_warning_errno(UNIT(a), errno, "Failed to stat automount point: %m"); + goto fail; + } + + if (!S_ISDIR(st.st_mode) || st.st_dev != a->dev_id) + log_unit_info(UNIT(a), "Automount point already active?"); + else { + Unit *trigger; + + trigger = UNIT_TRIGGER(UNIT(a)); + if (!trigger) { + log_unit_error(UNIT(a), "Unit to trigger vanished."); + goto fail; + } + + r = manager_add_job(UNIT(a)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL); + if (r < 0) { + log_unit_warning(UNIT(a), "Failed to queue mount startup job: %s", bus_error_message(&error, r)); + goto fail; + } + } + + automount_set_state(a, AUTOMOUNT_RUNNING); + return; + +fail: + automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); +} + +static int automount_start(Unit *u) { + Automount *a = AUTOMOUNT(u); + Unit *trigger; + + assert(a); + assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_FAILED); + + if (path_is_mount_point(a->where, 0) > 0) { + log_unit_error(u, "Path %s is already a mount point, refusing start.", a->where); + return -EEXIST; + } + + trigger = UNIT_TRIGGER(u); + if (!trigger || trigger->load_state != UNIT_LOADED) { + log_unit_error(u, "Refusing to start, unit to trigger not loaded."); + return -ENOENT; + } + + a->result = AUTOMOUNT_SUCCESS; + automount_enter_waiting(a); + return 1; +} + +static int automount_stop(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(a); + assert(a->state == AUTOMOUNT_WAITING || a->state == AUTOMOUNT_RUNNING); + + automount_enter_dead(a, AUTOMOUNT_SUCCESS); + return 1; +} + +static int automount_serialize(Unit *u, FILE *f, FDSet *fds) { + Automount *a = AUTOMOUNT(u); + Iterator i; + void *p; + int r; + + assert(a); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", automount_state_to_string(a->state)); + unit_serialize_item(u, f, "result", automount_result_to_string(a->result)); + unit_serialize_item_format(u, f, "dev-id", "%u", (unsigned) a->dev_id); + + SET_FOREACH(p, a->tokens, i) + unit_serialize_item_format(u, f, "token", "%u", PTR_TO_UINT(p)); + SET_FOREACH(p, a->expire_tokens, i) + unit_serialize_item_format(u, f, "expire-token", "%u", PTR_TO_UINT(p)); + + r = unit_serialize_item_fd(u, f, fds, "pipe-fd", a->pipe_fd); + if (r < 0) + return r; + + return 0; +} + +static int automount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Automount *a = AUTOMOUNT(u); + int r; + + assert(a); + assert(fds); + + if (streq(key, "state")) { + AutomountState state; + + state = automount_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + a->deserialized_state = state; + } else if (streq(key, "result")) { + AutomountResult f; + + f = automount_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != AUTOMOUNT_SUCCESS) + a->result = f; + + } else if (streq(key, "dev-id")) { + unsigned d; + + if (safe_atou(value, &d) < 0) + log_unit_debug(u, "Failed to parse dev-id value: %s", value); + else + a->dev_id = (unsigned) d; + } else if (streq(key, "token")) { + unsigned token; + + if (safe_atou(value, &token) < 0) + log_unit_debug(u, "Failed to parse token value: %s", value); + else { + r = set_ensure_allocated(&a->tokens, NULL); + if (r < 0) { + log_oom(); + return 0; + } + + r = set_put(a->tokens, UINT_TO_PTR(token)); + if (r < 0) + log_unit_error_errno(u, r, "Failed to add token to set: %m"); + } + } else if (streq(key, "expire-token")) { + unsigned token; + + if (safe_atou(value, &token) < 0) + log_unit_debug(u, "Failed to parse token value: %s", value); + else { + r = set_ensure_allocated(&a->expire_tokens, NULL); + if (r < 0) { + log_oom(); + return 0; + } + + r = set_put(a->expire_tokens, UINT_TO_PTR(token)); + if (r < 0) + log_unit_error_errno(u, r, "Failed to add expire token to set: %m"); + } + } else if (streq(key, "pipe-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse pipe-fd value: %s", value); + else { + safe_close(a->pipe_fd); + a->pipe_fd = fdset_remove(fds, fd); + } + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +static UnitActiveState automount_active_state(Unit *u) { + assert(u); + + return state_translation_table[AUTOMOUNT(u)->state]; +} + +static const char *automount_sub_state_to_string(Unit *u) { + assert(u); + + return automount_state_to_string(AUTOMOUNT(u)->state); +} + +static bool automount_check_gc(Unit *u) { + assert(u); + + if (!UNIT_TRIGGER(u)) + return false; + + return UNIT_VTABLE(UNIT_TRIGGER(u))->check_gc(UNIT_TRIGGER(u)); +} + +static int automount_dispatch_io(sd_event_source *s, int fd, uint32_t events, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + union autofs_v5_packet_union packet; + Automount *a = AUTOMOUNT(userdata); + struct stat st; + Unit *trigger; + int r; + + assert(a); + assert(fd == a->pipe_fd); + + if (events != EPOLLIN) { + log_unit_error(UNIT(a), "Got invalid poll event %"PRIu32" on pipe (fd=%d)", events, fd); + goto fail; + } + + r = loop_read_exact(a->pipe_fd, &packet, sizeof(packet), true); + if (r < 0) { + log_unit_error_errno(UNIT(a), r, "Invalid read from pipe: %m"); + goto fail; + } + + switch (packet.hdr.type) { + + case autofs_ptype_missing_direct: + + if (packet.v5_packet.pid > 0) { + _cleanup_free_ char *p = NULL; + + get_process_comm(packet.v5_packet.pid, &p); + log_unit_info(UNIT(a), "Got automount request for %s, triggered by %"PRIu32" (%s)", a->where, packet.v5_packet.pid, strna(p)); + } else + log_unit_debug(UNIT(a), "Got direct mount request on %s", a->where); + + r = set_ensure_allocated(&a->tokens, NULL); + if (r < 0) { + log_unit_error(UNIT(a), "Failed to allocate token set."); + goto fail; + } + + r = set_put(a->tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token)); + if (r < 0) { + log_unit_error_errno(UNIT(a), r, "Failed to remember token: %m"); + goto fail; + } + + automount_enter_runnning(a); + break; + + case autofs_ptype_expire_direct: + log_unit_debug(UNIT(a), "Got direct umount request on %s", a->where); + + (void) sd_event_source_set_enabled(a->expire_event_source, SD_EVENT_OFF); + + r = set_ensure_allocated(&a->expire_tokens, NULL); + if (r < 0) { + log_unit_error(UNIT(a), "Failed to allocate token set."); + goto fail; + } + + r = set_put(a->expire_tokens, UINT_TO_PTR(packet.v5_packet.wait_queue_token)); + if (r < 0) { + log_unit_error_errno(UNIT(a), r, "Failed to remember token: %m"); + goto fail; + } + + /* Before we do anything, let's see if somebody is playing games with us? */ + if (lstat(a->where, &st) < 0) { + log_unit_warning_errno(UNIT(a), errno, "Failed to stat automount point: %m"); + goto fail; + } + + if (!S_ISDIR(st.st_mode) || st.st_dev == a->dev_id) { + log_unit_info(UNIT(a), "Automount point already unmounted?"); + automount_send_ready(a, a->expire_tokens, 0); + break; + } + + trigger = UNIT_TRIGGER(UNIT(a)); + if (!trigger) { + log_unit_error(UNIT(a), "Unit to trigger vanished."); + goto fail; + } + + r = manager_add_job(UNIT(a)->manager, JOB_STOP, trigger, JOB_REPLACE, &error, NULL); + if (r < 0) { + log_unit_warning(UNIT(a), "Failed to queue umount startup job: %s", bus_error_message(&error, r)); + goto fail; + } + break; + + default: + log_unit_error(UNIT(a), "Received unknown automount request %i", packet.hdr.type); + break; + } + + return 0; + +fail: + automount_enter_dead(a, AUTOMOUNT_FAILURE_RESOURCES); + return 0; +} + +static void automount_shutdown(Manager *m) { + assert(m); + + m->dev_autofs_fd = safe_close(m->dev_autofs_fd); +} + +static void automount_reset_failed(Unit *u) { + Automount *a = AUTOMOUNT(u); + + assert(a); + + if (a->state == AUTOMOUNT_FAILED) + automount_set_state(a, AUTOMOUNT_DEAD); + + a->result = AUTOMOUNT_SUCCESS; +} + +static bool automount_supported(void) { + static int supported = -1; + + if (supported < 0) + supported = access("/dev/autofs", F_OK) >= 0; + + return supported; +} + +static const char* const automount_result_table[_AUTOMOUNT_RESULT_MAX] = { + [AUTOMOUNT_SUCCESS] = "success", + [AUTOMOUNT_FAILURE_RESOURCES] = "resources" +}; + +DEFINE_STRING_TABLE_LOOKUP(automount_result, AutomountResult); + +const UnitVTable automount_vtable = { + .object_size = sizeof(Automount), + + .sections = + "Unit\0" + "Automount\0" + "Install\0", + + .no_alias = true, + .no_instances = true, + + .init = automount_init, + .load = automount_load, + .done = automount_done, + + .coldplug = automount_coldplug, + + .dump = automount_dump, + + .start = automount_start, + .stop = automount_stop, + + .serialize = automount_serialize, + .deserialize_item = automount_deserialize_item, + + .active_state = automount_active_state, + .sub_state_to_string = automount_sub_state_to_string, + + .check_gc = automount_check_gc, + + .reset_failed = automount_reset_failed, + + .bus_vtable = bus_automount_vtable, + + .shutdown = automount_shutdown, + .supported = automount_supported, + + .status_message_formats = { + .finished_start_job = { + [JOB_DONE] = "Set up automount %s.", + [JOB_FAILED] = "Failed to set up automount %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Unset automount %s.", + [JOB_FAILED] = "Failed to unset automount %s.", + }, + }, +}; diff --git a/src/libcore/automount.h b/src/libcore/automount.h new file mode 100644 index 0000000000..cf5b1cf994 --- /dev/null +++ b/src/libcore/automount.h @@ -0,0 +1,59 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Automount Automount; + +#include "unit.h" + +typedef enum AutomountResult { + AUTOMOUNT_SUCCESS, + AUTOMOUNT_FAILURE_RESOURCES, + _AUTOMOUNT_RESULT_MAX, + _AUTOMOUNT_RESULT_INVALID = -1 +} AutomountResult; + +struct Automount { + Unit meta; + + AutomountState state, deserialized_state; + + char *where; + usec_t timeout_idle_usec; + + int pipe_fd; + sd_event_source *pipe_event_source; + mode_t directory_mode; + dev_t dev_id; + + Set *tokens; + Set *expire_tokens; + + sd_event_source *expire_event_source; + + AutomountResult result; +}; + +extern const UnitVTable automount_vtable; + +int automount_update_mount(Automount *a, MountState old_state, MountState state); + +const char* automount_result_to_string(AutomountResult i) _const_; +AutomountResult automount_result_from_string(const char *s) _pure_; diff --git a/src/libcore/bus-endpoint.c b/src/libcore/bus-endpoint.c new file mode 100644 index 0000000000..d22a80c91f --- /dev/null +++ b/src/libcore/bus-endpoint.c @@ -0,0 +1,135 @@ +/*** + This file is part of systemd. + + Copyright 2014 Daniel Mack + + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "bus-endpoint.h" +#include "bus-kernel.h" +#include "bus-policy.h" +#include "kdbus.h" + +int bus_kernel_set_endpoint_policy(int fd, uid_t uid, BusEndpoint *ep) { + + struct kdbus_cmd *update; + struct kdbus_item *n; + BusEndpointPolicy *po; + Iterator i; + size_t size; + int r; + + size = ALIGN8(offsetof(struct kdbus_cmd, items)); + + HASHMAP_FOREACH(po, ep->policy_hash, i) { + size += ALIGN8(offsetof(struct kdbus_item, str) + strlen(po->name) + 1); + size += ALIGN8(offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access)); + } + + update = alloca0_align(size, 8); + update->size = size; + + n = update->items; + + HASHMAP_FOREACH(po, ep->policy_hash, i) { + n->type = KDBUS_ITEM_NAME; + n->size = offsetof(struct kdbus_item, str) + strlen(po->name) + 1; + strcpy(n->str, po->name); + n = KDBUS_ITEM_NEXT(n); + + n->type = KDBUS_ITEM_POLICY_ACCESS; + n->size = offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access); + + n->policy_access.type = KDBUS_POLICY_ACCESS_USER; + n->policy_access.access = bus_kernel_translate_access(po->access); + n->policy_access.id = uid; + + n = KDBUS_ITEM_NEXT(n); + } + + r = ioctl(fd, KDBUS_CMD_ENDPOINT_UPDATE, update); + if (r < 0) + return -errno; + + return 0; +} + +int bus_endpoint_new(BusEndpoint **ep) { + assert(ep); + + *ep = new0(BusEndpoint, 1); + if (!*ep) + return -ENOMEM; + + return 0; +} + +int bus_endpoint_add_policy(BusEndpoint *ep, const char *name, BusPolicyAccess access) { + _cleanup_free_ BusEndpointPolicy *po = NULL; + _cleanup_free_ char *key = NULL; + int r; + + assert(ep); + assert(name); + assert(access > _BUS_POLICY_ACCESS_INVALID && access < _BUS_POLICY_ACCESS_MAX); + + /* check if we already have this name in the policy list. If we do, see if the new access level + * is higher than the exising one, and upgrade the entry in that case. Otherwise, do nothing. + */ + + if (ep->policy_hash) { + po = hashmap_get(ep->policy_hash, name); + if (po) { + if (po->access < access) + po->access = access; + + return 0; + } + } else { + ep->policy_hash = hashmap_new(&string_hash_ops); + if (!ep->policy_hash) + return -ENOMEM; + } + + po = new0(BusEndpointPolicy, 1); + if (!po) + return -ENOMEM; + + key = strdup(name); + if (!key) + return -ENOMEM; + + po->name = key; + po->access = access; + + r = hashmap_put(ep->policy_hash, key, po); + if (r < 0) + return r; + + po = NULL; + key = NULL; + return 0; +} + +void bus_endpoint_free(BusEndpoint *endpoint) { + if (!endpoint) + return; + + hashmap_free_free_free(endpoint->policy_hash); + free(endpoint); +} diff --git a/src/libcore/bus-endpoint.h b/src/libcore/bus-endpoint.h new file mode 100644 index 0000000000..f2fbc4701c --- /dev/null +++ b/src/libcore/bus-endpoint.h @@ -0,0 +1,42 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Daniel Mack + + 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 . +***/ + +typedef struct BusEndpoint BusEndpoint; +typedef struct BusEndpointPolicy BusEndpointPolicy; + +#include "bus-policy.h" +#include "hashmap.h" + +struct BusEndpointPolicy { + char *name; + BusPolicyAccess access; +}; + +struct BusEndpoint { + Hashmap *policy_hash; +}; + +int bus_endpoint_new(BusEndpoint **ep); +void bus_endpoint_free(BusEndpoint *endpoint); + +int bus_endpoint_add_policy(BusEndpoint *ep, const char *name, BusPolicyAccess access); + +int bus_kernel_set_endpoint_policy(int fd, uid_t uid, BusEndpoint *ep); diff --git a/src/libcore/bus-policy.c b/src/libcore/bus-policy.c new file mode 100644 index 0000000000..4907c268e8 --- /dev/null +++ b/src/libcore/bus-policy.c @@ -0,0 +1,180 @@ +/*** + This file is part of systemd. + + Copyright 2014 Daniel Mack + + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "bus-kernel.h" +#include "bus-policy.h" +#include "kdbus.h" +#include "string-table.h" +#include "user-util.h" +#include "util.h" + +int bus_kernel_translate_access(BusPolicyAccess access) { + assert(access >= 0); + assert(access < _BUS_POLICY_ACCESS_MAX); + + switch (access) { + + case BUS_POLICY_ACCESS_SEE: + return KDBUS_POLICY_SEE; + + case BUS_POLICY_ACCESS_TALK: + return KDBUS_POLICY_TALK; + + case BUS_POLICY_ACCESS_OWN: + return KDBUS_POLICY_OWN; + + default: + assert_not_reached("Unknown policy access"); + } +} + +int bus_kernel_translate_policy(const BusNamePolicy *policy, struct kdbus_item *item) { + int r; + + assert(policy); + assert(item); + + switch (policy->type) { + + case BUSNAME_POLICY_TYPE_USER: { + const char *user = policy->name; + uid_t uid; + + r = get_user_creds(&user, &uid, NULL, NULL, NULL); + if (r < 0) + return r; + + item->policy_access.type = KDBUS_POLICY_ACCESS_USER; + item->policy_access.id = uid; + break; + } + + case BUSNAME_POLICY_TYPE_GROUP: { + const char *group = policy->name; + gid_t gid; + + r = get_group_creds(&group, &gid); + if (r < 0) + return r; + + item->policy_access.type = KDBUS_POLICY_ACCESS_GROUP; + item->policy_access.id = gid; + break; + } + + default: + assert_not_reached("Unknown policy type"); + } + + item->policy_access.access = bus_kernel_translate_access(policy->access); + + return 0; +} + +int bus_kernel_make_starter( + int fd, + const char *name, + bool activating, + bool accept_fd, + BusNamePolicy *policy, + BusPolicyAccess world_policy) { + + struct kdbus_cmd_free cmd_free = { .size = sizeof(cmd_free) }; + struct kdbus_cmd_hello *hello; + struct kdbus_item *n; + size_t policy_cnt = 0; + BusNamePolicy *po; + size_t size; + int r; + + assert(fd >= 0); + assert(name); + + LIST_FOREACH(policy, po, policy) + policy_cnt++; + + if (world_policy >= 0) + policy_cnt++; + + size = offsetof(struct kdbus_cmd_hello, items) + + ALIGN8(offsetof(struct kdbus_item, str) + strlen(name) + 1) + + policy_cnt * ALIGN8(offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access)); + + hello = alloca0_align(size, 8); + + n = hello->items; + strcpy(n->str, name); + n->size = offsetof(struct kdbus_item, str) + strlen(n->str) + 1; + n->type = KDBUS_ITEM_NAME; + n = KDBUS_ITEM_NEXT(n); + + LIST_FOREACH(policy, po, policy) { + n->type = KDBUS_ITEM_POLICY_ACCESS; + n->size = offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access); + + r = bus_kernel_translate_policy(po, n); + if (r < 0) + return r; + + n = KDBUS_ITEM_NEXT(n); + } + + if (world_policy >= 0) { + n->type = KDBUS_ITEM_POLICY_ACCESS; + n->size = offsetof(struct kdbus_item, policy_access) + sizeof(struct kdbus_policy_access); + n->policy_access.type = KDBUS_POLICY_ACCESS_WORLD; + n->policy_access.access = bus_kernel_translate_access(world_policy); + } + + hello->size = size; + hello->flags = + (activating ? KDBUS_HELLO_ACTIVATOR : KDBUS_HELLO_POLICY_HOLDER) | + (accept_fd ? KDBUS_HELLO_ACCEPT_FD : 0); + hello->pool_size = KDBUS_POOL_SIZE; + hello->attach_flags_send = _KDBUS_ATTACH_ANY; + hello->attach_flags_recv = _KDBUS_ATTACH_ANY; + + if (ioctl(fd, KDBUS_CMD_HELLO, hello) < 0) { + if (errno == ENOTTY) /* Major API change */ + return -ESOCKTNOSUPPORT; + return -errno; + } + + /* not interested in any output values */ + cmd_free.offset = hello->offset; + (void) ioctl(fd, KDBUS_CMD_FREE, &cmd_free); + + /* The higher 32bit of the bus_flags fields are considered + * 'incompatible flags'. Refuse them all for now. */ + if (hello->bus_flags > 0xFFFFFFFFULL) + return -ESOCKTNOSUPPORT; + + return fd; +} + +static const char* const bus_policy_access_table[_BUS_POLICY_ACCESS_MAX] = { + [BUS_POLICY_ACCESS_SEE] = "see", + [BUS_POLICY_ACCESS_TALK] = "talk", + [BUS_POLICY_ACCESS_OWN] = "own", +}; + +DEFINE_STRING_TABLE_LOOKUP(bus_policy_access, BusPolicyAccess); diff --git a/src/libcore/bus-policy.h b/src/libcore/bus-policy.h new file mode 100644 index 0000000000..5b2c4d5953 --- /dev/null +++ b/src/libcore/bus-policy.h @@ -0,0 +1,64 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Daniel Mack + + 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 . +***/ + +#include "kdbus.h" +#include "list.h" +#include "macro.h" + +typedef struct BusNamePolicy BusNamePolicy; + +typedef enum BusPolicyAccess { + BUS_POLICY_ACCESS_SEE, + BUS_POLICY_ACCESS_TALK, + BUS_POLICY_ACCESS_OWN, + _BUS_POLICY_ACCESS_MAX, + _BUS_POLICY_ACCESS_INVALID = -1 +} BusPolicyAccess; + +typedef enum BusNamePolicyType { + BUSNAME_POLICY_TYPE_USER, + BUSNAME_POLICY_TYPE_GROUP, + _BUSNAME_POLICY_TYPE_MAX, + _BUSNAME_POLICY_TYPE_INVALID = -1 +} BusNamePolicyType; + +struct BusNamePolicy { + BusNamePolicyType type; + BusPolicyAccess access; + + char *name; + + LIST_FIELDS(BusNamePolicy, policy); +}; + +int bus_kernel_translate_access(BusPolicyAccess access); +int bus_kernel_translate_policy(const BusNamePolicy *policy, struct kdbus_item *item); + +const char* bus_policy_access_to_string(BusPolicyAccess i) _const_; +BusPolicyAccess bus_policy_access_from_string(const char *s) _pure_; + +int bus_kernel_make_starter( + int fd, + const char *name, + bool activating, + bool accept_fd, + BusNamePolicy *policy, + BusPolicyAccess world_policy); diff --git a/src/libcore/busname.c b/src/libcore/busname.c new file mode 100644 index 0000000000..de2a21ccde --- /dev/null +++ b/src/libcore/busname.c @@ -0,0 +1,1067 @@ +/*** + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-kernel.h" +#include "bus-policy.h" +#include "bus-util.h" +#include "busname.h" +#include "dbus-busname.h" +#include "fd-util.h" +#include "formats-util.h" +#include "kdbus.h" +#include "parse-util.h" +#include "process-util.h" +#include "service.h" +#include "signal-util.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" + +static const UnitActiveState state_translation_table[_BUSNAME_STATE_MAX] = { + [BUSNAME_DEAD] = UNIT_INACTIVE, + [BUSNAME_MAKING] = UNIT_ACTIVATING, + [BUSNAME_REGISTERED] = UNIT_ACTIVE, + [BUSNAME_LISTENING] = UNIT_ACTIVE, + [BUSNAME_RUNNING] = UNIT_ACTIVE, + [BUSNAME_SIGTERM] = UNIT_DEACTIVATING, + [BUSNAME_SIGKILL] = UNIT_DEACTIVATING, + [BUSNAME_FAILED] = UNIT_FAILED +}; + +static int busname_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int busname_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); + +static void busname_init(Unit *u) { + BusName *n = BUSNAME(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + n->starter_fd = -1; + n->accept_fd = true; + n->activating = true; + + n->timeout_usec = u->manager->default_timeout_start_usec; +} + +static void busname_unwatch_control_pid(BusName *n) { + assert(n); + + if (n->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(n), n->control_pid); + n->control_pid = 0; +} + +static void busname_free_policy(BusName *n) { + BusNamePolicy *p; + + assert(n); + + while ((p = n->policy)) { + LIST_REMOVE(policy, n->policy, p); + + free(p->name); + free(p); + } +} + +static void busname_close_fd(BusName *n) { + assert(n); + + n->starter_event_source = sd_event_source_unref(n->starter_event_source); + n->starter_fd = safe_close(n->starter_fd); +} + +static void busname_done(Unit *u) { + BusName *n = BUSNAME(u); + + assert(n); + + n->name = mfree(n->name); + + busname_free_policy(n); + busname_unwatch_control_pid(n); + busname_close_fd(n); + + unit_ref_unset(&n->service); + + n->timer_event_source = sd_event_source_unref(n->timer_event_source); +} + +static int busname_arm_timer(BusName *n, usec_t usec) { + int r; + + assert(n); + + if (n->timer_event_source) { + r = sd_event_source_set_time(n->timer_event_source, usec); + if (r < 0) + return r; + + return sd_event_source_set_enabled(n->timer_event_source, SD_EVENT_ONESHOT); + } + + if (usec == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + UNIT(n)->manager->event, + &n->timer_event_source, + CLOCK_MONOTONIC, + usec, 0, + busname_dispatch_timer, n); + if (r < 0) + return r; + + (void) sd_event_source_set_description(n->timer_event_source, "busname-timer"); + + return 0; +} + +static int busname_add_default_default_dependencies(BusName *n) { + int r; + + assert(n); + + r = unit_add_dependency_by_name(UNIT(n), UNIT_BEFORE, SPECIAL_BUSNAMES_TARGET, NULL, true); + if (r < 0) + return r; + + if (UNIT(n)->manager->running_as == MANAGER_SYSTEM) { + r = unit_add_two_dependencies_by_name(UNIT(n), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); + if (r < 0) + return r; + } + + return unit_add_two_dependencies_by_name(UNIT(n), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static int busname_add_extras(BusName *n) { + Unit *u = UNIT(n); + int r; + + assert(n); + + if (!n->name) { + r = unit_name_to_prefix(u->id, &n->name); + if (r < 0) + return r; + } + + if (!u->description) { + r = unit_set_description(u, n->name); + if (r < 0) + return r; + } + + if (n->activating) { + if (!UNIT_DEREF(n->service)) { + Unit *x; + + r = unit_load_related_unit(u, ".service", &x); + if (r < 0) + return r; + + unit_ref_set(&n->service, x); + } + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(n->service), true); + if (r < 0) + return r; + } + + if (u->default_dependencies) { + r = busname_add_default_default_dependencies(n); + if (r < 0) + return r; + } + + return 0; +} + +static int busname_verify(BusName *n) { + char *e; + + assert(n); + + if (UNIT(n)->load_state != UNIT_LOADED) + return 0; + + if (!service_name_is_valid(n->name)) { + log_unit_error(UNIT(n), "Name= setting is not a valid service name Refusing."); + return -EINVAL; + } + + e = strjoina(n->name, ".busname"); + if (!unit_has_name(UNIT(n), e)) { + log_unit_error(UNIT(n), "Name= setting doesn't match unit name. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int busname_load(Unit *u) { + BusName *n = BUSNAME(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + r = unit_load_fragment_and_dropin(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + /* This is a new unit? Then let's add in some extras */ + r = busname_add_extras(n); + if (r < 0) + return r; + } + + return busname_verify(n); +} + +static void busname_dump(Unit *u, FILE *f, const char *prefix) { + BusName *n = BUSNAME(u); + + assert(n); + assert(f); + + fprintf(f, + "%sBus Name State: %s\n" + "%sResult: %s\n" + "%sName: %s\n" + "%sActivating: %s\n" + "%sAccept FD: %s\n", + prefix, busname_state_to_string(n->state), + prefix, busname_result_to_string(n->result), + prefix, n->name, + prefix, yes_no(n->activating), + prefix, yes_no(n->accept_fd)); + + if (n->control_pid > 0) + fprintf(f, + "%sControl PID: "PID_FMT"\n", + prefix, n->control_pid); +} + +static void busname_unwatch_fd(BusName *n) { + int r; + + assert(n); + + if (!n->starter_event_source) + return; + + r = sd_event_source_set_enabled(n->starter_event_source, SD_EVENT_OFF); + if (r < 0) + log_unit_debug_errno(UNIT(n), r, "Failed to disable event source: %m"); +} + +static int busname_watch_fd(BusName *n) { + int r; + + assert(n); + + if (n->starter_fd < 0) + return 0; + + if (n->starter_event_source) { + r = sd_event_source_set_enabled(n->starter_event_source, SD_EVENT_ON); + if (r < 0) + goto fail; + } else { + r = sd_event_add_io(UNIT(n)->manager->event, &n->starter_event_source, n->starter_fd, EPOLLIN, busname_dispatch_io, n); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(n->starter_event_source, "busname-starter"); + } + + return 0; + +fail: + log_unit_warning_errno(UNIT(n), r, "Failed to watch starter fd: %m"); + busname_unwatch_fd(n); + return r; +} + +static int busname_open_fd(BusName *n) { + _cleanup_free_ char *path = NULL; + const char *mode; + + assert(n); + + if (n->starter_fd >= 0) + return 0; + + mode = UNIT(n)->manager->running_as == MANAGER_SYSTEM ? "system" : "user"; + n->starter_fd = bus_kernel_open_bus_fd(mode, &path); + if (n->starter_fd < 0) + return log_unit_warning_errno(UNIT(n), n->starter_fd, "Failed to open %s: %m", path ?: "kdbus"); + + return 0; +} + +static void busname_set_state(BusName *n, BusNameState state) { + BusNameState old_state; + assert(n); + + old_state = n->state; + n->state = state; + + if (!IN_SET(state, BUSNAME_MAKING, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) { + n->timer_event_source = sd_event_source_unref(n->timer_event_source); + busname_unwatch_control_pid(n); + } + + if (state != BUSNAME_LISTENING) + busname_unwatch_fd(n); + + if (!IN_SET(state, BUSNAME_LISTENING, BUSNAME_MAKING, BUSNAME_REGISTERED, BUSNAME_RUNNING)) + busname_close_fd(n); + + if (state != old_state) + log_unit_debug(UNIT(n), "Changed %s -> %s", busname_state_to_string(old_state), busname_state_to_string(state)); + + unit_notify(UNIT(n), state_translation_table[old_state], state_translation_table[state], true); +} + +static int busname_coldplug(Unit *u) { + BusName *n = BUSNAME(u); + int r; + + assert(n); + assert(n->state == BUSNAME_DEAD); + + if (n->deserialized_state == n->state) + return 0; + + if (n->control_pid > 0 && + pid_is_unwaited(n->control_pid) && + IN_SET(n->deserialized_state, BUSNAME_MAKING, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) { + + r = unit_watch_pid(UNIT(n), n->control_pid); + if (r < 0) + return r; + + r = busname_arm_timer(n, usec_add(u->state_change_timestamp.monotonic, n->timeout_usec)); + if (r < 0) + return r; + } + + if (IN_SET(n->deserialized_state, BUSNAME_MAKING, BUSNAME_LISTENING, BUSNAME_REGISTERED, BUSNAME_RUNNING)) { + r = busname_open_fd(n); + if (r < 0) + return r; + } + + if (n->deserialized_state == BUSNAME_LISTENING) { + r = busname_watch_fd(n); + if (r < 0) + return r; + } + + busname_set_state(n, n->deserialized_state); + return 0; +} + +static int busname_make_starter(BusName *n, pid_t *_pid) { + pid_t pid; + int r; + + r = busname_arm_timer(n, usec_add(now(CLOCK_MONOTONIC), n->timeout_usec)); + if (r < 0) + goto fail; + + /* We have to resolve the user/group names out-of-process, + * hence let's fork here. It's messy, but well, what can we + * do? */ + + pid = fork(); + if (pid < 0) + return -errno; + + if (pid == 0) { + int ret; + + (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE, -1); + (void) ignore_signals(SIGPIPE, -1); + log_forget_fds(); + + r = bus_kernel_make_starter(n->starter_fd, n->name, n->activating, n->accept_fd, n->policy, n->policy_world); + if (r < 0) { + ret = EXIT_MAKE_STARTER; + goto fail_child; + } + + _exit(0); + + fail_child: + log_open(); + log_error_errno(r, "Failed to create starter connection at step %s: %m", exit_status_to_string(ret, EXIT_STATUS_SYSTEMD)); + + _exit(ret); + } + + r = unit_watch_pid(UNIT(n), pid); + if (r < 0) + goto fail; + + *_pid = pid; + return 0; + +fail: + n->timer_event_source = sd_event_source_unref(n->timer_event_source); + return r; +} + +static void busname_enter_dead(BusName *n, BusNameResult f) { + assert(n); + + if (f != BUSNAME_SUCCESS) + n->result = f; + + busname_set_state(n, n->result != BUSNAME_SUCCESS ? BUSNAME_FAILED : BUSNAME_DEAD); +} + +static void busname_enter_signal(BusName *n, BusNameState state, BusNameResult f) { + KillContext kill_context = {}; + int r; + + assert(n); + + if (f != BUSNAME_SUCCESS) + n->result = f; + + kill_context_init(&kill_context); + + r = unit_kill_context(UNIT(n), + &kill_context, + state != BUSNAME_SIGTERM ? KILL_KILL : KILL_TERMINATE, + -1, + n->control_pid, + false); + if (r < 0) { + log_unit_warning_errno(UNIT(n), r, "Failed to kill control process: %m"); + goto fail; + } + + if (r > 0) { + r = busname_arm_timer(n, usec_add(now(CLOCK_MONOTONIC), n->timeout_usec)); + if (r < 0) { + log_unit_warning_errno(UNIT(n), r, "Failed to arm timer: %m"); + goto fail; + } + + busname_set_state(n, state); + } else if (state == BUSNAME_SIGTERM) + busname_enter_signal(n, BUSNAME_SIGKILL, BUSNAME_SUCCESS); + else + busname_enter_dead(n, BUSNAME_SUCCESS); + + return; + +fail: + busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); +} + +static void busname_enter_listening(BusName *n) { + int r; + + assert(n); + + if (n->activating) { + r = busname_watch_fd(n); + if (r < 0) { + log_unit_warning_errno(UNIT(n), r, "Failed to watch names: %m"); + goto fail; + } + + busname_set_state(n, BUSNAME_LISTENING); + } else + busname_set_state(n, BUSNAME_REGISTERED); + + return; + +fail: + busname_enter_signal(n, BUSNAME_SIGTERM, BUSNAME_FAILURE_RESOURCES); +} + +static void busname_enter_making(BusName *n) { + int r; + + assert(n); + + r = busname_open_fd(n); + if (r < 0) + goto fail; + + if (n->policy) { + /* If there is a policy, we need to resolve user/group + * names, which we can't do from PID1, hence let's + * fork. */ + busname_unwatch_control_pid(n); + + r = busname_make_starter(n, &n->control_pid); + if (r < 0) { + log_unit_warning_errno(UNIT(n), r, "Failed to fork 'making' task: %m"); + goto fail; + } + + busname_set_state(n, BUSNAME_MAKING); + } else { + /* If there is no policy, we can do everything + * directly from PID 1, hence do so. */ + + r = bus_kernel_make_starter(n->starter_fd, n->name, n->activating, n->accept_fd, NULL, n->policy_world); + if (r < 0) { + log_unit_warning_errno(UNIT(n), r, "Failed to make starter: %m"); + goto fail; + } + + busname_enter_listening(n); + } + + return; + +fail: + busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); +} + +static void busname_enter_running(BusName *n) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + bool pending = false; + Unit *other; + Iterator i; + int r; + + assert(n); + + if (!n->activating) + return; + + /* We don't take connections anymore if we are supposed to + * shut down anyway */ + + if (unit_stop_pending(UNIT(n))) { + log_unit_debug(UNIT(n), "Suppressing activation request since unit stop is scheduled."); + + /* Flush all queued activation reqeuest by closing and reopening the connection */ + bus_kernel_drop_one(n->starter_fd); + + busname_enter_listening(n); + return; + } + + /* If there's already a start pending don't bother to do + * anything */ + SET_FOREACH(other, UNIT(n)->dependencies[UNIT_TRIGGERS], i) + if (unit_active_or_pending(other)) { + pending = true; + break; + } + + if (!pending) { + if (!UNIT_ISSET(n->service)) { + log_unit_error(UNIT(n), "Service to activate vanished, refusing activation."); + r = -ENOENT; + goto fail; + } + + r = manager_add_job(UNIT(n)->manager, JOB_START, UNIT_DEREF(n->service), JOB_REPLACE, &error, NULL); + if (r < 0) + goto fail; + } + + busname_set_state(n, BUSNAME_RUNNING); + return; + +fail: + log_unit_warning(UNIT(n), "Failed to queue service startup job: %s", bus_error_message(&error, r)); + busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); +} + +static int busname_start(Unit *u) { + BusName *n = BUSNAME(u); + + assert(n); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (IN_SET(n->state, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) + return -EAGAIN; + + /* Already on it! */ + if (n->state == BUSNAME_MAKING) + return 0; + + if (n->activating && UNIT_ISSET(n->service)) { + Service *service; + + service = SERVICE(UNIT_DEREF(n->service)); + + if (UNIT(service)->load_state != UNIT_LOADED) { + log_unit_error(u, "Bus service %s not loaded, refusing.", UNIT(service)->id); + return -ENOENT; + } + } + + assert(IN_SET(n->state, BUSNAME_DEAD, BUSNAME_FAILED)); + + n->result = BUSNAME_SUCCESS; + busname_enter_making(n); + + return 1; +} + +static int busname_stop(Unit *u) { + BusName *n = BUSNAME(u); + + assert(n); + + /* Already on it */ + if (IN_SET(n->state, BUSNAME_SIGTERM, BUSNAME_SIGKILL)) + return 0; + + /* If there's already something running, we go directly into + * kill mode. */ + + if (n->state == BUSNAME_MAKING) { + busname_enter_signal(n, BUSNAME_SIGTERM, BUSNAME_SUCCESS); + return -EAGAIN; + } + + assert(IN_SET(n->state, BUSNAME_REGISTERED, BUSNAME_LISTENING, BUSNAME_RUNNING)); + + busname_enter_dead(n, BUSNAME_SUCCESS); + return 1; +} + +static int busname_serialize(Unit *u, FILE *f, FDSet *fds) { + BusName *n = BUSNAME(u); + int r; + + assert(n); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", busname_state_to_string(n->state)); + unit_serialize_item(u, f, "result", busname_result_to_string(n->result)); + + if (n->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", PID_FMT, n->control_pid); + + r = unit_serialize_item_fd(u, f, fds, "starter-fd", n->starter_fd); + if (r < 0) + return r; + + return 0; +} + +static int busname_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + BusName *n = BUSNAME(u); + + assert(n); + assert(key); + assert(value); + + if (streq(key, "state")) { + BusNameState state; + + state = busname_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + n->deserialized_state = state; + + } else if (streq(key, "result")) { + BusNameResult f; + + f = busname_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != BUSNAME_SUCCESS) + n->result = f; + + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse control-pid value: %s", value); + else + n->control_pid = pid; + } else if (streq(key, "starter-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse starter fd value: %s", value); + else { + safe_close(n->starter_fd); + n->starter_fd = fdset_remove(fds, fd); + } + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +_pure_ static UnitActiveState busname_active_state(Unit *u) { + assert(u); + + return state_translation_table[BUSNAME(u)->state]; +} + +_pure_ static const char *busname_sub_state_to_string(Unit *u) { + assert(u); + + return busname_state_to_string(BUSNAME(u)->state); +} + +static int busname_peek_message(BusName *n) { + struct kdbus_cmd_recv cmd_recv = { + .size = sizeof(cmd_recv), + .flags = KDBUS_RECV_PEEK, + }; + struct kdbus_cmd_free cmd_free = { + .size = sizeof(cmd_free), + }; + const char *comm = NULL; + struct kdbus_item *d; + struct kdbus_msg *k; + size_t start, ps, sz, delta; + void *p = NULL; + pid_t pid = 0; + int r; + + /* Generate a friendly debug log message about which process + * caused triggering of this bus name. This simply peeks the + * metadata of the first queued message and logs it. */ + + assert(n); + + /* Let's shortcut things a bit, if debug logging is turned off + * anyway. */ + + if (log_get_max_level() < LOG_DEBUG) + return 0; + + r = ioctl(n->starter_fd, KDBUS_CMD_RECV, &cmd_recv); + if (r < 0) { + if (errno == EINTR || errno == EAGAIN) + return 0; + + return log_unit_error_errno(UNIT(n), errno, "Failed to query activation message: %m"); + } + + /* We map as late as possible, and unmap imemdiately after + * use. On 32bit address space is scarce and we want to be + * able to handle a lot of activator connections at the same + * time, and hence shouldn't keep the mmap()s around for + * longer than necessary. */ + + ps = page_size(); + start = (cmd_recv.msg.offset / ps) * ps; + delta = cmd_recv.msg.offset - start; + sz = PAGE_ALIGN(delta + cmd_recv.msg.msg_size); + + p = mmap(NULL, sz, PROT_READ, MAP_SHARED, n->starter_fd, start); + if (p == MAP_FAILED) { + r = log_unit_error_errno(UNIT(n), errno, "Failed to map activation message: %m"); + goto finish; + } + + k = (struct kdbus_msg *) ((uint8_t *) p + delta); + KDBUS_ITEM_FOREACH(d, k, items) { + switch (d->type) { + + case KDBUS_ITEM_PIDS: + pid = d->pids.pid; + break; + + case KDBUS_ITEM_PID_COMM: + comm = d->str; + break; + } + } + + if (pid > 0) + log_unit_debug(UNIT(n), "Activation triggered by process " PID_FMT " (%s)", pid, strna(comm)); + + r = 0; + +finish: + if (p) + (void) munmap(p, sz); + + cmd_free.offset = cmd_recv.msg.offset; + if (ioctl(n->starter_fd, KDBUS_CMD_FREE, &cmd_free) < 0) + log_unit_warning(UNIT(n), "Failed to free peeked message, ignoring: %m"); + + return r; +} + +static int busname_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + BusName *n = userdata; + + assert(n); + assert(fd >= 0); + + if (n->state != BUSNAME_LISTENING) + return 0; + + log_unit_debug(UNIT(n), "Activation request"); + + if (revents != EPOLLIN) { + log_unit_error(UNIT(n), "Got unexpected poll event (0x%x) on starter fd.", revents); + goto fail; + } + + busname_peek_message(n); + busname_enter_running(n); + return 0; +fail: + + busname_enter_dead(n, BUSNAME_FAILURE_RESOURCES); + return 0; +} + +static void busname_sigchld_event(Unit *u, pid_t pid, int code, int status) { + BusName *n = BUSNAME(u); + BusNameResult f; + + assert(n); + assert(pid >= 0); + + if (pid != n->control_pid) + return; + + n->control_pid = 0; + + if (is_clean_exit(code, status, NULL)) + f = BUSNAME_SUCCESS; + else if (code == CLD_EXITED) + f = BUSNAME_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = BUSNAME_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = BUSNAME_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown sigchld code"); + + log_unit_full(u, f == BUSNAME_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, + "Control process exited, code=%s status=%i", sigchld_code_to_string(code), status); + + if (f != BUSNAME_SUCCESS) + n->result = f; + + switch (n->state) { + + case BUSNAME_MAKING: + if (f == BUSNAME_SUCCESS) + busname_enter_listening(n); + else + busname_enter_signal(n, BUSNAME_SIGTERM, f); + break; + + case BUSNAME_SIGTERM: + case BUSNAME_SIGKILL: + busname_enter_dead(n, f); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); +} + +static int busname_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { + BusName *n = BUSNAME(userdata); + + assert(n); + assert(n->timer_event_source == source); + + switch (n->state) { + + case BUSNAME_MAKING: + log_unit_warning(UNIT(n), "Making timed out. Terminating."); + busname_enter_signal(n, BUSNAME_SIGTERM, BUSNAME_FAILURE_TIMEOUT); + break; + + case BUSNAME_SIGTERM: + log_unit_warning(UNIT(n), "Stopping timed out. Killing."); + busname_enter_signal(n, BUSNAME_SIGKILL, BUSNAME_FAILURE_TIMEOUT); + break; + + case BUSNAME_SIGKILL: + log_unit_warning(UNIT(n), "Processes still around after SIGKILL. Ignoring."); + busname_enter_dead(n, BUSNAME_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } + + return 0; +} + +static void busname_reset_failed(Unit *u) { + BusName *n = BUSNAME(u); + + assert(n); + + if (n->state == BUSNAME_FAILED) + busname_set_state(n, BUSNAME_DEAD); + + n->result = BUSNAME_SUCCESS; +} + +static void busname_trigger_notify(Unit *u, Unit *other) { + BusName *n = BUSNAME(u); + + assert(n); + assert(other); + + if (!IN_SET(n->state, BUSNAME_RUNNING, BUSNAME_LISTENING)) + return; + + if (other->start_limit_hit) { + busname_enter_dead(n, BUSNAME_FAILURE_SERVICE_START_LIMIT_HIT); + return; + } + + if (other->load_state != UNIT_LOADED || other->type != UNIT_SERVICE) + return; + + if (IN_SET(SERVICE(other)->state, + SERVICE_DEAD, SERVICE_FAILED, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, + SERVICE_AUTO_RESTART)) + busname_enter_listening(n); + + if (SERVICE(other)->state == SERVICE_RUNNING) + busname_set_state(n, BUSNAME_RUNNING); +} + +static int busname_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + return unit_kill_common(u, who, signo, -1, BUSNAME(u)->control_pid, error); +} + +static int busname_get_timeout(Unit *u, usec_t *timeout) { + BusName *n = BUSNAME(u); + usec_t t; + int r; + + if (!n->timer_event_source) + return 0; + + r = sd_event_source_get_time(n->timer_event_source, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + return 0; + + *timeout = t; + return 1; +} + +static bool busname_supported(void) { + static int supported = -1; + + if (supported < 0) + supported = is_kdbus_available(); + + return supported; +} + +static const char* const busname_result_table[_BUSNAME_RESULT_MAX] = { + [BUSNAME_SUCCESS] = "success", + [BUSNAME_FAILURE_RESOURCES] = "resources", + [BUSNAME_FAILURE_TIMEOUT] = "timeout", + [BUSNAME_FAILURE_EXIT_CODE] = "exit-code", + [BUSNAME_FAILURE_SIGNAL] = "signal", + [BUSNAME_FAILURE_CORE_DUMP] = "core-dump", + [BUSNAME_FAILURE_SERVICE_START_LIMIT_HIT] = "service-start-limit-hit", +}; + +DEFINE_STRING_TABLE_LOOKUP(busname_result, BusNameResult); + +const UnitVTable busname_vtable = { + .object_size = sizeof(BusName), + + .sections = + "Unit\0" + "BusName\0" + "Install\0", + .private_section = "BusName", + + .no_alias = true, + .no_instances = true, + + .init = busname_init, + .done = busname_done, + .load = busname_load, + + .coldplug = busname_coldplug, + + .dump = busname_dump, + + .start = busname_start, + .stop = busname_stop, + + .kill = busname_kill, + + .get_timeout = busname_get_timeout, + + .serialize = busname_serialize, + .deserialize_item = busname_deserialize_item, + + .active_state = busname_active_state, + .sub_state_to_string = busname_sub_state_to_string, + + .sigchld_event = busname_sigchld_event, + + .trigger_notify = busname_trigger_notify, + + .reset_failed = busname_reset_failed, + + .supported = busname_supported, + + .bus_vtable = bus_busname_vtable, + + .status_message_formats = { + .finished_start_job = { + [JOB_DONE] = "Listening on %s.", + [JOB_FAILED] = "Failed to listen on %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Closed %s.", + [JOB_FAILED] = "Failed stopping %s.", + }, + }, +}; diff --git a/src/libcore/busname.h b/src/libcore/busname.h new file mode 100644 index 0000000000..6b6f6c62d4 --- /dev/null +++ b/src/libcore/busname.h @@ -0,0 +1,67 @@ +#pragma once + +/*** + 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 . +***/ + +typedef struct BusName BusName; +typedef struct BusNamePolicy BusNamePolicy; + +#include "unit.h" + +typedef enum BusNameResult { + BUSNAME_SUCCESS, + BUSNAME_FAILURE_RESOURCES, + BUSNAME_FAILURE_TIMEOUT, + BUSNAME_FAILURE_EXIT_CODE, + BUSNAME_FAILURE_SIGNAL, + BUSNAME_FAILURE_CORE_DUMP, + BUSNAME_FAILURE_SERVICE_START_LIMIT_HIT, + _BUSNAME_RESULT_MAX, + _BUSNAME_RESULT_INVALID = -1 +} BusNameResult; + +struct BusName { + Unit meta; + + char *name; + int starter_fd; + + bool activating; + bool accept_fd; + + UnitRef service; + + BusNameState state, deserialized_state; + BusNameResult result; + + usec_t timeout_usec; + + sd_event_source *starter_event_source; + sd_event_source *timer_event_source; + + pid_t control_pid; + + LIST_HEAD(BusNamePolicy, policy); + BusPolicyAccess policy_world; +}; + +extern const UnitVTable busname_vtable; + +const char* busname_result_to_string(BusNameResult i) _const_; +BusNameResult busname_result_from_string(const char *s) _pure_; diff --git a/src/libcore/cgroup.c b/src/libcore/cgroup.c new file mode 100644 index 0000000000..39235a95f6 --- /dev/null +++ b/src/libcore/cgroup.c @@ -0,0 +1,1620 @@ +/*** + 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 . +***/ + +#include +#include + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "cgroup.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" + +#define CGROUP_CPU_QUOTA_PERIOD_USEC ((usec_t) 100 * USEC_PER_MSEC) + +void cgroup_context_init(CGroupContext *c) { + assert(c); + + /* Initialize everything to the kernel defaults, assuming the + * structure is preinitialized to 0 */ + + c->cpu_shares = CGROUP_CPU_SHARES_INVALID; + c->startup_cpu_shares = CGROUP_CPU_SHARES_INVALID; + c->cpu_quota_per_sec_usec = USEC_INFINITY; + + c->memory_limit = (uint64_t) -1; + + c->blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID; + c->startup_blockio_weight = CGROUP_BLKIO_WEIGHT_INVALID; + + c->tasks_max = (uint64_t) -1; +} + +void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a) { + assert(c); + assert(a); + + LIST_REMOVE(device_allow, c->device_allow, a); + free(a->path); + free(a); +} + +void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w) { + assert(c); + assert(w); + + LIST_REMOVE(device_weights, c->blockio_device_weights, w); + free(w->path); + free(w); +} + +void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b) { + assert(c); + assert(b); + + LIST_REMOVE(device_bandwidths, c->blockio_device_bandwidths, b); + free(b->path); + free(b); +} + +void cgroup_context_done(CGroupContext *c) { + assert(c); + + while (c->blockio_device_weights) + cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); + + while (c->blockio_device_bandwidths) + cgroup_context_free_blockio_device_bandwidth(c, c->blockio_device_bandwidths); + + while (c->device_allow) + cgroup_context_free_device_allow(c, c->device_allow); +} + +void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix) { + CGroupBlockIODeviceBandwidth *b; + CGroupBlockIODeviceWeight *w; + CGroupDeviceAllow *a; + char u[FORMAT_TIMESPAN_MAX]; + + assert(c); + assert(f); + + prefix = strempty(prefix); + + fprintf(f, + "%sCPUAccounting=%s\n" + "%sBlockIOAccounting=%s\n" + "%sMemoryAccounting=%s\n" + "%sTasksAccounting=%s\n" + "%sCPUShares=%" PRIu64 "\n" + "%sStartupCPUShares=%" PRIu64 "\n" + "%sCPUQuotaPerSecSec=%s\n" + "%sBlockIOWeight=%" PRIu64 "\n" + "%sStartupBlockIOWeight=%" PRIu64 "\n" + "%sMemoryLimit=%" PRIu64 "\n" + "%sTasksMax=%" PRIu64 "\n" + "%sDevicePolicy=%s\n" + "%sDelegate=%s\n", + prefix, yes_no(c->cpu_accounting), + prefix, yes_no(c->blockio_accounting), + prefix, yes_no(c->memory_accounting), + prefix, yes_no(c->tasks_accounting), + prefix, c->cpu_shares, + prefix, c->startup_cpu_shares, + prefix, format_timespan(u, sizeof(u), c->cpu_quota_per_sec_usec, 1), + prefix, c->blockio_weight, + prefix, c->startup_blockio_weight, + prefix, c->memory_limit, + prefix, c->tasks_max, + prefix, cgroup_device_policy_to_string(c->device_policy), + prefix, yes_no(c->delegate)); + + LIST_FOREACH(device_allow, a, c->device_allow) + fprintf(f, + "%sDeviceAllow=%s %s%s%s\n", + prefix, + a->path, + a->r ? "r" : "", a->w ? "w" : "", a->m ? "m" : ""); + + LIST_FOREACH(device_weights, w, c->blockio_device_weights) + fprintf(f, + "%sBlockIODeviceWeight=%s %" PRIu64, + prefix, + w->path, + w->weight); + + LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { + char buf[FORMAT_BYTES_MAX]; + + fprintf(f, + "%s%s=%s %s\n", + prefix, + b->read ? "BlockIOReadBandwidth" : "BlockIOWriteBandwidth", + b->path, + format_bytes(buf, sizeof(buf), b->bandwidth)); + } +} + +static int lookup_blkio_device(const char *p, dev_t *dev) { + struct stat st; + int r; + + assert(p); + assert(dev); + + r = stat(p, &st); + if (r < 0) + return log_warning_errno(errno, "Couldn't stat device %s: %m", p); + + if (S_ISBLK(st.st_mode)) + *dev = st.st_rdev; + else if (major(st.st_dev) != 0) { + /* If this is not a device node then find the block + * device this file is stored on */ + *dev = st.st_dev; + + /* If this is a partition, try to get the originating + * block device */ + block_get_whole_disk(*dev, dev); + } else { + log_warning("%s is not a block device and file system block device cannot be determined or is not local.", p); + return -ENODEV; + } + + return 0; +} + +static int whitelist_device(const char *path, const char *node, const char *acc) { + char buf[2+DECIMAL_STR_MAX(dev_t)*2+2+4]; + struct stat st; + int r; + + assert(path); + assert(acc); + + if (stat(node, &st) < 0) { + log_warning("Couldn't stat device %s", node); + return -errno; + } + + if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { + log_warning("%s is not a device.", node); + return -ENODEV; + } + + sprintf(buf, + "%c %u:%u %s", + S_ISCHR(st.st_mode) ? 'c' : 'b', + major(st.st_rdev), minor(st.st_rdev), + acc); + + r = cg_set_attribute("devices", path, "devices.allow", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set devices.allow on %s: %m", path); + + return r; +} + +static int whitelist_major(const char *path, const char *name, char type, const char *acc) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + bool good = false; + int r; + + assert(path); + assert(acc); + assert(type == 'b' || type == 'c'); + + f = fopen("/proc/devices", "re"); + if (!f) + return log_warning_errno(errno, "Cannot open /proc/devices to resolve %s (%c): %m", name, type); + + FOREACH_LINE(line, f, goto fail) { + char buf[2+DECIMAL_STR_MAX(unsigned)+3+4], *p, *w; + unsigned maj; + + truncate_nl(line); + + if (type == 'c' && streq(line, "Character devices:")) { + good = true; + continue; + } + + if (type == 'b' && streq(line, "Block devices:")) { + good = true; + continue; + } + + if (isempty(line)) { + good = false; + continue; + } + + if (!good) + continue; + + p = strstrip(line); + + w = strpbrk(p, WHITESPACE); + if (!w) + continue; + *w = 0; + + r = safe_atou(p, &maj); + if (r < 0) + continue; + if (maj <= 0) + continue; + + w++; + w += strspn(w, WHITESPACE); + + if (fnmatch(name, w, 0) != 0) + continue; + + sprintf(buf, + "%c %u:* %s", + type, + maj, + acc); + + r = cg_set_attribute("devices", path, "devices.allow", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set devices.allow on %s: %m", path); + } + + return 0; + +fail: + log_warning_errno(errno, "Failed to read /proc/devices: %m"); + return -errno; +} + +void cgroup_context_apply(CGroupContext *c, CGroupMask mask, const char *path, ManagerState state) { + bool is_root; + int r; + + assert(c); + assert(path); + + if (mask == 0) + return; + + /* Some cgroup attributes are not supported on the root cgroup, + * hence silently ignore */ + is_root = isempty(path) || path_equal(path, "/"); + if (is_root) + /* Make sure we don't try to display messages with an empty path. */ + path = "/"; + + /* We generally ignore errors caused by read-only mounted + * cgroup trees (assuming we are running in a container then), + * and missing cgroups, i.e. EROFS and ENOENT. */ + + if ((mask & CGROUP_MASK_CPU) && !is_root) { + char buf[MAX(DECIMAL_STR_MAX(uint64_t), DECIMAL_STR_MAX(usec_t)) + 1]; + + sprintf(buf, "%" PRIu64 "\n", + IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && c->startup_cpu_shares != CGROUP_CPU_SHARES_INVALID ? c->startup_cpu_shares : + c->cpu_shares != CGROUP_CPU_SHARES_INVALID ? c->cpu_shares : CGROUP_CPU_SHARES_DEFAULT); + r = cg_set_attribute("cpu", path, "cpu.shares", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.shares on %s: %m", path); + + sprintf(buf, USEC_FMT "\n", CGROUP_CPU_QUOTA_PERIOD_USEC); + r = cg_set_attribute("cpu", path, "cpu.cfs_period_us", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.cfs_period_us on %s: %m", path); + + if (c->cpu_quota_per_sec_usec != USEC_INFINITY) { + sprintf(buf, USEC_FMT "\n", c->cpu_quota_per_sec_usec * CGROUP_CPU_QUOTA_PERIOD_USEC / USEC_PER_SEC); + r = cg_set_attribute("cpu", path, "cpu.cfs_quota_us", buf); + } else + r = cg_set_attribute("cpu", path, "cpu.cfs_quota_us", "-1"); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set cpu.cfs_quota_us on %s: %m", path); + } + + if (mask & CGROUP_MASK_BLKIO) { + char buf[MAX(DECIMAL_STR_MAX(uint64_t)+1, + DECIMAL_STR_MAX(dev_t)*2+2+DECIMAL_STR_MAX(uint64_t)+1)]; + CGroupBlockIODeviceWeight *w; + CGroupBlockIODeviceBandwidth *b; + + if (!is_root) { + sprintf(buf, "%" PRIu64 "\n", + IN_SET(state, MANAGER_STARTING, MANAGER_INITIALIZING) && c->startup_blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID ? c->startup_blockio_weight : + c->blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID ? c->blockio_weight : CGROUP_BLKIO_WEIGHT_DEFAULT); + r = cg_set_attribute("blkio", path, "blkio.weight", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set blkio.weight on %s: %m", path); + + /* FIXME: no way to reset this list */ + LIST_FOREACH(device_weights, w, c->blockio_device_weights) { + dev_t dev; + + r = lookup_blkio_device(w->path, &dev); + if (r < 0) + continue; + + sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), w->weight); + r = cg_set_attribute("blkio", path, "blkio.weight_device", buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set blkio.weight_device on %s: %m", path); + } + } + + /* FIXME: no way to reset this list */ + LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { + const char *a; + dev_t dev; + + r = lookup_blkio_device(b->path, &dev); + if (r < 0) + continue; + + a = b->read ? "blkio.throttle.read_bps_device" : "blkio.throttle.write_bps_device"; + + sprintf(buf, "%u:%u %" PRIu64 "\n", major(dev), minor(dev), b->bandwidth); + r = cg_set_attribute("blkio", path, a, buf); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set %s on %s: %m", a, path); + } + } + + if ((mask & CGROUP_MASK_MEMORY) && !is_root) { + if (c->memory_limit != (uint64_t) -1) { + char buf[DECIMAL_STR_MAX(uint64_t) + 1]; + + sprintf(buf, "%" PRIu64 "\n", c->memory_limit); + + if (cg_unified() <= 0) + r = cg_set_attribute("memory", path, "memory.limit_in_bytes", buf); + else + r = cg_set_attribute("memory", path, "memory.max", buf); + + } else { + if (cg_unified() <= 0) + r = cg_set_attribute("memory", path, "memory.limit_in_bytes", "-1"); + else + r = cg_set_attribute("memory", path, "memory.max", "max"); + } + + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set memory.limit_in_bytes/memory.max on %s: %m", path); + } + + if ((mask & CGROUP_MASK_DEVICES) && !is_root) { + CGroupDeviceAllow *a; + + /* Changing the devices list of a populated cgroup + * might result in EINVAL, hence ignore EINVAL + * here. */ + + if (c->device_allow || c->device_policy != CGROUP_AUTO) + r = cg_set_attribute("devices", path, "devices.deny", "a"); + else + r = cg_set_attribute("devices", path, "devices.allow", "a"); + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EINVAL, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to reset devices.list on %s: %m", path); + + if (c->device_policy == CGROUP_CLOSED || + (c->device_policy == CGROUP_AUTO && c->device_allow)) { + static const char auto_devices[] = + "/dev/null\0" "rwm\0" + "/dev/zero\0" "rwm\0" + "/dev/full\0" "rwm\0" + "/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 */ + + const char *x, *y; + + NULSTR_FOREACH_PAIR(x, y, auto_devices) + whitelist_device(path, x, y); + + whitelist_major(path, "pts", 'c', "rw"); + whitelist_major(path, "kdbus", 'c', "rw"); + whitelist_major(path, "kdbus/*", 'c', "rw"); + } + + LIST_FOREACH(device_allow, a, c->device_allow) { + char acc[4]; + unsigned k = 0; + + if (a->r) + acc[k++] = 'r'; + if (a->w) + acc[k++] = 'w'; + if (a->m) + acc[k++] = 'm'; + + if (k == 0) + continue; + + acc[k++] = 0; + + if (startswith(a->path, "/dev/")) + whitelist_device(path, a->path, acc); + else if (startswith(a->path, "block-")) + whitelist_major(path, a->path + 6, 'b', acc); + else if (startswith(a->path, "char-")) + whitelist_major(path, a->path + 5, 'c', acc); + else + log_debug("Ignoring device %s while writing cgroup attribute.", a->path); + } + } + + if ((mask & CGROUP_MASK_PIDS) && !is_root) { + + if (c->tasks_max != (uint64_t) -1) { + char buf[DECIMAL_STR_MAX(uint64_t) + 2]; + + sprintf(buf, "%" PRIu64 "\n", c->tasks_max); + r = cg_set_attribute("pids", path, "pids.max", buf); + } else + r = cg_set_attribute("pids", path, "pids.max", "max"); + + if (r < 0) + log_full_errno(IN_SET(r, -ENOENT, -EROFS, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to set pids.max on %s: %m", path); + } +} + +CGroupMask cgroup_context_get_mask(CGroupContext *c) { + CGroupMask mask = 0; + + /* Figure out which controllers we need */ + + if (c->cpu_accounting || + c->cpu_shares != CGROUP_CPU_SHARES_INVALID || + c->startup_cpu_shares != CGROUP_CPU_SHARES_INVALID || + c->cpu_quota_per_sec_usec != USEC_INFINITY) + mask |= CGROUP_MASK_CPUACCT | CGROUP_MASK_CPU; + + if (c->blockio_accounting || + c->blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID || + c->startup_blockio_weight != CGROUP_BLKIO_WEIGHT_INVALID || + c->blockio_device_weights || + c->blockio_device_bandwidths) + mask |= CGROUP_MASK_BLKIO; + + if (c->memory_accounting || + c->memory_limit != (uint64_t) -1) + mask |= CGROUP_MASK_MEMORY; + + if (c->device_allow || + c->device_policy != CGROUP_AUTO) + mask |= CGROUP_MASK_DEVICES; + + if (c->tasks_accounting || + c->tasks_max != (uint64_t) -1) + mask |= CGROUP_MASK_PIDS; + + return mask; +} + +CGroupMask unit_get_own_mask(Unit *u) { + CGroupContext *c; + + /* Returns the mask of controllers the unit needs for itself */ + + c = unit_get_cgroup_context(u); + if (!c) + return 0; + + /* If delegation is turned on, then turn on all cgroups, + * unless we are on the legacy hierarchy and the process we + * fork into it is known to drop privileges, and hence + * shouldn't get access to the controllers. + * + * Note that on the unified hierarchy it is safe to delegate + * controllers to unprivileged services. */ + + if (c->delegate) { + ExecContext *e; + + e = unit_get_exec_context(u); + if (!e || + exec_context_maintains_privileges(e) || + cg_unified() > 0) + return _CGROUP_MASK_ALL; + } + + return cgroup_context_get_mask(c); +} + +CGroupMask unit_get_members_mask(Unit *u) { + assert(u); + + /* Returns the mask of controllers all of the unit's children + * require, merged */ + + if (u->cgroup_members_mask_valid) + return u->cgroup_members_mask; + + u->cgroup_members_mask = 0; + + if (u->type == UNIT_SLICE) { + Unit *member; + Iterator i; + + SET_FOREACH(member, u->dependencies[UNIT_BEFORE], i) { + + if (member == u) + continue; + + if (UNIT_DEREF(member->slice) != u) + continue; + + u->cgroup_members_mask |= + unit_get_own_mask(member) | + unit_get_members_mask(member); + } + } + + u->cgroup_members_mask_valid = true; + return u->cgroup_members_mask; +} + +CGroupMask unit_get_siblings_mask(Unit *u) { + assert(u); + + /* Returns the mask of controllers all of the unit's siblings + * require, i.e. the members mask of the unit's parent slice + * if there is one. */ + + if (UNIT_ISSET(u->slice)) + return unit_get_members_mask(UNIT_DEREF(u->slice)); + + return unit_get_own_mask(u) | unit_get_members_mask(u); +} + +CGroupMask unit_get_subtree_mask(Unit *u) { + + /* Returns the mask of this subtree, meaning of the group + * itself and its children. */ + + return unit_get_own_mask(u) | unit_get_members_mask(u); +} + +CGroupMask unit_get_target_mask(Unit *u) { + CGroupMask mask; + + /* This returns the cgroup mask of all controllers to enable + * for a specific cgroup, i.e. everything it needs itself, + * plus all that its children need, plus all that its siblings + * need. This is primarily useful on the legacy cgroup + * hierarchy, where we need to duplicate each cgroup in each + * hierarchy that shall be enabled for it. */ + + mask = unit_get_own_mask(u) | unit_get_members_mask(u) | unit_get_siblings_mask(u); + mask &= u->manager->cgroup_supported; + + return mask; +} + +CGroupMask unit_get_enable_mask(Unit *u) { + CGroupMask mask; + + /* This returns the cgroup mask of all controllers to enable + * for the children of a specific cgroup. This is primarily + * useful for the unified cgroup hierarchy, where each cgroup + * controls which controllers are enabled for its children. */ + + mask = unit_get_members_mask(u); + mask &= u->manager->cgroup_supported; + + return mask; +} + +/* Recurse from a unit up through its containing slices, propagating + * mask bits upward. A unit is also member of itself. */ +void unit_update_cgroup_members_masks(Unit *u) { + CGroupMask m; + bool more; + + assert(u); + + /* Calculate subtree mask */ + m = unit_get_subtree_mask(u); + + /* See if anything changed from the previous invocation. If + * not, we're done. */ + if (u->cgroup_subtree_mask_valid && m == u->cgroup_subtree_mask) + return; + + more = + u->cgroup_subtree_mask_valid && + ((m & ~u->cgroup_subtree_mask) != 0) && + ((~m & u->cgroup_subtree_mask) == 0); + + u->cgroup_subtree_mask = m; + u->cgroup_subtree_mask_valid = true; + + if (UNIT_ISSET(u->slice)) { + Unit *s = UNIT_DEREF(u->slice); + + if (more) + /* There's more set now than before. We + * propagate the new mask to the parent's mask + * (not caring if it actually was valid or + * not). */ + + s->cgroup_members_mask |= m; + + else + /* There's less set now than before (or we + * don't know), we need to recalculate + * everything, so let's invalidate the + * parent's members mask */ + + s->cgroup_members_mask_valid = false; + + /* And now make sure that this change also hits our + * grandparents */ + unit_update_cgroup_members_masks(s); + } +} + +static const char *migrate_callback(CGroupMask mask, void *userdata) { + Unit *u = userdata; + + assert(mask != 0); + assert(u); + + while (u) { + if (u->cgroup_path && + u->cgroup_realized && + (u->cgroup_realized_mask & mask) == mask) + return u->cgroup_path; + + u = UNIT_DEREF(u->slice); + } + + return NULL; +} + +char *unit_default_cgroup_path(Unit *u) { + _cleanup_free_ char *escaped = NULL, *slice = NULL; + int r; + + assert(u); + + if (unit_has_name(u, SPECIAL_ROOT_SLICE)) + return strdup(u->manager->cgroup_root); + + if (UNIT_ISSET(u->slice) && !unit_has_name(UNIT_DEREF(u->slice), SPECIAL_ROOT_SLICE)) { + r = cg_slice_to_path(UNIT_DEREF(u->slice)->id, &slice); + if (r < 0) + return NULL; + } + + escaped = cg_escape(u->id); + if (!escaped) + return NULL; + + if (slice) + return strjoin(u->manager->cgroup_root, "/", slice, "/", escaped, NULL); + else + return strjoin(u->manager->cgroup_root, "/", escaped, NULL); +} + +int unit_set_cgroup_path(Unit *u, const char *path) { + _cleanup_free_ char *p = NULL; + int r; + + assert(u); + + if (path) { + p = strdup(path); + if (!p) + return -ENOMEM; + } else + p = NULL; + + if (streq_ptr(u->cgroup_path, p)) + return 0; + + if (p) { + r = hashmap_put(u->manager->cgroup_unit, p, u); + if (r < 0) + return r; + } + + unit_release_cgroup(u); + + u->cgroup_path = p; + p = NULL; + + return 1; +} + +int unit_watch_cgroup(Unit *u) { + _cleanup_free_ char *populated = NULL; + int r; + + assert(u); + + if (!u->cgroup_path) + return 0; + + if (u->cgroup_inotify_wd >= 0) + return 0; + + /* 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"); + if (r == 0) + return 0; + + /* Don't watch the root slice, it's pointless. */ + if (unit_has_name(u, SPECIAL_ROOT_SLICE)) + return 0; + + r = hashmap_ensure_allocated(&u->manager->cgroup_inotify_wd_unit, &trivial_hash_ops); + if (r < 0) + return log_oom(); + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, "cgroup.populated", &populated); + if (r < 0) + return log_oom(); + + u->cgroup_inotify_wd = inotify_add_watch(u->manager->cgroup_inotify_fd, populated, IN_MODIFY); + if (u->cgroup_inotify_wd < 0) { + + if (errno == ENOENT) /* If the directory is already + * gone we don't need to track + * it, so this is not an error */ + return 0; + + return log_unit_error_errno(u, errno, "Failed to add inotify watch descriptor for control group %s: %m", u->cgroup_path); + } + + r = hashmap_put(u->manager->cgroup_inotify_wd_unit, INT_TO_PTR(u->cgroup_inotify_wd), u); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to add inotify watch descriptor to hash map: %m"); + + return 0; +} + +static int unit_create_cgroup( + Unit *u, + CGroupMask target_mask, + CGroupMask enable_mask) { + + CGroupContext *c; + int r; + + assert(u); + + c = unit_get_cgroup_context(u); + if (!c) + return 0; + + if (!u->cgroup_path) { + _cleanup_free_ char *path = NULL; + + path = unit_default_cgroup_path(u); + if (!path) + return log_oom(); + + r = unit_set_cgroup_path(u, path); + if (r == -EEXIST) + return log_unit_error_errno(u, r, "Control group %s exists already.", path); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to set unit's control group path to %s: %m", path); + } + + /* First, create our own group */ + r = cg_create_everywhere(u->manager->cgroup_supported, target_mask, u->cgroup_path); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to create cgroup %s: %m", u->cgroup_path); + + /* Start watching it */ + (void) unit_watch_cgroup(u); + + /* Enable all controllers we need */ + r = cg_enable_everywhere(u->manager->cgroup_supported, enable_mask, u->cgroup_path); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to enable controllers on cgroup %s, ignoring: %m", u->cgroup_path); + + /* Keep track that this is now realized */ + u->cgroup_realized = true; + u->cgroup_realized_mask = target_mask; + + if (u->type != UNIT_SLICE && !c->delegate) { + + /* Then, possibly move things over, but not if + * subgroups may contain processes, which is the case + * for slice and delegation units. */ + r = cg_migrate_everywhere(u->manager->cgroup_supported, u->cgroup_path, u->cgroup_path, migrate_callback, u); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to migrate cgroup from to %s, ignoring: %m", u->cgroup_path); + } + + return 0; +} + +int unit_attach_pids_to_cgroup(Unit *u) { + int r; + assert(u); + + r = unit_realize_cgroup(u); + if (r < 0) + return r; + + r = cg_attach_many_everywhere(u->manager->cgroup_supported, u->cgroup_path, u->pids, migrate_callback, u); + if (r < 0) + return r; + + return 0; +} + +static bool unit_has_mask_realized(Unit *u, CGroupMask target_mask) { + assert(u); + + return u->cgroup_realized && u->cgroup_realized_mask == target_mask; +} + +/* Check if necessary controllers and attributes for a unit are in place. + * + * If so, do nothing. + * If not, create paths, move processes over, and set attributes. + * + * Returns 0 on success and < 0 on failure. */ +static int unit_realize_cgroup_now(Unit *u, ManagerState state) { + CGroupMask target_mask, enable_mask; + int r; + + assert(u); + + if (u->in_cgroup_queue) { + LIST_REMOVE(cgroup_queue, u->manager->cgroup_queue, u); + u->in_cgroup_queue = false; + } + + target_mask = unit_get_target_mask(u); + if (unit_has_mask_realized(u, target_mask)) + return 0; + + /* First, realize parents */ + if (UNIT_ISSET(u->slice)) { + r = unit_realize_cgroup_now(UNIT_DEREF(u->slice), state); + if (r < 0) + return r; + } + + /* And then do the real work */ + enable_mask = unit_get_enable_mask(u); + r = unit_create_cgroup(u, target_mask, enable_mask); + if (r < 0) + return r; + + /* Finally, apply the necessary attributes. */ + cgroup_context_apply(unit_get_cgroup_context(u), target_mask, u->cgroup_path, state); + + return 0; +} + +static void unit_add_to_cgroup_queue(Unit *u) { + + if (u->in_cgroup_queue) + return; + + LIST_PREPEND(cgroup_queue, u->manager->cgroup_queue, u); + u->in_cgroup_queue = true; +} + +unsigned manager_dispatch_cgroup_queue(Manager *m) { + ManagerState state; + unsigned n = 0; + Unit *i; + int r; + + state = manager_state(m); + + while ((i = m->cgroup_queue)) { + assert(i->in_cgroup_queue); + + r = unit_realize_cgroup_now(i, state); + if (r < 0) + log_warning_errno(r, "Failed to realize cgroups for queued unit %s, ignoring: %m", i->id); + + n++; + } + + return n; +} + +static void unit_queue_siblings(Unit *u) { + Unit *slice; + + /* This adds the siblings of the specified unit and the + * siblings of all parent units to the cgroup queue. (But + * neither the specified unit itself nor the parents.) */ + + while ((slice = UNIT_DEREF(u->slice))) { + Iterator i; + Unit *m; + + SET_FOREACH(m, slice->dependencies[UNIT_BEFORE], i) { + if (m == u) + continue; + + /* Skip units that have a dependency on the slice + * but aren't actually in it. */ + if (UNIT_DEREF(m->slice) != slice) + continue; + + /* No point in doing cgroup application for units + * without active processes. */ + if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(m))) + continue; + + /* If the unit doesn't need any new controllers + * and has current ones realized, it doesn't need + * any changes. */ + if (unit_has_mask_realized(m, unit_get_target_mask(m))) + continue; + + unit_add_to_cgroup_queue(m); + } + + u = slice; + } +} + +int unit_realize_cgroup(Unit *u) { + assert(u); + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return 0; + + /* So, here's the deal: when realizing the cgroups for this + * unit, we need to first create all parents, but there's more + * actually: for the weight-based controllers we also need to + * make sure that all our siblings (i.e. units that are in the + * same slice as we are) have cgroups, too. Otherwise, things + * would become very uneven as each of their processes would + * get as much resources as all our group together. This call + * will synchronously create the parent cgroups, but will + * defer work on the siblings to the next event loop + * iteration. */ + + /* Add all sibling slices to the cgroup queue. */ + unit_queue_siblings(u); + + /* And realize this one now (and apply the values) */ + return unit_realize_cgroup_now(u, manager_state(u->manager)); +} + +void unit_release_cgroup(Unit *u) { + assert(u); + + /* Forgets all cgroup details for this cgroup */ + + if (u->cgroup_path) { + (void) hashmap_remove(u->manager->cgroup_unit, u->cgroup_path); + u->cgroup_path = mfree(u->cgroup_path); + } + + if (u->cgroup_inotify_wd >= 0) { + if (inotify_rm_watch(u->manager->cgroup_inotify_fd, u->cgroup_inotify_wd) < 0) + log_unit_debug_errno(u, errno, "Failed to remove cgroup inotify watch %i for %s, ignoring", u->cgroup_inotify_wd, u->id); + + (void) hashmap_remove(u->manager->cgroup_inotify_wd_unit, INT_TO_PTR(u->cgroup_inotify_wd)); + u->cgroup_inotify_wd = -1; + } +} + +void unit_prune_cgroup(Unit *u) { + int r; + bool is_root_slice; + + assert(u); + + /* Removes the cgroup, if empty and possible, and stops watching it. */ + + if (!u->cgroup_path) + return; + + is_root_slice = unit_has_name(u, SPECIAL_ROOT_SLICE); + + r = cg_trim_everywhere(u->manager->cgroup_supported, u->cgroup_path, !is_root_slice); + if (r < 0) { + log_debug_errno(r, "Failed to destroy cgroup %s, ignoring: %m", u->cgroup_path); + return; + } + + if (is_root_slice) + return; + + unit_release_cgroup(u); + + u->cgroup_realized = false; + u->cgroup_realized_mask = 0; +} + +int unit_search_main_pid(Unit *u, pid_t *ret) { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid = 0, npid, mypid; + int r; + + assert(u); + assert(ret); + + if (!u->cgroup_path) + return -ENXIO; + + r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, &f); + if (r < 0) + return r; + + mypid = getpid(); + while (cg_read_pid(f, &npid) > 0) { + pid_t ppid; + + if (npid == pid) + continue; + + /* Ignore processes that aren't our kids */ + if (get_process_ppid(npid, &ppid) >= 0 && ppid != mypid) + continue; + + if (pid != 0) + /* Dang, there's more than one daemonized PID + in this group, so we don't know what process + is the main process. */ + + return -ENODATA; + + pid = npid; + } + + *ret = pid; + return 0; +} + +static int unit_watch_pids_in_path(Unit *u, const char *path) { + _cleanup_closedir_ DIR *d = NULL; + _cleanup_fclose_ FILE *f = NULL; + int ret = 0, r; + + assert(u); + assert(path); + + r = cg_enumerate_processes(SYSTEMD_CGROUP_CONTROLLER, path, &f); + if (r < 0) + ret = r; + else { + pid_t pid; + + while ((r = cg_read_pid(f, &pid)) > 0) { + r = unit_watch_pid(u, pid); + if (r < 0 && ret >= 0) + ret = r; + } + + if (r < 0 && ret >= 0) + ret = r; + } + + r = cg_enumerate_subgroups(SYSTEMD_CGROUP_CONTROLLER, path, &d); + if (r < 0) { + if (ret >= 0) + ret = r; + } else { + char *fn; + + while ((r = cg_read_subgroup(d, &fn)) > 0) { + _cleanup_free_ char *p = NULL; + + p = strjoin(path, "/", fn, NULL); + free(fn); + + if (!p) + return -ENOMEM; + + r = unit_watch_pids_in_path(u, p); + if (r < 0 && ret >= 0) + ret = r; + } + + if (r < 0 && ret >= 0) + ret = r; + } + + return ret; +} + +int unit_watch_all_pids(Unit *u) { + assert(u); + + /* Adds all PIDs from our cgroup to the set of PIDs we + * watch. This is a fallback logic for cases where we do not + * get reliable cgroup empty notifications: we try to use + * SIGCHLD as replacement. */ + + if (!u->cgroup_path) + return -ENOENT; + + if (cg_unified() > 0) /* On unified we can use proper notifications */ + return 0; + + return unit_watch_pids_in_path(u, u->cgroup_path); +} + +int unit_notify_cgroup_empty(Unit *u) { + int r; + + assert(u); + + if (!u->cgroup_path) + return 0; + + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path); + if (r <= 0) + return r; + + unit_add_to_gc_queue(u); + + if (UNIT_VTABLE(u)->notify_cgroup_empty) + UNIT_VTABLE(u)->notify_cgroup_empty(u); + + return 0; +} + +static int on_cgroup_inotify_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + + assert(s); + assert(fd >= 0); + assert(m); + + for (;;) { + union inotify_event_buffer buffer; + struct inotify_event *e; + ssize_t l; + + l = read(fd, &buffer, sizeof(buffer)); + if (l < 0) { + if (errno == EINTR || errno == EAGAIN) + return 0; + + return log_error_errno(errno, "Failed to read control group inotify events: %m"); + } + + FOREACH_INOTIFY_EVENT(e, buffer, l) { + Unit *u; + + if (e->wd < 0) + /* Queue overflow has no watch descriptor */ + continue; + + if (e->mask & IN_IGNORED) + /* The watch was just removed */ + continue; + + u = hashmap_get(m->cgroup_inotify_wd_unit, INT_TO_PTR(e->wd)); + if (!u) /* Not that inotify might deliver + * events for a watch even after it + * was removed, because it was queued + * before the removal. Let's ignore + * this here safely. */ + continue; + + (void) unit_notify_cgroup_empty(u); + } + } +} + +int manager_setup_cgroup(Manager *m) { + _cleanup_free_ char *path = NULL; + CGroupController c; + int r, unified; + char *e; + + assert(m); + + /* 1. Determine hierarchy */ + m->cgroup_root = mfree(m->cgroup_root); + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &m->cgroup_root); + if (r < 0) + return log_error_errno(r, "Cannot determine cgroup we are running in: %m"); + + /* Chop off the init scope, if we are already located in it */ + e = endswith(m->cgroup_root, "/" SPECIAL_INIT_SCOPE); + + /* LEGACY: Also chop off the system slice if we are in + * it. This is to support live upgrades from older systemd + * versions where PID 1 was moved there. Also see + * cg_get_root_path(). */ + if (!e && m->running_as == MANAGER_SYSTEM) { + e = endswith(m->cgroup_root, "/" SPECIAL_SYSTEM_SLICE); + if (!e) + e = endswith(m->cgroup_root, "/system"); /* even more legacy */ + } + if (e) + *e = 0; + + /* And make sure to store away the root value without trailing + * slash, even for the root dir, so that we can easily prepend + * it everywhere. */ + while ((e = endswith(m->cgroup_root, "/"))) + *e = 0; + + /* 2. Show data */ + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, NULL, &path); + if (r < 0) + return log_error_errno(r, "Cannot find cgroup mount point: %m"); + + unified = cg_unified(); + if (unified < 0) + return log_error_errno(r, "Couldn't determine if we are running in the unified hierarchy: %m"); + if (unified > 0) + log_debug("Unified cgroup hierarchy is located at %s.", path); + else + log_debug("Using cgroup controller " SYSTEMD_CGROUP_CONTROLLER ". File system hierarchy is at %s.", path); + + if (!m->test_run) { + const char *scope_path; + + /* 3. Install agent */ + if (unified) { + + /* In the unified hierarchy we can can get + * cgroup empty notifications via inotify. */ + + m->cgroup_inotify_event_source = sd_event_source_unref(m->cgroup_inotify_event_source); + safe_close(m->cgroup_inotify_fd); + + m->cgroup_inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (m->cgroup_inotify_fd < 0) + return log_error_errno(errno, "Failed to create control group inotify object: %m"); + + r = sd_event_add_io(m->event, &m->cgroup_inotify_event_source, m->cgroup_inotify_fd, EPOLLIN, on_cgroup_inotify_event, m); + if (r < 0) + return log_error_errno(r, "Failed to watch control group inotify object: %m"); + + r = sd_event_source_set_priority(m->cgroup_inotify_event_source, SD_EVENT_PRIORITY_IDLE - 5); + if (r < 0) + return log_error_errno(r, "Failed to set priority of inotify event source: %m"); + + (void) sd_event_source_set_description(m->cgroup_inotify_event_source, "cgroup-inotify"); + + } else if (m->running_as == MANAGER_SYSTEM) { + + /* On the legacy hierarchy we only get + * notifications via cgroup agents. (Which + * isn't really reliable, since it does not + * generate events when control groups with + * children run empty. */ + + r = cg_install_release_agent(SYSTEMD_CGROUP_CONTROLLER, SYSTEMD_CGROUP_AGENT_PATH); + if (r < 0) + log_warning_errno(r, "Failed to install release agent, ignoring: %m"); + else if (r > 0) + log_debug("Installed release agent."); + else if (r == 0) + log_debug("Release agent already installed."); + } + + /* 4. Make sure we are in the special "init.scope" unit in the root slice. */ + scope_path = strjoina(m->cgroup_root, "/" SPECIAL_INIT_SCOPE); + r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, scope_path, 0); + if (r < 0) + return log_error_errno(r, "Failed to create %s control group: %m", scope_path); + + /* 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); + if (r < 0) + log_warning_errno(r, "Couldn't move remaining userspace processes, ignoring: %m"); + + /* 5. And pin it, so that it cannot be unmounted */ + safe_close(m->pin_cgroupfs_fd); + m->pin_cgroupfs_fd = open(path, O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY|O_NONBLOCK); + if (m->pin_cgroupfs_fd < 0) + return log_error_errno(errno, "Failed to open pin file: %m"); + + /* 6. Always enable hierarchical support if it exists... */ + if (!unified) + (void) cg_set_attribute("memory", "/", "memory.use_hierarchy", "1"); + } + + /* 7. Figure out which controllers are supported */ + r = cg_mask_supported(&m->cgroup_supported); + if (r < 0) + 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)); + + return 0; +} + +void manager_shutdown_cgroup(Manager *m, bool delete) { + assert(m); + + /* We can't really delete the group, since we are in it. But + * let's trim it. */ + if (delete && m->cgroup_root) + (void) cg_trim(SYSTEMD_CGROUP_CONTROLLER, m->cgroup_root, false); + + m->cgroup_inotify_wd_unit = hashmap_free(m->cgroup_inotify_wd_unit); + + m->cgroup_inotify_event_source = sd_event_source_unref(m->cgroup_inotify_event_source); + m->cgroup_inotify_fd = safe_close(m->cgroup_inotify_fd); + + m->pin_cgroupfs_fd = safe_close(m->pin_cgroupfs_fd); + + m->cgroup_root = mfree(m->cgroup_root); +} + +Unit* manager_get_unit_by_cgroup(Manager *m, const char *cgroup) { + char *p; + Unit *u; + + assert(m); + assert(cgroup); + + u = hashmap_get(m->cgroup_unit, cgroup); + if (u) + return u; + + p = strdupa(cgroup); + for (;;) { + char *e; + + e = strrchr(p, '/'); + if (!e || e == p) + return hashmap_get(m->cgroup_unit, SPECIAL_ROOT_SLICE); + + *e = 0; + + u = hashmap_get(m->cgroup_unit, p); + if (u) + return u; + } +} + +Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid) { + _cleanup_free_ char *cgroup = NULL; + int r; + + assert(m); + + if (pid <= 0) + return NULL; + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup); + if (r < 0) + return NULL; + + return manager_get_unit_by_cgroup(m, cgroup); +} + +Unit *manager_get_unit_by_pid(Manager *m, pid_t pid) { + Unit *u; + + assert(m); + + if (pid <= 0) + return NULL; + + if (pid == 1) + return hashmap_get(m->units, SPECIAL_INIT_SCOPE); + + u = hashmap_get(m->watch_pids1, PID_TO_PTR(pid)); + if (u) + return u; + + u = hashmap_get(m->watch_pids2, PID_TO_PTR(pid)); + if (u) + return u; + + return manager_get_unit_by_pid_cgroup(m, pid); +} + +int manager_notify_cgroup_empty(Manager *m, const char *cgroup) { + Unit *u; + + assert(m); + assert(cgroup); + + u = manager_get_unit_by_cgroup(m, cgroup); + if (!u) + return 0; + + return unit_notify_cgroup_empty(u); +} + +int unit_get_memory_current(Unit *u, uint64_t *ret) { + _cleanup_free_ char *v = NULL; + int r; + + assert(u); + assert(ret); + + if (!u->cgroup_path) + return -ENODATA; + + if ((u->cgroup_realized_mask & CGROUP_MASK_MEMORY) == 0) + return -ENODATA; + + if (cg_unified() <= 0) + r = cg_get_attribute("memory", u->cgroup_path, "memory.usage_in_bytes", &v); + else + r = cg_get_attribute("memory", u->cgroup_path, "memory.current", &v); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + + return safe_atou64(v, ret); +} + +int unit_get_tasks_current(Unit *u, uint64_t *ret) { + _cleanup_free_ char *v = NULL; + int r; + + assert(u); + assert(ret); + + if (!u->cgroup_path) + return -ENODATA; + + if ((u->cgroup_realized_mask & CGROUP_MASK_PIDS) == 0) + return -ENODATA; + + r = cg_get_attribute("pids", u->cgroup_path, "pids.current", &v); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + + return safe_atou64(v, ret); +} + +static int unit_get_cpu_usage_raw(Unit *u, nsec_t *ret) { + _cleanup_free_ char *v = NULL; + uint64_t ns; + int r; + + assert(u); + assert(ret); + + if (!u->cgroup_path) + return -ENODATA; + + if ((u->cgroup_realized_mask & CGROUP_MASK_CPUACCT) == 0) + return -ENODATA; + + r = cg_get_attribute("cpuacct", u->cgroup_path, "cpuacct.usage", &v); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + + r = safe_atou64(v, &ns); + if (r < 0) + return r; + + *ret = ns; + return 0; +} + +int unit_get_cpu_usage(Unit *u, nsec_t *ret) { + nsec_t ns; + int r; + + r = unit_get_cpu_usage_raw(u, &ns); + if (r < 0) + return r; + + if (ns > u->cpuacct_usage_base) + ns -= u->cpuacct_usage_base; + else + ns = 0; + + *ret = ns; + return 0; +} + +int unit_reset_cpu_usage(Unit *u) { + nsec_t ns; + int r; + + assert(u); + + r = unit_get_cpu_usage_raw(u, &ns); + if (r < 0) { + u->cpuacct_usage_base = 0; + return r; + } + + u->cpuacct_usage_base = ns; + return 0; +} + +bool unit_cgroup_delegate(Unit *u) { + CGroupContext *c; + + assert(u); + + c = unit_get_cgroup_context(u); + if (!c) + return false; + + return c->delegate; +} + +void unit_invalidate_cgroup(Unit *u, CGroupMask m) { + assert(u); + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return; + + if (m == 0) + return; + + if ((u->cgroup_realized_mask & m) == 0) + return; + + u->cgroup_realized_mask &= ~m; + unit_add_to_cgroup_queue(u); +} + +void manager_invalidate_startup_units(Manager *m) { + Iterator i; + Unit *u; + + assert(m); + + SET_FOREACH(u, m->startup_units, i) + unit_invalidate_cgroup(u, CGROUP_MASK_CPU|CGROUP_MASK_BLKIO); +} + +static const char* const cgroup_device_policy_table[_CGROUP_DEVICE_POLICY_MAX] = { + [CGROUP_AUTO] = "auto", + [CGROUP_CLOSED] = "closed", + [CGROUP_STRICT] = "strict", +}; + +DEFINE_STRING_TABLE_LOOKUP(cgroup_device_policy, CGroupDevicePolicy); diff --git a/src/libcore/cgroup.h b/src/libcore/cgroup.h new file mode 100644 index 0000000000..360bbca30f --- /dev/null +++ b/src/libcore/cgroup.h @@ -0,0 +1,155 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include "list.h" +#include "time-util.h" + +typedef struct CGroupContext CGroupContext; +typedef struct CGroupDeviceAllow CGroupDeviceAllow; +typedef struct CGroupBlockIODeviceWeight CGroupBlockIODeviceWeight; +typedef struct CGroupBlockIODeviceBandwidth CGroupBlockIODeviceBandwidth; + +typedef enum CGroupDevicePolicy { + + /* When devices listed, will allow those, plus built-in ones, + if none are listed will allow everything. */ + CGROUP_AUTO, + + /* Everything forbidden, except built-in ones and listed ones. */ + CGROUP_CLOSED, + + /* Everythings forbidden, except for the listed devices */ + CGROUP_STRICT, + + _CGROUP_DEVICE_POLICY_MAX, + _CGROUP_DEVICE_POLICY_INVALID = -1 +} CGroupDevicePolicy; + +struct CGroupDeviceAllow { + LIST_FIELDS(CGroupDeviceAllow, device_allow); + char *path; + bool r:1; + bool w:1; + bool m:1; +}; + +struct CGroupBlockIODeviceWeight { + LIST_FIELDS(CGroupBlockIODeviceWeight, device_weights); + char *path; + uint64_t weight; +}; + +struct CGroupBlockIODeviceBandwidth { + LIST_FIELDS(CGroupBlockIODeviceBandwidth, device_bandwidths); + char *path; + uint64_t bandwidth; + bool read; +}; + +struct CGroupContext { + bool cpu_accounting; + bool blockio_accounting; + bool memory_accounting; + bool tasks_accounting; + + uint64_t cpu_shares; + uint64_t startup_cpu_shares; + usec_t cpu_quota_per_sec_usec; + + uint64_t blockio_weight; + uint64_t startup_blockio_weight; + LIST_HEAD(CGroupBlockIODeviceWeight, blockio_device_weights); + LIST_HEAD(CGroupBlockIODeviceBandwidth, blockio_device_bandwidths); + + uint64_t memory_limit; + + CGroupDevicePolicy device_policy; + LIST_HEAD(CGroupDeviceAllow, device_allow); + + uint64_t tasks_max; + + bool delegate; +}; + +#include "cgroup-util.h" +#include "unit.h" + +void cgroup_context_init(CGroupContext *c); +void cgroup_context_done(CGroupContext *c); +void cgroup_context_dump(CGroupContext *c, FILE* f, const char *prefix); +void cgroup_context_apply(CGroupContext *c, CGroupMask mask, const char *path, ManagerState state); + +CGroupMask cgroup_context_get_mask(CGroupContext *c); + +void cgroup_context_free_device_allow(CGroupContext *c, CGroupDeviceAllow *a); +void cgroup_context_free_blockio_device_weight(CGroupContext *c, CGroupBlockIODeviceWeight *w); +void cgroup_context_free_blockio_device_bandwidth(CGroupContext *c, CGroupBlockIODeviceBandwidth *b); + +CGroupMask unit_get_own_mask(Unit *u); +CGroupMask unit_get_siblings_mask(Unit *u); +CGroupMask unit_get_members_mask(Unit *u); +CGroupMask unit_get_subtree_mask(Unit *u); + +CGroupMask unit_get_target_mask(Unit *u); +CGroupMask unit_get_enable_mask(Unit *u); + +void unit_update_cgroup_members_masks(Unit *u); + +char *unit_default_cgroup_path(Unit *u); +int unit_set_cgroup_path(Unit *u, const char *path); + +int unit_realize_cgroup(Unit *u); +void unit_release_cgroup(Unit *u); +void unit_prune_cgroup(Unit *u); +int unit_watch_cgroup(Unit *u); + +int unit_attach_pids_to_cgroup(Unit *u); + +int manager_setup_cgroup(Manager *m); +void manager_shutdown_cgroup(Manager *m, bool delete); + +unsigned manager_dispatch_cgroup_queue(Manager *m); + +Unit *manager_get_unit_by_cgroup(Manager *m, const char *cgroup); +Unit *manager_get_unit_by_pid_cgroup(Manager *m, pid_t pid); +Unit* manager_get_unit_by_pid(Manager *m, pid_t pid); + +int unit_search_main_pid(Unit *u, pid_t *ret); +int unit_watch_all_pids(Unit *u); + +int unit_get_memory_current(Unit *u, uint64_t *ret); +int unit_get_tasks_current(Unit *u, uint64_t *ret); +int unit_get_cpu_usage(Unit *u, nsec_t *ret); +int unit_reset_cpu_usage(Unit *u); + +bool unit_cgroup_delegate(Unit *u); + +int unit_notify_cgroup_empty(Unit *u); +int manager_notify_cgroup_empty(Manager *m, const char *group); + +void unit_invalidate_cgroup(Unit *u, CGroupMask m); + +void manager_invalidate_startup_units(Manager *m); + +const char* cgroup_device_policy_to_string(CGroupDevicePolicy i) _const_; +CGroupDevicePolicy cgroup_device_policy_from_string(const char *s) _pure_; diff --git a/src/libcore/dbus-automount.c b/src/libcore/dbus-automount.c new file mode 100644 index 0000000000..b2806ad86f --- /dev/null +++ b/src/libcore/dbus-automount.c @@ -0,0 +1,34 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "automount.h" +#include "bus-util.h" +#include "dbus-automount.h" +#include "string-util.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, automount_result, AutomountResult); + +const sd_bus_vtable bus_automount_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Where", "s", NULL, offsetof(Automount, where), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Automount, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Automount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("TimeoutIdleUSec", "t", bus_property_get_usec, offsetof(Automount, timeout_idle_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_VTABLE_END +}; diff --git a/src/libcore/dbus-automount.h b/src/libcore/dbus-automount.h new file mode 100644 index 0000000000..7b51eb973a --- /dev/null +++ b/src/libcore/dbus-automount.h @@ -0,0 +1,23 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + + +extern const sd_bus_vtable bus_automount_vtable[]; diff --git a/src/libcore/dbus-busname.c b/src/libcore/dbus-busname.c new file mode 100644 index 0000000000..cf816ba15b --- /dev/null +++ b/src/libcore/dbus-busname.c @@ -0,0 +1,37 @@ +/*** + 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 . +***/ + +#include "bus-util.h" +#include "busname.h" +#include "dbus-busname.h" +#include "string-util.h" +#include "unit.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, busname_result, BusNameResult); + +const sd_bus_vtable bus_busname_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Name", "s", NULL, offsetof(BusName, name), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(BusName, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(BusName, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(BusName, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Activating", "b", bus_property_get_bool, offsetof(BusName, activating), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("AcceptFileDescriptors", "b", bus_property_get_bool, offsetof(BusName, accept_fd), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_VTABLE_END +}; diff --git a/src/libcore/dbus-busname.h b/src/libcore/dbus-busname.h new file mode 100644 index 0000000000..8643d1a404 --- /dev/null +++ b/src/libcore/dbus-busname.h @@ -0,0 +1,23 @@ +#pragma once + +/*** + 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 . +***/ + + +extern const sd_bus_vtable bus_busname_vtable[]; diff --git a/src/libcore/dbus-cgroup.c b/src/libcore/dbus-cgroup.c new file mode 100644 index 0000000000..859d155ec1 --- /dev/null +++ b/src/libcore/dbus-cgroup.c @@ -0,0 +1,699 @@ +/*** + 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 . +***/ + +#include "alloc-util.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "cgroup.h" +#include "dbus-cgroup.h" +#include "fd-util.h" +#include "fileio.h" +#include "path-util.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_cgroup_device_policy, cgroup_device_policy, CGroupDevicePolicy); + +static int property_get_blockio_device_weight( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + CGroupContext *c = userdata; + CGroupBlockIODeviceWeight *w; + int r; + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'a', "(st)"); + if (r < 0) + return r; + + LIST_FOREACH(device_weights, w, c->blockio_device_weights) { + r = sd_bus_message_append(reply, "(st)", w->path, w->weight); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_blockio_device_bandwidths( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + CGroupContext *c = userdata; + CGroupBlockIODeviceBandwidth *b; + int r; + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'a', "(st)"); + if (r < 0) + return r; + + LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { + + if (streq(property, "BlockIOReadBandwidth") != b->read) + continue; + + r = sd_bus_message_append(reply, "(st)", b->path, b->bandwidth); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_device_allow( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + CGroupContext *c = userdata; + CGroupDeviceAllow *a; + int r; + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'a', "(ss)"); + if (r < 0) + return r; + + LIST_FOREACH(device_allow, a, c->device_allow) { + unsigned k = 0; + char rwm[4]; + + if (a->r) + rwm[k++] = 'r'; + if (a->w) + rwm[k++] = 'w'; + if (a->m) + rwm[k++] = 'm'; + + rwm[k] = 0; + + r = sd_bus_message_append(reply, "(ss)", a->path, rwm); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +const sd_bus_vtable bus_cgroup_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Delegate", "b", bus_property_get_bool, offsetof(CGroupContext, delegate), 0), + SD_BUS_PROPERTY("CPUAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, cpu_accounting), 0), + SD_BUS_PROPERTY("CPUShares", "t", NULL, offsetof(CGroupContext, cpu_shares), 0), + SD_BUS_PROPERTY("StartupCPUShares", "t", NULL, offsetof(CGroupContext, startup_cpu_shares), 0), + SD_BUS_PROPERTY("CPUQuotaPerSecUSec", "t", bus_property_get_usec, offsetof(CGroupContext, cpu_quota_per_sec_usec), 0), + SD_BUS_PROPERTY("BlockIOAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, blockio_accounting), 0), + SD_BUS_PROPERTY("BlockIOWeight", "t", NULL, offsetof(CGroupContext, blockio_weight), 0), + SD_BUS_PROPERTY("StartupBlockIOWeight", "t", NULL, offsetof(CGroupContext, startup_blockio_weight), 0), + SD_BUS_PROPERTY("BlockIODeviceWeight", "a(st)", property_get_blockio_device_weight, 0, 0), + SD_BUS_PROPERTY("BlockIOReadBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0), + SD_BUS_PROPERTY("BlockIOWriteBandwidth", "a(st)", property_get_blockio_device_bandwidths, 0, 0), + SD_BUS_PROPERTY("MemoryAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, memory_accounting), 0), + SD_BUS_PROPERTY("MemoryLimit", "t", NULL, offsetof(CGroupContext, memory_limit), 0), + SD_BUS_PROPERTY("DevicePolicy", "s", property_get_cgroup_device_policy, offsetof(CGroupContext, device_policy), 0), + SD_BUS_PROPERTY("DeviceAllow", "a(ss)", property_get_device_allow, 0, 0), + SD_BUS_PROPERTY("TasksAccounting", "b", bus_property_get_bool, offsetof(CGroupContext, tasks_accounting), 0), + SD_BUS_PROPERTY("TasksMax", "t", NULL, offsetof(CGroupContext, tasks_max), 0), + SD_BUS_VTABLE_END +}; + +static int bus_cgroup_set_transient_property( + Unit *u, + CGroupContext *c, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(u); + assert(c); + assert(name); + assert(message); + + if (streq(name, "Delegate")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->delegate = b; + unit_write_drop_in_private(u, mode, name, b ? "Delegate=yes" : "Delegate=no"); + } + + return 1; + } + + return 0; +} + +int bus_cgroup_set_property( + Unit *u, + CGroupContext *c, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(u); + assert(c); + assert(name); + assert(message); + + if (streq(name, "CPUAccounting")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->cpu_accounting = b; + unit_invalidate_cgroup(u, CGROUP_MASK_CPUACCT|CGROUP_MASK_CPU); + unit_write_drop_in_private(u, mode, name, b ? "CPUAccounting=yes" : "CPUAccounting=no"); + } + + return 1; + + } else if (streq(name, "CPUShares")) { + uint64_t shares; + + r = sd_bus_message_read(message, "t", &shares); + if (r < 0) + return r; + + if (!CGROUP_CPU_SHARES_IS_OK(shares)) + return sd_bus_error_set_errnof(error, EINVAL, "CPUShares value out of range"); + + if (mode != UNIT_CHECK) { + c->cpu_shares = shares; + unit_invalidate_cgroup(u, CGROUP_MASK_CPU); + + if (shares == CGROUP_CPU_SHARES_INVALID) + unit_write_drop_in_private(u, mode, name, "CPUShares="); + else + unit_write_drop_in_private_format(u, mode, name, "CPUShares=%" PRIu64, shares); + } + + return 1; + + } else if (streq(name, "StartupCPUShares")) { + uint64_t shares; + + r = sd_bus_message_read(message, "t", &shares); + if (r < 0) + return r; + + if (!CGROUP_CPU_SHARES_IS_OK(shares)) + return sd_bus_error_set_errnof(error, EINVAL, "StartupCPUShares value out of range"); + + if (mode != UNIT_CHECK) { + c->startup_cpu_shares = shares; + unit_invalidate_cgroup(u, CGROUP_MASK_CPU); + + if (shares == CGROUP_CPU_SHARES_INVALID) + unit_write_drop_in_private(u, mode, name, "StartupCPUShares="); + else + unit_write_drop_in_private_format(u, mode, name, "StartupCPUShares=%" PRIu64, shares); + } + + return 1; + + } else if (streq(name, "CPUQuotaPerSecUSec")) { + uint64_t u64; + + r = sd_bus_message_read(message, "t", &u64); + if (r < 0) + return r; + + if (u64 <= 0) + return sd_bus_error_set_errnof(error, EINVAL, "CPUQuotaPerSecUSec value out of range"); + + if (mode != UNIT_CHECK) { + c->cpu_quota_per_sec_usec = u64; + unit_invalidate_cgroup(u, CGROUP_MASK_CPU); + unit_write_drop_in_private_format(u, mode, "CPUQuota", "CPUQuota=%0.f%%", (double) (c->cpu_quota_per_sec_usec / 10000)); + } + + return 1; + + } else if (streq(name, "BlockIOAccounting")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->blockio_accounting = b; + unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); + unit_write_drop_in_private(u, mode, name, b ? "BlockIOAccounting=yes" : "BlockIOAccounting=no"); + } + + return 1; + + } else if (streq(name, "BlockIOWeight")) { + uint64_t weight; + + r = sd_bus_message_read(message, "t", &weight); + if (r < 0) + return r; + + if (!CGROUP_BLKIO_WEIGHT_IS_OK(weight)) + return sd_bus_error_set_errnof(error, EINVAL, "BlockIOWeight value out of range"); + + if (mode != UNIT_CHECK) { + c->blockio_weight = weight; + unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); + + if (weight == CGROUP_BLKIO_WEIGHT_INVALID) + unit_write_drop_in_private(u, mode, name, "BlockIOWeight="); + else + unit_write_drop_in_private_format(u, mode, name, "BlockIOWeight=%" PRIu64, weight); + } + + return 1; + + } else if (streq(name, "StartupBlockIOWeight")) { + uint64_t weight; + + r = sd_bus_message_read(message, "t", &weight); + if (r < 0) + return r; + + if (CGROUP_BLKIO_WEIGHT_IS_OK(weight)) + return sd_bus_error_set_errnof(error, EINVAL, "StartupBlockIOWeight value out of range"); + + if (mode != UNIT_CHECK) { + c->startup_blockio_weight = weight; + unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); + + if (weight == CGROUP_BLKIO_WEIGHT_INVALID) + unit_write_drop_in_private(u, mode, name, "StartupBlockIOWeight="); + else + unit_write_drop_in_private_format(u, mode, name, "StartupBlockIOWeight=%" PRIu64, weight); + } + + return 1; + + } else if (streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth")) { + const char *path; + bool read = true; + unsigned n = 0; + uint64_t u64; + + if (streq(name, "BlockIOWriteBandwidth")) + read = false; + + r = sd_bus_message_enter_container(message, 'a', "(st)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "(st)", &path, &u64)) > 0) { + + if (mode != UNIT_CHECK) { + CGroupBlockIODeviceBandwidth *a = NULL, *b; + + LIST_FOREACH(device_bandwidths, b, c->blockio_device_bandwidths) { + if (path_equal(path, b->path) && read == b->read) { + a = b; + break; + } + } + + if (!a) { + a = new0(CGroupBlockIODeviceBandwidth, 1); + if (!a) + return -ENOMEM; + + a->read = read; + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + + LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, a); + } + + a->bandwidth = u64; + } + + n++; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + CGroupBlockIODeviceBandwidth *a, *next; + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + size_t size = 0; + + if (n == 0) { + LIST_FOREACH_SAFE(device_bandwidths, a, next, c->blockio_device_bandwidths) + if (a->read == read) + cgroup_context_free_blockio_device_bandwidth(c, a); + } + + unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + if (read) { + fputs("BlockIOReadBandwidth=\n", f); + LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) + if (a->read) + fprintf(f, "BlockIOReadBandwidth=%s %" PRIu64 "\n", a->path, a->bandwidth); + } else { + fputs("BlockIOWriteBandwidth=\n", f); + LIST_FOREACH(device_bandwidths, a, c->blockio_device_bandwidths) + if (!a->read) + fprintf(f, "BlockIOWriteBandwidth=%s %" PRIu64 "\n", a->path, a->bandwidth); + } + + r = fflush_and_check(f); + if (r < 0) + return r; + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + + } else if (streq(name, "BlockIODeviceWeight")) { + const char *path; + uint64_t weight; + unsigned n = 0; + + r = sd_bus_message_enter_container(message, 'a', "(st)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "(st)", &path, &weight)) > 0) { + + if (!CGROUP_BLKIO_WEIGHT_IS_OK(weight) || weight == CGROUP_BLKIO_WEIGHT_INVALID) + return sd_bus_error_set_errnof(error, EINVAL, "BlockIODeviceWeight out of range"); + + if (mode != UNIT_CHECK) { + CGroupBlockIODeviceWeight *a = NULL, *b; + + LIST_FOREACH(device_weights, b, c->blockio_device_weights) { + if (path_equal(b->path, path)) { + a = b; + break; + } + } + + if (!a) { + a = new0(CGroupBlockIODeviceWeight, 1); + if (!a) + return -ENOMEM; + + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + LIST_PREPEND(device_weights,c->blockio_device_weights, a); + } + + a->weight = weight; + } + + n++; + } + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + CGroupBlockIODeviceWeight *a; + size_t size = 0; + + if (n == 0) { + while (c->blockio_device_weights) + cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); + } + + unit_invalidate_cgroup(u, CGROUP_MASK_BLKIO); + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fputs("BlockIODeviceWeight=\n", f); + LIST_FOREACH(device_weights, a, c->blockio_device_weights) + fprintf(f, "BlockIODeviceWeight=%s %" PRIu64 "\n", a->path, a->weight); + + r = fflush_and_check(f); + if (r < 0) + return r; + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + + } else if (streq(name, "MemoryAccounting")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->memory_accounting = b; + unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY); + unit_write_drop_in_private(u, mode, name, b ? "MemoryAccounting=yes" : "MemoryAccounting=no"); + } + + return 1; + + } else if (streq(name, "MemoryLimit")) { + uint64_t limit; + + r = sd_bus_message_read(message, "t", &limit); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->memory_limit = limit; + unit_invalidate_cgroup(u, CGROUP_MASK_MEMORY); + + if (limit == (uint64_t) -1) + unit_write_drop_in_private(u, mode, name, "MemoryLimit=infinity"); + else + unit_write_drop_in_private_format(u, mode, name, "MemoryLimit=%" PRIu64, limit); + } + + return 1; + + } else if (streq(name, "DevicePolicy")) { + const char *policy; + CGroupDevicePolicy p; + + r = sd_bus_message_read(message, "s", &policy); + if (r < 0) + return r; + + p = cgroup_device_policy_from_string(policy); + if (p < 0) + return -EINVAL; + + if (mode != UNIT_CHECK) { + char *buf; + + c->device_policy = p; + unit_invalidate_cgroup(u, CGROUP_MASK_DEVICES); + + buf = strjoina("DevicePolicy=", policy); + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + + } else if (streq(name, "DeviceAllow")) { + const char *path, *rwm; + unsigned n = 0; + + r = sd_bus_message_enter_container(message, 'a', "(ss)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "(ss)", &path, &rwm)) > 0) { + + if ((!startswith(path, "/dev/") && + !startswith(path, "block-") && + !startswith(path, "char-")) || + strpbrk(path, WHITESPACE)) + return sd_bus_error_set_errnof(error, EINVAL, "DeviceAllow= requires device node"); + + if (isempty(rwm)) + rwm = "rwm"; + + if (!in_charset(rwm, "rwm")) + return sd_bus_error_set_errnof(error, EINVAL, "DeviceAllow= requires combination of rwm flags"); + + if (mode != UNIT_CHECK) { + CGroupDeviceAllow *a = NULL, *b; + + LIST_FOREACH(device_allow, b, c->device_allow) { + if (path_equal(b->path, path)) { + a = b; + break; + } + } + + if (!a) { + a = new0(CGroupDeviceAllow, 1); + if (!a) + return -ENOMEM; + + a->path = strdup(path); + if (!a->path) { + free(a); + return -ENOMEM; + } + + LIST_PREPEND(device_allow, c->device_allow, a); + } + + a->r = !!strchr(rwm, 'r'); + a->w = !!strchr(rwm, 'w'); + a->m = !!strchr(rwm, 'm'); + } + + n++; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + CGroupDeviceAllow *a; + size_t size = 0; + + if (n == 0) { + while (c->device_allow) + cgroup_context_free_device_allow(c, c->device_allow); + } + + unit_invalidate_cgroup(u, CGROUP_MASK_DEVICES); + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fputs("DeviceAllow=\n", f); + LIST_FOREACH(device_allow, a, c->device_allow) + fprintf(f, "DeviceAllow=%s %s%s%s\n", a->path, a->r ? "r" : "", a->w ? "w" : "", a->m ? "m" : ""); + + r = fflush_and_check(f); + if (r < 0) + return r; + unit_write_drop_in_private(u, mode, name, buf); + } + + return 1; + + } else if (streq(name, "TasksAccounting")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->tasks_accounting = b; + unit_invalidate_cgroup(u, CGROUP_MASK_PIDS); + unit_write_drop_in_private(u, mode, name, b ? "TasksAccounting=yes" : "TasksAccounting=no"); + } + + return 1; + + } else if (streq(name, "TasksMax")) { + uint64_t limit; + + r = sd_bus_message_read(message, "t", &limit); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->tasks_max = limit; + unit_invalidate_cgroup(u, CGROUP_MASK_PIDS); + + if (limit == (uint64_t) -1) + unit_write_drop_in_private(u, mode, name, "TasksMax=infinity"); + else + unit_write_drop_in_private_format(u, mode, name, "TasksMax=%" PRIu64, limit); + } + + return 1; + } + + if (u->transient && u->load_state == UNIT_STUB) { + r = bus_cgroup_set_transient_property(u, c, name, message, mode, error); + if (r != 0) + return r; + + } + + return 0; +} diff --git a/src/libcore/dbus-cgroup.h b/src/libcore/dbus-cgroup.h new file mode 100644 index 0000000000..84d0f1ba04 --- /dev/null +++ b/src/libcore/dbus-cgroup.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include "cgroup.h" + +extern const sd_bus_vtable bus_cgroup_vtable[]; + +int bus_cgroup_set_property(Unit *u, CGroupContext *c, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/libcore/dbus-device.c b/src/libcore/dbus-device.c new file mode 100644 index 0000000000..e1a12224d3 --- /dev/null +++ b/src/libcore/dbus-device.c @@ -0,0 +1,28 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "dbus-device.h" +#include "device.h" +#include "unit.h" + +const sd_bus_vtable bus_device_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("SysFSPath", "s", NULL, offsetof(Device, sysfs), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_VTABLE_END +}; diff --git a/src/libcore/dbus-device.h b/src/libcore/dbus-device.h new file mode 100644 index 0000000000..eb1d8c3278 --- /dev/null +++ b/src/libcore/dbus-device.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "unit.h" + +extern const sd_bus_vtable bus_device_vtable[]; diff --git a/src/libcore/dbus-execute.c b/src/libcore/dbus-execute.c new file mode 100644 index 0000000000..f2fc301f8e --- /dev/null +++ b/src/libcore/dbus-execute.c @@ -0,0 +1,1564 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#ifdef HAVE_SECCOMP +#include +#endif + +#include "af-list.h" +#include "alloc-util.h" +#include "bus-util.h" +#include "capability-util.h" +#include "dbus-execute.h" +#include "env-util.h" +#include "execute.h" +#include "fd-util.h" +#include "fileio.h" +#include "ioprio.h" +#include "missing.h" +#include "namespace.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "rlimit-util.h" +#ifdef HAVE_SECCOMP +#include "seccomp-util.h" +#endif +#include "strv.h" +#include "syslog-util.h" +#include "utf8.h" + +BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_exec_output, exec_output, ExecOutput); + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_input, exec_input, ExecInput); + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode); + +static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_home, protect_home, ProtectHome); +static BUS_DEFINE_PROPERTY_GET_ENUM(bus_property_get_protect_system, protect_system, ProtectSystem); + +static int property_get_environment_files( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + char **j; + int r; + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'a', "(sb)"); + if (r < 0) + return r; + + STRV_FOREACH(j, c->environment_files) { + const char *fn = *j; + + r = sd_bus_message_append(reply, "(sb)", fn[0] == '-' ? fn + 1 : fn, fn[0] == '-'); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_oom_score_adjust( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + + ExecContext *c = userdata; + int32_t n; + + assert(bus); + assert(reply); + assert(c); + + if (c->oom_score_adjust_set) + n = c->oom_score_adjust; + else { + _cleanup_free_ char *t = NULL; + + n = 0; + if (read_one_line_file("/proc/self/oom_score_adj", &t) >= 0) + safe_atoi32(t, &n); + } + + return sd_bus_message_append(reply, "i", n); +} + +static int property_get_nice( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + + ExecContext *c = userdata; + int32_t n; + + assert(bus); + assert(reply); + assert(c); + + if (c->nice_set) + n = c->nice; + else { + errno = 0; + n = getpriority(PRIO_PROCESS, 0); + if (errno > 0) + n = 0; + } + + return sd_bus_message_append(reply, "i", n); +} + +static int property_get_ioprio( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + + ExecContext *c = userdata; + int32_t n; + + assert(bus); + assert(reply); + assert(c); + + if (c->ioprio_set) + n = c->ioprio; + else { + n = ioprio_get(IOPRIO_WHO_PROCESS, 0); + if (n < 0) + n = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 4); + } + + return sd_bus_message_append(reply, "i", n); +} + +static int property_get_cpu_sched_policy( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + int32_t n; + + assert(bus); + assert(reply); + assert(c); + + if (c->cpu_sched_set) + n = c->cpu_sched_policy; + else { + n = sched_getscheduler(0); + if (n < 0) + n = SCHED_OTHER; + } + + return sd_bus_message_append(reply, "i", n); +} + +static int property_get_cpu_sched_priority( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + int32_t n; + + assert(bus); + assert(reply); + assert(c); + + if (c->cpu_sched_set) + n = c->cpu_sched_priority; + else { + struct sched_param p = {}; + + if (sched_getparam(0, &p) >= 0) + n = p.sched_priority; + else + n = 0; + } + + return sd_bus_message_append(reply, "i", n); +} + +static int property_get_cpu_affinity( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + if (c->cpuset) + return sd_bus_message_append_array(reply, 'y', c->cpuset, CPU_ALLOC_SIZE(c->cpuset_ncpus)); + else + return sd_bus_message_append_array(reply, 'y', NULL, 0); +} + +static int property_get_timer_slack_nsec( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + uint64_t u; + + assert(bus); + assert(reply); + assert(c); + + if (c->timer_slack_nsec != NSEC_INFINITY) + u = (uint64_t) c->timer_slack_nsec; + else + u = (uint64_t) prctl(PR_GET_TIMERSLACK); + + return sd_bus_message_append(reply, "t", u); +} + +static int property_get_capability_bounding_set( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "t", c->capability_bounding_set); +} + +static int property_get_ambient_capabilities( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "t", c->capability_ambient_set); +} + +static int property_get_capabilities( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + _cleanup_cap_free_charp_ char *t = NULL; + const char *s; + + assert(bus); + assert(reply); + assert(c); + + if (c->capabilities) + s = t = cap_to_text(c->capabilities, NULL); + else + s = ""; + + if (!s) + return -ENOMEM; + + return sd_bus_message_append(reply, "s", s); +} + +static int property_get_syscall_filter( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + _cleanup_strv_free_ char **l = NULL; + int r; + +#ifdef HAVE_SECCOMP + Iterator i; + void *id; +#endif + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'r', "bas"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "b", c->syscall_whitelist); + if (r < 0) + return r; + +#ifdef HAVE_SECCOMP + SET_FOREACH(id, c->syscall_filter, i) { + char *name; + + name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1); + if (!name) + continue; + + r = strv_consume(&l, name); + if (r < 0) + return r; + } +#endif + + strv_sort(l); + + r = sd_bus_message_append_strv(reply, l); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + +static int property_get_syscall_archs( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + _cleanup_strv_free_ char **l = NULL; + int r; + +#ifdef HAVE_SECCOMP + Iterator i; + void *id; +#endif + + assert(bus); + assert(reply); + assert(c); + +#ifdef HAVE_SECCOMP + SET_FOREACH(id, c->syscall_archs, i) { + const char *name; + + name = seccomp_arch_to_string(PTR_TO_UINT32(id) - 1); + if (!name) + continue; + + r = strv_extend(&l, name); + if (r < 0) + return -ENOMEM; + } +#endif + + strv_sort(l); + + r = sd_bus_message_append_strv(reply, l); + if (r < 0) + return r; + + return 0; +} + +static int property_get_syscall_errno( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "i", (int32_t) c->syscall_errno); +} + +static int property_get_selinux_context( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "(bs)", c->selinux_context_ignore, c->selinux_context); +} + +static int property_get_apparmor_profile( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "(bs)", c->apparmor_profile_ignore, c->apparmor_profile); +} + +static int property_get_smack_process_label( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "(bs)", c->smack_process_label_ignore, c->smack_process_label); +} + +static int property_get_personality( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "s", personality_to_string(c->personality)); +} + +static int property_get_address_families( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + _cleanup_strv_free_ char **l = NULL; + Iterator i; + void *af; + int r; + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'r', "bas"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "b", c->address_families_whitelist); + if (r < 0) + return r; + + SET_FOREACH(af, c->address_families, i) { + const char *name; + + name = af_to_name(PTR_TO_INT(af)); + if (!name) + continue; + + r = strv_extend(&l, name); + if (r < 0) + return -ENOMEM; + } + + strv_sort(l); + + r = sd_bus_message_append_strv(reply, l); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + +static int property_get_working_directory( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + const char *wd; + + assert(bus); + assert(reply); + assert(c); + + if (c->working_directory_home) + wd = "~"; + else + wd = c->working_directory; + + if (c->working_directory_missing_ok) + wd = strjoina("!", wd); + + return sd_bus_message_append(reply, "s", wd); +} + +static int property_get_syslog_level( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "i", LOG_PRI(c->syslog_priority)); +} + +static int property_get_syslog_facility( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "i", LOG_FAC(c->syslog_priority)); +} + +const sd_bus_vtable bus_exec_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("EnvironmentFiles", "a(sb)", property_get_environment_files, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PassEnvironment", "as", NULL, offsetof(ExecContext, pass_environment), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("UMask", "u", bus_property_get_mode, offsetof(ExecContext, umask), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitCPU", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitCPUSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitFSIZE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitFSIZESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitDATA", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitDATASoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitSTACK", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitSTACKSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitCORE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitCORESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitRSS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitRSSSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitNOFILE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitNOFILESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitAS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitASSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitNPROC", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitNPROCSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitMEMLOCK", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitMEMLOCKSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitLOCKS", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitLOCKSSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitSIGPENDING", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitSIGPENDINGSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitMSGQUEUE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitMSGQUEUESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitNICE", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitNICESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitRTPRIO", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitRTPRIOSoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitRTTIME", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LimitRTTIMESoft", "t", bus_property_get_rlimit, offsetof(ExecContext, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("WorkingDirectory", "s", property_get_working_directory, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(ExecContext, root_directory), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OOMScoreAdjust", "i", property_get_oom_score_adjust, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Nice", "i", property_get_nice, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("IOScheduling", "i", property_get_ioprio, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CPUSchedulingPolicy", "i", property_get_cpu_sched_policy, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CPUSchedulingPriority", "i", property_get_cpu_sched_priority, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CPUAffinity", "ay", property_get_cpu_affinity, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CPUSchedulingResetOnFork", "b", bus_property_get_bool, offsetof(ExecContext, cpu_sched_reset_on_fork), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NonBlocking", "b", bus_property_get_bool, offsetof(ExecContext, non_blocking), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StandardInput", "s", property_get_exec_input, offsetof(ExecContext, std_input), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StandardOutput", "s", bus_property_get_exec_output, offsetof(ExecContext, std_output), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StandardError", "s", bus_property_get_exec_output, offsetof(ExecContext, std_error), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TTYPath", "s", NULL, offsetof(ExecContext, tty_path), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TTYReset", "b", bus_property_get_bool, offsetof(ExecContext, tty_reset), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TTYVHangup", "b", bus_property_get_bool, offsetof(ExecContext, tty_vhangup), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TTYVTDisallocate", "b", bus_property_get_bool, offsetof(ExecContext, tty_vt_disallocate), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SyslogPriority", "i", bus_property_get_int, offsetof(ExecContext, syslog_priority), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SyslogIdentifier", "s", NULL, offsetof(ExecContext, syslog_identifier), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SyslogLevelPrefix", "b", bus_property_get_bool, offsetof(ExecContext, syslog_level_prefix), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SyslogLevel", "i", property_get_syslog_level, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SyslogFacility", "i", property_get_syslog_facility, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Capabilities", "s", property_get_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SecureBits", "i", bus_property_get_int, offsetof(ExecContext, secure_bits), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CapabilityBoundingSet", "t", property_get_capability_bounding_set, 0, SD_BUS_VTABLE_PROPERTY_CONST), + 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("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("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), + SD_BUS_PROPERTY("PrivateDevices", "b", bus_property_get_bool, offsetof(ExecContext, private_devices), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ProtectHome", "s", bus_property_get_protect_home, offsetof(ExecContext, protect_home), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ProtectSystem", "s", bus_property_get_protect_system, offsetof(ExecContext, protect_system), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SameProcessGroup", "b", bus_property_get_bool, offsetof(ExecContext, same_pgrp), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("UtmpIdentifier", "s", NULL, offsetof(ExecContext, utmp_id), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("UtmpMode", "s", property_get_exec_utmp_mode, offsetof(ExecContext, utmp_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SELinuxContext", "(bs)", property_get_selinux_context, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("AppArmorProfile", "(bs)", property_get_apparmor_profile, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SmackProcessLabel", "(bs)", property_get_smack_process_label, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("IgnoreSIGPIPE", "b", bus_property_get_bool, offsetof(ExecContext, ignore_sigpipe), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NoNewPrivileges", "b", bus_property_get_bool, offsetof(ExecContext, no_new_privileges), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SystemCallFilter", "(bas)", property_get_syscall_filter, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SystemCallArchitectures", "as", property_get_syscall_archs, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SystemCallErrorNumber", "i", property_get_syscall_errno, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Personality", "s", property_get_personality, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST), + 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_VTABLE_END +}; + +static int append_exec_command(sd_bus_message *reply, ExecCommand *c) { + int r; + + assert(reply); + assert(c); + + if (!c->path) + return 0; + + r = sd_bus_message_open_container(reply, 'r', "sasbttttuii"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", c->path); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(reply, c->argv); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "bttttuii", + c->ignore, + c->exec_status.start_timestamp.realtime, + c->exec_status.start_timestamp.monotonic, + c->exec_status.exit_timestamp.realtime, + c->exec_status.exit_timestamp.monotonic, + (uint32_t) c->exec_status.pid, + (int32_t) c->exec_status.code, + (int32_t) c->exec_status.status); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + +int bus_property_get_exec_command( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *ret_error) { + + ExecCommand *c = (ExecCommand*) userdata; + int r; + + assert(bus); + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "(sasbttttuii)"); + if (r < 0) + return r; + + r = append_exec_command(reply, c); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + +int bus_property_get_exec_command_list( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *ret_error) { + + ExecCommand *c = *(ExecCommand**) userdata; + int r; + + assert(bus); + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "(sasbttttuii)"); + if (r < 0) + return r; + + LIST_FOREACH(command, c, c) { + r = append_exec_command(reply, c); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +int bus_exec_context_set_transient_property( + Unit *u, + ExecContext *c, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + const char *soft = NULL; + int r, ri; + + assert(u); + assert(c); + assert(name); + assert(message); + + if (streq(name, "User")) { + const char *uu; + + r = sd_bus_message_read(message, "s", &uu); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + + if (isempty(uu)) { + c->user = mfree(c->user); + } else { + char *t; + + t = strdup(uu); + if (!t) + return -ENOMEM; + + free(c->user); + c->user = t; + } + + unit_write_drop_in_private_format(u, mode, name, "User=%s\n", uu); + } + + return 1; + + } else if (streq(name, "Group")) { + const char *gg; + + r = sd_bus_message_read(message, "s", &gg); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + + if (isempty(gg)) { + c->group = mfree(c->group); + } else { + char *t; + + t = strdup(gg); + if (!t) + return -ENOMEM; + + free(c->group); + c->group = t; + } + + unit_write_drop_in_private_format(u, mode, name, "Group=%s\n", gg); + } + + return 1; + } else if (streq(name, "SyslogIdentifier")) { + const char *id; + + r = sd_bus_message_read(message, "s", &id); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + + if (isempty(id)) { + c->syslog_identifier = mfree(c->syslog_identifier); + } else { + char *t; + + t = strdup(id); + if (!t) + return -ENOMEM; + + free(c->syslog_identifier); + c->syslog_identifier = t; + } + + unit_write_drop_in_private_format(u, mode, name, "SyslogIdentifier=%s\n", id); + } + + return 1; + } else if (streq(name, "SyslogLevel")) { + int level; + + r = sd_bus_message_read(message, "i", &level); + if (r < 0) + return r; + + if (!log_level_is_valid(level)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Log level value out of range"); + + if (mode != UNIT_CHECK) { + c->syslog_priority = (c->syslog_priority & LOG_FACMASK) | level; + unit_write_drop_in_private_format(u, mode, name, "SyslogLevel=%i\n", level); + } + + return 1; + } else if (streq(name, "SyslogFacility")) { + int facility; + + r = sd_bus_message_read(message, "i", &facility); + if (r < 0) + return r; + + if (!log_facility_unshifted_is_valid(facility)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Log facility value out of range"); + + if (mode != UNIT_CHECK) { + c->syslog_priority = (facility << 3) | LOG_PRI(c->syslog_priority); + unit_write_drop_in_private_format(u, mode, name, "SyslogFacility=%i\n", facility); + } + + return 1; + } else if (streq(name, "Nice")) { + int n; + + r = sd_bus_message_read(message, "i", &n); + if (r < 0) + return r; + + if (n < PRIO_MIN || n >= PRIO_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Nice value out of range"); + + if (mode != UNIT_CHECK) { + c->nice = n; + unit_write_drop_in_private_format(u, mode, name, "Nice=%i\n", n); + } + + return 1; + + } else if (STR_IN_SET(name, "TTYPath", "RootDirectory")) { + const char *s; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + if (!path_is_absolute(s)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s takes an absolute path", name); + + if (mode != UNIT_CHECK) { + if (streq(name, "TTYPath")) + r = free_and_strdup(&c->tty_path, s); + else { + assert(streq(name, "RootDirectory")); + r = free_and_strdup(&c->root_directory, s); + } + if (r < 0) + return r; + + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, s); + } + + return 1; + + } else if (streq(name, "WorkingDirectory")) { + const char *s; + bool missing_ok; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + if (s[0] == '-') { + missing_ok = true; + s++; + } else + missing_ok = false; + + if (!streq(s, "~") && !path_is_absolute(s)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "WorkingDirectory= expects an absolute path or '~'"); + + if (mode != UNIT_CHECK) { + if (streq(s, "~")) { + c->working_directory = mfree(c->working_directory); + c->working_directory_home = true; + } else { + r = free_and_strdup(&c->working_directory, s); + if (r < 0) + return r; + + c->working_directory_home = false; + } + + c->working_directory_missing_ok = missing_ok; + unit_write_drop_in_private_format(u, mode, name, "WorkingDirectory=%s%s", missing_ok ? "-" : "", s); + } + + return 1; + + } else if (streq(name, "StandardInput")) { + const char *s; + ExecInput p; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + p = exec_input_from_string(s); + if (p < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard input name"); + + if (mode != UNIT_CHECK) { + c->std_input = p; + + unit_write_drop_in_private_format(u, mode, name, "StandardInput=%s\n", exec_input_to_string(p)); + } + + return 1; + + + } else if (streq(name, "StandardOutput")) { + const char *s; + ExecOutput p; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + p = exec_output_from_string(s); + if (p < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard output name"); + + if (mode != UNIT_CHECK) { + c->std_output = p; + + unit_write_drop_in_private_format(u, mode, name, "StandardOutput=%s\n", exec_output_to_string(p)); + } + + return 1; + + } else if (streq(name, "StandardError")) { + const char *s; + ExecOutput p; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + p = exec_output_from_string(s); + if (p < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid standard error name"); + + if (mode != UNIT_CHECK) { + c->std_error = p; + + unit_write_drop_in_private_format(u, mode, name, "StandardError=%s\n", exec_output_to_string(p)); + } + + return 1; + + } else if (STR_IN_SET(name, + "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", + "PrivateTmp", "PrivateDevices", "PrivateNetwork", + "NoNewPrivileges", "SyslogLevelPrefix")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + if (streq(name, "IgnoreSIGPIPE")) + c->ignore_sigpipe = b; + else if (streq(name, "TTYVHangup")) + c->tty_vhangup = b; + else if (streq(name, "TTYReset")) + c->tty_reset = b; + else if (streq(name, "PrivateTmp")) + c->private_tmp = b; + else if (streq(name, "PrivateDevices")) + c->private_devices = b; + else if (streq(name, "PrivateNetwork")) + c->private_network = b; + else if (streq(name, "NoNewPrivileges")) + c->no_new_privileges = b; + else if (streq(name, "SyslogLevelPrefix")) + c->syslog_level_prefix = b; + + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, yes_no(b)); + } + + return 1; + + } else if (streq(name, "UtmpIdentifier")) { + const char *id; + + r = sd_bus_message_read(message, "s", &id); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + if (isempty(id)) + c->utmp_id = mfree(c->utmp_id); + else if (free_and_strdup(&c->utmp_id, id) < 0) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "UtmpIdentifier=%s\n", strempty(id)); + } + + return 1; + + } else if (streq(name, "UtmpMode")) { + const char *s; + ExecUtmpMode m; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + m = exec_utmp_mode_from_string(s); + if (m < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid utmp mode"); + + if (mode != UNIT_CHECK) { + c->utmp_mode = m; + + unit_write_drop_in_private_format(u, mode, name, "UtmpMode=%s\n", exec_utmp_mode_to_string(m)); + } + + return 1; + + } else if (streq(name, "PAMName")) { + const char *n; + + r = sd_bus_message_read(message, "s", &n); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + if (isempty(n)) + c->pam_name = mfree(c->pam_name); + else if (free_and_strdup(&c->pam_name, n) < 0) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "PAMName=%s\n", strempty(n)); + } + + return 1; + + } else if (streq(name, "Environment")) { + + _cleanup_strv_free_ char **l = NULL; + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + if (!strv_env_is_valid(l)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment block."); + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *joined = NULL; + char **e; + + if (strv_length(l) == 0) { + c->environment = strv_free(c->environment); + unit_write_drop_in_private_format(u, mode, name, "Environment=\n"); + } else { + e = strv_env_merge(2, c->environment, l); + if (!e) + return -ENOMEM; + + strv_free(c->environment); + c->environment = e; + + joined = strv_join_quoted(c->environment); + if (!joined) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "Environment=%s\n", joined); + } + } + + return 1; + + } else if (streq(name, "TimerSlackNSec")) { + + nsec_t n; + + r = sd_bus_message_read(message, "t", &n); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->timer_slack_nsec = n; + unit_write_drop_in_private_format(u, mode, name, "TimerSlackNSec=" NSEC_FMT "\n", n); + } + + return 1; + + } else if (streq(name, "OOMScoreAdjust")) { + int oa; + + r = sd_bus_message_read(message, "i", &oa); + if (r < 0) + return r; + + if (!oom_score_adjust_is_valid(oa)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "OOM score adjust value out of range"); + + if (mode != UNIT_CHECK) { + c->oom_score_adjust = oa; + c->oom_score_adjust_set = true; + unit_write_drop_in_private_format(u, mode, name, "OOMScoreAdjust=%i\n", oa); + } + + return 1; + + } else if (streq(name, "EnvironmentFiles")) { + + _cleanup_free_ char *joined = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char **l = NULL; + size_t size = 0; + char **i; + + r = sd_bus_message_enter_container(message, 'a', "(sb)"); + if (r < 0) + return r; + + f = open_memstream(&joined, &size); + if (!f) + return -ENOMEM; + + STRV_FOREACH(i, c->environment_files) + fprintf(f, "EnvironmentFile=%s\n", *i); + + while ((r = sd_bus_message_enter_container(message, 'r', "sb")) > 0) { + const char *path; + int b; + + r = sd_bus_message_read(message, "sb", &path, &b); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (!isempty(path) && !path_is_absolute(path)) + return sd_bus_error_set_errnof(error, EINVAL, "Path %s is not absolute.", path); + + if (mode != UNIT_CHECK) { + char *buf = NULL; + + buf = strjoin(b ? "-" : "", path, NULL); + if (!buf) + return -ENOMEM; + + fprintf(f, "EnvironmentFile=%s\n", buf); + + r = strv_consume(&l, buf); + if (r < 0) + return r; + } + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + r = fflush_and_check(f); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + if (strv_isempty(l)) { + c->environment_files = strv_free(c->environment_files); + unit_write_drop_in_private(u, mode, name, "EnvironmentFile=\n"); + } else { + r = strv_extend_strv(&c->environment_files, l, true); + if (r < 0) + return r; + + unit_write_drop_in_private(u, mode, name, joined); + } + } + + return 1; + + } else if (streq(name, "PassEnvironment")) { + + _cleanup_strv_free_ char **l = NULL; + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + if (!strv_env_name_is_valid(l)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PassEnvironment block."); + + if (mode != UNIT_CHECK) { + if (strv_isempty(l)) { + c->pass_environment = strv_free(c->pass_environment); + unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=\n"); + } else { + _cleanup_free_ char *joined = NULL; + + r = strv_extend_strv(&c->pass_environment, l, true); + if (r < 0) + return r; + + joined = strv_join_quoted(c->pass_environment); + if (!joined) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "PassEnvironment=%s\n", joined); + } + } + + return 1; + + } else if (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) { + + _cleanup_strv_free_ char **l = NULL; + char ***dirs; + char **p; + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + STRV_FOREACH(p, l) { + int offset; + if (!utf8_is_valid(*p)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s", name); + + offset = **p == '-'; + if (!path_is_absolute(*p + offset)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid %s", name); + } + + 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 (strv_length(l) == 0) { + *dirs = strv_free(*dirs); + unit_write_drop_in_private_format(u, mode, name, "%s=\n", name); + } else { + r = strv_extend_strv(dirs, l, true); + + if (r < 0) + return -ENOMEM; + + joined = strv_join_quoted(*dirs); + if (!joined) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, joined); + } + + } + + return 1; + + } else if (streq(name, "ProtectSystem")) { + const char *s; + ProtectSystem ps; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + r = parse_boolean(s); + if (r > 0) + ps = PROTECT_SYSTEM_YES; + else if (r == 0) + ps = PROTECT_SYSTEM_NO; + else { + ps = protect_system_from_string(s); + if (ps < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse protect system value"); + } + + if (mode != UNIT_CHECK) { + c->protect_system = ps; + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, s); + } + + return 1; + + } else if (streq(name, "ProtectHome")) { + const char *s; + ProtectHome ph; + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + r = parse_boolean(s); + if (r > 0) + ph = PROTECT_HOME_YES; + else if (r == 0) + ph = PROTECT_HOME_NO; + else { + ph = protect_home_from_string(s); + if (ph < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Failed to parse protect home value"); + } + + if (mode != UNIT_CHECK) { + c->protect_home = ph; + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, s); + } + + return 1; + + } else if (streq(name, "RuntimeDirectory")) { + _cleanup_strv_free_ char **l = NULL; + char **p; + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + STRV_FOREACH(p, l) { + if (!filename_is_valid(*p)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Runtime directory is not valid %s", *p); + } + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *joined = NULL; + + if (strv_isempty(l)) { + c->runtime_directory = strv_free(c->runtime_directory); + unit_write_drop_in_private_format(u, mode, name, "%s=\n", name); + } else { + r = strv_extend_strv(&c->runtime_directory, l, true); + + if (r < 0) + return -ENOMEM; + + joined = strv_join_quoted(c->runtime_directory); + if (!joined) + return -ENOMEM; + + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, joined); + } + } + + return 1; + + } + + ri = rlimit_from_string(name); + if (ri < 0) { + soft = endswith(name, "Soft"); + if (soft) { + const char *n; + + n = strndupa(name, soft - name); + ri = rlimit_from_string(n); + if (ri >= 0) + name = n; + + } + } + + if (ri >= 0) { + uint64_t rl; + rlim_t x; + + r = sd_bus_message_read(message, "t", &rl); + if (r < 0) + return r; + + if (rl == (uint64_t) -1) + x = RLIM_INFINITY; + else { + x = (rlim_t) rl; + + if ((uint64_t) x != rl) + return -ERANGE; + } + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *f = NULL; + struct rlimit nl; + + if (c->rlimit[ri]) { + nl = *c->rlimit[ri]; + + if (soft) + nl.rlim_cur = x; + else + nl.rlim_max = x; + } else + /* When the resource limit is not initialized yet, then assign the value to both fields */ + nl = (struct rlimit) { + .rlim_cur = x, + .rlim_max = x, + }; + + r = rlimit_format(&nl, &f); + if (r < 0) + return r; + + if (c->rlimit[ri]) + *c->rlimit[ri] = nl; + else { + c->rlimit[ri] = newdup(struct rlimit, &nl, 1); + if (!c->rlimit[ri]) + return -ENOMEM; + } + + unit_write_drop_in_private_format(u, mode, name, "%s=%s\n", name, f); + } + + return 1; + } + + return 0; +} diff --git a/src/libcore/dbus-execute.h b/src/libcore/dbus-execute.h new file mode 100644 index 0000000000..bdfef41db4 --- /dev/null +++ b/src/libcore/dbus-execute.h @@ -0,0 +1,45 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "execute.h" + +#define BUS_EXEC_STATUS_VTABLE(prefix, offset, flags) \ + BUS_PROPERTY_DUAL_TIMESTAMP(prefix "StartTimestamp", (offset) + offsetof(ExecStatus, start_timestamp), flags), \ + BUS_PROPERTY_DUAL_TIMESTAMP(prefix "ExitTimestamp", (offset) + offsetof(ExecStatus, exit_timestamp), flags), \ + SD_BUS_PROPERTY(prefix "PID", "u", bus_property_get_pid, (offset) + offsetof(ExecStatus, pid), flags), \ + SD_BUS_PROPERTY(prefix "Code", "i", bus_property_get_int, (offset) + offsetof(ExecStatus, code), flags), \ + SD_BUS_PROPERTY(prefix "Status", "i", bus_property_get_int, (offset) + offsetof(ExecStatus, status), flags) + +#define BUS_EXEC_COMMAND_VTABLE(name, offset, flags) \ + SD_BUS_PROPERTY(name, "a(sasbttttuii)", bus_property_get_exec_command, offset, flags) + +#define BUS_EXEC_COMMAND_LIST_VTABLE(name, offset, flags) \ + SD_BUS_PROPERTY(name, "a(sasbttttuii)", bus_property_get_exec_command_list, offset, flags) + +extern const sd_bus_vtable bus_exec_vtable[]; + +int bus_property_get_exec_output(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); +int bus_property_get_exec_command(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); +int bus_property_get_exec_command_list(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); + +int bus_exec_context_set_transient_property(Unit *u, ExecContext *c, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/libcore/dbus-job.c b/src/libcore/dbus-job.c new file mode 100644 index 0000000000..337cef8a4f --- /dev/null +++ b/src/libcore/dbus-job.c @@ -0,0 +1,193 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "alloc-util.h" +#include "dbus-job.h" +#include "dbus.h" +#include "job.h" +#include "log.h" +#include "selinux-access.h" +#include "string-util.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, job_type, JobType); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_state, job_state, JobState); + +static int property_get_unit( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_free_ char *p = NULL; + Job *j = userdata; + + assert(bus); + assert(reply); + assert(j); + + p = unit_dbus_path(j->unit); + if (!p) + return -ENOMEM; + + return sd_bus_message_append(reply, "(so)", j->unit->id, p); +} + +int bus_job_method_cancel(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Job *j = userdata; + int r; + + assert(message); + assert(j); + + r = mac_selinux_unit_access_check(j->unit, message, "stop", error); + if (r < 0) + return r; + + /* Access is granted to the job owner */ + if (!sd_bus_track_contains(j->clients, sd_bus_message_get_sender(message))) { + + /* And for everybody else consult PolicyKit */ + r = bus_verify_manage_units_async(j->unit->manager, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + } + + job_finish_and_invalidate(j, JOB_CANCELED, true); + + return sd_bus_reply_method_return(message, NULL); +} + +const sd_bus_vtable bus_job_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("Cancel", NULL, NULL, bus_job_method_cancel, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_PROPERTY("Id", "u", NULL, offsetof(Job, id), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Unit", "(so)", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("JobType", "s", property_get_type, offsetof(Job, type), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("State", "s", property_get_state, offsetof(Job, state), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_VTABLE_END +}; + +static int send_new_signal(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *p = NULL; + Job *j = userdata; + int r; + + assert(bus); + assert(j); + + p = job_dbus_path(j); + if (!p) + return -ENOMEM; + + r = sd_bus_message_new_signal( + bus, + &m, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "JobNew"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "uos", j->id, p, j->unit->id); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +static int send_changed_signal(sd_bus *bus, void *userdata) { + _cleanup_free_ char *p = NULL; + Job *j = userdata; + + assert(bus); + assert(j); + + p = job_dbus_path(j); + if (!p) + return -ENOMEM; + + return sd_bus_emit_properties_changed(bus, p, "org.freedesktop.systemd1.Job", "State", NULL); +} + +void bus_job_send_change_signal(Job *j) { + int r; + + assert(j); + + if (j->in_dbus_queue) { + LIST_REMOVE(dbus_queue, j->manager->dbus_job_queue, j); + j->in_dbus_queue = false; + } + + r = bus_foreach_bus(j->manager, j->clients, j->sent_dbus_new_signal ? send_changed_signal : send_new_signal, j); + if (r < 0) + log_debug_errno(r, "Failed to send job change signal for %u: %m", j->id); + + j->sent_dbus_new_signal = true; +} + +static int send_removed_signal(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *p = NULL; + Job *j = userdata; + int r; + + assert(bus); + assert(j); + + p = job_dbus_path(j); + if (!p) + return -ENOMEM; + + r = sd_bus_message_new_signal( + bus, + &m, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "JobRemoved"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "uoss", j->id, p, j->unit->id, job_result_to_string(j->result)); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +void bus_job_send_removed_signal(Job *j) { + int r; + + assert(j); + + if (!j->sent_dbus_new_signal) + bus_job_send_change_signal(j); + + r = bus_foreach_bus(j->manager, j->clients, send_removed_signal, j); + if (r < 0) + log_debug_errno(r, "Failed to send job remove signal for %u: %m", j->id); +} diff --git a/src/libcore/dbus-job.h b/src/libcore/dbus-job.h new file mode 100644 index 0000000000..95664cb90c --- /dev/null +++ b/src/libcore/dbus-job.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "job.h" + +extern const sd_bus_vtable bus_job_vtable[]; + +int bus_job_method_cancel(sd_bus_message *message, void *job, sd_bus_error *error); + +void bus_job_send_change_signal(Job *j); +void bus_job_send_removed_signal(Job *j); diff --git a/src/libcore/dbus-kill.c b/src/libcore/dbus-kill.c new file mode 100644 index 0000000000..fc50fafaad --- /dev/null +++ b/src/libcore/dbus-kill.c @@ -0,0 +1,122 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include "bus-util.h" +#include "dbus-kill.h" +#include "kill.h" +#include "signal-util.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_kill_mode, kill_mode, KillMode); + +const sd_bus_vtable bus_kill_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("KillMode", "s", property_get_kill_mode, offsetof(KillContext, kill_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("KillSignal", "i", bus_property_get_int, offsetof(KillContext, kill_signal), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SendSIGKILL", "b", bus_property_get_bool, offsetof(KillContext, send_sigkill), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SendSIGHUP", "b", bus_property_get_bool, offsetof(KillContext, send_sighup), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_VTABLE_END +}; + +int bus_kill_context_set_transient_property( + Unit *u, + KillContext *c, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(u); + assert(c); + assert(name); + assert(message); + + if (streq(name, "KillMode")) { + const char *m; + KillMode k; + + r = sd_bus_message_read(message, "s", &m); + if (r < 0) + return r; + + k = kill_mode_from_string(m); + if (k < 0) + return -EINVAL; + + if (mode != UNIT_CHECK) { + c->kill_mode = k; + + unit_write_drop_in_private_format(u, mode, name, "KillMode=%s\n", kill_mode_to_string(k)); + } + + return 1; + + } else if (streq(name, "KillSignal")) { + int sig; + + r = sd_bus_message_read(message, "i", &sig); + if (r < 0) + return r; + + if (sig <= 0 || sig >= _NSIG) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Signal %i out of range", sig); + + if (mode != UNIT_CHECK) { + c->kill_signal = sig; + + unit_write_drop_in_private_format(u, mode, name, "KillSignal=%s\n", signal_to_string(sig)); + } + + return 1; + + } else if (streq(name, "SendSIGHUP")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->send_sighup = b; + + unit_write_drop_in_private_format(u, mode, name, "SendSIGHUP=%s\n", yes_no(b)); + } + + return 1; + + } else if (streq(name, "SendSIGKILL")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + c->send_sigkill = b; + + unit_write_drop_in_private_format(u, mode, name, "SendSIGKILL=%s\n", yes_no(b)); + } + + return 1; + + } + + return 0; +} diff --git a/src/libcore/dbus-kill.h b/src/libcore/dbus-kill.h new file mode 100644 index 0000000000..b32ce9d223 --- /dev/null +++ b/src/libcore/dbus-kill.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include + +#include "kill.h" +#include "unit.h" + +extern const sd_bus_vtable bus_kill_vtable[]; + +int bus_kill_context_set_transient_property(Unit *u, KillContext *c, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/libcore/dbus-manager.c b/src/libcore/dbus-manager.c new file mode 100644 index 0000000000..f939196397 --- /dev/null +++ b/src/libcore/dbus-manager.c @@ -0,0 +1,2137 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#include "alloc-util.h" +#include "architecture.h" +#include "build.h" +#include "bus-common-errors.h" +#include "clock-util.h" +#include "dbus-execute.h" +#include "dbus-job.h" +#include "dbus-manager.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "install.h" +#include "log.h" +#include "path-util.h" +#include "selinux-access.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "syslog-util.h" +#include "virt.h" +#include "watchdog.h" + +static int property_get_version( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "s", PACKAGE_VERSION); +} + +static int property_get_features( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "s", SYSTEMD_FEATURES); +} + +static int property_get_virtualization( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + int v; + + assert(bus); + assert(reply); + + v = detect_virtualization(); + + /* Make sure to return the empty string when we detect no virtualization, as that is the API. + * + * https://github.com/systemd/systemd/issues/1423 + */ + + return sd_bus_message_append( + reply, "s", + v == VIRTUALIZATION_NONE ? "" : virtualization_to_string(v)); +} + +static int property_get_architecture( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "s", architecture_to_string(uname_architecture())); +} + +static int property_get_tainted( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + char buf[sizeof("split-usr:cgroups-missing:local-hwclock:")] = "", *e = buf; + Manager *m = userdata; + + assert(bus); + assert(reply); + assert(m); + + if (m->taint_usr) + e = stpcpy(e, "split-usr:"); + + if (access("/proc/cgroups", F_OK) < 0) + e = stpcpy(e, "cgroups-missing:"); + + if (clock_is_localtime() > 0) + e = stpcpy(e, "local-hwclock:"); + + /* remove the last ':' */ + if (e != buf) + e[-1] = 0; + + return sd_bus_message_append(reply, "s", buf); +} + +static int property_get_log_target( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "s", log_target_to_string(log_get_target())); +} + +static int property_set_log_target( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *userdata, + sd_bus_error *error) { + + const char *t; + int r; + + assert(bus); + assert(value); + + r = sd_bus_message_read(value, "s", &t); + if (r < 0) + return r; + + return log_set_target_from_string(t); +} + +static int property_get_log_level( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_free_ char *t = NULL; + int r; + + assert(bus); + assert(reply); + + r = log_level_to_string_alloc(log_get_max_level(), &t); + if (r < 0) + return r; + + return sd_bus_message_append(reply, "s", t); +} + +static int property_set_log_level( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *userdata, + sd_bus_error *error) { + + const char *t; + int r; + + assert(bus); + assert(value); + + r = sd_bus_message_read(value, "s", &t); + if (r < 0) + return r; + + r = log_set_max_level_from_string(t); + if (r == 0) + log_info("Setting log level to %s.", t); + return r; +} + +static int property_get_n_names( + 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; + + assert(bus); + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "u", (uint32_t) hashmap_size(m->units)); +} + +static int property_get_n_failed_units( + 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; + + assert(bus); + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "u", (uint32_t) set_size(m->failed_units)); +} + +static int property_get_n_jobs( + 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; + + assert(bus); + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "u", (uint32_t) hashmap_size(m->jobs)); +} + +static int property_get_progress( + 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; + double d; + + assert(bus); + assert(reply); + assert(m); + + if (dual_timestamp_is_set(&m->finish_timestamp)) + d = 1.0; + else + d = 1.0 - ((double) hashmap_size(m->jobs) / (double) m->n_installed_jobs); + + return sd_bus_message_append(reply, "d", d); +} + +static int property_get_system_state( + 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; + + assert(bus); + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "s", manager_state_to_string(manager_state(m))); +} + +static int property_set_runtime_watchdog( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *userdata, + sd_bus_error *error) { + + usec_t *t = userdata; + int r; + + assert(bus); + assert(value); + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + + r = sd_bus_message_read(value, "t", t); + if (r < 0) + return r; + + return watchdog_set_timeout(t); +} + +static int property_get_timer_slack_nsec( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "t", (uint64_t) prctl(PR_GET_TIMERSLACK)); +} + +static int method_get_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *path = NULL; + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (isempty(name)) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + pid_t pid; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + + u = manager_get_unit_by_pid(m, pid); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Client not member of any unit."); + } else { + u = manager_get_unit(m, name); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not loaded.", name); + } + + r = mac_selinux_unit_access_check(u, message, "status", error); + if (r < 0) + return r; + + path = unit_dbus_path(u); + if (!path) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", path); +} + +static int method_get_unit_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *path = NULL; + Manager *m = userdata; + pid_t pid; + Unit *u; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(pid_t) == sizeof(uint32_t)); + + /* Anyone can call this method */ + + r = sd_bus_message_read(message, "u", &pid); + if (r < 0) + return r; + if (pid < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid PID " PID_FMT, pid); + + if (pid == 0) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + } + + u = manager_get_unit_by_pid(m, pid); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_UNIT_FOR_PID, "PID "PID_FMT" does not belong to any loaded unit.", pid); + + r = mac_selinux_unit_access_check(u, message, "status", error); + if (r < 0) + return r; + + path = unit_dbus_path(u); + if (!path) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", path); +} + +static int method_load_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *path = NULL; + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (isempty(name)) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + pid_t pid; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + + u = manager_get_unit_by_pid(m, pid); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Client not member of any unit."); + } else { + r = manager_load_unit(m, name, NULL, error, &u); + if (r < 0) + return r; + } + + r = mac_selinux_unit_access_check(u, message, "status", error); + if (r < 0) + return r; + + path = unit_dbus_path(u); + if (!path) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", path); +} + +static int method_start_unit_generic(sd_bus_message *message, Manager *m, JobType job_type, bool reload_if_possible, sd_bus_error *error) { + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + r = manager_load_unit(m, name, NULL, error, &u); + if (r < 0) + return r; + + return bus_unit_method_start_generic(message, u, job_type, reload_if_possible, error); +} + +static int method_start_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_START, false, error); +} + +static int method_stop_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_STOP, false, error); +} + +static int method_reload_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_RELOAD, false, error); +} + +static int method_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_RESTART, false, error); +} + +static int method_try_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_TRY_RESTART, false, error); +} + +static int method_reload_or_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_RESTART, true, error); +} + +static int method_reload_or_try_restart_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_start_unit_generic(message, userdata, JOB_TRY_RESTART, true, error); +} + +static int method_start_unit_replace(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *old_name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &old_name); + if (r < 0) + return r; + + u = manager_get_unit(m, old_name); + if (!u || !u->job || u->job->type != JOB_START) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_JOB, "No job queued for unit %s", old_name); + + return method_start_unit_generic(message, m, JOB_START, false, error); +} + +static int method_kill_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + u = manager_get_unit(m, name); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); + + return bus_unit_method_kill(message, u, error); +} + +static int method_reset_failed_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + u = manager_get_unit(m, name); + if (!u) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s is not loaded.", name); + + return bus_unit_method_reset_failed(message, u, error); +} + +static int method_set_unit_properties(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + r = manager_load_unit(m, name, NULL, error, &u); + if (r < 0) + return r; + + r = bus_unit_check_load_state(u, error); + if (r < 0) + return r; + + return bus_unit_method_set_properties(message, u, error); +} + +static int transient_unit_from_message( + Manager *m, + sd_bus_message *message, + const char *name, + Unit **unit, + sd_bus_error *error) { + + UnitType t; + Unit *u; + int r; + + assert(m); + assert(message); + assert(name); + + t = unit_name_to_type(name); + if (t < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit name or type."); + + if (!unit_vtable[t]->can_transient) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit type %s does not support transient units.", unit_type_to_string(t)); + + r = manager_load_unit(m, name, NULL, error, &u); + if (r < 0) + return r; + + if (!unit_is_pristine(u)) + return sd_bus_error_setf(error, BUS_ERROR_UNIT_EXISTS, "Unit %s already exists.", name); + + /* OK, the unit failed to load and is unreferenced, now let's + * fill in the transient data instead */ + r = unit_make_transient(u); + if (r < 0) + return r; + + /* Set our properties */ + r = bus_unit_set_properties(u, message, UNIT_RUNTIME, false, error); + if (r < 0) + return r; + + /* Now load the missing bits of the unit we just created */ + manager_dispatch_load_queue(m); + + *unit = u; + + return 0; +} + +static int transient_aux_units_from_message( + Manager *m, + sd_bus_message *message, + sd_bus_error *error) { + + int r; + + assert(m); + assert(message); + + r = sd_bus_message_enter_container(message, 'a', "(sa(sv))"); + if (r < 0) + return r; + + while ((r = sd_bus_message_enter_container(message, 'r', "sa(sv)")) > 0) { + const char *name = NULL; + Unit *u; + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + r = transient_unit_from_message(m, message, name, &u, error); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + return 0; +} + +static int method_start_transient_unit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *name, *smode; + Manager *m = userdata; + JobMode mode; + Unit *u; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "start", error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "ss", &name, &smode); + if (r < 0) + return r; + + mode = job_mode_from_string(smode); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Job mode %s is invalid.", smode); + + r = bus_verify_manage_units_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = transient_unit_from_message(m, message, name, &u, error); + if (r < 0) + return r; + + r = transient_aux_units_from_message(m, message, error); + if (r < 0) + return r; + + /* Finally, start it */ + return bus_unit_queue_job(message, u, JOB_START, mode, false, error); +} + +static int method_get_job(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *path = NULL; + Manager *m = userdata; + uint32_t id; + Job *j; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = sd_bus_message_read(message, "u", &id); + if (r < 0) + return r; + + j = manager_get_job(m, id); + if (!j) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_JOB, "Job %u does not exist.", (unsigned) id); + + r = mac_selinux_unit_access_check(j->unit, message, "status", error); + if (r < 0) + return r; + + path = job_dbus_path(j); + if (!path) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", path); +} + +static int method_cancel_job(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + uint32_t id; + Job *j; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "u", &id); + if (r < 0) + return r; + + j = manager_get_job(m, id); + if (!j) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_JOB, "Job %u does not exist.", (unsigned) id); + + return bus_job_method_cancel(message, j, error); +} + +static int method_clear_jobs(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = bus_verify_manage_units_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + manager_clear_jobs(m); + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = bus_verify_manage_units_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + manager_reset_failed(m); + + return sd_bus_reply_method_return(message, NULL); +} + +static int list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_error *error, char **states) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + const char *k; + Iterator i; + Unit *u; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(ssssssouso)"); + if (r < 0) + return r; + + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + _cleanup_free_ char *unit_path = NULL, *job_path = NULL; + Unit *following; + + if (k != u->id) + continue; + + following = unit_following(u); + + if (!strv_isempty(states) && + !strv_contains(states, unit_load_state_to_string(u->load_state)) && + !strv_contains(states, unit_active_state_to_string(unit_active_state(u))) && + !strv_contains(states, unit_sub_state_to_string(u))) + continue; + + unit_path = unit_dbus_path(u); + if (!unit_path) + return -ENOMEM; + + if (u->job) { + job_path = job_dbus_path(u->job); + if (!job_path) + return -ENOMEM; + } + + r = sd_bus_message_append( + reply, "(ssssssouso)", + u->id, + unit_description(u), + unit_load_state_to_string(u->load_state), + unit_active_state_to_string(unit_active_state(u)), + unit_sub_state_to_string(u), + following ? following->id : "", + unit_path, + u->job ? u->job->id : 0, + u->job ? job_type_to_string(u->job->type) : "", + job_path ? job_path : "/"); + 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_list_units(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return list_units_filtered(message, userdata, error, NULL); +} + +static int method_list_units_filtered(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **states = NULL; + int r; + + r = sd_bus_message_read_strv(message, &states); + if (r < 0) + return r; + + return list_units_filtered(message, userdata, error, states); +} + +static int method_list_jobs(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + Iterator i; + Job *j; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(usssoo)"); + if (r < 0) + return r; + + HASHMAP_FOREACH(j, m->jobs, i) { + _cleanup_free_ char *unit_path = NULL, *job_path = NULL; + + job_path = job_dbus_path(j); + if (!job_path) + return -ENOMEM; + + unit_path = unit_dbus_path(j->unit); + if (!unit_path) + return -ENOMEM; + + r = sd_bus_message_append( + reply, "(usssoo)", + j->id, + j->unit->id, + job_type_to_string(j->type), + job_state_to_string(j->state), + job_path, + unit_path); + 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_subscribe(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + if (sd_bus_message_get_bus(message) == m->api_bus) { + + /* Note that direct bus connection subscribe by + * default, we only track peers on the API bus here */ + + if (!m->subscribed) { + r = sd_bus_track_new(sd_bus_message_get_bus(message), &m->subscribed, NULL, NULL); + if (r < 0) + return r; + } + + r = sd_bus_track_add_sender(m->subscribed, message); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_ALREADY_SUBSCRIBED, "Client is already subscribed."); + } + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_unsubscribe(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + if (sd_bus_message_get_bus(message) == m->api_bus) { + r = sd_bus_track_remove_sender(m->subscribed, message); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NOT_SUBSCRIBED, "Client is not subscribed."); + } + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_dump(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *dump = NULL; + _cleanup_fclose_ FILE *f = NULL; + Manager *m = userdata; + size_t size; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + f = open_memstream(&dump, &size); + if (!f) + return -ENOMEM; + + manager_dump_units(m, f, NULL); + manager_dump_jobs(m, f, NULL); + + r = fflush_and_check(f); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, "s", dump); +} + +static int method_refuse_snapshot(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Support for snapshots has been removed."); +} + +static int method_reload(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = bus_verify_reload_daemon_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + /* Instead of sending the reply back right away, we just + * remember that we need to and then send it after the reload + * is finished. That way the caller knows when the reload + * finished. */ + + assert(!m->queued_message); + r = sd_bus_message_new_method_return(message, &m->queued_message); + if (r < 0) + return r; + + m->exit_code = MANAGER_RELOAD; + + return 1; +} + +static int method_reexecute(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = bus_verify_reload_daemon_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + /* We don't send a reply back here, the client should + * just wait for us disconnecting. */ + + m->exit_code = MANAGER_REEXECUTE; + return 1; +} + +static int method_exit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "halt", error); + if (r < 0) + return r; + + /* Exit() (in contrast to SetExitCode()) is actually allowed even if + * we are running on the host. It will fall back on reboot() in + * systemd-shutdown if it cannot do the exit() because it isn't a + * container. */ + + m->exit_code = MANAGER_EXIT; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_reboot(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reboot", error); + if (r < 0) + return r; + + if (m->running_as != MANAGER_SYSTEM) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Reboot is only supported for system managers."); + + m->exit_code = MANAGER_REBOOT; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_poweroff(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "halt", error); + if (r < 0) + return r; + + if (m->running_as != MANAGER_SYSTEM) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Powering off is only supported for system managers."); + + m->exit_code = MANAGER_POWEROFF; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_halt(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "halt", error); + if (r < 0) + return r; + + if (m->running_as != MANAGER_SYSTEM) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Halt is only supported for system managers."); + + m->exit_code = MANAGER_HALT; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_kexec(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reboot", error); + if (r < 0) + return r; + + if (m->running_as != MANAGER_SYSTEM) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "KExec is only supported for system managers."); + + m->exit_code = MANAGER_KEXEC; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_switch_root(sd_bus_message *message, void *userdata, sd_bus_error *error) { + char *ri = NULL, *rt = NULL; + const char *root, *init; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reboot", error); + if (r < 0) + return r; + + if (m->running_as != MANAGER_SYSTEM) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Root switching is only supported by system manager."); + + r = sd_bus_message_read(message, "ss", &root, &init); + if (r < 0) + return r; + + if (path_equal(root, "/") || !path_is_absolute(root)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid switch root path %s", root); + + /* Safety check */ + if (isempty(init)) { + if (!path_is_os_tree(root)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified switch root path %s does not seem to be an OS tree. os-release file is missing.", root); + } else { + _cleanup_free_ char *p = NULL; + + if (!path_is_absolute(init)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid init path %s", init); + + p = strappend(root, init); + if (!p) + return -ENOMEM; + + if (access(p, X_OK) < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified init binary %s does not exist.", p); + } + + rt = strdup(root); + if (!rt) + return -ENOMEM; + + if (!isempty(init)) { + ri = strdup(init); + if (!ri) { + free(rt); + return -ENOMEM; + } + } + + free(m->switch_root); + m->switch_root = rt; + + free(m->switch_root_init); + m->switch_root_init = ri; + + m->exit_code = MANAGER_SWITCH_ROOT; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_set_environment(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **plus = NULL; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &plus); + if (r < 0) + return r; + if (!strv_env_is_valid(plus)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); + + r = bus_verify_set_environment_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = manager_environment_add(m, NULL, plus); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_unset_environment(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **minus = NULL; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &minus); + if (r < 0) + return r; + + if (!strv_env_name_or_assignment_is_valid(minus)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment variable names or assignments"); + + r = bus_verify_set_environment_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = manager_environment_add(m, minus, NULL); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_unset_and_set_environment(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **minus = NULL, **plus = NULL; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reload", error); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &minus); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &plus); + if (r < 0) + return r; + + if (!strv_env_name_or_assignment_is_valid(minus)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment variable names or assignments"); + if (!strv_env_is_valid(plus)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); + + r = bus_verify_set_environment_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = manager_environment_add(m, minus, plus); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_set_exit_code(sd_bus_message *message, void *userdata, sd_bus_error *error) { + uint8_t code; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "exit", error); + if (r < 0) + return r; + + r = sd_bus_message_read_basic(message, 'y', &code); + if (r < 0) + return r; + + if (m->running_as == MANAGER_SYSTEM && detect_container() <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "ExitCode can only be set for user service managers or in containers."); + + m->return_value = code; + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_list_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + UnitFileList *item; + Hashmap *h; + Iterator i; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + h = hashmap_new(&string_hash_ops); + if (!h) + return -ENOMEM; + + r = unit_file_get_list(m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, NULL, h); + if (r < 0) + goto fail; + + r = sd_bus_message_open_container(reply, 'a', "(ss)"); + if (r < 0) + goto fail; + + HASHMAP_FOREACH(item, h, i) { + + r = sd_bus_message_append(reply, "(ss)", item->path, unit_file_state_to_string(item->state)); + if (r < 0) + goto fail; + } + + unit_file_list_free(h); + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); + +fail: + unit_file_list_free(h); + return r; +} + +static int method_get_unit_file_state(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *name; + UnitFileState state; + UnitFileScope scope; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; + + r = unit_file_get_state(scope, NULL, name, &state); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, "s", unit_file_state_to_string(state)); +} + +static int method_get_default_target(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *default_target = NULL; + Manager *m = userdata; + UnitFileScope scope; + int r; + + assert(message); + assert(m); + + /* Anyone can call this method */ + + r = mac_selinux_access_check(message, "status", error); + if (r < 0) + return r; + + scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; + + r = unit_file_get_default(scope, NULL, &default_target); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, "s", default_target); +} + +static int send_unit_files_changed(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; + int r; + + assert(bus); + + r = sd_bus_message_new_signal(bus, &message, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "UnitFilesChanged"); + if (r < 0) + return r; + + return sd_bus_send(bus, message, NULL); +} + +static int reply_unit_file_changes_and_free( + Manager *m, + sd_bus_message *message, + int carries_install_info, + UnitFileChange *changes, + unsigned n_changes) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + unsigned i; + int r; + + if (n_changes > 0) { + r = bus_foreach_bus(m, NULL, send_unit_files_changed, NULL); + if (r < 0) + log_debug_errno(r, "Failed to send UnitFilesChanged signal: %m"); + } + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + goto fail; + + if (carries_install_info >= 0) { + r = sd_bus_message_append(reply, "b", carries_install_info); + if (r < 0) + goto fail; + } + + r = sd_bus_message_open_container(reply, 'a', "(sss)"); + if (r < 0) + goto fail; + + for (i = 0; i < n_changes; i++) { + r = sd_bus_message_append( + reply, "(sss)", + unit_file_change_type_to_string(changes[i].type), + changes[i].path, + changes[i].source); + if (r < 0) + goto fail; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto fail; + + unit_file_changes_free(changes, n_changes); + return sd_bus_send(NULL, reply, NULL); + +fail: + unit_file_changes_free(changes, n_changes); + return r; +} + +static int method_enable_unit_files_generic( + sd_bus_message *message, + Manager *m, + const char *verb, + int (*call)(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], bool force, UnitFileChange **changes, unsigned *n_changes), + bool carries_install_info, + sd_bus_error *error) { + + _cleanup_strv_free_ char **l = NULL; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + UnitFileScope scope; + int runtime, force, r; + + assert(message); + assert(m); + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "bb", &runtime, &force); + if (r < 0) + return r; + + r = bus_verify_manage_unit_files_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; + + r = call(scope, runtime, NULL, l, force, &changes, &n_changes); + if (r == -ESHUTDOWN) + return sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED, "Unit file is masked"); + if (r < 0) + return r; + + return reply_unit_file_changes_and_free(m, message, carries_install_info ? r : -1, changes, n_changes); +} + +static int method_enable_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_enable_unit_files_generic(message, userdata, "enable", unit_file_enable, true, error); +} + +static int method_reenable_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_enable_unit_files_generic(message, userdata, "enable", unit_file_reenable, true, error); +} + +static int method_link_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_enable_unit_files_generic(message, userdata, "enable", unit_file_link, false, error); +} + +static int unit_file_preset_without_mode(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes) { + return unit_file_preset(scope, runtime, root_dir, files, UNIT_FILE_PRESET_FULL, force, changes, n_changes); +} + +static int method_preset_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_enable_unit_files_generic(message, userdata, "enable", unit_file_preset_without_mode, true, error); +} + +static int method_mask_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_enable_unit_files_generic(message, userdata, "disable", unit_file_mask, false, error); +} + +static int method_preset_unit_files_with_mode(sd_bus_message *message, void *userdata, sd_bus_error *error) { + + _cleanup_strv_free_ char **l = NULL; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + Manager *m = userdata; + UnitFilePresetMode mm; + UnitFileScope scope; + int runtime, force, r; + const char *mode; + + assert(message); + assert(m); + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "sbb", &mode, &runtime, &force); + if (r < 0) + return r; + + if (isempty(mode)) + mm = UNIT_FILE_PRESET_FULL; + else { + mm = unit_file_preset_mode_from_string(mode); + if (mm < 0) + return -EINVAL; + } + + r = bus_verify_manage_unit_files_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; + + r = unit_file_preset(scope, runtime, NULL, l, mm, force, &changes, &n_changes); + if (r < 0) + return r; + + return reply_unit_file_changes_and_free(m, message, r, changes, n_changes); +} + +static int method_disable_unit_files_generic( + sd_bus_message *message, + Manager *m, const + char *verb, + int (*call)(UnitFileScope scope, bool runtime, const char *root_dir, char *files[], UnitFileChange **changes, unsigned *n_changes), + sd_bus_error *error) { + + _cleanup_strv_free_ char **l = NULL; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + UnitFileScope scope; + int r, runtime; + + assert(message); + assert(m); + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "b", &runtime); + if (r < 0) + return r; + + scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; + + r = bus_verify_manage_unit_files_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = call(scope, runtime, NULL, l, &changes, &n_changes); + if (r < 0) + return r; + + return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); +} + +static int method_disable_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_disable_unit_files_generic(message, userdata, "disable", unit_file_disable, error); +} + +static int method_unmask_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_disable_unit_files_generic(message, userdata, "enable", unit_file_unmask, error); +} + +static int method_set_default_target(sd_bus_message *message, void *userdata, sd_bus_error *error) { + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + Manager *m = userdata; + UnitFileScope scope; + const char *name; + int force, r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "enable", error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "sb", &name, &force); + if (r < 0) + return r; + + r = bus_verify_manage_unit_files_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; + + r = unit_file_set_default(scope, NULL, name, force, &changes, &n_changes); + if (r < 0) + return r; + + return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); +} + +static int method_preset_all_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + Manager *m = userdata; + UnitFilePresetMode mm; + UnitFileScope scope; + const char *mode; + int force, runtime, r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "enable", error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "sbb", &mode, &runtime, &force); + if (r < 0) + return r; + + if (isempty(mode)) + mm = UNIT_FILE_PRESET_FULL; + else { + mm = unit_file_preset_mode_from_string(mode); + if (mm < 0) + return -EINVAL; + } + + r = bus_verify_manage_unit_files_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; + + r = unit_file_preset_all(scope, runtime, NULL, mm, force, &changes, &n_changes); + if (r < 0) { + unit_file_changes_free(changes, n_changes); + return r; + } + + return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); +} + +static int method_add_dependency_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + Manager *m = userdata; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + UnitFileScope scope; + int runtime, force, r; + char *target; + char *type; + UnitDependency dep; + + assert(message); + assert(m); + + r = bus_verify_manage_unit_files_async(m, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "ssbb", &target, &type, &runtime, &force); + if (r < 0) + return r; + + dep = unit_dependency_from_string(type); + if (dep < 0) + return -EINVAL; + + scope = m->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER; + + r = unit_file_add_dependency(scope, runtime, NULL, l, target, dep, force, &changes, &n_changes); + if (r == -ESHUTDOWN) + return sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED, "Unit file is masked"); + if (r < 0) + return r; + + return reply_unit_file_changes_and_free(m, message, -1, changes, n_changes); +} + +const sd_bus_vtable bus_manager_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("Version", "s", property_get_version, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Features", "s", property_get_features, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Virtualization", "s", property_get_virtualization, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Architecture", "s", property_get_architecture, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Tainted", "s", property_get_tainted, 0, SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("FirmwareTimestamp", offsetof(Manager, firmware_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("LoaderTimestamp", offsetof(Manager, loader_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("KernelTimestamp", offsetof(Manager, kernel_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("InitRDTimestamp", offsetof(Manager, initrd_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("UserspaceTimestamp", offsetof(Manager, userspace_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("FinishTimestamp", offsetof(Manager, finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("SecurityStartTimestamp", offsetof(Manager, security_start_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("SecurityFinishTimestamp", offsetof(Manager, security_finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("GeneratorsStartTimestamp", offsetof(Manager, generators_start_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("GeneratorsFinishTimestamp", offsetof(Manager, generators_finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("UnitsLoadStartTimestamp", offsetof(Manager, units_load_start_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("UnitsLoadFinishTimestamp", offsetof(Manager, units_load_finish_timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_WRITABLE_PROPERTY("LogLevel", "s", property_get_log_level, property_set_log_level, 0, 0), + SD_BUS_WRITABLE_PROPERTY("LogTarget", "s", property_get_log_target, property_set_log_target, 0, 0), + SD_BUS_PROPERTY("NNames", "u", property_get_n_names, 0, 0), + SD_BUS_PROPERTY("NFailedUnits", "u", property_get_n_failed_units, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("NJobs", "u", property_get_n_jobs, 0, 0), + SD_BUS_PROPERTY("NInstalledJobs", "u", bus_property_get_unsigned, offsetof(Manager, n_installed_jobs), 0), + SD_BUS_PROPERTY("NFailedJobs", "u", bus_property_get_unsigned, offsetof(Manager, n_failed_jobs), 0), + SD_BUS_PROPERTY("Progress", "d", property_get_progress, 0, 0), + SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(Manager, environment), 0), + SD_BUS_PROPERTY("ConfirmSpawn", "b", bus_property_get_bool, offsetof(Manager, confirm_spawn), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ShowStatus", "b", bus_property_get_bool, offsetof(Manager, show_status), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("UnitPath", "as", NULL, offsetof(Manager, lookup_paths.unit_path), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultStandardOutput", "s", bus_property_get_exec_output, offsetof(Manager, default_std_output), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultStandardError", "s", bus_property_get_exec_output, offsetof(Manager, default_std_output), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_WRITABLE_PROPERTY("RuntimeWatchdogUSec", "t", bus_property_get_usec, property_set_runtime_watchdog, offsetof(Manager, runtime_watchdog), 0), + SD_BUS_WRITABLE_PROPERTY("ShutdownWatchdogUSec", "t", bus_property_get_usec, bus_property_set_usec, offsetof(Manager, shutdown_watchdog), 0), + SD_BUS_PROPERTY("ControlGroup", "s", NULL, offsetof(Manager, cgroup_root), 0), + SD_BUS_PROPERTY("SystemState", "s", property_get_system_state, 0, 0), + SD_BUS_PROPERTY("ExitCode", "y", bus_property_get_unsigned, offsetof(Manager, return_value), 0), + SD_BUS_PROPERTY("DefaultTimerAccuracyUSec", "t", bus_property_get_usec, offsetof(Manager, default_timer_accuracy_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultTimeoutStartUSec", "t", bus_property_get_usec, offsetof(Manager, default_timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultTimeoutStopUSec", "t", bus_property_get_usec, offsetof(Manager, default_timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultRestartUSec", "t", bus_property_get_usec, offsetof(Manager, default_restart_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultStartLimitInterval", "t", bus_property_get_usec, offsetof(Manager, default_start_limit_interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultStartLimitBurst", "u", bus_property_get_unsigned, offsetof(Manager, default_start_limit_burst), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultCPUAccounting", "b", bus_property_get_bool, offsetof(Manager, default_cpu_accounting), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultBlockIOAccounting", "b", bus_property_get_bool, offsetof(Manager, default_blockio_accounting), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultMemoryAccounting", "b", bus_property_get_bool, offsetof(Manager, default_memory_accounting), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultTasksAccounting", "b", bus_property_get_bool, offsetof(Manager, default_tasks_accounting), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitCPU", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitCPUSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CPU]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitFSIZE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitFSIZESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_FSIZE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitDATA", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitDATASoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_DATA]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitSTACK", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitSTACKSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_STACK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitCORE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitCORESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_CORE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitRSS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitRSSSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RSS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitNOFILE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitNOFILESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NOFILE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitAS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitASSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_AS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitNPROC", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitNPROCSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NPROC]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitMEMLOCK", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitMEMLOCKSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MEMLOCK]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitLOCKS", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitLOCKSSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_LOCKS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitSIGPENDING", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitSIGPENDINGSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_SIGPENDING]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitMSGQUEUE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitMSGQUEUESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_MSGQUEUE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitNICE", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitNICESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_NICE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitRTPRIO", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitRTPRIOSoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTPRIO]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitRTTIME", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultLimitRTTIMESoft", "t", bus_property_get_rlimit, offsetof(Manager, rlimit[RLIMIT_RTTIME]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultTasksMax", "t", NULL, offsetof(Manager, default_tasks_max), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimerSlackNSec", "t", property_get_timer_slack_nsec, 0, SD_BUS_VTABLE_PROPERTY_CONST), + + SD_BUS_METHOD("GetUnit", "s", "o", method_get_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetUnitByPID", "u", "o", method_get_unit_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("LoadUnit", "s", "o", method_load_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("StartUnit", "ss", "o", method_start_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("StartUnitReplace", "sss", "o", method_start_unit_replace, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("StopUnit", "ss", "o", method_stop_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ReloadUnit", "ss", "o", method_reload_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RestartUnit", "ss", "o", method_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("TryRestartUnit", "ss", "o", method_try_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ReloadOrRestartUnit", "ss", "o", method_reload_or_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ReloadOrTryRestartUnit", "ss", "o", method_reload_or_try_restart_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("KillUnit", "ssi", NULL, method_kill_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResetFailedUnit", "s", NULL, method_reset_failed_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetUnitProperties", "sba(sv)", NULL, method_set_unit_properties, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("StartTransientUnit", "ssa(sv)a(sa(sv))", "o", method_start_transient_unit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetJob", "u", "o", method_get_job, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CancelJob", "u", NULL, method_cancel_job, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ClearJobs", NULL, NULL, method_clear_jobs, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResetFailed", NULL, NULL, method_reset_failed, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListUnits", NULL, "a(ssssssouso)", method_list_units, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListUnitsFiltered", "as", "a(ssssssouso)", method_list_units_filtered, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListJobs", NULL, "a(usssoo)", method_list_jobs, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Subscribe", NULL, NULL, method_subscribe, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Unsubscribe", NULL, NULL, method_unsubscribe, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Dump", NULL, "s", method_dump, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CreateSnapshot", "sb", "o", method_refuse_snapshot, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RemoveSnapshot", "s", NULL, method_refuse_snapshot, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Reload", NULL, NULL, method_reload, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Reexecute", NULL, NULL, method_reexecute, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Exit", NULL, NULL, method_exit, 0), + SD_BUS_METHOD("Reboot", NULL, NULL, method_reboot, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), + SD_BUS_METHOD("PowerOff", NULL, NULL, method_poweroff, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), + SD_BUS_METHOD("Halt", NULL, NULL, method_halt, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), + SD_BUS_METHOD("KExec", NULL, NULL, method_kexec, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), + SD_BUS_METHOD("SwitchRoot", "ss", NULL, method_switch_root, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), + SD_BUS_METHOD("SetEnvironment", "as", NULL, method_set_environment, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("UnsetEnvironment", "as", NULL, method_unset_environment, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("UnsetAndSetEnvironment", "asas", NULL, method_unset_and_set_environment, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListUnitFiles", NULL, "a(ss)", method_list_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetUnitFileState", "s", "s", method_get_unit_file_state, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("EnableUnitFiles", "asbb", "ba(sss)", method_enable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("DisableUnitFiles", "asb", "a(sss)", method_disable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ReenableUnitFiles", "asbb", "ba(sss)", method_reenable_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("LinkUnitFiles", "asbb", "a(sss)", method_link_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("PresetUnitFiles", "asbb", "ba(sss)", method_preset_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("PresetUnitFilesWithMode", "assbb", "ba(sss)", method_preset_unit_files_with_mode, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MaskUnitFiles", "asbb", "a(sss)", method_mask_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("UnmaskUnitFiles", "asb", "a(sss)", method_unmask_unit_files, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetDefaultTarget", "sb", "a(sss)", method_set_default_target, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetDefaultTarget", NULL, "s", method_get_default_target, SD_BUS_VTABLE_UNPRIVILEGED), + 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_SIGNAL("UnitNew", "so", 0), + SD_BUS_SIGNAL("UnitRemoved", "so", 0), + SD_BUS_SIGNAL("JobNew", "uos", 0), + SD_BUS_SIGNAL("JobRemoved", "uoss", 0), + SD_BUS_SIGNAL("StartupFinished", "tttttt", 0), + SD_BUS_SIGNAL("UnitFilesChanged", NULL, 0), + SD_BUS_SIGNAL("Reloading", "b", 0), + + SD_BUS_VTABLE_END +}; + +static int send_finished(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; + usec_t *times = userdata; + int r; + + assert(bus); + assert(times); + + r = sd_bus_message_new_signal(bus, &message, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "StartupFinished"); + if (r < 0) + return r; + + r = sd_bus_message_append(message, "tttttt", times[0], times[1], times[2], times[3], times[4], times[5]); + if (r < 0) + return r; + + return sd_bus_send(bus, message, NULL); +} + +void bus_manager_send_finished( + Manager *m, + usec_t firmware_usec, + usec_t loader_usec, + usec_t kernel_usec, + usec_t initrd_usec, + usec_t userspace_usec, + usec_t total_usec) { + + int r; + + assert(m); + + r = bus_foreach_bus( + m, + NULL, + send_finished, + (usec_t[6]) { + firmware_usec, + loader_usec, + kernel_usec, + initrd_usec, + userspace_usec, + total_usec + }); + if (r < 0) + log_debug_errno(r, "Failed to send finished signal: %m"); +} + +static int send_reloading(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *message = NULL; + int r; + + assert(bus); + + r = sd_bus_message_new_signal(bus, &message, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", "Reloading"); + if (r < 0) + return r; + + r = sd_bus_message_append(message, "b", PTR_TO_INT(userdata)); + if (r < 0) + return r; + + return sd_bus_send(bus, message, NULL); +} + +void bus_manager_send_reloading(Manager *m, bool active) { + int r; + + assert(m); + + r = bus_foreach_bus(m, NULL, send_reloading, INT_TO_PTR(active)); + if (r < 0) + log_debug_errno(r, "Failed to send reloading signal: %m"); +} + +static int send_changed_signal(sd_bus *bus, void *userdata) { + assert(bus); + + return sd_bus_emit_properties_changed_strv(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + NULL); +} + +void bus_manager_send_change_signal(Manager *m) { + int r; + + assert(m); + + r = bus_foreach_bus(m, NULL, send_changed_signal, NULL); + if (r < 0) + log_debug_errno(r, "Failed to send manager change signal: %m"); +} diff --git a/src/libcore/dbus-manager.h b/src/libcore/dbus-manager.h new file mode 100644 index 0000000000..36a2e9481b --- /dev/null +++ b/src/libcore/dbus-manager.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "manager.h" + +extern const sd_bus_vtable bus_manager_vtable[]; + +void bus_manager_send_finished(Manager *m, usec_t firmware_usec, usec_t loader_usec, usec_t kernel_usec, usec_t initrd_usec, usec_t userspace_usec, usec_t total_usec); +void bus_manager_send_reloading(Manager *m, bool active); +void bus_manager_send_change_signal(Manager *m); diff --git a/src/libcore/dbus-mount.c b/src/libcore/dbus-mount.c new file mode 100644 index 0000000000..935db7c48b --- /dev/null +++ b/src/libcore/dbus-mount.c @@ -0,0 +1,211 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-kill.h" +#include "dbus-mount.h" +#include "mount.h" +#include "string-util.h" +#include "unit.h" + +static int property_get_what( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Mount *m = userdata; + const char *d; + + assert(bus); + assert(reply); + assert(m); + + if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.what) + d = m->parameters_proc_self_mountinfo.what; + else if (m->from_fragment && m->parameters_fragment.what) + d = m->parameters_fragment.what; + else + d = ""; + + return sd_bus_message_append(reply, "s", d); +} + +static int property_get_options( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Mount *m = userdata; + const char *d; + + assert(bus); + assert(reply); + assert(m); + + if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.options) + d = m->parameters_proc_self_mountinfo.options; + else if (m->from_fragment && m->parameters_fragment.options) + d = m->parameters_fragment.options; + else + d = ""; + + return sd_bus_message_append(reply, "s", d); +} + +static int property_get_type( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Mount *m = userdata; + const char *d; + + assert(bus); + assert(reply); + assert(m); + + if (m->from_proc_self_mountinfo && m->parameters_proc_self_mountinfo.fstype) + d = m->parameters_proc_self_mountinfo.fstype; + else if (m->from_fragment && m->parameters_fragment.fstype) + d = m->parameters_fragment.fstype; + else + d = ""; + + return sd_bus_message_append(reply, "s", d); +} + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, mount_result, MountResult); + +const sd_bus_vtable bus_mount_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Where", "s", NULL, offsetof(Mount, where), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("What", "s", property_get_what, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Options","s", property_get_options, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Type", "s", property_get_type, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Mount, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Mount, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Mount, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SloppyOptions", "b", bus_property_get_bool, offsetof(Mount, sloppy_options), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Mount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_EXEC_COMMAND_VTABLE("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_VTABLE("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_VTABLE("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_VTABLE_END +}; + +static int bus_mount_set_transient_property( + Mount *m, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + const char *new_property; + char **property; + char *p; + int r; + + assert(m); + assert(name); + assert(message); + + if (streq(name, "What")) + property = &m->parameters_fragment.what; + else if (streq(name, "Options")) + property = &m->parameters_fragment.options; + else if (streq(name, "Type")) + property = &m->parameters_fragment.fstype; + else + return 0; + + r = sd_bus_message_read(message, "s", &new_property); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + p = strdup(new_property); + if (!p) + return -ENOMEM; + + free(*property); + *property = p; + } + + return 1; +} + +int bus_mount_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Mount *m = MOUNT(u); + int r; + + assert(m); + assert(name); + assert(message); + + r = bus_cgroup_set_property(u, &m->cgroup_context, name, message, mode, error); + if (r != 0) + return r; + + if (u->transient && u->load_state == UNIT_STUB) { + /* This is a transient unit, let's load a little more */ + + r = bus_mount_set_transient_property(m, name, message, mode, error); + if (r != 0) + return r; + + r = bus_exec_context_set_transient_property(u, &m->exec_context, name, message, mode, error); + if (r != 0) + return r; + + r = bus_kill_context_set_transient_property(u, &m->kill_context, name, message, mode, error); + if (r != 0) + return r; + } + + return 0; +} + +int bus_mount_commit_properties(Unit *u) { + assert(u); + + unit_update_cgroup_members_masks(u); + unit_realize_cgroup(u); + + return 0; +} diff --git a/src/libcore/dbus-mount.h b/src/libcore/dbus-mount.h new file mode 100644 index 0000000000..f9844e449d --- /dev/null +++ b/src/libcore/dbus-mount.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "unit.h" + +extern const sd_bus_vtable bus_mount_vtable[]; + +int bus_mount_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); +int bus_mount_commit_properties(Unit *u); diff --git a/src/libcore/dbus-path.c b/src/libcore/dbus-path.c new file mode 100644 index 0000000000..1e153e503f --- /dev/null +++ b/src/libcore/dbus-path.c @@ -0,0 +1,86 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "bus-util.h" +#include "dbus-path.h" +#include "path.h" +#include "string-util.h" +#include "unit.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, path_result, PathResult); + +static int property_get_paths( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Path *p = userdata; + PathSpec *k; + int r; + + assert(bus); + assert(reply); + assert(p); + + r = sd_bus_message_open_container(reply, 'a', "(ss)"); + if (r < 0) + return r; + + LIST_FOREACH(spec, k, p->specs) { + r = sd_bus_message_append(reply, "(ss)", path_type_to_string(k->type), k->path); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_unit( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *p = userdata, *trigger; + + assert(bus); + assert(reply); + assert(p); + + trigger = UNIT_TRIGGER(p); + + return sd_bus_message_append(reply, "s", trigger ? trigger->id : ""); +} + +const sd_bus_vtable bus_path_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Unit", "s", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Paths", "a(ss)", property_get_paths, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MakeDirectory", "b", bus_property_get_bool, offsetof(Path, make_directory), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Path, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Path, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_VTABLE_END +}; diff --git a/src/libcore/dbus-path.h b/src/libcore/dbus-path.h new file mode 100644 index 0000000000..d3c19e0c2b --- /dev/null +++ b/src/libcore/dbus-path.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + + + +extern const sd_bus_vtable bus_path_vtable[]; diff --git a/src/libcore/dbus-scope.c b/src/libcore/dbus-scope.c new file mode 100644 index 0000000000..34ee9a8fa9 --- /dev/null +++ b/src/libcore/dbus-scope.c @@ -0,0 +1,229 @@ +/*** + 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 . +***/ + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-internal.h" +#include "bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-kill.h" +#include "dbus-scope.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "scope.h" +#include "selinux-access.h" +#include "unit.h" + +static int bus_scope_abandon(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Scope *s = userdata; + int r; + + assert(message); + assert(s); + + r = mac_selinux_unit_access_check(UNIT(s), message, "stop", error); + if (r < 0) + return r; + + r = bus_verify_manage_units_async(UNIT(s)->manager, message, error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = scope_abandon(s); + if (r == -ESTALE) + return sd_bus_error_setf(error, BUS_ERROR_SCOPE_NOT_RUNNING, "Scope %s is not running, cannot abandon.", UNIT(s)->id); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, scope_result, ScopeResult); + +const sd_bus_vtable bus_scope_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Controller", "s", NULL, offsetof(Scope, controller), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Scope, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Scope, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_SIGNAL("RequestStop", NULL, 0), + SD_BUS_METHOD("Abandon", NULL, NULL, bus_scope_abandon, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END +}; + +static int bus_scope_set_transient_property( + Scope *s, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(s); + assert(name); + assert(message); + + if (streq(name, "PIDs")) { + unsigned n = 0; + uint32_t pid; + + r = sd_bus_message_enter_container(message, 'a', "u"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "u", &pid)) > 0) { + + if (pid <= 1) + return -EINVAL; + + if (mode != UNIT_CHECK) { + r = unit_watch_pid(UNIT(s), pid); + if (r < 0 && r != -EEXIST) + return r; + } + + n++; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (n <= 0) + return -EINVAL; + + return 1; + + } else if (streq(name, "Controller")) { + const char *controller; + char *c; + + r = sd_bus_message_read(message, "s", &controller); + if (r < 0) + return r; + + if (!isempty(controller) && !service_name_is_valid(controller)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Controller '%s' is not a valid bus name.", controller); + + if (mode != UNIT_CHECK) { + if (isempty(controller)) + c = NULL; + else { + c = strdup(controller); + if (!c) + return -ENOMEM; + } + + free(s->controller); + s->controller = c; + } + + return 1; + + } else if (streq(name, "TimeoutStopUSec")) { + + if (mode != UNIT_CHECK) { + r = sd_bus_message_read(message, "t", &s->timeout_stop_usec); + if (r < 0) + return r; + + unit_write_drop_in_format(UNIT(s), mode, name, "[Scope]\nTimeoutStopSec="USEC_FMT"us\n", s->timeout_stop_usec); + } else { + r = sd_bus_message_skip(message, "t"); + if (r < 0) + return r; + } + + return 1; + } + + return 0; +} + +int bus_scope_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Scope *s = SCOPE(u); + int r; + + assert(s); + assert(name); + assert(message); + + r = bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); + if (r != 0) + return r; + + if (u->load_state == UNIT_STUB) { + /* While we are created we still accept PIDs */ + + r = bus_scope_set_transient_property(s, name, message, mode, error); + if (r != 0) + return r; + + r = bus_kill_context_set_transient_property(u, &s->kill_context, name, message, mode, error); + if (r != 0) + return r; + } + + return 0; +} + +int bus_scope_commit_properties(Unit *u) { + assert(u); + + unit_update_cgroup_members_masks(u); + unit_realize_cgroup(u); + + return 0; +} + +int bus_scope_send_request_stop(Scope *s) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *p = NULL; + int r; + + assert(s); + + if (!s->controller) + return 0; + + p = unit_dbus_path(UNIT(s)); + if (!p) + return -ENOMEM; + + r = sd_bus_message_new_signal( + UNIT(s)->manager->api_bus, + &m, + p, + "org.freedesktop.systemd1.Scope", + "RequestStop"); + if (r < 0) + return r; + + return sd_bus_send_to(UNIT(s)->manager->api_bus, m, /* s->controller */ NULL, NULL); +} diff --git a/src/libcore/dbus-scope.h b/src/libcore/dbus-scope.h new file mode 100644 index 0000000000..f96ddef0cf --- /dev/null +++ b/src/libcore/dbus-scope.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include "unit.h" + +extern const sd_bus_vtable bus_scope_vtable[]; + +int bus_scope_set_property(Unit *u, const char *name, sd_bus_message *i, UnitSetPropertiesMode mode, sd_bus_error *error); +int bus_scope_commit_properties(Unit *u); + +int bus_scope_send_request_stop(Scope *s); diff --git a/src/libcore/dbus-service.c b/src/libcore/dbus-service.c new file mode 100644 index 0000000000..03eecca911 --- /dev/null +++ b/src/libcore/dbus-service.c @@ -0,0 +1,322 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "alloc-util.h" +#include "async.h" +#include "bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-kill.h" +#include "dbus-service.h" +#include "fd-util.h" +#include "fileio.h" +#include "path-util.h" +#include "service.h" +#include "string-util.h" +#include "strv.h" +#include "unit.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, service_type, ServiceType); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, service_result, ServiceResult); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_restart, service_restart, ServiceRestart); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_notify_access, notify_access, NotifyAccess); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_failure_action, failure_action, FailureAction); + +const sd_bus_vtable bus_service_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Restart", "s", property_get_restart, offsetof(Service, restart), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PIDFile", "s", NULL, offsetof(Service, pid_file), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NotifyAccess", "s", property_get_notify_access, offsetof(Service, notify_access), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RestartUSec", "t", bus_property_get_usec, offsetof(Service, restart_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimeoutStartUSec", "t", bus_property_get_usec, offsetof(Service, timeout_start_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimeoutStopUSec", "t", bus_property_get_usec, offsetof(Service, timeout_stop_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RuntimeMaxUSec", "t", bus_property_get_usec, offsetof(Service, runtime_max_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("WatchdogUSec", "t", bus_property_get_usec, offsetof(Service, watchdog_usec), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("WatchdogTimestamp", offsetof(Service, watchdog_timestamp), 0), + /* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */ + SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("StartLimitAction", "s", property_get_failure_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("FailureAction", "s", property_get_failure_action, offsetof(Service, failure_action), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PermissionsStartOnly", "b", bus_property_get_bool, offsetof(Service, permissions_start_only), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RootDirectoryStartOnly", "b", bus_property_get_bool, offsetof(Service, root_directory_start_only), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RemainAfterExit", "b", bus_property_get_bool, offsetof(Service, remain_after_exit), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("GuessMainPID", "b", bus_property_get_bool, offsetof(Service, guess_main_pid), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("MainPID", "u", bus_property_get_pid, offsetof(Service, main_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Service, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("BusName", "s", NULL, offsetof(Service, bus_name), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("FileDescriptorStoreMax", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store_max), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NFileDescriptorStore", "u", bus_property_get_unsigned, offsetof(Service, n_fd_store), 0), + SD_BUS_PROPERTY("StatusText", "s", NULL, offsetof(Service, status_text), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("StatusErrno", "i", NULL, offsetof(Service, status_errno), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Service, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("USBFunctionDescriptors", "s", NULL, offsetof(Service, usb_function_descriptors), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("USBFunctionStrings", "s", NULL, offsetof(Service, usb_function_strings), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Service, exec_command[SERVICE_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStart", offsetof(Service, exec_command[SERVICE_EXEC_START]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Service, exec_command[SERVICE_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecReload", offsetof(Service, exec_command[SERVICE_EXEC_RELOAD]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStop", offsetof(Service, exec_command[SERVICE_EXEC_STOP]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_VTABLE_END +}; + +static int bus_service_set_transient_property( + Service *s, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(s); + assert(name); + assert(message); + + if (streq(name, "RemainAfterExit")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + s->remain_after_exit = b; + unit_write_drop_in_private_format(UNIT(s), mode, name, "RemainAfterExit=%s\n", yes_no(b)); + } + + return 1; + + } else if (streq(name, "Type")) { + const char *t; + ServiceType k; + + r = sd_bus_message_read(message, "s", &t); + if (r < 0) + return r; + + k = service_type_from_string(t); + if (k < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service type %s", t); + + if (mode != UNIT_CHECK) { + s->type = k; + unit_write_drop_in_private_format(UNIT(s), mode, name, "Type=%s\n", service_type_to_string(s->type)); + } + + return 1; + } else if (streq(name, "RuntimeMaxUSec")) { + usec_t u; + + r = sd_bus_message_read(message, "t", &u); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + s->runtime_max_usec = u; + unit_write_drop_in_private_format(UNIT(s), mode, name, "RuntimeMaxSec=" USEC_FMT "us\n", u); + } + + return 1; + + } else if (STR_IN_SET(name, + "StandardInputFileDescriptor", + "StandardOutputFileDescriptor", + "StandardErrorFileDescriptor")) { + int fd; + + r = sd_bus_message_read(message, "h", &fd); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + int copy; + + copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (copy < 0) + return -errno; + + if (streq(name, "StandardInputFileDescriptor")) { + asynchronous_close(s->stdin_fd); + s->stdin_fd = copy; + } else if (streq(name, "StandardOutputFileDescriptor")) { + asynchronous_close(s->stdout_fd); + s->stdout_fd = copy; + } else { + asynchronous_close(s->stderr_fd); + s->stderr_fd = copy; + } + + s->exec_context.stdio_as_fds = true; + } + + return 1; + + } else if (streq(name, "ExecStart")) { + unsigned n = 0; + + r = sd_bus_message_enter_container(message, 'a', "(sasb)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_enter_container(message, 'r', "sasb")) > 0) { + _cleanup_strv_free_ char **argv = NULL; + const char *path; + int b; + + r = sd_bus_message_read(message, "s", &path); + if (r < 0) + return r; + + if (!path_is_absolute(path)) + return sd_bus_error_set_errnof(error, EINVAL, "Path %s is not absolute.", path); + + r = sd_bus_message_read_strv(message, &argv); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + ExecCommand *c; + + c = new0(ExecCommand, 1); + if (!c) + return -ENOMEM; + + c->path = strdup(path); + if (!c->path) { + free(c); + return -ENOMEM; + } + + c->argv = argv; + argv = NULL; + + c->ignore = b; + + path_kill_slashes(c->path); + exec_command_append_list(&s->exec_command[SERVICE_EXEC_START], c); + } + + n++; + } + + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *f = NULL; + ExecCommand *c; + size_t size = 0; + + if (n == 0) + s->exec_command[SERVICE_EXEC_START] = exec_command_free_list(s->exec_command[SERVICE_EXEC_START]); + + f = open_memstream(&buf, &size); + if (!f) + return -ENOMEM; + + fputs("ExecStart=\n", f); + + LIST_FOREACH(command, c, s->exec_command[SERVICE_EXEC_START]) { + _cleanup_free_ char *a; + + a = strv_join_quoted(c->argv); + if (!a) + return -ENOMEM; + + fprintf(f, "ExecStart=%s@%s %s\n", + c->ignore ? "-" : "", + c->path, + a); + } + + r = fflush_and_check(f); + if (r < 0) + return r; + unit_write_drop_in_private(UNIT(s), mode, name, buf); + } + + return 1; + } + + return 0; +} + +int bus_service_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Service *s = SERVICE(u); + int r; + + assert(s); + assert(name); + assert(message); + + r = bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); + if (r != 0) + return r; + + if (u->transient && u->load_state == UNIT_STUB) { + /* This is a transient unit, let's load a little more */ + + r = bus_service_set_transient_property(s, name, message, mode, error); + if (r != 0) + return r; + + r = bus_exec_context_set_transient_property(u, &s->exec_context, name, message, mode, error); + if (r != 0) + return r; + + r = bus_kill_context_set_transient_property(u, &s->kill_context, name, message, mode, error); + if (r != 0) + return r; + } + + return 0; +} + +int bus_service_commit_properties(Unit *u) { + assert(u); + + unit_update_cgroup_members_masks(u); + unit_realize_cgroup(u); + + return 0; +} diff --git a/src/libcore/dbus-service.h b/src/libcore/dbus-service.h new file mode 100644 index 0000000000..291959325c --- /dev/null +++ b/src/libcore/dbus-service.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "unit.h" + +extern const sd_bus_vtable bus_service_vtable[]; + +int bus_service_set_property(Unit *u, const char *name, sd_bus_message *i, UnitSetPropertiesMode mode, sd_bus_error *error); +int bus_service_commit_properties(Unit *u); diff --git a/src/libcore/dbus-slice.c b/src/libcore/dbus-slice.c new file mode 100644 index 0000000000..e37f50b283 --- /dev/null +++ b/src/libcore/dbus-slice.c @@ -0,0 +1,52 @@ +/*** + 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 . +***/ + +#include "dbus-cgroup.h" +#include "dbus-slice.h" +#include "slice.h" +#include "unit.h" + +const sd_bus_vtable bus_slice_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_VTABLE_END +}; + +int bus_slice_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Slice *s = SLICE(u); + + assert(name); + assert(u); + + return bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); +} + +int bus_slice_commit_properties(Unit *u) { + assert(u); + + unit_update_cgroup_members_masks(u); + unit_realize_cgroup(u); + + return 0; +} diff --git a/src/libcore/dbus-slice.h b/src/libcore/dbus-slice.h new file mode 100644 index 0000000000..8e4cabbf8a --- /dev/null +++ b/src/libcore/dbus-slice.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include "unit.h" + +extern const sd_bus_vtable bus_slice_vtable[]; + +int bus_slice_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); +int bus_slice_commit_properties(Unit *u); diff --git a/src/libcore/dbus-socket.c b/src/libcore/dbus-socket.c new file mode 100644 index 0000000000..d33e494f6b --- /dev/null +++ b/src/libcore/dbus-socket.c @@ -0,0 +1,182 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "alloc-util.h" +#include "bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-socket.h" +#include "socket.h" +#include "string-util.h" +#include "unit.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, socket_result, SocketResult); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_bind_ipv6_only, socket_address_bind_ipv6_only, SocketAddressBindIPv6Only); + +static int property_get_listen( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + + Socket *s = SOCKET(userdata); + SocketPort *p; + int r; + + assert(bus); + assert(reply); + assert(s); + + r = sd_bus_message_open_container(reply, 'a', "(ss)"); + if (r < 0) + return r; + + LIST_FOREACH(port, p, s->ports) { + _cleanup_free_ char *address = NULL; + const char *a; + + switch (p->type) { + case SOCKET_SOCKET: { + r = socket_address_print(&p->address, &address); + if (r) + return r; + + a = address; + break; + } + + case SOCKET_SPECIAL: + case SOCKET_MQUEUE: + case SOCKET_FIFO: + case SOCKET_USB_FUNCTION: + a = p->path; + break; + + default: + assert_not_reached("Unknown socket type"); + } + + r = sd_bus_message_append(reply, "(ss)", socket_port_type_to_string(p), a); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + + +static int property_get_fdname( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Socket *s = SOCKET(userdata); + + assert(bus); + assert(reply); + assert(s); + + return sd_bus_message_append(reply, "s", socket_fdname(s)); +} + +const sd_bus_vtable bus_socket_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("BindIPv6Only", "s", property_get_bind_ipv6_only, offsetof(Socket, bind_ipv6_only), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Backlog", "u", bus_property_get_unsigned, offsetof(Socket, backlog), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Socket, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("BindToDevice", "s", NULL, offsetof(Socket, bind_to_device), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SocketUser", "s", NULL, offsetof(Socket, user), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SocketGroup", "s", NULL, offsetof(Socket, group), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SocketMode", "u", bus_property_get_mode, offsetof(Socket, socket_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DirectoryMode", "u", bus_property_get_mode, offsetof(Socket, directory_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Accept", "b", bus_property_get_bool, offsetof(Socket, accept), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Writable", "b", bus_property_get_bool, offsetof(Socket, writable), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("KeepAlive", "b", bus_property_get_bool, offsetof(Socket, keep_alive), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("KeepAliveTimeUSec", "t", bus_property_get_usec, offsetof(Socket, keep_alive_time), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("KeepAliveIntervalUSec", "t", bus_property_get_usec, offsetof(Socket, keep_alive_interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("KeepAliveProbes", "u", bus_property_get_unsigned, offsetof(Socket, keep_alive_cnt), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DeferAcceptUSec" , "t", bus_property_get_usec, offsetof(Socket, defer_accept), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NoDelay", "b", bus_property_get_bool, offsetof(Socket, no_delay), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Priority", "i", bus_property_get_int, offsetof(Socket, priority), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ReceiveBuffer", "t", bus_property_get_size, offsetof(Socket, receive_buffer), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SendBuffer", "t", bus_property_get_size, offsetof(Socket, send_buffer), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("IPTOS", "i", bus_property_get_int, offsetof(Socket, ip_tos), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("IPTTL", "i", bus_property_get_int, offsetof(Socket, ip_ttl), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PipeSize", "t", bus_property_get_size, offsetof(Socket, pipe_size), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("FreeBind", "b", bus_property_get_bool, offsetof(Socket, free_bind), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Transparent", "b", bus_property_get_bool, offsetof(Socket, transparent), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Broadcast", "b", bus_property_get_bool, offsetof(Socket, broadcast), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PassCredentials", "b", bus_property_get_bool, offsetof(Socket, pass_cred), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PassSecurity", "b", bus_property_get_bool, offsetof(Socket, pass_sec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RemoveOnStop", "b", bus_property_get_bool, offsetof(Socket, remove_on_stop), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Listen", "a(ss)", property_get_listen, 0, SD_BUS_VTABLE_PROPERTY_CONST), + 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("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), + SD_BUS_PROPERTY("SmackLabel", "s", NULL, offsetof(Socket, smack), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SmackLabelIPIn", "s", NULL, offsetof(Socket, smack_ip_in), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SmackLabelIPOut", "s", NULL, offsetof(Socket, smack_ip_out), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Socket, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Socket, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("NConnections", "u", bus_property_get_unsigned, offsetof(Socket, n_connections), 0), + SD_BUS_PROPERTY("NAccepted", "u", bus_property_get_unsigned, offsetof(Socket, n_accepted), 0), + SD_BUS_PROPERTY("FileDescriptorName", "s", property_get_fdname, 0, 0), + SD_BUS_PROPERTY("SocketProtocol", "i", bus_property_get_int, offsetof(Socket, socket_protocol), SD_BUS_VTABLE_PROPERTY_CONST), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPre", offsetof(Socket, exec_command[SOCKET_EXEC_START_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStartPost", offsetof(Socket, exec_command[SOCKET_EXEC_START_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPre", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_PRE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Socket, exec_command[SOCKET_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_VTABLE_END +}; + +int bus_socket_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Socket *s = SOCKET(u); + + assert(s); + assert(name); + assert(message); + + return bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); +} + +int bus_socket_commit_properties(Unit *u) { + assert(u); + + unit_update_cgroup_members_masks(u); + unit_realize_cgroup(u); + + return 0; +} diff --git a/src/libcore/dbus-socket.h b/src/libcore/dbus-socket.h new file mode 100644 index 0000000000..a31906feea --- /dev/null +++ b/src/libcore/dbus-socket.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "unit.h" + +extern const sd_bus_vtable bus_socket_vtable[]; + +int bus_socket_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); +int bus_socket_commit_properties(Unit *u); diff --git a/src/libcore/dbus-swap.c b/src/libcore/dbus-swap.c new file mode 100644 index 0000000000..292f8738c6 --- /dev/null +++ b/src/libcore/dbus-swap.c @@ -0,0 +1,115 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Maarten Lankhorst + + 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 . +***/ + +#include "bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-swap.h" +#include "string-util.h" +#include "swap.h" +#include "unit.h" + +static int property_get_priority( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Swap *s = SWAP(userdata); + int p; + + assert(bus); + assert(reply); + assert(s); + + if (s->from_proc_swaps) + p = s->parameters_proc_swaps.priority; + else if (s->from_fragment) + p = s->parameters_fragment.priority; + else + p = -1; + + return sd_bus_message_append(reply, "i", p); +} + +static int property_get_options( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Swap *s = SWAP(userdata); + const char *options = NULL; + + assert(bus); + assert(reply); + assert(s); + + if (s->from_fragment) + options = s->parameters_fragment.options; + + return sd_bus_message_append(reply, "s", options); +} + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, swap_result, SwapResult); + +const sd_bus_vtable bus_swap_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("What", "s", NULL, offsetof(Swap, what), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Priority", "i", property_get_priority, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Options", "s", property_get_options, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("TimeoutUSec", "t", bus_property_get_usec, offsetof(Swap, timeout_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ControlPID", "u", bus_property_get_pid, offsetof(Swap, control_pid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Swap, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_EXEC_COMMAND_VTABLE("ExecActivate", offsetof(Swap, exec_command[SWAP_EXEC_ACTIVATE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + BUS_EXEC_COMMAND_VTABLE("ExecDeactivate", offsetof(Swap, exec_command[SWAP_EXEC_DEACTIVATE]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_VTABLE_END +}; + +int bus_swap_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Swap *s = SWAP(u); + + assert(s); + assert(name); + assert(message); + + return bus_cgroup_set_property(u, &s->cgroup_context, name, message, mode, error); +} + +int bus_swap_commit_properties(Unit *u) { + assert(u); + + unit_update_cgroup_members_masks(u); + unit_realize_cgroup(u); + + return 0; +} diff --git a/src/libcore/dbus-swap.h b/src/libcore/dbus-swap.h new file mode 100644 index 0000000000..19151fb771 --- /dev/null +++ b/src/libcore/dbus-swap.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Maarten Lankhorst + + 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 . +***/ + +#include + +#include "unit.h" + +extern const sd_bus_vtable bus_swap_vtable[]; + +int bus_swap_set_property(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); +int bus_swap_commit_properties(Unit *u); diff --git a/src/libcore/dbus-target.c b/src/libcore/dbus-target.c new file mode 100644 index 0000000000..6858b1ce72 --- /dev/null +++ b/src/libcore/dbus-target.c @@ -0,0 +1,26 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "dbus-target.h" +#include "unit.h" + +const sd_bus_vtable bus_target_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_VTABLE_END +}; diff --git a/src/libcore/dbus-target.h b/src/libcore/dbus-target.h new file mode 100644 index 0000000000..c97a9d626e --- /dev/null +++ b/src/libcore/dbus-target.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +extern const sd_bus_vtable bus_target_vtable[]; diff --git a/src/libcore/dbus-timer.c b/src/libcore/dbus-timer.c new file mode 100644 index 0000000000..bc121b83a2 --- /dev/null +++ b/src/libcore/dbus-timer.c @@ -0,0 +1,352 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "alloc-util.h" +#include "bus-util.h" +#include "dbus-timer.h" +#include "strv.h" +#include "timer.h" +#include "unit.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_result, timer_result, TimerResult); + +static int property_get_monotonic_timers( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Timer *t = userdata; + TimerValue *v; + int r; + + assert(bus); + assert(reply); + assert(t); + + r = sd_bus_message_open_container(reply, 'a', "(stt)"); + if (r < 0) + return r; + + LIST_FOREACH(value, v, t->values) { + _cleanup_free_ char *buf = NULL; + const char *s; + size_t l; + + if (v->base == TIMER_CALENDAR) + continue; + + s = timer_base_to_string(v->base); + assert(endswith(s, "Sec")); + + /* s/Sec/USec/ */ + l = strlen(s); + buf = new(char, l+2); + if (!buf) + return -ENOMEM; + + memcpy(buf, s, l-3); + memcpy(buf+l-3, "USec", 5); + + r = sd_bus_message_append(reply, "(stt)", buf, v->value, v->next_elapse); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_calendar_timers( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Timer *t = userdata; + TimerValue *v; + int r; + + assert(bus); + assert(reply); + assert(t); + + r = sd_bus_message_open_container(reply, 'a', "(sst)"); + if (r < 0) + return r; + + LIST_FOREACH(value, v, t->values) { + _cleanup_free_ char *buf = NULL; + + if (v->base != TIMER_CALENDAR) + continue; + + r = calendar_spec_to_string(v->calendar_spec, &buf); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "(sst)", timer_base_to_string(v->base), buf, v->next_elapse); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_unit( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata, *trigger; + + assert(bus); + assert(reply); + assert(u); + + trigger = UNIT_TRIGGER(u); + + return sd_bus_message_append(reply, "s", trigger ? trigger->id : ""); +} + +static int property_get_next_elapse_monotonic( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Timer *t = userdata; + usec_t x; + + assert(bus); + assert(reply); + assert(t); + + if (t->next_elapse_monotonic_or_boottime <= 0) + x = 0; + else if (t->wake_system) { + usec_t a, b; + + a = now(CLOCK_MONOTONIC); + b = now(CLOCK_BOOTTIME); + + if (t->next_elapse_monotonic_or_boottime + a > b) + x = t->next_elapse_monotonic_or_boottime + a - b; + else + x = 0; + } else + x = t->next_elapse_monotonic_or_boottime; + + return sd_bus_message_append(reply, "t", x); +} + +const sd_bus_vtable bus_timer_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Unit", "s", property_get_unit, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TimersMonotonic", "a(stt)", property_get_monotonic_timers, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_PROPERTY("TimersCalendar", "a(sst)", property_get_calendar_timers, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_PROPERTY("NextElapseUSecRealtime", "t", bus_property_get_usec, offsetof(Timer, next_elapse_realtime), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("NextElapseUSecMonotonic", "t", property_get_next_elapse_monotonic, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("LastTriggerUSec", offsetof(Timer, last_trigger), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Timer, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("AccuracyUSec", "t", bus_property_get_usec, offsetof(Timer, accuracy_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RandomizedDelayUSec", "t", bus_property_get_usec, offsetof(Timer, random_usec), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Persistent", "b", bus_property_get_bool, offsetof(Timer, persistent), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("WakeSystem", "b", bus_property_get_bool, offsetof(Timer, wake_system), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RemainAfterElapse", "b", bus_property_get_bool, offsetof(Timer, remain_after_elapse), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_VTABLE_END +}; + +static int bus_timer_set_transient_property( + Timer *t, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(t); + assert(name); + assert(message); + + if (STR_IN_SET(name, + "OnActiveSec", + "OnBootSec", + "OnStartupSec", + "OnUnitActiveSec", + "OnUnitInactiveSec")) { + + TimerValue *v; + TimerBase b = _TIMER_BASE_INVALID; + usec_t u = 0; + + b = timer_base_from_string(name); + if (b < 0) + return -EINVAL; + + r = sd_bus_message_read(message, "t", &u); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + char time[FORMAT_TIMESPAN_MAX]; + + unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, format_timespan(time, sizeof(time), u, USEC_PER_MSEC)); + + v = new0(TimerValue, 1); + if (!v) + return -ENOMEM; + + v->base = b; + v->value = u; + + LIST_PREPEND(value, t->values, v); + } + + return 1; + + } else if (streq(name, "OnCalendar")) { + + TimerValue *v; + CalendarSpec *c = NULL; + const char *str; + + r = sd_bus_message_read(message, "s", &str); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + r = calendar_spec_from_string(str, &c); + if (r < 0) + return r; + + unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, str); + + v = new0(TimerValue, 1); + if (!v) { + calendar_spec_free(c); + return -ENOMEM; + } + + v->base = TIMER_CALENDAR; + v->calendar_spec = c; + + LIST_PREPEND(value, t->values, v); + } + + return 1; + + } else if (STR_IN_SET(name, "AccuracyUSec", "AccuracySec")) { + usec_t u = 0; + + if (streq(name, "AccuracySec")) + log_notice("Client is using obsolete AccuracySec= transient property, please use AccuracyUSec= instead."); + + r = sd_bus_message_read(message, "t", &u); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + t->accuracy_usec = u; + unit_write_drop_in_private_format(UNIT(t), mode, name, "AccuracySec=" USEC_FMT "us\n", u); + } + + return 1; + + } else if (streq(name, "RandomizedDelayUSec")) { + usec_t u = 0; + + r = sd_bus_message_read(message, "t", &u); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + t->random_usec = u; + unit_write_drop_in_private_format(UNIT(t), mode, name, "RandomizedDelaySec=" USEC_FMT "us\n", u); + } + + return 1; + + } else if (streq(name, "WakeSystem")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + t->wake_system = b; + unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, yes_no(b)); + } + + return 1; + + } else if (streq(name, "RemainAfterElapse")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + t->remain_after_elapse = b; + unit_write_drop_in_private_format(UNIT(t), mode, name, "%s=%s\n", name, yes_no(b)); + } + + return 1; + } + + return 0; +} + +int bus_timer_set_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + Timer *t = TIMER(u); + int r; + + assert(t); + assert(name); + assert(message); + + if (u->transient && u->load_state == UNIT_STUB) { + r = bus_timer_set_transient_property(t, name, message, mode, error); + if (r != 0) + return r; + } + + return 0; +} diff --git a/src/libcore/dbus-timer.h b/src/libcore/dbus-timer.h new file mode 100644 index 0000000000..505fb5df72 --- /dev/null +++ b/src/libcore/dbus-timer.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "unit.h" + +extern const sd_bus_vtable bus_timer_vtable[]; + +int bus_timer_set_property(Unit *u, const char *name, sd_bus_message *i, UnitSetPropertiesMode mode, sd_bus_error *error); diff --git a/src/libcore/dbus-unit.c b/src/libcore/dbus-unit.c new file mode 100644 index 0000000000..9a09f59fc2 --- /dev/null +++ b/src/libcore/dbus-unit.c @@ -0,0 +1,1276 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "cgroup-util.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "locale-util.h" +#include "log.h" +#include "selinux-access.h" +#include "special.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_load_state, unit_load_state, UnitLoadState); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_job_mode, job_mode, JobMode); +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_failure_action, failure_action, FailureAction); + +static int property_get_names( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + Iterator i; + const char *t; + int r; + + assert(bus); + assert(reply); + assert(u); + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + return r; + + SET_FOREACH(t, u->names, i) { + r = sd_bus_message_append(reply, "s", t); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_following( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata, *f; + + assert(bus); + assert(reply); + assert(u); + + f = unit_following(u); + return sd_bus_message_append(reply, "s", f ? f->id : ""); +} + +static int property_get_dependencies( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Set *s = *(Set**) userdata; + Iterator j; + Unit *u; + int r; + + assert(bus); + assert(reply); + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + return r; + + SET_FOREACH(u, s, j) { + r = sd_bus_message_append(reply, "s", u->id); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_obsolete_dependencies( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + /* For dependency types we don't support anymore always return an empty array */ + return sd_bus_message_append(reply, "as", 0); +} + +static int property_get_description( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "s", unit_description(u)); +} + +static int property_get_active_state( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "s", unit_active_state_to_string(unit_active_state(u))); +} + +static int property_get_sub_state( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "s", unit_sub_state_to_string(u)); +} + +static int property_get_unit_file_preset( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + int r; + + assert(bus); + assert(reply); + assert(u); + + r = unit_get_unit_file_preset(u); + + return sd_bus_message_append(reply, "s", + r < 0 ? "": + r > 0 ? "enabled" : "disabled"); +} + +static int property_get_unit_file_state( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "s", unit_file_state_to_string(unit_get_unit_file_state(u))); +} + +static int property_get_can_start( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "b", unit_can_start(u) && !u->refuse_manual_start); +} + +static int property_get_can_stop( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + /* On the lower levels we assume that every unit we can start + * we can also stop */ + + return sd_bus_message_append(reply, "b", unit_can_start(u) && !u->refuse_manual_stop); +} + +static int property_get_can_reload( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "b", unit_can_reload(u)); +} + +static int property_get_can_isolate( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "b", unit_can_isolate(u) && !u->refuse_manual_start); +} + +static int property_get_job( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_free_ char *p = NULL; + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + if (!u->job) + return sd_bus_message_append(reply, "(uo)", 0, "/"); + + p = job_dbus_path(u->job); + if (!p) + return -ENOMEM; + + return sd_bus_message_append(reply, "(uo)", u->job->id, p); +} + +static int property_get_need_daemon_reload( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "b", unit_need_daemon_reload(u)); +} + +static int property_get_conditions( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + const char *(*to_string)(ConditionType type) = NULL; + Condition **list = userdata, *c; + int r; + + assert(bus); + assert(reply); + assert(list); + + to_string = streq(property, "Asserts") ? assert_type_to_string : condition_type_to_string; + + r = sd_bus_message_open_container(reply, 'a', "(sbbsi)"); + if (r < 0) + return r; + + LIST_FOREACH(conditions, c, *list) { + int tristate; + + tristate = + c->result == CONDITION_UNTESTED ? 0 : + c->result == CONDITION_SUCCEEDED ? 1 : -1; + + r = sd_bus_message_append(reply, "(sbbsi)", + to_string(c->type), + c->trigger, c->negate, + c->parameter, tristate); + if (r < 0) + return r; + + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_load_error( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + if (u->load_error != 0) + sd_bus_error_set_errno(&e, u->load_error); + + return sd_bus_message_append(reply, "(ss)", e.name, e.message); +} + +static int bus_verify_manage_units_async_full( + Unit *u, + const char *verb, + int capability, + const char *polkit_message, + sd_bus_message *call, + sd_bus_error *error) { + + const char *details[9] = { + "unit", u->id, + "verb", verb, + }; + + if (polkit_message) { + details[4] = "polkit.message"; + details[5] = polkit_message; + details[6] = "polkit.gettext_domain"; + details[7] = GETTEXT_PACKAGE; + } + + return bus_verify_polkit_async(call, capability, "org.freedesktop.systemd1.manage-units", details, false, UID_INVALID, &u->manager->polkit_registry, error); +} + +int bus_unit_method_start_generic( + sd_bus_message *message, + Unit *u, + JobType job_type, + bool reload_if_possible, + sd_bus_error *error) { + + const char *smode; + JobMode mode; + _cleanup_free_ char *verb = NULL; + static const char *const polkit_message_for_job[_JOB_TYPE_MAX] = { + [JOB_START] = N_("Authentication is required to start '$(unit)'."), + [JOB_STOP] = N_("Authentication is required to stop '$(unit)'."), + [JOB_RELOAD] = N_("Authentication is required to reload '$(unit)'."), + [JOB_RESTART] = N_("Authentication is required to restart '$(unit)'."), + [JOB_TRY_RESTART] = N_("Authentication is required to restart '$(unit)'."), + }; + int r; + + assert(message); + assert(u); + assert(job_type >= 0 && job_type < _JOB_TYPE_MAX); + + r = mac_selinux_unit_access_check( + u, message, + job_type_to_access_method(job_type), + error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "s", &smode); + if (r < 0) + return r; + + mode = job_mode_from_string(smode); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Job mode %s invalid", smode); + + if (reload_if_possible) + verb = strjoin("reload-or-", job_type_to_string(job_type), NULL); + else + verb = strdup(job_type_to_string(job_type)); + if (!verb) + return -ENOMEM; + + r = bus_verify_manage_units_async_full( + u, + verb, + CAP_SYS_ADMIN, + job_type < _JOB_TYPE_MAX ? polkit_message_for_job[job_type] : NULL, + message, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + return bus_unit_queue_job(message, u, job_type, mode, reload_if_possible, error); +} + +static int method_start(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_START, false, error); +} + +static int method_stop(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_STOP, false, error); +} + +static int method_reload(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_RELOAD, false, error); +} + +static int method_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_RESTART, false, error); +} + +static int method_try_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_TRY_RESTART, false, error); +} + +static int method_reload_or_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_RESTART, true, error); +} + +static int method_reload_or_try_restart(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return bus_unit_method_start_generic(message, userdata, JOB_TRY_RESTART, true, error); +} + +int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Unit *u = userdata; + const char *swho; + int32_t signo; + KillWho who; + int r; + + assert(message); + assert(u); + + r = mac_selinux_unit_access_check(u, message, "stop", error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "si", &swho, &signo); + if (r < 0) + return r; + + if (isempty(swho)) + who = KILL_ALL; + else { + who = kill_who_from_string(swho); + if (who < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid who argument %s", swho); + } + + if (signo <= 0 || signo >= _NSIG) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Signal number out of range."); + + r = bus_verify_manage_units_async_full( + u, + "kill", + CAP_KILL, + N_("Authentication is required to kill '$(unit)'."), + message, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = unit_kill(u, who, signo, error); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Unit *u = userdata; + int r; + + assert(message); + assert(u); + + r = mac_selinux_unit_access_check(u, message, "reload", error); + if (r < 0) + return r; + + r = bus_verify_manage_units_async_full( + u, + "reset-failed", + CAP_SYS_ADMIN, + N_("Authentication is required to reset the \"failed\" state of '$(unit)'."), + message, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + unit_reset_failed(u); + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Unit *u = userdata; + int runtime, r; + + assert(message); + assert(u); + + r = mac_selinux_unit_access_check(u, message, "start", error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "b", &runtime); + if (r < 0) + return r; + + r = bus_verify_manage_units_async_full( + u, + "set-property", + CAP_SYS_ADMIN, + N_("Authentication is required to set properties on '$(unit)'."), + message, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + r = bus_unit_set_properties(u, message, runtime ? UNIT_RUNTIME : UNIT_PERSISTENT, true, error); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +const sd_bus_vtable bus_unit_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("Id", "s", NULL, offsetof(Unit, id), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Names", "as", property_get_names, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Following", "s", property_get_following, 0, 0), + SD_BUS_PROPERTY("Requires", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUIRES]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Requisite", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUISITE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Wants", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_WANTS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("BindsTo", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BINDS_TO]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PartOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_PART_OF]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RequiredBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUIRED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RequisiteOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_REQUISITE_OF]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("WantedBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_WANTED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("BoundBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BOUND_BY]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ConsistsOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONSISTS_OF]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Conflicts", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONFLICTS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ConflictedBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_CONFLICTED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Before", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_BEFORE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("After", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_AFTER]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OnFailure", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_ON_FAILURE]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Triggers", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_TRIGGERS]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("TriggeredBy", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_TRIGGERED_BY]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("PropagatesReloadTo", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_PROPAGATES_RELOAD_TO]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ReloadPropagatedFrom", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_RELOAD_PROPAGATED_FROM]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("JoinsNamespaceOf", "as", property_get_dependencies, offsetof(Unit, dependencies[UNIT_JOINS_NAMESPACE_OF]), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RequiresOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RequisiteOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RequiredByOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RequisiteOfOverridable", "as", property_get_obsolete_dependencies, 0, SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("RequiresMountsFor", "as", NULL, offsetof(Unit, requires_mounts_for), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Documentation", "as", NULL, offsetof(Unit, documentation), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Description", "s", property_get_description, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("LoadState", "s", property_get_load_state, offsetof(Unit, load_state), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ActiveState", "s", property_get_active_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("SubState", "s", property_get_sub_state, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("FragmentPath", "s", NULL, offsetof(Unit, fragment_path), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SourcePath", "s", NULL, offsetof(Unit, source_path), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DropInPaths", "as", NULL, offsetof(Unit, dropin_paths), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("UnitFileState", "s", property_get_unit_file_state, 0, 0), + SD_BUS_PROPERTY("UnitFilePreset", "s", property_get_unit_file_preset, 0, 0), + BUS_PROPERTY_DUAL_TIMESTAMP("StateChangeTimestamp", offsetof(Unit, state_change_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("InactiveExitTimestamp", offsetof(Unit, inactive_exit_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("ActiveEnterTimestamp", offsetof(Unit, active_enter_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("ActiveExitTimestamp", offsetof(Unit, active_exit_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("InactiveEnterTimestamp", offsetof(Unit, inactive_enter_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("CanStart", "b", property_get_can_start, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CanStop", "b", property_get_can_stop, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CanReload", "b", property_get_can_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("CanIsolate", "b", property_get_can_isolate, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Job", "(uo)", property_get_job, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("StopWhenUnneeded", "b", bus_property_get_bool, offsetof(Unit, stop_when_unneeded), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RefuseManualStart", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_start), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RefuseManualStop", "b", bus_property_get_bool, offsetof(Unit, refuse_manual_stop), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("AllowIsolate", "b", bus_property_get_bool, offsetof(Unit, allow_isolate), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("DefaultDependencies", "b", bus_property_get_bool, offsetof(Unit, default_dependencies), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("OnFailureJobMode", "s", property_get_job_mode, offsetof(Unit, on_failure_job_mode), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("IgnoreOnIsolate", "b", bus_property_get_bool, offsetof(Unit, ignore_on_isolate), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NeedDaemonReload", "b", property_get_need_daemon_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("JobTimeoutUSec", "t", bus_property_get_usec, offsetof(Unit, job_timeout), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("JobTimeoutAction", "s", property_get_failure_action, offsetof(Unit, job_timeout_action), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("JobTimeoutRebootArgument", "s", NULL, offsetof(Unit, job_timeout_reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ConditionResult", "b", bus_property_get_bool, offsetof(Unit, condition_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("AssertResult", "b", bus_property_get_bool, offsetof(Unit, assert_result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("ConditionTimestamp", offsetof(Unit, condition_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_PROPERTY_DUAL_TIMESTAMP("AssertTimestamp", offsetof(Unit, assert_timestamp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Conditions", "a(sbbsi)", property_get_conditions, offsetof(Unit, conditions), 0), + SD_BUS_PROPERTY("Asserts", "a(sbbsi)", property_get_conditions, offsetof(Unit, asserts), 0), + SD_BUS_PROPERTY("LoadError", "(ss)", property_get_load_error, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Transient", "b", bus_property_get_bool, offsetof(Unit, transient), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_limit.interval), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_limit.burst), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("StartLimitAction", "s", property_get_failure_action, offsetof(Unit, start_limit_action), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RebootArgument", "s", NULL, offsetof(Unit, reboot_arg), SD_BUS_VTABLE_PROPERTY_CONST), + + SD_BUS_METHOD("Start", "s", "o", method_start, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Stop", "s", "o", method_stop, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Reload", "s", "o", method_reload, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Restart", "s", "o", method_restart, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("TryRestart", "s", "o", method_try_restart, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ReloadOrRestart", "s", "o", method_reload_or_restart, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ReloadOrTryRestart", "s", "o", method_reload_or_try_restart, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Kill", "si", NULL, bus_unit_method_kill, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ResetFailed", NULL, NULL, bus_unit_method_reset_failed, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetProperties", "ba(sv)", NULL, bus_unit_method_set_properties, SD_BUS_VTABLE_UNPRIVILEGED), + + SD_BUS_VTABLE_END +}; + +static int property_get_slice( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + + assert(bus); + assert(reply); + assert(u); + + return sd_bus_message_append(reply, "s", unit_slice_name(u)); +} + +static int property_get_current_memory( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t sz = (uint64_t) -1; + Unit *u = userdata; + int r; + + assert(bus); + assert(reply); + assert(u); + + r = unit_get_memory_current(u, &sz); + if (r < 0 && r != -ENODATA) + log_unit_warning_errno(u, r, "Failed to get memory.usage_in_bytes attribute: %m"); + + return sd_bus_message_append(reply, "t", sz); +} + +static int property_get_current_tasks( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t cn = (uint64_t) -1; + Unit *u = userdata; + int r; + + assert(bus); + assert(reply); + assert(u); + + r = unit_get_tasks_current(u, &cn); + if (r < 0 && r != -ENODATA) + log_unit_warning_errno(u, r, "Failed to get pids.current attribute: %m"); + + return sd_bus_message_append(reply, "t", cn); +} + +static int property_get_cpu_usage( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + nsec_t ns = (nsec_t) -1; + Unit *u = userdata; + int r; + + assert(bus); + assert(reply); + assert(u); + + r = unit_get_cpu_usage(u, &ns); + if (r < 0 && r != -ENODATA) + log_unit_warning_errno(u, r, "Failed to get cpuacct.usage attribute: %m"); + + return sd_bus_message_append(reply, "t", ns); +} + +static int property_get_cgroup( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Unit *u = userdata; + const char *t; + + assert(bus); + assert(reply); + assert(u); + + /* Three cases: a) u->cgroup_path is NULL, in which case the + * unit has no control group, which we report as the empty + * string. b) u->cgroup_path is the empty string, which + * indicates the root cgroup, which we report as "/". c) all + * other cases we report as-is. */ + + if (u->cgroup_path) + t = isempty(u->cgroup_path) ? "/" : u->cgroup_path; + else + t = ""; + + return sd_bus_message_append(reply, "s", t); +} + +const sd_bus_vtable bus_unit_cgroup_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Slice", "s", property_get_slice, 0, 0), + SD_BUS_PROPERTY("ControlGroup", "s", property_get_cgroup, 0, 0), + SD_BUS_PROPERTY("MemoryCurrent", "t", property_get_current_memory, 0, 0), + SD_BUS_PROPERTY("CPUUsageNSec", "t", property_get_cpu_usage, 0, 0), + SD_BUS_PROPERTY("TasksCurrent", "t", property_get_current_tasks, 0, 0), + SD_BUS_VTABLE_END +}; + +static int send_new_signal(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *p = NULL; + Unit *u = userdata; + int r; + + assert(bus); + assert(u); + + p = unit_dbus_path(u); + if (!p) + return -ENOMEM; + + r = sd_bus_message_new_signal( + bus, + &m, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnitNew"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "so", u->id, p); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +static int send_changed_signal(sd_bus *bus, void *userdata) { + _cleanup_free_ char *p = NULL; + Unit *u = userdata; + int r; + + assert(bus); + assert(u); + + p = unit_dbus_path(u); + if (!p) + return -ENOMEM; + + /* Send a properties changed signal. First for the specific + * type, then for the generic unit. The clients may rely on + * this order to get atomic behavior if needed. */ + + r = sd_bus_emit_properties_changed_strv( + bus, p, + unit_dbus_interface_from_type(u->type), + NULL); + if (r < 0) + return r; + + return sd_bus_emit_properties_changed_strv( + bus, p, + "org.freedesktop.systemd1.Unit", + NULL); +} + +void bus_unit_send_change_signal(Unit *u) { + int r; + assert(u); + + if (u->in_dbus_queue) { + LIST_REMOVE(dbus_queue, u->manager->dbus_unit_queue, u); + u->in_dbus_queue = false; + } + + if (!u->id) + return; + + r = bus_foreach_bus(u->manager, NULL, u->sent_dbus_new_signal ? send_changed_signal : send_new_signal, u); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to send unit change signal for %s: %m", u->id); + + u->sent_dbus_new_signal = true; +} + +static int send_removed_signal(sd_bus *bus, void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *p = NULL; + Unit *u = userdata; + int r; + + assert(bus); + assert(u); + + p = unit_dbus_path(u); + if (!p) + return -ENOMEM; + + r = sd_bus_message_new_signal( + bus, + &m, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnitRemoved"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "so", u->id, p); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +void bus_unit_send_removed_signal(Unit *u) { + int r; + assert(u); + + if (!u->sent_dbus_new_signal) + bus_unit_send_change_signal(u); + + if (!u->id) + return; + + r = bus_foreach_bus(u->manager, NULL, send_removed_signal, u); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to send unit remove signal for %s: %m", u->id); +} + +int bus_unit_queue_job( + sd_bus_message *message, + Unit *u, + JobType type, + JobMode mode, + bool reload_if_possible, + sd_bus_error *error) { + + _cleanup_free_ char *path = NULL; + Job *j; + int r; + + assert(message); + assert(u); + assert(type >= 0 && type < _JOB_TYPE_MAX); + assert(mode >= 0 && mode < _JOB_MODE_MAX); + + r = mac_selinux_unit_access_check( + u, message, + job_type_to_access_method(type), + error); + if (r < 0) + return r; + + if (reload_if_possible && unit_can_reload(u)) { + if (type == JOB_RESTART) + type = JOB_RELOAD_OR_START; + else if (type == JOB_TRY_RESTART) + type = JOB_TRY_RELOAD; + } + + + if (type == JOB_STOP && + (u->load_state == UNIT_NOT_FOUND || u->load_state == UNIT_ERROR) && + unit_active_state(u) == UNIT_INACTIVE) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not loaded.", u->id); + + if ((type == JOB_START && u->refuse_manual_start) || + (type == JOB_STOP && u->refuse_manual_stop) || + ((type == JOB_RESTART || type == JOB_TRY_RESTART) && (u->refuse_manual_start || u->refuse_manual_stop)) || + (type == JOB_RELOAD_OR_START && job_type_collapse(type, u) == JOB_START && u->refuse_manual_start)) + return sd_bus_error_setf(error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, unit %s may be requested by dependency only.", u->id); + + r = manager_add_job(u->manager, type, u, mode, error, &j); + if (r < 0) + return r; + + if (sd_bus_message_get_bus(message) == u->manager->api_bus) { + if (!j->clients) { + r = sd_bus_track_new(sd_bus_message_get_bus(message), &j->clients, NULL, NULL); + if (r < 0) + return r; + } + + r = sd_bus_track_add_sender(j->clients, message); + if (r < 0) + return r; + } + + path = job_dbus_path(j); + if (!path) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", path); +} + +static int bus_unit_set_transient_property( + Unit *u, + const char *name, + sd_bus_message *message, + UnitSetPropertiesMode mode, + sd_bus_error *error) { + + int r; + + assert(u); + assert(name); + assert(message); + + if (streq(name, "Description")) { + const char *d; + + r = sd_bus_message_read(message, "s", &d); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + r = unit_set_description(u, d); + if (r < 0) + return r; + + unit_write_drop_in_format(u, mode, name, "[Unit]\nDescription=%s\n", d); + } + + return 1; + + } else if (streq(name, "DefaultDependencies")) { + int b; + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) + return r; + + if (mode != UNIT_CHECK) { + u->default_dependencies = b; + unit_write_drop_in_format(u, mode, name, "[Unit]\nDefaultDependencies=%s\n", yes_no(b)); + } + + return 1; + + } else if (streq(name, "Slice")) { + Unit *slice; + const char *s; + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "The slice property is only available for units with control groups."); + if (u->type == UNIT_SLICE) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Slice may not be set for slice units."); + if (unit_has_name(u, SPECIAL_INIT_SCOPE)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot set slice for init.scope"); + + r = sd_bus_message_read(message, "s", &s); + if (r < 0) + return r; + + if (!unit_name_is_valid(s, UNIT_NAME_PLAIN)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit name '%s'", s); + + r = manager_load_unit(u->manager, s, NULL, error, &slice); + if (r < 0) + return r; + + if (slice->type != UNIT_SLICE) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit name '%s' is not a slice", s); + + if (mode != UNIT_CHECK) { + r = unit_set_slice(u, slice); + if (r < 0) + return r; + + unit_write_drop_in_private_format(u, mode, name, "Slice=%s\n", s); + } + + return 1; + + } else if (STR_IN_SET(name, + "Requires", "RequiresOverridable", + "Requisite", "RequisiteOverridable", + "Wants", + "BindsTo", + "Conflicts", + "Before", "After", + "OnFailure", + "PropagatesReloadTo", "ReloadPropagatedFrom", + "PartOf")) { + + UnitDependency d; + const char *other; + + if (streq(name, "RequiresOverridable")) + d = UNIT_REQUIRES; /* redirect for obsolete unit dependency type */ + else if (streq(name, "RequisiteOverridable")) + d = UNIT_REQUISITE; /* same here */ + else { + d = unit_dependency_from_string(name); + if (d < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit dependency: %s", name); + } + + r = sd_bus_message_enter_container(message, 'a', "s"); + if (r < 0) + return r; + + while ((r = sd_bus_message_read(message, "s", &other)) > 0) { + if (!unit_name_is_valid(other, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid unit name %s", other); + + if (mode != UNIT_CHECK) { + _cleanup_free_ char *label = NULL; + + r = unit_add_dependency_by_name(u, d, other, NULL, true); + if (r < 0) + return r; + + label = strjoin(name, "-", other, NULL); + if (!label) + return -ENOMEM; + + unit_write_drop_in_format(u, mode, label, "[Unit]\n%s=%s\n", name, other); + } + + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + return 1; + } + + return 0; +} + +int bus_unit_set_properties( + Unit *u, + sd_bus_message *message, + UnitSetPropertiesMode mode, + bool commit, + sd_bus_error *error) { + + bool for_real = false; + unsigned n = 0; + int r; + + assert(u); + assert(message); + + /* We iterate through the array twice. First run we just check + * if all passed data is valid, second run actually applies + * it. This is to implement transaction-like behaviour without + * actually providing full transactions. */ + + r = sd_bus_message_enter_container(message, 'a', "(sv)"); + if (r < 0) + return r; + + for (;;) { + const char *name; + + r = sd_bus_message_enter_container(message, 'r', "sv"); + if (r < 0) + return r; + if (r == 0) { + if (for_real || mode == UNIT_CHECK) + break; + + /* Reached EOF. Let's try again, and this time for realz... */ + r = sd_bus_message_rewind(message, false); + if (r < 0) + return r; + + for_real = true; + continue; + } + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (!UNIT_VTABLE(u)->bus_set_property) + return sd_bus_error_setf(error, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Objects of this type do not support setting properties."); + + r = sd_bus_message_enter_container(message, 'v', NULL); + if (r < 0) + return r; + + r = UNIT_VTABLE(u)->bus_set_property(u, name, message, for_real ? mode : UNIT_CHECK, error); + if (r == 0 && u->transient && u->load_state == UNIT_STUB) + r = bus_unit_set_transient_property(u, name, message, for_real ? mode : UNIT_CHECK, error); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Cannot set property %s, or unknown property.", name); + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + n += for_real; + } + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (commit && n > 0 && UNIT_VTABLE(u)->bus_commit_properties) + UNIT_VTABLE(u)->bus_commit_properties(u); + + return n; +} + +int bus_unit_check_load_state(Unit *u, sd_bus_error *error) { + + if (u->load_state == UNIT_LOADED) + return 0; + + /* Give a better description of the unit error when + * possible. Note that in the case of UNIT_MASKED, load_error + * is not set. */ + if (u->load_state == UNIT_MASKED) + return sd_bus_error_setf(error, BUS_ERROR_UNIT_MASKED, "Unit %s is masked.", u->id); + + if (u->load_state == UNIT_NOT_FOUND) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_UNIT, "Unit %s not found.", u->id); + + return sd_bus_error_set_errnof(error, u->load_error, "Unit %s is not loaded properly: %m.", u->id); +} diff --git a/src/libcore/dbus-unit.h b/src/libcore/dbus-unit.h new file mode 100644 index 0000000000..1a0070fce6 --- /dev/null +++ b/src/libcore/dbus-unit.h @@ -0,0 +1,40 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "unit.h" + +extern const sd_bus_vtable bus_unit_vtable[]; +extern const sd_bus_vtable bus_unit_cgroup_vtable[]; + +void bus_unit_send_change_signal(Unit *u); +void bus_unit_send_removed_signal(Unit *u); + +int bus_unit_method_start_generic(sd_bus_message *message, Unit *u, JobType job_type, bool reload_if_possible, sd_bus_error *error); +int bus_unit_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_unit_method_reset_failed(sd_bus_message *message, void *userdata, sd_bus_error *error); + +int bus_unit_queue_job(sd_bus_message *message, Unit *u, JobType type, JobMode mode, bool reload_if_possible, sd_bus_error *error); +int bus_unit_set_properties(Unit *u, sd_bus_message *message, UnitSetPropertiesMode mode, bool commit, sd_bus_error *error); +int bus_unit_method_set_properties(sd_bus_message *message, void *userdata, sd_bus_error *error); + +int bus_unit_check_load_state(Unit *u, sd_bus_error *error); diff --git a/src/libcore/dbus.c b/src/libcore/dbus.c new file mode 100644 index 0000000000..dc939b023a --- /dev/null +++ b/src/libcore/dbus.c @@ -0,0 +1,1258 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "bus-internal.h" +#include "bus-util.h" +#include "dbus-cgroup.h" +#include "dbus-execute.h" +#include "dbus-job.h" +#include "dbus-kill.h" +#include "dbus-manager.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "fd-util.h" +#include "log.h" +#include "missing.h" +#include "mkdir.h" +#include "selinux-access.h" +#include "special.h" +#include "string-util.h" +#include "strv.h" +#include "strxcpyx.h" +#include "user-util.h" + +#define CONNECTIONS_MAX 4096 + +static void destroy_bus(Manager *m, sd_bus **bus); + +int bus_send_queued_message(Manager *m) { + int r; + + assert(m); + + if (!m->queued_message) + return 0; + + /* If we cannot get rid of this message we won't dispatch any + * D-Bus messages, so that we won't end up wanting to queue + * another message. */ + + r = sd_bus_send(NULL, m->queued_message, NULL); + if (r < 0) + log_warning_errno(r, "Failed to send queued message: %m"); + + m->queued_message = sd_bus_message_unref(m->queued_message); + + return 0; +} + +static int signal_agent_released(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + const char *cgroup, *me; + Manager *m = userdata; + uid_t sender_uid; + sd_bus *bus; + int r; + + assert(message); + assert(m); + + /* ignore recursive events sent by us on the system/user bus */ + bus = sd_bus_message_get_bus(message); + if (!sd_bus_is_server(bus)) { + r = sd_bus_get_unique_name(bus, &me); + if (r < 0) + return r; + + if (streq_ptr(sd_bus_message_get_sender(message), me)) + return 0; + } + + /* only accept org.freedesktop.systemd1.Agent from UID=0 */ + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_EUID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_euid(creds, &sender_uid); + if (r < 0 || sender_uid != 0) + return 0; + + /* parse 'cgroup-empty' notification */ + r = sd_bus_message_read(message, "s", &cgroup); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + manager_notify_cgroup_empty(m, cgroup); + + /* if running as system-instance, forward under our name */ + if (m->running_as == MANAGER_SYSTEM && m->system_bus) { + r = sd_bus_message_rewind(message, 1); + if (r >= 0) + r = sd_bus_send(m->system_bus, message, NULL); + if (r < 0) + log_warning_errno(r, "Failed to forward Released message: %m"); + } + + return 0; +} + +static int signal_disconnected(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + sd_bus *bus; + + assert(message); + assert(m); + assert_se(bus = sd_bus_message_get_bus(message)); + + if (bus == m->api_bus) + destroy_bus(m, &m->api_bus); + if (bus == m->system_bus) + destroy_bus(m, &m->system_bus); + if (set_remove(m->private_buses, bus)) { + log_debug("Got disconnect on private connection."); + destroy_bus(m, &bus); + } + + return 0; +} + +static int signal_activation_request(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + const char *name; + Unit *u; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (manager_unit_inactive_or_pending(m, SPECIAL_DBUS_SERVICE) || + manager_unit_inactive_or_pending(m, SPECIAL_DBUS_SOCKET)) { + r = sd_bus_error_setf(&error, BUS_ERROR_SHUTTING_DOWN, "Refusing activation, D-Bus is shutting down."); + goto failed; + } + + r = manager_load_unit(m, name, NULL, &error, &u); + if (r < 0) + goto failed; + + if (u->refuse_manual_start) { + r = sd_bus_error_setf(&error, BUS_ERROR_ONLY_BY_DEPENDENCY, "Operation refused, %s may be requested by dependency only.", u->id); + goto failed; + } + + r = manager_add_job(m, JOB_START, u, JOB_REPLACE, &error, NULL); + if (r < 0) + goto failed; + + /* Successfully queued, that's it for us */ + return 0; + +failed: + if (!sd_bus_error_is_set(&error)) + sd_bus_error_set_errno(&error, r); + + log_debug("D-Bus activation failed for %s: %s", name, bus_error_message(&error, r)); + + r = sd_bus_message_new_signal(sd_bus_message_get_bus(message), &reply, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Activator", "ActivationFailure"); + if (r < 0) { + bus_log_create_error(r); + return 0; + } + + r = sd_bus_message_append(reply, "sss", name, error.name, error.message); + if (r < 0) { + bus_log_create_error(r); + return 0; + } + + r = sd_bus_send_to(NULL, reply, "org.freedesktop.DBus", NULL); + if (r < 0) + return log_error_errno(r, "Failed to respond with to bus activation request: %m"); + + return 0; +} + +#ifdef HAVE_SELINUX +static int mac_selinux_filter(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + const char *verb, *path; + Unit *u = NULL; + Job *j; + int r; + + assert(message); + + /* Our own method calls are all protected individually with + * selinux checks, but the built-in interfaces need to be + * protected too. */ + + if (sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Properties", "Set")) + verb = "reload"; + else if (sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Introspectable", NULL) || + sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Properties", NULL) || + sd_bus_message_is_method_call(message, "org.freedesktop.DBus.ObjectManager", NULL) || + sd_bus_message_is_method_call(message, "org.freedesktop.DBus.Peer", NULL)) + verb = "status"; + else + return 0; + + path = sd_bus_message_get_path(message); + + if (object_path_startswith("/org/freedesktop/systemd1", path)) { + + r = mac_selinux_access_check(message, verb, error); + if (r < 0) + return r; + + return 0; + } + + if (streq_ptr(path, "/org/freedesktop/systemd1/unit/self")) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + pid_t pid; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return 0; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return 0; + + u = manager_get_unit_by_pid(m, pid); + } else { + r = manager_get_job_from_dbus_path(m, path, &j); + if (r >= 0) + u = j->unit; + else + manager_load_unit_from_dbus_path(m, path, NULL, &u); + } + + if (!u) + return 0; + + r = mac_selinux_unit_access_check(u, message, verb, error); + if (r < 0) + return r; + + return 0; +} +#endif + +static int bus_job_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + Job *j; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = manager_get_job_from_dbus_path(m, path, &j); + if (r < 0) + return 0; + + *found = j; + return 1; +} + +static int find_unit(Manager *m, sd_bus *bus, const char *path, Unit **unit, sd_bus_error *error) { + Unit *u; + int r; + + assert(m); + assert(bus); + assert(path); + + if (streq_ptr(path, "/org/freedesktop/systemd1/unit/self")) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + sd_bus_message *message; + pid_t pid; + + message = sd_bus_get_current_message(bus); + if (!message) + return 0; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + + u = manager_get_unit_by_pid(m, pid); + } else { + r = manager_load_unit_from_dbus_path(m, path, error, &u); + if (r < 0) + return 0; + } + + if (!u) + return 0; + + *unit = u; + return 1; +} + +static int bus_unit_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + return find_unit(m, bus, path, (Unit**) found, error); +} + +static int bus_unit_interface_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + Unit *u; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = find_unit(m, bus, path, &u, error); + if (r <= 0) + return r; + + if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) + return 0; + + *found = u; + return 1; +} + +static int bus_unit_cgroup_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + Unit *u; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = find_unit(m, bus, path, &u, error); + if (r <= 0) + return r; + + if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) + return 0; + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return 0; + + *found = u; + return 1; +} + +static int bus_cgroup_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + CGroupContext *c; + Unit *u; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = find_unit(m, bus, path, &u, error); + if (r <= 0) + return r; + + if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) + return 0; + + c = unit_get_cgroup_context(u); + if (!c) + return 0; + + *found = c; + return 1; +} + +static int bus_exec_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + ExecContext *c; + Unit *u; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = find_unit(m, bus, path, &u, error); + if (r <= 0) + return r; + + if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) + return 0; + + c = unit_get_exec_context(u); + if (!c) + return 0; + + *found = c; + return 1; +} + +static int bus_kill_context_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + KillContext *c; + Unit *u; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = find_unit(m, bus, path, &u, error); + if (r <= 0) + return r; + + if (!streq_ptr(interface, unit_dbus_interface_from_type(u->type))) + return 0; + + c = unit_get_kill_context(u); + if (!c) + return 0; + + *found = c; + return 1; +} + +static int bus_job_enumerate(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_free_ char **l = NULL; + Manager *m = userdata; + unsigned k = 0; + Iterator i; + Job *j; + + l = new0(char*, hashmap_size(m->jobs)+1); + if (!l) + return -ENOMEM; + + HASHMAP_FOREACH(j, m->jobs, i) { + l[k] = job_dbus_path(j); + if (!l[k]) + return -ENOMEM; + + k++; + } + + assert(hashmap_size(m->jobs) == k); + + *nodes = l; + l = NULL; + + return k; +} + +static int bus_unit_enumerate(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_free_ char **l = NULL; + Manager *m = userdata; + unsigned k = 0; + Iterator i; + Unit *u; + + l = new0(char*, hashmap_size(m->units)+1); + if (!l) + return -ENOMEM; + + HASHMAP_FOREACH(u, m->units, i) { + l[k] = unit_dbus_path(u); + if (!l[k]) + return -ENOMEM; + + k++; + } + + *nodes = l; + l = NULL; + + return k; +} + +static int bus_setup_api_vtables(Manager *m, sd_bus *bus) { + UnitType t; + int r; + + assert(m); + assert(bus); + +#ifdef HAVE_SELINUX + r = sd_bus_add_filter(bus, NULL, mac_selinux_filter, m); + if (r < 0) + return log_error_errno(r, "Failed to add SELinux access filter: %m"); +#endif + + r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager", bus_manager_vtable, m); + if (r < 0) + return log_error_errno(r, "Failed to register Manager vtable: %m"); + + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/job", "org.freedesktop.systemd1.Job", bus_job_vtable, bus_job_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register Job vtable: %m"); + + r = sd_bus_add_node_enumerator(bus, NULL, "/org/freedesktop/systemd1/job", bus_job_enumerate, m); + if (r < 0) + return log_error_errno(r, "Failed to add job enumerator: %m"); + + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", "org.freedesktop.systemd1.Unit", bus_unit_vtable, bus_unit_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register Unit vtable: %m"); + + r = sd_bus_add_node_enumerator(bus, NULL, "/org/freedesktop/systemd1/unit", bus_unit_enumerate, m); + if (r < 0) + return log_error_errno(r, "Failed to add job enumerator: %m"); + + for (t = 0; t < _UNIT_TYPE_MAX; t++) { + const char *interface; + + assert_se(interface = unit_dbus_interface_from_type(t)); + + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, unit_vtable[t]->bus_vtable, bus_unit_interface_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register type specific vtable for %s: %m", interface); + + if (unit_vtable[t]->cgroup_context_offset > 0) { + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_unit_cgroup_vtable, bus_unit_cgroup_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register control group unit vtable for %s: %m", interface); + + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_cgroup_vtable, bus_cgroup_context_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register control group vtable for %s: %m", interface); + } + + if (unit_vtable[t]->exec_context_offset > 0) { + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_exec_vtable, bus_exec_context_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register execute vtable for %s: %m", interface); + } + + if (unit_vtable[t]->kill_context_offset > 0) { + r = sd_bus_add_fallback_vtable(bus, NULL, "/org/freedesktop/systemd1/unit", interface, bus_kill_vtable, bus_kill_context_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register kill vtable for %s: %m", interface); + } + } + + return 0; +} + +static int bus_setup_disconnected_match(Manager *m, sd_bus *bus) { + int r; + + assert(m); + assert(bus); + + r = sd_bus_add_match( + bus, + NULL, + "sender='org.freedesktop.DBus.Local'," + "type='signal'," + "path='/org/freedesktop/DBus/Local'," + "interface='org.freedesktop.DBus.Local'," + "member='Disconnected'", + signal_disconnected, m); + + if (r < 0) + return log_error_errno(r, "Failed to register match for Disconnected message: %m"); + + return 0; +} + +static int bus_on_connection(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_close_ int nfd = -1; + Manager *m = userdata; + sd_id128_t id; + int r; + + assert(s); + assert(m); + + nfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK|SOCK_CLOEXEC); + if (nfd < 0) { + log_warning_errno(errno, "Failed to accept private connection, ignoring: %m"); + return 0; + } + + if (set_size(m->private_buses) >= CONNECTIONS_MAX) { + log_warning("Too many concurrent connections, refusing"); + return 0; + } + + r = set_ensure_allocated(&m->private_buses, NULL); + if (r < 0) { + log_oom(); + return 0; + } + + r = sd_bus_new(&bus); + if (r < 0) { + log_warning_errno(r, "Failed to allocate new private connection bus: %m"); + return 0; + } + + r = sd_bus_set_fd(bus, nfd, nfd); + if (r < 0) { + log_warning_errno(r, "Failed to set fd on new connection bus: %m"); + return 0; + } + + nfd = -1; + + r = bus_check_peercred(bus); + if (r < 0) { + log_warning_errno(r, "Incoming private connection from unprivileged client, refusing: %m"); + return 0; + } + + assert_se(sd_id128_randomize(&id) >= 0); + + r = sd_bus_set_server(bus, 1, id); + if (r < 0) { + log_warning_errno(r, "Failed to enable server support for new connection bus: %m"); + return 0; + } + + r = sd_bus_negotiate_creds(bus, 1, + SD_BUS_CREDS_PID|SD_BUS_CREDS_UID| + SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS| + SD_BUS_CREDS_SELINUX_CONTEXT); + if (r < 0) { + log_warning_errno(r, "Failed to enable credentials for new connection: %m"); + return 0; + } + + r = sd_bus_start(bus); + if (r < 0) { + log_warning_errno(r, "Failed to start new connection bus: %m"); + return 0; + } + + r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) { + log_warning_errno(r, "Failed to attach new connection bus to event loop: %m"); + return 0; + } + + if (m->running_as == MANAGER_SYSTEM) { + /* When we run as system instance we get the Released + * signal via a direct connection */ + + r = sd_bus_add_match( + bus, + NULL, + "type='signal'," + "interface='org.freedesktop.systemd1.Agent'," + "member='Released'," + "path='/org/freedesktop/systemd1/agent'", + signal_agent_released, m); + + if (r < 0) { + log_warning_errno(r, "Failed to register Released match on new connection bus: %m"); + return 0; + } + } + + r = bus_setup_disconnected_match(m, bus); + if (r < 0) + return 0; + + r = bus_setup_api_vtables(m, bus); + if (r < 0) { + log_warning_errno(r, "Failed to set up API vtables on new connection bus: %m"); + return 0; + } + + r = set_put(m->private_buses, bus); + if (r < 0) { + log_warning_errno(r, "Failed to add new connection bus to set: %m"); + return 0; + } + + bus = NULL; + + log_debug("Accepted new private connection."); + + return 0; +} + +int manager_sync_bus_names(Manager *m, sd_bus *bus) { + _cleanup_strv_free_ char **names = NULL; + const char *name; + Iterator i; + Unit *u; + int r; + + assert(m); + assert(bus); + + r = sd_bus_list_names(bus, &names, NULL); + if (r < 0) + return log_error_errno(r, "Failed to get initial list of names: %m"); + + /* We have to synchronize the current bus names with the + * list of active services. To do this, walk the list of + * all units with bus names. */ + HASHMAP_FOREACH_KEY(u, name, m->watch_bus, i) { + Service *s = SERVICE(u); + + assert(s); + + if (!streq_ptr(s->bus_name, name)) { + log_unit_warning(u, "Bus name has changed from %s → %s, ignoring.", s->bus_name, name); + continue; + } + + /* Check if a service's bus name is in the list of currently + * active names */ + if (strv_contains(names, name)) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + const char *unique; + + /* If it is, determine its current owner */ + r = sd_bus_get_name_creds(bus, name, SD_BUS_CREDS_UNIQUE_NAME, &creds); + if (r < 0) { + log_error_errno(r, "Failed to get bus name owner %s: %m", name); + continue; + } + + r = sd_bus_creds_get_unique_name(creds, &unique); + if (r < 0) { + log_error_errno(r, "Failed to get unique name for %s: %m", name); + continue; + } + + /* Now, let's compare that to the previous bus owner, and + * if it's still the same, all is fine, so just don't + * bother the service. Otherwise, the name has apparently + * changed, so synthesize a name owner changed signal. */ + + if (!streq_ptr(unique, s->bus_name_owner)) + UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, unique); + } else { + /* So, the name we're watching is not on the bus. + * This either means it simply hasn't appeared yet, + * or it was lost during the daemon reload. + * Check if the service has a stored name owner, + * and synthesize a name loss signal in this case. */ + + if (s->bus_name_owner) + UNIT_VTABLE(u)->bus_name_owner_change(u, name, s->bus_name_owner, NULL); + } + } + + return 0; +} + +static int bus_setup_api(Manager *m, sd_bus *bus) { + Iterator i; + char *name; + Unit *u; + int r; + + assert(m); + assert(bus); + + /* Let's make sure we have enough credential bits so that we can make security and selinux decisions */ + r = sd_bus_negotiate_creds(bus, 1, + SD_BUS_CREDS_PID|SD_BUS_CREDS_UID| + SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS| + SD_BUS_CREDS_SELINUX_CONTEXT); + if (r < 0) + log_warning_errno(r, "Failed to enable credential passing, ignoring: %m"); + + r = bus_setup_api_vtables(m, bus); + if (r < 0) + return r; + + HASHMAP_FOREACH_KEY(u, name, m->watch_bus, i) { + r = unit_install_bus_match(u, bus, name); + if (r < 0) + log_error_errno(r, "Failed to subscribe to NameOwnerChanged signal for '%s': %m", name); + } + + r = sd_bus_add_match( + bus, + NULL, + "type='signal'," + "sender='org.freedesktop.DBus'," + "path='/org/freedesktop/DBus'," + "interface='org.freedesktop.systemd1.Activator'," + "member='ActivationRequest'", + signal_activation_request, m); + if (r < 0) + log_warning_errno(r, "Failed to subscribe to activation signal: %m"); + + /* Allow replacing of our name, to ease implementation of + * reexecution, where we keep the old connection open until + * after the new connection is set up and the name installed + * to allow clients to synchronously wait for reexecution to + * finish */ + r = sd_bus_request_name(bus,"org.freedesktop.systemd1", SD_BUS_NAME_REPLACE_EXISTING|SD_BUS_NAME_ALLOW_REPLACEMENT); + if (r < 0) + return log_error_errno(r, "Failed to register name: %m"); + + r = manager_sync_bus_names(m, bus); + if (r < 0) + return r; + + log_debug("Successfully connected to API bus."); + return 0; +} + +static int bus_init_api(Manager *m) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + int r; + + if (m->api_bus) + return 0; + + /* The API and system bus is the same if we are running in system mode */ + if (m->running_as == MANAGER_SYSTEM && m->system_bus) + bus = sd_bus_ref(m->system_bus); + else { + if (m->running_as == MANAGER_SYSTEM) + r = sd_bus_open_system(&bus); + else + r = sd_bus_open_user(&bus); + + if (r < 0) { + log_debug("Failed to connect to API bus, retrying later..."); + return 0; + } + + r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) { + log_error_errno(r, "Failed to attach API bus to event loop: %m"); + return 0; + } + + r = bus_setup_disconnected_match(m, bus); + if (r < 0) + return 0; + } + + r = bus_setup_api(m, bus); + if (r < 0) { + log_error_errno(r, "Failed to set up API bus: %m"); + return 0; + } + + m->api_bus = bus; + bus = NULL; + + return 0; +} + +static int bus_setup_system(Manager *m, sd_bus *bus) { + int r; + + assert(m); + assert(bus); + + /* On kdbus or if we are a user instance we get the Released message via the system bus */ + if (m->running_as == MANAGER_USER || m->kdbus_fd >= 0) { + r = sd_bus_add_match( + bus, + NULL, + "type='signal'," + "interface='org.freedesktop.systemd1.Agent'," + "member='Released'," + "path='/org/freedesktop/systemd1/agent'", + signal_agent_released, m); + if (r < 0) + log_warning_errno(r, "Failed to register Released match on system bus: %m"); + } + + log_debug("Successfully connected to system bus."); + return 0; +} + +static int bus_init_system(Manager *m) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + int r; + + if (m->system_bus) + return 0; + + /* The API and system bus is the same if we are running in system mode */ + if (m->running_as == MANAGER_SYSTEM && m->api_bus) { + m->system_bus = sd_bus_ref(m->api_bus); + return 0; + } + + r = sd_bus_open_system(&bus); + if (r < 0) { + log_debug("Failed to connect to system bus, retrying later..."); + return 0; + } + + r = bus_setup_disconnected_match(m, bus); + if (r < 0) + return 0; + + r = sd_bus_attach_event(bus, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) { + log_error_errno(r, "Failed to attach system bus to event loop: %m"); + return 0; + } + + r = bus_setup_system(m, bus); + if (r < 0) { + log_error_errno(r, "Failed to set up system bus: %m"); + return 0; + } + + m->system_bus = bus; + bus = NULL; + + return 0; +} + +static int bus_init_private(Manager *m) { + _cleanup_close_ int fd = -1; + union sockaddr_union sa = { + .un.sun_family = AF_UNIX + }; + sd_event_source *s; + socklen_t salen; + int r; + + assert(m); + + if (m->private_listen_fd >= 0) + return 0; + + /* We don't need the private socket if we have kdbus */ + if (m->kdbus_fd >= 0) + return 0; + + if (m->running_as == MANAGER_SYSTEM) { + + /* We want the private bus only when running as init */ + if (getpid() != 1) + return 0; + + strcpy(sa.un.sun_path, "/run/systemd/private"); + salen = offsetof(union sockaddr_union, un.sun_path) + strlen("/run/systemd/private"); + } else { + size_t left = sizeof(sa.un.sun_path); + char *p = sa.un.sun_path; + const char *e; + + e = secure_getenv("XDG_RUNTIME_DIR"); + if (!e) { + log_error("Failed to determine XDG_RUNTIME_DIR"); + return -EHOSTDOWN; + } + + left = strpcpy(&p, left, e); + left = strpcpy(&p, left, "/systemd/private"); + + salen = sizeof(sa.un) - left; + } + + (void) mkdir_parents_label(sa.un.sun_path, 0755); + (void) unlink(sa.un.sun_path); + + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return log_error_errno(errno, "Failed to allocate private socket: %m"); + + r = bind(fd, &sa.sa, salen); + if (r < 0) + return log_error_errno(errno, "Failed to bind private socket: %m"); + + r = listen(fd, SOMAXCONN); + if (r < 0) + return log_error_errno(errno, "Failed to make private socket listening: %m"); + + r = sd_event_add_io(m->event, &s, fd, EPOLLIN, bus_on_connection, m); + if (r < 0) + return log_error_errno(r, "Failed to allocate event source: %m"); + + (void) sd_event_source_set_description(s, "bus-connection"); + + m->private_listen_fd = fd; + m->private_listen_event_source = s; + fd = -1; + + log_debug("Successfully created private D-Bus server."); + + return 0; +} + +int bus_init(Manager *m, bool try_bus_connect) { + int r; + + if (try_bus_connect) { + r = bus_init_system(m); + if (r < 0) + return r; + + r = bus_init_api(m); + if (r < 0) + return r; + } + + r = bus_init_private(m); + if (r < 0) + return r; + + return 0; +} + +static void destroy_bus(Manager *m, sd_bus **bus) { + Iterator i; + Job *j; + + assert(m); + assert(bus); + + if (!*bus) + return; + + /* Get rid of tracked clients on this bus */ + if (m->subscribed && sd_bus_track_get_bus(m->subscribed) == *bus) + m->subscribed = sd_bus_track_unref(m->subscribed); + + HASHMAP_FOREACH(j, m->jobs, i) + if (j->clients && sd_bus_track_get_bus(j->clients) == *bus) + j->clients = sd_bus_track_unref(j->clients); + + /* Get rid of queued message on this bus */ + if (m->queued_message && sd_bus_message_get_bus(m->queued_message) == *bus) + m->queued_message = sd_bus_message_unref(m->queued_message); + + /* Possibly flush unwritten data, but only if we are + * unprivileged, since we don't want to sync here */ + if (m->running_as != MANAGER_SYSTEM) + sd_bus_flush(*bus); + + /* And destroy the object */ + sd_bus_close(*bus); + *bus = sd_bus_unref(*bus); +} + +void bus_done(Manager *m) { + sd_bus *b; + + assert(m); + + if (m->api_bus) + destroy_bus(m, &m->api_bus); + if (m->system_bus) + destroy_bus(m, &m->system_bus); + while ((b = set_steal_first(m->private_buses))) + destroy_bus(m, &b); + + m->private_buses = set_free(m->private_buses); + + m->subscribed = sd_bus_track_unref(m->subscribed); + m->deserialized_subscribed = strv_free(m->deserialized_subscribed); + + if (m->private_listen_event_source) + m->private_listen_event_source = sd_event_source_unref(m->private_listen_event_source); + + m->private_listen_fd = safe_close(m->private_listen_fd); + + bus_verify_polkit_async_registry_free(m->polkit_registry); +} + +int bus_fdset_add_all(Manager *m, FDSet *fds) { + Iterator i; + sd_bus *b; + int fd; + + assert(m); + assert(fds); + + /* When we are about to reexecute we add all D-Bus fds to the + * set to pass over to the newly executed systemd. They won't + * be used there however, except thatt they are closed at the + * very end of deserialization, those making it possible for + * clients to synchronously wait for systemd to reexec by + * simply waiting for disconnection */ + + if (m->api_bus) { + fd = sd_bus_get_fd(m->api_bus); + if (fd >= 0) { + fd = fdset_put_dup(fds, fd); + if (fd < 0) + return fd; + } + } + + SET_FOREACH(b, m->private_buses, i) { + fd = sd_bus_get_fd(b); + if (fd >= 0) { + fd = fdset_put_dup(fds, fd); + if (fd < 0) + return fd; + } + } + + /* We don't offer any APIs on the system bus (well, unless it + * is the same as the API bus) hence we don't bother with it + * here */ + + return 0; +} + +int bus_foreach_bus( + Manager *m, + sd_bus_track *subscribed2, + int (*send_message)(sd_bus *bus, void *userdata), + void *userdata) { + + Iterator i; + sd_bus *b; + int r, ret = 0; + + /* Send to all direct buses, unconditionally */ + SET_FOREACH(b, m->private_buses, i) { + r = send_message(b, userdata); + if (r < 0) + ret = r; + } + + /* Send to API bus, but only if somebody is subscribed */ + if (sd_bus_track_count(m->subscribed) > 0 || + sd_bus_track_count(subscribed2) > 0) { + r = send_message(m->api_bus, userdata); + if (r < 0) + ret = r; + } + + return ret; +} + +void bus_track_serialize(sd_bus_track *t, FILE *f) { + const char *n; + + assert(f); + + for (n = sd_bus_track_first(t); n; n = sd_bus_track_next(t)) + fprintf(f, "subscribed=%s\n", n); +} + +int bus_track_deserialize_item(char ***l, const char *line) { + const char *e; + int r; + + assert(l); + assert(line); + + e = startswith(line, "subscribed="); + if (!e) + return 0; + + r = strv_extend(l, e); + if (r < 0) + return r; + + return 1; +} + +int bus_track_coldplug(Manager *m, sd_bus_track **t, char ***l) { + int r = 0; + + assert(m); + assert(t); + assert(l); + + if (!strv_isempty(*l) && m->api_bus) { + char **i; + + if (!*t) { + r = sd_bus_track_new(m->api_bus, t, NULL, NULL); + if (r < 0) + return r; + } + + r = 0; + STRV_FOREACH(i, *l) { + int k; + + k = sd_bus_track_add_name(*t, *i); + if (k < 0) + r = k; + } + } + + *l = strv_free(*l); + + return r; +} + +int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { + return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.manage-units", NULL, false, UID_INVALID, &m->polkit_registry, error); +} + +int bus_verify_manage_unit_files_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { + return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.manage-unit-files", NULL, false, UID_INVALID, &m->polkit_registry, error); +} + +int bus_verify_reload_daemon_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { + return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.reload-daemon", NULL, false, UID_INVALID, &m->polkit_registry, error); +} + +int bus_verify_set_environment_async(Manager *m, sd_bus_message *call, sd_bus_error *error) { + return bus_verify_polkit_async(call, CAP_SYS_ADMIN, "org.freedesktop.systemd1.set-environment", NULL, false, UID_INVALID, &m->polkit_registry, error); +} diff --git a/src/libcore/dbus.h b/src/libcore/dbus.h new file mode 100644 index 0000000000..e16a84fbb8 --- /dev/null +++ b/src/libcore/dbus.h @@ -0,0 +1,42 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "manager.h" + +int bus_send_queued_message(Manager *m); + +int bus_init(Manager *m, bool try_bus_connect); +void bus_done(Manager *m); + +int bus_fdset_add_all(Manager *m, FDSet *fds); + +void bus_track_serialize(sd_bus_track *t, FILE *f); +int bus_track_deserialize_item(char ***l, const char *line); +int bus_track_coldplug(Manager *m, sd_bus_track **t, char ***l); + +int manager_sync_bus_names(Manager *m, sd_bus *bus); + +int bus_foreach_bus(Manager *m, sd_bus_track *subscribed2, int (*send_message)(sd_bus *bus, void *userdata), void *userdata); + +int bus_verify_manage_units_async(Manager *m, sd_bus_message *call, sd_bus_error *error); +int bus_verify_manage_unit_files_async(Manager *m, sd_bus_message *call, sd_bus_error *error); +int bus_verify_reload_daemon_async(Manager *m, sd_bus_message *call, sd_bus_error *error); +int bus_verify_set_environment_async(Manager *m, sd_bus_message *call, sd_bus_error *error); diff --git a/src/libcore/device.c b/src/libcore/device.c new file mode 100644 index 0000000000..d201dc5e4b --- /dev/null +++ b/src/libcore/device.c @@ -0,0 +1,878 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "libudev.h" + +#include "alloc-util.h" +#include "dbus-device.h" +#include "device.h" +#include "log.h" +#include "parse-util.h" +#include "path-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "swap.h" +#include "udev-util.h" +#include "unit-name.h" +#include "unit.h" + +static const UnitActiveState state_translation_table[_DEVICE_STATE_MAX] = { + [DEVICE_DEAD] = UNIT_INACTIVE, + [DEVICE_TENTATIVE] = UNIT_ACTIVATING, + [DEVICE_PLUGGED] = UNIT_ACTIVE, +}; + +static int device_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); + +static void device_unset_sysfs(Device *d) { + Hashmap *devices; + Device *first; + + assert(d); + + if (!d->sysfs) + return; + + /* Remove this unit from the chain of devices which share the + * same sysfs path. */ + devices = UNIT(d)->manager->devices_by_sysfs; + first = hashmap_get(devices, d->sysfs); + LIST_REMOVE(same_sysfs, first, d); + + if (first) + hashmap_remove_and_replace(devices, d->sysfs, first->sysfs, first); + else + hashmap_remove(devices, d->sysfs); + + d->sysfs = mfree(d->sysfs); +} + +static int device_set_sysfs(Device *d, const char *sysfs) { + Device *first; + char *copy; + int r; + + assert(d); + + if (streq_ptr(d->sysfs, sysfs)) + return 0; + + r = hashmap_ensure_allocated(&UNIT(d)->manager->devices_by_sysfs, &string_hash_ops); + if (r < 0) + return r; + + copy = strdup(sysfs); + if (!copy) + return -ENOMEM; + + device_unset_sysfs(d); + + first = hashmap_get(UNIT(d)->manager->devices_by_sysfs, sysfs); + LIST_PREPEND(same_sysfs, first, d); + + r = hashmap_replace(UNIT(d)->manager->devices_by_sysfs, copy, first); + if (r < 0) { + LIST_REMOVE(same_sysfs, first, d); + free(copy); + return r; + } + + d->sysfs = copy; + + return 0; +} + +static void device_init(Unit *u) { + Device *d = DEVICE(u); + + assert(d); + assert(UNIT(d)->load_state == UNIT_STUB); + + /* In contrast to all other unit types we timeout jobs waiting + * for devices by default. This is because they otherwise wait + * indefinitely for plugged in devices, something which cannot + * happen for the other units since their operations time out + * anyway. */ + u->job_timeout = u->manager->default_timeout_start_usec; + + u->ignore_on_isolate = true; +} + +static void device_done(Unit *u) { + Device *d = DEVICE(u); + + assert(d); + + device_unset_sysfs(d); +} + +static void device_set_state(Device *d, DeviceState state) { + DeviceState old_state; + assert(d); + + old_state = d->state; + d->state = state; + + if (state != old_state) + log_unit_debug(UNIT(d), "Changed %s -> %s", device_state_to_string(old_state), device_state_to_string(state)); + + unit_notify(UNIT(d), state_translation_table[old_state], state_translation_table[state], true); +} + +static int device_coldplug(Unit *u) { + Device *d = DEVICE(u); + + assert(d); + assert(d->state == DEVICE_DEAD); + + if (d->found & DEVICE_FOUND_UDEV) + /* If udev says the device is around, it's around */ + device_set_state(d, DEVICE_PLUGGED); + else if (d->found != DEVICE_NOT_FOUND && d->deserialized_state != DEVICE_PLUGGED) + /* If a device is found in /proc/self/mountinfo or + * /proc/swaps, and was not yet announced via udev, + * it's "tentatively" around. */ + device_set_state(d, DEVICE_TENTATIVE); + + return 0; +} + +static int device_serialize(Unit *u, FILE *f, FDSet *fds) { + Device *d = DEVICE(u); + + assert(u); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", device_state_to_string(d->state)); + + return 0; +} + +static int device_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Device *d = DEVICE(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + DeviceState state; + + state = device_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + d->deserialized_state = state; + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +static void device_dump(Unit *u, FILE *f, const char *prefix) { + Device *d = DEVICE(u); + + assert(d); + + fprintf(f, + "%sDevice State: %s\n" + "%sSysfs Path: %s\n", + prefix, device_state_to_string(d->state), + prefix, strna(d->sysfs)); +} + +_pure_ static UnitActiveState device_active_state(Unit *u) { + assert(u); + + return state_translation_table[DEVICE(u)->state]; +} + +_pure_ static const char *device_sub_state_to_string(Unit *u) { + assert(u); + + return device_state_to_string(DEVICE(u)->state); +} + +static int device_update_description(Unit *u, struct udev_device *dev, const char *path) { + const char *model; + int r; + + assert(u); + assert(dev); + assert(path); + + model = udev_device_get_property_value(dev, "ID_MODEL_FROM_DATABASE"); + if (!model) + model = udev_device_get_property_value(dev, "ID_MODEL"); + + if (model) { + const char *label; + + /* Try to concatenate the device model string with a label, if there is one */ + label = udev_device_get_property_value(dev, "ID_FS_LABEL"); + if (!label) + label = udev_device_get_property_value(dev, "ID_PART_ENTRY_NAME"); + if (!label) + label = udev_device_get_property_value(dev, "ID_PART_ENTRY_NUMBER"); + + if (label) { + _cleanup_free_ char *j; + + j = strjoin(model, " ", label, NULL); + if (j) + r = unit_set_description(u, j); + else + r = -ENOMEM; + } else + r = unit_set_description(u, model); + } else + r = unit_set_description(u, path); + + if (r < 0) + log_unit_error_errno(u, r, "Failed to set device description: %m"); + + return r; +} + +static int device_add_udev_wants(Unit *u, struct udev_device *dev) { + const char *wants; + const char *word, *state; + size_t l; + int r; + const char *property; + + assert(u); + assert(dev); + + property = u->manager->running_as == MANAGER_USER ? "SYSTEMD_USER_WANTS" : "SYSTEMD_WANTS"; + wants = udev_device_get_property_value(dev, property); + if (!wants) + return 0; + + FOREACH_WORD_QUOTED(word, l, wants, state) { + _cleanup_free_ char *n = NULL; + char e[l+1]; + + memcpy(e, word, l); + e[l] = 0; + + r = unit_name_mangle(e, UNIT_NAME_NOGLOB, &n); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to mangle unit name: %m"); + + r = unit_add_dependency_by_name(u, UNIT_WANTS, n, NULL, true); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to add wants dependency: %m"); + } + if (!isempty(state)) + log_unit_warning(u, "Property %s on %s has trailing garbage, ignoring.", property, strna(udev_device_get_syspath(dev))); + + return 0; +} + +static int device_setup_unit(Manager *m, struct udev_device *dev, const char *path, bool main) { + _cleanup_free_ char *e = NULL; + const char *sysfs = NULL; + Unit *u = NULL; + bool delete; + int r; + + assert(m); + assert(path); + + if (dev) { + sysfs = udev_device_get_syspath(dev); + if (!sysfs) + return 0; + } + + r = unit_name_from_path(path, ".device", &e); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name from device path: %m"); + + u = manager_get_unit(m, e); + + /* The device unit can still be present even if the device was + * unplugged: a mount unit can reference it hence preventing + * the GC to have garbaged it. That's desired since the device + * unit may have a dependency on the mount unit which was + * added during the loading of the later. */ + if (u && DEVICE(u)->state == DEVICE_PLUGGED) { + /* This unit is in plugged state: we're sure it's + * attached to a device. */ + if (!path_equal(DEVICE(u)->sysfs, sysfs)) { + log_unit_error(u, "Dev %s appeared twice with different sysfs paths %s and %s", + e, DEVICE(u)->sysfs, sysfs); + return -EEXIST; + } + } + + if (!u) { + delete = true; + + u = unit_new(m, sizeof(Device)); + if (!u) + return log_oom(); + + r = unit_add_name(u, e); + if (r < 0) + goto fail; + + unit_add_to_load_queue(u); + } else + delete = false; + + /* If this was created via some dependency and has not + * actually been seen yet ->sysfs will not be + * initialized. Hence initialize it if necessary. */ + if (sysfs) { + r = device_set_sysfs(DEVICE(u), sysfs); + if (r < 0) + goto fail; + + (void) device_update_description(u, dev, path); + + /* The additional systemd udev properties we only interpret + * for the main object */ + if (main) + (void) device_add_udev_wants(u, dev); + } + + + /* Note that this won't dispatch the load queue, the caller + * has to do that if needed and appropriate */ + + unit_add_to_dbus_queue(u); + return 0; + +fail: + log_unit_warning_errno(u, r, "Failed to set up device unit: %m"); + + if (delete) + unit_free(u); + + return r; +} + +static int device_process_new(Manager *m, struct udev_device *dev) { + const char *sysfs, *dn, *alias; + struct udev_list_entry *item = NULL, *first = NULL; + int r; + + assert(m); + + sysfs = udev_device_get_syspath(dev); + if (!sysfs) + return 0; + + /* Add the main unit named after the sysfs path */ + r = device_setup_unit(m, dev, sysfs, true); + if (r < 0) + return r; + + /* Add an additional unit for the device node */ + dn = udev_device_get_devnode(dev); + if (dn) + (void) device_setup_unit(m, dev, dn, false); + + /* Add additional units for all symlinks */ + first = udev_device_get_devlinks_list_entry(dev); + udev_list_entry_foreach(item, first) { + const char *p; + struct stat st; + + /* Don't bother with the /dev/block links */ + p = udev_list_entry_get_name(item); + + if (path_startswith(p, "/dev/block/") || + path_startswith(p, "/dev/char/")) + continue; + + /* Verify that the symlink in the FS actually belongs + * to this device. This is useful to deal with + * conflicting devices, e.g. when two disks want the + * same /dev/disk/by-label/xxx link because they have + * the same label. We want to make sure that the same + * device that won the symlink wins in systemd, so we + * check the device node major/minor */ + if (stat(p, &st) >= 0) + if ((!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) || + st.st_rdev != udev_device_get_devnum(dev)) + continue; + + (void) device_setup_unit(m, dev, p, false); + } + + /* Add additional units for all explicitly configured + * aliases */ + alias = udev_device_get_property_value(dev, "SYSTEMD_ALIAS"); + if (alias) { + const char *word, *state; + size_t l; + + FOREACH_WORD_QUOTED(word, l, alias, state) { + char e[l+1]; + + memcpy(e, word, l); + e[l] = 0; + + if (path_is_absolute(e)) + (void) device_setup_unit(m, dev, e, false); + else + log_warning("SYSTEMD_ALIAS for %s is not an absolute path, ignoring: %s", sysfs, e); + } + if (!isempty(state)) + log_warning("SYSTEMD_ALIAS for %s has trailing garbage, ignoring.", sysfs); + } + + return 0; +} + +static void device_update_found_one(Device *d, bool add, DeviceFound found, bool now) { + DeviceFound n, previous; + + assert(d); + + n = add ? (d->found | found) : (d->found & ~found); + if (n == d->found) + return; + + previous = d->found; + d->found = n; + + if (!now) + return; + + if (d->found & DEVICE_FOUND_UDEV) + /* When the device is known to udev we consider it + * plugged. */ + device_set_state(d, DEVICE_PLUGGED); + else if (d->found != DEVICE_NOT_FOUND && (previous & DEVICE_FOUND_UDEV) == 0) + /* If the device has not been seen by udev yet, but is + * now referenced by the kernel, then we assume the + * kernel knows it now, and udev might soon too. */ + device_set_state(d, DEVICE_TENTATIVE); + else + /* If nobody sees the device, or if the device was + * previously seen by udev and now is only referenced + * from the kernel, then we consider the device is + * gone, the kernel just hasn't noticed it yet. */ + device_set_state(d, DEVICE_DEAD); +} + +static int device_update_found_by_sysfs(Manager *m, const char *sysfs, bool add, DeviceFound found, bool now) { + Device *d, *l; + + assert(m); + assert(sysfs); + + if (found == DEVICE_NOT_FOUND) + return 0; + + l = hashmap_get(m->devices_by_sysfs, sysfs); + LIST_FOREACH(same_sysfs, d, l) + device_update_found_one(d, add, found, now); + + return 0; +} + +static int device_update_found_by_name(Manager *m, const char *path, bool add, DeviceFound found, bool now) { + _cleanup_free_ char *e = NULL; + Unit *u; + int r; + + assert(m); + assert(path); + + if (found == DEVICE_NOT_FOUND) + return 0; + + r = unit_name_from_path(path, ".device", &e); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name from device path: %m"); + + u = manager_get_unit(m, e); + if (!u) + return 0; + + device_update_found_one(DEVICE(u), add, found, now); + return 0; +} + +static bool device_is_ready(struct udev_device *dev) { + const char *ready; + + assert(dev); + + ready = udev_device_get_property_value(dev, "SYSTEMD_READY"); + if (!ready) + return true; + + return parse_boolean(ready) != 0; +} + +static Unit *device_following(Unit *u) { + Device *d = DEVICE(u); + Device *other, *first = NULL; + + assert(d); + + if (startswith(u->id, "sys-")) + return NULL; + + /* Make everybody follow the unit that's named after the sysfs path */ + for (other = d->same_sysfs_next; other; other = other->same_sysfs_next) + if (startswith(UNIT(other)->id, "sys-")) + return UNIT(other); + + for (other = d->same_sysfs_prev; other; other = other->same_sysfs_prev) { + if (startswith(UNIT(other)->id, "sys-")) + return UNIT(other); + + first = other; + } + + return UNIT(first); +} + +static int device_following_set(Unit *u, Set **_set) { + Device *d = DEVICE(u), *other; + Set *set; + int r; + + assert(d); + assert(_set); + + if (LIST_JUST_US(same_sysfs, d)) { + *_set = NULL; + return 0; + } + + set = set_new(NULL); + if (!set) + return -ENOMEM; + + LIST_FOREACH_AFTER(same_sysfs, other, d) { + r = set_put(set, other); + if (r < 0) + goto fail; + } + + LIST_FOREACH_BEFORE(same_sysfs, other, d) { + r = set_put(set, other); + if (r < 0) + goto fail; + } + + *_set = set; + return 1; + +fail: + set_free(set); + return r; +} + +static void device_shutdown(Manager *m) { + assert(m); + + m->udev_event_source = sd_event_source_unref(m->udev_event_source); + + if (m->udev_monitor) { + udev_monitor_unref(m->udev_monitor); + m->udev_monitor = NULL; + } + + m->devices_by_sysfs = hashmap_free(m->devices_by_sysfs); +} + +static void device_enumerate(Manager *m) { + _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + int r; + + assert(m); + + if (!m->udev_monitor) { + m->udev_monitor = udev_monitor_new_from_netlink(m->udev, "udev"); + if (!m->udev_monitor) { + log_oom(); + goto fail; + } + + /* This will fail if we are unprivileged, but that + * should not matter much, as user instances won't run + * during boot. */ + (void) udev_monitor_set_receive_buffer_size(m->udev_monitor, 128*1024*1024); + + r = udev_monitor_filter_add_match_tag(m->udev_monitor, "systemd"); + if (r < 0) { + log_error_errno(r, "Failed to add udev tag match: %m"); + goto fail; + } + + r = udev_monitor_enable_receiving(m->udev_monitor); + if (r < 0) { + log_error_errno(r, "Failed to enable udev event reception: %m"); + goto fail; + } + + r = sd_event_add_io(m->event, &m->udev_event_source, udev_monitor_get_fd(m->udev_monitor), EPOLLIN, device_dispatch_io, m); + if (r < 0) { + log_error_errno(r, "Failed to watch udev file descriptor: %m"); + goto fail; + } + + (void) sd_event_source_set_description(m->udev_event_source, "device"); + } + + e = udev_enumerate_new(m->udev); + if (!e) { + log_oom(); + goto fail; + } + + r = udev_enumerate_add_match_tag(e, "systemd"); + if (r < 0) { + log_error_errno(r, "Failed to create udev tag enumeration: %m"); + goto fail; + } + + r = udev_enumerate_add_match_is_initialized(e); + if (r < 0) { + log_error_errno(r, "Failed to install initialization match into enumeration: %m"); + goto fail; + } + + r = udev_enumerate_scan_devices(e); + if (r < 0) { + log_error_errno(r, "Failed to enumerate devices: %m"); + goto fail; + } + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + _cleanup_udev_device_unref_ struct udev_device *dev = NULL; + const char *sysfs; + + sysfs = udev_list_entry_get_name(item); + + dev = udev_device_new_from_syspath(m->udev, sysfs); + if (!dev) { + log_oom(); + continue; + } + + if (!device_is_ready(dev)) + continue; + + (void) device_process_new(m, dev); + + device_update_found_by_sysfs(m, sysfs, true, DEVICE_FOUND_UDEV, false); + } + + return; + +fail: + device_shutdown(m); +} + +static int device_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + _cleanup_udev_device_unref_ struct udev_device *dev = NULL; + Manager *m = userdata; + const char *action, *sysfs; + int r; + + assert(m); + + if (revents != EPOLLIN) { + static RATELIMIT_DEFINE(limit, 10*USEC_PER_SEC, 5); + + if (!ratelimit_test(&limit)) + log_error_errno(errno, "Failed to get udev event: %m"); + if (!(revents & EPOLLIN)) + return 0; + } + + /* + * libudev might filter-out devices which pass the bloom + * filter, so getting NULL here is not necessarily an error. + */ + dev = udev_monitor_receive_device(m->udev_monitor); + if (!dev) + return 0; + + sysfs = udev_device_get_syspath(dev); + if (!sysfs) { + log_error("Failed to get udev sys path."); + return 0; + } + + action = udev_device_get_action(dev); + if (!action) { + log_error("Failed to get udev action string."); + return 0; + } + + if (streq(action, "remove")) { + r = swap_process_device_remove(m, dev); + if (r < 0) + log_error_errno(r, "Failed to process swap device remove event: %m"); + + /* If we get notified that a device was removed by + * udev, then it's completely gone, hence unset all + * found bits */ + device_update_found_by_sysfs(m, sysfs, false, DEVICE_FOUND_UDEV|DEVICE_FOUND_MOUNT|DEVICE_FOUND_SWAP, true); + + } else if (device_is_ready(dev)) { + + (void) device_process_new(m, dev); + + r = swap_process_device_new(m, dev); + if (r < 0) + log_error_errno(r, "Failed to process swap device new event: %m"); + + manager_dispatch_load_queue(m); + + /* The device is found now, set the udev found bit */ + device_update_found_by_sysfs(m, sysfs, true, DEVICE_FOUND_UDEV, true); + + } else { + /* The device is nominally around, but not ready for + * us. Hence unset the udev bit, but leave the rest + * around. */ + + device_update_found_by_sysfs(m, sysfs, false, DEVICE_FOUND_UDEV, true); + } + + return 0; +} + +static bool device_supported(void) { + static int read_only = -1; + + /* If /sys is read-only we don't support device units, and any + * attempts to start one should fail immediately. */ + + if (read_only < 0) + read_only = path_is_read_only_fs("/sys"); + + return read_only <= 0; +} + +int device_found_node(Manager *m, const char *node, bool add, DeviceFound found, bool now) { + _cleanup_udev_device_unref_ struct udev_device *dev = NULL; + struct stat st; + + assert(m); + assert(node); + + if (!device_supported()) + return 0; + + /* This is called whenever we find a device referenced in + * /proc/swaps or /proc/self/mounts. Such a device might be + * mounted/enabled at a time where udev has not finished + * probing it yet, and we thus haven't learned about it + * yet. In this case we will set the device unit to + * "tentative" state. */ + + if (add) { + if (!path_startswith(node, "/dev")) + return 0; + + /* We make an extra check here, if the device node + * actually exists. If it's missing, then this is an + * indication that device was unplugged but is still + * referenced in /proc/swaps or + * /proc/self/mountinfo. Note that this check doesn't + * really cover all cases where a device might be gone + * away, since drives that can have a medium inserted + * will still have a device node even when the medium + * is not there... */ + + if (stat(node, &st) >= 0) { + if (!S_ISBLK(st.st_mode) && !S_ISCHR(st.st_mode)) + return 0; + + dev = udev_device_new_from_devnum(m->udev, S_ISBLK(st.st_mode) ? 'b' : 'c', st.st_rdev); + if (!dev && errno != ENOENT) + return log_error_errno(errno, "Failed to get udev device from devnum %u:%u: %m", major(st.st_rdev), minor(st.st_rdev)); + + } else if (errno != ENOENT) + return log_error_errno(errno, "Failed to stat device node file %s: %m", node); + + /* If the device is known in the kernel and newly + * appeared, then we'll create a device unit for it, + * under the name referenced in /proc/swaps or + * /proc/self/mountinfo. */ + + (void) device_setup_unit(m, dev, node, false); + } + + /* Update the device unit's state, should it exist */ + return device_update_found_by_name(m, node, add, found, now); +} + +const UnitVTable device_vtable = { + .object_size = sizeof(Device), + .sections = + "Unit\0" + "Device\0" + "Install\0", + + .no_instances = true, + + .init = device_init, + .done = device_done, + .load = unit_load_fragment_and_dropin_optional, + + .coldplug = device_coldplug, + + .serialize = device_serialize, + .deserialize_item = device_deserialize_item, + + .dump = device_dump, + + .active_state = device_active_state, + .sub_state_to_string = device_sub_state_to_string, + + .bus_vtable = bus_device_vtable, + + .following = device_following, + .following_set = device_following_set, + + .enumerate = device_enumerate, + .shutdown = device_shutdown, + .supported = device_supported, + + .status_message_formats = { + .starting_stopping = { + [0] = "Expecting device %s...", + }, + .finished_start_job = { + [JOB_DONE] = "Found device %s.", + [JOB_TIMEOUT] = "Timed out waiting for device %s.", + }, + }, +}; diff --git a/src/libcore/device.h b/src/libcore/device.h new file mode 100644 index 0000000000..184a1a349b --- /dev/null +++ b/src/libcore/device.h @@ -0,0 +1,47 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Device Device; + +typedef enum DeviceFound { + DEVICE_NOT_FOUND = 0, + DEVICE_FOUND_UDEV = 1, + DEVICE_FOUND_MOUNT = 2, + DEVICE_FOUND_SWAP = 4, +} DeviceFound; + +struct Device { + Unit meta; + + char *sysfs; + DeviceFound found; + + /* In order to be able to distinguish dependencies on + different device nodes we might end up creating multiple + devices for the same sysfs path. We chain them up here. */ + LIST_FIELDS(struct Device, same_sysfs); + + DeviceState state, deserialized_state; +}; + +extern const UnitVTable device_vtable; + +int device_found_node(Manager *m, const char *node, bool add, DeviceFound found, bool now); diff --git a/src/libcore/execute.c b/src/libcore/execute.c new file mode 100644 index 0000000000..6fd35c8350 --- /dev/null +++ b/src/libcore/execute.c @@ -0,0 +1,3165 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_PAM +#include +#endif + +#ifdef HAVE_SELINUX +#include +#endif + +#ifdef HAVE_SECCOMP +#include +#endif + +#ifdef HAVE_APPARMOR +#include +#endif + +#include + +#include "af-list.h" +#include "alloc-util.h" +#ifdef HAVE_APPARMOR +#include "apparmor-util.h" +#endif +#include "async.h" +#include "barrier.h" +#include "bus-endpoint.h" +#include "cap-list.h" +#include "capability-util.h" +#include "def.h" +#include "env-util.h" +#include "errno-list.h" +#include "execute.h" +#include "exit-status.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "glob-util.h" +#include "io-util.h" +#include "ioprio.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "namespace.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "rm-rf.h" +#ifdef HAVE_SECCOMP +#include "seccomp-util.h" +#endif +#include "securebits.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "smack-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "syslog-util.h" +#include "terminal-util.h" +#include "unit.h" +#include "user-util.h" +#include "util.h" +#include "utmp-wtmp.h" + +#define IDLE_TIMEOUT_USEC (5*USEC_PER_SEC) +#define IDLE_TIMEOUT2_USEC (1*USEC_PER_SEC) + +/* This assumes there is a 'tty' group */ +#define TTY_MODE 0620 + +#define SNDBUF_SIZE (8*1024*1024) + +static int shift_fds(int fds[], unsigned n_fds) { + int start, restart_from; + + if (n_fds <= 0) + return 0; + + /* Modifies the fds array! (sorts it) */ + + assert(fds); + + start = 0; + for (;;) { + int i; + + restart_from = -1; + + for (i = start; i < (int) n_fds; i++) { + int nfd; + + /* Already at right index? */ + if (fds[i] == i+3) + continue; + + nfd = fcntl(fds[i], F_DUPFD, i + 3); + if (nfd < 0) + return -errno; + + safe_close(fds[i]); + fds[i] = nfd; + + /* Hmm, the fd we wanted isn't free? Then + * let's remember that and try again from here */ + if (nfd != i+3 && restart_from < 0) + restart_from = i; + } + + if (restart_from < 0) + break; + + start = restart_from; + } + + return 0; +} + +static int flags_fds(const int fds[], unsigned n_fds, bool nonblock) { + unsigned i; + int r; + + if (n_fds <= 0) + return 0; + + assert(fds); + + /* Drops/Sets O_NONBLOCK and FD_CLOEXEC from the file flags */ + + for (i = 0; i < n_fds; i++) { + + r = fd_nonblock(fds[i], nonblock); + if (r < 0) + return r; + + /* We unconditionally drop FD_CLOEXEC from the fds, + * since after all we want to pass these fds to our + * children */ + + r = fd_cloexec(fds[i], false); + if (r < 0) + return r; + } + + return 0; +} + +static const char *exec_context_tty_path(const ExecContext *context) { + assert(context); + + if (context->stdio_as_fds) + return NULL; + + if (context->tty_path) + return context->tty_path; + + return "/dev/console"; +} + +static void exec_context_tty_reset(const ExecContext *context, const ExecParameters *p) { + const char *path; + + assert(context); + + path = exec_context_tty_path(context); + + if (context->tty_vhangup) { + if (p && p->stdin_fd >= 0) + (void) terminal_vhangup_fd(p->stdin_fd); + else if (path) + (void) terminal_vhangup(path); + } + + if (context->tty_reset) { + if (p && p->stdin_fd >= 0) + (void) reset_terminal_fd(p->stdin_fd, true); + else if (path) + (void) reset_terminal(path); + } + + if (context->tty_vt_disallocate && path) + (void) vt_disallocate(path); +} + +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; +} + +static int open_null_as(int flags, int nfd) { + int fd, r; + + assert(nfd >= 0); + + fd = open("/dev/null", flags|O_NOCTTY); + if (fd < 0) + return -errno; + + if (fd != nfd) { + r = dup2(fd, nfd) < 0 ? -errno : nfd; + safe_close(fd); + } else + r = nfd; + + return r; +} + +static int connect_journal_socket(int fd, uid_t uid, gid_t gid) { + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + .un.sun_path = "/run/systemd/journal/stdout", + }; + uid_t olduid = UID_INVALID; + gid_t oldgid = GID_INVALID; + int r; + + if (gid != GID_INVALID) { + oldgid = getgid(); + + r = setegid(gid); + if (r < 0) + return -errno; + } + + if (uid != UID_INVALID) { + olduid = getuid(); + + r = seteuid(uid); + if (r < 0) { + r = -errno; + goto restore_gid; + } + } + + r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)); + if (r < 0) + r = -errno; + + /* If we fail to restore the uid or gid, things will likely + fail later on. This should only happen if an LSM interferes. */ + + if (uid != UID_INVALID) + (void) seteuid(olduid); + + restore_gid: + if (gid != GID_INVALID) + (void) setegid(oldgid); + + 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) { + int fd, r; + + assert(context); + assert(output < _EXEC_OUTPUT_MAX); + assert(ident); + assert(nfd >= 0); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return -errno; + + r = connect_journal_socket(fd, uid, gid); + if (r < 0) + return r; + + if (shutdown(fd, SHUT_RD) < 0) { + safe_close(fd); + return -errno; + } + + fd_inc_sndbuf(fd, SNDBUF_SIZE); + + dprintf(fd, + "%s\n" + "%s\n" + "%i\n" + "%i\n" + "%i\n" + "%i\n" + "%i\n", + context->syslog_identifier ? context->syslog_identifier : ident, + 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; + + return r; +} +static int open_terminal_as(const char *path, mode_t mode, int nfd) { + int fd, r; + + assert(path); + assert(nfd >= 0); + + fd = open_terminal(path, mode | O_NOCTTY); + if (fd < 0) + return fd; + + if (fd != nfd) { + r = dup2(fd, nfd) < 0 ? -errno : nfd; + safe_close(fd); + } else + r = 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) + return EXEC_INPUT_NULL; + + if (std_input == EXEC_INPUT_SOCKET && socket_fd < 0) + return EXEC_INPUT_NULL; + + return std_input; +} + +static int fixup_output(ExecOutput std_output, int socket_fd) { + + if (std_output == EXEC_OUTPUT_SOCKET && socket_fd < 0) + return EXEC_OUTPUT_INHERIT; + + return std_output; +} + +static int setup_input( + const ExecContext *context, + const ExecParameters *params, + int socket_fd) { + + ExecInput i; + + assert(context); + assert(params); + + if (params->stdin_fd >= 0) { + if (dup2(params->stdin_fd, STDIN_FILENO) < 0) + return -errno; + + /* Try to make this the controlling tty, if it is a tty, and reset it */ + (void) ioctl(STDIN_FILENO, TIOCSCTTY, context->std_input == EXEC_INPUT_TTY_FORCE); + (void) reset_terminal_fd(STDIN_FILENO, true); + + return STDIN_FILENO; + } + + i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin); + + switch (i) { + + case EXEC_INPUT_NULL: + return open_null_as(O_RDONLY, STDIN_FILENO); + + case EXEC_INPUT_TTY: + case EXEC_INPUT_TTY_FORCE: + case EXEC_INPUT_TTY_FAIL: { + int fd, r; + + fd = acquire_terminal(exec_context_tty_path(context), + i == EXEC_INPUT_TTY_FAIL, + i == EXEC_INPUT_TTY_FORCE, + false, + USEC_INFINITY); + if (fd < 0) + return fd; + + if (fd != STDIN_FILENO) { + r = dup2(fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; + safe_close(fd); + } else + r = STDIN_FILENO; + + return r; + } + + case EXEC_INPUT_SOCKET: + return dup2(socket_fd, STDIN_FILENO) < 0 ? -errno : STDIN_FILENO; + + default: + assert_not_reached("Unknown input type"); + } +} + +static int setup_output( + Unit *unit, + const ExecContext *context, + const ExecParameters *params, + int fileno, + int socket_fd, + const char *ident, + uid_t uid, gid_t gid) { + + ExecOutput o; + ExecInput i; + int r; + + assert(unit); + assert(context); + assert(params); + assert(ident); + + if (fileno == STDOUT_FILENO && params->stdout_fd >= 0) { + + if (dup2(params->stdout_fd, STDOUT_FILENO) < 0) + return -errno; + + return STDOUT_FILENO; + } + + if (fileno == STDERR_FILENO && params->stderr_fd >= 0) { + if (dup2(params->stderr_fd, STDERR_FILENO) < 0) + return -errno; + + return STDERR_FILENO; + } + + i = fixup_input(context->std_input, socket_fd, params->apply_tty_stdin); + o = fixup_output(context->std_output, socket_fd); + + if (fileno == STDERR_FILENO) { + ExecOutput e; + e = fixup_output(context->std_error, socket_fd); + + /* This expects the input and output are already set up */ + + /* Don't change the stderr file descriptor if we inherit all + * the way and are not on a tty */ + if (e == EXEC_OUTPUT_INHERIT && + o == EXEC_OUTPUT_INHERIT && + i == EXEC_INPUT_NULL && + !is_terminal_input(context->std_input) && + getppid () != 1) + return fileno; + + /* Duplicate from stdout if possible */ + if (e == o || e == EXEC_OUTPUT_INHERIT) + return dup2(STDOUT_FILENO, fileno) < 0 ? -errno : fileno; + + o = e; + + } else if (o == EXEC_OUTPUT_INHERIT) { + /* If input got downgraded, inherit the original value */ + if (i == EXEC_INPUT_NULL && is_terminal_input(context->std_input)) + return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); + + /* If the input is connected to anything that's not a /dev/null, inherit that... */ + if (i != EXEC_INPUT_NULL) + return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno; + + /* If we are not started from PID 1 we just inherit STDOUT from our parent process. */ + if (getppid() != 1) + return fileno; + + /* We need to open /dev/null here anew, to get the right access mode. */ + return open_null_as(O_WRONLY, fileno); + } + + switch (o) { + + case EXEC_OUTPUT_NULL: + return open_null_as(O_WRONLY, fileno); + + case EXEC_OUTPUT_TTY: + if (is_terminal_input(i)) + return dup2(STDIN_FILENO, fileno) < 0 ? -errno : fileno; + + /* We don't reset the terminal if this is just about output */ + return open_terminal_as(exec_context_tty_path(context), O_WRONLY, fileno); + + case EXEC_OUTPUT_SYSLOG: + case EXEC_OUTPUT_SYSLOG_AND_CONSOLE: + case EXEC_OUTPUT_KMSG: + 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); + 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); + } + return r; + + case EXEC_OUTPUT_SOCKET: + assert(socket_fd >= 0); + return dup2(socket_fd, fileno) < 0 ? -errno : fileno; + + default: + assert_not_reached("Unknown error type"); + } +} + +static int chown_terminal(int fd, uid_t uid) { + struct stat st; + + assert(fd >= 0); + + /* This might fail. What matters are the results. */ + (void) fchown(fd, uid, -1); + (void) fchmod(fd, TTY_MODE); + + if (fstat(fd, &st) < 0) + return -errno; + + if (st.st_uid != uid || (st.st_mode & 0777) != TTY_MODE) + return -EPERM; + + return 0; +} + +static int setup_confirm_stdio(int *_saved_stdin, int *_saved_stdout) { + _cleanup_close_ int fd = -1, saved_stdin = -1, saved_stdout = -1; + int r; + + assert(_saved_stdin); + assert(_saved_stdout); + + saved_stdin = fcntl(STDIN_FILENO, F_DUPFD, 3); + if (saved_stdin < 0) + return -errno; + + saved_stdout = fcntl(STDOUT_FILENO, F_DUPFD, 3); + if (saved_stdout < 0) + return -errno; + + fd = acquire_terminal( + "/dev/console", + false, + false, + false, + DEFAULT_CONFIRM_USEC); + if (fd < 0) + return fd; + + r = chown_terminal(fd, getuid()); + if (r < 0) + return r; + + r = reset_terminal_fd(fd, true); + if (r < 0) + return r; + + if (dup2(fd, STDIN_FILENO) < 0) + return -errno; + + if (dup2(fd, STDOUT_FILENO) < 0) + return -errno; + + if (fd >= 2) + safe_close(fd); + fd = -1; + + *_saved_stdin = saved_stdin; + *_saved_stdout = saved_stdout; + + saved_stdin = saved_stdout = -1; + + return 0; +} + +_printf_(1, 2) static int write_confirm_message(const char *format, ...) { + _cleanup_close_ int fd = -1; + va_list ap; + + assert(format); + + fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + va_start(ap, format); + vdprintf(fd, format, ap); + va_end(ap); + + return 0; +} + +static int restore_confirm_stdio(int *saved_stdin, int *saved_stdout) { + int r = 0; + + assert(saved_stdin); + assert(saved_stdout); + + release_terminal(); + + if (*saved_stdin >= 0) + if (dup2(*saved_stdin, STDIN_FILENO) < 0) + r = -errno; + + if (*saved_stdout >= 0) + if (dup2(*saved_stdout, STDOUT_FILENO) < 0) + r = -errno; + + *saved_stdin = safe_close(*saved_stdin); + *saved_stdout = safe_close(*saved_stdout); + + return r; +} + +static int ask_for_confirmation(char *response, char **argv) { + int saved_stdout = -1, saved_stdin = -1, r; + _cleanup_free_ char *line = NULL; + + r = setup_confirm_stdio(&saved_stdin, &saved_stdout); + if (r < 0) + return r; + + line = exec_command_line(argv); + if (!line) + return -ENOMEM; + + r = ask_char(response, "yns", "Execute %s? [Yes, No, Skip] ", line); + + restore_confirm_stdio(&saved_stdin, &saved_stdout); + + return r; +} + +static int enforce_groups(const ExecContext *context, const char *username, gid_t gid) { + bool keep_groups = false; + int r; + + assert(context); + + /* Lookup and set GID and supplementary group list. Here too + * we avoid NSS lookups for gid=0. */ + + if (context->group || username) { + /* First step, initialize groups from /etc/groups */ + if (username && gid != 0) { + if (initgroups(username, gid) < 0) + return -errno; + + keep_groups = true; + } + + /* Second step, set our gids */ + if (setresgid(gid, gid, gid) < 0) + return -errno; + } + + if (context->supplementary_groups) { + int ngroups_max, k; + gid_t *gids; + char **i; + + /* Final step, initialize any manually set supplementary groups */ + assert_se((ngroups_max = (int) sysconf(_SC_NGROUPS_MAX)) > 0); + + if (!(gids = new(gid_t, ngroups_max))) + return -ENOMEM; + + if (keep_groups) { + k = getgroups(ngroups_max, gids); + if (k < 0) { + free(gids); + return -errno; + } + } else + k = 0; + + STRV_FOREACH(i, context->supplementary_groups) { + const char *g; + + if (k >= ngroups_max) { + free(gids); + return -E2BIG; + } + + g = *i; + r = get_group_creds(&g, gids+k); + if (r < 0) { + free(gids); + return r; + } + + k++; + } + + if (setgroups(k, gids) < 0) { + free(gids); + return -errno; + } + + free(gids); + } + + return 0; +} + +static int enforce_user(const ExecContext *context, uid_t uid) { + assert(context); + + /* Sets (but doesn't lookup) the uid and make sure we keep the + * capabilities while doing so. */ + + if (context->capabilities || context->capability_ambient_set != 0) { + + /* First step: If we need to keep capabilities but + * drop privileges we need to make sure we keep our + * caps, while we drop privileges. */ + if (uid != 0) { + int sb = context->secure_bits | 1<capabilities) { + _cleanup_cap_free_ cap_t d = NULL; + static const cap_value_t bits[] = { + CAP_SETUID, /* Necessary so that we can run setresuid() below */ + CAP_SETPCAP /* Necessary so that we can set PR_SET_SECUREBITS later on */ + }; + + d = cap_dup(context->capabilities); + if (!d) + return -errno; + + if (cap_set_flag(d, CAP_EFFECTIVE, ELEMENTSOF(bits), bits, CAP_SET) < 0 || + cap_set_flag(d, CAP_PERMITTED, ELEMENTSOF(bits), bits, CAP_SET) < 0) + return -errno; + + if (cap_set_proc(d) < 0) + return -errno; + } + } + + /* Third step: actually set the uids */ + if (setresuid(uid, uid, uid) < 0) + return -errno; + + /* At this point we should have all necessary capabilities but + are otherwise a normal user. However, the caps might got + corrupted due to the setresuid() so we need clean them up + later. This is done outside of this call. */ + + return 0; +} + +#ifdef HAVE_PAM + +static int null_conv( + int num_msg, + const struct pam_message **msg, + struct pam_response **resp, + void *appdata_ptr) { + + /* We don't support conversations */ + + return PAM_CONV_ERR; +} + +static int setup_pam( + const char *name, + const char *user, + uid_t uid, + const char *tty, + char ***pam_env, + int fds[], unsigned n_fds) { + + static const struct pam_conv conv = { + .conv = null_conv, + .appdata_ptr = NULL + }; + + _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; + pam_handle_t *handle = NULL; + sigset_t old_ss; + int pam_code = PAM_SUCCESS, r; + char **e = NULL; + bool close_session = false; + pid_t pam_pid = 0, parent_pid; + int flags = 0; + + assert(name); + assert(user); + assert(pam_env); + + /* We set up PAM in the parent process, then fork. The child + * will then stay around until killed via PR_GET_PDEATHSIG or + * systemd via the cgroup logic. It will then remove the PAM + * session again. The parent process will exec() the actual + * daemon. We do things this way to ensure that the main PID + * of the daemon is the one we initially fork()ed. */ + + r = barrier_create(&barrier); + if (r < 0) + goto fail; + + if (log_get_max_level() < LOG_DEBUG) + flags |= PAM_SILENT; + + pam_code = pam_start(name, user, &conv, &handle); + if (pam_code != PAM_SUCCESS) { + handle = NULL; + goto fail; + } + + if (tty) { + pam_code = pam_set_item(handle, PAM_TTY, tty); + if (pam_code != PAM_SUCCESS) + goto fail; + } + + pam_code = pam_acct_mgmt(handle, flags); + if (pam_code != PAM_SUCCESS) + goto fail; + + pam_code = pam_open_session(handle, flags); + if (pam_code != PAM_SUCCESS) + goto fail; + + close_session = true; + + e = pam_getenvlist(handle); + if (!e) { + pam_code = PAM_BUF_ERR; + goto fail; + } + + /* Block SIGTERM, so that we know that it won't get lost in + * the child */ + + assert_se(sigprocmask_many(SIG_BLOCK, &old_ss, SIGTERM, -1) >= 0); + + parent_pid = getpid(); + + pam_pid = fork(); + if (pam_pid < 0) { + r = -errno; + goto fail; + } + + if (pam_pid == 0) { + int sig, ret = EXIT_PAM; + + /* The child's job is to reset the PAM session on + * termination */ + barrier_set_role(&barrier, BARRIER_CHILD); + + /* This string must fit in 10 chars (i.e. the length + * of "/sbin/init"), to look pretty in /bin/ps */ + rename_process("(sd-pam)"); + + /* Make sure we don't keep open the passed fds in this + child. We assume that otherwise only those fds are + open here that have been opened by PAM. */ + close_many(fds, n_fds); + + /* Drop privileges - we don't need any to pam_close_session + * and this will make PR_SET_PDEATHSIG work in most cases. + * If this fails, ignore the error - but expect sd-pam threads + * to fail to exit normally */ + if (setresuid(uid, uid, uid) < 0) + log_error_errno(r, "Error: Failed to setresuid() in sd-pam: %m"); + + (void) ignore_signals(SIGPIPE, -1); + + /* Wait until our parent died. This will only work if + * the above setresuid() succeeds, otherwise the kernel + * will not allow unprivileged parents kill their privileged + * children this way. We rely on the control groups kill logic + * to do the rest for us. */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + goto child_finish; + + /* Tell the parent that our setup is done. This is especially + * important regarding dropping privileges. Otherwise, unit + * setup might race against our setresuid(2) call. */ + barrier_place(&barrier); + + /* Check if our parent process might already have + * died? */ + if (getppid() == parent_pid) { + sigset_t ss; + + assert_se(sigemptyset(&ss) >= 0); + assert_se(sigaddset(&ss, SIGTERM) >= 0); + + for (;;) { + if (sigwait(&ss, &sig) < 0) { + if (errno == EINTR) + continue; + + goto child_finish; + } + + assert(sig == SIGTERM); + break; + } + } + + /* If our parent died we'll end the session */ + if (getppid() != parent_pid) { + pam_code = pam_close_session(handle, flags); + if (pam_code != PAM_SUCCESS) + goto child_finish; + } + + ret = 0; + + child_finish: + pam_end(handle, pam_code | flags); + _exit(ret); + } + + barrier_set_role(&barrier, BARRIER_PARENT); + + /* If the child was forked off successfully it will do all the + * cleanups, so forget about the handle here. */ + handle = NULL; + + /* Unblock SIGTERM again in the parent */ + assert_se(sigprocmask(SIG_SETMASK, &old_ss, NULL) >= 0); + + /* We close the log explicitly here, since the PAM modules + * might have opened it, but we don't want this fd around. */ + closelog(); + + /* Synchronously wait for the child to initialize. We don't care for + * errors as we cannot recover. However, warn loudly if it happens. */ + if (!barrier_place_and_sync(&barrier)) + log_error("PAM initialization failed"); + + *pam_env = e; + e = NULL; + + return 0; + +fail: + if (pam_code != PAM_SUCCESS) { + log_error("PAM failed: %s", pam_strerror(handle, pam_code)); + r = -EPERM; /* PAM errors do not map to errno */ + } else + log_error_errno(r, "PAM failed: %m"); + + if (handle) { + if (close_session) + pam_code = pam_close_session(handle, flags); + + pam_end(handle, pam_code | flags); + } + + strv_free(e); + closelog(); + + return r; +} +#endif + +static void rename_process_from_path(const char *path) { + char process_name[11]; + const char *p; + size_t l; + + /* This resulting string must fit in 10 chars (i.e. the length + * of "/sbin/init") to look pretty in /bin/ps */ + + p = basename(path); + if (isempty(p)) { + rename_process("(...)"); + return; + } + + l = strlen(p); + if (l > 8) { + /* The end of the process name is usually more + * interesting, since the first bit might just be + * "systemd-" */ + p = p + l - 8; + l = 8; + } + + process_name[0] = '('; + memcpy(process_name+1, p, l); + process_name[1+l] = ')'; + process_name[1+l+1] = 0; + + rename_process(process_name); +} + +#ifdef HAVE_SECCOMP + +static int apply_seccomp(const ExecContext *c) { + uint32_t negative_action, action; + scmp_filter_ctx *seccomp; + Iterator i; + void *id; + int r; + + assert(c); + + negative_action = c->syscall_errno == 0 ? SCMP_ACT_KILL : SCMP_ACT_ERRNO(c->syscall_errno); + + seccomp = seccomp_init(c->syscall_whitelist ? negative_action : SCMP_ACT_ALLOW); + if (!seccomp) + return -ENOMEM; + + if (c->syscall_archs) { + + SET_FOREACH(id, c->syscall_archs, i) { + r = seccomp_arch_add(seccomp, PTR_TO_UINT32(id) - 1); + if (r == -EEXIST) + continue; + if (r < 0) + goto finish; + } + + } else { + r = seccomp_add_secondary_archs(seccomp); + if (r < 0) + goto finish; + } + + action = c->syscall_whitelist ? SCMP_ACT_ALLOW : negative_action; + SET_FOREACH(id, c->syscall_filter, i) { + r = seccomp_rule_add(seccomp, action, PTR_TO_INT(id) - 1, 0); + 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; +} + +static int apply_address_families(const ExecContext *c) { + scmp_filter_ctx *seccomp; + Iterator i; + int r; + + assert(c); + + seccomp = seccomp_init(SCMP_ACT_ALLOW); + if (!seccomp) + return -ENOMEM; + + r = seccomp_add_secondary_archs(seccomp); + if (r < 0) + goto finish; + + if (c->address_families_whitelist) { + int af, first = 0, last = 0; + void *afp; + + /* If this is a whitelist, we first block the address + * families that are out of range and then everything + * that is not in the set. First, we find the lowest + * and highest address family in the set. */ + + SET_FOREACH(afp, c->address_families, i) { + af = PTR_TO_INT(afp); + + if (af <= 0 || af >= af_max()) + continue; + + if (first == 0 || af < first) + first = af; + + if (last == 0 || af > last) + last = af; + } + + assert((first == 0) == (last == 0)); + + if (first == 0) { + + /* No entries in the valid range, block everything */ + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EPROTONOSUPPORT), + SCMP_SYS(socket), + 0); + if (r < 0) + goto finish; + + } else { + + /* Block everything below the first entry */ + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EPROTONOSUPPORT), + SCMP_SYS(socket), + 1, + SCMP_A0(SCMP_CMP_LT, first)); + if (r < 0) + goto finish; + + /* Block everything above the last entry */ + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EPROTONOSUPPORT), + SCMP_SYS(socket), + 1, + SCMP_A0(SCMP_CMP_GT, last)); + if (r < 0) + goto finish; + + /* Block everything between the first and last + * entry */ + for (af = 1; af < af_max(); af++) { + + if (set_contains(c->address_families, INT_TO_PTR(af))) + continue; + + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EPROTONOSUPPORT), + SCMP_SYS(socket), + 1, + SCMP_A0(SCMP_CMP_EQ, af)); + if (r < 0) + goto finish; + } + } + + } else { + void *af; + + /* If this is a blacklist, then generate one rule for + * each address family that are then combined in OR + * checks. */ + + SET_FOREACH(af, c->address_families, i) { + + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EPROTONOSUPPORT), + SCMP_SYS(socket), + 1, + SCMP_A0(SCMP_CMP_EQ, PTR_TO_INT(af))); + 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]) { + assert(idle_pipe); + + + idle_pipe[1] = safe_close(idle_pipe[1]); + idle_pipe[2] = safe_close(idle_pipe[2]); + + if (idle_pipe[0] >= 0) { + int r; + + r = fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT_USEC); + + if (idle_pipe[3] >= 0 && r == 0 /* timeout */) { + ssize_t n; + + /* Signal systemd that we are bored and want to continue. */ + n = write(idle_pipe[3], "x", 1); + if (n > 0) + /* Wait for systemd to react to the signal above. */ + fd_wait_for_event(idle_pipe[0], POLLHUP, IDLE_TIMEOUT2_USEC); + } + + idle_pipe[0] = safe_close(idle_pipe[0]); + + } + + idle_pipe[3] = safe_close(idle_pipe[3]); +} + +static int build_environment( + const ExecContext *c, + const ExecParameters *p, + unsigned n_fds, + const char *home, + const char *username, + const char *shell, + char ***ret) { + + _cleanup_strv_free_ char **our_env = NULL; + unsigned n_env = 0; + char *x; + + assert(c); + assert(ret); + + our_env = new0(char*, 11); + if (!our_env) + return -ENOMEM; + + if (n_fds > 0) { + _cleanup_free_ char *joined = NULL; + + if (asprintf(&x, "LISTEN_PID="PID_FMT, getpid()) < 0) + return -ENOMEM; + our_env[n_env++] = x; + + if (asprintf(&x, "LISTEN_FDS=%u", n_fds) < 0) + return -ENOMEM; + our_env[n_env++] = x; + + joined = strv_join(p->fd_names, ":"); + if (!joined) + return -ENOMEM; + + x = strjoin("LISTEN_FDNAMES=", joined, NULL); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; + } + + if (p->watchdog_usec > 0) { + if (asprintf(&x, "WATCHDOG_PID="PID_FMT, getpid()) < 0) + return -ENOMEM; + our_env[n_env++] = x; + + if (asprintf(&x, "WATCHDOG_USEC="USEC_FMT, p->watchdog_usec) < 0) + return -ENOMEM; + our_env[n_env++] = x; + } + + if (home) { + x = strappend("HOME=", home); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; + } + + if (username) { + x = strappend("LOGNAME=", username); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; + + x = strappend("USER=", username); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; + } + + if (shell) { + x = strappend("SHELL=", shell); + if (!x) + return -ENOMEM; + 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) { + + x = strdup(default_term_for_tty(exec_context_tty_path(c))); + if (!x) + return -ENOMEM; + our_env[n_env++] = x; + } + + our_env[n_env++] = NULL; + assert(n_env <= 11); + + *ret = our_env; + our_env = NULL; + + return 0; +} + +static int build_pass_environment(const ExecContext *c, char ***ret) { + _cleanup_strv_free_ char **pass_env = NULL; + size_t n_env = 0, n_bufsize = 0; + char **i; + + STRV_FOREACH(i, c->pass_environment) { + _cleanup_free_ char *x = NULL; + char *v; + + v = getenv(*i); + if (!v) + continue; + x = strjoin(*i, "=", v, NULL); + if (!x) + return -ENOMEM; + if (!GREEDY_REALLOC(pass_env, n_bufsize, n_env + 2)) + return -ENOMEM; + pass_env[n_env++] = x; + pass_env[n_env] = NULL; + x = NULL; + } + + *ret = pass_env; + pass_env = NULL; + + return 0; +} + +static bool exec_needs_mount_namespace( + const ExecContext *context, + const ExecParameters *params, + ExecRuntime *runtime) { + + assert(context); + assert(params); + + if (!strv_isempty(context->read_write_dirs) || + !strv_isempty(context->read_only_dirs) || + !strv_isempty(context->inaccessible_dirs)) + return true; + + if (context->mount_flags != 0) + return true; + + if (context->private_tmp && runtime && (runtime->tmp_dir || runtime->var_tmp_dir)) + return true; + + if (params->bus_endpoint_path) + return true; + + if (context->private_devices || + context->protect_system != PROTECT_SYSTEM_NO || + context->protect_home != PROTECT_HOME_NO) + return true; + + return false; +} + +static int close_remaining_fds( + const ExecParameters *params, + ExecRuntime *runtime, + int socket_fd, + int *fds, unsigned n_fds) { + + unsigned n_dont_close = 0; + int dont_close[n_fds + 7]; + + assert(params); + + if (params->stdin_fd >= 0) + dont_close[n_dont_close++] = params->stdin_fd; + if (params->stdout_fd >= 0) + dont_close[n_dont_close++] = params->stdout_fd; + if (params->stderr_fd >= 0) + dont_close[n_dont_close++] = params->stderr_fd; + + if (socket_fd >= 0) + dont_close[n_dont_close++] = socket_fd; + if (n_fds > 0) { + memcpy(dont_close + n_dont_close, fds, sizeof(int) * n_fds); + n_dont_close += n_fds; + } + + if (params->bus_endpoint_fd >= 0) + dont_close[n_dont_close++] = params->bus_endpoint_fd; + + 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]; + } + + return close_all_fds(dont_close, n_dont_close); +} + +static int exec_child( + Unit *unit, + ExecCommand *command, + const ExecContext *context, + const ExecParameters *params, + ExecRuntime *runtime, + 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_free_ char *mac_selinux_context_net = NULL; + const char *username = NULL, *home = NULL, *shell = NULL, *wd; + uid_t uid = UID_INVALID; + gid_t gid = GID_INVALID; + int i, r; + bool needs_mount_namespace; + + assert(unit); + assert(command); + assert(context); + assert(params); + assert(exit_status); + + rename_process_from_path(command->path); + + /* We reset exactly these signals, since they are the + * only ones we set to SIG_IGN in the main daemon. All + * others we leave untouched because we set them to + * SIG_DFL or a valid handler initially, both of which + * will be demoted to SIG_DFL. */ + (void) default_signals(SIGNALS_CRASH_HANDLER, + SIGNALS_IGNORE, -1); + + if (context->ignore_sigpipe) + (void) ignore_signals(SIGPIPE, -1); + + r = reset_signal_mask(); + if (r < 0) { + *exit_status = EXIT_SIGNAL_MASK; + return r; + } + + if (params->idle_pipe) + do_idle_pipe_dance(params->idle_pipe); + + /* Close sockets very early to make sure we don't + * block init reexecution because it cannot bind its + * sockets */ + + log_forget_fds(); + + r = close_remaining_fds(params, runtime, socket_fd, fds, n_fds); + if (r < 0) { + *exit_status = EXIT_FDS; + return r; + } + + if (!context->same_pgrp) + if (setsid() < 0) { + *exit_status = EXIT_SETSID; + return -errno; + } + + exec_context_tty_reset(context, params); + + if (params->confirm_spawn) { + char response; + + r = ask_for_confirmation(&response, argv); + if (r == -ETIMEDOUT) + write_confirm_message("Confirmation question timed out, assuming positive response.\n"); + else if (r < 0) + write_confirm_message("Couldn't ask confirmation question, assuming positive response: %s\n", strerror(-r)); + else if (response == 's') { + write_confirm_message("Skipping execution.\n"); + *exit_status = EXIT_CONFIRM; + return -ECANCELED; + } else if (response == 'n') { + write_confirm_message("Failing execution.\n"); + *exit_status = 0; + return 0; + } + } + + if (context->user) { + username = context->user; + r = get_user_creds(&username, &uid, &gid, &home, &shell); + if (r < 0) { + *exit_status = EXIT_USER; + return r; + } + } + + 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 */ + if (socket_fd >= 0) + (void) fd_nonblock(socket_fd, false); + + r = setup_input(context, params, socket_fd); + if (r < 0) { + *exit_status = EXIT_STDIN; + return r; + } + + r = setup_output(unit, context, params, STDOUT_FILENO, socket_fd, basename(command->path), uid, gid); + if (r < 0) { + *exit_status = EXIT_STDOUT; + return r; + } + + r = setup_output(unit, context, params, STDERR_FILENO, socket_fd, basename(command->path), uid, gid); + if (r < 0) { + *exit_status = EXIT_STDERR; + return r; + } + + if (params->cgroup_path) { + r = cg_attach_everywhere(params->cgroup_supported, params->cgroup_path, 0, NULL, NULL); + if (r < 0) { + *exit_status = EXIT_CGROUP; + return r; + } + } + + if (context->oom_score_adjust_set) { + char t[DECIMAL_STR_MAX(context->oom_score_adjust)]; + + /* When we can't make this change due to EPERM, then + * let's silently skip over it. User namespaces + * prohibit write access to this file, and we + * shouldn't trip up over that. */ + + sprintf(t, "%i", context->oom_score_adjust); + r = write_string_file("/proc/self/oom_score_adj", t, 0); + if (r == -EPERM || r == -EACCES) { + log_open(); + log_unit_debug_errno(unit, r, "Failed to adjust OOM setting, assuming containerized execution, ignoring: %m"); + log_close(); + } else if (r < 0) { + *exit_status = EXIT_OOM_ADJUST; + return -errno; + } + } + + if (context->nice_set) + if (setpriority(PRIO_PROCESS, 0, context->nice) < 0) { + *exit_status = EXIT_NICE; + return -errno; + } + + if (context->cpu_sched_set) { + struct sched_param param = { + .sched_priority = context->cpu_sched_priority, + }; + + r = sched_setscheduler(0, + context->cpu_sched_policy | + (context->cpu_sched_reset_on_fork ? + SCHED_RESET_ON_FORK : 0), + ¶m); + if (r < 0) { + *exit_status = EXIT_SETSCHEDULER; + return -errno; + } + } + + if (context->cpuset) + if (sched_setaffinity(0, CPU_ALLOC_SIZE(context->cpuset_ncpus), context->cpuset) < 0) { + *exit_status = EXIT_CPUAFFINITY; + return -errno; + } + + if (context->ioprio_set) + if (ioprio_set(IOPRIO_WHO_PROCESS, 0, context->ioprio) < 0) { + *exit_status = EXIT_IOPRIO; + return -errno; + } + + if (context->timer_slack_nsec != NSEC_INFINITY) + if (prctl(PR_SET_TIMERSLACK, context->timer_slack_nsec) < 0) { + *exit_status = EXIT_TIMERSLACK; + return -errno; + } + + if (context->personality != PERSONALITY_INVALID) + if (personality(context->personality) < 0) { + *exit_status = EXIT_PERSONALITY; + return -errno; + } + + if (context->utmp_id) + utmp_put_init_process(context->utmp_id, getpid(), getsid(0), context->tty_path, + context->utmp_mode == EXEC_UTMP_INIT ? INIT_PROCESS : + context->utmp_mode == EXEC_UTMP_LOGIN ? LOGIN_PROCESS : + USER_PROCESS, + username ? "root" : context->user); + + if (context->user && is_terminal_input(context->std_input)) { + r = chown_terminal(STDIN_FILENO, uid); + if (r < 0) { + *exit_status = EXIT_STDIN; + return r; + } + } + + if (params->bus_endpoint_fd >= 0 && context->bus_endpoint) { + uid_t ep_uid = (uid == UID_INVALID) ? 0 : uid; + + r = bus_kernel_set_endpoint_policy(params->bus_endpoint_fd, ep_uid, context->bus_endpoint); + if (r < 0) { + *exit_status = EXIT_BUS_ENDPOINT; + return r; + } + } + + /* If delegation is enabled we'll pass ownership of the cgroup + * (but only in systemd's own controller hierarchy!) to the + * user of the new process. */ + if (params->cgroup_path && context->user && params->cgroup_delegate) { + r = cg_set_task_access(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, 0644, uid, gid); + if (r < 0) { + *exit_status = EXIT_CGROUP; + return r; + } + + + r = cg_set_group_access(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, 0755, uid, gid); + if (r < 0) { + *exit_status = EXIT_CGROUP; + return r; + } + } + + if (!strv_isempty(context->runtime_directory) && params->runtime_prefix) { + char **rt; + + STRV_FOREACH(rt, context->runtime_directory) { + _cleanup_free_ char *p; + + p = strjoin(params->runtime_prefix, "/", *rt, NULL); + if (!p) { + *exit_status = EXIT_RUNTIME_DIRECTORY; + return -ENOMEM; + } + + r = mkdir_p_label(p, context->runtime_directory_mode); + if (r < 0) { + *exit_status = EXIT_RUNTIME_DIRECTORY; + return r; + } + + r = chmod_and_chown(p, context->runtime_directory_mode, uid, gid); + if (r < 0) { + *exit_status = EXIT_RUNTIME_DIRECTORY; + return r; + } + } + } + + umask(context->umask); + + if (params->apply_permissions) { + r = enforce_groups(context, username, gid); + if (r < 0) { + *exit_status = EXIT_GROUP; + return r; + } +#ifdef HAVE_SMACK + if (context->smack_process_label) { + r = mac_smack_apply_pid(0, context->smack_process_label); + if (r < 0) { + *exit_status = EXIT_SMACK_PROCESS_LABEL; + return r; + } + } +#ifdef SMACK_DEFAULT_PROCESS_LABEL + else { + _cleanup_free_ char *exec_label = NULL; + + r = mac_smack_read(command->path, SMACK_ATTR_EXEC, &exec_label); + if (r < 0 && r != -ENODATA && r != -EOPNOTSUPP) { + *exit_status = EXIT_SMACK_PROCESS_LABEL; + return r; + } + + r = mac_smack_apply_pid(0, exec_label ? : SMACK_DEFAULT_PROCESS_LABEL); + if (r < 0) { + *exit_status = EXIT_SMACK_PROCESS_LABEL; + return r; + } + } +#endif +#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); + if (r < 0) { + *exit_status = EXIT_PAM; + return r; + } + } +#endif + } + + if (context->private_network && runtime && runtime->netns_storage_socket[0] >= 0) { + r = setup_netns(runtime->netns_storage_socket); + if (r < 0) { + *exit_status = EXIT_NETWORK; + return r; + } + } + + needs_mount_namespace = exec_needs_mount_namespace(context, params, runtime); + + if (needs_mount_namespace) { + char *tmp = NULL, *var = NULL; + + /* The runtime struct only contains the parent + * of the private /tmp, which is + * non-accessible to world users. Inside of it + * there's a /tmp that is sticky, and that's + * the one we want to use here. */ + + if (context->private_tmp && runtime) { + if (runtime->tmp_dir) + tmp = strjoina(runtime->tmp_dir, "/tmp"); + if (runtime->var_tmp_dir) + var = strjoina(runtime->var_tmp_dir, "/tmp"); + } + + r = setup_namespace( + params->apply_chroot ? context->root_directory : NULL, + context->read_write_dirs, + context->read_only_dirs, + context->inaccessible_dirs, + tmp, + var, + params->bus_endpoint_path, + context->private_devices, + context->protect_home, + context->protect_system, + context->mount_flags); + + /* If we couldn't set up the namespace this is + * probably due to a missing capability. In this case, + * silently proceeed. */ + if (r == -EPERM || r == -EACCES) { + log_open(); + log_unit_debug_errno(unit, r, "Failed to set up namespace, assuming containerized execution, ignoring: %m"); + log_close(); + } else if (r < 0) { + *exit_status = EXIT_NAMESPACE; + return r; + } + } + + if (context->working_directory_home) + wd = home; + else if (context->working_directory) + wd = context->working_directory; + else + wd = "/"; + + if (params->apply_chroot) { + if (!needs_mount_namespace && context->root_directory) + if (chroot(context->root_directory) < 0) { + *exit_status = EXIT_CHROOT; + return -errno; + } + + if (chdir(wd) < 0 && + !context->working_directory_missing_ok) { + *exit_status = EXIT_CHDIR; + return -errno; + } + } else { + const char *d; + + d = strjoina(strempty(context->root_directory), "/", strempty(wd)); + if (chdir(d) < 0 && + !context->working_directory_missing_ok) { + *exit_status = EXIT_CHDIR; + return -errno; + } + } + +#ifdef HAVE_SELINUX + if (params->apply_permissions && mac_selinux_use() && params->selinux_context_net && socket_fd >= 0) { + 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; + return r; + } + } +#endif + + /* We repeat the fd closing here, to make sure that + * nothing is leaked from the PAM modules. Note that + * we are more aggressive this time since socket_fd + * and the netns fds we don't need anymore. The custom + * endpoint fd was needed to upload the policy and can + * now be closed as well. */ + r = close_all_fds(fds, n_fds); + if (r >= 0) + r = shift_fds(fds, n_fds); + if (r >= 0) + r = flags_fds(fds, n_fds, context->non_blocking); + if (r < 0) { + *exit_status = EXIT_FDS; + return r; + } + + if (params->apply_permissions) { + + 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) { + *exit_status = EXIT_LIMITS; + return -errno; + } + } + + if (!cap_test_all(context->capability_bounding_set)) { + r = capability_bounding_set_drop(context->capability_bounding_set, false); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + } + + /* This is done before enforce_user, but ambient set + * does not survive over setresuid() if keep_caps is not set. */ + if (context->capability_ambient_set != 0) { + r = capability_ambient_set_apply(context->capability_ambient_set, true); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + + if (context->capabilities) { + + /* The capabilities in ambient set need to be also in the inherited + * set. If they aren't, trying to get them will fail. Add the ambient + * set inherited capabilities to the capability set in the context. + * This is needed because if capabilities are set (using "Capabilities=" + * keyword), they will override whatever we set now. */ + + r = capability_update_inherited_set(context->capabilities, context->capability_ambient_set); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + } + } + + if (context->user) { + r = enforce_user(context, uid); + if (r < 0) { + *exit_status = EXIT_USER; + return r; + } + if (context->capability_ambient_set != 0) { + + /* Fix the ambient capabilities after user change. */ + r = capability_ambient_set_apply(context->capability_ambient_set, false); + if (r < 0) { + *exit_status = EXIT_CAPABILITIES; + return r; + } + + /* If we were asked to change user and ambient capabilities + * were requested, we had to add keep-caps to the securebits + * so that we would maintain the inherited capability set + * through the setresuid(). Make sure that the bit is added + * also to the context secure_bits so that we don't try to + * drop the bit away next. */ + + secure_bits |= 1<capabilities) + if (cap_set_proc(context->capabilities) < 0) { + *exit_status = EXIT_CAPABILITIES; + return -errno; + } + + if (context->no_new_privileges) + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { + *exit_status = EXIT_NO_NEW_PRIVILEGES; + return -errno; + } + +#ifdef HAVE_SECCOMP + if (context->address_families_whitelist || + !set_isempty(context->address_families)) { + r = apply_address_families(context); + if (r < 0) { + *exit_status = EXIT_ADDRESS_FAMILIES; + return r; + } + } + + if (context->syscall_whitelist || + !set_isempty(context->syscall_filter) || + !set_isempty(context->syscall_archs)) { + r = apply_seccomp(context); + if (r < 0) { + *exit_status = EXIT_SECCOMP; + return r; + } + } +#endif + +#ifdef HAVE_SELINUX + if (mac_selinux_use()) { + char *exec_context = mac_selinux_context_net ?: context->selinux_context; + + if (exec_context) { + r = setexeccon(exec_context); + if (r < 0) { + *exit_status = EXIT_SELINUX_CONTEXT; + return r; + } + } + } +#endif + +#ifdef HAVE_APPARMOR + if (context->apparmor_profile && mac_apparmor_use()) { + r = aa_change_onexec(context->apparmor_profile); + if (r < 0 && !context->apparmor_profile_ignore) { + *exit_status = EXIT_APPARMOR_PROFILE; + return -errno; + } + } +#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); + 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; + + line = exec_command_line(final_argv); + if (line) { + log_open(); + log_struct(LOG_DEBUG, + LOG_UNIT_ID(unit), + "EXECUTABLE=%s", command->path, + LOG_UNIT_MESSAGE(unit, "Executing: %s", line), + NULL); + log_close(); + } + } + + execve(command->path, final_argv, final_env); + *exit_status = EXIT_EXEC; + return -errno; +} + +int exec_spawn(Unit *unit, + ExecCommand *command, + const ExecContext *context, + const ExecParameters *params, + ExecRuntime *runtime, + pid_t *ret) { + + _cleanup_strv_free_ char **files_env = NULL; + int *fds = NULL; unsigned n_fds = 0; + _cleanup_free_ char *line = NULL; + int socket_fd, r; + char **argv; + pid_t pid; + + assert(unit); + assert(command); + assert(context); + assert(ret); + assert(params); + assert(params->fds || params->n_fds <= 0); + + if (context->std_input == EXEC_INPUT_SOCKET || + context->std_output == EXEC_OUTPUT_SOCKET || + context->std_error == EXEC_OUTPUT_SOCKET) { + + if (params->n_fds != 1) { + log_unit_error(unit, "Got more than one socket."); + return -EINVAL; + } + + socket_fd = params->fds[0]; + } else { + socket_fd = -1; + fds = params->fds; + n_fds = params->n_fds; + } + + r = exec_context_load_environment(unit, context, &files_env); + if (r < 0) + return log_unit_error_errno(unit, r, "Failed to load environment files: %m"); + + argv = params->argv ?: command->argv; + line = exec_command_line(argv); + if (!line) + return log_oom(); + + log_struct(LOG_DEBUG, + LOG_UNIT_ID(unit), + LOG_UNIT_MESSAGE(unit, "About to execute: %s", line), + "EXECUTABLE=%s", command->path, + NULL); + pid = fork(); + if (pid < 0) + return log_unit_error_errno(unit, errno, "Failed to fork: %m"); + + if (pid == 0) { + int exit_status; + + r = exec_child(unit, + command, + context, + params, + runtime, + argv, + socket_fd, + fds, n_fds, + files_env, + &exit_status); + if (r < 0) { + log_open(); + log_struct_errno(LOG_ERR, r, + LOG_MESSAGE_ID(SD_MESSAGE_SPAWN_FAILED), + LOG_UNIT_ID(unit), + LOG_UNIT_MESSAGE(unit, "Failed at step %s spawning %s: %m", + exit_status_to_string(exit_status, EXIT_STATUS_SYSTEMD), + command->path), + "EXECUTABLE=%s", command->path, + NULL); + } + + _exit(exit_status); + } + + log_unit_debug(unit, "Forked %s as "PID_FMT, command->path, pid); + + /* We add the new process to the cgroup both in the child (so + * that we can be sure that no user code is ever executed + * outside of the cgroup) and in the parent (so that we can be + * sure that when we kill the cgroup the process will be + * killed too). */ + if (params->cgroup_path) + (void) cg_attach(SYSTEMD_CGROUP_CONTROLLER, params->cgroup_path, pid); + + exec_status_start(&command->exec_status, pid); + + *ret = pid; + return 0; +} + +void exec_context_init(ExecContext *c) { + assert(c); + + c->umask = 0022; + c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_CLASS_BE, 0); + c->cpu_sched_policy = SCHED_OTHER; + c->syslog_priority = LOG_DAEMON|LOG_INFO; + c->syslog_level_prefix = true; + c->ignore_sigpipe = true; + c->timer_slack_nsec = NSEC_INFINITY; + c->personality = PERSONALITY_INVALID; + c->runtime_directory_mode = 0755; + c->capability_bounding_set = CAP_ALL; +} + +void exec_context_done(ExecContext *c) { + unsigned l; + + assert(c); + + c->environment = strv_free(c->environment); + c->environment_files = strv_free(c->environment_files); + c->pass_environment = strv_free(c->pass_environment); + + for (l = 0; l < ELEMENTSOF(c->rlimit); l++) + c->rlimit[l] = mfree(c->rlimit[l]); + + c->working_directory = mfree(c->working_directory); + c->root_directory = mfree(c->root_directory); + c->tty_path = mfree(c->tty_path); + c->syslog_identifier = mfree(c->syslog_identifier); + c->user = mfree(c->user); + c->group = mfree(c->group); + + c->supplementary_groups = strv_free(c->supplementary_groups); + + c->pam_name = mfree(c->pam_name); + + if (c->capabilities) { + cap_free(c->capabilities); + c->capabilities = NULL; + } + + 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); + + if (c->cpuset) + CPU_FREE(c->cpuset); + + c->utmp_id = mfree(c->utmp_id); + c->selinux_context = mfree(c->selinux_context); + c->apparmor_profile = mfree(c->apparmor_profile); + + c->syscall_filter = set_free(c->syscall_filter); + c->syscall_archs = set_free(c->syscall_archs); + c->address_families = set_free(c->address_families); + + c->runtime_directory = strv_free(c->runtime_directory); + + bus_endpoint_free(c->bus_endpoint); + c->bus_endpoint = NULL; +} + +int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_prefix) { + char **i; + + assert(c); + + if (!runtime_prefix) + return 0; + + STRV_FOREACH(i, c->runtime_directory) { + _cleanup_free_ char *p; + + p = strjoin(runtime_prefix, "/", *i, NULL); + if (!p) + return -ENOMEM; + + /* We execute this synchronously, since we need to be + * sure this is gone when we start the service + * next. */ + (void) rm_rf(p, REMOVE_ROOT); + } + + return 0; +} + +void exec_command_done(ExecCommand *c) { + assert(c); + + c->path = mfree(c->path); + + c->argv = strv_free(c->argv); +} + +void exec_command_done_array(ExecCommand *c, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) + exec_command_done(c+i); +} + +ExecCommand* exec_command_free_list(ExecCommand *c) { + ExecCommand *i; + + while ((i = c)) { + LIST_REMOVE(command, c, i); + exec_command_done(i); + free(i); + } + + return NULL; +} + +void exec_command_free_array(ExecCommand **c, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) + c[i] = exec_command_free_list(c[i]); +} + +typedef struct InvalidEnvInfo { + Unit *unit; + const char *path; +} InvalidEnvInfo; + +static void invalid_env(const char *p, void *userdata) { + InvalidEnvInfo *info = userdata; + + log_unit_error(info->unit, "Ignoring invalid environment assignment '%s': %s", p, info->path); +} + +int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l) { + char **i, **r = NULL; + + assert(c); + assert(l); + + STRV_FOREACH(i, c->environment_files) { + char *fn; + int k; + bool ignore = false; + char **p; + _cleanup_globfree_ glob_t pglob = {}; + int count, n; + + fn = *i; + + if (fn[0] == '-') { + ignore = true; + fn ++; + } + + if (!path_is_absolute(fn)) { + if (ignore) + continue; + + strv_free(r); + return -EINVAL; + } + + /* Filename supports globbing, take all matching files */ + errno = 0; + if (glob(fn, 0, NULL, &pglob) != 0) { + if (ignore) + continue; + + strv_free(r); + return errno > 0 ? -errno : -EINVAL; + } + count = pglob.gl_pathc; + if (count == 0) { + if (ignore) + continue; + + strv_free(r); + return -EINVAL; + } + for (n = 0; n < count; n++) { + k = load_env_file(NULL, pglob.gl_pathv[n], NULL, &p); + if (k < 0) { + if (ignore) + continue; + + strv_free(r); + return k; + } + /* Log invalid environment variables with filename */ + if (p) { + InvalidEnvInfo info = { + .unit = unit, + .path = pglob.gl_pathv[n] + }; + + p = strv_env_clean_with_callback(p, invalid_env, &info); + } + + if (r == NULL) + r = p; + else { + char **m; + + m = strv_env_merge(2, r, p); + strv_free(r); + strv_free(p); + if (!m) + return -ENOMEM; + + r = m; + } + } + } + + *l = r; + + return 0; +} + +static bool tty_may_match_dev_console(const char *tty) { + _cleanup_free_ char *active = NULL; + char *console; + + if (!tty) + return true; + + if (startswith(tty, "/dev/")) + tty += 5; + + /* trivial identity? */ + if (streq(tty, "console")) + return true; + + console = resolve_dev_console(&active); + /* if we could not resolve, assume it may */ + if (!console) + return true; + + /* "tty0" means the active VC, so it may be the same sometimes */ + return streq(console, tty) || (streq(console, "tty0") && tty_is_vc(tty)); +} + +bool exec_context_may_touch_console(ExecContext *ec) { + + return (ec->tty_reset || + ec->tty_vhangup || + ec->tty_vt_disallocate || + is_terminal_input(ec->std_input) || + is_terminal_output(ec->std_output) || + is_terminal_output(ec->std_error)) && + tty_may_match_dev_console(exec_context_tty_path(ec)); +} + +static void strv_fprintf(FILE *f, char **l) { + char **g; + + assert(f); + + STRV_FOREACH(g, l) + fprintf(f, " %s", *g); +} + +void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { + char **e, **d; + unsigned i; + + assert(c); + assert(f); + + prefix = strempty(prefix); + + fprintf(f, + "%sUMask: %04o\n" + "%sWorkingDirectory: %s\n" + "%sRootDirectory: %s\n" + "%sNonBlocking: %s\n" + "%sPrivateTmp: %s\n" + "%sPrivateNetwork: %s\n" + "%sPrivateDevices: %s\n" + "%sProtectHome: %s\n" + "%sProtectSystem: %s\n" + "%sIgnoreSIGPIPE: %s\n", + prefix, c->umask, + prefix, c->working_directory ? c->working_directory : "/", + prefix, c->root_directory ? c->root_directory : "/", + prefix, yes_no(c->non_blocking), + prefix, yes_no(c->private_tmp), + prefix, yes_no(c->private_network), + prefix, yes_no(c->private_devices), + prefix, protect_home_to_string(c->protect_home), + prefix, protect_system_to_string(c->protect_system), + prefix, yes_no(c->ignore_sigpipe)); + + STRV_FOREACH(e, c->environment) + fprintf(f, "%sEnvironment: %s\n", prefix, *e); + + STRV_FOREACH(e, c->environment_files) + fprintf(f, "%sEnvironmentFile: %s\n", prefix, *e); + + STRV_FOREACH(e, c->pass_environment) + fprintf(f, "%sPassEnvironment: %s\n", prefix, *e); + + fprintf(f, "%sRuntimeDirectoryMode: %04o\n", prefix, c->runtime_directory_mode); + + STRV_FOREACH(d, c->runtime_directory) + fprintf(f, "%sRuntimeDirectory: %s\n", prefix, *d); + + if (c->nice_set) + fprintf(f, + "%sNice: %i\n", + prefix, c->nice); + + if (c->oom_score_adjust_set) + fprintf(f, + "%sOOMScoreAdjust: %i\n", + prefix, c->oom_score_adjust); + + for (i = 0; i < RLIM_NLIMITS; i++) + if (c->rlimit[i]) { + fprintf(f, "%s%s: " RLIM_FMT "\n", + prefix, rlimit_to_string(i), c->rlimit[i]->rlim_max); + fprintf(f, "%s%sSoft: " RLIM_FMT "\n", + prefix, rlimit_to_string(i), c->rlimit[i]->rlim_cur); + } + + if (c->ioprio_set) { + _cleanup_free_ char *class_str = NULL; + + ioprio_class_to_string_alloc(IOPRIO_PRIO_CLASS(c->ioprio), &class_str); + fprintf(f, + "%sIOSchedulingClass: %s\n" + "%sIOPriority: %i\n", + prefix, strna(class_str), + prefix, (int) IOPRIO_PRIO_DATA(c->ioprio)); + } + + if (c->cpu_sched_set) { + _cleanup_free_ char *policy_str = NULL; + + sched_policy_to_string_alloc(c->cpu_sched_policy, &policy_str); + fprintf(f, + "%sCPUSchedulingPolicy: %s\n" + "%sCPUSchedulingPriority: %i\n" + "%sCPUSchedulingResetOnFork: %s\n", + prefix, strna(policy_str), + prefix, c->cpu_sched_priority, + prefix, yes_no(c->cpu_sched_reset_on_fork)); + } + + if (c->cpuset) { + fprintf(f, "%sCPUAffinity:", prefix); + for (i = 0; i < c->cpuset_ncpus; i++) + if (CPU_ISSET_S(i, CPU_ALLOC_SIZE(c->cpuset_ncpus), c->cpuset)) + fprintf(f, " %u", i); + fputs("\n", f); + } + + if (c->timer_slack_nsec != NSEC_INFINITY) + fprintf(f, "%sTimerSlackNSec: "NSEC_FMT "\n", prefix, c->timer_slack_nsec); + + fprintf(f, + "%sStandardInput: %s\n" + "%sStandardOutput: %s\n" + "%sStandardError: %s\n", + prefix, exec_input_to_string(c->std_input), + prefix, exec_output_to_string(c->std_output), + prefix, exec_output_to_string(c->std_error)); + + if (c->tty_path) + fprintf(f, + "%sTTYPath: %s\n" + "%sTTYReset: %s\n" + "%sTTYVHangup: %s\n" + "%sTTYVTDisallocate: %s\n", + prefix, c->tty_path, + prefix, yes_no(c->tty_reset), + prefix, yes_no(c->tty_vhangup), + prefix, yes_no(c->tty_vt_disallocate)); + + if (c->std_output == EXEC_OUTPUT_SYSLOG || + c->std_output == EXEC_OUTPUT_KMSG || + c->std_output == EXEC_OUTPUT_JOURNAL || + c->std_output == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || + c->std_output == EXEC_OUTPUT_KMSG_AND_CONSOLE || + c->std_output == EXEC_OUTPUT_JOURNAL_AND_CONSOLE || + c->std_error == EXEC_OUTPUT_SYSLOG || + c->std_error == EXEC_OUTPUT_KMSG || + c->std_error == EXEC_OUTPUT_JOURNAL || + c->std_error == EXEC_OUTPUT_SYSLOG_AND_CONSOLE || + c->std_error == EXEC_OUTPUT_KMSG_AND_CONSOLE || + c->std_error == EXEC_OUTPUT_JOURNAL_AND_CONSOLE) { + + _cleanup_free_ char *fac_str = NULL, *lvl_str = NULL; + + log_facility_unshifted_to_string_alloc(c->syslog_priority >> 3, &fac_str); + log_level_to_string_alloc(LOG_PRI(c->syslog_priority), &lvl_str); + + fprintf(f, + "%sSyslogFacility: %s\n" + "%sSyslogLevel: %s\n", + prefix, strna(fac_str), + prefix, strna(lvl_str)); + } + + if (c->capabilities) { + _cleanup_cap_free_charp_ char *t; + + t = cap_to_text(c->capabilities, NULL); + if (t) + fprintf(f, "%sCapabilities: %s\n", prefix, t); + } + + if (c->secure_bits) + fprintf(f, "%sSecure Bits:%s%s%s%s%s%s\n", + prefix, + (c->secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<secure_bits & 1<capability_bounding_set != CAP_ALL) { + unsigned long l; + fprintf(f, "%sCapabilityBoundingSet:", prefix); + + for (l = 0; l <= cap_last_cap(); l++) + if (c->capability_bounding_set & (UINT64_C(1) << l)) + fprintf(f, " %s", strna(capability_to_name(l))); + + fputs("\n", f); + } + + if (c->capability_ambient_set != 0) { + unsigned long l; + fprintf(f, "%sAmbientCapabilities:", prefix); + + for (l = 0; l <= cap_last_cap(); l++) + if (c->capability_ambient_set & (UINT64_C(1) << l)) + fprintf(f, " %s", strna(capability_to_name(l))); + + fputs("\n", f); + } + + if (c->user) + fprintf(f, "%sUser: %s\n", prefix, c->user); + if (c->group) + fprintf(f, "%sGroup: %s\n", prefix, c->group); + + if (strv_length(c->supplementary_groups) > 0) { + fprintf(f, "%sSupplementaryGroups:", prefix); + strv_fprintf(f, c->supplementary_groups); + fputs("\n", f); + } + + 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); + fputs("\n", f); + } + + if (strv_length(c->read_only_dirs) > 0) { + fprintf(f, "%sReadOnlyDirs:", prefix); + strv_fprintf(f, c->read_only_dirs); + fputs("\n", f); + } + + if (strv_length(c->inaccessible_dirs) > 0) { + fprintf(f, "%sInaccessibleDirs:", prefix); + strv_fprintf(f, c->inaccessible_dirs); + fputs("\n", f); + } + + if (c->utmp_id) + fprintf(f, + "%sUtmpIdentifier: %s\n", + prefix, c->utmp_id); + + if (c->selinux_context) + fprintf(f, + "%sSELinuxContext: %s%s\n", + prefix, c->selinux_context_ignore ? "-" : "", c->selinux_context); + + if (c->personality != PERSONALITY_INVALID) + fprintf(f, + "%sPersonality: %s\n", + prefix, strna(personality_to_string(c->personality))); + + if (c->syscall_filter) { +#ifdef HAVE_SECCOMP + Iterator j; + void *id; + bool first = true; +#endif + + fprintf(f, + "%sSystemCallFilter: ", + prefix); + + if (!c->syscall_whitelist) + fputc('~', f); + +#ifdef HAVE_SECCOMP + SET_FOREACH(id, c->syscall_filter, j) { + _cleanup_free_ char *name = NULL; + + if (first) + first = false; + else + fputc(' ', f); + + name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1); + fputs(strna(name), f); + } +#endif + + fputc('\n', f); + } + + if (c->syscall_archs) { +#ifdef HAVE_SECCOMP + Iterator j; + void *id; +#endif + + fprintf(f, + "%sSystemCallArchitectures:", + prefix); + +#ifdef HAVE_SECCOMP + SET_FOREACH(id, c->syscall_archs, j) + fprintf(f, " %s", strna(seccomp_arch_to_string(PTR_TO_UINT32(id) - 1))); +#endif + fputc('\n', f); + } + + if (c->syscall_errno > 0) + fprintf(f, + "%sSystemCallErrorNumber: %s\n", + prefix, strna(errno_to_name(c->syscall_errno))); + + if (c->apparmor_profile) + fprintf(f, + "%sAppArmorProfile: %s%s\n", + prefix, c->apparmor_profile_ignore ? "-" : "", c->apparmor_profile); +} + +bool exec_context_maintains_privileges(ExecContext *c) { + assert(c); + + /* Returns true if the process forked off would run run under + * an unchanged UID or as root. */ + + if (!c->user) + return true; + + if (streq(c->user, "root") || streq(c->user, "0")) + return true; + + return false; +} + +void exec_status_start(ExecStatus *s, pid_t pid) { + assert(s); + + zero(*s); + s->pid = pid; + dual_timestamp_get(&s->start_timestamp); +} + +void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status) { + assert(s); + + if (s->pid && s->pid != pid) + zero(*s); + + s->pid = pid; + dual_timestamp_get(&s->exit_timestamp); + + s->code = code; + s->status = status; + + if (context) { + if (context->utmp_id) + utmp_put_dead_process(context->utmp_id, pid, code, status); + + exec_context_tty_reset(context, NULL); + } +} + +void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix) { + char buf[FORMAT_TIMESTAMP_MAX]; + + assert(s); + assert(f); + + if (s->pid <= 0) + return; + + prefix = strempty(prefix); + + fprintf(f, + "%sPID: "PID_FMT"\n", + prefix, s->pid); + + if (s->start_timestamp.realtime > 0) + fprintf(f, + "%sStart Timestamp: %s\n", + prefix, format_timestamp(buf, sizeof(buf), s->start_timestamp.realtime)); + + if (s->exit_timestamp.realtime > 0) + fprintf(f, + "%sExit Timestamp: %s\n" + "%sExit Code: %s\n" + "%sExit Status: %i\n", + prefix, format_timestamp(buf, sizeof(buf), s->exit_timestamp.realtime), + prefix, sigchld_code_to_string(s->code), + prefix, s->status); +} + +char *exec_command_line(char **argv) { + size_t k; + char *n, *p, **a; + bool first = true; + + assert(argv); + + k = 1; + STRV_FOREACH(a, argv) + k += strlen(*a)+3; + + if (!(n = new(char, k))) + return NULL; + + p = n; + STRV_FOREACH(a, argv) { + + if (!first) + *(p++) = ' '; + else + first = false; + + if (strpbrk(*a, WHITESPACE)) { + *(p++) = '\''; + p = stpcpy(p, *a); + *(p++) = '\''; + } else + p = stpcpy(p, *a); + + } + + *p = 0; + + /* FIXME: this doesn't really handle arguments that have + * spaces and ticks in them */ + + return n; +} + +void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix) { + _cleanup_free_ char *cmd = NULL; + const char *prefix2; + + assert(c); + assert(f); + + prefix = strempty(prefix); + prefix2 = strjoina(prefix, "\t"); + + cmd = exec_command_line(c->argv); + fprintf(f, + "%sCommand Line: %s\n", + prefix, cmd ? cmd : strerror(ENOMEM)); + + exec_status_dump(&c->exec_status, f, prefix2); +} + +void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix) { + assert(f); + + prefix = strempty(prefix); + + LIST_FOREACH(command, c, c) + exec_command_dump(c, f, prefix); +} + +void exec_command_append_list(ExecCommand **l, ExecCommand *e) { + ExecCommand *end; + + assert(l); + assert(e); + + if (*l) { + /* It's kind of important, that we keep the order here */ + LIST_FIND_TAIL(command, *l, end); + LIST_INSERT_AFTER(command, *l, end, e); + } else + *l = e; +} + +int exec_command_set(ExecCommand *c, const char *path, ...) { + va_list ap; + char **l, *p; + + assert(c); + assert(path); + + va_start(ap, path); + l = strv_new_ap(path, ap); + va_end(ap); + + if (!l) + return -ENOMEM; + + p = strdup(path); + if (!p) { + strv_free(l); + return -ENOMEM; + } + + free(c->path); + c->path = p; + + strv_free(c->argv); + c->argv = l; + + return 0; +} + +int exec_command_append(ExecCommand *c, const char *path, ...) { + _cleanup_strv_free_ char **l = NULL; + va_list ap; + int r; + + assert(c); + assert(path); + + va_start(ap, path); + l = strv_new_ap(path, ap); + va_end(ap); + + if (!l) + return -ENOMEM; + + r = strv_extend_strv(&c->argv, l, false); + if (r < 0) + return r; + + return 0; +} + + +static int exec_runtime_allocate(ExecRuntime **rt) { + + if (*rt) + return 0; + + *rt = new0(ExecRuntime, 1); + if (!*rt) + return -ENOMEM; + + (*rt)->n_ref = 1; + (*rt)->netns_storage_socket[0] = (*rt)->netns_storage_socket[1] = -1; + + return 0; +} + +int exec_runtime_make(ExecRuntime **rt, ExecContext *c, const char *id) { + int r; + + assert(rt); + assert(c); + assert(id); + + if (*rt) + return 1; + + if (!c->private_network && !c->private_tmp) + return 0; + + r = exec_runtime_allocate(rt); + if (r < 0) + return r; + + if (c->private_network && (*rt)->netns_storage_socket[0] < 0) { + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, (*rt)->netns_storage_socket) < 0) + return -errno; + } + + if (c->private_tmp && !(*rt)->tmp_dir) { + r = setup_tmp_dirs(id, &(*rt)->tmp_dir, &(*rt)->var_tmp_dir); + if (r < 0) + return r; + } + + return 1; +} + +ExecRuntime *exec_runtime_ref(ExecRuntime *r) { + assert(r); + assert(r->n_ref > 0); + + r->n_ref++; + return r; +} + +ExecRuntime *exec_runtime_unref(ExecRuntime *r) { + + if (!r) + return NULL; + + assert(r->n_ref > 0); + + r->n_ref--; + if (r->n_ref > 0) + return NULL; + + free(r->tmp_dir); + free(r->var_tmp_dir); + safe_close_pair(r->netns_storage_socket); + free(r); + + return NULL; +} + +int exec_runtime_serialize(Unit *u, ExecRuntime *rt, FILE *f, FDSet *fds) { + assert(u); + assert(f); + assert(fds); + + if (!rt) + return 0; + + if (rt->tmp_dir) + unit_serialize_item(u, f, "tmp-dir", rt->tmp_dir); + + if (rt->var_tmp_dir) + unit_serialize_item(u, f, "var-tmp-dir", rt->var_tmp_dir); + + if (rt->netns_storage_socket[0] >= 0) { + int copy; + + copy = fdset_put_dup(fds, rt->netns_storage_socket[0]); + if (copy < 0) + return copy; + + unit_serialize_item_format(u, f, "netns-socket-0", "%i", copy); + } + + if (rt->netns_storage_socket[1] >= 0) { + int copy; + + copy = fdset_put_dup(fds, rt->netns_storage_socket[1]); + if (copy < 0) + return copy; + + unit_serialize_item_format(u, f, "netns-socket-1", "%i", copy); + } + + return 0; +} + +int exec_runtime_deserialize_item(Unit *u, ExecRuntime **rt, const char *key, const char *value, FDSet *fds) { + int r; + + assert(rt); + assert(key); + assert(value); + + if (streq(key, "tmp-dir")) { + char *copy; + + r = exec_runtime_allocate(rt); + if (r < 0) + return log_oom(); + + copy = strdup(value); + if (!copy) + return log_oom(); + + free((*rt)->tmp_dir); + (*rt)->tmp_dir = copy; + + } else if (streq(key, "var-tmp-dir")) { + char *copy; + + r = exec_runtime_allocate(rt); + if (r < 0) + return log_oom(); + + copy = strdup(value); + if (!copy) + return log_oom(); + + free((*rt)->var_tmp_dir); + (*rt)->var_tmp_dir = copy; + + } else if (streq(key, "netns-socket-0")) { + int fd; + + r = exec_runtime_allocate(rt); + if (r < 0) + return log_oom(); + + if (safe_atoi(value, &fd) < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse netns socket value: %s", value); + else { + safe_close((*rt)->netns_storage_socket[0]); + (*rt)->netns_storage_socket[0] = fdset_remove(fds, fd); + } + } else if (streq(key, "netns-socket-1")) { + int fd; + + r = exec_runtime_allocate(rt); + if (r < 0) + return log_oom(); + + if (safe_atoi(value, &fd) < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse netns socket value: %s", value); + else { + safe_close((*rt)->netns_storage_socket[1]); + (*rt)->netns_storage_socket[1] = fdset_remove(fds, fd); + } + } else + return 0; + + return 1; +} + +static void *remove_tmpdir_thread(void *p) { + _cleanup_free_ char *path = p; + + (void) rm_rf(path, REMOVE_ROOT|REMOVE_PHYSICAL); + return NULL; +} + +void exec_runtime_destroy(ExecRuntime *rt) { + int r; + + if (!rt) + return; + + /* If there are multiple users of this, let's leave the stuff around */ + if (rt->n_ref > 1) + return; + + if (rt->tmp_dir) { + log_debug("Spawning thread to nuke %s", rt->tmp_dir); + + r = asynchronous_job(remove_tmpdir_thread, rt->tmp_dir); + if (r < 0) { + log_warning_errno(r, "Failed to nuke %s: %m", rt->tmp_dir); + free(rt->tmp_dir); + } + + rt->tmp_dir = NULL; + } + + if (rt->var_tmp_dir) { + log_debug("Spawning thread to nuke %s", rt->var_tmp_dir); + + r = asynchronous_job(remove_tmpdir_thread, rt->var_tmp_dir); + if (r < 0) { + log_warning_errno(r, "Failed to nuke %s: %m", rt->var_tmp_dir); + free(rt->var_tmp_dir); + } + + rt->var_tmp_dir = NULL; + } + + safe_close_pair(rt->netns_storage_socket); +} + +static const char* const exec_input_table[_EXEC_INPUT_MAX] = { + [EXEC_INPUT_NULL] = "null", + [EXEC_INPUT_TTY] = "tty", + [EXEC_INPUT_TTY_FORCE] = "tty-force", + [EXEC_INPUT_TTY_FAIL] = "tty-fail", + [EXEC_INPUT_SOCKET] = "socket" +}; + +DEFINE_STRING_TABLE_LOOKUP(exec_input, ExecInput); + +static const char* const exec_output_table[_EXEC_OUTPUT_MAX] = { + [EXEC_OUTPUT_INHERIT] = "inherit", + [EXEC_OUTPUT_NULL] = "null", + [EXEC_OUTPUT_TTY] = "tty", + [EXEC_OUTPUT_SYSLOG] = "syslog", + [EXEC_OUTPUT_SYSLOG_AND_CONSOLE] = "syslog+console", + [EXEC_OUTPUT_KMSG] = "kmsg", + [EXEC_OUTPUT_KMSG_AND_CONSOLE] = "kmsg+console", + [EXEC_OUTPUT_JOURNAL] = "journal", + [EXEC_OUTPUT_JOURNAL_AND_CONSOLE] = "journal+console", + [EXEC_OUTPUT_SOCKET] = "socket" +}; + +DEFINE_STRING_TABLE_LOOKUP(exec_output, ExecOutput); + +static const char* const exec_utmp_mode_table[_EXEC_UTMP_MODE_MAX] = { + [EXEC_UTMP_INIT] = "init", + [EXEC_UTMP_LOGIN] = "login", + [EXEC_UTMP_USER] = "user", +}; + +DEFINE_STRING_TABLE_LOOKUP(exec_utmp_mode, ExecUtmpMode); diff --git a/src/libcore/execute.h b/src/libcore/execute.h new file mode 100644 index 0000000000..578f85b6bc --- /dev/null +++ b/src/libcore/execute.h @@ -0,0 +1,297 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct ExecStatus ExecStatus; +typedef struct ExecCommand ExecCommand; +typedef struct ExecContext ExecContext; +typedef struct ExecRuntime ExecRuntime; +typedef struct ExecParameters ExecParameters; + +#include +#include +#include +#include + +#include "bus-endpoint.h" +#include "fdset.h" +#include "list.h" +#include "missing.h" +#include "namespace.h" + +typedef enum ExecUtmpMode { + EXEC_UTMP_INIT, + EXEC_UTMP_LOGIN, + EXEC_UTMP_USER, + _EXEC_UTMP_MODE_MAX, + _EXEC_UTMP_MODE_INVALID = -1 +} ExecUtmpMode; + +typedef enum ExecInput { + EXEC_INPUT_NULL, + EXEC_INPUT_TTY, + EXEC_INPUT_TTY_FORCE, + EXEC_INPUT_TTY_FAIL, + EXEC_INPUT_SOCKET, + _EXEC_INPUT_MAX, + _EXEC_INPUT_INVALID = -1 +} ExecInput; + +typedef enum ExecOutput { + EXEC_OUTPUT_INHERIT, + EXEC_OUTPUT_NULL, + EXEC_OUTPUT_TTY, + EXEC_OUTPUT_SYSLOG, + EXEC_OUTPUT_SYSLOG_AND_CONSOLE, + EXEC_OUTPUT_KMSG, + EXEC_OUTPUT_KMSG_AND_CONSOLE, + EXEC_OUTPUT_JOURNAL, + EXEC_OUTPUT_JOURNAL_AND_CONSOLE, + EXEC_OUTPUT_SOCKET, + _EXEC_OUTPUT_MAX, + _EXEC_OUTPUT_INVALID = -1 +} ExecOutput; + +struct ExecStatus { + dual_timestamp start_timestamp; + dual_timestamp exit_timestamp; + pid_t pid; + int code; /* as in siginfo_t::si_code */ + int status; /* as in sigingo_t::si_status */ +}; + +struct ExecCommand { + char *path; + char **argv; + ExecStatus exec_status; + LIST_FIELDS(ExecCommand, command); /* useful for chaining commands */ + bool ignore; +}; + +struct ExecRuntime { + int n_ref; + + char *tmp_dir; + char *var_tmp_dir; + + int netns_storage_socket[2]; +}; + +struct ExecContext { + char **environment; + char **environment_files; + char **pass_environment; + + struct rlimit *rlimit[_RLIMIT_MAX]; + char *working_directory, *root_directory; + bool working_directory_missing_ok; + bool working_directory_home; + + mode_t umask; + int oom_score_adjust; + int nice; + int ioprio; + int cpu_sched_policy; + int cpu_sched_priority; + + cpu_set_t *cpuset; + unsigned cpuset_ncpus; + + ExecInput std_input; + ExecOutput std_output; + ExecOutput std_error; + + nsec_t timer_slack_nsec; + + bool stdio_as_fds; + + char *tty_path; + + bool tty_reset; + bool tty_vhangup; + bool tty_vt_disallocate; + + bool ignore_sigpipe; + + /* Since resolving these names might might involve socket + * connections and we don't want to deadlock ourselves these + * names are resolved on execution only and in the child + * process. */ + char *user; + char *group; + char **supplementary_groups; + + char *pam_name; + + char *utmp_id; + ExecUtmpMode utmp_mode; + + bool selinux_context_ignore; + char *selinux_context; + + bool apparmor_profile_ignore; + char *apparmor_profile; + + bool smack_process_label_ignore; + char *smack_process_label; + + char **read_write_dirs, **read_only_dirs, **inaccessible_dirs; + unsigned long mount_flags; + + uint64_t capability_bounding_set; + + uint64_t capability_ambient_set; + + cap_t capabilities; + int secure_bits; + + int syslog_priority; + char *syslog_identifier; + bool syslog_level_prefix; + + bool cpu_sched_reset_on_fork; + bool non_blocking; + bool private_tmp; + bool private_network; + bool private_devices; + ProtectSystem protect_system; + ProtectHome protect_home; + + bool no_new_privileges; + + /* 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 + * that the autofs logic detects that it belongs to us and we + * don't enter a trigger loop. */ + bool same_pgrp; + + unsigned long personality; + + Set *syscall_filter; + Set *syscall_archs; + int syscall_errno; + bool syscall_whitelist:1; + + Set *address_families; + bool address_families_whitelist:1; + + char **runtime_directory; + mode_t runtime_directory_mode; + + 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; + + /* custom dbus enpoint */ + BusEndpoint *bus_endpoint; +}; + +#include "cgroup-util.h" +#include "cgroup.h" + +struct ExecParameters { + char **argv; + char **environment; + + int *fds; + char **fd_names; + unsigned n_fds; + + bool apply_permissions:1; + bool apply_chroot:1; + bool apply_tty_stdin:1; + + bool confirm_spawn:1; + bool selinux_context_net:1; + + bool cgroup_delegate:1; + CGroupMask cgroup_supported; + const char *cgroup_path; + + const char *runtime_prefix; + + usec_t watchdog_usec; + + int *idle_pipe; + + char *bus_endpoint_path; + int bus_endpoint_fd; + + int stdin_fd; + int stdout_fd; + int stderr_fd; +}; + +int exec_spawn(Unit *unit, + ExecCommand *command, + const ExecContext *context, + const ExecParameters *exec_params, + ExecRuntime *runtime, + pid_t *ret); + +void exec_command_done(ExecCommand *c); +void exec_command_done_array(ExecCommand *c, unsigned n); + +ExecCommand* exec_command_free_list(ExecCommand *c); +void exec_command_free_array(ExecCommand **c, unsigned n); + +char *exec_command_line(char **argv); + +void exec_command_dump(ExecCommand *c, FILE *f, const char *prefix); +void exec_command_dump_list(ExecCommand *c, FILE *f, const char *prefix); +void exec_command_append_list(ExecCommand **l, ExecCommand *e); +int exec_command_set(ExecCommand *c, const char *path, ...); +int exec_command_append(ExecCommand *c, const char *path, ...); + +void exec_context_init(ExecContext *c); +void exec_context_done(ExecContext *c); +void exec_context_dump(ExecContext *c, FILE* f, const char *prefix); + +int exec_context_destroy_runtime_directory(ExecContext *c, const char *runtime_root); + +int exec_context_load_environment(Unit *unit, const ExecContext *c, char ***l); + +bool exec_context_may_touch_console(ExecContext *c); +bool exec_context_maintains_privileges(ExecContext *c); + +void exec_status_start(ExecStatus *s, pid_t pid); +void exec_status_exit(ExecStatus *s, ExecContext *context, pid_t pid, int code, int status); +void exec_status_dump(ExecStatus *s, FILE *f, const char *prefix); + +int exec_runtime_make(ExecRuntime **rt, ExecContext *c, const char *id); +ExecRuntime *exec_runtime_ref(ExecRuntime *r); +ExecRuntime *exec_runtime_unref(ExecRuntime *r); + +int exec_runtime_serialize(Unit *unit, ExecRuntime *rt, FILE *f, FDSet *fds); +int exec_runtime_deserialize_item(Unit *unit, ExecRuntime **rt, const char *key, const char *value, FDSet *fds); + +void exec_runtime_destroy(ExecRuntime *rt); + +const char* exec_output_to_string(ExecOutput i) _const_; +ExecOutput exec_output_from_string(const char *s) _pure_; + +const char* exec_input_to_string(ExecInput i) _const_; +ExecInput exec_input_from_string(const char *s) _pure_; + +const char* exec_utmp_mode_to_string(ExecUtmpMode i) _const_; +ExecUtmpMode exec_utmp_mode_from_string(const char *s) _pure_; diff --git a/src/libcore/failure-action.c b/src/libcore/failure-action.c new file mode 100644 index 0000000000..39f5519ca1 --- /dev/null +++ b/src/libcore/failure-action.c @@ -0,0 +1,125 @@ +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + Copyright 2012 Michael Olbrich + + 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 . +***/ + +#include +#include + +#include "bus-error.h" +#include "bus-util.h" +#include "failure-action.h" +#include "special.h" +#include "string-table.h" +#include "terminal-util.h" + +static void log_and_status(Manager *m, const char *message) { + log_warning("%s", message); + manager_status_printf(m, STATUS_TYPE_EMERGENCY, + ANSI_HIGHLIGHT_RED " !! " ANSI_NORMAL, + "%s", message); +} + +int failure_action( + Manager *m, + FailureAction action, + const char *reboot_arg) { + + assert(m); + assert(action >= 0); + assert(action < _FAILURE_ACTION_MAX); + + if (action == FAILURE_ACTION_NONE) + return -ECANCELED; + + if (m->running_as == MANAGER_USER) { + /* Downgrade all options to simply exiting if we run + * in user mode */ + + log_warning("Exiting as result of failure."); + m->exit_code = MANAGER_EXIT; + return -ECANCELED; + } + + switch (action) { + + case FAILURE_ACTION_REBOOT: + log_and_status(m, "Rebooting as result of failure."); + + update_reboot_param_file(reboot_arg); + (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_REBOOT_TARGET, JOB_REPLACE, NULL); + + break; + + case FAILURE_ACTION_REBOOT_FORCE: + log_and_status(m, "Forcibly rebooting as result of failure."); + + update_reboot_param_file(reboot_arg); + m->exit_code = MANAGER_REBOOT; + break; + + case FAILURE_ACTION_REBOOT_IMMEDIATE: + log_and_status(m, "Rebooting immediately as result of failure."); + + sync(); + + if (reboot_arg) { + log_info("Rebooting with argument '%s'.", reboot_arg); + syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, reboot_arg); + } + + log_info("Rebooting."); + reboot(RB_AUTOBOOT); + break; + + case FAILURE_ACTION_POWEROFF: + log_and_status(m, "Powering off as result of failure."); + (void) manager_add_job_by_name_and_warn(m, JOB_START, SPECIAL_POWEROFF_TARGET, JOB_REPLACE, NULL); + break; + + case FAILURE_ACTION_POWEROFF_FORCE: + log_and_status(m, "Forcibly powering off as result of failure."); + m->exit_code = MANAGER_POWEROFF; + break; + + case FAILURE_ACTION_POWEROFF_IMMEDIATE: + log_and_status(m, "Powering off immediately as result of failure."); + + sync(); + + log_info("Powering off."); + reboot(RB_POWER_OFF); + break; + + default: + assert_not_reached("Unknown failure action"); + } + + return -ECANCELED; +} + +static const char* const failure_action_table[_FAILURE_ACTION_MAX] = { + [FAILURE_ACTION_NONE] = "none", + [FAILURE_ACTION_REBOOT] = "reboot", + [FAILURE_ACTION_REBOOT_FORCE] = "reboot-force", + [FAILURE_ACTION_REBOOT_IMMEDIATE] = "reboot-immediate", + [FAILURE_ACTION_POWEROFF] = "poweroff", + [FAILURE_ACTION_POWEROFF_FORCE] = "poweroff-force", + [FAILURE_ACTION_POWEROFF_IMMEDIATE] = "poweroff-immediate" +}; +DEFINE_STRING_TABLE_LOOKUP(failure_action, FailureAction); diff --git a/src/libcore/failure-action.h b/src/libcore/failure-action.h new file mode 100644 index 0000000000..1adac4ad5c --- /dev/null +++ b/src/libcore/failure-action.h @@ -0,0 +1,41 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Lennart Poettering + Copyright 2012 Michael Olbrich + + 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 . +***/ + +typedef enum FailureAction { + FAILURE_ACTION_NONE, + FAILURE_ACTION_REBOOT, + FAILURE_ACTION_REBOOT_FORCE, + FAILURE_ACTION_REBOOT_IMMEDIATE, + FAILURE_ACTION_POWEROFF, + FAILURE_ACTION_POWEROFF_FORCE, + FAILURE_ACTION_POWEROFF_IMMEDIATE, + _FAILURE_ACTION_MAX, + _FAILURE_ACTION_INVALID = -1 +} FailureAction; + +#include "macro.h" +#include "manager.h" + +int failure_action(Manager *m, FailureAction action, const char *reboot_arg); + +const char* failure_action_to_string(FailureAction i) _const_; +FailureAction failure_action_from_string(const char *s) _pure_; diff --git a/src/libcore/hostname-setup.c b/src/libcore/hostname-setup.c new file mode 100644 index 0000000000..68be52856b --- /dev/null +++ b/src/libcore/hostname-setup.c @@ -0,0 +1,68 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#include "alloc-util.h" +#include "fileio.h" +#include "hostname-setup.h" +#include "hostname-util.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "util.h" + +int hostname_setup(void) { + int r; + _cleanup_free_ char *b = NULL; + const char *hn; + bool enoent = false; + + r = read_hostname_config("/etc/hostname", &b); + if (r < 0) { + if (r == -ENOENT) + enoent = true; + else + log_warning_errno(r, "Failed to read configured hostname: %m"); + + hn = NULL; + } else + hn = b; + + if (isempty(hn)) { + /* Don't override the hostname if it is already set + * and not explicitly configured */ + if (hostname_is_set()) + return 0; + + if (enoent) + log_info("No hostname configured."); + + hn = "localhost"; + } + + r = sethostname_idempotent(hn); + if (r < 0) + return log_warning_errno(r, "Failed to set hostname to <%s>: %m", hn); + + log_info("Set hostname to <%s>.", hn); + return 0; +} diff --git a/src/libcore/hostname-setup.h b/src/libcore/hostname-setup.h new file mode 100644 index 0000000000..73e8c75c71 --- /dev/null +++ b/src/libcore/hostname-setup.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int hostname_setup(void); diff --git a/src/libcore/ima-setup.c b/src/libcore/ima-setup.c new file mode 100644 index 0000000000..ff7558d500 --- /dev/null +++ b/src/libcore/ima-setup.c @@ -0,0 +1,80 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy + TORSEC group -- http://security.polito.it + + 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 . +***/ + +#include +#include + +#include "fd-util.h" +#include "fileio.h" +#include "ima-setup.h" +#include "log.h" +#include "util.h" + +#define IMA_SECFS_DIR "/sys/kernel/security/ima" +#define IMA_SECFS_POLICY IMA_SECFS_DIR "/policy" +#define IMA_POLICY_PATH "/etc/ima/ima-policy" + +int ima_setup(void) { +#ifdef HAVE_IMA + _cleanup_fclose_ FILE *input = NULL; + _cleanup_close_ int imafd = -1; + unsigned lineno = 0; + char line[page_size()]; + + if (access(IMA_SECFS_DIR, F_OK) < 0) { + log_debug("IMA support is disabled in the kernel, ignoring."); + return 0; + } + + input = fopen(IMA_POLICY_PATH, "re"); + if (!input) { + log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, + "Failed to open the IMA custom policy file "IMA_POLICY_PATH", ignoring: %m"); + return 0; + } + + if (access(IMA_SECFS_POLICY, F_OK) < 0) { + log_warning("Another IMA custom policy has already been loaded, ignoring."); + return 0; + } + + imafd = open(IMA_SECFS_POLICY, O_WRONLY|O_CLOEXEC); + if (imafd < 0) { + log_error_errno(errno, "Failed to open the IMA kernel interface "IMA_SECFS_POLICY", ignoring: %m"); + return 0; + } + + FOREACH_LINE(line, input, + return log_error_errno(errno, "Failed to read the IMA custom policy file "IMA_POLICY_PATH": %m")) { + size_t len; + + len = strlen(line); + lineno++; + + if (len > 0 && write(imafd, line, len) < 0) + return log_error_errno(errno, "Failed to load the IMA custom policy file "IMA_POLICY_PATH"%u: %m", + lineno); + } + + log_info("Successfully loaded the IMA custom policy "IMA_POLICY_PATH"."); +#endif /* HAVE_IMA */ + return 0; +} diff --git a/src/libcore/ima-setup.h b/src/libcore/ima-setup.h new file mode 100644 index 0000000000..3bad74b246 --- /dev/null +++ b/src/libcore/ima-setup.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright (C) 2012 Roberto Sassu - Politecnico di Torino, Italy + TORSEC group -- http://security.polito.it + + 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 . +***/ + +int ima_setup(void); diff --git a/src/libcore/job.c b/src/libcore/job.c new file mode 100644 index 0000000000..1e2f12bba0 --- /dev/null +++ b/src/libcore/job.c @@ -0,0 +1,1249 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include +#include + +#include "alloc-util.h" +#include "async.h" +#include "dbus-job.h" +#include "dbus.h" +#include "escape.h" +#include "job.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "set.h" +#include "special.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "unit.h" +#include "virt.h" + +Job* job_new_raw(Unit *unit) { + Job *j; + + /* used for deserialization */ + + assert(unit); + + j = new0(Job, 1); + if (!j) + return NULL; + + j->manager = unit->manager; + j->unit = unit; + j->type = _JOB_TYPE_INVALID; + + return j; +} + +Job* job_new(Unit *unit, JobType type) { + Job *j; + + assert(type < _JOB_TYPE_MAX); + + j = job_new_raw(unit); + if (!j) + return NULL; + + j->id = j->manager->current_job_id++; + j->type = type; + + /* We don't link it here, that's what job_dependency() is for */ + + return j; +} + +void job_free(Job *j) { + assert(j); + assert(!j->installed); + assert(!j->transaction_prev); + assert(!j->transaction_next); + assert(!j->subject_list); + assert(!j->object_list); + + if (j->in_run_queue) + LIST_REMOVE(run_queue, j->manager->run_queue, j); + + if (j->in_dbus_queue) + LIST_REMOVE(dbus_queue, j->manager->dbus_job_queue, j); + + sd_event_source_unref(j->timer_event_source); + + sd_bus_track_unref(j->clients); + strv_free(j->deserialized_clients); + + free(j); +} + +static void job_set_state(Job *j, JobState state) { + assert(j); + assert(state >= 0); + assert(state < _JOB_STATE_MAX); + + if (j->state == state) + return; + + j->state = state; + + if (!j->installed) + return; + + if (j->state == JOB_RUNNING) + j->unit->manager->n_running_jobs++; + else { + assert(j->state == JOB_WAITING); + assert(j->unit->manager->n_running_jobs > 0); + + j->unit->manager->n_running_jobs--; + + if (j->unit->manager->n_running_jobs <= 0) + j->unit->manager->jobs_in_progress_event_source = sd_event_source_unref(j->unit->manager->jobs_in_progress_event_source); + } +} + +void job_uninstall(Job *j) { + Job **pj; + + assert(j->installed); + + job_set_state(j, JOB_WAITING); + + pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; + assert(*pj == j); + + /* Detach from next 'bigger' objects */ + + /* daemon-reload should be transparent to job observers */ + if (j->manager->n_reloading <= 0) + bus_job_send_removed_signal(j); + + *pj = NULL; + + unit_add_to_gc_queue(j->unit); + + hashmap_remove(j->manager->jobs, UINT32_TO_PTR(j->id)); + j->installed = false; +} + +static bool job_type_allows_late_merge(JobType t) { + /* Tells whether it is OK to merge a job of type 't' with an already + * running job. + * Reloads cannot be merged this way. Think of the sequence: + * 1. Reload of a daemon is in progress; the daemon has already loaded + * its config file, but hasn't completed the reload operation yet. + * 2. Edit foo's config file. + * 3. Trigger another reload to have the daemon use the new config. + * Should the second reload job be merged into the first one, the daemon + * would not know about the new config. + * JOB_RESTART jobs on the other hand can be merged, because they get + * patched into JOB_START after stopping the unit. So if we see a + * JOB_RESTART running, it means the unit hasn't stopped yet and at + * this time the merge is still allowed. */ + return t != JOB_RELOAD; +} + +static void job_merge_into_installed(Job *j, Job *other) { + assert(j->installed); + assert(j->unit == other->unit); + + if (j->type != JOB_NOP) + job_type_merge_and_collapse(&j->type, other->type, j->unit); + else + assert(other->type == JOB_NOP); + + j->irreversible = j->irreversible || other->irreversible; + j->ignore_order = j->ignore_order || other->ignore_order; +} + +Job* job_install(Job *j) { + Job **pj; + Job *uj; + + assert(!j->installed); + assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); + assert(j->state == JOB_WAITING); + + pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; + uj = *pj; + + if (uj) { + if (job_type_is_conflicting(uj->type, j->type)) + job_finish_and_invalidate(uj, JOB_CANCELED, false); + else { + /* not conflicting, i.e. mergeable */ + + if (uj->state == JOB_WAITING || + (job_type_allows_late_merge(j->type) && job_type_is_superset(uj->type, j->type))) { + job_merge_into_installed(uj, j); + log_unit_debug(uj->unit, + "Merged into installed job %s/%s as %u", + uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id); + return uj; + } else { + /* already running and not safe to merge into */ + /* Patch uj to become a merged job and re-run it. */ + /* XXX It should be safer to queue j to run after uj finishes, but it is + * not currently possible to have more than one installed job per unit. */ + job_merge_into_installed(uj, j); + log_unit_debug(uj->unit, + "Merged into running job, re-running: %s/%s as %u", + uj->unit->id, job_type_to_string(uj->type), (unsigned) uj->id); + + job_set_state(uj, JOB_WAITING); + return uj; + } + } + } + + /* Install the job */ + *pj = j; + j->installed = true; + + j->manager->n_installed_jobs ++; + log_unit_debug(j->unit, + "Installed new job %s/%s as %u", + j->unit->id, job_type_to_string(j->type), (unsigned) j->id); + return j; +} + +int job_install_deserialized(Job *j) { + Job **pj; + + assert(!j->installed); + + if (j->type < 0 || j->type >= _JOB_TYPE_MAX_IN_TRANSACTION) { + log_debug("Invalid job type %s in deserialization.", strna(job_type_to_string(j->type))); + return -EINVAL; + } + + pj = (j->type == JOB_NOP) ? &j->unit->nop_job : &j->unit->job; + if (*pj) { + log_unit_debug(j->unit, "Unit already has a job installed. Not installing deserialized job."); + return -EEXIST; + } + + *pj = j; + j->installed = true; + + if (j->state == JOB_RUNNING) + j->unit->manager->n_running_jobs++; + + log_unit_debug(j->unit, + "Reinstalled deserialized job %s/%s as %u", + j->unit->id, job_type_to_string(j->type), (unsigned) j->id); + return 0; +} + +JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts) { + JobDependency *l; + + assert(object); + + /* Adds a new job link, which encodes that the 'subject' job + * needs the 'object' job in some way. If 'subject' is NULL + * this means the 'anchor' job (i.e. the one the user + * explicitly asked for) is the requester. */ + + if (!(l = new0(JobDependency, 1))) + return NULL; + + l->subject = subject; + l->object = object; + l->matters = matters; + l->conflicts = conflicts; + + if (subject) + LIST_PREPEND(subject, subject->subject_list, l); + + LIST_PREPEND(object, object->object_list, l); + + return l; +} + +void job_dependency_free(JobDependency *l) { + assert(l); + + if (l->subject) + LIST_REMOVE(subject, l->subject->subject_list, l); + + LIST_REMOVE(object, l->object->object_list, l); + + free(l); +} + +void job_dump(Job *j, FILE*f, const char *prefix) { + assert(j); + assert(f); + + if (!prefix) + prefix = ""; + + fprintf(f, + "%s-> Job %u:\n" + "%s\tAction: %s -> %s\n" + "%s\tState: %s\n" + "%s\tIrreversible: %s\n", + prefix, j->id, + prefix, j->unit->id, job_type_to_string(j->type), + prefix, job_state_to_string(j->state), + prefix, yes_no(j->irreversible)); +} + +/* + * Merging is commutative, so imagine the matrix as symmetric. We store only + * its lower triangle to avoid duplication. We don't store the main diagonal, + * because A merged with A is simply A. + * + * If the resulting type is collapsed immediately afterwards (to get rid of + * the JOB_RELOAD_OR_START, which lies outside the lookup function's domain), + * the following properties hold: + * + * Merging is associative! A merged with B, and then merged with C is the same + * as A merged with the result of B merged with C. + * + * Mergeability is transitive! If A can be merged with B and B with C then + * A also with C. + * + * Also, if A merged with B cannot be merged with C, then either A or B cannot + * be merged with C either. + */ +static const JobType job_merging_table[] = { +/* What \ With * JOB_START JOB_VERIFY_ACTIVE JOB_STOP JOB_RELOAD */ +/*********************************************************************************/ +/*JOB_START */ +/*JOB_VERIFY_ACTIVE */ JOB_START, +/*JOB_STOP */ -1, -1, +/*JOB_RELOAD */ JOB_RELOAD_OR_START, JOB_RELOAD, -1, +/*JOB_RESTART */ JOB_RESTART, JOB_RESTART, -1, JOB_RESTART, +}; + +JobType job_type_lookup_merge(JobType a, JobType b) { + assert_cc(ELEMENTSOF(job_merging_table) == _JOB_TYPE_MAX_MERGING * (_JOB_TYPE_MAX_MERGING - 1) / 2); + assert(a >= 0 && a < _JOB_TYPE_MAX_MERGING); + assert(b >= 0 && b < _JOB_TYPE_MAX_MERGING); + + if (a == b) + return a; + + if (a < b) { + JobType tmp = a; + a = b; + b = tmp; + } + + return job_merging_table[(a - 1) * a / 2 + b]; +} + +bool job_type_is_redundant(JobType a, UnitActiveState b) { + switch (a) { + + case JOB_START: + return + b == UNIT_ACTIVE || + b == UNIT_RELOADING; + + case JOB_STOP: + return + b == UNIT_INACTIVE || + b == UNIT_FAILED; + + case JOB_VERIFY_ACTIVE: + return + b == UNIT_ACTIVE || + b == UNIT_RELOADING; + + case JOB_RELOAD: + return + b == UNIT_RELOADING; + + case JOB_RESTART: + return + b == UNIT_ACTIVATING; + + case JOB_NOP: + return true; + + default: + assert_not_reached("Invalid job type"); + } +} + +JobType job_type_collapse(JobType t, Unit *u) { + UnitActiveState s; + + switch (t) { + + case JOB_TRY_RESTART: + s = unit_active_state(u); + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) + return JOB_NOP; + + return JOB_RESTART; + + case JOB_TRY_RELOAD: + s = unit_active_state(u); + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) + return JOB_NOP; + + return JOB_RELOAD; + + case JOB_RELOAD_OR_START: + s = unit_active_state(u); + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(s)) + return JOB_START; + + return JOB_RELOAD; + + default: + return t; + } +} + +int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u) { + JobType t; + + t = job_type_lookup_merge(*a, b); + if (t < 0) + return -EEXIST; + + *a = job_type_collapse(t, u); + return 0; +} + +static bool job_is_runnable(Job *j) { + Iterator i; + Unit *other; + + assert(j); + assert(j->installed); + + /* Checks whether there is any job running for the units this + * job needs to be running after (in the case of a 'positive' + * job type) or before (in the case of a 'negative' job + * type. */ + + /* Note that unit types have a say in what is runnable, + * too. For example, if they return -EAGAIN from + * unit_start() they can indicate they are not + * runnable yet. */ + + /* First check if there is an override */ + if (j->ignore_order) + return true; + + if (j->type == JOB_NOP) + return true; + + if (j->type == JOB_START || + j->type == JOB_VERIFY_ACTIVE || + j->type == JOB_RELOAD) { + + /* Immediate result is that the job is or might be + * started. In this case let's wait for the + * dependencies, regardless whether they are + * starting or stopping something. */ + + SET_FOREACH(other, j->unit->dependencies[UNIT_AFTER], i) + if (other->job) + return false; + } + + /* Also, if something else is being stopped and we should + * change state after it, then let's wait. */ + + SET_FOREACH(other, j->unit->dependencies[UNIT_BEFORE], i) + if (other->job && + (other->job->type == JOB_STOP || + other->job->type == JOB_RESTART)) + return false; + + /* This means that for a service a and a service b where b + * shall be started after a: + * + * start a + start b → 1st step start a, 2nd step start b + * start a + stop b → 1st step stop b, 2nd step start a + * stop a + start b → 1st step stop a, 2nd step start b + * stop a + stop b → 1st step stop b, 2nd step stop a + * + * This has the side effect that restarts are properly + * synchronized too. */ + + return true; +} + +static void job_change_type(Job *j, JobType newtype) { + assert(j); + + log_unit_debug(j->unit, + "Converting job %s/%s -> %s/%s", + j->unit->id, job_type_to_string(j->type), + j->unit->id, job_type_to_string(newtype)); + + j->type = newtype; +} + +static int job_perform_on_unit(Job **j) { + uint32_t id; + Manager *m; + JobType t; + Unit *u; + int r; + + /* While we execute this operation the job might go away (for + * example: because it finishes immediately or is replaced by + * a new, conflicting job.) To make sure we don't access a + * freed job later on we store the id here, so that we can + * verify the job is still valid. */ + + assert(j); + assert(*j); + + m = (*j)->manager; + u = (*j)->unit; + t = (*j)->type; + id = (*j)->id; + + switch (t) { + case JOB_START: + r = unit_start(u); + break; + + case JOB_RESTART: + t = JOB_STOP; + /* fall through */ + case JOB_STOP: + r = unit_stop(u); + break; + + case JOB_RELOAD: + r = unit_reload(u); + break; + + default: + assert_not_reached("Invalid job type"); + } + + /* Log if the job still exists and the start/stop/reload function + * actually did something. */ + *j = manager_get_job(m, id); + if (*j && r > 0) + unit_status_emit_starting_stopping_reloading(u, t); + + return r; +} + +int job_run_and_invalidate(Job *j) { + int r; + + assert(j); + assert(j->installed); + assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); + assert(j->in_run_queue); + + LIST_REMOVE(run_queue, j->manager->run_queue, j); + j->in_run_queue = false; + + if (j->state != JOB_WAITING) + return 0; + + if (!job_is_runnable(j)) + return -EAGAIN; + + job_set_state(j, JOB_RUNNING); + job_add_to_dbus_queue(j); + + + switch (j->type) { + + case JOB_VERIFY_ACTIVE: { + UnitActiveState t = unit_active_state(j->unit); + if (UNIT_IS_ACTIVE_OR_RELOADING(t)) + r = -EALREADY; + else if (t == UNIT_ACTIVATING) + r = -EAGAIN; + else + r = -EBADR; + break; + } + + case JOB_START: + case JOB_STOP: + case JOB_RESTART: + r = job_perform_on_unit(&j); + + /* If the unit type does not support starting/stopping, + * then simply wait. */ + if (r == -EBADR) + r = 0; + break; + + case JOB_RELOAD: + r = job_perform_on_unit(&j); + break; + + case JOB_NOP: + r = -EALREADY; + break; + + default: + assert_not_reached("Unknown job type"); + } + + if (j) { + if (r == -EALREADY) + r = job_finish_and_invalidate(j, JOB_DONE, true); + else if (r == -EBADR) + r = job_finish_and_invalidate(j, JOB_SKIPPED, true); + else if (r == -ENOEXEC) + r = job_finish_and_invalidate(j, JOB_INVALID, true); + else if (r == -EPROTO) + r = job_finish_and_invalidate(j, JOB_ASSERT, true); + else if (r == -EOPNOTSUPP) + r = job_finish_and_invalidate(j, JOB_UNSUPPORTED, true); + else if (r == -EAGAIN) + job_set_state(j, JOB_WAITING); + else if (r < 0) + r = job_finish_and_invalidate(j, JOB_FAILED, true); + } + + return r; +} + +_pure_ static const char *job_get_status_message_format(Unit *u, JobType t, JobResult result) { + + static const char *const generic_finished_start_job[_JOB_RESULT_MAX] = { + [JOB_DONE] = "Started %s.", + [JOB_TIMEOUT] = "Timed out starting %s.", + [JOB_FAILED] = "Failed to start %s.", + [JOB_DEPENDENCY] = "Dependency failed for %s.", + [JOB_ASSERT] = "Assertion failed for %s.", + [JOB_UNSUPPORTED] = "Starting of %s not supported.", + }; + static const char *const generic_finished_stop_job[_JOB_RESULT_MAX] = { + [JOB_DONE] = "Stopped %s.", + [JOB_FAILED] = "Stopped (with error) %s.", + [JOB_TIMEOUT] = "Timed out stoppping %s.", + }; + static const char *const generic_finished_reload_job[_JOB_RESULT_MAX] = { + [JOB_DONE] = "Reloaded %s.", + [JOB_FAILED] = "Reload failed for %s.", + [JOB_TIMEOUT] = "Timed out reloading %s.", + }; + /* When verify-active detects the unit is inactive, report it. + * Most likely a DEPEND warning from a requisiting unit will + * occur next and it's nice to see what was requisited. */ + static const char *const generic_finished_verify_active_job[_JOB_RESULT_MAX] = { + [JOB_SKIPPED] = "%s is not active.", + }; + + const UnitStatusMessageFormats *format_table; + const char *format; + + assert(u); + assert(t >= 0); + assert(t < _JOB_TYPE_MAX); + + if (IN_SET(t, JOB_START, JOB_STOP, JOB_RESTART)) { + format_table = &UNIT_VTABLE(u)->status_message_formats; + if (format_table) { + format = t == JOB_START ? format_table->finished_start_job[result] : + format_table->finished_stop_job[result]; + if (format) + return format; + } + } + + /* Return generic strings */ + if (t == JOB_START) + return generic_finished_start_job[result]; + else if (t == JOB_STOP || t == JOB_RESTART) + return generic_finished_stop_job[result]; + else if (t == JOB_RELOAD) + return generic_finished_reload_job[result]; + else if (t == JOB_VERIFY_ACTIVE) + return generic_finished_verify_active_job[result]; + + return NULL; +} + +static void job_print_status_message(Unit *u, JobType t, JobResult result) { + static const char* const job_result_status_table[_JOB_RESULT_MAX] = { + [JOB_DONE] = ANSI_GREEN " OK " ANSI_NORMAL, + [JOB_TIMEOUT] = ANSI_HIGHLIGHT_RED " TIME " ANSI_NORMAL, + [JOB_FAILED] = ANSI_HIGHLIGHT_RED "FAILED" ANSI_NORMAL, + [JOB_DEPENDENCY] = ANSI_HIGHLIGHT_YELLOW "DEPEND" ANSI_NORMAL, + [JOB_SKIPPED] = ANSI_HIGHLIGHT " INFO " ANSI_NORMAL, + [JOB_ASSERT] = ANSI_HIGHLIGHT_YELLOW "ASSERT" ANSI_NORMAL, + [JOB_UNSUPPORTED] = ANSI_HIGHLIGHT_YELLOW "UNSUPP" ANSI_NORMAL, + }; + + const char *format; + + assert(u); + assert(t >= 0); + assert(t < _JOB_TYPE_MAX); + + /* Reload status messages have traditionally not been printed to console. */ + if (t == JOB_RELOAD) + return; + + format = job_get_status_message_format(u, t, result); + if (!format) + return; + + if (result != JOB_DONE) + manager_flip_auto_status(u->manager, true); + + DISABLE_WARNING_FORMAT_NONLITERAL; + unit_status_printf(u, job_result_status_table[result], format); + REENABLE_WARNING; + + if (t == JOB_START && result == JOB_FAILED) { + _cleanup_free_ char *quoted; + + quoted = shell_maybe_quote(u->id); + manager_status_printf(u->manager, STATUS_TYPE_NORMAL, NULL, "See 'systemctl status %s' for details.", strna(quoted)); + } +} + +static void job_log_status_message(Unit *u, JobType t, JobResult result) { + const char *format; + char buf[LINE_MAX]; + sd_id128_t mid; + static const int job_result_log_level[_JOB_RESULT_MAX] = { + [JOB_DONE] = LOG_INFO, + [JOB_CANCELED] = LOG_INFO, + [JOB_TIMEOUT] = LOG_ERR, + [JOB_FAILED] = LOG_ERR, + [JOB_DEPENDENCY] = LOG_WARNING, + [JOB_SKIPPED] = LOG_NOTICE, + [JOB_INVALID] = LOG_INFO, + [JOB_ASSERT] = LOG_WARNING, + [JOB_UNSUPPORTED] = LOG_WARNING, + }; + + assert(u); + assert(t >= 0); + assert(t < _JOB_TYPE_MAX); + + /* Skip this if it goes to the console. since we already print + * to the console anyway... */ + + if (log_on_console()) + return; + + format = job_get_status_message_format(u, t, result); + if (!format) + return; + + DISABLE_WARNING_FORMAT_NONLITERAL; + xsprintf(buf, format, unit_description(u)); + REENABLE_WARNING; + + switch (t) { + + case JOB_START: + mid = result == JOB_DONE ? SD_MESSAGE_UNIT_STARTED : SD_MESSAGE_UNIT_FAILED; + break; + + case JOB_RELOAD: + mid = SD_MESSAGE_UNIT_RELOADED; + break; + + case JOB_STOP: + case JOB_RESTART: + mid = SD_MESSAGE_UNIT_STOPPED; + break; + + default: + log_struct(job_result_log_level[result], + LOG_UNIT_ID(u), + LOG_MESSAGE("%s", buf), + "RESULT=%s", job_result_to_string(result), + NULL); + return; + } + + log_struct(job_result_log_level[result], + LOG_MESSAGE_ID(mid), + LOG_UNIT_ID(u), + LOG_MESSAGE("%s", buf), + "RESULT=%s", job_result_to_string(result), + NULL); +} + +static void job_emit_status_message(Unit *u, JobType t, JobResult result) { + + /* No message if the job did not actually do anything due to failed condition. */ + if (t == JOB_START && result == JOB_DONE && !u->condition_result) + return; + + job_log_status_message(u, t, result); + job_print_status_message(u, t, result); +} + +static void job_fail_dependencies(Unit *u, UnitDependency d) { + Unit *other; + Iterator i; + + assert(u); + + SET_FOREACH(other, u->dependencies[d], i) { + Job *j = other->job; + + if (!j) + continue; + if (!IN_SET(j->type, JOB_START, JOB_VERIFY_ACTIVE)) + continue; + + job_finish_and_invalidate(j, JOB_DEPENDENCY, true); + } +} + +int job_finish_and_invalidate(Job *j, JobResult result, bool recursive) { + Unit *u; + Unit *other; + JobType t; + Iterator i; + + assert(j); + assert(j->installed); + assert(j->type < _JOB_TYPE_MAX_IN_TRANSACTION); + + u = j->unit; + t = j->type; + + j->result = result; + + log_unit_debug(u, "Job %s/%s finished, result=%s", u->id, job_type_to_string(t), job_result_to_string(result)); + + job_emit_status_message(u, t, result); + + job_add_to_dbus_queue(j); + + /* Patch restart jobs so that they become normal start jobs */ + if (result == JOB_DONE && t == JOB_RESTART) { + + job_change_type(j, JOB_START); + job_set_state(j, JOB_WAITING); + + job_add_to_run_queue(j); + + goto finish; + } + + if (result == JOB_FAILED || result == JOB_INVALID) + j->manager->n_failed_jobs ++; + + job_uninstall(j); + job_free(j); + + /* Fail depending jobs on failure */ + if (result != JOB_DONE && recursive) { + if (IN_SET(t, JOB_START, JOB_VERIFY_ACTIVE)) { + job_fail_dependencies(u, UNIT_REQUIRED_BY); + job_fail_dependencies(u, UNIT_REQUISITE_OF); + job_fail_dependencies(u, UNIT_BOUND_BY); + } else if (t == JOB_STOP) + job_fail_dependencies(u, UNIT_CONFLICTED_BY); + } + + /* Trigger OnFailure dependencies that are not generated by + * the unit itself. We don't treat JOB_CANCELED as failure in + * this context. And JOB_FAILURE is already handled by the + * unit itself. */ + if (result == JOB_TIMEOUT || result == JOB_DEPENDENCY) { + log_struct(LOG_NOTICE, + "JOB_TYPE=%s", job_type_to_string(t), + "JOB_RESULT=%s", job_result_to_string(result), + LOG_UNIT_ID(u), + LOG_UNIT_MESSAGE(u, "Job %s/%s failed with result '%s'.", + u->id, + job_type_to_string(t), + job_result_to_string(result)), + NULL); + + unit_start_on_failure(u); + } + + unit_trigger_notify(u); + +finish: + /* Try to start the next jobs that can be started */ + SET_FOREACH(other, u->dependencies[UNIT_AFTER], i) + if (other->job) + job_add_to_run_queue(other->job); + SET_FOREACH(other, u->dependencies[UNIT_BEFORE], i) + if (other->job) + job_add_to_run_queue(other->job); + + manager_check_finished(u->manager); + + return 0; +} + +static int job_dispatch_timer(sd_event_source *s, uint64_t monotonic, void *userdata) { + Job *j = userdata; + Unit *u; + + assert(j); + assert(s == j->timer_event_source); + + log_unit_warning(j->unit, "Job %s/%s timed out.", j->unit->id, job_type_to_string(j->type)); + + u = j->unit; + job_finish_and_invalidate(j, JOB_TIMEOUT, true); + + failure_action(u->manager, u->job_timeout_action, u->job_timeout_reboot_arg); + + return 0; +} + +int job_start_timer(Job *j) { + int r; + + if (j->timer_event_source) + return 0; + + j->begin_usec = now(CLOCK_MONOTONIC); + + if (j->unit->job_timeout == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + j->manager->event, + &j->timer_event_source, + CLOCK_MONOTONIC, + usec_add(j->begin_usec, j->unit->job_timeout), 0, + job_dispatch_timer, j); + if (r < 0) + return r; + + (void) sd_event_source_set_description(j->timer_event_source, "job-start"); + + return 0; +} + +void job_add_to_run_queue(Job *j) { + assert(j); + assert(j->installed); + + if (j->in_run_queue) + return; + + if (!j->manager->run_queue) + sd_event_source_set_enabled(j->manager->run_queue_event_source, SD_EVENT_ONESHOT); + + LIST_PREPEND(run_queue, j->manager->run_queue, j); + j->in_run_queue = true; +} + +void job_add_to_dbus_queue(Job *j) { + assert(j); + assert(j->installed); + + if (j->in_dbus_queue) + return; + + /* We don't check if anybody is subscribed here, since this + * job might just have been created and not yet assigned to a + * connection/client. */ + + LIST_PREPEND(dbus_queue, j->manager->dbus_job_queue, j); + j->in_dbus_queue = true; +} + +char *job_dbus_path(Job *j) { + char *p; + + assert(j); + + if (asprintf(&p, "/org/freedesktop/systemd1/job/%"PRIu32, j->id) < 0) + return NULL; + + return p; +} + +int job_serialize(Job *j, FILE *f, FDSet *fds) { + fprintf(f, "job-id=%u\n", j->id); + fprintf(f, "job-type=%s\n", job_type_to_string(j->type)); + fprintf(f, "job-state=%s\n", job_state_to_string(j->state)); + fprintf(f, "job-irreversible=%s\n", yes_no(j->irreversible)); + fprintf(f, "job-sent-dbus-new-signal=%s\n", yes_no(j->sent_dbus_new_signal)); + fprintf(f, "job-ignore-order=%s\n", yes_no(j->ignore_order)); + + if (j->begin_usec > 0) + fprintf(f, "job-begin="USEC_FMT"\n", j->begin_usec); + + bus_track_serialize(j->clients, f); + + /* End marker */ + fputc('\n', f); + return 0; +} + +int job_deserialize(Job *j, FILE *f, FDSet *fds) { + assert(j); + + for (;;) { + char line[LINE_MAX], *l, *v; + size_t k; + + if (!fgets(line, sizeof(line), f)) { + if (feof(f)) + return 0; + return -errno; + } + + char_array_0(line); + l = strstrip(line); + + /* End marker */ + if (l[0] == 0) + return 0; + + k = strcspn(l, "="); + + if (l[k] == '=') { + l[k] = 0; + v = l+k+1; + } else + v = l+k; + + if (streq(l, "job-id")) { + + if (safe_atou32(v, &j->id) < 0) + log_debug("Failed to parse job id value %s", v); + + } else if (streq(l, "job-type")) { + JobType t; + + t = job_type_from_string(v); + if (t < 0) + log_debug("Failed to parse job type %s", v); + else if (t >= _JOB_TYPE_MAX_IN_TRANSACTION) + log_debug("Cannot deserialize job of type %s", v); + else + j->type = t; + + } else if (streq(l, "job-state")) { + JobState s; + + s = job_state_from_string(v); + if (s < 0) + log_debug("Failed to parse job state %s", v); + else + job_set_state(j, s); + + } else if (streq(l, "job-irreversible")) { + int b; + + b = parse_boolean(v); + if (b < 0) + log_debug("Failed to parse job irreversible flag %s", v); + else + j->irreversible = j->irreversible || b; + + } else if (streq(l, "job-sent-dbus-new-signal")) { + int b; + + b = parse_boolean(v); + if (b < 0) + log_debug("Failed to parse job sent_dbus_new_signal flag %s", v); + else + j->sent_dbus_new_signal = j->sent_dbus_new_signal || b; + + } else if (streq(l, "job-ignore-order")) { + int b; + + b = parse_boolean(v); + if (b < 0) + log_debug("Failed to parse job ignore_order flag %s", v); + else + j->ignore_order = j->ignore_order || b; + + } else if (streq(l, "job-begin")) { + unsigned long long ull; + + if (sscanf(v, "%llu", &ull) != 1) + log_debug("Failed to parse job-begin value %s", v); + else + j->begin_usec = ull; + + } else if (streq(l, "subscribed")) { + + if (strv_extend(&j->deserialized_clients, v) < 0) + return log_oom(); + } + } +} + +int job_coldplug(Job *j) { + int r; + + assert(j); + + /* After deserialization is complete and the bus connection + * set up again, let's start watching our subscribers again */ + r = bus_track_coldplug(j->manager, &j->clients, &j->deserialized_clients); + if (r < 0) + return r; + + if (j->state == JOB_WAITING) + job_add_to_run_queue(j); + + if (j->begin_usec == 0 || j->unit->job_timeout == USEC_INFINITY) + return 0; + + j->timer_event_source = sd_event_source_unref(j->timer_event_source); + + r = sd_event_add_time( + j->manager->event, + &j->timer_event_source, + CLOCK_MONOTONIC, + usec_add(j->begin_usec, j->unit->job_timeout), 0, + job_dispatch_timer, j); + if (r < 0) + log_debug_errno(r, "Failed to restart timeout for job: %m"); + + (void) sd_event_source_set_description(j->timer_event_source, "job-timeout"); + + return r; +} + +void job_shutdown_magic(Job *j) { + assert(j); + + /* The shutdown target gets some special treatment here: we + * tell the kernel to begin with flushing its disk caches, to + * optimize shutdown time a bit. Ideally we wouldn't hardcode + * this magic into PID 1. However all other processes aren't + * options either since they'd exit much sooner than PID 1 and + * asynchronous sync() would cause their exit to be + * delayed. */ + + if (j->type != JOB_START) + return; + + if (j->unit->manager->running_as != MANAGER_SYSTEM) + return; + + if (!unit_has_name(j->unit, SPECIAL_SHUTDOWN_TARGET)) + return; + + /* In case messages on console has been disabled on boot */ + j->unit->manager->no_console_output = false; + + if (detect_container() > 0) + return; + + asynchronous_sync(); +} + +int job_get_timeout(Job *j, usec_t *timeout) { + usec_t x = USEC_INFINITY, y = USEC_INFINITY; + Unit *u = j->unit; + int r; + + assert(u); + + if (j->timer_event_source) { + r = sd_event_source_get_time(j->timer_event_source, &x); + if (r < 0) + return r; + } + + if (UNIT_VTABLE(u)->get_timeout) { + r = UNIT_VTABLE(u)->get_timeout(u, &y); + if (r < 0) + return r; + } + + if (x == USEC_INFINITY && y == USEC_INFINITY) + return 0; + + *timeout = MIN(x, y); + return 1; +} + +static const char* const job_state_table[_JOB_STATE_MAX] = { + [JOB_WAITING] = "waiting", + [JOB_RUNNING] = "running" +}; + +DEFINE_STRING_TABLE_LOOKUP(job_state, JobState); + +static const char* const job_type_table[_JOB_TYPE_MAX] = { + [JOB_START] = "start", + [JOB_VERIFY_ACTIVE] = "verify-active", + [JOB_STOP] = "stop", + [JOB_RELOAD] = "reload", + [JOB_RELOAD_OR_START] = "reload-or-start", + [JOB_RESTART] = "restart", + [JOB_TRY_RESTART] = "try-restart", + [JOB_TRY_RELOAD] = "try-reload", + [JOB_NOP] = "nop", +}; + +DEFINE_STRING_TABLE_LOOKUP(job_type, JobType); + +static const char* const job_mode_table[_JOB_MODE_MAX] = { + [JOB_FAIL] = "fail", + [JOB_REPLACE] = "replace", + [JOB_REPLACE_IRREVERSIBLY] = "replace-irreversibly", + [JOB_ISOLATE] = "isolate", + [JOB_FLUSH] = "flush", + [JOB_IGNORE_DEPENDENCIES] = "ignore-dependencies", + [JOB_IGNORE_REQUIREMENTS] = "ignore-requirements", +}; + +DEFINE_STRING_TABLE_LOOKUP(job_mode, JobMode); + +static const char* const job_result_table[_JOB_RESULT_MAX] = { + [JOB_DONE] = "done", + [JOB_CANCELED] = "canceled", + [JOB_TIMEOUT] = "timeout", + [JOB_FAILED] = "failed", + [JOB_DEPENDENCY] = "dependency", + [JOB_SKIPPED] = "skipped", + [JOB_INVALID] = "invalid", + [JOB_ASSERT] = "assert", + [JOB_UNSUPPORTED] = "unsupported", +}; + +DEFINE_STRING_TABLE_LOOKUP(job_result, JobResult); + +const char* job_type_to_access_method(JobType t) { + assert(t >= 0); + assert(t < _JOB_TYPE_MAX); + + if (IN_SET(t, JOB_START, JOB_RESTART, JOB_TRY_RESTART)) + return "start"; + else if (t == JOB_STOP) + return "stop"; + else + return "reload"; +} diff --git a/src/libcore/job.h b/src/libcore/job.h new file mode 100644 index 0000000000..de130303d2 --- /dev/null +++ b/src/libcore/job.h @@ -0,0 +1,242 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include + +#include "list.h" +#include "unit-name.h" + +typedef struct Job Job; +typedef struct JobDependency JobDependency; +typedef enum JobType JobType; +typedef enum JobState JobState; +typedef enum JobMode JobMode; +typedef enum JobResult JobResult; + +/* Be careful when changing the job types! Adjust job_merging_table[] accordingly! */ +enum JobType { + JOB_START, /* if a unit does not support being started, we'll just wait until it becomes active */ + JOB_VERIFY_ACTIVE, + + JOB_STOP, + + JOB_RELOAD, /* if running, reload */ + + /* Note that restarts are first treated like JOB_STOP, but + * then instead of finishing are patched to become + * JOB_START. */ + JOB_RESTART, /* If running, stop. Then start unconditionally. */ + + _JOB_TYPE_MAX_MERGING, + + /* JOB_NOP can enter into a transaction, but as it won't pull in + * any dependencies and it uses the special 'nop_job' slot in Unit, + * it won't have to merge with anything (except possibly into another + * JOB_NOP, previously installed). JOB_NOP is special-cased in + * job_type_is_*() functions so that the transaction can be + * activated. */ + JOB_NOP = _JOB_TYPE_MAX_MERGING, /* do nothing */ + + _JOB_TYPE_MAX_IN_TRANSACTION, + + /* JOB_TRY_RESTART can never appear in a transaction, because + * it always collapses into JOB_RESTART or JOB_NOP before entering. + * Thus we never need to merge it with anything. */ + JOB_TRY_RESTART = _JOB_TYPE_MAX_IN_TRANSACTION, /* if running, stop and then start */ + + /* Similar to JOB_TRY_RESTART but collapses to JOB_RELOAD or JOB_NOP */ + JOB_TRY_RELOAD, + + /* JOB_RELOAD_OR_START won't enter into a transaction and cannot result + * from transaction merging (there's no way for JOB_RELOAD and + * JOB_START to meet in one transaction). It can result from a merge + * during job installation, but then it will immediately collapse into + * one of the two simpler types. */ + JOB_RELOAD_OR_START, /* if running, reload, otherwise start */ + + _JOB_TYPE_MAX, + _JOB_TYPE_INVALID = -1 +}; + +enum JobState { + JOB_WAITING, + JOB_RUNNING, + _JOB_STATE_MAX, + _JOB_STATE_INVALID = -1 +}; + +enum JobMode { + JOB_FAIL, /* Fail if a conflicting job is already queued */ + JOB_REPLACE, /* Replace an existing conflicting job */ + JOB_REPLACE_IRREVERSIBLY,/* Like JOB_REPLACE + produce irreversible jobs */ + JOB_ISOLATE, /* Start a unit, and stop all others */ + JOB_FLUSH, /* Flush out all other queued jobs when queing this one */ + JOB_IGNORE_DEPENDENCIES, /* Ignore both requirement and ordering dependencies */ + JOB_IGNORE_REQUIREMENTS, /* Ignore requirement dependencies */ + _JOB_MODE_MAX, + _JOB_MODE_INVALID = -1 +}; + +enum JobResult { + JOB_DONE, /* Job completed successfully */ + JOB_CANCELED, /* Job canceled by a conflicting job installation or by explicit cancel request */ + JOB_TIMEOUT, /* Job timeout elapsed */ + JOB_FAILED, /* Job failed */ + JOB_DEPENDENCY, /* A required dependency job did not result in JOB_DONE */ + JOB_SKIPPED, /* Negative result of JOB_VERIFY_ACTIVE */ + JOB_INVALID, /* JOB_RELOAD of inactive unit */ + JOB_ASSERT, /* Couldn't start a unit, because an assert didn't hold */ + JOB_UNSUPPORTED, /* Couldn't start a unit, because the unit type is not supported on the system */ + _JOB_RESULT_MAX, + _JOB_RESULT_INVALID = -1 +}; + +#include "unit.h" + +struct JobDependency { + /* Encodes that the 'subject' job needs the 'object' job in + * some way. This structure is used only while building a transaction. */ + Job *subject; + Job *object; + + LIST_FIELDS(JobDependency, subject); + LIST_FIELDS(JobDependency, object); + + bool matters; + bool conflicts; +}; + +struct Job { + Manager *manager; + Unit *unit; + + LIST_FIELDS(Job, transaction); + LIST_FIELDS(Job, run_queue); + LIST_FIELDS(Job, dbus_queue); + + LIST_HEAD(JobDependency, subject_list); + LIST_HEAD(JobDependency, object_list); + + /* Used for graph algs as a "I have been here" marker */ + Job* marker; + unsigned generation; + + uint32_t id; + + JobType type; + JobState state; + + sd_event_source *timer_event_source; + usec_t begin_usec; + + /* + * This tracks where to send signals, and also which clients + * are allowed to call DBus methods on the job (other than + * root). + * + * There can be more than one client, because of job merging. + */ + sd_bus_track *clients; + char **deserialized_clients; + + JobResult result; + + bool installed:1; + bool in_run_queue:1; + bool matters_to_anchor:1; + bool in_dbus_queue:1; + bool sent_dbus_new_signal:1; + bool ignore_order:1; + bool irreversible:1; +}; + +Job* job_new(Unit *unit, JobType type); +Job* job_new_raw(Unit *unit); +void job_free(Job *job); +Job* job_install(Job *j); +int job_install_deserialized(Job *j); +void job_uninstall(Job *j); +void job_dump(Job *j, FILE*f, const char *prefix); +int job_serialize(Job *j, FILE *f, FDSet *fds); +int job_deserialize(Job *j, FILE *f, FDSet *fds); +int job_coldplug(Job *j); + +JobDependency* job_dependency_new(Job *subject, Job *object, bool matters, bool conflicts); +void job_dependency_free(JobDependency *l); + +int job_merge(Job *j, Job *other); + +JobType job_type_lookup_merge(JobType a, JobType b) _pure_; + +_pure_ static inline bool job_type_is_mergeable(JobType a, JobType b) { + return job_type_lookup_merge(a, b) >= 0; +} + +_pure_ static inline bool job_type_is_conflicting(JobType a, JobType b) { + return a != JOB_NOP && b != JOB_NOP && !job_type_is_mergeable(a, b); +} + +_pure_ static inline bool job_type_is_superset(JobType a, JobType b) { + /* Checks whether operation a is a "superset" of b in its actions */ + if (b == JOB_NOP) + return true; + if (a == JOB_NOP) + return false; + return a == job_type_lookup_merge(a, b); +} + +bool job_type_is_redundant(JobType a, UnitActiveState b) _pure_; + +/* Collapses a state-dependent job type into a simpler type by observing + * the state of the unit which it is going to be applied to. */ +JobType job_type_collapse(JobType t, Unit *u); + +int job_type_merge_and_collapse(JobType *a, JobType b, Unit *u); + +void job_add_to_run_queue(Job *j); +void job_add_to_dbus_queue(Job *j); + +int job_start_timer(Job *j); + +int job_run_and_invalidate(Job *j); +int job_finish_and_invalidate(Job *j, JobResult result, bool recursive); + +char *job_dbus_path(Job *j); + +void job_shutdown_magic(Job *j); + +int job_get_timeout(Job *j, usec_t *timeout) _pure_; + +const char* job_type_to_string(JobType t) _const_; +JobType job_type_from_string(const char *s) _pure_; + +const char* job_state_to_string(JobState t) _const_; +JobState job_state_from_string(const char *s) _pure_; + +const char* job_mode_to_string(JobMode t) _const_; +JobMode job_mode_from_string(const char *s) _pure_; + +const char* job_result_to_string(JobResult t) _const_; +JobResult job_result_from_string(const char *s) _pure_; + +const char* job_type_to_access_method(JobType t); diff --git a/src/libcore/kill.c b/src/libcore/kill.c new file mode 100644 index 0000000000..6854587d54 --- /dev/null +++ b/src/libcore/kill.c @@ -0,0 +1,68 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include "kill.h" +#include "signal-util.h" +#include "string-table.h" +#include "util.h" + +void kill_context_init(KillContext *c) { + assert(c); + + c->kill_signal = SIGTERM; + c->send_sigkill = true; + c->send_sighup = false; +} + +void kill_context_dump(KillContext *c, FILE *f, const char *prefix) { + assert(c); + + if (!prefix) + prefix = ""; + + fprintf(f, + "%sKillMode: %s\n" + "%sKillSignal: SIG%s\n" + "%sSendSIGKILL: %s\n" + "%sSendSIGHUP: %s\n", + prefix, kill_mode_to_string(c->kill_mode), + prefix, signal_to_string(c->kill_signal), + prefix, yes_no(c->send_sigkill), + prefix, yes_no(c->send_sighup)); +} + +static const char* const kill_mode_table[_KILL_MODE_MAX] = { + [KILL_CONTROL_GROUP] = "control-group", + [KILL_PROCESS] = "process", + [KILL_MIXED] = "mixed", + [KILL_NONE] = "none" +}; + +DEFINE_STRING_TABLE_LOOKUP(kill_mode, KillMode); + +static const char* const kill_who_table[_KILL_WHO_MAX] = { + [KILL_MAIN] = "main", + [KILL_CONTROL] = "control", + [KILL_ALL] = "all", + [KILL_MAIN_FAIL] = "main-fail", + [KILL_CONTROL_FAIL] = "control-fail", + [KILL_ALL_FAIL] = "all-fail" +}; + +DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/libcore/kill.h b/src/libcore/kill.h new file mode 100644 index 0000000000..b3d2056cb0 --- /dev/null +++ b/src/libcore/kill.h @@ -0,0 +1,65 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +typedef struct KillContext KillContext; + +#include +#include + +#include "macro.h" + +typedef enum KillMode { + /* The kill mode is a property of a unit. */ + KILL_CONTROL_GROUP = 0, + KILL_PROCESS, + KILL_MIXED, + KILL_NONE, + _KILL_MODE_MAX, + _KILL_MODE_INVALID = -1 +} KillMode; + +struct KillContext { + KillMode kill_mode; + int kill_signal; + bool send_sigkill; + bool send_sighup; +}; + +typedef enum KillWho { + /* Kill who is a property of an operation */ + KILL_MAIN, + KILL_CONTROL, + KILL_ALL, + KILL_MAIN_FAIL, + KILL_CONTROL_FAIL, + KILL_ALL_FAIL, + _KILL_WHO_MAX, + _KILL_WHO_INVALID = -1 +} KillWho; + +void kill_context_init(KillContext *c); +void kill_context_dump(KillContext *c, FILE *f, const char *prefix); + +const char *kill_mode_to_string(KillMode k) _const_; +KillMode kill_mode_from_string(const char *s) _pure_; + +const char *kill_who_to_string(KillWho k) _const_; +KillWho kill_who_from_string(const char *s) _pure_; diff --git a/src/libcore/killall.c b/src/libcore/killall.c new file mode 100644 index 0000000000..09378f7085 --- /dev/null +++ b/src/libcore/killall.c @@ -0,0 +1,249 @@ +/*** + This file is part of systemd. + + Copyright 2010 ProFUSION embedded systems + + 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 . +***/ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "killall.h" +#include "parse-util.h" +#include "process-util.h" +#include "set.h" +#include "string-util.h" +#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; + const char *p; + size_t count; + uid_t uid; + int r; + + /* We are PID 1, let's not commit suicide */ + if (pid == 1) + return true; + + r = get_process_uid(pid, &uid); + if (r < 0) + return true; /* not really, but better safe than sorry */ + + /* Non-root processes otherwise are always subject to be killed */ + if (uid != 0) + return false; + + p = procfs_file_alloca(pid, "cmdline"); + f = fopen(p, "re"); + if (!f) + return true; /* not really, but has the desired effect */ + + count = fread(&c, 1, 1, f); + + /* Kernel threads have an empty cmdline */ + if (count <= 0) + return true; + + /* Processes with argv[0][0] = '@' we ignore from the killing + * spree. + * + * http://www.freedesktop.org/wiki/Software/systemd/RootStorageDaemons */ + if (c == '@' && warn_rootfs) { + _cleanup_free_ char *comm = NULL; + + r = pid_from_same_root_fs(pid); + if (r < 0) + return true; + + get_process_comm(pid, &comm); + + if (r) + log_notice("Process " PID_FMT " (%s) has been 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)); + return true; + } else if (c == '@') + return true; + + return false; +} + +static void wait_for_children(Set *pids, sigset_t *mask) { + usec_t until; + + assert(mask); + + if (set_isempty(pids)) + return; + + until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC; + for (;;) { + struct timespec ts; + int k; + usec_t n; + void *p; + Iterator i; + + /* First, let the kernel inform us about killed + * children. Most processes will probably be our + * children, but some are not (might be our + * grandchildren instead...). */ + for (;;) { + pid_t pid; + + pid = waitpid(-1, NULL, WNOHANG); + if (pid == 0) + break; + if (pid < 0) { + if (errno == ECHILD) + break; + + log_error_errno(errno, "waitpid() failed: %m"); + return; + } + + (void) set_remove(pids, PID_TO_PTR(pid)); + } + + /* Now explicitly check who might be remaining, who + * might not be our child. */ + SET_FOREACH(p, pids, i) { + + /* We misuse getpgid as a check whether a + * process still exists. */ + if (getpgid(PTR_TO_PID(p)) >= 0) + continue; + + if (errno != ESRCH) + continue; + + set_remove(pids, p); + } + + if (set_isempty(pids)) + return; + + n = now(CLOCK_MONOTONIC); + if (n >= until) + return; + + timespec_store(&ts, until - n); + k = sigtimedwait(mask, NULL, &ts); + if (k != SIGCHLD) { + + if (k < 0 && errno != EAGAIN) { + log_error_errno(errno, "sigtimedwait() failed: %m"); + return; + } + + if (k >= 0) + log_warning("sigtimedwait() returned unexpected signal."); + } + } +} + +static int killall(int sig, Set *pids, bool send_sighup) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *d; + + dir = opendir("/proc"); + if (!dir) + return -errno; + + while ((d = readdir(dir))) { + pid_t pid; + int r; + + if (d->d_type != DT_DIR && + d->d_type != DT_UNKNOWN) + continue; + + if (parse_pid(d->d_name, &pid) < 0) + continue; + + if (ignore_proc(pid, sig == SIGKILL && !in_initrd())) + continue; + + if (sig == SIGKILL) { + _cleanup_free_ char *s = NULL; + + get_process_comm(pid, &s); + log_notice("Sending SIGKILL to PID "PID_FMT" (%s).", pid, strna(s)); + } + + if (kill(pid, sig) >= 0) { + if (pids) { + r = set_put(pids, PID_TO_PTR(pid)); + if (r < 0) + log_oom(); + } + } else if (errno != ENOENT) + log_warning_errno(errno, "Could not kill %d: %m", pid); + + if (send_sighup) { + /* Optionally, also send a SIGHUP signal, but + only if the process has a controlling + tty. This is useful to allow handling of + shells which ignore SIGTERM but react to + SIGHUP. We do not send this to processes that + have no controlling TTY since we don't want to + trigger reloads of daemon processes. Also we + make sure to only send this after SIGTERM so + that SIGTERM is always first in the queue. */ + + + if (get_ctty_devnr(pid, NULL) >= 0) + kill(pid, SIGHUP); + } + } + + return set_size(pids); +} + +void broadcast_signal(int sig, bool wait_for_exit, bool send_sighup) { + sigset_t mask, oldmask; + _cleanup_set_free_ Set *pids = NULL; + + if (wait_for_exit) + pids = set_new(NULL); + + assert_se(sigemptyset(&mask) == 0); + assert_se(sigaddset(&mask, SIGCHLD) == 0); + assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) == 0); + + if (kill(-1, SIGSTOP) < 0 && errno != ESRCH) + log_warning_errno(errno, "kill(-1, SIGSTOP) failed: %m"); + + killall(sig, pids, send_sighup); + + if (kill(-1, SIGCONT) < 0 && errno != ESRCH) + log_warning_errno(errno, "kill(-1, SIGCONT) failed: %m"); + + if (wait_for_exit) + wait_for_children(pids, &mask); + + assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0); +} diff --git a/src/libcore/killall.h b/src/libcore/killall.h new file mode 100644 index 0000000000..acc2439f00 --- /dev/null +++ b/src/libcore/killall.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +void broadcast_signal(int sig, bool wait_for_exit, bool send_sighup); diff --git a/src/libcore/kmod-setup.c b/src/libcore/kmod-setup.c new file mode 100644 index 0000000000..3503db52ed --- /dev/null +++ b/src/libcore/kmod-setup.c @@ -0,0 +1,131 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#ifdef HAVE_KMOD +#include +#endif + +#include "bus-util.h" +#include "capability-util.h" +#include "kmod-setup.h" +#include "macro.h" + +#ifdef HAVE_KMOD +static void systemd_kmod_log( + void *data, + int priority, + const char *file, int line, + const char *fn, + const char *format, + va_list args) { + + /* library logging is enabled at debug only */ + DISABLE_WARNING_FORMAT_NONLITERAL; + log_internalv(LOG_DEBUG, 0, file, line, fn, format, args); + REENABLE_WARNING; +} +#endif + +int kmod_setup(void) { +#ifdef HAVE_KMOD + + static const struct { + const char *module; + const char *path; + bool warn_if_unavailable:1; + bool warn_if_module:1; + bool (*condition_fn)(void); + } kmod_table[] = { + /* auto-loading on use doesn't work before udev is up */ + { "autofs4", "/sys/class/misc/autofs", true, false, NULL }, + + /* early configure of ::1 on the loopback device */ + { "ipv6", "/sys/module/ipv6", false, true, NULL }, + + /* 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 }, +#endif + }; + struct kmod_ctx *ctx = NULL; + unsigned int i; + int r; + + if (have_effective_cap(CAP_SYS_MODULE) == 0) + return 0; + + for (i = 0; i < ELEMENTSOF(kmod_table); i++) { + struct kmod_module *mod; + + if (kmod_table[i].path && access(kmod_table[i].path, F_OK) >= 0) + continue; + + if (kmod_table[i].condition_fn && !kmod_table[i].condition_fn()) + continue; + + if (kmod_table[i].warn_if_module) + log_debug("Your kernel apparently lacks built-in %s support. Might be " + "a good idea to compile it in. We'll now try to work around " + "this by loading the module...", kmod_table[i].module); + + if (!ctx) { + ctx = kmod_new(NULL, NULL); + if (!ctx) + return log_oom(); + + kmod_set_log_fn(ctx, systemd_kmod_log, NULL); + kmod_load_resources(ctx); + } + + r = kmod_module_new_from_name(ctx, kmod_table[i].module, &mod); + if (r < 0) { + log_error("Failed to lookup module '%s'", kmod_table[i].module); + continue; + } + + r = kmod_module_probe_insert_module(mod, KMOD_PROBE_APPLY_BLACKLIST, NULL, NULL, NULL, NULL); + if (r == 0) + log_debug("Inserted module '%s'", kmod_module_get_name(mod)); + else if (r == KMOD_PROBE_APPLY_BLACKLIST) + log_info("Module '%s' is blacklisted", kmod_module_get_name(mod)); + else { + bool print_warning = kmod_table[i].warn_if_unavailable || (r < 0 && r != -ENOENT); + + log_full_errno(print_warning ? LOG_WARNING : LOG_DEBUG, r, + "Failed to insert module '%s': %m", kmod_module_get_name(mod)); + } + + kmod_module_unref(mod); + } + + if (ctx) + kmod_unref(ctx); + +#endif + return 0; +} diff --git a/src/libcore/kmod-setup.h b/src/libcore/kmod-setup.h new file mode 100644 index 0000000000..685f4df301 --- /dev/null +++ b/src/libcore/kmod-setup.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int kmod_setup(void); diff --git a/src/libcore/linux/auto_dev-ioctl.h b/src/libcore/linux/auto_dev-ioctl.h new file mode 100644 index 0000000000..aeaeb3ea7a --- /dev/null +++ b/src/libcore/linux/auto_dev-ioctl.h @@ -0,0 +1,228 @@ +/* + * Copyright 2008 Red Hat, Inc. All rights reserved. + * Copyright 2008 Ian Kent + * + * This file is part of the Linux kernel and is made available under + * the terms of the GNU General Public License, version 2, or at your + * option, any later version, incorporated herein by reference. + */ + +#ifndef _LINUX_AUTO_DEV_IOCTL_H +#define _LINUX_AUTO_DEV_IOCTL_H + +#include + +#ifdef __KERNEL__ +#include +#else +#include +#endif /* __KERNEL__ */ + +#define AUTOFS_DEVICE_NAME "autofs" + +#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 +#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 + +#define AUTOFS_DEVID_LEN 16 + +#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) + +/* + * An ioctl interface for autofs mount point control. + */ + +struct args_protover { + __u32 version; +}; + +struct args_protosubver { + __u32 sub_version; +}; + +struct args_openmount { + __u32 devid; +}; + +struct args_ready { + __u32 token; +}; + +struct args_fail { + __u32 token; + __s32 status; +}; + +struct args_setpipefd { + __s32 pipefd; +}; + +struct args_timeout { + __u64 timeout; +}; + +struct args_requester { + __u32 uid; + __u32 gid; +}; + +struct args_expire { + __u32 how; +}; + +struct args_askumount { + __u32 may_umount; +}; + +struct args_ismountpoint { + union { + struct args_in { + __u32 type; + } in; + struct args_out { + __u32 devid; + __u32 magic; + } out; + }; +}; + +/* + * All the ioctls use this structure. + * When sending a path size must account for the total length + * of the chunk of memory otherwise is is the size of the + * structure. + */ + +struct autofs_dev_ioctl { + __u32 ver_major; + __u32 ver_minor; + __u32 size; /* total size of data passed in + * including this struct */ + __s32 ioctlfd; /* automount command fd */ + + /* Command parameters */ + + union { + struct args_protover protover; + struct args_protosubver protosubver; + struct args_openmount openmount; + struct args_ready ready; + struct args_fail fail; + struct args_setpipefd setpipefd; + struct args_timeout timeout; + struct args_requester requester; + struct args_expire expire; + struct args_askumount askumount; + struct args_ismountpoint ismountpoint; + }; + + char path[0]; +}; + +static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) { + memset(in, 0, sizeof(struct autofs_dev_ioctl)); + in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; + in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; + in->size = sizeof(struct autofs_dev_ioctl); + in->ioctlfd = -1; + return; +} + +/* + * If you change this make sure you make the corresponding change + * to autofs-dev-ioctl.c:lookup_ioctl() + */ +enum { + /* Get various version info */ + AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, + AUTOFS_DEV_IOCTL_PROTOVER_CMD, + AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, + + /* Open mount ioctl fd */ + AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, + + /* Close mount ioctl fd */ + AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, + + /* Mount/expire status returns */ + AUTOFS_DEV_IOCTL_READY_CMD, + AUTOFS_DEV_IOCTL_FAIL_CMD, + + /* Activate/deactivate autofs mount */ + AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, + AUTOFS_DEV_IOCTL_CATATONIC_CMD, + + /* Expiry timeout */ + AUTOFS_DEV_IOCTL_TIMEOUT_CMD, + + /* Get mount last requesting uid and gid */ + AUTOFS_DEV_IOCTL_REQUESTER_CMD, + + /* Check for eligible expire candidates */ + AUTOFS_DEV_IOCTL_EXPIRE_CMD, + + /* Request busy status */ + AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, + + /* Check if path is a mountpoint */ + AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, +}; + +#define AUTOFS_IOCTL 0x93 + +#define AUTOFS_DEV_IOCTL_VERSION \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_PROTOVER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_OPENMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_READY \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_FAIL \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_SETPIPEFD \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_CATATONIC \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_TIMEOUT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_REQUESTER \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_EXPIRE \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) + +#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ + _IOWR(AUTOFS_IOCTL, \ + AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) + +#endif /* _LINUX_AUTO_DEV_IOCTL_H */ diff --git a/src/libcore/load-dropin.c b/src/libcore/load-dropin.c new file mode 100644 index 0000000000..22b71b6f5e --- /dev/null +++ b/src/libcore/load-dropin.c @@ -0,0 +1,81 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + + +#include "conf-parser.h" +#include "load-dropin.h" +#include "load-fragment.h" +#include "log.h" +#include "strv.h" +#include "unit-name.h" +#include "unit.h" + +static int add_dependency_consumer( + UnitDependency dependency, + const char *entry, + const char* filepath, + void *arg) { + Unit *u = arg; + int r; + + assert(u); + + r = unit_add_dependency_by_name(u, dependency, entry, filepath, true); + if (r < 0) + log_error_errno(r, "Cannot add dependency %s to %s, ignoring: %m", entry, u->id); + + return 0; +} + +int unit_load_dropin(Unit *u) { + Iterator i; + char *t, **f; + int r; + + assert(u); + + /* Load dependencies from supplementary drop-in directories */ + + SET_FOREACH(t, u->names, i) { + char **p; + + STRV_FOREACH(p, u->manager->lookup_paths.unit_path) { + unit_file_process_dir(u->manager->unit_path_cache, *p, t, ".wants", UNIT_WANTS, + add_dependency_consumer, u, NULL); + unit_file_process_dir(u->manager->unit_path_cache, *p, t, ".requires", UNIT_REQUIRES, + add_dependency_consumer, u, NULL); + } + } + + u->dropin_paths = strv_free(u->dropin_paths); + r = unit_find_dropin_paths(u, &u->dropin_paths); + if (r <= 0) + return 0; + + STRV_FOREACH(f, u->dropin_paths) { + config_parse(u->id, *f, NULL, + UNIT_VTABLE(u)->sections, + config_item_perf_lookup, load_fragment_gperf_lookup, + false, false, false, u); + } + + u->dropin_mtime = now(CLOCK_REALTIME); + + return 0; +} diff --git a/src/libcore/load-dropin.h b/src/libcore/load-dropin.h new file mode 100644 index 0000000000..d8a4aefbb3 --- /dev/null +++ b/src/libcore/load-dropin.h @@ -0,0 +1,34 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "dropin.h" +#include "unit.h" + +/* Read service data supplementary drop-in directories */ + +static inline int unit_find_dropin_paths(Unit *u, char ***paths) { + return unit_file_find_dropin_paths(u->manager->lookup_paths.unit_path, + u->manager->unit_path_cache, + u->names, + paths); +} + +int unit_load_dropin(Unit *u); diff --git a/src/libcore/load-fragment-gperf.gperf.m4 b/src/libcore/load-fragment-gperf.gperf.m4 new file mode 100644 index 0000000000..5024fd19a5 --- /dev/null +++ b/src/libcore/load-fragment-gperf.gperf.m4 @@ -0,0 +1,378 @@ +%{ +#include +#include "conf-parser.h" +#include "load-fragment.h" +#include "missing.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name load_fragment_gperf_hash +%define lookup-function-name load_fragment_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +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.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) +$1.IOSchedulingPriority, config_parse_exec_io_priority, 0, offsetof($1, exec_context) +$1.CPUSchedulingPolicy, config_parse_exec_cpu_sched_policy, 0, offsetof($1, exec_context) +$1.CPUSchedulingPriority, config_parse_exec_cpu_sched_prio, 0, offsetof($1, exec_context) +$1.CPUSchedulingResetOnFork, config_parse_bool, 0, offsetof($1, exec_context.cpu_sched_reset_on_fork) +$1.CPUAffinity, config_parse_exec_cpu_affinity, 0, offsetof($1, exec_context) +$1.UMask, config_parse_mode, 0, offsetof($1, exec_context.umask) +$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.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) +$1.TTYPath, config_parse_unit_path_printf, 0, offsetof($1, exec_context.tty_path) +$1.TTYReset, config_parse_bool, 0, offsetof($1, exec_context.tty_reset) +$1.TTYVHangup, config_parse_bool, 0, offsetof($1, exec_context.tty_vhangup) +$1.TTYVTDisallocate, config_parse_bool, 0, offsetof($1, exec_context.tty_vt_disallocate) +$1.SyslogIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.syslog_identifier) +$1.SyslogFacility, config_parse_log_facility, 0, offsetof($1, exec_context.syslog_priority) +$1.SyslogLevel, config_parse_log_level, 0, offsetof($1, exec_context.syslog_priority) +$1.SyslogLevelPrefix, config_parse_bool, 0, offsetof($1, exec_context.syslog_level_prefix) +$1.Capabilities, config_parse_exec_capabilities, 0, offsetof($1, exec_context) +$1.SecureBits, config_parse_exec_secure_bits, 0, offsetof($1, exec_context) +$1.CapabilityBoundingSet, config_parse_capability_set, 0, offsetof($1, exec_context.capability_bounding_set) +$1.AmbientCapabilities, config_parse_capability_set, 0, offsetof($1, exec_context.capability_ambient_set) +$1.TimerSlackNSec, config_parse_nsec, 0, offsetof($1, exec_context.timer_slack_nsec) +$1.NoNewPrivileges, config_parse_no_new_privileges, 0, offsetof($1, exec_context) +m4_ifdef(`HAVE_SECCOMP', +`$1.SystemCallFilter, config_parse_syscall_filter, 0, offsetof($1, exec_context) +$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.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.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) +$1.LimitDATA, config_parse_limit, RLIMIT_DATA, offsetof($1, exec_context.rlimit) +$1.LimitSTACK, config_parse_limit, RLIMIT_STACK, offsetof($1, exec_context.rlimit) +$1.LimitCORE, config_parse_limit, RLIMIT_CORE, offsetof($1, exec_context.rlimit) +$1.LimitRSS, config_parse_limit, RLIMIT_RSS, offsetof($1, exec_context.rlimit) +$1.LimitNOFILE, config_parse_limit, RLIMIT_NOFILE, offsetof($1, exec_context.rlimit) +$1.LimitAS, config_parse_limit, RLIMIT_AS, offsetof($1, exec_context.rlimit) +$1.LimitNPROC, config_parse_limit, RLIMIT_NPROC, offsetof($1, exec_context.rlimit) +$1.LimitMEMLOCK, config_parse_limit, RLIMIT_MEMLOCK, offsetof($1, exec_context.rlimit) +$1.LimitLOCKS, config_parse_limit, RLIMIT_LOCKS, offsetof($1, exec_context.rlimit) +$1.LimitSIGPENDING, config_parse_limit, RLIMIT_SIGPENDING, offsetof($1, exec_context.rlimit) +$1.LimitMSGQUEUE, config_parse_limit, RLIMIT_MSGQUEUE, offsetof($1, exec_context.rlimit) +$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.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) +$1.ProtectSystem, config_parse_protect_system, 0, offsetof($1, exec_context) +$1.ProtectHome, config_parse_protect_home, 0, offsetof($1, exec_context) +$1.MountFlags, config_parse_exec_mount_flags, 0, offsetof($1, exec_context) +$1.Personality, config_parse_personality, 0, offsetof($1, exec_context.personality) +$1.RuntimeDirectoryMode, config_parse_mode, 0, offsetof($1, exec_context.runtime_directory_mode) +$1.RuntimeDirectory, config_parse_runtime_directory, 0, offsetof($1, exec_context.runtime_directory) +m4_ifdef(`HAVE_PAM', +`$1.PAMName, config_parse_unit_string_printf, 0, offsetof($1, exec_context.pam_name)', +`$1.PAMName, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') +$1.IgnoreSIGPIPE, config_parse_bool, 0, offsetof($1, exec_context.ignore_sigpipe) +$1.UtmpIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.utmp_id) +$1.UtmpMode, config_parse_exec_utmp_mode, 0, offsetof($1, exec_context.utmp_mode) +m4_ifdef(`HAVE_SELINUX', +`$1.SELinuxContext, config_parse_exec_selinux_context, 0, offsetof($1, exec_context)', +`$1.SELinuxContext, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') +m4_ifdef(`HAVE_APPARMOR', +`$1.AppArmorProfile, config_parse_exec_apparmor_profile, 0, offsetof($1, exec_context)', +`$1.AppArmorProfile, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') +m4_ifdef(`HAVE_SMACK', +`$1.SmackProcessLabel, config_parse_exec_smack_process_label, 0, offsetof($1, exec_context)', +`$1.SmackProcessLabel, config_parse_warn_compat, DISABLED_CONFIGURATION, 0')' +)m4_dnl +m4_define(`KILL_CONTEXT_CONFIG_ITEMS', +`$1.SendSIGKILL, config_parse_bool, 0, offsetof($1, kill_context.send_sigkill) +$1.SendSIGHUP, config_parse_bool, 0, offsetof($1, kill_context.send_sighup) +$1.KillMode, config_parse_kill_mode, 0, offsetof($1, kill_context.kill_mode) +$1.KillSignal, config_parse_signal, 0, offsetof($1, kill_context.kill_signal)' +)m4_dnl +m4_define(`CGROUP_CONTEXT_CONFIG_ITEMS', +`$1.Slice, config_parse_unit_slice, 0, 0 +$1.CPUAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.cpu_accounting) +$1.CPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.cpu_shares) +$1.StartupCPUShares, config_parse_cpu_shares, 0, offsetof($1, cgroup_context.startup_cpu_shares) +$1.CPUQuota, config_parse_cpu_quota, 0, offsetof($1, cgroup_context) +$1.MemoryAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.memory_accounting) +$1.MemoryLimit, config_parse_memory_limit, 0, offsetof($1, cgroup_context) +$1.DeviceAllow, config_parse_device_allow, 0, offsetof($1, cgroup_context) +$1.DevicePolicy, config_parse_device_policy, 0, offsetof($1, cgroup_context.device_policy) +$1.BlockIOAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.blockio_accounting) +$1.BlockIOWeight, config_parse_blockio_weight, 0, offsetof($1, cgroup_context.blockio_weight) +$1.StartupBlockIOWeight, config_parse_blockio_weight, 0, offsetof($1, cgroup_context.startup_blockio_weight) +$1.BlockIODeviceWeight, config_parse_blockio_device_weight, 0, offsetof($1, cgroup_context) +$1.BlockIOReadBandwidth, config_parse_blockio_bandwidth, 0, offsetof($1, cgroup_context) +$1.BlockIOWriteBandwidth, config_parse_blockio_bandwidth, 0, offsetof($1, cgroup_context) +$1.TasksAccounting, config_parse_bool, 0, offsetof($1, cgroup_context.tasks_accounting) +$1.TasksMax, config_parse_tasks_max, 0, offsetof($1, cgroup_context.tasks_max) +$1.Delegate, config_parse_bool, 0, offsetof($1, cgroup_context.delegate) +$1.NetClass, config_parse_warn_compat, DISABLED_LEGACY, 0' +)m4_dnl +Unit.Description, config_parse_unit_string_printf, 0, offsetof(Unit, description) +Unit.Documentation, config_parse_documentation, 0, offsetof(Unit, documentation) +Unit.SourcePath, config_parse_path, 0, offsetof(Unit, source_path) +Unit.Requires, config_parse_unit_deps, UNIT_REQUIRES, 0 +Unit.Requisite, config_parse_unit_deps, UNIT_REQUISITE, 0 +Unit.Wants, config_parse_unit_deps, UNIT_WANTS, 0 +Unit.BindsTo, config_parse_unit_deps, UNIT_BINDS_TO, 0 +Unit.BindTo, config_parse_unit_deps, UNIT_BINDS_TO, 0 +Unit.Conflicts, config_parse_unit_deps, UNIT_CONFLICTS, 0 +Unit.Before, config_parse_unit_deps, UNIT_BEFORE, 0 +Unit.After, config_parse_unit_deps, UNIT_AFTER, 0 +Unit.OnFailure, config_parse_unit_deps, UNIT_ON_FAILURE, 0 +Unit.PropagatesReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0 +Unit.PropagateReloadTo, config_parse_unit_deps, UNIT_PROPAGATES_RELOAD_TO, 0 +Unit.ReloadPropagatedFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0 +Unit.PropagateReloadFrom, config_parse_unit_deps, UNIT_RELOAD_PROPAGATED_FROM, 0 +Unit.PartOf, config_parse_unit_deps, UNIT_PART_OF, 0 +Unit.JoinsNamespaceOf, config_parse_unit_deps, UNIT_JOINS_NAMESPACE_OF, 0 +Unit.RequiresOverridable, config_parse_obsolete_unit_deps, UNIT_REQUIRES, 0 +Unit.RequisiteOverridable, config_parse_obsolete_unit_deps, UNIT_REQUISITE, 0 +Unit.RequiresMountsFor, config_parse_unit_requires_mounts_for, 0, 0 +Unit.StopWhenUnneeded, config_parse_bool, 0, offsetof(Unit, stop_when_unneeded) +Unit.RefuseManualStart, config_parse_bool, 0, offsetof(Unit, refuse_manual_start) +Unit.RefuseManualStop, config_parse_bool, 0, offsetof(Unit, refuse_manual_stop) +Unit.AllowIsolate, config_parse_bool, 0, offsetof(Unit, allow_isolate) +Unit.DefaultDependencies, config_parse_bool, 0, offsetof(Unit, default_dependencies) +Unit.OnFailureJobMode, config_parse_job_mode, 0, offsetof(Unit, on_failure_job_mode) +Unit.OnFailureIsolate, config_parse_job_mode_isolate, 0, offsetof(Unit, on_failure_job_mode) +Unit.IgnoreOnIsolate, config_parse_bool, 0, offsetof(Unit, ignore_on_isolate) +Unit.IgnoreOnSnapshot, config_parse_warn_compat, DISABLED_LEGACY, 0 +Unit.JobTimeoutSec, config_parse_sec_fix_0, 0, offsetof(Unit, job_timeout) +Unit.JobTimeoutAction, config_parse_failure_action, 0, offsetof(Unit, job_timeout_action) +Unit.JobTimeoutRebootArgument, config_parse_string, 0, offsetof(Unit, job_timeout_reboot_arg) +Unit.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) +Unit.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst) +Unit.StartLimitAction, config_parse_failure_action, 0, offsetof(Unit, start_limit_action) +Unit.RebootArgument, config_parse_string, 0, offsetof(Unit, reboot_arg) +Unit.ConditionPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, conditions) +Unit.ConditionPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, conditions) +Unit.ConditionPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, offsetof(Unit, conditions) +Unit.ConditionPathIsSymbolicLink,config_parse_unit_condition_path, CONDITION_PATH_IS_SYMBOLIC_LINK,offsetof(Unit, conditions) +Unit.ConditionPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, offsetof(Unit, conditions) +Unit.ConditionPathIsReadWrite, config_parse_unit_condition_path, CONDITION_PATH_IS_READ_WRITE, offsetof(Unit, conditions) +Unit.ConditionDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, offsetof(Unit, conditions) +Unit.ConditionFileNotEmpty, config_parse_unit_condition_path, CONDITION_FILE_NOT_EMPTY, offsetof(Unit, conditions) +Unit.ConditionFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, offsetof(Unit, conditions) +Unit.ConditionNeedsUpdate, config_parse_unit_condition_path, CONDITION_NEEDS_UPDATE, offsetof(Unit, conditions) +Unit.ConditionFirstBoot, config_parse_unit_condition_string, CONDITION_FIRST_BOOT, offsetof(Unit, conditions) +Unit.ConditionKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, conditions) +Unit.ConditionArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, conditions) +Unit.ConditionVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, conditions) +Unit.ConditionSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, offsetof(Unit, conditions) +Unit.ConditionCapability, config_parse_unit_condition_string, CONDITION_CAPABILITY, offsetof(Unit, conditions) +Unit.ConditionHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, conditions) +Unit.ConditionACPower, config_parse_unit_condition_string, CONDITION_AC_POWER, offsetof(Unit, conditions) +Unit.ConditionNull, config_parse_unit_condition_null, 0, offsetof(Unit, conditions) +Unit.AssertPathExists, config_parse_unit_condition_path, CONDITION_PATH_EXISTS, offsetof(Unit, asserts) +Unit.AssertPathExistsGlob, config_parse_unit_condition_path, CONDITION_PATH_EXISTS_GLOB, offsetof(Unit, asserts) +Unit.AssertPathIsDirectory, config_parse_unit_condition_path, CONDITION_PATH_IS_DIRECTORY, offsetof(Unit, asserts) +Unit.AssertPathIsSymbolicLink, config_parse_unit_condition_path, CONDITION_PATH_IS_SYMBOLIC_LINK,offsetof(Unit, asserts) +Unit.AssertPathIsMountPoint, config_parse_unit_condition_path, CONDITION_PATH_IS_MOUNT_POINT, offsetof(Unit, asserts) +Unit.AssertPathIsReadWrite, config_parse_unit_condition_path, CONDITION_PATH_IS_READ_WRITE, offsetof(Unit, asserts) +Unit.AssertDirectoryNotEmpty, config_parse_unit_condition_path, CONDITION_DIRECTORY_NOT_EMPTY, offsetof(Unit, asserts) +Unit.AssertFileNotEmpty, config_parse_unit_condition_path, CONDITION_FILE_NOT_EMPTY, offsetof(Unit, asserts) +Unit.AssertFileIsExecutable, config_parse_unit_condition_path, CONDITION_FILE_IS_EXECUTABLE, offsetof(Unit, asserts) +Unit.AssertNeedsUpdate, config_parse_unit_condition_path, CONDITION_NEEDS_UPDATE, offsetof(Unit, asserts) +Unit.AssertFirstBoot, config_parse_unit_condition_string, CONDITION_FIRST_BOOT, offsetof(Unit, asserts) +Unit.AssertKernelCommandLine, config_parse_unit_condition_string, CONDITION_KERNEL_COMMAND_LINE, offsetof(Unit, asserts) +Unit.AssertArchitecture, config_parse_unit_condition_string, CONDITION_ARCHITECTURE, offsetof(Unit, asserts) +Unit.AssertVirtualization, config_parse_unit_condition_string, CONDITION_VIRTUALIZATION, offsetof(Unit, asserts) +Unit.AssertSecurity, config_parse_unit_condition_string, CONDITION_SECURITY, offsetof(Unit, asserts) +Unit.AssertCapability, config_parse_unit_condition_string, CONDITION_CAPABILITY, offsetof(Unit, asserts) +Unit.AssertHost, config_parse_unit_condition_string, CONDITION_HOST, offsetof(Unit, asserts) +Unit.AssertACPower, config_parse_unit_condition_string, CONDITION_AC_POWER, offsetof(Unit, asserts) +Unit.AssertNull, config_parse_unit_condition_null, 0, offsetof(Unit, asserts) +m4_dnl +Service.PIDFile, config_parse_unit_path_printf, 0, offsetof(Service, pid_file) +Service.ExecStartPre, config_parse_exec, SERVICE_EXEC_START_PRE, offsetof(Service, exec_command) +Service.ExecStart, config_parse_exec, SERVICE_EXEC_START, offsetof(Service, exec_command) +Service.ExecStartPost, config_parse_exec, SERVICE_EXEC_START_POST, offsetof(Service, exec_command) +Service.ExecReload, config_parse_exec, SERVICE_EXEC_RELOAD, offsetof(Service, exec_command) +Service.ExecStop, config_parse_exec, SERVICE_EXEC_STOP, offsetof(Service, exec_command) +Service.ExecStopPost, config_parse_exec, SERVICE_EXEC_STOP_POST, offsetof(Service, exec_command) +Service.RestartSec, config_parse_sec, 0, offsetof(Service, restart_usec) +Service.TimeoutSec, config_parse_service_timeout, 0, 0 +Service.TimeoutStartSec, config_parse_service_timeout, 0, 0 +Service.TimeoutStopSec, config_parse_service_timeout, 0, 0 +Service.RuntimeMaxSec, config_parse_sec, 0, offsetof(Service, runtime_max_usec) +Service.WatchdogSec, config_parse_sec, 0, offsetof(Service, watchdog_usec) +Service.StartLimitInterval, config_parse_sec, 0, offsetof(Unit, start_limit.interval) +Service.StartLimitBurst, config_parse_unsigned, 0, offsetof(Unit, start_limit.burst) +Service.StartLimitAction, config_parse_failure_action, 0, offsetof(Unit, start_limit_action) +Service.RebootArgument, config_parse_string, 0, offsetof(Unit, reboot_arg) +Service.FailureAction, config_parse_failure_action, 0, offsetof(Service, failure_action) +Service.Type, config_parse_service_type, 0, offsetof(Service, type) +Service.Restart, config_parse_service_restart, 0, offsetof(Service, restart) +Service.PermissionsStartOnly, config_parse_bool, 0, offsetof(Service, permissions_start_only) +Service.RootDirectoryStartOnly, config_parse_bool, 0, offsetof(Service, root_directory_start_only) +Service.RemainAfterExit, config_parse_bool, 0, offsetof(Service, remain_after_exit) +Service.GuessMainPID, config_parse_bool, 0, offsetof(Service, guess_main_pid) +Service.RestartPreventExitStatus, config_parse_set_status, 0, offsetof(Service, restart_prevent_status) +Service.RestartForceExitStatus, config_parse_set_status, 0, offsetof(Service, restart_force_status) +Service.SuccessExitStatus, config_parse_set_status, 0, offsetof(Service, success_status) +Service.SysVStartPriority, config_parse_warn_compat, DISABLED_LEGACY, 0 +Service.NonBlocking, config_parse_bool, 0, offsetof(Service, exec_context.non_blocking) +Service.BusName, config_parse_bus_name, 0, offsetof(Service, bus_name) +Service.FileDescriptorStoreMax, config_parse_unsigned, 0, offsetof(Service, n_fd_store_max) +Service.NotifyAccess, config_parse_notify_access, 0, offsetof(Service, notify_access) +Service.Sockets, config_parse_service_sockets, 0, 0 +Service.BusPolicy, config_parse_bus_endpoint_policy, 0, offsetof(Service, exec_context) +Service.USBFunctionDescriptors, config_parse_path, 0, offsetof(Service, usb_function_descriptors) +Service.USBFunctionStrings, config_parse_path, 0, offsetof(Service, usb_function_strings) +EXEC_CONTEXT_CONFIG_ITEMS(Service)m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Service)m4_dnl +KILL_CONTEXT_CONFIG_ITEMS(Service)m4_dnl +m4_dnl +Socket.ListenStream, config_parse_socket_listen, SOCKET_SOCKET, 0 +Socket.ListenDatagram, config_parse_socket_listen, SOCKET_SOCKET, 0 +Socket.ListenSequentialPacket, config_parse_socket_listen, SOCKET_SOCKET, 0 +Socket.ListenFIFO, config_parse_socket_listen, SOCKET_FIFO, 0 +Socket.ListenNetlink, config_parse_socket_listen, SOCKET_SOCKET, 0 +Socket.ListenSpecial, config_parse_socket_listen, SOCKET_SPECIAL, 0 +Socket.ListenMessageQueue, config_parse_socket_listen, SOCKET_MQUEUE, 0 +Socket.ListenUSBFunction, config_parse_socket_listen, SOCKET_USB_FUNCTION, 0 +Socket.SocketProtocol, config_parse_socket_protocol, 0, 0 +Socket.BindIPv6Only, config_parse_socket_bind, 0, 0, +Socket.Backlog, config_parse_unsigned, 0, offsetof(Socket, backlog) +Socket.BindToDevice, config_parse_socket_bindtodevice, 0, 0 +Socket.ExecStartPre, config_parse_exec, SOCKET_EXEC_START_PRE, offsetof(Socket, exec_command) +Socket.ExecStartPost, config_parse_exec, SOCKET_EXEC_START_POST, offsetof(Socket, exec_command) +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.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.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) +Socket.KeepAliveProbes, config_parse_unsigned, 0, offsetof(Socket, keep_alive_cnt) +Socket.DeferAcceptSec, config_parse_sec, 0, offsetof(Socket, defer_accept) +Socket.NoDelay, config_parse_bool, 0, offsetof(Socket, no_delay) +Socket.Priority, config_parse_int, 0, offsetof(Socket, priority) +Socket.ReceiveBuffer, config_parse_iec_size, 0, offsetof(Socket, receive_buffer) +Socket.SendBuffer, config_parse_iec_size, 0, offsetof(Socket, send_buffer) +Socket.IPTOS, config_parse_ip_tos, 0, offsetof(Socket, ip_tos) +Socket.IPTTL, config_parse_int, 0, offsetof(Socket, ip_ttl) +Socket.Mark, config_parse_int, 0, offsetof(Socket, mark) +Socket.PipeSize, config_parse_iec_size, 0, offsetof(Socket, pipe_size) +Socket.FreeBind, config_parse_bool, 0, offsetof(Socket, free_bind) +Socket.Transparent, config_parse_bool, 0, offsetof(Socket, transparent) +Socket.Broadcast, config_parse_bool, 0, offsetof(Socket, broadcast) +Socket.PassCredentials, config_parse_bool, 0, offsetof(Socket, pass_cred) +Socket.PassSecurity, config_parse_bool, 0, offsetof(Socket, pass_sec) +Socket.TCPCongestion, config_parse_string, 0, offsetof(Socket, tcp_congestion) +Socket.ReusePort, config_parse_bool, 0, offsetof(Socket, reuse_port) +Socket.MessageQueueMaxMessages, config_parse_long, 0, offsetof(Socket, mq_maxmsg) +Socket.MessageQueueMessageSize, config_parse_long, 0, offsetof(Socket, mq_msgsize) +Socket.RemoveOnStop, config_parse_bool, 0, offsetof(Socket, remove_on_stop) +Socket.Symlinks, config_parse_unit_path_strv_printf, 0, offsetof(Socket, symlinks) +Socket.FileDescriptorName, config_parse_fdname, 0, 0 +Socket.Service, config_parse_socket_service, 0, 0 +m4_ifdef(`HAVE_SMACK', +`Socket.SmackLabel, config_parse_string, 0, offsetof(Socket, smack) +Socket.SmackLabelIPIn, config_parse_string, 0, offsetof(Socket, smack_ip_in) +Socket.SmackLabelIPOut, config_parse_string, 0, offsetof(Socket, smack_ip_out)', +`Socket.SmackLabel, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 +Socket.SmackLabelIPIn, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 +Socket.SmackLabelIPOut, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') +m4_ifdef(`HAVE_SELINUX', +`Socket.SELinuxContextFromNet, config_parse_bool, 0, offsetof(Socket, selinux_context_from_net)', +`Socket.SELinuxContextFromNet, config_parse_warn_compat, DISABLED_CONFIGURATION, 0') +EXEC_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl +KILL_CONTEXT_CONFIG_ITEMS(Socket)m4_dnl +m4_dnl +BusName.Name, config_parse_string, 0, offsetof(BusName, name) +BusName.Activating, config_parse_bool, 0, offsetof(BusName, activating) +BusName.Service, config_parse_busname_service, 0, 0 +BusName.AllowUser, config_parse_bus_policy, 0, 0 +BusName.AllowGroup, config_parse_bus_policy, 0, 0 +BusName.AllowWorld, config_parse_bus_policy_world, 0, offsetof(BusName, policy_world) +BusName.SELinuxContext, config_parse_exec_selinux_context, 0, 0 +BusName.AcceptFileDescriptors, config_parse_bool, 0, offsetof(BusName, accept_fd) +m4_dnl +Mount.What, config_parse_string, 0, offsetof(Mount, parameters_fragment.what) +Mount.Where, config_parse_path, 0, offsetof(Mount, where) +Mount.Options, config_parse_string, 0, offsetof(Mount, parameters_fragment.options) +Mount.Type, config_parse_string, 0, offsetof(Mount, parameters_fragment.fstype) +Mount.TimeoutSec, config_parse_sec, 0, offsetof(Mount, timeout_usec) +Mount.DirectoryMode, config_parse_mode, 0, offsetof(Mount, directory_mode) +Mount.SloppyOptions, config_parse_bool, 0, offsetof(Mount, sloppy_options) +EXEC_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl +KILL_CONTEXT_CONFIG_ITEMS(Mount)m4_dnl +m4_dnl +Automount.Where, config_parse_path, 0, offsetof(Automount, where) +Automount.DirectoryMode, config_parse_mode, 0, offsetof(Automount, directory_mode) +Automount.TimeoutIdleSec, config_parse_sec, 0, offsetof(Automount, timeout_idle_usec) +m4_dnl +Swap.What, config_parse_path, 0, offsetof(Swap, parameters_fragment.what) +Swap.Priority, config_parse_int, 0, offsetof(Swap, parameters_fragment.priority) +Swap.Options, config_parse_string, 0, offsetof(Swap, parameters_fragment.options) +Swap.TimeoutSec, config_parse_sec, 0, offsetof(Swap, timeout_usec) +EXEC_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl +KILL_CONTEXT_CONFIG_ITEMS(Swap)m4_dnl +m4_dnl +Timer.OnCalendar, config_parse_timer, 0, 0 +Timer.OnActiveSec, config_parse_timer, 0, 0 +Timer.OnBootSec, config_parse_timer, 0, 0 +Timer.OnStartupSec, config_parse_timer, 0, 0 +Timer.OnUnitActiveSec, config_parse_timer, 0, 0 +Timer.OnUnitInactiveSec, config_parse_timer, 0, 0 +Timer.Persistent, config_parse_bool, 0, offsetof(Timer, persistent) +Timer.WakeSystem, config_parse_bool, 0, offsetof(Timer, wake_system) +Timer.RemainAfterElapse, config_parse_bool, 0, offsetof(Timer, remain_after_elapse) +Timer.AccuracySec, config_parse_sec, 0, offsetof(Timer, accuracy_usec) +Timer.RandomizedDelaySec, config_parse_sec, 0, offsetof(Timer, random_usec) +Timer.Unit, config_parse_trigger_unit, 0, 0 +m4_dnl +Path.PathExists, config_parse_path_spec, 0, 0 +Path.PathExistsGlob, config_parse_path_spec, 0, 0 +Path.PathChanged, config_parse_path_spec, 0, 0 +Path.PathModified, config_parse_path_spec, 0, 0 +Path.DirectoryNotEmpty, config_parse_path_spec, 0, 0 +Path.Unit, config_parse_trigger_unit, 0, 0 +Path.MakeDirectory, config_parse_bool, 0, offsetof(Path, make_directory) +Path.DirectoryMode, config_parse_mode, 0, offsetof(Path, directory_mode) +m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Slice)m4_dnl +m4_dnl +CGROUP_CONTEXT_CONFIG_ITEMS(Scope)m4_dnl +KILL_CONTEXT_CONFIG_ITEMS(Scope)m4_dnl +Scope.TimeoutStopSec, config_parse_sec, 0, offsetof(Scope, timeout_stop_usec) +m4_dnl The [Install] section is ignored here. +Install.Alias, NULL, 0, 0 +Install.WantedBy, NULL, 0, 0 +Install.RequiredBy, NULL, 0, 0 +Install.Also, NULL, 0, 0 +Install.DefaultInstance, NULL, 0, 0 diff --git a/src/libcore/load-fragment.c b/src/libcore/load-fragment.c new file mode 100644 index 0000000000..8804b3ac41 --- /dev/null +++ b/src/libcore/load-fragment.c @@ -0,0 +1,3939 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2012 Holger Hans Peter Freyther + + 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 . +***/ + +#include +#include +#include +#include +#ifdef HAVE_SECCOMP +#include +#endif +#include +#include +#include +#include + +#include "af-list.h" +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-internal.h" +#include "bus-util.h" +#include "cap-list.h" +#include "capability-util.h" +#include "cgroup.h" +#include "conf-parser.h" +#include "cpu-set-util.h" +#include "env-util.h" +#include "errno-list.h" +#include "escape.h" +#include "fd-util.h" +#include "fs-util.h" +#include "ioprio.h" +#include "load-fragment.h" +#include "log.h" +#include "missing.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "rlimit-util.h" +#ifdef HAVE_SECCOMP +#include "seccomp-util.h" +#endif +#include "securebits.h" +#include "signal-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit-printf.h" +#include "unit.h" +#include "utf8.h" +#include "web-util.h" + +int config_parse_warn_compat( + 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) { + Disabled reason = ltype; + + switch(reason) { + case DISABLED_CONFIGURATION: + log_syntax(unit, LOG_DEBUG, filename, line, 0, + "Support for option %s= has been disabled at compile time and it is ignored", lvalue); + break; + case DISABLED_LEGACY: + log_syntax(unit, LOG_INFO, filename, line, 0, + "Support for option %s= has been removed and it is ignored", lvalue); + break; + case DISABLED_EXPERIMENTAL: + log_syntax(unit, LOG_INFO, filename, line, 0, + "Support for option %s= has not yet been enabled and it is ignored", lvalue); + break; + }; + + return 0; +} + +int config_parse_unit_deps( + 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) { + + UnitDependency d = ltype; + Unit *u = userdata; + const char *p; + + assert(filename); + assert(lvalue); + assert(rvalue); + + p = rvalue; + for(;;) { + _cleanup_free_ char *word = NULL, *k = NULL; + int r; + + r = extract_first_word(&p, &word, NULL, EXTRACT_RETAIN_ESCAPE); + 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_name_printf(u, word, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + continue; + } + + r = unit_add_dependency_by_name(u, d, k, NULL, true); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add dependency on %s, ignoring: %m", k); + } + + return 0; +} + +int config_parse_obsolete_unit_deps( + 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) { + + log_syntax(unit, LOG_WARNING, filename, line, 0, + "Unit dependency type %s= is obsolete, replacing by %s=, please update your unit file", lvalue, unit_dependency_to_string(ltype)); + + return config_parse_unit_deps(unit, filename, line, section, section_line, lvalue, ltype, rvalue, data, userdata); +} + +int config_parse_unit_string_printf( + 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) { + + _cleanup_free_ char *k = NULL; + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); +} + +int config_parse_unit_strv_printf( + 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) { + + Unit *u = userdata; + _cleanup_free_ char *k = NULL; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + return config_parse_strv(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); +} + +int config_parse_unit_path_printf( + 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) { + + _cleanup_free_ char *k = NULL; + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + return config_parse_path(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); +} + +int config_parse_unit_path_strv_printf( + 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 ***x = data; + const char *word, *state; + Unit *u = userdata; + size_t l; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + _cleanup_free_ char *k = NULL; + char t[l+1]; + + memcpy(t, word, l); + t[l] = 0; + + r = unit_full_printf(u, t, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", t); + return 0; + } + + if (!utf8_is_valid(k)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); + return 0; + } + + if (!path_is_absolute(k)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Symlink path %s is not absolute, ignoring: %m", k); + return 0; + } + + path_kill_slashes(k); + + r = strv_push(x, k); + if (r < 0) + return log_oom(); + + k = NULL; + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid syntax, ignoring."); + + return 0; +} + +int config_parse_socket_listen(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) { + + _cleanup_free_ SocketPort *p = NULL; + SocketPort *tail; + Socket *s; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + s = SOCKET(data); + + if (isempty(rvalue)) { + /* An empty assignment removes all ports */ + socket_free_ports(s); + return 0; + } + + p = new0(SocketPort, 1); + if (!p) + return log_oom(); + + if (ltype != SOCKET_SOCKET) { + + p->type = ltype; + r = unit_full_printf(UNIT(s), rvalue, &p->path); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + path_kill_slashes(p->path); + + } else if (streq(lvalue, "ListenNetlink")) { + _cleanup_free_ char *k = NULL; + + p->type = SOCKET_SOCKET; + r = unit_full_printf(UNIT(s), rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + r = socket_address_parse_netlink(&p->address, k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address value, ignoring: %s", rvalue); + return 0; + } + + } else { + _cleanup_free_ char *k = NULL; + + p->type = SOCKET_SOCKET; + r = unit_full_printf(UNIT(s), rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r,"Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + r = socket_address_parse_and_warn(&p->address, k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse address value, ignoring: %s", rvalue); + return 0; + } + + if (streq(lvalue, "ListenStream")) + p->address.type = SOCK_STREAM; + else if (streq(lvalue, "ListenDatagram")) + p->address.type = SOCK_DGRAM; + else { + assert(streq(lvalue, "ListenSequentialPacket")); + p->address.type = SOCK_SEQPACKET; + } + + if (socket_address_family(&p->address) != AF_LOCAL && p->address.type == SOCK_SEQPACKET) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Address family not supported, ignoring: %s", rvalue); + return 0; + } + } + + p->fd = -1; + p->auxiliary_fds = NULL; + p->n_auxiliary_fds = 0; + p->socket = s; + + if (s->ports) { + LIST_FIND_TAIL(port, s->ports, tail); + LIST_INSERT_AFTER(port, s->ports, tail, p); + } else + LIST_PREPEND(port, s->ports, p); + p = NULL; + + return 0; +} + +int config_parse_socket_protocol(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) { + Socket *s; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + s = SOCKET(data); + + if (streq(rvalue, "udplite")) + s->socket_protocol = IPPROTO_UDPLITE; + else if (streq(rvalue, "sctp")) + s->socket_protocol = IPPROTO_SCTP; + else { + log_syntax(unit, LOG_ERR, filename, line, 0, "Socket protocol not supported, ignoring: %s", rvalue); + return 0; + } + + return 0; +} + +int config_parse_socket_bind(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) { + + Socket *s; + SocketAddressBindIPv6Only b; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + s = SOCKET(data); + + b = socket_address_bind_ipv6_only_from_string(rvalue); + if (b < 0) { + int r; + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse bind IPv6 only value, ignoring: %s", rvalue); + return 0; + } + + s->bind_ipv6_only = r ? SOCKET_ADDRESS_IPV6_ONLY : SOCKET_ADDRESS_BOTH; + } else + s->bind_ipv6_only = b; + + return 0; +} + +int config_parse_exec_nice(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) { + + ExecContext *c = data; + int priority, r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atoi(rvalue, &priority); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse nice priority, ignoring: %s", rvalue); + return 0; + } + + if (priority < PRIO_MIN || priority >= PRIO_MAX) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Nice priority out of range, ignoring: %s", rvalue); + return 0; + } + + c->nice = priority; + c->nice_set = true; + + return 0; +} + +int config_parse_exec_oom_score_adjust(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) { + + ExecContext *c = data; + int oa, r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atoi(rvalue, &oa); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse the OOM score adjust value, ignoring: %s", rvalue); + return 0; + } + + if (oa < OOM_SCORE_ADJ_MIN || oa > OOM_SCORE_ADJ_MAX) { + log_syntax(unit, LOG_ERR, filename, line, 0, "OOM score adjust value out of range, ignoring: %s", rvalue); + return 0; + } + + c->oom_score_adjust = oa; + c->oom_score_adjust_set = true; + + return 0; +} + +int config_parse_exec( + 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) { + + ExecCommand **e = data; + const char *p; + bool semicolon; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(e); + + e += ltype; + rvalue += strspn(rvalue, WHITESPACE); + + if (isempty(rvalue)) { + /* An empty assignment resets the list */ + *e = exec_command_free_list(*e); + return 0; + } + + p = rvalue; + do { + _cleanup_free_ char *path = NULL, *firstword = NULL; + bool separate_argv0 = false, ignore = false; + _cleanup_free_ ExecCommand *nce = NULL; + _cleanup_strv_free_ char **n = NULL; + size_t nlen = 0, nbufsize = 0; + char *f; + int i; + + semicolon = false; + + r = extract_first_word_and_warn(&p, &firstword, WHITESPACE, EXTRACT_QUOTES|EXTRACT_CUNESCAPE, unit, filename, line, rvalue); + if (r <= 0) + 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]. */ + if (*f == '-' && !ignore) + ignore = true; + else if (*f == '@' && !separate_argv0) + separate_argv0 = true; + else + break; + f ++; + } + + if (isempty(f)) { + /* First word is either "-" or "@" with no command. */ + log_syntax(unit, LOG_ERR, filename, line, 0, "Empty path in command line, ignoring: \"%s\"", rvalue); + return 0; + } + if (!string_is_safe(f)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path contains special characters, ignoring: %s", rvalue); + return 0; + } + if (!path_is_absolute(f)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path is not absolute, ignoring: %s", rvalue); + return 0; + } + if (endswith(f, "/")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Executable path specifies a directory, ignoring: %s", rvalue); + return 0; + } + + if (f == firstword) { + path = firstword; + firstword = NULL; + } else { + path = strdup(f); + if (!path) + return log_oom(); + } + + if (!separate_argv0) { + if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) + return log_oom(); + f = strdup(path); + if (!f) + return log_oom(); + n[nlen++] = f; + n[nlen] = NULL; + } + + path_kill_slashes(path); + + while (!isempty(p)) { + _cleanup_free_ char *word = NULL; + + /* Check explicitly for an unquoted semicolon as + * command separator token. */ + if (p[0] == ';' && (!p[1] || strchr(WHITESPACE, p[1]))) { + p ++; + p += strspn(p, WHITESPACE); + semicolon = true; + break; + } + + /* Check for \; explicitly, to not confuse it with \\; + * or "\;" or "\\;" etc. extract_first_word would + * return the same for all of those. */ + if (p[0] == '\\' && p[1] == ';' && (!p[2] || strchr(WHITESPACE, p[2]))) { + p += 2; + p += strspn(p, WHITESPACE); + if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) + return log_oom(); + f = strdup(";"); + if (!f) + return log_oom(); + n[nlen++] = f; + n[nlen] = NULL; + continue; + } + + r = extract_first_word_and_warn(&p, &word, WHITESPACE, EXTRACT_QUOTES|EXTRACT_CUNESCAPE, unit, filename, line, rvalue); + if (r == 0) + break; + else if (r < 0) + return 0; + + if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) + return log_oom(); + n[nlen++] = word; + n[nlen] = NULL; + word = NULL; + } + + if (!n || !n[0]) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Empty executable name or zeroeth argument, ignoring: %s", rvalue); + return 0; + } + + nce = new0(ExecCommand, 1); + if (!nce) + return log_oom(); + + nce->argv = n; + nce->path = path; + nce->ignore = ignore; + + exec_command_append_list(e, nce); + + /* Do not _cleanup_free_ these. */ + n = NULL; + path = NULL; + nce = NULL; + + rvalue = p; + } while (semicolon); + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_service_type, service_type, ServiceType, "Failed to parse service type"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_service_restart, service_restart, ServiceRestart, "Failed to parse service restart specifier"); + +int config_parse_socket_bindtodevice(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) { + + Socket *s = data; + char *n; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (rvalue[0] && !streq(rvalue, "*")) { + n = strdup(rvalue); + if (!n) + return log_oom(); + } else + n = NULL; + + free(s->bind_to_device); + s->bind_to_device = n; + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_output, exec_output, ExecOutput, "Failed to parse output specifier"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_input, exec_input, ExecInput, "Failed to parse input specifier"); + +int config_parse_exec_io_class(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) { + + ExecContext *c = data; + int x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + x = ioprio_class_from_string(rvalue); + if (x < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse IO scheduling class, ignoring: %s", rvalue); + return 0; + } + + c->ioprio = IOPRIO_PRIO_VALUE(x, IOPRIO_PRIO_DATA(c->ioprio)); + c->ioprio_set = true; + + return 0; +} + +int config_parse_exec_io_priority(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) { + + ExecContext *c = data; + int i, r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atoi(rvalue, &i); + if (r < 0 || i < 0 || i >= IOPRIO_BE_NR) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse IO priority, ignoring: %s", rvalue); + return 0; + } + + c->ioprio = IOPRIO_PRIO_VALUE(IOPRIO_PRIO_CLASS(c->ioprio), i); + c->ioprio_set = true; + + return 0; +} + +int config_parse_exec_cpu_sched_policy(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) { + + + ExecContext *c = data; + int x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + x = sched_policy_from_string(rvalue); + if (x < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse CPU scheduling policy, ignoring: %s", rvalue); + return 0; + } + + c->cpu_sched_policy = x; + /* Moving to or from real-time policy? We need to adjust the priority */ + c->cpu_sched_priority = CLAMP(c->cpu_sched_priority, sched_get_priority_min(x), sched_get_priority_max(x)); + c->cpu_sched_set = true; + + return 0; +} + +int config_parse_exec_cpu_sched_prio(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) { + + ExecContext *c = data; + int i, min, max, r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atoi(rvalue, &i); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CPU scheduling policy, ignoring: %s", rvalue); + return 0; + } + + /* On Linux RR/FIFO range from 1 to 99 and OTHER/BATCH may only be 0 */ + min = sched_get_priority_min(c->cpu_sched_policy); + max = sched_get_priority_max(c->cpu_sched_policy); + + if (i < min || i > max) { + log_syntax(unit, LOG_ERR, filename, line, 0, "CPU scheduling priority is out of range, ignoring: %s", rvalue); + return 0; + } + + c->cpu_sched_priority = i; + c->cpu_sched_set = true; + + return 0; +} + +int config_parse_exec_cpu_affinity(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) { + + ExecContext *c = data; + _cleanup_cpu_free_ cpu_set_t *cpuset = NULL; + int ncpus; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + ncpus = parse_cpu_set_and_warn(rvalue, &cpuset, unit, filename, line, lvalue); + if (ncpus < 0) + return ncpus; + + if (c->cpuset) + CPU_FREE(c->cpuset); + + if (ncpus == 0) + /* An empty assignment resets the CPU list */ + c->cpuset = NULL; + else { + c->cpuset = cpuset; + cpuset = NULL; + } + c->cpuset_ncpus = ncpus; + + return 0; +} + +int config_parse_exec_capabilities(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) { + + ExecContext *c = data; + cap_t cap; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + cap = cap_from_text(rvalue); + if (!cap) { + log_syntax(unit, LOG_ERR, filename, line, errno, "Failed to parse capabilities, ignoring: %s", rvalue); + return 0; + } + + if (c->capabilities) + cap_free(c->capabilities); + c->capabilities = cap; + + return 0; +} + +int config_parse_exec_secure_bits(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) { + + ExecContext *c = data; + size_t l; + const char *word, *state; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* An empty assignment resets the field */ + c->secure_bits = 0; + return 0; + } + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + if (first_word(word, "keep-caps")) + c->secure_bits |= 1<secure_bits |= 1<secure_bits |= 1<secure_bits |= 1<secure_bits |= 1<secure_bits |= 1< replace */ + *capability_set = sum; + else + /* previous data -> merge */ + *capability_set |= sum; + + return 0; +} + +int config_parse_limit( + 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) { + + struct rlimit **rl = data, d = {}; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = rlimit_parse(ltype, rvalue, &d); + if (r == -EILSEQ) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Soft resource limit chosen higher than hard limit, ignoring: %s", rvalue); + return 0; + } + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse resource value, ignoring: %s", rvalue); + return 0; + } + + if (rl[ltype]) + *rl[ltype] = d; + else { + rl[ltype] = newdup(struct rlimit, &d, 1); + if (!rl[ltype]) + return log_oom(); + } + + return 0; +} + +#ifdef HAVE_SYSV_COMPAT +int config_parse_sysv_priority(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 *priority = data; + int i, r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atoi(rvalue, &i); + if (r < 0 || i < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse SysV start priority, ignoring: %s", rvalue); + return 0; + } + + *priority = (int) i; + return 0; +} +#endif + +DEFINE_CONFIG_PARSE_ENUM(config_parse_exec_utmp_mode, exec_utmp_mode, ExecUtmpMode, "Failed to parse utmp mode"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_kill_mode, kill_mode, KillMode, "Failed to parse kill mode"); + +int config_parse_exec_mount_flags(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) { + + + unsigned long flags = 0; + ExecContext *c = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (streq(rvalue, "shared")) + flags = MS_SHARED; + else if (streq(rvalue, "slave")) + flags = MS_SLAVE; + else if (streq(rvalue, "private")) + flags = MS_PRIVATE; + else { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse mount flag %s, ignoring.", rvalue); + return 0; + } + + c->mount_flags = flags; + + return 0; +} + +int config_parse_exec_selinux_context( + 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) { + + ExecContext *c = data; + Unit *u = userdata; + bool ignore; + char *k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + c->selinux_context = mfree(c->selinux_context); + c->selinux_context_ignore = false; + return 0; + } + + if (rvalue[0] == '-') { + ignore = true; + rvalue++; + } else + ignore = false; + + r = unit_name_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + return 0; + } + + free(c->selinux_context); + c->selinux_context = k; + c->selinux_context_ignore = ignore; + + return 0; +} + +int config_parse_exec_apparmor_profile( + 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) { + + ExecContext *c = data; + Unit *u = userdata; + bool ignore; + char *k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + c->apparmor_profile = mfree(c->apparmor_profile); + c->apparmor_profile_ignore = false; + return 0; + } + + if (rvalue[0] == '-') { + ignore = true; + rvalue++; + } else + ignore = false; + + r = unit_name_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + return 0; + } + + free(c->apparmor_profile); + c->apparmor_profile = k; + c->apparmor_profile_ignore = ignore; + + return 0; +} + +int config_parse_exec_smack_process_label( + 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) { + + ExecContext *c = data; + Unit *u = userdata; + bool ignore; + char *k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + c->smack_process_label = mfree(c->smack_process_label); + c->smack_process_label_ignore = false; + return 0; + } + + if (rvalue[0] == '-') { + ignore = true; + rvalue++; + } else + ignore = false; + + r = unit_name_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + return 0; + } + + free(c->smack_process_label); + c->smack_process_label = k; + c->smack_process_label_ignore = ignore; + + return 0; +} + +int config_parse_timer(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) { + + Timer *t = data; + usec_t u = 0; + TimerValue *v; + TimerBase b; + CalendarSpec *c = NULL; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets list */ + timer_free_values(t); + return 0; + } + + b = timer_base_from_string(lvalue); + if (b < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse timer base, ignoring: %s", lvalue); + return 0; + } + + if (b == TIMER_CALENDAR) { + if (calendar_spec_from_string(rvalue, &c) < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse calendar specification, ignoring: %s", rvalue); + return 0; + } + } else { + if (parse_sec(rvalue, &u) < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse timer value, ignoring: %s", rvalue); + return 0; + } + } + + v = new0(TimerValue, 1); + if (!v) { + calendar_spec_free(c); + return log_oom(); + } + + v->base = b; + v->value = u; + v->calendar_spec = c; + + LIST_PREPEND(value, t->values, v); + + return 0; +} + +int config_parse_trigger_unit( + 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) { + + _cleanup_free_ char *p = NULL; + Unit *u = data; + UnitType type; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (!set_isempty(u->dependencies[UNIT_TRIGGERS])) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Multiple units to trigger specified, ignoring: %s", rvalue); + return 0; + } + + r = unit_name_printf(u, rvalue, &p); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + return 0; + } + + type = unit_name_to_type(p); + if (type < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Unit type not valid, ignoring: %s", rvalue); + return 0; + } + + if (type == u->type) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Trigger cannot be of same type, ignoring: %s", rvalue); + return 0; + } + + r = unit_add_two_dependencies_by_name(u, UNIT_BEFORE, UNIT_TRIGGERS, p, NULL, true); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add trigger on %s, ignoring: %m", p); + return 0; + } + + return 0; +} + +int config_parse_path_spec(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) { + + Path *p = data; + PathSpec *s; + PathType b; + _cleanup_free_ char *k = NULL; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment clears list */ + path_free_specs(p); + return 0; + } + + b = path_type_from_string(lvalue); + if (b < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse path type, ignoring: %s", lvalue); + return 0; + } + + r = unit_full_printf(UNIT(p), rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); + return 0; + } + + if (!path_is_absolute(k)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Path is not absolute, ignoring: %s", k); + return 0; + } + + s = new0(PathSpec, 1); + if (!s) + return log_oom(); + + s->unit = UNIT(p); + s->path = path_kill_slashes(k); + k = NULL; + s->type = b; + s->inotify_fd = -1; + + LIST_PREPEND(spec, p->specs, s); + + return 0; +} + +int config_parse_socket_service( + 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) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *p = NULL; + Socket *s = data; + Unit *x; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = unit_name_printf(UNIT(s), rvalue, &p); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + + if (!endswith(p, ".service")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type service, ignoring: %s", rvalue); + return 0; + } + + r = manager_load_unit(UNIT(s)->manager, p, NULL, &error, &x); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load unit %s, ignoring: %s", rvalue, bus_error_message(&error, r)); + return 0; + } + + unit_ref_set(&s->service, x); + + return 0; +} + +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) { + + _cleanup_free_ char *p = NULL; + Socket *s = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + s->fdname = mfree(s->fdname); + return 0; + } + + r = unit_name_printf(UNIT(s), rvalue, &p); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + + if (!fdname_is_valid(p)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid file descriptor name, ignoring: %s", p); + return 0; + } + + free(s->fdname); + s->fdname = p; + p = NULL; + + return 0; +} + +int config_parse_service_sockets( + 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) { + + Service *s = data; + const char *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + p = rvalue; + for(;;) { + _cleanup_free_ char *word = NULL, *k = NULL; + + r = extract_first_word(&p, &word, NULL, 0); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Trailing garbage in sockets, ignoring: %s", rvalue); + break; + } + + r = unit_name_printf(UNIT(s), word, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + continue; + } + + if (!endswith(k, ".socket")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type socket, ignoring: %s", k); + continue; + } + + r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_WANTS, UNIT_AFTER, k, NULL, true); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add dependency on %s, ignoring: %m", k); + + r = unit_add_dependency_by_name(UNIT(s), UNIT_TRIGGERED_BY, k, NULL, true); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add dependency on %s, ignoring: %m", k); + } + + return 0; +} + +int config_parse_bus_name( + 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) { + + _cleanup_free_ char *k = NULL; + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s, ignoring: %m", rvalue); + return 0; + } + + if (!service_name_is_valid(k)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid bus name %s, ignoring.", k); + return 0; + } + + return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, k, data, userdata); +} + +int config_parse_service_timeout( + 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) { + + Service *s = userdata; + usec_t usec; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(s); + + /* This is called for three cases: TimeoutSec=, TimeoutStopSec= and TimeoutStartSec=. */ + + r = parse_sec(rvalue, &usec); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s= parameter, ignoring: %s", lvalue, rvalue); + return 0; + } + + /* Traditionally, these options accepted 0 to disable the timeouts. However, a timeout of 0 suggests it happens + * immediately, hence fix this to become USEC_INFINITY instead. This is in-line with how we internally handle + * all other timeouts. */ + if (usec <= 0) + usec = USEC_INFINITY; + + if (!streq(lvalue, "TimeoutStopSec")) { + s->start_timeout_defined = true; + s->timeout_start_usec = usec; + } + + if (!streq(lvalue, "TimeoutStartSec")) + s->timeout_stop_usec = usec; + + return 0; +} + +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) { + + usec_t *usec = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(usec); + + /* This is pretty much like config_parse_sec(), except that this treats a time of 0 as infinity, for + * compatibility with older versions of systemd where 0 instead of infinity was used as indicator to turn off a + * timeout. */ + + r = parse_sec(rvalue, usec); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse %s= parameter, ignoring: %s", lvalue, rvalue); + return 0; + } + + if (*usec <= 0) + *usec = USEC_INFINITY; + + return 0; +} + +int config_parse_busname_service( + 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) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + BusName *n = data; + int r; + Unit *x; + _cleanup_free_ char *p = NULL; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = unit_name_printf(UNIT(n), rvalue, &p); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + + if (!endswith(p, ".service")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Unit must be of type service, ignoring: %s", rvalue); + return 0; + } + + r = manager_load_unit(UNIT(n)->manager, p, NULL, &error, &x); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load unit %s, ignoring: %s", rvalue, bus_error_message(&error, r)); + return 0; + } + + unit_ref_set(&n->service, x); + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_bus_policy_world, bus_policy_access, BusPolicyAccess, "Failed to parse bus name policy access"); + +int config_parse_bus_policy( + 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) { + + _cleanup_free_ BusNamePolicy *p = NULL; + _cleanup_free_ char *id_str = NULL; + BusName *busname = data; + char *access_str; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + p = new0(BusNamePolicy, 1); + if (!p) + return log_oom(); + + if (streq(lvalue, "AllowUser")) + p->type = BUSNAME_POLICY_TYPE_USER; + else if (streq(lvalue, "AllowGroup")) + p->type = BUSNAME_POLICY_TYPE_GROUP; + else + assert_not_reached("Unknown lvalue"); + + id_str = strdup(rvalue); + if (!id_str) + return log_oom(); + + access_str = strpbrk(id_str, WHITESPACE); + if (!access_str) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid busname policy value '%s'", rvalue); + return 0; + } + + *access_str = '\0'; + access_str++; + access_str += strspn(access_str, WHITESPACE); + + p->access = bus_policy_access_from_string(access_str); + if (p->access < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid busname policy access type '%s'", access_str); + return 0; + } + + p->name = id_str; + id_str = NULL; + + LIST_PREPEND(policy, busname->policy, p); + p = NULL; + + return 0; +} + +int config_parse_bus_endpoint_policy( + 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) { + + _cleanup_free_ char *name = NULL; + BusPolicyAccess access; + ExecContext *c = data; + char *access_str; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + name = strdup(rvalue); + if (!name) + return log_oom(); + + access_str = strpbrk(name, WHITESPACE); + if (!access_str) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid endpoint policy value '%s'", rvalue); + return 0; + } + + *access_str = '\0'; + access_str++; + access_str += strspn(access_str, WHITESPACE); + + access = bus_policy_access_from_string(access_str); + if (access <= _BUS_POLICY_ACCESS_INVALID || + access >= _BUS_POLICY_ACCESS_MAX) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid endpoint policy access type '%s'", access_str); + return 0; + } + + if (!c->bus_endpoint) { + r = bus_endpoint_new(&c->bus_endpoint); + if (r < 0) + return log_error_errno(r, "Failed to create bus endpoint object: %m"); + } + + return bus_endpoint_add_policy(c->bus_endpoint, name, access); +} + +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) { + + ExecContext *c = data; + Unit *u = userdata; + bool missing_ok; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(c); + assert(u); + + if (rvalue[0] == '-') { + missing_ok = true; + rvalue++; + } else + missing_ok = false; + + if (streq(rvalue, "~")) { + c->working_directory_home = true; + c->working_directory = mfree(c->working_directory); + } 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 working directory path '%s', ignoring: %m", rvalue); + return 0; + } + + path_kill_slashes(k); + + if (!utf8_is_valid(k)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); + return 0; + } + + if (!path_is_absolute(k)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Working directory path '%s' is not absolute, ignoring.", rvalue); + return 0; + } + + free(c->working_directory); + c->working_directory = k; + k = NULL; + + c->working_directory_home = false; + } + + c->working_directory_missing_ok = missing_ok; + return 0; +} + +int config_parse_unit_env_file(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 ***env = data; + Unit *u = userdata; + _cleanup_free_ char *n = NULL; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment frees the list */ + *env = strv_free(*env); + return 0; + } + + r = unit_full_printf(u, rvalue, &n); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + + if (!path_is_absolute(n[0] == '-' ? n + 1 : n)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Path '%s' is not absolute, ignoring.", n); + return 0; + } + + r = strv_extend(env, n); + if (r < 0) + return log_oom(); + + return 0; +} + +int config_parse_environ(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) { + + Unit *u = userdata; + char*** env = data; + const char *word, *state; + size_t l; + _cleanup_free_ char *k = NULL; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *env = strv_free(*env); + return 0; + } + + if (u) { + r = unit_full_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + } + + if (!k) { + k = strdup(rvalue); + if (!k) + return log_oom(); + } + + FOREACH_WORD_QUOTED(word, l, k, state) { + _cleanup_free_ char *n = NULL; + char **x; + + r = cunescape_length(word, l, 0, &n); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Couldn't unescape assignment, ignoring: %s", rvalue); + continue; + } + + if (!env_assignment_is_valid(n)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid environment assignment, ignoring: %s", rvalue); + continue; + } + + x = strv_env_set(*env, n); + if (!x) + return log_oom(); + + strv_free(*env); + *env = x; + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} + +int config_parse_pass_environ(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) { + + const char *whole_rvalue = rvalue; + char*** passenv = data; + _cleanup_strv_free_ char **n = NULL; + size_t nlen = 0, nbufsize = 0; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *passenv = strv_free(*passenv); + return 0; + } + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Trailing garbage in %s, ignoring: %s", lvalue, whole_rvalue); + break; + } + + if (!env_name_is_valid(word)) { + log_syntax(unit, LOG_ERR, filename, line, EINVAL, + "Invalid environment name for %s, ignoring: %s", lvalue, word); + continue; + } + + if (!GREEDY_REALLOC(n, nbufsize, nlen + 2)) + return log_oom(); + n[nlen++] = word; + n[nlen] = NULL; + word = NULL; + } + + if (n) { + r = strv_extend_strv(passenv, n, true); + if (r < 0) + return r; + } + + return 0; +} + +int config_parse_ip_tos(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 *ip_tos = data, x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + x = ip_tos_from_string(rvalue); + if (x < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse IP TOS value, ignoring: %s", rvalue); + return 0; + } + + *ip_tos = x; + return 0; +} + +int config_parse_unit_condition_path( + 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) { + + _cleanup_free_ char *p = NULL; + Condition **list = data, *c; + ConditionType t = ltype; + bool trigger, negate; + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *list = condition_free_list(*list); + return 0; + } + + trigger = rvalue[0] == '|'; + if (trigger) + rvalue++; + + negate = rvalue[0] == '!'; + if (negate) + rvalue++; + + r = unit_full_printf(u, rvalue, &p); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + + if (!path_is_absolute(p)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Path in condition not absolute, ignoring: %s", p); + return 0; + } + + c = condition_new(t, p, trigger, negate); + if (!c) + return log_oom(); + + LIST_PREPEND(conditions, *list, c); + return 0; +} + +int config_parse_unit_condition_string( + 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) { + + _cleanup_free_ char *s = NULL; + Condition **list = data, *c; + ConditionType t = ltype; + bool trigger, negate; + Unit *u = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *list = condition_free_list(*list); + return 0; + } + + trigger = rvalue[0] == '|'; + if (trigger) + rvalue++; + + negate = rvalue[0] == '!'; + if (negate) + rvalue++; + + r = unit_full_printf(u, rvalue, &s); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %s", rvalue); + return 0; + } + + c = condition_new(t, s, trigger, negate); + if (!c) + return log_oom(); + + LIST_PREPEND(conditions, *list, c); + return 0; +} + +int config_parse_unit_condition_null( + 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) { + + Condition **list = data, *c; + bool trigger, negate; + int b; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *list = condition_free_list(*list); + return 0; + } + + trigger = rvalue[0] == '|'; + if (trigger) + rvalue++; + + negate = rvalue[0] == '!'; + if (negate) + rvalue++; + + b = parse_boolean(rvalue); + if (b < 0) { + log_syntax(unit, LOG_ERR, filename, line, b, "Failed to parse boolean value in condition, ignoring: %s", rvalue); + return 0; + } + + if (!b) + negate = !negate; + + c = condition_new(CONDITION_NULL, NULL, trigger, negate); + if (!c) + return log_oom(); + + LIST_PREPEND(conditions, *list, c); + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_notify_access, notify_access, NotifyAccess, "Failed to parse notify access specifier"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_failure_action, failure_action, FailureAction, "Failed to parse failure action specifier"); + +int config_parse_unit_requires_mounts_for( + 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) { + + Unit *u = userdata; + const char *word, *state; + size_t l; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + int r; + _cleanup_free_ char *n; + + n = strndup(word, l); + if (!n) + return log_oom(); + + if (!utf8_is_valid(n)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); + continue; + } + + r = unit_require_mounts_for(u, n); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to add required mount for, ignoring: %s", rvalue); + continue; + } + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} + +int config_parse_documentation(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) { + + Unit *u = userdata; + int r; + char **a, **b; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + u->documentation = strv_free(u->documentation); + return 0; + } + + r = config_parse_unit_strv_printf(unit, filename, line, section, section_line, lvalue, ltype, + rvalue, data, userdata); + if (r < 0) + return r; + + for (a = b = u->documentation; a && *a; a++) { + + if (documentation_url_is_valid(*a)) + *(b++) = *a; + else { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid URL, ignoring: %s", *a); + free(*a); + } + } + if (b) + *b = NULL; + + return r; +} + +#ifdef HAVE_SECCOMP +int config_parse_syscall_filter( + 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) { + + static const char default_syscalls[] = + "execve\0" + "exit\0" + "exit_group\0" + "rt_sigreturn\0" + "sigreturn\0"; + + ExecContext *c = data; + Unit *u = userdata; + bool invert = false; + const char *word, *state; + size_t l; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + c->syscall_filter = set_free(c->syscall_filter); + c->syscall_whitelist = false; + return 0; + } + + if (rvalue[0] == '~') { + invert = true; + rvalue++; + } + + if (!c->syscall_filter) { + c->syscall_filter = set_new(NULL); + if (!c->syscall_filter) + return log_oom(); + + if (invert) + /* Allow everything but the ones listed */ + c->syscall_whitelist = false; + else { + const char *i; + + /* Allow nothing but the ones listed */ + c->syscall_whitelist = true; + + /* Accept default syscalls if we are on a whitelist */ + NULSTR_FOREACH(i, default_syscalls) { + int id; + + id = seccomp_syscall_resolve_name(i); + if (id < 0) + continue; + + r = set_put(c->syscall_filter, INT_TO_PTR(id + 1)); + if (r == 0) + continue; + if (r < 0) + return log_oom(); + } + } + } + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + _cleanup_free_ char *t = NULL; + int id; + + t = strndup(word, l); + if (!t) + return log_oom(); + + id = seccomp_syscall_resolve_name(t); + if (id < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse system call, ignoring: %s", t); + continue; + } + + /* If we previously wanted to forbid a syscall and now + * we want to allow it, then remove it from the list + */ + if (!invert == c->syscall_whitelist) { + r = set_put(c->syscall_filter, INT_TO_PTR(id + 1)); + if (r == 0) + continue; + if (r < 0) + return log_oom(); + } else + set_remove(c->syscall_filter, INT_TO_PTR(id + 1)); + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + /* Turn on NNP, but only if it wasn't configured explicitly + * before, and only if we are in user mode. */ + if (!c->no_new_privileges_set && u->manager->running_as == MANAGER_USER) + c->no_new_privileges = true; + + return 0; +} + +int config_parse_syscall_archs( + 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) { + + Set **archs = data; + const char *word, *state; + size_t l; + int r; + + if (isempty(rvalue)) { + *archs = set_free(*archs); + return 0; + } + + r = set_ensure_allocated(archs, NULL); + if (r < 0) + return log_oom(); + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + _cleanup_free_ char *t = NULL; + uint32_t a; + + t = strndup(word, l); + if (!t) + return log_oom(); + + r = seccomp_arch_from_string(t, &a); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse system call architecture, ignoring: %s", t); + continue; + } + + r = set_put(*archs, UINT32_TO_PTR(a + 1)); + if (r == 0) + continue; + if (r < 0) + return log_oom(); + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} + +int config_parse_syscall_errno( + 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) { + + ExecContext *c = data; + int e; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + /* Empty assignment resets to KILL */ + c->syscall_errno = 0; + return 0; + } + + e = errno_from_name(rvalue); + if (e < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse error number, ignoring: %s", rvalue); + return 0; + } + + c->syscall_errno = e; + return 0; +} + +int config_parse_address_families( + 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) { + + ExecContext *c = data; + bool invert = false; + const char *word, *state; + size_t l; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + c->address_families = set_free(c->address_families); + c->address_families_whitelist = false; + return 0; + } + + if (rvalue[0] == '~') { + invert = true; + rvalue++; + } + + if (!c->address_families) { + c->address_families = set_new(NULL); + if (!c->address_families) + return log_oom(); + + c->address_families_whitelist = !invert; + } + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + _cleanup_free_ char *t = NULL; + int af; + + t = strndup(word, l); + if (!t) + return log_oom(); + + af = af_from_name(t); + if (af <= 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse address family, ignoring: %s", t); + continue; + } + + /* If we previously wanted to forbid an address family and now + * we want to allow it, then remove it from the list + */ + if (!invert == c->address_families_whitelist) { + r = set_put(c->address_families, INT_TO_PTR(af)); + if (r == 0) + continue; + if (r < 0) + return log_oom(); + } else + set_remove(c->address_families, INT_TO_PTR(af)); + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} +#endif + +int config_parse_unit_slice( + 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) { + + _cleanup_free_ char *k = NULL; + Unit *u = userdata, *slice = NULL; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + r = unit_name_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve unit specifiers on %s. Ignoring.", rvalue); + return 0; + } + + r = manager_load_unit(u->manager, k, NULL, NULL, &slice); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to load slice unit %s. Ignoring.", k); + return 0; + } + + r = unit_set_slice(u, slice); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to assign slice %s to unit %s. Ignoring.", slice->id, u->id); + return 0; + } + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_device_policy, cgroup_device_policy, CGroupDevicePolicy, "Failed to parse device policy"); + +int config_parse_cpu_shares( + 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 *shares = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = cg_cpu_shares_parse(rvalue, shares); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "CPU shares '%s' invalid. Ignoring.", rvalue); + return 0; + } + + return 0; +} + +int config_parse_cpu_quota( + 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) { + + CGroupContext *c = data; + double percent; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + c->cpu_quota_per_sec_usec = USEC_INFINITY; + return 0; + } + + if (!endswith(rvalue, "%")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "CPU quota '%s' not ending in '%%'. 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); + + return 0; +} + +int config_parse_memory_limit( + 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) { + + CGroupContext *c = data; + uint64_t bytes; + int r; + + if (isempty(rvalue) || streq(rvalue, "infinity")) { + c->memory_limit = (uint64_t) -1; + return 0; + } + + 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); + return 0; + } + + c->memory_limit = bytes; + return 0; +} + +int config_parse_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 *tasks_max = data, u; + int r; + + if (isempty(rvalue) || streq(rvalue, "infinity")) { + *tasks_max = (uint64_t) -1; + 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); + return 0; + } + + *tasks_max = u; + return 0; +} + +int config_parse_device_allow( + 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) { + + _cleanup_free_ char *path = NULL; + CGroupContext *c = data; + CGroupDeviceAllow *a; + const char *m; + size_t n; + + if (isempty(rvalue)) { + while (c->device_allow) + cgroup_context_free_device_allow(c, c->device_allow); + + return 0; + } + + n = strcspn(rvalue, WHITESPACE); + path = strndup(rvalue, n); + if (!path) + return log_oom(); + + if (!startswith(path, "/dev/") && + !startswith(path, "block-") && + !startswith(path, "char-")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); + return 0; + } + + m = rvalue + n + strspn(rvalue + n, WHITESPACE); + if (isempty(m)) + m = "rwm"; + + if (!in_charset(m, "rwm")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device rights '%s'. Ignoring.", m); + return 0; + } + + a = new0(CGroupDeviceAllow, 1); + if (!a) + return log_oom(); + + a->path = path; + path = NULL; + a->r = !!strchr(m, 'r'); + a->w = !!strchr(m, 'w'); + a->m = !!strchr(m, 'm'); + + LIST_PREPEND(device_allow, c->device_allow, a); + return 0; +} + +int config_parse_blockio_weight( + 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 *weight = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = cg_blkio_weight_parse(rvalue, weight); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Block IO weight '%s' invalid. Ignoring.", rvalue); + return 0; + } + + return 0; +} + +int config_parse_blockio_device_weight( + 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) { + + _cleanup_free_ char *path = NULL; + CGroupBlockIODeviceWeight *w; + CGroupContext *c = data; + const char *weight; + uint64_t u; + size_t n; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) { + while (c->blockio_device_weights) + cgroup_context_free_blockio_device_weight(c, c->blockio_device_weights); + + return 0; + } + + n = strcspn(rvalue, WHITESPACE); + weight = rvalue + n; + weight += strspn(weight, WHITESPACE); + + if (isempty(weight)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Expected block device and device weight. Ignoring."); + return 0; + } + + path = strndup(rvalue, n); + if (!path) + return log_oom(); + + if (!path_startswith(path, "/dev")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); + return 0; + } + + r = cg_blkio_weight_parse(weight, &u); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Block IO weight '%s' invalid. Ignoring.", weight); + return 0; + } + + assert(u != CGROUP_BLKIO_WEIGHT_INVALID); + + w = new0(CGroupBlockIODeviceWeight, 1); + if (!w) + return log_oom(); + + w->path = path; + path = NULL; + + w->weight = u; + + LIST_PREPEND(device_weights, c->blockio_device_weights, w); + return 0; +} + +int config_parse_blockio_bandwidth( + 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) { + + _cleanup_free_ char *path = NULL; + CGroupBlockIODeviceBandwidth *b; + CGroupContext *c = data; + const char *bandwidth; + uint64_t bytes; + bool read; + size_t n; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + read = streq("BlockIOReadBandwidth", lvalue); + + if (isempty(rvalue)) { + CGroupBlockIODeviceBandwidth *next; + + LIST_FOREACH_SAFE (device_bandwidths, b, next, c->blockio_device_bandwidths) + if (b->read == read) + cgroup_context_free_blockio_device_bandwidth(c, b); + + return 0; + } + + n = strcspn(rvalue, WHITESPACE); + bandwidth = rvalue + n; + bandwidth += strspn(bandwidth, WHITESPACE); + + if (!*bandwidth) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Expected space separated pair of device node and bandwidth. Ignoring."); + return 0; + } + + path = strndup(rvalue, n); + if (!path) + return log_oom(); + + if (!path_startswith(path, "/dev")) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid device node path '%s'. Ignoring.", path); + return 0; + } + + r = parse_size(bandwidth, 1000, &bytes); + if (r < 0 || bytes <= 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Block IO Bandwidth '%s' invalid. Ignoring.", rvalue); + return 0; + } + + b = new0(CGroupBlockIODeviceBandwidth, 1); + if (!b) + return log_oom(); + + b->path = path; + path = NULL; + b->bandwidth = bytes; + b->read = read; + + LIST_PREPEND(device_bandwidths, c->blockio_device_bandwidths, b); + + return 0; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_job_mode, job_mode, JobMode, "Failed to parse job mode"); + +int config_parse_job_mode_isolate( + 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) { + + JobMode *m = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse boolean, ignoring: %s", rvalue); + return 0; + } + + *m = r ? JOB_ISOLATE : JOB_REPLACE; + return 0; +} + +int config_parse_runtime_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) { + + char***rt = data; + Unit *u = userdata; + const char *word, *state; + size_t l; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *rt = strv_free(*rt); + return 0; + } + + FOREACH_WORD_QUOTED(word, l, rvalue, state) { + _cleanup_free_ char *t = NULL, *n = NULL; + + t = strndup(word, l); + if (!t) + return log_oom(); + + r = unit_name_printf(u, t, &n); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to resolve specifiers, ignoring: %m"); + continue; + } + + if (!filename_is_valid(n)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Runtime directory is not valid, ignoring assignment: %s", rvalue); + continue; + } + + r = strv_push(rt, n); + if (r < 0) + return log_oom(); + + n = NULL; + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} + +int config_parse_set_status( + 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) { + + size_t l; + const char *word, *state; + int r; + ExitStatusSet *status_set = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* Empty assignment resets the list */ + if (isempty(rvalue)) { + exit_status_set_free(status_set); + return 0; + } + + FOREACH_WORD(word, l, rvalue, state) { + _cleanup_free_ char *temp; + int val; + Set **set; + + temp = strndup(word, l); + if (!temp) + return log_oom(); + + r = safe_atoi(temp, &val); + if (r < 0) { + val = signal_from_string_try_harder(temp); + + if (val <= 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse value, ignoring: %s", word); + continue; + } + set = &status_set->signal; + } else { + if (val < 0 || val > 255) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Value %d is outside range 0-255, ignoring", val); + continue; + } + set = &status_set->status; + } + + r = set_ensure_allocated(set, NULL); + if (r < 0) + return log_oom(); + + r = set_put(*set, INT_TO_PTR(val)); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Unable to store: %s", word); + return r; + } + } + if (!isempty(state)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} + +int config_parse_namespace_path_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*** sv = data; + const char *prev; + const char *cur; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + *sv = strv_free(*sv); + return 0; + } + + prev = cur = rvalue; + for (;;) { + _cleanup_free_ char *word = NULL; + int offset; + + r = extract_first_word(&cur, &word, NULL, EXTRACT_QUOTES); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Trailing garbage, ignoring: %s", prev); + return 0; + } + + if (!utf8_is_valid(word)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, word); + prev = cur; + continue; + } + + offset = word[0] == '-'; + if (!path_is_absolute(word + offset)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute path, ignoring: %s", word); + prev = cur; + continue; + } + + path_kill_slashes(word + offset); + + r = strv_push(sv, word); + if (r < 0) + return log_oom(); + + prev = cur; + word = NULL; + } + + return 0; +} + +int config_parse_no_new_privileges( + 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) { + + ExecContext *c = data; + int k; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + k = parse_boolean(rvalue); + if (k < 0) { + log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue); + return 0; + } + + c->no_new_privileges = !!k; + c->no_new_privileges_set = true; + + return 0; +} + +int config_parse_protect_home( + 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) { + + ExecContext *c = data; + int k; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* Our enum shall be a superset of booleans, hence first try + * to parse as as boolean, and then as enum */ + + k = parse_boolean(rvalue); + if (k > 0) + c->protect_home = PROTECT_HOME_YES; + else if (k == 0) + c->protect_home = PROTECT_HOME_NO; + else { + ProtectHome h; + + h = protect_home_from_string(rvalue); + if (h < 0){ + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse protect home value, ignoring: %s", rvalue); + return 0; + } + + c->protect_home = h; + } + + return 0; +} + +int config_parse_protect_system( + 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) { + + ExecContext *c = data; + int k; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* Our enum shall be a superset of booleans, hence first try + * to parse as as boolean, and then as enum */ + + k = parse_boolean(rvalue); + if (k > 0) + c->protect_system = PROTECT_SYSTEM_YES; + else if (k == 0) + c->protect_system = PROTECT_SYSTEM_NO; + else { + ProtectSystem s; + + s = protect_system_from_string(rvalue); + if (s < 0){ + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse protect system value, ignoring: %s", rvalue); + return 0; + } + + c->protect_system = s; + } + + return 0; +} + +#define FOLLOW_MAX 8 + +static int open_follow(char **filename, FILE **_f, Set *names, char **_final) { + unsigned c = 0; + int fd, r; + FILE *f; + char *id = NULL; + + assert(filename); + assert(*filename); + assert(_f); + assert(names); + + /* This will update the filename pointer if the loaded file is + * reached by a symlink. The old string will be freed. */ + + for (;;) { + char *target, *name; + + if (c++ >= FOLLOW_MAX) + return -ELOOP; + + path_kill_slashes(*filename); + + /* Add the file name we are currently looking at to + * the names of this unit, but only if it is a valid + * unit name. */ + name = basename(*filename); + + if (unit_name_is_valid(name, UNIT_NAME_ANY)) { + + id = set_get(names, name); + if (!id) { + id = strdup(name); + if (!id) + return -ENOMEM; + + r = set_consume(names, id); + if (r < 0) + return r; + } + } + + /* Try to open the file name, but don't if its a symlink */ + fd = open(*filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd >= 0) + break; + + if (errno != ELOOP) + return -errno; + + /* Hmm, so this is a symlink. Let's read the name, and follow it manually */ + r = readlink_and_make_absolute(*filename, &target); + if (r < 0) + return r; + + free(*filename); + *filename = target; + } + + f = fdopen(fd, "re"); + if (!f) { + safe_close(fd); + return -errno; + } + + *_f = f; + *_final = id; + return 0; +} + +static int merge_by_names(Unit **u, Set *names, const char *id) { + char *k; + int r; + + assert(u); + assert(*u); + assert(names); + + /* Let's try to add in all symlink names we found */ + while ((k = set_steal_first(names))) { + + /* First try to merge in the other name into our + * unit */ + r = unit_merge_by_name(*u, k); + if (r < 0) { + Unit *other; + + /* Hmm, we couldn't merge the other unit into + * ours? Then let's try it the other way + * round */ + + other = manager_get_unit((*u)->manager, k); + free(k); + + if (other) { + r = unit_merge(other, *u); + if (r >= 0) { + *u = other; + return merge_by_names(u, names, NULL); + } + } + + return r; + } + + if (id == k) + unit_choose_id(*u, id); + + free(k); + } + + return 0; +} + +static int load_from_path(Unit *u, const char *path) { + int r; + _cleanup_set_free_free_ Set *symlink_names = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *filename = NULL; + char *id = NULL; + Unit *merged; + struct stat st; + + assert(u); + assert(path); + + symlink_names = set_new(&string_hash_ops); + if (!symlink_names) + return -ENOMEM; + + if (path_is_absolute(path)) { + + filename = strdup(path); + if (!filename) + return -ENOMEM; + + r = open_follow(&filename, &f, symlink_names, &id); + if (r < 0) { + filename = mfree(filename); + if (r != -ENOENT) + return r; + } + + } else { + char **p; + + STRV_FOREACH(p, u->manager->lookup_paths.unit_path) { + + /* Instead of opening the path right away, we manually + * follow all symlinks and add their name to our unit + * name set while doing so */ + filename = path_make_absolute(path, *p); + if (!filename) + return -ENOMEM; + + if (u->manager->unit_path_cache && + !set_get(u->manager->unit_path_cache, filename)) + r = -ENOENT; + else + r = open_follow(&filename, &f, symlink_names, &id); + + if (r < 0) { + filename = mfree(filename); + if (r != -ENOENT) + return r; + + /* Empty the symlink names for the next run */ + set_clear_free(symlink_names); + continue; + } + + break; + } + } + + if (!filename) + /* Hmm, no suitable file found? */ + return 0; + + merged = u; + r = merge_by_names(&merged, symlink_names, id); + if (r < 0) + return r; + + if (merged != u) { + u->load_state = UNIT_MERGED; + return 0; + } + + if (fstat(fileno(f), &st) < 0) + return -errno; + + if (null_or_empty(&st)) + u->load_state = UNIT_MASKED; + else { + u->load_state = UNIT_LOADED; + + /* Now, parse the file contents */ + r = config_parse(u->id, filename, f, + UNIT_VTABLE(u)->sections, + config_item_perf_lookup, load_fragment_gperf_lookup, + false, true, false, u); + if (r < 0) + return r; + } + + free(u->fragment_path); + u->fragment_path = filename; + filename = NULL; + + u->fragment_mtime = timespec_load(&st.st_mtim); + + if (u->source_path) { + if (stat(u->source_path, &st) >= 0) + u->source_mtime = timespec_load(&st.st_mtim); + else + u->source_mtime = 0; + } + + return 0; +} + +int unit_load_fragment(Unit *u) { + int r; + Iterator i; + const char *t; + + assert(u); + assert(u->load_state == UNIT_STUB); + assert(u->id); + + if (u->transient) { + u->load_state = UNIT_LOADED; + return 0; + } + + /* First, try to find the unit under its id. We always look + * for unit files in the default directories, to make it easy + * to override things by placing things in /etc/systemd/system */ + r = load_from_path(u, u->id); + if (r < 0) + return r; + + /* Try to find an alias we can load this with */ + if (u->load_state == UNIT_STUB) { + SET_FOREACH(t, u->names, i) { + + if (t == u->id) + continue; + + r = load_from_path(u, t); + if (r < 0) + return r; + + if (u->load_state != UNIT_STUB) + break; + } + } + + /* And now, try looking for it under the suggested (originally linked) path */ + if (u->load_state == UNIT_STUB && u->fragment_path) { + + r = load_from_path(u, u->fragment_path); + if (r < 0) + return r; + + if (u->load_state == UNIT_STUB) + /* Hmm, this didn't work? Then let's get rid + * of the fragment path stored for us, so that + * we don't point to an invalid location. */ + u->fragment_path = mfree(u->fragment_path); + } + + /* Look for a template */ + if (u->load_state == UNIT_STUB && u->instance) { + _cleanup_free_ char *k = NULL; + + r = unit_name_template(u->id, &k); + if (r < 0) + return r; + + r = load_from_path(u, k); + if (r < 0) + return r; + + if (u->load_state == UNIT_STUB) { + SET_FOREACH(t, u->names, i) { + _cleanup_free_ char *z = NULL; + + if (t == u->id) + continue; + + r = unit_name_template(t, &z); + if (r < 0) + return r; + + r = load_from_path(u, z); + if (r < 0) + return r; + + if (u->load_state != UNIT_STUB) + break; + } + } + } + + return 0; +} + +void unit_dump_config_items(FILE *f) { + static const struct { + const ConfigParserCallback callback; + const char *rvalue; + } table[] = { +#if !defined(HAVE_SYSV_COMPAT) || !defined(HAVE_SECCOMP) || !defined(HAVE_PAM) || !defined(HAVE_SELINUX) || !defined(HAVE_SMACK) || !defined(HAVE_APPARMOR) + { config_parse_warn_compat, "NOTSUPPORTED" }, +#endif + { config_parse_int, "INTEGER" }, + { config_parse_unsigned, "UNSIGNED" }, + { config_parse_iec_size, "SIZE" }, + { config_parse_iec_uint64, "SIZE" }, + { config_parse_si_size, "SIZE" }, + { config_parse_bool, "BOOLEAN" }, + { config_parse_string, "STRING" }, + { config_parse_path, "PATH" }, + { config_parse_unit_path_printf, "PATH" }, + { config_parse_strv, "STRING [...]" }, + { config_parse_exec_nice, "NICE" }, + { config_parse_exec_oom_score_adjust, "OOMSCOREADJUST" }, + { config_parse_exec_io_class, "IOCLASS" }, + { config_parse_exec_io_priority, "IOPRIORITY" }, + { config_parse_exec_cpu_sched_policy, "CPUSCHEDPOLICY" }, + { config_parse_exec_cpu_sched_prio, "CPUSCHEDPRIO" }, + { config_parse_exec_cpu_affinity, "CPUAFFINITY" }, + { config_parse_mode, "MODE" }, + { config_parse_unit_env_file, "FILE" }, + { config_parse_output, "OUTPUT" }, + { config_parse_input, "INPUT" }, + { config_parse_log_facility, "FACILITY" }, + { config_parse_log_level, "LEVEL" }, + { config_parse_exec_capabilities, "CAPABILITIES" }, + { config_parse_exec_secure_bits, "SECUREBITS" }, + { config_parse_capability_set, "BOUNDINGSET" }, + { config_parse_limit, "LIMIT" }, + { config_parse_unit_deps, "UNIT [...]" }, + { config_parse_exec, "PATH [ARGUMENT [...]]" }, + { config_parse_service_type, "SERVICETYPE" }, + { config_parse_service_restart, "SERVICERESTART" }, +#ifdef HAVE_SYSV_COMPAT + { config_parse_sysv_priority, "SYSVPRIORITY" }, +#endif + { config_parse_kill_mode, "KILLMODE" }, + { config_parse_signal, "SIGNAL" }, + { config_parse_socket_listen, "SOCKET [...]" }, + { config_parse_socket_bind, "SOCKETBIND" }, + { config_parse_socket_bindtodevice, "NETWORKINTERFACE" }, + { config_parse_sec, "SECONDS" }, + { config_parse_nsec, "NANOSECONDS" }, + { config_parse_namespace_path_strv, "PATH [...]" }, + { config_parse_unit_requires_mounts_for, "PATH [...]" }, + { config_parse_exec_mount_flags, "MOUNTFLAG [...]" }, + { config_parse_unit_string_printf, "STRING" }, + { config_parse_trigger_unit, "UNIT" }, + { config_parse_timer, "TIMER" }, + { config_parse_path_spec, "PATH" }, + { config_parse_notify_access, "ACCESS" }, + { config_parse_ip_tos, "TOS" }, + { config_parse_unit_condition_path, "CONDITION" }, + { config_parse_unit_condition_string, "CONDITION" }, + { config_parse_unit_condition_null, "CONDITION" }, + { config_parse_unit_slice, "SLICE" }, + { config_parse_documentation, "URL" }, + { config_parse_service_timeout, "SECONDS" }, + { config_parse_failure_action, "ACTION" }, + { config_parse_set_status, "STATUS" }, + { config_parse_service_sockets, "SOCKETS" }, + { config_parse_environ, "ENVIRON" }, +#ifdef HAVE_SECCOMP + { config_parse_syscall_filter, "SYSCALLS" }, + { config_parse_syscall_archs, "ARCHS" }, + { config_parse_syscall_errno, "ERRNO" }, + { config_parse_address_families, "FAMILIES" }, +#endif + { config_parse_cpu_shares, "SHARES" }, + { config_parse_memory_limit, "LIMIT" }, + { config_parse_device_allow, "DEVICE" }, + { config_parse_device_policy, "POLICY" }, + { config_parse_blockio_bandwidth, "BANDWIDTH" }, + { config_parse_blockio_weight, "WEIGHT" }, + { config_parse_blockio_device_weight, "DEVICEWEIGHT" }, + { config_parse_long, "LONG" }, + { config_parse_socket_service, "SERVICE" }, +#ifdef HAVE_SELINUX + { config_parse_exec_selinux_context, "LABEL" }, +#endif + { config_parse_job_mode, "MODE" }, + { config_parse_job_mode_isolate, "BOOLEAN" }, + { config_parse_personality, "PERSONALITY" }, + }; + + const char *prev = NULL; + const char *i; + + assert(f); + + NULSTR_FOREACH(i, load_fragment_gperf_nulstr) { + const char *rvalue = "OTHER", *lvalue; + unsigned j; + size_t prefix_len; + const char *dot; + const ConfigPerfItem *p; + + assert_se(p = load_fragment_gperf_lookup(i, strlen(i))); + + dot = strchr(i, '.'); + lvalue = dot ? dot + 1 : i; + prefix_len = dot-i; + + if (dot) + if (!prev || !strneq(prev, i, prefix_len+1)) { + if (prev) + fputc('\n', f); + + fprintf(f, "[%.*s]\n", (int) prefix_len, i); + } + + for (j = 0; j < ELEMENTSOF(table); j++) + if (p->parse == table[j].callback) { + rvalue = table[j].rvalue; + break; + } + + fprintf(f, "%s=%s\n", lvalue, rvalue); + prev = i; + } +} diff --git a/src/libcore/load-fragment.h b/src/libcore/load-fragment.h new file mode 100644 index 0000000000..5fb5910919 --- /dev/null +++ b/src/libcore/load-fragment.h @@ -0,0 +1,122 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "unit.h" + +/* Read service data from .desktop file style configuration fragments */ + +int unit_load_fragment(Unit *u); + +void unit_dump_config_items(FILE *f); + +int config_parse_warn_compat(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_unit_deps(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_obsolete_unit_deps(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_unit_string_printf(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_unit_strv_printf(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_unit_path_printf(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_unit_path_strv_printf(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_documentation(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_socket_listen(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_socket_protocol(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_socket_bind(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_exec_nice(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_exec_oom_score_adjust(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_exec(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_service_timeout(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_service_type(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_service_restart(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_socket_bindtodevice(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_output(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_input(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_exec_io_class(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_exec_io_priority(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_exec_cpu_sched_policy(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_exec_cpu_sched_prio(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_exec_cpu_affinity(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_exec_capabilities(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_exec_secure_bits(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_capability_set(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_limit(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_sysv_priority(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_kill_signal(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_exec_mount_flags(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_timer(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_trigger_unit(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_path_spec(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_socket_service(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_service_sockets(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_busname_service(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_bus_policy(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_bus_policy_world(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_bus_endpoint_policy(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_unit_env_file(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_ip_tos(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_unit_condition_path(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_unit_condition_string(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_unit_condition_null(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_kill_mode(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_notify_access(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_failure_action(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_unit_requires_mounts_for(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_syscall_filter(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_syscall_archs(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_syscall_errno(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_environ(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_pass_environ(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_unit_slice(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_cpu_shares(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_memory_limit(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_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 config_parse_device_policy(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_device_allow(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_blockio_weight(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_blockio_device_weight(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_blockio_bandwidth(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_netclass(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_job_mode(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_job_mode_isolate(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_exec_selinux_context(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_exec_apparmor_profile(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_exec_smack_process_label(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_address_families(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_runtime_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_set_status(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_namespace_path_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); +int config_parse_no_new_privileges(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_cpu_quota(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_protect_home(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_protect_system(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_bus_name(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_exec_utmp_mode(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_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); + +/* gperf prototypes */ +const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length); +extern const char load_fragment_gperf_nulstr[]; + +typedef enum Disabled { + DISABLED_CONFIGURATION, + DISABLED_LEGACY, + DISABLED_EXPERIMENTAL, +} Disabled; diff --git a/src/libcore/locale-setup.c b/src/libcore/locale-setup.c new file mode 100644 index 0000000000..ccf61d29fb --- /dev/null +++ b/src/libcore/locale-setup.c @@ -0,0 +1,124 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "env-util.h" +#include "fileio.h" +#include "locale-setup.h" +#include "locale-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" +#include "virt.h" + +int locale_setup(char ***environment) { + char **add; + char *variables[_VARIABLE_LC_MAX] = {}; + int r = 0, i; + + if (detect_container() <= 0) { + r = parse_env_file("/proc/cmdline", WHITESPACE, + "locale.LANG", &variables[VARIABLE_LANG], + "locale.LANGUAGE", &variables[VARIABLE_LANGUAGE], + "locale.LC_CTYPE", &variables[VARIABLE_LC_CTYPE], + "locale.LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], + "locale.LC_TIME", &variables[VARIABLE_LC_TIME], + "locale.LC_COLLATE", &variables[VARIABLE_LC_COLLATE], + "locale.LC_MONETARY", &variables[VARIABLE_LC_MONETARY], + "locale.LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], + "locale.LC_PAPER", &variables[VARIABLE_LC_PAPER], + "locale.LC_NAME", &variables[VARIABLE_LC_NAME], + "locale.LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], + "locale.LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], + "locale.LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], + "locale.LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], + NULL); + + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read /proc/cmdline: %m"); + } + + /* Hmm, nothing set on the kernel cmd line? Then let's + * try /etc/locale.conf */ + if (r <= 0) { + r = parse_env_file("/etc/locale.conf", NEWLINE, + "LANG", &variables[VARIABLE_LANG], + "LANGUAGE", &variables[VARIABLE_LANGUAGE], + "LC_CTYPE", &variables[VARIABLE_LC_CTYPE], + "LC_NUMERIC", &variables[VARIABLE_LC_NUMERIC], + "LC_TIME", &variables[VARIABLE_LC_TIME], + "LC_COLLATE", &variables[VARIABLE_LC_COLLATE], + "LC_MONETARY", &variables[VARIABLE_LC_MONETARY], + "LC_MESSAGES", &variables[VARIABLE_LC_MESSAGES], + "LC_PAPER", &variables[VARIABLE_LC_PAPER], + "LC_NAME", &variables[VARIABLE_LC_NAME], + "LC_ADDRESS", &variables[VARIABLE_LC_ADDRESS], + "LC_TELEPHONE", &variables[VARIABLE_LC_TELEPHONE], + "LC_MEASUREMENT", &variables[VARIABLE_LC_MEASUREMENT], + "LC_IDENTIFICATION", &variables[VARIABLE_LC_IDENTIFICATION], + NULL); + + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read /etc/locale.conf: %m"); + } + + add = NULL; + for (i = 0; i < _VARIABLE_LC_MAX; i++) { + char *s; + + if (!variables[i]) + continue; + + s = strjoin(locale_variable_to_string(i), "=", variables[i], NULL); + if (!s) { + r = -ENOMEM; + goto finish; + } + + if (strv_consume(&add, s) < 0) { + r = -ENOMEM; + goto finish; + } + } + + if (!strv_isempty(add)) { + char **e; + + e = strv_env_merge(2, *environment, add); + if (!e) { + r = -ENOMEM; + goto finish; + } + + strv_free(*environment); + *environment = e; + } + + r = 0; + +finish: + strv_free(add); + + for (i = 0; i < _VARIABLE_LC_MAX; i++) + free(variables[i]); + + return r; +} diff --git a/src/libcore/locale-setup.h b/src/libcore/locale-setup.h new file mode 100644 index 0000000000..3b97497afe --- /dev/null +++ b/src/libcore/locale-setup.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int locale_setup(char ***environment); diff --git a/src/libcore/loopback-setup.c b/src/libcore/loopback-setup.c new file mode 100644 index 0000000000..d56bbfa6fc --- /dev/null +++ b/src/libcore/loopback-setup.c @@ -0,0 +1,90 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include + +#include "loopback-setup.h" +#include "missing.h" +#include "netlink-util.h" + +static int start_loopback(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + int r; + + r = sd_rtnl_message_new_link(rtnl, &req, RTM_SETLINK, LOOPBACK_IFINDEX); + if (r < 0) + return r; + + r = sd_rtnl_message_link_set_flags(req, IFF_UP, IFF_UP); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, req, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +static bool check_loopback(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + unsigned flags; + int r; + + r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, LOOPBACK_IFINDEX); + if (r < 0) + return false; + + r = sd_netlink_call(rtnl, req, 0, &reply); + if (r < 0) + return false; + + r = sd_rtnl_message_link_get_flags(reply, &flags); + if (r < 0) + return false; + + return flags & IFF_UP; +} + +int loopback_setup(void) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int r; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return r; + + r = start_loopback(rtnl); + if (r < 0) { + + /* If we lack the permissions to configure the + * loopback device, but we find it to be already + * configured, let's exit cleanly, in order to + * supported unprivileged containers. */ + if (r == -EPERM && check_loopback(rtnl)) + return 0; + + return log_warning_errno(r, "Failed to configure loopback device: %m"); + } + + return 0; +} diff --git a/src/libcore/loopback-setup.h b/src/libcore/loopback-setup.h new file mode 100644 index 0000000000..e7547b8a26 --- /dev/null +++ b/src/libcore/loopback-setup.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int loopback_setup(void); diff --git a/src/libcore/machine-id-setup.c b/src/libcore/machine-id-setup.c new file mode 100644 index 0000000000..30f27ffc67 --- /dev/null +++ b/src/libcore/machine-id-setup.c @@ -0,0 +1,375 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hexdecoct.h" +#include "io-util.h" +#include "log.h" +#include "machine-id-setup.h" +#include "macro.h" +#include "mkdir.h" +#include "mount-util.h" +#include "path-util.h" +#include "process-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "umask-util.h" +#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, char id[34]) { + assert(fd >= 0); + assert(id); + + if (lseek(fd, 0, SEEK_SET) < 0) + return -errno; + + return loop_write(fd, id, 33, false); +} + +static int generate_machine_id(char id[34], const char *root) { + int fd, r; + unsigned char *p; + sd_id128_t buf; + char *q; + const char *dbus_machine_id; + + assert(id); + + if (isempty(root)) + dbus_machine_id = "/var/lib/dbus/machine-id"; + else + dbus_machine_id = strjoina(root, "/var/lib/dbus/machine-id"); + + /* First, try reading the D-Bus machine id, unless it is a symlink */ + 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) { + log_info("Initializing machine ID from D-Bus machine ID."); + return 0; + } + } + + if (isempty(root)) { + /* If that didn't work, see if we are running in a container, + * and a machine ID was passed in via $container_uuid the way + * libvirt/LXC does it */ + + 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; + } + } + + } else if (detect_vm() == VIRTUALIZATION_KVM) { + + /* If we are not running in a container, see if we are + * 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 that didn't work, generate a random machine id */ + r = sd_id128_randomize(&buf); + 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; + + log_info("Initializing machine ID from random generator."); + + return 0; +} + +int machine_id_setup(const char *root, sd_id128_t machine_id) { + const char *etc_machine_id, *run_machine_id; + _cleanup_close_ int fd = -1; + bool writable = true; + char id[34]; /* 32 + \n + \0 */ + int r; + + if (isempty(root)) { + etc_machine_id = "/etc/machine-id"; + run_machine_id = "/run/machine-id"; + } else { + char *x; + + x = strjoina(root, "/etc/machine-id"); + etc_machine_id = path_kill_slashes(x); + + x = strjoina(root, "/run/machine-id"); + run_machine_id = path_kill_slashes(x); + } + + RUN_WITH_UMASK(0000) { + /* We create this 0444, to indicate that this isn't really + * something you should ever modify. Of course, since the file + * will be owned by root it doesn't matter much, but maybe + * people look. */ + + 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; + + fd = open(etc_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (old_errno == EROFS && errno == ENOENT) + log_error_errno(errno, + "System cannot boot: Missing /etc/machine-id and /etc is mounted read-only.\n" + "Booting up is supported only when:\n" + "1) /etc/machine-id exists and is populated.\n" + "2) /etc/machine-id exists and is empty.\n" + "3) /etc/machine-id is missing and /etc is writable.\n"); + else + log_error_errno(errno, "Cannot open %s: %m", etc_machine_id); + + return -errno; + } + + writable = false; + } + } + + /* 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; + + /* Hmm, so, the id currently stored is not useful, then let's + * generate one */ + + r = generate_machine_id(id, root); + if (r < 0) + return r; + } + + if (writable) + if (write_machine_id(fd, id) >= 0) + return 0; + + fd = safe_close(fd); + + /* 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); + } + + /* And now, let's mount it over */ + if (mount(run_machine_id, etc_machine_id, NULL, MS_BIND, NULL) < 0) { + (void) unlink_noerrno(run_machine_id); + return log_error_errno(errno, "Failed to mount %s: %m", etc_machine_id); + } + + log_info("Installed transient %s file.", etc_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); + + return 0; +} + +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 */ + int r; + + if (isempty(root)) + etc_machine_id = "/etc/machine-id"; + else { + char *x; + + x = strjoina(root, "/etc/machine-id"); + etc_machine_id = path_kill_slashes(x); + } + + 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); + return 0; + } + + /* Read existing machine-id */ + fd = open(etc_machine_id, O_RDONLY|O_CLOEXEC|O_NOCTTY); + 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); + if (r == 0) { + log_error("%s is not on a temporary file system.", etc_machine_id); + return -EROFS; + } + + fd = safe_close(fd); + + /* Store current mount namespace */ + r = namespace_open(0, NULL, &initial_mntns_fd, NULL, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Can't fetch current mount namespace: %m"); + + /* Switch to a new mount namespace, isolate ourself and unmount etc_machine_id in our new namespace */ + if (unshare(CLONE_NEWNS) < 0) + return log_error_errno(errno, "Failed to enter new namespace: %m"); + + if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) + return log_error_errno(errno, "Couldn't make-rslave / mountpoint in our private namespace: %m"); + + if (umount(etc_machine_id) < 0) + 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); + if (r < 0) + return log_error_errno(r, "Cannot write %s: %m", etc_machine_id); + + fd = safe_close(fd); + + /* Return to initial namespace and proceed a lazy tmpfs unmount */ + r = namespace_enter(-1, initial_mntns_fd, -1, -1, -1); + if (r < 0) + return log_warning_errno(r, "Failed to switch back to initial mount namespace: %m.\nWe'll keep transient %s file until next reboot.", etc_machine_id); + + if (umount2(etc_machine_id, MNT_DETACH) < 0) + return log_warning_errno(errno, "Failed to unmount transient %s file: %m.\nWe keep that mount until next reboot.", etc_machine_id); + + return 0; +} diff --git a/src/libcore/machine-id-setup.h b/src/libcore/machine-id-setup.h new file mode 100644 index 0000000000..a7e7678ed9 --- /dev/null +++ b/src/libcore/machine-id-setup.h @@ -0,0 +1,23 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +int machine_id_commit(const char *root); +int machine_id_setup(const char *root, sd_id128_t machine_id); diff --git a/src/libcore/manager.c b/src/libcore/manager.c new file mode 100644 index 0000000000..9a34742cf6 --- /dev/null +++ b/src/libcore/manager.c @@ -0,0 +1,3124 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_AUDIT +#include +#endif + +#include +#include + +#include "alloc-util.h" +#include "audit-fd.h" +#include "boot-timestamps.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "bus-kernel.h" +#include "bus-util.h" +#include "dbus-job.h" +#include "dbus-manager.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "env-util.h" +#include "escape.h" +#include "exit-status.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hashmap.h" +#include "io-util.h" +#include "locale-setup.h" +#include "log.h" +#include "macro.h" +#include "manager.h" +#include "missing.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-lookup.h" +#include "path-util.h" +#include "process-util.h" +#include "ratelimit.h" +#include "rm-rf.h" +#include "signal-util.h" +#include "special.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "time-util.h" +#include "transaction.h" +#include "umask-util.h" +#include "unit-name.h" +#include "util.h" +#include "virt.h" +#include "watchdog.h" + +#define NOTIFY_RCVBUF_SIZE (8*1024*1024) + +/* Initial delay and the interval for printing status messages about running jobs */ +#define JOBS_IN_PROGRESS_WAIT_USEC (5*USEC_PER_SEC) +#define JOBS_IN_PROGRESS_PERIOD_USEC (USEC_PER_SEC / 3) +#define JOBS_IN_PROGRESS_PERIOD_DIVISOR 3 + +static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata); +static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata); +static int manager_dispatch_run_queue(sd_event_source *source, void *userdata); +static int manager_run_generators(Manager *m); +static void manager_undo_generators(Manager *m); + +static void manager_watch_jobs_in_progress(Manager *m) { + usec_t next; + int r; + + assert(m); + + if (m->jobs_in_progress_event_source) + return; + + next = now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_WAIT_USEC; + r = sd_event_add_time( + m->event, + &m->jobs_in_progress_event_source, + CLOCK_MONOTONIC, + next, 0, + manager_dispatch_jobs_in_progress, m); + if (r < 0) + return; + + (void) sd_event_source_set_description(m->jobs_in_progress_event_source, "manager-jobs-in-progress"); +} + +#define CYLON_BUFFER_EXTRA (2*(sizeof(ANSI_RED)-1) + sizeof(ANSI_HIGHLIGHT_RED)-1 + 2*(sizeof(ANSI_NORMAL)-1)) + +static void draw_cylon(char buffer[], size_t buflen, unsigned width, unsigned pos) { + char *p = buffer; + + assert(buflen >= CYLON_BUFFER_EXTRA + width + 1); + assert(pos <= width+1); /* 0 or width+1 mean that the center light is behind the corner */ + + if (pos > 1) { + if (pos > 2) + p = mempset(p, ' ', pos-2); + p = stpcpy(p, ANSI_RED); + *p++ = '*'; + } + + if (pos > 0 && pos <= width) { + p = stpcpy(p, ANSI_HIGHLIGHT_RED); + *p++ = '*'; + } + + p = stpcpy(p, ANSI_NORMAL); + + if (pos < width) { + p = stpcpy(p, ANSI_RED); + *p++ = '*'; + if (pos < width-1) + p = mempset(p, ' ', width-1-pos); + strcpy(p, ANSI_NORMAL); + } +} + +void manager_flip_auto_status(Manager *m, bool enable) { + assert(m); + + if (enable) { + if (m->show_status == SHOW_STATUS_AUTO) + manager_set_show_status(m, SHOW_STATUS_TEMPORARY); + } else { + if (m->show_status == SHOW_STATUS_TEMPORARY) + manager_set_show_status(m, SHOW_STATUS_AUTO); + } +} + +static void manager_print_jobs_in_progress(Manager *m) { + _cleanup_free_ char *job_of_n = NULL; + Iterator i; + Job *j; + unsigned counter = 0, print_nr; + char cylon[6 + CYLON_BUFFER_EXTRA + 1]; + unsigned cylon_pos; + char time[FORMAT_TIMESPAN_MAX], limit[FORMAT_TIMESPAN_MAX] = "no limit"; + uint64_t x; + + assert(m); + assert(m->n_running_jobs > 0); + + manager_flip_auto_status(m, true); + + print_nr = (m->jobs_in_progress_iteration / JOBS_IN_PROGRESS_PERIOD_DIVISOR) % m->n_running_jobs; + + HASHMAP_FOREACH(j, m->jobs, i) + if (j->state == JOB_RUNNING && counter++ == print_nr) + break; + + /* m->n_running_jobs must be consistent with the contents of m->jobs, + * so the above loop must have succeeded in finding j. */ + assert(counter == print_nr + 1); + assert(j); + + cylon_pos = m->jobs_in_progress_iteration % 14; + if (cylon_pos >= 8) + cylon_pos = 14 - cylon_pos; + draw_cylon(cylon, sizeof(cylon), 6, cylon_pos); + + m->jobs_in_progress_iteration++; + + if (m->n_running_jobs > 1) { + if (asprintf(&job_of_n, "(%u of %u) ", counter, m->n_running_jobs) < 0) + job_of_n = NULL; + } + + format_timespan(time, sizeof(time), now(CLOCK_MONOTONIC) - j->begin_usec, 1*USEC_PER_SEC); + if (job_get_timeout(j, &x) > 0) + format_timespan(limit, sizeof(limit), x - j->begin_usec, 1*USEC_PER_SEC); + + manager_status_printf(m, STATUS_TYPE_EPHEMERAL, cylon, + "%sA %s job is running for %s (%s / %s)", + strempty(job_of_n), + job_type_to_string(j->type), + unit_description(j->unit), + time, limit); +} + +static int have_ask_password(void) { + _cleanup_closedir_ DIR *dir; + + dir = opendir("/run/systemd/ask-password"); + if (!dir) { + if (errno == ENOENT) + return false; + else + return -errno; + } + + for (;;) { + struct dirent *de; + + errno = 0; + de = readdir(dir); + if (!de && errno > 0) + return -errno; + if (!de) + return false; + + if (startswith(de->d_name, "ask.")) + return true; + } +} + +static int manager_dispatch_ask_password_fd(sd_event_source *source, + int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + + assert(m); + + flush_fd(fd); + + m->have_ask_password = have_ask_password(); + if (m->have_ask_password < 0) + /* Log error but continue. Negative have_ask_password + * is treated as unknown status. */ + log_error_errno(m->have_ask_password, "Failed to list /run/systemd/ask-password: %m"); + + return 0; +} + +static void manager_close_ask_password(Manager *m) { + assert(m); + + m->ask_password_event_source = sd_event_source_unref(m->ask_password_event_source); + m->ask_password_inotify_fd = safe_close(m->ask_password_inotify_fd); + m->have_ask_password = -EINVAL; +} + +static int manager_check_ask_password(Manager *m) { + int r; + + assert(m); + + if (!m->ask_password_event_source) { + assert(m->ask_password_inotify_fd < 0); + + mkdir_p_label("/run/systemd/ask-password", 0755); + + m->ask_password_inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (m->ask_password_inotify_fd < 0) + return log_error_errno(errno, "inotify_init1() failed: %m"); + + if (inotify_add_watch(m->ask_password_inotify_fd, "/run/systemd/ask-password", IN_CREATE|IN_DELETE|IN_MOVE) < 0) { + log_error_errno(errno, "Failed to add watch on /run/systemd/ask-password: %m"); + manager_close_ask_password(m); + return -errno; + } + + r = sd_event_add_io(m->event, &m->ask_password_event_source, + m->ask_password_inotify_fd, EPOLLIN, + manager_dispatch_ask_password_fd, m); + if (r < 0) { + log_error_errno(errno, "Failed to add event source for /run/systemd/ask-password: %m"); + manager_close_ask_password(m); + return -errno; + } + + (void) sd_event_source_set_description(m->ask_password_event_source, "manager-ask-password"); + + /* Queries might have been added meanwhile... */ + manager_dispatch_ask_password_fd(m->ask_password_event_source, + m->ask_password_inotify_fd, EPOLLIN, m); + } + + return m->have_ask_password; +} + +static int manager_watch_idle_pipe(Manager *m) { + int r; + + assert(m); + + if (m->idle_pipe_event_source) + return 0; + + if (m->idle_pipe[2] < 0) + return 0; + + r = sd_event_add_io(m->event, &m->idle_pipe_event_source, m->idle_pipe[2], EPOLLIN, manager_dispatch_idle_pipe_fd, m); + if (r < 0) + return log_error_errno(r, "Failed to watch idle pipe: %m"); + + (void) sd_event_source_set_description(m->idle_pipe_event_source, "manager-idle-pipe"); + + return 0; +} + +static void manager_close_idle_pipe(Manager *m) { + assert(m); + + m->idle_pipe_event_source = sd_event_source_unref(m->idle_pipe_event_source); + + safe_close_pair(m->idle_pipe); + safe_close_pair(m->idle_pipe + 2); +} + +static int manager_setup_time_change(Manager *m) { + int r; + + /* We only care for the cancellation event, hence we set the + * timeout to the latest possible value. */ + struct itimerspec its = { + .it_value.tv_sec = TIME_T_MAX, + }; + + assert(m); + assert_cc(sizeof(time_t) == sizeof(TIME_T_MAX)); + + if (m->test_run) + return 0; + + /* Uses TFD_TIMER_CANCEL_ON_SET to get notifications whenever + * CLOCK_REALTIME makes a jump relative to CLOCK_MONOTONIC */ + + m->time_change_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC); + if (m->time_change_fd < 0) + return log_error_errno(errno, "Failed to create timerfd: %m"); + + if (timerfd_settime(m->time_change_fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) { + log_debug_errno(errno, "Failed to set up TFD_TIMER_CANCEL_ON_SET, ignoring: %m"); + m->time_change_fd = safe_close(m->time_change_fd); + return 0; + } + + r = sd_event_add_io(m->event, &m->time_change_event_source, m->time_change_fd, EPOLLIN, manager_dispatch_time_change_fd, m); + if (r < 0) + return log_error_errno(r, "Failed to create time change event source: %m"); + + (void) sd_event_source_set_description(m->time_change_event_source, "manager-time-change"); + + log_debug("Set up TFD_TIMER_CANCEL_ON_SET timerfd."); + + return 0; +} + +static int enable_special_signals(Manager *m) { + _cleanup_close_ int fd = -1; + + assert(m); + + if (m->test_run) + return 0; + + /* Enable that we get SIGINT on control-alt-del. In containers + * this will fail with EPERM (older) or EINVAL (newer), so + * ignore that. */ + if (reboot(RB_DISABLE_CAD) < 0 && errno != EPERM && errno != EINVAL) + log_warning_errno(errno, "Failed to enable ctrl-alt-del handling: %m"); + + fd = open_terminal("/dev/tty0", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) { + /* Support systems without virtual console */ + if (fd != -ENOENT) + log_warning_errno(errno, "Failed to open /dev/tty0: %m"); + } else { + /* Enable that we get SIGWINCH on kbrequest */ + if (ioctl(fd, KDSIGACCEPT, SIGWINCH) < 0) + log_warning_errno(errno, "Failed to enable kbrequest handling: %m"); + } + + return 0; +} + +static int manager_setup_signals(Manager *m) { + struct sigaction sa = { + .sa_handler = SIG_DFL, + .sa_flags = SA_NOCLDSTOP|SA_RESTART, + }; + sigset_t mask; + int r; + + assert(m); + + assert_se(sigaction(SIGCHLD, &sa, NULL) == 0); + + /* We make liberal use of realtime signals here. On + * Linux/glibc we have 30 of them (with the exception of Linux + * on hppa, see below), between SIGRTMIN+0 ... SIGRTMIN+30 + * (aka SIGRTMAX). */ + + assert_se(sigemptyset(&mask) == 0); + sigset_add_many(&mask, + SIGCHLD, /* Child died */ + SIGTERM, /* Reexecute daemon */ + SIGHUP, /* Reload configuration */ + SIGUSR1, /* systemd/upstart: reconnect to D-Bus */ + SIGUSR2, /* systemd: dump status */ + SIGINT, /* Kernel sends us this on control-alt-del */ + SIGWINCH, /* Kernel sends us this on kbrequest (alt-arrowup) */ + SIGPWR, /* Some kernel drivers and upsd send us this on power failure */ + + SIGRTMIN+0, /* systemd: start default.target */ + SIGRTMIN+1, /* systemd: isolate rescue.target */ + SIGRTMIN+2, /* systemd: isolate emergency.target */ + SIGRTMIN+3, /* systemd: start halt.target */ + SIGRTMIN+4, /* systemd: start poweroff.target */ + SIGRTMIN+5, /* systemd: start reboot.target */ + SIGRTMIN+6, /* systemd: start kexec.target */ + + /* ... space for more special targets ... */ + + SIGRTMIN+13, /* systemd: Immediate halt */ + SIGRTMIN+14, /* systemd: Immediate poweroff */ + SIGRTMIN+15, /* systemd: Immediate reboot */ + SIGRTMIN+16, /* systemd: Immediate kexec */ + + /* ... space for more immediate system state changes ... */ + + SIGRTMIN+20, /* systemd: enable status messages */ + SIGRTMIN+21, /* systemd: disable status messages */ + SIGRTMIN+22, /* systemd: set log level to LOG_DEBUG */ + SIGRTMIN+23, /* systemd: set log level to LOG_INFO */ + SIGRTMIN+24, /* systemd: Immediate exit (--user only) */ + + /* .. one free signal here ... */ + +#if !defined(__hppa64__) && !defined(__hppa__) + /* Apparently Linux on hppa has fewer RT + * signals (SIGRTMAX is SIGRTMIN+25 there), + * hence let's not try to make use of them + * here. Since these commands are accessible + * by different means and only really a safety + * net, the missing functionality on hppa + * shouldn't matter. */ + + SIGRTMIN+26, /* systemd: set log target to journal-or-kmsg */ + SIGRTMIN+27, /* systemd: set log target to console */ + SIGRTMIN+28, /* systemd: set log target to kmsg */ + SIGRTMIN+29, /* systemd: set log target to syslog-or-kmsg (obsolete) */ + + /* ... one free signal here SIGRTMIN+30 ... */ +#endif + -1); + assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0); + + m->signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (m->signal_fd < 0) + return -errno; + + r = sd_event_add_io(m->event, &m->signal_event_source, m->signal_fd, EPOLLIN, manager_dispatch_signal_fd, m); + if (r < 0) + return r; + + (void) sd_event_source_set_description(m->signal_event_source, "manager-signal"); + + /* Process signals a bit earlier than the rest of things, but + * later than notify_fd processing, so that the notify + * processing can still figure out to which process/service a + * message belongs, before we reap the process. */ + r = sd_event_source_set_priority(m->signal_event_source, SD_EVENT_PRIORITY_NORMAL-5); + if (r < 0) + return r; + + if (m->running_as == MANAGER_SYSTEM) + return enable_special_signals(m); + + return 0; +} + +static void manager_clean_environment(Manager *m) { + assert(m); + + /* Let's remove some environment variables that we + * need ourselves to communicate with our clients */ + strv_env_unset_many( + m->environment, + "NOTIFY_SOCKET", + "MAINPID", + "MANAGERPID", + "LISTEN_PID", + "LISTEN_FDS", + "LISTEN_FDNAMES", + "WATCHDOG_PID", + "WATCHDOG_USEC", + NULL); +} + +static int manager_default_environment(Manager *m) { + assert(m); + + if (m->running_as == MANAGER_SYSTEM) { + /* The system manager always starts with a clean + * environment for its children. It does not import + * the kernel or the parents exported variables. + * + * The initial passed environ is untouched to keep + * /proc/self/environ valid; it is used for tagging + * the init process inside containers. */ + m->environment = strv_new("PATH=" DEFAULT_PATH, + NULL); + + /* Import locale variables LC_*= from configuration */ + locale_setup(&m->environment); + } else { + /* The user manager passes its own environment + * along to its children. */ + m->environment = strv_copy(environ); + } + + if (!m->environment) + return -ENOMEM; + + manager_clean_environment(m); + strv_sort(m->environment); + + return 0; +} + + +int manager_new(ManagerRunningAs running_as, bool test_run, Manager **_m) { + + static const char * const unit_log_fields[_MANAGER_RUNNING_AS_MAX] = { + [MANAGER_SYSTEM] = "UNIT=", + [MANAGER_USER] = "USER_UNIT=", + }; + + static const char * const unit_log_format_strings[_MANAGER_RUNNING_AS_MAX] = { + [MANAGER_SYSTEM] = "UNIT=%s", + [MANAGER_USER] = "USER_UNIT=%s", + }; + + Manager *m; + int r; + + assert(_m); + assert(running_as >= 0); + assert(running_as < _MANAGER_RUNNING_AS_MAX); + + m = new0(Manager, 1); + if (!m) + return -ENOMEM; + +#ifdef ENABLE_EFI + if (running_as == MANAGER_SYSTEM && detect_container() <= 0) + boot_timestamps(&m->userspace_timestamp, &m->firmware_timestamp, &m->loader_timestamp); +#endif + + m->running_as = running_as; + 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); + + /* Prepare log fields we can use for structured logging */ + m->unit_log_field = unit_log_fields[running_as]; + m->unit_log_format_string = unit_log_format_strings[running_as]; + + m->idle_pipe[0] = m->idle_pipe[1] = m->idle_pipe[2] = m->idle_pipe[3] = -1; + + m->pin_cgroupfs_fd = m->notify_fd = m->signal_fd = m->time_change_fd = + m->dev_autofs_fd = m->private_listen_fd = m->kdbus_fd = m->cgroup_inotify_fd = -1; + + m->current_job_id = 1; /* start as id #1, so that we can leave #0 around as "null-like" value */ + + m->ask_password_inotify_fd = -1; + m->have_ask_password = -EINVAL; /* we don't know */ + m->first_boot = -1; + + m->test_run = test_run; + + /* Reboot immediately if the user hits C-A-D more often than 7x per 2s */ + RATELIMIT_INIT(m->ctrl_alt_del_ratelimit, 2 * USEC_PER_SEC, 7); + + r = manager_default_environment(m); + if (r < 0) + goto fail; + + r = hashmap_ensure_allocated(&m->units, &string_hash_ops); + if (r < 0) + goto fail; + + r = hashmap_ensure_allocated(&m->jobs, NULL); + if (r < 0) + goto fail; + + r = hashmap_ensure_allocated(&m->cgroup_unit, &string_hash_ops); + if (r < 0) + goto fail; + + r = hashmap_ensure_allocated(&m->watch_bus, &string_hash_ops); + if (r < 0) + goto fail; + + r = sd_event_default(&m->event); + if (r < 0) + goto fail; + + r = sd_event_add_defer(m->event, &m->run_queue_event_source, manager_dispatch_run_queue, m); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(m->run_queue_event_source, SD_EVENT_PRIORITY_IDLE); + if (r < 0) + goto fail; + + r = sd_event_source_set_enabled(m->run_queue_event_source, SD_EVENT_OFF); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(m->run_queue_event_source, "manager-run-queue"); + + r = manager_setup_signals(m); + if (r < 0) + goto fail; + + r = manager_setup_cgroup(m); + if (r < 0) + goto fail; + + r = manager_setup_time_change(m); + if (r < 0) + goto fail; + + m->udev = udev_new(); + if (!m->udev) { + r = -ENOMEM; + goto fail; + } + + /* Note that we set up neither kdbus, nor the notify fd + * here. We do that after deserialization, since they might + * have gotten serialized across the reexec. */ + + m->taint_usr = dir_is_empty("/usr") > 0; + + *_m = m; + return 0; + +fail: + manager_free(m); + return r; +} + +static int manager_setup_notify(Manager *m) { + int r; + + if (m->test_run) + return 0; + + if (m->notify_fd < 0) { + _cleanup_close_ int fd = -1; + union sockaddr_union sa = { + .sa.sa_family = AF_UNIX, + }; + static const int one = 1; + + /* First free all secondary fields */ + m->notify_socket = mfree(m->notify_socket); + m->notify_event_source = sd_event_source_unref(m->notify_event_source); + + 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"); + + fd_inc_rcvbuf(fd, NOTIFY_RCVBUF_SIZE); + + if (m->running_as == MANAGER_SYSTEM) + m->notify_socket = strdup("/run/systemd/notify"); + else { + const char *e; + + e = getenv("XDG_RUNTIME_DIR"); + if (!e) { + log_error_errno(errno, "XDG_RUNTIME_DIR is not set: %m"); + return -EINVAL; + } + + m->notify_socket = strappend(e, "/systemd/notify"); + } + if (!m->notify_socket) + return log_oom(); + + (void) mkdir_parents_label(m->notify_socket, 0755); + (void) unlink(m->notify_socket); + + strncpy(sa.un.sun_path, m->notify_socket, sizeof(sa.un.sun_path)-1); + r = bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)); + if (r < 0) + 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) + return log_error_errno(errno, "SO_PASSCRED failed: %m"); + + m->notify_fd = fd; + fd = -1; + + log_debug("Using notification socket %s", m->notify_socket); + } + + if (!m->notify_event_source) { + r = sd_event_add_io(m->event, &m->notify_event_source, m->notify_fd, EPOLLIN, manager_dispatch_notify_fd, m); + if (r < 0) + return log_error_errno(r, "Failed to allocate notify event source: %m"); + + /* Process signals a bit earlier than SIGCHLD, so that we can + * still identify to which service an exit message belongs */ + r = sd_event_source_set_priority(m->notify_event_source, SD_EVENT_PRIORITY_NORMAL-7); + if (r < 0) + return log_error_errno(r, "Failed to set priority of notify event source: %m"); + + (void) sd_event_source_set_description(m->notify_event_source, "manager-notify"); + } + + 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( + m->running_as == MANAGER_SYSTEM ? "system" : "user", + m->running_as == MANAGER_SYSTEM, &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; + + assert(m); + + if (m->test_run) + return 0; + + try_bus_connect = + m->kdbus_fd >= 0 || + reexecuting || + (m->running_as == MANAGER_USER && getenv("DBUS_SESSION_BUS_ADDRESS")); + + /* Try to connect to the buses, if possible. */ + return bus_init(m, try_bus_connect); +} + +static unsigned manager_dispatch_cleanup_queue(Manager *m) { + Unit *u; + unsigned n = 0; + + assert(m); + + while ((u = m->cleanup_queue)) { + assert(u->in_cleanup_queue); + + unit_free(u); + n++; + } + + return n; +} + +enum { + GC_OFFSET_IN_PATH, /* This one is on the path we were traveling */ + GC_OFFSET_UNSURE, /* No clue */ + GC_OFFSET_GOOD, /* We still need this unit */ + GC_OFFSET_BAD, /* We don't need this unit anymore */ + _GC_OFFSET_MAX +}; + +static void unit_gc_sweep(Unit *u, unsigned gc_marker) { + Iterator i; + Unit *other; + bool is_bad; + + assert(u); + + if (u->gc_marker == gc_marker + GC_OFFSET_GOOD || + u->gc_marker == gc_marker + GC_OFFSET_BAD || + u->gc_marker == gc_marker + GC_OFFSET_IN_PATH) + return; + + if (u->in_cleanup_queue) + goto bad; + + if (unit_check_gc(u)) + goto good; + + u->gc_marker = gc_marker + GC_OFFSET_IN_PATH; + + is_bad = true; + + SET_FOREACH(other, u->dependencies[UNIT_REFERENCED_BY], i) { + unit_gc_sweep(other, gc_marker); + + if (other->gc_marker == gc_marker + GC_OFFSET_GOOD) + goto good; + + if (other->gc_marker != gc_marker + GC_OFFSET_BAD) + is_bad = false; + } + + if (is_bad) + goto bad; + + /* We were unable to find anything out about this entry, so + * let's investigate it later */ + u->gc_marker = gc_marker + GC_OFFSET_UNSURE; + unit_add_to_gc_queue(u); + return; + +bad: + /* We definitely know that this one is not useful anymore, so + * let's mark it for deletion */ + u->gc_marker = gc_marker + GC_OFFSET_BAD; + unit_add_to_cleanup_queue(u); + return; + +good: + u->gc_marker = gc_marker + GC_OFFSET_GOOD; +} + +static unsigned manager_dispatch_gc_queue(Manager *m) { + Unit *u; + unsigned n = 0; + unsigned gc_marker; + + assert(m); + + /* log_debug("Running GC..."); */ + + m->gc_marker += _GC_OFFSET_MAX; + if (m->gc_marker + _GC_OFFSET_MAX <= _GC_OFFSET_MAX) + m->gc_marker = 1; + + gc_marker = m->gc_marker; + + while ((u = m->gc_queue)) { + assert(u->in_gc_queue); + + unit_gc_sweep(u, gc_marker); + + LIST_REMOVE(gc_queue, m->gc_queue, u); + u->in_gc_queue = false; + + n++; + + if (u->gc_marker == gc_marker + GC_OFFSET_BAD || + u->gc_marker == gc_marker + GC_OFFSET_UNSURE) { + if (u->id) + log_unit_debug(u, "Collecting."); + u->gc_marker = gc_marker + GC_OFFSET_BAD; + unit_add_to_cleanup_queue(u); + } + } + + m->n_in_gc_queue = 0; + + return n; +} + +static void manager_clear_jobs_and_units(Manager *m) { + Unit *u; + + assert(m); + + while ((u = hashmap_first(m->units))) + unit_free(u); + + manager_dispatch_cleanup_queue(m); + + assert(!m->load_queue); + assert(!m->run_queue); + assert(!m->dbus_unit_queue); + assert(!m->dbus_job_queue); + assert(!m->cleanup_queue); + assert(!m->gc_queue); + + assert(hashmap_isempty(m->jobs)); + assert(hashmap_isempty(m->units)); + + m->n_on_console = 0; + m->n_running_jobs = 0; +} + +Manager* manager_free(Manager *m) { + UnitType c; + int i; + + if (!m) + return NULL; + + manager_clear_jobs_and_units(m); + + for (c = 0; c < _UNIT_TYPE_MAX; c++) + if (unit_vtable[c]->shutdown) + unit_vtable[c]->shutdown(m); + + /* If we reexecute ourselves, we keep the root cgroup + * around */ + manager_shutdown_cgroup(m, m->exit_code != MANAGER_REEXECUTE); + + manager_undo_generators(m); + + bus_done(m); + + hashmap_free(m->units); + hashmap_free(m->jobs); + hashmap_free(m->watch_pids1); + hashmap_free(m->watch_pids2); + hashmap_free(m->watch_bus); + + set_free(m->startup_units); + set_free(m->failed_units); + + sd_event_source_unref(m->signal_event_source); + sd_event_source_unref(m->notify_event_source); + sd_event_source_unref(m->time_change_event_source); + sd_event_source_unref(m->jobs_in_progress_event_source); + sd_event_source_unref(m->run_queue_event_source); + + safe_close(m->signal_fd); + safe_close(m->notify_fd); + safe_close(m->time_change_fd); + safe_close(m->kdbus_fd); + + manager_close_ask_password(m); + + manager_close_idle_pipe(m); + + udev_unref(m->udev); + sd_event_unref(m->event); + + free(m->notify_socket); + + lookup_paths_free(&m->lookup_paths); + strv_free(m->environment); + + hashmap_free(m->cgroup_unit); + set_free_free(m->unit_path_cache); + + free(m->switch_root); + free(m->switch_root_init); + + for (i = 0; i < _RLIMIT_MAX; i++) + m->rlimit[i] = mfree(m->rlimit[i]); + + assert(hashmap_isempty(m->units_requiring_mounts_for)); + hashmap_free(m->units_requiring_mounts_for); + + free(m); + return NULL; +} + +void manager_enumerate(Manager *m) { + UnitType c; + + assert(m); + + /* Let's ask every type to load all units from disk/kernel + * that it might know */ + for (c = 0; c < _UNIT_TYPE_MAX; c++) { + if (!unit_type_supported(c)) { + log_debug("Unit type .%s is not supported on this system.", unit_type_to_string(c)); + continue; + } + + if (!unit_vtable[c]->enumerate) + continue; + + unit_vtable[c]->enumerate(m); + } + + manager_dispatch_load_queue(m); +} + +static void manager_coldplug(Manager *m) { + Iterator i; + Unit *u; + char *k; + int r; + + assert(m); + + /* Then, let's set up their initial state. */ + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + + /* ignore aliases */ + if (u->id != k) + continue; + + r = unit_coldplug(u); + if (r < 0) + log_warning_errno(r, "We couldn't coldplug %s, proceeding anyway: %m", u->id); + } +} + +static void manager_build_unit_path_cache(Manager *m) { + char **i; + _cleanup_closedir_ DIR *d = NULL; + int r; + + assert(m); + + set_free_free(m->unit_path_cache); + + m->unit_path_cache = set_new(&string_hash_ops); + if (!m->unit_path_cache) { + log_error("Failed to allocate unit path cache."); + return; + } + + /* This simply builds a list of files we know exist, so that + * we don't always have to go to disk */ + + STRV_FOREACH(i, m->lookup_paths.unit_path) { + struct dirent *de; + + d = opendir(*i); + if (!d) { + if (errno != ENOENT) + log_error_errno(errno, "Failed to open directory %s: %m", *i); + continue; + } + + while ((de = readdir(d))) { + char *p; + + if (hidden_file(de->d_name)) + continue; + + p = strjoin(streq(*i, "/") ? "" : *i, "/", de->d_name, NULL); + if (!p) { + r = -ENOMEM; + goto fail; + } + + r = set_consume(m->unit_path_cache, p); + if (r < 0) + goto fail; + } + + d = safe_closedir(d); + } + + return; + +fail: + log_error_errno(r, "Failed to build unit path cache: %m"); + + set_free_free(m->unit_path_cache); + m->unit_path_cache = NULL; +} + + +static void manager_distribute_fds(Manager *m, FDSet *fds) { + Iterator i; + Unit *u; + + assert(m); + + HASHMAP_FOREACH(u, m->units, i) { + + if (fdset_size(fds) <= 0) + break; + + if (!UNIT_VTABLE(u)->distribute_fds) + continue; + + UNIT_VTABLE(u)->distribute_fds(u, fds); + } +} + +int manager_startup(Manager *m, FILE *serialization, FDSet *fds) { + int r, q; + + assert(m); + + dual_timestamp_get(&m->generators_start_timestamp); + r = manager_run_generators(m); + dual_timestamp_get(&m->generators_finish_timestamp); + if (r < 0) + return r; + + r = lookup_paths_init( + &m->lookup_paths, m->running_as, true, + NULL, + m->generator_unit_path, + m->generator_unit_path_early, + m->generator_unit_path_late); + if (r < 0) + return r; + + manager_build_unit_path_cache(m); + + /* If we will deserialize make sure that during enumeration + * this is already known, so we increase the counter here + * already */ + if (serialization) + m->n_reloading ++; + + /* First, enumerate what we can from all config files */ + dual_timestamp_get(&m->units_load_start_timestamp); + manager_enumerate(m); + dual_timestamp_get(&m->units_load_finish_timestamp); + + /* Second, deserialize if there is something to deserialize */ + if (serialization) + r = manager_deserialize(m, serialization, fds); + + /* Any fds left? Find some unit which wants them. This is + * useful to allow container managers to pass some file + * descriptors to us pre-initialized. This enables + * socket-based activation of entire containers. */ + manager_distribute_fds(m, fds); + + /* We might have deserialized the notify fd, but if we didn't + * then let's create the bus now */ + q = manager_setup_notify(m); + if (q < 0 && r == 0) + r = q; + + /* 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); + + if (serialization) { + assert(m->n_reloading > 0); + m->n_reloading --; + + /* Let's wait for the UnitNew/JobNew messages being + * sent, before we notify that the reload is + * finished */ + m->send_reloading_done = true; + } + + return r; +} + +int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_error *e, Job **_ret) { + int r; + Transaction *tr; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(unit); + assert(mode < _JOB_MODE_MAX); + + if (mode == JOB_ISOLATE && type != JOB_START) + return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Isolate is only valid for start."); + + if (mode == JOB_ISOLATE && !unit->allow_isolate) + return sd_bus_error_setf(e, BUS_ERROR_NO_ISOLATION, "Operation refused, unit may not be isolated."); + + log_unit_debug(unit, "Trying to enqueue job %s/%s/%s", unit->id, job_type_to_string(type), job_mode_to_string(mode)); + + type = job_type_collapse(type, unit); + + tr = transaction_new(mode == JOB_REPLACE_IRREVERSIBLY); + if (!tr) + return -ENOMEM; + + r = transaction_add_job_and_dependencies(tr, type, unit, NULL, true, false, + mode == JOB_IGNORE_DEPENDENCIES || mode == JOB_IGNORE_REQUIREMENTS, + mode == JOB_IGNORE_DEPENDENCIES, e); + if (r < 0) + goto tr_abort; + + if (mode == JOB_ISOLATE) { + r = transaction_add_isolate_jobs(tr, m); + if (r < 0) + goto tr_abort; + } + + r = transaction_activate(tr, m, mode, e); + if (r < 0) + goto tr_abort; + + log_unit_debug(unit, + "Enqueued job %s/%s as %u", unit->id, + job_type_to_string(type), (unsigned) tr->anchor_job->id); + + if (_ret) + *_ret = tr->anchor_job; + + transaction_free(tr); + return 0; + +tr_abort: + transaction_abort(tr); + transaction_free(tr); + return r; +} + +int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, sd_bus_error *e, Job **ret) { + Unit *unit; + int r; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(name); + assert(mode < _JOB_MODE_MAX); + + r = manager_load_unit(m, name, NULL, NULL, &unit); + if (r < 0) + return r; + + return manager_add_job(m, type, unit, mode, e, ret); +} + +int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Job **ret) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(m); + assert(type < _JOB_TYPE_MAX); + assert(name); + assert(mode < _JOB_MODE_MAX); + + r = manager_add_job_by_name(m, type, name, mode, &error, ret); + if (r < 0) + return log_warning_errno(r, "Failed to enqueue %s job for %s: %s", job_mode_to_string(mode), name, bus_error_message(&error, r)); + + return r; +} + +Job *manager_get_job(Manager *m, uint32_t id) { + assert(m); + + return hashmap_get(m->jobs, UINT32_TO_PTR(id)); +} + +Unit *manager_get_unit(Manager *m, const char *name) { + assert(m); + assert(name); + + return hashmap_get(m->units, name); +} + +unsigned manager_dispatch_load_queue(Manager *m) { + Unit *u; + unsigned n = 0; + + assert(m); + + /* Make sure we are not run recursively */ + if (m->dispatching_load_queue) + return 0; + + m->dispatching_load_queue = true; + + /* Dispatches the load queue. Takes a unit from the queue and + * tries to load its data until the queue is empty */ + + while ((u = m->load_queue)) { + assert(u->in_load_queue); + + unit_load(u); + n++; + } + + m->dispatching_load_queue = false; + return n; +} + +int manager_load_unit_prepare( + Manager *m, + const char *name, + const char *path, + sd_bus_error *e, + Unit **_ret) { + + Unit *ret; + UnitType t; + int r; + + assert(m); + assert(name || path); + + /* This will prepare the unit for loading, but not actually + * load anything from disk. */ + + if (path && !is_path(path)) + return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Path %s is not absolute.", path); + + if (!name) + name = basename(path); + + t = unit_name_to_type(name); + + if (t == _UNIT_TYPE_INVALID || !unit_name_is_valid(name, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) + return sd_bus_error_setf(e, SD_BUS_ERROR_INVALID_ARGS, "Unit name %s is not valid.", name); + + ret = manager_get_unit(m, name); + if (ret) { + *_ret = ret; + return 1; + } + + ret = unit_new(m, unit_vtable[t]->object_size); + if (!ret) + return -ENOMEM; + + if (path) { + ret->fragment_path = strdup(path); + if (!ret->fragment_path) { + unit_free(ret); + return -ENOMEM; + } + } + + r = unit_add_name(ret, name); + if (r < 0) { + unit_free(ret); + return r; + } + + unit_add_to_load_queue(ret); + unit_add_to_dbus_queue(ret); + unit_add_to_gc_queue(ret); + + if (_ret) + *_ret = ret; + + return 0; +} + +int manager_load_unit( + Manager *m, + const char *name, + const char *path, + sd_bus_error *e, + Unit **_ret) { + + int r; + + assert(m); + + /* This will load the service information files, but not actually + * start any services or anything. */ + + r = manager_load_unit_prepare(m, name, path, e, _ret); + if (r != 0) + return r; + + manager_dispatch_load_queue(m); + + if (_ret) + *_ret = unit_follow_merge(*_ret); + + return 0; +} + +void manager_dump_jobs(Manager *s, FILE *f, const char *prefix) { + Iterator i; + Job *j; + + assert(s); + assert(f); + + HASHMAP_FOREACH(j, s->jobs, i) + job_dump(j, f, prefix); +} + +void manager_dump_units(Manager *s, FILE *f, const char *prefix) { + Iterator i; + Unit *u; + const char *t; + + assert(s); + assert(f); + + HASHMAP_FOREACH_KEY(u, t, s->units, i) + if (u->id == t) + unit_dump(u, f, prefix); +} + +void manager_clear_jobs(Manager *m) { + Job *j; + + assert(m); + + while ((j = hashmap_first(m->jobs))) + /* No need to recurse. We're cancelling all jobs. */ + job_finish_and_invalidate(j, JOB_CANCELED, false); +} + +static int manager_dispatch_run_queue(sd_event_source *source, void *userdata) { + Manager *m = userdata; + Job *j; + + assert(source); + assert(m); + + while ((j = m->run_queue)) { + assert(j->installed); + assert(j->in_run_queue); + + job_run_and_invalidate(j); + } + + if (m->n_running_jobs > 0) + manager_watch_jobs_in_progress(m); + + if (m->n_on_console > 0) + manager_watch_idle_pipe(m); + + return 1; +} + +static unsigned manager_dispatch_dbus_queue(Manager *m) { + Job *j; + Unit *u; + unsigned n = 0; + + assert(m); + + if (m->dispatching_dbus_queue) + return 0; + + m->dispatching_dbus_queue = true; + + while ((u = m->dbus_unit_queue)) { + assert(u->in_dbus_queue); + + bus_unit_send_change_signal(u); + n++; + } + + while ((j = m->dbus_job_queue)) { + assert(j->in_dbus_queue); + + bus_job_send_change_signal(j); + n++; + } + + m->dispatching_dbus_queue = false; + + if (m->send_reloading_done) { + m->send_reloading_done = false; + + bus_manager_send_reloading(m, false); + } + + if (m->queued_message) + bus_send_queued_message(m); + + return n; +} + +static void manager_invoke_notify_message(Manager *m, Unit *u, pid_t pid, const char *buf, size_t n, FDSet *fds) { + _cleanup_strv_free_ char **tags = NULL; + + assert(m); + assert(u); + assert(buf); + assert(n > 0); + + tags = strv_split(buf, "\n\r"); + if (!tags) { + log_oom(); + return; + } + + if (UNIT_VTABLE(u)->notify_message) + UNIT_VTABLE(u)->notify_message(u, pid, tags, fds); + else + log_unit_debug(u, "Got notification message for unit. Ignoring."); +} + +static int manager_dispatch_notify_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + _cleanup_fdset_free_ FDSet *fds = NULL; + Manager *m = userdata; + + char buf[NOTIFY_BUFFER_MAX+1]; + 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; + bool found = false; + Unit *u1, *u2, *u3; + int r, *fd_array = NULL; + unsigned n_fds = 0; + ssize_t n; + + assert(m); + assert(m->notify_fd == fd); + + if (revents != EPOLLIN) { + log_warning("Got unexpected poll event for notify fd."); + return 0; + } + + n = recvmsg(m->notify_fd, &msghdr, MSG_DONTWAIT|MSG_CMSG_CLOEXEC); + if (n < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + CMSG_FOREACH(cmsg, &msghdr) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + + fd_array = (int*) CMSG_DATA(cmsg); + n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + } else 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 (n_fds > 0) { + assert(fd_array); + + r = fdset_new_array(&fds, fd_array, n_fds); + if (r < 0) { + close_many(fd_array, n_fds); + return log_oom(); + } + } + + if (!ucred || ucred->pid <= 0) { + 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; + + /* Notify every unit that might be interested, but try + * to avoid notifying the same one multiple times. */ + u1 = manager_get_unit_by_pid_cgroup(m, ucred->pid); + if (u1) { + manager_invoke_notify_message(m, u1, ucred->pid, buf, n, fds); + found = true; + } + + u2 = hashmap_get(m->watch_pids1, PID_TO_PTR(ucred->pid)); + if (u2 && u2 != u1) { + manager_invoke_notify_message(m, u2, ucred->pid, buf, n, fds); + found = true; + } + + u3 = hashmap_get(m->watch_pids2, PID_TO_PTR(ucred->pid)); + if (u3 && u3 != u2 && u3 != u1) { + manager_invoke_notify_message(m, u3, ucred->pid, buf, n, fds); + found = true; + } + + if (!found) + log_warning("Cannot find unit for notify message of PID "PID_FMT".", ucred->pid); + + if (fdset_size(fds) > 0) + log_warning("Got auxiliary fds with notification message, closing all."); + + return 0; +} + +static void invoke_sigchld_event(Manager *m, Unit *u, const siginfo_t *si) { + assert(m); + assert(u); + assert(si); + + log_unit_debug(u, "Child "PID_FMT" belongs to %s", si->si_pid, u->id); + + unit_unwatch_pid(u, si->si_pid); + UNIT_VTABLE(u)->sigchld_event(u, si->si_pid, si->si_code, si->si_status); +} + +static int manager_dispatch_sigchld(Manager *m) { + assert(m); + + for (;;) { + siginfo_t si = {}; + + /* First we call waitd() for a PID and do not reap the + * zombie. That way we can still access /proc/$PID for + * it while it is a zombie. */ + if (waitid(P_ALL, 0, &si, WEXITED|WNOHANG|WNOWAIT) < 0) { + + if (errno == ECHILD) + break; + + if (errno == EINTR) + continue; + + return -errno; + } + + if (si.si_pid <= 0) + break; + + if (si.si_code == CLD_EXITED || si.si_code == CLD_KILLED || si.si_code == CLD_DUMPED) { + _cleanup_free_ char *name = NULL; + Unit *u1, *u2, *u3; + + get_process_comm(si.si_pid, &name); + + log_debug("Child "PID_FMT" (%s) died (code=%s, status=%i/%s)", + si.si_pid, strna(name), + sigchld_code_to_string(si.si_code), + si.si_status, + strna(si.si_code == CLD_EXITED + ? exit_status_to_string(si.si_status, EXIT_STATUS_FULL) + : signal_to_string(si.si_status))); + + /* And now figure out the unit this belongs + * to, it might be multiple... */ + u1 = manager_get_unit_by_pid_cgroup(m, si.si_pid); + if (u1) + invoke_sigchld_event(m, u1, &si); + u2 = hashmap_get(m->watch_pids1, PID_TO_PTR(si.si_pid)); + if (u2 && u2 != u1) + invoke_sigchld_event(m, u2, &si); + u3 = hashmap_get(m->watch_pids2, PID_TO_PTR(si.si_pid)); + if (u3 && u3 != u2 && u3 != u1) + invoke_sigchld_event(m, u3, &si); + } + + /* And now, we actually reap the zombie. */ + if (waitid(P_PID, si.si_pid, &si, WEXITED) < 0) { + if (errno == EINTR) + continue; + + return -errno; + } + } + + return 0; +} + +static int manager_start_target(Manager *m, const char *name, JobMode mode) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + log_debug("Activating special unit %s", name); + + r = manager_add_job_by_name(m, JOB_START, name, mode, &error, NULL); + if (r < 0) + log_error("Failed to enqueue %s job: %s", name, bus_error_message(&error, r)); + + return r; +} + +static int manager_dispatch_signal_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + ssize_t n; + struct signalfd_siginfo sfsi; + bool sigchld = false; + int r; + + assert(m); + assert(m->signal_fd == fd); + + if (revents != EPOLLIN) { + log_warning("Got unexpected events from signal file descriptor."); + return 0; + } + + for (;;) { + n = read(m->signal_fd, &sfsi, sizeof(sfsi)); + if (n != sizeof(sfsi)) { + + if (n >= 0) + return -EIO; + + if (errno == EINTR || errno == EAGAIN) + break; + + return -errno; + } + + log_received_signal(sfsi.ssi_signo == SIGCHLD || + (sfsi.ssi_signo == SIGTERM && m->running_as == MANAGER_USER) + ? LOG_DEBUG : LOG_INFO, + &sfsi); + + switch (sfsi.ssi_signo) { + + case SIGCHLD: + sigchld = true; + break; + + case SIGTERM: + if (m->running_as == MANAGER_SYSTEM) { + /* This is for compatibility with the + * original sysvinit */ + m->exit_code = MANAGER_REEXECUTE; + break; + } + + /* Fall through */ + + case SIGINT: + if (m->running_as == MANAGER_SYSTEM) { + + /* If the user presses C-A-D more than + * 7 times within 2s, we reboot + * immediately. */ + + if (ratelimit_test(&m->ctrl_alt_del_ratelimit)) + manager_start_target(m, SPECIAL_CTRL_ALT_DEL_TARGET, JOB_REPLACE_IRREVERSIBLY); + else { + log_notice("Ctrl-Alt-Del was pressed more than 7 times within 2s, rebooting immediately."); + status_printf(NULL, true, false, "Ctrl-Alt-Del was pressed more than 7 times within 2s, rebooting immediately."); + m->exit_code = MANAGER_REBOOT; + } + + break; + } + + /* Run the exit target if there is one, if not, just exit. */ + if (manager_start_target(m, SPECIAL_EXIT_TARGET, JOB_REPLACE) < 0) { + m->exit_code = MANAGER_EXIT; + return 0; + } + + break; + + case SIGWINCH: + if (m->running_as == MANAGER_SYSTEM) + manager_start_target(m, SPECIAL_KBREQUEST_TARGET, JOB_REPLACE); + + /* This is a nop on non-init */ + break; + + case SIGPWR: + if (m->running_as == MANAGER_SYSTEM) + manager_start_target(m, SPECIAL_SIGPWR_TARGET, JOB_REPLACE); + + /* This is a nop on non-init */ + break; + + case SIGUSR1: { + Unit *u; + + u = manager_get_unit(m, SPECIAL_DBUS_SERVICE); + + if (!u || UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) { + log_info("Trying to reconnect to bus..."); + bus_init(m, true); + } + + if (!u || !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) { + log_info("Loading D-Bus service..."); + manager_start_target(m, SPECIAL_DBUS_SERVICE, JOB_REPLACE); + } + + break; + } + + case SIGUSR2: { + _cleanup_free_ char *dump = NULL; + _cleanup_fclose_ FILE *f = NULL; + size_t size; + + f = open_memstream(&dump, &size); + if (!f) { + log_warning_errno(errno, "Failed to allocate memory stream: %m"); + break; + } + + manager_dump_units(m, f, "\t"); + manager_dump_jobs(m, f, "\t"); + + r = fflush_and_check(f); + if (r < 0) { + log_warning_errno(r, "Failed to write status stream: %m"); + break; + } + + log_dump(LOG_INFO, dump); + break; + } + + case SIGHUP: + m->exit_code = MANAGER_RELOAD; + break; + + default: { + + /* Starting SIGRTMIN+0 */ + static const char * const target_table[] = { + [0] = SPECIAL_DEFAULT_TARGET, + [1] = SPECIAL_RESCUE_TARGET, + [2] = SPECIAL_EMERGENCY_TARGET, + [3] = SPECIAL_HALT_TARGET, + [4] = SPECIAL_POWEROFF_TARGET, + [5] = SPECIAL_REBOOT_TARGET, + [6] = SPECIAL_KEXEC_TARGET + }; + + /* Starting SIGRTMIN+13, so that target halt and system halt are 10 apart */ + static const ManagerExitCode code_table[] = { + [0] = MANAGER_HALT, + [1] = MANAGER_POWEROFF, + [2] = MANAGER_REBOOT, + [3] = MANAGER_KEXEC + }; + + if ((int) sfsi.ssi_signo >= SIGRTMIN+0 && + (int) sfsi.ssi_signo < SIGRTMIN+(int) ELEMENTSOF(target_table)) { + int idx = (int) sfsi.ssi_signo - SIGRTMIN; + manager_start_target(m, target_table[idx], + (idx == 1 || idx == 2) ? JOB_ISOLATE : JOB_REPLACE); + break; + } + + if ((int) sfsi.ssi_signo >= SIGRTMIN+13 && + (int) sfsi.ssi_signo < SIGRTMIN+13+(int) ELEMENTSOF(code_table)) { + m->exit_code = code_table[sfsi.ssi_signo - SIGRTMIN - 13]; + break; + } + + switch (sfsi.ssi_signo - SIGRTMIN) { + + case 20: + manager_set_show_status(m, SHOW_STATUS_YES); + break; + + case 21: + manager_set_show_status(m, SHOW_STATUS_NO); + break; + + case 22: + log_set_max_level(LOG_DEBUG); + log_info("Setting log level to debug."); + break; + + case 23: + log_set_max_level(LOG_INFO); + log_info("Setting log level to info."); + break; + + case 24: + if (m->running_as == MANAGER_USER) { + m->exit_code = MANAGER_EXIT; + return 0; + } + + /* This is a nop on init */ + break; + + case 26: + case 29: /* compatibility: used to be mapped to LOG_TARGET_SYSLOG_OR_KMSG */ + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + log_notice("Setting log target to journal-or-kmsg."); + break; + + case 27: + log_set_target(LOG_TARGET_CONSOLE); + log_notice("Setting log target to console."); + break; + + case 28: + log_set_target(LOG_TARGET_KMSG); + log_notice("Setting log target to kmsg."); + break; + + default: + log_warning("Got unhandled signal <%s>.", signal_to_string(sfsi.ssi_signo)); + } + } + } + } + + if (sigchld) + manager_dispatch_sigchld(m); + + return 0; +} + +static int manager_dispatch_time_change_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + Iterator i; + Unit *u; + + assert(m); + assert(m->time_change_fd == fd); + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE), + LOG_MESSAGE("Time has been changed"), + NULL); + + /* Restart the watch */ + m->time_change_event_source = sd_event_source_unref(m->time_change_event_source); + m->time_change_fd = safe_close(m->time_change_fd); + + manager_setup_time_change(m); + + HASHMAP_FOREACH(u, m->units, i) + if (UNIT_VTABLE(u)->time_change) + UNIT_VTABLE(u)->time_change(u); + + return 0; +} + +static int manager_dispatch_idle_pipe_fd(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + + assert(m); + assert(m->idle_pipe[2] == fd); + + m->no_console_output = m->n_on_console > 0; + + manager_close_idle_pipe(m); + + return 0; +} + +static int manager_dispatch_jobs_in_progress(sd_event_source *source, usec_t usec, void *userdata) { + Manager *m = userdata; + int r; + uint64_t next; + + assert(m); + assert(source); + + manager_print_jobs_in_progress(m); + + next = now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_PERIOD_USEC; + r = sd_event_source_set_time(source, next); + if (r < 0) + return r; + + return sd_event_source_set_enabled(source, SD_EVENT_ONESHOT); +} + +int manager_loop(Manager *m) { + int r; + + RATELIMIT_DEFINE(rl, 1*USEC_PER_SEC, 50000); + + assert(m); + m->exit_code = MANAGER_OK; + + /* Release the path cache */ + m->unit_path_cache = set_free_free(m->unit_path_cache); + + manager_check_finished(m); + + /* There might still be some zombies hanging around from + * before we were exec()'ed. Let's reap them. */ + r = manager_dispatch_sigchld(m); + if (r < 0) + return r; + + while (m->exit_code == MANAGER_OK) { + usec_t wait_usec; + + if (m->runtime_watchdog > 0 && m->running_as == MANAGER_SYSTEM) + watchdog_ping(); + + if (!ratelimit_test(&rl)) { + /* Yay, something is going seriously wrong, pause a little */ + log_warning("Looping too fast. Throttling execution a little."); + sleep(1); + } + + if (manager_dispatch_load_queue(m) > 0) + continue; + + if (manager_dispatch_gc_queue(m) > 0) + continue; + + if (manager_dispatch_cleanup_queue(m) > 0) + continue; + + if (manager_dispatch_cgroup_queue(m) > 0) + continue; + + if (manager_dispatch_dbus_queue(m) > 0) + continue; + + /* Sleep for half the watchdog time */ + if (m->runtime_watchdog > 0 && m->running_as == MANAGER_SYSTEM) { + wait_usec = m->runtime_watchdog / 2; + if (wait_usec <= 0) + wait_usec = 1; + } else + wait_usec = USEC_INFINITY; + + r = sd_event_run(m->event, wait_usec); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + } + + return m->exit_code; +} + +int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u) { + _cleanup_free_ char *n = NULL; + Unit *u; + int r; + + assert(m); + assert(s); + assert(_u); + + r = unit_name_from_dbus_path(s, &n); + if (r < 0) + return r; + + r = manager_load_unit(m, n, NULL, e, &u); + if (r < 0) + return r; + + *_u = u; + + return 0; +} + +int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j) { + const char *p; + unsigned id; + Job *j; + int r; + + assert(m); + assert(s); + assert(_j); + + p = startswith(s, "/org/freedesktop/systemd1/job/"); + if (!p) + return -EINVAL; + + r = safe_atou(p, &id); + if (r < 0) + return r; + + j = manager_get_job(m, id); + if (!j) + return -ENOENT; + + *_j = j; + + return 0; +} + +void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success) { + +#ifdef HAVE_AUDIT + _cleanup_free_ char *p = NULL; + const char *msg; + int audit_fd, r; + + if (m->running_as != MANAGER_SYSTEM) + return; + + audit_fd = get_audit_fd(); + if (audit_fd < 0) + return; + + /* Don't generate audit events if the service was already + * started and we're just deserializing */ + if (m->n_reloading > 0) + return; + + if (u->type != UNIT_SERVICE) + return; + + r = unit_name_to_prefix_and_instance(u->id, &p); + if (r < 0) { + log_error_errno(r, "Failed to extract prefix and instance of unit name: %m"); + return; + } + + msg = strjoina("unit=", p); + if (audit_log_user_comm_message(audit_fd, type, msg, "systemd", NULL, NULL, NULL, success) < 0) { + if (errno == EPERM) + /* We aren't allowed to send audit messages? + * Then let's not retry again. */ + close_audit_fd(); + else + log_warning_errno(errno, "Failed to send audit message: %m"); + } +#endif + +} + +void manager_send_unit_plymouth(Manager *m, Unit *u) { + union sockaddr_union sa = PLYMOUTH_SOCKET; + + int n = 0; + _cleanup_free_ char *message = NULL; + _cleanup_close_ int fd = -1; + + /* Don't generate plymouth events if the service was already + * started and we're just deserializing */ + if (m->n_reloading > 0) + return; + + if (m->running_as != MANAGER_SYSTEM) + return; + + if (detect_container() > 0) + return; + + if (u->type != UNIT_SERVICE && + u->type != UNIT_MOUNT && + u->type != UNIT_SWAP) + return; + + /* We set SOCK_NONBLOCK here so that we rather drop the + * message then wait for plymouth */ + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) { + log_error_errno(errno, "socket() failed: %m"); + return; + } + + if (connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)) < 0) { + + if (!IN_SET(errno, EPIPE, EAGAIN, ENOENT, ECONNREFUSED, ECONNRESET, ECONNABORTED)) + log_error_errno(errno, "connect() failed: %m"); + return; + } + + if (asprintf(&message, "U\002%c%s%n", (int) (strlen(u->id) + 1), u->id, &n) < 0) { + log_oom(); + return; + } + + errno = 0; + if (write(fd, message, n + 1) != n + 1) + if (!IN_SET(errno, EPIPE, EAGAIN, ENOENT, ECONNREFUSED, ECONNRESET, ECONNABORTED)) + log_error_errno(errno, "Failed to write Plymouth message: %m"); +} + +int manager_open_serialization(Manager *m, FILE **_f) { + const char *path; + int fd = -1; + FILE *f; + + assert(_f); + + path = m->running_as == MANAGER_SYSTEM ? "/run/systemd" : "/tmp"; + fd = open_tmpfile(path, O_RDWR|O_CLOEXEC); + if (fd < 0) + return -errno; + + log_debug("Serializing state to %s", path); + + f = fdopen(fd, "w+"); + if (!f) { + safe_close(fd); + return -errno; + } + + *_f = f; + + return 0; +} + +int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root) { + Iterator i; + Unit *u; + const char *t; + char **e; + int r; + + assert(m); + assert(f); + assert(fds); + + m->n_reloading ++; + + fprintf(f, "current-job-id=%"PRIu32"\n", m->current_job_id); + fprintf(f, "taint-usr=%s\n", yes_no(m->taint_usr)); + fprintf(f, "n-installed-jobs=%u\n", m->n_installed_jobs); + fprintf(f, "n-failed-jobs=%u\n", m->n_failed_jobs); + + dual_timestamp_serialize(f, "firmware-timestamp", &m->firmware_timestamp); + dual_timestamp_serialize(f, "loader-timestamp", &m->loader_timestamp); + dual_timestamp_serialize(f, "kernel-timestamp", &m->kernel_timestamp); + dual_timestamp_serialize(f, "initrd-timestamp", &m->initrd_timestamp); + + if (!in_initrd()) { + dual_timestamp_serialize(f, "userspace-timestamp", &m->userspace_timestamp); + dual_timestamp_serialize(f, "finish-timestamp", &m->finish_timestamp); + dual_timestamp_serialize(f, "security-start-timestamp", &m->security_start_timestamp); + dual_timestamp_serialize(f, "security-finish-timestamp", &m->security_finish_timestamp); + dual_timestamp_serialize(f, "generators-start-timestamp", &m->generators_start_timestamp); + dual_timestamp_serialize(f, "generators-finish-timestamp", &m->generators_finish_timestamp); + dual_timestamp_serialize(f, "units-load-start-timestamp", &m->units_load_start_timestamp); + dual_timestamp_serialize(f, "units-load-finish-timestamp", &m->units_load_finish_timestamp); + } + + if (!switching_root) { + STRV_FOREACH(e, m->environment) { + _cleanup_free_ char *ce; + + ce = cescape(*e); + if (!ce) + return -ENOMEM; + + fprintf(f, "env=%s\n", *e); + } + } + + if (m->notify_fd >= 0) { + int copy; + + copy = fdset_put_dup(fds, m->notify_fd); + if (copy < 0) + return copy; + + fprintf(f, "notify-fd=%i\n", copy); + fprintf(f, "notify-socket=%s\n", m->notify_socket); + } + + if (m->kdbus_fd >= 0) { + int copy; + + copy = fdset_put_dup(fds, m->kdbus_fd); + if (copy < 0) + return copy; + + fprintf(f, "kdbus-fd=%i\n", copy); + } + + bus_track_serialize(m->subscribed, f); + + fputc('\n', f); + + HASHMAP_FOREACH_KEY(u, t, m->units, i) { + if (u->id != t) + continue; + + /* Start marker */ + fputs(u->id, f); + fputc('\n', f); + + r = unit_serialize(u, f, fds, !switching_root); + if (r < 0) { + m->n_reloading --; + return r; + } + } + + assert(m->n_reloading > 0); + m->n_reloading --; + + if (ferror(f)) + return -EIO; + + r = bus_fdset_add_all(m, fds); + if (r < 0) + return r; + + return 0; +} + +int manager_deserialize(Manager *m, FILE *f, FDSet *fds) { + int r = 0; + + assert(m); + assert(f); + + log_debug("Deserializing state..."); + + m->n_reloading ++; + + for (;;) { + char line[LINE_MAX], *l; + + if (!fgets(line, sizeof(line), f)) { + if (feof(f)) + r = 0; + else + r = -errno; + + goto finish; + } + + char_array_0(line); + l = strstrip(line); + + if (l[0] == 0) + break; + + if (startswith(l, "current-job-id=")) { + uint32_t id; + + if (safe_atou32(l+15, &id) < 0) + log_debug("Failed to parse current job id value %s", l+15); + else + m->current_job_id = MAX(m->current_job_id, id); + + } else if (startswith(l, "n-installed-jobs=")) { + uint32_t n; + + if (safe_atou32(l+17, &n) < 0) + log_debug("Failed to parse installed jobs counter %s", l+17); + else + m->n_installed_jobs += n; + + } else if (startswith(l, "n-failed-jobs=")) { + uint32_t n; + + if (safe_atou32(l+14, &n) < 0) + log_debug("Failed to parse failed jobs counter %s", l+14); + else + m->n_failed_jobs += n; + + } else if (startswith(l, "taint-usr=")) { + int b; + + b = parse_boolean(l+10); + if (b < 0) + log_debug("Failed to parse taint /usr flag %s", l+10); + else + m->taint_usr = m->taint_usr || b; + + } else if (startswith(l, "firmware-timestamp=")) + dual_timestamp_deserialize(l+19, &m->firmware_timestamp); + else if (startswith(l, "loader-timestamp=")) + dual_timestamp_deserialize(l+17, &m->loader_timestamp); + else if (startswith(l, "kernel-timestamp=")) + dual_timestamp_deserialize(l+17, &m->kernel_timestamp); + else if (startswith(l, "initrd-timestamp=")) + dual_timestamp_deserialize(l+17, &m->initrd_timestamp); + else if (startswith(l, "userspace-timestamp=")) + dual_timestamp_deserialize(l+20, &m->userspace_timestamp); + else if (startswith(l, "finish-timestamp=")) + dual_timestamp_deserialize(l+17, &m->finish_timestamp); + else if (startswith(l, "security-start-timestamp=")) + dual_timestamp_deserialize(l+25, &m->security_start_timestamp); + else if (startswith(l, "security-finish-timestamp=")) + dual_timestamp_deserialize(l+26, &m->security_finish_timestamp); + else if (startswith(l, "generators-start-timestamp=")) + dual_timestamp_deserialize(l+27, &m->generators_start_timestamp); + else if (startswith(l, "generators-finish-timestamp=")) + dual_timestamp_deserialize(l+28, &m->generators_finish_timestamp); + else if (startswith(l, "units-load-start-timestamp=")) + dual_timestamp_deserialize(l+27, &m->units_load_start_timestamp); + else if (startswith(l, "units-load-finish-timestamp=")) + dual_timestamp_deserialize(l+28, &m->units_load_finish_timestamp); + else if (startswith(l, "env=")) { + _cleanup_free_ char *uce = NULL; + char **e; + + r = cunescape(l + 4, UNESCAPE_RELAX, &uce); + if (r < 0) + goto finish; + + e = strv_env_set(m->environment, uce); + if (!e) { + r = -ENOMEM; + goto finish; + } + + strv_free(m->environment); + m->environment = e; + + } else if (startswith(l, "notify-fd=")) { + int fd; + + if (safe_atoi(l + 10, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_debug("Failed to parse notify fd: %s", l + 10); + else { + m->notify_event_source = sd_event_source_unref(m->notify_event_source); + safe_close(m->notify_fd); + m->notify_fd = fdset_remove(fds, fd); + } + + } else if (startswith(l, "notify-socket=")) { + char *n; + + n = strdup(l+14); + if (!n) { + r = -ENOMEM; + goto finish; + } + + free(m->notify_socket); + m->notify_socket = n; + + } else if (startswith(l, "kdbus-fd=")) { + int fd; + + if (safe_atoi(l + 9, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_debug("Failed to parse kdbus fd: %s", l + 9); + else { + safe_close(m->kdbus_fd); + m->kdbus_fd = fdset_remove(fds, fd); + } + + } else { + int k; + + k = bus_track_deserialize_item(&m->deserialized_subscribed, l); + if (k < 0) + log_debug_errno(k, "Failed to deserialize bus tracker object: %m"); + else if (k == 0) + log_debug("Unknown serialization item '%s'", l); + } + } + + for (;;) { + Unit *u; + char name[UNIT_NAME_MAX+2]; + + /* Start marker */ + if (!fgets(name, sizeof(name), f)) { + if (feof(f)) + r = 0; + else + r = -errno; + + goto finish; + } + + char_array_0(name); + + r = manager_load_unit(m, strstrip(name), NULL, NULL, &u); + if (r < 0) + goto finish; + + r = unit_deserialize(u, f, fds); + if (r < 0) + goto finish; + } + +finish: + if (ferror(f)) + r = -EIO; + + assert(m->n_reloading > 0); + m->n_reloading --; + + return r; +} + +int manager_reload(Manager *m) { + int r, q; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_fdset_free_ FDSet *fds = NULL; + + assert(m); + + r = manager_open_serialization(m, &f); + if (r < 0) + return r; + + m->n_reloading ++; + bus_manager_send_reloading(m, true); + + fds = fdset_new(); + if (!fds) { + m->n_reloading --; + return -ENOMEM; + } + + r = manager_serialize(m, f, fds, false); + if (r < 0) { + m->n_reloading --; + return r; + } + + if (fseeko(f, 0, SEEK_SET) < 0) { + m->n_reloading --; + return -errno; + } + + /* From here on there is no way back. */ + manager_clear_jobs_and_units(m); + manager_undo_generators(m); + lookup_paths_free(&m->lookup_paths); + + /* Find new unit paths */ + q = manager_run_generators(m); + if (q < 0 && r >= 0) + r = q; + + q = lookup_paths_init( + &m->lookup_paths, m->running_as, true, + NULL, + m->generator_unit_path, + m->generator_unit_path_early, + m->generator_unit_path_late); + if (q < 0 && r >= 0) + r = q; + + manager_build_unit_path_cache(m); + + /* First, enumerate what we can from all config files */ + manager_enumerate(m); + + /* Second, deserialize our stored data */ + q = manager_deserialize(m, f, fds); + if (q < 0 && r >= 0) + r = q; + + fclose(f); + f = NULL; + + /* Re-register notify_fd as event source */ + q = manager_setup_notify(m); + if (q < 0 && r >= 0) + r = q; + + /* Third, fire things up! */ + manager_coldplug(m); + + /* Sync current state of bus names with our set of listening units */ + if (m->api_bus) + manager_sync_bus_names(m, m->api_bus); + + assert(m->n_reloading > 0); + m->n_reloading--; + + m->send_reloading_done = true; + + return r; +} + +bool manager_is_reloading_or_reexecuting(Manager *m) { + assert(m); + + return m->n_reloading != 0; +} + +void manager_reset_failed(Manager *m) { + Unit *u; + Iterator i; + + assert(m); + + HASHMAP_FOREACH(u, m->units, i) + unit_reset_failed(u); +} + +bool manager_unit_inactive_or_pending(Manager *m, const char *name) { + Unit *u; + + assert(m); + assert(name); + + /* Returns true if the unit is inactive or going down */ + u = manager_get_unit(m, name); + if (!u) + return true; + + return unit_inactive_or_pending(u); +} + +static void manager_notify_finished(Manager *m) { + char userspace[FORMAT_TIMESPAN_MAX], initrd[FORMAT_TIMESPAN_MAX], kernel[FORMAT_TIMESPAN_MAX], sum[FORMAT_TIMESPAN_MAX]; + usec_t firmware_usec, loader_usec, kernel_usec, initrd_usec, userspace_usec, total_usec; + + if (m->test_run) + return; + + if (m->running_as == MANAGER_SYSTEM && detect_container() <= 0) { + + /* Note that m->kernel_usec.monotonic is always at 0, + * and m->firmware_usec.monotonic and + * m->loader_usec.monotonic should be considered + * negative values. */ + + firmware_usec = m->firmware_timestamp.monotonic - m->loader_timestamp.monotonic; + loader_usec = m->loader_timestamp.monotonic - m->kernel_timestamp.monotonic; + userspace_usec = m->finish_timestamp.monotonic - m->userspace_timestamp.monotonic; + total_usec = m->firmware_timestamp.monotonic + m->finish_timestamp.monotonic; + + if (dual_timestamp_is_set(&m->initrd_timestamp)) { + + kernel_usec = m->initrd_timestamp.monotonic - m->kernel_timestamp.monotonic; + initrd_usec = m->userspace_timestamp.monotonic - m->initrd_timestamp.monotonic; + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED), + "KERNEL_USEC="USEC_FMT, kernel_usec, + "INITRD_USEC="USEC_FMT, initrd_usec, + "USERSPACE_USEC="USEC_FMT, userspace_usec, + LOG_MESSAGE("Startup finished in %s (kernel) + %s (initrd) + %s (userspace) = %s.", + format_timespan(kernel, sizeof(kernel), kernel_usec, USEC_PER_MSEC), + format_timespan(initrd, sizeof(initrd), initrd_usec, USEC_PER_MSEC), + format_timespan(userspace, sizeof(userspace), userspace_usec, USEC_PER_MSEC), + format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)), + NULL); + } else { + kernel_usec = m->userspace_timestamp.monotonic - m->kernel_timestamp.monotonic; + initrd_usec = 0; + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED), + "KERNEL_USEC="USEC_FMT, kernel_usec, + "USERSPACE_USEC="USEC_FMT, userspace_usec, + LOG_MESSAGE("Startup finished in %s (kernel) + %s (userspace) = %s.", + format_timespan(kernel, sizeof(kernel), kernel_usec, USEC_PER_MSEC), + format_timespan(userspace, sizeof(userspace), userspace_usec, USEC_PER_MSEC), + format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)), + NULL); + } + } else { + firmware_usec = loader_usec = initrd_usec = kernel_usec = 0; + total_usec = userspace_usec = m->finish_timestamp.monotonic - m->userspace_timestamp.monotonic; + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_STARTUP_FINISHED), + "USERSPACE_USEC="USEC_FMT, userspace_usec, + LOG_MESSAGE("Startup finished in %s.", + format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)), + NULL); + } + + bus_manager_send_finished(m, firmware_usec, loader_usec, kernel_usec, initrd_usec, userspace_usec, total_usec); + + sd_notifyf(false, + "READY=1\n" + "STATUS=Startup finished in %s.", + format_timespan(sum, sizeof(sum), total_usec, USEC_PER_MSEC)); +} + +void manager_check_finished(Manager *m) { + assert(m); + + if (m->n_reloading > 0) + return; + + /* Verify that we are actually running currently. Initially + * the exit code is set to invalid, and during operation it is + * then set to MANAGER_OK */ + if (m->exit_code != MANAGER_OK) + return; + + if (hashmap_size(m->jobs) > 0) { + if (m->jobs_in_progress_event_source) + /* Ignore any failure, this is only for feedback */ + (void) sd_event_source_set_time(m->jobs_in_progress_event_source, now(CLOCK_MONOTONIC) + JOBS_IN_PROGRESS_WAIT_USEC); + + return; + } + + manager_flip_auto_status(m, false); + + /* Notify Type=idle units that we are done now */ + manager_close_idle_pipe(m); + + /* Turn off confirm spawn now */ + m->confirm_spawn = false; + + /* No need to update ask password status when we're going non-interactive */ + manager_close_ask_password(m); + + /* This is no longer the first boot */ + manager_set_first_boot(m, false); + + if (dual_timestamp_is_set(&m->finish_timestamp)) + return; + + dual_timestamp_get(&m->finish_timestamp); + + manager_notify_finished(m); + + manager_invalidate_startup_units(m); +} + +static int create_generator_dir(Manager *m, char **generator, const char *name) { + char *p; + int r; + + assert(m); + assert(generator); + assert(name); + + if (*generator) + return 0; + + if (m->running_as == MANAGER_SYSTEM && getpid() == 1) { + /* systemd --system, not running --test */ + + p = strappend("/run/systemd/", name); + if (!p) + return log_oom(); + + r = mkdir_p_label(p, 0755); + if (r < 0) { + log_error_errno(r, "Failed to create generator directory %s: %m", p); + free(p); + return r; + } + } else if (m->running_as == MANAGER_USER) { + const char *s = NULL; + + s = getenv("XDG_RUNTIME_DIR"); + if (!s) + return -EINVAL; + p = strjoin(s, "/systemd/", name, NULL); + if (!p) + return log_oom(); + + r = mkdir_p_label(p, 0755); + if (r < 0) { + log_error_errno(r, "Failed to create generator directory %s: %m", p); + free(p); + return r; + } + } else { + /* systemd --system --test */ + + p = strjoin("/tmp/systemd-", name, ".XXXXXX", NULL); + if (!p) + return log_oom(); + + if (!mkdtemp(p)) { + log_error_errno(errno, "Failed to create generator directory %s: %m", p); + free(p); + return -errno; + } + } + + *generator = p; + return 0; +} + +static void trim_generator_dir(Manager *m, char **generator) { + assert(m); + assert(generator); + + if (!*generator) + return; + + if (rmdir(*generator) >= 0) + *generator = mfree(*generator); + + return; +} + +static int manager_run_generators(Manager *m) { + _cleanup_strv_free_ char **paths = NULL; + const char *argv[5]; + char **path; + int r; + + assert(m); + + if (m->test_run) + return 0; + + paths = generator_paths(m->running_as); + if (!paths) + return log_oom(); + + /* Optimize by skipping the whole process by not creating output directories + * if no generators are found. */ + STRV_FOREACH(path, paths) { + r = access(*path, F_OK); + if (r == 0) + goto found; + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open generator directory %s: %m", *path); + } + return 0; + + found: + r = create_generator_dir(m, &m->generator_unit_path, "generator"); + if (r < 0) + goto finish; + + r = create_generator_dir(m, &m->generator_unit_path_early, "generator.early"); + if (r < 0) + goto finish; + + r = create_generator_dir(m, &m->generator_unit_path_late, "generator.late"); + if (r < 0) + goto finish; + + argv[0] = NULL; /* Leave this empty, execute_directory() will fill something in */ + argv[1] = m->generator_unit_path; + argv[2] = m->generator_unit_path_early; + argv[3] = m->generator_unit_path_late; + argv[4] = NULL; + + RUN_WITH_UMASK(0022) + execute_directories((const char* const*) paths, DEFAULT_TIMEOUT_USEC, (char**) argv); + +finish: + trim_generator_dir(m, &m->generator_unit_path); + trim_generator_dir(m, &m->generator_unit_path_early); + trim_generator_dir(m, &m->generator_unit_path_late); + return r; +} + +static void remove_generator_dir(Manager *m, char **generator) { + assert(m); + assert(generator); + + if (!*generator) + return; + + strv_remove(m->lookup_paths.unit_path, *generator); + (void) rm_rf(*generator, REMOVE_ROOT); + + *generator = mfree(*generator); +} + +static void manager_undo_generators(Manager *m) { + assert(m); + + remove_generator_dir(m, &m->generator_unit_path); + remove_generator_dir(m, &m->generator_unit_path_early); + remove_generator_dir(m, &m->generator_unit_path_late); +} + +int manager_environment_add(Manager *m, char **minus, char **plus) { + char **a = NULL, **b = NULL, **l; + assert(m); + + l = m->environment; + + if (!strv_isempty(minus)) { + a = strv_env_delete(l, 1, minus); + if (!a) + return -ENOMEM; + + l = a; + } + + if (!strv_isempty(plus)) { + b = strv_env_merge(2, l, plus); + if (!b) { + strv_free(a); + return -ENOMEM; + } + + l = b; + } + + if (m->environment != l) + strv_free(m->environment); + if (a != l) + strv_free(a); + if (b != l) + strv_free(b); + + m->environment = l; + manager_clean_environment(m); + strv_sort(m->environment); + + return 0; +} + +int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit) { + int i; + + assert(m); + + for (i = 0; i < _RLIMIT_MAX; i++) { + m->rlimit[i] = mfree(m->rlimit[i]); + + if (!default_rlimit[i]) + continue; + + m->rlimit[i] = newdup(struct rlimit, default_rlimit[i], 1); + if (!m->rlimit[i]) + return -ENOMEM; + } + + return 0; +} + +void manager_recheck_journal(Manager *m) { + Unit *u; + + assert(m); + + if (m->running_as != MANAGER_SYSTEM) + return; + + u = manager_get_unit(m, SPECIAL_JOURNALD_SOCKET); + if (u && SOCKET(u)->state != SOCKET_RUNNING) { + log_close_journal(); + return; + } + + u = manager_get_unit(m, SPECIAL_JOURNALD_SERVICE); + if (u && SERVICE(u)->state != SERVICE_RUNNING) { + log_close_journal(); + return; + } + + /* Hmm, OK, so the socket is fully up and the service is up + * too, then let's make use of the thing. */ + log_open(); +} + +void manager_set_show_status(Manager *m, ShowStatus mode) { + assert(m); + assert(IN_SET(mode, SHOW_STATUS_AUTO, SHOW_STATUS_NO, SHOW_STATUS_YES, SHOW_STATUS_TEMPORARY)); + + if (m->running_as != MANAGER_SYSTEM) + return; + + if (m->show_status != mode) + log_debug("%s showing of status.", + mode == SHOW_STATUS_NO ? "Disabling" : "Enabling"); + m->show_status = mode; + + if (mode > 0) + (void) touch("/run/systemd/show-status"); + else + (void) unlink("/run/systemd/show-status"); +} + +static bool manager_get_show_status(Manager *m, StatusType type) { + assert(m); + + if (m->running_as != MANAGER_SYSTEM) + return false; + + if (m->no_console_output) + return false; + + if (!IN_SET(manager_state(m), MANAGER_INITIALIZING, MANAGER_STARTING, MANAGER_STOPPING)) + return false; + + /* If we cannot find out the status properly, just proceed. */ + if (type != STATUS_TYPE_EMERGENCY && manager_check_ask_password(m) > 0) + return false; + + if (m->show_status > 0) + return true; + + return false; +} + +void manager_set_first_boot(Manager *m, bool b) { + assert(m); + + if (m->running_as != MANAGER_SYSTEM) + return; + + if (m->first_boot != (int) b) { + if (b) + (void) touch("/run/systemd/first-boot"); + else + (void) unlink("/run/systemd/first-boot"); + } + + m->first_boot = b; +} + +void manager_status_printf(Manager *m, StatusType type, const char *status, const char *format, ...) { + va_list ap; + + /* If m is NULL, assume we're after shutdown and let the messages through. */ + + if (m && !manager_get_show_status(m, type)) + return; + + /* XXX We should totally drop the check for ephemeral here + * and thus effectively make 'Type=idle' pointless. */ + if (type == STATUS_TYPE_EPHEMERAL && m && m->n_on_console > 0) + return; + + va_start(ap, format); + status_vprintf(status, true, type == STATUS_TYPE_EPHEMERAL, format, ap); + va_end(ap); +} + +Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path) { + char p[strlen(path)+1]; + + assert(m); + assert(path); + + strcpy(p, path); + path_kill_slashes(p); + + return hashmap_get(m->units_requiring_mounts_for, streq(p, "/") ? "" : p); +} + +const char *manager_get_runtime_prefix(Manager *m) { + assert(m); + + return m->running_as == MANAGER_SYSTEM ? + "/run" : + getenv("XDG_RUNTIME_DIR"); +} + +int manager_update_failed_units(Manager *m, Unit *u, bool failed) { + unsigned size; + int r; + + assert(m); + assert(u->manager == m); + + size = set_size(m->failed_units); + + if (failed) { + r = set_ensure_allocated(&m->failed_units, NULL); + if (r < 0) + return log_oom(); + + if (set_put(m->failed_units, u) < 0) + return log_oom(); + } else + (void) set_remove(m->failed_units, u); + + if (set_size(m->failed_units) != size) + bus_manager_send_change_signal(m); + + return 0; +} + +ManagerState manager_state(Manager *m) { + Unit *u; + + assert(m); + + /* Did we ever finish booting? If not then we are still starting up */ + if (!dual_timestamp_is_set(&m->finish_timestamp)) { + + u = manager_get_unit(m, SPECIAL_BASIC_TARGET); + if (!u || !UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) + return MANAGER_INITIALIZING; + + return MANAGER_STARTING; + } + + /* Is the special shutdown target queued? If so, we are in shutdown state */ + u = manager_get_unit(m, SPECIAL_SHUTDOWN_TARGET); + if (u && u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)) + return MANAGER_STOPPING; + + /* Are the rescue or emergency targets active or queued? If so we are in maintenance state */ + u = manager_get_unit(m, SPECIAL_RESCUE_TARGET); + if (u && (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)) || + (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)))) + return MANAGER_MAINTENANCE; + + u = manager_get_unit(m, SPECIAL_EMERGENCY_TARGET); + if (u && (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u)) || + (u->job && IN_SET(u->job->type, JOB_START, JOB_RESTART, JOB_RELOAD_OR_START)))) + return MANAGER_MAINTENANCE; + + /* Are there any failed units? If so, we are in degraded mode */ + if (set_size(m->failed_units) > 0) + return MANAGER_DEGRADED; + + return MANAGER_RUNNING; +} + +static const char *const manager_state_table[_MANAGER_STATE_MAX] = { + [MANAGER_INITIALIZING] = "initializing", + [MANAGER_STARTING] = "starting", + [MANAGER_RUNNING] = "running", + [MANAGER_DEGRADED] = "degraded", + [MANAGER_MAINTENANCE] = "maintenance", + [MANAGER_STOPPING] = "stopping", +}; + +DEFINE_STRING_TABLE_LOOKUP(manager_state, ManagerState); diff --git a/src/libcore/manager.h b/src/libcore/manager.h new file mode 100644 index 0000000000..d6c97d8f3b --- /dev/null +++ b/src/libcore/manager.h @@ -0,0 +1,376 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#include +#include + +#include "cgroup-util.h" +#include "fdset.h" +#include "hashmap.h" +#include "list.h" +#include "ratelimit.h" + +/* Enforce upper limit how many names we allow */ +#define MANAGER_MAX_NAMES 131072 /* 128K */ + +typedef struct Manager Manager; + +typedef enum ManagerState { + MANAGER_INITIALIZING, + MANAGER_STARTING, + MANAGER_RUNNING, + MANAGER_DEGRADED, + MANAGER_MAINTENANCE, + MANAGER_STOPPING, + _MANAGER_STATE_MAX, + _MANAGER_STATE_INVALID = -1 +} ManagerState; + +typedef enum ManagerExitCode { + MANAGER_OK, + MANAGER_EXIT, + MANAGER_RELOAD, + MANAGER_REEXECUTE, + MANAGER_REBOOT, + MANAGER_POWEROFF, + MANAGER_HALT, + MANAGER_KEXEC, + MANAGER_SWITCH_ROOT, + _MANAGER_EXIT_CODE_MAX, + _MANAGER_EXIT_CODE_INVALID = -1 +} ManagerExitCode; + +typedef enum StatusType { + STATUS_TYPE_EPHEMERAL, + STATUS_TYPE_NORMAL, + STATUS_TYPE_EMERGENCY, +} StatusType; + +#include "execute.h" +#include "job.h" +#include "path-lookup.h" +#include "show-status.h" +#include "unit-name.h" + +struct Manager { + /* Note that the set of units we know of is allowed to be + * inconsistent. However the subset of it that is loaded may + * not, and the list of jobs may neither. */ + + /* Active jobs and units */ + Hashmap *units; /* name string => Unit object n:1 */ + Hashmap *jobs; /* job id => Job object 1:1 */ + + /* To make it easy to iterate through the units of a specific + * type we maintain a per type linked list */ + LIST_HEAD(Unit, units_by_type[_UNIT_TYPE_MAX]); + + /* Units that need to be loaded */ + LIST_HEAD(Unit, load_queue); /* this is actually more a stack than a queue, but uh. */ + + /* Jobs that need to be run */ + LIST_HEAD(Job, run_queue); /* more a stack than a queue, too */ + + /* Units and jobs that have not yet been announced via + * D-Bus. When something about a job changes it is added here + * if it is not in there yet. This allows easy coalescing of + * D-Bus change signals. */ + LIST_HEAD(Unit, dbus_unit_queue); + LIST_HEAD(Job, dbus_job_queue); + + /* Units to remove */ + LIST_HEAD(Unit, cleanup_queue); + + /* Units to check when doing GC */ + LIST_HEAD(Unit, gc_queue); + + /* Units that should be realized */ + LIST_HEAD(Unit, cgroup_queue); + + sd_event *event; + + /* We use two hash tables here, since the same PID might be + * watched by two different units: once the unit that forked + * it off, and possibly a different unit to which it was + * joined as cgroup member. Since we know that it is either + * one or two units for each PID we just use to hashmaps + * here. */ + Hashmap *watch_pids1; /* pid => Unit object n:1 */ + Hashmap *watch_pids2; /* pid => Unit object n:1 */ + + /* A set contains all units which cgroup should be refreshed after startup */ + Set *startup_units; + + /* A set which contains all currently failed units */ + Set *failed_units; + + sd_event_source *run_queue_event_source; + + char *notify_socket; + int notify_fd; + sd_event_source *notify_event_source; + + int signal_fd; + sd_event_source *signal_event_source; + + int time_change_fd; + sd_event_source *time_change_event_source; + + sd_event_source *jobs_in_progress_event_source; + + LookupPaths lookup_paths; + Set *unit_path_cache; + + char **environment; + + usec_t runtime_watchdog; + usec_t shutdown_watchdog; + + dual_timestamp firmware_timestamp; + dual_timestamp loader_timestamp; + dual_timestamp kernel_timestamp; + dual_timestamp initrd_timestamp; + dual_timestamp userspace_timestamp; + dual_timestamp finish_timestamp; + + dual_timestamp security_start_timestamp; + dual_timestamp security_finish_timestamp; + dual_timestamp generators_start_timestamp; + dual_timestamp generators_finish_timestamp; + dual_timestamp units_load_start_timestamp; + dual_timestamp units_load_finish_timestamp; + + char *generator_unit_path; + char *generator_unit_path_early; + char *generator_unit_path_late; + + struct udev* udev; + + /* Data specific to the device subsystem */ + struct udev_monitor* udev_monitor; + sd_event_source *udev_event_source; + Hashmap *devices_by_sysfs; + + /* Data specific to the mount subsystem */ + struct libmnt_monitor *mount_monitor; + sd_event_source *mount_event_source; + + /* Data specific to the swap filesystem */ + FILE *proc_swaps; + sd_event_source *swap_event_source; + Hashmap *swaps_by_devnode; + + /* Data specific to the D-Bus subsystem */ + sd_bus *api_bus, *system_bus; + Set *private_buses; + int private_listen_fd; + sd_event_source *private_listen_event_source; + + /* Contains all the clients that are subscribed to signals via + the API bus. Note that private bus connections are always + considered subscribes, since they last for very short only, + and it is much simpler that way. */ + sd_bus_track *subscribed; + char **deserialized_subscribed; + + /* This is used during reloading: before the reload we queue + * the reply message here, and afterwards we send it */ + sd_bus_message *queued_message; + + Hashmap *watch_bus; /* D-Bus names => Unit object n:1 */ + + bool send_reloading_done; + + uint32_t current_job_id; + uint32_t default_unit_job_id; + + /* Data specific to the Automount subsystem */ + int dev_autofs_fd; + + /* Data specific to the cgroup subsystem */ + Hashmap *cgroup_unit; + CGroupMask cgroup_supported; + char *cgroup_root; + + /* Notifications from cgroups, when the unified hierarchy is + * used is done via inotify. */ + int cgroup_inotify_fd; + sd_event_source *cgroup_inotify_event_source; + Hashmap *cgroup_inotify_wd_unit; + + /* Make sure the user cannot accidentally unmount our cgroup + * file system */ + int pin_cgroupfs_fd; + + int gc_marker; + unsigned n_in_gc_queue; + + /* Flags */ + ManagerRunningAs running_as; + ManagerExitCode exit_code:5; + + bool dispatching_load_queue:1; + bool dispatching_dbus_queue:1; + + bool taint_usr:1; + + bool test_run:1; + + /* If non-zero, exit with the following value when the systemd + * process terminate. Useful for containers: systemd-nspawn could get + * the return value. */ + uint8_t return_value; + + ShowStatus show_status; + bool confirm_spawn; + bool no_console_output; + + ExecOutput default_std_output, default_std_error; + + usec_t default_restart_usec, default_timeout_start_usec, default_timeout_stop_usec; + + usec_t default_start_limit_interval; + unsigned default_start_limit_burst; + + bool default_cpu_accounting; + bool default_memory_accounting; + bool default_blockio_accounting; + bool default_tasks_accounting; + + uint64_t default_tasks_max; + usec_t default_timer_accuracy_usec; + + struct rlimit *rlimit[_RLIMIT_MAX]; + + /* non-zero if we are reloading or reexecuting, */ + int n_reloading; + + unsigned n_installed_jobs; + unsigned n_failed_jobs; + + /* Jobs in progress watching */ + unsigned n_running_jobs; + unsigned n_on_console; + unsigned jobs_in_progress_iteration; + + /* Do we have any outstanding password prompts? */ + int have_ask_password; + int ask_password_inotify_fd; + sd_event_source *ask_password_event_source; + + /* Type=idle pipes */ + int idle_pipe[4]; + sd_event_source *idle_pipe_event_source; + + char *switch_root; + char *switch_root_init; + + /* This maps all possible path prefixes to the units needing + * them. It's a hashmap with a path string as key and a Set as + * value where Unit objects are contained. */ + Hashmap *units_requiring_mounts_for; + + /* Reference to the kdbus bus control fd */ + int kdbus_fd; + + /* Used for processing polkit authorization responses */ + Hashmap *polkit_registry; + + /* When the user hits C-A-D more than 7 times per 2s, reboot immediately... */ + RateLimit ctrl_alt_del_ratelimit; + + const char *unit_log_field; + const char *unit_log_format_string; + + int first_boot; +}; + +int manager_new(ManagerRunningAs running_as, bool test_run, Manager **m); +Manager* manager_free(Manager *m); + +void manager_enumerate(Manager *m); +int manager_startup(Manager *m, FILE *serialization, FDSet *fds); + +Job *manager_get_job(Manager *m, uint32_t id); +Unit *manager_get_unit(Manager *m, const char *name); + +int manager_get_job_from_dbus_path(Manager *m, const char *s, Job **_j); + +int manager_load_unit_prepare(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **_ret); +int manager_load_unit(Manager *m, const char *name, const char *path, sd_bus_error *e, Unit **_ret); +int manager_load_unit_from_dbus_path(Manager *m, const char *s, sd_bus_error *e, Unit **_u); + +int manager_add_job(Manager *m, JobType type, Unit *unit, JobMode mode, sd_bus_error *e, Job **_ret); +int manager_add_job_by_name(Manager *m, JobType type, const char *name, JobMode mode, sd_bus_error *e, Job **_ret); +int manager_add_job_by_name_and_warn(Manager *m, JobType type, const char *name, JobMode mode, Job **ret); + +void manager_dump_units(Manager *s, FILE *f, const char *prefix); +void manager_dump_jobs(Manager *s, FILE *f, const char *prefix); + +void manager_clear_jobs(Manager *m); + +unsigned manager_dispatch_load_queue(Manager *m); + +int manager_environment_add(Manager *m, char **minus, char **plus); +int manager_set_default_rlimits(Manager *m, struct rlimit **default_rlimit); + +int manager_loop(Manager *m); + +int manager_open_serialization(Manager *m, FILE **_f); + +int manager_serialize(Manager *m, FILE *f, FDSet *fds, bool switching_root); +int manager_deserialize(Manager *m, FILE *f, FDSet *fds); + +int manager_reload(Manager *m); + +bool manager_is_reloading_or_reexecuting(Manager *m) _pure_; + +void manager_reset_failed(Manager *m); + +void manager_send_unit_audit(Manager *m, Unit *u, int type, bool success); +void manager_send_unit_plymouth(Manager *m, Unit *u); + +bool manager_unit_inactive_or_pending(Manager *m, const char *name); + +void manager_check_finished(Manager *m); + +void manager_recheck_journal(Manager *m); + +void manager_set_show_status(Manager *m, ShowStatus mode); +void manager_set_first_boot(Manager *m, bool b); + +void manager_status_printf(Manager *m, StatusType type, const char *status, const char *format, ...) _printf_(4,5); +void manager_flip_auto_status(Manager *m, bool enable); + +Set *manager_get_units_requiring_mounts_for(Manager *m, const char *path); + +const char *manager_get_runtime_prefix(Manager *m); + +ManagerState manager_state(Manager *m); + +int manager_update_failed_units(Manager *m, Unit *u, bool failed); + +const char *manager_state_to_string(ManagerState m) _const_; +ManagerState manager_state_from_string(const char *s) _pure_; diff --git a/src/libcore/mount-setup.c b/src/libcore/mount-setup.c new file mode 100644 index 0000000000..de1a361cc4 --- /dev/null +++ b/src/libcore/mount-setup.c @@ -0,0 +1,412 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "dev-setup.h" +#include "efivars.h" +#include "label.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "mount-setup.h" +#include "mount-util.h" +#include "path-util.h" +#include "set.h" +#include "smack-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" +#include "virt.h" + +typedef enum MountMode { + MNT_NONE = 0, + MNT_FATAL = 1 << 0, + MNT_IN_CONTAINER = 1 << 1, +} MountMode; + +typedef struct MountPoint { + const char *what; + const char *where; + const char *type; + const char *options; + unsigned long flags; + bool (*condition_fn)(void); + MountMode mode; +} MountPoint; + +/* The first three entries we might need before SELinux is up. The + * fourth (securityfs) is needed by IMA to load a custom policy. The + * other ones we can delay until SELinux and IMA are loaded. When + * SMACK is enabled we need smackfs, too, so it's a fifth one. */ +#ifdef HAVE_SMACK +#define N_EARLY_MOUNT 5 +#else +#define N_EARLY_MOUNT 4 +#endif + +static const MountPoint mount_table[] = { + { "sysfs", "/sys", "sysfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + NULL, MNT_FATAL|MNT_IN_CONTAINER }, + { "proc", "/proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + NULL, MNT_FATAL|MNT_IN_CONTAINER }, + { "devtmpfs", "/dev", "devtmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME, + NULL, MNT_FATAL|MNT_IN_CONTAINER }, + { "securityfs", "/sys/kernel/security", "securityfs", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + NULL, MNT_NONE }, +#ifdef HAVE_SMACK + { "smackfs", "/sys/fs/smackfs", "smackfs", "smackfsdef=*", MS_NOSUID|MS_NOEXEC|MS_NODEV, + mac_smack_use, MNT_FATAL }, + { "tmpfs", "/dev/shm", "tmpfs", "mode=1777,smackfsroot=*", MS_NOSUID|MS_NODEV|MS_STRICTATIME, + mac_smack_use, MNT_FATAL }, +#endif + { "tmpfs", "/dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME, + NULL, MNT_FATAL|MNT_IN_CONTAINER }, + { "devpts", "/dev/pts", "devpts", "mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, + NULL, MNT_IN_CONTAINER }, +#ifdef HAVE_SMACK + { "tmpfs", "/run", "tmpfs", "mode=755,smackfsroot=*", MS_NOSUID|MS_NODEV|MS_STRICTATIME, + mac_smack_use, MNT_FATAL }, +#endif + { "tmpfs", "/run", "tmpfs", "mode=755", MS_NOSUID|MS_NODEV|MS_STRICTATIME, + NULL, MNT_FATAL|MNT_IN_CONTAINER }, + { "cgroup", "/sys/fs/cgroup", "cgroup", "__DEVEL__sane_behavior", MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_unified_wanted, MNT_FATAL|MNT_IN_CONTAINER }, + { "tmpfs", "/sys/fs/cgroup", "tmpfs", "mode=755", MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, + cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, + { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd,xattr", MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_legacy_wanted, MNT_IN_CONTAINER }, + { "cgroup", "/sys/fs/cgroup/systemd", "cgroup", "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, + cg_is_legacy_wanted, MNT_FATAL|MNT_IN_CONTAINER }, + { "pstore", "/sys/fs/pstore", "pstore", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV, + NULL, MNT_NONE }, +#ifdef ENABLE_EFI + { "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, + * we just list them here so that we know that we should ignore them */ + +static const char ignore_paths[] = + /* SELinux file systems */ + "/sys/fs/selinux\0" + /* Container bind mounts */ + "/proc/sys\0" + "/dev/console\0" + "/proc/kmsg\0"; + +bool mount_point_is_api(const char *path) { + unsigned i; + + /* Checks if this mount point is considered "API", and hence + * should be ignored */ + + for (i = 0; i < ELEMENTSOF(mount_table); i ++) + if (path_equal(path, mount_table[i].where)) + return true; + + return path_startswith(path, "/sys/fs/cgroup/"); +} + +bool mount_point_ignore(const char *path) { + const char *i; + + NULSTR_FOREACH(i, ignore_paths) + if (path_equal(path, i)) + return true; + + return false; +} + +static int mount_one(const MountPoint *p, bool relabel) { + int r; + + assert(p); + + if (p->condition_fn && !p->condition_fn()) + return 0; + + /* Relabel first, just in case */ + if (relabel) + (void) label_fix(p->where, true, true); + + r = path_is_mount_point(p->where, AT_SYMLINK_FOLLOW); + if (r < 0 && r != -ENOENT) { + log_full_errno((p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG, r, "Failed to determine whether %s is a mount point: %m", p->where); + return (p->mode & MNT_FATAL) ? r : 0; + } + if (r > 0) + return 0; + + /* Skip securityfs in a container */ + if (!(p->mode & MNT_IN_CONTAINER) && detect_container() > 0) + return 0; + + /* The access mode here doesn't really matter too much, since + * the mounted file system will take precedence anyway. */ + if (relabel) + (void) mkdir_p_label(p->where, 0755); + else + (void) mkdir_p(p->where, 0755); + + log_debug("Mounting %s to %s of type %s with options %s.", + p->what, + p->where, + p->type, + strna(p->options)); + + if (mount(p->what, + p->where, + p->type, + p->flags, + p->options) < 0) { + log_full_errno((p->mode & MNT_FATAL) ? LOG_ERR : LOG_DEBUG, errno, "Failed to mount %s at %s: %m", p->type, p->where); + return (p->mode & MNT_FATAL) ? -errno : 0; + } + + /* Relabel again, since we now mounted something fresh here */ + if (relabel) + (void) label_fix(p->where, false, false); + + return 1; +} + +static int mount_points_setup(unsigned n, bool loaded_policy) { + unsigned i; + int r = 0; + + for (i = 0; i < n; i ++) { + int j; + + j = mount_one(mount_table + i, loaded_policy); + if (j != 0 && r >= 0) + r = j; + } + + return r; +} + +int mount_setup_early(void) { + assert_cc(N_EARLY_MOUNT <= ELEMENTSOF(mount_table)); + + /* Do a minimal mount of /proc and friends to enable the most + * basic stuff, such as SELinux */ + return mount_points_setup(N_EARLY_MOUNT, false); +} + +int mount_cgroup_controllers(char ***join_controllers) { + _cleanup_set_free_free_ Set *controllers = NULL; + int r; + + if (!cg_is_legacy_wanted()) + return 0; + + /* Mount all available cgroup controllers that are built into the kernel. */ + + controllers = set_new(&string_hash_ops); + if (!controllers) + return log_oom(); + + r = cg_kernel_controllers(controllers); + if (r < 0) + return log_error_errno(r, "Failed to enumerate cgroup controllers: %m"); + + for (;;) { + _cleanup_free_ char *options = NULL, *controller = NULL, *where = NULL; + MountPoint p = { + .what = "cgroup", + .type = "cgroup", + .flags = MS_NOSUID|MS_NOEXEC|MS_NODEV, + .mode = MNT_IN_CONTAINER, + }; + char ***k = NULL; + + controller = set_steal_first(controllers); + if (!controller) + break; + + if (join_controllers) + for (k = join_controllers; *k; k++) + if (strv_find(*k, controller)) + break; + + if (k && *k) { + char **i, **j; + + for (i = *k, j = *k; *i; i++) { + + if (!streq(*i, controller)) { + _cleanup_free_ char *t; + + t = set_remove(controllers, *i); + if (!t) { + free(*i); + continue; + } + } + + *(j++) = *i; + } + + *j = NULL; + + options = strv_join(*k, ","); + if (!options) + return log_oom(); + } else { + options = controller; + controller = NULL; + } + + where = strappend("/sys/fs/cgroup/", options); + if (!where) + return log_oom(); + + p.where = where; + p.options = options; + + r = mount_one(&p, true); + if (r < 0) + return r; + + if (r > 0 && k && *k) { + char **i; + + for (i = *k; *i; i++) { + _cleanup_free_ char *t = NULL; + + t = strappend("/sys/fs/cgroup/", *i); + if (!t) + return log_oom(); + + r = symlink(options, t); + if (r >= 0) { +#ifdef SMACK_RUN_LABEL + _cleanup_free_ char *src; + src = strappend("/sys/fs/cgroup/", options); + if (!src) + return log_oom(); + r = mac_smack_copy(t, src); + if (r < 0 && r != -EOPNOTSUPP) + return log_error_errno(r, "Failed to copy smack label from %s to %s: %m", src, t); +#endif + } else if (errno != EEXIST) + return log_error_errno(errno, "Failed to create symlink %s: %m", t); + } + } + } + + /* Now that we mounted everything, let's make the tmpfs the + * cgroup file systems are mounted into read-only. */ + (void) mount("tmpfs", "/sys/fs/cgroup", "tmpfs", MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755"); + + return 0; +} + +#if defined(HAVE_SELINUX) || defined(HAVE_SMACK) +static int nftw_cb( + const char *fpath, + const struct stat *sb, + int tflag, + struct FTW *ftwbuf) { + + /* No need to label /dev twice in a row... */ + if (_unlikely_(ftwbuf->level == 0)) + return FTW_CONTINUE; + + label_fix(fpath, false, false); + + /* /run/initramfs is static data and big, no need to + * dynamically relabel its contents at boot... */ + if (_unlikely_(ftwbuf->level == 1 && + tflag == FTW_D && + streq(fpath, "/run/initramfs"))) + return FTW_SKIP_SUBTREE; + + return FTW_CONTINUE; +}; +#endif + +int mount_setup(bool loaded_policy) { + int r = 0; + + r = mount_points_setup(ELEMENTSOF(mount_table), loaded_policy); + + if (r < 0) + return r; + +#if defined(HAVE_SELINUX) || defined(HAVE_SMACK) + /* Nodes in devtmpfs and /run need to be manually updated for + * the appropriate labels, after mounting. The other virtual + * API file systems like /sys and /proc do not need that, they + * use the same label for all their files. */ + if (loaded_policy) { + usec_t before_relabel, after_relabel; + char timespan[FORMAT_TIMESPAN_MAX]; + + before_relabel = now(CLOCK_MONOTONIC); + + nftw("/dev", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); + nftw("/run", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL); + + after_relabel = now(CLOCK_MONOTONIC); + + log_info("Relabelled /dev and /run in %s.", + format_timespan(timespan, sizeof(timespan), after_relabel - before_relabel, 0)); + } +#endif + + /* Create a few default symlinks, which are normally created + * by udevd, but some scripts might need them before we start + * udevd. */ + dev_setup(NULL, UID_INVALID, GID_INVALID); + + /* Mark the root directory as shared in regards to mount + * propagation. The kernel defaults to "private", but we think + * it makes more sense to have a default of "shared" so that + * nspawn and the container tools work out of the box. If + * specific setups need other settings they can reset the + * propagation mode to private if needed. */ + if (detect_container() <= 0) + if (mount(NULL, "/", NULL, MS_REC|MS_SHARED, NULL) < 0) + log_warning_errno(errno, "Failed to set up the root directory for shared mount propagation: %m"); + + /* Create a few directories we always want around, Note that + * sd_booted() checks for /run/systemd/system, so this mkdir + * 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); + + return 0; +} diff --git a/src/libcore/mount-setup.h b/src/libcore/mount-setup.h new file mode 100644 index 0000000000..647bd770ae --- /dev/null +++ b/src/libcore/mount-setup.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +int mount_setup_early(void); +int mount_setup(bool loaded_policy); + +int mount_cgroup_controllers(char ***join_controllers); + +bool mount_point_is_api(const char *path); +bool mount_point_ignore(const char *path); diff --git a/src/libcore/mount.c b/src/libcore/mount.c new file mode 100644 index 0000000000..d573e92759 --- /dev/null +++ b/src/libcore/mount.c @@ -0,0 +1,1873 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "dbus-mount.h" +#include "escape.h" +#include "exit-status.h" +#include "formats-util.h" +#include "fstab-util.h" +#include "log.h" +#include "manager.h" +#include "mkdir.h" +#include "mount-setup.h" +#include "mount-util.h" +#include "mount.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit.h" + +#define RETRY_UMOUNT_MAX 32 + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct libmnt_table*, mnt_free_table); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct libmnt_iter*, mnt_free_iter); + +static const UnitActiveState state_translation_table[_MOUNT_STATE_MAX] = { + [MOUNT_DEAD] = UNIT_INACTIVE, + [MOUNT_MOUNTING] = UNIT_ACTIVATING, + [MOUNT_MOUNTING_DONE] = UNIT_ACTIVE, + [MOUNT_MOUNTED] = UNIT_ACTIVE, + [MOUNT_REMOUNTING] = UNIT_RELOADING, + [MOUNT_UNMOUNTING] = UNIT_DEACTIVATING, + [MOUNT_MOUNTING_SIGTERM] = UNIT_DEACTIVATING, + [MOUNT_MOUNTING_SIGKILL] = UNIT_DEACTIVATING, + [MOUNT_REMOUNTING_SIGTERM] = UNIT_RELOADING, + [MOUNT_REMOUNTING_SIGKILL] = UNIT_RELOADING, + [MOUNT_UNMOUNTING_SIGTERM] = UNIT_DEACTIVATING, + [MOUNT_UNMOUNTING_SIGKILL] = UNIT_DEACTIVATING, + [MOUNT_FAILED] = UNIT_FAILED +}; + +static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); +static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); + +static bool mount_needs_network(const char *options, const char *fstype) { + if (fstab_test_option(options, "_netdev\0")) + return true; + + if (fstype && fstype_is_network(fstype)) + return true; + + return false; +} + +static bool mount_is_network(const MountParameters *p) { + assert(p); + + return mount_needs_network(p->options, p->fstype); +} + +static bool mount_is_bind(const MountParameters *p) { + assert(p); + + if (fstab_test_option(p->options, "bind\0" "rbind\0")) + return true; + + if (p->fstype && STR_IN_SET(p->fstype, "bind", "rbind")) + return true; + + return false; +} + +static bool mount_is_auto(const MountParameters *p) { + assert(p); + + return !fstab_test_option(p->options, "noauto\0"); +} + +static bool needs_quota(const MountParameters *p) { + assert(p); + + /* Quotas are not enabled on network filesystems, + * but we want them, for example, on storage connected via iscsi */ + if (p->fstype && fstype_is_network(p->fstype)) + return false; + + if (mount_is_bind(p)) + return false; + + return fstab_test_option(p->options, + "usrquota\0" "grpquota\0" "quota\0" "usrjquota\0" "grpjquota\0"); +} + +static void mount_init(Unit *u) { + Mount *m = MOUNT(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + m->timeout_usec = u->manager->default_timeout_start_usec; + m->directory_mode = 0755; + + if (unit_has_name(u, "-.mount")) { + /* Don't allow start/stop for root directory */ + u->refuse_manual_start = true; + u->refuse_manual_stop = true; + } else { + /* The stdio/kmsg bridge socket is on /, in order to avoid a + * dep loop, don't use kmsg logging for -.mount */ + m->exec_context.std_output = u->manager->default_std_output; + m->exec_context.std_error = u->manager->default_std_error; + } + + /* We need to make sure that /usr/bin/mount is always called + * in the same process group as us, so that the autofs kernel + * side doesn't send us another mount request while we are + * already trying to comply its last one. */ + m->exec_context.same_pgrp = true; + + m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; + + u->ignore_on_isolate = true; +} + +static int mount_arm_timer(Mount *m, usec_t usec) { + int r; + + assert(m); + + if (m->timer_event_source) { + r = sd_event_source_set_time(m->timer_event_source, usec); + if (r < 0) + return r; + + return sd_event_source_set_enabled(m->timer_event_source, SD_EVENT_ONESHOT); + } + + if (usec == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + UNIT(m)->manager->event, + &m->timer_event_source, + CLOCK_MONOTONIC, + usec, 0, + mount_dispatch_timer, m); + if (r < 0) + return r; + + (void) sd_event_source_set_description(m->timer_event_source, "mount-timer"); + + return 0; +} + +static void mount_unwatch_control_pid(Mount *m) { + assert(m); + + if (m->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(m), m->control_pid); + m->control_pid = 0; +} + +static void mount_parameters_done(MountParameters *p) { + assert(p); + + free(p->what); + free(p->options); + free(p->fstype); + + p->what = p->options = p->fstype = NULL; +} + +static void mount_done(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + m->where = mfree(m->where); + + mount_parameters_done(&m->parameters_proc_self_mountinfo); + mount_parameters_done(&m->parameters_fragment); + + m->exec_runtime = exec_runtime_unref(m->exec_runtime); + exec_command_done_array(m->exec_command, _MOUNT_EXEC_COMMAND_MAX); + m->control_command = NULL; + + mount_unwatch_control_pid(m); + + m->timer_event_source = sd_event_source_unref(m->timer_event_source); +} + +_pure_ static MountParameters* get_mount_parameters_fragment(Mount *m) { + assert(m); + + if (m->from_fragment) + return &m->parameters_fragment; + + return NULL; +} + +_pure_ static MountParameters* get_mount_parameters(Mount *m) { + assert(m); + + if (m->from_proc_self_mountinfo) + return &m->parameters_proc_self_mountinfo; + + return get_mount_parameters_fragment(m); +} + +static int mount_add_mount_links(Mount *m) { + _cleanup_free_ char *parent = NULL; + MountParameters *pm; + Unit *other; + Iterator i; + Set *s; + int r; + + assert(m); + + if (!path_equal(m->where, "/")) { + /* Adds in links to other mount points that might lie further + * up in the hierarchy */ + + parent = dirname_malloc(m->where); + if (!parent) + return -ENOMEM; + + r = unit_require_mounts_for(UNIT(m), parent); + if (r < 0) + return r; + } + + /* Adds in links to other mount points that might be needed + * for the source path (if this is a bind mount) to be + * available. */ + pm = get_mount_parameters_fragment(m); + if (pm && pm->what && + path_is_absolute(pm->what) && + !mount_is_network(pm)) { + + r = unit_require_mounts_for(UNIT(m), pm->what); + if (r < 0) + return r; + } + + /* Adds in links to other units that use this path or paths + * further down in the hierarchy */ + s = manager_get_units_requiring_mounts_for(UNIT(m)->manager, m->where); + SET_FOREACH(other, s, i) { + + if (other->load_state != UNIT_LOADED) + continue; + + if (other == UNIT(m)) + continue; + + r = unit_add_dependency(other, UNIT_AFTER, UNIT(m), true); + if (r < 0) + return r; + + if (UNIT(m)->fragment_path) { + /* If we have fragment configuration, then make this dependency required */ + r = unit_add_dependency(other, UNIT_REQUIRES, UNIT(m), true); + if (r < 0) + return r; + } + } + + return 0; +} + +static int mount_add_device_links(Mount *m) { + MountParameters *p; + bool device_wants_mount = false; + int r; + + assert(m); + + p = get_mount_parameters(m); + if (!p) + return 0; + + if (!p->what) + return 0; + + if (mount_is_bind(p)) + return 0; + + if (!is_device_path(p->what)) + return 0; + + /* /dev/root is a really weird thing, it's not a real device, + * but just a path the kernel exports for the root file system + * specified on the kernel command line. Ignore it here. */ + if (path_equal(p->what, "/dev/root")) + return 0; + + if (path_equal(m->where, "/")) + return 0; + + if (mount_is_auto(p) && UNIT(m)->manager->running_as == MANAGER_SYSTEM) + device_wants_mount = true; + + r = unit_add_node_link(UNIT(m), p->what, device_wants_mount, m->from_fragment ? UNIT_BINDS_TO : UNIT_REQUIRES); + if (r < 0) + return r; + + return 0; +} + +static int mount_add_quota_links(Mount *m) { + int r; + MountParameters *p; + + assert(m); + + if (UNIT(m)->manager->running_as != MANAGER_SYSTEM) + return 0; + + p = get_mount_parameters_fragment(m); + if (!p) + return 0; + + if (!needs_quota(p)) + return 0; + + r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTACHECK_SERVICE, NULL, true); + if (r < 0) + return r; + + r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_WANTS, SPECIAL_QUOTAON_SERVICE, NULL, true); + if (r < 0) + return r; + + return 0; +} + +static bool should_umount(Mount *m) { + MountParameters *p; + + if (path_equal(m->where, "/") || + path_equal(m->where, "/usr")) + return false; + + p = get_mount_parameters(m); + if (p && fstab_test_option(p->options, "x-initrd.mount\0") && + !in_initrd()) + return false; + + return true; +} + +static int mount_add_default_dependencies(Mount *m) { + MountParameters *p; + const char *after; + int r; + + assert(m); + + if (!UNIT(m)->default_dependencies) + return 0; + + if (UNIT(m)->manager->running_as != MANAGER_SYSTEM) + return 0; + + /* We do not add any default dependencies to / and /usr, since + * they are guaranteed to stay mounted the whole time, since + * our system is on it. Also, don't bother with anything + * mounted below virtual file systems, it's also going to be + * virtual, and hence not worth the effort. */ + if (path_equal(m->where, "/") || + path_equal(m->where, "/usr") || + path_startswith(m->where, "/proc") || + path_startswith(m->where, "/sys") || + path_startswith(m->where, "/dev")) + return 0; + + p = get_mount_parameters(m); + if (!p) + return 0; + + if (mount_is_network(p)) { + /* We order ourselves after network.target. This is + * primarily useful at shutdown: services that take + * down the network should order themselves before + * network.target, so that they are shut down only + * after this mount unit is stopped. */ + + r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, SPECIAL_NETWORK_TARGET, NULL, true); + if (r < 0) + return r; + + /* We pull in network-online.target, and order + * ourselves after it. This is useful at start-up to + * actively pull in tools that want to be started + * before we start mounting network file systems, and + * whose purpose it is to delay this until the network + * is "up". */ + + r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_WANTS, UNIT_AFTER, SPECIAL_NETWORK_ONLINE_TARGET, NULL, true); + if (r < 0) + return r; + + after = SPECIAL_REMOTE_FS_PRE_TARGET; + } else + after = SPECIAL_LOCAL_FS_PRE_TARGET; + + r = unit_add_dependency_by_name(UNIT(m), UNIT_AFTER, after, NULL, true); + if (r < 0) + return r; + + if (should_umount(m)) { + r = unit_add_two_dependencies_by_name(UNIT(m), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); + if (r < 0) + return r; + } + + return 0; +} + +static int mount_verify(Mount *m) { + _cleanup_free_ char *e = NULL; + int r; + + assert(m); + + if (UNIT(m)->load_state != UNIT_LOADED) + return 0; + + if (!m->from_fragment && !m->from_proc_self_mountinfo) + return -ENOENT; + + r = unit_name_from_path(m->where, ".mount", &e); + if (r < 0) + return log_unit_error_errno(UNIT(m), r, "Failed to generate unit name from mount path: %m"); + + if (!unit_has_name(UNIT(m), e)) { + log_unit_error(UNIT(m), "Where= setting doesn't match unit name. Refusing."); + return -EINVAL; + } + + if (mount_point_is_api(m->where) || mount_point_ignore(m->where)) { + log_unit_error(UNIT(m), "Cannot create mount unit for API file system %s. Refusing.", m->where); + return -EINVAL; + } + + if (UNIT(m)->fragment_path && !m->parameters_fragment.what) { + log_unit_error(UNIT(m), "What= setting is missing. Refusing."); + return -EBADMSG; + } + + if (m->exec_context.pam_name && m->kill_context.kill_mode != KILL_CONTROL_GROUP) { + log_unit_error(UNIT(m), "Unit has PAM enabled. Kill mode must be set to control-group'. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int mount_add_extras(Mount *m) { + Unit *u = UNIT(m); + int r; + + assert(m); + + if (u->fragment_path) + m->from_fragment = true; + + if (!m->where) { + r = unit_name_to_path(u->id, &m->where); + if (r < 0) + return r; + } + + path_kill_slashes(m->where); + + if (!u->description) { + r = unit_set_description(u, m->where); + if (r < 0) + return r; + } + + r = mount_add_device_links(m); + if (r < 0) + return r; + + r = mount_add_mount_links(m); + if (r < 0) + return r; + + r = mount_add_quota_links(m); + if (r < 0) + return r; + + r = unit_patch_contexts(u); + if (r < 0) + return r; + + r = unit_add_exec_dependencies(u, &m->exec_context); + if (r < 0) + return r; + + r = unit_set_default_slice(u); + if (r < 0) + return r; + + r = mount_add_default_dependencies(m); + if (r < 0) + return r; + + return 0; +} + +static int mount_load(Unit *u) { + Mount *m = MOUNT(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + if (m->from_proc_self_mountinfo) + r = unit_load_fragment_and_dropin_optional(u); + else + r = unit_load_fragment_and_dropin(u); + + if (r < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + r = mount_add_extras(m); + if (r < 0) + return r; + } + + return mount_verify(m); +} + +static int mount_notify_automount(Mount *m, MountState old_state, MountState state) { + Unit *p; + int r; + Iterator i; + + assert(m); + + SET_FOREACH(p, UNIT(m)->dependencies[UNIT_TRIGGERED_BY], i) + if (p->type == UNIT_AUTOMOUNT) { + r = automount_update_mount(AUTOMOUNT(p), old_state, state); + if (r < 0) + return r; + } + + return 0; +} + +static void mount_set_state(Mount *m, MountState state) { + MountState old_state; + assert(m); + + old_state = m->state; + m->state = state; + + if (state != MOUNT_MOUNTING && + state != MOUNT_MOUNTING_DONE && + state != MOUNT_REMOUNTING && + state != MOUNT_UNMOUNTING && + state != MOUNT_MOUNTING_SIGTERM && + state != MOUNT_MOUNTING_SIGKILL && + state != MOUNT_UNMOUNTING_SIGTERM && + state != MOUNT_UNMOUNTING_SIGKILL && + state != MOUNT_REMOUNTING_SIGTERM && + state != MOUNT_REMOUNTING_SIGKILL) { + m->timer_event_source = sd_event_source_unref(m->timer_event_source); + mount_unwatch_control_pid(m); + m->control_command = NULL; + m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; + } + + mount_notify_automount(m, old_state, state); + + if (state != old_state) + log_unit_debug(UNIT(m), "Changed %s -> %s", mount_state_to_string(old_state), mount_state_to_string(state)); + + unit_notify(UNIT(m), state_translation_table[old_state], state_translation_table[state], m->reload_result == MOUNT_SUCCESS); + m->reload_result = MOUNT_SUCCESS; +} + +static int mount_coldplug(Unit *u) { + Mount *m = MOUNT(u); + MountState new_state = MOUNT_DEAD; + int r; + + assert(m); + assert(m->state == MOUNT_DEAD); + + if (m->deserialized_state != m->state) + new_state = m->deserialized_state; + else if (m->from_proc_self_mountinfo) + new_state = MOUNT_MOUNTED; + + if (new_state == m->state) + return 0; + + if (m->control_pid > 0 && + pid_is_unwaited(m->control_pid) && + IN_SET(new_state, + MOUNT_MOUNTING, + MOUNT_MOUNTING_DONE, + MOUNT_REMOUNTING, + MOUNT_UNMOUNTING, + MOUNT_MOUNTING_SIGTERM, + MOUNT_MOUNTING_SIGKILL, + MOUNT_UNMOUNTING_SIGTERM, + MOUNT_UNMOUNTING_SIGKILL, + MOUNT_REMOUNTING_SIGTERM, + MOUNT_REMOUNTING_SIGKILL)) { + + r = unit_watch_pid(UNIT(m), m->control_pid); + if (r < 0) + return r; + + r = mount_arm_timer(m, usec_add(u->state_change_timestamp.monotonic, m->timeout_usec)); + if (r < 0) + return r; + } + + mount_set_state(m, new_state); + return 0; +} + +static void mount_dump(Unit *u, FILE *f, const char *prefix) { + Mount *m = MOUNT(u); + MountParameters *p; + + assert(m); + assert(f); + + p = get_mount_parameters(m); + + fprintf(f, + "%sMount State: %s\n" + "%sResult: %s\n" + "%sWhere: %s\n" + "%sWhat: %s\n" + "%sFile System Type: %s\n" + "%sOptions: %s\n" + "%sFrom /proc/self/mountinfo: %s\n" + "%sFrom fragment: %s\n" + "%sDirectoryMode: %04o\n", + prefix, mount_state_to_string(m->state), + prefix, mount_result_to_string(m->result), + prefix, m->where, + prefix, p ? strna(p->what) : "n/a", + prefix, p ? strna(p->fstype) : "n/a", + prefix, p ? strna(p->options) : "n/a", + prefix, yes_no(m->from_proc_self_mountinfo), + prefix, yes_no(m->from_fragment), + prefix, m->directory_mode); + + if (m->control_pid > 0) + fprintf(f, + "%sControl PID: "PID_FMT"\n", + prefix, m->control_pid); + + exec_context_dump(&m->exec_context, f, prefix); + kill_context_dump(&m->kill_context, f, prefix); +} + +static int mount_spawn(Mount *m, ExecCommand *c, pid_t *_pid) { + pid_t pid; + int r; + ExecParameters exec_params = { + .apply_permissions = true, + .apply_chroot = true, + .apply_tty_stdin = true, + .bus_endpoint_fd = -1, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, + }; + + assert(m); + assert(c); + assert(_pid); + + (void) unit_realize_cgroup(UNIT(m)); + if (m->reset_cpu_usage) { + (void) unit_reset_cpu_usage(UNIT(m)); + m->reset_cpu_usage = false; + } + + r = unit_setup_exec_runtime(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; + + exec_params.environment = UNIT(m)->manager->environment; + exec_params.confirm_spawn = UNIT(m)->manager->confirm_spawn; + exec_params.cgroup_supported = UNIT(m)->manager->cgroup_supported; + exec_params.cgroup_path = UNIT(m)->cgroup_path; + exec_params.cgroup_delegate = m->cgroup_context.delegate; + exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(m)->manager); + + r = exec_spawn(UNIT(m), + c, + &m->exec_context, + &exec_params, + m->exec_runtime, + &pid); + if (r < 0) + return r; + + r = unit_watch_pid(UNIT(m), pid); + if (r < 0) + /* FIXME: we need to do something here */ + return r; + + *_pid = pid; + + return 0; +} + +static void mount_enter_dead(Mount *m, MountResult f) { + assert(m); + + if (f != MOUNT_SUCCESS) + m->result = f; + + 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); +} + +static void mount_enter_mounted(Mount *m, MountResult f) { + assert(m); + + if (f != MOUNT_SUCCESS) + m->result = f; + + mount_set_state(m, MOUNT_MOUNTED); +} + +static void mount_enter_signal(Mount *m, MountState state, MountResult f) { + int r; + + assert(m); + + if (f != MOUNT_SUCCESS) + m->result = f; + + r = unit_kill_context( + UNIT(m), + &m->kill_context, + (state != MOUNT_MOUNTING_SIGTERM && state != MOUNT_UNMOUNTING_SIGTERM && state != MOUNT_REMOUNTING_SIGTERM) ? + KILL_KILL : KILL_TERMINATE, + -1, + m->control_pid, + false); + if (r < 0) + goto fail; + + if (r > 0) { + r = mount_arm_timer(m, usec_add(now(CLOCK_MONOTONIC), m->timeout_usec)); + if (r < 0) + goto fail; + + mount_set_state(m, state); + } else if (state == MOUNT_REMOUNTING_SIGTERM) + mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, MOUNT_SUCCESS); + else if (state == MOUNT_REMOUNTING_SIGKILL) + mount_enter_mounted(m, MOUNT_SUCCESS); + else if (state == MOUNT_MOUNTING_SIGTERM) + mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, MOUNT_SUCCESS); + else if (state == MOUNT_UNMOUNTING_SIGTERM) + mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_SUCCESS); + else + mount_enter_dead(m, MOUNT_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(m), r, "Failed to kill processes: %m"); + + if (state == MOUNT_REMOUNTING_SIGTERM || state == MOUNT_REMOUNTING_SIGKILL) + mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); + else + mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); +} + +static void mount_enter_unmounting(Mount *m) { + int r; + + assert(m); + + /* Start counting our attempts */ + if (!IN_SET(m->state, + MOUNT_UNMOUNTING, + MOUNT_UNMOUNTING_SIGTERM, + MOUNT_UNMOUNTING_SIGKILL)) + m->n_retry_umount = 0; + + m->control_command_id = MOUNT_EXEC_UNMOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_UNMOUNT; + + r = exec_command_set(m->control_command, UMOUNT_PATH, m->where, NULL); + if (r < 0) + goto fail; + + mount_unwatch_control_pid(m); + + r = mount_spawn(m, m->control_command, &m->control_pid); + if (r < 0) + goto fail; + + mount_set_state(m, MOUNT_UNMOUNTING); + + return; + +fail: + log_unit_warning_errno(UNIT(m), r, "Failed to run 'umount' task: %m"); + mount_enter_mounted(m, MOUNT_FAILURE_RESOURCES); +} + +static int mount_get_opts(Mount *m, char **ret) { + return fstab_filter_options(m->parameters_fragment.options, + "nofail\0" "noauto\0" "auto\0", NULL, NULL, ret); +} + +static void mount_enter_mounting(Mount *m) { + int r; + MountParameters *p; + + assert(m); + + m->control_command_id = MOUNT_EXEC_MOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_MOUNT; + + r = unit_fail_if_symlink(UNIT(m), m->where); + if (r < 0) + goto fail; + + (void) mkdir_p_label(m->where, m->directory_mode); + + unit_warn_if_dir_nonempty(UNIT(m), m->where); + + /* Create the source directory for bind-mounts if needed */ + p = get_mount_parameters_fragment(m); + if (p && mount_is_bind(p)) + (void) mkdir_p_label(p->what, m->directory_mode); + + if (m->from_fragment) { + _cleanup_free_ char *opts = NULL; + + r = mount_get_opts(m, &opts); + if (r < 0) + goto fail; + + r = exec_command_set(m->control_command, MOUNT_PATH, + m->parameters_fragment.what, m->where, NULL); + if (r >= 0 && m->sloppy_options) + r = exec_command_append(m->control_command, "-s", NULL); + if (r >= 0 && m->parameters_fragment.fstype) + r = exec_command_append(m->control_command, "-t", m->parameters_fragment.fstype, NULL); + if (r >= 0 && !isempty(opts)) + r = exec_command_append(m->control_command, "-o", opts, NULL); + } else + r = -ENOENT; + + if (r < 0) + goto fail; + + mount_unwatch_control_pid(m); + + r = mount_spawn(m, m->control_command, &m->control_pid); + if (r < 0) + goto fail; + + mount_set_state(m, MOUNT_MOUNTING); + + return; + +fail: + log_unit_warning_errno(UNIT(m), r, "Failed to run 'mount' task: %m"); + mount_enter_dead(m, MOUNT_FAILURE_RESOURCES); +} + +static void mount_enter_remounting(Mount *m) { + int r; + + assert(m); + + m->control_command_id = MOUNT_EXEC_REMOUNT; + m->control_command = m->exec_command + MOUNT_EXEC_REMOUNT; + + if (m->from_fragment) { + const char *o; + + if (m->parameters_fragment.options) + o = strjoina("remount,", m->parameters_fragment.options); + else + o = "remount"; + + r = exec_command_set(m->control_command, MOUNT_PATH, + m->parameters_fragment.what, m->where, + "-o", o, NULL); + if (r >= 0 && m->sloppy_options) + r = exec_command_append(m->control_command, "-s", NULL); + if (r >= 0 && m->parameters_fragment.fstype) + r = exec_command_append(m->control_command, "-t", m->parameters_fragment.fstype, NULL); + } else + r = -ENOENT; + + if (r < 0) + goto fail; + + mount_unwatch_control_pid(m); + + r = mount_spawn(m, m->control_command, &m->control_pid); + if (r < 0) + goto fail; + + mount_set_state(m, MOUNT_REMOUNTING); + + return; + +fail: + log_unit_warning_errno(UNIT(m), r, "Failed to run 'remount' task: %m"); + m->reload_result = MOUNT_FAILURE_RESOURCES; + mount_enter_mounted(m, MOUNT_SUCCESS); +} + +static int mount_start(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (m->state == MOUNT_UNMOUNTING || + m->state == MOUNT_UNMOUNTING_SIGTERM || + m->state == MOUNT_UNMOUNTING_SIGKILL || + m->state == MOUNT_MOUNTING_SIGTERM || + m->state == MOUNT_MOUNTING_SIGKILL) + return -EAGAIN; + + /* Already on it! */ + if (m->state == MOUNT_MOUNTING) + return 0; + + assert(m->state == MOUNT_DEAD || m->state == MOUNT_FAILED); + + m->result = MOUNT_SUCCESS; + m->reload_result = MOUNT_SUCCESS; + m->reset_cpu_usage = true; + + mount_enter_mounting(m); + return 1; +} + +static int mount_stop(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + /* Already on it */ + if (m->state == MOUNT_UNMOUNTING || + m->state == MOUNT_UNMOUNTING_SIGKILL || + m->state == MOUNT_UNMOUNTING_SIGTERM || + m->state == MOUNT_MOUNTING_SIGTERM || + m->state == MOUNT_MOUNTING_SIGKILL) + return 0; + + assert(m->state == MOUNT_MOUNTING || + m->state == MOUNT_MOUNTING_DONE || + m->state == MOUNT_MOUNTED || + m->state == MOUNT_REMOUNTING || + m->state == MOUNT_REMOUNTING_SIGTERM || + m->state == MOUNT_REMOUNTING_SIGKILL); + + mount_enter_unmounting(m); + return 1; +} + +static int mount_reload(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + if (m->state == MOUNT_MOUNTING_DONE) + return -EAGAIN; + + assert(m->state == MOUNT_MOUNTED); + + mount_enter_remounting(m); + return 1; +} + +static int mount_serialize(Unit *u, FILE *f, FDSet *fds) { + Mount *m = MOUNT(u); + + assert(m); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", mount_state_to_string(m->state)); + unit_serialize_item(u, f, "result", mount_result_to_string(m->result)); + unit_serialize_item(u, f, "reload-result", mount_result_to_string(m->reload_result)); + + if (m->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", PID_FMT, m->control_pid); + + if (m->control_command_id >= 0) + unit_serialize_item(u, f, "control-command", mount_exec_command_to_string(m->control_command_id)); + + return 0; +} + +static int mount_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Mount *m = MOUNT(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + MountState state; + + if ((state = mount_state_from_string(value)) < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + m->deserialized_state = state; + } else if (streq(key, "result")) { + MountResult f; + + f = mount_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != MOUNT_SUCCESS) + m->result = f; + + } else if (streq(key, "reload-result")) { + MountResult f; + + f = mount_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse reload result value: %s", value); + else if (f != MOUNT_SUCCESS) + m->reload_result = f; + + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse control-pid value: %s", value); + else + m->control_pid = pid; + } else if (streq(key, "control-command")) { + MountExecCommand id; + + id = mount_exec_command_from_string(value); + if (id < 0) + log_unit_debug(u, "Failed to parse exec-command value: %s", value); + else { + m->control_command_id = id; + m->control_command = m->exec_command + id; + } + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +_pure_ static UnitActiveState mount_active_state(Unit *u) { + assert(u); + + return state_translation_table[MOUNT(u)->state]; +} + +_pure_ static const char *mount_sub_state_to_string(Unit *u) { + assert(u); + + return mount_state_to_string(MOUNT(u)->state); +} + +_pure_ static bool mount_check_gc(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + return m->from_proc_self_mountinfo; +} + +static void mount_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Mount *m = MOUNT(u); + MountResult f; + + assert(m); + assert(pid >= 0); + + if (pid != m->control_pid) + return; + + m->control_pid = 0; + + if (is_clean_exit(code, status, NULL)) + f = MOUNT_SUCCESS; + else if (code == CLD_EXITED) + f = MOUNT_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = MOUNT_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = MOUNT_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown code"); + + if (f != MOUNT_SUCCESS) + m->result = f; + + if (m->control_command) { + exec_status_exit(&m->control_command->exec_status, &m->exec_context, pid, code, status); + + m->control_command = NULL; + m->control_command_id = _MOUNT_EXEC_COMMAND_INVALID; + } + + log_unit_full(u, f == MOUNT_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, + "Mount process exited, code=%s status=%i", sigchld_code_to_string(code), status); + + /* Note that mount(8) returning and the kernel sending us a + * mount table change event might happen out-of-order. If an + * operation succeed we assume the kernel will follow soon too + * and already change into the resulting state. If it fails + * we check if the kernel still knows about the mount. and + * change state accordingly. */ + + switch (m->state) { + + case MOUNT_MOUNTING: + case MOUNT_MOUNTING_DONE: + case MOUNT_MOUNTING_SIGKILL: + case MOUNT_MOUNTING_SIGTERM: + + if (f == MOUNT_SUCCESS) + mount_enter_mounted(m, f); + else if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, f); + else + mount_enter_dead(m, f); + break; + + case MOUNT_REMOUNTING: + case MOUNT_REMOUNTING_SIGKILL: + case MOUNT_REMOUNTING_SIGTERM: + + m->reload_result = f; + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_SUCCESS); + else + mount_enter_dead(m, MOUNT_SUCCESS); + + break; + + case MOUNT_UNMOUNTING: + case MOUNT_UNMOUNTING_SIGKILL: + case MOUNT_UNMOUNTING_SIGTERM: + + if (f == MOUNT_SUCCESS) { + + if (m->from_proc_self_mountinfo) { + + /* Still a mount point? If so, let's + * try again. Most likely there were + * multiple mount points stacked on + * top of each other. Note that due to + * the io event priority logic we can + * be sure the new mountinfo is loaded + * before we process the SIGCHLD for + * the mount command. */ + + if (m->n_retry_umount < RETRY_UMOUNT_MAX) { + log_unit_debug(u, "Mount still present, trying again."); + m->n_retry_umount++; + mount_enter_unmounting(m); + } else { + log_unit_debug(u, "Mount still present after %u attempts to unmount, giving up.", m->n_retry_umount); + mount_enter_mounted(m, f); + } + } else + mount_enter_dead(m, f); + + } else if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, f); + else + mount_enter_dead(m, f); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); +} + +static int mount_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { + Mount *m = MOUNT(userdata); + + assert(m); + assert(m->timer_event_source == source); + + switch (m->state) { + + case MOUNT_MOUNTING: + case MOUNT_MOUNTING_DONE: + log_unit_warning(UNIT(m), "Mounting timed out. Stopping."); + mount_enter_signal(m, MOUNT_MOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); + break; + + case MOUNT_REMOUNTING: + log_unit_warning(UNIT(m), "Remounting timed out. Stopping."); + m->reload_result = MOUNT_FAILURE_TIMEOUT; + mount_enter_mounted(m, MOUNT_SUCCESS); + break; + + case MOUNT_UNMOUNTING: + log_unit_warning(UNIT(m), "Unmounting timed out. Stopping."); + mount_enter_signal(m, MOUNT_UNMOUNTING_SIGTERM, MOUNT_FAILURE_TIMEOUT); + break; + + case MOUNT_MOUNTING_SIGTERM: + if (m->kill_context.send_sigkill) { + log_unit_warning(UNIT(m), "Mounting timed out. Killing."); + mount_enter_signal(m, MOUNT_MOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(m), "Mounting timed out. Skipping SIGKILL. Ignoring."); + + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); + else + mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); + } + break; + + case MOUNT_REMOUNTING_SIGTERM: + if (m->kill_context.send_sigkill) { + log_unit_warning(UNIT(m), "Remounting timed out. Killing."); + mount_enter_signal(m, MOUNT_REMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(m), "Remounting timed out. Skipping SIGKILL. Ignoring."); + + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); + else + mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); + } + break; + + case MOUNT_UNMOUNTING_SIGTERM: + if (m->kill_context.send_sigkill) { + log_unit_warning(UNIT(m), "Unmounting timed out. Killing."); + mount_enter_signal(m, MOUNT_UNMOUNTING_SIGKILL, MOUNT_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(m), "Unmounting timed out. Skipping SIGKILL. Ignoring."); + + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); + else + mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); + } + break; + + case MOUNT_MOUNTING_SIGKILL: + case MOUNT_REMOUNTING_SIGKILL: + case MOUNT_UNMOUNTING_SIGKILL: + log_unit_warning(UNIT(m),"Mount process still around after SIGKILL. Ignoring."); + + if (m->from_proc_self_mountinfo) + mount_enter_mounted(m, MOUNT_FAILURE_TIMEOUT); + else + mount_enter_dead(m, MOUNT_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } + + return 0; +} + +static int mount_setup_unit( + Manager *m, + const char *what, + const char *where, + const char *options, + const char *fstype, + bool set_flags) { + + _cleanup_free_ char *e = NULL, *w = NULL, *o = NULL, *f = NULL; + bool load_extras = false; + MountParameters *p; + bool delete, changed = false; + Unit *u; + int r; + + assert(m); + assert(what); + assert(where); + assert(options); + assert(fstype); + + /* Ignore API mount points. They should never be referenced in + * dependencies ever. */ + if (mount_point_is_api(where) || mount_point_ignore(where)) + return 0; + + if (streq(fstype, "autofs")) + return 0; + + /* probably some kind of swap, ignore */ + if (!is_path(where)) + return 0; + + r = unit_name_from_path(where, ".mount", &e); + if (r < 0) + return r; + + u = manager_get_unit(m, e); + if (!u) { + delete = true; + + u = unit_new(m, sizeof(Mount)); + if (!u) + return log_oom(); + + r = unit_add_name(u, e); + if (r < 0) + goto fail; + + MOUNT(u)->where = strdup(where); + if (!MOUNT(u)->where) { + r = -ENOMEM; + goto fail; + } + + u->source_path = strdup("/proc/self/mountinfo"); + if (!u->source_path) { + r = -ENOMEM; + goto fail; + } + + if (m->running_as == MANAGER_SYSTEM) { + const char* target; + + target = mount_needs_network(options, fstype) ? SPECIAL_REMOTE_FS_TARGET : SPECIAL_LOCAL_FS_TARGET; + r = unit_add_dependency_by_name(u, UNIT_BEFORE, target, NULL, true); + if (r < 0) + goto fail; + + if (should_umount(MOUNT(u))) { + r = unit_add_dependency_by_name(u, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); + if (r < 0) + goto fail; + } + } + + unit_add_to_load_queue(u); + changed = true; + } else { + delete = false; + + if (!MOUNT(u)->where) { + MOUNT(u)->where = strdup(where); + if (!MOUNT(u)->where) { + r = -ENOMEM; + goto fail; + } + } + + if (m->running_as == MANAGER_SYSTEM && + mount_needs_network(options, fstype)) { + /* _netdev option may have shown up late, or on a + * remount. Add remote-fs dependencies, even though + * local-fs ones may already be there. */ + unit_add_dependency_by_name(u, UNIT_BEFORE, SPECIAL_REMOTE_FS_TARGET, NULL, true); + load_extras = true; + } + + if (u->load_state == UNIT_NOT_FOUND) { + u->load_state = UNIT_LOADED; + u->load_error = 0; + + /* Load in the extras later on, after we + * finished initialization of the unit */ + load_extras = true; + changed = true; + } + } + + w = strdup(what); + o = strdup(options); + f = strdup(fstype); + if (!w || !o || !f) { + r = -ENOMEM; + goto fail; + } + + p = &MOUNT(u)->parameters_proc_self_mountinfo; + + changed = changed || + !streq_ptr(p->options, options) || + !streq_ptr(p->what, what) || + !streq_ptr(p->fstype, fstype); + + if (set_flags) { + MOUNT(u)->is_mounted = true; + MOUNT(u)->just_mounted = !MOUNT(u)->from_proc_self_mountinfo; + MOUNT(u)->just_changed = changed; + } + + MOUNT(u)->from_proc_self_mountinfo = true; + + free(p->what); + p->what = w; + w = NULL; + + free(p->options); + p->options = o; + o = NULL; + + free(p->fstype); + p->fstype = f; + f = NULL; + + if (load_extras) { + r = mount_add_extras(MOUNT(u)); + if (r < 0) + goto fail; + } + + if (changed) + unit_add_to_dbus_queue(u); + + return 0; + +fail: + log_warning_errno(r, "Failed to set up mount unit: %m"); + + if (delete && u) + unit_free(u); + + return r; +} + +static int mount_load_proc_self_mountinfo(Manager *m, bool set_flags) { + _cleanup_(mnt_free_tablep) struct libmnt_table *t = NULL; + _cleanup_(mnt_free_iterp) struct libmnt_iter *i = NULL; + int r = 0; + + assert(m); + + t = mnt_new_table(); + if (!t) + return log_oom(); + + i = mnt_new_iter(MNT_ITER_FORWARD); + if (!i) + return log_oom(); + + r = mnt_table_parse_mtab(t, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse /proc/self/mountinfo: %m"); + + r = 0; + for (;;) { + const char *device, *path, *options, *fstype; + _cleanup_free_ char *d = NULL, *p = NULL; + struct libmnt_fs *fs; + int k; + + k = mnt_table_next_fs(t, i, &fs); + if (k == 1) + break; + if (k < 0) + return log_error_errno(k, "Failed to get next entry from /proc/self/mountinfo: %m"); + + device = mnt_fs_get_source(fs); + path = mnt_fs_get_target(fs); + options = mnt_fs_get_options(fs); + fstype = mnt_fs_get_fstype(fs); + + if (!device || !path) + continue; + + if (cunescape(device, UNESCAPE_RELAX, &d) < 0) + return log_oom(); + + if (cunescape(path, UNESCAPE_RELAX, &p) < 0) + return log_oom(); + + (void) device_found_node(m, d, true, DEVICE_FOUND_MOUNT, set_flags); + + k = mount_setup_unit(m, d, p, options, fstype, set_flags); + if (r == 0 && k < 0) + r = k; + } + + return r; +} + +static void mount_shutdown(Manager *m) { + + assert(m); + + m->mount_event_source = sd_event_source_unref(m->mount_event_source); + + mnt_unref_monitor(m->mount_monitor); + m->mount_monitor = NULL; +} + +static int mount_get_timeout(Unit *u, usec_t *timeout) { + Mount *m = MOUNT(u); + usec_t t; + int r; + + if (!m->timer_event_source) + return 0; + + r = sd_event_source_get_time(m->timer_event_source, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + return 0; + + *timeout = t; + return 1; +} + +static void mount_enumerate(Manager *m) { + int r; + + assert(m); + + mnt_init_debug(0); + + if (!m->mount_monitor) { + int fd; + + m->mount_monitor = mnt_new_monitor(); + if (!m->mount_monitor) { + log_oom(); + goto fail; + } + + r = mnt_monitor_enable_kernel(m->mount_monitor, 1); + if (r < 0) { + log_error_errno(r, "Failed to enable watching of kernel mount events: %m"); + goto fail; + } + + r = mnt_monitor_enable_userspace(m->mount_monitor, 1, NULL); + if (r < 0) { + log_error_errno(r, "Failed to enable watching of userspace mount events: %m"); + goto fail; + } + + /* mnt_unref_monitor() will close the fd */ + fd = r = mnt_monitor_get_fd(m->mount_monitor); + if (r < 0) { + log_error_errno(r, "Failed to acquire watch file descriptor: %m"); + goto fail; + } + + r = sd_event_add_io(m->event, &m->mount_event_source, fd, EPOLLIN, mount_dispatch_io, m); + if (r < 0) { + log_error_errno(r, "Failed to watch mount file descriptor: %m"); + goto fail; + } + + r = sd_event_source_set_priority(m->mount_event_source, -10); + if (r < 0) { + log_error_errno(r, "Failed to adjust mount watch priority: %m"); + goto fail; + } + + (void) sd_event_source_set_description(m->mount_event_source, "mount-monitor-dispatch"); + } + + r = mount_load_proc_self_mountinfo(m, false); + if (r < 0) + goto fail; + + return; + +fail: + mount_shutdown(m); +} + +static int mount_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + _cleanup_set_free_ Set *around = NULL, *gone = NULL; + Manager *m = userdata; + const char *what; + Iterator i; + Unit *u; + int r; + + assert(m); + assert(revents & EPOLLIN); + + if (fd == mnt_monitor_get_fd(m->mount_monitor)) { + bool rescan = false; + + /* Drain all events and verify that the event is valid. + * + * Note that libmount also monitors /run/mount mkdir if the + * directory does not exist yet. The mkdir may generate event + * which is irrelevant for us. + * + * error: r < 0; valid: r == 0, false positive: rc == 1 */ + do { + r = mnt_monitor_next_change(m->mount_monitor, NULL, NULL); + if (r == 0) + rescan = true; + else if (r < 0) + return log_error_errno(r, "Failed to drain libmount events"); + } while (r == 0); + + log_debug("libmount event [rescan: %s]", yes_no(rescan)); + if (!rescan) + return 0; + } + + r = mount_load_proc_self_mountinfo(m, true); + if (r < 0) { + /* Reset flags, just in case, for later calls */ + LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_MOUNT]) { + Mount *mount = MOUNT(u); + + mount->is_mounted = mount->just_mounted = mount->just_changed = false; + } + + return 0; + } + + manager_dispatch_load_queue(m); + + LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_MOUNT]) { + Mount *mount = MOUNT(u); + + if (!mount->is_mounted) { + + /* A mount point is not around right now. It + * might be gone, or might never have + * existed. */ + + if (mount->from_proc_self_mountinfo && + mount->parameters_proc_self_mountinfo.what) { + + /* Remember that this device might just have disappeared */ + if (set_ensure_allocated(&gone, &string_hash_ops) < 0 || + set_put(gone, mount->parameters_proc_self_mountinfo.what) < 0) + log_oom(); /* we don't care too much about OOM here... */ + } + + mount->from_proc_self_mountinfo = false; + + switch (mount->state) { + + case MOUNT_MOUNTED: + /* This has just been unmounted by + * somebody else, follow the state + * change. */ + mount_enter_dead(mount, MOUNT_SUCCESS); + break; + + default: + break; + } + + } else if (mount->just_mounted || mount->just_changed) { + + /* A mount point was added or changed */ + + switch (mount->state) { + + case MOUNT_DEAD: + case MOUNT_FAILED: + /* This has just been mounted by + * somebody else, follow the state + * change. */ + mount_enter_mounted(mount, MOUNT_SUCCESS); + break; + + case MOUNT_MOUNTING: + mount_set_state(mount, MOUNT_MOUNTING_DONE); + break; + + default: + /* Nothing really changed, but let's + * issue an notification call + * nonetheless, in case somebody is + * waiting for this. (e.g. file system + * ro/rw remounts.) */ + mount_set_state(mount, mount->state); + break; + } + } + + if (mount->is_mounted && + mount->from_proc_self_mountinfo && + mount->parameters_proc_self_mountinfo.what) { + + if (set_ensure_allocated(&around, &string_hash_ops) < 0 || + set_put(around, mount->parameters_proc_self_mountinfo.what) < 0) + log_oom(); + } + + /* Reset the flags for later calls */ + mount->is_mounted = mount->just_mounted = mount->just_changed = false; + } + + SET_FOREACH(what, gone, i) { + if (set_contains(around, what)) + continue; + + /* Let the device units know that the device is no longer mounted */ + (void) device_found_node(m, what, false, DEVICE_FOUND_MOUNT, true); + } + + return 0; +} + +static void mount_reset_failed(Unit *u) { + Mount *m = MOUNT(u); + + assert(m); + + if (m->state == MOUNT_FAILED) + mount_set_state(m, MOUNT_DEAD); + + m->result = MOUNT_SUCCESS; + m->reload_result = MOUNT_SUCCESS; +} + +static int mount_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + return unit_kill_common(u, who, signo, -1, MOUNT(u)->control_pid, error); +} + +static const char* const mount_exec_command_table[_MOUNT_EXEC_COMMAND_MAX] = { + [MOUNT_EXEC_MOUNT] = "ExecMount", + [MOUNT_EXEC_UNMOUNT] = "ExecUnmount", + [MOUNT_EXEC_REMOUNT] = "ExecRemount", +}; + +DEFINE_STRING_TABLE_LOOKUP(mount_exec_command, MountExecCommand); + +static const char* const mount_result_table[_MOUNT_RESULT_MAX] = { + [MOUNT_SUCCESS] = "success", + [MOUNT_FAILURE_RESOURCES] = "resources", + [MOUNT_FAILURE_TIMEOUT] = "timeout", + [MOUNT_FAILURE_EXIT_CODE] = "exit-code", + [MOUNT_FAILURE_SIGNAL] = "signal", + [MOUNT_FAILURE_CORE_DUMP] = "core-dump" +}; + +DEFINE_STRING_TABLE_LOOKUP(mount_result, MountResult); + +const UnitVTable mount_vtable = { + .object_size = sizeof(Mount), + .exec_context_offset = offsetof(Mount, exec_context), + .cgroup_context_offset = offsetof(Mount, cgroup_context), + .kill_context_offset = offsetof(Mount, kill_context), + .exec_runtime_offset = offsetof(Mount, exec_runtime), + + .sections = + "Unit\0" + "Mount\0" + "Install\0", + .private_section = "Mount", + + .no_alias = true, + .no_instances = true, + + .init = mount_init, + .load = mount_load, + .done = mount_done, + + .coldplug = mount_coldplug, + + .dump = mount_dump, + + .start = mount_start, + .stop = mount_stop, + .reload = mount_reload, + + .kill = mount_kill, + + .serialize = mount_serialize, + .deserialize_item = mount_deserialize_item, + + .active_state = mount_active_state, + .sub_state_to_string = mount_sub_state_to_string, + + .check_gc = mount_check_gc, + + .sigchld_event = mount_sigchld_event, + + .reset_failed = mount_reset_failed, + + .bus_vtable = bus_mount_vtable, + .bus_set_property = bus_mount_set_property, + .bus_commit_properties = bus_mount_commit_properties, + + .get_timeout = mount_get_timeout, + + .can_transient = true, + + .enumerate = mount_enumerate, + .shutdown = mount_shutdown, + + .status_message_formats = { + .starting_stopping = { + [0] = "Mounting %s...", + [1] = "Unmounting %s...", + }, + .finished_start_job = { + [JOB_DONE] = "Mounted %s.", + [JOB_FAILED] = "Failed to mount %s.", + [JOB_TIMEOUT] = "Timed out mounting %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Unmounted %s.", + [JOB_FAILED] = "Failed unmounting %s.", + [JOB_TIMEOUT] = "Timed out unmounting %s.", + }, + }, +}; diff --git a/src/libcore/mount.h b/src/libcore/mount.h new file mode 100644 index 0000000000..3b343c6b1f --- /dev/null +++ b/src/libcore/mount.h @@ -0,0 +1,107 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Mount Mount; + +#include "execute.h" +#include "kill.h" + +typedef enum MountExecCommand { + MOUNT_EXEC_MOUNT, + MOUNT_EXEC_UNMOUNT, + MOUNT_EXEC_REMOUNT, + _MOUNT_EXEC_COMMAND_MAX, + _MOUNT_EXEC_COMMAND_INVALID = -1 +} MountExecCommand; + +typedef enum MountResult { + MOUNT_SUCCESS, + MOUNT_FAILURE_RESOURCES, + MOUNT_FAILURE_TIMEOUT, + MOUNT_FAILURE_EXIT_CODE, + MOUNT_FAILURE_SIGNAL, + MOUNT_FAILURE_CORE_DUMP, + _MOUNT_RESULT_MAX, + _MOUNT_RESULT_INVALID = -1 +} MountResult; + +typedef struct MountParameters { + char *what; + char *options; + char *fstype; +} MountParameters; + +struct Mount { + Unit meta; + + char *where; + + MountParameters parameters_proc_self_mountinfo; + MountParameters parameters_fragment; + + bool from_proc_self_mountinfo:1; + bool from_fragment:1; + + /* Used while looking for mount points that vanished or got + * added from/to /proc/self/mountinfo */ + bool is_mounted:1; + bool just_mounted:1; + bool just_changed:1; + + bool reset_cpu_usage:1; + + bool sloppy_options; + + MountResult result; + MountResult reload_result; + + mode_t directory_mode; + + usec_t timeout_usec; + + ExecCommand exec_command[_MOUNT_EXEC_COMMAND_MAX]; + + ExecContext exec_context; + KillContext kill_context; + CGroupContext cgroup_context; + + ExecRuntime *exec_runtime; + + MountState state, deserialized_state; + + ExecCommand* control_command; + MountExecCommand control_command_id; + pid_t control_pid; + + sd_event_source *timer_event_source; + + unsigned n_retry_umount; +}; + +extern const UnitVTable mount_vtable; + +void mount_fd_event(Manager *m, int events); + +const char* mount_exec_command_to_string(MountExecCommand i) _const_; +MountExecCommand mount_exec_command_from_string(const char *s) _pure_; + +const char* mount_result_to_string(MountResult i) _const_; +MountResult mount_result_from_string(const char *s) _pure_; diff --git a/src/libcore/namespace.c b/src/libcore/namespace.c new file mode 100644 index 0000000000..4fa381db5b --- /dev/null +++ b/src/libcore/namespace.c @@ -0,0 +1,727 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "dev-setup.h" +#include "fd-util.h" +#include "loopback-setup.h" +#include "missing.h" +#include "mkdir.h" +#include "mount-util.h" +#include "namespace.h" +#include "path-util.h" +#include "selinux-util.h" +#include "socket-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "umask-util.h" +#include "user-util.h" +#include "util.h" + +typedef enum MountMode { + /* This is ordered by priority! */ + INACCESSIBLE, + READONLY, + PRIVATE_TMP, + PRIVATE_VAR_TMP, + PRIVATE_DEV, + PRIVATE_BUS_ENDPOINT, + READWRITE +} MountMode; + +typedef struct BindMount { + const char *path; + MountMode mode; + bool done; + bool ignore; +} BindMount; + +static int append_mounts(BindMount **p, char **strv, MountMode mode) { + char **i; + + assert(p); + + STRV_FOREACH(i, strv) { + + (*p)->ignore = false; + (*p)->done = false; + + if ((mode == INACCESSIBLE || mode == READONLY || mode == READWRITE) && (*i)[0] == '-') { + (*p)->ignore = true; + (*i)++; + } + + if (!path_is_absolute(*i)) + return -EINVAL; + + (*p)->path = *i; + (*p)->mode = mode; + (*p)++; + } + + return 0; +} + +static int mount_path_compare(const void *a, const void *b) { + const BindMount *p = a, *q = b; + int d; + + d = path_compare(p->path, q->path); + + if (d == 0) { + /* If the paths are equal, check the mode */ + if (p->mode < q->mode) + return -1; + + if (p->mode > q->mode) + return 1; + + return 0; + } + + /* If the paths are not equal, then order prefixes first */ + return d; +} + +static void drop_duplicates(BindMount *m, unsigned *n) { + BindMount *f, *t, *previous; + + assert(m); + assert(n); + + for (f = m, t = m, previous = NULL; f < m+*n; f++) { + + /* The first one wins */ + if (previous && path_equal(f->path, previous->path)) + continue; + + *t = *f; + + previous = t; + + t++; + } + + *n = t - m; +} + +static int mount_dev(BindMount *m) { + static const char devnodes[] = + "/dev/null\0" + "/dev/zero\0" + "/dev/full\0" + "/dev/random\0" + "/dev/urandom\0" + "/dev/tty\0"; + + char temporary_mount[] = "/tmp/namespace-dev-XXXXXX"; + const char *d, *dev = NULL, *devpts = NULL, *devshm = NULL, *devhugepages = NULL, *devmqueue = NULL, *devlog = NULL, *devptmx = NULL; + _cleanup_umask_ mode_t u; + int r; + + assert(m); + + u = umask(0000); + + if (!mkdtemp(temporary_mount)) + return -errno; + + dev = strjoina(temporary_mount, "/dev"); + (void) mkdir(dev, 0755); + if (mount("tmpfs", dev, "tmpfs", MS_NOSUID|MS_STRICTATIME, "mode=755") < 0) { + r = -errno; + goto fail; + } + + devpts = strjoina(temporary_mount, "/dev/pts"); + (void) mkdir(devpts, 0755); + if (mount("/dev/pts", devpts, NULL, MS_BIND, NULL) < 0) { + r = -errno; + goto fail; + } + + devptmx = strjoina(temporary_mount, "/dev/ptmx"); + if (symlink("pts/ptmx", devptmx) < 0) { + r = -errno; + goto fail; + } + + devshm = strjoina(temporary_mount, "/dev/shm"); + (void) mkdir(devshm, 01777); + r = mount("/dev/shm", devshm, NULL, MS_BIND, NULL); + if (r < 0) { + r = -errno; + goto fail; + } + + devmqueue = strjoina(temporary_mount, "/dev/mqueue"); + (void) mkdir(devmqueue, 0755); + (void) mount("/dev/mqueue", devmqueue, NULL, MS_BIND, NULL); + + devhugepages = strjoina(temporary_mount, "/dev/hugepages"); + (void) mkdir(devhugepages, 0755); + (void) mount("/dev/hugepages", devhugepages, NULL, MS_BIND, NULL); + + devlog = strjoina(temporary_mount, "/dev/log"); + (void) symlink("/run/systemd/journal/dev-log", devlog); + + NULSTR_FOREACH(d, devnodes) { + _cleanup_free_ char *dn = NULL; + struct stat st; + + r = stat(d, &st); + if (r < 0) { + + if (errno == ENOENT) + continue; + + r = -errno; + goto fail; + } + + if (!S_ISBLK(st.st_mode) && + !S_ISCHR(st.st_mode)) { + r = -EINVAL; + goto fail; + } + + if (st.st_rdev == 0) + continue; + + dn = strappend(temporary_mount, d); + if (!dn) { + r = -ENOMEM; + goto fail; + } + + mac_selinux_create_file_prepare(d, st.st_mode); + r = mknod(dn, st.st_mode, st.st_rdev); + mac_selinux_create_file_clear(); + + if (r < 0) { + r = -errno; + goto fail; + } + } + + dev_setup(temporary_mount, UID_INVALID, GID_INVALID); + + /* Create the /dev directory if missing. It is more likely to be + * missing when the service is started with RootDirectory. This is + * consistent with mount units creating the mount points when missing. + */ + (void) mkdir_p_label(m->path, 0755); + + if (mount(dev, m->path, NULL, MS_MOVE, NULL) < 0) { + r = -errno; + goto fail; + } + + rmdir(dev); + rmdir(temporary_mount); + + return 0; + +fail: + if (devpts) + umount(devpts); + + if (devshm) + umount(devshm); + + if (devhugepages) + umount(devhugepages); + + if (devmqueue) + umount(devmqueue); + + umount(dev); + rmdir(dev); + rmdir(temporary_mount); + + return r; +} + +static int mount_kdbus(BindMount *m) { + + char temporary_mount[] = "/tmp/kdbus-dev-XXXXXX"; + _cleanup_free_ char *basepath = NULL; + _cleanup_umask_ mode_t u; + char *busnode = NULL, *root; + struct stat st; + int r; + + assert(m); + + u = umask(0000); + + if (!mkdtemp(temporary_mount)) + return log_error_errno(errno, "Failed create temp dir: %m"); + + root = strjoina(temporary_mount, "/kdbus"); + (void) mkdir(root, 0755); + if (mount("tmpfs", root, "tmpfs", MS_NOSUID|MS_STRICTATIME, "mode=777") < 0) { + r = -errno; + goto fail; + } + + /* create a new /dev/null dev node copy so we have some fodder to + * bind-mount the custom endpoint over. */ + if (stat("/dev/null", &st) < 0) { + r = log_error_errno(errno, "Failed to stat /dev/null: %m"); + goto fail; + } + + busnode = strjoina(root, "/bus"); + if (mknod(busnode, (st.st_mode & ~07777) | 0600, st.st_rdev) < 0) { + r = log_error_errno(errno, "mknod() for %s failed: %m", + busnode); + goto fail; + } + + r = mount(m->path, busnode, NULL, MS_BIND, NULL); + if (r < 0) { + r = log_error_errno(errno, "bind mount of %s failed: %m", + m->path); + goto fail; + } + + basepath = dirname_malloc(m->path); + if (!basepath) { + r = -ENOMEM; + goto fail; + } + + if (mount(root, basepath, NULL, MS_MOVE, NULL) < 0) { + r = log_error_errno(errno, "bind mount of %s failed: %m", + basepath); + goto fail; + } + + rmdir(temporary_mount); + return 0; + +fail: + if (busnode) { + umount(busnode); + unlink(busnode); + } + + umount(root); + rmdir(root); + rmdir(temporary_mount); + + return r; +} + +static int apply_mount( + BindMount *m, + const char *tmp_dir, + const char *var_tmp_dir) { + + const char *what; + int r; + + assert(m); + + switch (m->mode) { + + case INACCESSIBLE: + + /* First, get rid of everything that is below if there + * is anything... Then, overmount it with an + * inaccessible directory. */ + umount_recursive(m->path, 0); + + what = "/run/systemd/inaccessible"; + break; + + case READONLY: + case READWRITE: + /* Nothing to mount here, we just later toggle the + * MS_RDONLY bit for the mount point */ + return 0; + + case PRIVATE_TMP: + what = tmp_dir; + break; + + case PRIVATE_VAR_TMP: + what = var_tmp_dir; + break; + + case PRIVATE_DEV: + return mount_dev(m); + + case PRIVATE_BUS_ENDPOINT: + return mount_kdbus(m); + + default: + assert_not_reached("Unknown mode"); + } + + assert(what); + + r = mount(what, m->path, NULL, MS_BIND|MS_REC, NULL); + if (r >= 0) + log_debug("Successfully mounted %s to %s", what, m->path); + else if (m->ignore && errno == ENOENT) + return 0; + + return r; +} + +static int make_read_only(BindMount *m) { + int r; + + assert(m); + + if (IN_SET(m->mode, INACCESSIBLE, READONLY)) + r = bind_remount_recursive(m->path, true); + else if (IN_SET(m->mode, READWRITE, PRIVATE_TMP, PRIVATE_VAR_TMP, PRIVATE_DEV)) + r = bind_remount_recursive(m->path, false); + else + r = 0; + + if (m->ignore && r == -ENOENT) + return 0; + + return r; +} + +int setup_namespace( + const char* root_directory, + char** read_write_dirs, + char** read_only_dirs, + char** inaccessible_dirs, + const char* tmp_dir, + const char* var_tmp_dir, + const char* bus_endpoint_path, + bool private_dev, + ProtectHome protect_home, + ProtectSystem protect_system, + unsigned long mount_flags) { + + BindMount *m, *mounts = NULL; + unsigned n; + int r = 0; + + if (mount_flags == 0) + mount_flags = MS_SHARED; + + if (unshare(CLONE_NEWNS) < 0) + return -errno; + + n = !!tmp_dir + !!var_tmp_dir + !!bus_endpoint_path + + strv_length(read_write_dirs) + + strv_length(read_only_dirs) + + strv_length(inaccessible_dirs) + + private_dev + + (protect_home != PROTECT_HOME_NO ? 3 : 0) + + (protect_system != PROTECT_SYSTEM_NO ? 2 : 0) + + (protect_system == PROTECT_SYSTEM_FULL ? 1 : 0); + + if (n > 0) { + m = mounts = (BindMount *) alloca0(n * sizeof(BindMount)); + r = append_mounts(&m, read_write_dirs, READWRITE); + if (r < 0) + return r; + + r = append_mounts(&m, read_only_dirs, READONLY); + if (r < 0) + return r; + + r = append_mounts(&m, inaccessible_dirs, INACCESSIBLE); + if (r < 0) + return r; + + if (tmp_dir) { + m->path = prefix_roota(root_directory, "/tmp"); + m->mode = PRIVATE_TMP; + m++; + } + + if (var_tmp_dir) { + m->path = prefix_roota(root_directory, "/var/tmp"); + m->mode = PRIVATE_VAR_TMP; + m++; + } + + if (private_dev) { + m->path = prefix_roota(root_directory, "/dev"); + m->mode = PRIVATE_DEV; + m++; + } + + if (bus_endpoint_path) { + m->path = prefix_roota(root_directory, bus_endpoint_path); + m->mode = PRIVATE_BUS_ENDPOINT; + m++; + } + + if (protect_home != PROTECT_HOME_NO) { + const char *home_dir, *run_user_dir, *root_dir; + + home_dir = prefix_roota(root_directory, "/home"); + home_dir = strjoina("-", home_dir); + run_user_dir = prefix_roota(root_directory, "/run/user"); + run_user_dir = strjoina("-", run_user_dir); + root_dir = prefix_roota(root_directory, "/root"); + root_dir = strjoina("-", root_dir); + + r = append_mounts(&m, STRV_MAKE(home_dir, run_user_dir, root_dir), + protect_home == PROTECT_HOME_READ_ONLY ? READONLY : INACCESSIBLE); + if (r < 0) + return r; + } + + if (protect_system != PROTECT_SYSTEM_NO) { + const char *usr_dir, *boot_dir, *etc_dir; + + usr_dir = prefix_roota(root_directory, "/usr"); + boot_dir = prefix_roota(root_directory, "/boot"); + boot_dir = strjoina("-", boot_dir); + etc_dir = prefix_roota(root_directory, "/etc"); + + r = append_mounts(&m, protect_system == PROTECT_SYSTEM_FULL + ? STRV_MAKE(usr_dir, boot_dir, etc_dir) + : STRV_MAKE(usr_dir, boot_dir), READONLY); + if (r < 0) + return r; + } + + assert(mounts + n == m); + + qsort(mounts, n, sizeof(BindMount), mount_path_compare); + drop_duplicates(mounts, &n); + } + + if (n > 0 || root_directory) { + /* Remount / as SLAVE so that nothing now mounted in the namespace + shows up in the parent */ + if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) + return -errno; + } + + if (root_directory) { + /* Turn directory into bind mount */ + if (mount(root_directory, root_directory, NULL, MS_BIND|MS_REC, NULL) < 0) + return -errno; + } + + if (n > 0) { + for (m = mounts; m < mounts + n; ++m) { + r = apply_mount(m, tmp_dir, var_tmp_dir); + if (r < 0) + goto fail; + } + + for (m = mounts; m < mounts + n; ++m) { + r = make_read_only(m); + if (r < 0) + goto fail; + } + } + + if (root_directory) { + /* MS_MOVE does not work on MS_SHARED so the remount MS_SHARED will be done later */ + r = mount_move_root(root_directory); + + /* at this point, we cannot rollback */ + if (r < 0) + return r; + } + + /* Remount / as the desired mode. Not that this will not + * reestablish propagation from our side to the host, since + * what's disconnected is disconnected. */ + if (mount(NULL, "/", NULL, mount_flags | MS_REC, NULL) < 0) + /* at this point, we cannot rollback */ + return -errno; + + return 0; + +fail: + if (n > 0) { + for (m = mounts; m < mounts + n; ++m) + if (m->done) + (void) umount2(m->path, MNT_DETACH); + } + + return r; +} + +static int setup_one_tmp_dir(const char *id, const char *prefix, char **path) { + _cleanup_free_ char *x = NULL; + char bid[SD_ID128_STRING_MAX]; + sd_id128_t boot_id; + int r; + + assert(id); + assert(prefix); + assert(path); + + /* We include the boot id in the directory so that after a + * reboot we can easily identify obsolete directories. */ + + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return r; + + x = strjoin(prefix, "/systemd-private-", sd_id128_to_string(boot_id, bid), "-", id, "-XXXXXX", NULL); + if (!x) + return -ENOMEM; + + RUN_WITH_UMASK(0077) + if (!mkdtemp(x)) + return -errno; + + RUN_WITH_UMASK(0000) { + char *y; + + y = strjoina(x, "/tmp"); + + if (mkdir(y, 0777 | S_ISVTX) < 0) + return -errno; + } + + *path = x; + x = NULL; + + return 0; +} + +int setup_tmp_dirs(const char *id, char **tmp_dir, char **var_tmp_dir) { + char *a, *b; + int r; + + assert(id); + assert(tmp_dir); + assert(var_tmp_dir); + + r = setup_one_tmp_dir(id, "/tmp", &a); + if (r < 0) + return r; + + r = setup_one_tmp_dir(id, "/var/tmp", &b); + if (r < 0) { + char *t; + + t = strjoina(a, "/tmp"); + rmdir(t); + rmdir(a); + + free(a); + return r; + } + + *tmp_dir = a; + *var_tmp_dir = b; + + return 0; +} + +int setup_netns(int netns_storage_socket[2]) { + _cleanup_close_ int netns = -1; + int r, q; + + assert(netns_storage_socket); + assert(netns_storage_socket[0] >= 0); + assert(netns_storage_socket[1] >= 0); + + /* We use the passed socketpair as a storage buffer for our + * namespace reference fd. Whatever process runs this first + * shall create a new namespace, all others should just join + * it. To serialize that we use a file lock on the socket + * pair. + * + * It's a bit crazy, but hey, works great! */ + + if (lockf(netns_storage_socket[0], F_LOCK, 0) < 0) + return -errno; + + netns = receive_one_fd(netns_storage_socket[0], MSG_DONTWAIT); + if (netns == -EAGAIN) { + /* Nothing stored yet, so let's create a new namespace */ + + if (unshare(CLONE_NEWNET) < 0) { + r = -errno; + goto fail; + } + + loopback_setup(); + + netns = open("/proc/self/ns/net", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (netns < 0) { + r = -errno; + goto fail; + } + + r = 1; + + } else if (netns < 0) { + r = netns; + goto fail; + + } else { + /* Yay, found something, so let's join the namespace */ + if (setns(netns, CLONE_NEWNET) < 0) { + r = -errno; + goto fail; + } + + r = 0; + } + + q = send_one_fd(netns_storage_socket[1], netns, MSG_DONTWAIT); + if (q < 0) { + r = q; + goto fail; + } + +fail: + lockf(netns_storage_socket[0], F_ULOCK, 0); + return r; +} + +static const char *const protect_home_table[_PROTECT_HOME_MAX] = { + [PROTECT_HOME_NO] = "no", + [PROTECT_HOME_YES] = "yes", + [PROTECT_HOME_READ_ONLY] = "read-only", +}; + +DEFINE_STRING_TABLE_LOOKUP(protect_home, ProtectHome); + +static const char *const protect_system_table[_PROTECT_SYSTEM_MAX] = { + [PROTECT_SYSTEM_NO] = "no", + [PROTECT_SYSTEM_YES] = "yes", + [PROTECT_SYSTEM_FULL] = "full", +}; + +DEFINE_STRING_TABLE_LOOKUP(protect_system, ProtectSystem); diff --git a/src/libcore/namespace.h b/src/libcore/namespace.h new file mode 100644 index 0000000000..40bee74e2c --- /dev/null +++ b/src/libcore/namespace.h @@ -0,0 +1,64 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "macro.h" + +typedef enum ProtectHome { + PROTECT_HOME_NO, + PROTECT_HOME_YES, + PROTECT_HOME_READ_ONLY, + _PROTECT_HOME_MAX, + _PROTECT_HOME_INVALID = -1 +} ProtectHome; + +typedef enum ProtectSystem { + PROTECT_SYSTEM_NO, + PROTECT_SYSTEM_YES, + PROTECT_SYSTEM_FULL, + _PROTECT_SYSTEM_MAX, + _PROTECT_SYSTEM_INVALID = -1 +} ProtectSystem; + +int setup_namespace(const char *chroot, + char **read_write_dirs, + char **read_only_dirs, + char **inaccessible_dirs, + const char *tmp_dir, + const char *var_tmp_dir, + const char *endpoint_path, + bool private_dev, + ProtectHome protect_home, + ProtectSystem protect_system, + unsigned long mount_flags); + +int setup_tmp_dirs(const char *id, + char **tmp_dir, + char **var_tmp_dir); + +int setup_netns(int netns_storage_socket[2]); + +const char* protect_home_to_string(ProtectHome p) _const_; +ProtectHome protect_home_from_string(const char *s) _pure_; + +const char* protect_system_to_string(ProtectSystem p) _const_; +ProtectSystem protect_system_from_string(const char *s) _pure_; diff --git a/src/libcore/path.c b/src/libcore/path.c new file mode 100644 index 0000000000..460c1d3bf2 --- /dev/null +++ b/src/libcore/path.c @@ -0,0 +1,778 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "bus-error.h" +#include "bus-util.h" +#include "dbus-path.h" +#include "fd-util.h" +#include "fs-util.h" +#include "glob-util.h" +#include "macro.h" +#include "mkdir.h" +#include "path.h" +#include "special.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "unit-name.h" +#include "unit.h" + +static const UnitActiveState state_translation_table[_PATH_STATE_MAX] = { + [PATH_DEAD] = UNIT_INACTIVE, + [PATH_WAITING] = UNIT_ACTIVE, + [PATH_RUNNING] = UNIT_ACTIVE, + [PATH_FAILED] = UNIT_FAILED +}; + +static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); + +int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler) { + + static const int flags_table[_PATH_TYPE_MAX] = { + [PATH_EXISTS] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB, + [PATH_EXISTS_GLOB] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB, + [PATH_CHANGED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO, + [PATH_MODIFIED] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CLOSE_WRITE|IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO|IN_MODIFY, + [PATH_DIRECTORY_NOT_EMPTY] = IN_DELETE_SELF|IN_MOVE_SELF|IN_ATTRIB|IN_CREATE|IN_MOVED_TO + }; + + bool exists = false; + char *slash, *oldslash = NULL; + int r; + + assert(s); + assert(s->unit); + assert(handler); + + path_spec_unwatch(s); + + s->inotify_fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (s->inotify_fd < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(s->unit->manager->event, &s->event_source, s->inotify_fd, EPOLLIN, handler, s); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(s->event_source, "path"); + + /* This assumes the path was passed through path_kill_slashes()! */ + + for (slash = strchr(s->path, '/'); ; slash = strchr(slash+1, '/')) { + char *cut = NULL; + int flags; + char tmp; + + if (slash) { + cut = slash + (slash == s->path); + tmp = *cut; + *cut = '\0'; + + flags = IN_MOVE_SELF | IN_DELETE_SELF | IN_ATTRIB | IN_CREATE | IN_MOVED_TO; + } else + flags = flags_table[s->type]; + + r = inotify_add_watch(s->inotify_fd, s->path, flags); + if (r < 0) { + if (errno == EACCES || errno == ENOENT) { + if (cut) + *cut = tmp; + break; + } + + r = log_warning_errno(errno, "Failed to add watch on %s: %s", s->path, errno == ENOSPC ? "too many watches" : strerror(-r)); + if (cut) + *cut = tmp; + goto fail; + } else { + exists = true; + + /* Path exists, we don't need to watch parent + too closely. */ + if (oldslash) { + char *cut2 = oldslash + (oldslash == s->path); + char tmp2 = *cut2; + *cut2 = '\0'; + + inotify_add_watch(s->inotify_fd, s->path, IN_MOVE_SELF); + /* Error is ignored, the worst can happen is + we get spurious events. */ + + *cut2 = tmp2; + } + } + + if (cut) + *cut = tmp; + + if (slash) + oldslash = slash; + else { + /* whole path has been iterated over */ + s->primary_wd = r; + break; + } + } + + if (!exists) { + r = log_error_errno(errno, "Failed to add watch on any of the components of %s: %m", s->path); + /* either EACCESS or ENOENT */ + goto fail; + } + + return 0; + +fail: + path_spec_unwatch(s); + return r; +} + +void path_spec_unwatch(PathSpec *s) { + assert(s); + + s->event_source = sd_event_source_unref(s->event_source); + s->inotify_fd = safe_close(s->inotify_fd); +} + +int path_spec_fd_event(PathSpec *s, uint32_t revents) { + union inotify_event_buffer buffer; + struct inotify_event *e; + ssize_t l; + int r = 0; + + if (revents != EPOLLIN) { + log_error("Got invalid poll event on inotify."); + return -EINVAL; + } + + l = read(s->inotify_fd, &buffer, sizeof(buffer)); + if (l < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return log_error_errno(errno, "Failed to read inotify event: %m"); + } + + FOREACH_INOTIFY_EVENT(e, buffer, l) { + if ((s->type == PATH_CHANGED || s->type == PATH_MODIFIED) && + s->primary_wd == e->wd) + r = 1; + } + + return r; +} + +static bool path_spec_check_good(PathSpec *s, bool initial) { + bool good = false; + + switch (s->type) { + + case PATH_EXISTS: + good = access(s->path, F_OK) >= 0; + break; + + case PATH_EXISTS_GLOB: + good = glob_exists(s->path) > 0; + break; + + case PATH_DIRECTORY_NOT_EMPTY: { + int k; + + k = dir_is_empty(s->path); + good = !(k == -ENOENT || k > 0); + break; + } + + case PATH_CHANGED: + case PATH_MODIFIED: { + bool b; + + b = access(s->path, F_OK) >= 0; + good = !initial && b != s->previous_exists; + s->previous_exists = b; + break; + } + + default: + ; + } + + return good; +} + +static void path_spec_mkdir(PathSpec *s, mode_t mode) { + int r; + + if (s->type == PATH_EXISTS || s->type == PATH_EXISTS_GLOB) + return; + + r = mkdir_p_label(s->path, mode); + if (r < 0) + log_warning_errno(r, "mkdir(%s) failed: %m", s->path); +} + +static void path_spec_dump(PathSpec *s, FILE *f, const char *prefix) { + fprintf(f, + "%s%s: %s\n", + prefix, + path_type_to_string(s->type), + s->path); +} + +void path_spec_done(PathSpec *s) { + assert(s); + assert(s->inotify_fd == -1); + + free(s->path); +} + +static void path_init(Unit *u) { + Path *p = PATH(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + p->directory_mode = 0755; +} + +void path_free_specs(Path *p) { + PathSpec *s; + + assert(p); + + while ((s = p->specs)) { + path_spec_unwatch(s); + LIST_REMOVE(spec, p->specs, s); + path_spec_done(s); + free(s); + } +} + +static void path_done(Unit *u) { + Path *p = PATH(u); + + assert(p); + + path_free_specs(p); +} + +static int path_add_mount_links(Path *p) { + PathSpec *s; + int r; + + assert(p); + + LIST_FOREACH(spec, s, p->specs) { + r = unit_require_mounts_for(UNIT(p), s->path); + if (r < 0) + return r; + } + + return 0; +} + +static int path_verify(Path *p) { + assert(p); + + if (UNIT(p)->load_state != UNIT_LOADED) + return 0; + + if (!p->specs) { + log_unit_error(UNIT(p), "Path unit lacks path setting. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int path_add_default_dependencies(Path *p) { + int r; + + assert(p); + + if (!UNIT(p)->default_dependencies) + return 0; + + r = unit_add_dependency_by_name(UNIT(p), UNIT_BEFORE, SPECIAL_PATHS_TARGET, NULL, true); + if (r < 0) + return r; + + if (UNIT(p)->manager->running_as == MANAGER_SYSTEM) { + r = unit_add_two_dependencies_by_name(UNIT(p), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); + if (r < 0) + return r; + } + + return unit_add_two_dependencies_by_name(UNIT(p), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static int path_load(Unit *u) { + Path *p = PATH(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + r = unit_load_fragment_and_dropin(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + + if (set_isempty(u->dependencies[UNIT_TRIGGERS])) { + Unit *x; + + r = unit_load_related_unit(u, ".service", &x); + if (r < 0) + return r; + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, x, true); + if (r < 0) + return r; + } + + r = path_add_mount_links(p); + if (r < 0) + return r; + + r = path_add_default_dependencies(p); + if (r < 0) + return r; + } + + return path_verify(p); +} + +static void path_dump(Unit *u, FILE *f, const char *prefix) { + Path *p = PATH(u); + Unit *trigger; + PathSpec *s; + + assert(p); + assert(f); + + trigger = UNIT_TRIGGER(u); + + fprintf(f, + "%sPath State: %s\n" + "%sResult: %s\n" + "%sUnit: %s\n" + "%sMakeDirectory: %s\n" + "%sDirectoryMode: %04o\n", + prefix, path_state_to_string(p->state), + prefix, path_result_to_string(p->result), + prefix, trigger ? trigger->id : "n/a", + prefix, yes_no(p->make_directory), + prefix, p->directory_mode); + + LIST_FOREACH(spec, s, p->specs) + path_spec_dump(s, f, prefix); +} + +static void path_unwatch(Path *p) { + PathSpec *s; + + assert(p); + + LIST_FOREACH(spec, s, p->specs) + path_spec_unwatch(s); +} + +static int path_watch(Path *p) { + int r; + PathSpec *s; + + assert(p); + + LIST_FOREACH(spec, s, p->specs) { + r = path_spec_watch(s, path_dispatch_io); + if (r < 0) + return r; + } + + return 0; +} + +static void path_set_state(Path *p, PathState state) { + PathState old_state; + assert(p); + + old_state = p->state; + p->state = state; + + if (state != PATH_WAITING && + (state != PATH_RUNNING || p->inotify_triggered)) + path_unwatch(p); + + if (state != old_state) + log_unit_debug(UNIT(p), "Changed %s -> %s", path_state_to_string(old_state), path_state_to_string(state)); + + unit_notify(UNIT(p), state_translation_table[old_state], state_translation_table[state], true); +} + +static void path_enter_waiting(Path *p, bool initial, bool recheck); + +static int path_coldplug(Unit *u) { + Path *p = PATH(u); + + assert(p); + assert(p->state == PATH_DEAD); + + if (p->deserialized_state != p->state) { + + if (p->deserialized_state == PATH_WAITING || + p->deserialized_state == PATH_RUNNING) + path_enter_waiting(p, true, true); + else + path_set_state(p, p->deserialized_state); + } + + return 0; +} + +static void path_enter_dead(Path *p, PathResult f) { + assert(p); + + if (f != PATH_SUCCESS) + p->result = f; + + path_set_state(p, p->result != PATH_SUCCESS ? PATH_FAILED : PATH_DEAD); +} + +static void path_enter_running(Path *p) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + Unit *trigger; + int r; + + assert(p); + + /* Don't start job if we are supposed to go down */ + if (unit_stop_pending(UNIT(p))) + return; + + trigger = UNIT_TRIGGER(UNIT(p)); + if (!trigger) { + log_unit_error(UNIT(p), "Unit to trigger vanished."); + path_enter_dead(p, TIMER_FAILURE_RESOURCES); + return; + } + + r = manager_add_job(UNIT(p)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL); + if (r < 0) + goto fail; + + p->inotify_triggered = false; + + r = path_watch(p); + if (r < 0) + goto fail; + + path_set_state(p, PATH_RUNNING); + return; + +fail: + log_unit_warning(UNIT(p), "Failed to queue unit startup job: %s", bus_error_message(&error, r)); + path_enter_dead(p, PATH_FAILURE_RESOURCES); +} + +static bool path_check_good(Path *p, bool initial) { + PathSpec *s; + bool good = false; + + assert(p); + + LIST_FOREACH(spec, s, p->specs) { + good = path_spec_check_good(s, initial); + + if (good) + break; + } + + return good; +} + +static void path_enter_waiting(Path *p, bool initial, bool recheck) { + int r; + + if (recheck) + if (path_check_good(p, initial)) { + log_unit_debug(UNIT(p), "Got triggered."); + path_enter_running(p); + return; + } + + r = path_watch(p); + if (r < 0) + goto fail; + + /* Hmm, so now we have created inotify watches, but the file + * might have appeared/been removed by now, so we must + * recheck */ + + if (recheck) + if (path_check_good(p, false)) { + log_unit_debug(UNIT(p), "Got triggered."); + path_enter_running(p); + return; + } + + path_set_state(p, PATH_WAITING); + return; + +fail: + log_unit_warning_errno(UNIT(p), r, "Failed to enter waiting state: %m"); + path_enter_dead(p, PATH_FAILURE_RESOURCES); +} + +static void path_mkdir(Path *p) { + PathSpec *s; + + assert(p); + + if (!p->make_directory) + return; + + LIST_FOREACH(spec, s, p->specs) + path_spec_mkdir(s, p->directory_mode); +} + +static int path_start(Unit *u) { + Path *p = PATH(u); + Unit *trigger; + + assert(p); + assert(p->state == PATH_DEAD || p->state == PATH_FAILED); + + trigger = UNIT_TRIGGER(u); + if (!trigger || trigger->load_state != UNIT_LOADED) { + log_unit_error(u, "Refusing to start, unit to trigger not loaded."); + return -ENOENT; + } + + path_mkdir(p); + + p->result = PATH_SUCCESS; + path_enter_waiting(p, true, true); + + return 1; +} + +static int path_stop(Unit *u) { + Path *p = PATH(u); + + assert(p); + assert(p->state == PATH_WAITING || p->state == PATH_RUNNING); + + path_enter_dead(p, PATH_SUCCESS); + return 1; +} + +static int path_serialize(Unit *u, FILE *f, FDSet *fds) { + Path *p = PATH(u); + + assert(u); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", path_state_to_string(p->state)); + unit_serialize_item(u, f, "result", path_result_to_string(p->result)); + + return 0; +} + +static int path_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Path *p = PATH(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + PathState state; + + state = path_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + p->deserialized_state = state; + + } else if (streq(key, "result")) { + PathResult f; + + f = path_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != PATH_SUCCESS) + p->result = f; + + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +_pure_ static UnitActiveState path_active_state(Unit *u) { + assert(u); + + return state_translation_table[PATH(u)->state]; +} + +_pure_ static const char *path_sub_state_to_string(Unit *u) { + assert(u); + + return path_state_to_string(PATH(u)->state); +} + +static int path_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + PathSpec *s = userdata; + Path *p; + int changed; + + assert(s); + assert(s->unit); + assert(fd >= 0); + + p = PATH(s->unit); + + if (p->state != PATH_WAITING && + p->state != PATH_RUNNING) + return 0; + + /* log_debug("inotify wakeup on %s.", u->id); */ + + LIST_FOREACH(spec, s, p->specs) + if (path_spec_owns_inotify_fd(s, fd)) + break; + + if (!s) { + log_error("Got event on unknown fd."); + goto fail; + } + + changed = path_spec_fd_event(s, revents); + if (changed < 0) + goto fail; + + /* If we are already running, then remember that one event was + * dispatched so that we restart the service only if something + * actually changed on disk */ + p->inotify_triggered = true; + + if (changed) + path_enter_running(p); + else + path_enter_waiting(p, false, true); + + return 0; + +fail: + path_enter_dead(p, PATH_FAILURE_RESOURCES); + return 0; +} + +static void path_trigger_notify(Unit *u, Unit *other) { + Path *p = PATH(u); + + assert(u); + assert(other); + + /* Invoked whenever the unit we trigger changes state or gains + * or loses a job */ + + if (other->load_state != UNIT_LOADED) + return; + + if (p->state == PATH_RUNNING && + UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) { + log_unit_debug(UNIT(p), "Got notified about unit deactivation."); + + /* Hmm, so inotify was triggered since the + * last activation, so I guess we need to + * recheck what is going on. */ + path_enter_waiting(p, false, p->inotify_triggered); + } +} + +static void path_reset_failed(Unit *u) { + Path *p = PATH(u); + + assert(p); + + if (p->state == PATH_FAILED) + path_set_state(p, PATH_DEAD); + + p->result = PATH_SUCCESS; +} + +static const char* const path_type_table[_PATH_TYPE_MAX] = { + [PATH_EXISTS] = "PathExists", + [PATH_EXISTS_GLOB] = "PathExistsGlob", + [PATH_DIRECTORY_NOT_EMPTY] = "DirectoryNotEmpty", + [PATH_CHANGED] = "PathChanged", + [PATH_MODIFIED] = "PathModified", +}; + +DEFINE_STRING_TABLE_LOOKUP(path_type, PathType); + +static const char* const path_result_table[_PATH_RESULT_MAX] = { + [PATH_SUCCESS] = "success", + [PATH_FAILURE_RESOURCES] = "resources", +}; + +DEFINE_STRING_TABLE_LOOKUP(path_result, PathResult); + +const UnitVTable path_vtable = { + .object_size = sizeof(Path), + + .sections = + "Unit\0" + "Path\0" + "Install\0", + + .init = path_init, + .done = path_done, + .load = path_load, + + .coldplug = path_coldplug, + + .dump = path_dump, + + .start = path_start, + .stop = path_stop, + + .serialize = path_serialize, + .deserialize_item = path_deserialize_item, + + .active_state = path_active_state, + .sub_state_to_string = path_sub_state_to_string, + + .trigger_notify = path_trigger_notify, + + .reset_failed = path_reset_failed, + + .bus_vtable = bus_path_vtable +}; diff --git a/src/libcore/path.h b/src/libcore/path.h new file mode 100644 index 0000000000..bbbcebd78e --- /dev/null +++ b/src/libcore/path.h @@ -0,0 +1,92 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Path Path; +typedef struct PathSpec PathSpec; + +#include "unit.h" + +typedef enum PathType { + PATH_EXISTS, + PATH_EXISTS_GLOB, + PATH_DIRECTORY_NOT_EMPTY, + PATH_CHANGED, + PATH_MODIFIED, + _PATH_TYPE_MAX, + _PATH_TYPE_INVALID = -1 +} PathType; + +typedef struct PathSpec { + Unit *unit; + + char *path; + + sd_event_source *event_source; + + LIST_FIELDS(struct PathSpec, spec); + + PathType type; + int inotify_fd; + int primary_wd; + + bool previous_exists; +} PathSpec; + +int path_spec_watch(PathSpec *s, sd_event_io_handler_t handler); +void path_spec_unwatch(PathSpec *s); +int path_spec_fd_event(PathSpec *s, uint32_t events); +void path_spec_done(PathSpec *s); + +static inline bool path_spec_owns_inotify_fd(PathSpec *s, int fd) { + return s->inotify_fd == fd; +} + +typedef enum PathResult { + PATH_SUCCESS, + PATH_FAILURE_RESOURCES, + _PATH_RESULT_MAX, + _PATH_RESULT_INVALID = -1 +} PathResult; + +struct Path { + Unit meta; + + LIST_HEAD(PathSpec, specs); + + PathState state, deserialized_state; + + bool inotify_triggered; + + bool make_directory; + mode_t directory_mode; + + PathResult result; +}; + +void path_free_specs(Path *p); + +extern const UnitVTable path_vtable; + +const char* path_type_to_string(PathType i) _const_; +PathType path_type_from_string(const char *s) _pure_; + +const char* path_result_to_string(PathResult i) _const_; +PathResult path_result_from_string(const char *s) _pure_; diff --git a/src/libcore/scope.c b/src/libcore/scope.c new file mode 100644 index 0000000000..c5d0ecef04 --- /dev/null +++ b/src/libcore/scope.c @@ -0,0 +1,610 @@ +/*** + 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 . +***/ + +#include +#include + +#include "alloc-util.h" +#include "dbus-scope.h" +#include "load-dropin.h" +#include "log.h" +#include "scope.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit.h" + +static const UnitActiveState state_translation_table[_SCOPE_STATE_MAX] = { + [SCOPE_DEAD] = UNIT_INACTIVE, + [SCOPE_RUNNING] = UNIT_ACTIVE, + [SCOPE_ABANDONED] = UNIT_ACTIVE, + [SCOPE_STOP_SIGTERM] = UNIT_DEACTIVATING, + [SCOPE_STOP_SIGKILL] = UNIT_DEACTIVATING, + [SCOPE_FAILED] = UNIT_FAILED +}; + +static int scope_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); + +static void scope_init(Unit *u) { + Scope *s = SCOPE(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + s->timeout_stop_usec = u->manager->default_timeout_stop_usec; + + UNIT(s)->ignore_on_isolate = true; +} + +static void scope_done(Unit *u) { + Scope *s = SCOPE(u); + + assert(u); + + free(s->controller); + + s->timer_event_source = sd_event_source_unref(s->timer_event_source); +} + +static int scope_arm_timer(Scope *s, usec_t usec) { + int r; + + assert(s); + + if (s->timer_event_source) { + r = sd_event_source_set_time(s->timer_event_source, usec); + if (r < 0) + return r; + + return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); + } + + if (usec == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + UNIT(s)->manager->event, + &s->timer_event_source, + CLOCK_MONOTONIC, + usec, 0, + scope_dispatch_timer, s); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s->timer_event_source, "scope-timer"); + + return 0; +} + +static void scope_set_state(Scope *s, ScopeState state) { + ScopeState old_state; + assert(s); + + old_state = s->state; + s->state = state; + + if (!IN_SET(state, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL)) + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + + if (IN_SET(state, SCOPE_DEAD, SCOPE_FAILED)) + unit_unwatch_all_pids(UNIT(s)); + + if (state != old_state) + log_debug("%s changed %s -> %s", UNIT(s)->id, scope_state_to_string(old_state), scope_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); +} + +static int scope_add_default_dependencies(Scope *s) { + int r; + + assert(s); + + if (!UNIT(s)->default_dependencies) + return 0; + + /* Make sure scopes are unloaded on shutdown */ + r = unit_add_two_dependencies_by_name( + UNIT(s), + UNIT_BEFORE, UNIT_CONFLICTS, + SPECIAL_SHUTDOWN_TARGET, NULL, true); + if (r < 0) + return r; + + return 0; +} + +static int scope_verify(Scope *s) { + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (set_isempty(UNIT(s)->pids) && + !manager_is_reloading_or_reexecuting(UNIT(s)->manager) && + !unit_has_name(UNIT(s), SPECIAL_INIT_SCOPE)) { + log_unit_error(UNIT(s), "Scope has no PIDs. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int scope_load(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + assert(u->load_state == UNIT_STUB); + + if (!u->transient && !manager_is_reloading_or_reexecuting(u->manager)) + return -ENOENT; + + u->load_state = UNIT_LOADED; + + r = unit_load_dropin(u); + if (r < 0) + return r; + + r = unit_patch_contexts(u); + if (r < 0) + return r; + + r = unit_set_default_slice(u); + if (r < 0) + return r; + + r = scope_add_default_dependencies(s); + if (r < 0) + return r; + + return scope_verify(s); +} + +static int scope_coldplug(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + assert(s->state == SCOPE_DEAD); + + if (s->deserialized_state == s->state) + return 0; + + if (IN_SET(s->deserialized_state, SCOPE_STOP_SIGKILL, SCOPE_STOP_SIGTERM)) { + r = scope_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_stop_usec)); + if (r < 0) + return r; + } + + if (!IN_SET(s->deserialized_state, SCOPE_DEAD, SCOPE_FAILED)) + unit_watch_all_pids(UNIT(s)); + + scope_set_state(s, s->deserialized_state); + return 0; +} + +static void scope_dump(Unit *u, FILE *f, const char *prefix) { + Scope *s = SCOPE(u); + + assert(s); + assert(f); + + fprintf(f, + "%sScope State: %s\n" + "%sResult: %s\n", + prefix, scope_state_to_string(s->state), + prefix, scope_result_to_string(s->result)); + + cgroup_context_dump(&s->cgroup_context, f, prefix); + kill_context_dump(&s->kill_context, f, prefix); +} + +static void scope_enter_dead(Scope *s, ScopeResult f) { + assert(s); + + if (f != SCOPE_SUCCESS) + s->result = f; + + scope_set_state(s, s->result != SCOPE_SUCCESS ? SCOPE_FAILED : SCOPE_DEAD); +} + +static void scope_enter_signal(Scope *s, ScopeState state, ScopeResult f) { + bool skip_signal = false; + int r; + + assert(s); + + if (f != SCOPE_SUCCESS) + s->result = f; + + unit_watch_all_pids(UNIT(s)); + + /* 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 */ + if (state == SCOPE_STOP_SIGTERM) + skip_signal = bus_scope_send_request_stop(s) > 0; + + if (!skip_signal) { + r = unit_kill_context( + UNIT(s), + &s->kill_context, + state != SCOPE_STOP_SIGTERM ? KILL_KILL : KILL_TERMINATE, + -1, -1, false); + if (r < 0) + goto fail; + } else + r = 1; + + if (r > 0) { + r = scope_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); + if (r < 0) + goto fail; + + scope_set_state(s, state); + } else if (state == SCOPE_STOP_SIGTERM) + scope_enter_signal(s, SCOPE_STOP_SIGKILL, SCOPE_SUCCESS); + else + scope_enter_dead(s, SCOPE_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); + + scope_enter_dead(s, SCOPE_FAILURE_RESOURCES); +} + +static int scope_start(Unit *u) { + Scope *s = SCOPE(u); + int r; + + assert(s); + + if (unit_has_name(u, SPECIAL_INIT_SCOPE)) + return -EPERM; + + if (s->state == SCOPE_FAILED) + return -EPERM; + + /* We can't fulfill this right now, please try again later */ + if (s->state == SCOPE_STOP_SIGTERM || + s->state == SCOPE_STOP_SIGKILL) + return -EAGAIN; + + assert(s->state == SCOPE_DEAD); + + if (!u->transient && !manager_is_reloading_or_reexecuting(u->manager)) + return -ENOENT; + + (void) unit_realize_cgroup(u); + (void) unit_reset_cpu_usage(u); + + r = unit_attach_pids_to_cgroup(u); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to add PIDs to scope's control group: %m"); + scope_enter_dead(s, SCOPE_FAILURE_RESOURCES); + return r; + } + + s->result = SCOPE_SUCCESS; + + scope_set_state(s, SCOPE_RUNNING); + return 1; +} + +static int scope_stop(Unit *u) { + Scope *s = SCOPE(u); + + assert(s); + + if (s->state == SCOPE_STOP_SIGTERM || + s->state == SCOPE_STOP_SIGKILL) + return 0; + + assert(s->state == SCOPE_RUNNING || + s->state == SCOPE_ABANDONED); + + scope_enter_signal(s, SCOPE_STOP_SIGTERM, SCOPE_SUCCESS); + return 1; +} + +static void scope_reset_failed(Unit *u) { + Scope *s = SCOPE(u); + + assert(s); + + if (s->state == SCOPE_FAILED) + scope_set_state(s, SCOPE_DEAD); + + s->result = SCOPE_SUCCESS; +} + +static int scope_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + return unit_kill_common(u, who, signo, -1, -1, error); +} + +static int scope_get_timeout(Unit *u, usec_t *timeout) { + Scope *s = SCOPE(u); + usec_t t; + int r; + + if (!s->timer_event_source) + return 0; + + r = sd_event_source_get_time(s->timer_event_source, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + return 0; + + *timeout = t; + return 1; +} + +static int scope_serialize(Unit *u, FILE *f, FDSet *fds) { + Scope *s = SCOPE(u); + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", scope_state_to_string(s->state)); + return 0; +} + +static int scope_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Scope *s = SCOPE(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + ScopeState state; + + state = scope_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + s->deserialized_state = state; + + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +static bool scope_check_gc(Unit *u) { + assert(u); + + /* Never clean up scopes that still have a process around, + * even if the scope is formally dead. */ + + if (!u->cgroup_path) + return false; + + return cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path) <= 0; +} + +static void scope_notify_cgroup_empty_event(Unit *u) { + Scope *s = SCOPE(u); + assert(u); + + log_unit_debug(u, "cgroup is empty"); + + if (IN_SET(s->state, SCOPE_RUNNING, SCOPE_ABANDONED, SCOPE_STOP_SIGTERM, SCOPE_STOP_SIGKILL)) + scope_enter_dead(s, SCOPE_SUCCESS); +} + +static void scope_sigchld_event(Unit *u, pid_t pid, int code, int status) { + + /* If we get a SIGCHLD event for one of the processes we were + interested in, then we look for others to watch, under the + assumption that we'll sooner or later get a SIGCHLD for + them, as the original process we watched was probably the + parent of them, and they are hence now our children. */ + + 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)) + scope_notify_cgroup_empty_event(u); +} + +static int scope_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { + Scope *s = SCOPE(userdata); + + assert(s); + assert(s->timer_event_source == source); + + switch (s->state) { + + case SCOPE_STOP_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "Stopping timed out. Killing."); + scope_enter_signal(s, SCOPE_STOP_SIGKILL, SCOPE_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "Stopping timed out. Skipping SIGKILL."); + scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); + } + + break; + + case SCOPE_STOP_SIGKILL: + log_unit_warning(UNIT(s), "Still around after SIGKILL. Ignoring."); + scope_enter_dead(s, SCOPE_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } + + return 0; +} + +int scope_abandon(Scope *s) { + assert(s); + + if (unit_has_name(UNIT(s), SPECIAL_INIT_SCOPE)) + return -EPERM; + + if (!IN_SET(s->state, SCOPE_RUNNING, SCOPE_ABANDONED)) + return -ESTALE; + + s->controller = mfree(s->controller); + + /* The client is no longer watching the remaining processes, + * so let's step in here, under the assumption that the + * remaining processes will be sooner or later reassigned to + * us as parent. */ + + unit_tidy_watch_pids(UNIT(s), 0, 0); + unit_watch_all_pids(UNIT(s)); + + /* If the PID set is empty now, then let's finish this off */ + if (set_isempty(UNIT(s)->pids)) + scope_notify_cgroup_empty_event(UNIT(s)); + else + scope_set_state(s, SCOPE_ABANDONED); + + return 0; +} + +_pure_ static UnitActiveState scope_active_state(Unit *u) { + assert(u); + + return state_translation_table[SCOPE(u)->state]; +} + +_pure_ static const char *scope_sub_state_to_string(Unit *u) { + assert(u); + + return scope_state_to_string(SCOPE(u)->state); +} + +static void scope_enumerate(Manager *m) { + Unit *u; + int r; + + assert(m); + + /* Let's unconditionally add the "init.scope" special unit + * that encapsulates PID 1. Note that PID 1 already is in the + * cgroup for this, we hence just need to allocate the object + * for it and that's it. */ + + u = manager_get_unit(m, SPECIAL_INIT_SCOPE); + if (!u) { + u = unit_new(m, sizeof(Scope)); + if (!u) { + log_oom(); + return; + } + + r = unit_add_name(u, SPECIAL_INIT_SCOPE); + if (r < 0) { + unit_free(u); + log_error_errno(r, "Failed to add init.scope name"); + return; + } + } + + u->transient = true; + u->default_dependencies = false; + u->no_gc = true; + u->ignore_on_isolate = true; + u->refuse_manual_start = true; + u->refuse_manual_stop = true; + SCOPE(u)->deserialized_state = SCOPE_RUNNING; + SCOPE(u)->kill_context.kill_signal = SIGRTMIN+14; + + /* Prettify things, if we can. */ + if (!u->description) + u->description = strdup("System and Service Manager"); + if (!u->documentation) + (void) strv_extend(&u->documentation, "man:systemd(1)"); + + unit_add_to_load_queue(u); + unit_add_to_dbus_queue(u); +} + +static const char* const scope_result_table[_SCOPE_RESULT_MAX] = { + [SCOPE_SUCCESS] = "success", + [SCOPE_FAILURE_RESOURCES] = "resources", + [SCOPE_FAILURE_TIMEOUT] = "timeout", +}; + +DEFINE_STRING_TABLE_LOOKUP(scope_result, ScopeResult); + +const UnitVTable scope_vtable = { + .object_size = sizeof(Scope), + .cgroup_context_offset = offsetof(Scope, cgroup_context), + .kill_context_offset = offsetof(Scope, kill_context), + + .sections = + "Unit\0" + "Scope\0" + "Install\0", + .private_section = "Scope", + + .no_alias = true, + .no_instances = true, + .can_transient = true, + + .init = scope_init, + .load = scope_load, + .done = scope_done, + + .coldplug = scope_coldplug, + + .dump = scope_dump, + + .start = scope_start, + .stop = scope_stop, + + .kill = scope_kill, + + .get_timeout = scope_get_timeout, + + .serialize = scope_serialize, + .deserialize_item = scope_deserialize_item, + + .active_state = scope_active_state, + .sub_state_to_string = scope_sub_state_to_string, + + .check_gc = scope_check_gc, + + .sigchld_event = scope_sigchld_event, + + .reset_failed = scope_reset_failed, + + .notify_cgroup_empty = scope_notify_cgroup_empty_event, + + .bus_vtable = bus_scope_vtable, + .bus_set_property = bus_scope_set_property, + .bus_commit_properties = bus_scope_commit_properties, + + .enumerate = scope_enumerate, +}; diff --git a/src/libcore/scope.h b/src/libcore/scope.h new file mode 100644 index 0000000000..2dc86325c5 --- /dev/null +++ b/src/libcore/scope.h @@ -0,0 +1,55 @@ +#pragma once + +/*** + 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 . +***/ + +typedef struct Scope Scope; + +#include "kill.h" + +typedef enum ScopeResult { + SCOPE_SUCCESS, + SCOPE_FAILURE_RESOURCES, + SCOPE_FAILURE_TIMEOUT, + _SCOPE_RESULT_MAX, + _SCOPE_RESULT_INVALID = -1 +} ScopeResult; + +struct Scope { + Unit meta; + + CGroupContext cgroup_context; + KillContext kill_context; + + ScopeState state, deserialized_state; + ScopeResult result; + + usec_t timeout_stop_usec; + + char *controller; + + sd_event_source *timer_event_source; +}; + +extern const UnitVTable scope_vtable; + +int scope_abandon(Scope *s); + +const char* scope_result_to_string(ScopeResult i) _const_; +ScopeResult scope_result_from_string(const char *s) _pure_; diff --git a/src/libcore/selinux-access.c b/src/libcore/selinux-access.c new file mode 100644 index 0000000000..76fcc9a6be --- /dev/null +++ b/src/libcore/selinux-access.c @@ -0,0 +1,280 @@ +/*** + This file is part of systemd. + + Copyright 2012 Dan Walsh + + 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 . +***/ + +#include "selinux-access.h" + +#ifdef HAVE_SELINUX + +#include +#include +#include +#include +#ifdef HAVE_AUDIT +#include +#endif + +#include + +#include "alloc-util.h" +#include "audit-fd.h" +#include "bus-util.h" +#include "log.h" +#include "path-util.h" +#include "selinux-util.h" +#include "stdio-util.h" +#include "strv.h" +#include "util.h" + +static bool initialized = false; + +struct audit_info { + sd_bus_creds *creds; + const char *path; + const char *cmdline; +}; + +/* + Any time an access gets denied this callback will be called + with the audit data. We then need to just copy the audit data into the msgbuf. +*/ +static int audit_callback( + void *auditdata, + security_class_t cls, + char *msgbuf, + size_t msgbufsize) { + + const struct audit_info *audit = auditdata; + uid_t uid = 0, login_uid = 0; + gid_t gid = 0; + char login_uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a"; + char uid_buf[DECIMAL_STR_MAX(uid_t) + 1] = "n/a"; + char gid_buf[DECIMAL_STR_MAX(gid_t) + 1] = "n/a"; + + if (sd_bus_creds_get_audit_login_uid(audit->creds, &login_uid) >= 0) + xsprintf(login_uid_buf, UID_FMT, login_uid); + if (sd_bus_creds_get_euid(audit->creds, &uid) >= 0) + xsprintf(uid_buf, UID_FMT, uid); + if (sd_bus_creds_get_egid(audit->creds, &gid) >= 0) + xsprintf(gid_buf, GID_FMT, gid); + + snprintf(msgbuf, msgbufsize, + "auid=%s uid=%s gid=%s%s%s%s%s%s%s", + login_uid_buf, uid_buf, gid_buf, + audit->path ? " path=\"" : "", strempty(audit->path), audit->path ? "\"" : "", + audit->cmdline ? " cmdline=\"" : "", strempty(audit->cmdline), audit->cmdline ? "\"" : ""); + + return 0; +} + +static int callback_type_to_priority(int type) { + switch(type) { + + case SELINUX_ERROR: + return LOG_ERR; + + case SELINUX_WARNING: + return LOG_WARNING; + + case SELINUX_INFO: + return LOG_INFO; + + case SELINUX_AVC: + default: + return LOG_NOTICE; + } +} + +/* + libselinux uses this callback when access gets denied or other + events happen. If audit is turned on, messages will be reported + using audit netlink, otherwise they will be logged using the usual + channels. + + Code copied from dbus and modified. +*/ +_printf_(2, 3) static int log_callback(int type, const char *fmt, ...) { + va_list ap; + +#ifdef HAVE_AUDIT + int fd; + + fd = get_audit_fd(); + + if (fd >= 0) { + _cleanup_free_ char *buf = NULL; + int r; + + va_start(ap, fmt); + r = vasprintf(&buf, fmt, ap); + va_end(ap); + + if (r >= 0) { + audit_log_user_avc_message(fd, AUDIT_USER_AVC, buf, NULL, NULL, NULL, 0); + return 0; + } + } +#endif + + va_start(ap, fmt); + log_internalv(LOG_AUTH | callback_type_to_priority(type), 0, __FILE__, __LINE__, __FUNCTION__, fmt, ap); + va_end(ap); + + return 0; +} + +static int access_init(sd_bus_error *error) { + + if (!mac_selinux_use()) + return 0; + + if (initialized) + return 1; + + if (avc_open(NULL, 0) != 0) { + int enforce, saved_errno = errno; + + enforce = security_getenforce(); + log_full_errno(enforce != 0 ? LOG_ERR : LOG_WARNING, saved_errno, "Failed to open the SELinux AVC: %m"); + + /* If enforcement isn't on, then let's suppress this + * error, and just don't do any AVC checks. The + * warning we printed is hence all the admin will + * see. */ + if (enforce == 0) + return 0; + + /* Return an access denied error, if we couldn't load + * the AVC but enforcing mode was on, or we couldn't + * determine whether it is one. */ + return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to open the SELinux AVC: %s", strerror(saved_errno)); + } + + selinux_set_callback(SELINUX_CB_AUDIT, (union selinux_callback) audit_callback); + selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) log_callback); + + initialized = true; + return 1; +} + +/* + This function communicates with the kernel to check whether or not it should + allow the access. + If the machine is in permissive mode it will return ok. Audit messages will + still be generated if the access would be denied in enforcing mode. +*/ +int mac_selinux_generic_access_check( + sd_bus_message *message, + const char *path, + const char *permission, + sd_bus_error *error) { + + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + const char *tclass = NULL, *scon = NULL; + struct audit_info audit_info = {}; + _cleanup_free_ char *cl = NULL; + security_context_t fcon = NULL; + char **cmdline = NULL; + int r = 0; + + assert(message); + assert(permission); + assert(error); + + r = access_init(error); + if (r <= 0) + return r; + + r = sd_bus_query_sender_creds( + message, + SD_BUS_CREDS_PID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EGID| + SD_BUS_CREDS_CMDLINE|SD_BUS_CREDS_AUDIT_LOGIN_UID| + SD_BUS_CREDS_SELINUX_CONTEXT| + SD_BUS_CREDS_AUGMENT /* get more bits from /proc */, + &creds); + if (r < 0) + goto finish; + + /* The SELinux context is something we really should have + * gotten directly from the message or sender, and not be an + * augmented field. If it was augmented we cannot use it for + * authorization, since this is racy and vulnerable. Let's add + * an extra check, just in case, even though this really + * shouldn't be possible. */ + assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_SELINUX_CONTEXT) == 0, -EPERM); + + r = sd_bus_creds_get_selinux_context(creds, &scon); + if (r < 0) + goto finish; + + if (path) { + /* Get the file context of the unit file */ + + r = getfilecon_raw(path, &fcon); + if (r < 0) { + r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get file context on %s.", path); + goto finish; + } + + tclass = "service"; + } else { + r = getcon_raw(&fcon); + if (r < 0) { + r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Failed to get current context."); + goto finish; + } + + tclass = "system"; + } + + sd_bus_creds_get_cmdline(creds, &cmdline); + cl = strv_join(cmdline, " "); + + audit_info.creds = creds; + audit_info.path = path; + audit_info.cmdline = cl; + + r = selinux_check_access(scon, fcon, tclass, permission, &audit_info); + if (r < 0) + r = sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "SELinux policy denies access."); + + log_debug("SELinux access check scon=%s tcon=%s tclass=%s perm=%s path=%s cmdline=%s: %i", scon, fcon, tclass, permission, path, cl, r); + +finish: + freecon(fcon); + + if (r < 0 && security_getenforce() != 1) { + sd_bus_error_free(error); + r = 0; + } + + return r; +} + +#else + +int mac_selinux_generic_access_check( + sd_bus_message *message, + const char *path, + const char *permission, + sd_bus_error *error) { + + return 0; +} + +#endif diff --git a/src/libcore/selinux-access.h b/src/libcore/selinux-access.h new file mode 100644 index 0000000000..cbf33ef6c4 --- /dev/null +++ b/src/libcore/selinux-access.h @@ -0,0 +1,45 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 Dan Walsh + + 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 . +***/ + +#include + +#include "bus-util.h" +#include "manager.h" + +int mac_selinux_generic_access_check(sd_bus_message *message, const char *path, const char *permission, sd_bus_error *error); + +#ifdef HAVE_SELINUX + +#define mac_selinux_access_check(message, permission, error) \ + mac_selinux_generic_access_check((message), NULL, (permission), (error)) + +#define mac_selinux_unit_access_check(unit, message, permission, error) \ + ({ \ + Unit *_unit = (unit); \ + mac_selinux_generic_access_check((message), _unit->source_path ?: _unit->fragment_path, (permission), (error)); \ + }) + +#else + +#define mac_selinux_access_check(message, permission, error) 0 +#define mac_selinux_unit_access_check(unit, message, permission, error) 0 + +#endif diff --git a/src/libcore/selinux-setup.c b/src/libcore/selinux-setup.c new file mode 100644 index 0000000000..9a115a4387 --- /dev/null +++ b/src/libcore/selinux-setup.c @@ -0,0 +1,121 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#ifdef HAVE_SELINUX +#include +#endif + +#include "log.h" +#include "macro.h" +#include "selinux-setup.h" +#include "selinux-util.h" +#include "string-util.h" +#include "util.h" + +#ifdef HAVE_SELINUX +_printf_(2,3) +static int null_log(int type, const char *fmt, ...) { + return 0; +} +#endif + +int mac_selinux_setup(bool *loaded_policy) { + +#ifdef HAVE_SELINUX + int enforce = 0; + usec_t before_load, after_load; + security_context_t con; + int r; + union selinux_callback cb; + bool initialized = false; + + assert(loaded_policy); + + /* Turn off all of SELinux' own logging, we want to do that */ + cb.func_log = null_log; + selinux_set_callback(SELINUX_CB_LOG, cb); + + /* Don't load policy in the initrd if we don't appear to have + * it. For the real root, we check below if we've already + * loaded policy, and return gracefully. + */ + if (in_initrd() && access(selinux_path(), F_OK) < 0) + return 0; + + /* Already initialized by somebody else? */ + r = getcon_raw(&con); + if (r == 0) { + initialized = !streq(con, "kernel"); + freecon(con); + } + + /* Make sure we have no fds open while loading the policy and + * transitioning */ + log_close(); + + /* Now load the policy */ + before_load = now(CLOCK_MONOTONIC); + r = selinux_init_load_policy(&enforce); + if (r == 0) { + _cleanup_(mac_selinux_freep) char *label = NULL; + char timespan[FORMAT_TIMESPAN_MAX]; + + mac_selinux_retest(); + + /* Transition to the new context */ + r = mac_selinux_get_create_label_from_exe(SYSTEMD_BINARY_PATH, &label); + if (r < 0 || !label) { + log_open(); + log_error("Failed to compute init label, ignoring."); + } else { + r = setcon(label); + + log_open(); + if (r < 0) + log_error("Failed to transition into init label '%s', ignoring.", label); + } + + after_load = now(CLOCK_MONOTONIC); + + log_info("Successfully loaded SELinux policy in %s.", + format_timespan(timespan, sizeof(timespan), after_load - before_load, 0)); + + *loaded_policy = true; + + } else { + log_open(); + + if (enforce > 0) { + if (!initialized) { + log_emergency("Failed to load SELinux policy."); + return -EIO; + } + + log_warning("Failed to load new SELinux policy. Continuing with old policy."); + } else + log_debug("Unable to load SELinux policy. Ignoring."); + } +#endif + + return 0; +} diff --git a/src/libcore/selinux-setup.h b/src/libcore/selinux-setup.h new file mode 100644 index 0000000000..7b613249b0 --- /dev/null +++ b/src/libcore/selinux-setup.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +int mac_selinux_setup(bool *loaded_policy); diff --git a/src/libcore/service.c b/src/libcore/service.c new file mode 100644 index 0000000000..ac7e41d777 --- /dev/null +++ b/src/libcore/service.c @@ -0,0 +1,3366 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#include "alloc-util.h" +#include "async.h" +#include "bus-error.h" +#include "bus-kernel.h" +#include "bus-util.h" +#include "dbus-service.h" +#include "def.h" +#include "env-util.h" +#include "escape.h" +#include "exit-status.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "load-dropin.h" +#include "load-fragment.h" +#include "log.h" +#include "manager.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "service.h" +#include "signal-util.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit-printf.h" +#include "unit.h" +#include "utf8.h" +#include "util.h" + +static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { + [SERVICE_DEAD] = UNIT_INACTIVE, + [SERVICE_START_PRE] = UNIT_ACTIVATING, + [SERVICE_START] = UNIT_ACTIVATING, + [SERVICE_START_POST] = UNIT_ACTIVATING, + [SERVICE_RUNNING] = UNIT_ACTIVE, + [SERVICE_EXITED] = UNIT_ACTIVE, + [SERVICE_RELOAD] = UNIT_RELOADING, + [SERVICE_STOP] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGABRT] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, + [SERVICE_STOP_POST] = UNIT_DEACTIVATING, + [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, + [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, + [SERVICE_FAILED] = UNIT_FAILED, + [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING +}; + +/* For Type=idle we never want to delay any other jobs, hence we + * consider idle jobs active as soon as we start working on them */ +static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = { + [SERVICE_DEAD] = UNIT_INACTIVE, + [SERVICE_START_PRE] = UNIT_ACTIVE, + [SERVICE_START] = UNIT_ACTIVE, + [SERVICE_START_POST] = UNIT_ACTIVE, + [SERVICE_RUNNING] = UNIT_ACTIVE, + [SERVICE_EXITED] = UNIT_ACTIVE, + [SERVICE_RELOAD] = UNIT_RELOADING, + [SERVICE_STOP] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGABRT] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGKILL] = UNIT_DEACTIVATING, + [SERVICE_STOP_POST] = UNIT_DEACTIVATING, + [SERVICE_FINAL_SIGTERM] = UNIT_DEACTIVATING, + [SERVICE_FINAL_SIGKILL] = UNIT_DEACTIVATING, + [SERVICE_FAILED] = UNIT_FAILED, + [SERVICE_AUTO_RESTART] = UNIT_ACTIVATING +}; + +static int service_dispatch_io(sd_event_source *source, int fd, uint32_t events, void *userdata); +static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); +static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata); + +static void service_enter_signal(Service *s, ServiceState state, ServiceResult f); +static void service_enter_reload_by_notify(Service *s); + +static void service_init(Unit *u) { + Service *s = SERVICE(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + s->timeout_start_usec = u->manager->default_timeout_start_usec; + s->timeout_stop_usec = u->manager->default_timeout_stop_usec; + s->restart_usec = u->manager->default_restart_usec; + s->runtime_max_usec = USEC_INFINITY; + s->type = _SERVICE_TYPE_INVALID; + s->socket_fd = -1; + s->bus_endpoint_fd = -1; + s->stdin_fd = s->stdout_fd = s->stderr_fd = -1; + s->guess_main_pid = true; + + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; +} + +static void service_unwatch_control_pid(Service *s) { + assert(s); + + if (s->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(s), s->control_pid); + s->control_pid = 0; +} + +static void service_unwatch_main_pid(Service *s) { + assert(s); + + if (s->main_pid <= 0) + return; + + unit_unwatch_pid(UNIT(s), s->main_pid); + s->main_pid = 0; +} + +static void service_unwatch_pid_file(Service *s) { + if (!s->pid_file_pathspec) + return; + + log_unit_debug(UNIT(s), "Stopping watch for PID file %s", s->pid_file_pathspec->path); + path_spec_unwatch(s->pid_file_pathspec); + path_spec_done(s->pid_file_pathspec); + s->pid_file_pathspec = mfree(s->pid_file_pathspec); +} + +static int service_set_main_pid(Service *s, pid_t pid) { + pid_t ppid; + + assert(s); + + if (pid <= 1) + return -EINVAL; + + if (pid == getpid()) + return -EINVAL; + + if (s->main_pid == pid && s->main_pid_known) + return 0; + + if (s->main_pid != pid) { + service_unwatch_main_pid(s); + exec_status_start(&s->main_exec_status, pid); + } + + s->main_pid = pid; + s->main_pid_known = true; + + if (get_process_ppid(pid, &ppid) >= 0 && ppid != getpid()) { + log_unit_warning(UNIT(s), "Supervising process "PID_FMT" which is not our child. We'll most likely not notice when it exits.", pid); + s->main_pid_alien = true; + } else + s->main_pid_alien = false; + + return 0; +} + +static void service_close_socket_fd(Service *s) { + assert(s); + + s->socket_fd = asynchronous_close(s->socket_fd); +} + +static void service_connection_unref(Service *s) { + assert(s); + + if (!UNIT_ISSET(s->accept_socket)) + return; + + socket_connection_unref(SOCKET(UNIT_DEREF(s->accept_socket))); + unit_ref_unset(&s->accept_socket); +} + +static void service_stop_watchdog(Service *s) { + assert(s); + + s->watchdog_event_source = sd_event_source_unref(s->watchdog_event_source); + s->watchdog_timestamp = DUAL_TIMESTAMP_NULL; +} + +static void service_start_watchdog(Service *s) { + int r; + + assert(s); + + if (s->watchdog_usec <= 0) + 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)); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to reset watchdog timer: %m"); + return; + } + + r = sd_event_source_set_enabled(s->watchdog_event_source, SD_EVENT_ONESHOT); + } else { + r = sd_event_add_time( + UNIT(s)->manager->event, + &s->watchdog_event_source, + CLOCK_MONOTONIC, + usec_add(s->watchdog_timestamp.monotonic, s->watchdog_usec), 0, + service_dispatch_watchdog, s); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to add watchdog timer: %m"); + return; + } + + (void) sd_event_source_set_description(s->watchdog_event_source, "service-watchdog"); + + /* Let's process everything else which might be a sign + * of living before we consider a service died. */ + r = sd_event_source_set_priority(s->watchdog_event_source, SD_EVENT_PRIORITY_IDLE); + } + + if (r < 0) + log_unit_warning_errno(UNIT(s), r, "Failed to install watchdog timer: %m"); +} + +static void service_reset_watchdog(Service *s) { + assert(s); + + dual_timestamp_get(&s->watchdog_timestamp); + service_start_watchdog(s); +} + +static void service_fd_store_unlink(ServiceFDStore *fs) { + + if (!fs) + return; + + if (fs->service) { + assert(fs->service->n_fd_store > 0); + LIST_REMOVE(fd_store, fs->service->fd_store, fs); + fs->service->n_fd_store--; + } + + if (fs->event_source) { + sd_event_source_set_enabled(fs->event_source, SD_EVENT_OFF); + sd_event_source_unref(fs->event_source); + } + + free(fs->fdname); + safe_close(fs->fd); + free(fs); +} + +static void service_release_resources(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + if (!s->fd_store && s->stdin_fd < 0 && s->stdout_fd < 0 && s->stderr_fd < 0) + return; + + log_unit_debug(u, "Releasing all resources."); + + s->stdin_fd = safe_close(s->stdin_fd); + s->stdout_fd = safe_close(s->stdout_fd); + s->stderr_fd = safe_close(s->stderr_fd); + + while (s->fd_store) + service_fd_store_unlink(s->fd_store); + + assert(s->n_fd_store == 0); +} + +static void service_done(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + s->pid_file = mfree(s->pid_file); + s->status_text = mfree(s->status_text); + + s->exec_runtime = exec_runtime_unref(s->exec_runtime); + exec_command_free_array(s->exec_command, _SERVICE_EXEC_COMMAND_MAX); + s->control_command = NULL; + s->main_command = NULL; + + exit_status_set_free(&s->restart_prevent_status); + exit_status_set_free(&s->restart_force_status); + exit_status_set_free(&s->success_status); + + /* This will leak a process, but at least no memory or any of + * our resources */ + service_unwatch_main_pid(s); + service_unwatch_control_pid(s); + service_unwatch_pid_file(s); + + if (s->bus_name) { + unit_unwatch_bus_name(u, s->bus_name); + s->bus_name = mfree(s->bus_name); + } + + s->bus_name_owner = mfree(s->bus_name_owner); + + s->bus_endpoint_fd = safe_close(s->bus_endpoint_fd); + service_close_socket_fd(s); + service_connection_unref(s); + + unit_ref_unset(&s->accept_socket); + + service_stop_watchdog(s); + + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + + service_release_resources(u); +} + +static int on_fd_store_io(sd_event_source *e, int fd, uint32_t revents, void *userdata) { + ServiceFDStore *fs = userdata; + + assert(e); + assert(fs); + + /* If we get either EPOLLHUP or EPOLLERR, it's time to remove this entry from the fd store */ + service_fd_store_unlink(fs); + return 0; +} + +static int service_add_fd_store(Service *s, int fd, const char *name) { + ServiceFDStore *fs; + int r; + + assert(s); + assert(fd >= 0); + + if (s->n_fd_store >= s->n_fd_store_max) + return 0; + + LIST_FOREACH(fd_store, fs, s->fd_store) { + r = same_fd(fs->fd, fd); + if (r < 0) + return r; + if (r > 0) { + /* Already included */ + safe_close(fd); + return 1; + } + } + + fs = new0(ServiceFDStore, 1); + if (!fs) + return -ENOMEM; + + fs->fd = fd; + fs->service = s; + fs->fdname = strdup(name ?: "stored"); + if (!fs->fdname) { + free(fs); + return -ENOMEM; + } + + r = sd_event_add_io(UNIT(s)->manager->event, &fs->event_source, fd, 0, on_fd_store_io, fs); + if (r < 0) { + free(fs->fdname); + free(fs); + return r; + } + + (void) sd_event_source_set_description(fs->event_source, "service-fd-store"); + + LIST_PREPEND(fd_store, s->fd_store, fs); + s->n_fd_store++; + + return 1; +} + +static int service_add_fd_store_set(Service *s, FDSet *fds, const char *name) { + int r; + + assert(s); + + if (fdset_size(fds) <= 0) + return 0; + + while (s->n_fd_store < s->n_fd_store_max) { + _cleanup_close_ int fd = -1; + + fd = fdset_steal_first(fds); + if (fd < 0) + break; + + r = service_add_fd_store(s, fd, name); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Couldn't add fd to fd store: %m"); + if (r > 0) { + log_unit_debug(UNIT(s), "Added fd to fd store."); + fd = -1; + } + } + + if (fdset_size(fds) > 0) + log_unit_warning(UNIT(s), "Tried to store more fds than FileDescriptorStoreMax=%u allows, closing remaining.", s->n_fd_store_max); + + return 0; +} + +static int service_arm_timer(Service *s, usec_t usec) { + int r; + + assert(s); + + if (s->timer_event_source) { + r = sd_event_source_set_time(s->timer_event_source, usec); + if (r < 0) + return r; + + return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); + } + + if (usec == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + UNIT(s)->manager->event, + &s->timer_event_source, + CLOCK_MONOTONIC, + usec, 0, + service_dispatch_timer, s); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s->timer_event_source, "service-timer"); + + return 0; +} + +static int service_verify(Service *s) { + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (!s->exec_command[SERVICE_EXEC_START] && !s->exec_command[SERVICE_EXEC_STOP]) { + log_unit_error(UNIT(s), "Service lacks both ExecStart= and ExecStop= setting. Refusing."); + return -EINVAL; + } + + if (s->type != SERVICE_ONESHOT && !s->exec_command[SERVICE_EXEC_START]) { + log_unit_error(UNIT(s), "Service has no ExecStart= setting, which is only allowed for Type=oneshot services. Refusing."); + return -EINVAL; + } + + if (!s->remain_after_exit && !s->exec_command[SERVICE_EXEC_START]) { + log_unit_error(UNIT(s), "Service has no ExecStart= setting, which is only allowed for RemainAfterExit=yes services. Refusing."); + return -EINVAL; + } + + if (s->type != SERVICE_ONESHOT && s->exec_command[SERVICE_EXEC_START]->command_next) { + log_unit_error(UNIT(s), "Service has more than one ExecStart= setting, which is only allowed for Type=oneshot services. Refusing."); + return -EINVAL; + } + + if (s->type == SERVICE_ONESHOT && s->restart != SERVICE_RESTART_NO) { + log_unit_error(UNIT(s), "Service has Restart= setting other than no, which isn't allowed for Type=oneshot services. Refusing."); + return -EINVAL; + } + + if (s->type == SERVICE_ONESHOT && !exit_status_set_is_empty(&s->restart_force_status)) { + log_unit_error(UNIT(s), "Service has RestartForceStatus= set, which isn't allowed for Type=oneshot services. Refusing."); + return -EINVAL; + } + + if (s->type == SERVICE_DBUS && !s->bus_name) { + log_unit_error(UNIT(s), "Service is of type D-Bus but no D-Bus service name has been specified. Refusing."); + return -EINVAL; + } + + if (s->bus_name && s->type != SERVICE_DBUS) + log_unit_warning(UNIT(s), "Service has a D-Bus service name specified, but is not of type dbus. Ignoring."); + + if (s->exec_context.pam_name && !(s->kill_context.kill_mode == KILL_CONTROL_GROUP || s->kill_context.kill_mode == KILL_MIXED)) { + log_unit_error(UNIT(s), "Service has PAM enabled. Kill mode must be set to 'control-group' or 'mixed'. Refusing."); + return -EINVAL; + } + + if (s->usb_function_descriptors && !s->usb_function_strings) + log_unit_warning(UNIT(s), "Service has USBFunctionDescriptors= setting, but no USBFunctionStrings=. Ignoring."); + + if (!s->usb_function_descriptors && s->usb_function_strings) + log_unit_warning(UNIT(s), "Service has USBFunctionStrings= setting, but no USBFunctionDescriptors=. Ignoring."); + + if (s->runtime_max_usec != USEC_INFINITY && s->type == SERVICE_ONESHOT) + log_unit_warning(UNIT(s), "MaxRuntimeSec= has no effect in combination with Type=oneshot. Ignoring."); + + return 0; +} + +static int service_add_default_dependencies(Service *s) { + int r; + + assert(s); + + if (!UNIT(s)->default_dependencies) + return 0; + + /* Add a number of automatic dependencies useful for the + * majority of services. */ + + if (UNIT(s)->manager->running_as == MANAGER_SYSTEM) { + /* First, pull in the really early boot stuff, and + * require it, so that we fail if we can't acquire + * it. */ + + r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); + if (r < 0) + return r; + } else { + + /* In the --user instance there's no sysinit.target, + * in that case require basic.target instead. */ + + r = unit_add_dependency_by_name(UNIT(s), UNIT_REQUIRES, SPECIAL_BASIC_TARGET, NULL, true); + if (r < 0) + return r; + } + + /* Second, if the rest of the base system is in the same + * transaction, order us after it, but do not pull it in or + * even require it. */ + r = unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_BASIC_TARGET, NULL, true); + if (r < 0) + return r; + + /* Third, add us in for normal shutdown. */ + return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static void service_fix_output(Service *s) { + assert(s); + + /* If nothing has been explicitly configured, patch default + * output in. If input is socket/tty we avoid this however, + * since in that case we want output to default to the same + * place as we read input from. */ + + if (s->exec_context.std_error == EXEC_OUTPUT_INHERIT && + s->exec_context.std_output == EXEC_OUTPUT_INHERIT && + s->exec_context.std_input == EXEC_INPUT_NULL) + s->exec_context.std_error = UNIT(s)->manager->default_std_error; + + if (s->exec_context.std_output == EXEC_OUTPUT_INHERIT && + s->exec_context.std_input == EXEC_INPUT_NULL) + s->exec_context.std_output = UNIT(s)->manager->default_std_output; +} + +static int service_setup_bus_name(Service *s) { + int r; + + assert(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"); + } + + /* 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); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to add dependency on " SPECIAL_DBUS_SOCKET ": %m"); + + r = unit_watch_bus_name(UNIT(s), s->bus_name); + if (r == -EEXIST) + return log_unit_error_errno(UNIT(s), r, "Two services allocated for the same bus name %s, refusing operation.", s->bus_name); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Cannot watch bus name %s: %m", s->bus_name); + + return 0; +} + +static int service_add_extras(Service *s) { + int r; + + assert(s); + + if (s->type == _SERVICE_TYPE_INVALID) { + /* Figure out a type automatically */ + if (s->bus_name) + s->type = SERVICE_DBUS; + else if (s->exec_command[SERVICE_EXEC_START]) + s->type = SERVICE_SIMPLE; + else + s->type = SERVICE_ONESHOT; + } + + /* Oneshot services have disabled start timeout by default */ + if (s->type == SERVICE_ONESHOT && !s->start_timeout_defined) + s->timeout_start_usec = USEC_INFINITY; + + service_fix_output(s); + + r = unit_patch_contexts(UNIT(s)); + if (r < 0) + return r; + + r = unit_add_exec_dependencies(UNIT(s), &s->exec_context); + if (r < 0) + return r; + + r = unit_set_default_slice(UNIT(s)); + if (r < 0) + return r; + + if (s->type == SERVICE_NOTIFY && s->notify_access == NOTIFY_NONE) + s->notify_access = NOTIFY_MAIN; + + if (s->watchdog_usec > 0 && s->notify_access == NOTIFY_NONE) + s->notify_access = NOTIFY_MAIN; + + r = service_add_default_dependencies(s); + if (r < 0) + return r; + + r = service_setup_bus_name(s); + if (r < 0) + return r; + + return 0; +} + +static int service_load(Unit *u) { + Service *s = SERVICE(u); + int r; + + assert(s); + + /* Load a .service file */ + r = unit_load_fragment(u); + if (r < 0) + return r; + + /* Still nothing found? Then let's give up */ + if (u->load_state == UNIT_STUB) + return -ENOENT; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + + /* We were able to load something, then let's add in + * the dropin directories. */ + r = unit_load_dropin(u); + if (r < 0) + return r; + + /* This is a new unit? Then let's add in some + * extras */ + r = service_add_extras(s); + if (r < 0) + return r; + } + + return service_verify(s); +} + +static void service_dump(Unit *u, FILE *f, const char *prefix) { + ServiceExecCommand c; + Service *s = SERVICE(u); + const char *prefix2; + + assert(s); + + prefix = strempty(prefix); + prefix2 = strjoina(prefix, "\t"); + + fprintf(f, + "%sService State: %s\n" + "%sResult: %s\n" + "%sReload Result: %s\n" + "%sPermissionsStartOnly: %s\n" + "%sRootDirectoryStartOnly: %s\n" + "%sRemainAfterExit: %s\n" + "%sGuessMainPID: %s\n" + "%sType: %s\n" + "%sRestart: %s\n" + "%sNotifyAccess: %s\n" + "%sNotifyState: %s\n", + prefix, service_state_to_string(s->state), + prefix, service_result_to_string(s->result), + prefix, service_result_to_string(s->reload_result), + prefix, yes_no(s->permissions_start_only), + prefix, yes_no(s->root_directory_start_only), + prefix, yes_no(s->remain_after_exit), + prefix, yes_no(s->guess_main_pid), + prefix, service_type_to_string(s->type), + prefix, service_restart_to_string(s->restart), + prefix, notify_access_to_string(s->notify_access), + prefix, notify_state_to_string(s->notify_state)); + + if (s->control_pid > 0) + fprintf(f, + "%sControl PID: "PID_FMT"\n", + prefix, s->control_pid); + + if (s->main_pid > 0) + fprintf(f, + "%sMain PID: "PID_FMT"\n" + "%sMain PID Known: %s\n" + "%sMain PID Alien: %s\n", + prefix, s->main_pid, + prefix, yes_no(s->main_pid_known), + prefix, yes_no(s->main_pid_alien)); + + if (s->pid_file) + fprintf(f, + "%sPIDFile: %s\n", + prefix, s->pid_file); + + if (s->bus_name) + fprintf(f, + "%sBusName: %s\n" + "%sBus Name Good: %s\n", + prefix, s->bus_name, + prefix, yes_no(s->bus_name_good)); + + kill_context_dump(&s->kill_context, f, prefix); + exec_context_dump(&s->exec_context, f, prefix); + + for (c = 0; c < _SERVICE_EXEC_COMMAND_MAX; c++) { + + if (!s->exec_command[c]) + continue; + + fprintf(f, "%s-> %s:\n", + prefix, service_exec_command_to_string(c)); + + exec_command_dump_list(s->exec_command[c], f, prefix2); + } + + if (s->status_text) + fprintf(f, "%sStatus Text: %s\n", + prefix, s->status_text); + + if (s->n_fd_store_max > 0) + fprintf(f, + "%sFile Descriptor Store Max: %u\n" + "%sFile Descriptor Store Current: %u\n", + prefix, s->n_fd_store_max, + prefix, s->n_fd_store); +} + +static int service_load_pid_file(Service *s, bool may_warn) { + _cleanup_free_ char *k = NULL; + int r; + pid_t pid; + + assert(s); + + if (!s->pid_file) + return -ENOENT; + + r = read_one_line_file(s->pid_file, &k); + if (r < 0) { + if (may_warn) + log_unit_info_errno(UNIT(s), r, "PID file %s not readable (yet?) after %s: %m", s->pid_file, service_state_to_string(s->state)); + return r; + } + + r = parse_pid(k, &pid); + if (r < 0) { + if (may_warn) + log_unit_info_errno(UNIT(s), r, "Failed to read PID from file %s: %m", s->pid_file); + return r; + } + + if (!pid_is_alive(pid)) { + if (may_warn) + log_unit_info(UNIT(s), "PID "PID_FMT" read from file %s does not exist or is a zombie.", pid, s->pid_file); + return -ESRCH; + } + + if (s->main_pid_known) { + if (pid == s->main_pid) + return 0; + + log_unit_debug(UNIT(s), "Main PID changing: "PID_FMT" -> "PID_FMT, s->main_pid, pid); + + service_unwatch_main_pid(s); + s->main_pid_known = false; + } else + log_unit_debug(UNIT(s), "Main PID loaded: "PID_FMT, pid); + + r = service_set_main_pid(s, pid); + if (r < 0) + return r; + + r = unit_watch_pid(UNIT(s), pid); + if (r < 0) { + /* FIXME: we need to do something here */ + log_unit_warning_errno(UNIT(s), r, "Failed to watch PID "PID_FMT" for service: %m", pid); + return r; + } + + return 0; +} + +static int service_search_main_pid(Service *s) { + pid_t pid = 0; + int r; + + assert(s); + + /* If we know it anyway, don't ever fallback to unreliable + * heuristics */ + if (s->main_pid_known) + return 0; + + if (!s->guess_main_pid) + return 0; + + assert(s->main_pid <= 0); + + r = unit_search_main_pid(UNIT(s), &pid); + if (r < 0) + return r; + + log_unit_debug(UNIT(s), "Main PID guessed: "PID_FMT, pid); + r = service_set_main_pid(s, pid); + if (r < 0) + return r; + + r = unit_watch_pid(UNIT(s), pid); + if (r < 0) { + /* FIXME: we need to do something here */ + log_unit_warning_errno(UNIT(s), r, "Failed to watch PID "PID_FMT" from: %m", pid); + return r; + } + + return 0; +} + +static void service_set_state(Service *s, ServiceState state) { + ServiceState old_state; + const UnitActiveState *table; + + assert(s); + + table = s->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table; + + old_state = s->state; + s->state = state; + + service_unwatch_pid_file(s); + + if (!IN_SET(state, + SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RUNNING, + SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, + SERVICE_AUTO_RESTART)) + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + + if (!IN_SET(state, + SERVICE_START, SERVICE_START_POST, + SERVICE_RUNNING, SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { + service_unwatch_main_pid(s); + s->main_command = NULL; + } + + if (!IN_SET(state, + SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { + service_unwatch_control_pid(s); + s->control_command = NULL; + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; + } + + if (IN_SET(state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) + unit_unwatch_all_pids(UNIT(s)); + + if (!IN_SET(state, + SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RUNNING, SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) && + !(state == SERVICE_DEAD && UNIT(s)->job)) { + service_close_socket_fd(s); + service_connection_unref(s); + } + + if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) + service_stop_watchdog(s); + + /* For the inactive states unit_notify() will trim the cgroup, + * but for exit we have to do that ourselves... */ + if (state == SERVICE_EXITED && UNIT(s)->manager->n_reloading <= 0) + unit_prune_cgroup(UNIT(s)); + + /* For remain_after_exit services, let's see if we can "release" the + * hold on the console, since unit_notify() only does that in case of + * change of state */ + if (state == SERVICE_EXITED && + s->remain_after_exit && + UNIT(s)->manager->n_on_console > 0) { + + ExecContext *ec; + + ec = unit_get_exec_context(UNIT(s)); + if (ec && exec_context_may_touch_console(ec)) { + Manager *m = UNIT(s)->manager; + + m->n_on_console --; + if (m->n_on_console == 0) + /* unset no_console_output flag, since the console is free */ + m->no_console_output = false; + } + } + + if (old_state != state) + log_unit_debug(UNIT(s), "Changed %s -> %s", service_state_to_string(old_state), service_state_to_string(state)); + + unit_notify(UNIT(s), table[old_state], table[state], s->reload_result == SERVICE_SUCCESS); +} + +static usec_t service_coldplug_timeout(Service *s) { + assert(s); + + switch (s->deserialized_state) { + + case SERVICE_START_PRE: + case SERVICE_START: + case SERVICE_START_POST: + case SERVICE_RELOAD: + return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_start_usec); + + case SERVICE_RUNNING: + return usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec); + + case SERVICE_STOP: + case SERVICE_STOP_SIGABRT: + case SERVICE_STOP_SIGTERM: + case SERVICE_STOP_SIGKILL: + case SERVICE_STOP_POST: + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_stop_usec); + + case SERVICE_AUTO_RESTART: + return usec_add(UNIT(s)->inactive_enter_timestamp.monotonic, s->restart_usec); + + default: + return USEC_INFINITY; + } +} + +static int service_coldplug(Unit *u) { + Service *s = SERVICE(u); + int r; + + assert(s); + assert(s->state == SERVICE_DEAD); + + if (s->deserialized_state == s->state) + return 0; + + r = service_arm_timer(s, service_coldplug_timeout(s)); + if (r < 0) + return r; + + if (s->main_pid > 0 && + pid_is_unwaited(s->main_pid) && + ((s->deserialized_state == SERVICE_START && IN_SET(s->type, SERVICE_FORKING, SERVICE_DBUS, SERVICE_ONESHOT, SERVICE_NOTIFY)) || + IN_SET(s->deserialized_state, + SERVICE_START, SERVICE_START_POST, + SERVICE_RUNNING, SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))) { + r = unit_watch_pid(UNIT(s), s->main_pid); + if (r < 0) + return r; + } + + if (s->control_pid > 0 && + pid_is_unwaited(s->control_pid) && + IN_SET(s->deserialized_state, + SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RELOAD, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { + r = unit_watch_pid(UNIT(s), s->control_pid); + if (r < 0) + return r; + } + + if (!IN_SET(s->deserialized_state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) + unit_watch_all_pids(UNIT(s)); + + if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) + service_start_watchdog(s); + + service_set_state(s, s->deserialized_state); + return 0; +} + +static int service_collect_fds(Service *s, int **fds, char ***fd_names) { + _cleanup_strv_free_ char **rfd_names = NULL; + _cleanup_free_ int *rfds = NULL; + int rn_fds = 0, r; + + assert(s); + assert(fds); + assert(fd_names); + + if (s->socket_fd >= 0) { + + /* Pass the per-connection socket */ + + rfds = new(int, 1); + if (!rfds) + return -ENOMEM; + rfds[0] = s->socket_fd; + + rfd_names = strv_new("connection", NULL); + if (!rfd_names) + return -ENOMEM; + + rn_fds = 1; + } else { + Iterator i; + Unit *u; + + /* Pass all our configured sockets for singleton services */ + + SET_FOREACH(u, UNIT(s)->dependencies[UNIT_TRIGGERED_BY], i) { + _cleanup_free_ int *cfds = NULL; + Socket *sock; + int cn_fds; + + if (u->type != UNIT_SOCKET) + continue; + + sock = SOCKET(u); + + cn_fds = socket_collect_fds(sock, &cfds); + if (cn_fds < 0) + return cn_fds; + + if (cn_fds <= 0) + continue; + + if (!rfds) { + rfds = cfds; + rn_fds = cn_fds; + + cfds = NULL; + } else { + int *t; + + t = realloc(rfds, (rn_fds + cn_fds) * sizeof(int)); + if (!t) + return -ENOMEM; + + memcpy(t + rn_fds, cfds, cn_fds * sizeof(int)); + + rfds = t; + rn_fds += cn_fds; + } + + r = strv_extend_n(&rfd_names, socket_fdname(sock), cn_fds); + if (r < 0) + return r; + } + } + + if (s->n_fd_store > 0) { + ServiceFDStore *fs; + char **nl; + int *t; + + t = realloc(rfds, (rn_fds + s->n_fd_store) * sizeof(int)); + if (!t) + return -ENOMEM; + + rfds = t; + + nl = realloc(rfd_names, (rn_fds + s->n_fd_store + 1) * sizeof(char*)); + if (!nl) + return -ENOMEM; + + rfd_names = nl; + + LIST_FOREACH(fd_store, fs, s->fd_store) { + rfds[rn_fds] = fs->fd; + rfd_names[rn_fds] = strdup(strempty(fs->fdname)); + if (!rfd_names[rn_fds]) + return -ENOMEM; + + rn_fds++; + } + + rfd_names[rn_fds] = NULL; + } + + *fds = rfds; + *fd_names = rfd_names; + + rfds = NULL; + rfd_names = NULL; + + return rn_fds; +} + +static int service_spawn( + Service *s, + ExecCommand *c, + usec_t timeout, + bool pass_fds, + bool apply_permissions, + bool apply_chroot, + bool apply_tty_stdin, + bool is_control, + pid_t *_pid) { + + _cleanup_strv_free_ char **argv = NULL, **final_env = NULL, **our_env = NULL, **fd_names = NULL; + _cleanup_free_ char *bus_endpoint_path = NULL; + _cleanup_free_ int *fds = NULL; + unsigned n_fds = 0, n_env = 0; + const char *path; + pid_t pid; + + ExecParameters exec_params = { + .apply_permissions = apply_permissions, + .apply_chroot = apply_chroot, + .apply_tty_stdin = apply_tty_stdin, + .bus_endpoint_fd = -1, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, + }; + + int r; + + assert(s); + assert(c); + assert(_pid); + + (void) unit_realize_cgroup(UNIT(s)); + if (s->reset_cpu_usage) { + (void) unit_reset_cpu_usage(UNIT(s)); + s->reset_cpu_usage = false; + } + + r = unit_setup_exec_runtime(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 || + s->exec_context.std_error == EXEC_OUTPUT_SOCKET) { + + r = service_collect_fds(s, &fds, &fd_names); + if (r < 0) + return r; + + n_fds = r; + } + + r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), timeout)); + if (r < 0) + return r; + + r = unit_full_printf_strv(UNIT(s), c->argv, &argv); + if (r < 0) + return r; + + our_env = new0(char*, 6); + if (!our_env) + return -ENOMEM; + + if (is_control ? s->notify_access == NOTIFY_ALL : s->notify_access != NOTIFY_NONE) + if (asprintf(our_env + n_env++, "NOTIFY_SOCKET=%s", UNIT(s)->manager->notify_socket) < 0) + return -ENOMEM; + + if (s->main_pid > 0) + if (asprintf(our_env + n_env++, "MAINPID="PID_FMT, s->main_pid) < 0) + return -ENOMEM; + + if (UNIT(s)->manager->running_as != MANAGER_SYSTEM) + if (asprintf(our_env + n_env++, "MANAGERPID="PID_FMT, getpid()) < 0) + return -ENOMEM; + + if (s->socket_fd >= 0) { + union sockaddr_union sa; + socklen_t salen = sizeof(sa); + + r = getpeername(s->socket_fd, &sa.sa, &salen); + if (r < 0) + return -errno; + + if (IN_SET(sa.sa.sa_family, AF_INET, AF_INET6)) { + _cleanup_free_ char *addr = NULL; + char *t; + int port; + + r = sockaddr_pretty(&sa.sa, salen, true, false, &addr); + if (r < 0) + return r; + + t = strappend("REMOTE_ADDR=", addr); + if (!t) + return -ENOMEM; + our_env[n_env++] = t; + + port = sockaddr_port(&sa.sa); + if (port < 0) + return port; + + if (asprintf(&t, "REMOTE_PORT=%u", port) < 0) + return -ENOMEM; + our_env[n_env++] = t; + } + } + + final_env = strv_env_merge(2, UNIT(s)->manager->environment, our_env, NULL); + if (!final_env) + return -ENOMEM; + + if (is_control && UNIT(s)->cgroup_path) { + path = strjoina(UNIT(s)->cgroup_path, "/control"); + (void) cg_create(SYSTEMD_CGROUP_CONTROLLER, path); + } else + path = UNIT(s)->cgroup_path; + + if (s->exec_context.bus_endpoint) { + r = bus_kernel_create_endpoint(UNIT(s)->manager->running_as == MANAGER_SYSTEM ? "system" : "user", + UNIT(s)->id, &bus_endpoint_path); + if (r < 0) + return r; + + /* Pass the fd to the exec_params so that the child process can upload the policy. + * Keep a reference to the fd in the service, so the endpoint is kept alive as long + * as the service is running. */ + exec_params.bus_endpoint_fd = s->bus_endpoint_fd = r; + } + + exec_params.argv = argv; + exec_params.fds = fds; + exec_params.fd_names = fd_names; + exec_params.n_fds = n_fds; + exec_params.environment = final_env; + exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; + exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; + exec_params.cgroup_path = path; + exec_params.cgroup_delegate = s->cgroup_context.delegate; + exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(s)->manager); + exec_params.watchdog_usec = s->watchdog_usec; + exec_params.bus_endpoint_path = bus_endpoint_path; + exec_params.selinux_context_net = s->socket_fd_selinux_context_net; + if (s->type == SERVICE_IDLE) + exec_params.idle_pipe = UNIT(s)->manager->idle_pipe; + exec_params.stdin_fd = s->stdin_fd; + exec_params.stdout_fd = s->stdout_fd; + exec_params.stderr_fd = s->stderr_fd; + + r = exec_spawn(UNIT(s), + c, + &s->exec_context, + &exec_params, + s->exec_runtime, + &pid); + if (r < 0) + return r; + + r = unit_watch_pid(UNIT(s), pid); + if (r < 0) + /* FIXME: we need to do something here */ + return r; + + *_pid = pid; + + return 0; +} + +static int main_pid_good(Service *s) { + assert(s); + + /* Returns 0 if the pid is dead, 1 if it is good, -1 if we + * don't know */ + + /* If we know the pid file, then let's just check if it is + * still valid */ + if (s->main_pid_known) { + + /* If it's an alien child let's check if it is still + * alive ... */ + if (s->main_pid_alien && s->main_pid > 0) + return pid_is_alive(s->main_pid); + + /* .. otherwise assume we'll get a SIGCHLD for it, + * which we really should wait for to collect exit + * status and code */ + return s->main_pid > 0; + } + + /* We don't know the pid */ + return -EAGAIN; +} + +_pure_ static int control_pid_good(Service *s) { + assert(s); + + return s->control_pid > 0; +} + +static int cgroup_good(Service *s) { + int r; + + assert(s); + + if (!UNIT(s)->cgroup_path) + return 0; + + r = cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, UNIT(s)->cgroup_path); + if (r < 0) + return r; + + return !r; +} + +static bool service_shall_restart(Service *s) { + assert(s); + + /* Don't restart after manual stops */ + if (s->forbid_restart) + return false; + + /* Never restart if this is configured as special exception */ + if (exit_status_set_test(&s->restart_prevent_status, s->main_exec_status.code, s->main_exec_status.status)) + return false; + + /* Restart if the exit code/status are configured as restart triggers */ + if (exit_status_set_test(&s->restart_force_status, s->main_exec_status.code, s->main_exec_status.status)) + return true; + + switch (s->restart) { + + case SERVICE_RESTART_NO: + return false; + + case SERVICE_RESTART_ALWAYS: + return true; + + case SERVICE_RESTART_ON_SUCCESS: + return s->result == SERVICE_SUCCESS; + + case SERVICE_RESTART_ON_FAILURE: + return s->result != SERVICE_SUCCESS; + + case SERVICE_RESTART_ON_ABNORMAL: + return !IN_SET(s->result, SERVICE_SUCCESS, SERVICE_FAILURE_EXIT_CODE); + + case SERVICE_RESTART_ON_WATCHDOG: + return s->result == SERVICE_FAILURE_WATCHDOG; + + case SERVICE_RESTART_ON_ABORT: + return IN_SET(s->result, SERVICE_FAILURE_SIGNAL, SERVICE_FAILURE_CORE_DUMP); + + default: + assert_not_reached("unknown restart setting"); + } +} + +static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) { + int r; + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + service_set_state(s, s->result != SERVICE_SUCCESS ? SERVICE_FAILED : SERVICE_DEAD); + + if (s->result != SERVICE_SUCCESS) { + log_unit_warning(UNIT(s), "Failed with result '%s'.", service_result_to_string(s->result)); + failure_action(UNIT(s)->manager, s->failure_action, UNIT(s)->reboot_arg); + } + + if (allow_restart && service_shall_restart(s)) { + + r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->restart_usec)); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_AUTO_RESTART); + } + + /* The next restart might not be a manual stop, hence reset the flag indicating manual stops */ + s->forbid_restart = false; + + /* We want fresh tmpdirs in case service is started again immediately */ + exec_runtime_destroy(s->exec_runtime); + s->exec_runtime = exec_runtime_unref(s->exec_runtime); + + /* Also, remove the runtime directory in */ + exec_context_destroy_runtime_directory(&s->exec_context, manager_get_runtime_prefix(UNIT(s)->manager)); + + /* 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. */ + if (s->pid_file) + (void) unlink(s->pid_file); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run install restart timer: %m"); + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); +} + +static void service_enter_stop_post(Service *s, ServiceResult f) { + int r; + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + service_unwatch_control_pid(s); + unit_watch_all_pids(UNIT(s)); + + s->control_command = s->exec_command[SERVICE_EXEC_STOP_POST]; + if (s->control_command) { + s->control_command_id = SERVICE_EXEC_STOP_POST; + + r = service_spawn(s, + s->control_command, + s->timeout_stop_usec, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + true, + true, + &s->control_pid); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_STOP_POST); + } else + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop-post' task: %m"); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); +} + +static int state_to_kill_operation(ServiceState state) { + switch (state) { + + case SERVICE_STOP_SIGABRT: + return KILL_ABORT; + + case SERVICE_STOP_SIGTERM: + case SERVICE_FINAL_SIGTERM: + return KILL_TERMINATE; + + case SERVICE_STOP_SIGKILL: + case SERVICE_FINAL_SIGKILL: + return KILL_KILL; + + default: + return _KILL_OPERATION_INVALID; + } +} + +static void service_enter_signal(Service *s, ServiceState state, ServiceResult f) { + int r; + + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + unit_watch_all_pids(UNIT(s)); + + r = unit_kill_context( + UNIT(s), + &s->kill_context, + state_to_kill_operation(state), + s->main_pid, + s->control_pid, + s->main_pid_alien); + + if (r < 0) + goto fail; + + if (r > 0) { + r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); + if (r < 0) + goto fail; + + service_set_state(s, state); + } else if (IN_SET(state, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM) && s->kill_context.send_sigkill) + service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_SUCCESS); + else if (IN_SET(state, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL)) + service_enter_stop_post(s, SERVICE_SUCCESS); + else if (state == SERVICE_FINAL_SIGTERM && s->kill_context.send_sigkill) + service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_SUCCESS); + else + service_enter_dead(s, SERVICE_SUCCESS, true); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); + + if (IN_SET(state, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL)) + service_enter_stop_post(s, SERVICE_FAILURE_RESOURCES); + else + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); +} + +static void service_enter_stop_by_notify(Service *s) { + assert(s); + + unit_watch_all_pids(UNIT(s)); + + service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); + + /* The service told us it's stopping, so it's as if we SIGTERM'd it. */ + service_set_state(s, SERVICE_STOP_SIGTERM); +} + +static void service_enter_stop(Service *s, ServiceResult f) { + int r; + + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + service_unwatch_control_pid(s); + unit_watch_all_pids(UNIT(s)); + + s->control_command = s->exec_command[SERVICE_EXEC_STOP]; + if (s->control_command) { + s->control_command_id = SERVICE_EXEC_STOP; + + r = service_spawn(s, + s->control_command, + s->timeout_stop_usec, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + false, + true, + &s->control_pid); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_STOP); + } else + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop' task: %m"); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); +} + +static bool service_good(Service *s) { + int main_pid_ok; + assert(s); + + if (s->type == SERVICE_DBUS && !s->bus_name_good) + return false; + + main_pid_ok = main_pid_good(s); + if (main_pid_ok > 0) /* It's alive */ + return true; + if (main_pid_ok == 0) /* It's dead */ + return false; + + /* OK, we don't know anything about the main PID, maybe + * because there is none. Let's check the control group + * instead. */ + + return cgroup_good(s) != 0; +} + +static void service_enter_running(Service *s, ServiceResult f) { + assert(s); + + if (f != SERVICE_SUCCESS) + s->result = f; + + service_unwatch_control_pid(s); + + if (service_good(s)) { + + /* If there are any queued up sd_notify() + * notifications, process them now */ + if (s->notify_state == NOTIFY_RELOADING) + service_enter_reload_by_notify(s); + else if (s->notify_state == NOTIFY_STOPPING) + service_enter_stop_by_notify(s); + else { + service_set_state(s, SERVICE_RUNNING); + service_arm_timer(s, usec_add(UNIT(s)->active_enter_timestamp.monotonic, s->runtime_max_usec)); + } + + } else if (s->remain_after_exit) + service_set_state(s, SERVICE_EXITED); + else + service_enter_stop(s, SERVICE_SUCCESS); +} + +static void service_enter_start_post(Service *s) { + int r; + assert(s); + + service_unwatch_control_pid(s); + service_reset_watchdog(s); + + s->control_command = s->exec_command[SERVICE_EXEC_START_POST]; + if (s->control_command) { + s->control_command_id = SERVICE_EXEC_START_POST; + + r = service_spawn(s, + s->control_command, + s->timeout_start_usec, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + false, + true, + &s->control_pid); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_START_POST); + } else + service_enter_running(s, SERVICE_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-post' task: %m"); + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); +} + +static void service_kill_control_processes(Service *s) { + char *p; + + if (!UNIT(s)->cgroup_path) + return; + + p = strjoina(UNIT(s)->cgroup_path, "/control"); + cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, p, SIGKILL, true, true, true, NULL); +} + +static void service_enter_start(Service *s) { + ExecCommand *c; + usec_t timeout; + pid_t pid; + int r; + + assert(s); + + service_unwatch_control_pid(s); + service_unwatch_main_pid(s); + + /* We want to ensure that nobody leaks processes from + * START_PRE here, so let's go on a killing spree, People + * should not spawn long running processes from START_PRE. */ + service_kill_control_processes(s); + + if (s->type == SERVICE_FORKING) { + s->control_command_id = SERVICE_EXEC_START; + c = s->control_command = s->exec_command[SERVICE_EXEC_START]; + + s->main_command = NULL; + } else { + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; + s->control_command = NULL; + + c = s->main_command = s->exec_command[SERVICE_EXEC_START]; + } + + if (!c) { + assert(s->type == SERVICE_ONESHOT); + service_enter_start_post(s); + return; + } + + if (IN_SET(s->type, SERVICE_SIMPLE, SERVICE_IDLE)) + /* For simple + idle this is the main process. We don't apply any timeout here, but + * service_enter_running() will later apply the .runtime_max_usec timeout. */ + timeout = USEC_INFINITY; + else + timeout = s->timeout_start_usec; + + r = service_spawn(s, + c, + timeout, + true, + true, + true, + true, + false, + &pid); + if (r < 0) + goto fail; + + if (IN_SET(s->type, SERVICE_SIMPLE, SERVICE_IDLE)) { + /* For simple services we immediately start + * the START_POST binaries. */ + + service_set_main_pid(s, pid); + service_enter_start_post(s); + + } else if (s->type == SERVICE_FORKING) { + + /* For forking services we wait until the start + * process exited. */ + + s->control_pid = pid; + service_set_state(s, SERVICE_START); + + } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY)) { + + /* For oneshot services we wait until the start + * process exited, too, but it is our main process. */ + + /* For D-Bus services we know the main pid right away, + * but wait for the bus name to appear on the + * bus. Notify services are similar. */ + + service_set_main_pid(s, pid); + service_set_state(s, SERVICE_START); + } else + assert_not_reached("Unknown service type"); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'start' task: %m"); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); +} + +static void service_enter_start_pre(Service *s) { + int r; + + assert(s); + + service_unwatch_control_pid(s); + + s->control_command = s->exec_command[SERVICE_EXEC_START_PRE]; + if (s->control_command) { + /* Before we start anything, let's clear up what might + * be left from previous runs. */ + service_kill_control_processes(s); + + s->control_command_id = SERVICE_EXEC_START_PRE; + + r = service_spawn(s, + s->control_command, + s->timeout_start_usec, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + true, + true, + &s->control_pid); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_START_PRE); + } else + service_enter_start(s); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-pre' task: %m"); + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); +} + +static void service_enter_restart(Service *s) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(s); + + if (UNIT(s)->job && UNIT(s)->job->type == JOB_STOP) { + /* Don't restart things if we are going down anyway */ + log_unit_info(UNIT(s), "Stop job pending for unit, delaying automatic restart."); + + r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->restart_usec)); + if (r < 0) + goto fail; + + return; + } + + /* Any units that are bound to this service must also be + * restarted. We use JOB_RESTART (instead of the more obvious + * JOB_START) here so that those dependency jobs will be added + * as well. */ + r = manager_add_job(UNIT(s)->manager, JOB_RESTART, UNIT(s), JOB_FAIL, &error, NULL); + if (r < 0) + goto fail; + + /* Note that we stay in the SERVICE_AUTO_RESTART state here, + * it will be canceled as part of the service_stop() call that + * is executed as part of JOB_RESTART. */ + + log_unit_debug(UNIT(s), "Scheduled restart job."); + return; + +fail: + log_unit_warning(UNIT(s), "Failed to schedule restart job: %s", bus_error_message(&error, -r)); + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, false); +} + +static void service_enter_reload_by_notify(Service *s) { + assert(s); + + service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_start_usec)); + service_set_state(s, SERVICE_RELOAD); +} + +static void service_enter_reload(Service *s) { + int r; + + assert(s); + + service_unwatch_control_pid(s); + s->reload_result = SERVICE_SUCCESS; + + s->control_command = s->exec_command[SERVICE_EXEC_RELOAD]; + if (s->control_command) { + s->control_command_id = SERVICE_EXEC_RELOAD; + + r = service_spawn(s, + s->control_command, + s->timeout_start_usec, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + false, + true, + &s->control_pid); + if (r < 0) + goto fail; + + service_set_state(s, SERVICE_RELOAD); + } else + service_enter_running(s, SERVICE_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'reload' task: %m"); + s->reload_result = SERVICE_FAILURE_RESOURCES; + service_enter_running(s, SERVICE_SUCCESS); +} + +static void service_run_next_control(Service *s) { + usec_t timeout; + int r; + + assert(s); + assert(s->control_command); + assert(s->control_command->command_next); + + assert(s->control_command_id != SERVICE_EXEC_START); + + s->control_command = s->control_command->command_next; + service_unwatch_control_pid(s); + + if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) + timeout = s->timeout_start_usec; + else + timeout = s->timeout_stop_usec; + + r = service_spawn(s, + s->control_command, + timeout, + false, + !s->permissions_start_only, + !s->root_directory_start_only, + s->control_command_id == SERVICE_EXEC_START_PRE || + s->control_command_id == SERVICE_EXEC_STOP_POST, + true, + &s->control_pid); + if (r < 0) + goto fail; + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run next control task: %m"); + + if (s->state == SERVICE_START_PRE) + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); + else if (s->state == SERVICE_STOP) + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); + else if (s->state == SERVICE_STOP_POST) + service_enter_dead(s, SERVICE_FAILURE_RESOURCES, true); + else if (s->state == SERVICE_RELOAD) { + s->reload_result = SERVICE_FAILURE_RESOURCES; + service_enter_running(s, SERVICE_SUCCESS); + } else + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); +} + +static void service_run_next_main(Service *s) { + pid_t pid; + int r; + + assert(s); + assert(s->main_command); + assert(s->main_command->command_next); + assert(s->type == SERVICE_ONESHOT); + + s->main_command = s->main_command->command_next; + service_unwatch_main_pid(s); + + r = service_spawn(s, + s->main_command, + s->timeout_start_usec, + true, + true, + true, + true, + false, + &pid); + if (r < 0) + goto fail; + + service_set_main_pid(s, pid); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run next main task: %m"); + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); +} + +static int service_start(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (IN_SET(s->state, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) + return -EAGAIN; + + /* Already on it! */ + if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST)) + return 0; + + /* A service that will be restarted must be stopped first to + * trigger BindsTo and/or OnFailure dependencies. If a user + * does not want to wait for the holdoff time to elapse, the + * service should be manually restarted, not started. We + * simply return EAGAIN here, so that any start jobs stay + * queued, and assume that the auto restart timer will + * eventually trigger the restart. */ + if (s->state == SERVICE_AUTO_RESTART) + return -EAGAIN; + + assert(IN_SET(s->state, SERVICE_DEAD, SERVICE_FAILED)); + + s->result = SERVICE_SUCCESS; + s->reload_result = SERVICE_SUCCESS; + s->main_pid_known = false; + s->main_pid_alien = false; + s->forbid_restart = false; + s->reset_cpu_usage = true; + + s->status_text = mfree(s->status_text); + s->status_errno = 0; + + s->notify_state = NOTIFY_UNKNOWN; + + service_enter_start_pre(s); + return 1; +} + +static int service_stop(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + /* Don't create restart jobs from manual stops. */ + s->forbid_restart = true; + + /* Already on it */ + if (IN_SET(s->state, + SERVICE_STOP, SERVICE_STOP_SIGABRT, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) + return 0; + + /* A restart will be scheduled or is in progress. */ + if (s->state == SERVICE_AUTO_RESTART) { + service_set_state(s, SERVICE_DEAD); + return 0; + } + + /* If there's already something running we go directly into + * kill mode. */ + if (IN_SET(s->state, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD)) { + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); + return 0; + } + + assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED)); + + service_enter_stop(s, SERVICE_SUCCESS); + return 1; +} + +static int service_reload(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + assert(s->state == SERVICE_RUNNING || s->state == SERVICE_EXITED); + + service_enter_reload(s); + return 1; +} + +_pure_ static bool service_can_reload(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + return !!s->exec_command[SERVICE_EXEC_RELOAD]; +} + +static int service_serialize(Unit *u, FILE *f, FDSet *fds) { + Service *s = SERVICE(u); + ServiceFDStore *fs; + int r; + + assert(u); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", service_state_to_string(s->state)); + unit_serialize_item(u, f, "result", service_result_to_string(s->result)); + unit_serialize_item(u, f, "reload-result", service_result_to_string(s->reload_result)); + + if (s->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", PID_FMT, s->control_pid); + + if (s->main_pid_known && s->main_pid > 0) + unit_serialize_item_format(u, f, "main-pid", PID_FMT, s->main_pid); + + unit_serialize_item(u, f, "main-pid-known", yes_no(s->main_pid_known)); + unit_serialize_item(u, f, "bus-name-good", yes_no(s->bus_name_good)); + unit_serialize_item(u, f, "bus-name-owner", s->bus_name_owner); + + r = unit_serialize_item_escaped(u, f, "status-text", s->status_text); + if (r < 0) + return r; + + /* FIXME: There's a minor uncleanliness here: if there are + * multiple commands attached here, we will start from the + * first one again */ + if (s->control_command_id >= 0) + unit_serialize_item(u, f, "control-command", service_exec_command_to_string(s->control_command_id)); + + r = unit_serialize_item_fd(u, f, fds, "stdin-fd", s->stdin_fd); + if (r < 0) + return r; + r = unit_serialize_item_fd(u, f, fds, "stdout-fd", s->stdout_fd); + if (r < 0) + return r; + r = unit_serialize_item_fd(u, f, fds, "stderr-fd", s->stderr_fd); + if (r < 0) + return r; + + r = unit_serialize_item_fd(u, f, fds, "socket-fd", s->socket_fd); + if (r < 0) + return r; + r = unit_serialize_item_fd(u, f, fds, "endpoint-fd", s->bus_endpoint_fd); + if (r < 0) + return r; + + LIST_FOREACH(fd_store, fs, s->fd_store) { + _cleanup_free_ char *c = NULL; + int copy; + + copy = fdset_put_dup(fds, fs->fd); + if (copy < 0) + return copy; + + c = cescape(fs->fdname); + + unit_serialize_item_format(u, f, "fd-store-fd", "%i %s", copy, strempty(c)); + } + + if (s->main_exec_status.pid > 0) { + unit_serialize_item_format(u, f, "main-exec-status-pid", PID_FMT, s->main_exec_status.pid); + dual_timestamp_serialize(f, "main-exec-status-start", &s->main_exec_status.start_timestamp); + dual_timestamp_serialize(f, "main-exec-status-exit", &s->main_exec_status.exit_timestamp); + + if (dual_timestamp_is_set(&s->main_exec_status.exit_timestamp)) { + unit_serialize_item_format(u, f, "main-exec-status-code", "%i", s->main_exec_status.code); + unit_serialize_item_format(u, f, "main-exec-status-status", "%i", s->main_exec_status.status); + } + } + + if (dual_timestamp_is_set(&s->watchdog_timestamp)) + dual_timestamp_serialize(f, "watchdog-timestamp", &s->watchdog_timestamp); + + unit_serialize_item(u, f, "forbid-restart", yes_no(s->forbid_restart)); + + return 0; +} + +static int service_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Service *s = SERVICE(u); + int r; + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + ServiceState state; + + state = service_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + s->deserialized_state = state; + } else if (streq(key, "result")) { + ServiceResult f; + + f = service_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != SERVICE_SUCCESS) + s->result = f; + + } else if (streq(key, "reload-result")) { + ServiceResult f; + + f = service_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse reload result value: %s", value); + else if (f != SERVICE_SUCCESS) + s->reload_result = f; + + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse control-pid value: %s", value); + else + s->control_pid = pid; + } else if (streq(key, "main-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse main-pid value: %s", value); + else { + service_set_main_pid(s, pid); + unit_watch_pid(UNIT(s), pid); + } + } else if (streq(key, "main-pid-known")) { + int b; + + b = parse_boolean(value); + if (b < 0) + log_unit_debug(u, "Failed to parse main-pid-known value: %s", value); + else + s->main_pid_known = b; + } else if (streq(key, "bus-name-good")) { + int b; + + b = parse_boolean(value); + if (b < 0) + log_unit_debug(u, "Failed to parse bus-name-good value: %s", value); + else + s->bus_name_good = b; + } else if (streq(key, "bus-name-owner")) { + r = free_and_strdup(&s->bus_name_owner, value); + if (r < 0) + log_unit_error_errno(u, r, "Unable to deserialize current bus owner %s: %m", value); + } else if (streq(key, "status-text")) { + char *t; + + r = cunescape(value, 0, &t); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to unescape status text: %s", value); + else { + free(s->status_text); + s->status_text = t; + } + + } else if (streq(key, "control-command")) { + ServiceExecCommand id; + + id = service_exec_command_from_string(value); + if (id < 0) + log_unit_debug(u, "Failed to parse exec-command value: %s", value); + else { + s->control_command_id = id; + s->control_command = s->exec_command[id]; + } + } else if (streq(key, "socket-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse socket-fd value: %s", value); + else { + asynchronous_close(s->socket_fd); + s->socket_fd = fdset_remove(fds, fd); + } + } else if (streq(key, "endpoint-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse endpoint-fd value: %s", value); + else { + safe_close(s->bus_endpoint_fd); + s->bus_endpoint_fd = fdset_remove(fds, fd); + } + } else if (streq(key, "fd-store-fd")) { + const char *fdv; + size_t pf; + int fd; + + pf = strcspn(value, WHITESPACE); + fdv = strndupa(value, pf); + + if (safe_atoi(fdv, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse fd-store-fd value: %s", value); + else { + _cleanup_free_ char *t = NULL; + const char *fdn; + + fdn = value + pf; + fdn += strspn(fdn, WHITESPACE); + (void) cunescape(fdn, 0, &t); + + r = service_add_fd_store(s, fd, t); + if (r < 0) + log_unit_error_errno(u, r, "Failed to add fd to store: %m"); + else if (r > 0) + fdset_remove(fds, fd); + } + + } else if (streq(key, "main-exec-status-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse main-exec-status-pid value: %s", value); + else + s->main_exec_status.pid = pid; + } else if (streq(key, "main-exec-status-code")) { + int i; + + if (safe_atoi(value, &i) < 0) + log_unit_debug(u, "Failed to parse main-exec-status-code value: %s", value); + else + s->main_exec_status.code = i; + } else if (streq(key, "main-exec-status-status")) { + int i; + + if (safe_atoi(value, &i) < 0) + log_unit_debug(u, "Failed to parse main-exec-status-status value: %s", value); + else + s->main_exec_status.status = i; + } else if (streq(key, "main-exec-status-start")) + dual_timestamp_deserialize(value, &s->main_exec_status.start_timestamp); + else if (streq(key, "main-exec-status-exit")) + dual_timestamp_deserialize(value, &s->main_exec_status.exit_timestamp); + else if (streq(key, "watchdog-timestamp")) + dual_timestamp_deserialize(value, &s->watchdog_timestamp); + else if (streq(key, "forbid-restart")) { + int b; + + b = parse_boolean(value); + if (b < 0) + log_unit_debug(u, "Failed to parse forbid-restart value: %s", value); + else + s->forbid_restart = b; + } else if (streq(key, "stdin-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse stdin-fd value: %s", value); + else { + asynchronous_close(s->stdin_fd); + s->stdin_fd = fdset_remove(fds, fd); + s->exec_context.stdio_as_fds = true; + } + } else if (streq(key, "stdout-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse stdout-fd value: %s", value); + else { + asynchronous_close(s->stdout_fd); + s->stdout_fd = fdset_remove(fds, fd); + s->exec_context.stdio_as_fds = true; + } + } else if (streq(key, "stderr-fd")) { + int fd; + + if (safe_atoi(value, &fd) < 0 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse stderr-fd value: %s", value); + else { + asynchronous_close(s->stderr_fd); + s->stderr_fd = fdset_remove(fds, fd); + s->exec_context.stdio_as_fds = true; + } + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +_pure_ static UnitActiveState service_active_state(Unit *u) { + const UnitActiveState *table; + + assert(u); + + table = SERVICE(u)->type == SERVICE_IDLE ? state_translation_table_idle : state_translation_table; + + return table[SERVICE(u)->state]; +} + +static const char *service_sub_state_to_string(Unit *u) { + assert(u); + + return service_state_to_string(SERVICE(u)->state); +} + +static bool service_check_gc(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + /* Never clean up services that still have a process around, + * even if the service is formally dead. */ + if (cgroup_good(s) > 0 || + main_pid_good(s) > 0 || + control_pid_good(s) > 0) + return true; + + return false; +} + +static int service_retry_pid_file(Service *s) { + int r; + + assert(s->pid_file); + assert(s->state == SERVICE_START || s->state == SERVICE_START_POST); + + r = service_load_pid_file(s, false); + if (r < 0) + return r; + + service_unwatch_pid_file(s); + + service_enter_running(s, SERVICE_SUCCESS); + return 0; +} + +static int service_watch_pid_file(Service *s) { + int r; + + log_unit_debug(UNIT(s), "Setting watch for PID file %s", s->pid_file_pathspec->path); + + r = path_spec_watch(s->pid_file_pathspec, service_dispatch_io); + if (r < 0) + goto fail; + + /* the pidfile might have appeared just before we set the watch */ + log_unit_debug(UNIT(s), "Trying to read PID file %s in case it changed", s->pid_file_pathspec->path); + service_retry_pid_file(s); + + return 0; +fail: + log_unit_error_errno(UNIT(s), r, "Failed to set a watch for PID file %s: %m", s->pid_file_pathspec->path); + service_unwatch_pid_file(s); + return r; +} + +static int service_demand_pid_file(Service *s) { + PathSpec *ps; + + assert(s->pid_file); + assert(!s->pid_file_pathspec); + + ps = new0(PathSpec, 1); + if (!ps) + return -ENOMEM; + + ps->unit = UNIT(s); + ps->path = strdup(s->pid_file); + if (!ps->path) { + free(ps); + return -ENOMEM; + } + + path_kill_slashes(ps->path); + + /* PATH_CHANGED would not be enough. There are daemons (sendmail) that + * keep their PID file open all the time. */ + ps->type = PATH_MODIFIED; + ps->inotify_fd = -1; + + s->pid_file_pathspec = ps; + + return service_watch_pid_file(s); +} + +static int service_dispatch_io(sd_event_source *source, int fd, uint32_t events, void *userdata) { + PathSpec *p = userdata; + Service *s; + + assert(p); + + s = SERVICE(p->unit); + + assert(s); + assert(fd >= 0); + assert(s->state == SERVICE_START || s->state == SERVICE_START_POST); + assert(s->pid_file_pathspec); + assert(path_spec_owns_inotify_fd(s->pid_file_pathspec, fd)); + + log_unit_debug(UNIT(s), "inotify event"); + + if (path_spec_fd_event(p, events) < 0) + goto fail; + + if (service_retry_pid_file(s) == 0) + return 0; + + if (service_watch_pid_file(s) < 0) + goto fail; + + return 0; + +fail: + service_unwatch_pid_file(s); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_RESOURCES); + return 0; +} + +static void service_notify_cgroup_empty_event(Unit *u) { + Service *s = SERVICE(u); + + assert(u); + + log_unit_debug(u, "cgroup is empty"); + + switch (s->state) { + + /* Waiting for SIGCHLD is usually more interesting, + * because it includes return codes/signals. Which is + * why we ignore the cgroup events for most cases, + * except when we don't know pid which to expect the + * SIGCHLD for. */ + + case SERVICE_START: + case SERVICE_START_POST: + /* If we were hoping for the daemon to write its PID file, + * we can give up now. */ + if (s->pid_file_pathspec) { + log_unit_warning(u, "Daemon never wrote its PID file. Failing."); + + service_unwatch_pid_file(s); + if (s->state == SERVICE_START) + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); + else + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); + } + break; + + case SERVICE_RUNNING: + /* service_enter_running() will figure out what to do */ + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_STOP_SIGABRT: + case SERVICE_STOP_SIGTERM: + case SERVICE_STOP_SIGKILL: + + if (main_pid_good(s) <= 0 && !control_pid_good(s)) + service_enter_stop_post(s, SERVICE_SUCCESS); + + break; + + case SERVICE_STOP_POST: + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + if (main_pid_good(s) <= 0 && !control_pid_good(s)) + service_enter_dead(s, SERVICE_SUCCESS, true); + + break; + + default: + ; + } +} + +static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Service *s = SERVICE(u); + ServiceResult f; + + assert(s); + assert(pid >= 0); + + if (UNIT(s)->fragment_path ? is_clean_exit(code, status, &s->success_status) : + is_clean_exit_lsb(code, status, &s->success_status)) + f = SERVICE_SUCCESS; + else if (code == CLD_EXITED) + f = SERVICE_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = SERVICE_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = SERVICE_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown code"); + + if (s->main_pid == pid) { + /* Forking services may occasionally move to a new PID. + * As long as they update the PID file before exiting the old + * PID, they're fine. */ + if (service_load_pid_file(s, false) == 0) + return; + + s->main_pid = 0; + exec_status_exit(&s->main_exec_status, &s->exec_context, pid, code, status); + + if (s->main_command) { + /* If this is not a forking service than the + * main process got started and hence we copy + * the exit status so that it is recorded both + * as main and as control process exit + * status */ + + s->main_command->exec_status = s->main_exec_status; + + if (s->main_command->ignore) + f = SERVICE_SUCCESS; + } else if (s->exec_command[SERVICE_EXEC_START]) { + + /* If this is a forked process, then we should + * ignore the return value if this was + * configured for the starter process */ + + if (s->exec_command[SERVICE_EXEC_START]->ignore) + f = SERVICE_SUCCESS; + } + + log_struct(f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, + LOG_UNIT_ID(u), + LOG_UNIT_MESSAGE(u, "Main process exited, code=%s, status=%i/%s", + sigchld_code_to_string(code), status, + strna(code == CLD_EXITED + ? exit_status_to_string(status, EXIT_STATUS_FULL) + : signal_to_string(status))), + "EXIT_CODE=%s", sigchld_code_to_string(code), + "EXIT_STATUS=%i", status, + NULL); + + if (f != SERVICE_SUCCESS) + s->result = f; + + if (s->main_command && + s->main_command->command_next && + f == SERVICE_SUCCESS) { + + /* There is another command to * + * execute, so let's do that. */ + + log_unit_debug(u, "Running next main command for state %s.", service_state_to_string(s->state)); + service_run_next_main(s); + + } else { + + /* The service exited, so the service is officially + * gone. */ + s->main_command = NULL; + + switch (s->state) { + + case SERVICE_START_POST: + case SERVICE_RELOAD: + case SERVICE_STOP: + /* Need to wait until the operation is + * done */ + break; + + case SERVICE_START: + if (s->type == SERVICE_ONESHOT) { + /* This was our main goal, so let's go on */ + if (f == SERVICE_SUCCESS) + service_enter_start_post(s); + else + service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + break; + } + + /* Fall through */ + + case SERVICE_RUNNING: + service_enter_running(s, f); + break; + + case SERVICE_STOP_SIGABRT: + case SERVICE_STOP_SIGTERM: + case SERVICE_STOP_SIGKILL: + + if (!control_pid_good(s)) + service_enter_stop_post(s, f); + + /* If there is still a control process, wait for that first */ + break; + + case SERVICE_STOP_POST: + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + + if (!control_pid_good(s)) + service_enter_dead(s, f, true); + break; + + default: + assert_not_reached("Uh, main process died at wrong time."); + } + } + + } else if (s->control_pid == pid) { + s->control_pid = 0; + + if (s->control_command) { + exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); + + if (s->control_command->ignore) + f = SERVICE_SUCCESS; + } + + log_unit_full(u, f == SERVICE_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, + "Control process exited, code=%s status=%i", + sigchld_code_to_string(code), status); + + if (f != SERVICE_SUCCESS) + s->result = f; + + /* Immediately get rid of the cgroup, so that the + * kernel doesn't delay the cgroup empty messages for + * the service cgroup any longer than necessary */ + service_kill_control_processes(s); + + if (s->control_command && + s->control_command->command_next && + f == SERVICE_SUCCESS) { + + /* There is another command to * + * execute, so let's do that. */ + + log_unit_debug(u, "Running next control command for state %s.", service_state_to_string(s->state)); + service_run_next_control(s); + + } else { + /* No further commands for this step, so let's + * figure out what to do next */ + + s->control_command = NULL; + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; + + log_unit_debug(u, "Got final SIGCHLD for state %s.", service_state_to_string(s->state)); + + switch (s->state) { + + case SERVICE_START_PRE: + if (f == SERVICE_SUCCESS) + service_enter_start(s); + else + service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + break; + + case SERVICE_START: + if (s->type != SERVICE_FORKING) + /* Maybe spurious event due to a reload that changed the type? */ + break; + + if (f != SERVICE_SUCCESS) { + service_enter_signal(s, SERVICE_FINAL_SIGTERM, f); + break; + } + + if (s->pid_file) { + bool has_start_post; + int r; + + /* Let's try to load the pid file here if we can. + * The PID file might actually be created by a START_POST + * script. In that case don't worry if the loading fails. */ + + has_start_post = !!s->exec_command[SERVICE_EXEC_START_POST]; + r = service_load_pid_file(s, !has_start_post); + if (!has_start_post && r < 0) { + r = service_demand_pid_file(s); + if (r < 0 || !cgroup_good(s)) + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_RESOURCES); + break; + } + } else + (void) service_search_main_pid(s); + + service_enter_start_post(s); + break; + + case SERVICE_START_POST: + if (f != SERVICE_SUCCESS) { + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); + break; + } + + if (s->pid_file) { + int r; + + r = service_load_pid_file(s, true); + if (r < 0) { + r = service_demand_pid_file(s); + if (r < 0 || !cgroup_good(s)) + service_enter_stop(s, SERVICE_FAILURE_RESOURCES); + break; + } + } else + (void) service_search_main_pid(s); + + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_RELOAD: + if (f == SERVICE_SUCCESS) { + service_load_pid_file(s, true); + (void) service_search_main_pid(s); + } + + s->reload_result = f; + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_STOP: + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); + break; + + case SERVICE_STOP_SIGABRT: + case SERVICE_STOP_SIGTERM: + case SERVICE_STOP_SIGKILL: + if (main_pid_good(s) <= 0) + service_enter_stop_post(s, f); + + /* If there is still a service + * process around, wait until + * that one quit, too */ + break; + + case SERVICE_STOP_POST: + case SERVICE_FINAL_SIGTERM: + case SERVICE_FINAL_SIGKILL: + if (main_pid_good(s) <= 0) + service_enter_dead(s, f, true); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + } + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); + + /* We got one SIGCHLD for the service, let's watch all + * processes that are now running of the service, and watch + * that. Among the PIDs we then watch will be children + * reassigned to us, which hopefully allows us to identify + * when all children are gone */ + 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)) + service_notify_cgroup_empty_event(u); +} + +static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { + Service *s = SERVICE(userdata); + + assert(s); + assert(source == s->timer_event_source); + + switch (s->state) { + + case SERVICE_START_PRE: + case SERVICE_START: + log_unit_warning(UNIT(s), "%s operation timed out. Terminating.", s->state == SERVICE_START ? "Start" : "Start-pre"); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_START_POST: + log_unit_warning(UNIT(s), "Start-post operation timed out. Stopping."); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_RUNNING: + log_unit_warning(UNIT(s), "Service reached runtime time limit. Stopping."); + service_enter_stop(s, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_RELOAD: + log_unit_warning(UNIT(s), "Reload operation timed out. Killing reload process."); + service_kill_control_processes(s); + s->reload_result = SERVICE_FAILURE_TIMEOUT; + service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_STOP: + log_unit_warning(UNIT(s), "Stopping timed out. Terminating."); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_STOP_SIGABRT: + log_unit_warning(UNIT(s), "State 'stop-sigabrt' timed out. Terminating."); + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_STOP_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "State 'stop-sigterm' timed out. Killing."); + service_enter_signal(s, SERVICE_STOP_SIGKILL, SERVICE_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "State 'stop-sigterm' timed out. Skipping SIGKILL."); + service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT); + } + + break; + + case SERVICE_STOP_SIGKILL: + /* Uh, we sent a SIGKILL and it is still not gone? + * Must be something we cannot kill, so let's just be + * weirded out and continue */ + + log_unit_warning(UNIT(s), "Processes still around after SIGKILL. Ignoring."); + service_enter_stop_post(s, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_STOP_POST: + log_unit_warning(UNIT(s), "State 'stop-post' timed out. Terminating."); + service_enter_signal(s, SERVICE_FINAL_SIGTERM, SERVICE_FAILURE_TIMEOUT); + break; + + case SERVICE_FINAL_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "State 'stop-final-sigterm' timed out. Killing."); + service_enter_signal(s, SERVICE_FINAL_SIGKILL, SERVICE_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "State 'stop-final-sigterm' timed out. Skipping SIGKILL. Entering failed mode."); + service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, false); + } + + break; + + case SERVICE_FINAL_SIGKILL: + log_unit_warning(UNIT(s), "Processes still around after final SIGKILL. Entering failed mode."); + service_enter_dead(s, SERVICE_FAILURE_TIMEOUT, true); + break; + + case SERVICE_AUTO_RESTART: + log_unit_info(UNIT(s), + s->restart_usec > 0 ? + "Service hold-off time over, scheduling restart." : + "Service has no hold-off time, scheduling restart."); + service_enter_restart(s); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } + + return 0; +} + +static int service_dispatch_watchdog(sd_event_source *source, usec_t usec, void *userdata) { + Service *s = SERVICE(userdata); + char t[FORMAT_TIMESPAN_MAX]; + + assert(s); + assert(source == s->watchdog_event_source); + + log_unit_error(UNIT(s), "Watchdog timeout (limit %s)!", + format_timespan(t, sizeof(t), s->watchdog_usec, 1)); + + service_enter_signal(s, SERVICE_STOP_SIGABRT, SERVICE_FAILURE_WATCHDOG); + + return 0; +} + +static void service_notify_message(Unit *u, pid_t pid, char **tags, FDSet *fds) { + Service *s = SERVICE(u); + _cleanup_free_ char *cc = NULL; + bool notify_dbus = false; + const char *e; + + assert(u); + + cc = strv_join(tags, ", "); + + if (s->notify_access == NOTIFY_NONE) { + log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception is disabled.", pid); + return; + } else if (s->notify_access == NOTIFY_MAIN && pid != s->main_pid) { + if (s->main_pid != 0) + log_unit_warning(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID "PID_FMT, pid, s->main_pid); + else + log_unit_debug(u, "Got notification message from PID "PID_FMT", but reception only permitted for main PID which is currently not known", pid); + return; + } else + log_unit_debug(u, "Got notification message from PID "PID_FMT" (%s)", pid, isempty(cc) ? "n/a" : cc); + + /* Interpret MAINPID= */ + e = strv_find_startswith(tags, "MAINPID="); + if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) { + if (parse_pid(e, &pid) < 0) + log_unit_warning(u, "Failed to parse MAINPID= field in notification message: %s", e); + else { + service_set_main_pid(s, pid); + unit_watch_pid(UNIT(s), pid); + notify_dbus = true; + } + } + + /* Interpret RELOADING= */ + if (strv_find(tags, "RELOADING=1")) { + + s->notify_state = NOTIFY_RELOADING; + + if (s->state == SERVICE_RUNNING) + service_enter_reload_by_notify(s); + + notify_dbus = true; + } + + /* Interpret READY= */ + if (strv_find(tags, "READY=1")) { + + s->notify_state = NOTIFY_READY; + + /* Type=notify services inform us about completed + * initialization with READY=1 */ + if (s->type == SERVICE_NOTIFY && s->state == SERVICE_START) + service_enter_start_post(s); + + /* Sending READY=1 while we are reloading informs us + * that the reloading is complete */ + if (s->state == SERVICE_RELOAD && s->control_pid == 0) + service_enter_running(s, SERVICE_SUCCESS); + + notify_dbus = true; + } + + /* Interpret STOPPING= */ + if (strv_find(tags, "STOPPING=1")) { + + s->notify_state = NOTIFY_STOPPING; + + if (s->state == SERVICE_RUNNING) + service_enter_stop_by_notify(s); + + notify_dbus = true; + } + + /* Interpret STATUS= */ + e = strv_find_startswith(tags, "STATUS="); + if (e) { + _cleanup_free_ char *t = NULL; + + if (!isempty(e)) { + if (!utf8_is_valid(e)) + log_unit_warning(u, "Status message in notification message is not UTF-8 clean."); + else { + t = strdup(e); + if (!t) + log_oom(); + } + } + + if (!streq_ptr(s->status_text, t)) { + + free(s->status_text); + s->status_text = t; + t = NULL; + + notify_dbus = true; + } + } + + /* Interpret ERRNO= */ + e = strv_find_startswith(tags, "ERRNO="); + if (e) { + int status_errno; + + if (safe_atoi(e, &status_errno) < 0 || status_errno < 0) + log_unit_warning(u, "Failed to parse ERRNO= field in notification message: %s", e); + else { + if (s->status_errno != status_errno) { + s->status_errno = status_errno; + notify_dbus = true; + } + } + } + + /* Interpret WATCHDOG= */ + if (strv_find(tags, "WATCHDOG=1")) + service_reset_watchdog(s); + + if (strv_find(tags, "FDSTORE=1")) { + const char *name; + + name = strv_find_startswith(tags, "FDNAME="); + if (name && !fdname_is_valid(name)) { + log_unit_warning(u, "Passed FDNAME= name is invalid, ignoring."); + name = NULL; + } + + service_add_fd_store_set(s, fds, name); + } + + /* Notify clients about changed status or main pid */ + if (notify_dbus) + unit_add_to_dbus_queue(u); +} + +static int service_get_timeout(Unit *u, usec_t *timeout) { + Service *s = SERVICE(u); + uint64_t t; + int r; + + if (!s->timer_event_source) + return 0; + + r = sd_event_source_get_time(s->timer_event_source, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + return 0; + + *timeout = t; + return 1; +} + +static void service_bus_name_owner_change( + Unit *u, + const char *name, + const char *old_owner, + const char *new_owner) { + + Service *s = SERVICE(u); + int r; + + assert(s); + assert(name); + + assert(streq(s->bus_name, name)); + assert(old_owner || new_owner); + + if (old_owner && new_owner) + log_unit_debug(u, "D-Bus name %s changed owner from %s to %s", name, old_owner, new_owner); + else if (old_owner) + log_unit_debug(u, "D-Bus name %s no longer registered by %s", name, old_owner); + else + log_unit_debug(u, "D-Bus name %s now registered by %s", name, new_owner); + + s->bus_name_good = !!new_owner; + + /* Track the current owner, so we can reconstruct changes after a daemon reload */ + r = free_and_strdup(&s->bus_name_owner, new_owner); + if (r < 0) { + log_unit_error_errno(u, r, "Unable to set new bus name owner %s: %m", new_owner); + return; + } + + if (s->type == SERVICE_DBUS) { + + /* service_enter_running() will figure out what to + * do */ + if (s->state == SERVICE_RUNNING) + service_enter_running(s, SERVICE_SUCCESS); + else if (s->state == SERVICE_START && new_owner) + service_enter_start_post(s); + + } else if (new_owner && + s->main_pid <= 0 && + (s->state == SERVICE_START || + s->state == SERVICE_START_POST || + s->state == SERVICE_RUNNING || + s->state == SERVICE_RELOAD)) { + + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + pid_t pid; + + /* Try to acquire PID from bus service */ + + r = sd_bus_get_name_creds(u->manager->api_bus, name, SD_BUS_CREDS_PID, &creds); + if (r >= 0) + r = sd_bus_creds_get_pid(creds, &pid); + if (r >= 0) { + log_unit_debug(u, "D-Bus name %s is now owned by process %u", name, (unsigned) pid); + + service_set_main_pid(s, pid); + unit_watch_pid(UNIT(s), pid); + } + } +} + +int service_set_socket_fd(Service *s, int fd, Socket *sock, bool selinux_context_net) { + _cleanup_free_ char *peer = NULL; + int r; + + assert(s); + assert(fd >= 0); + + /* This is called by the socket code when instantiating a new + * service for a stream socket and the socket needs to be + * configured. */ + + if (UNIT(s)->load_state != UNIT_LOADED) + return -EINVAL; + + if (s->socket_fd >= 0) + return -EBUSY; + + if (s->state != SERVICE_DEAD) + return -EAGAIN; + + if (getpeername_pretty(fd, true, &peer) >= 0) { + + if (UNIT(s)->description) { + _cleanup_free_ char *a; + + a = strjoin(UNIT(s)->description, " (", peer, ")", NULL); + if (!a) + return -ENOMEM; + + r = unit_set_description(UNIT(s), a); + } else + r = unit_set_description(UNIT(s), peer); + + if (r < 0) + return r; + } + + s->socket_fd = fd; + s->socket_fd_selinux_context_net = selinux_context_net; + + unit_ref_set(&s->accept_socket, UNIT(sock)); + + return unit_add_two_dependencies(UNIT(sock), UNIT_BEFORE, UNIT_TRIGGERS, UNIT(s), false); +} + +static void service_reset_failed(Unit *u) { + Service *s = SERVICE(u); + + assert(s); + + if (s->state == SERVICE_FAILED) + service_set_state(s, SERVICE_DEAD); + + s->result = SERVICE_SUCCESS; + s->reload_result = SERVICE_SUCCESS; +} + +static int service_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + Service *s = SERVICE(u); + + return unit_kill_common(u, who, signo, s->main_pid, s->control_pid, error); +} + +static const char* const service_restart_table[_SERVICE_RESTART_MAX] = { + [SERVICE_RESTART_NO] = "no", + [SERVICE_RESTART_ON_SUCCESS] = "on-success", + [SERVICE_RESTART_ON_FAILURE] = "on-failure", + [SERVICE_RESTART_ON_ABNORMAL] = "on-abnormal", + [SERVICE_RESTART_ON_WATCHDOG] = "on-watchdog", + [SERVICE_RESTART_ON_ABORT] = "on-abort", + [SERVICE_RESTART_ALWAYS] = "always", +}; + +DEFINE_STRING_TABLE_LOOKUP(service_restart, ServiceRestart); + +static const char* const service_type_table[_SERVICE_TYPE_MAX] = { + [SERVICE_SIMPLE] = "simple", + [SERVICE_FORKING] = "forking", + [SERVICE_ONESHOT] = "oneshot", + [SERVICE_DBUS] = "dbus", + [SERVICE_NOTIFY] = "notify", + [SERVICE_IDLE] = "idle" +}; + +DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType); + +static const char* const service_exec_command_table[_SERVICE_EXEC_COMMAND_MAX] = { + [SERVICE_EXEC_START_PRE] = "ExecStartPre", + [SERVICE_EXEC_START] = "ExecStart", + [SERVICE_EXEC_START_POST] = "ExecStartPost", + [SERVICE_EXEC_RELOAD] = "ExecReload", + [SERVICE_EXEC_STOP] = "ExecStop", + [SERVICE_EXEC_STOP_POST] = "ExecStopPost", +}; + +DEFINE_STRING_TABLE_LOOKUP(service_exec_command, ServiceExecCommand); + +static const char* const notify_access_table[_NOTIFY_ACCESS_MAX] = { + [NOTIFY_NONE] = "none", + [NOTIFY_MAIN] = "main", + [NOTIFY_ALL] = "all" +}; + +DEFINE_STRING_TABLE_LOOKUP(notify_access, NotifyAccess); + +static const char* const notify_state_table[_NOTIFY_STATE_MAX] = { + [NOTIFY_UNKNOWN] = "unknown", + [NOTIFY_READY] = "ready", + [NOTIFY_RELOADING] = "reloading", + [NOTIFY_STOPPING] = "stopping", +}; + +DEFINE_STRING_TABLE_LOOKUP(notify_state, NotifyState); + +static const char* const service_result_table[_SERVICE_RESULT_MAX] = { + [SERVICE_SUCCESS] = "success", + [SERVICE_FAILURE_RESOURCES] = "resources", + [SERVICE_FAILURE_TIMEOUT] = "timeout", + [SERVICE_FAILURE_EXIT_CODE] = "exit-code", + [SERVICE_FAILURE_SIGNAL] = "signal", + [SERVICE_FAILURE_CORE_DUMP] = "core-dump", + [SERVICE_FAILURE_WATCHDOG] = "watchdog", +}; + +DEFINE_STRING_TABLE_LOOKUP(service_result, ServiceResult); + +const UnitVTable service_vtable = { + .object_size = sizeof(Service), + .exec_context_offset = offsetof(Service, exec_context), + .cgroup_context_offset = offsetof(Service, cgroup_context), + .kill_context_offset = offsetof(Service, kill_context), + .exec_runtime_offset = offsetof(Service, exec_runtime), + + .sections = + "Unit\0" + "Service\0" + "Install\0", + .private_section = "Service", + + .init = service_init, + .done = service_done, + .load = service_load, + .release_resources = service_release_resources, + + .coldplug = service_coldplug, + + .dump = service_dump, + + .start = service_start, + .stop = service_stop, + .reload = service_reload, + + .can_reload = service_can_reload, + + .kill = service_kill, + + .serialize = service_serialize, + .deserialize_item = service_deserialize_item, + + .active_state = service_active_state, + .sub_state_to_string = service_sub_state_to_string, + + .check_gc = service_check_gc, + + .sigchld_event = service_sigchld_event, + + .reset_failed = service_reset_failed, + + .notify_cgroup_empty = service_notify_cgroup_empty_event, + .notify_message = service_notify_message, + + .bus_name_owner_change = service_bus_name_owner_change, + + .bus_vtable = bus_service_vtable, + .bus_set_property = bus_service_set_property, + .bus_commit_properties = bus_service_commit_properties, + + .get_timeout = service_get_timeout, + .can_transient = true, + + .status_message_formats = { + .starting_stopping = { + [0] = "Starting %s...", + [1] = "Stopping %s...", + }, + .finished_start_job = { + [JOB_DONE] = "Started %s.", + [JOB_FAILED] = "Failed to start %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Stopped %s.", + [JOB_FAILED] = "Stopped (with error) %s.", + }, + }, +}; diff --git a/src/libcore/service.h b/src/libcore/service.h new file mode 100644 index 0000000000..d342e000bb --- /dev/null +++ b/src/libcore/service.h @@ -0,0 +1,220 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Service Service; +typedef struct ServiceFDStore ServiceFDStore; + +#include "exit-status.h" +#include "kill.h" +#include "path.h" +#include "ratelimit.h" + +typedef enum ServiceRestart { + SERVICE_RESTART_NO, + SERVICE_RESTART_ON_SUCCESS, + SERVICE_RESTART_ON_FAILURE, + SERVICE_RESTART_ON_ABNORMAL, + SERVICE_RESTART_ON_WATCHDOG, + SERVICE_RESTART_ON_ABORT, + SERVICE_RESTART_ALWAYS, + _SERVICE_RESTART_MAX, + _SERVICE_RESTART_INVALID = -1 +} ServiceRestart; + +typedef enum ServiceType { + SERVICE_SIMPLE, /* we fork and go on right-away (i.e. modern socket activated daemons) */ + SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */ + SERVICE_ONESHOT, /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */ + SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */ + SERVICE_NOTIFY, /* we fork and wait until a daemon sends us a ready message with sd_notify() */ + SERVICE_IDLE, /* much like simple, but delay exec() until all jobs are dispatched. */ + _SERVICE_TYPE_MAX, + _SERVICE_TYPE_INVALID = -1 +} ServiceType; + +typedef enum ServiceExecCommand { + SERVICE_EXEC_START_PRE, + SERVICE_EXEC_START, + SERVICE_EXEC_START_POST, + SERVICE_EXEC_RELOAD, + SERVICE_EXEC_STOP, + SERVICE_EXEC_STOP_POST, + _SERVICE_EXEC_COMMAND_MAX, + _SERVICE_EXEC_COMMAND_INVALID = -1 +} ServiceExecCommand; + +typedef enum NotifyAccess { + NOTIFY_NONE, + NOTIFY_ALL, + NOTIFY_MAIN, + _NOTIFY_ACCESS_MAX, + _NOTIFY_ACCESS_INVALID = -1 +} NotifyAccess; + +typedef enum NotifyState { + NOTIFY_UNKNOWN, + NOTIFY_READY, + NOTIFY_RELOADING, + NOTIFY_STOPPING, + _NOTIFY_STATE_MAX, + _NOTIFY_STATE_INVALID = -1 +} NotifyState; + +typedef enum ServiceResult { + SERVICE_SUCCESS, + SERVICE_FAILURE_RESOURCES, + SERVICE_FAILURE_TIMEOUT, + SERVICE_FAILURE_EXIT_CODE, + SERVICE_FAILURE_SIGNAL, + SERVICE_FAILURE_CORE_DUMP, + SERVICE_FAILURE_WATCHDOG, + _SERVICE_RESULT_MAX, + _SERVICE_RESULT_INVALID = -1 +} ServiceResult; + +struct ServiceFDStore { + Service *service; + + int fd; + char *fdname; + sd_event_source *event_source; + + LIST_FIELDS(ServiceFDStore, fd_store); +}; + +struct Service { + Unit meta; + + ServiceType type; + ServiceRestart restart; + ExitStatusSet restart_prevent_status; + ExitStatusSet restart_force_status; + ExitStatusSet success_status; + + /* If set we'll read the main daemon PID from this file */ + char *pid_file; + + usec_t restart_usec; + usec_t timeout_start_usec; + usec_t timeout_stop_usec; + usec_t runtime_max_usec; + + dual_timestamp watchdog_timestamp; + usec_t watchdog_usec; + sd_event_source *watchdog_event_source; + + ExecCommand* exec_command[_SERVICE_EXEC_COMMAND_MAX]; + + ExecContext exec_context; + KillContext kill_context; + CGroupContext cgroup_context; + + ServiceState state, deserialized_state; + + /* The exit status of the real main process */ + ExecStatus main_exec_status; + + /* The currently executed control process */ + ExecCommand *control_command; + + /* The currently executed main process, which may be NULL if + * the main process got started via forking mode and not by + * us */ + ExecCommand *main_command; + + /* The ID of the control command currently being executed */ + ServiceExecCommand control_command_id; + + /* Runtime data of the execution context */ + ExecRuntime *exec_runtime; + + pid_t main_pid, control_pid; + int socket_fd; + bool socket_fd_selinux_context_net; + + int bus_endpoint_fd; + + bool permissions_start_only; + bool root_directory_start_only; + bool remain_after_exit; + bool guess_main_pid; + + /* If we shut down, remember why */ + ServiceResult result; + ServiceResult reload_result; + + bool main_pid_known:1; + bool main_pid_alien:1; + bool bus_name_good:1; + bool forbid_restart:1; + bool start_timeout_defined:1; + + bool reset_cpu_usage:1; + + char *bus_name; + char *bus_name_owner; /* unique name of the current owner */ + + char *status_text; + int status_errno; + + FailureAction failure_action; + + UnitRef accept_socket; + + sd_event_source *timer_event_source; + PathSpec *pid_file_pathspec; + + NotifyAccess notify_access; + NotifyState notify_state; + + ServiceFDStore *fd_store; + unsigned n_fd_store; + unsigned n_fd_store_max; + + char *usb_function_descriptors; + char *usb_function_strings; + + int stdin_fd; + int stdout_fd; + int stderr_fd; +}; + +extern const UnitVTable service_vtable; + +int service_set_socket_fd(Service *s, int fd, struct Socket *socket, bool selinux_context_net); + +const char* service_restart_to_string(ServiceRestart i) _const_; +ServiceRestart service_restart_from_string(const char *s) _pure_; + +const char* service_type_to_string(ServiceType i) _const_; +ServiceType service_type_from_string(const char *s) _pure_; + +const char* service_exec_command_to_string(ServiceExecCommand i) _const_; +ServiceExecCommand service_exec_command_from_string(const char *s) _pure_; + +const char* notify_access_to_string(NotifyAccess i) _const_; +NotifyAccess notify_access_from_string(const char *s) _pure_; + +const char* notify_state_to_string(NotifyState i) _const_; +NotifyState notify_state_from_string(const char *s) _pure_; + +const char* service_result_to_string(ServiceResult i) _const_; +ServiceResult service_result_from_string(const char *s) _pure_; diff --git a/src/libcore/show-status.c b/src/libcore/show-status.c new file mode 100644 index 0000000000..59ebdc7219 --- /dev/null +++ b/src/libcore/show-status.c @@ -0,0 +1,124 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "alloc-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "parse-util.h" +#include "show-status.h" +#include "string-util.h" +#include "terminal-util.h" +#include "util.h" + +int parse_show_status(const char *v, ShowStatus *ret) { + int r; + + assert(v); + assert(ret); + + if (streq(v, "auto")) { + *ret = SHOW_STATUS_AUTO; + return 0; + } + + r = parse_boolean(v); + if (r < 0) + return r; + + *ret = r ? SHOW_STATUS_YES : SHOW_STATUS_NO; + return 0; +} + +int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) { + static const char status_indent[] = " "; /* "[" STATUS "] " */ + _cleanup_free_ char *s = NULL; + _cleanup_close_ int fd = -1; + struct iovec iovec[6] = {}; + int n = 0; + static bool prev_ephemeral; + + assert(format); + + /* This is independent of logging, as status messages are + * optional and go exclusively to the console. */ + + if (vasprintf(&s, format, ap) < 0) + return log_oom(); + + fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return fd; + + if (ellipse) { + char *e; + size_t emax, sl; + int c; + + c = fd_columns(fd); + if (c <= 0) + c = 80; + + sl = status ? sizeof(status_indent)-1 : 0; + + emax = c - sl - 1; + if (emax < 3) + emax = 3; + + e = ellipsize(s, emax, 50); + if (e) { + free(s); + s = e; + } + } + + if (prev_ephemeral) + IOVEC_SET_STRING(iovec[n++], "\r" ANSI_ERASE_TO_END_OF_LINE); + prev_ephemeral = ephemeral; + + if (status) { + if (!isempty(status)) { + IOVEC_SET_STRING(iovec[n++], "["); + IOVEC_SET_STRING(iovec[n++], status); + IOVEC_SET_STRING(iovec[n++], "] "); + } else + IOVEC_SET_STRING(iovec[n++], status_indent); + } + + IOVEC_SET_STRING(iovec[n++], s); + if (!ephemeral) + IOVEC_SET_STRING(iovec[n++], "\n"); + + if (writev(fd, iovec, n) < 0) + return -errno; + + return 0; +} + +int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) { + va_list ap; + int r; + + assert(format); + + va_start(ap, format); + r = status_vprintf(status, ellipse, ephemeral, format, ap); + va_end(ap); + + return r; +} diff --git a/src/libcore/show-status.h b/src/libcore/show-status.h new file mode 100644 index 0000000000..9a29e72645 --- /dev/null +++ b/src/libcore/show-status.h @@ -0,0 +1,39 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "macro.h" + +/* Manager status */ + +typedef enum ShowStatus { + _SHOW_STATUS_UNSET = -2, + SHOW_STATUS_AUTO = -1, + SHOW_STATUS_NO = 0, + SHOW_STATUS_YES = 1, + SHOW_STATUS_TEMPORARY = 2, +} ShowStatus; + +int parse_show_status(const char *v, ShowStatus *ret); + +int status_vprintf(const char *status, bool ellipse, bool ephemeral, const char *format, va_list ap) _printf_(4,0); +int status_printf(const char *status, bool ellipse, bool ephemeral, const char *format, ...) _printf_(4,5); diff --git a/src/libcore/shutdown.c b/src/libcore/shutdown.c new file mode 100644 index 0000000000..6296b4c94a --- /dev/null +++ b/src/libcore/shutdown.c @@ -0,0 +1,435 @@ +/*** + This file is part of systemd. + + Copyright 2010 ProFUSION embedded systems + + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "def.h" +#include "fileio.h" +#include "killall.h" +#include "log.h" +#include "missing.h" +#include "parse-util.h" +#include "process-util.h" +#include "string-util.h" +#include "switch-root.h" +#include "terminal-util.h" +#include "umount.h" +#include "util.h" +#include "virt.h" +#include "watchdog.h" + +#define FINALIZE_ATTEMPTS 50 + +static char* arg_verb; +static uint8_t arg_exit_code; + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_LOG_LEVEL = 0x100, + ARG_LOG_TARGET, + ARG_LOG_COLOR, + ARG_LOG_LOCATION, + ARG_EXIT_CODE, + }; + + static const struct option options[] = { + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-target", required_argument, NULL, ARG_LOG_TARGET }, + { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, + { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, + { "exit-code", required_argument, NULL, ARG_EXIT_CODE }, + {} + }; + + int c, r; + + assert(argc >= 1); + assert(argv); + + /* "-" prevents getopt from permuting argv[] and moving the verb away + * from argv[1]. Our interface to initrd promises it'll be there. */ + while ((c = getopt_long(argc, argv, "-", options, NULL)) >= 0) + switch (c) { + + case ARG_LOG_LEVEL: + r = log_set_max_level_from_string(optarg); + if (r < 0) + log_error("Failed to parse log level %s, ignoring.", optarg); + + break; + + case ARG_LOG_TARGET: + r = log_set_target_from_string(optarg); + if (r < 0) + log_error("Failed to parse log target %s, ignoring", optarg); + + break; + + case ARG_LOG_COLOR: + + if (optarg) { + r = log_show_color_from_string(optarg); + if (r < 0) + log_error("Failed to parse log color setting %s, ignoring", optarg); + } else + log_show_color(true); + + break; + + case ARG_LOG_LOCATION: + if (optarg) { + r = log_show_location_from_string(optarg); + if (r < 0) + log_error("Failed to parse log location setting %s, ignoring", optarg); + } else + log_show_location(true); + + break; + + case ARG_EXIT_CODE: + r = safe_atou8(optarg, &arg_exit_code); + if (r < 0) + log_error("Failed to parse exit code %s, ignoring", optarg); + + break; + + case '\001': + if (!arg_verb) + arg_verb = optarg; + else + log_error("Excess arguments, ignoring"); + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option code."); + } + + if (!arg_verb) { + log_error("Verb argument missing."); + return -EINVAL; + } + + return 0; +} + +static int switch_root_initramfs(void) { + if (mount("/run/initramfs", "/run/initramfs", NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Failed to mount bind /run/initramfs on /run/initramfs: %m"); + + if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) + return log_error_errno(errno, "Failed to make /run/initramfs private mount: %m"); + + /* switch_root with MS_BIND, because there might still be processes lurking around, which have open file descriptors. + * /run/initramfs/shutdown will take care of these. + * Also do not detach the old root, because /run/initramfs/shutdown needs to access it. + */ + 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; + _cleanup_free_ char *cgroup = NULL; + char *arguments[3]; + unsigned retries; + int cmd, r; + static const char* const dirs[] = {SYSTEM_SHUTDOWN_PATH, NULL}; + + log_parse_environment(); + r = parse_argv(argc, argv); + if (r < 0) + goto error; + + /* journald will die if not gone yet. The log target defaults + * to console, but may have been changed by command line options. */ + + log_close_console(); /* force reopen of /dev/console */ + log_open(); + + umask(0022); + + if (getpid() != 1) { + log_error("Not executed by init (PID 1)."); + r = -EPERM; + goto error; + } + + if (streq(arg_verb, "reboot")) + cmd = RB_AUTOBOOT; + else if (streq(arg_verb, "poweroff")) + cmd = RB_POWER_OFF; + else if (streq(arg_verb, "halt")) + cmd = RB_HALT_SYSTEM; + else if (streq(arg_verb, "kexec")) + cmd = LINUX_REBOOT_CMD_KEXEC; + else if (streq(arg_verb, "exit")) + cmd = 0; /* ignored, just checking that arg_verb is valid */ + else { + r = -EINVAL; + log_error("Unknown action '%s'.", arg_verb); + goto error; + } + + cg_get_root_path(&cgroup); + + use_watchdog = !!getenv("WATCHDOG_USEC"); + + /* lock us into memory */ + mlockall(MCL_CURRENT|MCL_FUTURE); + + 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; + need_dm_detach = !in_container; + + /* Unmount all mountpoints, swaps, and loopback devices */ + for (retries = 0; retries < FINALIZE_ATTEMPTS; retries++) { + bool changed = false; + + if (use_watchdog) + watchdog_ping(); + + /* Let's trim the cgroup tree on each iteration so + that we leave an empty cgroup tree around, so that + container managers get a nice notify event when we + are down */ + if (cgroup) + cg_trim(SYSTEMD_CGROUP_CONTROLLER, cgroup, false); + + if (need_umount) { + log_info("Unmounting file systems."); + r = umount_all(&changed); + if (r == 0) { + need_umount = false; + log_info("All filesystems unmounted."); + } else if (r > 0) + log_info("Not all file systems unmounted, %d left.", r); + else + log_error_errno(r, "Failed to unmount file systems: %m"); + } + + if (need_swapoff) { + log_info("Deactivating swaps."); + r = swapoff_all(&changed); + if (r == 0) { + need_swapoff = false; + log_info("All swaps deactivated."); + } else if (r > 0) + log_info("Not all swaps deactivated, %d left.", r); + else + log_error_errno(r, "Failed to deactivate swaps: %m"); + } + + if (need_loop_detach) { + log_info("Detaching loop devices."); + r = loopback_detach_all(&changed); + if (r == 0) { + need_loop_detach = false; + log_info("All loop devices detached."); + } else if (r > 0) + log_info("Not all loop devices detached, %d left.", r); + else + log_error_errno(r, "Failed to detach loop devices: %m"); + } + + if (need_dm_detach) { + log_info("Detaching DM devices."); + r = dm_detach_all(&changed); + if (r == 0) { + need_dm_detach = false; + log_info("All DM devices detached."); + } else if (r > 0) + log_info("Not all DM devices detached, %d left.", r); + else + log_error_errno(r, "Failed to detach DM devices: %m"); + } + + if (!need_umount && !need_swapoff && !need_loop_detach && !need_dm_detach) { + if (retries > 0) + log_info("All filesystems, swaps, loop devices, DM devices detached."); + /* Yay, done */ + goto initrd_jump; + } + + /* If in this iteration we didn't manage to + * unmount/deactivate anything, we simply give up */ + if (!changed) { + log_info("Cannot finalize remaining%s%s%s%s continuing.", + need_umount ? " file systems," : "", + need_swapoff ? " swap devices," : "", + need_loop_detach ? " loop devices," : "", + need_dm_detach ? " DM devices," : ""); + goto initrd_jump; + } + + log_debug("After %u retries, couldn't finalize remaining %s%s%s%s trying again.", + retries + 1, + need_umount ? " file systems," : "", + need_swapoff ? " swap devices," : "", + need_loop_detach ? " loop devices," : "", + need_dm_detach ? " DM devices," : ""); + } + + log_error("Too many iterations, giving up."); + + initrd_jump: + + arguments[0] = NULL; + arguments[1] = arg_verb; + arguments[2] = NULL; + execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments); + + if (!in_container && !in_initrd() && + access("/run/initramfs/shutdown", X_OK) == 0) { + r = switch_root_initramfs(); + if (r >= 0) { + argv[0] = (char*) "/shutdown"; + + setsid(); + make_console_stdio(); + + log_info("Successfully changed into root pivot.\n" + "Returning to initrd..."); + + execv("/shutdown", argv); + log_error_errno(errno, "Failed to execute shutdown binary: %m"); + } else + log_error_errno(r, "Failed to switch root to \"/run/initramfs\": %m"); + + } + + if (need_umount || need_swapoff || need_loop_detach || need_dm_detach) + log_error("Failed to finalize %s%s%s%s ignoring", + need_umount ? " file systems," : "", + need_swapoff ? " swap devices," : "", + 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. */ + if (!in_container) + sync(); + + if (streq(arg_verb, "exit")) { + if (in_container) + exit(arg_exit_code); + else { + /* We cannot exit() on the host, fallback on another + * method. */ + cmd = RB_POWER_OFF; + } + } + + switch (cmd) { + + case LINUX_REBOOT_CMD_KEXEC: + + if (!in_container) { + /* We cheat and exec kexec to avoid doing all its work */ + pid_t pid; + + log_info("Rebooting with kexec."); + + pid = fork(); + if (pid < 0) + log_error_errno(errno, "Failed to fork: %m"); + else if (pid == 0) { + + const char * const args[] = { + KEXEC, "-e", NULL + }; + + /* Child */ + + execv(args[0], (char * const *) args); + _exit(EXIT_FAILURE); + } else + wait_for_terminate_and_warn("kexec", pid, true); + } + + cmd = RB_AUTOBOOT; + /* Fall through */ + + case RB_AUTOBOOT: + + if (!in_container) { + _cleanup_free_ char *param = NULL; + + if (read_one_line_file(REBOOT_PARAM_FILE, ¶m) >= 0) { + log_info("Rebooting with argument '%s'.", param); + syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, param); + } + } + + log_info("Rebooting."); + break; + + case RB_POWER_OFF: + log_info("Powering off."); + break; + + case RB_HALT_SYSTEM: + log_info("Halting system."); + break; + + default: + assert_not_reached("Unknown magic"); + } + + reboot(cmd); + if (errno == EPERM && in_container) { + /* If we are in a container, and we lacked + * CAP_SYS_BOOT just exit, this will kill our + * container for good. */ + log_info("Exiting container."); + exit(0); + } + + r = log_error_errno(errno, "Failed to invoke reboot(): %m"); + + error: + log_emergency_errno(r, "Critical error while doing system shutdown: %m"); + freeze(); +} diff --git a/src/libcore/slice.c b/src/libcore/slice.c new file mode 100644 index 0000000000..d65364c6f4 --- /dev/null +++ b/src/libcore/slice.c @@ -0,0 +1,339 @@ +/*** + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "dbus-slice.h" +#include "log.h" +#include "slice.h" +#include "special.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit.h" + +static const UnitActiveState state_translation_table[_SLICE_STATE_MAX] = { + [SLICE_DEAD] = UNIT_INACTIVE, + [SLICE_ACTIVE] = UNIT_ACTIVE +}; + +static void slice_set_state(Slice *t, SliceState state) { + SliceState old_state; + assert(t); + + old_state = t->state; + t->state = state; + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(t)->id, + slice_state_to_string(old_state), + slice_state_to_string(state)); + + unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); +} + +static int slice_add_parent_slice(Slice *s) { + char *a, *dash; + Unit *parent; + int r; + + assert(s); + + if (UNIT_ISSET(UNIT(s)->slice)) + return 0; + + if (unit_has_name(UNIT(s), SPECIAL_ROOT_SLICE)) + return 0; + + a = strdupa(UNIT(s)->id); + dash = strrchr(a, '-'); + if (dash) + strcpy(dash, ".slice"); + else + a = (char*) SPECIAL_ROOT_SLICE; + + r = manager_load_unit(UNIT(s)->manager, a, NULL, NULL, &parent); + if (r < 0) + return r; + + unit_ref_set(&UNIT(s)->slice, parent); + return 0; +} + +static int slice_add_default_dependencies(Slice *s) { + int r; + + assert(s); + + if (!UNIT(s)->default_dependencies) + return 0; + + /* Make sure slices are unloaded on shutdown */ + r = unit_add_two_dependencies_by_name( + UNIT(s), + UNIT_BEFORE, UNIT_CONFLICTS, + SPECIAL_SHUTDOWN_TARGET, NULL, true); + if (r < 0) + return r; + + return 0; +} + +static int slice_verify(Slice *s) { + _cleanup_free_ char *parent = NULL; + int r; + + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (!slice_name_is_valid(UNIT(s)->id)) { + log_unit_error(UNIT(s), "Slice name %s is not valid. Refusing.", UNIT(s)->id); + return -EINVAL; + } + + r = slice_build_parent_slice(UNIT(s)->id, &parent); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to determine parent slice: %m"); + + if (parent ? !unit_has_name(UNIT_DEREF(UNIT(s)->slice), parent) : UNIT_ISSET(UNIT(s)->slice)) { + log_unit_error(UNIT(s), "Located outside of parent slice. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int slice_load(Unit *u) { + Slice *s = SLICE(u); + int r; + + assert(s); + + r = unit_load_fragment_and_dropin_optional(u); + if (r < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED) { + + r = unit_patch_contexts(u); + if (r < 0) + return r; + + r = slice_add_parent_slice(s); + if (r < 0) + return r; + + r = slice_add_default_dependencies(s); + if (r < 0) + return r; + } + + return slice_verify(s); +} + +static int slice_coldplug(Unit *u) { + Slice *t = SLICE(u); + + assert(t); + assert(t->state == SLICE_DEAD); + + if (t->deserialized_state != t->state) + slice_set_state(t, t->deserialized_state); + + return 0; +} + +static void slice_dump(Unit *u, FILE *f, const char *prefix) { + Slice *t = SLICE(u); + + assert(t); + assert(f); + + fprintf(f, + "%sSlice State: %s\n", + prefix, slice_state_to_string(t->state)); + + cgroup_context_dump(&t->cgroup_context, f, prefix); +} + +static int slice_start(Unit *u) { + Slice *t = SLICE(u); + + assert(t); + assert(t->state == SLICE_DEAD); + + (void) unit_realize_cgroup(u); + (void) unit_reset_cpu_usage(u); + + slice_set_state(t, SLICE_ACTIVE); + return 1; +} + +static int slice_stop(Unit *u) { + Slice *t = SLICE(u); + + assert(t); + assert(t->state == SLICE_ACTIVE); + + /* We do not need to destroy the cgroup explicitly, + * unit_notify() will do that for us anyway. */ + + slice_set_state(t, SLICE_DEAD); + return 1; +} + +static int slice_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + return unit_kill_common(u, who, signo, -1, -1, error); +} + +static int slice_serialize(Unit *u, FILE *f, FDSet *fds) { + Slice *s = SLICE(u); + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", slice_state_to_string(s->state)); + return 0; +} + +static int slice_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Slice *s = SLICE(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + SliceState state; + + state = slice_state_from_string(value); + if (state < 0) + log_debug("Failed to parse state value %s", value); + else + s->deserialized_state = state; + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +_pure_ static UnitActiveState slice_active_state(Unit *u) { + assert(u); + + return state_translation_table[SLICE(u)->state]; +} + +_pure_ static const char *slice_sub_state_to_string(Unit *u) { + assert(u); + + return slice_state_to_string(SLICE(u)->state); +} + +static void slice_enumerate(Manager *m) { + Unit *u; + int r; + + assert(m); + + u = manager_get_unit(m, SPECIAL_ROOT_SLICE); + if (!u) { + u = unit_new(m, sizeof(Slice)); + if (!u) { + log_oom(); + return; + } + + r = unit_add_name(u, SPECIAL_ROOT_SLICE); + if (r < 0) { + unit_free(u); + log_error_errno(r, "Failed to add -.slice name"); + return; + } + } + + u->default_dependencies = false; + u->no_gc = true; + u->ignore_on_isolate = true; + u->refuse_manual_start = true; + u->refuse_manual_stop = true; + SLICE(u)->deserialized_state = SLICE_ACTIVE; + + if (!u->description) + u->description = strdup("Root Slice"); + if (!u->documentation) + (void) strv_extend(&u->documentation, "man:systemd.special(7)"); + + unit_add_to_load_queue(u); + unit_add_to_dbus_queue(u); +} + +const UnitVTable slice_vtable = { + .object_size = sizeof(Slice), + .cgroup_context_offset = offsetof(Slice, cgroup_context), + + .sections = + "Unit\0" + "Slice\0" + "Install\0", + .private_section = "Slice", + + .no_alias = true, + .no_instances = true, + .can_transient = true, + + .load = slice_load, + + .coldplug = slice_coldplug, + + .dump = slice_dump, + + .start = slice_start, + .stop = slice_stop, + + .kill = slice_kill, + + .serialize = slice_serialize, + .deserialize_item = slice_deserialize_item, + + .active_state = slice_active_state, + .sub_state_to_string = slice_sub_state_to_string, + + .bus_vtable = bus_slice_vtable, + .bus_set_property = bus_slice_set_property, + .bus_commit_properties = bus_slice_commit_properties, + + .enumerate = slice_enumerate, + + .status_message_formats = { + .finished_start_job = { + [JOB_DONE] = "Created slice %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Removed slice %s.", + }, + }, +}; diff --git a/src/libcore/slice.h b/src/libcore/slice.h new file mode 100644 index 0000000000..c9f3f61067 --- /dev/null +++ b/src/libcore/slice.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + 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 . +***/ + +typedef struct Slice Slice; + +struct Slice { + Unit meta; + + SliceState state, deserialized_state; + + CGroupContext cgroup_context; +}; + +extern const UnitVTable slice_vtable; diff --git a/src/libcore/smack-setup.c b/src/libcore/smack-setup.c new file mode 100644 index 0000000000..0c26e85460 --- /dev/null +++ b/src/libcore/smack-setup.c @@ -0,0 +1,346 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation + Authors: + Nathaniel Chen + + 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "log.h" +#include "macro.h" +#include "smack-setup.h" +#include "string-util.h" +#include "util.h" + +#ifdef HAVE_SMACK + +static int write_access2_rules(const char* srcdir) { + _cleanup_close_ int load2_fd = -1, change_fd = -1; + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *entry; + char buf[NAME_MAX]; + int dfd = -1; + int r = 0; + + load2_fd = open("/sys/fs/smackfs/load2", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (load2_fd < 0) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open '/sys/fs/smackfs/load2': %m"); + return -errno; /* negative error */ + } + + change_fd = open("/sys/fs/smackfs/change-rule", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (change_fd < 0) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open '/sys/fs/smackfs/change-rule': %m"); + return -errno; /* negative error */ + } + + /* write rules to load2 or change-rule from every file in the directory */ + dir = opendir(srcdir); + if (!dir) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to opendir '%s': %m", srcdir); + return errno; /* positive on purpose */ + } + + dfd = dirfd(dir); + assert(dfd >= 0); + + FOREACH_DIRENT(entry, dir, return 0) { + int fd; + _cleanup_fclose_ FILE *policy = NULL; + + if (!dirent_is_file(entry)) + continue; + + fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + if (r == 0) + r = -errno; + log_warning_errno(errno, "Failed to open '%s': %m", entry->d_name); + continue; + } + + policy = fdopen(fd, "re"); + if (!policy) { + if (r == 0) + r = -errno; + safe_close(fd); + log_error_errno(errno, "Failed to open '%s': %m", entry->d_name); + continue; + } + + /* load2 write rules in the kernel require a line buffered stream */ + FOREACH_LINE(buf, policy, + log_error_errno(errno, "Failed to read line from '%s': %m", + entry->d_name)) { + + _cleanup_free_ char *sbj = NULL, *obj = NULL, *acc1 = NULL, *acc2 = NULL; + + if (isempty(truncate_nl(buf))) + continue; + + /* if 3 args -> load rule : subject object access1 */ + /* if 4 args -> change rule : subject object access1 access2 */ + if (sscanf(buf, "%ms %ms %ms %ms", &sbj, &obj, &acc1, &acc2) < 3) { + log_error_errno(errno, "Failed to parse rule '%s' in '%s', ignoring.", buf, entry->d_name); + continue; + } + + if (write(isempty(acc2) ? load2_fd : change_fd, buf, strlen(buf)) < 0) { + if (r == 0) + r = -errno; + log_error_errno(errno, "Failed to write '%s' to '%s' in '%s'", + buf, isempty(acc2) ? "/sys/fs/smackfs/load2" : "/sys/fs/smackfs/change-rule", entry->d_name); + } + } + } + + return r; +} + +static int write_cipso2_rules(const char* srcdir) { + _cleanup_close_ int cipso2_fd = -1; + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *entry; + char buf[NAME_MAX]; + int dfd = -1; + int r = 0; + + cipso2_fd = open("/sys/fs/smackfs/cipso2", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (cipso2_fd < 0) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open '/sys/fs/smackfs/cipso2': %m"); + return -errno; /* negative error */ + } + + /* write rules to cipso2 from every file in the directory */ + dir = opendir(srcdir); + if (!dir) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to opendir '%s': %m", srcdir); + return errno; /* positive on purpose */ + } + + dfd = dirfd(dir); + assert(dfd >= 0); + + FOREACH_DIRENT(entry, dir, return 0) { + int fd; + _cleanup_fclose_ FILE *policy = NULL; + + if (!dirent_is_file(entry)) + continue; + + fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + if (r == 0) + r = -errno; + log_error_errno(errno, "Failed to open '%s': %m", entry->d_name); + continue; + } + + policy = fdopen(fd, "re"); + if (!policy) { + if (r == 0) + r = -errno; + safe_close(fd); + log_error_errno(errno, "Failed to open '%s': %m", entry->d_name); + continue; + } + + /* cipso2 write rules in the kernel require a line buffered stream */ + FOREACH_LINE(buf, policy, + log_error_errno(errno, "Failed to read line from '%s': %m", + entry->d_name)) { + + if (isempty(truncate_nl(buf))) + continue; + + if (write(cipso2_fd, buf, strlen(buf)) < 0) { + if (r == 0) + r = -errno; + log_error_errno(errno, "Failed to write '%s' to '/sys/fs/smackfs/cipso2' in '%s'", + buf, entry->d_name); + break; + } + } + } + + return r; +} + +static int write_netlabel_rules(const char* srcdir) { + _cleanup_fclose_ FILE *dst = NULL; + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *entry; + char buf[NAME_MAX]; + int dfd = -1; + int r = 0; + + dst = fopen("/sys/fs/smackfs/netlabel", "we"); + if (!dst) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to open /sys/fs/smackfs/netlabel: %m"); + return -errno; /* negative error */ + } + + /* write rules to dst from every file in the directory */ + dir = opendir(srcdir); + if (!dir) { + if (errno != ENOENT) + log_warning_errno(errno, "Failed to opendir %s: %m", srcdir); + return errno; /* positive on purpose */ + } + + dfd = dirfd(dir); + assert(dfd >= 0); + + FOREACH_DIRENT(entry, dir, return 0) { + int fd; + _cleanup_fclose_ FILE *policy = NULL; + + fd = openat(dfd, entry->d_name, O_RDONLY|O_CLOEXEC); + if (fd < 0) { + if (r == 0) + r = -errno; + log_warning_errno(errno, "Failed to open %s: %m", entry->d_name); + continue; + } + + policy = fdopen(fd, "re"); + if (!policy) { + if (r == 0) + r = -errno; + safe_close(fd); + log_error_errno(errno, "Failed to open %s: %m", entry->d_name); + continue; + } + + /* load2 write rules in the kernel require a line buffered stream */ + FOREACH_LINE(buf, policy, + log_error_errno(errno, "Failed to read line from %s: %m", + entry->d_name)) { + if (!fputs(buf, dst)) { + if (r == 0) + r = -EINVAL; + log_error_errno(errno, "Failed to write line to /sys/fs/smackfs/netlabel"); + break; + } + if (fflush(dst)) { + if (r == 0) + r = -errno; + log_error_errno(errno, "Failed to flush writes to /sys/fs/smackfs/netlabel: %m"); + break; + } + } + } + + return r; +} + +#endif + +int mac_smack_setup(bool *loaded_policy) { + +#ifdef HAVE_SMACK + + int r; + + assert(loaded_policy); + + r = write_access2_rules("/etc/smack/accesses.d/"); + switch(r) { + case -ENOENT: + log_debug("Smack is not enabled in the kernel."); + return 0; + case ENOENT: + log_debug("Smack access rules directory '/etc/smack/accesses.d/' not found"); + return 0; + case 0: + log_info("Successfully loaded Smack policies."); + break; + default: + log_warning_errno(r, "Failed to load Smack access rules, ignoring: %m"); + return 0; + } + +#ifdef SMACK_RUN_LABEL + r = write_string_file("/proc/self/attr/current", SMACK_RUN_LABEL, 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK label \"" SMACK_RUN_LABEL "\" on self: %m"); + r = write_string_file("/sys/fs/smackfs/ambient", SMACK_RUN_LABEL, 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK ambient label \"" SMACK_RUN_LABEL "\": %m"); + r = write_string_file("/sys/fs/smackfs/netlabel", + "0.0.0.0/0 " SMACK_RUN_LABEL, 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK netlabel rule \"0.0.0.0/0 " SMACK_RUN_LABEL "\": %m"); + r = write_string_file("/sys/fs/smackfs/netlabel", "127.0.0.1 -CIPSO", 0); + if (r < 0) + log_warning_errno(r, "Failed to set SMACK netlabel rule \"127.0.0.1 -CIPSO\": %m"); +#endif + + r = write_cipso2_rules("/etc/smack/cipso.d/"); + switch(r) { + case -ENOENT: + log_debug("Smack/CIPSO is not enabled in the kernel."); + return 0; + case ENOENT: + log_debug("Smack/CIPSO access rules directory '/etc/smack/cipso.d/' not found"); + break; + case 0: + log_info("Successfully loaded Smack/CIPSO policies."); + break; + default: + log_warning_errno(r, "Failed to load Smack/CIPSO access rules, ignoring: %m"); + break; + } + + r = write_netlabel_rules("/etc/smack/netlabel.d/"); + switch(r) { + case -ENOENT: + log_debug("Smack/CIPSO is not enabled in the kernel."); + return 0; + case ENOENT: + log_debug("Smack network host rules directory '/etc/smack/netlabel.d/' not found"); + break; + case 0: + log_info("Successfully loaded Smack network host rules."); + break; + default: + log_warning_errno(r, "Failed to load Smack network host rules: %m, ignoring."); + break; + } + + *loaded_policy = true; + +#endif + + return 0; +} diff --git a/src/libcore/smack-setup.h b/src/libcore/smack-setup.h new file mode 100644 index 0000000000..78164c85e6 --- /dev/null +++ b/src/libcore/smack-setup.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation + Authors: + Nathaniel Chen + + 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 . +***/ + +int mac_smack_setup(bool *loaded_policy); diff --git a/src/libcore/socket.c b/src/libcore/socket.c new file mode 100644 index 0000000000..f36e312f7b --- /dev/null +++ b/src/libcore/socket.c @@ -0,0 +1,2866 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "copy.h" +#include "dbus-socket.h" +#include "def.h" +#include "exit-status.h" +#include "fd-util.h" +#include "formats-util.h" +#include "label.h" +#include "log.h" +#include "missing.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "smack-util.h" +#include "socket.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit-printf.h" +#include "unit.h" +#include "user-util.h" + +static const UnitActiveState state_translation_table[_SOCKET_STATE_MAX] = { + [SOCKET_DEAD] = UNIT_INACTIVE, + [SOCKET_START_PRE] = UNIT_ACTIVATING, + [SOCKET_START_CHOWN] = UNIT_ACTIVATING, + [SOCKET_START_POST] = UNIT_ACTIVATING, + [SOCKET_LISTENING] = UNIT_ACTIVE, + [SOCKET_RUNNING] = UNIT_ACTIVE, + [SOCKET_STOP_PRE] = UNIT_DEACTIVATING, + [SOCKET_STOP_PRE_SIGTERM] = UNIT_DEACTIVATING, + [SOCKET_STOP_PRE_SIGKILL] = UNIT_DEACTIVATING, + [SOCKET_STOP_POST] = UNIT_DEACTIVATING, + [SOCKET_FINAL_SIGTERM] = UNIT_DEACTIVATING, + [SOCKET_FINAL_SIGKILL] = UNIT_DEACTIVATING, + [SOCKET_FAILED] = UNIT_FAILED +}; + +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); + +static void socket_init(Unit *u) { + Socket *s = SOCKET(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + s->backlog = SOMAXCONN; + s->timeout_usec = u->manager->default_timeout_start_usec; + s->directory_mode = 0755; + s->socket_mode = 0666; + + s->max_connections = 64; + + s->priority = -1; + s->ip_tos = -1; + s->ip_ttl = -1; + s->mark = -1; + + s->exec_context.std_output = u->manager->default_std_output; + s->exec_context.std_error = u->manager->default_std_error; + + s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; +} + +static void socket_unwatch_control_pid(Socket *s) { + assert(s); + + if (s->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(s), s->control_pid); + s->control_pid = 0; +} + +static void socket_cleanup_fd_list(SocketPort *p) { + assert(p); + + close_many(p->auxiliary_fds, p->n_auxiliary_fds); + p->auxiliary_fds = mfree(p->auxiliary_fds); + p->n_auxiliary_fds = 0; +} + +void socket_free_ports(Socket *s) { + SocketPort *p; + + assert(s); + + while ((p = s->ports)) { + LIST_REMOVE(port, s->ports, p); + + sd_event_source_unref(p->event_source); + + socket_cleanup_fd_list(p); + safe_close(p->fd); + free(p->path); + free(p); + } +} + +static void socket_done(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + socket_free_ports(s); + + s->exec_runtime = exec_runtime_unref(s->exec_runtime); + exec_command_free_array(s->exec_command, _SOCKET_EXEC_COMMAND_MAX); + s->control_command = NULL; + + socket_unwatch_control_pid(s); + + unit_ref_unset(&s->service); + + s->tcp_congestion = mfree(s->tcp_congestion); + s->bind_to_device = mfree(s->bind_to_device); + + s->smack = mfree(s->smack); + s->smack_ip_in = mfree(s->smack_ip_in); + s->smack_ip_out = mfree(s->smack_ip_out); + + strv_free(s->symlinks); + + s->user = mfree(s->user); + s->group = mfree(s->group); + + s->fdname = mfree(s->fdname); + + s->timer_event_source = sd_event_source_unref(s->timer_event_source); +} + +static int socket_arm_timer(Socket *s, usec_t usec) { + int r; + + assert(s); + + if (s->timer_event_source) { + r = sd_event_source_set_time(s->timer_event_source, usec); + if (r < 0) + return r; + + return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); + } + + if (usec == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + UNIT(s)->manager->event, + &s->timer_event_source, + CLOCK_MONOTONIC, + usec, 0, + socket_dispatch_timer, s); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s->timer_event_source, "socket-timer"); + + return 0; +} + +int socket_instantiate_service(Socket *s) { + _cleanup_free_ char *prefix = NULL, *name = NULL; + int r; + Unit *u; + + assert(s); + + /* This fills in s->service if it isn't filled in yet. For + * Accept=yes sockets we create the next connection service + * here. For Accept=no this is mostly a NOP since the service + * is figured out at load time anyway. */ + + if (UNIT_DEREF(s->service)) + return 0; + + if (!s->accept) + return 0; + + r = unit_name_to_prefix(UNIT(s)->id, &prefix); + if (r < 0) + return r; + + if (asprintf(&name, "%s@%u.service", prefix, s->n_accepted) < 0) + return -ENOMEM; + + r = manager_load_unit(UNIT(s)->manager, name, NULL, NULL, &u); + if (r < 0) + return r; + + u->no_gc = true; + unit_ref_set(&s->service, u); + + return unit_add_two_dependencies(UNIT(s), UNIT_BEFORE, UNIT_TRIGGERS, u, false); +} + +static bool have_non_accept_socket(Socket *s) { + SocketPort *p; + + assert(s); + + if (!s->accept) + return true; + + LIST_FOREACH(port, p, s->ports) { + + if (p->type != SOCKET_SOCKET) + return true; + + if (!socket_address_can_accept(&p->address)) + return true; + } + + return false; +} + +static int socket_add_mount_links(Socket *s) { + SocketPort *p; + int r; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + const char *path = NULL; + + if (p->type == SOCKET_SOCKET) + path = socket_address_get_path(&p->address); + else if (IN_SET(p->type, SOCKET_FIFO, SOCKET_SPECIAL, SOCKET_USB_FUNCTION)) + path = p->path; + + if (!path) + continue; + + r = unit_require_mounts_for(UNIT(s), path); + if (r < 0) + return r; + } + + return 0; +} + +static int socket_add_device_link(Socket *s) { + char *t; + + assert(s); + + if (!s->bind_to_device || streq(s->bind_to_device, "lo")) + return 0; + + t = strjoina("/sys/subsystem/net/devices/", s->bind_to_device); + return unit_add_node_link(UNIT(s), t, false, UNIT_BINDS_TO); +} + +static int socket_add_default_dependencies(Socket *s) { + int r; + assert(s); + + if (!UNIT(s)->default_dependencies) + return 0; + + r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SOCKETS_TARGET, NULL, true); + if (r < 0) + return r; + + if (UNIT(s)->manager->running_as == MANAGER_SYSTEM) { + r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); + if (r < 0) + return r; + } + + return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +_pure_ static bool socket_has_exec(Socket *s) { + unsigned i; + assert(s); + + for (i = 0; i < _SOCKET_EXEC_COMMAND_MAX; i++) + if (s->exec_command[i]) + return true; + + return false; +} + +static int socket_add_extras(Socket *s) { + Unit *u = UNIT(s); + int r; + + assert(s); + + if (have_non_accept_socket(s)) { + + if (!UNIT_DEREF(s->service)) { + Unit *x; + + r = unit_load_related_unit(u, ".service", &x); + if (r < 0) + return r; + + unit_ref_set(&s->service, x); + } + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(s->service), true); + if (r < 0) + return r; + } + + r = socket_add_mount_links(s); + if (r < 0) + return r; + + r = socket_add_device_link(s); + if (r < 0) + return r; + + r = unit_patch_contexts(u); + if (r < 0) + return r; + + if (socket_has_exec(s)) { + r = unit_add_exec_dependencies(u, &s->exec_context); + if (r < 0) + return r; + + r = unit_set_default_slice(u); + if (r < 0) + return r; + } + + r = socket_add_default_dependencies(s); + if (r < 0) + return r; + + return 0; +} + +static const char *socket_find_symlink_target(Socket *s) { + const char *found = NULL; + SocketPort *p; + + LIST_FOREACH(port, p, s->ports) { + const char *f = NULL; + + switch (p->type) { + + case SOCKET_FIFO: + f = p->path; + break; + + case SOCKET_SOCKET: + if (p->address.sockaddr.un.sun_path[0] != 0) + f = p->address.sockaddr.un.sun_path; + break; + + default: + break; + } + + if (f) { + if (found) + return NULL; + + found = f; + } + } + + return found; +} + +static int socket_verify(Socket *s) { + assert(s); + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + if (!s->ports) { + log_unit_error(UNIT(s), "Unit lacks Listen setting. Refusing."); + return -EINVAL; + } + + if (s->accept && have_non_accept_socket(s)) { + log_unit_error(UNIT(s), "Unit configured for accepting sockets, but sockets are non-accepting. Refusing."); + return -EINVAL; + } + + if (s->accept && s->max_connections <= 0) { + log_unit_error(UNIT(s), "MaxConnection= setting too small. Refusing."); + return -EINVAL; + } + + if (s->accept && UNIT_DEREF(s->service)) { + log_unit_error(UNIT(s), "Explicit service configuration for accepting socket units not supported. Refusing."); + return -EINVAL; + } + + if (s->exec_context.pam_name && s->kill_context.kill_mode != KILL_CONTROL_GROUP) { + log_unit_error(UNIT(s), "Unit has PAM enabled. Kill mode must be set to 'control-group'. Refusing."); + return -EINVAL; + } + + if (!strv_isempty(s->symlinks) && !socket_find_symlink_target(s)) { + log_unit_error(UNIT(s), "Unit has symlinks set but none or more than one node in the file system. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int socket_load(Unit *u) { + Socket *s = SOCKET(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + r = unit_load_fragment_and_dropin(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + /* This is a new unit? Then let's add in some extras */ + r = socket_add_extras(s); + if (r < 0) + return r; + } + + return socket_verify(s); +} + +_const_ static const char* listen_lookup(int family, int type) { + + if (family == AF_NETLINK) + return "ListenNetlink"; + + if (type == SOCK_STREAM) + return "ListenStream"; + else if (type == SOCK_DGRAM) + return "ListenDatagram"; + else if (type == SOCK_SEQPACKET) + return "ListenSequentialPacket"; + + assert_not_reached("Unknown socket type"); + return NULL; +} + +static void socket_dump(Unit *u, FILE *f, const char *prefix) { + char time_string[FORMAT_TIMESPAN_MAX]; + SocketExecCommand c; + Socket *s = SOCKET(u); + SocketPort *p; + const char *prefix2; + + assert(s); + assert(f); + + prefix = strempty(prefix); + prefix2 = strjoina(prefix, "\t"); + + fprintf(f, + "%sSocket State: %s\n" + "%sResult: %s\n" + "%sBindIPv6Only: %s\n" + "%sBacklog: %u\n" + "%sSocketMode: %04o\n" + "%sDirectoryMode: %04o\n" + "%sKeepAlive: %s\n" + "%sNoDelay: %s\n" + "%sFreeBind: %s\n" + "%sTransparent: %s\n" + "%sBroadcast: %s\n" + "%sPassCredentials: %s\n" + "%sPassSecurity: %s\n" + "%sTCPCongestion: %s\n" + "%sRemoveOnStop: %s\n" + "%sWritable: %s\n" + "%sFDName: %s\n" + "%sSELinuxContextFromNet: %s\n", + prefix, socket_state_to_string(s->state), + prefix, socket_result_to_string(s->result), + prefix, socket_address_bind_ipv6_only_to_string(s->bind_ipv6_only), + prefix, s->backlog, + prefix, s->socket_mode, + prefix, s->directory_mode, + prefix, yes_no(s->keep_alive), + prefix, yes_no(s->no_delay), + prefix, yes_no(s->free_bind), + prefix, yes_no(s->transparent), + prefix, yes_no(s->broadcast), + prefix, yes_no(s->pass_cred), + prefix, yes_no(s->pass_sec), + prefix, strna(s->tcp_congestion), + prefix, yes_no(s->remove_on_stop), + prefix, yes_no(s->writable), + prefix, socket_fdname(s), + prefix, yes_no(s->selinux_context_from_net)); + + if (s->control_pid > 0) + fprintf(f, + "%sControl PID: "PID_FMT"\n", + prefix, s->control_pid); + + if (s->bind_to_device) + fprintf(f, + "%sBindToDevice: %s\n", + prefix, s->bind_to_device); + + if (s->accept) + fprintf(f, + "%sAccepted: %u\n" + "%sNConnections: %u\n" + "%sMaxConnections: %u\n", + prefix, s->n_accepted, + prefix, s->n_connections, + prefix, s->max_connections); + + if (s->priority >= 0) + fprintf(f, + "%sPriority: %i\n", + prefix, s->priority); + + if (s->receive_buffer > 0) + fprintf(f, + "%sReceiveBuffer: %zu\n", + prefix, s->receive_buffer); + + if (s->send_buffer > 0) + fprintf(f, + "%sSendBuffer: %zu\n", + prefix, s->send_buffer); + + if (s->ip_tos >= 0) + fprintf(f, + "%sIPTOS: %i\n", + prefix, s->ip_tos); + + if (s->ip_ttl >= 0) + fprintf(f, + "%sIPTTL: %i\n", + prefix, s->ip_ttl); + + if (s->pipe_size > 0) + fprintf(f, + "%sPipeSize: %zu\n", + prefix, s->pipe_size); + + if (s->mark >= 0) + fprintf(f, + "%sMark: %i\n", + prefix, s->mark); + + if (s->mq_maxmsg > 0) + fprintf(f, + "%sMessageQueueMaxMessages: %li\n", + prefix, s->mq_maxmsg); + + if (s->mq_msgsize > 0) + fprintf(f, + "%sMessageQueueMessageSize: %li\n", + prefix, s->mq_msgsize); + + if (s->reuse_port) + fprintf(f, + "%sReusePort: %s\n", + prefix, yes_no(s->reuse_port)); + + if (s->smack) + fprintf(f, + "%sSmackLabel: %s\n", + prefix, s->smack); + + if (s->smack_ip_in) + fprintf(f, + "%sSmackLabelIPIn: %s\n", + prefix, s->smack_ip_in); + + if (s->smack_ip_out) + fprintf(f, + "%sSmackLabelIPOut: %s\n", + prefix, s->smack_ip_out); + + if (!isempty(s->user) || !isempty(s->group)) + fprintf(f, + "%sOwnerUser: %s\n" + "%sOwnerGroup: %s\n", + prefix, strna(s->user), + prefix, strna(s->group)); + + if (s->keep_alive_time > 0) + fprintf(f, + "%sKeepAliveTimeSec: %s\n", + prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->keep_alive_time, USEC_PER_SEC)); + + if (s->keep_alive_interval) + fprintf(f, + "%sKeepAliveIntervalSec: %s\n", + prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->keep_alive_interval, USEC_PER_SEC)); + + if (s->keep_alive_cnt) + fprintf(f, + "%sKeepAliveProbes: %u\n", + prefix, s->keep_alive_cnt); + + if (s->defer_accept) + fprintf(f, + "%sDeferAcceptSec: %s\n", + prefix, format_timespan(time_string, FORMAT_TIMESPAN_MAX, s->defer_accept, USEC_PER_SEC)); + + LIST_FOREACH(port, p, s->ports) { + + if (p->type == SOCKET_SOCKET) { + const char *t; + int r; + char *k = NULL; + + r = socket_address_print(&p->address, &k); + if (r < 0) + t = strerror(-r); + else + t = k; + + fprintf(f, "%s%s: %s\n", prefix, listen_lookup(socket_address_family(&p->address), p->address.type), t); + free(k); + } else if (p->type == SOCKET_SPECIAL) + fprintf(f, "%sListenSpecial: %s\n", prefix, p->path); + else if (p->type == SOCKET_USB_FUNCTION) + fprintf(f, "%sListenUSBFunction: %s\n", prefix, p->path); + else if (p->type == SOCKET_MQUEUE) + fprintf(f, "%sListenMessageQueue: %s\n", prefix, p->path); + else + fprintf(f, "%sListenFIFO: %s\n", prefix, p->path); + } + + exec_context_dump(&s->exec_context, f, prefix); + kill_context_dump(&s->kill_context, f, prefix); + + for (c = 0; c < _SOCKET_EXEC_COMMAND_MAX; c++) { + if (!s->exec_command[c]) + continue; + + fprintf(f, "%s-> %s:\n", + prefix, socket_exec_command_to_string(c)); + + exec_command_dump_list(s->exec_command[c], f, prefix2); + } +} + +static int instance_from_socket(int fd, unsigned nr, char **instance) { + socklen_t l; + char *r; + union sockaddr_union local, remote; + + assert(fd >= 0); + assert(instance); + + l = sizeof(local); + if (getsockname(fd, &local.sa, &l) < 0) + return -errno; + + l = sizeof(remote); + if (getpeername(fd, &remote.sa, &l) < 0) + return -errno; + + switch (local.sa.sa_family) { + + case AF_INET: { + uint32_t + a = ntohl(local.in.sin_addr.s_addr), + b = ntohl(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), + b >> 24, (b >> 16) & 0xFF, (b >> 8) & 0xFF, b & 0xFF, + ntohs(remote.in.sin_port)) < 0) + return -ENOMEM; + + break; + } + + case AF_INET6: { + static const unsigned char ipv4_prefix[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF + }; + + if (memcmp(&local.in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0 && + memcmp(&remote.in6.sin6_addr, ipv4_prefix, sizeof(ipv4_prefix)) == 0) { + const uint8_t + *a = local.in6.sin6_addr.s6_addr+12, + *b = remote.in6.sin6_addr.s6_addr+12; + + if (asprintf(&r, + "%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), + b[0], b[1], b[2], b[3], + ntohs(remote.in6.sin6_port)) < 0) + return -ENOMEM; + } else { + char a[INET6_ADDRSTRLEN], b[INET6_ADDRSTRLEN]; + + if (asprintf(&r, + "%u-%s:%u-%s:%u", + nr, + inet_ntop(AF_INET6, &local.in6.sin6_addr, a, sizeof(a)), + ntohs(local.in6.sin6_port), + inet_ntop(AF_INET6, &remote.in6.sin6_addr, b, sizeof(b)), + ntohs(remote.in6.sin6_port)) < 0) + return -ENOMEM; + } + + break; + } + + case AF_UNIX: { + struct ucred ucred; + int k; + + k = getpeercred(fd, &ucred); + if (k >= 0) { + if (asprintf(&r, + "%u-"PID_FMT"-"UID_FMT, + nr, ucred.pid, ucred.uid) < 0) + return -ENOMEM; + } else if (k == -ENODATA) { + /* This handles the case where somebody is + * connecting from another pid/uid namespace + * (e.g. from outside of our container). */ + if (asprintf(&r, + "%u-unknown", + nr) < 0) + return -ENOMEM; + } else + return k; + + break; + } + + default: + assert_not_reached("Unhandled socket type."); + } + + *instance = r; + return 0; +} + +static void socket_close_fds(Socket *s) { + SocketPort *p; + char **i; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + + p->event_source = sd_event_source_unref(p->event_source); + + if (p->fd < 0) + continue; + + p->fd = safe_close(p->fd); + socket_cleanup_fd_list(p); + + /* One little note: we should normally not delete any + * sockets in the file system here! After all some + * other process we spawned might still have a + * reference of this fd and wants to continue to use + * it. Therefore we delete sockets in the file system + * before we create a new one, not after we stopped + * using one! */ + + if (s->remove_on_stop) { + switch (p->type) { + + case SOCKET_FIFO: + unlink(p->path); + break; + + case SOCKET_MQUEUE: + mq_unlink(p->path); + break; + + case SOCKET_SOCKET: + socket_address_unlink(&p->address); + break; + + default: + break; + } + } + } + + if (s->remove_on_stop) + STRV_FOREACH(i, s->symlinks) + unlink(*i); +} + +static void socket_apply_socket_options(Socket *s, int fd) { + int r; + + assert(s); + assert(fd >= 0); + + if (s->keep_alive) { + int b = s->keep_alive; + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &b, sizeof(b)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_KEEPALIVE failed: %m"); + } + + if (s->keep_alive_time) { + int value = s->keep_alive_time / USEC_PER_SEC; + if (setsockopt(fd, SOL_TCP, TCP_KEEPIDLE, &value, sizeof(value)) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_KEEPIDLE failed: %m"); + } + + if (s->keep_alive_interval) { + int value = s->keep_alive_interval / USEC_PER_SEC; + if (setsockopt(fd, SOL_TCP, TCP_KEEPINTVL, &value, sizeof(value)) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_KEEPINTVL failed: %m"); + } + + if (s->keep_alive_cnt) { + int value = s->keep_alive_cnt; + if (setsockopt(fd, SOL_TCP, TCP_KEEPCNT, &value, sizeof(value)) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_KEEPCNT failed: %m"); + } + + if (s->defer_accept) { + int value = s->defer_accept / USEC_PER_SEC; + if (setsockopt(fd, SOL_TCP, TCP_DEFER_ACCEPT, &value, sizeof(value)) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_DEFER_ACCEPT failed: %m"); + } + + if (s->no_delay) { + int b = s->no_delay; + + if (s->socket_protocol == IPPROTO_SCTP) { + if (setsockopt(fd, SOL_SCTP, SCTP_NODELAY, &b, sizeof(b)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SCTP_NODELAY failed: %m"); + } else { + if (setsockopt(fd, SOL_TCP, TCP_NODELAY, &b, sizeof(b)) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_NODELAY failed: %m"); + } + } + + if (s->broadcast) { + int one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &one, sizeof(one)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_BROADCAST failed: %m"); + } + + if (s->pass_cred) { + int one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_PASSCRED failed: %m"); + } + + if (s->pass_sec) { + int one = 1; + if (setsockopt(fd, SOL_SOCKET, SO_PASSSEC, &one, sizeof(one)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_PASSSEC failed: %m"); + } + + if (s->priority >= 0) + if (setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &s->priority, sizeof(s->priority)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_PRIORITY failed: %m"); + + if (s->receive_buffer > 0) { + int value = (int) s->receive_buffer; + + /* We first try with SO_RCVBUFFORCE, in case we have the perms for that */ + + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUFFORCE, &value, sizeof(value)) < 0) + if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_RCVBUF failed: %m"); + } + + if (s->send_buffer > 0) { + int value = (int) s->send_buffer; + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUFFORCE, &value, sizeof(value)) < 0) + if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &value, sizeof(value)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_SNDBUF failed: %m"); + } + + if (s->mark >= 0) + if (setsockopt(fd, SOL_SOCKET, SO_MARK, &s->mark, sizeof(s->mark)) < 0) + log_unit_warning_errno(UNIT(s), errno, "SO_MARK failed: %m"); + + if (s->ip_tos >= 0) + if (setsockopt(fd, IPPROTO_IP, IP_TOS, &s->ip_tos, sizeof(s->ip_tos)) < 0) + log_unit_warning_errno(UNIT(s), errno, "IP_TOS failed: %m"); + + if (s->ip_ttl >= 0) { + int x; + + r = setsockopt(fd, IPPROTO_IP, IP_TTL, &s->ip_ttl, sizeof(s->ip_ttl)); + + if (socket_ipv6_is_supported()) + x = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &s->ip_ttl, sizeof(s->ip_ttl)); + else { + x = -1; + errno = EAFNOSUPPORT; + } + + if (r < 0 && x < 0) + log_unit_warning_errno(UNIT(s), errno, "IP_TTL/IPV6_UNICAST_HOPS failed: %m"); + } + + if (s->tcp_congestion) + if (setsockopt(fd, SOL_TCP, TCP_CONGESTION, s->tcp_congestion, strlen(s->tcp_congestion)+1) < 0) + log_unit_warning_errno(UNIT(s), errno, "TCP_CONGESTION failed: %m"); + + if (s->smack_ip_in) { + r = mac_smack_apply_fd(fd, SMACK_ATTR_IPIN, s->smack_ip_in); + if (r < 0) + log_unit_error_errno(UNIT(s), r, "mac_smack_apply_ip_in_fd: %m"); + } + + if (s->smack_ip_out) { + r = mac_smack_apply_fd(fd, SMACK_ATTR_IPOUT, s->smack_ip_out); + if (r < 0) + log_unit_error_errno(UNIT(s), r, "mac_smack_apply_ip_out_fd: %m"); + } +} + +static void socket_apply_fifo_options(Socket *s, int fd) { + int r; + + assert(s); + assert(fd >= 0); + + if (s->pipe_size > 0) + if (fcntl(fd, F_SETPIPE_SZ, s->pipe_size) < 0) + log_unit_warning_errno(UNIT(s), errno, "Setting pipe size failed, ignoring: %m"); + + if (s->smack) { + r = mac_smack_apply_fd(fd, SMACK_ATTR_ACCESS, s->smack); + if (r < 0) + log_unit_error_errno(UNIT(s), r, "SMACK relabelling failed, ignoring: %m"); + } +} + +static int fifo_address_create( + const char *path, + mode_t directory_mode, + mode_t socket_mode) { + + _cleanup_close_ int fd = -1; + mode_t old_mask; + struct stat st; + int r; + + assert(path); + + mkdir_parents_label(path, directory_mode); + + r = mac_selinux_create_file_prepare(path, S_IFIFO); + if (r < 0) + return r; + + /* Enforce the right access mode for the fifo */ + old_mask = umask(~ socket_mode); + + /* Include the original umask in our mask */ + (void) umask(~socket_mode | old_mask); + + r = mkfifo(path, socket_mode); + (void) umask(old_mask); + + if (r < 0 && errno != EEXIST) { + r = -errno; + goto fail; + } + + fd = open(path, O_RDWR | O_CLOEXEC | O_NOCTTY | O_NONBLOCK | O_NOFOLLOW); + if (fd < 0) { + r = -errno; + goto fail; + } + + mac_selinux_create_file_clear(); + + if (fstat(fd, &st) < 0) { + r = -errno; + goto fail; + } + + if (!S_ISFIFO(st.st_mode) || + (st.st_mode & 0777) != (socket_mode & ~old_mask) || + st.st_uid != getuid() || + st.st_gid != getgid()) { + r = -EEXIST; + goto fail; + } + + r = fd; + fd = -1; + + return r; + +fail: + mac_selinux_create_file_clear(); + return r; +} + +static int special_address_create(const char *path, bool writable) { + _cleanup_close_ int fd = -1; + struct stat st; + int r; + + assert(path); + + fd = open(path, (writable ? O_RDWR : O_RDONLY)|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + /* Check whether this is a /proc, /sys or /dev file or char device */ + if (!S_ISREG(st.st_mode) && !S_ISCHR(st.st_mode)) + return -EEXIST; + + r = fd; + fd = -1; + + return r; +} + +static int usbffs_address_create(const char *path) { + _cleanup_close_ int fd = -1; + struct stat st; + int r; + + assert(path); + + fd = open(path, O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK|O_NOFOLLOW); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + /* Check whether this is a regular file (ffs endpoint)*/ + if (!S_ISREG(st.st_mode)) + return -EEXIST; + + r = fd; + fd = -1; + + return r; +} + +static int mq_address_create( + const char *path, + mode_t mq_mode, + long maxmsg, + long msgsize) { + + _cleanup_close_ int fd = -1; + struct stat st; + mode_t old_mask; + struct mq_attr _attr, *attr = NULL; + int r; + + assert(path); + + if (maxmsg > 0 && msgsize > 0) { + _attr = (struct mq_attr) { + .mq_flags = O_NONBLOCK, + .mq_maxmsg = maxmsg, + .mq_msgsize = msgsize, + }; + attr = &_attr; + } + + /* Enforce the right access mode for the mq */ + old_mask = umask(~ mq_mode); + + /* Include the original umask in our mask */ + (void) umask(~mq_mode | old_mask); + fd = mq_open(path, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_CREAT, mq_mode, attr); + (void) umask(old_mask); + + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + + if ((st.st_mode & 0777) != (mq_mode & ~old_mask) || + st.st_uid != getuid() || + st.st_gid != getgid()) + return -EEXIST; + + r = fd; + fd = -1; + + return r; +} + +static int socket_symlink(Socket *s) { + const char *p; + char **i; + + assert(s); + + p = socket_find_symlink_target(s); + if (!p) + return 0; + + STRV_FOREACH(i, s->symlinks) + symlink_label(p, *i); + + return 0; +} + +static int usbffs_write_descs(int fd, Service *s) { + int r; + + if (!s->usb_function_descriptors || !s->usb_function_strings) + return -EINVAL; + + r = copy_file_fd(s->usb_function_descriptors, fd, false); + if (r < 0) + return r; + + return copy_file_fd(s->usb_function_strings, fd, false); +} + +static int usbffs_select_ep(const struct dirent *d) { + return d->d_name[0] != '.' && !streq(d->d_name, "ep0"); +} + +static int usbffs_dispatch_eps(SocketPort *p) { + _cleanup_free_ struct dirent **ent = NULL; + _cleanup_free_ char *path = NULL; + int r, i, n, k; + + path = dirname_malloc(p->path); + if (!path) + return -ENOMEM; + + r = scandir(path, &ent, usbffs_select_ep, alphasort); + if (r < 0) + return -errno; + + n = r; + p->auxiliary_fds = new(int, n); + if (!p->auxiliary_fds) + return -ENOMEM; + + p->n_auxiliary_fds = n; + + k = 0; + for (i = 0; i < n; ++i) { + _cleanup_free_ char *ep = NULL; + + ep = path_make_absolute(ent[i]->d_name, path); + if (!ep) + return -ENOMEM; + + path_kill_slashes(ep); + + r = usbffs_address_create(ep); + if (r < 0) + goto fail; + + p->auxiliary_fds[k] = r; + + ++k; + free(ent[i]); + } + + return r; + +fail: + close_many(p->auxiliary_fds, k); + p->auxiliary_fds = mfree(p->auxiliary_fds); + p->n_auxiliary_fds = 0; + + return r; +} + +static int socket_open_fds(Socket *s) { + _cleanup_(mac_selinux_freep) char *label = NULL; + bool know_label = false; + SocketPort *p; + int r; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + + if (p->fd >= 0) + continue; + + switch (p->type) { + + case SOCKET_SOCKET: + + if (!know_label) { + /* Figure out label, if we don't it know + * yet. We do it once, for the first + * socket where we need this and + * remember it for the rest. */ + + if (s->selinux_context_from_net) { + /* Get it from the network label */ + + r = mac_selinux_get_our_label(&label); + if (r < 0 && r != -EOPNOTSUPP) + goto rollback; + + } else { + /* Get it from the executable we are about to start */ + + r = socket_instantiate_service(s); + if (r < 0) + goto rollback; + + if (UNIT_ISSET(s->service) && + SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]) { + r = mac_selinux_get_create_label_from_exe(SERVICE(UNIT_DEREF(s->service))->exec_command[SERVICE_EXEC_START]->path, &label); + if (r < 0 && r != -EPERM && r != -EOPNOTSUPP) + goto rollback; + } + } + + know_label = true; + } + + /* Apply the socket protocol */ + switch(p->address.type) { + case SOCK_STREAM: + case SOCK_SEQPACKET: + if (p->socket->socket_protocol == IPPROTO_SCTP) + p->address.protocol = p->socket->socket_protocol; + break; + case SOCK_DGRAM: + if (p->socket->socket_protocol == IPPROTO_UDPLITE) + p->address.protocol = p->socket->socket_protocol; + break; + } + + r = socket_address_listen( + &p->address, + SOCK_CLOEXEC|SOCK_NONBLOCK, + s->backlog, + s->bind_ipv6_only, + s->bind_to_device, + s->reuse_port, + s->free_bind, + s->transparent, + s->directory_mode, + s->socket_mode, + label); + if (r < 0) + goto rollback; + + p->fd = r; + socket_apply_socket_options(s, p->fd); + socket_symlink(s); + break; + + case SOCKET_SPECIAL: + + p->fd = special_address_create(p->path, s->writable); + if (p->fd < 0) { + r = p->fd; + goto rollback; + } + break; + + case SOCKET_FIFO: + + p->fd = fifo_address_create( + p->path, + s->directory_mode, + s->socket_mode); + if (p->fd < 0) { + r = p->fd; + goto rollback; + } + + socket_apply_fifo_options(s, p->fd); + socket_symlink(s); + break; + + case SOCKET_MQUEUE: + + p->fd = mq_address_create( + p->path, + s->socket_mode, + s->mq_maxmsg, + s->mq_msgsize); + if (p->fd < 0) { + r = p->fd; + goto rollback; + } + break; + + case SOCKET_USB_FUNCTION: + + p->fd = usbffs_address_create(p->path); + if (p->fd < 0) { + r = p->fd; + goto rollback; + } + + r = usbffs_write_descs(p->fd, SERVICE(UNIT_DEREF(s->service))); + if (r < 0) + goto rollback; + + r = usbffs_dispatch_eps(p); + if (r < 0) + goto rollback; + + break; + + default: + assert_not_reached("Unknown port type"); + } + } + + return 0; + +rollback: + socket_close_fds(s); + return r; +} + +static void socket_unwatch_fds(Socket *s) { + SocketPort *p; + int r; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + continue; + + if (!p->event_source) + continue; + + r = sd_event_source_set_enabled(p->event_source, SD_EVENT_OFF); + if (r < 0) + log_unit_debug_errno(UNIT(s), r, "Failed to disable event source: %m"); + } +} + +static int socket_watch_fds(Socket *s) { + SocketPort *p; + int r; + + assert(s); + + LIST_FOREACH(port, p, s->ports) { + if (p->fd < 0) + continue; + + if (p->event_source) { + r = sd_event_source_set_enabled(p->event_source, SD_EVENT_ON); + if (r < 0) + goto fail; + } else { + r = sd_event_add_io(UNIT(s)->manager->event, &p->event_source, p->fd, EPOLLIN, socket_dispatch_io, p); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(p->event_source, "socket-port-io"); + } + } + + return 0; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to watch listening fds: %m"); + socket_unwatch_fds(s); + return r; +} + +static void socket_set_state(Socket *s, SocketState state) { + SocketState old_state; + assert(s); + + old_state = s->state; + s->state = state; + + if (!IN_SET(state, + SOCKET_START_PRE, + SOCKET_START_CHOWN, + SOCKET_START_POST, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL, + SOCKET_STOP_POST, + SOCKET_FINAL_SIGTERM, + SOCKET_FINAL_SIGKILL)) { + + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + socket_unwatch_control_pid(s); + s->control_command = NULL; + s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; + } + + if (state != SOCKET_LISTENING) + socket_unwatch_fds(s); + + if (!IN_SET(state, + SOCKET_START_CHOWN, + SOCKET_START_POST, + SOCKET_LISTENING, + SOCKET_RUNNING, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL)) + socket_close_fds(s); + + if (state != old_state) + log_unit_debug(UNIT(s), "Changed %s -> %s", socket_state_to_string(old_state), socket_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); +} + +static int socket_coldplug(Unit *u) { + Socket *s = SOCKET(u); + int r; + + assert(s); + assert(s->state == SOCKET_DEAD); + + if (s->deserialized_state == s->state) + return 0; + + if (s->control_pid > 0 && + pid_is_unwaited(s->control_pid) && + IN_SET(s->deserialized_state, + SOCKET_START_PRE, + SOCKET_START_CHOWN, + SOCKET_START_POST, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL, + SOCKET_STOP_POST, + SOCKET_FINAL_SIGTERM, + SOCKET_FINAL_SIGKILL)) { + + r = unit_watch_pid(UNIT(s), s->control_pid); + if (r < 0) + return r; + + r = socket_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_usec)); + if (r < 0) + return r; + } + + if (IN_SET(s->deserialized_state, + SOCKET_START_CHOWN, + SOCKET_START_POST, + SOCKET_LISTENING, + SOCKET_RUNNING, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL)) { + + r = socket_open_fds(s); + if (r < 0) + return r; + } + + if (s->deserialized_state == SOCKET_LISTENING) { + r = socket_watch_fds(s); + if (r < 0) + return r; + } + + socket_set_state(s, s->deserialized_state); + return 0; +} + +static int socket_spawn(Socket *s, ExecCommand *c, pid_t *_pid) { + _cleanup_free_ char **argv = NULL; + pid_t pid; + int r; + ExecParameters exec_params = { + .apply_permissions = true, + .apply_chroot = true, + .apply_tty_stdin = true, + .bus_endpoint_fd = -1, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, + }; + + assert(s); + assert(c); + assert(_pid); + + (void) unit_realize_cgroup(UNIT(s)); + if (s->reset_cpu_usage) { + (void) unit_reset_cpu_usage(UNIT(s)); + s->reset_cpu_usage = false; + } + + r = unit_setup_exec_runtime(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; + + r = unit_full_printf_strv(UNIT(s), c->argv, &argv); + if (r < 0) + return r; + + exec_params.argv = argv; + exec_params.environment = UNIT(s)->manager->environment; + exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; + exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; + exec_params.cgroup_path = UNIT(s)->cgroup_path; + exec_params.cgroup_delegate = s->cgroup_context.delegate; + exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(s)->manager); + + r = exec_spawn(UNIT(s), + c, + &s->exec_context, + &exec_params, + s->exec_runtime, + &pid); + if (r < 0) + return r; + + r = unit_watch_pid(UNIT(s), pid); + if (r < 0) + /* FIXME: we need to do something here */ + return r; + + *_pid = pid; + return 0; +} + +static int socket_chown(Socket *s, pid_t *_pid) { + pid_t pid; + int r; + + r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); + if (r < 0) + goto fail; + + /* We have to resolve the user names out-of-process, hence + * let's fork here. It's messy, but well, what can we do? */ + + pid = fork(); + if (pid < 0) + return -errno; + + if (pid == 0) { + SocketPort *p; + uid_t uid = UID_INVALID; + gid_t gid = GID_INVALID; + int ret; + + (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE, -1); + (void) ignore_signals(SIGPIPE, -1); + log_forget_fds(); + + if (!isempty(s->user)) { + const char *user = s->user; + + r = get_user_creds(&user, &uid, &gid, NULL, NULL); + if (r < 0) { + ret = EXIT_USER; + goto fail_child; + } + } + + if (!isempty(s->group)) { + const char *group = s->group; + + r = get_group_creds(&group, &gid); + if (r < 0) { + ret = EXIT_GROUP; + goto fail_child; + } + } + + LIST_FOREACH(port, p, s->ports) { + const char *path = NULL; + + if (p->type == SOCKET_SOCKET) + path = socket_address_get_path(&p->address); + else if (p->type == SOCKET_FIFO) + path = p->path; + + if (!path) + continue; + + if (chown(path, uid, gid) < 0) { + r = -errno; + ret = EXIT_CHOWN; + goto fail_child; + } + } + + _exit(0); + + fail_child: + log_open(); + log_error_errno(r, "Failed to chown socket at step %s: %m", exit_status_to_string(ret, EXIT_STATUS_SYSTEMD)); + + _exit(ret); + } + + r = unit_watch_pid(UNIT(s), pid); + if (r < 0) + goto fail; + + *_pid = pid; + return 0; + +fail: + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + return r; +} + +static void socket_enter_dead(Socket *s, SocketResult f) { + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + 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); +} + +static void socket_enter_signal(Socket *s, SocketState state, SocketResult f); + +static void socket_enter_stop_post(Socket *s, SocketResult f) { + int r; + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + socket_unwatch_control_pid(s); + s->control_command_id = SOCKET_EXEC_STOP_POST; + s->control_command = s->exec_command[SOCKET_EXEC_STOP_POST]; + + if (s->control_command) { + r = socket_spawn(s, s->control_command, &s->control_pid); + if (r < 0) + goto fail; + + socket_set_state(s, SOCKET_STOP_POST); + } else + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop-post' task: %m"); + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_signal(Socket *s, SocketState state, SocketResult f) { + int r; + + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + r = unit_kill_context( + UNIT(s), + &s->kill_context, + (state != SOCKET_STOP_PRE_SIGTERM && state != SOCKET_FINAL_SIGTERM) ? + KILL_KILL : KILL_TERMINATE, + -1, + s->control_pid, + false); + if (r < 0) + goto fail; + + if (r > 0) { + r = socket_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); + if (r < 0) + goto fail; + + socket_set_state(s, state); + } else if (state == SOCKET_STOP_PRE_SIGTERM) + socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, SOCKET_SUCCESS); + else if (state == SOCKET_STOP_PRE_SIGKILL) + socket_enter_stop_post(s, SOCKET_SUCCESS); + else if (state == SOCKET_FINAL_SIGTERM) + socket_enter_signal(s, SOCKET_FINAL_SIGKILL, SOCKET_SUCCESS); + else + socket_enter_dead(s, SOCKET_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); + + if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL) + socket_enter_stop_post(s, SOCKET_FAILURE_RESOURCES); + else + socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_stop_pre(Socket *s, SocketResult f) { + int r; + assert(s); + + if (f != SOCKET_SUCCESS) + s->result = f; + + socket_unwatch_control_pid(s); + s->control_command_id = SOCKET_EXEC_STOP_PRE; + s->control_command = s->exec_command[SOCKET_EXEC_STOP_PRE]; + + if (s->control_command) { + r = socket_spawn(s, s->control_command, &s->control_pid); + if (r < 0) + goto fail; + + socket_set_state(s, SOCKET_STOP_PRE); + } else + socket_enter_stop_post(s, SOCKET_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'stop-pre' task: %m"); + socket_enter_stop_post(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_listening(Socket *s) { + int r; + assert(s); + + r = socket_watch_fds(s); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to watch sockets: %m"); + goto fail; + } + + socket_set_state(s, SOCKET_LISTENING); + return; + +fail: + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_start_post(Socket *s) { + int r; + assert(s); + + socket_unwatch_control_pid(s); + s->control_command_id = SOCKET_EXEC_START_POST; + s->control_command = s->exec_command[SOCKET_EXEC_START_POST]; + + if (s->control_command) { + r = socket_spawn(s, s->control_command, &s->control_pid); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-post' task: %m"); + goto fail; + } + + socket_set_state(s, SOCKET_START_POST); + } else + socket_enter_listening(s); + + return; + +fail: + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_start_chown(Socket *s) { + int r; + + assert(s); + + r = socket_open_fds(s); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to listen on sockets: %m"); + goto fail; + } + + if (!isempty(s->user) || !isempty(s->group)) { + + socket_unwatch_control_pid(s); + s->control_command_id = SOCKET_EXEC_START_CHOWN; + s->control_command = NULL; + + r = socket_chown(s, &s->control_pid); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to fork 'start-chown' task: %m"); + goto fail; + } + + socket_set_state(s, SOCKET_START_CHOWN); + } else + socket_enter_start_post(s); + + return; + +fail: + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_start_pre(Socket *s) { + int r; + assert(s); + + socket_unwatch_control_pid(s); + s->control_command_id = SOCKET_EXEC_START_PRE; + s->control_command = s->exec_command[SOCKET_EXEC_START_PRE]; + + if (s->control_command) { + r = socket_spawn(s, s->control_command, &s->control_pid); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to run 'start-pre' task: %m"); + goto fail; + } + + socket_set_state(s, SOCKET_START_PRE); + } else + socket_enter_start_chown(s); + + return; + +fail: + socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); +} + +static void socket_enter_running(Socket *s, int cfd) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(s); + + /* We don't take connections anymore if we are supposed to + * shut down anyway */ + if (unit_stop_pending(UNIT(s))) { + + log_unit_debug(UNIT(s), "Suppressing connection request since unit stop is scheduled."); + + if (cfd >= 0) + safe_close(cfd); + else { + /* Flush all sockets by closing and reopening them */ + socket_close_fds(s); + + r = socket_open_fds(s); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to listen on sockets: %m"); + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + return; + } + + r = socket_watch_fds(s); + if (r < 0) { + log_unit_warning_errno(UNIT(s), r, "Failed to watch sockets: %m"); + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + } + } + + return; + } + + if (cfd < 0) { + Iterator i; + Unit *other; + bool pending = false; + + /* If there's already a start pending don't bother to + * do anything */ + SET_FOREACH(other, UNIT(s)->dependencies[UNIT_TRIGGERS], i) + if (unit_active_or_pending(other)) { + pending = true; + break; + } + + if (!pending) { + if (!UNIT_ISSET(s->service)) { + log_unit_error(UNIT(s), "Service to activate vanished, refusing activation."); + r = -ENOENT; + goto fail; + } + + r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT_DEREF(s->service), JOB_REPLACE, &error, NULL); + if (r < 0) + goto fail; + } + + socket_set_state(s, SOCKET_RUNNING); + } else { + _cleanup_free_ char *prefix = NULL, *instance = NULL, *name = NULL; + Service *service; + + if (s->n_connections >= s->max_connections) { + log_unit_warning(UNIT(s), "Too many incoming connections (%u)", s->n_connections); + safe_close(cfd); + return; + } + + r = socket_instantiate_service(s); + if (r < 0) + goto fail; + + r = instance_from_socket(cfd, s->n_accepted, &instance); + if (r < 0) { + if (r != -ENOTCONN) + goto fail; + + /* ENOTCONN is legitimate if TCP RST was received. + * This connection is over, but the socket unit lives on. */ + safe_close(cfd); + return; + } + + r = unit_name_to_prefix(UNIT(s)->id, &prefix); + if (r < 0) + goto fail; + + r = unit_name_build(prefix, instance, ".service", &name); + if (r < 0) + goto fail; + + r = unit_add_name(UNIT_DEREF(s->service), name); + if (r < 0) + goto fail; + + service = SERVICE(UNIT_DEREF(s->service)); + unit_ref_unset(&s->service); + s->n_accepted ++; + + UNIT(service)->no_gc = false; + + unit_choose_id(UNIT(service), name); + + r = service_set_socket_fd(service, cfd, s, s->selinux_context_from_net); + if (r < 0) + goto fail; + + cfd = -1; + s->n_connections ++; + + r = manager_add_job(UNIT(s)->manager, JOB_START, UNIT(service), JOB_REPLACE, &error, NULL); + if (r < 0) + goto fail; + + /* Notify clients about changed counters */ + unit_add_to_dbus_queue(UNIT(s)); + } + + return; + +fail: + log_unit_warning(UNIT(s), "Failed to queue service startup job (Maybe the service file is missing or not a %s unit?): %s", + cfd >= 0 ? "template" : "non-template", + bus_error_message(&error, r)); + + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + safe_close(cfd); +} + +static void socket_run_next(Socket *s) { + int r; + + assert(s); + assert(s->control_command); + assert(s->control_command->command_next); + + socket_unwatch_control_pid(s); + + s->control_command = s->control_command->command_next; + + r = socket_spawn(s, s->control_command, &s->control_pid); + if (r < 0) + goto fail; + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run next task: %m"); + + if (s->state == SOCKET_START_POST) + socket_enter_stop_pre(s, SOCKET_FAILURE_RESOURCES); + else if (s->state == SOCKET_STOP_POST) + socket_enter_dead(s, SOCKET_FAILURE_RESOURCES); + else + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_RESOURCES); +} + +static int socket_start(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + /* We cannot fulfill this request right now, try again later + * please! */ + if (IN_SET(s->state, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGKILL, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_POST, + SOCKET_FINAL_SIGTERM, + SOCKET_FINAL_SIGKILL)) + return -EAGAIN; + + /* Already on it! */ + if (IN_SET(s->state, + SOCKET_START_PRE, + SOCKET_START_CHOWN, + SOCKET_START_POST)) + return 0; + + /* Cannot run this without the service being around */ + if (UNIT_ISSET(s->service)) { + Service *service; + + service = SERVICE(UNIT_DEREF(s->service)); + + if (UNIT(service)->load_state != UNIT_LOADED) { + log_unit_error(u, "Socket service %s not loaded, refusing.", UNIT(service)->id); + return -ENOENT; + } + + /* If the service is already active we cannot start the + * socket */ + if (service->state != SERVICE_DEAD && + service->state != SERVICE_FAILED && + service->state != SERVICE_AUTO_RESTART) { + log_unit_error(u, "Socket service %s already active, refusing.", UNIT(service)->id); + return -EBUSY; + } + } + + assert(s->state == SOCKET_DEAD || s->state == SOCKET_FAILED); + + s->result = SOCKET_SUCCESS; + s->reset_cpu_usage = true; + + socket_enter_start_pre(s); + + return 1; +} + +static int socket_stop(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + /* Already on it */ + if (IN_SET(s->state, + SOCKET_STOP_PRE, + SOCKET_STOP_PRE_SIGTERM, + SOCKET_STOP_PRE_SIGKILL, + SOCKET_STOP_POST, + SOCKET_FINAL_SIGTERM, + SOCKET_FINAL_SIGKILL)) + return 0; + + /* If there's already something running we go directly into + * kill mode. */ + if (IN_SET(s->state, + SOCKET_START_PRE, + SOCKET_START_CHOWN, + SOCKET_START_POST)) { + socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_SUCCESS); + return -EAGAIN; + } + + assert(s->state == SOCKET_LISTENING || s->state == SOCKET_RUNNING); + + socket_enter_stop_pre(s, SOCKET_SUCCESS); + return 1; +} + +static int socket_serialize(Unit *u, FILE *f, FDSet *fds) { + Socket *s = SOCKET(u); + SocketPort *p; + int r; + + assert(u); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", socket_state_to_string(s->state)); + unit_serialize_item(u, f, "result", socket_result_to_string(s->result)); + unit_serialize_item_format(u, f, "n-accepted", "%u", s->n_accepted); + + if (s->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", PID_FMT, s->control_pid); + + if (s->control_command_id >= 0) + unit_serialize_item(u, f, "control-command", socket_exec_command_to_string(s->control_command_id)); + + LIST_FOREACH(port, p, s->ports) { + int copy; + + if (p->fd < 0) + continue; + + copy = fdset_put_dup(fds, p->fd); + if (copy < 0) + return copy; + + if (p->type == SOCKET_SOCKET) { + _cleanup_free_ char *t = NULL; + + r = socket_address_print(&p->address, &t); + if (r < 0) + return r; + + if (socket_address_family(&p->address) == AF_NETLINK) + unit_serialize_item_format(u, f, "netlink", "%i %s", copy, t); + else + unit_serialize_item_format(u, f, "socket", "%i %i %s", copy, p->address.type, t); + + } else if (p->type == SOCKET_SPECIAL) + unit_serialize_item_format(u, f, "special", "%i %s", copy, p->path); + else if (p->type == SOCKET_MQUEUE) + unit_serialize_item_format(u, f, "mqueue", "%i %s", copy, p->path); + else if (p->type == SOCKET_USB_FUNCTION) + unit_serialize_item_format(u, f, "ffs", "%i %s", copy, p->path); + else { + assert(p->type == SOCKET_FIFO); + unit_serialize_item_format(u, f, "fifo", "%i %s", copy, p->path); + } + } + + return 0; +} + +static int socket_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Socket *s = SOCKET(u); + + assert(u); + assert(key); + assert(value); + + if (streq(key, "state")) { + SocketState state; + + state = socket_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + s->deserialized_state = state; + } else if (streq(key, "result")) { + SocketResult f; + + f = socket_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != SOCKET_SUCCESS) + s->result = f; + + } else if (streq(key, "n-accepted")) { + unsigned k; + + if (safe_atou(value, &k) < 0) + log_unit_debug(u, "Failed to parse n-accepted value: %s", value); + else + s->n_accepted += k; + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse control-pid value: %s", value); + else + s->control_pid = pid; + } else if (streq(key, "control-command")) { + SocketExecCommand id; + + id = socket_exec_command_from_string(value); + if (id < 0) + log_unit_debug(u, "Failed to parse exec-command value: %s", value); + else { + s->control_command_id = id; + s->control_command = s->exec_command[id]; + } + } else if (streq(key, "fifo")) { + int fd, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse fifo value: %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (p->type == SOCKET_FIFO && + path_equal_or_files_same(p->path, value+skip)) + break; + + if (p) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else if (streq(key, "special")) { + int fd, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse special value: %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (p->type == SOCKET_SPECIAL && + path_equal_or_files_same(p->path, value+skip)) + break; + + if (p) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else if (streq(key, "mqueue")) { + int fd, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse mqueue value: %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (p->type == SOCKET_MQUEUE && + streq(p->path, value+skip)) + break; + + if (p) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else if (streq(key, "socket")) { + int fd, type, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %i %n", &fd, &type, &skip) < 2 || fd < 0 || type < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse socket value: %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (socket_address_is(&p->address, value+skip, type)) + break; + + if (p) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else if (streq(key, "netlink")) { + int fd, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse socket value: %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (socket_address_is_netlink(&p->address, value+skip)) + break; + + if (p) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else if (streq(key, "ffs")) { + int fd, skip = 0; + SocketPort *p; + + if (sscanf(value, "%i %n", &fd, &skip) < 1 || fd < 0 || !fdset_contains(fds, fd)) + log_unit_debug(u, "Failed to parse ffs value: %s", value); + else { + + LIST_FOREACH(port, p, s->ports) + if (p->type == SOCKET_USB_FUNCTION && + path_equal_or_files_same(p->path, value+skip)) + break; + + if (p) { + safe_close(p->fd); + p->fd = fdset_remove(fds, fd); + } + } + + } else + log_unit_debug(UNIT(s), "Unknown serialization key: %s", key); + + return 0; +} + +static void socket_distribute_fds(Unit *u, FDSet *fds) { + Socket *s = SOCKET(u); + SocketPort *p; + + assert(u); + + LIST_FOREACH(port, p, s->ports) { + Iterator i; + int fd; + + if (p->type != SOCKET_SOCKET) + continue; + + if (p->fd >= 0) + continue; + + FDSET_FOREACH(fd, fds, i) { + if (socket_address_matches_fd(&p->address, fd)) { + p->fd = fdset_remove(fds, fd); + s->deserialized_state = SOCKET_LISTENING; + break; + } + } + } +} + +_pure_ static UnitActiveState socket_active_state(Unit *u) { + assert(u); + + return state_translation_table[SOCKET(u)->state]; +} + +_pure_ static const char *socket_sub_state_to_string(Unit *u) { + assert(u); + + return socket_state_to_string(SOCKET(u)->state); +} + +const char* socket_port_type_to_string(SocketPort *p) { + + assert(p); + + switch (p->type) { + + case SOCKET_SOCKET: + + switch (p->address.type) { + + case SOCK_STREAM: + return "Stream"; + + case SOCK_DGRAM: + return "Datagram"; + + case SOCK_SEQPACKET: + return "SequentialPacket"; + + case SOCK_RAW: + if (socket_address_family(&p->address) == AF_NETLINK) + return "Netlink"; + + default: + return NULL; + } + + case SOCKET_SPECIAL: + return "Special"; + + case SOCKET_MQUEUE: + return "MessageQueue"; + + case SOCKET_FIFO: + return "FIFO"; + + case SOCKET_USB_FUNCTION: + return "USBFunction"; + + default: + return NULL; + } +} + +_pure_ static bool socket_check_gc(Unit *u) { + Socket *s = SOCKET(u); + + assert(u); + + return s->n_connections > 0; +} + +static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + SocketPort *p = userdata; + int cfd = -1; + + assert(p); + assert(fd >= 0); + + if (p->socket->state != SOCKET_LISTENING) + return 0; + + log_unit_debug(UNIT(p->socket), "Incoming traffic"); + + if (revents != EPOLLIN) { + + if (revents & EPOLLHUP) + log_unit_error(UNIT(p->socket), "Got POLLHUP on a listening socket. The service probably invoked shutdown() on it, and should better not do that."); + else + log_unit_error(UNIT(p->socket), "Got unexpected poll event (0x%x) on socket.", revents); + goto fail; + } + + if (p->socket->accept && + p->type == SOCKET_SOCKET && + socket_address_can_accept(&p->address)) { + + for (;;) { + + cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK); + if (cfd < 0) { + + if (errno == EINTR) + continue; + + log_unit_error_errno(UNIT(p->socket), errno, "Failed to accept socket: %m"); + goto fail; + } + + break; + } + + socket_apply_socket_options(p->socket, cfd); + } + + socket_enter_running(p->socket, cfd); + return 0; + +fail: + socket_enter_stop_pre(p->socket, SOCKET_FAILURE_RESOURCES); + return 0; +} + +static void socket_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Socket *s = SOCKET(u); + SocketResult f; + + assert(s); + assert(pid >= 0); + + if (pid != s->control_pid) + return; + + s->control_pid = 0; + + if (is_clean_exit(code, status, NULL)) + f = SOCKET_SUCCESS; + else if (code == CLD_EXITED) + f = SOCKET_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = SOCKET_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = SOCKET_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown sigchld code"); + + if (s->control_command) { + exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); + + if (s->control_command->ignore) + f = SOCKET_SUCCESS; + } + + log_unit_full(u, f == SOCKET_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, + "Control process exited, code=%s status=%i", + sigchld_code_to_string(code), status); + + if (f != SOCKET_SUCCESS) + s->result = f; + + if (s->control_command && + s->control_command->command_next && + f == SOCKET_SUCCESS) { + + log_unit_debug(u, "Running next command for state %s", socket_state_to_string(s->state)); + socket_run_next(s); + } else { + s->control_command = NULL; + s->control_command_id = _SOCKET_EXEC_COMMAND_INVALID; + + /* No further commands for this step, so let's figure + * out what to do next */ + + log_unit_debug(u, "Got final SIGCHLD for state %s", socket_state_to_string(s->state)); + + switch (s->state) { + + case SOCKET_START_PRE: + if (f == SOCKET_SUCCESS) + socket_enter_start_chown(s); + else + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, f); + break; + + case SOCKET_START_CHOWN: + if (f == SOCKET_SUCCESS) + socket_enter_start_post(s); + else + socket_enter_stop_pre(s, f); + break; + + case SOCKET_START_POST: + if (f == SOCKET_SUCCESS) + socket_enter_listening(s); + else + socket_enter_stop_pre(s, f); + break; + + case SOCKET_STOP_PRE: + case SOCKET_STOP_PRE_SIGTERM: + case SOCKET_STOP_PRE_SIGKILL: + socket_enter_stop_post(s, f); + break; + + case SOCKET_STOP_POST: + case SOCKET_FINAL_SIGTERM: + case SOCKET_FINAL_SIGKILL: + socket_enter_dead(s, f); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); +} + +static int socket_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { + Socket *s = SOCKET(userdata); + + assert(s); + assert(s->timer_event_source == source); + + switch (s->state) { + + case SOCKET_START_PRE: + log_unit_warning(UNIT(s), "Starting timed out. Terminating."); + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_START_CHOWN: + case SOCKET_START_POST: + log_unit_warning(UNIT(s), "Starting timed out. Stopping."); + socket_enter_stop_pre(s, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_STOP_PRE: + log_unit_warning(UNIT(s), "Stopping timed out. Terminating."); + socket_enter_signal(s, SOCKET_STOP_PRE_SIGTERM, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_STOP_PRE_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "Stopping timed out. Killing."); + socket_enter_signal(s, SOCKET_STOP_PRE_SIGKILL, SOCKET_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "Stopping timed out. Skipping SIGKILL. Ignoring."); + socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); + } + break; + + case SOCKET_STOP_PRE_SIGKILL: + log_unit_warning(UNIT(s), "Processes still around after SIGKILL. Ignoring."); + socket_enter_stop_post(s, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_STOP_POST: + log_unit_warning(UNIT(s), "Stopping timed out (2). Terminating."); + socket_enter_signal(s, SOCKET_FINAL_SIGTERM, SOCKET_FAILURE_TIMEOUT); + break; + + case SOCKET_FINAL_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "Stopping timed out (2). Killing."); + socket_enter_signal(s, SOCKET_FINAL_SIGKILL, SOCKET_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "Stopping timed out (2). Skipping SIGKILL. Ignoring."); + socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); + } + break; + + case SOCKET_FINAL_SIGKILL: + log_unit_warning(UNIT(s), "Still around after SIGKILL (2). Entering failed mode."); + socket_enter_dead(s, SOCKET_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } + + return 0; +} + +int socket_collect_fds(Socket *s, int **fds) { + int *rfds, k = 0, n = 0; + SocketPort *p; + + assert(s); + assert(fds); + + /* Called from the service code for requesting our fds */ + + LIST_FOREACH(port, p, s->ports) { + if (p->fd >= 0) + n++; + n += p->n_auxiliary_fds; + } + + if (n <= 0) { + *fds = NULL; + return 0; + } + + rfds = new(int, n); + if (!rfds) + return -ENOMEM; + + LIST_FOREACH(port, p, s->ports) { + int i; + + if (p->fd >= 0) + rfds[k++] = p->fd; + for (i = 0; i < p->n_auxiliary_fds; ++i) + rfds[k++] = p->auxiliary_fds[i]; + } + + assert(k == n); + + *fds = rfds; + return n; +} + +static void socket_reset_failed(Unit *u) { + Socket *s = SOCKET(u); + + assert(s); + + if (s->state == SOCKET_FAILED) + socket_set_state(s, SOCKET_DEAD); + + s->result = SOCKET_SUCCESS; +} + +void socket_connection_unref(Socket *s) { + assert(s); + + /* The service is dead. Yay! + * + * This is strictly for one-instance-per-connection + * services. */ + + assert(s->n_connections > 0); + s->n_connections--; + + log_unit_debug(UNIT(s), "One connection closed, %u left.", s->n_connections); +} + +static void socket_trigger_notify(Unit *u, Unit *other) { + Socket *s = SOCKET(u); + + assert(u); + assert(other); + + /* Don't propagate state changes from the service if we are + already down or accepting connections */ + if (!IN_SET(s->state, SOCKET_RUNNING, SOCKET_LISTENING) || s->accept) + return; + + if (other->start_limit_hit) { + socket_enter_stop_pre(s, SOCKET_FAILURE_SERVICE_START_LIMIT_HIT); + return; + } + + if (other->load_state != UNIT_LOADED || other->type != UNIT_SERVICE) + return; + + if (IN_SET(SERVICE(other)->state, + SERVICE_DEAD, SERVICE_FAILED, + SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, + SERVICE_AUTO_RESTART)) + socket_enter_listening(s); + + if (SERVICE(other)->state == SERVICE_RUNNING) + socket_set_state(s, SOCKET_RUNNING); +} + +static int socket_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + return unit_kill_common(u, who, signo, -1, SOCKET(u)->control_pid, error); +} + +static int socket_get_timeout(Unit *u, usec_t *timeout) { + Socket *s = SOCKET(u); + usec_t t; + int r; + + if (!s->timer_event_source) + return 0; + + r = sd_event_source_get_time(s->timer_event_source, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + return 0; + + *timeout = t; + return 1; +} + +char *socket_fdname(Socket *s) { + assert(s); + + /* Returns the name to use for $LISTEN_NAMES. If the user + * didn't specify anything specifically, use the socket unit's + * name as fallback. */ + + if (s->fdname) + return s->fdname; + + return UNIT(s)->id; +} + +static const char* const socket_exec_command_table[_SOCKET_EXEC_COMMAND_MAX] = { + [SOCKET_EXEC_START_PRE] = "StartPre", + [SOCKET_EXEC_START_CHOWN] = "StartChown", + [SOCKET_EXEC_START_POST] = "StartPost", + [SOCKET_EXEC_STOP_PRE] = "StopPre", + [SOCKET_EXEC_STOP_POST] = "StopPost" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_exec_command, SocketExecCommand); + +static const char* const socket_result_table[_SOCKET_RESULT_MAX] = { + [SOCKET_SUCCESS] = "success", + [SOCKET_FAILURE_RESOURCES] = "resources", + [SOCKET_FAILURE_TIMEOUT] = "timeout", + [SOCKET_FAILURE_EXIT_CODE] = "exit-code", + [SOCKET_FAILURE_SIGNAL] = "signal", + [SOCKET_FAILURE_CORE_DUMP] = "core-dump", + [SOCKET_FAILURE_SERVICE_START_LIMIT_HIT] = "service-start-limit-hit" +}; + +DEFINE_STRING_TABLE_LOOKUP(socket_result, SocketResult); + +const UnitVTable socket_vtable = { + .object_size = sizeof(Socket), + .exec_context_offset = offsetof(Socket, exec_context), + .cgroup_context_offset = offsetof(Socket, cgroup_context), + .kill_context_offset = offsetof(Socket, kill_context), + .exec_runtime_offset = offsetof(Socket, exec_runtime), + + .sections = + "Unit\0" + "Socket\0" + "Install\0", + .private_section = "Socket", + + .init = socket_init, + .done = socket_done, + .load = socket_load, + + .coldplug = socket_coldplug, + + .dump = socket_dump, + + .start = socket_start, + .stop = socket_stop, + + .kill = socket_kill, + + .get_timeout = socket_get_timeout, + + .serialize = socket_serialize, + .deserialize_item = socket_deserialize_item, + .distribute_fds = socket_distribute_fds, + + .active_state = socket_active_state, + .sub_state_to_string = socket_sub_state_to_string, + + .check_gc = socket_check_gc, + + .sigchld_event = socket_sigchld_event, + + .trigger_notify = socket_trigger_notify, + + .reset_failed = socket_reset_failed, + + .bus_vtable = bus_socket_vtable, + .bus_set_property = bus_socket_set_property, + .bus_commit_properties = bus_socket_commit_properties, + + .status_message_formats = { + /*.starting_stopping = { + [0] = "Starting socket %s...", + [1] = "Stopping socket %s...", + },*/ + .finished_start_job = { + [JOB_DONE] = "Listening on %s.", + [JOB_FAILED] = "Failed to listen on %s.", + [JOB_TIMEOUT] = "Timed out starting %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Closed %s.", + [JOB_FAILED] = "Failed stopping %s.", + [JOB_TIMEOUT] = "Timed out stopping %s.", + }, + }, +}; diff --git a/src/libcore/socket.h b/src/libcore/socket.h new file mode 100644 index 0000000000..b537b026a7 --- /dev/null +++ b/src/libcore/socket.h @@ -0,0 +1,181 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Socket Socket; + +#include "mount.h" +#include "service.h" +#include "socket-util.h" + +typedef enum SocketExecCommand { + SOCKET_EXEC_START_PRE, + SOCKET_EXEC_START_CHOWN, + SOCKET_EXEC_START_POST, + SOCKET_EXEC_STOP_PRE, + SOCKET_EXEC_STOP_POST, + _SOCKET_EXEC_COMMAND_MAX, + _SOCKET_EXEC_COMMAND_INVALID = -1 +} SocketExecCommand; + +typedef enum SocketType { + SOCKET_SOCKET, + SOCKET_FIFO, + SOCKET_SPECIAL, + SOCKET_MQUEUE, + SOCKET_USB_FUNCTION, + _SOCKET_FIFO_MAX, + _SOCKET_FIFO_INVALID = -1 +} SocketType; + +typedef enum SocketResult { + SOCKET_SUCCESS, + SOCKET_FAILURE_RESOURCES, + SOCKET_FAILURE_TIMEOUT, + SOCKET_FAILURE_EXIT_CODE, + SOCKET_FAILURE_SIGNAL, + SOCKET_FAILURE_CORE_DUMP, + SOCKET_FAILURE_SERVICE_START_LIMIT_HIT, + _SOCKET_RESULT_MAX, + _SOCKET_RESULT_INVALID = -1 +} SocketResult; + +typedef struct SocketPort { + Socket *socket; + + SocketType type; + int fd; + int *auxiliary_fds; + int n_auxiliary_fds; + + SocketAddress address; + char *path; + sd_event_source *event_source; + + LIST_FIELDS(struct SocketPort, port); +} SocketPort; + +struct Socket { + Unit meta; + + LIST_HEAD(SocketPort, ports); + + unsigned n_accepted; + unsigned n_connections; + unsigned max_connections; + + unsigned backlog; + unsigned keep_alive_cnt; + usec_t timeout_usec; + usec_t keep_alive_time; + usec_t keep_alive_interval; + usec_t defer_accept; + + ExecCommand* exec_command[_SOCKET_EXEC_COMMAND_MAX]; + ExecContext exec_context; + KillContext kill_context; + CGroupContext cgroup_context; + ExecRuntime *exec_runtime; + + /* For Accept=no sockets refers to the one service we'll + activate. For Accept=yes sockets is either NULL, or filled + when the next service we spawn. */ + UnitRef service; + + SocketState state, deserialized_state; + + sd_event_source *timer_event_source; + + ExecCommand* control_command; + SocketExecCommand control_command_id; + pid_t control_pid; + + mode_t directory_mode; + mode_t socket_mode; + + SocketResult result; + + char **symlinks; + + bool accept; + bool remove_on_stop; + bool writable; + + int socket_protocol; + + /* Socket options */ + bool keep_alive; + bool no_delay; + bool free_bind; + bool transparent; + bool broadcast; + bool pass_cred; + bool pass_sec; + + /* Only for INET6 sockets: issue IPV6_V6ONLY sockopt */ + SocketAddressBindIPv6Only bind_ipv6_only; + + int priority; + int mark; + size_t receive_buffer; + size_t send_buffer; + int ip_tos; + int ip_ttl; + size_t pipe_size; + char *bind_to_device; + char *tcp_congestion; + bool reuse_port; + long mq_maxmsg; + long mq_msgsize; + + char *smack; + char *smack_ip_in; + char *smack_ip_out; + + bool selinux_context_from_net; + + char *user, *group; + + bool reset_cpu_usage:1; + + char *fdname; +}; + +/* Called from the service code when collecting fds */ +int socket_collect_fds(Socket *s, int **fds); + +/* Called from the service code when a per-connection service ended */ +void socket_connection_unref(Socket *s); + +void socket_free_ports(Socket *s); + +int socket_instantiate_service(Socket *s); + +char *socket_fdname(Socket *s); + +extern const UnitVTable socket_vtable; + +const char* socket_exec_command_to_string(SocketExecCommand i) _const_; +SocketExecCommand socket_exec_command_from_string(const char *s) _pure_; + +const char* socket_result_to_string(SocketResult i) _const_; +SocketResult socket_result_from_string(const char *s) _pure_; + +const char* socket_port_type_to_string(SocketPort *p) _pure_; diff --git a/src/libcore/swap.c b/src/libcore/swap.c new file mode 100644 index 0000000000..1bf0c0a808 --- /dev/null +++ b/src/libcore/swap.c @@ -0,0 +1,1518 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "libudev.h" + +#include "alloc-util.h" +#include "dbus-swap.h" +#include "escape.h" +#include "exit-status.h" +#include "fd-util.h" +#include "formats-util.h" +#include "fstab-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" +#include "swap.h" +#include "udev-util.h" +#include "unit-name.h" +#include "unit.h" +#include "virt.h" + +static const UnitActiveState state_translation_table[_SWAP_STATE_MAX] = { + [SWAP_DEAD] = UNIT_INACTIVE, + [SWAP_ACTIVATING] = UNIT_ACTIVATING, + [SWAP_ACTIVATING_DONE] = UNIT_ACTIVE, + [SWAP_ACTIVE] = UNIT_ACTIVE, + [SWAP_DEACTIVATING] = UNIT_DEACTIVATING, + [SWAP_ACTIVATING_SIGTERM] = UNIT_DEACTIVATING, + [SWAP_ACTIVATING_SIGKILL] = UNIT_DEACTIVATING, + [SWAP_DEACTIVATING_SIGTERM] = UNIT_DEACTIVATING, + [SWAP_DEACTIVATING_SIGKILL] = UNIT_DEACTIVATING, + [SWAP_FAILED] = UNIT_FAILED +}; + +static int swap_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata); +static int swap_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata); + +static void swap_unset_proc_swaps(Swap *s) { + assert(s); + + if (!s->from_proc_swaps) + return; + + s->parameters_proc_swaps.what = mfree(s->parameters_proc_swaps.what); + + s->from_proc_swaps = false; +} + +static int swap_set_devnode(Swap *s, const char *devnode) { + Hashmap *swaps; + Swap *first; + int r; + + assert(s); + + r = hashmap_ensure_allocated(&UNIT(s)->manager->swaps_by_devnode, &string_hash_ops); + if (r < 0) + return r; + + swaps = UNIT(s)->manager->swaps_by_devnode; + + if (s->devnode) { + first = hashmap_get(swaps, s->devnode); + + LIST_REMOVE(same_devnode, first, s); + if (first) + hashmap_replace(swaps, first->devnode, first); + else + hashmap_remove(swaps, s->devnode); + + s->devnode = mfree(s->devnode); + } + + if (devnode) { + s->devnode = strdup(devnode); + if (!s->devnode) + return -ENOMEM; + + first = hashmap_get(swaps, s->devnode); + LIST_PREPEND(same_devnode, first, s); + + return hashmap_replace(swaps, first->devnode, first); + } + + return 0; +} + +static void swap_init(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + assert(UNIT(s)->load_state == UNIT_STUB); + + s->timeout_usec = u->manager->default_timeout_start_usec; + + s->exec_context.std_output = u->manager->default_std_output; + s->exec_context.std_error = u->manager->default_std_error; + + s->parameters_proc_swaps.priority = s->parameters_fragment.priority = -1; + + s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; + + u->ignore_on_isolate = true; +} + +static void swap_unwatch_control_pid(Swap *s) { + assert(s); + + if (s->control_pid <= 0) + return; + + unit_unwatch_pid(UNIT(s), s->control_pid); + s->control_pid = 0; +} + +static void swap_done(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + swap_unset_proc_swaps(s); + swap_set_devnode(s, NULL); + + s->what = mfree(s->what); + s->parameters_fragment.what = mfree(s->parameters_fragment.what); + s->parameters_fragment.options = mfree(s->parameters_fragment.options); + + s->exec_runtime = exec_runtime_unref(s->exec_runtime); + exec_command_done_array(s->exec_command, _SWAP_EXEC_COMMAND_MAX); + s->control_command = NULL; + + swap_unwatch_control_pid(s); + + s->timer_event_source = sd_event_source_unref(s->timer_event_source); +} + +static int swap_arm_timer(Swap *s, usec_t usec) { + int r; + + assert(s); + + if (s->timer_event_source) { + r = sd_event_source_set_time(s->timer_event_source, usec); + if (r < 0) + return r; + + return sd_event_source_set_enabled(s->timer_event_source, SD_EVENT_ONESHOT); + } + + if (usec == USEC_INFINITY) + return 0; + + r = sd_event_add_time( + UNIT(s)->manager->event, + &s->timer_event_source, + CLOCK_MONOTONIC, + usec, 0, + swap_dispatch_timer, s); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s->timer_event_source, "swap-timer"); + + return 0; +} + +static int swap_add_device_links(Swap *s) { + assert(s); + + if (!s->what) + return 0; + + if (!s->from_fragment) + return 0; + + if (is_device_path(s->what)) + return unit_add_node_link(UNIT(s), s->what, UNIT(s)->manager->running_as == MANAGER_SYSTEM, UNIT_BINDS_TO); + else + /* File based swap devices need to be ordered after + * systemd-remount-fs.service, since they might need a + * writable file system. */ + return unit_add_dependency_by_name(UNIT(s), UNIT_AFTER, SPECIAL_REMOUNT_FS_SERVICE, NULL, true); +} + +static int swap_add_default_dependencies(Swap *s) { + int r; + + assert(s); + + if (!UNIT(s)->default_dependencies) + return 0; + + if (UNIT(s)->manager->running_as != MANAGER_SYSTEM) + return 0; + + if (detect_container() > 0) + return 0; + + /* swap units generated for the swap dev links are missing the + * ordering dep against the swap target. */ + r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SWAP_TARGET, NULL, true); + if (r < 0) + return r; + + return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_UMOUNT_TARGET, NULL, true); +} + +static int swap_verify(Swap *s) { + _cleanup_free_ char *e = NULL; + int r; + + if (UNIT(s)->load_state != UNIT_LOADED) + return 0; + + r = unit_name_from_path(s->what, ".swap", &e); + if (r < 0) + return log_unit_error_errno(UNIT(s), r, "Failed to generate unit name from path: %m"); + + if (!unit_has_name(UNIT(s), e)) { + log_unit_error(UNIT(s), "Value of What= and unit name do not match, not loading."); + return -EINVAL; + } + + if (s->exec_context.pam_name && s->kill_context.kill_mode != KILL_CONTROL_GROUP) { + log_unit_error(UNIT(s), "Unit has PAM enabled. Kill mode must be set to 'control-group'. Refusing to load."); + return -EINVAL; + } + + return 0; +} + +static int swap_load_devnode(Swap *s) { + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + struct stat st; + const char *p; + + assert(s); + + if (stat(s->what, &st) < 0 || !S_ISBLK(st.st_mode)) + return 0; + + d = udev_device_new_from_devnum(UNIT(s)->manager->udev, 'b', st.st_rdev); + if (!d) + return 0; + + p = udev_device_get_devnode(d); + if (!p) + return 0; + + return swap_set_devnode(s, p); +} + +static int swap_load(Unit *u) { + int r; + Swap *s = SWAP(u); + + assert(s); + assert(u->load_state == UNIT_STUB); + + /* Load a .swap file */ + r = unit_load_fragment_and_dropin_optional(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + + if (UNIT(s)->fragment_path) + s->from_fragment = true; + + if (!s->what) { + if (s->parameters_fragment.what) + s->what = strdup(s->parameters_fragment.what); + else if (s->parameters_proc_swaps.what) + s->what = strdup(s->parameters_proc_swaps.what); + else { + r = unit_name_to_path(u->id, &s->what); + if (r < 0) + return r; + } + + if (!s->what) + return -ENOMEM; + } + + path_kill_slashes(s->what); + + if (!UNIT(s)->description) { + r = unit_set_description(u, s->what); + if (r < 0) + return r; + } + + r = unit_require_mounts_for(UNIT(s), s->what); + if (r < 0) + return r; + + r = swap_add_device_links(s); + if (r < 0) + return r; + + r = swap_load_devnode(s); + if (r < 0) + return r; + + r = unit_patch_contexts(u); + if (r < 0) + return r; + + r = unit_add_exec_dependencies(u, &s->exec_context); + if (r < 0) + return r; + + r = unit_set_default_slice(u); + if (r < 0) + return r; + + r = swap_add_default_dependencies(s); + if (r < 0) + return r; + } + + return swap_verify(s); +} + +static int swap_setup_unit( + Manager *m, + const char *what, + const char *what_proc_swaps, + int priority, + bool set_flags) { + + _cleanup_free_ char *e = NULL; + bool delete = false; + Unit *u = NULL; + int r; + SwapParameters *p; + + assert(m); + assert(what); + assert(what_proc_swaps); + + r = unit_name_from_path(what, ".swap", &e); + if (r < 0) + return log_unit_error_errno(u, r, "Failed to generate unit name from path: %m"); + + u = manager_get_unit(m, e); + + if (u && + SWAP(u)->from_proc_swaps && + !path_equal(SWAP(u)->parameters_proc_swaps.what, what_proc_swaps)) { + log_error("Swap %s appeared twice with different device paths %s and %s", e, SWAP(u)->parameters_proc_swaps.what, what_proc_swaps); + return -EEXIST; + } + + if (!u) { + delete = true; + + u = unit_new(m, sizeof(Swap)); + if (!u) + return log_oom(); + + r = unit_add_name(u, e); + if (r < 0) + goto fail; + + SWAP(u)->what = strdup(what); + if (!SWAP(u)->what) { + r = -ENOMEM; + goto fail; + } + + unit_add_to_load_queue(u); + } else + delete = false; + + p = &SWAP(u)->parameters_proc_swaps; + + if (!p->what) { + p->what = strdup(what_proc_swaps); + if (!p->what) { + r = -ENOMEM; + goto fail; + } + } + + if (set_flags) { + SWAP(u)->is_active = true; + SWAP(u)->just_activated = !SWAP(u)->from_proc_swaps; + } + + SWAP(u)->from_proc_swaps = true; + + p->priority = priority; + + unit_add_to_dbus_queue(u); + return 0; + +fail: + log_unit_warning_errno(u, r, "Failed to load swap unit: %m"); + + if (delete && u) + unit_free(u); + + return r; +} + +static int swap_process_new(Manager *m, const char *device, int prio, bool set_flags) { + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + const char *dn; + struct stat st; + int r; + + assert(m); + + r = swap_setup_unit(m, device, device, prio, set_flags); + if (r < 0) + return r; + + /* If this is a block device, then let's add duplicates for + * all other names of this block device */ + if (stat(device, &st) < 0 || !S_ISBLK(st.st_mode)) + return 0; + + d = udev_device_new_from_devnum(m->udev, 'b', st.st_rdev); + if (!d) + return 0; + + /* Add the main device node */ + dn = udev_device_get_devnode(d); + if (dn && !streq(dn, device)) + swap_setup_unit(m, dn, device, prio, set_flags); + + /* Add additional units for all symlinks */ + first = udev_device_get_devlinks_list_entry(d); + udev_list_entry_foreach(item, first) { + const char *p; + + /* Don't bother with the /dev/block links */ + p = udev_list_entry_get_name(item); + + if (streq(p, device)) + continue; + + if (path_startswith(p, "/dev/block/")) + continue; + + if (stat(p, &st) >= 0) + if (!S_ISBLK(st.st_mode) || + st.st_rdev != udev_device_get_devnum(d)) + continue; + + swap_setup_unit(m, p, device, prio, set_flags); + } + + return r; +} + +static void swap_set_state(Swap *s, SwapState state) { + SwapState old_state; + Swap *other; + + assert(s); + + old_state = s->state; + s->state = state; + + if (state != SWAP_ACTIVATING && + state != SWAP_ACTIVATING_SIGTERM && + state != SWAP_ACTIVATING_SIGKILL && + state != SWAP_ACTIVATING_DONE && + state != SWAP_DEACTIVATING && + state != SWAP_DEACTIVATING_SIGTERM && + state != SWAP_DEACTIVATING_SIGKILL) { + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + swap_unwatch_control_pid(s); + s->control_command = NULL; + s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; + } + + if (state != old_state) + log_unit_debug(UNIT(s), "Changed %s -> %s", swap_state_to_string(old_state), swap_state_to_string(state)); + + unit_notify(UNIT(s), state_translation_table[old_state], state_translation_table[state], true); + + /* If there other units for the same device node have a job + queued it might be worth checking again if it is runnable + now. This is necessary, since swap_start() refuses + operation with EAGAIN if there's already another job for + the same device node queued. */ + LIST_FOREACH_OTHERS(same_devnode, other, s) + if (UNIT(other)->job) + job_add_to_run_queue(UNIT(other)->job); +} + +static int swap_coldplug(Unit *u) { + Swap *s = SWAP(u); + SwapState new_state = SWAP_DEAD; + int r; + + assert(s); + assert(s->state == SWAP_DEAD); + + if (s->deserialized_state != s->state) + new_state = s->deserialized_state; + else if (s->from_proc_swaps) + new_state = SWAP_ACTIVE; + + if (new_state == s->state) + return 0; + + if (s->control_pid > 0 && + pid_is_unwaited(s->control_pid) && + IN_SET(new_state, + SWAP_ACTIVATING, + SWAP_ACTIVATING_SIGTERM, + SWAP_ACTIVATING_SIGKILL, + SWAP_ACTIVATING_DONE, + SWAP_DEACTIVATING, + SWAP_DEACTIVATING_SIGTERM, + SWAP_DEACTIVATING_SIGKILL)) { + + r = unit_watch_pid(UNIT(s), s->control_pid); + if (r < 0) + return r; + + r = swap_arm_timer(s, usec_add(u->state_change_timestamp.monotonic, s->timeout_usec)); + if (r < 0) + return r; + } + + swap_set_state(s, new_state); + return 0; +} + +static void swap_dump(Unit *u, FILE *f, const char *prefix) { + Swap *s = SWAP(u); + SwapParameters *p; + + assert(s); + assert(f); + + if (s->from_proc_swaps) + p = &s->parameters_proc_swaps; + else if (s->from_fragment) + p = &s->parameters_fragment; + else + p = NULL; + + fprintf(f, + "%sSwap State: %s\n" + "%sResult: %s\n" + "%sWhat: %s\n" + "%sFrom /proc/swaps: %s\n" + "%sFrom fragment: %s\n", + prefix, swap_state_to_string(s->state), + prefix, swap_result_to_string(s->result), + prefix, s->what, + prefix, yes_no(s->from_proc_swaps), + prefix, yes_no(s->from_fragment)); + + if (s->devnode) + fprintf(f, "%sDevice Node: %s\n", prefix, s->devnode); + + if (p) + fprintf(f, + "%sPriority: %i\n" + "%sOptions: %s\n", + prefix, p->priority, + prefix, strempty(p->options)); + + if (s->control_pid > 0) + fprintf(f, + "%sControl PID: "PID_FMT"\n", + prefix, s->control_pid); + + exec_context_dump(&s->exec_context, f, prefix); + kill_context_dump(&s->kill_context, f, prefix); +} + +static int swap_spawn(Swap *s, ExecCommand *c, pid_t *_pid) { + pid_t pid; + int r; + ExecParameters exec_params = { + .apply_permissions = true, + .apply_chroot = true, + .apply_tty_stdin = true, + .bus_endpoint_fd = -1, + .stdin_fd = -1, + .stdout_fd = -1, + .stderr_fd = -1, + }; + + assert(s); + assert(c); + assert(_pid); + + (void) unit_realize_cgroup(UNIT(s)); + if (s->reset_cpu_usage) { + (void) unit_reset_cpu_usage(UNIT(s)); + s->reset_cpu_usage = false; + } + + r = unit_setup_exec_runtime(UNIT(s)); + if (r < 0) + goto fail; + + r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); + if (r < 0) + goto fail; + + exec_params.environment = UNIT(s)->manager->environment; + exec_params.confirm_spawn = UNIT(s)->manager->confirm_spawn; + exec_params.cgroup_supported = UNIT(s)->manager->cgroup_supported; + exec_params.cgroup_path = UNIT(s)->cgroup_path; + exec_params.cgroup_delegate = s->cgroup_context.delegate; + exec_params.runtime_prefix = manager_get_runtime_prefix(UNIT(s)->manager); + + r = exec_spawn(UNIT(s), + c, + &s->exec_context, + &exec_params, + s->exec_runtime, + &pid); + if (r < 0) + goto fail; + + r = unit_watch_pid(UNIT(s), pid); + if (r < 0) + /* FIXME: we need to do something here */ + goto fail; + + *_pid = pid; + + return 0; + +fail: + s->timer_event_source = sd_event_source_unref(s->timer_event_source); + return r; +} + +static void swap_enter_dead(Swap *s, SwapResult f) { + assert(s); + + if (f != SWAP_SUCCESS) + s->result = f; + + 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); +} + +static void swap_enter_active(Swap *s, SwapResult f) { + assert(s); + + if (f != SWAP_SUCCESS) + s->result = f; + + swap_set_state(s, SWAP_ACTIVE); +} + +static void swap_enter_signal(Swap *s, SwapState state, SwapResult f) { + int r; + + assert(s); + + if (f != SWAP_SUCCESS) + s->result = f; + + r = unit_kill_context( + UNIT(s), + &s->kill_context, + (state != SWAP_ACTIVATING_SIGTERM && state != SWAP_DEACTIVATING_SIGTERM) ? + KILL_KILL : KILL_TERMINATE, + -1, + s->control_pid, + false); + if (r < 0) + goto fail; + + if (r > 0) { + r = swap_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_usec)); + if (r < 0) + goto fail; + + swap_set_state(s, state); + } else if (state == SWAP_ACTIVATING_SIGTERM) + swap_enter_signal(s, SWAP_ACTIVATING_SIGKILL, SWAP_SUCCESS); + else if (state == SWAP_DEACTIVATING_SIGTERM) + swap_enter_signal(s, SWAP_DEACTIVATING_SIGKILL, SWAP_SUCCESS); + else + swap_enter_dead(s, SWAP_SUCCESS); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m"); + swap_enter_dead(s, SWAP_FAILURE_RESOURCES); +} + +static void swap_enter_activating(Swap *s) { + _cleanup_free_ char *opts = NULL; + int r; + + assert(s); + + s->control_command_id = SWAP_EXEC_ACTIVATE; + s->control_command = s->exec_command + SWAP_EXEC_ACTIVATE; + + if (s->from_fragment) { + int priority = -1; + + r = fstab_find_pri(s->parameters_fragment.options, &priority); + if (r < 0) + log_warning_errno(r, "Failed to parse swap priority \"%s\", ignoring: %m", s->parameters_fragment.options); + else if (r == 1 && s->parameters_fragment.priority >= 0) + log_warning("Duplicate swap priority configuration by Priority and Options fields."); + + if (r <= 0 && s->parameters_fragment.priority >= 0) { + if (s->parameters_fragment.options) + r = asprintf(&opts, "%s,pri=%i", s->parameters_fragment.options, s->parameters_fragment.priority); + else + r = asprintf(&opts, "pri=%i", s->parameters_fragment.priority); + if (r < 0) + goto fail; + } + } + + r = exec_command_set(s->control_command, "/sbin/swapon", NULL); + if (r < 0) + goto fail; + + if (s->parameters_fragment.options || opts) { + r = exec_command_append(s->control_command, "-o", + opts ? : s->parameters_fragment.options, NULL); + if (r < 0) + goto fail; + } + + r = exec_command_append(s->control_command, s->what, NULL); + if (r < 0) + goto fail; + + swap_unwatch_control_pid(s); + + r = swap_spawn(s, s->control_command, &s->control_pid); + if (r < 0) + goto fail; + + swap_set_state(s, SWAP_ACTIVATING); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'swapon' task: %m"); + swap_enter_dead(s, SWAP_FAILURE_RESOURCES); +} + +static void swap_enter_deactivating(Swap *s) { + int r; + + assert(s); + + s->control_command_id = SWAP_EXEC_DEACTIVATE; + s->control_command = s->exec_command + SWAP_EXEC_DEACTIVATE; + + r = exec_command_set(s->control_command, + "/sbin/swapoff", + s->what, + NULL); + if (r < 0) + goto fail; + + swap_unwatch_control_pid(s); + + r = swap_spawn(s, s->control_command, &s->control_pid); + if (r < 0) + goto fail; + + swap_set_state(s, SWAP_DEACTIVATING); + + return; + +fail: + log_unit_warning_errno(UNIT(s), r, "Failed to run 'swapoff' task: %m"); + swap_enter_active(s, SWAP_FAILURE_RESOURCES); +} + +static int swap_start(Unit *u) { + Swap *s = SWAP(u), *other; + + assert(s); + + /* We cannot fulfill this request right now, try again later + * please! */ + + if (s->state == SWAP_DEACTIVATING || + s->state == SWAP_DEACTIVATING_SIGTERM || + s->state == SWAP_DEACTIVATING_SIGKILL || + s->state == SWAP_ACTIVATING_SIGTERM || + s->state == SWAP_ACTIVATING_SIGKILL) + return -EAGAIN; + + if (s->state == SWAP_ACTIVATING) + return 0; + + assert(s->state == SWAP_DEAD || s->state == SWAP_FAILED); + + if (detect_container() > 0) + return -EPERM; + + /* If there's a job for another swap unit for the same node + * running, then let's not dispatch this one for now, and wait + * until that other job has finished. */ + LIST_FOREACH_OTHERS(same_devnode, other, s) + if (UNIT(other)->job && UNIT(other)->job->state == JOB_RUNNING) + return -EAGAIN; + + s->result = SWAP_SUCCESS; + s->reset_cpu_usage = true; + + swap_enter_activating(s); + return 1; +} + +static int swap_stop(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + if (s->state == SWAP_DEACTIVATING || + s->state == SWAP_DEACTIVATING_SIGTERM || + s->state == SWAP_DEACTIVATING_SIGKILL || + s->state == SWAP_ACTIVATING_SIGTERM || + s->state == SWAP_ACTIVATING_SIGKILL) + return 0; + + assert(s->state == SWAP_ACTIVATING || + s->state == SWAP_ACTIVATING_DONE || + s->state == SWAP_ACTIVE); + + if (detect_container() > 0) + return -EPERM; + + swap_enter_deactivating(s); + return 1; +} + +static int swap_serialize(Unit *u, FILE *f, FDSet *fds) { + Swap *s = SWAP(u); + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", swap_state_to_string(s->state)); + unit_serialize_item(u, f, "result", swap_result_to_string(s->result)); + + if (s->control_pid > 0) + unit_serialize_item_format(u, f, "control-pid", PID_FMT, s->control_pid); + + if (s->control_command_id >= 0) + unit_serialize_item(u, f, "control-command", swap_exec_command_to_string(s->control_command_id)); + + return 0; +} + +static int swap_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Swap *s = SWAP(u); + + assert(s); + assert(fds); + + if (streq(key, "state")) { + SwapState state; + + state = swap_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + s->deserialized_state = state; + } else if (streq(key, "result")) { + SwapResult f; + + f = swap_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != SWAP_SUCCESS) + s->result = f; + } else if (streq(key, "control-pid")) { + pid_t pid; + + if (parse_pid(value, &pid) < 0) + log_unit_debug(u, "Failed to parse control-pid value: %s", value); + else + s->control_pid = pid; + + } else if (streq(key, "control-command")) { + SwapExecCommand id; + + id = swap_exec_command_from_string(value); + if (id < 0) + log_unit_debug(u, "Failed to parse exec-command value: %s", value); + else { + s->control_command_id = id; + s->control_command = s->exec_command + id; + } + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +_pure_ static UnitActiveState swap_active_state(Unit *u) { + assert(u); + + return state_translation_table[SWAP(u)->state]; +} + +_pure_ static const char *swap_sub_state_to_string(Unit *u) { + assert(u); + + return swap_state_to_string(SWAP(u)->state); +} + +_pure_ static bool swap_check_gc(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + return s->from_proc_swaps; +} + +static void swap_sigchld_event(Unit *u, pid_t pid, int code, int status) { + Swap *s = SWAP(u); + SwapResult f; + + assert(s); + assert(pid >= 0); + + if (pid != s->control_pid) + return; + + s->control_pid = 0; + + if (is_clean_exit(code, status, NULL)) + f = SWAP_SUCCESS; + else if (code == CLD_EXITED) + f = SWAP_FAILURE_EXIT_CODE; + else if (code == CLD_KILLED) + f = SWAP_FAILURE_SIGNAL; + else if (code == CLD_DUMPED) + f = SWAP_FAILURE_CORE_DUMP; + else + assert_not_reached("Unknown code"); + + if (f != SWAP_SUCCESS) + s->result = f; + + if (s->control_command) { + exec_status_exit(&s->control_command->exec_status, &s->exec_context, pid, code, status); + + s->control_command = NULL; + s->control_command_id = _SWAP_EXEC_COMMAND_INVALID; + } + + log_unit_full(u, f == SWAP_SUCCESS ? LOG_DEBUG : LOG_NOTICE, 0, + "Swap process exited, code=%s status=%i", sigchld_code_to_string(code), status); + + switch (s->state) { + + case SWAP_ACTIVATING: + case SWAP_ACTIVATING_DONE: + case SWAP_ACTIVATING_SIGTERM: + case SWAP_ACTIVATING_SIGKILL: + + if (f == SWAP_SUCCESS) + swap_enter_active(s, f); + else + swap_enter_dead(s, f); + break; + + case SWAP_DEACTIVATING: + case SWAP_DEACTIVATING_SIGKILL: + case SWAP_DEACTIVATING_SIGTERM: + + swap_enter_dead(s, f); + break; + + default: + assert_not_reached("Uh, control process died at wrong time."); + } + + /* Notify clients about changed exit status */ + unit_add_to_dbus_queue(u); +} + +static int swap_dispatch_timer(sd_event_source *source, usec_t usec, void *userdata) { + Swap *s = SWAP(userdata); + + assert(s); + assert(s->timer_event_source == source); + + switch (s->state) { + + case SWAP_ACTIVATING: + case SWAP_ACTIVATING_DONE: + log_unit_warning(UNIT(s), "Activation timed out. Stopping."); + swap_enter_signal(s, SWAP_ACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); + break; + + case SWAP_DEACTIVATING: + log_unit_warning(UNIT(s), "Deactivation timed out. Stopping."); + swap_enter_signal(s, SWAP_DEACTIVATING_SIGTERM, SWAP_FAILURE_TIMEOUT); + break; + + case SWAP_ACTIVATING_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "Activation timed out. Killing."); + swap_enter_signal(s, SWAP_ACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "Activation timed out. Skipping SIGKILL. Ignoring."); + swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); + } + break; + + case SWAP_DEACTIVATING_SIGTERM: + if (s->kill_context.send_sigkill) { + log_unit_warning(UNIT(s), "Deactivation timed out. Killing."); + swap_enter_signal(s, SWAP_DEACTIVATING_SIGKILL, SWAP_FAILURE_TIMEOUT); + } else { + log_unit_warning(UNIT(s), "Deactivation timed out. Skipping SIGKILL. Ignoring."); + swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); + } + break; + + case SWAP_ACTIVATING_SIGKILL: + case SWAP_DEACTIVATING_SIGKILL: + log_unit_warning(UNIT(s), "Swap process still around after SIGKILL. Ignoring."); + swap_enter_dead(s, SWAP_FAILURE_TIMEOUT); + break; + + default: + assert_not_reached("Timeout at wrong time."); + } + + return 0; +} + +static int swap_load_proc_swaps(Manager *m, bool set_flags) { + unsigned i; + int r = 0; + + assert(m); + + rewind(m->proc_swaps); + + (void) fscanf(m->proc_swaps, "%*s %*s %*s %*s %*s\n"); + + for (i = 1;; i++) { + _cleanup_free_ char *dev = NULL, *d = NULL; + int prio = 0, k; + + k = fscanf(m->proc_swaps, + "%ms " /* device/file */ + "%*s " /* type of swap */ + "%*s " /* swap size */ + "%*s " /* used */ + "%i\n", /* priority */ + &dev, &prio); + if (k != 2) { + if (k == EOF) + break; + + log_warning("Failed to parse /proc/swaps:%u.", i); + continue; + } + + if (cunescape(dev, UNESCAPE_RELAX, &d) < 0) + return log_oom(); + + device_found_node(m, d, true, DEVICE_FOUND_SWAP, set_flags); + + k = swap_process_new(m, d, prio, set_flags); + if (k < 0) + r = k; + } + + return r; +} + +static int swap_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + Unit *u; + int r; + + assert(m); + assert(revents & EPOLLPRI); + + r = swap_load_proc_swaps(m, true); + if (r < 0) { + log_error_errno(r, "Failed to reread /proc/swaps: %m"); + + /* Reset flags, just in case, for late calls */ + LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_SWAP]) { + Swap *swap = SWAP(u); + + swap->is_active = swap->just_activated = false; + } + + return 0; + } + + manager_dispatch_load_queue(m); + + LIST_FOREACH(units_by_type, u, m->units_by_type[UNIT_SWAP]) { + Swap *swap = SWAP(u); + + if (!swap->is_active) { + /* This has just been deactivated */ + + swap_unset_proc_swaps(swap); + + switch (swap->state) { + + case SWAP_ACTIVE: + swap_enter_dead(swap, SWAP_SUCCESS); + break; + + default: + /* Fire again */ + swap_set_state(swap, swap->state); + break; + } + + if (swap->what) + device_found_node(m, swap->what, false, DEVICE_FOUND_SWAP, true); + + } else if (swap->just_activated) { + + /* New swap entry */ + + switch (swap->state) { + + case SWAP_DEAD: + case SWAP_FAILED: + swap_enter_active(swap, SWAP_SUCCESS); + break; + + case SWAP_ACTIVATING: + swap_set_state(swap, SWAP_ACTIVATING_DONE); + break; + + default: + /* Nothing really changed, but let's + * issue an notification call + * nonetheless, in case somebody is + * waiting for this. */ + swap_set_state(swap, swap->state); + break; + } + } + + /* Reset the flags for later calls */ + swap->is_active = swap->just_activated = false; + } + + return 1; +} + +static Unit *swap_following(Unit *u) { + Swap *s = SWAP(u); + Swap *other, *first = NULL; + + assert(s); + + /* If the user configured the swap through /etc/fstab or + * a device unit, follow that. */ + + if (s->from_fragment) + return NULL; + + LIST_FOREACH_OTHERS(same_devnode, other, s) + if (other->from_fragment) + return UNIT(other); + + /* Otherwise, make everybody follow the unit that's named after + * the swap device in the kernel */ + + if (streq_ptr(s->what, s->devnode)) + return NULL; + + LIST_FOREACH_AFTER(same_devnode, other, s) + if (streq_ptr(other->what, other->devnode)) + return UNIT(other); + + LIST_FOREACH_BEFORE(same_devnode, other, s) { + if (streq_ptr(other->what, other->devnode)) + return UNIT(other); + + first = other; + } + + /* Fall back to the first on the list */ + return UNIT(first); +} + +static int swap_following_set(Unit *u, Set **_set) { + Swap *s = SWAP(u), *other; + Set *set; + int r; + + assert(s); + assert(_set); + + if (LIST_JUST_US(same_devnode, s)) { + *_set = NULL; + return 0; + } + + set = set_new(NULL); + if (!set) + return -ENOMEM; + + LIST_FOREACH_OTHERS(same_devnode, other, s) { + r = set_put(set, other); + if (r < 0) + goto fail; + } + + *_set = set; + return 1; + +fail: + set_free(set); + return r; +} + +static void swap_shutdown(Manager *m) { + assert(m); + + m->swap_event_source = sd_event_source_unref(m->swap_event_source); + + m->proc_swaps = safe_fclose(m->proc_swaps); + + m->swaps_by_devnode = hashmap_free(m->swaps_by_devnode); +} + +static void swap_enumerate(Manager *m) { + int r; + + assert(m); + + if (!m->proc_swaps) { + m->proc_swaps = fopen("/proc/swaps", "re"); + if (!m->proc_swaps) { + if (errno == ENOENT) + log_debug("Not swap enabled, skipping enumeration"); + else + log_error_errno(errno, "Failed to open /proc/swaps: %m"); + + return; + } + + r = sd_event_add_io(m->event, &m->swap_event_source, fileno(m->proc_swaps), EPOLLPRI, swap_dispatch_io, m); + if (r < 0) { + log_error_errno(r, "Failed to watch /proc/swaps: %m"); + goto fail; + } + + /* Dispatch this before we dispatch SIGCHLD, so that + * we always get the events from /proc/swaps before + * the SIGCHLD of /sbin/swapon. */ + r = sd_event_source_set_priority(m->swap_event_source, -10); + if (r < 0) { + log_error_errno(r, "Failed to change /proc/swaps priority: %m"); + goto fail; + } + + (void) sd_event_source_set_description(m->swap_event_source, "swap-proc"); + } + + r = swap_load_proc_swaps(m, false); + if (r < 0) + goto fail; + + return; + +fail: + swap_shutdown(m); +} + +int swap_process_device_new(Manager *m, struct udev_device *dev) { + struct udev_list_entry *item = NULL, *first = NULL; + _cleanup_free_ char *e = NULL; + const char *dn; + Swap *s; + int r = 0; + + assert(m); + assert(dev); + + dn = udev_device_get_devnode(dev); + if (!dn) + return 0; + + r = unit_name_from_path(dn, ".swap", &e); + if (r < 0) + return r; + + s = hashmap_get(m->units, e); + if (s) + r = swap_set_devnode(s, dn); + + first = udev_device_get_devlinks_list_entry(dev); + udev_list_entry_foreach(item, first) { + _cleanup_free_ char *n = NULL; + int q; + + q = unit_name_from_path(udev_list_entry_get_name(item), ".swap", &n); + if (q < 0) + return q; + + s = hashmap_get(m->units, n); + if (s) { + q = swap_set_devnode(s, dn); + if (q < 0) + r = q; + } + } + + return r; +} + +int swap_process_device_remove(Manager *m, struct udev_device *dev) { + const char *dn; + int r = 0; + Swap *s; + + dn = udev_device_get_devnode(dev); + if (!dn) + return 0; + + while ((s = hashmap_get(m->swaps_by_devnode, dn))) { + int q; + + q = swap_set_devnode(s, NULL); + if (q < 0) + r = q; + } + + return r; +} + +static void swap_reset_failed(Unit *u) { + Swap *s = SWAP(u); + + assert(s); + + if (s->state == SWAP_FAILED) + swap_set_state(s, SWAP_DEAD); + + s->result = SWAP_SUCCESS; +} + +static int swap_kill(Unit *u, KillWho who, int signo, sd_bus_error *error) { + return unit_kill_common(u, who, signo, -1, SWAP(u)->control_pid, error); +} + +static int swap_get_timeout(Unit *u, usec_t *timeout) { + Swap *s = SWAP(u); + usec_t t; + int r; + + if (!s->timer_event_source) + return 0; + + r = sd_event_source_get_time(s->timer_event_source, &t); + if (r < 0) + return r; + if (t == USEC_INFINITY) + return 0; + + *timeout = t; + return 1; +} + +static bool swap_supported(void) { + static int supported = -1; + + /* If swap support is not available in the kernel, or we are + * running in a container we don't support swap units, and any + * attempts to starting one should fail immediately. */ + + if (supported < 0) + supported = + access("/proc/swaps", F_OK) >= 0 && + detect_container() <= 0; + + return supported; +} + +static const char* const swap_exec_command_table[_SWAP_EXEC_COMMAND_MAX] = { + [SWAP_EXEC_ACTIVATE] = "ExecActivate", + [SWAP_EXEC_DEACTIVATE] = "ExecDeactivate", +}; + +DEFINE_STRING_TABLE_LOOKUP(swap_exec_command, SwapExecCommand); + +static const char* const swap_result_table[_SWAP_RESULT_MAX] = { + [SWAP_SUCCESS] = "success", + [SWAP_FAILURE_RESOURCES] = "resources", + [SWAP_FAILURE_TIMEOUT] = "timeout", + [SWAP_FAILURE_EXIT_CODE] = "exit-code", + [SWAP_FAILURE_SIGNAL] = "signal", + [SWAP_FAILURE_CORE_DUMP] = "core-dump" +}; + +DEFINE_STRING_TABLE_LOOKUP(swap_result, SwapResult); + +const UnitVTable swap_vtable = { + .object_size = sizeof(Swap), + .exec_context_offset = offsetof(Swap, exec_context), + .cgroup_context_offset = offsetof(Swap, cgroup_context), + .kill_context_offset = offsetof(Swap, kill_context), + .exec_runtime_offset = offsetof(Swap, exec_runtime), + + .sections = + "Unit\0" + "Swap\0" + "Install\0", + .private_section = "Swap", + + .no_alias = true, + .no_instances = true, + + .init = swap_init, + .load = swap_load, + .done = swap_done, + + .coldplug = swap_coldplug, + + .dump = swap_dump, + + .start = swap_start, + .stop = swap_stop, + + .kill = swap_kill, + + .get_timeout = swap_get_timeout, + + .serialize = swap_serialize, + .deserialize_item = swap_deserialize_item, + + .active_state = swap_active_state, + .sub_state_to_string = swap_sub_state_to_string, + + .check_gc = swap_check_gc, + + .sigchld_event = swap_sigchld_event, + + .reset_failed = swap_reset_failed, + + .bus_vtable = bus_swap_vtable, + .bus_set_property = bus_swap_set_property, + .bus_commit_properties = bus_swap_commit_properties, + + .following = swap_following, + .following_set = swap_following_set, + + .enumerate = swap_enumerate, + .shutdown = swap_shutdown, + .supported = swap_supported, + + .status_message_formats = { + .starting_stopping = { + [0] = "Activating swap %s...", + [1] = "Deactivating swap %s...", + }, + .finished_start_job = { + [JOB_DONE] = "Activated swap %s.", + [JOB_FAILED] = "Failed to activate swap %s.", + [JOB_TIMEOUT] = "Timed out activating swap %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Deactivated swap %s.", + [JOB_FAILED] = "Failed deactivating swap %s.", + [JOB_TIMEOUT] = "Timed out deactivating swap %s.", + }, + }, +}; diff --git a/src/libcore/swap.h b/src/libcore/swap.h new file mode 100644 index 0000000000..ac7a63d81b --- /dev/null +++ b/src/libcore/swap.h @@ -0,0 +1,109 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2010 Maarten Lankhorst + + 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 . +***/ + +#include "libudev.h" + +typedef struct Swap Swap; + +typedef enum SwapExecCommand { + SWAP_EXEC_ACTIVATE, + SWAP_EXEC_DEACTIVATE, + _SWAP_EXEC_COMMAND_MAX, + _SWAP_EXEC_COMMAND_INVALID = -1 +} SwapExecCommand; + +typedef enum SwapResult { + SWAP_SUCCESS, + SWAP_FAILURE_RESOURCES, + SWAP_FAILURE_TIMEOUT, + SWAP_FAILURE_EXIT_CODE, + SWAP_FAILURE_SIGNAL, + SWAP_FAILURE_CORE_DUMP, + _SWAP_RESULT_MAX, + _SWAP_RESULT_INVALID = -1 +} SwapResult; + +typedef struct SwapParameters { + char *what; + char *options; + int priority; +} SwapParameters; + +struct Swap { + Unit meta; + + char *what; + + /* If the device has already shown up, this is the device + * node, which might be different from what, due to + * symlinks */ + char *devnode; + + SwapParameters parameters_proc_swaps; + SwapParameters parameters_fragment; + + bool from_proc_swaps:1; + bool from_fragment:1; + + /* Used while looking for swaps that vanished or got added + * from/to /proc/swaps */ + bool is_active:1; + bool just_activated:1; + + bool reset_cpu_usage:1; + + SwapResult result; + + usec_t timeout_usec; + + ExecCommand exec_command[_SWAP_EXEC_COMMAND_MAX]; + ExecContext exec_context; + KillContext kill_context; + CGroupContext cgroup_context; + + ExecRuntime *exec_runtime; + + SwapState state, deserialized_state; + + ExecCommand* control_command; + SwapExecCommand control_command_id; + pid_t control_pid; + + sd_event_source *timer_event_source; + + /* In order to be able to distinguish dependencies on + different device nodes we might end up creating multiple + devices for the same swap. We chain them up here. */ + + LIST_FIELDS(struct Swap, same_devnode); +}; + +extern const UnitVTable swap_vtable; + +int swap_process_device_new(Manager *m, struct udev_device *dev); +int swap_process_device_remove(Manager *m, struct udev_device *dev); + +const char* swap_exec_command_to_string(SwapExecCommand i) _const_; +SwapExecCommand swap_exec_command_from_string(const char *s) _pure_; + +const char* swap_result_to_string(SwapResult i) _const_; +SwapResult swap_result_from_string(const char *s) _pure_; diff --git a/src/libcore/target.c b/src/libcore/target.c new file mode 100644 index 0000000000..61a91aad07 --- /dev/null +++ b/src/libcore/target.c @@ -0,0 +1,223 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "dbus-target.h" +#include "log.h" +#include "special.h" +#include "string-util.h" +#include "unit-name.h" +#include "unit.h" +#include "target.h" + +static const UnitActiveState state_translation_table[_TARGET_STATE_MAX] = { + [TARGET_DEAD] = UNIT_INACTIVE, + [TARGET_ACTIVE] = UNIT_ACTIVE +}; + +static void target_set_state(Target *t, TargetState state) { + TargetState old_state; + assert(t); + + old_state = t->state; + t->state = state; + + if (state != old_state) + log_debug("%s changed %s -> %s", + UNIT(t)->id, + target_state_to_string(old_state), + target_state_to_string(state)); + + unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); +} + +static int target_add_default_dependencies(Target *t) { + + static const UnitDependency deps[] = { + UNIT_REQUIRES, + UNIT_REQUISITE, + UNIT_WANTS, + UNIT_BINDS_TO, + UNIT_PART_OF + }; + + Iterator i; + Unit *other; + int r; + unsigned k; + + assert(t); + + /* Imply ordering for requirement dependencies on target + * units. Note that when the user created a contradicting + * ordering manually we won't add anything in here to make + * sure we don't create a loop. */ + + for (k = 0; k < ELEMENTSOF(deps); k++) + SET_FOREACH(other, UNIT(t)->dependencies[deps[k]], i) { + r = unit_add_default_target_dependency(other, UNIT(t)); + if (r < 0) + return r; + } + + /* Make sure targets are unloaded on shutdown */ + return unit_add_dependency_by_name(UNIT(t), UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static int target_load(Unit *u) { + Target *t = TARGET(u); + int r; + + assert(t); + + r = unit_load_fragment_and_dropin(u); + if (r < 0) + return r; + + /* This is a new unit? Then let's add in some extras */ + if (u->load_state == UNIT_LOADED && u->default_dependencies) { + r = target_add_default_dependencies(t); + if (r < 0) + return r; + } + + return 0; +} + +static int target_coldplug(Unit *u) { + Target *t = TARGET(u); + + assert(t); + assert(t->state == TARGET_DEAD); + + if (t->deserialized_state != t->state) + target_set_state(t, t->deserialized_state); + + return 0; +} + +static void target_dump(Unit *u, FILE *f, const char *prefix) { + Target *t = TARGET(u); + + assert(t); + assert(f); + + fprintf(f, + "%sTarget State: %s\n", + prefix, target_state_to_string(t->state)); +} + +static int target_start(Unit *u) { + Target *t = TARGET(u); + + assert(t); + assert(t->state == TARGET_DEAD); + + target_set_state(t, TARGET_ACTIVE); + return 1; +} + +static int target_stop(Unit *u) { + Target *t = TARGET(u); + + assert(t); + assert(t->state == TARGET_ACTIVE); + + target_set_state(t, TARGET_DEAD); + return 1; +} + +static int target_serialize(Unit *u, FILE *f, FDSet *fds) { + Target *s = TARGET(u); + + assert(s); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", target_state_to_string(s->state)); + return 0; +} + +static int target_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Target *s = TARGET(u); + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + TargetState state; + + state = target_state_from_string(value); + if (state < 0) + log_debug("Failed to parse state value %s", value); + else + s->deserialized_state = state; + + } else + log_debug("Unknown serialization key '%s'", key); + + return 0; +} + +_pure_ static UnitActiveState target_active_state(Unit *u) { + assert(u); + + return state_translation_table[TARGET(u)->state]; +} + +_pure_ static const char *target_sub_state_to_string(Unit *u) { + assert(u); + + return target_state_to_string(TARGET(u)->state); +} + +const UnitVTable target_vtable = { + .object_size = sizeof(Target), + + .sections = + "Unit\0" + "Target\0" + "Install\0", + + .load = target_load, + .coldplug = target_coldplug, + + .dump = target_dump, + + .start = target_start, + .stop = target_stop, + + .serialize = target_serialize, + .deserialize_item = target_deserialize_item, + + .active_state = target_active_state, + .sub_state_to_string = target_sub_state_to_string, + + .bus_vtable = bus_target_vtable, + + .status_message_formats = { + .finished_start_job = { + [JOB_DONE] = "Reached target %s.", + }, + .finished_stop_job = { + [JOB_DONE] = "Stopped target %s.", + }, + }, +}; diff --git a/src/libcore/target.h b/src/libcore/target.h new file mode 100644 index 0000000000..339aea154e --- /dev/null +++ b/src/libcore/target.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Target Target; + +struct Target { + Unit meta; + + TargetState state, deserialized_state; +}; + +extern const UnitVTable target_vtable; diff --git a/src/libcore/timer.c b/src/libcore/timer.c new file mode 100644 index 0000000000..3d0bae16e5 --- /dev/null +++ b/src/libcore/timer.c @@ -0,0 +1,851 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "dbus-timer.h" +#include "fs-util.h" +#include "parse-util.h" +#include "random-util.h" +#include "special.h" +#include "string-table.h" +#include "string-util.h" +#include "timer.h" +#include "unit-name.h" +#include "unit.h" +#include "user-util.h" +#include "virt.h" + +static const UnitActiveState state_translation_table[_TIMER_STATE_MAX] = { + [TIMER_DEAD] = UNIT_INACTIVE, + [TIMER_WAITING] = UNIT_ACTIVE, + [TIMER_RUNNING] = UNIT_ACTIVE, + [TIMER_ELAPSED] = UNIT_ACTIVE, + [TIMER_FAILED] = UNIT_FAILED +}; + +static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata); + +static void timer_init(Unit *u) { + Timer *t = TIMER(u); + + assert(u); + assert(u->load_state == UNIT_STUB); + + t->next_elapse_monotonic_or_boottime = USEC_INFINITY; + t->next_elapse_realtime = USEC_INFINITY; + t->accuracy_usec = u->manager->default_timer_accuracy_usec; + t->remain_after_elapse = true; +} + +void timer_free_values(Timer *t) { + TimerValue *v; + + assert(t); + + while ((v = t->values)) { + LIST_REMOVE(value, t->values, v); + calendar_spec_free(v->calendar_spec); + free(v); + } +} + +static void timer_done(Unit *u) { + Timer *t = TIMER(u); + + assert(t); + + timer_free_values(t); + + t->monotonic_event_source = sd_event_source_unref(t->monotonic_event_source); + t->realtime_event_source = sd_event_source_unref(t->realtime_event_source); + + free(t->stamp_path); +} + +static int timer_verify(Timer *t) { + assert(t); + + if (UNIT(t)->load_state != UNIT_LOADED) + return 0; + + if (!t->values) { + log_unit_error(UNIT(t), "Timer unit lacks value setting. Refusing."); + return -EINVAL; + } + + return 0; +} + +static int timer_add_default_dependencies(Timer *t) { + int r; + TimerValue *v; + + assert(t); + + if (!UNIT(t)->default_dependencies) + return 0; + + r = unit_add_dependency_by_name(UNIT(t), UNIT_BEFORE, SPECIAL_TIMERS_TARGET, NULL, true); + if (r < 0) + return r; + + if (UNIT(t)->manager->running_as == MANAGER_SYSTEM) { + r = unit_add_two_dependencies_by_name(UNIT(t), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true); + if (r < 0) + return r; + + LIST_FOREACH(value, v, t->values) { + if (v->base == TIMER_CALENDAR) { + r = unit_add_dependency_by_name(UNIT(t), UNIT_AFTER, SPECIAL_TIME_SYNC_TARGET, NULL, true); + if (r < 0) + return r; + break; + } + } + } + + return unit_add_two_dependencies_by_name(UNIT(t), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true); +} + +static int timer_setup_persistent(Timer *t) { + int r; + + assert(t); + + if (!t->persistent) + return 0; + + if (UNIT(t)->manager->running_as == MANAGER_SYSTEM) { + + r = unit_require_mounts_for(UNIT(t), "/var/lib/systemd/timers"); + if (r < 0) + return r; + + t->stamp_path = strappend("/var/lib/systemd/timers/stamp-", UNIT(t)->id); + } else { + const char *e; + + e = getenv("XDG_DATA_HOME"); + if (e) + t->stamp_path = strjoin(e, "/systemd/timers/stamp-", UNIT(t)->id, NULL); + else { + + _cleanup_free_ char *h = NULL; + + r = get_home_dir(&h); + if (r < 0) + return log_unit_error_errno(UNIT(t), r, "Failed to determine home directory: %m"); + + t->stamp_path = strjoin(h, "/.local/share/systemd/timers/stamp-", UNIT(t)->id, NULL); + } + } + + if (!t->stamp_path) + return log_oom(); + + return 0; +} + +static int timer_load(Unit *u) { + Timer *t = TIMER(u); + int r; + + assert(u); + assert(u->load_state == UNIT_STUB); + + r = unit_load_fragment_and_dropin(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_LOADED) { + + if (set_isempty(u->dependencies[UNIT_TRIGGERS])) { + Unit *x; + + r = unit_load_related_unit(u, ".service", &x); + if (r < 0) + return r; + + r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, x, true); + if (r < 0) + return r; + } + + r = timer_setup_persistent(t); + if (r < 0) + return r; + + r = timer_add_default_dependencies(t); + if (r < 0) + return r; + } + + return timer_verify(t); +} + +static void timer_dump(Unit *u, FILE *f, const char *prefix) { + char buf[FORMAT_TIMESPAN_MAX]; + Timer *t = TIMER(u); + Unit *trigger; + TimerValue *v; + + trigger = UNIT_TRIGGER(u); + + fprintf(f, + "%sTimer State: %s\n" + "%sResult: %s\n" + "%sUnit: %s\n" + "%sPersistent: %s\n" + "%sWakeSystem: %s\n" + "%sAccuracy: %s\n" + "%sRemainAfterElapse: %s\n", + prefix, timer_state_to_string(t->state), + prefix, timer_result_to_string(t->result), + prefix, trigger ? trigger->id : "n/a", + prefix, yes_no(t->persistent), + prefix, yes_no(t->wake_system), + prefix, format_timespan(buf, sizeof(buf), t->accuracy_usec, 1), + prefix, yes_no(t->remain_after_elapse)); + + LIST_FOREACH(value, v, t->values) { + + if (v->base == TIMER_CALENDAR) { + _cleanup_free_ char *p = NULL; + + calendar_spec_to_string(v->calendar_spec, &p); + + fprintf(f, + "%s%s: %s\n", + prefix, + timer_base_to_string(v->base), + strna(p)); + } else { + char timespan1[FORMAT_TIMESPAN_MAX]; + + fprintf(f, + "%s%s: %s\n", + prefix, + timer_base_to_string(v->base), + format_timespan(timespan1, sizeof(timespan1), v->value, 0)); + } + } +} + +static void timer_set_state(Timer *t, TimerState state) { + TimerState old_state; + assert(t); + + old_state = t->state; + t->state = state; + + if (state != TIMER_WAITING) { + t->monotonic_event_source = sd_event_source_unref(t->monotonic_event_source); + t->realtime_event_source = sd_event_source_unref(t->realtime_event_source); + } + + if (state != old_state) + log_unit_debug(UNIT(t), "Changed %s -> %s", timer_state_to_string(old_state), timer_state_to_string(state)); + + unit_notify(UNIT(t), state_translation_table[old_state], state_translation_table[state], true); +} + +static void timer_enter_waiting(Timer *t, bool initial); + +static int timer_coldplug(Unit *u) { + Timer *t = TIMER(u); + + assert(t); + assert(t->state == TIMER_DEAD); + + if (t->deserialized_state == t->state) + return 0; + + if (t->deserialized_state == TIMER_WAITING) + timer_enter_waiting(t, false); + else + timer_set_state(t, t->deserialized_state); + + return 0; +} + +static void timer_enter_dead(Timer *t, TimerResult f) { + assert(t); + + if (f != TIMER_SUCCESS) + t->result = f; + + timer_set_state(t, t->result != TIMER_SUCCESS ? TIMER_FAILED : TIMER_DEAD); +} + +static void timer_enter_elapsed(Timer *t, bool leave_around) { + assert(t); + + /* If a unit is marked with RemainAfterElapse=yes we leave it + * around even after it elapsed once, so that starting it + * later again does not necessarily mean immediate + * retriggering. We unconditionally leave units with + * TIMER_UNIT_ACTIVE or TIMER_UNIT_INACTIVE triggers around, + * since they might be restarted automatically at any time + * later on. */ + + if (t->remain_after_elapse || leave_around) + timer_set_state(t, TIMER_ELAPSED); + else + timer_enter_dead(t, TIMER_SUCCESS); +} + +static usec_t monotonic_to_boottime(usec_t t) { + usec_t a, b; + + if (t <= 0) + return 0; + + a = now(CLOCK_BOOTTIME); + b = now(CLOCK_MONOTONIC); + + if (t + a > b) + return t + a - b; + else + return 0; +} + +static void add_random(Timer *t, usec_t *v) { + char s[FORMAT_TIMESPAN_MAX]; + usec_t add; + + assert(t); + assert(v); + + if (t->random_usec == 0) + return; + if (*v == USEC_INFINITY) + return; + + add = random_u64() % t->random_usec; + + if (*v + add < *v) /* overflow */ + *v = (usec_t) -2; /* Highest possible value, that is not USEC_INFINITY */ + else + *v += add; + + log_unit_info(UNIT(t), "Adding %s random time.", format_timespan(s, sizeof(s), add, 0)); +} + +static void timer_enter_waiting(Timer *t, bool initial) { + bool found_monotonic = false, found_realtime = false; + usec_t ts_realtime, ts_monotonic; + usec_t base = 0; + bool leave_around = false; + TimerValue *v; + Unit *trigger; + int r; + + assert(t); + + trigger = UNIT_TRIGGER(UNIT(t)); + if (!trigger) { + log_unit_error(UNIT(t), "Unit to trigger vanished."); + timer_enter_dead(t, TIMER_FAILURE_RESOURCES); + return; + } + + /* If we shall wake the system we use the boottime clock + * rather than the monotonic clock. */ + + ts_realtime = now(CLOCK_REALTIME); + ts_monotonic = now(t->wake_system ? CLOCK_BOOTTIME : CLOCK_MONOTONIC); + t->next_elapse_monotonic_or_boottime = t->next_elapse_realtime = 0; + + LIST_FOREACH(value, v, t->values) { + + if (v->disabled) + continue; + + if (v->base == TIMER_CALENDAR) { + usec_t b; + + /* If we know the last time this was + * triggered, schedule the job based relative + * to that. If we don't just start from + * now. */ + + b = t->last_trigger.realtime > 0 ? t->last_trigger.realtime : ts_realtime; + + r = calendar_spec_next_usec(v->calendar_spec, b, &v->next_elapse); + if (r < 0) + continue; + + if (!found_realtime) + t->next_elapse_realtime = v->next_elapse; + else + t->next_elapse_realtime = MIN(t->next_elapse_realtime, v->next_elapse); + + found_realtime = true; + + } else { + switch (v->base) { + + case TIMER_ACTIVE: + if (state_translation_table[t->state] == UNIT_ACTIVE) + base = UNIT(t)->inactive_exit_timestamp.monotonic; + else + base = ts_monotonic; + break; + + case TIMER_BOOT: + if (detect_container() <= 0) { + /* CLOCK_MONOTONIC equals the uptime on Linux */ + base = 0; + break; + } + /* In a container we don't want to include the time the host + * was already up when the container started, so count from + * our own startup. Fall through. */ + case TIMER_STARTUP: + base = UNIT(t)->manager->userspace_timestamp.monotonic; + break; + + case TIMER_UNIT_ACTIVE: + leave_around = true; + base = trigger->inactive_exit_timestamp.monotonic; + + if (base <= 0) + base = t->last_trigger.monotonic; + + if (base <= 0) + continue; + + break; + + case TIMER_UNIT_INACTIVE: + leave_around = true; + base = trigger->inactive_enter_timestamp.monotonic; + + if (base <= 0) + base = t->last_trigger.monotonic; + + if (base <= 0) + continue; + + break; + + default: + assert_not_reached("Unknown timer base"); + } + + if (t->wake_system) + base = monotonic_to_boottime(base); + + v->next_elapse = base + v->value; + + if (!initial && v->next_elapse < ts_monotonic && IN_SET(v->base, TIMER_ACTIVE, TIMER_BOOT, TIMER_STARTUP)) { + /* This is a one time trigger, disable it now */ + v->disabled = true; + continue; + } + + if (!found_monotonic) + t->next_elapse_monotonic_or_boottime = v->next_elapse; + else + t->next_elapse_monotonic_or_boottime = MIN(t->next_elapse_monotonic_or_boottime, v->next_elapse); + + found_monotonic = true; + } + } + + if (!found_monotonic && !found_realtime) { + log_unit_debug(UNIT(t), "Timer is elapsed."); + timer_enter_elapsed(t, leave_around); + return; + } + + if (found_monotonic) { + char buf[FORMAT_TIMESPAN_MAX]; + usec_t left; + + add_random(t, &t->next_elapse_monotonic_or_boottime); + + left = t->next_elapse_monotonic_or_boottime > ts_monotonic ? t->next_elapse_monotonic_or_boottime - ts_monotonic : 0; + log_unit_debug(UNIT(t), "Monotonic timer elapses in %s.", format_timespan(buf, sizeof(buf), left, 0)); + + if (t->monotonic_event_source) { + r = sd_event_source_set_time(t->monotonic_event_source, t->next_elapse_monotonic_or_boottime); + if (r < 0) + goto fail; + + r = sd_event_source_set_enabled(t->monotonic_event_source, SD_EVENT_ONESHOT); + if (r < 0) + goto fail; + } else { + + r = sd_event_add_time( + UNIT(t)->manager->event, + &t->monotonic_event_source, + t->wake_system ? CLOCK_BOOTTIME_ALARM : CLOCK_MONOTONIC, + t->next_elapse_monotonic_or_boottime, t->accuracy_usec, + timer_dispatch, t); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(t->monotonic_event_source, "timer-monotonic"); + } + + } else if (t->monotonic_event_source) { + + r = sd_event_source_set_enabled(t->monotonic_event_source, SD_EVENT_OFF); + if (r < 0) + goto fail; + } + + if (found_realtime) { + char buf[FORMAT_TIMESTAMP_MAX]; + + add_random(t, &t->next_elapse_realtime); + + log_unit_debug(UNIT(t), "Realtime timer elapses at %s.", format_timestamp(buf, sizeof(buf), t->next_elapse_realtime)); + + if (t->realtime_event_source) { + r = sd_event_source_set_time(t->realtime_event_source, t->next_elapse_realtime); + if (r < 0) + goto fail; + + r = sd_event_source_set_enabled(t->realtime_event_source, SD_EVENT_ONESHOT); + if (r < 0) + goto fail; + } else { + r = sd_event_add_time( + UNIT(t)->manager->event, + &t->realtime_event_source, + t->wake_system ? CLOCK_REALTIME_ALARM : CLOCK_REALTIME, + t->next_elapse_realtime, t->accuracy_usec, + timer_dispatch, t); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(t->realtime_event_source, "timer-realtime"); + } + + } else if (t->realtime_event_source) { + + r = sd_event_source_set_enabled(t->realtime_event_source, SD_EVENT_OFF); + if (r < 0) + goto fail; + } + + timer_set_state(t, TIMER_WAITING); + return; + +fail: + log_unit_warning_errno(UNIT(t), r, "Failed to enter waiting state: %m"); + timer_enter_dead(t, TIMER_FAILURE_RESOURCES); +} + +static void timer_enter_running(Timer *t) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + Unit *trigger; + int r; + + assert(t); + + /* Don't start job if we are supposed to go down */ + if (unit_stop_pending(UNIT(t))) + return; + + trigger = UNIT_TRIGGER(UNIT(t)); + if (!trigger) { + log_unit_error(UNIT(t), "Unit to trigger vanished."); + timer_enter_dead(t, TIMER_FAILURE_RESOURCES); + return; + } + + r = manager_add_job(UNIT(t)->manager, JOB_START, trigger, JOB_REPLACE, &error, NULL); + if (r < 0) + goto fail; + + dual_timestamp_get(&t->last_trigger); + + if (t->stamp_path) + touch_file(t->stamp_path, true, t->last_trigger.realtime, UID_INVALID, GID_INVALID, MODE_INVALID); + + timer_set_state(t, TIMER_RUNNING); + return; + +fail: + log_unit_warning(UNIT(t), "Failed to queue unit startup job: %s", bus_error_message(&error, r)); + timer_enter_dead(t, TIMER_FAILURE_RESOURCES); +} + +static int timer_start(Unit *u) { + Timer *t = TIMER(u); + TimerValue *v; + Unit *trigger; + + assert(t); + assert(t->state == TIMER_DEAD || t->state == TIMER_FAILED); + + trigger = UNIT_TRIGGER(u); + if (!trigger || trigger->load_state != UNIT_LOADED) { + log_unit_error(u, "Refusing to start, unit to trigger not loaded."); + return -ENOENT; + } + + t->last_trigger = DUAL_TIMESTAMP_NULL; + + /* Reenable all timers that depend on unit activation time */ + LIST_FOREACH(value, v, t->values) + if (v->base == TIMER_ACTIVE) + v->disabled = false; + + if (t->stamp_path) { + struct stat st; + + if (stat(t->stamp_path, &st) >= 0) + t->last_trigger.realtime = timespec_load(&st.st_atim); + else if (errno == ENOENT) + /* The timer has never run before, + * make sure a stamp file exists. + */ + touch_file(t->stamp_path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, MODE_INVALID); + } + + t->result = TIMER_SUCCESS; + timer_enter_waiting(t, true); + return 1; +} + +static int timer_stop(Unit *u) { + Timer *t = TIMER(u); + + assert(t); + assert(t->state == TIMER_WAITING || t->state == TIMER_RUNNING || t->state == TIMER_ELAPSED); + + timer_enter_dead(t, TIMER_SUCCESS); + return 1; +} + +static int timer_serialize(Unit *u, FILE *f, FDSet *fds) { + Timer *t = TIMER(u); + + assert(u); + assert(f); + assert(fds); + + unit_serialize_item(u, f, "state", timer_state_to_string(t->state)); + unit_serialize_item(u, f, "result", timer_result_to_string(t->result)); + + if (t->last_trigger.realtime > 0) + unit_serialize_item_format(u, f, "last-trigger-realtime", "%" PRIu64, t->last_trigger.realtime); + + if (t->last_trigger.monotonic > 0) + unit_serialize_item_format(u, f, "last-trigger-monotonic", "%" PRIu64, t->last_trigger.monotonic); + + return 0; +} + +static int timer_deserialize_item(Unit *u, const char *key, const char *value, FDSet *fds) { + Timer *t = TIMER(u); + int r; + + assert(u); + assert(key); + assert(value); + assert(fds); + + if (streq(key, "state")) { + TimerState state; + + state = timer_state_from_string(value); + if (state < 0) + log_unit_debug(u, "Failed to parse state value: %s", value); + else + t->deserialized_state = state; + } else if (streq(key, "result")) { + TimerResult f; + + f = timer_result_from_string(value); + if (f < 0) + log_unit_debug(u, "Failed to parse result value: %s", value); + else if (f != TIMER_SUCCESS) + t->result = f; + } else if (streq(key, "last-trigger-realtime")) { + + r = safe_atou64(value, &t->last_trigger.realtime); + if (r < 0) + log_unit_debug(u, "Failed to parse last-trigger-realtime value: %s", value); + + } else if (streq(key, "last-trigger-monotonic")) { + + r = safe_atou64(value, &t->last_trigger.monotonic); + if (r < 0) + log_unit_debug(u, "Failed to parse last-trigger-monotonic value: %s", value); + + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + + return 0; +} + +_pure_ static UnitActiveState timer_active_state(Unit *u) { + assert(u); + + return state_translation_table[TIMER(u)->state]; +} + +_pure_ static const char *timer_sub_state_to_string(Unit *u) { + assert(u); + + return timer_state_to_string(TIMER(u)->state); +} + +static int timer_dispatch(sd_event_source *s, uint64_t usec, void *userdata) { + Timer *t = TIMER(userdata); + + assert(t); + + if (t->state != TIMER_WAITING) + return 0; + + log_unit_debug(UNIT(t), "Timer elapsed."); + timer_enter_running(t); + return 0; +} + +static void timer_trigger_notify(Unit *u, Unit *other) { + Timer *t = TIMER(u); + TimerValue *v; + + assert(u); + assert(other); + + if (other->load_state != UNIT_LOADED) + return; + + /* Reenable all timers that depend on unit state */ + LIST_FOREACH(value, v, t->values) + if (v->base == TIMER_UNIT_ACTIVE || + v->base == TIMER_UNIT_INACTIVE) + v->disabled = false; + + switch (t->state) { + + case TIMER_WAITING: + case TIMER_ELAPSED: + + /* Recalculate sleep time */ + timer_enter_waiting(t, false); + break; + + case TIMER_RUNNING: + + if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) { + log_unit_debug(UNIT(t), "Got notified about unit deactivation."); + timer_enter_waiting(t, false); + } + break; + + case TIMER_DEAD: + case TIMER_FAILED: + break; + + default: + assert_not_reached("Unknown timer state"); + } +} + +static void timer_reset_failed(Unit *u) { + Timer *t = TIMER(u); + + assert(t); + + if (t->state == TIMER_FAILED) + timer_set_state(t, TIMER_DEAD); + + t->result = TIMER_SUCCESS; +} + +static void timer_time_change(Unit *u) { + Timer *t = TIMER(u); + + assert(u); + + if (t->state != TIMER_WAITING) + return; + + log_unit_debug(u, "Time change, recalculating next elapse."); + timer_enter_waiting(t, false); +} + +static const char* const timer_base_table[_TIMER_BASE_MAX] = { + [TIMER_ACTIVE] = "OnActiveSec", + [TIMER_BOOT] = "OnBootSec", + [TIMER_STARTUP] = "OnStartupSec", + [TIMER_UNIT_ACTIVE] = "OnUnitActiveSec", + [TIMER_UNIT_INACTIVE] = "OnUnitInactiveSec", + [TIMER_CALENDAR] = "OnCalendar" +}; + +DEFINE_STRING_TABLE_LOOKUP(timer_base, TimerBase); + +static const char* const timer_result_table[_TIMER_RESULT_MAX] = { + [TIMER_SUCCESS] = "success", + [TIMER_FAILURE_RESOURCES] = "resources" +}; + +DEFINE_STRING_TABLE_LOOKUP(timer_result, TimerResult); + +const UnitVTable timer_vtable = { + .object_size = sizeof(Timer), + + .sections = + "Unit\0" + "Timer\0" + "Install\0", + .private_section = "Timer", + + .init = timer_init, + .done = timer_done, + .load = timer_load, + + .coldplug = timer_coldplug, + + .dump = timer_dump, + + .start = timer_start, + .stop = timer_stop, + + .serialize = timer_serialize, + .deserialize_item = timer_deserialize_item, + + .active_state = timer_active_state, + .sub_state_to_string = timer_sub_state_to_string, + + .trigger_notify = timer_trigger_notify, + + .reset_failed = timer_reset_failed, + .time_change = timer_time_change, + + .bus_vtable = bus_timer_vtable, + .bus_set_property = bus_timer_set_property, + + .can_transient = true, +}; diff --git a/src/libcore/timer.h b/src/libcore/timer.h new file mode 100644 index 0000000000..698e6da2f5 --- /dev/null +++ b/src/libcore/timer.h @@ -0,0 +1,88 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Timer Timer; + +#include "calendarspec.h" + +typedef enum TimerBase { + TIMER_ACTIVE, + TIMER_BOOT, + TIMER_STARTUP, + TIMER_UNIT_ACTIVE, + TIMER_UNIT_INACTIVE, + TIMER_CALENDAR, + _TIMER_BASE_MAX, + _TIMER_BASE_INVALID = -1 +} TimerBase; + +typedef struct TimerValue { + TimerBase base; + bool disabled; + + usec_t value; /* only for monotonic events */ + CalendarSpec *calendar_spec; /* only for calendar events */ + usec_t next_elapse; + + LIST_FIELDS(struct TimerValue, value); +} TimerValue; + +typedef enum TimerResult { + TIMER_SUCCESS, + TIMER_FAILURE_RESOURCES, + _TIMER_RESULT_MAX, + _TIMER_RESULT_INVALID = -1 +} TimerResult; + +struct Timer { + Unit meta; + + usec_t accuracy_usec; + usec_t random_usec; + + LIST_HEAD(TimerValue, values); + usec_t next_elapse_realtime; + usec_t next_elapse_monotonic_or_boottime; + dual_timestamp last_trigger; + + TimerState state, deserialized_state; + + sd_event_source *monotonic_event_source; + sd_event_source *realtime_event_source; + + TimerResult result; + + bool persistent; + bool wake_system; + bool remain_after_elapse; + + char *stamp_path; +}; + +void timer_free_values(Timer *t); + +extern const UnitVTable timer_vtable; + +const char *timer_base_to_string(TimerBase i) _const_; +TimerBase timer_base_from_string(const char *s) _pure_; + +const char* timer_result_to_string(TimerResult i) _const_; +TimerResult timer_result_from_string(const char *s) _pure_; diff --git a/src/libcore/transaction.c b/src/libcore/transaction.c new file mode 100644 index 0000000000..b28fc76785 --- /dev/null +++ b/src/libcore/transaction.c @@ -0,0 +1,1092 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "terminal-util.h" +#include "transaction.h" +#include "dbus-unit.h" + +static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies); + +static void transaction_delete_job(Transaction *tr, Job *j, bool delete_dependencies) { + assert(tr); + assert(j); + + /* Deletes one job from the transaction */ + + transaction_unlink_job(tr, j, delete_dependencies); + + job_free(j); +} + +static void transaction_delete_unit(Transaction *tr, Unit *u) { + Job *j; + + /* Deletes all jobs associated with a certain unit from the + * transaction */ + + while ((j = hashmap_get(tr->jobs, u))) + transaction_delete_job(tr, j, true); +} + +void transaction_abort(Transaction *tr) { + Job *j; + + assert(tr); + + while ((j = hashmap_first(tr->jobs))) + transaction_delete_job(tr, j, false); + + assert(hashmap_isempty(tr->jobs)); +} + +static void transaction_find_jobs_that_matter_to_anchor(Job *j, unsigned generation) { + JobDependency *l; + + /* A recursive sweep through the graph that marks all units + * that matter to the anchor job, i.e. are directly or + * indirectly a dependency of the anchor job via paths that + * are fully marked as mattering. */ + + j->matters_to_anchor = true; + j->generation = generation; + + LIST_FOREACH(subject, l, j->subject_list) { + + /* This link does not matter */ + if (!l->matters) + continue; + + /* This unit has already been marked */ + if (l->object->generation == generation) + continue; + + transaction_find_jobs_that_matter_to_anchor(l->object, generation); + } +} + +static void transaction_merge_and_delete_job(Transaction *tr, Job *j, Job *other, JobType t) { + JobDependency *l, *last; + + assert(j); + assert(other); + assert(j->unit == other->unit); + assert(!j->installed); + + /* Merges 'other' into 'j' and then deletes 'other'. */ + + j->type = t; + j->state = JOB_WAITING; + j->irreversible = j->irreversible || other->irreversible; + j->matters_to_anchor = j->matters_to_anchor || other->matters_to_anchor; + + /* Patch us in as new owner of the JobDependency objects */ + last = NULL; + LIST_FOREACH(subject, l, other->subject_list) { + assert(l->subject == other); + l->subject = j; + last = l; + } + + /* Merge both lists */ + if (last) { + last->subject_next = j->subject_list; + if (j->subject_list) + j->subject_list->subject_prev = last; + j->subject_list = other->subject_list; + } + + /* Patch us in as new owner of the JobDependency objects */ + last = NULL; + LIST_FOREACH(object, l, other->object_list) { + assert(l->object == other); + l->object = j; + last = l; + } + + /* Merge both lists */ + if (last) { + last->object_next = j->object_list; + if (j->object_list) + j->object_list->object_prev = last; + j->object_list = other->object_list; + } + + /* Kill the other job */ + other->subject_list = NULL; + other->object_list = NULL; + transaction_delete_job(tr, other, true); +} + +_pure_ static bool job_is_conflicted_by(Job *j) { + JobDependency *l; + + assert(j); + + /* Returns true if this job is pulled in by a least one + * ConflictedBy dependency. */ + + LIST_FOREACH(object, l, j->object_list) + if (l->conflicts) + return true; + + return false; +} + +static int delete_one_unmergeable_job(Transaction *tr, Job *j) { + Job *k; + + assert(j); + + /* Tries to delete one item in the linked list + * j->transaction_next->transaction_next->... that conflicts + * with another one, in an attempt to make an inconsistent + * transaction work. */ + + /* We rely here on the fact that if a merged with b does not + * merge with c, either a or b merge with c neither */ + LIST_FOREACH(transaction, j, j) + LIST_FOREACH(transaction, k, j->transaction_next) { + Job *d; + + /* Is this one mergeable? Then skip it */ + if (job_type_is_mergeable(j->type, k->type)) + continue; + + /* Ok, we found two that conflict, let's see if we can + * drop one of them */ + if (!j->matters_to_anchor && !k->matters_to_anchor) { + + /* Both jobs don't matter, so let's + * find the one that is smarter to + * remove. Let's think positive and + * rather remove stops then starts -- + * except if something is being + * stopped because it is conflicted by + * another unit in which case we + * rather remove the start. */ + + log_unit_debug(j->unit, + "Looking at job %s/%s conflicted_by=%s", + j->unit->id, job_type_to_string(j->type), + yes_no(j->type == JOB_STOP && job_is_conflicted_by(j))); + log_unit_debug(k->unit, + "Looking at job %s/%s conflicted_by=%s", + k->unit->id, job_type_to_string(k->type), + yes_no(k->type == JOB_STOP && job_is_conflicted_by(k))); + + if (j->type == JOB_STOP) { + + if (job_is_conflicted_by(j)) + d = k; + else + d = j; + + } else if (k->type == JOB_STOP) { + + if (job_is_conflicted_by(k)) + d = j; + else + d = k; + } else + d = j; + + } else if (!j->matters_to_anchor) + d = j; + else if (!k->matters_to_anchor) + d = k; + else + return -ENOEXEC; + + /* Ok, we can drop one, so let's do so. */ + log_unit_debug(d->unit, + "Fixing conflicting jobs %s/%s,%s/%s by deleting job %s/%s", + j->unit->id, job_type_to_string(j->type), + k->unit->id, job_type_to_string(k->type), + d->unit->id, job_type_to_string(d->type)); + transaction_delete_job(tr, d, true); + return 0; + } + + return -EINVAL; +} + +static int transaction_merge_jobs(Transaction *tr, sd_bus_error *e) { + Job *j; + Iterator i; + int r; + + assert(tr); + + /* First step, check whether any of the jobs for one specific + * task conflict. If so, try to drop one of them. */ + HASHMAP_FOREACH(j, tr->jobs, i) { + JobType t; + Job *k; + + t = j->type; + LIST_FOREACH(transaction, k, j->transaction_next) { + if (job_type_merge_and_collapse(&t, k->type, j->unit) >= 0) + continue; + + /* OK, we could not merge all jobs for this + * action. Let's see if we can get rid of one + * of them */ + + r = delete_one_unmergeable_job(tr, j); + if (r >= 0) + /* Ok, we managed to drop one, now + * let's ask our callers to call us + * again after garbage collecting */ + return -EAGAIN; + + /* We couldn't merge anything. Failure */ + return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_JOBS_CONFLICTING, + "Transaction contains conflicting jobs '%s' and '%s' for %s. " + "Probably contradicting requirement dependencies configured.", + job_type_to_string(t), + job_type_to_string(k->type), + k->unit->id); + } + } + + /* Second step, merge the jobs. */ + HASHMAP_FOREACH(j, tr->jobs, i) { + JobType t = j->type; + Job *k; + + /* Merge all transaction jobs for j->unit */ + LIST_FOREACH(transaction, k, j->transaction_next) + assert_se(job_type_merge_and_collapse(&t, k->type, j->unit) == 0); + + while ((k = j->transaction_next)) { + if (tr->anchor_job == k) { + transaction_merge_and_delete_job(tr, k, j, t); + j = k; + } else + transaction_merge_and_delete_job(tr, j, k, t); + } + + assert(!j->transaction_next); + assert(!j->transaction_prev); + } + + return 0; +} + +static void transaction_drop_redundant(Transaction *tr) { + Job *j; + Iterator i; + + /* Goes through the transaction and removes all jobs of the units + * whose jobs are all noops. If not all of a unit's jobs are + * redundant, they are kept. */ + + assert(tr); + +rescan: + HASHMAP_FOREACH(j, tr->jobs, i) { + Job *k; + + LIST_FOREACH(transaction, k, j) { + + if (tr->anchor_job == k || + !job_type_is_redundant(k->type, unit_active_state(k->unit)) || + (k->unit->job && job_type_is_conflicting(k->type, k->unit->job->type))) + goto next_unit; + } + + /* log_debug("Found redundant job %s/%s, dropping.", j->unit->id, job_type_to_string(j->type)); */ + transaction_delete_job(tr, j, false); + goto rescan; + next_unit:; + } +} + +_pure_ static bool unit_matters_to_anchor(Unit *u, Job *j) { + assert(u); + assert(!j->transaction_prev); + + /* Checks whether at least one of the jobs for this unit + * matters to the anchor. */ + + LIST_FOREACH(transaction, j, j) + if (j->matters_to_anchor) + return true; + + return false; +} + +static int transaction_verify_order_one(Transaction *tr, Job *j, Job *from, unsigned generation, sd_bus_error *e) { + Iterator i; + Unit *u; + int r; + + assert(tr); + assert(j); + assert(!j->transaction_prev); + + /* Does a recursive sweep through the ordering graph, looking + * for a cycle. If we find a cycle we try to break it. */ + + /* Have we seen this before? */ + if (j->generation == generation) { + Job *k, *delete; + + /* If the marker is NULL we have been here already and + * decided the job was loop-free from here. Hence + * shortcut things and return right-away. */ + if (!j->marker) + return 0; + + /* So, the marker is not NULL and we already have been + * here. We have a cycle. Let's try to break it. We go + * backwards in our path and try to find a suitable + * job to remove. We use the marker to find our way + * back, since smart how we are we stored our way back + * in there. */ + log_unit_warning(j->unit, + "Found ordering cycle on %s/%s", + j->unit->id, job_type_to_string(j->type)); + + 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 */ + log_unit_warning(j->unit, + "Found dependency on %s/%s", + k->unit->id, job_type_to_string(k->type)); + + if (!delete && hashmap_get(tr->jobs, k->unit) && !unit_matters_to_anchor(k->unit, k)) + /* Ok, we can drop this one, so let's + * do so. */ + delete = k; + + /* Check if this in fact was the beginning of + * the cycle */ + if (k == j) + break; + } + + + if (delete) { + /* logging for j not k here 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)); + log_unit_error(delete->unit, + "Job %s/%s deleted to break ordering cycle starting with %s/%s", + delete->unit->id, job_type_to_string(delete->type), + j->unit->id, job_type_to_string(j->type)); + unit_status_printf(delete->unit, ANSI_HIGHLIGHT_RED " SKIP " ANSI_NORMAL, + "Ordering cycle found, skipping %s"); + transaction_delete_unit(tr, delete->unit); + return -EAGAIN; + } + + log_error("Unable to break cycle"); + + return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, + "Transaction order is cyclic. See system logs for details."); + } + + /* Make the marker point to where we come from, so that we can + * find our way backwards if we want to break a cycle. We use + * a special marker for the beginning: we point to + * ourselves. */ + j->marker = from ? from : j; + j->generation = generation; + + /* We assume that the dependencies are bidirectional, and + * hence can ignore UNIT_AFTER */ + SET_FOREACH(u, j->unit->dependencies[UNIT_BEFORE], i) { + Job *o; + + /* Is there a job for this unit? */ + o = hashmap_get(tr->jobs, u); + if (!o) { + /* Ok, there is no job for this in the + * transaction, but maybe there is already one + * running? */ + o = u->job; + if (!o) + continue; + } + + r = transaction_verify_order_one(tr, o, j, generation, e); + if (r < 0) + return r; + } + + /* Ok, let's backtrack, and remember that this entry is not on + * our path anymore. */ + j->marker = NULL; + + return 0; +} + +static int transaction_verify_order(Transaction *tr, unsigned *generation, sd_bus_error *e) { + Job *j; + int r; + Iterator i; + unsigned g; + + assert(tr); + assert(generation); + + /* Check if the ordering graph is cyclic. If it is, try to fix + * that up by dropping one of the jobs. */ + + g = (*generation)++; + + HASHMAP_FOREACH(j, tr->jobs, i) { + r = transaction_verify_order_one(tr, j, NULL, g, e); + if (r < 0) + return r; + } + + return 0; +} + +static void transaction_collect_garbage(Transaction *tr) { + Iterator i; + Job *j; + + assert(tr); + + /* Drop jobs that are not required by any other job */ + +rescan: + HASHMAP_FOREACH(j, tr->jobs, i) { + if (tr->anchor_job == j || j->object_list) { + /* log_debug("Keeping job %s/%s because of %s/%s", */ + /* j->unit->id, job_type_to_string(j->type), */ + /* j->object_list->subject ? j->object_list->subject->unit->id : "root", */ + /* j->object_list->subject ? job_type_to_string(j->object_list->subject->type) : "root"); */ + continue; + } + + /* log_debug("Garbage collecting job %s/%s", j->unit->id, job_type_to_string(j->type)); */ + transaction_delete_job(tr, j, true); + goto rescan; + } +} + +static int transaction_is_destructive(Transaction *tr, JobMode mode, sd_bus_error *e) { + Iterator i; + Job *j; + + assert(tr); + + /* Checks whether applying this transaction means that + * existing jobs would be replaced */ + + HASHMAP_FOREACH(j, tr->jobs, i) { + + /* Assume merged */ + assert(!j->transaction_prev); + assert(!j->transaction_next); + + if (j->unit->job && (mode == JOB_FAIL || j->unit->job->irreversible) && + job_type_is_conflicting(j->unit->job->type, j->type)) + return sd_bus_error_setf(e, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, + "Transaction is destructive."); + } + + return 0; +} + +static void transaction_minimize_impact(Transaction *tr) { + Job *j; + Iterator i; + + assert(tr); + + /* Drops all unnecessary jobs that reverse already active jobs + * or that stop a running service. */ + +rescan: + HASHMAP_FOREACH(j, tr->jobs, i) { + LIST_FOREACH(transaction, j, j) { + bool stops_running_service, changes_existing_job; + + /* If it matters, we shouldn't drop it */ + if (j->matters_to_anchor) + continue; + + /* Would this stop a running service? + * Would this change an existing job? + * If so, let's drop this entry */ + + stops_running_service = + j->type == JOB_STOP && UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(j->unit)); + + changes_existing_job = + j->unit->job && + job_type_is_conflicting(j->type, j->unit->job->type); + + if (!stops_running_service && !changes_existing_job) + continue; + + if (stops_running_service) + log_unit_debug(j->unit, + "%s/%s would stop a running service.", + j->unit->id, job_type_to_string(j->type)); + + if (changes_existing_job) + log_unit_debug(j->unit, + "%s/%s would change existing job.", + j->unit->id, job_type_to_string(j->type)); + + /* Ok, let's get rid of this */ + log_unit_debug(j->unit, + "Deleting %s/%s to minimize impact.", + j->unit->id, job_type_to_string(j->type)); + + transaction_delete_job(tr, j, true); + goto rescan; + } + } +} + +static int transaction_apply(Transaction *tr, Manager *m, JobMode mode) { + Iterator i; + Job *j; + int r; + + /* Moves the transaction jobs to the set of active jobs */ + + if (mode == JOB_ISOLATE || mode == JOB_FLUSH) { + + /* When isolating first kill all installed jobs which + * aren't part of the new transaction */ + HASHMAP_FOREACH(j, m->jobs, i) { + assert(j->installed); + + if (hashmap_get(tr->jobs, j->unit)) + continue; + + /* Not invalidating recursively. Avoids triggering + * OnFailure= actions of dependent jobs. Also avoids + * invalidating our iterator. */ + job_finish_and_invalidate(j, JOB_CANCELED, false); + } + } + + HASHMAP_FOREACH(j, tr->jobs, i) { + /* Assume merged */ + assert(!j->transaction_prev); + assert(!j->transaction_next); + + r = hashmap_put(m->jobs, UINT32_TO_PTR(j->id), j); + if (r < 0) + goto rollback; + } + + while ((j = hashmap_steal_first(tr->jobs))) { + Job *installed_job; + + /* Clean the job dependencies */ + transaction_unlink_job(tr, j, false); + + installed_job = job_install(j); + if (installed_job != j) { + /* j has been merged into a previously installed job */ + if (tr->anchor_job == j) + tr->anchor_job = installed_job; + hashmap_remove(m->jobs, UINT32_TO_PTR(j->id)); + job_free(j); + j = installed_job; + } + + job_add_to_run_queue(j); + job_add_to_dbus_queue(j); + job_start_timer(j); + job_shutdown_magic(j); + } + + return 0; + +rollback: + + HASHMAP_FOREACH(j, tr->jobs, i) + hashmap_remove(m->jobs, UINT32_TO_PTR(j->id)); + + return r; +} + +int transaction_activate(Transaction *tr, Manager *m, JobMode mode, sd_bus_error *e) { + Iterator i; + Job *j; + int r; + unsigned generation = 1; + + assert(tr); + + /* This applies the changes recorded in tr->jobs to + * the actual list of jobs, if possible. */ + + /* Reset the generation counter of all installed jobs. The detection of cycles + * looks at installed jobs. If they had a non-zero generation from some previous + * walk of the graph, the algorithm would break. */ + HASHMAP_FOREACH(j, m->jobs, i) + j->generation = 0; + + /* First step: figure out which jobs matter */ + transaction_find_jobs_that_matter_to_anchor(tr->anchor_job, generation++); + + /* Second step: Try not to stop any running services if + * we don't have to. Don't try to reverse running + * jobs if we don't have to. */ + if (mode == JOB_FAIL) + transaction_minimize_impact(tr); + + /* Third step: Drop redundant jobs */ + transaction_drop_redundant(tr); + + for (;;) { + /* Fourth step: Let's remove unneeded jobs that might + * be lurking. */ + if (mode != JOB_ISOLATE) + transaction_collect_garbage(tr); + + /* Fifth step: verify order makes sense and correct + * cycles if necessary and possible */ + r = transaction_verify_order(tr, &generation, e); + if (r >= 0) + break; + + if (r != -EAGAIN) { + log_warning("Requested transaction contains an unfixable cyclic ordering dependency: %s", bus_error_message(e, r)); + return r; + } + + /* Let's see if the resulting transaction ordering + * graph is still cyclic... */ + } + + for (;;) { + /* Sixth step: let's drop unmergeable entries if + * necessary and possible, merge entries we can + * merge */ + r = transaction_merge_jobs(tr, e); + if (r >= 0) + break; + + if (r != -EAGAIN) { + log_warning("Requested transaction contains unmergeable jobs: %s", bus_error_message(e, r)); + return r; + } + + /* Seventh step: an entry got dropped, let's garbage + * collect its dependencies. */ + if (mode != JOB_ISOLATE) + transaction_collect_garbage(tr); + + /* Let's see if the resulting transaction still has + * unmergeable entries ... */ + } + + /* Eights step: Drop redundant jobs again, if the merging now allows us to drop more. */ + transaction_drop_redundant(tr); + + /* Ninth step: check whether we can actually apply this */ + r = transaction_is_destructive(tr, mode, e); + if (r < 0) { + log_notice("Requested transaction contradicts existing jobs: %s", bus_error_message(e, r)); + return r; + } + + /* Tenth step: apply changes */ + r = transaction_apply(tr, m, mode); + if (r < 0) + return log_warning_errno(r, "Failed to apply transaction: %m"); + + assert(hashmap_isempty(tr->jobs)); + + if (!hashmap_isempty(m->jobs)) { + /* Are there any jobs now? Then make sure we have the + * idle pipe around. We don't really care too much + * whether this works or not, as the idle pipe is a + * feature for cosmetics, not actually useful for + * anything beyond that. */ + + if (m->idle_pipe[0] < 0 && m->idle_pipe[1] < 0 && + m->idle_pipe[2] < 0 && m->idle_pipe[3] < 0) { + (void) pipe2(m->idle_pipe, O_NONBLOCK|O_CLOEXEC); + (void) pipe2(m->idle_pipe + 2, O_NONBLOCK|O_CLOEXEC); + } + } + + return 0; +} + +static Job* transaction_add_one_job(Transaction *tr, JobType type, Unit *unit, bool *is_new) { + Job *j, *f; + + assert(tr); + assert(unit); + + /* Looks for an existing prospective job and returns that. If + * it doesn't exist it is created and added to the prospective + * jobs list. */ + + f = hashmap_get(tr->jobs, unit); + + LIST_FOREACH(transaction, j, f) { + assert(j->unit == unit); + + if (j->type == type) { + if (is_new) + *is_new = false; + return j; + } + } + + j = job_new(unit, type); + if (!j) + return NULL; + + j->generation = 0; + j->marker = NULL; + j->matters_to_anchor = false; + j->irreversible = tr->irreversible; + + LIST_PREPEND(transaction, f, j); + + if (hashmap_replace(tr->jobs, unit, f) < 0) { + LIST_REMOVE(transaction, f, j); + job_free(j); + return NULL; + } + + if (is_new) + *is_new = true; + + /* log_debug("Added job %s/%s to transaction.", unit->id, job_type_to_string(type)); */ + + return j; +} + +static void transaction_unlink_job(Transaction *tr, Job *j, bool delete_dependencies) { + assert(tr); + assert(j); + + if (j->transaction_prev) + j->transaction_prev->transaction_next = j->transaction_next; + else if (j->transaction_next) + hashmap_replace(tr->jobs, j->unit, j->transaction_next); + else + hashmap_remove_value(tr->jobs, j->unit, j); + + if (j->transaction_next) + j->transaction_next->transaction_prev = j->transaction_prev; + + j->transaction_prev = j->transaction_next = NULL; + + while (j->subject_list) + job_dependency_free(j->subject_list); + + while (j->object_list) { + Job *other = j->object_list->matters ? j->object_list->subject : NULL; + + job_dependency_free(j->object_list); + + if (other && delete_dependencies) { + log_unit_debug(other->unit, + "Deleting job %s/%s as dependency of job %s/%s", + other->unit->id, job_type_to_string(other->type), + j->unit->id, job_type_to_string(j->type)); + transaction_delete_job(tr, other, delete_dependencies); + } + } +} + +int transaction_add_job_and_dependencies( + Transaction *tr, + JobType type, + Unit *unit, + Job *by, + bool matters, + bool conflicts, + bool ignore_requirements, + bool ignore_order, + sd_bus_error *e) { + Job *ret; + Iterator i; + Unit *dep; + int r; + bool is_new; + + assert(tr); + assert(type < _JOB_TYPE_MAX); + assert(type < _JOB_TYPE_MAX_IN_TRANSACTION); + assert(unit); + + /* Before adding jobs for this unit, let's ensure that its state has been loaded + * This matters when jobs are spawned as part of coldplugging itself (see e. g. path_coldplug()). + * This way, we "recursively" coldplug units, ensuring that we do not look at state of + * not-yet-coldplugged units. */ + if (unit->manager->n_reloading > 0) + unit_coldplug(unit); + + /* log_debug("Pulling in %s/%s from %s/%s", */ + /* unit->id, job_type_to_string(type), */ + /* by ? by->unit->id : "NA", */ + /* by ? job_type_to_string(by->type) : "NA"); */ + + if (!IN_SET(unit->load_state, UNIT_LOADED, UNIT_ERROR, UNIT_NOT_FOUND, UNIT_MASKED)) + return sd_bus_error_setf(e, BUS_ERROR_LOAD_FAILED, "Unit %s is not loaded properly.", unit->id); + + if (type != JOB_STOP) { + r = bus_unit_check_load_state(unit, e); + if (r < 0) + return r; + } + + if (!unit_job_is_applicable(unit, type)) + return sd_bus_error_setf(e, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, + "Job type %s is not applicable for unit %s.", + job_type_to_string(type), unit->id); + + + /* First add the job. */ + ret = transaction_add_one_job(tr, type, unit, &is_new); + if (!ret) + return -ENOMEM; + + ret->ignore_order = ret->ignore_order || ignore_order; + + /* Then, add a link to the job. */ + if (by) { + if (!job_dependency_new(by, ret, matters, conflicts)) + return -ENOMEM; + } else { + /* If the job has no parent job, it is the anchor job. */ + assert(!tr->anchor_job); + tr->anchor_job = ret; + } + + if (is_new && !ignore_requirements && type != JOB_NOP) { + Set *following; + + /* If we are following some other unit, make sure we + * add all dependencies of everybody following. */ + if (unit_following_set(ret->unit, &following) > 0) { + SET_FOREACH(dep, following, i) { + r = transaction_add_job_and_dependencies(tr, type, dep, ret, false, false, false, ignore_order, e); + if (r < 0) { + log_unit_warning(dep, "Cannot add dependency job for, ignoring: %s", bus_error_message(e, r)); + sd_bus_error_free(e); + } + } + + set_free(following); + } + + /* Finally, recursively add in all dependencies. */ + if (type == JOB_START || type == JOB_RESTART) { + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUIRES], i) { + r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, false, false, ignore_order, e); + if (r < 0) { + if (r != -EBADR) /* job type not applicable */ + goto fail; + + sd_bus_error_free(e); + } + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_BINDS_TO], i) { + r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, true, false, false, ignore_order, e); + if (r < 0) { + if (r != -EBADR) /* job type not applicable */ + goto fail; + + sd_bus_error_free(e); + } + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_WANTS], i) { + r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, false, false, false, ignore_order, e); + if (r < 0) { + /* unit masked, job type not applicable and unit not found are not considered as errors. */ + log_unit_full(dep, + IN_SET(r, -ESHUTDOWN, -EBADR, -ENOENT) ? LOG_DEBUG : LOG_WARNING, + r, "Cannot add dependency job, ignoring: %s", + bus_error_message(e, r)); + sd_bus_error_free(e); + } + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_REQUISITE], i) { + r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, true, false, false, ignore_order, e); + if (r < 0) { + if (r != -EBADR) /* job type not applicable */ + goto fail; + + sd_bus_error_free(e); + } + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTS], i) { + r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, true, true, false, ignore_order, e); + if (r < 0) { + if (r != -EBADR) /* job type not applicable */ + goto fail; + + sd_bus_error_free(e); + } + } + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_CONFLICTED_BY], i) { + r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, false, false, false, ignore_order, e); + if (r < 0) { + log_unit_warning(dep, + "Cannot add dependency job, ignoring: %s", + bus_error_message(e, r)); + sd_bus_error_free(e); + } + } + + } + + if (type == JOB_STOP || type == JOB_RESTART) { + static const UnitDependency propagate_deps[] = { + UNIT_REQUIRED_BY, + UNIT_REQUISITE_OF, + UNIT_BOUND_BY, + UNIT_CONSISTS_OF, + }; + + JobType ptype; + unsigned j; + + /* We propagate STOP as STOP, but RESTART only + * as TRY_RESTART, in order not to start + * dependencies that are not around. */ + ptype = type == JOB_RESTART ? JOB_TRY_RESTART : type; + + for (j = 0; j < ELEMENTSOF(propagate_deps); j++) + SET_FOREACH(dep, ret->unit->dependencies[propagate_deps[j]], i) { + JobType nt; + + nt = job_type_collapse(ptype, dep); + if (nt == JOB_NOP) + continue; + + r = transaction_add_job_and_dependencies(tr, nt, dep, ret, true, false, false, ignore_order, e); + if (r < 0) { + if (r != -EBADR) /* job type not applicable */ + goto fail; + + sd_bus_error_free(e); + } + } + } + + if (type == JOB_RELOAD) { + + SET_FOREACH(dep, ret->unit->dependencies[UNIT_PROPAGATES_RELOAD_TO], i) { + JobType nt; + + nt = job_type_collapse(JOB_TRY_RELOAD, dep); + if (nt == JOB_NOP) + continue; + + r = transaction_add_job_and_dependencies(tr, nt, dep, ret, false, false, false, ignore_order, e); + if (r < 0) { + log_unit_warning(dep, + "Cannot add dependency reload job, ignoring: %s", + bus_error_message(e, r)); + sd_bus_error_free(e); + } + } + } + + /* JOB_VERIFY_STARTED require no dependency handling */ + } + + return 0; + +fail: + return r; +} + +int transaction_add_isolate_jobs(Transaction *tr, Manager *m) { + Iterator i; + Unit *u; + char *k; + int r; + + assert(tr); + assert(m); + + HASHMAP_FOREACH_KEY(u, k, m->units, i) { + + /* ignore aliases */ + if (u->id != k) + continue; + + if (u->ignore_on_isolate) + continue; + + /* No need to stop inactive jobs */ + if (UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(u)) && !u->job) + continue; + + /* Is there already something listed for this? */ + if (hashmap_get(tr->jobs, u)) + continue; + + r = transaction_add_job_and_dependencies(tr, JOB_STOP, u, tr->anchor_job, true, false, false, false, NULL); + if (r < 0) + log_unit_warning_errno(u, r, "Cannot add isolate job, ignoring: %m"); + } + + return 0; +} + +Transaction *transaction_new(bool irreversible) { + Transaction *tr; + + tr = new0(Transaction, 1); + if (!tr) + return NULL; + + tr->jobs = hashmap_new(NULL); + if (!tr->jobs) { + free(tr); + return NULL; + } + + tr->irreversible = irreversible; + + return tr; +} + +void transaction_free(Transaction *tr) { + assert(hashmap_isempty(tr->jobs)); + hashmap_free(tr->jobs); + free(tr); +} diff --git a/src/libcore/transaction.h b/src/libcore/transaction.h new file mode 100644 index 0000000000..6a3f927b0f --- /dev/null +++ b/src/libcore/transaction.h @@ -0,0 +1,51 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef struct Transaction Transaction; + +#include "hashmap.h" +#include "job.h" +#include "manager.h" +#include "unit.h" + +struct Transaction { + /* Jobs to be added */ + Hashmap *jobs; /* Unit object => Job object list 1:1 */ + Job *anchor_job; /* the job the user asked for */ + bool irreversible; +}; + +Transaction *transaction_new(bool irreversible); +void transaction_free(Transaction *tr); + +int transaction_add_job_and_dependencies( + Transaction *tr, + JobType type, + Unit *unit, + Job *by, + bool matters, + bool conflicts, + bool ignore_requirements, + bool ignore_order, + sd_bus_error *e); +int transaction_activate(Transaction *tr, Manager *m, JobMode mode, sd_bus_error *e); +int transaction_add_isolate_jobs(Transaction *tr, Manager *m); +void transaction_abort(Transaction *tr); diff --git a/src/libcore/umount.c b/src/libcore/umount.c new file mode 100644 index 0000000000..a458768e7d --- /dev/null +++ b/src/libcore/umount.c @@ -0,0 +1,613 @@ +/*** + This file is part of systemd. + + Copyright 2010 ProFUSION embedded systems + + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "libudev.h" + +#include "alloc-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fstab-util.h" +#include "list.h" +#include "mount-setup.h" +#include "path-util.h" +#include "string-util.h" +#include "udev-util.h" +#include "umount.h" +#include "util.h" +#include "virt.h" + +typedef struct MountPoint { + char *path; + char *options; + dev_t devnum; + LIST_FIELDS(struct MountPoint, mount_point); +} MountPoint; + +static void mount_point_free(MountPoint **head, MountPoint *m) { + assert(head); + assert(m); + + LIST_REMOVE(mount_point, *head, m); + + free(m->path); + free(m); +} + +static void mount_points_list_free(MountPoint **head) { + assert(head); + + while (*head) + mount_point_free(head, *head); +} + +static int mount_points_list_get(MountPoint **head) { + _cleanup_fclose_ FILE *proc_self_mountinfo = NULL; + unsigned int i; + int r; + + assert(head); + + proc_self_mountinfo = fopen("/proc/self/mountinfo", "re"); + if (!proc_self_mountinfo) + return -errno; + + for (i = 1;; i++) { + _cleanup_free_ char *path = NULL, *options = NULL; + char *p = NULL; + MountPoint *m; + int k; + + k = fscanf(proc_self_mountinfo, + "%*s " /* (1) mount id */ + "%*s " /* (2) parent id */ + "%*s " /* (3) major:minor */ + "%*s " /* (4) root */ + "%ms " /* (5) mount point */ + "%*s" /* (6) mount flags */ + "%*[^-]" /* (7) optional fields */ + "- " /* (8) separator */ + "%*s " /* (9) file system type */ + "%*s" /* (10) mount source */ + "%ms" /* (11) mount options */ + "%*[^\n]", /* some rubbish at the end */ + &path, &options); + if (k != 2) { + if (k == EOF) + break; + + log_warning("Failed to parse /proc/self/mountinfo:%u.", i); + continue; + } + + r = cunescape(path, UNESCAPE_RELAX, &p); + if (r < 0) + return r; + + /* Ignore mount points we can't unmount because they + * are API or because we are keeping them open (like + * /dev/console). Also, ignore all mounts below API + * file systems, since they are likely virtual too, + * and hence not worth spending time on. Also, in + * unprivileged containers we might lack the rights to + * unmount these things, hence don't bother. */ + if (mount_point_is_api(p) || + mount_point_ignore(p) || + path_startswith(p, "/dev") || + path_startswith(p, "/sys") || + path_startswith(p, "/proc")) { + free(p); + continue; + } + + m = new0(MountPoint, 1); + if (!m) { + free(p); + return -ENOMEM; + } + + m->path = p; + m->options = options; + options = NULL; + + LIST_PREPEND(mount_point, *head, m); + } + + return 0; +} + +static int swap_list_get(MountPoint **head) { + _cleanup_fclose_ FILE *proc_swaps = NULL; + unsigned int i; + int r; + + assert(head); + + proc_swaps = fopen("/proc/swaps", "re"); + if (!proc_swaps) + return (errno == ENOENT) ? 0 : -errno; + + (void) fscanf(proc_swaps, "%*s %*s %*s %*s %*s\n"); + + for (i = 2;; i++) { + MountPoint *swap; + char *dev = NULL, *d; + int k; + + k = fscanf(proc_swaps, + "%ms " /* device/file */ + "%*s " /* type of swap */ + "%*s " /* swap size */ + "%*s " /* used */ + "%*s\n", /* priority */ + &dev); + + if (k != 1) { + if (k == EOF) + break; + + log_warning("Failed to parse /proc/swaps:%u.", i); + free(dev); + continue; + } + + if (endswith(dev, " (deleted)")) { + free(dev); + continue; + } + + r = cunescape(dev, UNESCAPE_RELAX, &d); + free(dev); + if (r < 0) + return r; + + swap = new0(MountPoint, 1); + if (!swap) { + free(d); + return -ENOMEM; + } + + swap->path = d; + LIST_PREPEND(mount_point, *head, swap); + } + + return 0; +} + +static int loopback_list_get(MountPoint **head) { + _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + _cleanup_udev_unref_ struct udev *udev = NULL; + int r; + + assert(head); + + udev = udev_new(); + if (!udev) + return -ENOMEM; + + e = udev_enumerate_new(udev); + if (!e) + return -ENOMEM; + + r = udev_enumerate_add_match_subsystem(e, "block"); + if (r < 0) + return r; + + r = udev_enumerate_add_match_sysname(e, "loop*"); + if (r < 0) + return r; + + r = udev_enumerate_add_match_sysattr(e, "loop/backing_file", NULL); + if (r < 0) + return r; + + r = udev_enumerate_scan_devices(e); + if (r < 0) + return r; + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + MountPoint *lb; + _cleanup_udev_device_unref_ struct udev_device *d; + char *loop; + const char *dn; + + d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!d) + return -ENOMEM; + + dn = udev_device_get_devnode(d); + if (!dn) + continue; + + loop = strdup(dn); + if (!loop) + return -ENOMEM; + + lb = new0(MountPoint, 1); + if (!lb) { + free(loop); + return -ENOMEM; + } + + lb->path = loop; + LIST_PREPEND(mount_point, *head, lb); + } + + return 0; +} + +static int dm_list_get(MountPoint **head) { + _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + _cleanup_udev_unref_ struct udev *udev = NULL; + int r; + + assert(head); + + udev = udev_new(); + if (!udev) + return -ENOMEM; + + e = udev_enumerate_new(udev); + if (!e) + return -ENOMEM; + + r = udev_enumerate_add_match_subsystem(e, "block"); + if (r < 0) + return r; + + r = udev_enumerate_add_match_sysname(e, "dm-*"); + if (r < 0) + return r; + + r = udev_enumerate_scan_devices(e); + if (r < 0) + return r; + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + MountPoint *m; + _cleanup_udev_device_unref_ struct udev_device *d; + dev_t devnum; + char *node; + const char *dn; + + d = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!d) + return -ENOMEM; + + devnum = udev_device_get_devnum(d); + dn = udev_device_get_devnode(d); + if (major(devnum) == 0 || !dn) + continue; + + node = strdup(dn); + if (!node) + return -ENOMEM; + + m = new(MountPoint, 1); + if (!m) { + free(node); + return -ENOMEM; + } + + m->path = node; + m->devnum = devnum; + LIST_PREPEND(mount_point, *head, m); + } + + return 0; +} + +static int delete_loopback(const char *device) { + _cleanup_close_ int fd = -1; + int r; + + fd = open(device, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return errno == ENOENT ? 0 : -errno; + + r = ioctl(fd, LOOP_CLR_FD, 0); + if (r >= 0) + return 1; + + /* ENXIO: not bound, so no error */ + if (errno == ENXIO) + return 0; + + return -errno; +} + +static int delete_dm(dev_t devnum) { + _cleanup_close_ int fd = -1; + int r; + struct dm_ioctl dm = { + .version = {DM_VERSION_MAJOR, + DM_VERSION_MINOR, + DM_VERSION_PATCHLEVEL}, + .data_size = sizeof(dm), + .dev = devnum, + }; + + assert(major(devnum) != 0); + + fd = open("/dev/mapper/control", O_RDWR|O_CLOEXEC); + if (fd < 0) + return -errno; + + r = ioctl(fd, DM_DEV_REMOVE, &dm); + return r >= 0 ? 0 : -errno; +} + +static int mount_points_list_umount(MountPoint **head, bool *changed, bool log_error) { + MountPoint *m, *n; + int n_failed = 0; + + assert(head); + + LIST_FOREACH_SAFE(mount_point, m, n, *head) { + + /* If we are in a container, don't attempt to + read-only mount anything as that brings no real + benefits, but might confuse the host, as we remount + the superblock here, not the bind mound. */ + if (detect_container() <= 0) { + _cleanup_free_ char *options = NULL; + /* MS_REMOUNT requires that the data parameter + * should be the same from the original mount + * except for the desired changes. Since we want + * to remount read-only, we should filter out + * rw (and ro too, because it confuses the kernel) */ + (void) fstab_filter_options(m->options, "rw\0ro\0", NULL, NULL, &options); + + /* We always try to remount directories + * read-only first, before we go on and umount + * them. + * + * Mount points can be stacked. If a mount + * point is stacked below / or /usr, we + * cannot umount or remount it directly, + * since there is no way to refer to the + * underlying mount. There's nothing we can do + * about it for the general case, but we can + * do something about it if it is aliased + * somehwere else via a bind mount. If we + * explicitly remount the super block of that + * alias read-only we hence should be + * relatively safe regarding keeping the fs we + * can otherwise not see dirty. */ + log_info("Remounting '%s' read-only with options '%s'.", m->path, options); + (void) mount(NULL, m->path, NULL, MS_REMOUNT|MS_RDONLY, options); + } + + /* Skip / and /usr since we cannot unmount that + * anyway, since we are running from it. They have + * already been remounted ro. */ + if (path_equal(m->path, "/") +#ifndef HAVE_SPLIT_USR + || path_equal(m->path, "/usr") +#endif + ) + continue; + + /* Trying to umount. We don't force here since we rely + * on busy NFS and FUSE file systems to return EBUSY + * until we closed everything on top of them. */ + log_info("Unmounting %s.", m->path); + if (umount2(m->path, 0) == 0) { + if (changed) + *changed = true; + + mount_point_free(head, m); + } else if (log_error) { + log_warning_errno(errno, "Could not unmount %s: %m", m->path); + n_failed++; + } + } + + return n_failed; +} + +static int swap_points_list_off(MountPoint **head, bool *changed) { + MountPoint *m, *n; + int n_failed = 0; + + assert(head); + + LIST_FOREACH_SAFE(mount_point, m, n, *head) { + log_info("Deactivating swap %s.", m->path); + if (swapoff(m->path) == 0) { + if (changed) + *changed = true; + + mount_point_free(head, m); + } else { + log_warning_errno(errno, "Could not deactivate swap %s: %m", m->path); + n_failed++; + } + } + + return n_failed; +} + +static int loopback_points_list_detach(MountPoint **head, bool *changed) { + MountPoint *m, *n; + int n_failed = 0, k; + struct stat root_st; + + assert(head); + + k = lstat("/", &root_st); + + LIST_FOREACH_SAFE(mount_point, m, n, *head) { + int r; + struct stat loopback_st; + + if (k >= 0 && + major(root_st.st_dev) != 0 && + lstat(m->path, &loopback_st) >= 0 && + root_st.st_dev == loopback_st.st_rdev) { + n_failed ++; + continue; + } + + log_info("Detaching loopback %s.", m->path); + r = delete_loopback(m->path); + if (r >= 0) { + if (r > 0 && changed) + *changed = true; + + mount_point_free(head, m); + } else { + log_warning_errno(errno, "Could not detach loopback %s: %m", m->path); + n_failed++; + } + } + + return n_failed; +} + +static int dm_points_list_detach(MountPoint **head, bool *changed) { + MountPoint *m, *n; + int n_failed = 0, k; + struct stat root_st; + + assert(head); + + k = lstat("/", &root_st); + + LIST_FOREACH_SAFE(mount_point, m, n, *head) { + int r; + + if (k >= 0 && + major(root_st.st_dev) != 0 && + root_st.st_dev == m->devnum) { + n_failed ++; + continue; + } + + log_info("Detaching DM %u:%u.", major(m->devnum), minor(m->devnum)); + r = delete_dm(m->devnum); + if (r >= 0) { + if (changed) + *changed = true; + + mount_point_free(head, m); + } else { + log_warning_errno(errno, "Could not detach DM %s: %m", m->path); + n_failed++; + } + } + + return n_failed; +} + +int umount_all(bool *changed) { + int r; + bool umount_changed; + LIST_HEAD(MountPoint, mp_list_head); + + LIST_HEAD_INIT(mp_list_head); + r = mount_points_list_get(&mp_list_head); + if (r < 0) + goto end; + + /* retry umount, until nothing can be umounted anymore */ + do { + umount_changed = false; + + mount_points_list_umount(&mp_list_head, &umount_changed, false); + if (umount_changed) + *changed = true; + + } while (umount_changed); + + /* umount one more time with logging enabled */ + r = mount_points_list_umount(&mp_list_head, &umount_changed, true); + if (r <= 0) + goto end; + + end: + mount_points_list_free(&mp_list_head); + + return r; +} + +int swapoff_all(bool *changed) { + int r; + LIST_HEAD(MountPoint, swap_list_head); + + LIST_HEAD_INIT(swap_list_head); + + r = swap_list_get(&swap_list_head); + if (r < 0) + goto end; + + r = swap_points_list_off(&swap_list_head, changed); + + end: + mount_points_list_free(&swap_list_head); + + return r; +} + +int loopback_detach_all(bool *changed) { + int r; + LIST_HEAD(MountPoint, loopback_list_head); + + LIST_HEAD_INIT(loopback_list_head); + + r = loopback_list_get(&loopback_list_head); + if (r < 0) + goto end; + + r = loopback_points_list_detach(&loopback_list_head, changed); + + end: + mount_points_list_free(&loopback_list_head); + + return r; +} + +int dm_detach_all(bool *changed) { + int r; + LIST_HEAD(MountPoint, dm_list_head); + + LIST_HEAD_INIT(dm_list_head); + + r = dm_list_get(&dm_list_head); + if (r < 0) + goto end; + + r = dm_points_list_detach(&dm_list_head, changed); + + end: + mount_points_list_free(&dm_list_head); + + return r; +} diff --git a/src/libcore/umount.h b/src/libcore/umount.h new file mode 100644 index 0000000000..4e2215a47d --- /dev/null +++ b/src/libcore/umount.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 ProFUSION embedded systems + + 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 . +***/ + +int umount_all(bool *changed); + +int swapoff_all(bool *changed); + +int loopback_detach_all(bool *changed); + +int dm_detach_all(bool *changed); diff --git a/src/libcore/unit-printf.c b/src/libcore/unit-printf.c new file mode 100644 index 0000000000..fc057d965c --- /dev/null +++ b/src/libcore/unit-printf.c @@ -0,0 +1,309 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "formats-util.h" +#include "macro.h" +#include "specifier.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit-printf.h" +#include "unit.h" +#include "user-util.h" + +static int specifier_prefix_and_instance(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + + assert(u); + + return unit_name_to_prefix_and_instance(u->id, ret); +} + +static int specifier_prefix(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + + assert(u); + + return unit_name_to_prefix(u->id, ret); +} + +static int specifier_prefix_unescaped(char specifier, void *data, void *userdata, char **ret) { + _cleanup_free_ char *p = NULL; + Unit *u = userdata; + int r; + + assert(u); + + r = unit_name_to_prefix(u->id, &p); + if (r < 0) + return r; + + return unit_name_unescape(p, ret); +} + +static int specifier_instance_unescaped(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + + assert(u); + + return unit_name_unescape(strempty(u->instance), ret); +} + +static int specifier_filename(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + + assert(u); + + if (u->instance) + return unit_name_path_unescape(u->instance, ret); + else + return unit_name_to_path(u->id, ret); +} + +static int specifier_cgroup(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + char *n; + + assert(u); + + if (u->cgroup_path) + n = strdup(u->cgroup_path); + else + n = unit_default_cgroup_path(u); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +static int specifier_cgroup_root(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + char *n; + + assert(u); + + n = strdup(u->manager->cgroup_root); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +static int specifier_cgroup_slice(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + char *n; + + assert(u); + + if (UNIT_ISSET(u->slice)) { + Unit *slice; + + slice = UNIT_DEREF(u->slice); + + if (slice->cgroup_path) + n = strdup(slice->cgroup_path); + else + n = unit_default_cgroup_path(slice); + } else + n = strdup(u->manager->cgroup_root); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +static int specifier_runtime(char specifier, void *data, void *userdata, char **ret) { + Unit *u = userdata; + const char *e; + char *n = NULL; + + assert(u); + + if (u->manager->running_as == MANAGER_SYSTEM) + e = "/run"; + else { + e = getenv("XDG_RUNTIME_DIR"); + if (!e) + return -EOPNOTSUPP; + } + + n = strdup(e); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +static int specifier_user_name(char specifier, void *data, void *userdata, char **ret) { + char *t; + + /* If we are UID 0 (root), this will not result in NSS, + * otherwise it might. This is good, as we want to be able to + * run this in PID 1, where our user ID is 0, but where NSS + * lookups are not allowed. */ + + t = getusername_malloc(); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; +} + +static int specifier_user_id(char specifier, void *data, void *userdata, char **ret) { + + if (asprintf(ret, UID_FMT, getuid()) < 0) + return -ENOMEM; + + return 0; +} + +static int specifier_user_home(char specifier, void *data, void *userdata, char **ret) { + + /* On PID 1 (which runs as root) this will not result in NSS, + * which is good. See above */ + + return get_home_dir(ret); +} + +static int specifier_user_shell(char specifier, void *data, void *userdata, char **ret) { + + /* On PID 1 (which runs as root) this will not result in NSS, + * which is good. See above */ + + return get_shell(ret); +} + +int unit_name_printf(Unit *u, const char* format, char **ret) { + + /* + * This will use the passed string as format string and + * replace the following specifiers: + * + * %n: the full id of the unit (foo@bar.waldo) + * %N: the id of the unit without the suffix (foo@bar) + * %p: the prefix (foo) + * %i: the instance (bar) + */ + + const Specifier table[] = { + { 'n', specifier_string, u->id }, + { 'N', specifier_prefix_and_instance, NULL }, + { 'p', specifier_prefix, NULL }, + { 'i', specifier_string, u->instance }, + { 0, NULL, NULL } + }; + + assert(u); + assert(format); + assert(ret); + + return specifier_printf(format, table, u, ret); +} + +int unit_full_printf(Unit *u, const char *format, char **ret) { + + /* This is similar to unit_name_printf() but also supports + * unescaping. Also, adds a couple of additional codes: + * + * %f the instance if set, otherwise the id + * %c cgroup path of unit + * %r where units in this slice are placed in the cgroup tree + * %R the root of this systemd's instance tree + * %t the runtime directory to place sockets in (e.g. "/run" or $XDG_RUNTIME_DIR) + * %U the UID of the running user + * %u the username of the running user + * %h the homedir of the running user + * %s the shell of the running user + * %m the machine ID of the running system + * %H the host name of the running system + * %b the boot ID of the running system + * %v `uname -r` of the running system + */ + + const Specifier table[] = { + { 'n', specifier_string, u->id }, + { 'N', specifier_prefix_and_instance, NULL }, + { 'p', specifier_prefix, NULL }, + { 'P', specifier_prefix_unescaped, NULL }, + { 'i', specifier_string, u->instance }, + { 'I', specifier_instance_unescaped, NULL }, + + { 'f', specifier_filename, NULL }, + { 'c', specifier_cgroup, NULL }, + { 'r', specifier_cgroup_slice, NULL }, + { 'R', specifier_cgroup_root, NULL }, + { 't', specifier_runtime, NULL }, + + { 'U', specifier_user_id, NULL }, + { 'u', specifier_user_name, NULL }, + { 'h', specifier_user_home, NULL }, + { 's', specifier_user_shell, NULL }, + + { 'm', specifier_machine_id, NULL }, + { 'H', specifier_host_name, NULL }, + { 'b', specifier_boot_id, NULL }, + { 'v', specifier_kernel_release, NULL }, + {} + }; + + assert(u); + assert(format); + assert(ret); + + return specifier_printf(format, table, u, ret); +} + +int unit_full_printf_strv(Unit *u, char **l, char ***ret) { + size_t n; + char **r, **i, **j; + int q; + + /* Applies unit_full_printf to every entry in l */ + + assert(u); + + n = strv_length(l); + r = new(char*, n+1); + if (!r) + return -ENOMEM; + + for (i = l, j = r; *i; i++, j++) { + q = unit_full_printf(u, *i, j); + if (q < 0) + goto fail; + } + + *j = NULL; + *ret = r; + return 0; + +fail: + for (j--; j >= r; j--) + free(*j); + + free(r); + return q; +} diff --git a/src/libcore/unit-printf.h b/src/libcore/unit-printf.h new file mode 100644 index 0000000000..4fc8531228 --- /dev/null +++ b/src/libcore/unit-printf.h @@ -0,0 +1,26 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include "unit.h" + +int unit_name_printf(Unit *u, const char* text, char **ret); +int unit_full_printf(Unit *u, const char *text, char **ret); +int unit_full_printf_strv(Unit *u, char **l, char ***ret); diff --git a/src/libcore/unit.c b/src/libcore/unit.c new file mode 100644 index 0000000000..4f7c0e2449 --- /dev/null +++ b/src/libcore/unit.c @@ -0,0 +1,3756 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include +#include + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "dbus-unit.h" +#include "dbus.h" +#include "dropin.h" +#include "escape.h" +#include "execute.h" +#include "fileio-label.h" +#include "formats-util.h" +#include "load-dropin.h" +#include "load-fragment.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "set.h" +#include "special.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "unit.h" +#include "user-util.h" +#include "virt.h" + +const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX] = { + [UNIT_SERVICE] = &service_vtable, + [UNIT_SOCKET] = &socket_vtable, + [UNIT_BUSNAME] = &busname_vtable, + [UNIT_TARGET] = &target_vtable, + [UNIT_DEVICE] = &device_vtable, + [UNIT_MOUNT] = &mount_vtable, + [UNIT_AUTOMOUNT] = &automount_vtable, + [UNIT_SWAP] = &swap_vtable, + [UNIT_TIMER] = &timer_vtable, + [UNIT_PATH] = &path_vtable, + [UNIT_SLICE] = &slice_vtable, + [UNIT_SCOPE] = &scope_vtable +}; + +static void maybe_warn_about_dependency(Unit *u, const char *other, UnitDependency dependency); + +Unit *unit_new(Manager *m, size_t size) { + Unit *u; + + assert(m); + assert(size >= sizeof(Unit)); + + u = malloc0(size); + if (!u) + return NULL; + + u->names = set_new(&string_hash_ops); + if (!u->names) { + free(u); + return NULL; + } + + u->manager = m; + u->type = _UNIT_TYPE_INVALID; + u->default_dependencies = true; + u->unit_file_state = _UNIT_FILE_STATE_INVALID; + u->unit_file_preset = -1; + u->on_failure_job_mode = JOB_REPLACE; + u->cgroup_inotify_wd = -1; + u->job_timeout = USEC_INFINITY; + + 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); + + return u; +} + +bool unit_has_name(Unit *u, const char *name) { + assert(u); + assert(name); + + return !!set_get(u->names, (char*) name); +} + +static void unit_init(Unit *u) { + CGroupContext *cc; + ExecContext *ec; + KillContext *kc; + + assert(u); + assert(u->manager); + assert(u->type >= 0); + + cc = unit_get_cgroup_context(u); + if (cc) { + cgroup_context_init(cc); + + /* Copy in the manager defaults into the cgroup + * context, _before_ the rest of the settings have + * been initialized */ + + cc->cpu_accounting = u->manager->default_cpu_accounting; + cc->blockio_accounting = u->manager->default_blockio_accounting; + cc->memory_accounting = u->manager->default_memory_accounting; + cc->tasks_accounting = u->manager->default_tasks_accounting; + + if (u->type != UNIT_SLICE) + cc->tasks_max = u->manager->default_tasks_max; + } + + ec = unit_get_exec_context(u); + if (ec) + exec_context_init(ec); + + kc = unit_get_kill_context(u); + if (kc) + kill_context_init(kc); + + if (UNIT_VTABLE(u)->init) + UNIT_VTABLE(u)->init(u); +} + +int unit_add_name(Unit *u, const char *text) { + _cleanup_free_ char *s = NULL, *i = NULL; + UnitType t; + int r; + + assert(u); + assert(text); + + if (unit_name_is_valid(text, UNIT_NAME_TEMPLATE)) { + + if (!u->instance) + return -EINVAL; + + r = unit_name_replace_instance(text, u->instance, &s); + if (r < 0) + return r; + } else { + s = strdup(text); + if (!s) + return -ENOMEM; + } + + if (set_contains(u->names, s)) + return 0; + if (hashmap_contains(u->manager->units, s)) + return -EEXIST; + + if (!unit_name_is_valid(s, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) + return -EINVAL; + + t = unit_name_to_type(s); + if (t < 0) + return -EINVAL; + + if (u->type != _UNIT_TYPE_INVALID && t != u->type) + return -EINVAL; + + r = unit_name_to_instance(s, &i); + if (r < 0) + return r; + + if (i && unit_vtable[t]->no_instances) + return -EINVAL; + + /* Ensure that this unit is either instanced or not instanced, + * but not both. Note that we do allow names with different + * instance names however! */ + if (u->type != _UNIT_TYPE_INVALID && !u->instance != !i) + return -EINVAL; + + if (unit_vtable[t]->no_alias && !set_isempty(u->names)) + return -EEXIST; + + if (hashmap_size(u->manager->units) >= MANAGER_MAX_NAMES) + return -E2BIG; + + r = set_put(u->names, s); + if (r < 0) + return r; + assert(r > 0); + + r = hashmap_put(u->manager->units, s, u); + if (r < 0) { + (void) set_remove(u->names, s); + return r; + } + + if (u->type == _UNIT_TYPE_INVALID) { + u->type = t; + u->id = s; + u->instance = i; + + LIST_PREPEND(units_by_type, u->manager->units_by_type[t], u); + + unit_init(u); + + i = NULL; + } + + s = NULL; + + unit_add_to_dbus_queue(u); + return 0; +} + +int unit_choose_id(Unit *u, const char *name) { + _cleanup_free_ char *t = NULL; + char *s, *i; + int r; + + assert(u); + assert(name); + + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { + + if (!u->instance) + return -EINVAL; + + r = unit_name_replace_instance(name, u->instance, &t); + if (r < 0) + return r; + + name = t; + } + + /* Selects one of the names of this unit as the id */ + s = set_get(u->names, (char*) name); + if (!s) + return -ENOENT; + + /* Determine the new instance from the new id */ + r = unit_name_to_instance(s, &i); + if (r < 0) + return r; + + u->id = s; + + free(u->instance); + u->instance = i; + + unit_add_to_dbus_queue(u); + + return 0; +} + +int unit_set_description(Unit *u, const char *description) { + char *s; + + assert(u); + + if (isempty(description)) + s = NULL; + else { + s = strdup(description); + if (!s) + return -ENOMEM; + } + + free(u->description); + u->description = s; + + unit_add_to_dbus_queue(u); + return 0; +} + +bool unit_check_gc(Unit *u) { + UnitActiveState state; + assert(u); + + if (u->job) + return true; + + if (u->nop_job) + return true; + + state = unit_active_state(u); + + /* If the unit is inactive and failed and no job is queued for + * it, then release its runtime resources */ + if (UNIT_IS_INACTIVE_OR_FAILED(state) && + UNIT_VTABLE(u)->release_resources) + UNIT_VTABLE(u)->release_resources(u); + + /* But we keep the unit object around for longer when it is + * referenced or configured to not be gc'ed */ + if (state != UNIT_INACTIVE) + return true; + + if (u->no_gc) + return true; + + if (u->refs) + return true; + + if (UNIT_VTABLE(u)->check_gc) + if (UNIT_VTABLE(u)->check_gc(u)) + return true; + + return false; +} + +void unit_add_to_load_queue(Unit *u) { + assert(u); + assert(u->type != _UNIT_TYPE_INVALID); + + if (u->load_state != UNIT_STUB || u->in_load_queue) + return; + + LIST_PREPEND(load_queue, u->manager->load_queue, u); + u->in_load_queue = true; +} + +void unit_add_to_cleanup_queue(Unit *u) { + assert(u); + + if (u->in_cleanup_queue) + return; + + LIST_PREPEND(cleanup_queue, u->manager->cleanup_queue, u); + u->in_cleanup_queue = true; +} + +void unit_add_to_gc_queue(Unit *u) { + assert(u); + + if (u->in_gc_queue || u->in_cleanup_queue) + return; + + if (unit_check_gc(u)) + return; + + LIST_PREPEND(gc_queue, u->manager->gc_queue, u); + u->in_gc_queue = true; + + u->manager->n_in_gc_queue ++; +} + +void unit_add_to_dbus_queue(Unit *u) { + assert(u); + assert(u->type != _UNIT_TYPE_INVALID); + + if (u->load_state == UNIT_STUB || u->in_dbus_queue) + return; + + /* Shortcut things if nobody cares */ + if (sd_bus_track_count(u->manager->subscribed) <= 0 && + set_isempty(u->manager->private_buses)) { + u->sent_dbus_new_signal = true; + return; + } + + LIST_PREPEND(dbus_queue, u->manager->dbus_unit_queue, u); + u->in_dbus_queue = true; +} + +static void bidi_set_free(Unit *u, Set *s) { + Iterator i; + Unit *other; + + assert(u); + + /* Frees the set and makes sure we are dropped from the + * inverse pointers */ + + SET_FOREACH(other, s, i) { + UnitDependency d; + + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) + set_remove(other->dependencies[d], u); + + unit_add_to_gc_queue(other); + } + + set_free(s); +} + +static void unit_remove_transient(Unit *u) { + char **i; + + assert(u); + + if (!u->transient) + return; + + if (u->fragment_path) + (void) unlink(u->fragment_path); + + STRV_FOREACH(i, u->dropin_paths) { + _cleanup_free_ char *p = NULL; + + (void) unlink(*i); + + p = dirname_malloc(*i); + if (p) + (void) rmdir(p); + } +} + +static void unit_free_requires_mounts_for(Unit *u) { + char **j; + + STRV_FOREACH(j, u->requires_mounts_for) { + char s[strlen(*j) + 1]; + + PATH_FOREACH_PREFIX_MORE(s, *j) { + char *y; + Set *x; + + x = hashmap_get2(u->manager->units_requiring_mounts_for, s, (void**) &y); + if (!x) + continue; + + set_remove(x, u); + + if (set_isempty(x)) { + hashmap_remove(u->manager->units_requiring_mounts_for, y); + free(y); + set_free(x); + } + } + } + + u->requires_mounts_for = strv_free(u->requires_mounts_for); +} + +static void unit_done(Unit *u) { + ExecContext *ec; + CGroupContext *cc; + + assert(u); + + if (u->type < 0) + return; + + if (UNIT_VTABLE(u)->done) + UNIT_VTABLE(u)->done(u); + + ec = unit_get_exec_context(u); + if (ec) + exec_context_done(ec); + + cc = unit_get_cgroup_context(u); + if (cc) + cgroup_context_done(cc); +} + +void unit_free(Unit *u) { + UnitDependency d; + Iterator i; + char *t; + + assert(u); + + if (u->manager->n_reloading <= 0) + unit_remove_transient(u); + + bus_unit_send_removed_signal(u); + + unit_done(u); + + sd_bus_slot_unref(u->match_bus_slot); + + unit_free_requires_mounts_for(u); + + SET_FOREACH(t, u->names, i) + hashmap_remove_value(u->manager->units, t, u); + + if (u->job) { + Job *j = u->job; + job_uninstall(j); + job_free(j); + } + + if (u->nop_job) { + Job *j = u->nop_job; + job_uninstall(j); + job_free(j); + } + + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) + bidi_set_free(u, u->dependencies[d]); + + if (u->type != _UNIT_TYPE_INVALID) + LIST_REMOVE(units_by_type, u->manager->units_by_type[u->type], u); + + if (u->in_load_queue) + LIST_REMOVE(load_queue, u->manager->load_queue, u); + + if (u->in_dbus_queue) + LIST_REMOVE(dbus_queue, u->manager->dbus_unit_queue, u); + + if (u->in_cleanup_queue) + LIST_REMOVE(cleanup_queue, u->manager->cleanup_queue, u); + + if (u->in_gc_queue) { + LIST_REMOVE(gc_queue, u->manager->gc_queue, u); + u->manager->n_in_gc_queue--; + } + + if (u->in_cgroup_queue) + LIST_REMOVE(cgroup_queue, u->manager->cgroup_queue, u); + + unit_release_cgroup(u); + + (void) manager_update_failed_units(u->manager, u, false); + set_remove(u->manager->startup_units, u); + + free(u->description); + strv_free(u->documentation); + free(u->fragment_path); + free(u->source_path); + strv_free(u->dropin_paths); + free(u->instance); + + free(u->job_timeout_reboot_arg); + + set_free_free(u->names); + + unit_unwatch_all_pids(u); + + condition_free_list(u->conditions); + condition_free_list(u->asserts); + + free(u->reboot_arg); + + unit_ref_unset(&u->slice); + + while (u->refs) + unit_ref_unset(u->refs); + + free(u); +} + +UnitActiveState unit_active_state(Unit *u) { + assert(u); + + if (u->load_state == UNIT_MERGED) + return unit_active_state(unit_follow_merge(u)); + + /* After a reload it might happen that a unit is not correctly + * loaded but still has a process around. That's why we won't + * shortcut failed loading to UNIT_INACTIVE_FAILED. */ + + return UNIT_VTABLE(u)->active_state(u); +} + +const char* unit_sub_state_to_string(Unit *u) { + assert(u); + + return UNIT_VTABLE(u)->sub_state_to_string(u); +} + +static int complete_move(Set **s, Set **other) { + int r; + + assert(s); + assert(other); + + if (!*other) + return 0; + + if (*s) { + r = set_move(*s, *other); + if (r < 0) + return r; + } else { + *s = *other; + *other = NULL; + } + + return 0; +} + +static int merge_names(Unit *u, Unit *other) { + char *t; + Iterator i; + int r; + + assert(u); + assert(other); + + r = complete_move(&u->names, &other->names); + if (r < 0) + return r; + + set_free_free(other->names); + other->names = NULL; + other->id = NULL; + + SET_FOREACH(t, u->names, i) + assert_se(hashmap_replace(u->manager->units, t, u) == 0); + + return 0; +} + +static int reserve_dependencies(Unit *u, Unit *other, UnitDependency d) { + unsigned n_reserve; + + assert(u); + assert(other); + assert(d < _UNIT_DEPENDENCY_MAX); + + /* + * If u does not have this dependency set allocated, there is no need + * to reserve anything. In that case other's set will be transferred + * as a whole to u by complete_move(). + */ + if (!u->dependencies[d]) + return 0; + + /* merge_dependencies() will skip a u-on-u dependency */ + n_reserve = set_size(other->dependencies[d]) - !!set_get(other->dependencies[d], u); + + return set_reserve(u->dependencies[d], n_reserve); +} + +static void merge_dependencies(Unit *u, Unit *other, const char *other_id, UnitDependency d) { + Iterator i; + Unit *back; + int r; + + assert(u); + assert(other); + assert(d < _UNIT_DEPENDENCY_MAX); + + /* Fix backwards pointers */ + SET_FOREACH(back, other->dependencies[d], i) { + UnitDependency k; + + for (k = 0; k < _UNIT_DEPENDENCY_MAX; k++) { + /* Do not add dependencies between u and itself */ + if (back == u) { + if (set_remove(back->dependencies[k], other)) + maybe_warn_about_dependency(u, other_id, k); + } else { + r = set_remove_and_put(back->dependencies[k], other, u); + if (r == -EEXIST) + set_remove(back->dependencies[k], other); + else + assert(r >= 0 || r == -ENOENT); + } + } + } + + /* Also do not move dependencies on u to itself */ + back = set_remove(other->dependencies[d], u); + if (back) + maybe_warn_about_dependency(u, other_id, d); + + /* The move cannot fail. The caller must have performed a reservation. */ + assert_se(complete_move(&u->dependencies[d], &other->dependencies[d]) == 0); + + other->dependencies[d] = set_free(other->dependencies[d]); +} + +int unit_merge(Unit *u, Unit *other) { + UnitDependency d; + const char *other_id = NULL; + int r; + + assert(u); + assert(other); + assert(u->manager == other->manager); + assert(u->type != _UNIT_TYPE_INVALID); + + other = unit_follow_merge(other); + + if (other == u) + return 0; + + if (u->type != other->type) + return -EINVAL; + + if (!u->instance != !other->instance) + return -EINVAL; + + if (other->load_state != UNIT_STUB && + other->load_state != UNIT_NOT_FOUND) + return -EEXIST; + + if (other->job) + return -EEXIST; + + if (other->nop_job) + return -EEXIST; + + if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) + return -EEXIST; + + if (other->id) + other_id = strdupa(other->id); + + /* Make reservations to ensure merge_dependencies() won't fail */ + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { + r = reserve_dependencies(u, other, d); + /* + * We don't rollback reservations if we fail. We don't have + * a way to undo reservations. A reservation is not a leak. + */ + if (r < 0) + return r; + } + + /* Merge names */ + r = merge_names(u, other); + if (r < 0) + return r; + + /* Redirect all references */ + while (other->refs) + unit_ref_set(other->refs, u); + + /* Merge dependencies */ + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) + merge_dependencies(u, other, other_id, d); + + other->load_state = UNIT_MERGED; + other->merged_into = u; + + /* If there is still some data attached to the other node, we + * don't need it anymore, and can free it. */ + if (other->load_state != UNIT_STUB) + if (UNIT_VTABLE(other)->done) + UNIT_VTABLE(other)->done(other); + + unit_add_to_dbus_queue(u); + unit_add_to_cleanup_queue(other); + + return 0; +} + +int unit_merge_by_name(Unit *u, const char *name) { + Unit *other; + int r; + _cleanup_free_ char *s = NULL; + + assert(u); + assert(name); + + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { + if (!u->instance) + return -EINVAL; + + r = unit_name_replace_instance(name, u->instance, &s); + if (r < 0) + return r; + + name = s; + } + + other = manager_get_unit(u->manager, name); + if (other) + return unit_merge(u, other); + + return unit_add_name(u, name); +} + +Unit* unit_follow_merge(Unit *u) { + assert(u); + + while (u->load_state == UNIT_MERGED) + assert_se(u = u->merged_into); + + return u; +} + +int unit_add_exec_dependencies(Unit *u, ExecContext *c) { + int r; + + assert(u); + assert(c); + + if (c->working_directory) { + r = unit_require_mounts_for(u, c->working_directory); + if (r < 0) + return r; + } + + if (c->root_directory) { + r = unit_require_mounts_for(u, c->root_directory); + if (r < 0) + return r; + } + + if (u->manager->running_as != MANAGER_SYSTEM) + return 0; + + if (c->private_tmp) { + r = unit_require_mounts_for(u, "/tmp"); + if (r < 0) + return r; + + r = unit_require_mounts_for(u, "/var/tmp"); + if (r < 0) + return r; + } + + if (c->std_output != EXEC_OUTPUT_KMSG && + c->std_output != EXEC_OUTPUT_SYSLOG && + c->std_output != EXEC_OUTPUT_JOURNAL && + c->std_output != EXEC_OUTPUT_KMSG_AND_CONSOLE && + c->std_output != EXEC_OUTPUT_SYSLOG_AND_CONSOLE && + c->std_output != EXEC_OUTPUT_JOURNAL_AND_CONSOLE && + c->std_error != EXEC_OUTPUT_KMSG && + c->std_error != EXEC_OUTPUT_SYSLOG && + c->std_error != EXEC_OUTPUT_JOURNAL && + c->std_error != EXEC_OUTPUT_KMSG_AND_CONSOLE && + c->std_error != EXEC_OUTPUT_JOURNAL_AND_CONSOLE && + c->std_error != EXEC_OUTPUT_SYSLOG_AND_CONSOLE) + return 0; + + /* If syslog or kernel logging is requested, make sure our own + * logging daemon is run first. */ + + r = unit_add_dependency_by_name(u, UNIT_AFTER, SPECIAL_JOURNALD_SOCKET, NULL, true); + if (r < 0) + return r; + + return 0; +} + +const char *unit_description(Unit *u) { + assert(u); + + if (u->description) + return u->description; + + return strna(u->id); +} + +void unit_dump(Unit *u, FILE *f, const char *prefix) { + char *t, **j; + UnitDependency d; + Iterator i; + const char *prefix2; + char + timestamp0[FORMAT_TIMESTAMP_MAX], + timestamp1[FORMAT_TIMESTAMP_MAX], + timestamp2[FORMAT_TIMESTAMP_MAX], + timestamp3[FORMAT_TIMESTAMP_MAX], + timestamp4[FORMAT_TIMESTAMP_MAX], + timespan[FORMAT_TIMESPAN_MAX]; + Unit *following; + _cleanup_set_free_ Set *following_set = NULL; + int r; + + assert(u); + assert(u->type >= 0); + + prefix = strempty(prefix); + prefix2 = strjoina(prefix, "\t"); + + fprintf(f, + "%s-> Unit %s:\n" + "%s\tDescription: %s\n" + "%s\tInstance: %s\n" + "%s\tUnit Load State: %s\n" + "%s\tUnit Active State: %s\n" + "%s\nState Change Timestamp: %s\n" + "%s\tInactive Exit Timestamp: %s\n" + "%s\tActive Enter Timestamp: %s\n" + "%s\tActive Exit Timestamp: %s\n" + "%s\tInactive Enter Timestamp: %s\n" + "%s\tGC Check Good: %s\n" + "%s\tNeed Daemon Reload: %s\n" + "%s\tTransient: %s\n" + "%s\tSlice: %s\n" + "%s\tCGroup: %s\n" + "%s\tCGroup realized: %s\n" + "%s\tCGroup mask: 0x%x\n" + "%s\tCGroup members mask: 0x%x\n", + prefix, u->id, + prefix, unit_description(u), + prefix, strna(u->instance), + prefix, unit_load_state_to_string(u->load_state), + prefix, unit_active_state_to_string(unit_active_state(u)), + prefix, strna(format_timestamp(timestamp0, sizeof(timestamp0), u->state_change_timestamp.realtime)), + prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->inactive_exit_timestamp.realtime)), + prefix, strna(format_timestamp(timestamp2, sizeof(timestamp2), u->active_enter_timestamp.realtime)), + prefix, strna(format_timestamp(timestamp3, sizeof(timestamp3), u->active_exit_timestamp.realtime)), + prefix, strna(format_timestamp(timestamp4, sizeof(timestamp4), u->inactive_enter_timestamp.realtime)), + prefix, yes_no(unit_check_gc(u)), + prefix, yes_no(unit_need_daemon_reload(u)), + prefix, yes_no(u->transient), + prefix, strna(unit_slice_name(u)), + prefix, strna(u->cgroup_path), + prefix, yes_no(u->cgroup_realized), + prefix, u->cgroup_realized_mask, + prefix, u->cgroup_members_mask); + + SET_FOREACH(t, u->names, i) + fprintf(f, "%s\tName: %s\n", prefix, t); + + STRV_FOREACH(j, u->documentation) + fprintf(f, "%s\tDocumentation: %s\n", prefix, *j); + + following = unit_following(u); + if (following) + fprintf(f, "%s\tFollowing: %s\n", prefix, following->id); + + r = unit_following_set(u, &following_set); + if (r >= 0) { + Unit *other; + + SET_FOREACH(other, following_set, i) + fprintf(f, "%s\tFollowing Set Member: %s\n", prefix, other->id); + } + + if (u->fragment_path) + fprintf(f, "%s\tFragment Path: %s\n", prefix, u->fragment_path); + + if (u->source_path) + fprintf(f, "%s\tSource Path: %s\n", prefix, u->source_path); + + STRV_FOREACH(j, u->dropin_paths) + fprintf(f, "%s\tDropIn Path: %s\n", prefix, *j); + + if (u->job_timeout != USEC_INFINITY) + fprintf(f, "%s\tJob Timeout: %s\n", prefix, format_timespan(timespan, sizeof(timespan), u->job_timeout, 0)); + + if (u->job_timeout_action != FAILURE_ACTION_NONE) + fprintf(f, "%s\tJob Timeout Action: %s\n", prefix, failure_action_to_string(u->job_timeout_action)); + + if (u->job_timeout_reboot_arg) + fprintf(f, "%s\tJob Timeout Reboot Argument: %s\n", prefix, u->job_timeout_reboot_arg); + + condition_dump_list(u->conditions, f, prefix, condition_type_to_string); + condition_dump_list(u->asserts, f, prefix, assert_type_to_string); + + if (dual_timestamp_is_set(&u->condition_timestamp)) + fprintf(f, + "%s\tCondition Timestamp: %s\n" + "%s\tCondition Result: %s\n", + prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->condition_timestamp.realtime)), + prefix, yes_no(u->condition_result)); + + if (dual_timestamp_is_set(&u->assert_timestamp)) + fprintf(f, + "%s\tAssert Timestamp: %s\n" + "%s\tAssert Result: %s\n", + prefix, strna(format_timestamp(timestamp1, sizeof(timestamp1), u->assert_timestamp.realtime)), + prefix, yes_no(u->assert_result)); + + for (d = 0; d < _UNIT_DEPENDENCY_MAX; d++) { + Unit *other; + + SET_FOREACH(other, u->dependencies[d], i) + fprintf(f, "%s\t%s: %s\n", prefix, unit_dependency_to_string(d), other->id); + } + + if (!strv_isempty(u->requires_mounts_for)) { + fprintf(f, + "%s\tRequiresMountsFor:", prefix); + + STRV_FOREACH(j, u->requires_mounts_for) + fprintf(f, " %s", *j); + + fputs("\n", f); + } + + if (u->load_state == UNIT_LOADED) { + + fprintf(f, + "%s\tStopWhenUnneeded: %s\n" + "%s\tRefuseManualStart: %s\n" + "%s\tRefuseManualStop: %s\n" + "%s\tDefaultDependencies: %s\n" + "%s\tOnFailureJobMode: %s\n" + "%s\tIgnoreOnIsolate: %s\n", + prefix, yes_no(u->stop_when_unneeded), + prefix, yes_no(u->refuse_manual_start), + prefix, yes_no(u->refuse_manual_stop), + prefix, yes_no(u->default_dependencies), + prefix, job_mode_to_string(u->on_failure_job_mode), + prefix, yes_no(u->ignore_on_isolate)); + + if (UNIT_VTABLE(u)->dump) + UNIT_VTABLE(u)->dump(u, f, prefix2); + + } else if (u->load_state == UNIT_MERGED) + fprintf(f, + "%s\tMerged into: %s\n", + prefix, u->merged_into->id); + else if (u->load_state == UNIT_ERROR) + fprintf(f, "%s\tLoad Error Code: %s\n", prefix, strerror(-u->load_error)); + + + if (u->job) + job_dump(u->job, f, prefix2); + + if (u->nop_job) + job_dump(u->nop_job, f, prefix2); + +} + +/* Common implementation for multiple backends */ +int unit_load_fragment_and_dropin(Unit *u) { + int r; + + assert(u); + + /* Load a .{service,socket,...} file */ + r = unit_load_fragment(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_STUB) + return -ENOENT; + + /* Load drop-in directory data */ + r = unit_load_dropin(unit_follow_merge(u)); + if (r < 0) + return r; + + return 0; +} + +/* Common implementation for multiple backends */ +int unit_load_fragment_and_dropin_optional(Unit *u) { + int r; + + assert(u); + + /* Same as unit_load_fragment_and_dropin(), but whether + * something can be loaded or not doesn't matter. */ + + /* Load a .service file */ + r = unit_load_fragment(u); + if (r < 0) + return r; + + if (u->load_state == UNIT_STUB) + u->load_state = UNIT_LOADED; + + /* Load drop-in directory data */ + r = unit_load_dropin(unit_follow_merge(u)); + if (r < 0) + return r; + + return 0; +} + +int unit_add_default_target_dependency(Unit *u, Unit *target) { + assert(u); + assert(target); + + if (target->type != UNIT_TARGET) + return 0; + + /* Only add the dependency if both units are loaded, so that + * that loop check below is reliable */ + if (u->load_state != UNIT_LOADED || + target->load_state != UNIT_LOADED) + return 0; + + /* If either side wants no automatic dependencies, then let's + * skip this */ + if (!u->default_dependencies || + !target->default_dependencies) + return 0; + + /* Don't create loops */ + if (set_get(target->dependencies[UNIT_BEFORE], u)) + return 0; + + return unit_add_dependency(target, UNIT_AFTER, u, true); +} + +static int unit_add_target_dependencies(Unit *u) { + + static const UnitDependency deps[] = { + UNIT_REQUIRED_BY, + UNIT_REQUISITE_OF, + UNIT_WANTED_BY, + UNIT_BOUND_BY + }; + + Unit *target; + Iterator i; + unsigned k; + int r = 0; + + assert(u); + + for (k = 0; k < ELEMENTSOF(deps); k++) + SET_FOREACH(target, u->dependencies[deps[k]], i) { + r = unit_add_default_target_dependency(u, target); + if (r < 0) + return r; + } + + return r; +} + +static int unit_add_slice_dependencies(Unit *u) { + assert(u); + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return 0; + + if (UNIT_ISSET(u->slice)) + return unit_add_two_dependencies(u, UNIT_AFTER, UNIT_REQUIRES, UNIT_DEREF(u->slice), true); + + if (unit_has_name(u, SPECIAL_ROOT_SLICE)) + return 0; + + return unit_add_two_dependencies_by_name(u, UNIT_AFTER, UNIT_REQUIRES, SPECIAL_ROOT_SLICE, NULL, true); +} + +static int unit_add_mount_dependencies(Unit *u) { + char **i; + int r; + + assert(u); + + STRV_FOREACH(i, u->requires_mounts_for) { + char prefix[strlen(*i) + 1]; + + PATH_FOREACH_PREFIX_MORE(prefix, *i) { + _cleanup_free_ char *p = NULL; + Unit *m; + + r = unit_name_from_path(prefix, ".mount", &p); + if (r < 0) + return r; + + m = manager_get_unit(u->manager, p); + if (!m) { + /* Make sure to load the mount unit if + * it exists. If so the dependencies + * on this unit will be added later + * during the loading of the mount + * unit. */ + (void) manager_load_unit_prepare(u->manager, p, NULL, NULL, &m); + continue; + } + if (m == u) + continue; + + if (m->load_state != UNIT_LOADED) + continue; + + r = unit_add_dependency(u, UNIT_AFTER, m, true); + if (r < 0) + return r; + + if (m->fragment_path) { + r = unit_add_dependency(u, UNIT_REQUIRES, m, true); + if (r < 0) + return r; + } + } + } + + return 0; +} + +static int unit_add_startup_units(Unit *u) { + CGroupContext *c; + int r; + + c = unit_get_cgroup_context(u); + if (!c) + return 0; + + if (c->startup_cpu_shares == CGROUP_CPU_SHARES_INVALID && + c->startup_blockio_weight == CGROUP_BLKIO_WEIGHT_INVALID) + return 0; + + r = set_ensure_allocated(&u->manager->startup_units, NULL); + if (r < 0) + return r; + + return set_put(u->manager->startup_units, u); +} + +int unit_load(Unit *u) { + int r; + + assert(u); + + if (u->in_load_queue) { + LIST_REMOVE(load_queue, u->manager->load_queue, u); + u->in_load_queue = false; + } + + if (u->type == _UNIT_TYPE_INVALID) + return -EINVAL; + + if (u->load_state != UNIT_STUB) + return 0; + + if (UNIT_VTABLE(u)->load) { + r = UNIT_VTABLE(u)->load(u); + if (r < 0) + goto fail; + } + + if (u->load_state == UNIT_STUB) { + r = -ENOENT; + goto fail; + } + + if (u->load_state == UNIT_LOADED) { + + r = unit_add_target_dependencies(u); + if (r < 0) + goto fail; + + r = unit_add_slice_dependencies(u); + if (r < 0) + goto fail; + + r = unit_add_mount_dependencies(u); + if (r < 0) + goto fail; + + r = unit_add_startup_units(u); + if (r < 0) + goto fail; + + if (u->on_failure_job_mode == JOB_ISOLATE && set_size(u->dependencies[UNIT_ON_FAILURE]) > 1) { + log_unit_error(u, "More than one OnFailure= dependencies specified but OnFailureJobMode=isolate set. Refusing."); + r = -EINVAL; + goto fail; + } + + unit_update_cgroup_members_masks(u); + } + + assert((u->load_state != UNIT_MERGED) == !u->merged_into); + + unit_add_to_dbus_queue(unit_follow_merge(u)); + unit_add_to_gc_queue(u); + + return 0; + +fail: + u->load_state = u->load_state == UNIT_STUB ? UNIT_NOT_FOUND : UNIT_ERROR; + u->load_error = r; + unit_add_to_dbus_queue(u); + unit_add_to_gc_queue(u); + + log_unit_debug_errno(u, r, "Failed to load configuration: %m"); + + return r; +} + +static bool unit_condition_test_list(Unit *u, Condition *first, const char *(*to_string)(ConditionType t)) { + Condition *c; + int triggered = -1; + + assert(u); + assert(to_string); + + /* If the condition list is empty, then it is true */ + if (!first) + return true; + + /* Otherwise, if all of the non-trigger conditions apply and + * if any of the trigger conditions apply (unless there are + * none) we return true */ + LIST_FOREACH(conditions, c, first) { + int r; + + r = condition_test(c); + if (r < 0) + log_unit_warning(u, + "Couldn't determine result for %s=%s%s%s, assuming failed: %m", + to_string(c->type), + c->trigger ? "|" : "", + c->negate ? "!" : "", + c->parameter); + else + log_unit_debug(u, + "%s=%s%s%s %s.", + to_string(c->type), + c->trigger ? "|" : "", + c->negate ? "!" : "", + c->parameter, + condition_result_to_string(c->result)); + + if (!c->trigger && r <= 0) + return false; + + if (c->trigger && triggered <= 0) + triggered = r > 0; + } + + return triggered != 0; +} + +static bool unit_condition_test(Unit *u) { + assert(u); + + dual_timestamp_get(&u->condition_timestamp); + u->condition_result = unit_condition_test_list(u, u->conditions, condition_type_to_string); + + return u->condition_result; +} + +static bool unit_assert_test(Unit *u) { + assert(u); + + dual_timestamp_get(&u->assert_timestamp); + u->assert_result = unit_condition_test_list(u, u->asserts, assert_type_to_string); + + return u->assert_result; +} + +void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) { + DISABLE_WARNING_FORMAT_NONLITERAL; + manager_status_printf(u->manager, STATUS_TYPE_NORMAL, status, unit_status_msg_format, unit_description(u)); + REENABLE_WARNING; +} + +_pure_ static const char* unit_get_status_message_format(Unit *u, JobType t) { + const char *format; + const UnitStatusMessageFormats *format_table; + + assert(u); + assert(IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)); + + if (t != JOB_RELOAD) { + format_table = &UNIT_VTABLE(u)->status_message_formats; + if (format_table) { + format = format_table->starting_stopping[t == JOB_STOP]; + if (format) + return format; + } + } + + /* Return generic strings */ + if (t == JOB_START) + return "Starting %s."; + else if (t == JOB_STOP) + return "Stopping %s."; + else + return "Reloading %s."; +} + +static void unit_status_print_starting_stopping(Unit *u, JobType t) { + const char *format; + + assert(u); + + /* Reload status messages have traditionally not been printed to console. */ + if (!IN_SET(t, JOB_START, JOB_STOP)) + return; + + format = unit_get_status_message_format(u, t); + + DISABLE_WARNING_FORMAT_NONLITERAL; + unit_status_printf(u, "", format); + REENABLE_WARNING; +} + +static void unit_status_log_starting_stopping_reloading(Unit *u, JobType t) { + const char *format; + char buf[LINE_MAX]; + sd_id128_t mid; + + assert(u); + + if (!IN_SET(t, JOB_START, JOB_STOP, JOB_RELOAD)) + return; + + if (log_on_console()) + return; + + /* We log status messages for all units and all operations. */ + + format = unit_get_status_message_format(u, t); + + DISABLE_WARNING_FORMAT_NONLITERAL; + xsprintf(buf, format, unit_description(u)); + REENABLE_WARNING; + + mid = t == JOB_START ? SD_MESSAGE_UNIT_STARTING : + t == JOB_STOP ? SD_MESSAGE_UNIT_STOPPING : + SD_MESSAGE_UNIT_RELOADING; + + /* Note that we deliberately use LOG_MESSAGE() instead of + * LOG_UNIT_MESSAGE() here, since this is supposed to mimic + * closely what is written to screen using the status output, + * which is supposed the highest level, friendliest output + * possible, which means we should avoid the low-level unit + * name. */ + log_struct(LOG_INFO, + LOG_MESSAGE_ID(mid), + LOG_UNIT_ID(u), + LOG_MESSAGE("%s", buf), + NULL); +} + +void unit_status_emit_starting_stopping_reloading(Unit *u, JobType t) { + assert(u); + assert(t >= 0); + assert(t < _JOB_TYPE_MAX); + + unit_status_log_starting_stopping_reloading(u, t); + unit_status_print_starting_stopping(u, t); +} + +static int unit_start_limit_test(Unit *u) { + assert(u); + + if (ratelimit_test(&u->start_limit)) { + u->start_limit_hit = false; + return 0; + } + + log_unit_warning(u, "Start request repeated too quickly."); + u->start_limit_hit = true; + + return failure_action(u->manager, u->start_limit_action, u->reboot_arg); +} + +/* Errors: + * -EBADR: This unit type does not support starting. + * -EALREADY: Unit is already started. + * -EAGAIN: An operation is already in progress. Retry later. + * -ECANCELED: Too many requests for now. + * -EPROTO: Assert failed + * -EINVAL: Unit not loaded + * -EOPNOTSUPP: Unit type not supported + */ +int unit_start(Unit *u) { + UnitActiveState state; + Unit *following; + int r; + + assert(u); + + /* If this is already started, then this will succeed. Note + * that this will even succeed if this unit is not startable + * by the user. This is relied on to detect when we need to + * wait for units and when waiting is finished. */ + state = unit_active_state(u); + if (UNIT_IS_ACTIVE_OR_RELOADING(state)) + return -EALREADY; + + /* Make sure we don't enter a busy loop of some kind. */ + r = unit_start_limit_test(u); + if (r < 0) + return r; + + /* Units that aren't loaded cannot be started */ + if (u->load_state != UNIT_LOADED) + return -EINVAL; + + /* If the conditions failed, don't do anything at all. If we + * already are activating this call might still be useful to + * speed up activation in case there is some hold-off time, + * but we don't want to recheck the condition in that case. */ + if (state != UNIT_ACTIVATING && + !unit_condition_test(u)) { + log_unit_debug(u, "Starting requested but condition failed. Not starting unit."); + return -EALREADY; + } + + /* If the asserts failed, fail the entire job */ + if (state != UNIT_ACTIVATING && + !unit_assert_test(u)) { + log_unit_notice(u, "Starting requested but asserts failed."); + return -EPROTO; + } + + /* Units of types that aren't supported cannot be + * started. Note that we do this test only after the condition + * checks, so that we rather return condition check errors + * (which are usually not considered a true failure) than "not + * supported" errors (which are considered a failure). + */ + if (!unit_supported(u)) + return -EOPNOTSUPP; + + /* Forward to the main object, if we aren't it. */ + following = unit_following(u); + if (following) { + log_unit_debug(u, "Redirecting start request from %s to %s.", u->id, following->id); + return unit_start(following); + } + + /* If it is stopped, but we cannot start it, then fail */ + if (!UNIT_VTABLE(u)->start) + return -EBADR; + + /* We don't suppress calls to ->start() here when we are + * already starting, to allow this request to be used as a + * "hurry up" call, for example when the unit is in some "auto + * restart" state where it waits for a holdoff timer to elapse + * before it will start again. */ + + unit_add_to_dbus_queue(u); + + return UNIT_VTABLE(u)->start(u); +} + +bool unit_can_start(Unit *u) { + assert(u); + + if (u->load_state != UNIT_LOADED) + return false; + + if (!unit_supported(u)) + return false; + + return !!UNIT_VTABLE(u)->start; +} + +bool unit_can_isolate(Unit *u) { + assert(u); + + return unit_can_start(u) && + u->allow_isolate; +} + +/* Errors: + * -EBADR: This unit type does not support stopping. + * -EALREADY: Unit is already stopped. + * -EAGAIN: An operation is already in progress. Retry later. + */ +int unit_stop(Unit *u) { + UnitActiveState state; + Unit *following; + + assert(u); + + state = unit_active_state(u); + if (UNIT_IS_INACTIVE_OR_FAILED(state)) + return -EALREADY; + + following = unit_following(u); + if (following) { + log_unit_debug(u, "Redirecting stop request from %s to %s.", u->id, following->id); + return unit_stop(following); + } + + if (!UNIT_VTABLE(u)->stop) + return -EBADR; + + unit_add_to_dbus_queue(u); + + return UNIT_VTABLE(u)->stop(u); +} + +/* Errors: + * -EBADR: This unit type does not support reloading. + * -ENOEXEC: Unit is not started. + * -EAGAIN: An operation is already in progress. Retry later. + */ +int unit_reload(Unit *u) { + UnitActiveState state; + Unit *following; + + assert(u); + + if (u->load_state != UNIT_LOADED) + return -EINVAL; + + if (!unit_can_reload(u)) + return -EBADR; + + state = unit_active_state(u); + if (state == UNIT_RELOADING) + return -EALREADY; + + if (state != UNIT_ACTIVE) { + log_unit_warning(u, "Unit cannot be reloaded because it is inactive."); + return -ENOEXEC; + } + + following = unit_following(u); + if (following) { + log_unit_debug(u, "Redirecting reload request from %s to %s.", u->id, following->id); + return unit_reload(following); + } + + unit_add_to_dbus_queue(u); + + return UNIT_VTABLE(u)->reload(u); +} + +bool unit_can_reload(Unit *u) { + assert(u); + + if (!UNIT_VTABLE(u)->reload) + return false; + + if (!UNIT_VTABLE(u)->can_reload) + return true; + + return UNIT_VTABLE(u)->can_reload(u); +} + +static void unit_check_unneeded(Unit *u) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + static const UnitDependency needed_dependencies[] = { + UNIT_REQUIRED_BY, + UNIT_REQUISITE_OF, + UNIT_WANTED_BY, + UNIT_BOUND_BY, + }; + + Unit *other; + Iterator i; + unsigned j; + int r; + + assert(u); + + /* If this service shall be shut down when unneeded then do + * so. */ + + if (!u->stop_when_unneeded) + return; + + if (!UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) + return; + + for (j = 0; j < ELEMENTSOF(needed_dependencies); j++) + SET_FOREACH(other, u->dependencies[needed_dependencies[j]], i) + if (unit_active_or_pending(other)) + return; + + /* If stopping a unit fails continously 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)) { + log_unit_warning(u, "Unit not needed anymore, but not stopping since we tried this too often recently."); + return; + } + + log_unit_info(u, "Unit not needed anymore. Stopping."); + + /* Ok, nobody needs us anymore. Sniff. Then let's commit suicide */ + r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); +} + +static void unit_check_binds_to(Unit *u) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + bool stop = false; + Unit *other; + Iterator i; + int r; + + assert(u); + + if (u->job) + return; + + if (unit_active_state(u) != UNIT_ACTIVE) + return; + + SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) { + if (other->job) + continue; + + if (!UNIT_IS_INACTIVE_OR_FAILED(unit_active_state(other))) + continue; + + stop = true; + break; + } + + if (!stop) + return; + + /* If stopping a unit fails continously 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)) { + log_unit_warning(u, "Unit is bound to inactive unit %s, but not stopping since we tried this too often recently.", other->id); + return; + } + + assert(other); + log_unit_info(u, "Unit is bound to inactive unit %s. Stopping, too.", other->id); + + /* A unit we need to run is gone. Sniff. Let's stop this. */ + r = manager_add_job(u->manager, JOB_STOP, u, JOB_FAIL, &error, NULL); + if (r < 0) + log_unit_warning_errno(u, r, "Failed to enqueue stop job, ignoring: %s", bus_error_message(&error, r)); +} + +static void retroactively_start_dependencies(Unit *u) { + Iterator i; + Unit *other; + + assert(u); + assert(UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))); + + SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i) + if (!set_get(u->dependencies[UNIT_AFTER], other) && + !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) + if (!set_get(u->dependencies[UNIT_AFTER], other) && + !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_START, other, JOB_REPLACE, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) + if (!set_get(u->dependencies[UNIT_AFTER], other) && + !UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_START, other, JOB_FAIL, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_CONFLICTS], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); + + SET_FOREACH(other, u->dependencies[UNIT_CONFLICTED_BY], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); +} + +static void retroactively_stop_dependencies(Unit *u) { + Iterator i; + Unit *other; + + assert(u); + assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); + + /* Pull down units which are bound to us recursively if enabled */ + SET_FOREACH(other, u->dependencies[UNIT_BOUND_BY], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + manager_add_job(u->manager, JOB_STOP, other, JOB_REPLACE, NULL, NULL); +} + +static void check_unneeded_dependencies(Unit *u) { + Iterator i; + Unit *other; + + assert(u); + assert(UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))); + + /* Garbage collect services that might not be needed anymore, if enabled */ + SET_FOREACH(other, u->dependencies[UNIT_REQUIRES], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); + SET_FOREACH(other, u->dependencies[UNIT_WANTS], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); + SET_FOREACH(other, u->dependencies[UNIT_REQUISITE], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); + SET_FOREACH(other, u->dependencies[UNIT_BINDS_TO], i) + if (!UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(other))) + unit_check_unneeded(other); +} + +void unit_start_on_failure(Unit *u) { + Unit *other; + Iterator i; + + assert(u); + + if (set_size(u->dependencies[UNIT_ON_FAILURE]) <= 0) + return; + + log_unit_info(u, "Triggering OnFailure= dependencies."); + + SET_FOREACH(other, u->dependencies[UNIT_ON_FAILURE], i) { + int r; + + r = manager_add_job(u->manager, JOB_START, other, u->on_failure_job_mode, NULL, NULL); + if (r < 0) + log_unit_error_errno(u, r, "Failed to enqueue OnFailure= job: %m"); + } +} + +void unit_trigger_notify(Unit *u) { + Unit *other; + Iterator i; + + assert(u); + + SET_FOREACH(other, u->dependencies[UNIT_TRIGGERED_BY], i) + if (UNIT_VTABLE(other)->trigger_notify) + UNIT_VTABLE(other)->trigger_notify(other, u); +} + +void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success) { + Manager *m; + bool unexpected; + + assert(u); + assert(os < _UNIT_ACTIVE_STATE_MAX); + assert(ns < _UNIT_ACTIVE_STATE_MAX); + + /* Note that this is called for all low-level state changes, + * even if they might map to the same high-level + * UnitActiveState! That means that ns == os is an expected + * behavior here. For example: if a mount point is remounted + * this function will be called too! */ + + m = u->manager; + + /* Update timestamps for state changes */ + if (m->n_reloading <= 0) { + dual_timestamp_get(&u->state_change_timestamp); + + if (UNIT_IS_INACTIVE_OR_FAILED(os) && !UNIT_IS_INACTIVE_OR_FAILED(ns)) + u->inactive_exit_timestamp = u->state_change_timestamp; + else if (!UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_INACTIVE_OR_FAILED(ns)) + u->inactive_enter_timestamp = u->state_change_timestamp; + + if (!UNIT_IS_ACTIVE_OR_RELOADING(os) && UNIT_IS_ACTIVE_OR_RELOADING(ns)) + u->active_enter_timestamp = u->state_change_timestamp; + else if (UNIT_IS_ACTIVE_OR_RELOADING(os) && !UNIT_IS_ACTIVE_OR_RELOADING(ns)) + u->active_exit_timestamp = u->state_change_timestamp; + } + + /* Keep track of failed units */ + (void) manager_update_failed_units(u->manager, u, ns == UNIT_FAILED); + + /* Make sure the cgroup is always removed when we become inactive */ + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + unit_prune_cgroup(u); + + /* Note that this doesn't apply to RemainAfterExit services exiting + * successfully, since there's no change of state in that case. Which is + * why it is handled in service_set_state() */ + if (UNIT_IS_INACTIVE_OR_FAILED(os) != UNIT_IS_INACTIVE_OR_FAILED(ns)) { + ExecContext *ec; + + ec = unit_get_exec_context(u); + if (ec && exec_context_may_touch_console(ec)) { + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) { + m->n_on_console --; + + if (m->n_on_console == 0) + /* unset no_console_output flag, since the console is free */ + m->no_console_output = false; + } else + m->n_on_console ++; + } + } + + if (u->job) { + unexpected = false; + + if (u->job->state == JOB_WAITING) + + /* So we reached a different state for this + * job. Let's see if we can run it now if it + * failed previously due to EAGAIN. */ + job_add_to_run_queue(u->job); + + /* Let's check whether this state change constitutes a + * finished job, or maybe contradicts a running job and + * hence needs to invalidate jobs. */ + + switch (u->job->type) { + + case JOB_START: + case JOB_VERIFY_ACTIVE: + + if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) + job_finish_and_invalidate(u->job, JOB_DONE, true); + else if (u->job->state == JOB_RUNNING && ns != UNIT_ACTIVATING) { + unexpected = true; + + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true); + } + + break; + + case JOB_RELOAD: + case JOB_RELOAD_OR_START: + case JOB_TRY_RELOAD: + + if (u->job->state == JOB_RUNNING) { + if (ns == UNIT_ACTIVE) + job_finish_and_invalidate(u->job, reload_success ? JOB_DONE : JOB_FAILED, true); + else if (ns != UNIT_ACTIVATING && ns != UNIT_RELOADING) { + unexpected = true; + + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + job_finish_and_invalidate(u->job, ns == UNIT_FAILED ? JOB_FAILED : JOB_DONE, true); + } + } + + break; + + case JOB_STOP: + case JOB_RESTART: + case JOB_TRY_RESTART: + + if (UNIT_IS_INACTIVE_OR_FAILED(ns)) + job_finish_and_invalidate(u->job, JOB_DONE, true); + else if (u->job->state == JOB_RUNNING && ns != UNIT_DEACTIVATING) { + unexpected = true; + job_finish_and_invalidate(u->job, JOB_FAILED, true); + } + + break; + + default: + assert_not_reached("Job type unknown"); + } + + } else + unexpected = true; + + if (m->n_reloading <= 0) { + + /* If this state change happened without being + * requested by a job, then let's retroactively start + * or stop dependencies. We skip that step when + * deserializing, since we don't want to create any + * additional jobs just because something is already + * activated. */ + + if (unexpected) { + if (UNIT_IS_INACTIVE_OR_FAILED(os) && UNIT_IS_ACTIVE_OR_ACTIVATING(ns)) + retroactively_start_dependencies(u); + else if (UNIT_IS_ACTIVE_OR_ACTIVATING(os) && UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) + retroactively_stop_dependencies(u); + } + + /* stop unneeded units regardless if going down was expected or not */ + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(ns)) + check_unneeded_dependencies(u); + + if (ns != os && ns == UNIT_FAILED) { + log_unit_notice(u, "Unit entered failed state."); + unit_start_on_failure(u); + } + } + + /* Some names are special */ + if (UNIT_IS_ACTIVE_OR_RELOADING(ns)) { + + if (unit_has_name(u, SPECIAL_DBUS_SERVICE)) + /* The bus might have just become available, + * hence try to connect to it, if we aren't + * yet connected. */ + bus_init(m, true); + + if (u->type == UNIT_SERVICE && + !UNIT_IS_ACTIVE_OR_RELOADING(os) && + m->n_reloading <= 0) { + /* Write audit record if we have just finished starting up */ + manager_send_unit_audit(m, u, AUDIT_SERVICE_START, true); + u->in_audit = true; + } + + if (!UNIT_IS_ACTIVE_OR_RELOADING(os)) + manager_send_unit_plymouth(m, u); + + } else { + + /* We don't care about D-Bus here, since we'll get an + * asynchronous notification for it anyway. */ + + if (u->type == UNIT_SERVICE && + UNIT_IS_INACTIVE_OR_FAILED(ns) && + !UNIT_IS_INACTIVE_OR_FAILED(os) && + m->n_reloading <= 0) { + + /* Hmm, if there was no start record written + * write it now, so that we always have a nice + * pair */ + if (!u->in_audit) { + manager_send_unit_audit(m, u, AUDIT_SERVICE_START, ns == UNIT_INACTIVE); + + if (ns == UNIT_INACTIVE) + manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, true); + } else + /* Write audit record if we have just finished shutting down */ + manager_send_unit_audit(m, u, AUDIT_SERVICE_STOP, ns == UNIT_INACTIVE); + + u->in_audit = false; + } + } + + manager_recheck_journal(m); + unit_trigger_notify(u); + + if (u->manager->n_reloading <= 0) { + /* Maybe we finished startup and are now ready for + * being stopped because unneeded? */ + unit_check_unneeded(u); + + /* Maybe we finished startup, but something we needed + * has vanished? Let's die then. (This happens when + * something BindsTo= to a Type=oneshot unit, as these + * units go directly from starting to inactive, + * without ever entering started.) */ + unit_check_binds_to(u); + } + + unit_add_to_dbus_queue(u); + unit_add_to_gc_queue(u); +} + +int unit_watch_pid(Unit *u, pid_t pid) { + int q, r; + + assert(u); + assert(pid >= 1); + + /* Watch a specific PID. We only support one or two units + * watching each PID for now, not more. */ + + r = set_ensure_allocated(&u->pids, NULL); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&u->manager->watch_pids1, NULL); + if (r < 0) + return r; + + r = hashmap_put(u->manager->watch_pids1, PID_TO_PTR(pid), u); + if (r == -EEXIST) { + r = hashmap_ensure_allocated(&u->manager->watch_pids2, NULL); + if (r < 0) + return r; + + r = hashmap_put(u->manager->watch_pids2, PID_TO_PTR(pid), u); + } + + q = set_put(u->pids, PID_TO_PTR(pid)); + if (q < 0) + return q; + + return r; +} + +void unit_unwatch_pid(Unit *u, pid_t pid) { + assert(u); + assert(pid >= 1); + + (void) hashmap_remove_value(u->manager->watch_pids1, PID_TO_PTR(pid), u); + (void) hashmap_remove_value(u->manager->watch_pids2, PID_TO_PTR(pid), u); + (void) set_remove(u->pids, PID_TO_PTR(pid)); +} + +void unit_unwatch_all_pids(Unit *u) { + assert(u); + + while (!set_isempty(u->pids)) + unit_unwatch_pid(u, PTR_TO_PID(set_first(u->pids))); + + u->pids = set_free(u->pids); +} + +void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2) { + Iterator i; + void *e; + + assert(u); + + /* Cleans dead PIDs from our list */ + + SET_FOREACH(e, u->pids, i) { + pid_t pid = PTR_TO_PID(e); + + if (pid == except1 || pid == except2) + continue; + + if (!pid_is_unwaited(pid)) + unit_unwatch_pid(u, pid); + } +} + +bool unit_job_is_applicable(Unit *u, JobType j) { + assert(u); + assert(j >= 0 && j < _JOB_TYPE_MAX); + + switch (j) { + + case JOB_VERIFY_ACTIVE: + case JOB_START: + case JOB_STOP: + case JOB_NOP: + return true; + + case JOB_RESTART: + case JOB_TRY_RESTART: + return unit_can_start(u); + + case JOB_RELOAD: + case JOB_TRY_RELOAD: + return unit_can_reload(u); + + case JOB_RELOAD_OR_START: + return unit_can_reload(u) && unit_can_start(u); + + default: + assert_not_reached("Invalid job type"); + } +} + +static void maybe_warn_about_dependency(Unit *u, const char *other, UnitDependency dependency) { + assert(u); + + /* Only warn about some unit types */ + if (!IN_SET(dependency, UNIT_CONFLICTS, UNIT_CONFLICTED_BY, UNIT_BEFORE, UNIT_AFTER, UNIT_ON_FAILURE, UNIT_TRIGGERS, UNIT_TRIGGERED_BY)) + return; + + if (streq_ptr(u->id, other)) + log_unit_warning(u, "Dependency %s=%s dropped", unit_dependency_to_string(dependency), u->id); + else + log_unit_warning(u, "Dependency %s=%s dropped, merged into %s", unit_dependency_to_string(dependency), strna(other), u->id); +} + +int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference) { + + static const UnitDependency inverse_table[_UNIT_DEPENDENCY_MAX] = { + [UNIT_REQUIRES] = UNIT_REQUIRED_BY, + [UNIT_WANTS] = UNIT_WANTED_BY, + [UNIT_REQUISITE] = UNIT_REQUISITE_OF, + [UNIT_BINDS_TO] = UNIT_BOUND_BY, + [UNIT_PART_OF] = UNIT_CONSISTS_OF, + [UNIT_REQUIRED_BY] = UNIT_REQUIRES, + [UNIT_REQUISITE_OF] = UNIT_REQUISITE, + [UNIT_WANTED_BY] = UNIT_WANTS, + [UNIT_BOUND_BY] = UNIT_BINDS_TO, + [UNIT_CONSISTS_OF] = UNIT_PART_OF, + [UNIT_CONFLICTS] = UNIT_CONFLICTED_BY, + [UNIT_CONFLICTED_BY] = UNIT_CONFLICTS, + [UNIT_BEFORE] = UNIT_AFTER, + [UNIT_AFTER] = UNIT_BEFORE, + [UNIT_ON_FAILURE] = _UNIT_DEPENDENCY_INVALID, + [UNIT_REFERENCES] = UNIT_REFERENCED_BY, + [UNIT_REFERENCED_BY] = UNIT_REFERENCES, + [UNIT_TRIGGERS] = UNIT_TRIGGERED_BY, + [UNIT_TRIGGERED_BY] = UNIT_TRIGGERS, + [UNIT_PROPAGATES_RELOAD_TO] = UNIT_RELOAD_PROPAGATED_FROM, + [UNIT_RELOAD_PROPAGATED_FROM] = UNIT_PROPAGATES_RELOAD_TO, + [UNIT_JOINS_NAMESPACE_OF] = UNIT_JOINS_NAMESPACE_OF, + }; + int r, q = 0, v = 0, w = 0; + Unit *orig_u = u, *orig_other = other; + + assert(u); + assert(d >= 0 && d < _UNIT_DEPENDENCY_MAX); + assert(other); + + u = unit_follow_merge(u); + other = unit_follow_merge(other); + + /* We won't allow dependencies on ourselves. We will not + * consider them an error however. */ + if (u == other) { + maybe_warn_about_dependency(orig_u, orig_other->id, d); + return 0; + } + + r = set_ensure_allocated(&u->dependencies[d], NULL); + if (r < 0) + return r; + + if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID) { + r = set_ensure_allocated(&other->dependencies[inverse_table[d]], NULL); + if (r < 0) + return r; + } + + if (add_reference) { + r = set_ensure_allocated(&u->dependencies[UNIT_REFERENCES], NULL); + if (r < 0) + return r; + + r = set_ensure_allocated(&other->dependencies[UNIT_REFERENCED_BY], NULL); + if (r < 0) + return r; + } + + q = set_put(u->dependencies[d], other); + if (q < 0) + return q; + + if (inverse_table[d] != _UNIT_DEPENDENCY_INVALID && inverse_table[d] != d) { + v = set_put(other->dependencies[inverse_table[d]], u); + if (v < 0) { + r = v; + goto fail; + } + } + + if (add_reference) { + w = set_put(u->dependencies[UNIT_REFERENCES], other); + if (w < 0) { + r = w; + goto fail; + } + + r = set_put(other->dependencies[UNIT_REFERENCED_BY], u); + if (r < 0) + goto fail; + } + + unit_add_to_dbus_queue(u); + return 0; + +fail: + if (q > 0) + set_remove(u->dependencies[d], other); + + if (v > 0) + set_remove(other->dependencies[inverse_table[d]], u); + + if (w > 0) + set_remove(u->dependencies[UNIT_REFERENCES], other); + + return r; +} + +int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference) { + int r; + + assert(u); + + r = unit_add_dependency(u, d, other, add_reference); + if (r < 0) + return r; + + return unit_add_dependency(u, e, other, add_reference); +} + +static int resolve_template(Unit *u, const char *name, const char*path, char **buf, const char **ret) { + int r; + + assert(u); + assert(name || path); + assert(buf); + assert(ret); + + if (!name) + name = basename(path); + + if (!unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { + *buf = NULL; + *ret = name; + return 0; + } + + if (u->instance) + r = unit_name_replace_instance(name, u->instance, buf); + else { + _cleanup_free_ char *i = NULL; + + r = unit_name_to_prefix(u->id, &i); + if (r < 0) + return r; + + r = unit_name_replace_instance(name, i, buf); + } + if (r < 0) + return r; + + *ret = *buf; + return 0; +} + +int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *path, bool add_reference) { + _cleanup_free_ char *buf = NULL; + Unit *other; + int r; + + assert(u); + assert(name || path); + + r = resolve_template(u, name, path, &buf, &name); + if (r < 0) + return r; + + r = manager_load_unit(u->manager, name, path, NULL, &other); + if (r < 0) + return r; + + return unit_add_dependency(u, d, other, add_reference); +} + +int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference) { + _cleanup_free_ char *buf = NULL; + Unit *other; + int r; + + assert(u); + assert(name || path); + + r = resolve_template(u, name, path, &buf, &name); + if (r < 0) + return r; + + r = manager_load_unit(u->manager, name, path, NULL, &other); + if (r < 0) + return r; + + return unit_add_two_dependencies(u, d, e, other, add_reference); +} + +int set_unit_path(const char *p) { + /* This is mostly for debug purposes */ + if (setenv("SYSTEMD_UNIT_PATH", p, 1) < 0) + return -errno; + + return 0; +} + +char *unit_dbus_path(Unit *u) { + assert(u); + + if (!u->id) + return NULL; + + return unit_dbus_path_from_name(u->id); +} + +int unit_set_slice(Unit *u, Unit *slice) { + assert(u); + assert(slice); + + /* Sets the unit slice if it has not been set before. Is extra + * careful, to only allow this for units that actually have a + * cgroup context. Also, we don't allow to set this for slices + * (since the parent slice is derived from the name). Make + * sure the unit we set is actually a slice. */ + + if (!UNIT_HAS_CGROUP_CONTEXT(u)) + return -EOPNOTSUPP; + + if (u->type == UNIT_SLICE) + return -EINVAL; + + if (unit_active_state(u) != UNIT_INACTIVE) + return -EBUSY; + + if (slice->type != UNIT_SLICE) + return -EINVAL; + + if (unit_has_name(u, SPECIAL_INIT_SCOPE) && + !unit_has_name(slice, SPECIAL_ROOT_SLICE)) + return -EPERM; + + if (UNIT_DEREF(u->slice) == slice) + return 0; + + if (UNIT_ISSET(u->slice)) + return -EBUSY; + + unit_ref_set(&u->slice, slice); + return 1; +} + +int unit_set_default_slice(Unit *u) { + _cleanup_free_ char *b = NULL; + const char *slice_name; + Unit *slice; + int r; + + assert(u); + + if (UNIT_ISSET(u->slice)) + return 0; + + if (u->instance) { + _cleanup_free_ char *prefix = NULL, *escaped = NULL; + + /* Implicitly place all instantiated units in their + * own per-template slice */ + + r = unit_name_to_prefix(u->id, &prefix); + if (r < 0) + return r; + + /* The prefix is already escaped, but it might include + * "-" which has a special meaning for slice units, + * hence escape it here extra. */ + escaped = unit_name_escape(prefix); + if (!escaped) + return -ENOMEM; + + if (u->manager->running_as == MANAGER_SYSTEM) + b = strjoin("system-", escaped, ".slice", NULL); + else + b = strappend(escaped, ".slice"); + if (!b) + return -ENOMEM; + + slice_name = b; + } else + slice_name = + u->manager->running_as == MANAGER_SYSTEM && !unit_has_name(u, SPECIAL_INIT_SCOPE) + ? SPECIAL_SYSTEM_SLICE + : SPECIAL_ROOT_SLICE; + + r = manager_load_unit(u->manager, slice_name, NULL, NULL, &slice); + if (r < 0) + return r; + + return unit_set_slice(u, slice); +} + +const char *unit_slice_name(Unit *u) { + assert(u); + + if (!UNIT_ISSET(u->slice)) + return NULL; + + return UNIT_DEREF(u->slice)->id; +} + +int unit_load_related_unit(Unit *u, const char *type, Unit **_found) { + _cleanup_free_ char *t = NULL; + int r; + + assert(u); + assert(type); + assert(_found); + + r = unit_name_change_suffix(u->id, type, &t); + if (r < 0) + return r; + if (unit_has_name(u, t)) + return -EINVAL; + + r = manager_load_unit(u->manager, t, NULL, NULL, _found); + assert(r < 0 || *_found != u); + return r; +} + +static int signal_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *name, *old_owner, *new_owner; + Unit *u = userdata; + int r; + + assert(message); + assert(u); + + r = sd_bus_message_read(message, "sss", &name, &old_owner, &new_owner); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (UNIT_VTABLE(u)->bus_name_owner_change) + UNIT_VTABLE(u)->bus_name_owner_change(u, name, old_owner, new_owner); + + return 0; +} + +int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name) { + const char *match; + + assert(u); + assert(bus); + assert(name); + + if (u->match_bus_slot) + return -EBUSY; + + match = strjoina("type='signal'," + "sender='org.freedesktop.DBus'," + "path='/org/freedesktop/DBus'," + "interface='org.freedesktop.DBus'," + "member='NameOwnerChanged'," + "arg0='", name, "'", + NULL); + + return sd_bus_add_match(bus, &u->match_bus_slot, match, signal_name_owner_changed, u); +} + +int unit_watch_bus_name(Unit *u, const char *name) { + int r; + + assert(u); + assert(name); + + /* Watch a specific name on the bus. We only support one unit + * watching each name for now. */ + + if (u->manager->api_bus) { + /* If the bus is already available, install the match directly. + * Otherwise, just put the name in the list. bus_setup_api() will take care later. */ + r = unit_install_bus_match(u, u->manager->api_bus, name); + if (r < 0) + return log_warning_errno(r, "Failed to subscribe to NameOwnerChanged signal for '%s': %m", name); + } + + r = hashmap_put(u->manager->watch_bus, name, u); + if (r < 0) { + u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot); + return log_warning_errno(r, "Failed to put bus name to hashmap: %m"); + } + + return 0; +} + +void unit_unwatch_bus_name(Unit *u, const char *name) { + assert(u); + assert(name); + + hashmap_remove_value(u->manager->watch_bus, name, u); + u->match_bus_slot = sd_bus_slot_unref(u->match_bus_slot); +} + +bool unit_can_serialize(Unit *u) { + assert(u); + + return UNIT_VTABLE(u)->serialize && UNIT_VTABLE(u)->deserialize_item; +} + +int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs) { + int r; + + assert(u); + assert(f); + assert(fds); + + if (unit_can_serialize(u)) { + ExecRuntime *rt; + + r = UNIT_VTABLE(u)->serialize(u, f, fds); + if (r < 0) + return r; + + rt = unit_get_exec_runtime(u); + if (rt) { + r = exec_runtime_serialize(u, rt, f, fds); + if (r < 0) + return r; + } + } + + dual_timestamp_serialize(f, "state-change-timestamp", &u->state_change_timestamp); + + dual_timestamp_serialize(f, "inactive-exit-timestamp", &u->inactive_exit_timestamp); + dual_timestamp_serialize(f, "active-enter-timestamp", &u->active_enter_timestamp); + dual_timestamp_serialize(f, "active-exit-timestamp", &u->active_exit_timestamp); + dual_timestamp_serialize(f, "inactive-enter-timestamp", &u->inactive_enter_timestamp); + + dual_timestamp_serialize(f, "condition-timestamp", &u->condition_timestamp); + dual_timestamp_serialize(f, "assert-timestamp", &u->assert_timestamp); + + if (dual_timestamp_is_set(&u->condition_timestamp)) + unit_serialize_item(u, f, "condition-result", yes_no(u->condition_result)); + + if (dual_timestamp_is_set(&u->assert_timestamp)) + unit_serialize_item(u, f, "assert-result", yes_no(u->assert_result)); + + unit_serialize_item(u, f, "transient", yes_no(u->transient)); + unit_serialize_item_format(u, f, "cpuacct-usage-base", "%" PRIu64, u->cpuacct_usage_base); + + if (u->cgroup_path) + unit_serialize_item(u, f, "cgroup", u->cgroup_path); + unit_serialize_item(u, f, "cgroup-realized", yes_no(u->cgroup_realized)); + + if (serialize_jobs) { + if (u->job) { + fprintf(f, "job\n"); + job_serialize(u->job, f, fds); + } + + if (u->nop_job) { + fprintf(f, "job\n"); + job_serialize(u->nop_job, f, fds); + } + } + + /* End marker */ + fputc('\n', f); + return 0; +} + +int unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value) { + assert(u); + assert(f); + assert(key); + + if (!value) + return 0; + + fputs(key, f); + fputc('=', f); + fputs(value, f); + fputc('\n', f); + + return 1; +} + +int unit_serialize_item_escaped(Unit *u, FILE *f, const char *key, const char *value) { + _cleanup_free_ char *c = NULL; + + assert(u); + assert(f); + assert(key); + + if (!value) + return 0; + + c = cescape(value); + if (!c) + return -ENOMEM; + + fputs(key, f); + fputc('=', f); + fputs(c, f); + fputc('\n', f); + + return 1; +} + +int unit_serialize_item_fd(Unit *u, FILE *f, FDSet *fds, const char *key, int fd) { + int copy; + + assert(u); + assert(f); + assert(key); + + if (fd < 0) + return 0; + + copy = fdset_put_dup(fds, fd); + if (copy < 0) + return copy; + + fprintf(f, "%s=%i\n", key, copy); + return 1; +} + +void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *format, ...) { + va_list ap; + + assert(u); + assert(f); + assert(key); + assert(format); + + fputs(key, f); + fputc('=', f); + + va_start(ap, format); + vfprintf(f, format, ap); + va_end(ap); + + fputc('\n', f); +} + +int unit_deserialize(Unit *u, FILE *f, FDSet *fds) { + ExecRuntime **rt = NULL; + size_t offset; + int r; + + assert(u); + assert(f); + assert(fds); + + offset = UNIT_VTABLE(u)->exec_runtime_offset; + if (offset > 0) + rt = (ExecRuntime**) ((uint8_t*) u + offset); + + for (;;) { + char line[LINE_MAX], *l, *v; + size_t k; + + if (!fgets(line, sizeof(line), f)) { + if (feof(f)) + return 0; + return -errno; + } + + char_array_0(line); + l = strstrip(line); + + /* End marker */ + if (isempty(l)) + break; + + k = strcspn(l, "="); + + if (l[k] == '=') { + l[k] = 0; + v = l+k+1; + } else + v = l+k; + + if (streq(l, "job")) { + if (v[0] == '\0') { + /* new-style serialized job */ + Job *j; + + j = job_new_raw(u); + if (!j) + return log_oom(); + + r = job_deserialize(j, f, fds); + if (r < 0) { + job_free(j); + return r; + } + + r = hashmap_put(u->manager->jobs, UINT32_TO_PTR(j->id), j); + if (r < 0) { + job_free(j); + return r; + } + + r = job_install_deserialized(j); + if (r < 0) { + hashmap_remove(u->manager->jobs, UINT32_TO_PTR(j->id)); + job_free(j); + return r; + } + } else /* legacy for pre-44 */ + log_unit_warning(u, "Update from too old systemd versions are unsupported, cannot deserialize job: %s", v); + continue; + } else if (streq(l, "state-change-timestamp")) { + dual_timestamp_deserialize(v, &u->state_change_timestamp); + continue; + } else if (streq(l, "inactive-exit-timestamp")) { + dual_timestamp_deserialize(v, &u->inactive_exit_timestamp); + continue; + } else if (streq(l, "active-enter-timestamp")) { + dual_timestamp_deserialize(v, &u->active_enter_timestamp); + continue; + } else if (streq(l, "active-exit-timestamp")) { + dual_timestamp_deserialize(v, &u->active_exit_timestamp); + continue; + } else if (streq(l, "inactive-enter-timestamp")) { + dual_timestamp_deserialize(v, &u->inactive_enter_timestamp); + continue; + } else if (streq(l, "condition-timestamp")) { + dual_timestamp_deserialize(v, &u->condition_timestamp); + continue; + } else if (streq(l, "assert-timestamp")) { + dual_timestamp_deserialize(v, &u->assert_timestamp); + continue; + } else if (streq(l, "condition-result")) { + + r = parse_boolean(v); + if (r < 0) + log_unit_debug(u, "Failed to parse condition result value %s, ignoring.", v); + else + u->condition_result = r; + + continue; + + } else if (streq(l, "assert-result")) { + + r = parse_boolean(v); + if (r < 0) + log_unit_debug(u, "Failed to parse assert result value %s, ignoring.", v); + else + u->assert_result = r; + + continue; + + } else if (streq(l, "transient")) { + + r = parse_boolean(v); + if (r < 0) + log_unit_debug(u, "Failed to parse transient bool %s, ignoring.", v); + else + u->transient = r; + + continue; + + } else if (streq(l, "cpuacct-usage-base")) { + + r = safe_atou64(v, &u->cpuacct_usage_base); + if (r < 0) + log_unit_debug(u, "Failed to parse CPU usage %s, ignoring.", v); + + continue; + + } else if (streq(l, "cgroup")) { + + r = unit_set_cgroup_path(u, v); + if (r < 0) + log_unit_debug_errno(u, r, "Failed to set cgroup path %s, ignoring: %m", v); + + (void) unit_watch_cgroup(u); + + continue; + } else if (streq(l, "cgroup-realized")) { + int b; + + b = parse_boolean(v); + if (b < 0) + log_unit_debug(u, "Failed to parse cgroup-realized bool %s, ignoring.", v); + else + u->cgroup_realized = b; + + continue; + } + + if (unit_can_serialize(u)) { + if (rt) { + r = exec_runtime_deserialize_item(u, rt, l, v, fds); + if (r < 0) { + log_unit_warning(u, "Failed to deserialize runtime parameter '%s', ignoring.", l); + continue; + } + + /* Returns positive if key was handled by the call */ + if (r > 0) + continue; + } + + r = UNIT_VTABLE(u)->deserialize_item(u, l, v, fds); + if (r < 0) + log_unit_warning(u, "Failed to deserialize unit parameter '%s', ignoring.", l); + } + } + + /* Versions before 228 did not carry a state change timestamp. In this case, take the current time. This is + * useful, so that timeouts based on this timestamp don't trigger too early, and is in-line with the logic from + * before 228 where the base for timeouts was not persistent across reboots. */ + + if (!dual_timestamp_is_set(&u->state_change_timestamp)) + dual_timestamp_get(&u->state_change_timestamp); + + return 0; +} + +int unit_add_node_link(Unit *u, const char *what, bool wants, UnitDependency dep) { + Unit *device; + _cleanup_free_ char *e = NULL; + int r; + + assert(u); + + /* Adds in links to the device node that this unit is based on */ + if (isempty(what)) + return 0; + + if (!is_device_path(what)) + return 0; + + /* When device units aren't supported (such as in a + * container), don't create dependencies on them. */ + if (!unit_type_supported(UNIT_DEVICE)) + return 0; + + r = unit_name_from_path(what, ".device", &e); + if (r < 0) + return r; + + r = manager_load_unit(u->manager, e, NULL, NULL, &device); + if (r < 0) + return r; + + r = unit_add_two_dependencies(u, UNIT_AFTER, + u->manager->running_as == MANAGER_SYSTEM ? dep : UNIT_WANTS, + device, true); + if (r < 0) + return r; + + if (wants) { + r = unit_add_dependency(device, UNIT_WANTS, u, false); + if (r < 0) + return r; + } + + return 0; +} + +int unit_coldplug(Unit *u) { + int r = 0, q = 0; + + assert(u); + + /* Make sure we don't enter a loop, when coldplugging + * recursively. */ + if (u->coldplugged) + return 0; + + u->coldplugged = true; + + if (UNIT_VTABLE(u)->coldplug) + r = UNIT_VTABLE(u)->coldplug(u); + + if (u->job) + q = job_coldplug(u->job); + + if (r < 0) + return r; + if (q < 0) + return q; + + return 0; +} + +bool unit_need_daemon_reload(Unit *u) { + _cleanup_strv_free_ char **t = NULL; + char **path; + struct stat st; + unsigned loaded_cnt, current_cnt; + + assert(u); + + if (u->fragment_path) { + zero(st); + if (stat(u->fragment_path, &st) < 0) + /* What, cannot access this anymore? */ + return true; + + if (u->fragment_mtime > 0 && + timespec_load(&st.st_mtim) != u->fragment_mtime) + return true; + } + + if (u->source_path) { + zero(st); + if (stat(u->source_path, &st) < 0) + return true; + + if (u->source_mtime > 0 && + timespec_load(&st.st_mtim) != u->source_mtime) + return true; + } + + (void) unit_find_dropin_paths(u, &t); + loaded_cnt = strv_length(t); + current_cnt = strv_length(u->dropin_paths); + + if (loaded_cnt == current_cnt) { + if (loaded_cnt == 0) + return false; + + if (strv_overlap(u->dropin_paths, t)) { + STRV_FOREACH(path, u->dropin_paths) { + zero(st); + if (stat(*path, &st) < 0) + return true; + + if (u->dropin_mtime > 0 && + timespec_load(&st.st_mtim) > u->dropin_mtime) + return true; + } + + return false; + } else + return true; + } else + return true; +} + +void unit_reset_failed(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->reset_failed) + UNIT_VTABLE(u)->reset_failed(u); + + RATELIMIT_RESET(u->start_limit); + u->start_limit_hit = false; +} + +Unit *unit_following(Unit *u) { + assert(u); + + if (UNIT_VTABLE(u)->following) + return UNIT_VTABLE(u)->following(u); + + return NULL; +} + +bool unit_stop_pending(Unit *u) { + assert(u); + + /* This call does check the current state of the unit. It's + * hence useful to be called from state change calls of the + * unit itself, where the state isn't updated yet. This is + * different from unit_inactive_or_pending() which checks both + * the current state and for a queued job. */ + + return u->job && u->job->type == JOB_STOP; +} + +bool unit_inactive_or_pending(Unit *u) { + assert(u); + + /* Returns true if the unit is inactive or going down */ + + if (UNIT_IS_INACTIVE_OR_DEACTIVATING(unit_active_state(u))) + return true; + + if (unit_stop_pending(u)) + return true; + + return false; +} + +bool unit_active_or_pending(Unit *u) { + assert(u); + + /* Returns true if the unit is active or going up */ + + if (UNIT_IS_ACTIVE_OR_ACTIVATING(unit_active_state(u))) + return true; + + if (u->job && + (u->job->type == JOB_START || + u->job->type == JOB_RELOAD_OR_START || + u->job->type == JOB_RESTART)) + return true; + + return false; +} + +int unit_kill(Unit *u, KillWho w, int signo, sd_bus_error *error) { + assert(u); + assert(w >= 0 && w < _KILL_WHO_MAX); + assert(signo > 0); + assert(signo < _NSIG); + + if (!UNIT_VTABLE(u)->kill) + return -EOPNOTSUPP; + + return UNIT_VTABLE(u)->kill(u, w, signo, error); +} + +static Set *unit_pid_set(pid_t main_pid, pid_t control_pid) { + Set *pid_set; + int r; + + pid_set = set_new(NULL); + if (!pid_set) + return NULL; + + /* Exclude the main/control pids from being killed via the cgroup */ + if (main_pid > 0) { + r = set_put(pid_set, PID_TO_PTR(main_pid)); + if (r < 0) + goto fail; + } + + if (control_pid > 0) { + r = set_put(pid_set, PID_TO_PTR(control_pid)); + if (r < 0) + goto fail; + } + + return pid_set; + +fail: + set_free(pid_set); + return NULL; +} + +int unit_kill_common( + Unit *u, + KillWho who, + int signo, + pid_t main_pid, + pid_t control_pid, + sd_bus_error *error) { + + int r = 0; + bool killed = false; + + if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL)) { + if (main_pid < 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no main processes", unit_type_to_string(u->type)); + else if (main_pid == 0) + return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No main process to kill"); + } + + if (IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL)) { + if (control_pid < 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_PROCESS, "%s units have no control processes", unit_type_to_string(u->type)); + else if (control_pid == 0) + return sd_bus_error_set_const(error, BUS_ERROR_NO_SUCH_PROCESS, "No control process to kill"); + } + + if (IN_SET(who, KILL_CONTROL, KILL_CONTROL_FAIL, KILL_ALL, KILL_ALL_FAIL)) + if (control_pid > 0) { + if (kill(control_pid, signo) < 0) + r = -errno; + else + killed = true; + } + + if (IN_SET(who, KILL_MAIN, KILL_MAIN_FAIL, KILL_ALL, KILL_ALL_FAIL)) + if (main_pid > 0) { + if (kill(main_pid, signo) < 0) + r = -errno; + else + killed = true; + } + + if (IN_SET(who, KILL_ALL, KILL_ALL_FAIL) && u->cgroup_path) { + _cleanup_set_free_ Set *pid_set = NULL; + int q; + + /* Exclude the main/control pids from being killed via the cgroup */ + pid_set = unit_pid_set(main_pid, control_pid); + if (!pid_set) + return -ENOMEM; + + q = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, signo, false, false, false, pid_set); + if (q < 0 && q != -EAGAIN && q != -ESRCH && q != -ENOENT) + r = q; + else + killed = true; + } + + if (r == 0 && !killed && IN_SET(who, KILL_ALL_FAIL, KILL_CONTROL_FAIL)) + return -ESRCH; + + return r; +} + +int unit_following_set(Unit *u, Set **s) { + assert(u); + assert(s); + + if (UNIT_VTABLE(u)->following_set) + return UNIT_VTABLE(u)->following_set(u, s); + + *s = NULL; + return 0; +} + +UnitFileState unit_get_unit_file_state(Unit *u) { + int r; + + assert(u); + + if (u->unit_file_state < 0 && u->fragment_path) { + r = unit_file_get_state( + u->manager->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, + NULL, + basename(u->fragment_path), + &u->unit_file_state); + if (r < 0) + u->unit_file_state = UNIT_FILE_BAD; + } + + return u->unit_file_state; +} + +int unit_get_unit_file_preset(Unit *u) { + assert(u); + + if (u->unit_file_preset < 0 && u->fragment_path) + u->unit_file_preset = unit_file_query_preset( + u->manager->running_as == MANAGER_SYSTEM ? UNIT_FILE_SYSTEM : UNIT_FILE_USER, + NULL, + basename(u->fragment_path)); + + return u->unit_file_preset; +} + +Unit* unit_ref_set(UnitRef *ref, Unit *u) { + assert(ref); + assert(u); + + if (ref->unit) + unit_ref_unset(ref); + + ref->unit = u; + LIST_PREPEND(refs, u->refs, ref); + return u; +} + +void unit_ref_unset(UnitRef *ref) { + assert(ref); + + if (!ref->unit) + return; + + LIST_REMOVE(refs, ref->unit->refs, ref); + ref->unit = NULL; +} + +int unit_patch_contexts(Unit *u) { + CGroupContext *cc; + ExecContext *ec; + unsigned i; + int r; + + assert(u); + + /* Patch in the manager defaults into the exec and cgroup + * contexts, _after_ the rest of the settings have been + * initialized */ + + ec = unit_get_exec_context(u); + if (ec) { + /* This only copies in the ones that need memory */ + for (i = 0; i < _RLIMIT_MAX; i++) + if (u->manager->rlimit[i] && !ec->rlimit[i]) { + ec->rlimit[i] = newdup(struct rlimit, u->manager->rlimit[i], 1); + if (!ec->rlimit[i]) + return -ENOMEM; + } + + if (u->manager->running_as == MANAGER_USER && + !ec->working_directory) { + + r = get_home_dir(&ec->working_directory); + if (r < 0) + return r; + + /* Allow user services to run, even if the + * home directory is missing */ + ec->working_directory_missing_ok = true; + } + + if (u->manager->running_as == MANAGER_USER && + (ec->syscall_whitelist || + !set_isempty(ec->syscall_filter) || + !set_isempty(ec->syscall_archs) || + ec->address_families_whitelist || + !set_isempty(ec->address_families))) + ec->no_new_privileges = true; + + if (ec->private_devices) + ec->capability_bounding_set &= ~(UINT64_C(1) << CAP_MKNOD); + } + + cc = unit_get_cgroup_context(u); + if (cc) { + + if (ec && + ec->private_devices && + cc->device_policy == CGROUP_AUTO) + cc->device_policy = CGROUP_CLOSED; + } + + return 0; +} + +ExecContext *unit_get_exec_context(Unit *u) { + size_t offset; + assert(u); + + if (u->type < 0) + return NULL; + + offset = UNIT_VTABLE(u)->exec_context_offset; + if (offset <= 0) + return NULL; + + return (ExecContext*) ((uint8_t*) u + offset); +} + +KillContext *unit_get_kill_context(Unit *u) { + size_t offset; + assert(u); + + if (u->type < 0) + return NULL; + + offset = UNIT_VTABLE(u)->kill_context_offset; + if (offset <= 0) + return NULL; + + return (KillContext*) ((uint8_t*) u + offset); +} + +CGroupContext *unit_get_cgroup_context(Unit *u) { + size_t offset; + + if (u->type < 0) + return NULL; + + offset = UNIT_VTABLE(u)->cgroup_context_offset; + if (offset <= 0) + return NULL; + + return (CGroupContext*) ((uint8_t*) u + offset); +} + +ExecRuntime *unit_get_exec_runtime(Unit *u) { + size_t offset; + + if (u->type < 0) + return NULL; + + offset = UNIT_VTABLE(u)->exec_runtime_offset; + if (offset <= 0) + return NULL; + + return *(ExecRuntime**) ((uint8_t*) u + offset); +} + +static int unit_drop_in_dir(Unit *u, UnitSetPropertiesMode mode, bool transient, char **dir) { + assert(u); + + if (u->manager->running_as == MANAGER_USER) { + int r; + + if (mode == UNIT_PERSISTENT && !transient) + r = user_config_home(dir); + else + r = user_runtime_dir(dir); + if (r == 0) + return -ENOENT; + + return r; + } + + if (mode == UNIT_PERSISTENT && !transient) + *dir = strdup("/etc/systemd/system"); + else + *dir = strdup("/run/systemd/system"); + if (!*dir) + return -ENOMEM; + + return 0; +} + +int unit_write_drop_in(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { + + _cleanup_free_ char *dir = NULL, *p = NULL, *q = NULL; + int r; + + assert(u); + + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) + return 0; + + r = unit_drop_in_dir(u, mode, u->transient, &dir); + if (r < 0) + return r; + + r = write_drop_in(dir, u->id, 50, name, data); + if (r < 0) + return r; + + r = drop_in_file(dir, u->id, 50, name, &p, &q); + if (r < 0) + return r; + + r = strv_extend(&u->dropin_paths, q); + if (r < 0) + return r; + + strv_sort(u->dropin_paths); + strv_uniq(u->dropin_paths); + + u->dropin_mtime = now(CLOCK_REALTIME); + + return 0; +} + +int unit_write_drop_in_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + assert(u); + assert(name); + assert(format); + + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) + return 0; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return unit_write_drop_in(u, mode, name, p); +} + +int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data) { + _cleanup_free_ char *ndata = NULL; + + assert(u); + assert(name); + assert(data); + + if (!UNIT_VTABLE(u)->private_section) + return -EINVAL; + + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) + return 0; + + ndata = strjoin("[", UNIT_VTABLE(u)->private_section, "]\n", data, NULL); + if (!ndata) + return -ENOMEM; + + return unit_write_drop_in(u, mode, name, ndata); +} + +int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) { + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + assert(u); + assert(name); + assert(format); + + if (!IN_SET(mode, UNIT_PERSISTENT, UNIT_RUNTIME)) + return 0; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return unit_write_drop_in_private(u, mode, name, p); +} + +int unit_make_transient(Unit *u) { + assert(u); + + if (!UNIT_VTABLE(u)->can_transient) + return -EOPNOTSUPP; + + u->load_state = UNIT_STUB; + u->load_error = 0; + u->transient = true; + + u->fragment_path = mfree(u->fragment_path); + u->source_path = mfree(u->source_path); + u->dropin_paths = strv_free(u->dropin_paths); + u->fragment_mtime = u->source_mtime = u->dropin_mtime = 0; + + unit_add_to_dbus_queue(u); + unit_add_to_gc_queue(u); + unit_add_to_load_queue(u); + + return 0; +} + +int unit_kill_context( + Unit *u, + KillContext *c, + KillOperation k, + pid_t main_pid, + pid_t control_pid, + bool main_pid_alien) { + + bool wait_for_exit = false; + int sig, r; + + assert(u); + assert(c); + + 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"); + } + + if (main_pid > 0) { + r = kill_and_sigcont(main_pid, sig); + + if (r < 0 && r != -ESRCH) { + _cleanup_free_ char *comm = NULL; + 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) + (void) kill(main_pid, SIGHUP); + } + } + + if (control_pid > 0) { + r = kill_and_sigcont(control_pid, sig); + + if (r < 0 && r != -ESRCH) { + _cleanup_free_ char *comm = NULL; + 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) + (void) kill(control_pid, SIGHUP); + } + } + + if (u->cgroup_path && + (c->kill_mode == KILL_CONTROL_GROUP || (c->kill_mode == KILL_MIXED && k == KILL_KILL))) { + _cleanup_set_free_ Set *pid_set = NULL; + + /* Exclude the main/control pids from being killed via the cgroup */ + pid_set = unit_pid_set(main_pid, control_pid); + if (!pid_set) + return -ENOMEM; + + r = cg_kill_recursive(SYSTEMD_CGROUP_CONTROLLER, u->cgroup_path, sig, true, k != KILL_TERMINATE, false, pid_set); + 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); + + } else if (r > 0) { + + /* FIXME: For now, on the legacy hierarchy, we + * will not wait for the cgroup members to die + * if we are running in a container or if this + * is a delegation unit, simply because cgroup + * notification is unreliable in these + * cases. It doesn't work at all in + * containers, and outside of containers it + * can be confused easily by left-over + * directories in the cgroup -- which however + * should not exist in non-delegated units. On + * the unified hierarchy that's different, + * there we get proper events. Hence rely on + * them.*/ + + if (cg_unified() > 0 || + (detect_container() == 0 && !unit_cgroup_delegate(u))) + wait_for_exit = true; + + if (c->send_sighup && k != KILL_KILL) { + 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); + } + } + } + + return wait_for_exit; +} + +int unit_require_mounts_for(Unit *u, const char *path) { + char prefix[strlen(path) + 1], *p; + int r; + + assert(u); + assert(path); + + /* Registers a unit for requiring a certain path and all its + * prefixes. We keep a simple array of these paths in the + * unit, since its usually short. However, we build a prefix + * table for all possible prefixes so that new appearing mount + * units can easily determine which units to make themselves a + * dependency of. */ + + if (!path_is_absolute(path)) + return -EINVAL; + + p = strdup(path); + if (!p) + return -ENOMEM; + + path_kill_slashes(p); + + if (!path_is_safe(p)) { + free(p); + return -EPERM; + } + + if (strv_contains(u->requires_mounts_for, p)) { + free(p); + return 0; + } + + r = strv_consume(&u->requires_mounts_for, p); + if (r < 0) + return r; + + PATH_FOREACH_PREFIX_MORE(prefix, p) { + Set *x; + + x = hashmap_get(u->manager->units_requiring_mounts_for, prefix); + if (!x) { + char *q; + + r = hashmap_ensure_allocated(&u->manager->units_requiring_mounts_for, &string_hash_ops); + if (r < 0) + return r; + + q = strdup(prefix); + if (!q) + return -ENOMEM; + + x = set_new(NULL); + if (!x) { + free(q); + return -ENOMEM; + } + + r = hashmap_put(u->manager->units_requiring_mounts_for, q, x); + if (r < 0) { + free(q); + set_free(x); + return r; + } + } + + r = set_put(x, u); + if (r < 0) + return r; + } + + return 0; +} + +int unit_setup_exec_runtime(Unit *u) { + ExecRuntime **rt; + size_t offset; + Iterator i; + Unit *other; + + offset = UNIT_VTABLE(u)->exec_runtime_offset; + assert(offset > 0); + + /* Check if there already is an ExecRuntime for this unit? */ + rt = (ExecRuntime**) ((uint8_t*) u + offset); + if (*rt) + return 0; + + /* Try to get it from somebody else */ + SET_FOREACH(other, u->dependencies[UNIT_JOINS_NAMESPACE_OF], i) { + + *rt = unit_get_exec_runtime(other); + if (*rt) { + exec_runtime_ref(*rt); + return 0; + } + } + + return exec_runtime_make(rt, unit_get_exec_context(u), u->id); +} + +bool unit_type_supported(UnitType t) { + if (_unlikely_(t < 0)) + return false; + if (_unlikely_(t >= _UNIT_TYPE_MAX)) + return false; + + if (!unit_vtable[t]->supported) + return true; + + return unit_vtable[t]->supported(); +} + +void unit_warn_if_dir_nonempty(Unit *u, const char* where) { + int r; + + assert(u); + assert(where); + + r = dir_is_empty(where); + if (r > 0) + return; + if (r < 0) { + log_unit_warning_errno(u, r, "Failed to check directory %s: %m", where); + return; + } + + log_struct(LOG_NOTICE, + LOG_MESSAGE_ID(SD_MESSAGE_OVERMOUNTING), + LOG_UNIT_ID(u), + LOG_UNIT_MESSAGE(u, "Directory %s to mount over is not empty, mounting anyway.", where), + "WHERE=%s", where, + NULL); +} + +int unit_fail_if_symlink(Unit *u, const char* where) { + int r; + + assert(u); + assert(where); + + r = is_symlink(where); + if (r < 0) { + log_unit_debug_errno(u, r, "Failed to check symlink %s, ignoring: %m", where); + return 0; + } + if (r == 0) + return 0; + + log_struct(LOG_ERR, + LOG_MESSAGE_ID(SD_MESSAGE_OVERMOUNTING), + LOG_UNIT_ID(u), + LOG_UNIT_MESSAGE(u, "Mount on symlink %s not allowed.", where), + "WHERE=%s", where, + NULL); + + return -ELOOP; +} + +bool unit_is_pristine(Unit *u) { + assert(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 + * actually loaded, as those unit types don't require a file + * on disk to validly load. */ + + return !(!IN_SET(u->load_state, UNIT_NOT_FOUND, UNIT_LOADED) || + u->fragment_path || + u->source_path || + !strv_isempty(u->dropin_paths) || + u->job || + u->merged_into); +} diff --git a/src/libcore/unit.h b/src/libcore/unit.h new file mode 100644 index 0000000000..601e763ce2 --- /dev/null +++ b/src/libcore/unit.h @@ -0,0 +1,630 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +typedef struct Unit Unit; +typedef struct UnitVTable UnitVTable; +typedef struct UnitRef UnitRef; +typedef struct UnitStatusMessageFormats UnitStatusMessageFormats; + +#include "condition.h" +#include "failure-action.h" +#include "install.h" +#include "list.h" +#include "unit-name.h" + +typedef enum KillOperation { + KILL_TERMINATE, + KILL_KILL, + KILL_ABORT, + _KILL_OPERATION_MAX, + _KILL_OPERATION_INVALID = -1 +} KillOperation; + +static inline bool UNIT_IS_ACTIVE_OR_RELOADING(UnitActiveState t) { + return t == UNIT_ACTIVE || t == UNIT_RELOADING; +} + +static inline bool UNIT_IS_ACTIVE_OR_ACTIVATING(UnitActiveState t) { + return t == UNIT_ACTIVE || t == UNIT_ACTIVATING || t == UNIT_RELOADING; +} + +static inline bool UNIT_IS_INACTIVE_OR_DEACTIVATING(UnitActiveState t) { + return t == UNIT_INACTIVE || t == UNIT_FAILED || t == UNIT_DEACTIVATING; +} + +static inline bool UNIT_IS_INACTIVE_OR_FAILED(UnitActiveState t) { + return t == UNIT_INACTIVE || t == UNIT_FAILED; +} + +#include "job.h" + +struct UnitRef { + /* Keeps tracks of references to a unit. This is useful so + * that we can merge two units if necessary and correct all + * references to them */ + + Unit* unit; + LIST_FIELDS(UnitRef, refs); +}; + +struct Unit { + Manager *manager; + + UnitType type; + UnitLoadState load_state; + Unit *merged_into; + + char *id; /* One name is special because we use it for identification. Points to an entry in the names set */ + char *instance; + + Set *names; + Set *dependencies[_UNIT_DEPENDENCY_MAX]; + + char **requires_mounts_for; + + char *description; + char **documentation; + + char *fragment_path; /* if loaded from a config file this is the primary path to it */ + char *source_path; /* if converted, the source file */ + char **dropin_paths; + + usec_t fragment_mtime; + usec_t source_mtime; + usec_t dropin_mtime; + + /* If there is something to do with this unit, then this is the installed job for it */ + Job *job; + + /* JOB_NOP jobs are special and can be installed without disturbing the real job. */ + Job *nop_job; + + /* The slot used for watching NameOwnerChanged signals */ + sd_bus_slot *match_bus_slot; + + /* Job timeout and action to take */ + usec_t job_timeout; + FailureAction job_timeout_action; + char *job_timeout_reboot_arg; + + /* References to this */ + LIST_HEAD(UnitRef, refs); + + /* Conditions to check */ + LIST_HEAD(Condition, conditions); + LIST_HEAD(Condition, asserts); + + dual_timestamp condition_timestamp; + dual_timestamp assert_timestamp; + + /* Updated whenever the low-level state changes */ + dual_timestamp state_change_timestamp; + + /* Updated whenever the (high-level) active state enters or leaves the active or inactive states */ + dual_timestamp inactive_exit_timestamp; + dual_timestamp active_enter_timestamp; + dual_timestamp active_exit_timestamp; + dual_timestamp inactive_enter_timestamp; + + UnitRef slice; + + /* Per type list */ + LIST_FIELDS(Unit, units_by_type); + + /* All units which have requires_mounts_for set */ + LIST_FIELDS(Unit, has_requires_mounts_for); + + /* Load queue */ + LIST_FIELDS(Unit, load_queue); + + /* D-Bus queue */ + LIST_FIELDS(Unit, dbus_queue); + + /* Cleanup queue */ + LIST_FIELDS(Unit, cleanup_queue); + + /* GC queue */ + LIST_FIELDS(Unit, gc_queue); + + /* CGroup realize members queue */ + LIST_FIELDS(Unit, cgroup_queue); + + /* Units with the same CGroup netclass */ + LIST_FIELDS(Unit, cgroup_netclass); + + /* PIDs we keep an eye on. Note that a unit might have many + * more, but these are the ones we care enough about to + * process SIGCHLD for */ + Set *pids; + + /* Used during GC sweeps */ + unsigned gc_marker; + + /* Error code when we didn't manage to load the unit (negative) */ + int load_error; + + /* Put a ratelimit on unit starting */ + RateLimit start_limit; + FailureAction start_limit_action; + char *reboot_arg; + + /* Make sure we never enter endless loops with the check unneeded logic, or the BindsTo= logic */ + RateLimit auto_stop_ratelimit; + + /* Cached unit file state and preset */ + UnitFileState unit_file_state; + int unit_file_preset; + + /* Where the cpuacct.usage cgroup counter was at the time the unit was started */ + nsec_t cpuacct_usage_base; + + /* Counterparts in the cgroup filesystem */ + char *cgroup_path; + CGroupMask cgroup_realized_mask; + CGroupMask cgroup_subtree_mask; + CGroupMask cgroup_members_mask; + int cgroup_inotify_wd; + + uint32_t cgroup_netclass_id; + + /* How to start OnFailure units */ + JobMode on_failure_job_mode; + + /* Garbage collect us we nobody wants or requires us anymore */ + bool stop_when_unneeded; + + /* Create default dependencies */ + bool default_dependencies; + + /* Refuse manual starting, allow starting only indirectly via dependency. */ + bool refuse_manual_start; + + /* Don't allow the user to stop this unit manually, allow stopping only indirectly via dependency. */ + bool refuse_manual_stop; + + /* Allow isolation requests */ + bool allow_isolate; + + /* Ignore this unit when isolating */ + bool ignore_on_isolate; + + /* Did the last condition check succeed? */ + bool condition_result; + bool assert_result; + + /* Is this a transient unit? */ + bool transient; + + bool in_load_queue:1; + bool in_dbus_queue:1; + bool in_cleanup_queue:1; + bool in_gc_queue:1; + bool in_cgroup_queue:1; + + bool sent_dbus_new_signal:1; + + bool no_gc:1; + + bool in_audit:1; + + bool cgroup_realized:1; + bool cgroup_members_mask_valid:1; + bool cgroup_subtree_mask_valid:1; + + bool start_limit_hit:1; + + /* Did we already invoke unit_coldplug() for this unit? */ + bool coldplugged:1; +}; + +struct UnitStatusMessageFormats { + const char *starting_stopping[2]; + const char *finished_start_job[_JOB_RESULT_MAX]; + const char *finished_stop_job[_JOB_RESULT_MAX]; +}; + +typedef enum UnitSetPropertiesMode { + UNIT_CHECK = 0, + UNIT_RUNTIME = 1, + UNIT_PERSISTENT = 2, +} UnitSetPropertiesMode; + +#include "automount.h" +#include "busname.h" +#include "device.h" +#include "path.h" +#include "scope.h" +#include "slice.h" +#include "socket.h" +#include "swap.h" +#include "target.h" +#include "timer.h" + +struct UnitVTable { + /* How much memory does an object of this unit type need */ + size_t object_size; + + /* If greater than 0, the offset into the object where + * ExecContext is found, if the unit type has that */ + size_t exec_context_offset; + + /* If greater than 0, the offset into the object where + * CGroupContext is found, if the unit type has that */ + size_t cgroup_context_offset; + + /* If greater than 0, the offset into the object where + * KillContext is found, if the unit type has that */ + size_t kill_context_offset; + + /* If greater than 0, the offset into the object where the + * pointer to ExecRuntime is found, if the unit type has + * that */ + size_t exec_runtime_offset; + + /* The name of the configuration file section with the private settings of this unit */ + const char *private_section; + + /* Config file sections this unit type understands, separated + * by NUL chars */ + const char *sections; + + /* This should reset all type-specific variables. This should + * not allocate memory, and is called with zero-initialized + * data. It should hence only initialize variables that need + * to be set != 0. */ + void (*init)(Unit *u); + + /* This should free all type-specific variables. It should be + * idempotent. */ + void (*done)(Unit *u); + + /* Actually load data from disk. This may fail, and should set + * load_state to UNIT_LOADED, UNIT_MERGED or leave it at + * UNIT_STUB if no configuration could be found. */ + int (*load)(Unit *u); + + /* If a lot of units got created via enumerate(), this is + * where to actually set the state and call unit_notify(). */ + int (*coldplug)(Unit *u); + + void (*dump)(Unit *u, FILE *f, const char *prefix); + + int (*start)(Unit *u); + int (*stop)(Unit *u); + int (*reload)(Unit *u); + + int (*kill)(Unit *u, KillWho w, int signo, sd_bus_error *error); + + bool (*can_reload)(Unit *u); + + /* Write all data that cannot be restored from other sources + * away using unit_serialize_item() */ + int (*serialize)(Unit *u, FILE *f, FDSet *fds); + + /* Restore one item from the serialization */ + int (*deserialize_item)(Unit *u, const char *key, const char *data, FDSet *fds); + + /* Try to match up fds with what we need for this unit */ + void (*distribute_fds)(Unit *u, FDSet *fds); + + /* Boils down the more complex internal state of this unit to + * a simpler one that the engine can understand */ + UnitActiveState (*active_state)(Unit *u); + + /* Returns the substate specific to this unit type as + * string. This is purely information so that we can give the + * user a more fine grained explanation in which actual state a + * unit is in. */ + const char* (*sub_state_to_string)(Unit *u); + + /* Return true when there is reason to keep this entry around + * even nothing references it and it isn't active in any + * way */ + bool (*check_gc)(Unit *u); + + /* When the unit is not running and no job for it queued we + * shall release its runtime resources */ + void (*release_resources)(Unit *u); + + /* Invoked on every child that died */ + void (*sigchld_event)(Unit *u, pid_t pid, int code, int status); + + /* Reset failed state if we are in failed state */ + void (*reset_failed)(Unit *u); + + /* Called whenever any of the cgroups this unit watches for + * ran empty */ + void (*notify_cgroup_empty)(Unit *u); + + /* Called whenever a process of this unit sends us a message */ + void (*notify_message)(Unit *u, pid_t pid, char **tags, FDSet *fds); + + /* Called whenever a name this Unit registered for comes or + * goes away. */ + void (*bus_name_owner_change)(Unit *u, const char *name, const char *old_owner, const char *new_owner); + + /* Called for each property that is being set */ + int (*bus_set_property)(Unit *u, const char *name, sd_bus_message *message, UnitSetPropertiesMode mode, sd_bus_error *error); + + /* Called after at least one property got changed to apply the necessary change */ + int (*bus_commit_properties)(Unit *u); + + /* Return the unit this unit is following */ + Unit *(*following)(Unit *u); + + /* Return the set of units that are following each other */ + int (*following_set)(Unit *u, Set **s); + + /* Invoked each time a unit this unit is triggering changes + * state or gains/loses a job */ + void (*trigger_notify)(Unit *u, Unit *trigger); + + /* Called whenever CLOCK_REALTIME made a jump */ + void (*time_change)(Unit *u); + + /* Returns the next timeout of a unit */ + int (*get_timeout)(Unit *u, usec_t *timeout); + + /* This is called for each unit type and should be used to + * enumerate existing devices and load them. However, + * everything that is loaded here should still stay in + * inactive state. It is the job of the coldplug() call above + * to put the units into the initial state. */ + void (*enumerate)(Manager *m); + + /* Type specific cleanups. */ + void (*shutdown)(Manager *m); + + /* If this function is set and return false all jobs for units + * of this type will immediately fail. */ + bool (*supported)(void); + + /* The bus vtable */ + const sd_bus_vtable *bus_vtable; + + /* The strings to print in status messages */ + UnitStatusMessageFormats status_message_formats; + + /* Can units of this type have multiple names? */ + bool no_alias:1; + + /* Instances make no sense for this type */ + bool no_instances:1; + + /* True if transient units of this type are OK */ + bool can_transient:1; +}; + +extern const UnitVTable * const unit_vtable[_UNIT_TYPE_MAX]; + +#define UNIT_VTABLE(u) unit_vtable[(u)->type] + +/* For casting a unit into the various unit types */ +#define DEFINE_CAST(UPPERCASE, MixedCase) \ + static inline MixedCase* UPPERCASE(Unit *u) { \ + if (_unlikely_(!u || u->type != UNIT_##UPPERCASE)) \ + return NULL; \ + \ + return (MixedCase*) u; \ + } + +/* For casting the various unit types into a unit */ +#define UNIT(u) (&(u)->meta) + +#define UNIT_HAS_EXEC_CONTEXT(u) (UNIT_VTABLE(u)->exec_context_offset > 0) +#define UNIT_HAS_CGROUP_CONTEXT(u) (UNIT_VTABLE(u)->cgroup_context_offset > 0) +#define UNIT_HAS_KILL_CONTEXT(u) (UNIT_VTABLE(u)->kill_context_offset > 0) + +#define UNIT_TRIGGER(u) ((Unit*) set_first((u)->dependencies[UNIT_TRIGGERS])) + +DEFINE_CAST(SERVICE, Service); +DEFINE_CAST(SOCKET, Socket); +DEFINE_CAST(BUSNAME, BusName); +DEFINE_CAST(TARGET, Target); +DEFINE_CAST(DEVICE, Device); +DEFINE_CAST(MOUNT, Mount); +DEFINE_CAST(AUTOMOUNT, Automount); +DEFINE_CAST(SWAP, Swap); +DEFINE_CAST(TIMER, Timer); +DEFINE_CAST(PATH, Path); +DEFINE_CAST(SLICE, Slice); +DEFINE_CAST(SCOPE, Scope); + +Unit *unit_new(Manager *m, size_t size); +void unit_free(Unit *u); + +int unit_add_name(Unit *u, const char *name); + +int unit_add_dependency(Unit *u, UnitDependency d, Unit *other, bool add_reference); +int unit_add_two_dependencies(Unit *u, UnitDependency d, UnitDependency e, Unit *other, bool add_reference); + +int unit_add_dependency_by_name(Unit *u, UnitDependency d, const char *name, const char *filename, bool add_reference); +int unit_add_two_dependencies_by_name(Unit *u, UnitDependency d, UnitDependency e, const char *name, const char *path, bool add_reference); + +int unit_add_exec_dependencies(Unit *u, ExecContext *c); + +int unit_choose_id(Unit *u, const char *name); +int unit_set_description(Unit *u, const char *description); + +bool unit_check_gc(Unit *u); + +void unit_add_to_load_queue(Unit *u); +void unit_add_to_dbus_queue(Unit *u); +void unit_add_to_cleanup_queue(Unit *u); +void unit_add_to_gc_queue(Unit *u); + +int unit_merge(Unit *u, Unit *other); +int unit_merge_by_name(Unit *u, const char *other); + +Unit *unit_follow_merge(Unit *u) _pure_; + +int unit_load_fragment_and_dropin(Unit *u); +int unit_load_fragment_and_dropin_optional(Unit *u); +int unit_load(Unit *unit); + +int unit_set_slice(Unit *u, Unit *slice); +int unit_set_default_slice(Unit *u); + +const char *unit_description(Unit *u) _pure_; + +bool unit_has_name(Unit *u, const char *name); + +UnitActiveState unit_active_state(Unit *u); + +const char* unit_sub_state_to_string(Unit *u); + +void unit_dump(Unit *u, FILE *f, const char *prefix); + +bool unit_can_reload(Unit *u) _pure_; +bool unit_can_start(Unit *u) _pure_; +bool unit_can_isolate(Unit *u) _pure_; + +int unit_start(Unit *u); +int unit_stop(Unit *u); +int unit_reload(Unit *u); + +int unit_kill(Unit *u, KillWho w, int signo, sd_bus_error *error); +int unit_kill_common(Unit *u, KillWho who, int signo, pid_t main_pid, pid_t control_pid, sd_bus_error *error); + +void unit_notify(Unit *u, UnitActiveState os, UnitActiveState ns, bool reload_success); + +int unit_watch_pid(Unit *u, pid_t pid); +void unit_unwatch_pid(Unit *u, pid_t pid); +void unit_unwatch_all_pids(Unit *u); + +void unit_tidy_watch_pids(Unit *u, pid_t except1, pid_t except2); + +int unit_install_bus_match(Unit *u, sd_bus *bus, const char *name); +int unit_watch_bus_name(Unit *u, const char *name); +void unit_unwatch_bus_name(Unit *u, const char *name); + +bool unit_job_is_applicable(Unit *u, JobType j); + +int set_unit_path(const char *p); + +char *unit_dbus_path(Unit *u); + +int unit_load_related_unit(Unit *u, const char *type, Unit **_found); + +bool unit_can_serialize(Unit *u) _pure_; + +int unit_serialize(Unit *u, FILE *f, FDSet *fds, bool serialize_jobs); +int unit_deserialize(Unit *u, FILE *f, FDSet *fds); + +int unit_serialize_item(Unit *u, FILE *f, const char *key, const char *value); +int unit_serialize_item_escaped(Unit *u, FILE *f, const char *key, const char *value); +int unit_serialize_item_fd(Unit *u, FILE *f, FDSet *fds, const char *key, int fd); +void unit_serialize_item_format(Unit *u, FILE *f, const char *key, const char *value, ...) _printf_(4,5); + +int unit_add_node_link(Unit *u, const char *what, bool wants, UnitDependency d); + +int unit_coldplug(Unit *u); + +void unit_status_printf(Unit *u, const char *status, const char *unit_status_msg_format) _printf_(3, 0); +void unit_status_emit_starting_stopping_reloading(Unit *u, JobType t); + +bool unit_need_daemon_reload(Unit *u); + +void unit_reset_failed(Unit *u); + +Unit *unit_following(Unit *u); +int unit_following_set(Unit *u, Set **s); + +const char *unit_slice_name(Unit *u); + +bool unit_stop_pending(Unit *u) _pure_; +bool unit_inactive_or_pending(Unit *u) _pure_; +bool unit_active_or_pending(Unit *u); + +int unit_add_default_target_dependency(Unit *u, Unit *target); + +void unit_start_on_failure(Unit *u); +void unit_trigger_notify(Unit *u); + +UnitFileState unit_get_unit_file_state(Unit *u); +int unit_get_unit_file_preset(Unit *u); + +Unit* unit_ref_set(UnitRef *ref, Unit *u); +void unit_ref_unset(UnitRef *ref); + +#define UNIT_DEREF(ref) ((ref).unit) +#define UNIT_ISSET(ref) (!!(ref).unit) + +int unit_patch_contexts(Unit *u); + +ExecContext *unit_get_exec_context(Unit *u) _pure_; +KillContext *unit_get_kill_context(Unit *u) _pure_; +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_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); + +int unit_write_drop_in_private(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *data); +int unit_write_drop_in_private_format(Unit *u, UnitSetPropertiesMode mode, const char *name, const char *format, ...) _printf_(4,5); + +int unit_kill_context(Unit *u, KillContext *c, KillOperation k, pid_t main_pid, pid_t control_pid, bool main_pid_alien); + +int unit_make_transient(Unit *u); + +int unit_require_mounts_for(Unit *u, const char *path); + +bool unit_type_supported(UnitType t); + +bool unit_is_pristine(Unit *u); + +static inline bool unit_supported(Unit *u) { + return unit_type_supported(u->type); +} + +void unit_warn_if_dir_nonempty(Unit *u, const char* where); +int unit_fail_if_symlink(Unit *u, const char* where); + +/* Macros which append UNIT= or USER_UNIT= to the message */ + +#define log_unit_full(unit, level, error, ...) \ + ({ \ + Unit *_u = (unit); \ + _u ? log_object_internal(level, error, __FILE__, __LINE__, __func__, _u->manager->unit_log_field, _u->id, ##__VA_ARGS__) : \ + log_internal(level, error, __FILE__, __LINE__, __func__, ##__VA_ARGS__); \ + }) + +#define log_unit_debug(unit, ...) log_unit_full(unit, LOG_DEBUG, 0, ##__VA_ARGS__) +#define log_unit_info(unit, ...) log_unit_full(unit, LOG_INFO, 0, ##__VA_ARGS__) +#define log_unit_notice(unit, ...) log_unit_full(unit, LOG_NOTICE, 0, ##__VA_ARGS__) +#define log_unit_warning(unit, ...) log_unit_full(unit, LOG_WARNING, 0, ##__VA_ARGS__) +#define log_unit_error(unit, ...) log_unit_full(unit, LOG_ERR, 0, ##__VA_ARGS__) + +#define log_unit_debug_errno(unit, error, ...) log_unit_full(unit, LOG_DEBUG, error, ##__VA_ARGS__) +#define log_unit_info_errno(unit, error, ...) log_unit_full(unit, LOG_INFO, error, ##__VA_ARGS__) +#define log_unit_notice_errno(unit, error, ...) log_unit_full(unit, LOG_NOTICE, error, ##__VA_ARGS__) +#define log_unit_warning_errno(unit, error, ...) log_unit_full(unit, LOG_WARNING, error, ##__VA_ARGS__) +#define log_unit_error_errno(unit, error, ...) log_unit_full(unit, LOG_ERR, error, ##__VA_ARGS__) + +#define LOG_UNIT_MESSAGE(unit, fmt, ...) "MESSAGE=%s: " fmt, (unit)->id, ##__VA_ARGS__ +#define LOG_UNIT_ID(unit) (unit)->manager->unit_log_format_string, (unit)->id diff --git a/src/libfirewall/Makefile b/src/libfirewall/Makefile new file mode 100644 index 0000000000..875d03a879 --- /dev/null +++ b/src/libfirewall/Makefile @@ -0,0 +1,42 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_LIBIPTC),) +noinst_LTLIBRARIES += \ + libfirewall.la + +libfirewall_la_SOURCES = \ + src/shared/firewall-util.h \ + src/shared/firewall-util.c + +libfirewall_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBIPTC_CFLAGS) + +libfirewall_la_LIBADD = \ + $(LIBIPTC_LIBS) +endif + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libfirewall/firewall-util.c b/src/libfirewall/firewall-util.c new file mode 100644 index 0000000000..0d3da2e6d2 --- /dev/null +++ b/src/libfirewall/firewall-util.c @@ -0,0 +1,349 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "firewall-util.h" +#include "in-addr-util.h" +#include "macro.h" + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct xtc_handle*, iptc_free); + +static int entry_fill_basics( + struct ipt_entry *entry, + int protocol, + const char *in_interface, + const union in_addr_union *source, + unsigned source_prefixlen, + const char *out_interface, + const union in_addr_union *destination, + unsigned destination_prefixlen) { + + assert(entry); + + if (out_interface && strlen(out_interface) >= IFNAMSIZ) + return -EINVAL; + + if (in_interface && strlen(in_interface) >= IFNAMSIZ) + return -EINVAL; + + entry->ip.proto = protocol; + + if (in_interface) { + strcpy(entry->ip.iniface, in_interface); + memset(entry->ip.iniface_mask, 0xFF, strlen(in_interface)+1); + } + if (source) { + entry->ip.src = source->in; + in_addr_prefixlen_to_netmask(&entry->ip.smsk, source_prefixlen); + } + + if (out_interface) { + strcpy(entry->ip.outiface, out_interface); + memset(entry->ip.outiface_mask, 0xFF, strlen(out_interface)+1); + } + if (destination) { + entry->ip.dst = destination->in; + in_addr_prefixlen_to_netmask(&entry->ip.dmsk, destination_prefixlen); + } + + return 0; +} + +int fw_add_masquerade( + bool add, + int af, + int protocol, + const union in_addr_union *source, + unsigned source_prefixlen, + const char *out_interface, + const union in_addr_union *destination, + unsigned destination_prefixlen) { + + _cleanup_(iptc_freep) struct xtc_handle *h = NULL; + struct ipt_entry *entry, *mask; + struct ipt_entry_target *t; + size_t sz; + struct nf_nat_ipv4_multi_range_compat *mr; + int r; + + if (af != AF_INET) + return -EOPNOTSUPP; + + if (protocol != 0 && protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) + return -EOPNOTSUPP; + + h = iptc_init("nat"); + if (!h) + return -errno; + + sz = XT_ALIGN(sizeof(struct ipt_entry)) + + XT_ALIGN(sizeof(struct ipt_entry_target)) + + XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); + + /* Put together the entry we want to add or remove */ + entry = alloca0(sz); + entry->next_offset = sz; + entry->target_offset = XT_ALIGN(sizeof(struct ipt_entry)); + r = entry_fill_basics(entry, protocol, NULL, source, source_prefixlen, out_interface, destination, destination_prefixlen); + if (r < 0) + return r; + + /* Fill in target part */ + t = ipt_get_target(entry); + t->u.target_size = + XT_ALIGN(sizeof(struct ipt_entry_target)) + + XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); + strncpy(t->u.user.name, "MASQUERADE", sizeof(t->u.user.name)); + mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; + mr->rangesize = 1; + + /* Create a search mask entry */ + mask = alloca(sz); + memset(mask, 0xFF, sz); + + if (add) { + if (iptc_check_entry("POSTROUTING", entry, (unsigned char*) mask, h)) + return 0; + if (errno != ENOENT) /* if other error than not existing yet, fail */ + return -errno; + + if (!iptc_insert_entry("POSTROUTING", entry, 0, h)) + return -errno; + } else { + if (!iptc_delete_entry("POSTROUTING", entry, (unsigned char*) mask, h)) { + if (errno == ENOENT) /* if it's already gone, all is good! */ + return 0; + + return -errno; + } + } + + if (!iptc_commit(h)) + return -errno; + + return 0; +} + +int fw_add_local_dnat( + bool add, + int af, + int protocol, + const char *in_interface, + const union in_addr_union *source, + unsigned source_prefixlen, + const union in_addr_union *destination, + unsigned destination_prefixlen, + uint16_t local_port, + const union in_addr_union *remote, + uint16_t remote_port, + const union in_addr_union *previous_remote) { + + + _cleanup_(iptc_freep) struct xtc_handle *h = NULL; + struct ipt_entry *entry, *mask; + struct ipt_entry_target *t; + struct ipt_entry_match *m; + struct xt_addrtype_info_v1 *at; + struct nf_nat_ipv4_multi_range_compat *mr; + size_t sz, msz; + int r; + + assert(add || !previous_remote); + + if (af != AF_INET) + return -EOPNOTSUPP; + + if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) + return -EOPNOTSUPP; + + if (local_port <= 0) + return -EINVAL; + + if (remote_port <= 0) + return -EINVAL; + + h = iptc_init("nat"); + if (!h) + return -errno; + + sz = XT_ALIGN(sizeof(struct ipt_entry)) + + XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + + XT_ALIGN(sizeof(struct ipt_entry_target)) + + XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); + + if (protocol == IPPROTO_TCP) + msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_tcp)); + else + msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_udp)); + + sz += msz; + + /* Fill in basic part */ + entry = alloca0(sz); + entry->next_offset = sz; + entry->target_offset = + XT_ALIGN(sizeof(struct ipt_entry)) + + XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + + msz; + r = entry_fill_basics(entry, protocol, in_interface, source, source_prefixlen, NULL, destination, destination_prefixlen); + if (r < 0) + return r; + + /* Fill in first match */ + m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry))); + m->u.match_size = msz; + if (protocol == IPPROTO_TCP) { + struct xt_tcp *tcp; + + strncpy(m->u.user.name, "tcp", sizeof(m->u.user.name)); + tcp = (struct xt_tcp*) m->data; + tcp->dpts[0] = tcp->dpts[1] = local_port; + tcp->spts[0] = 0; + tcp->spts[1] = 0xFFFF; + + } else { + struct xt_udp *udp; + + strncpy(m->u.user.name, "udp", sizeof(m->u.user.name)); + udp = (struct xt_udp*) m->data; + udp->dpts[0] = udp->dpts[1] = local_port; + udp->spts[0] = 0; + udp->spts[1] = 0xFFFF; + } + + /* Fill in second match */ + m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry)) + msz); + m->u.match_size = + XT_ALIGN(sizeof(struct ipt_entry_match)) + + XT_ALIGN(sizeof(struct xt_addrtype_info_v1)); + strncpy(m->u.user.name, "addrtype", sizeof(m->u.user.name)); + m->u.user.revision = 1; + at = (struct xt_addrtype_info_v1*) m->data; + at->dest = XT_ADDRTYPE_LOCAL; + + /* Fill in target part */ + t = ipt_get_target(entry); + t->u.target_size = + XT_ALIGN(sizeof(struct ipt_entry_target)) + + XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); + strncpy(t->u.user.name, "DNAT", sizeof(t->u.user.name)); + mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; + mr->rangesize = 1; + mr->range[0].flags = NF_NAT_RANGE_PROTO_SPECIFIED|NF_NAT_RANGE_MAP_IPS; + mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; + if (protocol == IPPROTO_TCP) + mr->range[0].min.tcp.port = mr->range[0].max.tcp.port = htons(remote_port); + else + mr->range[0].min.udp.port = mr->range[0].max.udp.port = htons(remote_port); + + mask = alloca0(sz); + memset(mask, 0xFF, sz); + + if (add) { + /* Add the PREROUTING rule, if it is missing so far */ + if (!iptc_check_entry("PREROUTING", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -EINVAL; + + if (!iptc_insert_entry("PREROUTING", entry, 0, h)) + return -errno; + } + + /* If a previous remote is set, remove its entry */ + if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { + mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; + + if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + } + + mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; + } + + /* Add the OUTPUT rule, if it is missing so far */ + if (!in_interface) { + + /* Don't apply onto loopback addresses */ + if (!destination) { + entry->ip.dst.s_addr = htobe32(0x7F000000); + entry->ip.dmsk.s_addr = htobe32(0xFF000000); + entry->ip.invflags = IPT_INV_DSTIP; + } + + if (!iptc_check_entry("OUTPUT", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + + if (!iptc_insert_entry("OUTPUT", entry, 0, h)) + return -errno; + } + + /* If a previous remote is set, remove its entry */ + if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { + mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; + + if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + } + } + } + } else { + if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + } + + if (!in_interface) { + if (!destination) { + entry->ip.dst.s_addr = htobe32(0x7F000000); + entry->ip.dmsk.s_addr = htobe32(0xFF000000); + entry->ip.invflags = IPT_INV_DSTIP; + } + + if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) { + if (errno != ENOENT) + return -errno; + } + } + } + + if (!iptc_commit(h)) + return -errno; + + return 0; +} diff --git a/src/libfirewall/firewall-util.h b/src/libfirewall/firewall-util.h new file mode 100644 index 0000000000..c39b34cf8f --- /dev/null +++ b/src/libfirewall/firewall-util.h @@ -0,0 +1,83 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include + +#include "in-addr-util.h" + +#ifdef HAVE_LIBIPTC + +int fw_add_masquerade( + bool add, + int af, + int protocol, + const union in_addr_union *source, + unsigned source_prefixlen, + const char *out_interface, + const union in_addr_union *destination, + unsigned destination_prefixlen); + +int fw_add_local_dnat( + bool add, + int af, + int protocol, + const char *in_interface, + const union in_addr_union *source, + unsigned source_prefixlen, + const union in_addr_union *destination, + unsigned destination_prefixlen, + uint16_t local_port, + const union in_addr_union *remote, + uint16_t remote_port, + const union in_addr_union *previous_remote); + +#else + +static inline int fw_add_masquerade( + bool add, + int af, + int protocol, + const union in_addr_union *source, + unsigned source_prefixlen, + const char *out_interface, + const union in_addr_union *destination, + unsigned destination_prefixlen) { + return -EOPNOTSUPP; +} + +static inline int fw_add_local_dnat( + bool add, + int af, + int protocol, + const char *in_interface, + const union in_addr_union *source, + unsigned source_prefixlen, + const union in_addr_union *destination, + unsigned destination_prefixlen, + uint16_t local_port, + const union in_addr_union *remote, + uint16_t remote_port, + const union in_addr_union *previous_remote) { + return -EOPNOTSUPP; +} + +#endif diff --git a/src/libshared/Makefile b/src/libshared/Makefile new file mode 100644 index 0000000000..d0141969b3 --- /dev/null +++ b/src/libshared/Makefile @@ -0,0 +1,140 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +noinst_LTLIBRARIES += \ + libshared.la + +libshared_la_SOURCES = \ + src/shared/output-mode.h \ + src/shared/gpt.h \ + src/shared/udev-util.h \ + src/shared/linux/auto_dev-ioctl.h \ + src/shared/initreq.h \ + src/shared/dns-domain.c \ + src/shared/dns-domain.h \ + src/shared/architecture.c \ + src/shared/architecture.h \ + src/shared/efivars.c \ + src/shared/efivars.h \ + src/shared/fstab-util.c \ + src/shared/fstab-util.h \ + src/shared/sleep-config.c \ + src/shared/sleep-config.h \ + src/shared/conf-parser.c \ + src/shared/conf-parser.h \ + src/shared/pager.c \ + src/shared/pager.h \ + src/shared/spawn-polkit-agent.c \ + src/shared/spawn-polkit-agent.h \ + src/shared/apparmor-util.c \ + src/shared/apparmor-util.h \ + src/shared/ima-util.c \ + src/shared/ima-util.h \ + src/shared/ptyfwd.c \ + src/shared/ptyfwd.h \ + src/shared/base-filesystem.c \ + src/shared/base-filesystem.h \ + src/shared/uid-range.c \ + src/shared/uid-range.h \ + src/shared/install.c \ + src/shared/install.h \ + src/shared/install-printf.c \ + src/shared/install-printf.h \ + src/shared/path-lookup.c \ + src/shared/path-lookup.h \ + src/shared/specifier.c \ + src/shared/specifier.h \ + src/shared/dev-setup.c \ + src/shared/dev-setup.h \ + src/shared/dropin.c \ + src/shared/dropin.h \ + src/shared/condition.c \ + src/shared/condition.h \ + src/shared/clean-ipc.c \ + src/shared/clean-ipc.h \ + src/shared/generator.h \ + src/shared/generator.c \ + src/shared/acpi-fpdt.h \ + src/shared/acpi-fpdt.c \ + src/shared/boot-timestamps.h \ + src/shared/boot-timestamps.c \ + src/shared/cgroup-show.c \ + src/shared/cgroup-show.h \ + src/shared/utmp-wtmp.h \ + src/shared/watchdog.c \ + src/shared/watchdog.h \ + src/shared/spawn-ask-password-agent.c \ + src/shared/spawn-ask-password-agent.h \ + src/shared/ask-password-api.c \ + src/shared/ask-password-api.h \ + src/shared/switch-root.h \ + src/shared/switch-root.c \ + src/shared/import-util.c \ + src/shared/import-util.h \ + src/shared/sysctl-util.c \ + src/shared/sysctl-util.h \ + src/shared/bus-util.c \ + src/shared/bus-util.h \ + src/shared/logs-show.c \ + src/shared/logs-show.h \ + src/shared/machine-image.c \ + src/shared/machine-image.h \ + src/shared/machine-pool.c \ + src/shared/machine-pool.h \ + src/shared/resolve-util.c \ + src/shared/resolve-util.h + +ifneq ($(HAVE_UTMP),) +libshared_la_SOURCES += \ + src/shared/utmp-wtmp.c +endif + +ifneq ($(HAVE_SECCOMP),) +libshared_la_SOURCES += \ + src/shared/seccomp-util.h \ + src/shared/seccomp-util.c +endif + +ifneq ($(HAVE_ACL),) +libshared_la_SOURCES += \ + src/shared/acl-util.c \ + src/shared/acl-util.h +endif + +libshared_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(ACL_CFLAGS) \ + $(LIBIDN_CFLAGS) \ + $(SECCOMP_CFLAGS) + +libshared_la_LIBADD = \ + libsystemd-internal.la \ + libsystemd-journal-internal.la \ + libudev-internal.la \ + $(ACL_LIBS) \ + $(LIBIDN_LIBS) \ + $(SECCOMP_LIBS) + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libshared/acl-util.c b/src/libshared/acl-util.c new file mode 100644 index 0000000000..2aa951fce9 --- /dev/null +++ b/src/libshared/acl-util.c @@ -0,0 +1,429 @@ +/*** + This file is part of systemd. + + Copyright 2011,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 . +***/ + +#include +#include + +#include "acl-util.h" +#include "alloc-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry) { + acl_entry_t i; + int r; + + assert(acl); + assert(entry); + + for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); + r > 0; + r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) { + + acl_tag_t tag; + uid_t *u; + bool b; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if (tag != ACL_USER) + continue; + + u = acl_get_qualifier(i); + if (!u) + return -errno; + + b = *u == uid; + acl_free(u); + + if (b) { + *entry = i; + return 1; + } + } + if (r < 0) + return -errno; + + return 0; +} + +int calc_acl_mask_if_needed(acl_t *acl_p) { + acl_entry_t i; + int r; + bool need = false; + + assert(acl_p); + + for (r = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i); + r > 0; + r = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) { + acl_tag_t tag; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if (tag == ACL_MASK) + return 0; + + if (IN_SET(tag, ACL_USER, ACL_GROUP)) + need = true; + } + if (r < 0) + return -errno; + + if (need && acl_calc_mask(acl_p) < 0) + return -errno; + + return need; +} + +int add_base_acls_if_needed(acl_t *acl_p, const char *path) { + acl_entry_t i; + int r; + bool have_user_obj = false, have_group_obj = false, have_other = false; + struct stat st; + _cleanup_(acl_freep) acl_t basic = NULL; + + assert(acl_p); + + for (r = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i); + r > 0; + r = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) { + acl_tag_t tag; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if (tag == ACL_USER_OBJ) + have_user_obj = true; + else if (tag == ACL_GROUP_OBJ) + have_group_obj = true; + else if (tag == ACL_OTHER) + have_other = true; + if (have_user_obj && have_group_obj && have_other) + return 0; + } + if (r < 0) + return -errno; + + r = stat(path, &st); + if (r < 0) + return -errno; + + basic = acl_from_mode(st.st_mode); + if (!basic) + return -errno; + + for (r = acl_get_entry(basic, ACL_FIRST_ENTRY, &i); + r > 0; + r = acl_get_entry(basic, ACL_NEXT_ENTRY, &i)) { + acl_tag_t tag; + acl_entry_t dst; + + if (acl_get_tag_type(i, &tag) < 0) + return -errno; + + if ((tag == ACL_USER_OBJ && have_user_obj) || + (tag == ACL_GROUP_OBJ && have_group_obj) || + (tag == ACL_OTHER && have_other)) + continue; + + r = acl_create_entry(acl_p, &dst); + if (r < 0) + return -errno; + + r = acl_copy_entry(dst, i); + if (r < 0) + return -errno; + } + if (r < 0) + return -errno; + return 0; +} + +int acl_search_groups(const char *path, char ***ret_groups) { + _cleanup_strv_free_ char **g = NULL; + _cleanup_(acl_free) acl_t acl = NULL; + bool ret = false; + acl_entry_t entry; + int r; + + assert(path); + + acl = acl_get_file(path, ACL_TYPE_DEFAULT); + if (!acl) + return -errno; + + r = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); + for (;;) { + _cleanup_(acl_free_gid_tpp) gid_t *gid = NULL; + acl_tag_t tag; + + if (r < 0) + return -errno; + if (r == 0) + break; + + if (acl_get_tag_type(entry, &tag) < 0) + return -errno; + + if (tag != ACL_GROUP) + goto next; + + gid = acl_get_qualifier(entry); + if (!gid) + return -errno; + + if (in_gid(*gid) > 0) { + if (!ret_groups) + return true; + + ret = true; + } + + if (ret_groups) { + char *name; + + name = gid_to_name(*gid); + if (!name) + return -ENOMEM; + + r = strv_consume(&g, name); + if (r < 0) + return r; + } + + next: + r = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry); + } + + if (ret_groups) { + *ret_groups = g; + g = NULL; + } + + return ret; +} + +int parse_acl(const char *text, acl_t *acl_access, acl_t *acl_default, bool want_mask) { + _cleanup_free_ char **a = NULL, **d = NULL; /* strings are not be freed */ + _cleanup_strv_free_ char **split; + char **entry; + int r = -EINVAL; + _cleanup_(acl_freep) acl_t a_acl = NULL, d_acl = NULL; + + split = strv_split(text, ","); + if (!split) + return -ENOMEM; + + STRV_FOREACH(entry, split) { + char *p; + + p = startswith(*entry, "default:"); + if (!p) + p = startswith(*entry, "d:"); + + if (p) + r = strv_push(&d, p); + else + r = strv_push(&a, *entry); + if (r < 0) + return r; + } + + if (!strv_isempty(a)) { + _cleanup_free_ char *join; + + join = strv_join(a, ","); + if (!join) + return -ENOMEM; + + a_acl = acl_from_text(join); + if (!a_acl) + return -errno; + + if (want_mask) { + r = calc_acl_mask_if_needed(&a_acl); + if (r < 0) + return r; + } + } + + if (!strv_isempty(d)) { + _cleanup_free_ char *join; + + join = strv_join(d, ","); + if (!join) + return -ENOMEM; + + d_acl = acl_from_text(join); + if (!d_acl) + return -errno; + + if (want_mask) { + r = calc_acl_mask_if_needed(&d_acl); + if (r < 0) + return r; + } + } + + *acl_access = a_acl; + *acl_default = d_acl; + a_acl = d_acl = NULL; + + return 0; +} + +static int acl_entry_equal(acl_entry_t a, acl_entry_t b) { + acl_tag_t tag_a, tag_b; + + if (acl_get_tag_type(a, &tag_a) < 0) + return -errno; + + if (acl_get_tag_type(b, &tag_b) < 0) + return -errno; + + if (tag_a != tag_b) + return false; + + switch (tag_a) { + case ACL_USER_OBJ: + case ACL_GROUP_OBJ: + case ACL_MASK: + case ACL_OTHER: + /* can have only one of those */ + return true; + case ACL_USER: { + _cleanup_(acl_free_uid_tpp) uid_t *uid_a = NULL, *uid_b = NULL; + + uid_a = acl_get_qualifier(a); + if (!uid_a) + return -errno; + + uid_b = acl_get_qualifier(b); + if (!uid_b) + return -errno; + + return *uid_a == *uid_b; + } + case ACL_GROUP: { + _cleanup_(acl_free_gid_tpp) gid_t *gid_a = NULL, *gid_b = NULL; + + gid_a = acl_get_qualifier(a); + if (!gid_a) + return -errno; + + gid_b = acl_get_qualifier(b); + if (!gid_b) + return -errno; + + return *gid_a == *gid_b; + } + default: + assert_not_reached("Unknown acl tag type"); + } +} + +static int find_acl_entry(acl_t acl, acl_entry_t entry, acl_entry_t *out) { + acl_entry_t i; + int r; + + for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); + r > 0; + r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) { + + r = acl_entry_equal(i, entry); + if (r < 0) + return r; + if (r > 0) { + *out = i; + return 1; + } + } + if (r < 0) + return -errno; + return 0; +} + +int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl) { + _cleanup_(acl_freep) acl_t old; + acl_entry_t i; + int r; + + old = acl_get_file(path, type); + if (!old) + return -errno; + + for (r = acl_get_entry(new, ACL_FIRST_ENTRY, &i); + r > 0; + r = acl_get_entry(new, ACL_NEXT_ENTRY, &i)) { + + acl_entry_t j; + + r = find_acl_entry(old, i, &j); + if (r < 0) + return r; + if (r == 0) + if (acl_create_entry(&old, &j) < 0) + return -errno; + + if (acl_copy_entry(j, i) < 0) + return -errno; + } + if (r < 0) + return -errno; + + *acl = old; + old = NULL; + return 0; +} + +int add_acls_for_user(int fd, uid_t uid) { + _cleanup_(acl_freep) acl_t acl = NULL; + acl_entry_t entry; + acl_permset_t permset; + int r; + + acl = acl_get_fd(fd); + if (!acl) + return -errno; + + r = acl_find_uid(acl, uid, &entry); + if (r <= 0) { + if (acl_create_entry(&acl, &entry) < 0 || + acl_set_tag_type(entry, ACL_USER) < 0 || + acl_set_qualifier(entry, &uid) < 0) + return -errno; + } + + /* We do not recalculate the mask unconditionally here, + * so that the fchmod() mask above stays intact. */ + if (acl_get_permset(entry, &permset) < 0 || + acl_add_perm(permset, ACL_READ) < 0) + return -errno; + + r = calc_acl_mask_if_needed(&acl); + if (r < 0) + return r; + + return acl_set_fd(fd, acl); +} diff --git a/src/libshared/acl-util.h b/src/libshared/acl-util.h new file mode 100644 index 0000000000..396e9e067e --- /dev/null +++ b/src/libshared/acl-util.h @@ -0,0 +1,48 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#ifdef HAVE_ACL + +#include +#include +#include + +#include "macro.h" + +int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry); +int calc_acl_mask_if_needed(acl_t *acl_p); +int add_base_acls_if_needed(acl_t *acl_p, const char *path); +int acl_search_groups(const char* path, char ***ret_groups); +int parse_acl(const char *text, acl_t *acl_access, acl_t *acl_default, bool want_mask); +int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl); +int add_acls_for_user(int fd, uid_t uid); + +/* acl_free takes multiple argument types. + * Multiple cleanup functions are necessary. */ +DEFINE_TRIVIAL_CLEANUP_FUNC(acl_t, acl_free); +#define acl_free_charp acl_free +DEFINE_TRIVIAL_CLEANUP_FUNC(char*, acl_free_charp); +#define acl_free_uid_tp acl_free +DEFINE_TRIVIAL_CLEANUP_FUNC(uid_t*, acl_free_uid_tp); +#define acl_free_gid_tp acl_free +DEFINE_TRIVIAL_CLEANUP_FUNC(gid_t*, acl_free_gid_tp); + +#endif diff --git a/src/libshared/acpi-fpdt.c b/src/libshared/acpi-fpdt.c new file mode 100644 index 0000000000..3cb9e781fd --- /dev/null +++ b/src/libshared/acpi-fpdt.c @@ -0,0 +1,160 @@ +/*** + This file is part of systemd. + + 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "acpi-fpdt.h" +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "time-util.h" + +struct acpi_table_header { + char signature[4]; + uint32_t length; + uint8_t revision; + uint8_t checksum; + char oem_id[6]; + char oem_table_id[8]; + uint32_t oem_revision; + char asl_compiler_id[4]; + uint32_t asl_compiler_revision; +}; + +enum { + ACPI_FPDT_TYPE_BOOT = 0, + ACPI_FPDT_TYPE_S3PERF = 1, +}; + +struct acpi_fpdt_header { + uint16_t type; + uint8_t length; + uint8_t revision; + uint8_t reserved[4]; + uint64_t ptr; +}; + +struct acpi_fpdt_boot_header { + char signature[4]; + uint32_t length; +}; + +enum { + ACPI_FPDT_S3PERF_RESUME_REC = 0, + ACPI_FPDT_S3PERF_SUSPEND_REC = 1, + ACPI_FPDT_BOOT_REC = 2, +}; + +struct acpi_fpdt_boot { + uint16_t type; + uint8_t length; + uint8_t revision; + uint8_t reserved[4]; + uint64_t reset_end; + uint64_t load_start; + uint64_t startup_start; + uint64_t exit_services_entry; + uint64_t exit_services_exit; +}; + +int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit) { + _cleanup_free_ char *buf = NULL; + struct acpi_table_header *tbl; + size_t l = 0; + struct acpi_fpdt_header *rec; + int r; + uint64_t ptr = 0; + _cleanup_close_ int fd = -1; + struct acpi_fpdt_boot_header hbrec; + struct acpi_fpdt_boot brec; + + r = read_full_file("/sys/firmware/acpi/tables/FPDT", &buf, &l); + if (r < 0) + return r; + + if (l < sizeof(struct acpi_table_header) + sizeof(struct acpi_fpdt_header)) + return -EINVAL; + + tbl = (struct acpi_table_header *)buf; + if (l != tbl->length) + return -EINVAL; + + if (memcmp(tbl->signature, "FPDT", 4) != 0) + return -EINVAL; + + /* find Firmware Basic Boot Performance Pointer Record */ + for (rec = (struct acpi_fpdt_header *)(buf + sizeof(struct acpi_table_header)); + (char *)rec < buf + l; + rec = (struct acpi_fpdt_header *)((char *)rec + rec->length)) { + if (rec->length <= 0) + break; + if (rec->type != ACPI_FPDT_TYPE_BOOT) + continue; + if (rec->length != sizeof(struct acpi_fpdt_header)) + continue; + + ptr = rec->ptr; + break; + } + + if (ptr == 0) + return -EINVAL; + + /* read Firmware Basic Boot Performance Data Record */ + fd = open("/dev/mem", O_CLOEXEC|O_RDONLY); + if (fd < 0) + return -errno; + + l = pread(fd, &hbrec, sizeof(struct acpi_fpdt_boot_header), ptr); + if (l != sizeof(struct acpi_fpdt_boot_header)) + return -EINVAL; + + if (memcmp(hbrec.signature, "FBPT", 4) != 0) + return -EINVAL; + + if (hbrec.length < sizeof(struct acpi_fpdt_boot_header) + sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + l = pread(fd, &brec, sizeof(struct acpi_fpdt_boot), ptr + sizeof(struct acpi_fpdt_boot_header)); + if (l != sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + if (brec.length != sizeof(struct acpi_fpdt_boot)) + return -EINVAL; + + if (brec.type != ACPI_FPDT_BOOT_REC) + return -EINVAL; + + if (brec.startup_start == 0 || brec.exit_services_exit < brec.startup_start) + return -EINVAL; + if (brec.exit_services_exit > NSEC_PER_HOUR) + return -EINVAL; + + if (loader_start) + *loader_start = brec.startup_start / 1000; + if (loader_exit) + *loader_exit = brec.exit_services_exit / 1000; + + return 0; +} diff --git a/src/libshared/acpi-fpdt.h b/src/libshared/acpi-fpdt.h new file mode 100644 index 0000000000..fc28175d0a --- /dev/null +++ b/src/libshared/acpi-fpdt.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + 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 . +***/ + +#include + +int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit); diff --git a/src/libshared/apparmor-util.c b/src/libshared/apparmor-util.c new file mode 100644 index 0000000000..edd695fd23 --- /dev/null +++ b/src/libshared/apparmor-util.c @@ -0,0 +1,39 @@ +/*** + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "apparmor-util.h" +#include "fileio.h" +#include "parse-util.h" + +bool mac_apparmor_use(void) { + static int cached_use = -1; + + if (cached_use < 0) { + _cleanup_free_ char *p = NULL; + + cached_use = + read_one_line_file("/sys/module/apparmor/parameters/enabled", &p) >= 0 && + parse_boolean(p) > 0; + } + + return cached_use; +} diff --git a/src/libshared/apparmor-util.h b/src/libshared/apparmor-util.h new file mode 100644 index 0000000000..524f740152 --- /dev/null +++ b/src/libshared/apparmor-util.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +bool mac_apparmor_use(void); diff --git a/src/libshared/architecture.c b/src/libshared/architecture.c new file mode 100644 index 0000000000..a9ecfc1cd6 --- /dev/null +++ b/src/libshared/architecture.c @@ -0,0 +1,176 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "architecture.h" +#include "macro.h" +#include "string-table.h" +#include "string-util.h" + +int uname_architecture(void) { + + /* Return a sanitized enum identifying the architecture we are + * running on. This is based on uname(), and the user may + * hence control what this returns by using + * personality(). This puts the user in control on systems + * that can run binaries of multiple architectures. + * + * We do not translate the string returned by uname() + * 1:1. Instead we try to clean it up and break down the + * confusion on x86 and arm in particular. + * + * We do not try to distinguish CPUs not CPU features, but + * actual architectures, i.e. that have genuinely different + * code. */ + + static const struct { + const char *machine; + int arch; + } arch_map[] = { +#if defined(__x86_64__) || defined(__i386__) + { "x86_64", ARCHITECTURE_X86_64 }, + { "i686", ARCHITECTURE_X86 }, + { "i586", ARCHITECTURE_X86 }, + { "i486", ARCHITECTURE_X86 }, + { "i386", ARCHITECTURE_X86 }, +#elif defined(__powerpc__) || defined(__powerpc64__) + { "ppc64", ARCHITECTURE_PPC64 }, + { "ppc64le", ARCHITECTURE_PPC64_LE }, + { "ppc", ARCHITECTURE_PPC }, + { "ppcle", ARCHITECTURE_PPC_LE }, +#elif defined(__ia64__) + { "ia64", ARCHITECTURE_IA64 }, +#elif defined(__hppa__) || defined(__hppa64__) + { "parisc64", ARCHITECTURE_PARISC64 }, + { "parisc", ARCHITECTURE_PARISC }, +#elif defined(__s390__) || defined(__s390x__) + { "s390x", ARCHITECTURE_S390X }, + { "s390", ARCHITECTURE_S390 }, +#elif defined(__sparc__) || defined(__sparc64__) + { "sparc64", ARCHITECTURE_SPARC64 }, + { "sparc", ARCHITECTURE_SPARC }, +#elif defined(__mips__) || defined(__mips64__) + { "mips64", ARCHITECTURE_MIPS64 }, + { "mips", ARCHITECTURE_MIPS }, +#elif defined(__alpha__) + { "alpha" , ARCHITECTURE_ALPHA }, +#elif defined(__arm__) || defined(__aarch64__) + { "aarch64", ARCHITECTURE_ARM64 }, + { "aarch64_be", ARCHITECTURE_ARM64_BE }, + { "armv4l", ARCHITECTURE_ARM }, + { "armv4b", ARCHITECTURE_ARM_BE }, + { "armv4tl", ARCHITECTURE_ARM }, + { "armv4tb", ARCHITECTURE_ARM_BE }, + { "armv5tl", ARCHITECTURE_ARM }, + { "armv5tb", ARCHITECTURE_ARM_BE }, + { "armv5tel", ARCHITECTURE_ARM }, + { "armv5teb" , ARCHITECTURE_ARM_BE }, + { "armv5tejl", ARCHITECTURE_ARM }, + { "armv5tejb", ARCHITECTURE_ARM_BE }, + { "armv6l", ARCHITECTURE_ARM }, + { "armv6b", ARCHITECTURE_ARM_BE }, + { "armv7l", ARCHITECTURE_ARM }, + { "armv7b", ARCHITECTURE_ARM_BE }, + { "armv7ml", ARCHITECTURE_ARM }, + { "armv7mb", ARCHITECTURE_ARM_BE }, + { "armv4l", ARCHITECTURE_ARM }, + { "armv4b", ARCHITECTURE_ARM_BE }, + { "armv4tl", ARCHITECTURE_ARM }, + { "armv4tb", ARCHITECTURE_ARM_BE }, + { "armv5tl", ARCHITECTURE_ARM }, + { "armv5tb", ARCHITECTURE_ARM_BE }, + { "armv5tel", ARCHITECTURE_ARM }, + { "armv5teb", ARCHITECTURE_ARM_BE }, + { "armv5tejl", ARCHITECTURE_ARM }, + { "armv5tejb", ARCHITECTURE_ARM_BE }, + { "armv6l", ARCHITECTURE_ARM }, + { "armv6b", ARCHITECTURE_ARM_BE }, + { "armv7l", ARCHITECTURE_ARM }, + { "armv7b", ARCHITECTURE_ARM_BE }, + { "armv7ml", ARCHITECTURE_ARM }, + { "armv7mb", ARCHITECTURE_ARM_BE }, + { "armv8l", ARCHITECTURE_ARM }, + { "armv8b", ARCHITECTURE_ARM_BE }, +#elif defined(__sh__) || defined(__sh64__) + { "sh5", ARCHITECTURE_SH64 }, + { "sh2", ARCHITECTURE_SH }, + { "sh2a", ARCHITECTURE_SH }, + { "sh3", ARCHITECTURE_SH }, + { "sh4", ARCHITECTURE_SH }, + { "sh4a", ARCHITECTURE_SH }, +#elif defined(__m68k__) + { "m68k", ARCHITECTURE_M68K }, +#elif defined(__tilegx__) + { "tilegx", ARCHITECTURE_TILEGX }, +#elif defined(__cris__) + { "crisv32", ARCHITECTURE_CRIS }, +#else +#error "Please register your architecture here!" +#endif + }; + + static int cached = _ARCHITECTURE_INVALID; + struct utsname u; + unsigned i; + + if (cached != _ARCHITECTURE_INVALID) + return cached; + + assert_se(uname(&u) >= 0); + + for (i = 0; i < ELEMENTSOF(arch_map); i++) + if (streq(arch_map[i].machine, u.machine)) + return cached = arch_map[i].arch; + + assert_not_reached("Couldn't identify architecture. You need to patch systemd."); + return _ARCHITECTURE_INVALID; +} + +static const char *const architecture_table[_ARCHITECTURE_MAX] = { + [ARCHITECTURE_X86] = "x86", + [ARCHITECTURE_X86_64] = "x86-64", + [ARCHITECTURE_PPC] = "ppc", + [ARCHITECTURE_PPC_LE] = "ppc-le", + [ARCHITECTURE_PPC64] = "ppc64", + [ARCHITECTURE_PPC64_LE] = "ppc64-le", + [ARCHITECTURE_IA64] = "ia64", + [ARCHITECTURE_PARISC] = "parisc", + [ARCHITECTURE_PARISC64] = "parisc64", + [ARCHITECTURE_S390] = "s390", + [ARCHITECTURE_S390X] = "s390x", + [ARCHITECTURE_SPARC] = "sparc", + [ARCHITECTURE_SPARC64] = "sparc64", + [ARCHITECTURE_MIPS] = "mips", + [ARCHITECTURE_MIPS_LE] = "mips-le", + [ARCHITECTURE_MIPS64] = "mips64", + [ARCHITECTURE_MIPS64_LE] = "mips64-le", + [ARCHITECTURE_ALPHA] = "alpha", + [ARCHITECTURE_ARM] = "arm", + [ARCHITECTURE_ARM_BE] = "arm-be", + [ARCHITECTURE_ARM64] = "arm64", + [ARCHITECTURE_ARM64_BE] = "arm64-be", + [ARCHITECTURE_SH] = "sh", + [ARCHITECTURE_SH64] = "sh64", + [ARCHITECTURE_M68K] = "m68k", + [ARCHITECTURE_TILEGX] = "tilegx", + [ARCHITECTURE_CRIS] = "cris", +}; + +DEFINE_STRING_TABLE_LOOKUP(architecture, int); diff --git a/src/libshared/architecture.h b/src/libshared/architecture.h new file mode 100644 index 0000000000..26679e28c6 --- /dev/null +++ b/src/libshared/architecture.h @@ -0,0 +1,207 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "macro.h" +#include "util.h" + +/* A cleaned up architecture definition. We don't want to get lost in + * processor features, models, generations or even ABIs. Hence we + * focus on general family, and distinguish word width and + * endianness. */ + +enum { + ARCHITECTURE_X86 = 0, + ARCHITECTURE_X86_64, + ARCHITECTURE_PPC, + ARCHITECTURE_PPC_LE, + ARCHITECTURE_PPC64, + ARCHITECTURE_PPC64_LE, + ARCHITECTURE_IA64, + ARCHITECTURE_PARISC, + ARCHITECTURE_PARISC64, + ARCHITECTURE_S390, + ARCHITECTURE_S390X, + ARCHITECTURE_SPARC, + ARCHITECTURE_SPARC64, + ARCHITECTURE_MIPS, + ARCHITECTURE_MIPS_LE, + ARCHITECTURE_MIPS64, + ARCHITECTURE_MIPS64_LE, + ARCHITECTURE_ALPHA, + ARCHITECTURE_ARM, + ARCHITECTURE_ARM_BE, + ARCHITECTURE_ARM64, + ARCHITECTURE_ARM64_BE, + ARCHITECTURE_SH, + ARCHITECTURE_SH64, + ARCHITECTURE_M68K, + ARCHITECTURE_TILEGX, + ARCHITECTURE_CRIS, + _ARCHITECTURE_MAX, + _ARCHITECTURE_INVALID = -1 +}; + +int uname_architecture(void); + +/* + * LIB_ARCH_TUPLE should resolve to the local library path + * architecture tuple systemd is built for, according to the Debian + * tuple list: + * + * https://wiki.debian.org/Multiarch/Tuples + * + * This is used in library search paths that should understand + * Debian's paths on all distributions. + */ + +#if defined(__x86_64__) +# define native_architecture() ARCHITECTURE_X86_64 +# define LIB_ARCH_TUPLE "x86_64-linux-gnu" +# define PROC_CPUINFO_MODEL "model name" +#elif defined(__i386__) +# define native_architecture() ARCHITECTURE_X86 +# define LIB_ARCH_TUPLE "i386-linux-gnu" +# define PROC_CPUINFO_MODEL "model name" +#elif defined(__powerpc64__) +# if __BYTE_ORDER == __BIG_ENDIAN +# define native_architecture() ARCHITECTURE_PPC64 +# define LIB_ARCH_TUPLE "ppc64-linux-gnu" +# else +# define native_architecture() ARCHITECTURE_PPC64_LE +# define LIB_ARCH_TUPLE "powerpc64le-linux-gnu" +# endif +# define PROC_CPUINFO_MODEL "cpu" +#elif defined(__powerpc__) +# if __BYTE_ORDER == __BIG_ENDIAN +# define native_architecture() ARCHITECTURE_PPC +# define LIB_ARCH_TUPLE "powerpc-linux-gnu" +# else +# define native_architecture() ARCHITECTURE_PPC_LE +# error "Missing LIB_ARCH_TUPLE for PPCLE" +# endif +# define PROC_CPUINFO_MODEL "cpu" +#elif defined(__ia64__) +# define native_architecture() ARCHITECTURE_IA64 +# define LIB_ARCH_TUPLE "ia64-linux-gnu" +#elif defined(__hppa64__) +# define native_architecture() ARCHITECTURE_PARISC64 +# error "Missing LIB_ARCH_TUPLE for HPPA64" +# define PROC_CPUINFO_MODEL "cpu" +#elif defined(__hppa__) +# define native_architecture() ARCHITECTURE_PARISC +# define LIB_ARCH_TUPLE "hppa‑linux‑gnu" +# define PROC_CPUINFO_MODEL "cpu" +#elif defined(__s390x__) +# define native_architecture() ARCHITECTURE_S390X +# define LIB_ARCH_TUPLE "s390x-linux-gnu" +#elif defined(__s390__) +# define native_architecture() ARCHITECTURE_S390 +# define LIB_ARCH_TUPLE "s390-linux-gnu" +#elif defined(__sparc64__) +# define native_architecture() ARCHITECTURE_SPARC64 +# define LIB_ARCH_TUPLE "sparc64-linux-gnu" +# define PROC_CPUINFO_MODEL "cpu" +#elif defined(__sparc__) +# define native_architecture() ARCHITECTURE_SPARC +# define LIB_ARCH_TUPLE "sparc-linux-gnu" +# define PROC_CPUINFO_MODEL "cpu" +#elif defined(__mips64__) +# if __BYTE_ORDER == __BIG_ENDIAN +# define native_architecture() ARCHITECTURE_MIPS64 +# error "Missing LIB_ARCH_TUPLE for MIPS64" +# else +# define native_architecture() ARCHITECTURE_MIPS64_LE +# error "Missing LIB_ARCH_TUPLE for MIPS64_LE" +# endif +# define PROC_CPUINFO_MODEL "cpu model" +#elif defined(__mips__) +# if __BYTE_ORDER == __BIG_ENDIAN +# define native_architecture() ARCHITECTURE_MIPS +# define LIB_ARCH_TUPLE "mips-linux-gnu" +# else +# define native_architecture() ARCHITECTURE_MIPS_LE +# define LIB_ARCH_TUPLE "mipsel-linux-gnu" +# endif +# define PROC_CPUINFO_MODEL "cpu model" +#elif defined(__alpha__) +# define native_architecture() ARCHITECTURE_ALPHA +# define LIB_ARCH_TUPLE "alpha-linux-gnu" +#elif defined(__aarch64__) +# if __BYTE_ORDER == __BIG_ENDIAN +# define native_architecture() ARCHITECTURE_ARM64_BE +# define LIB_ARCH_TUPLE "aarch64_be-linux-gnu" +# else +# define native_architecture() ARCHITECTURE_ARM64 +# define LIB_ARCH_TUPLE "aarch64-linux-gnu" +# endif +#elif defined(__arm__) +# if __BYTE_ORDER == __BIG_ENDIAN +# define native_architecture() ARCHITECTURE_ARM_BE +# if defined(__ARM_EABI__) +# if defined(__ARM_PCS_VFP) +# define LIB_ARCH_TUPLE "armeb-linux-gnueabihf" +# else +# define LIB_ARCH_TUPLE "armeb-linux-gnueabi" +# endif +# else +# define LIB_ARCH_TUPLE "armeb-linux-gnu" +# endif +# else +# define native_architecture() ARCHITECTURE_ARM +# if defined(__ARM_EABI__) +# if defined(__ARM_PCS_VFP) +# define LIB_ARCH_TUPLE "arm-linux-gnueabihf" +# else +# define LIB_ARCH_TUPLE "arm-linux-gnueabi" +# endif +# else +# define LIB_ARCH_TUPLE "arm-linux-gnu" +# endif +# endif +# define PROC_CPUINFO_MODEL "model name" +#elif defined(__sh64__) +# define native_architecture() ARCHITECTURE_SH64 +# error "Missing LIB_ARCH_TUPLE for SH64" +#elif defined(__sh__) +# define native_architecture() ARCHITECTURE_SH +# define LIB_ARCH_TUPLE "sh4-linux-gnu" +#elif defined(__m68k__) +# define native_architecture() ARCHITECTURE_M68K +# define LIB_ARCH_TUPLE "m68k-linux-gnu" +#elif defined(__tilegx__) +# define native_architecture() ARCHITECTURE_TILEGX +# error "Missing LIB_ARCH_TUPLE for TILEGX" +#elif defined(__cris__) +# define native_architecture() ARCHITECTURE_CRIS +# error "Missing LIB_ARCH_TUPLE for CRIS" +#else +# error "Please register your architecture here!" +#endif + +#ifndef PROC_CPUINFO_MODEL +#warning "PROC_CPUINFO_MODEL not defined for your architecture" +#define PROC_CPUINFO_MODEL "model name" +#endif + +const char *architecture_to_string(int a) _const_; +int architecture_from_string(const char *s) _pure_; diff --git a/src/libshared/ask-password-api.c b/src/libshared/ask-password-api.c new file mode 100644 index 0000000000..6805873f9e --- /dev/null +++ b/src/libshared/ask-password-api.c @@ -0,0 +1,736 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "ask-password-api.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "io-util.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "random-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "time-util.h" +#include "umask-util.h" +#include "utf8.h" +#include "util.h" + +#define KEYRING_TIMEOUT_USEC ((5 * USEC_PER_MINUTE) / 2) + +static int lookup_key(const char *keyname, key_serial_t *ret) { + key_serial_t serial; + + assert(keyname); + assert(ret); + + serial = request_key("user", keyname, NULL, 0); + if (serial == -1) + return negative_errno(); + + *ret = serial; + return 0; +} + +static int retrieve_key(key_serial_t serial, char ***ret) { + _cleanup_free_ char *p = NULL; + long m = 100, n; + char **l; + + assert(ret); + + for (;;) { + p = new(char, m); + if (!p) + return -ENOMEM; + + n = keyctl(KEYCTL_READ, (unsigned long) serial, (unsigned long) p, (unsigned long) m, 0); + if (n < 0) + return -errno; + + if (n < m) + break; + + memory_erase(p, n); + free(p); + m *= 2; + } + + l = strv_parse_nulstr(p, n); + if (!l) + return -ENOMEM; + + memory_erase(p, n); + + *ret = l; + return 0; +} + +static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **passwords) { + _cleanup_strv_free_erase_ char **l = NULL; + _cleanup_free_ char *p = NULL; + key_serial_t serial; + size_t n; + int r; + + assert(keyname); + assert(passwords); + + if (!(flags & ASK_PASSWORD_PUSH_CACHE)) + return 0; + + r = lookup_key(keyname, &serial); + if (r >= 0) { + r = retrieve_key(serial, &l); + if (r < 0) + return r; + } else if (r != -ENOKEY) + return r; + + r = strv_extend_strv(&l, passwords, true); + if (r <= 0) + return r; + + r = strv_make_nulstr(l, &p, &n); + 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); + memory_erase(p, n); + if (serial == -1) + return -errno; + + if (keyctl(KEYCTL_SET_TIMEOUT, + (unsigned long) serial, + (unsigned long) DIV_ROUND_UP(KEYRING_TIMEOUT_USEC, USEC_PER_SEC), 0, 0) < 0) + log_debug_errno(errno, "Failed to adjust timeout: %m"); + + log_debug("Added key to keyring as %" PRIi32 ".", serial); + + return 1; +} + +static int add_to_keyring_and_log(const char *keyname, AskPasswordFlags flags, char **passwords) { + int r; + + assert(keyname); + assert(passwords); + + r = add_to_keyring(keyname, flags, passwords); + if (r < 0) + return log_debug_errno(r, "Failed to add password to keyring: %m"); + + return 0; +} + +int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret) { + + key_serial_t serial; + int r; + + assert(keyname); + assert(ret); + + if (!(flags & ASK_PASSWORD_ACCEPT_CACHED)) + return -EUNATCH; + + r = lookup_key(keyname, &serial); + if (r == -ENOSYS) /* when retrieving the distinction doesn't matter */ + return -ENOKEY; + if (r < 0) + return r; + + return retrieve_key(serial, ret); +} + +static void backspace_chars(int ttyfd, size_t p) { + + if (ttyfd < 0) + return; + + while (p > 0) { + p--; + + loop_write(ttyfd, "\b \b", 3, false); + } +} + +int ask_password_tty( + const char *message, + const char *keyname, + usec_t until, + AskPasswordFlags flags, + const char *flag_file, + char **ret) { + + struct termios old_termios, new_termios; + char passphrase[LINE_MAX + 1] = {}, *x; + size_t p = 0, codepoint = 0; + int r; + _cleanup_close_ int ttyfd = -1, notify = -1; + struct pollfd pollfd[2]; + bool reset_tty = false; + bool dirty = false; + enum { + POLL_TTY, + POLL_INOTIFY + }; + + assert(ret); + + if (flags & ASK_PASSWORD_NO_TTY) + return -EUNATCH; + + if (!message) + message = "Password:"; + + if (flag_file) { + notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); + if (notify < 0) { + r = -errno; + goto finish; + } + + if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) { + r = -errno; + goto finish; + } + } + + ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (ttyfd >= 0) { + + if (tcgetattr(ttyfd, &old_termios) < 0) { + r = -errno; + goto finish; + } + + loop_write(ttyfd, ANSI_HIGHLIGHT, strlen(ANSI_HIGHLIGHT), false); + loop_write(ttyfd, message, strlen(message), false); + loop_write(ttyfd, " ", 1, false); + loop_write(ttyfd, ANSI_NORMAL, strlen(ANSI_NORMAL), false); + + new_termios = old_termios; + new_termios.c_lflag &= ~(ICANON|ECHO); + new_termios.c_cc[VMIN] = 1; + new_termios.c_cc[VTIME] = 0; + + if (tcsetattr(ttyfd, TCSADRAIN, &new_termios) < 0) { + r = -errno; + goto finish; + } + + reset_tty = true; + } + + zero(pollfd); + pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO; + pollfd[POLL_TTY].events = POLLIN; + pollfd[POLL_INOTIFY].fd = notify; + pollfd[POLL_INOTIFY].events = POLLIN; + + for (;;) { + char c; + int sleep_for = -1, k; + ssize_t n; + + if (until > 0) { + usec_t y; + + y = now(CLOCK_MONOTONIC); + + if (y > until) { + r = -ETIME; + goto finish; + } + + sleep_for = (int) ((until - y) / USEC_PER_MSEC); + } + + if (flag_file) + if (access(flag_file, F_OK) < 0) { + r = -errno; + goto finish; + } + + k = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for); + if (k < 0) { + if (errno == EINTR) + continue; + + r = -errno; + goto finish; + } else if (k == 0) { + r = -ETIME; + goto finish; + } + + if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0) + flush_fd(notify); + + if (pollfd[POLL_TTY].revents == 0) + continue; + + n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1); + if (n < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + + r = -errno; + goto finish; + + } else if (n == 0) + break; + + if (c == '\n') + break; + else if (c == 21) { /* C-u */ + + if (!(flags & ASK_PASSWORD_SILENT)) + backspace_chars(ttyfd, p); + p = 0; + + } else if (c == '\b' || c == 127) { + + if (p > 0) { + + if (!(flags & ASK_PASSWORD_SILENT)) + backspace_chars(ttyfd, 1); + + p--; + } else if (!dirty && !(flags & ASK_PASSWORD_SILENT)) { + + flags |= ASK_PASSWORD_SILENT; + + /* There are two ways to enter silent + * mode. Either by pressing backspace + * as first key (and only as first + * key), or ... */ + if (ttyfd >= 0) + loop_write(ttyfd, "(no echo) ", 10, false); + + } else if (ttyfd >= 0) + loop_write(ttyfd, "\a", 1, false); + + } else if (c == '\t' && !(flags & ASK_PASSWORD_SILENT)) { + + backspace_chars(ttyfd, p); + flags |= ASK_PASSWORD_SILENT; + + /* ... or by pressing TAB at any time. */ + + if (ttyfd >= 0) + loop_write(ttyfd, "(no echo) ", 10, false); + } else { + if (p >= sizeof(passphrase)-1) { + loop_write(ttyfd, "\a", 1, false); + continue; + } + + passphrase[p++] = c; + + if (!(flags & ASK_PASSWORD_SILENT) && ttyfd >= 0) { + n = utf8_encoded_valid_unichar(passphrase + codepoint); + if (n >= 0) { + codepoint = p; + loop_write(ttyfd, (flags & ASK_PASSWORD_ECHO) ? &c : "*", 1, false); + } + } + + dirty = true; + } + + c = 'x'; + } + + x = strndup(passphrase, p); + memory_erase(passphrase, p); + if (!x) { + r = -ENOMEM; + goto finish; + } + + if (keyname) + (void) add_to_keyring_and_log(keyname, flags, STRV_MAKE(x)); + + *ret = x; + r = 0; + +finish: + if (ttyfd >= 0 && reset_tty) { + loop_write(ttyfd, "\n", 1, false); + tcsetattr(ttyfd, TCSADRAIN, &old_termios); + } + + return r; +} + +static int create_socket(char **name) { + union sockaddr_union sa = { + .un.sun_family = AF_UNIX, + }; + _cleanup_close_ int fd = -1; + static const int one = 1; + char *c; + int r; + + assert(name); + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return -errno; + + snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%" PRIx64, random_u64()); + + RUN_WITH_UMASK(0177) { + if (bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) + return -errno; + } + + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) + return -errno; + + c = strdup(sa.un.sun_path); + if (!c) + return -ENOMEM; + + *name = c; + + r = fd; + fd = -1; + + return r; +} + +int ask_password_agent( + const char *message, + const char *icon, + const char *id, + const char *keyname, + usec_t until, + AskPasswordFlags flags, + char ***ret) { + + enum { + FD_SOCKET, + FD_SIGNAL, + _FD_MAX + }; + + _cleanup_close_ int socket_fd = -1, signal_fd = -1, fd = -1; + char temp[] = "/run/systemd/ask-password/tmp.XXXXXX"; + char final[sizeof(temp)] = ""; + _cleanup_free_ char *socket_name = NULL; + _cleanup_strv_free_ char **l = NULL; + _cleanup_fclose_ FILE *f = NULL; + struct pollfd pollfd[_FD_MAX]; + sigset_t mask, oldmask; + int r; + + assert(ret); + + if (flags & ASK_PASSWORD_NO_AGENT) + return -EUNATCH; + + assert_se(sigemptyset(&mask) >= 0); + assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0); + assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) >= 0); + + (void) mkdir_p_label("/run/systemd/ask-password", 0755); + + fd = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC); + if (fd < 0) { + r = fd; + goto finish; + } + + (void) fchmod(fd, 0644); + + f = fdopen(fd, "w"); + if (!f) { + r = -errno; + goto finish; + } + + fd = -1; + + signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (signal_fd < 0) { + r = -errno; + goto finish; + } + + socket_fd = create_socket(&socket_name); + if (socket_fd < 0) { + r = socket_fd; + goto finish; + } + + fprintf(f, + "[Ask]\n" + "PID="PID_FMT"\n" + "Socket=%s\n" + "AcceptCached=%i\n" + "Echo=%i\n" + "NotAfter="USEC_FMT"\n", + getpid(), + socket_name, + (flags & ASK_PASSWORD_ACCEPT_CACHED) ? 1 : 0, + (flags & ASK_PASSWORD_ECHO) ? 1 : 0, + until); + + if (message) + fprintf(f, "Message=%s\n", message); + + if (icon) + fprintf(f, "Icon=%s\n", icon); + + if (id) + fprintf(f, "Id=%s\n", id); + + r = fflush_and_check(f); + if (r < 0) + goto finish; + + memcpy(final, temp, sizeof(temp)); + + final[sizeof(final)-11] = 'a'; + final[sizeof(final)-10] = 's'; + final[sizeof(final)-9] = 'k'; + + if (rename(temp, final) < 0) { + r = -errno; + goto finish; + } + + zero(pollfd); + pollfd[FD_SOCKET].fd = socket_fd; + pollfd[FD_SOCKET].events = POLLIN; + pollfd[FD_SIGNAL].fd = signal_fd; + pollfd[FD_SIGNAL].events = POLLIN; + + for (;;) { + char passphrase[LINE_MAX+1]; + struct msghdr msghdr; + struct iovec iovec; + struct ucred *ucred; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; + } control; + ssize_t n; + int k; + usec_t t; + + t = now(CLOCK_MONOTONIC); + + if (until > 0 && until <= t) { + r = -ETIME; + goto finish; + } + + k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1); + if (k < 0) { + if (errno == EINTR) + continue; + + r = -errno; + goto finish; + } + + if (k <= 0) { + r = -ETIME; + goto finish; + } + + if (pollfd[FD_SIGNAL].revents & POLLIN) { + r = -EINTR; + goto finish; + } + + if (pollfd[FD_SOCKET].revents != POLLIN) { + r = -EIO; + goto finish; + } + + zero(iovec); + iovec.iov_base = passphrase; + iovec.iov_len = sizeof(passphrase); + + zero(control); + zero(msghdr); + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + n = recvmsg(socket_fd, &msghdr, 0); + if (n < 0) { + if (errno == EAGAIN || + errno == EINTR) + continue; + + r = -errno; + goto finish; + } + + cmsg_close_all(&msghdr); + + if (n <= 0) { + log_debug("Message too short"); + continue; + } + + if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) || + control.cmsghdr.cmsg_level != SOL_SOCKET || + control.cmsghdr.cmsg_type != SCM_CREDENTIALS || + control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) { + log_debug("Received message without credentials. Ignoring."); + continue; + } + + ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); + if (ucred->uid != 0) { + log_debug("Got request from unprivileged user. Ignoring."); + continue; + } + + if (passphrase[0] == '+') { + /* An empty message refers to the empty password */ + if (n == 1) + l = strv_new("", NULL); + else + l = strv_parse_nulstr(passphrase+1, n-1); + memory_erase(passphrase, n); + if (!l) { + r = -ENOMEM; + goto finish; + } + + if (strv_length(l) <= 0) { + l = strv_free(l); + log_debug("Invalid packet"); + continue; + } + + break; + } + + if (passphrase[0] == '-') { + r = -ECANCELED; + goto finish; + } + + log_debug("Invalid packet"); + } + + if (keyname) + (void) add_to_keyring_and_log(keyname, flags, l); + + *ret = l; + l = NULL; + r = 0; + +finish: + if (socket_name) + (void) unlink(socket_name); + + (void) unlink(temp); + + if (final[0]) + (void) unlink(final); + + assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0); + return r; +} + +int ask_password_auto( + const char *message, + const char *icon, + const char *id, + const char *keyname, + usec_t until, + AskPasswordFlags flags, + char ***ret) { + + int r; + + assert(ret); + + if ((flags & ASK_PASSWORD_ACCEPT_CACHED) && keyname) { + r = ask_password_keyring(keyname, flags, ret); + if (r != -ENOKEY) + return r; + } + + if (!(flags & ASK_PASSWORD_NO_TTY) && isatty(STDIN_FILENO)) { + char *s = NULL, **l = NULL; + + r = ask_password_tty(message, keyname, until, flags, NULL, &s); + if (r < 0) + return r; + + r = strv_push(&l, s); + if (r < 0) { + string_erase(s); + free(s); + return -ENOMEM; + } + + *ret = l; + return 0; + } + + if (!(flags & ASK_PASSWORD_NO_AGENT)) + return ask_password_agent(message, icon, id, keyname, until, flags, ret); + + return -EUNATCH; +} diff --git a/src/libshared/ask-password-api.h b/src/libshared/ask-password-api.h new file mode 100644 index 0000000000..9d7f65130c --- /dev/null +++ b/src/libshared/ask-password-api.h @@ -0,0 +1,38 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "time-util.h" + +typedef enum AskPasswordFlags { + ASK_PASSWORD_ACCEPT_CACHED = 1, + ASK_PASSWORD_PUSH_CACHE = 2, + ASK_PASSWORD_ECHO = 4, /* show the password literally while reading, instead of "*" */ + ASK_PASSWORD_SILENT = 8, /* do no show any password at all while reading */ + ASK_PASSWORD_NO_TTY = 16, + ASK_PASSWORD_NO_AGENT = 32, +} AskPasswordFlags; + +int ask_password_tty(const char *message, const char *keyname, usec_t until, AskPasswordFlags flags, const char *flag_file, char **ret); +int ask_password_agent(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret); +int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret); +int ask_password_auto(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret); diff --git a/src/libshared/base-filesystem.c b/src/libshared/base-filesystem.c new file mode 100644 index 0000000000..59a34a9d11 --- /dev/null +++ b/src/libshared/base-filesystem.c @@ -0,0 +1,129 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "base-filesystem.h" +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "umask-util.h" +#include "user-util.h" +#include "util.h" + +typedef struct BaseFilesystem { + const char *dir; + mode_t mode; + const char *target; + const char *exists; + bool ignore_failure; +} BaseFilesystem; + +static const BaseFilesystem table[] = { + { "bin", 0, "usr/bin\0", NULL }, + { "lib", 0, "usr/lib\0", NULL }, + { "root", 0755, NULL, NULL, true }, + { "sbin", 0, "usr/sbin\0", NULL }, + { "usr", 0755, NULL, NULL }, + { "var", 0755, NULL, NULL }, + { "etc", 0755, NULL, NULL }, +#if defined(__i386__) || defined(__x86_64__) + { "lib64", 0, "usr/lib/x86_64-linux-gnu\0" + "usr/lib64\0", "ld-linux-x86-64.so.2" }, +#endif +}; + +int base_filesystem_create(const char *root, uid_t uid, gid_t gid) { + _cleanup_close_ int fd = -1; + unsigned i; + int r = 0; + + fd = open(root, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) + return log_error_errno(errno, "Failed to open root file system: %m"); + + for (i = 0; i < ELEMENTSOF(table); i ++) { + if (faccessat(fd, table[i].dir, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) + continue; + + if (table[i].target) { + const char *target = NULL, *s; + + /* check if one of the targets exists */ + NULSTR_FOREACH(s, table[i].target) { + if (faccessat(fd, s, F_OK, AT_SYMLINK_NOFOLLOW) < 0) + continue; + + /* check if a specific file exists at the target path */ + if (table[i].exists) { + _cleanup_free_ char *p = NULL; + + p = strjoin(s, "/", table[i].exists, NULL); + if (!p) + return log_oom(); + + if (faccessat(fd, p, F_OK, AT_SYMLINK_NOFOLLOW) < 0) + continue; + } + + target = s; + break; + } + + if (!target) + continue; + + r = symlinkat(target, fd, table[i].dir); + if (r < 0 && errno != EEXIST) + return log_error_errno(errno, "Failed to create symlink at %s/%s: %m", root, table[i].dir); + + if (uid != UID_INVALID || gid != UID_INVALID) { + if (fchownat(fd, table[i].dir, uid, gid, AT_SYMLINK_NOFOLLOW) < 0) + return log_error_errno(errno, "Failed to chown symlink at %s/%s: %m", root, table[i].dir); + } + + continue; + } + + RUN_WITH_UMASK(0000) + r = mkdirat(fd, table[i].dir, table[i].mode); + if (r < 0 && errno != EEXIST) { + log_full_errno(table[i].ignore_failure ? LOG_DEBUG : LOG_ERR, errno, + "Failed to create directory at %s/%s: %m", root, table[i].dir); + + if (!table[i].ignore_failure) + return -errno; + } + + if (uid != UID_INVALID || gid != UID_INVALID) { + if (fchownat(fd, table[i].dir, uid, gid, AT_SYMLINK_NOFOLLOW) < 0) + return log_error_errno(errno, "Failed to chown directory at %s/%s: %m", root, table[i].dir); + } + } + + return 0; +} diff --git a/src/libshared/base-filesystem.h b/src/libshared/base-filesystem.h new file mode 100644 index 0000000000..49599f0a60 --- /dev/null +++ b/src/libshared/base-filesystem.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +int base_filesystem_create(const char *root, uid_t uid, gid_t gid); diff --git a/src/libshared/boot-timestamps.c b/src/libshared/boot-timestamps.c new file mode 100644 index 0000000000..7e0152761c --- /dev/null +++ b/src/libshared/boot-timestamps.c @@ -0,0 +1,64 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include "acpi-fpdt.h" +#include "boot-timestamps.h" +#include "efivars.h" +#include "macro.h" +#include "time-util.h" + +int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader) { + usec_t x = 0, y = 0, a; + int r; + dual_timestamp _n; + + assert(firmware); + assert(loader); + + if (!n) { + dual_timestamp_get(&_n); + n = &_n; + } + + r = acpi_get_boot_usec(&x, &y); + if (r < 0) { + r = efi_loader_get_boot_usec(&x, &y); + if (r < 0) + return r; + } + + /* Let's convert this to timestamps where the firmware + * began/loader began working. To make this more confusing: + * since usec_t is unsigned and the kernel's monotonic clock + * begins at kernel initialization we'll actually initialize + * the monotonic timestamps here as negative of the actual + * value. */ + + firmware->monotonic = y; + loader->monotonic = y - x; + + a = n->monotonic + firmware->monotonic; + firmware->realtime = n->realtime > a ? n->realtime - a : 0; + + a = n->monotonic + loader->monotonic; + loader->realtime = n->realtime > a ? n->realtime - a : 0; + + return 0; +} diff --git a/src/libshared/boot-timestamps.h b/src/libshared/boot-timestamps.h new file mode 100644 index 0000000000..6f691026be --- /dev/null +++ b/src/libshared/boot-timestamps.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include + +int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader); diff --git a/src/libshared/bus-util.c b/src/libshared/bus-util.c new file mode 100644 index 0000000000..a874bb84cc --- /dev/null +++ b/src/libshared/bus-util.c @@ -0,0 +1,2412 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-label.h" +#include "bus-message.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "def.h" +#include "env-util.h" +#include "escape.h" +#include "extract-word.h" +#include "fd-util.h" +#include "hashmap.h" +#include "install.h" +#include "kdbus.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "set.h" +#include "signal-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "syslog-util.h" +#include "time-util.h" +#include "unit-name.h" +#include "user-util.h" +#include "utf8.h" +#include "util.h" + +static int name_owner_change_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + sd_event *e = userdata; + + assert(m); + assert(e); + + sd_bus_close(sd_bus_message_get_bus(m)); + sd_event_exit(e, 0); + + return 1; +} + +int bus_async_unregister_and_exit(sd_event *e, sd_bus *bus, const char *name) { + _cleanup_free_ char *match = NULL; + const char *unique; + int r; + + assert(e); + assert(bus); + assert(name); + + /* We unregister the name here and then wait for the + * NameOwnerChanged signal for this event to arrive before we + * quit. We do this in order to make sure that any queued + * requests are still processed before we really exit. */ + + r = sd_bus_get_unique_name(bus, &unique); + if (r < 0) + return r; + + r = asprintf(&match, + "sender='org.freedesktop.DBus'," + "type='signal'," + "interface='org.freedesktop.DBus'," + "member='NameOwnerChanged'," + "path='/org/freedesktop/DBus'," + "arg0='%s'," + "arg1='%s'," + "arg2=''", name, unique); + if (r < 0) + return -ENOMEM; + + r = sd_bus_add_match(bus, NULL, match, name_owner_change_callback, e); + if (r < 0) + return r; + + r = sd_bus_release_name(bus, name); + if (r < 0) + return r; + + return 0; +} + +int bus_event_loop_with_idle( + sd_event *e, + sd_bus *bus, + const char *name, + usec_t timeout, + check_idle_t check_idle, + void *userdata) { + bool exiting = false; + int r, code; + + assert(e); + assert(bus); + assert(name); + + for (;;) { + bool idle; + + r = sd_event_get_state(e); + if (r < 0) + return r; + if (r == SD_EVENT_FINISHED) + break; + + if (check_idle) + idle = check_idle(userdata); + else + idle = true; + + r = sd_event_run(e, exiting || !idle ? (uint64_t) -1 : timeout); + if (r < 0) + return r; + + if (r == 0 && !exiting && idle) { + + r = sd_bus_try_close(bus); + if (r == -EBUSY) + continue; + + /* Fallback for dbus1 connections: we + * unregister the name and wait for the + * response to come through for it */ + if (r == -EOPNOTSUPP) { + + /* Inform the service manager that we + * are going down, so that it will + * queue all further start requests, + * instead of assuming we are already + * running. */ + sd_notify(false, "STOPPING=1"); + + r = bus_async_unregister_and_exit(e, bus, name); + if (r < 0) + return r; + + exiting = true; + continue; + } + + if (r < 0) + return r; + + sd_event_exit(e, 0); + break; + } + } + + r = sd_event_get_exit_code(e, &code); + if (r < 0) + return r; + + return code; +} + +int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *rep = NULL; + int r, has_owner = 0; + + assert(c); + assert(name); + + r = sd_bus_call_method(c, + "org.freedesktop.DBus", + "/org/freedesktop/dbus", + "org.freedesktop.DBus", + "NameHasOwner", + error, + &rep, + "s", + name); + if (r < 0) + return r; + + r = sd_bus_message_read_basic(rep, 'b', &has_owner); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + return has_owner; +} + +static int check_good_user(sd_bus_message *m, uid_t good_user) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + uid_t sender_uid; + int r; + + assert(m); + + if (good_user == UID_INVALID) + return 0; + + r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds); + if (r < 0) + return r; + + /* Don't trust augmented credentials for authorization */ + assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EUID) == 0, -EPERM); + + r = sd_bus_creds_get_euid(creds, &sender_uid); + if (r < 0) + return r; + + return sender_uid == good_user; +} + +int bus_test_polkit( + sd_bus_message *call, + int capability, + const char *action, + const char **details, + uid_t good_user, + bool *_challenge, + sd_bus_error *e) { + + int r; + + assert(call); + assert(action); + + /* Tests non-interactively! */ + + r = check_good_user(call, good_user); + if (r != 0) + return r; + + r = sd_bus_query_sender_privilege(call, capability); + if (r < 0) + return r; + else if (r > 0) + return 1; +#ifdef ENABLE_POLKIT + else { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int authorized = false, challenge = false; + const char *sender, **k, **v; + + sender = sd_bus_message_get_sender(call); + if (!sender) + return -EBADMSG; + + r = sd_bus_message_new_method_call( + call->bus, + &request, + "org.freedesktop.PolicyKit1", + "/org/freedesktop/PolicyKit1/Authority", + "org.freedesktop.PolicyKit1.Authority", + "CheckAuthorization"); + if (r < 0) + return r; + + r = sd_bus_message_append( + request, + "(sa{sv})s", + "system-bus-name", 1, "name", "s", sender, + action); + if (r < 0) + return r; + + r = sd_bus_message_open_container(request, 'a', "{ss}"); + if (r < 0) + return r; + + STRV_FOREACH_PAIR(k, v, details) { + r = sd_bus_message_append(request, "{ss}", *k, *v); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(request); + if (r < 0) + return r; + + r = sd_bus_message_append(request, "us", 0, NULL); + if (r < 0) + return r; + + r = sd_bus_call(call->bus, request, 0, e, &reply); + if (r < 0) { + /* Treat no PK available as access denied */ + if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN)) { + sd_bus_error_free(e); + return -EACCES; + } + + return r; + } + + r = sd_bus_message_enter_container(reply, 'r', "bba{ss}"); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "bb", &authorized, &challenge); + if (r < 0) + return r; + + if (authorized) + return 1; + + if (_challenge) { + *_challenge = challenge; + return 0; + } + } +#endif + + return -EACCES; +} + +#ifdef ENABLE_POLKIT + +typedef struct AsyncPolkitQuery { + sd_bus_message *request, *reply; + sd_bus_message_handler_t callback; + void *userdata; + sd_bus_slot *slot; + Hashmap *registry; +} AsyncPolkitQuery; + +static void async_polkit_query_free(AsyncPolkitQuery *q) { + + if (!q) + return; + + sd_bus_slot_unref(q->slot); + + if (q->registry && q->request) + hashmap_remove(q->registry, q->request); + + sd_bus_message_unref(q->request); + sd_bus_message_unref(q->reply); + + free(q); +} + +static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + AsyncPolkitQuery *q = userdata; + int r; + + assert(reply); + assert(q); + + q->slot = sd_bus_slot_unref(q->slot); + q->reply = sd_bus_message_ref(reply); + + r = sd_bus_message_rewind(q->request, true); + if (r < 0) { + r = sd_bus_reply_method_errno(q->request, r, NULL); + goto finish; + } + + r = q->callback(q->request, q->userdata, &error_buffer); + r = bus_maybe_reply_error(q->request, r, &error_buffer); + +finish: + async_polkit_query_free(q); + + return r; +} + +#endif + +int bus_verify_polkit_async( + sd_bus_message *call, + int capability, + const char *action, + const char **details, + bool interactive, + uid_t good_user, + Hashmap **registry, + sd_bus_error *error) { + +#ifdef ENABLE_POLKIT + _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL; + AsyncPolkitQuery *q; + const char *sender, **k, **v; + sd_bus_message_handler_t callback; + void *userdata; + int c; +#endif + int r; + + assert(call); + assert(action); + assert(registry); + + r = check_good_user(call, good_user); + if (r != 0) + return r; + +#ifdef ENABLE_POLKIT + q = hashmap_get(*registry, call); + if (q) { + int authorized, challenge; + + /* This is the second invocation of this function, and + * there's already a response from polkit, let's + * process it */ + assert(q->reply); + + if (sd_bus_message_is_method_error(q->reply, NULL)) { + const sd_bus_error *e; + + /* Copy error from polkit reply */ + e = sd_bus_message_get_error(q->reply); + sd_bus_error_copy(error, e); + + /* Treat no PK available as access denied */ + if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN)) + return -EACCES; + + return -sd_bus_error_get_errno(e); + } + + r = sd_bus_message_enter_container(q->reply, 'r', "bba{ss}"); + if (r >= 0) + r = sd_bus_message_read(q->reply, "bb", &authorized, &challenge); + + if (r < 0) + return r; + + if (authorized) + return 1; + + if (challenge) + return sd_bus_error_set(error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required."); + + return -EACCES; + } +#endif + + r = sd_bus_query_sender_privilege(call, capability); + if (r < 0) + return r; + else if (r > 0) + return 1; + +#ifdef ENABLE_POLKIT + if (sd_bus_get_current_message(call->bus) != call) + return -EINVAL; + + callback = sd_bus_get_current_handler(call->bus); + if (!callback) + return -EINVAL; + + userdata = sd_bus_get_current_userdata(call->bus); + + sender = sd_bus_message_get_sender(call); + if (!sender) + return -EBADMSG; + + c = sd_bus_message_get_allow_interactive_authorization(call); + if (c < 0) + return c; + if (c > 0) + interactive = true; + + r = hashmap_ensure_allocated(registry, NULL); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call( + call->bus, + &pk, + "org.freedesktop.PolicyKit1", + "/org/freedesktop/PolicyKit1/Authority", + "org.freedesktop.PolicyKit1.Authority", + "CheckAuthorization"); + if (r < 0) + return r; + + r = sd_bus_message_append( + pk, + "(sa{sv})s", + "system-bus-name", 1, "name", "s", sender, + action); + if (r < 0) + return r; + + r = sd_bus_message_open_container(pk, 'a', "{ss}"); + if (r < 0) + return r; + + STRV_FOREACH_PAIR(k, v, details) { + r = sd_bus_message_append(pk, "{ss}", *k, *v); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(pk); + if (r < 0) + return r; + + r = sd_bus_message_append(pk, "us", !!interactive, NULL); + if (r < 0) + return r; + + q = new0(AsyncPolkitQuery, 1); + if (!q) + return -ENOMEM; + + q->request = sd_bus_message_ref(call); + q->callback = callback; + q->userdata = userdata; + + r = hashmap_put(*registry, call, q); + if (r < 0) { + async_polkit_query_free(q); + return r; + } + + q->registry = *registry; + + r = sd_bus_call_async(call->bus, &q->slot, pk, async_polkit_callback, q, 0); + if (r < 0) { + async_polkit_query_free(q); + return r; + } + + return 0; +#endif + + return -EACCES; +} + +void bus_verify_polkit_async_registry_free(Hashmap *registry) { +#ifdef ENABLE_POLKIT + AsyncPolkitQuery *q; + + while ((q = hashmap_steal_first(registry))) + async_polkit_query_free(q); + + hashmap_free(registry); +#endif +} + +int bus_check_peercred(sd_bus *c) { + struct ucred ucred; + socklen_t l; + int fd; + + assert(c); + + fd = sd_bus_get_fd(c); + if (fd < 0) + return fd; + + l = sizeof(struct ucred); + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l) < 0) + return -errno; + + if (l != sizeof(struct ucred)) + return -E2BIG; + + if (ucred.uid != 0 && ucred.uid != geteuid()) + return -EPERM; + + return 1; +} + +int bus_connect_system_systemd(sd_bus **_bus) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + int r; + + assert(_bus); + + if (geteuid() != 0) + return sd_bus_default_system(_bus); + + /* If we are root and kdbus is not available, then let's talk + * directly to the system instance, instead of going via the + * bus */ + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + r = sd_bus_set_address(bus, KERNEL_SYSTEM_BUS_ADDRESS); + if (r < 0) + return r; + + bus->bus_client = true; + + r = sd_bus_start(bus); + if (r >= 0) { + *_bus = bus; + bus = NULL; + return 0; + } + + bus = sd_bus_unref(bus); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + r = sd_bus_set_address(bus, "unix:path=/run/systemd/private"); + if (r < 0) + return r; + + r = sd_bus_start(bus); + if (r < 0) + return sd_bus_default_system(_bus); + + r = bus_check_peercred(bus); + if (r < 0) + return r; + + *_bus = bus; + bus = NULL; + + return 0; +} + +int bus_connect_user_systemd(sd_bus **_bus) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *ee = NULL; + const char *e; + int r; + + /* Try via kdbus first, and then directly */ + + assert(_bus); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + if (asprintf(&bus->address, KERNEL_USER_BUS_ADDRESS_FMT, getuid()) < 0) + return -ENOMEM; + + bus->bus_client = true; + + r = sd_bus_start(bus); + if (r >= 0) { + *_bus = bus; + bus = NULL; + return 0; + } + + bus = sd_bus_unref(bus); + + e = secure_getenv("XDG_RUNTIME_DIR"); + if (!e) + return sd_bus_default_user(_bus); + + ee = bus_address_escape(e); + if (!ee) + return -ENOMEM; + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + bus->address = strjoin("unix:path=", ee, "/systemd/private", NULL); + if (!bus->address) + return -ENOMEM; + + r = sd_bus_start(bus); + if (r < 0) + return sd_bus_default_user(_bus); + + r = bus_check_peercred(bus); + if (r < 0) + return r; + + *_bus = bus; + bus = NULL; + + return 0; +} + +int bus_print_property(const char *name, sd_bus_message *property, bool all) { + char type; + const char *contents; + int r; + + assert(name); + assert(property); + + r = sd_bus_message_peek_type(property, &type, &contents); + if (r < 0) + return r; + + switch (type) { + + case SD_BUS_TYPE_STRING: { + const char *s; + + r = sd_bus_message_read_basic(property, type, &s); + if (r < 0) + return r; + + if (all || !isempty(s)) { + _cleanup_free_ char *escaped = NULL; + + escaped = xescape(s, "\n"); + if (!escaped) + return -ENOMEM; + + printf("%s=%s\n", name, escaped); + } + + return 1; + } + + case SD_BUS_TYPE_BOOLEAN: { + int b; + + r = sd_bus_message_read_basic(property, type, &b); + if (r < 0) + return r; + + printf("%s=%s\n", name, yes_no(b)); + + return 1; + } + + case SD_BUS_TYPE_UINT64: { + uint64_t u; + + r = sd_bus_message_read_basic(property, type, &u); + if (r < 0) + return r; + + /* Yes, heuristics! But we can change this check + * should it turn out to not be sufficient */ + + if (endswith(name, "Timestamp")) { + char timestamp[FORMAT_TIMESTAMP_MAX], *t; + + t = format_timestamp(timestamp, sizeof(timestamp), u); + if (t || all) + printf("%s=%s\n", name, strempty(t)); + + } else if (strstr(name, "USec")) { + char timespan[FORMAT_TIMESPAN_MAX]; + + printf("%s=%s\n", name, format_timespan(timespan, sizeof(timespan), u, 0)); + } else + printf("%s=%llu\n", name, (unsigned long long) u); + + return 1; + } + + case SD_BUS_TYPE_INT64: { + int64_t i; + + r = sd_bus_message_read_basic(property, type, &i); + if (r < 0) + return r; + + printf("%s=%lld\n", name, (long long) i); + + return 1; + } + + case SD_BUS_TYPE_UINT32: { + uint32_t u; + + r = sd_bus_message_read_basic(property, type, &u); + if (r < 0) + return r; + + if (strstr(name, "UMask") || strstr(name, "Mode")) + printf("%s=%04o\n", name, u); + else + printf("%s=%u\n", name, (unsigned) u); + + return 1; + } + + case SD_BUS_TYPE_INT32: { + int32_t i; + + r = sd_bus_message_read_basic(property, type, &i); + if (r < 0) + return r; + + printf("%s=%i\n", name, (int) i); + return 1; + } + + case SD_BUS_TYPE_DOUBLE: { + double d; + + r = sd_bus_message_read_basic(property, type, &d); + if (r < 0) + return r; + + printf("%s=%g\n", name, d); + return 1; + } + + case SD_BUS_TYPE_ARRAY: + if (streq(contents, "s")) { + bool first = true; + const char *str; + + r = sd_bus_message_enter_container(property, SD_BUS_TYPE_ARRAY, contents); + if (r < 0) + return r; + + while((r = sd_bus_message_read_basic(property, SD_BUS_TYPE_STRING, &str)) > 0) { + _cleanup_free_ char *escaped = NULL; + + if (first) + printf("%s=", name); + + escaped = xescape(str, "\n "); + if (!escaped) + return -ENOMEM; + + printf("%s%s", first ? "" : " ", escaped); + + first = false; + } + if (r < 0) + return r; + + if (first && all) + printf("%s=", name); + if (!first || all) + puts(""); + + r = sd_bus_message_exit_container(property); + if (r < 0) + return r; + + return 1; + + } else if (streq(contents, "y")) { + const uint8_t *u; + size_t n; + + r = sd_bus_message_read_array(property, SD_BUS_TYPE_BYTE, (const void**) &u, &n); + if (r < 0) + return r; + + if (all || n > 0) { + unsigned int i; + + printf("%s=", name); + + for (i = 0; i < n; i++) + printf("%02x", u[i]); + + puts(""); + } + + return 1; + + } else if (streq(contents, "u")) { + uint32_t *u; + size_t n; + + r = sd_bus_message_read_array(property, SD_BUS_TYPE_UINT32, (const void**) &u, &n); + if (r < 0) + return r; + + if (all || n > 0) { + unsigned int i; + + printf("%s=", name); + + for (i = 0; i < n; i++) + printf("%08x", u[i]); + + puts(""); + } + + return 1; + } + + break; + } + + return 0; +} + +int bus_print_all_properties(sd_bus *bus, const char *dest, const char *path, char **filter, bool all) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + + r = sd_bus_call_method(bus, + dest, + path, + "org.freedesktop.DBus.Properties", + "GetAll", + &error, + &reply, + "s", ""); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) + return r; + + while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const char *name; + const char *contents; + + r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &name); + if (r < 0) + return r; + + if (!filter || strv_find(filter, name)) { + r = sd_bus_message_peek_type(reply, NULL, &contents); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, contents); + if (r < 0) + return r; + + r = bus_print_property(name, reply, all); + if (r < 0) + return r; + if (r == 0) { + if (all) + printf("%s=[unprintable]\n", name); + /* skip what we didn't read */ + r = sd_bus_message_skip(reply, contents); + if (r < 0) + return r; + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + } else { + r = sd_bus_message_skip(reply, "v"); + if (r < 0) + return r; + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + return 0; +} + +int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { + sd_id128_t *p = userdata; + const void *v; + size_t n; + int r; + + r = sd_bus_message_read_array(m, SD_BUS_TYPE_BYTE, &v, &n); + if (r < 0) + return r; + + if (n == 0) + *p = SD_ID128_NULL; + else if (n == 16) + memcpy((*p).bytes, v, n); + else + return -EINVAL; + + return 0; +} + +static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { + char type; + int r; + + r = sd_bus_message_peek_type(m, &type, NULL); + if (r < 0) + return r; + + switch (type) { + case SD_BUS_TYPE_STRING: { + const char *s; + char **p = userdata; + + r = sd_bus_message_read_basic(m, type, &s); + if (r < 0) + break; + + if (isempty(s)) + break; + + r = free_and_strdup(p, s); + break; + } + + case SD_BUS_TYPE_ARRAY: { + _cleanup_strv_free_ char **l = NULL; + char ***p = userdata; + + r = bus_message_read_strv_extend(m, &l); + if (r < 0) + break; + + strv_free(*p); + *p = l; + l = NULL; + + break; + } + + case SD_BUS_TYPE_BOOLEAN: { + unsigned b; + bool *p = userdata; + + r = sd_bus_message_read_basic(m, type, &b); + if (r < 0) + break; + + *p = b; + + break; + } + + case SD_BUS_TYPE_UINT32: { + uint64_t u; + uint32_t *p = userdata; + + r = sd_bus_message_read_basic(m, type, &u); + if (r < 0) + break; + + *p = u; + + break; + } + + case SD_BUS_TYPE_UINT64: { + uint64_t t; + uint64_t *p = userdata; + + r = sd_bus_message_read_basic(m, type, &t); + if (r < 0) + break; + + *p = t; + + break; + } + + default: + break; + } + + return r; +} + +int bus_message_map_all_properties( + sd_bus_message *m, + const struct bus_properties_map *map, + void *userdata) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(m); + assert(map); + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) + return r; + + while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const struct bus_properties_map *prop; + const char *member; + const char *contents; + void *v; + unsigned i; + + r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member); + if (r < 0) + return r; + + for (i = 0, prop = NULL; map[i].member; i++) + if (streq(map[i].member, member)) { + prop = &map[i]; + break; + } + + if (prop) { + r = sd_bus_message_peek_type(m, NULL, &contents); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); + if (r < 0) + return r; + + v = (uint8_t *)userdata + prop->offset; + if (map[i].set) + r = prop->set(sd_bus_message_get_bus(m), member, m, &error, v); + else + r = map_basic(sd_bus_message_get_bus(m), member, m, &error, v); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } else { + r = sd_bus_message_skip(m, "v"); + if (r < 0) + return r; + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } + if (r < 0) + return r; + + return sd_bus_message_exit_container(m); +} + +int bus_message_map_properties_changed( + sd_bus_message *m, + const struct bus_properties_map *map, + void *userdata) { + + const char *member; + int r, invalidated, i; + + assert(m); + assert(map); + + r = bus_message_map_all_properties(m, map, userdata); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s"); + if (r < 0) + return r; + + invalidated = 0; + while ((r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member)) > 0) + for (i = 0; map[i].member; i++) + if (streq(map[i].member, member)) { + ++invalidated; + break; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + return invalidated; +} + +int bus_map_all_properties( + sd_bus *bus, + const char *destination, + const char *path, + const struct bus_properties_map *map, + void *userdata) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(destination); + assert(path); + assert(map); + + r = sd_bus_call_method( + bus, + destination, + path, + "org.freedesktop.DBus.Properties", + "GetAll", + &error, + &m, + "s", ""); + if (r < 0) + return r; + + return bus_message_map_all_properties(m, map, userdata); +} + +int bus_connect_transport(BusTransport transport, const char *host, bool user, sd_bus **bus) { + int r; + + assert(transport >= 0); + assert(transport < _BUS_TRANSPORT_MAX); + assert(bus); + + assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL); + assert_return(transport == BUS_TRANSPORT_LOCAL || !user, -EOPNOTSUPP); + + switch (transport) { + + case BUS_TRANSPORT_LOCAL: + if (user) + r = sd_bus_default_user(bus); + else + r = sd_bus_default_system(bus); + + break; + + case BUS_TRANSPORT_REMOTE: + r = sd_bus_open_system_remote(bus, host); + break; + + case BUS_TRANSPORT_MACHINE: + r = sd_bus_open_system_machine(bus, host); + break; + + default: + assert_not_reached("Hmm, unknown transport type."); + } + + return r; +} + +int bus_connect_transport_systemd(BusTransport transport, const char *host, bool user, sd_bus **bus) { + int r; + + assert(transport >= 0); + assert(transport < _BUS_TRANSPORT_MAX); + assert(bus); + + assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL); + assert_return(transport == BUS_TRANSPORT_LOCAL || !user, -EOPNOTSUPP); + + switch (transport) { + + case BUS_TRANSPORT_LOCAL: + if (user) + r = bus_connect_user_systemd(bus); + else + r = bus_connect_system_systemd(bus); + + break; + + case BUS_TRANSPORT_REMOTE: + r = sd_bus_open_system_remote(bus, host); + break; + + case BUS_TRANSPORT_MACHINE: + r = sd_bus_open_system_machine(bus, host); + break; + + default: + assert_not_reached("Hmm, unknown transport type."); + } + + return r; +} + +int bus_property_get_bool( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + int b = *(bool*) userdata; + + return sd_bus_message_append_basic(reply, 'b', &b); +} + +#if __SIZEOF_SIZE_T__ != 8 +int bus_property_get_size( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t sz = *(size_t*) userdata; + + return sd_bus_message_append_basic(reply, 't', &sz); +} +#endif + +#if __SIZEOF_LONG__ != 8 +int bus_property_get_long( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + int64_t l = *(long*) userdata; + + return sd_bus_message_append_basic(reply, 'x', &l); +} + +int bus_property_get_ulong( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t ul = *(unsigned long*) userdata; + + return sd_bus_message_append_basic(reply, 't', &ul); +} +#endif + +int bus_log_parse_error(int r) { + return log_error_errno(r, "Failed to parse bus message: %m"); +} + +int bus_log_create_error(int r) { + return log_error_errno(r, "Failed to create bus message: %m"); +} + +int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u) { + assert(message); + assert(u); + + u->machine = NULL; + + return sd_bus_message_read( + message, + "(ssssssouso)", + &u->id, + &u->description, + &u->load_state, + &u->active_state, + &u->sub_state, + &u->following, + &u->unit_path, + &u->job_id, + &u->job_type, + &u->job_path); +} + +int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment) { + const char *eq, *field; + int r, rl; + + assert(m); + assert(assignment); + + eq = strchr(assignment, '='); + if (!eq) { + log_error("Not an assignment: %s", assignment); + return -EINVAL; + } + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); + if (r < 0) + return bus_log_create_error(r); + + field = strndupa(assignment, eq - assignment); + eq ++; + + if (streq(field, "CPUQuota")) { + + 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); + 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; + } + + goto finish; + + } else if (streq(field, "EnvironmentFile")) { + + r = sd_bus_message_append(m, "sv", "EnvironmentFiles", "a(sb)", 1, + eq[0] == '-' ? eq + 1 : eq, + eq[0] == '-'); + goto finish; + + } else if (STR_IN_SET(field, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) { + 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); + + l = strlen(field); + n = newa(char, l + 2); + if (!n) + return log_oom(); + + /* Change suffix Sec → USec */ + strcpy(mempcpy(n, field, l - 3), "USec"); + r = sd_bus_message_append(m, "sv", n, "t", t); + goto finish; + } + + r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field); + if (r < 0) + return bus_log_create_error(r); + + rl = rlimit_from_string(field); + if (rl >= 0) { + const char *sn; + struct rlimit l; + + r = rlimit_parse(rl, eq, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse resource limit: %s", eq); + + r = sd_bus_message_append(m, "v", "t", l.rlim_max); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); + if (r < 0) + return bus_log_create_error(r); + + sn = strjoina(field, "Soft"); + r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur); + + } else if (STR_IN_SET(field, + "CPUAccounting", "MemoryAccounting", "BlockIOAccounting", "TasksAccounting", + "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies", + "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit", + "PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges", + "SyslogLevelPrefix", "Delegate", "RemainAfterElapse")) { + + r = parse_boolean(eq); + if (r < 0) + return log_error_errno(r, "Failed to parse boolean assignment %s.", assignment); + + r = sd_bus_message_append(m, "v", "b", r); + + } else if (streq(field, "MemoryLimit")) { + uint64_t bytes; + + if (isempty(eq) || streq(eq, "infinity")) + bytes = (uint64_t) -1; + 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; + + r = cg_cpu_shares_parse(eq, &u); + if (r < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "t", u); + + } else if (STR_IN_SET(field, "BlockIOWeight", "StartupBlockIOWeight")) { + uint64_t u; + + r = cg_cpu_shares_parse(eq, &u); + if (r < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "t", u); + + } else if (STR_IN_SET(field, + "User", "Group", "DevicePolicy", "KillMode", + "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath", + "StandardInput", "StandardOutput", "StandardError", + "Description", "Slice", "Type", "WorkingDirectory", + "RootDirectory", "SyslogIdentifier", "ProtectSystem", + "ProtectHome")) + r = sd_bus_message_append(m, "v", "s", eq); + + else if (streq(field, "SyslogLevel")) { + int level; + + level = log_level_from_string(eq); + if (level < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "i", level); + + } else if (streq(field, "SyslogFacility")) { + int facility; + + facility = log_facility_unshifted_from_string(eq); + if (facility < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "i", facility); + + } else if (streq(field, "DeviceAllow")) { + + if (isempty(eq)) + r = sd_bus_message_append(m, "v", "a(ss)", 0); + else { + const char *path, *rwm, *e; + + e = strchr(eq, ' '); + if (e) { + path = strndupa(eq, e - eq); + rwm = e+1; + } else { + path = eq; + rwm = ""; + } + + if (!path_startswith(path, "/dev")) { + log_error("%s is not a device file in /dev.", path); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "a(ss)", 1, path, rwm); + } + + } else if (STR_IN_SET(field, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) { + + if (isempty(eq)) + r = sd_bus_message_append(m, "v", "a(st)", 0); + else { + const char *path, *bandwidth, *e; + uint64_t bytes; + + e = strchr(eq, ' '); + if (e) { + path = strndupa(eq, e - eq); + bandwidth = e+1; + } else { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + if (!path_startswith(path, "/dev")) { + log_error("%s is not a device file in /dev.", path); + return -EINVAL; + } + + r = parse_size(bandwidth, 1000, &bytes); + if (r < 0) { + log_error("Failed to parse byte value %s.", bandwidth); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "a(st)", 1, path, bytes); + } + + } else if (streq(field, "BlockIODeviceWeight")) { + + if (isempty(eq)) + r = sd_bus_message_append(m, "v", "a(st)", 0); + else { + const char *path, *weight, *e; + uint64_t u; + + e = strchr(eq, ' '); + if (e) { + path = strndupa(eq, e - eq); + weight = e+1; + } else { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + if (!path_startswith(path, "/dev")) { + log_error("%s is not a device file in /dev.", path); + return -EINVAL; + } + + r = safe_atou64(weight, &u); + if (r < 0) { + log_error("Failed to parse %s value %s.", field, weight); + return -EINVAL; + } + r = sd_bus_message_append(m, "v", "a(st)", path, u); + } + + } else if (streq(field, "Nice")) { + int32_t i; + + r = safe_atoi32(eq, &i); + if (r < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "i", i); + + } else if (STR_IN_SET(field, "Environment", "PassEnvironment")) { + const char *p; + + r = sd_bus_message_open_container(m, 'v', "as"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + p = eq; + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE); + if (r < 0) { + log_error("Failed to parse Environment value %s", eq); + return -EINVAL; + } + if (r == 0) + break; + + if (streq(field, "Environment")) { + if (!env_assignment_is_valid(word)) { + log_error("Invalid environment assignment: %s", word); + return -EINVAL; + } + } else { /* PassEnvironment */ + if (!env_name_is_valid(word)) { + log_error("Invalid environment variable name: %s", word); + return -EINVAL; + } + } + + r = sd_bus_message_append_basic(m, 's', word); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + + } else if (streq(field, "KillSignal")) { + int sig; + + sig = signal_from_string_try_harder(eq); + if (sig < 0) { + log_error("Failed to parse %s value %s.", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "i", sig); + + } else if (streq(field, "TimerSlackNSec")) { + nsec_t n; + + r = parse_nsec(eq, &n); + if (r < 0) { + log_error("Failed to parse %s value %s", field, eq); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "t", n); + } else if (streq(field, "OOMScoreAdjust")) { + int oa; + + r = safe_atoi(eq, &oa); + if (r < 0) { + log_error("Failed to parse %s value %s", field, eq); + return -EINVAL; + } + + if (!oom_score_adjust_is_valid(oa)) { + log_error("OOM score adjust value out of range"); + return -EINVAL; + } + + r = sd_bus_message_append(m, "v", "i", oa); + } else if (STR_IN_SET(field, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) { + const char *p; + + r = sd_bus_message_open_container(m, 'v', "as"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + p = eq; + + for (;;) { + _cleanup_free_ char *word = NULL; + int offset; + + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES); + if (r < 0) { + log_error("Failed to parse %s value %s", field, eq); + return -EINVAL; + } + if (r == 0) + break; + + if (!utf8_is_valid(word)) { + log_error("Failed to parse %s value %s", field, eq); + return -EINVAL; + } + + offset = word[0] == '-'; + if (!path_is_absolute(word + offset)) { + log_error("Failed to parse %s value %s", field, eq); + return -EINVAL; + } + + path_kill_slashes(word + offset); + + r = sd_bus_message_append_basic(m, 's', word); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + + } else if (streq(field, "RuntimeDirectory")) { + const char *p; + + r = sd_bus_message_open_container(m, 'v', "as"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + p = eq; + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES); + if (r < 0) + return log_error_errno(r, "Failed to parse %s value %s", field, eq); + + if (r == 0) + break; + + r = sd_bus_message_append_basic(m, 's', word); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + + } else { + log_error("Unknown assignment %s.", assignment); + return -EINVAL; + } + +finish: + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + return 0; +} + +typedef struct BusWaitForJobs { + sd_bus *bus; + Set *jobs; + + char *name; + char *result; + + sd_bus_slot *slot_job_removed; + sd_bus_slot *slot_disconnected; +} BusWaitForJobs; + +static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) { + assert(m); + + log_error("Warning! D-Bus connection terminated."); + sd_bus_close(sd_bus_message_get_bus(m)); + + return 0; +} + +static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + const char *path, *unit, *result; + BusWaitForJobs *d = userdata; + uint32_t id; + char *found; + int r; + + assert(m); + assert(d); + + r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + found = set_remove(d->jobs, (char*) path); + if (!found) + return 0; + + free(found); + + if (!isempty(result)) + d->result = strdup(result); + + if (!isempty(unit)) + d->name = strdup(unit); + + return 0; +} + +void bus_wait_for_jobs_free(BusWaitForJobs *d) { + if (!d) + return; + + set_free_free(d->jobs); + + sd_bus_slot_unref(d->slot_disconnected); + sd_bus_slot_unref(d->slot_job_removed); + + sd_bus_unref(d->bus); + + free(d->name); + free(d->result); + + free(d); +} + +int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) { + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL; + int r; + + assert(bus); + assert(ret); + + d = new0(BusWaitForJobs, 1); + if (!d) + return -ENOMEM; + + d->bus = sd_bus_ref(bus); + + /* When we are a bus client we match by sender. Direct + * connections OTOH have no initialized sender field, and + * hence we ignore the sender then */ + r = sd_bus_add_match( + bus, + &d->slot_job_removed, + bus->bus_client ? + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='JobRemoved'," + "path='/org/freedesktop/systemd1'" : + "type='signal'," + "interface='org.freedesktop.systemd1.Manager'," + "member='JobRemoved'," + "path='/org/freedesktop/systemd1'", + match_job_removed, d); + if (r < 0) + return r; + + r = sd_bus_add_match( + bus, + &d->slot_disconnected, + "type='signal'," + "sender='org.freedesktop.DBus.Local'," + "interface='org.freedesktop.DBus.Local'," + "member='Disconnected'", + match_disconnected, d); + if (r < 0) + return r; + + *ret = d; + d = NULL; + + return 0; +} + +static int bus_process_wait(sd_bus *bus) { + int r; + + for (;;) { + r = sd_bus_process(bus, NULL); + if (r < 0) + return r; + if (r > 0) + return 0; + + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) + return r; + } +} + +static int bus_job_get_service_result(BusWaitForJobs *d, char **result) { + _cleanup_free_ char *dbus_path = NULL; + + assert(d); + assert(d->name); + assert(result); + + dbus_path = unit_dbus_path_from_name(d->name); + if (!dbus_path) + return -ENOMEM; + + return sd_bus_get_property_string(d->bus, + "org.freedesktop.systemd1", + dbus_path, + "org.freedesktop.systemd1.Service", + "Result", + NULL, + result); +} + +static const struct { + const char *result, *explanation; +} explanations [] = { + { "resources", "a configured resource limit was exceeded" }, + { "timeout", "a timeout was exceeded" }, + { "exit-code", "the control process exited with error code" }, + { "signal", "a fatal signal was delivered to the control process" }, + { "core-dump", "a fatal signal was delivered causing the control process to dump core" }, + { "watchdog", "the service failed to send watchdog ping" }, + { "start-limit", "start of the service was attempted too often" } +}; + +static void log_job_error_with_service_result(const char* service, const char *result, const char *extra_args) { + _cleanup_free_ char *service_shell_quoted = NULL, *systemctl_extra_args = NULL; + + assert(service); + + service_shell_quoted = shell_maybe_quote(service); + + systemctl_extra_args = strjoin("systemctl ", extra_args, " ", NULL); + if (!systemctl_extra_args) { + log_oom(); + return; + } + + systemctl_extra_args = strstrip(systemctl_extra_args); + + if (!isempty(result)) { + unsigned i; + + for (i = 0; i < ELEMENTSOF(explanations); ++i) + if (streq(result, explanations[i].result)) + break; + + if (i < ELEMENTSOF(explanations)) { + log_error("Job for %s failed because %s. See \"%s status %s\" and \"journalctl -xe\" for details.\n", + service, + explanations[i].explanation, + systemctl_extra_args, + strna(service_shell_quoted)); + + goto finish; + } + } + + log_error("Job for %s failed. See \"%s status %s\" and \"journalctl -xe\" for details.\n", + service, + systemctl_extra_args, + strna(service_shell_quoted)); + +finish: + /* For some results maybe additional explanation is required */ + if (streq_ptr(result, "start-limit")) + log_info("To force a start use \"%1$s reset-failed %2$s\" followed by \"%1$s start %2$s\" again.", + systemctl_extra_args, + strna(service_shell_quoted)); +} + +static int check_wait_response(BusWaitForJobs *d, bool quiet, const char *extra_args) { + int r = 0; + + assert(d->result); + + if (!quiet) { + if (streq(d->result, "canceled")) + log_error("Job for %s canceled.", strna(d->name)); + else if (streq(d->result, "timeout")) + log_error("Job for %s timed out.", strna(d->name)); + else if (streq(d->result, "dependency")) + log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name)); + else if (streq(d->result, "invalid")) + log_error("%s is not active, cannot reload.", strna(d->name)); + else if (streq(d->result, "assert")) + log_error("Assertion failed on job for %s.", strna(d->name)); + else if (streq(d->result, "unsupported")) + log_error("Operation on or unit type of %s not supported on this system.", strna(d->name)); + else if (!streq(d->result, "done") && !streq(d->result, "skipped")) { + if (d->name) { + int q; + _cleanup_free_ char *result = NULL; + + q = bus_job_get_service_result(d, &result); + if (q < 0) + log_debug_errno(q, "Failed to get Result property of service %s: %m", d->name); + + log_job_error_with_service_result(d->name, result, extra_args); + } else + log_error("Job failed. See \"journalctl -xe\" for details."); + } + } + + if (streq(d->result, "canceled")) + r = -ECANCELED; + else if (streq(d->result, "timeout")) + r = -ETIME; + else if (streq(d->result, "dependency")) + r = -EIO; + else if (streq(d->result, "invalid")) + r = -ENOEXEC; + else if (streq(d->result, "assert")) + r = -EPROTO; + else if (streq(d->result, "unsupported")) + r = -EOPNOTSUPP; + else if (!streq(d->result, "done") && !streq(d->result, "skipped")) + r = -EIO; + + return r; +} + +int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char *extra_args) { + int r = 0; + + assert(d); + + while (!set_isempty(d->jobs)) { + int q; + + q = bus_process_wait(d->bus); + if (q < 0) + return log_error_errno(q, "Failed to wait for response: %m"); + + if (d->result) { + q = check_wait_response(d, quiet, extra_args); + /* Return the first error as it is most likely to be + * meaningful. */ + if (q < 0 && r == 0) + r = q; + + log_debug_errno(q, "Got result %s/%m for job %s", strna(d->result), strna(d->name)); + } + + d->name = mfree(d->name); + d->result = mfree(d->result); + } + + return r; +} + +int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) { + int r; + + assert(d); + + r = set_ensure_allocated(&d->jobs, &string_hash_ops); + if (r < 0) + return r; + + return set_put_strdup(d->jobs, path); +} + +int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) { + int r; + + r = bus_wait_for_jobs_add(d, path); + if (r < 0) + return log_oom(); + + return bus_wait_for_jobs(d, quiet, NULL); +} + +int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes) { + const char *type, *path, *source; + int r; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(sss)", &type, &path, &source)) > 0) { + if (!quiet) { + if (streq(type, "symlink")) + log_info("Created symlink from %s to %s.", path, source); + else + log_info("Removed symlink %s.", path); + } + + r = unit_file_changes_add(changes, n_changes, streq(type, "symlink") ? UNIT_FILE_SYMLINK : UNIT_FILE_UNLINK, path, source); + if (r < 0) + return r; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; +} + +/** + * bus_path_encode_unique() - encode unique object path + * @b: bus connection or NULL + * @prefix: object path prefix + * @sender_id: unique-name of client, or NULL + * @external_id: external ID to be chosen by client, or NULL + * @ret_path: storage for encoded object path pointer + * + * Whenever we provide a bus API that allows clients to create and manage + * server-side objects, we need to provide a unique name for these objects. If + * we let the server choose the name, we suffer from a race condition: If a + * client creates an object asynchronously, it cannot destroy that object until + * it received the method reply. It cannot know the name of the new object, + * thus, it cannot destroy it. Furthermore, it enforces a round-trip. + * + * Therefore, many APIs allow the client to choose the unique name for newly + * created objects. There're two problems to solve, though: + * 1) Object names are usually defined via dbus object paths, which are + * usually globally namespaced. Therefore, multiple clients must be able + * to choose unique object names without interference. + * 2) If multiple libraries share the same bus connection, they must be + * able to choose unique object names without interference. + * The first problem is solved easily by prefixing a name with the + * unique-bus-name of a connection. The server side must enforce this and + * reject any other name. The second problem is solved by providing unique + * suffixes from within sd-bus. + * + * This helper allows clients to create unique object-paths. It uses the + * template '/prefix/sender_id/external_id' and returns the new path in + * @ret_path (must be freed by the caller). + * If @sender_id is NULL, the unique-name of @b is used. If @external_id is + * NULL, this function allocates a unique suffix via @b (by requesting a new + * cookie). If both @sender_id and @external_id are given, @b can be passed as + * NULL. + * + * Returns: 0 on success, negative error code on failure. + */ +int bus_path_encode_unique(sd_bus *b, const char *prefix, const char *sender_id, const char *external_id, char **ret_path) { + _cleanup_free_ char *sender_label = NULL, *external_label = NULL; + char external_buf[DECIMAL_STR_MAX(uint64_t)], *p; + int r; + + assert_return(b || (sender_id && external_id), -EINVAL); + assert_return(object_path_is_valid(prefix), -EINVAL); + assert_return(ret_path, -EINVAL); + + if (!sender_id) { + r = sd_bus_get_unique_name(b, &sender_id); + if (r < 0) + return r; + } + + if (!external_id) { + xsprintf(external_buf, "%"PRIu64, ++b->cookie); + external_id = external_buf; + } + + sender_label = bus_label_escape(sender_id); + if (!sender_label) + return -ENOMEM; + + external_label = bus_label_escape(external_id); + if (!external_label) + return -ENOMEM; + + p = strjoin(prefix, "/", sender_label, "/", external_label, NULL); + if (!p) + return -ENOMEM; + + *ret_path = p; + return 0; +} + +/** + * bus_path_decode_unique() - decode unique object path + * @path: object path to decode + * @prefix: object path prefix + * @ret_sender: output parameter for sender-id label + * @ret_external: output parameter for external-id label + * + * This does the reverse of bus_path_encode_unique() (see its description for + * details). Both trailing labels, sender-id and external-id, are unescaped and + * returned in the given output parameters (the caller must free them). + * + * Note that this function returns 0 if the path does not match the template + * (see bus_path_encode_unique()), 1 if it matched. + * + * Returns: Negative error code on failure, 0 if the given object path does not + * match the template (return parameters are set to NULL), 1 if it was + * parsed successfully (return parameters contain allocated labels). + */ +int bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external) { + const char *p, *q; + char *sender, *external; + + assert(object_path_is_valid(path)); + assert(object_path_is_valid(prefix)); + assert(ret_sender); + assert(ret_external); + + p = object_path_startswith(path, prefix); + if (!p) { + *ret_sender = NULL; + *ret_external = NULL; + return 0; + } + + q = strchr(p, '/'); + if (!q) { + *ret_sender = NULL; + *ret_external = NULL; + return 0; + } + + sender = bus_label_unescape_n(p, q - p); + external = bus_label_unescape(q + 1); + if (!sender || !external) { + free(sender); + free(external); + return -ENOMEM; + } + + *ret_sender = sender; + *ret_external = external; + 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, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + struct rlimit *rl; + uint64_t u; + rlim_t x; + const char *is_soft; + + assert(bus); + assert(reply); + assert(userdata); + + is_soft = endswith(property, "Soft"); + rl = *(struct rlimit**) userdata; + if (rl) + x = is_soft ? rl->rlim_cur : rl->rlim_max; + else { + struct rlimit buf = {}; + int z; + const char *s; + + s = is_soft ? strndupa(property, is_soft - property) : property; + + z = rlimit_from_string(strstr(s, "Limit")); + assert(z >= 0); + + getrlimit(z, &buf); + x = is_soft ? buf.rlim_cur : buf.rlim_max; + } + + /* rlim_t might have different sizes, let's map + * RLIMIT_INFINITY to (uint64_t) -1, so that it is the same on + * all archs */ + u = x == RLIM_INFINITY ? (uint64_t) -1 : (uint64_t) x; + + return sd_bus_message_append(reply, "t", u); +} diff --git a/src/libshared/bus-util.h b/src/libshared/bus-util.h new file mode 100644 index 0000000000..77474522be --- /dev/null +++ b/src/libshared/bus-util.h @@ -0,0 +1,196 @@ +#pragma once + +/*** + 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 . +***/ + +#include +#include +#include +#include + +#include +#include +#include + +#include "hashmap.h" +#include "install.h" +#include "macro.h" +#include "string-util.h" +#include "time-util.h" + +typedef enum BusTransport { + BUS_TRANSPORT_LOCAL, + BUS_TRANSPORT_REMOTE, + BUS_TRANSPORT_MACHINE, + _BUS_TRANSPORT_MAX, + _BUS_TRANSPORT_INVALID = -1 +} BusTransport; + +typedef int (*bus_property_set_t) (sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata); + +struct bus_properties_map { + const char *member; + const char *signature; + bus_property_set_t set; + size_t offset; +}; + +int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata); + +int bus_message_map_all_properties(sd_bus_message *m, const struct bus_properties_map *map, void *userdata); +int bus_message_map_properties_changed(sd_bus_message *m, const struct bus_properties_map *map, void *userdata); +int bus_map_all_properties(sd_bus *bus, const char *destination, const char *path, const struct bus_properties_map *map, void *userdata); + +int bus_async_unregister_and_exit(sd_event *e, sd_bus *bus, const char *name); + +typedef bool (*check_idle_t)(void *userdata); + +int bus_event_loop_with_idle(sd_event *e, sd_bus *bus, const char *name, usec_t timeout, check_idle_t check_idle, void *userdata); + +int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error); + +int bus_check_peercred(sd_bus *c); + +int bus_test_polkit(sd_bus_message *call, int capability, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e); + +int bus_verify_polkit_async(sd_bus_message *call, int capability, const char *action, const char **details, bool interactive, uid_t good_user, Hashmap **registry, sd_bus_error *error); +void bus_verify_polkit_async_registry_free(Hashmap *registry); + +int bus_connect_system_systemd(sd_bus **_bus); +int bus_connect_user_systemd(sd_bus **_bus); + +int bus_connect_transport(BusTransport transport, const char *host, bool user, sd_bus **bus); +int bus_connect_transport_systemd(BusTransport transport, const char *host, bool user, sd_bus **bus); + +int bus_print_property(const char *name, sd_bus_message *property, bool all); +int bus_print_all_properties(sd_bus *bus, const char *dest, const char *path, char **filter, bool all); + +int bus_property_get_bool(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); + +#define bus_property_get_usec ((sd_bus_property_get_t) NULL) +#define bus_property_set_usec ((sd_bus_property_set_t) NULL) + +assert_cc(sizeof(int) == sizeof(int32_t)); +#define bus_property_get_int ((sd_bus_property_get_t) NULL) + +assert_cc(sizeof(unsigned) == sizeof(unsigned)); +#define bus_property_get_unsigned ((sd_bus_property_get_t) NULL) + +/* On 64bit machines we can use the default serializer for size_t and + * friends, otherwise we need to cast this manually */ +#if __SIZEOF_SIZE_T__ == 8 +#define bus_property_get_size ((sd_bus_property_get_t) NULL) +#else +int bus_property_get_size(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); +#endif + +#if __SIZEOF_LONG__ == 8 +#define bus_property_get_long ((sd_bus_property_get_t) NULL) +#define bus_property_get_ulong ((sd_bus_property_get_t) NULL) +#else +int bus_property_get_long(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); +int bus_property_get_ulong(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); +#endif + +/* uid_t and friends on Linux 32 bit. This means we can just use the + * default serializer for 32bit unsigned, for serializing it, and map + * it to NULL here */ +assert_cc(sizeof(uid_t) == sizeof(uint32_t)); +#define bus_property_get_uid ((sd_bus_property_get_t) NULL) + +assert_cc(sizeof(gid_t) == sizeof(uint32_t)); +#define bus_property_get_gid ((sd_bus_property_get_t) NULL) + +assert_cc(sizeof(pid_t) == sizeof(uint32_t)); +#define bus_property_get_pid ((sd_bus_property_get_t) NULL) + +assert_cc(sizeof(mode_t) == sizeof(uint32_t)); +#define bus_property_get_mode ((sd_bus_property_get_t) NULL) + +int bus_log_parse_error(int r); +int bus_log_create_error(int r); + +typedef struct UnitInfo { + const char *machine; + const char *id; + const char *description; + const char *load_state; + const char *active_state; + const char *sub_state; + const char *following; + const char *unit_path; + uint32_t job_id; + const char *job_type; + const char *job_path; +} UnitInfo; + +int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u); + +#define BUS_DEFINE_PROPERTY_GET_ENUM(function, name, type) \ + int function(sd_bus *bus, \ + const char *path, \ + const char *interface, \ + const char *property, \ + sd_bus_message *reply, \ + void *userdata, \ + sd_bus_error *error) { \ + \ + const char *value; \ + type *field = userdata; \ + int r; \ + \ + assert(bus); \ + assert(reply); \ + assert(field); \ + \ + value = strempty(name##_to_string(*field)); \ + \ + r = sd_bus_message_append_basic(reply, 's', value); \ + if (r < 0) \ + return r; \ + \ + return 1; \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +#define BUS_PROPERTY_DUAL_TIMESTAMP(name, offset, flags) \ + SD_BUS_PROPERTY(name, "t", bus_property_get_usec, (offset) + offsetof(struct dual_timestamp, realtime), (flags)), \ + SD_BUS_PROPERTY(name "Monotonic", "t", bus_property_get_usec, (offset) + offsetof(struct dual_timestamp, monotonic), (flags)) + +int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment); + +typedef struct BusWaitForJobs BusWaitForJobs; + +int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret); +void bus_wait_for_jobs_free(BusWaitForJobs *d); +int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path); +int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char *extra_args); +int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet); + +DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForJobs*, bus_wait_for_jobs_free); + +int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes); + +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/libshared/cgroup-show.c b/src/libshared/cgroup-show.c new file mode 100644 index 0000000000..f3039b23f7 --- /dev/null +++ b/src/libshared/cgroup-show.c @@ -0,0 +1,278 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "cgroup-show.h" +#include "cgroup-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "locale-util.h" +#include "macro.h" +#include "output-mode.h" +#include "path-util.h" +#include "process-util.h" +#include "string-util.h" +#include "terminal-util.h" + +static int compare(const void *a, const void *b) { + const pid_t *p = a, *q = b; + + if (*p < *q) + return -1; + if (*p > *q) + return 1; + return 0; +} + +static void show_pid_array(pid_t pids[], unsigned n_pids, const char *prefix, unsigned n_columns, bool extra, bool more, bool kernel_threads, OutputFlags flags) { + unsigned i, j, pid_width; + + if (n_pids == 0) + return; + + qsort(pids, n_pids, sizeof(pid_t), compare); + + /* Filter duplicates */ + for (j = 0, i = 1; i < n_pids; i++) { + if (pids[i] == pids[j]) + continue; + pids[++j] = pids[i]; + } + n_pids = j + 1; + pid_width = DECIMAL_STR_WIDTH(pids[j]); + + if (flags & OUTPUT_FULL_WIDTH) + n_columns = 0; + else { + if (n_columns > pid_width+2) + n_columns -= pid_width+2; + else + n_columns = 20; + } + for (i = 0; i < n_pids; i++) { + _cleanup_free_ char *t = NULL; + + get_process_cmdline(pids[i], n_columns, true, &t); + + if (extra) + printf("%s%s ", prefix, draw_special_char(DRAW_TRIANGULAR_BULLET)); + else + printf("%s%s", prefix, draw_special_char(((more || i < n_pids-1) ? DRAW_TREE_BRANCH : DRAW_TREE_RIGHT))); + + printf("%*"PID_PRI" %s\n", pid_width, pids[i], strna(t)); + } +} + + +static int show_cgroup_one_by_path(const char *path, const char *prefix, unsigned n_columns, bool more, bool kernel_threads, OutputFlags flags) { + char *fn; + _cleanup_fclose_ FILE *f = NULL; + size_t n = 0, n_allocated = 0; + _cleanup_free_ pid_t *pids = NULL; + _cleanup_free_ char *p = NULL; + pid_t pid; + int r; + + r = cg_mangle_path(path, &p); + if (r < 0) + return r; + + fn = strjoina(p, "/cgroup.procs"); + f = fopen(fn, "re"); + if (!f) + return -errno; + + while ((r = cg_read_pid(f, &pid)) > 0) { + + if (!kernel_threads && is_kernel_thread(pid) > 0) + continue; + + if (!GREEDY_REALLOC(pids, n_allocated, n + 1)) + return -ENOMEM; + + assert(n < n_allocated); + pids[n++] = pid; + } + + if (r < 0) + return r; + + show_pid_array(pids, n, prefix, n_columns, false, more, kernel_threads, flags); + + return 0; +} + +int show_cgroup_by_path(const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, OutputFlags flags) { + _cleanup_free_ char *fn = NULL, *p1 = NULL, *last = NULL, *p2 = NULL; + _cleanup_closedir_ DIR *d = NULL; + char *gn = NULL; + bool shown_pids = false; + int r; + + assert(path); + + if (n_columns <= 0) + n_columns = columns(); + + if (!prefix) + prefix = ""; + + r = cg_mangle_path(path, &fn); + if (r < 0) + return r; + + d = opendir(fn); + if (!d) + return -errno; + + while ((r = cg_read_subgroup(d, &gn)) > 0) { + _cleanup_free_ char *k = NULL; + + k = strjoin(fn, "/", gn, NULL); + free(gn); + if (!k) + return -ENOMEM; + + if (!(flags & OUTPUT_SHOW_ALL) && cg_is_empty_recursive(NULL, k) > 0) + continue; + + if (!shown_pids) { + show_cgroup_one_by_path(path, prefix, n_columns, true, kernel_threads, flags); + shown_pids = true; + } + + if (last) { + printf("%s%s%s\n", prefix, draw_special_char(DRAW_TREE_BRANCH), cg_unescape(basename(last))); + + if (!p1) { + p1 = strappend(prefix, draw_special_char(DRAW_TREE_VERTICAL)); + if (!p1) + return -ENOMEM; + } + + show_cgroup_by_path(last, p1, n_columns-2, kernel_threads, flags); + free(last); + } + + last = k; + k = NULL; + } + + if (r < 0) + return r; + + if (!shown_pids) + show_cgroup_one_by_path(path, prefix, n_columns, !!last, kernel_threads, flags); + + if (last) { + printf("%s%s%s\n", prefix, draw_special_char(DRAW_TREE_RIGHT), cg_unescape(basename(last))); + + if (!p2) { + p2 = strappend(prefix, " "); + if (!p2) + return -ENOMEM; + } + + show_cgroup_by_path(last, p2, n_columns-2, kernel_threads, flags); + } + + return 0; +} + +int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, OutputFlags flags) { + _cleanup_free_ char *p = NULL; + int r; + + assert(path); + + r = cg_get_path(controller, path, NULL, &p); + if (r < 0) + return r; + + return show_cgroup_by_path(p, prefix, n_columns, kernel_threads, flags); +} + +static int show_extra_pids(const char *controller, const char *path, const char *prefix, unsigned n_columns, const pid_t pids[], unsigned n_pids, OutputFlags flags) { + _cleanup_free_ pid_t *copy = NULL; + unsigned i, j; + int r; + + assert(path); + + if (n_pids <= 0) + return 0; + + if (n_columns <= 0) + n_columns = columns(); + + prefix = strempty(prefix); + + copy = new(pid_t, n_pids); + if (!copy) + return -ENOMEM; + + for (i = 0, j = 0; i < n_pids; i++) { + _cleanup_free_ char *k = NULL; + + r = cg_pid_get_path(controller, pids[i], &k); + if (r < 0) + return r; + + if (path_startswith(k, path)) + continue; + + copy[j++] = pids[i]; + } + + show_pid_array(copy, j, prefix, n_columns, true, false, false, flags); + + return 0; +} + +int show_cgroup_and_extra(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags) { + int r; + + assert(path); + + r = show_cgroup(controller, path, prefix, n_columns, kernel_threads, flags); + if (r < 0) + return r; + + return show_extra_pids(controller, path, prefix, n_columns, extra_pids, n_extra_pids, flags); +} + +int show_cgroup_and_extra_by_spec(const char *spec, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags) { + _cleanup_free_ char *controller = NULL, *path = NULL; + int r; + + assert(spec); + + r = cg_split_spec(spec, &controller, &path); + if (r < 0) + return r; + + return show_cgroup_and_extra(controller, path, prefix, n_columns, kernel_threads, extra_pids, n_extra_pids, flags); +} diff --git a/src/libshared/cgroup-show.h b/src/libshared/cgroup-show.h new file mode 100644 index 0000000000..3ab7dfb33c --- /dev/null +++ b/src/libshared/cgroup-show.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "logs-show.h" +#include "output-mode.h" + +int show_cgroup_by_path(const char *path, const char *prefix, unsigned columns, bool kernel_threads, OutputFlags flags); +int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned columns, bool kernel_threads, OutputFlags flags); + +int show_cgroup_and_extra_by_spec(const char *spec, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags); +int show_cgroup_and_extra(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags); diff --git a/src/libshared/clean-ipc.c b/src/libshared/clean-ipc.c new file mode 100644 index 0000000000..a3ac7aeb82 --- /dev/null +++ b/src/libshared/clean-ipc.c @@ -0,0 +1,365 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clean-ipc.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "strv.h" + +static int clean_sysvipc_shm(uid_t delete_uid) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + bool first = true; + int ret = 0; + + f = fopen("/proc/sysvipc/shm", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + return log_warning_errno(errno, "Failed to open /proc/sysvipc/shm: %m"); + } + + FOREACH_LINE(line, f, goto fail) { + unsigned n_attached; + pid_t cpid, lpid; + uid_t uid, cuid; + gid_t gid, cgid; + int shmid; + + if (first) { + first = false; + continue; + } + + truncate_nl(line); + + if (sscanf(line, "%*i %i %*o %*u " PID_FMT " " PID_FMT " %u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, + &shmid, &cpid, &lpid, &n_attached, &uid, &gid, &cuid, &cgid) != 8) + continue; + + if (n_attached > 0) + continue; + + if (uid != delete_uid) + continue; + + if (shmctl(shmid, IPC_RMID, NULL) < 0) { + + /* Ignore entries that are already deleted */ + if (errno == EIDRM || errno == EINVAL) + continue; + + ret = log_warning_errno(errno, + "Failed to remove SysV shared memory segment %i: %m", + shmid); + } + } + + return ret; + +fail: + return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m"); +} + +static int clean_sysvipc_sem(uid_t delete_uid) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + bool first = true; + int ret = 0; + + f = fopen("/proc/sysvipc/sem", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + return log_warning_errno(errno, "Failed to open /proc/sysvipc/sem: %m"); + } + + FOREACH_LINE(line, f, goto fail) { + uid_t uid, cuid; + gid_t gid, cgid; + int semid; + + if (first) { + first = false; + continue; + } + + truncate_nl(line); + + if (sscanf(line, "%*i %i %*o %*u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, + &semid, &uid, &gid, &cuid, &cgid) != 5) + continue; + + if (uid != delete_uid) + continue; + + if (semctl(semid, 0, IPC_RMID) < 0) { + + /* Ignore entries that are already deleted */ + if (errno == EIDRM || errno == EINVAL) + continue; + + ret = log_warning_errno(errno, + "Failed to remove SysV semaphores object %i: %m", + semid); + } + } + + return ret; + +fail: + return log_warning_errno(errno, "Failed to read /proc/sysvipc/sem: %m"); +} + +static int clean_sysvipc_msg(uid_t delete_uid) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + bool first = true; + int ret = 0; + + f = fopen("/proc/sysvipc/msg", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + return log_warning_errno(errno, "Failed to open /proc/sysvipc/msg: %m"); + } + + FOREACH_LINE(line, f, goto fail) { + uid_t uid, cuid; + gid_t gid, cgid; + pid_t cpid, lpid; + int msgid; + + if (first) { + first = false; + continue; + } + + truncate_nl(line); + + if (sscanf(line, "%*i %i %*o %*u %*u " PID_FMT " " PID_FMT " " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, + &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7) + continue; + + if (uid != delete_uid) + continue; + + if (msgctl(msgid, IPC_RMID, NULL) < 0) { + + /* Ignore entries that are already deleted */ + if (errno == EIDRM || errno == EINVAL) + continue; + + ret = log_warning_errno(errno, + "Failed to remove SysV message queue %i: %m", + msgid); + } + } + + return ret; + +fail: + return log_warning_errno(errno, "Failed to read /proc/sysvipc/msg: %m"); +} + +static int clean_posix_shm_internal(DIR *dir, uid_t uid) { + struct dirent *de; + int ret = 0, r; + + assert(dir); + + FOREACH_DIRENT(de, dir, goto fail) { + struct stat st; + + if (STR_IN_SET(de->d_name, "..", ".")) + continue; + + if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno == ENOENT) + continue; + + log_warning_errno(errno, "Failed to stat() POSIX shared memory segment %s: %m", de->d_name); + ret = -errno; + continue; + } + + if (st.st_uid != uid) + continue; + + if (S_ISDIR(st.st_mode)) { + _cleanup_closedir_ DIR *kid; + + kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME); + if (!kid) { + if (errno != ENOENT) { + log_warning_errno(errno, "Failed to enter shared memory directory %s: %m", de->d_name); + ret = -errno; + } + } else { + r = clean_posix_shm_internal(kid, uid); + if (r < 0) + ret = r; + } + + if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0) { + + if (errno == ENOENT) + continue; + + log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s: %m", de->d_name); + ret = -errno; + } + } else { + + if (unlinkat(dirfd(dir), de->d_name, 0) < 0) { + + if (errno == ENOENT) + continue; + + log_warning_errno(errno, "Failed to remove POSIX shared memory segment %s: %m", de->d_name); + ret = -errno; + } + } + } + + return ret; + +fail: + log_warning_errno(errno, "Failed to read /dev/shm: %m"); + return -errno; +} + +static int clean_posix_shm(uid_t uid) { + _cleanup_closedir_ DIR *dir = NULL; + + dir = opendir("/dev/shm"); + if (!dir) { + if (errno == ENOENT) + return 0; + + return log_warning_errno(errno, "Failed to open /dev/shm: %m"); + } + + return clean_posix_shm_internal(dir, uid); +} + +static int clean_posix_mq(uid_t uid) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *de; + int ret = 0; + + dir = opendir("/dev/mqueue"); + if (!dir) { + if (errno == ENOENT) + return 0; + + return log_warning_errno(errno, "Failed to open /dev/mqueue: %m"); + } + + FOREACH_DIRENT(de, dir, goto fail) { + struct stat st; + char fn[1+strlen(de->d_name)+1]; + + if (STR_IN_SET(de->d_name, "..", ".")) + continue; + + if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno == ENOENT) + continue; + + ret = log_warning_errno(errno, + "Failed to stat() MQ segment %s: %m", + de->d_name); + continue; + } + + if (st.st_uid != uid) + continue; + + fn[0] = '/'; + strcpy(fn+1, de->d_name); + + if (mq_unlink(fn) < 0) { + if (errno == ENOENT) + continue; + + ret = log_warning_errno(errno, + "Failed to unlink POSIX message queue %s: %m", + fn); + } + } + + return ret; + +fail: + return log_warning_errno(errno, "Failed to read /dev/mqueue: %m"); +} + +int clean_ipc(uid_t uid) { + int ret = 0, r; + + /* Refuse to clean IPC of the root and system users */ + if (uid <= SYSTEM_UID_MAX) + return 0; + + r = clean_sysvipc_shm(uid); + if (r < 0) + ret = r; + + r = clean_sysvipc_sem(uid); + if (r < 0) + ret = r; + + r = clean_sysvipc_msg(uid); + if (r < 0) + ret = r; + + r = clean_posix_shm(uid); + if (r < 0) + ret = r; + + r = clean_posix_mq(uid); + if (r < 0) + ret = r; + + return ret; +} diff --git a/src/libshared/clean-ipc.h b/src/libshared/clean-ipc.h new file mode 100644 index 0000000000..44a83afcf7 --- /dev/null +++ b/src/libshared/clean-ipc.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +int clean_ipc(uid_t uid); diff --git a/src/libshared/condition.c b/src/libshared/condition.c new file mode 100644 index 0000000000..1719a4c7a6 --- /dev/null +++ b/src/libshared/condition.c @@ -0,0 +1,541 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "apparmor-util.h" +#include "architecture.h" +#include "audit-util.h" +#include "cap-list.h" +#include "condition.h" +#include "extract-word.h" +#include "fd-util.h" +#include "glob-util.h" +#include "hostname-util.h" +#include "ima-util.h" +#include "list.h" +#include "macro.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "selinux-util.h" +#include "smack-util.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "util.h" +#include "virt.h" + +Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) { + Condition *c; + int r; + + assert(type >= 0); + assert(type < _CONDITION_TYPE_MAX); + assert((!parameter) == (type == CONDITION_NULL)); + + c = new0(Condition, 1); + if (!c) + return NULL; + + c->type = type; + c->trigger = trigger; + c->negate = negate; + + r = free_and_strdup(&c->parameter, parameter); + if (r < 0) { + free(c); + return NULL; + } + + return c; +} + +void condition_free(Condition *c) { + assert(c); + + free(c->parameter); + free(c); +} + +Condition* condition_free_list(Condition *first) { + Condition *c, *n; + + LIST_FOREACH_SAFE(conditions, c, n, first) + condition_free(c); + + return NULL; +} + +static int condition_test_kernel_command_line(Condition *c) { + _cleanup_free_ char *line = NULL; + const char *p; + bool equal; + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_KERNEL_COMMAND_LINE); + + r = proc_cmdline(&line); + if (r < 0) + return r; + + equal = !!strchr(c->parameter, '='); + p = line; + + for (;;) { + _cleanup_free_ char *word = NULL; + bool found; + + r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); + if (r < 0) + return r; + if (r == 0) + break; + + if (equal) + found = streq(word, c->parameter); + else { + const char *f; + + f = startswith(word, c->parameter); + found = f && (*f == '=' || *f == 0); + } + + if (found) + return true; + } + + return false; +} + +static int condition_test_virtualization(Condition *c) { + int b, v; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_VIRTUALIZATION); + + v = detect_virtualization(); + if (v < 0) + return v; + + /* First, compare with yes/no */ + b = parse_boolean(c->parameter); + + if (v > 0 && b > 0) + return true; + + if (v == 0 && b == 0) + return true; + + /* Then, compare categorization */ + if (VIRTUALIZATION_IS_VM(v) && streq(c->parameter, "vm")) + return true; + + if (VIRTUALIZATION_IS_CONTAINER(v) && streq(c->parameter, "container")) + return true; + + /* Finally compare id */ + return v != VIRTUALIZATION_NONE && streq(c->parameter, virtualization_to_string(v)); +} + +static int condition_test_architecture(Condition *c) { + int a, b; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_ARCHITECTURE); + + a = uname_architecture(); + if (a < 0) + return a; + + if (streq(c->parameter, "native")) + b = native_architecture(); + else + b = architecture_from_string(c->parameter); + if (b < 0) + return b; + + return a == b; +} + +static int condition_test_host(Condition *c) { + _cleanup_free_ char *h = NULL; + sd_id128_t x, y; + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_HOST); + + if (sd_id128_from_string(c->parameter, &x) >= 0) { + + r = sd_id128_get_machine(&y); + if (r < 0) + return r; + + return sd_id128_equal(x, y); + } + + h = gethostname_malloc(); + if (!h) + return -ENOMEM; + + return fnmatch(c->parameter, h, FNM_CASEFOLD) == 0; +} + +static int condition_test_ac_power(Condition *c) { + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_AC_POWER); + + r = parse_boolean(c->parameter); + if (r < 0) + return r; + + return (on_ac_power() != 0) == !!r; +} + +static int condition_test_security(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_SECURITY); + + if (streq(c->parameter, "selinux")) + return mac_selinux_have(); + if (streq(c->parameter, "smack")) + return mac_smack_use(); + if (streq(c->parameter, "apparmor")) + return mac_apparmor_use(); + if (streq(c->parameter, "audit")) + return use_audit(); + if (streq(c->parameter, "ima")) + return use_ima(); + + return false; +} + +static int condition_test_capability(Condition *c) { + _cleanup_fclose_ FILE *f = NULL; + int value; + char line[LINE_MAX]; + unsigned long long capabilities = -1; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_CAPABILITY); + + /* If it's an invalid capability, we don't have it */ + value = capability_from_name(c->parameter); + if (value < 0) + return -EINVAL; + + /* If it's a valid capability we default to assume + * that we have it */ + + f = fopen("/proc/self/status", "re"); + if (!f) + return -errno; + + while (fgets(line, sizeof(line), f)) { + truncate_nl(line); + + if (startswith(line, "CapBnd:")) { + (void) sscanf(line+7, "%llx", &capabilities); + break; + } + } + + return !!(capabilities & (1ULL << value)); +} + +static int condition_test_needs_update(Condition *c) { + const char *p; + struct stat usr, other; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_NEEDS_UPDATE); + + /* If the file system is read-only we shouldn't suggest an update */ + if (path_is_read_only_fs(c->parameter) > 0) + return false; + + /* Any other failure means we should allow the condition to be true, + * so that we rather invoke too many update tools then too + * few. */ + + if (!path_is_absolute(c->parameter)) + return true; + + p = strjoina(c->parameter, "/.updated"); + if (lstat(p, &other) < 0) + return true; + + if (lstat("/usr/", &usr) < 0) + return true; + + return usr.st_mtim.tv_sec > other.st_mtim.tv_sec || + (usr.st_mtim.tv_sec == other.st_mtim.tv_sec && usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec); +} + +static int condition_test_first_boot(Condition *c) { + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_FIRST_BOOT); + + r = parse_boolean(c->parameter); + if (r < 0) + return r; + + return (access("/run/systemd/first-boot", F_OK) >= 0) == !!r; +} + +static int condition_test_path_exists(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_PATH_EXISTS); + + return access(c->parameter, F_OK) >= 0; +} + +static int condition_test_path_exists_glob(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_PATH_EXISTS_GLOB); + + return glob_exists(c->parameter) > 0; +} + +static int condition_test_path_is_directory(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_PATH_IS_DIRECTORY); + + return is_dir(c->parameter, true) > 0; +} + +static int condition_test_path_is_symbolic_link(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_PATH_IS_SYMBOLIC_LINK); + + return is_symlink(c->parameter) > 0; +} + +static int condition_test_path_is_mount_point(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_PATH_IS_MOUNT_POINT); + + return path_is_mount_point(c->parameter, AT_SYMLINK_FOLLOW) > 0; +} + +static int condition_test_path_is_read_write(Condition *c) { + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_PATH_IS_READ_WRITE); + + return path_is_read_only_fs(c->parameter) <= 0; +} + +static int condition_test_directory_not_empty(Condition *c) { + int r; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_DIRECTORY_NOT_EMPTY); + + r = dir_is_empty(c->parameter); + return r <= 0 && r != -ENOENT; +} + +static int condition_test_file_not_empty(Condition *c) { + struct stat st; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_FILE_NOT_EMPTY); + + return (stat(c->parameter, &st) >= 0 && + S_ISREG(st.st_mode) && + st.st_size > 0); +} + +static int condition_test_file_is_executable(Condition *c) { + struct stat st; + + assert(c); + assert(c->parameter); + assert(c->type == CONDITION_FILE_IS_EXECUTABLE); + + return (stat(c->parameter, &st) >= 0 && + S_ISREG(st.st_mode) && + (st.st_mode & 0111)); +} + +static int condition_test_null(Condition *c) { + assert(c); + assert(c->type == CONDITION_NULL); + + /* Note that during parsing we already evaluate the string and + * store it in c->negate */ + return true; +} + +int condition_test(Condition *c) { + + static int (*const condition_tests[_CONDITION_TYPE_MAX])(Condition *c) = { + [CONDITION_PATH_EXISTS] = condition_test_path_exists, + [CONDITION_PATH_EXISTS_GLOB] = condition_test_path_exists_glob, + [CONDITION_PATH_IS_DIRECTORY] = condition_test_path_is_directory, + [CONDITION_PATH_IS_SYMBOLIC_LINK] = condition_test_path_is_symbolic_link, + [CONDITION_PATH_IS_MOUNT_POINT] = condition_test_path_is_mount_point, + [CONDITION_PATH_IS_READ_WRITE] = condition_test_path_is_read_write, + [CONDITION_DIRECTORY_NOT_EMPTY] = condition_test_directory_not_empty, + [CONDITION_FILE_NOT_EMPTY] = condition_test_file_not_empty, + [CONDITION_FILE_IS_EXECUTABLE] = condition_test_file_is_executable, + [CONDITION_KERNEL_COMMAND_LINE] = condition_test_kernel_command_line, + [CONDITION_VIRTUALIZATION] = condition_test_virtualization, + [CONDITION_SECURITY] = condition_test_security, + [CONDITION_CAPABILITY] = condition_test_capability, + [CONDITION_HOST] = condition_test_host, + [CONDITION_AC_POWER] = condition_test_ac_power, + [CONDITION_ARCHITECTURE] = condition_test_architecture, + [CONDITION_NEEDS_UPDATE] = condition_test_needs_update, + [CONDITION_FIRST_BOOT] = condition_test_first_boot, + [CONDITION_NULL] = condition_test_null, + }; + + int r, b; + + assert(c); + assert(c->type >= 0); + assert(c->type < _CONDITION_TYPE_MAX); + + r = condition_tests[c->type](c); + if (r < 0) { + c->result = CONDITION_ERROR; + return r; + } + + b = (r > 0) == !c->negate; + c->result = b ? CONDITION_SUCCEEDED : CONDITION_FAILED; + return b; +} + +void condition_dump(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)) { + assert(c); + assert(f); + + if (!prefix) + prefix = ""; + + fprintf(f, + "%s\t%s: %s%s%s %s\n", + prefix, + to_string(c->type), + c->trigger ? "|" : "", + c->negate ? "!" : "", + c->parameter, + condition_result_to_string(c->result)); +} + +void condition_dump_list(Condition *first, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)) { + Condition *c; + + LIST_FOREACH(conditions, c, first) + condition_dump(c, f, prefix, to_string); +} + +static const char* const condition_type_table[_CONDITION_TYPE_MAX] = { + [CONDITION_ARCHITECTURE] = "ConditionArchitecture", + [CONDITION_VIRTUALIZATION] = "ConditionVirtualization", + [CONDITION_HOST] = "ConditionHost", + [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine", + [CONDITION_SECURITY] = "ConditionSecurity", + [CONDITION_CAPABILITY] = "ConditionCapability", + [CONDITION_AC_POWER] = "ConditionACPower", + [CONDITION_NEEDS_UPDATE] = "ConditionNeedsUpdate", + [CONDITION_FIRST_BOOT] = "ConditionFirstBoot", + [CONDITION_PATH_EXISTS] = "ConditionPathExists", + [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob", + [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory", + [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink", + [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint", + [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite", + [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty", + [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty", + [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable", + [CONDITION_NULL] = "ConditionNull" +}; + +DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType); + +static const char* const assert_type_table[_CONDITION_TYPE_MAX] = { + [CONDITION_ARCHITECTURE] = "AssertArchitecture", + [CONDITION_VIRTUALIZATION] = "AssertVirtualization", + [CONDITION_HOST] = "AssertHost", + [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine", + [CONDITION_SECURITY] = "AssertSecurity", + [CONDITION_CAPABILITY] = "AssertCapability", + [CONDITION_AC_POWER] = "AssertACPower", + [CONDITION_NEEDS_UPDATE] = "AssertNeedsUpdate", + [CONDITION_FIRST_BOOT] = "AssertFirstBoot", + [CONDITION_PATH_EXISTS] = "AssertPathExists", + [CONDITION_PATH_EXISTS_GLOB] = "AssertPathExistsGlob", + [CONDITION_PATH_IS_DIRECTORY] = "AssertPathIsDirectory", + [CONDITION_PATH_IS_SYMBOLIC_LINK] = "AssertPathIsSymbolicLink", + [CONDITION_PATH_IS_MOUNT_POINT] = "AssertPathIsMountPoint", + [CONDITION_PATH_IS_READ_WRITE] = "AssertPathIsReadWrite", + [CONDITION_DIRECTORY_NOT_EMPTY] = "AssertDirectoryNotEmpty", + [CONDITION_FILE_NOT_EMPTY] = "AssertFileNotEmpty", + [CONDITION_FILE_IS_EXECUTABLE] = "AssertFileIsExecutable", + [CONDITION_NULL] = "AssertNull" +}; + +DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType); + +static const char* const condition_result_table[_CONDITION_RESULT_MAX] = { + [CONDITION_UNTESTED] = "untested", + [CONDITION_SUCCEEDED] = "succeeded", + [CONDITION_FAILED] = "failed", + [CONDITION_ERROR] = "error", +}; + +DEFINE_STRING_TABLE_LOOKUP(condition_result, ConditionResult); diff --git a/src/libshared/condition.h b/src/libshared/condition.h new file mode 100644 index 0000000000..bdda04b770 --- /dev/null +++ b/src/libshared/condition.h @@ -0,0 +1,94 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "list.h" +#include "macro.h" + +typedef enum ConditionType { + CONDITION_ARCHITECTURE, + CONDITION_VIRTUALIZATION, + CONDITION_HOST, + CONDITION_KERNEL_COMMAND_LINE, + CONDITION_SECURITY, + CONDITION_CAPABILITY, + CONDITION_AC_POWER, + + CONDITION_NEEDS_UPDATE, + CONDITION_FIRST_BOOT, + + CONDITION_PATH_EXISTS, + CONDITION_PATH_EXISTS_GLOB, + CONDITION_PATH_IS_DIRECTORY, + CONDITION_PATH_IS_SYMBOLIC_LINK, + CONDITION_PATH_IS_MOUNT_POINT, + CONDITION_PATH_IS_READ_WRITE, + CONDITION_DIRECTORY_NOT_EMPTY, + CONDITION_FILE_NOT_EMPTY, + CONDITION_FILE_IS_EXECUTABLE, + + CONDITION_NULL, + + _CONDITION_TYPE_MAX, + _CONDITION_TYPE_INVALID = -1 +} ConditionType; + +typedef enum ConditionResult { + CONDITION_UNTESTED, + CONDITION_SUCCEEDED, + CONDITION_FAILED, + CONDITION_ERROR, + _CONDITION_RESULT_MAX, + _CONDITION_RESULT_INVALID = -1 +} ConditionResult; + +typedef struct Condition { + ConditionType type:8; + + bool trigger:1; + bool negate:1; + + ConditionResult result:6; + + char *parameter; + + LIST_FIELDS(struct Condition, conditions); +} Condition; + +Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate); +void condition_free(Condition *c); +Condition* condition_free_list(Condition *c); + +int condition_test(Condition *c); + +void condition_dump(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)); +void condition_dump_list(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)); + +const char* condition_type_to_string(ConditionType t) _const_; +ConditionType condition_type_from_string(const char *s) _pure_; + +const char* assert_type_to_string(ConditionType t) _const_; +ConditionType assert_type_from_string(const char *s) _pure_; + +const char* condition_result_to_string(ConditionResult r) _const_; +ConditionResult condition_result_from_string(const char *s) _pure_; diff --git a/src/libshared/conf-parser.c b/src/libshared/conf-parser.c new file mode 100644 index 0000000000..e7fe9ac21e --- /dev/null +++ b/src/libshared/conf-parser.c @@ -0,0 +1,870 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "conf-files.h" +#include "conf-parser.h" +#include "extract-word.h" +#include "fd-util.h" +#include "fs-util.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "strv.h" +#include "syslog-util.h" +#include "time-util.h" +#include "utf8.h" + +int config_item_table_lookup( + const void *table, + const char *section, + const char *lvalue, + ConfigParserCallback *func, + int *ltype, + void **data, + void *userdata) { + + const ConfigTableItem *t; + + assert(table); + assert(lvalue); + assert(func); + assert(ltype); + assert(data); + + for (t = table; t->lvalue; t++) { + + if (!streq(lvalue, t->lvalue)) + continue; + + if (!streq_ptr(section, t->section)) + continue; + + *func = t->parse; + *ltype = t->ltype; + *data = t->data; + return 1; + } + + return 0; +} + +int config_item_perf_lookup( + const void *table, + const char *section, + const char *lvalue, + ConfigParserCallback *func, + int *ltype, + void **data, + void *userdata) { + + ConfigPerfItemLookup lookup = (ConfigPerfItemLookup) table; + const ConfigPerfItem *p; + + assert(table); + assert(lvalue); + assert(func); + assert(ltype); + assert(data); + + if (!section) + p = lookup(lvalue, strlen(lvalue)); + else { + char *key; + + key = strjoin(section, ".", lvalue, NULL); + if (!key) + return -ENOMEM; + + p = lookup(key, strlen(key)); + free(key); + } + + if (!p) + return 0; + + *func = p->parse; + *ltype = p->ltype; + *data = (uint8_t*) userdata + p->offset; + return 1; +} + +/* Run the user supplied parser for an assignment */ +static int next_assignment(const char *unit, + const char *filename, + unsigned line, + ConfigItemLookup lookup, + const void *table, + const char *section, + unsigned section_line, + const char *lvalue, + const char *rvalue, + bool relaxed, + void *userdata) { + + ConfigParserCallback func = NULL; + int ltype = 0; + void *data = NULL; + int r; + + assert(filename); + assert(line > 0); + assert(lookup); + assert(lvalue); + assert(rvalue); + + r = lookup(table, section, lvalue, &func, <ype, &data, userdata); + if (r < 0) + return r; + + if (r > 0) { + if (func) + return func(unit, filename, line, section, section_line, + lvalue, ltype, rvalue, data, userdata); + + return 0; + } + + /* Warn about unknown non-extension fields. */ + if (!relaxed && !startswith(lvalue, "X-")) + log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown lvalue '%s' in section '%s'", lvalue, section); + + return 0; +} + +/* Parse a variable assignment line */ +static int parse_line(const char* unit, + const char *filename, + unsigned line, + const char *sections, + ConfigItemLookup lookup, + const void *table, + bool relaxed, + bool allow_include, + char **section, + unsigned *section_line, + bool *section_ignored, + char *l, + void *userdata) { + + char *e; + + assert(filename); + assert(line > 0); + assert(lookup); + assert(l); + + l = strstrip(l); + + if (!*l) + return 0; + + if (strchr(COMMENTS "\n", *l)) + return 0; + + if (startswith(l, ".include ")) { + _cleanup_free_ char *fn = NULL; + + /* .includes are a bad idea, we only support them here + * for historical reasons. They create cyclic include + * problems and make it difficult to detect + * configuration file changes with an easy + * stat(). Better approaches, such as .d/ drop-in + * snippets exist. + * + * Support for them should be eventually removed. */ + + if (!allow_include) { + log_syntax(unit, LOG_ERR, filename, line, 0, ".include not allowed here. Ignoring."); + return 0; + } + + fn = file_in_same_dir(filename, strstrip(l+9)); + if (!fn) + return -ENOMEM; + + return config_parse(unit, fn, NULL, sections, lookup, table, relaxed, false, false, userdata); + } + + if (*l == '[') { + size_t k; + char *n; + + k = strlen(l); + assert(k > 0); + + if (l[k-1] != ']') { + log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid section header '%s'", l); + return -EBADMSG; + } + + n = strndup(l+1, k-2); + if (!n) + return -ENOMEM; + + if (sections && !nulstr_contains(sections, n)) { + + if (!relaxed && !startswith(n, "X-")) + log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown section '%s'. Ignoring.", n); + + free(n); + *section = mfree(*section); + *section_line = 0; + *section_ignored = true; + } else { + free(*section); + *section = n; + *section_line = line; + *section_ignored = false; + } + + return 0; + } + + if (sections && !*section) { + + if (!relaxed && !*section_ignored) + log_syntax(unit, LOG_WARNING, filename, line, 0, "Assignment outside of section. Ignoring."); + + return 0; + } + + e = strchr(l, '='); + if (!e) { + log_syntax(unit, LOG_WARNING, filename, line, 0, "Missing '='."); + return -EINVAL; + } + + *e = 0; + e++; + + return next_assignment(unit, + filename, + line, + lookup, + table, + *section, + *section_line, + strstrip(l), + strstrip(e), + relaxed, + userdata); +} + +/* Go through the file and parse each line */ +int config_parse(const char *unit, + const char *filename, + FILE *f, + const char *sections, + ConfigItemLookup lookup, + const void *table, + bool relaxed, + bool allow_include, + bool warn, + void *userdata) { + + _cleanup_free_ char *section = NULL, *continuation = NULL; + _cleanup_fclose_ FILE *ours = NULL; + unsigned line = 0, section_line = 0; + bool section_ignored = false; + int r; + + assert(filename); + assert(lookup); + + if (!f) { + f = ours = fopen(filename, "re"); + if (!f) { + /* Only log on request, except for ENOENT, + * since we return 0 to the caller. */ + if (warn || errno == ENOENT) + log_full(errno == ENOENT ? LOG_DEBUG : LOG_ERR, + "Failed to open configuration file '%s': %m", filename); + return errno == ENOENT ? 0 : -errno; + } + } + + fd_warn_permissions(filename, fileno(f)); + + while (!feof(f)) { + char l[LINE_MAX], *p, *c = NULL, *e; + bool escaped = false; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + log_error_errno(errno, "Failed to read configuration file '%s': %m", filename); + return -errno; + } + + truncate_nl(l); + + if (continuation) { + c = strappend(continuation, l); + if (!c) { + if (warn) + log_oom(); + return -ENOMEM; + } + + continuation = mfree(continuation); + p = c; + } else + p = l; + + for (e = p; *e; e++) { + if (escaped) + escaped = false; + else if (*e == '\\') + escaped = true; + } + + if (escaped) { + *(e-1) = ' '; + + if (c) + continuation = c; + else { + continuation = strdup(l); + if (!continuation) { + if (warn) + log_oom(); + return -ENOMEM; + } + } + + continue; + } + + r = parse_line(unit, + filename, + ++line, + sections, + lookup, + table, + relaxed, + allow_include, + §ion, + §ion_line, + §ion_ignored, + p, + userdata); + free(c); + + if (r < 0) { + if (warn) + log_warning_errno(r, "Failed to parse file '%s': %m", + filename); + return r; + } + } + + return 0; +} + +/* Parse each config file in the specified directories. */ +int config_parse_many(const char *conf_file, + const char *conf_file_dirs, + const char *sections, + ConfigItemLookup lookup, + const void *table, + bool relaxed, + void *userdata) { + _cleanup_strv_free_ char **files = NULL; + char **fn; + int r; + + r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs); + if (r < 0) + return r; + + if (conf_file) { + r = config_parse(NULL, conf_file, NULL, sections, lookup, table, relaxed, false, true, userdata); + if (r < 0) + return r; + } + + STRV_FOREACH(fn, files) { + r = config_parse(NULL, *fn, NULL, sections, lookup, table, relaxed, false, true, userdata); + if (r < 0) + return r; + } + + return 0; +} + +#define DEFINE_PARSER(type, vartype, conv_func) \ + int config_parse_##type( \ + 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) { \ + \ + vartype *i = data; \ + int r; \ + \ + assert(filename); \ + assert(lvalue); \ + assert(rvalue); \ + assert(data); \ + \ + r = conv_func(rvalue, i); \ + if (r < 0) \ + log_syntax(unit, LOG_ERR, filename, line, r, \ + "Failed to parse %s value, ignoring: %s", \ + #type, rvalue); \ + \ + return 0; \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +DEFINE_PARSER(int, int, safe_atoi); +DEFINE_PARSER(long, long, safe_atoli); +DEFINE_PARSER(uint32, uint32_t, safe_atou32); +DEFINE_PARSER(uint64, uint64_t, safe_atou64); +DEFINE_PARSER(unsigned, unsigned, safe_atou); +DEFINE_PARSER(double, double, safe_atod); +DEFINE_PARSER(nsec, nsec_t, parse_nsec); +DEFINE_PARSER(sec, usec_t, parse_sec); +DEFINE_PARSER(mode, mode_t, parse_mode); + +int config_parse_iec_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) { + + size_t *sz = data; + uint64_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_size(rvalue, 1024, &v); + if (r < 0 || (uint64_t) (size_t) v != v) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue); + return 0; + } + + *sz = (size_t) v; + return 0; +} + +int config_parse_si_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) { + + size_t *sz = data; + uint64_t v; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_size(rvalue, 1000, &v); + if (r < 0 || (uint64_t) (size_t) v != v) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue); + return 0; + } + + *sz = (size_t) v; + return 0; +} + +int config_parse_iec_uint64(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 *bytes = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = parse_size(rvalue, 1024, bytes); + if (r < 0) + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue); + + return 0; +} + +int config_parse_bool(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 k; + bool *b = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + k = parse_boolean(rvalue); + if (k < 0) { + log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue); + return 0; + } + + *b = !!k; + return 0; +} + +int config_parse_tristate( + 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 k, *t = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + /* A tristate is pretty much a boolean, except that it can + * also take the special value -1, indicating "uninitialized", + * much like NULL is for a pointer type. */ + + k = parse_boolean(rvalue); + if (k < 0) { + log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue); + return 0; + } + + *t = !!k; + return 0; +} + +int config_parse_string( + 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 **s = data, *n; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (!utf8_is_valid(rvalue)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); + return 0; + } + + if (isempty(rvalue)) + n = NULL; + else { + n = strdup(rvalue); + if (!n) + return log_oom(); + } + + free(*s); + *s = n; + + return 0; +} + +int config_parse_path( + 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 **s = data, *n; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (!utf8_is_valid(rvalue)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); + return 0; + } + + if (!path_is_absolute(rvalue)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute path, ignoring: %s", rvalue); + return 0; + } + + n = strdup(rvalue); + if (!n) + return log_oom(); + + path_kill_slashes(n); + + free(*s); + *s = n; + + return 0; +} + +int config_parse_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 ***sv = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + char **empty; + + /* Empty assignment resets the list. As a special rule + * 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); + 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); + 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; + } + + if (!utf8_is_valid(word)) { + log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); + free(word); + continue; + } + r = strv_consume(sv, word); + if (r < 0) + return log_oom(); + } + + return 0; +} + +int config_parse_log_facility( + 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 *o = data, x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + x = log_facility_unshifted_from_string(rvalue); + if (x < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse log facility, ignoring: %s", rvalue); + return 0; + } + + *o = (x << 3) | LOG_PRI(*o); + + return 0; +} + +int config_parse_log_level( + 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 *o = data, x; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + x = log_level_from_string(rvalue); + if (x < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse log level, ignoring: %s", rvalue); + return 0; + } + + *o = (*o & LOG_FACMASK) | x; + return 0; +} + +int config_parse_signal( + 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 *sig = data, r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(sig); + + r = signal_from_string_try_harder(rvalue); + if (r <= 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse signal name, ignoring: %s", rvalue); + return 0; + } + + *sig = r; + return 0; +} + +int config_parse_personality( + 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) { + + unsigned long *personality = data, p; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(personality); + + p = personality_from_string(rvalue); + if (p == PERSONALITY_INVALID) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse personality, ignoring: %s", rvalue); + return 0; + } + + *personality = p; + return 0; +} diff --git a/src/libshared/conf-parser.h b/src/libshared/conf-parser.h new file mode 100644 index 0000000000..a91c94c322 --- /dev/null +++ b/src/libshared/conf-parser.h @@ -0,0 +1,228 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "log.h" +#include "macro.h" + +/* An abstract parser for simple, line based, shallow configuration + * files consisting of variable assignments only. */ + +/* Prototype for a parser for a specific configuration setting */ +typedef int (*ConfigParserCallback)(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); + +/* Wraps information for parsing a specific configuration variable, to + * be stored in a simple array */ +typedef struct ConfigTableItem { + const char *section; /* Section */ + const char *lvalue; /* Name of the variable */ + ConfigParserCallback parse; /* Function that is called to parse the variable's value */ + int ltype; /* Distinguish different variables passed to the same callback */ + void *data; /* Where to store the variable's data */ +} ConfigTableItem; + +/* Wraps information for parsing a specific configuration variable, to + * be stored in a gperf perfect hashtable */ +typedef struct ConfigPerfItem { + const char *section_and_lvalue; /* Section + "." + name of the variable */ + ConfigParserCallback parse; /* Function that is called to parse the variable's value */ + int ltype; /* Distinguish different variables passed to the same callback */ + size_t offset; /* Offset where to store data, from the beginning of userdata */ +} ConfigPerfItem; + +/* Prototype for a low-level gperf lookup function */ +typedef const ConfigPerfItem* (*ConfigPerfItemLookup)(const char *section_and_lvalue, unsigned length); + +/* Prototype for a generic high-level lookup function */ +typedef int (*ConfigItemLookup)( + const void *table, + const char *section, + const char *lvalue, + ConfigParserCallback *func, + int *ltype, + void **data, + void *userdata); + +/* Linear table search implementation of ConfigItemLookup, based on + * ConfigTableItem arrays */ +int config_item_table_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata); + +/* gperf implementation of ConfigItemLookup, based on gperf + * ConfigPerfItem tables */ +int config_item_perf_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata); + +int config_parse(const char *unit, + const char *filename, + FILE *f, + const char *sections, /* nulstr */ + ConfigItemLookup lookup, + const void *table, + bool relaxed, + bool allow_include, + bool warn, + void *userdata); + +int config_parse_many(const char *conf_file, /* possibly NULL */ + const char *conf_file_dirs, /* nulstr */ + const char *sections, /* nulstr */ + ConfigItemLookup lookup, + const void *table, + bool relaxed, + void *userdata); + +/* Generic parsers */ +int config_parse_int(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_unsigned(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_long(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_uint32(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_uint64(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_double(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_iec_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_si_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_iec_uint64(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_bool(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_tristate(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_string(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_path(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_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); +int config_parse_sec(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_nsec(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_mode(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_log_facility(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_log_level(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_signal(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_personality(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); + +#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \ + int function(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) { \ + \ + type *i = data, x; \ + \ + assert(filename); \ + assert(lvalue); \ + assert(rvalue); \ + assert(data); \ + \ + if ((x = name##_from_string(rvalue)) < 0) { \ + log_syntax(unit, LOG_ERR, filename, line, -x, \ + msg ", ignoring: %s", rvalue); \ + return 0; \ + } \ + \ + *i = x; \ + return 0; \ + } + +#define DEFINE_CONFIG_PARSE_ENUMV(function,name,type,invalid,msg) \ + int function(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) { \ + \ + type **enums = data, x, *ys; \ + _cleanup_free_ type *xs = NULL; \ + const char *word, *state; \ + size_t l, i = 0; \ + \ + assert(filename); \ + assert(lvalue); \ + assert(rvalue); \ + assert(data); \ + \ + xs = new0(type, 1); \ + if(!xs) \ + return -ENOMEM; \ + \ + *xs = invalid; \ + \ + FOREACH_WORD(word, l, rvalue, state) { \ + _cleanup_free_ char *en = NULL; \ + type *new_xs; \ + \ + en = strndup(word, l); \ + if (!en) \ + return -ENOMEM; \ + \ + if ((x = name##_from_string(en)) < 0) { \ + log_syntax(unit, LOG_ERR, filename, line, \ + -x, msg ", ignoring: %s", en); \ + continue; \ + } \ + \ + for (ys = xs; x != invalid && *ys != invalid; ys++) { \ + if (*ys == x) { \ + log_syntax(unit, LOG_ERR, filename, \ + line, -x, \ + "Duplicate entry, ignoring: %s", \ + en); \ + x = invalid; \ + } \ + } \ + \ + if (x == invalid) \ + continue; \ + \ + *(xs + i) = x; \ + new_xs = realloc(xs, (++i + 1) * sizeof(type)); \ + if (new_xs) \ + xs = new_xs; \ + else \ + return -ENOMEM; \ + \ + *(xs + i) = invalid; \ + } \ + \ + free(*enums); \ + *enums = xs; \ + xs = NULL; \ + \ + return 0; \ + } diff --git a/src/libshared/dev-setup.c b/src/libshared/dev-setup.c new file mode 100644 index 0000000000..b2d464c117 --- /dev/null +++ b/src/libshared/dev-setup.c @@ -0,0 +1,73 @@ +/*** + This file is part of systemd. + + Copyright 2010-2012 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 . +***/ + +#include +#include +#include + +#include "alloc-util.h" +#include "dev-setup.h" +#include "label.h" +#include "log.h" +#include "path-util.h" +#include "user-util.h" +#include "util.h" + +int dev_setup(const char *prefix, uid_t uid, gid_t gid) { + static const char symlinks[] = + "-/proc/kcore\0" "/dev/core\0" + "/proc/self/fd\0" "/dev/fd\0" + "/proc/self/fd/0\0" "/dev/stdin\0" + "/proc/self/fd/1\0" "/dev/stdout\0" + "/proc/self/fd/2\0" "/dev/stderr\0"; + + const char *j, *k; + int r; + + NULSTR_FOREACH_PAIR(j, k, symlinks) { + _cleanup_free_ char *link_name = NULL; + const char *n; + + if (j[0] == '-') { + j++; + + if (access(j, F_OK) < 0) + continue; + } + + if (prefix) { + link_name = prefix_root(prefix, k); + if (!link_name) + return -ENOMEM; + + n = link_name; + } else + n = k; + + r = symlink_label(j, n); + if (r < 0) + log_debug_errno(r, "Failed to symlink %s to %s: %m", j, n); + + if (uid != UID_INVALID || gid != GID_INVALID) + if (lchown(n, uid, gid) < 0) + log_debug_errno(errno, "Failed to chown %s: %m", n); + } + + return 0; +} diff --git a/src/libshared/dev-setup.h b/src/libshared/dev-setup.h new file mode 100644 index 0000000000..5766a62060 --- /dev/null +++ b/src/libshared/dev-setup.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-2012 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 . +***/ + +#include + +int dev_setup(const char *prefix, uid_t uid, gid_t gid); diff --git a/src/libshared/dns-domain.c b/src/libshared/dns-domain.c new file mode 100644 index 0000000000..45d24c0079 --- /dev/null +++ b/src/libshared/dns-domain.c @@ -0,0 +1,1322 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . + ***/ + +#ifdef HAVE_LIBIDN +#include +#include +#endif + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "dns-domain.h" +#include "hashmap.h" +#include "hexdecoct.h" +#include "in-addr-util.h" +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" +#include "utf8.h" + +int dns_label_unescape(const char **name, char *dest, size_t sz) { + const char *n; + char *d; + int r = 0; + + assert(name); + assert(*name); + + n = *name; + d = dest; + + for (;;) { + if (*n == '.') { + n++; + break; + } + + if (*n == 0) + break; + + if (r >= DNS_LABEL_MAX) + return -EINVAL; + + if (sz <= 0) + return -ENOBUFS; + + if (*n == '\\') { + /* Escaped character */ + + n++; + + if (*n == 0) + /* Ending NUL */ + return -EINVAL; + + else if (*n == '\\' || *n == '.') { + /* Escaped backslash or dot */ + + if (d) + *(d++) = *n; + sz--; + r++; + n++; + + } else if (n[0] >= '0' && n[0] <= '9') { + unsigned k; + + /* Escaped literal ASCII character */ + + if (!(n[1] >= '0' && n[1] <= '9') || + !(n[2] >= '0' && n[2] <= '9')) + return -EINVAL; + + k = ((unsigned) (n[0] - '0') * 100) + + ((unsigned) (n[1] - '0') * 10) + + ((unsigned) (n[2] - '0')); + + /* Don't allow anything that doesn't + * fit in 8bit. Note that we do allow + * control characters, as some servers + * (e.g. cloudflare) are happy to + * generate labels with them + * inside. */ + if (k > 255) + return -EINVAL; + + if (d) + *(d++) = (char) k; + sz--; + r++; + + n += 3; + } else + return -EINVAL; + + } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) { + + /* Normal character */ + + if (d) + *(d++) = *n; + sz--; + r++; + n++; + } else + return -EINVAL; + } + + /* Empty label that is not at the end? */ + if (r == 0 && *n) + return -EINVAL; + + if (sz >= 1 && d) + *d = 0; + + *name = n; + return r; +} + +/* @label_terminal: terminal character of a label, updated to point to the terminal character of + * the previous label (always skipping one dot) or to NULL if there are no more + * labels. */ +int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) { + const char *terminal; + int r; + + assert(name); + assert(label_terminal); + assert(dest); + + /* no more labels */ + if (!*label_terminal) { + if (sz >= 1) + *dest = 0; + + return 0; + } + + terminal = *label_terminal; + assert(*terminal == '.' || *terminal == 0); + + /* Skip current terminal character (and accept domain names ending it ".") */ + if (*terminal == 0) + terminal--; + if (terminal >= name && *terminal == '.') + terminal--; + + /* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */ + for (;;) { + if (terminal < name) { + /* Reached the first label, so indicate that there are no more */ + terminal = NULL; + break; + } + + /* Find the start of the last label */ + if (*terminal == '.') { + const char *y; + unsigned slashes = 0; + + for (y = terminal - 1; y >= name && *y == '\\'; y--) + slashes ++; + + if (slashes % 2 == 0) { + /* The '.' was not escaped */ + name = terminal + 1; + break; + } else { + terminal = y; + continue; + } + } + + terminal --; + } + + r = dns_label_unescape(&name, dest, sz); + if (r < 0) + return r; + + *label_terminal = terminal; + + return r; +} + +int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { + char *q; + + /* DNS labels must be between 1 and 63 characters long. A + * zero-length label does not exist. See RFC 2182, Section + * 11. */ + + if (l <= 0 || l > DNS_LABEL_MAX) + return -EINVAL; + if (sz < 1) + return -ENOBUFS; + + assert(p); + assert(dest); + + q = dest; + while (l > 0) { + + if (*p == '.' || *p == '\\') { + + /* Dot or backslash */ + + if (sz < 3) + return -ENOBUFS; + + *(q++) = '\\'; + *(q++) = *p; + + sz -= 2; + + } else if (*p == '_' || + *p == '-' || + (*p >= '0' && *p <= '9') || + (*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z')) { + + /* Proper character */ + + if (sz < 2) + return -ENOBUFS; + + *(q++) = *p; + sz -= 1; + + } else { + + /* Everything else */ + + if (sz < 5) + return -ENOBUFS; + + *(q++) = '\\'; + *(q++) = '0' + (char) ((uint8_t) *p / 100); + *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10); + *(q++) = '0' + (char) ((uint8_t) *p % 10); + + sz -= 4; + } + + p++; + l--; + } + + *q = 0; + return (int) (q - dest); +} + +int dns_label_escape_new(const char *p, size_t l, char **ret) { + _cleanup_free_ char *s = NULL; + int r; + + assert(p); + assert(ret); + + if (l <= 0 || l > DNS_LABEL_MAX) + return -EINVAL; + + s = new(char, DNS_LABEL_ESCAPED_MAX); + if (!s) + return -ENOMEM; + + r = dns_label_escape(p, l, s, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + *ret = s; + s = NULL; + + return r; +} + +int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { +#ifdef HAVE_LIBIDN + _cleanup_free_ uint32_t *input = NULL; + size_t input_size, l; + const char *p; + bool contains_8bit = false; + char buffer[DNS_LABEL_MAX+1]; + + assert(encoded); + assert(decoded); + + /* Converts an U-label into an A-label */ + + if (encoded_size <= 0) + return -EINVAL; + + for (p = encoded; p < encoded + encoded_size; p++) + if ((uint8_t) *p > 127) + contains_8bit = true; + + if (!contains_8bit) { + if (encoded_size > DNS_LABEL_MAX) + return -EINVAL; + + return 0; + } + + input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); + if (!input) + return -ENOMEM; + + if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0) + return -EINVAL; + + l = strlen(buffer); + + /* Verify that the the result is not longer than one DNS label. */ + if (l <= 0 || l > DNS_LABEL_MAX) + return -EINVAL; + if (l > decoded_max) + return -ENOBUFS; + + memcpy(decoded, buffer, l); + + /* If there's room, append a trailing NUL byte, but only then */ + if (decoded_max > l) + decoded[l] = 0; + + return (int) l; +#else + return 0; +#endif +} + +int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { +#ifdef HAVE_LIBIDN + size_t input_size, output_size; + _cleanup_free_ uint32_t *input = NULL; + _cleanup_free_ char *result = NULL; + uint32_t *output = NULL; + size_t w; + + /* To be invoked after unescaping. Converts an A-label into an U-label. */ + + assert(encoded); + assert(decoded); + + if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX) + return -EINVAL; + + if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1) + return 0; + + if (memcmp(encoded, IDNA_ACE_PREFIX, sizeof(IDNA_ACE_PREFIX) -1) != 0) + return 0; + + input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); + if (!input) + return -ENOMEM; + + output_size = input_size; + output = newa(uint32_t, output_size); + + idna_to_unicode_44i(input, input_size, output, &output_size, 0); + + result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w); + if (!result) + return -ENOMEM; + if (w <= 0) + return -EINVAL; + if (w > decoded_max) + return -ENOBUFS; + + memcpy(decoded, result, w); + + /* Append trailing NUL byte if there's space, but only then. */ + if (decoded_max > w) + decoded[w] = 0; + + return w; +#else + return 0; +#endif +} + +int dns_name_concat(const char *a, const char *b, char **_ret) { + _cleanup_free_ char *ret = NULL; + size_t n = 0, allocated = 0; + const char *p; + bool first = true; + int r; + + if (a) + p = a; + else if (b) { + p = b; + b = NULL; + } else + goto finish; + + for (;;) { + char label[DNS_LABEL_MAX]; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + return r; + if (r == 0) { + if (*p != 0) + return -EINVAL; + + if (b) { + /* Now continue with the second string, if there is one */ + p = b; + b = NULL; + continue; + } + + break; + } + + if (_ret) { + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + r = dns_label_escape(label, r, ret + n + !first, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + if (!first) + ret[n] = '.'; + } else { + char escaped[DNS_LABEL_ESCAPED_MAX]; + + r = dns_label_escape(label, r, escaped, sizeof(escaped)); + if (r < 0) + return r; + } + + if (!first) + n++; + else + first = false; + + n += r; + } + +finish: + if (n > DNS_HOSTNAME_MAX) + return -EINVAL; + + if (_ret) { + if (n == 0) { + /* Nothing appended? If so, generate at least a single dot, to indicate the DNS root domain */ + if (!GREEDY_REALLOC(ret, allocated, 2)) + return -ENOMEM; + + ret[n++] = '.'; + } else { + if (!GREEDY_REALLOC(ret, allocated, n + 1)) + return -ENOMEM; + } + + ret[n] = 0; + *_ret = ret; + ret = NULL; + } + + return 0; +} + +void dns_name_hash_func(const void *s, struct siphash *state) { + const char *p = s; + int r; + + assert(p); + + for (;;) { + char label[DNS_LABEL_MAX+1]; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + break; + if (r == 0) + break; + + ascii_strlower_n(label, r); + siphash24_compress(label, r, state); + siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */ + } + + /* enforce that all names are terminated by the empty label */ + string_hash_func("", state); +} + +int dns_name_compare_func(const void *a, const void *b) { + const char *x, *y; + int r, q; + + assert(a); + assert(b); + + x = (const char *) a + strlen(a); + y = (const char *) b + strlen(b); + + for (;;) { + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + + if (x == NULL && y == NULL) + return 0; + + r = dns_label_unescape_suffix(a, &x, la, sizeof(la)); + q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb)); + if (r < 0 || q < 0) + return r - q; + + r = ascii_strcasecmp_nn(la, r, lb, q); + if (r != 0) + return r; + } +} + +const struct hash_ops dns_name_hash_ops = { + .hash = dns_name_hash_func, + .compare = dns_name_compare_func +}; + +int dns_name_equal(const char *x, const char *y) { + int r, q; + + assert(x); + assert(y); + + for (;;) { + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + + r = dns_label_unescape(&x, la, sizeof(la)); + if (r < 0) + return r; + + q = dns_label_unescape(&y, lb, sizeof(lb)); + if (q < 0) + return q; + + if (r != q) + return false; + if (r == 0) + return true; + + if (ascii_strcasecmp_n(la, lb, r) != 0) + return false; + } +} + +int dns_name_endswith(const char *name, const char *suffix) { + const char *n, *s, *saved_n = NULL; + int r, q; + + assert(name); + assert(suffix); + + n = name; + s = suffix; + + for (;;) { + char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; + + r = dns_label_unescape(&n, ln, sizeof(ln)); + if (r < 0) + return r; + + if (!saved_n) + saved_n = n; + + q = dns_label_unescape(&s, ls, sizeof(ls)); + if (q < 0) + return q; + + if (r == 0 && q == 0) + return true; + if (r == 0 && saved_n == n) + return false; + + if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { + + /* Not the same, let's jump back, and try with the next label again */ + s = suffix; + n = saved_n; + saved_n = NULL; + } + } +} + +int dns_name_startswith(const char *name, const char *prefix) { + const char *n, *p; + int r, q; + + assert(name); + assert(prefix); + + n = name; + p = prefix; + + for (;;) { + char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX]; + + r = dns_label_unescape(&p, lp, sizeof(lp)); + if (r < 0) + return r; + if (r == 0) + return true; + + q = dns_label_unescape(&n, ln, sizeof(ln)); + if (q < 0) + return q; + + if (r != q) + return false; + if (ascii_strcasecmp_n(ln, lp, r) != 0) + return false; + } +} + +int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) { + const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix; + int r, q; + + assert(name); + assert(old_suffix); + assert(new_suffix); + assert(ret); + + n = name; + s = old_suffix; + + for (;;) { + char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; + + if (!saved_before) + saved_before = n; + + r = dns_label_unescape(&n, ln, sizeof(ln)); + if (r < 0) + return r; + + if (!saved_after) + saved_after = n; + + q = dns_label_unescape(&s, ls, sizeof(ls)); + if (q < 0) + return q; + + if (r == 0 && q == 0) + break; + if (r == 0 && saved_after == n) { + *ret = NULL; /* doesn't match */ + return 0; + } + + if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { + + /* Not the same, let's jump back, and try with the next label again */ + s = old_suffix; + n = saved_after; + saved_after = saved_before = NULL; + } + } + + /* Found it! Now generate the new name */ + prefix = strndupa(name, saved_before - name); + + r = dns_name_concat(prefix, new_suffix, ret); + if (r < 0) + return r; + + return 1; +} + +int dns_name_between(const char *a, const char *b, const char *c) { + int n; + + /* Determine if b is strictly greater than a and strictly smaller than c. + We consider the order of names to be circular, so that if a is + strictly greater than c, we consider b to be between them if it is + either greater than a or smaller than c. This is how the canonical + DNS name order used in NSEC records work. */ + + n = dns_name_compare_func(a, c); + if (n == 0) + return -EINVAL; + else if (n < 0) + /* a<---b--->c */ + return dns_name_compare_func(a, b) < 0 && + dns_name_compare_func(b, c) < 0; + else + /* <--b--c a--b--> */ + return dns_name_compare_func(b, c) < 0 || + dns_name_compare_func(a, b) < 0; +} + +int dns_name_reverse(int family, const union in_addr_union *a, char **ret) { + const uint8_t *p; + int r; + + assert(a); + assert(ret); + + p = (const uint8_t*) a; + + if (family == AF_INET) + r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]); + else if (family == AF_INET6) + r = asprintf(ret, "%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.ip6.arpa", + hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4), + hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4), + hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4), + hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4), + hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4), + hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4), + hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4), + hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4)); + else + return -EAFNOSUPPORT; + if (r < 0) + return -ENOMEM; + + return 0; +} + +int dns_name_address(const char *p, int *family, union in_addr_union *address) { + int r; + + assert(p); + assert(family); + assert(address); + + r = dns_name_endswith(p, "in-addr.arpa"); + if (r < 0) + return r; + if (r > 0) { + uint8_t a[4]; + unsigned i; + + for (i = 0; i < ELEMENTSOF(a); i++) { + char label[DNS_LABEL_MAX+1]; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + if (r > 3) + return -EINVAL; + + r = safe_atou8(label, &a[i]); + if (r < 0) + return r; + } + + r = dns_name_equal(p, "in-addr.arpa"); + if (r <= 0) + return r; + + *family = AF_INET; + address->in.s_addr = htobe32(((uint32_t) a[3] << 24) | + ((uint32_t) a[2] << 16) | + ((uint32_t) a[1] << 8) | + (uint32_t) a[0]); + + return 1; + } + + r = dns_name_endswith(p, "ip6.arpa"); + if (r < 0) + return r; + if (r > 0) { + struct in6_addr a; + unsigned i; + + for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) { + char label[DNS_LABEL_MAX+1]; + int x, y; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r <= 0) + return r; + if (r != 1) + return -EINVAL; + x = unhexchar(label[0]); + if (x < 0) + return -EINVAL; + + r = dns_label_unescape(&p, label, sizeof(label)); + if (r <= 0) + return r; + if (r != 1) + return -EINVAL; + y = unhexchar(label[0]); + if (y < 0) + return -EINVAL; + + a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x; + } + + r = dns_name_equal(p, "ip6.arpa"); + if (r <= 0) + return r; + + *family = AF_INET6; + address->in6 = a; + return 1; + } + + return 0; +} + +bool dns_name_is_root(const char *name) { + + assert(name); + + /* There are exactly two ways to encode the root domain name: + * as empty string, or with a single dot. */ + + return STR_IN_SET(name, "", "."); +} + +bool dns_name_is_single_label(const char *name) { + int r; + + assert(name); + + r = dns_name_parent(&name); + if (r <= 0) + return false; + + return dns_name_is_root(name); +} + +/* Encode a domain name according to RFC 1035 Section 3.1, without compression */ +int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical) { + uint8_t *label_length, *out; + int r; + + assert(domain); + assert(buffer); + + out = buffer; + + do { + /* Reserve a byte for label length */ + if (len <= 0) + return -ENOBUFS; + len--; + label_length = out; + out++; + + /* Convert and copy a single label. Note that + * dns_label_unescape() returns 0 when it hits the end + * of the domain name, which we rely on here to encode + * the trailing NUL byte. */ + r = dns_label_unescape(&domain, (char *) out, len); + if (r < 0) + return r; + + /* Optionally, output the name in DNSSEC canonical + * format, as described in RFC 4034, section 6.2. Or + * in other words: in lower-case. */ + if (canonical) + ascii_strlower_n((char*) out, (size_t) r); + + /* Fill label length, move forward */ + *label_length = r; + out += r; + len -= r; + + } while (r != 0); + + /* Verify the maximum size of the encoded name. The trailing + * dot + NUL byte account are included this time, hence + * compare against DNS_HOSTNAME_MAX + 2 (which is 255) this + * time. */ + if (out - buffer > DNS_HOSTNAME_MAX + 2) + return -EINVAL; + + return out - buffer; +} + +static bool srv_type_label_is_valid(const char *label, size_t n) { + size_t k; + + assert(label); + + if (n < 2) /* Label needs to be at least 2 chars long */ + return false; + + if (label[0] != '_') /* First label char needs to be underscore */ + return false; + + /* Second char must be a letter */ + if (!(label[1] >= 'A' && label[1] <= 'Z') && + !(label[1] >= 'a' && label[1] <= 'z')) + return false; + + /* Third and further chars must be alphanumeric or a hyphen */ + for (k = 2; k < n; k++) { + if (!(label[k] >= 'A' && label[k] <= 'Z') && + !(label[k] >= 'a' && label[k] <= 'z') && + !(label[k] >= '0' && label[k] <= '9') && + label[k] != '-') + return false; + } + + return true; +} + +bool dns_srv_type_is_valid(const char *name) { + unsigned c = 0; + int r; + + if (!name) + return false; + + for (;;) { + char label[DNS_LABEL_MAX]; + + /* This more or less implements RFC 6335, Section 5.1 */ + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + return false; + if (r == 0) + break; + + if (c >= 2) + return false; + + if (!srv_type_label_is_valid(label, r)) + return false; + + c++; + } + + return c == 2; /* exactly two labels */ +} + +bool dns_service_name_is_valid(const char *name) { + size_t l; + + /* This more or less implements RFC 6763, Section 4.1.1 */ + + if (!name) + return false; + + if (!utf8_is_valid(name)) + return false; + + if (string_has_cc(name, NULL)) + return false; + + l = strlen(name); + if (l <= 0) + return false; + if (l > 63) + return false; + + return true; +} + +int dns_service_join(const char *name, const char *type, const char *domain, char **ret) { + char escaped[DNS_LABEL_ESCAPED_MAX]; + _cleanup_free_ char *n = NULL; + int r; + + assert(type); + assert(domain); + assert(ret); + + if (!dns_srv_type_is_valid(type)) + return -EINVAL; + + if (!name) + return dns_name_concat(type, domain, ret); + + if (!dns_service_name_is_valid(name)) + return -EINVAL; + + r = dns_label_escape(name, strlen(name), escaped, sizeof(escaped)); + if (r < 0) + return r; + + r = dns_name_concat(type, domain, &n); + if (r < 0) + return r; + + return dns_name_concat(escaped, n, ret); +} + +static bool dns_service_name_label_is_valid(const char *label, size_t n) { + char *s; + + assert(label); + + if (memchr(label, 0, n)) + return false; + + s = strndupa(label, n); + return dns_service_name_is_valid(s); +} + +int dns_service_split(const char *joined, char **_name, char **_type, char **_domain) { + _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; + const char *p = joined, *q = NULL, *d = NULL; + char a[DNS_LABEL_MAX], b[DNS_LABEL_MAX], c[DNS_LABEL_MAX]; + int an, bn, cn, r; + unsigned x = 0; + + assert(joined); + + /* Get first label from the full name */ + an = dns_label_unescape(&p, a, sizeof(a)); + if (an < 0) + return an; + + if (an > 0) { + x++; + + /* If there was a first label, try to get the second one */ + bn = dns_label_unescape(&p, b, sizeof(b)); + if (bn < 0) + return bn; + + if (bn > 0) { + x++; + + /* If there was a second label, try to get the third one */ + q = p; + cn = dns_label_unescape(&p, c, sizeof(c)); + if (cn < 0) + return cn; + + if (cn > 0) + x++; + } else + cn = 0; + } else + an = 0; + + if (x >= 2 && srv_type_label_is_valid(b, bn)) { + + if (x >= 3 && srv_type_label_is_valid(c, cn)) { + + if (dns_service_name_label_is_valid(a, an)) { + /* OK, got . . . */ + + name = strndup(a, an); + if (!name) + return -ENOMEM; + + type = strjoin(b, ".", c, NULL); + if (!type) + return -ENOMEM; + + d = p; + goto finish; + } + + } else if (srv_type_label_is_valid(a, an)) { + + /* OK, got . . */ + + name = NULL; + + type = strjoin(a, ".", b, NULL); + if (!type) + return -ENOMEM; + + d = q; + goto finish; + } + } + + name = NULL; + type = NULL; + d = joined; + +finish: + r = dns_name_normalize(d, &domain); + if (r < 0) + return r; + + if (_domain) { + *_domain = domain; + domain = NULL; + } + + if (_type) { + *_type = type; + type = NULL; + } + + if (_name) { + *_name = name; + name = NULL; + } + + return 0; +} + +static int dns_name_build_suffix_table(const char *name, const char*table[]) { + const char *p; + unsigned n = 0; + int r; + + assert(name); + assert(table); + + p = name; + for (;;) { + if (n > DNS_N_LABELS_MAX) + return -EINVAL; + + table[n] = p; + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + + n++; + } + + return (int) n; +} + +int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) { + const char* labels[DNS_N_LABELS_MAX+1]; + int n; + + assert(name); + assert(ret); + + n = dns_name_build_suffix_table(name, labels); + if (n < 0) + return n; + + if ((unsigned) n < n_labels) + return -EINVAL; + + *ret = labels[n - n_labels]; + return (int) (n - n_labels); +} + +int dns_name_skip(const char *a, unsigned n_labels, const char **ret) { + int r; + + assert(a); + assert(ret); + + for (; n_labels > 0; n_labels --) { + r = dns_name_parent(&a); + if (r < 0) + return r; + if (r == 0) { + *ret = ""; + return 0; + } + } + + *ret = a; + return 1; +} + +int dns_name_count_labels(const char *name) { + unsigned n = 0; + const char *p; + int r; + + assert(name); + + p = name; + for (;;) { + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + + if (n >= DNS_N_LABELS_MAX) + return -EINVAL; + + n++; + } + + return (int) n; +} + +int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) { + int r; + + assert(a); + assert(b); + + r = dns_name_skip(a, n_labels, &a); + if (r <= 0) + return r; + + return dns_name_equal(a, b); +} + +int dns_name_common_suffix(const char *a, const char *b, const char **ret) { + const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1]; + int n = 0, m = 0, k = 0, r, q; + + assert(a); + assert(b); + assert(ret); + + /* Determines the common suffix of domain names a and b */ + + n = dns_name_build_suffix_table(a, a_labels); + if (n < 0) + return n; + + m = dns_name_build_suffix_table(b, b_labels); + if (m < 0) + return m; + + for (;;) { + char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; + const char *x, *y; + + if (k >= n || k >= m) { + *ret = a_labels[n - k]; + return 0; + } + + x = a_labels[n - 1 - k]; + r = dns_label_unescape(&x, la, sizeof(la)); + if (r < 0) + return r; + + y = b_labels[m - 1 - k]; + q = dns_label_unescape(&y, lb, sizeof(lb)); + if (q < 0) + return q; + + if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) { + *ret = a_labels[n - k]; + return 0; + } + + k++; + } +} + +int dns_name_apply_idna(const char *name, char **ret) { + _cleanup_free_ char *buf = NULL; + size_t n = 0, allocated = 0; + bool first = true; + int r, q; + + assert(name); + assert(ret); + + for (;;) { + char label[DNS_LABEL_MAX]; + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + return r; + if (r == 0) + break; + + q = dns_label_apply_idna(label, r, label, sizeof(label)); + if (q < 0) + return q; + if (q > 0) + r = q; + + if (!GREEDY_REALLOC(buf, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) + return -ENOMEM; + + r = dns_label_escape(label, r, buf + n + !first, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + if (first) + first = false; + else + buf[n++] = '.'; + + n +=r; + } + + if (n > DNS_HOSTNAME_MAX) + return -EINVAL; + + if (!GREEDY_REALLOC(buf, allocated, n + 1)) + return -ENOMEM; + + buf[n] = 0; + *ret = buf; + buf = NULL; + + return (int) n; +} diff --git a/src/libshared/dns-domain.h b/src/libshared/dns-domain.h new file mode 100644 index 0000000000..2de3642cb3 --- /dev/null +++ b/src/libshared/dns-domain.h @@ -0,0 +1,110 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . + ***/ + +#pragma once + + +#include +#include +#include +#include + +#include "hashmap.h" +#include "in-addr-util.h" + +/* Length of a single label, with all escaping removed, excluding any trailing dot or NUL byte */ +#define DNS_LABEL_MAX 63 + +/* Worst case length of a single label, with all escaping applied and room for a trailing NUL byte. */ +#define DNS_LABEL_ESCAPED_MAX (DNS_LABEL_MAX*4+1) + +/* Maximum length of a full hostname, consisting of a series of unescaped labels, and no trailing dot or NUL byte */ +#define DNS_HOSTNAME_MAX 253 + +/* Maximum length of a full hostname, on the wire, including the final NUL byte */ +#define DNS_WIRE_FOMAT_HOSTNAME_MAX 255 + +/* Maximum number of labels per valid hostname */ +#define DNS_N_LABELS_MAX 127 + +int dns_label_unescape(const char **name, char *dest, size_t sz); +int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz); +int dns_label_escape(const char *p, size_t l, char *dest, size_t sz); +int dns_label_escape_new(const char *p, size_t l, char **ret); + +static inline int dns_name_parent(const char **name) { + return dns_label_unescape(name, NULL, DNS_LABEL_MAX); +} + +int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); +int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); + +int dns_name_concat(const char *a, const char *b, char **ret); + +static inline int dns_name_normalize(const char *s, char **ret) { + /* dns_name_concat() normalizes as a side-effect */ + return dns_name_concat(s, NULL, ret); +} + +static inline int dns_name_is_valid(const char *s) { + int r; + + /* dns_name_normalize() verifies as a side effect */ + r = dns_name_normalize(s, NULL); + if (r == -EINVAL) + return 0; + if (r < 0) + return r; + return 1; +} + +void dns_name_hash_func(const void *s, struct siphash *state); +int dns_name_compare_func(const void *a, const void *b); +extern const struct hash_ops dns_name_hash_ops; + +int dns_name_between(const char *a, const char *b, const char *c); +int dns_name_equal(const char *x, const char *y); +int dns_name_endswith(const char *name, const char *suffix); +int dns_name_startswith(const char *name, const char *prefix); + +int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret); + +int dns_name_reverse(int family, const union in_addr_union *a, char **ret); +int dns_name_address(const char *p, int *family, union in_addr_union *a); + +bool dns_name_is_root(const char *name); +bool dns_name_is_single_label(const char *name); + +int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical); + +bool dns_srv_type_is_valid(const char *name); +bool dns_service_name_is_valid(const char *name); + +int dns_service_join(const char *name, const char *type, const char *domain, char **ret); +int dns_service_split(const char *joined, char **name, char **type, char **domain); + +int dns_name_suffix(const char *name, unsigned n_labels, const char **ret); +int dns_name_count_labels(const char *name); + +int dns_name_skip(const char *a, unsigned n_labels, const char **ret); +int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b); + +int dns_name_common_suffix(const char *a, const char *b, const char **ret); + +int dns_name_apply_idna(const char *name, char **ret); diff --git a/src/libshared/dropin.c b/src/libshared/dropin.c new file mode 100644 index 0000000000..cc1acd6f23 --- /dev/null +++ b/src/libshared/dropin.c @@ -0,0 +1,251 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "conf-files.h" +#include "dropin.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio-label.h" +#include "hashmap.h" +#include "log.h" +#include "macro.h" +#include "mkdir.h" +#include "path-util.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" + +int drop_in_file(const char *dir, const char *unit, unsigned level, + const char *name, char **_p, char **_q) { + + _cleanup_free_ char *b = NULL; + char *p, *q; + + char prefix[DECIMAL_STR_MAX(unsigned)]; + + assert(unit); + assert(name); + assert(_p); + assert(_q); + + sprintf(prefix, "%u", level); + + b = xescape(name, "/."); + if (!b) + return -ENOMEM; + + if (!filename_is_valid(b)) + return -EINVAL; + + p = strjoin(dir, "/", unit, ".d", NULL); + if (!p) + return -ENOMEM; + + q = strjoin(p, "/", prefix, "-", b, ".conf", NULL); + if (!q) { + free(p); + return -ENOMEM; + } + + *_p = p; + *_q = q; + return 0; +} + +int write_drop_in(const char *dir, const char *unit, unsigned level, + const char *name, const char *data) { + + _cleanup_free_ char *p = NULL, *q = NULL; + int r; + + assert(dir); + assert(unit); + assert(name); + assert(data); + + r = drop_in_file(dir, unit, level, name, &p, &q); + if (r < 0) + return r; + + (void) mkdir_p(p, 0755); + return write_string_file_atomic_label(q, data); +} + +int write_drop_in_format(const char *dir, const char *unit, unsigned level, + const char *name, const char *format, ...) { + _cleanup_free_ char *p = NULL; + va_list ap; + int r; + + assert(dir); + assert(unit); + assert(name); + assert(format); + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0) + return -ENOMEM; + + return write_drop_in(dir, unit, level, name, p); +} + +static int iterate_dir( + const char *path, + UnitDependency dependency, + dependency_consumer_t consumer, + void *arg, + char ***strv) { + + _cleanup_closedir_ DIR *d = NULL; + int r; + + assert(path); + + /* The config directories are special, since the order of the + * drop-ins matters */ + if (dependency < 0) { + r = strv_extend(strv, path); + if (r < 0) + return log_oom(); + + return 0; + } + + assert(consumer); + + d = opendir(path); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open directory %s: %m", path); + } + + for (;;) { + struct dirent *de; + _cleanup_free_ char *f = NULL; + + errno = 0; + de = readdir(d); + if (!de && errno > 0) + return log_error_errno(errno, "Failed to read directory %s: %m", path); + + if (!de) + break; + + if (hidden_file(de->d_name)) + continue; + + f = strjoin(path, "/", de->d_name, NULL); + if (!f) + return log_oom(); + + r = consumer(dependency, de->d_name, f, arg); + if (r < 0) + return r; + } + + return 0; +} + +int unit_file_process_dir( + Set *unit_path_cache, + const char *unit_path, + const char *name, + const char *suffix, + UnitDependency dependency, + dependency_consumer_t consumer, + void *arg, + char ***strv) { + + _cleanup_free_ char *path = NULL; + int r; + + assert(unit_path); + assert(name); + assert(suffix); + + path = strjoin(unit_path, "/", name, suffix, NULL); + if (!path) + return log_oom(); + + if (!unit_path_cache || set_get(unit_path_cache, path)) + (void) iterate_dir(path, dependency, consumer, arg, strv); + + if (unit_name_is_valid(name, UNIT_NAME_INSTANCE)) { + _cleanup_free_ char *template = NULL, *p = NULL; + /* Also try the template dir */ + + r = unit_name_template(name, &template); + if (r < 0) + return log_error_errno(r, "Failed to generate template from unit name: %m"); + + p = strjoin(unit_path, "/", template, suffix, NULL); + if (!p) + return log_oom(); + + if (!unit_path_cache || set_get(unit_path_cache, p)) + (void) iterate_dir(p, dependency, consumer, arg, strv); + } + + return 0; +} + +int unit_file_find_dropin_paths( + char **lookup_path, + Set *unit_path_cache, + Set *names, + char ***paths) { + + _cleanup_strv_free_ char **strv = NULL, **ans = NULL; + Iterator i; + char *t; + int r; + + assert(paths); + + SET_FOREACH(t, names, i) { + char **p; + + STRV_FOREACH(p, lookup_path) + unit_file_process_dir(unit_path_cache, *p, t, ".d", _UNIT_DEPENDENCY_INVALID, NULL, NULL, &strv); + } + + if (strv_isempty(strv)) + return 0; + + r = conf_files_list_strv(&ans, ".conf", NULL, (const char**) strv); + if (r < 0) + return log_warning_errno(r, "Failed to get list of configuration files: %m"); + + *paths = ans; + ans = NULL; + return 1; +} diff --git a/src/libshared/dropin.h b/src/libshared/dropin.h new file mode 100644 index 0000000000..c1936f397b --- /dev/null +++ b/src/libshared/dropin.h @@ -0,0 +1,61 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "hashmap.h" +#include "macro.h" +#include "set.h" +#include "unit-name.h" + +int drop_in_file(const char *dir, const char *unit, unsigned level, + const char *name, char **_p, char **_q); + +int write_drop_in(const char *dir, const char *unit, unsigned level, + const char *name, const char *data); + +int write_drop_in_format(const char *dir, const char *unit, unsigned level, + const char *name, const char *format, ...) _printf_(5, 6); + +/** + * This callback will be called for each directory entry @entry, + * with @filepath being the full path to the entry. + * + * If return value is negative, loop will be aborted. + */ +typedef int (*dependency_consumer_t)(UnitDependency dependency, + const char *entry, + const char* filepath, + void *arg); + +int unit_file_process_dir( + Set * unit_path_cache, + const char *unit_path, + const char *name, + const char *suffix, + UnitDependency dependency, + dependency_consumer_t consumer, + void *arg, + char ***strv); + +int unit_file_find_dropin_paths( + char **lookup_path, + Set *unit_path_cache, + Set *names, + char ***paths); diff --git a/src/libshared/efivars.c b/src/libshared/efivars.c new file mode 100644 index 0000000000..5073c61740 --- /dev/null +++ b/src/libshared/efivars.c @@ -0,0 +1,715 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "efivars.h" +#include "fd-util.h" +#include "io-util.h" +#include "macro.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "time-util.h" +#include "utf8.h" +#include "util.h" +#include "virt.h" + +#ifdef ENABLE_EFI + +#define LOAD_OPTION_ACTIVE 0x00000001 +#define MEDIA_DEVICE_PATH 0x04 +#define MEDIA_HARDDRIVE_DP 0x01 +#define MEDIA_FILEPATH_DP 0x04 +#define SIGNATURE_TYPE_GUID 0x02 +#define MBR_TYPE_EFI_PARTITION_TABLE_HEADER 0x02 +#define END_DEVICE_PATH_TYPE 0x7f +#define END_ENTIRE_DEVICE_PATH_SUBTYPE 0xff +#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001 + +struct boot_option { + uint32_t attr; + uint16_t path_len; + uint16_t title[]; +} _packed_; + +struct drive_path { + uint32_t part_nr; + uint64_t part_start; + uint64_t part_size; + char signature[16]; + uint8_t mbr_type; + uint8_t signature_type; +} _packed_; + +struct device_path { + uint8_t type; + uint8_t sub_type; + uint16_t length; + union { + uint16_t path[0]; + struct drive_path drive; + }; +} _packed_; + +bool is_efi_boot(void) { + return access("/sys/firmware/efi", F_OK) >= 0; +} + +static int read_flag(const char *varname) { + int r; + _cleanup_free_ void *v = NULL; + size_t s; + uint8_t b; + + r = efi_get_variable(EFI_VENDOR_GLOBAL, varname, NULL, &v, &s); + if (r < 0) + return r; + + if (s != 1) + return -EINVAL; + + b = *(uint8_t *)v; + r = b > 0; + return r; +} + +bool is_efi_secure_boot(void) { + return read_flag("SecureBoot") > 0; +} + +bool is_efi_secure_boot_setup_mode(void) { + return read_flag("SetupMode") > 0; +} + +int efi_reboot_to_firmware_supported(void) { + int r; + size_t s; + uint64_t b; + _cleanup_free_ void *v = NULL; + + if (!is_efi_boot() || detect_container() > 0) + return -EOPNOTSUPP; + + r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndicationsSupported", NULL, &v, &s); + if (r < 0) + return r; + else if (s != sizeof(uint64_t)) + return -EINVAL; + + b = *(uint64_t *)v; + b &= EFI_OS_INDICATIONS_BOOT_TO_FW_UI; + return b > 0 ? 0 : -EOPNOTSUPP; +} + +static int get_os_indications(uint64_t *os_indication) { + int r; + size_t s; + _cleanup_free_ void *v = NULL; + + r = efi_reboot_to_firmware_supported(); + if (r < 0) + return r; + + r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndications", NULL, &v, &s); + if (r == -ENOENT) { + /* Some firmware implementations that do support + * OsIndications and report that with + * OsIndicationsSupported will remove the + * OsIndications variable when it is unset. Let's + * pretend it's 0 then, to hide this implementation + * detail. Note that this call will return -ENOENT + * then only if the support for OsIndications is + * missing entirely, as determined by + * efi_reboot_to_firmware_supported() above. */ + *os_indication = 0; + return 0; + } else if (r < 0) + return r; + else if (s != sizeof(uint64_t)) + return -EINVAL; + + *os_indication = *(uint64_t *)v; + return 0; +} + +int efi_get_reboot_to_firmware(void) { + int r; + uint64_t b; + + r = get_os_indications(&b); + if (r < 0) + return r; + + return !!(b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI); +} + +int efi_set_reboot_to_firmware(bool value) { + int r; + uint64_t b, b_new; + + r = get_os_indications(&b); + if (r < 0) + return r; + + if (value) + b_new = b | EFI_OS_INDICATIONS_BOOT_TO_FW_UI; + else + b_new = b & ~EFI_OS_INDICATIONS_BOOT_TO_FW_UI; + + /* Avoid writing to efi vars store if we can due to firmware bugs. */ + if (b != b_new) + return efi_set_variable(EFI_VENDOR_GLOBAL, "OsIndications", &b_new, sizeof(uint64_t)); + + return 0; +} + +int efi_get_variable( + sd_id128_t vendor, + const char *name, + uint32_t *attribute, + void **value, + size_t *size) { + + _cleanup_close_ int fd = -1; + _cleanup_free_ char *p = NULL; + uint32_t a; + ssize_t n; + struct stat st; + _cleanup_free_ void *buf = NULL; + + assert(name); + assert(value); + assert(size); + + if (asprintf(&p, + "/sys/firmware/efi/efivars/%s-%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + name, SD_ID128_FORMAT_VAL(vendor)) < 0) + return -ENOMEM; + + fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + if (fstat(fd, &st) < 0) + return -errno; + if (st.st_size < 4) + return -EIO; + if (st.st_size > 4*1024*1024 + 4) + return -E2BIG; + + n = read(fd, &a, sizeof(a)); + if (n < 0) + return -errno; + if (n != sizeof(a)) + return -EIO; + + buf = malloc(st.st_size - 4 + 2); + if (!buf) + return -ENOMEM; + + n = read(fd, buf, (size_t) st.st_size - 4); + if (n < 0) + return -errno; + if (n != (ssize_t) st.st_size - 4) + return -EIO; + + /* Always NUL terminate (2 bytes, to protect UTF-16) */ + ((char*) buf)[st.st_size - 4] = 0; + ((char*) buf)[st.st_size - 4 + 1] = 0; + + *value = buf; + buf = NULL; + *size = (size_t) st.st_size - 4; + + if (attribute) + *attribute = a; + + return 0; +} + +int efi_set_variable( + sd_id128_t vendor, + const char *name, + const void *value, + size_t size) { + + struct var { + uint32_t attr; + char buf[]; + } _packed_ * _cleanup_free_ buf = NULL; + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -1; + + assert(name); + + if (asprintf(&p, + "/sys/firmware/efi/efivars/%s-%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + name, SD_ID128_FORMAT_VAL(vendor)) < 0) + return -ENOMEM; + + if (size == 0) { + if (unlink(p) < 0) + return -errno; + return 0; + } + + fd = open(p, O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0644); + if (fd < 0) + return -errno; + + buf = malloc(sizeof(uint32_t) + size); + if (!buf) + return -ENOMEM; + + buf->attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; + memcpy(buf->buf, value, size); + + return loop_write(fd, buf, sizeof(uint32_t) + size, false); +} + +int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) { + _cleanup_free_ void *s = NULL; + size_t ss = 0; + int r; + char *x; + + r = efi_get_variable(vendor, name, NULL, &s, &ss); + if (r < 0) + return r; + + x = utf16_to_utf8(s, ss); + if (!x) + return -ENOMEM; + + *p = x; + return 0; +} + +static size_t utf16_size(const uint16_t *s) { + size_t l = 0; + + while (s[l] > 0) + l++; + + return (l+1) * sizeof(uint16_t); +} + +static void efi_guid_to_id128(const void *guid, sd_id128_t *id128) { + struct uuid { + uint32_t u1; + uint16_t u2; + uint16_t u3; + uint8_t u4[8]; + } _packed_; + const struct uuid *uuid = guid; + + id128->bytes[0] = (uuid->u1 >> 24) & 0xff; + id128->bytes[1] = (uuid->u1 >> 16) & 0xff; + id128->bytes[2] = (uuid->u1 >> 8) & 0xff; + id128->bytes[3] = (uuid->u1) & 0xff; + id128->bytes[4] = (uuid->u2 >> 8) & 0xff; + id128->bytes[5] = (uuid->u2) & 0xff; + id128->bytes[6] = (uuid->u3 >> 8) & 0xff; + id128->bytes[7] = (uuid->u3) & 0xff; + memcpy(&id128->bytes[8], uuid->u4, sizeof(uuid->u4)); +} + +int efi_get_boot_option( + uint16_t id, + char **title, + sd_id128_t *part_uuid, + char **path, + bool *active) { + + char boot_id[9]; + _cleanup_free_ uint8_t *buf = NULL; + size_t l; + struct boot_option *header; + size_t title_size; + _cleanup_free_ char *s = NULL, *p = NULL; + sd_id128_t p_uuid = SD_ID128_NULL; + int r; + + xsprintf(boot_id, "Boot%04X", id); + r = efi_get_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, (void **)&buf, &l); + if (r < 0) + return r; + if (l < sizeof(struct boot_option)) + return -ENOENT; + + header = (struct boot_option *)buf; + title_size = utf16_size(header->title); + if (title_size > l - offsetof(struct boot_option, title)) + return -EINVAL; + + if (title) { + s = utf16_to_utf8(header->title, title_size); + if (!s) + return -ENOMEM; + } + + if (header->path_len > 0) { + uint8_t *dbuf; + size_t dnext; + + dbuf = buf + offsetof(struct boot_option, title) + title_size; + dnext = 0; + while (dnext < header->path_len) { + struct device_path *dpath; + + dpath = (struct device_path *)(dbuf + dnext); + if (dpath->length < 4) + break; + + /* Type 0x7F – End of Hardware Device Path, Sub-Type 0xFF – End Entire Device Path */ + if (dpath->type == END_DEVICE_PATH_TYPE && dpath->sub_type == END_ENTIRE_DEVICE_PATH_SUBTYPE) + break; + + dnext += dpath->length; + + /* Type 0x04 – Media Device Path */ + if (dpath->type != MEDIA_DEVICE_PATH) + continue; + + /* Sub-Type 1 – Hard Drive */ + if (dpath->sub_type == MEDIA_HARDDRIVE_DP) { + /* 0x02 – GUID Partition Table */ + if (dpath->drive.mbr_type != MBR_TYPE_EFI_PARTITION_TABLE_HEADER) + continue; + + /* 0x02 – GUID signature */ + if (dpath->drive.signature_type != SIGNATURE_TYPE_GUID) + continue; + + if (part_uuid) + efi_guid_to_id128(dpath->drive.signature, &p_uuid); + continue; + } + + /* Sub-Type 4 – File Path */ + if (dpath->sub_type == MEDIA_FILEPATH_DP && !p && path) { + p = utf16_to_utf8(dpath->path, dpath->length-4); + efi_tilt_backslashes(p); + continue; + } + } + } + + if (title) { + *title = s; + s = NULL; + } + if (part_uuid) + *part_uuid = p_uuid; + if (path) { + *path = p; + p = NULL; + } + if (active) + *active = !!(header->attr & LOAD_OPTION_ACTIVE); + + return 0; +} + +static void to_utf16(uint16_t *dest, const char *src) { + int i; + + for (i = 0; src[i] != '\0'; i++) + dest[i] = src[i]; + dest[i] = '\0'; +} + +struct guid { + uint32_t u1; + uint16_t u2; + uint16_t u3; + uint8_t u4[8]; +} _packed_; + +static void id128_to_efi_guid(sd_id128_t id, void *guid) { + struct guid *uuid = guid; + + uuid->u1 = id.bytes[0] << 24 | id.bytes[1] << 16 | id.bytes[2] << 8 | id.bytes[3]; + uuid->u2 = id.bytes[4] << 8 | id.bytes[5]; + uuid->u3 = id.bytes[6] << 8 | id.bytes[7]; + memcpy(uuid->u4, id.bytes+8, sizeof(uuid->u4)); +} + +static uint16_t *tilt_slashes(uint16_t *s) { + uint16_t *p; + + for (p = s; *p; p++) + if (*p == '/') + *p = '\\'; + + return s; +} + +int efi_add_boot_option(uint16_t id, const char *title, + uint32_t part, uint64_t pstart, uint64_t psize, + sd_id128_t part_uuid, const char *path) { + char boot_id[9]; + size_t size; + size_t title_len; + size_t path_len; + struct boot_option *option; + struct device_path *devicep; + _cleanup_free_ char *buf = NULL; + + title_len = (strlen(title)+1) * 2; + path_len = (strlen(path)+1) * 2; + + buf = calloc(sizeof(struct boot_option) + title_len + + sizeof(struct drive_path) + + sizeof(struct device_path) + path_len, 1); + if (!buf) + return -ENOMEM; + + /* header */ + option = (struct boot_option *)buf; + option->attr = LOAD_OPTION_ACTIVE; + option->path_len = offsetof(struct device_path, drive) + sizeof(struct drive_path) + + offsetof(struct device_path, path) + path_len + + offsetof(struct device_path, path); + to_utf16(option->title, title); + size = offsetof(struct boot_option, title) + title_len; + + /* partition info */ + devicep = (struct device_path *)(buf + size); + devicep->type = MEDIA_DEVICE_PATH; + devicep->sub_type = MEDIA_HARDDRIVE_DP; + devicep->length = offsetof(struct device_path, drive) + sizeof(struct drive_path); + devicep->drive.part_nr = part; + devicep->drive.part_start = pstart; + devicep->drive.part_size = psize; + devicep->drive.signature_type = SIGNATURE_TYPE_GUID; + devicep->drive.mbr_type = MBR_TYPE_EFI_PARTITION_TABLE_HEADER; + id128_to_efi_guid(part_uuid, devicep->drive.signature); + size += devicep->length; + + /* path to loader */ + devicep = (struct device_path *)(buf + size); + devicep->type = MEDIA_DEVICE_PATH; + devicep->sub_type = MEDIA_FILEPATH_DP; + devicep->length = offsetof(struct device_path, path) + path_len; + to_utf16(devicep->path, path); + tilt_slashes(devicep->path); + size += devicep->length; + + /* end of path */ + devicep = (struct device_path *)(buf + size); + devicep->type = END_DEVICE_PATH_TYPE; + devicep->sub_type = END_ENTIRE_DEVICE_PATH_SUBTYPE; + devicep->length = offsetof(struct device_path, path); + size += devicep->length; + + xsprintf(boot_id, "Boot%04X", id); + return efi_set_variable(EFI_VENDOR_GLOBAL, boot_id, buf, size); +} + +int efi_remove_boot_option(uint16_t id) { + char boot_id[9]; + + xsprintf(boot_id, "Boot%04X", id); + return efi_set_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, 0); +} + +int efi_get_boot_order(uint16_t **order) { + _cleanup_free_ void *buf = NULL; + size_t l; + int r; + + r = efi_get_variable(EFI_VENDOR_GLOBAL, "BootOrder", NULL, &buf, &l); + if (r < 0) + return r; + + if (l <= 0) + return -ENOENT; + + if (l % sizeof(uint16_t) > 0 || + l / sizeof(uint16_t) > INT_MAX) + return -EINVAL; + + *order = buf; + buf = NULL; + return (int) (l / sizeof(uint16_t)); +} + +int efi_set_boot_order(uint16_t *order, size_t n) { + return efi_set_variable(EFI_VENDOR_GLOBAL, "BootOrder", order, n * sizeof(uint16_t)); +} + +static int boot_id_hex(const char s[4]) { + int i; + int id = 0; + + for (i = 0; i < 4; i++) + if (s[i] >= '0' && s[i] <= '9') + id |= (s[i] - '0') << (3 - i) * 4; + else if (s[i] >= 'A' && s[i] <= 'F') + id |= (s[i] - 'A' + 10) << (3 - i) * 4; + else + return -EINVAL; + + return id; +} + +static int cmp_uint16(const void *_a, const void *_b) { + const uint16_t *a = _a, *b = _b; + + return (int)*a - (int)*b; +} + +int efi_get_boot_options(uint16_t **options) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *de; + _cleanup_free_ uint16_t *list = NULL; + size_t alloc = 0; + int count = 0; + + assert(options); + + dir = opendir("/sys/firmware/efi/efivars/"); + if (!dir) + return -errno; + + FOREACH_DIRENT(de, dir, return -errno) { + int id; + + if (strncmp(de->d_name, "Boot", 4) != 0) + continue; + + if (strlen(de->d_name) != 45) + continue; + + if (strcmp(de->d_name + 8, "-8be4df61-93ca-11d2-aa0d-00e098032b8c") != 0) + continue; + + id = boot_id_hex(de->d_name + 4); + if (id < 0) + continue; + + if (!GREEDY_REALLOC(list, alloc, count + 1)) + return -ENOMEM; + + list[count++] = id; + } + + qsort_safe(list, count, sizeof(uint16_t), cmp_uint16); + + *options = list; + list = NULL; + return count; +} + +static int read_usec(sd_id128_t vendor, const char *name, usec_t *u) { + _cleanup_free_ char *j = NULL; + int r; + uint64_t x = 0; + + assert(name); + assert(u); + + r = efi_get_variable_string(EFI_VENDOR_LOADER, name, &j); + if (r < 0) + return r; + + r = safe_atou64(j, &x); + if (r < 0) + return r; + + *u = x; + return 0; +} + +int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) { + uint64_t x, y; + int r; + + assert(firmware); + assert(loader); + + r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeInitUSec", &x); + if (r < 0) + return r; + + r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeExecUSec", &y); + if (r < 0) + return r; + + if (y == 0 || y < x) + return -EIO; + + if (y > USEC_PER_HOUR) + return -EIO; + + *firmware = x; + *loader = y; + + return 0; +} + +int efi_loader_get_device_part_uuid(sd_id128_t *u) { + _cleanup_free_ char *p = NULL; + int r, parsed[16]; + + r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderDevicePartUUID", &p); + if (r < 0) + return r; + + if (sscanf(p, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + &parsed[0], &parsed[1], &parsed[2], &parsed[3], + &parsed[4], &parsed[5], &parsed[6], &parsed[7], + &parsed[8], &parsed[9], &parsed[10], &parsed[11], + &parsed[12], &parsed[13], &parsed[14], &parsed[15]) != 16) + return -EIO; + + if (u) { + unsigned i; + + for (i = 0; i < ELEMENTSOF(parsed); i++) + u->bytes[i] = parsed[i]; + } + + return 0; +} + +#endif + +char *efi_tilt_backslashes(char *s) { + char *p; + + for (p = s; *p; p++) + if (*p == '\\') + *p = '/'; + + return s; +} diff --git a/src/libshared/efivars.h b/src/libshared/efivars.h new file mode 100644 index 0000000000..243151f922 --- /dev/null +++ b/src/libshared/efivars.h @@ -0,0 +1,131 @@ +#pragma once + +/*** + 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 . +***/ + +#include +#include +#include + +#include + +#include "time-util.h" + +#define EFI_VENDOR_LOADER SD_ID128_MAKE(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f) +#define EFI_VENDOR_GLOBAL SD_ID128_MAKE(8b,e4,df,61,93,ca,11,d2,aa,0d,00,e0,98,03,2b,8c) +#define EFI_VARIABLE_NON_VOLATILE 0x0000000000000001 +#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x0000000000000002 +#define EFI_VARIABLE_RUNTIME_ACCESS 0x0000000000000004 + +#ifdef ENABLE_EFI + +bool is_efi_boot(void); +bool is_efi_secure_boot(void); +bool is_efi_secure_boot_setup_mode(void); +int efi_reboot_to_firmware_supported(void); +int efi_get_reboot_to_firmware(void); +int efi_set_reboot_to_firmware(bool value); + +int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size); +int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size); +int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p); + +int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *part_uuid, char **path, bool *active); +int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path); +int efi_remove_boot_option(uint16_t id); +int efi_get_boot_order(uint16_t **order); +int efi_set_boot_order(uint16_t *order, size_t n); +int efi_get_boot_options(uint16_t **options); + +int efi_loader_get_device_part_uuid(sd_id128_t *u); +int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader); + +#else + +static inline bool is_efi_boot(void) { + return false; +} + +static inline bool is_efi_secure_boot(void) { + return false; +} + +static inline bool is_efi_secure_boot_setup_mode(void) { + return false; +} + +static inline int efi_reboot_to_firmware_supported(void) { + return -EOPNOTSUPP; +} + +static inline int efi_get_reboot_to_firmware(void) { + return -EOPNOTSUPP; +} + +static inline int efi_set_reboot_to_firmware(bool value) { + return -EOPNOTSUPP; +} + +static inline int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size) { + return -EOPNOTSUPP; +} + +static inline int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size) { + return -EOPNOTSUPP; +} + +static inline int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) { + return -EOPNOTSUPP; +} + +static inline int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *part_uuid, char **path, bool *active) { + return -EOPNOTSUPP; +} + +static inline int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path) { + return -EOPNOTSUPP; +} + +static inline int efi_remove_boot_option(uint16_t id) { + return -EOPNOTSUPP; +} + +static inline int efi_get_boot_order(uint16_t **order) { + return -EOPNOTSUPP; +} + +static inline int efi_set_boot_order(uint16_t *order, size_t n) { + return -EOPNOTSUPP; +} + +static inline int efi_get_boot_options(uint16_t **options) { + return -EOPNOTSUPP; +} + +static inline int efi_loader_get_device_part_uuid(sd_id128_t *u) { + return -EOPNOTSUPP; +} + +static inline int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) { + return -EOPNOTSUPP; +} + +#endif + +char *efi_tilt_backslashes(char *s); diff --git a/src/libshared/fstab-util.c b/src/libshared/fstab-util.c new file mode 100644 index 0000000000..a4e0cd3267 --- /dev/null +++ b/src/libshared/fstab-util.c @@ -0,0 +1,263 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "device-nodes.h" +#include "fstab-util.h" +#include "macro.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +bool fstab_is_mount_point(const char *mount) { + _cleanup_endmntent_ FILE *f = NULL; + struct mntent *m; + + f = setmntent("/etc/fstab", "r"); + if (!f) + return false; + + while ((m = getmntent(f))) + if (path_equal(m->mnt_dir, mount)) + return true; + + return false; +} + +int fstab_filter_options(const char *opts, const char *names, + const char **namefound, char **value, char **filtered) { + const char *name, *n = NULL, *x; + _cleanup_strv_free_ char **stor = NULL; + _cleanup_free_ char *v = NULL, **strv = NULL; + + assert(names && *names); + + if (!opts) + goto answer; + + /* If !value and !filtered, this function is not allowed to fail. */ + + if (!filtered) { + const char *word, *state; + size_t l; + + FOREACH_WORD_SEPARATOR(word, l, opts, ",", state) + NULSTR_FOREACH(name, names) { + if (l < strlen(name)) + continue; + if (!strneq(word, name, strlen(name))) + continue; + + /* we know that the string is NUL + * terminated, so *x is valid */ + x = word + strlen(name); + if (IN_SET(*x, '\0', '=', ',')) { + n = name; + if (value) { + free(v); + if (IN_SET(*x, '\0', ',')) + v = NULL; + else { + assert(*x == '='); + x++; + v = strndup(x, l - strlen(name) - 1); + if (!v) + return -ENOMEM; + } + } + } + } + } else { + char **t, **s; + + stor = strv_split(opts, ","); + if (!stor) + return -ENOMEM; + strv = memdup(stor, sizeof(char*) * (strv_length(stor) + 1)); + if (!strv) + return -ENOMEM; + + for (s = t = strv; *s; s++) { + NULSTR_FOREACH(name, names) { + x = startswith(*s, name); + if (x && IN_SET(*x, '\0', '=')) + goto found; + } + + *t = *s; + t++; + continue; + found: + /* Keep the last occurence found */ + n = name; + if (value) { + free(v); + if (*x == '\0') + v = NULL; + else { + assert(*x == '='); + x++; + v = strdup(x); + if (!v) + return -ENOMEM; + } + } + } + *t = NULL; + } + +answer: + if (namefound) + *namefound = n; + if (filtered) { + char *f; + + f = strv_join(strv, ","); + if (!f) + return -ENOMEM; + + *filtered = f; + } + if (value) { + *value = v; + v = NULL; + } + + return !!n; +} + +int fstab_extract_values(const char *opts, const char *name, char ***values) { + _cleanup_strv_free_ char **optsv = NULL, **res = NULL; + char **s; + + assert(opts); + assert(name); + assert(values); + + optsv = strv_split(opts, ","); + if (!optsv) + return -ENOMEM; + + STRV_FOREACH(s, optsv) { + char *arg; + int r; + + arg = startswith(*s, name); + if (!arg || *arg != '=') + continue; + r = strv_extend(&res, arg + 1); + if (r < 0) + return r; + } + + *values = res; + res = NULL; + + return !!*values; +} + +int fstab_find_pri(const char *options, int *ret) { + _cleanup_free_ char *opt = NULL; + int r; + unsigned pri; + + assert(ret); + + r = fstab_filter_options(options, "pri\0", NULL, &opt, NULL); + if (r < 0) + return r; + if (r == 0 || !opt) + return 0; + + r = safe_atou(opt, &pri); + if (r < 0) + return r; + + if ((int) pri < 0) + return -ERANGE; + + *ret = (int) pri; + return 1; +} + +static char *unquote(const char *s, const char* quotes) { + size_t l; + assert(s); + + /* This is rather stupid, simply removes the heading and + * trailing quotes if there is one. Doesn't care about + * escaping or anything. + * + * DON'T USE THIS FOR NEW CODE ANYMORE!*/ + + l = strlen(s); + if (l < 2) + return strdup(s); + + if (strchr(quotes, s[0]) && s[l-1] == s[0]) + return strndup(s+1, l-2); + + return strdup(s); +} + +static char *tag_to_udev_node(const char *tagvalue, const char *by) { + _cleanup_free_ char *t = NULL, *u = NULL; + size_t enc_len; + + u = unquote(tagvalue, QUOTES); + if (!u) + return NULL; + + enc_len = strlen(u) * 4 + 1; + t = new(char, enc_len); + if (!t) + return NULL; + + if (encode_devnode_name(u, t, enc_len) < 0) + return NULL; + + return strjoin("/dev/disk/by-", by, "/", t, NULL); +} + +char *fstab_node_to_udev_node(const char *p) { + assert(p); + + if (startswith(p, "LABEL=")) + return tag_to_udev_node(p+6, "label"); + + if (startswith(p, "UUID=")) + return tag_to_udev_node(p+5, "uuid"); + + if (startswith(p, "PARTUUID=")) + return tag_to_udev_node(p+9, "partuuid"); + + if (startswith(p, "PARTLABEL=")) + return tag_to_udev_node(p+10, "partlabel"); + + return strdup(p); +} diff --git a/src/libshared/fstab-util.h b/src/libshared/fstab-util.h new file mode 100644 index 0000000000..679f6902f7 --- /dev/null +++ b/src/libshared/fstab-util.h @@ -0,0 +1,52 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include + +#include "macro.h" + +bool fstab_is_mount_point(const char *mount); + +int fstab_filter_options(const char *opts, const char *names, const char **namefound, char **value, char **filtered); + +int fstab_extract_values(const char *opts, const char *name, char ***values); + +static inline bool fstab_test_option(const char *opts, const char *names) { + return !!fstab_filter_options(opts, names, NULL, NULL, NULL); +} + +int fstab_find_pri(const char *options, int *ret); + +static inline bool fstab_test_yes_no_option(const char *opts, const char *yes_no) { + int r; + const char *opt; + + /* If first name given is last, return 1. + * If second name given is last or neither is found, return 0. */ + + r = fstab_filter_options(opts, yes_no, &opt, NULL, NULL); + assert(r >= 0); + + return opt == yes_no; +} + +char *fstab_node_to_udev_node(const char *p); diff --git a/src/libshared/generator.c b/src/libshared/generator.c new file mode 100644 index 0000000000..cd3c35cd55 --- /dev/null +++ b/src/libshared/generator.c @@ -0,0 +1,193 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include + +#include "alloc-util.h" +#include "dropin.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "fstab-util.h" +#include "generator.h" +#include "log.h" +#include "macro.h" +#include "mkdir.h" +#include "path-util.h" +#include "special.h" +#include "string-util.h" +#include "time-util.h" +#include "unit-name.h" +#include "util.h" + +static int write_fsck_sysroot_service(const char *dir, const char *what) { + _cleanup_free_ char *device = NULL, *escaped = NULL; + _cleanup_fclose_ FILE *f = NULL; + const char *unit; + int r; + + escaped = cescape(what); + if (!escaped) + return log_oom(); + + unit = strjoina(dir, "/systemd-fsck-root.service"); + log_debug("Creating %s", unit); + + r = unit_name_from_path(what, ".device", &device); + if (r < 0) + return log_error_errno(r, "Failed to convert device \"%s\" to unit name: %m", what); + + f = fopen(unit, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", unit); + + fprintf(f, + "# Automatically generated by %1$s\n\n" + "[Unit]\n" + "Documentation=man:systemd-fsck-root.service(8)\n" + "Description=File System Check on %2$s\n" + "DefaultDependencies=no\n" + "BindsTo=%3$s\n" + "After=%3$s local-fs-pre.target\n" + "Before=shutdown.target\n" + "\n" + "[Service]\n" + "Type=oneshot\n" + "RemainAfterExit=yes\n" + "ExecStart=" SYSTEMD_FSCK_PATH " %4$s\n" + "TimeoutSec=0\n", + program_invocation_short_name, + what, + device, + escaped); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write unit file %s: %m", unit); + + return 0; +} + +int generator_write_fsck_deps( + FILE *f, + const char *dir, + const char *what, + const char *where, + const char *fstype) { + + int r; + + assert(f); + assert(dir); + assert(what); + assert(where); + + if (!is_device_path(what)) { + log_warning("Checking was requested for \"%s\", but it is not a device.", what); + return 0; + } + + if (!isempty(fstype) && !streq(fstype, "auto")) { + r = fsck_exists(fstype); + if (r < 0) + log_warning_errno(r, "Checking was requested for %s, but couldn't detect if fsck.%s may be used, proceeding: %m", what, fstype); + else if (r == 0) { + /* treat missing check as essentially OK */ + log_debug("Checking was requested for %s, but fsck.%s does not exist.", what, fstype); + return 0; + } + } + + if (path_equal(where, "/")) { + const char *lnk; + + lnk = strjoina(dir, "/" SPECIAL_LOCAL_FS_TARGET ".wants/systemd-fsck-root.service"); + + mkdir_parents(lnk, 0755); + if (symlink(SYSTEM_DATA_UNIT_PATH "/systemd-fsck-root.service", lnk) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); + + } else { + _cleanup_free_ char *_fsck = NULL; + const char *fsck; + + if (in_initrd() && path_equal(where, "/sysroot")) { + r = write_fsck_sysroot_service(dir, what); + if (r < 0) + return r; + + fsck = "systemd-fsck-root.service"; + } else { + r = unit_name_from_path_instance("systemd-fsck", what, ".service", &_fsck); + if (r < 0) + return log_error_errno(r, "Failed to create fsck service name: %m"); + + fsck = _fsck; + } + + fprintf(f, + "Requires=%1$s\n" + "After=%1$s\n", + fsck); + } + + return 0; +} + +int generator_write_timeouts( + const char *dir, + const char *what, + const char *where, + const char *opts, + char **filtered) { + + /* Allow configuration how long we wait for a device that + * backs a mount point to show up. This is useful to support + * endless device timeouts for devices that show up only after + * user input, like crypto devices. */ + + _cleanup_free_ char *node = NULL, *unit = NULL, *timeout = NULL; + usec_t u; + int r; + + r = fstab_filter_options(opts, "comment=systemd.device-timeout\0" "x-systemd.device-timeout\0", + NULL, &timeout, filtered); + if (r <= 0) + return r; + + r = parse_sec(timeout, &u); + if (r < 0) { + log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout); + return 0; + } + + node = fstab_node_to_udev_node(what); + if (!node) + return log_oom(); + + r = unit_name_from_path(node, ".device", &unit); + if (r < 0) + return log_error_errno(r, "Failed to make unit name from path: %m"); + + return write_drop_in_format(dir, unit, 50, "device-timeout", + "# Automatically generated by %s\n\n" + "[Unit]\nJobTimeoutSec=%s", + program_invocation_short_name, timeout); +} diff --git a/src/libshared/generator.h b/src/libshared/generator.h new file mode 100644 index 0000000000..a734e13970 --- /dev/null +++ b/src/libshared/generator.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +int generator_write_fsck_deps( + FILE *f, + const char *dir, + const char *what, + const char *where, + const char *type); + +int generator_write_timeouts( + const char *dir, + const char *what, + const char *where, + const char *opts, + char **filtered); diff --git a/src/libshared/gpt.h b/src/libshared/gpt.h new file mode 100644 index 0000000000..9b8f59abee --- /dev/null +++ b/src/libshared/gpt.h @@ -0,0 +1,66 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#pragma once + +#include + +#include + +/* We only support root disk discovery for x86, x86-64, Itanium and ARM for + * now, since EFI for anything else doesn't really exist, and we only + * care for root partitions on the same disk as the EFI ESP. */ + +#define GPT_ROOT_X86 SD_ID128_MAKE(44,47,95,40,f2,97,41,b2,9a,f7,d1,31,d5,f0,45,8a) +#define GPT_ROOT_X86_64 SD_ID128_MAKE(4f,68,bc,e3,e8,cd,4d,b1,96,e7,fb,ca,f9,84,b7,09) +#define GPT_ROOT_ARM SD_ID128_MAKE(69,da,d7,10,2c,e4,4e,3c,b1,6c,21,a1,d4,9a,be,d3) +#define GPT_ROOT_ARM_64 SD_ID128_MAKE(b9,21,b0,45,1d,f0,41,c3,af,44,4c,6f,28,0d,3f,ae) +#define GPT_ROOT_IA64 SD_ID128_MAKE(99,3d,8d,3d,f8,0e,42,25,85,5a,9d,af,8e,d7,ea,97) + +#define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b) +#define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f) +#define GPT_HOME SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15) +#define GPT_SRV SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8) + +#if defined(__x86_64__) +# define GPT_ROOT_NATIVE GPT_ROOT_X86_64 +# define GPT_ROOT_SECONDARY GPT_ROOT_X86 +#elif defined(__i386__) +# define GPT_ROOT_NATIVE GPT_ROOT_X86 +#endif + +#if defined(__ia64__) +# define GPT_ROOT_NATIVE GPT_ROOT_IA64 +#endif + +#if defined(__aarch64__) && (__BYTE_ORDER != __BIG_ENDIAN) +# define GPT_ROOT_NATIVE GPT_ROOT_ARM_64 +# define GPT_ROOT_SECONDARY GPT_ROOT_ARM +#elif defined(__arm__) && (__BYTE_ORDER != __BIG_ENDIAN) +# define GPT_ROOT_NATIVE GPT_ROOT_ARM +#endif + +/* Flags we recognize on the root, swap, home and srv partitions when + * doing auto-discovery. These happen to be identical to what + * Microsoft defines for its own Basic Data Partitions, but that's + * just because we saw no point in defining any other values here. */ +#define GPT_FLAG_READ_ONLY (1ULL << 60) +#define GPT_FLAG_NO_AUTO (1ULL << 63) + +#define GPT_LINUX_GENERIC SD_ID128_MAKE(0f,c6,3d,af,84,83,47,72,8e,79,3d,69,d8,47,7d,e4) diff --git a/src/libshared/ima-util.c b/src/libshared/ima-util.c new file mode 100644 index 0000000000..789064d653 --- /dev/null +++ b/src/libshared/ima-util.c @@ -0,0 +1,32 @@ +/*** + 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 . +***/ + +#include + +#include "ima-util.h" + +static int use_ima_cached = -1; + +bool use_ima(void) { + + if (use_ima_cached < 0) + use_ima_cached = access("/sys/kernel/security/ima/", F_OK) >= 0; + + return use_ima_cached; +} diff --git a/src/libshared/ima-util.h b/src/libshared/ima-util.h new file mode 100644 index 0000000000..5be94761fd --- /dev/null +++ b/src/libshared/ima-util.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +bool use_ima(void); diff --git a/src/libshared/import-util.c b/src/libshared/import-util.c new file mode 100644 index 0000000000..ab701ad8b2 --- /dev/null +++ b/src/libshared/import-util.c @@ -0,0 +1,185 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include + +#include "alloc-util.h" +#include "btrfs-util.h" +#include "import-util.h" +#include "log.h" +#include "macro.h" +#include "path-util.h" +#include "string-table.h" +#include "string-util.h" +#include "util.h" + +int import_url_last_component(const char *url, char **ret) { + const char *e, *p; + char *s; + + e = strchrnul(url, '?'); + + while (e > url && e[-1] == '/') + e--; + + p = e; + while (p > url && p[-1] != '/') + p--; + + if (e <= p) + return -EINVAL; + + s = strndup(p, e - p); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + + +int import_url_change_last_component(const char *url, const char *suffix, char **ret) { + const char *e; + char *s; + + assert(url); + assert(ret); + + e = strchrnul(url, '?'); + + while (e > url && e[-1] == '/') + e--; + + while (e > url && e[-1] != '/') + e--; + + if (e <= url) + return -EINVAL; + + s = new(char, (e - url) + strlen(suffix) + 1); + if (!s) + return -ENOMEM; + + strcpy(mempcpy(s, url, e - url), suffix); + *ret = s; + return 0; +} + +static const char* const import_verify_table[_IMPORT_VERIFY_MAX] = { + [IMPORT_VERIFY_NO] = "no", + [IMPORT_VERIFY_CHECKSUM] = "checksum", + [IMPORT_VERIFY_SIGNATURE] = "signature", +}; + +DEFINE_STRING_TABLE_LOOKUP(import_verify, ImportVerify); + +int tar_strip_suffixes(const char *name, char **ret) { + const char *e; + char *s; + + e = endswith(name, ".tar"); + if (!e) + e = endswith(name, ".tar.xz"); + if (!e) + e = endswith(name, ".tar.gz"); + if (!e) + e = endswith(name, ".tar.bz2"); + if (!e) + e = endswith(name, ".tgz"); + if (!e) + e = strchr(name, 0); + + if (e <= name) + return -EINVAL; + + s = strndup(name, e - name); + if (!s) + return -ENOMEM; + + *ret = s; + return 0; +} + +int raw_strip_suffixes(const char *p, char **ret) { + + static const char suffixes[] = + ".xz\0" + ".gz\0" + ".bz2\0" + ".raw\0" + ".qcow2\0" + ".img\0" + ".bin\0"; + + _cleanup_free_ char *q = NULL; + + q = strdup(p); + if (!q) + return -ENOMEM; + + for (;;) { + const char *sfx; + bool changed = false; + + NULSTR_FOREACH(sfx, suffixes) { + char *e; + + e = endswith(q, sfx); + if (e) { + *e = 0; + changed = true; + } + } + + if (!changed) + break; + } + + *ret = q; + q = NULL; + + return 0; +} + +int import_assign_pool_quota_and_warn(const char *path) { + int r; + + r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true); + if (r == -ENOTTY) { + log_debug_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, as directory is not on btrfs or not a subvolume. Ignoring."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines: %m"); + if (r > 0) + log_info("Set up default quota hierarchy for /var/lib/machines."); + + r = btrfs_subvol_auto_qgroup(path, 0, true); + if (r == -ENOTTY) { + log_debug_errno(r, "Failed to set up quota hierarchy for %s, as directory is not on btrfs or not a subvolume. Ignoring.", path); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to set up default quota hierarchy for %s: %m", path); + if (r > 0) + log_info("Set up default quota hierarchy for %s.", path); + + return 0; +} diff --git a/src/libshared/import-util.h b/src/libshared/import-util.h new file mode 100644 index 0000000000..77b17d91f3 --- /dev/null +++ b/src/libshared/import-util.h @@ -0,0 +1,43 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include + +#include "macro.h" + +typedef enum ImportVerify { + IMPORT_VERIFY_NO, + IMPORT_VERIFY_CHECKSUM, + IMPORT_VERIFY_SIGNATURE, + _IMPORT_VERIFY_MAX, + _IMPORT_VERIFY_INVALID = -1, +} ImportVerify; + +int import_url_last_component(const char *url, char **ret); +int import_url_change_last_component(const char *url, const char *suffix, char **ret); + +const char* import_verify_to_string(ImportVerify v) _const_; +ImportVerify import_verify_from_string(const char *s) _pure_; + +int tar_strip_suffixes(const char *name, char **ret); +int raw_strip_suffixes(const char *name, char **ret); + +int import_assign_pool_quota_and_warn(const char *path); diff --git a/src/libshared/initreq.h b/src/libshared/initreq.h new file mode 100644 index 0000000000..710037d84b --- /dev/null +++ b/src/libshared/initreq.h @@ -0,0 +1,77 @@ +/* + * initreq.h Interface to talk to init through /dev/initctl. + * + * Copyright (C) 1995-2004 Miquel van Smoorenburg + * + * This library 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 of the License, or (at your option) any later version. + * + * Version: @(#)initreq.h 1.28 31-Mar-2004 MvS + * + */ +#ifndef _INITREQ_H +#define _INITREQ_H + +#include + +#if defined(__FreeBSD_kernel__) +# define INIT_FIFO "/etc/.initctl" +#else +# define INIT_FIFO "/dev/initctl" +#endif + +#define INIT_MAGIC 0x03091969 +#define INIT_CMD_START 0 +#define INIT_CMD_RUNLVL 1 +#define INIT_CMD_POWERFAIL 2 +#define INIT_CMD_POWERFAILNOW 3 +#define INIT_CMD_POWEROK 4 +#define INIT_CMD_BSD 5 +#define INIT_CMD_SETENV 6 +#define INIT_CMD_UNSETENV 7 + +#define INIT_CMD_CHANGECONS 12345 + +#ifdef MAXHOSTNAMELEN +# define INITRQ_HLEN MAXHOSTNAMELEN +#else +# define INITRQ_HLEN 64 +#endif + +/* + * This is what BSD 4.4 uses when talking to init. + * Linux doesn't use this right now. + */ +struct init_request_bsd { + char gen_id[8]; /* Beats me.. telnetd uses "fe" */ + char tty_id[16]; /* Tty name minus /dev/tty */ + char host[INITRQ_HLEN]; /* Hostname */ + char term_type[16]; /* Terminal type */ + int signal; /* Signal to send */ + int pid; /* Process to send to */ + char exec_name[128]; /* Program to execute */ + char reserved[128]; /* For future expansion. */ +}; + + +/* + * Because of legacy interfaces, "runlevel" and "sleeptime" + * aren't in a separate struct in the union. + * + * The weird sizes are because init expects the whole + * struct to be 384 bytes. + */ +struct init_request { + int magic; /* Magic number */ + int cmd; /* What kind of request */ + int runlevel; /* Runlevel to change to */ + int sleeptime; /* Time between TERM and KILL */ + union { + struct init_request_bsd bsd; + char data[368]; + } i; +}; + +#endif diff --git a/src/libshared/install-printf.c b/src/libshared/install-printf.c new file mode 100644 index 0000000000..88143361da --- /dev/null +++ b/src/libshared/install-printf.c @@ -0,0 +1,133 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#include +#include +#include +#include + +#include "formats-util.h" +#include "install-printf.h" +#include "install.h" +#include "macro.h" +#include "specifier.h" +#include "unit-name.h" +#include "user-util.h" + +static int specifier_prefix_and_instance(char specifier, void *data, void *userdata, char **ret) { + UnitFileInstallInfo *i = userdata; + + assert(i); + + return unit_name_to_prefix_and_instance(i->name, ret); +} + +static int specifier_prefix(char specifier, void *data, void *userdata, char **ret) { + UnitFileInstallInfo *i = userdata; + + assert(i); + + return unit_name_to_prefix(i->name, ret); +} + +static int specifier_instance(char specifier, void *data, void *userdata, char **ret) { + UnitFileInstallInfo *i = userdata; + char *instance; + int r; + + assert(i); + + r = unit_name_to_instance(i->name, &instance); + if (r < 0) + return r; + + if (!instance) { + instance = strdup(""); + if (!instance) + return -ENOMEM; + } + + *ret = instance; + return 0; +} + +static int specifier_user_name(char specifier, void *data, void *userdata, char **ret) { + char *t; + + /* If we are UID 0 (root), this will not result in NSS, + * otherwise it might. This is good, as we want to be able to + * run this in PID 1, where our user ID is 0, but where NSS + * lookups are not allowed. */ + + t = getusername_malloc(); + if (!t) + return -ENOMEM; + + *ret = t; + return 0; +} + +static int specifier_user_id(char specifier, void *data, void *userdata, char **ret) { + + if (asprintf(ret, UID_FMT, getuid()) < 0) + return -ENOMEM; + + return 0; +} + +int install_full_printf(UnitFileInstallInfo *i, const char *format, char **ret) { + + /* This is similar to unit_full_printf() but does not support + * anything path-related. + * + * %n: the full id of the unit (foo@bar.waldo) + * %N: the id of the unit without the suffix (foo@bar) + * %p: the prefix (foo) + * %i: the instance (bar) + + * %U the UID of the running user + * %u the username of running user + * %m the machine ID of the running system + * %H the host name of the running system + * %b the boot ID of the running system + * %v `uname -r` of the running system + */ + + const Specifier table[] = { + { 'n', specifier_string, i->name }, + { 'N', specifier_prefix_and_instance, NULL }, + { 'p', specifier_prefix, NULL }, + { 'i', specifier_instance, NULL }, + + { 'U', specifier_user_id, NULL }, + { 'u', specifier_user_name, NULL }, + + { 'm', specifier_machine_id, NULL }, + { 'H', specifier_host_name, NULL }, + { 'b', specifier_boot_id, NULL }, + { 'v', specifier_kernel_release, NULL }, + {} + }; + + assert(i); + assert(format); + assert(ret); + + return specifier_printf(format, table, i, ret); +} diff --git a/src/libshared/install-printf.h b/src/libshared/install-printf.h new file mode 100644 index 0000000000..acf519f4f7 --- /dev/null +++ b/src/libshared/install-printf.h @@ -0,0 +1,24 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#pragma once + +#include "install.h" + +int install_full_printf(UnitFileInstallInfo *i, const char *format, char **ret); diff --git a/src/libshared/install.c b/src/libshared/install.c new file mode 100644 index 0000000000..ef8f485cae --- /dev/null +++ b/src/libshared/install.c @@ -0,0 +1,2474 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "conf-files.h" +#include "conf-parser.h" +#include "dirent-util.h" +#include "extract-word.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hashmap.h" +#include "install-printf.h" +#include "install.h" +#include "log.h" +#include "macro.h" +#include "mkdir.h" +#include "path-lookup.h" +#include "path-util.h" +#include "set.h" +#include "special.h" +#include "stat-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" + +#define UNIT_FILE_FOLLOW_SYMLINK_MAX 64 + +typedef enum SearchFlags { + SEARCH_LOAD = 1, + SEARCH_FOLLOW_CONFIG_SYMLINKS = 2, +} SearchFlags; + +typedef struct { + OrderedHashmap *will_process; + OrderedHashmap *have_processed; +} InstallContext; + +static int in_search_path(const char *path, char **search) { + _cleanup_free_ char *parent = NULL; + char **i; + + assert(path); + + parent = dirname_malloc(path); + if (!parent) + return -ENOMEM; + + STRV_FOREACH(i, search) + if (path_equal(parent, *i)) + return true; + + return false; +} + +static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) { + char *p = NULL; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(ret); + + /* This determines where we shall create or remove our + * installation ("configuration") symlinks */ + + switch (scope) { + + case UNIT_FILE_SYSTEM: + + if (runtime) + p = path_join(root_dir, "/run/systemd/system", NULL); + else + p = path_join(root_dir, SYSTEM_CONFIG_UNIT_PATH, NULL); + break; + + case UNIT_FILE_GLOBAL: + + if (root_dir) + return -EINVAL; + + if (runtime) + p = strdup("/run/systemd/user"); + else + p = strdup(USER_CONFIG_UNIT_PATH); + break; + + case UNIT_FILE_USER: + + if (root_dir) + return -EINVAL; + + if (runtime) + r = user_runtime_dir(&p); + else + r = user_config_home(&p); + if (r < 0) + return r; + if (r == 0) + return -ENOENT; + + break; + + default: + assert_not_reached("Bad scope"); + } + + if (!p) + return -ENOMEM; + + *ret = p; + return 0; +} + +static bool is_config_path(UnitFileScope scope, const char *path) { + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(path); + + /* Checks whether the specified path is intended for + * configuration or is outside of it */ + + switch (scope) { + + case UNIT_FILE_SYSTEM: + case UNIT_FILE_GLOBAL: + return path_startswith(path, "/etc") || + path_startswith(path, SYSTEM_CONFIG_UNIT_PATH) || + path_startswith(path, "/run"); + + + case UNIT_FILE_USER: { + _cleanup_free_ char *p = NULL; + + r = user_config_home(&p); + if (r < 0) + return r; + if (r > 0 && path_startswith(path, p)) + return true; + + p = mfree(p); + + r = user_runtime_dir(&p); + if (r < 0) + return r; + if (r > 0 && path_startswith(path, p)) + return true; + + return false; + } + + default: + assert_not_reached("Bad scope"); + } +} + + +static int verify_root_dir(UnitFileScope scope, const char **root_dir) { + int r; + + assert(root_dir); + + /* Verifies that the specified root directory to operate on + * makes sense. Reset it to NULL if it is the root directory + * or set to empty */ + + if (isempty(*root_dir) || path_equal(*root_dir, "/")) { + *root_dir = NULL; + return 0; + } + + if (scope != UNIT_FILE_SYSTEM) + return -EINVAL; + + r = is_dir(*root_dir, true); + if (r < 0) + return r; + if (r == 0) + return -ENOTDIR; + + return 0; +} + +int unit_file_changes_add( + UnitFileChange **changes, + unsigned *n_changes, + UnitFileChangeType type, + const char *path, + const char *source) { + + UnitFileChange *c; + unsigned i; + + assert(path); + assert(!changes == !n_changes); + + if (!changes) + return 0; + + c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange)); + if (!c) + return -ENOMEM; + + *changes = c; + i = *n_changes; + + c[i].type = type; + c[i].path = strdup(path); + if (!c[i].path) + return -ENOMEM; + + path_kill_slashes(c[i].path); + + if (source) { + c[i].source = strdup(source); + if (!c[i].source) { + free(c[i].path); + return -ENOMEM; + } + + path_kill_slashes(c[i].path); + } else + c[i].source = NULL; + + *n_changes = i+1; + return 0; +} + +void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) { + unsigned i; + + assert(changes || n_changes == 0); + + if (!changes) + return; + + for (i = 0; i < n_changes; i++) { + free(changes[i].path); + free(changes[i].source); + } + + free(changes); +} + +static int create_symlink( + const char *old_path, + const char *new_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_free_ char *dest = NULL; + int r; + + assert(old_path); + assert(new_path); + + /* Actually create a symlink, and remember that we did. Is + * smart enough to check if there's already a valid symlink in + * place. */ + + mkdir_parents_label(new_path, 0755); + + if (symlink(old_path, new_path) >= 0) { + unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); + return 0; + } + + if (errno != EEXIST) + return -errno; + + r = readlink_malloc(new_path, &dest); + if (r < 0) + return r; + + if (path_equal(dest, old_path)) + return 0; + + if (!force) + return -EEXIST; + + r = symlink_atomic(old_path, new_path); + if (r < 0) + return r; + + unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL); + unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); + + return 0; +} + +static int mark_symlink_for_removal( + Set **remove_symlinks_to, + const char *p) { + + char *n; + int r; + + assert(p); + + r = set_ensure_allocated(remove_symlinks_to, &string_hash_ops); + if (r < 0) + return r; + + n = strdup(p); + if (!n) + return -ENOMEM; + + path_kill_slashes(n); + + r = set_consume(*remove_symlinks_to, n); + if (r == -EEXIST) + return 0; + if (r < 0) + return r; + + return 1; +} + +static int remove_marked_symlinks_fd( + Set *remove_symlinks_to, + int fd, + const char *path, + const char *config_path, + bool *restart, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0; + + assert(remove_symlinks_to); + assert(fd >= 0); + assert(path); + assert(config_path); + assert(restart); + + d = fdopendir(fd); + if (!d) { + safe_close(fd); + return -errno; + } + + rewinddir(d); + + FOREACH_DIRENT(de, d, return -errno) { + + dirent_ensure_type(d, de); + + if (de->d_type == DT_DIR) { + _cleanup_free_ char *p = NULL; + int nfd, q; + + nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (nfd < 0) { + if (errno == ENOENT) + continue; + + if (r == 0) + r = -errno; + continue; + } + + p = path_make_absolute(de->d_name, path); + if (!p) { + safe_close(nfd); + return -ENOMEM; + } + + /* This will close nfd, regardless whether it succeeds or not */ + q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, restart, changes, n_changes); + if (q < 0 && r == 0) + r = q; + + } else if (de->d_type == DT_LNK) { + _cleanup_free_ char *p = NULL, *dest = NULL; + bool found; + int q; + + if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) + continue; + + p = path_make_absolute(de->d_name, path); + if (!p) + return -ENOMEM; + + q = readlink_malloc(p, &dest); + if (q < 0) { + if (q == -ENOENT) + continue; + + if (r == 0) + r = q; + continue; + } + + /* We remove all links pointing to a file or + * path that is marked, as well as all files + * sharing the same name as a file that is + * marked. */ + + found = + set_contains(remove_symlinks_to, dest) || + set_contains(remove_symlinks_to, basename(dest)) || + set_contains(remove_symlinks_to, de->d_name); + + if (!found) + continue; + + if (unlink(p) < 0 && errno != ENOENT) { + if (r == 0) + r = -errno; + continue; + } + + path_kill_slashes(p); + (void) rmdir_parents(p, config_path); + + unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, p, NULL); + + q = mark_symlink_for_removal(&remove_symlinks_to, p); + if (q < 0) + return q; + if (q > 0) + *restart = true; + } + } + + return r; +} + +static int remove_marked_symlinks( + Set *remove_symlinks_to, + const char *config_path, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_close_ int fd = -1; + bool restart; + int r = 0; + + assert(config_path); + + if (set_size(remove_symlinks_to) <= 0) + return 0; + + fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) + return -errno; + + do { + int q, cfd; + restart = false; + + cfd = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (cfd < 0) + return -errno; + + /* This takes possession of cfd and closes it */ + q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, &restart, changes, n_changes); + if (r == 0) + r = q; + } while (restart); + + return r; +} + +static int find_symlinks_fd( + const char *root_dir, + const char *name, + int fd, + const char *path, + const char *config_path, + bool *same_name_link) { + + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0; + + assert(name); + assert(fd >= 0); + assert(path); + assert(config_path); + assert(same_name_link); + + d = fdopendir(fd); + if (!d) { + safe_close(fd); + return -errno; + } + + FOREACH_DIRENT(de, d, return -errno) { + + dirent_ensure_type(d, de); + + if (de->d_type == DT_DIR) { + _cleanup_free_ char *p = NULL; + int nfd, q; + + nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (nfd < 0) { + if (errno == ENOENT) + continue; + + if (r == 0) + r = -errno; + continue; + } + + p = path_make_absolute(de->d_name, path); + if (!p) { + safe_close(nfd); + return -ENOMEM; + } + + /* This will close nfd, regardless whether it succeeds or not */ + q = find_symlinks_fd(root_dir, name, nfd, p, config_path, same_name_link); + if (q > 0) + return 1; + if (r == 0) + r = q; + + } else if (de->d_type == DT_LNK) { + _cleanup_free_ char *p = NULL, *dest = NULL; + bool found_path, found_dest, b = false; + int q; + + /* Acquire symlink name */ + p = path_make_absolute(de->d_name, path); + if (!p) + return -ENOMEM; + + /* Acquire symlink destination */ + q = readlink_malloc(p, &dest); + if (q == -ENOENT) + continue; + if (q < 0) { + if (r == 0) + r = q; + continue; + } + + /* Make absolute */ + if (!path_is_absolute(dest)) { + char *x; + + x = prefix_root(root_dir, dest); + if (!x) + return -ENOMEM; + + free(dest); + dest = x; + } + + /* Check if the symlink itself matches what we + * are looking for */ + if (path_is_absolute(name)) + found_path = path_equal(p, name); + else + found_path = streq(de->d_name, name); + + /* Check if what the symlink points to + * matches what we are looking for */ + if (path_is_absolute(name)) + found_dest = path_equal(dest, name); + else + found_dest = streq(basename(dest), name); + + if (found_path && found_dest) { + _cleanup_free_ char *t = NULL; + + /* Filter out same name links in the main + * config path */ + t = path_make_absolute(name, config_path); + if (!t) + return -ENOMEM; + + b = path_equal(t, p); + } + + if (b) + *same_name_link = true; + else if (found_path || found_dest) + return 1; + } + } + + return r; +} + +static int find_symlinks( + const char *root_dir, + const char *name, + const char *config_path, + bool *same_name_link) { + + int fd; + + assert(name); + assert(config_path); + assert(same_name_link); + + fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); + if (fd < 0) { + if (errno == ENOENT) + return 0; + return -errno; + } + + /* This takes possession of fd and closes it */ + return find_symlinks_fd(root_dir, name, fd, config_path, config_path, same_name_link); +} + +static int find_symlinks_in_scope( + UnitFileScope scope, + const char *root_dir, + const char *name, + UnitFileState *state) { + + _cleanup_free_ char *normal_path = NULL, *runtime_path = NULL; + bool same_name_link_runtime = false, same_name_link = false; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + /* First look in the normal config path */ + r = get_config_path(scope, false, root_dir, &normal_path); + if (r < 0) + return r; + + r = find_symlinks(root_dir, name, normal_path, &same_name_link); + if (r < 0) + return r; + if (r > 0) { + *state = UNIT_FILE_ENABLED; + return r; + } + + /* Then look in runtime config path */ + r = get_config_path(scope, true, root_dir, &runtime_path); + if (r < 0) + return r; + + r = find_symlinks(root_dir, name, runtime_path, &same_name_link_runtime); + if (r < 0) + return r; + if (r > 0) { + *state = UNIT_FILE_ENABLED_RUNTIME; + return r; + } + + /* Hmm, we didn't find it, but maybe we found the same name + * link? */ + if (same_name_link) { + *state = UNIT_FILE_LINKED; + return 1; + } + if (same_name_link_runtime) { + *state = UNIT_FILE_LINKED_RUNTIME; + return 1; + } + + return 0; +} + +static void install_info_free(UnitFileInstallInfo *i) { + + if (!i) + return; + + free(i->name); + free(i->path); + strv_free(i->aliases); + strv_free(i->wanted_by); + strv_free(i->required_by); + strv_free(i->also); + free(i->default_instance); + free(i->symlink_target); + free(i); +} + +static OrderedHashmap* install_info_hashmap_free(OrderedHashmap *m) { + UnitFileInstallInfo *i; + + if (!m) + return NULL; + + while ((i = ordered_hashmap_steal_first(m))) + install_info_free(i); + + return ordered_hashmap_free(m); +} + +static void install_context_done(InstallContext *c) { + assert(c); + + c->will_process = install_info_hashmap_free(c->will_process); + c->have_processed = install_info_hashmap_free(c->have_processed); +} + +static UnitFileInstallInfo *install_info_find(InstallContext *c, const char *name) { + UnitFileInstallInfo *i; + + i = ordered_hashmap_get(c->have_processed, name); + if (i) + return i; + + return ordered_hashmap_get(c->will_process, name); +} + +static int install_info_add( + InstallContext *c, + const char *name, + const char *path, + UnitFileInstallInfo **ret) { + + UnitFileInstallInfo *i = NULL; + int r; + + assert(c); + assert(name || path); + + if (!name) + name = basename(path); + + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + i = install_info_find(c, name); + if (i) { + if (ret) + *ret = i; + return 0; + } + + r = ordered_hashmap_ensure_allocated(&c->will_process, &string_hash_ops); + if (r < 0) + return r; + + i = new0(UnitFileInstallInfo, 1); + if (!i) + return -ENOMEM; + i->type = _UNIT_FILE_TYPE_INVALID; + + i->name = strdup(name); + if (!i->name) { + r = -ENOMEM; + goto fail; + } + + if (path) { + i->path = strdup(path); + if (!i->path) { + r = -ENOMEM; + goto fail; + } + } + + r = ordered_hashmap_put(c->will_process, i->name, i); + if (r < 0) + goto fail; + + if (ret) + *ret = i; + + return 0; + +fail: + install_info_free(i); + return r; +} + +static int install_info_add_auto( + InstallContext *c, + const char *name_or_path, + UnitFileInstallInfo **ret) { + + assert(c); + assert(name_or_path); + + if (path_is_absolute(name_or_path)) + return install_info_add(c, NULL, name_or_path, ret); + else + return install_info_add(c, name_or_path, NULL, ret); +} + +static int config_parse_also( + 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) { + + UnitFileInstallInfo *i = userdata; + InstallContext *c = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&rvalue, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + r = install_info_add(c, word, NULL, NULL); + if (r < 0) + return r; + + r = strv_push(&i->also, word); + if (r < 0) + return r; + + word = NULL; + } + + return 0; +} + +static int config_parse_default_instance( + 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) { + + UnitFileInstallInfo *i = data; + char *printed; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = install_full_printf(i, rvalue, &printed); + if (r < 0) + return r; + + if (!unit_instance_is_valid(printed)) { + free(printed); + return -EINVAL; + } + + free(i->default_instance); + i->default_instance = printed; + + return 0; +} + +static int unit_file_load( + InstallContext *c, + UnitFileInstallInfo *info, + const char *path, + const char *root_dir, + SearchFlags flags) { + + const ConfigTableItem items[] = { + { "Install", "Alias", config_parse_strv, 0, &info->aliases }, + { "Install", "WantedBy", config_parse_strv, 0, &info->wanted_by }, + { "Install", "RequiredBy", config_parse_strv, 0, &info->required_by }, + { "Install", "DefaultInstance", config_parse_default_instance, 0, info }, + { "Install", "Also", config_parse_also, 0, c }, + {} + }; + + _cleanup_fclose_ FILE *f = NULL; + _cleanup_close_ int fd = -1; + struct stat st; + int r; + + assert(c); + assert(info); + assert(path); + + path = prefix_roota(root_dir, path); + + if (!(flags & SEARCH_LOAD)) { + r = lstat(path, &st); + if (r < 0) + return -errno; + + if (null_or_empty(&st)) + info->type = UNIT_FILE_TYPE_MASKED; + else if (S_ISREG(st.st_mode)) + info->type = UNIT_FILE_TYPE_REGULAR; + else if (S_ISLNK(st.st_mode)) + return -ELOOP; + else if (S_ISDIR(st.st_mode)) + return -EISDIR; + else + return -ENOTTY; + + return 0; + } + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + if (fstat(fd, &st) < 0) + return -errno; + if (null_or_empty(&st)) { + info->type = UNIT_FILE_TYPE_MASKED; + return 0; + } + if (S_ISDIR(st.st_mode)) + return -EISDIR; + if (!S_ISREG(st.st_mode)) + return -ENOTTY; + + f = fdopen(fd, "re"); + if (!f) + return -errno; + fd = -1; + + r = config_parse(NULL, path, f, + NULL, + config_item_table_lookup, items, + true, true, false, info); + if (r < 0) + return r; + + info->type = UNIT_FILE_TYPE_REGULAR; + + return + (int) strv_length(info->aliases) + + (int) strv_length(info->wanted_by) + + (int) strv_length(info->required_by); +} + +static int unit_file_load_or_readlink( + InstallContext *c, + UnitFileInstallInfo *info, + const char *path, + const char *root_dir, + SearchFlags flags) { + + _cleanup_free_ char *np = NULL; + int r; + + r = unit_file_load(c, info, path, root_dir, flags); + if (r != -ELOOP) + return r; + + /* This is a symlink, let's read it. */ + + r = readlink_and_make_absolute_root(root_dir, path, &np); + if (r < 0) + return r; + + if (path_equal(np, "/dev/null")) + info->type = UNIT_FILE_TYPE_MASKED; + else { + const char *bn; + UnitType a, b; + + bn = basename(np); + + if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN)) { + + if (!unit_name_is_valid(bn, UNIT_NAME_PLAIN)) + return -EINVAL; + + } else if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) { + + if (!unit_name_is_valid(bn, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) + return -EINVAL; + + } else if (unit_name_is_valid(info->name, UNIT_NAME_TEMPLATE)) { + + if (!unit_name_is_valid(bn, UNIT_NAME_TEMPLATE)) + return -EINVAL; + } else + return -EINVAL; + + /* Enforce that the symlink destination does not + * change the unit file type. */ + + a = unit_name_to_type(info->name); + b = unit_name_to_type(bn); + if (a < 0 || b < 0 || a != b) + return -EINVAL; + + info->type = UNIT_FILE_TYPE_SYMLINK; + info->symlink_target = np; + np = NULL; + } + + return 0; +} + +static int unit_file_search( + InstallContext *c, + UnitFileInstallInfo *info, + const LookupPaths *paths, + const char *root_dir, + SearchFlags flags) { + + char **p; + int r; + + assert(c); + assert(info); + assert(paths); + + /* Was this unit already loaded? */ + if (info->type != _UNIT_FILE_TYPE_INVALID) + return 0; + + if (info->path) + return unit_file_load_or_readlink(c, info, info->path, root_dir, flags); + + assert(info->name); + + STRV_FOREACH(p, paths->unit_path) { + _cleanup_free_ char *path = NULL; + + path = strjoin(*p, "/", info->name, NULL); + if (!path) + return -ENOMEM; + + r = unit_file_load_or_readlink(c, info, path, root_dir, flags); + if (r < 0) { + if (r != -ENOENT) + return r; + } else { + info->path = path; + path = NULL; + return r; + } + } + + if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) { + + /* Unit file doesn't exist, however instance + * enablement was requested. We will check if it is + * possible to load template unit file. */ + + _cleanup_free_ char *template = NULL; + + r = unit_name_template(info->name, &template); + if (r < 0) + return r; + + STRV_FOREACH(p, paths->unit_path) { + _cleanup_free_ char *path = NULL; + + path = strjoin(*p, "/", template, NULL); + if (!path) + return -ENOMEM; + + r = unit_file_load_or_readlink(c, info, path, root_dir, flags); + if (r < 0) { + if (r != -ENOENT) + return r; + } else { + info->path = path; + path = NULL; + return r; + } + } + } + + return -ENOENT; +} + +static int install_info_follow( + InstallContext *c, + UnitFileInstallInfo *i, + const char *root_dir, + SearchFlags flags) { + + assert(c); + assert(i); + + if (i->type != UNIT_FILE_TYPE_SYMLINK) + return -EINVAL; + if (!i->symlink_target) + return -EINVAL; + + /* If the basename doesn't match, the caller should add a + * complete new entry for this. */ + + if (!streq(basename(i->symlink_target), i->name)) + return -EXDEV; + + free(i->path); + i->path = i->symlink_target; + i->symlink_target = NULL; + i->type = _UNIT_FILE_TYPE_INVALID; + + return unit_file_load_or_readlink(c, i, i->path, root_dir, flags); +} + +static int install_info_traverse( + UnitFileScope scope, + InstallContext *c, + const char *root_dir, + const LookupPaths *paths, + UnitFileInstallInfo *start, + SearchFlags flags, + UnitFileInstallInfo **ret) { + + UnitFileInstallInfo *i; + unsigned k = 0; + int r; + + assert(paths); + assert(start); + assert(c); + + r = unit_file_search(c, start, paths, root_dir, flags); + if (r < 0) + return r; + + i = start; + while (i->type == UNIT_FILE_TYPE_SYMLINK) { + /* Follow the symlink */ + + if (++k > UNIT_FILE_FOLLOW_SYMLINK_MAX) + return -ELOOP; + + if (!(flags & SEARCH_FOLLOW_CONFIG_SYMLINKS) && is_config_path(scope, i->path)) + return -ELOOP; + + r = install_info_follow(c, i, root_dir, flags); + if (r < 0) { + _cleanup_free_ char *buffer = NULL; + const char *bn; + + if (r != -EXDEV) + return r; + + /* Target has a different name, create a new + * install info object for that, and continue + * with that. */ + + bn = basename(i->symlink_target); + + if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE) && + unit_name_is_valid(bn, UNIT_NAME_TEMPLATE)) { + + _cleanup_free_ char *instance = NULL; + + r = unit_name_to_instance(i->name, &instance); + if (r < 0) + return r; + + r = unit_name_replace_instance(bn, instance, &buffer); + if (r < 0) + return r; + + bn = buffer; + } + + r = install_info_add(c, bn, NULL, &i); + if (r < 0) + return r; + + r = unit_file_search(c, i, paths, root_dir, flags); + if (r < 0) + return r; + } + + /* Try again, with the new target we found. */ + } + + if (ret) + *ret = i; + + return 0; +} + +static int install_info_discover( + UnitFileScope scope, + InstallContext *c, + const char *root_dir, + const LookupPaths *paths, + const char *name, + SearchFlags flags, + UnitFileInstallInfo **ret) { + + UnitFileInstallInfo *i; + int r; + + assert(c); + assert(paths); + assert(name); + + r = install_info_add_auto(c, name, &i); + if (r < 0) + return r; + + return install_info_traverse(scope, c, root_dir, paths, i, flags, ret); +} + +static int install_info_symlink_alias( + UnitFileInstallInfo *i, + const char *config_path, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + char **s; + int r = 0, q; + + assert(i); + assert(config_path); + + STRV_FOREACH(s, i->aliases) { + _cleanup_free_ char *alias_path = NULL, *dst = NULL; + + q = install_full_printf(i, *s, &dst); + if (q < 0) + return q; + + alias_path = path_make_absolute(dst, config_path); + if (!alias_path) + return -ENOMEM; + + q = create_symlink(i->path, alias_path, force, changes, n_changes); + if (r == 0) + r = q; + } + + return r; +} + +static int install_info_symlink_wants( + UnitFileInstallInfo *i, + const char *config_path, + char **list, + const char *suffix, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_free_ char *buf = NULL; + const char *n; + char **s; + int r = 0, q; + + assert(i); + assert(config_path); + + if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE)) { + + /* Don't install any symlink if there's no default + * instance configured */ + + if (!i->default_instance) + return 0; + + r = unit_name_replace_instance(i->name, i->default_instance, &buf); + if (r < 0) + return r; + + n = buf; + } else + n = i->name; + + STRV_FOREACH(s, list) { + _cleanup_free_ char *path = NULL, *dst = NULL; + + q = install_full_printf(i, *s, &dst); + if (q < 0) + return q; + + if (!unit_name_is_valid(dst, UNIT_NAME_ANY)) { + r = -EINVAL; + continue; + } + + path = strjoin(config_path, "/", dst, suffix, n, NULL); + if (!path) + return -ENOMEM; + + q = create_symlink(i->path, path, force, changes, n_changes); + if (r == 0) + r = q; + } + + return r; +} + +static int install_info_symlink_link( + UnitFileInstallInfo *i, + const LookupPaths *paths, + const char *config_path, + const char *root_dir, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_free_ char *path = NULL; + int r; + + assert(i); + assert(paths); + assert(config_path); + assert(i->path); + + r = in_search_path(i->path, paths->unit_path); + if (r != 0) + return r; + + path = strjoin(config_path, "/", i->name, NULL); + if (!path) + return -ENOMEM; + + return create_symlink(i->path, path, force, changes, n_changes); +} + +static int install_info_apply( + UnitFileInstallInfo *i, + const LookupPaths *paths, + const char *config_path, + const char *root_dir, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + int r, q; + + assert(i); + assert(paths); + assert(config_path); + + if (i->type != UNIT_FILE_TYPE_REGULAR) + return 0; + + r = install_info_symlink_alias(i, config_path, force, changes, n_changes); + + q = install_info_symlink_wants(i, config_path, i->wanted_by, ".wants/", force, changes, n_changes); + if (r == 0) + r = q; + + q = install_info_symlink_wants(i, config_path, i->required_by, ".requires/", force, changes, n_changes); + if (r == 0) + r = q; + + q = install_info_symlink_link(i, paths, config_path, root_dir, force, changes, n_changes); + if (r == 0) + r = q; + + return r; +} + +static int install_context_apply( + UnitFileScope scope, + InstallContext *c, + const LookupPaths *paths, + const char *config_path, + const char *root_dir, + bool force, + SearchFlags flags, + UnitFileChange **changes, + unsigned *n_changes) { + + UnitFileInstallInfo *i; + int r; + + assert(c); + assert(paths); + assert(config_path); + + if (ordered_hashmap_isempty(c->will_process)) + return 0; + + r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops); + if (r < 0) + return r; + + r = 0; + while ((i = ordered_hashmap_first(c->will_process))) { + int q; + + q = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name); + if (q < 0) + return q; + + r = install_info_traverse(scope, c, root_dir, paths, i, flags, NULL); + if (r < 0) + return r; + + if (i->type != UNIT_FILE_TYPE_REGULAR) + continue; + + q = install_info_apply(i, paths, config_path, root_dir, force, changes, n_changes); + if (r >= 0) { + if (q < 0) + r = q; + else + r+= q; + } + } + + return r; +} + +static int install_context_mark_for_removal( + UnitFileScope scope, + InstallContext *c, + const LookupPaths *paths, + Set **remove_symlinks_to, + const char *config_path, + const char *root_dir) { + + UnitFileInstallInfo *i; + int r; + + assert(c); + assert(paths); + assert(config_path); + + /* Marks all items for removal */ + + if (ordered_hashmap_isempty(c->will_process)) + return 0; + + r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops); + if (r < 0) + return r; + + while ((i = ordered_hashmap_first(c->will_process))) { + + r = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name); + if (r < 0) + return r; + + r = install_info_traverse(scope, c, root_dir, paths, i, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, NULL); + if (r < 0) + return r; + + if (i->type != UNIT_FILE_TYPE_REGULAR) + continue; + + r = mark_symlink_for_removal(remove_symlinks_to, i->name); + if (r < 0) + return r; + } + + return 0; +} + +int unit_file_mask( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_free_ char *prefix = NULL; + char **i; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &prefix); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + _cleanup_free_ char *path = NULL; + int q; + + if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) { + if (r == 0) + r = -EINVAL; + continue; + } + + path = path_make_absolute(*i, prefix); + if (!path) + return -ENOMEM; + + q = create_symlink("/dev/null", path, force, changes, n_changes); + if (q < 0 && r >= 0) + r = q; + } + + return r; +} + +int unit_file_unmask( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; + _cleanup_free_ char *config_path = NULL; + _cleanup_free_ char **todo = NULL; + size_t n_todo = 0, n_allocated = 0; + char **i; + int r, q; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + _cleanup_free_ char *path = NULL; + + if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) + return -EINVAL; + + path = path_make_absolute(*i, config_path); + if (!path) + return -ENOMEM; + + r = null_or_empty_path(path); + if (r == -ENOENT) + continue; + if (r < 0) + return r; + if (r == 0) + continue; + + if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2)) + return -ENOMEM; + + todo[n_todo++] = *i; + } + + strv_uniq(todo); + + r = 0; + STRV_FOREACH(i, todo) { + _cleanup_free_ char *path = NULL; + + path = path_make_absolute(*i, config_path); + if (!path) + return -ENOMEM; + + if (unlink(path) < 0) { + if (errno != -ENOENT && r >= 0) + r = -errno; + } else { + q = mark_symlink_for_removal(&remove_symlinks_to, path); + if (q < 0) + return q; + + unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); + } + } + + q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); + if (r >= 0) + r = q; + + return r; +} + +int unit_file_link( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_free_ char *config_path = NULL; + _cleanup_free_ char **todo = NULL; + size_t n_todo = 0, n_allocated = 0; + char **i; + int r, q; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + r = lookup_paths_init_from_scope(&paths, scope, root_dir); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + _cleanup_free_ char *full = NULL; + struct stat st; + char *fn; + + if (!path_is_absolute(*i)) + return -EINVAL; + + fn = basename(*i); + if (!unit_name_is_valid(fn, UNIT_NAME_ANY)) + return -EINVAL; + + full = prefix_root(root_dir, *i); + if (!full) + return -ENOMEM; + + if (lstat(full, &st) < 0) + return -errno; + if (S_ISLNK(st.st_mode)) + return -ELOOP; + if (S_ISDIR(st.st_mode)) + return -EISDIR; + if (!S_ISREG(st.st_mode)) + return -ENOTTY; + + q = in_search_path(*i, paths.unit_path); + if (q < 0) + return q; + if (q > 0) + continue; + + if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2)) + return -ENOMEM; + + todo[n_todo++] = *i; + } + + strv_uniq(todo); + + r = 0; + STRV_FOREACH(i, todo) { + _cleanup_free_ char *path = NULL; + + path = path_make_absolute(basename(*i), config_path); + if (!path) + return -ENOMEM; + + q = create_symlink(*i, path, force, changes, n_changes); + if (q < 0 && r >= 0) + r = q; + } + + return r; +} + +int unit_file_add_dependency( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + const char *target, + UnitDependency dep, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_(install_context_done) InstallContext c = {}; + _cleanup_free_ char *config_path = NULL; + UnitFileInstallInfo *i, *target_info; + char **f; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(target); + + if (!IN_SET(dep, UNIT_WANTS, UNIT_REQUIRES)) + return -EINVAL; + + if (!unit_name_is_valid(target, UNIT_NAME_ANY)) + return -EINVAL; + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + r = lookup_paths_init_from_scope(&paths, scope, root_dir); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + return r; + + r = install_info_discover(scope, &c, root_dir, &paths, target, SEARCH_FOLLOW_CONFIG_SYMLINKS, &target_info); + if (r < 0) + return r; + if (target_info->type == UNIT_FILE_TYPE_MASKED) + return -ESHUTDOWN; + + assert(target_info->type == UNIT_FILE_TYPE_REGULAR); + + STRV_FOREACH(f, files) { + char ***l; + + r = install_info_discover(scope, &c, root_dir, &paths, *f, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + if (r < 0) + return r; + if (i->type == UNIT_FILE_TYPE_MASKED) + return -ESHUTDOWN; + + assert(i->type == UNIT_FILE_TYPE_REGULAR); + + /* We didn't actually load anything from the unit + * file, but instead just add in our new symlink to + * create. */ + + if (dep == UNIT_WANTS) + l = &i->wanted_by; + else + l = &i->required_by; + + strv_free(*l); + *l = strv_new(target_info->name, NULL); + if (!*l) + return -ENOMEM; + } + + return install_context_apply(scope, &c, &paths, config_path, root_dir, force, SEARCH_FOLLOW_CONFIG_SYMLINKS, changes, n_changes); +} + +int unit_file_enable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_(install_context_done) InstallContext c = {}; + _cleanup_free_ char *config_path = NULL; + UnitFileInstallInfo *i; + char **f; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + r = lookup_paths_init_from_scope(&paths, scope, root_dir); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + return r; + + STRV_FOREACH(f, files) { + r = install_info_discover(scope, &c, root_dir, &paths, *f, SEARCH_LOAD, &i); + if (r < 0) + return r; + if (i->type == UNIT_FILE_TYPE_MASKED) + return -ESHUTDOWN; + + assert(i->type == UNIT_FILE_TYPE_REGULAR); + } + + /* This will return the number of symlink rules that were + supposed to be created, not the ones actually created. This + is useful to determine whether the passed files had any + installation data at all. */ + + return install_context_apply(scope, &c, &paths, config_path, root_dir, force, SEARCH_LOAD, changes, n_changes); +} + +int unit_file_disable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_(install_context_done) InstallContext c = {}; + _cleanup_free_ char *config_path = NULL; + _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; + char **i; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + r = lookup_paths_init_from_scope(&paths, scope, root_dir); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) + return -EINVAL; + + r = install_info_add(&c, *i, NULL, NULL); + if (r < 0) + return r; + } + + r = install_context_mark_for_removal(scope, &c, &paths, &remove_symlinks_to, config_path, root_dir); + if (r < 0) + return r; + + return remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); +} + +int unit_file_reenable( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + char **n; + int r; + size_t l, i; + + /* First, we invoke the disable command with only the basename... */ + l = strv_length(files); + n = newa(char*, l+1); + for (i = 0; i < l; i++) + n[i] = basename(files[i]); + n[i] = NULL; + + r = unit_file_disable(scope, runtime, root_dir, n, changes, n_changes); + if (r < 0) + return r; + + /* But the enable command with the full name */ + return unit_file_enable(scope, runtime, root_dir, files, force, changes, n_changes); +} + +int unit_file_set_default( + UnitFileScope scope, + const char *root_dir, + const char *name, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_(install_context_done) InstallContext c = {}; + _cleanup_free_ char *config_path = NULL; + UnitFileInstallInfo *i; + const char *path; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + if (unit_name_to_type(name) != UNIT_TARGET) + return -EINVAL; + if (streq(name, SPECIAL_DEFAULT_TARGET)) + return -EINVAL; + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + r = lookup_paths_init_from_scope(&paths, scope, root_dir); + if (r < 0) + return r; + + r = get_config_path(scope, false, root_dir, &config_path); + if (r < 0) + return r; + + r = install_info_discover(scope, &c, root_dir, &paths, name, 0, &i); + if (r < 0) + return r; + if (i->type == UNIT_FILE_TYPE_MASKED) + return -ESHUTDOWN; + + path = strjoina(config_path, "/" SPECIAL_DEFAULT_TARGET); + + return create_symlink(i->path, path, force, changes, n_changes); +} + +int unit_file_get_default( + UnitFileScope scope, + const char *root_dir, + char **name) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_(install_context_done) InstallContext c = {}; + UnitFileInstallInfo *i; + char *n; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + r = lookup_paths_init_from_scope(&paths, scope, root_dir); + if (r < 0) + return r; + + r = install_info_discover(scope, &c, root_dir, &paths, SPECIAL_DEFAULT_TARGET, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + if (r < 0) + return r; + if (i->type == UNIT_FILE_TYPE_MASKED) + return -ESHUTDOWN; + + n = strdup(i->name); + if (!n) + return -ENOMEM; + + *name = n; + return 0; +} + +int unit_file_lookup_state( + UnitFileScope scope, + const char *root_dir, + const LookupPaths *paths, + const char *name, + UnitFileState *ret) { + + _cleanup_(install_context_done) InstallContext c = {}; + UnitFileInstallInfo *i; + UnitFileState state; + int r; + + assert(paths); + assert(name); + + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + r = install_info_discover(scope, &c, root_dir, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + if (r < 0) + return r; + + /* Shortcut things, if the caller just wants to know if this unit exists. */ + if (!ret) + return 0; + + switch (i->type) { + + case UNIT_FILE_TYPE_MASKED: + state = path_startswith(i->path, "/run") ? UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; + break; + + case UNIT_FILE_TYPE_REGULAR: + r = find_symlinks_in_scope(scope, root_dir, i->name, &state); + if (r < 0) + return r; + if (r == 0) { + if (UNIT_FILE_INSTALL_INFO_HAS_RULES(i)) + state = UNIT_FILE_DISABLED; + else if (UNIT_FILE_INSTALL_INFO_HAS_ALSO(i)) + state = UNIT_FILE_INDIRECT; + else + state = UNIT_FILE_STATIC; + } + + break; + + default: + assert_not_reached("Unexpect unit file type."); + } + + *ret = state; + return 0; +} + +int unit_file_get_state( + UnitFileScope scope, + const char *root_dir, + const char *name, + UnitFileState *ret) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + r = lookup_paths_init_from_scope(&paths, scope, root_dir); + if (r < 0) + return r; + + return unit_file_lookup_state(scope, root_dir, &paths, name, ret); +} + +int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name) { + _cleanup_strv_free_ char **files = NULL; + char **p; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(name); + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + if (scope == UNIT_FILE_SYSTEM) + r = conf_files_list(&files, ".preset", root_dir, + "/etc/systemd/system-preset", + "/usr/local/lib/systemd/system-preset", + "/usr/lib/systemd/system-preset", +#ifdef HAVE_SPLIT_USR + "/lib/systemd/system-preset", +#endif + NULL); + else if (scope == UNIT_FILE_GLOBAL) + r = conf_files_list(&files, ".preset", root_dir, + "/etc/systemd/user-preset", + "/usr/local/lib/systemd/user-preset", + "/usr/lib/systemd/user-preset", + NULL); + else + return 1; /* Default is "enable" */ + + if (r < 0) + return r; + + STRV_FOREACH(p, files) { + _cleanup_fclose_ FILE *f; + char line[LINE_MAX]; + + f = fopen(*p, "re"); + if (!f) { + if (errno == ENOENT) + continue; + + return -errno; + } + + FOREACH_LINE(line, f, return -errno) { + const char *parameter; + char *l; + + l = strstrip(line); + + if (isempty(l)) + continue; + if (strchr(COMMENTS, *l)) + continue; + + parameter = first_word(l, "enable"); + if (parameter) { + if (fnmatch(parameter, name, FNM_NOESCAPE) == 0) { + log_debug("Preset file says enable %s.", name); + return 1; + } + + continue; + } + + parameter = first_word(l, "disable"); + if (parameter) { + if (fnmatch(parameter, name, FNM_NOESCAPE) == 0) { + log_debug("Preset file says disable %s.", name); + return 0; + } + + continue; + } + + log_debug("Couldn't parse line '%s'", l); + } + } + + /* Default is "enable" */ + log_debug("Preset file doesn't say anything about %s, enabling.", name); + return 1; +} + +static int execute_preset( + UnitFileScope scope, + InstallContext *plus, + InstallContext *minus, + const LookupPaths *paths, + const char *config_path, + const char *root_dir, + char **files, + UnitFilePresetMode mode, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + int r; + + assert(plus); + assert(minus); + assert(paths); + assert(config_path); + + if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) { + _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; + + r = install_context_mark_for_removal(scope, minus, paths, &remove_symlinks_to, config_path, root_dir); + if (r < 0) + return r; + + r = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); + } else + r = 0; + + if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) { + int q; + + /* Returns number of symlinks that where supposed to be installed. */ + q = install_context_apply(scope, plus, paths, config_path, root_dir, force, SEARCH_LOAD, changes, n_changes); + if (r >= 0) { + if (q < 0) + r = q; + else + r+= q; + } + } + + return r; +} + +static int preset_prepare_one( + UnitFileScope scope, + InstallContext *plus, + InstallContext *minus, + LookupPaths *paths, + const char *root_dir, + UnitFilePresetMode mode, + const char *name) { + + UnitFileInstallInfo *i; + int r; + + if (install_info_find(plus, name) || + install_info_find(minus, name)) + return 0; + + r = unit_file_query_preset(scope, root_dir, name); + if (r < 0) + return r; + + if (r > 0) { + r = install_info_discover(scope, plus, root_dir, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + if (r < 0) + return r; + + if (i->type == UNIT_FILE_TYPE_MASKED) + return -ESHUTDOWN; + } else + r = install_info_discover(scope, minus, root_dir, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); + + return r; +} + +int unit_file_preset( + UnitFileScope scope, + bool runtime, + const char *root_dir, + char **files, + UnitFilePresetMode mode, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_free_ char *config_path = NULL; + char **i; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(mode < _UNIT_FILE_PRESET_MAX); + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + r = lookup_paths_init_from_scope(&paths, scope, root_dir); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + return r; + + STRV_FOREACH(i, files) { + if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) + return -EINVAL; + + r = preset_prepare_one(scope, &plus, &minus, &paths, root_dir, mode, *i); + if (r < 0) + return r; + } + + return execute_preset(scope, &plus, &minus, &paths, config_path, root_dir, files, mode, force, changes, n_changes); +} + +int unit_file_preset_all( + UnitFileScope scope, + bool runtime, + const char *root_dir, + UnitFilePresetMode mode, + bool force, + UnitFileChange **changes, + unsigned *n_changes) { + + _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + _cleanup_free_ char *config_path = NULL; + char **i; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(mode < _UNIT_FILE_PRESET_MAX); + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + r = lookup_paths_init_from_scope(&paths, scope, root_dir); + if (r < 0) + return r; + + r = get_config_path(scope, runtime, root_dir, &config_path); + if (r < 0) + return r; + + STRV_FOREACH(i, paths.unit_path) { + _cleanup_closedir_ DIR *d = NULL; + _cleanup_free_ char *units_dir; + struct dirent *de; + + units_dir = path_join(root_dir, *i, NULL); + if (!units_dir) + return -ENOMEM; + + d = opendir(units_dir); + if (!d) { + if (errno == ENOENT) + continue; + + return -errno; + } + + FOREACH_DIRENT(de, d, return -errno) { + + if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) + continue; + + dirent_ensure_type(d, de); + + if (!IN_SET(de->d_type, DT_LNK, DT_REG)) + continue; + + r = preset_prepare_one(scope, &plus, &minus, &paths, root_dir, mode, de->d_name); + if (r < 0) + return r; + } + } + + return execute_preset(scope, &plus, &minus, &paths, config_path, root_dir, NULL, mode, force, changes, n_changes); +} + +static void unit_file_list_free_one(UnitFileList *f) { + if (!f) + return; + + free(f->path); + free(f); +} + +Hashmap* unit_file_list_free(Hashmap *h) { + UnitFileList *i; + + while ((i = hashmap_steal_first(h))) + unit_file_list_free_one(i); + + return hashmap_free(h); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free_one); + +int unit_file_get_list( + UnitFileScope scope, + const char *root_dir, + Hashmap *h) { + + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + char **i; + int r; + + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + assert(h); + + r = verify_root_dir(scope, &root_dir); + if (r < 0) + return r; + + r = lookup_paths_init_from_scope(&paths, scope, root_dir); + if (r < 0) + return r; + + STRV_FOREACH(i, paths.unit_path) { + _cleanup_closedir_ DIR *d = NULL; + _cleanup_free_ char *units_dir; + struct dirent *de; + + units_dir = path_join(root_dir, *i, NULL); + if (!units_dir) + return -ENOMEM; + + d = opendir(units_dir); + if (!d) { + if (errno == ENOENT) + continue; + + return -errno; + } + + FOREACH_DIRENT(de, d, return -errno) { + _cleanup_(unit_file_list_free_onep) UnitFileList *f = NULL; + + if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) + continue; + + if (hashmap_get(h, de->d_name)) + continue; + + dirent_ensure_type(d, de); + + if (!IN_SET(de->d_type, DT_LNK, DT_REG)) + continue; + + f = new0(UnitFileList, 1); + if (!f) + return -ENOMEM; + + f->path = path_make_absolute(de->d_name, units_dir); + if (!f->path) + return -ENOMEM; + + r = unit_file_lookup_state(scope, root_dir, &paths, basename(f->path), &f->state); + if (r < 0) + f->state = UNIT_FILE_BAD; + + r = hashmap_put(h, basename(f->path), f); + if (r < 0) + return r; + + f = NULL; /* prevent cleanup */ + } + } + + return 0; +} + +static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = { + [UNIT_FILE_ENABLED] = "enabled", + [UNIT_FILE_ENABLED_RUNTIME] = "enabled-runtime", + [UNIT_FILE_LINKED] = "linked", + [UNIT_FILE_LINKED_RUNTIME] = "linked-runtime", + [UNIT_FILE_MASKED] = "masked", + [UNIT_FILE_MASKED_RUNTIME] = "masked-runtime", + [UNIT_FILE_STATIC] = "static", + [UNIT_FILE_DISABLED] = "disabled", + [UNIT_FILE_INDIRECT] = "indirect", + [UNIT_FILE_BAD] = "bad", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState); + +static const char* const unit_file_change_type_table[_UNIT_FILE_CHANGE_TYPE_MAX] = { + [UNIT_FILE_SYMLINK] = "symlink", + [UNIT_FILE_UNLINK] = "unlink", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_file_change_type, UnitFileChangeType); + +static const char* const unit_file_preset_mode_table[_UNIT_FILE_PRESET_MAX] = { + [UNIT_FILE_PRESET_FULL] = "full", + [UNIT_FILE_PRESET_ENABLE_ONLY] = "enable-only", + [UNIT_FILE_PRESET_DISABLE_ONLY] = "disable-only", +}; + +DEFINE_STRING_TABLE_LOOKUP(unit_file_preset_mode, UnitFilePresetMode); diff --git a/src/libshared/install.h b/src/libshared/install.h new file mode 100644 index 0000000000..c1a43e23e7 --- /dev/null +++ b/src/libshared/install.h @@ -0,0 +1,156 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +typedef enum UnitFileScope UnitFileScope; +typedef enum UnitFileState UnitFileState; +typedef enum UnitFilePresetMode UnitFilePresetMode; +typedef enum UnitFileChangeType UnitFileChangeType; +typedef enum UnitFileType UnitFileType; +typedef struct UnitFileChange UnitFileChange; +typedef struct UnitFileList UnitFileList; +typedef struct UnitFileInstallInfo UnitFileInstallInfo; + +#include + +#include "hashmap.h" +#include "macro.h" +#include "path-lookup.h" +#include "strv.h" +#include "unit-name.h" + +enum UnitFileScope { + UNIT_FILE_SYSTEM, + UNIT_FILE_GLOBAL, + UNIT_FILE_USER, + _UNIT_FILE_SCOPE_MAX, + _UNIT_FILE_SCOPE_INVALID = -1 +}; + +enum UnitFileState { + UNIT_FILE_ENABLED, + UNIT_FILE_ENABLED_RUNTIME, + UNIT_FILE_LINKED, + UNIT_FILE_LINKED_RUNTIME, + UNIT_FILE_MASKED, + UNIT_FILE_MASKED_RUNTIME, + UNIT_FILE_STATIC, + UNIT_FILE_DISABLED, + UNIT_FILE_INDIRECT, + UNIT_FILE_BAD, + _UNIT_FILE_STATE_MAX, + _UNIT_FILE_STATE_INVALID = -1 +}; + +enum UnitFilePresetMode { + UNIT_FILE_PRESET_FULL, + UNIT_FILE_PRESET_ENABLE_ONLY, + UNIT_FILE_PRESET_DISABLE_ONLY, + _UNIT_FILE_PRESET_MAX, + _UNIT_FILE_PRESET_INVALID = -1 +}; + +enum UnitFileChangeType { + UNIT_FILE_SYMLINK, + UNIT_FILE_UNLINK, + _UNIT_FILE_CHANGE_TYPE_MAX, + _UNIT_FILE_CHANGE_TYPE_INVALID = -1 +}; + +struct UnitFileChange { + UnitFileChangeType type; + char *path; + char *source; +}; + +struct UnitFileList { + char *path; + UnitFileState state; +}; + +enum UnitFileType { + UNIT_FILE_TYPE_REGULAR, + UNIT_FILE_TYPE_SYMLINK, + UNIT_FILE_TYPE_MASKED, + _UNIT_FILE_TYPE_MAX, + _UNIT_FILE_TYPE_INVALID = -1, +}; + +struct UnitFileInstallInfo { + char *name; + char *path; + + char **aliases; + char **wanted_by; + char **required_by; + char **also; + + char *default_instance; + + UnitFileType type; + + char *symlink_target; +}; + +static inline bool UNIT_FILE_INSTALL_INFO_HAS_RULES(UnitFileInstallInfo *i) { + assert(i); + + return !strv_isempty(i->aliases) || + !strv_isempty(i->wanted_by) || + !strv_isempty(i->required_by); +} + +static inline bool UNIT_FILE_INSTALL_INFO_HAS_ALSO(UnitFileInstallInfo *i) { + assert(i); + + return !strv_isempty(i->also); +} + +int unit_file_enable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_disable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes); +int unit_file_reenable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_link(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_preset(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFilePresetMode mode, bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_preset_all(UnitFileScope scope, bool runtime, const char *root_dir, UnitFilePresetMode mode, bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_mask(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_unmask(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes); +int unit_file_set_default(UnitFileScope scope, const char *root_dir, const char *file, bool force, UnitFileChange **changes, unsigned *n_changes); +int unit_file_get_default(UnitFileScope scope, const char *root_dir, char **name); +int unit_file_add_dependency(UnitFileScope scope, bool runtime, const char *root_dir, char **files, const char *target, UnitDependency dep, bool force, UnitFileChange **changes, unsigned *n_changes); + +int unit_file_lookup_state(UnitFileScope scope, const char *root_dir,const LookupPaths *paths, const char *name, UnitFileState *ret); +int unit_file_get_state(UnitFileScope scope, const char *root_dir, const char *filename, UnitFileState *ret); + +int unit_file_get_list(UnitFileScope scope, const char *root_dir, Hashmap *h); +Hashmap* unit_file_list_free(Hashmap *h); + +int unit_file_changes_add(UnitFileChange **changes, unsigned *n_changes, UnitFileChangeType type, const char *path, const char *source); +void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes); + +int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name); + +const char *unit_file_state_to_string(UnitFileState s) _const_; +UnitFileState unit_file_state_from_string(const char *s) _pure_; + +const char *unit_file_change_type_to_string(UnitFileChangeType s) _const_; +UnitFileChangeType unit_file_change_type_from_string(const char *s) _pure_; + +const char *unit_file_preset_mode_to_string(UnitFilePresetMode m) _const_; +UnitFilePresetMode unit_file_preset_mode_from_string(const char *s) _pure_; diff --git a/src/libshared/logs-show.c b/src/libshared/logs-show.c new file mode 100644 index 0000000000..7ac6d49549 --- /dev/null +++ b/src/libshared/logs-show.c @@ -0,0 +1,1305 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "hashmap.h" +#include "hostname-util.h" +#include "io-util.h" +#include "journal-internal.h" +#include "log.h" +#include "logs-show.h" +#include "macro.h" +#include "output-mode.h" +#include "parse-util.h" +#include "process-util.h" +#include "sparse-endian.h" +#include "string-table.h" +#include "string-util.h" +#include "terminal-util.h" +#include "time-util.h" +#include "utf8.h" +#include "util.h" + +/* up to three lines (each up to 100 characters) or 300 characters, whichever is less */ +#define PRINT_LINE_THRESHOLD 3 +#define PRINT_CHAR_THRESHOLD 300 + +#define JSON_THRESHOLD 4096 + +static int print_catalog(FILE *f, sd_journal *j) { + int r; + _cleanup_free_ char *t = NULL, *z = NULL; + + + r = sd_journal_get_catalog(j, &t); + if (r < 0) + return r; + + z = strreplace(strstrip(t), "\n", "\n-- "); + if (!z) + return log_oom(); + + fputs("-- ", f); + fputs(z, f); + fputc('\n', f); + + return 0; +} + +static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) { + size_t fl, nl; + char *buf; + + assert(data); + assert(field); + assert(target); + + fl = strlen(field); + if (length < fl) + return 0; + + if (memcmp(data, field, fl)) + return 0; + + nl = length - fl; + buf = new(char, nl+1); + if (!buf) + return log_oom(); + + memcpy(buf, (const char*) data + fl, nl); + buf[nl] = 0; + + free(*target); + *target = buf; + + if (target_size) + *target_size = nl; + + return 1; +} + +static bool shall_print(const char *p, size_t l, OutputFlags flags) { + assert(p); + + if (flags & OUTPUT_SHOW_ALL) + return true; + + if (l >= PRINT_CHAR_THRESHOLD) + return false; + + if (!utf8_is_printable(p, l)) + return false; + + return true; +} + +static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, OutputFlags flags, int priority, const char* message, size_t message_len) { + const char *color_on = "", *color_off = ""; + const char *pos, *end; + bool ellipsized = false; + int line = 0; + + if (flags & OUTPUT_COLOR) { + if (priority <= LOG_ERR) { + color_on = ANSI_HIGHLIGHT_RED; + color_off = ANSI_NORMAL; + } else if (priority <= LOG_NOTICE) { + color_on = ANSI_HIGHLIGHT; + color_off = ANSI_NORMAL; + } + } + + /* A special case: make sure that we print a newline when + the message is empty. */ + if (message_len == 0) + fputs("\n", f); + + for (pos = message; + pos < message + message_len; + pos = end + 1, line++) { + bool continuation = line > 0; + bool tail_line; + int len; + for (end = pos; end < message + message_len && *end != '\n'; end++) + ; + len = end - pos; + assert(len >= 0); + + /* We need to figure out when we are showing not-last line, *and* + * will skip subsequent lines. In that case, we will put the dots + * at the end of the line, instead of putting dots in the middle + * or not at all. + */ + tail_line = + line + 1 == PRINT_LINE_THRESHOLD || + end + 1 >= message + PRINT_CHAR_THRESHOLD; + + if (flags & (OUTPUT_FULL_WIDTH | OUTPUT_SHOW_ALL) || + (prefix + len + 1 < n_columns && !tail_line)) { + fprintf(f, "%*s%s%.*s%s\n", + continuation * prefix, "", + color_on, len, pos, color_off); + continue; + } + + /* Beyond this point, ellipsization will happen. */ + ellipsized = true; + + if (prefix < n_columns && n_columns - prefix >= 3) { + if (n_columns - prefix > (unsigned) len + 3) + fprintf(f, "%*s%s%.*s...%s\n", + continuation * prefix, "", + color_on, len, pos, color_off); + else { + _cleanup_free_ char *e; + + e = ellipsize_mem(pos, len, n_columns - prefix, + tail_line ? 100 : 90); + if (!e) + fprintf(f, "%*s%s%.*s%s\n", + continuation * prefix, "", + color_on, len, pos, color_off); + else + fprintf(f, "%*s%s%s%s\n", + continuation * prefix, "", + color_on, e, color_off); + } + } else + fputs("...\n", f); + + if (tail_line) + break; + } + + return ellipsized; +} + +static int output_short( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + int r; + const void *data; + size_t length; + size_t n = 0; + _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL, *priority = NULL; + size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, realtime_len = 0, monotonic_len = 0, priority_len = 0; + int p = LOG_INFO; + bool ellipsized = false; + + assert(f); + assert(j); + + /* Set the threshold to one bigger than the actual print + * threshold, so that if the line is actually longer than what + * we're willing to print, ellipsization will occur. This way + * we won't output a misleading line without any indication of + * truncation. + */ + sd_journal_set_data_threshold(j, flags & (OUTPUT_SHOW_ALL|OUTPUT_FULL_WIDTH) ? 0 : PRINT_CHAR_THRESHOLD + 1); + + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + + r = parse_field(data, length, "PRIORITY=", &priority, &priority_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_HOSTNAME=", &hostname, &hostname_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &identifier_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_COMM=", &comm, &comm_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_PID=", &pid, &pid_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "SYSLOG_PID=", &fake_pid, &fake_pid_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_field(data, length, "MESSAGE=", &message, &message_len); + if (r < 0) + return r; + } + + if (r < 0) + return log_error_errno(r, "Failed to get journal fields: %m"); + + if (!message) { + log_debug("Skipping message without MESSAGE= field."); + return 0; + } + + if (!(flags & OUTPUT_SHOW_ALL)) + strip_tab_ansi(&message, &message_len); + + 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_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 (hostname && shall_print(hostname, hostname_len, flags)) { + fprintf(f, " %.*s", (int) hostname_len, hostname); + n += hostname_len + 1; + } + + if (identifier && shall_print(identifier, identifier_len, flags)) { + fprintf(f, " %.*s", (int) identifier_len, identifier); + n += identifier_len + 1; + } else if (comm && shall_print(comm, comm_len, flags)) { + fprintf(f, " %.*s", (int) comm_len, comm); + n += comm_len + 1; + } else + fputs(" unknown", f); + + if (pid && shall_print(pid, pid_len, flags)) { + fprintf(f, "[%.*s]", (int) pid_len, pid); + n += pid_len + 2; + } else if (fake_pid && shall_print(fake_pid, fake_pid_len, flags)) { + fprintf(f, "[%.*s]", (int) fake_pid_len, fake_pid); + n += fake_pid_len + 2; + } + + if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(message, message_len)) { + char bytes[FORMAT_BYTES_MAX]; + fprintf(f, ": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len)); + } else { + fputs(": ", f); + ellipsized |= + print_multiline(f, n + 2, n_columns, flags, p, message, message_len); + } + + if (flags & OUTPUT_CATALOG) + print_catalog(f, j); + + return ellipsized; +} + +static int output_verbose( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + const void *data; + size_t length; + _cleanup_free_ char *cursor = NULL; + uint64_t realtime = 0; + char ts[FORMAT_TIMESTAMP_MAX + 7]; + int r; + + assert(f); + assert(j); + + sd_journal_set_data_threshold(j, 0); + + r = sd_journal_get_data(j, "_SOURCE_REALTIME_TIMESTAMP", &data, &length); + if (r == -ENOENT) + log_debug("Source realtime timestamp not found"); + else if (r < 0) + return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get source realtime timestamp: %m"); + else { + _cleanup_free_ char *value = NULL; + + r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &value, NULL); + if (r < 0) + return r; + assert(r > 0); + + r = safe_atou64(value, &realtime); + if (r < 0) + log_debug_errno(r, "Failed to parse realtime timestamp: %m"); + } + + if (r < 0) { + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) + return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get realtime timestamp: %m"); + } + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + fprintf(f, "%s [%s]\n", + flags & OUTPUT_UTC ? + format_timestamp_us_utc(ts, sizeof(ts), realtime) : + format_timestamp_us(ts, sizeof(ts), realtime), + cursor); + + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + const char *c; + int fieldlen; + const char *on = "", *off = ""; + + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + fieldlen = c - (const char*) data; + + if (flags & OUTPUT_COLOR && startswith(data, "MESSAGE=")) { + on = ANSI_HIGHLIGHT; + off = ANSI_NORMAL; + } + + 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); + print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, c + 1, length - fieldlen - 1); + fputs(off, f); + } else { + char bytes[FORMAT_BYTES_MAX]; + + fprintf(f, " %s%.*s=[%s blob data]%s\n", + on, + (int) (c - (const char*) data), + (const char*) data, + format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1), + off); + } + } + + if (r < 0) + return r; + + if (flags & OUTPUT_CATALOG) + print_catalog(f, j); + + return 0; +} + +static int output_export( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + sd_id128_t boot_id; + char sid[33]; + int r; + usec_t realtime, monotonic; + _cleanup_free_ char *cursor = NULL; + const void *data; + size_t length; + + assert(j); + + sd_journal_set_data_threshold(j, 0); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + fprintf(f, + "__CURSOR=%s\n" + "__REALTIME_TIMESTAMP="USEC_FMT"\n" + "__MONOTONIC_TIMESTAMP="USEC_FMT"\n" + "_BOOT_ID=%s\n", + cursor, + realtime, + monotonic, + sd_id128_to_string(boot_id, sid)); + + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + + /* We already printed the boot id, from the data in + * the header, hence let's suppress it here */ + if (length >= 9 && + startswith(data, "_BOOT_ID=")) + continue; + + if (utf8_is_printable_newline(data, length, false)) + fwrite(data, length, 1, f); + else { + const char *c; + uint64_t le64; + + c = memchr(data, '=', length); + if (!c) { + log_error("Invalid field."); + return -EINVAL; + } + + fwrite(data, c - (const char*) data, 1, f); + fputc('\n', f); + le64 = htole64(length - (c - (const char*) data) - 1); + fwrite(&le64, sizeof(le64), 1, f); + fwrite(c + 1, length - (c - (const char*) data) - 1, 1, f); + } + + fputc('\n', f); + } + + if (r < 0) + return r; + + fputc('\n', f); + + return 0; +} + +void json_escape( + FILE *f, + const char* p, + size_t l, + OutputFlags flags) { + + assert(f); + assert(p); + + if (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD) + fputs("null", f); + + else if (!utf8_is_printable(p, l)) { + bool not_first = false; + + fputs("[ ", f); + + while (l > 0) { + if (not_first) + fprintf(f, ", %u", (uint8_t) *p); + else { + not_first = true; + fprintf(f, "%u", (uint8_t) *p); + } + + p++; + l--; + } + + fputs(" ]", f); + } else { + fputc('\"', f); + + while (l > 0) { + if (*p == '"' || *p == '\\') { + fputc('\\', f); + fputc(*p, f); + } else if (*p == '\n') + fputs("\\n", f); + else if ((uint8_t) *p < ' ') + fprintf(f, "\\u%04x", (uint8_t) *p); + else + fputc(*p, f); + + p++; + l--; + } + + fputc('\"', f); + } +} + +static int output_json( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + uint64_t realtime, monotonic; + _cleanup_free_ char *cursor = NULL; + const void *data; + size_t length; + sd_id128_t boot_id; + char sid[33], *k; + int r; + Hashmap *h = NULL; + bool done, separator; + + assert(j); + + sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD); + + r = sd_journal_get_realtime_usec(j, &realtime); + if (r < 0) + return log_error_errno(r, "Failed to get realtime timestamp: %m"); + + r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get monotonic timestamp: %m"); + + r = sd_journal_get_cursor(j, &cursor); + if (r < 0) + return log_error_errno(r, "Failed to get cursor: %m"); + + if (mode == OUTPUT_JSON_PRETTY) + fprintf(f, + "{\n" + "\t\"__CURSOR\" : \"%s\",\n" + "\t\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\",\n" + "\t\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\",\n" + "\t\"_BOOT_ID\" : \"%s\"", + cursor, + realtime, + monotonic, + sd_id128_to_string(boot_id, sid)); + else { + if (mode == OUTPUT_JSON_SSE) + fputs("data: ", f); + + fprintf(f, + "{ \"__CURSOR\" : \"%s\", " + "\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\", " + "\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\", " + "\"_BOOT_ID\" : \"%s\"", + cursor, + realtime, + monotonic, + sd_id128_to_string(boot_id, sid)); + } + + h = hashmap_new(&string_hash_ops); + if (!h) + return log_oom(); + + /* First round, iterate through the entry and count how often each field appears */ + JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { + const char *eq; + char *n; + unsigned u; + + if (length >= 9 && + memcmp(data, "_BOOT_ID=", 9) == 0) + continue; + + eq = memchr(data, '=', length); + if (!eq) + continue; + + n = strndup(data, eq - (const char*) data); + if (!n) { + r = log_oom(); + goto finish; + } + + u = PTR_TO_UINT(hashmap_get(h, n)); + if (u == 0) { + r = hashmap_put(h, n, UINT_TO_PTR(1)); + if (r < 0) { + free(n); + log_oom(); + goto finish; + } + } else { + r = hashmap_update(h, n, UINT_TO_PTR(u + 1)); + free(n); + if (r < 0) { + log_oom(); + goto finish; + } + } + } + + if (r < 0) + return r; + + separator = true; + do { + done = true; + + SD_JOURNAL_FOREACH_DATA(j, data, length) { + const char *eq; + char *kk, *n; + size_t m; + unsigned u; + + /* We already printed the boot id, from the data in + * the header, hence let's suppress it here */ + if (length >= 9 && + memcmp(data, "_BOOT_ID=", 9) == 0) + continue; + + eq = memchr(data, '=', length); + if (!eq) + continue; + + if (separator) { + if (mode == OUTPUT_JSON_PRETTY) + fputs(",\n\t", f); + else + fputs(", ", f); + } + + m = eq - (const char*) data; + + n = strndup(data, m); + if (!n) { + r = log_oom(); + goto finish; + } + + u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk)); + if (u == 0) { + /* We already printed this, let's jump to the next */ + free(n); + separator = false; + + continue; + } else if (u == 1) { + /* Field only appears once, output it directly */ + + json_escape(f, data, m, flags); + fputs(" : ", f); + + json_escape(f, eq + 1, length - m - 1, flags); + + hashmap_remove(h, n); + free(kk); + free(n); + + separator = true; + + continue; + + } else { + /* Field appears multiple times, output it as array */ + json_escape(f, data, m, flags); + fputs(" : [ ", f); + json_escape(f, eq + 1, length - m - 1, flags); + + /* Iterate through the end of the list */ + + while (sd_journal_enumerate_data(j, &data, &length) > 0) { + if (length < m + 1) + continue; + + if (memcmp(data, n, m) != 0) + continue; + + if (((const char*) data)[m] != '=') + continue; + + fputs(", ", f); + json_escape(f, (const char*) data + m + 1, length - m - 1, flags); + } + + fputs(" ]", f); + + hashmap_remove(h, n); + free(kk); + free(n); + + /* Iterate data fields form the beginning */ + done = false; + separator = true; + + break; + } + } + + } while (!done); + + if (mode == OUTPUT_JSON_PRETTY) + fputs("\n}\n", f); + else if (mode == OUTPUT_JSON_SSE) + fputs("}\n\n", f); + else + fputs(" }\n", f); + + r = 0; + +finish: + while ((k = hashmap_steal_first_key(h))) + free(k); + + hashmap_free(h); + + return r; +} + +static int output_cat( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) { + + const void *data; + size_t l; + int r; + + assert(j); + assert(f); + + sd_journal_set_data_threshold(j, 0); + + r = sd_journal_get_data(j, "MESSAGE", &data, &l); + if (r < 0) { + /* An entry without MESSAGE=? */ + if (r == -ENOENT) + return 0; + + return log_error_errno(r, "Failed to get data: %m"); + } + + assert(l >= 8); + + fwrite((const char*) data + 8, 1, l - 8, f); + fputc('\n', f); + + return 0; +} + +static int (*output_funcs[_OUTPUT_MODE_MAX])( + FILE *f, + sd_journal*j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags) = { + + [OUTPUT_SHORT] = output_short, + [OUTPUT_SHORT_ISO] = output_short, + [OUTPUT_SHORT_PRECISE] = output_short, + [OUTPUT_SHORT_MONOTONIC] = output_short, + [OUTPUT_VERBOSE] = output_verbose, + [OUTPUT_EXPORT] = output_export, + [OUTPUT_JSON] = output_json, + [OUTPUT_JSON_PRETTY] = output_json, + [OUTPUT_JSON_SSE] = output_json, + [OUTPUT_CAT] = output_cat +}; + +int output_journal( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags, + bool *ellipsized) { + + int ret; + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + + if (n_columns <= 0) + n_columns = columns(); + + ret = output_funcs[mode](f, j, mode, n_columns, flags); + fflush(stdout); + + if (ellipsized && ret > 0) + *ellipsized = true; + + return ret; +} + +static int maybe_print_begin_newline(FILE *f, OutputFlags *flags) { + assert(f); + assert(flags); + + if (!(*flags & OUTPUT_BEGIN_NEWLINE)) + return 0; + + /* Print a beginning new line if that's request, but only once + * on the first line we print. */ + + fputc('\n', f); + *flags &= ~OUTPUT_BEGIN_NEWLINE; + return 0; +} + +static int show_journal(FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + usec_t not_before, + unsigned how_many, + OutputFlags flags, + bool *ellipsized) { + + int r; + unsigned line = 0; + bool need_seek = false; + int warn_cutoff = flags & OUTPUT_WARN_CUTOFF; + + assert(j); + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + + /* Seek to end */ + r = sd_journal_seek_tail(j); + if (r < 0) + return log_error_errno(r, "Failed to seek to tail: %m"); + + r = sd_journal_previous_skip(j, how_many); + if (r < 0) + return log_error_errno(r, "Failed to skip previous: %m"); + + for (;;) { + for (;;) { + usec_t usec; + + if (need_seek) { + r = sd_journal_next(j); + if (r < 0) + return log_error_errno(r, "Failed to iterate through journal: %m"); + } + + if (r == 0) + break; + + need_seek = true; + + if (not_before > 0) { + r = sd_journal_get_monotonic_usec(j, &usec, NULL); + + /* -ESTALE is returned if the + timestamp is not from this boot */ + if (r == -ESTALE) + continue; + else if (r < 0) + return log_error_errno(r, "Failed to get journal time: %m"); + + if (usec < not_before) + continue; + } + + line ++; + maybe_print_begin_newline(f, &flags); + + r = output_journal(f, j, mode, n_columns, flags, ellipsized); + if (r < 0) + return r; + } + + if (warn_cutoff && line < how_many && not_before > 0) { + sd_id128_t boot_id; + usec_t cutoff = 0; + + /* Check whether the cutoff line is too early */ + + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get boot id: %m"); + + r = sd_journal_get_cutoff_monotonic_usec(j, boot_id, &cutoff, NULL); + if (r < 0) + return log_error_errno(r, "Failed to get journal cutoff time: %m"); + + if (r > 0 && not_before < cutoff) { + maybe_print_begin_newline(f, &flags); + fprintf(f, "Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.\n"); + } + + warn_cutoff = false; + } + + if (!(flags & OUTPUT_FOLLOW)) + break; + + r = sd_journal_wait(j, USEC_INFINITY); + if (r < 0) + return log_error_errno(r, "Failed to wait for journal: %m"); + + } + + return 0; +} + +int add_matches_for_unit(sd_journal *j, const char *unit) { + int r; + char *m1, *m2, *m3, *m4; + + assert(j); + assert(unit); + + m1 = strjoina("_SYSTEMD_UNIT=", unit); + m2 = strjoina("COREDUMP_UNIT=", unit); + m3 = strjoina("UNIT=", unit); + m4 = strjoina("OBJECT_SYSTEMD_UNIT=", unit); + + (void)( + /* Look for messages from the service itself */ + (r = sd_journal_add_match(j, m1, 0)) || + + /* Look for coredumps of the service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) || + (r = sd_journal_add_match(j, m2, 0)) || + + /* Look for messages from PID 1 about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "_PID=1", 0)) || + (r = sd_journal_add_match(j, m3, 0)) || + + /* Look for messages from authorized daemons about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) || + (r = sd_journal_add_match(j, m4, 0)) + ); + + if (r == 0 && endswith(unit, ".slice")) { + char *m5 = strappend("_SYSTEMD_SLICE=", unit); + + /* Show all messages belonging to a slice */ + (void)( + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m5, 0)) + ); + } + + return r; +} + +int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) { + int r; + char *m1, *m2, *m3, *m4; + char muid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)]; + + assert(j); + assert(unit); + + m1 = strjoina("_SYSTEMD_USER_UNIT=", unit); + m2 = strjoina("USER_UNIT=", unit); + m3 = strjoina("COREDUMP_USER_UNIT=", unit); + m4 = strjoina("OBJECT_SYSTEMD_USER_UNIT=", unit); + sprintf(muid, "_UID="UID_FMT, uid); + + (void) ( + /* Look for messages from the user service itself */ + (r = sd_journal_add_match(j, m1, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || + + /* Look for messages from systemd about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m2, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || + + /* Look for coredumps of the service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m3, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) || + + /* Look for messages from authorized daemons about this service */ + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m4, 0)) || + (r = sd_journal_add_match(j, muid, 0)) || + (r = sd_journal_add_match(j, "_UID=0", 0)) + ); + + if (r == 0 && endswith(unit, ".slice")) { + char *m5 = strappend("_SYSTEMD_SLICE=", unit); + + /* Show all messages belonging to a slice */ + (void)( + (r = sd_journal_add_disjunction(j)) || + (r = sd_journal_add_match(j, m5, 0)) || + (r = sd_journal_add_match(j, muid, 0)) + ); + } + + return r; +} + +static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) { + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1; + pid_t pid, child; + siginfo_t si; + char buf[37]; + ssize_t k; + int r; + + assert(machine); + assert(boot_id); + + if (!machine_name_is_valid(machine)) + return -EINVAL; + + r = container_get_leader(machine, &pid); + if (r < 0) + return r; + + r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, NULL, &rootfd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return -errno; + + if (child == 0) { + int fd; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(pidnsfd, mntnsfd, -1, -1, rootfd); + if (r < 0) + _exit(EXIT_FAILURE); + + fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + _exit(EXIT_FAILURE); + + r = loop_read_exact(fd, buf, 36, false); + safe_close(fd); + if (r < 0) + _exit(EXIT_FAILURE); + + k = send(pair[1], buf, 36, MSG_NOSIGNAL); + if (k != 36) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate(child, &si); + if (r < 0 || si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return r < 0 ? r : -EIO; + + k = recv(pair[0], buf, 36, 0); + if (k != 36) + return -EIO; + + buf[36] = 0; + r = sd_id128_from_string(buf, boot_id); + if (r < 0) + return r; + + return 0; +} + +int add_match_this_boot(sd_journal *j, const char *machine) { + char match[9+32+1] = "_BOOT_ID="; + sd_id128_t boot_id; + int r; + + assert(j); + + if (machine) { + r = get_boot_id_for_machine(machine, &boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get boot id of container %s: %m", machine); + } else { + r = sd_id128_get_boot(&boot_id); + if (r < 0) + return log_error_errno(r, "Failed to get boot id: %m"); + } + + sd_id128_to_string(boot_id, match + 9); + r = sd_journal_add_match(j, match, strlen(match)); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + + r = sd_journal_add_conjunction(j); + if (r < 0) + return log_error_errno(r, "Failed to add conjunction: %m"); + + return 0; +} + +int show_journal_by_unit( + FILE *f, + const char *unit, + OutputMode mode, + unsigned n_columns, + usec_t not_before, + unsigned how_many, + uid_t uid, + OutputFlags flags, + int journal_open_flags, + bool system_unit, + bool *ellipsized) { + + _cleanup_(sd_journal_closep) sd_journal *j = NULL; + int r; + + assert(mode >= 0); + assert(mode < _OUTPUT_MODE_MAX); + assert(unit); + + if (how_many <= 0) + return 0; + + r = sd_journal_open(&j, journal_open_flags); + if (r < 0) + return log_error_errno(r, "Failed to open journal: %m"); + + r = add_match_this_boot(j, NULL); + if (r < 0) + return r; + + if (system_unit) + r = add_matches_for_unit(j, unit); + else + r = add_matches_for_user_unit(j, unit, uid); + if (r < 0) + return log_error_errno(r, "Failed to add unit matches: %m"); + + if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) { + _cleanup_free_ char *filter; + + filter = journal_make_match_string(j); + if (!filter) + return log_oom(); + + log_debug("Journal filter: %s", filter); + } + + return show_journal(f, j, mode, n_columns, not_before, how_many, flags, ellipsized); +} + +static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { + [OUTPUT_SHORT] = "short", + [OUTPUT_SHORT_ISO] = "short-iso", + [OUTPUT_SHORT_PRECISE] = "short-precise", + [OUTPUT_SHORT_MONOTONIC] = "short-monotonic", + [OUTPUT_VERBOSE] = "verbose", + [OUTPUT_EXPORT] = "export", + [OUTPUT_JSON] = "json", + [OUTPUT_JSON_PRETTY] = "json-pretty", + [OUTPUT_JSON_SSE] = "json-sse", + [OUTPUT_CAT] = "cat" +}; + +DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode); diff --git a/src/libshared/logs-show.h b/src/libshared/logs-show.h new file mode 100644 index 0000000000..682a4adc7b --- /dev/null +++ b/src/libshared/logs-show.h @@ -0,0 +1,73 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include +#include +#include +#include + +#include + +#include "macro.h" +#include "output-mode.h" +#include "time-util.h" +#include "util.h" + +int output_journal( + FILE *f, + sd_journal *j, + OutputMode mode, + unsigned n_columns, + OutputFlags flags, + bool *ellipsized); + +int add_match_this_boot(sd_journal *j, const char *machine); + +int add_matches_for_unit( + sd_journal *j, + const char *unit); + +int add_matches_for_user_unit( + sd_journal *j, + const char *unit, + uid_t uid); + +int show_journal_by_unit( + FILE *f, + const char *unit, + OutputMode mode, + unsigned n_columns, + usec_t not_before, + unsigned how_many, + uid_t uid, + OutputFlags flags, + int journal_open_flags, + bool system_unit, + bool *ellipsized); + +void json_escape( + FILE *f, + const char* p, + size_t l, + OutputFlags flags); + +const char* output_mode_to_string(OutputMode m) _const_; +OutputMode output_mode_from_string(const char *s) _pure_; diff --git a/src/libshared/machine-image.c b/src/libshared/machine-image.c new file mode 100644 index 0000000000..ed8a29c575 --- /dev/null +++ b/src/libshared/machine-image.c @@ -0,0 +1,813 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "alloc-util.h" +#include "btrfs-util.h" +#include "chattr-util.h" +#include "copy.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "lockfile-util.h" +#include "log.h" +#include "macro.h" +#include "machine-image.h" +#include "mkdir.h" +#include "path-util.h" +#include "rm-rf.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "utf8.h" +#include "util.h" +#include "xattr-util.h" + +static const char image_search_path[] = + "/var/lib/machines\0" + "/var/lib/container\0" /* legacy */ + "/usr/local/lib/machines\0" + "/usr/lib/machines\0"; + +Image *image_unref(Image *i) { + if (!i) + return NULL; + + free(i->name); + free(i->path); + free(i); + return NULL; +} + +static char **image_settings_path(Image *image) { + _cleanup_strv_free_ char **l = NULL; + char **ret; + const char *fn, *s; + unsigned i = 0; + + assert(image); + + l = new0(char*, 4); + if (!l) + return NULL; + + fn = strjoina(image->name, ".nspawn"); + + FOREACH_STRING(s, "/etc/systemd/nspawn/", "/run/systemd/nspawn/") { + l[i] = strappend(s, fn); + if (!l[i]) + return NULL; + + i++; + } + + l[i] = file_in_same_dir(image->path, fn); + if (!l[i]) + return NULL; + + ret = l; + l = NULL; + + return ret; +} + +static int image_new( + ImageType t, + const char *pretty, + const char *path, + const char *filename, + bool read_only, + usec_t crtime, + usec_t mtime, + Image **ret) { + + _cleanup_(image_unrefp) Image *i = NULL; + + assert(t >= 0); + assert(t < _IMAGE_TYPE_MAX); + assert(pretty); + assert(filename); + assert(ret); + + i = new0(Image, 1); + if (!i) + return -ENOMEM; + + i->type = t; + i->read_only = read_only; + i->crtime = crtime; + i->mtime = mtime; + i->usage = i->usage_exclusive = (uint64_t) -1; + i->limit = i->limit_exclusive = (uint64_t) -1; + + i->name = strdup(pretty); + if (!i->name) + return -ENOMEM; + + if (path) + i->path = strjoin(path, "/", filename, NULL); + else + i->path = strdup(filename); + + if (!i->path) + return -ENOMEM; + + path_kill_slashes(i->path); + + *ret = i; + i = NULL; + + return 0; +} + +static int image_make( + const char *pretty, + int dfd, + const char *path, + const char *filename, + Image **ret) { + + struct stat st; + bool read_only; + int r; + + assert(filename); + + /* We explicitly *do* follow symlinks here, since we want to + * allow symlinking trees into /var/lib/machines/, and treat + * them normally. */ + + if (fstatat(dfd, filename, &st, 0) < 0) + return -errno; + + read_only = + (path && path_startswith(path, "/usr")) || + (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS); + + if (S_ISDIR(st.st_mode)) { + _cleanup_close_ int fd = -1; + unsigned file_attr = 0; + + if (!ret) + return 1; + + if (!pretty) + pretty = filename; + + fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY); + if (fd < 0) + return -errno; + + /* btrfs subvolumes have inode 256 */ + if (st.st_ino == 256) { + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (r) { + BtrfsSubvolInfo info; + + /* It's a btrfs subvolume */ + + r = btrfs_subvol_get_info_fd(fd, 0, &info); + if (r < 0) + return r; + + r = image_new(IMAGE_SUBVOLUME, + pretty, + path, + filename, + info.read_only || read_only, + info.otime, + 0, + ret); + if (r < 0) + return r; + + if (btrfs_quota_scan_ongoing(fd) == 0) { + BtrfsQuotaInfo quota; + + r = btrfs_subvol_get_subtree_quota_fd(fd, 0, "a); + if (r >= 0) { + (*ret)->usage = quota.referenced; + (*ret)->usage_exclusive = quota.exclusive; + + (*ret)->limit = quota.referenced_max; + (*ret)->limit_exclusive = quota.exclusive_max; + } + } + + return 1; + } + } + + /* If the IMMUTABLE bit is set, we consider the + * directory read-only. Since the ioctl is not + * supported everywhere we ignore failures. */ + (void) read_attr_fd(fd, &file_attr); + + /* It's just a normal directory. */ + r = image_new(IMAGE_DIRECTORY, + pretty, + path, + filename, + read_only || (file_attr & FS_IMMUTABLE_FL), + 0, + 0, + ret); + if (r < 0) + return r; + + return 1; + + } else if (S_ISREG(st.st_mode) && endswith(filename, ".raw")) { + usec_t crtime = 0; + + /* It's a RAW disk image */ + + if (!ret) + return 1; + + fd_getcrtime_at(dfd, filename, &crtime, 0); + + if (!pretty) + pretty = strndupa(filename, strlen(filename) - 4); + + r = image_new(IMAGE_RAW, + pretty, + path, + filename, + !(st.st_mode & 0222) || read_only, + crtime, + timespec_load(&st.st_mtim), + ret); + if (r < 0) + return r; + + (*ret)->usage = (*ret)->usage_exclusive = st.st_blocks * 512; + (*ret)->limit = (*ret)->limit_exclusive = st.st_size; + + return 1; + } + + return 0; +} + +int image_find(const char *name, Image **ret) { + const char *path; + int r; + + assert(name); + + /* There are no images with invalid names */ + if (!image_name_is_valid(name)) + return 0; + + NULSTR_FOREACH(path, image_search_path) { + _cleanup_closedir_ DIR *d = NULL; + + d = opendir(path); + if (!d) { + if (errno == ENOENT) + continue; + + return -errno; + } + + r = image_make(NULL, dirfd(d), path, name, ret); + if (r == 0 || r == -ENOENT) { + _cleanup_free_ char *raw = NULL; + + raw = strappend(name, ".raw"); + if (!raw) + return -ENOMEM; + + r = image_make(NULL, dirfd(d), path, raw, ret); + if (r == 0 || r == -ENOENT) + continue; + } + if (r < 0) + return r; + + return 1; + } + + if (streq(name, ".host")) + return image_make(".host", AT_FDCWD, NULL, "/", ret); + + return 0; +}; + +int image_discover(Hashmap *h) { + const char *path; + int r; + + assert(h); + + NULSTR_FOREACH(path, image_search_path) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + + d = opendir(path); + if (!d) { + if (errno == ENOENT) + continue; + + return -errno; + } + + FOREACH_DIRENT_ALL(de, d, return -errno) { + _cleanup_(image_unrefp) Image *image = NULL; + + if (!image_name_is_valid(de->d_name)) + continue; + + if (hashmap_contains(h, de->d_name)) + continue; + + r = image_make(NULL, dirfd(d), path, de->d_name, &image); + if (r == 0 || r == -ENOENT) + continue; + if (r < 0) + return r; + + r = hashmap_put(h, image->name, image); + if (r < 0) + return r; + + image = NULL; + } + } + + if (!hashmap_contains(h, ".host")) { + _cleanup_(image_unrefp) Image *image = NULL; + + r = image_make(".host", AT_FDCWD, NULL, "/", &image); + if (r < 0) + return r; + + r = hashmap_put(h, image->name, image); + if (r < 0) + return r; + + image = NULL; + + } + + return 0; +} + +void image_hashmap_free(Hashmap *map) { + Image *i; + + while ((i = hashmap_steal_first(map))) + image_unref(i); + + hashmap_free(map); +} + +int image_remove(Image *i) { + _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT; + _cleanup_strv_free_ char **settings = NULL; + char **j; + int r; + + assert(i); + + if (path_equal(i->path, "/") || + path_startswith(i->path, "/usr")) + return -EROFS; + + settings = image_settings_path(i); + if (!settings) + return -ENOMEM; + + /* Make sure we don't interfere with a running nspawn */ + r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); + if (r < 0) + return r; + + switch (i->type) { + + case IMAGE_SUBVOLUME: + r = btrfs_subvol_remove(i->path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); + if (r < 0) + return r; + break; + + case IMAGE_DIRECTORY: + /* Allow deletion of read-only directories */ + (void) chattr_path(i->path, false, FS_IMMUTABLE_FL); + r = rm_rf(i->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); + if (r < 0) + return r; + + break; + + case IMAGE_RAW: + if (unlink(i->path) < 0) + return -errno; + break; + + default: + return -EOPNOTSUPP; + } + + STRV_FOREACH(j, settings) { + if (unlink(*j) < 0 && errno != ENOENT) + log_debug_errno(errno, "Failed to unlink %s, ignoring: %m", *j); + } + + return 0; +} + +static int rename_settings_file(const char *path, const char *new_name) { + _cleanup_free_ char *rs = NULL; + const char *fn; + + fn = strjoina(new_name, ".nspawn"); + + rs = file_in_same_dir(path, fn); + if (!rs) + return -ENOMEM; + + return rename_noreplace(AT_FDCWD, path, AT_FDCWD, rs); +} + +int image_rename(Image *i, const char *new_name) { + _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT, name_lock = LOCK_FILE_INIT; + _cleanup_free_ char *new_path = NULL, *nn = NULL; + _cleanup_strv_free_ char **settings = NULL; + unsigned file_attr = 0; + char **j; + int r; + + assert(i); + + if (!image_name_is_valid(new_name)) + return -EINVAL; + + if (path_equal(i->path, "/") || + path_startswith(i->path, "/usr")) + return -EROFS; + + settings = image_settings_path(i); + if (!settings) + return -ENOMEM; + + /* Make sure we don't interfere with a running nspawn */ + r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); + if (r < 0) + return r; + + /* Make sure nobody takes the new name, between the time we + * checked it is currently unused in all search paths, and the + * time we take possesion of it */ + r = image_name_lock(new_name, LOCK_EX|LOCK_NB, &name_lock); + if (r < 0) + return r; + + r = image_find(new_name, NULL); + if (r < 0) + return r; + if (r > 0) + return -EEXIST; + + switch (i->type) { + + case IMAGE_DIRECTORY: + /* Turn of the immutable bit while we rename the image, so that we can rename it */ + (void) read_attr_path(i->path, &file_attr); + + if (file_attr & FS_IMMUTABLE_FL) + (void) chattr_path(i->path, false, FS_IMMUTABLE_FL); + + /* fall through */ + + case IMAGE_SUBVOLUME: + new_path = file_in_same_dir(i->path, new_name); + break; + + case IMAGE_RAW: { + const char *fn; + + fn = strjoina(new_name, ".raw"); + new_path = file_in_same_dir(i->path, fn); + break; + } + + default: + return -EOPNOTSUPP; + } + + if (!new_path) + return -ENOMEM; + + nn = strdup(new_name); + if (!nn) + return -ENOMEM; + + r = rename_noreplace(AT_FDCWD, i->path, AT_FDCWD, new_path); + if (r < 0) + return r; + + /* Restore the immutable bit, if it was set before */ + if (file_attr & FS_IMMUTABLE_FL) + (void) chattr_path(new_path, true, FS_IMMUTABLE_FL); + + free(i->path); + i->path = new_path; + new_path = NULL; + + free(i->name); + i->name = nn; + nn = NULL; + + STRV_FOREACH(j, settings) { + r = rename_settings_file(*j, new_name); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to rename settings file %s, ignoring: %m", *j); + } + + return 0; +} + +static int clone_settings_file(const char *path, const char *new_name) { + _cleanup_free_ char *rs = NULL; + const char *fn; + + fn = strjoina(new_name, ".nspawn"); + + rs = file_in_same_dir(path, fn); + if (!rs) + return -ENOMEM; + + return copy_file_atomic(path, rs, 0664, false, 0); +} + +int image_clone(Image *i, const char *new_name, bool read_only) { + _cleanup_release_lock_file_ LockFile name_lock = LOCK_FILE_INIT; + _cleanup_strv_free_ char **settings = NULL; + const char *new_path; + char **j; + int r; + + assert(i); + + if (!image_name_is_valid(new_name)) + return -EINVAL; + + settings = image_settings_path(i); + if (!settings) + return -ENOMEM; + + /* Make sure nobody takes the new name, between the time we + * checked it is currently unused in all search paths, and the + * time we take possesion of it */ + r = image_name_lock(new_name, LOCK_EX|LOCK_NB, &name_lock); + if (r < 0) + return r; + + r = image_find(new_name, NULL); + if (r < 0) + return r; + if (r > 0) + return -EEXIST; + + switch (i->type) { + + case IMAGE_SUBVOLUME: + case IMAGE_DIRECTORY: + new_path = strjoina("/var/lib/machines/", new_name); + + r = btrfs_subvol_snapshot(i->path, new_path, (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); + + /* Enable "subtree" quotas for the copy, if we didn't + * copy any quota from the source. */ + (void) btrfs_subvol_auto_qgroup(i->path, 0, true); + + break; + + case IMAGE_RAW: + new_path = strjoina("/var/lib/machines/", new_name, ".raw"); + + r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, false, FS_NOCOW_FL); + break; + + default: + return -EOPNOTSUPP; + } + + if (r < 0) + return r; + + STRV_FOREACH(j, settings) { + r = clone_settings_file(*j, new_name); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to clone settings %s, ignoring: %m", *j); + } + + return 0; +} + +int image_read_only(Image *i, bool b) { + _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT; + int r; + assert(i); + + if (path_equal(i->path, "/") || + path_startswith(i->path, "/usr")) + return -EROFS; + + /* Make sure we don't interfere with a running nspawn */ + r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); + if (r < 0) + return r; + + switch (i->type) { + + case IMAGE_SUBVOLUME: + + /* Note that we set the flag only on the top-level + * subvolume of the image. */ + + r = btrfs_subvol_set_read_only(i->path, b); + if (r < 0) + return r; + + break; + + case IMAGE_DIRECTORY: + /* For simple directory trees we cannot use the access + mode of the top-level directory, since it has an + effect on the container itself. However, we can + use the "immutable" flag, to at least make the + top-level directory read-only. It's not as good as + a read-only subvolume, but at least something, and + we can read the value back.*/ + + r = chattr_path(i->path, b, FS_IMMUTABLE_FL); + if (r < 0) + return r; + + break; + + case IMAGE_RAW: { + struct stat st; + + if (stat(i->path, &st) < 0) + return -errno; + + if (chmod(i->path, (st.st_mode & 0444) | (b ? 0000 : 0200)) < 0) + return -errno; + + /* If the images is now read-only, it's a good time to + * defrag it, given that no write patterns will + * fragment it again. */ + if (b) + (void) btrfs_defrag(i->path); + break; + } + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local) { + _cleanup_free_ char *p = NULL; + LockFile t = LOCK_FILE_INIT; + struct stat st; + int r; + + assert(path); + assert(global); + assert(local); + + /* Locks an image path. This actually creates two locks: one + * "local" one, next to the image path itself, which might be + * shared via NFS. And another "global" one, in /run, that + * uses the device/inode number. This has the benefit that we + * can even lock a tree that is a mount point, correctly. */ + + if (path_equal(path, "/")) + return -EBUSY; + + if (!path_is_absolute(path)) + return -EINVAL; + + if (stat(path, &st) >= 0) { + if (asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino) < 0) + return -ENOMEM; + } + + r = make_lock_file_for(path, operation, &t); + if (r < 0) + return r; + + if (p) { + mkdir_p("/run/systemd/nspawn/locks", 0700); + + r = make_lock_file(p, operation, global); + if (r < 0) { + release_lock_file(&t); + return r; + } + } + + *local = t; + return 0; +} + +int image_set_limit(Image *i, uint64_t referenced_max) { + assert(i); + + if (path_equal(i->path, "/") || + path_startswith(i->path, "/usr")) + return -EROFS; + + if (i->type != IMAGE_SUBVOLUME) + return -EOPNOTSUPP; + + /* We set the quota both for the subvolume as well as for the + * subtree. The latter is mostly for historical reasons, since + * we didn't use to have a concept of subtree quota, and hence + * only modified the subvolume quota. */ + + (void) btrfs_qgroup_set_limit(i->path, 0, referenced_max); + (void) btrfs_subvol_auto_qgroup(i->path, 0, true); + return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max); +} + +int image_name_lock(const char *name, int operation, LockFile *ret) { + const char *p; + + assert(name); + assert(ret); + + /* Locks an image name, regardless of the precise path used. */ + + if (!image_name_is_valid(name)) + return -EINVAL; + + if (streq(name, ".host")) + return -EBUSY; + + mkdir_p("/run/systemd/nspawn/locks", 0700); + p = strjoina("/run/systemd/nspawn/locks/name-", name); + + return make_lock_file(p, operation, ret); +} + +bool image_name_is_valid(const char *s) { + if (!filename_is_valid(s)) + return false; + + if (string_has_cc(s, NULL)) + return false; + + if (!utf8_is_valid(s)) + return false; + + /* Temporary files for atomically creating new files */ + if (startswith(s, ".#")) + return false; + + return true; +} + +static const char* const image_type_table[_IMAGE_TYPE_MAX] = { + [IMAGE_DIRECTORY] = "directory", + [IMAGE_SUBVOLUME] = "subvolume", + [IMAGE_RAW] = "raw", +}; + +DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType); diff --git a/src/libshared/machine-image.h b/src/libshared/machine-image.h new file mode 100644 index 0000000000..31b720d50c --- /dev/null +++ b/src/libshared/machine-image.h @@ -0,0 +1,77 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include + +#include "hashmap.h" +#include "lockfile-util.h" +#include "macro.h" +#include "time-util.h" + +typedef enum ImageType { + IMAGE_DIRECTORY, + IMAGE_SUBVOLUME, + IMAGE_RAW, + _IMAGE_TYPE_MAX, + _IMAGE_TYPE_INVALID = -1 +} ImageType; + +typedef struct Image { + ImageType type; + char *name; + char *path; + bool read_only; + + usec_t crtime; + usec_t mtime; + + uint64_t usage; + uint64_t usage_exclusive; + uint64_t limit; + uint64_t limit_exclusive; + + void *userdata; +} Image; + +Image *image_unref(Image *i); +void image_hashmap_free(Hashmap *map); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Image*, image_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, image_hashmap_free); + +int image_find(const char *name, Image **ret); +int image_discover(Hashmap *map); + +int image_remove(Image *i); +int image_rename(Image *i, const char *new_name); +int image_clone(Image *i, const char *new_name, bool read_only); +int image_read_only(Image *i, bool b); + +const char* image_type_to_string(ImageType t) _const_; +ImageType image_type_from_string(const char *s) _pure_; + +bool image_name_is_valid(const char *s) _pure_; + +int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local); +int image_name_lock(const char *name, int operation, LockFile *ret); + +int image_set_limit(Image *i, uint64_t referenced_max); diff --git a/src/libshared/machine-pool.c b/src/libshared/machine-pool.c new file mode 100644 index 0000000000..056b5bfae5 --- /dev/null +++ b/src/libshared/machine-pool.c @@ -0,0 +1,427 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "alloc-util.h" +#include "btrfs-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "lockfile-util.h" +#include "log.h" +#include "machine-pool.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "stat-util.h" +#include "string-util.h" + +#define VAR_LIB_MACHINES_SIZE_START (1024UL*1024UL*500UL) +#define VAR_LIB_MACHINES_FREE_MIN (1024UL*1024UL*750UL) + +static int check_btrfs(void) { + struct statfs sfs; + + if (statfs("/var/lib/machines", &sfs) < 0) { + if (errno != ENOENT) + return -errno; + + if (statfs("/var/lib", &sfs) < 0) + return -errno; + } + + return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC); +} + +static int setup_machine_raw(uint64_t size, sd_bus_error *error) { + _cleanup_free_ char *tmp = NULL; + _cleanup_close_ int fd = -1; + struct statvfs ss; + pid_t pid = 0; + siginfo_t si; + int r; + + /* We want to be able to make use of btrfs-specific file + * system features, in particular subvolumes, reflinks and + * quota. Hence, if we detect that /var/lib/machines.raw is + * not located on btrfs, let's create a loopback file, place a + * btrfs file system into it, and mount it to + * /var/lib/machines. */ + + fd = open("/var/lib/machines.raw", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd >= 0) { + r = fd; + fd = -1; + return r; + } + + if (errno != ENOENT) + return sd_bus_error_set_errnof(error, errno, "Failed to open /var/lib/machines.raw: %m"); + + r = tempfn_xxxxxx("/var/lib/machines.raw", NULL, &tmp); + if (r < 0) + return r; + + (void) mkdir_p_label("/var/lib", 0755); + fd = open(tmp, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0600); + if (fd < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to create /var/lib/machines.raw: %m"); + + if (fstatvfs(fd, &ss) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to determine free space on /var/lib/machines.raw: %m"); + goto fail; + } + + if (ss.f_bsize * ss.f_bavail < VAR_LIB_MACHINES_FREE_MIN) { + r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Not enough free disk space to set up /var/lib/machines."); + goto fail; + } + + if (ftruncate(fd, size) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to enlarge /var/lib/machines.raw: %m"); + goto fail; + } + + pid = fork(); + if (pid < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to fork mkfs.btrfs: %m"); + goto fail; + } + + if (pid == 0) { + + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + fd = safe_close(fd); + + execlp("mkfs.btrfs", "-Lvar-lib-machines", tmp, NULL); + if (errno == ENOENT) + return 99; + + _exit(EXIT_FAILURE); + } + + r = wait_for_terminate(pid, &si); + if (r < 0) { + sd_bus_error_set_errnof(error, r, "Failed to wait for mkfs.btrfs: %m"); + goto fail; + } + + pid = 0; + + if (si.si_code != CLD_EXITED) { + r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "mkfs.btrfs died abnormally."); + goto fail; + } + if (si.si_status == 99) { + r = sd_bus_error_set_errnof(error, ENOENT, "Cannot set up /var/lib/machines, mkfs.btrfs is missing"); + goto fail; + } + if (si.si_status != 0) { + r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "mkfs.btrfs failed with error code %i", si.si_status); + goto fail; + } + + r = rename_noreplace(AT_FDCWD, tmp, AT_FDCWD, "/var/lib/machines.raw"); + if (r < 0) { + sd_bus_error_set_errnof(error, r, "Failed to move /var/lib/machines.raw into place: %m"); + goto fail; + } + + r = fd; + fd = -1; + + return r; + +fail: + unlink_noerrno(tmp); + + if (pid > 1) + kill_and_sigcont(pid, SIGKILL); + + return r; +} + +int setup_machine_directory(uint64_t size, sd_bus_error *error) { + _cleanup_release_lock_file_ LockFile lock_file = LOCK_FILE_INIT; + struct loop_info64 info = { + .lo_flags = LO_FLAGS_AUTOCLEAR, + }; + _cleanup_close_ int fd = -1, control = -1, loop = -1; + _cleanup_free_ char* loopdev = NULL; + char tmpdir[] = "/tmp/machine-pool.XXXXXX", *mntdir = NULL; + bool tmpdir_made = false, mntdir_made = false, mntdir_mounted = false; + char buf[FORMAT_BYTES_MAX]; + int r, nr = -1; + + /* btrfs cannot handle file systems < 16M, hence use this as minimum */ + if (size == (uint64_t) -1) + size = VAR_LIB_MACHINES_SIZE_START; + else if (size < 16*1024*1024) + size = 16*1024*1024; + + /* Make sure we only set the directory up once at a time */ + r = make_lock_file("/run/systemd/machines.lock", LOCK_EX, &lock_file); + if (r < 0) + return r; + + r = check_btrfs(); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to determine whether /var/lib/machines is located on btrfs: %m"); + if (r > 0) { + (void) btrfs_subvol_make_label("/var/lib/machines"); + + r = btrfs_quota_enable("/var/lib/machines", true); + if (r < 0) + log_warning_errno(r, "Failed to enable quota for /var/lib/machines, ignoring: %m"); + + r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true); + if (r < 0) + log_warning_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, ignoring: %m"); + + return 1; + } + + if (path_is_mount_point("/var/lib/machines", AT_SYMLINK_FOLLOW) > 0) { + log_debug("/var/lib/machines is already a mount point, not creating loopback file for it."); + return 0; + } + + r = dir_is_populated("/var/lib/machines"); + if (r < 0 && r != -ENOENT) + return r; + if (r > 0) { + log_debug("/var/log/machines is already populated, not creating loopback file for it."); + return 0; + } + + r = mkfs_exists("btrfs"); + if (r == -ENOENT) { + log_debug("mkfs.btrfs is missing, cannot create loopback file for /var/lib/machines."); + return 0; + } + if (r < 0) + return r; + + fd = setup_machine_raw(size, error); + if (fd < 0) + return fd; + + control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + if (control < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to open /dev/loop-control: %m"); + + nr = ioctl(control, LOOP_CTL_GET_FREE); + if (nr < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to allocate loop device: %m"); + + if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) { + r = -ENOMEM; + goto fail; + } + + loop = open(loopdev, O_CLOEXEC|O_RDWR|O_NOCTTY|O_NONBLOCK); + if (loop < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to open loopback device: %m"); + goto fail; + } + + if (ioctl(loop, LOOP_SET_FD, fd) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to bind loopback device: %m"); + goto fail; + } + + if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to enable auto-clear for loopback device: %m"); + goto fail; + } + + /* We need to make sure the new /var/lib/machines directory + * has an access mode of 0700 at the time it is first made + * available. mkfs will create it with 0755 however. Hence, + * let's mount the directory into an inaccessible directory + * below /tmp first, fix the access mode, and move it to the + * public place then. */ + + if (!mkdtemp(tmpdir)) { + r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount parent directory: %m"); + goto fail; + } + tmpdir_made = true; + + mntdir = strjoina(tmpdir, "/mnt"); + if (mkdir(mntdir, 0700) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount directory: %m"); + goto fail; + } + mntdir_made = true; + + if (mount(loopdev, mntdir, "btrfs", 0, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to mount loopback device: %m"); + goto fail; + } + mntdir_mounted = true; + + r = btrfs_quota_enable(mntdir, true); + if (r < 0) + log_warning_errno(r, "Failed to enable quota, ignoring: %m"); + + r = btrfs_subvol_auto_qgroup(mntdir, 0, true); + if (r < 0) + log_warning_errno(r, "Failed to set up default quota hierarchy, ignoring: %m"); + + if (chmod(mntdir, 0700) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to fix owner: %m"); + goto fail; + } + + (void) mkdir_p_label("/var/lib/machines", 0700); + + if (mount(mntdir, "/var/lib/machines", NULL, MS_BIND, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to mount directory into right place: %m"); + goto fail; + } + + (void) syncfs(fd); + + log_info("Set up /var/lib/machines as btrfs loopback file system of size %s mounted on /var/lib/machines.raw.", format_bytes(buf, sizeof(buf), size)); + + (void) umount2(mntdir, MNT_DETACH); + (void) rmdir(mntdir); + (void) rmdir(tmpdir); + + return 1; + +fail: + if (mntdir_mounted) + (void) umount2(mntdir, MNT_DETACH); + + if (mntdir_made) + (void) rmdir(mntdir); + if (tmpdir_made) + (void) rmdir(tmpdir); + + if (loop >= 0) { + (void) ioctl(loop, LOOP_CLR_FD); + loop = safe_close(loop); + } + + if (control >= 0 && nr >= 0) + (void) ioctl(control, LOOP_CTL_REMOVE, nr); + + return r; +} + +static int sync_path(const char *p) { + _cleanup_close_ int fd = -1; + + fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + if (syncfs(fd) < 0) + return -errno; + + return 0; +} + +int grow_machine_directory(void) { + char buf[FORMAT_BYTES_MAX]; + struct statvfs a, b; + uint64_t old_size, new_size, max_add; + int r; + + /* Ensure the disk space data is accurate */ + sync_path("/var/lib/machines"); + sync_path("/var/lib/machines.raw"); + + if (statvfs("/var/lib/machines.raw", &a) < 0) + return -errno; + + if (statvfs("/var/lib/machines", &b) < 0) + return -errno; + + /* Don't grow if not enough disk space is available on the host */ + if (((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) <= VAR_LIB_MACHINES_FREE_MIN) + return 0; + + /* Don't grow if at least 1/3th of the fs is still free */ + if (b.f_bavail > b.f_blocks / 3) + return 0; + + /* Calculate how much we are willing to add at most */ + max_add = ((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) - VAR_LIB_MACHINES_FREE_MIN; + + /* Calculate the old size */ + old_size = (uint64_t) b.f_blocks * (uint64_t) b.f_bsize; + + /* Calculate the new size as three times the size of what is used right now */ + new_size = ((uint64_t) b.f_blocks - (uint64_t) b.f_bavail) * (uint64_t) b.f_bsize * 3; + + /* Always, grow at least to the start size */ + if (new_size < VAR_LIB_MACHINES_SIZE_START) + new_size = VAR_LIB_MACHINES_SIZE_START; + + /* If the new size is smaller than the old size, don't grow */ + if (new_size < old_size) + return 0; + + /* Ensure we never add more than the maximum */ + if (new_size > old_size + max_add) + new_size = old_size + max_add; + + r = btrfs_resize_loopback("/var/lib/machines", new_size, true); + if (r <= 0) + return r; + + /* Also bump the quota, of both the subvolume leaf qgroup, as + * well as of any subtree quota group by the same id but a + * higher level, if it exists. */ + (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, new_size); + (void) btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, new_size); + + log_info("Grew /var/lib/machines btrfs loopback file system to %s.", format_bytes(buf, sizeof(buf), new_size)); + return 1; +} diff --git a/src/libshared/machine-pool.h b/src/libshared/machine-pool.h new file mode 100644 index 0000000000..fe99b7e0ae --- /dev/null +++ b/src/libshared/machine-pool.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include + +#include + +/* Grow the /var/lib/machines directory after each 10MiB written */ +#define GROW_INTERVAL_BYTES (UINT64_C(10) * UINT64_C(1024) * UINT64_C(1024)) + +int setup_machine_directory(uint64_t size, sd_bus_error *error); +int grow_machine_directory(void); diff --git a/src/libshared/output-mode.h b/src/libshared/output-mode.h new file mode 100644 index 0000000000..c5470e7c1b --- /dev/null +++ b/src/libshared/output-mode.h @@ -0,0 +1,46 @@ +#pragma once + +/*** + 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 . +***/ + +typedef enum OutputMode { + OUTPUT_SHORT, + OUTPUT_SHORT_ISO, + OUTPUT_SHORT_PRECISE, + OUTPUT_SHORT_MONOTONIC, + OUTPUT_VERBOSE, + OUTPUT_EXPORT, + OUTPUT_JSON, + OUTPUT_JSON_PRETTY, + OUTPUT_JSON_SSE, + OUTPUT_CAT, + _OUTPUT_MODE_MAX, + _OUTPUT_MODE_INVALID = -1 +} OutputMode; + +typedef enum OutputFlags { + OUTPUT_SHOW_ALL = 1 << 0, + OUTPUT_FOLLOW = 1 << 1, + OUTPUT_WARN_CUTOFF = 1 << 2, + OUTPUT_FULL_WIDTH = 1 << 3, + OUTPUT_COLOR = 1 << 4, + OUTPUT_CATALOG = 1 << 5, + OUTPUT_BEGIN_NEWLINE = 1 << 6, + OUTPUT_UTC = 1 << 7, +} OutputFlags; diff --git a/src/libshared/pager.c b/src/libshared/pager.c new file mode 100644 index 0000000000..05b2b15e40 --- /dev/null +++ b/src/libshared/pager.c @@ -0,0 +1,223 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "copy.h" +#include "fd-util.h" +#include "locale-util.h" +#include "log.h" +#include "macro.h" +#include "pager.h" +#include "process-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "terminal-util.h" + +static pid_t pager_pid = 0; + +noreturn static void pager_fallback(void) { + int r; + + r = copy_bytes(STDIN_FILENO, STDOUT_FILENO, (uint64_t) -1, false); + if (r < 0) { + log_error_errno(r, "Internal pager failed: %m"); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); +} + +int pager_open(bool jump_to_end) { + _cleanup_close_pair_ int fd[2] = { -1, -1 }; + const char *pager; + pid_t parent_pid; + + if (pager_pid > 0) + return 1; + + if (!on_tty()) + return 0; + + pager = getenv("SYSTEMD_PAGER"); + if (!pager) + pager = getenv("PAGER"); + + /* If the pager is explicitly turned off, honour it */ + if (pager && (pager[0] == 0 || streq(pager, "cat"))) + return 0; + + /* Determine and cache number of columns before we spawn the + * pager so that we get the value from the actual tty */ + (void) columns(); + + if (pipe(fd) < 0) + return log_error_errno(errno, "Failed to create pager pipe: %m"); + + parent_pid = getpid(); + + pager_pid = fork(); + if (pager_pid < 0) + return log_error_errno(errno, "Failed to fork pager: %m"); + + /* In the child start the pager */ + if (pager_pid == 0) { + const char* less_opts, *less_charset; + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + (void) dup2(fd[0], STDIN_FILENO); + safe_close_pair(fd); + + /* Initialize a good set of less options */ + less_opts = getenv("SYSTEMD_LESS"); + if (!less_opts) + less_opts = "FRSXMK"; + if (jump_to_end) + less_opts = strjoina(less_opts, " +G"); + setenv("LESS", less_opts, 1); + + /* Initialize a good charset for less. This is + * particularly important if we output UTF-8 + * characters. */ + less_charset = getenv("SYSTEMD_LESSCHARSET"); + if (!less_charset && is_locale_utf8()) + less_charset = "utf-8"; + if (less_charset) + setenv("LESSCHARSET", less_charset, 1); + + /* Make sure the pager goes away when the parent dies */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); + + /* Check whether our parent died before we were able + * to set the death signal */ + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); + + if (pager) { + execlp(pager, pager, NULL); + execl("/bin/sh", "sh", "-c", pager, NULL); + } + + /* Debian's alternatives command for pagers is + * called 'pager'. Note that we do not call + * sensible-pagers here, since that is just a + * shell script that implements a logic that + * is similar to this one anyway, but is + * Debian-specific. */ + execlp("pager", "pager", NULL); + + execlp("less", "less", NULL); + execlp("more", "more", NULL); + + pager_fallback(); + /* not reached */ + } + + /* Return in the parent */ + if (dup2(fd[1], STDOUT_FILENO) < 0) + return log_error_errno(errno, "Failed to duplicate pager pipe: %m"); + if (dup2(fd[1], STDERR_FILENO) < 0) + return log_error_errno(errno, "Failed to duplicate pager pipe: %m"); + + return 1; +} + +void pager_close(void) { + + if (pager_pid <= 0) + return; + + /* Inform pager that we are done */ + stdout = safe_fclose(stdout); + stderr = safe_fclose(stderr); + + (void) kill(pager_pid, SIGCONT); + (void) wait_for_terminate(pager_pid, NULL); + pager_pid = 0; +} + +bool pager_have(void) { + return pager_pid > 0; +} + +int show_man_page(const char *desc, bool null_stdio) { + const char *args[4] = { "man", NULL, NULL, NULL }; + char *e = NULL; + pid_t pid; + size_t k; + int r; + siginfo_t status; + + k = strlen(desc); + + if (desc[k-1] == ')') + e = strrchr(desc, '('); + + if (e) { + char *page = NULL, *section = NULL; + + page = strndupa(desc, e - desc); + section = strndupa(e + 1, desc + k - e - 2); + + args[1] = section; + args[2] = page; + } else + args[1] = desc; + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + + if (pid == 0) { + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + if (null_stdio) { + r = make_null_stdio(); + if (r < 0) { + log_error_errno(r, "Failed to kill stdio: %m"); + _exit(EXIT_FAILURE); + } + } + + execvp(args[0], (char**) args); + log_error_errno(errno, "Failed to execute man: %m"); + _exit(EXIT_FAILURE); + } + + r = wait_for_terminate(pid, &status); + if (r < 0) + return r; + + log_debug("Exit code %i status %i", status.si_code, status.si_status); + return status.si_status; +} diff --git a/src/libshared/pager.h b/src/libshared/pager.h new file mode 100644 index 0000000000..9fb05796bb --- /dev/null +++ b/src/libshared/pager.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "macro.h" + +int pager_open(bool jump_to_end); +void pager_close(void); +bool pager_have(void) _pure_; + +int show_man_page(const char *page, bool null_stdio); diff --git a/src/libshared/path-lookup.c b/src/libshared/path-lookup.c new file mode 100644 index 0000000000..5410620725 --- /dev/null +++ b/src/libshared/path-lookup.c @@ -0,0 +1,441 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "install.h" +#include "log.h" +#include "macro.h" +#include "path-lookup.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +int user_config_home(char **config_home) { + const char *e; + char *r; + + e = getenv("XDG_CONFIG_HOME"); + if (e) { + r = strappend(e, "/systemd/user"); + if (!r) + return -ENOMEM; + + *config_home = r; + return 1; + } else { + const char *home; + + home = getenv("HOME"); + if (home) { + r = strappend(home, "/.config/systemd/user"); + if (!r) + return -ENOMEM; + + *config_home = r; + return 1; + } + } + + return 0; +} + +int user_runtime_dir(char **runtime_dir) { + const char *e; + char *r; + + e = getenv("XDG_RUNTIME_DIR"); + if (e) { + r = strappend(e, "/systemd/user"); + if (!r) + return -ENOMEM; + + *runtime_dir = r; + return 1; + } + + return 0; +} + +static int user_data_home_dir(char **dir, const char *suffix) { + const char *e; + char *res; + + /* We don't treat /etc/xdg/systemd here as the spec + * suggests because we assume that that is a link to + * /etc/systemd/ anyway. */ + + e = getenv("XDG_DATA_HOME"); + if (e) + res = strappend(e, suffix); + else { + const char *home; + + home = getenv("HOME"); + if (home) + res = strjoin(home, "/.local/share", suffix, NULL); + else + return 0; + } + if (!res) + return -ENOMEM; + + *dir = res; + return 0; +} + +static char** user_dirs( + const char *generator, + const char *generator_early, + const char *generator_late) { + + const char * const config_unit_paths[] = { + USER_CONFIG_UNIT_PATH, + "/etc/systemd/user", + NULL + }; + + const char * const runtime_unit_path = "/run/systemd/user"; + + const char * const data_unit_paths[] = { + "/usr/local/lib/systemd/user", + "/usr/local/share/systemd/user", + USER_DATA_UNIT_PATH, + "/usr/lib/systemd/user", + "/usr/share/systemd/user", + NULL + }; + + const char *e; + _cleanup_free_ char *config_home = NULL, *runtime_dir = NULL, *data_home = NULL; + _cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL; + _cleanup_free_ char **res = NULL; + char **tmp; + int r; + + /* Implement the mechanisms defined in + * + * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html + * + * We look in both the config and the data dirs because we + * want to encourage that distributors ship their unit files + * as data, and allow overriding as configuration. + */ + + if (user_config_home(&config_home) < 0) + return NULL; + + if (user_runtime_dir(&runtime_dir) < 0) + return NULL; + + e = getenv("XDG_CONFIG_DIRS"); + if (e) { + config_dirs = strv_split(e, ":"); + if (!config_dirs) + return NULL; + } + + r = user_data_home_dir(&data_home, "/systemd/user"); + if (r < 0) + return NULL; + + e = getenv("XDG_DATA_DIRS"); + if (e) + data_dirs = strv_split(e, ":"); + else + data_dirs = strv_new("/usr/local/share", + "/usr/share", + NULL); + if (!data_dirs) + return NULL; + + /* Now merge everything we found. */ + if (generator_early) + if (strv_extend(&res, generator_early) < 0) + return NULL; + + if (config_home) + if (strv_extend(&res, config_home) < 0) + return NULL; + + if (!strv_isempty(config_dirs)) + if (strv_extend_strv_concat(&res, config_dirs, "/systemd/user") < 0) + return NULL; + + if (strv_extend_strv(&res, (char**) config_unit_paths, false) < 0) + return NULL; + + if (runtime_dir) + if (strv_extend(&res, runtime_dir) < 0) + return NULL; + + if (strv_extend(&res, runtime_unit_path) < 0) + return NULL; + + if (generator) + if (strv_extend(&res, generator) < 0) + return NULL; + + if (data_home) + if (strv_extend(&res, data_home) < 0) + return NULL; + + if (!strv_isempty(data_dirs)) + if (strv_extend_strv_concat(&res, data_dirs, "/systemd/user") < 0) + return NULL; + + if (strv_extend_strv(&res, (char**) data_unit_paths, false) < 0) + return NULL; + + if (generator_late) + if (strv_extend(&res, generator_late) < 0) + return NULL; + + if (path_strv_make_absolute_cwd(res) < 0) + return NULL; + + tmp = res; + res = NULL; + return tmp; +} + +char **generator_paths(ManagerRunningAs running_as) { + if (running_as == MANAGER_USER) + return strv_new("/run/systemd/user-generators", + "/etc/systemd/user-generators", + "/usr/local/lib/systemd/user-generators", + USER_GENERATOR_PATH, + NULL); + else + return strv_new("/run/systemd/system-generators", + "/etc/systemd/system-generators", + "/usr/local/lib/systemd/system-generators", + SYSTEM_GENERATOR_PATH, + NULL); +} + +int lookup_paths_init( + LookupPaths *p, + ManagerRunningAs running_as, + bool personal, + const char *root_dir, + const char *generator, + const char *generator_early, + const char *generator_late) { + + const char *e; + bool append = false; /* Add items from SYSTEMD_UNIT_PATH before normal directories */ + int r; + + assert(p); + + /* First priority is whatever has been passed to us via env + * vars */ + e = getenv("SYSTEMD_UNIT_PATH"); + if (e) { + if (endswith(e, ":")) { + e = strndupa(e, strlen(e) - 1); + append = true; + } + + /* FIXME: empty components in other places should be + * rejected. */ + + r = path_split_and_make_absolute(e, &p->unit_path); + if (r < 0) + return r; + } else + p->unit_path = NULL; + + if (!p->unit_path || append) { + /* Let's figure something out. */ + + _cleanup_strv_free_ char **unit_path; + + /* For the user units we include share/ in the search + * path in order to comply with the XDG basedir spec. + * For the system stuff we avoid such nonsense. OTOH + * we include /lib in the search path for the system + * stuff but avoid it for user stuff. */ + + if (running_as == MANAGER_USER) { + if (personal) + unit_path = user_dirs(generator, generator_early, generator_late); + else + unit_path = strv_new( + /* If you modify this you also want to modify + * systemduserunitpath= in systemd.pc.in, and + * the arrays in user_dirs() above! */ + STRV_IFNOTNULL(generator_early), + USER_CONFIG_UNIT_PATH, + "/etc/systemd/user", + "/run/systemd/user", + STRV_IFNOTNULL(generator), + "/usr/local/lib/systemd/user", + "/usr/local/share/systemd/user", + USER_DATA_UNIT_PATH, + "/usr/lib/systemd/user", + "/usr/share/systemd/user", + STRV_IFNOTNULL(generator_late), + NULL); + } else + unit_path = strv_new( + /* If you modify this you also want to modify + * systemdsystemunitpath= in systemd.pc.in! */ + STRV_IFNOTNULL(generator_early), + SYSTEM_CONFIG_UNIT_PATH, + "/etc/systemd/system", + "/run/systemd/system", + STRV_IFNOTNULL(generator), + "/usr/local/lib/systemd/system", + SYSTEM_DATA_UNIT_PATH, + "/usr/lib/systemd/system", +#ifdef HAVE_SPLIT_USR + "/lib/systemd/system", +#endif + STRV_IFNOTNULL(generator_late), + NULL); + + if (!unit_path) + return -ENOMEM; + + r = strv_extend_strv(&p->unit_path, unit_path, false); + if (r < 0) + return r; + } + + if (!path_strv_resolve_uniq(p->unit_path, root_dir)) + return -ENOMEM; + + if (!strv_isempty(p->unit_path)) { + _cleanup_free_ char *t = strv_join(p->unit_path, "\n\t"); + if (!t) + return -ENOMEM; + log_debug("Looking for unit files in (higher priority first):\n\t%s", t); + } else { + log_debug("Ignoring unit files."); + p->unit_path = strv_free(p->unit_path); + } + + if (running_as == MANAGER_SYSTEM) { +#ifdef HAVE_SYSV_COMPAT + /* /etc/init.d/ compatibility does not matter to users */ + + e = getenv("SYSTEMD_SYSVINIT_PATH"); + if (e) { + r = path_split_and_make_absolute(e, &p->sysvinit_path); + if (r < 0) + return r; + } else + p->sysvinit_path = NULL; + + if (strv_isempty(p->sysvinit_path)) { + strv_free(p->sysvinit_path); + + p->sysvinit_path = strv_new( + SYSTEM_SYSVINIT_PATH, /* /etc/init.d/ */ + NULL); + if (!p->sysvinit_path) + return -ENOMEM; + } + + e = getenv("SYSTEMD_SYSVRCND_PATH"); + if (e) { + r = path_split_and_make_absolute(e, &p->sysvrcnd_path); + if (r < 0) + return r; + } else + p->sysvrcnd_path = NULL; + + if (strv_isempty(p->sysvrcnd_path)) { + strv_free(p->sysvrcnd_path); + + p->sysvrcnd_path = strv_new( + SYSTEM_SYSVRCND_PATH, /* /etc/rcN.d/ */ + NULL); + if (!p->sysvrcnd_path) + return -ENOMEM; + } + + if (!path_strv_resolve_uniq(p->sysvinit_path, root_dir)) + return -ENOMEM; + + if (!path_strv_resolve_uniq(p->sysvrcnd_path, root_dir)) + return -ENOMEM; + + if (!strv_isempty(p->sysvinit_path)) { + _cleanup_free_ char *t = strv_join(p->sysvinit_path, "\n\t"); + if (!t) + return -ENOMEM; + log_debug("Looking for SysV init scripts in:\n\t%s", t); + } else { + log_debug("Ignoring SysV init scripts."); + p->sysvinit_path = strv_free(p->sysvinit_path); + } + + if (!strv_isempty(p->sysvrcnd_path)) { + _cleanup_free_ char *t = + strv_join(p->sysvrcnd_path, "\n\t"); + if (!t) + return -ENOMEM; + + log_debug("Looking for SysV rcN.d links in:\n\t%s", t); + } else { + log_debug("Ignoring SysV rcN.d links."); + p->sysvrcnd_path = strv_free(p->sysvrcnd_path); + } +#else + log_debug("SysV init scripts and rcN.d links support disabled"); +#endif + } + + return 0; +} + +void lookup_paths_free(LookupPaths *p) { + assert(p); + + p->unit_path = strv_free(p->unit_path); + +#ifdef HAVE_SYSV_COMPAT + p->sysvinit_path = strv_free(p->sysvinit_path); + p->sysvrcnd_path = strv_free(p->sysvrcnd_path); +#endif +} + +int lookup_paths_init_from_scope(LookupPaths *paths, + UnitFileScope scope, + const char *root_dir) { + assert(paths); + assert(scope >= 0); + assert(scope < _UNIT_FILE_SCOPE_MAX); + + zero(*paths); + + return lookup_paths_init(paths, + scope == UNIT_FILE_SYSTEM ? MANAGER_SYSTEM : MANAGER_USER, + scope == UNIT_FILE_USER, + root_dir, + NULL, NULL, NULL); +} diff --git a/src/libshared/path-lookup.h b/src/libshared/path-lookup.h new file mode 100644 index 0000000000..26c83d6111 --- /dev/null +++ b/src/libshared/path-lookup.h @@ -0,0 +1,60 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include "macro.h" + +typedef struct LookupPaths { + char **unit_path; +#ifdef HAVE_SYSV_COMPAT + char **sysvinit_path; + char **sysvrcnd_path; +#endif +} LookupPaths; + +typedef enum ManagerRunningAs { + MANAGER_SYSTEM, + MANAGER_USER, + _MANAGER_RUNNING_AS_MAX, + _MANAGER_RUNNING_AS_INVALID = -1 +} ManagerRunningAs; + +int user_config_home(char **config_home); +int user_runtime_dir(char **runtime_dir); + +char **generator_paths(ManagerRunningAs running_as); + +int lookup_paths_init(LookupPaths *p, + ManagerRunningAs running_as, + bool personal, + const char *root_dir, + const char *generator, + const char *generator_early, + const char *generator_late); + +#include "install.h" + +int lookup_paths_init_from_scope(LookupPaths *paths, + UnitFileScope scope, + const char *root_dir); + +void lookup_paths_free(LookupPaths *p); +#define _cleanup_lookup_paths_free_ _cleanup_(lookup_paths_free) diff --git a/src/libshared/ptyfwd.c b/src/libshared/ptyfwd.c new file mode 100644 index 0000000000..a97e0d5143 --- /dev/null +++ b/src/libshared/ptyfwd.c @@ -0,0 +1,487 @@ +/*** + This file is part of systemd. + + Copyright 2010-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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "ptyfwd.h" +#include "time-util.h" + +struct PTYForward { + sd_event *event; + + int master; + + PTYForwardFlags flags; + + sd_event_source *stdin_event_source; + sd_event_source *stdout_event_source; + sd_event_source *master_event_source; + + sd_event_source *sigwinch_event_source; + + struct termios saved_stdin_attr; + struct termios saved_stdout_attr; + + bool saved_stdin:1; + bool saved_stdout:1; + + bool stdin_readable:1; + bool stdin_hangup:1; + bool stdout_writable:1; + bool stdout_hangup:1; + bool master_readable:1; + bool master_writable:1; + bool master_hangup:1; + + bool read_from_master:1; + + bool last_char_set:1; + char last_char; + + char in_buffer[LINE_MAX], out_buffer[LINE_MAX]; + size_t in_buffer_full, out_buffer_full; + + usec_t escape_timestamp; + unsigned escape_counter; +}; + +#define ESCAPE_USEC (1*USEC_PER_SEC) + +static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) { + const char *p; + + assert(f); + assert(buffer); + assert(n > 0); + + for (p = buffer; p < buffer + n; p++) { + + /* Check for ^] */ + if (*p == 0x1D) { + usec_t nw = now(CLOCK_MONOTONIC); + + if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) { + f->escape_timestamp = nw; + f->escape_counter = 1; + } else { + (f->escape_counter)++; + + if (f->escape_counter >= 3) + return true; + } + } else { + f->escape_timestamp = 0; + f->escape_counter = 0; + } + } + + return false; +} + +static bool ignore_vhangup(PTYForward *f) { + assert(f); + + if (f->flags & PTY_FORWARD_IGNORE_VHANGUP) + return true; + + if ((f->flags & PTY_FORWARD_IGNORE_INITIAL_VHANGUP) && !f->read_from_master) + return true; + + return false; +} + +static int shovel(PTYForward *f) { + ssize_t k; + + assert(f); + + while ((f->stdin_readable && f->in_buffer_full <= 0) || + (f->master_writable && f->in_buffer_full > 0) || + (f->master_readable && f->out_buffer_full <= 0) || + (f->stdout_writable && f->out_buffer_full > 0)) { + + if (f->stdin_readable && f->in_buffer_full < LINE_MAX) { + + k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full); + if (k < 0) { + + if (errno == EAGAIN) + f->stdin_readable = false; + else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) { + f->stdin_readable = false; + f->stdin_hangup = true; + + f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); + } else { + log_error_errno(errno, "read(): %m"); + return sd_event_exit(f->event, EXIT_FAILURE); + } + } else if (k == 0) { + /* EOF on stdin */ + f->stdin_readable = false; + f->stdin_hangup = true; + + f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); + } else { + /* Check if ^] has been + * pressed three times within + * one second. If we get this + * we quite immediately. */ + if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k)) + return sd_event_exit(f->event, EXIT_FAILURE); + + f->in_buffer_full += (size_t) k; + } + } + + if (f->master_writable && f->in_buffer_full > 0) { + + k = write(f->master, f->in_buffer, f->in_buffer_full); + if (k < 0) { + + if (errno == EAGAIN || errno == EIO) + f->master_writable = false; + else if (errno == EPIPE || errno == ECONNRESET) { + f->master_writable = f->master_readable = false; + f->master_hangup = true; + + f->master_event_source = sd_event_source_unref(f->master_event_source); + } else { + log_error_errno(errno, "write(): %m"); + return sd_event_exit(f->event, EXIT_FAILURE); + } + } else { + assert(f->in_buffer_full >= (size_t) k); + memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k); + f->in_buffer_full -= k; + } + } + + if (f->master_readable && f->out_buffer_full < LINE_MAX) { + + k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full); + if (k < 0) { + + /* Note that EIO on the master device + * might be caused by vhangup() or + * temporary closing of everything on + * the other side, we treat it like + * EAGAIN here and try again, unless + * ignore_vhangup is off. */ + + if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f))) + f->master_readable = false; + else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) { + f->master_readable = f->master_writable = false; + f->master_hangup = true; + + f->master_event_source = sd_event_source_unref(f->master_event_source); + } else { + log_error_errno(errno, "read(): %m"); + return sd_event_exit(f->event, EXIT_FAILURE); + } + } else { + f->read_from_master = true; + f->out_buffer_full += (size_t) k; + } + } + + if (f->stdout_writable && f->out_buffer_full > 0) { + + k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full); + if (k < 0) { + + if (errno == EAGAIN) + f->stdout_writable = false; + else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) { + f->stdout_writable = false; + f->stdout_hangup = true; + f->stdout_event_source = sd_event_source_unref(f->stdout_event_source); + } else { + log_error_errno(errno, "write(): %m"); + return sd_event_exit(f->event, EXIT_FAILURE); + } + + } else { + + if (k > 0) { + f->last_char = f->out_buffer[k-1]; + f->last_char_set = true; + } + + assert(f->out_buffer_full >= (size_t) k); + memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k); + f->out_buffer_full -= k; + } + } + } + + if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) { + /* Exit the loop if any side hung up and if there's + * nothing more to write or nothing we could write. */ + + if ((f->out_buffer_full <= 0 || f->stdout_hangup) && + (f->in_buffer_full <= 0 || f->master_hangup)) + return sd_event_exit(f->event, EXIT_SUCCESS); + } + + return 0; +} + +static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { + PTYForward *f = userdata; + + assert(f); + assert(e); + assert(e == f->master_event_source); + assert(fd >= 0); + assert(fd == f->master); + + if (revents & (EPOLLIN|EPOLLHUP)) + f->master_readable = true; + + if (revents & (EPOLLOUT|EPOLLHUP)) + f->master_writable = true; + + return shovel(f); +} + +static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { + PTYForward *f = userdata; + + assert(f); + assert(e); + assert(e == f->stdin_event_source); + assert(fd >= 0); + assert(fd == STDIN_FILENO); + + if (revents & (EPOLLIN|EPOLLHUP)) + f->stdin_readable = true; + + return shovel(f); +} + +static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { + PTYForward *f = userdata; + + assert(f); + assert(e); + assert(e == f->stdout_event_source); + assert(fd >= 0); + assert(fd == STDOUT_FILENO); + + if (revents & (EPOLLOUT|EPOLLHUP)) + f->stdout_writable = true; + + return shovel(f); +} + +static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) { + PTYForward *f = userdata; + struct winsize ws; + + assert(f); + assert(e); + assert(e == f->sigwinch_event_source); + + /* The window size changed, let's forward that. */ + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0) + (void) ioctl(f->master, TIOCSWINSZ, &ws); + + return 0; +} + +int pty_forward_new( + sd_event *event, + int master, + PTYForwardFlags flags, + PTYForward **ret) { + + _cleanup_(pty_forward_freep) PTYForward *f = NULL; + struct winsize ws; + int r; + + f = new0(PTYForward, 1); + if (!f) + return -ENOMEM; + + f->flags = flags; + + if (event) + f->event = sd_event_ref(event); + else { + r = sd_event_default(&f->event); + if (r < 0) + return r; + } + + if (!(flags & PTY_FORWARD_READ_ONLY)) { + r = fd_nonblock(STDIN_FILENO, true); + if (r < 0) + return r; + + r = fd_nonblock(STDOUT_FILENO, true); + if (r < 0) + return r; + } + + r = fd_nonblock(master, true); + if (r < 0) + return r; + + f->master = master; + + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0) + (void) ioctl(master, TIOCSWINSZ, &ws); + + if (!(flags & PTY_FORWARD_READ_ONLY)) { + if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) { + struct termios raw_stdin_attr; + + f->saved_stdin = true; + + raw_stdin_attr = f->saved_stdin_attr; + cfmakeraw(&raw_stdin_attr); + raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag; + tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr); + } + + if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) { + struct termios raw_stdout_attr; + + f->saved_stdout = true; + + raw_stdout_attr = f->saved_stdout_attr; + cfmakeraw(&raw_stdout_attr); + raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag; + raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag; + tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr); + } + + r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f); + if (r < 0 && r != -EPERM) + return r; + } + + r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f); + if (r == -EPERM) + /* stdout without epoll support. Likely redirected to regular file. */ + f->stdout_writable = true; + else if (r < 0) + return r; + + r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f); + if (r < 0) + return r; + + r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f); + if (r < 0) + return r; + + *ret = f; + f = NULL; + + return 0; +} + +PTYForward *pty_forward_free(PTYForward *f) { + + if (f) { + sd_event_source_unref(f->stdin_event_source); + sd_event_source_unref(f->stdout_event_source); + sd_event_source_unref(f->master_event_source); + sd_event_source_unref(f->sigwinch_event_source); + sd_event_unref(f->event); + + if (f->saved_stdout) + tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr); + if (f->saved_stdin) + tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr); + + free(f); + } + + /* STDIN/STDOUT should not be nonblocking normally, so let's + * unconditionally reset it */ + fd_nonblock(STDIN_FILENO, false); + fd_nonblock(STDOUT_FILENO, false); + + return NULL; +} + +int pty_forward_get_last_char(PTYForward *f, char *ch) { + assert(f); + assert(ch); + + if (!f->last_char_set) + return -ENXIO; + + *ch = f->last_char; + return 0; +} + +int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) { + int r; + + assert(f); + + if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b) + return 0; + + if (b) + f->flags |= PTY_FORWARD_IGNORE_VHANGUP; + else + f->flags &= ~PTY_FORWARD_IGNORE_VHANGUP; + + if (!ignore_vhangup(f)) { + + /* We shall now react to vhangup()s? Let's check + * immediately if we might be in one */ + + f->master_readable = true; + r = shovel(f); + if (r < 0) + return r; + } + + return 0; +} + +int pty_forward_get_ignore_vhangup(PTYForward *f) { + assert(f); + + return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP); +} diff --git a/src/libshared/ptyfwd.h b/src/libshared/ptyfwd.h new file mode 100644 index 0000000000..83c1f60970 --- /dev/null +++ b/src/libshared/ptyfwd.h @@ -0,0 +1,48 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010-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 . +***/ + +#include + +#include + +#include "macro.h" + +typedef struct PTYForward PTYForward; + +typedef enum PTYForwardFlags { + PTY_FORWARD_READ_ONLY = 1, + + /* Continue reading after hangup? */ + PTY_FORWARD_IGNORE_VHANGUP = 2, + + /* Continue reading after hangup but only if we never read anything else? */ + PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 4, +} PTYForwardFlags; + +int pty_forward_new(sd_event *event, int master, PTYForwardFlags flags, PTYForward **f); +PTYForward *pty_forward_free(PTYForward *f); + +int pty_forward_get_last_char(PTYForward *f, char *ch); + +int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup); +int pty_forward_get_ignore_vhangup(PTYForward *f); + +DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free); diff --git a/src/libshared/resolve-util.c b/src/libshared/resolve-util.c new file mode 100644 index 0000000000..e2da81bab7 --- /dev/null +++ b/src/libshared/resolve-util.c @@ -0,0 +1,39 @@ +/*** + 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 . +***/ + +#include "conf-parser.h" +#include "resolve-util.h" +#include "string-table.h" + +DEFINE_CONFIG_PARSE_ENUM(config_parse_resolve_support, resolve_support, ResolveSupport, "Failed to parse resolve support setting"); +DEFINE_CONFIG_PARSE_ENUM(config_parse_dnssec_mode, dnssec_mode, DnssecMode, "Failed to parse DNSSEC mode setting"); + +static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = { + [RESOLVE_SUPPORT_NO] = "no", + [RESOLVE_SUPPORT_YES] = "yes", + [RESOLVE_SUPPORT_RESOLVE] = "resolve", +}; +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(resolve_support, ResolveSupport, RESOLVE_SUPPORT_YES); + +static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = { + [DNSSEC_NO] = "no", + [DNSSEC_ALLOW_DOWNGRADE] = "allow-downgrade", + [DNSSEC_YES] = "yes", +}; +DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dnssec_mode, DnssecMode, DNSSEC_YES); diff --git a/src/libshared/resolve-util.h b/src/libshared/resolve-util.h new file mode 100644 index 0000000000..8636a6c134 --- /dev/null +++ b/src/libshared/resolve-util.h @@ -0,0 +1,60 @@ +#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 . +***/ + +#include "macro.h" + +typedef enum ResolveSupport ResolveSupport; +typedef enum DnssecMode DnssecMode; + +enum ResolveSupport { + RESOLVE_SUPPORT_NO, + RESOLVE_SUPPORT_YES, + RESOLVE_SUPPORT_RESOLVE, + _RESOLVE_SUPPORT_MAX, + _RESOLVE_SUPPORT_INVALID = -1 +}; + +enum DnssecMode { + /* No DNSSEC validation is done */ + DNSSEC_NO, + + /* Validate locally, if the server knows DO, but if not, + * don't. Don't trust the AD bit. If the server doesn't do + * DNSSEC properly, downgrade to non-DNSSEC operation. Of + * course, we then are vulnerable to a downgrade attack, but + * that's life and what is configured. */ + DNSSEC_ALLOW_DOWNGRADE, + + /* Insist on DNSSEC server support, and rather fail than downgrading. */ + DNSSEC_YES, + + _DNSSEC_MODE_MAX, + _DNSSEC_MODE_INVALID = -1 +}; + +int config_parse_resolve_support(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_dnssec_mode(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); + +const char* resolve_support_to_string(ResolveSupport p) _const_; +ResolveSupport resolve_support_from_string(const char *s) _pure_; + +const char* dnssec_mode_to_string(DnssecMode p) _const_; +DnssecMode dnssec_mode_from_string(const char *s) _pure_; diff --git a/src/libshared/seccomp-util.c b/src/libshared/seccomp-util.c new file mode 100644 index 0000000000..cebe0fce2a --- /dev/null +++ b/src/libshared/seccomp-util.c @@ -0,0 +1,90 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include + +#include "macro.h" +#include "seccomp-util.h" +#include "string-util.h" + +const char* seccomp_arch_to_string(uint32_t c) { + + if (c == SCMP_ARCH_NATIVE) + return "native"; + if (c == SCMP_ARCH_X86) + return "x86"; + if (c == SCMP_ARCH_X86_64) + return "x86-64"; + if (c == SCMP_ARCH_X32) + return "x32"; + if (c == SCMP_ARCH_ARM) + return "arm"; + + return NULL; +} + +int seccomp_arch_from_string(const char *n, uint32_t *ret) { + if (!n) + return -EINVAL; + + assert(ret); + + if (streq(n, "native")) + *ret = SCMP_ARCH_NATIVE; + else if (streq(n, "x86")) + *ret = SCMP_ARCH_X86; + else if (streq(n, "x86-64")) + *ret = SCMP_ARCH_X86_64; + else if (streq(n, "x32")) + *ret = SCMP_ARCH_X32; + else if (streq(n, "arm")) + *ret = SCMP_ARCH_ARM; + else + return -EINVAL; + + return 0; +} + +int seccomp_add_secondary_archs(scmp_filter_ctx *c) { + +#if defined(__i386__) || defined(__x86_64__) + int r; + + /* Add in all possible secondary archs we are aware of that + * this kernel might support. */ + + r = seccomp_arch_add(c, SCMP_ARCH_X86); + if (r < 0 && r != -EEXIST) + return r; + + r = seccomp_arch_add(c, SCMP_ARCH_X86_64); + if (r < 0 && r != -EEXIST) + return r; + + r = seccomp_arch_add(c, SCMP_ARCH_X32); + if (r < 0 && r != -EEXIST) + return r; + +#endif + + return 0; + +} diff --git a/src/libshared/seccomp-util.h b/src/libshared/seccomp-util.h new file mode 100644 index 0000000000..4ed2afc1b2 --- /dev/null +++ b/src/libshared/seccomp-util.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include + +const char* seccomp_arch_to_string(uint32_t c); +int seccomp_arch_from_string(const char *n, uint32_t *ret); + +int seccomp_add_secondary_archs(scmp_filter_ctx *c); diff --git a/src/libshared/sleep-config.c b/src/libshared/sleep-config.c new file mode 100644 index 0000000000..a0aef66bc8 --- /dev/null +++ b/src/libshared/sleep-config.c @@ -0,0 +1,274 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "conf-parser.h" +#include "def.h" +#include "fd-util.h" +#include "fileio.h" +#include "log.h" +#include "macro.h" +#include "parse-util.h" +#include "sleep-config.h" +#include "string-util.h" +#include "strv.h" + +#define USE(x, y) do{ (x) = (y); (y) = NULL; } while(0) + +int parse_sleep_config(const char *verb, char ***_modes, char ***_states) { + + _cleanup_strv_free_ char + **suspend_mode = NULL, **suspend_state = NULL, + **hibernate_mode = NULL, **hibernate_state = NULL, + **hybrid_mode = NULL, **hybrid_state = NULL; + char **modes, **states; + + const ConfigTableItem items[] = { + { "Sleep", "SuspendMode", config_parse_strv, 0, &suspend_mode }, + { "Sleep", "SuspendState", config_parse_strv, 0, &suspend_state }, + { "Sleep", "HibernateMode", config_parse_strv, 0, &hibernate_mode }, + { "Sleep", "HibernateState", config_parse_strv, 0, &hibernate_state }, + { "Sleep", "HybridSleepMode", config_parse_strv, 0, &hybrid_mode }, + { "Sleep", "HybridSleepState", config_parse_strv, 0, &hybrid_state }, + {} + }; + + config_parse_many(PKGSYSCONFDIR "/sleep.conf", + CONF_PATHS_NULSTR("systemd/sleep.conf.d"), + "Sleep\0", config_item_table_lookup, items, + false, NULL); + + if (streq(verb, "suspend")) { + /* empty by default */ + USE(modes, suspend_mode); + + if (suspend_state) + USE(states, suspend_state); + else + states = strv_new("mem", "standby", "freeze", NULL); + + } else if (streq(verb, "hibernate")) { + if (hibernate_mode) + USE(modes, hibernate_mode); + else + modes = strv_new("platform", "shutdown", NULL); + + if (hibernate_state) + USE(states, hibernate_state); + else + states = strv_new("disk", NULL); + + } else if (streq(verb, "hybrid-sleep")) { + if (hybrid_mode) + USE(modes, hybrid_mode); + else + modes = strv_new("suspend", "platform", "shutdown", NULL); + + if (hybrid_state) + USE(states, hybrid_state); + else + states = strv_new("disk", NULL); + + } else + assert_not_reached("what verb"); + + if ((!modes && !streq(verb, "suspend")) || !states) { + strv_free(modes); + strv_free(states); + return log_oom(); + } + + *_modes = modes; + *_states = states; + return 0; +} + +int can_sleep_state(char **types) { + char **type; + int r; + _cleanup_free_ char *p = NULL; + + if (strv_isempty(types)) + return true; + + /* If /sys is read-only we cannot sleep */ + if (access("/sys/power/state", W_OK) < 0) + return false; + + r = read_one_line_file("/sys/power/state", &p); + if (r < 0) + return false; + + STRV_FOREACH(type, types) { + const char *word, *state; + size_t l, k; + + k = strlen(*type); + FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) + if (l == k && memcmp(word, *type, l) == 0) + return true; + } + + return false; +} + +int can_sleep_disk(char **types) { + char **type; + int r; + _cleanup_free_ char *p = NULL; + + if (strv_isempty(types)) + return true; + + /* If /sys is read-only we cannot sleep */ + if (access("/sys/power/disk", W_OK) < 0) + return false; + + r = read_one_line_file("/sys/power/disk", &p); + if (r < 0) + return false; + + STRV_FOREACH(type, types) { + const char *word, *state; + size_t l, k; + + k = strlen(*type); + FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) { + if (l == k && memcmp(word, *type, l) == 0) + return true; + + if (l == k + 2 && + word[0] == '[' && + memcmp(word + 1, *type, l - 2) == 0 && + word[l-1] == ']') + return true; + } + } + + return false; +} + +#define HIBERNATION_SWAP_THRESHOLD 0.98 + +static int hibernation_partition_size(size_t *size, size_t *used) { + _cleanup_fclose_ FILE *f; + unsigned i; + + assert(size); + assert(used); + + f = fopen("/proc/swaps", "re"); + if (!f) { + log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, + "Failed to retrieve open /proc/swaps: %m"); + assert(errno > 0); + return -errno; + } + + (void) fscanf(f, "%*s %*s %*s %*s %*s\n"); + + for (i = 1;; i++) { + _cleanup_free_ char *dev = NULL, *type = NULL; + size_t size_field, used_field; + int k; + + k = fscanf(f, + "%ms " /* device/file */ + "%ms " /* type of swap */ + "%zu " /* swap size */ + "%zu " /* used */ + "%*i\n", /* priority */ + &dev, &type, &size_field, &used_field); + if (k != 4) { + if (k == EOF) + break; + + log_warning("Failed to parse /proc/swaps:%u", i); + continue; + } + + if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) { + log_warning("Ignoring deleted swapfile '%s'.", dev); + continue; + } + + *size = size_field; + *used = used_field; + return 0; + } + + log_debug("No swap partitions were found."); + return -ENOSYS; +} + +static bool enough_memory_for_hibernation(void) { + _cleanup_free_ char *active = NULL; + unsigned long long act = 0; + size_t size = 0, used = 0; + int r; + + r = hibernation_partition_size(&size, &used); + if (r < 0) + return false; + + r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active); + if (r < 0) { + log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m"); + return false; + } + + r = safe_atollu(active, &act); + if (r < 0) { + log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m", + active); + return false; + } + + r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD; + log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%", + r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD); + + return r; +} + +int can_sleep(const char *verb) { + _cleanup_strv_free_ char **modes = NULL, **states = NULL; + int r; + + assert(streq(verb, "suspend") || + streq(verb, "hibernate") || + streq(verb, "hybrid-sleep")); + + r = parse_sleep_config(verb, &modes, &states); + if (r < 0) + return false; + + if (!can_sleep_state(states) || !can_sleep_disk(modes)) + return false; + + return streq(verb, "suspend") || enough_memory_for_hibernation(); +} diff --git a/src/libshared/sleep-config.h b/src/libshared/sleep-config.h new file mode 100644 index 0000000000..51f4621844 --- /dev/null +++ b/src/libshared/sleep-config.h @@ -0,0 +1,26 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#pragma once + +int parse_sleep_config(const char *verb, char ***modes, char ***states); + +int can_sleep(const char *verb); +int can_sleep_disk(char **types); +int can_sleep_state(char **types); diff --git a/src/libshared/spawn-ask-password-agent.c b/src/libshared/spawn-ask-password-agent.c new file mode 100644 index 0000000000..a46b7525f0 --- /dev/null +++ b/src/libshared/spawn-ask-password-agent.c @@ -0,0 +1,62 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include + +#include "log.h" +#include "process-util.h" +#include "spawn-ask-password-agent.h" +#include "util.h" + +static pid_t agent_pid = 0; + +int ask_password_agent_open(void) { + int r; + + if (agent_pid > 0) + return 0; + + /* We check STDIN here, not STDOUT, since this is about input, + * not output */ + if (!isatty(STDIN_FILENO)) + return 0; + + r = fork_agent(&agent_pid, + NULL, 0, + SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, + SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, "--watch", NULL); + if (r < 0) + return log_error_errno(r, "Failed to fork TTY ask password agent: %m"); + + return 1; +} + +void ask_password_agent_close(void) { + + if (agent_pid <= 0) + return; + + /* Inform agent that we are done */ + (void) kill(agent_pid, SIGTERM); + (void) kill(agent_pid, SIGCONT); + (void) wait_for_terminate(agent_pid, NULL); + agent_pid = 0; +} diff --git a/src/libshared/spawn-ask-password-agent.h b/src/libshared/spawn-ask-password-agent.h new file mode 100644 index 0000000000..fb0749b13f --- /dev/null +++ b/src/libshared/spawn-ask-password-agent.h @@ -0,0 +1,23 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +int ask_password_agent_open(void); +void ask_password_agent_close(void); diff --git a/src/libshared/spawn-polkit-agent.c b/src/libshared/spawn-polkit-agent.c new file mode 100644 index 0000000000..cf3c8ad5a3 --- /dev/null +++ b/src/libshared/spawn-polkit-agent.c @@ -0,0 +1,98 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include +#include +#include + +#include "fd-util.h" +#include "io-util.h" +#include "log.h" +#include "macro.h" +#include "process-util.h" +#include "spawn-polkit-agent.h" +#include "stdio-util.h" +#include "time-util.h" +#include "util.h" + +#ifdef ENABLE_POLKIT +static pid_t agent_pid = 0; + +int polkit_agent_open(void) { + int r; + int pipe_fd[2]; + char notify_fd[DECIMAL_STR_MAX(int) + 1]; + + if (agent_pid > 0) + return 0; + + /* We check STDIN here, not STDOUT, since this is about input, + * not output */ + if (!isatty(STDIN_FILENO)) + return 0; + + if (pipe2(pipe_fd, 0) < 0) + return -errno; + + xsprintf(notify_fd, "%i", pipe_fd[1]); + + r = fork_agent(&agent_pid, + &pipe_fd[1], 1, + POLKIT_AGENT_BINARY_PATH, + POLKIT_AGENT_BINARY_PATH, "--notify-fd", notify_fd, "--fallback", NULL); + + /* Close the writing side, because that's the one for the agent */ + safe_close(pipe_fd[1]); + + if (r < 0) + log_error_errno(r, "Failed to fork TTY ask password agent: %m"); + else + /* Wait until the agent closes the fd */ + fd_wait_for_event(pipe_fd[0], POLLHUP, USEC_INFINITY); + + safe_close(pipe_fd[0]); + + return r; +} + +void polkit_agent_close(void) { + + if (agent_pid <= 0) + return; + + /* Inform agent that we are done */ + (void) kill(agent_pid, SIGTERM); + (void) kill(agent_pid, SIGCONT); + + (void) wait_for_terminate(agent_pid, NULL); + agent_pid = 0; +} + +#else + +int polkit_agent_open(void) { + return 0; +} + +void polkit_agent_close(void) { +} + +#endif diff --git a/src/libshared/spawn-polkit-agent.h b/src/libshared/spawn-polkit-agent.h new file mode 100644 index 0000000000..42b2989ded --- /dev/null +++ b/src/libshared/spawn-polkit-agent.h @@ -0,0 +1,23 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +int polkit_agent_open(void); +void polkit_agent_close(void); diff --git a/src/libshared/specifier.c b/src/libshared/specifier.c new file mode 100644 index 0000000000..303ddf0401 --- /dev/null +++ b/src/libshared/specifier.c @@ -0,0 +1,188 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "hostname-util.h" +#include "macro.h" +#include "specifier.h" +#include "string-util.h" + +/* + * Generic infrastructure for replacing %x style specifiers in + * strings. Will call a callback for each replacement. + * + */ + +int specifier_printf(const char *text, const Specifier table[], void *userdata, char **_ret) { + char *ret, *t; + const char *f; + bool percent = false; + size_t l; + int r; + + assert(text); + assert(table); + + l = strlen(text); + ret = new(char, l+1); + if (!ret) + return -ENOMEM; + + t = ret; + + for (f = text; *f; f++, l--) { + + if (percent) { + if (*f == '%') + *(t++) = '%'; + else { + const Specifier *i; + + for (i = table; i->specifier; i++) + if (i->specifier == *f) + break; + + if (i->lookup) { + _cleanup_free_ char *w = NULL; + char *n; + size_t k, j; + + r = i->lookup(i->specifier, i->data, userdata, &w); + if (r < 0) { + free(ret); + return r; + } + + j = t - ret; + k = strlen(w); + + n = new(char, j + k + l + 1); + if (!n) { + free(ret); + return -ENOMEM; + } + + memcpy(n, ret, j); + memcpy(n + j, w, k); + + free(ret); + + ret = n; + t = n + j + k; + } else { + *(t++) = '%'; + *(t++) = *f; + } + } + + percent = false; + } else if (*f == '%') + percent = true; + else + *(t++) = *f; + } + + *t = 0; + *_ret = ret; + return 0; +} + +/* Generic handler for simple string replacements */ + +int specifier_string(char specifier, void *data, void *userdata, char **ret) { + char *n; + + n = strdup(strempty(data)); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +int specifier_machine_id(char specifier, void *data, void *userdata, char **ret) { + sd_id128_t id; + char *n; + int r; + + r = sd_id128_get_machine(&id); + if (r < 0) + return r; + + n = new(char, 33); + if (!n) + return -ENOMEM; + + *ret = sd_id128_to_string(id, n); + return 0; +} + +int specifier_boot_id(char specifier, void *data, void *userdata, char **ret) { + sd_id128_t id; + char *n; + int r; + + r = sd_id128_get_boot(&id); + if (r < 0) + return r; + + n = new(char, 33); + if (!n) + return -ENOMEM; + + *ret = sd_id128_to_string(id, n); + return 0; +} + +int specifier_host_name(char specifier, void *data, void *userdata, char **ret) { + char *n; + + n = gethostname_malloc(); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} + +int specifier_kernel_release(char specifier, void *data, void *userdata, char **ret) { + struct utsname uts; + char *n; + int r; + + r = uname(&uts); + if (r < 0) + return -errno; + + n = strdup(uts.release); + if (!n) + return -ENOMEM; + + *ret = n; + return 0; +} diff --git a/src/libshared/specifier.h b/src/libshared/specifier.h new file mode 100644 index 0000000000..6b1623ee61 --- /dev/null +++ b/src/libshared/specifier.h @@ -0,0 +1,37 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +typedef int (*SpecifierCallback)(char specifier, void *data, void *userdata, char **ret); + +typedef struct Specifier { + const char specifier; + const SpecifierCallback lookup; + void *data; +} Specifier; + +int specifier_printf(const char *text, const Specifier table[], void *userdata, char **ret); + +int specifier_string(char specifier, void *data, void *userdata, char **ret); + +int specifier_machine_id(char specifier, void *data, void *userdata, char **ret); +int specifier_boot_id(char specifier, void *data, void *userdata, char **ret); +int specifier_host_name(char specifier, void *data, void *userdata, char **ret); +int specifier_kernel_release(char specifier, void *data, void *userdata, char **ret); diff --git a/src/libshared/switch-root.c b/src/libshared/switch-root.c new file mode 100644 index 0000000000..47d3a5a1fa --- /dev/null +++ b/src/libshared/switch-root.c @@ -0,0 +1,156 @@ +/*** + This file is part of systemd. + + Copyright 2012 Harald Hoyer, 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "base-filesystem.h" +#include "fd-util.h" +#include "log.h" +#include "missing.h" +#include "mkdir.h" +#include "path-util.h" +#include "rm-rf.h" +#include "stdio-util.h" +#include "string-util.h" +#include "switch-root.h" +#include "user-util.h" +#include "util.h" + +int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags) { + + /* Don't try to unmount/move the old "/", there's no way to do it. */ + static const char move_mounts[] = + "/dev\0" + "/proc\0" + "/sys\0" + "/run\0"; + + _cleanup_close_ int old_root_fd = -1; + struct stat new_root_stat; + bool old_root_remove; + const char *i, *temporary_old_root; + + if (path_equal(new_root, "/")) + return 0; + + temporary_old_root = strjoina(new_root, oldroot); + mkdir_p_label(temporary_old_root, 0755); + + old_root_remove = in_initrd(); + + if (stat(new_root, &new_root_stat) < 0) + return log_error_errno(errno, "Failed to stat directory %s: %m", new_root); + + /* Work-around for kernel design: the kernel refuses switching + * root if any file systems are mounted MS_SHARED. Hence + * remount them MS_PRIVATE here as a work-around. + * + * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */ + if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0) + log_warning_errno(errno, "Failed to make \"/\" private mount: %m"); + + NULSTR_FOREACH(i, move_mounts) { + char new_mount[PATH_MAX]; + struct stat sb; + + xsprintf(new_mount, "%s%s", new_root, i); + + mkdir_p_label(new_mount, 0755); + + if ((stat(new_mount, &sb) < 0) || + sb.st_dev != new_root_stat.st_dev) { + + /* Mount point seems to be mounted already or + * stat failed. Unmount the old mount + * point. */ + if (umount2(i, MNT_DETACH) < 0) + log_warning_errno(errno, "Failed to unmount %s: %m", i); + continue; + } + + if (mount(i, new_mount, NULL, mountflags, NULL) < 0) { + if (mountflags & MS_MOVE) { + log_error_errno(errno, "Failed to move mount %s to %s, forcing unmount: %m", i, new_mount); + + if (umount2(i, MNT_FORCE) < 0) + log_warning_errno(errno, "Failed to unmount %s: %m", i); + } + if (mountflags & MS_BIND) + log_error_errno(errno, "Failed to bind mount %s to %s: %m", i, new_mount); + + } + } + + /* Do not fail, if base_filesystem_create() fails. Not all + * switch roots are like base_filesystem_create() wants them + * to look like. They might even boot, if they are RO and + * don't have the FS layout. Just ignore the error and + * switch_root() nevertheless. */ + (void) base_filesystem_create(new_root, UID_INVALID, GID_INVALID); + + if (chdir(new_root) < 0) + return log_error_errno(errno, "Failed to change directory to %s: %m", new_root); + + if (old_root_remove) { + old_root_fd = open("/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY|O_DIRECTORY); + if (old_root_fd < 0) + log_warning_errno(errno, "Failed to open root directory: %m"); + } + + /* We first try a pivot_root() so that we can umount the old + * root dir. In many cases (i.e. where rootfs is /), that's + * not possible however, and hence we simply overmount root */ + if (pivot_root(new_root, temporary_old_root) >= 0) { + + /* Immediately get rid of the old root, if detach_oldroot is set. + * Since we are running off it we need to do this lazily. */ + if (detach_oldroot && umount2(oldroot, MNT_DETACH) < 0) + log_error_errno(errno, "Failed to lazily umount old root dir %s, %s: %m", + oldroot, + errno == ENOENT ? "ignoring" : "leaving it around"); + + } else if (mount(new_root, "/", NULL, MS_MOVE, NULL) < 0) + return log_error_errno(errno, "Failed to mount moving %s to /: %m", new_root); + + if (chroot(".") < 0) + return log_error_errno(errno, "Failed to change root: %m"); + + if (chdir("/") < 0) + return log_error_errno(errno, "Failed to change directory: %m"); + + if (old_root_fd >= 0) { + struct stat rb; + + if (fstat(old_root_fd, &rb) < 0) + log_warning_errno(errno, "Failed to stat old root directory, leaving: %m"); + else { + (void) rm_rf_children(old_root_fd, 0, &rb); + old_root_fd = -1; + } + } + + return 0; +} diff --git a/src/libshared/switch-root.h b/src/libshared/switch-root.h new file mode 100644 index 0000000000..a7a080b3e8 --- /dev/null +++ b/src/libshared/switch-root.h @@ -0,0 +1,23 @@ +#pragma once + +#include +/*** + This file is part of systemd. + + Copyright 2012 Harald Hoyer, 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 . +***/ + +int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags); diff --git a/src/libshared/sysctl-util.c b/src/libshared/sysctl-util.c new file mode 100644 index 0000000000..e1ccb3294c --- /dev/null +++ b/src/libshared/sysctl-util.c @@ -0,0 +1,73 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "fileio.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "sysctl-util.h" + +char *sysctl_normalize(char *s) { + char *n; + + n = strpbrk(s, "/."); + /* If the first separator is a slash, the path is + * assumed to be normalized and slashes remain slashes + * and dots remains dots. */ + if (!n || *n == '/') + return s; + + /* Otherwise, dots become slashes and slashes become + * dots. Fun. */ + while (n) { + if (*n == '.') + *n = '/'; + else + *n = '.'; + + n = strpbrk(n + 1, "/."); + } + + return s; +} + +int sysctl_write(const char *property, const char *value) { + char *p; + + assert(property); + assert(value); + + log_debug("Setting '%s' to '%s'", property, value); + + p = strjoina("/proc/sys/", property); + return write_string_file(p, value, 0); +} + +int sysctl_read(const char *property, char **content) { + char *p; + + assert(property); + assert(content); + + p = strjoina("/proc/sys/", property); + return read_full_file(p, content, NULL); +} diff --git a/src/libshared/sysctl-util.h b/src/libshared/sysctl-util.h new file mode 100644 index 0000000000..2decb39f58 --- /dev/null +++ b/src/libshared/sysctl-util.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +char *sysctl_normalize(char *s); +int sysctl_read(const char *property, char **value); +int sysctl_write(const char *property, const char *value); + diff --git a/src/libshared/test-tables.h b/src/libshared/test-tables.h new file mode 100644 index 0000000000..228e510104 --- /dev/null +++ b/src/libshared/test-tables.h @@ -0,0 +1,60 @@ +/*** + This file is part of systemd + + Copyright 2013 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 . +***/ + +#include +#include + +typedef const char* (*lookup_t)(int); +typedef int (*reverse_t)(const char*); + +static inline void _test_table(const char *name, + lookup_t lookup, + reverse_t reverse, + int size, + bool sparse) { + int i, boring = 0; + + for (i = -1; i < size + 1; i++) { + const char* val = lookup(i); + int rev; + + if (val) { + rev = reverse(val); + boring = 0; + } else { + rev = reverse("--no-such--value----"); + boring += i >= 0; + } + + if (boring < 1 || i == size) + printf("%s: %d → %s → %d\n", name, i, val, rev); + else if (boring == 1) + printf("%*s ...\n", (int) strlen(name), ""); + + assert_se(!(i >= 0 && i < size ? + sparse ? rev != i && rev != -1 : val == NULL || rev != i : + val != NULL || rev != -1)); + } +} + +#define test_table(lower, upper) \ + _test_table(STRINGIFY(lower), lower##_to_string, lower##_from_string, _##upper##_MAX, false) + +#define test_table_sparse(lower, upper) \ + _test_table(STRINGIFY(lower), lower##_to_string, lower##_from_string, _##upper##_MAX, true) diff --git a/src/libshared/udev-util.h b/src/libshared/udev-util.h new file mode 100644 index 0000000000..ca0889f8a6 --- /dev/null +++ b/src/libshared/udev-util.h @@ -0,0 +1,44 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#include "udev.h" +#include "util.h" + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev*, udev_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_device*, udev_device_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_enumerate*, udev_enumerate_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_event*, udev_event_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_rules*, udev_rules_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl*, udev_ctrl_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl_connection*, udev_ctrl_connection_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl_msg*, udev_ctrl_msg_unref); +DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_monitor*, udev_monitor_unref); + +#define _cleanup_udev_unref_ _cleanup_(udev_unrefp) +#define _cleanup_udev_device_unref_ _cleanup_(udev_device_unrefp) +#define _cleanup_udev_enumerate_unref_ _cleanup_(udev_enumerate_unrefp) +#define _cleanup_udev_event_unref_ _cleanup_(udev_event_unrefp) +#define _cleanup_udev_rules_unref_ _cleanup_(udev_rules_unrefp) +#define _cleanup_udev_ctrl_unref_ _cleanup_(udev_ctrl_unrefp) +#define _cleanup_udev_ctrl_connection_unref_ _cleanup_(udev_ctrl_connection_unrefp) +#define _cleanup_udev_ctrl_msg_unref_ _cleanup_(udev_ctrl_msg_unrefp) +#define _cleanup_udev_monitor_unref_ _cleanup_(udev_monitor_unrefp) +#define _cleanup_udev_list_cleanup_ _cleanup_(udev_list_cleanup) diff --git a/src/libshared/uid-range.c b/src/libshared/uid-range.c new file mode 100644 index 0000000000..eb251492c3 --- /dev/null +++ b/src/libshared/uid-range.c @@ -0,0 +1,208 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include + +#include "macro.h" +#include "uid-range.h" +#include "user-util.h" + +static bool uid_range_intersect(UidRange *range, uid_t start, uid_t nr) { + assert(range); + + return range->start <= start + nr && + range->start + range->nr >= start; +} + +static void uid_range_coalesce(UidRange **p, unsigned *n) { + unsigned i, j; + + assert(p); + assert(n); + + for (i = 0; i < *n; i++) { + for (j = i + 1; j < *n; j++) { + UidRange *x = (*p)+i, *y = (*p)+j; + + if (uid_range_intersect(x, y->start, y->nr)) { + uid_t begin, end; + + begin = MIN(x->start, y->start); + end = MAX(x->start + x->nr, y->start + y->nr); + + x->start = begin; + x->nr = end - begin; + + if (*n > j+1) + memmove(y, y+1, sizeof(UidRange) * (*n - j -1)); + + (*n) --; + j--; + } + } + } + +} + +static int uid_range_compare(const void *a, const void *b) { + const UidRange *x = a, *y = b; + + if (x->start < y->start) + return -1; + if (x->start > y->start) + return 1; + + if (x->nr < y->nr) + return -1; + if (x->nr > y->nr) + return 1; + + return 0; +} + +int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr) { + bool found = false; + UidRange *x; + unsigned i; + + assert(p); + assert(n); + + if (nr <= 0) + return 0; + + for (i = 0; i < *n; i++) { + x = (*p) + i; + if (uid_range_intersect(x, start, nr)) { + found = true; + break; + } + } + + if (found) { + uid_t begin, end; + + begin = MIN(x->start, start); + end = MAX(x->start + x->nr, start + nr); + + x->start = begin; + x->nr = end - begin; + } else { + UidRange *t; + + t = realloc(*p, sizeof(UidRange) * (*n + 1)); + if (!t) + return -ENOMEM; + + *p = t; + x = t + ((*n) ++); + + x->start = start; + x->nr = nr; + } + + qsort(*p, *n, sizeof(UidRange), uid_range_compare); + uid_range_coalesce(p, n); + + return *n; +} + +int uid_range_add_str(UidRange **p, unsigned *n, const char *s) { + uid_t start, nr; + const char *t; + int r; + + assert(p); + assert(n); + assert(s); + + t = strchr(s, '-'); + if (t) { + char *b; + uid_t end; + + b = strndupa(s, t - s); + r = parse_uid(b, &start); + if (r < 0) + return r; + + r = parse_uid(t+1, &end); + if (r < 0) + return r; + + if (end < start) + return -EINVAL; + + nr = end - start + 1; + } else { + r = parse_uid(s, &start); + if (r < 0) + return r; + + nr = 1; + } + + return uid_range_add(p, n, start, nr); +} + +int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid) { + uid_t closest = UID_INVALID, candidate; + unsigned i; + + assert(p); + assert(uid); + + candidate = *uid - 1; + + for (i = 0; i < n; i++) { + uid_t begin, end; + + begin = p[i].start; + end = p[i].start + p[i].nr - 1; + + if (candidate >= begin && candidate <= end) { + *uid = candidate; + return 1; + } + + if (end < candidate) + closest = end; + } + + if (closest == UID_INVALID) + return -EBUSY; + + *uid = closest; + return 1; +} + +bool uid_range_contains(const UidRange *p, unsigned n, uid_t uid) { + unsigned i; + + assert(p); + assert(uid); + + for (i = 0; i < n; i++) + if (uid >= p[i].start && uid < p[i].start + p[i].nr) + return true; + + return false; +} diff --git a/src/libshared/uid-range.h b/src/libshared/uid-range.h new file mode 100644 index 0000000000..4044eb4c9c --- /dev/null +++ b/src/libshared/uid-range.h @@ -0,0 +1,33 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include + +typedef struct UidRange { + uid_t start, nr; +} UidRange; + +int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr); +int uid_range_add_str(UidRange **p, unsigned *n, const char *s); + +int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid); +bool uid_range_contains(const UidRange *p, unsigned n, uid_t uid); diff --git a/src/libshared/utmp-wtmp.c b/src/libshared/utmp-wtmp.c new file mode 100644 index 0000000000..9750dcd817 --- /dev/null +++ b/src/libshared/utmp-wtmp.c @@ -0,0 +1,445 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "macro.h" +#include "path-util.h" +#include "string-util.h" +#include "terminal-util.h" +#include "time-util.h" +#include "user-util.h" +#include "util.h" +#include "utmp-wtmp.h" + +int utmp_get_runlevel(int *runlevel, int *previous) { + struct utmpx *found, lookup = { .ut_type = RUN_LVL }; + int r; + const char *e; + + assert(runlevel); + + /* If these values are set in the environment this takes + * precedence. Presumably, sysvinit does this to work around a + * race condition that would otherwise exist where we'd always + * go to disk and hence might read runlevel data that might be + * very new and does not apply to the current script being + * executed. */ + + e = getenv("RUNLEVEL"); + if (e && e[0] > 0) { + *runlevel = e[0]; + + if (previous) { + /* $PREVLEVEL seems to be an Upstart thing */ + + e = getenv("PREVLEVEL"); + if (e && e[0] > 0) + *previous = e[0]; + else + *previous = 0; + } + + return 0; + } + + if (utmpxname(_PATH_UTMPX) < 0) + return -errno; + + setutxent(); + + found = getutxid(&lookup); + if (!found) + r = -errno; + else { + int a, b; + + a = found->ut_pid & 0xFF; + b = (found->ut_pid >> 8) & 0xFF; + + *runlevel = a; + if (previous) + *previous = b; + + r = 0; + } + + endutxent(); + + return r; +} + +static void init_timestamp(struct utmpx *store, usec_t t) { + assert(store); + + if (t <= 0) + t = now(CLOCK_REALTIME); + + store->ut_tv.tv_sec = t / USEC_PER_SEC; + store->ut_tv.tv_usec = t % USEC_PER_SEC; +} + +static void init_entry(struct utmpx *store, usec_t t) { + struct utsname uts = {}; + + assert(store); + + init_timestamp(store, t); + + if (uname(&uts) >= 0) + strncpy(store->ut_host, uts.release, sizeof(store->ut_host)); + + strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */ + strncpy(store->ut_id, "~~", sizeof(store->ut_id)); +} + +static int write_entry_utmp(const struct utmpx *store) { + int r; + + assert(store); + + /* utmp is similar to wtmp, but there is only one entry for + * each entry type resp. user; i.e. basically a key/value + * table. */ + + if (utmpxname(_PATH_UTMPX) < 0) + return -errno; + + setutxent(); + + if (!pututxline(store)) + r = -errno; + else + r = 0; + + endutxent(); + + return r; +} + +static int write_entry_wtmp(const struct utmpx *store) { + assert(store); + + /* wtmp is a simple append-only file where each entry is + simply appended to the end; i.e. basically a log. */ + + errno = 0; + updwtmpx(_PATH_WTMPX, store); + return -errno; +} + +static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) { + int r, s; + + r = write_entry_utmp(store_utmp); + s = write_entry_wtmp(store_wtmp); + + if (r >= 0) + r = s; + + /* If utmp/wtmp have been disabled, that's a good thing, hence + * ignore the errors */ + if (r == -ENOENT) + r = 0; + + return r; +} + +static int write_entry_both(const struct utmpx *store) { + return write_utmp_wtmp(store, store); +} + +int utmp_put_shutdown(void) { + struct utmpx store = {}; + + init_entry(&store, 0); + + store.ut_type = RUN_LVL; + strncpy(store.ut_user, "shutdown", sizeof(store.ut_user)); + + return write_entry_both(&store); +} + +int utmp_put_reboot(usec_t t) { + struct utmpx store = {}; + + init_entry(&store, t); + + store.ut_type = BOOT_TIME; + strncpy(store.ut_user, "reboot", sizeof(store.ut_user)); + + return write_entry_both(&store); +} + +_pure_ static const char *sanitize_id(const char *id) { + size_t l; + + assert(id); + l = strlen(id); + + if (l <= sizeof(((struct utmpx*) NULL)->ut_id)) + return id; + + return id + l - sizeof(((struct utmpx*) NULL)->ut_id); +} + +int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) { + struct utmpx store = { + .ut_type = INIT_PROCESS, + .ut_pid = pid, + .ut_session = sid, + }; + int r; + + assert(id); + + init_timestamp(&store, 0); + + /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */ + strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id)); + + if (line) + strncpy(store.ut_line, basename(line), sizeof(store.ut_line)); + + r = write_entry_both(&store); + if (r < 0) + return r; + + if (ut_type == LOGIN_PROCESS || ut_type == USER_PROCESS) { + store.ut_type = LOGIN_PROCESS; + r = write_entry_both(&store); + if (r < 0) + return r; + } + + if (ut_type == USER_PROCESS) { + store.ut_type = USER_PROCESS; + strncpy(store.ut_user, user, sizeof(store.ut_user)-1); + r = write_entry_both(&store); + if (r < 0) + return r; + } + + return 0; +} + +int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) { + struct utmpx lookup = { + .ut_type = INIT_PROCESS /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */ + }, store, store_wtmp, *found; + + assert(id); + + setutxent(); + + /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */ + strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id)); + + found = getutxid(&lookup); + if (!found) + return 0; + + if (found->ut_pid != pid) + return 0; + + memcpy(&store, found, sizeof(store)); + store.ut_type = DEAD_PROCESS; + store.ut_exit.e_termination = code; + store.ut_exit.e_exit = status; + + zero(store.ut_user); + zero(store.ut_host); + zero(store.ut_tv); + + memcpy(&store_wtmp, &store, sizeof(store_wtmp)); + /* wtmp wants the current time */ + init_timestamp(&store_wtmp, 0); + + return write_utmp_wtmp(&store, &store_wtmp); +} + + +int utmp_put_runlevel(int runlevel, int previous) { + struct utmpx store = {}; + int r; + + assert(runlevel > 0); + + if (previous <= 0) { + /* Find the old runlevel automatically */ + + r = utmp_get_runlevel(&previous, NULL); + if (r < 0) { + if (r != -ESRCH) + return r; + + previous = 0; + } + } + + if (previous == runlevel) + return 0; + + init_entry(&store, 0); + + store.ut_type = RUN_LVL; + store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8); + strncpy(store.ut_user, "runlevel", sizeof(store.ut_user)); + + return write_entry_both(&store); +} + +#define TIMEOUT_MSEC 50 + +static int write_to_terminal(const char *tty, const char *message) { + _cleanup_close_ int fd = -1; + const char *p; + size_t left; + usec_t end; + + assert(tty); + assert(message); + + fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC); + if (fd < 0 || !isatty(fd)) + return -errno; + + p = message; + left = strlen(message); + + end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC; + + while (left > 0) { + ssize_t n; + struct pollfd pollfd = { + .fd = fd, + .events = POLLOUT, + }; + usec_t t; + int k; + + t = now(CLOCK_MONOTONIC); + + if (t >= end) + return -ETIME; + + k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC); + if (k < 0) + return -errno; + + if (k == 0) + return -ETIME; + + n = write(fd, p, left); + if (n < 0) { + if (errno == EAGAIN) + continue; + + return -errno; + } + + assert((size_t) n <= left); + + p += n; + left -= n; + } + + return 0; +} + +int utmp_wall( + const char *message, + const char *username, + const char *origin_tty, + bool (*match_tty)(const char *tty, void *userdata), + void *userdata) { + + _cleanup_free_ char *text = NULL, *hn = NULL, *un = NULL, *stdin_tty = NULL; + char date[FORMAT_TIMESTAMP_MAX]; + struct utmpx *u; + int r; + + hn = gethostname_malloc(); + if (!hn) + return -ENOMEM; + if (!username) { + un = getlogname_malloc(); + if (!un) + return -ENOMEM; + } + + if (!origin_tty) { + getttyname_harder(STDIN_FILENO, &stdin_tty); + origin_tty = stdin_tty; + } + + if (asprintf(&text, + "\a\r\n" + "Broadcast message from %s@%s%s%s (%s):\r\n\r\n" + "%s\r\n\r\n", + un ?: username, hn, + origin_tty ? " on " : "", strempty(origin_tty), + format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)), + message) < 0) + return -ENOMEM; + + setutxent(); + + r = 0; + + while ((u = getutxent())) { + _cleanup_free_ char *buf = NULL; + const char *path; + int q; + + if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0) + continue; + + /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */ + if (path_startswith(u->ut_line, "/dev/")) + path = u->ut_line; + else { + if (asprintf(&buf, "/dev/%.*s", (int) sizeof(u->ut_line), u->ut_line) < 0) + return -ENOMEM; + + path = buf; + } + + if (!match_tty || match_tty(path, userdata)) { + q = write_to_terminal(path, text); + if (q < 0) + r = q; + } + } + + return r; +} diff --git a/src/libshared/utmp-wtmp.h b/src/libshared/utmp-wtmp.h new file mode 100644 index 0000000000..438e270a26 --- /dev/null +++ b/src/libshared/utmp-wtmp.h @@ -0,0 +1,74 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "time-util.h" +#include "util.h" + +#ifdef HAVE_UTMP +int utmp_get_runlevel(int *runlevel, int *previous); + +int utmp_put_shutdown(void); +int utmp_put_reboot(usec_t timestamp); +int utmp_put_runlevel(int runlevel, int previous); + +int utmp_put_dead_process(const char *id, pid_t pid, int code, int status); +int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user); + +int utmp_wall( + const char *message, + const char *username, + const char *origin_tty, + bool (*match_tty)(const char *tty, void *userdata), + void *userdata); + +#else /* HAVE_UTMP */ + +static inline int utmp_get_runlevel(int *runlevel, int *previous) { + return -ESRCH; +} +static inline int utmp_put_shutdown(void) { + return 0; +} +static inline int utmp_put_reboot(usec_t timestamp) { + return 0; +} +static inline int utmp_put_runlevel(int runlevel, int previous) { + return 0; +} +static inline int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) { + return 0; +} +static inline int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) { + return 0; +} +static inline int utmp_wall( + const char *message, + const char *username, + const char *origin_tty, + bool (*match_tty)(const char *tty, void *userdata), + void *userdata) { + return 0; +} + +#endif /* HAVE_UTMP */ diff --git a/src/libshared/watchdog.c b/src/libshared/watchdog.c new file mode 100644 index 0000000000..4f3e0125f3 --- /dev/null +++ b/src/libshared/watchdog.c @@ -0,0 +1,164 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "fd-util.h" +#include "log.h" +#include "time-util.h" +#include "watchdog.h" + +static int watchdog_fd = -1; +static usec_t watchdog_timeout = USEC_INFINITY; + +static int update_timeout(void) { + int r; + + if (watchdog_fd < 0) + return 0; + + if (watchdog_timeout == USEC_INFINITY) + return 0; + else if (watchdog_timeout == 0) { + int flags; + + flags = WDIOS_DISABLECARD; + r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); + if (r < 0) + return log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); + } else { + int sec, flags; + char buf[FORMAT_TIMESPAN_MAX]; + + sec = (int) ((watchdog_timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); + r = ioctl(watchdog_fd, WDIOC_SETTIMEOUT, &sec); + if (r < 0) + return log_warning_errno(errno, "Failed to set timeout to %is: %m", sec); + + watchdog_timeout = (usec_t) sec * USEC_PER_SEC; + log_info("Set hardware watchdog to %s.", format_timespan(buf, sizeof(buf), watchdog_timeout, 0)); + + flags = WDIOS_ENABLECARD; + r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); + if (r < 0) { + /* ENOTTY means the watchdog is always enabled so we're fine */ + log_full(errno == ENOTTY ? LOG_DEBUG : LOG_WARNING, + "Failed to enable hardware watchdog: %m"); + if (errno != ENOTTY) + return -errno; + } + + r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); + if (r < 0) + return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); + } + + return 0; +} + +static int open_watchdog(void) { + struct watchdog_info ident; + + if (watchdog_fd >= 0) + return 0; + + watchdog_fd = open("/dev/watchdog", O_WRONLY|O_CLOEXEC); + if (watchdog_fd < 0) + return -errno; + + if (ioctl(watchdog_fd, WDIOC_GETSUPPORT, &ident) >= 0) + log_info("Hardware watchdog '%s', version %x", + ident.identity, + ident.firmware_version); + + return update_timeout(); +} + +int watchdog_set_timeout(usec_t *usec) { + int r; + + watchdog_timeout = *usec; + + /* If we didn't open the watchdog yet and didn't get any + * explicit timeout value set, don't do anything */ + if (watchdog_fd < 0 && watchdog_timeout == USEC_INFINITY) + return 0; + + if (watchdog_fd < 0) + r = open_watchdog(); + else + r = update_timeout(); + + *usec = watchdog_timeout; + + return r; +} + +int watchdog_ping(void) { + int r; + + if (watchdog_fd < 0) { + r = open_watchdog(); + if (r < 0) + return r; + } + + r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); + if (r < 0) + return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); + + return 0; +} + +void watchdog_close(bool disarm) { + int r; + + if (watchdog_fd < 0) + return; + + if (disarm) { + int flags; + + /* Explicitly disarm it */ + flags = WDIOS_DISABLECARD; + r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); + if (r < 0) + log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); + + /* To be sure, use magic close logic, too */ + for (;;) { + static const char v = 'V'; + + if (write(watchdog_fd, &v, 1) > 0) + break; + + if (errno != EINTR) { + log_error_errno(errno, "Failed to disarm watchdog timer: %m"); + break; + } + } + } + + watchdog_fd = safe_close(watchdog_fd); +} diff --git a/src/libshared/watchdog.h b/src/libshared/watchdog.h new file mode 100644 index 0000000000..f6ec178ea1 --- /dev/null +++ b/src/libshared/watchdog.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include + +#include "time-util.h" +#include "util.h" + +int watchdog_set_timeout(usec_t *usec); +int watchdog_ping(void); +void watchdog_close(bool disarm); diff --git a/src/libsystemd-network/Makefile b/src/libsystemd-network/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd-network/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd-network/Makefile b/src/libsystemd-network/Makefile new file mode 100644 index 0000000000..45f782b184 --- /dev/null +++ b/src/libsystemd-network/Makefile @@ -0,0 +1,180 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +noinst_LTLIBRARIES += \ + libsystemd-network.la + +libsystemd_network_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(KMOD_CFLAGS) + +libsystemd_network_la_SOURCES = \ + src/systemd/sd-dhcp-client.h \ + src/systemd/sd-dhcp-server.h \ + src/systemd/sd-dhcp-lease.h \ + src/systemd/sd-ipv4ll.h \ + src/systemd/sd-ipv4acd.h \ + src/systemd/sd-ndisc.h \ + src/systemd/sd-dhcp6-client.h \ + src/systemd/sd-dhcp6-lease.h \ + src/systemd/sd-lldp.h \ + src/libsystemd-network/sd-dhcp-client.c \ + src/libsystemd-network/sd-dhcp-server.c \ + src/libsystemd-network/dhcp-network.c \ + src/libsystemd-network/dhcp-option.c \ + src/libsystemd-network/dhcp-packet.c \ + src/libsystemd-network/dhcp-internal.h \ + src/libsystemd-network/dhcp-server-internal.h \ + src/libsystemd-network/dhcp-protocol.h \ + src/libsystemd-network/dhcp-lease-internal.h \ + src/libsystemd-network/sd-dhcp-lease.c \ + src/libsystemd-network/sd-ipv4ll.c \ + src/libsystemd-network/sd-ipv4acd.c \ + src/libsystemd-network/arp-util.h \ + src/libsystemd-network/arp-util.c \ + src/libsystemd-network/network-internal.c \ + src/libsystemd-network/network-internal.h \ + src/libsystemd-network/sd-ndisc.c \ + src/libsystemd-network/icmp6-util.h \ + src/libsystemd-network/icmp6-util.c \ + src/libsystemd-network/sd-dhcp6-client.c \ + src/libsystemd-network/dhcp6-internal.h \ + src/libsystemd-network/dhcp6-protocol.h \ + src/libsystemd-network/dhcp6-network.c \ + src/libsystemd-network/dhcp6-option.c \ + src/libsystemd-network/dhcp6-lease-internal.h \ + src/libsystemd-network/sd-dhcp6-lease.c \ + src/libsystemd-network/dhcp-identifier.h \ + src/libsystemd-network/dhcp-identifier.c \ + src/libsystemd-network/lldp.h \ + src/libsystemd-network/lldp-tlv.h \ + src/libsystemd-network/lldp-tlv.c \ + src/libsystemd-network/lldp-network.h \ + src/libsystemd-network/lldp-network.c \ + src/libsystemd-network/lldp-port.h \ + src/libsystemd-network/lldp-port.c \ + src/libsystemd-network/lldp-internal.h \ + src/libsystemd-network/lldp-internal.c \ + src/libsystemd-network/sd-lldp.c + +libsystemd_network_la_LIBADD = \ + $(KMOD_LIBS) + +test_dhcp_option_SOURCES = \ + src/libsystemd-network/dhcp-protocol.h \ + src/libsystemd-network/dhcp-internal.h \ + src/libsystemd-network/test-dhcp-option.c + +test_dhcp_option_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_dhcp_client_SOURCES = \ + src/systemd/sd-dhcp-client.h \ + src/libsystemd-network/dhcp-protocol.h \ + src/libsystemd-network/dhcp-internal.h \ + src/libsystemd-network/test-dhcp-client.c + +test_dhcp_client_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_dhcp_server_SOURCES = \ + src/libsystemd-network/test-dhcp-server.c + +test_dhcp_server_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_ipv4ll_SOURCES = \ + src/systemd/sd-ipv4ll.h \ + src/libsystemd-network/arp-util.h \ + src/libsystemd-network/test-ipv4ll.c + +test_ipv4ll_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_ipv4ll_manual_SOURCES = \ + src/systemd/sd-ipv4ll.h \ + src/libsystemd-network/test-ipv4ll-manual.c + +test_ipv4ll_manual_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_acd_SOURCES = \ + src/systemd/sd-ipv4acd.h \ + src/libsystemd-network/test-acd.c + +test_acd_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_ndisc_rs_SOURCES = \ + src/systemd/sd-dhcp6-client.h \ + src/systemd/sd-ndisc.h \ + src/libsystemd-network/icmp6-util.h \ + src/libsystemd-network/test-ndisc-rs.c \ + src/libsystemd-network/dhcp-identifier.h \ + src/libsystemd-network/dhcp-identifier.c + +test_ndisc_rs_LDADD = \ + libsystemd-network.la \ + libudev.la \ + libshared.la + +test_dhcp6_client_SOURCES = \ + src/systemd/sd-dhcp6-client.h \ + src/libsystemd-network/dhcp6-internal.h \ + src/libsystemd-network/test-dhcp6-client.c \ + src/libsystemd-network/dhcp-identifier.h \ + src/libsystemd-network/dhcp-identifier.c + +test_dhcp6_client_LDADD = \ + libsystemd-network.la \ + libudev.la \ + libshared.la + +test_lldp_SOURCES = \ + src/libsystemd-network/lldp.h \ + src/libsystemd-network/lldp-tlv.h \ + src/libsystemd-network/lldp-tlv.c \ + src/libsystemd-network/test-lldp.c + +test_lldp_LDADD = \ + libsystemd-network.la \ + libshared.la + +tests += \ + test-dhcp-option \ + test-dhcp-client \ + test-dhcp-server \ + test-ipv4ll \ + test-ndisc-rs \ + test-dhcp6-client \ + test-lldp + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd-network/dhcp-identifier.c b/src/libsystemd-network/dhcp-identifier.c index 1d9ec7be82..d58f9029cc 100644 --- a/src/libsystemd-network/dhcp-identifier.c +++ b/src/libsystemd-network/dhcp-identifier.c @@ -18,7 +18,7 @@ ***/ #include "libudev.h" -#include "sd-id128.h" +#include #include "dhcp-identifier.h" #include "dhcp6-protocol.h" diff --git a/src/libsystemd-network/dhcp-identifier.h b/src/libsystemd-network/dhcp-identifier.h index 93f06f5938..31f461abd1 100644 --- a/src/libsystemd-network/dhcp-identifier.h +++ b/src/libsystemd-network/dhcp-identifier.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-id128.h" +#include #include "macro.h" #include "sparse-endian.h" diff --git a/src/libsystemd-network/dhcp-internal.h b/src/libsystemd-network/dhcp-internal.h index a3b842cda3..51cb193847 100644 --- a/src/libsystemd-network/dhcp-internal.h +++ b/src/libsystemd-network/dhcp-internal.h @@ -25,7 +25,7 @@ #include #include -#include "sd-dhcp-client.h" +#include #include "dhcp-protocol.h" #include "socket-util.h" diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index 82cae2300a..646e612cee 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -23,7 +23,7 @@ #include #include -#include "sd-dhcp-client.h" +#include #include "dhcp-protocol.h" #include "list.h" diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index bf123f1439..a5306b2907 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -20,8 +20,8 @@ #pragma once -#include "sd-dhcp-server.h" -#include "sd-event.h" +#include +#include #include "dhcp-internal.h" #include "hashmap.h" diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 749086d33a..228feb49d8 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -22,7 +22,7 @@ #include #include -#include "sd-event.h" +#include #include "list.h" #include "macro.h" diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h index 14e708ef63..8e9a6f8e8c 100644 --- a/src/libsystemd-network/dhcp6-lease-internal.h +++ b/src/libsystemd-network/dhcp6-lease-internal.h @@ -22,7 +22,7 @@ #include -#include "sd-dhcp6-lease.h" +#include #include "dhcp6-internal.h" diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index b073906660..91d7f8a7da 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -21,7 +21,7 @@ #include #include -#include "sd-dhcp6-client.h" +#include #include "alloc-util.h" #include "dhcp6-internal.h" diff --git a/src/libsystemd-network/lldp-internal.c b/src/libsystemd-network/lldp-internal.c index c8740ce5f0..902a262f39 100644 --- a/src/libsystemd-network/lldp-internal.c +++ b/src/libsystemd-network/lldp-internal.c @@ -18,7 +18,7 @@ along with systemd; If not, see . ***/ -#include "sd-lldp.h" +#include #include "alloc-util.h" #include "lldp-internal.h" diff --git a/src/libsystemd-network/lldp-internal.h b/src/libsystemd-network/lldp-internal.h index 15b4a11b15..565149fbcf 100644 --- a/src/libsystemd-network/lldp-internal.h +++ b/src/libsystemd-network/lldp-internal.h @@ -20,7 +20,7 @@ #pragma once -#include "sd-event.h" +#include #include "list.h" #include "lldp-tlv.h" diff --git a/src/libsystemd-network/lldp-network.h b/src/libsystemd-network/lldp-network.h index dcf31faa95..3eb03a102d 100644 --- a/src/libsystemd-network/lldp-network.h +++ b/src/libsystemd-network/lldp-network.h @@ -20,6 +20,6 @@ #pragma once -#include "sd-event.h" +#include int lldp_network_bind_raw_socket(int ifindex); diff --git a/src/libsystemd-network/lldp-port.h b/src/libsystemd-network/lldp-port.h index 96092f8df9..36e1edc17c 100644 --- a/src/libsystemd-network/lldp-port.h +++ b/src/libsystemd-network/lldp-port.h @@ -22,8 +22,8 @@ #include -#include "sd-event.h" -#include "sd-lldp.h" +#include +#include #include "util.h" diff --git a/src/libsystemd-network/lldp-tlv.h b/src/libsystemd-network/lldp-tlv.h index 8e7706c612..4362b2ace6 100644 --- a/src/libsystemd-network/lldp-tlv.h +++ b/src/libsystemd-network/lldp-tlv.h @@ -22,7 +22,7 @@ #include -#include "sd-lldp.h" +#include #include "list.h" #include "lldp.h" diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index fdafcd84d8..35860cf11a 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -21,7 +21,7 @@ #include #include -#include "sd-ndisc.h" +#include #include "alloc-util.h" #include "condition.h" diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h index c8a531ab0f..2549d1420e 100644 --- a/src/libsystemd-network/network-internal.h +++ b/src/libsystemd-network/network-internal.h @@ -21,7 +21,7 @@ #include -#include "sd-dhcp-lease.h" +#include #include "condition.h" #include "udev.h" diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 729ef880ce..bd5ec76fc5 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -26,7 +26,7 @@ #include #include -#include "sd-dhcp-client.h" +#include #include "alloc-util.h" #include "async.h" diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 7a119fd488..d8d6f446ee 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -24,7 +24,7 @@ #include #include -#include "sd-dhcp-lease.h" +#include #include "alloc-util.h" #include "dhcp-lease-internal.h" diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index 54ff1a3f28..e11462e6ac 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -20,7 +20,7 @@ #include -#include "sd-dhcp-server.h" +#include #include "alloc-util.h" #include "dhcp-internal.h" diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 7d56d4cc60..494036cb54 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -22,7 +22,7 @@ #include #include -#include "sd-dhcp6-client.h" +#include #include "alloc-util.h" #include "dhcp-identifier.h" diff --git a/src/libsystemd-network/sd-ipv4acd.c b/src/libsystemd-network/sd-ipv4acd.c index f7880a891c..f97fc0bf47 100644 --- a/src/libsystemd-network/sd-ipv4acd.c +++ b/src/libsystemd-network/sd-ipv4acd.c @@ -24,7 +24,7 @@ #include #include -#include "sd-ipv4acd.h" +#include #include "alloc-util.h" #include "arp-util.h" diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index db6cf22aaa..7725b67548 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -24,8 +24,8 @@ #include #include -#include "sd-ipv4acd.h" -#include "sd-ipv4ll.h" +#include +#include #include "alloc-util.h" #include "in-addr-util.h" diff --git a/src/libsystemd-network/sd-lldp.c b/src/libsystemd-network/sd-lldp.c index 885ca62425..6bb2c7797b 100644 --- a/src/libsystemd-network/sd-lldp.c +++ b/src/libsystemd-network/sd-lldp.c @@ -20,7 +20,7 @@ #include -#include "sd-lldp.h" +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c index bae6a49fe6..cd07fc1b3f 100644 --- a/src/libsystemd-network/sd-ndisc.c +++ b/src/libsystemd-network/sd-ndisc.c @@ -24,7 +24,7 @@ #include #include -#include "sd-ndisc.h" +#include #include "alloc-util.h" #include "async.h" diff --git a/src/libsystemd-network/test-acd.c b/src/libsystemd-network/test-acd.c index 75564615b9..53ddfc3b62 100644 --- a/src/libsystemd-network/test-acd.c +++ b/src/libsystemd-network/test-acd.c @@ -24,9 +24,9 @@ #include #include -#include "sd-event.h" -#include "sd-ipv4acd.h" -#include "sd-netlink.h" +#include +#include +#include #include "in-addr-util.h" #include "netlink-util.h" diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index c3c08fef5e..478b370c4c 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -22,8 +22,8 @@ #include #include -#include "sd-dhcp-client.h" -#include "sd-event.h" +#include +#include #include "alloc-util.h" #include "dhcp-identifier.h" diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index e81c508c7f..7dc315c07f 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -20,8 +20,8 @@ #include -#include "sd-dhcp-server.h" -#include "sd-event.h" +#include +#include #include "dhcp-server-internal.h" diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index e74c8c72db..0548e8381e 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -24,8 +24,8 @@ #include #include -#include "sd-dhcp6-client.h" -#include "sd-event.h" +#include +#include #include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" diff --git a/src/libsystemd-network/test-ipv4ll-manual.c b/src/libsystemd-network/test-ipv4ll-manual.c index 85dd61470d..caa315b210 100644 --- a/src/libsystemd-network/test-ipv4ll-manual.c +++ b/src/libsystemd-network/test-ipv4ll-manual.c @@ -23,9 +23,9 @@ #include #include -#include "sd-event.h" -#include "sd-ipv4ll.h" -#include "sd-netlink.h" +#include +#include +#include #include "alloc-util.h" #include "in-addr-util.h" diff --git a/src/libsystemd-network/test-ipv4ll.c b/src/libsystemd-network/test-ipv4ll.c index a233e0378c..b7278834f2 100644 --- a/src/libsystemd-network/test-ipv4ll.c +++ b/src/libsystemd-network/test-ipv4ll.c @@ -25,7 +25,7 @@ #include #include -#include "sd-ipv4ll.h" +#include #include "arp-util.h" #include "fd-util.h" diff --git a/src/libsystemd-network/test-lldp.c b/src/libsystemd-network/test-lldp.c index b8490073dd..0243bc132b 100644 --- a/src/libsystemd-network/test-lldp.c +++ b/src/libsystemd-network/test-lldp.c @@ -23,8 +23,8 @@ #include #include -#include "sd-event.h" -#include "sd-lldp.h" +#include +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c index f7b2eb8050..863a76637c 100644 --- a/src/libsystemd-network/test-ndisc-rs.c +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -19,7 +19,7 @@ #include -#include "sd-ndisc.h" +#include #include "icmp6-util.h" #include "socket-util.h" diff --git a/src/libsystemd/Makefile b/src/libsystemd/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd/Makefile b/src/libsystemd/Makefile new file mode 100644 index 0000000000..79cfae8f47 --- /dev/null +++ b/src/libsystemd/Makefile @@ -0,0 +1,283 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +LIBSYSTEMD_CURRENT=14 +LIBSYSTEMD_REVISION=0 +LIBSYSTEMD_AGE=14 + +# The following four libraries only exist for compatibility reasons, +# their version info should not be bumped anymore +LIBSYSTEMD_LOGIN_CURRENT=9 +LIBSYSTEMD_LOGIN_REVISION=3 +LIBSYSTEMD_LOGIN_AGE=9 + +LIBSYSTEMD_DAEMON_CURRENT=0 +LIBSYSTEMD_DAEMON_REVISION=12 +LIBSYSTEMD_DAEMON_AGE=0 + +LIBSYSTEMD_ID128_CURRENT=0 +LIBSYSTEMD_ID128_REVISION=28 +LIBSYSTEMD_ID128_AGE=0 + +LIBSYSTEMD_JOURNAL_CURRENT=11 +LIBSYSTEMD_JOURNAL_REVISION=5 +LIBSYSTEMD_JOURNAL_AGE=11 + +EXTRA_DIST += \ + src/libsystemd/libsystemd.pc.in \ + src/libsystemd/sd-bus/DIFFERENCES \ + src/libsystemd/sd-bus/GVARIANT-SERIALIZATION + +libsystemd_la_SOURCES = \ + $(libsystemd_internal_la_SOURCES) \ + $(libsystemd_journal_internal_la_SOURCES) + +nodist_libsystemd_la_SOURCES = \ + $(nodist_libsystemd_internal_la_SOURCES) + +libsystemd_la_CFLAGS = \ + $(libsystemd_internal_la_CFLAGS) \ + $(libsystemd_journal_internal_la_CFLAGS) + +libsystemd_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -version-info $(LIBSYSTEMD_CURRENT):$(LIBSYSTEMD_REVISION):$(LIBSYSTEMD_AGE) \ + -Wl,--version-script=$(top_srcdir)/src/libsystemd/libsystemd.sym + +libsystemd_la_LIBADD = \ + $(libsystemd_internal_la_LIBADD) \ + $(libsystemd_journal_internal_la_LIBADD) + +libsystemd-install-hook: + libname=libsystemd.so && $(move-to-rootlibdir) + +libsystemd-uninstall-hook: + rm -f $(DESTDIR)$(rootlibdir)/libsystemd.so* + +INSTALL_EXEC_HOOKS += libsystemd-install-hook +UNINSTALL_EXEC_HOOKS += libsystemd-uninstall-hook + +pkgconfiglib_DATA += \ + src/libsystemd/libsystemd.pc + +pkginclude_HEADERS += \ + src/systemd/sd-bus.h \ + src/systemd/sd-bus-protocol.h \ + src/systemd/sd-bus-vtable.h \ + src/systemd/sd-event.h \ + src/systemd/sd-login.h \ + src/systemd/sd-id128.h \ + src/systemd/sd-daemon.h + +lib_LTLIBRARIES += \ + libsystemd.la + +tests += \ + test-bus-marshal \ + test-bus-signature \ + test-bus-benchmark \ + test-bus-chat \ + test-bus-cleanup \ + test-bus-server \ + test-bus-match \ + test-bus-proxy \ + test-bus-kernel \ + test-bus-kernel-bloom \ + test-bus-zero-copy \ + test-bus-introspect \ + test-bus-objects \ + test-bus-error \ + test-bus-creds \ + test-bus-gvariant \ + test-event \ + test-netlink \ + test-local-addresses \ + test-resolve + +bin_PROGRAMS += \ + busctl + +test_bus_marshal_SOURCES = \ + src/libsystemd/sd-bus/test-bus-marshal.c + +test_bus_marshal_LDADD = \ + libshared.la \ + $(GLIB_LIBS) \ + $(DBUS_LIBS) + +test_bus_marshal_CFLAGS = \ + $(AM_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) + +test_bus_signature_SOURCES = \ + src/libsystemd/sd-bus/test-bus-signature.c + +test_bus_signature_LDADD = \ + libshared.la + +test_bus_chat_SOURCES = \ + src/libsystemd/sd-bus/test-bus-chat.c + +test_bus_chat_LDADD = \ + libshared.la + +test_bus_cleanup_SOURCES = \ + src/libsystemd/sd-bus/test-bus-cleanup.c + +test_bus_cleanup_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) + +test_bus_cleanup_LDADD = \ + libshared.la + +test_bus_server_SOURCES = \ + src/libsystemd/sd-bus/test-bus-server.c + +test_bus_server_LDADD = \ + libshared.la + +test_bus_objects_SOURCES = \ + src/libsystemd/sd-bus/test-bus-objects.c + +test_bus_objects_LDADD = \ + libshared.la + +test_bus_error_SOURCES = \ + src/libsystemd/sd-bus/test-bus-error.c + +test_bus_error_LDADD = \ + libshared.la + +test_bus_gvariant_SOURCES = \ + src/libsystemd/sd-bus/test-bus-gvariant.c + +test_bus_gvariant_LDADD = \ + libshared.la \ + $(GLIB_LIBS) + +test_bus_gvariant_CFLAGS = \ + $(AM_CFLAGS) \ + $(GLIB_CFLAGS) + +test_bus_creds_SOURCES = \ + src/libsystemd/sd-bus/test-bus-creds.c + +test_bus_creds_LDADD = \ + libshared.la + +test_bus_match_SOURCES = \ + src/libsystemd/sd-bus/test-bus-match.c + +test_bus_match_LDADD = \ + libshared.la + +test_bus_proxy_SOURCES = \ + src/libsystemd/sd-bus/test-bus-proxy.c + +test_bus_proxy_LDADD = \ + libshared.la + +test_bus_kernel_SOURCES = \ + src/libsystemd/sd-bus/test-bus-kernel.c + +test_bus_kernel_LDADD = \ + libshared.la + +test_bus_kernel_bloom_SOURCES = \ + src/libsystemd/sd-bus/test-bus-kernel-bloom.c + +test_bus_kernel_bloom_LDADD = \ + libshared.la + +test_bus_benchmark_SOURCES = \ + src/libsystemd/sd-bus/test-bus-benchmark.c + +test_bus_benchmark_LDADD = \ + libshared.la + +test_bus_zero_copy_SOURCES = \ + src/libsystemd/sd-bus/test-bus-zero-copy.c + +test_bus_zero_copy_LDADD = \ + libshared.la + +test_bus_introspect_SOURCES = \ + src/libsystemd/sd-bus/test-bus-introspect.c + +test_bus_introspect_LDADD = \ + libshared.la + +test_event_SOURCES = \ + src/libsystemd/sd-event/test-event.c + +test_event_LDADD = \ + libshared.la + +test_netlink_SOURCES = \ + src/libsystemd/sd-netlink/test-netlink.c + +test_netlink_LDADD = \ + libshared.la + +test_local_addresses_SOURCES = \ + src/libsystemd/sd-netlink/test-local-addresses.c + +test_local_addresses_LDADD = \ + libshared.la + +test_resolve_SOURCES = \ + src/libsystemd/sd-resolve/test-resolve.c + +test_resolve_LDADD = \ + libshared.la + +busctl_SOURCES = \ + src/libsystemd/sd-bus/busctl.c \ + src/libsystemd/sd-bus/busctl-introspect.c \ + src/libsystemd/sd-bus/busctl-introspect.h + +busctl_LDADD = \ + libshared.la + +test-libsystemd-sym.c: \ + $(top_builddir)/src/libsystemd/libsystemd.sym \ + src/systemd/sd-journal.h \ + src/systemd/sd-daemon.h \ + src/systemd/sd-login.h \ + src/systemd/sd-bus.h \ + src/systemd/sd-utf8.h \ + src/systemd/sd-resolve.h \ + src/systemd/sd-path.h \ + src/systemd/sd-event.h + $(generate-sym-test) + +nodist_test_libsystemd_sym_SOURCES = \ + test-libsystemd-sym.c +test_libsystemd_sym_LDADD = \ + libsystemd.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd/compat-libs/.gitignore b/src/libsystemd/compat-libs/.gitignore new file mode 100644 index 0000000000..662c154cdd --- /dev/null +++ b/src/libsystemd/compat-libs/.gitignore @@ -0,0 +1 @@ +/libsystemd-*.pc diff --git a/src/libsystemd/compat-libs/Makefile b/src/libsystemd/compat-libs/Makefile new file mode 100644 index 0000000000..9ac9d1b560 --- /dev/null +++ b/src/libsystemd/compat-libs/Makefile @@ -0,0 +1,146 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_COMPAT_LIBS),) +libsystemd-%.c: src/compat-libs/libsystemd-%.sym + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)sed -r -n 's/^ +(sd_.*);/obsolete_lib(\1,$(notdir $(basename $<)));/p' <$< >$@ + +BUILT_SOURCES += \ + libsystemd-journal.c \ + libsystemd-login.c \ + libsystemd-id128.c \ + libsystemd-daemon.c + +nodist_libsystemd_journal_la_SOURCES = \ + libsystemd-journal.c + +libsystemd_journal_la_SOURCES = \ + src/compat-libs/libsystemd-journal.sym + +libsystemd_journal_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -imacros$(top_srcdir)/src/compat-libs/linkwarning.h + +libsystemd_journal_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -version-info $(LIBSYSTEMD_JOURNAL_CURRENT):$(LIBSYSTEMD_JOURNAL_REVISION):$(LIBSYSTEMD_JOURNAL_AGE) \ + -Wl,--version-script=$(top_srcdir)/src/compat-libs/libsystemd-journal.sym + +libsystemd_journal_la_LIBADD = \ + libsystemd-journal-internal.la \ + libsystemd-internal.la + +nodist_libsystemd_login_la_SOURCES = \ + libsystemd-login.c + +libsystemd_login_la_SOURCES = \ + src/compat-libs/libsystemd-login.sym + +libsystemd_login_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -imacros$(top_srcdir)/src/compat-libs/linkwarning.h + +libsystemd_login_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -version-info $(LIBSYSTEMD_LOGIN_CURRENT):$(LIBSYSTEMD_LOGIN_REVISION):$(LIBSYSTEMD_LOGIN_AGE) \ + -Wl,--version-script=$(top_srcdir)/src/compat-libs/libsystemd-login.sym + +libsystemd_login_la_LIBADD = \ + libsystemd-internal.la + +nodist_libsystemd_id128_la_SOURCES = \ + libsystemd-id128.c + +libsystemd_id128_la_SOURCES = \ + src/compat-libs/libsystemd-id128.sym + +libsystemd_id128_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -imacros$(top_srcdir)/src/compat-libs/linkwarning.h + +libsystemd_id128_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -version-info $(LIBSYSTEMD_ID128_CURRENT):$(LIBSYSTEMD_ID128_REVISION):$(LIBSYSTEMD_ID128_AGE) \ + -Wl,--version-script=$(top_srcdir)/src/compat-libs/libsystemd-id128.sym + +libsystemd_id128_la_LIBADD = \ + libsystemd-internal.la + +nodist_libsystemd_daemon_la_SOURCES = \ + libsystemd-daemon.c + +libsystemd_daemon_la_SOURCES = \ + src/compat-libs/libsystemd-daemon.sym + +libsystemd_daemon_la_CPPFLAGS = \ + $(AM_CPPFLAGS) \ + -imacros$(top_srcdir)/src/compat-libs/linkwarning.h + +libsystemd_daemon_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -version-info $(LIBSYSTEMD_DAEMON_CURRENT):$(LIBSYSTEMD_DAEMON_REVISION):$(LIBSYSTEMD_DAEMON_AGE) \ + -Wl,--version-script=$(top_srcdir)/src/compat-libs/libsystemd-daemon.sym + +libsystemd_daemon_la_LIBADD = \ + libsystemd-internal.la + +lib_LTLIBRARIES += \ + libsystemd-journal.la \ + libsystemd-login.la \ + libsystemd-id128.la \ + libsystemd-daemon.la + +pkgconfiglib_DATA += \ + src/compat-libs/libsystemd-journal.pc \ + src/compat-libs/libsystemd-login.pc \ + src/compat-libs/libsystemd-id128.pc \ + src/compat-libs/libsystemd-daemon.pc + +# move lib from $(libdir) to $(rootlibdir) and update devel link, if needed +compat-lib-install-hook: + libname=libsystemd-login.so && $(move-to-rootlibdir) + libname=libsystemd-journal.so && $(move-to-rootlibdir) + libname=libsystemd-id128.so && $(move-to-rootlibdir) + libname=libsystemd-daemon.so && $(move-to-rootlibdir) + +compat-lib-uninstall-hook: + rm -f $(DESTDIR)$(rootlibdir)/libsystemd-login.so* + rm -f $(DESTDIR)$(rootlibdir)/libsystemd-journal.so* + rm -f $(DESTDIR)$(rootlibdir)/libsystemd-id128.so* + rm -f $(DESTDIR)$(rootlibdir)/libsystemd-daemon.so* + +INSTALL_EXEC_HOOKS += compat-lib-install-hook +UNINSTALL_EXEC_HOOKS += compat-lib-uninstall-hook +endif + +EXTRA_DIST += \ + src/compat-libs/linkwarning.h \ + src/compat-libs/libsystemd-journal.pc.in \ + src/compat-libs/libsystemd-login.pc.in \ + src/compat-libs/libsystemd-id128.pc.in \ + src/compat-libs/libsystemd-daemon.pc.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd/compat-libs/libsystemd-daemon.pc.in b/src/libsystemd/compat-libs/libsystemd-daemon.pc.in new file mode 100644 index 0000000000..847afc9d60 --- /dev/null +++ b/src/libsystemd/compat-libs/libsystemd-daemon.pc.in @@ -0,0 +1,19 @@ +# Permission is hereby granted, free of charge, to any person +# obtaining a copy of this software and associated documentation files +# (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, +# publish, distribute, sublicense, and/or sell copies of the Software, +# and to permit persons to whom the Software is furnished to do so, +# subject to the following conditions: + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: systemd +Description: systemd Daemon Utility Library - deprecated +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lsystemd +Cflags: -I${includedir} diff --git a/src/libsystemd/compat-libs/libsystemd-daemon.sym b/src/libsystemd/compat-libs/libsystemd-daemon.sym new file mode 100644 index 0000000000..f440238931 --- /dev/null +++ b/src/libsystemd/compat-libs/libsystemd-daemon.sym @@ -0,0 +1,27 @@ +/*** + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation files + (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, + publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: +***/ + +/* Original symbols from systemd v31 */ + +LIBSYSTEMD_DAEMON_31 { +global: + sd_booted; + sd_is_fifo; + sd_is_mq; + sd_is_socket; + sd_is_socket_inet; + sd_is_socket_unix; + sd_is_special; + sd_listen_fds; + sd_notify; + sd_notifyf; +local: + *; +}; diff --git a/src/libsystemd/compat-libs/libsystemd-id128.pc.in b/src/libsystemd/compat-libs/libsystemd-id128.pc.in new file mode 100644 index 0000000000..80f8fee6c3 --- /dev/null +++ b/src/libsystemd/compat-libs/libsystemd-id128.pc.in @@ -0,0 +1,18 @@ +# 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. + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: systemd +Description: systemd 128 Bit ID Utility Library - deprecated +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lsystemd +Cflags: -I${includedir} diff --git a/src/libsystemd/compat-libs/libsystemd-id128.sym b/src/libsystemd/compat-libs/libsystemd-id128.sym new file mode 100644 index 0000000000..604c0026c6 --- /dev/null +++ b/src/libsystemd/compat-libs/libsystemd-id128.sym @@ -0,0 +1,21 @@ +/*** + 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. +***/ + +/* Original symbols from systemd v38 */ + +LIBSYSTEMD_ID128_38 { +global: + sd_id128_to_string; + sd_id128_from_string; + sd_id128_randomize; + sd_id128_get_machine; + sd_id128_get_boot; +local: + *; +}; diff --git a/src/libsystemd/compat-libs/libsystemd-journal.pc.in b/src/libsystemd/compat-libs/libsystemd-journal.pc.in new file mode 100644 index 0000000000..395f71005b --- /dev/null +++ b/src/libsystemd/compat-libs/libsystemd-journal.pc.in @@ -0,0 +1,19 @@ +# 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. + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: systemd +Description: systemd Journal Utility Library - deprecated +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ +Requires: libsystemd = @PACKAGE_VERSION@ +Libs: -L${libdir} -lsystemd +Cflags: -I${includedir} diff --git a/src/libsystemd/compat-libs/libsystemd-journal.sym b/src/libsystemd/compat-libs/libsystemd-journal.sym new file mode 100644 index 0000000000..4eb15910d2 --- /dev/null +++ b/src/libsystemd/compat-libs/libsystemd-journal.sym @@ -0,0 +1,111 @@ +/*** + 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. +***/ + +/* Original symbols from systemd v38 */ + +LIBSYSTEMD_JOURNAL_38 { +global: + sd_journal_print; + sd_journal_printv; + sd_journal_send; + sd_journal_sendv; + sd_journal_stream_fd; + sd_journal_open; + sd_journal_close; + sd_journal_previous; + sd_journal_next; + sd_journal_previous_skip; + sd_journal_next_skip; + sd_journal_get_realtime_usec; + sd_journal_get_monotonic_usec; + sd_journal_get_data; + sd_journal_enumerate_data; + sd_journal_restart_data; + sd_journal_add_match; + sd_journal_flush_matches; + sd_journal_seek_head; + sd_journal_seek_tail; + sd_journal_seek_monotonic_usec; + sd_journal_seek_realtime_usec; + sd_journal_seek_cursor; + sd_journal_get_cursor; + sd_journal_get_fd; + sd_journal_process; +local: + *; +}; + +LIBSYSTEMD_JOURNAL_183 { +global: + sd_journal_print_with_location; + sd_journal_printv_with_location; + sd_journal_send_with_location; + sd_journal_sendv_with_location; +} LIBSYSTEMD_JOURNAL_38; + +LIBSYSTEMD_JOURNAL_184 { +global: + sd_journal_get_cutoff_realtime_usec; + sd_journal_get_cutoff_monotonic_usec; +} LIBSYSTEMD_JOURNAL_183; + +LIBSYSTEMD_JOURNAL_187 { +global: + sd_journal_wait; + sd_journal_open_directory; + sd_journal_add_disjunction; +} LIBSYSTEMD_JOURNAL_184; + +LIBSYSTEMD_JOURNAL_188 { +global: + sd_journal_perror; + sd_journal_perror_with_location; +} LIBSYSTEMD_JOURNAL_187; + +LIBSYSTEMD_JOURNAL_190 { +global: + sd_journal_get_usage; +} LIBSYSTEMD_JOURNAL_188; + +LIBSYSTEMD_JOURNAL_195 { +global: + sd_journal_test_cursor; + sd_journal_query_unique; + sd_journal_enumerate_unique; + sd_journal_restart_unique; +} LIBSYSTEMD_JOURNAL_190; + +LIBSYSTEMD_JOURNAL_196 { +global: + sd_journal_get_catalog; + sd_journal_get_catalog_for_message_id; + sd_journal_set_data_threshold; + sd_journal_get_data_threshold; +} LIBSYSTEMD_JOURNAL_195; + +LIBSYSTEMD_JOURNAL_198 { +global: + sd_journal_reliable_fd; +} LIBSYSTEMD_JOURNAL_196; + +LIBSYSTEMD_JOURNAL_201 { +global: + sd_journal_get_events; + sd_journal_get_timeout; +} LIBSYSTEMD_JOURNAL_198; + +LIBSYSTEMD_JOURNAL_202 { +global: + sd_journal_add_conjunction; +} LIBSYSTEMD_JOURNAL_201; + +LIBSYSTEMD_JOURNAL_205 { +global: + sd_journal_open_files; +} LIBSYSTEMD_JOURNAL_202; diff --git a/src/libsystemd/compat-libs/libsystemd-login.pc.in b/src/libsystemd/compat-libs/libsystemd-login.pc.in new file mode 100644 index 0000000000..db3f79c99a --- /dev/null +++ b/src/libsystemd/compat-libs/libsystemd-login.pc.in @@ -0,0 +1,18 @@ +# 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. + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: systemd +Description: systemd Login Utility Library - deprecated +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ +Libs: -L${libdir} -lsystemd +Cflags: -I${includedir} diff --git a/src/libsystemd/compat-libs/libsystemd-login.sym b/src/libsystemd/compat-libs/libsystemd-login.sym new file mode 100644 index 0000000000..54aa91c609 --- /dev/null +++ b/src/libsystemd/compat-libs/libsystemd-login.sym @@ -0,0 +1,87 @@ +/*** + 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. +***/ + +/* Original symbols from systemd v31 */ + +LIBSYSTEMD_LOGIN_31 { +global: + sd_get_seats; + sd_get_sessions; + sd_get_uids; + sd_login_monitor_flush; + sd_login_monitor_get_fd; + sd_login_monitor_new; + sd_login_monitor_unref; + sd_pid_get_owner_uid; + sd_pid_get_session; + sd_seat_can_multi_session; + sd_seat_get_active; + sd_seat_get_sessions; + sd_session_get_seat; + sd_session_get_uid; + sd_session_is_active; + sd_uid_get_seats; + sd_uid_get_sessions; + sd_uid_get_state; + sd_uid_is_on_seat; +local: + *; +}; + +LIBSYSTEMD_LOGIN_38 { +global: + sd_pid_get_unit; + sd_session_get_service; +} LIBSYSTEMD_LOGIN_31; + +LIBSYSTEMD_LOGIN_43 { +global: + sd_session_get_type; + sd_session_get_class; + sd_session_get_display; +} LIBSYSTEMD_LOGIN_38; + +LIBSYSTEMD_LOGIN_186 { +global: + sd_session_get_state; + sd_seat_can_tty; + sd_seat_can_graphical; +} LIBSYSTEMD_LOGIN_43; + +LIBSYSTEMD_LOGIN_198 { +global: + sd_session_get_tty; +} LIBSYSTEMD_LOGIN_186; + +LIBSYSTEMD_LOGIN_201 { +global: + sd_login_monitor_get_events; + sd_login_monitor_get_timeout; +} LIBSYSTEMD_LOGIN_198; + +LIBSYSTEMD_LOGIN_202 { +global: + sd_pid_get_user_unit; + sd_pid_get_machine_name; +} LIBSYSTEMD_LOGIN_201; + +LIBSYSTEMD_LOGIN_203 { +global: + sd_get_machine_names; +} LIBSYSTEMD_LOGIN_202; + +LIBSYSTEMD_LOGIN_205 { +global: + sd_pid_get_slice; +} LIBSYSTEMD_LOGIN_203; + +LIBSYSTEMD_LOGIN_207 { +global: + sd_session_get_vt; +} LIBSYSTEMD_LOGIN_205; diff --git a/src/libsystemd/compat-libs/linkwarning.h b/src/libsystemd/compat-libs/linkwarning.h new file mode 100644 index 0000000000..79ece9e7d1 --- /dev/null +++ b/src/libsystemd/compat-libs/linkwarning.h @@ -0,0 +1,35 @@ +/*** + This file is part of systemd, but is heavily based on + glibc's libc-symbols.h. + + Copyright (C) 1995-1998,2000-2006,2008,2009 Free Software Foundation, Inc + + 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 . +***/ + +#pragma once + +#define __make_section_unallocated(section_string) \ + asm (".section " section_string "\n\t.previous"); + +#define __sec_comment "\n#APP\n\t#" + +#define link_warning(symbol, msg) \ + __make_section_unallocated (".gnu.warning." #symbol) \ + static const char __evoke_link_warning_##symbol[] \ + __attribute__ ((used, section (".gnu.warning." #symbol __sec_comment))) \ + = msg + +#define obsolete_lib(name, lib) \ + link_warning(name, #name " was moved to libsystemd. Do not use " #lib ".") diff --git a/src/libsystemd/include/systemd/_sd-common.h b/src/libsystemd/include/systemd/_sd-common.h new file mode 100644 index 0000000000..2d4e1f26e1 --- /dev/null +++ b/src/libsystemd/include/systemd/_sd-common.h @@ -0,0 +1,83 @@ +#ifndef foosdcommonhfoo +#define foosdcommonhfoo + +/*** + 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 . +***/ + +/* This is a private header; never even think of including this directly! */ + +#if __INCLUDE_LEVEL__ <= 1 +#error "Do not include _sd-common.h directly; it is a private header." +#endif + +#ifndef _sd_printf_ +# if __GNUC__ >= 4 +# define _sd_printf_(a,b) __attribute__ ((format (printf, a, b))) +# else +# define _sd_printf_(a,b) +# endif +#endif + +#ifndef _sd_sentinel_ +# define _sd_sentinel_ __attribute__((sentinel)) +#endif + +#ifndef _sd_packed_ +# define _sd_packed_ __attribute__((packed)) +#endif + +#ifndef _sd_pure_ +# define _sd_pure_ __attribute__((pure)) +#endif + +#ifndef _SD_STRINGIFY +# define _SD_XSTRINGIFY(x) #x +# define _SD_STRINGIFY(x) _SD_XSTRINGIFY(x) +#endif + +#ifndef _SD_BEGIN_DECLARATIONS +# ifdef __cplusplus +# define _SD_BEGIN_DECLARATIONS \ + extern "C" { \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ +# else +# define _SD_BEGIN_DECLARATIONS \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ +# endif +#endif + +#ifndef _SD_END_DECLARATIONS +# ifdef __cplusplus +# define _SD_END_DECLARATIONS \ + } \ + struct _sd_useless_cpp_struct_to_allow_trailing_semicolon_ +# else +# define _SD_END_DECLARATIONS \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ +# endif +#endif + +#define _SD_DEFINE_POINTER_CLEANUP_FUNC(type, func) \ + static inline void func##p(type **p) { \ + if (*p) \ + func(*p); \ + } \ + struct _sd_useless_struct_to_allow_trailing_semicolon_ + +#endif diff --git a/src/libsystemd/include/systemd/sd-bus-protocol.h b/src/libsystemd/include/systemd/sd-bus-protocol.h new file mode 100644 index 0000000000..47b256d5b9 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-bus-protocol.h @@ -0,0 +1,102 @@ +#ifndef foosdbusprotocolhfoo +#define foosdbusprotocolhfoo + +/*** + 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 . +***/ + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* Types of message */ + +enum { + _SD_BUS_MESSAGE_TYPE_INVALID = 0, + SD_BUS_MESSAGE_METHOD_CALL, + SD_BUS_MESSAGE_METHOD_RETURN, + SD_BUS_MESSAGE_METHOD_ERROR, + SD_BUS_MESSAGE_SIGNAL, + _SD_BUS_MESSAGE_TYPE_MAX +}; + +/* Primitive types */ + +enum { + _SD_BUS_TYPE_INVALID = 0, + SD_BUS_TYPE_BYTE = 'y', + SD_BUS_TYPE_BOOLEAN = 'b', + SD_BUS_TYPE_INT16 = 'n', + SD_BUS_TYPE_UINT16 = 'q', + SD_BUS_TYPE_INT32 = 'i', + SD_BUS_TYPE_UINT32 = 'u', + SD_BUS_TYPE_INT64 = 'x', + SD_BUS_TYPE_UINT64 = 't', + SD_BUS_TYPE_DOUBLE = 'd', + SD_BUS_TYPE_STRING = 's', + SD_BUS_TYPE_OBJECT_PATH = 'o', + SD_BUS_TYPE_SIGNATURE = 'g', + SD_BUS_TYPE_UNIX_FD = 'h', + SD_BUS_TYPE_ARRAY = 'a', + SD_BUS_TYPE_VARIANT = 'v', + SD_BUS_TYPE_STRUCT = 'r', /* not actually used in signatures */ + SD_BUS_TYPE_STRUCT_BEGIN = '(', + SD_BUS_TYPE_STRUCT_END = ')', + SD_BUS_TYPE_DICT_ENTRY = 'e', /* not actually used in signatures */ + SD_BUS_TYPE_DICT_ENTRY_BEGIN = '{', + SD_BUS_TYPE_DICT_ENTRY_END = '}', +}; + +/* Well-known errors. Note that this is only a sanitized subset of the + * errors that the reference implementation generates. */ + +#define SD_BUS_ERROR_FAILED "org.freedesktop.DBus.Error.Failed" +#define SD_BUS_ERROR_NO_MEMORY "org.freedesktop.DBus.Error.NoMemory" +#define SD_BUS_ERROR_SERVICE_UNKNOWN "org.freedesktop.DBus.Error.ServiceUnknown" +#define SD_BUS_ERROR_NAME_HAS_NO_OWNER "org.freedesktop.DBus.Error.NameHasNoOwner" +#define SD_BUS_ERROR_NO_REPLY "org.freedesktop.DBus.Error.NoReply" +#define SD_BUS_ERROR_IO_ERROR "org.freedesktop.DBus.Error.IOError" +#define SD_BUS_ERROR_BAD_ADDRESS "org.freedesktop.DBus.Error.BadAddress" +#define SD_BUS_ERROR_NOT_SUPPORTED "org.freedesktop.DBus.Error.NotSupported" +#define SD_BUS_ERROR_LIMITS_EXCEEDED "org.freedesktop.DBus.Error.LimitsExceeded" +#define SD_BUS_ERROR_ACCESS_DENIED "org.freedesktop.DBus.Error.AccessDenied" +#define SD_BUS_ERROR_AUTH_FAILED "org.freedesktop.DBus.Error.AuthFailed" +#define SD_BUS_ERROR_NO_SERVER "org.freedesktop.DBus.Error.NoServer" +#define SD_BUS_ERROR_TIMEOUT "org.freedesktop.DBus.Error.Timeout" +#define SD_BUS_ERROR_NO_NETWORK "org.freedesktop.DBus.Error.NoNetwork" +#define SD_BUS_ERROR_ADDRESS_IN_USE "org.freedesktop.DBus.Error.AddressInUse" +#define SD_BUS_ERROR_DISCONNECTED "org.freedesktop.DBus.Error.Disconnected" +#define SD_BUS_ERROR_INVALID_ARGS "org.freedesktop.DBus.Error.InvalidArgs" +#define SD_BUS_ERROR_FILE_NOT_FOUND "org.freedesktop.DBus.Error.FileNotFound" +#define SD_BUS_ERROR_FILE_EXISTS "org.freedesktop.DBus.Error.FileExists" +#define SD_BUS_ERROR_UNKNOWN_METHOD "org.freedesktop.DBus.Error.UnknownMethod" +#define SD_BUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject" +#define SD_BUS_ERROR_UNKNOWN_INTERFACE "org.freedesktop.DBus.Error.UnknownInterface" +#define SD_BUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" +#define SD_BUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly" +#define SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN "org.freedesktop.DBus.Error.UnixProcessIdUnknown" +#define SD_BUS_ERROR_INVALID_SIGNATURE "org.freedesktop.DBus.Error.InvalidSignature" +#define SD_BUS_ERROR_INCONSISTENT_MESSAGE "org.freedesktop.DBus.Error.InconsistentMessage" +#define SD_BUS_ERROR_MATCH_RULE_NOT_FOUND "org.freedesktop.DBus.Error.MatchRuleNotFound" +#define SD_BUS_ERROR_MATCH_RULE_INVALID "org.freedesktop.DBus.Error.MatchRuleInvalid" +#define SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED \ + "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired" + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-bus-vtable.h b/src/libsystemd/include/systemd/sd-bus-vtable.h new file mode 100644 index 0000000000..9363f5425a --- /dev/null +++ b/src/libsystemd/include/systemd/sd-bus-vtable.h @@ -0,0 +1,141 @@ +#ifndef foosdbusvtablehfoo +#define foosdbusvtablehfoo + +/*** + 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 . +***/ + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_bus_vtable sd_bus_vtable; + +#include + +enum { + _SD_BUS_VTABLE_START = '<', + _SD_BUS_VTABLE_END = '>', + _SD_BUS_VTABLE_METHOD = 'M', + _SD_BUS_VTABLE_SIGNAL = 'S', + _SD_BUS_VTABLE_PROPERTY = 'P', + _SD_BUS_VTABLE_WRITABLE_PROPERTY = 'W', +}; + +enum { + SD_BUS_VTABLE_DEPRECATED = 1ULL << 0, + SD_BUS_VTABLE_HIDDEN = 1ULL << 1, + SD_BUS_VTABLE_UNPRIVILEGED = 1ULL << 2, + SD_BUS_VTABLE_METHOD_NO_REPLY = 1ULL << 3, + SD_BUS_VTABLE_PROPERTY_CONST = 1ULL << 4, + SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE = 1ULL << 5, + SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION = 1ULL << 6, + SD_BUS_VTABLE_PROPERTY_EXPLICIT = 1ULL << 7, + _SD_BUS_VTABLE_CAPABILITY_MASK = 0xFFFFULL << 40 +}; + +#define SD_BUS_VTABLE_CAPABILITY(x) ((uint64_t) (((x)+1) & 0xFFFF) << 40) + +struct sd_bus_vtable { + /* Please do not initialize this structure directly, use the + * macros below instead */ + + uint8_t type:8; + uint64_t flags:56; + union { + struct { + size_t element_size; + } start; + struct { + const char *member; + const char *signature; + const char *result; + sd_bus_message_handler_t handler; + size_t offset; + } method; + struct { + const char *member; + const char *signature; + } signal; + struct { + const char *member; + const char *signature; + sd_bus_property_get_t get; + sd_bus_property_set_t set; + size_t offset; + } property; + } x; +}; + +#define SD_BUS_VTABLE_START(_flags) \ + { \ + .type = _SD_BUS_VTABLE_START, \ + .flags = _flags, \ + .x.start.element_size = sizeof(sd_bus_vtable), \ + } + +#define SD_BUS_METHOD_WITH_OFFSET(_member, _signature, _result, _handler, _offset, _flags) \ + { \ + .type = _SD_BUS_VTABLE_METHOD, \ + .flags = _flags, \ + .x.method.member = _member, \ + .x.method.signature = _signature, \ + .x.method.result = _result, \ + .x.method.handler = _handler, \ + .x.method.offset = _offset, \ + } +#define SD_BUS_METHOD(_member, _signature, _result, _handler, _flags) \ + SD_BUS_METHOD_WITH_OFFSET(_member, _signature, _result, _handler, 0, _flags) + +#define SD_BUS_SIGNAL(_member, _signature, _flags) \ + { \ + .type = _SD_BUS_VTABLE_SIGNAL, \ + .flags = _flags, \ + .x.signal.member = _member, \ + .x.signal.signature = _signature, \ + } + +#define SD_BUS_PROPERTY(_member, _signature, _get, _offset, _flags) \ + { \ + .type = _SD_BUS_VTABLE_PROPERTY, \ + .flags = _flags, \ + .x.property.member = _member, \ + .x.property.signature = _signature, \ + .x.property.get = _get, \ + .x.property.offset = _offset, \ + } + +#define SD_BUS_WRITABLE_PROPERTY(_member, _signature, _get, _set, _offset, _flags) \ + { \ + .type = _SD_BUS_VTABLE_WRITABLE_PROPERTY, \ + .flags = _flags, \ + .x.property.member = _member, \ + .x.property.signature = _signature, \ + .x.property.get = _get, \ + .x.property.set = _set, \ + .x.property.offset = _offset, \ + } + +#define SD_BUS_VTABLE_END \ + { \ + .type = _SD_BUS_VTABLE_END, \ + } + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-bus.h b/src/libsystemd/include/systemd/sd-bus.h new file mode 100644 index 0000000000..2ded47c2b6 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-bus.h @@ -0,0 +1,456 @@ +#ifndef foosdbushfoo +#define foosdbushfoo + +/*** + 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 . +***/ + +#include +#include +#include +#include + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* Types */ + +typedef struct sd_bus sd_bus; +typedef struct sd_bus_message sd_bus_message; +typedef struct sd_bus_slot sd_bus_slot; +typedef struct sd_bus_creds sd_bus_creds; +typedef struct sd_bus_track sd_bus_track; + +typedef struct { + const char *name; + const char *message; + int _need_free; +} sd_bus_error; + +typedef struct { + const char* name; + int code; +} sd_bus_error_map; + +/* Flags */ + +enum { + SD_BUS_CREDS_PID = 1ULL << 0, + SD_BUS_CREDS_TID = 1ULL << 1, + SD_BUS_CREDS_PPID = 1ULL << 2, + SD_BUS_CREDS_UID = 1ULL << 3, + SD_BUS_CREDS_EUID = 1ULL << 4, + SD_BUS_CREDS_SUID = 1ULL << 5, + SD_BUS_CREDS_FSUID = 1ULL << 6, + SD_BUS_CREDS_GID = 1ULL << 7, + SD_BUS_CREDS_EGID = 1ULL << 8, + SD_BUS_CREDS_SGID = 1ULL << 9, + SD_BUS_CREDS_FSGID = 1ULL << 10, + SD_BUS_CREDS_SUPPLEMENTARY_GIDS = 1ULL << 11, + SD_BUS_CREDS_COMM = 1ULL << 12, + SD_BUS_CREDS_TID_COMM = 1ULL << 13, + SD_BUS_CREDS_EXE = 1ULL << 14, + SD_BUS_CREDS_CMDLINE = 1ULL << 15, + SD_BUS_CREDS_CGROUP = 1ULL << 16, + SD_BUS_CREDS_UNIT = 1ULL << 17, + SD_BUS_CREDS_SLICE = 1ULL << 18, + SD_BUS_CREDS_USER_UNIT = 1ULL << 19, + SD_BUS_CREDS_USER_SLICE = 1ULL << 20, + SD_BUS_CREDS_SESSION = 1ULL << 21, + SD_BUS_CREDS_OWNER_UID = 1ULL << 22, + SD_BUS_CREDS_EFFECTIVE_CAPS = 1ULL << 23, + SD_BUS_CREDS_PERMITTED_CAPS = 1ULL << 24, + SD_BUS_CREDS_INHERITABLE_CAPS = 1ULL << 25, + SD_BUS_CREDS_BOUNDING_CAPS = 1ULL << 26, + SD_BUS_CREDS_SELINUX_CONTEXT = 1ULL << 27, + SD_BUS_CREDS_AUDIT_SESSION_ID = 1ULL << 28, + SD_BUS_CREDS_AUDIT_LOGIN_UID = 1ULL << 29, + SD_BUS_CREDS_TTY = 1ULL << 30, + SD_BUS_CREDS_UNIQUE_NAME = 1ULL << 31, + SD_BUS_CREDS_WELL_KNOWN_NAMES = 1ULL << 32, + SD_BUS_CREDS_DESCRIPTION = 1ULL << 33, + SD_BUS_CREDS_AUGMENT = 1ULL << 63, /* special flag, if on sd-bus will augment creds struct, in a potentially race-full way. */ + _SD_BUS_CREDS_ALL = (1ULL << 34) -1, +}; + +enum { + SD_BUS_NAME_REPLACE_EXISTING = 1ULL << 0, + SD_BUS_NAME_ALLOW_REPLACEMENT = 1ULL << 1, + SD_BUS_NAME_QUEUE = 1ULL << 2, +}; + +/* Callbacks */ + +typedef int (*sd_bus_message_handler_t)(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); +typedef int (*sd_bus_property_get_t) (sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); +typedef int (*sd_bus_property_set_t) (sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *value, void *userdata, sd_bus_error *ret_error); +typedef int (*sd_bus_object_find_t) (sd_bus *bus, const char *path, const char *interface, void *userdata, void **ret_found, sd_bus_error *ret_error); +typedef int (*sd_bus_node_enumerator_t) (sd_bus *bus, const char *prefix, void *userdata, char ***ret_nodes, sd_bus_error *ret_error); +typedef int (*sd_bus_track_handler_t) (sd_bus_track *track, void *userdata); + +#include +#include + +/* Connections */ + +int sd_bus_default(sd_bus **ret); +int sd_bus_default_user(sd_bus **ret); +int sd_bus_default_system(sd_bus **ret); + +int sd_bus_open(sd_bus **ret); +int sd_bus_open_user(sd_bus **ret); +int sd_bus_open_system(sd_bus **ret); +int sd_bus_open_system_remote(sd_bus **ret, const char *host); +int sd_bus_open_system_machine(sd_bus **ret, const char *machine); + +int sd_bus_new(sd_bus **ret); + +int sd_bus_set_address(sd_bus *bus, const char *address); +int sd_bus_set_fd(sd_bus *bus, int input_fd, int output_fd); +int sd_bus_set_exec(sd_bus *bus, const char *path, char *const argv[]); +int sd_bus_get_address(sd_bus *bus, const char **address); +int sd_bus_set_bus_client(sd_bus *bus, int b); +int sd_bus_is_bus_client(sd_bus *bus); +int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t bus_id); +int sd_bus_is_server(sd_bus *bus); +int sd_bus_set_anonymous(sd_bus *bus, int b); +int sd_bus_is_anonymous(sd_bus *bus); +int sd_bus_set_trusted(sd_bus *bus, int b); +int sd_bus_is_trusted(sd_bus *bus); +int sd_bus_set_monitor(sd_bus *bus, int b); +int sd_bus_is_monitor(sd_bus *bus); +int sd_bus_set_description(sd_bus *bus, const char *description); +int sd_bus_get_description(sd_bus *bus, const char **description); +int sd_bus_negotiate_creds(sd_bus *bus, int b, uint64_t creds_mask); +int sd_bus_negotiate_timestamp(sd_bus *bus, int b); +int sd_bus_negotiate_fds(sd_bus *bus, int b); +int sd_bus_can_send(sd_bus *bus, char type); +int sd_bus_get_creds_mask(sd_bus *bus, uint64_t *creds_mask); +int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b); +int sd_bus_get_allow_interactive_authorization(sd_bus *bus); + +int sd_bus_start(sd_bus *ret); + +int sd_bus_try_close(sd_bus *bus); +void sd_bus_close(sd_bus *bus); + +sd_bus *sd_bus_ref(sd_bus *bus); +sd_bus *sd_bus_unref(sd_bus *bus); +sd_bus *sd_bus_flush_close_unref(sd_bus *bus); + +void sd_bus_default_flush_close(void); + +int sd_bus_is_open(sd_bus *bus); + +int sd_bus_get_bus_id(sd_bus *bus, sd_id128_t *id); +int sd_bus_get_scope(sd_bus *bus, const char **scope); +int sd_bus_get_tid(sd_bus *bus, pid_t *tid); +int sd_bus_get_owner_creds(sd_bus *bus, uint64_t creds_mask, sd_bus_creds **ret); + +int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie); +int sd_bus_send_to(sd_bus *bus, sd_bus_message *m, const char *destination, uint64_t *cookie); +int sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply); +int sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec); + +int sd_bus_get_fd(sd_bus *bus); +int sd_bus_get_events(sd_bus *bus); +int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec); +int sd_bus_process(sd_bus *bus, sd_bus_message **r); +int sd_bus_process_priority(sd_bus *bus, int64_t max_priority, sd_bus_message **r); +int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec); +int sd_bus_flush(sd_bus *bus); + +sd_bus_slot* sd_bus_get_current_slot(sd_bus *bus); +sd_bus_message* sd_bus_get_current_message(sd_bus *bus); +sd_bus_message_handler_t sd_bus_get_current_handler(sd_bus *bus); +void* sd_bus_get_current_userdata(sd_bus *bus); + +int sd_bus_attach_event(sd_bus *bus, sd_event *e, int priority); +int sd_bus_detach_event(sd_bus *bus); +sd_event *sd_bus_get_event(sd_bus *bus); + +int sd_bus_add_filter(sd_bus *bus, sd_bus_slot **slot, sd_bus_message_handler_t callback, void *userdata); +int sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata); +int sd_bus_add_object(sd_bus *bus, sd_bus_slot **slot, const char *path, sd_bus_message_handler_t callback, void *userdata); +int sd_bus_add_fallback(sd_bus *bus, sd_bus_slot **slot, const char *prefix, sd_bus_message_handler_t callback, void *userdata); +int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata); +int sd_bus_add_fallback_vtable(sd_bus *bus, sd_bus_slot **slot, const char *prefix, const char *interface, const sd_bus_vtable *vtable, sd_bus_object_find_t find, void *userdata); +int sd_bus_add_node_enumerator(sd_bus *bus, sd_bus_slot **slot, const char *path, sd_bus_node_enumerator_t callback, void *userdata); +int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path); + +/* Slot object */ + +sd_bus_slot* sd_bus_slot_ref(sd_bus_slot *slot); +sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot); + +sd_bus* sd_bus_slot_get_bus(sd_bus_slot *slot); +void *sd_bus_slot_get_userdata(sd_bus_slot *slot); +void *sd_bus_slot_set_userdata(sd_bus_slot *slot, void *userdata); +int sd_bus_slot_set_description(sd_bus_slot *slot, const char *description); +int sd_bus_slot_get_description(sd_bus_slot *slot, const char **description); + +sd_bus_message* sd_bus_slot_get_current_message(sd_bus_slot *slot); +sd_bus_message_handler_t sd_bus_slot_get_current_handler(sd_bus_slot *bus); +void *sd_bus_slot_get_current_userdata(sd_bus_slot *slot); + +/* Message object */ + +int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member); +int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member); +int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m); +int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e); +int sd_bus_message_new_method_errorf(sd_bus_message *call, sd_bus_message **m, const char *name, const char *format, ...) _sd_printf_(4, 5); +int sd_bus_message_new_method_errno(sd_bus_message *call, sd_bus_message **m, int error, const sd_bus_error *e); +int sd_bus_message_new_method_errnof(sd_bus_message *call, sd_bus_message **m, int error, const char *format, ...) _sd_printf_(4, 5); + +sd_bus_message* sd_bus_message_ref(sd_bus_message *m); +sd_bus_message* sd_bus_message_unref(sd_bus_message *m); + +int sd_bus_message_get_type(sd_bus_message *m, uint8_t *type); +int sd_bus_message_get_cookie(sd_bus_message *m, uint64_t *cookie); +int sd_bus_message_get_reply_cookie(sd_bus_message *m, uint64_t *cookie); +int sd_bus_message_get_priority(sd_bus_message *m, int64_t *priority); + +int sd_bus_message_get_expect_reply(sd_bus_message *m); +int sd_bus_message_get_auto_start(sd_bus_message *m); +int sd_bus_message_get_allow_interactive_authorization(sd_bus_message *m); + +const char *sd_bus_message_get_signature(sd_bus_message *m, int complete); +const char *sd_bus_message_get_path(sd_bus_message *m); +const char *sd_bus_message_get_interface(sd_bus_message *m); +const char *sd_bus_message_get_member(sd_bus_message *m); +const char *sd_bus_message_get_destination(sd_bus_message *m); +const char *sd_bus_message_get_sender(sd_bus_message *m); +const sd_bus_error *sd_bus_message_get_error(sd_bus_message *m); +int sd_bus_message_get_errno(sd_bus_message *m); + +int sd_bus_message_get_monotonic_usec(sd_bus_message *m, uint64_t *usec); +int sd_bus_message_get_realtime_usec(sd_bus_message *m, uint64_t *usec); +int sd_bus_message_get_seqnum(sd_bus_message *m, uint64_t* seqnum); + +sd_bus* sd_bus_message_get_bus(sd_bus_message *m); +sd_bus_creds *sd_bus_message_get_creds(sd_bus_message *m); /* do not unref the result */ + +int sd_bus_message_is_signal(sd_bus_message *m, const char *interface, const char *member); +int sd_bus_message_is_method_call(sd_bus_message *m, const char *interface, const char *member); +int sd_bus_message_is_method_error(sd_bus_message *m, const char *name); +int sd_bus_message_is_empty(sd_bus_message *m); +int sd_bus_message_has_signature(sd_bus_message *m, const char *signature); + +int sd_bus_message_set_expect_reply(sd_bus_message *m, int b); +int sd_bus_message_set_auto_start(sd_bus_message *m, int b); +int sd_bus_message_set_allow_interactive_authorization(sd_bus_message *m, int b); + +int sd_bus_message_set_destination(sd_bus_message *m, const char *destination); +int sd_bus_message_set_priority(sd_bus_message *m, int64_t priority); + +int sd_bus_message_append(sd_bus_message *m, const char *types, ...); +int sd_bus_message_append_basic(sd_bus_message *m, char type, const void *p); +int sd_bus_message_append_array(sd_bus_message *m, char type, const void *ptr, size_t size); +int sd_bus_message_append_array_space(sd_bus_message *m, char type, size_t size, void **ptr); +int sd_bus_message_append_array_iovec(sd_bus_message *m, char type, const struct iovec *iov, unsigned n); +int sd_bus_message_append_array_memfd(sd_bus_message *m, char type, int memfd, uint64_t offset, uint64_t size); +int sd_bus_message_append_string_space(sd_bus_message *m, size_t size, char **s); +int sd_bus_message_append_string_iovec(sd_bus_message *m, const struct iovec *iov, unsigned n); +int sd_bus_message_append_string_memfd(sd_bus_message *m, int memfd, uint64_t offset, uint64_t size); +int sd_bus_message_append_strv(sd_bus_message *m, char **l); +int sd_bus_message_open_container(sd_bus_message *m, char type, const char *contents); +int sd_bus_message_close_container(sd_bus_message *m); +int sd_bus_message_copy(sd_bus_message *m, sd_bus_message *source, int all); + +int sd_bus_message_read(sd_bus_message *m, const char *types, ...); +int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p); +int sd_bus_message_read_array(sd_bus_message *m, char type, const void **ptr, size_t *size); +int sd_bus_message_read_strv(sd_bus_message *m, char ***l); /* free the result! */ +int sd_bus_message_skip(sd_bus_message *m, const char *types); +int sd_bus_message_enter_container(sd_bus_message *m, char type, const char *contents); +int sd_bus_message_exit_container(sd_bus_message *m); +int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char **contents); +int sd_bus_message_verify_type(sd_bus_message *m, char type, const char *contents); +int sd_bus_message_at_end(sd_bus_message *m, int complete); +int sd_bus_message_rewind(sd_bus_message *m, int complete); + +/* Bus management */ + +int sd_bus_get_unique_name(sd_bus *bus, const char **unique); +int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags); +int sd_bus_release_name(sd_bus *bus, const char *name); +int sd_bus_list_names(sd_bus *bus, char ***acquired, char ***activatable); /* free the results */ +int sd_bus_get_name_creds(sd_bus *bus, const char *name, uint64_t mask, sd_bus_creds **creds); /* unref the result! */ +int sd_bus_get_name_machine_id(sd_bus *bus, const char *name, sd_id128_t *machine); + +/* Convenience calls */ + +int sd_bus_call_method(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, sd_bus_message **reply, const char *types, ...); +int sd_bus_call_method_async(sd_bus *bus, sd_bus_slot **slot, const char *destination, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata, const char *types, ...); +int sd_bus_get_property(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, sd_bus_message **reply, const char *type); +int sd_bus_get_property_trivial(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char type, void *ret_ptr); +int sd_bus_get_property_string(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char **ret); /* free the result! */ +int sd_bus_get_property_strv(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char ***ret); /* free the result! */ +int sd_bus_set_property(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, const char *type, ...); + +int sd_bus_reply_method_return(sd_bus_message *call, const char *types, ...); +int sd_bus_reply_method_error(sd_bus_message *call, const sd_bus_error *e); +int sd_bus_reply_method_errorf(sd_bus_message *call, const char *name, const char *format, ...) _sd_printf_(3, 4); +int sd_bus_reply_method_errno(sd_bus_message *call, int error, const sd_bus_error *e); +int sd_bus_reply_method_errnof(sd_bus_message *call, int error, const char *format, ...) _sd_printf_(3, 4); + +int sd_bus_emit_signal(sd_bus *bus, const char *path, const char *interface, const char *member, const char *types, ...); + +int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names); +int sd_bus_emit_properties_changed(sd_bus *bus, const char *path, const char *interface, const char *name, ...) _sd_sentinel_; + +int sd_bus_emit_object_added(sd_bus *bus, const char *path); +int sd_bus_emit_object_removed(sd_bus *bus, const char *path); +int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces); +int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interface, ...) _sd_sentinel_; +int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces); +int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interface, ...) _sd_sentinel_; + +int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_bus_creds **creds); +int sd_bus_query_sender_privilege(sd_bus_message *call, int capability); + +/* Credential handling */ + +int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t creds_mask); +sd_bus_creds *sd_bus_creds_ref(sd_bus_creds *c); +sd_bus_creds *sd_bus_creds_unref(sd_bus_creds *c); +uint64_t sd_bus_creds_get_mask(const sd_bus_creds *c); +uint64_t sd_bus_creds_get_augmented_mask(const sd_bus_creds *c); + +int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid); +int sd_bus_creds_get_ppid(sd_bus_creds *c, pid_t *ppid); +int sd_bus_creds_get_tid(sd_bus_creds *c, pid_t *tid); +int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid); +int sd_bus_creds_get_euid(sd_bus_creds *c, uid_t *euid); +int sd_bus_creds_get_suid(sd_bus_creds *c, uid_t *suid); +int sd_bus_creds_get_fsuid(sd_bus_creds *c, uid_t *fsuid); +int sd_bus_creds_get_gid(sd_bus_creds *c, gid_t *gid); +int sd_bus_creds_get_egid(sd_bus_creds *c, gid_t *egid); +int sd_bus_creds_get_sgid(sd_bus_creds *c, gid_t *sgid); +int sd_bus_creds_get_fsgid(sd_bus_creds *c, gid_t *fsgid); +int sd_bus_creds_get_supplementary_gids(sd_bus_creds *c, const gid_t **gids); +int sd_bus_creds_get_comm(sd_bus_creds *c, const char **comm); +int sd_bus_creds_get_tid_comm(sd_bus_creds *c, const char **comm); +int sd_bus_creds_get_exe(sd_bus_creds *c, const char **exe); +int sd_bus_creds_get_cmdline(sd_bus_creds *c, char ***cmdline); +int sd_bus_creds_get_cgroup(sd_bus_creds *c, const char **cgroup); +int sd_bus_creds_get_unit(sd_bus_creds *c, const char **unit); +int sd_bus_creds_get_slice(sd_bus_creds *c, const char **slice); +int sd_bus_creds_get_user_unit(sd_bus_creds *c, const char **unit); +int sd_bus_creds_get_user_slice(sd_bus_creds *c, const char **slice); +int sd_bus_creds_get_session(sd_bus_creds *c, const char **session); +int sd_bus_creds_get_owner_uid(sd_bus_creds *c, uid_t *uid); +int sd_bus_creds_has_effective_cap(sd_bus_creds *c, int capability); +int sd_bus_creds_has_permitted_cap(sd_bus_creds *c, int capability); +int sd_bus_creds_has_inheritable_cap(sd_bus_creds *c, int capability); +int sd_bus_creds_has_bounding_cap(sd_bus_creds *c, int capability); +int sd_bus_creds_get_selinux_context(sd_bus_creds *c, const char **context); +int sd_bus_creds_get_audit_session_id(sd_bus_creds *c, uint32_t *sessionid); +int sd_bus_creds_get_audit_login_uid(sd_bus_creds *c, uid_t *loginuid); +int sd_bus_creds_get_tty(sd_bus_creds *c, const char **tty); +int sd_bus_creds_get_unique_name(sd_bus_creds *c, const char **name); +int sd_bus_creds_get_well_known_names(sd_bus_creds *c, char ***names); +int sd_bus_creds_get_description(sd_bus_creds *c, const char **name); + +/* Error structures */ + +#define SD_BUS_ERROR_MAKE_CONST(name, message) ((const sd_bus_error) {(name), (message), 0}) +#define SD_BUS_ERROR_NULL SD_BUS_ERROR_MAKE_CONST(NULL, NULL) + +void sd_bus_error_free(sd_bus_error *e); +int sd_bus_error_set(sd_bus_error *e, const char *name, const char *message); +int sd_bus_error_setf(sd_bus_error *e, const char *name, const char *format, ...) _sd_printf_(3, 4); +int sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message); +int sd_bus_error_set_errno(sd_bus_error *e, int error); +int sd_bus_error_set_errnof(sd_bus_error *e, int error, const char *format, ...) _sd_printf_(3, 4); +int sd_bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_list ap) _sd_printf_(3,0); +int sd_bus_error_get_errno(const sd_bus_error *e); +int sd_bus_error_copy(sd_bus_error *dest, const sd_bus_error *e); +int sd_bus_error_is_set(const sd_bus_error *e); +int sd_bus_error_has_name(const sd_bus_error *e, const char *name); + +#define SD_BUS_ERROR_MAP(_name, _code) \ + { \ + .name = _name, \ + .code = _code, \ + } +#define SD_BUS_ERROR_MAP_END \ + { \ + .name = NULL, \ + .code = - 'x', \ + } + +int sd_bus_error_add_map(const sd_bus_error_map *map); + +/* Auxiliary macros */ + +#define SD_BUS_MESSAGE_APPEND_ID128(x) 16, \ + (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], \ + (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], \ + (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], \ + (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15] + +#define SD_BUS_MESSAGE_READ_ID128(x) 16, \ + &(x).bytes[0], &(x).bytes[1], &(x).bytes[2], &(x).bytes[3], \ + &(x).bytes[4], &(x).bytes[5], &(x).bytes[6], &(x).bytes[7], \ + &(x).bytes[8], &(x).bytes[9], &(x).bytes[10], &(x).bytes[11], \ + &(x).bytes[12], &(x).bytes[13], &(x).bytes[14], &(x).bytes[15] + +/* Label escaping */ + +int sd_bus_path_encode(const char *prefix, const char *external_id, char **ret_path); +int sd_bus_path_encode_many(char **out, const char *path_template, ...); +int sd_bus_path_decode(const char *path, const char *prefix, char **ret_external_id); +int sd_bus_path_decode_many(const char *path, const char *path_template, ...); + +/* Tracking peers */ + +int sd_bus_track_new(sd_bus *bus, sd_bus_track **track, sd_bus_track_handler_t handler, void *userdata); +sd_bus_track* sd_bus_track_ref(sd_bus_track *track); +sd_bus_track* sd_bus_track_unref(sd_bus_track *track); + +sd_bus* sd_bus_track_get_bus(sd_bus_track *track); +void *sd_bus_track_get_userdata(sd_bus_track *track); +void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata); + +int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m); +int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m); +int sd_bus_track_add_name(sd_bus_track *track, const char *name); +int sd_bus_track_remove_name(sd_bus_track *track, const char *name); + +unsigned sd_bus_track_count(sd_bus_track *track); +const char* sd_bus_track_contains(sd_bus_track *track, const char *names); +const char* sd_bus_track_first(sd_bus_track *track); +const char* sd_bus_track_next(sd_bus_track *track); + +/* Define helpers so that __attribute__((cleanup(sd_bus_unrefp))) and similar may be used. */ +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus, sd_bus_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus, sd_bus_flush_close_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_slot, sd_bus_slot_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_message, sd_bus_message_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_creds, sd_bus_creds_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_track, sd_bus_track_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-daemon.h b/src/libsystemd/include/systemd/sd-daemon.h new file mode 100644 index 0000000000..e6787b0a64 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-daemon.h @@ -0,0 +1,289 @@ +#ifndef foosddaemonhfoo +#define foosddaemonhfoo + +/*** + 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 . +***/ + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* + The following functionality is provided: + + - Support for logging with log levels on stderr + - File descriptor passing for socket-based activation + - Daemon startup and status notification + - Detection of systemd boots + + See sd-daemon(3) for more information. +*/ + +/* + Log levels for usage on stderr: + + fprintf(stderr, SD_NOTICE "Hello World!\n"); + + This is similar to printk() usage in the kernel. +*/ +#define SD_EMERG "<0>" /* system is unusable */ +#define SD_ALERT "<1>" /* action must be taken immediately */ +#define SD_CRIT "<2>" /* critical conditions */ +#define SD_ERR "<3>" /* error conditions */ +#define SD_WARNING "<4>" /* warning conditions */ +#define SD_NOTICE "<5>" /* normal but significant condition */ +#define SD_INFO "<6>" /* informational */ +#define SD_DEBUG "<7>" /* debug-level messages */ + +/* The first passed file descriptor is fd 3 */ +#define SD_LISTEN_FDS_START 3 + +/* + Returns how many file descriptors have been passed, or a negative + errno code on failure. Optionally, removes the $LISTEN_FDS and + $LISTEN_PID file descriptors from the environment (recommended, but + problematic in threaded environments). If r is the return value of + this function you'll find the file descriptors passed as fds + SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative + errno style error code on failure. This function call ensures that + the FD_CLOEXEC flag is set for the passed file descriptors, to make + sure they are not passed on to child processes. If FD_CLOEXEC shall + not be set, the caller needs to unset it after this call for all file + descriptors that are used. + + See sd_listen_fds(3) for more information. +*/ +int sd_listen_fds(int unset_environment); + +int sd_listen_fds_with_names(int unset_environment, char ***names); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a FIFO in the file system stored under the + specified path, 0 otherwise. If path is NULL a path name check will + not be done and the call only verifies if the file descriptor + refers to a FIFO. Returns a negative errno style error code on + failure. + + See sd_is_fifo(3) for more information. +*/ +int sd_is_fifo(int fd, const char *path); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a special character device on the file + system stored under the specified path, 0 otherwise. + If path is NULL a path name check will not be done and the call + only verifies if the file descriptor refers to a special character. + Returns a negative errno style error code on failure. + + See sd_is_special(3) for more information. +*/ +int sd_is_special(int fd, const char *path); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a socket of the specified family (AF_INET, + ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If + family is 0 a socket family check will not be done. If type is 0 a + socket type check will not be done and the call only verifies if + the file descriptor refers to a socket. If listening is > 0 it is + verified that the socket is in listening mode. (i.e. listen() has + been called) If listening is == 0 it is verified that the socket is + not in listening mode. If listening is < 0 no listening mode check + is done. Returns a negative errno style error code on failure. + + See sd_is_socket(3) for more information. +*/ +int sd_is_socket(int fd, int family, int type, int listening); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is an Internet socket, of the specified family + (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM, + SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version + check is not done. If type is 0 a socket type check will not be + done. If port is 0 a socket port check will not be done. The + listening flag is used the same way as in sd_is_socket(). Returns a + negative errno style error code on failure. + + See sd_is_socket_inet(3) for more information. +*/ +int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is an AF_UNIX socket of the specified type + (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0 + a socket type check will not be done. If path is NULL a socket path + check will not be done. For normal AF_UNIX sockets set length to + 0. For abstract namespace sockets set length to the length of the + socket name (including the initial 0 byte), and pass the full + socket path in path (including the initial 0 byte). The listening + flag is used the same way as in sd_is_socket(). Returns a negative + errno style error code on failure. + + See sd_is_socket_unix(3) for more information. +*/ +int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length); + +/* + Helper call for identifying a passed file descriptor. Returns 1 if + the file descriptor is a POSIX Message Queue of the specified name, + 0 otherwise. If path is NULL a message queue name check is not + done. Returns a negative errno style error code on failure. + + See sd_is_mq(3) for more information. +*/ +int sd_is_mq(int fd, const char *path); + +/* + Informs systemd about changed daemon state. This takes a number of + newline separated environment-style variable assignments in a + string. The following variables are known: + + READY=1 Tells systemd that daemon startup is finished (only + relevant for services of Type=notify). The passed + argument is a boolean "1" or "0". Since there is + little value in signaling non-readiness the only + value daemons should send is "READY=1". + + STATUS=... Passes a single-line status string back to systemd + that describes the daemon state. This is free-form + and can be used for various purposes: general state + feedback, fsck-like programs could pass completion + percentages and failing programs could pass a human + readable error message. Example: "STATUS=Completed + 66% of file system check..." + + ERRNO=... If a daemon fails, the errno-style error code, + formatted as string. Example: "ERRNO=2" for ENOENT. + + BUSERROR=... If a daemon fails, the D-Bus error-style error + code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut" + + MAINPID=... The main pid of a daemon, in case systemd did not + fork off the process itself. Example: "MAINPID=4711" + + WATCHDOG=1 Tells systemd to update the watchdog timestamp. + Services using this feature should do this in + regular intervals. A watchdog framework can use the + timestamps to detect failed services. Also see + sd_watchdog_enabled() below. + + FDSTORE=1 Store the file descriptors passed along with the + message in the per-service file descriptor store, + and pass them to the main process again on next + invocation. This variable is only supported with + sd_pid_notify_with_fds(). + + Daemons can choose to send additional variables. However, it is + recommended to prefix variable names not listed above with X_. + + Returns a negative errno-style error code on failure. Returns > 0 + if systemd could be notified, 0 if it couldn't possibly because + systemd is not running. + + Example: When a daemon finished starting up, it could issue this + call to notify systemd about it: + + sd_notify(0, "READY=1"); + + See sd_notifyf() for more complete examples. + + See sd_notify(3) for more information. +*/ +int sd_notify(int unset_environment, const char *state); + +/* + Similar to sd_notify() but takes a format string. + + Example 1: A daemon could send the following after initialization: + + sd_notifyf(0, "READY=1\n" + "STATUS=Processing requests...\n" + "MAINPID=%lu", + (unsigned long) getpid()); + + Example 2: A daemon could send the following shortly before + exiting, on failure: + + sd_notifyf(0, "STATUS=Failed to start up: %s\n" + "ERRNO=%i", + strerror(errno), + errno); + + See sd_notifyf(3) for more information. +*/ +int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_(2,3); + +/* + Similar to sd_notify(), but send the message on behalf of another + process, if the appropriate permissions are available. +*/ +int sd_pid_notify(pid_t pid, int unset_environment, const char *state); + +/* + Similar to sd_notifyf(), but send the message on behalf of another + process, if the appropriate permissions are available. +*/ +int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) _sd_printf_(3,4); + +/* + Similar to sd_pid_notify(), but also passes the specified fd array + to the service manager for storage. This is particularly useful for + FDSTORE=1 messages. +*/ +int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds); + +/* + Returns > 0 if the system was booted with systemd. Returns < 0 on + error. Returns 0 if the system was not booted with systemd. Note + that all of the functions above handle non-systemd boots just + fine. You should NOT protect them with a call to this function. Also + note that this function checks whether the system, not the user + session is controlled by systemd. However the functions above work + for both user and system services. + + See sd_booted(3) for more information. +*/ +int sd_booted(void); + +/* + Returns > 0 if the service manager expects watchdog keep-alive + events to be sent regularly via sd_notify(0, "WATCHDOG=1"). Returns + 0 if it does not expect this. If the usec argument is non-NULL + returns the watchdog timeout in µs after which the service manager + will act on a process that has not sent a watchdog keep alive + message. This function is useful to implement services that + recognize automatically if they are being run under supervision of + systemd with WatchdogSec= set. It is recommended for clients to + generate keep-alive pings via sd_notify(0, "WATCHDOG=1") every half + of the returned time. + + See sd_watchdog_enabled(3) for more information. +*/ +int sd_watchdog_enabled(int unset_environment, uint64_t *usec); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-device.h b/src/libsystemd/include/systemd/sd-device.h new file mode 100644 index 0000000000..5bfca6ecec --- /dev/null +++ b/src/libsystemd/include/systemd/sd-device.h @@ -0,0 +1,100 @@ +#ifndef foosddevicehfoo +#define foosddevicehfoo + +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2014-2015 Tom Gundersen + + 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 . +***/ + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_device sd_device; +typedef struct sd_device_enumerator sd_device_enumerator; + +/* device */ + +sd_device *sd_device_ref(sd_device *device); +sd_device *sd_device_unref(sd_device *device); + +int sd_device_new_from_syspath(sd_device **ret, const char *syspath); +int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum); +int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname); +int sd_device_new_from_device_id(sd_device **ret, const char *id); + +int sd_device_get_parent(sd_device *child, sd_device **ret); +int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret); + +int sd_device_get_syspath(sd_device *device, const char **ret); +int sd_device_get_subsystem(sd_device *device, const char **ret); +int sd_device_get_devtype(sd_device *device, const char **ret); +int sd_device_get_devnum(sd_device *device, dev_t *devnum); +int sd_device_get_ifindex(sd_device *device, int *ifindex); +int sd_device_get_driver(sd_device *device, const char **ret); +int sd_device_get_devpath(sd_device *device, const char **ret); +int sd_device_get_devname(sd_device *device, const char **ret); +int sd_device_get_sysname(sd_device *device, const char **ret); +int sd_device_get_sysnum(sd_device *device, const char **ret); + +int sd_device_get_is_initialized(sd_device *device, int *initialized); +int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *usec); + +const char *sd_device_get_tag_first(sd_device *device); +const char *sd_device_get_tag_next(sd_device *device); +const char *sd_device_get_devlink_first(sd_device *device); +const char *sd_device_get_devlink_next(sd_device *device); +const char *sd_device_get_property_first(sd_device *device, const char **value); +const char *sd_device_get_property_next(sd_device *device, const char **value); +const char *sd_device_get_sysattr_first(sd_device *device); +const char *sd_device_get_sysattr_next(sd_device *device); + +int sd_device_has_tag(sd_device *device, const char *tag); +int sd_device_get_property_value(sd_device *device, const char *key, const char **value); +int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value); + +int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, char *value); + +/* device enumerator */ + +int sd_device_enumerator_new(sd_device_enumerator **ret); +sd_device_enumerator *sd_device_enumerator_ref(sd_device_enumerator *enumerator); +sd_device_enumerator *sd_device_enumerator_unref(sd_device_enumerator *enumerator); + +sd_device *sd_device_enumerator_get_device_first(sd_device_enumerator *enumerator); +sd_device *sd_device_enumerator_get_device_next(sd_device_enumerator *enumerator); +sd_device *sd_device_enumerator_get_subsystem_first(sd_device_enumerator *enumerator); +sd_device *sd_device_enumerator_get_subsystem_next(sd_device_enumerator *enumerator); + +int sd_device_enumerator_add_match_subsystem(sd_device_enumerator *enumerator, const char *subsystem, int match); +int sd_device_enumerator_add_match_sysattr(sd_device_enumerator *enumerator, const char *sysattr, const char *value, int match); +int sd_device_enumerator_add_match_property(sd_device_enumerator *enumerator, const char *property, const char *value); +int sd_device_enumerator_add_match_sysname(sd_device_enumerator *enumerator, const char *sysname); +int sd_device_enumerator_add_match_tag(sd_device_enumerator *enumerator, const char *tag); +int sd_device_enumerator_add_match_parent(sd_device_enumerator *enumerator, sd_device *parent); +int sd_device_enumerator_allow_uninitialized(sd_device_enumerator *enumerator); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device, sd_device_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device_enumerator, sd_device_enumerator_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-dhcp-client.h b/src/libsystemd/include/systemd/sd-dhcp-client.h new file mode 100644 index 0000000000..897a6b0223 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-dhcp-client.h @@ -0,0 +1,124 @@ +#ifndef foosddhcpclienthfoo +#define foosddhcpclienthfoo + +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (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 . +***/ + +#include +#include +#include +#include + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_DHCP_CLIENT_EVENT_STOP = 0, + SD_DHCP_CLIENT_EVENT_IP_ACQUIRE = 1, + SD_DHCP_CLIENT_EVENT_IP_CHANGE = 2, + SD_DHCP_CLIENT_EVENT_EXPIRED = 3, + SD_DHCP_CLIENT_EVENT_RENEW = 4, +}; + +enum { + SD_DHCP_OPTION_PAD = 0, + SD_DHCP_OPTION_SUBNET_MASK = 1, + SD_DHCP_OPTION_TIME_OFFSET = 2, + SD_DHCP_OPTION_ROUTER = 3, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER = 6, + SD_DHCP_OPTION_HOST_NAME = 12, + SD_DHCP_OPTION_BOOT_FILE_SIZE = 13, + SD_DHCP_OPTION_DOMAIN_NAME = 15, + SD_DHCP_OPTION_ROOT_PATH = 17, + SD_DHCP_OPTION_ENABLE_IP_FORWARDING = 19, + SD_DHCP_OPTION_ENABLE_IP_FORWARDING_NL = 20, + SD_DHCP_OPTION_POLICY_FILTER = 21, + SD_DHCP_OPTION_INTERFACE_MDR = 22, + SD_DHCP_OPTION_INTERFACE_TTL = 23, + SD_DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24, + SD_DHCP_OPTION_INTERFACE_MTU = 26, + SD_DHCP_OPTION_BROADCAST = 28, + SD_DHCP_OPTION_STATIC_ROUTE = 33, + SD_DHCP_OPTION_NTP_SERVER = 42, + SD_DHCP_OPTION_VENDOR_SPECIFIC = 43, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, + SD_DHCP_OPTION_OVERLOAD = 52, + SD_DHCP_OPTION_MESSAGE_TYPE = 53, + SD_DHCP_OPTION_SERVER_IDENTIFIER = 54, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, + SD_DHCP_OPTION_ERROR_MESSAGE = 56, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, + SD_DHCP_OPTION_RENEWAL_T1_TIME = 58, + SD_DHCP_OPTION_REBINDING_T2_TIME = 59, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, + SD_DHCP_OPTION_CLIENT_IDENTIFIER = 61, + SD_DHCP_OPTION_FQDN = 81, + SD_DHCP_OPTION_NEW_POSIX_TIMEZONE = 100, + SD_DHCP_OPTION_NEW_TZDB_TIMEZONE = 101, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, + SD_DHCP_OPTION_PRIVATE_BASE = 224, + SD_DHCP_OPTION_PRIVATE_LAST = 254, + SD_DHCP_OPTION_END = 255, +}; + +typedef struct sd_dhcp_client sd_dhcp_client; + +typedef void (*sd_dhcp_client_cb_t)(sd_dhcp_client *client, int event, + void *userdata); +int sd_dhcp_client_set_callback(sd_dhcp_client *client, sd_dhcp_client_cb_t cb, + void *userdata); + +int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option); +int sd_dhcp_client_set_request_address(sd_dhcp_client *client, + const struct in_addr *last_address); +int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast); +int sd_dhcp_client_set_index(sd_dhcp_client *client, int interface_index); +int sd_dhcp_client_set_mac(sd_dhcp_client *client, const uint8_t *addr, + size_t addr_len, uint16_t arp_type); +int sd_dhcp_client_set_client_id(sd_dhcp_client *client, uint8_t type, + const uint8_t *data, size_t data_len); +int sd_dhcp_client_get_client_id(sd_dhcp_client *client, uint8_t *type, + const uint8_t **data, size_t *data_len); +int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu); +int sd_dhcp_client_set_hostname(sd_dhcp_client *client, const char *hostname); +int sd_dhcp_client_set_vendor_class_identifier(sd_dhcp_client *client, const char *vci); +int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret); + +int sd_dhcp_client_stop(sd_dhcp_client *client); +int sd_dhcp_client_start(sd_dhcp_client *client); + +sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client); +sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client); + +int sd_dhcp_client_new(sd_dhcp_client **ret); + +int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int priority); +int sd_dhcp_client_detach_event(sd_dhcp_client *client); +sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_client, sd_dhcp_client_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-dhcp-lease.h b/src/libsystemd/include/systemd/sd-dhcp-lease.h new file mode 100644 index 0000000000..2f565ca825 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-dhcp-lease.h @@ -0,0 +1,67 @@ +#ifndef foosddhcpleasehfoo +#define foosddhcpleasehfoo + +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + Copyright (C) 2014 Tom Gundersen + + 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 . +***/ + +#include +#include +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp_lease sd_dhcp_lease; +typedef struct sd_dhcp_route sd_dhcp_route; + +sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease); +sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease); + +int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime); +int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1); +int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2); +int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu); +int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname); +int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname); +int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path); +int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes); +int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len); +int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len); +int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **timezone); + +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination); +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length); +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-dhcp-server.h b/src/libsystemd/include/systemd/sd-dhcp-server.h new file mode 100644 index 0000000000..ede8f18533 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-dhcp-server.h @@ -0,0 +1,64 @@ +#ifndef foosddhcpserverhfoo +#define foosddhcpserverhfoo + +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + Copyright (C) 2014 Tom Gundersen + + 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 . +***/ + +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp_server sd_dhcp_server; + +int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex); + +sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server); +sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server); + +int sd_dhcp_server_attach_event(sd_dhcp_server *client, sd_event *event, int priority); +int sd_dhcp_server_detach_event(sd_dhcp_server *client); +sd_event *sd_dhcp_server_get_event(sd_dhcp_server *client); + +int sd_dhcp_server_is_running(sd_dhcp_server *server); + +int sd_dhcp_server_start(sd_dhcp_server *server); +int sd_dhcp_server_stop(sd_dhcp_server *server); + +int sd_dhcp_server_configure_pool(sd_dhcp_server *server, struct in_addr *address, unsigned char prefixlen, uint32_t offset, uint32_t size); + +int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *timezone); +int sd_dhcp_server_set_dns(sd_dhcp_server *server, const struct in_addr ntp[], unsigned n); +int sd_dhcp_server_set_ntp(sd_dhcp_server *server, const struct in_addr dns[], unsigned n); + +int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint32_t t); +int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint32_t t); + +int sd_dhcp_server_forcerenew(sd_dhcp_server *server); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_server, sd_dhcp_server_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-dhcp6-client.h b/src/libsystemd/include/systemd/sd-dhcp6-client.h new file mode 100644 index 0000000000..9c05336ab9 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-dhcp6-client.h @@ -0,0 +1,112 @@ +#ifndef foosddhcp6clienthfoo +#define foosddhcp6clienthfoo + +/*** + 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 . +***/ + +#include +#include +#include + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_DHCP6_CLIENT_EVENT_STOP = 0, + SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE = 10, + SD_DHCP6_CLIENT_EVENT_RETRANS_MAX = 11, + SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE = 12, + SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST = 13, +}; + +enum { + SD_DHCP6_OPTION_CLIENTID = 1, + SD_DHCP6_OPTION_SERVERID = 2, + SD_DHCP6_OPTION_IA_NA = 3, + SD_DHCP6_OPTION_IA_TA = 4, + SD_DHCP6_OPTION_IAADDR = 5, + SD_DHCP6_OPTION_ORO = 6, + SD_DHCP6_OPTION_PREFERENCE = 7, + SD_DHCP6_OPTION_ELAPSED_TIME = 8, + SD_DHCP6_OPTION_RELAY_MSG = 9, + /* option code 10 is unassigned */ + SD_DHCP6_OPTION_AUTH = 11, + SD_DHCP6_OPTION_UNICAST = 12, + SD_DHCP6_OPTION_STATUS_CODE = 13, + SD_DHCP6_OPTION_RAPID_COMMIT = 14, + SD_DHCP6_OPTION_USER_CLASS = 15, + SD_DHCP6_OPTION_VENDOR_CLASS = 16, + SD_DHCP6_OPTION_VENDOR_OPTS = 17, + SD_DHCP6_OPTION_INTERFACE_ID = 18, + SD_DHCP6_OPTION_RECONF_MSG = 19, + SD_DHCP6_OPTION_RECONF_ACCEPT = 20, + + SD_DHCP6_OPTION_DNS_SERVERS = 23, /* RFC 3646 */ + SD_DHCP6_OPTION_DOMAIN_LIST = 24, /* RFC 3646 */ + + SD_DHCP6_OPTION_SNTP_SERVERS = 31, /* RFC 4075, deprecated */ + + /* option code 35 is unassigned */ + + SD_DHCP6_OPTION_NTP_SERVER = 56, /* RFC 5908 */ + + /* option codes 89-142 are unassigned */ + /* option codes 144-65535 are unassigned */ +}; + +typedef struct sd_dhcp6_client sd_dhcp6_client; + +typedef void (*sd_dhcp6_client_cb_t)(sd_dhcp6_client *client, int event, + void *userdata); +int sd_dhcp6_client_set_callback(sd_dhcp6_client *client, + sd_dhcp6_client_cb_t cb, void *userdata); + +int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index); +int sd_dhcp6_client_set_local_address(sd_dhcp6_client *client, const struct in6_addr *local_address); +int sd_dhcp6_client_set_mac(sd_dhcp6_client *client, const uint8_t *addr, + size_t addr_len, uint16_t arp_type); +int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, uint16_t type, uint8_t *duid, + size_t duid_len); +int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled); +int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enabled); +int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, + uint16_t option); + +int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret); + +int sd_dhcp6_client_stop(sd_dhcp6_client *client); +int sd_dhcp6_client_start(sd_dhcp6_client *client); +int sd_dhcp6_client_is_running(sd_dhcp6_client *client); +int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, + int priority); +int sd_dhcp6_client_detach_event(sd_dhcp6_client *client); +sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client); +sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client); +sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client); +int sd_dhcp6_client_new(sd_dhcp6_client **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_client, sd_dhcp6_client_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-dhcp6-lease.h b/src/libsystemd/include/systemd/sd-dhcp6-lease.h new file mode 100644 index 0000000000..184fbb8e0d --- /dev/null +++ b/src/libsystemd/include/systemd/sd-dhcp6-lease.h @@ -0,0 +1,52 @@ +#ifndef foosddhcp6leasehfoo +#define foosddhcp6leasehfoo + +/*** + This file is part of systemd. + + Copyright (C) 2014 Tom Gundersen + Copyright (C) 2014-2015 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 . +***/ + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp6_lease sd_dhcp6_lease; + +void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease); +int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, + struct in6_addr *addr, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid); + +int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, struct in6_addr **addrs); +int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains); +int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, + struct in6_addr **addrs); +int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn); + +sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease); +sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_lease, sd_dhcp6_lease_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-event.h b/src/libsystemd/include/systemd/sd-event.h new file mode 100644 index 0000000000..1ea97e47f8 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-event.h @@ -0,0 +1,138 @@ +#ifndef foosdeventhfoo +#define foosdeventhfoo + +/*** + 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 . +***/ + +#include +#include +#include +#include +#include + +#include "_sd-common.h" + +/* + Why is this better than pure epoll? + + - Supports event source prioritization + - Scales better with a large number of time events because it does not require one timerfd each + - Automatically tries to coalesce timer events system-wide + - Handles signals and child PIDs +*/ + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_event sd_event; +typedef struct sd_event_source sd_event_source; + +enum { + SD_EVENT_OFF = 0, + SD_EVENT_ON = 1, + SD_EVENT_ONESHOT = -1 +}; + +enum { + SD_EVENT_INITIAL, + SD_EVENT_ARMED, + SD_EVENT_PENDING, + SD_EVENT_RUNNING, + SD_EVENT_EXITING, + SD_EVENT_FINISHED, + SD_EVENT_PREPARING, +}; + +enum { + /* And everything in-between and outside is good too */ + SD_EVENT_PRIORITY_IMPORTANT = -100, + SD_EVENT_PRIORITY_NORMAL = 0, + SD_EVENT_PRIORITY_IDLE = 100 +}; + +typedef int (*sd_event_handler_t)(sd_event_source *s, void *userdata); +typedef int (*sd_event_io_handler_t)(sd_event_source *s, int fd, uint32_t revents, void *userdata); +typedef int (*sd_event_time_handler_t)(sd_event_source *s, uint64_t usec, void *userdata); +typedef int (*sd_event_signal_handler_t)(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata); +typedef int (*sd_event_child_handler_t)(sd_event_source *s, const siginfo_t *si, void *userdata); + +int sd_event_default(sd_event **e); + +int sd_event_new(sd_event **e); +sd_event* sd_event_ref(sd_event *e); +sd_event* sd_event_unref(sd_event *e); + +int sd_event_add_io(sd_event *e, sd_event_source **s, int fd, uint32_t events, sd_event_io_handler_t callback, void *userdata); +int sd_event_add_time(sd_event *e, sd_event_source **s, clockid_t clock, uint64_t usec, uint64_t accuracy, sd_event_time_handler_t callback, void *userdata); +int sd_event_add_signal(sd_event *e, sd_event_source **s, int sig, sd_event_signal_handler_t callback, void *userdata); +int sd_event_add_child(sd_event *e, sd_event_source **s, pid_t pid, int options, sd_event_child_handler_t callback, void *userdata); +int sd_event_add_defer(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); +int sd_event_add_post(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); +int sd_event_add_exit(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); + +int sd_event_prepare(sd_event *e); +int sd_event_wait(sd_event *e, uint64_t usec); +int sd_event_dispatch(sd_event *e); +int sd_event_run(sd_event *e, uint64_t usec); +int sd_event_loop(sd_event *e); +int sd_event_exit(sd_event *e, int code); + +int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec); + +int sd_event_get_fd(sd_event *e); +int sd_event_get_state(sd_event *e); +int sd_event_get_tid(sd_event *e, pid_t *tid); +int sd_event_get_exit_code(sd_event *e, int *code); +int sd_event_set_watchdog(sd_event *e, int b); +int sd_event_get_watchdog(sd_event *e); + +sd_event_source* sd_event_source_ref(sd_event_source *s); +sd_event_source* sd_event_source_unref(sd_event_source *s); + +sd_event *sd_event_source_get_event(sd_event_source *s); +void* sd_event_source_get_userdata(sd_event_source *s); +void* sd_event_source_set_userdata(sd_event_source *s, void *userdata); + +int sd_event_source_set_description(sd_event_source *s, const char *description); +int sd_event_source_get_description(sd_event_source *s, const char **description); +int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback); +int sd_event_source_get_pending(sd_event_source *s); +int sd_event_source_get_priority(sd_event_source *s, int64_t *priority); +int sd_event_source_set_priority(sd_event_source *s, int64_t priority); +int sd_event_source_get_enabled(sd_event_source *s, int *enabled); +int sd_event_source_set_enabled(sd_event_source *s, int enabled); +int sd_event_source_get_io_fd(sd_event_source *s); +int sd_event_source_set_io_fd(sd_event_source *s, int fd); +int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events); +int sd_event_source_set_io_events(sd_event_source *s, uint32_t events); +int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents); +int sd_event_source_get_time(sd_event_source *s, uint64_t *usec); +int sd_event_source_set_time(sd_event_source *s, uint64_t usec); +int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec); +int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec); +int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock); +int sd_event_source_get_signal(sd_event_source *s); +int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid); + +/* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */ +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-hwdb.h b/src/libsystemd/include/systemd/sd-hwdb.h new file mode 100644 index 0000000000..7105920492 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-hwdb.h @@ -0,0 +1,49 @@ +#ifndef foosdhwdbhfoo +#define foosdhwdbhfoo + +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_hwdb sd_hwdb; + +sd_hwdb *sd_hwdb_ref(sd_hwdb *hwdb); +sd_hwdb *sd_hwdb_unref(sd_hwdb *hwdb); + +int sd_hwdb_new(sd_hwdb **ret); + +int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **value); + +int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias); +int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value); + +/* the inverse condition avoids ambiguity of dangling 'else' after the macro */ +#define SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value) \ + if (sd_hwdb_seek(hwdb, modalias) < 0) { } \ + else while (sd_hwdb_enumerate(hwdb, &(key), &(value)) > 0) + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_hwdb, sd_hwdb_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-id128.h b/src/libsystemd/include/systemd/sd-id128.h new file mode 100644 index 0000000000..a3bf5897b8 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-id128.h @@ -0,0 +1,115 @@ +#ifndef foosdid128hfoo +#define foosdid128hfoo + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* 128-bit ID APIs. See sd-id128(3) for more information. */ + +typedef union sd_id128 sd_id128_t; + +union sd_id128 { + uint8_t bytes[16]; + uint64_t qwords[2]; +}; + +#define SD_ID128_STRING_MAX 33 + +char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]); + +int sd_id128_from_string(const char *s, sd_id128_t *ret); + +int sd_id128_randomize(sd_id128_t *ret); + +int sd_id128_get_machine(sd_id128_t *ret); + +int sd_id128_get_boot(sd_id128_t *ret); + +#define SD_ID128_MAKE(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ + ((const sd_id128_t) { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ + 0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }}) + +#define SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ + { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ + 0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }} + +/* Note that SD_ID128_FORMAT_VAL will evaluate the passed argument 16 + * times. It is hence not a good idea to call this macro with an + * expensive function as parameter or an expression with side + * effects */ + +#define SD_ID128_FORMAT_STR "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" +#define SD_ID128_FORMAT_VAL(x) (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15] + +#define SD_ID128_CONST_STR(x) \ + ((const char[SD_ID128_STRING_MAX]) { \ + ((x).bytes[0] >> 4) >= 10 ? 'a' + ((x).bytes[0] >> 4) - 10 : '0' + ((x).bytes[0] >> 4), \ + ((x).bytes[0] & 15) >= 10 ? 'a' + ((x).bytes[0] & 15) - 10 : '0' + ((x).bytes[0] & 15), \ + ((x).bytes[1] >> 4) >= 10 ? 'a' + ((x).bytes[1] >> 4) - 10 : '0' + ((x).bytes[1] >> 4), \ + ((x).bytes[1] & 15) >= 10 ? 'a' + ((x).bytes[1] & 15) - 10 : '0' + ((x).bytes[1] & 15), \ + ((x).bytes[2] >> 4) >= 10 ? 'a' + ((x).bytes[2] >> 4) - 10 : '0' + ((x).bytes[2] >> 4), \ + ((x).bytes[2] & 15) >= 10 ? 'a' + ((x).bytes[2] & 15) - 10 : '0' + ((x).bytes[2] & 15), \ + ((x).bytes[3] >> 4) >= 10 ? 'a' + ((x).bytes[3] >> 4) - 10 : '0' + ((x).bytes[3] >> 4), \ + ((x).bytes[3] & 15) >= 10 ? 'a' + ((x).bytes[3] & 15) - 10 : '0' + ((x).bytes[3] & 15), \ + ((x).bytes[4] >> 4) >= 10 ? 'a' + ((x).bytes[4] >> 4) - 10 : '0' + ((x).bytes[4] >> 4), \ + ((x).bytes[4] & 15) >= 10 ? 'a' + ((x).bytes[4] & 15) - 10 : '0' + ((x).bytes[4] & 15), \ + ((x).bytes[5] >> 4) >= 10 ? 'a' + ((x).bytes[5] >> 4) - 10 : '0' + ((x).bytes[5] >> 4), \ + ((x).bytes[5] & 15) >= 10 ? 'a' + ((x).bytes[5] & 15) - 10 : '0' + ((x).bytes[5] & 15), \ + ((x).bytes[6] >> 4) >= 10 ? 'a' + ((x).bytes[6] >> 4) - 10 : '0' + ((x).bytes[6] >> 4), \ + ((x).bytes[6] & 15) >= 10 ? 'a' + ((x).bytes[6] & 15) - 10 : '0' + ((x).bytes[6] & 15), \ + ((x).bytes[7] >> 4) >= 10 ? 'a' + ((x).bytes[7] >> 4) - 10 : '0' + ((x).bytes[7] >> 4), \ + ((x).bytes[7] & 15) >= 10 ? 'a' + ((x).bytes[7] & 15) - 10 : '0' + ((x).bytes[7] & 15), \ + ((x).bytes[8] >> 4) >= 10 ? 'a' + ((x).bytes[8] >> 4) - 10 : '0' + ((x).bytes[8] >> 4), \ + ((x).bytes[8] & 15) >= 10 ? 'a' + ((x).bytes[8] & 15) - 10 : '0' + ((x).bytes[8] & 15), \ + ((x).bytes[9] >> 4) >= 10 ? 'a' + ((x).bytes[9] >> 4) - 10 : '0' + ((x).bytes[9] >> 4), \ + ((x).bytes[9] & 15) >= 10 ? 'a' + ((x).bytes[9] & 15) - 10 : '0' + ((x).bytes[9] & 15), \ + ((x).bytes[10] >> 4) >= 10 ? 'a' + ((x).bytes[10] >> 4) - 10 : '0' + ((x).bytes[10] >> 4), \ + ((x).bytes[10] & 15) >= 10 ? 'a' + ((x).bytes[10] & 15) - 10 : '0' + ((x).bytes[10] & 15), \ + ((x).bytes[11] >> 4) >= 10 ? 'a' + ((x).bytes[11] >> 4) - 10 : '0' + ((x).bytes[11] >> 4), \ + ((x).bytes[11] & 15) >= 10 ? 'a' + ((x).bytes[11] & 15) - 10 : '0' + ((x).bytes[11] & 15), \ + ((x).bytes[12] >> 4) >= 10 ? 'a' + ((x).bytes[12] >> 4) - 10 : '0' + ((x).bytes[12] >> 4), \ + ((x).bytes[12] & 15) >= 10 ? 'a' + ((x).bytes[12] & 15) - 10 : '0' + ((x).bytes[12] & 15), \ + ((x).bytes[13] >> 4) >= 10 ? 'a' + ((x).bytes[13] >> 4) - 10 : '0' + ((x).bytes[13] >> 4), \ + ((x).bytes[13] & 15) >= 10 ? 'a' + ((x).bytes[13] & 15) - 10 : '0' + ((x).bytes[13] & 15), \ + ((x).bytes[14] >> 4) >= 10 ? 'a' + ((x).bytes[14] >> 4) - 10 : '0' + ((x).bytes[14] >> 4), \ + ((x).bytes[14] & 15) >= 10 ? 'a' + ((x).bytes[14] & 15) - 10 : '0' + ((x).bytes[14] & 15), \ + ((x).bytes[15] >> 4) >= 10 ? 'a' + ((x).bytes[15] >> 4) - 10 : '0' + ((x).bytes[15] >> 4), \ + ((x).bytes[15] & 15) >= 10 ? 'a' + ((x).bytes[15] & 15) - 10 : '0' + ((x).bytes[15] & 15), \ + 0 }) + +_sd_pure_ static inline int sd_id128_equal(sd_id128_t a, sd_id128_t b) { + return memcmp(&a, &b, 16) == 0; +} + +_sd_pure_ static inline int sd_id128_is_null(sd_id128_t a) { + return a.qwords[0] == 0 && a.qwords[1] == 0; +} + +#define SD_ID128_NULL ((const sd_id128_t) { .qwords = { 0, 0 }}) + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-ipv4acd.h b/src/libsystemd/include/systemd/sd-ipv4acd.h new file mode 100644 index 0000000000..0fb52b33f9 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-ipv4acd.h @@ -0,0 +1,60 @@ +#ifndef foosdipv4acdfoo +#define foosdipv4acdfoo + +/*** + This file is part of systemd. + + Copyright (C) 2014 Axis Communications AB. All rights reserved. + Copyright (C) 2015 Tom Gundersen + + 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 . +***/ + +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_IPV4ACD_EVENT_STOP = 0, + SD_IPV4ACD_EVENT_BIND = 1, + SD_IPV4ACD_EVENT_CONFLICT = 2, +}; + +typedef struct sd_ipv4acd sd_ipv4acd; +typedef void (*sd_ipv4acd_cb_t)(sd_ipv4acd *ll, int event, void *userdata); + +int sd_ipv4acd_detach_event(sd_ipv4acd *ll); +int sd_ipv4acd_attach_event(sd_ipv4acd *ll, sd_event *event, int priority); +int sd_ipv4acd_get_address(sd_ipv4acd *ll, struct in_addr *address); +int sd_ipv4acd_set_callback(sd_ipv4acd *ll, sd_ipv4acd_cb_t cb, void *userdata); +int sd_ipv4acd_set_mac(sd_ipv4acd *ll, const struct ether_addr *addr); +int sd_ipv4acd_set_index(sd_ipv4acd *ll, int interface_index); +int sd_ipv4acd_set_address(sd_ipv4acd *ll, const struct in_addr *address); +int sd_ipv4acd_is_running(sd_ipv4acd *ll); +int sd_ipv4acd_start(sd_ipv4acd *ll); +int sd_ipv4acd_stop(sd_ipv4acd *ll); +sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *ll); +sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *ll); +int sd_ipv4acd_new(sd_ipv4acd **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4acd, sd_ipv4acd_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-ipv4ll.h b/src/libsystemd/include/systemd/sd-ipv4ll.h new file mode 100644 index 0000000000..2d1d51a473 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-ipv4ll.h @@ -0,0 +1,60 @@ +#ifndef foosdipv4llfoo +#define foosdipv4llfoo + +/*** + This file is part of systemd. + + Copyright (C) 2014 Axis Communications AB. 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 . +***/ + +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_IPV4LL_EVENT_STOP = 0, + SD_IPV4LL_EVENT_BIND = 1, + SD_IPV4LL_EVENT_CONFLICT = 2, +}; + +typedef struct sd_ipv4ll sd_ipv4ll; +typedef void (*sd_ipv4ll_cb_t)(sd_ipv4ll *ll, int event, void *userdata); + +int sd_ipv4ll_detach_event(sd_ipv4ll *ll); +int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int priority); +int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address); +int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_cb_t cb, void *userdata); +int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr); +int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index); +int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address); +int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, unsigned seed); +int sd_ipv4ll_is_running(sd_ipv4ll *ll); +int sd_ipv4ll_start(sd_ipv4ll *ll); +int sd_ipv4ll_stop(sd_ipv4ll *ll); +sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll); +sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll); +int sd_ipv4ll_new (sd_ipv4ll **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4ll, sd_ipv4ll_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-journal.h b/src/libsystemd/include/systemd/sd-journal.h new file mode 100644 index 0000000000..559a8fa929 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-journal.h @@ -0,0 +1,172 @@ +#ifndef foosdjournalhfoo +#define foosdjournalhfoo + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include +#include +#include + +#include + +#include "_sd-common.h" + +/* Journal APIs. See sd-journal(3) for more information. */ + +_SD_BEGIN_DECLARATIONS; + +/* Write to daemon */ +int sd_journal_print(int priority, const char *format, ...) _sd_printf_(2, 3); +int sd_journal_printv(int priority, const char *format, va_list ap) _sd_printf_(2, 0); +int sd_journal_send(const char *format, ...) _sd_printf_(1, 0) _sd_sentinel_; +int sd_journal_sendv(const struct iovec *iov, int n); +int sd_journal_perror(const char *message); + +/* Used by the macros below. You probably don't want to call this directly. */ +int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) _sd_printf_(5, 6); +int sd_journal_printv_with_location(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) _sd_printf_(5, 0); +int sd_journal_send_with_location(const char *file, const char *line, const char *func, const char *format, ...) _sd_printf_(4, 0) _sd_sentinel_; +int sd_journal_sendv_with_location(const char *file, const char *line, const char *func, const struct iovec *iov, int n); +int sd_journal_perror_with_location(const char *file, const char *line, const char *func, const char *message); + +/* implicitly add code location to messages sent, if this is enabled */ +#ifndef SD_JOURNAL_SUPPRESS_LOCATION + +#define sd_journal_print(priority, ...) sd_journal_print_with_location(priority, "CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, __VA_ARGS__) +#define sd_journal_printv(priority, format, ap) sd_journal_printv_with_location(priority, "CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, format, ap) +#define sd_journal_send(...) sd_journal_send_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, __VA_ARGS__) +#define sd_journal_sendv(iovec, n) sd_journal_sendv_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, iovec, n) +#define sd_journal_perror(message) sd_journal_perror_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, message) + +#endif + +int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix); + +/* Browse journal stream */ + +typedef struct sd_journal sd_journal; + +/* Open flags */ +enum { + SD_JOURNAL_LOCAL_ONLY = 1, + SD_JOURNAL_RUNTIME_ONLY = 2, + SD_JOURNAL_SYSTEM = 4, + SD_JOURNAL_CURRENT_USER = 8, + + SD_JOURNAL_SYSTEM_ONLY = SD_JOURNAL_SYSTEM, /* deprecated name */ +}; + +/* Wakeup event types */ +enum { + SD_JOURNAL_NOP, + SD_JOURNAL_APPEND, + SD_JOURNAL_INVALIDATE +}; + +int sd_journal_open(sd_journal **ret, int flags); +int sd_journal_open_directory(sd_journal **ret, const char *path, int flags); +int sd_journal_open_files(sd_journal **ret, const char **paths, int flags); +int sd_journal_open_container(sd_journal **ret, const char *machine, int flags); +void sd_journal_close(sd_journal *j); + +int sd_journal_previous(sd_journal *j); +int sd_journal_next(sd_journal *j); + +int sd_journal_previous_skip(sd_journal *j, uint64_t skip); +int sd_journal_next_skip(sd_journal *j, uint64_t skip); + +int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret); +int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id); + +int sd_journal_set_data_threshold(sd_journal *j, size_t sz); +int sd_journal_get_data_threshold(sd_journal *j, size_t *sz); + +int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *l); +int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *l); +void sd_journal_restart_data(sd_journal *j); + +int sd_journal_add_match(sd_journal *j, const void *data, size_t size); +int sd_journal_add_disjunction(sd_journal *j); +int sd_journal_add_conjunction(sd_journal *j); +void sd_journal_flush_matches(sd_journal *j); + +int sd_journal_seek_head(sd_journal *j); +int sd_journal_seek_tail(sd_journal *j); +int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec); +int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec); +int sd_journal_seek_cursor(sd_journal *j, const char *cursor); + +int sd_journal_get_cursor(sd_journal *j, char **cursor); +int sd_journal_test_cursor(sd_journal *j, const char *cursor); + +int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to); +int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, const sd_id128_t boot_id, uint64_t *from, uint64_t *to); + +int sd_journal_get_usage(sd_journal *j, uint64_t *bytes); + +int sd_journal_query_unique(sd_journal *j, const char *field); +int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l); +void sd_journal_restart_unique(sd_journal *j); + +int sd_journal_enumerate_fields(sd_journal *j, const char **field); +void sd_journal_restart_fields(sd_journal *j); + +int sd_journal_get_fd(sd_journal *j); +int sd_journal_get_events(sd_journal *j); +int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec); +int sd_journal_process(sd_journal *j); +int sd_journal_wait(sd_journal *j, uint64_t timeout_usec); +int sd_journal_reliable_fd(sd_journal *j); + +int sd_journal_get_catalog(sd_journal *j, char **text); +int sd_journal_get_catalog_for_message_id(sd_id128_t id, char **text); + +int sd_journal_has_runtime_files(sd_journal *j); +int sd_journal_has_persistent_files(sd_journal *j); + +/* The inverse condition avoids ambiguity of dangling 'else' after the macro */ +#define SD_JOURNAL_FOREACH(j) \ + if (sd_journal_seek_head(j) < 0) { } \ + else while (sd_journal_next(j) > 0) + +/* The inverse condition avoids ambiguity of dangling 'else' after the macro */ +#define SD_JOURNAL_FOREACH_BACKWARDS(j) \ + if (sd_journal_seek_tail(j) < 0) { } \ + else while (sd_journal_previous(j) > 0) + +/* Iterate through the data fields of the current journal entry */ +#define SD_JOURNAL_FOREACH_DATA(j, data, l) \ + for (sd_journal_restart_data(j); sd_journal_enumerate_data((j), &(data), &(l)) > 0; ) + +/* Iterate through the all known values of a specific field */ +#define SD_JOURNAL_FOREACH_UNIQUE(j, data, l) \ + for (sd_journal_restart_unique(j); sd_journal_enumerate_unique((j), &(data), &(l)) > 0; ) + +/* Iterate through all known field names */ +#define SD_JOURNAL_FOREACH_FIELD(j, field) \ + for (sd_journal_restart_fields(j); sd_journal_enumerate_fields((j), &(field)) > 0; ) + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_journal, sd_journal_close); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-lldp.h b/src/libsystemd/include/systemd/sd-lldp.h new file mode 100644 index 0000000000..45d8a36697 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-lldp.h @@ -0,0 +1,87 @@ +#ifndef foosdlldphfoo +#define foosdlldphfoo + +/*** + This file is part of systemd. + + Copyright (C) 2014 Tom Gundersen + Copyright (C) 2014 Susant Sahani + + 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 . +***/ + +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_LLDP_EVENT_UPDATE_INFO = 0, +}; + +enum { + SD_LLDP_DESTINATION_TYPE_NEAREST_BRIDGE, + SD_LLDP_DESTINATION_TYPE_NEAREST_NON_TPMR_BRIDGE, + SD_LLDP_DESTINATION_TYPE_NEAREST_CUSTOMER_BRIDGE, +}; + +typedef struct sd_lldp sd_lldp; +typedef struct sd_lldp_packet sd_lldp_packet; + +typedef void (*sd_lldp_cb_t)(sd_lldp *lldp, int event, void *userdata); + +int sd_lldp_new(int ifindex, const char *ifname, const struct ether_addr *mac, sd_lldp **ret); +sd_lldp* sd_lldp_unref(sd_lldp *lldp); + +int sd_lldp_start(sd_lldp *lldp); +int sd_lldp_stop(sd_lldp *lldp); + +int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int priority); +int sd_lldp_detach_event(sd_lldp *lldp); + +int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_cb_t cb, void *userdata); +int sd_lldp_save(sd_lldp *lldp, const char *file); + +int sd_lldp_packet_read_chassis_id(sd_lldp_packet *tlv, uint8_t *type, uint8_t **data, uint16_t *length); +int sd_lldp_packet_read_port_id(sd_lldp_packet *tlv, uint8_t *type, uint8_t **data, uint16_t *length); +int sd_lldp_packet_read_ttl(sd_lldp_packet *tlv, uint16_t *ttl); +int sd_lldp_packet_read_system_name(sd_lldp_packet *tlv, char **data, uint16_t *length); +int sd_lldp_packet_read_system_description(sd_lldp_packet *tlv, char **data, uint16_t *length); +int sd_lldp_packet_read_system_capability(sd_lldp_packet *tlv, uint16_t *data); +int sd_lldp_packet_read_port_description(sd_lldp_packet *tlv, char **data, uint16_t *length); + +/* IEEE 802.1 organizationally specific TLVs */ +int sd_lldp_packet_read_port_vlan_id(sd_lldp_packet *tlv, uint16_t *id); +int sd_lldp_packet_read_port_protocol_vlan_id(sd_lldp_packet *tlv, uint8_t *flags, uint16_t *id); +int sd_lldp_packet_read_vlan_name(sd_lldp_packet *tlv, uint16_t *vlan_id, char **name, uint16_t *length); +int sd_lldp_packet_read_management_vid(sd_lldp_packet *tlv, uint16_t *id); +int sd_lldp_packet_read_link_aggregation(sd_lldp_packet *tlv, uint8_t *status, uint32_t *id); + +sd_lldp_packet *sd_lldp_packet_ref(sd_lldp_packet *tlv); +sd_lldp_packet *sd_lldp_packet_unref(sd_lldp_packet *tlv); + +int sd_lldp_packet_get_destination_type(sd_lldp_packet *tlv, int *dest); + +int sd_lldp_get_packets(sd_lldp *lldp, sd_lldp_packet ***tlvs); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp, sd_lldp_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_packet, sd_lldp_packet_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-login.h b/src/libsystemd/include/systemd/sd-login.h new file mode 100644 index 0000000000..3c10ff032f --- /dev/null +++ b/src/libsystemd/include/systemd/sd-login.h @@ -0,0 +1,245 @@ +#ifndef foosdloginhfoo +#define foosdloginhfoo + +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include + +#include "_sd-common.h" + +/* + * A few points: + * + * Instead of returning an empty string array or empty uid array, we + * may return NULL. + * + * Free the data the library returns with libc free(). String arrays + * are NULL terminated, and you need to free the array itself, in + * addition to the strings contained. + * + * We return error codes as negative errno, kernel-style. On success, we + * return 0 or positive. + * + * These functions access data in /proc, /sys/fs/cgroup, and /run. All + * of these are virtual file systems; therefore, accesses are + * relatively cheap. + * + * See sd-login(3) for more information. + */ + +_SD_BEGIN_DECLARATIONS; + +/* Get session from PID. Note that 'shared' processes of a user are + * not attached to a session, but only attached to a user. This will + * return an error for system processes and 'shared' processes of a + * user. */ +int sd_pid_get_session(pid_t pid, char **session); + +/* Get UID of the owner of the session of the PID (or in case the + * process is a 'shared' user process, the UID of that user is + * returned). This will not return the UID of the process, but rather + * the UID of the owner of the cgroup that the process is in. This will + * return an error for system processes. */ +int sd_pid_get_owner_uid(pid_t pid, uid_t *uid); + +/* Get systemd non-slice unit (i.e. service) name from PID, for system + * services. This will return an error for non-service processes. */ +int sd_pid_get_unit(pid_t pid, char **unit); + +/* Get systemd non-slice unit (i.e. service) name from PID, for user + * services. This will return an error for non-user-service + * processes. */ +int sd_pid_get_user_unit(pid_t pid, char **unit); + +/* Get slice name from PID. */ +int sd_pid_get_slice(pid_t pid, char **slice); + +/* Get user slice name from PID. */ +int sd_pid_get_user_slice(pid_t pid, char **slice); + +/* Get machine name from PID, for processes assigned to a VM or + * container. This will return an error for non-machine processes. */ +int sd_pid_get_machine_name(pid_t pid, char **machine); + +/* Get the control group from a PID, relative to the root of the + * hierarchy. */ +int sd_pid_get_cgroup(pid_t pid, char **cgroup); + +/* Similar to sd_pid_get_session(), but retrieves data about the peer + * of a connected AF_UNIX socket */ +int sd_peer_get_session(int fd, char **session); + +/* Similar to sd_pid_get_owner_uid(), but retrieves data about the peer of + * a connected AF_UNIX socket */ +int sd_peer_get_owner_uid(int fd, uid_t *uid); + +/* Similar to sd_pid_get_unit(), but retrieves data about the peer of + * a connected AF_UNIX socket */ +int sd_peer_get_unit(int fd, char **unit); + +/* Similar to sd_pid_get_user_unit(), but retrieves data about the peer of + * a connected AF_UNIX socket */ +int sd_peer_get_user_unit(int fd, char **unit); + +/* Similar to sd_pid_get_slice(), but retrieves data about the peer of + * a connected AF_UNIX socket */ +int sd_peer_get_slice(int fd, char **slice); + +/* Similar to sd_pid_get_user_slice(), but retrieves data about the peer of + * a connected AF_UNIX socket */ +int sd_peer_get_user_slice(int fd, char **slice); + +/* Similar to sd_pid_get_machine_name(), but retrieves data about the + * peer of a a connected AF_UNIX socket */ +int sd_peer_get_machine_name(int fd, char **machine); + +/* Similar to sd_pid_get_cgroup(), but retrieves data about the peer + * of a connected AF_UNIX socket. */ +int sd_peer_get_cgroup(pid_t pid, char **cgroup); + +/* Get state from UID. Possible states: offline, lingering, online, active, closing */ +int sd_uid_get_state(uid_t uid, char **state); + +/* Return primary session of user, if there is any */ +int sd_uid_get_display(uid_t uid, char **session); + +/* Return 1 if UID has session on seat. If require_active is true, this will + * look for active sessions only. */ +int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat); + +/* Return sessions of user. If require_active is true, this will look for + * active sessions only. Returns the number of sessions. + * If sessions is NULL, this will just return the number of sessions. */ +int sd_uid_get_sessions(uid_t uid, int require_active, char ***sessions); + +/* Return seats of user is on. If require_active is true, this will look for + * active seats only. Returns the number of seats. + * If seats is NULL, this will just return the number of seats. */ +int sd_uid_get_seats(uid_t uid, int require_active, char ***seats); + +/* Return 1 if the session is active. */ +int sd_session_is_active(const char *session); + +/* Return 1 if the session is remote. */ +int sd_session_is_remote(const char *session); + +/* Get state from session. Possible states: online, active, closing. + * This function is a more generic version of sd_session_is_active(). */ +int sd_session_get_state(const char *session, char **state); + +/* Determine user ID of session */ +int sd_session_get_uid(const char *session, uid_t *uid); + +/* Determine seat of session */ +int sd_session_get_seat(const char *session, char **seat); + +/* Determine the (PAM) service name this session was registered by. */ +int sd_session_get_service(const char *session, char **service); + +/* Determine the type of this session, i.e. one of "tty", "x11", "wayland", "mir" or "unspecified". */ +int sd_session_get_type(const char *session, char **type); + +/* Determine the class of this session, i.e. one of "user", "greeter" or "lock-screen". */ +int sd_session_get_class(const char *session, char **clazz); + +/* Determine the desktop brand of this session, i.e. something like "GNOME", "KDE" or "systemd-console". */ +int sd_session_get_desktop(const char *session, char **desktop); + +/* Determine the X11 display of this session. */ +int sd_session_get_display(const char *session, char **display); + +/* Determine the remote host of this session. */ +int sd_session_get_remote_host(const char *session, char **remote_host); + +/* Determine the remote user of this session (if provided by PAM). */ +int sd_session_get_remote_user(const char *session, char **remote_user); + +/* Determine the TTY of this session. */ +int sd_session_get_tty(const char *session, char **display); + +/* Determine the VT number of this session. */ +int sd_session_get_vt(const char *session, unsigned *vtnr); + +/* Return active session and user of seat */ +int sd_seat_get_active(const char *seat, char **session, uid_t *uid); + +/* Return sessions and users on seat. Returns number of sessions. + * If sessions is NULL, this returns only the number of sessions. */ +int sd_seat_get_sessions(const char *seat, char ***sessions, uid_t **uid, unsigned *n_uids); + +/* Return whether the seat is multi-session capable */ +int sd_seat_can_multi_session(const char *seat); + +/* Return whether the seat is TTY capable, i.e. suitable for showing console UIs */ +int sd_seat_can_tty(const char *seat); + +/* Return whether the seat is graphics capable, i.e. suitable for showing graphical UIs */ +int sd_seat_can_graphical(const char *seat); + +/* Return the class of machine */ +int sd_machine_get_class(const char *machine, char **clazz); + +/* Return the list if host-side network interface indices of a machine */ +int sd_machine_get_ifindices(const char *machine, int **ifindices); + +/* Get all seats, store in *seats. Returns the number of seats. If + * seats is NULL, this only returns the number of seats. */ +int sd_get_seats(char ***seats); + +/* Get all sessions, store in *sessions. Returns the number of + * sessions. If sessions is NULL, this only returns the number of sessions. */ +int sd_get_sessions(char ***sessions); + +/* Get all logged in users, store in *users. Returns the number of + * users. If users is NULL, this only returns the number of users. */ +int sd_get_uids(uid_t **users); + +/* Get all running virtual machines/containers */ +int sd_get_machine_names(char ***machines); + +/* Monitor object */ +typedef struct sd_login_monitor sd_login_monitor; + +/* Create a new monitor. Category must be NULL, "seat", "session", + * "uid", or "machine" to get monitor events for the specific category + * (or all). */ +int sd_login_monitor_new(const char *category, sd_login_monitor** ret); + +/* Destroys the passed monitor. Returns NULL. */ +sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m); + +/* Flushes the monitor */ +int sd_login_monitor_flush(sd_login_monitor *m); + +/* Get FD from monitor */ +int sd_login_monitor_get_fd(sd_login_monitor *m); + +/* Get poll() mask to monitor */ +int sd_login_monitor_get_events(sd_login_monitor *m); + +/* Get timeout for poll(), as usec value relative to CLOCK_MONOTONIC's epoch */ +int sd_login_monitor_get_timeout(sd_login_monitor *m, uint64_t *timeout_usec); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_login_monitor, sd_login_monitor_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-messages.h b/src/libsystemd/include/systemd/sd-messages.h new file mode 100644 index 0000000000..1aeac17851 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-messages.h @@ -0,0 +1,93 @@ +#ifndef foosdmessageshfoo +#define foosdmessageshfoo + +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* Hey! If you add a new message here, you *must* also update the + * message catalog with an appropriate explanation */ + +/* And if you add a new ID here, make sure to generate a random one + * with journalctl --new-id128. Do not use any other IDs, and do not + * count them up manually. */ + +#define SD_MESSAGE_JOURNAL_START SD_ID128_MAKE(f7,73,79,a8,49,0b,40,8b,be,5f,69,40,50,5a,77,7b) +#define SD_MESSAGE_JOURNAL_STOP SD_ID128_MAKE(d9,3f,b3,c9,c2,4d,45,1a,97,ce,a6,15,ce,59,c0,0b) +#define SD_MESSAGE_JOURNAL_DROPPED SD_ID128_MAKE(a5,96,d6,fe,7b,fa,49,94,82,8e,72,30,9e,95,d6,1e) +#define SD_MESSAGE_JOURNAL_MISSED SD_ID128_MAKE(e9,bf,28,e6,e8,34,48,1b,b6,f4,8f,54,8a,d1,36,06) +#define SD_MESSAGE_JOURNAL_USAGE SD_ID128_MAKE(ec,38,7f,57,7b,84,4b,8f,a9,48,f3,3c,ad,9a,75,e6) + +#define SD_MESSAGE_COREDUMP SD_ID128_MAKE(fc,2e,22,bc,6e,e6,47,b6,b9,07,29,ab,34,a2,50,b1) + +#define SD_MESSAGE_SESSION_START SD_ID128_MAKE(8d,45,62,0c,1a,43,48,db,b1,74,10,da,57,c6,0c,66) +#define SD_MESSAGE_SESSION_STOP SD_ID128_MAKE(33,54,93,94,24,b4,45,6d,98,02,ca,83,33,ed,42,4a) +#define SD_MESSAGE_SEAT_START SD_ID128_MAKE(fc,be,fc,5d,a2,3d,42,80,93,f9,7c,82,a9,29,0f,7b) +#define SD_MESSAGE_SEAT_STOP SD_ID128_MAKE(e7,85,2b,fe,46,78,4e,d0,ac,cd,e0,4b,c8,64,c2,d5) +#define SD_MESSAGE_MACHINE_START SD_ID128_MAKE(24,d8,d4,45,25,73,40,24,96,06,83,81,a6,31,2d,f2) +#define SD_MESSAGE_MACHINE_STOP SD_ID128_MAKE(58,43,2b,d3,ba,ce,47,7c,b5,14,b5,63,81,b8,a7,58) + +#define SD_MESSAGE_TIME_CHANGE SD_ID128_MAKE(c7,a7,87,07,9b,35,4e,aa,a9,e7,7b,37,18,93,cd,27) +#define SD_MESSAGE_TIMEZONE_CHANGE SD_ID128_MAKE(45,f8,2f,4a,ef,7a,4b,bf,94,2c,e8,61,d1,f2,09,90) + +#define SD_MESSAGE_STARTUP_FINISHED SD_ID128_MAKE(b0,7a,24,9c,d0,24,41,4a,82,dd,00,cd,18,13,78,ff) + +#define SD_MESSAGE_SLEEP_START SD_ID128_MAKE(6b,bd,95,ee,97,79,41,e4,97,c4,8b,e2,7c,25,41,28) +#define SD_MESSAGE_SLEEP_STOP SD_ID128_MAKE(88,11,e6,df,2a,8e,40,f5,8a,94,ce,a2,6f,8e,bf,14) + +#define SD_MESSAGE_SHUTDOWN SD_ID128_MAKE(98,26,88,66,d1,d5,4a,49,9c,4e,98,92,1d,93,bc,40) + +#define SD_MESSAGE_UNIT_STARTING SD_ID128_MAKE(7d,49,58,e8,42,da,4a,75,8f,6c,1c,dc,7b,36,dc,c5) +#define SD_MESSAGE_UNIT_STARTED SD_ID128_MAKE(39,f5,34,79,d3,a0,45,ac,8e,11,78,62,48,23,1f,bf) +#define SD_MESSAGE_UNIT_STOPPING SD_ID128_MAKE(de,5b,42,6a,63,be,47,a7,b6,ac,3e,aa,c8,2e,2f,6f) +#define SD_MESSAGE_UNIT_STOPPED SD_ID128_MAKE(9d,1a,aa,27,d6,01,40,bd,96,36,54,38,aa,d2,02,86) +#define SD_MESSAGE_UNIT_FAILED SD_ID128_MAKE(be,02,cf,68,55,d2,42,8b,a4,0d,f7,e9,d0,22,f0,3d) +#define SD_MESSAGE_UNIT_RELOADING SD_ID128_MAKE(d3,4d,03,7f,ff,18,47,e6,ae,66,9a,37,0e,69,47,25) +#define SD_MESSAGE_UNIT_RELOADED SD_ID128_MAKE(7b,05,eb,c6,68,38,42,22,ba,a8,88,11,79,cf,da,54) + +#define SD_MESSAGE_SPAWN_FAILED SD_ID128_MAKE(64,12,57,65,1c,1b,4e,c9,a8,62,4d,7a,40,a9,e1,e7) + +#define SD_MESSAGE_FORWARD_SYSLOG_MISSED SD_ID128_MAKE(00,27,22,9c,a0,64,41,81,a7,6c,4e,92,45,8a,fa,2e) + +#define SD_MESSAGE_OVERMOUNTING SD_ID128_MAKE(1d,ee,03,69,c7,fc,47,36,b7,09,9b,38,ec,b4,6e,e7) + +#define SD_MESSAGE_LID_OPENED SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,6f) +#define SD_MESSAGE_LID_CLOSED SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,70) +#define SD_MESSAGE_SYSTEM_DOCKED SD_ID128_MAKE(f5,f4,16,b8,62,07,4b,28,92,7a,48,c3,ba,7d,51,ff) +#define SD_MESSAGE_SYSTEM_UNDOCKED SD_ID128_MAKE(51,e1,71,bd,58,52,48,56,81,10,14,4c,51,7c,ca,53) +#define SD_MESSAGE_POWER_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,71) +#define SD_MESSAGE_SUSPEND_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,72) +#define SD_MESSAGE_HIBERNATE_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,73) + +#define SD_MESSAGE_INVALID_CONFIGURATION SD_ID128_MAKE(c7,72,d2,4e,9a,88,4c,be,b9,ea,12,62,5c,30,6c,01) + +#define SD_MESSAGE_BOOTCHART SD_ID128_MAKE(9f,26,aa,56,2c,f4,40,c2,b1,6c,77,3d,04,79,b5,18) + +#define SD_MESSAGE_DNSSEC_FAILURE SD_ID128_MAKE(16,75,d7,f1,72,17,40,98,b1,10,8b,f8,c7,dc,8f,5d) +#define SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED SD_ID128_MAKE(4d,44,08,cf,d0,d1,44,85,91,84,d1,e6,5d,7c,8a,65) +#define SD_MESSAGE_DNSSEC_DOWNGRADE SD_ID128_MAKE(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57) + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-ndisc.h b/src/libsystemd/include/systemd/sd-ndisc.h new file mode 100644 index 0000000000..734701bca3 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-ndisc.h @@ -0,0 +1,84 @@ +#ifndef foosdndiscfoo +#define foosdndiscfoo + +/*** + 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 . +***/ + +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_NDISC_EVENT_STOP = 0, + SD_NDISC_EVENT_TIMEOUT = 1, +}; + +typedef struct sd_ndisc sd_ndisc; + +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_index(sd_ndisc *nd, int interface_index); +int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr); + +int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int priority); +int sd_ndisc_detach_event(sd_ndisc *nd); +sd_event *sd_ndisc_get_event(sd_ndisc *nd); + +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_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" + +#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]) + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-netlink.h b/src/libsystemd/include/systemd/sd-netlink.h new file mode 100644 index 0000000000..333df4676f --- /dev/null +++ b/src/libsystemd/include/systemd/sd-netlink.h @@ -0,0 +1,161 @@ +#ifndef foosdnetlinkhfoo +#define foosdnetlinkhfoo + +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 . +***/ + +#include +#include +#include +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_netlink sd_netlink; +typedef struct sd_netlink_message sd_netlink_message; + +/* callback */ + +typedef int (*sd_netlink_message_handler_t)(sd_netlink *nl, sd_netlink_message *m, void *userdata); + +/* bus */ +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); + +sd_netlink *sd_netlink_ref(sd_netlink *nl); +sd_netlink *sd_netlink_unref(sd_netlink *nl); + +int sd_netlink_send(sd_netlink *nl, sd_netlink_message *message, uint32_t *serial); +int sd_netlink_call_async(sd_netlink *nl, sd_netlink_message *message, + sd_netlink_message_handler_t callback, + void *userdata, uint64_t usec, uint32_t *serial); +int sd_netlink_call_async_cancel(sd_netlink *nl, uint32_t serial); +int sd_netlink_call(sd_netlink *nl, sd_netlink_message *message, uint64_t timeout, + sd_netlink_message **reply); + +int sd_netlink_get_events(sd_netlink *nl); +int sd_netlink_get_timeout(sd_netlink *nl, uint64_t *timeout); +int sd_netlink_process(sd_netlink *nl, sd_netlink_message **ret); +int sd_netlink_wait(sd_netlink *nl, uint64_t timeout); + +int sd_netlink_add_match(sd_netlink *nl, uint16_t match, sd_netlink_message_handler_t c, void *userdata); +int sd_netlink_remove_match(sd_netlink *nl, uint16_t match, sd_netlink_message_handler_t c, void *userdata); + +int sd_netlink_attach_event(sd_netlink *nl, sd_event *e, int priority); +int sd_netlink_detach_event(sd_netlink *nl); + +int sd_netlink_message_append_string(sd_netlink_message *m, unsigned short type, const char *data); +int sd_netlink_message_append_flag(sd_netlink_message *m, unsigned short type); +int sd_netlink_message_append_u8(sd_netlink_message *m, unsigned short type, uint8_t data); +int sd_netlink_message_append_u16(sd_netlink_message *m, unsigned short type, uint16_t data); +int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, uint32_t data); +int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len); +int sd_netlink_message_append_in_addr(sd_netlink_message *m, unsigned short type, const struct in_addr *data); +int sd_netlink_message_append_in6_addr(sd_netlink_message *m, unsigned short type, const struct in6_addr *data); +int sd_netlink_message_append_ether_addr(sd_netlink_message *m, unsigned short type, const struct ether_addr *data); +int sd_netlink_message_append_cache_info(sd_netlink_message *m, unsigned short type, const struct ifa_cacheinfo *info); + +int sd_netlink_message_open_container(sd_netlink_message *m, unsigned short type); +int sd_netlink_message_open_container_union(sd_netlink_message *m, unsigned short type, const char *key); +int sd_netlink_message_close_container(sd_netlink_message *m); + +int sd_netlink_message_read_string(sd_netlink_message *m, unsigned short type, const char **data); +int sd_netlink_message_read_u8(sd_netlink_message *m, unsigned short type, uint8_t *data); +int sd_netlink_message_read_u16(sd_netlink_message *m, unsigned short type, uint16_t *data); +int sd_netlink_message_read_u32(sd_netlink_message *m, unsigned short type, uint32_t *data); +int sd_netlink_message_read_ether_addr(sd_netlink_message *m, unsigned short type, struct ether_addr *data); +int sd_netlink_message_read_cache_info(sd_netlink_message *m, unsigned short type, struct ifa_cacheinfo *info); +int sd_netlink_message_read_in_addr(sd_netlink_message *m, unsigned short type, struct in_addr *data); +int sd_netlink_message_read_in6_addr(sd_netlink_message *m, unsigned short type, struct in6_addr *data); +int sd_netlink_message_enter_container(sd_netlink_message *m, unsigned short type); +int sd_netlink_message_exit_container(sd_netlink_message *m); + +int sd_netlink_message_rewind(sd_netlink_message *m); + +sd_netlink_message *sd_netlink_message_next(sd_netlink_message *m); + +sd_netlink_message *sd_netlink_message_ref(sd_netlink_message *m); +sd_netlink_message *sd_netlink_message_unref(sd_netlink_message *m); + +int sd_netlink_message_request_dump(sd_netlink_message *m, int dump); +int sd_netlink_message_is_error(sd_netlink_message *m); +int sd_netlink_message_get_errno(sd_netlink_message *m); +int sd_netlink_message_get_type(sd_netlink_message *m, uint16_t *type); +int sd_netlink_message_set_flags(sd_netlink_message *m, uint16_t flags); +int sd_netlink_message_is_broadcast(sd_netlink_message *m); + +/* rtnl */ + +int sd_rtnl_message_new_link(sd_netlink *nl, sd_netlink_message **ret, uint16_t msg_type, int index); +int sd_rtnl_message_new_addr_update(sd_netlink *nl, sd_netlink_message **ret, int index, int family); +int sd_rtnl_message_new_addr(sd_netlink *nl, sd_netlink_message **ret, uint16_t msg_type, int index, int family); +int sd_rtnl_message_new_route(sd_netlink *nl, sd_netlink_message **ret, uint16_t nlmsg_type, int rtm_family, unsigned char rtm_protocol); +int sd_rtnl_message_new_neigh(sd_netlink *nl, sd_netlink_message **ret, uint16_t msg_type, int index, int nda_family); + +int sd_rtnl_message_get_family(sd_netlink_message *m, int *family); + +int sd_rtnl_message_addr_set_prefixlen(sd_netlink_message *m, unsigned char prefixlen); +int sd_rtnl_message_addr_set_scope(sd_netlink_message *m, unsigned char scope); +int sd_rtnl_message_addr_set_flags(sd_netlink_message *m, unsigned char flags); +int sd_rtnl_message_addr_get_family(sd_netlink_message *m, int *family); +int sd_rtnl_message_addr_get_prefixlen(sd_netlink_message *m, unsigned char *prefixlen); +int sd_rtnl_message_addr_get_scope(sd_netlink_message *m, unsigned char *scope); +int sd_rtnl_message_addr_get_flags(sd_netlink_message *m, unsigned char *flags); +int sd_rtnl_message_addr_get_ifindex(sd_netlink_message *m, int *ifindex); + +int sd_rtnl_message_link_set_flags(sd_netlink_message *m, unsigned flags, unsigned change); +int sd_rtnl_message_link_set_type(sd_netlink_message *m, unsigned type); +int sd_rtnl_message_link_set_family(sd_netlink_message *m, unsigned family); +int sd_rtnl_message_link_get_ifindex(sd_netlink_message *m, int *ifindex); +int sd_rtnl_message_link_get_flags(sd_netlink_message *m, unsigned *flags); +int sd_rtnl_message_link_get_type(sd_netlink_message *m, unsigned *type); + +int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen); +int sd_rtnl_message_route_set_src_prefixlen(sd_netlink_message *m, unsigned char prefixlen); +int sd_rtnl_message_route_set_scope(sd_netlink_message *m, unsigned char scope); +int sd_rtnl_message_route_set_flags(sd_netlink_message *m, unsigned flags); +int sd_rtnl_message_route_get_flags(sd_netlink_message *m, unsigned *flags); +int sd_rtnl_message_route_get_family(sd_netlink_message *m, int *family); +int sd_rtnl_message_route_get_protocol(sd_netlink_message *m, unsigned char *protocol); +int sd_rtnl_message_route_get_scope(sd_netlink_message *m, unsigned char *scope); +int sd_rtnl_message_route_get_tos(sd_netlink_message *m, unsigned char *tos); +int sd_rtnl_message_route_get_table(sd_netlink_message *m, unsigned char *table); +int sd_rtnl_message_route_get_dst_prefixlen(sd_netlink_message *m, unsigned char *dst_len); +int sd_rtnl_message_route_get_src_prefixlen(sd_netlink_message *m, unsigned char *src_len); + +int sd_rtnl_message_neigh_set_flags(sd_netlink_message *m, uint8_t flags); +int sd_rtnl_message_neigh_set_state(sd_netlink_message *m, uint16_t state); +int sd_rtnl_message_neigh_get_family(sd_netlink_message *m, int *family); +int sd_rtnl_message_neigh_get_ifindex(sd_netlink_message *m, int *family); +int sd_rtnl_message_neigh_get_state(sd_netlink_message *m, uint16_t *state); +int sd_rtnl_message_neigh_get_flags(sd_netlink_message *m, uint8_t *flags); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_netlink, sd_netlink_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_netlink_message, sd_netlink_message_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-network.h b/src/libsystemd/include/systemd/sd-network.h new file mode 100644 index 0000000000..e20d12c44d --- /dev/null +++ b/src/libsystemd/include/systemd/sd-network.h @@ -0,0 +1,178 @@ +#ifndef foosdnetworkhfoo +#define foosdnetworkhfoo + +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include +#include + +#include "_sd-common.h" + +/* + * A few points: + * + * Instead of returning an empty string array or empty integer array, we + * may return NULL. + * + * Free the data the library returns with libc free(). String arrays + * are NULL terminated, and you need to free the array itself in + * addition to the strings contained. + * + * We return error codes as negative errno, kernel-style. On success, we + * return 0 or positive. + * + * These functions access data in /run. This is a virtual file system; + * therefore, accesses are relatively cheap. + * + * See sd-network(3) for more information. + */ + +_SD_BEGIN_DECLARATIONS; + +/* Get overall operational state + * Possible states: down, up, dormant, carrier, degraded, routable + * Possible return codes: + * -ENODATA: networkd is not aware of any links + */ +int sd_network_get_operational_state(char **state); + +/* Get DNS entries for all links. These are string representations of + * IP addresses */ +int sd_network_get_dns(char ***dns); + +/* Get NTP entries for all links. These are domain names or string + * representations of IP addresses */ +int sd_network_get_ntp(char ***ntp); + +/* Get the search domains for all links. */ +int sd_network_get_search_domains(char ***domains); + +/* Get the search domains for all links. */ +int sd_network_get_route_domains(char ***domains); + +/* Get setup state from ifindex. + * Possible states: + * pending: udev is still processing the link, we don't yet know if we will manage it + * failed: networkd failed to manage the link + * configuring: in the process of retrieving configuration or configuring the link + * configured: link configured successfully + * unmanaged: networkd is not handling the link + * linger: the link is gone, but has not yet been dropped by networkd + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_setup_state(int ifindex, char **state); + +/* Get operational state from ifindex. + * Possible states: + * off: the device is powered down + * no-carrier: the device is powered up, but it does not yet have a carrier + * dormant: the device has a carrier, but is not yet ready for normal traffic + * carrier: the link has a carrier + * degraded: the link has carrier and addresses valid on the local link configured + * routable: the link has carrier and routable address configured + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_operational_state(int ifindex, char **state); + +/* Get path to .network file applied to link */ +int sd_network_link_get_network_file(int ifindex, char **filename); + +/* Get DNS entries for a given link. These are string representations of + * IP addresses */ +int sd_network_link_get_dns(int ifindex, char ***addr); + +/* Get NTP entries for a given link. These are domain names or string + * representations of IP addresses */ +int sd_network_link_get_ntp(int ifindex, char ***addr); + +/* Indicates whether or not LLMNR should be enabled for the link + * Possible levels of support: yes, no, resolve + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_llmnr(int ifindex, char **llmnr); + +/* Indicates whether or not MulticastDNS should be enabled for the + * link. + * Possible levels of support: yes, no, resolve + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_mdns(int ifindex, char **mdns); + +/* Indicates whether or not DNSSEC should be enabled for the link + * Possible levels of support: yes, no, allow-downgrade + * Possible return codes: + * -ENODATA: networkd is not aware of the link + */ +int sd_network_link_get_dnssec(int ifindex, char **dnssec); + +/* Returns the list of per-interface DNSSEC negative trust anchors + * Possible return codes: + * -ENODATA: networkd is not aware of the link, or has no such data + */ +int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta); + +int sd_network_link_get_lldp(int ifindex, char **lldp); + +/* Get the search DNS domain names for a given link. */ +int sd_network_link_get_search_domains(int ifindex, char ***domains); + +/* Get the route DNS domain names for a given link. */ +int sd_network_link_get_route_domains(int ifindex, char ***domains); + +/* Get the CARRIERS to which current link is bound to. */ +int sd_network_link_get_carrier_bound_to(int ifindex, char ***carriers); + +/* Get the CARRIERS that are bound to current link. */ +int sd_network_link_get_carrier_bound_by(int ifindex, char ***carriers); + +/* Get the timezone that was learnt on a specific link. */ +int sd_network_link_get_timezone(int ifindex, char **timezone); + +/* Monitor object */ +typedef struct sd_network_monitor sd_network_monitor; + +/* Create a new monitor. Category must be NULL, "links" or "leases". */ +int sd_network_monitor_new(sd_network_monitor **ret, const char *category); + +/* Destroys the passed monitor. Returns NULL. */ +sd_network_monitor* sd_network_monitor_unref(sd_network_monitor *m); + +/* Flushes the monitor */ +int sd_network_monitor_flush(sd_network_monitor *m); + +/* Get FD from monitor */ +int sd_network_monitor_get_fd(sd_network_monitor *m); + +/* Get poll() mask to monitor */ +int sd_network_monitor_get_events(sd_network_monitor *m); + +/* Get timeout for poll(), as usec value relative to CLOCK_MONOTONIC's epoch */ +int sd_network_monitor_get_timeout(sd_network_monitor *m, uint64_t *timeout_usec); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_network_monitor, sd_network_monitor_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-path.h b/src/libsystemd/include/systemd/sd-path.h new file mode 100644 index 0000000000..be6abdcd03 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-path.h @@ -0,0 +1,91 @@ +#ifndef foosdpathhfoo +#define foosdpathhfoo + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + /* Temporary files */ + SD_PATH_TEMPORARY = 0x0ULL, + SD_PATH_TEMPORARY_LARGE, + + /* Vendor supplied data */ + SD_PATH_SYSTEM_BINARIES, + SD_PATH_SYSTEM_INCLUDE, + SD_PATH_SYSTEM_LIBRARY_PRIVATE, + SD_PATH_SYSTEM_LIBRARY_ARCH, + SD_PATH_SYSTEM_SHARED, + SD_PATH_SYSTEM_CONFIGURATION_FACTORY, + SD_PATH_SYSTEM_STATE_FACTORY, + + /* System configuration, runtime, state, ... */ + SD_PATH_SYSTEM_CONFIGURATION, + SD_PATH_SYSTEM_RUNTIME, + SD_PATH_SYSTEM_RUNTIME_LOGS, + SD_PATH_SYSTEM_STATE_PRIVATE, + SD_PATH_SYSTEM_STATE_LOGS, + SD_PATH_SYSTEM_STATE_CACHE, + SD_PATH_SYSTEM_STATE_SPOOL, + + /* Vendor supplied data */ + SD_PATH_USER_BINARIES, + SD_PATH_USER_LIBRARY_PRIVATE, + SD_PATH_USER_LIBRARY_ARCH, + SD_PATH_USER_SHARED, + + /* User configuration, state, runtime ... */ + SD_PATH_USER_CONFIGURATION, /* takes both actual configuration (like /etc) and state (like /var/lib) */ + SD_PATH_USER_RUNTIME, + SD_PATH_USER_STATE_CACHE, + + /* User resources */ + SD_PATH_USER, /* $HOME itself */ + SD_PATH_USER_DOCUMENTS, + SD_PATH_USER_MUSIC, + SD_PATH_USER_PICTURES, + SD_PATH_USER_VIDEOS, + SD_PATH_USER_DOWNLOAD, + SD_PATH_USER_PUBLIC, + SD_PATH_USER_TEMPLATES, + SD_PATH_USER_DESKTOP, + + /* Search paths */ + SD_PATH_SEARCH_BINARIES, + SD_PATH_SEARCH_LIBRARY_PRIVATE, + SD_PATH_SEARCH_LIBRARY_ARCH, + SD_PATH_SEARCH_SHARED, + SD_PATH_SEARCH_CONFIGURATION_FACTORY, + SD_PATH_SEARCH_STATE_FACTORY, + SD_PATH_SEARCH_CONFIGURATION, + + _SD_PATH_MAX, +}; + +int sd_path_home(uint64_t type, const char *suffix, char **path); +int sd_path_search(uint64_t type, const char *suffix, char ***paths); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-resolve.h b/src/libsystemd/include/systemd/sd-resolve.h new file mode 100644 index 0000000000..9c3b0ee70f --- /dev/null +++ b/src/libsystemd/include/systemd/sd-resolve.h @@ -0,0 +1,117 @@ +#ifndef foosdresolvehfoo +#define foosdresolvehfoo + +/*** + This file is part of systemd. + + Copyright 2005-2014 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 . +***/ + +#include +#include +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* An opaque sd-resolve session structure */ +typedef struct sd_resolve sd_resolve; + +/* An opaque sd-resolve query structure */ +typedef struct sd_resolve_query sd_resolve_query; + +/* A callback on completion */ +typedef int (*sd_resolve_getaddrinfo_handler_t)(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata); +typedef int (*sd_resolve_getnameinfo_handler_t)(sd_resolve_query *q, int ret, const char *host, const char *serv, void *userdata); + +enum { + SD_RESOLVE_GET_HOST = UINT64_C(1), + SD_RESOLVE_GET_SERVICE = UINT64_C(2), + SD_RESOLVE_GET_BOTH = UINT64_C(3), +}; + +int sd_resolve_default(sd_resolve **ret); + +/* Allocate a new sd-resolve session. */ +int sd_resolve_new(sd_resolve **ret); + +/* Free a sd-resolve session. This destroys all attached + * sd_resolve_query objects automatically. */ +sd_resolve* sd_resolve_unref(sd_resolve *resolve); +sd_resolve* sd_resolve_ref(sd_resolve *resolve); + +/* Return the UNIX file descriptor to poll() for events on. Use this + * function to integrate sd-resolve with your custom main loop. */ +int sd_resolve_get_fd(sd_resolve *resolve); + +/* Return the poll() events (a combination of flags like POLLIN, + * POLLOUT, ...) to check for. */ +int sd_resolve_get_events(sd_resolve *resolve); + +/* Return the poll() timeout to pass. Returns (uint64_t) -1 as + * timeout if no timeout is needed. */ +int sd_resolve_get_timeout(sd_resolve *resolve, uint64_t *timeout_usec); + +/* Process pending responses. After this function is called, you can + * get the next completed query object(s) using + * sd_resolve_get_next(). */ +int sd_resolve_process(sd_resolve *resolve); + +/* Wait for a resolve event to complete. */ +int sd_resolve_wait(sd_resolve *resolve, uint64_t timeout_usec); + +int sd_resolve_get_tid(sd_resolve *resolve, pid_t *tid); + +int sd_resolve_attach_event(sd_resolve *resolve, sd_event *e, int priority); +int sd_resolve_detach_event(sd_resolve *resolve); +sd_event *sd_resolve_get_event(sd_resolve *resolve); + +/* Issue a name-to-address query on the specified session. The + * arguments are compatible with those of libc's + * getaddrinfo(3). The function returns a new query object. When the + * query is completed, you may retrieve the results using + * sd_resolve_getaddrinfo_done(). */ +int sd_resolve_getaddrinfo(sd_resolve *resolve, sd_resolve_query **q, const char *node, const char *service, const struct addrinfo *hints, sd_resolve_getaddrinfo_handler_t callback, void *userdata); + +/* Issue an address-to-name query on the specified session. The + * arguments are compatible with those of libc's + * getnameinfo(3). The function returns a new query object. When the + * query is completed, you may retrieve the results using + * sd_resolve_getnameinfo_done(). Set gethost (resp. getserv) to non-zero + * if you want to query the hostname (resp. the service name). */ +int sd_resolve_getnameinfo(sd_resolve *resolve, sd_resolve_query **q, const struct sockaddr *sa, socklen_t salen, int flags, uint64_t get, sd_resolve_getnameinfo_handler_t callback, void *userdata); + +sd_resolve_query *sd_resolve_query_ref(sd_resolve_query* q); +sd_resolve_query *sd_resolve_query_unref(sd_resolve_query* q); + +/* Returns non-zero when the query operation specified by q has been completed. */ +int sd_resolve_query_is_done(sd_resolve_query*q); + +void *sd_resolve_query_get_userdata(sd_resolve_query *q); +void *sd_resolve_query_set_userdata(sd_resolve_query *q, void *userdata); + +sd_resolve *sd_resolve_query_get_resolve(sd_resolve_query *q); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_resolve, sd_resolve_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_resolve_query, sd_resolve_query_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/include/systemd/sd-utf8.h b/src/libsystemd/include/systemd/sd-utf8.h new file mode 100644 index 0000000000..6781983878 --- /dev/null +++ b/src/libsystemd/include/systemd/sd-utf8.h @@ -0,0 +1,32 @@ +#ifndef foosdutf8hfoo +#define foosdutf8hfoo + +/*** + 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 . +***/ + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +_sd_pure_ const char *sd_utf8_is_valid(const char *s); +_sd_pure_ const char *sd_ascii_is_valid(const char *s); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd/libsystemd-internal/Makefile b/src/libsystemd/libsystemd-internal/Makefile new file mode 100644 index 0000000000..079d9c4745 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/Makefile @@ -0,0 +1,123 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +libsystemd_internal_la_SOURCES = \ + src/systemd/sd-bus.h \ + src/systemd/sd-bus-protocol.h \ + src/systemd/sd-bus-vtable.h \ + src/systemd/sd-utf8.h \ + src/systemd/sd-event.h \ + src/systemd/sd-netlink.h \ + src/systemd/sd-resolve.h \ + src/systemd/sd-login.h \ + src/systemd/sd-id128.h \ + src/systemd/sd-daemon.h \ + src/systemd/sd-path.h \ + src/systemd/sd-network.h \ + src/systemd/sd-hwdb.h \ + src/systemd/sd-device.h \ + src/libsystemd/libsystemd.sym \ + src/libsystemd/sd-bus/sd-bus.c \ + src/libsystemd/sd-bus/bus-control.c \ + src/libsystemd/sd-bus/bus-control.h \ + src/libsystemd/sd-bus/bus-error.c \ + src/libsystemd/sd-bus/bus-error.h \ + src/libsystemd/sd-bus/bus-common-errors.h \ + src/libsystemd/sd-bus/bus-common-errors.c \ + src/libsystemd/sd-bus/bus-internal.c \ + src/libsystemd/sd-bus/bus-internal.h \ + src/libsystemd/sd-bus/bus-socket.c \ + src/libsystemd/sd-bus/bus-socket.h \ + src/libsystemd/sd-bus/bus-kernel.c \ + src/libsystemd/sd-bus/bus-kernel.h \ + src/libsystemd/sd-bus/bus-container.c \ + src/libsystemd/sd-bus/bus-container.h \ + src/libsystemd/sd-bus/bus-message.c \ + src/libsystemd/sd-bus/bus-message.h \ + src/libsystemd/sd-bus/bus-creds.c \ + src/libsystemd/sd-bus/bus-creds.h \ + src/libsystemd/sd-bus/bus-signature.c \ + src/libsystemd/sd-bus/bus-signature.h \ + src/libsystemd/sd-bus/bus-type.c \ + src/libsystemd/sd-bus/bus-type.h \ + src/libsystemd/sd-bus/bus-match.c \ + src/libsystemd/sd-bus/bus-match.h \ + src/libsystemd/sd-bus/bus-bloom.c \ + src/libsystemd/sd-bus/bus-bloom.h \ + src/libsystemd/sd-bus/bus-introspect.c \ + src/libsystemd/sd-bus/bus-introspect.h \ + src/libsystemd/sd-bus/bus-objects.c \ + src/libsystemd/sd-bus/bus-objects.h \ + src/libsystemd/sd-bus/bus-gvariant.c \ + src/libsystemd/sd-bus/bus-gvariant.h \ + src/libsystemd/sd-bus/bus-convenience.c \ + src/libsystemd/sd-bus/bus-track.c \ + src/libsystemd/sd-bus/bus-track.h \ + src/libsystemd/sd-bus/bus-slot.c \ + src/libsystemd/sd-bus/bus-slot.h \ + src/libsystemd/sd-bus/bus-protocol.h \ + src/libsystemd/sd-bus/kdbus.h \ + src/libsystemd/sd-bus/bus-dump.c \ + src/libsystemd/sd-bus/bus-dump.h \ + src/libsystemd/sd-utf8/sd-utf8.c \ + src/libsystemd/sd-event/sd-event.c \ + src/libsystemd/sd-netlink/sd-netlink.c \ + src/libsystemd/sd-netlink/netlink-internal.h \ + src/libsystemd/sd-netlink/netlink-message.c \ + src/libsystemd/sd-netlink/netlink-socket.c \ + src/libsystemd/sd-netlink/rtnl-message.c \ + src/libsystemd/sd-netlink/netlink-types.h \ + src/libsystemd/sd-netlink/netlink-types.c \ + src/libsystemd/sd-netlink/netlink-util.h \ + src/libsystemd/sd-netlink/netlink-util.c \ + src/libsystemd/sd-netlink/local-addresses.h \ + src/libsystemd/sd-netlink/local-addresses.c \ + src/libsystemd/sd-id128/sd-id128.c \ + src/libsystemd/sd-daemon/sd-daemon.c \ + src/libsystemd/sd-login/sd-login.c \ + src/libsystemd/sd-path/sd-path.c \ + src/libsystemd/sd-network/sd-network.c \ + src/libsystemd/sd-network/network-util.h \ + src/libsystemd/sd-network/network-util.c \ + src/libsystemd/sd-hwdb/sd-hwdb.c \ + src/libsystemd/sd-hwdb/hwdb-util.h \ + src/libsystemd/sd-hwdb/hwdb-internal.h \ + src/libsystemd/sd-device/device-internal.h \ + src/libsystemd/sd-device/device-util.h \ + src/libsystemd/sd-device/device-enumerator.c \ + src/libsystemd/sd-device/device-enumerator-private.h \ + src/libsystemd/sd-device/sd-device.c \ + src/libsystemd/sd-device/device-private.c \ + src/libsystemd/sd-device/device-private.h \ + src/libsystemd/sd-resolve/sd-resolve.c + +libsystemd_internal_la_LIBADD = \ + libbasic.la \ + -lresolv + +noinst_LTLIBRARIES += \ + libsystemd-internal.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd/libsystemd-internal/sd-bus/DIFFERENCES b/src/libsystemd/libsystemd-internal/sd-bus/DIFFERENCES new file mode 100644 index 0000000000..db269675a7 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/DIFFERENCES @@ -0,0 +1,25 @@ +Known differences between dbus1 and kdbus: + +- NameAcquired/NameLost is gone entirely on kdbus backends if + libsystemd is used. It is still added in by systemd-bus-proxyd + for old dbus1 clients, and it is available if libsystemd is used + against the classic dbus1 daemon. If you want to write compatible + code with libsystem-bus you need to explicitly subscribe to + NameOwnerChanged signals and just ignore NameAcquired/NameLost + +- Applications have to deal with spurious signals they didn't expect, + due to the probabilistic bloom filters. They need to handle this + anyway, given that any client can send anything to arbitrary clients + anyway, even in dbus1, so not much changes. + +- clients of the system bus when kdbus is used must roll their own + security. Only legacy dbus1 clients get the old XML policy enforced, + which is implemented by systemd-bus-proxyd. + +- Serial numbers of synthesized messages are always (uint32_t) -1. + +- NameOwnerChanged is a synthetic message, generated locally and not + by the driver. On dbus1 only the Disconnected message was + synthesized like this. + +- There's no standard per-session bus anymore. Only a per-user bus. diff --git a/src/libsystemd/libsystemd-internal/sd-bus/GVARIANT-SERIALIZATION b/src/libsystemd/libsystemd-internal/sd-bus/GVARIANT-SERIALIZATION new file mode 100644 index 0000000000..6aeb11364a --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/GVARIANT-SERIALIZATION @@ -0,0 +1,110 @@ +How we use GVariant for serializing D-Bus messages +-------------------------------------------------- + +We stay close to the original dbus1 framing as possible, but make +certain changes to adapt for GVariant. dbus1 has the following +framing: + + 1. A fixed header of "yyyyuu" + 2. Additional header fields of "a(yv)" + 3. Padding with NUL bytes to pad up to next 8byte boundary + 4. The body + +Note that the body is not padded at the end, the complete message +hence might have a non-aligned size. Reading multiple messages at once +will hence result in possibly unaligned messages in memory. + +The header consists of the following: + + y Endianness, 'l' or 'B' + y Message Type + y Flags + y Protocol version, '1' + u Length of the body, i.e. the length of part 4 above + u 32bit Serial number + + = 12 bytes + +This header is then followed by the fields array, whose first value is +a 32bit array size. + +When using GVariant we keep the basic structure in place, only +slightly alter the header, and define protocol version '2'. The new +header: + + y Endianness, 'l' or 'B' + y Message Type + y Flags + y Protocol version, '2' + u Reserved, must be 0 + t 64bit Cookie + + = 16 bytes + +This is then followed by the GVariant fields array ("a{tv}"), and +finally the actual body as variant (v). Putting this altogether a +packet on dbus2 hence qualifies as a fully compliant GVariant +structure of (yyyyuta{tv}v). + +For details on gvariant, see: + +https://people.gnome.org/~desrt/gvariant-serialisation.pdf + +Regarding the framing of dbus2, also see: + +https://wiki.gnome.org/Projects/GLib/GDBus/Version2 + +The first four bytes of the header are defined the same way for dbus1 +and dbus2. The first bytes contain the endianess field and the +protocol version, so that the remainder of the message can be safely +made sense of just by looking at the first 32bit. + +Note that the length of the body is no longer included in the header +on dbus2! In fact, the message size must be known in advance, from the +underlying transport in order to parse dbus2 messages, while it is +directly included in dbus1 message headers. This change of semantics +is an effect of GVariant's basic design. + +The serial number has been renamed cookie and has been extended from +32bit to 64bit. It is recommended to avoid the higher 32bit of the +cookie field though, to simplify compatibility with dbus1 peers. Note +that not only the cookie/serial field in the fixed header, but also +the reply_cookie/reply_serial additional header field has been +increased from 32bit to 64bit, too! + +The header field identifiers have been extended from 8bit to +64bit. This has been done to simplify things (as kdbus otherwise uses +exclusively 64bit types, unless there is a strong reason not to), and +has no effect on the serialization size, as due to alignment for each +8bit header field identifier 56 bits of padding had to be added. + +Note that the header size changed, due to these changes. However, +consider that on dbus1 the beginning of the fields array contains the +32bit array size (since that is how arrays are encoded on dbus1), +thus, if one considers that size part of the header, instead of the +array, the size of the header on dbus1 and dbus2 stays identical, at +16 bytes. + + 0 4 8 12 16 + Common: | E | T | F | V | ... + + dbus1: | (as above) | Body Length | Serial | Fields Length | Fields array ... + + gvariant: | (as above) | Reserved | Cookie | Fields array ... + +And that's already it. + +Note: to simplify parsing, valid kdbus/dbus2 messages must include the +entire fixed header and additional header fields in a single non-memfd +message part. Also, the signature string of the body variant all the +way to the end of the message must be in a single non-memfd part +too. The parts for this extended header and footer can be the same +one, and can also continue any amount of additional body bytes. + +Note: on kdbus only native endian messages marshalled in gvariant may + be sent. If a client receives a message in non-native endianness + or in dbus1 marshalling it shall ignore the message. + +Note: The GVariant "MAYBE" type is not supported, so that messages can + be fully converted forth and back between dbus1 and gvariant + representations. diff --git a/src/libsystemd/libsystemd-internal/sd-bus/PORTING-DBUS1 b/src/libsystemd/libsystemd-internal/sd-bus/PORTING-DBUS1 new file mode 100644 index 0000000000..2dedb28bcf --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/PORTING-DBUS1 @@ -0,0 +1,535 @@ +A few hints on supporting kdbus as backend in your favorite D-Bus library. + +~~~ + +Before you read this, have a look at the DIFFERENCES and +GVARIANT_SERIALIZATION texts you find in the same directory where you +found this. + +We invite you to port your favorite D-Bus protocol implementation +over to kdbus. However, there are a couple of complexities +involved. On kdbus we only speak GVariant marshaling, kdbus clients +ignore traffic in dbus1 marshaling. Thus, you need to add a second, +GVariant compatible marshaler to your library first. + +After you have done that: here's the basic principle how kdbus works: + +You connect to a bus by opening its bus node in /sys/fs/kdbus/. All +buses have a device node there, it starts with a numeric UID of the +owner of the bus, followed by a dash and a string identifying the +bus. The system bus is thus called /sys/fs/kdbus/0-system, and for user +buses the device node is /sys/fs/kdbus/1000-user (if 1000 is your user +id). + +(Before we proceed, please always keep a copy of libsystemd next +to you, ultimately that's where the details are, this document simply +is a rough overview to help you grok things.) + +CONNECTING + +To connect to a bus, simply open() its device node and issue the +KDBUS_CMD_HELLO call. That's it. Now you are connected. Do not send +Hello messages or so (as you would on dbus1), that does not exist for +kdbus. + +The structure you pass to the ioctl will contain a couple of +parameters that you need to know, to operate on the bus. + +There are two flags fields, one indicating features of the kdbus +kernel side ("conn_flags"), the other one ("bus_flags") indicating +features of the bus owner (i.e. systemd). Both flags fields are 64bit +in width. + +When calling into the ioctl, you need to place your own supported +feature bits into these fields. This tells the kernel about the +features you support. When the ioctl returns, it will contain the +features the kernel supports. + +If any of the higher 32bit are set on the two flags fields and your +client does not know what they mean, it must disconnect. The upper +32bit are used to indicate "incompatible" feature additions on the bus +system, the lower 32bit indicate "compatible" feature additions. A +client that does not support a "compatible" feature addition can go on +communicating with the bus, however a client that does not support an +"incompatible" feature must not proceed with the connection. When a +client encountes such an "incompatible" feature it should immediately +try the next bus address configured in the bus address string. + +The hello structure also contains another flags field "attach_flags" +which indicates metadata that is optionally attached to all incoming +messages. You probably want to set KDBUS_ATTACH_NAMES unconditionally +in it. This has the effect that all well-known names of a sender are +attached to all incoming messages. You need this information to +implement matches that match on a message sender name correctly. Of +course, you should only request the attachment of as little metadata +fields as you need. + +The kernel will return in the "id" field your unique id. This is a +simple numeric value. For compatibility with classic dbus1 simply +format this as string and prefix ":1.". + +The kernel will also return the bloom filter size and bloom filter +hash function number used for the signal broadcast bloom filter (see +below). + +The kernel will also return the bus ID of the bus in a 128bit field. + +The pool size field specifies the size of the memory mapped buffer. +After the calling the hello ioctl, you should memory map the kdbus +fd. In this memory mapped region, the kernel will place all your incoming +messages. + +SENDING MESSAGES + +Use the MSG_SEND ioctl to send a message to another peer. The ioctl +takes a structure that contains a variety of fields: + +The flags field corresponds closely to the old dbus1 message header +flags field, though the DONT_EXPECT_REPLY field got inverted into +EXPECT_REPLY. + +The dst_id/src_id field contains the unique id of the destination and +the sender. The sender field is overridden by the kernel usually, hence +you shouldn't fill it in. The destination field can also take the +special value KDBUS_DST_ID_BROADCAST for broadcast messages. For +messages intended to a well-known name set the field to +KDBUS_DST_ID_NAME, and attach the name in a special "items" entry to +the message (see below). + +The payload field indicates the payload. For all dbus traffic it +should carry the value 0x4442757344427573ULL. (Which encodes +'DBusDBus'). + +The cookie field corresponds with the "serial" field of classic +dbus1. We simply renamed it here (and extended it to 64bit) since we +didn't want to imply the monotonicity of the assignment the way the +word "serial" indicates it. + +When sending a message that expects a reply, you need to set the +EXPECT_REPLY flag in the message flag field. In this case you should +also fill out the "timeout_ns" value which indicates the timeout in +nsec for this call. If the peer does not respond in this time you will +get a notification of a timeout. Note that this is also used for +security purposes: a single reply messages is only allowed through the +bus as long as the timeout has not ended. With this timeout value you +hence "open a time window" in which the peer might respond to your +request and the policy allows the response to go through. + +When sending a message that is a reply, you need to fill in the +cookie_reply field, which is similar to the reply_serial field of +dbus1. Note that a message cannot have EXPECT_REPLY and a reply_serial +at the same time! + +This pretty much explains the ioctl header. The actual payload of the +data is now referenced in additional items that are attached to this +ioctl header structure at the end. When sending a message, you attach +items of the type PAYLOAD_VEC, PAYLOAD_MEMFD, FDS, BLOOM_FILTER, +DST_NAME to it: + + KDBUS_ITEM_PAYLOAD_VEC: contains a pointer + length pair for + referencing arbitrary user memory. This is how you reference most + of your data. It's a lot like the good old iovec structure of glibc. + + KDBUS_ITEM_PAYLOAD_MEMFD: for large data blocks it is preferable + to send prepared "memfds" (see below) over. This item contains an + fd for a memfd plus a size. + + KDBUS_ITEM_FDS: for sending over fds attach an item of this type with + an array of fds. + + KDBUS_ITEM_BLOOM_FILTER: the calculated bloom filter of this message, + only for undirected (broadcast) message. + + KDBUS_ITEM_DST_NAME: for messages that are directed to a well-known + name (instead of a unique name), this item contains the well-known + name field. + +A single message may consists of no, one or more payload items of type +PAYLOAD_VEC or PAYLOAD_MEMFD. D-Bus protocol implementations should +treat them as a single block that just happens to be split up into +multiple items. Some restrictions apply however: + + The message header in its entirety must be contained in a single + PAYLOAD_VEC item. + + You may only split your message up right in front of each GVariant + contained in the payload, as well is immediately before framing of a + Gvariant, as well after as any padding bytes if there are any. The + padding bytes must be wholly contained in the preceding + PAYLOAD_VEC/PAYLOAD_MEMFD item. You may not split up basic types + nor arrays of fixed types. The latter is necessary to allow APIs + to return direct pointers to linear arrays of numeric + values. Examples: The basic types "u", "s", "t" have to be in the + same payload item. The array of fixed types "ay", "ai" have to be + fully in contained in the same payload item. For an array "as" or + "a(si)" the only restriction however is to keep each string + individually in an uninterrupted item, to keep the framing of each + element and the array in a single uninterrupted item, however the + various strings might end up in different items. + +Note again, that splitting up messages into separate items is up to the +implementation. Also note that the kdbus kernel side might merge +separate items if it deems this to be useful. However, the order in +which items are contained in the message is left untouched. + +PAYLOAD_MEMFD items allow zero-copy data transfer (see below regarding +the memfd concept). Note however that the overhead of mapping these +makes them relatively expensive, and only worth the trouble for memory +blocks > 512K (this value appears to be quite universal across +architectures, as we tested). Thus we recommend sending PAYLOAD_VEC +items over for small messages and restore to PAYLOAD_MEMFD items for +messages > 512K. Since while building up the message you might not +know yet whether it will grow beyond this boundary a good approach is +to simply build the message unconditionally in a memfd +object. However, when the message is sealed to be sent away check for +the size limit. If the size of the message is < 512K, then simply send +the data as PAYLOAD_VEC and reuse the memfd. If it is >= 512K, seal +the memfd and send it as PAYLOAD_MEMFD, and allocate a new memfd for +the next message. + +RECEIVING MESSAGES + +Use the MSG_RECV ioctl to read a message from kdbus. This will return +an offset into the pool memory map, relative to its beginning. + +The received message structure more or less follows the structure of +the message originally sent. However, certain changes have been +made. In the header the src_id field will be filled in. + +The payload items might have gotten merged and PAYLOAD_VEC items are +not used. Instead, you will only find PAYLOAD_OFF and PAYLOAD_MEMFD +items. The former contain an offset and size into your memory mapped +pool where you find the payload. + +If during the HELLO ioctl you asked for getting metadata attached to +your message, you will find additional KDBUS_ITEM_CREDS, +KDBUS_ITEM_PID_COMM, KDBUS_ITEM_TID_COMM, KDBUS_ITEM_TIMESTAMP, +KDBUS_ITEM_EXE, KDBUS_ITEM_CMDLINE, KDBUS_ITEM_CGROUP, +KDBUS_ITEM_CAPS, KDBUS_ITEM_SECLABEL, KDBUS_ITEM_AUDIT items that +contain this metadata. This metadata will be gathered from the sender +at the point in time it sends the message. This information is +uncached, and since it is appended by the kernel, trustable. The +KDBUS_ITEM_SECLABEL item usually contains the SELinux security label, +if it is used. + +After processing the message you need to call the KDBUS_CMD_FREE +ioctl, which releases the message from the pool, and allows the kernel +to store another message there. Note that the memory used by the pool +is ordinary anonymous, swappable memory that is backed by tmpfs. Hence +there is no need to copy the message out of it quickly, instead you +can just leave it there as long as you need it and release it via the +FREE ioctl only after that's done. + +BLOOM FILTERS + +The kernel does not understand dbus marshaling, it will not look into +the message payload. To allow clients to subscribe to specific subsets +of the broadcast matches we employ bloom filters. + +When broadcasting messages, a bloom filter needs to be attached to the +message in a KDBUS_ITEM_BLOOM item (and only for broadcasting +messages!). If you don't know what bloom filters are, read up now on +Wikipedia. In short: they are a very efficient way how to +probabilistically check whether a certain word is contained in a +vocabulary. It knows no false negatives, but it does know false +positives. + +The parameters for the bloom filters that need to be included in +broadcast message is communicated to userspace as part of the hello +response structure (see above). By default it has the parameters m=512 +(bits in the filter), k=8 (nr of hash functions). Note however, that +this is subject to change in later versions, and userspace +implementations must be capable of handling m values between at least +m=8 and m=2^32, and k values between at least k=1 and k=32. The +underlying hash function is SipHash-2-4. It is used with a number of +constant (yet originally randomly generated) 128bit hash keys, more +specifically: + + b9,66,0b,f0,46,70,47,c1,88,75,c4,9c,54,b9,bd,15, + aa,a1,54,a2,e0,71,4b,39,bf,e1,dd,2e,9f,c5,4a,3b, + 63,fd,ae,be,cd,82,48,12,a1,6e,41,26,cb,fa,a0,c8, + 23,be,45,29,32,d2,46,2d,82,03,52,28,fe,37,17,f5, + 56,3b,bf,ee,5a,4f,43,39,af,aa,94,08,df,f0,fc,10, + 31,80,c8,73,c7,ea,46,d3,aa,25,75,0f,9e,4c,09,29, + 7d,f7,18,4b,7b,a4,44,d5,85,3c,06,e0,65,53,96,6d, + f2,77,e9,6f,93,b5,4e,71,9a,0c,34,88,39,25,bf,35 + +When calculating the first bit index into the bloom filter, the +SipHash-2-4 hash value is calculated for the input data and the first +16 bytes of the array above as hash key. Of the resulting 8 bytes of +output, as many full bytes are taken for the bit index as necessary, +starting from the output's first byte. For the second bit index the +same hash value is used, continuing with the next unused output byte, +and so on. Each time the bytes returned by the hash function are +depleted it is recalculated with the next 16 byte hash key from the +array above and the same input data. + +For each message to send across the bus we populate the bloom filter +with all possible matchable strings. If a client then wants to +subscribe to messages of this type, it simply tells the kernel to test +its own calculated bit mask against the bloom filter of each message. + +More specifically, the following strings are added to the bloom filter +of each message that is broadcasted: + + The string "interface:" suffixed by the interface name + + The string "member:" suffixed by the member name + + The string "path:" suffixed by the path name + + The string "path-slash-prefix:" suffixed with the path name, and + also all prefixes of the path name (cut off at "/"), also prefixed + with "path-slash-prefix". + + The string "message-type:" suffixed with the strings "signal", + "method_call", "error" or "method_return" for the respective message + type of the message. + + If the first argument of the message is a string, "arg0:" suffixed + with the first argument. + + If the first argument of the message is a string, "arg0-dot-prefix" + suffixed with the first argument, and also all prefixes of the + argument (cut off at "."), also prefixed with "arg0-dot-prefix". + + If the first argument of the message is a string, + "arg0-slash-prefix" suffixed with the first argument, and also all + prefixes of the argument (cut off at "/"), also prefixed with + "arg0-slash-prefix". + + Similar for all further arguments that are strings up to 63, for the + arguments and their "dot" and "slash" prefixes. On the first + argument that is not a string, addition to the bloom filter should be + stopped however. + +(Note that the bloom filter does not contain sender nor receiver +names!) + +When a client wants to subscribe to messages matching a certain +expression, it should calculate the bloom mask following the same +algorithm. The kernel will then simply test the mask against the +attached bloom filters. + +Note that bloom filters are probabilistic, which means that clients +might get messages they did not expect. Your bus protocol +implementation must be capable of dealing with these unexpected +messages (which it needs to anyway, given that transfers are +relatively unrestricted on kdbus and people can send you all kinds of +non-sense). + +If a client connects to a bus whose bloom filter metrics (i.e. filter +size and number of hash functions) are outside of the range the client +supports it must immediately disconnect and continue connection with +the next bus address of the bus connection string. + +INSTALLING MATCHES + +To install matches for broadcast messages, use the KDBUS_CMD_ADD_MATCH +ioctl. It takes a structure that contains an encoded match expression, +and that is followed by one or more items, which are combined in an +AND way. (Meaning: a message is matched exactly when all items +attached to the original ioctl struct match). + +To match against other user messages add a KDBUS_ITEM_BLOOM item in +the match (see above). Note that the bloom filter does not include +matches to the sender names. To additionally check against sender +names, use the KDBUS_ITEM_ID (for unique id matches) and +KDBUS_ITEM_NAME (for well-known name matches) item types. + +To match against kernel generated messages (see below) you should add +items of the same type as the kernel messages include, +i.e. KDBUS_ITEM_NAME_ADD, KDBUS_ITEM_NAME_REMOVE, +KDBUS_ITEM_NAME_CHANGE, KDBUS_ITEM_ID_ADD, KDBUS_ITEM_ID_REMOVE and +fill them out. Note however, that you have some wildcards in this +case, for example the .id field of KDBUS_ITEM_ID_ADD/KDBUS_ITEM_ID_REMOVE +structures may be set to 0 to match against any id addition/removal. + +Note that dbus match strings do no map 1:1 to these ioctl() calls. In +many cases (where the match string is "underspecified") you might need +to issue up to six different ioctl() calls for the same match. For +example, the empty match (which matches against all messages), would +translate into one KDBUS_ITEM_BLOOM ioctl, one KDBUS_ITEM_NAME_ADD, +one KDBUS_ITEM_NAME_CHANGE, one KDBUS_ITEM_NAME_REMOVE, one +KDBUS_ITEM_ID_ADD and one KDBUS_ITEM_ID_REMOVE. + +When creating a match, you may attach a "cookie" value to them, which +is used for deleting this match again. The cookie can be selected freely +by the client. When issuing KDBUS_CMD_REMOVE_MATCH, simply pass the +same cookie as before and all matches matching the same "cookie" value +will be removed. This is particularly handy for the case where multiple +ioctl()s are added for a single match strings. + +MEMFDS + +memfds may be sent across kdbus via KDBUS_ITEM_PAYLOAD_MEMFD items +attached to messages. If this is done, the data included in the memfd +is considered part of the payload stream of a message, and are treated +the same way as KDBUS_ITEM_PAYLOAD_VEC by the receiving side. It is +possible to interleave KDBUS_ITEM_PAYLOAD_MEMFD and +KDBUS_ITEM_PAYLOAD_VEC items freely, by the reader they will be +considered a single stream of bytes in the order these items appear in +the message, that just happens to be split up at various places +(regarding rules how they may be split up, see above). The kernel will +refuse taking KDBUS_ITEM_PAYLOAD_MEMFD items that refer to memfds that +are not sealed. + +Note that sealed memfds may be unsealed again if they are not mapped +you have the only fd reference to them. + +Alternatively to sending memfds as KDBUS_ITEM_PAYLOAD_MEMFD items +(where they are just a part of the payload stream of a message) you can +also simply attach any memfd to a message using +KDBUS_ITEM_PAYLOAD_FDS. In this case, the memfd contents is not +considered part of the payload stream of the message, but simply fds +like any other, that happen to be attached to the message. + +MESSAGES FROM THE KERNEL + +A couple of messages previously generated by the dbus1 bus driver are +now generated by the kernel. Since the kernel does not understand the +payload marshaling, they are generated by the kernel in a different +format. This is indicated with the "payload type" field of the +messages set to 0. Library implementations should take these messages +and synthesize traditional driver messages for them on reception. + +More specifically: + + Instead of the NameOwnerChanged, NameLost, NameAcquired signals + there are kernel messages containing KDBUS_ITEM_NAME_ADD, + KDBUS_ITEM_NAME_REMOVE, KDBUS_ITEM_NAME_CHANGE, KDBUS_ITEM_ID_ADD, + KDBUS_ITEM_ID_REMOVE items are generated (each message will contain + exactly one of these items). Note that in libsystemd we have + obsoleted NameLost/NameAcquired messages, since they are entirely + redundant to NameOwnerChanged. This library will hence only + synthesize NameOwnerChanged messages from these kernel messages, + and never generate NameLost/NameAcquired. If your library needs to + stay compatible to the old dbus1 userspace, you possibly might need + to synthesize both a NameOwnerChanged and NameLost/NameAcquired + message from the same kernel message. + + When a method call times out, a KDBUS_ITEM_REPLY_TIMEOUT message is + generated. This should be synthesized into a method error reply + message to the original call. + + When a method call fails because the peer terminated the connection + before responding, a KDBUS_ITEM_REPLY_DEAD message is + generated. Similarly, it should be synthesized into a method error + reply message. + +For synthesized messages we recommend setting the cookie field to +(uint32_t) -1 (and not (uint64_t) -1!), so that the cookie is not 0 +(which the dbus1 spec does not allow), but clearly recognizable as +synthetic. + +Note that the KDBUS_ITEM_NAME_XYZ messages will actually inform you +about all kinds of names, including activatable ones. Classic dbus1 +NameOwnerChanged messages OTOH are only generated when a name is +really acquired on the bus and not just simply activatable. This means +you must explicitly check for the case where an activatable name +becomes acquired or an acquired name is lost and returns to be +activatable. + +NAME REGISTRY + +To acquire names on the bus, use the KDBUS_CMD_NAME_ACQUIRE ioctl(). It +takes a flags field similar to dbus1's RequestName() bus driver call, +however the NO_QUEUE flag got inverted into a QUEUE flag instead. + +To release a previously acquired name use the KDBUS_CMD_NAME_RELEASE +ioctl(). + +To list acquired names use the KDBUS_CMD_CONN_INFO ioctl. It may be +used to list unique names, well known names as well as activatable +names and clients currently queuing for ownership of a well-known +name. The ioctl will return an offset into the memory pool. After +reading all the data you need, you need to release this via the +KDBUS_CMD_FREE ioctl(), similar how you release a received message. + +CREDENTIALS + +kdbus can optionally attach various kinds of metadata about the sender at +the point of time of sending ("credentials") to messages, on request +of the receiver. This is both supported on directed and undirected +(broadcast) messages. The metadata to attach is selected at time of +the HELLO ioctl of the receiver via a flags field (see above). Note +that clients must be able to handle that messages contain more +metadata than they asked for themselves, to simplify implementation of +broadcasting in the kernel. The receiver should not rely on this data +to be around though, even though it will be correct if it happens to +be attached. In order to avoid programming errors in applications, we +recommend though not passing this data on to clients that did not +explicitly ask for it. + +Credentials may also be queried for a well-known or unique name. Use +the KDBUS_CMD_CONN_INFO for this. It will return an offset to the pool +area again, which will contain the same credential items as messages +have attached. Note that when issuing the ioctl, you can select a +different set of credentials to gather, than what was originally requested +for being attached to incoming messages. + +Credentials are always specific to the sender's domain that was +current at the time of sending, and of the process that opened the +bus connection at the time of opening it. Note that this latter data +is cached! + +POLICY + +The kernel enforces only very limited policy on names. It will not do +access filtering by userspace payload, and thus not by interface or +method name. + +This ultimately means that most fine-grained policy enforcement needs +to be done by the receiving process. We recommend using PolicyKit for +any more complex checks. However, libraries should make simple static +policy decisions regarding privileged/unprivileged method calls +easy. We recommend doing this by enabling KDBUS_ATTACH_CAPS and +KDBUS_ATTACH_CREDS for incoming messages, and then discerning client +access by some capability, or if sender and receiver UIDs match. + +BUS ADDRESSES + +When connecting to kdbus use the "kernel:" protocol prefix in DBus +address strings. The device node path is encoded in its "path=" +parameter. + +Client libraries should use the following connection string when +connecting to the system bus: + + kernel:path=/sys/fs/kdbus/0-system/bus;unix:path=/var/run/dbus/system_bus_socket + +This will ensure that kdbus is preferred over the legacy AF_UNIX +socket, but compatibility is kept. For the user bus use: + + kernel:path=/sys/fs/kdbus/$UID-user/bus;unix:path=$XDG_RUNTIME_DIR/bus + +With $UID replaced by the callers numer user ID, and $XDG_RUNTIME_DIR +following the XDG basedir spec. + +Of course the $DBUS_SYSTEM_BUS_ADDRESS and $DBUS_SESSION_BUS_ADDRESS +variables should still take precedence. + +DBUS SERVICE FILES + +Activatable services for kdbus may not use classic dbus1 service +activation files. Instead, programs should drop in native systemd +.service and .busname unit files, so that they are treated uniformly +with other types of units and activation of the system. + +Note that this results in a major difference to classic dbus1: +activatable bus names can be established at any time in the boot process. +This is unlike dbus1 where activatable names are unconditionally available +as long as dbus-daemon is running. Being able to control when +activatable names are established is essential to allow usage of kdbus +during early boot and in initrds, without the risk of triggering +services too early. + +DISCLAIMER + +This all is so far just the status quo. We are putting this together, because +we are quite confident that further API changes will be smaller, but +to make this very clear: this is all subject to change, still! + +We invite you to port over your favorite dbus library to this new +scheme, but please be prepared to make minor changes when we still +change these interfaces! diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.c new file mode 100644 index 0000000000..112769fcb6 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.c @@ -0,0 +1,156 @@ +/*** + 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 . +***/ + +#include "bus-bloom.h" +#include "siphash24.h" +#include "util.h" + +static inline void set_bit(uint64_t filter[], unsigned long b) { + filter[b >> 6] |= 1ULL << (b & 63); +} + +static const sd_id128_t hash_keys[] = { + SD_ID128_ARRAY(b9,66,0b,f0,46,70,47,c1,88,75,c4,9c,54,b9,bd,15), + SD_ID128_ARRAY(aa,a1,54,a2,e0,71,4b,39,bf,e1,dd,2e,9f,c5,4a,3b), + SD_ID128_ARRAY(63,fd,ae,be,cd,82,48,12,a1,6e,41,26,cb,fa,a0,c8), + SD_ID128_ARRAY(23,be,45,29,32,d2,46,2d,82,03,52,28,fe,37,17,f5), + SD_ID128_ARRAY(56,3b,bf,ee,5a,4f,43,39,af,aa,94,08,df,f0,fc,10), + SD_ID128_ARRAY(31,80,c8,73,c7,ea,46,d3,aa,25,75,0f,9e,4c,09,29), + SD_ID128_ARRAY(7d,f7,18,4b,7b,a4,44,d5,85,3c,06,e0,65,53,96,6d), + SD_ID128_ARRAY(f2,77,e9,6f,93,b5,4e,71,9a,0c,34,88,39,25,bf,35), +}; + +static void bloom_add_data( + uint64_t filter[], /* The filter bits */ + size_t size, /* Size of the filter in bytes */ + unsigned k, /* Number of hash functions */ + const void *data, /* Data to hash */ + size_t n) { /* Size of data to hash in bytes */ + + uint64_t h; + uint64_t m; + unsigned w, i, c = 0; + unsigned hash_index; + + assert(size > 0); + assert(k > 0); + + /* Determine bits in filter */ + m = size * 8; + + /* Determine how many bytes we need to generate a bit index 0..m for this filter */ + w = (u64log2(m) + 7) / 8; + + assert(w <= sizeof(uint64_t)); + + /* Make sure we have enough hash keys to generate m * k bits + * of hash value. Note that SipHash24 generates 64 bits of + * hash value for each 128 bits of hash key. */ + assert(k * w <= ELEMENTSOF(hash_keys) * 8); + + for (i = 0, hash_index = 0; i < k; i++) { + uint64_t p = 0; + unsigned d; + + for (d = 0; d < w; d++) { + if (c <= 0) { + h = siphash24(data, n, hash_keys[hash_index++].bytes); + c += 8; + } + + p = (p << 8ULL) | (uint64_t) ((uint8_t *)&h)[8 - c]; + c--; + } + + p &= m - 1; + set_bit(filter, p); + } + + /* log_debug("bloom: adding <%.*s>", (int) n, (char*) data); */ +} + +void bloom_add_pair(uint64_t filter[], size_t size, unsigned k, const char *a, const char *b) { + size_t n; + char *c; + + assert(filter); + assert(a); + assert(b); + + n = strlen(a) + 1 + strlen(b); + c = alloca(n + 1); + strcpy(stpcpy(stpcpy(c, a), ":"), b); + + bloom_add_data(filter, size, k, c, n); +} + +void bloom_add_prefixes(uint64_t filter[], size_t size, unsigned k, const char *a, const char *b, char sep) { + size_t n; + char *c, *p; + + assert(filter); + assert(a); + assert(b); + + n = strlen(a) + 1 + strlen(b); + c = alloca(n + 1); + + p = stpcpy(stpcpy(c, a), ":"); + strcpy(p, b); + + bloom_add_data(filter, size, k, c, n); + + for (;;) { + char *e; + + e = strrchr(p, sep); + if (!e) + break; + + *(e + 1) = 0; + bloom_add_data(filter, size, k, c, e - c + 1); + + if (e == p) + break; + + *e = 0; + bloom_add_data(filter, size, k, c, e - c); + } +} + +bool bloom_validate_parameters(size_t size, unsigned k) { + uint64_t m; + unsigned w; + + if (size <= 0) + return false; + + if (k <= 0) + return false; + + m = size * 8; + w = (u64log2(m) + 7) / 8; + if (w > sizeof(uint64_t)) + return false; + + if (k * w > ELEMENTSOF(hash_keys) * 8) + return false; + + return true; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.h new file mode 100644 index 0000000000..c824622b95 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-bloom.h @@ -0,0 +1,43 @@ +#pragma once + +/*** + 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 . +***/ + +#include +#include +#include + +/* + * Our default bloom filter has the following parameters: + * + * m=512 (bits in the filter) + * k=8 (hash functions) + * + * We use SipHash24 as hash function with a number of (originally + * randomized) but fixed hash keys. + * + */ + +#define DEFAULT_BLOOM_SIZE (512/8) /* m: filter size */ +#define DEFAULT_BLOOM_N_HASH 8 /* k: number of hash functions */ + +void bloom_add_pair(uint64_t filter[], size_t size, unsigned n_hash, const char *a, const char *b); +void bloom_add_prefixes(uint64_t filter[], size_t size, unsigned n_hash, const char *a, const char *b, char sep); + +bool bloom_validate_parameters(size_t size, unsigned n_hash); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.c new file mode 100644 index 0000000000..2d8ca47987 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.c @@ -0,0 +1,87 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include + +#include "bus-common-errors.h" +#include "bus-error.h" + +BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_UNIT, ENOENT), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_UNIT_FOR_PID, ESRCH), + SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_EXISTS, EEXIST), + SD_BUS_ERROR_MAP(BUS_ERROR_LOAD_FAILED, EIO), + SD_BUS_ERROR_MAP(BUS_ERROR_JOB_FAILED, EREMOTEIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_JOB, ENOENT), + SD_BUS_ERROR_MAP(BUS_ERROR_NOT_SUBSCRIBED, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_ALREADY_SUBSCRIBED, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_ONLY_BY_DEPENDENCY, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_JOBS_CONFLICTING, EDEADLK), + SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, EDEADLK), + SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, EDEADLK), + SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_MASKED, ESHUTDOWN), + SD_BUS_ERROR_MAP(BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, EBADR), + 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_MACHINE, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_IMAGE, ENOENT), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_MACHINE_FOR_PID, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_MACHINE_EXISTS, EEXIST), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRIVATE_NETWORKING, ENOSYS), + + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SESSION, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SESSION_FOR_PID, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_USER, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_USER_FOR_PID, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SEAT, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_SESSION_NOT_ON_SEAT, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_NOT_IN_CONTROL, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_DEVICE_IS_TAKEN, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_DEVICE_NOT_TAKEN, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_OPERATION_IN_PROGRESS, EINPROGRESS), + SD_BUS_ERROR_MAP(BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, EOPNOTSUPP), + + SD_BUS_ERROR_MAP(BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, EALREADY), + + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_PROCESS, ESRCH), + + SD_BUS_ERROR_MAP(BUS_ERROR_NO_NAME_SERVERS, ESRCH), + SD_BUS_ERROR_MAP(BUS_ERROR_INVALID_REPLY, EINVAL), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_RR, ENOENT), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_RESOURCES, ENOMEM), + SD_BUS_ERROR_MAP(BUS_ERROR_CNAME_LOOP, EDEADLK), + SD_BUS_ERROR_MAP(BUS_ERROR_ABORTED, ECANCELED), + SD_BUS_ERROR_MAP(BUS_ERROR_CONNECTION_FAILURE, ECONNREFUSED), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SERVICE, EUNATCH), + SD_BUS_ERROR_MAP(BUS_ERROR_DNSSEC_FAILED, EHOSTUNREACH), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_TRUST_ANCHOR, EHOSTUNREACH), + SD_BUS_ERROR_MAP(BUS_ERROR_RR_TYPE_UNSUPPORTED, EOPNOTSUPP), + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY), + SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN, ENETDOWN), + + SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_TRANSFER, ENXIO), + SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY), + + SD_BUS_ERROR_MAP_END +}; diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.h new file mode 100644 index 0000000000..fab8748f46 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-common-errors.h @@ -0,0 +1,86 @@ +#pragma once + +/*** + 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 . +***/ + +#include "bus-error.h" + +#define BUS_ERROR_NO_SUCH_UNIT "org.freedesktop.systemd1.NoSuchUnit" +#define BUS_ERROR_NO_UNIT_FOR_PID "org.freedesktop.systemd1.NoUnitForPID" +#define BUS_ERROR_UNIT_EXISTS "org.freedesktop.systemd1.UnitExists" +#define BUS_ERROR_LOAD_FAILED "org.freedesktop.systemd1.LoadFailed" +#define BUS_ERROR_JOB_FAILED "org.freedesktop.systemd1.JobFailed" +#define BUS_ERROR_NO_SUCH_JOB "org.freedesktop.systemd1.NoSuchJob" +#define BUS_ERROR_NOT_SUBSCRIBED "org.freedesktop.systemd1.NotSubscribed" +#define BUS_ERROR_ALREADY_SUBSCRIBED "org.freedesktop.systemd1.AlreadySubscribed" +#define BUS_ERROR_ONLY_BY_DEPENDENCY "org.freedesktop.systemd1.OnlyByDependency" +#define BUS_ERROR_TRANSACTION_JOBS_CONFLICTING "org.freedesktop.systemd1.TransactionJobsConflicting" +#define BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC "org.freedesktop.systemd1.TransactionOrderIsCyclic" +#define BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE "org.freedesktop.systemd1.TransactionIsDestructive" +#define BUS_ERROR_UNIT_MASKED "org.freedesktop.systemd1.UnitMasked" +#define BUS_ERROR_JOB_TYPE_NOT_APPLICABLE "org.freedesktop.systemd1.JobTypeNotApplicable" +#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_MACHINE "org.freedesktop.machine1.NoSuchMachine" +#define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage" +#define BUS_ERROR_NO_MACHINE_FOR_PID "org.freedesktop.machine1.NoMachineForPID" +#define BUS_ERROR_MACHINE_EXISTS "org.freedesktop.machine1.MachineExists" +#define BUS_ERROR_NO_PRIVATE_NETWORKING "org.freedesktop.machine1.NoPrivateNetworking" +#define BUS_ERROR_NO_SUCH_USER_MAPPING "org.freedesktop.machine1.NoSuchUserMapping" +#define BUS_ERROR_NO_SUCH_GROUP_MAPPING "org.freedesktop.machine1.NoSuchGroupMapping" + +#define BUS_ERROR_NO_SUCH_SESSION "org.freedesktop.login1.NoSuchSession" +#define BUS_ERROR_NO_SESSION_FOR_PID "org.freedesktop.login1.NoSessionForPID" +#define BUS_ERROR_NO_SUCH_USER "org.freedesktop.login1.NoSuchUser" +#define BUS_ERROR_NO_USER_FOR_PID "org.freedesktop.login1.NoUserForPID" +#define BUS_ERROR_NO_SUCH_SEAT "org.freedesktop.login1.NoSuchSeat" +#define BUS_ERROR_SESSION_NOT_ON_SEAT "org.freedesktop.login1.SessionNotOnSeat" +#define BUS_ERROR_NOT_IN_CONTROL "org.freedesktop.login1.NotInControl" +#define BUS_ERROR_DEVICE_IS_TAKEN "org.freedesktop.login1.DeviceIsTaken" +#define BUS_ERROR_DEVICE_NOT_TAKEN "org.freedesktop.login1.DeviceNotTaken" +#define BUS_ERROR_OPERATION_IN_PROGRESS "org.freedesktop.login1.OperationInProgress" +#define BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED "org.freedesktop.login1.SleepVerbNotSupported" +#define BUS_ERROR_SESSION_BUSY "org.freedesktop.login1.SessionBusy" + +#define BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED "org.freedesktop.timedate1.AutomaticTimeSyncEnabled" + +#define BUS_ERROR_NO_SUCH_PROCESS "org.freedesktop.systemd1.NoSuchProcess" + +#define BUS_ERROR_NO_NAME_SERVERS "org.freedesktop.resolve1.NoNameServers" +#define BUS_ERROR_INVALID_REPLY "org.freedesktop.resolve1.InvalidReply" +#define BUS_ERROR_NO_SUCH_RR "org.freedesktop.resolve1.NoSuchRR" +#define BUS_ERROR_NO_RESOURCES "org.freedesktop.resolve1.NoResources" +#define BUS_ERROR_CNAME_LOOP "org.freedesktop.resolve1.CNameLoop" +#define BUS_ERROR_ABORTED "org.freedesktop.resolve1.Aborted" +#define BUS_ERROR_CONNECTION_FAILURE "org.freedesktop.resolve1.ConnectionFailure" +#define BUS_ERROR_NO_SUCH_SERVICE "org.freedesktop.resolve1.NoSuchService" +#define BUS_ERROR_DNSSEC_FAILED "org.freedesktop.resolve1.DnssecFailed" +#define BUS_ERROR_NO_TRUST_ANCHOR "org.freedesktop.resolve1.NoTrustAnchor" +#define BUS_ERROR_RR_TYPE_UNSUPPORTED "org.freedesktop.resolve1.ResourceRecordTypeUnsupported" +#define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink" +#define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy" +#define BUS_ERROR_NETWORK_DOWN "org.freedesktop.resolve1.NetworkDown" +#define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError." + +#define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer" +#define BUS_ERROR_TRANSFER_IN_PROGRESS "org.freedesktop.import1.TransferInProgress" + +BUS_ERROR_MAP_ELF_USE(bus_common_errors); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-container.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-container.c new file mode 100644 index 0000000000..3191d27ded --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-container.c @@ -0,0 +1,277 @@ +/*** + 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 . +***/ + +#include +#include + +#include "bus-container.h" +#include "bus-internal.h" +#include "bus-socket.h" +#include "fd-util.h" +#include "process-util.h" +#include "util.h" + +int bus_container_connect_socket(sd_bus *b) { + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; + pid_t child; + siginfo_t si; + int r, error_buf = 0; + ssize_t n; + + assert(b); + assert(b->input_fd < 0); + assert(b->output_fd < 0); + assert(b->nspid > 0 || b->machine); + + if (b->nspid <= 0) { + r = container_get_leader(b->machine, &b->nspid); + if (r < 0) + return r; + } + + r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); + if (r < 0) + return r; + + b->input_fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (b->input_fd < 0) + return -errno; + + b->output_fd = b->input_fd; + + bus_socket_setup(b); + + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return -errno; + + if (child == 0) { + pid_t grandchild; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); + if (r < 0) + _exit(EXIT_FAILURE); + + /* We just changed PID namespace, however it will only + * take effect on the children we now fork. Hence, + * let's fork another time, and connect from this + * grandchild, so that SO_PEERCRED of our connection + * comes from a process from within the container, and + * not outside of it */ + + grandchild = fork(); + if (grandchild < 0) + _exit(EXIT_FAILURE); + + if (grandchild == 0) { + + r = connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size); + if (r < 0) { + /* Try to send error up */ + error_buf = errno; + (void) write(pair[1], &error_buf, sizeof(error_buf)); + _exit(EXIT_FAILURE); + } + + _exit(EXIT_SUCCESS); + } + + r = wait_for_terminate(grandchild, &si); + if (r < 0) + _exit(EXIT_FAILURE); + + if (si.si_code != CLD_EXITED) + _exit(EXIT_FAILURE); + + _exit(si.si_status); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) + return r; + + n = read(pair[0], &error_buf, sizeof(error_buf)); + if (n < 0) + return -errno; + + if (n > 0) { + if (n != sizeof(error_buf)) + return -EIO; + + if (error_buf < 0) + return -EIO; + + if (error_buf == EINPROGRESS) + return 1; + + if (error_buf > 0) + return -error_buf; + } + + if (si.si_code != CLD_EXITED) + return -EIO; + + if (si.si_status != EXIT_SUCCESS) + return -EIO; + + return bus_socket_start_auth(b); +} + +int bus_container_connect_kernel(sd_bus *b) { + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int))]; + } control = {}; + int error_buf = 0; + struct iovec iov = { + .iov_base = &error_buf, + .iov_len = sizeof(error_buf), + }; + struct msghdr mh = { + .msg_control = &control, + .msg_controllen = sizeof(control), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + struct cmsghdr *cmsg; + pid_t child; + siginfo_t si; + int r, fd = -1; + ssize_t n; + + assert(b); + assert(b->input_fd < 0); + assert(b->output_fd < 0); + assert(b->nspid > 0 || b->machine); + + if (b->nspid <= 0) { + r = container_get_leader(b->machine, &b->nspid); + if (r < 0) + return r; + } + + r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return -errno; + + if (child == 0) { + pid_t grandchild; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); + if (r < 0) + _exit(EXIT_FAILURE); + + /* We just changed PID namespace, however it will only + * take effect on the children we now fork. Hence, + * let's fork another time, and connect from this + * grandchild, so that kdbus only sees the credentials + * of this process which comes from within the + * container, and not outside of it */ + + grandchild = fork(); + if (grandchild < 0) + _exit(EXIT_FAILURE); + + if (grandchild == 0) { + fd = open(b->kernel, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) { + /* Try to send error up */ + error_buf = errno; + (void) write(pair[1], &error_buf, sizeof(error_buf)); + _exit(EXIT_FAILURE); + } + + r = send_one_fd(pair[1], fd, 0); + if (r < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + r = wait_for_terminate(grandchild, &si); + if (r < 0) + _exit(EXIT_FAILURE); + + if (si.si_code != CLD_EXITED) + _exit(EXIT_FAILURE); + + _exit(si.si_status); + } + + pair[1] = safe_close(pair[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) + return r; + + n = recvmsg(pair[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); + if (n < 0) + return -errno; + + CMSG_FOREACH(cmsg, &mh) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + int *fds; + unsigned n_fds; + + assert(fd < 0); + + fds = (int*) CMSG_DATA(cmsg); + n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + if (n_fds != 1) { + close_many(fds, n_fds); + return -EIO; + } + + fd = fds[0]; + } + } + + /* If there's an fd passed, we are good. */ + if (fd >= 0) { + b->input_fd = b->output_fd = fd; + return bus_kernel_take_fd(b); + } + + /* If there's an error passed, use it */ + if (n == sizeof(error_buf) && error_buf > 0) + return -error_buf; + + /* Otherwise, we have no clue */ + return -EIO; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-container.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-container.h new file mode 100644 index 0000000000..5cd6d15ede --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-container.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +int bus_container_connect_socket(sd_bus *b); +int bus_container_connect_kernel(sd_bus *b); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-control.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-control.c new file mode 100644 index 0000000000..9a975baa24 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-control.c @@ -0,0 +1,1586 @@ +/*** + 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 . +***/ + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-bloom.h" +#include "bus-control.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-util.h" +#include "capability-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" + +_public_ int sd_bus_get_unique_name(sd_bus *bus, const char **unique) { + int r; + + assert_return(bus, -EINVAL); + assert_return(unique, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!bus->bus_client) + return -EINVAL; + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + *unique = bus->unique_name; + return 0; +} + +static int bus_request_name_kernel(sd_bus *bus, const char *name, uint64_t flags) { + struct kdbus_cmd *n; + size_t size, l; + int r; + + assert(bus); + assert(name); + + l = strlen(name) + 1; + size = offsetof(struct kdbus_cmd, items) + KDBUS_ITEM_SIZE(l); + n = alloca0_align(size, 8); + n->size = size; + n->flags = request_name_flags_to_kdbus(flags); + + n->items[0].size = KDBUS_ITEM_HEADER_SIZE + l; + n->items[0].type = KDBUS_ITEM_NAME; + memcpy(n->items[0].str, name, l); + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(n, n->size); +#endif + + r = ioctl(bus->input_fd, KDBUS_CMD_NAME_ACQUIRE, n); + if (r < 0) + return -errno; + + if (n->return_flags & KDBUS_NAME_IN_QUEUE) + return 0; + + return 1; +} + +static int bus_request_name_dbus1(sd_bus *bus, const char *name, uint64_t flags) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + uint32_t ret, param = 0; + int r; + + assert(bus); + assert(name); + + if (flags & SD_BUS_NAME_ALLOW_REPLACEMENT) + param |= BUS_NAME_ALLOW_REPLACEMENT; + if (flags & SD_BUS_NAME_REPLACE_EXISTING) + param |= BUS_NAME_REPLACE_EXISTING; + if (!(flags & SD_BUS_NAME_QUEUE)) + param |= BUS_NAME_DO_NOT_QUEUE; + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "RequestName", + NULL, + &reply, + "su", + name, + param); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "u", &ret); + if (r < 0) + return r; + + if (ret == BUS_NAME_ALREADY_OWNER) + return -EALREADY; + else if (ret == BUS_NAME_EXISTS) + return -EEXIST; + else if (ret == BUS_NAME_IN_QUEUE) + return 0; + else if (ret == BUS_NAME_PRIMARY_OWNER) + return 1; + + return -EIO; +} + +_public_ int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) { + assert_return(bus, -EINVAL); + assert_return(name, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(!(flags & ~(SD_BUS_NAME_ALLOW_REPLACEMENT|SD_BUS_NAME_REPLACE_EXISTING|SD_BUS_NAME_QUEUE)), -EINVAL); + assert_return(service_name_is_valid(name), -EINVAL); + assert_return(name[0] != ':', -EINVAL); + + if (!bus->bus_client) + return -EINVAL; + + /* Don't allow requesting the special driver and local names */ + if (STR_IN_SET(name, "org.freedesktop.DBus", "org.freedesktop.DBus.Local")) + return -EINVAL; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->is_kernel) + return bus_request_name_kernel(bus, name, flags); + else + return bus_request_name_dbus1(bus, name, flags); +} + +static int bus_release_name_kernel(sd_bus *bus, const char *name) { + struct kdbus_cmd *n; + size_t size, l; + int r; + + assert(bus); + assert(name); + + l = strlen(name) + 1; + size = offsetof(struct kdbus_cmd, items) + KDBUS_ITEM_SIZE(l); + n = alloca0_align(size, 8); + n->size = size; + + n->items[0].size = KDBUS_ITEM_HEADER_SIZE + l; + n->items[0].type = KDBUS_ITEM_NAME; + memcpy(n->items[0].str, name, l); + +#ifdef HAVE_VALGRIND_MEMCHECK_H + VALGRIND_MAKE_MEM_DEFINED(n, n->size); +#endif + r = ioctl(bus->input_fd, KDBUS_CMD_NAME_RELEASE, n); + if (r < 0) + return -errno; + + return 0; +} + +static int bus_release_name_dbus1(sd_bus *bus, const char *name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + uint32_t ret; + int r; + + assert(bus); + assert(name); + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "ReleaseName", + NULL, + &reply, + "s", + name); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "u", &ret); + if (r < 0) + return r; + if (ret == BUS_NAME_NON_EXISTENT) + return -ESRCH; + if (ret == BUS_NAME_NOT_OWNER) + return -EADDRINUSE; + if (ret == BUS_NAME_RELEASED) + return 0; + + return -EINVAL; +} + +_public_ int sd_bus_release_name(sd_bus *bus, const char *name) { + assert_return(bus, -EINVAL); + assert_return(name, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(service_name_is_valid(name), -EINVAL); + assert_return(name[0] != ':', -EINVAL); + + if (!bus->bus_client) + return -EINVAL; + + /* Don't allow releasing the special driver and local names */ + if (STR_IN_SET(name, "org.freedesktop.DBus", "org.freedesktop.DBus.Local")) + return -EINVAL; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->is_kernel) + return bus_release_name_kernel(bus, name); + else + return bus_release_name_dbus1(bus, name); +} + +static int kernel_get_list(sd_bus *bus, uint64_t flags, char ***x) { + struct kdbus_cmd_list cmd = { + .size = sizeof(cmd), + .flags = flags, + }; + struct kdbus_info *name_list, *name; + uint64_t previous_id = 0; + int r; + + /* Caller will free half-constructed list on failure... */ + + r = ioctl(bus->input_fd, KDBUS_CMD_LIST, &cmd); + if (r < 0) + return -errno; + + name_list = (struct kdbus_info *) ((uint8_t *) bus->kdbus_buffer + cmd.offset); + + KDBUS_FOREACH(name, name_list, cmd.list_size) { + struct kdbus_item *item; + + if ((flags & KDBUS_LIST_UNIQUE) && name->id != previous_id && !(name->flags & KDBUS_HELLO_ACTIVATOR)) { + char *n; + + if (asprintf(&n, ":1.%llu", (unsigned long long) name->id) < 0) { + r = -ENOMEM; + goto fail; + } + + r = strv_consume(x, n); + if (r < 0) + goto fail; + + previous_id = name->id; + } + + KDBUS_ITEM_FOREACH(item, name, items) { + if (item->type == KDBUS_ITEM_OWNED_NAME) { + if (service_name_is_valid(item->name.name)) { + r = strv_extend(x, item->name.name); + if (r < 0) { + r = -ENOMEM; + goto fail; + } + } + } + } + } + + r = 0; + +fail: + bus_kernel_cmd_free(bus, cmd.offset); + return r; +} + +static int bus_list_names_kernel(sd_bus *bus, char ***acquired, char ***activatable) { + _cleanup_strv_free_ char **x = NULL, **y = NULL; + int r; + + if (acquired) { + r = kernel_get_list(bus, KDBUS_LIST_UNIQUE | KDBUS_LIST_NAMES, &x); + if (r < 0) + return r; + } + + if (activatable) { + r = kernel_get_list(bus, KDBUS_LIST_ACTIVATORS, &y); + if (r < 0) + return r; + + *activatable = y; + y = NULL; + } + + if (acquired) { + *acquired = x; + x = NULL; + } + + return 0; +} + +static int bus_list_names_dbus1(sd_bus *bus, char ***acquired, char ***activatable) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_strv_free_ char **x = NULL, **y = NULL; + int r; + + if (acquired) { + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "ListNames", + NULL, + &reply, + NULL); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(reply, &x); + if (r < 0) + return r; + + reply = sd_bus_message_unref(reply); + } + + if (activatable) { + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "ListActivatableNames", + NULL, + &reply, + NULL); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(reply, &y); + if (r < 0) + return r; + + *activatable = y; + y = NULL; + } + + if (acquired) { + *acquired = x; + x = NULL; + } + + return 0; +} + +_public_ int sd_bus_list_names(sd_bus *bus, char ***acquired, char ***activatable) { + assert_return(bus, -EINVAL); + assert_return(acquired || activatable, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!bus->bus_client) + return -EINVAL; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->is_kernel) + return bus_list_names_kernel(bus, acquired, activatable); + else + return bus_list_names_dbus1(bus, acquired, activatable); +} + +static int bus_populate_creds_from_items( + sd_bus *bus, + struct kdbus_info *info, + uint64_t mask, + sd_bus_creds *c) { + + struct kdbus_item *item; + uint64_t m; + int r; + + assert(bus); + assert(info); + assert(c); + + KDBUS_ITEM_FOREACH(item, info, items) { + + switch (item->type) { + + case KDBUS_ITEM_PIDS: + + if (mask & SD_BUS_CREDS_PID && item->pids.pid > 0) { + c->pid = (pid_t) item->pids.pid; + c->mask |= SD_BUS_CREDS_PID; + } + + if (mask & SD_BUS_CREDS_TID && item->pids.tid > 0) { + c->tid = (pid_t) item->pids.tid; + c->mask |= SD_BUS_CREDS_TID; + } + + if (mask & SD_BUS_CREDS_PPID) { + if (item->pids.ppid > 0) { + c->ppid = (pid_t) item->pids.ppid; + c->mask |= SD_BUS_CREDS_PPID; + } else if (item->pids.pid == 1) { + /* The structure doesn't + * really distinguish the case + * where a process has no + * parent and where we don't + * know it because it could + * not be translated due to + * namespaces. However, we + * know that PID 1 has no + * parent process, hence let's + * patch that in, manually. */ + c->ppid = 0; + c->mask |= SD_BUS_CREDS_PPID; + } + } + + break; + + case KDBUS_ITEM_CREDS: + + if (mask & SD_BUS_CREDS_UID && (uid_t) item->creds.uid != UID_INVALID) { + c->uid = (uid_t) item->creds.uid; + c->mask |= SD_BUS_CREDS_UID; + } + + if (mask & SD_BUS_CREDS_EUID && (uid_t) item->creds.euid != UID_INVALID) { + c->euid = (uid_t) item->creds.euid; + c->mask |= SD_BUS_CREDS_EUID; + } + + if (mask & SD_BUS_CREDS_SUID && (uid_t) item->creds.suid != UID_INVALID) { + c->suid = (uid_t) item->creds.suid; + c->mask |= SD_BUS_CREDS_SUID; + } + + if (mask & SD_BUS_CREDS_FSUID && (uid_t) item->creds.fsuid != UID_INVALID) { + c->fsuid = (uid_t) item->creds.fsuid; + c->mask |= SD_BUS_CREDS_FSUID; + } + + if (mask & SD_BUS_CREDS_GID && (gid_t) item->creds.gid != GID_INVALID) { + c->gid = (gid_t) item->creds.gid; + c->mask |= SD_BUS_CREDS_GID; + } + + if (mask & SD_BUS_CREDS_EGID && (gid_t) item->creds.egid != GID_INVALID) { + c->egid = (gid_t) item->creds.egid; + c->mask |= SD_BUS_CREDS_EGID; + } + + if (mask & SD_BUS_CREDS_SGID && (gid_t) item->creds.sgid != GID_INVALID) { + c->sgid = (gid_t) item->creds.sgid; + c->mask |= SD_BUS_CREDS_SGID; + } + + if (mask & SD_BUS_CREDS_FSGID && (gid_t) item->creds.fsgid != GID_INVALID) { + c->fsgid = (gid_t) item->creds.fsgid; + c->mask |= SD_BUS_CREDS_FSGID; + } + + break; + + case KDBUS_ITEM_PID_COMM: + if (mask & SD_BUS_CREDS_COMM) { + r = free_and_strdup(&c->comm, item->str); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_COMM; + } + break; + + case KDBUS_ITEM_TID_COMM: + if (mask & SD_BUS_CREDS_TID_COMM) { + r = free_and_strdup(&c->tid_comm, item->str); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_TID_COMM; + } + break; + + case KDBUS_ITEM_EXE: + if (mask & SD_BUS_CREDS_EXE) { + r = free_and_strdup(&c->exe, item->str); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_EXE; + } + break; + + case KDBUS_ITEM_CMDLINE: + if (mask & SD_BUS_CREDS_CMDLINE) { + c->cmdline_size = item->size - offsetof(struct kdbus_item, data); + c->cmdline = memdup(item->data, c->cmdline_size); + if (!c->cmdline) + return -ENOMEM; + + c->mask |= SD_BUS_CREDS_CMDLINE; + } + break; + + case KDBUS_ITEM_CGROUP: + m = (SD_BUS_CREDS_CGROUP | SD_BUS_CREDS_UNIT | + SD_BUS_CREDS_USER_UNIT | SD_BUS_CREDS_SLICE | + SD_BUS_CREDS_SESSION | SD_BUS_CREDS_OWNER_UID) & mask; + + if (m) { + r = free_and_strdup(&c->cgroup, item->str); + if (r < 0) + return r; + + r = bus_get_root_path(bus); + if (r < 0) + return r; + + r = free_and_strdup(&c->cgroup_root, bus->cgroup_root); + if (r < 0) + return r; + + c->mask |= m; + } + break; + + case KDBUS_ITEM_CAPS: + m = (SD_BUS_CREDS_EFFECTIVE_CAPS | SD_BUS_CREDS_PERMITTED_CAPS | + SD_BUS_CREDS_INHERITABLE_CAPS | SD_BUS_CREDS_BOUNDING_CAPS) & mask; + + if (m) { + if (item->caps.last_cap != cap_last_cap() || + item->size - offsetof(struct kdbus_item, caps.caps) < DIV_ROUND_UP(item->caps.last_cap, 32U) * 4 * 4) + return -EBADMSG; + + c->capability = memdup(item->caps.caps, item->size - offsetof(struct kdbus_item, caps.caps)); + if (!c->capability) + return -ENOMEM; + + c->mask |= m; + } + break; + + case KDBUS_ITEM_SECLABEL: + if (mask & SD_BUS_CREDS_SELINUX_CONTEXT) { + r = free_and_strdup(&c->label, item->str); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; + } + break; + + case KDBUS_ITEM_AUDIT: + if (mask & SD_BUS_CREDS_AUDIT_SESSION_ID) { + c->audit_session_id = (uint32_t) item->audit.sessionid; + c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; + } + + if (mask & SD_BUS_CREDS_AUDIT_LOGIN_UID) { + c->audit_login_uid = (uid_t) item->audit.loginuid; + c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; + } + break; + + case KDBUS_ITEM_OWNED_NAME: + if ((mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) && service_name_is_valid(item->name.name)) { + r = strv_extend(&c->well_known_names, item->name.name); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES; + } + break; + + case KDBUS_ITEM_CONN_DESCRIPTION: + if (mask & SD_BUS_CREDS_DESCRIPTION) { + r = free_and_strdup(&c->description, item->str); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_DESCRIPTION; + } + break; + + case KDBUS_ITEM_AUXGROUPS: + if (mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { + size_t i, n; + uid_t *g; + + n = (item->size - offsetof(struct kdbus_item, data64)) / sizeof(uint64_t); + g = new(gid_t, n); + if (!g) + return -ENOMEM; + + for (i = 0; i < n; i++) + g[i] = item->data64[i]; + + free(c->supplementary_gids); + c->supplementary_gids = g; + c->n_supplementary_gids = n; + + c->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; + } + break; + } + } + + return 0; +} + +int bus_get_name_creds_kdbus( + sd_bus *bus, + const char *name, + uint64_t mask, + bool allow_activator, + sd_bus_creds **creds) { + + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; + struct kdbus_cmd_info *cmd; + struct kdbus_info *conn_info; + size_t size, l; + uint64_t id; + int r; + + if (streq(name, "org.freedesktop.DBus")) + return -EOPNOTSUPP; + + r = bus_kernel_parse_unique_name(name, &id); + if (r < 0) + return r; + if (r > 0) { + size = offsetof(struct kdbus_cmd_info, items); + cmd = alloca0_align(size, 8); + cmd->id = id; + } else { + l = strlen(name) + 1; + size = offsetof(struct kdbus_cmd_info, items) + KDBUS_ITEM_SIZE(l); + cmd = alloca0_align(size, 8); + cmd->items[0].size = KDBUS_ITEM_HEADER_SIZE + l; + cmd->items[0].type = KDBUS_ITEM_NAME; + memcpy(cmd->items[0].str, name, l); + } + + /* If augmentation is on, and the bus didn't provide us + * the bits we want, then ask for the PID/TID so that we + * can read the rest from /proc. */ + if ((mask & SD_BUS_CREDS_AUGMENT) && + (mask & (SD_BUS_CREDS_PPID| + SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| + SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| + SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| + SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| + SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| + SD_BUS_CREDS_SELINUX_CONTEXT| + SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID))) + mask |= SD_BUS_CREDS_PID; + + cmd->size = size; + cmd->attach_flags = attach_flags_to_kdbus(mask); + + r = ioctl(bus->input_fd, KDBUS_CMD_CONN_INFO, cmd); + if (r < 0) + return -errno; + + conn_info = (struct kdbus_info *) ((uint8_t *) bus->kdbus_buffer + cmd->offset); + + /* Non-activated names are considered not available */ + if (!allow_activator && (conn_info->flags & KDBUS_HELLO_ACTIVATOR)) { + if (name[0] == ':') + r = -ENXIO; + else + r = -ESRCH; + goto fail; + } + + c = bus_creds_new(); + if (!c) { + r = -ENOMEM; + goto fail; + } + + if (mask & SD_BUS_CREDS_UNIQUE_NAME) { + if (asprintf(&c->unique_name, ":1.%llu", (unsigned long long) conn_info->id) < 0) { + r = -ENOMEM; + goto fail; + } + + c->mask |= SD_BUS_CREDS_UNIQUE_NAME; + } + + /* If KDBUS_ITEM_OWNED_NAME is requested then we'll get 0 of + them in case the service has no names. This does not mean + however that the list of owned names could not be + acquired. Hence, let's explicitly clarify that the data is + complete. */ + c->mask |= mask & SD_BUS_CREDS_WELL_KNOWN_NAMES; + + r = bus_populate_creds_from_items(bus, conn_info, mask, c); + if (r < 0) + goto fail; + + r = bus_creds_add_more(c, mask, 0, 0); + if (r < 0) + goto fail; + + if (creds) { + *creds = c; + c = NULL; + } + + r = 0; + +fail: + bus_kernel_cmd_free(bus, cmd->offset); + return r; +} + +static int bus_get_name_creds_dbus1( + sd_bus *bus, + const char *name, + uint64_t mask, + sd_bus_creds **creds) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_unique = NULL, *reply = NULL; + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; + const char *unique = NULL; + pid_t pid = 0; + int r; + + /* Only query the owner if the caller wants to know it or if + * the caller just wants to check whether a name exists */ + if ((mask & SD_BUS_CREDS_UNIQUE_NAME) || mask == 0) { + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetNameOwner", + NULL, + &reply_unique, + "s", + name); + if (r < 0) + return r; + + r = sd_bus_message_read(reply_unique, "s", &unique); + if (r < 0) + return r; + } + + if (mask != 0) { + c = bus_creds_new(); + if (!c) + return -ENOMEM; + + if ((mask & SD_BUS_CREDS_UNIQUE_NAME) && unique) { + c->unique_name = strdup(unique); + if (!c->unique_name) + return -ENOMEM; + + c->mask |= SD_BUS_CREDS_UNIQUE_NAME; + } + + if ((mask & SD_BUS_CREDS_PID) || + ((mask & SD_BUS_CREDS_AUGMENT) && + (mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| + SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| + SD_BUS_CREDS_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| + SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| + SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| + SD_BUS_CREDS_SELINUX_CONTEXT| + SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID)))) { + + uint32_t u; + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixProcessID", + NULL, + &reply, + "s", + unique ? unique : name); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "u", &u); + if (r < 0) + return r; + + pid = u; + if (mask & SD_BUS_CREDS_PID) { + c->pid = u; + c->mask |= SD_BUS_CREDS_PID; + } + + reply = sd_bus_message_unref(reply); + } + + if (mask & SD_BUS_CREDS_EUID) { + uint32_t u; + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionUnixUser", + NULL, + &reply, + "s", + unique ? unique : name); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "u", &u); + if (r < 0) + return r; + + c->euid = u; + c->mask |= SD_BUS_CREDS_EUID; + + reply = sd_bus_message_unref(reply); + } + + if (mask & SD_BUS_CREDS_SELINUX_CONTEXT) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const void *p = NULL; + size_t sz = 0; + + r = sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetConnectionSELinuxSecurityContext", + &error, + &reply, + "s", + unique ? unique : name); + if (r < 0) { + if (!sd_bus_error_has_name(&error, "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown")) + return r; + } else { + r = sd_bus_message_read_array(reply, 'y', &p, &sz); + if (r < 0) + return r; + + c->label = strndup(p, sz); + if (!c->label) + return -ENOMEM; + + c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; + } + } + + r = bus_creds_add_more(c, mask, pid, 0); + if (r < 0) + return r; + } + + if (creds) { + *creds = c; + c = NULL; + } + + return 0; +} + +_public_ int sd_bus_get_name_creds( + sd_bus *bus, + const char *name, + uint64_t mask, + sd_bus_creds **creds) { + + assert_return(bus, -EINVAL); + assert_return(name, -EINVAL); + assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); + assert_return(mask == 0 || creds, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(service_name_is_valid(name), -EINVAL); + + if (!bus->bus_client) + return -EINVAL; + + if (streq(name, "org.freedesktop.DBus.Local")) + return -EINVAL; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->is_kernel) + return bus_get_name_creds_kdbus(bus, name, mask, false, creds); + else + return bus_get_name_creds_dbus1(bus, name, mask, creds); +} + +static int bus_get_owner_creds_kdbus(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; + struct kdbus_cmd_info cmd = { + .size = sizeof(struct kdbus_cmd_info), + }; + struct kdbus_info *creator_info; + pid_t pid = 0; + int r; + + c = bus_creds_new(); + if (!c) + return -ENOMEM; + + /* If augmentation is on, and the bus doesn't didn't allow us + * to get the bits we want, then ask for the PID/TID so that we + * can read the rest from /proc. */ + if ((mask & SD_BUS_CREDS_AUGMENT) && + (mask & (SD_BUS_CREDS_PPID| + SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| + SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| + SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| + SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| + SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| + SD_BUS_CREDS_SELINUX_CONTEXT| + SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID))) + mask |= SD_BUS_CREDS_PID; + + cmd.attach_flags = attach_flags_to_kdbus(mask); + + r = ioctl(bus->input_fd, KDBUS_CMD_BUS_CREATOR_INFO, &cmd); + if (r < 0) + return -errno; + + creator_info = (struct kdbus_info *) ((uint8_t *) bus->kdbus_buffer + cmd.offset); + + r = bus_populate_creds_from_items(bus, creator_info, mask, c); + bus_kernel_cmd_free(bus, cmd.offset); + if (r < 0) + return r; + + r = bus_creds_add_more(c, mask, pid, 0); + if (r < 0) + return r; + + *ret = c; + c = NULL; + return 0; +} + +static int bus_get_owner_creds_dbus1(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; + pid_t pid = 0; + bool do_label; + int r; + + assert(bus); + + do_label = bus->label && (mask & SD_BUS_CREDS_SELINUX_CONTEXT); + + /* Avoid allocating anything if we have no chance of returning useful data */ + if (!bus->ucred_valid && !do_label) + return -ENODATA; + + c = bus_creds_new(); + if (!c) + return -ENOMEM; + + if (bus->ucred_valid) { + if (bus->ucred.pid > 0) { + pid = c->pid = bus->ucred.pid; + c->mask |= SD_BUS_CREDS_PID & mask; + } + + if (bus->ucred.uid != UID_INVALID) { + c->euid = bus->ucred.uid; + c->mask |= SD_BUS_CREDS_EUID & mask; + } + + if (bus->ucred.gid != GID_INVALID) { + c->egid = bus->ucred.gid; + c->mask |= SD_BUS_CREDS_EGID & mask; + } + } + + if (do_label) { + c->label = strdup(bus->label); + if (!c->label) + return -ENOMEM; + + c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; + } + + r = bus_creds_add_more(c, mask, pid, 0); + if (r < 0) + return r; + + *ret = c; + c = NULL; + return 0; +} + +_public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { + assert_return(bus, -EINVAL); + assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); + assert_return(ret, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->is_kernel) + return bus_get_owner_creds_kdbus(bus, mask, ret); + else + return bus_get_owner_creds_dbus1(bus, mask, ret); +} + +static int add_name_change_match(sd_bus *bus, + uint64_t cookie, + const char *name, + const char *old_owner, + const char *new_owner) { + + uint64_t name_id = KDBUS_MATCH_ID_ANY, old_owner_id = 0, new_owner_id = 0; + int is_name_id = -1, r; + struct kdbus_item *item; + + assert(bus); + + /* If we encounter a match that could match against + * NameOwnerChanged messages, then we need to create + * KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE} and + * KDBUS_ITEM_ID_{ADD,REMOVE} matches for it, possibly + * multiple if the match is underspecified. + * + * The NameOwnerChanged signals take three parameters with + * unique or well-known names, but only some forms actually + * exist: + * + * WELLKNOWN, "", UNIQUE → KDBUS_ITEM_NAME_ADD + * WELLKNOWN, UNIQUE, "" → KDBUS_ITEM_NAME_REMOVE + * WELLKNOWN, UNIQUE, UNIQUE → KDBUS_ITEM_NAME_CHANGE + * UNIQUE, "", UNIQUE → KDBUS_ITEM_ID_ADD + * UNIQUE, UNIQUE, "" → KDBUS_ITEM_ID_REMOVE + * + * For the latter two the two unique names must be identical. + * + * */ + + if (name) { + is_name_id = bus_kernel_parse_unique_name(name, &name_id); + if (is_name_id < 0) + return 0; + } + + if (!isempty(old_owner)) { + r = bus_kernel_parse_unique_name(old_owner, &old_owner_id); + if (r < 0) + return 0; + if (r == 0) + return 0; + if (is_name_id > 0 && old_owner_id != name_id) + return 0; + } else + old_owner_id = KDBUS_MATCH_ID_ANY; + + if (!isempty(new_owner)) { + r = bus_kernel_parse_unique_name(new_owner, &new_owner_id); + if (r < 0) + return r; + if (r == 0) + return 0; + if (is_name_id > 0 && new_owner_id != name_id) + return 0; + } else + new_owner_id = KDBUS_MATCH_ID_ANY; + + if (is_name_id <= 0) { + struct kdbus_cmd_match *m; + size_t sz, l; + + /* If the name argument is missing or is a well-known + * name, then add KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE} + * matches for it */ + + l = name ? strlen(name) + 1 : 0; + + sz = ALIGN8(offsetof(struct kdbus_cmd_match, items) + + offsetof(struct kdbus_item, name_change) + + offsetof(struct kdbus_notify_name_change, name) + + l); + + m = alloca0_align(sz, 8); + m->size = sz; + m->cookie = cookie; + + item = m->items; + item->size = + offsetof(struct kdbus_item, name_change) + + offsetof(struct kdbus_notify_name_change, name) + + l; + + item->name_change.old_id.id = old_owner_id; + item->name_change.new_id.id = new_owner_id; + + if (name) + memcpy(item->name_change.name, name, l); + + /* If the old name is unset or empty, then + * this can match against added names */ + if (isempty(old_owner)) { + item->type = KDBUS_ITEM_NAME_ADD; + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); + if (r < 0) + return -errno; + } + + /* If the new name is unset or empty, then + * this can match against removed names */ + if (isempty(new_owner)) { + item->type = KDBUS_ITEM_NAME_REMOVE; + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); + if (r < 0) + return -errno; + } + + /* The CHANGE match we need in either case, because + * what is reported as a name change by the kernel + * might just be an owner change between starter and + * normal clients. For userspace such a change should + * be considered a removal/addition, hence let's + * subscribe to this unconditionally. */ + item->type = KDBUS_ITEM_NAME_CHANGE; + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); + if (r < 0) + return -errno; + } + + if (is_name_id != 0) { + struct kdbus_cmd_match *m; + uint64_t sz; + + /* If the name argument is missing or is a unique + * name, then add KDBUS_ITEM_ID_{ADD,REMOVE} matches + * for it */ + + sz = ALIGN8(offsetof(struct kdbus_cmd_match, items) + + offsetof(struct kdbus_item, id_change) + + sizeof(struct kdbus_notify_id_change)); + + m = alloca0_align(sz, 8); + m->size = sz; + m->cookie = cookie; + + item = m->items; + item->size = + offsetof(struct kdbus_item, id_change) + + sizeof(struct kdbus_notify_id_change); + item->id_change.id = name_id; + + /* If the old name is unset or empty, then this can + * match against added ids */ + if (isempty(old_owner)) { + item->type = KDBUS_ITEM_ID_ADD; + if (!isempty(new_owner)) + item->id_change.id = new_owner_id; + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); + if (r < 0) + return -errno; + } + + /* If thew new name is unset or empty, then this can + * match against removed ids */ + if (isempty(new_owner)) { + item->type = KDBUS_ITEM_ID_REMOVE; + if (!isempty(old_owner)) + item->id_change.id = old_owner_id; + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); + if (r < 0) + return -errno; + } + } + + return 0; +} + +int bus_add_match_internal_kernel( + sd_bus *bus, + struct bus_match_component *components, + unsigned n_components, + uint64_t cookie) { + + struct kdbus_cmd_match *m; + struct kdbus_item *item; + uint64_t *bloom; + size_t sz; + const char *sender = NULL; + size_t sender_length = 0; + uint64_t src_id = KDBUS_MATCH_ID_ANY, dst_id = KDBUS_MATCH_ID_ANY; + bool using_bloom = false; + unsigned i; + bool matches_name_change = true; + const char *name_change_arg[3] = {}; + int r; + + assert(bus); + + /* Monitor streams don't support matches, make this a NOP */ + if (bus->hello_flags & KDBUS_HELLO_MONITOR) + return 0; + + bloom = alloca0(bus->bloom_size); + + sz = ALIGN8(offsetof(struct kdbus_cmd_match, items)); + + for (i = 0; i < n_components; i++) { + struct bus_match_component *c = &components[i]; + + switch (c->type) { + + case BUS_MATCH_SENDER: + if (!streq(c->value_str, "org.freedesktop.DBus")) + matches_name_change = false; + + r = bus_kernel_parse_unique_name(c->value_str, &src_id); + if (r < 0) + return r; + else if (r > 0) + sz += ALIGN8(offsetof(struct kdbus_item, id) + sizeof(uint64_t)); + else { + sender = c->value_str; + sender_length = strlen(sender); + sz += ALIGN8(offsetof(struct kdbus_item, str) + sender_length + 1); + } + + break; + + case BUS_MATCH_MESSAGE_TYPE: + if (c->value_u8 != SD_BUS_MESSAGE_SIGNAL) + matches_name_change = false; + + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "message-type", bus_message_type_to_string(c->value_u8)); + using_bloom = true; + break; + + case BUS_MATCH_INTERFACE: + if (!streq(c->value_str, "org.freedesktop.DBus")) + matches_name_change = false; + + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "interface", c->value_str); + using_bloom = true; + break; + + case BUS_MATCH_MEMBER: + if (!streq(c->value_str, "NameOwnerChanged")) + matches_name_change = false; + + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "member", c->value_str); + using_bloom = true; + break; + + case BUS_MATCH_PATH: + if (!streq(c->value_str, "/org/freedesktop/DBus")) + matches_name_change = false; + + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "path", c->value_str); + using_bloom = true; + break; + + case BUS_MATCH_PATH_NAMESPACE: + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "path-slash-prefix", c->value_str); + using_bloom = true; + break; + + case BUS_MATCH_ARG...BUS_MATCH_ARG_LAST: { + char buf[sizeof("arg")-1 + 2 + 1]; + + if (c->type - BUS_MATCH_ARG < 3) + name_change_arg[c->type - BUS_MATCH_ARG] = c->value_str; + + xsprintf(buf, "arg%i", c->type - BUS_MATCH_ARG); + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, buf, c->value_str); + using_bloom = true; + break; + } + + case BUS_MATCH_ARG_HAS...BUS_MATCH_ARG_HAS_LAST: { + char buf[sizeof("arg")-1 + 2 + sizeof("-has")]; + + xsprintf(buf, "arg%i-has", c->type - BUS_MATCH_ARG_HAS); + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, buf, c->value_str); + using_bloom = true; + break; + } + + case BUS_MATCH_ARG_PATH...BUS_MATCH_ARG_PATH_LAST: + /* + * XXX: DBus spec defines arg[0..63]path= matching to be + * a two-way glob. That is, if either string is a prefix + * of the other, it matches. + * This is really hard to realize in bloom-filters, as + * we would have to create a bloom-match for each prefix + * of @c->value_str. This is excessive, hence we just + * ignore all those matches and accept everything from + * the kernel. People should really avoid those matches. + * If they're used in real-life some day, we will have + * to properly support multiple-matches here. + */ + break; + + case BUS_MATCH_ARG_NAMESPACE...BUS_MATCH_ARG_NAMESPACE_LAST: { + char buf[sizeof("arg")-1 + 2 + sizeof("-dot-prefix")]; + + xsprintf(buf, "arg%i-dot-prefix", c->type - BUS_MATCH_ARG_NAMESPACE); + bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, buf, c->value_str); + using_bloom = true; + break; + } + + case BUS_MATCH_DESTINATION: + /* + * Kernel only supports matching on destination IDs, but + * not on destination names. So just skip the + * destination name restriction and verify it in + * user-space on retrieval. + */ + r = bus_kernel_parse_unique_name(c->value_str, &dst_id); + if (r < 0) + return r; + else if (r > 0) + sz += ALIGN8(offsetof(struct kdbus_item, id) + sizeof(uint64_t)); + + /* if not a broadcast, it cannot be a name-change */ + if (r <= 0 || dst_id != KDBUS_DST_ID_BROADCAST) + matches_name_change = false; + + break; + + case BUS_MATCH_ROOT: + case BUS_MATCH_VALUE: + case BUS_MATCH_LEAF: + case _BUS_MATCH_NODE_TYPE_MAX: + case _BUS_MATCH_NODE_TYPE_INVALID: + assert_not_reached("Invalid match type?"); + } + } + + if (using_bloom) + sz += ALIGN8(offsetof(struct kdbus_item, data64) + bus->bloom_size); + + m = alloca0_align(sz, 8); + m->size = sz; + m->cookie = cookie; + + item = m->items; + + if (src_id != KDBUS_MATCH_ID_ANY) { + item->size = offsetof(struct kdbus_item, id) + sizeof(uint64_t); + item->type = KDBUS_ITEM_ID; + item->id = src_id; + item = KDBUS_ITEM_NEXT(item); + } + + if (dst_id != KDBUS_MATCH_ID_ANY) { + item->size = offsetof(struct kdbus_item, id) + sizeof(uint64_t); + item->type = KDBUS_ITEM_DST_ID; + item->id = dst_id; + item = KDBUS_ITEM_NEXT(item); + } + + if (using_bloom) { + item->size = offsetof(struct kdbus_item, data64) + bus->bloom_size; + item->type = KDBUS_ITEM_BLOOM_MASK; + memcpy(item->data64, bloom, bus->bloom_size); + item = KDBUS_ITEM_NEXT(item); + } + + if (sender) { + item->size = offsetof(struct kdbus_item, str) + sender_length + 1; + item->type = KDBUS_ITEM_NAME; + memcpy(item->str, sender, sender_length + 1); + } + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); + if (r < 0) + return -errno; + + if (matches_name_change) { + + /* If this match could theoretically match + * NameOwnerChanged messages, we need to + * install a second non-bloom filter explitly + * for it */ + + r = add_name_change_match(bus, cookie, name_change_arg[0], name_change_arg[1], name_change_arg[2]); + if (r < 0) + return r; + } + + return 0; +} + +#define internal_match(bus, m) \ + ((bus)->hello_flags & KDBUS_HELLO_MONITOR \ + ? (isempty(m) ? "eavesdrop='true'" : strjoina((m), ",eavesdrop='true'")) \ + : (m)) + +static int bus_add_match_internal_dbus1( + sd_bus *bus, + const char *match) { + + const char *e; + + assert(bus); + assert(match); + + e = internal_match(bus, match); + + return sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "AddMatch", + NULL, + NULL, + "s", + e); +} + +int bus_add_match_internal( + sd_bus *bus, + const char *match, + struct bus_match_component *components, + unsigned n_components, + uint64_t cookie) { + + assert(bus); + + if (!bus->bus_client) + return -EINVAL; + + if (bus->is_kernel) + return bus_add_match_internal_kernel(bus, components, n_components, cookie); + else + return bus_add_match_internal_dbus1(bus, match); +} + +int bus_remove_match_internal_kernel( + sd_bus *bus, + uint64_t cookie) { + + struct kdbus_cmd_match m = { + .size = offsetof(struct kdbus_cmd_match, items), + .cookie = cookie, + }; + int r; + + assert(bus); + + /* Monitor streams don't support matches, make this a NOP */ + if (bus->hello_flags & KDBUS_HELLO_MONITOR) + return 0; + + r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_REMOVE, &m); + if (r < 0) + return -errno; + + return 0; +} + +static int bus_remove_match_internal_dbus1( + sd_bus *bus, + const char *match) { + + const char *e; + + assert(bus); + assert(match); + + e = internal_match(bus, match); + + return sd_bus_call_method( + bus, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "RemoveMatch", + NULL, + NULL, + "s", + e); +} + +int bus_remove_match_internal( + sd_bus *bus, + const char *match, + uint64_t cookie) { + + assert(bus); + + if (!bus->bus_client) + return -EINVAL; + + if (bus->is_kernel) + return bus_remove_match_internal_kernel(bus, cookie); + else + return bus_remove_match_internal_dbus1(bus, match); +} + +_public_ int sd_bus_get_name_machine_id(sd_bus *bus, const char *name, sd_id128_t *machine) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + const char *mid; + int r; + + assert_return(bus, -EINVAL); + assert_return(name, -EINVAL); + assert_return(machine, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(service_name_is_valid(name), -EINVAL); + + if (!bus->bus_client) + return -EINVAL; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (streq_ptr(name, bus->unique_name)) + return sd_id128_get_machine(machine); + + r = sd_bus_message_new_method_call( + bus, + &m, + name, + "/", + "org.freedesktop.DBus.Peer", + "GetMachineId"); + if (r < 0) + return r; + + r = sd_bus_message_set_auto_start(m, false); + if (r < 0) + return r; + + r = sd_bus_call(bus, m, 0, NULL, &reply); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "s", &mid); + if (r < 0) + return r; + + return sd_id128_from_string(mid, machine); +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-control.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-control.h new file mode 100644 index 0000000000..229c95efb0 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-control.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include "bus-match.h" + +int bus_add_match_internal(sd_bus *bus, const char *match, struct bus_match_component *components, unsigned n_components, uint64_t cookie); +int bus_remove_match_internal(sd_bus *bus, const char *match, uint64_t cookie); + +int bus_add_match_internal_kernel(sd_bus *bus, struct bus_match_component *components, unsigned n_components, uint64_t cookie); +int bus_remove_match_internal_kernel(sd_bus *bus, uint64_t cookie); + +int bus_get_name_creds_kdbus(sd_bus *bus, const char *name, uint64_t mask, bool allow_activator, sd_bus_creds **creds); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-convenience.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-convenience.c new file mode 100644 index 0000000000..2d06bf541f --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-convenience.c @@ -0,0 +1,626 @@ +/*** + 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 . +***/ + +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-signature.h" +#include "bus-type.h" +#include "bus-util.h" +#include "string-util.h" + +_public_ int sd_bus_emit_signal( + sd_bus *bus, + const char *path, + const char *interface, + const char *member, + const char *types, ...) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + r = sd_bus_message_new_signal(bus, &m, path, interface, member); + if (r < 0) + return r; + + if (!isempty(types)) { + va_list ap; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + return r; + } + + return sd_bus_send(bus, m, NULL); +} + +_public_ int sd_bus_call_method_async( + sd_bus *bus, + sd_bus_slot **slot, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_message_handler_t callback, + void *userdata, + const char *types, ...) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + r = sd_bus_message_new_method_call(bus, &m, destination, path, interface, member); + if (r < 0) + return r; + + if (!isempty(types)) { + va_list ap; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + return r; + } + + return sd_bus_call_async(bus, slot, m, callback, userdata, 0); +} + +_public_ int sd_bus_call_method( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + sd_bus_message **reply, + const char *types, ...) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + bus_assert_return(bus, -EINVAL, error); + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = sd_bus_message_new_method_call(bus, &m, destination, path, interface, member); + if (r < 0) + goto fail; + + if (!isempty(types)) { + va_list ap; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + goto fail; + } + + return sd_bus_call(bus, m, 0, error, reply); + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_reply_method_return( + sd_bus_message *call, + const char *types, ...) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + r = sd_bus_message_new_method_return(call, &m); + if (r < 0) + return r; + + if (!isempty(types)) { + va_list ap; + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + if (r < 0) + return r; + } + + return sd_bus_send(call->bus, m, NULL); +} + +_public_ int sd_bus_reply_method_error( + sd_bus_message *call, + const sd_bus_error *e) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(sd_bus_error_is_set(e), -EINVAL); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + r = sd_bus_message_new_method_error(call, &m, e); + if (r < 0) + return r; + + return sd_bus_send(call->bus, m, NULL); +} + +_public_ int sd_bus_reply_method_errorf( + sd_bus_message *call, + const char *name, + const char *format, + ...) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + va_list ap; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + va_start(ap, format); + bus_error_setfv(&error, name, format, ap); + va_end(ap); + + return sd_bus_reply_method_error(call, &error); +} + +_public_ int sd_bus_reply_method_errno( + sd_bus_message *call, + int error, + const sd_bus_error *p) { + + _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + if (sd_bus_error_is_set(p)) + return sd_bus_reply_method_error(call, p); + + sd_bus_error_set_errno(&berror, error); + + return sd_bus_reply_method_error(call, &berror); +} + +_public_ int sd_bus_reply_method_errnof( + sd_bus_message *call, + int error, + const char *format, + ...) { + + _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; + va_list ap; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 0; + + va_start(ap, format); + sd_bus_error_set_errnofv(&berror, error, format, ap); + va_end(ap); + + return sd_bus_reply_method_error(call, &berror); +} + +_public_ int sd_bus_get_property( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + sd_bus_message **reply, + const char *type) { + + sd_bus_message *rep = NULL; + int r; + + bus_assert_return(bus, -EINVAL, error); + bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); + bus_assert_return(member_name_is_valid(member), -EINVAL, error); + bus_assert_return(reply, -EINVAL, error); + bus_assert_return(signature_is_single(type, false), -EINVAL, error); + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &rep, "ss", strempty(interface), member); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(rep, 'v', type); + if (r < 0) { + sd_bus_message_unref(rep); + goto fail; + } + + *reply = rep; + return 0; + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_get_property_trivial( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + char type, void *ptr) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + bus_assert_return(bus, -EINVAL, error); + bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); + bus_assert_return(member_name_is_valid(member), -EINVAL, error); + bus_assert_return(bus_type_is_trivial(type), -EINVAL, error); + bus_assert_return(ptr, -EINVAL, error); + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'v', CHAR_TO_STR(type)); + if (r < 0) + goto fail; + + r = sd_bus_message_read_basic(reply, type, ptr); + if (r < 0) + goto fail; + + return 0; + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_get_property_string( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + char **ret) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *s; + char *n; + int r; + + bus_assert_return(bus, -EINVAL, error); + bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); + bus_assert_return(member_name_is_valid(member), -EINVAL, error); + bus_assert_return(ret, -EINVAL, error); + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'v', "s"); + if (r < 0) + goto fail; + + r = sd_bus_message_read_basic(reply, 's', &s); + if (r < 0) + goto fail; + + n = strdup(s); + if (!n) { + r = -ENOMEM; + goto fail; + } + + *ret = n; + return 0; + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_get_property_strv( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + char ***ret) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + bus_assert_return(bus, -EINVAL, error); + bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); + bus_assert_return(member_name_is_valid(member), -EINVAL, error); + bus_assert_return(ret, -EINVAL, error); + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'v', NULL); + if (r < 0) + goto fail; + + r = sd_bus_message_read_strv(reply, ret); + if (r < 0) + goto fail; + + return 0; + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_set_property( + sd_bus *bus, + const char *destination, + const char *path, + const char *interface, + const char *member, + sd_bus_error *error, + const char *type, ...) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + va_list ap; + int r; + + bus_assert_return(bus, -EINVAL, error); + bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); + bus_assert_return(member_name_is_valid(member), -EINVAL, error); + bus_assert_return(signature_is_single(type, false), -EINVAL, error); + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = sd_bus_message_new_method_call(bus, &m, destination, path, "org.freedesktop.DBus.Properties", "Set"); + if (r < 0) + goto fail; + + r = sd_bus_message_append(m, "ss", strempty(interface), member); + if (r < 0) + goto fail; + + r = sd_bus_message_open_container(m, 'v', type); + if (r < 0) + goto fail; + + va_start(ap, type); + r = bus_message_append_ap(m, type, ap); + va_end(ap); + if (r < 0) + goto fail; + + r = sd_bus_message_close_container(m); + if (r < 0) + goto fail; + + return sd_bus_call(bus, m, 0, error, NULL); + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_bus_creds **creds) { + sd_bus_creds *c; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + c = sd_bus_message_get_creds(call); + + /* All data we need? */ + if (c && (mask & ~c->mask) == 0) { + *creds = sd_bus_creds_ref(c); + return 0; + } + + /* No data passed? Or not enough data passed to retrieve the missing bits? */ + if (!c || !(c->mask & SD_BUS_CREDS_PID)) { + /* We couldn't read anything from the call, let's try + * to get it from the sender or peer. */ + + if (call->sender) + /* There's a sender, but the creds are + * missing. This means we are talking via + * dbus1, or are getting a message that was + * sent to us via kdbus, but was converted + * from a dbus1 message by the bus-proxy and + * thus also lacks the creds. */ + return sd_bus_get_name_creds(call->bus, call->sender, mask, creds); + else + /* There's no sender, hence we are on a dbus1 + * direct connection. For direct connections + * the credentials of the AF_UNIX peer matter, + * which may be queried via + * sd_bus_get_owner_creds(). */ + return sd_bus_get_owner_creds(call->bus, mask, creds); + } + + return bus_creds_extend_by_pid(c, mask, creds); +} + +_public_ int sd_bus_query_sender_privilege(sd_bus_message *call, int capability) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + uid_t our_uid; + bool know_caps = false; + int r; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->bus, -EINVAL); + assert_return(!bus_pid_changed(call->bus), -ECHILD); + + if (!BUS_IS_OPEN(call->bus->state)) + return -ENOTCONN; + + if (capability >= 0) { + + r = sd_bus_query_sender_creds(call, SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS, &creds); + if (r < 0) + return r; + + /* We cannot use augmented caps for authorization, + * since then data is acquired raceful from + * /proc. This can never actually happen, but let's + * better be safe than sorry, and do an extra check + * here. */ + assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EFFECTIVE_CAPS) == 0, -EPERM); + + /* Note that not even on kdbus we might have the caps + * field, due to faked identities, or namespace + * translation issues. */ + r = sd_bus_creds_has_effective_cap(creds, capability); + if (r > 0) + return 1; + if (r == 0) + know_caps = true; + } else { + r = sd_bus_query_sender_creds(call, SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID, &creds); + if (r < 0) + return r; + } + + /* Now, check the UID, but only if the capability check wasn't + * sufficient */ + our_uid = getuid(); + if (our_uid != 0 || !know_caps || capability < 0) { + uid_t sender_uid; + + /* We cannot use augmented uid/euid for authorization, + * since then data is acquired raceful from + * /proc. This can never actually happen, but let's + * better be safe than sorry, and do an extra check + * here. */ + assert_return((sd_bus_creds_get_augmented_mask(creds) & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID)) == 0, -EPERM); + + /* Try to use the EUID, if we have it. */ + r = sd_bus_creds_get_euid(creds, &sender_uid); + if (r < 0) + r = sd_bus_creds_get_uid(creds, &sender_uid); + + if (r >= 0) { + /* Sender has same UID as us, then let's grant access */ + if (sender_uid == our_uid) + return 1; + + /* Sender is root, we are not root. */ + if (our_uid != 0 && sender_uid == 0) + return 1; + } + } + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-creds.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-creds.c new file mode 100644 index 0000000000..c4f693dee9 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-creds.c @@ -0,0 +1,1349 @@ +/*** + 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 . +***/ + +#include +#include + +#include "alloc-util.h" +#include "audit-util.h" +#include "bus-creds.h" +#include "bus-label.h" +#include "bus-message.h" +#include "bus-util.h" +#include "capability-util.h" +#include "cgroup-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "hexdecoct.h" +#include "parse-util.h" +#include "process-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "user-util.h" +#include "util.h" + +enum { + CAP_OFFSET_INHERITABLE = 0, + CAP_OFFSET_PERMITTED = 1, + CAP_OFFSET_EFFECTIVE = 2, + CAP_OFFSET_BOUNDING = 3 +}; + +void bus_creds_done(sd_bus_creds *c) { + assert(c); + + /* For internal bus cred structures that are allocated by + * something else */ + + free(c->session); + free(c->unit); + free(c->user_unit); + free(c->slice); + free(c->user_slice); + free(c->unescaped_description); + free(c->supplementary_gids); + free(c->tty); + + free(c->well_known_names); /* note that this is an strv, but + * we only free the array, not the + * strings the array points to. The + * full strv we only free if + * c->allocated is set, see + * below. */ + + strv_free(c->cmdline_array); +} + +_public_ sd_bus_creds *sd_bus_creds_ref(sd_bus_creds *c) { + + if (!c) + return NULL; + + if (c->allocated) { + assert(c->n_ref > 0); + c->n_ref++; + } else { + sd_bus_message *m; + + /* If this is an embedded creds structure, then + * forward ref counting to the message */ + m = container_of(c, sd_bus_message, creds); + sd_bus_message_ref(m); + } + + return c; +} + +_public_ sd_bus_creds *sd_bus_creds_unref(sd_bus_creds *c) { + + if (!c) + return NULL; + + if (c->allocated) { + assert(c->n_ref > 0); + c->n_ref--; + + if (c->n_ref == 0) { + free(c->comm); + free(c->tid_comm); + free(c->exe); + free(c->cmdline); + free(c->cgroup); + free(c->capability); + free(c->label); + free(c->unique_name); + free(c->cgroup_root); + free(c->description); + + c->supplementary_gids = mfree(c->supplementary_gids); + + c->well_known_names = strv_free(c->well_known_names); + + bus_creds_done(c); + + free(c); + } + } else { + sd_bus_message *m; + + m = container_of(c, sd_bus_message, creds); + sd_bus_message_unref(m); + } + + + return NULL; +} + +_public_ uint64_t sd_bus_creds_get_mask(const sd_bus_creds *c) { + assert_return(c, 0); + + return c->mask; +} + +_public_ uint64_t sd_bus_creds_get_augmented_mask(const sd_bus_creds *c) { + assert_return(c, 0); + + return c->augmented; +} + +sd_bus_creds* bus_creds_new(void) { + sd_bus_creds *c; + + c = new0(sd_bus_creds, 1); + if (!c) + return NULL; + + c->allocated = true; + c->n_ref = 1; + return c; +} + +_public_ int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t mask) { + sd_bus_creds *c; + int r; + + assert_return(pid >= 0, -EINVAL); + assert_return(mask <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); + assert_return(ret, -EINVAL); + + if (pid == 0) + pid = getpid(); + + c = bus_creds_new(); + if (!c) + return -ENOMEM; + + r = bus_creds_add_more(c, mask | SD_BUS_CREDS_AUGMENT, pid, 0); + if (r < 0) { + sd_bus_creds_unref(c); + return r; + } + + /* Check if the process existed at all, in case we haven't + * figured that out already */ + if (!pid_is_alive(pid)) { + sd_bus_creds_unref(c); + return -ESRCH; + } + + *ret = c; + return 0; +} + +_public_ int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid) { + assert_return(c, -EINVAL); + assert_return(uid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_UID)) + return -ENODATA; + + *uid = c->uid; + return 0; +} + +_public_ int sd_bus_creds_get_euid(sd_bus_creds *c, uid_t *euid) { + assert_return(c, -EINVAL); + assert_return(euid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_EUID)) + return -ENODATA; + + *euid = c->euid; + return 0; +} + +_public_ int sd_bus_creds_get_suid(sd_bus_creds *c, uid_t *suid) { + assert_return(c, -EINVAL); + assert_return(suid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_SUID)) + return -ENODATA; + + *suid = c->suid; + return 0; +} + + +_public_ int sd_bus_creds_get_fsuid(sd_bus_creds *c, uid_t *fsuid) { + assert_return(c, -EINVAL); + assert_return(fsuid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_FSUID)) + return -ENODATA; + + *fsuid = c->fsuid; + return 0; +} + +_public_ int sd_bus_creds_get_gid(sd_bus_creds *c, gid_t *gid) { + assert_return(c, -EINVAL); + assert_return(gid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_GID)) + return -ENODATA; + + *gid = c->gid; + return 0; +} + +_public_ int sd_bus_creds_get_egid(sd_bus_creds *c, gid_t *egid) { + assert_return(c, -EINVAL); + assert_return(egid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_EGID)) + return -ENODATA; + + *egid = c->egid; + return 0; +} + +_public_ int sd_bus_creds_get_sgid(sd_bus_creds *c, gid_t *sgid) { + assert_return(c, -EINVAL); + assert_return(sgid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_SGID)) + return -ENODATA; + + *sgid = c->sgid; + return 0; +} + +_public_ int sd_bus_creds_get_fsgid(sd_bus_creds *c, gid_t *fsgid) { + assert_return(c, -EINVAL); + assert_return(fsgid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_FSGID)) + return -ENODATA; + + *fsgid = c->fsgid; + return 0; +} + +_public_ int sd_bus_creds_get_supplementary_gids(sd_bus_creds *c, const gid_t **gids) { + assert_return(c, -EINVAL); + assert_return(gids, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS)) + return -ENODATA; + + *gids = c->supplementary_gids; + return (int) c->n_supplementary_gids; +} + +_public_ int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid) { + assert_return(c, -EINVAL); + assert_return(pid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_PID)) + return -ENODATA; + + assert(c->pid > 0); + *pid = c->pid; + return 0; +} + +_public_ int sd_bus_creds_get_ppid(sd_bus_creds *c, pid_t *ppid) { + assert_return(c, -EINVAL); + assert_return(ppid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_PPID)) + return -ENODATA; + + /* PID 1 has no parent process. Let's distinguish the case of + * not knowing and not having a parent process by the returned + * error code. */ + if (c->ppid == 0) + return -ENXIO; + + *ppid = c->ppid; + return 0; +} + +_public_ int sd_bus_creds_get_tid(sd_bus_creds *c, pid_t *tid) { + assert_return(c, -EINVAL); + assert_return(tid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_TID)) + return -ENODATA; + + assert(c->tid > 0); + *tid = c->tid; + return 0; +} + +_public_ int sd_bus_creds_get_selinux_context(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_SELINUX_CONTEXT)) + return -ENODATA; + + assert(c->label); + *ret = c->label; + return 0; +} + +_public_ int sd_bus_creds_get_comm(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_COMM)) + return -ENODATA; + + assert(c->comm); + *ret = c->comm; + return 0; +} + +_public_ int sd_bus_creds_get_tid_comm(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_TID_COMM)) + return -ENODATA; + + assert(c->tid_comm); + *ret = c->tid_comm; + return 0; +} + +_public_ int sd_bus_creds_get_exe(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_EXE)) + return -ENODATA; + + if (!c->exe) + return -ENXIO; + + *ret = c->exe; + return 0; +} + +_public_ int sd_bus_creds_get_cgroup(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_CGROUP)) + return -ENODATA; + + assert(c->cgroup); + *ret = c->cgroup; + return 0; +} + +_public_ int sd_bus_creds_get_unit(sd_bus_creds *c, const char **ret) { + int r; + + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_UNIT)) + return -ENODATA; + + assert(c->cgroup); + + if (!c->unit) { + const char *shifted; + + r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); + if (r < 0) + return r; + + r = cg_path_get_unit(shifted, (char**) &c->unit); + if (r < 0) + return r; + } + + *ret = c->unit; + return 0; +} + +_public_ int sd_bus_creds_get_user_unit(sd_bus_creds *c, const char **ret) { + int r; + + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_USER_UNIT)) + return -ENODATA; + + assert(c->cgroup); + + if (!c->user_unit) { + const char *shifted; + + r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); + if (r < 0) + return r; + + r = cg_path_get_user_unit(shifted, (char**) &c->user_unit); + if (r < 0) + return r; + } + + *ret = c->user_unit; + return 0; +} + +_public_ int sd_bus_creds_get_slice(sd_bus_creds *c, const char **ret) { + int r; + + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_SLICE)) + return -ENODATA; + + assert(c->cgroup); + + if (!c->slice) { + const char *shifted; + + r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); + if (r < 0) + return r; + + r = cg_path_get_slice(shifted, (char**) &c->slice); + if (r < 0) + return r; + } + + *ret = c->slice; + return 0; +} + +_public_ int sd_bus_creds_get_user_slice(sd_bus_creds *c, const char **ret) { + int r; + + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_USER_SLICE)) + return -ENODATA; + + assert(c->cgroup); + + if (!c->user_slice) { + const char *shifted; + + r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); + if (r < 0) + return r; + + r = cg_path_get_user_slice(shifted, (char**) &c->user_slice); + if (r < 0) + return r; + } + + *ret = c->user_slice; + return 0; +} + +_public_ int sd_bus_creds_get_session(sd_bus_creds *c, const char **ret) { + int r; + + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_SESSION)) + return -ENODATA; + + assert(c->cgroup); + + if (!c->session) { + const char *shifted; + + r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); + if (r < 0) + return r; + + r = cg_path_get_session(shifted, (char**) &c->session); + if (r < 0) + return r; + } + + *ret = c->session; + return 0; +} + +_public_ int sd_bus_creds_get_owner_uid(sd_bus_creds *c, uid_t *uid) { + const char *shifted; + int r; + + assert_return(c, -EINVAL); + assert_return(uid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_OWNER_UID)) + return -ENODATA; + + assert(c->cgroup); + + r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); + if (r < 0) + return r; + + return cg_path_get_owner_uid(shifted, uid); +} + +_public_ int sd_bus_creds_get_cmdline(sd_bus_creds *c, char ***cmdline) { + assert_return(c, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_CMDLINE)) + return -ENODATA; + + if (!c->cmdline) + return -ENXIO; + + if (!c->cmdline_array) { + c->cmdline_array = strv_parse_nulstr(c->cmdline, c->cmdline_size); + if (!c->cmdline_array) + return -ENOMEM; + } + + *cmdline = c->cmdline_array; + return 0; +} + +_public_ int sd_bus_creds_get_audit_session_id(sd_bus_creds *c, uint32_t *sessionid) { + assert_return(c, -EINVAL); + assert_return(sessionid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_AUDIT_SESSION_ID)) + return -ENODATA; + + if (c->audit_session_id == AUDIT_SESSION_INVALID) + return -ENXIO; + + *sessionid = c->audit_session_id; + return 0; +} + +_public_ int sd_bus_creds_get_audit_login_uid(sd_bus_creds *c, uid_t *uid) { + assert_return(c, -EINVAL); + assert_return(uid, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_AUDIT_LOGIN_UID)) + return -ENODATA; + + if (c->audit_login_uid == UID_INVALID) + return -ENXIO; + + *uid = c->audit_login_uid; + return 0; +} + +_public_ int sd_bus_creds_get_tty(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_TTY)) + return -ENODATA; + + if (!c->tty) + return -ENXIO; + + *ret = c->tty; + return 0; +} + +_public_ int sd_bus_creds_get_unique_name(sd_bus_creds *c, const char **unique_name) { + assert_return(c, -EINVAL); + assert_return(unique_name, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_UNIQUE_NAME)) + return -ENODATA; + + *unique_name = c->unique_name; + return 0; +} + +_public_ int sd_bus_creds_get_well_known_names(sd_bus_creds *c, char ***well_known_names) { + assert_return(c, -EINVAL); + assert_return(well_known_names, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_WELL_KNOWN_NAMES)) + return -ENODATA; + + /* As a special hack we return the bus driver as well-known + * names list when this is requested. */ + if (c->well_known_names_driver) { + static const char* const wkn[] = { + "org.freedesktop.DBus", + NULL + }; + + *well_known_names = (char**) wkn; + return 0; + } + + if (c->well_known_names_local) { + static const char* const wkn[] = { + "org.freedesktop.DBus.Local", + NULL + }; + + *well_known_names = (char**) wkn; + return 0; + } + + *well_known_names = c->well_known_names; + return 0; +} + +_public_ int sd_bus_creds_get_description(sd_bus_creds *c, const char **ret) { + assert_return(c, -EINVAL); + assert_return(ret, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_DESCRIPTION)) + return -ENODATA; + + assert(c->description); + + if (!c->unescaped_description) { + c->unescaped_description = bus_label_unescape(c->description); + if (!c->unescaped_description) + return -ENOMEM; + } + + *ret = c->unescaped_description; + return 0; +} + +static int has_cap(sd_bus_creds *c, unsigned offset, int capability) { + size_t sz; + + assert(c); + assert(capability >= 0); + assert(c->capability); + + if ((unsigned) capability > cap_last_cap()) + return 0; + + sz = DIV_ROUND_UP(cap_last_cap(), 32U); + + return !!(c->capability[offset * sz + CAP_TO_INDEX(capability)] & CAP_TO_MASK(capability)); +} + +_public_ int sd_bus_creds_has_effective_cap(sd_bus_creds *c, int capability) { + assert_return(c, -EINVAL); + assert_return(capability >= 0, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_EFFECTIVE_CAPS)) + return -ENODATA; + + return has_cap(c, CAP_OFFSET_EFFECTIVE, capability); +} + +_public_ int sd_bus_creds_has_permitted_cap(sd_bus_creds *c, int capability) { + assert_return(c, -EINVAL); + assert_return(capability >= 0, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_PERMITTED_CAPS)) + return -ENODATA; + + return has_cap(c, CAP_OFFSET_PERMITTED, capability); +} + +_public_ int sd_bus_creds_has_inheritable_cap(sd_bus_creds *c, int capability) { + assert_return(c, -EINVAL); + assert_return(capability >= 0, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_INHERITABLE_CAPS)) + return -ENODATA; + + return has_cap(c, CAP_OFFSET_INHERITABLE, capability); +} + +_public_ int sd_bus_creds_has_bounding_cap(sd_bus_creds *c, int capability) { + assert_return(c, -EINVAL); + assert_return(capability >= 0, -EINVAL); + + if (!(c->mask & SD_BUS_CREDS_BOUNDING_CAPS)) + return -ENODATA; + + return has_cap(c, CAP_OFFSET_BOUNDING, capability); +} + +static int parse_caps(sd_bus_creds *c, unsigned offset, const char *p) { + size_t sz, max; + unsigned i, j; + + assert(c); + assert(p); + + max = DIV_ROUND_UP(cap_last_cap(), 32U); + p += strspn(p, WHITESPACE); + + sz = strlen(p); + if (sz % 8 != 0) + return -EINVAL; + + sz /= 8; + if (sz > max) + return -EINVAL; + + if (!c->capability) { + c->capability = new0(uint32_t, max * 4); + if (!c->capability) + return -ENOMEM; + } + + for (i = 0; i < sz; i ++) { + uint32_t v = 0; + + for (j = 0; j < 8; ++j) { + int t; + + t = unhexchar(*p++); + if (t < 0) + return -EINVAL; + + v = (v << 4) | t; + } + + c->capability[offset * max + (sz - i - 1)] = v; + } + + return 0; +} + +int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { + uint64_t missing; + int r; + + assert(c); + assert(c->allocated); + + if (!(mask & SD_BUS_CREDS_AUGMENT)) + return 0; + + /* Try to retrieve PID from creds if it wasn't passed to us */ + if (pid > 0) { + c->pid = pid; + c->mask |= SD_BUS_CREDS_PID; + } else if (c->mask & SD_BUS_CREDS_PID) + pid = c->pid; + else + /* Without pid we cannot do much... */ + return 0; + + /* Try to retrieve TID from creds if it wasn't passed to us */ + if (tid <= 0 && (c->mask & SD_BUS_CREDS_TID)) + tid = c->tid; + + /* Calculate what we shall and can add */ + missing = mask & ~(c->mask|SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_DESCRIPTION|SD_BUS_CREDS_AUGMENT); + if (missing == 0) + return 0; + + if (tid > 0) { + c->tid = tid; + c->mask |= SD_BUS_CREDS_TID; + } + + if (missing & (SD_BUS_CREDS_PPID | + SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_SUID | SD_BUS_CREDS_FSUID | + SD_BUS_CREDS_GID | SD_BUS_CREDS_EGID | SD_BUS_CREDS_SGID | SD_BUS_CREDS_FSGID | + SD_BUS_CREDS_SUPPLEMENTARY_GIDS | + SD_BUS_CREDS_EFFECTIVE_CAPS | SD_BUS_CREDS_INHERITABLE_CAPS | + SD_BUS_CREDS_PERMITTED_CAPS | SD_BUS_CREDS_BOUNDING_CAPS)) { + + _cleanup_fclose_ FILE *f = NULL; + const char *p; + + p = procfs_file_alloca(pid, "status"); + + f = fopen(p, "re"); + if (!f) { + if (errno == ENOENT) + return -ESRCH; + else if (errno != EPERM && errno != EACCES) + return -errno; + } else { + char line[LINE_MAX]; + + FOREACH_LINE(line, f, return -errno) { + truncate_nl(line); + + if (missing & SD_BUS_CREDS_PPID) { + p = startswith(line, "PPid:"); + if (p) { + p += strspn(p, WHITESPACE); + + /* Explicitly check for PPID 0 (which is the case for PID 1) */ + if (!streq(p, "0")) { + r = parse_pid(p, &c->ppid); + if (r < 0) + return r; + + } else + c->ppid = 0; + + c->mask |= SD_BUS_CREDS_PPID; + continue; + } + } + + if (missing & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID)) { + p = startswith(line, "Uid:"); + if (p) { + unsigned long uid, euid, suid, fsuid; + + p += strspn(p, WHITESPACE); + if (sscanf(p, "%lu %lu %lu %lu", &uid, &euid, &suid, &fsuid) != 4) + return -EIO; + + if (missing & SD_BUS_CREDS_UID) + c->uid = (uid_t) uid; + if (missing & SD_BUS_CREDS_EUID) + c->euid = (uid_t) euid; + if (missing & SD_BUS_CREDS_SUID) + c->suid = (uid_t) suid; + if (missing & SD_BUS_CREDS_FSUID) + c->fsuid = (uid_t) fsuid; + + c->mask |= missing & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID); + continue; + } + } + + if (missing & (SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID)) { + p = startswith(line, "Gid:"); + if (p) { + unsigned long gid, egid, sgid, fsgid; + + p += strspn(p, WHITESPACE); + if (sscanf(p, "%lu %lu %lu %lu", &gid, &egid, &sgid, &fsgid) != 4) + return -EIO; + + if (missing & SD_BUS_CREDS_GID) + c->gid = (gid_t) gid; + if (missing & SD_BUS_CREDS_EGID) + c->egid = (gid_t) egid; + if (missing & SD_BUS_CREDS_SGID) + c->sgid = (gid_t) sgid; + if (missing & SD_BUS_CREDS_FSGID) + c->fsgid = (gid_t) fsgid; + + c->mask |= missing & (SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID); + continue; + } + } + + if (missing & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { + p = startswith(line, "Groups:"); + if (p) { + size_t allocated = 0; + + for (;;) { + unsigned long g; + int n = 0; + + p += strspn(p, WHITESPACE); + if (*p == 0) + break; + + if (sscanf(p, "%lu%n", &g, &n) != 1) + return -EIO; + + if (!GREEDY_REALLOC(c->supplementary_gids, allocated, c->n_supplementary_gids+1)) + return -ENOMEM; + + c->supplementary_gids[c->n_supplementary_gids++] = (gid_t) g; + p += n; + } + + c->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; + continue; + } + } + + if (missing & SD_BUS_CREDS_EFFECTIVE_CAPS) { + p = startswith(line, "CapEff:"); + if (p) { + r = parse_caps(c, CAP_OFFSET_EFFECTIVE, p); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_EFFECTIVE_CAPS; + continue; + } + } + + if (missing & SD_BUS_CREDS_PERMITTED_CAPS) { + p = startswith(line, "CapPrm:"); + if (p) { + r = parse_caps(c, CAP_OFFSET_PERMITTED, p); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_PERMITTED_CAPS; + continue; + } + } + + if (missing & SD_BUS_CREDS_INHERITABLE_CAPS) { + p = startswith(line, "CapInh:"); + if (p) { + r = parse_caps(c, CAP_OFFSET_INHERITABLE, p); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_INHERITABLE_CAPS; + continue; + } + } + + if (missing & SD_BUS_CREDS_BOUNDING_CAPS) { + p = startswith(line, "CapBnd:"); + if (p) { + r = parse_caps(c, CAP_OFFSET_BOUNDING, p); + if (r < 0) + return r; + + c->mask |= SD_BUS_CREDS_BOUNDING_CAPS; + continue; + } + } + } + } + } + + if (missing & SD_BUS_CREDS_SELINUX_CONTEXT) { + const char *p; + + p = procfs_file_alloca(pid, "attr/current"); + r = read_one_line_file(p, &c->label); + if (r < 0) { + if (r != -ENOENT && r != -EINVAL && r != -EPERM && r != -EACCES) + return r; + } else + c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; + } + + if (missing & SD_BUS_CREDS_COMM) { + r = get_process_comm(pid, &c->comm); + if (r < 0) { + if (r != -EPERM && r != -EACCES) + return r; + } else + c->mask |= SD_BUS_CREDS_COMM; + } + + if (missing & SD_BUS_CREDS_EXE) { + r = get_process_exe(pid, &c->exe); + if (r == -ESRCH) { + /* Unfortunately we cannot really distinguish + * the case here where the process does not + * exist, and /proc/$PID/exe being unreadable + * because $PID is a kernel thread. Hence, + * assume it is a kernel thread, and rely on + * that this case is caught with a later + * call. */ + c->exe = NULL; + c->mask |= SD_BUS_CREDS_EXE; + } else if (r < 0) { + if (r != -EPERM && r != -EACCES) + return r; + } else + c->mask |= SD_BUS_CREDS_EXE; + } + + if (missing & SD_BUS_CREDS_CMDLINE) { + const char *p; + + p = procfs_file_alloca(pid, "cmdline"); + r = read_full_file(p, &c->cmdline, &c->cmdline_size); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) { + if (r != -EPERM && r != -EACCES) + return r; + } else { + if (c->cmdline_size == 0) + c->cmdline = mfree(c->cmdline); + + c->mask |= SD_BUS_CREDS_CMDLINE; + } + } + + if (tid > 0 && (missing & SD_BUS_CREDS_TID_COMM)) { + _cleanup_free_ char *p = NULL; + + if (asprintf(&p, "/proc/"PID_FMT"/task/"PID_FMT"/comm", pid, tid) < 0) + return -ENOMEM; + + r = read_one_line_file(p, &c->tid_comm); + if (r == -ENOENT) + return -ESRCH; + if (r < 0) { + if (r != -EPERM && r != -EACCES) + return r; + } else + c->mask |= SD_BUS_CREDS_TID_COMM; + } + + if (missing & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID)) { + + if (!c->cgroup) { + r = cg_pid_get_path(NULL, pid, &c->cgroup); + if (r < 0) { + if (r != -EPERM && r != -EACCES) + return r; + } + } + + if (!c->cgroup_root) { + r = cg_get_root_path(&c->cgroup_root); + if (r < 0) + return r; + } + + if (c->cgroup) + c->mask |= missing & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID); + } + + if (missing & SD_BUS_CREDS_AUDIT_SESSION_ID) { + r = audit_session_from_pid(pid, &c->audit_session_id); + if (r == -ENODATA) { + /* ENODATA means: no audit session id assigned */ + c->audit_session_id = AUDIT_SESSION_INVALID; + c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; + } else if (r < 0) { + if (r != -EOPNOTSUPP && r != -ENOENT && r != -EPERM && r != -EACCES) + return r; + } else + c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; + } + + if (missing & SD_BUS_CREDS_AUDIT_LOGIN_UID) { + r = audit_loginuid_from_pid(pid, &c->audit_login_uid); + if (r == -ENODATA) { + /* ENODATA means: no audit login uid assigned */ + c->audit_login_uid = UID_INVALID; + c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; + } else if (r < 0) { + if (r != -EOPNOTSUPP && r != -ENOENT && r != -EPERM && r != -EACCES) + return r; + } else + c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; + } + + if (missing & SD_BUS_CREDS_TTY) { + r = get_ctty(pid, NULL, &c->tty); + if (r == -ENXIO) { + /* ENXIO means: process has no controlling TTY */ + c->tty = NULL; + c->mask |= SD_BUS_CREDS_TTY; + } else if (r < 0) { + if (r != -EPERM && r != -EACCES && r != -ENOENT) + return r; + } else + c->mask |= SD_BUS_CREDS_TTY; + } + + /* In case only the exe path was to be read we cannot + * distinguish the case where the exe path was unreadable + * because the process was a kernel thread, or when the + * process didn't exist at all. Hence, let's do a final check, + * to be sure. */ + if (!pid_is_alive(pid)) + return -ESRCH; + + if (tid > 0 && tid != pid && !pid_is_unwaited(tid)) + return -ESRCH; + + c->augmented = missing & c->mask; + + return 0; +} + +int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *n = NULL; + int r; + + assert(c); + assert(ret); + + if ((mask & ~c->mask) == 0 || (!(mask & SD_BUS_CREDS_AUGMENT))) { + /* There's already all data we need, or augmentation + * wasn't turned on. */ + + *ret = sd_bus_creds_ref(c); + return 0; + } + + n = bus_creds_new(); + if (!n) + return -ENOMEM; + + /* Copy the original data over */ + + if (c->mask & mask & SD_BUS_CREDS_PID) { + n->pid = c->pid; + n->mask |= SD_BUS_CREDS_PID; + } + + if (c->mask & mask & SD_BUS_CREDS_TID) { + n->tid = c->tid; + n->mask |= SD_BUS_CREDS_TID; + } + + if (c->mask & mask & SD_BUS_CREDS_PPID) { + n->ppid = c->ppid; + n->mask |= SD_BUS_CREDS_PPID; + } + + if (c->mask & mask & SD_BUS_CREDS_UID) { + n->uid = c->uid; + n->mask |= SD_BUS_CREDS_UID; + } + + if (c->mask & mask & SD_BUS_CREDS_EUID) { + n->euid = c->euid; + n->mask |= SD_BUS_CREDS_EUID; + } + + if (c->mask & mask & SD_BUS_CREDS_SUID) { + n->suid = c->suid; + n->mask |= SD_BUS_CREDS_SUID; + } + + if (c->mask & mask & SD_BUS_CREDS_FSUID) { + n->fsuid = c->fsuid; + n->mask |= SD_BUS_CREDS_FSUID; + } + + if (c->mask & mask & SD_BUS_CREDS_GID) { + n->gid = c->gid; + n->mask |= SD_BUS_CREDS_GID; + } + + if (c->mask & mask & SD_BUS_CREDS_EGID) { + n->egid = c->egid; + n->mask |= SD_BUS_CREDS_EGID; + } + + if (c->mask & mask & SD_BUS_CREDS_SGID) { + n->sgid = c->sgid; + n->mask |= SD_BUS_CREDS_SGID; + } + + if (c->mask & mask & SD_BUS_CREDS_FSGID) { + n->fsgid = c->fsgid; + n->mask |= SD_BUS_CREDS_FSGID; + } + + if (c->mask & mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { + if (c->supplementary_gids) { + n->supplementary_gids = newdup(gid_t, c->supplementary_gids, c->n_supplementary_gids); + if (!n->supplementary_gids) + return -ENOMEM; + n->n_supplementary_gids = c->n_supplementary_gids; + } else { + n->supplementary_gids = NULL; + n->n_supplementary_gids = 0; + } + + n->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; + } + + if (c->mask & mask & SD_BUS_CREDS_COMM) { + assert(c->comm); + + n->comm = strdup(c->comm); + if (!n->comm) + return -ENOMEM; + + n->mask |= SD_BUS_CREDS_COMM; + } + + if (c->mask & mask & SD_BUS_CREDS_TID_COMM) { + assert(c->tid_comm); + + n->tid_comm = strdup(c->tid_comm); + if (!n->tid_comm) + return -ENOMEM; + + n->mask |= SD_BUS_CREDS_TID_COMM; + } + + if (c->mask & mask & SD_BUS_CREDS_EXE) { + if (c->exe) { + n->exe = strdup(c->exe); + if (!n->exe) + return -ENOMEM; + } else + n->exe = NULL; + + n->mask |= SD_BUS_CREDS_EXE; + } + + if (c->mask & mask & SD_BUS_CREDS_CMDLINE) { + if (c->cmdline) { + n->cmdline = memdup(c->cmdline, c->cmdline_size); + if (!n->cmdline) + return -ENOMEM; + + n->cmdline_size = c->cmdline_size; + } else { + n->cmdline = NULL; + n->cmdline_size = 0; + } + + n->mask |= SD_BUS_CREDS_CMDLINE; + } + + if (c->mask & mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_OWNER_UID)) { + assert(c->cgroup); + + n->cgroup = strdup(c->cgroup); + if (!n->cgroup) + return -ENOMEM; + + n->cgroup_root = strdup(c->cgroup_root); + if (!n->cgroup_root) + return -ENOMEM; + + n->mask |= mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_OWNER_UID); + } + + if (c->mask & mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS)) { + assert(c->capability); + + n->capability = memdup(c->capability, DIV_ROUND_UP(cap_last_cap(), 32U) * 4 * 4); + if (!n->capability) + return -ENOMEM; + + n->mask |= c->mask & mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS); + } + + if (c->mask & mask & SD_BUS_CREDS_SELINUX_CONTEXT) { + assert(c->label); + + n->label = strdup(c->label); + if (!n->label) + return -ENOMEM; + n->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; + } + + if (c->mask & mask & SD_BUS_CREDS_AUDIT_SESSION_ID) { + n->audit_session_id = c->audit_session_id; + n->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; + } + if (c->mask & mask & SD_BUS_CREDS_AUDIT_LOGIN_UID) { + n->audit_login_uid = c->audit_login_uid; + n->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; + } + + if (c->mask & mask & SD_BUS_CREDS_TTY) { + if (c->tty) { + n->tty = strdup(c->tty); + if (!n->tty) + return -ENOMEM; + } else + n->tty = NULL; + n->mask |= SD_BUS_CREDS_TTY; + } + + if (c->mask & mask & SD_BUS_CREDS_UNIQUE_NAME) { + assert(c->unique_name); + + n->unique_name = strdup(c->unique_name); + if (!n->unique_name) + return -ENOMEM; + n->mask |= SD_BUS_CREDS_UNIQUE_NAME; + } + + if (c->mask & mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) { + if (strv_isempty(c->well_known_names)) + n->well_known_names = NULL; + else { + n->well_known_names = strv_copy(c->well_known_names); + if (!n->well_known_names) + return -ENOMEM; + } + n->well_known_names_driver = c->well_known_names_driver; + n->well_known_names_local = c->well_known_names_local; + n->mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES; + } + + if (c->mask & mask & SD_BUS_CREDS_DESCRIPTION) { + assert(c->description); + n->description = strdup(c->description); + if (!n->description) + return -ENOMEM; + n->mask |= SD_BUS_CREDS_DESCRIPTION; + } + + n->augmented = c->augmented & n->mask; + + /* Get more data */ + + r = bus_creds_add_more(n, mask, 0, 0); + if (r < 0) + return r; + + *ret = n; + n = NULL; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-creds.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-creds.h new file mode 100644 index 0000000000..3e2311f91d --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-creds.h @@ -0,0 +1,90 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include + +struct sd_bus_creds { + bool allocated; + unsigned n_ref; + + uint64_t mask; + uint64_t augmented; + + uid_t uid; + uid_t euid; + uid_t suid; + uid_t fsuid; + gid_t gid; + gid_t egid; + gid_t sgid; + gid_t fsgid; + + gid_t *supplementary_gids; + unsigned n_supplementary_gids; + + pid_t ppid; + pid_t pid; + pid_t tid; + + char *comm; + char *tid_comm; + char *exe; + + char *cmdline; + size_t cmdline_size; + char **cmdline_array; + + char *cgroup; + char *session; + char *unit; + char *user_unit; + char *slice; + char *user_slice; + + char *tty; + + uint32_t *capability; + + uint32_t audit_session_id; + uid_t audit_login_uid; + + char *label; + + char *unique_name; + + char **well_known_names; + bool well_known_names_driver:1; + bool well_known_names_local:1; + + char *cgroup_root; + + char *description, *unescaped_description; +}; + +sd_bus_creds* bus_creds_new(void); + +void bus_creds_done(sd_bus_creds *c); + +int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid); + +int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-dump.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-dump.c new file mode 100644 index 0000000000..7c81e7a25d --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-dump.c @@ -0,0 +1,602 @@ +/*** + 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 . +***/ + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-type.h" +#include "cap-list.h" +#include "capability-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "locale-util.h" +#include "macro.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "util.h" + +static char *indent(unsigned level, unsigned flags) { + char *p; + unsigned n, i = 0; + + n = 0; + + if (flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY && level > 0) + level -= 1; + + if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) + n += 2; + + p = new(char, n + level*8 + 1); + if (!p) + return NULL; + + if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) { + p[i++] = ' '; + p[i++] = ' '; + } + + memset(p + i, ' ', level*8); + p[i + level*8] = 0; + + return p; +} + +int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags) { + unsigned level = 1; + int r; + + assert(m); + + if (!f) + f = stdout; + + if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) { + fprintf(f, + "%s%s%s Type=%s%s%s Endian=%c Flags=%u Version=%u Priority=%"PRIi64, + m->header->type == SD_BUS_MESSAGE_METHOD_ERROR ? ansi_highlight_red() : + m->header->type == SD_BUS_MESSAGE_METHOD_RETURN ? ansi_highlight_green() : + m->header->type != SD_BUS_MESSAGE_SIGNAL ? ansi_highlight() : "", draw_special_char(DRAW_TRIANGULAR_BULLET), ansi_normal(), + ansi_highlight(), bus_message_type_to_string(m->header->type), ansi_normal(), + m->header->endian, + m->header->flags, + m->header->version, + m->priority); + + /* Display synthetic message serial number in a more readable + * format than (uint32_t) -1 */ + if (BUS_MESSAGE_COOKIE(m) == 0xFFFFFFFFULL) + fprintf(f, " Cookie=-1"); + else + fprintf(f, " Cookie=%" PRIu64, BUS_MESSAGE_COOKIE(m)); + + if (m->reply_cookie != 0) + fprintf(f, " ReplyCookie=%" PRIu64, m->reply_cookie); + + fputs("\n", f); + + if (m->sender) + fprintf(f, " Sender=%s%s%s", ansi_highlight(), m->sender, ansi_normal()); + if (m->destination) + fprintf(f, " Destination=%s%s%s", ansi_highlight(), m->destination, ansi_normal()); + if (m->path) + fprintf(f, " Path=%s%s%s", ansi_highlight(), m->path, ansi_normal()); + if (m->interface) + fprintf(f, " Interface=%s%s%s", ansi_highlight(), m->interface, ansi_normal()); + if (m->member) + fprintf(f, " Member=%s%s%s", ansi_highlight(), m->member, ansi_normal()); + + if (m->sender || m->destination || m->path || m->interface || m->member) + fputs("\n", f); + + if (sd_bus_error_is_set(&m->error)) + fprintf(f, + " ErrorName=%s%s%s" + " ErrorMessage=%s\"%s\"%s\n", + ansi_highlight_red(), strna(m->error.name), ansi_normal(), + ansi_highlight_red(), strna(m->error.message), ansi_normal()); + + if (m->monotonic != 0) + fprintf(f, " Monotonic="USEC_FMT, m->monotonic); + if (m->realtime != 0) + fprintf(f, " Realtime="USEC_FMT, m->realtime); + if (m->seqnum != 0) + fprintf(f, " SequenceNumber=%"PRIu64, m->seqnum); + + if (m->monotonic != 0 || m->realtime != 0 || m->seqnum != 0) + fputs("\n", f); + + bus_creds_dump(&m->creds, f, true); + } + + r = sd_bus_message_rewind(m, !(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)); + if (r < 0) + return log_error_errno(r, "Failed to rewind: %m"); + + if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) { + _cleanup_free_ char *prefix = NULL; + + prefix = indent(0, flags); + if (!prefix) + return log_oom(); + + fprintf(f, "%sMESSAGE \"%s\" {\n", prefix, strempty(m->root_container.signature)); + } + + for (;;) { + _cleanup_free_ char *prefix = NULL; + const char *contents = NULL; + char type; + union { + uint8_t u8; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + uint64_t u64; + int64_t s64; + double d64; + const char *string; + int i; + } basic; + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r < 0) + return log_error_errno(r, "Failed to peek type: %m"); + + if (r == 0) { + if (level <= 1) + break; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return log_error_errno(r, "Failed to exit container: %m"); + + level--; + + prefix = indent(level, flags); + if (!prefix) + return log_oom(); + + fprintf(f, "%s};\n", prefix); + continue; + } + + prefix = indent(level, flags); + if (!prefix) + return log_oom(); + + if (bus_type_is_container(type) > 0) { + r = sd_bus_message_enter_container(m, type, contents); + if (r < 0) + return log_error_errno(r, "Failed to enter container: %m"); + + if (type == SD_BUS_TYPE_ARRAY) + fprintf(f, "%sARRAY \"%s\" {\n", prefix, contents); + else if (type == SD_BUS_TYPE_VARIANT) + fprintf(f, "%sVARIANT \"%s\" {\n", prefix, contents); + else if (type == SD_BUS_TYPE_STRUCT) + fprintf(f, "%sSTRUCT \"%s\" {\n", prefix, contents); + else if (type == SD_BUS_TYPE_DICT_ENTRY) + fprintf(f, "%sDICT_ENTRY \"%s\" {\n", prefix, contents); + + level ++; + + continue; + } + + r = sd_bus_message_read_basic(m, type, &basic); + if (r < 0) + return log_error_errno(r, "Failed to get basic: %m"); + + assert(r > 0); + + switch (type) { + + case SD_BUS_TYPE_BYTE: + fprintf(f, "%sBYTE %s%u%s;\n", prefix, ansi_highlight(), basic.u8, ansi_normal()); + break; + + case SD_BUS_TYPE_BOOLEAN: + fprintf(f, "%sBOOLEAN %s%s%s;\n", prefix, ansi_highlight(), true_false(basic.i), ansi_normal()); + break; + + case SD_BUS_TYPE_INT16: + fprintf(f, "%sINT16 %s%i%s;\n", prefix, ansi_highlight(), basic.s16, ansi_normal()); + break; + + case SD_BUS_TYPE_UINT16: + fprintf(f, "%sUINT16 %s%u%s;\n", prefix, ansi_highlight(), basic.u16, ansi_normal()); + break; + + case SD_BUS_TYPE_INT32: + fprintf(f, "%sINT32 %s%i%s;\n", prefix, ansi_highlight(), basic.s32, ansi_normal()); + break; + + case SD_BUS_TYPE_UINT32: + fprintf(f, "%sUINT32 %s%u%s;\n", prefix, ansi_highlight(), basic.u32, ansi_normal()); + break; + + case SD_BUS_TYPE_INT64: + fprintf(f, "%sINT64 %s%"PRIi64"%s;\n", prefix, ansi_highlight(), basic.s64, ansi_normal()); + break; + + case SD_BUS_TYPE_UINT64: + fprintf(f, "%sUINT64 %s%"PRIu64"%s;\n", prefix, ansi_highlight(), basic.u64, ansi_normal()); + break; + + case SD_BUS_TYPE_DOUBLE: + fprintf(f, "%sDOUBLE %s%g%s;\n", prefix, ansi_highlight(), basic.d64, ansi_normal()); + break; + + case SD_BUS_TYPE_STRING: + fprintf(f, "%sSTRING \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal()); + break; + + case SD_BUS_TYPE_OBJECT_PATH: + fprintf(f, "%sOBJECT_PATH \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal()); + break; + + case SD_BUS_TYPE_SIGNATURE: + fprintf(f, "%sSIGNATURE \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal()); + break; + + case SD_BUS_TYPE_UNIX_FD: + fprintf(f, "%sUNIX_FD %s%i%s;\n", prefix, ansi_highlight(), basic.i, ansi_normal()); + break; + + default: + assert_not_reached("Unknown basic type."); + } + } + + if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) { + _cleanup_free_ char *prefix = NULL; + + prefix = indent(0, flags); + if (!prefix) + return log_oom(); + + fprintf(f, "%s};\n\n", prefix); + } + + return 0; +} + +static void dump_capabilities( + sd_bus_creds *c, + FILE *f, + const char *name, + bool terse, + int (*has)(sd_bus_creds *c, int capability)) { + + unsigned long i, last_cap; + unsigned n = 0; + int r; + + assert(c); + assert(f); + assert(name); + assert(has); + + i = 0; + r = has(c, i); + if (r < 0) + return; + + fprintf(f, "%s%s=%s", terse ? " " : "", name, terse ? "" : ansi_highlight()); + last_cap = cap_last_cap(); + + for (;;) { + if (r > 0) { + + if (n > 0) + fputc(' ', f); + if (n % 4 == 3) + fprintf(f, terse ? "\n " : "\n "); + + fprintf(f, "%s", strna(capability_to_name(i))); + n++; + } + + i++; + + if (i > last_cap) + break; + + r = has(c, i); + } + + fputs("\n", f); + + if (!terse) + fputs(ansi_normal(), f); +} + +int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse) { + uid_t owner, audit_loginuid; + uint32_t audit_sessionid; + char **cmdline = NULL, **well_known = NULL; + const char *prefix, *color, *suffix, *s; + int r, q, v, w, z; + + assert(c); + + if (!f) + f = stdout; + + if (terse) { + prefix = " "; + suffix = ""; + color = ""; + } else { + const char *off; + + prefix = ""; + color = ansi_highlight(); + + off = ansi_normal(); + suffix = strjoina(off, "\n"); + } + + if (c->mask & SD_BUS_CREDS_PID) + fprintf(f, "%sPID=%s"PID_FMT"%s", prefix, color, c->pid, suffix); + if (c->mask & SD_BUS_CREDS_TID) + fprintf(f, "%sTID=%s"PID_FMT"%s", prefix, color, c->tid, suffix); + if (c->mask & SD_BUS_CREDS_PPID) { + if (c->ppid == 0) + fprintf(f, "%sPPID=%sn/a%s", prefix, color, suffix); + else + fprintf(f, "%sPPID=%s"PID_FMT"%s", prefix, color, c->ppid, suffix); + } + if (c->mask & SD_BUS_CREDS_TTY) + fprintf(f, "%sTTY=%s%s%s", prefix, color, strna(c->tty), suffix); + + if (terse && ((c->mask & (SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_PPID|SD_BUS_CREDS_TTY)))) + fputs("\n", f); + + if (c->mask & SD_BUS_CREDS_UID) + fprintf(f, "%sUID=%s"UID_FMT"%s", prefix, color, c->uid, suffix); + if (c->mask & SD_BUS_CREDS_EUID) + fprintf(f, "%sEUID=%s"UID_FMT"%s", prefix, color, c->euid, suffix); + if (c->mask & SD_BUS_CREDS_SUID) + fprintf(f, "%sSUID=%s"UID_FMT"%s", prefix, color, c->suid, suffix); + if (c->mask & SD_BUS_CREDS_FSUID) + fprintf(f, "%sFSUID=%s"UID_FMT"%s", prefix, color, c->fsuid, suffix); + r = sd_bus_creds_get_owner_uid(c, &owner); + if (r >= 0) + fprintf(f, "%sOwnerUID=%s"UID_FMT"%s", prefix, color, owner, suffix); + if (c->mask & SD_BUS_CREDS_GID) + fprintf(f, "%sGID=%s"GID_FMT"%s", prefix, color, c->gid, suffix); + if (c->mask & SD_BUS_CREDS_EGID) + fprintf(f, "%sEGID=%s"GID_FMT"%s", prefix, color, c->egid, suffix); + if (c->mask & SD_BUS_CREDS_SGID) + fprintf(f, "%sSGID=%s"GID_FMT"%s", prefix, color, c->sgid, suffix); + if (c->mask & SD_BUS_CREDS_FSGID) + fprintf(f, "%sFSGID=%s"GID_FMT"%s", prefix, color, c->fsgid, suffix); + + if (c->mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { + unsigned i; + + fprintf(f, "%sSupplementaryGIDs=%s", prefix, color); + for (i = 0; i < c->n_supplementary_gids; i++) + fprintf(f, "%s" GID_FMT, i > 0 ? " " : "", c->supplementary_gids[i]); + fprintf(f, "%s", suffix); + } + + if (terse && ((c->mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| + SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| + SD_BUS_CREDS_SUPPLEMENTARY_GIDS)) || r >= 0)) + fputs("\n", f); + + if (c->mask & SD_BUS_CREDS_COMM) + fprintf(f, "%sComm=%s%s%s", prefix, color, c->comm, suffix); + if (c->mask & SD_BUS_CREDS_TID_COMM) + fprintf(f, "%sTIDComm=%s%s%s", prefix, color, c->tid_comm, suffix); + if (c->mask & SD_BUS_CREDS_EXE) + fprintf(f, "%sExe=%s%s%s", prefix, color, strna(c->exe), suffix); + + if (terse && (c->mask & (SD_BUS_CREDS_EXE|SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM))) + fputs("\n", f); + + r = sd_bus_creds_get_cmdline(c, &cmdline); + if (r >= 0) { + char **i; + + fprintf(f, "%sCommandLine=%s", prefix, color); + STRV_FOREACH(i, cmdline) { + if (i != cmdline) + fputc(' ', f); + + fputs(*i, f); + } + + fprintf(f, "%s", suffix); + } else if (r != -ENODATA) + fprintf(f, "%sCommandLine=%sn/a%s", prefix, color, suffix); + + if (c->mask & SD_BUS_CREDS_SELINUX_CONTEXT) + fprintf(f, "%sLabel=%s%s%s", prefix, color, c->label, suffix); + if (c->mask & SD_BUS_CREDS_DESCRIPTION) + fprintf(f, "%sDescription=%s%s%s", prefix, color, c->description, suffix); + + if (terse && (c->mask & (SD_BUS_CREDS_SELINUX_CONTEXT|SD_BUS_CREDS_DESCRIPTION))) + fputs("\n", f); + + if (c->mask & SD_BUS_CREDS_CGROUP) + fprintf(f, "%sCGroup=%s%s%s", prefix, color, c->cgroup, suffix); + s = NULL; + r = sd_bus_creds_get_unit(c, &s); + if (r != -ENODATA) + fprintf(f, "%sUnit=%s%s%s", prefix, color, strna(s), suffix); + s = NULL; + v = sd_bus_creds_get_slice(c, &s); + if (v != -ENODATA) + fprintf(f, "%sSlice=%s%s%s", prefix, color, strna(s), suffix); + s = NULL; + q = sd_bus_creds_get_user_unit(c, &s); + if (q != -ENODATA) + fprintf(f, "%sUserUnit=%s%s%s", prefix, color, strna(s), suffix); + s = NULL; + w = sd_bus_creds_get_user_slice(c, &s); + if (w != -ENODATA) + fprintf(f, "%sUserSlice=%s%s%s", prefix, color, strna(s), suffix); + s = NULL; + z = sd_bus_creds_get_session(c, &s); + if (z != -ENODATA) + fprintf(f, "%sSession=%s%s%s", prefix, color, strna(s), suffix); + + if (terse && ((c->mask & SD_BUS_CREDS_CGROUP) || r != -ENODATA || q != -ENODATA || v != -ENODATA || w != -ENODATA || z != -ENODATA)) + fputs("\n", f); + + r = sd_bus_creds_get_audit_login_uid(c, &audit_loginuid); + if (r >= 0) + fprintf(f, "%sAuditLoginUID=%s"UID_FMT"%s", prefix, color, audit_loginuid, suffix); + else if (r != -ENODATA) + fprintf(f, "%sAuditLoginUID=%sn/a%s", prefix, color, suffix); + q = sd_bus_creds_get_audit_session_id(c, &audit_sessionid); + if (q >= 0) + fprintf(f, "%sAuditSessionID=%s%"PRIu32"%s", prefix, color, audit_sessionid, suffix); + else if (q != -ENODATA) + fprintf(f, "%sAuditSessionID=%sn/a%s", prefix, color, suffix); + + if (terse && (r != -ENODATA || q != -ENODATA)) + fputs("\n", f); + + if (c->mask & SD_BUS_CREDS_UNIQUE_NAME) + fprintf(f, "%sUniqueName=%s%s%s", prefix, color, c->unique_name, suffix); + + if (sd_bus_creds_get_well_known_names(c, &well_known) >= 0) { + char **i; + + fprintf(f, "%sWellKnownNames=%s", prefix, color); + STRV_FOREACH(i, well_known) { + if (i != well_known) + fputc(' ', f); + + fputs(*i, f); + } + + fprintf(f, "%s", suffix); + } + + if (terse && (c->mask & SD_BUS_CREDS_UNIQUE_NAME || well_known)) + fputc('\n', f); + + dump_capabilities(c, f, "EffectiveCapabilities", terse, sd_bus_creds_has_effective_cap); + dump_capabilities(c, f, "PermittedCapabilities", terse, sd_bus_creds_has_permitted_cap); + dump_capabilities(c, f, "InheritableCapabilities", terse, sd_bus_creds_has_inheritable_cap); + dump_capabilities(c, f, "BoundingCapabilities", terse, sd_bus_creds_has_bounding_cap); + + return 0; +} + +/* + * For details about the file format, see: + * + * http://wiki.wireshark.org/Development/LibpcapFileFormat + */ + +typedef struct _packed_ pcap_hdr_s { + uint32_t magic_number; /* magic number */ + uint16_t version_major; /* major version number */ + uint16_t version_minor; /* minor version number */ + int32_t thiszone; /* GMT to local correction */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets */ + uint32_t network; /* data link type */ +} pcap_hdr_t ; + +typedef struct _packed_ pcaprec_hdr_s { + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +} pcaprec_hdr_t; + +int bus_pcap_header(size_t snaplen, FILE *f) { + + pcap_hdr_t hdr = { + .magic_number = 0xa1b2c3d4U, + .version_major = 2, + .version_minor = 4, + .thiszone = 0, /* UTC */ + .sigfigs = 0, + .network = 231, /* D-Bus */ + }; + + if (!f) + f = stdout; + + assert(snaplen > 0); + assert((size_t) (uint32_t) snaplen == snaplen); + + hdr.snaplen = (uint32_t) snaplen; + + fwrite(&hdr, 1, sizeof(hdr), f); + + return fflush_and_check(f); +} + +int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f) { + struct bus_body_part *part; + pcaprec_hdr_t hdr = {}; + struct timeval tv; + unsigned i; + size_t w; + + if (!f) + f = stdout; + + assert(m); + assert(snaplen > 0); + assert((size_t) (uint32_t) snaplen == snaplen); + + if (m->realtime != 0) + timeval_store(&tv, m->realtime); + else + assert_se(gettimeofday(&tv, NULL) >= 0); + + hdr.ts_sec = tv.tv_sec; + hdr.ts_usec = tv.tv_usec; + hdr.orig_len = BUS_MESSAGE_SIZE(m); + hdr.incl_len = MIN(hdr.orig_len, snaplen); + + /* write the pcap header */ + fwrite(&hdr, 1, sizeof(hdr), f); + + /* write the dbus header */ + w = MIN(BUS_MESSAGE_BODY_BEGIN(m), snaplen); + fwrite(m->header, 1, w, f); + snaplen -= w; + + /* write the dbus body */ + MESSAGE_FOREACH_PART(part, i, m) { + if (snaplen <= 0) + break; + + w = MIN(part->size, snaplen); + fwrite(part->data, 1, w, f); + snaplen -= w; + } + + return fflush_and_check(f); +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-dump.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-dump.h new file mode 100644 index 0000000000..68fa043786 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-dump.h @@ -0,0 +1,37 @@ +#pragma once + +/*** + 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 . +***/ + +#include +#include + +#include + +enum { + BUS_MESSAGE_DUMP_WITH_HEADER = 1, + BUS_MESSAGE_DUMP_SUBTREE_ONLY = 2, +}; + +int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags); + +int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse); + +int bus_pcap_header(size_t snaplen, FILE *f); +int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-error.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-error.c new file mode 100644 index 0000000000..b6bb0c4a83 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-error.c @@ -0,0 +1,608 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "errno-list.h" +#include "string-util.h" +#include "util.h" + +BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_standard_errors[] = { + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Failed", EACCES), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoMemory", ENOMEM), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ServiceUnknown", EHOSTUNREACH), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NameHasNoOwner", ENXIO), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoReply", ETIMEDOUT), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.IOError", EIO), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.BadAddress", EADDRNOTAVAIL), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NotSupported", EOPNOTSUPP), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.LimitsExceeded", ENOBUFS), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AccessDenied", EACCES), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AuthFailed", EACCES), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InteractiveAuthorizationRequired", EACCES), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoServer", EHOSTDOWN), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Timeout", ETIMEDOUT), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoNetwork", ENONET), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AddressInUse", EADDRINUSE), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Disconnected", ECONNRESET), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidArgs", EINVAL), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileNotFound", ENOENT), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileExists", EEXIST), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownMethod", EBADR), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownObject", EBADR), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownInterface", EBADR), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownProperty", EBADR), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.PropertyReadOnly", EROFS), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnixProcessIdUnknown", ESRCH), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidSignature", EINVAL), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InconsistentMessage", EBADMSG), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.TimedOut", ETIMEDOUT), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleInvalid", EINVAL), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidFileContent", EINVAL), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleNotFound", ENOENT), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown", ESRCH), + SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ObjectPathInUse", EBUSY), + SD_BUS_ERROR_MAP_END +}; + +/* GCC maps this magically to the beginning and end of the BUS_ERROR_MAP section. + * Hide them; for currently unknown reasons they get exported to the shared libries + * even without being listed in the sym file. */ +extern const sd_bus_error_map __start_BUS_ERROR_MAP[] _hidden_; +extern const sd_bus_error_map __stop_BUS_ERROR_MAP[] _hidden_; + +/* Additional maps registered with sd_bus_error_add_map() are in this + * NULL terminated array */ +static const sd_bus_error_map **additional_error_maps = NULL; + +static int bus_error_name_to_errno(const char *name) { + const sd_bus_error_map **map, *m; + const char *p; + int r; + + if (!name) + return EINVAL; + + p = startswith(name, "System.Error."); + if (p) { + r = errno_from_name(p); + if (r < 0) + return EIO; + + return r; + } + + if (additional_error_maps) + for (map = additional_error_maps; *map; map++) + for (m = *map;; m++) { + /* For additional error maps the end marker is actually the end marker */ + if (m->code == BUS_ERROR_MAP_END_MARKER) + break; + + if (streq(m->name, name)) + return m->code; + } + + m = __start_BUS_ERROR_MAP; + while (m < __stop_BUS_ERROR_MAP) { + /* For magic ELF error maps, the end marker might + * appear in the middle of things, since multiple maps + * might appear in the same section. Hence, let's skip + * over it, but realign the pointer to the next 8 byte + * boundary, which is the selected alignment for the + * arrays. */ + if (m->code == BUS_ERROR_MAP_END_MARKER) { + m = ALIGN8_PTR(m+1); + continue; + } + + if (streq(m->name, name)) + return m->code; + + m++; + } + + return EIO; +} + +static sd_bus_error errno_to_bus_error_const(int error) { + + if (error < 0) + error = -error; + + switch (error) { + + case ENOMEM: + return BUS_ERROR_OOM; + + case EPERM: + case EACCES: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_ACCESS_DENIED, "Access denied"); + + case EINVAL: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid argument"); + + case ESRCH: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, "No such process"); + + case ENOENT: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_NOT_FOUND, "File not found"); + + case EEXIST: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_EXISTS, "File exists"); + + case ETIMEDOUT: + case ETIME: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_TIMEOUT, "Timed out"); + + case EIO: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_IO_ERROR, "Input/output error"); + + case ENETRESET: + case ECONNABORTED: + case ECONNRESET: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_DISCONNECTED, "Disconnected"); + + case EOPNOTSUPP: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NOT_SUPPORTED, "Not supported"); + + case EADDRNOTAVAIL: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_BAD_ADDRESS, "Address not available"); + + case ENOBUFS: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_LIMITS_EXCEEDED, "Limits exceeded"); + + case EADDRINUSE: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_ADDRESS_IN_USE, "Address in use"); + + case EBADMSG: + return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Inconsistent message"); + } + + return SD_BUS_ERROR_NULL; +} + +static int errno_to_bus_error_name_new(int error, char **ret) { + const char *name; + char *n; + + if (error < 0) + error = -error; + + name = errno_to_name(error); + if (!name) + return 0; + + n = strappend("System.Error.", name); + if (!n) + return -ENOMEM; + + *ret = n; + return 1; +} + +bool bus_error_is_dirty(sd_bus_error *e) { + if (!e) + return false; + + return e->name || e->message || e->_need_free != 0; +} + +_public_ void sd_bus_error_free(sd_bus_error *e) { + if (!e) + return; + + if (e->_need_free > 0) { + free((void*) e->name); + free((void*) e->message); + } + + e->name = e->message = NULL; + e->_need_free = 0; +} + +_public_ int sd_bus_error_set(sd_bus_error *e, const char *name, const char *message) { + + if (!name) + return 0; + if (!e) + goto finish; + + assert_return(!bus_error_is_dirty(e), -EINVAL); + + e->name = strdup(name); + if (!e->name) { + *e = BUS_ERROR_OOM; + return -ENOMEM; + } + + if (message) + e->message = strdup(message); + + e->_need_free = 1; + +finish: + return -bus_error_name_to_errno(name); +} + +int bus_error_setfv(sd_bus_error *e, const char *name, const char *format, va_list ap) { + + if (!name) + return 0; + + if (e) { + assert_return(!bus_error_is_dirty(e), -EINVAL); + + e->name = strdup(name); + if (!e->name) { + *e = BUS_ERROR_OOM; + return -ENOMEM; + } + + /* If we hit OOM on formatting the pretty message, we ignore + * this, since we at least managed to write the error name */ + if (format) + (void) vasprintf((char**) &e->message, format, ap); + + e->_need_free = 1; + } + + return -bus_error_name_to_errno(name); +} + +_public_ int sd_bus_error_setf(sd_bus_error *e, const char *name, const char *format, ...) { + + if (format) { + int r; + va_list ap; + + va_start(ap, format); + r = bus_error_setfv(e, name, format, ap); + va_end(ap); + + return r; + } + + return sd_bus_error_set(e, name, NULL); +} + +_public_ int sd_bus_error_copy(sd_bus_error *dest, const sd_bus_error *e) { + + if (!sd_bus_error_is_set(e)) + return 0; + if (!dest) + goto finish; + + assert_return(!bus_error_is_dirty(dest), -EINVAL); + + /* + * _need_free < 0 indicates that the error is temporarily const, needs deep copying + * _need_free == 0 indicates that the error is perpetually const, needs no deep copying + * _need_free > 0 indicates that the error is fully dynamic, needs deep copying + */ + + if (e->_need_free == 0) + *dest = *e; + else { + dest->name = strdup(e->name); + if (!dest->name) { + *dest = BUS_ERROR_OOM; + return -ENOMEM; + } + + if (e->message) + dest->message = strdup(e->message); + + dest->_need_free = 1; + } + +finish: + return -bus_error_name_to_errno(e->name); +} + +_public_ int sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message) { + if (!name) + return 0; + if (!e) + goto finish; + + assert_return(!bus_error_is_dirty(e), -EINVAL); + + *e = SD_BUS_ERROR_MAKE_CONST(name, message); + +finish: + return -bus_error_name_to_errno(name); +} + +_public_ int sd_bus_error_is_set(const sd_bus_error *e) { + if (!e) + return 0; + + return !!e->name; +} + +_public_ int sd_bus_error_has_name(const sd_bus_error *e, const char *name) { + if (!e) + return 0; + + return streq_ptr(e->name, name); +} + +_public_ int sd_bus_error_get_errno(const sd_bus_error* e) { + if (!e) + return 0; + + if (!e->name) + return 0; + + return bus_error_name_to_errno(e->name); +} + +static void bus_error_strerror(sd_bus_error *e, int error) { + size_t k = 64; + char *m; + + assert(e); + + for (;;) { + char *x; + + m = new(char, k); + if (!m) + return; + + errno = 0; + x = strerror_r(error, m, k); + if (errno == ERANGE || strlen(x) >= k - 1) { + free(m); + k *= 2; + continue; + } + + if (errno) { + free(m); + return; + } + + if (x == m) { + if (e->_need_free > 0) { + /* Error is already dynamic, let's just update the message */ + free((char*) e->message); + e->message = x; + + } else { + char *t; + /* Error was const so far, let's make it dynamic, if we can */ + + t = strdup(e->name); + if (!t) { + free(m); + return; + } + + e->_need_free = 1; + e->name = t; + e->message = x; + } + } else { + free(m); + + if (e->_need_free > 0) { + char *t; + + /* Error is dynamic, let's hence make the message also dynamic */ + t = strdup(x); + if (!t) + return; + + free((char*) e->message); + e->message = t; + } else { + /* Error is const, hence we can just override */ + e->message = x; + } + } + + return; + } +} + +_public_ int sd_bus_error_set_errno(sd_bus_error *e, int error) { + + if (error < 0) + error = -error; + + if (!e) + return -error; + if (error == 0) + return -error; + + assert_return(!bus_error_is_dirty(e), -EINVAL); + + /* First, try a const translation */ + *e = errno_to_bus_error_const(error); + + if (!sd_bus_error_is_set(e)) { + int k; + + /* If that didn't work, try a dynamic one. */ + + k = errno_to_bus_error_name_new(error, (char**) &e->name); + if (k > 0) + e->_need_free = 1; + else if (k < 0) { + *e = BUS_ERROR_OOM; + return -error; + } else + *e = BUS_ERROR_FAILED; + } + + /* Now, fill in the message from strerror() if we can */ + bus_error_strerror(e, error); + return -error; +} + +_public_ int sd_bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_list ap) { + PROTECT_ERRNO; + int r; + + if (error < 0) + error = -error; + + if (!e) + return -error; + if (error == 0) + return 0; + + assert_return(!bus_error_is_dirty(e), -EINVAL); + + /* First, try a const translation */ + *e = errno_to_bus_error_const(error); + + if (!sd_bus_error_is_set(e)) { + int k; + + /* If that didn't work, try a dynamic one */ + + k = errno_to_bus_error_name_new(error, (char**) &e->name); + if (k > 0) + e->_need_free = 1; + else if (k < 0) { + *e = BUS_ERROR_OOM; + return -ENOMEM; + } else + *e = BUS_ERROR_FAILED; + } + + if (format) { + char *m; + + /* Then, let's try to fill in the supplied message */ + + errno = error; /* Make sure that %m resolves to the specified error */ + r = vasprintf(&m, format, ap); + if (r >= 0) { + + if (e->_need_free <= 0) { + char *t; + + t = strdup(e->name); + if (t) { + e->_need_free = 1; + e->name = t; + e->message = m; + return -error; + } + + free(m); + } else { + free((char*) e->message); + e->message = m; + return -error; + } + } + } + + /* If that didn't work, use strerror() for the message */ + bus_error_strerror(e, error); + return -error; +} + +_public_ int sd_bus_error_set_errnof(sd_bus_error *e, int error, const char *format, ...) { + int r; + + if (error < 0) + error = -error; + + if (!e) + return -error; + if (error == 0) + return 0; + + assert_return(!bus_error_is_dirty(e), -EINVAL); + + if (format) { + va_list ap; + + va_start(ap, format); + r = sd_bus_error_set_errnofv(e, error, format, ap); + va_end(ap); + + return r; + } + + return sd_bus_error_set_errno(e, error); +} + +const char *bus_error_message(const sd_bus_error *e, int error) { + + if (e) { + /* Sometimes, the D-Bus server is a little bit too verbose with + * its error messages, so let's override them here */ + if (sd_bus_error_has_name(e, SD_BUS_ERROR_ACCESS_DENIED)) + return "Access denied"; + + if (e->message) + return e->message; + } + + if (error < 0) + error = -error; + + return strerror(error); +} + +static bool map_ok(const sd_bus_error_map *map) { + for (; map->code != BUS_ERROR_MAP_END_MARKER; map++) + if (!map->name || map->code <=0) + return false; + return true; +} + +_public_ int sd_bus_error_add_map(const sd_bus_error_map *map) { + const sd_bus_error_map **maps = NULL; + unsigned n = 0; + + assert_return(map, -EINVAL); + assert_return(map_ok(map), -EINVAL); + + if (additional_error_maps) + for (; additional_error_maps[n] != NULL; n++) + if (additional_error_maps[n] == map) + return 0; + + maps = realloc_multiply(additional_error_maps, sizeof(struct sd_bus_error_map*), n + 2); + if (!maps) + return -ENOMEM; + + maps[n] = map; + maps[n+1] = NULL; + + additional_error_maps = maps; + return 1; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-error.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-error.h new file mode 100644 index 0000000000..b7aedf406d --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-error.h @@ -0,0 +1,64 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include + +#include "macro.h" + +bool bus_error_is_dirty(sd_bus_error *e); + +const char *bus_error_message(const sd_bus_error *e, int error); + +int bus_error_setfv(sd_bus_error *e, const char *name, const char *format, va_list ap) _printf_(3,0); +int bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_list ap) _printf_(3,0); + +#define BUS_ERROR_OOM SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_MEMORY, "Out of memory") +#define BUS_ERROR_FAILED SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FAILED, "Operation failed") + +/* + * There are two ways to register error maps with the error translation + * logic: by using BUS_ERROR_MAP_ELF_REGISTER, which however only + * works when linked into the same ELF module, or via + * sd_bus_error_add_map() which is the official, external API, that + * works from any module. + * + * Note that BUS_ERROR_MAP_ELF_REGISTER has to be used as decorator in + * the bus error table, and BUS_ERROR_MAP_ELF_USE has to be used at + * least once per compilation unit (i.e. per library), to ensure that + * the error map is really added to the final binary. + */ + +#define BUS_ERROR_MAP_ELF_REGISTER \ + __attribute__ ((__section__("BUS_ERROR_MAP"))) \ + __attribute__ ((__used__)) \ + __attribute__ ((aligned(8))) + +#define BUS_ERROR_MAP_ELF_USE(errors) \ + extern const sd_bus_error_map errors[]; \ + __attribute__ ((used)) static const sd_bus_error_map * const CONCATENATE(errors ## _copy_, __COUNTER__) = errors; + +/* We use something exotic as end marker, to ensure people build the + * maps using the macsd-ros. */ +#define BUS_ERROR_MAP_END_MARKER -'x' + +BUS_ERROR_MAP_ELF_USE(bus_standard_errors); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.c new file mode 100644 index 0000000000..58782767fa --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.c @@ -0,0 +1,311 @@ +/*** + 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 . +***/ + +#include "bus-gvariant.h" +#include "bus-signature.h" +#include "bus-type.h" + +int bus_gvariant_get_size(const char *signature) { + const char *p; + int sum = 0, r; + + /* For fixed size structs. Fails for variable size structs. */ + + p = signature; + while (*p != 0) { + size_t n; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + else { + char t[n+1]; + + memcpy(t, p, n); + t[n] = 0; + + r = bus_gvariant_get_alignment(t); + if (r < 0) + return r; + + sum = ALIGN_TO(sum, r); + } + + switch (*p) { + + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_BYTE: + sum += 1; + break; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + sum += 2; + break; + + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_UNIX_FD: + sum += 4; + break; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + sum += 8; + break; + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + if (n == 2) { + /* unary type () has fixed size of 1 */ + r = 1; + } else { + char t[n-1]; + + memcpy(t, p + 1, n - 2); + t[n - 2] = 0; + + r = bus_gvariant_get_size(t); + if (r < 0) + return r; + } + + sum += r; + break; + } + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_ARRAY: + case SD_BUS_TYPE_VARIANT: + return -EINVAL; + + default: + assert_not_reached("Unknown signature type"); + } + + p += n; + } + + r = bus_gvariant_get_alignment(signature); + if (r < 0) + return r; + + return ALIGN_TO(sum, r); +} + +int bus_gvariant_get_alignment(const char *signature) { + size_t alignment = 1; + const char *p; + int r; + + p = signature; + while (*p != 0 && alignment < 8) { + size_t n; + int a; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + + switch (*p) { + + case SD_BUS_TYPE_BYTE: + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + a = 1; + break; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + a = 2; + break; + + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_UNIX_FD: + a = 4; + break; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + case SD_BUS_TYPE_VARIANT: + a = 8; + break; + + case SD_BUS_TYPE_ARRAY: { + char t[n]; + + memcpy(t, p + 1, n - 1); + t[n - 1] = 0; + + a = bus_gvariant_get_alignment(t); + break; + } + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + char t[n-1]; + + memcpy(t, p + 1, n - 2); + t[n - 2] = 0; + + a = bus_gvariant_get_alignment(t); + break; + } + + default: + assert_not_reached("Unknown signature type"); + } + + if (a < 0) + return a; + + assert(a > 0 && a <= 8); + if ((size_t) a > alignment) + alignment = (size_t) a; + + p += n; + } + + return alignment; +} + +int bus_gvariant_is_fixed_size(const char *signature) { + const char *p; + int r; + + assert(signature); + + p = signature; + while (*p != 0) { + size_t n; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + + switch (*p) { + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_ARRAY: + case SD_BUS_TYPE_VARIANT: + return 0; + + case SD_BUS_TYPE_BYTE: + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_UNIX_FD: + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + break; + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + char t[n-1]; + + memcpy(t, p + 1, n - 2); + t[n - 2] = 0; + + r = bus_gvariant_is_fixed_size(t); + if (r <= 0) + return r; + break; + } + + default: + assert_not_reached("Unknown signature type"); + } + + p += n; + } + + return true; +} + +size_t bus_gvariant_determine_word_size(size_t sz, size_t extra) { + if (sz + extra <= 0xFF) + return 1; + else if (sz + extra*2 <= 0xFFFF) + return 2; + else if (sz + extra*4 <= 0xFFFFFFFF) + return 4; + else + return 8; +} + +size_t bus_gvariant_read_word_le(void *p, size_t sz) { + union { + uint16_t u16; + uint32_t u32; + uint64_t u64; + } x; + + assert(p); + + if (sz == 1) + return *(uint8_t*) p; + + memcpy(&x, p, sz); + + if (sz == 2) + return le16toh(x.u16); + else if (sz == 4) + return le32toh(x.u32); + else if (sz == 8) + return le64toh(x.u64); + + assert_not_reached("unknown word width"); +} + +void bus_gvariant_write_word_le(void *p, size_t sz, size_t value) { + union { + uint16_t u16; + uint32_t u32; + uint64_t u64; + } x; + + assert(p); + assert(sz == 8 || (value < (1ULL << (sz*8)))); + + if (sz == 1) { + *(uint8_t*) p = value; + return; + } else if (sz == 2) + x.u16 = htole16((uint16_t) value); + else if (sz == 4) + x.u32 = htole32((uint32_t) value); + else if (sz == 8) + x.u64 = htole64((uint64_t) value); + else + assert_not_reached("unknown word width"); + + memcpy(p, &x, sz); +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.h new file mode 100644 index 0000000000..6da637fb05 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-gvariant.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + 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 . +***/ + +#include "macro.h" + +int bus_gvariant_get_size(const char *signature) _pure_; +int bus_gvariant_get_alignment(const char *signature) _pure_; +int bus_gvariant_is_fixed_size(const char *signature) _pure_; + +size_t bus_gvariant_determine_word_size(size_t sz, size_t extra); +void bus_gvariant_write_word_le(void *p, size_t sz, size_t value); +size_t bus_gvariant_read_word_le(void *p, size_t sz); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-internal.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-internal.c new file mode 100644 index 0000000000..caca679086 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-internal.c @@ -0,0 +1,374 @@ +/*** + 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 . +***/ + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "hexdecoct.h" +#include "string-util.h" + +bool object_path_is_valid(const char *p) { + const char *q; + bool slash; + + if (!p) + return false; + + if (p[0] != '/') + return false; + + if (p[1] == 0) + return true; + + for (slash = true, q = p+1; *q; q++) + if (*q == '/') { + if (slash) + return false; + + slash = true; + } else { + bool good; + + good = + (*q >= 'a' && *q <= 'z') || + (*q >= 'A' && *q <= 'Z') || + (*q >= '0' && *q <= '9') || + *q == '_'; + + if (!good) + return false; + + slash = false; + } + + if (slash) + return false; + + return true; +} + +char* object_path_startswith(const char *a, const char *b) { + const char *p; + + if (!object_path_is_valid(a) || + !object_path_is_valid(b)) + return NULL; + + if (streq(b, "/")) + return (char*) a + 1; + + p = startswith(a, b); + if (!p) + return NULL; + + if (*p == 0) + return (char*) p; + + if (*p == '/') + return (char*) p + 1; + + return NULL; +} + +bool interface_name_is_valid(const char *p) { + const char *q; + bool dot, found_dot = false; + + if (isempty(p)) + return false; + + for (dot = true, q = p; *q; q++) + if (*q == '.') { + if (dot) + return false; + + found_dot = dot = true; + } else { + bool good; + + good = + (*q >= 'a' && *q <= 'z') || + (*q >= 'A' && *q <= 'Z') || + (!dot && *q >= '0' && *q <= '9') || + *q == '_'; + + if (!good) + return false; + + dot = false; + } + + if (q - p > 255) + return false; + + if (dot) + return false; + + if (!found_dot) + return false; + + return true; +} + +bool service_name_is_valid(const char *p) { + const char *q; + bool dot, found_dot = false, unique; + + if (isempty(p)) + return false; + + unique = p[0] == ':'; + + for (dot = true, q = unique ? p+1 : p; *q; q++) + if (*q == '.') { + if (dot) + return false; + + found_dot = dot = true; + } else { + bool good; + + good = + (*q >= 'a' && *q <= 'z') || + (*q >= 'A' && *q <= 'Z') || + ((!dot || unique) && *q >= '0' && *q <= '9') || + *q == '_' || *q == '-'; + + if (!good) + return false; + + dot = false; + } + + if (q - p > 255) + return false; + + if (dot) + return false; + + if (!found_dot) + return false; + + return true; +} + +char* service_name_startswith(const char *a, const char *b) { + const char *p; + + if (!service_name_is_valid(a) || + !service_name_is_valid(b)) + return NULL; + + p = startswith(a, b); + if (!p) + return NULL; + + if (*p == 0) + return (char*) p; + + if (*p == '.') + return (char*) p + 1; + + return NULL; +} + +bool member_name_is_valid(const char *p) { + const char *q; + + if (isempty(p)) + return false; + + for (q = p; *q; q++) { + bool good; + + good = + (*q >= 'a' && *q <= 'z') || + (*q >= 'A' && *q <= 'Z') || + (*q >= '0' && *q <= '9') || + *q == '_'; + + if (!good) + return false; + } + + if (q - p > 255) + return false; + + return true; +} + +/* + * Complex pattern match + * This checks whether @a is a 'complex-prefix' of @b, or @b is a + * 'complex-prefix' of @a, based on strings that consist of labels with @c as + * spearator. This function returns true if: + * - both strings are equal + * - either is a prefix of the other and ends with @c + * The second rule makes sure that either string needs to be fully included in + * the other, and the string which is considered the prefix needs to end with a + * separator. + */ +static bool complex_pattern_check(char c, const char *a, const char *b) { + bool separator = false; + + if (!a && !b) + return true; + + if (!a || !b) + return false; + + for (;;) { + if (*a != *b) + return (separator && (*a == 0 || *b == 0)); + + if (*a == 0) + return true; + + separator = *a == c; + + a++, b++; + } +} + +bool namespace_complex_pattern(const char *pattern, const char *value) { + return complex_pattern_check('.', pattern, value); +} + +bool path_complex_pattern(const char *pattern, const char *value) { + return complex_pattern_check('/', pattern, value); +} + +/* + * Simple pattern match + * This checks whether @a is a 'simple-prefix' of @b, based on strings that + * consist of labels with @c as separator. This function returns true, if: + * - if @a and @b are equal + * - if @a is a prefix of @b, and the first following character in @b (or the + * last character in @a) is @c + * The second rule basically makes sure that if @a is a prefix of @b, then @b + * must follow with a new label separated by @c. It cannot extend the label. + */ +static bool simple_pattern_check(char c, const char *a, const char *b) { + bool separator = false; + + if (!a && !b) + return true; + + if (!a || !b) + return false; + + for (;;) { + if (*a != *b) + return *a == 0 && (*b == c || separator); + + if (*a == 0) + return true; + + separator = *a == c; + + a++, b++; + } +} + +bool namespace_simple_pattern(const char *pattern, const char *value) { + return simple_pattern_check('.', pattern, value); +} + +bool path_simple_pattern(const char *pattern, const char *value) { + return simple_pattern_check('/', pattern, value); +} + +int bus_message_type_from_string(const char *s, uint8_t *u) { + if (streq(s, "signal")) + *u = SD_BUS_MESSAGE_SIGNAL; + else if (streq(s, "method_call")) + *u = SD_BUS_MESSAGE_METHOD_CALL; + else if (streq(s, "error")) + *u = SD_BUS_MESSAGE_METHOD_ERROR; + else if (streq(s, "method_return")) + *u = SD_BUS_MESSAGE_METHOD_RETURN; + else + return -EINVAL; + + return 0; +} + +const char *bus_message_type_to_string(uint8_t u) { + if (u == SD_BUS_MESSAGE_SIGNAL) + return "signal"; + else if (u == SD_BUS_MESSAGE_METHOD_CALL) + return "method_call"; + else if (u == SD_BUS_MESSAGE_METHOD_ERROR) + return "error"; + else if (u == SD_BUS_MESSAGE_METHOD_RETURN) + return "method_return"; + else + return NULL; +} + +char *bus_address_escape(const char *v) { + const char *a; + char *r, *b; + + r = new(char, strlen(v)*3+1); + if (!r) + return NULL; + + for (a = v, b = r; *a; a++) { + + if ((*a >= '0' && *a <= '9') || + (*a >= 'a' && *a <= 'z') || + (*a >= 'A' && *a <= 'Z') || + strchr("_-/.", *a)) + *(b++) = *a; + else { + *(b++) = '%'; + *(b++) = hexchar(*a >> 4); + *(b++) = hexchar(*a & 0xF); + } + } + + *b = 0; + return r; +} + +int bus_maybe_reply_error(sd_bus_message *m, int r, sd_bus_error *error) { + assert(m); + + if (r < 0) { + if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) + sd_bus_reply_method_errno(m, r, error); + + } else if (sd_bus_error_is_set(error)) { + if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) + sd_bus_reply_method_error(m, error); + } else + return r; + + log_debug("Failed to process message [type=%s sender=%s path=%s interface=%s member=%s signature=%s]: %s", + bus_message_type_to_string(m->header->type), + strna(m->sender), + strna(m->path), + strna(m->interface), + strna(m->member), + strna(m->root_container.signature), + bus_error_message(error, r)); + + return 1; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-internal.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-internal.h new file mode 100644 index 0000000000..8c4c6fa772 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-internal.h @@ -0,0 +1,399 @@ +#pragma once + +/*** + 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 . +***/ + +#include +#include + +#include + +#include "bus-error.h" +#include "bus-kernel.h" +#include "bus-match.h" +#include "hashmap.h" +#include "kdbus.h" +#include "list.h" +#include "prioq.h" +#include "refcnt.h" +#include "socket-util.h" +#include "util.h" + +struct reply_callback { + sd_bus_message_handler_t callback; + usec_t timeout; + uint64_t cookie; + unsigned prioq_idx; +}; + +struct filter_callback { + sd_bus_message_handler_t callback; + + unsigned last_iteration; + + LIST_FIELDS(struct filter_callback, callbacks); +}; + +struct match_callback { + sd_bus_message_handler_t callback; + + uint64_t cookie; + unsigned last_iteration; + + char *match_string; + + struct bus_match_node *match_node; +}; + +struct node { + char *path; + struct node *parent; + LIST_HEAD(struct node, child); + LIST_FIELDS(struct node, siblings); + + LIST_HEAD(struct node_callback, callbacks); + LIST_HEAD(struct node_vtable, vtables); + LIST_HEAD(struct node_enumerator, enumerators); + LIST_HEAD(struct node_object_manager, object_managers); +}; + +struct node_callback { + struct node *node; + + bool is_fallback; + sd_bus_message_handler_t callback; + + unsigned last_iteration; + + LIST_FIELDS(struct node_callback, callbacks); +}; + +struct node_enumerator { + struct node *node; + + sd_bus_node_enumerator_t callback; + + unsigned last_iteration; + + LIST_FIELDS(struct node_enumerator, enumerators); +}; + +struct node_object_manager { + struct node *node; + + LIST_FIELDS(struct node_object_manager, object_managers); +}; + +struct node_vtable { + struct node *node; + + char *interface; + bool is_fallback; + const sd_bus_vtable *vtable; + sd_bus_object_find_t find; + + unsigned last_iteration; + + LIST_FIELDS(struct node_vtable, vtables); +}; + +struct vtable_member { + const char *path; + const char *interface; + const char *member; + struct node_vtable *parent; + unsigned last_iteration; + const sd_bus_vtable *vtable; +}; + +typedef enum BusSlotType { + BUS_REPLY_CALLBACK, + BUS_FILTER_CALLBACK, + BUS_MATCH_CALLBACK, + BUS_NODE_CALLBACK, + BUS_NODE_ENUMERATOR, + BUS_NODE_VTABLE, + BUS_NODE_OBJECT_MANAGER, + _BUS_SLOT_INVALID = -1, +} BusSlotType; + +struct sd_bus_slot { + unsigned n_ref; + sd_bus *bus; + void *userdata; + BusSlotType type:5; + bool floating:1; + bool match_added:1; + char *description; + + LIST_FIELDS(sd_bus_slot, slots); + + union { + struct reply_callback reply_callback; + struct filter_callback filter_callback; + struct match_callback match_callback; + struct node_callback node_callback; + struct node_enumerator node_enumerator; + struct node_object_manager node_object_manager; + struct node_vtable node_vtable; + }; +}; + +enum bus_state { + BUS_UNSET, + BUS_OPENING, + BUS_AUTHENTICATING, + BUS_HELLO, + BUS_RUNNING, + BUS_CLOSING, + BUS_CLOSED +}; + +static inline bool BUS_IS_OPEN(enum bus_state state) { + return state > BUS_UNSET && state < BUS_CLOSING; +} + +enum bus_auth { + _BUS_AUTH_INVALID, + BUS_AUTH_EXTERNAL, + BUS_AUTH_ANONYMOUS +}; + +struct sd_bus { + /* We use atomic ref counting here since sd_bus_message + objects retain references to their originating sd_bus but + we want to allow them to be processed in a different + thread. We won't provide full thread safety, but only the + bare minimum that makes it possible to use sd_bus and + sd_bus_message objects independently and on different + threads as long as each object is used only once at the + same time. */ + RefCount n_ref; + + enum bus_state state; + int input_fd, output_fd; + int message_version; + int message_endian; + + bool is_kernel:1; + bool can_fds:1; + bool bus_client:1; + bool ucred_valid:1; + bool is_server:1; + bool anonymous_auth:1; + bool prefer_readv:1; + bool prefer_writev:1; + bool match_callbacks_modified:1; + bool filter_callbacks_modified:1; + bool nodes_modified:1; + bool trusted:1; + bool fake_creds_valid:1; + bool fake_pids_valid:1; + bool manual_peer_interface:1; + bool is_system:1; + bool is_user:1; + bool allow_interactive_authorization:1; + + int use_memfd; + + void *rbuffer; + size_t rbuffer_size; + + sd_bus_message **rqueue; + unsigned rqueue_size; + size_t rqueue_allocated; + + sd_bus_message **wqueue; + unsigned wqueue_size; + size_t windex; + size_t wqueue_allocated; + + uint64_t cookie; + + char *unique_name; + uint64_t unique_id; + + struct bus_match_node match_callbacks; + Prioq *reply_callbacks_prioq; + OrderedHashmap *reply_callbacks; + LIST_HEAD(struct filter_callback, filter_callbacks); + + Hashmap *nodes; + Hashmap *vtable_methods; + Hashmap *vtable_properties; + + union sockaddr_union sockaddr; + socklen_t sockaddr_size; + + char *kernel; + char *machine; + pid_t nspid; + + sd_id128_t server_id; + + char *address; + unsigned address_index; + + int last_connect_error; + + enum bus_auth auth; + size_t auth_rbegin; + struct iovec auth_iovec[3]; + unsigned auth_index; + char *auth_buffer; + usec_t auth_timeout; + + struct ucred ucred; + char *label; + + uint64_t creds_mask; + + int *fds; + unsigned n_fds; + + char *exec_path; + char **exec_argv; + + unsigned iteration_counter; + + void *kdbus_buffer; + + /* We do locking around the memfd cache, since we want to + * allow people to process a sd_bus_message in a different + * thread then it was generated on and free it there. Since + * adding something to the memfd cache might happen when a + * message is released, we hence need to protect this bit with + * a mutex. */ + pthread_mutex_t memfd_cache_mutex; + struct memfd_cache memfd_cache[MEMFD_CACHE_MAX]; + unsigned n_memfd_cache; + + pid_t original_pid; + + uint64_t hello_flags; + uint64_t attach_flags; + + uint64_t match_cookie; + + sd_event_source *input_io_event_source; + sd_event_source *output_io_event_source; + sd_event_source *time_event_source; + sd_event_source *quit_event_source; + sd_event *event; + int event_priority; + + sd_bus_message *current_message; + sd_bus_slot *current_slot; + sd_bus_message_handler_t current_handler; + void *current_userdata; + + sd_bus **default_bus_ptr; + pid_t tid; + + struct kdbus_creds fake_creds; + struct kdbus_pids fake_pids; + char *fake_label; + + char *cgroup_root; + + char *description; + + size_t bloom_size; + unsigned bloom_n_hash; + + sd_bus_track *track_queue; + + LIST_HEAD(sd_bus_slot, slots); +}; + +#define BUS_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC)) + +#define BUS_WQUEUE_MAX 1024 +#define BUS_RQUEUE_MAX 64*1024 + +#define BUS_MESSAGE_SIZE_MAX (64*1024*1024) +#define BUS_AUTH_SIZE_MAX (64*1024) + +#define BUS_CONTAINER_DEPTH 128 + +/* Defined by the specification as maximum size of an array in + * bytes */ +#define BUS_ARRAY_MAX_SIZE 67108864 + +#define BUS_FDS_MAX 1024 + +#define BUS_EXEC_ARGV_MAX 256 + +bool interface_name_is_valid(const char *p) _pure_; +bool service_name_is_valid(const char *p) _pure_; +char* service_name_startswith(const char *a, const char *b); +bool member_name_is_valid(const char *p) _pure_; +bool object_path_is_valid(const char *p) _pure_; +char *object_path_startswith(const char *a, const char *b) _pure_; + +bool namespace_complex_pattern(const char *pattern, const char *value) _pure_; +bool path_complex_pattern(const char *pattern, const char *value) _pure_; + +bool namespace_simple_pattern(const char *pattern, const char *value) _pure_; +bool path_simple_pattern(const char *pattern, const char *value) _pure_; + +int bus_message_type_from_string(const char *s, uint8_t *u) _pure_; +const char *bus_message_type_to_string(uint8_t u) _pure_; + +#define error_name_is_valid interface_name_is_valid + +int bus_ensure_running(sd_bus *bus); +int bus_start_running(sd_bus *bus); +int bus_next_address(sd_bus *bus); + +int bus_seal_synthetic_message(sd_bus *b, sd_bus_message *m); + +int bus_rqueue_make_room(sd_bus *bus); + +bool bus_pid_changed(sd_bus *bus); + +char *bus_address_escape(const char *v); + +#define OBJECT_PATH_FOREACH_PREFIX(prefix, path) \ + for (char *_slash = ({ strcpy((prefix), (path)); streq((prefix), "/") ? NULL : strrchr((prefix), '/'); }) ; \ + _slash && !(_slash[(_slash) == (prefix)] = 0); \ + _slash = streq((prefix), "/") ? NULL : strrchr((prefix), '/')) + +/* If we are invoking callbacks of a bus object, ensure unreffing the + * bus from the callback doesn't destroy the object we are working + * on */ +#define BUS_DONT_DESTROY(bus) \ + _cleanup_(sd_bus_unrefp) _unused_ sd_bus *_dont_destroy_##bus = sd_bus_ref(bus) + +int bus_set_address_system(sd_bus *bus); +int bus_set_address_user(sd_bus *bus); +int bus_set_address_system_remote(sd_bus *b, const char *host); +int bus_set_address_system_machine(sd_bus *b, const char *machine); + +int bus_remove_match_by_string(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata); + +int bus_get_root_path(sd_bus *bus); + +int bus_maybe_reply_error(sd_bus_message *m, int r, sd_bus_error *error); + +#define bus_assert_return(expr, r, error) \ + do { \ + if (!assert_log(expr, #expr)) \ + return sd_bus_error_set_errno(error, r); \ + } while (false) diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.c new file mode 100644 index 0000000000..8f93edb8da --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.c @@ -0,0 +1,212 @@ +/*** + 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 . +***/ + +#include "bus-internal.h" +#include "bus-introspect.h" +#include "bus-protocol.h" +#include "bus-signature.h" +#include "fd-util.h" +#include "fileio.h" +#include "string-util.h" +#include "util.h" + +int introspect_begin(struct introspect *i, bool trusted) { + assert(i); + + zero(*i); + i->trusted = trusted; + + i->f = open_memstream(&i->introspection, &i->size); + if (!i->f) + return -ENOMEM; + + fputs(BUS_INTROSPECT_DOCTYPE + "\n", i->f); + + return 0; +} + +int introspect_write_default_interfaces(struct introspect *i, bool object_manager) { + assert(i); + + fputs(BUS_INTROSPECT_INTERFACE_PEER + BUS_INTROSPECT_INTERFACE_INTROSPECTABLE + BUS_INTROSPECT_INTERFACE_PROPERTIES, i->f); + + if (object_manager) + fputs(BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER, i->f); + + return 0; +} + +int introspect_write_child_nodes(struct introspect *i, Set *s, const char *prefix) { + char *node; + + assert(i); + assert(prefix); + + while ((node = set_steal_first(s))) { + const char *e; + + e = object_path_startswith(node, prefix); + if (e && e[0]) + fprintf(i->f, " \n", e); + + free(node); + } + + return 0; +} + +static void introspect_write_flags(struct introspect *i, int type, int flags) { + if (flags & SD_BUS_VTABLE_DEPRECATED) + fputs(" \n", i->f); + + if (type == _SD_BUS_VTABLE_METHOD && (flags & SD_BUS_VTABLE_METHOD_NO_REPLY)) + fputs(" \n", i->f); + + if (type == _SD_BUS_VTABLE_PROPERTY || type == _SD_BUS_VTABLE_WRITABLE_PROPERTY) { + if (flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT) + fputs(" \n", i->f); + + if (flags & SD_BUS_VTABLE_PROPERTY_CONST) + fputs(" \n", i->f); + else if (flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) + fputs(" \n", i->f); + else if (!(flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) + fputs(" \n", i->f); + } + + if (!i->trusted && + (type == _SD_BUS_VTABLE_METHOD || type == _SD_BUS_VTABLE_WRITABLE_PROPERTY) && + !(flags & SD_BUS_VTABLE_UNPRIVILEGED)) + fputs(" \n", i->f); +} + +static int introspect_write_arguments(struct introspect *i, const char *signature, const char *direction) { + int r; + + for (;;) { + size_t l; + + if (!*signature) + return 0; + + r = signature_element_length(signature, &l); + if (r < 0) + return r; + + fprintf(i->f, " f, " direction=\"%s\"/>\n", direction); + else + fputs("/>\n", i->f); + + signature += l; + } +} + +int introspect_write_interface(struct introspect *i, const sd_bus_vtable *v) { + assert(i); + assert(v); + + for (; v->type != _SD_BUS_VTABLE_END; v++) { + + /* Ignore methods, signals and properties that are + * marked "hidden", but do show the interface + * itself */ + + if (v->type != _SD_BUS_VTABLE_START && (v->flags & SD_BUS_VTABLE_HIDDEN)) + continue; + + switch (v->type) { + + case _SD_BUS_VTABLE_START: + if (v->flags & SD_BUS_VTABLE_DEPRECATED) + fputs(" \n", i->f); + break; + + case _SD_BUS_VTABLE_METHOD: + fprintf(i->f, " \n", v->x.method.member); + introspect_write_arguments(i, strempty(v->x.method.signature), "in"); + introspect_write_arguments(i, strempty(v->x.method.result), "out"); + introspect_write_flags(i, v->type, v->flags); + fputs(" \n", i->f); + break; + + case _SD_BUS_VTABLE_PROPERTY: + case _SD_BUS_VTABLE_WRITABLE_PROPERTY: + fprintf(i->f, " \n", + v->x.property.member, + v->x.property.signature, + v->type == _SD_BUS_VTABLE_WRITABLE_PROPERTY ? "readwrite" : "read"); + introspect_write_flags(i, v->type, v->flags); + fputs(" \n", i->f); + break; + + case _SD_BUS_VTABLE_SIGNAL: + fprintf(i->f, " \n", v->x.signal.member); + introspect_write_arguments(i, strempty(v->x.signal.signature), NULL); + introspect_write_flags(i, v->type, v->flags); + fputs(" \n", i->f); + break; + } + + } + + return 0; +} + +int introspect_finish(struct introspect *i, sd_bus *bus, sd_bus_message *m, sd_bus_message **reply) { + sd_bus_message *q; + int r; + + assert(i); + assert(m); + assert(reply); + + fputs("\n", i->f); + + r = fflush_and_check(i->f); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(m, &q); + if (r < 0) + return r; + + r = sd_bus_message_append(q, "s", i->introspection); + if (r < 0) { + sd_bus_message_unref(q); + return r; + } + + *reply = q; + return 0; +} + +void introspect_free(struct introspect *i) { + assert(i); + + safe_fclose(i->f); + + free(i->introspection); + zero(*i); +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.h new file mode 100644 index 0000000000..87ac03b26a --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-introspect.h @@ -0,0 +1,40 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include + +#include "set.h" + +struct introspect { + FILE *f; + char *introspection; + size_t size; + bool trusted; +}; + +int introspect_begin(struct introspect *i, bool trusted); +int introspect_write_default_interfaces(struct introspect *i, bool object_manager); +int introspect_write_child_nodes(struct introspect *i, Set *s, const char *prefix); +int introspect_write_interface(struct introspect *i, const sd_bus_vtable *v); +int introspect_finish(struct introspect *i, sd_bus *bus, sd_bus_message *m, sd_bus_message **reply); +void introspect_free(struct introspect *i); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.c new file mode 100644 index 0000000000..0896eeb177 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.c @@ -0,0 +1,1826 @@ +/*** + 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 . +***/ + +#ifdef HAVE_VALGRIND_MEMCHECK_H +#include +#endif + +#include +#include +#include +#include + +/* When we include libgen.h because we need dirname() we immediately + * undefine basename() since libgen.h defines it as a macro to the POSIX + * version which is really broken. We prefer GNU basename(). */ +#include +#undef basename + +#include "alloc-util.h" +#include "bus-bloom.h" +#include "bus-internal.h" +#include "bus-kernel.h" +#include "bus-label.h" +#include "bus-message.h" +#include "bus-util.h" +#include "capability-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "memfd-util.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +#define UNIQUE_NAME_MAX (3+DECIMAL_STR_MAX(uint64_t)) + +int bus_kernel_parse_unique_name(const char *s, uint64_t *id) { + int r; + + assert(s); + assert(id); + + if (!startswith(s, ":1.")) + return 0; + + r = safe_atou64(s + 3, id); + if (r < 0) + return r; + + return 1; +} + +static void append_payload_vec(struct kdbus_item **d, const void *p, size_t sz) { + assert(d); + assert(sz > 0); + + *d = ALIGN8_PTR(*d); + + /* Note that p can be NULL, which encodes a region full of + * zeroes, which is useful to optimize certain padding + * conditions */ + + (*d)->size = offsetof(struct kdbus_item, vec) + sizeof(struct kdbus_vec); + (*d)->type = KDBUS_ITEM_PAYLOAD_VEC; + (*d)->vec.address = PTR_TO_UINT64(p); + (*d)->vec.size = sz; + + *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); +} + +static void append_payload_memfd(struct kdbus_item **d, int memfd, size_t start, size_t sz) { + assert(d); + assert(memfd >= 0); + assert(sz > 0); + + *d = ALIGN8_PTR(*d); + (*d)->size = offsetof(struct kdbus_item, memfd) + sizeof(struct kdbus_memfd); + (*d)->type = KDBUS_ITEM_PAYLOAD_MEMFD; + (*d)->memfd.fd = memfd; + (*d)->memfd.start = start; + (*d)->memfd.size = sz; + + *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); +} + +static void append_destination(struct kdbus_item **d, const char *s, size_t length) { + assert(d); + assert(s); + + *d = ALIGN8_PTR(*d); + + (*d)->size = offsetof(struct kdbus_item, str) + length + 1; + (*d)->type = KDBUS_ITEM_DST_NAME; + memcpy((*d)->str, s, length + 1); + + *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); +} + +static struct kdbus_bloom_filter *append_bloom(struct kdbus_item **d, size_t length) { + struct kdbus_item *i; + + assert(d); + + i = ALIGN8_PTR(*d); + + i->size = offsetof(struct kdbus_item, bloom_filter) + + offsetof(struct kdbus_bloom_filter, data) + + length; + i->type = KDBUS_ITEM_BLOOM_FILTER; + + *d = (struct kdbus_item *) ((uint8_t*) i + i->size); + + return &i->bloom_filter; +} + +static void append_fds(struct kdbus_item **d, const int fds[], unsigned n_fds) { + assert(d); + assert(fds); + assert(n_fds > 0); + + *d = ALIGN8_PTR(*d); + (*d)->size = offsetof(struct kdbus_item, fds) + sizeof(int) * n_fds; + (*d)->type = KDBUS_ITEM_FDS; + memcpy((*d)->fds, fds, sizeof(int) * n_fds); + + *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); +} + +static void add_bloom_arg(void *data, size_t size, unsigned n_hash, unsigned i, const char *t) { + char buf[sizeof("arg")-1 + 2 + sizeof("-slash-prefix")]; + char *e; + + assert(data); + assert(size > 0); + assert(i < 64); + assert(t); + + e = stpcpy(buf, "arg"); + if (i < 10) + *(e++) = '0' + (char) i; + else { + *(e++) = '0' + (char) (i / 10); + *(e++) = '0' + (char) (i % 10); + } + + *e = 0; + bloom_add_pair(data, size, n_hash, buf, t); + + strcpy(e, "-dot-prefix"); + bloom_add_prefixes(data, size, n_hash, buf, t, '.'); + strcpy(e, "-slash-prefix"); + bloom_add_prefixes(data, size, n_hash, buf, t, '/'); +} + +static void add_bloom_arg_has(void *data, size_t size, unsigned n_hash, unsigned i, const char *t) { + char buf[sizeof("arg")-1 + 2 + sizeof("-has")]; + char *e; + + assert(data); + assert(size > 0); + assert(i < 64); + assert(t); + + e = stpcpy(buf, "arg"); + if (i < 10) + *(e++) = '0' + (char) i; + else { + *(e++) = '0' + (char) (i / 10); + *(e++) = '0' + (char) (i % 10); + } + + strcpy(e, "-has"); + bloom_add_pair(data, size, n_hash, buf, t); +} + +static int bus_message_setup_bloom(sd_bus_message *m, struct kdbus_bloom_filter *bloom) { + void *data; + unsigned i; + int r; + + assert(m); + assert(bloom); + + data = bloom->data; + memzero(data, m->bus->bloom_size); + bloom->generation = 0; + + bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "message-type", bus_message_type_to_string(m->header->type)); + + if (m->interface) + bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "interface", m->interface); + if (m->member) + bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "member", m->member); + if (m->path) { + bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "path", m->path); + bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "path-slash-prefix", m->path); + bloom_add_prefixes(data, m->bus->bloom_size, m->bus->bloom_n_hash, "path-slash-prefix", m->path, '/'); + } + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + for (i = 0; i < 64; i++) { + const char *t, *contents; + char type; + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r < 0) + return r; + + if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE)) { + + /* The bloom filter includes simple strings of any kind */ + r = sd_bus_message_read_basic(m, type, &t); + if (r < 0) + return r; + + add_bloom_arg(data, m->bus->bloom_size, m->bus->bloom_n_hash, i, t); + } + + if (type == SD_BUS_TYPE_ARRAY && STR_IN_SET(contents, "s", "o", "g")) { + + /* As well as array of simple strings of any kinds */ + r = sd_bus_message_enter_container(m, type, contents); + if (r < 0) + return r; + + while ((r = sd_bus_message_read_basic(m, contents[0], &t)) > 0) + add_bloom_arg_has(data, m->bus->bloom_size, m->bus->bloom_n_hash, i, t); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + } else + /* Stop adding to bloom filter as soon as we + * run into the first argument we cannot add + * to it. */ + break; + } + + return 0; +} + +static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { + struct bus_body_part *part; + struct kdbus_item *d; + const char *destination; + bool well_known = false; + uint64_t dst_id; + size_t sz, dl; + unsigned i; + int r; + + assert(b); + assert(m); + assert(m->sealed); + + /* We put this together only once, if this message is reused + * we reuse the earlier-built version */ + if (m->kdbus) + return 0; + + destination = m->destination ?: m->destination_ptr; + + if (destination) { + r = bus_kernel_parse_unique_name(destination, &dst_id); + if (r < 0) + return r; + if (r == 0) { + well_known = true; + + /* verify_destination_id will usually be 0, which makes the kernel + * driver only look at the provided well-known name. Otherwise, + * the kernel will make sure the provided destination id matches + * the owner of the provided well-known-name, and fail if they + * differ. Currently, this is only needed for bus-proxyd. */ + dst_id = m->verify_destination_id; + } + } else + dst_id = KDBUS_DST_ID_BROADCAST; + + sz = offsetof(struct kdbus_msg, items); + + /* Add in fixed header, fields header and payload */ + sz += (1 + m->n_body_parts) * ALIGN8(offsetof(struct kdbus_item, vec) + + MAX(sizeof(struct kdbus_vec), + sizeof(struct kdbus_memfd))); + + /* Add space for bloom filter */ + sz += ALIGN8(offsetof(struct kdbus_item, bloom_filter) + + offsetof(struct kdbus_bloom_filter, data) + + m->bus->bloom_size); + + /* Add in well-known destination header */ + if (well_known) { + dl = strlen(destination); + sz += ALIGN8(offsetof(struct kdbus_item, str) + dl + 1); + } + + /* Add space for unix fds */ + if (m->n_fds > 0) + sz += ALIGN8(offsetof(struct kdbus_item, fds) + sizeof(int)*m->n_fds); + + m->kdbus = memalign(8, sz); + if (!m->kdbus) { + r = -ENOMEM; + goto fail; + } + + m->free_kdbus = true; + memzero(m->kdbus, sz); + + m->kdbus->flags = + ((m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) ? 0 : KDBUS_MSG_EXPECT_REPLY) | + ((m->header->flags & BUS_MESSAGE_NO_AUTO_START) ? KDBUS_MSG_NO_AUTO_START : 0) | + ((m->header->type == SD_BUS_MESSAGE_SIGNAL) ? KDBUS_MSG_SIGNAL : 0); + + m->kdbus->dst_id = dst_id; + m->kdbus->payload_type = KDBUS_PAYLOAD_DBUS; + m->kdbus->cookie = m->header->dbus2.cookie; + m->kdbus->priority = m->priority; + + if (m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + m->kdbus->cookie_reply = m->reply_cookie; + else { + struct timespec now; + + assert_se(clock_gettime(CLOCK_MONOTONIC_COARSE, &now) == 0); + m->kdbus->timeout_ns = now.tv_sec * NSEC_PER_SEC + now.tv_nsec + + m->timeout * NSEC_PER_USEC; + } + + d = m->kdbus->items; + + if (well_known) + append_destination(&d, destination, dl); + + append_payload_vec(&d, m->header, BUS_MESSAGE_BODY_BEGIN(m)); + + MESSAGE_FOREACH_PART(part, i, m) { + if (part->is_zero) { + /* If this is padding then simply send a + * vector with a NULL data pointer which the + * kernel will just pass through. This is the + * most efficient way to encode zeroes */ + + append_payload_vec(&d, NULL, part->size); + continue; + } + + if (part->memfd >= 0 && part->sealed && destination) { + /* Try to send a memfd, if the part is + * sealed and this is not a broadcast. Since we can only */ + + append_payload_memfd(&d, part->memfd, part->memfd_offset, part->size); + continue; + } + + /* Otherwise, let's send a vector to the actual data. + * For that, we need to map it first. */ + r = bus_body_part_map(part); + if (r < 0) + goto fail; + + append_payload_vec(&d, part->data, part->size); + } + + if (m->header->type == SD_BUS_MESSAGE_SIGNAL) { + struct kdbus_bloom_filter *bloom; + + bloom = append_bloom(&d, m->bus->bloom_size); + r = bus_message_setup_bloom(m, bloom); + if (r < 0) + goto fail; + } + + if (m->n_fds > 0) + append_fds(&d, m->fds, m->n_fds); + + m->kdbus->size = (uint8_t*) d - (uint8_t*) m->kdbus; + assert(m->kdbus->size <= sz); + + return 0; + +fail: + m->poisoned = true; + return r; +} + +static void unset_memfds(struct sd_bus_message *m) { + struct bus_body_part *part; + unsigned i; + + assert(m); + + /* Make sure the memfds are not freed twice */ + MESSAGE_FOREACH_PART(part, i, m) + if (part->memfd >= 0) + part->memfd = -1; +} + +static void message_set_timestamp(sd_bus *bus, sd_bus_message *m, const struct kdbus_timestamp *ts) { + assert(bus); + assert(m); + + if (!ts) + return; + + if (!(bus->attach_flags & KDBUS_ATTACH_TIMESTAMP)) + return; + + m->realtime = ts->realtime_ns / NSEC_PER_USEC; + m->monotonic = ts->monotonic_ns / NSEC_PER_USEC; + m->seqnum = ts->seqnum; +} + +static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k) { + sd_bus_message *m = NULL; + struct kdbus_item *d; + unsigned n_fds = 0; + _cleanup_free_ int *fds = NULL; + struct bus_header *header = NULL; + void *footer = NULL; + size_t header_size = 0, footer_size = 0; + size_t n_bytes = 0, idx = 0; + const char *destination = NULL, *seclabel = NULL; + bool last_was_memfd = false; + int r; + + assert(bus); + assert(k); + assert(k->payload_type == KDBUS_PAYLOAD_DBUS); + + KDBUS_ITEM_FOREACH(d, k, items) { + size_t l; + + l = d->size - offsetof(struct kdbus_item, data); + + switch (d->type) { + + case KDBUS_ITEM_PAYLOAD_OFF: + if (!header) { + header = (struct bus_header*)((uint8_t*) k + d->vec.offset); + header_size = d->vec.size; + } + + footer = (uint8_t*) k + d->vec.offset; + footer_size = d->vec.size; + + n_bytes += d->vec.size; + last_was_memfd = false; + break; + + case KDBUS_ITEM_PAYLOAD_MEMFD: + if (!header) /* memfd cannot be first part */ + return -EBADMSG; + + n_bytes += d->memfd.size; + last_was_memfd = true; + break; + + case KDBUS_ITEM_FDS: { + int *f; + unsigned j; + + j = l / sizeof(int); + f = realloc(fds, sizeof(int) * (n_fds + j)); + if (!f) + return -ENOMEM; + + fds = f; + memcpy(fds + n_fds, d->fds, sizeof(int) * j); + n_fds += j; + break; + } + + case KDBUS_ITEM_SECLABEL: + seclabel = d->str; + break; + } + } + + if (last_was_memfd) /* memfd cannot be last part */ + return -EBADMSG; + + if (!header) + return -EBADMSG; + + if (header_size < sizeof(struct bus_header)) + return -EBADMSG; + + /* on kdbus we only speak native endian gvariant, never dbus1 + * marshalling or reverse endian */ + if (header->version != 2 || + header->endian != BUS_NATIVE_ENDIAN) + return -EPROTOTYPE; + + r = bus_message_from_header( + bus, + header, header_size, + footer, footer_size, + n_bytes, + fds, n_fds, + seclabel, 0, &m); + if (r < 0) + return r; + + /* The well-known names list is different from the other + credentials. If we asked for it, but nothing is there, this + means that the list of well-known names is simply empty, not + that we lack any data */ + + m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask; + + KDBUS_ITEM_FOREACH(d, k, items) { + size_t l; + + l = d->size - offsetof(struct kdbus_item, data); + + switch (d->type) { + + case KDBUS_ITEM_PAYLOAD_OFF: { + size_t begin_body; + + begin_body = BUS_MESSAGE_BODY_BEGIN(m); + + if (idx + d->vec.size > begin_body) { + struct bus_body_part *part; + + /* Contains body material */ + + part = message_append_part(m); + if (!part) { + r = -ENOMEM; + goto fail; + } + + /* A -1 offset is NUL padding. */ + part->is_zero = d->vec.offset == ~0ULL; + + if (idx >= begin_body) { + if (!part->is_zero) + part->data = (uint8_t* )k + d->vec.offset; + part->size = d->vec.size; + } else { + if (!part->is_zero) + part->data = (uint8_t*) k + d->vec.offset + (begin_body - idx); + part->size = d->vec.size - (begin_body - idx); + } + + part->sealed = true; + } + + idx += d->vec.size; + break; + } + + case KDBUS_ITEM_PAYLOAD_MEMFD: { + struct bus_body_part *part; + + if (idx < BUS_MESSAGE_BODY_BEGIN(m)) { + r = -EBADMSG; + goto fail; + } + + part = message_append_part(m); + if (!part) { + r = -ENOMEM; + goto fail; + } + + part->memfd = d->memfd.fd; + part->memfd_offset = d->memfd.start; + part->size = d->memfd.size; + part->sealed = true; + + idx += d->memfd.size; + break; + } + + case KDBUS_ITEM_PIDS: + + /* The PID/TID might be missing, when the data + * is faked by a bus proxy and it lacks that + * information about the real client (since + * SO_PEERCRED is used for that). Also kernel + * namespacing might make some of this data + * unavailable when untranslatable. */ + + if (d->pids.pid > 0) { + m->creds.pid = (pid_t) d->pids.pid; + m->creds.mask |= SD_BUS_CREDS_PID & bus->creds_mask; + } + + if (d->pids.tid > 0) { + m->creds.tid = (pid_t) d->pids.tid; + m->creds.mask |= SD_BUS_CREDS_TID & bus->creds_mask; + } + + if (d->pids.ppid > 0) { + m->creds.ppid = (pid_t) d->pids.ppid; + m->creds.mask |= SD_BUS_CREDS_PPID & bus->creds_mask; + } else if (d->pids.pid == 1) { + m->creds.ppid = 0; + m->creds.mask |= SD_BUS_CREDS_PPID & bus->creds_mask; + } + + break; + + case KDBUS_ITEM_CREDS: + + /* EUID/SUID/FSUID/EGID/SGID/FSGID might be + * missing too (see above). */ + + if ((uid_t) d->creds.uid != UID_INVALID) { + m->creds.uid = (uid_t) d->creds.uid; + m->creds.mask |= SD_BUS_CREDS_UID & bus->creds_mask; + } + + if ((uid_t) d->creds.euid != UID_INVALID) { + m->creds.euid = (uid_t) d->creds.euid; + m->creds.mask |= SD_BUS_CREDS_EUID & bus->creds_mask; + } + + if ((uid_t) d->creds.suid != UID_INVALID) { + m->creds.suid = (uid_t) d->creds.suid; + m->creds.mask |= SD_BUS_CREDS_SUID & bus->creds_mask; + } + + if ((uid_t) d->creds.fsuid != UID_INVALID) { + m->creds.fsuid = (uid_t) d->creds.fsuid; + m->creds.mask |= SD_BUS_CREDS_FSUID & bus->creds_mask; + } + + if ((gid_t) d->creds.gid != GID_INVALID) { + m->creds.gid = (gid_t) d->creds.gid; + m->creds.mask |= SD_BUS_CREDS_GID & bus->creds_mask; + } + + if ((gid_t) d->creds.egid != GID_INVALID) { + m->creds.egid = (gid_t) d->creds.egid; + m->creds.mask |= SD_BUS_CREDS_EGID & bus->creds_mask; + } + + if ((gid_t) d->creds.sgid != GID_INVALID) { + m->creds.sgid = (gid_t) d->creds.sgid; + m->creds.mask |= SD_BUS_CREDS_SGID & bus->creds_mask; + } + + if ((gid_t) d->creds.fsgid != GID_INVALID) { + m->creds.fsgid = (gid_t) d->creds.fsgid; + m->creds.mask |= SD_BUS_CREDS_FSGID & bus->creds_mask; + } + + break; + + case KDBUS_ITEM_TIMESTAMP: + message_set_timestamp(bus, m, &d->timestamp); + break; + + case KDBUS_ITEM_PID_COMM: + m->creds.comm = d->str; + m->creds.mask |= SD_BUS_CREDS_COMM & bus->creds_mask; + break; + + case KDBUS_ITEM_TID_COMM: + m->creds.tid_comm = d->str; + m->creds.mask |= SD_BUS_CREDS_TID_COMM & bus->creds_mask; + break; + + case KDBUS_ITEM_EXE: + m->creds.exe = d->str; + m->creds.mask |= SD_BUS_CREDS_EXE & bus->creds_mask; + break; + + case KDBUS_ITEM_CMDLINE: + m->creds.cmdline = d->str; + m->creds.cmdline_size = l; + m->creds.mask |= SD_BUS_CREDS_CMDLINE & bus->creds_mask; + break; + + case KDBUS_ITEM_CGROUP: + m->creds.cgroup = d->str; + m->creds.mask |= (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID) & bus->creds_mask; + + r = bus_get_root_path(bus); + if (r < 0) + goto fail; + + m->creds.cgroup_root = bus->cgroup_root; + break; + + case KDBUS_ITEM_AUDIT: + m->creds.audit_session_id = (uint32_t) d->audit.sessionid; + m->creds.mask |= SD_BUS_CREDS_AUDIT_SESSION_ID & bus->creds_mask; + + m->creds.audit_login_uid = (uid_t) d->audit.loginuid; + m->creds.mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID & bus->creds_mask; + break; + + case KDBUS_ITEM_CAPS: + if (d->caps.last_cap != cap_last_cap() || + d->size - offsetof(struct kdbus_item, caps.caps) < DIV_ROUND_UP(d->caps.last_cap, 32U) * 4 * 4) { + r = -EBADMSG; + goto fail; + } + + m->creds.capability = d->caps.caps; + m->creds.mask |= (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS) & bus->creds_mask; + break; + + case KDBUS_ITEM_DST_NAME: + if (!service_name_is_valid(d->str)) { + r = -EBADMSG; + goto fail; + } + + destination = d->str; + break; + + case KDBUS_ITEM_OWNED_NAME: + if (!service_name_is_valid(d->name.name)) { + r = -EBADMSG; + goto fail; + } + + if (bus->creds_mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) { + char **wkn; + size_t n; + + /* We just extend the array here, but + * do not allocate the strings inside + * of it, instead we just point to our + * buffer directly. */ + n = strv_length(m->creds.well_known_names); + wkn = realloc(m->creds.well_known_names, (n + 2) * sizeof(char*)); + if (!wkn) { + r = -ENOMEM; + goto fail; + } + + wkn[n] = d->name.name; + wkn[n+1] = NULL; + m->creds.well_known_names = wkn; + + m->creds.mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES; + } + break; + + case KDBUS_ITEM_CONN_DESCRIPTION: + m->creds.description = d->str; + m->creds.mask |= SD_BUS_CREDS_DESCRIPTION & bus->creds_mask; + break; + + case KDBUS_ITEM_AUXGROUPS: + + if (bus->creds_mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { + size_t i, n; + gid_t *g; + + n = (d->size - offsetof(struct kdbus_item, data64)) / sizeof(uint64_t); + g = new(gid_t, n); + if (!g) { + r = -ENOMEM; + goto fail; + } + + for (i = 0; i < n; i++) + g[i] = d->data64[i]; + + m->creds.supplementary_gids = g; + m->creds.n_supplementary_gids = n; + m->creds.mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; + } + + break; + + case KDBUS_ITEM_FDS: + case KDBUS_ITEM_SECLABEL: + case KDBUS_ITEM_BLOOM_FILTER: + break; + + default: + log_debug("Got unknown field from kernel %llu", d->type); + } + } + + /* If we requested the list of well-known names to be appended + * and the sender had none no item for it will be + * attached. However, this does *not* mean that the kernel + * didn't want to provide this information to us. Hence, let's + * explicitly mark this information as available if it was + * requested. */ + m->creds.mask |= bus->creds_mask & SD_BUS_CREDS_WELL_KNOWN_NAMES; + + r = bus_message_parse_fields(m); + if (r < 0) + goto fail; + + /* Refuse messages if kdbus and dbus1 cookie doesn't match up */ + if ((uint64_t) m->header->dbus2.cookie != k->cookie) { + r = -EBADMSG; + goto fail; + } + + /* Refuse messages where the reply flag doesn't match up */ + if (!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) != !!(k->flags & KDBUS_MSG_EXPECT_REPLY)) { + r = -EBADMSG; + goto fail; + } + + /* Refuse reply messages where the reply cookie doesn't match up */ + if ((m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) && m->reply_cookie != k->cookie_reply) { + r = -EBADMSG; + goto fail; + } + + /* Refuse messages where the autostart flag doesn't match up */ + if (!(m->header->flags & BUS_MESSAGE_NO_AUTO_START) != !(k->flags & KDBUS_MSG_NO_AUTO_START)) { + r = -EBADMSG; + goto fail; + } + + /* Override information from the user header with data from the kernel */ + if (k->src_id == KDBUS_SRC_ID_KERNEL) + bus_message_set_sender_driver(bus, m); + else { + xsprintf(m->sender_buffer, ":1.%llu", + (unsigned long long)k->src_id); + m->sender = m->creds.unique_name = m->sender_buffer; + } + + if (destination) + m->destination = destination; + else if (k->dst_id == KDBUS_DST_ID_BROADCAST) + m->destination = NULL; + else if (k->dst_id == KDBUS_DST_ID_NAME) + m->destination = bus->unique_name; /* fill in unique name if the well-known name is missing */ + else { + xsprintf(m->destination_buffer, ":1.%llu", + (unsigned long long)k->dst_id); + m->destination = m->destination_buffer; + } + + /* We take possession of the kmsg struct now */ + m->kdbus = k; + m->release_kdbus = true; + m->free_fds = true; + fds = NULL; + + bus->rqueue[bus->rqueue_size++] = m; + + return 1; + +fail: + unset_memfds(m); + sd_bus_message_unref(m); + + return r; +} + +int bus_kernel_take_fd(sd_bus *b) { + struct kdbus_bloom_parameter *bloom = NULL; + struct kdbus_item *items, *item; + struct kdbus_cmd_hello *hello; + _cleanup_free_ char *g = NULL; + const char *name; + size_t l = 0, m = 0, sz; + int r; + + assert(b); + + if (b->is_server) + return -EINVAL; + + b->use_memfd = 1; + + if (b->description) { + g = bus_label_escape(b->description); + if (!g) + return -ENOMEM; + + name = g; + } else { + char pr[17] = {}; + + /* If no name is explicitly set, we'll include a hint + * indicating the library implementation, a hint which + * kind of bus this is and the thread name */ + + assert_se(prctl(PR_GET_NAME, (unsigned long) pr) >= 0); + + if (isempty(pr)) { + name = b->is_system ? "sd-system" : + b->is_user ? "sd-user" : "sd"; + } else { + _cleanup_free_ char *e = NULL; + + e = bus_label_escape(pr); + if (!e) + return -ENOMEM; + + g = strappend(b->is_system ? "sd-system-" : + b->is_user ? "sd-user-" : "sd-", + e); + if (!g) + return -ENOMEM; + + name = g; + } + + b->description = bus_label_unescape(name); + if (!b->description) + return -ENOMEM; + } + + m = strlen(name); + + sz = ALIGN8(offsetof(struct kdbus_cmd_hello, items)) + + ALIGN8(offsetof(struct kdbus_item, str) + m + 1); + + if (b->fake_creds_valid) + sz += ALIGN8(offsetof(struct kdbus_item, creds) + sizeof(struct kdbus_creds)); + + if (b->fake_pids_valid) + sz += ALIGN8(offsetof(struct kdbus_item, pids) + sizeof(struct kdbus_pids)); + + if (b->fake_label) { + l = strlen(b->fake_label); + sz += ALIGN8(offsetof(struct kdbus_item, str) + l + 1); + } + + hello = alloca0_align(sz, 8); + hello->size = sz; + hello->flags = b->hello_flags; + hello->attach_flags_send = _KDBUS_ATTACH_ANY; + hello->attach_flags_recv = b->attach_flags; + hello->pool_size = KDBUS_POOL_SIZE; + + item = hello->items; + + item->size = offsetof(struct kdbus_item, str) + m + 1; + item->type = KDBUS_ITEM_CONN_DESCRIPTION; + memcpy(item->str, name, m + 1); + item = KDBUS_ITEM_NEXT(item); + + if (b->fake_creds_valid) { + item->size = offsetof(struct kdbus_item, creds) + sizeof(struct kdbus_creds); + item->type = KDBUS_ITEM_CREDS; + item->creds = b->fake_creds; + + item = KDBUS_ITEM_NEXT(item); + } + + if (b->fake_pids_valid) { + item->size = offsetof(struct kdbus_item, pids) + sizeof(struct kdbus_pids); + item->type = KDBUS_ITEM_PIDS; + item->pids = b->fake_pids; + + item = KDBUS_ITEM_NEXT(item); + } + + if (b->fake_label) { + item->size = offsetof(struct kdbus_item, str) + l + 1; + item->type = KDBUS_ITEM_SECLABEL; + memcpy(item->str, b->fake_label, l+1); + } + + r = ioctl(b->input_fd, KDBUS_CMD_HELLO, hello); + if (r < 0) { + if (errno == ENOTTY) + /* If the ioctl is not supported we assume that the + * API version changed in a major incompatible way, + * let's indicate an API incompatibility in this + * case. */ + return -ESOCKTNOSUPPORT; + + return -errno; + } + + if (!b->kdbus_buffer) { + b->kdbus_buffer = mmap(NULL, KDBUS_POOL_SIZE, PROT_READ, MAP_SHARED, b->input_fd, 0); + if (b->kdbus_buffer == MAP_FAILED) { + b->kdbus_buffer = NULL; + r = -errno; + goto fail; + } + } + + /* The higher 32bit of the bus_flags fields are considered + * 'incompatible flags'. Refuse them all for now. */ + if (hello->bus_flags > 0xFFFFFFFFULL) { + r = -ESOCKTNOSUPPORT; + goto fail; + } + + /* extract bloom parameters from items */ + items = (void*)((uint8_t*)b->kdbus_buffer + hello->offset); + KDBUS_FOREACH(item, items, hello->items_size) { + switch (item->type) { + case KDBUS_ITEM_BLOOM_PARAMETER: + bloom = &item->bloom_parameter; + break; + } + } + + if (!bloom || !bloom_validate_parameters((size_t) bloom->size, (unsigned) bloom->n_hash)) { + r = -EOPNOTSUPP; + goto fail; + } + + b->bloom_size = (size_t) bloom->size; + b->bloom_n_hash = (unsigned) bloom->n_hash; + + if (asprintf(&b->unique_name, ":1.%llu", (unsigned long long) hello->id) < 0) { + r = -ENOMEM; + goto fail; + } + + b->unique_id = hello->id; + + b->is_kernel = true; + b->bus_client = true; + b->can_fds = !!(hello->flags & KDBUS_HELLO_ACCEPT_FD); + b->message_version = 2; + b->message_endian = BUS_NATIVE_ENDIAN; + + /* the kernel told us the UUID of the underlying bus */ + memcpy(b->server_id.bytes, hello->id128, sizeof(b->server_id.bytes)); + + /* free returned items */ + (void) bus_kernel_cmd_free(b, hello->offset); + return bus_start_running(b); + +fail: + (void) bus_kernel_cmd_free(b, hello->offset); + return r; +} + +int bus_kernel_connect(sd_bus *b) { + assert(b); + assert(b->input_fd < 0); + assert(b->output_fd < 0); + assert(b->kernel); + + if (b->is_server) + return -EINVAL; + + b->input_fd = open(b->kernel, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (b->input_fd < 0) + return -errno; + + b->output_fd = b->input_fd; + + return bus_kernel_take_fd(b); +} + +int bus_kernel_cmd_free(sd_bus *bus, uint64_t offset) { + struct kdbus_cmd_free cmd = { + .size = sizeof(cmd), + .offset = offset, + }; + int r; + + assert(bus); + assert(bus->is_kernel); + + r = ioctl(bus->input_fd, KDBUS_CMD_FREE, &cmd); + if (r < 0) + return -errno; + + return 0; +} + +static void close_kdbus_msg(sd_bus *bus, struct kdbus_msg *k) { + struct kdbus_item *d; + + assert(bus); + assert(k); + + KDBUS_ITEM_FOREACH(d, k, items) { + if (d->type == KDBUS_ITEM_FDS) + close_many(d->fds, (d->size - offsetof(struct kdbus_item, fds)) / sizeof(int)); + else if (d->type == KDBUS_ITEM_PAYLOAD_MEMFD) + safe_close(d->memfd.fd); + } + + bus_kernel_cmd_free(bus, (uint8_t*) k - (uint8_t*) bus->kdbus_buffer); +} + +int bus_kernel_write_message(sd_bus *bus, sd_bus_message *m, bool hint_sync_call) { + struct kdbus_cmd_send cmd = { }; + int r; + + assert(bus); + assert(m); + assert(bus->state == BUS_RUNNING); + + /* If we can't deliver, we want room for the error message */ + r = bus_rqueue_make_room(bus); + if (r < 0) + return r; + + r = bus_message_setup_kmsg(bus, m); + if (r < 0) + return r; + + cmd.size = sizeof(cmd); + cmd.msg_address = (uintptr_t)m->kdbus; + + /* If this is a synchronous method call, then let's tell the + * kernel, so that it can pass CPU time/scheduling to the + * destination for the time, if it wants to. If we + * synchronously wait for the result anyway, we won't need CPU + * anyway. */ + if (hint_sync_call) { + m->kdbus->flags |= KDBUS_MSG_EXPECT_REPLY; + cmd.flags |= KDBUS_SEND_SYNC_REPLY; + } + + r = ioctl(bus->output_fd, KDBUS_CMD_SEND, &cmd); + if (r < 0) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus_message *reply; + + if (errno == EAGAIN || errno == EINTR) + return 0; + else if (errno == ENXIO || errno == ESRCH) { + + /* ENXIO: unique name not known + * ESRCH: well-known name not known */ + + if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) + sd_bus_error_setf(&error, SD_BUS_ERROR_SERVICE_UNKNOWN, "Destination %s not known", m->destination); + else { + log_debug("Could not deliver message to %s as destination is not known. Ignoring.", m->destination); + return 0; + } + + } else if (errno == EADDRNOTAVAIL) { + + /* EADDRNOTAVAIL: activation is possible, but turned off in request flags */ + + if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) + sd_bus_error_setf(&error, SD_BUS_ERROR_SERVICE_UNKNOWN, "Activation of %s not requested", m->destination); + else { + log_debug("Could not deliver message to %s as destination is not activated. Ignoring.", m->destination); + return 0; + } + } else + return -errno; + + r = bus_message_new_synthetic_error( + bus, + BUS_MESSAGE_COOKIE(m), + &error, + &reply); + + if (r < 0) + return r; + + r = bus_seal_synthetic_message(bus, reply); + if (r < 0) + return r; + + bus->rqueue[bus->rqueue_size++] = reply; + + } else if (hint_sync_call) { + struct kdbus_msg *k; + + k = (struct kdbus_msg *)((uint8_t *)bus->kdbus_buffer + cmd.reply.offset); + assert(k); + + if (k->payload_type == KDBUS_PAYLOAD_DBUS) { + + r = bus_kernel_make_message(bus, k); + if (r < 0) { + close_kdbus_msg(bus, k); + + /* Anybody can send us invalid messages, let's just drop them. */ + if (r == -EBADMSG || r == -EPROTOTYPE) + log_debug_errno(r, "Ignoring invalid synchronous reply: %m"); + else + return r; + } + } else { + log_debug("Ignoring message with unknown payload type %llu.", (unsigned long long) k->payload_type); + close_kdbus_msg(bus, k); + } + } + + return 1; +} + +static int push_name_owner_changed( + sd_bus *bus, + const char *name, + const char *old_owner, + const char *new_owner, + const struct kdbus_timestamp *ts) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(bus); + + r = sd_bus_message_new_signal( + bus, + &m, + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "NameOwnerChanged"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "sss", name, old_owner, new_owner); + if (r < 0) + return r; + + bus_message_set_sender_driver(bus, m); + message_set_timestamp(bus, m, ts); + + r = bus_seal_synthetic_message(bus, m); + if (r < 0) + return r; + + bus->rqueue[bus->rqueue_size++] = m; + m = NULL; + + return 1; +} + +static int translate_name_change( + sd_bus *bus, + const struct kdbus_msg *k, + const struct kdbus_item *d, + const struct kdbus_timestamp *ts) { + + char new_owner[UNIQUE_NAME_MAX], old_owner[UNIQUE_NAME_MAX]; + + assert(bus); + assert(k); + assert(d); + + if (d->type == KDBUS_ITEM_NAME_ADD || (d->name_change.old_id.flags & (KDBUS_NAME_IN_QUEUE|KDBUS_NAME_ACTIVATOR))) + old_owner[0] = 0; + else + sprintf(old_owner, ":1.%llu", (unsigned long long) d->name_change.old_id.id); + + if (d->type == KDBUS_ITEM_NAME_REMOVE || (d->name_change.new_id.flags & (KDBUS_NAME_IN_QUEUE|KDBUS_NAME_ACTIVATOR))) { + + if (isempty(old_owner)) + return 0; + + new_owner[0] = 0; + } else + sprintf(new_owner, ":1.%llu", (unsigned long long) d->name_change.new_id.id); + + return push_name_owner_changed(bus, d->name_change.name, old_owner, new_owner, ts); +} + +static int translate_id_change( + sd_bus *bus, + const struct kdbus_msg *k, + const struct kdbus_item *d, + const struct kdbus_timestamp *ts) { + + char owner[UNIQUE_NAME_MAX]; + + assert(bus); + assert(k); + assert(d); + + sprintf(owner, ":1.%llu", d->id_change.id); + + return push_name_owner_changed( + bus, owner, + d->type == KDBUS_ITEM_ID_ADD ? NULL : owner, + d->type == KDBUS_ITEM_ID_ADD ? owner : NULL, + ts); +} + +static int translate_reply( + sd_bus *bus, + const struct kdbus_msg *k, + const struct kdbus_item *d, + const struct kdbus_timestamp *ts) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(bus); + assert(k); + assert(d); + + r = bus_message_new_synthetic_error( + bus, + k->cookie_reply, + d->type == KDBUS_ITEM_REPLY_TIMEOUT ? + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Method call timed out") : + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Method call peer died"), + &m); + if (r < 0) + return r; + + message_set_timestamp(bus, m, ts); + + r = bus_seal_synthetic_message(bus, m); + if (r < 0) + return r; + + bus->rqueue[bus->rqueue_size++] = m; + m = NULL; + + return 1; +} + +static int bus_kernel_translate_message(sd_bus *bus, struct kdbus_msg *k) { + static int (* const translate[])(sd_bus *bus, const struct kdbus_msg *k, const struct kdbus_item *d, const struct kdbus_timestamp *ts) = { + [KDBUS_ITEM_NAME_ADD - _KDBUS_ITEM_KERNEL_BASE] = translate_name_change, + [KDBUS_ITEM_NAME_REMOVE - _KDBUS_ITEM_KERNEL_BASE] = translate_name_change, + [KDBUS_ITEM_NAME_CHANGE - _KDBUS_ITEM_KERNEL_BASE] = translate_name_change, + + [KDBUS_ITEM_ID_ADD - _KDBUS_ITEM_KERNEL_BASE] = translate_id_change, + [KDBUS_ITEM_ID_REMOVE - _KDBUS_ITEM_KERNEL_BASE] = translate_id_change, + + [KDBUS_ITEM_REPLY_TIMEOUT - _KDBUS_ITEM_KERNEL_BASE] = translate_reply, + [KDBUS_ITEM_REPLY_DEAD - _KDBUS_ITEM_KERNEL_BASE] = translate_reply, + }; + + struct kdbus_item *d, *found = NULL; + struct kdbus_timestamp *ts = NULL; + + assert(bus); + assert(k); + assert(k->payload_type == KDBUS_PAYLOAD_KERNEL); + + KDBUS_ITEM_FOREACH(d, k, items) { + if (d->type == KDBUS_ITEM_TIMESTAMP) + ts = &d->timestamp; + else if (d->type >= _KDBUS_ITEM_KERNEL_BASE && d->type < _KDBUS_ITEM_KERNEL_BASE + ELEMENTSOF(translate)) { + if (found) + return -EBADMSG; + found = d; + } else + log_debug("Got unknown field from kernel %llu", d->type); + } + + if (!found) { + log_debug("Didn't find a kernel message to translate."); + return 0; + } + + return translate[found->type - _KDBUS_ITEM_KERNEL_BASE](bus, k, found, ts); +} + +int bus_kernel_read_message(sd_bus *bus, bool hint_priority, int64_t priority) { + struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; + struct kdbus_msg *k; + int r; + + assert(bus); + + r = bus_rqueue_make_room(bus); + if (r < 0) + return r; + + if (hint_priority) { + recv.flags |= KDBUS_RECV_USE_PRIORITY; + recv.priority = priority; + } + + r = ioctl(bus->input_fd, KDBUS_CMD_RECV, &recv); + if (recv.return_flags & KDBUS_RECV_RETURN_DROPPED_MSGS) + log_debug("%s: kdbus reports %" PRIu64 " dropped broadcast messages, ignoring.", strna(bus->description), (uint64_t) recv.dropped_msgs); + if (r < 0) { + if (errno == EAGAIN) + return 0; + + return -errno; + } + + k = (struct kdbus_msg *)((uint8_t *)bus->kdbus_buffer + recv.msg.offset); + if (k->payload_type == KDBUS_PAYLOAD_DBUS) { + r = bus_kernel_make_message(bus, k); + + /* Anybody can send us invalid messages, let's just drop them. */ + if (r == -EBADMSG || r == -EPROTOTYPE) { + log_debug_errno(r, "Ignoring invalid message: %m"); + r = 0; + } + + if (r <= 0) + close_kdbus_msg(bus, k); + } else if (k->payload_type == KDBUS_PAYLOAD_KERNEL) { + r = bus_kernel_translate_message(bus, k); + close_kdbus_msg(bus, k); + } else { + log_debug("Ignoring message with unknown payload type %llu.", (unsigned long long) k->payload_type); + r = 0; + close_kdbus_msg(bus, k); + } + + return r < 0 ? r : 1; +} + +int bus_kernel_pop_memfd(sd_bus *bus, void **address, size_t *mapped, size_t *allocated) { + struct memfd_cache *c; + int fd; + + assert(address); + assert(mapped); + assert(allocated); + + if (!bus || !bus->is_kernel) + return -EOPNOTSUPP; + + assert_se(pthread_mutex_lock(&bus->memfd_cache_mutex) == 0); + + if (bus->n_memfd_cache <= 0) { + int r; + + assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); + + r = memfd_new(bus->description); + if (r < 0) + return r; + + *address = NULL; + *mapped = 0; + *allocated = 0; + return r; + } + + c = &bus->memfd_cache[--bus->n_memfd_cache]; + + assert(c->fd >= 0); + assert(c->mapped == 0 || c->address); + + *address = c->address; + *mapped = c->mapped; + *allocated = c->allocated; + fd = c->fd; + + assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); + + return fd; +} + +static void close_and_munmap(int fd, void *address, size_t size) { + if (size > 0) + assert_se(munmap(address, PAGE_ALIGN(size)) >= 0); + + safe_close(fd); +} + +void bus_kernel_push_memfd(sd_bus *bus, int fd, void *address, size_t mapped, size_t allocated) { + struct memfd_cache *c; + uint64_t max_mapped = PAGE_ALIGN(MEMFD_CACHE_ITEM_SIZE_MAX); + + assert(fd >= 0); + assert(mapped == 0 || address); + + if (!bus || !bus->is_kernel) { + close_and_munmap(fd, address, mapped); + return; + } + + assert_se(pthread_mutex_lock(&bus->memfd_cache_mutex) == 0); + + if (bus->n_memfd_cache >= ELEMENTSOF(bus->memfd_cache)) { + assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); + + close_and_munmap(fd, address, mapped); + return; + } + + c = &bus->memfd_cache[bus->n_memfd_cache++]; + c->fd = fd; + c->address = address; + + /* If overly long, let's return a bit to the OS */ + if (mapped > max_mapped) { + assert_se(memfd_set_size(fd, max_mapped) >= 0); + assert_se(munmap((uint8_t*) address + max_mapped, PAGE_ALIGN(mapped - max_mapped)) >= 0); + c->mapped = c->allocated = max_mapped; + } else { + c->mapped = mapped; + c->allocated = allocated; + } + + assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); +} + +void bus_kernel_flush_memfd(sd_bus *b) { + unsigned i; + + assert(b); + + for (i = 0; i < b->n_memfd_cache; i++) + close_and_munmap(b->memfd_cache[i].fd, b->memfd_cache[i].address, b->memfd_cache[i].mapped); +} + +uint64_t request_name_flags_to_kdbus(uint64_t flags) { + uint64_t f = 0; + + if (flags & SD_BUS_NAME_ALLOW_REPLACEMENT) + f |= KDBUS_NAME_ALLOW_REPLACEMENT; + + if (flags & SD_BUS_NAME_REPLACE_EXISTING) + f |= KDBUS_NAME_REPLACE_EXISTING; + + if (flags & SD_BUS_NAME_QUEUE) + f |= KDBUS_NAME_QUEUE; + + return f; +} + +uint64_t attach_flags_to_kdbus(uint64_t mask) { + uint64_t m = 0; + + if (mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| + SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID)) + m |= KDBUS_ATTACH_CREDS; + + if (mask & (SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_PPID)) + m |= KDBUS_ATTACH_PIDS; + + if (mask & SD_BUS_CREDS_COMM) + m |= KDBUS_ATTACH_PID_COMM; + + if (mask & SD_BUS_CREDS_TID_COMM) + m |= KDBUS_ATTACH_TID_COMM; + + if (mask & SD_BUS_CREDS_EXE) + m |= KDBUS_ATTACH_EXE; + + if (mask & SD_BUS_CREDS_CMDLINE) + m |= KDBUS_ATTACH_CMDLINE; + + if (mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID)) + m |= KDBUS_ATTACH_CGROUP; + + if (mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS)) + m |= KDBUS_ATTACH_CAPS; + + if (mask & SD_BUS_CREDS_SELINUX_CONTEXT) + m |= KDBUS_ATTACH_SECLABEL; + + if (mask & (SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID)) + m |= KDBUS_ATTACH_AUDIT; + + if (mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) + m |= KDBUS_ATTACH_NAMES; + + if (mask & SD_BUS_CREDS_DESCRIPTION) + m |= KDBUS_ATTACH_CONN_DESCRIPTION; + + if (mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) + m |= KDBUS_ATTACH_AUXGROUPS; + + return m; +} + +int bus_kernel_create_bus(const char *name, bool world, char **s) { + struct kdbus_cmd *make; + struct kdbus_item *n; + size_t l; + int fd; + + assert(name); + assert(s); + + fd = open("/sys/fs/kdbus/control", O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) + return -errno; + + l = strlen(name); + make = alloca0_align(offsetof(struct kdbus_cmd, items) + + ALIGN8(offsetof(struct kdbus_item, bloom_parameter) + sizeof(struct kdbus_bloom_parameter)) + + ALIGN8(offsetof(struct kdbus_item, data64) + sizeof(uint64_t)) + + ALIGN8(offsetof(struct kdbus_item, str) + DECIMAL_STR_MAX(uid_t) + 1 + l + 1), + 8); + + make->size = offsetof(struct kdbus_cmd, items); + + /* Set the bloom parameters */ + n = make->items; + n->size = offsetof(struct kdbus_item, bloom_parameter) + + sizeof(struct kdbus_bloom_parameter); + n->type = KDBUS_ITEM_BLOOM_PARAMETER; + n->bloom_parameter.size = DEFAULT_BLOOM_SIZE; + n->bloom_parameter.n_hash = DEFAULT_BLOOM_N_HASH; + + assert_cc(DEFAULT_BLOOM_SIZE > 0); + assert_cc(DEFAULT_BLOOM_N_HASH > 0); + + make->size += ALIGN8(n->size); + + /* Provide all metadata via bus-owner queries */ + n = KDBUS_ITEM_NEXT(n); + n->type = KDBUS_ITEM_ATTACH_FLAGS_SEND; + n->size = offsetof(struct kdbus_item, data64) + sizeof(uint64_t); + n->data64[0] = _KDBUS_ATTACH_ANY; + make->size += ALIGN8(n->size); + + /* Set the a good name */ + n = KDBUS_ITEM_NEXT(n); + sprintf(n->str, UID_FMT "-%s", getuid(), name); + n->size = offsetof(struct kdbus_item, str) + strlen(n->str) + 1; + n->type = KDBUS_ITEM_MAKE_NAME; + make->size += ALIGN8(n->size); + + make->flags = world ? KDBUS_MAKE_ACCESS_WORLD : 0; + + if (ioctl(fd, KDBUS_CMD_BUS_MAKE, make) < 0) { + safe_close(fd); + + /* Major API change? then the ioctls got shuffled around. */ + if (errno == ENOTTY) + return -ESOCKTNOSUPPORT; + + return -errno; + } + + if (s) { + char *p; + + p = strjoin("/sys/fs/kdbus/", n->str, "/bus", NULL); + if (!p) { + safe_close(fd); + return -ENOMEM; + } + + *s = p; + } + + return fd; +} + +int bus_kernel_open_bus_fd(const char *bus, char **path) { + char *p; + int fd; + size_t len; + + assert(bus); + + len = strlen("/sys/fs/kdbus/") + DECIMAL_STR_MAX(uid_t) + 1 + strlen(bus) + strlen("/bus") + 1; + + if (path) { + p = new(char, len); + if (!p) + return -ENOMEM; + } else + p = newa(char, len); + + sprintf(p, "/sys/fs/kdbus/" UID_FMT "-%s/bus", getuid(), bus); + + fd = open(p, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) { + if (path) + free(p); + + return -errno; + } + + if (path) + *path = p; + + return fd; +} + +int bus_kernel_create_endpoint(const char *bus_name, const char *ep_name, char **ep_path) { + _cleanup_free_ char *path = NULL; + struct kdbus_cmd *make; + struct kdbus_item *n; + const char *name; + int fd; + + fd = bus_kernel_open_bus_fd(bus_name, &path); + if (fd < 0) + return fd; + + make = alloca0_align(ALIGN8(offsetof(struct kdbus_cmd, items)) + + ALIGN8(offsetof(struct kdbus_item, str) + DECIMAL_STR_MAX(uid_t) + 1 + strlen(ep_name) + 1), + 8); + make->size = ALIGN8(offsetof(struct kdbus_cmd, items)); + make->flags = KDBUS_MAKE_ACCESS_WORLD; + + n = make->items; + sprintf(n->str, UID_FMT "-%s", getuid(), ep_name); + n->size = offsetof(struct kdbus_item, str) + strlen(n->str) + 1; + n->type = KDBUS_ITEM_MAKE_NAME; + make->size += ALIGN8(n->size); + name = n->str; + + if (ioctl(fd, KDBUS_CMD_ENDPOINT_MAKE, make) < 0) { + safe_close(fd); + return -errno; + } + + if (ep_path) { + char *p; + + p = strjoin(dirname(path), "/", name, NULL); + if (!p) { + safe_close(fd); + return -ENOMEM; + } + + *ep_path = p; + } + + return fd; +} + +int bus_kernel_try_close(sd_bus *bus) { + struct kdbus_cmd byebye = { .size = sizeof(byebye) }; + + assert(bus); + assert(bus->is_kernel); + + if (ioctl(bus->input_fd, KDBUS_CMD_BYEBYE, &byebye) < 0) + return -errno; + + return 0; +} + +int bus_kernel_drop_one(int fd) { + struct kdbus_cmd_recv recv = { + .size = sizeof(recv), + .flags = KDBUS_RECV_DROP, + }; + + assert(fd >= 0); + + if (ioctl(fd, KDBUS_CMD_RECV, &recv) < 0) + return -errno; + + return 0; +} + +int bus_kernel_realize_attach_flags(sd_bus *bus) { + struct kdbus_cmd *update; + struct kdbus_item *n; + + assert(bus); + assert(bus->is_kernel); + + update = alloca0_align(offsetof(struct kdbus_cmd, items) + + ALIGN8(offsetof(struct kdbus_item, data64) + sizeof(uint64_t)), + 8); + + n = update->items; + n->type = KDBUS_ITEM_ATTACH_FLAGS_RECV; + n->size = offsetof(struct kdbus_item, data64) + sizeof(uint64_t); + n->data64[0] = bus->attach_flags; + + update->size = + offsetof(struct kdbus_cmd, items) + + ALIGN8(n->size); + + if (ioctl(bus->input_fd, KDBUS_CMD_UPDATE, update) < 0) + return -errno; + + return 0; +} + +int bus_kernel_get_bus_name(sd_bus *bus, char **name) { + struct kdbus_cmd_info cmd = { + .size = sizeof(struct kdbus_cmd_info), + }; + struct kdbus_info *info; + struct kdbus_item *item; + char *n = NULL; + int r; + + assert(bus); + assert(name); + assert(bus->is_kernel); + + r = ioctl(bus->input_fd, KDBUS_CMD_BUS_CREATOR_INFO, &cmd); + if (r < 0) + return -errno; + + info = (struct kdbus_info*) ((uint8_t*) bus->kdbus_buffer + cmd.offset); + + KDBUS_ITEM_FOREACH(item, info, items) + if (item->type == KDBUS_ITEM_MAKE_NAME) { + r = free_and_strdup(&n, item->str); + break; + } + + bus_kernel_cmd_free(bus, cmd.offset); + + if (r < 0) + return r; + if (!n) + return -EIO; + + *name = n; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.h new file mode 100644 index 0000000000..2927ba26a5 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-kernel.h @@ -0,0 +1,93 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include + +#define KDBUS_ITEM_NEXT(item) \ + (typeof(item))(((uint8_t *)item) + ALIGN8((item)->size)) + +#define KDBUS_ITEM_FOREACH(part, head, first) \ + for (part = (head)->first; \ + ((uint8_t *)(part) < (uint8_t *)(head) + (head)->size) && \ + ((uint8_t *) part >= (uint8_t *) head); \ + part = KDBUS_ITEM_NEXT(part)) +#define KDBUS_FOREACH(iter, first, _size) \ + for (iter = (first); \ + ((uint8_t *)(iter) < (uint8_t *)(first) + (_size)) && \ + ((uint8_t *)(iter) >= (uint8_t *)(first)); \ + iter = (void*)(((uint8_t *)iter) + ALIGN8((iter)->size))) + +#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data) +#define KDBUS_ITEM_SIZE(s) ALIGN8((s) + KDBUS_ITEM_HEADER_SIZE) + +#define MEMFD_CACHE_MAX 32 + +/* When we cache a memfd block for reuse, we will truncate blocks + * longer than this in order not to keep too much data around. */ +#define MEMFD_CACHE_ITEM_SIZE_MAX (128*1024) + +/* This determines at which minimum size we prefer sending memfds over + * sending vectors */ +#define MEMFD_MIN_SIZE (512*1024) + +/* The size of the per-connection memory pool that we set up and where + * the kernel places our incoming messages */ +#define KDBUS_POOL_SIZE (16*1024*1024) + +struct memfd_cache { + int fd; + void *address; + size_t mapped; + size_t allocated; +}; + +int bus_kernel_connect(sd_bus *b); +int bus_kernel_take_fd(sd_bus *b); + +int bus_kernel_write_message(sd_bus *bus, sd_bus_message *m, bool hint_sync_call); +int bus_kernel_read_message(sd_bus *bus, bool hint_priority, int64_t priority); + +int bus_kernel_open_bus_fd(const char *bus, char **path); + +int bus_kernel_create_bus(const char *name, bool world, char **s); +int bus_kernel_create_endpoint(const char *bus_name, const char *ep_name, char **path); + +int bus_kernel_pop_memfd(sd_bus *bus, void **address, size_t *mapped, size_t *allocated); +void bus_kernel_push_memfd(sd_bus *bus, int fd, void *address, size_t mapped, size_t allocated); + +void bus_kernel_flush_memfd(sd_bus *bus); + +int bus_kernel_parse_unique_name(const char *s, uint64_t *id); + +uint64_t request_name_flags_to_kdbus(uint64_t sd_bus_flags); +uint64_t attach_flags_to_kdbus(uint64_t sd_bus_flags); + +int bus_kernel_try_close(sd_bus *bus); + +int bus_kernel_drop_one(int fd); + +int bus_kernel_realize_attach_flags(sd_bus *bus); + +int bus_kernel_get_bus_name(sd_bus *bus, char **name); + +int bus_kernel_cmd_free(sd_bus *bus, uint64_t offset); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-match.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-match.c new file mode 100644 index 0000000000..397baf6f33 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-match.c @@ -0,0 +1,1218 @@ +/*** + 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 . +***/ + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-match.h" +#include "bus-message.h" +#include "bus-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "string-util.h" +#include "strv.h" + +/* Example: + * + * A: type=signal,sender=foo,interface=bar + * B: type=signal,sender=quux,interface=fips + * C: type=signal,sender=quux,interface=waldo + * D: type=signal,member=test + * E: sender=miau + * F: type=signal + * G: type=signal + * + * results in this tree: + * + * BUS_MATCH_ROOT + * + BUS_MATCH_MESSAGE_TYPE + * | ` BUS_MATCH_VALUE: value == signal + * | + DBUS_MATCH_SENDER + * | | + BUS_MATCH_VALUE: value == foo + * | | | ` DBUS_MATCH_INTERFACE + * | | | ` BUS_MATCH_VALUE: value == bar + * | | | ` BUS_MATCH_LEAF: A + * | | ` BUS_MATCH_VALUE: value == quux + * | | ` DBUS_MATCH_INTERFACE + * | | | BUS_MATCH_VALUE: value == fips + * | | | ` BUS_MATCH_LEAF: B + * | | ` BUS_MATCH_VALUE: value == waldo + * | | ` BUS_MATCH_LEAF: C + * | + DBUS_MATCH_MEMBER + * | | ` BUS_MATCH_VALUE: value == test + * | | ` BUS_MATCH_LEAF: D + * | + BUS_MATCH_LEAF: F + * | ` BUS_MATCH_LEAF: G + * ` BUS_MATCH_SENDER + * ` BUS_MATCH_VALUE: value == miau + * ` BUS_MATCH_LEAF: E + */ + +static inline bool BUS_MATCH_IS_COMPARE(enum bus_match_node_type t) { + return t >= BUS_MATCH_SENDER && t <= BUS_MATCH_ARG_HAS_LAST; +} + +static inline bool BUS_MATCH_CAN_HASH(enum bus_match_node_type t) { + return (t >= BUS_MATCH_MESSAGE_TYPE && t <= BUS_MATCH_PATH) || + (t >= BUS_MATCH_ARG && t <= BUS_MATCH_ARG_LAST) || + (t >= BUS_MATCH_ARG_HAS && t <= BUS_MATCH_ARG_HAS_LAST); +} + +static void bus_match_node_free(struct bus_match_node *node) { + assert(node); + assert(node->parent); + assert(!node->child); + assert(node->type != BUS_MATCH_ROOT); + assert(node->type < _BUS_MATCH_NODE_TYPE_MAX); + + if (node->parent->child) { + /* We are apparently linked into the parent's child + * list. Let's remove us from there. */ + if (node->prev) { + assert(node->prev->next == node); + node->prev->next = node->next; + } else { + assert(node->parent->child == node); + node->parent->child = node->next; + } + + if (node->next) + node->next->prev = node->prev; + } + + if (node->type == BUS_MATCH_VALUE) { + /* We might be in the parent's hash table, so clean + * this up */ + + if (node->parent->type == BUS_MATCH_MESSAGE_TYPE) + hashmap_remove(node->parent->compare.children, UINT_TO_PTR(node->value.u8)); + else if (BUS_MATCH_CAN_HASH(node->parent->type) && node->value.str) + hashmap_remove(node->parent->compare.children, node->value.str); + + free(node->value.str); + } + + if (BUS_MATCH_IS_COMPARE(node->type)) { + assert(hashmap_isempty(node->compare.children)); + hashmap_free(node->compare.children); + } + + free(node); +} + +static bool bus_match_node_maybe_free(struct bus_match_node *node) { + assert(node); + + if (node->type == BUS_MATCH_ROOT) + return false; + + if (node->child) + return false; + + if (BUS_MATCH_IS_COMPARE(node->type) && !hashmap_isempty(node->compare.children)) + return true; + + bus_match_node_free(node); + return true; +} + +static bool value_node_test( + struct bus_match_node *node, + enum bus_match_node_type parent_type, + uint8_t value_u8, + const char *value_str, + char **value_strv, + sd_bus_message *m) { + + assert(node); + assert(node->type == BUS_MATCH_VALUE); + + /* Tests parameters against this value node, doing prefix + * magic and stuff. */ + + switch (parent_type) { + + case BUS_MATCH_MESSAGE_TYPE: + return node->value.u8 == value_u8; + + case BUS_MATCH_SENDER: + if (streq_ptr(node->value.str, value_str)) + return true; + + if (m->creds.mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) { + char **i; + + /* on kdbus we have the well known names list + * in the credentials, let's make use of that + * for an accurate match */ + + STRV_FOREACH(i, m->creds.well_known_names) + if (streq_ptr(node->value.str, *i)) + return true; + + } else { + + /* If we don't have kdbus, we don't know the + * well-known names of the senders. In that, + * let's just hope that dbus-daemon doesn't + * send us stuff we didn't want. */ + + if (node->value.str[0] != ':' && value_str && value_str[0] == ':') + return true; + } + + return false; + + case BUS_MATCH_DESTINATION: + case BUS_MATCH_INTERFACE: + case BUS_MATCH_MEMBER: + case BUS_MATCH_PATH: + case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: + + if (value_str) + return streq_ptr(node->value.str, value_str); + + return false; + + case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: { + char **i; + + STRV_FOREACH(i, value_strv) + if (streq_ptr(node->value.str, *i)) + return true; + + return false; + } + + case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: + if (value_str) + return namespace_simple_pattern(node->value.str, value_str); + + return false; + + case BUS_MATCH_PATH_NAMESPACE: + return path_simple_pattern(node->value.str, value_str); + + case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: + if (value_str) + return path_complex_pattern(node->value.str, value_str); + + return false; + + default: + assert_not_reached("Invalid node type"); + } +} + +static bool value_node_same( + struct bus_match_node *node, + enum bus_match_node_type parent_type, + uint8_t value_u8, + const char *value_str) { + + /* Tests parameters against this value node, not doing prefix + * magic and stuff, i.e. this one actually compares the match + * itself. */ + + assert(node); + assert(node->type == BUS_MATCH_VALUE); + + switch (parent_type) { + + case BUS_MATCH_MESSAGE_TYPE: + return node->value.u8 == value_u8; + + case BUS_MATCH_SENDER: + case BUS_MATCH_DESTINATION: + case BUS_MATCH_INTERFACE: + case BUS_MATCH_MEMBER: + case BUS_MATCH_PATH: + case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: + case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: + case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: + case BUS_MATCH_PATH_NAMESPACE: + case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: + return streq(node->value.str, value_str); + + default: + assert_not_reached("Invalid node type"); + } +} + +int bus_match_run( + sd_bus *bus, + struct bus_match_node *node, + sd_bus_message *m) { + + _cleanup_strv_free_ char **test_strv = NULL; + const char *test_str = NULL; + uint8_t test_u8 = 0; + int r; + + assert(m); + + if (!node) + return 0; + + if (bus && bus->match_callbacks_modified) + return 0; + + /* Not these special semantics: when traversing the tree we + * usually let bus_match_run() when called for a node + * recursively invoke bus_match_run(). There's are two + * exceptions here though, which are BUS_NODE_ROOT (which + * cannot have a sibling), and BUS_NODE_VALUE (whose siblings + * are invoked anyway by its parent. */ + + switch (node->type) { + + case BUS_MATCH_ROOT: + + /* Run all children. Since we cannot have any siblings + * we won't call any. The children of the root node + * are compares or leaves, they will automatically + * call their siblings. */ + return bus_match_run(bus, node->child, m); + + case BUS_MATCH_VALUE: + + /* Run all children. We don't execute any siblings, we + * assume our caller does that. The children of value + * nodes are compares or leaves, they will + * automatically call their siblings */ + + assert(node->child); + return bus_match_run(bus, node->child, m); + + case BUS_MATCH_LEAF: + + if (bus) { + if (node->leaf.callback->last_iteration == bus->iteration_counter) + return 0; + + node->leaf.callback->last_iteration = bus->iteration_counter; + } + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + /* Run the callback. And then invoke siblings. */ + if (node->leaf.callback->callback) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + sd_bus_slot *slot; + + slot = container_of(node->leaf.callback, sd_bus_slot, match_callback); + if (bus) { + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = node->leaf.callback->callback; + bus->current_userdata = slot->userdata; + } + r = node->leaf.callback->callback(m, slot->userdata, &error_buffer); + if (bus) { + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + } + + r = bus_maybe_reply_error(m, r, &error_buffer); + if (r != 0) + return r; + + if (bus && bus->match_callbacks_modified) + return 0; + } + + return bus_match_run(bus, node->next, m); + + case BUS_MATCH_MESSAGE_TYPE: + test_u8 = m->header->type; + break; + + case BUS_MATCH_SENDER: + test_str = m->sender; + /* FIXME: resolve test_str from a well-known to a unique name first */ + break; + + case BUS_MATCH_DESTINATION: + test_str = m->destination; + break; + + case BUS_MATCH_INTERFACE: + test_str = m->interface; + break; + + case BUS_MATCH_MEMBER: + test_str = m->member; + break; + + case BUS_MATCH_PATH: + case BUS_MATCH_PATH_NAMESPACE: + test_str = m->path; + break; + + case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: + (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG, &test_str); + break; + + case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: + (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG_PATH, &test_str); + break; + + case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: + (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG_NAMESPACE, &test_str); + break; + + case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: + (void) bus_message_get_arg_strv(m, node->type - BUS_MATCH_ARG_HAS, &test_strv); + break; + + default: + assert_not_reached("Unknown match type."); + } + + if (BUS_MATCH_CAN_HASH(node->type)) { + struct bus_match_node *found; + + /* Lookup via hash table, nice! So let's jump directly. */ + + if (test_str) + found = hashmap_get(node->compare.children, test_str); + else if (test_strv) { + char **i; + + STRV_FOREACH(i, test_strv) { + found = hashmap_get(node->compare.children, *i); + if (found) { + r = bus_match_run(bus, found, m); + if (r != 0) + return r; + } + } + + found = NULL; + } else if (node->type == BUS_MATCH_MESSAGE_TYPE) + found = hashmap_get(node->compare.children, UINT_TO_PTR(test_u8)); + else + found = NULL; + + if (found) { + r = bus_match_run(bus, found, m); + if (r != 0) + return r; + } + } else { + struct bus_match_node *c; + + /* No hash table, so let's iterate manually... */ + + for (c = node->child; c; c = c->next) { + if (!value_node_test(c, node->type, test_u8, test_str, test_strv, m)) + continue; + + r = bus_match_run(bus, c, m); + if (r != 0) + return r; + } + } + + if (bus && bus->match_callbacks_modified) + return 0; + + /* And now, let's invoke our siblings */ + return bus_match_run(bus, node->next, m); +} + +static int bus_match_add_compare_value( + struct bus_match_node *where, + enum bus_match_node_type t, + uint8_t value_u8, + const char *value_str, + struct bus_match_node **ret) { + + struct bus_match_node *c = NULL, *n = NULL; + int r; + + assert(where); + assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); + assert(BUS_MATCH_IS_COMPARE(t)); + assert(ret); + + for (c = where->child; c && c->type != t; c = c->next) + ; + + if (c) { + /* Comparison node already exists? Then let's see if + * the value node exists too. */ + + if (t == BUS_MATCH_MESSAGE_TYPE) + n = hashmap_get(c->compare.children, UINT_TO_PTR(value_u8)); + else if (BUS_MATCH_CAN_HASH(t)) + n = hashmap_get(c->compare.children, value_str); + else { + for (n = c->child; n && !value_node_same(n, t, value_u8, value_str); n = n->next) + ; + } + + if (n) { + *ret = n; + return 0; + } + } else { + /* Comparison node, doesn't exist yet? Then let's + * create it. */ + + c = new0(struct bus_match_node, 1); + if (!c) { + r = -ENOMEM; + goto fail; + } + + c->type = t; + c->parent = where; + c->next = where->child; + if (c->next) + c->next->prev = c; + where->child = c; + + if (t == BUS_MATCH_MESSAGE_TYPE) { + c->compare.children = hashmap_new(NULL); + if (!c->compare.children) { + r = -ENOMEM; + goto fail; + } + } else if (BUS_MATCH_CAN_HASH(t)) { + c->compare.children = hashmap_new(&string_hash_ops); + if (!c->compare.children) { + r = -ENOMEM; + goto fail; + } + } + } + + n = new0(struct bus_match_node, 1); + if (!n) { + r = -ENOMEM; + goto fail; + } + + n->type = BUS_MATCH_VALUE; + n->value.u8 = value_u8; + if (value_str) { + n->value.str = strdup(value_str); + if (!n->value.str) { + r = -ENOMEM; + goto fail; + } + } + + n->parent = c; + if (c->compare.children) { + + if (t == BUS_MATCH_MESSAGE_TYPE) + r = hashmap_put(c->compare.children, UINT_TO_PTR(value_u8), n); + else + r = hashmap_put(c->compare.children, n->value.str, n); + + if (r < 0) + goto fail; + } else { + n->next = c->child; + if (n->next) + n->next->prev = n; + c->child = n; + } + + *ret = n; + return 1; + +fail: + if (c) + bus_match_node_maybe_free(c); + + if (n) { + free(n->value.str); + free(n); + } + + return r; +} + +static int bus_match_find_compare_value( + struct bus_match_node *where, + enum bus_match_node_type t, + uint8_t value_u8, + const char *value_str, + struct bus_match_node **ret) { + + struct bus_match_node *c, *n; + + assert(where); + assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); + assert(BUS_MATCH_IS_COMPARE(t)); + assert(ret); + + for (c = where->child; c && c->type != t; c = c->next) + ; + + if (!c) + return 0; + + if (t == BUS_MATCH_MESSAGE_TYPE) + n = hashmap_get(c->compare.children, UINT_TO_PTR(value_u8)); + else if (BUS_MATCH_CAN_HASH(t)) + n = hashmap_get(c->compare.children, value_str); + else { + for (n = c->child; n && !value_node_same(n, t, value_u8, value_str); n = n->next) + ; + } + + if (n) { + *ret = n; + return 1; + } + + return 0; +} + +static int bus_match_add_leaf( + struct bus_match_node *where, + struct match_callback *callback) { + + struct bus_match_node *n; + + assert(where); + assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); + assert(callback); + + n = new0(struct bus_match_node, 1); + if (!n) + return -ENOMEM; + + n->type = BUS_MATCH_LEAF; + n->parent = where; + n->next = where->child; + if (n->next) + n->next->prev = n; + + n->leaf.callback = callback; + callback->match_node = n; + + where->child = n; + + return 1; +} + +static int bus_match_find_leaf( + struct bus_match_node *where, + sd_bus_message_handler_t callback, + void *userdata, + struct bus_match_node **ret) { + + struct bus_match_node *c; + + assert(where); + assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); + assert(ret); + + for (c = where->child; c; c = c->next) { + sd_bus_slot *s; + + s = container_of(c->leaf.callback, sd_bus_slot, match_callback); + + if (c->type == BUS_MATCH_LEAF && + c->leaf.callback->callback == callback && + s->userdata == userdata) { + *ret = c; + return 1; + } + } + + return 0; +} + +enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n) { + assert(k); + + if (n == 4 && startswith(k, "type")) + return BUS_MATCH_MESSAGE_TYPE; + if (n == 6 && startswith(k, "sender")) + return BUS_MATCH_SENDER; + if (n == 11 && startswith(k, "destination")) + return BUS_MATCH_DESTINATION; + if (n == 9 && startswith(k, "interface")) + return BUS_MATCH_INTERFACE; + if (n == 6 && startswith(k, "member")) + return BUS_MATCH_MEMBER; + if (n == 4 && startswith(k, "path")) + return BUS_MATCH_PATH; + if (n == 14 && startswith(k, "path_namespace")) + return BUS_MATCH_PATH_NAMESPACE; + + if (n == 4 && startswith(k, "arg")) { + int j; + + j = undecchar(k[3]); + if (j < 0) + return -EINVAL; + + return BUS_MATCH_ARG + j; + } + + if (n == 5 && startswith(k, "arg")) { + int a, b; + enum bus_match_node_type t; + + a = undecchar(k[3]); + b = undecchar(k[4]); + if (a <= 0 || b < 0) + return -EINVAL; + + t = BUS_MATCH_ARG + a * 10 + b; + if (t > BUS_MATCH_ARG_LAST) + return -EINVAL; + + return t; + } + + if (n == 8 && startswith(k, "arg") && startswith(k + 4, "path")) { + int j; + + j = undecchar(k[3]); + if (j < 0) + return -EINVAL; + + return BUS_MATCH_ARG_PATH + j; + } + + if (n == 9 && startswith(k, "arg") && startswith(k + 5, "path")) { + enum bus_match_node_type t; + int a, b; + + a = undecchar(k[3]); + b = undecchar(k[4]); + if (a <= 0 || b < 0) + return -EINVAL; + + t = BUS_MATCH_ARG_PATH + a * 10 + b; + if (t > BUS_MATCH_ARG_PATH_LAST) + return -EINVAL; + + return t; + } + + if (n == 13 && startswith(k, "arg") && startswith(k + 4, "namespace")) { + int j; + + j = undecchar(k[3]); + if (j < 0) + return -EINVAL; + + return BUS_MATCH_ARG_NAMESPACE + j; + } + + if (n == 14 && startswith(k, "arg") && startswith(k + 5, "namespace")) { + enum bus_match_node_type t; + int a, b; + + a = undecchar(k[3]); + b = undecchar(k[4]); + if (a <= 0 || b < 0) + return -EINVAL; + + t = BUS_MATCH_ARG_NAMESPACE + a * 10 + b; + if (t > BUS_MATCH_ARG_NAMESPACE_LAST) + return -EINVAL; + + return t; + } + + if (n == 7 && startswith(k, "arg") && startswith(k + 4, "has")) { + int j; + + j = undecchar(k[3]); + if (j < 0) + return -EINVAL; + + return BUS_MATCH_ARG_HAS + j; + } + + if (n == 8 && startswith(k, "arg") && startswith(k + 5, "has")) { + enum bus_match_node_type t; + int a, b; + + a = undecchar(k[3]); + b = undecchar(k[4]); + if (a <= 0 || b < 0) + return -EINVAL; + + t = BUS_MATCH_ARG_HAS + a * 10 + b; + if (t > BUS_MATCH_ARG_HAS_LAST) + return -EINVAL; + + return t; + } + + return -EINVAL; +} + +static int match_component_compare(const void *a, const void *b) { + const struct bus_match_component *x = a, *y = b; + + if (x->type < y->type) + return -1; + if (x->type > y->type) + return 1; + + return 0; +} + +void bus_match_parse_free(struct bus_match_component *components, unsigned n_components) { + unsigned i; + + for (i = 0; i < n_components; i++) + free(components[i].value_str); + + free(components); +} + +int bus_match_parse( + const char *match, + struct bus_match_component **_components, + unsigned *_n_components) { + + const char *p = match; + struct bus_match_component *components = NULL; + size_t components_allocated = 0; + unsigned n_components = 0, i; + _cleanup_free_ char *value = NULL; + int r; + + assert(match); + assert(_components); + assert(_n_components); + + while (*p != 0) { + const char *eq, *q; + enum bus_match_node_type t; + unsigned j = 0; + size_t value_allocated = 0; + bool escaped = false, quoted; + uint8_t u; + + /* Avahi's match rules appear to include whitespace, skip over it */ + p += strspn(p, " "); + + eq = strchr(p, '='); + if (!eq) + return -EINVAL; + + t = bus_match_node_type_from_string(p, eq - p); + if (t < 0) + return -EINVAL; + + quoted = eq[1] == '\''; + + for (q = eq + 1 + quoted;; q++) { + + if (*q == 0) { + + if (quoted) { + r = -EINVAL; + goto fail; + } else { + if (value) + value[j] = 0; + break; + } + } + + if (!escaped) { + if (*q == '\\') { + escaped = true; + continue; + } + + if (quoted) { + if (*q == '\'') { + if (value) + value[j] = 0; + break; + } + } else { + if (*q == ',') { + if (value) + value[j] = 0; + + break; + } + } + } + + if (!GREEDY_REALLOC(value, value_allocated, j + 2)) { + r = -ENOMEM; + goto fail; + } + + value[j++] = *q; + escaped = false; + } + + if (!value) { + value = strdup(""); + if (!value) { + r = -ENOMEM; + goto fail; + } + } + + if (t == BUS_MATCH_MESSAGE_TYPE) { + r = bus_message_type_from_string(value, &u); + if (r < 0) + goto fail; + + value = mfree(value); + } else + u = 0; + + if (!GREEDY_REALLOC(components, components_allocated, n_components + 1)) { + r = -ENOMEM; + goto fail; + } + + components[n_components].type = t; + components[n_components].value_str = value; + components[n_components].value_u8 = u; + n_components++; + + value = NULL; + + if (q[quoted] == 0) + break; + + if (q[quoted] != ',') { + r = -EINVAL; + goto fail; + } + + p = q + 1 + quoted; + } + + /* Order the whole thing, so that we always generate the same tree */ + qsort_safe(components, n_components, sizeof(struct bus_match_component), match_component_compare); + + /* Check for duplicates */ + for (i = 0; i+1 < n_components; i++) + if (components[i].type == components[i+1].type) { + r = -EINVAL; + goto fail; + } + + *_components = components; + *_n_components = n_components; + + return 0; + +fail: + bus_match_parse_free(components, n_components); + return r; +} + +char *bus_match_to_string(struct bus_match_component *components, unsigned n_components) { + _cleanup_fclose_ FILE *f = NULL; + char *buffer = NULL; + size_t size = 0; + unsigned i; + int r; + + if (n_components <= 0) + return strdup(""); + + assert(components); + + f = open_memstream(&buffer, &size); + if (!f) + return NULL; + + for (i = 0; i < n_components; i++) { + char buf[32]; + + if (i != 0) + fputc(',', f); + + fputs(bus_match_node_type_to_string(components[i].type, buf, sizeof(buf)), f); + fputc('=', f); + fputc('\'', f); + + if (components[i].type == BUS_MATCH_MESSAGE_TYPE) + fputs(bus_message_type_to_string(components[i].value_u8), f); + else + fputs(components[i].value_str, f); + + fputc('\'', f); + } + + r = fflush_and_check(f); + if (r < 0) + return NULL; + + return buffer; +} + +int bus_match_add( + struct bus_match_node *root, + struct bus_match_component *components, + unsigned n_components, + struct match_callback *callback) { + + unsigned i; + struct bus_match_node *n; + int r; + + assert(root); + assert(callback); + + n = root; + for (i = 0; i < n_components; i++) { + r = bus_match_add_compare_value( + n, components[i].type, + components[i].value_u8, components[i].value_str, &n); + if (r < 0) + return r; + } + + return bus_match_add_leaf(n, callback); +} + +int bus_match_remove( + struct bus_match_node *root, + struct match_callback *callback) { + + struct bus_match_node *node, *pp; + + assert(root); + assert(callback); + + node = callback->match_node; + if (!node) + return 0; + + assert(node->type == BUS_MATCH_LEAF); + + callback->match_node = NULL; + + /* Free the leaf */ + pp = node->parent; + bus_match_node_free(node); + + /* Prune the tree above */ + while (pp) { + node = pp; + pp = node->parent; + + if (!bus_match_node_maybe_free(node)) + break; + } + + return 1; +} + +int bus_match_find( + struct bus_match_node *root, + struct bus_match_component *components, + unsigned n_components, + sd_bus_message_handler_t callback, + void *userdata, + struct match_callback **ret) { + + struct bus_match_node *n, **gc; + unsigned i; + int r; + + assert(root); + assert(ret); + + gc = newa(struct bus_match_node*, n_components); + + n = root; + for (i = 0; i < n_components; i++) { + r = bus_match_find_compare_value( + n, components[i].type, + components[i].value_u8, components[i].value_str, + &n); + if (r <= 0) + return r; + + gc[i] = n; + } + + r = bus_match_find_leaf(n, callback, userdata, &n); + if (r <= 0) + return r; + + *ret = n->leaf.callback; + return 1; +} + +void bus_match_free(struct bus_match_node *node) { + struct bus_match_node *c; + + if (!node) + return; + + if (BUS_MATCH_CAN_HASH(node->type)) { + Iterator i; + + HASHMAP_FOREACH(c, node->compare.children, i) + bus_match_free(c); + + assert(hashmap_isempty(node->compare.children)); + } + + while ((c = node->child)) + bus_match_free(c); + + if (node->type != BUS_MATCH_ROOT) + bus_match_node_free(node); +} + +const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l) { + switch (t) { + + case BUS_MATCH_ROOT: + return "root"; + + case BUS_MATCH_VALUE: + return "value"; + + case BUS_MATCH_LEAF: + return "leaf"; + + case BUS_MATCH_MESSAGE_TYPE: + return "type"; + + case BUS_MATCH_SENDER: + return "sender"; + + case BUS_MATCH_DESTINATION: + return "destination"; + + case BUS_MATCH_INTERFACE: + return "interface"; + + case BUS_MATCH_MEMBER: + return "member"; + + case BUS_MATCH_PATH: + return "path"; + + case BUS_MATCH_PATH_NAMESPACE: + return "path_namespace"; + + case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: + snprintf(buf, l, "arg%i", t - BUS_MATCH_ARG); + return buf; + + case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: + snprintf(buf, l, "arg%ipath", t - BUS_MATCH_ARG_PATH); + return buf; + + case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: + snprintf(buf, l, "arg%inamespace", t - BUS_MATCH_ARG_NAMESPACE); + return buf; + + case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: + snprintf(buf, l, "arg%ihas", t - BUS_MATCH_ARG_HAS); + return buf; + + default: + return NULL; + } +} + +void bus_match_dump(struct bus_match_node *node, unsigned level) { + struct bus_match_node *c; + _cleanup_free_ char *pfx = NULL; + char buf[32]; + + if (!node) + return; + + pfx = strrep(" ", level); + printf("%s[%s]", strempty(pfx), bus_match_node_type_to_string(node->type, buf, sizeof(buf))); + + if (node->type == BUS_MATCH_VALUE) { + if (node->parent->type == BUS_MATCH_MESSAGE_TYPE) + printf(" <%u>\n", node->value.u8); + else + printf(" <%s>\n", node->value.str); + } else if (node->type == BUS_MATCH_ROOT) + puts(" root"); + else if (node->type == BUS_MATCH_LEAF) + printf(" %p/%p\n", node->leaf.callback->callback, container_of(node->leaf.callback, sd_bus_slot, match_callback)->userdata); + else + putchar('\n'); + + if (BUS_MATCH_CAN_HASH(node->type)) { + Iterator i; + + HASHMAP_FOREACH(c, node->compare.children, i) + bus_match_dump(c, level + 1); + } + + for (c = node->child; c; c = c->next) + bus_match_dump(c, level + 1); +} + +enum bus_match_scope bus_match_get_scope(const struct bus_match_component *components, unsigned n_components) { + bool found_driver = false; + unsigned i; + + if (n_components <= 0) + return BUS_MATCH_GENERIC; + + assert(components); + + /* Checks whether the specified match can only match the + * pseudo-service for local messages, which we detect by + * sender, interface or path. If a match is not restricted to + * local messages, then we check if it only matches on the + * driver. */ + + for (i = 0; i < n_components; i++) { + const struct bus_match_component *c = components + i; + + if (c->type == BUS_MATCH_SENDER) { + if (streq_ptr(c->value_str, "org.freedesktop.DBus.Local")) + return BUS_MATCH_LOCAL; + + if (streq_ptr(c->value_str, "org.freedesktop.DBus")) + found_driver = true; + } + + if (c->type == BUS_MATCH_INTERFACE && streq_ptr(c->value_str, "org.freedesktop.DBus.Local")) + return BUS_MATCH_LOCAL; + + if (c->type == BUS_MATCH_PATH && streq_ptr(c->value_str, "/org/freedesktop/DBus/Local")) + return BUS_MATCH_LOCAL; + } + + return found_driver ? BUS_MATCH_DRIVER : BUS_MATCH_GENERIC; + +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-match.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-match.h new file mode 100644 index 0000000000..3f71720185 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-match.h @@ -0,0 +1,100 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include "hashmap.h" + +enum bus_match_node_type { + BUS_MATCH_ROOT, + BUS_MATCH_VALUE, + BUS_MATCH_LEAF, + + /* The following are all different kinds of compare nodes */ + BUS_MATCH_SENDER, + BUS_MATCH_MESSAGE_TYPE, + BUS_MATCH_DESTINATION, + BUS_MATCH_INTERFACE, + BUS_MATCH_MEMBER, + BUS_MATCH_PATH, + BUS_MATCH_PATH_NAMESPACE, + BUS_MATCH_ARG, + BUS_MATCH_ARG_LAST = BUS_MATCH_ARG + 63, + BUS_MATCH_ARG_PATH, + BUS_MATCH_ARG_PATH_LAST = BUS_MATCH_ARG_PATH + 63, + BUS_MATCH_ARG_NAMESPACE, + BUS_MATCH_ARG_NAMESPACE_LAST = BUS_MATCH_ARG_NAMESPACE + 63, + BUS_MATCH_ARG_HAS, + BUS_MATCH_ARG_HAS_LAST = BUS_MATCH_ARG_HAS + 63, + _BUS_MATCH_NODE_TYPE_MAX, + _BUS_MATCH_NODE_TYPE_INVALID = -1 +}; + +struct bus_match_node { + enum bus_match_node_type type; + struct bus_match_node *parent, *next, *prev, *child; + + union { + struct { + char *str; + uint8_t u8; + } value; + struct { + struct match_callback *callback; + } leaf; + struct { + /* If this is set, then the child is NULL */ + Hashmap *children; + } compare; + }; +}; + +struct bus_match_component { + enum bus_match_node_type type; + uint8_t value_u8; + char *value_str; +}; + +enum bus_match_scope { + BUS_MATCH_GENERIC, + BUS_MATCH_LOCAL, + BUS_MATCH_DRIVER, +}; + +int bus_match_run(sd_bus *bus, struct bus_match_node *root, sd_bus_message *m); + +int bus_match_add(struct bus_match_node *root, struct bus_match_component *components, unsigned n_components, struct match_callback *callback); +int bus_match_remove(struct bus_match_node *root, struct match_callback *callback); + +int bus_match_find(struct bus_match_node *root, struct bus_match_component *components, unsigned n_components, sd_bus_message_handler_t callback, void *userdata, struct match_callback **ret); + +void bus_match_free(struct bus_match_node *node); + +void bus_match_dump(struct bus_match_node *node, unsigned level); + +const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l); +enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n); + +int bus_match_parse(const char *match, struct bus_match_component **_components, unsigned *_n_components); +void bus_match_parse_free(struct bus_match_component *components, unsigned n_components); +char *bus_match_to_string(struct bus_match_component *components, unsigned n_components); + +enum bus_match_scope bus_match_get_scope(const struct bus_match_component *components, unsigned n_components); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-message.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-message.c new file mode 100644 index 0000000000..6a9e87c2d8 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-message.c @@ -0,0 +1,5949 @@ +/*** + 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 . +***/ + +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-gvariant.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-signature.h" +#include "bus-type.h" +#include "bus-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "memfd-util.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "utf8.h" +#include "util.h" + +static int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored); + +static void *adjust_pointer(const void *p, void *old_base, size_t sz, void *new_base) { + + if (p == NULL) + return NULL; + + if (old_base == new_base) + return (void*) p; + + if ((uint8_t*) p < (uint8_t*) old_base) + return (void*) p; + + if ((uint8_t*) p >= (uint8_t*) old_base + sz) + return (void*) p; + + return (uint8_t*) new_base + ((uint8_t*) p - (uint8_t*) old_base); +} + +static void message_free_part(sd_bus_message *m, struct bus_body_part *part) { + assert(m); + assert(part); + + if (part->memfd >= 0) { + /* If we can reuse the memfd, try that. For that it + * can't be sealed yet. */ + + if (!part->sealed) { + assert(part->memfd_offset == 0); + assert(part->data == part->mmap_begin); + bus_kernel_push_memfd(m->bus, part->memfd, part->data, part->mapped, part->allocated); + } else { + if (part->mapped > 0) + assert_se(munmap(part->mmap_begin, part->mapped) == 0); + + safe_close(part->memfd); + } + + } else if (part->munmap_this) + munmap(part->mmap_begin, part->mapped); + else if (part->free_this) + free(part->data); + + if (part != &m->body) + free(part); +} + +static void message_reset_parts(sd_bus_message *m) { + struct bus_body_part *part; + + assert(m); + + part = &m->body; + while (m->n_body_parts > 0) { + struct bus_body_part *next = part->next; + message_free_part(m, part); + part = next; + m->n_body_parts--; + } + + m->body_end = NULL; + + m->cached_rindex_part = NULL; + m->cached_rindex_part_begin = 0; +} + +static void message_reset_containers(sd_bus_message *m) { + unsigned i; + + assert(m); + + for (i = 0; i < m->n_containers; i++) { + free(m->containers[i].signature); + free(m->containers[i].offsets); + } + + m->containers = mfree(m->containers); + + m->n_containers = m->containers_allocated = 0; + m->root_container.index = 0; +} + +static void message_free(sd_bus_message *m) { + assert(m); + + if (m->free_header) + free(m->header); + + message_reset_parts(m); + + if (m->release_kdbus) + bus_kernel_cmd_free(m->bus, (uint8_t *) m->kdbus - (uint8_t *) m->bus->kdbus_buffer); + + if (m->free_kdbus) + free(m->kdbus); + + sd_bus_unref(m->bus); + + if (m->free_fds) { + close_many(m->fds, m->n_fds); + free(m->fds); + } + + if (m->iovec != m->iovec_fixed) + free(m->iovec); + + m->destination_ptr = mfree(m->destination_ptr); + message_reset_containers(m); + free(m->root_container.signature); + free(m->root_container.offsets); + + free(m->root_container.peeked_signature); + + bus_creds_done(&m->creds); + free(m); +} + +static void *message_extend_fields(sd_bus_message *m, size_t align, size_t sz, bool add_offset) { + void *op, *np; + size_t old_size, new_size, start; + + assert(m); + + if (m->poisoned) + return NULL; + + old_size = sizeof(struct bus_header) + m->fields_size; + start = ALIGN_TO(old_size, align); + new_size = start + sz; + + if (new_size < start || + new_size > (size_t) ((uint32_t) -1)) + goto poison; + + if (old_size == new_size) + return (uint8_t*) m->header + old_size; + + if (m->free_header) { + np = realloc(m->header, ALIGN8(new_size)); + if (!np) + goto poison; + } else { + /* Initially, the header is allocated as part of of + * the sd_bus_message itself, let's replace it by + * dynamic data */ + + np = malloc(ALIGN8(new_size)); + if (!np) + goto poison; + + memcpy(np, m->header, sizeof(struct bus_header)); + } + + /* Zero out padding */ + if (start > old_size) + memzero((uint8_t*) np + old_size, start - old_size); + + op = m->header; + m->header = np; + m->fields_size = new_size - sizeof(struct bus_header); + + /* Adjust quick access pointers */ + m->path = adjust_pointer(m->path, op, old_size, m->header); + m->interface = adjust_pointer(m->interface, op, old_size, m->header); + m->member = adjust_pointer(m->member, op, old_size, m->header); + m->destination = adjust_pointer(m->destination, op, old_size, m->header); + m->sender = adjust_pointer(m->sender, op, old_size, m->header); + m->error.name = adjust_pointer(m->error.name, op, old_size, m->header); + + m->free_header = true; + + if (add_offset) { + if (m->n_header_offsets >= ELEMENTSOF(m->header_offsets)) + goto poison; + + m->header_offsets[m->n_header_offsets++] = new_size - sizeof(struct bus_header); + } + + return (uint8_t*) np + start; + +poison: + m->poisoned = true; + return NULL; +} + +static int message_append_field_string( + sd_bus_message *m, + uint64_t h, + char type, + const char *s, + const char **ret) { + + size_t l; + uint8_t *p; + + assert(m); + + /* dbus1 only allows 8bit header field ids */ + if (h > 0xFF) + return -EINVAL; + + /* dbus1 doesn't allow strings over 32bit, let's enforce this + * globally, to not risk convertability */ + l = strlen(s); + if (l > (size_t) (uint32_t) -1) + return -EINVAL; + + /* Signature "(yv)" where the variant contains "s" */ + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + + /* (field id 64bit, ((string + NUL) + NUL + signature string 's') */ + p = message_extend_fields(m, 8, 8 + l + 1 + 1 + 1, true); + if (!p) + return -ENOMEM; + + *((uint64_t*) p) = h; + memcpy(p+8, s, l); + p[8+l] = 0; + p[8+l+1] = 0; + p[8+l+2] = type; + + if (ret) + *ret = (char*) p + 8; + + } else { + /* (field id byte + (signature length + signature 's' + NUL) + (string length + string + NUL)) */ + p = message_extend_fields(m, 8, 4 + 4 + l + 1, false); + if (!p) + return -ENOMEM; + + p[0] = (uint8_t) h; + p[1] = 1; + p[2] = type; + p[3] = 0; + + ((uint32_t*) p)[1] = l; + memcpy(p + 8, s, l + 1); + + if (ret) + *ret = (char*) p + 8; + } + + return 0; +} + +static int message_append_field_signature( + sd_bus_message *m, + uint64_t h, + const char *s, + const char **ret) { + + size_t l; + uint8_t *p; + + assert(m); + + /* dbus1 only allows 8bit header field ids */ + if (h > 0xFF) + return -EINVAL; + + /* dbus1 doesn't allow signatures over 8bit, let's enforce + * this globally, to not risk convertability */ + l = strlen(s); + if (l > 255) + return -EINVAL; + + /* Signature "(yv)" where the variant contains "g" */ + + if (BUS_MESSAGE_IS_GVARIANT(m)) + /* For gvariant the serialization is the same as for normal strings */ + return message_append_field_string(m, h, 'g', s, ret); + else { + /* (field id byte + (signature length + signature 'g' + NUL) + (string length + string + NUL)) */ + p = message_extend_fields(m, 8, 4 + 1 + l + 1, false); + if (!p) + return -ENOMEM; + + p[0] = (uint8_t) h; + p[1] = 1; + p[2] = SD_BUS_TYPE_SIGNATURE; + p[3] = 0; + p[4] = l; + memcpy(p + 5, s, l + 1); + + if (ret) + *ret = (const char*) p + 5; + } + + return 0; +} + +static int message_append_field_uint32(sd_bus_message *m, uint64_t h, uint32_t x) { + uint8_t *p; + + assert(m); + + /* dbus1 only allows 8bit header field ids */ + if (h > 0xFF) + return -EINVAL; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + /* (field id 64bit + ((value + NUL + signature string 'u') */ + + p = message_extend_fields(m, 8, 8 + 4 + 1 + 1, true); + if (!p) + return -ENOMEM; + + *((uint64_t*) p) = h; + *((uint32_t*) (p + 8)) = x; + p[12] = 0; + p[13] = 'u'; + } else { + /* (field id byte + (signature length + signature 'u' + NUL) + value) */ + p = message_extend_fields(m, 8, 4 + 4, false); + if (!p) + return -ENOMEM; + + p[0] = (uint8_t) h; + p[1] = 1; + p[2] = 'u'; + p[3] = 0; + + ((uint32_t*) p)[1] = x; + } + + return 0; +} + +static int message_append_field_uint64(sd_bus_message *m, uint64_t h, uint64_t x) { + uint8_t *p; + + assert(m); + + /* dbus1 only allows 8bit header field ids */ + if (h > 0xFF) + return -EINVAL; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + /* (field id 64bit + ((value + NUL + signature string 't') */ + + p = message_extend_fields(m, 8, 8 + 8 + 1 + 1, true); + if (!p) + return -ENOMEM; + + *((uint64_t*) p) = h; + *((uint64_t*) (p + 8)) = x; + p[16] = 0; + p[17] = 't'; + } else { + /* (field id byte + (signature length + signature 't' + NUL) + 4 byte padding + value) */ + p = message_extend_fields(m, 8, 4 + 4 + 8, false); + if (!p) + return -ENOMEM; + + p[0] = (uint8_t) h; + p[1] = 1; + p[2] = 't'; + p[3] = 0; + p[4] = 0; + p[5] = 0; + p[6] = 0; + p[7] = 0; + + ((uint64_t*) p)[1] = x; + } + + return 0; +} + +static int message_append_reply_cookie(sd_bus_message *m, uint64_t cookie) { + assert(m); + + if (BUS_MESSAGE_IS_GVARIANT(m)) + return message_append_field_uint64(m, BUS_MESSAGE_HEADER_REPLY_SERIAL, cookie); + else { + /* 64bit cookies are not supported on dbus1 */ + if (cookie > 0xffffffffUL) + return -EOPNOTSUPP; + + return message_append_field_uint32(m, BUS_MESSAGE_HEADER_REPLY_SERIAL, (uint32_t) cookie); + } +} + +int bus_message_from_header( + sd_bus *bus, + void *header, + size_t header_accessible, + void *footer, + size_t footer_accessible, + size_t message_size, + int *fds, + unsigned n_fds, + const char *label, + size_t extra, + sd_bus_message **ret) { + + _cleanup_free_ sd_bus_message *m = NULL; + struct bus_header *h; + size_t a, label_sz; + + assert(bus); + assert(header || header_accessible <= 0); + assert(footer || footer_accessible <= 0); + assert(fds || n_fds <= 0); + assert(ret); + + if (header_accessible < sizeof(struct bus_header)) + return -EBADMSG; + + if (header_accessible > message_size) + return -EBADMSG; + if (footer_accessible > message_size) + return -EBADMSG; + + h = header; + if (!IN_SET(h->version, 1, 2)) + return -EBADMSG; + + if (h->type == _SD_BUS_MESSAGE_TYPE_INVALID) + return -EBADMSG; + + if (!IN_SET(h->endian, BUS_LITTLE_ENDIAN, BUS_BIG_ENDIAN)) + return -EBADMSG; + + /* Note that we are happy with unknown flags in the flags header! */ + + a = ALIGN(sizeof(sd_bus_message)) + ALIGN(extra); + + if (label) { + label_sz = strlen(label); + a += label_sz + 1; + } + + m = malloc0(a); + if (!m) + return -ENOMEM; + + m->n_ref = 1; + m->sealed = true; + m->header = header; + m->header_accessible = header_accessible; + m->footer = footer; + m->footer_accessible = footer_accessible; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + size_t ws; + + if (h->dbus2.cookie == 0) + return -EBADMSG; + + /* dbus2 derives the sizes from the message size and + the offset table at the end, since it is formatted as + gvariant "yyyyuta{tv}v". Since the message itself is a + structure with precisely to variable sized entries, + there's only one offset in the table, which marks the + end of the fields array. */ + + ws = bus_gvariant_determine_word_size(message_size, 0); + if (footer_accessible < ws) + return -EBADMSG; + + m->fields_size = bus_gvariant_read_word_le((uint8_t*) footer + footer_accessible - ws, ws); + if (ALIGN8(m->fields_size) > message_size - ws) + return -EBADMSG; + if (m->fields_size < sizeof(struct bus_header)) + return -EBADMSG; + + m->fields_size -= sizeof(struct bus_header); + m->body_size = message_size - (sizeof(struct bus_header) + ALIGN8(m->fields_size)); + } else { + if (h->dbus1.serial == 0) + return -EBADMSG; + + /* dbus1 has the sizes in the header */ + m->fields_size = BUS_MESSAGE_BSWAP32(m, h->dbus1.fields_size); + m->body_size = BUS_MESSAGE_BSWAP32(m, h->dbus1.body_size); + + if (sizeof(struct bus_header) + ALIGN8(m->fields_size) + m->body_size != message_size) + return -EBADMSG; + } + + m->fds = fds; + m->n_fds = n_fds; + + if (label) { + m->creds.label = (char*) m + ALIGN(sizeof(sd_bus_message)) + ALIGN(extra); + memcpy(m->creds.label, label, label_sz + 1); + + m->creds.mask |= SD_BUS_CREDS_SELINUX_CONTEXT; + } + + m->bus = sd_bus_ref(bus); + *ret = m; + m = NULL; + + return 0; +} + +int bus_message_from_malloc( + sd_bus *bus, + void *buffer, + size_t length, + int *fds, + unsigned n_fds, + const char *label, + sd_bus_message **ret) { + + sd_bus_message *m; + size_t sz; + int r; + + r = bus_message_from_header( + bus, + buffer, length, /* in this case the initial bytes and the final bytes are the same */ + buffer, length, + length, + fds, n_fds, + label, + 0, &m); + if (r < 0) + return r; + + sz = length - sizeof(struct bus_header) - ALIGN8(m->fields_size); + if (sz > 0) { + m->n_body_parts = 1; + m->body.data = (uint8_t*) buffer + sizeof(struct bus_header) + ALIGN8(m->fields_size); + m->body.size = sz; + m->body.sealed = true; + m->body.memfd = -1; + } + + m->n_iovec = 1; + m->iovec = m->iovec_fixed; + m->iovec[0].iov_base = buffer; + m->iovec[0].iov_len = length; + + r = bus_message_parse_fields(m); + if (r < 0) + goto fail; + + /* We take possession of the memory and fds now */ + m->free_header = true; + m->free_fds = true; + + *ret = m; + return 0; + +fail: + message_free(m); + return r; +} + +static sd_bus_message *message_new(sd_bus *bus, uint8_t type) { + sd_bus_message *m; + + assert(bus); + + m = malloc0(ALIGN(sizeof(sd_bus_message)) + sizeof(struct bus_header)); + if (!m) + return NULL; + + m->n_ref = 1; + m->header = (struct bus_header*) ((uint8_t*) m + ALIGN(sizeof(struct sd_bus_message))); + m->header->endian = BUS_NATIVE_ENDIAN; + m->header->type = type; + m->header->version = bus->message_version; + m->allow_fds = bus->can_fds || (bus->state != BUS_HELLO && bus->state != BUS_RUNNING); + m->root_container.need_offsets = BUS_MESSAGE_IS_GVARIANT(m); + m->bus = sd_bus_ref(bus); + + if (bus->allow_interactive_authorization) + m->header->flags |= BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION; + + return m; +} + +_public_ int sd_bus_message_new_signal( + sd_bus *bus, + sd_bus_message **m, + const char *path, + const char *interface, + const char *member) { + + sd_bus_message *t; + int r; + + assert_return(bus, -ENOTCONN); + assert_return(bus->state != BUS_UNSET, -ENOTCONN); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(interface_name_is_valid(interface), -EINVAL); + assert_return(member_name_is_valid(member), -EINVAL); + assert_return(m, -EINVAL); + + t = message_new(bus, SD_BUS_MESSAGE_SIGNAL); + if (!t) + return -ENOMEM; + + t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; + + r = message_append_field_string(t, BUS_MESSAGE_HEADER_PATH, SD_BUS_TYPE_OBJECT_PATH, path, &t->path); + if (r < 0) + goto fail; + r = message_append_field_string(t, BUS_MESSAGE_HEADER_INTERFACE, SD_BUS_TYPE_STRING, interface, &t->interface); + if (r < 0) + goto fail; + r = message_append_field_string(t, BUS_MESSAGE_HEADER_MEMBER, SD_BUS_TYPE_STRING, member, &t->member); + if (r < 0) + goto fail; + + *m = t; + return 0; + +fail: + sd_bus_message_unref(t); + return r; +} + +_public_ int sd_bus_message_new_method_call( + sd_bus *bus, + sd_bus_message **m, + const char *destination, + const char *path, + const char *interface, + const char *member) { + + sd_bus_message *t; + int r; + + assert_return(bus, -ENOTCONN); + assert_return(bus->state != BUS_UNSET, -ENOTCONN); + assert_return(!destination || service_name_is_valid(destination), -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!interface || interface_name_is_valid(interface), -EINVAL); + assert_return(member_name_is_valid(member), -EINVAL); + assert_return(m, -EINVAL); + + t = message_new(bus, SD_BUS_MESSAGE_METHOD_CALL); + if (!t) + return -ENOMEM; + + r = message_append_field_string(t, BUS_MESSAGE_HEADER_PATH, SD_BUS_TYPE_OBJECT_PATH, path, &t->path); + if (r < 0) + goto fail; + r = message_append_field_string(t, BUS_MESSAGE_HEADER_MEMBER, SD_BUS_TYPE_STRING, member, &t->member); + if (r < 0) + goto fail; + + if (interface) { + r = message_append_field_string(t, BUS_MESSAGE_HEADER_INTERFACE, SD_BUS_TYPE_STRING, interface, &t->interface); + if (r < 0) + goto fail; + } + + if (destination) { + r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, destination, &t->destination); + if (r < 0) + goto fail; + } + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + +static int message_new_reply( + sd_bus_message *call, + uint8_t type, + sd_bus_message **m) { + + sd_bus_message *t; + int r; + + assert_return(call, -EINVAL); + assert_return(call->sealed, -EPERM); + assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(call->bus->state != BUS_UNSET, -ENOTCONN); + assert_return(m, -EINVAL); + + t = message_new(call->bus, type); + if (!t) + return -ENOMEM; + + t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; + t->reply_cookie = BUS_MESSAGE_COOKIE(call); + if (t->reply_cookie == 0) + return -EOPNOTSUPP; + + r = message_append_reply_cookie(t, t->reply_cookie); + if (r < 0) + goto fail; + + if (call->sender) { + r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, call->sender, &t->destination); + if (r < 0) + goto fail; + } + + t->dont_send = !!(call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED); + t->enforced_reply_signature = call->enforced_reply_signature; + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + +_public_ int sd_bus_message_new_method_return( + sd_bus_message *call, + sd_bus_message **m) { + + return message_new_reply(call, SD_BUS_MESSAGE_METHOD_RETURN, m); +} + +_public_ int sd_bus_message_new_method_error( + sd_bus_message *call, + sd_bus_message **m, + const sd_bus_error *e) { + + sd_bus_message *t; + int r; + + assert_return(sd_bus_error_is_set(e), -EINVAL); + assert_return(m, -EINVAL); + + r = message_new_reply(call, SD_BUS_MESSAGE_METHOD_ERROR, &t); + if (r < 0) + return r; + + r = message_append_field_string(t, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, e->name, &t->error.name); + if (r < 0) + goto fail; + + if (e->message) { + r = message_append_basic(t, SD_BUS_TYPE_STRING, e->message, (const void**) &t->error.message); + if (r < 0) + goto fail; + } + + t->error._need_free = -1; + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + +_public_ int sd_bus_message_new_method_errorf( + sd_bus_message *call, + sd_bus_message **m, + const char *name, + const char *format, + ...) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + va_list ap; + + assert_return(name, -EINVAL); + assert_return(m, -EINVAL); + + va_start(ap, format); + bus_error_setfv(&error, name, format, ap); + va_end(ap); + + return sd_bus_message_new_method_error(call, m, &error); +} + +_public_ int sd_bus_message_new_method_errno( + sd_bus_message *call, + sd_bus_message **m, + int error, + const sd_bus_error *p) { + + _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; + + if (sd_bus_error_is_set(p)) + return sd_bus_message_new_method_error(call, m, p); + + sd_bus_error_set_errno(&berror, error); + + return sd_bus_message_new_method_error(call, m, &berror); +} + +_public_ int sd_bus_message_new_method_errnof( + sd_bus_message *call, + sd_bus_message **m, + int error, + const char *format, + ...) { + + _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; + va_list ap; + + va_start(ap, format); + sd_bus_error_set_errnofv(&berror, error, format, ap); + va_end(ap); + + return sd_bus_message_new_method_error(call, m, &berror); +} + +void bus_message_set_sender_local(sd_bus *bus, sd_bus_message *m) { + assert(bus); + assert(m); + + m->sender = m->creds.unique_name = (char*) "org.freedesktop.DBus.Local"; + m->creds.well_known_names_local = true; + m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask; +} + +void bus_message_set_sender_driver(sd_bus *bus, sd_bus_message *m) { + assert(bus); + assert(m); + + m->sender = m->creds.unique_name = (char*) "org.freedesktop.DBus"; + m->creds.well_known_names_driver = true; + m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask; +} + +int bus_message_new_synthetic_error( + sd_bus *bus, + uint64_t cookie, + const sd_bus_error *e, + sd_bus_message **m) { + + sd_bus_message *t; + int r; + + assert(bus); + assert(sd_bus_error_is_set(e)); + assert(m); + + t = message_new(bus, SD_BUS_MESSAGE_METHOD_ERROR); + if (!t) + return -ENOMEM; + + t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; + t->reply_cookie = cookie; + + r = message_append_reply_cookie(t, t->reply_cookie); + if (r < 0) + goto fail; + + if (bus && bus->unique_name) { + r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, bus->unique_name, &t->destination); + if (r < 0) + goto fail; + } + + r = message_append_field_string(t, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, e->name, &t->error.name); + if (r < 0) + goto fail; + + if (e->message) { + r = message_append_basic(t, SD_BUS_TYPE_STRING, e->message, (const void**) &t->error.message); + if (r < 0) + goto fail; + } + + t->error._need_free = -1; + + bus_message_set_sender_driver(bus, t); + + *m = t; + return 0; + +fail: + message_free(t); + return r; +} + +_public_ sd_bus_message* sd_bus_message_ref(sd_bus_message *m) { + + if (!m) + return NULL; + + assert(m->n_ref > 0); + m->n_ref++; + + return m; +} + +_public_ sd_bus_message* sd_bus_message_unref(sd_bus_message *m) { + + if (!m) + return NULL; + + assert(m->n_ref > 0); + m->n_ref--; + + if (m->n_ref > 0) + return NULL; + + message_free(m); + return NULL; +} + +_public_ int sd_bus_message_get_type(sd_bus_message *m, uint8_t *type) { + assert_return(m, -EINVAL); + assert_return(type, -EINVAL); + + *type = m->header->type; + return 0; +} + +_public_ int sd_bus_message_get_cookie(sd_bus_message *m, uint64_t *cookie) { + uint64_t c; + + assert_return(m, -EINVAL); + assert_return(cookie, -EINVAL); + + c = BUS_MESSAGE_COOKIE(m); + if (c == 0) + return -ENODATA; + + *cookie = BUS_MESSAGE_COOKIE(m); + return 0; +} + +_public_ int sd_bus_message_get_reply_cookie(sd_bus_message *m, uint64_t *cookie) { + assert_return(m, -EINVAL); + assert_return(cookie, -EINVAL); + + if (m->reply_cookie == 0) + return -ENODATA; + + *cookie = m->reply_cookie; + return 0; +} + +_public_ int sd_bus_message_get_expect_reply(sd_bus_message *m) { + assert_return(m, -EINVAL); + + return m->header->type == SD_BUS_MESSAGE_METHOD_CALL && + !(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED); +} + +_public_ int sd_bus_message_get_auto_start(sd_bus_message *m) { + assert_return(m, -EINVAL); + + return !(m->header->flags & BUS_MESSAGE_NO_AUTO_START); +} + +_public_ int sd_bus_message_get_allow_interactive_authorization(sd_bus_message *m) { + assert_return(m, -EINVAL); + + return m->header->type == SD_BUS_MESSAGE_METHOD_CALL && + (m->header->flags & BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION); +} + +_public_ const char *sd_bus_message_get_path(sd_bus_message *m) { + assert_return(m, NULL); + + return m->path; +} + +_public_ const char *sd_bus_message_get_interface(sd_bus_message *m) { + assert_return(m, NULL); + + return m->interface; +} + +_public_ const char *sd_bus_message_get_member(sd_bus_message *m) { + assert_return(m, NULL); + + return m->member; +} + +_public_ const char *sd_bus_message_get_destination(sd_bus_message *m) { + assert_return(m, NULL); + + return m->destination; +} + +_public_ const char *sd_bus_message_get_sender(sd_bus_message *m) { + assert_return(m, NULL); + + return m->sender; +} + +_public_ const sd_bus_error *sd_bus_message_get_error(sd_bus_message *m) { + assert_return(m, NULL); + + if (!sd_bus_error_is_set(&m->error)) + return NULL; + + return &m->error; +} + +_public_ int sd_bus_message_get_monotonic_usec(sd_bus_message *m, uint64_t *usec) { + assert_return(m, -EINVAL); + assert_return(usec, -EINVAL); + + if (m->monotonic <= 0) + return -ENODATA; + + *usec = m->monotonic; + return 0; +} + +_public_ int sd_bus_message_get_realtime_usec(sd_bus_message *m, uint64_t *usec) { + assert_return(m, -EINVAL); + assert_return(usec, -EINVAL); + + if (m->realtime <= 0) + return -ENODATA; + + *usec = m->realtime; + return 0; +} + +_public_ int sd_bus_message_get_seqnum(sd_bus_message *m, uint64_t *seqnum) { + assert_return(m, -EINVAL); + assert_return(seqnum, -EINVAL); + + if (m->seqnum <= 0) + return -ENODATA; + + *seqnum = m->seqnum; + return 0; +} + +_public_ sd_bus_creds *sd_bus_message_get_creds(sd_bus_message *m) { + assert_return(m, NULL); + + if (m->creds.mask == 0) + return NULL; + + return &m->creds; +} + +_public_ int sd_bus_message_is_signal( + sd_bus_message *m, + const char *interface, + const char *member) { + + assert_return(m, -EINVAL); + + if (m->header->type != SD_BUS_MESSAGE_SIGNAL) + return 0; + + if (interface && (!m->interface || !streq(m->interface, interface))) + return 0; + + if (member && (!m->member || !streq(m->member, member))) + return 0; + + return 1; +} + +_public_ int sd_bus_message_is_method_call( + sd_bus_message *m, + const char *interface, + const char *member) { + + assert_return(m, -EINVAL); + + if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) + return 0; + + if (interface && (!m->interface || !streq(m->interface, interface))) + return 0; + + if (member && (!m->member || !streq(m->member, member))) + return 0; + + return 1; +} + +_public_ int sd_bus_message_is_method_error(sd_bus_message *m, const char *name) { + assert_return(m, -EINVAL); + + if (m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) + return 0; + + if (name && (!m->error.name || !streq(m->error.name, name))) + return 0; + + return 1; +} + +_public_ int sd_bus_message_set_expect_reply(sd_bus_message *m, int b) { + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EPERM); + + if (b) + m->header->flags &= ~BUS_MESSAGE_NO_REPLY_EXPECTED; + else + m->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; + + return 0; +} + +_public_ int sd_bus_message_set_auto_start(sd_bus_message *m, int b) { + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + if (b) + m->header->flags &= ~BUS_MESSAGE_NO_AUTO_START; + else + m->header->flags |= BUS_MESSAGE_NO_AUTO_START; + + return 0; +} + +_public_ int sd_bus_message_set_allow_interactive_authorization(sd_bus_message *m, int b) { + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + if (b) + m->header->flags |= BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION; + else + m->header->flags &= ~BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION; + + return 0; +} + +static struct bus_container *message_get_container(sd_bus_message *m) { + assert(m); + + if (m->n_containers == 0) + return &m->root_container; + + assert(m->containers); + return m->containers + m->n_containers - 1; +} + +struct bus_body_part *message_append_part(sd_bus_message *m) { + struct bus_body_part *part; + + assert(m); + + if (m->poisoned) + return NULL; + + if (m->n_body_parts <= 0) { + part = &m->body; + zero(*part); + } else { + assert(m->body_end); + + part = new0(struct bus_body_part, 1); + if (!part) { + m->poisoned = true; + return NULL; + } + + m->body_end->next = part; + } + + part->memfd = -1; + m->body_end = part; + m->n_body_parts ++; + + return part; +} + +static void part_zero(struct bus_body_part *part, size_t sz) { + assert(part); + assert(sz > 0); + assert(sz < 8); + + /* All other fields can be left in their defaults */ + assert(!part->data); + assert(part->memfd < 0); + + part->size = sz; + part->is_zero = true; + part->sealed = true; +} + +static int part_make_space( + struct sd_bus_message *m, + struct bus_body_part *part, + size_t sz, + void **q) { + + void *n; + int r; + + assert(m); + assert(part); + assert(!part->sealed); + + if (m->poisoned) + return -ENOMEM; + + if (!part->data && part->memfd < 0) { + part->memfd = bus_kernel_pop_memfd(m->bus, &part->data, &part->mapped, &part->allocated); + part->mmap_begin = part->data; + } + + if (part->memfd >= 0) { + + if (part->allocated == 0 || sz > part->allocated) { + uint64_t new_allocated; + + new_allocated = PAGE_ALIGN(sz > 0 ? 2 * sz : 1); + r = memfd_set_size(part->memfd, new_allocated); + if (r < 0) { + m->poisoned = true; + return r; + } + + part->allocated = new_allocated; + } + + if (!part->data || sz > part->mapped) { + size_t psz; + + psz = PAGE_ALIGN(sz > 0 ? sz : 1); + if (part->mapped <= 0) + n = mmap(NULL, psz, PROT_READ|PROT_WRITE, MAP_SHARED, part->memfd, 0); + else + n = mremap(part->mmap_begin, part->mapped, psz, MREMAP_MAYMOVE); + + if (n == MAP_FAILED) { + m->poisoned = true; + return -errno; + } + + part->mmap_begin = part->data = n; + part->mapped = psz; + part->memfd_offset = 0; + } + + part->munmap_this = true; + } else { + if (part->allocated == 0 || sz > part->allocated) { + size_t new_allocated; + + new_allocated = sz > 0 ? 2 * sz : 64; + n = realloc(part->data, new_allocated); + if (!n) { + m->poisoned = true; + return -ENOMEM; + } + + part->data = n; + part->allocated = new_allocated; + part->free_this = true; + } + } + + if (q) + *q = part->data ? (uint8_t*) part->data + part->size : NULL; + + part->size = sz; + return 0; +} + +static int message_add_offset(sd_bus_message *m, size_t offset) { + struct bus_container *c; + + assert(m); + assert(BUS_MESSAGE_IS_GVARIANT(m)); + + /* Add offset to current container, unless this is the first + * item in it, which will have the 0 offset, which we can + * ignore. */ + c = message_get_container(m); + + if (!c->need_offsets) + return 0; + + if (!GREEDY_REALLOC(c->offsets, c->offsets_allocated, c->n_offsets + 1)) + return -ENOMEM; + + c->offsets[c->n_offsets++] = offset; + return 0; +} + +static void message_extend_containers(sd_bus_message *m, size_t expand) { + struct bus_container *c; + + assert(m); + + if (expand <= 0) + return; + + /* Update counters */ + for (c = m->containers; c < m->containers + m->n_containers; c++) { + + if (c->array_size) + *c->array_size += expand; + } +} + +static void *message_extend_body( + sd_bus_message *m, + size_t align, + size_t sz, + bool add_offset, + bool force_inline) { + + size_t start_body, end_body, padding, added; + void *p; + int r; + + assert(m); + assert(align > 0); + assert(!m->sealed); + + if (m->poisoned) + return NULL; + + start_body = ALIGN_TO((size_t) m->body_size, align); + end_body = start_body + sz; + + padding = start_body - m->body_size; + added = padding + sz; + + /* Check for 32bit overflows */ + if (end_body > (size_t) ((uint32_t) -1) || + end_body < start_body) { + m->poisoned = true; + return NULL; + } + + if (added > 0) { + struct bus_body_part *part = NULL; + bool add_new_part; + + add_new_part = + m->n_body_parts <= 0 || + m->body_end->sealed || + (padding != ALIGN_TO(m->body_end->size, align) - m->body_end->size) || + (force_inline && m->body_end->size > MEMFD_MIN_SIZE); /* if this must be an inlined extension, let's create a new part if the previous part is large enough to be inlined */ + + if (add_new_part) { + if (padding > 0) { + part = message_append_part(m); + if (!part) + return NULL; + + part_zero(part, padding); + } + + part = message_append_part(m); + if (!part) + return NULL; + + r = part_make_space(m, part, sz, &p); + if (r < 0) + return NULL; + } else { + struct bus_container *c; + void *op; + size_t os, start_part, end_part; + + part = m->body_end; + op = part->data; + os = part->size; + + start_part = ALIGN_TO(part->size, align); + end_part = start_part + sz; + + r = part_make_space(m, part, end_part, &p); + if (r < 0) + return NULL; + + if (padding > 0) { + memzero(p, padding); + p = (uint8_t*) p + padding; + } + + /* Readjust pointers */ + for (c = m->containers; c < m->containers + m->n_containers; c++) + c->array_size = adjust_pointer(c->array_size, op, os, part->data); + + m->error.message = (const char*) adjust_pointer(m->error.message, op, os, part->data); + } + } else + /* Return something that is not NULL and is aligned */ + p = (uint8_t *) NULL + align; + + m->body_size = end_body; + message_extend_containers(m, added); + + if (add_offset) { + r = message_add_offset(m, end_body); + if (r < 0) { + m->poisoned = true; + return NULL; + } + } + + return p; +} + +static int message_push_fd(sd_bus_message *m, int fd) { + int *f, copy; + + assert(m); + + if (fd < 0) + return -EINVAL; + + if (!m->allow_fds) + return -EOPNOTSUPP; + + copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); + if (copy < 0) + return -errno; + + f = realloc(m->fds, sizeof(int) * (m->n_fds + 1)); + if (!f) { + m->poisoned = true; + safe_close(copy); + return -ENOMEM; + } + + m->fds = f; + m->fds[m->n_fds] = copy; + m->free_fds = true; + + return copy; +} + +int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored) { + _cleanup_close_ int fd = -1; + struct bus_container *c; + ssize_t align, sz; + void *a; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(bus_type_is_basic(type), -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + c = message_get_container(m); + + if (c->signature && c->signature[c->index]) { + /* Container signature is already set */ + + if (c->signature[c->index] != type) + return -ENXIO; + } else { + char *e; + + /* Maybe we can append to the signature? But only if this is the top-level container */ + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(type), NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + uint8_t u8; + uint32_t u32; + + switch (type) { + + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_STRING: + p = strempty(p); + + /* Fall through... */ + case SD_BUS_TYPE_OBJECT_PATH: + if (!p) + return -EINVAL; + + align = 1; + sz = strlen(p) + 1; + break; + + case SD_BUS_TYPE_BOOLEAN: + + u8 = p && *(int*) p; + p = &u8; + + align = sz = 1; + break; + + case SD_BUS_TYPE_UNIX_FD: + + if (!p) + return -EINVAL; + + fd = message_push_fd(m, *(int*) p); + if (fd < 0) + return fd; + + u32 = m->n_fds; + p = &u32; + + align = sz = 4; + break; + + default: + align = bus_gvariant_get_alignment(CHAR_TO_STR(type)); + sz = bus_gvariant_get_size(CHAR_TO_STR(type)); + break; + } + + assert(align > 0); + assert(sz > 0); + + a = message_extend_body(m, align, sz, true, false); + if (!a) + return -ENOMEM; + + memcpy(a, p, sz); + + if (stored) + *stored = (const uint8_t*) a; + + } else { + uint32_t u32; + + switch (type) { + + case SD_BUS_TYPE_STRING: + /* To make things easy we'll serialize a NULL string + * into the empty string */ + p = strempty(p); + + /* Fall through... */ + case SD_BUS_TYPE_OBJECT_PATH: + + if (!p) + return -EINVAL; + + align = 4; + sz = 4 + strlen(p) + 1; + break; + + case SD_BUS_TYPE_SIGNATURE: + + p = strempty(p); + + align = 1; + sz = 1 + strlen(p) + 1; + break; + + case SD_BUS_TYPE_BOOLEAN: + + u32 = p && *(int*) p; + p = &u32; + + align = sz = 4; + break; + + case SD_BUS_TYPE_UNIX_FD: + + if (!p) + return -EINVAL; + + fd = message_push_fd(m, *(int*) p); + if (fd < 0) + return fd; + + u32 = m->n_fds; + p = &u32; + + align = sz = 4; + break; + + default: + align = bus_type_get_alignment(type); + sz = bus_type_get_size(type); + break; + } + + assert(align > 0); + assert(sz > 0); + + a = message_extend_body(m, align, sz, false, false); + if (!a) + return -ENOMEM; + + if (type == SD_BUS_TYPE_STRING || type == SD_BUS_TYPE_OBJECT_PATH) { + *(uint32_t*) a = sz - 5; + memcpy((uint8_t*) a + 4, p, sz - 4); + + if (stored) + *stored = (const uint8_t*) a + 4; + + } else if (type == SD_BUS_TYPE_SIGNATURE) { + *(uint8_t*) a = sz - 2; + memcpy((uint8_t*) a + 1, p, sz - 1); + + if (stored) + *stored = (const uint8_t*) a + 1; + } else { + memcpy(a, p, sz); + + if (stored) + *stored = a; + } + } + + if (type == SD_BUS_TYPE_UNIX_FD) + m->n_fds ++; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + fd = -1; + return 0; +} + +_public_ int sd_bus_message_append_basic(sd_bus_message *m, char type, const void *p) { + return message_append_basic(m, type, p, NULL); +} + +_public_ int sd_bus_message_append_string_space( + sd_bus_message *m, + size_t size, + char **s) { + + struct bus_container *c; + void *a; + + assert_return(m, -EINVAL); + assert_return(s, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(!m->poisoned, -ESTALE); + + c = message_get_container(m); + + if (c->signature && c->signature[c->index]) { + /* Container signature is already set */ + + if (c->signature[c->index] != SD_BUS_TYPE_STRING) + return -ENXIO; + } else { + char *e; + + /* Maybe we can append to the signature? But only if this is the top-level container */ + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRING), NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + a = message_extend_body(m, 1, size + 1, true, false); + if (!a) + return -ENOMEM; + + *s = a; + } else { + a = message_extend_body(m, 4, 4 + size + 1, false, false); + if (!a) + return -ENOMEM; + + *(uint32_t*) a = size; + *s = (char*) a + 4; + } + + (*s)[size] = 0; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 0; +} + +_public_ int sd_bus_message_append_string_iovec( + sd_bus_message *m, + const struct iovec *iov, + unsigned n) { + + size_t size; + unsigned i; + char *p; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(iov || n == 0, -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + size = IOVEC_TOTAL_SIZE(iov, n); + + r = sd_bus_message_append_string_space(m, size, &p); + if (r < 0) + return r; + + for (i = 0; i < n; i++) { + + if (iov[i].iov_base) + memcpy(p, iov[i].iov_base, iov[i].iov_len); + else + memset(p, ' ', iov[i].iov_len); + + p += iov[i].iov_len; + } + + return 0; +} + +static int bus_message_open_array( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + uint32_t **array_size, + size_t *begin, + bool *need_offsets) { + + unsigned nindex; + int alignment, r; + + assert(m); + assert(c); + assert(contents); + assert(array_size); + assert(begin); + assert(need_offsets); + + if (!signature_is_single(contents, true)) + return -EINVAL; + + if (c->signature && c->signature[c->index]) { + + /* Verify the existing signature */ + + if (c->signature[c->index] != SD_BUS_TYPE_ARRAY) + return -ENXIO; + + if (!startswith(c->signature + c->index + 1, contents)) + return -ENXIO; + + nindex = c->index + 1 + strlen(contents); + } else { + char *e; + + if (c->enclosing != 0) + return -ENXIO; + + /* Extend the existing signature */ + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_ARRAY), contents, NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + + nindex = e - c->signature; + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + alignment = bus_gvariant_get_alignment(contents); + if (alignment < 0) + return alignment; + + /* Add alignment padding and add to offset list */ + if (!message_extend_body(m, alignment, 0, false, false)) + return -ENOMEM; + + r = bus_gvariant_is_fixed_size(contents); + if (r < 0) + return r; + + *begin = m->body_size; + *need_offsets = r == 0; + } else { + void *a, *op; + size_t os; + struct bus_body_part *o; + + alignment = bus_type_get_alignment(contents[0]); + if (alignment < 0) + return alignment; + + a = message_extend_body(m, 4, 4, false, false); + if (!a) + return -ENOMEM; + + o = m->body_end; + op = m->body_end->data; + os = m->body_end->size; + + /* Add alignment between size and first element */ + if (!message_extend_body(m, alignment, 0, false, false)) + return -ENOMEM; + + /* location of array size might have changed so let's readjust a */ + if (o == m->body_end) + a = adjust_pointer(a, op, os, m->body_end->data); + + *(uint32_t*) a = 0; + *array_size = a; + } + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index = nindex; + + return 0; +} + +static int bus_message_open_variant( + sd_bus_message *m, + struct bus_container *c, + const char *contents) { + + assert(m); + assert(c); + assert(contents); + + if (!signature_is_single(contents, false)) + return -EINVAL; + + if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN) + return -EINVAL; + + if (c->signature && c->signature[c->index]) { + + if (c->signature[c->index] != SD_BUS_TYPE_VARIANT) + return -ENXIO; + + } else { + char *e; + + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_VARIANT), NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + /* Variants are always aligned to 8 */ + + if (!message_extend_body(m, 8, 0, false, false)) + return -ENOMEM; + + } else { + size_t l; + void *a; + + l = strlen(contents); + a = message_extend_body(m, 1, 1 + l + 1, false, false); + if (!a) + return -ENOMEM; + + *(uint8_t*) a = l; + memcpy((uint8_t*) a + 1, contents, l + 1); + } + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 0; +} + +static int bus_message_open_struct( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + size_t *begin, + bool *need_offsets) { + + size_t nindex; + int r; + + assert(m); + assert(c); + assert(contents); + assert(begin); + assert(need_offsets); + + if (!signature_is_valid(contents, false)) + return -EINVAL; + + if (c->signature && c->signature[c->index]) { + size_t l; + + l = strlen(contents); + + if (c->signature[c->index] != SD_BUS_TYPE_STRUCT_BEGIN || + !startswith(c->signature + c->index + 1, contents) || + c->signature[c->index + 1 + l] != SD_BUS_TYPE_STRUCT_END) + return -ENXIO; + + nindex = c->index + 1 + l + 1; + } else { + char *e; + + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_BEGIN), contents, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_END), NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + + nindex = e - c->signature; + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + int alignment; + + alignment = bus_gvariant_get_alignment(contents); + if (alignment < 0) + return alignment; + + if (!message_extend_body(m, alignment, 0, false, false)) + return -ENOMEM; + + r = bus_gvariant_is_fixed_size(contents); + if (r < 0) + return r; + + *begin = m->body_size; + *need_offsets = r == 0; + } else { + /* Align contents to 8 byte boundary */ + if (!message_extend_body(m, 8, 0, false, false)) + return -ENOMEM; + } + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index = nindex; + + return 0; +} + +static int bus_message_open_dict_entry( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + size_t *begin, + bool *need_offsets) { + + int r; + + assert(m); + assert(c); + assert(contents); + assert(begin); + assert(need_offsets); + + if (!signature_is_pair(contents)) + return -EINVAL; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + return -ENXIO; + + if (c->signature && c->signature[c->index]) { + size_t l; + + l = strlen(contents); + + if (c->signature[c->index] != SD_BUS_TYPE_DICT_ENTRY_BEGIN || + !startswith(c->signature + c->index + 1, contents) || + c->signature[c->index + 1 + l] != SD_BUS_TYPE_DICT_ENTRY_END) + return -ENXIO; + } else + return -ENXIO; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + int alignment; + + alignment = bus_gvariant_get_alignment(contents); + if (alignment < 0) + return alignment; + + if (!message_extend_body(m, alignment, 0, false, false)) + return -ENOMEM; + + r = bus_gvariant_is_fixed_size(contents); + if (r < 0) + return r; + + *begin = m->body_size; + *need_offsets = r == 0; + } else { + /* Align contents to 8 byte boundary */ + if (!message_extend_body(m, 8, 0, false, false)) + return -ENOMEM; + } + + return 0; +} + +_public_ int sd_bus_message_open_container( + sd_bus_message *m, + char type, + const char *contents) { + + struct bus_container *c, *w; + uint32_t *array_size = NULL; + char *signature; + size_t before, begin = 0; + bool need_offsets = false; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(contents, -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + /* Make sure we have space for one more container */ + if (!GREEDY_REALLOC(m->containers, m->containers_allocated, m->n_containers + 1)) { + m->poisoned = true; + return -ENOMEM; + } + + c = message_get_container(m); + + signature = strdup(contents); + if (!signature) { + m->poisoned = true; + return -ENOMEM; + } + + /* Save old index in the parent container, in case we have to + * abort this container */ + c->saved_index = c->index; + before = m->body_size; + + if (type == SD_BUS_TYPE_ARRAY) + r = bus_message_open_array(m, c, contents, &array_size, &begin, &need_offsets); + else if (type == SD_BUS_TYPE_VARIANT) + r = bus_message_open_variant(m, c, contents); + else if (type == SD_BUS_TYPE_STRUCT) + r = bus_message_open_struct(m, c, contents, &begin, &need_offsets); + else if (type == SD_BUS_TYPE_DICT_ENTRY) + r = bus_message_open_dict_entry(m, c, contents, &begin, &need_offsets); + else + r = -EINVAL; + + if (r < 0) { + free(signature); + return r; + } + + /* OK, let's fill it in */ + w = m->containers + m->n_containers++; + w->enclosing = type; + w->signature = signature; + w->index = 0; + w->array_size = array_size; + w->before = before; + w->begin = begin; + w->n_offsets = w->offsets_allocated = 0; + w->offsets = NULL; + w->need_offsets = need_offsets; + + return 0; +} + +static int bus_message_close_array(sd_bus_message *m, struct bus_container *c) { + + assert(m); + assert(c); + + if (!BUS_MESSAGE_IS_GVARIANT(m)) + return 0; + + if (c->need_offsets) { + size_t payload, sz, i; + uint8_t *a; + + /* Variable-width arrays */ + + payload = c->n_offsets > 0 ? c->offsets[c->n_offsets-1] - c->begin : 0; + sz = bus_gvariant_determine_word_size(payload, c->n_offsets); + + a = message_extend_body(m, 1, sz * c->n_offsets, true, false); + if (!a) + return -ENOMEM; + + for (i = 0; i < c->n_offsets; i++) + bus_gvariant_write_word_le(a + sz*i, sz, c->offsets[i] - c->begin); + } else { + void *a; + + /* Fixed-width or empty arrays */ + + a = message_extend_body(m, 1, 0, true, false); /* let's add offset to parent */ + if (!a) + return -ENOMEM; + } + + return 0; +} + +static int bus_message_close_variant(sd_bus_message *m, struct bus_container *c) { + uint8_t *a; + size_t l; + + assert(m); + assert(c); + assert(c->signature); + + if (!BUS_MESSAGE_IS_GVARIANT(m)) + return 0; + + l = strlen(c->signature); + + a = message_extend_body(m, 1, 1 + l, true, false); + if (!a) + return -ENOMEM; + + a[0] = 0; + memcpy(a+1, c->signature, l); + + return 0; +} + +static int bus_message_close_struct(sd_bus_message *m, struct bus_container *c, bool add_offset) { + bool fixed_size = true; + size_t n_variable = 0; + unsigned i = 0; + const char *p; + uint8_t *a; + int r; + + assert(m); + assert(c); + + if (!BUS_MESSAGE_IS_GVARIANT(m)) + return 0; + + p = strempty(c->signature); + while (*p != 0) { + size_t n; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + else { + char t[n+1]; + + memcpy(t, p, n); + t[n] = 0; + + r = bus_gvariant_is_fixed_size(t); + if (r < 0) + return r; + } + + assert(!c->need_offsets || i <= c->n_offsets); + + /* We need to add an offset for each item that has a + * variable size and that is not the last one in the + * list */ + if (r == 0) + fixed_size = false; + if (r == 0 && p[n] != 0) + n_variable++; + + i++; + p += n; + } + + assert(!c->need_offsets || i == c->n_offsets); + assert(c->need_offsets || n_variable == 0); + + if (isempty(c->signature)) { + /* The unary type is encoded as fixed 1 byte padding */ + a = message_extend_body(m, 1, 1, add_offset, false); + if (!a) + return -ENOMEM; + + *a = 0; + } else if (n_variable <= 0) { + int alignment = 1; + + /* Structures with fixed-size members only have to be + * fixed-size themselves. But gvariant requires all fixed-size + * elements to be sized a multiple of their alignment. Hence, + * we must *always* add final padding after the last member so + * the overall size of the structure is properly aligned. */ + if (fixed_size) + alignment = bus_gvariant_get_alignment(strempty(c->signature)); + + assert(alignment > 0); + + a = message_extend_body(m, alignment, 0, add_offset, false); + if (!a) + return -ENOMEM; + } else { + size_t sz; + unsigned j; + + assert(c->offsets[c->n_offsets-1] == m->body_size); + + sz = bus_gvariant_determine_word_size(m->body_size - c->begin, n_variable); + + a = message_extend_body(m, 1, sz * n_variable, add_offset, false); + if (!a) + return -ENOMEM; + + p = strempty(c->signature); + for (i = 0, j = 0; i < c->n_offsets; i++) { + unsigned k; + size_t n; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + else { + char t[n+1]; + + memcpy(t, p, n); + t[n] = 0; + + p += n; + + r = bus_gvariant_is_fixed_size(t); + if (r < 0) + return r; + if (r > 0 || p[0] == 0) + continue; + } + + k = n_variable - 1 - j; + + bus_gvariant_write_word_le(a + k * sz, sz, c->offsets[i] - c->begin); + + j++; + } + } + + return 0; +} + +_public_ int sd_bus_message_close_container(sd_bus_message *m) { + struct bus_container *c; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(m->n_containers > 0, -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + c = message_get_container(m); + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + if (c->signature && c->signature[c->index] != 0) + return -EINVAL; + + m->n_containers--; + + if (c->enclosing == SD_BUS_TYPE_ARRAY) + r = bus_message_close_array(m, c); + else if (c->enclosing == SD_BUS_TYPE_VARIANT) + r = bus_message_close_variant(m, c); + else if (c->enclosing == SD_BUS_TYPE_STRUCT || c->enclosing == SD_BUS_TYPE_DICT_ENTRY) + r = bus_message_close_struct(m, c, true); + else + assert_not_reached("Unknown container type"); + + free(c->signature); + free(c->offsets); + + return r; +} + +typedef struct { + const char *types; + unsigned n_struct; + unsigned n_array; +} TypeStack; + +static int type_stack_push(TypeStack *stack, unsigned max, unsigned *i, const char *types, unsigned n_struct, unsigned n_array) { + assert(stack); + assert(max > 0); + + if (*i >= max) + return -EINVAL; + + stack[*i].types = types; + stack[*i].n_struct = n_struct; + stack[*i].n_array = n_array; + (*i)++; + + return 0; +} + +static int type_stack_pop(TypeStack *stack, unsigned max, unsigned *i, const char **types, unsigned *n_struct, unsigned *n_array) { + assert(stack); + assert(max > 0); + assert(types); + assert(n_struct); + assert(n_array); + + if (*i <= 0) + return 0; + + (*i)--; + *types = stack[*i].types; + *n_struct = stack[*i].n_struct; + *n_array = stack[*i].n_array; + + return 1; +} + +int bus_message_append_ap( + sd_bus_message *m, + const char *types, + va_list ap) { + + unsigned n_array, n_struct; + TypeStack stack[BUS_CONTAINER_DEPTH]; + unsigned stack_ptr = 0; + int r; + + assert(m); + + if (!types) + return 0; + + n_array = (unsigned) -1; + n_struct = strlen(types); + + for (;;) { + const char *t; + + if (n_array == 0 || (n_array == (unsigned) -1 && n_struct == 0)) { + r = type_stack_pop(stack, ELEMENTSOF(stack), &stack_ptr, &types, &n_struct, &n_array); + if (r < 0) + return r; + if (r == 0) + break; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + continue; + } + + t = types; + if (n_array != (unsigned) -1) + n_array --; + else { + types ++; + n_struct--; + } + + switch (*t) { + + case SD_BUS_TYPE_BYTE: { + uint8_t x; + + x = (uint8_t) va_arg(ap, int); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_UNIX_FD: { + uint32_t x; + + /* We assume a boolean is the same as int32_t */ + assert_cc(sizeof(int32_t) == sizeof(int)); + + x = va_arg(ap, uint32_t); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: { + uint16_t x; + + x = (uint16_t) va_arg(ap, int); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: { + uint64_t x; + + x = va_arg(ap, uint64_t); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_DOUBLE: { + double x; + + x = va_arg(ap, double); + r = sd_bus_message_append_basic(m, *t, &x); + break; + } + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: { + const char *x; + + x = va_arg(ap, const char*); + r = sd_bus_message_append_basic(m, *t, x); + break; + } + + case SD_BUS_TYPE_ARRAY: { + size_t k; + + r = signature_element_length(t + 1, &k); + if (r < 0) + return r; + + { + char s[k + 1]; + memcpy(s, t + 1, k); + s[k] = 0; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, s); + if (r < 0) + return r; + } + + if (n_array == (unsigned) -1) { + types += k; + n_struct -= k; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = t + 1; + n_struct = k; + n_array = va_arg(ap, unsigned); + + break; + } + + case SD_BUS_TYPE_VARIANT: { + const char *s; + + s = va_arg(ap, const char*); + if (!s) + return -EINVAL; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, s); + if (r < 0) + return r; + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = s; + n_struct = strlen(s); + n_array = (unsigned) -1; + + break; + } + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + size_t k; + + r = signature_element_length(t, &k); + if (r < 0) + return r; + + { + char s[k - 1]; + + memcpy(s, t + 1, k - 2); + s[k - 2] = 0; + + r = sd_bus_message_open_container(m, *t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); + if (r < 0) + return r; + } + + if (n_array == (unsigned) -1) { + types += k - 1; + n_struct -= k - 1; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = t + 1; + n_struct = k - 2; + n_array = (unsigned) -1; + + break; + } + + default: + r = -EINVAL; + } + + if (r < 0) + return r; + } + + return 1; +} + +_public_ int sd_bus_message_append(sd_bus_message *m, const char *types, ...) { + va_list ap; + int r; + + assert_return(m, -EINVAL); + assert_return(types, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(!m->poisoned, -ESTALE); + + va_start(ap, types); + r = bus_message_append_ap(m, types, ap); + va_end(ap); + + return r; +} + +_public_ int sd_bus_message_append_array_space( + sd_bus_message *m, + char type, + size_t size, + void **ptr) { + + ssize_t align, sz; + void *a; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(bus_type_is_trivial(type) && type != SD_BUS_TYPE_BOOLEAN, -EINVAL); + assert_return(ptr || size == 0, -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + /* alignment and size of the trivial types (except bool) is + * identical for gvariant and dbus1 marshalling */ + align = bus_type_get_alignment(type); + sz = bus_type_get_size(type); + + assert_se(align > 0); + assert_se(sz > 0); + + if (size % sz != 0) + return -EINVAL; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); + if (r < 0) + return r; + + a = message_extend_body(m, align, size, false, false); + if (!a) + return -ENOMEM; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + *ptr = a; + return 0; +} + +_public_ int sd_bus_message_append_array( + sd_bus_message *m, + char type, + const void *ptr, + size_t size) { + int r; + void *p; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(bus_type_is_trivial(type), -EINVAL); + assert_return(ptr || size == 0, -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + r = sd_bus_message_append_array_space(m, type, size, &p); + if (r < 0) + return r; + + if (size > 0) + memcpy(p, ptr, size); + + return 0; +} + +_public_ int sd_bus_message_append_array_iovec( + sd_bus_message *m, + char type, + const struct iovec *iov, + unsigned n) { + + size_t size; + unsigned i; + void *p; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(bus_type_is_trivial(type), -EINVAL); + assert_return(iov || n == 0, -EINVAL); + assert_return(!m->poisoned, -ESTALE); + + size = IOVEC_TOTAL_SIZE(iov, n); + + r = sd_bus_message_append_array_space(m, type, size, &p); + if (r < 0) + return r; + + for (i = 0; i < n; i++) { + + if (iov[i].iov_base) + memcpy(p, iov[i].iov_base, iov[i].iov_len); + else + memzero(p, iov[i].iov_len); + + p = (uint8_t*) p + iov[i].iov_len; + } + + return 0; +} + +_public_ int sd_bus_message_append_array_memfd( + sd_bus_message *m, + char type, + int memfd, + uint64_t offset, + uint64_t size) { + + _cleanup_close_ int copy_fd = -1; + struct bus_body_part *part; + ssize_t align, sz; + uint64_t real_size; + void *a; + int r; + + assert_return(m, -EINVAL); + assert_return(memfd >= 0, -EBADF); + assert_return(bus_type_is_trivial(type), -EINVAL); + assert_return(size > 0, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(!m->poisoned, -ESTALE); + + r = memfd_set_sealed(memfd); + if (r < 0) + return r; + + copy_fd = dup(memfd); + if (copy_fd < 0) + return copy_fd; + + r = memfd_get_size(memfd, &real_size); + if (r < 0) + return r; + + if (offset == 0 && size == (uint64_t) -1) + size = real_size; + else if (offset + size > real_size) + return -EMSGSIZE; + + align = bus_type_get_alignment(type); + sz = bus_type_get_size(type); + + assert_se(align > 0); + assert_se(sz > 0); + + if (offset % align != 0) + return -EINVAL; + + if (size % sz != 0) + return -EINVAL; + + if (size > (uint64_t) (uint32_t) -1) + return -EINVAL; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); + if (r < 0) + return r; + + a = message_extend_body(m, align, 0, false, false); + if (!a) + return -ENOMEM; + + part = message_append_part(m); + if (!part) + return -ENOMEM; + + part->memfd = copy_fd; + part->memfd_offset = offset; + part->sealed = true; + part->size = size; + copy_fd = -1; + + m->body_size += size; + message_extend_containers(m, size); + + return sd_bus_message_close_container(m); +} + +_public_ int sd_bus_message_append_string_memfd( + sd_bus_message *m, + int memfd, + uint64_t offset, + uint64_t size) { + + _cleanup_close_ int copy_fd = -1; + struct bus_body_part *part; + struct bus_container *c; + uint64_t real_size; + void *a; + int r; + + assert_return(m, -EINVAL); + assert_return(memfd >= 0, -EBADF); + assert_return(size > 0, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(!m->poisoned, -ESTALE); + + r = memfd_set_sealed(memfd); + if (r < 0) + return r; + + copy_fd = dup(memfd); + if (copy_fd < 0) + return copy_fd; + + r = memfd_get_size(memfd, &real_size); + if (r < 0) + return r; + + if (offset == 0 && size == (uint64_t) -1) + size = real_size; + else if (offset + size > real_size) + return -EMSGSIZE; + + /* We require this to be NUL terminated */ + if (size == 0) + return -EINVAL; + + if (size > (uint64_t) (uint32_t) -1) + return -EINVAL; + + c = message_get_container(m); + if (c->signature && c->signature[c->index]) { + /* Container signature is already set */ + + if (c->signature[c->index] != SD_BUS_TYPE_STRING) + return -ENXIO; + } else { + char *e; + + /* Maybe we can append to the signature? But only if this is the top-level container */ + if (c->enclosing != 0) + return -ENXIO; + + e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRING), NULL); + if (!e) { + m->poisoned = true; + return -ENOMEM; + } + } + + if (!BUS_MESSAGE_IS_GVARIANT(m)) { + a = message_extend_body(m, 4, 4, false, false); + if (!a) + return -ENOMEM; + + *(uint32_t*) a = size - 1; + } + + part = message_append_part(m); + if (!part) + return -ENOMEM; + + part->memfd = copy_fd; + part->memfd_offset = offset; + part->sealed = true; + part->size = size; + copy_fd = -1; + + m->body_size += size; + message_extend_containers(m, size); + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + r = message_add_offset(m, m->body_size); + if (r < 0) { + m->poisoned = true; + return -ENOMEM; + } + } + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 0; +} + +_public_ int sd_bus_message_append_strv(sd_bus_message *m, char **l) { + char **i; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(!m->poisoned, -ESTALE); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return r; + + STRV_FOREACH(i, l) { + r = sd_bus_message_append_basic(m, 's', *i); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(m); +} + +static int bus_message_close_header(sd_bus_message *m) { + + assert(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 + put the outer container end. */ + m->user_body_size = m->body_size; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + const char *signature; + size_t sz, l; + void *d; + + /* Add offset table to end of fields array */ + if (m->n_header_offsets >= 1) { + uint8_t *a; + unsigned i; + + assert(m->fields_size == m->header_offsets[m->n_header_offsets-1]); + + sz = bus_gvariant_determine_word_size(m->fields_size, m->n_header_offsets); + a = message_extend_fields(m, 1, sz * m->n_header_offsets, false); + if (!a) + return -ENOMEM; + + for (i = 0; i < m->n_header_offsets; i++) + bus_gvariant_write_word_le(a + sz*i, sz, m->header_offsets[i]); + } + + /* Add gvariant NUL byte plus signature to the end of + * the body, followed by the final offset pointing to + * the end of the fields array */ + + signature = strempty(m->root_container.signature); + l = strlen(signature); + + sz = bus_gvariant_determine_word_size(sizeof(struct bus_header) + ALIGN8(m->fields_size) + m->body_size + 1 + l + 2, 1); + d = message_extend_body(m, 1, 1 + l + 2 + sz, false, true); + if (!d) + return -ENOMEM; + + *(uint8_t*) d = 0; + *((uint8_t*) d + 1) = SD_BUS_TYPE_STRUCT_BEGIN; + memcpy((uint8_t*) d + 2, signature, l); + *((uint8_t*) d + 1 + l + 1) = SD_BUS_TYPE_STRUCT_END; + + bus_gvariant_write_word_le((uint8_t*) d + 1 + l + 2, sz, sizeof(struct bus_header) + m->fields_size); + + m->footer = d; + m->footer_accessible = 1 + l + 2 + sz; + } else { + m->header->dbus1.fields_size = m->fields_size; + m->header->dbus1.body_size = m->body_size; + } + + return 0; +} + +int bus_message_seal(sd_bus_message *m, uint64_t cookie, usec_t timeout) { + struct bus_body_part *part; + size_t a; + unsigned i; + int r; + + assert(m); + + if (m->sealed) + return -EPERM; + + if (m->n_containers > 0) + return -EBADMSG; + + if (m->poisoned) + return -ESTALE; + + if (cookie > 0xffffffffULL && + !BUS_MESSAGE_IS_GVARIANT(m)) + return -EOPNOTSUPP; + + /* In vtables the return signature of method calls is listed, + * let's check if they match if this is a response */ + if (m->header->type == SD_BUS_MESSAGE_METHOD_RETURN && + m->enforced_reply_signature && + !streq(strempty(m->root_container.signature), m->enforced_reply_signature)) + return -ENOMSG; + + /* If gvariant marshalling is used we need to close the body structure */ + r = bus_message_close_struct(m, &m->root_container, false); + if (r < 0) + return r; + + /* If there's a non-trivial signature set, then add it in + * here, but only on dbus1 */ + if (!isempty(m->root_container.signature) && !BUS_MESSAGE_IS_GVARIANT(m)) { + r = message_append_field_signature(m, BUS_MESSAGE_HEADER_SIGNATURE, m->root_container.signature, NULL); + if (r < 0) + return r; + } + + if (m->n_fds > 0) { + r = message_append_field_uint32(m, BUS_MESSAGE_HEADER_UNIX_FDS, m->n_fds); + if (r < 0) + return r; + } + + r = bus_message_close_header(m); + if (r < 0) + return r; + + if (BUS_MESSAGE_IS_GVARIANT(m)) + m->header->dbus2.cookie = cookie; + else + m->header->dbus1.serial = (uint32_t) cookie; + + m->timeout = m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED ? 0 : timeout; + + /* Add padding at the end of the fields part, since we know + * the body needs to start at an 8 byte alignment. We made + * sure we allocated enough space for this, so all we need to + * do here is to zero it out. */ + a = ALIGN8(m->fields_size) - m->fields_size; + if (a > 0) + memzero((uint8_t*) BUS_MESSAGE_FIELDS(m) + m->fields_size, a); + + /* If this is something we can send as memfd, then let's seal + the memfd now. Note that we can send memfds as payload only + for directed messages, and not for broadcasts. */ + if (m->destination && m->bus->use_memfd) { + MESSAGE_FOREACH_PART(part, i, m) + if (part->memfd >= 0 && + !part->sealed && + (part->size > MEMFD_MIN_SIZE || m->bus->use_memfd < 0) && + part != m->body_end) { /* The last part may never be sent as memfd */ + uint64_t sz; + + /* Try to seal it if that makes + * sense. First, unmap our own map to + * make sure we don't keep it busy. */ + bus_body_part_unmap(part); + + /* Then, sync up real memfd size */ + sz = part->size; + r = memfd_set_size(part->memfd, sz); + if (r < 0) + return r; + + /* Finally, try to seal */ + if (memfd_set_sealed(part->memfd) >= 0) + part->sealed = true; + } + } + + m->root_container.end = m->user_body_size; + m->root_container.index = 0; + m->root_container.offset_index = 0; + m->root_container.item_size = m->root_container.n_offsets > 0 ? m->root_container.offsets[0] : 0; + + m->sealed = true; + + return 0; +} + +int bus_body_part_map(struct bus_body_part *part) { + void *p; + size_t psz, shift; + + assert_se(part); + + if (part->data) + return 0; + + if (part->size <= 0) + return 0; + + /* For smaller zero parts (as used for padding) we don't need to map anything... */ + if (part->memfd < 0 && part->is_zero && part->size < 8) { + static const uint8_t zeroes[7] = { }; + part->data = (void*) zeroes; + return 0; + } + + shift = part->memfd_offset - ((part->memfd_offset / page_size()) * page_size()); + psz = PAGE_ALIGN(part->size + shift); + + if (part->memfd >= 0) + p = mmap(NULL, psz, PROT_READ, MAP_PRIVATE, part->memfd, part->memfd_offset - shift); + else if (part->is_zero) + p = mmap(NULL, psz, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + else + return -EINVAL; + + if (p == MAP_FAILED) + return -errno; + + part->mapped = psz; + part->mmap_begin = p; + part->data = (uint8_t*) p + shift; + part->munmap_this = true; + + return 0; +} + +void bus_body_part_unmap(struct bus_body_part *part) { + + assert_se(part); + + if (part->memfd < 0) + return; + + if (!part->mmap_begin) + return; + + if (!part->munmap_this) + return; + + assert_se(munmap(part->mmap_begin, part->mapped) == 0); + + part->mmap_begin = NULL; + part->data = NULL; + part->mapped = 0; + part->munmap_this = false; + + return; +} + +static int buffer_peek(const void *p, uint32_t sz, size_t *rindex, size_t align, size_t nbytes, void **r) { + size_t k, start, end; + + assert(rindex); + assert(align > 0); + + start = ALIGN_TO((size_t) *rindex, align); + end = start + nbytes; + + if (end > sz) + return -EBADMSG; + + /* Verify that padding is 0 */ + for (k = *rindex; k < start; k++) + if (((const uint8_t*) p)[k] != 0) + return -EBADMSG; + + if (r) + *r = (uint8_t*) p + start; + + *rindex = end; + + return 1; +} + +static bool message_end_of_signature(sd_bus_message *m) { + struct bus_container *c; + + assert(m); + + c = message_get_container(m); + return !c->signature || c->signature[c->index] == 0; +} + +static bool message_end_of_array(sd_bus_message *m, size_t index) { + struct bus_container *c; + + assert(m); + + c = message_get_container(m); + if (c->enclosing != SD_BUS_TYPE_ARRAY) + return false; + + if (BUS_MESSAGE_IS_GVARIANT(m)) + return index >= c->end; + else { + assert(c->array_size); + return index >= c->begin + BUS_MESSAGE_BSWAP32(m, *c->array_size); + } +} + +_public_ int sd_bus_message_at_end(sd_bus_message *m, int complete) { + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + + if (complete && m->n_containers > 0) + return false; + + if (message_end_of_signature(m)) + return true; + + if (message_end_of_array(m, m->rindex)) + return true; + + return false; +} + +static struct bus_body_part* find_part(sd_bus_message *m, size_t index, size_t sz, void **p) { + struct bus_body_part *part; + size_t begin; + int r; + + assert(m); + + if (m->cached_rindex_part && index >= m->cached_rindex_part_begin) { + part = m->cached_rindex_part; + begin = m->cached_rindex_part_begin; + } else { + part = &m->body; + begin = 0; + } + + while (part) { + if (index < begin) + return NULL; + + if (index + sz <= begin + part->size) { + + r = bus_body_part_map(part); + if (r < 0) + return NULL; + + if (p) + *p = (uint8_t*) part->data + index - begin; + + m->cached_rindex_part = part; + m->cached_rindex_part_begin = begin; + + return part; + } + + begin += part->size; + part = part->next; + } + + return NULL; +} + +static int container_next_item(sd_bus_message *m, struct bus_container *c, size_t *rindex) { + int r; + + assert(m); + assert(c); + assert(rindex); + + if (!BUS_MESSAGE_IS_GVARIANT(m)) + return 0; + + if (c->enclosing == SD_BUS_TYPE_ARRAY) { + int sz; + + sz = bus_gvariant_get_size(c->signature); + if (sz < 0) { + int alignment; + + if (c->offset_index+1 >= c->n_offsets) + goto end; + + /* Variable-size array */ + + alignment = bus_gvariant_get_alignment(c->signature); + assert(alignment > 0); + + *rindex = ALIGN_TO(c->offsets[c->offset_index], alignment); + c->item_size = c->offsets[c->offset_index+1] - *rindex; + } else { + + if (c->offset_index+1 >= (c->end-c->begin)/sz) + goto end; + + /* Fixed-size array */ + *rindex = c->begin + (c->offset_index+1) * sz; + c->item_size = sz; + } + + c->offset_index++; + + } else if (c->enclosing == 0 || + c->enclosing == SD_BUS_TYPE_STRUCT || + c->enclosing == SD_BUS_TYPE_DICT_ENTRY) { + + int alignment; + size_t n, j; + + if (c->offset_index+1 >= c->n_offsets) + goto end; + + r = signature_element_length(c->signature + c->index, &n); + if (r < 0) + return r; + + r = signature_element_length(c->signature + c->index + n, &j); + if (r < 0) + return r; + else { + char t[j+1]; + memcpy(t, c->signature + c->index + n, j); + t[j] = 0; + + alignment = bus_gvariant_get_alignment(t); + } + + assert(alignment > 0); + + *rindex = ALIGN_TO(c->offsets[c->offset_index], alignment); + c->item_size = c->offsets[c->offset_index+1] - *rindex; + + c->offset_index++; + + } else if (c->enclosing == SD_BUS_TYPE_VARIANT) + goto end; + else + assert_not_reached("Unknown container type"); + + return 0; + +end: + /* Reached the end */ + *rindex = c->end; + c->item_size = 0; + return 0; +} + + +static int message_peek_body( + sd_bus_message *m, + size_t *rindex, + size_t align, + size_t nbytes, + void **ret) { + + size_t k, start, end, padding; + struct bus_body_part *part; + uint8_t *q; + + assert(m); + assert(rindex); + assert(align > 0); + + start = ALIGN_TO((size_t) *rindex, align); + padding = start - *rindex; + end = start + nbytes; + + if (end > m->user_body_size) + return -EBADMSG; + + part = find_part(m, *rindex, padding, (void**) &q); + if (!part) + return -EBADMSG; + + if (q) { + /* Verify padding */ + for (k = 0; k < padding; k++) + if (q[k] != 0) + return -EBADMSG; + } + + part = find_part(m, start, nbytes, (void**) &q); + if (!part || (nbytes > 0 && !q)) + return -EBADMSG; + + *rindex = end; + + if (ret) + *ret = q; + + return 0; +} + +static bool validate_nul(const char *s, size_t l) { + + /* Check for NUL chars in the string */ + if (memchr(s, 0, l)) + return false; + + /* Check for NUL termination */ + if (s[l] != 0) + return false; + + return true; +} + +static bool validate_string(const char *s, size_t l) { + + if (!validate_nul(s, l)) + return false; + + /* Check if valid UTF8 */ + if (!utf8_is_valid(s)) + return false; + + return true; +} + +static bool validate_signature(const char *s, size_t l) { + + if (!validate_nul(s, l)) + return false; + + /* Check if valid signature */ + if (!signature_is_valid(s, true)) + return false; + + return true; +} + +static bool validate_object_path(const char *s, size_t l) { + + if (!validate_nul(s, l)) + return false; + + if (!object_path_is_valid(s)) + return false; + + return true; +} + +_public_ int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p) { + struct bus_container *c; + size_t rindex; + void *q; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(bus_type_is_basic(type), -EINVAL); + + if (message_end_of_signature(m)) + return -ENXIO; + + if (message_end_of_array(m, m->rindex)) + return 0; + + c = message_get_container(m); + if (c->signature[c->index] != type) + return -ENXIO; + + rindex = m->rindex; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + + if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE)) { + bool ok; + + r = message_peek_body(m, &rindex, 1, c->item_size, &q); + if (r < 0) + return r; + + if (type == SD_BUS_TYPE_STRING) + ok = validate_string(q, c->item_size-1); + else if (type == SD_BUS_TYPE_OBJECT_PATH) + ok = validate_object_path(q, c->item_size-1); + else + ok = validate_signature(q, c->item_size-1); + + if (!ok) + return -EBADMSG; + + if (p) + *(const char**) p = q; + } else { + int sz, align; + + sz = bus_gvariant_get_size(CHAR_TO_STR(type)); + assert(sz > 0); + if ((size_t) sz != c->item_size) + return -EBADMSG; + + align = bus_gvariant_get_alignment(CHAR_TO_STR(type)); + assert(align > 0); + + r = message_peek_body(m, &rindex, align, c->item_size, &q); + if (r < 0) + return r; + + switch (type) { + + case SD_BUS_TYPE_BYTE: + if (p) + *(uint8_t*) p = *(uint8_t*) q; + break; + + case SD_BUS_TYPE_BOOLEAN: + if (p) + *(int*) p = !!*(uint8_t*) q; + break; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + if (p) + *(uint16_t*) p = BUS_MESSAGE_BSWAP16(m, *(uint16_t*) q); + break; + + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + if (p) + *(uint32_t*) p = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + break; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + if (p) + *(uint64_t*) p = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q); + break; + + case SD_BUS_TYPE_UNIX_FD: { + uint32_t j; + + j = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + if (j >= m->n_fds) + return -EBADMSG; + + if (p) + *(int*) p = m->fds[j]; + + break; + } + + default: + assert_not_reached("unexpected type"); + } + } + + r = container_next_item(m, c, &rindex); + if (r < 0) + return r; + } else { + + if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH)) { + uint32_t l; + bool ok; + + r = message_peek_body(m, &rindex, 4, 4, &q); + if (r < 0) + return r; + + l = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + r = message_peek_body(m, &rindex, 1, l+1, &q); + if (r < 0) + return r; + + if (type == SD_BUS_TYPE_OBJECT_PATH) + ok = validate_object_path(q, l); + else + ok = validate_string(q, l); + if (!ok) + return -EBADMSG; + + if (p) + *(const char**) p = q; + + } else if (type == SD_BUS_TYPE_SIGNATURE) { + uint8_t l; + + r = message_peek_body(m, &rindex, 1, 1, &q); + if (r < 0) + return r; + + l = *(uint8_t*) q; + r = message_peek_body(m, &rindex, 1, l+1, &q); + if (r < 0) + return r; + + if (!validate_signature(q, l)) + return -EBADMSG; + + if (p) + *(const char**) p = q; + + } else { + ssize_t sz, align; + + align = bus_type_get_alignment(type); + assert(align > 0); + + sz = bus_type_get_size(type); + assert(sz > 0); + + r = message_peek_body(m, &rindex, align, sz, &q); + if (r < 0) + return r; + + switch (type) { + + case SD_BUS_TYPE_BYTE: + if (p) + *(uint8_t*) p = *(uint8_t*) q; + break; + + case SD_BUS_TYPE_BOOLEAN: + if (p) + *(int*) p = !!*(uint32_t*) q; + break; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + if (p) + *(uint16_t*) p = BUS_MESSAGE_BSWAP16(m, *(uint16_t*) q); + break; + + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + if (p) + *(uint32_t*) p = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + break; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + if (p) + *(uint64_t*) p = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q); + break; + + case SD_BUS_TYPE_UNIX_FD: { + uint32_t j; + + j = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + if (j >= m->n_fds) + return -EBADMSG; + + if (p) + *(int*) p = m->fds[j]; + break; + } + + default: + assert_not_reached("Unknown basic type..."); + } + } + } + + m->rindex = rindex; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 1; +} + +static int bus_message_enter_array( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + uint32_t **array_size, + size_t *item_size, + size_t **offsets, + size_t *n_offsets) { + + size_t rindex; + void *q; + int r, alignment; + + assert(m); + assert(c); + assert(contents); + assert(array_size); + assert(item_size); + assert(offsets); + assert(n_offsets); + + if (!signature_is_single(contents, true)) + return -EINVAL; + + if (!c->signature || c->signature[c->index] == 0) + return -ENXIO; + + if (c->signature[c->index] != SD_BUS_TYPE_ARRAY) + return -ENXIO; + + if (!startswith(c->signature + c->index + 1, contents)) + return -ENXIO; + + rindex = m->rindex; + + if (!BUS_MESSAGE_IS_GVARIANT(m)) { + /* dbus1 */ + + r = message_peek_body(m, &rindex, 4, 4, &q); + if (r < 0) + return r; + + if (BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q) > BUS_ARRAY_MAX_SIZE) + return -EBADMSG; + + alignment = bus_type_get_alignment(contents[0]); + if (alignment < 0) + return alignment; + + r = message_peek_body(m, &rindex, alignment, 0, NULL); + if (r < 0) + return r; + + *array_size = (uint32_t*) q; + + } else if (c->item_size <= 0) { + + /* gvariant: empty array */ + *item_size = 0; + *offsets = NULL; + *n_offsets = 0; + + } else if (bus_gvariant_is_fixed_size(contents)) { + + /* gvariant: fixed length array */ + *item_size = bus_gvariant_get_size(contents); + *offsets = NULL; + *n_offsets = 0; + + } else { + size_t where, p = 0, framing, sz; + unsigned i; + + /* gvariant: variable length array */ + sz = bus_gvariant_determine_word_size(c->item_size, 0); + + where = rindex + c->item_size - sz; + r = message_peek_body(m, &where, 1, sz, &q); + if (r < 0) + return r; + + framing = bus_gvariant_read_word_le(q, sz); + if (framing > c->item_size - sz) + return -EBADMSG; + if ((c->item_size - framing) % sz != 0) + return -EBADMSG; + + *n_offsets = (c->item_size - framing) / sz; + + where = rindex + framing; + r = message_peek_body(m, &where, 1, *n_offsets * sz, &q); + if (r < 0) + return r; + + *offsets = new(size_t, *n_offsets); + if (!*offsets) + return -ENOMEM; + + for (i = 0; i < *n_offsets; i++) { + size_t x; + + x = bus_gvariant_read_word_le((uint8_t*) q + i * sz, sz); + if (x > c->item_size - sz) + return -EBADMSG; + if (x < p) + return -EBADMSG; + + (*offsets)[i] = rindex + x; + p = x; + } + + *item_size = (*offsets)[0] - rindex; + } + + m->rindex = rindex; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index += 1 + strlen(contents); + + return 1; +} + +static int bus_message_enter_variant( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + size_t *item_size) { + + size_t rindex; + uint8_t l; + void *q; + int r; + + assert(m); + assert(c); + assert(contents); + assert(item_size); + + if (!signature_is_single(contents, false)) + return -EINVAL; + + if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN) + return -EINVAL; + + if (!c->signature || c->signature[c->index] == 0) + return -ENXIO; + + if (c->signature[c->index] != SD_BUS_TYPE_VARIANT) + return -ENXIO; + + rindex = m->rindex; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + size_t k, where; + + k = strlen(contents); + if (1+k > c->item_size) + return -EBADMSG; + + where = rindex + c->item_size - (1+k); + r = message_peek_body(m, &where, 1, 1+k, &q); + if (r < 0) + return r; + + if (*(char*) q != 0) + return -EBADMSG; + + if (memcmp((uint8_t*) q+1, contents, k)) + return -ENXIO; + + *item_size = c->item_size - (1+k); + + } else { + r = message_peek_body(m, &rindex, 1, 1, &q); + if (r < 0) + return r; + + l = *(uint8_t*) q; + r = message_peek_body(m, &rindex, 1, l+1, &q); + if (r < 0) + return r; + + if (!validate_signature(q, l)) + return -EBADMSG; + + if (!streq(q, contents)) + return -ENXIO; + } + + m->rindex = rindex; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index++; + + return 1; +} + +static int build_struct_offsets( + sd_bus_message *m, + const char *signature, + size_t size, + size_t *item_size, + size_t **offsets, + size_t *n_offsets) { + + unsigned n_variable = 0, n_total = 0, v; + size_t previous = 0, where; + const char *p; + size_t sz; + void *q; + int r; + + assert(m); + assert(item_size); + assert(offsets); + assert(n_offsets); + + if (isempty(signature)) { + /* Unary type is encoded as *fixed* 1 byte padding */ + r = message_peek_body(m, &m->rindex, 1, 1, &q); + if (r < 0) + return r; + + if (*(uint8_t *) q != 0) + return -EBADMSG; + + *item_size = 0; + *offsets = NULL; + *n_offsets = 0; + return 0; + } + + sz = bus_gvariant_determine_word_size(size, 0); + if (sz <= 0) + return -EBADMSG; + + /* First, loop over signature and count variable elements and + * elements in general. We use this to know how large the + * offset array is at the end of the structure. Note that + * GVariant only stores offsets for all variable size elements + * that are not the last item. */ + + p = signature; + while (*p != 0) { + size_t n; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + else { + char t[n+1]; + + memcpy(t, p, n); + t[n] = 0; + + r = bus_gvariant_is_fixed_size(t); + } + + if (r < 0) + return r; + if (r == 0 && p[n] != 0) /* except the last item */ + n_variable ++; + n_total++; + + p += n; + } + + if (size < n_variable * sz) + return -EBADMSG; + + where = m->rindex + size - (n_variable * sz); + r = message_peek_body(m, &where, 1, n_variable * sz, &q); + if (r < 0) + return r; + + v = n_variable; + + *offsets = new(size_t, n_total); + if (!*offsets) + return -ENOMEM; + + *n_offsets = 0; + + /* Second, loop again and build an offset table */ + p = signature; + while (*p != 0) { + size_t n, offset; + int k; + + r = signature_element_length(p, &n); + if (r < 0) + return r; + else { + char t[n+1]; + + memcpy(t, p, n); + t[n] = 0; + + k = bus_gvariant_get_size(t); + if (k < 0) { + size_t x; + + /* variable size */ + if (v > 0) { + v--; + + x = bus_gvariant_read_word_le((uint8_t*) q + v*sz, sz); + if (x >= size) + return -EBADMSG; + if (m->rindex + x < previous) + return -EBADMSG; + } else + /* The last item's end + * is determined from + * the start of the + * offset array */ + x = size - (n_variable * sz); + + offset = m->rindex + x; + + } else { + size_t align; + + /* fixed size */ + align = bus_gvariant_get_alignment(t); + assert(align > 0); + + offset = (*n_offsets == 0 ? m->rindex : ALIGN_TO((*offsets)[*n_offsets-1], align)) + k; + } + } + + previous = (*offsets)[(*n_offsets)++] = offset; + p += n; + } + + assert(v == 0); + assert(*n_offsets == n_total); + + *item_size = (*offsets)[0] - m->rindex; + return 0; +} + +static int enter_struct_or_dict_entry( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + size_t *item_size, + size_t **offsets, + size_t *n_offsets) { + + int r; + + assert(m); + assert(c); + assert(contents); + assert(item_size); + assert(offsets); + assert(n_offsets); + + if (!BUS_MESSAGE_IS_GVARIANT(m)) { + + /* dbus1 */ + r = message_peek_body(m, &m->rindex, 8, 0, NULL); + if (r < 0) + return r; + + } else + /* gvariant with contents */ + return build_struct_offsets(m, contents, c->item_size, item_size, offsets, n_offsets); + + return 0; +} + +static int bus_message_enter_struct( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + size_t *item_size, + size_t **offsets, + size_t *n_offsets) { + + size_t l; + int r; + + assert(m); + assert(c); + assert(contents); + assert(item_size); + assert(offsets); + assert(n_offsets); + + if (!signature_is_valid(contents, false)) + return -EINVAL; + + if (!c->signature || c->signature[c->index] == 0) + return -ENXIO; + + l = strlen(contents); + + if (c->signature[c->index] != SD_BUS_TYPE_STRUCT_BEGIN || + !startswith(c->signature + c->index + 1, contents) || + c->signature[c->index + 1 + l] != SD_BUS_TYPE_STRUCT_END) + return -ENXIO; + + r = enter_struct_or_dict_entry(m, c, contents, item_size, offsets, n_offsets); + if (r < 0) + return r; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index += 1 + l + 1; + + return 1; +} + +static int bus_message_enter_dict_entry( + sd_bus_message *m, + struct bus_container *c, + const char *contents, + size_t *item_size, + size_t **offsets, + size_t *n_offsets) { + + size_t l; + int r; + + assert(m); + assert(c); + assert(contents); + + if (!signature_is_pair(contents)) + return -EINVAL; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + return -ENXIO; + + if (!c->signature || c->signature[c->index] == 0) + return 0; + + l = strlen(contents); + + if (c->signature[c->index] != SD_BUS_TYPE_DICT_ENTRY_BEGIN || + !startswith(c->signature + c->index + 1, contents) || + c->signature[c->index + 1 + l] != SD_BUS_TYPE_DICT_ENTRY_END) + return -ENXIO; + + r = enter_struct_or_dict_entry(m, c, contents, item_size, offsets, n_offsets); + if (r < 0) + return r; + + if (c->enclosing != SD_BUS_TYPE_ARRAY) + c->index += 1 + l + 1; + + return 1; +} + +_public_ int sd_bus_message_enter_container(sd_bus_message *m, + char type, + const char *contents) { + struct bus_container *c, *w; + uint32_t *array_size = NULL; + char *signature; + size_t before; + size_t *offsets = NULL; + size_t n_offsets = 0, item_size = 0; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(type != 0 || !contents, -EINVAL); + + if (type == 0 || !contents) { + const char *cc; + char tt; + + /* Allow entering into anonymous containers */ + r = sd_bus_message_peek_type(m, &tt, &cc); + if (r < 0) + return r; + + if (type != 0 && type != tt) + return -ENXIO; + + if (contents && !streq(contents, cc)) + return -ENXIO; + + type = tt; + contents = cc; + } + + /* + * We enforce a global limit on container depth, that is much + * higher than the 32 structs and 32 arrays the specification + * mandates. This is simpler to implement for us, and we need + * this only to ensure our container array doesn't grow + * without bounds. We are happy to return any data from a + * message as long as the data itself is valid, even if the + * overall message might be not. + * + * Note that the message signature is validated when + * parsing the headers, and that validation does check the + * 32/32 limit. + * + * Note that the specification defines no limits on the depth + * of stacked variants, but we do. + */ + if (m->n_containers >= BUS_CONTAINER_DEPTH) + return -EBADMSG; + + if (!GREEDY_REALLOC(m->containers, m->containers_allocated, m->n_containers + 1)) + return -ENOMEM; + + if (message_end_of_signature(m)) + return -ENXIO; + + if (message_end_of_array(m, m->rindex)) + return 0; + + c = message_get_container(m); + + signature = strdup(contents); + if (!signature) + return -ENOMEM; + + c->saved_index = c->index; + before = m->rindex; + + if (type == SD_BUS_TYPE_ARRAY) + r = bus_message_enter_array(m, c, contents, &array_size, &item_size, &offsets, &n_offsets); + else if (type == SD_BUS_TYPE_VARIANT) + r = bus_message_enter_variant(m, c, contents, &item_size); + else if (type == SD_BUS_TYPE_STRUCT) + r = bus_message_enter_struct(m, c, contents, &item_size, &offsets, &n_offsets); + else if (type == SD_BUS_TYPE_DICT_ENTRY) + r = bus_message_enter_dict_entry(m, c, contents, &item_size, &offsets, &n_offsets); + else + r = -EINVAL; + + if (r <= 0) { + free(signature); + free(offsets); + return r; + } + + /* OK, let's fill it in */ + w = m->containers + m->n_containers++; + w->enclosing = type; + w->signature = signature; + w->peeked_signature = NULL; + w->index = 0; + + w->before = before; + w->begin = m->rindex; + + /* Unary type has fixed size of 1, but virtual size of 0 */ + if (BUS_MESSAGE_IS_GVARIANT(m) && + type == SD_BUS_TYPE_STRUCT && + isempty(signature)) + w->end = m->rindex + 0; + else + w->end = m->rindex + c->item_size; + + w->array_size = array_size; + w->item_size = item_size; + w->offsets = offsets; + w->n_offsets = n_offsets; + w->offset_index = 0; + + return 1; +} + +_public_ int sd_bus_message_exit_container(sd_bus_message *m) { + struct bus_container *c; + unsigned saved; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(m->n_containers > 0, -ENXIO); + + c = message_get_container(m); + + if (c->enclosing != SD_BUS_TYPE_ARRAY) { + if (c->signature && c->signature[c->index] != 0) + return -EBUSY; + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + if (m->rindex < c->end) + return -EBUSY; + + } else if (c->enclosing == SD_BUS_TYPE_ARRAY) { + uint32_t l; + + l = BUS_MESSAGE_BSWAP32(m, *c->array_size); + if (c->begin + l != m->rindex) + return -EBUSY; + } + + free(c->signature); + free(c->peeked_signature); + free(c->offsets); + m->n_containers--; + + c = message_get_container(m); + + saved = c->index; + c->index = c->saved_index; + r = container_next_item(m, c, &m->rindex); + c->index = saved; + if (r < 0) + return r; + + return 1; +} + +static void message_quit_container(sd_bus_message *m) { + struct bus_container *c; + + assert(m); + assert(m->sealed); + assert(m->n_containers > 0); + + c = message_get_container(m); + + /* Undo seeks */ + assert(m->rindex >= c->before); + m->rindex = c->before; + + /* Free container */ + free(c->signature); + free(c->offsets); + m->n_containers--; + + /* Correct index of new top-level container */ + c = message_get_container(m); + c->index = c->saved_index; +} + +_public_ int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char **contents) { + struct bus_container *c; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + + if (message_end_of_signature(m)) + goto eof; + + if (message_end_of_array(m, m->rindex)) + goto eof; + + c = message_get_container(m); + + if (bus_type_is_basic(c->signature[c->index])) { + if (contents) + *contents = NULL; + if (type) + *type = c->signature[c->index]; + return 1; + } + + if (c->signature[c->index] == SD_BUS_TYPE_ARRAY) { + + if (contents) { + size_t l; + char *sig; + + r = signature_element_length(c->signature+c->index+1, &l); + if (r < 0) + return r; + + assert(l >= 1); + + sig = strndup(c->signature + c->index + 1, l); + if (!sig) + return -ENOMEM; + + free(c->peeked_signature); + *contents = c->peeked_signature = sig; + } + + if (type) + *type = SD_BUS_TYPE_ARRAY; + + return 1; + } + + if (c->signature[c->index] == SD_BUS_TYPE_STRUCT_BEGIN || + c->signature[c->index] == SD_BUS_TYPE_DICT_ENTRY_BEGIN) { + + if (contents) { + size_t l; + char *sig; + + r = signature_element_length(c->signature+c->index, &l); + if (r < 0) + return r; + + assert(l >= 2); + sig = strndup(c->signature + c->index + 1, l - 2); + if (!sig) + return -ENOMEM; + + free(c->peeked_signature); + *contents = c->peeked_signature = sig; + } + + if (type) + *type = c->signature[c->index] == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY; + + return 1; + } + + if (c->signature[c->index] == SD_BUS_TYPE_VARIANT) { + if (contents) { + void *q; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + size_t k; + + if (c->item_size < 2) + return -EBADMSG; + + /* Look for the NUL delimiter that + separates the payload from the + signature. Since the body might be + in a different part that then the + signature we map byte by byte. */ + + for (k = 2; k <= c->item_size; k++) { + size_t where; + + where = m->rindex + c->item_size - k; + r = message_peek_body(m, &where, 1, k, &q); + if (r < 0) + return r; + + if (*(char*) q == 0) + break; + } + + if (k > c->item_size) + return -EBADMSG; + + free(c->peeked_signature); + c->peeked_signature = strndup((char*) q + 1, k - 1); + if (!c->peeked_signature) + return -ENOMEM; + + if (!signature_is_valid(c->peeked_signature, true)) + return -EBADMSG; + + *contents = c->peeked_signature; + } else { + size_t rindex, l; + + rindex = m->rindex; + r = message_peek_body(m, &rindex, 1, 1, &q); + if (r < 0) + return r; + + l = *(uint8_t*) q; + r = message_peek_body(m, &rindex, 1, l+1, &q); + if (r < 0) + return r; + + if (!validate_signature(q, l)) + return -EBADMSG; + + *contents = q; + } + } + + if (type) + *type = SD_BUS_TYPE_VARIANT; + + return 1; + } + + return -EINVAL; + +eof: + if (type) + *type = 0; + if (contents) + *contents = NULL; + return 0; +} + +_public_ int sd_bus_message_rewind(sd_bus_message *m, int complete) { + struct bus_container *c; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + + if (complete) { + message_reset_containers(m); + m->rindex = 0; + + c = message_get_container(m); + } else { + c = message_get_container(m); + + c->offset_index = 0; + c->index = 0; + m->rindex = c->begin; + } + + c->offset_index = 0; + c->item_size = (c->n_offsets > 0 ? c->offsets[0] : c->end) - c->begin; + + return !isempty(c->signature); +} + +static int message_read_ap( + sd_bus_message *m, + const char *types, + va_list ap) { + + unsigned n_array, n_struct; + TypeStack stack[BUS_CONTAINER_DEPTH]; + unsigned stack_ptr = 0; + unsigned n_loop = 0; + int r; + + assert(m); + + if (isempty(types)) + return 0; + + /* Ideally, we'd just call ourselves recursively on every + * complex type. However, the state of a va_list that is + * passed to a function is undefined after that function + * returns. This means we need to docode the va_list linearly + * in a single stackframe. We hence implement our own + * home-grown stack in an array. */ + + n_array = (unsigned) -1; /* length of current array entries */ + n_struct = strlen(types); /* length of current struct contents signature */ + + for (;;) { + const char *t; + + n_loop++; + + if (n_array == 0 || (n_array == (unsigned) -1 && n_struct == 0)) { + r = type_stack_pop(stack, ELEMENTSOF(stack), &stack_ptr, &types, &n_struct, &n_array); + if (r < 0) + return r; + if (r == 0) + break; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + continue; + } + + t = types; + if (n_array != (unsigned) -1) + n_array --; + else { + types ++; + n_struct--; + } + + switch (*t) { + + case SD_BUS_TYPE_BYTE: + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_UNIX_FD: { + void *p; + + p = va_arg(ap, void*); + r = sd_bus_message_read_basic(m, *t, p); + if (r < 0) + return r; + if (r == 0) { + if (n_loop <= 1) + return 0; + + return -ENXIO; + } + + break; + } + + case SD_BUS_TYPE_ARRAY: { + size_t k; + + r = signature_element_length(t + 1, &k); + if (r < 0) + return r; + + { + char s[k + 1]; + memcpy(s, t + 1, k); + s[k] = 0; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, s); + if (r < 0) + return r; + if (r == 0) { + if (n_loop <= 1) + return 0; + + return -ENXIO; + } + } + + if (n_array == (unsigned) -1) { + types += k; + n_struct -= k; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = t + 1; + n_struct = k; + n_array = va_arg(ap, unsigned); + + break; + } + + case SD_BUS_TYPE_VARIANT: { + const char *s; + + s = va_arg(ap, const char *); + if (!s) + return -EINVAL; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, s); + if (r < 0) + return r; + if (r == 0) { + if (n_loop <= 1) + return 0; + + return -ENXIO; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = s; + n_struct = strlen(s); + n_array = (unsigned) -1; + + break; + } + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + size_t k; + + r = signature_element_length(t, &k); + if (r < 0) + return r; + + { + char s[k - 1]; + memcpy(s, t + 1, k - 2); + s[k - 2] = 0; + + r = sd_bus_message_enter_container(m, *t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); + if (r < 0) + return r; + if (r == 0) { + if (n_loop <= 1) + return 0; + return -ENXIO; + } + } + + if (n_array == (unsigned) -1) { + types += k - 1; + n_struct -= k - 1; + } + + r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); + if (r < 0) + return r; + + types = t + 1; + n_struct = k - 2; + n_array = (unsigned) -1; + + break; + } + + default: + return -EINVAL; + } + } + + return 1; +} + +_public_ int sd_bus_message_read(sd_bus_message *m, const char *types, ...) { + va_list ap; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(types, -EINVAL); + + va_start(ap, types); + r = message_read_ap(m, types, ap); + va_end(ap); + + return r; +} + +_public_ int sd_bus_message_skip(sd_bus_message *m, const char *types) { + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + + /* If types is NULL, read exactly one element */ + if (!types) { + struct bus_container *c; + size_t l; + + if (message_end_of_signature(m)) + return -ENXIO; + + if (message_end_of_array(m, m->rindex)) + return 0; + + c = message_get_container(m); + + r = signature_element_length(c->signature + c->index, &l); + if (r < 0) + return r; + + types = strndupa(c->signature + c->index, l); + } + + switch (*types) { + + case 0: /* Nothing to drop */ + return 0; + + case SD_BUS_TYPE_BYTE: + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_UNIX_FD: + + r = sd_bus_message_read_basic(m, *types, NULL); + if (r <= 0) + return r; + + r = sd_bus_message_skip(m, types + 1); + if (r < 0) + return r; + + return 1; + + case SD_BUS_TYPE_ARRAY: { + size_t k; + + r = signature_element_length(types + 1, &k); + if (r < 0) + return r; + + { + char s[k+1]; + memcpy(s, types+1, k); + s[k] = 0; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, s); + if (r <= 0) + return r; + + for (;;) { + r = sd_bus_message_skip(m, s); + if (r < 0) + return r; + if (r == 0) + break; + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } + + r = sd_bus_message_skip(m, types + 1 + k); + if (r < 0) + return r; + + return 1; + } + + case SD_BUS_TYPE_VARIANT: { + const char *contents; + char x; + + r = sd_bus_message_peek_type(m, &x, &contents); + if (r <= 0) + return r; + + if (x != SD_BUS_TYPE_VARIANT) + return -ENXIO; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); + if (r <= 0) + return r; + + r = sd_bus_message_skip(m, contents); + if (r < 0) + return r; + assert(r != 0); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + r = sd_bus_message_skip(m, types + 1); + if (r < 0) + return r; + + return 1; + } + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + size_t k; + + r = signature_element_length(types, &k); + if (r < 0) + return r; + + { + char s[k-1]; + memcpy(s, types+1, k-2); + s[k-2] = 0; + + r = sd_bus_message_enter_container(m, *types == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); + if (r <= 0) + return r; + + r = sd_bus_message_skip(m, s); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } + + r = sd_bus_message_skip(m, types + k); + if (r < 0) + return r; + + return 1; + } + + default: + return -EINVAL; + } +} + +_public_ int sd_bus_message_read_array( + sd_bus_message *m, + char type, + const void **ptr, + size_t *size) { + + struct bus_container *c; + void *p; + size_t sz; + ssize_t align; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(bus_type_is_trivial(type), -EINVAL); + assert_return(ptr, -EINVAL); + assert_return(size, -EINVAL); + assert_return(!BUS_MESSAGE_NEED_BSWAP(m), -EOPNOTSUPP); + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); + if (r <= 0) + return r; + + c = message_get_container(m); + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + align = bus_gvariant_get_alignment(CHAR_TO_STR(type)); + if (align < 0) + return align; + + sz = c->end - c->begin; + } else { + align = bus_type_get_alignment(type); + if (align < 0) + return align; + + sz = BUS_MESSAGE_BSWAP32(m, *c->array_size); + } + + if (sz == 0) + /* Zero length array, let's return some aligned + * pointer that is not NULL */ + p = (uint8_t*) NULL + align; + else { + r = message_peek_body(m, &m->rindex, align, sz, &p); + if (r < 0) + goto fail; + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + goto fail; + + *ptr = (const void*) p; + *size = sz; + + return 1; + +fail: + message_quit_container(m); + return r; +} + +static int message_peek_fields( + sd_bus_message *m, + size_t *rindex, + size_t align, + size_t nbytes, + void **ret) { + + assert(m); + assert(rindex); + assert(align > 0); + + return buffer_peek(BUS_MESSAGE_FIELDS(m), m->fields_size, rindex, align, nbytes, ret); +} + +static int message_peek_field_uint32( + sd_bus_message *m, + size_t *ri, + size_t item_size, + uint32_t *ret) { + + int r; + void *q; + + assert(m); + assert(ri); + + if (BUS_MESSAGE_IS_GVARIANT(m) && item_size != 4) + return -EBADMSG; + + /* identical for gvariant and dbus1 */ + + r = message_peek_fields(m, ri, 4, 4, &q); + if (r < 0) + return r; + + if (ret) + *ret = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); + + return 0; +} + +static int message_peek_field_uint64( + sd_bus_message *m, + size_t *ri, + size_t item_size, + uint64_t *ret) { + + int r; + void *q; + + assert(m); + assert(ri); + + if (BUS_MESSAGE_IS_GVARIANT(m) && item_size != 8) + return -EBADMSG; + + /* identical for gvariant and dbus1 */ + + r = message_peek_fields(m, ri, 8, 8, &q); + if (r < 0) + return r; + + if (ret) + *ret = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q); + + return 0; +} + +static int message_peek_field_string( + sd_bus_message *m, + bool (*validate)(const char *p), + size_t *ri, + size_t item_size, + const char **ret) { + + uint32_t l; + int r; + void *q; + + assert(m); + assert(ri); + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + + if (item_size <= 0) + return -EBADMSG; + + r = message_peek_fields(m, ri, 1, item_size, &q); + if (r < 0) + return r; + + l = item_size - 1; + } else { + r = message_peek_field_uint32(m, ri, 4, &l); + if (r < 0) + return r; + + r = message_peek_fields(m, ri, 1, l+1, &q); + if (r < 0) + return r; + } + + if (validate) { + if (!validate_nul(q, l)) + return -EBADMSG; + + if (!validate(q)) + return -EBADMSG; + } else { + if (!validate_string(q, l)) + return -EBADMSG; + } + + if (ret) + *ret = q; + + return 0; +} + +static int message_peek_field_signature( + sd_bus_message *m, + size_t *ri, + size_t item_size, + const char **ret) { + + size_t l; + int r; + void *q; + + assert(m); + assert(ri); + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + + if (item_size <= 0) + return -EBADMSG; + + r = message_peek_fields(m, ri, 1, item_size, &q); + if (r < 0) + return r; + + l = item_size - 1; + } else { + r = message_peek_fields(m, ri, 1, 1, &q); + if (r < 0) + return r; + + l = *(uint8_t*) q; + r = message_peek_fields(m, ri, 1, l+1, &q); + if (r < 0) + return r; + } + + if (!validate_signature(q, l)) + return -EBADMSG; + + if (ret) + *ret = q; + + return 0; +} + +static int message_skip_fields( + sd_bus_message *m, + size_t *ri, + uint32_t array_size, + const char **signature) { + + size_t original_index; + int r; + + assert(m); + assert(ri); + assert(signature); + assert(!BUS_MESSAGE_IS_GVARIANT(m)); + + original_index = *ri; + + for (;;) { + char t; + size_t l; + + if (array_size != (uint32_t) -1 && + array_size <= *ri - original_index) + return 0; + + t = **signature; + if (!t) + return 0; + + if (t == SD_BUS_TYPE_STRING) { + + r = message_peek_field_string(m, NULL, ri, 0, NULL); + if (r < 0) + return r; + + (*signature)++; + + } else if (t == SD_BUS_TYPE_OBJECT_PATH) { + + r = message_peek_field_string(m, object_path_is_valid, ri, 0, NULL); + if (r < 0) + return r; + + (*signature)++; + + } else if (t == SD_BUS_TYPE_SIGNATURE) { + + r = message_peek_field_signature(m, ri, 0, NULL); + if (r < 0) + return r; + + (*signature)++; + + } else if (bus_type_is_basic(t)) { + ssize_t align, k; + + align = bus_type_get_alignment(t); + k = bus_type_get_size(t); + assert(align > 0 && k > 0); + + r = message_peek_fields(m, ri, align, k, NULL); + if (r < 0) + return r; + + (*signature)++; + + } else if (t == SD_BUS_TYPE_ARRAY) { + + r = signature_element_length(*signature+1, &l); + if (r < 0) + return r; + + assert(l >= 1); + { + char sig[l-1], *s; + uint32_t nas; + int alignment; + + strncpy(sig, *signature + 1, l-1); + s = sig; + + alignment = bus_type_get_alignment(sig[0]); + if (alignment < 0) + return alignment; + + r = message_peek_field_uint32(m, ri, 0, &nas); + if (r < 0) + return r; + if (nas > BUS_ARRAY_MAX_SIZE) + return -EBADMSG; + + r = message_peek_fields(m, ri, alignment, 0, NULL); + if (r < 0) + return r; + + r = message_skip_fields(m, ri, nas, (const char**) &s); + if (r < 0) + return r; + } + + (*signature) += 1 + l; + + } else if (t == SD_BUS_TYPE_VARIANT) { + const char *s; + + r = message_peek_field_signature(m, ri, 0, &s); + if (r < 0) + return r; + + r = message_skip_fields(m, ri, (uint32_t) -1, (const char**) &s); + if (r < 0) + return r; + + (*signature)++; + + } else if (t == SD_BUS_TYPE_STRUCT || + t == SD_BUS_TYPE_DICT_ENTRY) { + + r = signature_element_length(*signature, &l); + if (r < 0) + return r; + + assert(l >= 2); + { + char sig[l-1], *s; + strncpy(sig, *signature + 1, l-1); + s = sig; + + r = message_skip_fields(m, ri, (uint32_t) -1, (const char**) &s); + if (r < 0) + return r; + } + + *signature += l; + } else + return -EINVAL; + } +} + +int bus_message_parse_fields(sd_bus_message *m) { + size_t ri; + int r; + uint32_t unix_fds = 0; + bool unix_fds_set = false; + void *offsets = NULL; + unsigned n_offsets = 0; + size_t sz = 0; + unsigned i = 0; + + assert(m); + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + char *p; + + /* Read the signature from the end of the body variant first */ + sz = bus_gvariant_determine_word_size(BUS_MESSAGE_SIZE(m), 0); + if (m->footer_accessible < 1 + sz) + return -EBADMSG; + + p = (char*) m->footer + m->footer_accessible - (1 + sz); + for (;;) { + if (p < (char*) m->footer) + return -EBADMSG; + + if (*p == 0) { + size_t l; + char *c; + + /* We found the beginning of the signature + * string, yay! We require the body to be a + * structure, so verify it and then strip the + * opening/closing brackets. */ + + l = ((char*) m->footer + m->footer_accessible) - p - (1 + sz); + if (l < 2 || + p[1] != SD_BUS_TYPE_STRUCT_BEGIN || + p[1 + l - 1] != SD_BUS_TYPE_STRUCT_END) + return -EBADMSG; + + c = strndup(p + 1 + 1, l - 2); + if (!c) + return -ENOMEM; + + free(m->root_container.signature); + m->root_container.signature = c; + break; + } + + p--; + } + + /* Calculate the actual user body size, by removing + * the trailing variant signature and struct offset + * table */ + m->user_body_size = m->body_size - ((char*) m->footer + m->footer_accessible - p); + + /* Pull out the offset table for the fields array */ + sz = bus_gvariant_determine_word_size(m->fields_size, 0); + if (sz > 0) { + size_t framing; + void *q; + + ri = m->fields_size - sz; + r = message_peek_fields(m, &ri, 1, sz, &q); + if (r < 0) + return r; + + framing = bus_gvariant_read_word_le(q, sz); + if (framing >= m->fields_size - sz) + return -EBADMSG; + if ((m->fields_size - framing) % sz != 0) + return -EBADMSG; + + ri = framing; + r = message_peek_fields(m, &ri, 1, m->fields_size - framing, &offsets); + if (r < 0) + return r; + + n_offsets = (m->fields_size - framing) / sz; + } + } else + m->user_body_size = m->body_size; + + ri = 0; + while (ri < m->fields_size) { + _cleanup_free_ char *sig = NULL; + const char *signature; + uint64_t field_type; + size_t item_size = (size_t) -1; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + uint64_t *u64; + + if (i >= n_offsets) + break; + + if (i == 0) + ri = 0; + else + ri = ALIGN_TO(bus_gvariant_read_word_le((uint8_t*) offsets + (i-1)*sz, sz), 8); + + r = message_peek_fields(m, &ri, 8, 8, (void**) &u64); + if (r < 0) + return r; + + field_type = BUS_MESSAGE_BSWAP64(m, *u64); + } else { + uint8_t *u8; + + r = message_peek_fields(m, &ri, 8, 1, (void**) &u8); + if (r < 0) + return r; + + field_type = *u8; + } + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + size_t where, end; + char *b; + void *q; + + end = bus_gvariant_read_word_le((uint8_t*) offsets + i*sz, sz); + + if (end < ri) + return -EBADMSG; + + where = ri = ALIGN_TO(ri, 8); + item_size = end - ri; + r = message_peek_fields(m, &where, 1, item_size, &q); + if (r < 0) + return r; + + b = memrchr(q, 0, item_size); + if (!b) + return -EBADMSG; + + sig = strndup(b+1, item_size - (b+1-(char*) q)); + if (!sig) + return -ENOMEM; + + signature = sig; + item_size = b - (char*) q; + } else { + r = message_peek_field_signature(m, &ri, 0, &signature); + if (r < 0) + return r; + } + + switch (field_type) { + + case _BUS_MESSAGE_HEADER_INVALID: + return -EBADMSG; + + case BUS_MESSAGE_HEADER_PATH: + + if (m->path) + return -EBADMSG; + + if (!streq(signature, "o")) + return -EBADMSG; + + r = message_peek_field_string(m, object_path_is_valid, &ri, item_size, &m->path); + break; + + case BUS_MESSAGE_HEADER_INTERFACE: + + if (m->interface) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, interface_name_is_valid, &ri, item_size, &m->interface); + break; + + case BUS_MESSAGE_HEADER_MEMBER: + + if (m->member) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, member_name_is_valid, &ri, item_size, &m->member); + break; + + case BUS_MESSAGE_HEADER_ERROR_NAME: + + if (m->error.name) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, error_name_is_valid, &ri, item_size, &m->error.name); + if (r >= 0) + m->error._need_free = -1; + + break; + + case BUS_MESSAGE_HEADER_DESTINATION: + + if (m->destination) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, service_name_is_valid, &ri, item_size, &m->destination); + break; + + case BUS_MESSAGE_HEADER_SENDER: + + if (m->sender) + return -EBADMSG; + + if (!streq(signature, "s")) + return -EBADMSG; + + r = message_peek_field_string(m, service_name_is_valid, &ri, item_size, &m->sender); + + if (r >= 0 && m->sender[0] == ':' && m->bus->bus_client && !m->bus->is_kernel) { + m->creds.unique_name = (char*) m->sender; + m->creds.mask |= SD_BUS_CREDS_UNIQUE_NAME & m->bus->creds_mask; + } + + break; + + + case BUS_MESSAGE_HEADER_SIGNATURE: { + const char *s; + char *c; + + if (BUS_MESSAGE_IS_GVARIANT(m)) /* only applies to dbus1 */ + return -EBADMSG; + + if (m->root_container.signature) + return -EBADMSG; + + if (!streq(signature, "g")) + return -EBADMSG; + + r = message_peek_field_signature(m, &ri, item_size, &s); + if (r < 0) + return r; + + c = strdup(s); + if (!c) + return -ENOMEM; + + free(m->root_container.signature); + m->root_container.signature = c; + break; + } + + case BUS_MESSAGE_HEADER_REPLY_SERIAL: + + if (m->reply_cookie != 0) + return -EBADMSG; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + /* 64bit on dbus2 */ + + if (!streq(signature, "t")) + return -EBADMSG; + + r = message_peek_field_uint64(m, &ri, item_size, &m->reply_cookie); + if (r < 0) + return r; + } else { + /* 32bit on dbus1 */ + uint32_t serial; + + if (!streq(signature, "u")) + return -EBADMSG; + + r = message_peek_field_uint32(m, &ri, item_size, &serial); + if (r < 0) + return r; + + m->reply_cookie = serial; + } + + if (m->reply_cookie == 0) + return -EBADMSG; + + break; + + case BUS_MESSAGE_HEADER_UNIX_FDS: + if (unix_fds_set) + return -EBADMSG; + + if (!streq(signature, "u")) + return -EBADMSG; + + r = message_peek_field_uint32(m, &ri, item_size, &unix_fds); + if (r < 0) + return -EBADMSG; + + unix_fds_set = true; + break; + + default: + if (!BUS_MESSAGE_IS_GVARIANT(m)) + r = message_skip_fields(m, &ri, (uint32_t) -1, (const char **) &signature); + } + + if (r < 0) + return r; + + i++; + } + + if (m->n_fds != unix_fds) + return -EBADMSG; + + switch (m->header->type) { + + case SD_BUS_MESSAGE_SIGNAL: + if (!m->path || !m->interface || !m->member) + return -EBADMSG; + + if (m->reply_cookie != 0) + return -EBADMSG; + + break; + + case SD_BUS_MESSAGE_METHOD_CALL: + + if (!m->path || !m->member) + return -EBADMSG; + + if (m->reply_cookie != 0) + return -EBADMSG; + + break; + + case SD_BUS_MESSAGE_METHOD_RETURN: + + if (m->reply_cookie == 0) + return -EBADMSG; + break; + + case SD_BUS_MESSAGE_METHOD_ERROR: + + if (m->reply_cookie == 0 || !m->error.name) + return -EBADMSG; + break; + } + + /* Refuse non-local messages that claim they are local */ + if (streq_ptr(m->path, "/org/freedesktop/DBus/Local")) + return -EBADMSG; + if (streq_ptr(m->interface, "org.freedesktop.DBus.Local")) + return -EBADMSG; + if (streq_ptr(m->sender, "org.freedesktop.DBus.Local")) + return -EBADMSG; + + m->root_container.end = m->user_body_size; + + if (BUS_MESSAGE_IS_GVARIANT(m)) { + r = build_struct_offsets( + m, + m->root_container.signature, + m->user_body_size, + &m->root_container.item_size, + &m->root_container.offsets, + &m->root_container.n_offsets); + if (r < 0) + return r; + } + + /* Try to read the error message, but if we can't it's a non-issue */ + if (m->header->type == SD_BUS_MESSAGE_METHOD_ERROR) + (void) sd_bus_message_read(m, "s", &m->error.message); + + return 0; +} + +_public_ int sd_bus_message_set_destination(sd_bus_message *m, const char *destination) { + assert_return(m, -EINVAL); + assert_return(destination, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(!m->destination, -EEXIST); + + return message_append_field_string(m, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, destination, &m->destination); +} + +int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz) { + size_t total; + void *p, *e; + unsigned i; + struct bus_body_part *part; + + assert(m); + assert(buffer); + assert(sz); + + total = BUS_MESSAGE_SIZE(m); + + p = malloc(total); + if (!p) + return -ENOMEM; + + e = mempcpy(p, m->header, BUS_MESSAGE_BODY_BEGIN(m)); + MESSAGE_FOREACH_PART(part, i, m) + e = mempcpy(e, part->data, part->size); + + assert(total == (size_t) ((uint8_t*) e - (uint8_t*) p)); + + *buffer = p; + *sz = total; + + return 0; +} + +int bus_message_read_strv_extend(sd_bus_message *m, char ***l) { + const char *s; + int r; + + assert(m); + assert(l); + + r = sd_bus_message_enter_container(m, 'a', "s"); + if (r <= 0) + return r; + + while ((r = sd_bus_message_read_basic(m, 's', &s)) > 0) { + r = strv_extend(l, s); + if (r < 0) + return r; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + return 1; +} + +_public_ int sd_bus_message_read_strv(sd_bus_message *m, char ***l) { + char **strv = NULL; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(l, -EINVAL); + + r = bus_message_read_strv_extend(m, &strv); + if (r <= 0) { + strv_free(strv); + return r; + } + + *l = strv; + return 1; +} + +static int bus_message_get_arg_skip( + sd_bus_message *m, + unsigned i, + char *_type, + const char **_contents) { + + unsigned j; + int r; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + for (j = 0;; j++) { + const char *contents; + char type; + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r < 0) + return r; + if (r == 0) + return -ENXIO; + + /* Don't match against arguments after the first one we don't understand */ + if (!IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE) && + !(type == SD_BUS_TYPE_ARRAY && STR_IN_SET(contents, "s", "o", "g"))) + return -ENXIO; + + if (j >= i) { + if (_contents) + *_contents = contents; + if (_type) + *_type = type; + return 0; + } + + r = sd_bus_message_skip(m, NULL); + if (r < 0) + return r; + } + +} + +int bus_message_get_arg(sd_bus_message *m, unsigned i, const char **str) { + char type; + int r; + + assert(m); + assert(str); + + r = bus_message_get_arg_skip(m, i, &type, NULL); + if (r < 0) + return r; + + if (!IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE)) + return -ENXIO; + + return sd_bus_message_read_basic(m, type, str); +} + +int bus_message_get_arg_strv(sd_bus_message *m, unsigned i, char ***strv) { + const char *contents; + char type; + int r; + + assert(m); + assert(strv); + + r = bus_message_get_arg_skip(m, i, &type, &contents); + if (r < 0) + return r; + + if (type != SD_BUS_TYPE_ARRAY) + return -ENXIO; + if (!STR_IN_SET(contents, "s", "o", "g")) + return -ENXIO; + + return sd_bus_message_read_strv(m, strv); +} + +_public_ int sd_bus_message_get_errno(sd_bus_message *m) { + assert_return(m, EINVAL); + + if (m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) + return 0; + + return sd_bus_error_get_errno(&m->error); +} + +_public_ const char* sd_bus_message_get_signature(sd_bus_message *m, int complete) { + struct bus_container *c; + + assert_return(m, NULL); + + c = complete ? &m->root_container : message_get_container(m); + return strempty(c->signature); +} + +_public_ int sd_bus_message_is_empty(sd_bus_message *m) { + assert_return(m, -EINVAL); + + return isempty(m->root_container.signature); +} + +_public_ int sd_bus_message_has_signature(sd_bus_message *m, const char *signature) { + assert_return(m, -EINVAL); + + return streq(strempty(m->root_container.signature), strempty(signature)); +} + +_public_ int sd_bus_message_copy(sd_bus_message *m, sd_bus_message *source, int all) { + bool done_something = false; + int r; + + assert_return(m, -EINVAL); + assert_return(source, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(source->sealed, -EPERM); + + do { + const char *contents; + char type; + union { + uint8_t u8; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + uint64_t u64; + int64_t s64; + double d64; + const char *string; + int i; + } basic; + + r = sd_bus_message_peek_type(source, &type, &contents); + if (r < 0) + return r; + if (r == 0) + break; + + done_something = true; + + if (bus_type_is_container(type) > 0) { + + r = sd_bus_message_enter_container(source, type, contents); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, type, contents); + if (r < 0) + return r; + + r = sd_bus_message_copy(m, source, true); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(source); + if (r < 0) + return r; + + continue; + } + + r = sd_bus_message_read_basic(source, type, &basic); + if (r < 0) + return r; + + assert(r > 0); + + if (type == SD_BUS_TYPE_OBJECT_PATH || + type == SD_BUS_TYPE_SIGNATURE || + type == SD_BUS_TYPE_STRING) + r = sd_bus_message_append_basic(m, type, basic.string); + else + r = sd_bus_message_append_basic(m, type, &basic); + + if (r < 0) + return r; + + } while (all); + + return done_something; +} + +_public_ int sd_bus_message_verify_type(sd_bus_message *m, char type, const char *contents) { + const char *c; + char t; + int r; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(!type || bus_type_is_valid(type), -EINVAL); + assert_return(!contents || signature_is_valid(contents, true), -EINVAL); + assert_return(type || contents, -EINVAL); + assert_return(!contents || !type || bus_type_is_container(type), -EINVAL); + + r = sd_bus_message_peek_type(m, &t, &c); + if (r <= 0) + return r; + + if (type != 0 && type != t) + return 0; + + if (contents && !streq_ptr(contents, c)) + return 0; + + return 1; +} + +_public_ sd_bus *sd_bus_message_get_bus(sd_bus_message *m) { + assert_return(m, NULL); + + return m->bus; +} + +int bus_message_remarshal(sd_bus *bus, sd_bus_message **m) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *n = NULL; + usec_t timeout; + int r; + + assert(bus); + assert(m); + assert(*m); + + switch ((*m)->header->type) { + + case SD_BUS_MESSAGE_SIGNAL: + r = sd_bus_message_new_signal(bus, &n, (*m)->path, (*m)->interface, (*m)->member); + if (r < 0) + return r; + + break; + + case SD_BUS_MESSAGE_METHOD_CALL: + r = sd_bus_message_new_method_call(bus, &n, (*m)->destination, (*m)->path, (*m)->interface, (*m)->member); + if (r < 0) + return r; + + break; + + case SD_BUS_MESSAGE_METHOD_RETURN: + case SD_BUS_MESSAGE_METHOD_ERROR: + + n = message_new(bus, (*m)->header->type); + if (!n) + return -ENOMEM; + + n->reply_cookie = (*m)->reply_cookie; + + r = message_append_reply_cookie(n, n->reply_cookie); + if (r < 0) + return r; + + if ((*m)->header->type == SD_BUS_MESSAGE_METHOD_ERROR && (*m)->error.name) { + r = message_append_field_string(n, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, (*m)->error.name, &n->error.message); + if (r < 0) + return r; + + n->error._need_free = -1; + } + + break; + + default: + return -EINVAL; + } + + if ((*m)->destination && !n->destination) { + r = message_append_field_string(n, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, (*m)->destination, &n->destination); + if (r < 0) + return r; + } + + if ((*m)->sender && !n->sender) { + r = message_append_field_string(n, BUS_MESSAGE_HEADER_SENDER, SD_BUS_TYPE_STRING, (*m)->sender, &n->sender); + if (r < 0) + return r; + } + + n->header->flags |= (*m)->header->flags & (BUS_MESSAGE_NO_REPLY_EXPECTED|BUS_MESSAGE_NO_AUTO_START); + + r = sd_bus_message_copy(n, *m, true); + if (r < 0) + return r; + + timeout = (*m)->timeout; + if (timeout == 0 && !((*m)->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED)) + timeout = BUS_DEFAULT_TIMEOUT; + + r = bus_message_seal(n, BUS_MESSAGE_COOKIE(*m), timeout); + if (r < 0) + return r; + + sd_bus_message_unref(*m); + *m = n; + n = NULL; + + return 0; +} + +int bus_message_append_sender(sd_bus_message *m, const char *sender) { + assert(m); + assert(sender); + + assert_return(!m->sealed, -EPERM); + assert_return(!m->sender, -EPERM); + + return message_append_field_string(m, BUS_MESSAGE_HEADER_SENDER, SD_BUS_TYPE_STRING, sender, &m->sender); +} + +_public_ int sd_bus_message_get_priority(sd_bus_message *m, int64_t *priority) { + assert_return(m, -EINVAL); + assert_return(priority, -EINVAL); + + *priority = m->priority; + return 0; +} + +_public_ int sd_bus_message_set_priority(sd_bus_message *m, int64_t priority) { + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + m->priority = priority; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-message.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-message.h new file mode 100644 index 0000000000..6a2c2d533c --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-message.h @@ -0,0 +1,244 @@ +#pragma once + +/*** + 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 . +***/ + +#include +#include +#include + +#include + +#include "bus-creds.h" +#include "bus-protocol.h" +#include "macro.h" +#include "time-util.h" + +struct bus_container { + char enclosing; + bool need_offsets:1; + + /* Indexes into the signature string */ + unsigned index, saved_index; + char *signature; + + size_t before, begin, end; + + /* dbus1: pointer to the array size value, if this is a value */ + uint32_t *array_size; + + /* gvariant: list of offsets to end of children if this is struct/dict entry/array */ + size_t *offsets, n_offsets, offsets_allocated, offset_index; + size_t item_size; + + char *peeked_signature; +}; + +struct bus_body_part { + struct bus_body_part *next; + void *data; + void *mmap_begin; + size_t size; + size_t mapped; + size_t allocated; + uint64_t memfd_offset; + int memfd; + bool free_this:1; + bool munmap_this:1; + bool sealed:1; + bool is_zero:1; +}; + +struct sd_bus_message { + unsigned n_ref; + + sd_bus *bus; + + uint64_t reply_cookie; + + const char *path; + const char *interface; + const char *member; + const char *destination; + const char *sender; + + sd_bus_error error; + + sd_bus_creds creds; + + usec_t monotonic; + usec_t realtime; + uint64_t seqnum; + int64_t priority; + uint64_t verify_destination_id; + + bool sealed:1; + bool dont_send:1; + bool allow_fds:1; + bool free_header:1; + bool free_kdbus:1; + bool free_fds:1; + bool release_kdbus:1; + bool poisoned:1; + + /* The first and last bytes of the message */ + struct bus_header *header; + void *footer; + + /* How many bytes are accessible in the above pointers */ + size_t header_accessible; + size_t footer_accessible; + + size_t fields_size; + size_t body_size; + size_t user_body_size; + + struct bus_body_part body; + struct bus_body_part *body_end; + unsigned n_body_parts; + + size_t rindex; + struct bus_body_part *cached_rindex_part; + size_t cached_rindex_part_begin; + + uint32_t n_fds; + int *fds; + + struct bus_container root_container, *containers; + size_t n_containers; + size_t containers_allocated; + + struct iovec *iovec; + struct iovec iovec_fixed[2]; + unsigned n_iovec; + + struct kdbus_msg *kdbus; + + char *peeked_signature; + + /* If set replies to this message must carry the signature + * specified here to successfully seal. This is initialized + * from the vtable data */ + const char *enforced_reply_signature; + + usec_t timeout; + + char sender_buffer[3 + DECIMAL_STR_MAX(uint64_t) + 1]; + char destination_buffer[3 + DECIMAL_STR_MAX(uint64_t) + 1]; + char *destination_ptr; + + size_t header_offsets[_BUS_MESSAGE_HEADER_MAX]; + unsigned n_header_offsets; +}; + +static inline bool BUS_MESSAGE_NEED_BSWAP(sd_bus_message *m) { + return m->header->endian != BUS_NATIVE_ENDIAN; +} + +static inline uint16_t BUS_MESSAGE_BSWAP16(sd_bus_message *m, uint16_t u) { + return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_16(u) : u; +} + +static inline uint32_t BUS_MESSAGE_BSWAP32(sd_bus_message *m, uint32_t u) { + return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_32(u) : u; +} + +static inline uint64_t BUS_MESSAGE_BSWAP64(sd_bus_message *m, uint64_t u) { + return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_64(u) : u; +} + +static inline uint64_t BUS_MESSAGE_COOKIE(sd_bus_message *m) { + if (m->header->version == 2) + return BUS_MESSAGE_BSWAP64(m, m->header->dbus2.cookie); + + return BUS_MESSAGE_BSWAP32(m, m->header->dbus1.serial); +} + +static inline size_t BUS_MESSAGE_SIZE(sd_bus_message *m) { + return + sizeof(struct bus_header) + + ALIGN8(m->fields_size) + + m->body_size; +} + +static inline size_t BUS_MESSAGE_BODY_BEGIN(sd_bus_message *m) { + return + sizeof(struct bus_header) + + ALIGN8(m->fields_size); +} + +static inline void* BUS_MESSAGE_FIELDS(sd_bus_message *m) { + return (uint8_t*) m->header + sizeof(struct bus_header); +} + +static inline bool BUS_MESSAGE_IS_GVARIANT(sd_bus_message *m) { + return m->header->version == 2; +} + +int bus_message_seal(sd_bus_message *m, uint64_t serial, usec_t timeout); +int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz); +int bus_message_read_strv_extend(sd_bus_message *m, char ***l); + +int bus_message_from_header( + sd_bus *bus, + void *header, + size_t header_accessible, + void *footer, + size_t footer_accessible, + size_t message_size, + int *fds, + unsigned n_fds, + const char *label, + size_t extra, + sd_bus_message **ret); + +int bus_message_from_malloc( + sd_bus *bus, + void *buffer, + size_t length, + int *fds, + unsigned n_fds, + const char *label, + sd_bus_message **ret); + +int bus_message_get_arg(sd_bus_message *m, unsigned i, const char **str); +int bus_message_get_arg_strv(sd_bus_message *m, unsigned i, char ***strv); + +int bus_message_append_ap(sd_bus_message *m, const char *types, va_list ap); + +int bus_message_parse_fields(sd_bus_message *m); + +struct bus_body_part *message_append_part(sd_bus_message *m); + +#define MESSAGE_FOREACH_PART(part, i, m) \ + for ((i) = 0, (part) = &(m)->body; (i) < (m)->n_body_parts; (i)++, (part) = (part)->next) + +int bus_body_part_map(struct bus_body_part *part); +void bus_body_part_unmap(struct bus_body_part *part); + +int bus_message_to_errno(sd_bus_message *m); + +int bus_message_new_synthetic_error(sd_bus *bus, uint64_t serial, const sd_bus_error *e, sd_bus_message **m); + +int bus_message_remarshal(sd_bus *bus, sd_bus_message **m); + +int bus_message_append_sender(sd_bus_message *m, const char *sender); + +void bus_message_set_sender_driver(sd_bus *bus, sd_bus_message *m); +void bus_message_set_sender_local(sd_bus *bus, sd_bus_message *m); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-objects.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-objects.c new file mode 100644 index 0000000000..1f285ae8a6 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-objects.c @@ -0,0 +1,2806 @@ +/*** + 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 . +***/ + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-introspect.h" +#include "bus-message.h" +#include "bus-objects.h" +#include "bus-signature.h" +#include "bus-slot.h" +#include "bus-type.h" +#include "bus-util.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" + +static int node_vtable_get_userdata( + sd_bus *bus, + const char *path, + struct node_vtable *c, + void **userdata, + sd_bus_error *error) { + + sd_bus_slot *s; + void *u; + int r; + + assert(bus); + assert(path); + assert(c); + + s = container_of(c, sd_bus_slot, node_vtable); + u = s->userdata; + if (c->find) { + bus->current_slot = sd_bus_slot_ref(s); + bus->current_userdata = u; + r = c->find(bus, path, c->interface, u, &u, error); + bus->current_userdata = NULL; + bus->current_slot = sd_bus_slot_unref(s); + + if (r < 0) + return r; + if (sd_bus_error_is_set(error)) + return -sd_bus_error_get_errno(error); + if (r == 0) + return r; + } + + if (userdata) + *userdata = u; + + return 1; +} + +static void *vtable_method_convert_userdata(const sd_bus_vtable *p, void *u) { + assert(p); + + return (uint8_t*) u + p->x.method.offset; +} + +static void *vtable_property_convert_userdata(const sd_bus_vtable *p, void *u) { + assert(p); + + return (uint8_t*) u + p->x.property.offset; +} + +static int vtable_property_get_userdata( + sd_bus *bus, + const char *path, + struct vtable_member *p, + void **userdata, + sd_bus_error *error) { + + void *u; + int r; + + assert(bus); + assert(path); + assert(p); + assert(userdata); + + r = node_vtable_get_userdata(bus, path, p->parent, &u, error); + if (r <= 0) + return r; + if (bus->nodes_modified) + return 0; + + *userdata = vtable_property_convert_userdata(p->vtable, u); + return 1; +} + +static int add_enumerated_to_set( + sd_bus *bus, + const char *prefix, + struct node_enumerator *first, + Set *s, + sd_bus_error *error) { + + struct node_enumerator *c; + int r; + + assert(bus); + assert(prefix); + assert(s); + + LIST_FOREACH(enumerators, c, first) { + char **children = NULL, **k; + sd_bus_slot *slot; + + if (bus->nodes_modified) + return 0; + + slot = container_of(c, sd_bus_slot, node_enumerator); + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_userdata = slot->userdata; + r = c->callback(bus, prefix, slot->userdata, &children, error); + bus->current_userdata = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + + if (r < 0) + return r; + if (sd_bus_error_is_set(error)) + return -sd_bus_error_get_errno(error); + + STRV_FOREACH(k, children) { + if (r < 0) { + free(*k); + continue; + } + + if (!object_path_is_valid(*k)){ + free(*k); + r = -EINVAL; + continue; + } + + if (!object_path_startswith(*k, prefix)) { + free(*k); + continue; + } + + r = set_consume(s, *k); + if (r == -EEXIST) + r = 0; + } + + free(children); + if (r < 0) + return r; + } + + return 0; +} + +enum { + /* if set, add_subtree() works recursively */ + CHILDREN_RECURSIVE = (1U << 1), + /* if set, add_subtree() scans object-manager hierarchies recursively */ + CHILDREN_SUBHIERARCHIES = (1U << 0), +}; + +static int add_subtree_to_set( + sd_bus *bus, + const char *prefix, + struct node *n, + unsigned int flags, + Set *s, + sd_bus_error *error) { + + struct node *i; + int r; + + assert(bus); + assert(prefix); + assert(n); + assert(s); + + r = add_enumerated_to_set(bus, prefix, n->enumerators, s, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + LIST_FOREACH(siblings, i, n->child) { + char *t; + + if (!object_path_startswith(i->path, prefix)) + continue; + + t = strdup(i->path); + if (!t) + return -ENOMEM; + + r = set_consume(s, t); + if (r < 0 && r != -EEXIST) + return r; + + if ((flags & CHILDREN_RECURSIVE) && + ((flags & CHILDREN_SUBHIERARCHIES) || !i->object_managers)) { + r = add_subtree_to_set(bus, prefix, i, flags, s, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + } + + return 0; +} + +static int get_child_nodes( + sd_bus *bus, + const char *prefix, + struct node *n, + unsigned int flags, + Set **_s, + sd_bus_error *error) { + + Set *s = NULL; + int r; + + assert(bus); + assert(prefix); + assert(n); + assert(_s); + + s = set_new(&string_hash_ops); + if (!s) + return -ENOMEM; + + r = add_subtree_to_set(bus, prefix, n, flags, s, error); + if (r < 0) { + set_free_free(s); + return r; + } + + *_s = s; + return 0; +} + +static int node_callbacks_run( + sd_bus *bus, + sd_bus_message *m, + struct node_callback *first, + bool require_fallback, + bool *found_object) { + + struct node_callback *c; + int r; + + assert(bus); + assert(m); + assert(found_object); + + LIST_FOREACH(callbacks, c, first) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + sd_bus_slot *slot; + + if (bus->nodes_modified) + return 0; + + if (require_fallback && !c->is_fallback) + continue; + + *found_object = true; + + if (c->last_iteration == bus->iteration_counter) + continue; + + c->last_iteration = bus->iteration_counter; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + slot = container_of(c, sd_bus_slot, node_callback); + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = c->callback; + bus->current_userdata = slot->userdata; + r = c->callback(m, slot->userdata, &error_buffer); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + + r = bus_maybe_reply_error(m, r, &error_buffer); + if (r != 0) + return r; + } + + return 0; +} + +#define CAPABILITY_SHIFT(x) (((x) >> __builtin_ctzll(_SD_BUS_VTABLE_CAPABILITY_MASK)) & 0xFFFF) + +static int check_access(sd_bus *bus, sd_bus_message *m, struct vtable_member *c, sd_bus_error *error) { + uint64_t cap; + int r; + + assert(bus); + assert(m); + assert(c); + + /* If the entire bus is trusted let's grant access */ + if (bus->trusted) + return 0; + + /* If the member is marked UNPRIVILEGED let's grant access */ + if (c->vtable->flags & SD_BUS_VTABLE_UNPRIVILEGED) + return 0; + + /* Check have the caller has the requested capability + * set. Note that the flags value contains the capability + * number plus one, which we need to subtract here. We do this + * so that we have 0 as special value for "default + * capability". */ + cap = CAPABILITY_SHIFT(c->vtable->flags); + if (cap == 0) + cap = CAPABILITY_SHIFT(c->parent->vtable[0].flags); + if (cap == 0) + cap = CAP_SYS_ADMIN; + else + cap --; + + r = sd_bus_query_sender_privilege(m, cap); + if (r < 0) + return r; + if (r > 0) + return 0; + + return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Access to %s.%s() not permitted.", c->interface, c->member); +} + +static int method_callbacks_run( + sd_bus *bus, + sd_bus_message *m, + struct vtable_member *c, + bool require_fallback, + bool *found_object) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *signature; + void *u; + int r; + + assert(bus); + assert(m); + assert(c); + assert(found_object); + + if (require_fallback && !c->parent->is_fallback) + return 0; + + r = check_access(bus, m, c, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + + r = node_vtable_get_userdata(bus, m->path, c->parent, &u, &error); + if (r <= 0) + return bus_maybe_reply_error(m, r, &error); + if (bus->nodes_modified) + return 0; + + u = vtable_method_convert_userdata(c->vtable, u); + + *found_object = true; + + if (c->last_iteration == bus->iteration_counter) + return 0; + + c->last_iteration = bus->iteration_counter; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + signature = sd_bus_message_get_signature(m, true); + if (!signature) + return -EINVAL; + + if (!streq(strempty(c->vtable->x.method.signature), signature)) + return sd_bus_reply_method_errorf( + m, + SD_BUS_ERROR_INVALID_ARGS, + "Invalid arguments '%s' to call %s.%s(), expecting '%s'.", + signature, c->interface, c->member, strempty(c->vtable->x.method.signature)); + + /* Keep track what the signature of the reply to this message + * should be, so that this can be enforced when sealing the + * reply. */ + m->enforced_reply_signature = strempty(c->vtable->x.method.result); + + if (c->vtable->x.method.handler) { + sd_bus_slot *slot; + + slot = container_of(c->parent, sd_bus_slot, node_vtable); + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = c->vtable->x.method.handler; + bus->current_userdata = u; + r = c->vtable->x.method.handler(m, u, &error); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + + return bus_maybe_reply_error(m, r, &error); + } + + /* If the method callback is NULL, make this a successful NOP */ + r = sd_bus_reply_method_return(m, NULL); + if (r < 0) + return r; + + return 1; +} + +static int invoke_property_get( + sd_bus *bus, + sd_bus_slot *slot, + const sd_bus_vtable *v, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + const void *p; + int r; + + assert(bus); + assert(slot); + assert(v); + assert(path); + assert(interface); + assert(property); + assert(reply); + + if (v->x.property.get) { + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_userdata = userdata; + r = v->x.property.get(bus, path, interface, property, reply, userdata, error); + bus->current_userdata = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + + if (r < 0) + return r; + if (sd_bus_error_is_set(error)) + return -sd_bus_error_get_errno(error); + return r; + } + + /* Automatic handling if no callback is defined. */ + + if (streq(v->x.property.signature, "as")) + return sd_bus_message_append_strv(reply, *(char***) userdata); + + assert(signature_is_single(v->x.property.signature, false)); + assert(bus_type_is_basic(v->x.property.signature[0])); + + switch (v->x.property.signature[0]) { + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_SIGNATURE: + p = strempty(*(char**) userdata); + break; + + case SD_BUS_TYPE_OBJECT_PATH: + p = *(char**) userdata; + assert(p); + break; + + default: + p = userdata; + break; + } + + return sd_bus_message_append_basic(reply, v->x.property.signature[0], p); +} + +static int invoke_property_set( + sd_bus *bus, + sd_bus_slot *slot, + const sd_bus_vtable *v, + const char *path, + const char *interface, + const char *property, + sd_bus_message *value, + void *userdata, + sd_bus_error *error) { + + int r; + + assert(bus); + assert(slot); + assert(v); + assert(path); + assert(interface); + assert(property); + assert(value); + + if (v->x.property.set) { + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_userdata = userdata; + r = v->x.property.set(bus, path, interface, property, value, userdata, error); + bus->current_userdata = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + + if (r < 0) + return r; + if (sd_bus_error_is_set(error)) + return -sd_bus_error_get_errno(error); + return r; + } + + /* Automatic handling if no callback is defined. */ + + assert(signature_is_single(v->x.property.signature, false)); + assert(bus_type_is_basic(v->x.property.signature[0])); + + switch (v->x.property.signature[0]) { + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: { + const char *p; + char *n; + + r = sd_bus_message_read_basic(value, v->x.property.signature[0], &p); + if (r < 0) + return r; + + n = strdup(p); + if (!n) + return -ENOMEM; + + free(*(char**) userdata); + *(char**) userdata = n; + + break; + } + + default: + r = sd_bus_message_read_basic(value, v->x.property.signature[0], userdata); + if (r < 0) + return r; + + break; + } + + return 1; +} + +static int property_get_set_callbacks_run( + sd_bus *bus, + sd_bus_message *m, + struct vtable_member *c, + bool require_fallback, + bool is_get, + bool *found_object) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + sd_bus_slot *slot; + void *u = NULL; + int r; + + assert(bus); + assert(m); + assert(c); + assert(found_object); + + if (require_fallback && !c->parent->is_fallback) + return 0; + + r = vtable_property_get_userdata(bus, m->path, c, &u, &error); + if (r <= 0) + return bus_maybe_reply_error(m, r, &error); + if (bus->nodes_modified) + return 0; + + slot = container_of(c->parent, sd_bus_slot, node_vtable); + + *found_object = true; + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) + return r; + + if (is_get) { + /* Note that we do not protect against reexecution + * here (using the last_iteration check, see below), + * should the node tree have changed and we got called + * again. We assume that property Get() calls are + * ultimately without side-effects or if they aren't + * then at least idempotent. */ + + r = sd_bus_message_open_container(reply, 'v', c->vtable->x.property.signature); + if (r < 0) + return r; + + /* Note that we do not do an access check here. Read + * access to properties is always unrestricted, since + * PropertiesChanged signals broadcast contents + * anyway. */ + + r = invoke_property_get(bus, slot, c->vtable, m->path, c->interface, c->member, reply, u, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + + if (bus->nodes_modified) + return 0; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + } else { + const char *signature = NULL; + char type = 0; + + if (c->vtable->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Property '%s' is not writable.", c->member); + + /* Avoid that we call the set routine more than once + * if the processing of this message got restarted + * because the node tree changed. */ + if (c->last_iteration == bus->iteration_counter) + return 0; + + c->last_iteration = bus->iteration_counter; + + r = sd_bus_message_peek_type(m, &type, &signature); + if (r < 0) + return r; + + if (type != 'v' || !streq(strempty(signature), strempty(c->vtable->x.property.signature))) + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Incorrect parameters for property '%s', expected '%s', got '%s'.", c->member, strempty(c->vtable->x.property.signature), strempty(signature)); + + r = sd_bus_message_enter_container(m, 'v', c->vtable->x.property.signature); + if (r < 0) + return r; + + r = check_access(bus, m, c, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + + r = invoke_property_set(bus, slot, c->vtable, m->path, c->interface, c->member, m, u, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + + if (bus->nodes_modified) + return 0; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + } + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + + return 1; +} + +static int vtable_append_one_property( + sd_bus *bus, + sd_bus_message *reply, + const char *path, + struct node_vtable *c, + const sd_bus_vtable *v, + void *userdata, + sd_bus_error *error) { + + sd_bus_slot *slot; + int r; + + assert(bus); + assert(reply); + assert(path); + assert(c); + assert(v); + + r = sd_bus_message_open_container(reply, 'e', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", v->x.property.member); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'v', v->x.property.signature); + if (r < 0) + return r; + + slot = container_of(c, sd_bus_slot, node_vtable); + + r = invoke_property_get(bus, slot, v, path, c->interface, v->x.property.member, reply, vtable_property_convert_userdata(v, userdata), error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return 0; +} + +static int vtable_append_all_properties( + sd_bus *bus, + sd_bus_message *reply, + const char *path, + struct node_vtable *c, + void *userdata, + sd_bus_error *error) { + + const sd_bus_vtable *v; + int r; + + assert(bus); + assert(reply); + assert(path); + assert(c); + + if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN) + return 1; + + for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { + if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) + continue; + + if (v->flags & SD_BUS_VTABLE_HIDDEN) + continue; + + if (v->flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT) + continue; + + r = vtable_append_one_property(bus, reply, path, c, v, userdata, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + + return 1; +} + +static int property_get_all_callbacks_run( + sd_bus *bus, + sd_bus_message *m, + struct node_vtable *first, + bool require_fallback, + const char *iface, + bool *found_object) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + struct node_vtable *c; + bool found_interface; + int r; + + assert(bus); + assert(m); + assert(found_object); + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + + found_interface = !iface || + streq(iface, "org.freedesktop.DBus.Properties") || + streq(iface, "org.freedesktop.DBus.Peer") || + streq(iface, "org.freedesktop.DBus.Introspectable"); + + LIST_FOREACH(vtables, c, first) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + void *u; + + if (require_fallback && !c->is_fallback) + continue; + + r = node_vtable_get_userdata(bus, m->path, c, &u, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + *found_object = true; + + if (iface && !streq(c->interface, iface)) + continue; + found_interface = true; + + r = vtable_append_all_properties(bus, reply, m->path, c, u, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + if (bus->nodes_modified) + return 0; + } + + if (!found_interface) { + r = sd_bus_reply_method_errorf( + m, + SD_BUS_ERROR_UNKNOWN_INTERFACE, + "Unknown interface '%s'.", iface); + if (r < 0) + return r; + + return 1; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + + return 1; +} + +static int bus_node_exists( + sd_bus *bus, + struct node *n, + const char *path, + bool require_fallback) { + + struct node_vtable *c; + struct node_callback *k; + int r; + + assert(bus); + assert(n); + assert(path); + + /* Tests if there's anything attached directly to this node + * for the specified path */ + + if (!require_fallback && (n->enumerators || n->object_managers)) + return true; + + LIST_FOREACH(callbacks, k, n->callbacks) { + if (require_fallback && !k->is_fallback) + continue; + + return 1; + } + + LIST_FOREACH(vtables, c, n->vtables) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + if (require_fallback && !c->is_fallback) + continue; + + r = node_vtable_get_userdata(bus, path, c, NULL, &error); + if (r != 0) + return r; + if (bus->nodes_modified) + return 0; + } + + return 0; +} + +static int process_introspect( + sd_bus *bus, + sd_bus_message *m, + struct node *n, + bool require_fallback, + bool *found_object) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_set_free_free_ Set *s = NULL; + const char *previous_interface = NULL; + struct introspect intro; + struct node_vtable *c; + bool empty; + int r; + + assert(bus); + assert(m); + assert(n); + assert(found_object); + + r = get_child_nodes(bus, m->path, n, 0, &s, &error); + if (r < 0) + return bus_maybe_reply_error(m, r, &error); + if (bus->nodes_modified) + return 0; + + r = introspect_begin(&intro, bus->trusted); + if (r < 0) + return r; + + r = introspect_write_default_interfaces(&intro, !require_fallback && n->object_managers); + if (r < 0) + return r; + + empty = set_isempty(s); + + LIST_FOREACH(vtables, c, n->vtables) { + if (require_fallback && !c->is_fallback) + continue; + + r = node_vtable_get_userdata(bus, m->path, c, NULL, &error); + if (r < 0) { + r = bus_maybe_reply_error(m, r, &error); + goto finish; + } + if (bus->nodes_modified) { + r = 0; + goto finish; + } + if (r == 0) + continue; + + empty = false; + + if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN) + continue; + + if (!streq_ptr(previous_interface, c->interface)) { + + if (previous_interface) + fputs(" \n", intro.f); + + fprintf(intro.f, " \n", c->interface); + } + + r = introspect_write_interface(&intro, c->vtable); + if (r < 0) + goto finish; + + previous_interface = c->interface; + } + + if (previous_interface) + fputs(" \n", intro.f); + + if (empty) { + /* Nothing?, let's see if we exist at all, and if not + * refuse to do anything */ + r = bus_node_exists(bus, n, m->path, require_fallback); + if (r <= 0) + goto finish; + if (bus->nodes_modified) { + r = 0; + goto finish; + } + } + + *found_object = true; + + r = introspect_write_child_nodes(&intro, s, m->path); + if (r < 0) + goto finish; + + r = introspect_finish(&intro, bus, m, &reply); + if (r < 0) + goto finish; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + goto finish; + + r = 1; + +finish: + introspect_free(&intro); + return r; +} + +static int object_manager_serialize_path( + sd_bus *bus, + sd_bus_message *reply, + const char *prefix, + const char *path, + bool require_fallback, + sd_bus_error *error) { + + const char *previous_interface = NULL; + bool found_something = false; + struct node_vtable *i; + struct node *n; + int r; + + assert(bus); + assert(reply); + assert(prefix); + assert(path); + assert(error); + + n = hashmap_get(bus->nodes, prefix); + if (!n) + return 0; + + LIST_FOREACH(vtables, i, n->vtables) { + void *u; + + if (require_fallback && !i->is_fallback) + continue; + + r = node_vtable_get_userdata(bus, path, i, &u, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + if (!found_something) { + + /* Open the object part */ + + r = sd_bus_message_open_container(reply, 'e', "oa{sa{sv}}"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "o", path); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "{sa{sv}}"); + if (r < 0) + return r; + + found_something = true; + } + + if (!streq_ptr(previous_interface, i->interface)) { + + /* Maybe close the previous interface part */ + + if (previous_interface) { + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + + /* Open the new interface part */ + + r = sd_bus_message_open_container(reply, 'e', "sa{sv}"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", i->interface); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "{sv}"); + if (r < 0) + return r; + } + + r = vtable_append_all_properties(bus, reply, path, i, u, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + previous_interface = i->interface; + } + + if (previous_interface) { + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + + if (found_something) { + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + + return 1; +} + +static int object_manager_serialize_path_and_fallbacks( + sd_bus *bus, + sd_bus_message *reply, + const char *path, + sd_bus_error *error) { + + char *prefix; + int r; + + assert(bus); + assert(reply); + assert(path); + assert(error); + + /* First, add all vtables registered for this path */ + r = object_manager_serialize_path(bus, reply, path, path, false, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + /* Second, add fallback vtables registered for any of the prefixes */ + prefix = alloca(strlen(path) + 1); + OBJECT_PATH_FOREACH_PREFIX(prefix, path) { + r = object_manager_serialize_path(bus, reply, prefix, path, true, error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + + return 0; +} + +static int process_get_managed_objects( + sd_bus *bus, + sd_bus_message *m, + struct node *n, + bool require_fallback, + bool *found_object) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_set_free_free_ Set *s = NULL; + Iterator i; + char *path; + int r; + + assert(bus); + assert(m); + assert(n); + assert(found_object); + + /* Spec says, GetManagedObjects() is only implemented on the root of a + * sub-tree. Therefore, we require a registered object-manager on + * exactly the queried path, otherwise, we refuse to respond. */ + + if (require_fallback || !n->object_managers) + return 0; + + r = get_child_nodes(bus, m->path, n, CHILDREN_RECURSIVE, &s, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "{oa{sa{sv}}}"); + if (r < 0) + return r; + + SET_FOREACH(path, s, i) { + r = object_manager_serialize_path_and_fallbacks(bus, reply, path, &error); + if (r < 0) + return r; + + if (bus->nodes_modified) + return 0; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + + return 1; +} + +static int object_find_and_run( + sd_bus *bus, + sd_bus_message *m, + const char *p, + bool require_fallback, + bool *found_object) { + + struct node *n; + struct vtable_member vtable_key, *v; + int r; + + assert(bus); + assert(m); + assert(p); + assert(found_object); + + n = hashmap_get(bus->nodes, p); + if (!n) + return 0; + + /* First, try object callbacks */ + r = node_callbacks_run(bus, m, n->callbacks, require_fallback, found_object); + if (r != 0) + return r; + if (bus->nodes_modified) + return 0; + + if (!m->interface || !m->member) + return 0; + + /* Then, look for a known method */ + vtable_key.path = (char*) p; + vtable_key.interface = m->interface; + vtable_key.member = m->member; + + v = hashmap_get(bus->vtable_methods, &vtable_key); + if (v) { + r = method_callbacks_run(bus, m, v, require_fallback, found_object); + if (r != 0) + return r; + if (bus->nodes_modified) + return 0; + } + + /* Then, look for a known property */ + if (streq(m->interface, "org.freedesktop.DBus.Properties")) { + bool get = false; + + get = streq(m->member, "Get"); + + if (get || streq(m->member, "Set")) { + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + vtable_key.path = (char*) p; + + r = sd_bus_message_read(m, "ss", &vtable_key.interface, &vtable_key.member); + if (r < 0) + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface and member parameters"); + + v = hashmap_get(bus->vtable_properties, &vtable_key); + if (v) { + r = property_get_set_callbacks_run(bus, m, v, require_fallback, get, found_object); + if (r != 0) + return r; + } + + } else if (streq(m->member, "GetAll")) { + const char *iface; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + r = sd_bus_message_read(m, "s", &iface); + if (r < 0) + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface parameter"); + + if (iface[0] == 0) + iface = NULL; + + r = property_get_all_callbacks_run(bus, m, n->vtables, require_fallback, iface, found_object); + if (r != 0) + return r; + } + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { + + if (!isempty(sd_bus_message_get_signature(m, true))) + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters"); + + r = process_introspect(bus, m, n, require_fallback, found_object); + if (r != 0) + return r; + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.ObjectManager", "GetManagedObjects")) { + + if (!isempty(sd_bus_message_get_signature(m, true))) + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters"); + + r = process_get_managed_objects(bus, m, n, require_fallback, found_object); + if (r != 0) + return r; + } + + if (bus->nodes_modified) + return 0; + + if (!*found_object) { + r = bus_node_exists(bus, n, m->path, require_fallback); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r > 0) + *found_object = true; + } + + return 0; +} + +int bus_process_object(sd_bus *bus, sd_bus_message *m) { + int r; + size_t pl; + bool found_object = false; + + assert(bus); + assert(m); + + if (bus->hello_flags & KDBUS_HELLO_MONITOR) + return 0; + + if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) + return 0; + + if (hashmap_isempty(bus->nodes)) + return 0; + + /* Never respond to broadcast messages */ + if (bus->bus_client && !m->destination) + return 0; + + assert(m->path); + assert(m->member); + + pl = strlen(m->path); + do { + char prefix[pl+1]; + + bus->nodes_modified = false; + + r = object_find_and_run(bus, m, m->path, false, &found_object); + if (r != 0) + return r; + + /* Look for fallback prefixes */ + OBJECT_PATH_FOREACH_PREFIX(prefix, m->path) { + + if (bus->nodes_modified) + break; + + r = object_find_and_run(bus, m, prefix, true, &found_object); + if (r != 0) + return r; + } + + } while (bus->nodes_modified); + + if (!found_object) + return 0; + + if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get") || + sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Set")) + r = sd_bus_reply_method_errorf( + m, + SD_BUS_ERROR_UNKNOWN_PROPERTY, + "Unknown property or interface."); + else + r = sd_bus_reply_method_errorf( + m, + SD_BUS_ERROR_UNKNOWN_METHOD, + "Unknown method '%s' or interface '%s'.", m->member, m->interface); + + if (r < 0) + return r; + + return 1; +} + +static struct node *bus_node_allocate(sd_bus *bus, const char *path) { + struct node *n, *parent; + const char *e; + _cleanup_free_ char *s = NULL; + char *p; + int r; + + assert(bus); + assert(path); + assert(path[0] == '/'); + + n = hashmap_get(bus->nodes, path); + if (n) + return n; + + r = hashmap_ensure_allocated(&bus->nodes, &string_hash_ops); + if (r < 0) + return NULL; + + s = strdup(path); + if (!s) + return NULL; + + if (streq(path, "/")) + parent = NULL; + else { + e = strrchr(path, '/'); + assert(e); + + p = strndupa(path, MAX(1, e - path)); + + parent = bus_node_allocate(bus, p); + if (!parent) + return NULL; + } + + n = new0(struct node, 1); + if (!n) + return NULL; + + n->parent = parent; + n->path = s; + s = NULL; /* do not free */ + + r = hashmap_put(bus->nodes, n->path, n); + if (r < 0) { + free(n->path); + free(n); + return NULL; + } + + if (parent) + LIST_PREPEND(siblings, parent->child, n); + + return n; +} + +void bus_node_gc(sd_bus *b, struct node *n) { + assert(b); + + if (!n) + return; + + if (n->child || + n->callbacks || + n->vtables || + n->enumerators || + n->object_managers) + return; + + assert(hashmap_remove(b->nodes, n->path) == n); + + if (n->parent) + LIST_REMOVE(siblings, n->parent->child, n); + + free(n->path); + bus_node_gc(b, n->parent); + free(n); +} + +static int bus_find_parent_object_manager(sd_bus *bus, struct node **out, const char *path) { + struct node *n; + + assert(bus); + assert(path); + + n = hashmap_get(bus->nodes, path); + if (!n) { + char *prefix; + + prefix = alloca(strlen(path) + 1); + OBJECT_PATH_FOREACH_PREFIX(prefix, path) { + n = hashmap_get(bus->nodes, prefix); + if (n) + break; + } + } + + while (n && !n->object_managers) + n = n->parent; + + if (out) + *out = n; + return !!n; +} + +static int bus_add_object( + sd_bus *bus, + sd_bus_slot **slot, + bool fallback, + const char *path, + sd_bus_message_handler_t callback, + void *userdata) { + + sd_bus_slot *s; + struct node *n; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + n = bus_node_allocate(bus, path); + if (!n) + return -ENOMEM; + + s = bus_slot_allocate(bus, !slot, BUS_NODE_CALLBACK, sizeof(struct node_callback), userdata); + if (!s) { + r = -ENOMEM; + goto fail; + } + + s->node_callback.callback = callback; + s->node_callback.is_fallback = fallback; + + s->node_callback.node = n; + LIST_PREPEND(callbacks, n->callbacks, &s->node_callback); + bus->nodes_modified = true; + + if (slot) + *slot = s; + + return 0; + +fail: + sd_bus_slot_unref(s); + bus_node_gc(bus, n); + + return r; +} + +_public_ int sd_bus_add_object( + sd_bus *bus, + sd_bus_slot **slot, + const char *path, + sd_bus_message_handler_t callback, + void *userdata) { + + return bus_add_object(bus, slot, false, path, callback, userdata); +} + +_public_ int sd_bus_add_fallback( + sd_bus *bus, + sd_bus_slot **slot, + const char *prefix, + sd_bus_message_handler_t callback, + void *userdata) { + + return bus_add_object(bus, slot, true, prefix, callback, userdata); +} + +static void vtable_member_hash_func(const void *a, struct siphash *state) { + const struct vtable_member *m = a; + + assert(m); + + string_hash_func(m->path, state); + string_hash_func(m->interface, state); + string_hash_func(m->member, state); +} + +static int vtable_member_compare_func(const void *a, const void *b) { + const struct vtable_member *x = a, *y = b; + int r; + + assert(x); + assert(y); + + r = strcmp(x->path, y->path); + if (r != 0) + return r; + + r = strcmp(x->interface, y->interface); + if (r != 0) + return r; + + return strcmp(x->member, y->member); +} + +static const struct hash_ops vtable_member_hash_ops = { + .hash = vtable_member_hash_func, + .compare = vtable_member_compare_func +}; + +static int add_object_vtable_internal( + sd_bus *bus, + sd_bus_slot **slot, + const char *path, + const char *interface, + const sd_bus_vtable *vtable, + bool fallback, + sd_bus_object_find_t find, + void *userdata) { + + sd_bus_slot *s = NULL; + struct node_vtable *i, *existing = NULL; + const sd_bus_vtable *v; + struct node *n; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(interface_name_is_valid(interface), -EINVAL); + assert_return(vtable, -EINVAL); + assert_return(vtable[0].type == _SD_BUS_VTABLE_START, -EINVAL); + assert_return(vtable[0].x.start.element_size == sizeof(struct sd_bus_vtable), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(!streq(interface, "org.freedesktop.DBus.Properties") && + !streq(interface, "org.freedesktop.DBus.Introspectable") && + !streq(interface, "org.freedesktop.DBus.Peer") && + !streq(interface, "org.freedesktop.DBus.ObjectManager"), -EINVAL); + + r = hashmap_ensure_allocated(&bus->vtable_methods, &vtable_member_hash_ops); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&bus->vtable_properties, &vtable_member_hash_ops); + if (r < 0) + return r; + + n = bus_node_allocate(bus, path); + if (!n) + return -ENOMEM; + + LIST_FOREACH(vtables, i, n->vtables) { + if (i->is_fallback != fallback) { + r = -EPROTOTYPE; + goto fail; + } + + if (streq(i->interface, interface)) { + + if (i->vtable == vtable) { + r = -EEXIST; + goto fail; + } + + existing = i; + } + } + + s = bus_slot_allocate(bus, !slot, BUS_NODE_VTABLE, sizeof(struct node_vtable), userdata); + if (!s) { + r = -ENOMEM; + goto fail; + } + + s->node_vtable.is_fallback = fallback; + s->node_vtable.vtable = vtable; + s->node_vtable.find = find; + + s->node_vtable.interface = strdup(interface); + if (!s->node_vtable.interface) { + r = -ENOMEM; + goto fail; + } + + for (v = s->node_vtable.vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { + + switch (v->type) { + + case _SD_BUS_VTABLE_METHOD: { + struct vtable_member *m; + + if (!member_name_is_valid(v->x.method.member) || + !signature_is_valid(strempty(v->x.method.signature), false) || + !signature_is_valid(strempty(v->x.method.result), false) || + !(v->x.method.handler || (isempty(v->x.method.signature) && isempty(v->x.method.result))) || + v->flags & (SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) { + r = -EINVAL; + goto fail; + } + + m = new0(struct vtable_member, 1); + if (!m) { + r = -ENOMEM; + goto fail; + } + + m->parent = &s->node_vtable; + m->path = n->path; + m->interface = s->node_vtable.interface; + m->member = v->x.method.member; + m->vtable = v; + + r = hashmap_put(bus->vtable_methods, m, m); + if (r < 0) { + free(m); + goto fail; + } + + break; + } + + case _SD_BUS_VTABLE_WRITABLE_PROPERTY: + + if (!(v->x.property.set || bus_type_is_basic(v->x.property.signature[0]))) { + r = -EINVAL; + goto fail; + } + + if (v->flags & SD_BUS_VTABLE_PROPERTY_CONST) { + r = -EINVAL; + goto fail; + } + + /* Fall through */ + + case _SD_BUS_VTABLE_PROPERTY: { + struct vtable_member *m; + + if (!member_name_is_valid(v->x.property.member) || + !signature_is_single(v->x.property.signature, false) || + !(v->x.property.get || bus_type_is_basic(v->x.property.signature[0]) || streq(v->x.property.signature, "as")) || + (v->flags & SD_BUS_VTABLE_METHOD_NO_REPLY) || + (!!(v->flags & SD_BUS_VTABLE_PROPERTY_CONST) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) > 1 || + ((v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) && (v->flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT)) || + (v->flags & SD_BUS_VTABLE_UNPRIVILEGED && v->type == _SD_BUS_VTABLE_PROPERTY)) { + r = -EINVAL; + goto fail; + } + + m = new0(struct vtable_member, 1); + if (!m) { + r = -ENOMEM; + goto fail; + } + + m->parent = &s->node_vtable; + m->path = n->path; + m->interface = s->node_vtable.interface; + m->member = v->x.property.member; + m->vtable = v; + + r = hashmap_put(bus->vtable_properties, m, m); + if (r < 0) { + free(m); + goto fail; + } + + break; + } + + case _SD_BUS_VTABLE_SIGNAL: + + if (!member_name_is_valid(v->x.signal.member) || + !signature_is_valid(strempty(v->x.signal.signature), false) || + v->flags & SD_BUS_VTABLE_UNPRIVILEGED) { + r = -EINVAL; + goto fail; + } + + break; + + default: + r = -EINVAL; + goto fail; + } + } + + s->node_vtable.node = n; + LIST_INSERT_AFTER(vtables, n->vtables, existing, &s->node_vtable); + bus->nodes_modified = true; + + if (slot) + *slot = s; + + return 0; + +fail: + sd_bus_slot_unref(s); + bus_node_gc(bus, n); + + return r; +} + +_public_ int sd_bus_add_object_vtable( + sd_bus *bus, + sd_bus_slot **slot, + const char *path, + const char *interface, + const sd_bus_vtable *vtable, + void *userdata) { + + return add_object_vtable_internal(bus, slot, path, interface, vtable, false, NULL, userdata); +} + +_public_ int sd_bus_add_fallback_vtable( + sd_bus *bus, + sd_bus_slot **slot, + const char *prefix, + const char *interface, + const sd_bus_vtable *vtable, + sd_bus_object_find_t find, + void *userdata) { + + return add_object_vtable_internal(bus, slot, prefix, interface, vtable, true, find, userdata); +} + +_public_ int sd_bus_add_node_enumerator( + sd_bus *bus, + sd_bus_slot **slot, + const char *path, + sd_bus_node_enumerator_t callback, + void *userdata) { + + sd_bus_slot *s; + struct node *n; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + n = bus_node_allocate(bus, path); + if (!n) + return -ENOMEM; + + s = bus_slot_allocate(bus, !slot, BUS_NODE_ENUMERATOR, sizeof(struct node_enumerator), userdata); + if (!s) { + r = -ENOMEM; + goto fail; + } + + s->node_enumerator.callback = callback; + + s->node_enumerator.node = n; + LIST_PREPEND(enumerators, n->enumerators, &s->node_enumerator); + bus->nodes_modified = true; + + if (slot) + *slot = s; + + return 0; + +fail: + sd_bus_slot_unref(s); + bus_node_gc(bus, n); + + return r; +} + +static int emit_properties_changed_on_interface( + sd_bus *bus, + const char *prefix, + const char *path, + const char *interface, + bool require_fallback, + bool *found_interface, + char **names) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + bool has_invalidating = false, has_changing = false; + struct vtable_member key = {}; + struct node_vtable *c; + struct node *n; + char **property; + void *u = NULL; + int r; + + assert(bus); + assert(prefix); + assert(path); + assert(interface); + assert(found_interface); + + n = hashmap_get(bus->nodes, prefix); + if (!n) + return 0; + + r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.Properties", "PropertiesChanged"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", interface); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "{sv}"); + if (r < 0) + return r; + + key.path = prefix; + key.interface = interface; + + LIST_FOREACH(vtables, c, n->vtables) { + if (require_fallback && !c->is_fallback) + continue; + + if (!streq(c->interface, interface)) + continue; + + r = node_vtable_get_userdata(bus, path, c, &u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + *found_interface = true; + + if (names) { + /* If the caller specified a list of + * properties we include exactly those in the + * PropertiesChanged message */ + + STRV_FOREACH(property, names) { + struct vtable_member *v; + + assert_return(member_name_is_valid(*property), -EINVAL); + + key.member = *property; + v = hashmap_get(bus->vtable_properties, &key); + if (!v) + return -ENOENT; + + /* If there are two vtables for the same + * interface, let's handle this property when + * we come to that vtable. */ + if (c != v->parent) + continue; + + assert_return(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE || + v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION, -EDOM); + + assert_return(!(v->vtable->flags & SD_BUS_VTABLE_HIDDEN), -EDOM); + + if (v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) { + has_invalidating = true; + continue; + } + + has_changing = true; + + r = vtable_append_one_property(bus, m, m->path, c, v->vtable, u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + } else { + const sd_bus_vtable *v; + + /* If the caller specified no properties list + * we include all properties that are marked + * as changing in the message. */ + + for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { + if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) + continue; + + if (v->flags & SD_BUS_VTABLE_HIDDEN) + continue; + + if (v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) { + has_invalidating = true; + continue; + } + + if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) + continue; + + has_changing = true; + + r = vtable_append_one_property(bus, m, m->path, c, v, u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + } + } + + if (!has_invalidating && !has_changing) + return 0; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return r; + + if (has_invalidating) { + LIST_FOREACH(vtables, c, n->vtables) { + if (require_fallback && !c->is_fallback) + continue; + + if (!streq(c->interface, interface)) + continue; + + r = node_vtable_get_userdata(bus, path, c, &u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + if (names) { + STRV_FOREACH(property, names) { + struct vtable_member *v; + + key.member = *property; + assert_se(v = hashmap_get(bus->vtable_properties, &key)); + assert(c == v->parent); + + if (!(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) + continue; + + r = sd_bus_message_append(m, "s", *property); + if (r < 0) + return r; + } + } else { + const sd_bus_vtable *v; + + for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { + if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) + continue; + + if (v->flags & SD_BUS_VTABLE_HIDDEN) + continue; + + if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) + continue; + + r = sd_bus_message_append(m, "s", v->x.property.member); + if (r < 0) + return r; + } + } + } + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_send(bus, m, NULL); + if (r < 0) + return r; + + return 1; +} + +_public_ int sd_bus_emit_properties_changed_strv( + sd_bus *bus, + const char *path, + const char *interface, + char **names) { + + BUS_DONT_DESTROY(bus); + bool found_interface = false; + char *prefix; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(interface_name_is_valid(interface), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + /* A non-NULL but empty names list means nothing needs to be + generated. A NULL list OTOH indicates that all properties + that are set to EMITS_CHANGE or EMITS_INVALIDATION shall be + included in the PropertiesChanged message. */ + if (names && names[0] == NULL) + return 0; + + do { + bus->nodes_modified = false; + + r = emit_properties_changed_on_interface(bus, path, path, interface, false, &found_interface, names); + if (r != 0) + return r; + if (bus->nodes_modified) + continue; + + prefix = alloca(strlen(path) + 1); + OBJECT_PATH_FOREACH_PREFIX(prefix, path) { + r = emit_properties_changed_on_interface(bus, prefix, path, interface, true, &found_interface, names); + if (r != 0) + return r; + if (bus->nodes_modified) + break; + } + + } while (bus->nodes_modified); + + return found_interface ? 0 : -ENOENT; +} + +_public_ int sd_bus_emit_properties_changed( + sd_bus *bus, + const char *path, + const char *interface, + const char *name, ...) { + + char **names; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(interface_name_is_valid(interface), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (!name) + return 0; + + names = strv_from_stdarg_alloca(name); + + return sd_bus_emit_properties_changed_strv(bus, path, interface, names); +} + +static int object_added_append_all_prefix( + sd_bus *bus, + sd_bus_message *m, + Set *s, + const char *prefix, + const char *path, + bool require_fallback) { + + const char *previous_interface = NULL; + struct node_vtable *c; + struct node *n; + int r; + + assert(bus); + assert(m); + assert(s); + assert(prefix); + assert(path); + + n = hashmap_get(bus->nodes, prefix); + if (!n) + return 0; + + LIST_FOREACH(vtables, c, n->vtables) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + void *u = NULL; + + if (require_fallback && !c->is_fallback) + continue; + + r = node_vtable_get_userdata(bus, path, c, &u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + if (!streq_ptr(c->interface, previous_interface)) { + /* If a child-node already handled this interface, we + * skip it on any of its parents. The child vtables + * always fully override any conflicting vtables of + * any parent node. */ + if (set_get(s, c->interface)) + continue; + + r = set_put(s, c->interface); + if (r < 0) + return r; + + if (previous_interface) { + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + r = sd_bus_message_open_container(m, 'e', "sa{sv}"); + if (r < 0) + return r; + r = sd_bus_message_append(m, "s", c->interface); + if (r < 0) + return r; + r = sd_bus_message_open_container(m, 'a', "{sv}"); + if (r < 0) + return r; + + previous_interface = c->interface; + } + + r = vtable_append_all_properties(bus, m, path, c, u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + + if (previous_interface) { + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + return 0; +} + +static int object_added_append_all(sd_bus *bus, sd_bus_message *m, const char *path) { + _cleanup_set_free_ Set *s = NULL; + char *prefix; + int r; + + assert(bus); + assert(m); + assert(path); + + /* + * This appends all interfaces registered on path @path. We first add + * the builtin interfaces, which are always available and handled by + * sd-bus. Then, we add all interfaces registered on the exact node, + * followed by all fallback interfaces registered on any parent prefix. + * + * If an interface is registered multiple times on the same node with + * different vtables, we merge all the properties across all vtables. + * However, if a child node has the same interface registered as one of + * its parent nodes has as fallback, we make the child overwrite the + * parent instead of extending it. Therefore, we keep a "Set" of all + * handled interfaces during parent traversal, so we skip interfaces on + * a parent that were overwritten by a child. + */ + + s = set_new(&string_hash_ops); + if (!s) + return -ENOMEM; + + r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Peer", 0); + if (r < 0) + return r; + r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Introspectable", 0); + if (r < 0) + return r; + r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Properties", 0); + if (r < 0) + return r; + r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.ObjectManager", 0); + if (r < 0) + return r; + + r = object_added_append_all_prefix(bus, m, s, path, path, false); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + prefix = alloca(strlen(path) + 1); + OBJECT_PATH_FOREACH_PREFIX(prefix, path) { + r = object_added_append_all_prefix(bus, m, s, prefix, path, true); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + + return 0; +} + +_public_ int sd_bus_emit_object_added(sd_bus *bus, const char *path) { + BUS_DONT_DESTROY(bus); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + struct node *object_manager; + int r; + + /* + * This emits an InterfacesAdded signal on the given path, by iterating + * all registered vtables and fallback vtables on the path. All + * properties are queried and included in the signal. + * This call is equivalent to sd_bus_emit_interfaces_added() with an + * explicit list of registered interfaces. However, unlike + * interfaces_added(), this call can figure out the list of supported + * interfaces itself. Furthermore, it properly adds the builtin + * org.freedesktop.DBus.* interfaces. + */ + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + r = bus_find_parent_object_manager(bus, &object_manager, path); + if (r < 0) + return r; + if (r == 0) + return -ESRCH; + + do { + bus->nodes_modified = false; + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"); + if (r < 0) + return r; + + r = sd_bus_message_append_basic(m, 'o', path); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "{sa{sv}}"); + if (r < 0) + return r; + + r = object_added_append_all(bus, m, path); + if (r < 0) + return r; + + if (bus->nodes_modified) + continue; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + } while (bus->nodes_modified); + + return sd_bus_send(bus, m, NULL); +} + +static int object_removed_append_all_prefix( + sd_bus *bus, + sd_bus_message *m, + Set *s, + const char *prefix, + const char *path, + bool require_fallback) { + + const char *previous_interface = NULL; + struct node_vtable *c; + struct node *n; + int r; + + assert(bus); + assert(m); + assert(s); + assert(prefix); + assert(path); + + n = hashmap_get(bus->nodes, prefix); + if (!n) + return 0; + + LIST_FOREACH(vtables, c, n->vtables) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + void *u = NULL; + + if (require_fallback && !c->is_fallback) + continue; + if (streq_ptr(c->interface, previous_interface)) + continue; + + /* If a child-node already handled this interface, we + * skip it on any of its parents. The child vtables + * always fully override any conflicting vtables of + * any parent node. */ + if (set_get(s, c->interface)) + continue; + + r = node_vtable_get_userdata(bus, path, c, &u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + r = set_put(s, c->interface); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", c->interface); + if (r < 0) + return r; + + previous_interface = c->interface; + } + + return 0; +} + +static int object_removed_append_all(sd_bus *bus, sd_bus_message *m, const char *path) { + _cleanup_set_free_ Set *s = NULL; + char *prefix; + int r; + + assert(bus); + assert(m); + assert(path); + + /* see sd_bus_emit_object_added() for details */ + + s = set_new(&string_hash_ops); + if (!s) + return -ENOMEM; + + r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Peer"); + if (r < 0) + return r; + r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Introspectable"); + if (r < 0) + return r; + r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Properties"); + if (r < 0) + return r; + r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.ObjectManager"); + if (r < 0) + return r; + + r = object_removed_append_all_prefix(bus, m, s, path, path, false); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + + prefix = alloca(strlen(path) + 1); + OBJECT_PATH_FOREACH_PREFIX(prefix, path) { + r = object_removed_append_all_prefix(bus, m, s, prefix, path, true); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + + return 0; +} + +_public_ int sd_bus_emit_object_removed(sd_bus *bus, const char *path) { + BUS_DONT_DESTROY(bus); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + struct node *object_manager; + int r; + + /* + * This is like sd_bus_emit_object_added(), but emits an + * InterfacesRemoved signal on the given path. This only includes any + * registered interfaces but skips the properties. Note that this will + * call into the find() callbacks of any registered vtable. Therefore, + * you must call this function before destroying/unlinking your object. + * Otherwise, the list of interfaces will be incomplete. However, note + * that this will *NOT* call into any property callback. Therefore, the + * object might be in an "destructed" state, as long as we can find it. + */ + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + r = bus_find_parent_object_manager(bus, &object_manager, path); + if (r < 0) + return r; + if (r == 0) + return -ESRCH; + + do { + bus->nodes_modified = false; + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"); + if (r < 0) + return r; + + r = sd_bus_message_append_basic(m, 'o', path); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return r; + + r = object_removed_append_all(bus, m, path); + if (r < 0) + return r; + + if (bus->nodes_modified) + continue; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + } while (bus->nodes_modified); + + return sd_bus_send(bus, m, NULL); +} + +static int interfaces_added_append_one_prefix( + sd_bus *bus, + sd_bus_message *m, + const char *prefix, + const char *path, + const char *interface, + bool require_fallback) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + bool found_interface = false; + struct node_vtable *c; + struct node *n; + void *u = NULL; + int r; + + assert(bus); + assert(m); + assert(prefix); + assert(path); + assert(interface); + + n = hashmap_get(bus->nodes, prefix); + if (!n) + return 0; + + LIST_FOREACH(vtables, c, n->vtables) { + if (require_fallback && !c->is_fallback) + continue; + + if (!streq(c->interface, interface)) + continue; + + r = node_vtable_get_userdata(bus, path, c, &u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + if (r == 0) + continue; + + if (!found_interface) { + r = sd_bus_message_append_basic(m, 's', interface); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "{sv}"); + if (r < 0) + return r; + + found_interface = true; + } + + r = vtable_append_all_properties(bus, m, path, c, u, &error); + if (r < 0) + return r; + if (bus->nodes_modified) + return 0; + } + + if (found_interface) { + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + return found_interface; +} + +static int interfaces_added_append_one( + sd_bus *bus, + sd_bus_message *m, + const char *path, + const char *interface) { + + char *prefix; + int r; + + assert(bus); + assert(m); + assert(path); + assert(interface); + + r = interfaces_added_append_one_prefix(bus, m, path, path, interface, false); + if (r != 0) + return r; + if (bus->nodes_modified) + return 0; + + prefix = alloca(strlen(path) + 1); + OBJECT_PATH_FOREACH_PREFIX(prefix, path) { + r = interfaces_added_append_one_prefix(bus, m, prefix, path, interface, true); + if (r != 0) + return r; + if (bus->nodes_modified) + return 0; + } + + return -ENOENT; +} + +_public_ int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces) { + BUS_DONT_DESTROY(bus); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + struct node *object_manager; + char **i; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (strv_isempty(interfaces)) + return 0; + + r = bus_find_parent_object_manager(bus, &object_manager, path); + if (r < 0) + return r; + if (r == 0) + return -ESRCH; + + do { + bus->nodes_modified = false; + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"); + if (r < 0) + return r; + + r = sd_bus_message_append_basic(m, 'o', path); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "{sa{sv}}"); + if (r < 0) + return r; + + STRV_FOREACH(i, interfaces) { + assert_return(interface_name_is_valid(*i), -EINVAL); + + r = sd_bus_message_open_container(m, 'e', "sa{sv}"); + if (r < 0) + return r; + + r = interfaces_added_append_one(bus, m, path, *i); + if (r < 0) + return r; + + if (bus->nodes_modified) + break; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + if (bus->nodes_modified) + continue; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + } while (bus->nodes_modified); + + return sd_bus_send(bus, m, NULL); +} + +_public_ int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interface, ...) { + char **interfaces; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + interfaces = strv_from_stdarg_alloca(interface); + + return sd_bus_emit_interfaces_added_strv(bus, path, interfaces); +} + +_public_ int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + struct node *object_manager; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (strv_isempty(interfaces)) + return 0; + + r = bus_find_parent_object_manager(bus, &object_manager, path); + if (r < 0) + return r; + if (r == 0) + return -ESRCH; + + r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"); + if (r < 0) + return r; + + r = sd_bus_message_append_basic(m, 'o', path); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(m, interfaces); + if (r < 0) + return r; + + return sd_bus_send(bus, m, NULL); +} + +_public_ int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interface, ...) { + char **interfaces; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + interfaces = strv_from_stdarg_alloca(interface); + + return sd_bus_emit_interfaces_removed_strv(bus, path, interfaces); +} + +_public_ int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path) { + sd_bus_slot *s; + struct node *n; + int r; + + assert_return(bus, -EINVAL); + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + n = bus_node_allocate(bus, path); + if (!n) + return -ENOMEM; + + s = bus_slot_allocate(bus, !slot, BUS_NODE_OBJECT_MANAGER, sizeof(struct node_object_manager), NULL); + if (!s) { + r = -ENOMEM; + goto fail; + } + + s->node_object_manager.node = n; + LIST_PREPEND(object_managers, n->object_managers, &s->node_object_manager); + bus->nodes_modified = true; + + if (slot) + *slot = s; + + return 0; + +fail: + sd_bus_slot_unref(s); + bus_node_gc(bus, n); + + return r; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-objects.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-objects.h new file mode 100644 index 0000000000..e0b8c534ed --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-objects.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + 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 . +***/ + +#include "bus-internal.h" + +int bus_process_object(sd_bus *bus, sd_bus_message *m); +void bus_node_gc(sd_bus *b, struct node *n); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-protocol.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-protocol.h new file mode 100644 index 0000000000..9d180cb284 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-protocol.h @@ -0,0 +1,180 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include "macro.h" + +/* Packet header */ + +struct _packed_ bus_header { + /* The first four fields are identical for dbus1, and dbus2 */ + uint8_t endian; + uint8_t type; + uint8_t flags; + uint8_t version; + + union _packed_ { + /* dbus1: Used for SOCK_STREAM connections */ + struct _packed_ { + uint32_t body_size; + + /* Note that what the bus spec calls "serial" we'll call + "cookie" instead, because we don't want to imply that the + cookie was in any way monotonically increasing. */ + uint32_t serial; + uint32_t fields_size; + } dbus1; + + /* dbus2: Used for kdbus connections */ + struct _packed_ { + uint32_t _reserved; + uint64_t cookie; + } dbus2; + + /* Note that both header versions have the same size! */ + }; +}; + +/* Endianness */ + +enum { + _BUS_INVALID_ENDIAN = 0, + BUS_LITTLE_ENDIAN = 'l', + BUS_BIG_ENDIAN = 'B', +#if __BYTE_ORDER == __BIG_ENDIAN + BUS_NATIVE_ENDIAN = BUS_BIG_ENDIAN, + BUS_REVERSE_ENDIAN = BUS_LITTLE_ENDIAN +#else + BUS_NATIVE_ENDIAN = BUS_LITTLE_ENDIAN, + BUS_REVERSE_ENDIAN = BUS_BIG_ENDIAN +#endif +}; + +/* Flags */ + +enum { + BUS_MESSAGE_NO_REPLY_EXPECTED = 1, + BUS_MESSAGE_NO_AUTO_START = 2, + BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION = 4, +}; + +/* Header fields */ + +enum { + _BUS_MESSAGE_HEADER_INVALID = 0, + BUS_MESSAGE_HEADER_PATH, + BUS_MESSAGE_HEADER_INTERFACE, + BUS_MESSAGE_HEADER_MEMBER, + BUS_MESSAGE_HEADER_ERROR_NAME, + BUS_MESSAGE_HEADER_REPLY_SERIAL, + BUS_MESSAGE_HEADER_DESTINATION, + BUS_MESSAGE_HEADER_SENDER, + BUS_MESSAGE_HEADER_SIGNATURE, + BUS_MESSAGE_HEADER_UNIX_FDS, + _BUS_MESSAGE_HEADER_MAX +}; + +/* RequestName parameters */ + +enum { + BUS_NAME_ALLOW_REPLACEMENT = 1, + BUS_NAME_REPLACE_EXISTING = 2, + BUS_NAME_DO_NOT_QUEUE = 4 +}; + +/* RequestName returns */ +enum { + BUS_NAME_PRIMARY_OWNER = 1, + BUS_NAME_IN_QUEUE = 2, + BUS_NAME_EXISTS = 3, + BUS_NAME_ALREADY_OWNER = 4 +}; + +/* ReleaseName returns */ +enum { + BUS_NAME_RELEASED = 1, + BUS_NAME_NON_EXISTENT = 2, + BUS_NAME_NOT_OWNER = 3, +}; + +/* StartServiceByName returns */ +enum { + BUS_START_REPLY_SUCCESS = 1, + BUS_START_REPLY_ALREADY_RUNNING = 2, +}; + +#define BUS_INTROSPECT_DOCTYPE \ + "\n" + +#define BUS_INTROSPECT_INTERFACE_PEER \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_INTROSPECT_INTERFACE_INTROSPECTABLE \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_INTROSPECT_INTERFACE_PROPERTIES \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" + +#define BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" \ + " \n" diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-signature.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-signature.c new file mode 100644 index 0000000000..7bc243494a --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-signature.c @@ -0,0 +1,158 @@ +/*** + 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 . +***/ + +#include + +#include "bus-signature.h" +#include "bus-type.h" + +static int signature_element_length_internal( + const char *s, + bool allow_dict_entry, + unsigned array_depth, + unsigned struct_depth, + size_t *l) { + + int r; + + if (!s) + return -EINVAL; + + assert(l); + + if (bus_type_is_basic(*s) || *s == SD_BUS_TYPE_VARIANT) { + *l = 1; + return 0; + } + + if (*s == SD_BUS_TYPE_ARRAY) { + size_t t; + + if (array_depth >= 32) + return -EINVAL; + + r = signature_element_length_internal(s + 1, true, array_depth+1, struct_depth, &t); + if (r < 0) + return r; + + *l = t + 1; + return 0; + } + + if (*s == SD_BUS_TYPE_STRUCT_BEGIN) { + const char *p = s + 1; + + if (struct_depth >= 32) + return -EINVAL; + + while (*p != SD_BUS_TYPE_STRUCT_END) { + size_t t; + + r = signature_element_length_internal(p, false, array_depth, struct_depth+1, &t); + if (r < 0) + return r; + + p += t; + } + + *l = p - s + 1; + return 0; + } + + if (*s == SD_BUS_TYPE_DICT_ENTRY_BEGIN && allow_dict_entry) { + const char *p = s + 1; + unsigned n = 0; + + if (struct_depth >= 32) + return -EINVAL; + + while (*p != SD_BUS_TYPE_DICT_ENTRY_END) { + size_t t; + + if (n == 0 && !bus_type_is_basic(*p)) + return -EINVAL; + + r = signature_element_length_internal(p, false, array_depth, struct_depth+1, &t); + if (r < 0) + return r; + + p += t; + n++; + } + + if (n != 2) + return -EINVAL; + + *l = p - s + 1; + return 0; + } + + return -EINVAL; +} + + +int signature_element_length(const char *s, size_t *l) { + return signature_element_length_internal(s, true, 0, 0, l); +} + +bool signature_is_single(const char *s, bool allow_dict_entry) { + int r; + size_t t; + + if (!s) + return false; + + r = signature_element_length_internal(s, allow_dict_entry, 0, 0, &t); + if (r < 0) + return false; + + return s[t] == 0; +} + +bool signature_is_pair(const char *s) { + + if (!s) + return false; + + if (!bus_type_is_basic(*s)) + return false; + + return signature_is_single(s + 1, false); +} + +bool signature_is_valid(const char *s, bool allow_dict_entry) { + const char *p; + int r; + + if (!s) + return false; + + p = s; + while (*p) { + size_t t; + + r = signature_element_length_internal(p, allow_dict_entry, 0, 0, &t); + if (r < 0) + return false; + + p += t; + } + + return p - s <= 255; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-signature.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-signature.h new file mode 100644 index 0000000000..1e0cd7f587 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-signature.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +bool signature_is_single(const char *s, bool allow_dict_entry); +bool signature_is_pair(const char *s); +bool signature_is_valid(const char *s, bool allow_dict_entry); + +int signature_element_length(const char *s, size_t *l); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-slot.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-slot.c new file mode 100644 index 0000000000..b1783cd4a9 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-slot.c @@ -0,0 +1,286 @@ +/*** + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "bus-control.h" +#include "bus-objects.h" +#include "bus-slot.h" +#include "string-util.h" + +sd_bus_slot *bus_slot_allocate( + sd_bus *bus, + bool floating, + BusSlotType type, + size_t extra, + void *userdata) { + + sd_bus_slot *slot; + + assert(bus); + + slot = malloc0(offsetof(sd_bus_slot, reply_callback) + extra); + if (!slot) + return NULL; + + slot->n_ref = 1; + slot->type = type; + slot->bus = bus; + slot->floating = floating; + slot->userdata = userdata; + + if (!floating) + sd_bus_ref(bus); + + LIST_PREPEND(slots, bus->slots, slot); + + return slot; +} + +_public_ sd_bus_slot* sd_bus_slot_ref(sd_bus_slot *slot) { + + if (!slot) + return NULL; + + assert(slot->n_ref > 0); + + slot->n_ref++; + return slot; +} + +void bus_slot_disconnect(sd_bus_slot *slot) { + sd_bus *bus; + + assert(slot); + + if (!slot->bus) + return; + + switch (slot->type) { + + case BUS_REPLY_CALLBACK: + + if (slot->reply_callback.cookie != 0) + ordered_hashmap_remove(slot->bus->reply_callbacks, &slot->reply_callback.cookie); + + if (slot->reply_callback.timeout != 0) + prioq_remove(slot->bus->reply_callbacks_prioq, &slot->reply_callback, &slot->reply_callback.prioq_idx); + + break; + + case BUS_FILTER_CALLBACK: + slot->bus->filter_callbacks_modified = true; + LIST_REMOVE(callbacks, slot->bus->filter_callbacks, &slot->filter_callback); + break; + + case BUS_MATCH_CALLBACK: + + if (slot->match_added) + bus_remove_match_internal(slot->bus, slot->match_callback.match_string, slot->match_callback.cookie); + + slot->bus->match_callbacks_modified = true; + bus_match_remove(&slot->bus->match_callbacks, &slot->match_callback); + + free(slot->match_callback.match_string); + + break; + + case BUS_NODE_CALLBACK: + + if (slot->node_callback.node) { + LIST_REMOVE(callbacks, slot->node_callback.node->callbacks, &slot->node_callback); + slot->bus->nodes_modified = true; + + bus_node_gc(slot->bus, slot->node_callback.node); + } + + break; + + case BUS_NODE_ENUMERATOR: + + if (slot->node_enumerator.node) { + LIST_REMOVE(enumerators, slot->node_enumerator.node->enumerators, &slot->node_enumerator); + slot->bus->nodes_modified = true; + + bus_node_gc(slot->bus, slot->node_enumerator.node); + } + + break; + + case BUS_NODE_OBJECT_MANAGER: + + if (slot->node_object_manager.node) { + LIST_REMOVE(object_managers, slot->node_object_manager.node->object_managers, &slot->node_object_manager); + slot->bus->nodes_modified = true; + + bus_node_gc(slot->bus, slot->node_object_manager.node); + } + + break; + + case BUS_NODE_VTABLE: + + if (slot->node_vtable.node && slot->node_vtable.interface && slot->node_vtable.vtable) { + const sd_bus_vtable *v; + + for (v = slot->node_vtable.vtable; v->type != _SD_BUS_VTABLE_END; v++) { + struct vtable_member *x = NULL; + + switch (v->type) { + + case _SD_BUS_VTABLE_METHOD: { + struct vtable_member key; + + key.path = slot->node_vtable.node->path; + key.interface = slot->node_vtable.interface; + key.member = v->x.method.member; + + x = hashmap_remove(slot->bus->vtable_methods, &key); + break; + } + + case _SD_BUS_VTABLE_PROPERTY: + case _SD_BUS_VTABLE_WRITABLE_PROPERTY: { + struct vtable_member key; + + key.path = slot->node_vtable.node->path; + key.interface = slot->node_vtable.interface; + key.member = v->x.method.member; + + + x = hashmap_remove(slot->bus->vtable_properties, &key); + break; + }} + + free(x); + } + } + + free(slot->node_vtable.interface); + + if (slot->node_vtable.node) { + LIST_REMOVE(vtables, slot->node_vtable.node->vtables, &slot->node_vtable); + slot->bus->nodes_modified = true; + + bus_node_gc(slot->bus, slot->node_vtable.node); + } + + break; + + default: + assert_not_reached("Wut? Unknown slot type?"); + } + + bus = slot->bus; + + slot->type = _BUS_SLOT_INVALID; + slot->bus = NULL; + LIST_REMOVE(slots, bus->slots, slot); + + if (!slot->floating) + sd_bus_unref(bus); +} + +_public_ sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) { + + if (!slot) + return NULL; + + assert(slot->n_ref > 0); + + if (slot->n_ref > 1) { + slot->n_ref --; + return NULL; + } + + bus_slot_disconnect(slot); + free(slot->description); + free(slot); + + return NULL; +} + +_public_ sd_bus* sd_bus_slot_get_bus(sd_bus_slot *slot) { + assert_return(slot, NULL); + + return slot->bus; +} + +_public_ void *sd_bus_slot_get_userdata(sd_bus_slot *slot) { + assert_return(slot, NULL); + + return slot->userdata; +} + +_public_ void *sd_bus_slot_set_userdata(sd_bus_slot *slot, void *userdata) { + void *ret; + + assert_return(slot, NULL); + + ret = slot->userdata; + slot->userdata = userdata; + + return ret; +} + +_public_ sd_bus_message *sd_bus_slot_get_current_message(sd_bus_slot *slot) { + assert_return(slot, NULL); + assert_return(slot->type >= 0, NULL); + + if (slot->bus->current_slot != slot) + return NULL; + + return slot->bus->current_message; +} + +_public_ sd_bus_message_handler_t sd_bus_slot_get_current_handler(sd_bus_slot *slot) { + assert_return(slot, NULL); + assert_return(slot->type >= 0, NULL); + + if (slot->bus->current_slot != slot) + return NULL; + + return slot->bus->current_handler; +} + +_public_ void* sd_bus_slot_get_current_userdata(sd_bus_slot *slot) { + assert_return(slot, NULL); + assert_return(slot->type >= 0, NULL); + + if (slot->bus->current_slot != slot) + return NULL; + + return slot->bus->current_userdata; +} + +_public_ int sd_bus_slot_set_description(sd_bus_slot *slot, const char *description) { + assert_return(slot, -EINVAL); + + return free_and_strdup(&slot->description, description); +} + +_public_ int sd_bus_slot_get_description(sd_bus_slot *slot, const char **description) { + assert_return(slot, -EINVAL); + assert_return(description, -EINVAL); + assert_return(slot->description, -ENXIO); + + *description = slot->description; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-slot.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-slot.h new file mode 100644 index 0000000000..b862799d1c --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-slot.h @@ -0,0 +1,28 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "bus-internal.h" + +sd_bus_slot *bus_slot_allocate(sd_bus *bus, bool floating, BusSlotType type, size_t extra, void *userdata); + +void bus_slot_disconnect(sd_bus_slot *slot); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-socket.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-socket.c new file mode 100644 index 0000000000..a0008190c5 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-socket.c @@ -0,0 +1,1064 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include + +#include +#include + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-socket.h" +#include "fd-util.h" +#include "formats-util.h" +#include "hexdecoct.h" +#include "macro.h" +#include "missing.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "user-util.h" +#include "utf8.h" +#include "util.h" + +#define SNDBUF_SIZE (8*1024*1024) + +static void iovec_advance(struct iovec iov[], unsigned *idx, size_t size) { + + while (size > 0) { + struct iovec *i = iov + *idx; + + if (i->iov_len > size) { + i->iov_base = (uint8_t*) i->iov_base + size; + i->iov_len -= size; + return; + } + + size -= i->iov_len; + + i->iov_base = NULL; + i->iov_len = 0; + + (*idx) ++; + } +} + +static int append_iovec(sd_bus_message *m, const void *p, size_t sz) { + assert(m); + assert(p); + assert(sz > 0); + + m->iovec[m->n_iovec].iov_base = (void*) p; + m->iovec[m->n_iovec].iov_len = sz; + m->n_iovec++; + + return 0; +} + +static int bus_message_setup_iovec(sd_bus_message *m) { + struct bus_body_part *part; + unsigned n, i; + int r; + + assert(m); + assert(m->sealed); + + if (m->n_iovec > 0) + return 0; + + assert(!m->iovec); + + n = 1 + m->n_body_parts; + if (n < ELEMENTSOF(m->iovec_fixed)) + m->iovec = m->iovec_fixed; + else { + m->iovec = new(struct iovec, n); + if (!m->iovec) { + r = -ENOMEM; + goto fail; + } + } + + r = append_iovec(m, m->header, BUS_MESSAGE_BODY_BEGIN(m)); + if (r < 0) + goto fail; + + MESSAGE_FOREACH_PART(part, i, m) { + r = bus_body_part_map(part); + if (r < 0) + goto fail; + + r = append_iovec(m, part->data, part->size); + if (r < 0) + goto fail; + } + + assert(n == m->n_iovec); + + return 0; + +fail: + m->poisoned = true; + return r; +} + +bool bus_socket_auth_needs_write(sd_bus *b) { + + unsigned i; + + if (b->auth_index >= ELEMENTSOF(b->auth_iovec)) + return false; + + for (i = b->auth_index; i < ELEMENTSOF(b->auth_iovec); i++) { + struct iovec *j = b->auth_iovec + i; + + if (j->iov_len > 0) + return true; + } + + return false; +} + +static int bus_socket_write_auth(sd_bus *b) { + ssize_t k; + + assert(b); + assert(b->state == BUS_AUTHENTICATING); + + if (!bus_socket_auth_needs_write(b)) + return 0; + + if (b->prefer_writev) + k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index); + else { + struct msghdr mh; + zero(mh); + + mh.msg_iov = b->auth_iovec + b->auth_index; + mh.msg_iovlen = ELEMENTSOF(b->auth_iovec) - b->auth_index; + + k = sendmsg(b->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + if (k < 0 && errno == ENOTSOCK) { + b->prefer_writev = true; + k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index); + } + } + + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + + iovec_advance(b->auth_iovec, &b->auth_index, (size_t) k); + return 1; +} + +static int bus_socket_auth_verify_client(sd_bus *b) { + char *e, *f, *start; + sd_id128_t peer; + unsigned i; + int r; + + assert(b); + + /* We expect two response lines: "OK" and possibly + * "AGREE_UNIX_FD" */ + + e = memmem_safe(b->rbuffer, b->rbuffer_size, "\r\n", 2); + if (!e) + return 0; + + if (b->hello_flags & KDBUS_HELLO_ACCEPT_FD) { + f = memmem(e + 2, b->rbuffer_size - (e - (char*) b->rbuffer) - 2, "\r\n", 2); + if (!f) + return 0; + + start = f + 2; + } else { + f = NULL; + start = e + 2; + } + + /* Nice! We got all the lines we need. First check the OK + * line */ + + if (e - (char*) b->rbuffer != 3 + 32) + return -EPERM; + + if (memcmp(b->rbuffer, "OK ", 3)) + return -EPERM; + + b->auth = b->anonymous_auth ? BUS_AUTH_ANONYMOUS : BUS_AUTH_EXTERNAL; + + for (i = 0; i < 32; i += 2) { + int x, y; + + x = unhexchar(((char*) b->rbuffer)[3 + i]); + y = unhexchar(((char*) b->rbuffer)[3 + i + 1]); + + if (x < 0 || y < 0) + return -EINVAL; + + peer.bytes[i/2] = ((uint8_t) x << 4 | (uint8_t) y); + } + + if (!sd_id128_equal(b->server_id, SD_ID128_NULL) && + !sd_id128_equal(b->server_id, peer)) + return -EPERM; + + b->server_id = peer; + + /* And possibly check the second line, too */ + + if (f) + b->can_fds = + (f - e == strlen("\r\nAGREE_UNIX_FD")) && + memcmp(e + 2, "AGREE_UNIX_FD", strlen("AGREE_UNIX_FD")) == 0; + + b->rbuffer_size -= (start - (char*) b->rbuffer); + memmove(b->rbuffer, start, b->rbuffer_size); + + r = bus_start_running(b); + if (r < 0) + return r; + + return 1; +} + +static bool line_equals(const char *s, size_t m, const char *line) { + size_t l; + + l = strlen(line); + if (l != m) + return false; + + return memcmp(s, line, l) == 0; +} + +static bool line_begins(const char *s, size_t m, const char *word) { + size_t l; + + l = strlen(word); + if (m < l) + return false; + + if (memcmp(s, word, l) != 0) + return false; + + return m == l || (m > l && s[l] == ' '); +} + +static int verify_anonymous_token(sd_bus *b, const char *p, size_t l) { + _cleanup_free_ char *token = NULL; + size_t len; + int r; + + if (!b->anonymous_auth) + return 0; + + if (l <= 0) + return 1; + + assert(p[0] == ' '); + p++; l--; + + if (l % 2 != 0) + return 0; + + r = unhexmem(p, l, (void **) &token, &len); + if (r < 0) + return 0; + + if (memchr(token, 0, len)) + return 0; + + return !!utf8_is_valid(token); +} + +static int verify_external_token(sd_bus *b, const char *p, size_t l) { + _cleanup_free_ char *token = NULL; + size_t len; + uid_t u; + int r; + + /* We don't do any real authentication here. Instead, we if + * the owner of this bus wanted authentication he should have + * checked SO_PEERCRED before even creating the bus object. */ + + if (!b->anonymous_auth && !b->ucred_valid) + return 0; + + if (l <= 0) + return 1; + + assert(p[0] == ' '); + p++; l--; + + if (l % 2 != 0) + return 0; + + r = unhexmem(p, l, (void**) &token, &len); + if (r < 0) + return 0; + + if (memchr(token, 0, len)) + return 0; + + r = parse_uid(token, &u); + if (r < 0) + return 0; + + /* We ignore the passed value if anonymous authentication is + * on anyway. */ + if (!b->anonymous_auth && u != b->ucred.uid) + return 0; + + return 1; +} + +static int bus_socket_auth_write(sd_bus *b, const char *t) { + char *p; + size_t l; + + assert(b); + assert(t); + + /* We only make use of the first iovec */ + assert(b->auth_index == 0 || b->auth_index == 1); + + l = strlen(t); + p = malloc(b->auth_iovec[0].iov_len + l); + if (!p) + return -ENOMEM; + + memcpy(p, b->auth_iovec[0].iov_base, b->auth_iovec[0].iov_len); + memcpy(p + b->auth_iovec[0].iov_len, t, l); + + b->auth_iovec[0].iov_base = p; + b->auth_iovec[0].iov_len += l; + + free(b->auth_buffer); + b->auth_buffer = p; + b->auth_index = 0; + return 0; +} + +static int bus_socket_auth_write_ok(sd_bus *b) { + char t[3 + 32 + 2 + 1]; + + assert(b); + + xsprintf(t, "OK " SD_ID128_FORMAT_STR "\r\n", SD_ID128_FORMAT_VAL(b->server_id)); + + return bus_socket_auth_write(b, t); +} + +static int bus_socket_auth_verify_server(sd_bus *b) { + char *e; + const char *line; + size_t l; + bool processed = false; + int r; + + assert(b); + + if (b->rbuffer_size < 1) + return 0; + + /* First char must be a NUL byte */ + if (*(char*) b->rbuffer != 0) + return -EIO; + + if (b->rbuffer_size < 3) + return 0; + + /* Begin with the first line */ + if (b->auth_rbegin <= 0) + b->auth_rbegin = 1; + + for (;;) { + /* Check if line is complete */ + line = (char*) b->rbuffer + b->auth_rbegin; + e = memmem(line, b->rbuffer_size - b->auth_rbegin, "\r\n", 2); + if (!e) + return processed; + + l = e - line; + + if (line_begins(line, l, "AUTH ANONYMOUS")) { + + r = verify_anonymous_token(b, line + 14, l - 14); + if (r < 0) + return r; + if (r == 0) + r = bus_socket_auth_write(b, "REJECTED\r\n"); + else { + b->auth = BUS_AUTH_ANONYMOUS; + r = bus_socket_auth_write_ok(b); + } + + } else if (line_begins(line, l, "AUTH EXTERNAL")) { + + r = verify_external_token(b, line + 13, l - 13); + if (r < 0) + return r; + if (r == 0) + r = bus_socket_auth_write(b, "REJECTED\r\n"); + else { + b->auth = BUS_AUTH_EXTERNAL; + r = bus_socket_auth_write_ok(b); + } + + } else if (line_begins(line, l, "AUTH")) + r = bus_socket_auth_write(b, "REJECTED EXTERNAL ANONYMOUS\r\n"); + else if (line_equals(line, l, "CANCEL") || + line_begins(line, l, "ERROR")) { + + b->auth = _BUS_AUTH_INVALID; + r = bus_socket_auth_write(b, "REJECTED\r\n"); + + } else if (line_equals(line, l, "BEGIN")) { + + if (b->auth == _BUS_AUTH_INVALID) + r = bus_socket_auth_write(b, "ERROR\r\n"); + else { + /* We can't leave from the auth phase + * before we haven't written + * everything queued, so let's check + * that */ + + if (bus_socket_auth_needs_write(b)) + return 1; + + b->rbuffer_size -= (e + 2 - (char*) b->rbuffer); + memmove(b->rbuffer, e + 2, b->rbuffer_size); + return bus_start_running(b); + } + + } else if (line_begins(line, l, "DATA")) { + + if (b->auth == _BUS_AUTH_INVALID) + r = bus_socket_auth_write(b, "ERROR\r\n"); + else { + if (b->auth == BUS_AUTH_ANONYMOUS) + r = verify_anonymous_token(b, line + 4, l - 4); + else + r = verify_external_token(b, line + 4, l - 4); + + if (r < 0) + return r; + if (r == 0) { + b->auth = _BUS_AUTH_INVALID; + r = bus_socket_auth_write(b, "REJECTED\r\n"); + } else + r = bus_socket_auth_write_ok(b); + } + } else if (line_equals(line, l, "NEGOTIATE_UNIX_FD")) { + if (b->auth == _BUS_AUTH_INVALID || !(b->hello_flags & KDBUS_HELLO_ACCEPT_FD)) + r = bus_socket_auth_write(b, "ERROR\r\n"); + else { + b->can_fds = true; + r = bus_socket_auth_write(b, "AGREE_UNIX_FD\r\n"); + } + } else + r = bus_socket_auth_write(b, "ERROR\r\n"); + + if (r < 0) + return r; + + b->auth_rbegin = e + 2 - (char*) b->rbuffer; + + processed = true; + } +} + +static int bus_socket_auth_verify(sd_bus *b) { + assert(b); + + if (b->is_server) + return bus_socket_auth_verify_server(b); + else + return bus_socket_auth_verify_client(b); +} + +static int bus_socket_read_auth(sd_bus *b) { + struct msghdr mh; + struct iovec iov = {}; + size_t n; + ssize_t k; + int r; + void *p; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int) * BUS_FDS_MAX)]; + } control; + bool handle_cmsg = false; + + assert(b); + assert(b->state == BUS_AUTHENTICATING); + + r = bus_socket_auth_verify(b); + if (r != 0) + return r; + + n = MAX(256u, b->rbuffer_size * 2); + + if (n > BUS_AUTH_SIZE_MAX) + n = BUS_AUTH_SIZE_MAX; + + if (b->rbuffer_size >= n) + return -ENOBUFS; + + p = realloc(b->rbuffer, n); + if (!p) + return -ENOMEM; + + b->rbuffer = p; + + iov.iov_base = (uint8_t*) b->rbuffer + b->rbuffer_size; + iov.iov_len = n - b->rbuffer_size; + + if (b->prefer_readv) + k = readv(b->input_fd, &iov, 1); + else { + zero(mh); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + + k = recvmsg(b->input_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); + if (k < 0 && errno == ENOTSOCK) { + b->prefer_readv = true; + k = readv(b->input_fd, &iov, 1); + } else + handle_cmsg = true; + } + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + if (k == 0) + return -ECONNRESET; + + b->rbuffer_size += k; + + if (handle_cmsg) { + struct cmsghdr *cmsg; + + CMSG_FOREACH(cmsg, &mh) + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + int j; + + /* Whut? We received fds during the auth + * protocol? Somebody is playing games with + * us. Close them all, and fail */ + j = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + close_many((int*) CMSG_DATA(cmsg), j); + return -EIO; + } else + log_debug("Got unexpected auxiliary data with level=%d and type=%d", + cmsg->cmsg_level, cmsg->cmsg_type); + } + + r = bus_socket_auth_verify(b); + if (r != 0) + return r; + + return 1; +} + +void bus_socket_setup(sd_bus *b) { + assert(b); + + /* Increase the buffers to 8 MB */ + fd_inc_rcvbuf(b->input_fd, SNDBUF_SIZE); + fd_inc_sndbuf(b->output_fd, SNDBUF_SIZE); + + b->is_kernel = false; + b->message_version = 1; + b->message_endian = 0; +} + +static void bus_get_peercred(sd_bus *b) { + int r; + + assert(b); + + /* Get the peer for socketpair() sockets */ + b->ucred_valid = getpeercred(b->input_fd, &b->ucred) >= 0; + + /* Get the SELinux context of the peer */ + if (mac_selinux_have()) { + r = getpeersec(b->input_fd, &b->label); + if (r < 0 && r != -EOPNOTSUPP) + log_debug_errno(r, "Failed to determine peer security context: %m"); + } +} + +static int bus_socket_start_auth_client(sd_bus *b) { + size_t l; + const char *auth_suffix, *auth_prefix; + + assert(b); + + if (b->anonymous_auth) { + auth_prefix = "\0AUTH ANONYMOUS "; + + /* For ANONYMOUS auth we send some arbitrary "trace" string */ + l = 9; + b->auth_buffer = hexmem("anonymous", l); + } else { + char text[DECIMAL_STR_MAX(uid_t) + 1]; + + auth_prefix = "\0AUTH EXTERNAL "; + + xsprintf(text, UID_FMT, geteuid()); + + l = strlen(text); + b->auth_buffer = hexmem(text, l); + } + + if (!b->auth_buffer) + return -ENOMEM; + + if (b->hello_flags & KDBUS_HELLO_ACCEPT_FD) + auth_suffix = "\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n"; + else + auth_suffix = "\r\nBEGIN\r\n"; + + b->auth_iovec[0].iov_base = (void*) auth_prefix; + b->auth_iovec[0].iov_len = 1 + strlen(auth_prefix + 1); + b->auth_iovec[1].iov_base = (void*) b->auth_buffer; + b->auth_iovec[1].iov_len = l * 2; + b->auth_iovec[2].iov_base = (void*) auth_suffix; + b->auth_iovec[2].iov_len = strlen(auth_suffix); + + return bus_socket_write_auth(b); +} + +int bus_socket_start_auth(sd_bus *b) { + assert(b); + + bus_get_peercred(b); + + b->state = BUS_AUTHENTICATING; + b->auth_timeout = now(CLOCK_MONOTONIC) + BUS_DEFAULT_TIMEOUT; + + if (sd_is_socket(b->input_fd, AF_UNIX, 0, 0) <= 0) + b->hello_flags &= ~KDBUS_HELLO_ACCEPT_FD; + + if (b->output_fd != b->input_fd) + if (sd_is_socket(b->output_fd, AF_UNIX, 0, 0) <= 0) + b->hello_flags &= ~KDBUS_HELLO_ACCEPT_FD; + + if (b->is_server) + return bus_socket_read_auth(b); + else + return bus_socket_start_auth_client(b); +} + +int bus_socket_connect(sd_bus *b) { + int r; + + assert(b); + assert(b->input_fd < 0); + assert(b->output_fd < 0); + assert(b->sockaddr.sa.sa_family != AF_UNSPEC); + + b->input_fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (b->input_fd < 0) + return -errno; + + b->output_fd = b->input_fd; + + bus_socket_setup(b); + + r = connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size); + if (r < 0) { + if (errno == EINPROGRESS) + return 1; + + return -errno; + } + + return bus_socket_start_auth(b); +} + +int bus_socket_exec(sd_bus *b) { + int s[2], r; + pid_t pid; + + assert(b); + assert(b->input_fd < 0); + assert(b->output_fd < 0); + assert(b->exec_path); + + r = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, s); + if (r < 0) + return -errno; + + pid = fork(); + if (pid < 0) { + safe_close_pair(s); + return -errno; + } + if (pid == 0) { + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + close_all_fds(s+1, 1); + + assert_se(dup3(s[1], STDIN_FILENO, 0) == STDIN_FILENO); + assert_se(dup3(s[1], STDOUT_FILENO, 0) == STDOUT_FILENO); + + if (s[1] != STDIN_FILENO && s[1] != STDOUT_FILENO) + safe_close(s[1]); + + fd_cloexec(STDIN_FILENO, false); + fd_cloexec(STDOUT_FILENO, false); + fd_nonblock(STDIN_FILENO, false); + fd_nonblock(STDOUT_FILENO, false); + + if (b->exec_argv) + execvp(b->exec_path, b->exec_argv); + else { + const char *argv[] = { b->exec_path, NULL }; + execvp(b->exec_path, (char**) argv); + } + + _exit(EXIT_FAILURE); + } + + safe_close(s[1]); + b->output_fd = b->input_fd = s[0]; + + bus_socket_setup(b); + + return bus_socket_start_auth(b); +} + +int bus_socket_take_fd(sd_bus *b) { + assert(b); + + bus_socket_setup(b); + + return bus_socket_start_auth(b); +} + +int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) { + struct iovec *iov; + ssize_t k; + size_t n; + unsigned j; + int r; + + assert(bus); + assert(m); + assert(idx); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + if (*idx >= BUS_MESSAGE_SIZE(m)) + return 0; + + r = bus_message_setup_iovec(m); + if (r < 0) + return r; + + n = m->n_iovec * sizeof(struct iovec); + iov = alloca(n); + memcpy(iov, m->iovec, n); + + j = 0; + iovec_advance(iov, &j, *idx); + + if (bus->prefer_writev) + k = writev(bus->output_fd, iov, m->n_iovec); + else { + struct msghdr mh = { + .msg_iov = iov, + .msg_iovlen = m->n_iovec, + }; + + if (m->n_fds > 0) { + struct cmsghdr *control; + + mh.msg_control = control = alloca(CMSG_SPACE(sizeof(int) * m->n_fds)); + mh.msg_controllen = control->cmsg_len = CMSG_LEN(sizeof(int) * m->n_fds); + control->cmsg_level = SOL_SOCKET; + control->cmsg_type = SCM_RIGHTS; + memcpy(CMSG_DATA(control), m->fds, sizeof(int) * m->n_fds); + } + + k = sendmsg(bus->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); + if (k < 0 && errno == ENOTSOCK) { + bus->prefer_writev = true; + k = writev(bus->output_fd, iov, m->n_iovec); + } + } + + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + + *idx += (size_t) k; + return 1; +} + +static int bus_socket_read_message_need(sd_bus *bus, size_t *need) { + uint32_t a, b; + uint8_t e; + uint64_t sum; + + assert(bus); + assert(need); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + if (bus->rbuffer_size < sizeof(struct bus_header)) { + *need = sizeof(struct bus_header) + 8; + + /* Minimum message size: + * + * Header + + * + * Method Call: +2 string headers + * Signal: +3 string headers + * Method Error: +1 string headers + * +1 uint32 headers + * Method Reply: +1 uint32 headers + * + * A string header is at least 9 bytes + * A uint32 header is at least 8 bytes + * + * Hence the minimum message size of a valid message + * is header + 8 bytes */ + + return 0; + } + + a = ((const uint32_t*) bus->rbuffer)[1]; + b = ((const uint32_t*) bus->rbuffer)[3]; + + e = ((const uint8_t*) bus->rbuffer)[0]; + if (e == BUS_LITTLE_ENDIAN) { + a = le32toh(a); + b = le32toh(b); + } else if (e == BUS_BIG_ENDIAN) { + a = be32toh(a); + b = be32toh(b); + } else + return -EBADMSG; + + sum = (uint64_t) sizeof(struct bus_header) + (uint64_t) ALIGN_TO(b, 8) + (uint64_t) a; + if (sum >= BUS_MESSAGE_SIZE_MAX) + return -ENOBUFS; + + *need = (size_t) sum; + return 0; +} + +static int bus_socket_make_message(sd_bus *bus, size_t size) { + sd_bus_message *t; + void *b; + int r; + + assert(bus); + assert(bus->rbuffer_size >= size); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + r = bus_rqueue_make_room(bus); + if (r < 0) + return r; + + if (bus->rbuffer_size > size) { + b = memdup((const uint8_t*) bus->rbuffer + size, + bus->rbuffer_size - size); + if (!b) + return -ENOMEM; + } else + b = NULL; + + r = bus_message_from_malloc(bus, + bus->rbuffer, size, + bus->fds, bus->n_fds, + NULL, + &t); + if (r < 0) { + free(b); + return r; + } + + bus->rbuffer = b; + bus->rbuffer_size -= size; + + bus->fds = NULL; + bus->n_fds = 0; + + bus->rqueue[bus->rqueue_size++] = t; + + return 1; +} + +int bus_socket_read_message(sd_bus *bus) { + struct msghdr mh; + struct iovec iov = {}; + ssize_t k; + size_t need; + int r; + void *b; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int) * BUS_FDS_MAX)]; + } control; + bool handle_cmsg = false; + + assert(bus); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + r = bus_socket_read_message_need(bus, &need); + if (r < 0) + return r; + + if (bus->rbuffer_size >= need) + return bus_socket_make_message(bus, need); + + b = realloc(bus->rbuffer, need); + if (!b) + return -ENOMEM; + + bus->rbuffer = b; + + iov.iov_base = (uint8_t*) bus->rbuffer + bus->rbuffer_size; + iov.iov_len = need - bus->rbuffer_size; + + if (bus->prefer_readv) + k = readv(bus->input_fd, &iov, 1); + else { + zero(mh); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + + k = recvmsg(bus->input_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); + if (k < 0 && errno == ENOTSOCK) { + bus->prefer_readv = true; + k = readv(bus->input_fd, &iov, 1); + } else + handle_cmsg = true; + } + if (k < 0) + return errno == EAGAIN ? 0 : -errno; + if (k == 0) + return -ECONNRESET; + + bus->rbuffer_size += k; + + if (handle_cmsg) { + struct cmsghdr *cmsg; + + CMSG_FOREACH(cmsg, &mh) + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) { + int n, *f; + + n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); + + if (!bus->can_fds) { + /* Whut? We received fds but this + * isn't actually enabled? Close them, + * and fail */ + + close_many((int*) CMSG_DATA(cmsg), n); + return -EIO; + } + + f = realloc(bus->fds, sizeof(int) * (bus->n_fds + n)); + if (!f) { + close_many((int*) CMSG_DATA(cmsg), n); + return -ENOMEM; + } + + memcpy(f + bus->n_fds, CMSG_DATA(cmsg), n * sizeof(int)); + bus->fds = f; + bus->n_fds += n; + } else + log_debug("Got unexpected auxiliary data with level=%d and type=%d", + cmsg->cmsg_level, cmsg->cmsg_type); + } + + r = bus_socket_read_message_need(bus, &need); + if (r < 0) + return r; + + if (bus->rbuffer_size >= need) + return bus_socket_make_message(bus, need); + + return 1; +} + +int bus_socket_process_opening(sd_bus *b) { + int error = 0; + socklen_t slen = sizeof(error); + struct pollfd p = { + .fd = b->output_fd, + .events = POLLOUT, + }; + int r; + + assert(b->state == BUS_OPENING); + + r = poll(&p, 1, 0); + if (r < 0) + return -errno; + + if (!(p.revents & (POLLOUT|POLLERR|POLLHUP))) + return 0; + + r = getsockopt(b->output_fd, SOL_SOCKET, SO_ERROR, &error, &slen); + if (r < 0) + b->last_connect_error = errno; + else if (error != 0) + b->last_connect_error = error; + else if (p.revents & (POLLERR|POLLHUP)) + b->last_connect_error = ECONNREFUSED; + else + return bus_socket_start_auth(b); + + return bus_next_address(b); +} + +int bus_socket_process_authenticating(sd_bus *b) { + int r; + + assert(b); + assert(b->state == BUS_AUTHENTICATING); + + if (now(CLOCK_MONOTONIC) >= b->auth_timeout) + return -ETIMEDOUT; + + r = bus_socket_write_auth(b); + if (r != 0) + return r; + + return bus_socket_read_auth(b); +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-socket.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-socket.h new file mode 100644 index 0000000000..6e1d32e6a7 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-socket.h @@ -0,0 +1,37 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +void bus_socket_setup(sd_bus *b); + +int bus_socket_connect(sd_bus *b); +int bus_socket_exec(sd_bus *b); +int bus_socket_take_fd(sd_bus *b); +int bus_socket_start_auth(sd_bus *b); + +int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx); +int bus_socket_read_message(sd_bus *bus); + +int bus_socket_process_opening(sd_bus *b); +int bus_socket_process_authenticating(sd_bus *b); + +bool bus_socket_auth_needs_write(sd_bus *b); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-track.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-track.c new file mode 100644 index 0000000000..5d08b0a37b --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-track.c @@ -0,0 +1,337 @@ +/*** + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-track.h" +#include "bus-util.h" + +struct sd_bus_track { + unsigned n_ref; + sd_bus *bus; + sd_bus_track_handler_t handler; + void *userdata; + Hashmap *names; + LIST_FIELDS(sd_bus_track, queue); + Iterator iterator; + bool in_queue; + bool modified; +}; + +#define MATCH_PREFIX \ + "type='signal'," \ + "sender='org.freedesktop.DBus'," \ + "path='/org/freedesktop/DBus'," \ + "interface='org.freedesktop.DBus'," \ + "member='NameOwnerChanged'," \ + "arg0='" + +#define MATCH_SUFFIX \ + "'" + +#define MATCH_FOR_NAME(name) \ + ({ \ + char *_x; \ + size_t _l = strlen(name); \ + _x = alloca(strlen(MATCH_PREFIX)+_l+strlen(MATCH_SUFFIX)+1); \ + strcpy(stpcpy(stpcpy(_x, MATCH_PREFIX), name), MATCH_SUFFIX); \ + _x; \ + }) + +static void bus_track_add_to_queue(sd_bus_track *track) { + assert(track); + + if (track->in_queue) + return; + + if (!track->handler) + return; + + LIST_PREPEND(queue, track->bus->track_queue, track); + track->in_queue = true; +} + +static void bus_track_remove_from_queue(sd_bus_track *track) { + assert(track); + + if (!track->in_queue) + return; + + LIST_REMOVE(queue, track->bus->track_queue, track); + track->in_queue = false; +} + +_public_ int sd_bus_track_new( + sd_bus *bus, + sd_bus_track **track, + sd_bus_track_handler_t handler, + void *userdata) { + + sd_bus_track *t; + + assert_return(bus, -EINVAL); + assert_return(track, -EINVAL); + + if (!bus->bus_client) + return -EINVAL; + + t = new0(sd_bus_track, 1); + if (!t) + return -ENOMEM; + + t->n_ref = 1; + t->handler = handler; + t->userdata = userdata; + t->bus = sd_bus_ref(bus); + + bus_track_add_to_queue(t); + + *track = t; + return 0; +} + +_public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) { + + if (!track) + return NULL; + + assert(track->n_ref > 0); + + track->n_ref++; + + return track; +} + +_public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) { + const char *n; + + if (!track) + return NULL; + + assert(track->n_ref > 0); + + if (track->n_ref > 1) { + track->n_ref --; + return NULL; + } + + while ((n = hashmap_first_key(track->names))) + sd_bus_track_remove_name(track, n); + + bus_track_remove_from_queue(track); + hashmap_free(track->names); + sd_bus_unref(track->bus); + free(track); + + return NULL; +} + +static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + sd_bus_track *track = userdata; + const char *name, *old, *new; + int r; + + assert(message); + assert(track); + + r = sd_bus_message_read(message, "sss", &name, &old, &new); + if (r < 0) + return 0; + + sd_bus_track_remove_name(track, name); + return 0; +} + +_public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) { + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_free_ char *n = NULL; + const char *match; + int r; + + assert_return(track, -EINVAL); + assert_return(service_name_is_valid(name), -EINVAL); + + r = hashmap_ensure_allocated(&track->names, &string_hash_ops); + if (r < 0) + return r; + + n = strdup(name); + if (!n) + return -ENOMEM; + + /* First, subscribe to this name */ + match = MATCH_FOR_NAME(n); + r = sd_bus_add_match(track->bus, &slot, match, on_name_owner_changed, track); + if (r < 0) + return r; + + r = hashmap_put(track->names, n, slot); + if (r == -EEXIST) + return 0; + if (r < 0) + return r; + + /* Second, check if it is currently existing, or maybe + * doesn't, or maybe disappeared already. */ + r = sd_bus_get_name_creds(track->bus, n, 0, NULL); + if (r < 0) { + hashmap_remove(track->names, n); + return r; + } + + n = NULL; + slot = NULL; + + bus_track_remove_from_queue(track); + track->modified = true; + + return 1; +} + +_public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) { + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_free_ char *n = NULL; + + assert_return(name, -EINVAL); + + if (!track) + return 0; + + slot = hashmap_remove2(track->names, (char*) name, (void**) &n); + if (!slot) + return 0; + + if (hashmap_isempty(track->names)) + bus_track_add_to_queue(track); + + track->modified = true; + + return 1; +} + +_public_ unsigned sd_bus_track_count(sd_bus_track *track) { + if (!track) + return 0; + + return hashmap_size(track->names); +} + +_public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) { + assert_return(track, NULL); + assert_return(name, NULL); + + return hashmap_get(track->names, (void*) name) ? name : NULL; +} + +_public_ const char* sd_bus_track_first(sd_bus_track *track) { + const char *n = NULL; + + if (!track) + return NULL; + + track->modified = false; + track->iterator = ITERATOR_FIRST; + + hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); + return n; +} + +_public_ const char* sd_bus_track_next(sd_bus_track *track) { + const char *n = NULL; + + if (!track) + return NULL; + + if (track->modified) + return NULL; + + hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); + return n; +} + +_public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) { + const char *sender; + + assert_return(track, -EINVAL); + assert_return(m, -EINVAL); + + sender = sd_bus_message_get_sender(m); + if (!sender) + return -EINVAL; + + return sd_bus_track_add_name(track, sender); +} + +_public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) { + const char *sender; + + assert_return(track, -EINVAL); + assert_return(m, -EINVAL); + + sender = sd_bus_message_get_sender(m); + if (!sender) + return -EINVAL; + + return sd_bus_track_remove_name(track, sender); +} + +_public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) { + assert_return(track, NULL); + + return track->bus; +} + +void bus_track_dispatch(sd_bus_track *track) { + int r; + + assert(track); + assert(track->in_queue); + assert(track->handler); + + bus_track_remove_from_queue(track); + + sd_bus_track_ref(track); + + r = track->handler(track, track->userdata); + if (r < 0) + log_debug_errno(r, "Failed to process track handler: %m"); + else if (r == 0) + bus_track_add_to_queue(track); + + sd_bus_track_unref(track); +} + +_public_ void *sd_bus_track_get_userdata(sd_bus_track *track) { + assert_return(track, NULL); + + return track->userdata; +} + +_public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) { + void *ret; + + assert_return(track, NULL); + + ret = track->userdata; + track->userdata = userdata; + + return ret; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-track.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-track.h new file mode 100644 index 0000000000..7d93a727d6 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-track.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + 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 . +***/ + +void bus_track_dispatch(sd_bus_track *track); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-type.c b/src/libsystemd/libsystemd-internal/sd-bus/bus-type.c new file mode 100644 index 0000000000..c692afc580 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-type.c @@ -0,0 +1,176 @@ +/*** + 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 . +***/ + +#include "bus-type.h" + +bool bus_type_is_valid(char c) { + static const char valid[] = { + SD_BUS_TYPE_BYTE, + SD_BUS_TYPE_BOOLEAN, + SD_BUS_TYPE_INT16, + SD_BUS_TYPE_UINT16, + SD_BUS_TYPE_INT32, + SD_BUS_TYPE_UINT32, + SD_BUS_TYPE_INT64, + SD_BUS_TYPE_UINT64, + SD_BUS_TYPE_DOUBLE, + SD_BUS_TYPE_STRING, + SD_BUS_TYPE_OBJECT_PATH, + SD_BUS_TYPE_SIGNATURE, + SD_BUS_TYPE_ARRAY, + SD_BUS_TYPE_VARIANT, + SD_BUS_TYPE_STRUCT, + SD_BUS_TYPE_DICT_ENTRY, + SD_BUS_TYPE_UNIX_FD + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +bool bus_type_is_valid_in_signature(char c) { + static const char valid[] = { + SD_BUS_TYPE_BYTE, + SD_BUS_TYPE_BOOLEAN, + SD_BUS_TYPE_INT16, + SD_BUS_TYPE_UINT16, + SD_BUS_TYPE_INT32, + SD_BUS_TYPE_UINT32, + SD_BUS_TYPE_INT64, + SD_BUS_TYPE_UINT64, + SD_BUS_TYPE_DOUBLE, + SD_BUS_TYPE_STRING, + SD_BUS_TYPE_OBJECT_PATH, + SD_BUS_TYPE_SIGNATURE, + SD_BUS_TYPE_ARRAY, + SD_BUS_TYPE_VARIANT, + SD_BUS_TYPE_STRUCT_BEGIN, + SD_BUS_TYPE_STRUCT_END, + SD_BUS_TYPE_DICT_ENTRY_BEGIN, + SD_BUS_TYPE_DICT_ENTRY_END, + SD_BUS_TYPE_UNIX_FD + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +bool bus_type_is_basic(char c) { + static const char valid[] = { + SD_BUS_TYPE_BYTE, + SD_BUS_TYPE_BOOLEAN, + SD_BUS_TYPE_INT16, + SD_BUS_TYPE_UINT16, + SD_BUS_TYPE_INT32, + SD_BUS_TYPE_UINT32, + SD_BUS_TYPE_INT64, + SD_BUS_TYPE_UINT64, + SD_BUS_TYPE_DOUBLE, + SD_BUS_TYPE_STRING, + SD_BUS_TYPE_OBJECT_PATH, + SD_BUS_TYPE_SIGNATURE, + SD_BUS_TYPE_UNIX_FD + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +bool bus_type_is_trivial(char c) { + static const char valid[] = { + SD_BUS_TYPE_BYTE, + SD_BUS_TYPE_BOOLEAN, + SD_BUS_TYPE_INT16, + SD_BUS_TYPE_UINT16, + SD_BUS_TYPE_INT32, + SD_BUS_TYPE_UINT32, + SD_BUS_TYPE_INT64, + SD_BUS_TYPE_UINT64, + SD_BUS_TYPE_DOUBLE + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +bool bus_type_is_container(char c) { + static const char valid[] = { + SD_BUS_TYPE_ARRAY, + SD_BUS_TYPE_VARIANT, + SD_BUS_TYPE_STRUCT, + SD_BUS_TYPE_DICT_ENTRY + }; + + return !!memchr(valid, c, sizeof(valid)); +} + +int bus_type_get_alignment(char c) { + + switch (c) { + case SD_BUS_TYPE_BYTE: + case SD_BUS_TYPE_SIGNATURE: + case SD_BUS_TYPE_VARIANT: + return 1; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + return 2; + + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_ARRAY: + case SD_BUS_TYPE_UNIX_FD: + return 4; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + case SD_BUS_TYPE_STRUCT: + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: + return 8; + } + + return -EINVAL; +} + +int bus_type_get_size(char c) { + + switch (c) { + case SD_BUS_TYPE_BYTE: + return 1; + + case SD_BUS_TYPE_INT16: + case SD_BUS_TYPE_UINT16: + return 2; + + case SD_BUS_TYPE_BOOLEAN: + case SD_BUS_TYPE_INT32: + case SD_BUS_TYPE_UINT32: + case SD_BUS_TYPE_UNIX_FD: + return 4; + + case SD_BUS_TYPE_INT64: + case SD_BUS_TYPE_UINT64: + case SD_BUS_TYPE_DOUBLE: + return 8; + } + + return -EINVAL; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/bus-type.h b/src/libsystemd/libsystemd-internal/sd-bus/bus-type.h new file mode 100644 index 0000000000..7169b0f765 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/bus-type.h @@ -0,0 +1,37 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include + +#include "macro.h" + +bool bus_type_is_valid(char c) _const_; +bool bus_type_is_valid_in_signature(char c) _const_; +bool bus_type_is_basic(char c) _const_; +/* "trivial" is systemd's term for what the D-Bus Specification calls + * a "fixed type": that is, a basic type of fixed length */ +bool bus_type_is_trivial(char c) _const_; +bool bus_type_is_container(char c) _const_; + +int bus_type_get_alignment(char c) _const_; +int bus_type_get_size(char c) _const_; diff --git a/src/libsystemd/libsystemd-internal/sd-bus/busctl-introspect.c b/src/libsystemd/libsystemd-internal/sd-bus/busctl-introspect.c new file mode 100644 index 0000000000..3c49ad5c57 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/busctl-introspect.c @@ -0,0 +1,790 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "alloc-util.h" +#include "busctl-introspect.h" +#include "string-util.h" +#include "util.h" +#include "xml.h" + +#define NODE_DEPTH_MAX 16 + +typedef struct Context { + const XMLIntrospectOps *ops; + void *userdata; + + char *interface_name; + uint64_t interface_flags; + + char *member_name; + char *member_signature; + char *member_result; + uint64_t member_flags; + bool member_writable; + + const char *current; + void *xml_state; +} Context; + +static void context_reset_member(Context *c) { + free(c->member_name); + free(c->member_signature); + free(c->member_result); + + c->member_name = c->member_signature = c->member_result = NULL; + c->member_flags = 0; + c->member_writable = false; +} + +static void context_reset_interface(Context *c) { + c->interface_name = mfree(c->interface_name); + c->interface_flags = 0; + + context_reset_member(c); +} + +static int parse_xml_annotation(Context *context, uint64_t *flags) { + + enum { + STATE_ANNOTATION, + STATE_NAME, + STATE_VALUE + } state = STATE_ANNOTATION; + + _cleanup_free_ char *field = NULL, *value = NULL; + + assert(context); + + for (;;) { + _cleanup_free_ char *name = NULL; + + int t; + + t = xml_tokenize(&context->current, &name, &context->xml_state, NULL); + if (t < 0) { + log_error("XML parse error."); + return t; + } + + if (t == XML_END) { + log_error("Premature end of XML data."); + return -EBADMSG; + } + + switch (state) { + + case STATE_ANNOTATION: + + if (t == XML_ATTRIBUTE_NAME) { + + if (streq_ptr(name, "name")) + state = STATE_NAME; + + else if (streq_ptr(name, "value")) + state = STATE_VALUE; + + else { + log_error("Unexpected attribute %s.", name); + return -EBADMSG; + } + + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "annotation"))) { + + if (flags) { + if (streq_ptr(field, "org.freedesktop.DBus.Deprecated")) { + + if (streq_ptr(value, "true")) + *flags |= SD_BUS_VTABLE_DEPRECATED; + + } else if (streq_ptr(field, "org.freedesktop.DBus.Method.NoReply")) { + + if (streq_ptr(value, "true")) + *flags |= SD_BUS_VTABLE_METHOD_NO_REPLY; + + } else if (streq_ptr(field, "org.freedesktop.DBus.Property.EmitsChangedSignal")) { + + if (streq_ptr(value, "const")) + *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) | SD_BUS_VTABLE_PROPERTY_CONST; + else if (streq_ptr(value, "invalidates")) + *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST)) | SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION; + else if (streq_ptr(value, "false")) + *flags = *flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION); + } + } + + return 0; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in . (1)"); + return -EINVAL; + } + + break; + + case STATE_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + free(field); + field = name; + name = NULL; + + state = STATE_ANNOTATION; + } else { + log_error("Unexpected token in . (2)"); + return -EINVAL; + } + + break; + + case STATE_VALUE: + + if (t == XML_ATTRIBUTE_VALUE) { + free(value); + value = name; + name = NULL; + + state = STATE_ANNOTATION; + } else { + log_error("Unexpected token in . (3)"); + return -EINVAL; + } + + break; + + default: + assert_not_reached("Bad state"); + } + } +} + +static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth) { + + enum { + STATE_NODE, + STATE_NODE_NAME, + STATE_INTERFACE, + STATE_INTERFACE_NAME, + STATE_METHOD, + STATE_METHOD_NAME, + STATE_METHOD_ARG, + STATE_METHOD_ARG_NAME, + STATE_METHOD_ARG_TYPE, + STATE_METHOD_ARG_DIRECTION, + STATE_SIGNAL, + STATE_SIGNAL_NAME, + STATE_SIGNAL_ARG, + STATE_SIGNAL_ARG_NAME, + STATE_SIGNAL_ARG_TYPE, + STATE_PROPERTY, + STATE_PROPERTY_NAME, + STATE_PROPERTY_TYPE, + STATE_PROPERTY_ACCESS, + } state = STATE_NODE; + + _cleanup_free_ char *node_path = NULL, *argument_type = NULL, *argument_direction = NULL; + const char *np = prefix; + int r; + + assert(context); + assert(prefix); + + if (n_depth > NODE_DEPTH_MAX) { + log_error(" depth too high."); + return -EINVAL; + } + + for (;;) { + _cleanup_free_ char *name = NULL; + int t; + + t = xml_tokenize(&context->current, &name, &context->xml_state, NULL); + if (t < 0) { + log_error("XML parse error."); + return t; + } + + if (t == XML_END) { + log_error("Premature end of XML data."); + return -EBADMSG; + } + + switch (state) { + + case STATE_NODE: + if (t == XML_ATTRIBUTE_NAME) { + + if (streq_ptr(name, "name")) + state = STATE_NODE_NAME; + else { + log_error("Unexpected attribute %s.", name); + return -EBADMSG; + } + + } else if (t == XML_TAG_OPEN) { + + if (streq_ptr(name, "interface")) + state = STATE_INTERFACE; + else if (streq_ptr(name, "node")) { + + r = parse_xml_node(context, np, n_depth+1); + if (r < 0) + return r; + } else { + log_error("Unexpected tag %s.", name); + return -EBADMSG; + } + + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "node"))) { + + if (context->ops->on_path) { + r = context->ops->on_path(node_path ? node_path : np, context->userdata); + if (r < 0) + return r; + } + + return 0; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in . (1)"); + return -EINVAL; + } + + break; + + case STATE_NODE_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + + free(node_path); + + if (name[0] == '/') { + node_path = name; + name = NULL; + } else { + + if (endswith(prefix, "/")) + node_path = strappend(prefix, name); + else + node_path = strjoin(prefix, "/", name, NULL); + if (!node_path) + return log_oom(); + } + + np = node_path; + state = STATE_NODE; + } else { + log_error("Unexpected token in . (2)"); + return -EINVAL; + } + + break; + + case STATE_INTERFACE: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_INTERFACE_NAME; + else { + log_error("Unexpected attribute %s.", name); + return -EBADMSG; + } + + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "method")) + state = STATE_METHOD; + else if (streq_ptr(name, "signal")) + state = STATE_SIGNAL; + else if (streq_ptr(name, "property")) { + context->member_flags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE; + state = STATE_PROPERTY; + } else if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, &context->interface_flags); + if (r < 0) + return r; + } else { + log_error("Unexpected tag %s.", name); + return -EINVAL; + } + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "interface"))) { + + if (n_depth == 0) { + if (context->ops->on_interface) { + r = context->ops->on_interface(context->interface_name, context->interface_flags, context->userdata); + if (r < 0) + return r; + } + + context_reset_interface(context); + } + + state = STATE_NODE; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in . (1)"); + return -EINVAL; + } + + break; + + case STATE_INTERFACE_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + if (n_depth == 0) { + free(context->interface_name); + context->interface_name = name; + name = NULL; + } + + state = STATE_INTERFACE; + } else { + log_error("Unexpected token in . (2)"); + return -EINVAL; + } + + break; + + case STATE_METHOD: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_METHOD_NAME; + else { + log_error("Unexpected attribute %s", name); + return -EBADMSG; + } + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "arg")) + state = STATE_METHOD_ARG; + else if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, &context->member_flags); + if (r < 0) + return r; + } else { + log_error("Unexpected tag %s.", name); + return -EINVAL; + } + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "method"))) { + + if (n_depth == 0) { + if (context->ops->on_method) { + r = context->ops->on_method(context->interface_name, context->member_name, context->member_signature, context->member_result, context->member_flags, context->userdata); + if (r < 0) + return r; + } + + context_reset_member(context); + } + + state = STATE_INTERFACE; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in (1)."); + return -EINVAL; + } + + break; + + case STATE_METHOD_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + + if (n_depth == 0) { + free(context->member_name); + context->member_name = name; + name = NULL; + } + + state = STATE_METHOD; + } else { + log_error("Unexpected token in (2)."); + return -EINVAL; + } + + break; + + case STATE_METHOD_ARG: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_METHOD_ARG_NAME; + else if (streq_ptr(name, "type")) + state = STATE_METHOD_ARG_TYPE; + else if (streq_ptr(name, "direction")) + state = STATE_METHOD_ARG_DIRECTION; + else { + log_error("Unexpected method attribute %s.", name); + return -EBADMSG; + } + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, NULL); + if (r < 0) + return r; + } else { + log_error("Unexpected method tag %s.", name); + return -EINVAL; + } + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) { + + if (n_depth == 0) { + + if (argument_type) { + if (!argument_direction || streq(argument_direction, "in")) { + if (!strextend(&context->member_signature, argument_type, NULL)) + return log_oom(); + } else if (streq(argument_direction, "out")) { + if (!strextend(&context->member_result, argument_type, NULL)) + return log_oom(); + } + } + + argument_type = mfree(argument_type); + argument_direction = mfree(argument_direction); + } + + state = STATE_METHOD; + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in method . (1)"); + return -EINVAL; + } + + break; + + case STATE_METHOD_ARG_NAME: + + if (t == XML_ATTRIBUTE_VALUE) + state = STATE_METHOD_ARG; + else { + log_error("Unexpected token in method . (2)"); + return -EINVAL; + } + + break; + + case STATE_METHOD_ARG_TYPE: + + if (t == XML_ATTRIBUTE_VALUE) { + free(argument_type); + argument_type = name; + name = NULL; + + state = STATE_METHOD_ARG; + } else { + log_error("Unexpected token in method . (3)"); + return -EINVAL; + } + + break; + + case STATE_METHOD_ARG_DIRECTION: + + if (t == XML_ATTRIBUTE_VALUE) { + free(argument_direction); + argument_direction = name; + name = NULL; + + state = STATE_METHOD_ARG; + } else { + log_error("Unexpected token in method . (4)"); + return -EINVAL; + } + + break; + + case STATE_SIGNAL: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_SIGNAL_NAME; + else { + log_error("Unexpected attribute %s.", name); + return -EBADMSG; + } + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "arg")) + state = STATE_SIGNAL_ARG; + else if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, &context->member_flags); + if (r < 0) + return r; + } else { + log_error("Unexpected tag %s.", name); + return -EINVAL; + } + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "signal"))) { + + if (n_depth == 0) { + if (context->ops->on_signal) { + r = context->ops->on_signal(context->interface_name, context->member_name, context->member_signature, context->member_flags, context->userdata); + if (r < 0) + return r; + } + + context_reset_member(context); + } + + state = STATE_INTERFACE; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in . (1)"); + return -EINVAL; + } + + break; + + case STATE_SIGNAL_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + + if (n_depth == 0) { + free(context->member_name); + context->member_name = name; + name = NULL; + } + + state = STATE_SIGNAL; + } else { + log_error("Unexpected token in . (2)"); + return -EINVAL; + } + + break; + + + case STATE_SIGNAL_ARG: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_SIGNAL_ARG_NAME; + else if (streq_ptr(name, "type")) + state = STATE_SIGNAL_ARG_TYPE; + else { + log_error("Unexpected signal attribute %s.", name); + return -EBADMSG; + } + } else if (t == XML_TAG_OPEN) { + if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, NULL); + if (r < 0) + return r; + } else { + log_error("Unexpected signal tag %s.", name); + return -EINVAL; + } + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) { + + if (argument_type) { + if (!strextend(&context->member_signature, argument_type, NULL)) + return log_oom(); + + argument_type = mfree(argument_type); + } + + state = STATE_SIGNAL; + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in signal (1)."); + return -EINVAL; + } + + break; + + case STATE_SIGNAL_ARG_NAME: + + if (t == XML_ATTRIBUTE_VALUE) + state = STATE_SIGNAL_ARG; + else { + log_error("Unexpected token in signal (2)."); + return -EINVAL; + } + + break; + + case STATE_SIGNAL_ARG_TYPE: + + if (t == XML_ATTRIBUTE_VALUE) { + free(argument_type); + argument_type = name; + name = NULL; + + state = STATE_SIGNAL_ARG; + } else { + log_error("Unexpected token in signal (3)."); + return -EINVAL; + } + + break; + + case STATE_PROPERTY: + + if (t == XML_ATTRIBUTE_NAME) { + if (streq_ptr(name, "name")) + state = STATE_PROPERTY_NAME; + else if (streq_ptr(name, "type")) + state = STATE_PROPERTY_TYPE; + else if (streq_ptr(name, "access")) + state = STATE_PROPERTY_ACCESS; + else { + log_error("Unexpected attribute %s.", name); + return -EBADMSG; + } + } else if (t == XML_TAG_OPEN) { + + if (streq_ptr(name, "annotation")) { + r = parse_xml_annotation(context, &context->member_flags); + if (r < 0) + return r; + } else { + log_error("Unexpected tag %s.", name); + return -EINVAL; + } + + } else if (t == XML_TAG_CLOSE_EMPTY || + (t == XML_TAG_CLOSE && streq_ptr(name, "property"))) { + + if (n_depth == 0) { + if (context->ops->on_property) { + r = context->ops->on_property(context->interface_name, context->member_name, context->member_signature, context->member_writable, context->member_flags, context->userdata); + if (r < 0) + return r; + } + + context_reset_member(context); + } + + state = STATE_INTERFACE; + + } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token in . (1)"); + return -EINVAL; + } + + break; + + case STATE_PROPERTY_NAME: + + if (t == XML_ATTRIBUTE_VALUE) { + + if (n_depth == 0) { + free(context->member_name); + context->member_name = name; + name = NULL; + } + state = STATE_PROPERTY; + } else { + log_error("Unexpected token in . (2)"); + return -EINVAL; + } + + break; + + case STATE_PROPERTY_TYPE: + + if (t == XML_ATTRIBUTE_VALUE) { + + if (n_depth == 0) { + free(context->member_signature); + context->member_signature = name; + name = NULL; + } + + state = STATE_PROPERTY; + } else { + log_error("Unexpected token in . (3)"); + return -EINVAL; + } + + break; + + case STATE_PROPERTY_ACCESS: + + if (t == XML_ATTRIBUTE_VALUE) { + + if (streq(name, "readwrite") || streq(name, "write")) + context->member_writable = true; + + state = STATE_PROPERTY; + } else { + log_error("Unexpected token in . (4)"); + return -EINVAL; + } + + break; + } + } +} + +int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata) { + Context context = { + .ops = ops, + .userdata = userdata, + .current = xml, + }; + + int r; + + assert(prefix); + assert(xml); + assert(ops); + + for (;;) { + _cleanup_free_ char *name = NULL; + + r = xml_tokenize(&context.current, &name, &context.xml_state, NULL); + if (r < 0) { + log_error("XML parse error"); + goto finish; + } + + if (r == XML_END) { + r = 0; + break; + } + + if (r == XML_TAG_OPEN) { + + if (streq(name, "node")) { + r = parse_xml_node(&context, prefix, 0); + if (r < 0) + goto finish; + } else { + log_error("Unexpected tag '%s' in introspection data.", name); + r = -EBADMSG; + goto finish; + } + } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) { + log_error("Unexpected token."); + r = -EBADMSG; + goto finish; + } + } + +finish: + context_reset_interface(&context); + + return r; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/busctl-introspect.h b/src/libsystemd/libsystemd-internal/sd-bus/busctl-introspect.h new file mode 100644 index 0000000000..d922e352db --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/busctl-introspect.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +typedef struct XMLIntrospectOps { + int (*on_path)(const char *path, void *userdata); + int (*on_interface)(const char *name, uint64_t flags, void *userdata); + int (*on_method)(const char *interface, const char *name, const char *signature, const char *result, uint64_t flags, void *userdata); + int (*on_signal)(const char *interface, const char *name, const char *signature, uint64_t flags, void *userdata); + int (*on_property)(const char *interface, const char *name, const char *signature, bool writable, uint64_t flags, void *userdata); +} XMLIntrospectOps; + +int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata); diff --git a/src/libsystemd/libsystemd-internal/sd-bus/busctl.c b/src/libsystemd/libsystemd-internal/sd-bus/busctl.c new file mode 100644 index 0000000000..fd3b241aad --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/busctl.c @@ -0,0 +1,2073 @@ +/*** + 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 . +***/ + +#include + +#include + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-internal.h" +#include "bus-signature.h" +#include "bus-type.h" +#include "bus-util.h" +#include "busctl-introspect.h" +#include "escape.h" +#include "fd-util.h" +#include "locale-util.h" +#include "log.h" +#include "pager.h" +#include "parse-util.h" +#include "path-util.h" +#include "set.h" +#include "strv.h" +#include "terminal-util.h" +#include "user-util.h" +#include "util.h" + +static bool arg_no_pager = false; +static bool arg_legend = true; +static char *arg_address = NULL; +static bool arg_unique = false; +static bool arg_acquired = false; +static bool arg_activatable = false; +static bool arg_show_machine = false; +static char **arg_matches = NULL; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_user = false; +static size_t arg_snaplen = 4096; +static bool arg_list = false; +static bool arg_quiet = false; +static bool arg_verbose = false; +static bool arg_expect_reply = true; +static bool arg_auto_start = true; +static bool arg_allow_interactive_authorization = true; +static bool arg_augment_creds = true; +static usec_t arg_timeout = 0; + +static void pager_open_if_enabled(void) { + + /* Cache result before we open the pager */ + if (arg_no_pager) + return; + + pager_open(false); +} + +#define NAME_IS_ACQUIRED INT_TO_PTR(1) +#define NAME_IS_ACTIVATABLE INT_TO_PTR(2) + +static int list_bus_names(sd_bus *bus, char **argv) { + _cleanup_strv_free_ char **acquired = NULL, **activatable = NULL; + _cleanup_free_ char **merged = NULL; + _cleanup_hashmap_free_ Hashmap *names = NULL; + char **i; + int r; + size_t max_i = 0; + unsigned n = 0; + void *v; + char *k; + Iterator iterator; + + assert(bus); + + if (!arg_unique && !arg_acquired && !arg_activatable) + arg_unique = arg_acquired = arg_activatable = true; + + r = sd_bus_list_names(bus, (arg_acquired || arg_unique) ? &acquired : NULL, arg_activatable ? &activatable : NULL); + if (r < 0) + return log_error_errno(r, "Failed to list names: %m"); + + pager_open_if_enabled(); + + names = hashmap_new(&string_hash_ops); + if (!names) + return log_oom(); + + STRV_FOREACH(i, acquired) { + max_i = MAX(max_i, strlen(*i)); + + r = hashmap_put(names, *i, NAME_IS_ACQUIRED); + if (r < 0) + return log_error_errno(r, "Failed to add to hashmap: %m"); + } + + STRV_FOREACH(i, activatable) { + max_i = MAX(max_i, strlen(*i)); + + r = hashmap_put(names, *i, NAME_IS_ACTIVATABLE); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Failed to add to hashmap: %m"); + } + + merged = new(char*, hashmap_size(names) + 1); + HASHMAP_FOREACH_KEY(v, k, names, iterator) + merged[n++] = k; + + merged[n] = NULL; + strv_sort(merged); + + if (arg_legend) { + printf("%-*s %*s %-*s %-*s %-*s %-*s %-*s %-*s", + (int) max_i, "NAME", 10, "PID", 15, "PROCESS", 16, "USER", 13, "CONNECTION", 25, "UNIT", 10, "SESSION", 19, "DESCRIPTION"); + + if (arg_show_machine) + puts(" MACHINE"); + else + putchar('\n'); + } + + STRV_FOREACH(i, merged) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + sd_id128_t mid; + + if (hashmap_get(names, *i) == NAME_IS_ACTIVATABLE) { + /* Activatable */ + + printf("%-*s", (int) max_i, *i); + printf(" - - - (activatable) - - "); + if (arg_show_machine) + puts(" -"); + else + putchar('\n'); + continue; + + } + + if (!arg_unique && (*i)[0] == ':') + continue; + + if (!arg_acquired && (*i)[0] != ':') + continue; + + printf("%-*s", (int) max_i, *i); + + r = sd_bus_get_name_creds( + bus, *i, + (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | + SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM| + SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_SESSION| + SD_BUS_CREDS_DESCRIPTION, &creds); + if (r >= 0) { + const char *unique, *session, *unit, *cn; + pid_t pid; + uid_t uid; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r >= 0) { + const char *comm = NULL; + + sd_bus_creds_get_comm(creds, &comm); + + printf(" %10lu %-15s", (unsigned long) pid, strna(comm)); + } else + fputs(" - - ", stdout); + + r = sd_bus_creds_get_euid(creds, &uid); + if (r >= 0) { + _cleanup_free_ char *u = NULL; + + u = uid_to_name(uid); + if (!u) + return log_oom(); + + if (strlen(u) > 16) + u[16] = 0; + + printf(" %-16s", u); + } else + fputs(" - ", stdout); + + r = sd_bus_creds_get_unique_name(creds, &unique); + if (r >= 0) + printf(" %-13s", unique); + else + fputs(" - ", stdout); + + r = sd_bus_creds_get_unit(creds, &unit); + if (r >= 0) { + _cleanup_free_ char *e; + + e = ellipsize(unit, 25, 100); + if (!e) + return log_oom(); + + printf(" %-25s", e); + } else + fputs(" - ", stdout); + + r = sd_bus_creds_get_session(creds, &session); + if (r >= 0) + printf(" %-10s", session); + else + fputs(" - ", stdout); + + r = sd_bus_creds_get_description(creds, &cn); + if (r >= 0) + printf(" %-19s", cn); + else + fputs(" - ", stdout); + + } else + printf(" - - - - - - - "); + + if (arg_show_machine) { + r = sd_bus_get_name_machine_id(bus, *i, &mid); + if (r >= 0) { + char m[SD_ID128_STRING_MAX]; + printf(" %s\n", sd_id128_to_string(mid, m)); + } else + puts(" -"); + } else + putchar('\n'); + } + + return 0; +} + +static void print_subtree(const char *prefix, const char *path, char **l) { + const char *vertical, *space; + char **n; + + /* We assume the list is sorted. Let's first skip over the + * entry we are looking at. */ + for (;;) { + if (!*l) + return; + + if (!streq(*l, path)) + break; + + l++; + } + + vertical = strjoina(prefix, draw_special_char(DRAW_TREE_VERTICAL)); + space = strjoina(prefix, draw_special_char(DRAW_TREE_SPACE)); + + for (;;) { + bool has_more = false; + + if (!*l || !path_startswith(*l, path)) + break; + + n = l + 1; + for (;;) { + if (!*n || !path_startswith(*n, path)) + break; + + if (!path_startswith(*n, *l)) { + has_more = true; + break; + } + + n++; + } + + printf("%s%s%s\n", prefix, draw_special_char(has_more ? DRAW_TREE_BRANCH : DRAW_TREE_RIGHT), *l); + + print_subtree(has_more ? vertical : space, *l, l); + l = n; + } +} + +static void print_tree(const char *prefix, char **l) { + + pager_open_if_enabled(); + + prefix = strempty(prefix); + + if (arg_list) { + char **i; + + STRV_FOREACH(i, l) + printf("%s%s\n", prefix, *i); + return; + } + + if (strv_isempty(l)) { + printf("No objects discovered.\n"); + return; + } + + if (streq(l[0], "/") && !l[1]) { + printf("Only root object discovered.\n"); + return; + } + + print_subtree(prefix, "/", l); +} + +static int on_path(const char *path, void *userdata) { + Set *paths = userdata; + int r; + + assert(paths); + + r = set_put_strdup(paths, path); + if (r < 0) + return log_oom(); + + return 0; +} + +static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *paths, bool many) { + static const XMLIntrospectOps ops = { + .on_path = on_path, + }; + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *xml; + int r; + + r = sd_bus_call_method(bus, service, path, "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + if (r < 0) { + if (many) + printf("Failed to introspect object %s of service %s: %s\n", path, service, bus_error_message(&error, r)); + else + log_error("Failed to introspect object %s of service %s: %s", path, service, bus_error_message(&error, r)); + return r; + } + + r = sd_bus_message_read(reply, "s", &xml); + if (r < 0) + return bus_log_parse_error(r); + + return parse_xml_introspect(path, xml, &ops, paths); +} + +static int tree_one(sd_bus *bus, const char *service, const char *prefix, bool many) { + _cleanup_set_free_free_ Set *paths = NULL, *done = NULL, *failed = NULL; + _cleanup_free_ char **l = NULL; + char *m; + int r; + + paths = set_new(&string_hash_ops); + if (!paths) + return log_oom(); + + done = set_new(&string_hash_ops); + if (!done) + return log_oom(); + + failed = set_new(&string_hash_ops); + if (!failed) + return log_oom(); + + m = strdup("/"); + if (!m) + return log_oom(); + + r = set_put(paths, m); + if (r < 0) { + free(m); + return log_oom(); + } + + for (;;) { + _cleanup_free_ char *p = NULL; + int q; + + p = set_steal_first(paths); + if (!p) + break; + + if (set_contains(done, p) || + set_contains(failed, p)) + continue; + + q = find_nodes(bus, service, p, paths, many); + if (q < 0) { + if (r >= 0) + r = q; + + q = set_put(failed, p); + } else + q = set_put(done, p); + + if (q < 0) + return log_oom(); + + assert(q != 0); + p = NULL; + } + + pager_open_if_enabled(); + + l = set_get_strv(done); + if (!l) + return log_oom(); + + strv_sort(l); + print_tree(prefix, l); + + fflush(stdout); + + return r; +} + +static int tree(sd_bus *bus, char **argv) { + char **i; + int r = 0; + + if (!arg_unique && !arg_acquired) + arg_acquired = true; + + if (strv_length(argv) <= 1) { + _cleanup_strv_free_ char **names = NULL; + bool not_first = false; + + r = sd_bus_list_names(bus, &names, NULL); + if (r < 0) + return log_error_errno(r, "Failed to get name list: %m"); + + pager_open_if_enabled(); + + STRV_FOREACH(i, names) { + int q; + + if (!arg_unique && (*i)[0] == ':') + continue; + + if (!arg_acquired && (*i)[0] == ':') + continue; + + if (not_first) + printf("\n"); + + printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal()); + + q = tree_one(bus, *i, NULL, true); + if (q < 0 && r >= 0) + r = q; + + not_first = true; + } + } else { + STRV_FOREACH(i, argv+1) { + int q; + + if (i > argv+1) + printf("\n"); + + if (argv[2]) { + pager_open_if_enabled(); + printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal()); + } + + q = tree_one(bus, *i, NULL, !!argv[2]); + if (q < 0 && r >= 0) + r = q; + } + } + + return r; +} + +static int format_cmdline(sd_bus_message *m, FILE *f, bool needs_space) { + int r; + + for (;;) { + const char *contents = NULL; + char type; + union { + uint8_t u8; + uint16_t u16; + int16_t s16; + uint32_t u32; + int32_t s32; + uint64_t u64; + int64_t s64; + double d64; + const char *string; + int i; + } basic; + + r = sd_bus_message_peek_type(m, &type, &contents); + if (r <= 0) + return r; + + if (bus_type_is_container(type) > 0) { + + r = sd_bus_message_enter_container(m, type, contents); + if (r < 0) + return r; + + if (type == SD_BUS_TYPE_ARRAY) { + unsigned n = 0; + + /* count array entries */ + for (;;) { + + r = sd_bus_message_skip(m, contents); + if (r < 0) + return r; + if (r == 0) + break; + + n++; + } + + r = sd_bus_message_rewind(m, false); + if (r < 0) + return r; + + if (needs_space) + fputc(' ', f); + + fprintf(f, "%u", n); + } else if (type == SD_BUS_TYPE_VARIANT) { + + if (needs_space) + fputc(' ', f); + + fprintf(f, "%s", contents); + } + + r = format_cmdline(m, f, needs_space || IN_SET(type, SD_BUS_TYPE_ARRAY, SD_BUS_TYPE_VARIANT)); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + continue; + } + + r = sd_bus_message_read_basic(m, type, &basic); + if (r < 0) + return r; + + if (needs_space) + fputc(' ', f); + + switch (type) { + case SD_BUS_TYPE_BYTE: + fprintf(f, "%u", basic.u8); + break; + + case SD_BUS_TYPE_BOOLEAN: + fputs(true_false(basic.i), f); + break; + + case SD_BUS_TYPE_INT16: + fprintf(f, "%i", basic.s16); + break; + + case SD_BUS_TYPE_UINT16: + fprintf(f, "%u", basic.u16); + break; + + case SD_BUS_TYPE_INT32: + fprintf(f, "%i", basic.s32); + break; + + case SD_BUS_TYPE_UINT32: + fprintf(f, "%u", basic.u32); + break; + + case SD_BUS_TYPE_INT64: + fprintf(f, "%" PRIi64, basic.s64); + break; + + case SD_BUS_TYPE_UINT64: + fprintf(f, "%" PRIu64, basic.u64); + break; + + case SD_BUS_TYPE_DOUBLE: + fprintf(f, "%g", basic.d64); + break; + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: { + _cleanup_free_ char *b = NULL; + + b = cescape(basic.string); + if (!b) + return -ENOMEM; + + fprintf(f, "\"%s\"", b); + break; + } + + case SD_BUS_TYPE_UNIX_FD: + fprintf(f, "%i", basic.i); + break; + + default: + assert_not_reached("Unknown basic type."); + } + + needs_space = true; + } +} + +typedef struct Member { + const char *type; + char *interface; + char *name; + char *signature; + char *result; + char *value; + bool writable; + uint64_t flags; +} Member; + +static void member_hash_func(const void *p, struct siphash *state) { + const Member *m = p; + uint64_t arity = 1; + + assert(m); + assert(m->type); + + string_hash_func(m->type, state); + + arity += !!m->name + !!m->interface; + + uint64_hash_func(&arity, state); + + if (m->name) + string_hash_func(m->name, state); + + if (m->interface) + string_hash_func(m->interface, state); +} + +static int member_compare_func(const void *a, const void *b) { + const Member *x = a, *y = b; + int d; + + assert(x); + assert(y); + assert(x->type); + assert(y->type); + + d = strcmp_ptr(x->interface, y->interface); + if (d != 0) + return d; + + d = strcmp(x->type, y->type); + if (d != 0) + return d; + + return strcmp_ptr(x->name, y->name); +} + +static int member_compare_funcp(const void *a, const void *b) { + const Member *const * x = (const Member *const *) a, * const *y = (const Member *const *) b; + + return member_compare_func(*x, *y); +} + +static void member_free(Member *m) { + if (!m) + return; + + free(m->interface); + free(m->name); + free(m->signature); + free(m->result); + free(m->value); + free(m); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Member*, member_free); + +static void member_set_free(Set *s) { + Member *m; + + while ((m = set_steal_first(s))) + member_free(m); + + set_free(s); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, member_set_free); + +static int on_interface(const char *interface, uint64_t flags, void *userdata) { + _cleanup_(member_freep) Member *m; + Set *members = userdata; + int r; + + assert(interface); + assert(members); + + m = new0(Member, 1); + if (!m) + return log_oom(); + + m->type = "interface"; + m->flags = flags; + + r = free_and_strdup(&m->interface, interface); + if (r < 0) + return log_oom(); + + r = set_put(members, m); + if (r <= 0) { + log_error("Duplicate interface"); + return -EINVAL; + } + + m = NULL; + return 0; +} + +static int on_method(const char *interface, const char *name, const char *signature, const char *result, uint64_t flags, void *userdata) { + _cleanup_(member_freep) Member *m; + Set *members = userdata; + int r; + + assert(interface); + assert(name); + + m = new0(Member, 1); + if (!m) + return log_oom(); + + m->type = "method"; + m->flags = flags; + + r = free_and_strdup(&m->interface, interface); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->name, name); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->signature, signature); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->result, result); + if (r < 0) + return log_oom(); + + r = set_put(members, m); + if (r <= 0) { + log_error("Duplicate method"); + return -EINVAL; + } + + m = NULL; + return 0; +} + +static int on_signal(const char *interface, const char *name, const char *signature, uint64_t flags, void *userdata) { + _cleanup_(member_freep) Member *m; + Set *members = userdata; + int r; + + assert(interface); + assert(name); + + m = new0(Member, 1); + if (!m) + return log_oom(); + + m->type = "signal"; + m->flags = flags; + + r = free_and_strdup(&m->interface, interface); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->name, name); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->signature, signature); + if (r < 0) + return log_oom(); + + r = set_put(members, m); + if (r <= 0) { + log_error("Duplicate signal"); + return -EINVAL; + } + + m = NULL; + return 0; +} + +static int on_property(const char *interface, const char *name, const char *signature, bool writable, uint64_t flags, void *userdata) { + _cleanup_(member_freep) Member *m; + Set *members = userdata; + int r; + + assert(interface); + assert(name); + + m = new0(Member, 1); + if (!m) + return log_oom(); + + m->type = "property"; + m->flags = flags; + m->writable = writable; + + r = free_and_strdup(&m->interface, interface); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->name, name); + if (r < 0) + return log_oom(); + + r = free_and_strdup(&m->signature, signature); + if (r < 0) + return log_oom(); + + r = set_put(members, m); + if (r <= 0) { + log_error("Duplicate property"); + return -EINVAL; + } + + m = NULL; + return 0; +} + +static const char *strdash(const char *x) { + return isempty(x) ? "-" : x; +} + +static int introspect(sd_bus *bus, char **argv) { + static const struct hash_ops member_hash_ops = { + .hash = member_hash_func, + .compare = member_compare_func, + }; + + static const XMLIntrospectOps ops = { + .on_interface = on_interface, + .on_method = on_method, + .on_signal = on_signal, + .on_property = on_property, + }; + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(member_set_freep) Set *members = NULL; + Iterator i; + Member *m; + const char *xml; + int r; + unsigned name_width, type_width, signature_width, result_width; + Member **sorted = NULL; + unsigned k = 0, j, n_args; + + n_args = strv_length(argv); + if (n_args < 3) { + log_error("Requires service and object path argument."); + return -EINVAL; + } + + if (n_args > 4) { + log_error("Too many arguments."); + return -EINVAL; + } + + members = set_new(&member_hash_ops); + if (!members) + return log_oom(); + + r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + if (r < 0) { + log_error("Failed to introspect object %s of service %s: %s", argv[2], argv[1], bus_error_message(&error, r)); + return r; + } + + r = sd_bus_message_read(reply, "s", &xml); + if (r < 0) + return bus_log_parse_error(r); + + /* First, get list of all properties */ + r = parse_xml_introspect(argv[2], xml, &ops, members); + if (r < 0) + return r; + + /* Second, find the current values for them */ + SET_FOREACH(m, members, i) { + + if (!streq(m->type, "property")) + continue; + + if (m->value) + continue; + + if (argv[3] && !streq(argv[3], m->interface)) + continue; + + r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", m->interface); + if (r < 0) { + log_error("%s", bus_error_message(&error, r)); + return r; + } + + r = sd_bus_message_enter_container(reply, 'a', "{sv}"); + if (r < 0) + return bus_log_parse_error(r); + + for (;;) { + Member *z; + _cleanup_free_ char *buf = NULL; + _cleanup_fclose_ FILE *mf = NULL; + size_t sz = 0; + const char *name; + + r = sd_bus_message_enter_container(reply, 'e', "sv"); + if (r < 0) + return bus_log_parse_error(r); + + if (r == 0) + break; + + r = sd_bus_message_read(reply, "s", &name); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, 'v', NULL); + if (r < 0) + return bus_log_parse_error(r); + + mf = open_memstream(&buf, &sz); + if (!mf) + return log_oom(); + + r = format_cmdline(reply, mf, false); + if (r < 0) + return bus_log_parse_error(r); + + fclose(mf); + mf = NULL; + + z = set_get(members, &((Member) { + .type = "property", + .interface = m->interface, + .name = (char*) name })); + if (z) { + free(z->value); + z->value = buf; + buf = NULL; + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + pager_open_if_enabled(); + + name_width = strlen("NAME"); + type_width = strlen("TYPE"); + signature_width = strlen("SIGNATURE"); + result_width = strlen("RESULT/VALUE"); + + sorted = newa(Member*, set_size(members)); + + SET_FOREACH(m, members, i) { + + if (argv[3] && !streq(argv[3], m->interface)) + continue; + + if (m->interface) + name_width = MAX(name_width, strlen(m->interface)); + if (m->name) + name_width = MAX(name_width, strlen(m->name) + 1); + if (m->type) + type_width = MAX(type_width, strlen(m->type)); + if (m->signature) + signature_width = MAX(signature_width, strlen(m->signature)); + if (m->result) + result_width = MAX(result_width, strlen(m->result)); + if (m->value) + result_width = MAX(result_width, strlen(m->value)); + + sorted[k++] = m; + } + + if (result_width > 40) + result_width = 40; + + qsort(sorted, k, sizeof(Member*), member_compare_funcp); + + if (arg_legend) { + printf("%-*s %-*s %-*s %-*s %s\n", + (int) name_width, "NAME", + (int) type_width, "TYPE", + (int) signature_width, "SIGNATURE", + (int) result_width, "RESULT/VALUE", + "FLAGS"); + } + + for (j = 0; j < k; j++) { + _cleanup_free_ char *ellipsized = NULL; + const char *rv; + bool is_interface; + + m = sorted[j]; + + if (argv[3] && !streq(argv[3], m->interface)) + continue; + + is_interface = streq(m->type, "interface"); + + if (argv[3] && is_interface) + continue; + + if (m->value) { + ellipsized = ellipsize(m->value, result_width, 100); + if (!ellipsized) + return log_oom(); + + rv = ellipsized; + } else + rv = strdash(m->result); + + printf("%s%s%-*s%s %-*s %-*s %-*s%s%s%s%s%s%s\n", + is_interface ? ansi_highlight() : "", + is_interface ? "" : ".", + - !is_interface + (int) name_width, strdash(streq_ptr(m->type, "interface") ? m->interface : m->name), + is_interface ? ansi_normal() : "", + (int) type_width, strdash(m->type), + (int) signature_width, strdash(m->signature), + (int) result_width, rv, + (m->flags & SD_BUS_VTABLE_DEPRECATED) ? " deprecated" : (m->flags || m->writable ? "" : " -"), + (m->flags & SD_BUS_VTABLE_METHOD_NO_REPLY) ? " no-reply" : "", + (m->flags & SD_BUS_VTABLE_PROPERTY_CONST) ? " const" : "", + (m->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) ? " emits-change" : "", + (m->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) ? " emits-invalidation" : "", + m->writable ? " writable" : ""); + } + + return 0; +} + +static int message_dump(sd_bus_message *m, FILE *f) { + return bus_message_dump(m, f, BUS_MESSAGE_DUMP_WITH_HEADER); +} + +static int message_pcap(sd_bus_message *m, FILE *f) { + return bus_message_pcap_frame(m, arg_snaplen, f); +} + +static int monitor(sd_bus *bus, char *argv[], int (*dump)(sd_bus_message *m, FILE *f)) { + bool added_something = false; + char **i; + int r; + + STRV_FOREACH(i, argv+1) { + _cleanup_free_ char *m = NULL; + + if (!service_name_is_valid(*i)) { + log_error("Invalid service name '%s'", *i); + return -EINVAL; + } + + m = strjoin("sender='", *i, "'", NULL); + if (!m) + return log_oom(); + + r = sd_bus_add_match(bus, NULL, m, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + + free(m); + m = strjoin("destination='", *i, "'", NULL); + if (!m) + return log_oom(); + + r = sd_bus_add_match(bus, NULL, m, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + + added_something = true; + } + + STRV_FOREACH(i, arg_matches) { + r = sd_bus_add_match(bus, NULL, *i, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + + added_something = true; + } + + if (!added_something) { + r = sd_bus_add_match(bus, NULL, "", NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add match: %m"); + } + + log_info("Monitoring bus message stream."); + + for (;;) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = sd_bus_process(bus, &m); + if (r < 0) + return log_error_errno(r, "Failed to process bus: %m"); + + if (m) { + dump(m, stdout); + fflush(stdout); + + if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected") > 0) { + log_info("Connection terminated, exiting."); + return 0; + } + + continue; + } + + if (r > 0) + continue; + + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) + return log_error_errno(r, "Failed to wait for bus: %m"); + } +} + +static int capture(sd_bus *bus, char *argv[]) { + int r; + + if (isatty(fileno(stdout)) > 0) { + log_error("Refusing to write message data to console, please redirect output to a file."); + return -EINVAL; + } + + bus_pcap_header(arg_snaplen, stdout); + + r = monitor(bus, argv, message_pcap); + if (r < 0) + return r; + + if (ferror(stdout)) { + log_error("Couldn't write capture file."); + return -EIO; + } + + return r; +} + +static int status(sd_bus *bus, char *argv[]) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + pid_t pid; + int r; + + assert(bus); + + if (strv_length(argv) > 2) { + log_error("Expects no or one argument."); + return -EINVAL; + } + + if (argv[1]) { + r = parse_pid(argv[1], &pid); + if (r < 0) + r = sd_bus_get_name_creds( + bus, + argv[1], + (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | _SD_BUS_CREDS_ALL, + &creds); + else + r = sd_bus_creds_new_from_pid( + &creds, + pid, + _SD_BUS_CREDS_ALL); + } else { + const char *scope, *address; + sd_id128_t bus_id; + + r = sd_bus_get_address(bus, &address); + if (r >= 0) + printf("BusAddress=%s%s%s\n", ansi_highlight(), address, ansi_normal()); + + r = sd_bus_get_scope(bus, &scope); + if (r >= 0) + printf("BusScope=%s%s%s\n", ansi_highlight(), scope, ansi_normal()); + + r = sd_bus_get_bus_id(bus, &bus_id); + if (r >= 0) + printf("BusID=%s" SD_ID128_FORMAT_STR "%s\n", ansi_highlight(), SD_ID128_FORMAT_VAL(bus_id), ansi_normal()); + + r = sd_bus_get_owner_creds( + bus, + (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | _SD_BUS_CREDS_ALL, + &creds); + } + + if (r < 0) + return log_error_errno(r, "Failed to get credentials: %m"); + + bus_creds_dump(creds, NULL, false); + return 0; +} + +static int message_append_cmdline(sd_bus_message *m, const char *signature, char ***x) { + char **p; + int r; + + assert(m); + assert(signature); + assert(x); + + p = *x; + + for (;;) { + const char *v; + char t; + + t = *signature; + v = *p; + + if (t == 0) + break; + if (!v) { + log_error("Too few parameters for signature."); + return -EINVAL; + } + + signature++; + p++; + + switch (t) { + + case SD_BUS_TYPE_BOOLEAN: + + r = parse_boolean(v); + if (r < 0) { + log_error("Failed to parse as boolean: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &r); + break; + + case SD_BUS_TYPE_BYTE: { + uint8_t z; + + r = safe_atou8(v, &z); + if (r < 0) { + log_error("Failed to parse as byte (unsigned 8bit integer): %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_INT16: { + int16_t z; + + r = safe_atoi16(v, &z); + if (r < 0) { + log_error("Failed to parse as signed 16bit integer: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_UINT16: { + uint16_t z; + + r = safe_atou16(v, &z); + if (r < 0) { + log_error("Failed to parse as unsigned 16bit integer: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_INT32: { + int32_t z; + + r = safe_atoi32(v, &z); + if (r < 0) { + log_error("Failed to parse as signed 32bit integer: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_UINT32: { + uint32_t z; + + r = safe_atou32(v, &z); + if (r < 0) { + log_error("Failed to parse as unsigned 32bit integer: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_INT64: { + int64_t z; + + r = safe_atoi64(v, &z); + if (r < 0) { + log_error("Failed to parse as signed 64bit integer: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_UINT64: { + uint64_t z; + + r = safe_atou64(v, &z); + if (r < 0) { + log_error("Failed to parse as unsigned 64bit integer: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + + case SD_BUS_TYPE_DOUBLE: { + double z; + + r = safe_atod(v, &z); + if (r < 0) { + log_error("Failed to parse as double precision floating point: %s", v); + return r; + } + + r = sd_bus_message_append_basic(m, t, &z); + break; + } + + case SD_BUS_TYPE_STRING: + case SD_BUS_TYPE_OBJECT_PATH: + case SD_BUS_TYPE_SIGNATURE: + + r = sd_bus_message_append_basic(m, t, v); + break; + + case SD_BUS_TYPE_ARRAY: { + uint32_t n; + size_t k; + + r = safe_atou32(v, &n); + if (r < 0) { + log_error("Failed to parse number of array entries: %s", v); + return r; + } + + r = signature_element_length(signature, &k); + if (r < 0) { + log_error("Invalid array signature."); + return r; + } + + { + unsigned i; + char s[k + 1]; + memcpy(s, signature, k); + s[k] = 0; + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, s); + if (r < 0) + return bus_log_create_error(r); + + for (i = 0; i < n; i++) { + r = message_append_cmdline(m, s, &p); + if (r < 0) + return r; + } + } + + signature += k; + + r = sd_bus_message_close_container(m); + break; + } + + case SD_BUS_TYPE_VARIANT: + r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, v); + if (r < 0) + return bus_log_create_error(r); + + r = message_append_cmdline(m, v, &p); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + break; + + case SD_BUS_TYPE_STRUCT_BEGIN: + case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { + size_t k; + + signature--; + p--; + + r = signature_element_length(signature, &k); + if (r < 0) { + log_error("Invalid struct/dict entry signature."); + return r; + } + + { + char s[k-1]; + memcpy(s, signature + 1, k - 2); + s[k - 2] = 0; + + r = sd_bus_message_open_container(m, t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); + if (r < 0) + return bus_log_create_error(r); + + r = message_append_cmdline(m, s, &p); + if (r < 0) + return r; + } + + signature += k; + + r = sd_bus_message_close_container(m); + break; + } + + case SD_BUS_TYPE_UNIX_FD: + log_error("UNIX file descriptor not supported as type."); + return -EINVAL; + + default: + log_error("Unknown signature type %c.", t); + return -EINVAL; + } + + if (r < 0) + return bus_log_create_error(r); + } + + *x = p; + return 0; +} + +static int call(sd_bus *bus, char *argv[]) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + int r; + + assert(bus); + + if (strv_length(argv) < 5) { + log_error("Expects at least four arguments."); + return -EINVAL; + } + + r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2], argv[3], argv[4]); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_expect_reply(m, arg_expect_reply); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_auto_start(m, arg_auto_start); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_allow_interactive_authorization); + if (r < 0) + return bus_log_create_error(r); + + if (!isempty(argv[5])) { + char **p; + + p = argv+6; + + r = message_append_cmdline(m, argv[5], &p); + if (r < 0) + return r; + + if (*p) { + log_error("Too many parameters for signature."); + return -EINVAL; + } + } + + if (!arg_expect_reply) { + r = sd_bus_send(bus, m, NULL); + if (r < 0) { + log_error("Failed to send message."); + return r; + } + + return 0; + } + + r = sd_bus_call(bus, m, arg_timeout, &error, &reply); + if (r < 0) { + log_error("%s", bus_error_message(&error, r)); + return r; + } + + r = sd_bus_message_is_empty(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (r == 0 && !arg_quiet) { + + if (arg_verbose) { + pager_open_if_enabled(); + + r = bus_message_dump(reply, stdout, 0); + if (r < 0) + return r; + } else { + + fputs(sd_bus_message_get_signature(reply, true), stdout); + fputc(' ', stdout); + + r = format_cmdline(reply, stdout, false); + if (r < 0) + return bus_log_parse_error(r); + + fputc('\n', stdout); + } + } + + return 0; +} + +static int get_property(sd_bus *bus, char *argv[]) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + unsigned n; + char **i; + int r; + + assert(bus); + + n = strv_length(argv); + if (n < 5) { + log_error("Expects at least four arguments."); + return -EINVAL; + } + + STRV_FOREACH(i, argv + 4) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *contents = NULL; + char type; + + r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Properties", "Get", &error, &reply, "ss", argv[3], *i); + if (r < 0) { + log_error("%s", bus_error_message(&error, r)); + return r; + } + + r = sd_bus_message_peek_type(reply, &type, &contents); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, 'v', contents); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_verbose) { + pager_open_if_enabled(); + + r = bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_SUBTREE_ONLY); + if (r < 0) + return r; + } else { + fputs(contents, stdout); + fputc(' ', stdout); + + r = format_cmdline(reply, stdout, false); + if (r < 0) + return bus_log_parse_error(r); + + fputc('\n', stdout); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + return 0; +} + +static int set_property(sd_bus *bus, char *argv[]) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + unsigned n; + char **p; + int r; + + assert(bus); + + n = strv_length(argv); + if (n < 6) { + log_error("Expects at least five arguments."); + return -EINVAL; + } + + r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2], "org.freedesktop.DBus.Properties", "Set"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "ss", argv[3], argv[4]); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'v', argv[5]); + if (r < 0) + return bus_log_create_error(r); + + p = argv+6; + r = message_append_cmdline(m, argv[5], &p); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + if (*p) { + log_error("Too many parameters for signature."); + return -EINVAL; + } + + r = sd_bus_call(bus, m, arg_timeout, &error, NULL); + if (r < 0) { + log_error("%s", bus_error_message(&error, r)); + return r; + } + + return 0; +} + +static int help(void) { + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Introspect the bus.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not show the headers and footers\n" + " --system Connect to system bus\n" + " --user Connect to user bus\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --address=ADDRESS Connect to bus specified by address\n" + " --show-machine Show machine ID column in list\n" + " --unique Only show unique names\n" + " --acquired Only show acquired names\n" + " --activatable Only show activatable names\n" + " --match=MATCH Only show matching messages\n" + " --size=SIZE Maximum length of captured packet\n" + " --list Don't show tree, but simple object path list\n" + " --quiet Don't show method call reply\n" + " --verbose Show result values in long format\n" + " --expect-reply=BOOL Expect a method call reply\n" + " --auto-start=BOOL Auto-start destination service\n" + " --allow-interactive-authorization=BOOL\n" + " Allow interactive authorization for operation\n" + " --timeout=SECS Maximum time to wait for method call completion\n" + " --augment-creds=BOOL Extend credential data with data read from /proc/$PID\n\n" + "Commands:\n" + " list List bus names\n" + " status [SERVICE] Show bus service, process or bus owner credentials\n" + " monitor [SERVICE...] Show bus traffic\n" + " capture [SERVICE...] Capture bus traffic as pcap\n" + " tree [SERVICE...] Show object tree of service\n" + " introspect SERVICE OBJECT [INTERFACE]\n" + " call SERVICE OBJECT INTERFACE METHOD [SIGNATURE [ARGUMENT...]]\n" + " Call a method\n" + " get-property SERVICE OBJECT INTERFACE PROPERTY...\n" + " Get property value\n" + " set-property SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT...\n" + " Set property value\n" + " help Show this help\n" + , program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_LEGEND, + ARG_SYSTEM, + ARG_USER, + ARG_ADDRESS, + ARG_MATCH, + ARG_SHOW_MACHINE, + ARG_UNIQUE, + ARG_ACQUIRED, + ARG_ACTIVATABLE, + ARG_SIZE, + ARG_LIST, + ARG_VERBOSE, + ARG_EXPECT_REPLY, + ARG_AUTO_START, + ARG_ALLOW_INTERACTIVE_AUTHORIZATION, + ARG_TIMEOUT, + ARG_AUGMENT_CREDS, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "address", required_argument, NULL, ARG_ADDRESS }, + { "show-machine", no_argument, NULL, ARG_SHOW_MACHINE }, + { "unique", no_argument, NULL, ARG_UNIQUE }, + { "acquired", no_argument, NULL, ARG_ACQUIRED }, + { "activatable", no_argument, NULL, ARG_ACTIVATABLE }, + { "match", required_argument, NULL, ARG_MATCH }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "size", required_argument, NULL, ARG_SIZE }, + { "list", no_argument, NULL, ARG_LIST }, + { "quiet", no_argument, NULL, 'q' }, + { "verbose", no_argument, NULL, ARG_VERBOSE }, + { "expect-reply", required_argument, NULL, ARG_EXPECT_REPLY }, + { "auto-start", required_argument, NULL, ARG_AUTO_START }, + { "allow-interactive-authorization", required_argument, NULL, ARG_ALLOW_INTERACTIVE_AUTHORIZATION }, + { "timeout", required_argument, NULL, ARG_TIMEOUT }, + { "augment-creds",required_argument, NULL, ARG_AUGMENT_CREDS}, + {}, + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hH:M:q", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(); + + case ARG_VERSION: + return version(); + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case ARG_USER: + arg_user = true; + break; + + case ARG_SYSTEM: + arg_user = false; + break; + + case ARG_ADDRESS: + arg_address = optarg; + break; + + case ARG_SHOW_MACHINE: + arg_show_machine = true; + break; + + case ARG_UNIQUE: + arg_unique = true; + break; + + case ARG_ACQUIRED: + arg_acquired = true; + break; + + case ARG_ACTIVATABLE: + arg_activatable = true; + break; + + case ARG_MATCH: + if (strv_extend(&arg_matches, optarg) < 0) + return log_oom(); + break; + + case ARG_SIZE: { + uint64_t sz; + + r = parse_size(optarg, 1024, &sz); + if (r < 0) { + log_error("Failed to parse size: %s", optarg); + return r; + } + + if ((uint64_t) (size_t) sz != sz) { + log_error("Size out of range."); + return -E2BIG; + } + + arg_snaplen = (size_t) sz; + break; + } + + case ARG_LIST: + arg_list = true; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_VERBOSE: + arg_verbose = true; + break; + + case ARG_EXPECT_REPLY: + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --expect-reply= parameter."); + return r; + } + + arg_expect_reply = !!r; + break; + + + case ARG_AUTO_START: + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --auto-start= parameter."); + return r; + } + + arg_auto_start = !!r; + break; + + + case ARG_ALLOW_INTERACTIVE_AUTHORIZATION: + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --allow-interactive-authorization= parameter."); + return r; + } + + arg_allow_interactive_authorization = !!r; + break; + + case ARG_TIMEOUT: + r = parse_sec(optarg, &arg_timeout); + if (r < 0) { + log_error("Failed to parse --timeout= parameter."); + return r; + } + + break; + + case ARG_AUGMENT_CREDS: + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --augment-creds= parameter."); + return r; + } + + arg_augment_creds = !!r; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int busctl_main(sd_bus *bus, int argc, char *argv[]) { + assert(bus); + + if (optind >= argc || + streq(argv[optind], "list")) + return list_bus_names(bus, argv + optind); + + if (streq(argv[optind], "monitor")) + return monitor(bus, argv + optind, message_dump); + + if (streq(argv[optind], "capture")) + return capture(bus, argv + optind); + + if (streq(argv[optind], "status")) + return status(bus, argv + optind); + + if (streq(argv[optind], "tree")) + return tree(bus, argv + optind); + + if (streq(argv[optind], "introspect")) + return introspect(bus, argv + optind); + + if (streq(argv[optind], "call")) + return call(bus, argv + optind); + + if (streq(argv[optind], "get-property")) + return get_property(bus, argv + optind); + + if (streq(argv[optind], "set-property")) + return set_property(bus, argv + optind); + + if (streq(argv[optind], "help")) + return help(); + + log_error("Unknown command '%s'", argv[optind]); + return -EINVAL; +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = sd_bus_new(&bus); + if (r < 0) { + log_error_errno(r, "Failed to allocate bus: %m"); + goto finish; + } + + if (streq_ptr(argv[optind], "monitor") || + streq_ptr(argv[optind], "capture")) { + + r = sd_bus_set_monitor(bus, true); + if (r < 0) { + log_error_errno(r, "Failed to set monitor mode: %m"); + goto finish; + } + + r = sd_bus_negotiate_creds(bus, true, _SD_BUS_CREDS_ALL); + if (r < 0) { + log_error_errno(r, "Failed to enable credentials: %m"); + goto finish; + } + + r = sd_bus_negotiate_timestamp(bus, true); + if (r < 0) { + log_error_errno(r, "Failed to enable timestamps: %m"); + goto finish; + } + + r = sd_bus_negotiate_fds(bus, true); + if (r < 0) { + log_error_errno(r, "Failed to enable fds: %m"); + goto finish; + } + } + + r = sd_bus_set_bus_client(bus, true); + if (r < 0) { + log_error_errno(r, "Failed to set bus client: %m"); + goto finish; + } + + if (arg_address) + r = sd_bus_set_address(bus, arg_address); + else { + switch (arg_transport) { + + case BUS_TRANSPORT_LOCAL: + if (arg_user) { + bus->is_user = true; + r = bus_set_address_user(bus); + } else { + bus->is_system = true; + r = bus_set_address_system(bus); + } + break; + + case BUS_TRANSPORT_REMOTE: + r = bus_set_address_system_remote(bus, arg_host); + break; + + case BUS_TRANSPORT_MACHINE: + r = bus_set_address_system_machine(bus, arg_host); + break; + + default: + assert_not_reached("Hmm, unknown transport type."); + } + } + if (r < 0) { + log_error_errno(r, "Failed to set address: %m"); + goto finish; + } + + r = sd_bus_start(bus); + if (r < 0) { + log_error_errno(r, "Failed to connect to bus: %m"); + goto finish; + } + + r = busctl_main(bus, argc, argv); + +finish: + pager_close(); + + strv_free(arg_matches); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/kdbus.h b/src/libsystemd/libsystemd-internal/sd-bus/kdbus.h new file mode 100644 index 0000000000..ecffc6b13c --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/kdbus.h @@ -0,0 +1,980 @@ +/* + * kdbus 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. + */ + +#ifndef _UAPI_KDBUS_H_ +#define _UAPI_KDBUS_H_ + +#include +#include + +#define KDBUS_IOCTL_MAGIC 0x95 +#define KDBUS_SRC_ID_KERNEL (0) +#define KDBUS_DST_ID_NAME (0) +#define KDBUS_MATCH_ID_ANY (~0ULL) +#define KDBUS_DST_ID_BROADCAST (~0ULL) +#define KDBUS_FLAG_NEGOTIATE (1ULL << 63) + +/** + * struct kdbus_notify_id_change - name registry change message + * @id: New or former owner of the name + * @flags: flags field from KDBUS_HELLO_* + * + * Sent from kernel to userspace when the owner or activator of + * a well-known name changes. + * + * Attached to: + * KDBUS_ITEM_ID_ADD + * KDBUS_ITEM_ID_REMOVE + */ +struct kdbus_notify_id_change { + __u64 id; + __u64 flags; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_notify_name_change - name registry change message + * @old_id: ID and flags of former owner of a name + * @new_id: ID and flags of new owner of a name + * @name: Well-known name + * + * Sent from kernel to userspace when the owner or activator of + * a well-known name changes. + * + * Attached to: + * KDBUS_ITEM_NAME_ADD + * KDBUS_ITEM_NAME_REMOVE + * KDBUS_ITEM_NAME_CHANGE + */ +struct kdbus_notify_name_change { + struct kdbus_notify_id_change old_id; + struct kdbus_notify_id_change new_id; + char name[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_creds - process credentials + * @uid: User ID + * @euid: Effective UID + * @suid: Saved UID + * @fsuid: Filesystem UID + * @gid: Group ID + * @egid: Effective GID + * @sgid: Saved GID + * @fsgid: Filesystem GID + * + * Attached to: + * KDBUS_ITEM_CREDS + */ +struct kdbus_creds { + __u64 uid; + __u64 euid; + __u64 suid; + __u64 fsuid; + __u64 gid; + __u64 egid; + __u64 sgid; + __u64 fsgid; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_pids - process identifiers + * @pid: Process ID + * @tid: Thread ID + * @ppid: Parent process ID + * + * The PID and TID of a process. + * + * Attached to: + * KDBUS_ITEM_PIDS + */ +struct kdbus_pids { + __u64 pid; + __u64 tid; + __u64 ppid; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_caps - process capabilities + * @last_cap: Highest currently known capability bit + * @caps: Variable number of 32-bit capabilities flags + * + * Contains a variable number of 32-bit capabilities flags. + * + * Attached to: + * KDBUS_ITEM_CAPS + */ +struct kdbus_caps { + __u32 last_cap; + __u32 caps[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_audit - audit information + * @sessionid: The audit session ID + * @loginuid: The audit login uid + * + * Attached to: + * KDBUS_ITEM_AUDIT + */ +struct kdbus_audit { + __u32 sessionid; + __u32 loginuid; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_timestamp + * @seqnum: Global per-domain message sequence number + * @monotonic_ns: Monotonic timestamp, in nanoseconds + * @realtime_ns: Realtime timestamp, in nanoseconds + * + * Attached to: + * KDBUS_ITEM_TIMESTAMP + */ +struct kdbus_timestamp { + __u64 seqnum; + __u64 monotonic_ns; + __u64 realtime_ns; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_vec - I/O vector for kdbus payload items + * @size: The size of the vector + * @address: Memory address of data buffer + * @offset: Offset in the in-message payload memory, + * relative to the message head + * + * Attached to: + * KDBUS_ITEM_PAYLOAD_VEC, KDBUS_ITEM_PAYLOAD_OFF + */ +struct kdbus_vec { + __u64 size; + union { + __u64 address; + __u64 offset; + }; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_bloom_parameter - bus-wide bloom parameters + * @size: Size of the bit field in bytes (m / 8) + * @n_hash: Number of hash functions used (k) + */ +struct kdbus_bloom_parameter { + __u64 size; + __u64 n_hash; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_bloom_filter - bloom filter containing n elements + * @generation: Generation of the element set in the filter + * @data: Bit field, multiple of 8 bytes + */ +struct kdbus_bloom_filter { + __u64 generation; + __u64 data[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_memfd - a kdbus memfd + * @start: The offset into the memfd where the segment starts + * @size: The size of the memfd segment + * @fd: The file descriptor number + * @__pad: Padding to ensure proper alignment and size + * + * Attached to: + * KDBUS_ITEM_PAYLOAD_MEMFD + */ +struct kdbus_memfd { + __u64 start; + __u64 size; + int fd; + __u32 __pad; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_name - a registered well-known name with its flags + * @flags: Flags from KDBUS_NAME_* + * @name: Well-known name + * + * Attached to: + * KDBUS_ITEM_OWNED_NAME + */ +struct kdbus_name { + __u64 flags; + char name[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_policy_access_type - permissions of a policy record + * @_KDBUS_POLICY_ACCESS_NULL: Uninitialized/invalid + * @KDBUS_POLICY_ACCESS_USER: Grant access to a uid + * @KDBUS_POLICY_ACCESS_GROUP: Grant access to gid + * @KDBUS_POLICY_ACCESS_WORLD: World-accessible + */ +enum kdbus_policy_access_type { + _KDBUS_POLICY_ACCESS_NULL, + KDBUS_POLICY_ACCESS_USER, + KDBUS_POLICY_ACCESS_GROUP, + KDBUS_POLICY_ACCESS_WORLD, +}; + +/** + * enum kdbus_policy_access_flags - mode flags + * @KDBUS_POLICY_OWN: Allow to own a well-known name + * Implies KDBUS_POLICY_TALK and KDBUS_POLICY_SEE + * @KDBUS_POLICY_TALK: Allow communication to a well-known name + * Implies KDBUS_POLICY_SEE + * @KDBUS_POLICY_SEE: Allow to see a well-known name + */ +enum kdbus_policy_type { + KDBUS_POLICY_SEE = 0, + KDBUS_POLICY_TALK, + KDBUS_POLICY_OWN, +}; + +/** + * struct kdbus_policy_access - policy access item + * @type: One of KDBUS_POLICY_ACCESS_* types + * @access: Access to grant + * @id: For KDBUS_POLICY_ACCESS_USER, the uid + * For KDBUS_POLICY_ACCESS_GROUP, the gid + */ +struct kdbus_policy_access { + __u64 type; /* USER, GROUP, WORLD */ + __u64 access; /* OWN, TALK, SEE */ + __u64 id; /* uid, gid, 0 */ +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_attach_flags - flags for metadata attachments + * @KDBUS_ATTACH_TIMESTAMP: Timestamp + * @KDBUS_ATTACH_CREDS: Credentials + * @KDBUS_ATTACH_PIDS: PIDs + * @KDBUS_ATTACH_AUXGROUPS: Auxiliary groups + * @KDBUS_ATTACH_NAMES: Well-known names + * @KDBUS_ATTACH_TID_COMM: The "comm" process identifier of the TID + * @KDBUS_ATTACH_PID_COMM: The "comm" process identifier of the PID + * @KDBUS_ATTACH_EXE: The path of the executable + * @KDBUS_ATTACH_CMDLINE: The process command line + * @KDBUS_ATTACH_CGROUP: The croup membership + * @KDBUS_ATTACH_CAPS: The process capabilities + * @KDBUS_ATTACH_SECLABEL: The security label + * @KDBUS_ATTACH_AUDIT: The audit IDs + * @KDBUS_ATTACH_CONN_DESCRIPTION: The human-readable connection name + * @_KDBUS_ATTACH_ALL: All of the above + * @_KDBUS_ATTACH_ANY: Wildcard match to enable any kind of + * metatdata. + */ +enum kdbus_attach_flags { + KDBUS_ATTACH_TIMESTAMP = 1ULL << 0, + KDBUS_ATTACH_CREDS = 1ULL << 1, + KDBUS_ATTACH_PIDS = 1ULL << 2, + KDBUS_ATTACH_AUXGROUPS = 1ULL << 3, + KDBUS_ATTACH_NAMES = 1ULL << 4, + KDBUS_ATTACH_TID_COMM = 1ULL << 5, + KDBUS_ATTACH_PID_COMM = 1ULL << 6, + KDBUS_ATTACH_EXE = 1ULL << 7, + KDBUS_ATTACH_CMDLINE = 1ULL << 8, + KDBUS_ATTACH_CGROUP = 1ULL << 9, + KDBUS_ATTACH_CAPS = 1ULL << 10, + KDBUS_ATTACH_SECLABEL = 1ULL << 11, + KDBUS_ATTACH_AUDIT = 1ULL << 12, + KDBUS_ATTACH_CONN_DESCRIPTION = 1ULL << 13, + _KDBUS_ATTACH_ALL = (1ULL << 14) - 1, + _KDBUS_ATTACH_ANY = ~0ULL +}; + +/** + * enum kdbus_item_type - item types to chain data in a list + * @_KDBUS_ITEM_NULL: Uninitialized/invalid + * @_KDBUS_ITEM_USER_BASE: Start of user items + * @KDBUS_ITEM_NEGOTIATE: Negotiate supported items + * @KDBUS_ITEM_PAYLOAD_VEC: Vector to data + * @KDBUS_ITEM_PAYLOAD_OFF: Data at returned offset to message head + * @KDBUS_ITEM_PAYLOAD_MEMFD: Data as sealed memfd + * @KDBUS_ITEM_FDS: Attached file descriptors + * @KDBUS_ITEM_CANCEL_FD: FD used to cancel a synchronous + * operation by writing to it from + * userspace + * @KDBUS_ITEM_BLOOM_PARAMETER: Bus-wide bloom parameters, used with + * KDBUS_CMD_BUS_MAKE, carries a + * struct kdbus_bloom_parameter + * @KDBUS_ITEM_BLOOM_FILTER: Bloom filter carried with a message, + * used to match against a bloom mask of a + * connection, carries a struct + * kdbus_bloom_filter + * @KDBUS_ITEM_BLOOM_MASK: Bloom mask used to match against a + * message'sbloom filter + * @KDBUS_ITEM_DST_NAME: Destination's well-known name + * @KDBUS_ITEM_MAKE_NAME: Name of domain, bus, endpoint + * @KDBUS_ITEM_ATTACH_FLAGS_SEND: Attach-flags, used for updating which + * metadata a connection opts in to send + * @KDBUS_ITEM_ATTACH_FLAGS_RECV: Attach-flags, used for updating which + * metadata a connection requests to + * receive for each reeceived message + * @KDBUS_ITEM_ID: Connection ID + * @KDBUS_ITEM_NAME: Well-know name with flags + * @_KDBUS_ITEM_ATTACH_BASE: Start of metadata attach items + * @KDBUS_ITEM_TIMESTAMP: Timestamp + * @KDBUS_ITEM_CREDS: Process credentials + * @KDBUS_ITEM_PIDS: Process identifiers + * @KDBUS_ITEM_AUXGROUPS: Auxiliary process groups + * @KDBUS_ITEM_OWNED_NAME: A name owned by the associated + * connection + * @KDBUS_ITEM_TID_COMM: Thread ID "comm" identifier + * (Don't trust this, see below.) + * @KDBUS_ITEM_PID_COMM: Process ID "comm" identifier + * (Don't trust this, see below.) + * @KDBUS_ITEM_EXE: The path of the executable + * (Don't trust this, see below.) + * @KDBUS_ITEM_CMDLINE: The process command line + * (Don't trust this, see below.) + * @KDBUS_ITEM_CGROUP: The croup membership + * @KDBUS_ITEM_CAPS: The process capabilities + * @KDBUS_ITEM_SECLABEL: The security label + * @KDBUS_ITEM_AUDIT: The audit IDs + * @KDBUS_ITEM_CONN_DESCRIPTION: The connection's human-readable name + * (debugging) + * @_KDBUS_ITEM_POLICY_BASE: Start of policy items + * @KDBUS_ITEM_POLICY_ACCESS: Policy access block + * @_KDBUS_ITEM_KERNEL_BASE: Start of kernel-generated message items + * @KDBUS_ITEM_NAME_ADD: Notification in kdbus_notify_name_change + * @KDBUS_ITEM_NAME_REMOVE: Notification in kdbus_notify_name_change + * @KDBUS_ITEM_NAME_CHANGE: Notification in kdbus_notify_name_change + * @KDBUS_ITEM_ID_ADD: Notification in kdbus_notify_id_change + * @KDBUS_ITEM_ID_REMOVE: Notification in kdbus_notify_id_change + * @KDBUS_ITEM_REPLY_TIMEOUT: Timeout has been reached + * @KDBUS_ITEM_REPLY_DEAD: Destination died + * + * N.B: The process and thread COMM fields, as well as the CMDLINE and + * EXE fields may be altered by unprivileged processes und should + * hence *not* used for security decisions. Peers should make use of + * these items only for informational purposes, such as generating log + * records. + */ +enum kdbus_item_type { + _KDBUS_ITEM_NULL, + _KDBUS_ITEM_USER_BASE, + KDBUS_ITEM_NEGOTIATE = _KDBUS_ITEM_USER_BASE, + KDBUS_ITEM_PAYLOAD_VEC, + KDBUS_ITEM_PAYLOAD_OFF, + KDBUS_ITEM_PAYLOAD_MEMFD, + KDBUS_ITEM_FDS, + KDBUS_ITEM_CANCEL_FD, + KDBUS_ITEM_BLOOM_PARAMETER, + KDBUS_ITEM_BLOOM_FILTER, + KDBUS_ITEM_BLOOM_MASK, + KDBUS_ITEM_DST_NAME, + KDBUS_ITEM_MAKE_NAME, + KDBUS_ITEM_ATTACH_FLAGS_SEND, + KDBUS_ITEM_ATTACH_FLAGS_RECV, + KDBUS_ITEM_ID, + KDBUS_ITEM_NAME, + KDBUS_ITEM_DST_ID, + + /* keep these item types in sync with KDBUS_ATTACH_* flags */ + _KDBUS_ITEM_ATTACH_BASE = 0x1000, + KDBUS_ITEM_TIMESTAMP = _KDBUS_ITEM_ATTACH_BASE, + KDBUS_ITEM_CREDS, + KDBUS_ITEM_PIDS, + KDBUS_ITEM_AUXGROUPS, + KDBUS_ITEM_OWNED_NAME, + KDBUS_ITEM_TID_COMM, + KDBUS_ITEM_PID_COMM, + KDBUS_ITEM_EXE, + KDBUS_ITEM_CMDLINE, + KDBUS_ITEM_CGROUP, + KDBUS_ITEM_CAPS, + KDBUS_ITEM_SECLABEL, + KDBUS_ITEM_AUDIT, + KDBUS_ITEM_CONN_DESCRIPTION, + + _KDBUS_ITEM_POLICY_BASE = 0x2000, + KDBUS_ITEM_POLICY_ACCESS = _KDBUS_ITEM_POLICY_BASE, + + _KDBUS_ITEM_KERNEL_BASE = 0x8000, + KDBUS_ITEM_NAME_ADD = _KDBUS_ITEM_KERNEL_BASE, + KDBUS_ITEM_NAME_REMOVE, + KDBUS_ITEM_NAME_CHANGE, + KDBUS_ITEM_ID_ADD, + KDBUS_ITEM_ID_REMOVE, + KDBUS_ITEM_REPLY_TIMEOUT, + KDBUS_ITEM_REPLY_DEAD, +}; + +/** + * struct kdbus_item - chain of data blocks + * @size: Overall data record size + * @type: Kdbus_item type of data + * @data: Generic bytes + * @data32: Generic 32 bit array + * @data64: Generic 64 bit array + * @str: Generic string + * @id: Connection ID + * @vec: KDBUS_ITEM_PAYLOAD_VEC + * @creds: KDBUS_ITEM_CREDS + * @audit: KDBUS_ITEM_AUDIT + * @timestamp: KDBUS_ITEM_TIMESTAMP + * @name: KDBUS_ITEM_NAME + * @bloom_parameter: KDBUS_ITEM_BLOOM_PARAMETER + * @bloom_filter: KDBUS_ITEM_BLOOM_FILTER + * @memfd: KDBUS_ITEM_PAYLOAD_MEMFD + * @name_change: KDBUS_ITEM_NAME_ADD + * KDBUS_ITEM_NAME_REMOVE + * KDBUS_ITEM_NAME_CHANGE + * @id_change: KDBUS_ITEM_ID_ADD + * KDBUS_ITEM_ID_REMOVE + * @policy: KDBUS_ITEM_POLICY_ACCESS + */ +struct kdbus_item { + __u64 size; + __u64 type; + union { + __u8 data[0]; + __u32 data32[0]; + __u64 data64[0]; + char str[0]; + + __u64 id; + struct kdbus_vec vec; + struct kdbus_creds creds; + struct kdbus_pids pids; + struct kdbus_audit audit; + struct kdbus_caps caps; + struct kdbus_timestamp timestamp; + struct kdbus_name name; + struct kdbus_bloom_parameter bloom_parameter; + struct kdbus_bloom_filter bloom_filter; + struct kdbus_memfd memfd; + int fds[0]; + struct kdbus_notify_name_change name_change; + struct kdbus_notify_id_change id_change; + struct kdbus_policy_access policy_access; + }; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_msg_flags - type of message + * @KDBUS_MSG_EXPECT_REPLY: Expect a reply message, used for + * method calls. The userspace-supplied + * cookie identifies the message and the + * respective reply carries the cookie + * in cookie_reply + * @KDBUS_MSG_NO_AUTO_START: Do not start a service if the addressed + * name is not currently active. This flag is + * not looked at by the kernel but only + * serves as hint for userspace implementations. + * @KDBUS_MSG_SIGNAL: Treat this message as signal + */ +enum kdbus_msg_flags { + KDBUS_MSG_EXPECT_REPLY = 1ULL << 0, + KDBUS_MSG_NO_AUTO_START = 1ULL << 1, + KDBUS_MSG_SIGNAL = 1ULL << 2, +}; + +/** + * enum kdbus_payload_type - type of payload carried by message + * @KDBUS_PAYLOAD_KERNEL: Kernel-generated simple message + * @KDBUS_PAYLOAD_DBUS: D-Bus marshalling "DBusDBus" + * + * Any payload-type is accepted. Common types will get added here once + * established. + */ +enum kdbus_payload_type { + KDBUS_PAYLOAD_KERNEL, + KDBUS_PAYLOAD_DBUS = 0x4442757344427573ULL, +}; + +/** + * struct kdbus_msg - the representation of a kdbus message + * @size: Total size of the message + * @flags: Message flags (KDBUS_MSG_*), userspace → kernel + * @priority: Message queue priority value + * @dst_id: 64-bit ID of the destination connection + * @src_id: 64-bit ID of the source connection + * @payload_type: Payload type (KDBUS_PAYLOAD_*) + * @cookie: Userspace-supplied cookie, for the connection + * to identify its messages + * @timeout_ns: The time to wait for a message reply from the peer. + * If there is no reply, and the send command is + * executed asynchronously, a kernel-generated message + * with an attached KDBUS_ITEM_REPLY_TIMEOUT item + * is sent to @src_id. For synchronously executed send + * command, the value denotes the maximum time the call + * blocks to wait for a reply. The timeout is expected in + * nanoseconds and as absolute CLOCK_MONOTONIC value. + * @cookie_reply: A reply to the requesting message with the same + * cookie. The requesting connection can match its + * request and the reply with this value + * @items: A list of kdbus_items containing the message payload + */ +struct kdbus_msg { + __u64 size; + __u64 flags; + __s64 priority; + __u64 dst_id; + __u64 src_id; + __u64 payload_type; + __u64 cookie; + union { + __u64 timeout_ns; + __u64 cookie_reply; + }; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_msg_info - returned message container + * @offset: Offset of kdbus_msg slice in pool + * @msg_size: Copy of the kdbus_msg.size field + * @return_flags: Command return flags, kernel → userspace + */ +struct kdbus_msg_info { + __u64 offset; + __u64 msg_size; + __u64 return_flags; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_send_flags - flags for sending messages + * @KDBUS_SEND_SYNC_REPLY: Wait for destination connection to + * reply to this message. The + * KDBUS_CMD_SEND ioctl() will block + * until the reply is received, and + * reply in struct kdbus_cmd_send will + * yield the offset in the sender's pool + * where the reply can be found. + * This flag is only valid if + * @KDBUS_MSG_EXPECT_REPLY is set as well. + */ +enum kdbus_send_flags { + KDBUS_SEND_SYNC_REPLY = 1ULL << 0, +}; + +/** + * struct kdbus_cmd_send - send message + * @size: Overall size of this structure + * @flags: Flags to change send behavior (KDBUS_SEND_*) + * @return_flags: Command return flags, kernel → userspace + * @msg_address: Storage address of the kdbus_msg to send + * @reply: Storage for message reply if KDBUS_SEND_SYNC_REPLY + * was given + * @items: Additional items for this command + */ +struct kdbus_cmd_send { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 msg_address; + struct kdbus_msg_info reply; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_recv_flags - flags for de-queuing messages + * @KDBUS_RECV_PEEK: Return the next queued message without + * actually de-queuing it, and without installing + * any file descriptors or other resources. It is + * usually used to determine the activating + * connection of a bus name. + * @KDBUS_RECV_DROP: Drop and free the next queued message and all + * its resources without actually receiving it. + * @KDBUS_RECV_USE_PRIORITY: Only de-queue messages with the specified or + * higher priority (lowest values); if not set, + * the priority value is ignored. + */ +enum kdbus_recv_flags { + KDBUS_RECV_PEEK = 1ULL << 0, + KDBUS_RECV_DROP = 1ULL << 1, + KDBUS_RECV_USE_PRIORITY = 1ULL << 2, +}; + +/** + * enum kdbus_recv_return_flags - return flags for message receive commands + * @KDBUS_RECV_RETURN_INCOMPLETE_FDS: One or more file descriptors could not + * be installed. These descriptors in + * KDBUS_ITEM_FDS will carry the value -1. + * @KDBUS_RECV_RETURN_DROPPED_MSGS: There have been dropped messages since + * the last time a message was received. + * The 'dropped_msgs' counter contains the + * number of messages dropped pool + * overflows or other missed broadcasts. + */ +enum kdbus_recv_return_flags { + KDBUS_RECV_RETURN_INCOMPLETE_FDS = 1ULL << 0, + KDBUS_RECV_RETURN_DROPPED_MSGS = 1ULL << 1, +}; + +/** + * struct kdbus_cmd_recv - struct to de-queue a buffered message + * @size: Overall size of this object + * @flags: KDBUS_RECV_* flags, userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @priority: Minimum priority of the messages to de-queue. Lowest + * values have the highest priority. + * @dropped_msgs: In case there were any dropped messages since the last + * time a message was received, this will be set to the + * number of lost messages and + * KDBUS_RECV_RETURN_DROPPED_MSGS will be set in + * 'return_flags'. This can only happen if the ioctl + * returns 0 or EAGAIN. + * @msg: Return storage for received message. + * @items: Additional items for this command. + * + * This struct is used with the KDBUS_CMD_RECV ioctl. + */ +struct kdbus_cmd_recv { + __u64 size; + __u64 flags; + __u64 return_flags; + __s64 priority; + __u64 dropped_msgs; + struct kdbus_msg_info msg; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_cmd_free - struct to free a slice of memory in the pool + * @size: Overall size of this structure + * @flags: Flags for the free command, userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @offset: The offset of the memory slice, as returned by other + * ioctls + * @items: Additional items to modify the behavior + * + * This struct is used with the KDBUS_CMD_FREE ioctl. + */ +struct kdbus_cmd_free { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 offset; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_hello_flags - flags for struct kdbus_cmd_hello + * @KDBUS_HELLO_ACCEPT_FD: The connection allows the reception of + * any passed file descriptors + * @KDBUS_HELLO_ACTIVATOR: Special-purpose connection which registers + * a well-know name for a process to be started + * when traffic arrives + * @KDBUS_HELLO_POLICY_HOLDER: Special-purpose connection which registers + * policy entries for a name. The provided name + * is not activated and not registered with the + * name database, it only allows unprivileged + * connections to acquire a name, talk or discover + * a service + * @KDBUS_HELLO_MONITOR: Special-purpose connection to monitor + * bus traffic + */ +enum kdbus_hello_flags { + KDBUS_HELLO_ACCEPT_FD = 1ULL << 0, + KDBUS_HELLO_ACTIVATOR = 1ULL << 1, + KDBUS_HELLO_POLICY_HOLDER = 1ULL << 2, + KDBUS_HELLO_MONITOR = 1ULL << 3, +}; + +/** + * struct kdbus_cmd_hello - struct to say hello to kdbus + * @size: The total size of the structure + * @flags: Connection flags (KDBUS_HELLO_*), userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @attach_flags_send: Mask of metadata to attach to each message sent + * off by this connection (KDBUS_ATTACH_*) + * @attach_flags_recv: Mask of metadata to attach to each message receieved + * by the new connection (KDBUS_ATTACH_*) + * @bus_flags: The flags field copied verbatim from the original + * KDBUS_CMD_BUS_MAKE ioctl. It's intended to be useful + * to do negotiation of features of the payload that is + * transferred (kernel → userspace) + * @id: The ID of this connection (kernel → userspace) + * @pool_size: Size of the connection's buffer where the received + * messages are placed + * @offset: Pool offset where items are returned to report + * additional information about the bus and the newly + * created connection. + * @items_size: Size of buffer returned in the pool slice at @offset. + * @id128: Unique 128-bit ID of the bus (kernel → userspace) + * @items: A list of items + * + * This struct is used with the KDBUS_CMD_HELLO ioctl. + */ +struct kdbus_cmd_hello { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 attach_flags_send; + __u64 attach_flags_recv; + __u64 bus_flags; + __u64 id; + __u64 pool_size; + __u64 offset; + __u64 items_size; + __u8 id128[16]; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_info - connection information + * @size: total size of the struct + * @id: 64bit object ID + * @flags: object creation flags + * @items: list of items + * + * Note that the user is responsible for freeing the allocated memory with + * the KDBUS_CMD_FREE ioctl. + */ +struct kdbus_info { + __u64 size; + __u64 id; + __u64 flags; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_list_flags - what to include into the returned list + * @KDBUS_LIST_UNIQUE: active connections + * @KDBUS_LIST_ACTIVATORS: activator connections + * @KDBUS_LIST_NAMES: known well-known names + * @KDBUS_LIST_QUEUED: queued-up names + */ +enum kdbus_list_flags { + KDBUS_LIST_UNIQUE = 1ULL << 0, + KDBUS_LIST_NAMES = 1ULL << 1, + KDBUS_LIST_ACTIVATORS = 1ULL << 2, + KDBUS_LIST_QUEUED = 1ULL << 3, +}; + +/** + * struct kdbus_cmd_list - list connections + * @size: overall size of this object + * @flags: flags for the query (KDBUS_LIST_*), userspace → kernel + * @return_flags: command return flags, kernel → userspace + * @offset: Offset in the caller's pool buffer where an array of + * kdbus_info objects is stored. + * The user must use KDBUS_CMD_FREE to free the + * allocated memory. + * @list_size: size of returned list in bytes + * @items: Items for the command. Reserved for future use. + * + * This structure is used with the KDBUS_CMD_LIST ioctl. + */ +struct kdbus_cmd_list { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 offset; + __u64 list_size; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * struct kdbus_cmd_info - struct used for KDBUS_CMD_CONN_INFO ioctl + * @size: The total size of the struct + * @flags: Flags for this ioctl, userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @id: The 64-bit ID of the connection. If set to zero, passing + * @name is required. kdbus will look up the name to + * determine the ID in this case. + * @attach_flags: Set of attach flags to specify the set of information + * to receive, userspace → kernel + * @offset: Returned offset in the caller's pool buffer where the + * kdbus_info struct result is stored. The user must + * use KDBUS_CMD_FREE to free the allocated memory. + * @info_size: Output buffer to report size of data at @offset. + * @items: The optional item list, containing the + * well-known name to look up as a KDBUS_ITEM_NAME. + * Only needed in case @id is zero. + * + * On success, the KDBUS_CMD_CONN_INFO ioctl will return 0 and @offset will + * tell the user the offset in the connection pool buffer at which to find the + * result in a struct kdbus_info. + */ +struct kdbus_cmd_info { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 id; + __u64 attach_flags; + __u64 offset; + __u64 info_size; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_cmd_match_flags - flags to control the KDBUS_CMD_MATCH_ADD ioctl + * @KDBUS_MATCH_REPLACE: If entries with the supplied cookie already + * exists, remove them before installing the new + * matches. + */ +enum kdbus_cmd_match_flags { + KDBUS_MATCH_REPLACE = 1ULL << 0, +}; + +/** + * struct kdbus_cmd_match - struct to add or remove matches + * @size: The total size of the struct + * @flags: Flags for match command (KDBUS_MATCH_*), + * userspace → kernel + * @return_flags: Command return flags, kernel → userspace + * @cookie: Userspace supplied cookie. When removing, the cookie + * identifies the match to remove + * @items: A list of items for additional information + * + * This structure is used with the KDBUS_CMD_MATCH_ADD and + * KDBUS_CMD_MATCH_REMOVE ioctl. + */ +struct kdbus_cmd_match { + __u64 size; + __u64 flags; + __u64 return_flags; + __u64 cookie; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * enum kdbus_make_flags - Flags for KDBUS_CMD_{BUS,ENDPOINT}_MAKE + * @KDBUS_MAKE_ACCESS_GROUP: Make the bus or endpoint node group-accessible + * @KDBUS_MAKE_ACCESS_WORLD: Make the bus or endpoint node world-accessible + */ +enum kdbus_make_flags { + KDBUS_MAKE_ACCESS_GROUP = 1ULL << 0, + KDBUS_MAKE_ACCESS_WORLD = 1ULL << 1, +}; + +/** + * enum kdbus_name_flags - flags for KDBUS_CMD_NAME_ACQUIRE + * @KDBUS_NAME_REPLACE_EXISTING: Try to replace name of other connections + * @KDBUS_NAME_ALLOW_REPLACEMENT: Allow the replacement of the name + * @KDBUS_NAME_QUEUE: Name should be queued if busy + * @KDBUS_NAME_IN_QUEUE: Name is queued + * @KDBUS_NAME_ACTIVATOR: Name is owned by a activator connection + */ +enum kdbus_name_flags { + KDBUS_NAME_REPLACE_EXISTING = 1ULL << 0, + KDBUS_NAME_ALLOW_REPLACEMENT = 1ULL << 1, + KDBUS_NAME_QUEUE = 1ULL << 2, + KDBUS_NAME_IN_QUEUE = 1ULL << 3, + KDBUS_NAME_ACTIVATOR = 1ULL << 4, +}; + +/** + * struct kdbus_cmd - generic ioctl payload + * @size: Overall size of this structure + * @flags: Flags for this ioctl, userspace → kernel + * @return_flags: Ioctl return flags, kernel → userspace + * @items: Additional items to modify the behavior + * + * This is a generic ioctl payload object. It's used by all ioctls that only + * take flags and items as input. + */ +struct kdbus_cmd { + __u64 size; + __u64 flags; + __u64 return_flags; + struct kdbus_item items[0]; +} __attribute__((__aligned__(8))); + +/** + * Ioctl API + * + * KDBUS_CMD_BUS_MAKE: After opening the "control" node, this command + * creates a new bus with the specified + * name. The bus is immediately shut down and + * cleaned up when the opened file descriptor is + * closed. + * + * KDBUS_CMD_ENDPOINT_MAKE: Creates a new named special endpoint to talk to + * the bus. Such endpoints usually carry a more + * restrictive policy and grant restricted access + * to specific applications. + * KDBUS_CMD_ENDPOINT_UPDATE: Update the properties of a custom enpoint. Used + * to update the policy. + * + * KDBUS_CMD_HELLO: By opening the bus node, a connection is + * created. After a HELLO the opened connection + * becomes an active peer on the bus. + * KDBUS_CMD_UPDATE: Update the properties of a connection. Used to + * update the metadata subscription mask and + * policy. + * KDBUS_CMD_BYEBYE: Disconnect a connection. If there are no + * messages queued up in the connection's pool, + * the call succeeds, and the handle is rendered + * unusable. Otherwise, -EBUSY is returned without + * any further side-effects. + * KDBUS_CMD_FREE: Release the allocated memory in the receiver's + * pool. + * KDBUS_CMD_CONN_INFO: Retrieve credentials and properties of the + * initial creator of the connection. The data was + * stored at registration time and does not + * necessarily represent the connected process or + * the actual state of the process. + * KDBUS_CMD_BUS_CREATOR_INFO: Retrieve information of the creator of the bus + * a connection is attached to. + * + * KDBUS_CMD_SEND: Send a message and pass data from userspace to + * the kernel. + * KDBUS_CMD_RECV: Receive a message from the kernel which is + * placed in the receiver's pool. + * + * KDBUS_CMD_NAME_ACQUIRE: Request a well-known bus name to associate with + * the connection. Well-known names are used to + * address a peer on the bus. + * KDBUS_CMD_NAME_RELEASE: Release a well-known name the connection + * currently owns. + * KDBUS_CMD_LIST: Retrieve the list of all currently registered + * well-known and unique names. + * + * KDBUS_CMD_MATCH_ADD: Install a match which broadcast messages should + * be delivered to the connection. + * KDBUS_CMD_MATCH_REMOVE: Remove a current match for broadcast messages. + */ +enum kdbus_ioctl_type { + /* bus owner (00-0f) */ + KDBUS_CMD_BUS_MAKE = _IOW(KDBUS_IOCTL_MAGIC, 0x00, + struct kdbus_cmd), + + /* endpoint owner (10-1f) */ + KDBUS_CMD_ENDPOINT_MAKE = _IOW(KDBUS_IOCTL_MAGIC, 0x10, + struct kdbus_cmd), + KDBUS_CMD_ENDPOINT_UPDATE = _IOW(KDBUS_IOCTL_MAGIC, 0x11, + struct kdbus_cmd), + + /* connection owner (80-ff) */ + KDBUS_CMD_HELLO = _IOWR(KDBUS_IOCTL_MAGIC, 0x80, + struct kdbus_cmd_hello), + KDBUS_CMD_UPDATE = _IOW(KDBUS_IOCTL_MAGIC, 0x81, + struct kdbus_cmd), + KDBUS_CMD_BYEBYE = _IOW(KDBUS_IOCTL_MAGIC, 0x82, + struct kdbus_cmd), + KDBUS_CMD_FREE = _IOW(KDBUS_IOCTL_MAGIC, 0x83, + struct kdbus_cmd_free), + KDBUS_CMD_CONN_INFO = _IOR(KDBUS_IOCTL_MAGIC, 0x84, + struct kdbus_cmd_info), + KDBUS_CMD_BUS_CREATOR_INFO = _IOR(KDBUS_IOCTL_MAGIC, 0x85, + struct kdbus_cmd_info), + KDBUS_CMD_LIST = _IOR(KDBUS_IOCTL_MAGIC, 0x86, + struct kdbus_cmd_list), + + KDBUS_CMD_SEND = _IOW(KDBUS_IOCTL_MAGIC, 0x90, + struct kdbus_cmd_send), + KDBUS_CMD_RECV = _IOR(KDBUS_IOCTL_MAGIC, 0x91, + struct kdbus_cmd_recv), + + KDBUS_CMD_NAME_ACQUIRE = _IOW(KDBUS_IOCTL_MAGIC, 0xa0, + struct kdbus_cmd), + KDBUS_CMD_NAME_RELEASE = _IOW(KDBUS_IOCTL_MAGIC, 0xa1, + struct kdbus_cmd), + + KDBUS_CMD_MATCH_ADD = _IOW(KDBUS_IOCTL_MAGIC, 0xb0, + struct kdbus_cmd_match), + KDBUS_CMD_MATCH_REMOVE = _IOW(KDBUS_IOCTL_MAGIC, 0xb1, + struct kdbus_cmd_match), +}; + +#endif /* _UAPI_KDBUS_H_ */ diff --git a/src/libsystemd/libsystemd-internal/sd-bus/sd-bus.c b/src/libsystemd/libsystemd-internal/sd-bus/sd-bus.c new file mode 100644 index 0000000000..850949fa25 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/sd-bus.c @@ -0,0 +1,3794 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-container.h" +#include "bus-control.h" +#include "bus-internal.h" +#include "bus-kernel.h" +#include "bus-label.h" +#include "bus-message.h" +#include "bus-objects.h" +#include "bus-protocol.h" +#include "bus-slot.h" +#include "bus-socket.h" +#include "bus-track.h" +#include "bus-type.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "def.h" +#include "fd-util.h" +#include "hexdecoct.h" +#include "hostname-util.h" +#include "macro.h" +#include "missing.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +#define log_debug_bus_message(m) \ + do { \ + sd_bus_message *_mm = (m); \ + log_debug("Got message type=%s sender=%s destination=%s object=%s interface=%s member=%s cookie=%" PRIu64 " reply_cookie=%" PRIu64 " error=%s", \ + bus_message_type_to_string(_mm->header->type), \ + strna(sd_bus_message_get_sender(_mm)), \ + strna(sd_bus_message_get_destination(_mm)), \ + strna(sd_bus_message_get_path(_mm)), \ + strna(sd_bus_message_get_interface(_mm)), \ + strna(sd_bus_message_get_member(_mm)), \ + BUS_MESSAGE_COOKIE(_mm), \ + _mm->reply_cookie, \ + strna(_mm->error.message)); \ + } while (false) + +static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec); +static int attach_io_events(sd_bus *b); +static void detach_io_events(sd_bus *b); + +static thread_local sd_bus *default_system_bus = NULL; +static thread_local sd_bus *default_user_bus = NULL; +static thread_local sd_bus *default_starter_bus = NULL; + +static void bus_close_fds(sd_bus *b) { + assert(b); + + detach_io_events(b); + + if (b->input_fd != b->output_fd) + safe_close(b->output_fd); + b->output_fd = b->input_fd = safe_close(b->input_fd); +} + +static void bus_reset_queues(sd_bus *b) { + assert(b); + + while (b->rqueue_size > 0) + sd_bus_message_unref(b->rqueue[--b->rqueue_size]); + + b->rqueue = mfree(b->rqueue); + b->rqueue_allocated = 0; + + while (b->wqueue_size > 0) + sd_bus_message_unref(b->wqueue[--b->wqueue_size]); + + b->wqueue = mfree(b->wqueue); + b->wqueue_allocated = 0; +} + +static void bus_free(sd_bus *b) { + sd_bus_slot *s; + + assert(b); + assert(!b->track_queue); + + b->state = BUS_CLOSED; + + sd_bus_detach_event(b); + + while ((s = b->slots)) { + /* At this point only floating slots can still be + * around, because the non-floating ones keep a + * reference to the bus, and we thus couldn't be + * destructing right now... We forcibly disconnect the + * slots here, so that they still can be referenced by + * apps, but are dead. */ + + assert(s->floating); + bus_slot_disconnect(s); + sd_bus_slot_unref(s); + } + + if (b->default_bus_ptr) + *b->default_bus_ptr = NULL; + + bus_close_fds(b); + + if (b->kdbus_buffer) + munmap(b->kdbus_buffer, KDBUS_POOL_SIZE); + + free(b->label); + free(b->rbuffer); + free(b->unique_name); + free(b->auth_buffer); + free(b->address); + free(b->kernel); + free(b->machine); + free(b->fake_label); + free(b->cgroup_root); + free(b->description); + + free(b->exec_path); + strv_free(b->exec_argv); + + close_many(b->fds, b->n_fds); + free(b->fds); + + bus_reset_queues(b); + + ordered_hashmap_free_free(b->reply_callbacks); + prioq_free(b->reply_callbacks_prioq); + + assert(b->match_callbacks.type == BUS_MATCH_ROOT); + bus_match_free(&b->match_callbacks); + + hashmap_free_free(b->vtable_methods); + hashmap_free_free(b->vtable_properties); + + assert(hashmap_isempty(b->nodes)); + hashmap_free(b->nodes); + + bus_kernel_flush_memfd(b); + + assert_se(pthread_mutex_destroy(&b->memfd_cache_mutex) == 0); + + free(b); +} + +_public_ int sd_bus_new(sd_bus **ret) { + sd_bus *r; + + assert_return(ret, -EINVAL); + + r = new0(sd_bus, 1); + if (!r) + return -ENOMEM; + + r->n_ref = REFCNT_INIT; + r->input_fd = r->output_fd = -1; + r->message_version = 1; + r->creds_mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME; + r->hello_flags |= KDBUS_HELLO_ACCEPT_FD; + r->attach_flags |= KDBUS_ATTACH_NAMES; + r->original_pid = getpid(); + + assert_se(pthread_mutex_init(&r->memfd_cache_mutex, NULL) == 0); + + /* We guarantee that wqueue always has space for at least one + * entry */ + if (!GREEDY_REALLOC(r->wqueue, r->wqueue_allocated, 1)) { + free(r); + return -ENOMEM; + } + + *ret = r; + return 0; +} + +_public_ int sd_bus_set_address(sd_bus *bus, const char *address) { + char *a; + + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(address, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + a = strdup(address); + if (!a) + return -ENOMEM; + + free(bus->address); + bus->address = a; + + return 0; +} + +_public_ int sd_bus_set_fd(sd_bus *bus, int input_fd, int output_fd) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(input_fd >= 0, -EBADF); + assert_return(output_fd >= 0, -EBADF); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->input_fd = input_fd; + bus->output_fd = output_fd; + return 0; +} + +_public_ int sd_bus_set_exec(sd_bus *bus, const char *path, char *const argv[]) { + char *p, **a; + + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(path, -EINVAL); + assert_return(!strv_isempty(argv), -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + p = strdup(path); + if (!p) + return -ENOMEM; + + a = strv_copy(argv); + if (!a) { + free(p); + return -ENOMEM; + } + + free(bus->exec_path); + strv_free(bus->exec_argv); + + bus->exec_path = p; + bus->exec_argv = a; + + return 0; +} + +_public_ int sd_bus_set_bus_client(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->bus_client = !!b; + return 0; +} + +_public_ int sd_bus_set_monitor(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + SET_FLAG(bus->hello_flags, KDBUS_HELLO_MONITOR, b); + return 0; +} + +_public_ int sd_bus_negotiate_fds(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + SET_FLAG(bus->hello_flags, KDBUS_HELLO_ACCEPT_FD, b); + return 0; +} + +_public_ int sd_bus_negotiate_timestamp(sd_bus *bus, int b) { + uint64_t new_flags; + assert_return(bus, -EINVAL); + assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + new_flags = bus->attach_flags; + SET_FLAG(new_flags, KDBUS_ATTACH_TIMESTAMP, b); + + if (bus->attach_flags == new_flags) + return 0; + + bus->attach_flags = new_flags; + if (bus->state != BUS_UNSET && bus->is_kernel) + bus_kernel_realize_attach_flags(bus); + + return 0; +} + +_public_ int sd_bus_negotiate_creds(sd_bus *bus, int b, uint64_t mask) { + uint64_t new_flags; + + assert_return(bus, -EINVAL); + assert_return(mask <= _SD_BUS_CREDS_ALL, -EINVAL); + assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (b) + bus->creds_mask |= mask; + else + bus->creds_mask &= ~mask; + + /* The well knowns we need unconditionally, so that matches can work */ + bus->creds_mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME; + + /* Make sure we don't lose the timestamp flag */ + new_flags = (bus->attach_flags & KDBUS_ATTACH_TIMESTAMP) | attach_flags_to_kdbus(bus->creds_mask); + if (bus->attach_flags == new_flags) + return 0; + + bus->attach_flags = new_flags; + if (bus->state != BUS_UNSET && bus->is_kernel) + bus_kernel_realize_attach_flags(bus); + + return 0; +} + +_public_ int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t server_id) { + assert_return(bus, -EINVAL); + assert_return(b || sd_id128_equal(server_id, SD_ID128_NULL), -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->is_server = !!b; + bus->server_id = server_id; + return 0; +} + +_public_ int sd_bus_set_anonymous(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->anonymous_auth = !!b; + return 0; +} + +_public_ int sd_bus_set_trusted(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->trusted = !!b; + return 0; +} + +_public_ int sd_bus_set_description(sd_bus *bus, const char *description) { + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return free_and_strdup(&bus->description, description); +} + +_public_ int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->allow_interactive_authorization = !!b; + return 0; +} + +_public_ int sd_bus_get_allow_interactive_authorization(sd_bus *bus) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return bus->allow_interactive_authorization; +} + +static int hello_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) { + const char *s; + sd_bus *bus; + int r; + + assert(reply); + bus = reply->bus; + assert(bus); + assert(bus->state == BUS_HELLO || bus->state == BUS_CLOSING); + + r = sd_bus_message_get_errno(reply); + if (r > 0) + return -r; + + r = sd_bus_message_read(reply, "s", &s); + if (r < 0) + return r; + + if (!service_name_is_valid(s) || s[0] != ':') + return -EBADMSG; + + bus->unique_name = strdup(s); + if (!bus->unique_name) + return -ENOMEM; + + if (bus->state == BUS_HELLO) + bus->state = BUS_RUNNING; + + return 1; +} + +static int bus_send_hello(sd_bus *bus) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(bus); + + if (!bus->bus_client || bus->is_kernel) + return 0; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "Hello"); + if (r < 0) + return r; + + return sd_bus_call_async(bus, NULL, m, hello_callback, NULL, 0); +} + +int bus_start_running(sd_bus *bus) { + assert(bus); + + if (bus->bus_client && !bus->is_kernel) { + bus->state = BUS_HELLO; + return 1; + } + + bus->state = BUS_RUNNING; + return 1; +} + +static int parse_address_key(const char **p, const char *key, char **value) { + size_t l, n = 0, allocated = 0; + const char *a; + char *r = NULL; + + assert(p); + assert(*p); + assert(value); + + if (key) { + l = strlen(key); + if (strncmp(*p, key, l) != 0) + return 0; + + if ((*p)[l] != '=') + return 0; + + if (*value) + return -EINVAL; + + a = *p + l + 1; + } else + a = *p; + + while (*a != ';' && *a != ',' && *a != 0) { + char c; + + if (*a == '%') { + int x, y; + + x = unhexchar(a[1]); + if (x < 0) { + free(r); + return x; + } + + y = unhexchar(a[2]); + if (y < 0) { + free(r); + return y; + } + + c = (char) ((x << 4) | y); + a += 3; + } else { + c = *a; + a++; + } + + if (!GREEDY_REALLOC(r, allocated, n + 2)) + return -ENOMEM; + + r[n++] = c; + } + + if (!r) { + r = strdup(""); + if (!r) + return -ENOMEM; + } else + r[n] = 0; + + if (*a == ',') + a++; + + *p = a; + + free(*value); + *value = r; + + return 1; +} + +static void skip_address_key(const char **p) { + assert(p); + assert(*p); + + *p += strcspn(*p, ","); + + if (**p == ',') + (*p) ++; +} + +static int parse_unix_address(sd_bus *b, const char **p, char **guid) { + _cleanup_free_ char *path = NULL, *abstract = NULL; + size_t l; + int r; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "path", &path); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "abstract", &abstract); + if (r < 0) + return r; + else if (r > 0) + continue; + + skip_address_key(p); + } + + if (!path && !abstract) + return -EINVAL; + + if (path && abstract) + return -EINVAL; + + if (path) { + l = strlen(path); + if (l > sizeof(b->sockaddr.un.sun_path)) + return -E2BIG; + + b->sockaddr.un.sun_family = AF_UNIX; + strncpy(b->sockaddr.un.sun_path, path, sizeof(b->sockaddr.un.sun_path)); + b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + l; + } else if (abstract) { + l = strlen(abstract); + if (l > sizeof(b->sockaddr.un.sun_path) - 1) + return -E2BIG; + + b->sockaddr.un.sun_family = AF_UNIX; + b->sockaddr.un.sun_path[0] = 0; + strncpy(b->sockaddr.un.sun_path+1, abstract, sizeof(b->sockaddr.un.sun_path)-1); + b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + 1 + l; + } + + return 0; +} + +static int parse_tcp_address(sd_bus *b, const char **p, char **guid) { + _cleanup_free_ char *host = NULL, *port = NULL, *family = NULL; + int r; + struct addrinfo *result, hints = { + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_ADDRCONFIG, + }; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "host", &host); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "port", &port); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "family", &family); + if (r < 0) + return r; + else if (r > 0) + continue; + + skip_address_key(p); + } + + if (!host || !port) + return -EINVAL; + + if (family) { + if (streq(family, "ipv4")) + hints.ai_family = AF_INET; + else if (streq(family, "ipv6")) + hints.ai_family = AF_INET6; + else + return -EINVAL; + } + + r = getaddrinfo(host, port, &hints, &result); + if (r == EAI_SYSTEM) + return -errno; + else if (r != 0) + return -EADDRNOTAVAIL; + + memcpy(&b->sockaddr, result->ai_addr, result->ai_addrlen); + b->sockaddr_size = result->ai_addrlen; + + freeaddrinfo(result); + + return 0; +} + +static int parse_exec_address(sd_bus *b, const char **p, char **guid) { + char *path = NULL; + unsigned n_argv = 0, j; + char **argv = NULL; + size_t allocated = 0; + int r; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + goto fail; + else if (r > 0) + continue; + + r = parse_address_key(p, "path", &path); + if (r < 0) + goto fail; + else if (r > 0) + continue; + + if (startswith(*p, "argv")) { + unsigned ul; + + errno = 0; + ul = strtoul(*p + 4, (char**) p, 10); + if (errno > 0 || **p != '=' || ul > 256) { + r = -EINVAL; + goto fail; + } + + (*p) ++; + + if (ul >= n_argv) { + if (!GREEDY_REALLOC0(argv, allocated, ul + 2)) { + r = -ENOMEM; + goto fail; + } + + n_argv = ul + 1; + } + + r = parse_address_key(p, NULL, argv + ul); + if (r < 0) + goto fail; + + continue; + } + + skip_address_key(p); + } + + if (!path) { + r = -EINVAL; + goto fail; + } + + /* Make sure there are no holes in the array, with the + * exception of argv[0] */ + for (j = 1; j < n_argv; j++) + if (!argv[j]) { + r = -EINVAL; + goto fail; + } + + if (argv && argv[0] == NULL) { + argv[0] = strdup(path); + if (!argv[0]) { + r = -ENOMEM; + goto fail; + } + } + + b->exec_path = path; + b->exec_argv = argv; + return 0; + +fail: + for (j = 0; j < n_argv; j++) + free(argv[j]); + + free(argv); + free(path); + return r; +} + +static int parse_kernel_address(sd_bus *b, const char **p, char **guid) { + _cleanup_free_ char *path = NULL; + int r; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "path", &path); + if (r < 0) + return r; + else if (r > 0) + continue; + + skip_address_key(p); + } + + if (!path) + return -EINVAL; + + free(b->kernel); + b->kernel = path; + path = NULL; + + return 0; +} + +static int parse_container_unix_address(sd_bus *b, const char **p, char **guid) { + _cleanup_free_ char *machine = NULL, *pid = NULL; + int r; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "machine", &machine); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "pid", &pid); + if (r < 0) + return r; + else if (r > 0) + continue; + + skip_address_key(p); + } + + if (!machine == !pid) + return -EINVAL; + + if (machine) { + if (!machine_name_is_valid(machine)) + return -EINVAL; + + free(b->machine); + b->machine = machine; + machine = NULL; + } else { + b->machine = mfree(b->machine); + } + + if (pid) { + r = parse_pid(pid, &b->nspid); + if (r < 0) + return r; + } else + b->nspid = 0; + + b->sockaddr.un.sun_family = AF_UNIX; + strncpy(b->sockaddr.un.sun_path, "/var/run/dbus/system_bus_socket", sizeof(b->sockaddr.un.sun_path)); + b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen("/var/run/dbus/system_bus_socket"); + + return 0; +} + +static int parse_container_kernel_address(sd_bus *b, const char **p, char **guid) { + _cleanup_free_ char *machine = NULL, *pid = NULL; + int r; + + assert(b); + assert(p); + assert(*p); + assert(guid); + + while (**p != 0 && **p != ';') { + r = parse_address_key(p, "guid", guid); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "machine", &machine); + if (r < 0) + return r; + else if (r > 0) + continue; + + r = parse_address_key(p, "pid", &pid); + if (r < 0) + return r; + else if (r > 0) + continue; + + skip_address_key(p); + } + + if (!machine == !pid) + return -EINVAL; + + if (machine) { + if (!machine_name_is_valid(machine)) + return -EINVAL; + + free(b->machine); + b->machine = machine; + machine = NULL; + } else { + b->machine = mfree(b->machine); + } + + if (pid) { + r = parse_pid(pid, &b->nspid); + if (r < 0) + return r; + } else + b->nspid = 0; + + r = free_and_strdup(&b->kernel, "/sys/fs/kdbus/0-system/bus"); + if (r < 0) + return r; + + return 0; +} + +static void bus_reset_parsed_address(sd_bus *b) { + assert(b); + + zero(b->sockaddr); + b->sockaddr_size = 0; + b->exec_argv = strv_free(b->exec_argv); + b->exec_path = mfree(b->exec_path); + b->server_id = SD_ID128_NULL; + b->kernel = mfree(b->kernel); + b->machine = mfree(b->machine); + b->nspid = 0; +} + +static int bus_parse_next_address(sd_bus *b) { + _cleanup_free_ char *guid = NULL; + const char *a; + int r; + + assert(b); + + if (!b->address) + return 0; + if (b->address[b->address_index] == 0) + return 0; + + bus_reset_parsed_address(b); + + a = b->address + b->address_index; + + while (*a != 0) { + + if (*a == ';') { + a++; + continue; + } + + if (startswith(a, "unix:")) { + a += 5; + + r = parse_unix_address(b, &a, &guid); + if (r < 0) + return r; + break; + + } else if (startswith(a, "tcp:")) { + + a += 4; + r = parse_tcp_address(b, &a, &guid); + if (r < 0) + return r; + + break; + + } else if (startswith(a, "unixexec:")) { + + a += 9; + r = parse_exec_address(b, &a, &guid); + if (r < 0) + return r; + + break; + + } else if (startswith(a, "kernel:")) { + + a += 7; + r = parse_kernel_address(b, &a, &guid); + if (r < 0) + return r; + + break; + } else if (startswith(a, "x-machine-unix:")) { + + a += 15; + r = parse_container_unix_address(b, &a, &guid); + if (r < 0) + return r; + + break; + } else if (startswith(a, "x-machine-kernel:")) { + + a += 17; + r = parse_container_kernel_address(b, &a, &guid); + if (r < 0) + return r; + + break; + } + + a = strchr(a, ';'); + if (!a) + return 0; + } + + if (guid) { + r = sd_id128_from_string(guid, &b->server_id); + if (r < 0) + return r; + } + + b->address_index = a - b->address; + return 1; +} + +static int bus_start_address(sd_bus *b) { + bool container_kdbus_available = false; + bool kdbus_available = false; + int r; + + assert(b); + + for (;;) { + bool skipped = false; + + bus_close_fds(b); + + /* + * Usually, if you provide multiple different bus-addresses, we + * try all of them in order. We use the first one that + * succeeds. However, if you mix kernel and unix addresses, we + * never try unix-addresses if a previous kernel address was + * tried and kdbus was available. This is required to prevent + * clients to fallback to the bus-proxy if kdbus is available + * but failed (eg., too many connections). + */ + + if (b->exec_path) + r = bus_socket_exec(b); + else if ((b->nspid > 0 || b->machine) && b->kernel) { + r = bus_container_connect_kernel(b); + if (r < 0 && !IN_SET(r, -ENOENT, -ESOCKTNOSUPPORT)) + container_kdbus_available = true; + + } else if ((b->nspid > 0 || b->machine) && b->sockaddr.sa.sa_family != AF_UNSPEC) { + if (!container_kdbus_available) + r = bus_container_connect_socket(b); + else + skipped = true; + + } else if (b->kernel) { + r = bus_kernel_connect(b); + if (r < 0 && !IN_SET(r, -ENOENT, -ESOCKTNOSUPPORT)) + kdbus_available = true; + + } else if (b->sockaddr.sa.sa_family != AF_UNSPEC) { + if (!kdbus_available) + r = bus_socket_connect(b); + else + skipped = true; + } else + skipped = true; + + if (!skipped) { + if (r >= 0) { + r = attach_io_events(b); + if (r >= 0) + return r; + } + + b->last_connect_error = -r; + } + + r = bus_parse_next_address(b); + if (r < 0) + return r; + if (r == 0) + return b->last_connect_error ? -b->last_connect_error : -ECONNREFUSED; + } +} + +int bus_next_address(sd_bus *b) { + assert(b); + + bus_reset_parsed_address(b); + return bus_start_address(b); +} + +static int bus_start_fd(sd_bus *b) { + struct stat st; + int r; + + assert(b); + assert(b->input_fd >= 0); + assert(b->output_fd >= 0); + + r = fd_nonblock(b->input_fd, true); + if (r < 0) + return r; + + r = fd_cloexec(b->input_fd, true); + if (r < 0) + return r; + + if (b->input_fd != b->output_fd) { + r = fd_nonblock(b->output_fd, true); + if (r < 0) + return r; + + r = fd_cloexec(b->output_fd, true); + if (r < 0) + return r; + } + + if (fstat(b->input_fd, &st) < 0) + return -errno; + + if (S_ISCHR(b->input_fd)) + return bus_kernel_take_fd(b); + else + return bus_socket_take_fd(b); +} + +_public_ int sd_bus_start(sd_bus *bus) { + int r; + + assert_return(bus, -EINVAL); + assert_return(bus->state == BUS_UNSET, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + bus->state = BUS_OPENING; + + if (bus->is_server && bus->bus_client) + return -EINVAL; + + if (bus->input_fd >= 0) + r = bus_start_fd(bus); + else if (bus->address || bus->sockaddr.sa.sa_family != AF_UNSPEC || bus->exec_path || bus->kernel || bus->machine) + r = bus_start_address(bus); + else + return -EINVAL; + + if (r < 0) { + sd_bus_close(bus); + return r; + } + + return bus_send_hello(bus); +} + +_public_ int sd_bus_open(sd_bus **ret) { + const char *e; + sd_bus *b; + int r; + + assert_return(ret, -EINVAL); + + /* Let's connect to the starter bus if it is set, and + * otherwise to the bus that is appropropriate for the scope + * we are running in */ + + e = secure_getenv("DBUS_STARTER_BUS_TYPE"); + if (e) { + if (streq(e, "system")) + return sd_bus_open_system(ret); + else if (STR_IN_SET(e, "session", "user")) + return sd_bus_open_user(ret); + } + + e = secure_getenv("DBUS_STARTER_ADDRESS"); + if (!e) { + if (cg_pid_get_owner_uid(0, NULL) >= 0) + return sd_bus_open_user(ret); + else + return sd_bus_open_system(ret); + } + + r = sd_bus_new(&b); + if (r < 0) + return r; + + r = sd_bus_set_address(b, e); + if (r < 0) + goto fail; + + b->bus_client = true; + + /* We don't know whether the bus is trusted or not, so better + * be safe, and authenticate everything */ + b->trusted = false; + b->attach_flags |= KDBUS_ATTACH_CAPS | KDBUS_ATTACH_CREDS; + b->creds_mask |= SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_EFFECTIVE_CAPS; + + r = sd_bus_start(b); + if (r < 0) + goto fail; + + *ret = b; + return 0; + +fail: + bus_free(b); + return r; +} + +int bus_set_address_system(sd_bus *b) { + const char *e; + assert(b); + + e = secure_getenv("DBUS_SYSTEM_BUS_ADDRESS"); + if (e) + return sd_bus_set_address(b, e); + + return sd_bus_set_address(b, DEFAULT_SYSTEM_BUS_ADDRESS); +} + +_public_ int sd_bus_open_system(sd_bus **ret) { + sd_bus *b; + int r; + + assert_return(ret, -EINVAL); + + r = sd_bus_new(&b); + if (r < 0) + return r; + + r = bus_set_address_system(b); + if (r < 0) + goto fail; + + b->bus_client = true; + b->is_system = true; + + /* Let's do per-method access control on the system bus. We + * need the caller's UID and capability set for that. */ + b->trusted = false; + b->attach_flags |= KDBUS_ATTACH_CAPS | KDBUS_ATTACH_CREDS; + b->creds_mask |= SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_EFFECTIVE_CAPS; + + r = sd_bus_start(b); + if (r < 0) + goto fail; + + *ret = b; + return 0; + +fail: + bus_free(b); + return r; +} + +int bus_set_address_user(sd_bus *b) { + const char *e; + uid_t uid; + int r; + + assert(b); + + e = secure_getenv("DBUS_SESSION_BUS_ADDRESS"); + if (e) + return sd_bus_set_address(b, e); + + r = cg_pid_get_owner_uid(0, &uid); + if (r < 0) + uid = getuid(); + + e = secure_getenv("XDG_RUNTIME_DIR"); + if (e) { + _cleanup_free_ char *ee = NULL; + + ee = bus_address_escape(e); + if (!ee) + return -ENOMEM; + + (void) asprintf(&b->address, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, uid, ee); + } else + (void) asprintf(&b->address, KERNEL_USER_BUS_ADDRESS_FMT, uid); + + if (!b->address) + return -ENOMEM; + + return 0; +} + +_public_ int sd_bus_open_user(sd_bus **ret) { + sd_bus *b; + int r; + + assert_return(ret, -EINVAL); + + r = sd_bus_new(&b); + if (r < 0) + return r; + + r = bus_set_address_user(b); + if (r < 0) + return r; + + b->bus_client = true; + b->is_user = true; + + /* We don't do any per-method access control on the user + * bus. */ + b->trusted = true; + + r = sd_bus_start(b); + if (r < 0) + goto fail; + + *ret = b; + return 0; + +fail: + bus_free(b); + return r; +} + +int bus_set_address_system_remote(sd_bus *b, const char *host) { + _cleanup_free_ char *e = NULL; + char *m = NULL, *c = NULL; + + assert(b); + assert(host); + + /* Let's see if we shall enter some container */ + m = strchr(host, ':'); + if (m) { + m++; + + /* Let's make sure this is not a port of some kind, + * and is a valid machine name. */ + if (!in_charset(m, "0123456789") && machine_name_is_valid(m)) { + char *t; + + /* Cut out the host part */ + t = strndupa(host, m - host - 1); + e = bus_address_escape(t); + if (!e) + return -ENOMEM; + + c = strjoina(",argv4=--machine=", m); + } + } + + if (!e) { + e = bus_address_escape(host); + if (!e) + return -ENOMEM; + } + + b->address = strjoin("unixexec:path=ssh,argv1=-xT,argv2=", e, ",argv3=systemd-stdio-bridge", c, NULL); + if (!b->address) + return -ENOMEM; + + return 0; + } + +_public_ int sd_bus_open_system_remote(sd_bus **ret, const char *host) { + sd_bus *bus; + int r; + + assert_return(host, -EINVAL); + assert_return(ret, -EINVAL); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + r = bus_set_address_system_remote(bus, host); + if (r < 0) + goto fail; + + bus->bus_client = true; + bus->trusted = false; + bus->is_system = true; + + r = sd_bus_start(bus); + if (r < 0) + goto fail; + + *ret = bus; + return 0; + +fail: + bus_free(bus); + return r; +} + +int bus_set_address_system_machine(sd_bus *b, const char *machine) { + _cleanup_free_ char *e = NULL; + + assert(b); + assert(machine); + + e = bus_address_escape(machine); + if (!e) + return -ENOMEM; + + b->address = strjoin("x-machine-kernel:machine=", e, ";x-machine-unix:machine=", e, NULL); + if (!b->address) + return -ENOMEM; + + return 0; +} + +_public_ int sd_bus_open_system_machine(sd_bus **ret, const char *machine) { + sd_bus *bus; + int r; + + assert_return(machine, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(machine_name_is_valid(machine), -EINVAL); + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + r = bus_set_address_system_machine(bus, machine); + if (r < 0) + goto fail; + + bus->bus_client = true; + bus->trusted = false; + bus->is_system = true; + + r = sd_bus_start(bus); + if (r < 0) + goto fail; + + *ret = bus; + return 0; + +fail: + bus_free(bus); + return r; +} + +_public_ void sd_bus_close(sd_bus *bus) { + + if (!bus) + return; + if (bus->state == BUS_CLOSED) + return; + if (bus_pid_changed(bus)) + return; + + bus->state = BUS_CLOSED; + + sd_bus_detach_event(bus); + + /* Drop all queued messages so that they drop references to + * the bus object and the bus may be freed */ + bus_reset_queues(bus); + + if (!bus->is_kernel) + bus_close_fds(bus); + + /* We'll leave the fd open in case this is a kernel bus, since + * there might still be memblocks around that reference this + * bus, and they might need to invoke the KDBUS_CMD_FREE + * ioctl on the fd when they are freed. */ +} + +_public_ sd_bus* sd_bus_flush_close_unref(sd_bus *bus) { + + if (!bus) + return NULL; + + sd_bus_flush(bus); + sd_bus_close(bus); + + return sd_bus_unref(bus); +} + +static void bus_enter_closing(sd_bus *bus) { + assert(bus); + + if (bus->state != BUS_OPENING && + bus->state != BUS_AUTHENTICATING && + bus->state != BUS_HELLO && + bus->state != BUS_RUNNING) + return; + + bus->state = BUS_CLOSING; +} + +_public_ sd_bus *sd_bus_ref(sd_bus *bus) { + + if (!bus) + return NULL; + + assert_se(REFCNT_INC(bus->n_ref) >= 2); + + return bus; +} + +_public_ sd_bus *sd_bus_unref(sd_bus *bus) { + unsigned i; + + if (!bus) + return NULL; + + i = REFCNT_DEC(bus->n_ref); + if (i > 0) + return NULL; + + bus_free(bus); + return NULL; +} + +_public_ int sd_bus_is_open(sd_bus *bus) { + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return BUS_IS_OPEN(bus->state); +} + +_public_ int sd_bus_can_send(sd_bus *bus, char type) { + int r; + + assert_return(bus, -EINVAL); + assert_return(bus->state != BUS_UNSET, -ENOTCONN); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (bus->hello_flags & KDBUS_HELLO_MONITOR) + return 0; + + if (type == SD_BUS_TYPE_UNIX_FD) { + if (!(bus->hello_flags & KDBUS_HELLO_ACCEPT_FD)) + return 0; + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + return bus->can_fds; + } + + return bus_type_is_valid(type); +} + +_public_ int sd_bus_get_bus_id(sd_bus *bus, sd_id128_t *id) { + int r; + + assert_return(bus, -EINVAL); + assert_return(id, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + *id = bus->server_id; + return 0; +} + +static int bus_seal_message(sd_bus *b, sd_bus_message *m, usec_t timeout) { + assert(b); + assert(m); + + if (m->sealed) { + /* If we copy the same message to multiple + * destinations, avoid using the same cookie + * numbers. */ + b->cookie = MAX(b->cookie, BUS_MESSAGE_COOKIE(m)); + return 0; + } + + if (timeout == 0) + timeout = BUS_DEFAULT_TIMEOUT; + + return bus_message_seal(m, ++b->cookie, timeout); +} + +static int bus_remarshal_message(sd_bus *b, sd_bus_message **m) { + bool remarshal = false; + + assert(b); + + /* wrong packet version */ + if (b->message_version != 0 && b->message_version != (*m)->header->version) + remarshal = true; + + /* wrong packet endianness */ + if (b->message_endian != 0 && b->message_endian != (*m)->header->endian) + remarshal = true; + + /* TODO: kdbus-messages received from the kernel contain data which is + * not allowed to be passed to KDBUS_CMD_SEND. Therefore, we have to + * force remarshaling of the message. Technically, we could just + * recreate the kdbus message, but that is non-trivial as other parts of + * the message refer to m->kdbus already. This should be fixed! */ + if ((*m)->kdbus && (*m)->release_kdbus) + remarshal = true; + + return remarshal ? bus_message_remarshal(b, m) : 0; +} + +int bus_seal_synthetic_message(sd_bus *b, sd_bus_message *m) { + assert(b); + assert(m); + + /* Fake some timestamps, if they were requested, and not + * already initialized */ + if (b->attach_flags & KDBUS_ATTACH_TIMESTAMP) { + if (m->realtime <= 0) + m->realtime = now(CLOCK_REALTIME); + + if (m->monotonic <= 0) + m->monotonic = now(CLOCK_MONOTONIC); + } + + /* The bus specification says the serial number cannot be 0, + * hence let's fill something in for synthetic messages. Since + * synthetic messages might have a fake sender and we don't + * want to interfere with the real sender's serial numbers we + * pick a fixed, artificial one. We use (uint32_t) -1 rather + * than (uint64_t) -1 since dbus1 only had 32bit identifiers, + * even though kdbus can do 64bit. */ + return bus_message_seal(m, 0xFFFFFFFFULL, 0); +} + +static int bus_write_message(sd_bus *bus, sd_bus_message *m, bool hint_sync_call, size_t *idx) { + int r; + + assert(bus); + assert(m); + + if (bus->is_kernel) + r = bus_kernel_write_message(bus, m, hint_sync_call); + else + r = bus_socket_write_message(bus, m, idx); + + if (r <= 0) + return r; + + if (bus->is_kernel || *idx >= BUS_MESSAGE_SIZE(m)) + log_debug("Sent message type=%s sender=%s destination=%s object=%s interface=%s member=%s cookie=%" PRIu64 " reply_cookie=%" PRIu64 " error=%s", + bus_message_type_to_string(m->header->type), + strna(sd_bus_message_get_sender(m)), + strna(sd_bus_message_get_destination(m)), + strna(sd_bus_message_get_path(m)), + strna(sd_bus_message_get_interface(m)), + strna(sd_bus_message_get_member(m)), + BUS_MESSAGE_COOKIE(m), + m->reply_cookie, + strna(m->error.message)); + + return r; +} + +static int dispatch_wqueue(sd_bus *bus) { + int r, ret = 0; + + assert(bus); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + while (bus->wqueue_size > 0) { + + r = bus_write_message(bus, bus->wqueue[0], false, &bus->windex); + if (r < 0) + return r; + else if (r == 0) + /* Didn't do anything this time */ + return ret; + else if (bus->is_kernel || bus->windex >= BUS_MESSAGE_SIZE(bus->wqueue[0])) { + /* Fully written. Let's drop the entry from + * the queue. + * + * This isn't particularly optimized, but + * well, this is supposed to be our worst-case + * buffer only, and the socket buffer is + * supposed to be our primary buffer, and if + * it got full, then all bets are off + * anyway. */ + + bus->wqueue_size --; + sd_bus_message_unref(bus->wqueue[0]); + memmove(bus->wqueue, bus->wqueue + 1, sizeof(sd_bus_message*) * bus->wqueue_size); + bus->windex = 0; + + ret = 1; + } + } + + return ret; +} + +static int bus_read_message(sd_bus *bus, bool hint_priority, int64_t priority) { + assert(bus); + + if (bus->is_kernel) + return bus_kernel_read_message(bus, hint_priority, priority); + else + return bus_socket_read_message(bus); +} + +int bus_rqueue_make_room(sd_bus *bus) { + assert(bus); + + if (bus->rqueue_size >= BUS_RQUEUE_MAX) + return -ENOBUFS; + + if (!GREEDY_REALLOC(bus->rqueue, bus->rqueue_allocated, bus->rqueue_size + 1)) + return -ENOMEM; + + return 0; +} + +static int dispatch_rqueue(sd_bus *bus, bool hint_priority, int64_t priority, sd_bus_message **m) { + int r, ret = 0; + + assert(bus); + assert(m); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + /* Note that the priority logic is only available on kdbus, + * where the rqueue is unused. We check the rqueue here + * anyway, because it's simple... */ + + for (;;) { + if (bus->rqueue_size > 0) { + /* Dispatch a queued message */ + + *m = bus->rqueue[0]; + bus->rqueue_size --; + memmove(bus->rqueue, bus->rqueue + 1, sizeof(sd_bus_message*) * bus->rqueue_size); + return 1; + } + + /* Try to read a new message */ + r = bus_read_message(bus, hint_priority, priority); + if (r < 0) + return r; + if (r == 0) + return ret; + + ret = 1; + } +} + +static int bus_send_internal(sd_bus *bus, sd_bus_message *_m, uint64_t *cookie, bool hint_sync_call) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); + int r; + + assert_return(m, -EINVAL); + + if (!bus) + bus = m->bus; + + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(!bus->is_kernel || !(bus->hello_flags & KDBUS_HELLO_MONITOR), -EROFS); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (m->n_fds > 0) { + r = sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD); + if (r < 0) + return r; + if (r == 0) + return -EOPNOTSUPP; + } + + /* If the cookie number isn't kept, then we know that no reply + * is expected */ + if (!cookie && !m->sealed) + m->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; + + r = bus_seal_message(bus, m, 0); + if (r < 0) + return r; + + /* Remarshall if we have to. This will possibly unref the + * message and place a replacement in m */ + r = bus_remarshal_message(bus, &m); + if (r < 0) + return r; + + /* If this is a reply and no reply was requested, then let's + * suppress this, if we can */ + if (m->dont_send) + goto finish; + + if ((bus->state == BUS_RUNNING || bus->state == BUS_HELLO) && bus->wqueue_size <= 0) { + size_t idx = 0; + + r = bus_write_message(bus, m, hint_sync_call, &idx); + if (r < 0) { + if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { + bus_enter_closing(bus); + return -ECONNRESET; + } + + return r; + } + + if (!bus->is_kernel && idx < BUS_MESSAGE_SIZE(m)) { + /* Wasn't fully written. So let's remember how + * much was written. Note that the first entry + * of the wqueue array is always allocated so + * that we always can remember how much was + * written. */ + bus->wqueue[0] = sd_bus_message_ref(m); + bus->wqueue_size = 1; + bus->windex = idx; + } + + } else { + /* Just append it to the queue. */ + + if (bus->wqueue_size >= BUS_WQUEUE_MAX) + return -ENOBUFS; + + if (!GREEDY_REALLOC(bus->wqueue, bus->wqueue_allocated, bus->wqueue_size + 1)) + return -ENOMEM; + + bus->wqueue[bus->wqueue_size ++] = sd_bus_message_ref(m); + } + +finish: + if (cookie) + *cookie = BUS_MESSAGE_COOKIE(m); + + return 1; +} + +_public_ int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie) { + return bus_send_internal(bus, m, cookie, false); +} + +_public_ int sd_bus_send_to(sd_bus *bus, sd_bus_message *m, const char *destination, uint64_t *cookie) { + int r; + + assert_return(m, -EINVAL); + + if (!bus) + bus = m->bus; + + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (!streq_ptr(m->destination, destination)) { + + if (!destination) + return -EEXIST; + + r = sd_bus_message_set_destination(m, destination); + if (r < 0) + return r; + } + + return sd_bus_send(bus, m, cookie); +} + +static usec_t calc_elapse(uint64_t usec) { + if (usec == (uint64_t) -1) + return 0; + + return now(CLOCK_MONOTONIC) + usec; +} + +static int timeout_compare(const void *a, const void *b) { + const struct reply_callback *x = a, *y = b; + + if (x->timeout != 0 && y->timeout == 0) + return -1; + + if (x->timeout == 0 && y->timeout != 0) + return 1; + + if (x->timeout < y->timeout) + return -1; + + if (x->timeout > y->timeout) + return 1; + + return 0; +} + +_public_ int sd_bus_call_async( + sd_bus *bus, + sd_bus_slot **slot, + sd_bus_message *_m, + sd_bus_message_handler_t callback, + void *userdata, + uint64_t usec) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *s = NULL; + int r; + + assert_return(m, -EINVAL); + assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); + assert_return(!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED), -EINVAL); + assert_return(callback, -EINVAL); + + if (!bus) + bus = m->bus; + + assert_return(!bus_pid_changed(bus), -ECHILD); + assert_return(!bus->is_kernel || !(bus->hello_flags & KDBUS_HELLO_MONITOR), -EROFS); + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + r = ordered_hashmap_ensure_allocated(&bus->reply_callbacks, &uint64_hash_ops); + if (r < 0) + return r; + + r = prioq_ensure_allocated(&bus->reply_callbacks_prioq, timeout_compare); + if (r < 0) + return r; + + r = bus_seal_message(bus, m, usec); + if (r < 0) + return r; + + r = bus_remarshal_message(bus, &m); + if (r < 0) + return r; + + s = bus_slot_allocate(bus, !slot, BUS_REPLY_CALLBACK, sizeof(struct reply_callback), userdata); + if (!s) + return -ENOMEM; + + s->reply_callback.callback = callback; + + s->reply_callback.cookie = BUS_MESSAGE_COOKIE(m); + r = ordered_hashmap_put(bus->reply_callbacks, &s->reply_callback.cookie, &s->reply_callback); + if (r < 0) { + s->reply_callback.cookie = 0; + return r; + } + + s->reply_callback.timeout = calc_elapse(m->timeout); + if (s->reply_callback.timeout != 0) { + r = prioq_put(bus->reply_callbacks_prioq, &s->reply_callback, &s->reply_callback.prioq_idx); + if (r < 0) { + s->reply_callback.timeout = 0; + return r; + } + } + + r = sd_bus_send(bus, m, &s->reply_callback.cookie); + if (r < 0) + return r; + + if (slot) + *slot = s; + s = NULL; + + return r; +} + +int bus_ensure_running(sd_bus *bus) { + int r; + + assert(bus); + + if (bus->state == BUS_UNSET || bus->state == BUS_CLOSED || bus->state == BUS_CLOSING) + return -ENOTCONN; + if (bus->state == BUS_RUNNING) + return 1; + + for (;;) { + r = sd_bus_process(bus, NULL); + if (r < 0) + return r; + if (bus->state == BUS_RUNNING) + return 1; + if (r > 0) + continue; + + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) + return r; + } +} + +_public_ int sd_bus_call( + sd_bus *bus, + sd_bus_message *_m, + uint64_t usec, + sd_bus_error *error, + sd_bus_message **reply) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); + usec_t timeout; + uint64_t cookie; + unsigned i; + int r; + + bus_assert_return(m, -EINVAL, error); + bus_assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL, error); + bus_assert_return(!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED), -EINVAL, error); + bus_assert_return(!bus_error_is_dirty(error), -EINVAL, error); + + if (!bus) + bus = m->bus; + + bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); + bus_assert_return(!bus->is_kernel || !(bus->hello_flags & KDBUS_HELLO_MONITOR), -EROFS, error); + + if (!BUS_IS_OPEN(bus->state)) { + r = -ENOTCONN; + goto fail; + } + + r = bus_ensure_running(bus); + if (r < 0) + goto fail; + + i = bus->rqueue_size; + + r = bus_seal_message(bus, m, usec); + if (r < 0) + goto fail; + + r = bus_remarshal_message(bus, &m); + if (r < 0) + goto fail; + + r = bus_send_internal(bus, m, &cookie, true); + if (r < 0) + goto fail; + + timeout = calc_elapse(m->timeout); + + for (;;) { + usec_t left; + + while (i < bus->rqueue_size) { + sd_bus_message *incoming = NULL; + + incoming = bus->rqueue[i]; + + if (incoming->reply_cookie == cookie) { + /* Found a match! */ + + memmove(bus->rqueue + i, bus->rqueue + i + 1, sizeof(sd_bus_message*) * (bus->rqueue_size - i - 1)); + bus->rqueue_size--; + log_debug_bus_message(incoming); + + if (incoming->header->type == SD_BUS_MESSAGE_METHOD_RETURN) { + + if (incoming->n_fds <= 0 || (bus->hello_flags & KDBUS_HELLO_ACCEPT_FD)) { + if (reply) + *reply = incoming; + else + sd_bus_message_unref(incoming); + + return 1; + } + + r = sd_bus_error_setf(error, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Reply message contained file descriptors which I couldn't accept. Sorry."); + sd_bus_message_unref(incoming); + return r; + + } else if (incoming->header->type == SD_BUS_MESSAGE_METHOD_ERROR) { + r = sd_bus_error_copy(error, &incoming->error); + sd_bus_message_unref(incoming); + return r; + } else { + r = -EIO; + goto fail; + } + + } else if (BUS_MESSAGE_COOKIE(incoming) == cookie && + bus->unique_name && + incoming->sender && + streq(bus->unique_name, incoming->sender)) { + + memmove(bus->rqueue + i, bus->rqueue + i + 1, sizeof(sd_bus_message*) * (bus->rqueue_size - i - 1)); + bus->rqueue_size--; + + /* Our own message? Somebody is trying + * to send its own client a message, + * let's not dead-lock, let's fail + * immediately. */ + + sd_bus_message_unref(incoming); + r = -ELOOP; + goto fail; + } + + /* Try to read more, right-away */ + i++; + } + + r = bus_read_message(bus, false, 0); + if (r < 0) { + if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { + bus_enter_closing(bus); + r = -ECONNRESET; + } + + goto fail; + } + if (r > 0) + continue; + + if (timeout > 0) { + usec_t n; + + n = now(CLOCK_MONOTONIC); + if (n >= timeout) { + r = -ETIMEDOUT; + goto fail; + } + + left = timeout - n; + } else + left = (uint64_t) -1; + + r = bus_poll(bus, true, left); + if (r < 0) + goto fail; + if (r == 0) { + r = -ETIMEDOUT; + goto fail; + } + + r = dispatch_wqueue(bus); + if (r < 0) { + if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { + bus_enter_closing(bus); + r = -ECONNRESET; + } + + goto fail; + } + } + +fail: + return sd_bus_error_set_errno(error, r); +} + +_public_ int sd_bus_get_fd(sd_bus *bus) { + + assert_return(bus, -EINVAL); + assert_return(bus->input_fd == bus->output_fd, -EPERM); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return bus->input_fd; +} + +_public_ int sd_bus_get_events(sd_bus *bus) { + int flags = 0; + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state) && bus->state != BUS_CLOSING) + return -ENOTCONN; + + if (bus->state == BUS_OPENING) + flags |= POLLOUT; + else if (bus->state == BUS_AUTHENTICATING) { + + if (bus_socket_auth_needs_write(bus)) + flags |= POLLOUT; + + flags |= POLLIN; + + } else if (bus->state == BUS_RUNNING || bus->state == BUS_HELLO) { + if (bus->rqueue_size <= 0) + flags |= POLLIN; + if (bus->wqueue_size > 0) + flags |= POLLOUT; + } + + return flags; +} + +_public_ int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) { + struct reply_callback *c; + + assert_return(bus, -EINVAL); + assert_return(timeout_usec, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!BUS_IS_OPEN(bus->state) && bus->state != BUS_CLOSING) + return -ENOTCONN; + + if (bus->track_queue) { + *timeout_usec = 0; + return 1; + } + + if (bus->state == BUS_CLOSING) { + *timeout_usec = 0; + return 1; + } + + if (bus->state == BUS_AUTHENTICATING) { + *timeout_usec = bus->auth_timeout; + return 1; + } + + if (bus->state != BUS_RUNNING && bus->state != BUS_HELLO) { + *timeout_usec = (uint64_t) -1; + return 0; + } + + if (bus->rqueue_size > 0) { + *timeout_usec = 0; + return 1; + } + + c = prioq_peek(bus->reply_callbacks_prioq); + if (!c) { + *timeout_usec = (uint64_t) -1; + return 0; + } + + if (c->timeout == 0) { + *timeout_usec = (uint64_t) -1; + return 0; + } + + *timeout_usec = c->timeout; + return 1; +} + +static int process_timeout(sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* m = NULL; + struct reply_callback *c; + sd_bus_slot *slot; + usec_t n; + int r; + + assert(bus); + + c = prioq_peek(bus->reply_callbacks_prioq); + if (!c) + return 0; + + n = now(CLOCK_MONOTONIC); + if (c->timeout > n) + return 0; + + r = bus_message_new_synthetic_error( + bus, + c->cookie, + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Method call timed out"), + &m); + if (r < 0) + return r; + + r = bus_seal_synthetic_message(bus, m); + if (r < 0) + return r; + + assert_se(prioq_pop(bus->reply_callbacks_prioq) == c); + c->timeout = 0; + + ordered_hashmap_remove(bus->reply_callbacks, &c->cookie); + c->cookie = 0; + + slot = container_of(c, sd_bus_slot, reply_callback); + + bus->iteration_counter ++; + + bus->current_message = m; + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = c->callback; + bus->current_userdata = slot->userdata; + r = c->callback(m, slot->userdata, &error_buffer); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = NULL; + bus->current_message = NULL; + + if (slot->floating) { + bus_slot_disconnect(slot); + sd_bus_slot_unref(slot); + } + + sd_bus_slot_unref(slot); + + return bus_maybe_reply_error(m, r, &error_buffer); +} + +static int process_hello(sd_bus *bus, sd_bus_message *m) { + assert(bus); + assert(m); + + if (bus->state != BUS_HELLO) + return 0; + + /* Let's make sure the first message on the bus is the HELLO + * reply. But note that we don't actually parse the message + * here (we leave that to the usual handling), we just verify + * we don't let any earlier msg through. */ + + if (m->header->type != SD_BUS_MESSAGE_METHOD_RETURN && + m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) + return -EIO; + + if (m->reply_cookie != 1) + return -EIO; + + return 0; +} + +static int process_reply(sd_bus *bus, sd_bus_message *m) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *synthetic_reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + struct reply_callback *c; + sd_bus_slot *slot; + int r; + + assert(bus); + assert(m); + + if (m->header->type != SD_BUS_MESSAGE_METHOD_RETURN && + m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) + return 0; + + if (bus->is_kernel && (bus->hello_flags & KDBUS_HELLO_MONITOR)) + return 0; + + if (m->destination && bus->unique_name && !streq_ptr(m->destination, bus->unique_name)) + return 0; + + c = ordered_hashmap_remove(bus->reply_callbacks, &m->reply_cookie); + if (!c) + return 0; + + c->cookie = 0; + + slot = container_of(c, sd_bus_slot, reply_callback); + + if (m->n_fds > 0 && !(bus->hello_flags & KDBUS_HELLO_ACCEPT_FD)) { + + /* If the reply contained a file descriptor which we + * didn't want we pass an error instead. */ + + r = bus_message_new_synthetic_error( + bus, + m->reply_cookie, + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Reply message contained file descriptor"), + &synthetic_reply); + if (r < 0) + return r; + + /* Copy over original timestamp */ + synthetic_reply->realtime = m->realtime; + synthetic_reply->monotonic = m->monotonic; + synthetic_reply->seqnum = m->seqnum; + + r = bus_seal_synthetic_message(bus, synthetic_reply); + if (r < 0) + return r; + + m = synthetic_reply; + } else { + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + } + + if (c->timeout != 0) { + prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); + c->timeout = 0; + } + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = c->callback; + bus->current_userdata = slot->userdata; + r = c->callback(m, slot->userdata, &error_buffer); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = NULL; + + if (slot->floating) { + bus_slot_disconnect(slot); + sd_bus_slot_unref(slot); + } + + sd_bus_slot_unref(slot); + + return bus_maybe_reply_error(m, r, &error_buffer); +} + +static int process_filter(sd_bus *bus, sd_bus_message *m) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + struct filter_callback *l; + int r; + + assert(bus); + assert(m); + + do { + bus->filter_callbacks_modified = false; + + LIST_FOREACH(callbacks, l, bus->filter_callbacks) { + sd_bus_slot *slot; + + if (bus->filter_callbacks_modified) + break; + + /* Don't run this more than once per iteration */ + if (l->last_iteration == bus->iteration_counter) + continue; + + l->last_iteration = bus->iteration_counter; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + slot = container_of(l, sd_bus_slot, filter_callback); + + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = l->callback; + bus->current_userdata = slot->userdata; + r = l->callback(m, slot->userdata, &error_buffer); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = sd_bus_slot_unref(slot); + + r = bus_maybe_reply_error(m, r, &error_buffer); + if (r != 0) + return r; + + } + + } while (bus->filter_callbacks_modified); + + return 0; +} + +static int process_match(sd_bus *bus, sd_bus_message *m) { + int r; + + assert(bus); + assert(m); + + do { + bus->match_callbacks_modified = false; + + r = bus_match_run(bus, &bus->match_callbacks, m); + if (r != 0) + return r; + + } while (bus->match_callbacks_modified); + + return 0; +} + +static int process_builtin(sd_bus *bus, sd_bus_message *m) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + assert(bus); + assert(m); + + if (bus->hello_flags & KDBUS_HELLO_MONITOR) + return 0; + + if (bus->manual_peer_interface) + return 0; + + if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) + return 0; + + if (!streq_ptr(m->interface, "org.freedesktop.DBus.Peer")) + return 0; + + if (m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) + return 1; + + if (streq_ptr(m->member, "Ping")) + r = sd_bus_message_new_method_return(m, &reply); + else if (streq_ptr(m->member, "GetMachineId")) { + sd_id128_t id; + char sid[33]; + + r = sd_id128_get_machine(&id); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "s", sd_id128_to_string(id, sid)); + } else { + r = sd_bus_message_new_method_errorf( + m, &reply, + SD_BUS_ERROR_UNKNOWN_METHOD, + "Unknown method '%s' on interface '%s'.", m->member, m->interface); + } + + if (r < 0) + return r; + + r = sd_bus_send(bus, reply, NULL); + if (r < 0) + return r; + + return 1; +} + +static int process_fd_check(sd_bus *bus, sd_bus_message *m) { + assert(bus); + assert(m); + + /* If we got a message with a file descriptor which we didn't + * want to accept, then let's drop it. How can this even + * happen? For example, when the kernel queues a message into + * an activatable names's queue which allows fds, and then is + * delivered to us later even though we ourselves did not + * negotiate it. */ + + if (bus->hello_flags & KDBUS_HELLO_MONITOR) + return 0; + + if (m->n_fds <= 0) + return 0; + + if (bus->hello_flags & KDBUS_HELLO_ACCEPT_FD) + return 0; + + if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) + return 1; /* just eat it up */ + + return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Message contains file descriptors, which I cannot accept. Sorry."); +} + +static int process_message(sd_bus *bus, sd_bus_message *m) { + int r; + + assert(bus); + assert(m); + + bus->current_message = m; + bus->iteration_counter++; + + log_debug_bus_message(m); + + r = process_hello(bus, m); + if (r != 0) + goto finish; + + r = process_reply(bus, m); + if (r != 0) + goto finish; + + r = process_fd_check(bus, m); + if (r != 0) + goto finish; + + r = process_filter(bus, m); + if (r != 0) + goto finish; + + r = process_match(bus, m); + if (r != 0) + goto finish; + + r = process_builtin(bus, m); + if (r != 0) + goto finish; + + r = bus_process_object(bus, m); + +finish: + bus->current_message = NULL; + return r; +} + +static int dispatch_track(sd_bus *bus) { + assert(bus); + + if (!bus->track_queue) + return 0; + + bus_track_dispatch(bus->track_queue); + return 1; +} + +static int process_running(sd_bus *bus, bool hint_priority, int64_t priority, sd_bus_message **ret) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + int r; + + assert(bus); + assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); + + r = process_timeout(bus); + if (r != 0) + goto null_message; + + r = dispatch_wqueue(bus); + if (r != 0) + goto null_message; + + r = dispatch_track(bus); + if (r != 0) + goto null_message; + + r = dispatch_rqueue(bus, hint_priority, priority, &m); + if (r < 0) + return r; + if (!m) + goto null_message; + + r = process_message(bus, m); + if (r != 0) + goto null_message; + + if (ret) { + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + *ret = m; + m = NULL; + return 1; + } + + if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) { + + log_debug("Unprocessed message call sender=%s object=%s interface=%s member=%s", + strna(sd_bus_message_get_sender(m)), + strna(sd_bus_message_get_path(m)), + strna(sd_bus_message_get_interface(m)), + strna(sd_bus_message_get_member(m))); + + r = sd_bus_reply_method_errorf( + m, + SD_BUS_ERROR_UNKNOWN_OBJECT, + "Unknown object '%s'.", m->path); + if (r < 0) + return r; + } + + return 1; + +null_message: + if (r >= 0 && ret) + *ret = NULL; + + return r; +} + +static int process_closing(sd_bus *bus, sd_bus_message **ret) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + struct reply_callback *c; + int r; + + assert(bus); + assert(bus->state == BUS_CLOSING); + + c = ordered_hashmap_first(bus->reply_callbacks); + if (c) { + _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; + sd_bus_slot *slot; + + /* First, fail all outstanding method calls */ + r = bus_message_new_synthetic_error( + bus, + c->cookie, + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Connection terminated"), + &m); + if (r < 0) + return r; + + r = bus_seal_synthetic_message(bus, m); + if (r < 0) + return r; + + if (c->timeout != 0) { + prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); + c->timeout = 0; + } + + ordered_hashmap_remove(bus->reply_callbacks, &c->cookie); + c->cookie = 0; + + slot = container_of(c, sd_bus_slot, reply_callback); + + bus->iteration_counter++; + + bus->current_message = m; + bus->current_slot = sd_bus_slot_ref(slot); + bus->current_handler = c->callback; + bus->current_userdata = slot->userdata; + r = c->callback(m, slot->userdata, &error_buffer); + bus->current_userdata = NULL; + bus->current_handler = NULL; + bus->current_slot = NULL; + bus->current_message = NULL; + + if (slot->floating) { + bus_slot_disconnect(slot); + sd_bus_slot_unref(slot); + } + + sd_bus_slot_unref(slot); + + return bus_maybe_reply_error(m, r, &error_buffer); + } + + /* Then, synthesize a Disconnected message */ + r = sd_bus_message_new_signal( + bus, + &m, + "/org/freedesktop/DBus/Local", + "org.freedesktop.DBus.Local", + "Disconnected"); + if (r < 0) + return r; + + bus_message_set_sender_local(bus, m); + + r = bus_seal_synthetic_message(bus, m); + if (r < 0) + return r; + + sd_bus_close(bus); + + bus->current_message = m; + bus->iteration_counter++; + + r = process_filter(bus, m); + if (r != 0) + goto finish; + + r = process_match(bus, m); + if (r != 0) + goto finish; + + if (ret) { + *ret = m; + m = NULL; + } + + r = 1; + +finish: + bus->current_message = NULL; + + return r; +} + +static int bus_process_internal(sd_bus *bus, bool hint_priority, int64_t priority, sd_bus_message **ret) { + BUS_DONT_DESTROY(bus); + int r; + + /* Returns 0 when we didn't do anything. This should cause the + * caller to invoke sd_bus_wait() before returning the next + * time. Returns > 0 when we did something, which possibly + * means *ret is filled in with an unprocessed message. */ + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + /* We don't allow recursively invoking sd_bus_process(). */ + assert_return(!bus->current_message, -EBUSY); + assert(!bus->current_slot); + + switch (bus->state) { + + case BUS_UNSET: + return -ENOTCONN; + + case BUS_CLOSED: + return -ECONNRESET; + + case BUS_OPENING: + r = bus_socket_process_opening(bus); + if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { + bus_enter_closing(bus); + r = 1; + } else if (r < 0) + return r; + if (ret) + *ret = NULL; + return r; + + case BUS_AUTHENTICATING: + r = bus_socket_process_authenticating(bus); + if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { + bus_enter_closing(bus); + r = 1; + } else if (r < 0) + return r; + + if (ret) + *ret = NULL; + + return r; + + case BUS_RUNNING: + case BUS_HELLO: + r = process_running(bus, hint_priority, priority, ret); + if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { + bus_enter_closing(bus); + r = 1; + + if (ret) + *ret = NULL; + } + + return r; + + case BUS_CLOSING: + return process_closing(bus, ret); + } + + assert_not_reached("Unknown state"); +} + +_public_ int sd_bus_process(sd_bus *bus, sd_bus_message **ret) { + return bus_process_internal(bus, false, 0, ret); +} + +_public_ int sd_bus_process_priority(sd_bus *bus, int64_t priority, sd_bus_message **ret) { + return bus_process_internal(bus, true, priority, ret); +} + +static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec) { + struct pollfd p[2] = {}; + int r, e, n; + struct timespec ts; + usec_t m = USEC_INFINITY; + + assert(bus); + + if (bus->state == BUS_CLOSING) + return 1; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + e = sd_bus_get_events(bus); + if (e < 0) + return e; + + if (need_more) + /* The caller really needs some more data, he doesn't + * care about what's already read, or any timeouts + * except its own. */ + e |= POLLIN; + else { + usec_t until; + /* The caller wants to process if there's something to + * process, but doesn't care otherwise */ + + r = sd_bus_get_timeout(bus, &until); + if (r < 0) + return r; + if (r > 0) { + usec_t nw; + nw = now(CLOCK_MONOTONIC); + m = until > nw ? until - nw : 0; + } + } + + if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) + m = timeout_usec; + + p[0].fd = bus->input_fd; + if (bus->output_fd == bus->input_fd) { + p[0].events = e; + n = 1; + } else { + p[0].events = e & POLLIN; + p[1].fd = bus->output_fd; + p[1].events = e & POLLOUT; + n = 2; + } + + r = ppoll(p, n, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); + if (r < 0) + return -errno; + + return r > 0 ? 1 : 0; +} + +_public_ int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec) { + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (bus->state == BUS_CLOSING) + return 0; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->rqueue_size > 0) + return 0; + + return bus_poll(bus, false, timeout_usec); +} + +_public_ int sd_bus_flush(sd_bus *bus) { + int r; + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (bus->state == BUS_CLOSING) + return 0; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + r = bus_ensure_running(bus); + if (r < 0) + return r; + + if (bus->wqueue_size <= 0) + return 0; + + for (;;) { + r = dispatch_wqueue(bus); + if (r < 0) { + if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { + bus_enter_closing(bus); + return -ECONNRESET; + } + + return r; + } + + if (bus->wqueue_size <= 0) + return 0; + + r = bus_poll(bus, false, (uint64_t) -1); + if (r < 0) + return r; + } +} + +_public_ int sd_bus_add_filter( + sd_bus *bus, + sd_bus_slot **slot, + sd_bus_message_handler_t callback, + void *userdata) { + + sd_bus_slot *s; + + assert_return(bus, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + s = bus_slot_allocate(bus, !slot, BUS_FILTER_CALLBACK, sizeof(struct filter_callback), userdata); + if (!s) + return -ENOMEM; + + s->filter_callback.callback = callback; + + bus->filter_callbacks_modified = true; + LIST_PREPEND(callbacks, bus->filter_callbacks, &s->filter_callback); + + if (slot) + *slot = s; + + return 0; +} + +_public_ int sd_bus_add_match( + sd_bus *bus, + sd_bus_slot **slot, + const char *match, + sd_bus_message_handler_t callback, + void *userdata) { + + struct bus_match_component *components = NULL; + unsigned n_components = 0; + sd_bus_slot *s = NULL; + int r = 0; + + assert_return(bus, -EINVAL); + assert_return(match, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + r = bus_match_parse(match, &components, &n_components); + if (r < 0) + goto finish; + + s = bus_slot_allocate(bus, !slot, BUS_MATCH_CALLBACK, sizeof(struct match_callback), userdata); + if (!s) { + r = -ENOMEM; + goto finish; + } + + s->match_callback.callback = callback; + s->match_callback.cookie = ++bus->match_cookie; + + if (bus->bus_client) { + enum bus_match_scope scope; + + scope = bus_match_get_scope(components, n_components); + + /* Do not install server-side matches for matches + * against the local service, interface or bus + * path. */ + if (scope != BUS_MATCH_LOCAL) { + + if (!bus->is_kernel) { + /* When this is not a kernel transport, we + * store the original match string, so that we + * can use it to remove the match again */ + + s->match_callback.match_string = strdup(match); + if (!s->match_callback.match_string) { + r = -ENOMEM; + goto finish; + } + } + + r = bus_add_match_internal(bus, s->match_callback.match_string, components, n_components, s->match_callback.cookie); + if (r < 0) + goto finish; + + s->match_added = true; + } + } + + bus->match_callbacks_modified = true; + r = bus_match_add(&bus->match_callbacks, components, n_components, &s->match_callback); + if (r < 0) + goto finish; + + if (slot) + *slot = s; + s = NULL; + +finish: + bus_match_parse_free(components, n_components); + sd_bus_slot_unref(s); + + return r; +} + +int bus_remove_match_by_string( + sd_bus *bus, + const char *match, + sd_bus_message_handler_t callback, + void *userdata) { + + struct bus_match_component *components = NULL; + unsigned n_components = 0; + struct match_callback *c; + int r = 0; + + assert_return(bus, -EINVAL); + assert_return(match, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + r = bus_match_parse(match, &components, &n_components); + if (r < 0) + goto finish; + + r = bus_match_find(&bus->match_callbacks, components, n_components, NULL, NULL, &c); + if (r <= 0) + goto finish; + + sd_bus_slot_unref(container_of(c, sd_bus_slot, match_callback)); + +finish: + bus_match_parse_free(components, n_components); + + return r; +} + +bool bus_pid_changed(sd_bus *bus) { + assert(bus); + + /* We don't support people creating a bus connection and + * keeping it around over a fork(). Let's complain. */ + + return bus->original_pid != getpid(); +} + +static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_bus *bus = userdata; + int r; + + assert(bus); + + r = sd_bus_process(bus, NULL); + if (r < 0) + return r; + + return 1; +} + +static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { + sd_bus *bus = userdata; + int r; + + assert(bus); + + r = sd_bus_process(bus, NULL); + if (r < 0) + return r; + + return 1; +} + +static int prepare_callback(sd_event_source *s, void *userdata) { + sd_bus *bus = userdata; + int r, e; + usec_t until; + + assert(s); + assert(bus); + + e = sd_bus_get_events(bus); + if (e < 0) + return e; + + if (bus->output_fd != bus->input_fd) { + + r = sd_event_source_set_io_events(bus->input_io_event_source, e & POLLIN); + if (r < 0) + return r; + + r = sd_event_source_set_io_events(bus->output_io_event_source, e & POLLOUT); + if (r < 0) + return r; + } else { + r = sd_event_source_set_io_events(bus->input_io_event_source, e); + if (r < 0) + return r; + } + + r = sd_bus_get_timeout(bus, &until); + if (r < 0) + return r; + if (r > 0) { + int j; + + j = sd_event_source_set_time(bus->time_event_source, until); + if (j < 0) + return j; + } + + r = sd_event_source_set_enabled(bus->time_event_source, r > 0); + if (r < 0) + return r; + + return 1; +} + +static int quit_callback(sd_event_source *event, void *userdata) { + sd_bus *bus = userdata; + + assert(event); + + sd_bus_flush(bus); + sd_bus_close(bus); + + return 1; +} + +static int attach_io_events(sd_bus *bus) { + int r; + + assert(bus); + + if (bus->input_fd < 0) + return 0; + + if (!bus->event) + return 0; + + if (!bus->input_io_event_source) { + r = sd_event_add_io(bus->event, &bus->input_io_event_source, bus->input_fd, 0, io_callback, bus); + if (r < 0) + return r; + + r = sd_event_source_set_prepare(bus->input_io_event_source, prepare_callback); + if (r < 0) + return r; + + r = sd_event_source_set_priority(bus->input_io_event_source, bus->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(bus->input_io_event_source, "bus-input"); + } else + r = sd_event_source_set_io_fd(bus->input_io_event_source, bus->input_fd); + + if (r < 0) + return r; + + if (bus->output_fd != bus->input_fd) { + assert(bus->output_fd >= 0); + + if (!bus->output_io_event_source) { + r = sd_event_add_io(bus->event, &bus->output_io_event_source, bus->output_fd, 0, io_callback, bus); + if (r < 0) + return r; + + r = sd_event_source_set_priority(bus->output_io_event_source, bus->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(bus->input_io_event_source, "bus-output"); + } else + r = sd_event_source_set_io_fd(bus->output_io_event_source, bus->output_fd); + + if (r < 0) + return r; + } + + return 0; +} + +static void detach_io_events(sd_bus *bus) { + assert(bus); + + if (bus->input_io_event_source) { + sd_event_source_set_enabled(bus->input_io_event_source, SD_EVENT_OFF); + bus->input_io_event_source = sd_event_source_unref(bus->input_io_event_source); + } + + if (bus->output_io_event_source) { + sd_event_source_set_enabled(bus->output_io_event_source, SD_EVENT_OFF); + bus->output_io_event_source = sd_event_source_unref(bus->output_io_event_source); + } +} + +_public_ int sd_bus_attach_event(sd_bus *bus, sd_event *event, int priority) { + int r; + + assert_return(bus, -EINVAL); + assert_return(!bus->event, -EBUSY); + + assert(!bus->input_io_event_source); + assert(!bus->output_io_event_source); + assert(!bus->time_event_source); + + if (event) + bus->event = sd_event_ref(event); + else { + r = sd_event_default(&bus->event); + if (r < 0) + return r; + } + + bus->event_priority = priority; + + r = sd_event_add_time(bus->event, &bus->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, bus); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(bus->time_event_source, priority); + if (r < 0) + goto fail; + + r = sd_event_source_set_description(bus->time_event_source, "bus-time"); + if (r < 0) + goto fail; + + r = sd_event_add_exit(bus->event, &bus->quit_event_source, quit_callback, bus); + if (r < 0) + goto fail; + + r = sd_event_source_set_description(bus->quit_event_source, "bus-exit"); + if (r < 0) + goto fail; + + r = attach_io_events(bus); + if (r < 0) + goto fail; + + return 0; + +fail: + sd_bus_detach_event(bus); + return r; +} + +_public_ int sd_bus_detach_event(sd_bus *bus) { + assert_return(bus, -EINVAL); + + if (!bus->event) + return 0; + + detach_io_events(bus); + + if (bus->time_event_source) { + sd_event_source_set_enabled(bus->time_event_source, SD_EVENT_OFF); + bus->time_event_source = sd_event_source_unref(bus->time_event_source); + } + + if (bus->quit_event_source) { + sd_event_source_set_enabled(bus->quit_event_source, SD_EVENT_OFF); + bus->quit_event_source = sd_event_source_unref(bus->quit_event_source); + } + + bus->event = sd_event_unref(bus->event); + return 1; +} + +_public_ sd_event* sd_bus_get_event(sd_bus *bus) { + assert_return(bus, NULL); + + return bus->event; +} + +_public_ sd_bus_message* sd_bus_get_current_message(sd_bus *bus) { + assert_return(bus, NULL); + + return bus->current_message; +} + +_public_ sd_bus_slot* sd_bus_get_current_slot(sd_bus *bus) { + assert_return(bus, NULL); + + return bus->current_slot; +} + +_public_ sd_bus_message_handler_t sd_bus_get_current_handler(sd_bus *bus) { + assert_return(bus, NULL); + + return bus->current_handler; +} + +_public_ void* sd_bus_get_current_userdata(sd_bus *bus) { + assert_return(bus, NULL); + + return bus->current_userdata; +} + +static int bus_default(int (*bus_open)(sd_bus **), sd_bus **default_bus, sd_bus **ret) { + sd_bus *b = NULL; + int r; + + assert(bus_open); + assert(default_bus); + + if (!ret) + return !!*default_bus; + + if (*default_bus) { + *ret = sd_bus_ref(*default_bus); + return 0; + } + + r = bus_open(&b); + if (r < 0) + return r; + + b->default_bus_ptr = default_bus; + b->tid = gettid(); + *default_bus = b; + + *ret = b; + return 1; +} + +_public_ int sd_bus_default_system(sd_bus **ret) { + return bus_default(sd_bus_open_system, &default_system_bus, ret); +} + + +_public_ int sd_bus_default_user(sd_bus **ret) { + return bus_default(sd_bus_open_user, &default_user_bus, ret); +} + +_public_ int sd_bus_default(sd_bus **ret) { + + const char *e; + + /* Let's try our best to reuse another cached connection. If + * the starter bus type is set, connect via our normal + * connection logic, ignoring $DBUS_STARTER_ADDRESS, so that + * we can share the connection with the user/system default + * bus. */ + + e = secure_getenv("DBUS_STARTER_BUS_TYPE"); + if (e) { + if (streq(e, "system")) + return sd_bus_default_system(ret); + else if (STR_IN_SET(e, "user", "session")) + return sd_bus_default_user(ret); + } + + /* No type is specified, so we have not other option than to + * use the starter address if it is set. */ + + e = secure_getenv("DBUS_STARTER_ADDRESS"); + if (e) { + + return bus_default(sd_bus_open, &default_starter_bus, ret); + } + + /* Finally, if nothing is set use the cached connection for + * the right scope */ + + if (cg_pid_get_owner_uid(0, NULL) >= 0) + return sd_bus_default_user(ret); + else + return sd_bus_default_system(ret); +} + +_public_ int sd_bus_get_tid(sd_bus *b, pid_t *tid) { + assert_return(b, -EINVAL); + assert_return(tid, -EINVAL); + assert_return(!bus_pid_changed(b), -ECHILD); + + if (b->tid != 0) { + *tid = b->tid; + return 0; + } + + if (b->event) + return sd_event_get_tid(b->event, tid); + + return -ENXIO; +} + +_public_ int sd_bus_path_encode(const char *prefix, const char *external_id, char **ret_path) { + _cleanup_free_ char *e = NULL; + char *ret; + + assert_return(object_path_is_valid(prefix), -EINVAL); + assert_return(external_id, -EINVAL); + assert_return(ret_path, -EINVAL); + + e = bus_label_escape(external_id); + if (!e) + return -ENOMEM; + + ret = strjoin(prefix, "/", e, NULL); + if (!ret) + return -ENOMEM; + + *ret_path = ret; + return 0; +} + +_public_ int sd_bus_path_decode(const char *path, const char *prefix, char **external_id) { + const char *e; + char *ret; + + assert_return(object_path_is_valid(path), -EINVAL); + assert_return(object_path_is_valid(prefix), -EINVAL); + assert_return(external_id, -EINVAL); + + e = object_path_startswith(path, prefix); + if (!e) { + *external_id = NULL; + return 0; + } + + ret = bus_label_unescape(e); + if (!ret) + return -ENOMEM; + + *external_id = ret; + return 1; +} + +_public_ int sd_bus_path_encode_many(char **out, const char *path_template, ...) { + _cleanup_strv_free_ char **labels = NULL; + char *path, *path_pos, **label_pos; + const char *sep, *template_pos; + size_t path_length; + va_list list; + int r; + + assert_return(out, -EINVAL); + assert_return(path_template, -EINVAL); + + path_length = strlen(path_template); + + va_start(list, path_template); + for (sep = strchr(path_template, '%'); sep; sep = strchr(sep + 1, '%')) { + const char *arg; + char *label; + + arg = va_arg(list, const char *); + if (!arg) { + va_end(list); + return -EINVAL; + } + + label = bus_label_escape(arg); + if (!label) { + va_end(list); + return -ENOMEM; + } + + r = strv_consume(&labels, label); + if (r < 0) { + va_end(list); + return r; + } + + /* add label length, but account for the format character */ + path_length += strlen(label) - 1; + } + va_end(list); + + path = malloc(path_length + 1); + if (!path) + return -ENOMEM; + + path_pos = path; + label_pos = labels; + + for (template_pos = path_template; *template_pos; ) { + sep = strchrnul(template_pos, '%'); + path_pos = mempcpy(path_pos, template_pos, sep - template_pos); + if (!*sep) + break; + + path_pos = stpcpy(path_pos, *label_pos++); + template_pos = sep + 1; + } + + *path_pos = 0; + *out = path; + return 0; +} + +_public_ int sd_bus_path_decode_many(const char *path, const char *path_template, ...) { + _cleanup_strv_free_ char **labels = NULL; + const char *template_pos, *path_pos; + char **label_pos; + va_list list; + int r; + + /* + * This decodes an object-path based on a template argument. The + * template consists of a verbatim path, optionally including special + * directives: + * + * - Each occurrence of '%' in the template matches an arbitrary + * substring of a label in the given path. At most one such + * directive is allowed per label. For each such directive, the + * caller must provide an output parameter (char **) via va_arg. If + * NULL is passed, the given label is verified, but not returned. + * For each matched label, the *decoded* label is stored in the + * passed output argument, and the caller is responsible to free + * it. Note that the output arguments are only modified if the + * actualy path matched the template. Otherwise, they're left + * untouched. + * + * This function returns <0 on error, 0 if the path does not match the + * template, 1 if it matched. + */ + + assert_return(path, -EINVAL); + assert_return(path_template, -EINVAL); + + path_pos = path; + + for (template_pos = path_template; *template_pos; ) { + const char *sep; + size_t length; + char *label; + + /* verify everything until the next '%' matches verbatim */ + sep = strchrnul(template_pos, '%'); + length = sep - template_pos; + if (strncmp(path_pos, template_pos, length)) + return 0; + + path_pos += length; + template_pos += length; + + if (!*template_pos) + break; + + /* We found the next '%' character. Everything up until here + * matched. We now skip ahead to the end of this label and make + * sure it matches the tail of the label in the path. Then we + * decode the string in-between and save it for later use. */ + + ++template_pos; /* skip over '%' */ + + sep = strchrnul(template_pos, '/'); + length = sep - template_pos; /* length of suffix to match verbatim */ + + /* verify the suffixes match */ + sep = strchrnul(path_pos, '/'); + if (sep - path_pos < (ssize_t)length || + strncmp(sep - length, template_pos, length)) + return 0; + + template_pos += length; /* skip over matched label */ + length = sep - path_pos - length; /* length of sub-label to decode */ + + /* store unescaped label for later use */ + label = bus_label_unescape_n(path_pos, length); + if (!label) + return -ENOMEM; + + r = strv_consume(&labels, label); + if (r < 0) + return r; + + path_pos = sep; /* skip decoded label and suffix */ + } + + /* end of template must match end of path */ + if (*path_pos) + return 0; + + /* copy the labels over to the caller */ + va_start(list, path_template); + for (label_pos = labels; label_pos && *label_pos; ++label_pos) { + char **arg; + + arg = va_arg(list, char **); + if (arg) + *arg = *label_pos; + else + free(*label_pos); + } + va_end(list); + + free(labels); + labels = NULL; + return 1; +} + +_public_ int sd_bus_try_close(sd_bus *bus) { + int r; + + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (!bus->is_kernel) + return -EOPNOTSUPP; + + if (!BUS_IS_OPEN(bus->state)) + return -ENOTCONN; + + if (bus->rqueue_size > 0) + return -EBUSY; + + if (bus->wqueue_size > 0) + return -EBUSY; + + r = bus_kernel_try_close(bus); + if (r < 0) + return r; + + sd_bus_close(bus); + return 0; +} + +_public_ int sd_bus_get_description(sd_bus *bus, const char **description) { + assert_return(bus, -EINVAL); + assert_return(description, -EINVAL); + assert_return(bus->description, -ENXIO); + assert_return(!bus_pid_changed(bus), -ECHILD); + + *description = bus->description; + return 0; +} + +int bus_get_root_path(sd_bus *bus) { + int r; + + if (bus->cgroup_root) + return 0; + + r = cg_get_root_path(&bus->cgroup_root); + if (r == -ENOENT) { + bus->cgroup_root = strdup("/"); + if (!bus->cgroup_root) + return -ENOMEM; + + r = 0; + } + + return r; +} + +_public_ int sd_bus_get_scope(sd_bus *bus, const char **scope) { + int r; + + assert_return(bus, -EINVAL); + assert_return(scope, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (bus->is_kernel) { + _cleanup_free_ char *n = NULL; + const char *dash; + + r = bus_kernel_get_bus_name(bus, &n); + if (r < 0) + return r; + + if (streq(n, "0-system")) { + *scope = "system"; + return 0; + } + + dash = strchr(n, '-'); + if (streq_ptr(dash, "-user")) { + *scope = "user"; + return 0; + } + } + + if (bus->is_user) { + *scope = "user"; + return 0; + } + + if (bus->is_system) { + *scope = "system"; + return 0; + } + + return -ENODATA; +} + +_public_ int sd_bus_get_address(sd_bus *bus, const char **address) { + + assert_return(bus, -EINVAL); + assert_return(address, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + if (bus->address) { + *address = bus->address; + return 0; + } + + return -ENODATA; +} + +_public_ int sd_bus_get_creds_mask(sd_bus *bus, uint64_t *mask) { + assert_return(bus, -EINVAL); + assert_return(mask, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + *mask = bus->creds_mask; + return 0; +} + +_public_ int sd_bus_is_bus_client(sd_bus *bus) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return bus->bus_client; +} + +_public_ int sd_bus_is_server(sd_bus *bus) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return bus->is_server; +} + +_public_ int sd_bus_is_anonymous(sd_bus *bus) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return bus->anonymous_auth; +} + +_public_ int sd_bus_is_trusted(sd_bus *bus) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return bus->trusted; +} + +_public_ int sd_bus_is_monitor(sd_bus *bus) { + assert_return(bus, -EINVAL); + assert_return(!bus_pid_changed(bus), -ECHILD); + + return !!(bus->hello_flags & KDBUS_HELLO_MONITOR); +} + +static void flush_close(sd_bus *bus) { + if (!bus) + return; + + /* Flushes and closes the specified bus. We take a ref before, + * to ensure the flushing does not cause the bus to be + * unreferenced. */ + + sd_bus_flush_close_unref(sd_bus_ref(bus)); +} + +_public_ void sd_bus_default_flush_close(void) { + flush_close(default_starter_bus); + flush_close(default_user_bus); + flush_close(default_system_bus); +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-benchmark.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-benchmark.c new file mode 100644 index 0000000000..a222d36bb4 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-benchmark.c @@ -0,0 +1,371 @@ +/*** + 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 . +***/ + +#include + +#include + +#include "alloc-util.h" +#include "bus-internal.h" +#include "bus-kernel.h" +#include "bus-util.h" +#include "def.h" +#include "fd-util.h" +#include "time-util.h" +#include "util.h" + +#define MAX_SIZE (2*1024*1024) + +static usec_t arg_loop_usec = 100 * USEC_PER_MSEC; + +typedef enum Type { + TYPE_KDBUS, + TYPE_LEGACY, + TYPE_DIRECT, +} Type; + +static void server(sd_bus *b, size_t *result) { + int r; + + for (;;) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + r = sd_bus_process(b, &m); + assert_se(r >= 0); + + if (r == 0) + assert_se(sd_bus_wait(b, USEC_INFINITY) >= 0); + if (!m) + continue; + + if (sd_bus_message_is_method_call(m, "benchmark.server", "Ping")) + assert_se(sd_bus_reply_method_return(m, NULL) >= 0); + else if (sd_bus_message_is_method_call(m, "benchmark.server", "Work")) { + const void *p; + size_t sz; + + /* Make sure the mmap is mapped */ + assert_se(sd_bus_message_read_array(m, 'y', &p, &sz) > 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + } else if (sd_bus_message_is_method_call(m, "benchmark.server", "Exit")) { + uint64_t res; + assert_se(sd_bus_message_read(m, "t", &res) > 0); + + *result = res; + return; + + } else if (!sd_bus_message_is_signal(m, NULL, NULL)) + assert_not_reached("Unknown method"); + } +} + +static void transaction(sd_bus *b, size_t sz, const char *server_name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + uint8_t *p; + + assert_se(sd_bus_message_new_method_call(b, &m, server_name, "/", "benchmark.server", "Work") >= 0); + assert_se(sd_bus_message_append_array_space(m, 'y', sz, (void**) &p) >= 0); + + memset(p, 0x80, sz); + + assert_se(sd_bus_call(b, m, 0, NULL, &reply) >= 0); +} + +static void client_bisect(const char *address, const char *server_name) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *x = NULL; + size_t lsize, rsize, csize; + sd_bus *b; + int r; + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + r = sd_bus_call_method(b, server_name, "/", "benchmark.server", "Ping", NULL, NULL, NULL); + assert_se(r >= 0); + + lsize = 1; + rsize = MAX_SIZE; + + printf("SIZE\tCOPY\tMEMFD\n"); + + for (;;) { + usec_t t; + unsigned n_copying, n_memfd; + + csize = (lsize + rsize) / 2; + + if (csize <= lsize) + break; + + if (csize <= 0) + break; + + printf("%zu\t", csize); + + b->use_memfd = 0; + + t = now(CLOCK_MONOTONIC); + for (n_copying = 0;; n_copying++) { + transaction(b, csize, server_name); + if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) + break; + } + printf("%u\t", (unsigned) ((n_copying * USEC_PER_SEC) / arg_loop_usec)); + + b->use_memfd = -1; + + t = now(CLOCK_MONOTONIC); + for (n_memfd = 0;; n_memfd++) { + transaction(b, csize, server_name); + if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) + break; + } + printf("%u\n", (unsigned) ((n_memfd * USEC_PER_SEC) / arg_loop_usec)); + + if (n_copying == n_memfd) + break; + + if (n_copying > n_memfd) + lsize = csize; + else + rsize = csize; + } + + b->use_memfd = 1; + assert_se(sd_bus_message_new_method_call(b, &x, server_name, "/", "benchmark.server", "Exit") >= 0); + assert_se(sd_bus_message_append(x, "t", csize) >= 0); + assert_se(sd_bus_send(b, x, NULL) >= 0); + + sd_bus_unref(b); +} + +static void client_chart(Type type, const char *address, const char *server_name, int fd) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *x = NULL; + size_t csize; + sd_bus *b; + int r; + + r = sd_bus_new(&b); + assert_se(r >= 0); + + if (type == TYPE_DIRECT) { + r = sd_bus_set_fd(b, fd, fd); + assert_se(r >= 0); + } else { + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_set_bus_client(b, true); + assert_se(r >= 0); + } + + r = sd_bus_start(b); + assert_se(r >= 0); + + r = sd_bus_call_method(b, server_name, "/", "benchmark.server", "Ping", NULL, NULL, NULL); + assert_se(r >= 0); + + switch (type) { + case TYPE_KDBUS: + printf("SIZE\tCOPY\tMEMFD\n"); + break; + case TYPE_LEGACY: + printf("SIZE\tLEGACY\n"); + break; + case TYPE_DIRECT: + printf("SIZE\tDIRECT\n"); + break; + } + + for (csize = 1; csize <= MAX_SIZE; csize *= 2) { + usec_t t; + unsigned n_copying, n_memfd; + + printf("%zu\t", csize); + + if (type == TYPE_KDBUS) { + b->use_memfd = 0; + + t = now(CLOCK_MONOTONIC); + for (n_copying = 0;; n_copying++) { + transaction(b, csize, server_name); + if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) + break; + } + + printf("%u\t", (unsigned) ((n_copying * USEC_PER_SEC) / arg_loop_usec)); + + b->use_memfd = -1; + } + + t = now(CLOCK_MONOTONIC); + for (n_memfd = 0;; n_memfd++) { + transaction(b, csize, server_name); + if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) + break; + } + + printf("%u\n", (unsigned) ((n_memfd * USEC_PER_SEC) / arg_loop_usec)); + } + + b->use_memfd = 1; + assert_se(sd_bus_message_new_method_call(b, &x, server_name, "/", "benchmark.server", "Exit") >= 0); + assert_se(sd_bus_message_append(x, "t", csize) >= 0); + assert_se(sd_bus_send(b, x, NULL) >= 0); + + sd_bus_unref(b); +} + +int main(int argc, char *argv[]) { + enum { + MODE_BISECT, + MODE_CHART, + } mode = MODE_BISECT; + Type type = TYPE_KDBUS; + int i, pair[2] = { -1, -1 }; + _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL, *server_name = NULL; + _cleanup_close_ int bus_ref = -1; + const char *unique; + cpu_set_t cpuset; + size_t result; + sd_bus *b; + pid_t pid; + int r; + + for (i = 1; i < argc; i++) { + if (streq(argv[i], "chart")) { + mode = MODE_CHART; + continue; + } else if (streq(argv[i], "legacy")) { + type = TYPE_LEGACY; + continue; + } else if (streq(argv[i], "direct")) { + type = TYPE_DIRECT; + continue; + } + + assert_se(parse_sec(argv[i], &arg_loop_usec) >= 0); + } + + assert_se(!MODE_BISECT || TYPE_KDBUS); + + assert_se(arg_loop_usec > 0); + + if (type == TYPE_KDBUS) { + assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); + + bus_ref = bus_kernel_create_bus(name, false, &bus_name); + if (bus_ref == -ENOENT) + exit(EXIT_TEST_SKIP); + + assert_se(bus_ref >= 0); + + address = strappend("kernel:path=", bus_name); + assert_se(address); + } else if (type == TYPE_LEGACY) { + const char *e; + + e = secure_getenv("DBUS_SESSION_BUS_ADDRESS"); + assert_se(e); + + address = strdup(e); + assert_se(address); + } + + r = sd_bus_new(&b); + assert_se(r >= 0); + + if (type == TYPE_DIRECT) { + assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) >= 0); + + r = sd_bus_set_fd(b, pair[0], pair[0]); + assert_se(r >= 0); + + r = sd_bus_set_server(b, true, SD_ID128_NULL); + assert_se(r >= 0); + } else { + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_set_bus_client(b, true); + assert_se(r >= 0); + } + + r = sd_bus_start(b); + assert_se(r >= 0); + + if (type != TYPE_DIRECT) { + r = sd_bus_get_unique_name(b, &unique); + assert_se(r >= 0); + + server_name = strdup(unique); + assert_se(server_name); + } + + sync(); + setpriority(PRIO_PROCESS, 0, -19); + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) { + CPU_ZERO(&cpuset); + CPU_SET(0, &cpuset); + pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); + + safe_close(bus_ref); + sd_bus_unref(b); + + switch (mode) { + case MODE_BISECT: + client_bisect(address, server_name); + break; + + case MODE_CHART: + client_chart(type, address, server_name, pair[1]); + break; + } + + _exit(0); + } + + CPU_ZERO(&cpuset); + CPU_SET(1, &cpuset); + pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); + + server(b, &result); + + if (mode == MODE_BISECT) + printf("Copying/memfd are equally fast at %zu bytes\n", result); + + assert_se(waitpid(pid, NULL, 0) == pid); + + safe_close(pair[1]); + sd_bus_unref(b); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-chat.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-chat.c new file mode 100644 index 0000000000..1f028d2b23 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-chat.c @@ -0,0 +1,560 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-internal.h" +#include "bus-match.h" +#include "bus-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "log.h" +#include "macro.h" +#include "util.h" + +static int match_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + log_info("Match triggered! interface=%s member=%s", strna(sd_bus_message_get_interface(m)), strna(sd_bus_message_get_member(m))); + return 0; +} + +static int object_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + int r; + + if (sd_bus_message_is_method_error(m, NULL)) + return 0; + + if (sd_bus_message_is_method_call(m, "org.object.test", "Foobar")) { + log_info("Invoked Foobar() on %s", sd_bus_message_get_path(m)); + + r = sd_bus_reply_method_return(m, NULL); + if (r < 0) + return log_error_errno(r, "Failed to send reply: %m"); + + return 1; + } + + return 0; +} + +static int server_init(sd_bus **_bus) { + sd_bus *bus = NULL; + sd_id128_t id; + int r; + const char *unique; + + assert_se(_bus); + + r = sd_bus_open_user(&bus); + if (r < 0) { + log_error_errno(r, "Failed to connect to user bus: %m"); + goto fail; + } + + r = sd_bus_get_bus_id(bus, &id); + if (r < 0) { + log_error_errno(r, "Failed to get server ID: %m"); + goto fail; + } + + r = sd_bus_get_unique_name(bus, &unique); + if (r < 0) { + log_error_errno(r, "Failed to get unique name: %m"); + goto fail; + } + + log_info("Peer ID is " SD_ID128_FORMAT_STR ".", SD_ID128_FORMAT_VAL(id)); + log_info("Unique ID: %s", unique); + log_info("Can send file handles: %i", sd_bus_can_send(bus, 'h')); + + r = sd_bus_request_name(bus, "org.freedesktop.systemd.test", 0); + if (r < 0) { + log_error_errno(r, "Failed to acquire name: %m"); + goto fail; + } + + r = sd_bus_add_fallback(bus, NULL, "/foo/bar", object_callback, NULL); + if (r < 0) { + log_error_errno(r, "Failed to add object: %m"); + goto fail; + } + + r = sd_bus_add_match(bus, NULL, "type='signal',interface='foo.bar',member='Notify'", match_callback, NULL); + if (r < 0) { + log_error_errno(r, "Failed to add match: %m"); + goto fail; + } + + r = sd_bus_add_match(bus, NULL, "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged'", match_callback, NULL); + if (r < 0) { + log_error_errno(r, "Failed to add match: %m"); + goto fail; + } + + bus_match_dump(&bus->match_callbacks, 0); + + *_bus = bus; + return 0; + +fail: + sd_bus_unref(bus); + return r; +} + +static int server(sd_bus *bus) { + int r; + bool client1_gone = false, client2_gone = false; + + while (!client1_gone || !client2_gone) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + pid_t pid = 0; + const char *label = NULL; + + r = sd_bus_process(bus, &m); + if (r < 0) { + log_error_errno(r, "Failed to process requests: %m"); + goto fail; + } + + if (r == 0) { + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) { + log_error_errno(r, "Failed to wait: %m"); + goto fail; + } + + continue; + } + + if (!m) + continue; + + sd_bus_creds_get_pid(sd_bus_message_get_creds(m), &pid); + sd_bus_creds_get_selinux_context(sd_bus_message_get_creds(m), &label); + log_info("Got message! member=%s pid="PID_FMT" label=%s", + strna(sd_bus_message_get_member(m)), + pid, + strna(label)); + /* bus_message_dump(m); */ + /* sd_bus_message_rewind(m, true); */ + + if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "LowerCase")) { + const char *hello; + _cleanup_free_ char *lowercase = NULL; + + r = sd_bus_message_read(m, "s", &hello); + if (r < 0) { + log_error_errno(r, "Failed to get parameter: %m"); + goto fail; + } + + lowercase = strdup(hello); + if (!lowercase) { + r = log_oom(); + goto fail; + } + + ascii_strlower(lowercase); + + r = sd_bus_reply_method_return(m, "s", lowercase); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "ExitClient1")) { + + r = sd_bus_reply_method_return(m, NULL); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + + client1_gone = true; + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "ExitClient2")) { + + r = sd_bus_reply_method_return(m, NULL); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + + client2_gone = true; + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Slow")) { + + sleep(1); + + r = sd_bus_reply_method_return(m, NULL); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + + } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "FileDescriptor")) { + int fd; + static const char x = 'X'; + + r = sd_bus_message_read(m, "h", &fd); + if (r < 0) { + log_error_errno(r, "Failed to get parameter: %m"); + goto fail; + } + + log_info("Received fd=%d", fd); + + if (write(fd, &x, 1) < 0) { + log_error_errno(errno, "Failed to write to fd: %m"); + safe_close(fd); + goto fail; + } + + r = sd_bus_reply_method_return(m, NULL); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + + } else if (sd_bus_message_is_method_call(m, NULL, NULL)) { + + r = sd_bus_reply_method_error( + m, + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNKNOWN_METHOD, "Unknown method.")); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + } + } + + r = 0; + +fail: + if (bus) { + sd_bus_flush(bus); + sd_bus_unref(bus); + } + + return r; +} + +static void* client1(void*p) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *hello; + int r; + _cleanup_close_pair_ int pp[2] = { -1, -1 }; + char x; + + r = sd_bus_open_user(&bus); + if (r < 0) { + log_error_errno(r, "Failed to connect to user bus: %m"); + goto finish; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "LowerCase", + &error, + &reply, + "s", + "HELLO"); + if (r < 0) { + log_error_errno(r, "Failed to issue method call: %m"); + goto finish; + } + + r = sd_bus_message_read(reply, "s", &hello); + if (r < 0) { + log_error_errno(r, "Failed to get string: %m"); + goto finish; + } + + assert_se(streq(hello, "hello")); + + if (pipe2(pp, O_CLOEXEC|O_NONBLOCK) < 0) { + log_error_errno(errno, "Failed to allocate pipe: %m"); + r = -errno; + goto finish; + } + + log_info("Sending fd=%d", pp[1]); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "FileDescriptor", + &error, + NULL, + "h", + pp[1]); + if (r < 0) { + log_error_errno(r, "Failed to issue method call: %m"); + goto finish; + } + + errno = 0; + if (read(pp[0], &x, 1) <= 0) { + log_error("Failed to read from pipe: %s", errno ? strerror(errno) : "early read"); + goto finish; + } + + r = 0; + +finish: + if (bus) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *q; + + r = sd_bus_message_new_method_call( + bus, + &q, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "ExitClient1"); + if (r < 0) + log_error_errno(r, "Failed to allocate method call: %m"); + else + sd_bus_send(bus, q, NULL); + + } + + return INT_TO_PTR(r); +} + +static int quit_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + bool *x = userdata; + + log_error("Quit callback: %s", strerror(sd_bus_message_get_errno(m))); + + *x = 1; + return 1; +} + +static void* client2(void*p) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + bool quit = false; + const char *mid; + int r; + + r = sd_bus_open_user(&bus); + if (r < 0) { + log_error_errno(r, "Failed to connect to user bus: %m"); + goto finish; + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd.test", + "/foo/bar/waldo/piep", + "org.object.test", + "Foobar"); + if (r < 0) { + log_error_errno(r, "Failed to allocate method call: %m"); + goto finish; + } + + r = sd_bus_send(bus, m, NULL); + if (r < 0) { + log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); + goto finish; + } + + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_signal( + bus, + &m, + "/foobar", + "foo.bar", + "Notify"); + if (r < 0) { + log_error_errno(r, "Failed to allocate signal: %m"); + goto finish; + } + + r = sd_bus_send(bus, m, NULL); + if (r < 0) { + log_error("Failed to issue signal: %s", bus_error_message(&error, -r)); + goto finish; + } + + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.DBus.Peer", + "GetMachineId"); + if (r < 0) { + log_error_errno(r, "Failed to allocate method call: %m"); + goto finish; + } + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); + goto finish; + } + + r = sd_bus_message_read(reply, "s", &mid); + if (r < 0) { + log_error_errno(r, "Failed to parse machine ID: %m"); + goto finish; + } + + log_info("Machine ID is %s.", mid); + + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "Slow"); + if (r < 0) { + log_error_errno(r, "Failed to allocate method call: %m"); + goto finish; + } + + reply = sd_bus_message_unref(reply); + + r = sd_bus_call(bus, m, 200 * USEC_PER_MSEC, &error, &reply); + if (r < 0) + log_info("Failed to issue method call: %s", bus_error_message(&error, -r)); + else + log_info("Slow call succeed."); + + m = sd_bus_message_unref(m); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "Slow"); + if (r < 0) { + log_error_errno(r, "Failed to allocate method call: %m"); + goto finish; + } + + r = sd_bus_call_async(bus, NULL, m, quit_callback, &quit, 200 * USEC_PER_MSEC); + if (r < 0) { + log_info("Failed to issue method call: %s", bus_error_message(&error, -r)); + goto finish; + } + + while (!quit) { + r = sd_bus_process(bus, NULL); + if (r < 0) { + log_error_errno(r, "Failed to process requests: %m"); + goto finish; + } + if (r == 0) { + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) { + log_error_errno(r, "Failed to wait: %m"); + goto finish; + } + } + } + + r = 0; + +finish: + if (bus) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *q; + + r = sd_bus_message_new_method_call( + bus, + &q, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "ExitClient2"); + if (r < 0) { + log_error_errno(r, "Failed to allocate method call: %m"); + goto finish; + } + + (void) sd_bus_send(bus, q, NULL); + } + + return INT_TO_PTR(r); +} + +int main(int argc, char *argv[]) { + pthread_t c1, c2; + sd_bus *bus; + void *p; + int q, r; + + r = server_init(&bus); + if (r < 0) { + log_info("Failed to connect to bus, skipping tests."); + return EXIT_TEST_SKIP; + } + + log_info("Initialized..."); + + r = pthread_create(&c1, NULL, client1, bus); + if (r != 0) + return EXIT_FAILURE; + + r = pthread_create(&c2, NULL, client2, bus); + if (r != 0) + return EXIT_FAILURE; + + r = server(bus); + + q = pthread_join(c1, &p); + if (q != 0) + return EXIT_FAILURE; + if (PTR_TO_INT(p) < 0) + return EXIT_FAILURE; + + q = pthread_join(c2, &p); + if (q != 0) + return EXIT_FAILURE; + if (PTR_TO_INT(p) < 0) + return EXIT_FAILURE; + + if (r < 0) + return EXIT_FAILURE; + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-cleanup.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-cleanup.c new file mode 100644 index 0000000000..bd4a3fbf34 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-cleanup.c @@ -0,0 +1,95 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#include + +#include + +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-util.h" +#include "refcnt.h" + +static void test_bus_new(void) { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + + assert_se(sd_bus_new(&bus) == 0); + printf("after new: refcount %u\n", REFCNT_GET(bus->n_ref)); +} + +static int test_bus_open(void) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + r = sd_bus_open_system(&bus); + if (r == -ECONNREFUSED || r == -ENOENT) + return r; + + assert_se(r >= 0); + printf("after open: refcount %u\n", REFCNT_GET(bus->n_ref)); + + return 0; +} + +static void test_bus_new_method_call(void) { + sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + assert_se(sd_bus_open_system(&bus) >= 0); + + assert_se(sd_bus_message_new_method_call(bus, &m, "a.service.name", "/an/object/path", "an.interface.name", "AMethodName") >= 0); + + printf("after message_new_method_call: refcount %u\n", REFCNT_GET(bus->n_ref)); + + sd_bus_flush_close_unref(bus); + printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); +} + +static void test_bus_new_signal(void) { + sd_bus *bus = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + + assert_se(sd_bus_open_system(&bus) >= 0); + + assert_se(sd_bus_message_new_signal(bus, &m, "/an/object/path", "an.interface.name", "Name") >= 0); + + printf("after message_new_signal: refcount %u\n", REFCNT_GET(bus->n_ref)); + + sd_bus_flush_close_unref(bus); + printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); +} + +int main(int argc, char **argv) { + int r; + + log_parse_environment(); + log_open(); + + test_bus_new(); + r = test_bus_open(); + if (r < 0) { + log_info("Failed to connect to bus, skipping tests."); + return EXIT_TEST_SKIP; + } + + test_bus_new_method_call(); + test_bus_new_signal(); + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-creds.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-creds.c new file mode 100644 index 0000000000..c58b76c258 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-creds.c @@ -0,0 +1,50 @@ +/*** + 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 . +***/ + +#include + +#include "bus-dump.h" +#include "bus-util.h" +#include "cgroup-util.h" + +int main(int argc, char *argv[]) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + int r; + + if (cg_unified() == -ENOMEDIUM) { + puts("Skipping test: /sys/fs/cgroup/ not available"); + return EXIT_TEST_SKIP; + } + + r = sd_bus_creds_new_from_pid(&creds, 0, _SD_BUS_CREDS_ALL); + assert_se(r >= 0); + + bus_creds_dump(creds, NULL, true); + + creds = sd_bus_creds_unref(creds); + + r = sd_bus_creds_new_from_pid(&creds, 1, _SD_BUS_CREDS_ALL); + if (r != -EACCES) { + assert_se(r >= 0); + putchar('\n'); + bus_creds_dump(creds, NULL, true); + } + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-error.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-error.c new file mode 100644 index 0000000000..b9ceda7a8b --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-error.c @@ -0,0 +1,232 @@ +/*** + 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 . +***/ + +#include + +#include "bus-common-errors.h" +#include "bus-error.h" +#include "bus-util.h" +#include "errno-list.h" + +static void test_error(void) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL, second = SD_BUS_ERROR_NULL; + const sd_bus_error const_error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_EXISTS, "const error"); + const sd_bus_error temporarily_const_error = { + .name = SD_BUS_ERROR_ACCESS_DENIED, + .message = "oh! no", + ._need_free = -1 + }; + + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_set(&error, SD_BUS_ERROR_NOT_SUPPORTED, "xxx") == -EOPNOTSUPP); + assert_se(streq(error.name, SD_BUS_ERROR_NOT_SUPPORTED)); + assert_se(streq(error.message, "xxx")); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_NOT_SUPPORTED)); + assert_se(sd_bus_error_get_errno(&error) == EOPNOTSUPP); + assert_se(sd_bus_error_is_set(&error)); + sd_bus_error_free(&error); + + /* Check with no error */ + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_setf(&error, NULL, "yyy %i", -1) == 0); + assert_se(error.name == NULL); + assert_se(error.message == NULL); + assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND)); + assert_se(sd_bus_error_get_errno(&error) == 0); + assert_se(!sd_bus_error_is_set(&error)); + + assert_se(sd_bus_error_setf(&error, SD_BUS_ERROR_FILE_NOT_FOUND, "yyy %i", -1) == -ENOENT); + assert_se(streq(error.name, SD_BUS_ERROR_FILE_NOT_FOUND)); + assert_se(streq(error.message, "yyy -1")); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND)); + assert_se(sd_bus_error_get_errno(&error) == ENOENT); + assert_se(sd_bus_error_is_set(&error)); + + assert_se(!sd_bus_error_is_set(&second)); + assert_se(second._need_free == 0); + assert_se(error._need_free > 0); + assert_se(sd_bus_error_copy(&second, &error) == -ENOENT); + assert_se(second._need_free > 0); + assert_se(streq(error.name, second.name)); + assert_se(streq(error.message, second.message)); + assert_se(sd_bus_error_get_errno(&second) == ENOENT); + assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_FILE_NOT_FOUND)); + assert_se(sd_bus_error_is_set(&second)); + + sd_bus_error_free(&error); + sd_bus_error_free(&second); + + assert_se(!sd_bus_error_is_set(&second)); + assert_se(const_error._need_free == 0); + assert_se(sd_bus_error_copy(&second, &const_error) == -EEXIST); + assert_se(second._need_free == 0); + assert_se(streq(const_error.name, second.name)); + assert_se(streq(const_error.message, second.message)); + assert_se(sd_bus_error_get_errno(&second) == EEXIST); + assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_FILE_EXISTS)); + assert_se(sd_bus_error_is_set(&second)); + sd_bus_error_free(&second); + + assert_se(!sd_bus_error_is_set(&second)); + assert_se(temporarily_const_error._need_free < 0); + assert_se(sd_bus_error_copy(&second, &temporarily_const_error) == -EACCES); + assert_se(second._need_free > 0); + assert_se(streq(temporarily_const_error.name, second.name)); + assert_se(streq(temporarily_const_error.message, second.message)); + assert_se(sd_bus_error_get_errno(&second) == EACCES); + assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_ACCESS_DENIED)); + assert_se(sd_bus_error_is_set(&second)); + + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_set_const(&error, "System.Error.EUCLEAN", "Hallo") == -EUCLEAN); + assert_se(streq(error.name, "System.Error.EUCLEAN")); + assert_se(streq(error.message, "Hallo")); + assert_se(sd_bus_error_has_name(&error, "System.Error.EUCLEAN")); + assert_se(sd_bus_error_get_errno(&error) == EUCLEAN); + assert_se(sd_bus_error_is_set(&error)); + sd_bus_error_free(&error); + + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_set_errno(&error, EBUSY) == -EBUSY); + assert_se(streq(error.name, "System.Error.EBUSY")); + assert_se(streq(error.message, strerror(EBUSY))); + assert_se(sd_bus_error_has_name(&error, "System.Error.EBUSY")); + assert_se(sd_bus_error_get_errno(&error) == EBUSY); + assert_se(sd_bus_error_is_set(&error)); + sd_bus_error_free(&error); + + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_set_errnof(&error, EIO, "Waldi %c", 'X') == -EIO); + assert_se(streq(error.name, SD_BUS_ERROR_IO_ERROR)); + assert_se(streq(error.message, "Waldi X")); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR)); + assert_se(sd_bus_error_get_errno(&error) == EIO); + assert_se(sd_bus_error_is_set(&error)); + sd_bus_error_free(&error); + + /* Check with no error */ + assert_se(!sd_bus_error_is_set(&error)); + assert_se(sd_bus_error_set_errnof(&error, 0, "Waldi %c", 'X') == 0); + assert_se(error.name == NULL); + assert_se(error.message == NULL); + assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR)); + assert_se(sd_bus_error_get_errno(&error) == 0); + assert_se(!sd_bus_error_is_set(&error)); +} + +extern const sd_bus_error_map __start_BUS_ERROR_MAP[]; +extern const sd_bus_error_map __stop_BUS_ERROR_MAP[]; + +static void dump_mapping_table(void) { + const sd_bus_error_map *m; + + printf("----- errno mappings ------\n"); + m = __start_BUS_ERROR_MAP; + while (m < __stop_BUS_ERROR_MAP) { + + if (m->code == BUS_ERROR_MAP_END_MARKER) { + m = ALIGN8_PTR(m+1); + continue; + } + + printf("%s -> %i/%s\n", strna(m->name), m->code, strna(errno_to_name(m->code))); + m ++; + } + printf("---------------------------\n"); +} + +static void test_errno_mapping_standard(void) { + assert_se(sd_bus_error_set(NULL, "System.Error.EUCLEAN", NULL) == -EUCLEAN); + assert_se(sd_bus_error_set(NULL, "System.Error.EBUSY", NULL) == -EBUSY); + assert_se(sd_bus_error_set(NULL, "System.Error.EINVAL", NULL) == -EINVAL); + assert_se(sd_bus_error_set(NULL, "System.Error.WHATSIT", NULL) == -EIO); +} + +BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error", 5), + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-2", 52), + SD_BUS_ERROR_MAP_END +}; + +BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors2[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-3", 33), + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-4", 44), + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-33", 333), + SD_BUS_ERROR_MAP_END +}; + +static const sd_bus_error_map test_errors3[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-88", 888), + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-99", 999), + SD_BUS_ERROR_MAP_END +}; + +static const sd_bus_error_map test_errors4[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-77", 777), + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-78", 778), + SD_BUS_ERROR_MAP_END +}; + +static const sd_bus_error_map test_errors_bad1[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", 0), + SD_BUS_ERROR_MAP_END +}; + +static const sd_bus_error_map test_errors_bad2[] = { + SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", -1), + SD_BUS_ERROR_MAP_END +}; + +static void test_errno_mapping_custom(void) { + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error", NULL) == -5); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-2", NULL) == -52); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-x", NULL) == -EIO); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-33", NULL) == -333); + + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-88", NULL) == -EIO); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-99", NULL) == -EIO); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-77", NULL) == -EIO); + + assert_se(sd_bus_error_add_map(test_errors3) > 0); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-88", NULL) == -888); + assert_se(sd_bus_error_add_map(test_errors4) > 0); + assert_se(sd_bus_error_add_map(test_errors4) == 0); + assert_se(sd_bus_error_add_map(test_errors3) == 0); + + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-99", NULL) == -999); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-77", NULL) == -777); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-78", NULL) == -778); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-2", NULL) == -52); + assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-y", NULL) == -EIO); + + assert_se(sd_bus_error_set(NULL, BUS_ERROR_NO_SUCH_UNIT, NULL) == -ENOENT); + + assert_se(sd_bus_error_add_map(test_errors_bad1) == -EINVAL); + assert_se(sd_bus_error_add_map(test_errors_bad2) == -EINVAL); +} + +int main(int argc, char *argv[]) { + dump_mapping_table(); + + test_error(); + test_errno_mapping_standard(); + test_errno_mapping_custom(); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-gvariant.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-gvariant.c new file mode 100644 index 0000000000..3c9ec9fef0 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-gvariant.c @@ -0,0 +1,224 @@ +/*** + 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 . +***/ + +#ifdef HAVE_GLIB +#include +#endif + +#include + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-gvariant.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-util.h" +#include "macro.h" +#include "util.h" + +static void test_bus_gvariant_is_fixed_size(void) { + assert_se(bus_gvariant_is_fixed_size("") > 0); + assert_se(bus_gvariant_is_fixed_size("()") > 0); + assert_se(bus_gvariant_is_fixed_size("y") > 0); + assert_se(bus_gvariant_is_fixed_size("u") > 0); + assert_se(bus_gvariant_is_fixed_size("b") > 0); + assert_se(bus_gvariant_is_fixed_size("n") > 0); + assert_se(bus_gvariant_is_fixed_size("q") > 0); + assert_se(bus_gvariant_is_fixed_size("i") > 0); + assert_se(bus_gvariant_is_fixed_size("t") > 0); + assert_se(bus_gvariant_is_fixed_size("d") > 0); + assert_se(bus_gvariant_is_fixed_size("s") == 0); + assert_se(bus_gvariant_is_fixed_size("o") == 0); + assert_se(bus_gvariant_is_fixed_size("g") == 0); + assert_se(bus_gvariant_is_fixed_size("h") > 0); + assert_se(bus_gvariant_is_fixed_size("ay") == 0); + assert_se(bus_gvariant_is_fixed_size("v") == 0); + assert_se(bus_gvariant_is_fixed_size("(u)") > 0); + assert_se(bus_gvariant_is_fixed_size("(uuuuy)") > 0); + assert_se(bus_gvariant_is_fixed_size("(uusuuy)") == 0); + assert_se(bus_gvariant_is_fixed_size("a{ss}") == 0); + assert_se(bus_gvariant_is_fixed_size("((u)yyy(b(iiii)))") > 0); + assert_se(bus_gvariant_is_fixed_size("((u)yyy(b(iiivi)))") == 0); +} + +static void test_bus_gvariant_get_size(void) { + assert_se(bus_gvariant_get_size("") == 0); + assert_se(bus_gvariant_get_size("()") == 1); + assert_se(bus_gvariant_get_size("y") == 1); + assert_se(bus_gvariant_get_size("u") == 4); + assert_se(bus_gvariant_get_size("b") == 1); + assert_se(bus_gvariant_get_size("n") == 2); + assert_se(bus_gvariant_get_size("q") == 2); + assert_se(bus_gvariant_get_size("i") == 4); + assert_se(bus_gvariant_get_size("t") == 8); + assert_se(bus_gvariant_get_size("d") == 8); + assert_se(bus_gvariant_get_size("s") < 0); + assert_se(bus_gvariant_get_size("o") < 0); + assert_se(bus_gvariant_get_size("g") < 0); + assert_se(bus_gvariant_get_size("h") == 4); + assert_se(bus_gvariant_get_size("ay") < 0); + assert_se(bus_gvariant_get_size("v") < 0); + assert_se(bus_gvariant_get_size("(u)") == 4); + assert_se(bus_gvariant_get_size("(uuuuy)") == 20); + assert_se(bus_gvariant_get_size("(uusuuy)") < 0); + assert_se(bus_gvariant_get_size("a{ss}") < 0); + assert_se(bus_gvariant_get_size("((u)yyy(b(iiii)))") == 28); + assert_se(bus_gvariant_get_size("((u)yyy(b(iiivi)))") < 0); + assert_se(bus_gvariant_get_size("((b)(t))") == 16); + assert_se(bus_gvariant_get_size("((b)(b)(t))") == 16); + assert_se(bus_gvariant_get_size("(bt)") == 16); + assert_se(bus_gvariant_get_size("((t)(b))") == 16); + assert_se(bus_gvariant_get_size("(tb)") == 16); + assert_se(bus_gvariant_get_size("((b)(b))") == 2); + assert_se(bus_gvariant_get_size("((t)(t))") == 16); +} + +static void test_bus_gvariant_get_alignment(void) { + assert_se(bus_gvariant_get_alignment("") == 1); + assert_se(bus_gvariant_get_alignment("()") == 1); + assert_se(bus_gvariant_get_alignment("y") == 1); + assert_se(bus_gvariant_get_alignment("b") == 1); + assert_se(bus_gvariant_get_alignment("u") == 4); + assert_se(bus_gvariant_get_alignment("s") == 1); + assert_se(bus_gvariant_get_alignment("o") == 1); + assert_se(bus_gvariant_get_alignment("g") == 1); + assert_se(bus_gvariant_get_alignment("v") == 8); + assert_se(bus_gvariant_get_alignment("h") == 4); + assert_se(bus_gvariant_get_alignment("i") == 4); + assert_se(bus_gvariant_get_alignment("t") == 8); + assert_se(bus_gvariant_get_alignment("x") == 8); + assert_se(bus_gvariant_get_alignment("q") == 2); + assert_se(bus_gvariant_get_alignment("n") == 2); + assert_se(bus_gvariant_get_alignment("d") == 8); + assert_se(bus_gvariant_get_alignment("ay") == 1); + assert_se(bus_gvariant_get_alignment("as") == 1); + assert_se(bus_gvariant_get_alignment("au") == 4); + assert_se(bus_gvariant_get_alignment("an") == 2); + assert_se(bus_gvariant_get_alignment("ans") == 2); + assert_se(bus_gvariant_get_alignment("ant") == 8); + assert_se(bus_gvariant_get_alignment("(ss)") == 1); + assert_se(bus_gvariant_get_alignment("(ssu)") == 4); + assert_se(bus_gvariant_get_alignment("a(ssu)") == 4); + assert_se(bus_gvariant_get_alignment("(u)") == 4); + assert_se(bus_gvariant_get_alignment("(uuuuy)") == 4); + assert_se(bus_gvariant_get_alignment("(uusuuy)") == 4); + assert_se(bus_gvariant_get_alignment("a{ss}") == 1); + assert_se(bus_gvariant_get_alignment("((u)yyy(b(iiii)))") == 4); + assert_se(bus_gvariant_get_alignment("((u)yyy(b(iiivi)))") == 8); + assert_se(bus_gvariant_get_alignment("((b)(t))") == 8); + assert_se(bus_gvariant_get_alignment("((b)(b)(t))") == 8); + assert_se(bus_gvariant_get_alignment("(bt)") == 8); + assert_se(bus_gvariant_get_alignment("((t)(b))") == 8); + assert_se(bus_gvariant_get_alignment("(tb)") == 8); + assert_se(bus_gvariant_get_alignment("((b)(b))") == 1); + assert_se(bus_gvariant_get_alignment("((t)(t))") == 8); +} + +static void test_marshal(void) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *n = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ void *blob; + size_t sz; + int r; + + r = sd_bus_open_system(&bus); + if (r < 0) + exit(EXIT_TEST_SKIP); + + bus->message_version = 2; /* dirty hack to enable gvariant */ + + assert_se(sd_bus_message_new_method_call(bus, &m, "a.service.name", "/an/object/path/which/is/really/really/long/so/that/we/hit/the/eight/bit/boundary/by/quite/some/margin/to/test/this/stuff/that/it/really/works", "an.interface.name", "AMethodName") >= 0); + + assert_cc(sizeof(struct bus_header) == 16); + + assert_se(sd_bus_message_append(m, + "a(usv)", 3, + 4711, "first-string-parameter", "(st)", "X", (uint64_t) 1111, + 4712, "second-string-parameter", "(a(si))", 2, "Y", 5, "Z", 6, + 4713, "third-string-parameter", "(uu)", 1, 2) >= 0); + + assert_se(bus_message_seal(m, 4711, 0) >= 0); + +#ifdef HAVE_GLIB + { + GVariant *v; + char *t; + +#if !defined(GLIB_VERSION_2_36) + g_type_init(); +#endif + + v = g_variant_new_from_data(G_VARIANT_TYPE("(yyyyuta{tv})"), m->header, sizeof(struct bus_header) + m->fields_size, false, NULL, NULL); + assert_se(g_variant_is_normal_form(v)); + t = g_variant_print(v, TRUE); + printf("%s\n", t); + g_free(t); + g_variant_unref(v); + + v = g_variant_new_from_data(G_VARIANT_TYPE("(a(usv))"), m->body.data, m->user_body_size, false, NULL, NULL); + assert_se(g_variant_is_normal_form(v)); + t = g_variant_print(v, TRUE); + printf("%s\n", t); + g_free(t); + g_variant_unref(v); + } +#endif + + assert_se(bus_message_dump(m, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0); + + assert_se(bus_message_get_blob(m, &blob, &sz) >= 0); + +#ifdef HAVE_GLIB + { + GVariant *v; + char *t; + + v = g_variant_new_from_data(G_VARIANT_TYPE("(yyyyuta{tv}v)"), blob, sz, false, NULL, NULL); + assert_se(g_variant_is_normal_form(v)); + t = g_variant_print(v, TRUE); + printf("%s\n", t); + g_free(t); + g_variant_unref(v); + } +#endif + + assert_se(bus_message_from_malloc(bus, blob, sz, NULL, 0, NULL, &n) >= 0); + blob = NULL; + + assert_se(bus_message_dump(n, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0); + + m = sd_bus_message_unref(m); + + assert_se(sd_bus_message_new_method_call(bus, &m, "a.x", "/a/x", "a.x", "Ax") >= 0); + + assert_se(sd_bus_message_append(m, "as", 0) >= 0); + + assert_se(bus_message_seal(m, 4712, 0) >= 0); + assert_se(bus_message_dump(m, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0); +} + +int main(int argc, char *argv[]) { + + test_bus_gvariant_is_fixed_size(); + test_bus_gvariant_get_size(); + test_bus_gvariant_get_alignment(); + test_marshal(); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-introspect.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-introspect.c new file mode 100644 index 0000000000..4425cfae26 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-introspect.c @@ -0,0 +1,63 @@ +/*** + 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 . +***/ + +#include "bus-introspect.h" +#include "log.h" + +static int prop_get(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { + return -EINVAL; +} + +static int prop_set(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { + return -EINVAL; +} + +static const sd_bus_vtable vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("Hello", "ssas", "a(uu)", NULL, 0), + SD_BUS_METHOD("DeprecatedHello", "", "", NULL, SD_BUS_VTABLE_DEPRECATED), + SD_BUS_METHOD("DeprecatedHelloNoReply", "", "", NULL, SD_BUS_VTABLE_DEPRECATED|SD_BUS_VTABLE_METHOD_NO_REPLY), + SD_BUS_SIGNAL("Wowza", "sss", 0), + SD_BUS_SIGNAL("DeprecatedWowza", "ut", SD_BUS_VTABLE_DEPRECATED), + SD_BUS_WRITABLE_PROPERTY("AProperty", "s", prop_get, prop_set, 0, 0), + SD_BUS_PROPERTY("AReadOnlyDeprecatedProperty", "(ut)", prop_get, 0, SD_BUS_VTABLE_DEPRECATED), + SD_BUS_PROPERTY("ChangingProperty", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Invalidating", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_PROPERTY("Constant", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EXPLICIT), + SD_BUS_VTABLE_END +}; + +int main(int argc, char *argv[]) { + struct introspect intro; + + log_set_max_level(LOG_DEBUG); + + assert_se(introspect_begin(&intro, false) >= 0); + + fprintf(intro.f, " \n"); + assert_se(introspect_write_interface(&intro, vtable) >= 0); + fputs(" \n", intro.f); + + fflush(intro.f); + fputs(intro.introspection, stdout); + + introspect_free(&intro); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel-bloom.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel-bloom.c new file mode 100644 index 0000000000..f16e14a310 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel-bloom.c @@ -0,0 +1,141 @@ +/*** + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "bus-kernel.h" +#include "bus-util.h" +#include "fd-util.h" +#include "log.h" +#include "util.h" + +static int test_match(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + int *found = userdata; + + *found = 1; + + return 0; +} + +static void test_one( + const char *path, + const char *interface, + const char *member, + bool as_list, + const char *arg0, + const char *match, + bool good) { + + _cleanup_close_ int bus_ref = -1; + _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + sd_bus *a, *b; + int r, found = 0; + + assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); + + bus_ref = bus_kernel_create_bus(name, false, &bus_name); + if (bus_ref == -ENOENT) + exit(EXIT_TEST_SKIP); + + assert_se(bus_ref >= 0); + + address = strappend("kernel:path=", bus_name); + assert_se(address); + + r = sd_bus_new(&a); + assert_se(r >= 0); + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_address(a, address); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_start(a); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + log_debug("match"); + r = sd_bus_add_match(b, NULL, match, test_match, &found); + assert_se(r >= 0); + + log_debug("signal"); + + if (as_list) + r = sd_bus_emit_signal(a, path, interface, member, "as", 1, arg0); + else + r = sd_bus_emit_signal(a, path, interface, member, "s", arg0); + assert_se(r >= 0); + + r = sd_bus_process(b, &m); + assert_se(r >= 0 && good == !!found); + + sd_bus_unref(a); + sd_bus_unref(b); +} + +int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo/tuut'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "interface='waldo.com'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "member='Piep'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "member='Pi_ep'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "arg0='foobar'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "arg0='foo_bar'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0='foobar'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0='foo_bar'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0has='foobar'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0has='foo_bar'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo',interface='waldo.com',member='Piep',arg0='foobar'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo',interface='waldo.com',member='Piep',arg0='foobar2'", false); + + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo/quux'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/bar/waldo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/bar'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/quux'", false); + test_one("/", "waldo.com", "Piep", false, "foobar", "path_namespace='/'", true); + + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo/'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/bar/waldo/'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/'", true); + + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo/bar/waldo", "arg0path='/foo/'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo", "arg0path='/foo'", true); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo", "arg0path='/foo/bar/waldo'", false); + test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo/", "arg0path='/foo/bar/waldo'", true); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel.c new file mode 100644 index 0000000000..2a5ba60cc9 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-kernel.c @@ -0,0 +1,190 @@ +/*** + 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 . +***/ + +#include + +#include + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-kernel.h" +#include "bus-util.h" +#include "fd-util.h" +#include "log.h" +#include "util.h" + +int main(int argc, char *argv[]) { + _cleanup_close_ int bus_ref = -1; + _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL, *bname = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *ua = NULL, *ub = NULL, *the_string = NULL; + sd_bus *a, *b; + int r, pipe_fds[2]; + const char *nn; + + log_set_max_level(LOG_DEBUG); + + assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); + + bus_ref = bus_kernel_create_bus(name, false, &bus_name); + if (bus_ref == -ENOENT) + return EXIT_TEST_SKIP; + + assert_se(bus_ref >= 0); + + address = strappend("kernel:path=", bus_name); + assert_se(address); + + r = sd_bus_new(&a); + assert_se(r >= 0); + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_description(a, "a"); + assert_se(r >= 0); + + r = sd_bus_set_address(a, address); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + assert_se(sd_bus_negotiate_timestamp(a, 1) >= 0); + assert_se(sd_bus_negotiate_creds(a, true, _SD_BUS_CREDS_ALL) >= 0); + + assert_se(sd_bus_negotiate_timestamp(b, 0) >= 0); + assert_se(sd_bus_negotiate_creds(b, true, 0) >= 0); + + r = sd_bus_start(a); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + assert_se(sd_bus_negotiate_timestamp(b, 1) >= 0); + assert_se(sd_bus_negotiate_creds(b, true, _SD_BUS_CREDS_ALL) >= 0); + + r = sd_bus_get_unique_name(a, &ua); + assert_se(r >= 0); + printf("unique a: %s\n", ua); + + r = sd_bus_get_description(a, &nn); + assert_se(r >= 0); + printf("name of a: %s\n", nn); + + r = sd_bus_get_unique_name(b, &ub); + assert_se(r >= 0); + printf("unique b: %s\n", ub); + + r = sd_bus_get_description(b, &nn); + assert_se(r >= 0); + printf("name of b: %s\n", nn); + + assert_se(bus_kernel_get_bus_name(b, &bname) >= 0); + assert_se(endswith(bname, name)); + + r = sd_bus_call_method(a, "this.doesnt.exist", "/foo", "meh.mah", "muh", &error, NULL, "s", "yayayay"); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_SERVICE_UNKNOWN)); + assert_se(r == -EHOSTUNREACH); + + r = sd_bus_add_match(b, NULL, "interface='waldo.com',member='Piep'", NULL, NULL); + assert_se(r >= 0); + + r = sd_bus_emit_signal(a, "/foo/bar/waldo", "waldo.com", "Piep", "sss", "I am a string", "/this/is/a/path", "and.this.a.domain.name"); + assert_se(r >= 0); + + r = sd_bus_try_close(b); + assert_se(r == -EBUSY); + + r = sd_bus_process_priority(b, -10, &m); + assert_se(r == 0); + + r = sd_bus_process(b, &m); + assert_se(r > 0); + assert_se(m); + + bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + assert_se(sd_bus_message_rewind(m, true) >= 0); + + r = sd_bus_message_read(m, "s", &the_string); + assert_se(r >= 0); + assert_se(streq(the_string, "I am a string")); + + sd_bus_message_unref(m); + m = NULL; + + r = sd_bus_request_name(a, "net.x0pointer.foobar", 0); + assert_se(r >= 0); + + r = sd_bus_message_new_method_call(b, &m, "net.x0pointer.foobar", "/a/path", "an.inter.face", "AMethod"); + assert_se(r >= 0); + + assert_se(pipe2(pipe_fds, O_CLOEXEC) >= 0); + + assert_se(write(pipe_fds[1], "x", 1) == 1); + + pipe_fds[1] = safe_close(pipe_fds[1]); + + r = sd_bus_message_append(m, "h", pipe_fds[0]); + assert_se(r >= 0); + + pipe_fds[0] = safe_close(pipe_fds[0]); + + r = sd_bus_send(b, m, NULL); + assert_se(r >= 0); + + for (;;) { + sd_bus_message_unref(m); + m = NULL; + r = sd_bus_process(a, &m); + assert_se(r > 0); + assert_se(m); + + bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + assert_se(sd_bus_message_rewind(m, true) >= 0); + + if (sd_bus_message_is_method_call(m, "an.inter.face", "AMethod")) { + int fd; + char x; + + r = sd_bus_message_read(m, "h", &fd); + assert_se(r >= 0); + + assert_se(read(fd, &x, 1) == 1); + assert_se(x == 'x'); + break; + } + } + + r = sd_bus_release_name(a, "net.x0pointer.foobar"); + assert_se(r >= 0); + + r = sd_bus_release_name(a, "net.x0pointer.foobar"); + assert_se(r == -ESRCH); + + r = sd_bus_try_close(a); + assert_se(r >= 0); + + sd_bus_unref(a); + sd_bus_unref(b); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-marshal.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-marshal.c new file mode 100644 index 0000000000..45db4764a0 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-marshal.c @@ -0,0 +1,432 @@ +/*** + 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 . +***/ + +#include +#include + +#ifdef HAVE_GLIB +#include +#endif + +#ifdef HAVE_DBUS +#include +#endif + +#include + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-label.h" +#include "bus-message.h" +#include "bus-util.h" +#include "fd-util.h" +#include "hexdecoct.h" +#include "log.h" +#include "util.h" + +static void test_bus_path_encode_unique(void) { + _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL; + + assert_se(bus_path_encode_unique(NULL, "/foo/bar", "some.sender", "a.suffix", &a) >= 0 && streq_ptr(a, "/foo/bar/some_2esender/a_2esuffix")); + assert_se(bus_path_decode_unique(a, "/foo/bar", &b, &c) > 0 && streq_ptr(b, "some.sender") && streq_ptr(c, "a.suffix")); + assert_se(bus_path_decode_unique(a, "/bar/foo", &d, &d) == 0 && !d); + assert_se(bus_path_decode_unique("/foo/bar/onlyOneSuffix", "/foo/bar", &d, &d) == 0 && !d); + assert_se(bus_path_decode_unique("/foo/bar/_/_", "/foo/bar", &d, &e) > 0 && streq_ptr(d, "") && streq_ptr(e, "")); +} + +static void test_bus_path_encode(void) { + _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *f = NULL; + + assert_se(sd_bus_path_encode("/foo/bar", "waldo", &a) >= 0 && streq(a, "/foo/bar/waldo")); + assert_se(sd_bus_path_decode(a, "/waldo", &b) == 0 && b == NULL); + assert_se(sd_bus_path_decode(a, "/foo/bar", &b) > 0 && streq(b, "waldo")); + + assert_se(sd_bus_path_encode("xxxx", "waldo", &c) < 0); + assert_se(sd_bus_path_encode("/foo/", "waldo", &c) < 0); + + assert_se(sd_bus_path_encode("/foo/bar", "", &c) >= 0 && streq(c, "/foo/bar/_")); + assert_se(sd_bus_path_decode(c, "/foo/bar", &d) > 0 && streq(d, "")); + + assert_se(sd_bus_path_encode("/foo/bar", "foo.bar", &e) >= 0 && streq(e, "/foo/bar/foo_2ebar")); + assert_se(sd_bus_path_decode(e, "/foo/bar", &f) > 0 && streq(f, "foo.bar")); +} + +static void test_bus_path_encode_many(void) { + _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *f = NULL; + + assert_se(sd_bus_path_decode_many("/foo/bar", "/prefix/%", NULL) == 0); + assert_se(sd_bus_path_decode_many("/prefix/bar", "/prefix/%bar", NULL) == 1); + assert_se(sd_bus_path_decode_many("/foo/bar", "/prefix/%/suffix", NULL) == 0); + assert_se(sd_bus_path_decode_many("/prefix/foobar/suffix", "/prefix/%/suffix", &a) == 1 && streq_ptr(a, "foobar")); + assert_se(sd_bus_path_decode_many("/prefix/one_foo_two/mid/three_bar_four/suffix", "/prefix/one_%_two/mid/three_%_four/suffix", &b, &c) == 1 && streq_ptr(b, "foo") && streq_ptr(c, "bar")); + assert_se(sd_bus_path_decode_many("/prefix/one_foo_two/mid/three_bar_four/suffix", "/prefix/one_%_two/mid/three_%_four/suffix", NULL, &d) == 1 && streq_ptr(d, "bar")); + + assert_se(sd_bus_path_decode_many("/foo/bar", "/foo/bar/%", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/bar%", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/bar", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%bar", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/bar/suffix") == 1); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%%/suffix", NULL, NULL) == 0); /* multiple '%' are treated verbatim */ + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/suffi", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/suffix", &e) == 1 && streq_ptr(e, "bar")); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/%", NULL, NULL) == 1); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%/%", NULL, NULL, NULL) == 1); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "%/%/%", NULL, NULL, NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%", NULL, NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%/", NULL, NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%", NULL) == 0); + assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "%", NULL) == 0); + + assert_se(sd_bus_path_encode_many(&f, "/prefix/one_%_two/mid/three_%_four/suffix", "foo", "bar") >= 0 && streq_ptr(f, "/prefix/one_foo_two/mid/three_bar_four/suffix")); +} + +static void test_bus_label_escape_one(const char *a, const char *b) { + _cleanup_free_ char *t = NULL, *x = NULL, *y = NULL; + + assert_se(t = bus_label_escape(a)); + assert_se(streq(t, b)); + + assert_se(x = bus_label_unescape(t)); + assert_se(streq(a, x)); + + assert_se(y = bus_label_unescape(b)); + assert_se(streq(a, y)); +} + +static void test_bus_label_escape(void) { + test_bus_label_escape_one("foo123bar", "foo123bar"); + test_bus_label_escape_one("foo.bar", "foo_2ebar"); + test_bus_label_escape_one("foo_2ebar", "foo_5f2ebar"); + test_bus_label_escape_one("", "_"); + test_bus_label_escape_one("_", "_5f"); + test_bus_label_escape_one("1", "_31"); + test_bus_label_escape_one(":1", "_3a1"); +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *copy = NULL; + int r, boolean; + const char *x, *x2, *y, *z, *a, *b, *c, *d, *a_signature; + uint8_t u, v; + void *buffer = NULL; + size_t sz; + char *h; + const int32_t integer_array[] = { -1, -2, 0, 1, 2 }, *return_array; + char *s; + _cleanup_free_ char *first = NULL, *second = NULL, *third = NULL; + _cleanup_fclose_ FILE *ms = NULL; + size_t first_size = 0, second_size = 0, third_size = 0; + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + double dbl; + uint64_t u64; + + r = sd_bus_default_system(&bus); + if (r < 0) + return EXIT_TEST_SKIP; + + r = sd_bus_message_new_method_call(bus, &m, "foobar.waldo", "/", "foobar.waldo", "Piep"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, ""); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "s", "a string"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "s", NULL); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "asg", 2, "string #1", "string #2", "sba(tt)ss"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "sass", "foobar", 5, "foo", "bar", "waldo", "piep", "pap", "after"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "a{yv}", 2, 3, "s", "foo", 5, "s", "waldo"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "y(ty)y(yt)y", 8, 777ULL, 7, 9, 77, 7777ULL, 10); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "()"); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "ba(ss)", 255, 3, "aaa", "1", "bbb", "2", "ccc", "3"); + assert_se(r >= 0); + + r = sd_bus_message_open_container(m, 'a', "s"); + assert_se(r >= 0); + + r = sd_bus_message_append_basic(m, 's', "foobar"); + assert_se(r >= 0); + + r = sd_bus_message_append_basic(m, 's', "waldo"); + assert_se(r >= 0); + + r = sd_bus_message_close_container(m); + assert_se(r >= 0); + + r = sd_bus_message_append_string_space(m, 5, &s); + assert_se(r >= 0); + strcpy(s, "hallo"); + + r = sd_bus_message_append_array(m, 'i', integer_array, sizeof(integer_array)); + assert_se(r >= 0); + + r = sd_bus_message_append_array(m, 'u', NULL, 0); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "a(stdo)", 1, "foo", 815ULL, 47.0, "/"); + assert_se(r >= 0); + + r = bus_message_seal(m, 4711, 0); + assert_se(r >= 0); + + bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + ms = open_memstream(&first, &first_size); + bus_message_dump(m, ms, 0); + fflush(ms); + assert_se(!ferror(ms)); + + r = bus_message_get_blob(m, &buffer, &sz); + assert_se(r >= 0); + + h = hexmem(buffer, sz); + assert_se(h); + + log_info("message size = %zu, contents =\n%s", sz, h); + free(h); + +#ifdef HAVE_GLIB + { + GDBusMessage *g; + char *p; + +#if !defined(GLIB_VERSION_2_36) + g_type_init(); +#endif + + g = g_dbus_message_new_from_blob(buffer, sz, 0, NULL); + p = g_dbus_message_print(g, 0); + log_info("%s", p); + g_free(p); + g_object_unref(g); + } +#endif + +#ifdef HAVE_DBUS + { + DBusMessage *w; + DBusError error; + + dbus_error_init(&error); + + w = dbus_message_demarshal(buffer, sz, &error); + if (!w) + log_error("%s", error.message); + else + dbus_message_unref(w); + + dbus_error_free(&error); + } +#endif + + m = sd_bus_message_unref(m); + + r = bus_message_from_malloc(bus, buffer, sz, NULL, 0, NULL, &m); + assert_se(r >= 0); + + bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + fclose(ms); + ms = open_memstream(&second, &second_size); + bus_message_dump(m, ms, 0); + fflush(ms); + assert_se(!ferror(ms)); + assert_se(first_size == second_size); + assert_se(memcmp(first, second, first_size) == 0); + + assert_se(sd_bus_message_rewind(m, true) >= 0); + + r = sd_bus_message_read(m, "ssasg", &x, &x2, 2, &y, &z, &a_signature); + assert_se(r > 0); + assert_se(streq(x, "a string")); + assert_se(streq(x2, "")); + assert_se(streq(y, "string #1")); + assert_se(streq(z, "string #2")); + assert_se(streq(a_signature, "sba(tt)ss")); + + r = sd_bus_message_read(m, "sass", &x, 5, &y, &z, &a, &b, &c, &d); + assert_se(r > 0); + assert_se(streq(x, "foobar")); + assert_se(streq(y, "foo")); + assert_se(streq(z, "bar")); + assert_se(streq(a, "waldo")); + assert_se(streq(b, "piep")); + assert_se(streq(c, "pap")); + assert_se(streq(d, "after")); + + r = sd_bus_message_read(m, "a{yv}", 2, &u, "s", &x, &v, "s", &y); + assert_se(r > 0); + assert_se(u == 3); + assert_se(streq(x, "foo")); + assert_se(v == 5); + assert_se(streq(y, "waldo")); + + r = sd_bus_message_read(m, "y(ty)", &v, &u64, &u); + assert_se(r > 0); + assert_se(v == 8); + assert_se(u64 == 777); + assert_se(u == 7); + + r = sd_bus_message_read(m, "y(yt)", &v, &u, &u64); + assert_se(r > 0); + assert_se(v == 9); + assert_se(u == 77); + assert_se(u64 == 7777); + + r = sd_bus_message_read(m, "y", &v); + assert_se(r > 0); + assert_se(v == 10); + + r = sd_bus_message_read(m, "()"); + assert_se(r > 0); + + r = sd_bus_message_read(m, "ba(ss)", &boolean, 3, &x, &y, &a, &b, &c, &d); + assert_se(r > 0); + assert_se(boolean); + assert_se(streq(x, "aaa")); + assert_se(streq(y, "1")); + assert_se(streq(a, "bbb")); + assert_se(streq(b, "2")); + assert_se(streq(c, "ccc")); + assert_se(streq(d, "3")); + + assert_se(sd_bus_message_verify_type(m, 'a', "s") > 0); + + r = sd_bus_message_read(m, "as", 2, &x, &y); + assert_se(r > 0); + assert_se(streq(x, "foobar")); + assert_se(streq(y, "waldo")); + + r = sd_bus_message_read_basic(m, 's', &s); + assert_se(r > 0); + assert_se(streq(s, "hallo")); + + r = sd_bus_message_read_array(m, 'i', (const void**) &return_array, &sz); + assert_se(r > 0); + assert_se(sz == sizeof(integer_array)); + assert_se(memcmp(integer_array, return_array, sz) == 0); + + r = sd_bus_message_read_array(m, 'u', (const void**) &return_array, &sz); + assert_se(r > 0); + assert_se(sz == 0); + + r = sd_bus_message_read(m, "a(stdo)", 1, &x, &u64, &dbl, &y); + assert_se(r > 0); + assert_se(streq(x, "foo")); + assert_se(u64 == 815ULL); + assert_se(fabs(dbl - 47.0) < 0.1); + assert_se(streq(y, "/")); + + r = sd_bus_message_peek_type(m, NULL, NULL); + assert_se(r == 0); + + r = sd_bus_message_new_method_call(bus, ©, "foobar.waldo", "/", "foobar.waldo", "Piep"); + assert_se(r >= 0); + + r = sd_bus_message_rewind(m, true); + assert_se(r >= 0); + + r = sd_bus_message_copy(copy, m, true); + assert_se(r >= 0); + + r = bus_message_seal(copy, 4712, 0); + assert_se(r >= 0); + + fclose(ms); + ms = open_memstream(&third, &third_size); + bus_message_dump(copy, ms, 0); + fflush(ms); + assert_se(!ferror(ms)); + + printf("<%.*s>\n", (int) first_size, first); + printf("<%.*s>\n", (int) third_size, third); + + assert_se(first_size == third_size); + assert_se(memcmp(first, third, third_size) == 0); + + r = sd_bus_message_rewind(m, true); + assert_se(r >= 0); + + assert_se(sd_bus_message_verify_type(m, 's', NULL) > 0); + + r = sd_bus_message_skip(m, "ssasg"); + assert_se(r > 0); + + assert_se(sd_bus_message_verify_type(m, 's', NULL) > 0); + + r = sd_bus_message_skip(m, "sass"); + assert_se(r >= 0); + + assert_se(sd_bus_message_verify_type(m, 'a', "{yv}") > 0); + + r = sd_bus_message_skip(m, "a{yv}y(ty)y(yt)y()"); + assert_se(r >= 0); + + assert_se(sd_bus_message_verify_type(m, 'b', NULL) > 0); + + r = sd_bus_message_read(m, "b", &boolean); + assert_se(r > 0); + assert_se(boolean); + + r = sd_bus_message_enter_container(m, 0, NULL); + assert_se(r > 0); + + r = sd_bus_message_read(m, "(ss)", &x, &y); + assert_se(r > 0); + + r = sd_bus_message_read(m, "(ss)", &a, &b); + assert_se(r > 0); + + r = sd_bus_message_read(m, "(ss)", &c, &d); + assert_se(r > 0); + + r = sd_bus_message_read(m, "(ss)", &x, &y); + assert_se(r == 0); + + r = sd_bus_message_exit_container(m); + assert_se(r >= 0); + + assert_se(streq(x, "aaa")); + assert_se(streq(y, "1")); + assert_se(streq(a, "bbb")); + assert_se(streq(b, "2")); + assert_se(streq(c, "ccc")); + assert_se(streq(d, "3")); + + test_bus_label_escape(); + test_bus_path_encode(); + test_bus_path_encode_unique(); + test_bus_path_encode_many(); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-match.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-match.c new file mode 100644 index 0000000000..29c4529f95 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-match.c @@ -0,0 +1,159 @@ +/*** + 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 . +***/ + +#include "bus-match.h" +#include "bus-message.h" +#include "bus-slot.h" +#include "bus-util.h" +#include "log.h" +#include "macro.h" + +static bool mask[32]; + +static int filter(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + log_info("Ran %u", PTR_TO_UINT(userdata)); + assert_se(PTR_TO_UINT(userdata) < ELEMENTSOF(mask)); + mask[PTR_TO_UINT(userdata)] = true; + return 0; +} + +static bool mask_contains(unsigned a[], unsigned n) { + unsigned i, j; + + for (i = 0; i < ELEMENTSOF(mask); i++) { + bool found = false; + + for (j = 0; j < n; j++) + if (a[j] == i) { + found = true; + break; + } + + if (found != mask[i]) + return false; + } + + return true; +} + +static int match_add(sd_bus_slot *slots, struct bus_match_node *root, const char *match, int value) { + struct bus_match_component *components = NULL; + unsigned n_components = 0; + sd_bus_slot *s; + int r; + + s = slots + value; + zero(*s); + + r = bus_match_parse(match, &components, &n_components); + if (r < 0) + return r; + + s->userdata = INT_TO_PTR(value); + s->match_callback.callback = filter; + + r = bus_match_add(root, components, n_components, &s->match_callback); + bus_match_parse_free(components, n_components); + + return r; +} + +static void test_match_scope(const char *match, enum bus_match_scope scope) { + struct bus_match_component *components = NULL; + unsigned n_components = 0; + + assert_se(bus_match_parse(match, &components, &n_components) >= 0); + assert_se(bus_match_get_scope(components, n_components) == scope); + bus_match_parse_free(components, n_components); +} + +int main(int argc, char *argv[]) { + struct bus_match_node root = { + .type = BUS_MATCH_ROOT, + }; + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + enum bus_match_node_type i; + sd_bus_slot slots[19]; + int r; + + r = sd_bus_open_system(&bus); + if (r < 0) + return EXIT_TEST_SKIP; + + assert_se(match_add(slots, &root, "arg2='wal\\'do',sender='foo',type='signal',interface='bar.x',", 1) >= 0); + assert_se(match_add(slots, &root, "arg2='wal\\'do2',sender='foo',type='signal',interface='bar.x',", 2) >= 0); + assert_se(match_add(slots, &root, "arg3='test',sender='foo',type='signal',interface='bar.x',", 3) >= 0); + assert_se(match_add(slots, &root, "arg3='test',sender='foo',type='method_call',interface='bar.x',", 4) >= 0); + assert_se(match_add(slots, &root, "", 5) >= 0); + assert_se(match_add(slots, &root, "interface='quux.x'", 6) >= 0); + assert_se(match_add(slots, &root, "interface='bar.x'", 7) >= 0); + assert_se(match_add(slots, &root, "member='waldo',path='/foo/bar'", 8) >= 0); + assert_se(match_add(slots, &root, "path='/foo/bar'", 9) >= 0); + assert_se(match_add(slots, &root, "path_namespace='/foo'", 10) >= 0); + assert_se(match_add(slots, &root, "path_namespace='/foo/quux'", 11) >= 0); + assert_se(match_add(slots, &root, "arg1='two'", 12) >= 0); + assert_se(match_add(slots, &root, "member='waldo',arg2path='/prefix/'", 13) >= 0); + assert_se(match_add(slots, &root, "member=waldo,path='/foo/bar',arg3namespace='prefix'", 14) >= 0); + assert_se(match_add(slots, &root, "arg4has='pi'", 15) >= 0); + assert_se(match_add(slots, &root, "arg4has='pa'", 16) >= 0); + assert_se(match_add(slots, &root, "arg4has='po'", 17) >= 0); + assert_se(match_add(slots, &root, "arg4='pi'", 18) >= 0); + + bus_match_dump(&root, 0); + + assert_se(sd_bus_message_new_signal(bus, &m, "/foo/bar", "bar.x", "waldo") >= 0); + assert_se(sd_bus_message_append(m, "ssssas", "one", "two", "/prefix/three", "prefix.four", 3, "pi", "pa", "po") >= 0); + assert_se(bus_message_seal(m, 1, 0) >= 0); + + zero(mask); + assert_se(bus_match_run(NULL, &root, m) == 0); + assert_se(mask_contains((unsigned[]) { 9, 8, 7, 5, 10, 12, 13, 14, 15, 16, 17 }, 11)); + + assert_se(bus_match_remove(&root, &slots[8].match_callback) >= 0); + assert_se(bus_match_remove(&root, &slots[13].match_callback) >= 0); + + bus_match_dump(&root, 0); + + zero(mask); + assert_se(bus_match_run(NULL, &root, m) == 0); + assert_se(mask_contains((unsigned[]) { 9, 5, 10, 12, 14, 7, 15, 16, 17 }, 9)); + + for (i = 0; i < _BUS_MATCH_NODE_TYPE_MAX; i++) { + char buf[32]; + const char *x; + + assert_se(x = bus_match_node_type_to_string(i, buf, sizeof(buf))); + + if (i >= BUS_MATCH_MESSAGE_TYPE) + assert_se(bus_match_node_type_from_string(x, strlen(x)) == i); + } + + bus_match_free(&root); + + test_match_scope("interface='foobar'", BUS_MATCH_GENERIC); + test_match_scope("", BUS_MATCH_GENERIC); + test_match_scope("interface='org.freedesktop.DBus.Local'", BUS_MATCH_LOCAL); + test_match_scope("sender='org.freedesktop.DBus.Local'", BUS_MATCH_LOCAL); + test_match_scope("member='gurke',path='/org/freedesktop/DBus/Local'", BUS_MATCH_LOCAL); + test_match_scope("arg2='piep',sender='org.freedesktop.DBus',member='waldo'", BUS_MATCH_DRIVER); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-objects.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-objects.c new file mode 100644 index 0000000000..e9bb655665 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-objects.c @@ -0,0 +1,555 @@ +/*** + 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 . +***/ + +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-internal.h" +#include "bus-message.h" +#include "bus-util.h" +#include "log.h" +#include "macro.h" +#include "strv.h" +#include "util.h" + +struct context { + int fds[2]; + bool quit; + char *something; + char *automatic_string_property; + uint32_t automatic_integer_property; +}; + +static int something_handler(sd_bus_message *m, void *userdata, sd_bus_error *error) { + struct context *c = userdata; + const char *s; + char *n = NULL; + int r; + + r = sd_bus_message_read(m, "s", &s); + assert_se(r > 0); + + n = strjoin("<<<", s, ">>>", NULL); + assert_se(n); + + free(c->something); + c->something = n; + + log_info("AlterSomething() called, got %s, returning %s", s, n); + + /* This should fail, since the return type doesn't match */ + assert_se(sd_bus_reply_method_return(m, "u", 4711) == -ENOMSG); + + r = sd_bus_reply_method_return(m, "s", n); + assert_se(r >= 0); + + return 1; +} + +static int exit_handler(sd_bus_message *m, void *userdata, sd_bus_error *error) { + struct context *c = userdata; + int r; + + c->quit = true; + + log_info("Exit called"); + + r = sd_bus_reply_method_return(m, ""); + assert_se(r >= 0); + + return 1; +} + +static int get_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { + struct context *c = userdata; + int r; + + log_info("property get for %s called, returning \"%s\".", property, c->something); + + r = sd_bus_message_append(reply, "s", c->something); + assert_se(r >= 0); + + return 1; +} + +static int set_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *value, void *userdata, sd_bus_error *error) { + struct context *c = userdata; + const char *s; + char *n; + int r; + + log_info("property set for %s called", property); + + r = sd_bus_message_read(value, "s", &s); + assert_se(r >= 0); + + n = strdup(s); + assert_se(n); + + free(c->something); + c->something = n; + + return 1; +} + +static int value_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *s = NULL; + const char *x; + int r; + + assert_se(asprintf(&s, "object %p, path %s", userdata, path) >= 0); + r = sd_bus_message_append(reply, "s", s); + assert_se(r >= 0); + + assert_se(x = startswith(path, "/value/")); + + assert_se(PTR_TO_UINT(userdata) == 30); + + return 1; +} + +static int notify_test(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int r; + + assert_se(sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), m->path, "org.freedesktop.systemd.ValueTest", "Value", NULL) >= 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + + return 1; +} + +static int notify_test2(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int r; + + assert_se(sd_bus_emit_properties_changed_strv(sd_bus_message_get_bus(m), m->path, "org.freedesktop.systemd.ValueTest", NULL) >= 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + + return 1; +} + +static int emit_interfaces_added(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int r; + + assert_se(sd_bus_emit_interfaces_added(sd_bus_message_get_bus(m), "/value/a/x", "org.freedesktop.systemd.ValueTest", NULL) >= 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + + return 1; +} + +static int emit_interfaces_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int r; + + assert_se(sd_bus_emit_interfaces_removed(sd_bus_message_get_bus(m), "/value/a/x", "org.freedesktop.systemd.ValueTest", NULL) >= 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + + return 1; +} + +static int emit_object_added(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int r; + + assert_se(sd_bus_emit_object_added(sd_bus_message_get_bus(m), "/value/a/x") >= 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + + return 1; +} + +static int emit_object_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int r; + + assert_se(sd_bus_emit_object_removed(sd_bus_message_get_bus(m), "/value/a/x") >= 0); + + r = sd_bus_reply_method_return(m, NULL); + assert_se(r >= 0); + + return 1; +} + +static const sd_bus_vtable vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("AlterSomething", "s", "s", something_handler, 0), + SD_BUS_METHOD("Exit", "", "", exit_handler, 0), + SD_BUS_WRITABLE_PROPERTY("Something", "s", get_handler, set_handler, 0, 0), + SD_BUS_WRITABLE_PROPERTY("AutomaticStringProperty", "s", NULL, NULL, offsetof(struct context, automatic_string_property), 0), + SD_BUS_WRITABLE_PROPERTY("AutomaticIntegerProperty", "u", NULL, NULL, offsetof(struct context, automatic_integer_property), 0), + SD_BUS_METHOD("NoOperation", NULL, NULL, NULL, 0), + SD_BUS_METHOD("EmitInterfacesAdded", NULL, NULL, emit_interfaces_added, 0), + SD_BUS_METHOD("EmitInterfacesRemoved", NULL, NULL, emit_interfaces_removed, 0), + SD_BUS_METHOD("EmitObjectAdded", NULL, NULL, emit_object_added, 0), + SD_BUS_METHOD("EmitObjectRemoved", NULL, NULL, emit_object_removed, 0), + SD_BUS_VTABLE_END +}; + +static const sd_bus_vtable vtable2[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("NotifyTest", "", "", notify_test, 0), + SD_BUS_METHOD("NotifyTest2", "", "", notify_test2, 0), + SD_BUS_PROPERTY("Value", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("Value2", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_PROPERTY("Value3", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Value4", "s", value_handler, 10, 0), + SD_BUS_PROPERTY("AnExplicitProperty", "s", NULL, offsetof(struct context, something), SD_BUS_VTABLE_PROPERTY_EXPLICIT|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_VTABLE_END +}; + +static int enumerator_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + + if (object_path_startswith("/value", path)) + assert_se(*nodes = strv_new("/value/a", "/value/b", "/value/c", NULL)); + + return 1; +} + +static int enumerator2_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + + if (object_path_startswith("/value/a", path)) + assert_se(*nodes = strv_new("/value/a/x", "/value/a/y", "/value/a/z", NULL)); + + return 1; +} + +static void *server(void *p) { + struct context *c = p; + sd_bus *bus = NULL; + sd_id128_t id; + int r; + + c->quit = false; + + assert_se(sd_id128_randomize(&id) >= 0); + + assert_se(sd_bus_new(&bus) >= 0); + assert_se(sd_bus_set_fd(bus, c->fds[0], c->fds[0]) >= 0); + assert_se(sd_bus_set_server(bus, 1, id) >= 0); + + assert_se(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.test", vtable, c) >= 0); + assert_se(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.test2", vtable, c) >= 0); + assert_se(sd_bus_add_fallback_vtable(bus, NULL, "/value", "org.freedesktop.systemd.ValueTest", vtable2, NULL, UINT_TO_PTR(20)) >= 0); + assert_se(sd_bus_add_node_enumerator(bus, NULL, "/value", enumerator_callback, NULL) >= 0); + assert_se(sd_bus_add_node_enumerator(bus, NULL, "/value/a", enumerator2_callback, NULL) >= 0); + assert_se(sd_bus_add_object_manager(bus, NULL, "/value") >= 0); + assert_se(sd_bus_add_object_manager(bus, NULL, "/value/a") >= 0); + + assert_se(sd_bus_start(bus) >= 0); + + log_error("Entering event loop on server"); + + while (!c->quit) { + log_error("Loop!"); + + r = sd_bus_process(bus, NULL); + if (r < 0) { + log_error_errno(r, "Failed to process requests: %m"); + goto fail; + } + + if (r == 0) { + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) { + log_error_errno(r, "Failed to wait: %m"); + goto fail; + } + + continue; + } + } + + r = 0; + +fail: + if (bus) { + sd_bus_flush(bus); + sd_bus_unref(bus); + } + + return INT_TO_PTR(r); +} + +static int client(struct context *c) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *s; + int r; + + assert_se(sd_bus_new(&bus) >= 0); + assert_se(sd_bus_set_fd(bus, c->fds[1], c->fds[1]) >= 0); + assert_se(sd_bus_start(bus) >= 0); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "NoOperation", &error, NULL, NULL); + assert_se(r >= 0); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AlterSomething", &error, &reply, "s", "hallo"); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + assert_se(streq(s, "<<>>")); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Doesntexist", &error, &reply, ""); + assert_se(r < 0); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)); + + sd_bus_error_free(&error); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AlterSomething", &error, &reply, "as", 1, "hallo"); + assert_se(r < 0); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)); + + sd_bus_error_free(&error); + + r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, &reply, "s"); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + assert_se(streq(s, "<<>>")); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, "s", "test"); + assert_se(r >= 0); + + r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, &reply, "s"); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + assert_se(streq(s, "test")); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AutomaticIntegerProperty", &error, "u", 815); + assert_se(r >= 0); + + assert_se(c->automatic_integer_property == 815); + + r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AutomaticStringProperty", &error, "s", "Du Dödel, Du!"); + assert_se(r >= 0); + + assert_se(streq(c->automatic_string_property, "Du Dödel, Du!")); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + fputs(s, stdout); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/value/xuzz", "org.freedesktop.systemd.ValueTest", "Value", &error, &reply, "s"); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + log_info("read %s", s); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + fputs(s, stdout); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + fputs(s, stdout); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); + assert_se(r >= 0); + + r = sd_bus_message_read(reply, "s", &s); + assert_se(r >= 0); + fputs(s, stdout); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", ""); + assert_se(r >= 0); + + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", "org.freedesktop.systemd.ValueTest2"); + assert_se(r < 0); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_INTERFACE)); + sd_bus_error_free(&error); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", &error, &reply, ""); + assert_se(r < 0); + assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)); + sd_bus_error_free(&error); + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", &error, &reply, ""); + assert_se(r >= 0); + + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.systemd.ValueTest", "NotifyTest", &error, NULL, ""); + assert_se(r >= 0); + + r = sd_bus_process(bus, &reply); + assert_se(r > 0); + + assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.Properties", "PropertiesChanged")); + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.systemd.ValueTest", "NotifyTest2", &error, NULL, ""); + assert_se(r >= 0); + + r = sd_bus_process(bus, &reply); + assert_se(r > 0); + + assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.Properties", "PropertiesChanged")); + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitInterfacesAdded", &error, NULL, ""); + assert_se(r >= 0); + + r = sd_bus_process(bus, &reply); + assert_se(r > 0); + + assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")); + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitInterfacesRemoved", &error, NULL, ""); + assert_se(r >= 0); + + r = sd_bus_process(bus, &reply); + assert_se(r > 0); + + assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")); + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitObjectAdded", &error, NULL, ""); + assert_se(r >= 0); + + r = sd_bus_process(bus, &reply); + assert_se(r > 0); + + assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")); + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitObjectRemoved", &error, NULL, ""); + assert_se(r >= 0); + + r = sd_bus_process(bus, &reply); + assert_se(r > 0); + + assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")); + bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + sd_bus_message_unref(reply); + reply = NULL; + + r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Exit", &error, NULL, ""); + assert_se(r >= 0); + + sd_bus_flush(bus); + + return 0; +} + +int main(int argc, char *argv[]) { + struct context c = {}; + pthread_t s; + void *p; + int r, q; + + zero(c); + + c.automatic_integer_property = 4711; + assert_se(c.automatic_string_property = strdup("dudeldu")); + + assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds) >= 0); + + r = pthread_create(&s, NULL, server, &c); + if (r != 0) + return -r; + + r = client(&c); + + q = pthread_join(s, &p); + if (q != 0) + return -q; + + if (r < 0) + return r; + + if (PTR_TO_INT(p) < 0) + return PTR_TO_INT(p); + + free(c.something); + free(c.automatic_string_property); + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-proxy.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-proxy.c new file mode 100644 index 0000000000..a9fe44e66c --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-proxy.c @@ -0,0 +1,117 @@ +/*** + This file is part of systemd. + + Copyright 2015 David Herrmann + + 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 . +***/ + +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-kernel.h" +#include "bus-util.h" +#include "log.h" +#include "util.h" + +typedef struct { + const char *sender; + int matched_acquired; +} TestProxyMatch; + +static int test_proxy_acquired(sd_bus_message *m, void *userdata, sd_bus_error *error) { + TestProxyMatch *match = userdata; + const char *name; + int r; + + r = sd_bus_message_read(m, "s", &name); + assert_se(r >= 0); + + if (!streq_ptr(match->sender, name)) + return 0; + + ++match->matched_acquired; + return 1; +} + +static void test_proxy_matched(void) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *a = NULL; + _cleanup_free_ char *matchstr = NULL; + TestProxyMatch match = {}; + const char *me; + int r; + + /* open bus 'a' */ + + r = sd_bus_new(&a); + assert_se(r >= 0); + + r = sd_bus_set_address(a, "unix:path=/var/run/dbus/system_bus_socket"); + assert_se(r >= 0); + + r = sd_bus_set_bus_client(a, true); + assert_se(r >= 0); + + r = sd_bus_start(a); + assert_se(r >= 0); + + r = sd_bus_get_unique_name(a, &me); + assert_se(r >= 0); + + matchstr = strjoin("type='signal'," + "member='NameAcquired'," + "destination='", + me, + "'", + NULL); + assert_se(matchstr); + r = sd_bus_add_match(a, NULL, matchstr, test_proxy_acquired, &match); + assert_se(r >= 0); + + r = sd_bus_get_unique_name(a, &match.sender); + assert_se(r >= 0); + + /* barrier to guarantee proxy/dbus-daemon handled the previous data */ + r = sd_bus_call_method(a, + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetId", + NULL, NULL, NULL); + assert_se(r >= 0); + + /* now we can be sure the Name* signals were sent */ + do { + r = sd_bus_process(a, NULL); + } while (r > 0); + assert_se(r == 0); + + assert_se(match.matched_acquired == 1); +} + +int main(int argc, char **argv) { + if (access("/var/run/dbus/system_bus_socket", F_OK) < 0) + return EXIT_TEST_SKIP; + + log_parse_environment(); + + test_proxy_matched(); + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-server.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-server.c new file mode 100644 index 0000000000..190410674b --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-server.c @@ -0,0 +1,216 @@ +/*** + 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 . +***/ + +#include +#include + +#include + +#include "bus-internal.h" +#include "bus-util.h" +#include "log.h" +#include "macro.h" +#include "util.h" + +struct context { + int fds[2]; + + bool client_negotiate_unix_fds; + bool server_negotiate_unix_fds; + + bool client_anonymous_auth; + bool server_anonymous_auth; +}; + +static void *server(void *p) { + struct context *c = p; + sd_bus *bus = NULL; + sd_id128_t id; + bool quit = false; + int r; + + assert_se(sd_id128_randomize(&id) >= 0); + + assert_se(sd_bus_new(&bus) >= 0); + assert_se(sd_bus_set_fd(bus, c->fds[0], c->fds[0]) >= 0); + assert_se(sd_bus_set_server(bus, 1, id) >= 0); + assert_se(sd_bus_set_anonymous(bus, c->server_anonymous_auth) >= 0); + assert_se(sd_bus_negotiate_fds(bus, c->server_negotiate_unix_fds) >= 0); + assert_se(sd_bus_start(bus) >= 0); + + while (!quit) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + + r = sd_bus_process(bus, &m); + if (r < 0) { + log_error_errno(r, "Failed to process requests: %m"); + goto fail; + } + + if (r == 0) { + r = sd_bus_wait(bus, (uint64_t) -1); + if (r < 0) { + log_error_errno(r, "Failed to wait: %m"); + goto fail; + } + + continue; + } + + if (!m) + continue; + + log_info("Got message! member=%s", strna(sd_bus_message_get_member(m))); + + if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Exit")) { + + assert_se((sd_bus_can_send(bus, 'h') >= 1) == (c->server_negotiate_unix_fds && c->client_negotiate_unix_fds)); + + r = sd_bus_message_new_method_return(m, &reply); + if (r < 0) { + log_error_errno(r, "Failed to allocate return: %m"); + goto fail; + } + + quit = true; + + } else if (sd_bus_message_is_method_call(m, NULL, NULL)) { + r = sd_bus_message_new_method_error( + m, + &reply, + &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNKNOWN_METHOD, "Unknown method.")); + if (r < 0) { + log_error_errno(r, "Failed to allocate return: %m"); + goto fail; + } + } + + if (reply) { + r = sd_bus_send(bus, reply, NULL); + if (r < 0) { + log_error_errno(r, "Failed to send reply: %m"); + goto fail; + } + } + } + + r = 0; + +fail: + if (bus) { + sd_bus_flush(bus); + sd_bus_unref(bus); + } + + return INT_TO_PTR(r); +} + +static int client(struct context *c) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert_se(sd_bus_new(&bus) >= 0); + assert_se(sd_bus_set_fd(bus, c->fds[1], c->fds[1]) >= 0); + assert_se(sd_bus_negotiate_fds(bus, c->client_negotiate_unix_fds) >= 0); + assert_se(sd_bus_set_anonymous(bus, c->client_anonymous_auth) >= 0); + assert_se(sd_bus_start(bus) >= 0); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd.test", + "/", + "org.freedesktop.systemd.test", + "Exit"); + if (r < 0) + return log_error_errno(r, "Failed to allocate method call: %m"); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int test_one(bool client_negotiate_unix_fds, bool server_negotiate_unix_fds, + bool client_anonymous_auth, bool server_anonymous_auth) { + + struct context c; + pthread_t s; + void *p; + int r, q; + + zero(c); + + assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds) >= 0); + + c.client_negotiate_unix_fds = client_negotiate_unix_fds; + c.server_negotiate_unix_fds = server_negotiate_unix_fds; + c.client_anonymous_auth = client_anonymous_auth; + c.server_anonymous_auth = server_anonymous_auth; + + r = pthread_create(&s, NULL, server, &c); + if (r != 0) + return -r; + + r = client(&c); + + q = pthread_join(s, &p); + if (q != 0) + return -q; + + if (r < 0) + return r; + + if (PTR_TO_INT(p) < 0) + return PTR_TO_INT(p); + + return 0; +} + +int main(int argc, char *argv[]) { + int r; + + r = test_one(true, true, false, false); + assert_se(r >= 0); + + r = test_one(true, false, false, false); + assert_se(r >= 0); + + r = test_one(false, true, false, false); + assert_se(r >= 0); + + r = test_one(false, false, false, false); + assert_se(r >= 0); + + r = test_one(true, true, true, true); + assert_se(r >= 0); + + r = test_one(true, true, false, true); + assert_se(r >= 0); + + r = test_one(true, true, true, false); + assert_se(r == -EPERM); + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-signature.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-signature.c new file mode 100644 index 0000000000..4f4fd093bf --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-signature.c @@ -0,0 +1,164 @@ +/*** + 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 . +***/ + +#include "bus-internal.h" +#include "bus-signature.h" +#include "log.h" +#include "string-util.h" + +int main(int argc, char *argv[]) { + char prefix[256]; + int r; + + assert_se(signature_is_single("y", false)); + assert_se(signature_is_single("u", false)); + assert_se(signature_is_single("v", false)); + assert_se(signature_is_single("as", false)); + assert_se(signature_is_single("(ss)", false)); + assert_se(signature_is_single("()", false)); + assert_se(signature_is_single("(()()()()())", false)); + assert_se(signature_is_single("(((())))", false)); + assert_se(signature_is_single("((((s))))", false)); + assert_se(signature_is_single("{ss}", true)); + assert_se(signature_is_single("a{ss}", false)); + assert_se(!signature_is_single("uu", false)); + assert_se(!signature_is_single("", false)); + assert_se(!signature_is_single("(", false)); + assert_se(!signature_is_single(")", false)); + assert_se(!signature_is_single("())", false)); + assert_se(!signature_is_single("((())", false)); + assert_se(!signature_is_single("{)", false)); + assert_se(!signature_is_single("{}", true)); + assert_se(!signature_is_single("{sss}", true)); + assert_se(!signature_is_single("{s}", true)); + assert_se(!signature_is_single("{ss}", false)); + assert_se(!signature_is_single("{ass}", true)); + assert_se(!signature_is_single("a}", true)); + + assert_se(signature_is_pair("yy")); + assert_se(signature_is_pair("ss")); + assert_se(signature_is_pair("sas")); + assert_se(signature_is_pair("sv")); + assert_se(signature_is_pair("sa(vs)")); + assert_se(!signature_is_pair("")); + assert_se(!signature_is_pair("va")); + assert_se(!signature_is_pair("sss")); + assert_se(!signature_is_pair("{s}ss")); + + assert_se(signature_is_valid("ssa{ss}sssub", true)); + assert_se(signature_is_valid("ssa{ss}sssub", false)); + assert_se(signature_is_valid("{ss}", true)); + assert_se(!signature_is_valid("{ss}", false)); + assert_se(signature_is_valid("", true)); + assert_se(signature_is_valid("", false)); + + assert_se(signature_is_valid("sssusa(uuubbba(uu)uuuu)a{u(uuuvas)}", false)); + + assert_se(!signature_is_valid("a", false)); + assert_se(signature_is_valid("as", false)); + assert_se(signature_is_valid("aas", false)); + assert_se(signature_is_valid("aaas", false)); + assert_se(signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad", false)); + assert_se(signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas", false)); + assert_se(!signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau", false)); + + assert_se(signature_is_valid("(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))", false)); + assert_se(!signature_is_valid("((((((((((((((((((((((((((((((((()))))))))))))))))))))))))))))))))", false)); + + assert_se(namespace_complex_pattern("", "")); + assert_se(namespace_complex_pattern("foobar", "foobar")); + assert_se(namespace_complex_pattern("foobar.waldo", "foobar.waldo")); + assert_se(namespace_complex_pattern("foobar.", "foobar.waldo")); + assert_se(namespace_complex_pattern("foobar.waldo", "foobar.")); + assert_se(!namespace_complex_pattern("foobar.waldo", "foobar")); + assert_se(!namespace_complex_pattern("foobar", "foobar.waldo")); + assert_se(!namespace_complex_pattern("", "foo")); + assert_se(!namespace_complex_pattern("foo", "")); + assert_se(!namespace_complex_pattern("foo.", "")); + + assert_se(path_complex_pattern("", "")); + assert_se(!path_complex_pattern("", "/")); + assert_se(!path_complex_pattern("/", "")); + assert_se(path_complex_pattern("/", "/")); + assert_se(path_complex_pattern("/foobar/", "/")); + assert_se(!path_complex_pattern("/foobar/", "/foobar")); + assert_se(path_complex_pattern("/foobar", "/foobar")); + assert_se(!path_complex_pattern("/foobar", "/foobar/")); + assert_se(!path_complex_pattern("/foobar", "/foobar/waldo")); + assert_se(path_complex_pattern("/foobar/", "/foobar/waldo")); + assert_se(path_complex_pattern("/foobar/waldo", "/foobar/")); + + assert_se(path_simple_pattern("/foo/", "/foo/bar/waldo")); + + assert_se(namespace_simple_pattern("", "")); + assert_se(namespace_simple_pattern("", ".foobar")); + assert_se(namespace_simple_pattern("foobar", "foobar")); + assert_se(namespace_simple_pattern("foobar.waldo", "foobar.waldo")); + assert_se(namespace_simple_pattern("foobar", "foobar.waldo")); + assert_se(!namespace_simple_pattern("foobar.waldo", "foobar")); + assert_se(!namespace_simple_pattern("", "foo")); + assert_se(!namespace_simple_pattern("foo", "")); + assert_se(namespace_simple_pattern("foo.", "foo.bar.waldo")); + + assert_se(streq(object_path_startswith("/foo/bar", "/foo"), "bar")); + assert_se(streq(object_path_startswith("/foo", "/foo"), "")); + assert_se(streq(object_path_startswith("/foo", "/"), "foo")); + assert_se(streq(object_path_startswith("/", "/"), "")); + assert_se(!object_path_startswith("/foo", "/bar")); + assert_se(!object_path_startswith("/", "/bar")); + assert_se(!object_path_startswith("/foo", "")); + + assert_se(object_path_is_valid("/foo/bar")); + assert_se(object_path_is_valid("/foo")); + assert_se(object_path_is_valid("/")); + assert_se(object_path_is_valid("/foo5")); + assert_se(object_path_is_valid("/foo_5")); + assert_se(!object_path_is_valid("")); + assert_se(!object_path_is_valid("/foo/")); + assert_se(!object_path_is_valid("//")); + assert_se(!object_path_is_valid("//foo")); + assert_se(!object_path_is_valid("/foo//bar")); + assert_se(!object_path_is_valid("/foo/aaaäöä")); + + OBJECT_PATH_FOREACH_PREFIX(prefix, "/") { + log_info("<%s>", prefix); + assert_not_reached("???"); + } + + r = 0; + OBJECT_PATH_FOREACH_PREFIX(prefix, "/xxx") { + log_info("<%s>", prefix); + assert_se(streq(prefix, "/")); + assert_se(r == 0); + r++; + } + assert_se(r == 1); + + r = 0; + OBJECT_PATH_FOREACH_PREFIX(prefix, "/xxx/yyy/zzz") { + log_info("<%s>", prefix); + assert_se(r != 0 || streq(prefix, "/xxx/yyy")); + assert_se(r != 1 || streq(prefix, "/xxx")); + assert_se(r != 2 || streq(prefix, "/")); + r++; + } + assert_se(r == 3); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-bus/test-bus-zero-copy.c b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-zero-copy.c new file mode 100644 index 0000000000..9e20d67670 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-bus/test-bus-zero-copy.c @@ -0,0 +1,210 @@ +/*** + 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 . +***/ + +#include + +#include + +#include "alloc-util.h" +#include "bus-dump.h" +#include "bus-kernel.h" +#include "bus-message.h" +#include "fd-util.h" +#include "log.h" +#include "memfd-util.h" +#include "string-util.h" +#include "util.h" + +#define FIRST_ARRAY 17 +#define SECOND_ARRAY 33 + +#define STRING_SIZE 123 + +int main(int argc, char *argv[]) { + _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL; + const char *unique; + uint8_t *p; + sd_bus *a, *b; + int r, bus_ref; + sd_bus_message *m; + int f; + uint64_t sz; + uint32_t u32; + size_t i, l; + char *s; + _cleanup_close_ int sfd = -1; + + log_set_max_level(LOG_DEBUG); + + assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); + + bus_ref = bus_kernel_create_bus(name, false, &bus_name); + if (bus_ref == -ENOENT) + return EXIT_TEST_SKIP; + + assert_se(bus_ref >= 0); + + address = strappend("kernel:path=", bus_name); + assert_se(address); + + r = sd_bus_new(&a); + assert_se(r >= 0); + + r = sd_bus_new(&b); + assert_se(r >= 0); + + r = sd_bus_set_address(a, address); + assert_se(r >= 0); + + r = sd_bus_set_address(b, address); + assert_se(r >= 0); + + r = sd_bus_start(a); + assert_se(r >= 0); + + r = sd_bus_start(b); + assert_se(r >= 0); + + r = sd_bus_get_unique_name(a, &unique); + assert_se(r >= 0); + + r = sd_bus_message_new_method_call(b, &m, unique, "/a/path", "an.inter.face", "AMethod"); + assert_se(r >= 0); + + r = sd_bus_message_open_container(m, 'r', "aysay"); + assert_se(r >= 0); + + r = sd_bus_message_append_array_space(m, 'y', FIRST_ARRAY, (void**) &p); + assert_se(r >= 0); + + p[0] = '<'; + memset(p+1, 'L', FIRST_ARRAY-2); + p[FIRST_ARRAY-1] = '>'; + + f = memfd_new_and_map(NULL, STRING_SIZE, (void**) &s); + assert_se(f >= 0); + + s[0] = '<'; + for (i = 1; i < STRING_SIZE-2; i++) + s[i] = '0' + (i % 10); + s[STRING_SIZE-2] = '>'; + s[STRING_SIZE-1] = 0; + munmap(s, STRING_SIZE); + + r = memfd_get_size(f, &sz); + assert_se(r >= 0); + assert_se(sz == STRING_SIZE); + + r = sd_bus_message_append_string_memfd(m, f, 0, (uint64_t) -1); + assert_se(r >= 0); + + close(f); + + f = memfd_new_and_map(NULL, SECOND_ARRAY, (void**) &p); + assert_se(f >= 0); + + p[0] = '<'; + memset(p+1, 'P', SECOND_ARRAY-2); + p[SECOND_ARRAY-1] = '>'; + munmap(p, SECOND_ARRAY); + + r = memfd_get_size(f, &sz); + assert_se(r >= 0); + assert_se(sz == SECOND_ARRAY); + + r = sd_bus_message_append_array_memfd(m, 'y', f, 0, (uint64_t) -1); + assert_se(r >= 0); + + close(f); + + r = sd_bus_message_close_container(m); + assert_se(r >= 0); + + r = sd_bus_message_append(m, "u", 4711); + assert_se(r >= 0); + + assert_se((sfd = memfd_new_and_map(NULL, 6, (void**) &p)) >= 0); + memcpy(p, "abcd\0", 6); + munmap(p, 6); + assert_se(sd_bus_message_append_string_memfd(m, sfd, 1, 4) >= 0); + + r = bus_message_seal(m, 55, 99*USEC_PER_SEC); + assert_se(r >= 0); + + bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + + r = sd_bus_send(b, m, NULL); + assert_se(r >= 0); + + sd_bus_message_unref(m); + + r = sd_bus_process(a, &m); + assert_se(r > 0); + + bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); + sd_bus_message_rewind(m, true); + + r = sd_bus_message_enter_container(m, 'r', "aysay"); + assert_se(r > 0); + + r = sd_bus_message_read_array(m, 'y', (const void**) &p, &l); + assert_se(r > 0); + assert_se(l == FIRST_ARRAY); + + assert_se(p[0] == '<'); + for (i = 1; i < l-1; i++) + assert_se(p[i] == 'L'); + assert_se(p[l-1] == '>'); + + r = sd_bus_message_read(m, "s", &s); + assert_se(r > 0); + + assert_se(s[0] == '<'); + for (i = 1; i < STRING_SIZE-2; i++) + assert_se(s[i] == (char) ('0' + (i % 10))); + assert_se(s[STRING_SIZE-2] == '>'); + assert_se(s[STRING_SIZE-1] == 0); + + r = sd_bus_message_read_array(m, 'y', (const void**) &p, &l); + assert_se(r > 0); + assert_se(l == SECOND_ARRAY); + + assert_se(p[0] == '<'); + for (i = 1; i < l-1; i++) + assert_se(p[i] == 'P'); + assert_se(p[l-1] == '>'); + + r = sd_bus_message_exit_container(m); + assert_se(r > 0); + + r = sd_bus_message_read(m, "u", &u32); + assert_se(r > 0); + assert_se(u32 == 4711); + + r = sd_bus_message_read(m, "s", &s); + assert_se(r > 0); + assert_se(streq_ptr(s, "bcd")); + + sd_bus_message_unref(m); + + sd_bus_unref(a); + sd_bus_unref(b); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-daemon/sd-daemon.c b/src/libsystemd/libsystemd-internal/sd-daemon/sd-daemon.c new file mode 100644 index 0000000000..7c79a938b9 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-daemon/sd-daemon.c @@ -0,0 +1,624 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "socket-util.h" +#include "strv.h" +#include "util.h" + +#define SNDBUF_SIZE (8*1024*1024) + +static void unsetenv_all(bool unset_environment) { + + if (!unset_environment) + return; + + unsetenv("LISTEN_PID"); + unsetenv("LISTEN_FDS"); + unsetenv("LISTEN_FDNAMES"); +} + +_public_ int sd_listen_fds(int unset_environment) { + const char *e; + int n, r, fd; + pid_t pid; + + e = getenv("LISTEN_PID"); + if (!e) { + r = 0; + goto finish; + } + + r = parse_pid(e, &pid); + if (r < 0) + goto finish; + + /* Is this for us? */ + if (getpid() != pid) { + r = 0; + goto finish; + } + + e = getenv("LISTEN_FDS"); + if (!e) { + r = 0; + goto finish; + } + + r = safe_atoi(e, &n); + if (r < 0) + goto finish; + + assert_cc(SD_LISTEN_FDS_START < INT_MAX); + if (n <= 0 || n > INT_MAX - SD_LISTEN_FDS_START) { + r = -EINVAL; + goto finish; + } + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { + r = fd_cloexec(fd, true); + if (r < 0) + goto finish; + } + + r = n; + +finish: + unsetenv_all(unset_environment); + return r; +} + +_public_ int sd_listen_fds_with_names(int unset_environment, char ***names) { + _cleanup_strv_free_ char **l = NULL; + bool have_names; + int n_names = 0, n_fds; + const char *e; + int r; + + if (!names) + return sd_listen_fds(unset_environment); + + e = getenv("LISTEN_FDNAMES"); + if (e) { + n_names = strv_split_extract(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (n_names < 0) { + unsetenv_all(unset_environment); + return n_names; + } + + have_names = true; + } else + have_names = false; + + n_fds = sd_listen_fds(unset_environment); + if (n_fds <= 0) + return n_fds; + + if (have_names) { + if (n_names != n_fds) + return -EINVAL; + } else { + r = strv_extend_n(&l, "unknown", n_fds); + if (r < 0) + return r; + } + + *names = l; + l = NULL; + + return n_fds; +} + +_public_ int sd_is_fifo(int fd, const char *path) { + struct stat st_fd; + + assert_return(fd >= 0, -EBADF); + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISFIFO(st_fd.st_mode)) + return 0; + + if (path) { + struct stat st_path; + + if (stat(path, &st_path) < 0) { + + if (errno == ENOENT || errno == ENOTDIR) + return 0; + + return -errno; + } + + return + st_path.st_dev == st_fd.st_dev && + st_path.st_ino == st_fd.st_ino; + } + + return 1; +} + +_public_ int sd_is_special(int fd, const char *path) { + struct stat st_fd; + + assert_return(fd >= 0, -EBADF); + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISREG(st_fd.st_mode) && !S_ISCHR(st_fd.st_mode)) + return 0; + + if (path) { + struct stat st_path; + + if (stat(path, &st_path) < 0) { + + if (errno == ENOENT || errno == ENOTDIR) + return 0; + + return -errno; + } + + if (S_ISREG(st_fd.st_mode) && S_ISREG(st_path.st_mode)) + return + st_path.st_dev == st_fd.st_dev && + st_path.st_ino == st_fd.st_ino; + else if (S_ISCHR(st_fd.st_mode) && S_ISCHR(st_path.st_mode)) + return st_path.st_rdev == st_fd.st_rdev; + else + return 0; + } + + return 1; +} + +static int sd_is_socket_internal(int fd, int type, int listening) { + struct stat st_fd; + + assert_return(fd >= 0, -EBADF); + assert_return(type >= 0, -EINVAL); + + if (fstat(fd, &st_fd) < 0) + return -errno; + + if (!S_ISSOCK(st_fd.st_mode)) + return 0; + + if (type != 0) { + int other_type = 0; + socklen_t l = sizeof(other_type); + + if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0) + return -errno; + + if (l != sizeof(other_type)) + return -EINVAL; + + if (other_type != type) + return 0; + } + + if (listening >= 0) { + int accepting = 0; + socklen_t l = sizeof(accepting); + + if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0) + return -errno; + + if (l != sizeof(accepting)) + return -EINVAL; + + if (!accepting != !listening) + return 0; + } + + return 1; +} + +_public_ int sd_is_socket(int fd, int family, int type, int listening) { + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(family >= 0, -EINVAL); + + r = sd_is_socket_internal(fd, type, listening); + if (r <= 0) + return r; + + if (family > 0) { + union sockaddr_union sockaddr = {}; + socklen_t l = sizeof(sockaddr); + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + return sockaddr.sa.sa_family == family; + } + + return 1; +} + +_public_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) { + union sockaddr_union sockaddr = {}; + socklen_t l = sizeof(sockaddr); + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(IN_SET(family, 0, AF_INET, AF_INET6), -EINVAL); + + r = sd_is_socket_internal(fd, type, listening); + if (r <= 0) + return r; + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + if (sockaddr.sa.sa_family != AF_INET && + sockaddr.sa.sa_family != AF_INET6) + return 0; + + if (family != 0) + if (sockaddr.sa.sa_family != family) + return 0; + + if (port > 0) { + if (sockaddr.sa.sa_family == AF_INET) { + if (l < sizeof(struct sockaddr_in)) + return -EINVAL; + + return htons(port) == sockaddr.in.sin_port; + } else { + if (l < sizeof(struct sockaddr_in6)) + return -EINVAL; + + return htons(port) == sockaddr.in6.sin6_port; + } + } + + return 1; +} + +_public_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) { + union sockaddr_union sockaddr = {}; + socklen_t l = sizeof(sockaddr); + int r; + + assert_return(fd >= 0, -EBADF); + + r = sd_is_socket_internal(fd, type, listening); + if (r <= 0) + return r; + + if (getsockname(fd, &sockaddr.sa, &l) < 0) + return -errno; + + if (l < sizeof(sa_family_t)) + return -EINVAL; + + if (sockaddr.sa.sa_family != AF_UNIX) + return 0; + + if (path) { + if (length == 0) + length = strlen(path); + + if (length == 0) + /* Unnamed socket */ + return l == offsetof(struct sockaddr_un, sun_path); + + if (path[0]) + /* Normal path socket */ + return + (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) && + memcmp(path, sockaddr.un.sun_path, length+1) == 0; + else + /* Abstract namespace socket */ + return + (l == offsetof(struct sockaddr_un, sun_path) + length) && + memcmp(path, sockaddr.un.sun_path, length) == 0; + } + + return 1; +} + +_public_ int sd_is_mq(int fd, const char *path) { + struct mq_attr attr; + + /* Check that the fd is valid */ + assert_return(fcntl(fd, F_GETFD) >= 0, -errno); + + if (mq_getattr(fd, &attr) < 0) { + if (errno == EBADF) + /* A non-mq fd (or an invalid one, but we ruled that out above) */ + return 0; + return -errno; + } + + if (path) { + char fpath[PATH_MAX]; + struct stat a, b; + + assert_return(path_is_absolute(path), -EINVAL); + + if (fstat(fd, &a) < 0) + return -errno; + + strncpy(stpcpy(fpath, "/dev/mqueue"), path, sizeof(fpath) - 12); + fpath[sizeof(fpath)-1] = 0; + + if (stat(fpath, &b) < 0) + return -errno; + + if (a.st_dev != b.st_dev || + a.st_ino != b.st_ino) + return 0; + } + + return 1; +} + +_public_ int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds) { + union sockaddr_union sockaddr = { + .sa.sa_family = AF_UNIX, + }; + struct iovec iovec = { + .iov_base = (char*) state, + }; + struct msghdr msghdr = { + .msg_iov = &iovec, + .msg_iovlen = 1, + .msg_name = &sockaddr, + }; + _cleanup_close_ int fd = -1; + struct cmsghdr *cmsg = NULL; + const char *e; + bool have_pid; + int r; + + if (!state) { + r = -EINVAL; + goto finish; + } + + if (n_fds > 0 && !fds) { + r = -EINVAL; + goto finish; + } + + e = getenv("NOTIFY_SOCKET"); + if (!e) + return 0; + + /* Must be an abstract socket, or an absolute path */ + if ((e[0] != '@' && e[0] != '/') || e[1] == 0) { + r = -EINVAL; + goto finish; + } + + if (strlen(e) > sizeof(sockaddr.un.sun_path)) { + r = -EINVAL; + goto finish; + } + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); + if (fd < 0) { + r = -errno; + goto finish; + } + + fd_inc_sndbuf(fd, SNDBUF_SIZE); + + iovec.iov_len = strlen(state); + + strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path)); + if (sockaddr.un.sun_path[0] == '@') + sockaddr.un.sun_path[0] = 0; + + msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e); + if (msghdr.msg_namelen > sizeof(struct sockaddr_un)) + msghdr.msg_namelen = sizeof(struct sockaddr_un); + + have_pid = pid != 0 && pid != getpid(); + + if (n_fds > 0 || have_pid) { + /* CMSG_SPACE(0) may return value different then zero, which results in miscalculated controllen. */ + msghdr.msg_controllen = + (n_fds > 0 ? CMSG_SPACE(sizeof(int) * n_fds) : 0) + + (have_pid ? CMSG_SPACE(sizeof(struct ucred)) : 0); + + msghdr.msg_control = alloca0(msghdr.msg_controllen); + + cmsg = CMSG_FIRSTHDR(&msghdr); + if (n_fds > 0) { + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = CMSG_LEN(sizeof(int) * n_fds); + + memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * n_fds); + + if (have_pid) + assert_se(cmsg = CMSG_NXTHDR(&msghdr, cmsg)); + } + + if (have_pid) { + struct ucred *ucred; + + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_CREDENTIALS; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); + + ucred = (struct ucred*) CMSG_DATA(cmsg); + ucred->pid = pid; + ucred->uid = getuid(); + ucred->gid = getgid(); + } + } + + /* First try with fake ucred data, as requested */ + if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) >= 0) { + r = 1; + goto finish; + } + + /* If that failed, try with our own ucred instead */ + if (have_pid) { + msghdr.msg_controllen -= CMSG_SPACE(sizeof(struct ucred)); + if (msghdr.msg_controllen == 0) + msghdr.msg_control = NULL; + + if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) >= 0) { + r = 1; + goto finish; + } + } + + r = -errno; + +finish: + if (unset_environment) + unsetenv("NOTIFY_SOCKET"); + + return r; +} + +_public_ int sd_pid_notify(pid_t pid, int unset_environment, const char *state) { + return sd_pid_notify_with_fds(pid, unset_environment, state, NULL, 0); +} + +_public_ int sd_notify(int unset_environment, const char *state) { + return sd_pid_notify_with_fds(0, unset_environment, state, NULL, 0); +} + +_public_ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) { + _cleanup_free_ char *p = NULL; + int r; + + if (format) { + va_list ap; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0 || !p) + return -ENOMEM; + } + + return sd_pid_notify(pid, unset_environment, p); +} + +_public_ int sd_notifyf(int unset_environment, const char *format, ...) { + _cleanup_free_ char *p = NULL; + int r; + + if (format) { + va_list ap; + + va_start(ap, format); + r = vasprintf(&p, format, ap); + va_end(ap); + + if (r < 0 || !p) + return -ENOMEM; + } + + return sd_pid_notify(0, unset_environment, p); +} + +_public_ int sd_booted(void) { + /* We test whether the runtime unit file directory has been + * created. This takes place in mount-setup.c, so is + * guaranteed to happen very early during boot. */ + + return laccess("/run/systemd/system/", F_OK) >= 0; +} + +_public_ int sd_watchdog_enabled(int unset_environment, uint64_t *usec) { + const char *s, *p = ""; /* p is set to dummy value to do unsetting */ + uint64_t u; + int r = 0; + + s = getenv("WATCHDOG_USEC"); + if (!s) + goto finish; + + r = safe_atou64(s, &u); + if (r < 0) + goto finish; + if (u <= 0 || u >= USEC_INFINITY) { + r = -EINVAL; + goto finish; + } + + p = getenv("WATCHDOG_PID"); + if (p) { + pid_t pid; + + r = parse_pid(p, &pid); + if (r < 0) + goto finish; + + /* Is this for us? */ + if (getpid() != pid) { + r = 0; + goto finish; + } + } + + if (usec) + *usec = u; + + r = 1; + +finish: + if (unset_environment && s) + unsetenv("WATCHDOG_USEC"); + if (unset_environment && p) + unsetenv("WATCHDOG_PID"); + + return r; +} diff --git a/src/libsystemd/libsystemd-internal/sd-device/device-enumerator-private.h b/src/libsystemd/libsystemd-internal/sd-device/device-enumerator-private.h new file mode 100644 index 0000000000..d46e26b56e --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/device-enumerator-private.h @@ -0,0 +1,34 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Tom Gundersen + + 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 . +***/ + +#include + +int device_enumerator_scan_devices(sd_device_enumerator *enumeartor); +int device_enumerator_scan_subsystems(sd_device_enumerator *enumeartor); +int device_enumerator_add_device(sd_device_enumerator *enumerator, sd_device *device); +int device_enumerator_add_match_is_initialized(sd_device_enumerator *enumerator); +sd_device *device_enumerator_get_first(sd_device_enumerator *enumerator); +sd_device *device_enumerator_get_next(sd_device_enumerator *enumerator); + +#define FOREACH_DEVICE_AND_SUBSYSTEM(enumerator, device) \ + for (device = device_enumerator_get_first(enumerator); \ + device; \ + device = device_enumerator_get_next(enumerator)) diff --git a/src/libsystemd/libsystemd-internal/sd-device/device-enumerator.c b/src/libsystemd/libsystemd-internal/sd-device/device-enumerator.c new file mode 100644 index 0000000000..796728ee0e --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/device-enumerator.c @@ -0,0 +1,986 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2014-2015 Tom Gundersen + + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "device-enumerator-private.h" +#include "device-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "prioq.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +#define DEVICE_ENUMERATE_MAX_DEPTH 256 + +typedef enum DeviceEnumerationType { + DEVICE_ENUMERATION_TYPE_DEVICES, + DEVICE_ENUMERATION_TYPE_SUBSYSTEMS, + _DEVICE_ENUMERATION_TYPE_MAX, + _DEVICE_ENUMERATION_TYPE_INVALID = -1, +} DeviceEnumerationType; + +struct sd_device_enumerator { + unsigned n_ref; + + DeviceEnumerationType type; + Prioq *devices; + bool scan_uptodate; + + Set *match_subsystem; + Set *nomatch_subsystem; + Hashmap *match_sysattr; + Hashmap *nomatch_sysattr; + Hashmap *match_property; + Set *match_sysname; + Set *match_tag; + sd_device *match_parent; + bool match_allow_uninitialized; +}; + +_public_ int sd_device_enumerator_new(sd_device_enumerator **ret) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *enumerator = NULL; + + assert(ret); + + enumerator = new0(sd_device_enumerator, 1); + if (!enumerator) + return -ENOMEM; + + enumerator->n_ref = 1; + enumerator->type = _DEVICE_ENUMERATION_TYPE_INVALID; + + *ret = enumerator; + enumerator = NULL; + + return 0; +} + +_public_ sd_device_enumerator *sd_device_enumerator_ref(sd_device_enumerator *enumerator) { + assert_return(enumerator, NULL); + + assert_se((++ enumerator->n_ref) >= 2); + + return enumerator; +} + +_public_ sd_device_enumerator *sd_device_enumerator_unref(sd_device_enumerator *enumerator) { + if (enumerator && (-- enumerator->n_ref) == 0) { + sd_device *device; + + while ((device = prioq_pop(enumerator->devices))) + sd_device_unref(device); + + prioq_free(enumerator->devices); + + set_free_free(enumerator->match_subsystem); + set_free_free(enumerator->nomatch_subsystem); + hashmap_free_free_free(enumerator->match_sysattr); + hashmap_free_free_free(enumerator->nomatch_sysattr); + hashmap_free_free_free(enumerator->match_property); + set_free_free(enumerator->match_sysname); + set_free_free(enumerator->match_tag); + sd_device_unref(enumerator->match_parent); + + free(enumerator); + } + + return NULL; +} + +_public_ int sd_device_enumerator_add_match_subsystem(sd_device_enumerator *enumerator, const char *subsystem, int match) { + Set **set; + int r; + + assert_return(enumerator, -EINVAL); + assert_return(subsystem, -EINVAL); + + if (match) + set = &enumerator->match_subsystem; + else + set = &enumerator->nomatch_subsystem; + + r = set_ensure_allocated(set, NULL); + if (r < 0) + return r; + + r = set_put_strdup(*set, subsystem); + if (r < 0) + return r; + + enumerator->scan_uptodate = false; + + return 0; +} + +_public_ int sd_device_enumerator_add_match_sysattr(sd_device_enumerator *enumerator, const char *_sysattr, const char *_value, int match) { + _cleanup_free_ char *sysattr = NULL, *value = NULL; + Hashmap **hashmap; + int r; + + assert_return(enumerator, -EINVAL); + assert_return(_sysattr, -EINVAL); + + if (match) + hashmap = &enumerator->match_sysattr; + else + hashmap = &enumerator->nomatch_sysattr; + + r = hashmap_ensure_allocated(hashmap, NULL); + if (r < 0) + return r; + + sysattr = strdup(_sysattr); + if (!sysattr) + return -ENOMEM; + + if (_value) { + value = strdup(_value); + if (!value) + return -ENOMEM; + } + + r = hashmap_put(*hashmap, sysattr, value); + if (r < 0) + return r; + + sysattr = NULL; + value = NULL; + + enumerator->scan_uptodate = false; + + return 0; +} + +_public_ int sd_device_enumerator_add_match_property(sd_device_enumerator *enumerator, const char *_property, const char *_value) { + _cleanup_free_ char *property = NULL, *value = NULL; + int r; + + assert_return(enumerator, -EINVAL); + assert_return(_property, -EINVAL); + + r = hashmap_ensure_allocated(&enumerator->match_property, NULL); + if (r < 0) + return r; + + property = strdup(_property); + if (!property) + return -ENOMEM; + + if (_value) { + value = strdup(_value); + if (!value) + return -ENOMEM; + } + + r = hashmap_put(enumerator->match_property, property, value); + if (r < 0) + return r; + + property = NULL; + value = NULL; + + enumerator->scan_uptodate = false; + + return 0; +} + +_public_ int sd_device_enumerator_add_match_sysname(sd_device_enumerator *enumerator, const char *sysname) { + int r; + + assert_return(enumerator, -EINVAL); + assert_return(sysname, -EINVAL); + + r = set_ensure_allocated(&enumerator->match_sysname, NULL); + if (r < 0) + return r; + + r = set_put_strdup(enumerator->match_sysname, sysname); + if (r < 0) + return r; + + enumerator->scan_uptodate = false; + + return 0; +} + +_public_ int sd_device_enumerator_add_match_tag(sd_device_enumerator *enumerator, const char *tag) { + int r; + + assert_return(enumerator, -EINVAL); + assert_return(tag, -EINVAL); + + r = set_ensure_allocated(&enumerator->match_tag, NULL); + if (r < 0) + return r; + + r = set_put_strdup(enumerator->match_tag, tag); + if (r < 0) + return r; + + enumerator->scan_uptodate = false; + + return 0; +} + +_public_ int sd_device_enumerator_add_match_parent(sd_device_enumerator *enumerator, sd_device *parent) { + assert_return(enumerator, -EINVAL); + assert_return(parent, -EINVAL); + + sd_device_unref(enumerator->match_parent); + enumerator->match_parent = sd_device_ref(parent); + + enumerator->scan_uptodate = false; + + return 0; +} + +_public_ int sd_device_enumerator_allow_uninitialized(sd_device_enumerator *enumerator) { + assert_return(enumerator, -EINVAL); + + enumerator->match_allow_uninitialized = true; + + enumerator->scan_uptodate = false; + + return 0; +} + +int device_enumerator_add_match_is_initialized(sd_device_enumerator *enumerator) { + assert_return(enumerator, -EINVAL); + + enumerator->match_allow_uninitialized = false; + + enumerator->scan_uptodate = false; + + return 0; +} + +static int device_compare(const void *_a, const void *_b) { + sd_device *a = (sd_device *)_a, *b = (sd_device *)_b; + const char *devpath_a, *devpath_b, *sound_a; + bool delay_a, delay_b; + + assert_se(sd_device_get_devpath(a, &devpath_a) >= 0); + assert_se(sd_device_get_devpath(b, &devpath_b) >= 0); + + sound_a = strstr(devpath_a, "/sound/card"); + if (sound_a) { + /* For sound cards the control device must be enumerated last to + * make sure it's the final device node that gets ACLs applied. + * Applications rely on this fact and use ACL changes on the + * control node as an indicator that the ACL change of the + * entire sound card completed. The kernel makes this guarantee + * when creating those devices, and hence we should too when + * enumerating them. */ + sound_a += strlen("/sound/card"); + sound_a = strchr(sound_a, '/'); + + if (sound_a) { + unsigned prefix_len; + + prefix_len = sound_a - devpath_a; + + if (strncmp(devpath_a, devpath_b, prefix_len) == 0) { + const char *sound_b; + + sound_b = devpath_b + prefix_len; + + if (startswith(sound_a, "/controlC") && + !startswith(sound_b, "/contolC")) + return 1; + + if (!startswith(sound_a, "/controlC") && + startswith(sound_b, "/controlC")) + return -1; + } + } + } + + /* md and dm devices are enumerated after all other devices */ + delay_a = strstr(devpath_a, "/block/md") || strstr(devpath_a, "/block/dm-"); + delay_b = strstr(devpath_b, "/block/md") || strstr(devpath_b, "/block/dm-"); + if (delay_a != delay_b) + return delay_a - delay_b; + + return strcmp(devpath_a, devpath_b); +} + +int device_enumerator_add_device(sd_device_enumerator *enumerator, sd_device *device) { + int r; + + assert_return(enumerator, -EINVAL); + assert_return(device, -EINVAL); + + r = prioq_ensure_allocated(&enumerator->devices, device_compare); + if (r < 0) + return r; + + r = prioq_put(enumerator->devices, device, NULL); + if (r < 0) + return r; + + sd_device_ref(device); + + return 0; +} + +static bool match_sysattr_value(sd_device *device, const char *sysattr, const char *match_value) { + const char *value; + int r; + + assert(device); + assert(sysattr); + + r = sd_device_get_sysattr_value(device, sysattr, &value); + if (r < 0) + return false; + + if (!match_value) + return true; + + if (fnmatch(match_value, value, 0) == 0) + return true; + + return false; +} + +static bool match_sysattr(sd_device_enumerator *enumerator, sd_device *device) { + const char *sysattr; + const char *value; + Iterator i; + + assert(enumerator); + assert(device); + + HASHMAP_FOREACH_KEY(value, sysattr, enumerator->nomatch_sysattr, i) + if (match_sysattr_value(device, sysattr, value)) + return false; + + HASHMAP_FOREACH_KEY(value, sysattr, enumerator->match_sysattr, i) + if (!match_sysattr_value(device, sysattr, value)) + return false; + + return true; +} + +static bool match_property(sd_device_enumerator *enumerator, sd_device *device) { + const char *property; + const char *value; + Iterator i; + + assert(enumerator); + assert(device); + + if (hashmap_isempty(enumerator->match_property)) + return true; + + HASHMAP_FOREACH_KEY(value, property, enumerator->match_property, i) { + const char *property_dev, *value_dev; + + FOREACH_DEVICE_PROPERTY(device, property_dev, value_dev) { + if (fnmatch(property, property_dev, 0) != 0) + continue; + + if (!value && !value_dev) + return true; + + if (!value || !value_dev) + continue; + + if (fnmatch(value, value_dev, 0) == 0) + return true; + } + } + + return false; +} + +static bool match_tag(sd_device_enumerator *enumerator, sd_device *device) { + const char *tag; + Iterator i; + + assert(enumerator); + assert(device); + + SET_FOREACH(tag, enumerator->match_tag, i) + if (!sd_device_has_tag(device, tag)) + return false; + + return true; +} + +static bool match_parent(sd_device_enumerator *enumerator, sd_device *device) { + const char *devpath, *devpath_dev; + int r; + + assert(enumerator); + assert(device); + + if (!enumerator->match_parent) + return true; + + r = sd_device_get_devpath(enumerator->match_parent, &devpath); + assert(r >= 0); + + r = sd_device_get_devpath(device, &devpath_dev); + assert(r >= 0); + + return startswith(devpath_dev, devpath); +} + +static bool match_sysname(sd_device_enumerator *enumerator, const char *sysname) { + const char *sysname_match; + Iterator i; + + assert(enumerator); + assert(sysname); + + if (set_isempty(enumerator->match_sysname)) + return true; + + SET_FOREACH(sysname_match, enumerator->match_sysname, i) + if (fnmatch(sysname_match, sysname, 0) == 0) + return true; + + return false; +} + +static int enumerator_scan_dir_and_add_devices(sd_device_enumerator *enumerator, const char *basedir, const char *subdir1, const char *subdir2) { + _cleanup_closedir_ DIR *dir = NULL; + char *path; + struct dirent *dent; + int r = 0; + + assert(enumerator); + assert(basedir); + + path = strjoina("/sys/", basedir, "/"); + + if (subdir1) + path = strjoina(path, subdir1, "/"); + + if (subdir2) + path = strjoina(path, subdir2, "/"); + + dir = opendir(path); + if (!dir) + return -errno; + + FOREACH_DIRENT_ALL(dent, dir, return -errno) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + char syspath[strlen(path) + 1 + strlen(dent->d_name) + 1]; + dev_t devnum; + int ifindex, initialized, k; + + if (dent->d_name[0] == '.') + continue; + + if (!match_sysname(enumerator, dent->d_name)) + continue; + + (void)sprintf(syspath, "%s%s", path, dent->d_name); + + k = sd_device_new_from_syspath(&device, syspath); + if (k < 0) { + if (k != -ENODEV) + /* this is necessarily racey, so ignore missing devices */ + r = k; + + continue; + } + + k = sd_device_get_devnum(device, &devnum); + if (k < 0) { + r = k; + continue; + } + + k = sd_device_get_ifindex(device, &ifindex); + if (k < 0) { + r = k; + continue; + } + + k = sd_device_get_is_initialized(device, &initialized); + if (k < 0) { + r = k; + continue; + } + + /* + * All devices with a device node or network interfaces + * possibly need udev to adjust the device node permission + * or context, or rename the interface before it can be + * reliably used from other processes. + * + * For now, we can only check these types of devices, we + * might not store a database, and have no way to find out + * for all other types of devices. + */ + if (!enumerator->match_allow_uninitialized && + !initialized && + (major(devnum) > 0 || ifindex > 0)) + continue; + + if (!match_parent(enumerator, device)) + continue; + + if (!match_tag(enumerator, device)) + continue; + + if (!match_property(enumerator, device)) + continue; + + if (!match_sysattr(enumerator, device)) + continue; + + k = device_enumerator_add_device(enumerator, device); + if (k < 0) + r = k; + } + + return r; +} + +static bool match_subsystem(sd_device_enumerator *enumerator, const char *subsystem) { + const char *subsystem_match; + Iterator i; + + assert(enumerator); + + if (!subsystem) + return false; + + SET_FOREACH(subsystem_match, enumerator->nomatch_subsystem, i) + if (fnmatch(subsystem_match, subsystem, 0) == 0) + return false; + + if (set_isempty(enumerator->match_subsystem)) + return true; + + SET_FOREACH(subsystem_match, enumerator->match_subsystem, i) + if (fnmatch(subsystem_match, subsystem, 0) == 0) + return true; + + return false; +} + +static int enumerator_scan_dir(sd_device_enumerator *enumerator, const char *basedir, const char *subdir, const char *subsystem) { + _cleanup_closedir_ DIR *dir = NULL; + char *path; + struct dirent *dent; + int r = 0; + + path = strjoina("/sys/", basedir); + + dir = opendir(path); + if (!dir) + return -errno; + + log_debug(" device-enumerator: scanning %s", path); + + FOREACH_DIRENT_ALL(dent, dir, return -errno) { + int k; + + if (dent->d_name[0] == '.') + continue; + + if (!match_subsystem(enumerator, subsystem ? : dent->d_name)) + continue; + + k = enumerator_scan_dir_and_add_devices(enumerator, basedir, dent->d_name, subdir); + if (k < 0) + r = k; + } + + return r; +} + +static int enumerator_scan_devices_tag(sd_device_enumerator *enumerator, const char *tag) { + _cleanup_closedir_ DIR *dir = NULL; + char *path; + struct dirent *dent; + int r = 0; + + assert(enumerator); + assert(tag); + + path = strjoina("/run/udev/tags/", tag); + + dir = opendir(path); + if (!dir) { + if (errno == ENOENT) + return 0; + else { + log_error("sd-device-enumerator: could not open tags directory %s: %m", path); + return -errno; + } + } + + /* TODO: filter away subsystems? */ + + FOREACH_DIRENT_ALL(dent, dir, return -errno) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + const char *subsystem, *sysname; + int k; + + if (dent->d_name[0] == '.') + continue; + + k = sd_device_new_from_device_id(&device, dent->d_name); + if (k < 0) { + if (k != -ENODEV) + /* this is necessarily racy, so ignore missing devices */ + r = k; + + continue; + } + + k = sd_device_get_subsystem(device, &subsystem); + if (k < 0) { + r = k; + continue; + } + + if (!match_subsystem(enumerator, subsystem)) + continue; + + k = sd_device_get_sysname(device, &sysname); + if (k < 0) { + r = k; + continue; + } + + if (!match_sysname(enumerator, sysname)) + continue; + + if (!match_parent(enumerator, device)) + continue; + + if (!match_property(enumerator, device)) + continue; + + if (!match_sysattr(enumerator, device)) + continue; + + k = device_enumerator_add_device(enumerator, device); + if (k < 0) { + r = k; + continue; + } + } + + return r; +} + +static int enumerator_scan_devices_tags(sd_device_enumerator *enumerator) { + const char *tag; + Iterator i; + int r; + + assert(enumerator); + + SET_FOREACH(tag, enumerator->match_tag, i) { + r = enumerator_scan_devices_tag(enumerator, tag); + if (r < 0) + return r; + } + + return 0; +} + +static int parent_add_child(sd_device_enumerator *enumerator, const char *path) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + const char *subsystem, *sysname; + int r; + + r = sd_device_new_from_syspath(&device, path); + if (r == -ENODEV) + /* this is necessarily racy, so ignore missing devices */ + return 0; + else if (r < 0) + return r; + + r = sd_device_get_subsystem(device, &subsystem); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + if (!match_subsystem(enumerator, subsystem)) + return 0; + + r = sd_device_get_sysname(device, &sysname); + if (r < 0) + return r; + + if (!match_sysname(enumerator, sysname)) + return 0; + + if (!match_property(enumerator, device)) + return 0; + + if (!match_sysattr(enumerator, device)) + return 0; + + r = device_enumerator_add_device(enumerator, device); + if (r < 0) + return r; + + return 1; +} + +static int parent_crawl_children(sd_device_enumerator *enumerator, const char *path, unsigned maxdepth) { + _cleanup_closedir_ DIR *dir = NULL; + struct dirent *dent; + int r = 0; + + dir = opendir(path); + if (!dir) { + log_debug("sd-device-enumerate: could not open parent directory %s: %m", path); + return -errno; + } + + FOREACH_DIRENT_ALL(dent, dir, return -errno) { + _cleanup_free_ char *child = NULL; + int k; + + if (dent->d_name[0] == '.') + continue; + + if (dent->d_type != DT_DIR) + continue; + + child = strjoin(path, "/", dent->d_name, NULL); + if (!child) + return -ENOMEM; + + k = parent_add_child(enumerator, child); + if (k < 0) + r = k; + + if (maxdepth > 0) + parent_crawl_children(enumerator, child, maxdepth - 1); + else + log_debug("device-enumerate: max depth reached, %s: ignoring devices", child); + } + + return r; +} + +static int enumerator_scan_devices_children(sd_device_enumerator *enumerator) { + const char *path; + int r = 0, k; + + r = sd_device_get_syspath(enumerator->match_parent, &path); + if (r < 0) + return r; + + k = parent_add_child(enumerator, path); + if (k < 0) + r = k; + + k = parent_crawl_children(enumerator, path, DEVICE_ENUMERATE_MAX_DEPTH); + if (k < 0) + r = k; + + return r; +} + +static int enumerator_scan_devices_all(sd_device_enumerator *enumerator) { + int r = 0; + + log_debug("device-enumerator: scan all dirs"); + + if (access("/sys/subsystem", F_OK) >= 0) { + /* we have /subsystem/, forget all the old stuff */ + r = enumerator_scan_dir(enumerator, "subsystem", "devices", NULL); + if (r < 0) + return log_debug_errno(r, "device-enumerator: failed to scan /sys/subsystem: %m"); + } else { + int k; + + k = enumerator_scan_dir(enumerator, "bus", "devices", NULL); + if (k < 0) { + log_debug_errno(k, "device-enumerator: failed to scan /sys/bus: %m"); + r = k; + } + + k = enumerator_scan_dir(enumerator, "class", NULL, NULL); + if (k < 0) { + log_debug_errno(k, "device-enumerator: failed to scan /sys/class: %m"); + r = k; + } + } + + return r; +} + +int device_enumerator_scan_devices(sd_device_enumerator *enumerator) { + sd_device *device; + int r; + + assert(enumerator); + + if (enumerator->scan_uptodate && + enumerator->type == DEVICE_ENUMERATION_TYPE_DEVICES) + return 0; + + while ((device = prioq_pop(enumerator->devices))) + sd_device_unref(device); + + if (!set_isempty(enumerator->match_tag)) { + r = enumerator_scan_devices_tags(enumerator); + if (r < 0) + return r; + } else if (enumerator->match_parent) { + r = enumerator_scan_devices_children(enumerator); + if (r < 0) + return r; + } else { + r = enumerator_scan_devices_all(enumerator); + if (r < 0) + return r; + } + + enumerator->scan_uptodate = true; + + return 0; +} + +_public_ sd_device *sd_device_enumerator_get_device_first(sd_device_enumerator *enumerator) { + int r; + + assert_return(enumerator, NULL); + + r = device_enumerator_scan_devices(enumerator); + if (r < 0) + return NULL; + + enumerator->type = DEVICE_ENUMERATION_TYPE_DEVICES; + + return prioq_peek(enumerator->devices); +} + +_public_ sd_device *sd_device_enumerator_get_device_next(sd_device_enumerator *enumerator) { + assert_return(enumerator, NULL); + + if (!enumerator->scan_uptodate || + enumerator->type != DEVICE_ENUMERATION_TYPE_DEVICES) + return NULL; + + sd_device_unref(prioq_pop(enumerator->devices)); + + return prioq_peek(enumerator->devices); +} + +int device_enumerator_scan_subsystems(sd_device_enumerator *enumerator) { + sd_device *device; + const char *subsysdir; + int r = 0, k; + + assert(enumerator); + + if (enumerator->scan_uptodate && + enumerator->type == DEVICE_ENUMERATION_TYPE_SUBSYSTEMS) + return 0; + + while ((device = prioq_pop(enumerator->devices))) + sd_device_unref(device); + + /* modules */ + if (match_subsystem(enumerator, "module")) { + k = enumerator_scan_dir_and_add_devices(enumerator, "module", NULL, NULL); + if (k < 0) { + log_debug_errno(k, "device-enumerator: failed to scan modules: %m"); + r = k; + } + } + + if (access("/sys/subsystem", F_OK) >= 0) + subsysdir = "subsystem"; + else + subsysdir = "bus"; + + /* subsystems (only buses support coldplug) */ + if (match_subsystem(enumerator, "subsystem")) { + k = enumerator_scan_dir_and_add_devices(enumerator, subsysdir, NULL, NULL); + if (k < 0) { + log_debug_errno(k, "device-enumerator: failed to scan subsystems: %m"); + r = k; + } + } + + /* subsystem drivers */ + if (match_subsystem(enumerator, "drivers")) { + k = enumerator_scan_dir(enumerator, subsysdir, "drivers", "drivers"); + if (k < 0) { + log_debug_errno(k, "device-enumerator: failed to scan drivers: %m"); + r = k; + } + } + + enumerator->scan_uptodate = true; + + return r; +} + +_public_ sd_device *sd_device_enumerator_get_subsystem_first(sd_device_enumerator *enumerator) { + int r; + + assert_return(enumerator, NULL); + + r = device_enumerator_scan_subsystems(enumerator); + if (r < 0) + return NULL; + + enumerator->type = DEVICE_ENUMERATION_TYPE_SUBSYSTEMS; + + return prioq_peek(enumerator->devices); +} + +_public_ sd_device *sd_device_enumerator_get_subsystem_next(sd_device_enumerator *enumerator) { + assert_return(enumerator, NULL); + + if (enumerator->scan_uptodate || + enumerator->type != DEVICE_ENUMERATION_TYPE_SUBSYSTEMS) + return NULL; + + sd_device_unref(prioq_pop(enumerator->devices)); + + return prioq_peek(enumerator->devices); +} + +sd_device *device_enumerator_get_first(sd_device_enumerator *enumerator) { + assert_return(enumerator, NULL); + + return prioq_peek(enumerator->devices); +} + +sd_device *device_enumerator_get_next(sd_device_enumerator *enumerator) { + assert_return(enumerator, NULL); + + sd_device_unref(prioq_pop(enumerator->devices)); + + return prioq_peek(enumerator->devices); +} diff --git a/src/libsystemd/libsystemd-internal/sd-device/device-internal.h b/src/libsystemd/libsystemd-internal/sd-device/device-internal.h new file mode 100644 index 0000000000..b96441de56 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/device-internal.h @@ -0,0 +1,126 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#pragma once + +#include "hashmap.h" +#include "set.h" + +struct sd_device { + uint64_t n_ref; + + sd_device *parent; + bool parent_set; /* no need to try to reload parent */ + + OrderedHashmap *properties; + Iterator properties_iterator; + uint64_t properties_generation; /* changes whenever the properties are changed */ + uint64_t properties_iterator_generation; /* generation when iteration was started */ + + /* the subset of the properties that should be written to the db*/ + OrderedHashmap *properties_db; + + Hashmap *sysattr_values; /* cached sysattr values */ + + Set *sysattrs; /* names of sysattrs */ + Iterator sysattrs_iterator; + bool sysattrs_read; /* don't try to re-read sysattrs once read */ + + Set *tags; + Iterator tags_iterator; + uint64_t tags_generation; /* changes whenever the tags are changed */ + uint64_t tags_iterator_generation; /* generation when iteration was started */ + bool property_tags_outdated; /* need to update TAGS= property */ + + Set *devlinks; + Iterator devlinks_iterator; + uint64_t devlinks_generation; /* changes whenever the devlinks are changed */ + uint64_t devlinks_iterator_generation; /* generation when iteration was started */ + bool property_devlinks_outdated; /* need to update DEVLINKS= property */ + int devlink_priority; + + char **properties_strv; /* the properties hashmap as a strv */ + uint8_t *properties_nulstr; /* the same as a nulstr */ + size_t properties_nulstr_len; + bool properties_buf_outdated; /* need to reread hashmap */ + + int watch_handle; + + char *syspath; + const char *devpath; + const char *sysnum; + char *sysname; + bool sysname_set; /* don't reread sysname */ + + char *devtype; + int ifindex; + char *devname; + dev_t devnum; + + char *subsystem; + bool subsystem_set; /* don't reread subsystem */ + char *driver; + bool driver_set; /* don't reread driver */ + + char *id_filename; + + bool is_initialized; + uint64_t usec_initialized; + + mode_t devmode; + uid_t devuid; + gid_t devgid; + + bool uevent_loaded; /* don't reread uevent */ + bool db_loaded; /* don't reread db */ + + bool sealed; /* don't read more information from uevent/db */ + bool db_persist; /* don't clean up the db when switching from initrd to real root */ +}; + +typedef enum DeviceAction { + DEVICE_ACTION_ADD, + DEVICE_ACTION_REMOVE, + DEVICE_ACTION_CHANGE, + DEVICE_ACTION_MOVE, + DEVICE_ACTION_ONLINE, + DEVICE_ACTION_OFFLINE, + _DEVICE_ACTION_MAX, + _DEVICE_ACTION_INVALID = -1, +} DeviceAction; + +int device_new_aux(sd_device **ret); +int device_add_property_aux(sd_device *device, const char *key, const char *value, bool db); +int device_add_property_internal(sd_device *device, const char *key, const char *value); +int device_read_uevent_file(sd_device *device); +int device_read_db_aux(sd_device *device, bool force); + +int device_set_syspath(sd_device *device, const char *_syspath, bool verify); +int device_set_ifindex(sd_device *device, const char *ifindex); +int device_set_devmode(sd_device *device, const char *devmode); +int device_set_devname(sd_device *device, const char *_devname); +int device_set_devtype(sd_device *device, const char *_devtype); +int device_set_devnum(sd_device *device, const char *major, const char *minor); +int device_set_subsystem(sd_device *device, const char *_subsystem); +int device_set_driver(sd_device *device, const char *_driver); +int device_set_usec_initialized(sd_device *device, const char *initialized); + +DeviceAction device_action_from_string(const char *s) _pure_; +const char *device_action_to_string(DeviceAction a) _const_; diff --git a/src/libsystemd/libsystemd-internal/sd-device/device-private.c b/src/libsystemd/libsystemd-internal/sd-device/device-private.c new file mode 100644 index 0000000000..bf7d3c6f9f --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/device-private.c @@ -0,0 +1,1119 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "device-internal.h" +#include "device-private.h" +#include "device-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hashmap.h" +#include "macro.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "refcnt.h" +#include "set.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "strxcpyx.h" +#include "user-util.h" +#include "util.h" + +int device_add_property(sd_device *device, const char *key, const char *value) { + int r; + + assert(device); + assert(key); + + r = device_add_property_aux(device, key, value, false); + if (r < 0) + return r; + + if (key[0] != '.') { + r = device_add_property_aux(device, key, value, true); + if (r < 0) + return r; + } + + return 0; +} + +static int device_add_property_internal_from_string(sd_device *device, const char *str) { + _cleanup_free_ char *key = NULL; + char *value; + + assert(device); + assert(str); + + key = strdup(str); + if (!key) + return -ENOMEM; + + value = strchr(key, '='); + if (!value) + return -EINVAL; + + *value = '\0'; + + if (isempty(++value)) + value = NULL; + + return device_add_property_internal(device, key, value); +} + +static int handle_db_line(sd_device *device, char key, const char *value) { + char *path; + int r; + + assert(device); + assert(value); + + switch (key) { + case 'S': + path = strjoina("/dev/", value); + r = device_add_devlink(device, path); + if (r < 0) + return r; + + break; + case 'L': + r = safe_atoi(value, &device->devlink_priority); + if (r < 0) + return r; + + break; + case 'E': + r = device_add_property_internal_from_string(device, value); + if (r < 0) + return r; + + break; + case 'G': + r = device_add_tag(device, value); + if (r < 0) + return r; + + break; + case 'W': + r = safe_atoi(value, &device->watch_handle); + if (r < 0) + return r; + + break; + case 'I': + r = device_set_usec_initialized(device, value); + if (r < 0) + return r; + + break; + default: + log_debug("device db: unknown key '%c'", key); + } + + return 0; +} + +void device_set_devlink_priority(sd_device *device, int priority) { + assert(device); + + device->devlink_priority = priority; +} + +void device_set_is_initialized(sd_device *device) { + assert(device); + + device->is_initialized = true; +} + +int device_ensure_usec_initialized(sd_device *device, sd_device *device_old) { + char num[DECIMAL_STR_MAX(usec_t)]; + usec_t usec_initialized; + int r; + + assert(device); + + if (device_old && device_old->usec_initialized > 0) + usec_initialized = device_old->usec_initialized; + else + usec_initialized = now(CLOCK_MONOTONIC); + + r = snprintf(num, sizeof(num), USEC_FMT, usec_initialized); + if (r < 0) + return -errno; + + r = device_set_usec_initialized(device, num); + if (r < 0) + return r; + + return 0; +} + +static int device_read_db(sd_device *device) { + _cleanup_free_ char *db = NULL; + char *path; + const char *id, *value; + char key; + size_t db_len; + unsigned i; + int r; + + enum { + PRE_KEY, + KEY, + PRE_VALUE, + VALUE, + INVALID_LINE, + } state = PRE_KEY; + + assert(device); + + if (device->db_loaded || device->sealed) + return 0; + + r = device_get_id_filename(device, &id); + if (r < 0) + return r; + + path = strjoina("/run/udev/data/", id); + + r = read_full_file(path, &db, &db_len); + if (r < 0) { + if (r == -ENOENT) + return 0; + else + return log_debug_errno(r, "sd-device: failed to read db '%s': %m", path); + } + + /* devices with a database entry are initialized */ + device_set_is_initialized(device); + + for (i = 0; i < db_len; i++) { + switch (state) { + case PRE_KEY: + if (!strchr(NEWLINE, db[i])) { + key = db[i]; + + state = KEY; + } + + break; + case KEY: + if (db[i] != ':') { + log_debug("sd-device: ignoring invalid db entry with key '%c'", key); + + state = INVALID_LINE; + } else { + db[i] = '\0'; + + state = PRE_VALUE; + } + + break; + case PRE_VALUE: + value = &db[i]; + + state = VALUE; + + break; + case INVALID_LINE: + if (strchr(NEWLINE, db[i])) + state = PRE_KEY; + + break; + case VALUE: + if (strchr(NEWLINE, db[i])) { + db[i] = '\0'; + r = handle_db_line(device, key, value); + if (r < 0) + log_debug_errno(r, "sd-device: failed to handle db entry '%c:%s': %m", key, value); + + state = PRE_KEY; + } + + break; + default: + assert_not_reached("invalid state when parsing db"); + } + } + + device->db_loaded = true; + + return 0; +} + +uint64_t device_get_properties_generation(sd_device *device) { + assert(device); + + return device->properties_generation; +} + +uint64_t device_get_tags_generation(sd_device *device) { + assert(device); + + return device->tags_generation; +} + +uint64_t device_get_devlinks_generation(sd_device *device) { + assert(device); + + return device->devlinks_generation; +} + +int device_get_devnode_mode(sd_device *device, mode_t *mode) { + int r; + + assert(device); + assert(mode); + + r = device_read_db(device); + if (r < 0) + return r; + + *mode = device->devmode; + + return 0; +} + +int device_get_devnode_uid(sd_device *device, uid_t *uid) { + int r; + + assert(device); + assert(uid); + + r = device_read_db(device); + if (r < 0) + return r; + + *uid = device->devuid; + + return 0; +} + +static int device_set_devuid(sd_device *device, const char *uid) { + unsigned u; + int r; + + assert(device); + assert(uid); + + r = safe_atou(uid, &u); + if (r < 0) + return r; + + r = device_add_property_internal(device, "DEVUID", uid); + if (r < 0) + return r; + + device->devuid = u; + + return 0; +} + +int device_get_devnode_gid(sd_device *device, gid_t *gid) { + int r; + + assert(device); + assert(gid); + + r = device_read_db(device); + if (r < 0) + return r; + + *gid = device->devgid; + + return 0; +} + +static int device_set_devgid(sd_device *device, const char *gid) { + unsigned g; + int r; + + assert(device); + assert(gid); + + r = safe_atou(gid, &g); + if (r < 0) + return r; + + r = device_add_property_internal(device, "DEVGID", gid); + if (r < 0) + return r; + + device->devgid = g; + + return 0; +} + +static int device_amend(sd_device *device, const char *key, const char *value) { + int r; + + assert(device); + assert(key); + assert(value); + + if (streq(key, "DEVPATH")) { + char *path; + + path = strjoina("/sys", value); + + /* the caller must verify or trust this data (e.g., if it comes from the kernel) */ + r = device_set_syspath(device, path, false); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set syspath to '%s': %m", path); + } else if (streq(key, "SUBSYSTEM")) { + r = device_set_subsystem(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set subsystem to '%s': %m", value); + } else if (streq(key, "DEVTYPE")) { + r = device_set_devtype(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devtype to '%s': %m", value); + } else if (streq(key, "DEVNAME")) { + r = device_set_devname(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devname to '%s': %m", value); + } else if (streq(key, "USEC_INITIALIZED")) { + r = device_set_usec_initialized(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set usec-initialized to '%s': %m", value); + } else if (streq(key, "DRIVER")) { + r = device_set_driver(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set driver to '%s': %m", value); + } else if (streq(key, "IFINDEX")) { + r = device_set_ifindex(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set ifindex to '%s': %m", value); + } else if (streq(key, "DEVMODE")) { + r = device_set_devmode(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devmode to '%s': %m", value); + } else if (streq(key, "DEVUID")) { + r = device_set_devuid(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devuid to '%s': %m", value); + } else if (streq(key, "DEVGID")) { + r = device_set_devgid(device, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devgid to '%s': %m", value); + } else if (streq(key, "DEVLINKS")) { + const char *word, *state; + size_t l; + + FOREACH_WORD(word, l, value, state) { + char devlink[l + 1]; + + strncpy(devlink, word, l); + devlink[l] = '\0'; + + r = device_add_devlink(device, devlink); + if (r < 0) + return log_debug_errno(r, "sd-device: could not add devlink '%s': %m", devlink); + } + } else if (streq(key, "TAGS")) { + const char *word, *state; + size_t l; + + FOREACH_WORD_SEPARATOR(word, l, value, ":", state) { + char tag[l + 1]; + + (void)strncpy(tag, word, l); + tag[l] = '\0'; + + r = device_add_tag(device, tag); + if (r < 0) + return log_debug_errno(r, "sd-device: could not add tag '%s': %m", tag); + } + } else { + r = device_add_property_internal(device, key, value); + if (r < 0) + return log_debug_errno(r, "sd-device: could not add property '%s=%s': %m", key, value); + } + + return 0; +} + +static const char* const device_action_table[_DEVICE_ACTION_MAX] = { + [DEVICE_ACTION_ADD] = "add", + [DEVICE_ACTION_REMOVE] = "remove", + [DEVICE_ACTION_CHANGE] = "change", + [DEVICE_ACTION_MOVE] = "move", + [DEVICE_ACTION_ONLINE] = "online", + [DEVICE_ACTION_OFFLINE] = "offline", +}; + +DEFINE_STRING_TABLE_LOOKUP(device_action, DeviceAction); + +static int device_append(sd_device *device, char *key, const char **_major, const char **_minor, uint64_t *_seqnum, + DeviceAction *_action) { + DeviceAction action = _DEVICE_ACTION_INVALID; + uint64_t seqnum = 0; + const char *major = NULL, *minor = NULL; + char *value; + int r; + + assert(device); + assert(key); + assert(_major); + assert(_minor); + assert(_seqnum); + assert(_action); + + value = strchr(key, '='); + if (!value) { + log_debug("sd-device: not a key-value pair: '%s'", key); + return -EINVAL; + } + + *value = '\0'; + + value++; + + if (streq(key, "MAJOR")) + major = value; + else if (streq(key, "MINOR")) + minor = value; + else { + if (streq(key, "ACTION")) { + action = device_action_from_string(value); + if (action == _DEVICE_ACTION_INVALID) + return -EINVAL; + } else if (streq(key, "SEQNUM")) { + r = safe_atou64(value, &seqnum); + if (r < 0) + return r; + else if (seqnum == 0) + /* kernel only sends seqnum > 0 */ + return -EINVAL; + } + + r = device_amend(device, key, value); + if (r < 0) + return r; + } + + if (major != 0) + *_major = major; + + if (minor != 0) + *_minor = minor; + + if (action != _DEVICE_ACTION_INVALID) + *_action = action; + + if (seqnum > 0) + *_seqnum = seqnum; + + return 0; +} + +void device_seal(sd_device *device) { + assert(device); + + device->sealed = true; +} + +static int device_verify(sd_device *device, DeviceAction action, uint64_t seqnum) { + assert(device); + + if (!device->devpath || !device->subsystem || action == _DEVICE_ACTION_INVALID || seqnum == 0) { + log_debug("sd-device: device created from strv lacks devpath, subsystem, action or seqnum"); + return -EINVAL; + } + + device->sealed = true; + + return 0; +} + +int device_new_from_strv(sd_device **ret, char **strv) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + char **key; + const char *major = NULL, *minor = NULL; + DeviceAction action = _DEVICE_ACTION_INVALID; + uint64_t seqnum; + int r; + + assert(ret); + assert(strv); + + r = device_new_aux(&device); + if (r < 0) + return r; + + STRV_FOREACH(key, strv) { + r = device_append(device, *key, &major, &minor, &seqnum, &action); + if (r < 0) + return r; + } + + if (major) { + r = device_set_devnum(device, major, minor); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devnum %s:%s: %m", major, minor); + } + + r = device_verify(device, action, seqnum); + if (r < 0) + return r; + + *ret = device; + device = NULL; + + return 0; +} + +int device_new_from_nulstr(sd_device **ret, uint8_t *nulstr, size_t len) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + const char *major = NULL, *minor = NULL; + DeviceAction action = _DEVICE_ACTION_INVALID; + uint64_t seqnum; + unsigned i = 0; + int r; + + assert(ret); + assert(nulstr); + assert(len); + + r = device_new_aux(&device); + if (r < 0) + return r; + + while (i < len) { + char *key; + const char *end; + + key = (char*)&nulstr[i]; + end = memchr(key, '\0', len - i); + if (!end) { + log_debug("sd-device: failed to parse nulstr"); + return -EINVAL; + } + i += end - key + 1; + + r = device_append(device, key, &major, &minor, &seqnum, &action); + if (r < 0) + return r; + } + + if (major) { + r = device_set_devnum(device, major, minor); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set devnum %s:%s: %m", major, minor); + } + + r = device_verify(device, action, seqnum); + if (r < 0) + return r; + + *ret = device; + device = NULL; + + return 0; +} + +static int device_update_properties_bufs(sd_device *device) { + const char *val, *prop; + _cleanup_free_ char **buf_strv = NULL; + _cleanup_free_ uint8_t *buf_nulstr = NULL; + size_t allocated_nulstr = 0; + size_t nulstr_len = 0, num = 0, i = 0; + + assert(device); + + if (!device->properties_buf_outdated) + return 0; + + FOREACH_DEVICE_PROPERTY(device, prop, val) { + size_t len = 0; + + len = strlen(prop) + 1 + strlen(val); + + buf_nulstr = GREEDY_REALLOC0(buf_nulstr, allocated_nulstr, nulstr_len + len + 2); + if (!buf_nulstr) + return -ENOMEM; + + strscpyl((char *)buf_nulstr + nulstr_len, len + 1, prop, "=", val, NULL); + nulstr_len += len + 1; + ++num; + } + + /* build buf_strv from buf_nulstr */ + buf_strv = new0(char *, num + 1); + if (!buf_strv) + return -ENOMEM; + + NULSTR_FOREACH(val, (char*) buf_nulstr) { + buf_strv[i] = (char *) val; + assert(i < num); + i++; + } + + free(device->properties_nulstr); + device->properties_nulstr = buf_nulstr; + buf_nulstr = NULL; + device->properties_nulstr_len = nulstr_len; + free(device->properties_strv); + device->properties_strv = buf_strv; + buf_strv = NULL; + + device->properties_buf_outdated = false; + + return 0; +} + +int device_get_properties_nulstr(sd_device *device, const uint8_t **nulstr, size_t *len) { + int r; + + assert(device); + assert(nulstr); + assert(len); + + r = device_update_properties_bufs(device); + if (r < 0) + return r; + + *nulstr = device->properties_nulstr; + *len = device->properties_nulstr_len; + + return 0; +} + +int device_get_properties_strv(sd_device *device, char ***strv) { + int r; + + assert(device); + assert(strv); + + r = device_update_properties_bufs(device); + if (r < 0) + return r; + + *strv = device->properties_strv; + + return 0; +} + +int device_get_devlink_priority(sd_device *device, int *priority) { + int r; + + assert(device); + assert(priority); + + r = device_read_db(device); + if (r < 0) + return r; + + *priority = device->devlink_priority; + + return 0; +} + +int device_get_watch_handle(sd_device *device, int *handle) { + int r; + + assert(device); + assert(handle); + + r = device_read_db(device); + if (r < 0) + return r; + + *handle = device->watch_handle; + + return 0; +} + +void device_set_watch_handle(sd_device *device, int handle) { + assert(device); + + device->watch_handle = handle; +} + +int device_rename(sd_device *device, const char *name) { + _cleanup_free_ char *dirname = NULL; + char *new_syspath; + const char *interface; + int r; + + assert(device); + assert(name); + + dirname = dirname_malloc(device->syspath); + if (!dirname) + return -ENOMEM; + + new_syspath = strjoina(dirname, "/", name); + + /* the user must trust that the new name is correct */ + r = device_set_syspath(device, new_syspath, false); + if (r < 0) + return r; + + r = sd_device_get_property_value(device, "INTERFACE", &interface); + if (r >= 0) { + r = device_add_property_internal(device, "INTERFACE", name); + if (r < 0) + return r; + + /* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */ + r = device_add_property_internal(device, "INTERFACE_OLD", interface); + if (r < 0) + return r; + } else if (r != -ENOENT) + return r; + + return 0; +} + +int device_shallow_clone(sd_device *old_device, sd_device **new_device) { + _cleanup_(sd_device_unrefp) sd_device *ret = NULL; + int r; + + assert(old_device); + assert(new_device); + + r = device_new_aux(&ret); + if (r < 0) + return r; + + r = device_set_syspath(ret, old_device->syspath, false); + if (r < 0) + return r; + + r = device_set_subsystem(ret, old_device->subsystem); + if (r < 0) + return r; + + ret->devnum = old_device->devnum; + + *new_device = ret; + ret = NULL; + + return 0; +} + +int device_clone_with_db(sd_device *old_device, sd_device **new_device) { + _cleanup_(sd_device_unrefp) sd_device *ret = NULL; + int r; + + assert(old_device); + assert(new_device); + + r = device_shallow_clone(old_device, &ret); + if (r < 0) + return r; + + r = device_read_db(ret); + if (r < 0) + return r; + + ret->sealed = true; + + *new_device = ret; + ret = NULL; + + return 0; +} + +int device_new_from_synthetic_event(sd_device **new_device, const char *syspath, const char *action) { + _cleanup_(sd_device_unrefp) sd_device *ret = NULL; + int r; + + assert(new_device); + assert(syspath); + assert(action); + + r = sd_device_new_from_syspath(&ret, syspath); + if (r < 0) + return r; + + r = device_read_uevent_file(ret); + if (r < 0) + return r; + + r = device_add_property_internal(ret, "ACTION", action); + if (r < 0) + return r; + + *new_device = ret; + ret = NULL; + + return 0; +} + +int device_copy_properties(sd_device *device_dst, sd_device *device_src) { + const char *property, *value; + int r; + + assert(device_dst); + assert(device_src); + + FOREACH_DEVICE_PROPERTY(device_src, property, value) { + r = device_add_property(device_dst, property, value); + if (r < 0) + return r; + } + + return 0; +} + +void device_cleanup_tags(sd_device *device) { + assert(device); + + set_free_free(device->tags); + device->tags = NULL; + device->property_tags_outdated = true; + device->tags_generation ++; +} + +void device_cleanup_devlinks(sd_device *device) { + assert(device); + + set_free_free(device->devlinks); + device->devlinks = NULL; + device->property_devlinks_outdated = true; + device->devlinks_generation ++; +} + +void device_remove_tag(sd_device *device, const char *tag) { + assert(device); + assert(tag); + + free(set_remove(device->tags, tag)); + device->property_tags_outdated = true; + device->tags_generation ++; +} + +static int device_tag(sd_device *device, const char *tag, bool add) { + const char *id; + char *path; + int r; + + assert(device); + assert(tag); + + r = device_get_id_filename(device, &id); + if (r < 0) + return r; + + path = strjoina("/run/udev/tags/", tag, "/", id); + + if (add) { + r = touch_file(path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, 0444); + if (r < 0) + return r; + } else { + r = unlink(path); + if (r < 0 && errno != ENOENT) + return -errno; + } + + return 0; +} + +int device_tag_index(sd_device *device, sd_device *device_old, bool add) { + const char *tag; + int r = 0, k; + + if (add && device_old) { + /* delete possible left-over tags */ + FOREACH_DEVICE_TAG(device_old, tag) { + if (!sd_device_has_tag(device, tag)) { + k = device_tag(device_old, tag, false); + if (r >= 0 && k < 0) + r = k; + } + } + } + + FOREACH_DEVICE_TAG(device, tag) { + k = device_tag(device, tag, add); + if (r >= 0 && k < 0) + r = k; + } + + return r; +} + +static bool device_has_info(sd_device *device) { + assert(device); + + if (!set_isempty(device->devlinks)) + return true; + + if (device->devlink_priority != 0) + return true; + + if (!ordered_hashmap_isempty(device->properties_db)) + return true; + + if (!set_isempty(device->tags)) + return true; + + if (device->watch_handle >= 0) + return true; + + return false; +} + +void device_set_db_persist(sd_device *device) { + assert(device); + + device->db_persist = true; +} + +int device_update_db(sd_device *device) { + const char *id; + char *path; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *path_tmp = NULL; + bool has_info; + int r; + + assert(device); + + has_info = device_has_info(device); + + r = device_get_id_filename(device, &id); + if (r < 0) + return r; + + path = strjoina("/run/udev/data/", id); + + /* do not store anything for otherwise empty devices */ + if (!has_info && major(device->devnum) == 0 && device->ifindex == 0) { + r = unlink(path); + if (r < 0 && errno != ENOENT) + return -errno; + + return 0; + } + + /* write a database file */ + r = mkdir_parents(path, 0755); + if (r < 0) + return r; + + r = fopen_temporary(path, &f, &path_tmp); + if (r < 0) + return r; + + /* + * set 'sticky' bit to indicate that we should not clean the + * database when we transition from initramfs to the real root + */ + if (device->db_persist) { + r = fchmod(fileno(f), 01644); + if (r < 0) { + r = -errno; + goto fail; + } + } else { + r = fchmod(fileno(f), 0644); + if (r < 0) { + r = -errno; + goto fail; + } + } + + if (has_info) { + const char *property, *value, *tag; + Iterator i; + + if (major(device->devnum) > 0) { + const char *devlink; + + FOREACH_DEVICE_DEVLINK(device, devlink) + fprintf(f, "S:%s\n", devlink + strlen("/dev/")); + + if (device->devlink_priority != 0) + fprintf(f, "L:%i\n", device->devlink_priority); + + if (device->watch_handle >= 0) + fprintf(f, "W:%i\n", device->watch_handle); + } + + if (device->usec_initialized > 0) + fprintf(f, "I:"USEC_FMT"\n", device->usec_initialized); + + ORDERED_HASHMAP_FOREACH_KEY(value, property, device->properties_db, i) + fprintf(f, "E:%s=%s\n", property, value); + + FOREACH_DEVICE_TAG(device, tag) + fprintf(f, "G:%s\n", tag); + } + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + r = rename(path_tmp, path); + if (r < 0) { + r = -errno; + goto fail; + } + + log_debug("created %s file '%s' for '%s'", has_info ? "db" : "empty", + path, device->devpath); + + return 0; + +fail: + (void) unlink(path); + (void) unlink(path_tmp); + + return log_error_errno(r, "failed to create %s file '%s' for '%s'", has_info ? "db" : "empty", path, device->devpath); +} + +int device_delete_db(sd_device *device) { + const char *id; + char *path; + int r; + + assert(device); + + r = device_get_id_filename(device, &id); + if (r < 0) + return r; + + path = strjoina("/run/udev/data/", id); + + r = unlink(path); + if (r < 0 && errno != ENOENT) + return -errno; + + return 0; +} + +int device_read_db_force(sd_device *device) { + assert(device); + + return device_read_db_aux(device, true); +} diff --git a/src/libsystemd/libsystemd-internal/sd-device/device-private.h b/src/libsystemd/libsystemd-internal/sd-device/device-private.h new file mode 100644 index 0000000000..d6add2f7b2 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/device-private.h @@ -0,0 +1,68 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include +#include +#include + +#include + +int device_new_from_nulstr(sd_device **ret, uint8_t *nulstr, size_t len); +int device_new_from_strv(sd_device **ret, char **strv); + +int device_get_id_filename(sd_device *device, const char **ret); + +int device_get_devlink_priority(sd_device *device, int *priority); +int device_get_watch_handle(sd_device *device, int *handle); +int device_get_devnode_mode(sd_device *device, mode_t *mode); +int device_get_devnode_uid(sd_device *device, uid_t *uid); +int device_get_devnode_gid(sd_device *device, gid_t *gid); + +void device_seal(sd_device *device); +void device_set_is_initialized(sd_device *device); +void device_set_watch_handle(sd_device *device, int fd); +void device_set_db_persist(sd_device *device); +void device_set_devlink_priority(sd_device *device, int priority); +int device_ensure_usec_initialized(sd_device *device, sd_device *device_old); +int device_add_devlink(sd_device *device, const char *devlink); +int device_add_property(sd_device *device, const char *property, const char *value); +int device_add_tag(sd_device *device, const char *tag); +void device_remove_tag(sd_device *device, const char *tag); +void device_cleanup_tags(sd_device *device); +void device_cleanup_devlinks(sd_device *device); + +uint64_t device_get_properties_generation(sd_device *device); +uint64_t device_get_tags_generation(sd_device *device); +uint64_t device_get_devlinks_generation(sd_device *device); + +int device_get_properties_nulstr(sd_device *device, const uint8_t **nulstr, size_t *len); +int device_get_properties_strv(sd_device *device, char ***strv); + +int device_rename(sd_device *device, const char *name); +int device_shallow_clone(sd_device *old_device, sd_device **new_device); +int device_clone_with_db(sd_device *old_device, sd_device **new_device); +int device_copy_properties(sd_device *device_dst, sd_device *device_src); +int device_new_from_synthetic_event(sd_device **new_device, const char *syspath, const char *action); + +int device_tag_index(sd_device *dev, sd_device *dev_old, bool add); +int device_update_db(sd_device *device); +int device_delete_db(sd_device *device); +int device_read_db_force(sd_device *device); diff --git a/src/libsystemd/libsystemd-internal/sd-device/device-util.h b/src/libsystemd/libsystemd-internal/sd-device/device-util.h new file mode 100644 index 0000000000..5b42e11de6 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/device-util.h @@ -0,0 +1,52 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014-2015 Tom Gundersen + + 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 . +***/ + +#include "util.h" + +#define FOREACH_DEVICE_PROPERTY(device, key, value) \ + for (key = sd_device_get_property_first(device, &(value)); \ + key; \ + key = sd_device_get_property_next(device, &(value))) + +#define FOREACH_DEVICE_TAG(device, tag) \ + for (tag = sd_device_get_tag_first(device); \ + tag; \ + tag = sd_device_get_tag_next(device)) + +#define FOREACH_DEVICE_SYSATTR(device, attr) \ + for (attr = sd_device_get_sysattr_first(device); \ + attr; \ + attr = sd_device_get_sysattr_next(device)) + +#define FOREACH_DEVICE_DEVLINK(device, devlink) \ + for (devlink = sd_device_get_devlink_first(device); \ + devlink; \ + devlink = sd_device_get_devlink_next(device)) + +#define FOREACH_DEVICE(enumerator, device) \ + for (device = sd_device_enumerator_get_device_first(enumerator); \ + device; \ + device = sd_device_enumerator_get_device_next(enumerator)) + +#define FOREACH_SUBSYSTEM(enumerator, device) \ + for (device = sd_device_enumerator_get_subsystem_first(enumerator); \ + device; \ + device = sd_device_enumerator_get_subsystem_next(enumerator)) diff --git a/src/libsystemd/libsystemd-internal/sd-device/sd-device.c b/src/libsystemd/libsystemd-internal/sd-device/sd-device.c new file mode 100644 index 0000000000..895002499c --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-device/sd-device.c @@ -0,0 +1,1873 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "device-internal.h" +#include "device-private.h" +#include "device-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "hashmap.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "set.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "strxcpyx.h" +#include "util.h" + +int device_new_aux(sd_device **ret) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + + assert(ret); + + device = new0(sd_device, 1); + if (!device) + return -ENOMEM; + + device->n_ref = 1; + device->watch_handle = -1; + + *ret = device; + device = NULL; + + return 0; +} + +_public_ sd_device *sd_device_ref(sd_device *device) { + if (device) + assert_se(++ device->n_ref >= 2); + + return device; +} + +_public_ sd_device *sd_device_unref(sd_device *device) { + if (device && -- device->n_ref == 0) { + sd_device_unref(device->parent); + free(device->syspath); + free(device->sysname); + free(device->devtype); + free(device->devname); + free(device->subsystem); + free(device->driver); + free(device->id_filename); + free(device->properties_strv); + free(device->properties_nulstr); + + ordered_hashmap_free_free_free(device->properties); + ordered_hashmap_free_free_free(device->properties_db); + hashmap_free_free_free(device->sysattr_values); + set_free_free(device->sysattrs); + set_free_free(device->tags); + set_free_free(device->devlinks); + + free(device); + } + + return NULL; +} + +int device_add_property_aux(sd_device *device, const char *_key, const char *_value, bool db) { + OrderedHashmap **properties; + + assert(device); + assert(_key); + + if (db) + properties = &device->properties_db; + else + properties = &device->properties; + + if (_value) { + _cleanup_free_ char *key = NULL, *value = NULL, *old_key = NULL, *old_value = NULL; + int r; + + r = ordered_hashmap_ensure_allocated(properties, &string_hash_ops); + if (r < 0) + return r; + + key = strdup(_key); + if (!key) + return -ENOMEM; + + value = strdup(_value); + if (!value) + return -ENOMEM; + + old_value = ordered_hashmap_get2(*properties, key, (void**) &old_key); + + r = ordered_hashmap_replace(*properties, key, value); + if (r < 0) + return r; + + key = NULL; + value = NULL; + } else { + _cleanup_free_ char *key = NULL; + _cleanup_free_ char *value = NULL; + + value = ordered_hashmap_remove2(*properties, _key, (void**) &key); + } + + if (!db) { + device->properties_generation ++; + device->properties_buf_outdated = true; + } + + return 0; +} + +int device_add_property_internal(sd_device *device, const char *key, const char *value) { + return device_add_property_aux(device, key, value, false); +} + +int device_set_syspath(sd_device *device, const char *_syspath, bool verify) { + _cleanup_free_ char *syspath = NULL; + const char *devpath; + int r; + + assert(device); + assert(_syspath); + + /* must be a subdirectory of /sys */ + if (!path_startswith(_syspath, "/sys/")) { + log_debug("sd-device: syspath '%s' is not a subdirectory of /sys", _syspath); + return -EINVAL; + } + + if (verify) { + r = readlink_and_canonicalize(_syspath, &syspath); + if (r == -ENOENT) + /* the device does not exist (any more?) */ + return -ENODEV; + else if (r == -EINVAL) { + /* not a symlink */ + syspath = canonicalize_file_name(_syspath); + if (!syspath) { + if (errno == ENOENT) + /* the device does not exist (any more?) */ + return -ENODEV; + + return log_debug_errno(errno, "sd-device: could not canonicalize '%s': %m", _syspath); + } + } else if (r < 0) { + log_debug_errno(r, "sd-device: could not get target of '%s': %m", _syspath); + return r; + } + + if (path_startswith(syspath, "/sys/devices/")) { + char *path; + + /* all 'devices' require an 'uevent' file */ + path = strjoina(syspath, "/uevent"); + r = access(path, F_OK); + if (r < 0) { + if (errno == ENOENT) + /* this is not a valid device */ + return -ENODEV; + + log_debug("sd-device: %s does not have an uevent file: %m", syspath); + return -errno; + } + } else { + /* everything else just just needs to be a directory */ + if (!is_dir(syspath, false)) + return -ENODEV; + } + } else { + syspath = strdup(_syspath); + if (!syspath) + return -ENOMEM; + } + + devpath = syspath + strlen("/sys"); + + r = device_add_property_internal(device, "DEVPATH", devpath); + if (r < 0) + return r; + + free(device->syspath); + device->syspath = syspath; + syspath = NULL; + + device->devpath = devpath; + + return 0; +} + +_public_ int sd_device_new_from_syspath(sd_device **ret, const char *syspath) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + int r; + + assert_return(ret, -EINVAL); + assert_return(syspath, -EINVAL); + + r = device_new_aux(&device); + if (r < 0) + return r; + + r = device_set_syspath(device, syspath, true); + if (r < 0) + return r; + + *ret = device; + device = NULL; + + return 0; +} + +_public_ int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum) { + char *syspath; + char id[DECIMAL_STR_MAX(unsigned) * 2 + 1]; + + assert_return(ret, -EINVAL); + assert_return(type == 'b' || type == 'c', -EINVAL); + + /* use /sys/dev/{block,char}/: link */ + snprintf(id, sizeof(id), "%u:%u", major(devnum), minor(devnum)); + + syspath = strjoina("/sys/dev/", (type == 'b' ? "block" : "char"), "/", id); + + return sd_device_new_from_syspath(ret, syspath); +} + +_public_ int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname) { + char *syspath; + + assert_return(ret, -EINVAL); + assert_return(subsystem, -EINVAL); + assert_return(sysname, -EINVAL); + + if (streq(subsystem, "subsystem")) { + syspath = strjoina("/sys/subsystem/", sysname); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + + syspath = strjoina("/sys/bus/", sysname); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + + syspath = strjoina("/sys/class/", sysname); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + } else if (streq(subsystem, "module")) { + syspath = strjoina("/sys/module/", sysname); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + } else if (streq(subsystem, "drivers")) { + char subsys[PATH_MAX]; + char *driver; + + strscpy(subsys, sizeof(subsys), sysname); + driver = strchr(subsys, ':'); + if (driver) { + driver[0] = '\0'; + driver++; + + syspath = strjoina("/sys/subsystem/", subsys, "/drivers/", driver); + if (access(syspath, F_OK) >= 0) + return sd_device_new_from_syspath(ret, syspath); + + 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] = '!'; + + 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/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); + } + + return -ENODEV; +} + +int device_set_devtype(sd_device *device, const char *_devtype) { + _cleanup_free_ char *devtype = NULL; + int r; + + assert(device); + assert(_devtype); + + devtype = strdup(_devtype); + if (!devtype) + return -ENOMEM; + + r = device_add_property_internal(device, "DEVTYPE", devtype); + if (r < 0) + return r; + + free(device->devtype); + device->devtype = devtype; + devtype = NULL; + + return 0; +} + +int device_set_ifindex(sd_device *device, const char *_ifindex) { + int ifindex, r; + + assert(device); + assert(_ifindex); + + r = parse_ifindex(_ifindex, &ifindex); + if (r < 0) + return r; + + r = device_add_property_internal(device, "IFINDEX", _ifindex); + if (r < 0) + return r; + + device->ifindex = ifindex; + + return 0; +} + +int device_set_devname(sd_device *device, const char *_devname) { + _cleanup_free_ char *devname = NULL; + int r; + + assert(device); + assert(_devname); + + if (_devname[0] != '/') { + r = asprintf(&devname, "/dev/%s", _devname); + if (r < 0) + return -ENOMEM; + } else { + devname = strdup(_devname); + if (!devname) + return -ENOMEM; + } + + r = device_add_property_internal(device, "DEVNAME", devname); + if (r < 0) + return r; + + free(device->devname); + device->devname = devname; + devname = NULL; + + return 0; +} + +int device_set_devmode(sd_device *device, const char *_devmode) { + unsigned devmode; + int r; + + assert(device); + assert(_devmode); + + r = safe_atou(_devmode, &devmode); + if (r < 0) + return r; + + if (devmode > 07777) + return -EINVAL; + + r = device_add_property_internal(device, "DEVMODE", _devmode); + if (r < 0) + return r; + + device->devmode = devmode; + + return 0; +} + +int device_set_devnum(sd_device *device, const char *major, const char *minor) { + unsigned maj = 0, min = 0; + int r; + + assert(device); + assert(major); + + r = safe_atou(major, &maj); + if (r < 0) + return r; + if (!maj) + return 0; + + if (minor) { + r = safe_atou(minor, &min); + if (r < 0) + return r; + } + + r = device_add_property_internal(device, "MAJOR", major); + if (r < 0) + return r; + + if (minor) { + r = device_add_property_internal(device, "MINOR", minor); + if (r < 0) + return r; + } + + device->devnum = makedev(maj, min); + + return 0; +} + +static int handle_uevent_line(sd_device *device, const char *key, const char *value, const char **major, const char **minor) { + int r; + + assert(device); + assert(key); + assert(value); + assert(major); + assert(minor); + + if (streq(key, "DEVTYPE")) { + r = device_set_devtype(device, value); + if (r < 0) + return r; + } else if (streq(key, "IFINDEX")) { + r = device_set_ifindex(device, value); + if (r < 0) + return r; + } else if (streq(key, "DEVNAME")) { + r = device_set_devname(device, value); + if (r < 0) + return r; + } else if (streq(key, "DEVMODE")) { + r = device_set_devmode(device, value); + if (r < 0) + return r; + } else if (streq(key, "MAJOR")) + *major = value; + else if (streq(key, "MINOR")) + *minor = value; + else { + r = device_add_property_internal(device, key, value); + if (r < 0) + return r; + } + + return 0; +} + +int device_read_uevent_file(sd_device *device) { + _cleanup_free_ char *uevent = NULL; + const char *syspath, *key = NULL, *value = NULL, *major = NULL, *minor = NULL; + char *path; + size_t uevent_len; + unsigned i; + int r; + + enum { + PRE_KEY, + KEY, + PRE_VALUE, + VALUE, + INVALID_LINE, + } state = PRE_KEY; + + assert(device); + + if (device->uevent_loaded || device->sealed) + return 0; + + device->uevent_loaded = true; + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + path = strjoina(syspath, "/uevent"); + + r = read_full_file(path, &uevent, &uevent_len); + if (r == -EACCES) + /* empty uevent files may be write-only */ + return 0; + else if (r == -ENOENT) + /* some devices may not have uevent files, see set_syspath() */ + return 0; + else if (r < 0) { + log_debug_errno(r, "sd-device: failed to read uevent file '%s': %m", path); + return r; + } + + for (i = 0; i < uevent_len; i++) { + switch (state) { + case PRE_KEY: + if (!strchr(NEWLINE, uevent[i])) { + key = &uevent[i]; + + state = KEY; + } + + break; + case KEY: + if (uevent[i] == '=') { + uevent[i] = '\0'; + + state = PRE_VALUE; + } else if (strchr(NEWLINE, uevent[i])) { + uevent[i] = '\0'; + log_debug("sd-device: ignoring invalid uevent line '%s'", key); + + state = PRE_KEY; + } + + break; + case PRE_VALUE: + value = &uevent[i]; + + state = VALUE; + + break; + case VALUE: + if (strchr(NEWLINE, uevent[i])) { + uevent[i] = '\0'; + + r = handle_uevent_line(device, key, value, &major, &minor); + if (r < 0) + log_debug_errno(r, "sd-device: failed to handle uevent entry '%s=%s': %m", key, value); + + state = PRE_KEY; + } + + break; + default: + assert_not_reached("invalid state when parsing uevent file"); + } + } + + if (major) { + r = device_set_devnum(device, major, minor); + if (r < 0) + log_debug_errno(r, "sd-device: could not set 'MAJOR=%s' or 'MINOR=%s' from '%s': %m", major, minor, path); + } + + return 0; +} + +_public_ int sd_device_get_ifindex(sd_device *device, int *ifindex) { + int r; + + assert_return(device, -EINVAL); + assert_return(ifindex, -EINVAL); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + *ifindex = device->ifindex; + + return 0; +} + +_public_ int sd_device_new_from_device_id(sd_device **ret, const char *id) { + int r; + + assert_return(ret, -EINVAL); + assert_return(id, -EINVAL); + + switch (id[0]) { + case 'b': + case 'c': + { + char type; + int maj, min; + + r = sscanf(id, "%c%i:%i", &type, &maj, &min); + if (r != 3) + return -EINVAL; + + return sd_device_new_from_devnum(ret, type, makedev(maj, min)); + } + case 'n': + { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + _cleanup_close_ int sk = -1; + struct ifreq ifr = {}; + int ifindex; + + r = parse_ifindex(&id[1], &ifr.ifr_ifindex); + if (r < 0) + return r; + + sk = socket(PF_INET, SOCK_DGRAM, 0); + if (sk < 0) + return -errno; + + r = ioctl(sk, SIOCGIFNAME, &ifr); + if (r < 0) + return -errno; + + r = sd_device_new_from_subsystem_sysname(&device, "net", ifr.ifr_name); + if (r < 0) + return r; + + r = sd_device_get_ifindex(device, &ifindex); + if (r < 0) + return r; + + /* this is racey, so we might end up with the wrong device */ + if (ifr.ifr_ifindex != ifindex) + return -ENODEV; + + *ret = device; + device = NULL; + + return 0; + } + case '+': + { + char subsys[PATH_MAX]; + char *sysname; + + (void)strscpy(subsys, sizeof(subsys), id + 1); + sysname = strchr(subsys, ':'); + if (!sysname) + return -EINVAL; + + sysname[0] = '\0'; + sysname ++; + + return sd_device_new_from_subsystem_sysname(ret, subsys, sysname); + } + default: + return -EINVAL; + } +} + +_public_ int sd_device_get_syspath(sd_device *device, const char **ret) { + assert_return(device, -EINVAL); + assert_return(ret, -EINVAL); + + assert(path_startswith(device->syspath, "/sys/")); + + *ret = device->syspath; + + return 0; +} + +static int device_new_from_child(sd_device **ret, sd_device *child) { + _cleanup_free_ char *path = NULL; + const char *subdir, *syspath; + int r; + + assert(ret); + assert(child); + + r = sd_device_get_syspath(child, &syspath); + if (r < 0) + return r; + + path = strdup(syspath); + if (!path) + return -ENOMEM; + subdir = path + strlen("/sys"); + + for (;;) { + char *pos; + + pos = strrchr(subdir, '/'); + if (!pos || pos < subdir + 2) + break; + + *pos = '\0'; + + r = sd_device_new_from_syspath(ret, path); + if (r < 0) + continue; + + return 0; + } + + return -ENODEV; +} + +_public_ int sd_device_get_parent(sd_device *child, sd_device **ret) { + + assert_return(ret, -EINVAL); + assert_return(child, -EINVAL); + + if (!child->parent_set) { + child->parent_set = true; + + (void)device_new_from_child(&child->parent, child); + } + + if (!child->parent) + return -ENOENT; + + *ret = child->parent; + + return 0; +} + +int device_set_subsystem(sd_device *device, const char *_subsystem) { + _cleanup_free_ char *subsystem = NULL; + int r; + + assert(device); + assert(_subsystem); + + subsystem = strdup(_subsystem); + if (!subsystem) + return -ENOMEM; + + r = device_add_property_internal(device, "SUBSYSTEM", subsystem); + if (r < 0) + return r; + + free(device->subsystem); + device->subsystem = subsystem; + subsystem = NULL; + + device->subsystem_set = true; + + return 0; +} + +_public_ int sd_device_get_subsystem(sd_device *device, const char **ret) { + assert_return(ret, -EINVAL); + assert_return(device, -EINVAL); + + 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) + r = device_set_subsystem(device, subsystem); + /* 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/")) + 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; + } + + if (!device->subsystem) + return -ENOENT; + + *ret = device->subsystem; + + return 0; +} + +_public_ int sd_device_get_devtype(sd_device *device, const char **devtype) { + int r; + + assert(devtype); + assert(device); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + *devtype = device->devtype; + + return 0; +} + +_public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret) { + sd_device *parent = NULL; + int r; + + assert_return(child, -EINVAL); + assert_return(subsystem, -EINVAL); + + r = sd_device_get_parent(child, &parent); + while (r >= 0) { + const char *parent_subsystem = NULL; + const char *parent_devtype = NULL; + + (void)sd_device_get_subsystem(parent, &parent_subsystem); + if (streq_ptr(parent_subsystem, subsystem)) { + if (!devtype) + break; + + (void)sd_device_get_devtype(parent, &parent_devtype); + if (streq_ptr(parent_devtype, devtype)) + break; + } + r = sd_device_get_parent(parent, &parent); + } + + if (r < 0) + return r; + + *ret = parent; + + return 0; +} + +_public_ int sd_device_get_devnum(sd_device *device, dev_t *devnum) { + int r; + + assert_return(device, -EINVAL); + assert_return(devnum, -EINVAL); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + *devnum = device->devnum; + + return 0; +} + +int device_set_driver(sd_device *device, const char *_driver) { + _cleanup_free_ char *driver = NULL; + int r; + + assert(device); + assert(_driver); + + driver = strdup(_driver); + if (!driver) + return -ENOMEM; + + r = device_add_property_internal(device, "DRIVER", driver); + if (r < 0) + return r; + + free(device->driver); + device->driver = driver; + driver = NULL; + + device->driver_set = true; + + return 0; +} + +_public_ int sd_device_get_driver(sd_device *device, const char **ret) { + assert_return(device, -EINVAL); + assert_return(ret, -EINVAL); + + if (!device->driver_set) { + _cleanup_free_ char *driver = NULL; + const char *syspath; + char *path; + int r; + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + path = strjoina(syspath, "/driver"); + r = readlink_value(path, &driver); + if (r >= 0) { + r = device_set_driver(device, driver); + if (r < 0) + return log_debug_errno(r, "sd-device: could not set driver for %s: %m", device->devpath); + } else if (r == -ENOENT) + device->driver_set = true; + else + return log_debug_errno(r, "sd-device: could not set driver for %s: %m", device->devpath); + } + + if (!device->driver) + return -ENOENT; + + *ret = device->driver; + + return 0; +} + +_public_ int sd_device_get_devpath(sd_device *device, const char **devpath) { + assert_return(device, -EINVAL); + assert_return(devpath, -EINVAL); + + assert(device->devpath); + assert(device->devpath[0] == '/'); + + *devpath = device->devpath; + + return 0; +} + +_public_ int sd_device_get_devname(sd_device *device, const char **devname) { + int r; + + assert_return(device, -EINVAL); + assert_return(devname, -EINVAL); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + if (!device->devname) + return -ENOENT; + + assert(path_startswith(device->devname, "/dev/")); + + *devname = device->devname; + + return 0; +} + +static int device_set_sysname(sd_device *device) { + _cleanup_free_ char *sysname = NULL; + const char *sysnum = NULL; + const char *pos; + size_t len = 0; + + pos = strrchr(device->devpath, '/'); + if (!pos) + return -EINVAL; + pos ++; + + /* devpath is not a root directory */ + if (*pos == '\0' || pos <= device->devpath) + return -EINVAL; + + sysname = strdup(pos); + if (!sysname) + return -ENOMEM; + + /* some devices have '!' in their name, change that to '/' */ + while (sysname[len] != '\0') { + if (sysname[len] == '!') + sysname[len] = '/'; + + len ++; + } + + /* trailing number */ + while (len > 0 && isdigit(sysname[--len])) + sysnum = &sysname[len]; + + if (len == 0) + sysnum = NULL; + + free(device->sysname); + device->sysname = sysname; + sysname = NULL; + + device->sysnum = sysnum; + + device->sysname_set = true; + + return 0; +} + +_public_ int sd_device_get_sysname(sd_device *device, const char **ret) { + int r; + + assert_return(device, -EINVAL); + assert_return(ret, -EINVAL); + + if (!device->sysname_set) { + r = device_set_sysname(device); + if (r < 0) + return r; + } + + assert_return(device->sysname, -ENOENT); + + *ret = device->sysname; + + return 0; +} + +_public_ int sd_device_get_sysnum(sd_device *device, const char **ret) { + int r; + + assert_return(device, -EINVAL); + assert_return(ret, -EINVAL); + + if (!device->sysname_set) { + r = device_set_sysname(device); + if (r < 0) + return r; + } + + *ret = device->sysnum; + + return 0; +} + +static bool is_valid_tag(const char *tag) { + assert(tag); + + return !strchr(tag, ':') && !strchr(tag, ' '); +} + +int device_add_tag(sd_device *device, const char *tag) { + int r; + + assert(device); + assert(tag); + + if (!is_valid_tag(tag)) + return -EINVAL; + + r = set_ensure_allocated(&device->tags, &string_hash_ops); + if (r < 0) + return r; + + r = set_put_strdup(device->tags, tag); + if (r < 0) + return r; + + device->tags_generation ++; + device->property_tags_outdated = true; + + return 0; +} + +int device_add_devlink(sd_device *device, const char *devlink) { + int r; + + assert(device); + assert(devlink); + + r = set_ensure_allocated(&device->devlinks, &string_hash_ops); + if (r < 0) + return r; + + r = set_put_strdup(device->devlinks, devlink); + if (r < 0) + return r; + + device->devlinks_generation ++; + device->property_devlinks_outdated = true; + + return 0; +} + +static int device_add_property_internal_from_string(sd_device *device, const char *str) { + _cleanup_free_ char *key = NULL; + char *value; + + assert(device); + assert(str); + + key = strdup(str); + if (!key) + return -ENOMEM; + + value = strchr(key, '='); + if (!value) + return -EINVAL; + + *value = '\0'; + + if (isempty(++value)) + value = NULL; + + return device_add_property_internal(device, key, value); +} + +int device_set_usec_initialized(sd_device *device, const char *initialized) { + uint64_t usec_initialized; + int r; + + assert(device); + assert(initialized); + + r = safe_atou64(initialized, &usec_initialized); + if (r < 0) + return r; + + r = device_add_property_internal(device, "USEC_INITIALIZED", initialized); + if (r < 0) + return r; + + device->usec_initialized = usec_initialized; + + return 0; +} + +static int handle_db_line(sd_device *device, char key, const char *value) { + char *path; + int r; + + assert(device); + assert(value); + + switch (key) { + case 'G': + r = device_add_tag(device, value); + if (r < 0) + return r; + + break; + case 'S': + path = strjoina("/dev/", value); + r = device_add_devlink(device, path); + if (r < 0) + return r; + + break; + case 'E': + r = device_add_property_internal_from_string(device, value); + if (r < 0) + return r; + + break; + case 'I': + r = device_set_usec_initialized(device, value); + if (r < 0) + return r; + + break; + case 'L': + r = safe_atoi(value, &device->devlink_priority); + if (r < 0) + return r; + + break; + case 'W': + r = safe_atoi(value, &device->watch_handle); + if (r < 0) + return r; + + break; + default: + log_debug("device db: unknown key '%c'", key); + } + + return 0; +} + +int device_get_id_filename(sd_device *device, const char **ret) { + assert(device); + assert(ret); + + if (!device->id_filename) { + _cleanup_free_ char *id = NULL; + const char *subsystem; + dev_t devnum; + int ifindex, r; + + r = sd_device_get_subsystem(device, &subsystem); + if (r < 0) + return r; + + r = sd_device_get_devnum(device, &devnum); + if (r < 0) + return r; + + r = sd_device_get_ifindex(device, &ifindex); + if (r < 0) + return r; + + if (major(devnum) > 0) { + assert(subsystem); + + /* use dev_t -- b259:131072, c254:0 */ + r = asprintf(&id, "%c%u:%u", + streq(subsystem, "block") ? 'b' : 'c', + major(devnum), minor(devnum)); + if (r < 0) + return -ENOMEM; + } else if (ifindex > 0) { + /* use netdev ifindex -- n3 */ + r = asprintf(&id, "n%u", ifindex); + if (r < 0) + return -ENOMEM; + } else { + /* use $subsys:$sysname -- pci:0000:00:1f.2 + * sysname() has '!' translated, get it from devpath + */ + const char *sysname; + + sysname = basename(device->devpath); + if (!sysname) + return -EINVAL; + + if (!subsystem) + return -EINVAL; + + r = asprintf(&id, "+%s:%s", subsystem, sysname); + if (r < 0) + return -ENOMEM; + } + + device->id_filename = id; + id = NULL; + } + + *ret = device->id_filename; + + return 0; +} + +int device_read_db_aux(sd_device *device, bool force) { + _cleanup_free_ char *db = NULL; + char *path; + const char *id, *value; + char key; + size_t db_len; + unsigned i; + int r; + + enum { + PRE_KEY, + KEY, + PRE_VALUE, + VALUE, + INVALID_LINE, + } state = PRE_KEY; + + if (device->db_loaded || (!force && device->sealed)) + return 0; + + device->db_loaded = true; + + r = device_get_id_filename(device, &id); + if (r < 0) + return r; + + path = strjoina("/run/udev/data/", id); + + r = read_full_file(path, &db, &db_len); + if (r < 0) { + if (r == -ENOENT) + return 0; + else + return log_debug_errno(r, "sd-device: failed to read db '%s': %m", path); + } + + /* devices with a database entry are initialized */ + device->is_initialized = true; + + for (i = 0; i < db_len; i++) { + switch (state) { + case PRE_KEY: + if (!strchr(NEWLINE, db[i])) { + key = db[i]; + + state = KEY; + } + + break; + case KEY: + if (db[i] != ':') { + log_debug("sd-device: ignoring invalid db entry with key '%c'", key); + + state = INVALID_LINE; + } else { + db[i] = '\0'; + + state = PRE_VALUE; + } + + break; + case PRE_VALUE: + value = &db[i]; + + state = VALUE; + + break; + case INVALID_LINE: + if (strchr(NEWLINE, db[i])) + state = PRE_KEY; + + break; + case VALUE: + if (strchr(NEWLINE, db[i])) { + db[i] = '\0'; + r = handle_db_line(device, key, value); + if (r < 0) + log_debug_errno(r, "sd-device: failed to handle db entry '%c:%s': %m", key, value); + + state = PRE_KEY; + } + + break; + default: + assert_not_reached("invalid state when parsing db"); + } + } + + return 0; +} + +static int device_read_db(sd_device *device) { + return device_read_db_aux(device, false); +} + +_public_ int sd_device_get_is_initialized(sd_device *device, int *initialized) { + int r; + + assert_return(device, -EINVAL); + assert_return(initialized, -EINVAL); + + r = device_read_db(device); + if (r < 0) + return r; + + *initialized = device->is_initialized; + + return 0; +} + +_public_ int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *usec) { + usec_t now_ts; + int r; + + assert_return(device, -EINVAL); + assert_return(usec, -EINVAL); + + r = device_read_db(device); + if (r < 0) + return r; + + if (!device->is_initialized) + return -EBUSY; + + if (!device->usec_initialized) + return -ENODATA; + + now_ts = now(clock_boottime_or_monotonic()); + + if (now_ts < device->usec_initialized) + return -EIO; + + *usec = now_ts - device->usec_initialized; + + return 0; +} + +_public_ const char *sd_device_get_tag_first(sd_device *device) { + void *v; + + assert_return(device, NULL); + + (void) device_read_db(device); + + device->tags_iterator_generation = device->tags_generation; + device->tags_iterator = ITERATOR_FIRST; + + set_iterate(device->tags, &device->tags_iterator, &v); + return v; +} + +_public_ const char *sd_device_get_tag_next(sd_device *device) { + void *v; + + assert_return(device, NULL); + + (void) device_read_db(device); + + if (device->tags_iterator_generation != device->tags_generation) + return NULL; + + set_iterate(device->tags, &device->tags_iterator, &v); + return v; +} + +_public_ const char *sd_device_get_devlink_first(sd_device *device) { + void *v; + + assert_return(device, NULL); + + (void) device_read_db(device); + + device->devlinks_iterator_generation = device->devlinks_generation; + device->devlinks_iterator = ITERATOR_FIRST; + + set_iterate(device->devlinks, &device->devlinks_iterator, &v); + return v; +} + +_public_ const char *sd_device_get_devlink_next(sd_device *device) { + void *v; + + assert_return(device, NULL); + + (void) device_read_db(device); + + if (device->devlinks_iterator_generation != device->devlinks_generation) + return NULL; + + set_iterate(device->devlinks, &device->devlinks_iterator, &v); + return v; +} + +static int device_properties_prepare(sd_device *device) { + int r; + + assert(device); + + r = device_read_uevent_file(device); + if (r < 0) + return r; + + r = device_read_db(device); + if (r < 0) + return r; + + if (device->property_devlinks_outdated) { + char *devlinks = NULL; + const char *devlink; + + devlink = sd_device_get_devlink_first(device); + if (devlink) + devlinks = strdupa(devlink); + + while ((devlink = sd_device_get_devlink_next(device))) + devlinks = strjoina(devlinks, " ", devlink); + + r = device_add_property_internal(device, "DEVLINKS", devlinks); + if (r < 0) + return r; + + device->property_devlinks_outdated = false; + } + + if (device->property_tags_outdated) { + char *tags = NULL; + const char *tag; + + tag = sd_device_get_tag_first(device); + if (tag) + tags = strjoina(":", tag); + + while ((tag = sd_device_get_tag_next(device))) + tags = strjoina(tags, ":", tag); + + tags = strjoina(tags, ":"); + + r = device_add_property_internal(device, "TAGS", tags); + if (r < 0) + return r; + + device->property_tags_outdated = false; + } + + return 0; +} + +_public_ const char *sd_device_get_property_first(sd_device *device, const char **_value) { + const char *key; + const char *value; + int r; + + assert_return(device, NULL); + + r = device_properties_prepare(device); + if (r < 0) + return NULL; + + device->properties_iterator_generation = device->properties_generation; + device->properties_iterator = ITERATOR_FIRST; + + ordered_hashmap_iterate(device->properties, &device->properties_iterator, (void**)&value, (const void**)&key); + + if (_value) + *_value = value; + + return key; +} + +_public_ const char *sd_device_get_property_next(sd_device *device, const char **_value) { + const char *key; + const char *value; + int r; + + assert_return(device, NULL); + + r = device_properties_prepare(device); + if (r < 0) + return NULL; + + if (device->properties_iterator_generation != device->properties_generation) + return NULL; + + ordered_hashmap_iterate(device->properties, &device->properties_iterator, (void**)&value, (const void**)&key); + + if (_value) + *_value = value; + + return key; +} + +static int device_sysattrs_read_all(sd_device *device) { + _cleanup_closedir_ DIR *dir = NULL; + const char *syspath; + struct dirent *dent; + int r; + + assert(device); + + if (device->sysattrs_read) + return 0; + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + dir = opendir(syspath); + if (!dir) + return -errno; + + r = set_ensure_allocated(&device->sysattrs, &string_hash_ops); + if (r < 0) + return r; + + for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { + char *path; + struct stat statbuf; + + /* only handle symlinks and regular files */ + if (dent->d_type != DT_LNK && dent->d_type != DT_REG) + continue; + + path = strjoina(syspath, "/", dent->d_name); + + if (lstat(path, &statbuf) != 0) + continue; + + if (!(statbuf.st_mode & S_IRUSR)) + continue; + + r = set_put_strdup(device->sysattrs, dent->d_name); + if (r < 0) + return r; + } + + device->sysattrs_read = true; + + return 0; +} + +_public_ const char *sd_device_get_sysattr_first(sd_device *device) { + void *v; + int r; + + assert_return(device, NULL); + + if (!device->sysattrs_read) { + r = device_sysattrs_read_all(device); + if (r < 0) { + errno = -r; + return NULL; + } + } + + device->sysattrs_iterator = ITERATOR_FIRST; + + set_iterate(device->sysattrs, &device->sysattrs_iterator, &v); + return v; +} + +_public_ const char *sd_device_get_sysattr_next(sd_device *device) { + void *v; + + assert_return(device, NULL); + + if (!device->sysattrs_read) + return NULL; + + set_iterate(device->sysattrs, &device->sysattrs_iterator, &v); + return v; +} + +_public_ int sd_device_has_tag(sd_device *device, const char *tag) { + assert_return(device, -EINVAL); + assert_return(tag, -EINVAL); + + (void) device_read_db(device); + + return !!set_contains(device->tags, tag); +} + +_public_ int sd_device_get_property_value(sd_device *device, const char *key, const char **_value) { + char *value; + int r; + + assert_return(device, -EINVAL); + assert_return(key, -EINVAL); + assert_return(_value, -EINVAL); + + r = device_properties_prepare(device); + if (r < 0) + return r; + + value = ordered_hashmap_get(device->properties, key); + if (!value) + return -ENOENT; + + *_value = value; + + return 0; +} + +/* replaces the value if it already exists */ +static int device_add_sysattr_value(sd_device *device, const char *_key, char *value) { + _cleanup_free_ char *key = NULL; + _cleanup_free_ char *value_old = NULL; + int r; + + assert(device); + assert(_key); + + r = hashmap_ensure_allocated(&device->sysattr_values, &string_hash_ops); + if (r < 0) + return r; + + value_old = hashmap_remove2(device->sysattr_values, _key, (void **)&key); + if (!key) { + key = strdup(_key); + if (!key) + return -ENOMEM; + } + + r = hashmap_put(device->sysattr_values, key, value); + if (r < 0) + return r; + + key = NULL; + + return 0; +} + +static int device_get_sysattr_value(sd_device *device, const char *_key, const char **_value) { + const char *key = NULL, *value; + + assert(device); + assert(_key); + + value = hashmap_get2(device->sysattr_values, _key, (void **) &key); + if (!key) + return -ENOENT; + + if (_value) + *_value = value; + + return 0; +} + +/* We cache all sysattr lookups. If an attribute does not exist, it is stored + * with a NULL value in the cache, otherwise the returned string is stored */ +_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value) { + _cleanup_free_ char *value = NULL; + const char *syspath, *cached_value = NULL; + char *path; + struct stat statbuf; + int r; + + assert_return(device, -EINVAL); + assert_return(sysattr, -EINVAL); + + /* look for possibly already cached result */ + r = device_get_sysattr_value(device, sysattr, &cached_value); + if (r != -ENOENT) { + if (r < 0) + return r; + + if (!cached_value) + /* we looked up the sysattr before and it did not exist */ + return -ENOENT; + + if (_value) + *_value = cached_value; + + return 0; + } + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + path = strjoina(syspath, "/", sysattr); + r = lstat(path, &statbuf); + if (r < 0) { + /* remember that we could not access the sysattr */ + r = device_add_sysattr_value(device, sysattr, NULL); + if (r < 0) + return r; + + return -ENOENT; + } else if (S_ISLNK(statbuf.st_mode)) { + /* Some core links return only the last element of the target path, + * these are just values, the paths should not be exposed. */ + if (STR_IN_SET(sysattr, "driver", "subsystem", "module")) { + r = readlink_value(path, &value); + if (r < 0) + return r; + } else + return -EINVAL; + } else if (S_ISDIR(statbuf.st_mode)) { + /* skip directories */ + return -EINVAL; + } else if (!(statbuf.st_mode & S_IRUSR)) { + /* skip non-readable files */ + return -EPERM; + } else { + size_t size; + + /* read attribute value */ + r = read_full_file(path, &value, &size); + if (r < 0) + return r; + + /* drop trailing newlines */ + while (size > 0 && value[--size] == '\n') + value[size] = '\0'; + } + + r = device_add_sysattr_value(device, sysattr, value); + if (r < 0) + return r; + + *_value = value; + value = NULL; + + return 0; +} + +static void device_remove_sysattr_value(sd_device *device, const char *_key) { + _cleanup_free_ char *key = NULL; + _cleanup_free_ char *value = NULL; + + assert(device); + assert(_key); + + value = hashmap_remove2(device->sysattr_values, _key, (void **) &key); + + return; +} + +/* set the attribute and save it in the cache. If a NULL value is passed the + * attribute is cleared from the cache */ +_public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, char *_value) { + _cleanup_close_ int fd = -1; + _cleanup_free_ char *value = NULL; + const char *syspath; + char *path; + struct stat statbuf; + size_t value_len = 0; + ssize_t size; + int r; + + assert_return(device, -EINVAL); + assert_return(sysattr, -EINVAL); + + if (!_value) { + device_remove_sysattr_value(device, sysattr); + + return 0; + } + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) + return r; + + path = strjoina(syspath, "/", sysattr); + r = lstat(path, &statbuf); + if (r < 0) { + value = strdup(""); + if (!value) + return -ENOMEM; + + r = device_add_sysattr_value(device, sysattr, value); + if (r < 0) + return r; + + return -ENXIO; + } + + if (S_ISLNK(statbuf.st_mode)) + return -EINVAL; + + /* skip directories */ + if (S_ISDIR(statbuf.st_mode)) + return -EISDIR; + + /* skip non-readable files */ + if ((statbuf.st_mode & S_IRUSR) == 0) + return -EACCES; + + value_len = strlen(_value); + + /* drop trailing newlines */ + while (value_len > 0 && _value[value_len - 1] == '\n') + _value[--value_len] = '\0'; + + /* value length is limited to 4k */ + if (value_len > 4096) + return -EINVAL; + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd < 0) + return -errno; + + value = strdup(_value); + if (!value) + return -ENOMEM; + + size = write(fd, value, value_len); + if (size < 0) + return -errno; + + if ((size_t)size != value_len) + return -EIO; + + r = device_add_sysattr_value(device, sysattr, value); + if (r < 0) + return r; + + value = NULL; + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-event/sd-event.c b/src/libsystemd/libsystemd-internal/sd-event/sd-event.c new file mode 100644 index 0000000000..0fec8d71b2 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-event/sd-event.c @@ -0,0 +1,2887 @@ +/*** + 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 . +***/ + +#include +#include +#include + +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "list.h" +#include "macro.h" +#include "missing.h" +#include "prioq.h" +#include "process-util.h" +#include "set.h" +#include "signal-util.h" +#include "string-table.h" +#include "string-util.h" +#include "time-util.h" +#include "util.h" + +#define DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC) + +typedef enum EventSourceType { + SOURCE_IO, + SOURCE_TIME_REALTIME, + SOURCE_TIME_BOOTTIME, + SOURCE_TIME_MONOTONIC, + SOURCE_TIME_REALTIME_ALARM, + SOURCE_TIME_BOOTTIME_ALARM, + SOURCE_SIGNAL, + SOURCE_CHILD, + SOURCE_DEFER, + SOURCE_POST, + SOURCE_EXIT, + SOURCE_WATCHDOG, + _SOURCE_EVENT_SOURCE_TYPE_MAX, + _SOURCE_EVENT_SOURCE_TYPE_INVALID = -1 +} EventSourceType; + +static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = { + [SOURCE_IO] = "io", + [SOURCE_TIME_REALTIME] = "realtime", + [SOURCE_TIME_BOOTTIME] = "bootime", + [SOURCE_TIME_MONOTONIC] = "monotonic", + [SOURCE_TIME_REALTIME_ALARM] = "realtime-alarm", + [SOURCE_TIME_BOOTTIME_ALARM] = "boottime-alarm", + [SOURCE_SIGNAL] = "signal", + [SOURCE_CHILD] = "child", + [SOURCE_DEFER] = "defer", + [SOURCE_POST] = "post", + [SOURCE_EXIT] = "exit", + [SOURCE_WATCHDOG] = "watchdog", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); + +/* All objects we use in epoll events start with this value, so that + * we know how to dispatch it */ +typedef enum WakeupType { + WAKEUP_NONE, + WAKEUP_EVENT_SOURCE, + WAKEUP_CLOCK_DATA, + WAKEUP_SIGNAL_DATA, + _WAKEUP_TYPE_MAX, + _WAKEUP_TYPE_INVALID = -1, +} WakeupType; + +#define EVENT_SOURCE_IS_TIME(t) IN_SET((t), SOURCE_TIME_REALTIME, SOURCE_TIME_BOOTTIME, SOURCE_TIME_MONOTONIC, SOURCE_TIME_REALTIME_ALARM, SOURCE_TIME_BOOTTIME_ALARM) + +struct sd_event_source { + WakeupType wakeup; + + unsigned n_ref; + + sd_event *event; + void *userdata; + sd_event_handler_t prepare; + + char *description; + + EventSourceType type:5; + int enabled:3; + bool pending:1; + bool dispatching:1; + bool floating:1; + + int64_t priority; + unsigned pending_index; + unsigned prepare_index; + unsigned pending_iteration; + unsigned prepare_iteration; + + LIST_FIELDS(sd_event_source, sources); + + union { + struct { + sd_event_io_handler_t callback; + int fd; + uint32_t events; + uint32_t revents; + bool registered:1; + } io; + struct { + sd_event_time_handler_t callback; + usec_t next, accuracy; + unsigned earliest_index; + unsigned latest_index; + } time; + struct { + sd_event_signal_handler_t callback; + struct signalfd_siginfo siginfo; + int sig; + } signal; + struct { + sd_event_child_handler_t callback; + siginfo_t siginfo; + pid_t pid; + int options; + } child; + struct { + sd_event_handler_t callback; + } defer; + struct { + sd_event_handler_t callback; + } post; + struct { + sd_event_handler_t callback; + unsigned prioq_index; + } exit; + }; +}; + +struct clock_data { + WakeupType wakeup; + int fd; + + /* For all clocks we maintain two priority queues each, one + * ordered for the earliest times the events may be + * dispatched, and one ordered by the latest times they must + * have been dispatched. The range between the top entries in + * the two prioqs is the time window we can freely schedule + * wakeups in */ + + Prioq *earliest; + Prioq *latest; + usec_t next; + + bool needs_rearm:1; +}; + +struct signal_data { + WakeupType wakeup; + + /* For each priority we maintain one signal fd, so that we + * only have to dequeue a single event per priority at a + * time. */ + + int fd; + int64_t priority; + sigset_t sigset; + sd_event_source *current; +}; + +struct sd_event { + unsigned n_ref; + + int epoll_fd; + int watchdog_fd; + + Prioq *pending; + Prioq *prepare; + + /* timerfd_create() only supports these five clocks so far. We + * can add support for more clocks when the kernel learns to + * deal with them, too. */ + struct clock_data realtime; + struct clock_data boottime; + struct clock_data monotonic; + struct clock_data realtime_alarm; + struct clock_data boottime_alarm; + + usec_t perturb; + + sd_event_source **signal_sources; /* indexed by signal number */ + Hashmap *signal_data; /* indexed by priority */ + + Hashmap *child_sources; + unsigned n_enabled_child_sources; + + Set *post_sources; + + Prioq *exit; + + pid_t original_pid; + + unsigned iteration; + dual_timestamp timestamp; + usec_t timestamp_boottime; + int state; + + bool exit_requested:1; + bool need_process_child:1; + bool watchdog:1; + bool profile_delays:1; + + int exit_code; + + pid_t tid; + sd_event **default_event_ptr; + + usec_t watchdog_last, watchdog_period; + + unsigned n_sources; + + LIST_HEAD(sd_event_source, sources); + + usec_t last_run, last_log; + unsigned delays[sizeof(usec_t) * 8]; +}; + +static void source_disconnect(sd_event_source *s); + +static int pending_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + assert(x->pending); + assert(y->pending); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Lower priority values first */ + if (x->priority < y->priority) + return -1; + if (x->priority > y->priority) + return 1; + + /* Older entries first */ + if (x->pending_iteration < y->pending_iteration) + return -1; + if (x->pending_iteration > y->pending_iteration) + return 1; + + return 0; +} + +static int prepare_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + assert(x->prepare); + assert(y->prepare); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Move most recently prepared ones last, so that we can stop + * preparing as soon as we hit one that has already been + * prepared in the current iteration */ + if (x->prepare_iteration < y->prepare_iteration) + return -1; + if (x->prepare_iteration > y->prepare_iteration) + return 1; + + /* Lower priority values first */ + if (x->priority < y->priority) + return -1; + if (x->priority > y->priority) + return 1; + + return 0; +} + +static int earliest_time_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + assert(EVENT_SOURCE_IS_TIME(x->type)); + assert(x->type == y->type); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Move the pending ones to the end */ + if (!x->pending && y->pending) + return -1; + if (x->pending && !y->pending) + return 1; + + /* Order by time */ + if (x->time.next < y->time.next) + return -1; + if (x->time.next > y->time.next) + return 1; + + return 0; +} + +static usec_t time_event_source_latest(const sd_event_source *s) { + return usec_add(s->time.next, s->time.accuracy); +} + +static int latest_time_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + assert(EVENT_SOURCE_IS_TIME(x->type)); + assert(x->type == y->type); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Move the pending ones to the end */ + if (!x->pending && y->pending) + return -1; + if (x->pending && !y->pending) + return 1; + + /* Order by time */ + if (time_event_source_latest(x) < time_event_source_latest(y)) + return -1; + if (time_event_source_latest(x) > time_event_source_latest(y)) + return 1; + + return 0; +} + +static int exit_prioq_compare(const void *a, const void *b) { + const sd_event_source *x = a, *y = b; + + assert(x->type == SOURCE_EXIT); + assert(y->type == SOURCE_EXIT); + + /* Enabled ones first */ + if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) + return -1; + if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) + return 1; + + /* Lower priority values first */ + if (x->priority < y->priority) + return -1; + if (x->priority > y->priority) + return 1; + + return 0; +} + +static void free_clock_data(struct clock_data *d) { + assert(d); + assert(d->wakeup == WAKEUP_CLOCK_DATA); + + safe_close(d->fd); + prioq_free(d->earliest); + prioq_free(d->latest); +} + +static void event_free(sd_event *e) { + sd_event_source *s; + + assert(e); + + while ((s = e->sources)) { + assert(s->floating); + source_disconnect(s); + sd_event_source_unref(s); + } + + assert(e->n_sources == 0); + + if (e->default_event_ptr) + *(e->default_event_ptr) = NULL; + + safe_close(e->epoll_fd); + safe_close(e->watchdog_fd); + + free_clock_data(&e->realtime); + free_clock_data(&e->boottime); + free_clock_data(&e->monotonic); + free_clock_data(&e->realtime_alarm); + free_clock_data(&e->boottime_alarm); + + prioq_free(e->pending); + prioq_free(e->prepare); + prioq_free(e->exit); + + free(e->signal_sources); + hashmap_free(e->signal_data); + + hashmap_free(e->child_sources); + set_free(e->post_sources); + free(e); +} + +_public_ int sd_event_new(sd_event** ret) { + sd_event *e; + int r; + + assert_return(ret, -EINVAL); + + e = new0(sd_event, 1); + if (!e) + return -ENOMEM; + + e->n_ref = 1; + e->watchdog_fd = e->epoll_fd = e->realtime.fd = e->boottime.fd = e->monotonic.fd = e->realtime_alarm.fd = e->boottime_alarm.fd = -1; + e->realtime.next = e->boottime.next = e->monotonic.next = e->realtime_alarm.next = e->boottime_alarm.next = USEC_INFINITY; + e->realtime.wakeup = e->boottime.wakeup = e->monotonic.wakeup = e->realtime_alarm.wakeup = e->boottime_alarm.wakeup = WAKEUP_CLOCK_DATA; + e->original_pid = getpid(); + e->perturb = USEC_INFINITY; + + r = prioq_ensure_allocated(&e->pending, pending_prioq_compare); + if (r < 0) + goto fail; + + e->epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (e->epoll_fd < 0) { + r = -errno; + goto fail; + } + + if (secure_getenv("SD_EVENT_PROFILE_DELAYS")) { + log_debug("Event loop profiling enabled. Logarithmic histogram of event loop iterations in the range 2^0 ... 2^63 us will be logged every 5s."); + e->profile_delays = true; + } + + *ret = e; + return 0; + +fail: + event_free(e); + return r; +} + +_public_ sd_event* sd_event_ref(sd_event *e) { + + if (!e) + return NULL; + + assert(e->n_ref >= 1); + e->n_ref++; + + return e; +} + +_public_ sd_event* sd_event_unref(sd_event *e) { + + if (!e) + return NULL; + + assert(e->n_ref >= 1); + e->n_ref--; + + if (e->n_ref <= 0) + event_free(e); + + return NULL; +} + +static bool event_pid_changed(sd_event *e) { + assert(e); + + /* We don't support people creating an event loop and keeping + * it around over a fork(). Let's complain. */ + + return e->original_pid != getpid(); +} + +static void source_io_unregister(sd_event_source *s) { + int r; + + assert(s); + assert(s->type == SOURCE_IO); + + if (event_pid_changed(s->event)) + return; + + if (!s->io.registered) + return; + + r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->io.fd, NULL); + if (r < 0) + log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll: %m", + strna(s->description), event_source_type_to_string(s->type)); + + s->io.registered = false; +} + +static int source_io_register( + sd_event_source *s, + int enabled, + uint32_t events) { + + struct epoll_event ev = {}; + int r; + + assert(s); + assert(s->type == SOURCE_IO); + assert(enabled != SD_EVENT_OFF); + + ev.events = events; + ev.data.ptr = s; + + if (enabled == SD_EVENT_ONESHOT) + ev.events |= EPOLLONESHOT; + + if (s->io.registered) + r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_MOD, s->io.fd, &ev); + else + r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_ADD, s->io.fd, &ev); + if (r < 0) + return -errno; + + s->io.registered = true; + + return 0; +} + +static clockid_t event_source_type_to_clock(EventSourceType t) { + + switch (t) { + + case SOURCE_TIME_REALTIME: + return CLOCK_REALTIME; + + case SOURCE_TIME_BOOTTIME: + return CLOCK_BOOTTIME; + + case SOURCE_TIME_MONOTONIC: + return CLOCK_MONOTONIC; + + case SOURCE_TIME_REALTIME_ALARM: + return CLOCK_REALTIME_ALARM; + + case SOURCE_TIME_BOOTTIME_ALARM: + return CLOCK_BOOTTIME_ALARM; + + default: + return (clockid_t) -1; + } +} + +static EventSourceType clock_to_event_source_type(clockid_t clock) { + + switch (clock) { + + case CLOCK_REALTIME: + return SOURCE_TIME_REALTIME; + + case CLOCK_BOOTTIME: + return SOURCE_TIME_BOOTTIME; + + case CLOCK_MONOTONIC: + return SOURCE_TIME_MONOTONIC; + + case CLOCK_REALTIME_ALARM: + return SOURCE_TIME_REALTIME_ALARM; + + case CLOCK_BOOTTIME_ALARM: + return SOURCE_TIME_BOOTTIME_ALARM; + + default: + return _SOURCE_EVENT_SOURCE_TYPE_INVALID; + } +} + +static struct clock_data* event_get_clock_data(sd_event *e, EventSourceType t) { + assert(e); + + switch (t) { + + case SOURCE_TIME_REALTIME: + return &e->realtime; + + case SOURCE_TIME_BOOTTIME: + return &e->boottime; + + case SOURCE_TIME_MONOTONIC: + return &e->monotonic; + + case SOURCE_TIME_REALTIME_ALARM: + return &e->realtime_alarm; + + case SOURCE_TIME_BOOTTIME_ALARM: + return &e->boottime_alarm; + + default: + return NULL; + } +} + +static int event_make_signal_data( + sd_event *e, + int sig, + struct signal_data **ret) { + + struct epoll_event ev = {}; + struct signal_data *d; + bool added = false; + sigset_t ss_copy; + int64_t priority; + int r; + + assert(e); + + if (event_pid_changed(e)) + return -ECHILD; + + if (e->signal_sources && e->signal_sources[sig]) + priority = e->signal_sources[sig]->priority; + else + priority = 0; + + d = hashmap_get(e->signal_data, &priority); + if (d) { + if (sigismember(&d->sigset, sig) > 0) { + if (ret) + *ret = d; + return 0; + } + } else { + r = hashmap_ensure_allocated(&e->signal_data, &uint64_hash_ops); + if (r < 0) + return r; + + d = new0(struct signal_data, 1); + if (!d) + return -ENOMEM; + + d->wakeup = WAKEUP_SIGNAL_DATA; + d->fd = -1; + d->priority = priority; + + r = hashmap_put(e->signal_data, &d->priority, d); + if (r < 0) { + free(d); + return r; + } + + added = true; + } + + ss_copy = d->sigset; + assert_se(sigaddset(&ss_copy, sig) >= 0); + + r = signalfd(d->fd, &ss_copy, SFD_NONBLOCK|SFD_CLOEXEC); + if (r < 0) { + r = -errno; + goto fail; + } + + d->sigset = ss_copy; + + if (d->fd >= 0) { + if (ret) + *ret = d; + return 0; + } + + d->fd = r; + + ev.events = EPOLLIN; + ev.data.ptr = d; + + r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev); + if (r < 0) { + r = -errno; + goto fail; + } + + if (ret) + *ret = d; + + return 0; + +fail: + if (added) { + d->fd = safe_close(d->fd); + hashmap_remove(e->signal_data, &d->priority); + free(d); + } + + return r; +} + +static void event_unmask_signal_data(sd_event *e, struct signal_data *d, int sig) { + assert(e); + assert(d); + + /* Turns off the specified signal in the signal data + * object. If the signal mask of the object becomes empty that + * way removes it. */ + + if (sigismember(&d->sigset, sig) == 0) + return; + + assert_se(sigdelset(&d->sigset, sig) >= 0); + + if (sigisemptyset(&d->sigset)) { + + /* If all the mask is all-zero we can get rid of the structure */ + hashmap_remove(e->signal_data, &d->priority); + assert(!d->current); + safe_close(d->fd); + free(d); + return; + } + + assert(d->fd >= 0); + + if (signalfd(d->fd, &d->sigset, SFD_NONBLOCK|SFD_CLOEXEC) < 0) + log_debug_errno(errno, "Failed to unset signal bit, ignoring: %m"); +} + +static void event_gc_signal_data(sd_event *e, const int64_t *priority, int sig) { + struct signal_data *d; + static const int64_t zero_priority = 0; + + assert(e); + + /* Rechecks if the specified signal is still something we are + * interested in. If not, we'll unmask it, and possibly drop + * the signalfd for it. */ + + if (sig == SIGCHLD && + e->n_enabled_child_sources > 0) + return; + + if (e->signal_sources && + e->signal_sources[sig] && + e->signal_sources[sig]->enabled != SD_EVENT_OFF) + return; + + /* + * The specified signal might be enabled in three different queues: + * + * 1) the one that belongs to the priority passed (if it is non-NULL) + * 2) the one that belongs to the priority of the event source of the signal (if there is one) + * 3) the 0 priority (to cover the SIGCHLD case) + * + * Hence, let's remove it from all three here. + */ + + if (priority) { + d = hashmap_get(e->signal_data, priority); + if (d) + event_unmask_signal_data(e, d, sig); + } + + if (e->signal_sources && e->signal_sources[sig]) { + d = hashmap_get(e->signal_data, &e->signal_sources[sig]->priority); + if (d) + event_unmask_signal_data(e, d, sig); + } + + d = hashmap_get(e->signal_data, &zero_priority); + if (d) + event_unmask_signal_data(e, d, sig); +} + +static void source_disconnect(sd_event_source *s) { + sd_event *event; + + assert(s); + + if (!s->event) + return; + + assert(s->event->n_sources > 0); + + switch (s->type) { + + case SOURCE_IO: + if (s->io.fd >= 0) + source_io_unregister(s); + + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: { + struct clock_data *d; + + d = event_get_clock_data(s->event, s->type); + assert(d); + + prioq_remove(d->earliest, s, &s->time.earliest_index); + prioq_remove(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + break; + } + + case SOURCE_SIGNAL: + if (s->signal.sig > 0) { + + if (s->event->signal_sources) + s->event->signal_sources[s->signal.sig] = NULL; + + event_gc_signal_data(s->event, &s->priority, s->signal.sig); + } + + break; + + case SOURCE_CHILD: + if (s->child.pid > 0) { + if (s->enabled != SD_EVENT_OFF) { + assert(s->event->n_enabled_child_sources > 0); + s->event->n_enabled_child_sources--; + } + + (void) hashmap_remove(s->event->child_sources, PID_TO_PTR(s->child.pid)); + event_gc_signal_data(s->event, &s->priority, SIGCHLD); + } + + break; + + case SOURCE_DEFER: + /* nothing */ + break; + + case SOURCE_POST: + set_remove(s->event->post_sources, s); + break; + + case SOURCE_EXIT: + prioq_remove(s->event->exit, s, &s->exit.prioq_index); + break; + + default: + assert_not_reached("Wut? I shouldn't exist."); + } + + if (s->pending) + prioq_remove(s->event->pending, s, &s->pending_index); + + if (s->prepare) + prioq_remove(s->event->prepare, s, &s->prepare_index); + + event = s->event; + + s->type = _SOURCE_EVENT_SOURCE_TYPE_INVALID; + s->event = NULL; + LIST_REMOVE(sources, event->sources, s); + event->n_sources--; + + if (!s->floating) + sd_event_unref(event); +} + +static void source_free(sd_event_source *s) { + assert(s); + + source_disconnect(s); + free(s->description); + free(s); +} + +static int source_set_pending(sd_event_source *s, bool b) { + int r; + + assert(s); + assert(s->type != SOURCE_EXIT); + + if (s->pending == b) + return 0; + + s->pending = b; + + if (b) { + s->pending_iteration = s->event->iteration; + + r = prioq_put(s->event->pending, s, &s->pending_index); + if (r < 0) { + s->pending = false; + return r; + } + } else + assert_se(prioq_remove(s->event->pending, s, &s->pending_index)); + + if (EVENT_SOURCE_IS_TIME(s->type)) { + struct clock_data *d; + + d = event_get_clock_data(s->event, s->type); + assert(d); + + prioq_reshuffle(d->earliest, s, &s->time.earliest_index); + prioq_reshuffle(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + } + + if (s->type == SOURCE_SIGNAL && !b) { + struct signal_data *d; + + d = hashmap_get(s->event->signal_data, &s->priority); + if (d && d->current == s) + d->current = NULL; + } + + return 0; +} + +static sd_event_source *source_new(sd_event *e, bool floating, EventSourceType type) { + sd_event_source *s; + + assert(e); + + s = new0(sd_event_source, 1); + if (!s) + return NULL; + + s->n_ref = 1; + s->event = e; + s->floating = floating; + s->type = type; + s->pending_index = s->prepare_index = PRIOQ_IDX_NULL; + + if (!floating) + sd_event_ref(e); + + LIST_PREPEND(sources, e->sources, s); + e->n_sources ++; + + return s; +} + +_public_ int sd_event_add_io( + sd_event *e, + sd_event_source **ret, + int fd, + uint32_t events, + sd_event_io_handler_t callback, + void *userdata) { + + sd_event_source *s; + int r; + + assert_return(e, -EINVAL); + assert_return(fd >= 0, -EBADF); + assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL); + assert_return(callback, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + s = source_new(e, !ret, SOURCE_IO); + if (!s) + return -ENOMEM; + + s->wakeup = WAKEUP_EVENT_SOURCE; + s->io.fd = fd; + s->io.events = events; + s->io.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ON; + + r = source_io_register(s, s->enabled, events); + if (r < 0) { + source_free(s); + return r; + } + + if (ret) + *ret = s; + + return 0; +} + +static void initialize_perturb(sd_event *e) { + sd_id128_t bootid = {}; + + /* When we sleep for longer, we try to realign the wakeup to + the same time wihtin each minute/second/250ms, so that + events all across the system can be coalesced into a single + CPU wakeup. However, let's take some system-specific + randomness for this value, so that in a network of systems + with synced clocks timer events are distributed a + bit. Here, we calculate a perturbation usec offset from the + boot ID. */ + + if (_likely_(e->perturb != USEC_INFINITY)) + return; + + if (sd_id128_get_boot(&bootid) >= 0) + e->perturb = (bootid.qwords[0] ^ bootid.qwords[1]) % USEC_PER_MINUTE; +} + +static int event_setup_timer_fd( + sd_event *e, + struct clock_data *d, + clockid_t clock) { + + struct epoll_event ev = {}; + int r, fd; + + assert(e); + assert(d); + + if (_likely_(d->fd >= 0)) + return 0; + + fd = timerfd_create(clock, TFD_NONBLOCK|TFD_CLOEXEC); + if (fd < 0) + return -errno; + + ev.events = EPOLLIN; + ev.data.ptr = d; + + r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, fd, &ev); + if (r < 0) { + safe_close(fd); + return -errno; + } + + d->fd = fd; + return 0; +} + +static int time_exit_callback(sd_event_source *s, uint64_t usec, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +_public_ int sd_event_add_time( + sd_event *e, + sd_event_source **ret, + clockid_t clock, + uint64_t usec, + uint64_t accuracy, + sd_event_time_handler_t callback, + void *userdata) { + + EventSourceType type; + sd_event_source *s; + struct clock_data *d; + int r; + + assert_return(e, -EINVAL); + assert_return(accuracy != (uint64_t) -1, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + 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); + + r = prioq_ensure_allocated(&d->earliest, earliest_time_prioq_compare); + if (r < 0) + return r; + + r = prioq_ensure_allocated(&d->latest, latest_time_prioq_compare); + if (r < 0) + return r; + + if (d->fd < 0) { + r = event_setup_timer_fd(e, d, clock); + if (r < 0) + return r; + } + + s = source_new(e, !ret, type); + if (!s) + return -ENOMEM; + + s->time.next = usec; + s->time.accuracy = accuracy == 0 ? DEFAULT_ACCURACY_USEC : accuracy; + s->time.callback = callback; + s->time.earliest_index = s->time.latest_index = PRIOQ_IDX_NULL; + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + d->needs_rearm = true; + + r = prioq_put(d->earliest, s, &s->time.earliest_index); + if (r < 0) + goto fail; + + r = prioq_put(d->latest, s, &s->time.latest_index); + if (r < 0) + goto fail; + + if (ret) + *ret = s; + + return 0; + +fail: + source_free(s); + return r; +} + +static int signal_exit_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + assert(s); + + return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); +} + +_public_ int sd_event_add_signal( + sd_event *e, + sd_event_source **ret, + int sig, + sd_event_signal_handler_t callback, + void *userdata) { + + sd_event_source *s; + struct signal_data *d; + sigset_t ss; + int r; + + assert_return(e, -EINVAL); + assert_return(sig > 0, -EINVAL); + assert_return(sig < _NSIG, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!callback) + callback = signal_exit_callback; + + r = pthread_sigmask(SIG_SETMASK, NULL, &ss); + if (r != 0) + return -r; + + if (!sigismember(&ss, sig)) + return -EBUSY; + + if (!e->signal_sources) { + e->signal_sources = new0(sd_event_source*, _NSIG); + if (!e->signal_sources) + return -ENOMEM; + } else if (e->signal_sources[sig]) + return -EBUSY; + + s = source_new(e, !ret, SOURCE_SIGNAL); + if (!s) + return -ENOMEM; + + s->signal.sig = sig; + s->signal.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ON; + + e->signal_sources[sig] = s; + + r = event_make_signal_data(e, sig, &d); + if (r < 0) { + source_free(s); + return r; + } + + /* Use the signal name as description for the event source by default */ + (void) sd_event_source_set_description(s, signal_to_string(sig)); + + if (ret) + *ret = s; + + return 0; +} + +_public_ int sd_event_add_child( + sd_event *e, + sd_event_source **ret, + pid_t pid, + int options, + sd_event_child_handler_t callback, + void *userdata) { + + sd_event_source *s; + int r; + + assert_return(e, -EINVAL); + assert_return(pid > 1, -EINVAL); + assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL); + assert_return(options != 0, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + r = hashmap_ensure_allocated(&e->child_sources, NULL); + if (r < 0) + return r; + + if (hashmap_contains(e->child_sources, PID_TO_PTR(pid))) + return -EBUSY; + + s = source_new(e, !ret, SOURCE_CHILD); + if (!s) + return -ENOMEM; + + s->child.pid = pid; + s->child.options = options; + s->child.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s); + if (r < 0) { + source_free(s); + return r; + } + + e->n_enabled_child_sources ++; + + r = event_make_signal_data(e, SIGCHLD, NULL); + if (r < 0) { + e->n_enabled_child_sources--; + source_free(s); + return r; + } + + e->need_process_child = true; + + if (ret) + *ret = s; + + return 0; +} + +_public_ int sd_event_add_defer( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + sd_event_source *s; + int r; + + assert_return(e, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + s = source_new(e, !ret, SOURCE_DEFER); + if (!s) + return -ENOMEM; + + s->defer.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ONESHOT; + + r = source_set_pending(s, true); + if (r < 0) { + source_free(s); + return r; + } + + if (ret) + *ret = s; + + return 0; +} + +_public_ int sd_event_add_post( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + sd_event_source *s; + int r; + + assert_return(e, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + r = set_ensure_allocated(&e->post_sources, NULL); + if (r < 0) + return r; + + s = source_new(e, !ret, SOURCE_POST); + if (!s) + return -ENOMEM; + + s->post.callback = callback; + s->userdata = userdata; + s->enabled = SD_EVENT_ON; + + r = set_put(e->post_sources, s); + if (r < 0) { + source_free(s); + return r; + } + + if (ret) + *ret = s; + + return 0; +} + +_public_ int sd_event_add_exit( + sd_event *e, + sd_event_source **ret, + sd_event_handler_t callback, + void *userdata) { + + sd_event_source *s; + int r; + + assert_return(e, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + r = prioq_ensure_allocated(&e->exit, exit_prioq_compare); + if (r < 0) + return r; + + s = source_new(e, !ret, SOURCE_EXIT); + if (!s) + return -ENOMEM; + + s->exit.callback = callback; + s->userdata = userdata; + s->exit.prioq_index = PRIOQ_IDX_NULL; + s->enabled = SD_EVENT_ONESHOT; + + r = prioq_put(s->event->exit, s, &s->exit.prioq_index); + if (r < 0) { + source_free(s); + return r; + } + + if (ret) + *ret = s; + + return 0; +} + +_public_ sd_event_source* sd_event_source_ref(sd_event_source *s) { + + if (!s) + return NULL; + + assert(s->n_ref >= 1); + s->n_ref++; + + return s; +} + +_public_ sd_event_source* sd_event_source_unref(sd_event_source *s) { + + if (!s) + return NULL; + + assert(s->n_ref >= 1); + s->n_ref--; + + if (s->n_ref <= 0) { + /* Here's a special hack: when we are called from a + * dispatch handler we won't free the event source + * immediately, but we will detach the fd from the + * epoll. This way it is safe for the caller to unref + * the event source and immediately close the fd, but + * we still retain a valid event source object after + * the callback. */ + + if (s->dispatching) { + if (s->type == SOURCE_IO) + source_io_unregister(s); + + source_disconnect(s); + } else + source_free(s); + } + + return NULL; +} + +_public_ int sd_event_source_set_description(sd_event_source *s, const char *description) { + assert_return(s, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return free_and_strdup(&s->description, description); +} + +_public_ int sd_event_source_get_description(sd_event_source *s, const char **description) { + assert_return(s, -EINVAL); + assert_return(description, -EINVAL); + assert_return(s->description, -ENXIO); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *description = s->description; + return 0; +} + +_public_ sd_event *sd_event_source_get_event(sd_event_source *s) { + assert_return(s, NULL); + + return s->event; +} + +_public_ int sd_event_source_get_pending(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type != SOURCE_EXIT, -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->pending; +} + +_public_ int sd_event_source_get_io_fd(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->io.fd; +} + +_public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) { + int r; + + assert_return(s, -EINVAL); + assert_return(fd >= 0, -EBADF); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->io.fd == fd) + return 0; + + if (s->enabled == SD_EVENT_OFF) { + s->io.fd = fd; + s->io.registered = false; + } else { + int saved_fd; + + saved_fd = s->io.fd; + assert(s->io.registered); + + s->io.fd = fd; + s->io.registered = false; + + r = source_io_register(s, s->enabled, s->io.events); + if (r < 0) { + s->io.fd = saved_fd; + s->io.registered = true; + return r; + } + + epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, saved_fd, NULL); + } + + return 0; +} + +_public_ int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events) { + assert_return(s, -EINVAL); + assert_return(events, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *events = s->io.events; + return 0; +} + +_public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events) { + int r; + + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + /* edge-triggered updates are never skipped, so we can reset edges */ + if (s->io.events == events && !(events & EPOLLET)) + return 0; + + if (s->enabled != SD_EVENT_OFF) { + r = source_io_register(s, s->enabled, events); + if (r < 0) + return r; + } + + s->io.events = events; + source_set_pending(s, false); + + return 0; +} + +_public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents) { + assert_return(s, -EINVAL); + assert_return(revents, -EINVAL); + assert_return(s->type == SOURCE_IO, -EDOM); + assert_return(s->pending, -ENODATA); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *revents = s->io.revents; + return 0; +} + +_public_ int sd_event_source_get_signal(sd_event_source *s) { + assert_return(s, -EINVAL); + assert_return(s->type == SOURCE_SIGNAL, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->signal.sig; +} + +_public_ int sd_event_source_get_priority(sd_event_source *s, int64_t *priority) { + assert_return(s, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + return s->priority; +} + +_public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority) { + int r; + + assert_return(s, -EINVAL); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->priority == priority) + return 0; + + if (s->type == SOURCE_SIGNAL && s->enabled != SD_EVENT_OFF) { + struct signal_data *old, *d; + + /* Move us from the signalfd belonging to the old + * priority to the signalfd of the new priority */ + + assert_se(old = hashmap_get(s->event->signal_data, &s->priority)); + + s->priority = priority; + + r = event_make_signal_data(s->event, s->signal.sig, &d); + if (r < 0) { + s->priority = old->priority; + return r; + } + + event_unmask_signal_data(s->event, old, s->signal.sig); + } else + s->priority = priority; + + if (s->pending) + prioq_reshuffle(s->event->pending, s, &s->pending_index); + + if (s->prepare) + prioq_reshuffle(s->event->prepare, s, &s->prepare_index); + + if (s->type == SOURCE_EXIT) + prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); + + return 0; +} + +_public_ int sd_event_source_get_enabled(sd_event_source *s, int *m) { + assert_return(s, -EINVAL); + assert_return(m, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *m = s->enabled; + return 0; +} + +_public_ int sd_event_source_set_enabled(sd_event_source *s, int m) { + int r; + + assert_return(s, -EINVAL); + assert_return(m == SD_EVENT_OFF || m == SD_EVENT_ON || m == SD_EVENT_ONESHOT, -EINVAL); + assert_return(!event_pid_changed(s->event), -ECHILD); + + /* If we are dead anyway, we are fine with turning off + * sources, but everything else needs to fail. */ + if (s->event->state == SD_EVENT_FINISHED) + return m == SD_EVENT_OFF ? 0 : -ESTALE; + + if (s->enabled == m) + return 0; + + if (m == SD_EVENT_OFF) { + + switch (s->type) { + + case SOURCE_IO: + source_io_unregister(s); + s->enabled = m; + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: { + struct clock_data *d; + + s->enabled = m; + d = event_get_clock_data(s->event, s->type); + assert(d); + + prioq_reshuffle(d->earliest, s, &s->time.earliest_index); + prioq_reshuffle(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + break; + } + + case SOURCE_SIGNAL: + s->enabled = m; + + event_gc_signal_data(s->event, &s->priority, s->signal.sig); + break; + + case SOURCE_CHILD: + s->enabled = m; + + assert(s->event->n_enabled_child_sources > 0); + s->event->n_enabled_child_sources--; + + event_gc_signal_data(s->event, &s->priority, SIGCHLD); + break; + + case SOURCE_EXIT: + s->enabled = m; + prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); + break; + + case SOURCE_DEFER: + case SOURCE_POST: + s->enabled = m; + break; + + default: + assert_not_reached("Wut? I shouldn't exist."); + } + + } else { + switch (s->type) { + + case SOURCE_IO: + r = source_io_register(s, m, s->io.events); + if (r < 0) + return r; + + s->enabled = m; + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: { + struct clock_data *d; + + s->enabled = m; + d = event_get_clock_data(s->event, s->type); + assert(d); + + prioq_reshuffle(d->earliest, s, &s->time.earliest_index); + prioq_reshuffle(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + break; + } + + case SOURCE_SIGNAL: + + s->enabled = m; + + r = event_make_signal_data(s->event, s->signal.sig, NULL); + if (r < 0) { + s->enabled = SD_EVENT_OFF; + event_gc_signal_data(s->event, &s->priority, s->signal.sig); + return r; + } + + break; + + case SOURCE_CHILD: + + if (s->enabled == SD_EVENT_OFF) + s->event->n_enabled_child_sources++; + + s->enabled = m; + + r = event_make_signal_data(s->event, SIGCHLD, NULL); + if (r < 0) { + s->enabled = SD_EVENT_OFF; + s->event->n_enabled_child_sources--; + event_gc_signal_data(s->event, &s->priority, SIGCHLD); + return r; + } + + break; + + case SOURCE_EXIT: + s->enabled = m; + prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); + break; + + case SOURCE_DEFER: + case SOURCE_POST: + s->enabled = m; + break; + + default: + assert_not_reached("Wut? I shouldn't exist."); + } + } + + if (s->pending) + prioq_reshuffle(s->event->pending, s, &s->pending_index); + + if (s->prepare) + prioq_reshuffle(s->event->prepare, s, &s->prepare_index); + + return 0; +} + +_public_ int sd_event_source_get_time(sd_event_source *s, uint64_t *usec) { + assert_return(s, -EINVAL); + assert_return(usec, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *usec = s->time.next; + return 0; +} + +_public_ int sd_event_source_set_time(sd_event_source *s, uint64_t usec) { + struct clock_data *d; + + assert_return(s, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + s->time.next = usec; + + source_set_pending(s, false); + + d = event_get_clock_data(s->event, s->type); + assert(d); + + prioq_reshuffle(d->earliest, s, &s->time.earliest_index); + prioq_reshuffle(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + + return 0; +} + +_public_ int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec) { + assert_return(s, -EINVAL); + assert_return(usec, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *usec = s->time.accuracy; + return 0; +} + +_public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec) { + struct clock_data *d; + + assert_return(s, -EINVAL); + assert_return(usec != (uint64_t) -1, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (usec == 0) + usec = DEFAULT_ACCURACY_USEC; + + s->time.accuracy = usec; + + source_set_pending(s, false); + + d = event_get_clock_data(s->event, s->type); + assert(d); + + prioq_reshuffle(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + + return 0; +} + +_public_ int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock) { + assert_return(s, -EINVAL); + assert_return(clock, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *clock = event_source_type_to_clock(s->type); + return 0; +} + +_public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) { + assert_return(s, -EINVAL); + assert_return(pid, -EINVAL); + assert_return(s->type == SOURCE_CHILD, -EDOM); + assert_return(!event_pid_changed(s->event), -ECHILD); + + *pid = s->child.pid; + return 0; +} + +_public_ int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback) { + int r; + + assert_return(s, -EINVAL); + assert_return(s->type != SOURCE_EXIT, -EDOM); + assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(s->event), -ECHILD); + + if (s->prepare == callback) + return 0; + + if (callback && s->prepare) { + s->prepare = callback; + return 0; + } + + r = prioq_ensure_allocated(&s->event->prepare, prepare_prioq_compare); + if (r < 0) + return r; + + s->prepare = callback; + + if (callback) { + r = prioq_put(s->event->prepare, s, &s->prepare_index); + if (r < 0) + return r; + } else + prioq_remove(s->event->prepare, s, &s->prepare_index); + + return 0; +} + +_public_ void* sd_event_source_get_userdata(sd_event_source *s) { + assert_return(s, NULL); + + return s->userdata; +} + +_public_ void *sd_event_source_set_userdata(sd_event_source *s, void *userdata) { + void *ret; + + assert_return(s, NULL); + + ret = s->userdata; + s->userdata = userdata; + + return ret; +} + +static usec_t sleep_between(sd_event *e, usec_t a, usec_t b) { + usec_t c; + assert(e); + assert(a <= b); + + if (a <= 0) + return 0; + if (a >= USEC_INFINITY) + return USEC_INFINITY; + + if (b <= a + 1) + return a; + + initialize_perturb(e); + + /* + Find a good time to wake up again between times a and b. We + have two goals here: + + a) We want to wake up as seldom as possible, hence prefer + later times over earlier times. + + b) But if we have to wake up, then let's make sure to + dispatch as much as possible on the entire system. + + We implement this by waking up everywhere at the same time + within any given minute if we can, synchronised via the + perturbation value determined from the boot ID. If we can't, + then we try to find the same spot in every 10s, then 1s and + then 250ms step. Otherwise, we pick the last possible time + to wake up. + */ + + c = (b / USEC_PER_MINUTE) * USEC_PER_MINUTE + e->perturb; + if (c >= b) { + if (_unlikely_(c < USEC_PER_MINUTE)) + return b; + + c -= USEC_PER_MINUTE; + } + + if (c >= a) + return c; + + c = (b / (USEC_PER_SEC*10)) * (USEC_PER_SEC*10) + (e->perturb % (USEC_PER_SEC*10)); + if (c >= b) { + if (_unlikely_(c < USEC_PER_SEC*10)) + return b; + + c -= USEC_PER_SEC*10; + } + + if (c >= a) + return c; + + c = (b / USEC_PER_SEC) * USEC_PER_SEC + (e->perturb % USEC_PER_SEC); + if (c >= b) { + if (_unlikely_(c < USEC_PER_SEC)) + return b; + + c -= USEC_PER_SEC; + } + + if (c >= a) + return c; + + c = (b / (USEC_PER_MSEC*250)) * (USEC_PER_MSEC*250) + (e->perturb % (USEC_PER_MSEC*250)); + if (c >= b) { + if (_unlikely_(c < USEC_PER_MSEC*250)) + return b; + + c -= USEC_PER_MSEC*250; + } + + if (c >= a) + return c; + + return b; +} + +static int event_arm_timer( + sd_event *e, + struct clock_data *d) { + + struct itimerspec its = {}; + sd_event_source *a, *b; + usec_t t; + int r; + + assert(e); + assert(d); + + if (!d->needs_rearm) + return 0; + else + d->needs_rearm = false; + + a = prioq_peek(d->earliest); + if (!a || a->enabled == SD_EVENT_OFF || a->time.next == USEC_INFINITY) { + + if (d->fd < 0) + return 0; + + if (d->next == USEC_INFINITY) + return 0; + + /* disarm */ + r = timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL); + if (r < 0) + return r; + + d->next = USEC_INFINITY; + return 0; + } + + b = prioq_peek(d->latest); + assert_se(b && b->enabled != SD_EVENT_OFF); + + t = sleep_between(e, a->time.next, time_event_source_latest(b)); + if (d->next == t) + return 0; + + assert_se(d->fd >= 0); + + if (t == 0) { + /* We don' want to disarm here, just mean some time looooong ago. */ + its.it_value.tv_sec = 0; + its.it_value.tv_nsec = 1; + } else + timespec_store(&its.it_value, t); + + r = timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL); + if (r < 0) + return -errno; + + d->next = t; + return 0; +} + +static int process_io(sd_event *e, sd_event_source *s, uint32_t revents) { + assert(e); + assert(s); + assert(s->type == SOURCE_IO); + + /* If the event source was already pending, we just OR in the + * new revents, otherwise we reset the value. The ORing is + * necessary to handle EPOLLONESHOT events properly where + * readability might happen independently of writability, and + * we need to keep track of both */ + + if (s->pending) + s->io.revents |= revents; + else + s->io.revents = revents; + + return source_set_pending(s, true); +} + +static int flush_timer(sd_event *e, int fd, uint32_t events, usec_t *next) { + uint64_t x; + ssize_t ss; + + assert(e); + assert(fd >= 0); + + assert_return(events == EPOLLIN, -EIO); + + ss = read(fd, &x, sizeof(x)); + if (ss < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + if (_unlikely_(ss != sizeof(x))) + return -EIO; + + if (next) + *next = USEC_INFINITY; + + return 0; +} + +static int process_timer( + sd_event *e, + usec_t n, + struct clock_data *d) { + + sd_event_source *s; + int r; + + assert(e); + assert(d); + + for (;;) { + s = prioq_peek(d->earliest); + if (!s || + s->time.next > n || + s->enabled == SD_EVENT_OFF || + s->pending) + break; + + r = source_set_pending(s, true); + if (r < 0) + return r; + + prioq_reshuffle(d->earliest, s, &s->time.earliest_index); + prioq_reshuffle(d->latest, s, &s->time.latest_index); + d->needs_rearm = true; + } + + return 0; +} + +static int process_child(sd_event *e) { + sd_event_source *s; + Iterator i; + int r; + + assert(e); + + e->need_process_child = false; + + /* + So, this is ugly. We iteratively invoke waitid() with P_PID + + WNOHANG for each PID we wait for, instead of using + P_ALL. This is because we only want to get child + information of very specific child processes, and not all + of them. We might not have processed the SIGCHLD even of a + previous invocation and we don't want to maintain a + unbounded *per-child* event queue, hence we really don't + want anything flushed out of the kernel's queue that we + don't care about. Since this is O(n) this means that if you + have a lot of processes you probably want to handle SIGCHLD + yourself. + + We do not reap the children here (by using WNOWAIT), this + is only done after the event source is dispatched so that + the callback still sees the process as a zombie. + */ + + HASHMAP_FOREACH(s, e->child_sources, i) { + assert(s->type == SOURCE_CHILD); + + if (s->pending) + continue; + + if (s->enabled == SD_EVENT_OFF) + continue; + + zero(s->child.siginfo); + r = waitid(P_PID, s->child.pid, &s->child.siginfo, + WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | s->child.options); + if (r < 0) + return -errno; + + if (s->child.siginfo.si_pid != 0) { + bool zombie = + s->child.siginfo.si_code == CLD_EXITED || + s->child.siginfo.si_code == CLD_KILLED || + s->child.siginfo.si_code == CLD_DUMPED; + + if (!zombie && (s->child.options & WEXITED)) { + /* If the child isn't dead then let's + * immediately remove the state change + * from the queue, since there's no + * benefit in leaving it queued */ + + assert(s->child.options & (WSTOPPED|WCONTINUED)); + waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|(s->child.options & (WSTOPPED|WCONTINUED))); + } + + r = source_set_pending(s, true); + if (r < 0) + return r; + } + } + + return 0; +} + +static int process_signal(sd_event *e, struct signal_data *d, uint32_t events) { + bool read_one = false; + int r; + + assert(e); + assert_return(events == EPOLLIN, -EIO); + + /* If there's a signal queued on this priority and SIGCHLD is + on this priority too, then make sure to recheck the + children we watch. This is because we only ever dequeue + the first signal per priority, and if we dequeue one, and + SIGCHLD might be enqueued later we wouldn't know, but we + might have higher priority children we care about hence we + need to check that explicitly. */ + + if (sigismember(&d->sigset, SIGCHLD)) + e->need_process_child = true; + + /* If there's already an event source pending for this + * priority we don't read another */ + if (d->current) + return 0; + + for (;;) { + struct signalfd_siginfo si; + ssize_t n; + sd_event_source *s = NULL; + + n = read(d->fd, &si, sizeof(si)); + if (n < 0) { + if (errno == EAGAIN || errno == EINTR) + return read_one; + + return -errno; + } + + if (_unlikely_(n != sizeof(si))) + return -EIO; + + assert(si.ssi_signo < _NSIG); + + read_one = true; + + if (e->signal_sources) + s = e->signal_sources[si.ssi_signo]; + if (!s) + continue; + if (s->pending) + continue; + + s->signal.siginfo = si; + d->current = s; + + r = source_set_pending(s, true); + if (r < 0) + return r; + + return 1; + } +} + +static int source_dispatch(sd_event_source *s) { + int r = 0; + + assert(s); + assert(s->pending || s->type == SOURCE_EXIT); + + if (s->type != SOURCE_DEFER && s->type != SOURCE_EXIT) { + r = source_set_pending(s, false); + if (r < 0) + return r; + } + + if (s->type != SOURCE_POST) { + sd_event_source *z; + Iterator i; + + /* If we execute a non-post source, let's mark all + * post sources as pending */ + + SET_FOREACH(z, s->event->post_sources, i) { + if (z->enabled == SD_EVENT_OFF) + continue; + + r = source_set_pending(z, true); + if (r < 0) + return r; + } + } + + if (s->enabled == SD_EVENT_ONESHOT) { + r = sd_event_source_set_enabled(s, SD_EVENT_OFF); + if (r < 0) + return r; + } + + s->dispatching = true; + + switch (s->type) { + + case SOURCE_IO: + r = s->io.callback(s, s->io.fd, s->io.revents, s->userdata); + break; + + case SOURCE_TIME_REALTIME: + case SOURCE_TIME_BOOTTIME: + case SOURCE_TIME_MONOTONIC: + case SOURCE_TIME_REALTIME_ALARM: + case SOURCE_TIME_BOOTTIME_ALARM: + r = s->time.callback(s, s->time.next, s->userdata); + break; + + case SOURCE_SIGNAL: + r = s->signal.callback(s, &s->signal.siginfo, s->userdata); + break; + + case SOURCE_CHILD: { + bool zombie; + + zombie = s->child.siginfo.si_code == CLD_EXITED || + s->child.siginfo.si_code == CLD_KILLED || + s->child.siginfo.si_code == CLD_DUMPED; + + r = s->child.callback(s, &s->child.siginfo, s->userdata); + + /* Now, reap the PID for good. */ + if (zombie) + waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|WEXITED); + + break; + } + + case SOURCE_DEFER: + r = s->defer.callback(s, s->userdata); + break; + + case SOURCE_POST: + r = s->post.callback(s, s->userdata); + break; + + case SOURCE_EXIT: + r = s->exit.callback(s, s->userdata); + break; + + case SOURCE_WATCHDOG: + case _SOURCE_EVENT_SOURCE_TYPE_MAX: + case _SOURCE_EVENT_SOURCE_TYPE_INVALID: + assert_not_reached("Wut? I shouldn't exist."); + } + + s->dispatching = false; + + if (r < 0) + log_debug_errno(r, "Event source %s (type %s) returned error, disabling: %m", + strna(s->description), event_source_type_to_string(s->type)); + + if (s->n_ref == 0) + source_free(s); + else if (r < 0) + sd_event_source_set_enabled(s, SD_EVENT_OFF); + + return 1; +} + +static int event_prepare(sd_event *e) { + int r; + + assert(e); + + for (;;) { + sd_event_source *s; + + s = prioq_peek(e->prepare); + if (!s || s->prepare_iteration == e->iteration || s->enabled == SD_EVENT_OFF) + break; + + s->prepare_iteration = e->iteration; + r = prioq_reshuffle(e->prepare, s, &s->prepare_index); + if (r < 0) + return r; + + assert(s->prepare); + + s->dispatching = true; + r = s->prepare(s, s->userdata); + s->dispatching = false; + + if (r < 0) + log_debug_errno(r, "Prepare callback of event source %s (type %s) returned error, disabling: %m", + strna(s->description), event_source_type_to_string(s->type)); + + if (s->n_ref == 0) + source_free(s); + else if (r < 0) + sd_event_source_set_enabled(s, SD_EVENT_OFF); + } + + return 0; +} + +static int dispatch_exit(sd_event *e) { + sd_event_source *p; + int r; + + assert(e); + + p = prioq_peek(e->exit); + if (!p || p->enabled == SD_EVENT_OFF) { + e->state = SD_EVENT_FINISHED; + return 0; + } + + sd_event_ref(e); + e->iteration++; + e->state = SD_EVENT_EXITING; + + r = source_dispatch(p); + + e->state = SD_EVENT_INITIAL; + sd_event_unref(e); + + return r; +} + +static sd_event_source* event_next_pending(sd_event *e) { + sd_event_source *p; + + assert(e); + + p = prioq_peek(e->pending); + if (!p) + return NULL; + + if (p->enabled == SD_EVENT_OFF) + return NULL; + + return p; +} + +static int arm_watchdog(sd_event *e) { + struct itimerspec its = {}; + usec_t t; + int r; + + assert(e); + assert(e->watchdog_fd >= 0); + + t = sleep_between(e, + e->watchdog_last + (e->watchdog_period / 2), + e->watchdog_last + (e->watchdog_period * 3 / 4)); + + timespec_store(&its.it_value, t); + + /* Make sure we never set the watchdog to 0, which tells the + * kernel to disable it. */ + if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0) + its.it_value.tv_nsec = 1; + + r = timerfd_settime(e->watchdog_fd, TFD_TIMER_ABSTIME, &its, NULL); + if (r < 0) + return -errno; + + return 0; +} + +static int process_watchdog(sd_event *e) { + assert(e); + + if (!e->watchdog) + return 0; + + /* Don't notify watchdog too often */ + if (e->watchdog_last + e->watchdog_period / 4 > e->timestamp.monotonic) + return 0; + + sd_notify(false, "WATCHDOG=1"); + e->watchdog_last = e->timestamp.monotonic; + + return arm_watchdog(e); +} + +_public_ int sd_event_prepare(sd_event *e) { + int r; + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + + if (e->exit_requested) + goto pending; + + e->iteration++; + + e->state = SD_EVENT_PREPARING; + r = event_prepare(e); + e->state = SD_EVENT_INITIAL; + if (r < 0) + return r; + + r = event_arm_timer(e, &e->realtime); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->boottime); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->monotonic); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->realtime_alarm); + if (r < 0) + return r; + + r = event_arm_timer(e, &e->boottime_alarm); + if (r < 0) + return r; + + if (event_next_pending(e) || e->need_process_child) + goto pending; + + e->state = SD_EVENT_ARMED; + + return 0; + +pending: + e->state = SD_EVENT_ARMED; + r = sd_event_wait(e, 0); + if (r == 0) + e->state = SD_EVENT_ARMED; + + return r; +} + +_public_ int sd_event_wait(sd_event *e, uint64_t timeout) { + struct epoll_event *ev_queue; + unsigned ev_queue_max; + int r, m, i; + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_ARMED, -EBUSY); + + if (e->exit_requested) { + e->state = SD_EVENT_PENDING; + return 1; + } + + ev_queue_max = MAX(e->n_sources, 1u); + ev_queue = newa(struct epoll_event, ev_queue_max); + + m = epoll_wait(e->epoll_fd, ev_queue, ev_queue_max, + timeout == (uint64_t) -1 ? -1 : (int) ((timeout + USEC_PER_MSEC - 1) / USEC_PER_MSEC)); + if (m < 0) { + if (errno == EINTR) { + e->state = SD_EVENT_PENDING; + return 1; + } + + r = -errno; + goto finish; + } + + dual_timestamp_get(&e->timestamp); + e->timestamp_boottime = now(CLOCK_BOOTTIME); + + for (i = 0; i < m; i++) { + + if (ev_queue[i].data.ptr == INT_TO_PTR(SOURCE_WATCHDOG)) + r = flush_timer(e, e->watchdog_fd, ev_queue[i].events, NULL); + else { + WakeupType *t = ev_queue[i].data.ptr; + + switch (*t) { + + case WAKEUP_EVENT_SOURCE: + r = process_io(e, ev_queue[i].data.ptr, ev_queue[i].events); + break; + + case WAKEUP_CLOCK_DATA: { + struct clock_data *d = ev_queue[i].data.ptr; + r = flush_timer(e, d->fd, ev_queue[i].events, &d->next); + break; + } + + case WAKEUP_SIGNAL_DATA: + r = process_signal(e, ev_queue[i].data.ptr, ev_queue[i].events); + break; + + default: + assert_not_reached("Invalid wake-up pointer"); + } + } + if (r < 0) + goto finish; + } + + r = process_watchdog(e); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.realtime, &e->realtime); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp_boottime, &e->boottime); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.monotonic, &e->monotonic); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp.realtime, &e->realtime_alarm); + if (r < 0) + goto finish; + + r = process_timer(e, e->timestamp_boottime, &e->boottime_alarm); + if (r < 0) + goto finish; + + if (e->need_process_child) { + r = process_child(e); + if (r < 0) + goto finish; + } + + if (event_next_pending(e)) { + e->state = SD_EVENT_PENDING; + + return 1; + } + + r = 0; + +finish: + e->state = SD_EVENT_INITIAL; + + return r; +} + +_public_ int sd_event_dispatch(sd_event *e) { + sd_event_source *p; + int r; + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_PENDING, -EBUSY); + + if (e->exit_requested) + return dispatch_exit(e); + + p = event_next_pending(e); + if (p) { + sd_event_ref(e); + + e->state = SD_EVENT_RUNNING; + r = source_dispatch(p); + e->state = SD_EVENT_INITIAL; + + sd_event_unref(e); + + return r; + } + + e->state = SD_EVENT_INITIAL; + + return 1; +} + +static void event_log_delays(sd_event *e) { + char b[ELEMENTSOF(e->delays) * DECIMAL_STR_MAX(unsigned) + 1]; + unsigned i; + int o; + + for (i = o = 0; i < ELEMENTSOF(e->delays); i++) { + o += snprintf(&b[o], sizeof(b) - o, "%u ", e->delays[i]); + e->delays[i] = 0; + } + log_debug("Event loop iterations: %.*s", o, b); +} + +_public_ int sd_event_run(sd_event *e, uint64_t timeout) { + int r; + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + + if (e->profile_delays && e->last_run) { + usec_t this_run; + unsigned l; + + this_run = now(CLOCK_MONOTONIC); + + l = u64log2(this_run - e->last_run); + assert(l < sizeof(e->delays)); + e->delays[l]++; + + if (this_run - e->last_log >= 5*USEC_PER_SEC) { + event_log_delays(e); + e->last_log = this_run; + } + } + + r = sd_event_prepare(e); + if (r == 0) + /* There was nothing? Then wait... */ + r = sd_event_wait(e, timeout); + + if (e->profile_delays) + e->last_run = now(CLOCK_MONOTONIC); + + if (r > 0) { + /* There's something now, then let's dispatch it */ + r = sd_event_dispatch(e); + if (r < 0) + return r; + + return 1; + } + + return r; +} + +_public_ int sd_event_loop(sd_event *e) { + int r; + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); + + sd_event_ref(e); + + while (e->state != SD_EVENT_FINISHED) { + r = sd_event_run(e, (uint64_t) -1); + if (r < 0) + goto finish; + } + + r = e->exit_code; + +finish: + sd_event_unref(e); + return r; +} + +_public_ int sd_event_get_fd(sd_event *e) { + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + return e->epoll_fd; +} + +_public_ int sd_event_get_state(sd_event *e) { + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + return e->state; +} + +_public_ int sd_event_get_exit_code(sd_event *e, int *code) { + assert_return(e, -EINVAL); + assert_return(code, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + if (!e->exit_requested) + return -ENODATA; + + *code = e->exit_code; + return 0; +} + +_public_ int sd_event_exit(sd_event *e, int code) { + assert_return(e, -EINVAL); + assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); + assert_return(!event_pid_changed(e), -ECHILD); + + e->exit_requested = true; + e->exit_code = code; + + return 0; +} + +_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 (!dual_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; + + default: + *usec = e->timestamp_boottime; + break; + } + + return 0; +} + +_public_ int sd_event_default(sd_event **ret) { + + static thread_local sd_event *default_event = NULL; + sd_event *e = NULL; + int r; + + if (!ret) + return !!default_event; + + if (default_event) { + *ret = sd_event_ref(default_event); + return 0; + } + + r = sd_event_new(&e); + if (r < 0) + return r; + + e->default_event_ptr = &default_event; + e->tid = gettid(); + default_event = e; + + *ret = e; + return 1; +} + +_public_ int sd_event_get_tid(sd_event *e, pid_t *tid) { + assert_return(e, -EINVAL); + assert_return(tid, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + if (e->tid != 0) { + *tid = e->tid; + return 0; + } + + return -ENXIO; +} + +_public_ int sd_event_set_watchdog(sd_event *e, int b) { + int r; + + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + if (e->watchdog == !!b) + return e->watchdog; + + if (b) { + struct epoll_event ev = {}; + + r = sd_watchdog_enabled(false, &e->watchdog_period); + if (r <= 0) + return r; + + /* Issue first ping immediately */ + sd_notify(false, "WATCHDOG=1"); + e->watchdog_last = now(CLOCK_MONOTONIC); + + e->watchdog_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC); + if (e->watchdog_fd < 0) + return -errno; + + r = arm_watchdog(e); + if (r < 0) + goto fail; + + ev.events = EPOLLIN; + ev.data.ptr = INT_TO_PTR(SOURCE_WATCHDOG); + + r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, e->watchdog_fd, &ev); + if (r < 0) { + r = -errno; + goto fail; + } + + } else { + if (e->watchdog_fd >= 0) { + epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, e->watchdog_fd, NULL); + e->watchdog_fd = safe_close(e->watchdog_fd); + } + } + + e->watchdog = !!b; + return e->watchdog; + +fail: + e->watchdog_fd = safe_close(e->watchdog_fd); + return r; +} + +_public_ int sd_event_get_watchdog(sd_event *e) { + assert_return(e, -EINVAL); + assert_return(!event_pid_changed(e), -ECHILD); + + return e->watchdog; +} diff --git a/src/libsystemd/libsystemd-internal/sd-event/test-event.c b/src/libsystemd/libsystemd-internal/sd-event/test-event.c new file mode 100644 index 0000000000..6beb1b08cd --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-event/test-event.c @@ -0,0 +1,357 @@ +/*** + 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 . +***/ + +#include + +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "signal-util.h" +#include "util.h" + +static int prepare_handler(sd_event_source *s, void *userdata) { + log_info("preparing %c", PTR_TO_INT(userdata)); + return 1; +} + +static bool got_a, got_b, got_c, got_unref; +static unsigned got_d; + +static int unref_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_event_source_unref(s); + got_unref = true; + return 0; +} + +static int io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + + log_info("got IO on %c", PTR_TO_INT(userdata)); + + if (userdata == INT_TO_PTR('a')) { + assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0); + assert_se(!got_a); + got_a = true; + } else if (userdata == INT_TO_PTR('b')) { + assert_se(!got_b); + got_b = true; + } else if (userdata == INT_TO_PTR('d')) { + got_d++; + if (got_d < 2) + assert_se(sd_event_source_set_enabled(s, SD_EVENT_ONESHOT) >= 0); + else + assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0); + } else + assert_not_reached("Yuck!"); + + return 1; +} + +static int child_handler(sd_event_source *s, const siginfo_t *si, void *userdata) { + + assert_se(s); + assert_se(si); + + log_info("got child on %c", PTR_TO_INT(userdata)); + + assert_se(userdata == INT_TO_PTR('f')); + + assert_se(sd_event_exit(sd_event_source_get_event(s), 0) >= 0); + sd_event_source_unref(s); + + return 1; +} + +static int signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + sd_event_source *p = NULL; + pid_t pid; + + assert_se(s); + assert_se(si); + + log_info("got signal on %c", PTR_TO_INT(userdata)); + + assert_se(userdata == INT_TO_PTR('e')); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + + pid = fork(); + assert_se(pid >= 0); + + if (pid == 0) + _exit(0); + + assert_se(sd_event_add_child(sd_event_source_get_event(s), &p, pid, WEXITED, child_handler, INT_TO_PTR('f')) >= 0); + assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); + + sd_event_source_unref(s); + + return 1; +} + +static int defer_handler(sd_event_source *s, void *userdata) { + sd_event_source *p = NULL; + + assert_se(s); + + log_info("got defer on %c", PTR_TO_INT(userdata)); + + assert_se(userdata == INT_TO_PTR('d')); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGUSR1, -1) >= 0); + + assert_se(sd_event_add_signal(sd_event_source_get_event(s), &p, SIGUSR1, signal_handler, INT_TO_PTR('e')) >= 0); + assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); + raise(SIGUSR1); + + sd_event_source_unref(s); + + return 1; +} + +static bool do_quit = false; + +static int time_handler(sd_event_source *s, uint64_t usec, void *userdata) { + log_info("got timer on %c", PTR_TO_INT(userdata)); + + if (userdata == INT_TO_PTR('c')) { + + if (do_quit) { + sd_event_source *p; + + assert_se(sd_event_add_defer(sd_event_source_get_event(s), &p, defer_handler, INT_TO_PTR('d')) >= 0); + assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); + } else { + assert_se(!got_c); + got_c = true; + } + } else + assert_not_reached("Huh?"); + + return 2; +} + +static bool got_exit = false; + +static int exit_handler(sd_event_source *s, void *userdata) { + log_info("got quit handler on %c", PTR_TO_INT(userdata)); + + got_exit = true; + + return 3; +} + +static bool got_post = false; + +static int post_handler(sd_event_source *s, void *userdata) { + log_info("got post handler"); + + got_post = true; + + return 2; +} + +static void test_basic(void) { + sd_event *e = NULL; + sd_event_source *w = NULL, *x = NULL, *y = NULL, *z = NULL, *q = NULL, *t = NULL; + static const char ch = 'x'; + int a[2] = { -1, -1 }, b[2] = { -1, -1}, d[2] = { -1, -1}, k[2] = { -1, -1 }; + uint64_t event_now; + + assert_se(pipe(a) >= 0); + assert_se(pipe(b) >= 0); + assert_se(pipe(d) >= 0); + assert_se(pipe(k) >= 0); + + assert_se(sd_event_default(&e) >= 0); + assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) > 0); + + assert_se(sd_event_set_watchdog(e, true) >= 0); + + /* Test whether we cleanly can destroy an io event source from its own handler */ + got_unref = false; + assert_se(sd_event_add_io(e, &t, k[0], EPOLLIN, unref_handler, NULL) >= 0); + assert_se(write(k[1], &ch, 1) == 1); + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(got_unref); + + got_a = false, got_b = false, got_c = false, got_d = 0; + + /* Add a oneshot handler, trigger it, re-enable it, and trigger + * it again. */ + assert_se(sd_event_add_io(e, &w, d[0], EPOLLIN, io_handler, INT_TO_PTR('d')) >= 0); + assert_se(sd_event_source_set_enabled(w, SD_EVENT_ONESHOT) >= 0); + assert_se(write(d[1], &ch, 1) >= 0); + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(got_d == 1); + assert_se(write(d[1], &ch, 1) >= 0); + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(got_d == 2); + + assert_se(sd_event_add_io(e, &x, a[0], EPOLLIN, io_handler, INT_TO_PTR('a')) >= 0); + assert_se(sd_event_add_io(e, &y, b[0], EPOLLIN, io_handler, INT_TO_PTR('b')) >= 0); + assert_se(sd_event_add_time(e, &z, CLOCK_MONOTONIC, 0, 0, time_handler, INT_TO_PTR('c')) >= 0); + assert_se(sd_event_add_exit(e, &q, exit_handler, INT_TO_PTR('g')) >= 0); + + assert_se(sd_event_source_set_priority(x, 99) >= 0); + assert_se(sd_event_source_set_enabled(y, SD_EVENT_ONESHOT) >= 0); + assert_se(sd_event_source_set_prepare(x, prepare_handler) >= 0); + assert_se(sd_event_source_set_priority(z, 50) >= 0); + assert_se(sd_event_source_set_enabled(z, SD_EVENT_ONESHOT) >= 0); + assert_se(sd_event_source_set_prepare(z, prepare_handler) >= 0); + + /* Test for floating event sources */ + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+1, -1) >= 0); + assert_se(sd_event_add_signal(e, NULL, SIGRTMIN+1, NULL, NULL) >= 0); + + assert_se(write(a[1], &ch, 1) >= 0); + assert_se(write(b[1], &ch, 1) >= 0); + + assert_se(!got_a && !got_b && !got_c); + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + + assert_se(!got_a && got_b && !got_c); + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + + assert_se(!got_a && got_b && got_c); + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + + assert_se(got_a && got_b && got_c); + + sd_event_source_unref(x); + sd_event_source_unref(y); + + do_quit = true; + assert_se(sd_event_add_post(e, NULL, post_handler, NULL) >= 0); + assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) == 0); + assert_se(sd_event_source_set_time(z, event_now + 200 * USEC_PER_MSEC) >= 0); + assert_se(sd_event_source_set_enabled(z, SD_EVENT_ONESHOT) >= 0); + + assert_se(sd_event_loop(e) >= 0); + assert_se(got_post); + assert_se(got_exit); + + sd_event_source_unref(z); + sd_event_source_unref(q); + + sd_event_source_unref(w); + + sd_event_unref(e); + + safe_close_pair(a); + safe_close_pair(b); + safe_close_pair(d); + safe_close_pair(k); +} + +static void test_sd_event_now(void) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + uint64_t event_now; + + assert_se(sd_event_new(&e) >= 0); + assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) > 0); + assert_se(sd_event_now(e, CLOCK_REALTIME, &event_now) > 0); + assert_se(sd_event_now(e, CLOCK_REALTIME_ALARM, &event_now) > 0); + assert_se(sd_event_now(e, CLOCK_BOOTTIME, &event_now) > 0); + assert_se(sd_event_now(e, CLOCK_BOOTTIME_ALARM, &event_now) > 0); + assert_se(sd_event_now(e, -1, &event_now) == -EOPNOTSUPP); + assert_se(sd_event_now(e, 900 /* arbitrary big number */, &event_now) == -EOPNOTSUPP); + + assert_se(sd_event_run(e, 0) == 0); + + assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) == 0); + assert_se(sd_event_now(e, CLOCK_REALTIME, &event_now) == 0); + assert_se(sd_event_now(e, CLOCK_REALTIME_ALARM, &event_now) == 0); + assert_se(sd_event_now(e, CLOCK_BOOTTIME, &event_now) == 0); + assert_se(sd_event_now(e, CLOCK_BOOTTIME_ALARM, &event_now) == 0); + assert_se(sd_event_now(e, -1, &event_now) == -EOPNOTSUPP); + assert_se(sd_event_now(e, 900 /* arbitrary big number */, &event_now) == -EOPNOTSUPP); +} + +static int last_rtqueue_sigval = 0; +static int n_rtqueue = 0; + +static int rtqueue_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + last_rtqueue_sigval = si->ssi_int; + n_rtqueue ++; + return 0; +} + +static void test_rtqueue(void) { + sd_event_source *u = NULL, *v = NULL, *s = NULL; + sd_event *e = NULL; + + assert_se(sd_event_default(&e) >= 0); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+2, SIGRTMIN+3, SIGUSR2, -1) >= 0); + assert_se(sd_event_add_signal(e, &u, SIGRTMIN+2, rtqueue_handler, NULL) >= 0); + assert_se(sd_event_add_signal(e, &v, SIGRTMIN+3, rtqueue_handler, NULL) >= 0); + assert_se(sd_event_add_signal(e, &s, SIGUSR2, rtqueue_handler, NULL) >= 0); + + assert_se(sd_event_source_set_priority(v, -10) >= 0); + + assert(sigqueue(getpid(), SIGRTMIN+2, (union sigval) { .sival_int = 1 }) >= 0); + assert(sigqueue(getpid(), SIGRTMIN+3, (union sigval) { .sival_int = 2 }) >= 0); + assert(sigqueue(getpid(), SIGUSR2, (union sigval) { .sival_int = 3 }) >= 0); + assert(sigqueue(getpid(), SIGRTMIN+3, (union sigval) { .sival_int = 4 }) >= 0); + assert(sigqueue(getpid(), SIGUSR2, (union sigval) { .sival_int = 5 }) >= 0); + + assert_se(n_rtqueue == 0); + assert_se(last_rtqueue_sigval == 0); + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(n_rtqueue == 1); + assert_se(last_rtqueue_sigval == 2); /* first SIGRTMIN+3 */ + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(n_rtqueue == 2); + assert_se(last_rtqueue_sigval == 4); /* second SIGRTMIN+3 */ + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(n_rtqueue == 3); + assert_se(last_rtqueue_sigval == 3); /* first SIGUSR2 */ + + assert_se(sd_event_run(e, (uint64_t) -1) >= 1); + assert_se(n_rtqueue == 4); + assert_se(last_rtqueue_sigval == 1); /* SIGRTMIN+2 */ + + assert_se(sd_event_run(e, 0) == 0); /* the other SIGUSR2 is dropped, because the first one was still queued */ + assert_se(n_rtqueue == 4); + assert_se(last_rtqueue_sigval == 1); + + sd_event_source_unref(u); + sd_event_source_unref(v); + sd_event_source_unref(s); + + sd_event_unref(e); +} + +int main(int argc, char *argv[]) { + + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + + test_basic(); + test_sd_event_now(); + test_rtqueue(); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-internal.h b/src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-internal.h new file mode 100644 index 0000000000..13fddfc8ad --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-internal.h @@ -0,0 +1,71 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ +#pragma once + +#include "sparse-endian.h" +#include "util.h" + +#define HWDB_SIG { 'K', 'S', 'L', 'P', 'H', 'H', 'R', 'H' } + +/* on-disk trie objects */ +struct trie_header_f { + uint8_t signature[8]; + + /* version of tool which created the file */ + le64_t tool_version; + le64_t file_size; + + /* size of structures to allow them to grow */ + le64_t header_size; + le64_t node_size; + le64_t child_entry_size; + le64_t value_entry_size; + + /* offset of the root trie node */ + le64_t nodes_root_off; + + /* size of the nodes and string section */ + le64_t nodes_len; + le64_t strings_len; +} _packed_; + +struct trie_node_f { + /* prefix of lookup string, shared by all children */ + le64_t prefix_off; + /* size of children entry array appended to the node */ + uint8_t children_count; + uint8_t padding[7]; + /* size of value entry array appended to the node */ + le64_t values_count; +} _packed_; + +/* array of child entries, follows directly the node record */ +struct trie_child_entry_f { + /* index of the child node */ + uint8_t c; + uint8_t padding[7]; + /* offset of the child node */ + le64_t child_off; +} _packed_; + +/* array of value entries, follows directly the node record/child array */ +struct trie_value_entry_f { + le64_t key_off; + le64_t value_off; +} _packed_; diff --git a/src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-util.h b/src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-util.h new file mode 100644 index 0000000000..05dc47962b --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-hwdb/hwdb-util.h @@ -0,0 +1,26 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include + +#include "util.h" + +bool hwdb_validate(sd_hwdb *hwdb); diff --git a/src/libsystemd/libsystemd-internal/sd-hwdb/sd-hwdb.c b/src/libsystemd/libsystemd-internal/sd-hwdb/sd-hwdb.c new file mode 100644 index 0000000000..7dcfe95b87 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-hwdb/sd-hwdb.c @@ -0,0 +1,470 @@ +/*** + This file is part of systemd. + + Copyright 2012 Kay Sievers + Copyright 2008 Alan Jenkins + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "hwdb-internal.h" +#include "hwdb-util.h" +#include "refcnt.h" +#include "string-util.h" + +struct sd_hwdb { + RefCount n_ref; + int refcount; + + FILE *f; + struct stat st; + union { + struct trie_header_f *head; + const char *map; + }; + + char *modalias; + + OrderedHashmap *properties; + Iterator properties_iterator; + bool properties_modified; +}; + +struct linebuf { + char bytes[LINE_MAX]; + size_t size; + size_t len; +}; + +static void linebuf_init(struct linebuf *buf) { + buf->size = 0; + buf->len = 0; +} + +static const char *linebuf_get(struct linebuf *buf) { + if (buf->len + 1 >= sizeof(buf->bytes)) + return NULL; + buf->bytes[buf->len] = '\0'; + return buf->bytes; +} + +static bool linebuf_add(struct linebuf *buf, const char *s, size_t len) { + if (buf->len + len >= sizeof(buf->bytes)) + return false; + memcpy(buf->bytes + buf->len, s, len); + buf->len += len; + return true; +} + +static bool linebuf_add_char(struct linebuf *buf, char c) { + if (buf->len + 1 >= sizeof(buf->bytes)) + return false; + buf->bytes[buf->len++] = c; + return true; +} + +static void linebuf_rem(struct linebuf *buf, size_t count) { + assert(buf->len >= count); + buf->len -= count; +} + +static void linebuf_rem_char(struct linebuf *buf) { + linebuf_rem(buf, 1); +} + +static const struct trie_child_entry_f *trie_node_children(sd_hwdb *hwdb, const struct trie_node_f *node) { + return (const struct trie_child_entry_f *)((const char *)node + le64toh(hwdb->head->node_size)); +} + +static const struct trie_value_entry_f *trie_node_values(sd_hwdb *hwdb, const struct trie_node_f *node) { + const char *base = (const char *)node; + + base += le64toh(hwdb->head->node_size); + base += node->children_count * le64toh(hwdb->head->child_entry_size); + return (const struct trie_value_entry_f *)base; +} + +static const struct trie_node_f *trie_node_from_off(sd_hwdb *hwdb, le64_t off) { + return (const struct trie_node_f *)(hwdb->map + le64toh(off)); +} + +static const char *trie_string(sd_hwdb *hwdb, le64_t off) { + return hwdb->map + le64toh(off); +} + +static int trie_children_cmp_f(const void *v1, const void *v2) { + const struct trie_child_entry_f *n1 = v1; + const struct trie_child_entry_f *n2 = v2; + + return n1->c - n2->c; +} + +static const struct trie_node_f *node_lookup_f(sd_hwdb *hwdb, const struct trie_node_f *node, uint8_t c) { + struct trie_child_entry_f *child; + struct trie_child_entry_f search; + + search.c = c; + child = bsearch(&search, trie_node_children(hwdb, node), node->children_count, + le64toh(hwdb->head->child_entry_size), trie_children_cmp_f); + if (child) + return trie_node_from_off(hwdb, child->child_off); + return NULL; +} + +static int hwdb_add_property(sd_hwdb *hwdb, const char *key, const char *value) { + int r; + + assert(hwdb); + assert(key); + assert(value); + + /* + * Silently ignore all properties which do not start with a + * space; future extensions might use additional prefixes. + */ + if (key[0] != ' ') + return 0; + + key++; + + r = ordered_hashmap_ensure_allocated(&hwdb->properties, &string_hash_ops); + if (r < 0) + return r; + + r = ordered_hashmap_replace(hwdb->properties, key, (char*)value); + if (r < 0) + return r; + + hwdb->properties_modified = true; + + return 0; +} + +static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t p, + struct linebuf *buf, const char *search) { + size_t len; + size_t i; + const char *prefix; + int err; + + prefix = trie_string(hwdb, node->prefix_off); + len = strlen(prefix + p); + linebuf_add(buf, prefix + p, len); + + for (i = 0; i < node->children_count; i++) { + const struct trie_child_entry_f *child = &trie_node_children(hwdb, node)[i]; + + linebuf_add_char(buf, child->c); + err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search); + if (err < 0) + return err; + linebuf_rem_char(buf); + } + + if (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0) + for (i = 0; i < le64toh(node->values_count); i++) { + err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[i].key_off), + trie_string(hwdb, trie_node_values(hwdb, node)[i].value_off)); + if (err < 0) + return err; + } + + linebuf_rem(buf, len); + return 0; +} + +static int trie_search_f(sd_hwdb *hwdb, const char *search) { + struct linebuf buf; + const struct trie_node_f *node; + size_t i = 0; + int err; + + linebuf_init(&buf); + + node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off); + while (node) { + const struct trie_node_f *child; + size_t p = 0; + + if (node->prefix_off) { + uint8_t c; + + for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) { + if (c == '*' || c == '?' || c == '[') + return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p); + if (c != search[i + p]) + return 0; + } + i += p; + } + + child = node_lookup_f(hwdb, node, '*'); + if (child) { + linebuf_add_char(&buf, '*'); + err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); + if (err < 0) + return err; + linebuf_rem_char(&buf); + } + + child = node_lookup_f(hwdb, node, '?'); + if (child) { + linebuf_add_char(&buf, '?'); + err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); + if (err < 0) + return err; + linebuf_rem_char(&buf); + } + + child = node_lookup_f(hwdb, node, '['); + if (child) { + linebuf_add_char(&buf, '['); + err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); + if (err < 0) + return err; + linebuf_rem_char(&buf); + } + + if (search[i] == '\0') { + size_t n; + + for (n = 0; n < le64toh(node->values_count); n++) { + err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[n].key_off), + trie_string(hwdb, trie_node_values(hwdb, node)[n].value_off)); + if (err < 0) + return err; + } + return 0; + } + + child = node_lookup_f(hwdb, node, search[i]); + node = child; + i++; + } + return 0; +} + +static const char hwdb_bin_paths[] = + "/etc/systemd/hwdb/hwdb.bin\0" + "/etc/udev/hwdb.bin\0" + "/usr/lib/systemd/hwdb/hwdb.bin\0" +#ifdef HAVE_SPLIT_USR + "/lib/systemd/hwdb/hwdb.bin\0" +#endif + UDEVLIBEXECDIR "/hwdb.bin\0"; + +_public_ int sd_hwdb_new(sd_hwdb **ret) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; + const char *hwdb_bin_path; + const char sig[] = HWDB_SIG; + + assert_return(ret, -EINVAL); + + hwdb = new0(sd_hwdb, 1); + if (!hwdb) + return -ENOMEM; + + hwdb->n_ref = REFCNT_INIT; + + /* find hwdb.bin in hwdb_bin_paths */ + NULSTR_FOREACH(hwdb_bin_path, hwdb_bin_paths) { + hwdb->f = fopen(hwdb_bin_path, "re"); + if (hwdb->f) + break; + else if (errno == ENOENT) + continue; + else + return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path); + } + + if (!hwdb->f) { + log_debug("hwdb.bin does not exist, please run udevadm hwdb --update"); + return -ENOENT; + } + + if (fstat(fileno(hwdb->f), &hwdb->st) < 0 || + (size_t)hwdb->st.st_size < offsetof(struct trie_header_f, strings_len) + 8) + return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path); + + hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0); + if (hwdb->map == MAP_FAILED) + return log_debug_errno(errno, "error mapping %s: %m", hwdb_bin_path); + + if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 || + (size_t)hwdb->st.st_size != le64toh(hwdb->head->file_size)) { + log_debug("error recognizing the format of %s", hwdb_bin_path); + return -EINVAL; + } + + log_debug("=== trie on-disk ==="); + log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version)); + log_debug("file size: %8"PRIi64" bytes", hwdb->st.st_size); + log_debug("header size %8"PRIu64" bytes", le64toh(hwdb->head->header_size)); + log_debug("strings %8"PRIu64" bytes", le64toh(hwdb->head->strings_len)); + log_debug("nodes %8"PRIu64" bytes", le64toh(hwdb->head->nodes_len)); + + *ret = hwdb; + hwdb = NULL; + + return 0; +} + +_public_ sd_hwdb *sd_hwdb_ref(sd_hwdb *hwdb) { + assert_return(hwdb, NULL); + + assert_se(REFCNT_INC(hwdb->n_ref) >= 2); + + return hwdb; +} + +_public_ sd_hwdb *sd_hwdb_unref(sd_hwdb *hwdb) { + if (hwdb && REFCNT_DEC(hwdb->n_ref) == 0) { + if (hwdb->map) + munmap((void *)hwdb->map, hwdb->st.st_size); + safe_fclose(hwdb->f); + free(hwdb->modalias); + ordered_hashmap_free(hwdb->properties); + free(hwdb); + } + + return NULL; +} + +bool hwdb_validate(sd_hwdb *hwdb) { + bool found = false; + const char* p; + struct stat st; + + if (!hwdb) + return false; + if (!hwdb->f) + return false; + + /* if hwdb.bin doesn't exist anywhere, we need to update */ + NULSTR_FOREACH(p, hwdb_bin_paths) { + if (stat(p, &st) >= 0) { + found = true; + break; + } + } + if (!found) + return true; + + if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim)) + return true; + return false; +} + +static int properties_prepare(sd_hwdb *hwdb, const char *modalias) { + _cleanup_free_ char *mod = NULL; + int r; + + assert(hwdb); + assert(modalias); + + if (streq_ptr(modalias, hwdb->modalias)) + return 0; + + mod = strdup(modalias); + if (!mod) + return -ENOMEM; + + ordered_hashmap_clear(hwdb->properties); + + hwdb->properties_modified = true; + + r = trie_search_f(hwdb, modalias); + if (r < 0) + return r; + + free(hwdb->modalias); + hwdb->modalias = mod; + mod = NULL; + + return 0; +} + +_public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **_value) { + const char *value; + int r; + + assert_return(hwdb, -EINVAL); + assert_return(hwdb->f, -EINVAL); + assert_return(modalias, -EINVAL); + assert_return(_value, -EINVAL); + + r = properties_prepare(hwdb, modalias); + if (r < 0) + return r; + + value = ordered_hashmap_get(hwdb->properties, key); + if (!value) + return -ENOENT; + + *_value = value; + + return 0; +} + +_public_ int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias) { + int r; + + assert_return(hwdb, -EINVAL); + assert_return(hwdb->f, -EINVAL); + assert_return(modalias, -EINVAL); + + r = properties_prepare(hwdb, modalias); + if (r < 0) + return r; + + hwdb->properties_modified = false; + hwdb->properties_iterator = ITERATOR_FIRST; + + return 0; +} + +_public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value) { + const void *k; + void *v; + + assert_return(hwdb, -EINVAL); + assert_return(key, -EINVAL); + assert_return(value, -EINVAL); + + if (hwdb->properties_modified) + return -EAGAIN; + + ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, &v, &k); + if (!k) + return 0; + + *key = k; + *value = v; + + return 1; +} diff --git a/src/libsystemd/libsystemd-internal/sd-id128/sd-id128.c b/src/libsystemd/libsystemd-internal/sd-id128/sd-id128.c new file mode 100644 index 0000000000..cda3e9f0df --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-id128/sd-id128.c @@ -0,0 +1,225 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include + +#include + +#include "fd-util.h" +#include "hexdecoct.h" +#include "io-util.h" +#include "macro.h" +#include "random-util.h" +#include "util.h" + +_public_ char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]) { + unsigned n; + + assert_return(s, NULL); + + for (n = 0; n < 16; n++) { + s[n*2] = hexchar(id.bytes[n] >> 4); + s[n*2+1] = hexchar(id.bytes[n] & 0xF); + } + + s[32] = 0; + + return s; +} + +_public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) { + unsigned n, i; + sd_id128_t t; + bool is_guid = false; + + assert_return(s, -EINVAL); + assert_return(ret, -EINVAL); + + for (n = 0, i = 0; n < 16;) { + int a, b; + + if (s[i] == '-') { + /* Is this a GUID? Then be nice, and skip over + * the dashes */ + + if (i == 8) + is_guid = true; + else if (i == 13 || i == 18 || i == 23) { + if (!is_guid) + return -EINVAL; + } else + return -EINVAL; + + i++; + continue; + } + + a = unhexchar(s[i++]); + if (a < 0) + return -EINVAL; + + b = unhexchar(s[i++]); + if (b < 0) + return -EINVAL; + + t.bytes[n++] = (a << 4) | b; + } + + if (i != (is_guid ? 36 : 32)) + return -EINVAL; + + if (s[i] != 0) + return -EINVAL; + + *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; + 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; + + t.bytes[j] = a << 4 | b; + } + + saved_machine_id = t; + saved_machine_id_valid = true; + + *ret = t; + 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; + int r; + + assert_return(ret, -EINVAL); + + if (saved_boot_id_valid) { + *ret = saved_boot_id; + return 0; + } + + 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; + + t.bytes[j] = a << 4 | b; + + p += 2; + } + + saved_boot_id = t; + saved_boot_id_valid = true; + + *ret = t; + return 0; +} + +_public_ int sd_id128_randomize(sd_id128_t *ret) { + sd_id128_t t; + int r; + + assert_return(ret, -EINVAL); + + r = dev_urandom(&t, sizeof(t)); + if (r < 0) + return r; + + /* Turn this into a valid v4 UUID, to be nice. Note that we + * only guarantee this for newly generated UUIDs, not for + * pre-existing ones. */ + + *ret = make_v4_uuid(t); + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-login/sd-login.c b/src/libsystemd/libsystemd-internal/sd-login/sd-login.c new file mode 100644 index 0000000000..84831d5e95 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-login/sd-login.c @@ -0,0 +1,1062 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "dirent-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "hostname-util.h" +#include "io-util.h" +#include "login-util.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +/* Error codes: + * + * invalid input parameters → -EINVAL + * invalid fd → -EBADF + * process does not exist → -ESRCH + * cgroup does not exist → -ENOENT + * machine, session does not exist → -ENXIO + * requested metadata on object is missing → -ENODATA + */ + +_public_ int sd_pid_get_session(pid_t pid, char **session) { + + assert_return(pid >= 0, -EINVAL); + assert_return(session, -EINVAL); + + return cg_pid_get_session(pid, session); +} + +_public_ int sd_pid_get_unit(pid_t pid, char **unit) { + + assert_return(pid >= 0, -EINVAL); + assert_return(unit, -EINVAL); + + return cg_pid_get_unit(pid, unit); +} + +_public_ int sd_pid_get_user_unit(pid_t pid, char **unit) { + + assert_return(pid >= 0, -EINVAL); + assert_return(unit, -EINVAL); + + return cg_pid_get_user_unit(pid, unit); +} + +_public_ int sd_pid_get_machine_name(pid_t pid, char **name) { + + assert_return(pid >= 0, -EINVAL); + assert_return(name, -EINVAL); + + return cg_pid_get_machine_name(pid, name); +} + +_public_ int sd_pid_get_slice(pid_t pid, char **slice) { + + assert_return(pid >= 0, -EINVAL); + assert_return(slice, -EINVAL); + + return cg_pid_get_slice(pid, slice); +} + +_public_ int sd_pid_get_user_slice(pid_t pid, char **slice) { + + assert_return(pid >= 0, -EINVAL); + assert_return(slice, -EINVAL); + + return cg_pid_get_user_slice(pid, slice); +} + +_public_ int sd_pid_get_owner_uid(pid_t pid, uid_t *uid) { + + assert_return(pid >= 0, -EINVAL); + assert_return(uid, -EINVAL); + + return cg_pid_get_owner_uid(pid, uid); +} + +_public_ int sd_pid_get_cgroup(pid_t pid, char **cgroup) { + char *c; + int r; + + assert_return(pid >= 0, -EINVAL); + assert_return(cgroup, -EINVAL); + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &c); + if (r < 0) + return r; + + /* 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. */ + if (isempty(c)) { + free(c); + c = strdup("/"); + if (!c) + return -ENOMEM; + + } + + *cgroup = c; + return 0; +} + +_public_ int sd_peer_get_session(int fd, char **session) { + struct ucred ucred = {}; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(session, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_session(ucred.pid, session); +} + +_public_ int sd_peer_get_owner_uid(int fd, uid_t *uid) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(uid, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_owner_uid(ucred.pid, uid); +} + +_public_ int sd_peer_get_unit(int fd, char **unit) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(unit, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_unit(ucred.pid, unit); +} + +_public_ int sd_peer_get_user_unit(int fd, char **unit) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(unit, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_user_unit(ucred.pid, unit); +} + +_public_ int sd_peer_get_machine_name(int fd, char **machine) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(machine, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_machine_name(ucred.pid, machine); +} + +_public_ int sd_peer_get_slice(int fd, char **slice) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(slice, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_slice(ucred.pid, slice); +} + +_public_ int sd_peer_get_user_slice(int fd, char **slice) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(slice, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return cg_pid_get_user_slice(ucred.pid, slice); +} + +_public_ int sd_peer_get_cgroup(int fd, char **cgroup) { + struct ucred ucred; + int r; + + assert_return(fd >= 0, -EBADF); + assert_return(cgroup, -EINVAL); + + r = getpeercred(fd, &ucred); + if (r < 0) + return r; + + return sd_pid_get_cgroup(ucred.pid, cgroup); +} + +static int file_of_uid(uid_t uid, char **p) { + + assert_return(uid_is_valid(uid), -EINVAL); + assert(p); + + if (asprintf(p, "/run/systemd/users/" UID_FMT, uid) < 0) + return -ENOMEM; + + return 0; +} + +_public_ int sd_uid_get_state(uid_t uid, char**state) { + _cleanup_free_ char *p = NULL; + char *s = NULL; + int r; + + assert_return(state, -EINVAL); + + r = file_of_uid(uid, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "STATE", &s, NULL); + if (r == -ENOENT) { + free(s); + s = strdup("offline"); + if (!s) + return -ENOMEM; + + } + if (r < 0) { + free(s); + return r; + } + if (isempty(s)) { + free(s); + return -EIO; + } + + *state = s; + return 0; +} + +_public_ int sd_uid_get_display(uid_t uid, char **session) { + _cleanup_free_ char *p = NULL, *s = NULL; + int r; + + assert_return(session, -EINVAL); + + r = file_of_uid(uid, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "DISPLAY", &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + *session = s; + s = NULL; + + return 0; +} + +static int file_of_seat(const char *seat, char **_p) { + char *p; + int r; + + assert(_p); + + if (seat) { + if (!filename_is_valid(seat)) + return -EINVAL; + + p = strappend("/run/systemd/seats/", seat); + } else { + _cleanup_free_ char *buf = NULL; + + r = sd_session_get_seat(NULL, &buf); + if (r < 0) + return r; + + p = strappend("/run/systemd/seats/", buf); + } + + if (!p) + return -ENOMEM; + + *_p = p; + p = NULL; + return 0; +} + +_public_ int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat) { + _cleanup_free_ char *t = NULL, *s = NULL, *p = NULL; + size_t l; + int r; + const char *word, *variable, *state; + + assert_return(uid_is_valid(uid), -EINVAL); + + r = file_of_seat(seat, &p); + if (r < 0) + return r; + + variable = require_active ? "ACTIVE_UID" : "UIDS"; + + r = parse_env_file(p, NEWLINE, variable, &s, NULL); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + if (isempty(s)) + return 0; + + if (asprintf(&t, UID_FMT, uid) < 0) + return -ENOMEM; + + FOREACH_WORD(word, l, s, state) + if (strneq(t, word, l)) + return 1; + + return 0; +} + +static int uid_get_array(uid_t uid, const char *variable, char ***array) { + _cleanup_free_ char *p = NULL, *s = NULL; + char **a; + int r; + + assert(variable); + + r = file_of_uid(uid, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, variable, &s, NULL); + if (r == -ENOENT || (r >= 0 && isempty(s))) { + if (array) + *array = NULL; + return 0; + } + if (r < 0) + return r; + + a = strv_split(s, " "); + if (!a) + return -ENOMEM; + + strv_uniq(a); + r = strv_length(a); + + if (array) + *array = a; + else + strv_free(a); + + return r; +} + +_public_ int sd_uid_get_sessions(uid_t uid, int require_active, char ***sessions) { + return uid_get_array( + uid, + require_active == 0 ? "ONLINE_SESSIONS" : + require_active > 0 ? "ACTIVE_SESSIONS" : + "SESSIONS", + sessions); +} + +_public_ int sd_uid_get_seats(uid_t uid, int require_active, char ***seats) { + return uid_get_array( + uid, + require_active == 0 ? "ONLINE_SEATS" : + require_active > 0 ? "ACTIVE_SEATS" : + "SEATS", + seats); +} + +static int file_of_session(const char *session, char **_p) { + char *p; + int r; + + assert(_p); + + if (session) { + if (!session_id_valid(session)) + return -EINVAL; + + p = strappend("/run/systemd/sessions/", session); + } else { + _cleanup_free_ char *buf = NULL; + + r = sd_pid_get_session(0, &buf); + if (r < 0) + return r; + + p = strappend("/run/systemd/sessions/", buf); + } + + if (!p) + return -ENOMEM; + + *_p = p; + return 0; +} + +_public_ int sd_session_is_active(const char *session) { + _cleanup_free_ char *p = NULL, *s = NULL; + int r; + + r = file_of_session(session, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "ACTIVE", &s, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (isempty(s)) + return -EIO; + + return parse_boolean(s); +} + +_public_ int sd_session_is_remote(const char *session) { + _cleanup_free_ char *p = NULL, *s = NULL; + int r; + + r = file_of_session(session, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "REMOTE", &s, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + return parse_boolean(s); +} + +_public_ int sd_session_get_state(const char *session, char **state) { + _cleanup_free_ char *p = NULL, *s = NULL; + int r; + + assert_return(state, -EINVAL); + + r = file_of_session(session, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "STATE", &s, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (isempty(s)) + return -EIO; + + *state = s; + s = NULL; + + return 0; +} + +_public_ int sd_session_get_uid(const char *session, uid_t *uid) { + int r; + _cleanup_free_ char *p = NULL, *s = NULL; + + assert_return(uid, -EINVAL); + + r = file_of_session(session, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, "UID", &s, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (isempty(s)) + return -EIO; + + return parse_uid(s, uid); +} + +static int session_get_string(const char *session, const char *field, char **value) { + _cleanup_free_ char *p = NULL, *s = NULL; + int r; + + assert_return(value, -EINVAL); + assert(field); + + r = file_of_session(session, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, field, &s, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + *value = s; + s = NULL; + return 0; +} + +_public_ int sd_session_get_seat(const char *session, char **seat) { + return session_get_string(session, "SEAT", seat); +} + +_public_ int sd_session_get_tty(const char *session, char **tty) { + return session_get_string(session, "TTY", tty); +} + +_public_ int sd_session_get_vt(const char *session, unsigned *vtnr) { + _cleanup_free_ char *vtnr_string = NULL; + unsigned u; + int r; + + assert_return(vtnr, -EINVAL); + + r = session_get_string(session, "VTNR", &vtnr_string); + if (r < 0) + return r; + + r = safe_atou(vtnr_string, &u); + if (r < 0) + return r; + + *vtnr = u; + return 0; +} + +_public_ int sd_session_get_service(const char *session, char **service) { + return session_get_string(session, "SERVICE", service); +} + +_public_ int sd_session_get_type(const char *session, char **type) { + return session_get_string(session, "TYPE", type); +} + +_public_ int sd_session_get_class(const char *session, char **class) { + return session_get_string(session, "CLASS", class); +} + +_public_ int sd_session_get_desktop(const char *session, char **desktop) { + _cleanup_free_ char *escaped = NULL; + char *t; + int r; + + assert_return(desktop, -EINVAL); + + r = session_get_string(session, "DESKTOP", &escaped); + if (r < 0) + return r; + + r = cunescape(escaped, 0, &t); + if (r < 0) + return r; + + *desktop = t; + return 0; +} + +_public_ int sd_session_get_display(const char *session, char **display) { + return session_get_string(session, "DISPLAY", display); +} + +_public_ int sd_session_get_remote_user(const char *session, char **remote_user) { + return session_get_string(session, "REMOTE_USER", remote_user); +} + +_public_ int sd_session_get_remote_host(const char *session, char **remote_host) { + return session_get_string(session, "REMOTE_HOST", remote_host); +} + +_public_ int sd_seat_get_active(const char *seat, char **session, uid_t *uid) { + _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL; + int r; + + assert_return(session || uid, -EINVAL); + + r = file_of_seat(seat, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, + "ACTIVE", &s, + "ACTIVE_UID", &t, + NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + + if (session && !s) + return -ENODATA; + + if (uid && !t) + return -ENODATA; + + if (uid && t) { + r = parse_uid(t, uid); + if (r < 0) + return r; + } + + if (session && s) { + *session = s; + s = NULL; + } + + return 0; +} + +_public_ int sd_seat_get_sessions(const char *seat, char ***sessions, uid_t **uids, unsigned *n_uids) { + _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL; + _cleanup_strv_free_ char **a = NULL; + _cleanup_free_ uid_t *b = NULL; + unsigned n = 0; + int r; + + r = file_of_seat(seat, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, + "SESSIONS", &s, + "ACTIVE_SESSIONS", &t, + NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + + if (s) { + a = strv_split(s, " "); + if (!a) + return -ENOMEM; + } + + if (uids && t) { + const char *word, *state; + size_t l; + + FOREACH_WORD(word, l, t, state) + n++; + + if (n > 0) { + unsigned i = 0; + + b = new(uid_t, n); + if (!b) + return -ENOMEM; + + FOREACH_WORD(word, l, t, state) { + _cleanup_free_ char *k = NULL; + + k = strndup(word, l); + if (!k) + return -ENOMEM; + + r = parse_uid(k, b + i); + if (r < 0) + continue; + + i++; + } + } + } + + r = strv_length(a); + + if (sessions) { + *sessions = a; + a = NULL; + } + + if (uids) { + *uids = b; + b = NULL; + } + + if (n_uids) + *n_uids = n; + + return r; +} + +static int seat_get_can(const char *seat, const char *variable) { + _cleanup_free_ char *p = NULL, *s = NULL; + int r; + + assert(variable); + + r = file_of_seat(seat, &p); + if (r < 0) + return r; + + r = parse_env_file(p, NEWLINE, + variable, &s, + NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + return parse_boolean(s); +} + +_public_ int sd_seat_can_multi_session(const char *seat) { + return seat_get_can(seat, "CAN_MULTI_SESSION"); +} + +_public_ int sd_seat_can_tty(const char *seat) { + return seat_get_can(seat, "CAN_TTY"); +} + +_public_ int sd_seat_can_graphical(const char *seat) { + return seat_get_can(seat, "CAN_GRAPHICAL"); +} + +_public_ int sd_get_seats(char ***seats) { + return get_files_in_directory("/run/systemd/seats/", seats); +} + +_public_ int sd_get_sessions(char ***sessions) { + return get_files_in_directory("/run/systemd/sessions/", sessions); +} + +_public_ int sd_get_uids(uid_t **users) { + _cleanup_closedir_ DIR *d; + int r = 0; + unsigned n = 0; + _cleanup_free_ uid_t *l = NULL; + + d = opendir("/run/systemd/users/"); + if (!d) + return -errno; + + for (;;) { + struct dirent *de; + int k; + uid_t uid; + + errno = 0; + de = readdir(d); + if (!de && errno > 0) + return -errno; + + if (!de) + break; + + dirent_ensure_type(d, de); + + if (!dirent_is_file(de)) + continue; + + k = parse_uid(de->d_name, &uid); + if (k < 0) + continue; + + if (users) { + if ((unsigned) r >= n) { + uid_t *t; + + n = MAX(16, 2*r); + t = realloc(l, sizeof(uid_t) * n); + if (!t) + return -ENOMEM; + + l = t; + } + + assert((unsigned) r < n); + l[r++] = uid; + } else + r++; + } + + if (users) { + *users = l; + l = NULL; + } + + return r; +} + +_public_ int sd_get_machine_names(char ***machines) { + char **l = NULL, **a, **b; + int r; + + assert_return(machines, -EINVAL); + + r = get_files_in_directory("/run/systemd/machines/", &l); + if (r < 0) + return r; + + if (l) { + r = 0; + + /* Filter out the unit: symlinks */ + for (a = l, b = l; *a; a++) { + if (startswith(*a, "unit:") || !machine_name_is_valid(*a)) + free(*a); + else { + *b = *a; + b++; + r++; + } + } + + *b = NULL; + } + + *machines = l; + return r; +} + +_public_ int sd_machine_get_class(const char *machine, char **class) { + _cleanup_free_ char *c = NULL; + const char *p; + int r; + + assert_return(machine_name_is_valid(machine), -EINVAL); + assert_return(class, -EINVAL); + + p = strjoina("/run/systemd/machines/", machine); + r = parse_env_file(p, NEWLINE, "CLASS", &c, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (!c) + return -EIO; + + *class = c; + c = NULL; + + return 0; +} + +_public_ int sd_machine_get_ifindices(const char *machine, int **ifindices) { + _cleanup_free_ char *netif = NULL; + size_t l, allocated = 0, nr = 0; + int *ni = NULL; + const char *p, *word, *state; + int r; + + assert_return(machine_name_is_valid(machine), -EINVAL); + assert_return(ifindices, -EINVAL); + + p = strjoina("/run/systemd/machines/", machine); + r = parse_env_file(p, NEWLINE, "NETIF", &netif, NULL); + if (r == -ENOENT) + return -ENXIO; + if (r < 0) + return r; + if (!netif) { + *ifindices = NULL; + return 0; + } + + FOREACH_WORD(word, l, netif, state) { + char buf[l+1]; + int ifi; + + *(char*) (mempcpy(buf, word, l)) = 0; + + if (parse_ifindex(buf, &ifi) < 0) + continue; + + if (!GREEDY_REALLOC(ni, allocated, nr+1)) { + free(ni); + return -ENOMEM; + } + + ni[nr++] = ifi; + } + + *ifindices = ni; + return nr; +} + +static inline int MONITOR_TO_FD(sd_login_monitor *m) { + return (int) (unsigned long) m - 1; +} + +static inline sd_login_monitor* FD_TO_MONITOR(int fd) { + return (sd_login_monitor*) (unsigned long) (fd + 1); +} + +_public_ int sd_login_monitor_new(const char *category, sd_login_monitor **m) { + int fd, k; + bool good = false; + + assert_return(m, -EINVAL); + + fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (fd < 0) + return -errno; + + if (!category || streq(category, "seat")) { + k = inotify_add_watch(fd, "/run/systemd/seats/", IN_MOVED_TO|IN_DELETE); + if (k < 0) { + safe_close(fd); + return -errno; + } + + good = true; + } + + if (!category || streq(category, "session")) { + k = inotify_add_watch(fd, "/run/systemd/sessions/", IN_MOVED_TO|IN_DELETE); + if (k < 0) { + safe_close(fd); + return -errno; + } + + good = true; + } + + if (!category || streq(category, "uid")) { + k = inotify_add_watch(fd, "/run/systemd/users/", IN_MOVED_TO|IN_DELETE); + if (k < 0) { + safe_close(fd); + return -errno; + } + + good = true; + } + + if (!category || streq(category, "machine")) { + k = inotify_add_watch(fd, "/run/systemd/machines/", IN_MOVED_TO|IN_DELETE); + if (k < 0) { + safe_close(fd); + return -errno; + } + + good = true; + } + + if (!good) { + close_nointr(fd); + return -EINVAL; + } + + *m = FD_TO_MONITOR(fd); + return 0; +} + +_public_ sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m) { + int fd; + + if (!m) + return NULL; + + fd = MONITOR_TO_FD(m); + close_nointr(fd); + + return NULL; +} + +_public_ int sd_login_monitor_flush(sd_login_monitor *m) { + + assert_return(m, -EINVAL); + + return flush_fd(MONITOR_TO_FD(m)); +} + +_public_ int sd_login_monitor_get_fd(sd_login_monitor *m) { + + assert_return(m, -EINVAL); + + return MONITOR_TO_FD(m); +} + +_public_ int sd_login_monitor_get_events(sd_login_monitor *m) { + + assert_return(m, -EINVAL); + + /* For now we will only return POLLIN here, since we don't + * need anything else ever for inotify. However, let's have + * this API to keep our options open should we later on need + * it. */ + return POLLIN; +} + +_public_ int sd_login_monitor_get_timeout(sd_login_monitor *m, uint64_t *timeout_usec) { + + assert_return(m, -EINVAL); + assert_return(timeout_usec, -EINVAL); + + /* For now we will only return (uint64_t) -1, since we don't + * need any timeout. However, let's have this API to keep our + * options open should we later on need it. */ + *timeout_usec = (uint64_t) -1; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-login/test-login.c b/src/libsystemd/libsystemd-internal/sd-login/test-login.c new file mode 100644 index 0000000000..994c76df4a --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-login/test-login.c @@ -0,0 +1,264 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +static void test_login(void) { + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_free_ char *pp = NULL, *qq = NULL; + int r, k; + uid_t u, u2; + char *seat, *type, *class, *display, *remote_user, *remote_host, *display_session, *cgroup; + char *session; + char *state; + char *session2; + char *t; + char **seats, **sessions, **machines; + uid_t *uids; + unsigned n; + struct pollfd pollfd; + sd_login_monitor *m = NULL; + + assert_se(sd_pid_get_session(0, &session) == 0); + printf("session = %s\n", session); + + assert_se(sd_pid_get_owner_uid(0, &u2) == 0); + printf("user = "UID_FMT"\n", u2); + + assert_se(sd_pid_get_cgroup(0, &cgroup) == 0); + printf("cgroup = %s\n", cgroup); + free(cgroup); + + display_session = NULL; + r = sd_uid_get_display(u2, &display_session); + assert_se(r >= 0 || r == -ENODATA); + printf("user's display session = %s\n", strna(display_session)); + free(display_session); + + assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == 0); + sd_peer_get_session(pair[0], &pp); + sd_peer_get_session(pair[1], &qq); + assert_se(streq_ptr(pp, qq)); + + r = sd_uid_get_sessions(u2, false, &sessions); + assert_se(r >= 0); + assert_se(r == (int) strv_length(sessions)); + assert_se(t = strv_join(sessions, ", ")); + strv_free(sessions); + printf("sessions = %s\n", t); + free(t); + + assert_se(r == sd_uid_get_sessions(u2, false, NULL)); + + r = sd_uid_get_seats(u2, false, &seats); + assert_se(r >= 0); + assert_se(r == (int) strv_length(seats)); + assert_se(t = strv_join(seats, ", ")); + strv_free(seats); + printf("seats = %s\n", t); + free(t); + + assert_se(r == sd_uid_get_seats(u2, false, NULL)); + + r = sd_session_is_active(session); + assert_se(r >= 0); + printf("active = %s\n", yes_no(r)); + + r = sd_session_is_remote(session); + assert_se(r >= 0); + printf("remote = %s\n", yes_no(r)); + + r = sd_session_get_state(session, &state); + assert_se(r >= 0); + printf("state = %s\n", state); + free(state); + + assert_se(sd_session_get_uid(session, &u) >= 0); + printf("uid = "UID_FMT"\n", u); + assert_se(u == u2); + + assert_se(sd_session_get_type(session, &type) >= 0); + printf("type = %s\n", type); + free(type); + + assert_se(sd_session_get_class(session, &class) >= 0); + printf("class = %s\n", class); + free(class); + + display = NULL; + r = sd_session_get_display(session, &display); + assert_se(r >= 0 || r == -ENODATA); + printf("display = %s\n", strna(display)); + free(display); + + remote_user = NULL; + r = sd_session_get_remote_user(session, &remote_user); + assert_se(r >= 0 || r == -ENODATA); + printf("remote_user = %s\n", strna(remote_user)); + free(remote_user); + + remote_host = NULL; + r = sd_session_get_remote_host(session, &remote_host); + assert_se(r >= 0 || r == -ENODATA); + printf("remote_host = %s\n", strna(remote_host)); + free(remote_host); + + assert_se(sd_session_get_seat(session, &seat) >= 0); + printf("seat = %s\n", seat); + + r = sd_seat_can_multi_session(seat); + assert_se(r >= 0); + printf("can do multi session = %s\n", yes_no(r)); + + r = sd_seat_can_tty(seat); + assert_se(r >= 0); + printf("can do tty = %s\n", yes_no(r)); + + r = sd_seat_can_graphical(seat); + assert_se(r >= 0); + printf("can do graphical = %s\n", yes_no(r)); + + assert_se(sd_uid_get_state(u, &state) >= 0); + printf("state = %s\n", state); + + assert_se(sd_uid_is_on_seat(u, 0, seat) > 0); + + k = sd_uid_is_on_seat(u, 1, seat); + assert_se(k >= 0); + assert_se(!!r == !!r); + + assert_se(sd_seat_get_active(seat, &session2, &u2) >= 0); + printf("session2 = %s\n", session2); + printf("uid2 = "UID_FMT"\n", u2); + + r = sd_seat_get_sessions(seat, &sessions, &uids, &n); + assert_se(r >= 0); + printf("n_sessions = %i\n", r); + assert_se(r == (int) strv_length(sessions)); + assert_se(t = strv_join(sessions, ", ")); + strv_free(sessions); + printf("sessions = %s\n", t); + free(t); + printf("uids ="); + for (k = 0; k < (int) n; k++) + printf(" "UID_FMT, uids[k]); + printf("\n"); + free(uids); + + assert_se(sd_seat_get_sessions(seat, NULL, NULL, NULL) == r); + + free(session); + free(state); + free(session2); + free(seat); + + r = sd_get_seats(&seats); + assert_se(r >= 0); + assert_se(r == (int) strv_length(seats)); + assert_se(t = strv_join(seats, ", ")); + strv_free(seats); + printf("n_seats = %i\n", r); + printf("seats = %s\n", t); + free(t); + + assert_se(sd_get_seats(NULL) == r); + + r = sd_seat_get_active(NULL, &t, NULL); + assert_se(r >= 0); + printf("active session on current seat = %s\n", t); + free(t); + + r = sd_get_sessions(&sessions); + assert_se(r >= 0); + assert_se(r == (int) strv_length(sessions)); + assert_se(t = strv_join(sessions, ", ")); + strv_free(sessions); + printf("n_sessions = %i\n", r); + printf("sessions = %s\n", t); + free(t); + + assert_se(sd_get_sessions(NULL) == r); + + r = sd_get_uids(&uids); + assert_se(r >= 0); + + printf("uids ="); + for (k = 0; k < r; k++) + printf(" "UID_FMT, uids[k]); + printf("\n"); + free(uids); + + printf("n_uids = %i\n", r); + assert_se(sd_get_uids(NULL) == r); + + r = sd_get_machine_names(&machines); + assert_se(r >= 0); + assert_se(r == (int) strv_length(machines)); + assert_se(t = strv_join(machines, ", ")); + strv_free(machines); + printf("n_machines = %i\n", r); + printf("machines = %s\n", t); + free(t); + + r = sd_login_monitor_new("session", &m); + assert_se(r >= 0); + + for (n = 0; n < 5; n++) { + usec_t timeout, nw; + + zero(pollfd); + assert_se((pollfd.fd = sd_login_monitor_get_fd(m)) >= 0); + assert_se((pollfd.events = sd_login_monitor_get_events(m)) >= 0); + + assert_se(sd_login_monitor_get_timeout(m, &timeout) >= 0); + + nw = now(CLOCK_MONOTONIC); + + r = poll(&pollfd, 1, + timeout == (uint64_t) -1 ? -1 : + timeout > nw ? (int) ((timeout - nw) / 1000) : + 0); + + assert_se(r >= 0); + + sd_login_monitor_flush(m); + printf("Wake!\n"); + } + + sd_login_monitor_unref(m); +} + +int main(int argc, char* argv[]) { + log_parse_environment(); + log_open(); + + test_login(); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/local-addresses.c b/src/libsystemd/libsystemd-internal/sd-netlink/local-addresses.c new file mode 100644 index 0000000000..e6feb859cd --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/local-addresses.c @@ -0,0 +1,276 @@ +/*** + This file is part of systemd. + + Copyright 2008-2011 Lennart Poettering + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "local-addresses.h" +#include "macro.h" +#include "netlink-util.h" + +static int address_compare(const void *_a, const void *_b) { + const struct local_address *a = _a, *b = _b; + + /* Order lowest scope first, IPv4 before IPv6, lowest interface index first */ + + if (a->family == AF_INET && b->family == AF_INET6) + return -1; + if (a->family == AF_INET6 && b->family == AF_INET) + return 1; + + if (a->scope < b->scope) + return -1; + if (a->scope > b->scope) + return 1; + + if (a->metric < b->metric) + return -1; + if (a->metric > b->metric) + return 1; + + if (a->ifindex < b->ifindex) + return -1; + if (a->ifindex > b->ifindex) + return 1; + + return memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(a->family)); +} + +int local_addresses(sd_netlink *context, int ifindex, int af, struct local_address **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_free_ struct local_address *list = NULL; + size_t n_list = 0, n_allocated = 0; + sd_netlink_message *m; + int r; + + assert(ret); + + if (context) + rtnl = sd_netlink_ref(context); + else { + r = sd_netlink_open(&rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, 0, af); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (m = reply; m; m = sd_netlink_message_next(m)) { + struct local_address *a; + unsigned char flags; + uint16_t type; + int ifi, family; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + return r; + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + if (type != RTM_NEWADDR) + continue; + + r = sd_rtnl_message_addr_get_ifindex(m, &ifi); + if (r < 0) + return r; + if (ifindex > 0 && ifi != ifindex) + continue; + + r = sd_rtnl_message_addr_get_family(m, &family); + if (r < 0) + return r; + if (af != AF_UNSPEC && af != family) + continue; + + r = sd_rtnl_message_addr_get_flags(m, &flags); + if (r < 0) + return r; + if (flags & IFA_F_DEPRECATED) + continue; + + if (!GREEDY_REALLOC0(list, n_allocated, n_list+1)) + return -ENOMEM; + + a = list + n_list; + + r = sd_rtnl_message_addr_get_scope(m, &a->scope); + if (r < 0) + return r; + + if (ifindex == 0 && (a->scope == RT_SCOPE_HOST || a->scope == RT_SCOPE_NOWHERE)) + continue; + + switch (family) { + + case AF_INET: + r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a->address.in); + if (r < 0) { + r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a->address.in); + if (r < 0) + continue; + } + break; + + case AF_INET6: + r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a->address.in6); + if (r < 0) { + r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a->address.in6); + if (r < 0) + continue; + } + break; + + default: + continue; + } + + a->ifindex = ifi; + a->family = family; + + n_list++; + }; + + if (n_list > 0) + qsort(list, n_list, sizeof(struct local_address), address_compare); + + *ret = list; + list = NULL; + + return (int) n_list; +} + +int local_gateways(sd_netlink *context, int ifindex, int af, struct local_address **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_free_ struct local_address *list = NULL; + sd_netlink_message *m = NULL; + size_t n_list = 0, n_allocated = 0; + int r; + + assert(ret); + + if (context) + rtnl = sd_netlink_ref(context); + else { + r = sd_netlink_open(&rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_route(rtnl, &req, RTM_GETROUTE, af, RTPROT_UNSPEC); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (m = reply; m; m = sd_netlink_message_next(m)) { + struct local_address *a; + uint16_t type; + unsigned char dst_len, src_len; + uint32_t ifi; + int family; + + r = sd_netlink_message_get_errno(m); + if (r < 0) + return r; + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + if (type != RTM_NEWROUTE) + continue; + + /* We only care for default routes */ + r = sd_rtnl_message_route_get_dst_prefixlen(m, &dst_len); + if (r < 0) + return r; + if (dst_len != 0) + continue; + + r = sd_rtnl_message_route_get_src_prefixlen(m, &src_len); + if (r < 0) + return r; + if (src_len != 0) + continue; + + r = sd_netlink_message_read_u32(m, RTA_OIF, &ifi); + if (r < 0) + return r; + if (ifindex > 0 && (int) ifi != ifindex) + continue; + + r = sd_rtnl_message_route_get_family(m, &family); + if (r < 0) + return r; + if (af != AF_UNSPEC && af != family) + continue; + + if (!GREEDY_REALLOC0(list, n_allocated, n_list + 1)) + return -ENOMEM; + + a = list + n_list; + + switch (family) { + case AF_INET: + r = sd_netlink_message_read_in_addr(m, RTA_GATEWAY, &a->address.in); + if (r < 0) + continue; + + break; + case AF_INET6: + r = sd_netlink_message_read_in6_addr(m, RTA_GATEWAY, &a->address.in6); + if (r < 0) + continue; + + break; + default: + continue; + } + + sd_netlink_message_read_u32(m, RTA_PRIORITY, &a->metric); + + a->ifindex = ifi; + a->family = family; + + n_list++; + } + + if (n_list > 0) + qsort(list, n_list, sizeof(struct local_address), address_compare); + + *ret = list; + list = NULL; + + return (int) n_list; +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/local-addresses.h b/src/libsystemd/libsystemd-internal/sd-netlink/local-addresses.h new file mode 100644 index 0000000000..1ddc50ace5 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/local-addresses.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2008-2011 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 . +***/ + + +#include + +#include "in-addr-util.h" + +struct local_address { + int family, ifindex; + unsigned char scope; + uint32_t metric; + union in_addr_union address; +}; + +int local_addresses(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); + +int local_gateways(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-internal.h b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-internal.h new file mode 100644 index 0000000000..1d29c3a369 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-internal.h @@ -0,0 +1,137 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 . +***/ + +#include + +#include + +#include "list.h" +#include "netlink-types.h" +#include "prioq.h" +#include "refcnt.h" + +#define RTNL_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC)) + +#define RTNL_WQUEUE_MAX 1024 +#define RTNL_RQUEUE_MAX 64*1024 + +#define RTNL_CONTAINER_DEPTH 32 + +struct reply_callback { + sd_netlink_message_handler_t callback; + void *userdata; + usec_t timeout; + uint64_t serial; + unsigned prioq_idx; +}; + +struct match_callback { + sd_netlink_message_handler_t callback; + uint16_t type; + void *userdata; + + LIST_FIELDS(struct match_callback, match_callbacks); +}; + +struct sd_netlink { + RefCount n_ref; + + int fd; + + union { + struct sockaddr sa; + struct sockaddr_nl nl; + } sockaddr; + + Hashmap *broadcast_group_refs; + bool broadcast_group_dont_leave:1; /* until we can rely on 4.2 */ + + sd_netlink_message **rqueue; + unsigned rqueue_size; + size_t rqueue_allocated; + + sd_netlink_message **rqueue_partial; + unsigned rqueue_partial_size; + size_t rqueue_partial_allocated; + + struct nlmsghdr *rbuffer; + size_t rbuffer_allocated; + + bool processing:1; + + uint32_t serial; + + struct Prioq *reply_callbacks_prioq; + Hashmap *reply_callbacks; + + LIST_HEAD(struct match_callback, match_callbacks); + + pid_t original_pid; + + sd_event_source *io_event_source; + sd_event_source *time_event_source; + sd_event_source *exit_event_source; + sd_event *event; +}; + +struct netlink_attribute { + size_t offset; /* offset from hdr to attribute */ + bool nested:1; + bool net_byteorder:1; +}; + +struct netlink_container { + const struct NLTypeSystem *type_system; /* the type system of the container */ + size_t offset; /* offset from hdr to the start of the container */ + struct netlink_attribute *attributes; + unsigned short n_attributes; /* number of attributes in container */ +}; + +struct sd_netlink_message { + RefCount n_ref; + + sd_netlink *rtnl; + + struct nlmsghdr *hdr; + struct netlink_container containers[RTNL_CONTAINER_DEPTH]; + unsigned n_containers; /* number of containers */ + bool sealed:1; + bool broadcast:1; + + sd_netlink_message *next; /* next in a chain of multi-part messages */ +}; + +int message_new(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t type); +int message_new_empty(sd_netlink *rtnl, sd_netlink_message **ret); + +int socket_open(int family); +int socket_bind(sd_netlink *nl); +int socket_broadcast_group_ref(sd_netlink *nl, unsigned group); +int socket_broadcast_group_unref(sd_netlink *nl, unsigned group); +int socket_write_message(sd_netlink *nl, sd_netlink_message *m); +int socket_read_message(sd_netlink *nl); + +int rtnl_rqueue_make_room(sd_netlink *rtnl); +int rtnl_rqueue_partial_make_room(sd_netlink *rtnl); + +/* Make sure callbacks don't destroy the rtnl connection */ +#define NETLINK_DONT_DESTROY(rtnl) \ + _cleanup_(sd_netlink_unrefp) _unused_ sd_netlink *_dont_destroy_##rtnl = sd_netlink_ref(rtnl) diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-message.c b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-message.c new file mode 100644 index 0000000000..0c307661f1 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-message.c @@ -0,0 +1,964 @@ +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 . +***/ + +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "formats-util.h" +#include "missing.h" +#include "netlink-internal.h" +#include "netlink-types.h" +#include "netlink-util.h" +#include "refcnt.h" +#include "socket-util.h" +#include "util.h" + +#define GET_CONTAINER(m, i) ((i) < (m)->n_containers ? (struct rtattr*)((uint8_t*)(m)->hdr + (m)->containers[i].offset) : NULL) +#define PUSH_CONTAINER(m, new) (m)->container_offsets[(m)->n_containers ++] = (uint8_t*)(new) - (uint8_t*)(m)->hdr; + +#define RTA_TYPE(rta) ((rta)->rta_type & NLA_TYPE_MASK) +#define RTA_FLAGS(rta) ((rta)->rta_type & ~NLA_TYPE_MASK) + +int message_new_empty(sd_netlink *rtnl, sd_netlink_message **ret) { + sd_netlink_message *m; + + assert_return(ret, -EINVAL); + + /* Note that 'rtnl' is currently unused, if we start using it internally + we must take care to avoid problems due to mutual references between + buses and their queued messages. See sd-bus. + */ + + m = new0(sd_netlink_message, 1); + if (!m) + return -ENOMEM; + + m->n_ref = REFCNT_INIT; + + m->sealed = false; + + *ret = m; + + return 0; +} + +int message_new(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t type) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + const NLType *nl_type; + size_t size; + int r; + + r = type_system_get_type(&type_system_root, &nl_type, type); + if (r < 0) + return r; + + if (type_get_type(nl_type) != NETLINK_TYPE_NESTED) + return -EINVAL; + + r = message_new_empty(rtnl, &m); + if (r < 0) + return r; + + size = NLMSG_SPACE(type_get_size(nl_type)); + + assert(size >= sizeof(struct nlmsghdr)); + m->hdr = malloc0(size); + if (!m->hdr) + return -ENOMEM; + + m->hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + type_get_type_system(nl_type, &m->containers[0].type_system); + m->hdr->nlmsg_len = size; + m->hdr->nlmsg_type = type; + + *ret = m; + m = NULL; + + return 0; +} + +int sd_netlink_message_request_dump(sd_netlink_message *m, int dump) { + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(m->hdr->nlmsg_type == RTM_GETLINK || + m->hdr->nlmsg_type == RTM_GETADDR || + m->hdr->nlmsg_type == RTM_GETROUTE || + m->hdr->nlmsg_type == RTM_GETNEIGH, + -EINVAL); + + if (dump) + m->hdr->nlmsg_flags |= NLM_F_DUMP; + else + m->hdr->nlmsg_flags &= ~NLM_F_DUMP; + + return 0; +} + +sd_netlink_message *sd_netlink_message_ref(sd_netlink_message *m) { + if (m) + assert_se(REFCNT_INC(m->n_ref) >= 2); + + return m; +} + +sd_netlink_message *sd_netlink_message_unref(sd_netlink_message *m) { + if (m && REFCNT_DEC(m->n_ref) == 0) { + unsigned i; + + free(m->hdr); + + for (i = 0; i <= m->n_containers; i++) + free(m->containers[i].attributes); + + sd_netlink_message_unref(m->next); + + free(m); + } + + return NULL; +} + +int sd_netlink_message_get_type(sd_netlink_message *m, uint16_t *type) { + assert_return(m, -EINVAL); + assert_return(type, -EINVAL); + + *type = m->hdr->nlmsg_type; + + return 0; +} + +int sd_netlink_message_set_flags(sd_netlink_message *m, uint16_t flags) { + assert_return(m, -EINVAL); + assert_return(flags, -EINVAL); + + m->hdr->nlmsg_flags = flags; + + return 0; +} + +int sd_netlink_message_is_broadcast(sd_netlink_message *m) { + assert_return(m, -EINVAL); + + return m->broadcast; +} + +/* If successful the updated message will be correctly aligned, if + unsuccessful the old message is untouched. */ +static int add_rtattr(sd_netlink_message *m, unsigned short type, const void *data, size_t data_length) { + uint32_t rta_length; + size_t message_length, padding_length; + struct nlmsghdr *new_hdr; + struct rtattr *rta; + char *padding; + unsigned i; + int offset; + + assert(m); + assert(m->hdr); + assert(!m->sealed); + assert(NLMSG_ALIGN(m->hdr->nlmsg_len) == m->hdr->nlmsg_len); + assert(!data || data_length); + + /* get offset of the new attribute */ + offset = m->hdr->nlmsg_len; + + /* get the size of the new rta attribute (with padding at the end) */ + rta_length = RTA_LENGTH(data_length); + + /* get the new message size (with padding at the end) */ + message_length = offset + RTA_ALIGN(rta_length); + + /* realloc to fit the new attribute */ + new_hdr = realloc(m->hdr, message_length); + if (!new_hdr) + return -ENOMEM; + m->hdr = new_hdr; + + /* get pointer to the attribute we are about to add */ + rta = (struct rtattr *) ((uint8_t *) m->hdr + offset); + + /* if we are inside containers, extend them */ + for (i = 0; i < m->n_containers; i++) + GET_CONTAINER(m, i)->rta_len += message_length - offset; + + /* fill in the attribute */ + rta->rta_type = type; + rta->rta_len = rta_length; + if (data) + /* we don't deal with the case where the user lies about the type + * and gives us too little data (so don't do that) + */ + padding = mempcpy(RTA_DATA(rta), data, data_length); + else { + /* if no data was passed, make sure we still initialize the padding + note that we can have data_length > 0 (used by some containers) */ + padding = RTA_DATA(rta); + } + + /* make sure also the padding at the end of the message is initialized */ + padding_length = (uint8_t*)m->hdr + message_length - (uint8_t*)padding; + memzero(padding, padding_length); + + /* update message size */ + m->hdr->nlmsg_len = message_length; + + return offset; +} + +static int message_attribute_has_type(sd_netlink_message *m, size_t *out_size, uint16_t attribute_type, uint16_t data_type) { + const NLType *type; + int r; + + assert(m); + + r = type_system_get_type(m->containers[m->n_containers].type_system, &type, attribute_type); + if (r < 0) + return r; + + if (type_get_type(type) != data_type) + return -EINVAL; + + if (out_size) + *out_size = type_get_size(type); + return 0; +} + +int sd_netlink_message_append_string(sd_netlink_message *m, unsigned short type, const char *data) { + size_t length, size; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_STRING); + if (r < 0) + return r; + + if (size) { + length = strnlen(data, size+1); + if (length > size) + return -EINVAL; + } else + length = strlen(data); + + r = add_rtattr(m, type, data, length + 1); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_flag(sd_netlink_message *m, unsigned short type) { + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_FLAG); + if (r < 0) + return r; + + r = add_rtattr(m, type, NULL, 0); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_u8(sd_netlink_message *m, unsigned short type, uint8_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U8); + if (r < 0) + return r; + + r = add_rtattr(m, type, &data, sizeof(uint8_t)); + if (r < 0) + return r; + + return 0; +} + + +int sd_netlink_message_append_u16(sd_netlink_message *m, unsigned short type, uint16_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U16); + if (r < 0) + return r; + + r = add_rtattr(m, type, &data, sizeof(uint16_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, uint32_t data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U32); + if (r < 0) + return r; + + r = add_rtattr(m, type, &data, sizeof(uint32_t)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = add_rtattr(m, type, &data, len); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_in_addr(sd_netlink_message *m, unsigned short type, const struct in_addr *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = add_rtattr(m, type, data, sizeof(struct in_addr)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_in6_addr(sd_netlink_message *m, unsigned short type, const struct in6_addr *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = add_rtattr(m, type, data, sizeof(struct in6_addr)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_ether_addr(sd_netlink_message *m, unsigned short type, const struct ether_addr *data) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(data, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_ETHER_ADDR); + if (r < 0) + return r; + + r = add_rtattr(m, type, data, ETH_ALEN); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_append_cache_info(sd_netlink_message *m, unsigned short type, const struct ifa_cacheinfo *info) { + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(info, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_CACHE_INFO); + if (r < 0) + return r; + + r = add_rtattr(m, type, info, sizeof(struct ifa_cacheinfo)); + if (r < 0) + return r; + + return 0; +} + +int sd_netlink_message_open_container(sd_netlink_message *m, unsigned short type) { + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(m->n_containers < RTNL_CONTAINER_DEPTH, -ERANGE); + + r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_NESTED); + if (r < 0) { + const NLTypeSystemUnion *type_system_union; + int family; + + r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_UNION); + if (r < 0) + return r; + + r = sd_rtnl_message_get_family(m, &family); + if (r < 0) + return r; + + r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, &type_system_union, type); + if (r < 0) + return r; + + r = type_system_union_protocol_get_type_system(type_system_union, + &m->containers[m->n_containers + 1].type_system, + family); + if (r < 0) + return r; + } else { + r = type_system_get_type_system(m->containers[m->n_containers].type_system, + &m->containers[m->n_containers + 1].type_system, + type); + if (r < 0) + return r; + } + + r = add_rtattr(m, type | NLA_F_NESTED, NULL, size); + if (r < 0) + return r; + + m->containers[m->n_containers ++].offset = r; + + return 0; +} + +int sd_netlink_message_open_container_union(sd_netlink_message *m, unsigned short type, const char *key) { + const NLTypeSystemUnion *type_system_union; + int r; + + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + + r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, &type_system_union, type); + if (r < 0) + return r; + + r = type_system_union_get_type_system(type_system_union, + &m->containers[m->n_containers + 1].type_system, + key); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(m, type_system_union->match, key); + if (r < 0) + return r; + + /* do we evere need non-null size */ + r = add_rtattr(m, type | NLA_F_NESTED, NULL, 0); + if (r < 0) + return r; + + m->containers[m->n_containers ++].offset = r; + + return 0; +} + + +int sd_netlink_message_close_container(sd_netlink_message *m) { + assert_return(m, -EINVAL); + assert_return(!m->sealed, -EPERM); + assert_return(m->n_containers > 0, -EINVAL); + + m->containers[m->n_containers].type_system = NULL; + m->n_containers --; + + return 0; +} + +static int netlink_message_read_internal(sd_netlink_message *m, unsigned short type, void **data, bool *net_byteorder) { + struct netlink_attribute *attribute; + struct rtattr *rta; + + assert_return(m, -EINVAL); + assert_return(m->sealed, -EPERM); + assert_return(data, -EINVAL); + assert(m->n_containers < RTNL_CONTAINER_DEPTH); + assert(m->containers[m->n_containers].attributes); + assert(type < m->containers[m->n_containers].n_attributes); + + attribute = &m->containers[m->n_containers].attributes[type]; + + if(!attribute->offset) + return -ENODATA; + + rta = (struct rtattr*)((uint8_t *) m->hdr + attribute->offset); + + *data = RTA_DATA(rta); + + if (net_byteorder) + *net_byteorder = attribute->net_byteorder; + + return RTA_PAYLOAD(rta); +} + +int sd_netlink_message_read_string(sd_netlink_message *m, unsigned short type, const char **data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_STRING); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if (strnlen(attr_data, r) >= (size_t) r) + return -EIO; + + if (data) + *data = (const char *) attr_data; + + return 0; +} + +int sd_netlink_message_read_u8(sd_netlink_message *m, unsigned short type, uint8_t *data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U8); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t) r < sizeof(uint8_t)) + return -EIO; + + if (data) + *data = *(uint8_t *) attr_data; + + return 0; +} + +int sd_netlink_message_read_u16(sd_netlink_message *m, unsigned short type, uint16_t *data) { + void *attr_data; + bool net_byteorder; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U16); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, &net_byteorder); + if (r < 0) + return r; + else if ((size_t) r < sizeof(uint16_t)) + return -EIO; + + if (data) { + if (net_byteorder) + *data = be16toh(*(uint16_t *) attr_data); + else + *data = *(uint16_t *) attr_data; + } + + return 0; +} + +int sd_netlink_message_read_u32(sd_netlink_message *m, unsigned short type, uint32_t *data) { + void *attr_data; + bool net_byteorder; + int r; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U32); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, &net_byteorder); + if (r < 0) + return r; + else if ((size_t)r < sizeof(uint32_t)) + return -EIO; + + if (data) { + if (net_byteorder) + *data = be32toh(*(uint32_t *) attr_data); + else + *data = *(uint32_t *) attr_data; + } + + return 0; +} + +int sd_netlink_message_read_ether_addr(sd_netlink_message *m, unsigned short type, struct ether_addr *data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_ETHER_ADDR); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t)r < sizeof(struct ether_addr)) + return -EIO; + + if (data) + memcpy(data, attr_data, sizeof(struct ether_addr)); + + return 0; +} + +int sd_netlink_message_read_cache_info(sd_netlink_message *m, unsigned short type, struct ifa_cacheinfo *info) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_CACHE_INFO); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t)r < sizeof(struct ifa_cacheinfo)) + return -EIO; + + if (info) + memcpy(info, attr_data, sizeof(struct ifa_cacheinfo)); + + return 0; +} + +int sd_netlink_message_read_in_addr(sd_netlink_message *m, unsigned short type, struct in_addr *data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t)r < sizeof(struct in_addr)) + return -EIO; + + if (data) + memcpy(data, attr_data, sizeof(struct in_addr)); + + return 0; +} + +int sd_netlink_message_read_in6_addr(sd_netlink_message *m, unsigned short type, struct in6_addr *data) { + int r; + void *attr_data; + + assert_return(m, -EINVAL); + + r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); + if (r < 0) + return r; + + r = netlink_message_read_internal(m, type, &attr_data, NULL); + if (r < 0) + return r; + else if ((size_t)r < sizeof(struct in6_addr)) + return -EIO; + + if (data) + memcpy(data, attr_data, sizeof(struct in6_addr)); + + return 0; +} + +static int netlink_container_parse(sd_netlink_message *m, + struct netlink_container *container, + int count, + struct rtattr *rta, + unsigned int rt_len) { + _cleanup_free_ struct netlink_attribute *attributes = NULL; + + attributes = new0(struct netlink_attribute, count); + if(!attributes) + return -ENOMEM; + + for (; RTA_OK(rta, rt_len); rta = RTA_NEXT(rta, rt_len)) { + unsigned short type; + + type = RTA_TYPE(rta); + + /* if the kernel is newer than the headers we used + when building, we ignore out-of-range attributes */ + if (type >= count) + continue; + + if (attributes[type].offset) + log_debug("rtnl: message parse - overwriting repeated attribute"); + + attributes[type].offset = (uint8_t *) rta - (uint8_t *) m->hdr; + attributes[type].nested = RTA_FLAGS(rta) & NLA_F_NESTED; + attributes[type].net_byteorder = RTA_FLAGS(rta) & NLA_F_NET_BYTEORDER; + } + + container->attributes = attributes; + attributes = NULL; + container->n_attributes = count; + + return 0; +} + +int sd_netlink_message_enter_container(sd_netlink_message *m, unsigned short type_id) { + const NLType *nl_type; + const NLTypeSystem *type_system; + void *container; + uint16_t type; + size_t size; + int r; + + assert_return(m, -EINVAL); + assert_return(m->n_containers < RTNL_CONTAINER_DEPTH, -EINVAL); + + r = type_system_get_type(m->containers[m->n_containers].type_system, + &nl_type, + type_id); + if (r < 0) + return r; + + type = type_get_type(nl_type); + + if (type == NETLINK_TYPE_NESTED) { + r = type_system_get_type_system(m->containers[m->n_containers].type_system, + &type_system, + type_id); + if (r < 0) + return r; + } else if (type == NETLINK_TYPE_UNION) { + const NLTypeSystemUnion *type_system_union; + + r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, + &type_system_union, + type_id); + if (r < 0) + return r; + + switch (type_system_union->match_type) { + case NL_MATCH_SIBLING: + { + const char *key; + + r = sd_netlink_message_read_string(m, type_system_union->match, &key); + if (r < 0) + return r; + + r = type_system_union_get_type_system(type_system_union, + &type_system, + key); + if (r < 0) + return r; + + break; + } + case NL_MATCH_PROTOCOL: + { + int family; + + r = sd_rtnl_message_get_family(m, &family); + if (r < 0) + return r; + + r = type_system_union_protocol_get_type_system(type_system_union, + &type_system, + family); + if (r < 0) + return r; + + break; + } + default: + assert_not_reached("sd-netlink: invalid type system union type"); + } + } else + return -EINVAL; + + r = netlink_message_read_internal(m, type_id, &container, NULL); + if (r < 0) + return r; + else + size = (size_t)r; + + m->n_containers ++; + + r = netlink_container_parse(m, + &m->containers[m->n_containers], + type_system_get_count(type_system), + container, + size); + if (r < 0) { + m->n_containers --; + return r; + } + + m->containers[m->n_containers].type_system = type_system; + + return 0; +} + +int sd_netlink_message_exit_container(sd_netlink_message *m) { + assert_return(m, -EINVAL); + assert_return(m->sealed, -EINVAL); + assert_return(m->n_containers > 0, -EINVAL); + + m->containers[m->n_containers].attributes = mfree(m->containers[m->n_containers].attributes); + m->containers[m->n_containers].type_system = NULL; + + m->n_containers --; + + return 0; +} + +uint32_t rtnl_message_get_serial(sd_netlink_message *m) { + assert(m); + assert(m->hdr); + + return m->hdr->nlmsg_seq; +} + +int sd_netlink_message_is_error(sd_netlink_message *m) { + assert_return(m, 0); + assert_return(m->hdr, 0); + + return m->hdr->nlmsg_type == NLMSG_ERROR; +} + +int sd_netlink_message_get_errno(sd_netlink_message *m) { + struct nlmsgerr *err; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + + if (!sd_netlink_message_is_error(m)) + return 0; + + err = NLMSG_DATA(m->hdr); + + return err->error; +} + +int sd_netlink_message_rewind(sd_netlink_message *m) { + const NLType *nl_type; + uint16_t type; + size_t size; + unsigned i; + int r; + + assert_return(m, -EINVAL); + + /* don't allow appending to message once parsed */ + if (!m->sealed) + rtnl_message_seal(m); + + for (i = 1; i <= m->n_containers; i++) + m->containers[i].attributes = mfree(m->containers[i].attributes); + + m->n_containers = 0; + + if (m->containers[0].attributes) + /* top-level attributes have already been parsed */ + return 0; + + assert(m->hdr); + + r = type_system_get_type(&type_system_root, &nl_type, m->hdr->nlmsg_type); + if (r < 0) + return r; + + type = type_get_type(nl_type); + size = type_get_size(nl_type); + + if (type == NETLINK_TYPE_NESTED) { + const NLTypeSystem *type_system; + + type_get_type_system(nl_type, &type_system); + + m->containers[0].type_system = type_system; + + r = netlink_container_parse(m, + &m->containers[m->n_containers], + type_system_get_count(type_system), + (struct rtattr*)((uint8_t*)NLMSG_DATA(m->hdr) + NLMSG_ALIGN(size)), + NLMSG_PAYLOAD(m->hdr, size)); + if (r < 0) + return r; + } + + return 0; +} + +void rtnl_message_seal(sd_netlink_message *m) { + assert(m); + assert(!m->sealed); + + m->sealed = true; +} + +sd_netlink_message *sd_netlink_message_next(sd_netlink_message *m) { + assert_return(m, NULL); + + return m->next; +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-socket.c b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-socket.c new file mode 100644 index 0000000000..25b7509217 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-socket.c @@ -0,0 +1,474 @@ +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 . +***/ + +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "formats-util.h" +#include "missing.h" +#include "netlink-internal.h" +#include "netlink-types.h" +#include "netlink-util.h" +#include "refcnt.h" +#include "socket-util.h" +#include "util.h" + +int socket_open(int family) { + int fd; + + fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, family); + if (fd < 0) + return -errno; + + return fd; +} + +static int broadcast_groups_get(sd_netlink *nl) { + _cleanup_free_ uint32_t *groups = NULL; + socklen_t len = 0, old_len; + unsigned i, j; + int r; + + assert(nl); + assert(nl->fd >= 0); + + r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, NULL, &len); + if (r < 0) { + if (errno == ENOPROTOOPT) { + nl->broadcast_group_dont_leave = true; + return 0; + } else + return -errno; + } + + if (len == 0) + return 0; + + groups = new0(uint32_t, len); + if (!groups) + return -ENOMEM; + + old_len = len; + + r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, groups, &len); + if (r < 0) + return -errno; + + if (old_len != len) + return -EIO; + + r = hashmap_ensure_allocated(&nl->broadcast_group_refs, NULL); + if (r < 0) + return r; + + for (i = 0; i < len; i++) { + for (j = 0; j < sizeof(uint32_t) * 8; j ++) { + uint32_t offset; + unsigned group; + + offset = 1U << j; + + if (!(groups[i] & offset)) + continue; + + group = i * sizeof(uint32_t) * 8 + j + 1; + + r = hashmap_put(nl->broadcast_group_refs, UINT_TO_PTR(group), UINT_TO_PTR(1)); + if (r < 0) + return r; + } + } + + return 0; +} + +int socket_bind(sd_netlink *nl) { + socklen_t addrlen; + int r, one = 1; + + r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one)); + if (r < 0) + return -errno; + + addrlen = sizeof(nl->sockaddr); + + r = bind(nl->fd, &nl->sockaddr.sa, addrlen); + /* ignore EINVAL to allow opening an already bound socket */ + if (r < 0 && errno != EINVAL) + return -errno; + + r = getsockname(nl->fd, &nl->sockaddr.sa, &addrlen); + if (r < 0) + return -errno; + + r = broadcast_groups_get(nl); + if (r < 0) + return r; + + return 0; +} + +static unsigned broadcast_group_get_ref(sd_netlink *nl, unsigned group) { + assert(nl); + + return PTR_TO_UINT(hashmap_get(nl->broadcast_group_refs, UINT_TO_PTR(group))); +} + +static int broadcast_group_set_ref(sd_netlink *nl, unsigned group, unsigned n_ref) { + int r; + + assert(nl); + + r = hashmap_replace(nl->broadcast_group_refs, UINT_TO_PTR(group), UINT_TO_PTR(n_ref)); + if (r < 0) + return r; + + return 0; +} + +static int broadcast_group_join(sd_netlink *nl, unsigned group) { + int r; + + assert(nl); + assert(nl->fd >= 0); + assert(group > 0); + + r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)); + if (r < 0) + return -errno; + + return 0; +} + +int socket_broadcast_group_ref(sd_netlink *nl, unsigned group) { + unsigned n_ref; + int r; + + assert(nl); + + n_ref = broadcast_group_get_ref(nl, group); + + n_ref ++; + + r = hashmap_ensure_allocated(&nl->broadcast_group_refs, NULL); + if (r < 0) + return r; + + r = broadcast_group_set_ref(nl, group, n_ref); + if (r < 0) + return r; + + if (n_ref > 1) + /* not yet in the group */ + return 0; + + r = broadcast_group_join(nl, group); + if (r < 0) + return r; + + return 0; +} + +static int broadcast_group_leave(sd_netlink *nl, unsigned group) { + int r; + + assert(nl); + assert(nl->fd >= 0); + assert(group > 0); + + if (nl->broadcast_group_dont_leave) + return 0; + + r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP, &group, sizeof(group)); + if (r < 0) + return -errno; + + return 0; +} + +int socket_broadcast_group_unref(sd_netlink *nl, unsigned group) { + unsigned n_ref; + int r; + + assert(nl); + + n_ref = broadcast_group_get_ref(nl, group); + + assert(n_ref > 0); + + n_ref --; + + r = broadcast_group_set_ref(nl, group, n_ref); + if (r < 0) + return r; + + if (n_ref > 0) + /* still refs left */ + return 0; + + r = broadcast_group_leave(nl, group); + if (r < 0) + return r; + + return 0; +} + +/* returns the number of bytes sent, or a negative error code */ +int socket_write_message(sd_netlink *nl, sd_netlink_message *m) { + union { + struct sockaddr sa; + struct sockaddr_nl nl; + } addr = { + .nl.nl_family = AF_NETLINK, + }; + ssize_t k; + + assert(nl); + assert(m); + assert(m->hdr); + + k = sendto(nl->fd, m->hdr, m->hdr->nlmsg_len, + 0, &addr.sa, sizeof(addr)); + if (k < 0) + return -errno; + + return k; +} + +static int socket_recv_message(int fd, struct iovec *iov, uint32_t *_group, bool peek) { + union sockaddr_union sender; + uint8_t cmsg_buffer[CMSG_SPACE(sizeof(struct nl_pktinfo))]; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = 1, + .msg_name = &sender, + .msg_namelen = sizeof(sender), + .msg_control = cmsg_buffer, + .msg_controllen = sizeof(cmsg_buffer), + }; + struct cmsghdr *cmsg; + uint32_t group = 0; + int r; + + assert(fd >= 0); + assert(iov); + + r = recvmsg(fd, &msg, MSG_TRUNC | (peek ? MSG_PEEK : 0)); + if (r < 0) { + /* no data */ + if (errno == ENOBUFS) + log_debug("rtnl: kernel receive buffer overrun"); + else if (errno == EAGAIN) + log_debug("rtnl: no data in socket"); + + return (errno == EAGAIN || errno == EINTR) ? 0 : -errno; + } + + if (sender.nl.nl_pid != 0) { + /* not from the kernel, ignore */ + log_debug("rtnl: ignoring message from portid %"PRIu32, sender.nl.nl_pid); + + if (peek) { + /* drop the message */ + r = recvmsg(fd, &msg, 0); + if (r < 0) + return (errno == EAGAIN || errno == EINTR) ? 0 : -errno; + } + + return 0; + } + + CMSG_FOREACH(cmsg, &msg) { + if (cmsg->cmsg_level == SOL_NETLINK && + cmsg->cmsg_type == NETLINK_PKTINFO && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct nl_pktinfo))) { + struct nl_pktinfo *pktinfo = (void *)CMSG_DATA(cmsg); + + /* multi-cast group */ + group = pktinfo->group; + } + } + + if (_group) + *_group = group; + + return r; +} + +/* On success, the number of bytes received is returned and *ret points to the received message + * which has a valid header and the correct size. + * If nothing useful was received 0 is returned. + * On failure, a negative error code is returned. + */ +int socket_read_message(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *first = NULL; + struct iovec iov = {}; + uint32_t group = 0; + bool multi_part = false, done = false; + struct nlmsghdr *new_msg; + size_t len; + int r; + unsigned i = 0; + + assert(rtnl); + assert(rtnl->rbuffer); + assert(rtnl->rbuffer_allocated >= sizeof(struct nlmsghdr)); + + /* read nothing, just get the pending message size */ + r = socket_recv_message(rtnl->fd, &iov, NULL, true); + if (r <= 0) + return r; + else + len = (size_t)r; + + /* make room for the pending message */ + if (!greedy_realloc((void **)&rtnl->rbuffer, + &rtnl->rbuffer_allocated, + len, sizeof(uint8_t))) + return -ENOMEM; + + iov.iov_base = rtnl->rbuffer; + iov.iov_len = rtnl->rbuffer_allocated; + + /* read the pending message */ + r = socket_recv_message(rtnl->fd, &iov, &group, false); + if (r <= 0) + return r; + else + len = (size_t)r; + + if (len > rtnl->rbuffer_allocated) + /* message did not fit in read buffer */ + return -EIO; + + if (NLMSG_OK(rtnl->rbuffer, len) && rtnl->rbuffer->nlmsg_flags & NLM_F_MULTI) { + multi_part = true; + + for (i = 0; i < rtnl->rqueue_partial_size; i++) { + if (rtnl_message_get_serial(rtnl->rqueue_partial[i]) == + rtnl->rbuffer->nlmsg_seq) { + first = rtnl->rqueue_partial[i]; + break; + } + } + } + + for (new_msg = rtnl->rbuffer; NLMSG_OK(new_msg, len) && !done; new_msg = NLMSG_NEXT(new_msg, len)) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + const NLType *nl_type; + + if (!group && new_msg->nlmsg_pid != rtnl->sockaddr.nl.nl_pid) + /* not broadcast and not for us */ + continue; + + if (new_msg->nlmsg_type == NLMSG_NOOP) + /* silently drop noop messages */ + continue; + + if (new_msg->nlmsg_type == NLMSG_DONE) { + /* finished reading multi-part message */ + done = true; + + /* if first is not defined, put NLMSG_DONE into the receive queue. */ + if (first) + continue; + } + + /* check that we support this message type */ + r = type_system_get_type(&type_system_root, &nl_type, new_msg->nlmsg_type); + if (r < 0) { + if (r == -EOPNOTSUPP) + log_debug("sd-netlink: ignored message with unknown type: %i", + new_msg->nlmsg_type); + + continue; + } + + /* check that the size matches the message type */ + if (new_msg->nlmsg_len < NLMSG_LENGTH(type_get_size(nl_type))) { + log_debug("sd-netlink: message larger than expected, dropping"); + continue; + } + + r = message_new_empty(rtnl, &m); + if (r < 0) + return r; + + m->broadcast = !!group; + + m->hdr = memdup(new_msg, new_msg->nlmsg_len); + if (!m->hdr) + return -ENOMEM; + + /* seal and parse the top-level message */ + r = sd_netlink_message_rewind(m); + if (r < 0) + return r; + + /* push the message onto the multi-part message stack */ + if (first) + m->next = first; + first = m; + m = NULL; + } + + if (len) + log_debug("sd-netlink: discarding %zu bytes of incoming message", len); + + if (!first) + return 0; + + if (!multi_part || done) { + /* we got a complete message, push it on the read queue */ + r = rtnl_rqueue_make_room(rtnl); + if (r < 0) + return r; + + rtnl->rqueue[rtnl->rqueue_size ++] = first; + first = NULL; + + if (multi_part && (i < rtnl->rqueue_partial_size)) { + /* remove the message form the partial read queue */ + memmove(rtnl->rqueue_partial + i,rtnl->rqueue_partial + i + 1, + sizeof(sd_netlink_message*) * (rtnl->rqueue_partial_size - i - 1)); + rtnl->rqueue_partial_size --; + } + + return 1; + } else { + /* we only got a partial multi-part message, push it on the + partial read queue */ + if (i < rtnl->rqueue_partial_size) { + rtnl->rqueue_partial[i] = first; + } else { + r = rtnl_rqueue_partial_make_room(rtnl); + if (r < 0) + return r; + + rtnl->rqueue_partial[rtnl->rqueue_partial_size ++] = first; + } + first = NULL; + + return 0; + } +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.c b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.c new file mode 100644 index 0000000000..a5758bb516 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.c @@ -0,0 +1,653 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "macro.h" +#include "missing.h" +#include "netlink-types.h" +#include "string-table.h" +#include "util.h" + +/* Maximum ARP IP target defined in kernel */ +#define BOND_MAX_ARP_TARGETS 16 + +typedef enum { + BOND_ARP_TARGETS_0, + BOND_ARP_TARGETS_1, + BOND_ARP_TARGETS_2, + BOND_ARP_TARGETS_3, + BOND_ARP_TARGETS_4, + BOND_ARP_TARGETS_5, + BOND_ARP_TARGETS_6, + BOND_ARP_TARGETS_7, + BOND_ARP_TARGETS_8, + BOND_ARP_TARGETS_9, + BOND_ARP_TARGETS_10, + BOND_ARP_TARGETS_11, + BOND_ARP_TARGETS_12, + BOND_ARP_TARGETS_13, + BOND_ARP_TARGETS_14, + BOND_ARP_TARGETS_MAX = BOND_MAX_ARP_TARGETS, +} BondArpTargets; + +struct NLType { + uint16_t type; + size_t size; + const NLTypeSystem *type_system; + const NLTypeSystemUnion *type_system_union; +}; + +struct NLTypeSystem { + uint16_t count; + const NLType *types; +}; + +static const NLTypeSystem rtnl_link_type_system; + +static const NLType empty_types[1] = { + /* fake array to avoid .types==NULL, which denotes invalid type-systems */ +}; + +static const NLTypeSystem empty_type_system = { + .count = 0, + .types = empty_types, +}; + +static const NLType rtnl_link_info_data_veth_types[] = { + [VETH_INFO_PEER] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, +}; + +static const NLType rtnl_link_info_data_ipvlan_types[] = { + [IFLA_IPVLAN_MODE] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_macvlan_types[] = { + [IFLA_MACVLAN_MODE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_MACVLAN_FLAGS] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_bridge_types[] = { + [IFLA_BR_FORWARD_DELAY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_HELLO_TIME] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_MAX_AGE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_AGEING_TIME] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_STP_STATE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BR_PRIORITY] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_vlan_types[] = { + [IFLA_VLAN_ID] = { .type = NETLINK_TYPE_U16 }, +/* + [IFLA_VLAN_FLAGS] = { .len = sizeof(struct ifla_vlan_flags) }, + [IFLA_VLAN_EGRESS_QOS] = { .type = NETLINK_TYPE_NESTED }, + [IFLA_VLAN_INGRESS_QOS] = { .type = NETLINK_TYPE_NESTED }, +*/ + [IFLA_VLAN_PROTOCOL] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_vxlan_types[] = { + [IFLA_VXLAN_ID] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VXLAN_GROUP] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VXLAN_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VXLAN_LOCAL] = { .type = NETLINK_TYPE_U32}, + [IFLA_VXLAN_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_TOS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_LEARNING] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_AGEING] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VXLAN_LIMIT] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VXLAN_PORT_RANGE] = { .type = NETLINK_TYPE_U32}, + [IFLA_VXLAN_PROXY] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_RSC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_L2MISS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_L3MISS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_PORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_VXLAN_GROUP6] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VXLAN_LOCAL6] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VXLAN_UDP_CSUM] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_UDP_ZERO_CSUM6_TX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_UDP_ZERO_CSUM6_RX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_REMCSUM_TX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_REMCSUM_RX] = { .type = NETLINK_TYPE_U8 }, + [IFLA_VXLAN_GBP] = { .type = NETLINK_TYPE_FLAG }, + [IFLA_VXLAN_REMCSUM_NOPARTIAL] = { .type = NETLINK_TYPE_FLAG }, +}; + +static const NLType rtnl_bond_arp_target_types[] = { + [BOND_ARP_TARGETS_0] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_1] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_2] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_3] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_4] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_5] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_6] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_7] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_8] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_9] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_10] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_11] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_12] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_13] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_14] = { .type = NETLINK_TYPE_U32 }, + [BOND_ARP_TARGETS_MAX] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLTypeSystem rtnl_bond_arp_type_system = { + .count = ELEMENTSOF(rtnl_bond_arp_target_types), + .types = rtnl_bond_arp_target_types, +}; + +static const NLType rtnl_link_info_data_bond_types[] = { + [IFLA_BOND_MODE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_ACTIVE_SLAVE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_MIIMON] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_UPDELAY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_DOWNDELAY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_USE_CARRIER] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_ARP_INTERVAL] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_ARP_IP_TARGET] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_bond_arp_type_system }, + [IFLA_BOND_ARP_VALIDATE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_ARP_ALL_TARGETS] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_PRIMARY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_PRIMARY_RESELECT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_FAIL_OVER_MAC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_XMIT_HASH_POLICY] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_RESEND_IGMP] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_NUM_PEER_NOTIF] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_ALL_SLAVES_ACTIVE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_MIN_LINKS] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_LP_INTERVAL] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_PACKETS_PER_SLAVE] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BOND_AD_LACP_RATE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_AD_SELECT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BOND_AD_INFO] = { .type = NETLINK_TYPE_NESTED }, +}; + +static const NLType rtnl_link_info_data_iptun_types[] = { + [IFLA_IPTUN_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IPTUN_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_TOS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_PMTUDISC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_FLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_PROTO] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_6RD_PREFIX] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_6RD_RELAY_PREFIX] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IPTUN_6RD_PREFIXLEN] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_6RD_RELAY_PREFIXLEN] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_TYPE] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_FLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_SPORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_IPTUN_ENCAP_DPORT] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_ipgre_types[] = { + [IFLA_GRE_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_IFLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_OFLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_IKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_OKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_GRE_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_GRE_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GRE_TOS] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GRE_PMTUDISC] = { .type = NETLINK_TYPE_U8 }, + [IFLA_GRE_FLOWINFO] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_FLAGS] = { .type = NETLINK_TYPE_U32 }, + [IFLA_GRE_ENCAP_TYPE] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_ENCAP_FLAGS] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_ENCAP_SPORT] = { .type = NETLINK_TYPE_U16 }, + [IFLA_GRE_ENCAP_DPORT] = { .type = NETLINK_TYPE_U16 }, +}; + +static const NLType rtnl_link_info_data_ipvti_types[] = { + [IFLA_VTI_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VTI_IKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VTI_OKEY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_VTI_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_VTI_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, +}; + +static const NLType rtnl_link_info_data_ip6tnl_types[] = { + [IFLA_IPTUN_LINK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IPTUN_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_IPTUN_TTL] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_FLAGS] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IPTUN_PROTO] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_ENCAP_LIMIT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_IPTUN_FLOWINFO] = { .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", + [NL_UNION_LINK_INFO_DATA_BRIDGE] = "bridge", + [NL_UNION_LINK_INFO_DATA_VLAN] = "vlan", + [NL_UNION_LINK_INFO_DATA_VETH] = "veth", + [NL_UNION_LINK_INFO_DATA_DUMMY] = "dummy", + [NL_UNION_LINK_INFO_DATA_MACVLAN] = "macvlan", + [NL_UNION_LINK_INFO_DATA_MACVTAP] = "macvtap", + [NL_UNION_LINK_INFO_DATA_IPVLAN] = "ipvlan", + [NL_UNION_LINK_INFO_DATA_VXLAN] = "vxlan", + [NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = "ipip", + [NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = "gre", + [NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL] = "gretap", + [NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = "ip6gre", + [NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL] = "ip6gretap", + [NL_UNION_LINK_INFO_DATA_SIT_TUNNEL] = "sit", + [NL_UNION_LINK_INFO_DATA_VTI_TUNNEL] = "vti", + [NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = "vti6", + [NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = "ip6tnl", +}; + +DEFINE_STRING_TABLE_LOOKUP(nl_union_link_info_data, NLUnionLinkInfoData); + +static const NLTypeSystem rtnl_link_info_data_type_systems[] = { + [NL_UNION_LINK_INFO_DATA_BOND] = { .count = ELEMENTSOF(rtnl_link_info_data_bond_types), + .types = rtnl_link_info_data_bond_types }, + [NL_UNION_LINK_INFO_DATA_BRIDGE] = { .count = ELEMENTSOF(rtnl_link_info_data_bridge_types), + .types = rtnl_link_info_data_bridge_types }, + [NL_UNION_LINK_INFO_DATA_VLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vlan_types), + .types = rtnl_link_info_data_vlan_types }, + [NL_UNION_LINK_INFO_DATA_VETH] = { .count = ELEMENTSOF(rtnl_link_info_data_veth_types), + .types = rtnl_link_info_data_veth_types }, + [NL_UNION_LINK_INFO_DATA_MACVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), + .types = rtnl_link_info_data_macvlan_types }, + [NL_UNION_LINK_INFO_DATA_MACVTAP] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), + .types = rtnl_link_info_data_macvlan_types }, + [NL_UNION_LINK_INFO_DATA_IPVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvlan_types), + .types = rtnl_link_info_data_ipvlan_types }, + [NL_UNION_LINK_INFO_DATA_VXLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vxlan_types), + .types = rtnl_link_info_data_vxlan_types }, + [NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), + .types = rtnl_link_info_data_iptun_types }, + [NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), + .types = rtnl_link_info_data_ipgre_types }, + [NL_UNION_LINK_INFO_DATA_SIT_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), + .types = rtnl_link_info_data_iptun_types }, + [NL_UNION_LINK_INFO_DATA_VTI_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), + .types = rtnl_link_info_data_ipvti_types }, + [NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), + .types = rtnl_link_info_data_ipvti_types }, + [NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ip6tnl_types), + .types = rtnl_link_info_data_ip6tnl_types }, + +}; + +static const NLTypeSystemUnion rtnl_link_info_data_type_system_union = { + .num = _NL_UNION_LINK_INFO_DATA_MAX, + .lookup = nl_union_link_info_data_from_string, + .type_systems = rtnl_link_info_data_type_systems, + .match_type = NL_MATCH_SIBLING, + .match = IFLA_INFO_KIND, +}; + +static const NLType rtnl_link_info_types[] = { + [IFLA_INFO_KIND] = { .type = NETLINK_TYPE_STRING }, + [IFLA_INFO_DATA] = { .type = NETLINK_TYPE_UNION, .type_system_union = &rtnl_link_info_data_type_system_union}, +/* + [IFLA_INFO_XSTATS], + [IFLA_INFO_SLAVE_KIND] = { .type = NETLINK_TYPE_STRING }, + [IFLA_INFO_SLAVE_DATA] = { .type = NETLINK_TYPE_NESTED }, +*/ +}; + +static const NLTypeSystem rtnl_link_info_type_system = { + .count = ELEMENTSOF(rtnl_link_info_types), + .types = rtnl_link_info_types, +}; + +static const struct NLType rtnl_prot_info_bridge_port_types[] = { + [IFLA_BRPORT_STATE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_COST] = { .type = NETLINK_TYPE_U32 }, + [IFLA_BRPORT_PRIORITY] = { .type = NETLINK_TYPE_U16 }, + [IFLA_BRPORT_MODE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_GUARD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_PROTECT] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_FAST_LEAVE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_LEARNING] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_UNICAST_FLOOD] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_PROXYARP] = { .type = NETLINK_TYPE_U8 }, + [IFLA_BRPORT_LEARNING_SYNC] = { .type = NETLINK_TYPE_U8 }, +}; + +static const NLTypeSystem rtnl_prot_info_type_systems[] = { + [AF_BRIDGE] = { .count = ELEMENTSOF(rtnl_prot_info_bridge_port_types), + .types = rtnl_prot_info_bridge_port_types }, +}; + +static const NLTypeSystemUnion rtnl_prot_info_type_system_union = { + .num = AF_MAX, + .type_systems = rtnl_prot_info_type_systems, + .match_type = NL_MATCH_PROTOCOL, +}; + +static const struct NLType rtnl_af_spec_inet6_types[] = { + [IFLA_INET6_FLAGS] = { .type = NETLINK_TYPE_U32 }, +/* + IFLA_INET6_CONF, + IFLA_INET6_STATS, + IFLA_INET6_MCAST, + IFLA_INET6_CACHEINFO, + IFLA_INET6_ICMP6STATS, +*/ + [IFLA_INET6_TOKEN] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFLA_INET6_ADDR_GEN_MODE] = { .type = NETLINK_TYPE_U8 }, +}; + +static const NLTypeSystem rtnl_af_spec_inet6_type_system = { + .count = ELEMENTSOF(rtnl_af_spec_inet6_types), + .types = rtnl_af_spec_inet6_types, +}; + +static const NLType rtnl_af_spec_types[] = { + [AF_INET6] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_af_spec_inet6_type_system }, +}; + +static const NLTypeSystem rtnl_af_spec_type_system = { + .count = ELEMENTSOF(rtnl_af_spec_types), + .types = rtnl_af_spec_types, +}; + +static const NLType rtnl_link_types[] = { + [IFLA_ADDRESS] = { .type = NETLINK_TYPE_ETHER_ADDR }, + [IFLA_BROADCAST] = { .type = NETLINK_TYPE_ETHER_ADDR }, + [IFLA_IFNAME] = { .type = NETLINK_TYPE_STRING, .size = IFNAMSIZ - 1 }, + [IFLA_MTU] = { .type = NETLINK_TYPE_U32 }, + [IFLA_LINK] = { .type = NETLINK_TYPE_U32 }, +/* + [IFLA_QDISC], + [IFLA_STATS], + [IFLA_COST], + [IFLA_PRIORITY], +*/ + [IFLA_MASTER] = { .type = NETLINK_TYPE_U32 }, +/* + [IFLA_WIRELESS], +*/ + [IFLA_PROTINFO] = { .type = NETLINK_TYPE_UNION, .type_system_union = &rtnl_prot_info_type_system_union }, + [IFLA_TXQLEN] = { .type = NETLINK_TYPE_U32 }, +/* + [IFLA_MAP] = { .len = sizeof(struct rtnl_link_ifmap) }, +*/ + [IFLA_WEIGHT] = { .type = NETLINK_TYPE_U32 }, + [IFLA_OPERSTATE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_LINKMODE] = { .type = NETLINK_TYPE_U8 }, + [IFLA_LINKINFO] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_info_type_system }, + [IFLA_NET_NS_PID] = { .type = NETLINK_TYPE_U32 }, + [IFLA_IFALIAS] = { .type = NETLINK_TYPE_STRING, .size = IFALIASZ - 1 }, +/* + [IFLA_NUM_VF], + [IFLA_VFINFO_LIST] = {. type = NETLINK_TYPE_NESTED, }, + [IFLA_STATS64], + [IFLA_VF_PORTS] = { .type = NETLINK_TYPE_NESTED }, + [IFLA_PORT_SELF] = { .type = NETLINK_TYPE_NESTED }, +*/ + [IFLA_AF_SPEC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_af_spec_type_system }, +/* + [IFLA_VF_PORTS], + [IFLA_PORT_SELF], + [IFLA_AF_SPEC], +*/ + [IFLA_GROUP] = { .type = NETLINK_TYPE_U32 }, + [IFLA_NET_NS_FD] = { .type = NETLINK_TYPE_U32 }, + [IFLA_EXT_MASK] = { .type = NETLINK_TYPE_U32 }, + [IFLA_PROMISCUITY] = { .type = NETLINK_TYPE_U32 }, + [IFLA_NUM_TX_QUEUES] = { .type = NETLINK_TYPE_U32 }, + [IFLA_NUM_RX_QUEUES] = { .type = NETLINK_TYPE_U32 }, + [IFLA_CARRIER] = { .type = NETLINK_TYPE_U8 }, +/* + [IFLA_PHYS_PORT_ID] = { .type = NETLINK_TYPE_BINARY, .len = MAX_PHYS_PORT_ID_LEN }, +*/ +}; + +static const NLTypeSystem rtnl_link_type_system = { + .count = ELEMENTSOF(rtnl_link_types), + .types = rtnl_link_types, +}; + +/* IFA_FLAGS was defined in kernel 3.14, but we still support older + * kernels where IFA_MAX is lower. */ +static const NLType rtnl_address_types[] = { + [IFA_ADDRESS] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFA_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, + [IFA_LABEL] = { .type = NETLINK_TYPE_STRING, .size = IFNAMSIZ - 1 }, + [IFA_BROADCAST] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ + [IFA_CACHEINFO] = { .type = NETLINK_TYPE_CACHE_INFO, .size = sizeof(struct ifa_cacheinfo) }, +/* + [IFA_ANYCAST], + [IFA_MULTICAST], +*/ + [IFA_FLAGS] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLTypeSystem rtnl_address_type_system = { + .count = ELEMENTSOF(rtnl_address_types), + .types = rtnl_address_types, +}; + +static const NLType rtnl_route_types[] = { + [RTA_DST] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ + [RTA_SRC] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ + [RTA_IIF] = { .type = NETLINK_TYPE_U32 }, + [RTA_OIF] = { .type = NETLINK_TYPE_U32 }, + [RTA_GATEWAY] = { .type = NETLINK_TYPE_IN_ADDR }, + [RTA_PRIORITY] = { .type = NETLINK_TYPE_U32 }, + [RTA_PREFSRC] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ +/* + [RTA_METRICS] = { .type = NETLINK_TYPE_NESTED }, + [RTA_MULTIPATH] = { .len = sizeof(struct rtnexthop) }, +*/ + [RTA_FLOW] = { .type = NETLINK_TYPE_U32 }, /* 6? */ +/* + RTA_CACHEINFO, + RTA_TABLE, + RTA_MARK, + RTA_MFC_STATS, + RTA_VIA, + RTA_NEWDST, +*/ + [RTA_PREF] = { .type = NETLINK_TYPE_U8 }, + +}; + +static const NLTypeSystem rtnl_route_type_system = { + .count = ELEMENTSOF(rtnl_route_types), + .types = rtnl_route_types, +}; + +static const NLType rtnl_neigh_types[] = { + [NDA_DST] = { .type = NETLINK_TYPE_IN_ADDR }, + [NDA_LLADDR] = { .type = NETLINK_TYPE_ETHER_ADDR }, + [NDA_CACHEINFO] = { .type = NETLINK_TYPE_CACHE_INFO, .size = sizeof(struct nda_cacheinfo) }, + [NDA_PROBES] = { .type = NETLINK_TYPE_U32 }, + [NDA_VLAN] = { .type = NETLINK_TYPE_U16 }, + [NDA_PORT] = { .type = NETLINK_TYPE_U16 }, + [NDA_VNI] = { .type = NETLINK_TYPE_U32 }, + [NDA_IFINDEX] = { .type = NETLINK_TYPE_U32 }, +}; + +static const NLTypeSystem rtnl_neigh_type_system = { + .count = ELEMENTSOF(rtnl_neigh_types), + .types = rtnl_neigh_types, +}; + +static const NLType rtnl_types[] = { + [NLMSG_DONE] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = 0 }, + [NLMSG_ERROR] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = sizeof(struct nlmsgerr) }, + [RTM_NEWLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, + [RTM_DELLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, + [RTM_GETLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, + [RTM_SETLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, + [RTM_NEWADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, + [RTM_DELADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, + [RTM_GETADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, + [RTM_NEWROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, + [RTM_DELROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, + [RTM_GETROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, + [RTM_NEWNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, + [RTM_DELNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, + [RTM_GETNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, +}; + +const NLTypeSystem type_system_root = { + .count = ELEMENTSOF(rtnl_types), + .types = rtnl_types, +}; + +uint16_t type_get_type(const NLType *type) { + assert(type); + return type->type; +} + +size_t type_get_size(const NLType *type) { + assert(type); + return type->size; +} + +void type_get_type_system(const NLType *nl_type, const NLTypeSystem **ret) { + assert(nl_type); + assert(ret); + assert(nl_type->type == NETLINK_TYPE_NESTED); + assert(nl_type->type_system); + + *ret = nl_type->type_system; +} + +void type_get_type_system_union(const NLType *nl_type, const NLTypeSystemUnion **ret) { + assert(nl_type); + assert(ret); + assert(nl_type->type == NETLINK_TYPE_UNION); + assert(nl_type->type_system_union); + + *ret = nl_type->type_system_union; +} + +uint16_t type_system_get_count(const NLTypeSystem *type_system) { + assert(type_system); + return type_system->count; +} + +int type_system_get_type(const NLTypeSystem *type_system, const NLType **ret, uint16_t type) { + const NLType *nl_type; + + assert(ret); + assert(type_system); + assert(type_system->types); + + if (type >= type_system->count) + return -EOPNOTSUPP; + + nl_type = &type_system->types[type]; + + if (nl_type->type == NETLINK_TYPE_UNSPEC) + return -EOPNOTSUPP; + + *ret = nl_type; + + return 0; +} + +int type_system_get_type_system(const NLTypeSystem *type_system, const NLTypeSystem **ret, uint16_t type) { + const NLType *nl_type; + int r; + + assert(ret); + + r = type_system_get_type(type_system, &nl_type, type); + if (r < 0) + return r; + + type_get_type_system(nl_type, ret); + return 0; +} + +int type_system_get_type_system_union(const NLTypeSystem *type_system, const NLTypeSystemUnion **ret, uint16_t type) { + const NLType *nl_type; + int r; + + assert(ret); + + r = type_system_get_type(type_system, &nl_type, type); + if (r < 0) + return r; + + type_get_type_system_union(nl_type, ret); + return 0; +} + +int type_system_union_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, const char *key) { + int type; + + assert(type_system_union); + assert(type_system_union->match_type == NL_MATCH_SIBLING); + assert(type_system_union->lookup); + assert(type_system_union->type_systems); + assert(ret); + assert(key); + + type = type_system_union->lookup(key); + if (type < 0) + return -EOPNOTSUPP; + + assert(type < type_system_union->num); + + *ret = &type_system_union->type_systems[type]; + + return 0; +} + +int type_system_union_protocol_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, uint16_t protocol) { + const NLTypeSystem *type_system; + + assert(type_system_union); + assert(type_system_union->type_systems); + assert(type_system_union->match_type == NL_MATCH_PROTOCOL); + assert(ret); + + if (protocol >= type_system_union->num) + return -EOPNOTSUPP; + + type_system = &type_system_union->type_systems[protocol]; + if (!type_system->types) + return -EOPNOTSUPP; + + *ret = type_system; + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.h b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.h new file mode 100644 index 0000000000..ecb20bfcdc --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-types.h @@ -0,0 +1,94 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include "macro.h" + +enum { + NETLINK_TYPE_UNSPEC, + NETLINK_TYPE_U8, /* NLA_U8 */ + NETLINK_TYPE_U16, /* NLA_U16 */ + NETLINK_TYPE_U32, /* NLA_U32 */ + NETLINK_TYPE_U64, /* NLA_U64 */ + NETLINK_TYPE_STRING, /* NLA_STRING */ + NETLINK_TYPE_FLAG, /* NLA_FLAG */ + NETLINK_TYPE_IN_ADDR, + NETLINK_TYPE_ETHER_ADDR, + NETLINK_TYPE_CACHE_INFO, + NETLINK_TYPE_NESTED, /* NLA_NESTED */ + NETLINK_TYPE_UNION, +}; + +typedef enum NLMatchType { + NL_MATCH_SIBLING, + NL_MATCH_PROTOCOL, +} NLMatchType; + +typedef struct NLTypeSystemUnion NLTypeSystemUnion; +typedef struct NLTypeSystem NLTypeSystem; +typedef struct NLType NLType; + +struct NLTypeSystemUnion { + int num; + NLMatchType match_type; + uint16_t match; + int (*lookup)(const char *); + const NLTypeSystem *type_systems; +}; + +extern const NLTypeSystem type_system_root; + +uint16_t type_get_type(const NLType *type); +size_t type_get_size(const NLType *type); +void type_get_type_system(const NLType *type, const NLTypeSystem **ret); +void type_get_type_system_union(const NLType *type, const NLTypeSystemUnion **ret); + +uint16_t type_system_get_count(const NLTypeSystem *type_system); +int type_system_get_type(const NLTypeSystem *type_system, const NLType **ret, uint16_t type); +int type_system_get_type_system(const NLTypeSystem *type_system, const NLTypeSystem **ret, uint16_t type); +int type_system_get_type_system_union(const NLTypeSystem *type_system, const NLTypeSystemUnion **ret, uint16_t type); +int type_system_union_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, const char *key); +int type_system_union_protocol_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, uint16_t protocol); + +typedef enum NLUnionLinkInfoData { + NL_UNION_LINK_INFO_DATA_BOND, + NL_UNION_LINK_INFO_DATA_BRIDGE, + NL_UNION_LINK_INFO_DATA_VLAN, + NL_UNION_LINK_INFO_DATA_VETH, + NL_UNION_LINK_INFO_DATA_DUMMY, + NL_UNION_LINK_INFO_DATA_MACVLAN, + NL_UNION_LINK_INFO_DATA_MACVTAP, + NL_UNION_LINK_INFO_DATA_IPVLAN, + NL_UNION_LINK_INFO_DATA_VXLAN, + NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL, + NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL, + NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL, + NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL, + NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL, + NL_UNION_LINK_INFO_DATA_SIT_TUNNEL, + 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_MAX, + _NL_UNION_LINK_INFO_DATA_INVALID = -1 +} NLUnionLinkInfoData; + +const char *nl_union_link_info_data_to_string(NLUnionLinkInfoData p) _const_; +NLUnionLinkInfoData nl_union_link_info_data_from_string(const char *p) _pure_; diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.c b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.c new file mode 100644 index 0000000000..828ae7db7f --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.c @@ -0,0 +1,170 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 Tom Gundersen + + 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 . +***/ + +#include + +#include "netlink-internal.h" +#include "netlink-util.h" + +int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + int r; + + assert(rtnl); + assert(ifindex > 0); + assert(name); + + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); + if (r < 0) + return r; + + r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); + if (r < 0) + return r; + + r = sd_netlink_call(*rtnl, message, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias, + const struct ether_addr *mac, unsigned mtu) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + int r; + + assert(rtnl); + assert(ifindex > 0); + + if (!alias && !mac && mtu == 0) + return 0; + + if (!*rtnl) { + r = sd_netlink_open(rtnl); + if (r < 0) + return r; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); + if (r < 0) + return r; + + if (alias) { + r = sd_netlink_message_append_string(message, IFLA_IFALIAS, alias); + if (r < 0) + return r; + } + + if (mac) { + r = sd_netlink_message_append_ether_addr(message, IFLA_ADDRESS, mac); + if (r < 0) + return r; + } + + if (mtu > 0) { + r = sd_netlink_message_append_u32(message, IFLA_MTU, mtu); + if (r < 0) + return r; + } + + r = sd_netlink_call(*rtnl, message, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +int rtnl_message_new_synthetic_error(int error, uint32_t serial, sd_netlink_message **ret) { + struct nlmsgerr *err; + int r; + + assert(error <= 0); + + r = message_new(NULL, ret, NLMSG_ERROR); + if (r < 0) + return r; + + (*ret)->hdr->nlmsg_seq = serial; + + err = NLMSG_DATA((*ret)->hdr); + + err->error = error; + + return 0; +} + +bool rtnl_message_type_is_neigh(uint16_t type) { + switch (type) { + case RTM_NEWNEIGH: + case RTM_GETNEIGH: + case RTM_DELNEIGH: + return true; + default: + return false; + } +} + +bool rtnl_message_type_is_route(uint16_t type) { + switch (type) { + case RTM_NEWROUTE: + case RTM_GETROUTE: + case RTM_DELROUTE: + return true; + default: + return false; + } +} + +bool rtnl_message_type_is_link(uint16_t type) { + switch (type) { + case RTM_NEWLINK: + case RTM_SETLINK: + case RTM_GETLINK: + case RTM_DELLINK: + return true; + default: + return false; + } +} + +bool rtnl_message_type_is_addr(uint16_t type) { + switch (type) { + case RTM_NEWADDR: + case RTM_GETADDR: + case RTM_DELADDR: + return true; + default: + return false; + } +} + +int rtnl_log_parse_error(int r) { + return log_error_errno(r, "Failed to parse netlink message: %m"); +} + +int rtnl_log_create_error(int r) { + return log_error_errno(r, "Failed to create netlink message: %m"); +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.h b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.h new file mode 100644 index 0000000000..e8f932549f --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/netlink-util.h @@ -0,0 +1,39 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2013 Tom Gundersen + + 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 . +***/ + +#include + +#include "util.h" + +int rtnl_message_new_synthetic_error(int error, uint32_t serial, sd_netlink_message **ret); +uint32_t rtnl_message_get_serial(sd_netlink_message *m); +void rtnl_message_seal(sd_netlink_message *m); + +bool rtnl_message_type_is_link(uint16_t type); +bool rtnl_message_type_is_addr(uint16_t type); +bool rtnl_message_type_is_route(uint16_t type); +bool rtnl_message_type_is_neigh(uint16_t type); + +int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name); +int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias, const struct ether_addr *mac, unsigned mtu); + +int rtnl_log_parse_error(int r); +int rtnl_log_create_error(int r); diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/rtnl-message.c b/src/libsystemd/libsystemd-internal/sd-netlink/rtnl-message.c new file mode 100644 index 0000000000..2f1254295a --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/rtnl-message.c @@ -0,0 +1,675 @@ +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 . +***/ + +#include +#include +#include + +#include + +#include "formats-util.h" +#include "missing.h" +#include "netlink-internal.h" +#include "netlink-types.h" +#include "netlink-util.h" +#include "refcnt.h" +#include "socket-util.h" +#include "util.h" + +int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + if ((rtm->rtm_family == AF_INET && prefixlen > 32) || + (rtm->rtm_family == AF_INET6 && prefixlen > 128)) + return -ERANGE; + + rtm->rtm_dst_len = prefixlen; + + return 0; +} + +int sd_rtnl_message_route_set_src_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + if ((rtm->rtm_family == AF_INET && prefixlen > 32) || + (rtm->rtm_family == AF_INET6 && prefixlen > 128)) + return -ERANGE; + + rtm->rtm_src_len = prefixlen; + + return 0; +} + +int sd_rtnl_message_route_set_scope(sd_netlink_message *m, unsigned char scope) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + rtm->rtm_scope = scope; + + return 0; +} + +int sd_rtnl_message_route_set_flags(sd_netlink_message *m, unsigned flags) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + rtm->rtm_flags = flags; + + return 0; +} + +int sd_rtnl_message_route_get_flags(sd_netlink_message *m, unsigned *flags) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(flags, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *flags = rtm->rtm_flags; + + return 0; +} + +int sd_rtnl_message_route_get_family(sd_netlink_message *m, int *family) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(family, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *family = rtm->rtm_family; + + return 0; +} + +int sd_rtnl_message_route_get_protocol(sd_netlink_message *m, unsigned char *protocol) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(protocol, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *protocol = rtm->rtm_protocol; + + return 0; +} + +int sd_rtnl_message_route_get_scope(sd_netlink_message *m, unsigned char *scope) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(scope, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *scope = rtm->rtm_scope; + + return 0; +} + +int sd_rtnl_message_route_get_tos(sd_netlink_message *m, unsigned char *tos) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(tos, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *tos = rtm->rtm_tos; + + return 0; +} + +int sd_rtnl_message_route_get_table(sd_netlink_message *m, unsigned char *table) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(table, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *table = rtm->rtm_table; + + return 0; +} + +int sd_rtnl_message_route_get_dst_prefixlen(sd_netlink_message *m, unsigned char *dst_len) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(dst_len, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *dst_len = rtm->rtm_dst_len; + + return 0; +} + +int sd_rtnl_message_route_get_src_prefixlen(sd_netlink_message *m, unsigned char *src_len) { + struct rtmsg *rtm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); + assert_return(src_len, -EINVAL); + + rtm = NLMSG_DATA(m->hdr); + + *src_len = rtm->rtm_src_len; + + return 0; +} + +int sd_rtnl_message_new_route(sd_netlink *rtnl, sd_netlink_message **ret, + uint16_t nlmsg_type, int rtm_family, + unsigned char rtm_protocol) { + struct rtmsg *rtm; + int r; + + assert_return(rtnl_message_type_is_route(nlmsg_type), -EINVAL); + assert_return((nlmsg_type == RTM_GETROUTE && rtm_family == AF_UNSPEC) || + rtm_family == AF_INET || rtm_family == AF_INET6, -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWROUTE) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; + + rtm = NLMSG_DATA((*ret)->hdr); + + rtm->rtm_family = rtm_family; + rtm->rtm_scope = RT_SCOPE_UNIVERSE; + rtm->rtm_type = RTN_UNICAST; + rtm->rtm_table = RT_TABLE_MAIN; + rtm->rtm_protocol = rtm_protocol; + + return 0; +} + +int sd_rtnl_message_neigh_set_flags(sd_netlink_message *m, uint8_t flags) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + ndm->ndm_flags |= flags; + + return 0; +} + +int sd_rtnl_message_neigh_set_state(sd_netlink_message *m, uint16_t state) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + ndm->ndm_state |= state; + + return 0; +} + +int sd_rtnl_message_neigh_get_flags(sd_netlink_message *m, uint8_t *flags) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + *flags = ndm->ndm_flags; + + return 0; +} + +int sd_rtnl_message_neigh_get_state(sd_netlink_message *m, uint16_t *state) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + *state = ndm->ndm_state; + + return 0; +} + +int sd_rtnl_message_neigh_get_family(sd_netlink_message *m, int *family) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + assert_return(family, -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + + *family = ndm->ndm_family; + + return 0; +} + +int sd_rtnl_message_neigh_get_ifindex(sd_netlink_message *m, int *index) { + struct ndmsg *ndm; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); + assert_return(index, -EINVAL); + + ndm = NLMSG_DATA(m->hdr); + + *index = ndm->ndm_ifindex; + + return 0; +} + +int sd_rtnl_message_new_neigh(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int index, int ndm_family) { + struct ndmsg *ndm; + int r; + + assert_return(rtnl_message_type_is_neigh(nlmsg_type), -EINVAL); + assert_return(ndm_family == AF_INET || + ndm_family == AF_INET6 || + ndm_family == PF_BRIDGE, -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWNEIGH) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; + + ndm = NLMSG_DATA((*ret)->hdr); + + ndm->ndm_family = ndm_family; + ndm->ndm_ifindex = index; + + return 0; +} + +int sd_rtnl_message_link_set_flags(sd_netlink_message *m, unsigned flags, unsigned change) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + assert_return(change, -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + ifi->ifi_flags = flags; + ifi->ifi_change = change; + + return 0; +} + +int sd_rtnl_message_link_set_type(sd_netlink_message *m, unsigned type) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + ifi->ifi_type = type; + + return 0; +} + +int sd_rtnl_message_link_set_family(sd_netlink_message *m, unsigned family) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + ifi->ifi_family = family; + + return 0; +} + +int sd_rtnl_message_new_link(sd_netlink *rtnl, sd_netlink_message **ret, + uint16_t nlmsg_type, int index) { + struct ifinfomsg *ifi; + int r; + + assert_return(rtnl_message_type_is_link(nlmsg_type), -EINVAL); + assert_return(nlmsg_type != RTM_DELLINK || index > 0, -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_NEWLINK) + (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; + + ifi = NLMSG_DATA((*ret)->hdr); + + ifi->ifi_family = AF_UNSPEC; + ifi->ifi_index = index; + + return 0; +} + +int sd_rtnl_message_addr_set_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + if ((ifa->ifa_family == AF_INET && prefixlen > 32) || + (ifa->ifa_family == AF_INET6 && prefixlen > 128)) + return -ERANGE; + + ifa->ifa_prefixlen = prefixlen; + + return 0; +} + +int sd_rtnl_message_addr_set_flags(sd_netlink_message *m, unsigned char flags) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + ifa->ifa_flags = flags; + + return 0; +} + +int sd_rtnl_message_addr_set_scope(sd_netlink_message *m, unsigned char scope) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + ifa->ifa_scope = scope; + + return 0; +} + +int sd_rtnl_message_addr_get_family(sd_netlink_message *m, int *family) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(family, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *family = ifa->ifa_family; + + return 0; +} + +int sd_rtnl_message_addr_get_prefixlen(sd_netlink_message *m, unsigned char *prefixlen) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(prefixlen, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *prefixlen = ifa->ifa_prefixlen; + + return 0; +} + +int sd_rtnl_message_addr_get_scope(sd_netlink_message *m, unsigned char *scope) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(scope, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *scope = ifa->ifa_scope; + + return 0; +} + +int sd_rtnl_message_addr_get_flags(sd_netlink_message *m, unsigned char *flags) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(flags, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *flags = ifa->ifa_flags; + + return 0; +} + +int sd_rtnl_message_addr_get_ifindex(sd_netlink_message *m, int *ifindex) { + struct ifaddrmsg *ifa; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); + assert_return(ifindex, -EINVAL); + + ifa = NLMSG_DATA(m->hdr); + + *ifindex = ifa->ifa_index; + + return 0; +} + +int sd_rtnl_message_new_addr(sd_netlink *rtnl, sd_netlink_message **ret, + uint16_t nlmsg_type, int index, + int family) { + struct ifaddrmsg *ifa; + int r; + + assert_return(rtnl_message_type_is_addr(nlmsg_type), -EINVAL); + assert_return((nlmsg_type == RTM_GETADDR && index == 0) || + index > 0, -EINVAL); + assert_return((nlmsg_type == RTM_GETADDR && family == AF_UNSPEC) || + family == AF_INET || family == AF_INET6, -EINVAL); + assert_return(ret, -EINVAL); + + r = message_new(rtnl, ret, nlmsg_type); + if (r < 0) + return r; + + if (nlmsg_type == RTM_GETADDR) + (*ret)->hdr->nlmsg_flags |= NLM_F_DUMP; + + ifa = NLMSG_DATA((*ret)->hdr); + + ifa->ifa_index = index; + ifa->ifa_family = family; + if (family == AF_INET) + ifa->ifa_prefixlen = 32; + else if (family == AF_INET6) + ifa->ifa_prefixlen = 128; + + return 0; +} + +int sd_rtnl_message_new_addr_update(sd_netlink *rtnl, sd_netlink_message **ret, + int index, int family) { + int r; + + r = sd_rtnl_message_new_addr(rtnl, ret, RTM_NEWADDR, index, family); + if (r < 0) + return r; + + (*ret)->hdr->nlmsg_flags |= NLM_F_REPLACE; + + return 0; +} + +int sd_rtnl_message_link_get_ifindex(sd_netlink_message *m, int *ifindex) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + assert_return(ifindex, -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + *ifindex = ifi->ifi_index; + + return 0; +} + +int sd_rtnl_message_link_get_flags(sd_netlink_message *m, unsigned *flags) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + assert_return(flags, -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + *flags = ifi->ifi_flags; + + return 0; +} + +int sd_rtnl_message_link_get_type(sd_netlink_message *m, unsigned *type) { + struct ifinfomsg *ifi; + + assert_return(m, -EINVAL); + assert_return(m->hdr, -EINVAL); + assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); + assert_return(type, -EINVAL); + + ifi = NLMSG_DATA(m->hdr); + + *type = ifi->ifi_type; + + return 0; +} + +int sd_rtnl_message_get_family(sd_netlink_message *m, int *family) { + assert_return(m, -EINVAL); + assert_return(family, -EINVAL); + + assert(m->hdr); + + if (rtnl_message_type_is_link(m->hdr->nlmsg_type)) { + struct ifinfomsg *ifi; + + ifi = NLMSG_DATA(m->hdr); + + *family = ifi->ifi_family; + + return 0; + } else if (rtnl_message_type_is_route(m->hdr->nlmsg_type)) { + struct rtmsg *rtm; + + rtm = NLMSG_DATA(m->hdr); + + *family = rtm->rtm_family; + + return 0; + } else if (rtnl_message_type_is_neigh(m->hdr->nlmsg_type)) { + struct ndmsg *ndm; + + ndm = NLMSG_DATA(m->hdr); + + *family = ndm->ndm_family; + + return 0; + } else if (rtnl_message_type_is_addr(m->hdr->nlmsg_type)) { + struct ifaddrmsg *ifa; + + ifa = NLMSG_DATA(m->hdr); + + *family = ifa->ifa_family; + + return 0; + } + + return -EOPNOTSUPP; +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/sd-netlink.c b/src/libsystemd/libsystemd-internal/sd-netlink/sd-netlink.c new file mode 100644 index 0000000000..c536364a24 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/sd-netlink.c @@ -0,0 +1,954 @@ +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 . +***/ + +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "hashmap.h" +#include "macro.h" +#include "missing.h" +#include "netlink-internal.h" +#include "netlink-util.h" +#include "socket-util.h" +#include "util.h" + +static int sd_netlink_new(sd_netlink **ret) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + + assert_return(ret, -EINVAL); + + rtnl = new0(sd_netlink, 1); + if (!rtnl) + return -ENOMEM; + + rtnl->n_ref = REFCNT_INIT; + rtnl->fd = -1; + rtnl->sockaddr.nl.nl_family = AF_NETLINK; + rtnl->original_pid = getpid(); + + LIST_HEAD_INIT(rtnl->match_callbacks); + + /* We guarantee that the read buffer has at least space for + * a message header */ + if (!greedy_realloc((void**)&rtnl->rbuffer, &rtnl->rbuffer_allocated, + sizeof(struct nlmsghdr), sizeof(uint8_t))) + return -ENOMEM; + + /* Change notification responses have sequence 0, so we must + * start our request sequence numbers at 1, or we may confuse our + * responses with notifications from the kernel */ + rtnl->serial = 1; + + *ret = rtnl; + rtnl = NULL; + + return 0; +} + +int sd_netlink_new_from_netlink(sd_netlink **ret, int fd) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + socklen_t addrlen; + int r; + + assert_return(ret, -EINVAL); + + r = sd_netlink_new(&rtnl); + if (r < 0) + return r; + + addrlen = sizeof(rtnl->sockaddr); + + r = getsockname(fd, &rtnl->sockaddr.sa, &addrlen); + if (r < 0) + return -errno; + + if (rtnl->sockaddr.nl.nl_family != AF_NETLINK) + return -EINVAL; + + rtnl->fd = fd; + + *ret = rtnl; + rtnl = NULL; + + return 0; +} + +static bool rtnl_pid_changed(sd_netlink *rtnl) { + assert(rtnl); + + /* We don't support people creating an rtnl connection and + * keeping it around over a fork(). Let's complain. */ + + return rtnl->original_pid != getpid(); +} + +int sd_netlink_open_fd(sd_netlink **ret, int fd) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int r; + + assert_return(ret, -EINVAL); + assert_return(fd >= 0, -EBADF); + + r = sd_netlink_new(&rtnl); + if (r < 0) + return r; + + rtnl->fd = fd; + + r = socket_bind(rtnl); + if (r < 0) { + rtnl->fd = -1; /* on failure, the caller remains owner of the fd, hence don't close it here */ + return r; + } + + *ret = rtnl; + rtnl = NULL; + + return 0; +} + +int sd_netlink_open(sd_netlink **ret) { + _cleanup_close_ int fd = -1; + int r; + + fd = socket_open(NETLINK_ROUTE); + if (fd < 0) + return fd; + + r = sd_netlink_open_fd(ret, fd); + if (r < 0) + return r; + + fd = -1; + + return 0; +} + +int sd_netlink_inc_rcvbuf(const sd_netlink *const rtnl, const int size) { + return fd_inc_rcvbuf(rtnl->fd, size); +} + +sd_netlink *sd_netlink_ref(sd_netlink *rtnl) { + assert_return(rtnl, NULL); + assert_return(!rtnl_pid_changed(rtnl), NULL); + + if (rtnl) + assert_se(REFCNT_INC(rtnl->n_ref) >= 2); + + return rtnl; +} + +sd_netlink *sd_netlink_unref(sd_netlink *rtnl) { + if (!rtnl) + return NULL; + + assert_return(!rtnl_pid_changed(rtnl), NULL); + + if (REFCNT_DEC(rtnl->n_ref) == 0) { + struct match_callback *f; + unsigned i; + + for (i = 0; i < rtnl->rqueue_size; i++) + sd_netlink_message_unref(rtnl->rqueue[i]); + free(rtnl->rqueue); + + for (i = 0; i < rtnl->rqueue_partial_size; i++) + sd_netlink_message_unref(rtnl->rqueue_partial[i]); + free(rtnl->rqueue_partial); + + free(rtnl->rbuffer); + + hashmap_free_free(rtnl->reply_callbacks); + prioq_free(rtnl->reply_callbacks_prioq); + + sd_event_source_unref(rtnl->io_event_source); + sd_event_source_unref(rtnl->time_event_source); + sd_event_unref(rtnl->event); + + while ((f = rtnl->match_callbacks)) { + sd_netlink_remove_match(rtnl, f->type, f->callback, f->userdata); + } + + hashmap_free(rtnl->broadcast_group_refs); + + safe_close(rtnl->fd); + free(rtnl); + } + + return NULL; +} + +static void rtnl_seal_message(sd_netlink *rtnl, sd_netlink_message *m) { + assert(rtnl); + assert(!rtnl_pid_changed(rtnl)); + assert(m); + assert(m->hdr); + + /* don't use seq == 0, as that is used for broadcasts, so we + would get confused by replies to such messages */ + m->hdr->nlmsg_seq = rtnl->serial++ ? : rtnl->serial++; + + rtnl_message_seal(m); + + return; +} + +int sd_netlink_send(sd_netlink *nl, + sd_netlink_message *message, + uint32_t *serial) { + int r; + + assert_return(nl, -EINVAL); + assert_return(!rtnl_pid_changed(nl), -ECHILD); + assert_return(message, -EINVAL); + assert_return(!message->sealed, -EPERM); + + rtnl_seal_message(nl, message); + + r = socket_write_message(nl, message); + if (r < 0) + return r; + + if (serial) + *serial = rtnl_message_get_serial(message); + + return 1; +} + +int rtnl_rqueue_make_room(sd_netlink *rtnl) { + assert(rtnl); + + if (rtnl->rqueue_size >= RTNL_RQUEUE_MAX) { + log_debug("rtnl: exhausted the read queue size (%d)", RTNL_RQUEUE_MAX); + return -ENOBUFS; + } + + if (!GREEDY_REALLOC(rtnl->rqueue, rtnl->rqueue_allocated, rtnl->rqueue_size + 1)) + return -ENOMEM; + + return 0; +} + +int rtnl_rqueue_partial_make_room(sd_netlink *rtnl) { + assert(rtnl); + + if (rtnl->rqueue_partial_size >= RTNL_RQUEUE_MAX) { + log_debug("rtnl: exhausted the partial read queue size (%d)", RTNL_RQUEUE_MAX); + return -ENOBUFS; + } + + if (!GREEDY_REALLOC(rtnl->rqueue_partial, rtnl->rqueue_partial_allocated, + rtnl->rqueue_partial_size + 1)) + return -ENOMEM; + + return 0; +} + +static int dispatch_rqueue(sd_netlink *rtnl, sd_netlink_message **message) { + int r; + + assert(rtnl); + assert(message); + + if (rtnl->rqueue_size <= 0) { + /* Try to read a new message */ + r = socket_read_message(rtnl); + if (r <= 0) + return r; + } + + /* Dispatch a queued message */ + *message = rtnl->rqueue[0]; + rtnl->rqueue_size --; + memmove(rtnl->rqueue, rtnl->rqueue + 1, sizeof(sd_netlink_message*) * rtnl->rqueue_size); + + return 1; +} + +static int process_timeout(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + struct reply_callback *c; + usec_t n; + int r; + + assert(rtnl); + + c = prioq_peek(rtnl->reply_callbacks_prioq); + if (!c) + return 0; + + n = now(CLOCK_MONOTONIC); + if (c->timeout > n) + return 0; + + r = rtnl_message_new_synthetic_error(-ETIMEDOUT, c->serial, &m); + if (r < 0) + return r; + + assert_se(prioq_pop(rtnl->reply_callbacks_prioq) == c); + hashmap_remove(rtnl->reply_callbacks, &c->serial); + + r = c->callback(rtnl, m, c->userdata); + if (r < 0) + log_debug_errno(r, "sd-netlink: timedout callback failed: %m"); + + free(c); + + return 1; +} + +static int process_reply(sd_netlink *rtnl, sd_netlink_message *m) { + _cleanup_free_ struct reply_callback *c = NULL; + uint64_t serial; + uint16_t type; + int r; + + assert(rtnl); + assert(m); + + serial = rtnl_message_get_serial(m); + c = hashmap_remove(rtnl->reply_callbacks, &serial); + if (!c) + return 0; + + if (c->timeout != 0) + prioq_remove(rtnl->reply_callbacks_prioq, c, &c->prioq_idx); + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return 0; + + if (type == NLMSG_DONE) + m = NULL; + + r = c->callback(rtnl, m, c->userdata); + if (r < 0) + log_debug_errno(r, "sd-netlink: callback failed: %m"); + + return 1; +} + +static int process_match(sd_netlink *rtnl, sd_netlink_message *m) { + struct match_callback *c; + uint16_t type; + int r; + + assert(rtnl); + assert(m); + + r = sd_netlink_message_get_type(m, &type); + if (r < 0) + return r; + + LIST_FOREACH(match_callbacks, c, rtnl->match_callbacks) { + if (type == c->type) { + r = c->callback(rtnl, m, c->userdata); + if (r != 0) { + if (r < 0) + log_debug_errno(r, "sd-netlink: match callback failed: %m"); + + break; + } + } + } + + return 1; +} + +static int process_running(sd_netlink *rtnl, sd_netlink_message **ret) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(rtnl); + + r = process_timeout(rtnl); + if (r != 0) + goto null_message; + + r = dispatch_rqueue(rtnl, &m); + if (r < 0) + return r; + if (!m) + goto null_message; + + if (sd_netlink_message_is_broadcast(m)) { + r = process_match(rtnl, m); + if (r != 0) + goto null_message; + } else { + r = process_reply(rtnl, m); + if (r != 0) + goto null_message; + } + + if (ret) { + *ret = m; + m = NULL; + + return 1; + } + + return 1; + +null_message: + if (r >= 0 && ret) + *ret = NULL; + + return r; +} + +int sd_netlink_process(sd_netlink *rtnl, sd_netlink_message **ret) { + NETLINK_DONT_DESTROY(rtnl); + int r; + + assert_return(rtnl, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + assert_return(!rtnl->processing, -EBUSY); + + rtnl->processing = true; + r = process_running(rtnl, ret); + rtnl->processing = false; + + return r; +} + +static usec_t calc_elapse(uint64_t usec) { + if (usec == (uint64_t) -1) + return 0; + + if (usec == 0) + usec = RTNL_DEFAULT_TIMEOUT; + + return now(CLOCK_MONOTONIC) + usec; +} + +static int rtnl_poll(sd_netlink *rtnl, bool need_more, uint64_t timeout_usec) { + struct pollfd p[1] = {}; + struct timespec ts; + usec_t m = USEC_INFINITY; + int r, e; + + assert(rtnl); + + e = sd_netlink_get_events(rtnl); + if (e < 0) + return e; + + if (need_more) + /* Caller wants more data, and doesn't care about + * what's been read or any other timeouts. */ + e |= POLLIN; + else { + usec_t until; + /* Caller wants to process if there is something to + * process, but doesn't care otherwise */ + + r = sd_netlink_get_timeout(rtnl, &until); + if (r < 0) + return r; + if (r > 0) { + usec_t nw; + nw = now(CLOCK_MONOTONIC); + m = until > nw ? until - nw : 0; + } + } + + if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) + m = timeout_usec; + + p[0].fd = rtnl->fd; + p[0].events = e; + + r = ppoll(p, 1, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); + if (r < 0) + return -errno; + + return r > 0 ? 1 : 0; +} + +int sd_netlink_wait(sd_netlink *nl, uint64_t timeout_usec) { + assert_return(nl, -EINVAL); + assert_return(!rtnl_pid_changed(nl), -ECHILD); + + if (nl->rqueue_size > 0) + return 0; + + return rtnl_poll(nl, false, timeout_usec); +} + +static int timeout_compare(const void *a, const void *b) { + const struct reply_callback *x = a, *y = b; + + if (x->timeout != 0 && y->timeout == 0) + return -1; + + if (x->timeout == 0 && y->timeout != 0) + return 1; + + if (x->timeout < y->timeout) + return -1; + + if (x->timeout > y->timeout) + return 1; + + return 0; +} + +int sd_netlink_call_async(sd_netlink *nl, + sd_netlink_message *m, + sd_netlink_message_handler_t callback, + void *userdata, + uint64_t usec, + uint32_t *serial) { + struct reply_callback *c; + uint32_t s; + int r, k; + + assert_return(nl, -EINVAL); + assert_return(m, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!rtnl_pid_changed(nl), -ECHILD); + + r = hashmap_ensure_allocated(&nl->reply_callbacks, &uint64_hash_ops); + if (r < 0) + return r; + + if (usec != (uint64_t) -1) { + r = prioq_ensure_allocated(&nl->reply_callbacks_prioq, timeout_compare); + if (r < 0) + return r; + } + + c = new0(struct reply_callback, 1); + if (!c) + return -ENOMEM; + + c->callback = callback; + c->userdata = userdata; + c->timeout = calc_elapse(usec); + + k = sd_netlink_send(nl, m, &s); + if (k < 0) { + free(c); + return k; + } + + c->serial = s; + + r = hashmap_put(nl->reply_callbacks, &c->serial, c); + if (r < 0) { + free(c); + return r; + } + + if (c->timeout != 0) { + r = prioq_put(nl->reply_callbacks_prioq, c, &c->prioq_idx); + if (r > 0) { + c->timeout = 0; + sd_netlink_call_async_cancel(nl, c->serial); + return r; + } + } + + if (serial) + *serial = s; + + return k; +} + +int sd_netlink_call_async_cancel(sd_netlink *nl, uint32_t serial) { + struct reply_callback *c; + uint64_t s = serial; + + assert_return(nl, -EINVAL); + assert_return(serial != 0, -EINVAL); + assert_return(!rtnl_pid_changed(nl), -ECHILD); + + c = hashmap_remove(nl->reply_callbacks, &s); + if (!c) + return 0; + + if (c->timeout != 0) + prioq_remove(nl->reply_callbacks_prioq, c, &c->prioq_idx); + + free(c); + return 1; +} + +int sd_netlink_call(sd_netlink *rtnl, + sd_netlink_message *message, + uint64_t usec, + sd_netlink_message **ret) { + usec_t timeout; + uint32_t serial; + int r; + + assert_return(rtnl, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + assert_return(message, -EINVAL); + + r = sd_netlink_send(rtnl, message, &serial); + if (r < 0) + return r; + + timeout = calc_elapse(usec); + + for (;;) { + usec_t left; + unsigned i; + + for (i = 0; i < rtnl->rqueue_size; i++) { + uint32_t received_serial; + + received_serial = rtnl_message_get_serial(rtnl->rqueue[i]); + + if (received_serial == serial) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *incoming = NULL; + uint16_t type; + + incoming = rtnl->rqueue[i]; + + /* found a match, remove from rqueue and return it */ + memmove(rtnl->rqueue + i,rtnl->rqueue + i + 1, + sizeof(sd_netlink_message*) * (rtnl->rqueue_size - i - 1)); + rtnl->rqueue_size--; + + r = sd_netlink_message_get_errno(incoming); + if (r < 0) + return r; + + r = sd_netlink_message_get_type(incoming, &type); + if (r < 0) + return r; + + if (type == NLMSG_DONE) { + *ret = NULL; + return 0; + } + + if (ret) { + *ret = incoming; + incoming = NULL; + } + + return 1; + } + } + + r = socket_read_message(rtnl); + if (r < 0) + return r; + if (r > 0) + /* received message, so try to process straight away */ + continue; + + if (timeout > 0) { + usec_t n; + + n = now(CLOCK_MONOTONIC); + if (n >= timeout) + return -ETIMEDOUT; + + left = timeout - n; + } else + left = (uint64_t) -1; + + r = rtnl_poll(rtnl, true, left); + if (r < 0) + return r; + else if (r == 0) + return -ETIMEDOUT; + } +} + +int sd_netlink_get_events(sd_netlink *rtnl) { + assert_return(rtnl, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + + if (rtnl->rqueue_size == 0) + return POLLIN; + else + return 0; +} + +int sd_netlink_get_timeout(sd_netlink *rtnl, uint64_t *timeout_usec) { + struct reply_callback *c; + + assert_return(rtnl, -EINVAL); + assert_return(timeout_usec, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + + if (rtnl->rqueue_size > 0) { + *timeout_usec = 0; + return 1; + } + + c = prioq_peek(rtnl->reply_callbacks_prioq); + if (!c) { + *timeout_usec = (uint64_t) -1; + return 0; + } + + *timeout_usec = c->timeout; + + return 1; +} + +static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_netlink *rtnl = userdata; + int r; + + assert(rtnl); + + r = sd_netlink_process(rtnl, NULL); + if (r < 0) + return r; + + return 1; +} + +static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { + sd_netlink *rtnl = userdata; + int r; + + assert(rtnl); + + r = sd_netlink_process(rtnl, NULL); + if (r < 0) + return r; + + return 1; +} + +static int prepare_callback(sd_event_source *s, void *userdata) { + sd_netlink *rtnl = userdata; + int r, e; + usec_t until; + + assert(s); + assert(rtnl); + + e = sd_netlink_get_events(rtnl); + if (e < 0) + return e; + + r = sd_event_source_set_io_events(rtnl->io_event_source, e); + if (r < 0) + return r; + + r = sd_netlink_get_timeout(rtnl, &until); + if (r < 0) + return r; + if (r > 0) { + int j; + + j = sd_event_source_set_time(rtnl->time_event_source, until); + if (j < 0) + return j; + } + + r = sd_event_source_set_enabled(rtnl->time_event_source, r > 0); + if (r < 0) + return r; + + return 1; +} + +int sd_netlink_attach_event(sd_netlink *rtnl, sd_event *event, int priority) { + int r; + + assert_return(rtnl, -EINVAL); + assert_return(!rtnl->event, -EBUSY); + + assert(!rtnl->io_event_source); + assert(!rtnl->time_event_source); + + if (event) + rtnl->event = sd_event_ref(event); + else { + r = sd_event_default(&rtnl->event); + if (r < 0) + return r; + } + + r = sd_event_add_io(rtnl->event, &rtnl->io_event_source, rtnl->fd, 0, io_callback, rtnl); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(rtnl->io_event_source, priority); + if (r < 0) + goto fail; + + r = sd_event_source_set_description(rtnl->io_event_source, "rtnl-receive-message"); + if (r < 0) + goto fail; + + r = sd_event_source_set_prepare(rtnl->io_event_source, prepare_callback); + if (r < 0) + goto fail; + + r = sd_event_add_time(rtnl->event, &rtnl->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, rtnl); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(rtnl->time_event_source, priority); + if (r < 0) + goto fail; + + r = sd_event_source_set_description(rtnl->time_event_source, "rtnl-timer"); + if (r < 0) + goto fail; + + return 0; + +fail: + sd_netlink_detach_event(rtnl); + return r; +} + +int sd_netlink_detach_event(sd_netlink *rtnl) { + assert_return(rtnl, -EINVAL); + assert_return(rtnl->event, -ENXIO); + + rtnl->io_event_source = sd_event_source_unref(rtnl->io_event_source); + + rtnl->time_event_source = sd_event_source_unref(rtnl->time_event_source); + + rtnl->event = sd_event_unref(rtnl->event); + + return 0; +} + +int sd_netlink_add_match(sd_netlink *rtnl, + uint16_t type, + sd_netlink_message_handler_t callback, + void *userdata) { + _cleanup_free_ struct match_callback *c = NULL; + int r; + + assert_return(rtnl, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + + c = new0(struct match_callback, 1); + if (!c) + return -ENOMEM; + + c->callback = callback; + c->type = type; + c->userdata = userdata; + + switch (type) { + case RTM_NEWLINK: + case RTM_DELLINK: + r = socket_broadcast_group_ref(rtnl, RTNLGRP_LINK); + if (r < 0) + return r; + + break; + case RTM_NEWADDR: + case RTM_DELADDR: + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_IFADDR); + if (r < 0) + return r; + + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_IFADDR); + if (r < 0) + return r; + + break; + case RTM_NEWROUTE: + case RTM_DELROUTE: + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_ROUTE); + if (r < 0) + return r; + + r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_ROUTE); + if (r < 0) + return r; + break; + default: + return -EOPNOTSUPP; + } + + LIST_PREPEND(match_callbacks, rtnl->match_callbacks, c); + + c = NULL; + + return 0; +} + +int sd_netlink_remove_match(sd_netlink *rtnl, + uint16_t type, + sd_netlink_message_handler_t callback, + void *userdata) { + struct match_callback *c; + int r; + + assert_return(rtnl, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!rtnl_pid_changed(rtnl), -ECHILD); + + LIST_FOREACH(match_callbacks, c, rtnl->match_callbacks) + if (c->callback == callback && c->type == type && c->userdata == userdata) { + LIST_REMOVE(match_callbacks, rtnl->match_callbacks, c); + free(c); + + switch (type) { + case RTM_NEWLINK: + case RTM_DELLINK: + r = socket_broadcast_group_unref(rtnl, RTNLGRP_LINK); + if (r < 0) + return r; + + break; + case RTM_NEWADDR: + case RTM_DELADDR: + r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV4_IFADDR); + if (r < 0) + return r; + + r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV6_IFADDR); + if (r < 0) + return r; + + break; + case RTM_NEWROUTE: + case RTM_DELROUTE: + r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV4_ROUTE); + if (r < 0) + return r; + + r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV6_ROUTE); + if (r < 0) + return r; + break; + default: + return -EOPNOTSUPP; + } + + return 1; + } + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/test-local-addresses.c b/src/libsystemd/libsystemd-internal/sd-netlink/test-local-addresses.c new file mode 100644 index 0000000000..e0e28cc0cc --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/test-local-addresses.c @@ -0,0 +1,56 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "af-list.h" +#include "alloc-util.h" +#include "in-addr-util.h" +#include "local-addresses.h" + +static void print_local_addresses(struct local_address *a, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) { + _cleanup_free_ char *b = NULL; + + assert_se(in_addr_to_string(a[i].family, &a[i].address, &b) >= 0); + printf("%s if%i scope=%i metric=%u address=%s\n", af_to_name(a[i].family), a[i].ifindex, a[i].scope, a[i].metric, b); + } +} + +int main(int argc, char *argv[]) { + struct local_address *a; + int n; + + a = NULL; + n = local_addresses(NULL, 0, AF_UNSPEC, &a); + assert_se(n >= 0); + + printf("Local Addresses:\n"); + print_local_addresses(a, (unsigned) n); + a = mfree(a); + + n = local_gateways(NULL, 0, AF_UNSPEC, &a); + assert_se(n >= 0); + + printf("Local Gateways:\n"); + print_local_addresses(a, (unsigned) n); + free(a); + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-netlink/test-netlink.c b/src/libsystemd/libsystemd-internal/sd-netlink/test-netlink.c new file mode 100644 index 0000000000..e16c35144d --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-netlink/test-netlink.c @@ -0,0 +1,440 @@ +/*** + This file is part of systemd. + + Copyright 2013 Tom Gundersen + + 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 . +***/ + +#include +#include + +#include + +#include "ether-addr-util.h" +#include "macro.h" +#include "missing.h" +#include "netlink-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "util.h" + +static void test_message_link_bridge(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + uint32_t cost; + + assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_NEWLINK, 1) >= 0); + assert_se(sd_rtnl_message_link_set_family(message, PF_BRIDGE) >= 0); + assert_se(sd_netlink_message_open_container(message, IFLA_PROTINFO) >= 0); + assert_se(sd_netlink_message_append_u32(message, IFLA_BRPORT_COST, 10) >= 0); + assert_se(sd_netlink_message_close_container(message) >= 0); + + assert_se(sd_netlink_message_rewind(message) >= 0); + + assert_se(sd_netlink_message_enter_container(message, IFLA_PROTINFO) >= 0); + assert_se(sd_netlink_message_read_u32(message, IFLA_BRPORT_COST, &cost) >= 0); + assert_se(cost == 10); + assert_se(sd_netlink_message_exit_container(message) >= 0); +} + +static void test_link_configure(sd_netlink *rtnl, int ifindex) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + const char *mac = "98:fe:94:3f:c6:18", *name = "test"; + char buffer[ETHER_ADDR_TO_STRING_MAX]; + unsigned int mtu = 1450, mtu_out; + const char *name_out; + struct ether_addr mac_out; + + /* we'd really like to test NEWLINK, but let's not mess with the running kernel */ + assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_GETLINK, ifindex) >= 0); + assert_se(sd_netlink_message_append_string(message, IFLA_IFNAME, name) >= 0); + assert_se(sd_netlink_message_append_ether_addr(message, IFLA_ADDRESS, ether_aton(mac)) >= 0); + assert_se(sd_netlink_message_append_u32(message, IFLA_MTU, mtu) >= 0); + + assert_se(sd_netlink_call(rtnl, message, 0, NULL) == 1); + assert_se(sd_netlink_message_rewind(message) >= 0); + + assert_se(sd_netlink_message_read_string(message, IFLA_IFNAME, &name_out) >= 0); + assert_se(streq(name, name_out)); + + assert_se(sd_netlink_message_read_ether_addr(message, IFLA_ADDRESS, &mac_out) >= 0); + assert_se(streq(mac, ether_addr_to_string(&mac_out, buffer))); + + assert_se(sd_netlink_message_read_u32(message, IFLA_MTU, &mtu_out) >= 0); + assert_se(mtu == mtu_out); +} + +static void test_link_get(sd_netlink *rtnl, int ifindex) { + sd_netlink_message *m; + sd_netlink_message *r; + unsigned int mtu = 1500; + const char *str_data; + uint8_t u8_data; + uint32_t u32_data; + struct ether_addr eth_data; + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + assert_se(m); + + /* u8 test cases */ + assert_se(sd_netlink_message_append_u8(m, IFLA_CARRIER, 0) >= 0); + assert_se(sd_netlink_message_append_u8(m, IFLA_OPERSTATE, 0) >= 0); + assert_se(sd_netlink_message_append_u8(m, IFLA_LINKMODE, 0) >= 0); + + /* u32 test cases */ + assert_se(sd_netlink_message_append_u32(m, IFLA_MTU, mtu) >= 0); + assert_se(sd_netlink_message_append_u32(m, IFLA_GROUP, 0) >= 0); + assert_se(sd_netlink_message_append_u32(m, IFLA_TXQLEN, 0) >= 0); + assert_se(sd_netlink_message_append_u32(m, IFLA_NUM_TX_QUEUES, 0) >= 0); + assert_se(sd_netlink_message_append_u32(m, IFLA_NUM_RX_QUEUES, 0) >= 0); + + assert_se(sd_netlink_call(rtnl, m, -1, &r) == 1); + + assert_se(sd_netlink_message_read_string(r, IFLA_IFNAME, &str_data) == 0); + + assert_se(sd_netlink_message_read_u8(r, IFLA_CARRIER, &u8_data) == 0); + assert_se(sd_netlink_message_read_u8(r, IFLA_OPERSTATE, &u8_data) == 0); + assert_se(sd_netlink_message_read_u8(r, IFLA_LINKMODE, &u8_data) == 0); + + assert_se(sd_netlink_message_read_u32(r, IFLA_MTU, &u32_data) == 0); + assert_se(sd_netlink_message_read_u32(r, IFLA_GROUP, &u32_data) == 0); + assert_se(sd_netlink_message_read_u32(r, IFLA_TXQLEN, &u32_data) == 0); + assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_TX_QUEUES, &u32_data) == 0); + assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_RX_QUEUES, &u32_data) == 0); + + assert_se(sd_netlink_message_read_ether_addr(r, IFLA_ADDRESS, ð_data) == 0); + + assert_se((m = sd_netlink_message_unref(m)) == NULL); + assert_se((r = sd_netlink_message_unref(r)) == NULL); +} + + +static void test_address_get(sd_netlink *rtnl, int ifindex) { + sd_netlink_message *m; + sd_netlink_message *r; + struct in_addr in_data; + struct ifa_cacheinfo cache; + const char *label; + + assert_se(sd_rtnl_message_new_addr(rtnl, &m, RTM_GETADDR, ifindex, AF_INET) >= 0); + assert_se(m); + + assert_se(sd_netlink_call(rtnl, m, -1, &r) == 1); + + assert_se(sd_netlink_message_read_in_addr(r, IFA_LOCAL, &in_data) == 0); + assert_se(sd_netlink_message_read_in_addr(r, IFA_ADDRESS, &in_data) == 0); + assert_se(sd_netlink_message_read_string(r, IFA_LABEL, &label) == 0); + assert_se(sd_netlink_message_read_cache_info(r, IFA_CACHEINFO, &cache) == 0); + + assert_se((m = sd_netlink_message_unref(m)) == NULL); + assert_se((r = sd_netlink_message_unref(r)) == NULL); + +} + +static void test_route(void) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req; + struct in_addr addr, addr_data; + uint32_t index = 2, u32_data; + int r; + + r = sd_rtnl_message_new_route(NULL, &req, RTM_NEWROUTE, AF_INET, RTPROT_STATIC); + if (r < 0) { + log_error_errno(r, "Could not create RTM_NEWROUTE message: %m"); + return; + } + + addr.s_addr = htonl(INADDR_LOOPBACK); + + r = sd_netlink_message_append_in_addr(req, RTA_GATEWAY, &addr); + if (r < 0) { + log_error_errno(r, "Could not append RTA_GATEWAY attribute: %m"); + return; + } + + r = sd_netlink_message_append_u32(req, RTA_OIF, index); + if (r < 0) { + log_error_errno(r, "Could not append RTA_OIF attribute: %m"); + return; + } + + assert_se(sd_netlink_message_rewind(req) >= 0); + + assert_se(sd_netlink_message_read_in_addr(req, RTA_GATEWAY, &addr_data) >= 0); + assert_se(addr_data.s_addr == addr.s_addr); + + assert_se(sd_netlink_message_read_u32(req, RTA_OIF, &u32_data) >= 0); + assert_se(u32_data == index); + + assert_se((req = sd_netlink_message_unref(req)) == NULL); +} + +static void test_multiple(void) { + sd_netlink *rtnl1, *rtnl2; + + assert_se(sd_netlink_open(&rtnl1) >= 0); + assert_se(sd_netlink_open(&rtnl2) >= 0); + + rtnl1 = sd_netlink_unref(rtnl1); + rtnl2 = sd_netlink_unref(rtnl2); +} + +static int link_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + char *ifname = userdata; + const char *data; + + assert_se(rtnl); + assert_se(m); + + log_info("got link info about %s", ifname); + free(ifname); + + assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &data) >= 0); + assert_se(streq(data, "lo")); + + return 1; +} + +static void test_event_loop(int ifindex) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + char *ifname; + + ifname = strdup("lo2"); + assert_se(ifname); + + assert_se(sd_netlink_open(&rtnl) >= 0); + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + + assert_se(sd_netlink_call_async(rtnl, m, &link_handler, ifname, 0, NULL) >= 0); + + assert_se(sd_event_default(&event) >= 0); + + assert_se(sd_netlink_attach_event(rtnl, event, 0) >= 0); + + assert_se(sd_event_run(event, 0) >= 0); + + assert_se(sd_netlink_detach_event(rtnl) >= 0); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +} + +static int pipe_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + int *counter = userdata; + int r; + + (*counter) --; + + r = sd_netlink_message_get_errno(m); + + log_info_errno(r, "%d left in pipe. got reply: %m", *counter); + + assert_se(r >= 0); + + return 1; +} + +static void test_async(int ifindex) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; + uint32_t serial; + char *ifname; + + ifname = strdup("lo"); + assert_se(ifname); + + assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); + + assert_se(sd_netlink_call_async(rtnl, m, &link_handler, ifname, 0, &serial) >= 0); + + assert_se(sd_netlink_wait(rtnl, 0) >= 0); + assert_se(sd_netlink_process(rtnl, &r) >= 0); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +} + +static void test_pipe(int ifindex) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m1 = NULL, *m2 = NULL; + int counter = 0; + + assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m1, RTM_GETLINK, ifindex) >= 0); + assert_se(sd_rtnl_message_new_link(rtnl, &m2, RTM_GETLINK, ifindex) >= 0); + + counter ++; + assert_se(sd_netlink_call_async(rtnl, m1, &pipe_handler, &counter, 0, NULL) >= 0); + + counter ++; + assert_se(sd_netlink_call_async(rtnl, m2, &pipe_handler, &counter, 0, NULL) >= 0); + + while (counter > 0) { + assert_se(sd_netlink_wait(rtnl, 0) >= 0); + assert_se(sd_netlink_process(rtnl, NULL) >= 0); + } + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +} + +static void test_container(void) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + uint16_t u16_data; + uint32_t u32_data; + const char *string_data; + + assert_se(sd_rtnl_message_new_link(NULL, &m, RTM_NEWLINK, 0) >= 0); + + assert_se(sd_netlink_message_open_container(m, IFLA_LINKINFO) >= 0); + assert_se(sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "vlan") >= 0); + assert_se(sd_netlink_message_append_u16(m, IFLA_VLAN_ID, 100) >= 0); + assert_se(sd_netlink_message_close_container(m) >= 0); + assert_se(sd_netlink_message_append_string(m, IFLA_INFO_KIND, "vlan") >= 0); + assert_se(sd_netlink_message_close_container(m) >= 0); + assert_se(sd_netlink_message_close_container(m) == -EINVAL); + + assert_se(sd_netlink_message_rewind(m) >= 0); + + assert_se(sd_netlink_message_enter_container(m, IFLA_LINKINFO) >= 0); + assert_se(sd_netlink_message_read_string(m, IFLA_INFO_KIND, &string_data) >= 0); + assert_se(streq("vlan", string_data)); + + assert_se(sd_netlink_message_enter_container(m, IFLA_INFO_DATA) >= 0); + assert_se(sd_netlink_message_read_u16(m, IFLA_VLAN_ID, &u16_data) >= 0); + assert_se(sd_netlink_message_exit_container(m) >= 0); + + assert_se(sd_netlink_message_read_string(m, IFLA_INFO_KIND, &string_data) >= 0); + assert_se(streq("vlan", string_data)); + assert_se(sd_netlink_message_exit_container(m) >= 0); + + assert_se(sd_netlink_message_read_u32(m, IFLA_LINKINFO, &u32_data) < 0); + + assert_se(sd_netlink_message_exit_container(m) == -EINVAL); +} + +static void test_match(void) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + + assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_netlink_add_match(rtnl, RTM_NEWLINK, &link_handler, NULL) >= 0); + assert_se(sd_netlink_add_match(rtnl, RTM_NEWLINK, &link_handler, NULL) >= 0); + + assert_se(sd_netlink_remove_match(rtnl, RTM_NEWLINK, &link_handler, NULL) == 1); + assert_se(sd_netlink_remove_match(rtnl, RTM_NEWLINK, &link_handler, NULL) == 1); + assert_se(sd_netlink_remove_match(rtnl, RTM_NEWLINK, &link_handler, NULL) == 0); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +} + +static void test_get_addresses(sd_netlink *rtnl) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + sd_netlink_message *m; + + assert_se(sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC) >= 0); + + assert_se(sd_netlink_call(rtnl, req, 0, &reply) >= 0); + + for (m = reply; m; m = sd_netlink_message_next(m)) { + uint16_t type; + unsigned char scope, flags; + int family, ifindex; + + assert_se(sd_netlink_message_get_type(m, &type) >= 0); + assert_se(type == RTM_NEWADDR); + + assert_se(sd_rtnl_message_addr_get_ifindex(m, &ifindex) >= 0); + assert_se(sd_rtnl_message_addr_get_family(m, &family) >= 0); + assert_se(sd_rtnl_message_addr_get_scope(m, &scope) >= 0); + assert_se(sd_rtnl_message_addr_get_flags(m, &flags) >= 0); + + assert_se(ifindex > 0); + assert_se(family == AF_INET || family == AF_INET6); + + log_info("got IPv%u address on ifindex %i", family == AF_INET ? 4: 6, ifindex); + } +} + +static void test_message(void) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + + assert_se(rtnl_message_new_synthetic_error(-ETIMEDOUT, 1, &m) >= 0); + assert_se(sd_netlink_message_get_errno(m) == -ETIMEDOUT); +} + +int main(void) { + sd_netlink *rtnl; + sd_netlink_message *m; + sd_netlink_message *r; + const char *string_data; + int if_loopback; + uint16_t type; + + test_message(); + + test_match(); + + test_multiple(); + + test_route(); + + test_container(); + + assert_se(sd_netlink_open(&rtnl) >= 0); + assert_se(rtnl); + + if_loopback = (int) if_nametoindex("lo"); + assert_se(if_loopback > 0); + + test_async(if_loopback); + + test_pipe(if_loopback); + + test_event_loop(if_loopback); + + test_link_configure(rtnl, if_loopback); + + test_get_addresses(rtnl); + + test_message_link_bridge(rtnl); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, if_loopback) >= 0); + assert_se(m); + + assert_se(sd_netlink_message_get_type(m, &type) >= 0); + assert_se(type == RTM_GETLINK); + + assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &string_data) == -EPERM); + + assert_se(sd_netlink_call(rtnl, m, 0, &r) == 1); + assert_se(sd_netlink_message_get_type(r, &type) >= 0); + assert_se(type == RTM_NEWLINK); + + assert_se((r = sd_netlink_message_unref(r)) == NULL); + + assert_se(sd_netlink_call(rtnl, m, -1, &r) == -EPERM); + assert_se((m = sd_netlink_message_unref(m)) == NULL); + assert_se((r = sd_netlink_message_unref(r)) == NULL); + + test_link_get(rtnl, if_loopback); + test_address_get(rtnl, if_loopback); + + assert_se((m = sd_netlink_message_unref(m)) == NULL); + assert_se((r = sd_netlink_message_unref(r)) == NULL); + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + + return EXIT_SUCCESS; +} diff --git a/src/libsystemd/libsystemd-internal/sd-network/network-util.c b/src/libsystemd/libsystemd-internal/sd-network/network-util.c new file mode 100644 index 0000000000..a0d9b5f1a4 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-network/network-util.c @@ -0,0 +1,37 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "alloc-util.h" +#include "fd-util.h" +#include "network-util.h" +#include "strv.h" + +bool network_is_online(void) { + _cleanup_free_ char *state = NULL; + int r; + + r = sd_network_get_operational_state(&state); + if (r < 0) /* if we don't know anything, we consider the system online */ + return true; + + if (STR_IN_SET(state, "routable", "degraded")) + return true; + + return false; +} diff --git a/src/libsystemd/libsystemd-internal/sd-network/network-util.h b/src/libsystemd/libsystemd-internal/sd-network/network-util.h new file mode 100644 index 0000000000..8c4dbc68b1 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-network/network-util.h @@ -0,0 +1,24 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Thomas Hindø Paabøl Andersen + + 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 . +***/ + +#include + +bool network_is_online(void); diff --git a/src/libsystemd/libsystemd-internal/sd-network/sd-network.c b/src/libsystemd/libsystemd-internal/sd-network/sd-network.c new file mode 100644 index 0000000000..5ebe7de7af --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-network/sd-network.c @@ -0,0 +1,373 @@ +/*** + This file is part of systemd. + + Copyright 2011 Lennart Poettering + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fs-util.h" +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +_public_ int sd_network_get_operational_state(char **state) { + _cleanup_free_ char *s = NULL; + int r; + + assert_return(state, -EINVAL); + + r = parse_env_file("/run/systemd/netif/state", NEWLINE, "OPER_STATE", &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + *state = s; + s = NULL; + + return 0; +} + +static int network_get_strv(const char *key, char ***ret) { + _cleanup_strv_free_ char **a = NULL; + _cleanup_free_ char *s = NULL; + int r; + + assert_return(ret, -EINVAL); + + r = parse_env_file("/run/systemd/netif/state", NEWLINE, key, &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) { + *ret = NULL; + return 0; + } + + a = strv_split(s, " "); + if (!a) + return -ENOMEM; + + strv_uniq(a); + r = strv_length(a); + + *ret = a; + a = NULL; + + return r; +} + +_public_ int sd_network_get_dns(char ***ret) { + return network_get_strv("DNS", ret); +} + +_public_ int sd_network_get_ntp(char ***ret) { + return network_get_strv("NTP", ret); +} + +_public_ int sd_network_get_search_domains(char ***ret) { + return network_get_strv("DOMAINS", ret); +} + +_public_ int sd_network_get_route_domains(char ***ret) { + return network_get_strv("ROUTE_DOMAINS", ret); +} + +static int network_link_get_string(int ifindex, const char *field, char **ret) { + _cleanup_free_ char *s = NULL, *p = NULL; + int r; + + assert_return(ifindex > 0, -EINVAL); + assert_return(ret, -EINVAL); + + if (asprintf(&p, "/run/systemd/netif/links/%i", ifindex) < 0) + return -ENOMEM; + + r = parse_env_file(p, NEWLINE, field, &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) + return -ENODATA; + + *ret = s; + s = NULL; + + return 0; +} + +static int network_link_get_strv(int ifindex, const char *key, char ***ret) { + _cleanup_free_ char *p = NULL, *s = NULL; + _cleanup_strv_free_ char **a = NULL; + int r; + + assert_return(ifindex > 0, -EINVAL); + assert_return(ret, -EINVAL); + + if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) + return -ENOMEM; + + r = parse_env_file(p, NEWLINE, key, &s, NULL); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (isempty(s)) { + *ret = NULL; + return 0; + } + + a = strv_split(s, " "); + if (!a) + return -ENOMEM; + + strv_uniq(a); + r = strv_length(a); + + *ret = a; + a = NULL; + + return r; +} + +_public_ int sd_network_link_get_setup_state(int ifindex, char **state) { + return network_link_get_string(ifindex, "ADMIN_STATE", state); +} + +_public_ int sd_network_link_get_network_file(int ifindex, char **filename) { + return network_link_get_string(ifindex, "NETWORK_FILE", filename); +} + +_public_ int sd_network_link_get_operational_state(int ifindex, char **state) { + return network_link_get_string(ifindex, "OPER_STATE", state); +} + +_public_ int sd_network_link_get_llmnr(int ifindex, char **llmnr) { + return network_link_get_string(ifindex, "LLMNR", llmnr); +} + +_public_ int sd_network_link_get_mdns(int ifindex, char **mdns) { + return network_link_get_string(ifindex, "MDNS", mdns); +} + +_public_ int sd_network_link_get_dnssec(int ifindex, char **dnssec) { + return network_link_get_string(ifindex, "DNSSEC", dnssec); +} + +_public_ int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta) { + return network_link_get_strv(ifindex, "DNSSEC_NTA", nta); +} + +_public_ int sd_network_link_get_lldp(int ifindex, char **lldp) { + _cleanup_free_ char *s = NULL, *p = NULL; + size_t size; + int r; + + assert_return(ifindex > 0, -EINVAL); + assert_return(lldp, -EINVAL); + + if (asprintf(&p, "/run/systemd/netif/lldp/%d", ifindex) < 0) + return -ENOMEM; + + r = read_full_file(p, &s, &size); + if (r == -ENOENT) + return -ENODATA; + if (r < 0) + return r; + if (size <= 0) + return -ENODATA; + + *lldp = s; + s = NULL; + + return 0; +} + +int sd_network_link_get_timezone(int ifindex, char **ret) { + return network_link_get_string(ifindex, "TIMEZONE", ret); +} + +_public_ int sd_network_link_get_dns(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "DNS", ret); +} + +_public_ int sd_network_link_get_ntp(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "NTP", ret); +} + +_public_ int sd_network_link_get_search_domains(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "DOMAINS", ret); +} + +_public_ int sd_network_link_get_route_domains(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "ROUTE_DOMAINS", ret); +} + +_public_ int sd_network_link_get_carrier_bound_to(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "CARRIER_BOUND_TO", ret); +} + +_public_ int sd_network_link_get_carrier_bound_by(int ifindex, char ***ret) { + return network_link_get_strv(ifindex, "CARRIER_BOUND_BY", ret); +} + +static inline int MONITOR_TO_FD(sd_network_monitor *m) { + return (int) (unsigned long) m - 1; +} + +static inline sd_network_monitor* FD_TO_MONITOR(int fd) { + return (sd_network_monitor*) (unsigned long) (fd + 1); +} + +static int monitor_add_inotify_watch(int fd) { + int k; + + k = inotify_add_watch(fd, "/run/systemd/netif/links/", IN_MOVED_TO|IN_DELETE); + if (k >= 0) + return 0; + else if (errno != ENOENT) + return -errno; + + k = inotify_add_watch(fd, "/run/systemd/netif/", IN_CREATE|IN_ISDIR); + if (k >= 0) + return 0; + else if (errno != ENOENT) + return -errno; + + k = inotify_add_watch(fd, "/run/systemd/", IN_CREATE|IN_ISDIR); + if (k < 0) + return -errno; + + return 0; +} + +_public_ int sd_network_monitor_new(sd_network_monitor **m, const char *category) { + _cleanup_close_ int fd = -1; + int k; + bool good = false; + + assert_return(m, -EINVAL); + + fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); + if (fd < 0) + return -errno; + + if (!category || streq(category, "links")) { + k = monitor_add_inotify_watch(fd); + if (k < 0) + return k; + + good = true; + } + + if (!good) + return -EINVAL; + + *m = FD_TO_MONITOR(fd); + fd = -1; + + return 0; +} + +_public_ sd_network_monitor* sd_network_monitor_unref(sd_network_monitor *m) { + int fd; + + if (m) { + fd = MONITOR_TO_FD(m); + close_nointr(fd); + } + + return NULL; +} + +_public_ int sd_network_monitor_flush(sd_network_monitor *m) { + union inotify_event_buffer buffer; + struct inotify_event *e; + ssize_t l; + int fd, k; + + assert_return(m, -EINVAL); + + fd = MONITOR_TO_FD(m); + + l = read(fd, &buffer, sizeof(buffer)); + if (l < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + FOREACH_INOTIFY_EVENT(e, buffer, l) { + if (e->mask & IN_ISDIR) { + k = monitor_add_inotify_watch(fd); + if (k < 0) + return k; + + k = inotify_rm_watch(fd, e->wd); + if (k < 0) + return -errno; + } + } + + return 0; +} + +_public_ int sd_network_monitor_get_fd(sd_network_monitor *m) { + + assert_return(m, -EINVAL); + + return MONITOR_TO_FD(m); +} + +_public_ int sd_network_monitor_get_events(sd_network_monitor *m) { + + assert_return(m, -EINVAL); + + /* For now we will only return POLLIN here, since we don't + * need anything else ever for inotify. However, let's have + * this API to keep our options open should we later on need + * it. */ + return POLLIN; +} + +_public_ int sd_network_monitor_get_timeout(sd_network_monitor *m, uint64_t *timeout_usec) { + + assert_return(m, -EINVAL); + assert_return(timeout_usec, -EINVAL); + + /* For now we will only return (uint64_t) -1, since we don't + * need any timeout. However, let's have this API to keep our + * options open should we later on need it. */ + *timeout_usec = (uint64_t) -1; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-path/sd-path.c b/src/libsystemd/libsystemd-internal/sd-path/sd-path.c new file mode 100644 index 0000000000..61a8b092da --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-path/sd-path.c @@ -0,0 +1,633 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "alloc-util.h" +#include "architecture.h" +#include "fd-util.h" +#include "fileio.h" +#include "missing.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +static int from_environment(const char *envname, const char *fallback, const char **ret) { + assert(ret); + + if (envname) { + const char *e; + + e = secure_getenv(envname); + if (e && path_is_absolute(e)) { + *ret = e; + return 0; + } + } + + if (fallback) { + *ret = fallback; + return 0; + } + + return -ENXIO; +} + +static int from_home_dir(const char *envname, const char *suffix, char **buffer, const char **ret) { + _cleanup_free_ char *h = NULL; + char *cc = NULL; + int r; + + assert(suffix); + assert(buffer); + assert(ret); + + if (envname) { + const char *e = NULL; + + e = secure_getenv(envname); + if (e && path_is_absolute(e)) { + *ret = e; + return 0; + } + } + + r = get_home_dir(&h); + if (r < 0) + return r; + + if (endswith(h, "/")) + cc = strappend(h, suffix); + else + cc = strjoin(h, "/", suffix, NULL); + if (!cc) + return -ENOMEM; + + *buffer = cc; + *ret = cc; + return 0; +} + +static int from_user_dir(const char *field, char **buffer, const char **ret) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *b = NULL; + const char *fn = NULL; + char line[LINE_MAX]; + size_t n; + int r; + + assert(field); + assert(buffer); + assert(ret); + + r = from_home_dir(NULL, ".config/user-dirs.dirs", &b, &fn); + if (r < 0) + return r; + + f = fopen(fn, "re"); + if (!f) { + if (errno == ENOENT) + goto fallback; + + return -errno; + } + + /* This is an awful parse, but it follows closely what + * xdg-user-dirs does upstream */ + + n = strlen(field); + FOREACH_LINE(line, f, return -errno) { + char *l, *p, *e; + + l = strstrip(line); + + if (!strneq(l, field, n)) + continue; + + p = l + n; + p += strspn(p, WHITESPACE); + + if (*p != '=') + continue; + p++; + + p += strspn(p, WHITESPACE); + + if (*p != '"') + continue; + p++; + + e = strrchr(p, '"'); + if (!e) + continue; + *e = 0; + + /* Three syntaxes permitted: relative to $HOME, $HOME itself, and absolute path */ + if (startswith(p, "$HOME/")) { + _cleanup_free_ char *h = NULL; + char *cc; + + r = get_home_dir(&h); + if (r < 0) + return r; + + cc = strappend(h, p+5); + if (!cc) + return -ENOMEM; + + *buffer = cc; + *ret = cc; + return 0; + } else if (streq(p, "$HOME")) { + + r = get_home_dir(buffer); + if (r < 0) + return r; + + *ret = *buffer; + return 0; + } else if (path_is_absolute(p)) { + char *copy; + + copy = strdup(p); + if (!copy) + return -ENOMEM; + + *buffer = copy; + *ret = copy; + return 0; + } + } + +fallback: + /* The desktop directory defaults to $HOME/Desktop, the others to $HOME */ + if (streq(field, "XDG_DESKTOP_DIR")) { + _cleanup_free_ char *h = NULL; + char *cc; + + r = get_home_dir(&h); + if (r < 0) + return r; + + cc = strappend(h, "/Desktop"); + if (!cc) + return -ENOMEM; + + *buffer = cc; + *ret = cc; + } else { + + r = get_home_dir(buffer); + if (r < 0) + return r; + + *ret = *buffer; + } + + return 0; +} + +static int get_path(uint64_t type, char **buffer, const char **ret) { + int r; + + assert(buffer); + assert(ret); + + switch (type) { + + case SD_PATH_TEMPORARY: + return from_environment("TMPDIR", "/tmp", ret); + + case SD_PATH_TEMPORARY_LARGE: + return from_environment("TMPDIR", "/var/tmp", ret); + + case SD_PATH_SYSTEM_BINARIES: + *ret = "/usr/bin"; + return 0; + + case SD_PATH_SYSTEM_INCLUDE: + *ret = "/usr/include"; + return 0; + + case SD_PATH_SYSTEM_LIBRARY_PRIVATE: + *ret = "/usr/lib"; + return 0; + + case SD_PATH_SYSTEM_LIBRARY_ARCH: + *ret = LIBDIR; + return 0; + + case SD_PATH_SYSTEM_SHARED: + *ret = "/usr/share"; + return 0; + + case SD_PATH_SYSTEM_CONFIGURATION_FACTORY: + *ret = "/usr/share/factory/etc"; + return 0; + + case SD_PATH_SYSTEM_STATE_FACTORY: + *ret = "/usr/share/factory/var"; + return 0; + + case SD_PATH_SYSTEM_CONFIGURATION: + *ret = "/etc"; + return 0; + + case SD_PATH_SYSTEM_RUNTIME: + *ret = "/run"; + return 0; + + case SD_PATH_SYSTEM_RUNTIME_LOGS: + *ret = "/run/log"; + return 0; + + case SD_PATH_SYSTEM_STATE_PRIVATE: + *ret = "/var/lib"; + return 0; + + case SD_PATH_SYSTEM_STATE_LOGS: + *ret = "/var/log"; + return 0; + + case SD_PATH_SYSTEM_STATE_CACHE: + *ret = "/var/cache"; + return 0; + + case SD_PATH_SYSTEM_STATE_SPOOL: + *ret = "/var/spool"; + return 0; + + case SD_PATH_USER_BINARIES: + return from_home_dir(NULL, ".local/bin", buffer, ret); + + case SD_PATH_USER_LIBRARY_PRIVATE: + return from_home_dir(NULL, ".local/lib", buffer, ret); + + case SD_PATH_USER_LIBRARY_ARCH: + return from_home_dir(NULL, ".local/lib/" LIB_ARCH_TUPLE, buffer, ret); + + case SD_PATH_USER_SHARED: + return from_home_dir("XDG_DATA_HOME", ".local/share", buffer, ret); + + case SD_PATH_USER_CONFIGURATION: + return from_home_dir("XDG_CONFIG_HOME", ".config", buffer, ret); + + case SD_PATH_USER_RUNTIME: + return from_environment("XDG_RUNTIME_DIR", NULL, ret); + + case SD_PATH_USER_STATE_CACHE: + return from_home_dir("XDG_CACHE_HOME", ".cache", buffer, ret); + + case SD_PATH_USER: + r = get_home_dir(buffer); + if (r < 0) + return r; + + *ret = *buffer; + return 0; + + case SD_PATH_USER_DOCUMENTS: + return from_user_dir("XDG_DOCUMENTS_DIR", buffer, ret); + + case SD_PATH_USER_MUSIC: + return from_user_dir("XDG_MUSIC_DIR", buffer, ret); + + case SD_PATH_USER_PICTURES: + return from_user_dir("XDG_PICTURES_DIR", buffer, ret); + + case SD_PATH_USER_VIDEOS: + return from_user_dir("XDG_VIDEOS_DIR", buffer, ret); + + case SD_PATH_USER_DOWNLOAD: + return from_user_dir("XDG_DOWNLOAD_DIR", buffer, ret); + + case SD_PATH_USER_PUBLIC: + return from_user_dir("XDG_PUBLICSHARE_DIR", buffer, ret); + + case SD_PATH_USER_TEMPLATES: + return from_user_dir("XDG_TEMPLATES_DIR", buffer, ret); + + case SD_PATH_USER_DESKTOP: + return from_user_dir("XDG_DESKTOP_DIR", buffer, ret); + } + + return -EOPNOTSUPP; +} + +_public_ int sd_path_home(uint64_t type, const char *suffix, char **path) { + char *buffer = NULL, *cc; + const char *ret; + int r; + + assert_return(path, -EINVAL); + + if (IN_SET(type, + SD_PATH_SEARCH_BINARIES, + SD_PATH_SEARCH_LIBRARY_PRIVATE, + SD_PATH_SEARCH_LIBRARY_ARCH, + SD_PATH_SEARCH_SHARED, + SD_PATH_SEARCH_CONFIGURATION_FACTORY, + SD_PATH_SEARCH_STATE_FACTORY, + SD_PATH_SEARCH_CONFIGURATION)) { + + _cleanup_strv_free_ char **l = NULL; + + r = sd_path_search(type, suffix, &l); + if (r < 0) + return r; + + buffer = strv_join(l, ":"); + if (!buffer) + return -ENOMEM; + + *path = buffer; + return 0; + } + + r = get_path(type, &buffer, &ret); + if (r < 0) + return r; + + if (!suffix) { + if (!buffer) { + buffer = strdup(ret); + if (!buffer) + return -ENOMEM; + } + + *path = buffer; + return 0; + } + + suffix += strspn(suffix, "/"); + + if (endswith(ret, "/")) + cc = strappend(ret, suffix); + else + cc = strjoin(ret, "/", suffix, NULL); + + free(buffer); + + if (!cc) + return -ENOMEM; + + *path = cc; + return 0; +} + +static int search_from_environment( + char ***list, + const char *env_home, + const char *home_suffix, + const char *env_search, + bool env_search_sufficient, + const char *first, ...) { + + const char *e; + char *h = NULL; + char **l = NULL; + int r; + + assert(list); + + if (env_search) { + e = secure_getenv(env_search); + if (e) { + l = strv_split(e, ":"); + if (!l) + return -ENOMEM; + + if (env_search_sufficient) { + *list = l; + return 0; + } + } + } + + if (!l && first) { + va_list ap; + + va_start(ap, first); + l = strv_new_ap(first, ap); + va_end(ap); + + if (!l) + return -ENOMEM; + } + + if (env_home) { + e = secure_getenv(env_home); + if (e && path_is_absolute(e)) { + h = strdup(e); + if (!h) { + strv_free(l); + return -ENOMEM; + } + } + } + + if (!h && home_suffix) { + e = secure_getenv("HOME"); + if (e && path_is_absolute(e)) { + if (endswith(e, "/")) + h = strappend(e, home_suffix); + else + h = strjoin(e, "/", home_suffix, NULL); + + if (!h) { + strv_free(l); + return -ENOMEM; + } + } + } + + if (h) { + r = strv_consume_prepend(&l, h); + if (r < 0) { + strv_free(l); + return -ENOMEM; + } + } + + *list = l; + return 0; +} + +static int get_search(uint64_t type, char ***list) { + + assert(list); + + switch(type) { + + case SD_PATH_SEARCH_BINARIES: + return search_from_environment(list, + NULL, + ".local/bin", + "PATH", + true, + "/usr/local/sbin", + "/usr/local/bin", + "/usr/sbin", + "/usr/bin", +#ifdef HAVE_SPLIT_USR + "/sbin", + "/bin", +#endif + NULL); + + case SD_PATH_SEARCH_LIBRARY_PRIVATE: + return search_from_environment(list, + NULL, + ".local/lib", + NULL, + false, + "/usr/local/lib", + "/usr/lib", +#ifdef HAVE_SPLIT_USR + "/lib", +#endif + NULL); + + case SD_PATH_SEARCH_LIBRARY_ARCH: + return search_from_environment(list, + NULL, + ".local/lib/" LIB_ARCH_TUPLE, + "LD_LIBRARY_PATH", + true, + LIBDIR, +#ifdef HAVE_SPLIT_USR + ROOTLIBDIR, +#endif + NULL); + + case SD_PATH_SEARCH_SHARED: + return search_from_environment(list, + "XDG_DATA_HOME", + ".local/share", + "XDG_DATA_DIRS", + false, + "/usr/local/share", + "/usr/share", + NULL); + + case SD_PATH_SEARCH_CONFIGURATION_FACTORY: + return search_from_environment(list, + NULL, + NULL, + NULL, + false, + "/usr/local/share/factory/etc", + "/usr/share/factory/etc", + NULL); + + case SD_PATH_SEARCH_STATE_FACTORY: + return search_from_environment(list, + NULL, + NULL, + NULL, + false, + "/usr/local/share/factory/var", + "/usr/share/factory/var", + NULL); + + case SD_PATH_SEARCH_CONFIGURATION: + return search_from_environment(list, + "XDG_CONFIG_HOME", + ".config", + "XDG_CONFIG_DIRS", + false, + "/etc", + NULL); + } + + return -EOPNOTSUPP; +} + +_public_ int sd_path_search(uint64_t type, const char *suffix, char ***paths) { + char **l, **i, **j, **n; + int r; + + assert_return(paths, -EINVAL); + + if (!IN_SET(type, + SD_PATH_SEARCH_BINARIES, + SD_PATH_SEARCH_LIBRARY_PRIVATE, + SD_PATH_SEARCH_LIBRARY_ARCH, + SD_PATH_SEARCH_SHARED, + SD_PATH_SEARCH_CONFIGURATION_FACTORY, + SD_PATH_SEARCH_STATE_FACTORY, + SD_PATH_SEARCH_CONFIGURATION)) { + + char *p; + + r = sd_path_home(type, suffix, &p); + if (r < 0) + return r; + + l = new(char*, 2); + if (!l) { + free(p); + return -ENOMEM; + } + + l[0] = p; + l[1] = NULL; + + *paths = l; + return 0; + } + + r = get_search(type, &l); + if (r < 0) + return r; + + if (!suffix) { + *paths = l; + return 0; + } + + n = new(char*, strv_length(l)+1); + if (!n) { + strv_free(l); + return -ENOMEM; + } + + j = n; + STRV_FOREACH(i, l) { + + if (endswith(*i, "/")) + *j = strappend(*i, suffix); + else + *j = strjoin(*i, "/", suffix, NULL); + + if (!*j) { + strv_free(l); + strv_free(n); + return -ENOMEM; + } + + j++; + } + + *j = NULL; + *paths = n; + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-resolve/sd-resolve.c b/src/libsystemd/libsystemd-internal/sd-resolve/sd-resolve.c new file mode 100644 index 0000000000..30176d7283 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-resolve/sd-resolve.c @@ -0,0 +1,1245 @@ +/*** + This file is part of systemd. + + Copyright 2005-2008 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "list.h" +#include "missing.h" +#include "socket-util.h" +#include "util.h" + +#define WORKERS_MIN 1U +#define WORKERS_MAX 16U +#define QUERIES_MAX 256U +#define BUFSIZE 10240U + +typedef enum { + REQUEST_ADDRINFO, + RESPONSE_ADDRINFO, + REQUEST_NAMEINFO, + RESPONSE_NAMEINFO, + REQUEST_TERMINATE, + RESPONSE_DIED +} QueryType; + +enum { + REQUEST_RECV_FD, + REQUEST_SEND_FD, + RESPONSE_RECV_FD, + RESPONSE_SEND_FD, + _FD_MAX +}; + +struct sd_resolve { + unsigned n_ref; + + bool dead:1; + pid_t original_pid; + + int fds[_FD_MAX]; + + pthread_t workers[WORKERS_MAX]; + unsigned n_valid_workers; + + unsigned current_id; + sd_resolve_query* query_array[QUERIES_MAX]; + unsigned n_queries, n_done, n_outstanding; + + sd_event_source *event_source; + sd_event *event; + + sd_resolve_query *current; + + sd_resolve **default_resolve_ptr; + pid_t tid; + + LIST_HEAD(sd_resolve_query, queries); +}; + +struct sd_resolve_query { + unsigned n_ref; + + sd_resolve *resolve; + + QueryType type:4; + bool done:1; + bool floating:1; + unsigned id; + + int ret; + int _errno; + int _h_errno; + struct addrinfo *addrinfo; + char *serv, *host; + + union { + sd_resolve_getaddrinfo_handler_t getaddrinfo_handler; + sd_resolve_getnameinfo_handler_t getnameinfo_handler; + }; + + void *userdata; + + LIST_FIELDS(sd_resolve_query, queries); +}; + +typedef struct RHeader { + QueryType type; + unsigned id; + size_t length; +} RHeader; + +typedef struct AddrInfoRequest { + struct RHeader header; + bool hints_valid; + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t node_len, service_len; +} AddrInfoRequest; + +typedef struct AddrInfoResponse { + struct RHeader header; + int ret; + int _errno; + int _h_errno; + /* followed by addrinfo_serialization[] */ +} AddrInfoResponse; + +typedef struct AddrInfoSerialization { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + size_t ai_addrlen; + size_t canonname_len; + /* Followed by ai_addr amd ai_canonname with variable lengths */ +} AddrInfoSerialization; + +typedef struct NameInfoRequest { + struct RHeader header; + int flags; + socklen_t sockaddr_len; + bool gethost:1, getserv:1; +} NameInfoRequest; + +typedef struct NameInfoResponse { + struct RHeader header; + size_t hostlen, servlen; + int ret; + int _errno; + int _h_errno; +} NameInfoResponse; + +typedef union Packet { + RHeader rheader; + AddrInfoRequest addrinfo_request; + AddrInfoResponse addrinfo_response; + NameInfoRequest nameinfo_request; + NameInfoResponse nameinfo_response; +} Packet; + +static int getaddrinfo_done(sd_resolve_query* q); +static int getnameinfo_done(sd_resolve_query *q); + +static void resolve_query_disconnect(sd_resolve_query *q); + +#define RESOLVE_DONT_DESTROY(resolve) \ + _cleanup_(sd_resolve_unrefp) _unused_ sd_resolve *_dont_destroy_##resolve = sd_resolve_ref(resolve) + +static int send_died(int out_fd) { + + RHeader rh = { + .type = RESPONSE_DIED, + .length = sizeof(RHeader), + }; + + assert(out_fd >= 0); + + if (send(out_fd, &rh, rh.length, MSG_NOSIGNAL) < 0) + return -errno; + + return 0; +} + +static void *serialize_addrinfo(void *p, const struct addrinfo *ai, size_t *length, size_t maxlength) { + AddrInfoSerialization s; + size_t cnl, l; + + assert(p); + assert(ai); + assert(length); + assert(*length <= maxlength); + + cnl = ai->ai_canonname ? strlen(ai->ai_canonname)+1 : 0; + l = sizeof(AddrInfoSerialization) + ai->ai_addrlen + cnl; + + if (*length + l > maxlength) + return NULL; + + s.ai_flags = ai->ai_flags; + s.ai_family = ai->ai_family; + s.ai_socktype = ai->ai_socktype; + s.ai_protocol = ai->ai_protocol; + s.ai_addrlen = ai->ai_addrlen; + s.canonname_len = cnl; + + memcpy((uint8_t*) p, &s, sizeof(AddrInfoSerialization)); + memcpy((uint8_t*) p + sizeof(AddrInfoSerialization), ai->ai_addr, ai->ai_addrlen); + + if (ai->ai_canonname) + memcpy((char*) p + sizeof(AddrInfoSerialization) + ai->ai_addrlen, ai->ai_canonname, cnl); + + *length += l; + return (uint8_t*) p + l; +} + +static int send_addrinfo_reply( + int out_fd, + unsigned id, + int ret, + struct addrinfo *ai, + int _errno, + int _h_errno) { + + AddrInfoResponse resp = { + .header.type = RESPONSE_ADDRINFO, + .header.id = id, + .header.length = sizeof(AddrInfoResponse), + .ret = ret, + ._errno = _errno, + ._h_errno = _h_errno, + }; + + struct msghdr mh = {}; + struct iovec iov[2]; + union { + AddrInfoSerialization ais; + uint8_t space[BUFSIZE]; + } buffer; + + assert(out_fd >= 0); + + if (ret == 0 && ai) { + void *p = &buffer; + struct addrinfo *k; + + for (k = ai; k; k = k->ai_next) { + p = serialize_addrinfo(p, k, &resp.header.length, (uint8_t*) &buffer + BUFSIZE - (uint8_t*) p); + if (!p) { + freeaddrinfo(ai); + return -ENOBUFS; + } + } + } + + if (ai) + freeaddrinfo(ai); + + iov[0] = (struct iovec) { .iov_base = &resp, .iov_len = sizeof(AddrInfoResponse) }; + iov[1] = (struct iovec) { .iov_base = &buffer, .iov_len = resp.header.length - sizeof(AddrInfoResponse) }; + + mh.msg_iov = iov; + mh.msg_iovlen = ELEMENTSOF(iov); + + if (sendmsg(out_fd, &mh, MSG_NOSIGNAL) < 0) + return -errno; + + return 0; +} + +static int send_nameinfo_reply( + int out_fd, + unsigned id, + int ret, + const char *host, + const char *serv, + int _errno, + int _h_errno) { + + NameInfoResponse resp = { + .header.type = RESPONSE_NAMEINFO, + .header.id = id, + .ret = ret, + ._errno = _errno, + ._h_errno = _h_errno, + }; + + struct msghdr mh = {}; + struct iovec iov[3]; + size_t hl, sl; + + assert(out_fd >= 0); + + sl = serv ? strlen(serv)+1 : 0; + hl = host ? strlen(host)+1 : 0; + + resp.header.length = sizeof(NameInfoResponse) + hl + sl; + resp.hostlen = hl; + resp.servlen = sl; + + iov[0] = (struct iovec) { .iov_base = &resp, .iov_len = sizeof(NameInfoResponse) }; + iov[1] = (struct iovec) { .iov_base = (void*) host, .iov_len = hl }; + iov[2] = (struct iovec) { .iov_base = (void*) serv, .iov_len = sl }; + + mh.msg_iov = iov; + mh.msg_iovlen = ELEMENTSOF(iov); + + if (sendmsg(out_fd, &mh, MSG_NOSIGNAL) < 0) + return -errno; + + return 0; +} + +static int handle_request(int out_fd, const Packet *packet, size_t length) { + const RHeader *req; + + assert(out_fd >= 0); + assert(packet); + + req = &packet->rheader; + + assert(length >= sizeof(RHeader)); + assert(length == req->length); + + switch (req->type) { + + case REQUEST_ADDRINFO: { + const AddrInfoRequest *ai_req = &packet->addrinfo_request; + struct addrinfo hints = {}, *result = NULL; + const char *node, *service; + int ret; + + assert(length >= sizeof(AddrInfoRequest)); + assert(length == sizeof(AddrInfoRequest) + ai_req->node_len + ai_req->service_len); + + hints.ai_flags = ai_req->ai_flags; + hints.ai_family = ai_req->ai_family; + hints.ai_socktype = ai_req->ai_socktype; + hints.ai_protocol = ai_req->ai_protocol; + + node = ai_req->node_len ? (const char*) ai_req + sizeof(AddrInfoRequest) : NULL; + service = ai_req->service_len ? (const char*) ai_req + sizeof(AddrInfoRequest) + ai_req->node_len : NULL; + + ret = getaddrinfo( + node, service, + ai_req->hints_valid ? &hints : NULL, + &result); + + /* send_addrinfo_reply() frees result */ + return send_addrinfo_reply(out_fd, req->id, ret, result, errno, h_errno); + } + + case REQUEST_NAMEINFO: { + const NameInfoRequest *ni_req = &packet->nameinfo_request; + char hostbuf[NI_MAXHOST], servbuf[NI_MAXSERV]; + union sockaddr_union sa; + int ret; + + assert(length >= sizeof(NameInfoRequest)); + assert(length == sizeof(NameInfoRequest) + ni_req->sockaddr_len); + assert(sizeof(sa) >= ni_req->sockaddr_len); + + memcpy(&sa, (const uint8_t *) ni_req + sizeof(NameInfoRequest), ni_req->sockaddr_len); + + ret = getnameinfo(&sa.sa, ni_req->sockaddr_len, + ni_req->gethost ? hostbuf : NULL, ni_req->gethost ? sizeof(hostbuf) : 0, + ni_req->getserv ? servbuf : NULL, ni_req->getserv ? sizeof(servbuf) : 0, + ni_req->flags); + + return send_nameinfo_reply(out_fd, req->id, ret, + ret == 0 && ni_req->gethost ? hostbuf : NULL, + ret == 0 && ni_req->getserv ? servbuf : NULL, + errno, h_errno); + } + + case REQUEST_TERMINATE: + /* Quit */ + return -ECONNRESET; + + default: + assert_not_reached("Unknown request"); + } + + return 0; +} + +static void* thread_worker(void *p) { + sd_resolve *resolve = p; + sigset_t fullset; + + /* No signals in this thread please */ + assert_se(sigfillset(&fullset) == 0); + assert_se(pthread_sigmask(SIG_BLOCK, &fullset, NULL) == 0); + + /* Assign a pretty name to this thread */ + prctl(PR_SET_NAME, (unsigned long) "sd-resolve"); + + while (!resolve->dead) { + union { + Packet packet; + uint8_t space[BUFSIZE]; + } buf; + ssize_t length; + + length = recv(resolve->fds[REQUEST_RECV_FD], &buf, sizeof(buf), 0); + if (length < 0) { + if (errno == EINTR) + continue; + + break; + } + if (length == 0) + break; + + if (resolve->dead) + break; + + if (handle_request(resolve->fds[RESPONSE_SEND_FD], &buf.packet, (size_t) length) < 0) + break; + } + + send_died(resolve->fds[RESPONSE_SEND_FD]); + + return NULL; +} + +static int start_threads(sd_resolve *resolve, unsigned extra) { + unsigned n; + int r; + + n = resolve->n_outstanding + extra; + n = CLAMP(n, WORKERS_MIN, WORKERS_MAX); + + while (resolve->n_valid_workers < n) { + + r = pthread_create(&resolve->workers[resolve->n_valid_workers], NULL, thread_worker, resolve); + if (r != 0) + return -r; + + resolve->n_valid_workers ++; + } + + return 0; +} + +static bool resolve_pid_changed(sd_resolve *r) { + assert(r); + + /* We don't support people creating a resolver and keeping it + * around after fork(). Let's complain. */ + + return r->original_pid != getpid(); +} + +_public_ int sd_resolve_new(sd_resolve **ret) { + sd_resolve *resolve = NULL; + int i, r; + + assert_return(ret, -EINVAL); + + resolve = new0(sd_resolve, 1); + if (!resolve) + return -ENOMEM; + + resolve->n_ref = 1; + resolve->original_pid = getpid(); + + for (i = 0; i < _FD_MAX; i++) + resolve->fds[i] = -1; + + r = socketpair(PF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, resolve->fds + REQUEST_RECV_FD); + if (r < 0) { + r = -errno; + goto fail; + } + + r = socketpair(PF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, resolve->fds + RESPONSE_RECV_FD); + if (r < 0) { + r = -errno; + goto fail; + } + + fd_inc_sndbuf(resolve->fds[REQUEST_SEND_FD], QUERIES_MAX * BUFSIZE); + fd_inc_rcvbuf(resolve->fds[REQUEST_RECV_FD], QUERIES_MAX * BUFSIZE); + fd_inc_sndbuf(resolve->fds[RESPONSE_SEND_FD], QUERIES_MAX * BUFSIZE); + fd_inc_rcvbuf(resolve->fds[RESPONSE_RECV_FD], QUERIES_MAX * BUFSIZE); + + fd_nonblock(resolve->fds[RESPONSE_RECV_FD], true); + + *ret = resolve; + return 0; + +fail: + sd_resolve_unref(resolve); + return r; +} + +_public_ int sd_resolve_default(sd_resolve **ret) { + + static thread_local sd_resolve *default_resolve = NULL; + sd_resolve *e = NULL; + int r; + + if (!ret) + return !!default_resolve; + + if (default_resolve) { + *ret = sd_resolve_ref(default_resolve); + return 0; + } + + r = sd_resolve_new(&e); + if (r < 0) + return r; + + e->default_resolve_ptr = &default_resolve; + e->tid = gettid(); + default_resolve = e; + + *ret = e; + return 1; +} + +_public_ int sd_resolve_get_tid(sd_resolve *resolve, pid_t *tid) { + assert_return(resolve, -EINVAL); + assert_return(tid, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + if (resolve->tid != 0) { + *tid = resolve->tid; + return 0; + } + + if (resolve->event) + return sd_event_get_tid(resolve->event, tid); + + return -ENXIO; +} + +static void resolve_free(sd_resolve *resolve) { + PROTECT_ERRNO; + sd_resolve_query *q; + unsigned i; + + assert(resolve); + + while ((q = resolve->queries)) { + assert(q->floating); + resolve_query_disconnect(q); + sd_resolve_query_unref(q); + } + + if (resolve->default_resolve_ptr) + *(resolve->default_resolve_ptr) = NULL; + + resolve->dead = true; + + sd_resolve_detach_event(resolve); + + if (resolve->fds[REQUEST_SEND_FD] >= 0) { + + RHeader req = { + .type = REQUEST_TERMINATE, + .length = sizeof(req) + }; + + /* Send one termination packet for each worker */ + for (i = 0; i < resolve->n_valid_workers; i++) + (void) send(resolve->fds[REQUEST_SEND_FD], &req, req.length, MSG_NOSIGNAL); + } + + /* Now terminate them and wait until they are gone. */ + for (i = 0; i < resolve->n_valid_workers; i++) + pthread_join(resolve->workers[i], NULL); + + /* Close all communication channels */ + for (i = 0; i < _FD_MAX; i++) + safe_close(resolve->fds[i]); + + free(resolve); +} + +_public_ sd_resolve* sd_resolve_ref(sd_resolve *resolve) { + assert_return(resolve, NULL); + + assert(resolve->n_ref >= 1); + resolve->n_ref++; + + return resolve; +} + +_public_ sd_resolve* sd_resolve_unref(sd_resolve *resolve) { + + if (!resolve) + return NULL; + + assert(resolve->n_ref >= 1); + resolve->n_ref--; + + if (resolve->n_ref <= 0) + resolve_free(resolve); + + return NULL; +} + +_public_ int sd_resolve_get_fd(sd_resolve *resolve) { + assert_return(resolve, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + return resolve->fds[RESPONSE_RECV_FD]; +} + +_public_ int sd_resolve_get_events(sd_resolve *resolve) { + assert_return(resolve, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + return resolve->n_queries > resolve->n_done ? POLLIN : 0; +} + +_public_ int sd_resolve_get_timeout(sd_resolve *resolve, uint64_t *usec) { + assert_return(resolve, -EINVAL); + assert_return(usec, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + *usec = (uint64_t) -1; + return 0; +} + +static sd_resolve_query *lookup_query(sd_resolve *resolve, unsigned id) { + sd_resolve_query *q; + + assert(resolve); + + q = resolve->query_array[id % QUERIES_MAX]; + if (q) + if (q->id == id) + return q; + + return NULL; +} + +static int complete_query(sd_resolve *resolve, sd_resolve_query *q) { + int r; + + assert(q); + assert(!q->done); + assert(q->resolve == resolve); + + q->done = true; + resolve->n_done ++; + + resolve->current = sd_resolve_query_ref(q); + + switch (q->type) { + + case REQUEST_ADDRINFO: + r = getaddrinfo_done(q); + break; + + case REQUEST_NAMEINFO: + r = getnameinfo_done(q); + break; + + default: + assert_not_reached("Cannot complete unknown query type"); + } + + resolve->current = NULL; + + if (q->floating) { + resolve_query_disconnect(q); + sd_resolve_query_unref(q); + } + + sd_resolve_query_unref(q); + + return r; +} + +static int unserialize_addrinfo(const void **p, size_t *length, struct addrinfo **ret_ai) { + AddrInfoSerialization s; + size_t l; + struct addrinfo *ai; + + assert(p); + assert(*p); + assert(ret_ai); + assert(length); + + if (*length < sizeof(AddrInfoSerialization)) + return -EBADMSG; + + memcpy(&s, *p, sizeof(s)); + + l = sizeof(AddrInfoSerialization) + s.ai_addrlen + s.canonname_len; + if (*length < l) + return -EBADMSG; + + ai = new0(struct addrinfo, 1); + if (!ai) + return -ENOMEM; + + ai->ai_flags = s.ai_flags; + ai->ai_family = s.ai_family; + ai->ai_socktype = s.ai_socktype; + ai->ai_protocol = s.ai_protocol; + ai->ai_addrlen = s.ai_addrlen; + + if (s.ai_addrlen > 0) { + ai->ai_addr = memdup((const uint8_t*) *p + sizeof(AddrInfoSerialization), s.ai_addrlen); + if (!ai->ai_addr) { + free(ai); + return -ENOMEM; + } + } + + if (s.canonname_len > 0) { + ai->ai_canonname = memdup((const uint8_t*) *p + sizeof(AddrInfoSerialization) + s.ai_addrlen, s.canonname_len); + if (!ai->ai_canonname) { + free(ai->ai_addr); + free(ai); + return -ENOMEM; + } + } + + *length -= l; + *ret_ai = ai; + *p = ((const uint8_t*) *p) + l; + + return 0; +} + +static int handle_response(sd_resolve *resolve, const Packet *packet, size_t length) { + const RHeader *resp; + sd_resolve_query *q; + int r; + + assert(resolve); + + resp = &packet->rheader; + assert(resp); + assert(length >= sizeof(RHeader)); + assert(length == resp->length); + + if (resp->type == RESPONSE_DIED) { + resolve->dead = true; + return 0; + } + + assert(resolve->n_outstanding > 0); + resolve->n_outstanding--; + + q = lookup_query(resolve, resp->id); + if (!q) + return 0; + + switch (resp->type) { + + case RESPONSE_ADDRINFO: { + const AddrInfoResponse *ai_resp = &packet->addrinfo_response; + const void *p; + size_t l; + struct addrinfo *prev = NULL; + + assert(length >= sizeof(AddrInfoResponse)); + assert(q->type == REQUEST_ADDRINFO); + + q->ret = ai_resp->ret; + q->_errno = ai_resp->_errno; + q->_h_errno = ai_resp->_h_errno; + + l = length - sizeof(AddrInfoResponse); + p = (const uint8_t*) resp + sizeof(AddrInfoResponse); + + while (l > 0 && p) { + struct addrinfo *ai = NULL; + + r = unserialize_addrinfo(&p, &l, &ai); + if (r < 0) { + q->ret = EAI_SYSTEM; + q->_errno = -r; + q->_h_errno = 0; + freeaddrinfo(q->addrinfo); + q->addrinfo = NULL; + break; + } + + if (prev) + prev->ai_next = ai; + else + q->addrinfo = ai; + + prev = ai; + } + + return complete_query(resolve, q); + } + + case RESPONSE_NAMEINFO: { + const NameInfoResponse *ni_resp = &packet->nameinfo_response; + + assert(length >= sizeof(NameInfoResponse)); + assert(q->type == REQUEST_NAMEINFO); + + q->ret = ni_resp->ret; + q->_errno = ni_resp->_errno; + q->_h_errno = ni_resp->_h_errno; + + if (ni_resp->hostlen > 0) { + q->host = strndup((const char*) ni_resp + sizeof(NameInfoResponse), ni_resp->hostlen-1); + if (!q->host) { + q->ret = EAI_MEMORY; + q->_errno = ENOMEM; + q->_h_errno = 0; + } + } + + if (ni_resp->servlen > 0) { + q->serv = strndup((const char*) ni_resp + sizeof(NameInfoResponse) + ni_resp->hostlen, ni_resp->servlen-1); + if (!q->serv) { + q->ret = EAI_MEMORY; + q->_errno = ENOMEM; + q->_h_errno = 0; + } + } + + return complete_query(resolve, q); + } + + default: + return 0; + } +} + +_public_ int sd_resolve_process(sd_resolve *resolve) { + RESOLVE_DONT_DESTROY(resolve); + + union { + Packet packet; + uint8_t space[BUFSIZE]; + } buf; + ssize_t l; + int r; + + assert_return(resolve, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + /* We don't allow recursively invoking sd_resolve_process(). */ + assert_return(!resolve->current, -EBUSY); + + l = recv(resolve->fds[RESPONSE_RECV_FD], &buf, sizeof(buf), 0); + if (l < 0) { + if (errno == EAGAIN) + return 0; + + return -errno; + } + if (l == 0) + return -ECONNREFUSED; + + r = handle_response(resolve, &buf.packet, (size_t) l); + if (r < 0) + return r; + + return 1; +} + +_public_ int sd_resolve_wait(sd_resolve *resolve, uint64_t timeout_usec) { + int r; + + assert_return(resolve, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + if (resolve->n_done >= resolve->n_queries) + return 0; + + do { + r = fd_wait_for_event(resolve->fds[RESPONSE_RECV_FD], POLLIN, timeout_usec); + } while (r == -EINTR); + + if (r < 0) + return r; + + return sd_resolve_process(resolve); +} + +static int alloc_query(sd_resolve *resolve, bool floating, sd_resolve_query **_q) { + sd_resolve_query *q; + int r; + + assert(resolve); + assert(_q); + + if (resolve->n_queries >= QUERIES_MAX) + return -ENOBUFS; + + r = start_threads(resolve, 1); + if (r < 0) + return r; + + while (resolve->query_array[resolve->current_id % QUERIES_MAX]) + resolve->current_id++; + + q = resolve->query_array[resolve->current_id % QUERIES_MAX] = new0(sd_resolve_query, 1); + if (!q) + return -ENOMEM; + + q->n_ref = 1; + q->resolve = resolve; + q->floating = floating; + q->id = resolve->current_id++; + + if (!floating) + sd_resolve_ref(resolve); + + LIST_PREPEND(queries, resolve->queries, q); + resolve->n_queries++; + + *_q = q; + return 0; +} + +_public_ int sd_resolve_getaddrinfo( + sd_resolve *resolve, + sd_resolve_query **_q, + const char *node, const char *service, + const struct addrinfo *hints, + sd_resolve_getaddrinfo_handler_t callback, void *userdata) { + + AddrInfoRequest req = {}; + struct msghdr mh = {}; + struct iovec iov[3]; + sd_resolve_query *q; + int r; + + assert_return(resolve, -EINVAL); + assert_return(node || service, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + r = alloc_query(resolve, !_q, &q); + if (r < 0) + return r; + + q->type = REQUEST_ADDRINFO; + q->getaddrinfo_handler = callback; + q->userdata = userdata; + + req.node_len = node ? strlen(node)+1 : 0; + req.service_len = service ? strlen(service)+1 : 0; + + req.header.id = q->id; + req.header.type = REQUEST_ADDRINFO; + req.header.length = sizeof(AddrInfoRequest) + req.node_len + req.service_len; + + if (hints) { + req.hints_valid = true; + req.ai_flags = hints->ai_flags; + req.ai_family = hints->ai_family; + req.ai_socktype = hints->ai_socktype; + req.ai_protocol = hints->ai_protocol; + } + + iov[mh.msg_iovlen++] = (struct iovec) { .iov_base = &req, .iov_len = sizeof(AddrInfoRequest) }; + if (node) + iov[mh.msg_iovlen++] = (struct iovec) { .iov_base = (void*) node, .iov_len = req.node_len }; + if (service) + iov[mh.msg_iovlen++] = (struct iovec) { .iov_base = (void*) service, .iov_len = req.service_len }; + mh.msg_iov = iov; + + if (sendmsg(resolve->fds[REQUEST_SEND_FD], &mh, MSG_NOSIGNAL) < 0) { + sd_resolve_query_unref(q); + return -errno; + } + + resolve->n_outstanding++; + + if (_q) + *_q = q; + + return 0; +} + +static int getaddrinfo_done(sd_resolve_query* q) { + assert(q); + assert(q->done); + assert(q->getaddrinfo_handler); + + errno = q->_errno; + h_errno = q->_h_errno; + + return q->getaddrinfo_handler(q, q->ret, q->addrinfo, q->userdata); +} + +_public_ int sd_resolve_getnameinfo( + sd_resolve *resolve, + sd_resolve_query**_q, + const struct sockaddr *sa, socklen_t salen, + int flags, + uint64_t get, + sd_resolve_getnameinfo_handler_t callback, + void *userdata) { + + NameInfoRequest req = {}; + struct msghdr mh = {}; + struct iovec iov[2]; + sd_resolve_query *q; + int r; + + assert_return(resolve, -EINVAL); + assert_return(sa, -EINVAL); + assert_return(salen >= sizeof(struct sockaddr), -EINVAL); + assert_return(salen <= sizeof(union sockaddr_union), -EINVAL); + assert_return((get & ~SD_RESOLVE_GET_BOTH) == 0, -EINVAL); + assert_return(callback, -EINVAL); + assert_return(!resolve_pid_changed(resolve), -ECHILD); + + r = alloc_query(resolve, !_q, &q); + if (r < 0) + return r; + + q->type = REQUEST_NAMEINFO; + q->getnameinfo_handler = callback; + q->userdata = userdata; + + req.header.id = q->id; + req.header.type = REQUEST_NAMEINFO; + req.header.length = sizeof(NameInfoRequest) + salen; + + req.flags = flags; + req.sockaddr_len = salen; + req.gethost = !!(get & SD_RESOLVE_GET_HOST); + req.getserv = !!(get & SD_RESOLVE_GET_SERVICE); + + iov[0] = (struct iovec) { .iov_base = &req, .iov_len = sizeof(NameInfoRequest) }; + iov[1] = (struct iovec) { .iov_base = (void*) sa, .iov_len = salen }; + + mh.msg_iov = iov; + mh.msg_iovlen = 2; + + if (sendmsg(resolve->fds[REQUEST_SEND_FD], &mh, MSG_NOSIGNAL) < 0) { + sd_resolve_query_unref(q); + return -errno; + } + + resolve->n_outstanding++; + + if (_q) + *_q = q; + + return 0; +} + +static int getnameinfo_done(sd_resolve_query *q) { + + assert(q); + assert(q->done); + assert(q->getnameinfo_handler); + + errno = q->_errno; + h_errno= q->_h_errno; + + return q->getnameinfo_handler(q, q->ret, q->host, q->serv, q->userdata); +} + +_public_ sd_resolve_query* sd_resolve_query_ref(sd_resolve_query *q) { + assert_return(q, NULL); + + assert(q->n_ref >= 1); + q->n_ref++; + + return q; +} + +static void resolve_freeaddrinfo(struct addrinfo *ai) { + while (ai) { + struct addrinfo *next = ai->ai_next; + + free(ai->ai_addr); + free(ai->ai_canonname); + free(ai); + ai = next; + } +} + +static void resolve_query_disconnect(sd_resolve_query *q) { + sd_resolve *resolve; + unsigned i; + + assert(q); + + if (!q->resolve) + return; + + resolve = q->resolve; + assert(resolve->n_queries > 0); + + if (q->done) { + assert(resolve->n_done > 0); + resolve->n_done--; + } + + i = q->id % QUERIES_MAX; + assert(resolve->query_array[i] == q); + resolve->query_array[i] = NULL; + LIST_REMOVE(queries, resolve->queries, q); + resolve->n_queries--; + + q->resolve = NULL; + if (!q->floating) + sd_resolve_unref(resolve); +} + +static void resolve_query_free(sd_resolve_query *q) { + assert(q); + + resolve_query_disconnect(q); + + resolve_freeaddrinfo(q->addrinfo); + free(q->host); + free(q->serv); + free(q); +} + +_public_ sd_resolve_query* sd_resolve_query_unref(sd_resolve_query* q) { + if (!q) + return NULL; + + assert(q->n_ref >= 1); + q->n_ref--; + + if (q->n_ref <= 0) + resolve_query_free(q); + + return NULL; +} + +_public_ int sd_resolve_query_is_done(sd_resolve_query *q) { + assert_return(q, -EINVAL); + assert_return(!resolve_pid_changed(q->resolve), -ECHILD); + + return q->done; +} + +_public_ void* sd_resolve_query_set_userdata(sd_resolve_query *q, void *userdata) { + void *ret; + + assert_return(q, NULL); + assert_return(!resolve_pid_changed(q->resolve), NULL); + + ret = q->userdata; + q->userdata = userdata; + + return ret; +} + +_public_ void* sd_resolve_query_get_userdata(sd_resolve_query *q) { + assert_return(q, NULL); + assert_return(!resolve_pid_changed(q->resolve), NULL); + + return q->userdata; +} + +_public_ sd_resolve *sd_resolve_query_get_resolve(sd_resolve_query *q) { + assert_return(q, NULL); + assert_return(!resolve_pid_changed(q->resolve), NULL); + + return q->resolve; +} + +static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + sd_resolve *resolve = userdata; + int r; + + assert(resolve); + + r = sd_resolve_process(resolve); + if (r < 0) + return r; + + return 1; +} + +_public_ int sd_resolve_attach_event(sd_resolve *resolve, sd_event *event, int priority) { + int r; + + assert_return(resolve, -EINVAL); + assert_return(!resolve->event, -EBUSY); + + assert(!resolve->event_source); + + if (event) + resolve->event = sd_event_ref(event); + else { + r = sd_event_default(&resolve->event); + if (r < 0) + return r; + } + + r = sd_event_add_io(resolve->event, &resolve->event_source, resolve->fds[RESPONSE_RECV_FD], POLLIN, io_callback, resolve); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(resolve->event_source, priority); + if (r < 0) + goto fail; + + return 0; + +fail: + sd_resolve_detach_event(resolve); + return r; +} + +_public_ int sd_resolve_detach_event(sd_resolve *resolve) { + assert_return(resolve, -EINVAL); + + if (!resolve->event) + return 0; + + if (resolve->event_source) { + sd_event_source_set_enabled(resolve->event_source, SD_EVENT_OFF); + resolve->event_source = sd_event_source_unref(resolve->event_source); + } + + resolve->event = sd_event_unref(resolve->event); + return 1; +} + +_public_ sd_event *sd_resolve_get_event(sd_resolve *resolve) { + assert_return(resolve, NULL); + + return resolve->event; +} diff --git a/src/libsystemd/libsystemd-internal/sd-resolve/test-resolve.c b/src/libsystemd/libsystemd-internal/sd-resolve/test-resolve.c new file mode 100644 index 0000000000..9e3ac16597 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-resolve/test-resolve.c @@ -0,0 +1,114 @@ +/*** + This file is part of systemd. + + Copyright 2005-2008 Lennart Poettering + Copyright 2014 Daniel Buch + + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "macro.h" +#include "socket-util.h" +#include "string-util.h" + +static int getaddrinfo_handler(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata) { + const struct addrinfo *i; + + assert_se(q); + + if (ret != 0) { + log_error("getaddrinfo error: %s %i", gai_strerror(ret), ret); + return 0; + } + + for (i = ai; i; i = i->ai_next) { + _cleanup_free_ char *addr = NULL; + + assert_se(sockaddr_pretty(i->ai_addr, i->ai_addrlen, false, true, &addr) == 0); + puts(addr); + } + + printf("canonical name: %s\n", strna(ai->ai_canonname)); + + return 0; +} + +static int getnameinfo_handler(sd_resolve_query *q, int ret, const char *host, const char *serv, void *userdata) { + assert_se(q); + + if (ret != 0) { + log_error("getnameinfo error: %s %i", gai_strerror(ret), ret); + return 0; + } + + printf("Host: %s -- Serv: %s\n", strna(host), strna(serv)); + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_resolve_query_unrefp) sd_resolve_query *q1 = NULL, *q2 = NULL; + _cleanup_(sd_resolve_unrefp) sd_resolve *resolve = NULL; + int r = 0; + + struct addrinfo hints = { + .ai_family = PF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_CANONNAME + }; + + struct sockaddr_in sa = { + .sin_family = AF_INET, + .sin_port = htons(80) + }; + + assert_se(sd_resolve_default(&resolve) >= 0); + + /* Test a floating resolver query */ + sd_resolve_getaddrinfo(resolve, NULL, "redhat.com", "http", NULL, getaddrinfo_handler, NULL); + + /* Make a name -> address query */ + r = sd_resolve_getaddrinfo(resolve, &q1, argc >= 2 ? argv[1] : "www.heise.de", NULL, &hints, getaddrinfo_handler, NULL); + if (r < 0) + log_error_errno(r, "sd_resolve_getaddrinfo(): %m"); + + /* Make an address -> name query */ + sa.sin_addr.s_addr = inet_addr(argc >= 3 ? argv[2] : "193.99.144.71"); + r = sd_resolve_getnameinfo(resolve, &q2, (struct sockaddr*) &sa, sizeof(sa), 0, SD_RESOLVE_GET_BOTH, getnameinfo_handler, NULL); + if (r < 0) + log_error_errno(r, "sd_resolve_getnameinfo(): %m"); + + /* Wait until all queries are completed */ + for (;;) { + r = sd_resolve_wait(resolve, (uint64_t) -1); + if (r == 0) + break; + if (r < 0) { + log_error_errno(r, "sd_resolve_wait(): %m"); + assert_not_reached("sd_resolve_wait() failed"); + } + } + + return 0; +} diff --git a/src/libsystemd/libsystemd-internal/sd-utf8/sd-utf8.c b/src/libsystemd/libsystemd-internal/sd-utf8/sd-utf8.c new file mode 100644 index 0000000000..77be8e1996 --- /dev/null +++ b/src/libsystemd/libsystemd-internal/sd-utf8/sd-utf8.c @@ -0,0 +1,35 @@ +/*** + 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 . +***/ + +#include + +#include "utf8.h" +#include "util.h" + +_public_ const char *sd_utf8_is_valid(const char *s) { + assert_return(s, NULL); + + return utf8_is_valid(s); +} + +_public_ const char *sd_ascii_is_valid(const char *s) { + assert_return(s, NULL); + + return ascii_is_valid(s); +} diff --git a/src/libsystemd/libsystemd-journal-internal/Makefile b/src/libsystemd/libsystemd-journal-internal/Makefile new file mode 100644 index 0000000000..4e1f7327ae --- /dev/null +++ b/src/libsystemd/libsystemd-journal-internal/Makefile @@ -0,0 +1,97 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +pkginclude_HEADERS += \ + src/systemd/sd-journal.h \ + src/systemd/sd-messages.h \ + src/systemd/_sd-common.h + +libsystemd_journal_internal_la_SOURCES = \ + src/journal/sd-journal.c \ + src/systemd/sd-journal.h \ + src/systemd/_sd-common.h \ + src/journal/journal-file.c \ + src/journal/journal-file.h \ + src/journal/journal-vacuum.c \ + src/journal/journal-vacuum.h \ + src/journal/journal-verify.c \ + src/journal/journal-verify.h \ + src/journal/lookup3.c \ + src/journal/lookup3.h \ + src/journal/journal-send.c \ + src/journal/journal-def.h \ + src/journal/compress.h \ + src/journal/catalog.c \ + src/journal/catalog.h \ + src/journal/mmap-cache.c \ + src/journal/mmap-cache.h \ + src/journal/compress.c \ + src/journal/audit-type.h \ + src/journal/audit-type.c + +nodist_libsystemd_journal_internal_la_SOURCES = \ + src/journal/audit_type-to-name.h + +gperf_txt_sources += \ + src/journal/audit_type-list.txt + +# using _CFLAGS = in the conditional below would suppress AM_CFLAGS +libsystemd_journal_internal_la_CFLAGS = \ + $(AM_CFLAGS) + +libsystemd_journal_internal_la_LIBADD = + +ifneq ($(HAVE_XZ),) +libsystemd_journal_internal_la_CFLAGS += \ + $(XZ_CFLAGS) + +libsystemd_journal_internal_la_LIBADD += \ + $(XZ_LIBS) +endif + +ifneq ($(HAVE_LZ4),) +libsystemd_journal_internal_la_LIBADD += \ + -llz4 +endif + +ifneq ($(HAVE_GCRYPT),) +libsystemd_journal_internal_la_SOURCES += \ + src/journal/journal-authenticate.c \ + src/journal/journal-authenticate.h \ + src/journal/fsprg.c \ + src/journal/fsprg.h + +libsystemd_journal_internal_la_LIBADD += \ + $(GCRYPT_LIBS) + +# fsprg.c is a drop-in file using void pointer arithmetic +libsystemd_journal_internal_la_CFLAGS += \ + $(GCRYPT_CFLAGS) \ + -Wno-pointer-arith +endif + +noinst_LTLIBRARIES += \ + libsystemd-journal-internal.la +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd/sd-bus/DIFFERENCES b/src/libsystemd/sd-bus/DIFFERENCES deleted file mode 100644 index db269675a7..0000000000 --- a/src/libsystemd/sd-bus/DIFFERENCES +++ /dev/null @@ -1,25 +0,0 @@ -Known differences between dbus1 and kdbus: - -- NameAcquired/NameLost is gone entirely on kdbus backends if - libsystemd is used. It is still added in by systemd-bus-proxyd - for old dbus1 clients, and it is available if libsystemd is used - against the classic dbus1 daemon. If you want to write compatible - code with libsystem-bus you need to explicitly subscribe to - NameOwnerChanged signals and just ignore NameAcquired/NameLost - -- Applications have to deal with spurious signals they didn't expect, - due to the probabilistic bloom filters. They need to handle this - anyway, given that any client can send anything to arbitrary clients - anyway, even in dbus1, so not much changes. - -- clients of the system bus when kdbus is used must roll their own - security. Only legacy dbus1 clients get the old XML policy enforced, - which is implemented by systemd-bus-proxyd. - -- Serial numbers of synthesized messages are always (uint32_t) -1. - -- NameOwnerChanged is a synthetic message, generated locally and not - by the driver. On dbus1 only the Disconnected message was - synthesized like this. - -- There's no standard per-session bus anymore. Only a per-user bus. diff --git a/src/libsystemd/sd-bus/GVARIANT-SERIALIZATION b/src/libsystemd/sd-bus/GVARIANT-SERIALIZATION deleted file mode 100644 index 6aeb11364a..0000000000 --- a/src/libsystemd/sd-bus/GVARIANT-SERIALIZATION +++ /dev/null @@ -1,110 +0,0 @@ -How we use GVariant for serializing D-Bus messages --------------------------------------------------- - -We stay close to the original dbus1 framing as possible, but make -certain changes to adapt for GVariant. dbus1 has the following -framing: - - 1. A fixed header of "yyyyuu" - 2. Additional header fields of "a(yv)" - 3. Padding with NUL bytes to pad up to next 8byte boundary - 4. The body - -Note that the body is not padded at the end, the complete message -hence might have a non-aligned size. Reading multiple messages at once -will hence result in possibly unaligned messages in memory. - -The header consists of the following: - - y Endianness, 'l' or 'B' - y Message Type - y Flags - y Protocol version, '1' - u Length of the body, i.e. the length of part 4 above - u 32bit Serial number - - = 12 bytes - -This header is then followed by the fields array, whose first value is -a 32bit array size. - -When using GVariant we keep the basic structure in place, only -slightly alter the header, and define protocol version '2'. The new -header: - - y Endianness, 'l' or 'B' - y Message Type - y Flags - y Protocol version, '2' - u Reserved, must be 0 - t 64bit Cookie - - = 16 bytes - -This is then followed by the GVariant fields array ("a{tv}"), and -finally the actual body as variant (v). Putting this altogether a -packet on dbus2 hence qualifies as a fully compliant GVariant -structure of (yyyyuta{tv}v). - -For details on gvariant, see: - -https://people.gnome.org/~desrt/gvariant-serialisation.pdf - -Regarding the framing of dbus2, also see: - -https://wiki.gnome.org/Projects/GLib/GDBus/Version2 - -The first four bytes of the header are defined the same way for dbus1 -and dbus2. The first bytes contain the endianess field and the -protocol version, so that the remainder of the message can be safely -made sense of just by looking at the first 32bit. - -Note that the length of the body is no longer included in the header -on dbus2! In fact, the message size must be known in advance, from the -underlying transport in order to parse dbus2 messages, while it is -directly included in dbus1 message headers. This change of semantics -is an effect of GVariant's basic design. - -The serial number has been renamed cookie and has been extended from -32bit to 64bit. It is recommended to avoid the higher 32bit of the -cookie field though, to simplify compatibility with dbus1 peers. Note -that not only the cookie/serial field in the fixed header, but also -the reply_cookie/reply_serial additional header field has been -increased from 32bit to 64bit, too! - -The header field identifiers have been extended from 8bit to -64bit. This has been done to simplify things (as kdbus otherwise uses -exclusively 64bit types, unless there is a strong reason not to), and -has no effect on the serialization size, as due to alignment for each -8bit header field identifier 56 bits of padding had to be added. - -Note that the header size changed, due to these changes. However, -consider that on dbus1 the beginning of the fields array contains the -32bit array size (since that is how arrays are encoded on dbus1), -thus, if one considers that size part of the header, instead of the -array, the size of the header on dbus1 and dbus2 stays identical, at -16 bytes. - - 0 4 8 12 16 - Common: | E | T | F | V | ... - - dbus1: | (as above) | Body Length | Serial | Fields Length | Fields array ... - - gvariant: | (as above) | Reserved | Cookie | Fields array ... - -And that's already it. - -Note: to simplify parsing, valid kdbus/dbus2 messages must include the -entire fixed header and additional header fields in a single non-memfd -message part. Also, the signature string of the body variant all the -way to the end of the message must be in a single non-memfd part -too. The parts for this extended header and footer can be the same -one, and can also continue any amount of additional body bytes. - -Note: on kdbus only native endian messages marshalled in gvariant may - be sent. If a client receives a message in non-native endianness - or in dbus1 marshalling it shall ignore the message. - -Note: The GVariant "MAYBE" type is not supported, so that messages can - be fully converted forth and back between dbus1 and gvariant - representations. diff --git a/src/libsystemd/sd-bus/Makefile b/src/libsystemd/sd-bus/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-bus/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-bus/PORTING-DBUS1 b/src/libsystemd/sd-bus/PORTING-DBUS1 deleted file mode 100644 index 2dedb28bcf..0000000000 --- a/src/libsystemd/sd-bus/PORTING-DBUS1 +++ /dev/null @@ -1,535 +0,0 @@ -A few hints on supporting kdbus as backend in your favorite D-Bus library. - -~~~ - -Before you read this, have a look at the DIFFERENCES and -GVARIANT_SERIALIZATION texts you find in the same directory where you -found this. - -We invite you to port your favorite D-Bus protocol implementation -over to kdbus. However, there are a couple of complexities -involved. On kdbus we only speak GVariant marshaling, kdbus clients -ignore traffic in dbus1 marshaling. Thus, you need to add a second, -GVariant compatible marshaler to your library first. - -After you have done that: here's the basic principle how kdbus works: - -You connect to a bus by opening its bus node in /sys/fs/kdbus/. All -buses have a device node there, it starts with a numeric UID of the -owner of the bus, followed by a dash and a string identifying the -bus. The system bus is thus called /sys/fs/kdbus/0-system, and for user -buses the device node is /sys/fs/kdbus/1000-user (if 1000 is your user -id). - -(Before we proceed, please always keep a copy of libsystemd next -to you, ultimately that's where the details are, this document simply -is a rough overview to help you grok things.) - -CONNECTING - -To connect to a bus, simply open() its device node and issue the -KDBUS_CMD_HELLO call. That's it. Now you are connected. Do not send -Hello messages or so (as you would on dbus1), that does not exist for -kdbus. - -The structure you pass to the ioctl will contain a couple of -parameters that you need to know, to operate on the bus. - -There are two flags fields, one indicating features of the kdbus -kernel side ("conn_flags"), the other one ("bus_flags") indicating -features of the bus owner (i.e. systemd). Both flags fields are 64bit -in width. - -When calling into the ioctl, you need to place your own supported -feature bits into these fields. This tells the kernel about the -features you support. When the ioctl returns, it will contain the -features the kernel supports. - -If any of the higher 32bit are set on the two flags fields and your -client does not know what they mean, it must disconnect. The upper -32bit are used to indicate "incompatible" feature additions on the bus -system, the lower 32bit indicate "compatible" feature additions. A -client that does not support a "compatible" feature addition can go on -communicating with the bus, however a client that does not support an -"incompatible" feature must not proceed with the connection. When a -client encountes such an "incompatible" feature it should immediately -try the next bus address configured in the bus address string. - -The hello structure also contains another flags field "attach_flags" -which indicates metadata that is optionally attached to all incoming -messages. You probably want to set KDBUS_ATTACH_NAMES unconditionally -in it. This has the effect that all well-known names of a sender are -attached to all incoming messages. You need this information to -implement matches that match on a message sender name correctly. Of -course, you should only request the attachment of as little metadata -fields as you need. - -The kernel will return in the "id" field your unique id. This is a -simple numeric value. For compatibility with classic dbus1 simply -format this as string and prefix ":1.". - -The kernel will also return the bloom filter size and bloom filter -hash function number used for the signal broadcast bloom filter (see -below). - -The kernel will also return the bus ID of the bus in a 128bit field. - -The pool size field specifies the size of the memory mapped buffer. -After the calling the hello ioctl, you should memory map the kdbus -fd. In this memory mapped region, the kernel will place all your incoming -messages. - -SENDING MESSAGES - -Use the MSG_SEND ioctl to send a message to another peer. The ioctl -takes a structure that contains a variety of fields: - -The flags field corresponds closely to the old dbus1 message header -flags field, though the DONT_EXPECT_REPLY field got inverted into -EXPECT_REPLY. - -The dst_id/src_id field contains the unique id of the destination and -the sender. The sender field is overridden by the kernel usually, hence -you shouldn't fill it in. The destination field can also take the -special value KDBUS_DST_ID_BROADCAST for broadcast messages. For -messages intended to a well-known name set the field to -KDBUS_DST_ID_NAME, and attach the name in a special "items" entry to -the message (see below). - -The payload field indicates the payload. For all dbus traffic it -should carry the value 0x4442757344427573ULL. (Which encodes -'DBusDBus'). - -The cookie field corresponds with the "serial" field of classic -dbus1. We simply renamed it here (and extended it to 64bit) since we -didn't want to imply the monotonicity of the assignment the way the -word "serial" indicates it. - -When sending a message that expects a reply, you need to set the -EXPECT_REPLY flag in the message flag field. In this case you should -also fill out the "timeout_ns" value which indicates the timeout in -nsec for this call. If the peer does not respond in this time you will -get a notification of a timeout. Note that this is also used for -security purposes: a single reply messages is only allowed through the -bus as long as the timeout has not ended. With this timeout value you -hence "open a time window" in which the peer might respond to your -request and the policy allows the response to go through. - -When sending a message that is a reply, you need to fill in the -cookie_reply field, which is similar to the reply_serial field of -dbus1. Note that a message cannot have EXPECT_REPLY and a reply_serial -at the same time! - -This pretty much explains the ioctl header. The actual payload of the -data is now referenced in additional items that are attached to this -ioctl header structure at the end. When sending a message, you attach -items of the type PAYLOAD_VEC, PAYLOAD_MEMFD, FDS, BLOOM_FILTER, -DST_NAME to it: - - KDBUS_ITEM_PAYLOAD_VEC: contains a pointer + length pair for - referencing arbitrary user memory. This is how you reference most - of your data. It's a lot like the good old iovec structure of glibc. - - KDBUS_ITEM_PAYLOAD_MEMFD: for large data blocks it is preferable - to send prepared "memfds" (see below) over. This item contains an - fd for a memfd plus a size. - - KDBUS_ITEM_FDS: for sending over fds attach an item of this type with - an array of fds. - - KDBUS_ITEM_BLOOM_FILTER: the calculated bloom filter of this message, - only for undirected (broadcast) message. - - KDBUS_ITEM_DST_NAME: for messages that are directed to a well-known - name (instead of a unique name), this item contains the well-known - name field. - -A single message may consists of no, one or more payload items of type -PAYLOAD_VEC or PAYLOAD_MEMFD. D-Bus protocol implementations should -treat them as a single block that just happens to be split up into -multiple items. Some restrictions apply however: - - The message header in its entirety must be contained in a single - PAYLOAD_VEC item. - - You may only split your message up right in front of each GVariant - contained in the payload, as well is immediately before framing of a - Gvariant, as well after as any padding bytes if there are any. The - padding bytes must be wholly contained in the preceding - PAYLOAD_VEC/PAYLOAD_MEMFD item. You may not split up basic types - nor arrays of fixed types. The latter is necessary to allow APIs - to return direct pointers to linear arrays of numeric - values. Examples: The basic types "u", "s", "t" have to be in the - same payload item. The array of fixed types "ay", "ai" have to be - fully in contained in the same payload item. For an array "as" or - "a(si)" the only restriction however is to keep each string - individually in an uninterrupted item, to keep the framing of each - element and the array in a single uninterrupted item, however the - various strings might end up in different items. - -Note again, that splitting up messages into separate items is up to the -implementation. Also note that the kdbus kernel side might merge -separate items if it deems this to be useful. However, the order in -which items are contained in the message is left untouched. - -PAYLOAD_MEMFD items allow zero-copy data transfer (see below regarding -the memfd concept). Note however that the overhead of mapping these -makes them relatively expensive, and only worth the trouble for memory -blocks > 512K (this value appears to be quite universal across -architectures, as we tested). Thus we recommend sending PAYLOAD_VEC -items over for small messages and restore to PAYLOAD_MEMFD items for -messages > 512K. Since while building up the message you might not -know yet whether it will grow beyond this boundary a good approach is -to simply build the message unconditionally in a memfd -object. However, when the message is sealed to be sent away check for -the size limit. If the size of the message is < 512K, then simply send -the data as PAYLOAD_VEC and reuse the memfd. If it is >= 512K, seal -the memfd and send it as PAYLOAD_MEMFD, and allocate a new memfd for -the next message. - -RECEIVING MESSAGES - -Use the MSG_RECV ioctl to read a message from kdbus. This will return -an offset into the pool memory map, relative to its beginning. - -The received message structure more or less follows the structure of -the message originally sent. However, certain changes have been -made. In the header the src_id field will be filled in. - -The payload items might have gotten merged and PAYLOAD_VEC items are -not used. Instead, you will only find PAYLOAD_OFF and PAYLOAD_MEMFD -items. The former contain an offset and size into your memory mapped -pool where you find the payload. - -If during the HELLO ioctl you asked for getting metadata attached to -your message, you will find additional KDBUS_ITEM_CREDS, -KDBUS_ITEM_PID_COMM, KDBUS_ITEM_TID_COMM, KDBUS_ITEM_TIMESTAMP, -KDBUS_ITEM_EXE, KDBUS_ITEM_CMDLINE, KDBUS_ITEM_CGROUP, -KDBUS_ITEM_CAPS, KDBUS_ITEM_SECLABEL, KDBUS_ITEM_AUDIT items that -contain this metadata. This metadata will be gathered from the sender -at the point in time it sends the message. This information is -uncached, and since it is appended by the kernel, trustable. The -KDBUS_ITEM_SECLABEL item usually contains the SELinux security label, -if it is used. - -After processing the message you need to call the KDBUS_CMD_FREE -ioctl, which releases the message from the pool, and allows the kernel -to store another message there. Note that the memory used by the pool -is ordinary anonymous, swappable memory that is backed by tmpfs. Hence -there is no need to copy the message out of it quickly, instead you -can just leave it there as long as you need it and release it via the -FREE ioctl only after that's done. - -BLOOM FILTERS - -The kernel does not understand dbus marshaling, it will not look into -the message payload. To allow clients to subscribe to specific subsets -of the broadcast matches we employ bloom filters. - -When broadcasting messages, a bloom filter needs to be attached to the -message in a KDBUS_ITEM_BLOOM item (and only for broadcasting -messages!). If you don't know what bloom filters are, read up now on -Wikipedia. In short: they are a very efficient way how to -probabilistically check whether a certain word is contained in a -vocabulary. It knows no false negatives, but it does know false -positives. - -The parameters for the bloom filters that need to be included in -broadcast message is communicated to userspace as part of the hello -response structure (see above). By default it has the parameters m=512 -(bits in the filter), k=8 (nr of hash functions). Note however, that -this is subject to change in later versions, and userspace -implementations must be capable of handling m values between at least -m=8 and m=2^32, and k values between at least k=1 and k=32. The -underlying hash function is SipHash-2-4. It is used with a number of -constant (yet originally randomly generated) 128bit hash keys, more -specifically: - - b9,66,0b,f0,46,70,47,c1,88,75,c4,9c,54,b9,bd,15, - aa,a1,54,a2,e0,71,4b,39,bf,e1,dd,2e,9f,c5,4a,3b, - 63,fd,ae,be,cd,82,48,12,a1,6e,41,26,cb,fa,a0,c8, - 23,be,45,29,32,d2,46,2d,82,03,52,28,fe,37,17,f5, - 56,3b,bf,ee,5a,4f,43,39,af,aa,94,08,df,f0,fc,10, - 31,80,c8,73,c7,ea,46,d3,aa,25,75,0f,9e,4c,09,29, - 7d,f7,18,4b,7b,a4,44,d5,85,3c,06,e0,65,53,96,6d, - f2,77,e9,6f,93,b5,4e,71,9a,0c,34,88,39,25,bf,35 - -When calculating the first bit index into the bloom filter, the -SipHash-2-4 hash value is calculated for the input data and the first -16 bytes of the array above as hash key. Of the resulting 8 bytes of -output, as many full bytes are taken for the bit index as necessary, -starting from the output's first byte. For the second bit index the -same hash value is used, continuing with the next unused output byte, -and so on. Each time the bytes returned by the hash function are -depleted it is recalculated with the next 16 byte hash key from the -array above and the same input data. - -For each message to send across the bus we populate the bloom filter -with all possible matchable strings. If a client then wants to -subscribe to messages of this type, it simply tells the kernel to test -its own calculated bit mask against the bloom filter of each message. - -More specifically, the following strings are added to the bloom filter -of each message that is broadcasted: - - The string "interface:" suffixed by the interface name - - The string "member:" suffixed by the member name - - The string "path:" suffixed by the path name - - The string "path-slash-prefix:" suffixed with the path name, and - also all prefixes of the path name (cut off at "/"), also prefixed - with "path-slash-prefix". - - The string "message-type:" suffixed with the strings "signal", - "method_call", "error" or "method_return" for the respective message - type of the message. - - If the first argument of the message is a string, "arg0:" suffixed - with the first argument. - - If the first argument of the message is a string, "arg0-dot-prefix" - suffixed with the first argument, and also all prefixes of the - argument (cut off at "."), also prefixed with "arg0-dot-prefix". - - If the first argument of the message is a string, - "arg0-slash-prefix" suffixed with the first argument, and also all - prefixes of the argument (cut off at "/"), also prefixed with - "arg0-slash-prefix". - - Similar for all further arguments that are strings up to 63, for the - arguments and their "dot" and "slash" prefixes. On the first - argument that is not a string, addition to the bloom filter should be - stopped however. - -(Note that the bloom filter does not contain sender nor receiver -names!) - -When a client wants to subscribe to messages matching a certain -expression, it should calculate the bloom mask following the same -algorithm. The kernel will then simply test the mask against the -attached bloom filters. - -Note that bloom filters are probabilistic, which means that clients -might get messages they did not expect. Your bus protocol -implementation must be capable of dealing with these unexpected -messages (which it needs to anyway, given that transfers are -relatively unrestricted on kdbus and people can send you all kinds of -non-sense). - -If a client connects to a bus whose bloom filter metrics (i.e. filter -size and number of hash functions) are outside of the range the client -supports it must immediately disconnect and continue connection with -the next bus address of the bus connection string. - -INSTALLING MATCHES - -To install matches for broadcast messages, use the KDBUS_CMD_ADD_MATCH -ioctl. It takes a structure that contains an encoded match expression, -and that is followed by one or more items, which are combined in an -AND way. (Meaning: a message is matched exactly when all items -attached to the original ioctl struct match). - -To match against other user messages add a KDBUS_ITEM_BLOOM item in -the match (see above). Note that the bloom filter does not include -matches to the sender names. To additionally check against sender -names, use the KDBUS_ITEM_ID (for unique id matches) and -KDBUS_ITEM_NAME (for well-known name matches) item types. - -To match against kernel generated messages (see below) you should add -items of the same type as the kernel messages include, -i.e. KDBUS_ITEM_NAME_ADD, KDBUS_ITEM_NAME_REMOVE, -KDBUS_ITEM_NAME_CHANGE, KDBUS_ITEM_ID_ADD, KDBUS_ITEM_ID_REMOVE and -fill them out. Note however, that you have some wildcards in this -case, for example the .id field of KDBUS_ITEM_ID_ADD/KDBUS_ITEM_ID_REMOVE -structures may be set to 0 to match against any id addition/removal. - -Note that dbus match strings do no map 1:1 to these ioctl() calls. In -many cases (where the match string is "underspecified") you might need -to issue up to six different ioctl() calls for the same match. For -example, the empty match (which matches against all messages), would -translate into one KDBUS_ITEM_BLOOM ioctl, one KDBUS_ITEM_NAME_ADD, -one KDBUS_ITEM_NAME_CHANGE, one KDBUS_ITEM_NAME_REMOVE, one -KDBUS_ITEM_ID_ADD and one KDBUS_ITEM_ID_REMOVE. - -When creating a match, you may attach a "cookie" value to them, which -is used for deleting this match again. The cookie can be selected freely -by the client. When issuing KDBUS_CMD_REMOVE_MATCH, simply pass the -same cookie as before and all matches matching the same "cookie" value -will be removed. This is particularly handy for the case where multiple -ioctl()s are added for a single match strings. - -MEMFDS - -memfds may be sent across kdbus via KDBUS_ITEM_PAYLOAD_MEMFD items -attached to messages. If this is done, the data included in the memfd -is considered part of the payload stream of a message, and are treated -the same way as KDBUS_ITEM_PAYLOAD_VEC by the receiving side. It is -possible to interleave KDBUS_ITEM_PAYLOAD_MEMFD and -KDBUS_ITEM_PAYLOAD_VEC items freely, by the reader they will be -considered a single stream of bytes in the order these items appear in -the message, that just happens to be split up at various places -(regarding rules how they may be split up, see above). The kernel will -refuse taking KDBUS_ITEM_PAYLOAD_MEMFD items that refer to memfds that -are not sealed. - -Note that sealed memfds may be unsealed again if they are not mapped -you have the only fd reference to them. - -Alternatively to sending memfds as KDBUS_ITEM_PAYLOAD_MEMFD items -(where they are just a part of the payload stream of a message) you can -also simply attach any memfd to a message using -KDBUS_ITEM_PAYLOAD_FDS. In this case, the memfd contents is not -considered part of the payload stream of the message, but simply fds -like any other, that happen to be attached to the message. - -MESSAGES FROM THE KERNEL - -A couple of messages previously generated by the dbus1 bus driver are -now generated by the kernel. Since the kernel does not understand the -payload marshaling, they are generated by the kernel in a different -format. This is indicated with the "payload type" field of the -messages set to 0. Library implementations should take these messages -and synthesize traditional driver messages for them on reception. - -More specifically: - - Instead of the NameOwnerChanged, NameLost, NameAcquired signals - there are kernel messages containing KDBUS_ITEM_NAME_ADD, - KDBUS_ITEM_NAME_REMOVE, KDBUS_ITEM_NAME_CHANGE, KDBUS_ITEM_ID_ADD, - KDBUS_ITEM_ID_REMOVE items are generated (each message will contain - exactly one of these items). Note that in libsystemd we have - obsoleted NameLost/NameAcquired messages, since they are entirely - redundant to NameOwnerChanged. This library will hence only - synthesize NameOwnerChanged messages from these kernel messages, - and never generate NameLost/NameAcquired. If your library needs to - stay compatible to the old dbus1 userspace, you possibly might need - to synthesize both a NameOwnerChanged and NameLost/NameAcquired - message from the same kernel message. - - When a method call times out, a KDBUS_ITEM_REPLY_TIMEOUT message is - generated. This should be synthesized into a method error reply - message to the original call. - - When a method call fails because the peer terminated the connection - before responding, a KDBUS_ITEM_REPLY_DEAD message is - generated. Similarly, it should be synthesized into a method error - reply message. - -For synthesized messages we recommend setting the cookie field to -(uint32_t) -1 (and not (uint64_t) -1!), so that the cookie is not 0 -(which the dbus1 spec does not allow), but clearly recognizable as -synthetic. - -Note that the KDBUS_ITEM_NAME_XYZ messages will actually inform you -about all kinds of names, including activatable ones. Classic dbus1 -NameOwnerChanged messages OTOH are only generated when a name is -really acquired on the bus and not just simply activatable. This means -you must explicitly check for the case where an activatable name -becomes acquired or an acquired name is lost and returns to be -activatable. - -NAME REGISTRY - -To acquire names on the bus, use the KDBUS_CMD_NAME_ACQUIRE ioctl(). It -takes a flags field similar to dbus1's RequestName() bus driver call, -however the NO_QUEUE flag got inverted into a QUEUE flag instead. - -To release a previously acquired name use the KDBUS_CMD_NAME_RELEASE -ioctl(). - -To list acquired names use the KDBUS_CMD_CONN_INFO ioctl. It may be -used to list unique names, well known names as well as activatable -names and clients currently queuing for ownership of a well-known -name. The ioctl will return an offset into the memory pool. After -reading all the data you need, you need to release this via the -KDBUS_CMD_FREE ioctl(), similar how you release a received message. - -CREDENTIALS - -kdbus can optionally attach various kinds of metadata about the sender at -the point of time of sending ("credentials") to messages, on request -of the receiver. This is both supported on directed and undirected -(broadcast) messages. The metadata to attach is selected at time of -the HELLO ioctl of the receiver via a flags field (see above). Note -that clients must be able to handle that messages contain more -metadata than they asked for themselves, to simplify implementation of -broadcasting in the kernel. The receiver should not rely on this data -to be around though, even though it will be correct if it happens to -be attached. In order to avoid programming errors in applications, we -recommend though not passing this data on to clients that did not -explicitly ask for it. - -Credentials may also be queried for a well-known or unique name. Use -the KDBUS_CMD_CONN_INFO for this. It will return an offset to the pool -area again, which will contain the same credential items as messages -have attached. Note that when issuing the ioctl, you can select a -different set of credentials to gather, than what was originally requested -for being attached to incoming messages. - -Credentials are always specific to the sender's domain that was -current at the time of sending, and of the process that opened the -bus connection at the time of opening it. Note that this latter data -is cached! - -POLICY - -The kernel enforces only very limited policy on names. It will not do -access filtering by userspace payload, and thus not by interface or -method name. - -This ultimately means that most fine-grained policy enforcement needs -to be done by the receiving process. We recommend using PolicyKit for -any more complex checks. However, libraries should make simple static -policy decisions regarding privileged/unprivileged method calls -easy. We recommend doing this by enabling KDBUS_ATTACH_CAPS and -KDBUS_ATTACH_CREDS for incoming messages, and then discerning client -access by some capability, or if sender and receiver UIDs match. - -BUS ADDRESSES - -When connecting to kdbus use the "kernel:" protocol prefix in DBus -address strings. The device node path is encoded in its "path=" -parameter. - -Client libraries should use the following connection string when -connecting to the system bus: - - kernel:path=/sys/fs/kdbus/0-system/bus;unix:path=/var/run/dbus/system_bus_socket - -This will ensure that kdbus is preferred over the legacy AF_UNIX -socket, but compatibility is kept. For the user bus use: - - kernel:path=/sys/fs/kdbus/$UID-user/bus;unix:path=$XDG_RUNTIME_DIR/bus - -With $UID replaced by the callers numer user ID, and $XDG_RUNTIME_DIR -following the XDG basedir spec. - -Of course the $DBUS_SYSTEM_BUS_ADDRESS and $DBUS_SESSION_BUS_ADDRESS -variables should still take precedence. - -DBUS SERVICE FILES - -Activatable services for kdbus may not use classic dbus1 service -activation files. Instead, programs should drop in native systemd -.service and .busname unit files, so that they are treated uniformly -with other types of units and activation of the system. - -Note that this results in a major difference to classic dbus1: -activatable bus names can be established at any time in the boot process. -This is unlike dbus1 where activatable names are unconditionally available -as long as dbus-daemon is running. Being able to control when -activatable names are established is essential to allow usage of kdbus -during early boot and in initrds, without the risk of triggering -services too early. - -DISCLAIMER - -This all is so far just the status quo. We are putting this together, because -we are quite confident that further API changes will be smaller, but -to make this very clear: this is all subject to change, still! - -We invite you to port over your favorite dbus library to this new -scheme, but please be prepared to make minor changes when we still -change these interfaces! diff --git a/src/libsystemd/sd-bus/bus-bloom.c b/src/libsystemd/sd-bus/bus-bloom.c deleted file mode 100644 index 112769fcb6..0000000000 --- a/src/libsystemd/sd-bus/bus-bloom.c +++ /dev/null @@ -1,156 +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 . -***/ - -#include "bus-bloom.h" -#include "siphash24.h" -#include "util.h" - -static inline void set_bit(uint64_t filter[], unsigned long b) { - filter[b >> 6] |= 1ULL << (b & 63); -} - -static const sd_id128_t hash_keys[] = { - SD_ID128_ARRAY(b9,66,0b,f0,46,70,47,c1,88,75,c4,9c,54,b9,bd,15), - SD_ID128_ARRAY(aa,a1,54,a2,e0,71,4b,39,bf,e1,dd,2e,9f,c5,4a,3b), - SD_ID128_ARRAY(63,fd,ae,be,cd,82,48,12,a1,6e,41,26,cb,fa,a0,c8), - SD_ID128_ARRAY(23,be,45,29,32,d2,46,2d,82,03,52,28,fe,37,17,f5), - SD_ID128_ARRAY(56,3b,bf,ee,5a,4f,43,39,af,aa,94,08,df,f0,fc,10), - SD_ID128_ARRAY(31,80,c8,73,c7,ea,46,d3,aa,25,75,0f,9e,4c,09,29), - SD_ID128_ARRAY(7d,f7,18,4b,7b,a4,44,d5,85,3c,06,e0,65,53,96,6d), - SD_ID128_ARRAY(f2,77,e9,6f,93,b5,4e,71,9a,0c,34,88,39,25,bf,35), -}; - -static void bloom_add_data( - uint64_t filter[], /* The filter bits */ - size_t size, /* Size of the filter in bytes */ - unsigned k, /* Number of hash functions */ - const void *data, /* Data to hash */ - size_t n) { /* Size of data to hash in bytes */ - - uint64_t h; - uint64_t m; - unsigned w, i, c = 0; - unsigned hash_index; - - assert(size > 0); - assert(k > 0); - - /* Determine bits in filter */ - m = size * 8; - - /* Determine how many bytes we need to generate a bit index 0..m for this filter */ - w = (u64log2(m) + 7) / 8; - - assert(w <= sizeof(uint64_t)); - - /* Make sure we have enough hash keys to generate m * k bits - * of hash value. Note that SipHash24 generates 64 bits of - * hash value for each 128 bits of hash key. */ - assert(k * w <= ELEMENTSOF(hash_keys) * 8); - - for (i = 0, hash_index = 0; i < k; i++) { - uint64_t p = 0; - unsigned d; - - for (d = 0; d < w; d++) { - if (c <= 0) { - h = siphash24(data, n, hash_keys[hash_index++].bytes); - c += 8; - } - - p = (p << 8ULL) | (uint64_t) ((uint8_t *)&h)[8 - c]; - c--; - } - - p &= m - 1; - set_bit(filter, p); - } - - /* log_debug("bloom: adding <%.*s>", (int) n, (char*) data); */ -} - -void bloom_add_pair(uint64_t filter[], size_t size, unsigned k, const char *a, const char *b) { - size_t n; - char *c; - - assert(filter); - assert(a); - assert(b); - - n = strlen(a) + 1 + strlen(b); - c = alloca(n + 1); - strcpy(stpcpy(stpcpy(c, a), ":"), b); - - bloom_add_data(filter, size, k, c, n); -} - -void bloom_add_prefixes(uint64_t filter[], size_t size, unsigned k, const char *a, const char *b, char sep) { - size_t n; - char *c, *p; - - assert(filter); - assert(a); - assert(b); - - n = strlen(a) + 1 + strlen(b); - c = alloca(n + 1); - - p = stpcpy(stpcpy(c, a), ":"); - strcpy(p, b); - - bloom_add_data(filter, size, k, c, n); - - for (;;) { - char *e; - - e = strrchr(p, sep); - if (!e) - break; - - *(e + 1) = 0; - bloom_add_data(filter, size, k, c, e - c + 1); - - if (e == p) - break; - - *e = 0; - bloom_add_data(filter, size, k, c, e - c); - } -} - -bool bloom_validate_parameters(size_t size, unsigned k) { - uint64_t m; - unsigned w; - - if (size <= 0) - return false; - - if (k <= 0) - return false; - - m = size * 8; - w = (u64log2(m) + 7) / 8; - if (w > sizeof(uint64_t)) - return false; - - if (k * w > ELEMENTSOF(hash_keys) * 8) - return false; - - return true; -} diff --git a/src/libsystemd/sd-bus/bus-bloom.h b/src/libsystemd/sd-bus/bus-bloom.h deleted file mode 100644 index c824622b95..0000000000 --- a/src/libsystemd/sd-bus/bus-bloom.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include -#include -#include - -/* - * Our default bloom filter has the following parameters: - * - * m=512 (bits in the filter) - * k=8 (hash functions) - * - * We use SipHash24 as hash function with a number of (originally - * randomized) but fixed hash keys. - * - */ - -#define DEFAULT_BLOOM_SIZE (512/8) /* m: filter size */ -#define DEFAULT_BLOOM_N_HASH 8 /* k: number of hash functions */ - -void bloom_add_pair(uint64_t filter[], size_t size, unsigned n_hash, const char *a, const char *b); -void bloom_add_prefixes(uint64_t filter[], size_t size, unsigned n_hash, const char *a, const char *b, char sep); - -bool bloom_validate_parameters(size_t size, unsigned n_hash); diff --git a/src/libsystemd/sd-bus/bus-common-errors.c b/src/libsystemd/sd-bus/bus-common-errors.c deleted file mode 100644 index 3c19f2b108..0000000000 --- a/src/libsystemd/sd-bus/bus-common-errors.c +++ /dev/null @@ -1,87 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "sd-bus.h" - -#include "bus-common-errors.h" -#include "bus-error.h" - -BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_common_errors[] = { - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_UNIT, ENOENT), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_UNIT_FOR_PID, ESRCH), - SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_EXISTS, EEXIST), - SD_BUS_ERROR_MAP(BUS_ERROR_LOAD_FAILED, EIO), - SD_BUS_ERROR_MAP(BUS_ERROR_JOB_FAILED, EREMOTEIO), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_JOB, ENOENT), - SD_BUS_ERROR_MAP(BUS_ERROR_NOT_SUBSCRIBED, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_ALREADY_SUBSCRIBED, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_ONLY_BY_DEPENDENCY, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_JOBS_CONFLICTING, EDEADLK), - SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC, EDEADLK), - SD_BUS_ERROR_MAP(BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE, EDEADLK), - SD_BUS_ERROR_MAP(BUS_ERROR_UNIT_MASKED, ESHUTDOWN), - SD_BUS_ERROR_MAP(BUS_ERROR_JOB_TYPE_NOT_APPLICABLE, EBADR), - 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_MACHINE, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_IMAGE, ENOENT), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_MACHINE_FOR_PID, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_MACHINE_EXISTS, EEXIST), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_PRIVATE_NETWORKING, ENOSYS), - - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SESSION, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SESSION_FOR_PID, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_USER, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_USER_FOR_PID, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SEAT, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_SESSION_NOT_ON_SEAT, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_NOT_IN_CONTROL, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_DEVICE_IS_TAKEN, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_DEVICE_NOT_TAKEN, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_OPERATION_IN_PROGRESS, EINPROGRESS), - SD_BUS_ERROR_MAP(BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED, EOPNOTSUPP), - - SD_BUS_ERROR_MAP(BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, EALREADY), - - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_PROCESS, ESRCH), - - SD_BUS_ERROR_MAP(BUS_ERROR_NO_NAME_SERVERS, ESRCH), - SD_BUS_ERROR_MAP(BUS_ERROR_INVALID_REPLY, EINVAL), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_RR, ENOENT), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_RESOURCES, ENOMEM), - SD_BUS_ERROR_MAP(BUS_ERROR_CNAME_LOOP, EDEADLK), - SD_BUS_ERROR_MAP(BUS_ERROR_ABORTED, ECANCELED), - SD_BUS_ERROR_MAP(BUS_ERROR_CONNECTION_FAILURE, ECONNREFUSED), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_SERVICE, EUNATCH), - SD_BUS_ERROR_MAP(BUS_ERROR_DNSSEC_FAILED, EHOSTUNREACH), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_TRUST_ANCHOR, EHOSTUNREACH), - SD_BUS_ERROR_MAP(BUS_ERROR_RR_TYPE_UNSUPPORTED, EOPNOTSUPP), - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_LINK, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_LINK_BUSY, EBUSY), - SD_BUS_ERROR_MAP(BUS_ERROR_NETWORK_DOWN, ENETDOWN), - - SD_BUS_ERROR_MAP(BUS_ERROR_NO_SUCH_TRANSFER, ENXIO), - SD_BUS_ERROR_MAP(BUS_ERROR_TRANSFER_IN_PROGRESS, EBUSY), - - SD_BUS_ERROR_MAP_END -}; diff --git a/src/libsystemd/sd-bus/bus-common-errors.h b/src/libsystemd/sd-bus/bus-common-errors.h deleted file mode 100644 index fab8748f46..0000000000 --- a/src/libsystemd/sd-bus/bus-common-errors.h +++ /dev/null @@ -1,86 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "bus-error.h" - -#define BUS_ERROR_NO_SUCH_UNIT "org.freedesktop.systemd1.NoSuchUnit" -#define BUS_ERROR_NO_UNIT_FOR_PID "org.freedesktop.systemd1.NoUnitForPID" -#define BUS_ERROR_UNIT_EXISTS "org.freedesktop.systemd1.UnitExists" -#define BUS_ERROR_LOAD_FAILED "org.freedesktop.systemd1.LoadFailed" -#define BUS_ERROR_JOB_FAILED "org.freedesktop.systemd1.JobFailed" -#define BUS_ERROR_NO_SUCH_JOB "org.freedesktop.systemd1.NoSuchJob" -#define BUS_ERROR_NOT_SUBSCRIBED "org.freedesktop.systemd1.NotSubscribed" -#define BUS_ERROR_ALREADY_SUBSCRIBED "org.freedesktop.systemd1.AlreadySubscribed" -#define BUS_ERROR_ONLY_BY_DEPENDENCY "org.freedesktop.systemd1.OnlyByDependency" -#define BUS_ERROR_TRANSACTION_JOBS_CONFLICTING "org.freedesktop.systemd1.TransactionJobsConflicting" -#define BUS_ERROR_TRANSACTION_ORDER_IS_CYCLIC "org.freedesktop.systemd1.TransactionOrderIsCyclic" -#define BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE "org.freedesktop.systemd1.TransactionIsDestructive" -#define BUS_ERROR_UNIT_MASKED "org.freedesktop.systemd1.UnitMasked" -#define BUS_ERROR_JOB_TYPE_NOT_APPLICABLE "org.freedesktop.systemd1.JobTypeNotApplicable" -#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_MACHINE "org.freedesktop.machine1.NoSuchMachine" -#define BUS_ERROR_NO_SUCH_IMAGE "org.freedesktop.machine1.NoSuchImage" -#define BUS_ERROR_NO_MACHINE_FOR_PID "org.freedesktop.machine1.NoMachineForPID" -#define BUS_ERROR_MACHINE_EXISTS "org.freedesktop.machine1.MachineExists" -#define BUS_ERROR_NO_PRIVATE_NETWORKING "org.freedesktop.machine1.NoPrivateNetworking" -#define BUS_ERROR_NO_SUCH_USER_MAPPING "org.freedesktop.machine1.NoSuchUserMapping" -#define BUS_ERROR_NO_SUCH_GROUP_MAPPING "org.freedesktop.machine1.NoSuchGroupMapping" - -#define BUS_ERROR_NO_SUCH_SESSION "org.freedesktop.login1.NoSuchSession" -#define BUS_ERROR_NO_SESSION_FOR_PID "org.freedesktop.login1.NoSessionForPID" -#define BUS_ERROR_NO_SUCH_USER "org.freedesktop.login1.NoSuchUser" -#define BUS_ERROR_NO_USER_FOR_PID "org.freedesktop.login1.NoUserForPID" -#define BUS_ERROR_NO_SUCH_SEAT "org.freedesktop.login1.NoSuchSeat" -#define BUS_ERROR_SESSION_NOT_ON_SEAT "org.freedesktop.login1.SessionNotOnSeat" -#define BUS_ERROR_NOT_IN_CONTROL "org.freedesktop.login1.NotInControl" -#define BUS_ERROR_DEVICE_IS_TAKEN "org.freedesktop.login1.DeviceIsTaken" -#define BUS_ERROR_DEVICE_NOT_TAKEN "org.freedesktop.login1.DeviceNotTaken" -#define BUS_ERROR_OPERATION_IN_PROGRESS "org.freedesktop.login1.OperationInProgress" -#define BUS_ERROR_SLEEP_VERB_NOT_SUPPORTED "org.freedesktop.login1.SleepVerbNotSupported" -#define BUS_ERROR_SESSION_BUSY "org.freedesktop.login1.SessionBusy" - -#define BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED "org.freedesktop.timedate1.AutomaticTimeSyncEnabled" - -#define BUS_ERROR_NO_SUCH_PROCESS "org.freedesktop.systemd1.NoSuchProcess" - -#define BUS_ERROR_NO_NAME_SERVERS "org.freedesktop.resolve1.NoNameServers" -#define BUS_ERROR_INVALID_REPLY "org.freedesktop.resolve1.InvalidReply" -#define BUS_ERROR_NO_SUCH_RR "org.freedesktop.resolve1.NoSuchRR" -#define BUS_ERROR_NO_RESOURCES "org.freedesktop.resolve1.NoResources" -#define BUS_ERROR_CNAME_LOOP "org.freedesktop.resolve1.CNameLoop" -#define BUS_ERROR_ABORTED "org.freedesktop.resolve1.Aborted" -#define BUS_ERROR_CONNECTION_FAILURE "org.freedesktop.resolve1.ConnectionFailure" -#define BUS_ERROR_NO_SUCH_SERVICE "org.freedesktop.resolve1.NoSuchService" -#define BUS_ERROR_DNSSEC_FAILED "org.freedesktop.resolve1.DnssecFailed" -#define BUS_ERROR_NO_TRUST_ANCHOR "org.freedesktop.resolve1.NoTrustAnchor" -#define BUS_ERROR_RR_TYPE_UNSUPPORTED "org.freedesktop.resolve1.ResourceRecordTypeUnsupported" -#define BUS_ERROR_NO_SUCH_LINK "org.freedesktop.resolve1.NoSuchLink" -#define BUS_ERROR_LINK_BUSY "org.freedesktop.resolve1.LinkBusy" -#define BUS_ERROR_NETWORK_DOWN "org.freedesktop.resolve1.NetworkDown" -#define _BUS_ERROR_DNS "org.freedesktop.resolve1.DnsError." - -#define BUS_ERROR_NO_SUCH_TRANSFER "org.freedesktop.import1.NoSuchTransfer" -#define BUS_ERROR_TRANSFER_IN_PROGRESS "org.freedesktop.import1.TransferInProgress" - -BUS_ERROR_MAP_ELF_USE(bus_common_errors); diff --git a/src/libsystemd/sd-bus/bus-container.c b/src/libsystemd/sd-bus/bus-container.c deleted file mode 100644 index 3191d27ded..0000000000 --- a/src/libsystemd/sd-bus/bus-container.c +++ /dev/null @@ -1,277 +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 . -***/ - -#include -#include - -#include "bus-container.h" -#include "bus-internal.h" -#include "bus-socket.h" -#include "fd-util.h" -#include "process-util.h" -#include "util.h" - -int bus_container_connect_socket(sd_bus *b) { - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; - pid_t child; - siginfo_t si; - int r, error_buf = 0; - ssize_t n; - - assert(b); - assert(b->input_fd < 0); - assert(b->output_fd < 0); - assert(b->nspid > 0 || b->machine); - - if (b->nspid <= 0) { - r = container_get_leader(b->machine, &b->nspid); - if (r < 0) - return r; - } - - r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); - if (r < 0) - return r; - - b->input_fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (b->input_fd < 0) - return -errno; - - b->output_fd = b->input_fd; - - bus_socket_setup(b); - - if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return -errno; - - if (child == 0) { - pid_t grandchild; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); - if (r < 0) - _exit(EXIT_FAILURE); - - /* We just changed PID namespace, however it will only - * take effect on the children we now fork. Hence, - * let's fork another time, and connect from this - * grandchild, so that SO_PEERCRED of our connection - * comes from a process from within the container, and - * not outside of it */ - - grandchild = fork(); - if (grandchild < 0) - _exit(EXIT_FAILURE); - - if (grandchild == 0) { - - r = connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size); - if (r < 0) { - /* Try to send error up */ - error_buf = errno; - (void) write(pair[1], &error_buf, sizeof(error_buf)); - _exit(EXIT_FAILURE); - } - - _exit(EXIT_SUCCESS); - } - - r = wait_for_terminate(grandchild, &si); - if (r < 0) - _exit(EXIT_FAILURE); - - if (si.si_code != CLD_EXITED) - _exit(EXIT_FAILURE); - - _exit(si.si_status); - } - - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate(child, &si); - if (r < 0) - return r; - - n = read(pair[0], &error_buf, sizeof(error_buf)); - if (n < 0) - return -errno; - - if (n > 0) { - if (n != sizeof(error_buf)) - return -EIO; - - if (error_buf < 0) - return -EIO; - - if (error_buf == EINPROGRESS) - return 1; - - if (error_buf > 0) - return -error_buf; - } - - if (si.si_code != CLD_EXITED) - return -EIO; - - if (si.si_status != EXIT_SUCCESS) - return -EIO; - - return bus_socket_start_auth(b); -} - -int bus_container_connect_kernel(sd_bus *b) { - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, usernsfd = -1, rootfd = -1; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int))]; - } control = {}; - int error_buf = 0; - struct iovec iov = { - .iov_base = &error_buf, - .iov_len = sizeof(error_buf), - }; - struct msghdr mh = { - .msg_control = &control, - .msg_controllen = sizeof(control), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - struct cmsghdr *cmsg; - pid_t child; - siginfo_t si; - int r, fd = -1; - ssize_t n; - - assert(b); - assert(b->input_fd < 0); - assert(b->output_fd < 0); - assert(b->nspid > 0 || b->machine); - - if (b->nspid <= 0) { - r = container_get_leader(b->machine, &b->nspid); - if (r < 0) - return r; - } - - r = namespace_open(b->nspid, &pidnsfd, &mntnsfd, NULL, &usernsfd, &rootfd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return -errno; - - if (child == 0) { - pid_t grandchild; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(pidnsfd, mntnsfd, -1, usernsfd, rootfd); - if (r < 0) - _exit(EXIT_FAILURE); - - /* We just changed PID namespace, however it will only - * take effect on the children we now fork. Hence, - * let's fork another time, and connect from this - * grandchild, so that kdbus only sees the credentials - * of this process which comes from within the - * container, and not outside of it */ - - grandchild = fork(); - if (grandchild < 0) - _exit(EXIT_FAILURE); - - if (grandchild == 0) { - fd = open(b->kernel, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) { - /* Try to send error up */ - error_buf = errno; - (void) write(pair[1], &error_buf, sizeof(error_buf)); - _exit(EXIT_FAILURE); - } - - r = send_one_fd(pair[1], fd, 0); - if (r < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - r = wait_for_terminate(grandchild, &si); - if (r < 0) - _exit(EXIT_FAILURE); - - if (si.si_code != CLD_EXITED) - _exit(EXIT_FAILURE); - - _exit(si.si_status); - } - - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate(child, &si); - if (r < 0) - return r; - - n = recvmsg(pair[0], &mh, MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); - if (n < 0) - return -errno; - - CMSG_FOREACH(cmsg, &mh) { - if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { - int *fds; - unsigned n_fds; - - assert(fd < 0); - - fds = (int*) CMSG_DATA(cmsg); - n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - - if (n_fds != 1) { - close_many(fds, n_fds); - return -EIO; - } - - fd = fds[0]; - } - } - - /* If there's an fd passed, we are good. */ - if (fd >= 0) { - b->input_fd = b->output_fd = fd; - return bus_kernel_take_fd(b); - } - - /* If there's an error passed, use it */ - if (n == sizeof(error_buf) && error_buf > 0) - return -error_buf; - - /* Otherwise, we have no clue */ - return -EIO; -} diff --git a/src/libsystemd/sd-bus/bus-container.h b/src/libsystemd/sd-bus/bus-container.h deleted file mode 100644 index 509ef45624..0000000000 --- a/src/libsystemd/sd-bus/bus-container.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "sd-bus.h" - -int bus_container_connect_socket(sd_bus *b); -int bus_container_connect_kernel(sd_bus *b); diff --git a/src/libsystemd/sd-bus/bus-control.c b/src/libsystemd/sd-bus/bus-control.c deleted file mode 100644 index 05222b8d30..0000000000 --- a/src/libsystemd/sd-bus/bus-control.c +++ /dev/null @@ -1,1586 +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 . -***/ - -#ifdef HAVE_VALGRIND_MEMCHECK_H -#include -#endif - -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-bloom.h" -#include "bus-control.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-util.h" -#include "capability-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" - -_public_ int sd_bus_get_unique_name(sd_bus *bus, const char **unique) { - int r; - - assert_return(bus, -EINVAL); - assert_return(unique, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!bus->bus_client) - return -EINVAL; - - r = bus_ensure_running(bus); - if (r < 0) - return r; - - *unique = bus->unique_name; - return 0; -} - -static int bus_request_name_kernel(sd_bus *bus, const char *name, uint64_t flags) { - struct kdbus_cmd *n; - size_t size, l; - int r; - - assert(bus); - assert(name); - - l = strlen(name) + 1; - size = offsetof(struct kdbus_cmd, items) + KDBUS_ITEM_SIZE(l); - n = alloca0_align(size, 8); - n->size = size; - n->flags = request_name_flags_to_kdbus(flags); - - n->items[0].size = KDBUS_ITEM_HEADER_SIZE + l; - n->items[0].type = KDBUS_ITEM_NAME; - memcpy(n->items[0].str, name, l); - -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(n, n->size); -#endif - - r = ioctl(bus->input_fd, KDBUS_CMD_NAME_ACQUIRE, n); - if (r < 0) - return -errno; - - if (n->return_flags & KDBUS_NAME_IN_QUEUE) - return 0; - - return 1; -} - -static int bus_request_name_dbus1(sd_bus *bus, const char *name, uint64_t flags) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - uint32_t ret, param = 0; - int r; - - assert(bus); - assert(name); - - if (flags & SD_BUS_NAME_ALLOW_REPLACEMENT) - param |= BUS_NAME_ALLOW_REPLACEMENT; - if (flags & SD_BUS_NAME_REPLACE_EXISTING) - param |= BUS_NAME_REPLACE_EXISTING; - if (!(flags & SD_BUS_NAME_QUEUE)) - param |= BUS_NAME_DO_NOT_QUEUE; - - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "RequestName", - NULL, - &reply, - "su", - name, - param); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "u", &ret); - if (r < 0) - return r; - - if (ret == BUS_NAME_ALREADY_OWNER) - return -EALREADY; - else if (ret == BUS_NAME_EXISTS) - return -EEXIST; - else if (ret == BUS_NAME_IN_QUEUE) - return 0; - else if (ret == BUS_NAME_PRIMARY_OWNER) - return 1; - - return -EIO; -} - -_public_ int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags) { - assert_return(bus, -EINVAL); - assert_return(name, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(!(flags & ~(SD_BUS_NAME_ALLOW_REPLACEMENT|SD_BUS_NAME_REPLACE_EXISTING|SD_BUS_NAME_QUEUE)), -EINVAL); - assert_return(service_name_is_valid(name), -EINVAL); - assert_return(name[0] != ':', -EINVAL); - - if (!bus->bus_client) - return -EINVAL; - - /* Don't allow requesting the special driver and local names */ - if (STR_IN_SET(name, "org.freedesktop.DBus", "org.freedesktop.DBus.Local")) - return -EINVAL; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->is_kernel) - return bus_request_name_kernel(bus, name, flags); - else - return bus_request_name_dbus1(bus, name, flags); -} - -static int bus_release_name_kernel(sd_bus *bus, const char *name) { - struct kdbus_cmd *n; - size_t size, l; - int r; - - assert(bus); - assert(name); - - l = strlen(name) + 1; - size = offsetof(struct kdbus_cmd, items) + KDBUS_ITEM_SIZE(l); - n = alloca0_align(size, 8); - n->size = size; - - n->items[0].size = KDBUS_ITEM_HEADER_SIZE + l; - n->items[0].type = KDBUS_ITEM_NAME; - memcpy(n->items[0].str, name, l); - -#ifdef HAVE_VALGRIND_MEMCHECK_H - VALGRIND_MAKE_MEM_DEFINED(n, n->size); -#endif - r = ioctl(bus->input_fd, KDBUS_CMD_NAME_RELEASE, n); - if (r < 0) - return -errno; - - return 0; -} - -static int bus_release_name_dbus1(sd_bus *bus, const char *name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - uint32_t ret; - int r; - - assert(bus); - assert(name); - - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "ReleaseName", - NULL, - &reply, - "s", - name); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "u", &ret); - if (r < 0) - return r; - if (ret == BUS_NAME_NON_EXISTENT) - return -ESRCH; - if (ret == BUS_NAME_NOT_OWNER) - return -EADDRINUSE; - if (ret == BUS_NAME_RELEASED) - return 0; - - return -EINVAL; -} - -_public_ int sd_bus_release_name(sd_bus *bus, const char *name) { - assert_return(bus, -EINVAL); - assert_return(name, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(service_name_is_valid(name), -EINVAL); - assert_return(name[0] != ':', -EINVAL); - - if (!bus->bus_client) - return -EINVAL; - - /* Don't allow releasing the special driver and local names */ - if (STR_IN_SET(name, "org.freedesktop.DBus", "org.freedesktop.DBus.Local")) - return -EINVAL; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->is_kernel) - return bus_release_name_kernel(bus, name); - else - return bus_release_name_dbus1(bus, name); -} - -static int kernel_get_list(sd_bus *bus, uint64_t flags, char ***x) { - struct kdbus_cmd_list cmd = { - .size = sizeof(cmd), - .flags = flags, - }; - struct kdbus_info *name_list, *name; - uint64_t previous_id = 0; - int r; - - /* Caller will free half-constructed list on failure... */ - - r = ioctl(bus->input_fd, KDBUS_CMD_LIST, &cmd); - if (r < 0) - return -errno; - - name_list = (struct kdbus_info *) ((uint8_t *) bus->kdbus_buffer + cmd.offset); - - KDBUS_FOREACH(name, name_list, cmd.list_size) { - struct kdbus_item *item; - - if ((flags & KDBUS_LIST_UNIQUE) && name->id != previous_id && !(name->flags & KDBUS_HELLO_ACTIVATOR)) { - char *n; - - if (asprintf(&n, ":1.%llu", (unsigned long long) name->id) < 0) { - r = -ENOMEM; - goto fail; - } - - r = strv_consume(x, n); - if (r < 0) - goto fail; - - previous_id = name->id; - } - - KDBUS_ITEM_FOREACH(item, name, items) { - if (item->type == KDBUS_ITEM_OWNED_NAME) { - if (service_name_is_valid(item->name.name)) { - r = strv_extend(x, item->name.name); - if (r < 0) { - r = -ENOMEM; - goto fail; - } - } - } - } - } - - r = 0; - -fail: - bus_kernel_cmd_free(bus, cmd.offset); - return r; -} - -static int bus_list_names_kernel(sd_bus *bus, char ***acquired, char ***activatable) { - _cleanup_strv_free_ char **x = NULL, **y = NULL; - int r; - - if (acquired) { - r = kernel_get_list(bus, KDBUS_LIST_UNIQUE | KDBUS_LIST_NAMES, &x); - if (r < 0) - return r; - } - - if (activatable) { - r = kernel_get_list(bus, KDBUS_LIST_ACTIVATORS, &y); - if (r < 0) - return r; - - *activatable = y; - y = NULL; - } - - if (acquired) { - *acquired = x; - x = NULL; - } - - return 0; -} - -static int bus_list_names_dbus1(sd_bus *bus, char ***acquired, char ***activatable) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_strv_free_ char **x = NULL, **y = NULL; - int r; - - if (acquired) { - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "ListNames", - NULL, - &reply, - NULL); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(reply, &x); - if (r < 0) - return r; - - reply = sd_bus_message_unref(reply); - } - - if (activatable) { - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "ListActivatableNames", - NULL, - &reply, - NULL); - if (r < 0) - return r; - - r = sd_bus_message_read_strv(reply, &y); - if (r < 0) - return r; - - *activatable = y; - y = NULL; - } - - if (acquired) { - *acquired = x; - x = NULL; - } - - return 0; -} - -_public_ int sd_bus_list_names(sd_bus *bus, char ***acquired, char ***activatable) { - assert_return(bus, -EINVAL); - assert_return(acquired || activatable, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!bus->bus_client) - return -EINVAL; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->is_kernel) - return bus_list_names_kernel(bus, acquired, activatable); - else - return bus_list_names_dbus1(bus, acquired, activatable); -} - -static int bus_populate_creds_from_items( - sd_bus *bus, - struct kdbus_info *info, - uint64_t mask, - sd_bus_creds *c) { - - struct kdbus_item *item; - uint64_t m; - int r; - - assert(bus); - assert(info); - assert(c); - - KDBUS_ITEM_FOREACH(item, info, items) { - - switch (item->type) { - - case KDBUS_ITEM_PIDS: - - if (mask & SD_BUS_CREDS_PID && item->pids.pid > 0) { - c->pid = (pid_t) item->pids.pid; - c->mask |= SD_BUS_CREDS_PID; - } - - if (mask & SD_BUS_CREDS_TID && item->pids.tid > 0) { - c->tid = (pid_t) item->pids.tid; - c->mask |= SD_BUS_CREDS_TID; - } - - if (mask & SD_BUS_CREDS_PPID) { - if (item->pids.ppid > 0) { - c->ppid = (pid_t) item->pids.ppid; - c->mask |= SD_BUS_CREDS_PPID; - } else if (item->pids.pid == 1) { - /* The structure doesn't - * really distinguish the case - * where a process has no - * parent and where we don't - * know it because it could - * not be translated due to - * namespaces. However, we - * know that PID 1 has no - * parent process, hence let's - * patch that in, manually. */ - c->ppid = 0; - c->mask |= SD_BUS_CREDS_PPID; - } - } - - break; - - case KDBUS_ITEM_CREDS: - - if (mask & SD_BUS_CREDS_UID && (uid_t) item->creds.uid != UID_INVALID) { - c->uid = (uid_t) item->creds.uid; - c->mask |= SD_BUS_CREDS_UID; - } - - if (mask & SD_BUS_CREDS_EUID && (uid_t) item->creds.euid != UID_INVALID) { - c->euid = (uid_t) item->creds.euid; - c->mask |= SD_BUS_CREDS_EUID; - } - - if (mask & SD_BUS_CREDS_SUID && (uid_t) item->creds.suid != UID_INVALID) { - c->suid = (uid_t) item->creds.suid; - c->mask |= SD_BUS_CREDS_SUID; - } - - if (mask & SD_BUS_CREDS_FSUID && (uid_t) item->creds.fsuid != UID_INVALID) { - c->fsuid = (uid_t) item->creds.fsuid; - c->mask |= SD_BUS_CREDS_FSUID; - } - - if (mask & SD_BUS_CREDS_GID && (gid_t) item->creds.gid != GID_INVALID) { - c->gid = (gid_t) item->creds.gid; - c->mask |= SD_BUS_CREDS_GID; - } - - if (mask & SD_BUS_CREDS_EGID && (gid_t) item->creds.egid != GID_INVALID) { - c->egid = (gid_t) item->creds.egid; - c->mask |= SD_BUS_CREDS_EGID; - } - - if (mask & SD_BUS_CREDS_SGID && (gid_t) item->creds.sgid != GID_INVALID) { - c->sgid = (gid_t) item->creds.sgid; - c->mask |= SD_BUS_CREDS_SGID; - } - - if (mask & SD_BUS_CREDS_FSGID && (gid_t) item->creds.fsgid != GID_INVALID) { - c->fsgid = (gid_t) item->creds.fsgid; - c->mask |= SD_BUS_CREDS_FSGID; - } - - break; - - case KDBUS_ITEM_PID_COMM: - if (mask & SD_BUS_CREDS_COMM) { - r = free_and_strdup(&c->comm, item->str); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_COMM; - } - break; - - case KDBUS_ITEM_TID_COMM: - if (mask & SD_BUS_CREDS_TID_COMM) { - r = free_and_strdup(&c->tid_comm, item->str); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_TID_COMM; - } - break; - - case KDBUS_ITEM_EXE: - if (mask & SD_BUS_CREDS_EXE) { - r = free_and_strdup(&c->exe, item->str); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_EXE; - } - break; - - case KDBUS_ITEM_CMDLINE: - if (mask & SD_BUS_CREDS_CMDLINE) { - c->cmdline_size = item->size - offsetof(struct kdbus_item, data); - c->cmdline = memdup(item->data, c->cmdline_size); - if (!c->cmdline) - return -ENOMEM; - - c->mask |= SD_BUS_CREDS_CMDLINE; - } - break; - - case KDBUS_ITEM_CGROUP: - m = (SD_BUS_CREDS_CGROUP | SD_BUS_CREDS_UNIT | - SD_BUS_CREDS_USER_UNIT | SD_BUS_CREDS_SLICE | - SD_BUS_CREDS_SESSION | SD_BUS_CREDS_OWNER_UID) & mask; - - if (m) { - r = free_and_strdup(&c->cgroup, item->str); - if (r < 0) - return r; - - r = bus_get_root_path(bus); - if (r < 0) - return r; - - r = free_and_strdup(&c->cgroup_root, bus->cgroup_root); - if (r < 0) - return r; - - c->mask |= m; - } - break; - - case KDBUS_ITEM_CAPS: - m = (SD_BUS_CREDS_EFFECTIVE_CAPS | SD_BUS_CREDS_PERMITTED_CAPS | - SD_BUS_CREDS_INHERITABLE_CAPS | SD_BUS_CREDS_BOUNDING_CAPS) & mask; - - if (m) { - if (item->caps.last_cap != cap_last_cap() || - item->size - offsetof(struct kdbus_item, caps.caps) < DIV_ROUND_UP(item->caps.last_cap, 32U) * 4 * 4) - return -EBADMSG; - - c->capability = memdup(item->caps.caps, item->size - offsetof(struct kdbus_item, caps.caps)); - if (!c->capability) - return -ENOMEM; - - c->mask |= m; - } - break; - - case KDBUS_ITEM_SECLABEL: - if (mask & SD_BUS_CREDS_SELINUX_CONTEXT) { - r = free_and_strdup(&c->label, item->str); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; - } - break; - - case KDBUS_ITEM_AUDIT: - if (mask & SD_BUS_CREDS_AUDIT_SESSION_ID) { - c->audit_session_id = (uint32_t) item->audit.sessionid; - c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; - } - - if (mask & SD_BUS_CREDS_AUDIT_LOGIN_UID) { - c->audit_login_uid = (uid_t) item->audit.loginuid; - c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; - } - break; - - case KDBUS_ITEM_OWNED_NAME: - if ((mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) && service_name_is_valid(item->name.name)) { - r = strv_extend(&c->well_known_names, item->name.name); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES; - } - break; - - case KDBUS_ITEM_CONN_DESCRIPTION: - if (mask & SD_BUS_CREDS_DESCRIPTION) { - r = free_and_strdup(&c->description, item->str); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_DESCRIPTION; - } - break; - - case KDBUS_ITEM_AUXGROUPS: - if (mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { - size_t i, n; - uid_t *g; - - n = (item->size - offsetof(struct kdbus_item, data64)) / sizeof(uint64_t); - g = new(gid_t, n); - if (!g) - return -ENOMEM; - - for (i = 0; i < n; i++) - g[i] = item->data64[i]; - - free(c->supplementary_gids); - c->supplementary_gids = g; - c->n_supplementary_gids = n; - - c->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; - } - break; - } - } - - return 0; -} - -int bus_get_name_creds_kdbus( - sd_bus *bus, - const char *name, - uint64_t mask, - bool allow_activator, - sd_bus_creds **creds) { - - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; - struct kdbus_cmd_info *cmd; - struct kdbus_info *conn_info; - size_t size, l; - uint64_t id; - int r; - - if (streq(name, "org.freedesktop.DBus")) - return -EOPNOTSUPP; - - r = bus_kernel_parse_unique_name(name, &id); - if (r < 0) - return r; - if (r > 0) { - size = offsetof(struct kdbus_cmd_info, items); - cmd = alloca0_align(size, 8); - cmd->id = id; - } else { - l = strlen(name) + 1; - size = offsetof(struct kdbus_cmd_info, items) + KDBUS_ITEM_SIZE(l); - cmd = alloca0_align(size, 8); - cmd->items[0].size = KDBUS_ITEM_HEADER_SIZE + l; - cmd->items[0].type = KDBUS_ITEM_NAME; - memcpy(cmd->items[0].str, name, l); - } - - /* If augmentation is on, and the bus didn't provide us - * the bits we want, then ask for the PID/TID so that we - * can read the rest from /proc. */ - if ((mask & SD_BUS_CREDS_AUGMENT) && - (mask & (SD_BUS_CREDS_PPID| - SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| - SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| - SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| - SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| - SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| - SD_BUS_CREDS_SELINUX_CONTEXT| - SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID))) - mask |= SD_BUS_CREDS_PID; - - cmd->size = size; - cmd->attach_flags = attach_flags_to_kdbus(mask); - - r = ioctl(bus->input_fd, KDBUS_CMD_CONN_INFO, cmd); - if (r < 0) - return -errno; - - conn_info = (struct kdbus_info *) ((uint8_t *) bus->kdbus_buffer + cmd->offset); - - /* Non-activated names are considered not available */ - if (!allow_activator && (conn_info->flags & KDBUS_HELLO_ACTIVATOR)) { - if (name[0] == ':') - r = -ENXIO; - else - r = -ESRCH; - goto fail; - } - - c = bus_creds_new(); - if (!c) { - r = -ENOMEM; - goto fail; - } - - if (mask & SD_BUS_CREDS_UNIQUE_NAME) { - if (asprintf(&c->unique_name, ":1.%llu", (unsigned long long) conn_info->id) < 0) { - r = -ENOMEM; - goto fail; - } - - c->mask |= SD_BUS_CREDS_UNIQUE_NAME; - } - - /* If KDBUS_ITEM_OWNED_NAME is requested then we'll get 0 of - them in case the service has no names. This does not mean - however that the list of owned names could not be - acquired. Hence, let's explicitly clarify that the data is - complete. */ - c->mask |= mask & SD_BUS_CREDS_WELL_KNOWN_NAMES; - - r = bus_populate_creds_from_items(bus, conn_info, mask, c); - if (r < 0) - goto fail; - - r = bus_creds_add_more(c, mask, 0, 0); - if (r < 0) - goto fail; - - if (creds) { - *creds = c; - c = NULL; - } - - r = 0; - -fail: - bus_kernel_cmd_free(bus, cmd->offset); - return r; -} - -static int bus_get_name_creds_dbus1( - sd_bus *bus, - const char *name, - uint64_t mask, - sd_bus_creds **creds) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply_unique = NULL, *reply = NULL; - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; - const char *unique = NULL; - pid_t pid = 0; - int r; - - /* Only query the owner if the caller wants to know it or if - * the caller just wants to check whether a name exists */ - if ((mask & SD_BUS_CREDS_UNIQUE_NAME) || mask == 0) { - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "GetNameOwner", - NULL, - &reply_unique, - "s", - name); - if (r < 0) - return r; - - r = sd_bus_message_read(reply_unique, "s", &unique); - if (r < 0) - return r; - } - - if (mask != 0) { - c = bus_creds_new(); - if (!c) - return -ENOMEM; - - if ((mask & SD_BUS_CREDS_UNIQUE_NAME) && unique) { - c->unique_name = strdup(unique); - if (!c->unique_name) - return -ENOMEM; - - c->mask |= SD_BUS_CREDS_UNIQUE_NAME; - } - - if ((mask & SD_BUS_CREDS_PID) || - ((mask & SD_BUS_CREDS_AUGMENT) && - (mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| - SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| - SD_BUS_CREDS_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| - SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| - SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| - SD_BUS_CREDS_SELINUX_CONTEXT| - SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID)))) { - - uint32_t u; - - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "GetConnectionUnixProcessID", - NULL, - &reply, - "s", - unique ? unique : name); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "u", &u); - if (r < 0) - return r; - - pid = u; - if (mask & SD_BUS_CREDS_PID) { - c->pid = u; - c->mask |= SD_BUS_CREDS_PID; - } - - reply = sd_bus_message_unref(reply); - } - - if (mask & SD_BUS_CREDS_EUID) { - uint32_t u; - - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "GetConnectionUnixUser", - NULL, - &reply, - "s", - unique ? unique : name); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "u", &u); - if (r < 0) - return r; - - c->euid = u; - c->mask |= SD_BUS_CREDS_EUID; - - reply = sd_bus_message_unref(reply); - } - - if (mask & SD_BUS_CREDS_SELINUX_CONTEXT) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const void *p = NULL; - size_t sz = 0; - - r = sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "GetConnectionSELinuxSecurityContext", - &error, - &reply, - "s", - unique ? unique : name); - if (r < 0) { - if (!sd_bus_error_has_name(&error, "org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown")) - return r; - } else { - r = sd_bus_message_read_array(reply, 'y', &p, &sz); - if (r < 0) - return r; - - c->label = strndup(p, sz); - if (!c->label) - return -ENOMEM; - - c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; - } - } - - r = bus_creds_add_more(c, mask, pid, 0); - if (r < 0) - return r; - } - - if (creds) { - *creds = c; - c = NULL; - } - - return 0; -} - -_public_ int sd_bus_get_name_creds( - sd_bus *bus, - const char *name, - uint64_t mask, - sd_bus_creds **creds) { - - assert_return(bus, -EINVAL); - assert_return(name, -EINVAL); - assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); - assert_return(mask == 0 || creds, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(service_name_is_valid(name), -EINVAL); - - if (!bus->bus_client) - return -EINVAL; - - if (streq(name, "org.freedesktop.DBus.Local")) - return -EINVAL; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->is_kernel) - return bus_get_name_creds_kdbus(bus, name, mask, false, creds); - else - return bus_get_name_creds_dbus1(bus, name, mask, creds); -} - -static int bus_get_owner_creds_kdbus(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; - struct kdbus_cmd_info cmd = { - .size = sizeof(struct kdbus_cmd_info), - }; - struct kdbus_info *creator_info; - pid_t pid = 0; - int r; - - c = bus_creds_new(); - if (!c) - return -ENOMEM; - - /* If augmentation is on, and the bus doesn't didn't allow us - * to get the bits we want, then ask for the PID/TID so that we - * can read the rest from /proc. */ - if ((mask & SD_BUS_CREDS_AUGMENT) && - (mask & (SD_BUS_CREDS_PPID| - SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| - SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| - SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM|SD_BUS_CREDS_EXE|SD_BUS_CREDS_CMDLINE| - SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID| - SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS| - SD_BUS_CREDS_SELINUX_CONTEXT| - SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID))) - mask |= SD_BUS_CREDS_PID; - - cmd.attach_flags = attach_flags_to_kdbus(mask); - - r = ioctl(bus->input_fd, KDBUS_CMD_BUS_CREATOR_INFO, &cmd); - if (r < 0) - return -errno; - - creator_info = (struct kdbus_info *) ((uint8_t *) bus->kdbus_buffer + cmd.offset); - - r = bus_populate_creds_from_items(bus, creator_info, mask, c); - bus_kernel_cmd_free(bus, cmd.offset); - if (r < 0) - return r; - - r = bus_creds_add_more(c, mask, pid, 0); - if (r < 0) - return r; - - *ret = c; - c = NULL; - return 0; -} - -static int bus_get_owner_creds_dbus1(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *c = NULL; - pid_t pid = 0; - bool do_label; - int r; - - assert(bus); - - do_label = bus->label && (mask & SD_BUS_CREDS_SELINUX_CONTEXT); - - /* Avoid allocating anything if we have no chance of returning useful data */ - if (!bus->ucred_valid && !do_label) - return -ENODATA; - - c = bus_creds_new(); - if (!c) - return -ENOMEM; - - if (bus->ucred_valid) { - if (bus->ucred.pid > 0) { - pid = c->pid = bus->ucred.pid; - c->mask |= SD_BUS_CREDS_PID & mask; - } - - if (bus->ucred.uid != UID_INVALID) { - c->euid = bus->ucred.uid; - c->mask |= SD_BUS_CREDS_EUID & mask; - } - - if (bus->ucred.gid != GID_INVALID) { - c->egid = bus->ucred.gid; - c->mask |= SD_BUS_CREDS_EGID & mask; - } - } - - if (do_label) { - c->label = strdup(bus->label); - if (!c->label) - return -ENOMEM; - - c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; - } - - r = bus_creds_add_more(c, mask, pid, 0); - if (r < 0) - return r; - - *ret = c; - c = NULL; - return 0; -} - -_public_ int sd_bus_get_owner_creds(sd_bus *bus, uint64_t mask, sd_bus_creds **ret) { - assert_return(bus, -EINVAL); - assert_return((mask & ~SD_BUS_CREDS_AUGMENT) <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); - assert_return(ret, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->is_kernel) - return bus_get_owner_creds_kdbus(bus, mask, ret); - else - return bus_get_owner_creds_dbus1(bus, mask, ret); -} - -static int add_name_change_match(sd_bus *bus, - uint64_t cookie, - const char *name, - const char *old_owner, - const char *new_owner) { - - uint64_t name_id = KDBUS_MATCH_ID_ANY, old_owner_id = 0, new_owner_id = 0; - int is_name_id = -1, r; - struct kdbus_item *item; - - assert(bus); - - /* If we encounter a match that could match against - * NameOwnerChanged messages, then we need to create - * KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE} and - * KDBUS_ITEM_ID_{ADD,REMOVE} matches for it, possibly - * multiple if the match is underspecified. - * - * The NameOwnerChanged signals take three parameters with - * unique or well-known names, but only some forms actually - * exist: - * - * WELLKNOWN, "", UNIQUE → KDBUS_ITEM_NAME_ADD - * WELLKNOWN, UNIQUE, "" → KDBUS_ITEM_NAME_REMOVE - * WELLKNOWN, UNIQUE, UNIQUE → KDBUS_ITEM_NAME_CHANGE - * UNIQUE, "", UNIQUE → KDBUS_ITEM_ID_ADD - * UNIQUE, UNIQUE, "" → KDBUS_ITEM_ID_REMOVE - * - * For the latter two the two unique names must be identical. - * - * */ - - if (name) { - is_name_id = bus_kernel_parse_unique_name(name, &name_id); - if (is_name_id < 0) - return 0; - } - - if (!isempty(old_owner)) { - r = bus_kernel_parse_unique_name(old_owner, &old_owner_id); - if (r < 0) - return 0; - if (r == 0) - return 0; - if (is_name_id > 0 && old_owner_id != name_id) - return 0; - } else - old_owner_id = KDBUS_MATCH_ID_ANY; - - if (!isempty(new_owner)) { - r = bus_kernel_parse_unique_name(new_owner, &new_owner_id); - if (r < 0) - return r; - if (r == 0) - return 0; - if (is_name_id > 0 && new_owner_id != name_id) - return 0; - } else - new_owner_id = KDBUS_MATCH_ID_ANY; - - if (is_name_id <= 0) { - struct kdbus_cmd_match *m; - size_t sz, l; - - /* If the name argument is missing or is a well-known - * name, then add KDBUS_ITEM_NAME_{ADD,REMOVE,CHANGE} - * matches for it */ - - l = name ? strlen(name) + 1 : 0; - - sz = ALIGN8(offsetof(struct kdbus_cmd_match, items) + - offsetof(struct kdbus_item, name_change) + - offsetof(struct kdbus_notify_name_change, name) + - l); - - m = alloca0_align(sz, 8); - m->size = sz; - m->cookie = cookie; - - item = m->items; - item->size = - offsetof(struct kdbus_item, name_change) + - offsetof(struct kdbus_notify_name_change, name) + - l; - - item->name_change.old_id.id = old_owner_id; - item->name_change.new_id.id = new_owner_id; - - if (name) - memcpy(item->name_change.name, name, l); - - /* If the old name is unset or empty, then - * this can match against added names */ - if (isempty(old_owner)) { - item->type = KDBUS_ITEM_NAME_ADD; - - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); - if (r < 0) - return -errno; - } - - /* If the new name is unset or empty, then - * this can match against removed names */ - if (isempty(new_owner)) { - item->type = KDBUS_ITEM_NAME_REMOVE; - - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); - if (r < 0) - return -errno; - } - - /* The CHANGE match we need in either case, because - * what is reported as a name change by the kernel - * might just be an owner change between starter and - * normal clients. For userspace such a change should - * be considered a removal/addition, hence let's - * subscribe to this unconditionally. */ - item->type = KDBUS_ITEM_NAME_CHANGE; - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); - if (r < 0) - return -errno; - } - - if (is_name_id != 0) { - struct kdbus_cmd_match *m; - uint64_t sz; - - /* If the name argument is missing or is a unique - * name, then add KDBUS_ITEM_ID_{ADD,REMOVE} matches - * for it */ - - sz = ALIGN8(offsetof(struct kdbus_cmd_match, items) + - offsetof(struct kdbus_item, id_change) + - sizeof(struct kdbus_notify_id_change)); - - m = alloca0_align(sz, 8); - m->size = sz; - m->cookie = cookie; - - item = m->items; - item->size = - offsetof(struct kdbus_item, id_change) + - sizeof(struct kdbus_notify_id_change); - item->id_change.id = name_id; - - /* If the old name is unset or empty, then this can - * match against added ids */ - if (isempty(old_owner)) { - item->type = KDBUS_ITEM_ID_ADD; - if (!isempty(new_owner)) - item->id_change.id = new_owner_id; - - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); - if (r < 0) - return -errno; - } - - /* If thew new name is unset or empty, then this can - * match against removed ids */ - if (isempty(new_owner)) { - item->type = KDBUS_ITEM_ID_REMOVE; - if (!isempty(old_owner)) - item->id_change.id = old_owner_id; - - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); - if (r < 0) - return -errno; - } - } - - return 0; -} - -int bus_add_match_internal_kernel( - sd_bus *bus, - struct bus_match_component *components, - unsigned n_components, - uint64_t cookie) { - - struct kdbus_cmd_match *m; - struct kdbus_item *item; - uint64_t *bloom; - size_t sz; - const char *sender = NULL; - size_t sender_length = 0; - uint64_t src_id = KDBUS_MATCH_ID_ANY, dst_id = KDBUS_MATCH_ID_ANY; - bool using_bloom = false; - unsigned i; - bool matches_name_change = true; - const char *name_change_arg[3] = {}; - int r; - - assert(bus); - - /* Monitor streams don't support matches, make this a NOP */ - if (bus->hello_flags & KDBUS_HELLO_MONITOR) - return 0; - - bloom = alloca0(bus->bloom_size); - - sz = ALIGN8(offsetof(struct kdbus_cmd_match, items)); - - for (i = 0; i < n_components; i++) { - struct bus_match_component *c = &components[i]; - - switch (c->type) { - - case BUS_MATCH_SENDER: - if (!streq(c->value_str, "org.freedesktop.DBus")) - matches_name_change = false; - - r = bus_kernel_parse_unique_name(c->value_str, &src_id); - if (r < 0) - return r; - else if (r > 0) - sz += ALIGN8(offsetof(struct kdbus_item, id) + sizeof(uint64_t)); - else { - sender = c->value_str; - sender_length = strlen(sender); - sz += ALIGN8(offsetof(struct kdbus_item, str) + sender_length + 1); - } - - break; - - case BUS_MATCH_MESSAGE_TYPE: - if (c->value_u8 != SD_BUS_MESSAGE_SIGNAL) - matches_name_change = false; - - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "message-type", bus_message_type_to_string(c->value_u8)); - using_bloom = true; - break; - - case BUS_MATCH_INTERFACE: - if (!streq(c->value_str, "org.freedesktop.DBus")) - matches_name_change = false; - - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "interface", c->value_str); - using_bloom = true; - break; - - case BUS_MATCH_MEMBER: - if (!streq(c->value_str, "NameOwnerChanged")) - matches_name_change = false; - - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "member", c->value_str); - using_bloom = true; - break; - - case BUS_MATCH_PATH: - if (!streq(c->value_str, "/org/freedesktop/DBus")) - matches_name_change = false; - - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "path", c->value_str); - using_bloom = true; - break; - - case BUS_MATCH_PATH_NAMESPACE: - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, "path-slash-prefix", c->value_str); - using_bloom = true; - break; - - case BUS_MATCH_ARG...BUS_MATCH_ARG_LAST: { - char buf[sizeof("arg")-1 + 2 + 1]; - - if (c->type - BUS_MATCH_ARG < 3) - name_change_arg[c->type - BUS_MATCH_ARG] = c->value_str; - - xsprintf(buf, "arg%i", c->type - BUS_MATCH_ARG); - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, buf, c->value_str); - using_bloom = true; - break; - } - - case BUS_MATCH_ARG_HAS...BUS_MATCH_ARG_HAS_LAST: { - char buf[sizeof("arg")-1 + 2 + sizeof("-has")]; - - xsprintf(buf, "arg%i-has", c->type - BUS_MATCH_ARG_HAS); - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, buf, c->value_str); - using_bloom = true; - break; - } - - case BUS_MATCH_ARG_PATH...BUS_MATCH_ARG_PATH_LAST: - /* - * XXX: DBus spec defines arg[0..63]path= matching to be - * a two-way glob. That is, if either string is a prefix - * of the other, it matches. - * This is really hard to realize in bloom-filters, as - * we would have to create a bloom-match for each prefix - * of @c->value_str. This is excessive, hence we just - * ignore all those matches and accept everything from - * the kernel. People should really avoid those matches. - * If they're used in real-life some day, we will have - * to properly support multiple-matches here. - */ - break; - - case BUS_MATCH_ARG_NAMESPACE...BUS_MATCH_ARG_NAMESPACE_LAST: { - char buf[sizeof("arg")-1 + 2 + sizeof("-dot-prefix")]; - - xsprintf(buf, "arg%i-dot-prefix", c->type - BUS_MATCH_ARG_NAMESPACE); - bloom_add_pair(bloom, bus->bloom_size, bus->bloom_n_hash, buf, c->value_str); - using_bloom = true; - break; - } - - case BUS_MATCH_DESTINATION: - /* - * Kernel only supports matching on destination IDs, but - * not on destination names. So just skip the - * destination name restriction and verify it in - * user-space on retrieval. - */ - r = bus_kernel_parse_unique_name(c->value_str, &dst_id); - if (r < 0) - return r; - else if (r > 0) - sz += ALIGN8(offsetof(struct kdbus_item, id) + sizeof(uint64_t)); - - /* if not a broadcast, it cannot be a name-change */ - if (r <= 0 || dst_id != KDBUS_DST_ID_BROADCAST) - matches_name_change = false; - - break; - - case BUS_MATCH_ROOT: - case BUS_MATCH_VALUE: - case BUS_MATCH_LEAF: - case _BUS_MATCH_NODE_TYPE_MAX: - case _BUS_MATCH_NODE_TYPE_INVALID: - assert_not_reached("Invalid match type?"); - } - } - - if (using_bloom) - sz += ALIGN8(offsetof(struct kdbus_item, data64) + bus->bloom_size); - - m = alloca0_align(sz, 8); - m->size = sz; - m->cookie = cookie; - - item = m->items; - - if (src_id != KDBUS_MATCH_ID_ANY) { - item->size = offsetof(struct kdbus_item, id) + sizeof(uint64_t); - item->type = KDBUS_ITEM_ID; - item->id = src_id; - item = KDBUS_ITEM_NEXT(item); - } - - if (dst_id != KDBUS_MATCH_ID_ANY) { - item->size = offsetof(struct kdbus_item, id) + sizeof(uint64_t); - item->type = KDBUS_ITEM_DST_ID; - item->id = dst_id; - item = KDBUS_ITEM_NEXT(item); - } - - if (using_bloom) { - item->size = offsetof(struct kdbus_item, data64) + bus->bloom_size; - item->type = KDBUS_ITEM_BLOOM_MASK; - memcpy(item->data64, bloom, bus->bloom_size); - item = KDBUS_ITEM_NEXT(item); - } - - if (sender) { - item->size = offsetof(struct kdbus_item, str) + sender_length + 1; - item->type = KDBUS_ITEM_NAME; - memcpy(item->str, sender, sender_length + 1); - } - - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_ADD, m); - if (r < 0) - return -errno; - - if (matches_name_change) { - - /* If this match could theoretically match - * NameOwnerChanged messages, we need to - * install a second non-bloom filter explitly - * for it */ - - r = add_name_change_match(bus, cookie, name_change_arg[0], name_change_arg[1], name_change_arg[2]); - if (r < 0) - return r; - } - - return 0; -} - -#define internal_match(bus, m) \ - ((bus)->hello_flags & KDBUS_HELLO_MONITOR \ - ? (isempty(m) ? "eavesdrop='true'" : strjoina((m), ",eavesdrop='true'")) \ - : (m)) - -static int bus_add_match_internal_dbus1( - sd_bus *bus, - const char *match) { - - const char *e; - - assert(bus); - assert(match); - - e = internal_match(bus, match); - - return sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "AddMatch", - NULL, - NULL, - "s", - e); -} - -int bus_add_match_internal( - sd_bus *bus, - const char *match, - struct bus_match_component *components, - unsigned n_components, - uint64_t cookie) { - - assert(bus); - - if (!bus->bus_client) - return -EINVAL; - - if (bus->is_kernel) - return bus_add_match_internal_kernel(bus, components, n_components, cookie); - else - return bus_add_match_internal_dbus1(bus, match); -} - -int bus_remove_match_internal_kernel( - sd_bus *bus, - uint64_t cookie) { - - struct kdbus_cmd_match m = { - .size = offsetof(struct kdbus_cmd_match, items), - .cookie = cookie, - }; - int r; - - assert(bus); - - /* Monitor streams don't support matches, make this a NOP */ - if (bus->hello_flags & KDBUS_HELLO_MONITOR) - return 0; - - r = ioctl(bus->input_fd, KDBUS_CMD_MATCH_REMOVE, &m); - if (r < 0) - return -errno; - - return 0; -} - -static int bus_remove_match_internal_dbus1( - sd_bus *bus, - const char *match) { - - const char *e; - - assert(bus); - assert(match); - - e = internal_match(bus, match); - - return sd_bus_call_method( - bus, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "RemoveMatch", - NULL, - NULL, - "s", - e); -} - -int bus_remove_match_internal( - sd_bus *bus, - const char *match, - uint64_t cookie) { - - assert(bus); - - if (!bus->bus_client) - return -EINVAL; - - if (bus->is_kernel) - return bus_remove_match_internal_kernel(bus, cookie); - else - return bus_remove_match_internal_dbus1(bus, match); -} - -_public_ int sd_bus_get_name_machine_id(sd_bus *bus, const char *name, sd_id128_t *machine) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; - const char *mid; - int r; - - assert_return(bus, -EINVAL); - assert_return(name, -EINVAL); - assert_return(machine, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(service_name_is_valid(name), -EINVAL); - - if (!bus->bus_client) - return -EINVAL; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (streq_ptr(name, bus->unique_name)) - return sd_id128_get_machine(machine); - - r = sd_bus_message_new_method_call( - bus, - &m, - name, - "/", - "org.freedesktop.DBus.Peer", - "GetMachineId"); - if (r < 0) - return r; - - r = sd_bus_message_set_auto_start(m, false); - if (r < 0) - return r; - - r = sd_bus_call(bus, m, 0, NULL, &reply); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "s", &mid); - if (r < 0) - return r; - - return sd_id128_from_string(mid, machine); -} diff --git a/src/libsystemd/sd-bus/bus-control.h b/src/libsystemd/sd-bus/bus-control.h deleted file mode 100644 index c181aa7959..0000000000 --- a/src/libsystemd/sd-bus/bus-control.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "sd-bus.h" - -#include "bus-match.h" - -int bus_add_match_internal(sd_bus *bus, const char *match, struct bus_match_component *components, unsigned n_components, uint64_t cookie); -int bus_remove_match_internal(sd_bus *bus, const char *match, uint64_t cookie); - -int bus_add_match_internal_kernel(sd_bus *bus, struct bus_match_component *components, unsigned n_components, uint64_t cookie); -int bus_remove_match_internal_kernel(sd_bus *bus, uint64_t cookie); - -int bus_get_name_creds_kdbus(sd_bus *bus, const char *name, uint64_t mask, bool allow_activator, sd_bus_creds **creds); diff --git a/src/libsystemd/sd-bus/bus-convenience.c b/src/libsystemd/sd-bus/bus-convenience.c deleted file mode 100644 index 2d06bf541f..0000000000 --- a/src/libsystemd/sd-bus/bus-convenience.c +++ /dev/null @@ -1,626 +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 . -***/ - -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-signature.h" -#include "bus-type.h" -#include "bus-util.h" -#include "string-util.h" - -_public_ int sd_bus_emit_signal( - sd_bus *bus, - const char *path, - const char *interface, - const char *member, - const char *types, ...) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - r = sd_bus_message_new_signal(bus, &m, path, interface, member); - if (r < 0) - return r; - - if (!isempty(types)) { - va_list ap; - - va_start(ap, types); - r = bus_message_append_ap(m, types, ap); - va_end(ap); - if (r < 0) - return r; - } - - return sd_bus_send(bus, m, NULL); -} - -_public_ int sd_bus_call_method_async( - sd_bus *bus, - sd_bus_slot **slot, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_message_handler_t callback, - void *userdata, - const char *types, ...) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - r = sd_bus_message_new_method_call(bus, &m, destination, path, interface, member); - if (r < 0) - return r; - - if (!isempty(types)) { - va_list ap; - - va_start(ap, types); - r = bus_message_append_ap(m, types, ap); - va_end(ap); - if (r < 0) - return r; - } - - return sd_bus_call_async(bus, slot, m, callback, userdata, 0); -} - -_public_ int sd_bus_call_method( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *error, - sd_bus_message **reply, - const char *types, ...) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - bus_assert_return(bus, -EINVAL, error); - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = sd_bus_message_new_method_call(bus, &m, destination, path, interface, member); - if (r < 0) - goto fail; - - if (!isempty(types)) { - va_list ap; - - va_start(ap, types); - r = bus_message_append_ap(m, types, ap); - va_end(ap); - if (r < 0) - goto fail; - } - - return sd_bus_call(bus, m, 0, error, reply); - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_reply_method_return( - sd_bus_message *call, - const char *types, ...) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - return 0; - - r = sd_bus_message_new_method_return(call, &m); - if (r < 0) - return r; - - if (!isempty(types)) { - va_list ap; - - va_start(ap, types); - r = bus_message_append_ap(m, types, ap); - va_end(ap); - if (r < 0) - return r; - } - - return sd_bus_send(call->bus, m, NULL); -} - -_public_ int sd_bus_reply_method_error( - sd_bus_message *call, - const sd_bus_error *e) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(sd_bus_error_is_set(e), -EINVAL); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - return 0; - - r = sd_bus_message_new_method_error(call, &m, e); - if (r < 0) - return r; - - return sd_bus_send(call->bus, m, NULL); -} - -_public_ int sd_bus_reply_method_errorf( - sd_bus_message *call, - const char *name, - const char *format, - ...) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - va_list ap; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - return 0; - - va_start(ap, format); - bus_error_setfv(&error, name, format, ap); - va_end(ap); - - return sd_bus_reply_method_error(call, &error); -} - -_public_ int sd_bus_reply_method_errno( - sd_bus_message *call, - int error, - const sd_bus_error *p) { - - _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - return 0; - - if (sd_bus_error_is_set(p)) - return sd_bus_reply_method_error(call, p); - - sd_bus_error_set_errno(&berror, error); - - return sd_bus_reply_method_error(call, &berror); -} - -_public_ int sd_bus_reply_method_errnof( - sd_bus_message *call, - int error, - const char *format, - ...) { - - _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; - va_list ap; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - if (call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - return 0; - - va_start(ap, format); - sd_bus_error_set_errnofv(&berror, error, format, ap); - va_end(ap); - - return sd_bus_reply_method_error(call, &berror); -} - -_public_ int sd_bus_get_property( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *error, - sd_bus_message **reply, - const char *type) { - - sd_bus_message *rep = NULL; - int r; - - bus_assert_return(bus, -EINVAL, error); - bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); - bus_assert_return(member_name_is_valid(member), -EINVAL, error); - bus_assert_return(reply, -EINVAL, error); - bus_assert_return(signature_is_single(type, false), -EINVAL, error); - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &rep, "ss", strempty(interface), member); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(rep, 'v', type); - if (r < 0) { - sd_bus_message_unref(rep); - goto fail; - } - - *reply = rep; - return 0; - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_get_property_trivial( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *error, - char type, void *ptr) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - bus_assert_return(bus, -EINVAL, error); - bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); - bus_assert_return(member_name_is_valid(member), -EINVAL, error); - bus_assert_return(bus_type_is_trivial(type), -EINVAL, error); - bus_assert_return(ptr, -EINVAL, error); - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, 'v', CHAR_TO_STR(type)); - if (r < 0) - goto fail; - - r = sd_bus_message_read_basic(reply, type, ptr); - if (r < 0) - goto fail; - - return 0; - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_get_property_string( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *error, - char **ret) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *s; - char *n; - int r; - - bus_assert_return(bus, -EINVAL, error); - bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); - bus_assert_return(member_name_is_valid(member), -EINVAL, error); - bus_assert_return(ret, -EINVAL, error); - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, 'v', "s"); - if (r < 0) - goto fail; - - r = sd_bus_message_read_basic(reply, 's', &s); - if (r < 0) - goto fail; - - n = strdup(s); - if (!n) { - r = -ENOMEM; - goto fail; - } - - *ret = n; - return 0; - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_get_property_strv( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *error, - char ***ret) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - bus_assert_return(bus, -EINVAL, error); - bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); - bus_assert_return(member_name_is_valid(member), -EINVAL, error); - bus_assert_return(ret, -EINVAL, error); - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = sd_bus_call_method(bus, destination, path, "org.freedesktop.DBus.Properties", "Get", error, &reply, "ss", strempty(interface), member); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, 'v', NULL); - if (r < 0) - goto fail; - - r = sd_bus_message_read_strv(reply, ret); - if (r < 0) - goto fail; - - return 0; - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_set_property( - sd_bus *bus, - const char *destination, - const char *path, - const char *interface, - const char *member, - sd_bus_error *error, - const char *type, ...) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - va_list ap; - int r; - - bus_assert_return(bus, -EINVAL, error); - bus_assert_return(isempty(interface) || interface_name_is_valid(interface), -EINVAL, error); - bus_assert_return(member_name_is_valid(member), -EINVAL, error); - bus_assert_return(signature_is_single(type, false), -EINVAL, error); - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = sd_bus_message_new_method_call(bus, &m, destination, path, "org.freedesktop.DBus.Properties", "Set"); - if (r < 0) - goto fail; - - r = sd_bus_message_append(m, "ss", strempty(interface), member); - if (r < 0) - goto fail; - - r = sd_bus_message_open_container(m, 'v', type); - if (r < 0) - goto fail; - - va_start(ap, type); - r = bus_message_append_ap(m, type, ap); - va_end(ap); - if (r < 0) - goto fail; - - r = sd_bus_message_close_container(m); - if (r < 0) - goto fail; - - return sd_bus_call(bus, m, 0, error, NULL); - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_bus_creds **creds) { - sd_bus_creds *c; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - c = sd_bus_message_get_creds(call); - - /* All data we need? */ - if (c && (mask & ~c->mask) == 0) { - *creds = sd_bus_creds_ref(c); - return 0; - } - - /* No data passed? Or not enough data passed to retrieve the missing bits? */ - if (!c || !(c->mask & SD_BUS_CREDS_PID)) { - /* We couldn't read anything from the call, let's try - * to get it from the sender or peer. */ - - if (call->sender) - /* There's a sender, but the creds are - * missing. This means we are talking via - * dbus1, or are getting a message that was - * sent to us via kdbus, but was converted - * from a dbus1 message by the bus-proxy and - * thus also lacks the creds. */ - return sd_bus_get_name_creds(call->bus, call->sender, mask, creds); - else - /* There's no sender, hence we are on a dbus1 - * direct connection. For direct connections - * the credentials of the AF_UNIX peer matter, - * which may be queried via - * sd_bus_get_owner_creds(). */ - return sd_bus_get_owner_creds(call->bus, mask, creds); - } - - return bus_creds_extend_by_pid(c, mask, creds); -} - -_public_ int sd_bus_query_sender_privilege(sd_bus_message *call, int capability) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - uid_t our_uid; - bool know_caps = false; - int r; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->bus, -EINVAL); - assert_return(!bus_pid_changed(call->bus), -ECHILD); - - if (!BUS_IS_OPEN(call->bus->state)) - return -ENOTCONN; - - if (capability >= 0) { - - r = sd_bus_query_sender_creds(call, SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_EFFECTIVE_CAPS, &creds); - if (r < 0) - return r; - - /* We cannot use augmented caps for authorization, - * since then data is acquired raceful from - * /proc. This can never actually happen, but let's - * better be safe than sorry, and do an extra check - * here. */ - assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EFFECTIVE_CAPS) == 0, -EPERM); - - /* Note that not even on kdbus we might have the caps - * field, due to faked identities, or namespace - * translation issues. */ - r = sd_bus_creds_has_effective_cap(creds, capability); - if (r > 0) - return 1; - if (r == 0) - know_caps = true; - } else { - r = sd_bus_query_sender_creds(call, SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID, &creds); - if (r < 0) - return r; - } - - /* Now, check the UID, but only if the capability check wasn't - * sufficient */ - our_uid = getuid(); - if (our_uid != 0 || !know_caps || capability < 0) { - uid_t sender_uid; - - /* We cannot use augmented uid/euid for authorization, - * since then data is acquired raceful from - * /proc. This can never actually happen, but let's - * better be safe than sorry, and do an extra check - * here. */ - assert_return((sd_bus_creds_get_augmented_mask(creds) & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID)) == 0, -EPERM); - - /* Try to use the EUID, if we have it. */ - r = sd_bus_creds_get_euid(creds, &sender_uid); - if (r < 0) - r = sd_bus_creds_get_uid(creds, &sender_uid); - - if (r >= 0) { - /* Sender has same UID as us, then let's grant access */ - if (sender_uid == our_uid) - return 1; - - /* Sender is root, we are not root. */ - if (our_uid != 0 && sender_uid == 0) - return 1; - } - } - - return 0; -} diff --git a/src/libsystemd/sd-bus/bus-creds.c b/src/libsystemd/sd-bus/bus-creds.c deleted file mode 100644 index c4f693dee9..0000000000 --- a/src/libsystemd/sd-bus/bus-creds.c +++ /dev/null @@ -1,1349 +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 . -***/ - -#include -#include - -#include "alloc-util.h" -#include "audit-util.h" -#include "bus-creds.h" -#include "bus-label.h" -#include "bus-message.h" -#include "bus-util.h" -#include "capability-util.h" -#include "cgroup-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "hexdecoct.h" -#include "parse-util.h" -#include "process-util.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "user-util.h" -#include "util.h" - -enum { - CAP_OFFSET_INHERITABLE = 0, - CAP_OFFSET_PERMITTED = 1, - CAP_OFFSET_EFFECTIVE = 2, - CAP_OFFSET_BOUNDING = 3 -}; - -void bus_creds_done(sd_bus_creds *c) { - assert(c); - - /* For internal bus cred structures that are allocated by - * something else */ - - free(c->session); - free(c->unit); - free(c->user_unit); - free(c->slice); - free(c->user_slice); - free(c->unescaped_description); - free(c->supplementary_gids); - free(c->tty); - - free(c->well_known_names); /* note that this is an strv, but - * we only free the array, not the - * strings the array points to. The - * full strv we only free if - * c->allocated is set, see - * below. */ - - strv_free(c->cmdline_array); -} - -_public_ sd_bus_creds *sd_bus_creds_ref(sd_bus_creds *c) { - - if (!c) - return NULL; - - if (c->allocated) { - assert(c->n_ref > 0); - c->n_ref++; - } else { - sd_bus_message *m; - - /* If this is an embedded creds structure, then - * forward ref counting to the message */ - m = container_of(c, sd_bus_message, creds); - sd_bus_message_ref(m); - } - - return c; -} - -_public_ sd_bus_creds *sd_bus_creds_unref(sd_bus_creds *c) { - - if (!c) - return NULL; - - if (c->allocated) { - assert(c->n_ref > 0); - c->n_ref--; - - if (c->n_ref == 0) { - free(c->comm); - free(c->tid_comm); - free(c->exe); - free(c->cmdline); - free(c->cgroup); - free(c->capability); - free(c->label); - free(c->unique_name); - free(c->cgroup_root); - free(c->description); - - c->supplementary_gids = mfree(c->supplementary_gids); - - c->well_known_names = strv_free(c->well_known_names); - - bus_creds_done(c); - - free(c); - } - } else { - sd_bus_message *m; - - m = container_of(c, sd_bus_message, creds); - sd_bus_message_unref(m); - } - - - return NULL; -} - -_public_ uint64_t sd_bus_creds_get_mask(const sd_bus_creds *c) { - assert_return(c, 0); - - return c->mask; -} - -_public_ uint64_t sd_bus_creds_get_augmented_mask(const sd_bus_creds *c) { - assert_return(c, 0); - - return c->augmented; -} - -sd_bus_creds* bus_creds_new(void) { - sd_bus_creds *c; - - c = new0(sd_bus_creds, 1); - if (!c) - return NULL; - - c->allocated = true; - c->n_ref = 1; - return c; -} - -_public_ int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t mask) { - sd_bus_creds *c; - int r; - - assert_return(pid >= 0, -EINVAL); - assert_return(mask <= _SD_BUS_CREDS_ALL, -EOPNOTSUPP); - assert_return(ret, -EINVAL); - - if (pid == 0) - pid = getpid(); - - c = bus_creds_new(); - if (!c) - return -ENOMEM; - - r = bus_creds_add_more(c, mask | SD_BUS_CREDS_AUGMENT, pid, 0); - if (r < 0) { - sd_bus_creds_unref(c); - return r; - } - - /* Check if the process existed at all, in case we haven't - * figured that out already */ - if (!pid_is_alive(pid)) { - sd_bus_creds_unref(c); - return -ESRCH; - } - - *ret = c; - return 0; -} - -_public_ int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid) { - assert_return(c, -EINVAL); - assert_return(uid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_UID)) - return -ENODATA; - - *uid = c->uid; - return 0; -} - -_public_ int sd_bus_creds_get_euid(sd_bus_creds *c, uid_t *euid) { - assert_return(c, -EINVAL); - assert_return(euid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_EUID)) - return -ENODATA; - - *euid = c->euid; - return 0; -} - -_public_ int sd_bus_creds_get_suid(sd_bus_creds *c, uid_t *suid) { - assert_return(c, -EINVAL); - assert_return(suid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_SUID)) - return -ENODATA; - - *suid = c->suid; - return 0; -} - - -_public_ int sd_bus_creds_get_fsuid(sd_bus_creds *c, uid_t *fsuid) { - assert_return(c, -EINVAL); - assert_return(fsuid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_FSUID)) - return -ENODATA; - - *fsuid = c->fsuid; - return 0; -} - -_public_ int sd_bus_creds_get_gid(sd_bus_creds *c, gid_t *gid) { - assert_return(c, -EINVAL); - assert_return(gid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_GID)) - return -ENODATA; - - *gid = c->gid; - return 0; -} - -_public_ int sd_bus_creds_get_egid(sd_bus_creds *c, gid_t *egid) { - assert_return(c, -EINVAL); - assert_return(egid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_EGID)) - return -ENODATA; - - *egid = c->egid; - return 0; -} - -_public_ int sd_bus_creds_get_sgid(sd_bus_creds *c, gid_t *sgid) { - assert_return(c, -EINVAL); - assert_return(sgid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_SGID)) - return -ENODATA; - - *sgid = c->sgid; - return 0; -} - -_public_ int sd_bus_creds_get_fsgid(sd_bus_creds *c, gid_t *fsgid) { - assert_return(c, -EINVAL); - assert_return(fsgid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_FSGID)) - return -ENODATA; - - *fsgid = c->fsgid; - return 0; -} - -_public_ int sd_bus_creds_get_supplementary_gids(sd_bus_creds *c, const gid_t **gids) { - assert_return(c, -EINVAL); - assert_return(gids, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS)) - return -ENODATA; - - *gids = c->supplementary_gids; - return (int) c->n_supplementary_gids; -} - -_public_ int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid) { - assert_return(c, -EINVAL); - assert_return(pid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_PID)) - return -ENODATA; - - assert(c->pid > 0); - *pid = c->pid; - return 0; -} - -_public_ int sd_bus_creds_get_ppid(sd_bus_creds *c, pid_t *ppid) { - assert_return(c, -EINVAL); - assert_return(ppid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_PPID)) - return -ENODATA; - - /* PID 1 has no parent process. Let's distinguish the case of - * not knowing and not having a parent process by the returned - * error code. */ - if (c->ppid == 0) - return -ENXIO; - - *ppid = c->ppid; - return 0; -} - -_public_ int sd_bus_creds_get_tid(sd_bus_creds *c, pid_t *tid) { - assert_return(c, -EINVAL); - assert_return(tid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_TID)) - return -ENODATA; - - assert(c->tid > 0); - *tid = c->tid; - return 0; -} - -_public_ int sd_bus_creds_get_selinux_context(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_SELINUX_CONTEXT)) - return -ENODATA; - - assert(c->label); - *ret = c->label; - return 0; -} - -_public_ int sd_bus_creds_get_comm(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_COMM)) - return -ENODATA; - - assert(c->comm); - *ret = c->comm; - return 0; -} - -_public_ int sd_bus_creds_get_tid_comm(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_TID_COMM)) - return -ENODATA; - - assert(c->tid_comm); - *ret = c->tid_comm; - return 0; -} - -_public_ int sd_bus_creds_get_exe(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_EXE)) - return -ENODATA; - - if (!c->exe) - return -ENXIO; - - *ret = c->exe; - return 0; -} - -_public_ int sd_bus_creds_get_cgroup(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_CGROUP)) - return -ENODATA; - - assert(c->cgroup); - *ret = c->cgroup; - return 0; -} - -_public_ int sd_bus_creds_get_unit(sd_bus_creds *c, const char **ret) { - int r; - - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_UNIT)) - return -ENODATA; - - assert(c->cgroup); - - if (!c->unit) { - const char *shifted; - - r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); - if (r < 0) - return r; - - r = cg_path_get_unit(shifted, (char**) &c->unit); - if (r < 0) - return r; - } - - *ret = c->unit; - return 0; -} - -_public_ int sd_bus_creds_get_user_unit(sd_bus_creds *c, const char **ret) { - int r; - - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_USER_UNIT)) - return -ENODATA; - - assert(c->cgroup); - - if (!c->user_unit) { - const char *shifted; - - r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); - if (r < 0) - return r; - - r = cg_path_get_user_unit(shifted, (char**) &c->user_unit); - if (r < 0) - return r; - } - - *ret = c->user_unit; - return 0; -} - -_public_ int sd_bus_creds_get_slice(sd_bus_creds *c, const char **ret) { - int r; - - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_SLICE)) - return -ENODATA; - - assert(c->cgroup); - - if (!c->slice) { - const char *shifted; - - r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); - if (r < 0) - return r; - - r = cg_path_get_slice(shifted, (char**) &c->slice); - if (r < 0) - return r; - } - - *ret = c->slice; - return 0; -} - -_public_ int sd_bus_creds_get_user_slice(sd_bus_creds *c, const char **ret) { - int r; - - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_USER_SLICE)) - return -ENODATA; - - assert(c->cgroup); - - if (!c->user_slice) { - const char *shifted; - - r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); - if (r < 0) - return r; - - r = cg_path_get_user_slice(shifted, (char**) &c->user_slice); - if (r < 0) - return r; - } - - *ret = c->user_slice; - return 0; -} - -_public_ int sd_bus_creds_get_session(sd_bus_creds *c, const char **ret) { - int r; - - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_SESSION)) - return -ENODATA; - - assert(c->cgroup); - - if (!c->session) { - const char *shifted; - - r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); - if (r < 0) - return r; - - r = cg_path_get_session(shifted, (char**) &c->session); - if (r < 0) - return r; - } - - *ret = c->session; - return 0; -} - -_public_ int sd_bus_creds_get_owner_uid(sd_bus_creds *c, uid_t *uid) { - const char *shifted; - int r; - - assert_return(c, -EINVAL); - assert_return(uid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_OWNER_UID)) - return -ENODATA; - - assert(c->cgroup); - - r = cg_shift_path(c->cgroup, c->cgroup_root, &shifted); - if (r < 0) - return r; - - return cg_path_get_owner_uid(shifted, uid); -} - -_public_ int sd_bus_creds_get_cmdline(sd_bus_creds *c, char ***cmdline) { - assert_return(c, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_CMDLINE)) - return -ENODATA; - - if (!c->cmdline) - return -ENXIO; - - if (!c->cmdline_array) { - c->cmdline_array = strv_parse_nulstr(c->cmdline, c->cmdline_size); - if (!c->cmdline_array) - return -ENOMEM; - } - - *cmdline = c->cmdline_array; - return 0; -} - -_public_ int sd_bus_creds_get_audit_session_id(sd_bus_creds *c, uint32_t *sessionid) { - assert_return(c, -EINVAL); - assert_return(sessionid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_AUDIT_SESSION_ID)) - return -ENODATA; - - if (c->audit_session_id == AUDIT_SESSION_INVALID) - return -ENXIO; - - *sessionid = c->audit_session_id; - return 0; -} - -_public_ int sd_bus_creds_get_audit_login_uid(sd_bus_creds *c, uid_t *uid) { - assert_return(c, -EINVAL); - assert_return(uid, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_AUDIT_LOGIN_UID)) - return -ENODATA; - - if (c->audit_login_uid == UID_INVALID) - return -ENXIO; - - *uid = c->audit_login_uid; - return 0; -} - -_public_ int sd_bus_creds_get_tty(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_TTY)) - return -ENODATA; - - if (!c->tty) - return -ENXIO; - - *ret = c->tty; - return 0; -} - -_public_ int sd_bus_creds_get_unique_name(sd_bus_creds *c, const char **unique_name) { - assert_return(c, -EINVAL); - assert_return(unique_name, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_UNIQUE_NAME)) - return -ENODATA; - - *unique_name = c->unique_name; - return 0; -} - -_public_ int sd_bus_creds_get_well_known_names(sd_bus_creds *c, char ***well_known_names) { - assert_return(c, -EINVAL); - assert_return(well_known_names, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_WELL_KNOWN_NAMES)) - return -ENODATA; - - /* As a special hack we return the bus driver as well-known - * names list when this is requested. */ - if (c->well_known_names_driver) { - static const char* const wkn[] = { - "org.freedesktop.DBus", - NULL - }; - - *well_known_names = (char**) wkn; - return 0; - } - - if (c->well_known_names_local) { - static const char* const wkn[] = { - "org.freedesktop.DBus.Local", - NULL - }; - - *well_known_names = (char**) wkn; - return 0; - } - - *well_known_names = c->well_known_names; - return 0; -} - -_public_ int sd_bus_creds_get_description(sd_bus_creds *c, const char **ret) { - assert_return(c, -EINVAL); - assert_return(ret, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_DESCRIPTION)) - return -ENODATA; - - assert(c->description); - - if (!c->unescaped_description) { - c->unescaped_description = bus_label_unescape(c->description); - if (!c->unescaped_description) - return -ENOMEM; - } - - *ret = c->unescaped_description; - return 0; -} - -static int has_cap(sd_bus_creds *c, unsigned offset, int capability) { - size_t sz; - - assert(c); - assert(capability >= 0); - assert(c->capability); - - if ((unsigned) capability > cap_last_cap()) - return 0; - - sz = DIV_ROUND_UP(cap_last_cap(), 32U); - - return !!(c->capability[offset * sz + CAP_TO_INDEX(capability)] & CAP_TO_MASK(capability)); -} - -_public_ int sd_bus_creds_has_effective_cap(sd_bus_creds *c, int capability) { - assert_return(c, -EINVAL); - assert_return(capability >= 0, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_EFFECTIVE_CAPS)) - return -ENODATA; - - return has_cap(c, CAP_OFFSET_EFFECTIVE, capability); -} - -_public_ int sd_bus_creds_has_permitted_cap(sd_bus_creds *c, int capability) { - assert_return(c, -EINVAL); - assert_return(capability >= 0, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_PERMITTED_CAPS)) - return -ENODATA; - - return has_cap(c, CAP_OFFSET_PERMITTED, capability); -} - -_public_ int sd_bus_creds_has_inheritable_cap(sd_bus_creds *c, int capability) { - assert_return(c, -EINVAL); - assert_return(capability >= 0, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_INHERITABLE_CAPS)) - return -ENODATA; - - return has_cap(c, CAP_OFFSET_INHERITABLE, capability); -} - -_public_ int sd_bus_creds_has_bounding_cap(sd_bus_creds *c, int capability) { - assert_return(c, -EINVAL); - assert_return(capability >= 0, -EINVAL); - - if (!(c->mask & SD_BUS_CREDS_BOUNDING_CAPS)) - return -ENODATA; - - return has_cap(c, CAP_OFFSET_BOUNDING, capability); -} - -static int parse_caps(sd_bus_creds *c, unsigned offset, const char *p) { - size_t sz, max; - unsigned i, j; - - assert(c); - assert(p); - - max = DIV_ROUND_UP(cap_last_cap(), 32U); - p += strspn(p, WHITESPACE); - - sz = strlen(p); - if (sz % 8 != 0) - return -EINVAL; - - sz /= 8; - if (sz > max) - return -EINVAL; - - if (!c->capability) { - c->capability = new0(uint32_t, max * 4); - if (!c->capability) - return -ENOMEM; - } - - for (i = 0; i < sz; i ++) { - uint32_t v = 0; - - for (j = 0; j < 8; ++j) { - int t; - - t = unhexchar(*p++); - if (t < 0) - return -EINVAL; - - v = (v << 4) | t; - } - - c->capability[offset * max + (sz - i - 1)] = v; - } - - return 0; -} - -int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid) { - uint64_t missing; - int r; - - assert(c); - assert(c->allocated); - - if (!(mask & SD_BUS_CREDS_AUGMENT)) - return 0; - - /* Try to retrieve PID from creds if it wasn't passed to us */ - if (pid > 0) { - c->pid = pid; - c->mask |= SD_BUS_CREDS_PID; - } else if (c->mask & SD_BUS_CREDS_PID) - pid = c->pid; - else - /* Without pid we cannot do much... */ - return 0; - - /* Try to retrieve TID from creds if it wasn't passed to us */ - if (tid <= 0 && (c->mask & SD_BUS_CREDS_TID)) - tid = c->tid; - - /* Calculate what we shall and can add */ - missing = mask & ~(c->mask|SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_DESCRIPTION|SD_BUS_CREDS_AUGMENT); - if (missing == 0) - return 0; - - if (tid > 0) { - c->tid = tid; - c->mask |= SD_BUS_CREDS_TID; - } - - if (missing & (SD_BUS_CREDS_PPID | - SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_SUID | SD_BUS_CREDS_FSUID | - SD_BUS_CREDS_GID | SD_BUS_CREDS_EGID | SD_BUS_CREDS_SGID | SD_BUS_CREDS_FSGID | - SD_BUS_CREDS_SUPPLEMENTARY_GIDS | - SD_BUS_CREDS_EFFECTIVE_CAPS | SD_BUS_CREDS_INHERITABLE_CAPS | - SD_BUS_CREDS_PERMITTED_CAPS | SD_BUS_CREDS_BOUNDING_CAPS)) { - - _cleanup_fclose_ FILE *f = NULL; - const char *p; - - p = procfs_file_alloca(pid, "status"); - - f = fopen(p, "re"); - if (!f) { - if (errno == ENOENT) - return -ESRCH; - else if (errno != EPERM && errno != EACCES) - return -errno; - } else { - char line[LINE_MAX]; - - FOREACH_LINE(line, f, return -errno) { - truncate_nl(line); - - if (missing & SD_BUS_CREDS_PPID) { - p = startswith(line, "PPid:"); - if (p) { - p += strspn(p, WHITESPACE); - - /* Explicitly check for PPID 0 (which is the case for PID 1) */ - if (!streq(p, "0")) { - r = parse_pid(p, &c->ppid); - if (r < 0) - return r; - - } else - c->ppid = 0; - - c->mask |= SD_BUS_CREDS_PPID; - continue; - } - } - - if (missing & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID)) { - p = startswith(line, "Uid:"); - if (p) { - unsigned long uid, euid, suid, fsuid; - - p += strspn(p, WHITESPACE); - if (sscanf(p, "%lu %lu %lu %lu", &uid, &euid, &suid, &fsuid) != 4) - return -EIO; - - if (missing & SD_BUS_CREDS_UID) - c->uid = (uid_t) uid; - if (missing & SD_BUS_CREDS_EUID) - c->euid = (uid_t) euid; - if (missing & SD_BUS_CREDS_SUID) - c->suid = (uid_t) suid; - if (missing & SD_BUS_CREDS_FSUID) - c->fsuid = (uid_t) fsuid; - - c->mask |= missing & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID); - continue; - } - } - - if (missing & (SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID)) { - p = startswith(line, "Gid:"); - if (p) { - unsigned long gid, egid, sgid, fsgid; - - p += strspn(p, WHITESPACE); - if (sscanf(p, "%lu %lu %lu %lu", &gid, &egid, &sgid, &fsgid) != 4) - return -EIO; - - if (missing & SD_BUS_CREDS_GID) - c->gid = (gid_t) gid; - if (missing & SD_BUS_CREDS_EGID) - c->egid = (gid_t) egid; - if (missing & SD_BUS_CREDS_SGID) - c->sgid = (gid_t) sgid; - if (missing & SD_BUS_CREDS_FSGID) - c->fsgid = (gid_t) fsgid; - - c->mask |= missing & (SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID); - continue; - } - } - - if (missing & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { - p = startswith(line, "Groups:"); - if (p) { - size_t allocated = 0; - - for (;;) { - unsigned long g; - int n = 0; - - p += strspn(p, WHITESPACE); - if (*p == 0) - break; - - if (sscanf(p, "%lu%n", &g, &n) != 1) - return -EIO; - - if (!GREEDY_REALLOC(c->supplementary_gids, allocated, c->n_supplementary_gids+1)) - return -ENOMEM; - - c->supplementary_gids[c->n_supplementary_gids++] = (gid_t) g; - p += n; - } - - c->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; - continue; - } - } - - if (missing & SD_BUS_CREDS_EFFECTIVE_CAPS) { - p = startswith(line, "CapEff:"); - if (p) { - r = parse_caps(c, CAP_OFFSET_EFFECTIVE, p); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_EFFECTIVE_CAPS; - continue; - } - } - - if (missing & SD_BUS_CREDS_PERMITTED_CAPS) { - p = startswith(line, "CapPrm:"); - if (p) { - r = parse_caps(c, CAP_OFFSET_PERMITTED, p); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_PERMITTED_CAPS; - continue; - } - } - - if (missing & SD_BUS_CREDS_INHERITABLE_CAPS) { - p = startswith(line, "CapInh:"); - if (p) { - r = parse_caps(c, CAP_OFFSET_INHERITABLE, p); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_INHERITABLE_CAPS; - continue; - } - } - - if (missing & SD_BUS_CREDS_BOUNDING_CAPS) { - p = startswith(line, "CapBnd:"); - if (p) { - r = parse_caps(c, CAP_OFFSET_BOUNDING, p); - if (r < 0) - return r; - - c->mask |= SD_BUS_CREDS_BOUNDING_CAPS; - continue; - } - } - } - } - } - - if (missing & SD_BUS_CREDS_SELINUX_CONTEXT) { - const char *p; - - p = procfs_file_alloca(pid, "attr/current"); - r = read_one_line_file(p, &c->label); - if (r < 0) { - if (r != -ENOENT && r != -EINVAL && r != -EPERM && r != -EACCES) - return r; - } else - c->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; - } - - if (missing & SD_BUS_CREDS_COMM) { - r = get_process_comm(pid, &c->comm); - if (r < 0) { - if (r != -EPERM && r != -EACCES) - return r; - } else - c->mask |= SD_BUS_CREDS_COMM; - } - - if (missing & SD_BUS_CREDS_EXE) { - r = get_process_exe(pid, &c->exe); - if (r == -ESRCH) { - /* Unfortunately we cannot really distinguish - * the case here where the process does not - * exist, and /proc/$PID/exe being unreadable - * because $PID is a kernel thread. Hence, - * assume it is a kernel thread, and rely on - * that this case is caught with a later - * call. */ - c->exe = NULL; - c->mask |= SD_BUS_CREDS_EXE; - } else if (r < 0) { - if (r != -EPERM && r != -EACCES) - return r; - } else - c->mask |= SD_BUS_CREDS_EXE; - } - - if (missing & SD_BUS_CREDS_CMDLINE) { - const char *p; - - p = procfs_file_alloca(pid, "cmdline"); - r = read_full_file(p, &c->cmdline, &c->cmdline_size); - if (r == -ENOENT) - return -ESRCH; - if (r < 0) { - if (r != -EPERM && r != -EACCES) - return r; - } else { - if (c->cmdline_size == 0) - c->cmdline = mfree(c->cmdline); - - c->mask |= SD_BUS_CREDS_CMDLINE; - } - } - - if (tid > 0 && (missing & SD_BUS_CREDS_TID_COMM)) { - _cleanup_free_ char *p = NULL; - - if (asprintf(&p, "/proc/"PID_FMT"/task/"PID_FMT"/comm", pid, tid) < 0) - return -ENOMEM; - - r = read_one_line_file(p, &c->tid_comm); - if (r == -ENOENT) - return -ESRCH; - if (r < 0) { - if (r != -EPERM && r != -EACCES) - return r; - } else - c->mask |= SD_BUS_CREDS_TID_COMM; - } - - if (missing & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID)) { - - if (!c->cgroup) { - r = cg_pid_get_path(NULL, pid, &c->cgroup); - if (r < 0) { - if (r != -EPERM && r != -EACCES) - return r; - } - } - - if (!c->cgroup_root) { - r = cg_get_root_path(&c->cgroup_root); - if (r < 0) - return r; - } - - if (c->cgroup) - c->mask |= missing & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID); - } - - if (missing & SD_BUS_CREDS_AUDIT_SESSION_ID) { - r = audit_session_from_pid(pid, &c->audit_session_id); - if (r == -ENODATA) { - /* ENODATA means: no audit session id assigned */ - c->audit_session_id = AUDIT_SESSION_INVALID; - c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; - } else if (r < 0) { - if (r != -EOPNOTSUPP && r != -ENOENT && r != -EPERM && r != -EACCES) - return r; - } else - c->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; - } - - if (missing & SD_BUS_CREDS_AUDIT_LOGIN_UID) { - r = audit_loginuid_from_pid(pid, &c->audit_login_uid); - if (r == -ENODATA) { - /* ENODATA means: no audit login uid assigned */ - c->audit_login_uid = UID_INVALID; - c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; - } else if (r < 0) { - if (r != -EOPNOTSUPP && r != -ENOENT && r != -EPERM && r != -EACCES) - return r; - } else - c->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; - } - - if (missing & SD_BUS_CREDS_TTY) { - r = get_ctty(pid, NULL, &c->tty); - if (r == -ENXIO) { - /* ENXIO means: process has no controlling TTY */ - c->tty = NULL; - c->mask |= SD_BUS_CREDS_TTY; - } else if (r < 0) { - if (r != -EPERM && r != -EACCES && r != -ENOENT) - return r; - } else - c->mask |= SD_BUS_CREDS_TTY; - } - - /* In case only the exe path was to be read we cannot - * distinguish the case where the exe path was unreadable - * because the process was a kernel thread, or when the - * process didn't exist at all. Hence, let's do a final check, - * to be sure. */ - if (!pid_is_alive(pid)) - return -ESRCH; - - if (tid > 0 && tid != pid && !pid_is_unwaited(tid)) - return -ESRCH; - - c->augmented = missing & c->mask; - - return 0; -} - -int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *n = NULL; - int r; - - assert(c); - assert(ret); - - if ((mask & ~c->mask) == 0 || (!(mask & SD_BUS_CREDS_AUGMENT))) { - /* There's already all data we need, or augmentation - * wasn't turned on. */ - - *ret = sd_bus_creds_ref(c); - return 0; - } - - n = bus_creds_new(); - if (!n) - return -ENOMEM; - - /* Copy the original data over */ - - if (c->mask & mask & SD_BUS_CREDS_PID) { - n->pid = c->pid; - n->mask |= SD_BUS_CREDS_PID; - } - - if (c->mask & mask & SD_BUS_CREDS_TID) { - n->tid = c->tid; - n->mask |= SD_BUS_CREDS_TID; - } - - if (c->mask & mask & SD_BUS_CREDS_PPID) { - n->ppid = c->ppid; - n->mask |= SD_BUS_CREDS_PPID; - } - - if (c->mask & mask & SD_BUS_CREDS_UID) { - n->uid = c->uid; - n->mask |= SD_BUS_CREDS_UID; - } - - if (c->mask & mask & SD_BUS_CREDS_EUID) { - n->euid = c->euid; - n->mask |= SD_BUS_CREDS_EUID; - } - - if (c->mask & mask & SD_BUS_CREDS_SUID) { - n->suid = c->suid; - n->mask |= SD_BUS_CREDS_SUID; - } - - if (c->mask & mask & SD_BUS_CREDS_FSUID) { - n->fsuid = c->fsuid; - n->mask |= SD_BUS_CREDS_FSUID; - } - - if (c->mask & mask & SD_BUS_CREDS_GID) { - n->gid = c->gid; - n->mask |= SD_BUS_CREDS_GID; - } - - if (c->mask & mask & SD_BUS_CREDS_EGID) { - n->egid = c->egid; - n->mask |= SD_BUS_CREDS_EGID; - } - - if (c->mask & mask & SD_BUS_CREDS_SGID) { - n->sgid = c->sgid; - n->mask |= SD_BUS_CREDS_SGID; - } - - if (c->mask & mask & SD_BUS_CREDS_FSGID) { - n->fsgid = c->fsgid; - n->mask |= SD_BUS_CREDS_FSGID; - } - - if (c->mask & mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { - if (c->supplementary_gids) { - n->supplementary_gids = newdup(gid_t, c->supplementary_gids, c->n_supplementary_gids); - if (!n->supplementary_gids) - return -ENOMEM; - n->n_supplementary_gids = c->n_supplementary_gids; - } else { - n->supplementary_gids = NULL; - n->n_supplementary_gids = 0; - } - - n->mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; - } - - if (c->mask & mask & SD_BUS_CREDS_COMM) { - assert(c->comm); - - n->comm = strdup(c->comm); - if (!n->comm) - return -ENOMEM; - - n->mask |= SD_BUS_CREDS_COMM; - } - - if (c->mask & mask & SD_BUS_CREDS_TID_COMM) { - assert(c->tid_comm); - - n->tid_comm = strdup(c->tid_comm); - if (!n->tid_comm) - return -ENOMEM; - - n->mask |= SD_BUS_CREDS_TID_COMM; - } - - if (c->mask & mask & SD_BUS_CREDS_EXE) { - if (c->exe) { - n->exe = strdup(c->exe); - if (!n->exe) - return -ENOMEM; - } else - n->exe = NULL; - - n->mask |= SD_BUS_CREDS_EXE; - } - - if (c->mask & mask & SD_BUS_CREDS_CMDLINE) { - if (c->cmdline) { - n->cmdline = memdup(c->cmdline, c->cmdline_size); - if (!n->cmdline) - return -ENOMEM; - - n->cmdline_size = c->cmdline_size; - } else { - n->cmdline = NULL; - n->cmdline_size = 0; - } - - n->mask |= SD_BUS_CREDS_CMDLINE; - } - - if (c->mask & mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_OWNER_UID)) { - assert(c->cgroup); - - n->cgroup = strdup(c->cgroup); - if (!n->cgroup) - return -ENOMEM; - - n->cgroup_root = strdup(c->cgroup_root); - if (!n->cgroup_root) - return -ENOMEM; - - n->mask |= mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_USER_SLICE|SD_BUS_CREDS_OWNER_UID); - } - - if (c->mask & mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS)) { - assert(c->capability); - - n->capability = memdup(c->capability, DIV_ROUND_UP(cap_last_cap(), 32U) * 4 * 4); - if (!n->capability) - return -ENOMEM; - - n->mask |= c->mask & mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS); - } - - if (c->mask & mask & SD_BUS_CREDS_SELINUX_CONTEXT) { - assert(c->label); - - n->label = strdup(c->label); - if (!n->label) - return -ENOMEM; - n->mask |= SD_BUS_CREDS_SELINUX_CONTEXT; - } - - if (c->mask & mask & SD_BUS_CREDS_AUDIT_SESSION_ID) { - n->audit_session_id = c->audit_session_id; - n->mask |= SD_BUS_CREDS_AUDIT_SESSION_ID; - } - if (c->mask & mask & SD_BUS_CREDS_AUDIT_LOGIN_UID) { - n->audit_login_uid = c->audit_login_uid; - n->mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID; - } - - if (c->mask & mask & SD_BUS_CREDS_TTY) { - if (c->tty) { - n->tty = strdup(c->tty); - if (!n->tty) - return -ENOMEM; - } else - n->tty = NULL; - n->mask |= SD_BUS_CREDS_TTY; - } - - if (c->mask & mask & SD_BUS_CREDS_UNIQUE_NAME) { - assert(c->unique_name); - - n->unique_name = strdup(c->unique_name); - if (!n->unique_name) - return -ENOMEM; - n->mask |= SD_BUS_CREDS_UNIQUE_NAME; - } - - if (c->mask & mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) { - if (strv_isempty(c->well_known_names)) - n->well_known_names = NULL; - else { - n->well_known_names = strv_copy(c->well_known_names); - if (!n->well_known_names) - return -ENOMEM; - } - n->well_known_names_driver = c->well_known_names_driver; - n->well_known_names_local = c->well_known_names_local; - n->mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES; - } - - if (c->mask & mask & SD_BUS_CREDS_DESCRIPTION) { - assert(c->description); - n->description = strdup(c->description); - if (!n->description) - return -ENOMEM; - n->mask |= SD_BUS_CREDS_DESCRIPTION; - } - - n->augmented = c->augmented & n->mask; - - /* Get more data */ - - r = bus_creds_add_more(n, mask, 0, 0); - if (r < 0) - return r; - - *ret = n; - n = NULL; - return 0; -} diff --git a/src/libsystemd/sd-bus/bus-creds.h b/src/libsystemd/sd-bus/bus-creds.h deleted file mode 100644 index df8a1f1005..0000000000 --- a/src/libsystemd/sd-bus/bus-creds.h +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -#include "sd-bus.h" - -struct sd_bus_creds { - bool allocated; - unsigned n_ref; - - uint64_t mask; - uint64_t augmented; - - uid_t uid; - uid_t euid; - uid_t suid; - uid_t fsuid; - gid_t gid; - gid_t egid; - gid_t sgid; - gid_t fsgid; - - gid_t *supplementary_gids; - unsigned n_supplementary_gids; - - pid_t ppid; - pid_t pid; - pid_t tid; - - char *comm; - char *tid_comm; - char *exe; - - char *cmdline; - size_t cmdline_size; - char **cmdline_array; - - char *cgroup; - char *session; - char *unit; - char *user_unit; - char *slice; - char *user_slice; - - char *tty; - - uint32_t *capability; - - uint32_t audit_session_id; - uid_t audit_login_uid; - - char *label; - - char *unique_name; - - char **well_known_names; - bool well_known_names_driver:1; - bool well_known_names_local:1; - - char *cgroup_root; - - char *description, *unescaped_description; -}; - -sd_bus_creds* bus_creds_new(void); - -void bus_creds_done(sd_bus_creds *c); - -int bus_creds_add_more(sd_bus_creds *c, uint64_t mask, pid_t pid, pid_t tid); - -int bus_creds_extend_by_pid(sd_bus_creds *c, uint64_t mask, sd_bus_creds **ret); diff --git a/src/libsystemd/sd-bus/bus-dump.c b/src/libsystemd/sd-bus/bus-dump.c deleted file mode 100644 index 7c81e7a25d..0000000000 --- a/src/libsystemd/sd-bus/bus-dump.c +++ /dev/null @@ -1,602 +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 . -***/ - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-type.h" -#include "cap-list.h" -#include "capability-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "locale-util.h" -#include "macro.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "util.h" - -static char *indent(unsigned level, unsigned flags) { - char *p; - unsigned n, i = 0; - - n = 0; - - if (flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY && level > 0) - level -= 1; - - if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) - n += 2; - - p = new(char, n + level*8 + 1); - if (!p) - return NULL; - - if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) { - p[i++] = ' '; - p[i++] = ' '; - } - - memset(p + i, ' ', level*8); - p[i + level*8] = 0; - - return p; -} - -int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags) { - unsigned level = 1; - int r; - - assert(m); - - if (!f) - f = stdout; - - if (flags & BUS_MESSAGE_DUMP_WITH_HEADER) { - fprintf(f, - "%s%s%s Type=%s%s%s Endian=%c Flags=%u Version=%u Priority=%"PRIi64, - m->header->type == SD_BUS_MESSAGE_METHOD_ERROR ? ansi_highlight_red() : - m->header->type == SD_BUS_MESSAGE_METHOD_RETURN ? ansi_highlight_green() : - m->header->type != SD_BUS_MESSAGE_SIGNAL ? ansi_highlight() : "", draw_special_char(DRAW_TRIANGULAR_BULLET), ansi_normal(), - ansi_highlight(), bus_message_type_to_string(m->header->type), ansi_normal(), - m->header->endian, - m->header->flags, - m->header->version, - m->priority); - - /* Display synthetic message serial number in a more readable - * format than (uint32_t) -1 */ - if (BUS_MESSAGE_COOKIE(m) == 0xFFFFFFFFULL) - fprintf(f, " Cookie=-1"); - else - fprintf(f, " Cookie=%" PRIu64, BUS_MESSAGE_COOKIE(m)); - - if (m->reply_cookie != 0) - fprintf(f, " ReplyCookie=%" PRIu64, m->reply_cookie); - - fputs("\n", f); - - if (m->sender) - fprintf(f, " Sender=%s%s%s", ansi_highlight(), m->sender, ansi_normal()); - if (m->destination) - fprintf(f, " Destination=%s%s%s", ansi_highlight(), m->destination, ansi_normal()); - if (m->path) - fprintf(f, " Path=%s%s%s", ansi_highlight(), m->path, ansi_normal()); - if (m->interface) - fprintf(f, " Interface=%s%s%s", ansi_highlight(), m->interface, ansi_normal()); - if (m->member) - fprintf(f, " Member=%s%s%s", ansi_highlight(), m->member, ansi_normal()); - - if (m->sender || m->destination || m->path || m->interface || m->member) - fputs("\n", f); - - if (sd_bus_error_is_set(&m->error)) - fprintf(f, - " ErrorName=%s%s%s" - " ErrorMessage=%s\"%s\"%s\n", - ansi_highlight_red(), strna(m->error.name), ansi_normal(), - ansi_highlight_red(), strna(m->error.message), ansi_normal()); - - if (m->monotonic != 0) - fprintf(f, " Monotonic="USEC_FMT, m->monotonic); - if (m->realtime != 0) - fprintf(f, " Realtime="USEC_FMT, m->realtime); - if (m->seqnum != 0) - fprintf(f, " SequenceNumber=%"PRIu64, m->seqnum); - - if (m->monotonic != 0 || m->realtime != 0 || m->seqnum != 0) - fputs("\n", f); - - bus_creds_dump(&m->creds, f, true); - } - - r = sd_bus_message_rewind(m, !(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)); - if (r < 0) - return log_error_errno(r, "Failed to rewind: %m"); - - if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) { - _cleanup_free_ char *prefix = NULL; - - prefix = indent(0, flags); - if (!prefix) - return log_oom(); - - fprintf(f, "%sMESSAGE \"%s\" {\n", prefix, strempty(m->root_container.signature)); - } - - for (;;) { - _cleanup_free_ char *prefix = NULL; - const char *contents = NULL; - char type; - union { - uint8_t u8; - uint16_t u16; - int16_t s16; - uint32_t u32; - int32_t s32; - uint64_t u64; - int64_t s64; - double d64; - const char *string; - int i; - } basic; - - r = sd_bus_message_peek_type(m, &type, &contents); - if (r < 0) - return log_error_errno(r, "Failed to peek type: %m"); - - if (r == 0) { - if (level <= 1) - break; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return log_error_errno(r, "Failed to exit container: %m"); - - level--; - - prefix = indent(level, flags); - if (!prefix) - return log_oom(); - - fprintf(f, "%s};\n", prefix); - continue; - } - - prefix = indent(level, flags); - if (!prefix) - return log_oom(); - - if (bus_type_is_container(type) > 0) { - r = sd_bus_message_enter_container(m, type, contents); - if (r < 0) - return log_error_errno(r, "Failed to enter container: %m"); - - if (type == SD_BUS_TYPE_ARRAY) - fprintf(f, "%sARRAY \"%s\" {\n", prefix, contents); - else if (type == SD_BUS_TYPE_VARIANT) - fprintf(f, "%sVARIANT \"%s\" {\n", prefix, contents); - else if (type == SD_BUS_TYPE_STRUCT) - fprintf(f, "%sSTRUCT \"%s\" {\n", prefix, contents); - else if (type == SD_BUS_TYPE_DICT_ENTRY) - fprintf(f, "%sDICT_ENTRY \"%s\" {\n", prefix, contents); - - level ++; - - continue; - } - - r = sd_bus_message_read_basic(m, type, &basic); - if (r < 0) - return log_error_errno(r, "Failed to get basic: %m"); - - assert(r > 0); - - switch (type) { - - case SD_BUS_TYPE_BYTE: - fprintf(f, "%sBYTE %s%u%s;\n", prefix, ansi_highlight(), basic.u8, ansi_normal()); - break; - - case SD_BUS_TYPE_BOOLEAN: - fprintf(f, "%sBOOLEAN %s%s%s;\n", prefix, ansi_highlight(), true_false(basic.i), ansi_normal()); - break; - - case SD_BUS_TYPE_INT16: - fprintf(f, "%sINT16 %s%i%s;\n", prefix, ansi_highlight(), basic.s16, ansi_normal()); - break; - - case SD_BUS_TYPE_UINT16: - fprintf(f, "%sUINT16 %s%u%s;\n", prefix, ansi_highlight(), basic.u16, ansi_normal()); - break; - - case SD_BUS_TYPE_INT32: - fprintf(f, "%sINT32 %s%i%s;\n", prefix, ansi_highlight(), basic.s32, ansi_normal()); - break; - - case SD_BUS_TYPE_UINT32: - fprintf(f, "%sUINT32 %s%u%s;\n", prefix, ansi_highlight(), basic.u32, ansi_normal()); - break; - - case SD_BUS_TYPE_INT64: - fprintf(f, "%sINT64 %s%"PRIi64"%s;\n", prefix, ansi_highlight(), basic.s64, ansi_normal()); - break; - - case SD_BUS_TYPE_UINT64: - fprintf(f, "%sUINT64 %s%"PRIu64"%s;\n", prefix, ansi_highlight(), basic.u64, ansi_normal()); - break; - - case SD_BUS_TYPE_DOUBLE: - fprintf(f, "%sDOUBLE %s%g%s;\n", prefix, ansi_highlight(), basic.d64, ansi_normal()); - break; - - case SD_BUS_TYPE_STRING: - fprintf(f, "%sSTRING \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal()); - break; - - case SD_BUS_TYPE_OBJECT_PATH: - fprintf(f, "%sOBJECT_PATH \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal()); - break; - - case SD_BUS_TYPE_SIGNATURE: - fprintf(f, "%sSIGNATURE \"%s%s%s\";\n", prefix, ansi_highlight(), basic.string, ansi_normal()); - break; - - case SD_BUS_TYPE_UNIX_FD: - fprintf(f, "%sUNIX_FD %s%i%s;\n", prefix, ansi_highlight(), basic.i, ansi_normal()); - break; - - default: - assert_not_reached("Unknown basic type."); - } - } - - if (!(flags & BUS_MESSAGE_DUMP_SUBTREE_ONLY)) { - _cleanup_free_ char *prefix = NULL; - - prefix = indent(0, flags); - if (!prefix) - return log_oom(); - - fprintf(f, "%s};\n\n", prefix); - } - - return 0; -} - -static void dump_capabilities( - sd_bus_creds *c, - FILE *f, - const char *name, - bool terse, - int (*has)(sd_bus_creds *c, int capability)) { - - unsigned long i, last_cap; - unsigned n = 0; - int r; - - assert(c); - assert(f); - assert(name); - assert(has); - - i = 0; - r = has(c, i); - if (r < 0) - return; - - fprintf(f, "%s%s=%s", terse ? " " : "", name, terse ? "" : ansi_highlight()); - last_cap = cap_last_cap(); - - for (;;) { - if (r > 0) { - - if (n > 0) - fputc(' ', f); - if (n % 4 == 3) - fprintf(f, terse ? "\n " : "\n "); - - fprintf(f, "%s", strna(capability_to_name(i))); - n++; - } - - i++; - - if (i > last_cap) - break; - - r = has(c, i); - } - - fputs("\n", f); - - if (!terse) - fputs(ansi_normal(), f); -} - -int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse) { - uid_t owner, audit_loginuid; - uint32_t audit_sessionid; - char **cmdline = NULL, **well_known = NULL; - const char *prefix, *color, *suffix, *s; - int r, q, v, w, z; - - assert(c); - - if (!f) - f = stdout; - - if (terse) { - prefix = " "; - suffix = ""; - color = ""; - } else { - const char *off; - - prefix = ""; - color = ansi_highlight(); - - off = ansi_normal(); - suffix = strjoina(off, "\n"); - } - - if (c->mask & SD_BUS_CREDS_PID) - fprintf(f, "%sPID=%s"PID_FMT"%s", prefix, color, c->pid, suffix); - if (c->mask & SD_BUS_CREDS_TID) - fprintf(f, "%sTID=%s"PID_FMT"%s", prefix, color, c->tid, suffix); - if (c->mask & SD_BUS_CREDS_PPID) { - if (c->ppid == 0) - fprintf(f, "%sPPID=%sn/a%s", prefix, color, suffix); - else - fprintf(f, "%sPPID=%s"PID_FMT"%s", prefix, color, c->ppid, suffix); - } - if (c->mask & SD_BUS_CREDS_TTY) - fprintf(f, "%sTTY=%s%s%s", prefix, color, strna(c->tty), suffix); - - if (terse && ((c->mask & (SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_PPID|SD_BUS_CREDS_TTY)))) - fputs("\n", f); - - if (c->mask & SD_BUS_CREDS_UID) - fprintf(f, "%sUID=%s"UID_FMT"%s", prefix, color, c->uid, suffix); - if (c->mask & SD_BUS_CREDS_EUID) - fprintf(f, "%sEUID=%s"UID_FMT"%s", prefix, color, c->euid, suffix); - if (c->mask & SD_BUS_CREDS_SUID) - fprintf(f, "%sSUID=%s"UID_FMT"%s", prefix, color, c->suid, suffix); - if (c->mask & SD_BUS_CREDS_FSUID) - fprintf(f, "%sFSUID=%s"UID_FMT"%s", prefix, color, c->fsuid, suffix); - r = sd_bus_creds_get_owner_uid(c, &owner); - if (r >= 0) - fprintf(f, "%sOwnerUID=%s"UID_FMT"%s", prefix, color, owner, suffix); - if (c->mask & SD_BUS_CREDS_GID) - fprintf(f, "%sGID=%s"GID_FMT"%s", prefix, color, c->gid, suffix); - if (c->mask & SD_BUS_CREDS_EGID) - fprintf(f, "%sEGID=%s"GID_FMT"%s", prefix, color, c->egid, suffix); - if (c->mask & SD_BUS_CREDS_SGID) - fprintf(f, "%sSGID=%s"GID_FMT"%s", prefix, color, c->sgid, suffix); - if (c->mask & SD_BUS_CREDS_FSGID) - fprintf(f, "%sFSGID=%s"GID_FMT"%s", prefix, color, c->fsgid, suffix); - - if (c->mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { - unsigned i; - - fprintf(f, "%sSupplementaryGIDs=%s", prefix, color); - for (i = 0; i < c->n_supplementary_gids; i++) - fprintf(f, "%s" GID_FMT, i > 0 ? " " : "", c->supplementary_gids[i]); - fprintf(f, "%s", suffix); - } - - if (terse && ((c->mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| - SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID| - SD_BUS_CREDS_SUPPLEMENTARY_GIDS)) || r >= 0)) - fputs("\n", f); - - if (c->mask & SD_BUS_CREDS_COMM) - fprintf(f, "%sComm=%s%s%s", prefix, color, c->comm, suffix); - if (c->mask & SD_BUS_CREDS_TID_COMM) - fprintf(f, "%sTIDComm=%s%s%s", prefix, color, c->tid_comm, suffix); - if (c->mask & SD_BUS_CREDS_EXE) - fprintf(f, "%sExe=%s%s%s", prefix, color, strna(c->exe), suffix); - - if (terse && (c->mask & (SD_BUS_CREDS_EXE|SD_BUS_CREDS_COMM|SD_BUS_CREDS_TID_COMM))) - fputs("\n", f); - - r = sd_bus_creds_get_cmdline(c, &cmdline); - if (r >= 0) { - char **i; - - fprintf(f, "%sCommandLine=%s", prefix, color); - STRV_FOREACH(i, cmdline) { - if (i != cmdline) - fputc(' ', f); - - fputs(*i, f); - } - - fprintf(f, "%s", suffix); - } else if (r != -ENODATA) - fprintf(f, "%sCommandLine=%sn/a%s", prefix, color, suffix); - - if (c->mask & SD_BUS_CREDS_SELINUX_CONTEXT) - fprintf(f, "%sLabel=%s%s%s", prefix, color, c->label, suffix); - if (c->mask & SD_BUS_CREDS_DESCRIPTION) - fprintf(f, "%sDescription=%s%s%s", prefix, color, c->description, suffix); - - if (terse && (c->mask & (SD_BUS_CREDS_SELINUX_CONTEXT|SD_BUS_CREDS_DESCRIPTION))) - fputs("\n", f); - - if (c->mask & SD_BUS_CREDS_CGROUP) - fprintf(f, "%sCGroup=%s%s%s", prefix, color, c->cgroup, suffix); - s = NULL; - r = sd_bus_creds_get_unit(c, &s); - if (r != -ENODATA) - fprintf(f, "%sUnit=%s%s%s", prefix, color, strna(s), suffix); - s = NULL; - v = sd_bus_creds_get_slice(c, &s); - if (v != -ENODATA) - fprintf(f, "%sSlice=%s%s%s", prefix, color, strna(s), suffix); - s = NULL; - q = sd_bus_creds_get_user_unit(c, &s); - if (q != -ENODATA) - fprintf(f, "%sUserUnit=%s%s%s", prefix, color, strna(s), suffix); - s = NULL; - w = sd_bus_creds_get_user_slice(c, &s); - if (w != -ENODATA) - fprintf(f, "%sUserSlice=%s%s%s", prefix, color, strna(s), suffix); - s = NULL; - z = sd_bus_creds_get_session(c, &s); - if (z != -ENODATA) - fprintf(f, "%sSession=%s%s%s", prefix, color, strna(s), suffix); - - if (terse && ((c->mask & SD_BUS_CREDS_CGROUP) || r != -ENODATA || q != -ENODATA || v != -ENODATA || w != -ENODATA || z != -ENODATA)) - fputs("\n", f); - - r = sd_bus_creds_get_audit_login_uid(c, &audit_loginuid); - if (r >= 0) - fprintf(f, "%sAuditLoginUID=%s"UID_FMT"%s", prefix, color, audit_loginuid, suffix); - else if (r != -ENODATA) - fprintf(f, "%sAuditLoginUID=%sn/a%s", prefix, color, suffix); - q = sd_bus_creds_get_audit_session_id(c, &audit_sessionid); - if (q >= 0) - fprintf(f, "%sAuditSessionID=%s%"PRIu32"%s", prefix, color, audit_sessionid, suffix); - else if (q != -ENODATA) - fprintf(f, "%sAuditSessionID=%sn/a%s", prefix, color, suffix); - - if (terse && (r != -ENODATA || q != -ENODATA)) - fputs("\n", f); - - if (c->mask & SD_BUS_CREDS_UNIQUE_NAME) - fprintf(f, "%sUniqueName=%s%s%s", prefix, color, c->unique_name, suffix); - - if (sd_bus_creds_get_well_known_names(c, &well_known) >= 0) { - char **i; - - fprintf(f, "%sWellKnownNames=%s", prefix, color); - STRV_FOREACH(i, well_known) { - if (i != well_known) - fputc(' ', f); - - fputs(*i, f); - } - - fprintf(f, "%s", suffix); - } - - if (terse && (c->mask & SD_BUS_CREDS_UNIQUE_NAME || well_known)) - fputc('\n', f); - - dump_capabilities(c, f, "EffectiveCapabilities", terse, sd_bus_creds_has_effective_cap); - dump_capabilities(c, f, "PermittedCapabilities", terse, sd_bus_creds_has_permitted_cap); - dump_capabilities(c, f, "InheritableCapabilities", terse, sd_bus_creds_has_inheritable_cap); - dump_capabilities(c, f, "BoundingCapabilities", terse, sd_bus_creds_has_bounding_cap); - - return 0; -} - -/* - * For details about the file format, see: - * - * http://wiki.wireshark.org/Development/LibpcapFileFormat - */ - -typedef struct _packed_ pcap_hdr_s { - uint32_t magic_number; /* magic number */ - uint16_t version_major; /* major version number */ - uint16_t version_minor; /* minor version number */ - int32_t thiszone; /* GMT to local correction */ - uint32_t sigfigs; /* accuracy of timestamps */ - uint32_t snaplen; /* max length of captured packets, in octets */ - uint32_t network; /* data link type */ -} pcap_hdr_t ; - -typedef struct _packed_ pcaprec_hdr_s { - uint32_t ts_sec; /* timestamp seconds */ - uint32_t ts_usec; /* timestamp microseconds */ - uint32_t incl_len; /* number of octets of packet saved in file */ - uint32_t orig_len; /* actual length of packet */ -} pcaprec_hdr_t; - -int bus_pcap_header(size_t snaplen, FILE *f) { - - pcap_hdr_t hdr = { - .magic_number = 0xa1b2c3d4U, - .version_major = 2, - .version_minor = 4, - .thiszone = 0, /* UTC */ - .sigfigs = 0, - .network = 231, /* D-Bus */ - }; - - if (!f) - f = stdout; - - assert(snaplen > 0); - assert((size_t) (uint32_t) snaplen == snaplen); - - hdr.snaplen = (uint32_t) snaplen; - - fwrite(&hdr, 1, sizeof(hdr), f); - - return fflush_and_check(f); -} - -int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f) { - struct bus_body_part *part; - pcaprec_hdr_t hdr = {}; - struct timeval tv; - unsigned i; - size_t w; - - if (!f) - f = stdout; - - assert(m); - assert(snaplen > 0); - assert((size_t) (uint32_t) snaplen == snaplen); - - if (m->realtime != 0) - timeval_store(&tv, m->realtime); - else - assert_se(gettimeofday(&tv, NULL) >= 0); - - hdr.ts_sec = tv.tv_sec; - hdr.ts_usec = tv.tv_usec; - hdr.orig_len = BUS_MESSAGE_SIZE(m); - hdr.incl_len = MIN(hdr.orig_len, snaplen); - - /* write the pcap header */ - fwrite(&hdr, 1, sizeof(hdr), f); - - /* write the dbus header */ - w = MIN(BUS_MESSAGE_BODY_BEGIN(m), snaplen); - fwrite(m->header, 1, w, f); - snaplen -= w; - - /* write the dbus body */ - MESSAGE_FOREACH_PART(part, i, m) { - if (snaplen <= 0) - break; - - w = MIN(part->size, snaplen); - fwrite(part->data, 1, w, f); - snaplen -= w; - } - - return fflush_and_check(f); -} diff --git a/src/libsystemd/sd-bus/bus-dump.h b/src/libsystemd/sd-bus/bus-dump.h deleted file mode 100644 index 874e86d09c..0000000000 --- a/src/libsystemd/sd-bus/bus-dump.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include -#include - -#include "sd-bus.h" - -enum { - BUS_MESSAGE_DUMP_WITH_HEADER = 1, - BUS_MESSAGE_DUMP_SUBTREE_ONLY = 2, -}; - -int bus_message_dump(sd_bus_message *m, FILE *f, unsigned flags); - -int bus_creds_dump(sd_bus_creds *c, FILE *f, bool terse); - -int bus_pcap_header(size_t snaplen, FILE *f); -int bus_message_pcap_frame(sd_bus_message *m, size_t snaplen, FILE *f); diff --git a/src/libsystemd/sd-bus/bus-error.c b/src/libsystemd/sd-bus/bus-error.c deleted file mode 100644 index 26219bdeed..0000000000 --- a/src/libsystemd/sd-bus/bus-error.c +++ /dev/null @@ -1,608 +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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "errno-list.h" -#include "string-util.h" -#include "util.h" - -BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map bus_standard_errors[] = { - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Failed", EACCES), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoMemory", ENOMEM), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ServiceUnknown", EHOSTUNREACH), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NameHasNoOwner", ENXIO), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoReply", ETIMEDOUT), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.IOError", EIO), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.BadAddress", EADDRNOTAVAIL), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NotSupported", EOPNOTSUPP), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.LimitsExceeded", ENOBUFS), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AccessDenied", EACCES), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AuthFailed", EACCES), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InteractiveAuthorizationRequired", EACCES), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoServer", EHOSTDOWN), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Timeout", ETIMEDOUT), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.NoNetwork", ENONET), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.AddressInUse", EADDRINUSE), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.Disconnected", ECONNRESET), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidArgs", EINVAL), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileNotFound", ENOENT), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.FileExists", EEXIST), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownMethod", EBADR), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownObject", EBADR), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownInterface", EBADR), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnknownProperty", EBADR), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.PropertyReadOnly", EROFS), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.UnixProcessIdUnknown", ESRCH), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidSignature", EINVAL), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InconsistentMessage", EBADMSG), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.TimedOut", ETIMEDOUT), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleInvalid", EINVAL), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.InvalidFileContent", EINVAL), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.MatchRuleNotFound", ENOENT), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.SELinuxSecurityContextUnknown", ESRCH), - SD_BUS_ERROR_MAP("org.freedesktop.DBus.Error.ObjectPathInUse", EBUSY), - SD_BUS_ERROR_MAP_END -}; - -/* GCC maps this magically to the beginning and end of the BUS_ERROR_MAP section. - * Hide them; for currently unknown reasons they get exported to the shared libries - * even without being listed in the sym file. */ -extern const sd_bus_error_map __start_BUS_ERROR_MAP[] _hidden_; -extern const sd_bus_error_map __stop_BUS_ERROR_MAP[] _hidden_; - -/* Additional maps registered with sd_bus_error_add_map() are in this - * NULL terminated array */ -static const sd_bus_error_map **additional_error_maps = NULL; - -static int bus_error_name_to_errno(const char *name) { - const sd_bus_error_map **map, *m; - const char *p; - int r; - - if (!name) - return EINVAL; - - p = startswith(name, "System.Error."); - if (p) { - r = errno_from_name(p); - if (r < 0) - return EIO; - - return r; - } - - if (additional_error_maps) - for (map = additional_error_maps; *map; map++) - for (m = *map;; m++) { - /* For additional error maps the end marker is actually the end marker */ - if (m->code == BUS_ERROR_MAP_END_MARKER) - break; - - if (streq(m->name, name)) - return m->code; - } - - m = __start_BUS_ERROR_MAP; - while (m < __stop_BUS_ERROR_MAP) { - /* For magic ELF error maps, the end marker might - * appear in the middle of things, since multiple maps - * might appear in the same section. Hence, let's skip - * over it, but realign the pointer to the next 8 byte - * boundary, which is the selected alignment for the - * arrays. */ - if (m->code == BUS_ERROR_MAP_END_MARKER) { - m = ALIGN8_PTR(m+1); - continue; - } - - if (streq(m->name, name)) - return m->code; - - m++; - } - - return EIO; -} - -static sd_bus_error errno_to_bus_error_const(int error) { - - if (error < 0) - error = -error; - - switch (error) { - - case ENOMEM: - return BUS_ERROR_OOM; - - case EPERM: - case EACCES: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_ACCESS_DENIED, "Access denied"); - - case EINVAL: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INVALID_ARGS, "Invalid argument"); - - case ESRCH: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN, "No such process"); - - case ENOENT: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_NOT_FOUND, "File not found"); - - case EEXIST: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_EXISTS, "File exists"); - - case ETIMEDOUT: - case ETIME: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_TIMEOUT, "Timed out"); - - case EIO: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_IO_ERROR, "Input/output error"); - - case ENETRESET: - case ECONNABORTED: - case ECONNRESET: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_DISCONNECTED, "Disconnected"); - - case EOPNOTSUPP: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NOT_SUPPORTED, "Not supported"); - - case EADDRNOTAVAIL: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_BAD_ADDRESS, "Address not available"); - - case ENOBUFS: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_LIMITS_EXCEEDED, "Limits exceeded"); - - case EADDRINUSE: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_ADDRESS_IN_USE, "Address in use"); - - case EBADMSG: - return SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Inconsistent message"); - } - - return SD_BUS_ERROR_NULL; -} - -static int errno_to_bus_error_name_new(int error, char **ret) { - const char *name; - char *n; - - if (error < 0) - error = -error; - - name = errno_to_name(error); - if (!name) - return 0; - - n = strappend("System.Error.", name); - if (!n) - return -ENOMEM; - - *ret = n; - return 1; -} - -bool bus_error_is_dirty(sd_bus_error *e) { - if (!e) - return false; - - return e->name || e->message || e->_need_free != 0; -} - -_public_ void sd_bus_error_free(sd_bus_error *e) { - if (!e) - return; - - if (e->_need_free > 0) { - free((void*) e->name); - free((void*) e->message); - } - - e->name = e->message = NULL; - e->_need_free = 0; -} - -_public_ int sd_bus_error_set(sd_bus_error *e, const char *name, const char *message) { - - if (!name) - return 0; - if (!e) - goto finish; - - assert_return(!bus_error_is_dirty(e), -EINVAL); - - e->name = strdup(name); - if (!e->name) { - *e = BUS_ERROR_OOM; - return -ENOMEM; - } - - if (message) - e->message = strdup(message); - - e->_need_free = 1; - -finish: - return -bus_error_name_to_errno(name); -} - -int bus_error_setfv(sd_bus_error *e, const char *name, const char *format, va_list ap) { - - if (!name) - return 0; - - if (e) { - assert_return(!bus_error_is_dirty(e), -EINVAL); - - e->name = strdup(name); - if (!e->name) { - *e = BUS_ERROR_OOM; - return -ENOMEM; - } - - /* If we hit OOM on formatting the pretty message, we ignore - * this, since we at least managed to write the error name */ - if (format) - (void) vasprintf((char**) &e->message, format, ap); - - e->_need_free = 1; - } - - return -bus_error_name_to_errno(name); -} - -_public_ int sd_bus_error_setf(sd_bus_error *e, const char *name, const char *format, ...) { - - if (format) { - int r; - va_list ap; - - va_start(ap, format); - r = bus_error_setfv(e, name, format, ap); - va_end(ap); - - return r; - } - - return sd_bus_error_set(e, name, NULL); -} - -_public_ int sd_bus_error_copy(sd_bus_error *dest, const sd_bus_error *e) { - - if (!sd_bus_error_is_set(e)) - return 0; - if (!dest) - goto finish; - - assert_return(!bus_error_is_dirty(dest), -EINVAL); - - /* - * _need_free < 0 indicates that the error is temporarily const, needs deep copying - * _need_free == 0 indicates that the error is perpetually const, needs no deep copying - * _need_free > 0 indicates that the error is fully dynamic, needs deep copying - */ - - if (e->_need_free == 0) - *dest = *e; - else { - dest->name = strdup(e->name); - if (!dest->name) { - *dest = BUS_ERROR_OOM; - return -ENOMEM; - } - - if (e->message) - dest->message = strdup(e->message); - - dest->_need_free = 1; - } - -finish: - return -bus_error_name_to_errno(e->name); -} - -_public_ int sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message) { - if (!name) - return 0; - if (!e) - goto finish; - - assert_return(!bus_error_is_dirty(e), -EINVAL); - - *e = SD_BUS_ERROR_MAKE_CONST(name, message); - -finish: - return -bus_error_name_to_errno(name); -} - -_public_ int sd_bus_error_is_set(const sd_bus_error *e) { - if (!e) - return 0; - - return !!e->name; -} - -_public_ int sd_bus_error_has_name(const sd_bus_error *e, const char *name) { - if (!e) - return 0; - - return streq_ptr(e->name, name); -} - -_public_ int sd_bus_error_get_errno(const sd_bus_error* e) { - if (!e) - return 0; - - if (!e->name) - return 0; - - return bus_error_name_to_errno(e->name); -} - -static void bus_error_strerror(sd_bus_error *e, int error) { - size_t k = 64; - char *m; - - assert(e); - - for (;;) { - char *x; - - m = new(char, k); - if (!m) - return; - - errno = 0; - x = strerror_r(error, m, k); - if (errno == ERANGE || strlen(x) >= k - 1) { - free(m); - k *= 2; - continue; - } - - if (errno) { - free(m); - return; - } - - if (x == m) { - if (e->_need_free > 0) { - /* Error is already dynamic, let's just update the message */ - free((char*) e->message); - e->message = x; - - } else { - char *t; - /* Error was const so far, let's make it dynamic, if we can */ - - t = strdup(e->name); - if (!t) { - free(m); - return; - } - - e->_need_free = 1; - e->name = t; - e->message = x; - } - } else { - free(m); - - if (e->_need_free > 0) { - char *t; - - /* Error is dynamic, let's hence make the message also dynamic */ - t = strdup(x); - if (!t) - return; - - free((char*) e->message); - e->message = t; - } else { - /* Error is const, hence we can just override */ - e->message = x; - } - } - - return; - } -} - -_public_ int sd_bus_error_set_errno(sd_bus_error *e, int error) { - - if (error < 0) - error = -error; - - if (!e) - return -error; - if (error == 0) - return -error; - - assert_return(!bus_error_is_dirty(e), -EINVAL); - - /* First, try a const translation */ - *e = errno_to_bus_error_const(error); - - if (!sd_bus_error_is_set(e)) { - int k; - - /* If that didn't work, try a dynamic one. */ - - k = errno_to_bus_error_name_new(error, (char**) &e->name); - if (k > 0) - e->_need_free = 1; - else if (k < 0) { - *e = BUS_ERROR_OOM; - return -error; - } else - *e = BUS_ERROR_FAILED; - } - - /* Now, fill in the message from strerror() if we can */ - bus_error_strerror(e, error); - return -error; -} - -_public_ int sd_bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_list ap) { - PROTECT_ERRNO; - int r; - - if (error < 0) - error = -error; - - if (!e) - return -error; - if (error == 0) - return 0; - - assert_return(!bus_error_is_dirty(e), -EINVAL); - - /* First, try a const translation */ - *e = errno_to_bus_error_const(error); - - if (!sd_bus_error_is_set(e)) { - int k; - - /* If that didn't work, try a dynamic one */ - - k = errno_to_bus_error_name_new(error, (char**) &e->name); - if (k > 0) - e->_need_free = 1; - else if (k < 0) { - *e = BUS_ERROR_OOM; - return -ENOMEM; - } else - *e = BUS_ERROR_FAILED; - } - - if (format) { - char *m; - - /* Then, let's try to fill in the supplied message */ - - errno = error; /* Make sure that %m resolves to the specified error */ - r = vasprintf(&m, format, ap); - if (r >= 0) { - - if (e->_need_free <= 0) { - char *t; - - t = strdup(e->name); - if (t) { - e->_need_free = 1; - e->name = t; - e->message = m; - return -error; - } - - free(m); - } else { - free((char*) e->message); - e->message = m; - return -error; - } - } - } - - /* If that didn't work, use strerror() for the message */ - bus_error_strerror(e, error); - return -error; -} - -_public_ int sd_bus_error_set_errnof(sd_bus_error *e, int error, const char *format, ...) { - int r; - - if (error < 0) - error = -error; - - if (!e) - return -error; - if (error == 0) - return 0; - - assert_return(!bus_error_is_dirty(e), -EINVAL); - - if (format) { - va_list ap; - - va_start(ap, format); - r = sd_bus_error_set_errnofv(e, error, format, ap); - va_end(ap); - - return r; - } - - return sd_bus_error_set_errno(e, error); -} - -const char *bus_error_message(const sd_bus_error *e, int error) { - - if (e) { - /* Sometimes, the D-Bus server is a little bit too verbose with - * its error messages, so let's override them here */ - if (sd_bus_error_has_name(e, SD_BUS_ERROR_ACCESS_DENIED)) - return "Access denied"; - - if (e->message) - return e->message; - } - - if (error < 0) - error = -error; - - return strerror(error); -} - -static bool map_ok(const sd_bus_error_map *map) { - for (; map->code != BUS_ERROR_MAP_END_MARKER; map++) - if (!map->name || map->code <=0) - return false; - return true; -} - -_public_ int sd_bus_error_add_map(const sd_bus_error_map *map) { - const sd_bus_error_map **maps = NULL; - unsigned n = 0; - - assert_return(map, -EINVAL); - assert_return(map_ok(map), -EINVAL); - - if (additional_error_maps) - for (; additional_error_maps[n] != NULL; n++) - if (additional_error_maps[n] == map) - return 0; - - maps = realloc_multiply(additional_error_maps, sizeof(struct sd_bus_error_map*), n + 2); - if (!maps) - return -ENOMEM; - - maps[n] = map; - maps[n+1] = NULL; - - additional_error_maps = maps; - return 1; -} diff --git a/src/libsystemd/sd-bus/bus-error.h b/src/libsystemd/sd-bus/bus-error.h deleted file mode 100644 index e2c4cf4b3f..0000000000 --- a/src/libsystemd/sd-bus/bus-error.h +++ /dev/null @@ -1,64 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -#include "sd-bus.h" - -#include "macro.h" - -bool bus_error_is_dirty(sd_bus_error *e); - -const char *bus_error_message(const sd_bus_error *e, int error); - -int bus_error_setfv(sd_bus_error *e, const char *name, const char *format, va_list ap) _printf_(3,0); -int bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_list ap) _printf_(3,0); - -#define BUS_ERROR_OOM SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_MEMORY, "Out of memory") -#define BUS_ERROR_FAILED SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FAILED, "Operation failed") - -/* - * There are two ways to register error maps with the error translation - * logic: by using BUS_ERROR_MAP_ELF_REGISTER, which however only - * works when linked into the same ELF module, or via - * sd_bus_error_add_map() which is the official, external API, that - * works from any module. - * - * Note that BUS_ERROR_MAP_ELF_REGISTER has to be used as decorator in - * the bus error table, and BUS_ERROR_MAP_ELF_USE has to be used at - * least once per compilation unit (i.e. per library), to ensure that - * the error map is really added to the final binary. - */ - -#define BUS_ERROR_MAP_ELF_REGISTER \ - __attribute__ ((__section__("BUS_ERROR_MAP"))) \ - __attribute__ ((__used__)) \ - __attribute__ ((aligned(8))) - -#define BUS_ERROR_MAP_ELF_USE(errors) \ - extern const sd_bus_error_map errors[]; \ - __attribute__ ((used)) static const sd_bus_error_map * const CONCATENATE(errors ## _copy_, __COUNTER__) = errors; - -/* We use something exotic as end marker, to ensure people build the - * maps using the macsd-ros. */ -#define BUS_ERROR_MAP_END_MARKER -'x' - -BUS_ERROR_MAP_ELF_USE(bus_standard_errors); diff --git a/src/libsystemd/sd-bus/bus-gvariant.c b/src/libsystemd/sd-bus/bus-gvariant.c deleted file mode 100644 index 58782767fa..0000000000 --- a/src/libsystemd/sd-bus/bus-gvariant.c +++ /dev/null @@ -1,311 +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 . -***/ - -#include "bus-gvariant.h" -#include "bus-signature.h" -#include "bus-type.h" - -int bus_gvariant_get_size(const char *signature) { - const char *p; - int sum = 0, r; - - /* For fixed size structs. Fails for variable size structs. */ - - p = signature; - while (*p != 0) { - size_t n; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - else { - char t[n+1]; - - memcpy(t, p, n); - t[n] = 0; - - r = bus_gvariant_get_alignment(t); - if (r < 0) - return r; - - sum = ALIGN_TO(sum, r); - } - - switch (*p) { - - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_BYTE: - sum += 1; - break; - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - sum += 2; - break; - - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_UNIX_FD: - sum += 4; - break; - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - sum += 8; - break; - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - if (n == 2) { - /* unary type () has fixed size of 1 */ - r = 1; - } else { - char t[n-1]; - - memcpy(t, p + 1, n - 2); - t[n - 2] = 0; - - r = bus_gvariant_get_size(t); - if (r < 0) - return r; - } - - sum += r; - break; - } - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: - case SD_BUS_TYPE_ARRAY: - case SD_BUS_TYPE_VARIANT: - return -EINVAL; - - default: - assert_not_reached("Unknown signature type"); - } - - p += n; - } - - r = bus_gvariant_get_alignment(signature); - if (r < 0) - return r; - - return ALIGN_TO(sum, r); -} - -int bus_gvariant_get_alignment(const char *signature) { - size_t alignment = 1; - const char *p; - int r; - - p = signature; - while (*p != 0 && alignment < 8) { - size_t n; - int a; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - - switch (*p) { - - case SD_BUS_TYPE_BYTE: - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: - a = 1; - break; - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - a = 2; - break; - - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_UNIX_FD: - a = 4; - break; - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - case SD_BUS_TYPE_VARIANT: - a = 8; - break; - - case SD_BUS_TYPE_ARRAY: { - char t[n]; - - memcpy(t, p + 1, n - 1); - t[n - 1] = 0; - - a = bus_gvariant_get_alignment(t); - break; - } - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - char t[n-1]; - - memcpy(t, p + 1, n - 2); - t[n - 2] = 0; - - a = bus_gvariant_get_alignment(t); - break; - } - - default: - assert_not_reached("Unknown signature type"); - } - - if (a < 0) - return a; - - assert(a > 0 && a <= 8); - if ((size_t) a > alignment) - alignment = (size_t) a; - - p += n; - } - - return alignment; -} - -int bus_gvariant_is_fixed_size(const char *signature) { - const char *p; - int r; - - assert(signature); - - p = signature; - while (*p != 0) { - size_t n; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - - switch (*p) { - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: - case SD_BUS_TYPE_ARRAY: - case SD_BUS_TYPE_VARIANT: - return 0; - - case SD_BUS_TYPE_BYTE: - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_UNIX_FD: - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - break; - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - char t[n-1]; - - memcpy(t, p + 1, n - 2); - t[n - 2] = 0; - - r = bus_gvariant_is_fixed_size(t); - if (r <= 0) - return r; - break; - } - - default: - assert_not_reached("Unknown signature type"); - } - - p += n; - } - - return true; -} - -size_t bus_gvariant_determine_word_size(size_t sz, size_t extra) { - if (sz + extra <= 0xFF) - return 1; - else if (sz + extra*2 <= 0xFFFF) - return 2; - else if (sz + extra*4 <= 0xFFFFFFFF) - return 4; - else - return 8; -} - -size_t bus_gvariant_read_word_le(void *p, size_t sz) { - union { - uint16_t u16; - uint32_t u32; - uint64_t u64; - } x; - - assert(p); - - if (sz == 1) - return *(uint8_t*) p; - - memcpy(&x, p, sz); - - if (sz == 2) - return le16toh(x.u16); - else if (sz == 4) - return le32toh(x.u32); - else if (sz == 8) - return le64toh(x.u64); - - assert_not_reached("unknown word width"); -} - -void bus_gvariant_write_word_le(void *p, size_t sz, size_t value) { - union { - uint16_t u16; - uint32_t u32; - uint64_t u64; - } x; - - assert(p); - assert(sz == 8 || (value < (1ULL << (sz*8)))); - - if (sz == 1) { - *(uint8_t*) p = value; - return; - } else if (sz == 2) - x.u16 = htole16((uint16_t) value); - else if (sz == 4) - x.u32 = htole32((uint32_t) value); - else if (sz == 8) - x.u64 = htole64((uint64_t) value); - else - assert_not_reached("unknown word width"); - - memcpy(p, &x, sz); -} diff --git a/src/libsystemd/sd-bus/bus-gvariant.h b/src/libsystemd/sd-bus/bus-gvariant.h deleted file mode 100644 index 6da637fb05..0000000000 --- a/src/libsystemd/sd-bus/bus-gvariant.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "macro.h" - -int bus_gvariant_get_size(const char *signature) _pure_; -int bus_gvariant_get_alignment(const char *signature) _pure_; -int bus_gvariant_is_fixed_size(const char *signature) _pure_; - -size_t bus_gvariant_determine_word_size(size_t sz, size_t extra); -void bus_gvariant_write_word_le(void *p, size_t sz, size_t value); -size_t bus_gvariant_read_word_le(void *p, size_t sz); diff --git a/src/libsystemd/sd-bus/bus-internal.c b/src/libsystemd/sd-bus/bus-internal.c deleted file mode 100644 index caca679086..0000000000 --- a/src/libsystemd/sd-bus/bus-internal.c +++ /dev/null @@ -1,374 +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 . -***/ - -#include "alloc-util.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "hexdecoct.h" -#include "string-util.h" - -bool object_path_is_valid(const char *p) { - const char *q; - bool slash; - - if (!p) - return false; - - if (p[0] != '/') - return false; - - if (p[1] == 0) - return true; - - for (slash = true, q = p+1; *q; q++) - if (*q == '/') { - if (slash) - return false; - - slash = true; - } else { - bool good; - - good = - (*q >= 'a' && *q <= 'z') || - (*q >= 'A' && *q <= 'Z') || - (*q >= '0' && *q <= '9') || - *q == '_'; - - if (!good) - return false; - - slash = false; - } - - if (slash) - return false; - - return true; -} - -char* object_path_startswith(const char *a, const char *b) { - const char *p; - - if (!object_path_is_valid(a) || - !object_path_is_valid(b)) - return NULL; - - if (streq(b, "/")) - return (char*) a + 1; - - p = startswith(a, b); - if (!p) - return NULL; - - if (*p == 0) - return (char*) p; - - if (*p == '/') - return (char*) p + 1; - - return NULL; -} - -bool interface_name_is_valid(const char *p) { - const char *q; - bool dot, found_dot = false; - - if (isempty(p)) - return false; - - for (dot = true, q = p; *q; q++) - if (*q == '.') { - if (dot) - return false; - - found_dot = dot = true; - } else { - bool good; - - good = - (*q >= 'a' && *q <= 'z') || - (*q >= 'A' && *q <= 'Z') || - (!dot && *q >= '0' && *q <= '9') || - *q == '_'; - - if (!good) - return false; - - dot = false; - } - - if (q - p > 255) - return false; - - if (dot) - return false; - - if (!found_dot) - return false; - - return true; -} - -bool service_name_is_valid(const char *p) { - const char *q; - bool dot, found_dot = false, unique; - - if (isempty(p)) - return false; - - unique = p[0] == ':'; - - for (dot = true, q = unique ? p+1 : p; *q; q++) - if (*q == '.') { - if (dot) - return false; - - found_dot = dot = true; - } else { - bool good; - - good = - (*q >= 'a' && *q <= 'z') || - (*q >= 'A' && *q <= 'Z') || - ((!dot || unique) && *q >= '0' && *q <= '9') || - *q == '_' || *q == '-'; - - if (!good) - return false; - - dot = false; - } - - if (q - p > 255) - return false; - - if (dot) - return false; - - if (!found_dot) - return false; - - return true; -} - -char* service_name_startswith(const char *a, const char *b) { - const char *p; - - if (!service_name_is_valid(a) || - !service_name_is_valid(b)) - return NULL; - - p = startswith(a, b); - if (!p) - return NULL; - - if (*p == 0) - return (char*) p; - - if (*p == '.') - return (char*) p + 1; - - return NULL; -} - -bool member_name_is_valid(const char *p) { - const char *q; - - if (isempty(p)) - return false; - - for (q = p; *q; q++) { - bool good; - - good = - (*q >= 'a' && *q <= 'z') || - (*q >= 'A' && *q <= 'Z') || - (*q >= '0' && *q <= '9') || - *q == '_'; - - if (!good) - return false; - } - - if (q - p > 255) - return false; - - return true; -} - -/* - * Complex pattern match - * This checks whether @a is a 'complex-prefix' of @b, or @b is a - * 'complex-prefix' of @a, based on strings that consist of labels with @c as - * spearator. This function returns true if: - * - both strings are equal - * - either is a prefix of the other and ends with @c - * The second rule makes sure that either string needs to be fully included in - * the other, and the string which is considered the prefix needs to end with a - * separator. - */ -static bool complex_pattern_check(char c, const char *a, const char *b) { - bool separator = false; - - if (!a && !b) - return true; - - if (!a || !b) - return false; - - for (;;) { - if (*a != *b) - return (separator && (*a == 0 || *b == 0)); - - if (*a == 0) - return true; - - separator = *a == c; - - a++, b++; - } -} - -bool namespace_complex_pattern(const char *pattern, const char *value) { - return complex_pattern_check('.', pattern, value); -} - -bool path_complex_pattern(const char *pattern, const char *value) { - return complex_pattern_check('/', pattern, value); -} - -/* - * Simple pattern match - * This checks whether @a is a 'simple-prefix' of @b, based on strings that - * consist of labels with @c as separator. This function returns true, if: - * - if @a and @b are equal - * - if @a is a prefix of @b, and the first following character in @b (or the - * last character in @a) is @c - * The second rule basically makes sure that if @a is a prefix of @b, then @b - * must follow with a new label separated by @c. It cannot extend the label. - */ -static bool simple_pattern_check(char c, const char *a, const char *b) { - bool separator = false; - - if (!a && !b) - return true; - - if (!a || !b) - return false; - - for (;;) { - if (*a != *b) - return *a == 0 && (*b == c || separator); - - if (*a == 0) - return true; - - separator = *a == c; - - a++, b++; - } -} - -bool namespace_simple_pattern(const char *pattern, const char *value) { - return simple_pattern_check('.', pattern, value); -} - -bool path_simple_pattern(const char *pattern, const char *value) { - return simple_pattern_check('/', pattern, value); -} - -int bus_message_type_from_string(const char *s, uint8_t *u) { - if (streq(s, "signal")) - *u = SD_BUS_MESSAGE_SIGNAL; - else if (streq(s, "method_call")) - *u = SD_BUS_MESSAGE_METHOD_CALL; - else if (streq(s, "error")) - *u = SD_BUS_MESSAGE_METHOD_ERROR; - else if (streq(s, "method_return")) - *u = SD_BUS_MESSAGE_METHOD_RETURN; - else - return -EINVAL; - - return 0; -} - -const char *bus_message_type_to_string(uint8_t u) { - if (u == SD_BUS_MESSAGE_SIGNAL) - return "signal"; - else if (u == SD_BUS_MESSAGE_METHOD_CALL) - return "method_call"; - else if (u == SD_BUS_MESSAGE_METHOD_ERROR) - return "error"; - else if (u == SD_BUS_MESSAGE_METHOD_RETURN) - return "method_return"; - else - return NULL; -} - -char *bus_address_escape(const char *v) { - const char *a; - char *r, *b; - - r = new(char, strlen(v)*3+1); - if (!r) - return NULL; - - for (a = v, b = r; *a; a++) { - - if ((*a >= '0' && *a <= '9') || - (*a >= 'a' && *a <= 'z') || - (*a >= 'A' && *a <= 'Z') || - strchr("_-/.", *a)) - *(b++) = *a; - else { - *(b++) = '%'; - *(b++) = hexchar(*a >> 4); - *(b++) = hexchar(*a & 0xF); - } - } - - *b = 0; - return r; -} - -int bus_maybe_reply_error(sd_bus_message *m, int r, sd_bus_error *error) { - assert(m); - - if (r < 0) { - if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) - sd_bus_reply_method_errno(m, r, error); - - } else if (sd_bus_error_is_set(error)) { - if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) - sd_bus_reply_method_error(m, error); - } else - return r; - - log_debug("Failed to process message [type=%s sender=%s path=%s interface=%s member=%s signature=%s]: %s", - bus_message_type_to_string(m->header->type), - strna(m->sender), - strna(m->path), - strna(m->interface), - strna(m->member), - strna(m->root_container.signature), - bus_error_message(error, r)); - - return 1; -} diff --git a/src/libsystemd/sd-bus/bus-internal.h b/src/libsystemd/sd-bus/bus-internal.h deleted file mode 100644 index 216d9f62bc..0000000000 --- a/src/libsystemd/sd-bus/bus-internal.h +++ /dev/null @@ -1,399 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include -#include - -#include "sd-bus.h" - -#include "bus-error.h" -#include "bus-kernel.h" -#include "bus-match.h" -#include "hashmap.h" -#include "kdbus.h" -#include "list.h" -#include "prioq.h" -#include "refcnt.h" -#include "socket-util.h" -#include "util.h" - -struct reply_callback { - sd_bus_message_handler_t callback; - usec_t timeout; - uint64_t cookie; - unsigned prioq_idx; -}; - -struct filter_callback { - sd_bus_message_handler_t callback; - - unsigned last_iteration; - - LIST_FIELDS(struct filter_callback, callbacks); -}; - -struct match_callback { - sd_bus_message_handler_t callback; - - uint64_t cookie; - unsigned last_iteration; - - char *match_string; - - struct bus_match_node *match_node; -}; - -struct node { - char *path; - struct node *parent; - LIST_HEAD(struct node, child); - LIST_FIELDS(struct node, siblings); - - LIST_HEAD(struct node_callback, callbacks); - LIST_HEAD(struct node_vtable, vtables); - LIST_HEAD(struct node_enumerator, enumerators); - LIST_HEAD(struct node_object_manager, object_managers); -}; - -struct node_callback { - struct node *node; - - bool is_fallback; - sd_bus_message_handler_t callback; - - unsigned last_iteration; - - LIST_FIELDS(struct node_callback, callbacks); -}; - -struct node_enumerator { - struct node *node; - - sd_bus_node_enumerator_t callback; - - unsigned last_iteration; - - LIST_FIELDS(struct node_enumerator, enumerators); -}; - -struct node_object_manager { - struct node *node; - - LIST_FIELDS(struct node_object_manager, object_managers); -}; - -struct node_vtable { - struct node *node; - - char *interface; - bool is_fallback; - const sd_bus_vtable *vtable; - sd_bus_object_find_t find; - - unsigned last_iteration; - - LIST_FIELDS(struct node_vtable, vtables); -}; - -struct vtable_member { - const char *path; - const char *interface; - const char *member; - struct node_vtable *parent; - unsigned last_iteration; - const sd_bus_vtable *vtable; -}; - -typedef enum BusSlotType { - BUS_REPLY_CALLBACK, - BUS_FILTER_CALLBACK, - BUS_MATCH_CALLBACK, - BUS_NODE_CALLBACK, - BUS_NODE_ENUMERATOR, - BUS_NODE_VTABLE, - BUS_NODE_OBJECT_MANAGER, - _BUS_SLOT_INVALID = -1, -} BusSlotType; - -struct sd_bus_slot { - unsigned n_ref; - sd_bus *bus; - void *userdata; - BusSlotType type:5; - bool floating:1; - bool match_added:1; - char *description; - - LIST_FIELDS(sd_bus_slot, slots); - - union { - struct reply_callback reply_callback; - struct filter_callback filter_callback; - struct match_callback match_callback; - struct node_callback node_callback; - struct node_enumerator node_enumerator; - struct node_object_manager node_object_manager; - struct node_vtable node_vtable; - }; -}; - -enum bus_state { - BUS_UNSET, - BUS_OPENING, - BUS_AUTHENTICATING, - BUS_HELLO, - BUS_RUNNING, - BUS_CLOSING, - BUS_CLOSED -}; - -static inline bool BUS_IS_OPEN(enum bus_state state) { - return state > BUS_UNSET && state < BUS_CLOSING; -} - -enum bus_auth { - _BUS_AUTH_INVALID, - BUS_AUTH_EXTERNAL, - BUS_AUTH_ANONYMOUS -}; - -struct sd_bus { - /* We use atomic ref counting here since sd_bus_message - objects retain references to their originating sd_bus but - we want to allow them to be processed in a different - thread. We won't provide full thread safety, but only the - bare minimum that makes it possible to use sd_bus and - sd_bus_message objects independently and on different - threads as long as each object is used only once at the - same time. */ - RefCount n_ref; - - enum bus_state state; - int input_fd, output_fd; - int message_version; - int message_endian; - - bool is_kernel:1; - bool can_fds:1; - bool bus_client:1; - bool ucred_valid:1; - bool is_server:1; - bool anonymous_auth:1; - bool prefer_readv:1; - bool prefer_writev:1; - bool match_callbacks_modified:1; - bool filter_callbacks_modified:1; - bool nodes_modified:1; - bool trusted:1; - bool fake_creds_valid:1; - bool fake_pids_valid:1; - bool manual_peer_interface:1; - bool is_system:1; - bool is_user:1; - bool allow_interactive_authorization:1; - - int use_memfd; - - void *rbuffer; - size_t rbuffer_size; - - sd_bus_message **rqueue; - unsigned rqueue_size; - size_t rqueue_allocated; - - sd_bus_message **wqueue; - unsigned wqueue_size; - size_t windex; - size_t wqueue_allocated; - - uint64_t cookie; - - char *unique_name; - uint64_t unique_id; - - struct bus_match_node match_callbacks; - Prioq *reply_callbacks_prioq; - OrderedHashmap *reply_callbacks; - LIST_HEAD(struct filter_callback, filter_callbacks); - - Hashmap *nodes; - Hashmap *vtable_methods; - Hashmap *vtable_properties; - - union sockaddr_union sockaddr; - socklen_t sockaddr_size; - - char *kernel; - char *machine; - pid_t nspid; - - sd_id128_t server_id; - - char *address; - unsigned address_index; - - int last_connect_error; - - enum bus_auth auth; - size_t auth_rbegin; - struct iovec auth_iovec[3]; - unsigned auth_index; - char *auth_buffer; - usec_t auth_timeout; - - struct ucred ucred; - char *label; - - uint64_t creds_mask; - - int *fds; - unsigned n_fds; - - char *exec_path; - char **exec_argv; - - unsigned iteration_counter; - - void *kdbus_buffer; - - /* We do locking around the memfd cache, since we want to - * allow people to process a sd_bus_message in a different - * thread then it was generated on and free it there. Since - * adding something to the memfd cache might happen when a - * message is released, we hence need to protect this bit with - * a mutex. */ - pthread_mutex_t memfd_cache_mutex; - struct memfd_cache memfd_cache[MEMFD_CACHE_MAX]; - unsigned n_memfd_cache; - - pid_t original_pid; - - uint64_t hello_flags; - uint64_t attach_flags; - - uint64_t match_cookie; - - sd_event_source *input_io_event_source; - sd_event_source *output_io_event_source; - sd_event_source *time_event_source; - sd_event_source *quit_event_source; - sd_event *event; - int event_priority; - - sd_bus_message *current_message; - sd_bus_slot *current_slot; - sd_bus_message_handler_t current_handler; - void *current_userdata; - - sd_bus **default_bus_ptr; - pid_t tid; - - struct kdbus_creds fake_creds; - struct kdbus_pids fake_pids; - char *fake_label; - - char *cgroup_root; - - char *description; - - size_t bloom_size; - unsigned bloom_n_hash; - - sd_bus_track *track_queue; - - LIST_HEAD(sd_bus_slot, slots); -}; - -#define BUS_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC)) - -#define BUS_WQUEUE_MAX 1024 -#define BUS_RQUEUE_MAX 64*1024 - -#define BUS_MESSAGE_SIZE_MAX (64*1024*1024) -#define BUS_AUTH_SIZE_MAX (64*1024) - -#define BUS_CONTAINER_DEPTH 128 - -/* Defined by the specification as maximum size of an array in - * bytes */ -#define BUS_ARRAY_MAX_SIZE 67108864 - -#define BUS_FDS_MAX 1024 - -#define BUS_EXEC_ARGV_MAX 256 - -bool interface_name_is_valid(const char *p) _pure_; -bool service_name_is_valid(const char *p) _pure_; -char* service_name_startswith(const char *a, const char *b); -bool member_name_is_valid(const char *p) _pure_; -bool object_path_is_valid(const char *p) _pure_; -char *object_path_startswith(const char *a, const char *b) _pure_; - -bool namespace_complex_pattern(const char *pattern, const char *value) _pure_; -bool path_complex_pattern(const char *pattern, const char *value) _pure_; - -bool namespace_simple_pattern(const char *pattern, const char *value) _pure_; -bool path_simple_pattern(const char *pattern, const char *value) _pure_; - -int bus_message_type_from_string(const char *s, uint8_t *u) _pure_; -const char *bus_message_type_to_string(uint8_t u) _pure_; - -#define error_name_is_valid interface_name_is_valid - -int bus_ensure_running(sd_bus *bus); -int bus_start_running(sd_bus *bus); -int bus_next_address(sd_bus *bus); - -int bus_seal_synthetic_message(sd_bus *b, sd_bus_message *m); - -int bus_rqueue_make_room(sd_bus *bus); - -bool bus_pid_changed(sd_bus *bus); - -char *bus_address_escape(const char *v); - -#define OBJECT_PATH_FOREACH_PREFIX(prefix, path) \ - for (char *_slash = ({ strcpy((prefix), (path)); streq((prefix), "/") ? NULL : strrchr((prefix), '/'); }) ; \ - _slash && !(_slash[(_slash) == (prefix)] = 0); \ - _slash = streq((prefix), "/") ? NULL : strrchr((prefix), '/')) - -/* If we are invoking callbacks of a bus object, ensure unreffing the - * bus from the callback doesn't destroy the object we are working - * on */ -#define BUS_DONT_DESTROY(bus) \ - _cleanup_(sd_bus_unrefp) _unused_ sd_bus *_dont_destroy_##bus = sd_bus_ref(bus) - -int bus_set_address_system(sd_bus *bus); -int bus_set_address_user(sd_bus *bus); -int bus_set_address_system_remote(sd_bus *b, const char *host); -int bus_set_address_system_machine(sd_bus *b, const char *machine); - -int bus_remove_match_by_string(sd_bus *bus, const char *match, sd_bus_message_handler_t callback, void *userdata); - -int bus_get_root_path(sd_bus *bus); - -int bus_maybe_reply_error(sd_bus_message *m, int r, sd_bus_error *error); - -#define bus_assert_return(expr, r, error) \ - do { \ - if (!assert_log(expr, #expr)) \ - return sd_bus_error_set_errno(error, r); \ - } while (false) diff --git a/src/libsystemd/sd-bus/bus-introspect.c b/src/libsystemd/sd-bus/bus-introspect.c deleted file mode 100644 index 8f93edb8da..0000000000 --- a/src/libsystemd/sd-bus/bus-introspect.c +++ /dev/null @@ -1,212 +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 . -***/ - -#include "bus-internal.h" -#include "bus-introspect.h" -#include "bus-protocol.h" -#include "bus-signature.h" -#include "fd-util.h" -#include "fileio.h" -#include "string-util.h" -#include "util.h" - -int introspect_begin(struct introspect *i, bool trusted) { - assert(i); - - zero(*i); - i->trusted = trusted; - - i->f = open_memstream(&i->introspection, &i->size); - if (!i->f) - return -ENOMEM; - - fputs(BUS_INTROSPECT_DOCTYPE - "\n", i->f); - - return 0; -} - -int introspect_write_default_interfaces(struct introspect *i, bool object_manager) { - assert(i); - - fputs(BUS_INTROSPECT_INTERFACE_PEER - BUS_INTROSPECT_INTERFACE_INTROSPECTABLE - BUS_INTROSPECT_INTERFACE_PROPERTIES, i->f); - - if (object_manager) - fputs(BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER, i->f); - - return 0; -} - -int introspect_write_child_nodes(struct introspect *i, Set *s, const char *prefix) { - char *node; - - assert(i); - assert(prefix); - - while ((node = set_steal_first(s))) { - const char *e; - - e = object_path_startswith(node, prefix); - if (e && e[0]) - fprintf(i->f, " \n", e); - - free(node); - } - - return 0; -} - -static void introspect_write_flags(struct introspect *i, int type, int flags) { - if (flags & SD_BUS_VTABLE_DEPRECATED) - fputs(" \n", i->f); - - if (type == _SD_BUS_VTABLE_METHOD && (flags & SD_BUS_VTABLE_METHOD_NO_REPLY)) - fputs(" \n", i->f); - - if (type == _SD_BUS_VTABLE_PROPERTY || type == _SD_BUS_VTABLE_WRITABLE_PROPERTY) { - if (flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT) - fputs(" \n", i->f); - - if (flags & SD_BUS_VTABLE_PROPERTY_CONST) - fputs(" \n", i->f); - else if (flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) - fputs(" \n", i->f); - else if (!(flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) - fputs(" \n", i->f); - } - - if (!i->trusted && - (type == _SD_BUS_VTABLE_METHOD || type == _SD_BUS_VTABLE_WRITABLE_PROPERTY) && - !(flags & SD_BUS_VTABLE_UNPRIVILEGED)) - fputs(" \n", i->f); -} - -static int introspect_write_arguments(struct introspect *i, const char *signature, const char *direction) { - int r; - - for (;;) { - size_t l; - - if (!*signature) - return 0; - - r = signature_element_length(signature, &l); - if (r < 0) - return r; - - fprintf(i->f, " f, " direction=\"%s\"/>\n", direction); - else - fputs("/>\n", i->f); - - signature += l; - } -} - -int introspect_write_interface(struct introspect *i, const sd_bus_vtable *v) { - assert(i); - assert(v); - - for (; v->type != _SD_BUS_VTABLE_END; v++) { - - /* Ignore methods, signals and properties that are - * marked "hidden", but do show the interface - * itself */ - - if (v->type != _SD_BUS_VTABLE_START && (v->flags & SD_BUS_VTABLE_HIDDEN)) - continue; - - switch (v->type) { - - case _SD_BUS_VTABLE_START: - if (v->flags & SD_BUS_VTABLE_DEPRECATED) - fputs(" \n", i->f); - break; - - case _SD_BUS_VTABLE_METHOD: - fprintf(i->f, " \n", v->x.method.member); - introspect_write_arguments(i, strempty(v->x.method.signature), "in"); - introspect_write_arguments(i, strempty(v->x.method.result), "out"); - introspect_write_flags(i, v->type, v->flags); - fputs(" \n", i->f); - break; - - case _SD_BUS_VTABLE_PROPERTY: - case _SD_BUS_VTABLE_WRITABLE_PROPERTY: - fprintf(i->f, " \n", - v->x.property.member, - v->x.property.signature, - v->type == _SD_BUS_VTABLE_WRITABLE_PROPERTY ? "readwrite" : "read"); - introspect_write_flags(i, v->type, v->flags); - fputs(" \n", i->f); - break; - - case _SD_BUS_VTABLE_SIGNAL: - fprintf(i->f, " \n", v->x.signal.member); - introspect_write_arguments(i, strempty(v->x.signal.signature), NULL); - introspect_write_flags(i, v->type, v->flags); - fputs(" \n", i->f); - break; - } - - } - - return 0; -} - -int introspect_finish(struct introspect *i, sd_bus *bus, sd_bus_message *m, sd_bus_message **reply) { - sd_bus_message *q; - int r; - - assert(i); - assert(m); - assert(reply); - - fputs("\n", i->f); - - r = fflush_and_check(i->f); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(m, &q); - if (r < 0) - return r; - - r = sd_bus_message_append(q, "s", i->introspection); - if (r < 0) { - sd_bus_message_unref(q); - return r; - } - - *reply = q; - return 0; -} - -void introspect_free(struct introspect *i) { - assert(i); - - safe_fclose(i->f); - - free(i->introspection); - zero(*i); -} diff --git a/src/libsystemd/sd-bus/bus-introspect.h b/src/libsystemd/sd-bus/bus-introspect.h deleted file mode 100644 index 8e2f3800ca..0000000000 --- a/src/libsystemd/sd-bus/bus-introspect.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -#include "sd-bus.h" - -#include "set.h" - -struct introspect { - FILE *f; - char *introspection; - size_t size; - bool trusted; -}; - -int introspect_begin(struct introspect *i, bool trusted); -int introspect_write_default_interfaces(struct introspect *i, bool object_manager); -int introspect_write_child_nodes(struct introspect *i, Set *s, const char *prefix); -int introspect_write_interface(struct introspect *i, const sd_bus_vtable *v); -int introspect_finish(struct introspect *i, sd_bus *bus, sd_bus_message *m, sd_bus_message **reply); -void introspect_free(struct introspect *i); diff --git a/src/libsystemd/sd-bus/bus-kernel.c b/src/libsystemd/sd-bus/bus-kernel.c deleted file mode 100644 index 0896eeb177..0000000000 --- a/src/libsystemd/sd-bus/bus-kernel.c +++ /dev/null @@ -1,1826 +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 . -***/ - -#ifdef HAVE_VALGRIND_MEMCHECK_H -#include -#endif - -#include -#include -#include -#include - -/* When we include libgen.h because we need dirname() we immediately - * undefine basename() since libgen.h defines it as a macro to the POSIX - * version which is really broken. We prefer GNU basename(). */ -#include -#undef basename - -#include "alloc-util.h" -#include "bus-bloom.h" -#include "bus-internal.h" -#include "bus-kernel.h" -#include "bus-label.h" -#include "bus-message.h" -#include "bus-util.h" -#include "capability-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "memfd-util.h" -#include "parse-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -#define UNIQUE_NAME_MAX (3+DECIMAL_STR_MAX(uint64_t)) - -int bus_kernel_parse_unique_name(const char *s, uint64_t *id) { - int r; - - assert(s); - assert(id); - - if (!startswith(s, ":1.")) - return 0; - - r = safe_atou64(s + 3, id); - if (r < 0) - return r; - - return 1; -} - -static void append_payload_vec(struct kdbus_item **d, const void *p, size_t sz) { - assert(d); - assert(sz > 0); - - *d = ALIGN8_PTR(*d); - - /* Note that p can be NULL, which encodes a region full of - * zeroes, which is useful to optimize certain padding - * conditions */ - - (*d)->size = offsetof(struct kdbus_item, vec) + sizeof(struct kdbus_vec); - (*d)->type = KDBUS_ITEM_PAYLOAD_VEC; - (*d)->vec.address = PTR_TO_UINT64(p); - (*d)->vec.size = sz; - - *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); -} - -static void append_payload_memfd(struct kdbus_item **d, int memfd, size_t start, size_t sz) { - assert(d); - assert(memfd >= 0); - assert(sz > 0); - - *d = ALIGN8_PTR(*d); - (*d)->size = offsetof(struct kdbus_item, memfd) + sizeof(struct kdbus_memfd); - (*d)->type = KDBUS_ITEM_PAYLOAD_MEMFD; - (*d)->memfd.fd = memfd; - (*d)->memfd.start = start; - (*d)->memfd.size = sz; - - *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); -} - -static void append_destination(struct kdbus_item **d, const char *s, size_t length) { - assert(d); - assert(s); - - *d = ALIGN8_PTR(*d); - - (*d)->size = offsetof(struct kdbus_item, str) + length + 1; - (*d)->type = KDBUS_ITEM_DST_NAME; - memcpy((*d)->str, s, length + 1); - - *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); -} - -static struct kdbus_bloom_filter *append_bloom(struct kdbus_item **d, size_t length) { - struct kdbus_item *i; - - assert(d); - - i = ALIGN8_PTR(*d); - - i->size = offsetof(struct kdbus_item, bloom_filter) + - offsetof(struct kdbus_bloom_filter, data) + - length; - i->type = KDBUS_ITEM_BLOOM_FILTER; - - *d = (struct kdbus_item *) ((uint8_t*) i + i->size); - - return &i->bloom_filter; -} - -static void append_fds(struct kdbus_item **d, const int fds[], unsigned n_fds) { - assert(d); - assert(fds); - assert(n_fds > 0); - - *d = ALIGN8_PTR(*d); - (*d)->size = offsetof(struct kdbus_item, fds) + sizeof(int) * n_fds; - (*d)->type = KDBUS_ITEM_FDS; - memcpy((*d)->fds, fds, sizeof(int) * n_fds); - - *d = (struct kdbus_item *) ((uint8_t*) *d + (*d)->size); -} - -static void add_bloom_arg(void *data, size_t size, unsigned n_hash, unsigned i, const char *t) { - char buf[sizeof("arg")-1 + 2 + sizeof("-slash-prefix")]; - char *e; - - assert(data); - assert(size > 0); - assert(i < 64); - assert(t); - - e = stpcpy(buf, "arg"); - if (i < 10) - *(e++) = '0' + (char) i; - else { - *(e++) = '0' + (char) (i / 10); - *(e++) = '0' + (char) (i % 10); - } - - *e = 0; - bloom_add_pair(data, size, n_hash, buf, t); - - strcpy(e, "-dot-prefix"); - bloom_add_prefixes(data, size, n_hash, buf, t, '.'); - strcpy(e, "-slash-prefix"); - bloom_add_prefixes(data, size, n_hash, buf, t, '/'); -} - -static void add_bloom_arg_has(void *data, size_t size, unsigned n_hash, unsigned i, const char *t) { - char buf[sizeof("arg")-1 + 2 + sizeof("-has")]; - char *e; - - assert(data); - assert(size > 0); - assert(i < 64); - assert(t); - - e = stpcpy(buf, "arg"); - if (i < 10) - *(e++) = '0' + (char) i; - else { - *(e++) = '0' + (char) (i / 10); - *(e++) = '0' + (char) (i % 10); - } - - strcpy(e, "-has"); - bloom_add_pair(data, size, n_hash, buf, t); -} - -static int bus_message_setup_bloom(sd_bus_message *m, struct kdbus_bloom_filter *bloom) { - void *data; - unsigned i; - int r; - - assert(m); - assert(bloom); - - data = bloom->data; - memzero(data, m->bus->bloom_size); - bloom->generation = 0; - - bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "message-type", bus_message_type_to_string(m->header->type)); - - if (m->interface) - bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "interface", m->interface); - if (m->member) - bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "member", m->member); - if (m->path) { - bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "path", m->path); - bloom_add_pair(data, m->bus->bloom_size, m->bus->bloom_n_hash, "path-slash-prefix", m->path); - bloom_add_prefixes(data, m->bus->bloom_size, m->bus->bloom_n_hash, "path-slash-prefix", m->path, '/'); - } - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - for (i = 0; i < 64; i++) { - const char *t, *contents; - char type; - - r = sd_bus_message_peek_type(m, &type, &contents); - if (r < 0) - return r; - - if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE)) { - - /* The bloom filter includes simple strings of any kind */ - r = sd_bus_message_read_basic(m, type, &t); - if (r < 0) - return r; - - add_bloom_arg(data, m->bus->bloom_size, m->bus->bloom_n_hash, i, t); - } - - if (type == SD_BUS_TYPE_ARRAY && STR_IN_SET(contents, "s", "o", "g")) { - - /* As well as array of simple strings of any kinds */ - r = sd_bus_message_enter_container(m, type, contents); - if (r < 0) - return r; - - while ((r = sd_bus_message_read_basic(m, contents[0], &t)) > 0) - add_bloom_arg_has(data, m->bus->bloom_size, m->bus->bloom_n_hash, i, t); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - } else - /* Stop adding to bloom filter as soon as we - * run into the first argument we cannot add - * to it. */ - break; - } - - return 0; -} - -static int bus_message_setup_kmsg(sd_bus *b, sd_bus_message *m) { - struct bus_body_part *part; - struct kdbus_item *d; - const char *destination; - bool well_known = false; - uint64_t dst_id; - size_t sz, dl; - unsigned i; - int r; - - assert(b); - assert(m); - assert(m->sealed); - - /* We put this together only once, if this message is reused - * we reuse the earlier-built version */ - if (m->kdbus) - return 0; - - destination = m->destination ?: m->destination_ptr; - - if (destination) { - r = bus_kernel_parse_unique_name(destination, &dst_id); - if (r < 0) - return r; - if (r == 0) { - well_known = true; - - /* verify_destination_id will usually be 0, which makes the kernel - * driver only look at the provided well-known name. Otherwise, - * the kernel will make sure the provided destination id matches - * the owner of the provided well-known-name, and fail if they - * differ. Currently, this is only needed for bus-proxyd. */ - dst_id = m->verify_destination_id; - } - } else - dst_id = KDBUS_DST_ID_BROADCAST; - - sz = offsetof(struct kdbus_msg, items); - - /* Add in fixed header, fields header and payload */ - sz += (1 + m->n_body_parts) * ALIGN8(offsetof(struct kdbus_item, vec) + - MAX(sizeof(struct kdbus_vec), - sizeof(struct kdbus_memfd))); - - /* Add space for bloom filter */ - sz += ALIGN8(offsetof(struct kdbus_item, bloom_filter) + - offsetof(struct kdbus_bloom_filter, data) + - m->bus->bloom_size); - - /* Add in well-known destination header */ - if (well_known) { - dl = strlen(destination); - sz += ALIGN8(offsetof(struct kdbus_item, str) + dl + 1); - } - - /* Add space for unix fds */ - if (m->n_fds > 0) - sz += ALIGN8(offsetof(struct kdbus_item, fds) + sizeof(int)*m->n_fds); - - m->kdbus = memalign(8, sz); - if (!m->kdbus) { - r = -ENOMEM; - goto fail; - } - - m->free_kdbus = true; - memzero(m->kdbus, sz); - - m->kdbus->flags = - ((m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) ? 0 : KDBUS_MSG_EXPECT_REPLY) | - ((m->header->flags & BUS_MESSAGE_NO_AUTO_START) ? KDBUS_MSG_NO_AUTO_START : 0) | - ((m->header->type == SD_BUS_MESSAGE_SIGNAL) ? KDBUS_MSG_SIGNAL : 0); - - m->kdbus->dst_id = dst_id; - m->kdbus->payload_type = KDBUS_PAYLOAD_DBUS; - m->kdbus->cookie = m->header->dbus2.cookie; - m->kdbus->priority = m->priority; - - if (m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - m->kdbus->cookie_reply = m->reply_cookie; - else { - struct timespec now; - - assert_se(clock_gettime(CLOCK_MONOTONIC_COARSE, &now) == 0); - m->kdbus->timeout_ns = now.tv_sec * NSEC_PER_SEC + now.tv_nsec + - m->timeout * NSEC_PER_USEC; - } - - d = m->kdbus->items; - - if (well_known) - append_destination(&d, destination, dl); - - append_payload_vec(&d, m->header, BUS_MESSAGE_BODY_BEGIN(m)); - - MESSAGE_FOREACH_PART(part, i, m) { - if (part->is_zero) { - /* If this is padding then simply send a - * vector with a NULL data pointer which the - * kernel will just pass through. This is the - * most efficient way to encode zeroes */ - - append_payload_vec(&d, NULL, part->size); - continue; - } - - if (part->memfd >= 0 && part->sealed && destination) { - /* Try to send a memfd, if the part is - * sealed and this is not a broadcast. Since we can only */ - - append_payload_memfd(&d, part->memfd, part->memfd_offset, part->size); - continue; - } - - /* Otherwise, let's send a vector to the actual data. - * For that, we need to map it first. */ - r = bus_body_part_map(part); - if (r < 0) - goto fail; - - append_payload_vec(&d, part->data, part->size); - } - - if (m->header->type == SD_BUS_MESSAGE_SIGNAL) { - struct kdbus_bloom_filter *bloom; - - bloom = append_bloom(&d, m->bus->bloom_size); - r = bus_message_setup_bloom(m, bloom); - if (r < 0) - goto fail; - } - - if (m->n_fds > 0) - append_fds(&d, m->fds, m->n_fds); - - m->kdbus->size = (uint8_t*) d - (uint8_t*) m->kdbus; - assert(m->kdbus->size <= sz); - - return 0; - -fail: - m->poisoned = true; - return r; -} - -static void unset_memfds(struct sd_bus_message *m) { - struct bus_body_part *part; - unsigned i; - - assert(m); - - /* Make sure the memfds are not freed twice */ - MESSAGE_FOREACH_PART(part, i, m) - if (part->memfd >= 0) - part->memfd = -1; -} - -static void message_set_timestamp(sd_bus *bus, sd_bus_message *m, const struct kdbus_timestamp *ts) { - assert(bus); - assert(m); - - if (!ts) - return; - - if (!(bus->attach_flags & KDBUS_ATTACH_TIMESTAMP)) - return; - - m->realtime = ts->realtime_ns / NSEC_PER_USEC; - m->monotonic = ts->monotonic_ns / NSEC_PER_USEC; - m->seqnum = ts->seqnum; -} - -static int bus_kernel_make_message(sd_bus *bus, struct kdbus_msg *k) { - sd_bus_message *m = NULL; - struct kdbus_item *d; - unsigned n_fds = 0; - _cleanup_free_ int *fds = NULL; - struct bus_header *header = NULL; - void *footer = NULL; - size_t header_size = 0, footer_size = 0; - size_t n_bytes = 0, idx = 0; - const char *destination = NULL, *seclabel = NULL; - bool last_was_memfd = false; - int r; - - assert(bus); - assert(k); - assert(k->payload_type == KDBUS_PAYLOAD_DBUS); - - KDBUS_ITEM_FOREACH(d, k, items) { - size_t l; - - l = d->size - offsetof(struct kdbus_item, data); - - switch (d->type) { - - case KDBUS_ITEM_PAYLOAD_OFF: - if (!header) { - header = (struct bus_header*)((uint8_t*) k + d->vec.offset); - header_size = d->vec.size; - } - - footer = (uint8_t*) k + d->vec.offset; - footer_size = d->vec.size; - - n_bytes += d->vec.size; - last_was_memfd = false; - break; - - case KDBUS_ITEM_PAYLOAD_MEMFD: - if (!header) /* memfd cannot be first part */ - return -EBADMSG; - - n_bytes += d->memfd.size; - last_was_memfd = true; - break; - - case KDBUS_ITEM_FDS: { - int *f; - unsigned j; - - j = l / sizeof(int); - f = realloc(fds, sizeof(int) * (n_fds + j)); - if (!f) - return -ENOMEM; - - fds = f; - memcpy(fds + n_fds, d->fds, sizeof(int) * j); - n_fds += j; - break; - } - - case KDBUS_ITEM_SECLABEL: - seclabel = d->str; - break; - } - } - - if (last_was_memfd) /* memfd cannot be last part */ - return -EBADMSG; - - if (!header) - return -EBADMSG; - - if (header_size < sizeof(struct bus_header)) - return -EBADMSG; - - /* on kdbus we only speak native endian gvariant, never dbus1 - * marshalling or reverse endian */ - if (header->version != 2 || - header->endian != BUS_NATIVE_ENDIAN) - return -EPROTOTYPE; - - r = bus_message_from_header( - bus, - header, header_size, - footer, footer_size, - n_bytes, - fds, n_fds, - seclabel, 0, &m); - if (r < 0) - return r; - - /* The well-known names list is different from the other - credentials. If we asked for it, but nothing is there, this - means that the list of well-known names is simply empty, not - that we lack any data */ - - m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask; - - KDBUS_ITEM_FOREACH(d, k, items) { - size_t l; - - l = d->size - offsetof(struct kdbus_item, data); - - switch (d->type) { - - case KDBUS_ITEM_PAYLOAD_OFF: { - size_t begin_body; - - begin_body = BUS_MESSAGE_BODY_BEGIN(m); - - if (idx + d->vec.size > begin_body) { - struct bus_body_part *part; - - /* Contains body material */ - - part = message_append_part(m); - if (!part) { - r = -ENOMEM; - goto fail; - } - - /* A -1 offset is NUL padding. */ - part->is_zero = d->vec.offset == ~0ULL; - - if (idx >= begin_body) { - if (!part->is_zero) - part->data = (uint8_t* )k + d->vec.offset; - part->size = d->vec.size; - } else { - if (!part->is_zero) - part->data = (uint8_t*) k + d->vec.offset + (begin_body - idx); - part->size = d->vec.size - (begin_body - idx); - } - - part->sealed = true; - } - - idx += d->vec.size; - break; - } - - case KDBUS_ITEM_PAYLOAD_MEMFD: { - struct bus_body_part *part; - - if (idx < BUS_MESSAGE_BODY_BEGIN(m)) { - r = -EBADMSG; - goto fail; - } - - part = message_append_part(m); - if (!part) { - r = -ENOMEM; - goto fail; - } - - part->memfd = d->memfd.fd; - part->memfd_offset = d->memfd.start; - part->size = d->memfd.size; - part->sealed = true; - - idx += d->memfd.size; - break; - } - - case KDBUS_ITEM_PIDS: - - /* The PID/TID might be missing, when the data - * is faked by a bus proxy and it lacks that - * information about the real client (since - * SO_PEERCRED is used for that). Also kernel - * namespacing might make some of this data - * unavailable when untranslatable. */ - - if (d->pids.pid > 0) { - m->creds.pid = (pid_t) d->pids.pid; - m->creds.mask |= SD_BUS_CREDS_PID & bus->creds_mask; - } - - if (d->pids.tid > 0) { - m->creds.tid = (pid_t) d->pids.tid; - m->creds.mask |= SD_BUS_CREDS_TID & bus->creds_mask; - } - - if (d->pids.ppid > 0) { - m->creds.ppid = (pid_t) d->pids.ppid; - m->creds.mask |= SD_BUS_CREDS_PPID & bus->creds_mask; - } else if (d->pids.pid == 1) { - m->creds.ppid = 0; - m->creds.mask |= SD_BUS_CREDS_PPID & bus->creds_mask; - } - - break; - - case KDBUS_ITEM_CREDS: - - /* EUID/SUID/FSUID/EGID/SGID/FSGID might be - * missing too (see above). */ - - if ((uid_t) d->creds.uid != UID_INVALID) { - m->creds.uid = (uid_t) d->creds.uid; - m->creds.mask |= SD_BUS_CREDS_UID & bus->creds_mask; - } - - if ((uid_t) d->creds.euid != UID_INVALID) { - m->creds.euid = (uid_t) d->creds.euid; - m->creds.mask |= SD_BUS_CREDS_EUID & bus->creds_mask; - } - - if ((uid_t) d->creds.suid != UID_INVALID) { - m->creds.suid = (uid_t) d->creds.suid; - m->creds.mask |= SD_BUS_CREDS_SUID & bus->creds_mask; - } - - if ((uid_t) d->creds.fsuid != UID_INVALID) { - m->creds.fsuid = (uid_t) d->creds.fsuid; - m->creds.mask |= SD_BUS_CREDS_FSUID & bus->creds_mask; - } - - if ((gid_t) d->creds.gid != GID_INVALID) { - m->creds.gid = (gid_t) d->creds.gid; - m->creds.mask |= SD_BUS_CREDS_GID & bus->creds_mask; - } - - if ((gid_t) d->creds.egid != GID_INVALID) { - m->creds.egid = (gid_t) d->creds.egid; - m->creds.mask |= SD_BUS_CREDS_EGID & bus->creds_mask; - } - - if ((gid_t) d->creds.sgid != GID_INVALID) { - m->creds.sgid = (gid_t) d->creds.sgid; - m->creds.mask |= SD_BUS_CREDS_SGID & bus->creds_mask; - } - - if ((gid_t) d->creds.fsgid != GID_INVALID) { - m->creds.fsgid = (gid_t) d->creds.fsgid; - m->creds.mask |= SD_BUS_CREDS_FSGID & bus->creds_mask; - } - - break; - - case KDBUS_ITEM_TIMESTAMP: - message_set_timestamp(bus, m, &d->timestamp); - break; - - case KDBUS_ITEM_PID_COMM: - m->creds.comm = d->str; - m->creds.mask |= SD_BUS_CREDS_COMM & bus->creds_mask; - break; - - case KDBUS_ITEM_TID_COMM: - m->creds.tid_comm = d->str; - m->creds.mask |= SD_BUS_CREDS_TID_COMM & bus->creds_mask; - break; - - case KDBUS_ITEM_EXE: - m->creds.exe = d->str; - m->creds.mask |= SD_BUS_CREDS_EXE & bus->creds_mask; - break; - - case KDBUS_ITEM_CMDLINE: - m->creds.cmdline = d->str; - m->creds.cmdline_size = l; - m->creds.mask |= SD_BUS_CREDS_CMDLINE & bus->creds_mask; - break; - - case KDBUS_ITEM_CGROUP: - m->creds.cgroup = d->str; - m->creds.mask |= (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID) & bus->creds_mask; - - r = bus_get_root_path(bus); - if (r < 0) - goto fail; - - m->creds.cgroup_root = bus->cgroup_root; - break; - - case KDBUS_ITEM_AUDIT: - m->creds.audit_session_id = (uint32_t) d->audit.sessionid; - m->creds.mask |= SD_BUS_CREDS_AUDIT_SESSION_ID & bus->creds_mask; - - m->creds.audit_login_uid = (uid_t) d->audit.loginuid; - m->creds.mask |= SD_BUS_CREDS_AUDIT_LOGIN_UID & bus->creds_mask; - break; - - case KDBUS_ITEM_CAPS: - if (d->caps.last_cap != cap_last_cap() || - d->size - offsetof(struct kdbus_item, caps.caps) < DIV_ROUND_UP(d->caps.last_cap, 32U) * 4 * 4) { - r = -EBADMSG; - goto fail; - } - - m->creds.capability = d->caps.caps; - m->creds.mask |= (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS) & bus->creds_mask; - break; - - case KDBUS_ITEM_DST_NAME: - if (!service_name_is_valid(d->str)) { - r = -EBADMSG; - goto fail; - } - - destination = d->str; - break; - - case KDBUS_ITEM_OWNED_NAME: - if (!service_name_is_valid(d->name.name)) { - r = -EBADMSG; - goto fail; - } - - if (bus->creds_mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) { - char **wkn; - size_t n; - - /* We just extend the array here, but - * do not allocate the strings inside - * of it, instead we just point to our - * buffer directly. */ - n = strv_length(m->creds.well_known_names); - wkn = realloc(m->creds.well_known_names, (n + 2) * sizeof(char*)); - if (!wkn) { - r = -ENOMEM; - goto fail; - } - - wkn[n] = d->name.name; - wkn[n+1] = NULL; - m->creds.well_known_names = wkn; - - m->creds.mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES; - } - break; - - case KDBUS_ITEM_CONN_DESCRIPTION: - m->creds.description = d->str; - m->creds.mask |= SD_BUS_CREDS_DESCRIPTION & bus->creds_mask; - break; - - case KDBUS_ITEM_AUXGROUPS: - - if (bus->creds_mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) { - size_t i, n; - gid_t *g; - - n = (d->size - offsetof(struct kdbus_item, data64)) / sizeof(uint64_t); - g = new(gid_t, n); - if (!g) { - r = -ENOMEM; - goto fail; - } - - for (i = 0; i < n; i++) - g[i] = d->data64[i]; - - m->creds.supplementary_gids = g; - m->creds.n_supplementary_gids = n; - m->creds.mask |= SD_BUS_CREDS_SUPPLEMENTARY_GIDS; - } - - break; - - case KDBUS_ITEM_FDS: - case KDBUS_ITEM_SECLABEL: - case KDBUS_ITEM_BLOOM_FILTER: - break; - - default: - log_debug("Got unknown field from kernel %llu", d->type); - } - } - - /* If we requested the list of well-known names to be appended - * and the sender had none no item for it will be - * attached. However, this does *not* mean that the kernel - * didn't want to provide this information to us. Hence, let's - * explicitly mark this information as available if it was - * requested. */ - m->creds.mask |= bus->creds_mask & SD_BUS_CREDS_WELL_KNOWN_NAMES; - - r = bus_message_parse_fields(m); - if (r < 0) - goto fail; - - /* Refuse messages if kdbus and dbus1 cookie doesn't match up */ - if ((uint64_t) m->header->dbus2.cookie != k->cookie) { - r = -EBADMSG; - goto fail; - } - - /* Refuse messages where the reply flag doesn't match up */ - if (!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) != !!(k->flags & KDBUS_MSG_EXPECT_REPLY)) { - r = -EBADMSG; - goto fail; - } - - /* Refuse reply messages where the reply cookie doesn't match up */ - if ((m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) && m->reply_cookie != k->cookie_reply) { - r = -EBADMSG; - goto fail; - } - - /* Refuse messages where the autostart flag doesn't match up */ - if (!(m->header->flags & BUS_MESSAGE_NO_AUTO_START) != !(k->flags & KDBUS_MSG_NO_AUTO_START)) { - r = -EBADMSG; - goto fail; - } - - /* Override information from the user header with data from the kernel */ - if (k->src_id == KDBUS_SRC_ID_KERNEL) - bus_message_set_sender_driver(bus, m); - else { - xsprintf(m->sender_buffer, ":1.%llu", - (unsigned long long)k->src_id); - m->sender = m->creds.unique_name = m->sender_buffer; - } - - if (destination) - m->destination = destination; - else if (k->dst_id == KDBUS_DST_ID_BROADCAST) - m->destination = NULL; - else if (k->dst_id == KDBUS_DST_ID_NAME) - m->destination = bus->unique_name; /* fill in unique name if the well-known name is missing */ - else { - xsprintf(m->destination_buffer, ":1.%llu", - (unsigned long long)k->dst_id); - m->destination = m->destination_buffer; - } - - /* We take possession of the kmsg struct now */ - m->kdbus = k; - m->release_kdbus = true; - m->free_fds = true; - fds = NULL; - - bus->rqueue[bus->rqueue_size++] = m; - - return 1; - -fail: - unset_memfds(m); - sd_bus_message_unref(m); - - return r; -} - -int bus_kernel_take_fd(sd_bus *b) { - struct kdbus_bloom_parameter *bloom = NULL; - struct kdbus_item *items, *item; - struct kdbus_cmd_hello *hello; - _cleanup_free_ char *g = NULL; - const char *name; - size_t l = 0, m = 0, sz; - int r; - - assert(b); - - if (b->is_server) - return -EINVAL; - - b->use_memfd = 1; - - if (b->description) { - g = bus_label_escape(b->description); - if (!g) - return -ENOMEM; - - name = g; - } else { - char pr[17] = {}; - - /* If no name is explicitly set, we'll include a hint - * indicating the library implementation, a hint which - * kind of bus this is and the thread name */ - - assert_se(prctl(PR_GET_NAME, (unsigned long) pr) >= 0); - - if (isempty(pr)) { - name = b->is_system ? "sd-system" : - b->is_user ? "sd-user" : "sd"; - } else { - _cleanup_free_ char *e = NULL; - - e = bus_label_escape(pr); - if (!e) - return -ENOMEM; - - g = strappend(b->is_system ? "sd-system-" : - b->is_user ? "sd-user-" : "sd-", - e); - if (!g) - return -ENOMEM; - - name = g; - } - - b->description = bus_label_unescape(name); - if (!b->description) - return -ENOMEM; - } - - m = strlen(name); - - sz = ALIGN8(offsetof(struct kdbus_cmd_hello, items)) + - ALIGN8(offsetof(struct kdbus_item, str) + m + 1); - - if (b->fake_creds_valid) - sz += ALIGN8(offsetof(struct kdbus_item, creds) + sizeof(struct kdbus_creds)); - - if (b->fake_pids_valid) - sz += ALIGN8(offsetof(struct kdbus_item, pids) + sizeof(struct kdbus_pids)); - - if (b->fake_label) { - l = strlen(b->fake_label); - sz += ALIGN8(offsetof(struct kdbus_item, str) + l + 1); - } - - hello = alloca0_align(sz, 8); - hello->size = sz; - hello->flags = b->hello_flags; - hello->attach_flags_send = _KDBUS_ATTACH_ANY; - hello->attach_flags_recv = b->attach_flags; - hello->pool_size = KDBUS_POOL_SIZE; - - item = hello->items; - - item->size = offsetof(struct kdbus_item, str) + m + 1; - item->type = KDBUS_ITEM_CONN_DESCRIPTION; - memcpy(item->str, name, m + 1); - item = KDBUS_ITEM_NEXT(item); - - if (b->fake_creds_valid) { - item->size = offsetof(struct kdbus_item, creds) + sizeof(struct kdbus_creds); - item->type = KDBUS_ITEM_CREDS; - item->creds = b->fake_creds; - - item = KDBUS_ITEM_NEXT(item); - } - - if (b->fake_pids_valid) { - item->size = offsetof(struct kdbus_item, pids) + sizeof(struct kdbus_pids); - item->type = KDBUS_ITEM_PIDS; - item->pids = b->fake_pids; - - item = KDBUS_ITEM_NEXT(item); - } - - if (b->fake_label) { - item->size = offsetof(struct kdbus_item, str) + l + 1; - item->type = KDBUS_ITEM_SECLABEL; - memcpy(item->str, b->fake_label, l+1); - } - - r = ioctl(b->input_fd, KDBUS_CMD_HELLO, hello); - if (r < 0) { - if (errno == ENOTTY) - /* If the ioctl is not supported we assume that the - * API version changed in a major incompatible way, - * let's indicate an API incompatibility in this - * case. */ - return -ESOCKTNOSUPPORT; - - return -errno; - } - - if (!b->kdbus_buffer) { - b->kdbus_buffer = mmap(NULL, KDBUS_POOL_SIZE, PROT_READ, MAP_SHARED, b->input_fd, 0); - if (b->kdbus_buffer == MAP_FAILED) { - b->kdbus_buffer = NULL; - r = -errno; - goto fail; - } - } - - /* The higher 32bit of the bus_flags fields are considered - * 'incompatible flags'. Refuse them all for now. */ - if (hello->bus_flags > 0xFFFFFFFFULL) { - r = -ESOCKTNOSUPPORT; - goto fail; - } - - /* extract bloom parameters from items */ - items = (void*)((uint8_t*)b->kdbus_buffer + hello->offset); - KDBUS_FOREACH(item, items, hello->items_size) { - switch (item->type) { - case KDBUS_ITEM_BLOOM_PARAMETER: - bloom = &item->bloom_parameter; - break; - } - } - - if (!bloom || !bloom_validate_parameters((size_t) bloom->size, (unsigned) bloom->n_hash)) { - r = -EOPNOTSUPP; - goto fail; - } - - b->bloom_size = (size_t) bloom->size; - b->bloom_n_hash = (unsigned) bloom->n_hash; - - if (asprintf(&b->unique_name, ":1.%llu", (unsigned long long) hello->id) < 0) { - r = -ENOMEM; - goto fail; - } - - b->unique_id = hello->id; - - b->is_kernel = true; - b->bus_client = true; - b->can_fds = !!(hello->flags & KDBUS_HELLO_ACCEPT_FD); - b->message_version = 2; - b->message_endian = BUS_NATIVE_ENDIAN; - - /* the kernel told us the UUID of the underlying bus */ - memcpy(b->server_id.bytes, hello->id128, sizeof(b->server_id.bytes)); - - /* free returned items */ - (void) bus_kernel_cmd_free(b, hello->offset); - return bus_start_running(b); - -fail: - (void) bus_kernel_cmd_free(b, hello->offset); - return r; -} - -int bus_kernel_connect(sd_bus *b) { - assert(b); - assert(b->input_fd < 0); - assert(b->output_fd < 0); - assert(b->kernel); - - if (b->is_server) - return -EINVAL; - - b->input_fd = open(b->kernel, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (b->input_fd < 0) - return -errno; - - b->output_fd = b->input_fd; - - return bus_kernel_take_fd(b); -} - -int bus_kernel_cmd_free(sd_bus *bus, uint64_t offset) { - struct kdbus_cmd_free cmd = { - .size = sizeof(cmd), - .offset = offset, - }; - int r; - - assert(bus); - assert(bus->is_kernel); - - r = ioctl(bus->input_fd, KDBUS_CMD_FREE, &cmd); - if (r < 0) - return -errno; - - return 0; -} - -static void close_kdbus_msg(sd_bus *bus, struct kdbus_msg *k) { - struct kdbus_item *d; - - assert(bus); - assert(k); - - KDBUS_ITEM_FOREACH(d, k, items) { - if (d->type == KDBUS_ITEM_FDS) - close_many(d->fds, (d->size - offsetof(struct kdbus_item, fds)) / sizeof(int)); - else if (d->type == KDBUS_ITEM_PAYLOAD_MEMFD) - safe_close(d->memfd.fd); - } - - bus_kernel_cmd_free(bus, (uint8_t*) k - (uint8_t*) bus->kdbus_buffer); -} - -int bus_kernel_write_message(sd_bus *bus, sd_bus_message *m, bool hint_sync_call) { - struct kdbus_cmd_send cmd = { }; - int r; - - assert(bus); - assert(m); - assert(bus->state == BUS_RUNNING); - - /* If we can't deliver, we want room for the error message */ - r = bus_rqueue_make_room(bus); - if (r < 0) - return r; - - r = bus_message_setup_kmsg(bus, m); - if (r < 0) - return r; - - cmd.size = sizeof(cmd); - cmd.msg_address = (uintptr_t)m->kdbus; - - /* If this is a synchronous method call, then let's tell the - * kernel, so that it can pass CPU time/scheduling to the - * destination for the time, if it wants to. If we - * synchronously wait for the result anyway, we won't need CPU - * anyway. */ - if (hint_sync_call) { - m->kdbus->flags |= KDBUS_MSG_EXPECT_REPLY; - cmd.flags |= KDBUS_SEND_SYNC_REPLY; - } - - r = ioctl(bus->output_fd, KDBUS_CMD_SEND, &cmd); - if (r < 0) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus_message *reply; - - if (errno == EAGAIN || errno == EINTR) - return 0; - else if (errno == ENXIO || errno == ESRCH) { - - /* ENXIO: unique name not known - * ESRCH: well-known name not known */ - - if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) - sd_bus_error_setf(&error, SD_BUS_ERROR_SERVICE_UNKNOWN, "Destination %s not known", m->destination); - else { - log_debug("Could not deliver message to %s as destination is not known. Ignoring.", m->destination); - return 0; - } - - } else if (errno == EADDRNOTAVAIL) { - - /* EADDRNOTAVAIL: activation is possible, but turned off in request flags */ - - if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) - sd_bus_error_setf(&error, SD_BUS_ERROR_SERVICE_UNKNOWN, "Activation of %s not requested", m->destination); - else { - log_debug("Could not deliver message to %s as destination is not activated. Ignoring.", m->destination); - return 0; - } - } else - return -errno; - - r = bus_message_new_synthetic_error( - bus, - BUS_MESSAGE_COOKIE(m), - &error, - &reply); - - if (r < 0) - return r; - - r = bus_seal_synthetic_message(bus, reply); - if (r < 0) - return r; - - bus->rqueue[bus->rqueue_size++] = reply; - - } else if (hint_sync_call) { - struct kdbus_msg *k; - - k = (struct kdbus_msg *)((uint8_t *)bus->kdbus_buffer + cmd.reply.offset); - assert(k); - - if (k->payload_type == KDBUS_PAYLOAD_DBUS) { - - r = bus_kernel_make_message(bus, k); - if (r < 0) { - close_kdbus_msg(bus, k); - - /* Anybody can send us invalid messages, let's just drop them. */ - if (r == -EBADMSG || r == -EPROTOTYPE) - log_debug_errno(r, "Ignoring invalid synchronous reply: %m"); - else - return r; - } - } else { - log_debug("Ignoring message with unknown payload type %llu.", (unsigned long long) k->payload_type); - close_kdbus_msg(bus, k); - } - } - - return 1; -} - -static int push_name_owner_changed( - sd_bus *bus, - const char *name, - const char *old_owner, - const char *new_owner, - const struct kdbus_timestamp *ts) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert(bus); - - r = sd_bus_message_new_signal( - bus, - &m, - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "NameOwnerChanged"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "sss", name, old_owner, new_owner); - if (r < 0) - return r; - - bus_message_set_sender_driver(bus, m); - message_set_timestamp(bus, m, ts); - - r = bus_seal_synthetic_message(bus, m); - if (r < 0) - return r; - - bus->rqueue[bus->rqueue_size++] = m; - m = NULL; - - return 1; -} - -static int translate_name_change( - sd_bus *bus, - const struct kdbus_msg *k, - const struct kdbus_item *d, - const struct kdbus_timestamp *ts) { - - char new_owner[UNIQUE_NAME_MAX], old_owner[UNIQUE_NAME_MAX]; - - assert(bus); - assert(k); - assert(d); - - if (d->type == KDBUS_ITEM_NAME_ADD || (d->name_change.old_id.flags & (KDBUS_NAME_IN_QUEUE|KDBUS_NAME_ACTIVATOR))) - old_owner[0] = 0; - else - sprintf(old_owner, ":1.%llu", (unsigned long long) d->name_change.old_id.id); - - if (d->type == KDBUS_ITEM_NAME_REMOVE || (d->name_change.new_id.flags & (KDBUS_NAME_IN_QUEUE|KDBUS_NAME_ACTIVATOR))) { - - if (isempty(old_owner)) - return 0; - - new_owner[0] = 0; - } else - sprintf(new_owner, ":1.%llu", (unsigned long long) d->name_change.new_id.id); - - return push_name_owner_changed(bus, d->name_change.name, old_owner, new_owner, ts); -} - -static int translate_id_change( - sd_bus *bus, - const struct kdbus_msg *k, - const struct kdbus_item *d, - const struct kdbus_timestamp *ts) { - - char owner[UNIQUE_NAME_MAX]; - - assert(bus); - assert(k); - assert(d); - - sprintf(owner, ":1.%llu", d->id_change.id); - - return push_name_owner_changed( - bus, owner, - d->type == KDBUS_ITEM_ID_ADD ? NULL : owner, - d->type == KDBUS_ITEM_ID_ADD ? owner : NULL, - ts); -} - -static int translate_reply( - sd_bus *bus, - const struct kdbus_msg *k, - const struct kdbus_item *d, - const struct kdbus_timestamp *ts) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert(bus); - assert(k); - assert(d); - - r = bus_message_new_synthetic_error( - bus, - k->cookie_reply, - d->type == KDBUS_ITEM_REPLY_TIMEOUT ? - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Method call timed out") : - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Method call peer died"), - &m); - if (r < 0) - return r; - - message_set_timestamp(bus, m, ts); - - r = bus_seal_synthetic_message(bus, m); - if (r < 0) - return r; - - bus->rqueue[bus->rqueue_size++] = m; - m = NULL; - - return 1; -} - -static int bus_kernel_translate_message(sd_bus *bus, struct kdbus_msg *k) { - static int (* const translate[])(sd_bus *bus, const struct kdbus_msg *k, const struct kdbus_item *d, const struct kdbus_timestamp *ts) = { - [KDBUS_ITEM_NAME_ADD - _KDBUS_ITEM_KERNEL_BASE] = translate_name_change, - [KDBUS_ITEM_NAME_REMOVE - _KDBUS_ITEM_KERNEL_BASE] = translate_name_change, - [KDBUS_ITEM_NAME_CHANGE - _KDBUS_ITEM_KERNEL_BASE] = translate_name_change, - - [KDBUS_ITEM_ID_ADD - _KDBUS_ITEM_KERNEL_BASE] = translate_id_change, - [KDBUS_ITEM_ID_REMOVE - _KDBUS_ITEM_KERNEL_BASE] = translate_id_change, - - [KDBUS_ITEM_REPLY_TIMEOUT - _KDBUS_ITEM_KERNEL_BASE] = translate_reply, - [KDBUS_ITEM_REPLY_DEAD - _KDBUS_ITEM_KERNEL_BASE] = translate_reply, - }; - - struct kdbus_item *d, *found = NULL; - struct kdbus_timestamp *ts = NULL; - - assert(bus); - assert(k); - assert(k->payload_type == KDBUS_PAYLOAD_KERNEL); - - KDBUS_ITEM_FOREACH(d, k, items) { - if (d->type == KDBUS_ITEM_TIMESTAMP) - ts = &d->timestamp; - else if (d->type >= _KDBUS_ITEM_KERNEL_BASE && d->type < _KDBUS_ITEM_KERNEL_BASE + ELEMENTSOF(translate)) { - if (found) - return -EBADMSG; - found = d; - } else - log_debug("Got unknown field from kernel %llu", d->type); - } - - if (!found) { - log_debug("Didn't find a kernel message to translate."); - return 0; - } - - return translate[found->type - _KDBUS_ITEM_KERNEL_BASE](bus, k, found, ts); -} - -int bus_kernel_read_message(sd_bus *bus, bool hint_priority, int64_t priority) { - struct kdbus_cmd_recv recv = { .size = sizeof(recv) }; - struct kdbus_msg *k; - int r; - - assert(bus); - - r = bus_rqueue_make_room(bus); - if (r < 0) - return r; - - if (hint_priority) { - recv.flags |= KDBUS_RECV_USE_PRIORITY; - recv.priority = priority; - } - - r = ioctl(bus->input_fd, KDBUS_CMD_RECV, &recv); - if (recv.return_flags & KDBUS_RECV_RETURN_DROPPED_MSGS) - log_debug("%s: kdbus reports %" PRIu64 " dropped broadcast messages, ignoring.", strna(bus->description), (uint64_t) recv.dropped_msgs); - if (r < 0) { - if (errno == EAGAIN) - return 0; - - return -errno; - } - - k = (struct kdbus_msg *)((uint8_t *)bus->kdbus_buffer + recv.msg.offset); - if (k->payload_type == KDBUS_PAYLOAD_DBUS) { - r = bus_kernel_make_message(bus, k); - - /* Anybody can send us invalid messages, let's just drop them. */ - if (r == -EBADMSG || r == -EPROTOTYPE) { - log_debug_errno(r, "Ignoring invalid message: %m"); - r = 0; - } - - if (r <= 0) - close_kdbus_msg(bus, k); - } else if (k->payload_type == KDBUS_PAYLOAD_KERNEL) { - r = bus_kernel_translate_message(bus, k); - close_kdbus_msg(bus, k); - } else { - log_debug("Ignoring message with unknown payload type %llu.", (unsigned long long) k->payload_type); - r = 0; - close_kdbus_msg(bus, k); - } - - return r < 0 ? r : 1; -} - -int bus_kernel_pop_memfd(sd_bus *bus, void **address, size_t *mapped, size_t *allocated) { - struct memfd_cache *c; - int fd; - - assert(address); - assert(mapped); - assert(allocated); - - if (!bus || !bus->is_kernel) - return -EOPNOTSUPP; - - assert_se(pthread_mutex_lock(&bus->memfd_cache_mutex) == 0); - - if (bus->n_memfd_cache <= 0) { - int r; - - assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); - - r = memfd_new(bus->description); - if (r < 0) - return r; - - *address = NULL; - *mapped = 0; - *allocated = 0; - return r; - } - - c = &bus->memfd_cache[--bus->n_memfd_cache]; - - assert(c->fd >= 0); - assert(c->mapped == 0 || c->address); - - *address = c->address; - *mapped = c->mapped; - *allocated = c->allocated; - fd = c->fd; - - assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); - - return fd; -} - -static void close_and_munmap(int fd, void *address, size_t size) { - if (size > 0) - assert_se(munmap(address, PAGE_ALIGN(size)) >= 0); - - safe_close(fd); -} - -void bus_kernel_push_memfd(sd_bus *bus, int fd, void *address, size_t mapped, size_t allocated) { - struct memfd_cache *c; - uint64_t max_mapped = PAGE_ALIGN(MEMFD_CACHE_ITEM_SIZE_MAX); - - assert(fd >= 0); - assert(mapped == 0 || address); - - if (!bus || !bus->is_kernel) { - close_and_munmap(fd, address, mapped); - return; - } - - assert_se(pthread_mutex_lock(&bus->memfd_cache_mutex) == 0); - - if (bus->n_memfd_cache >= ELEMENTSOF(bus->memfd_cache)) { - assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); - - close_and_munmap(fd, address, mapped); - return; - } - - c = &bus->memfd_cache[bus->n_memfd_cache++]; - c->fd = fd; - c->address = address; - - /* If overly long, let's return a bit to the OS */ - if (mapped > max_mapped) { - assert_se(memfd_set_size(fd, max_mapped) >= 0); - assert_se(munmap((uint8_t*) address + max_mapped, PAGE_ALIGN(mapped - max_mapped)) >= 0); - c->mapped = c->allocated = max_mapped; - } else { - c->mapped = mapped; - c->allocated = allocated; - } - - assert_se(pthread_mutex_unlock(&bus->memfd_cache_mutex) == 0); -} - -void bus_kernel_flush_memfd(sd_bus *b) { - unsigned i; - - assert(b); - - for (i = 0; i < b->n_memfd_cache; i++) - close_and_munmap(b->memfd_cache[i].fd, b->memfd_cache[i].address, b->memfd_cache[i].mapped); -} - -uint64_t request_name_flags_to_kdbus(uint64_t flags) { - uint64_t f = 0; - - if (flags & SD_BUS_NAME_ALLOW_REPLACEMENT) - f |= KDBUS_NAME_ALLOW_REPLACEMENT; - - if (flags & SD_BUS_NAME_REPLACE_EXISTING) - f |= KDBUS_NAME_REPLACE_EXISTING; - - if (flags & SD_BUS_NAME_QUEUE) - f |= KDBUS_NAME_QUEUE; - - return f; -} - -uint64_t attach_flags_to_kdbus(uint64_t mask) { - uint64_t m = 0; - - if (mask & (SD_BUS_CREDS_UID|SD_BUS_CREDS_EUID|SD_BUS_CREDS_SUID|SD_BUS_CREDS_FSUID| - SD_BUS_CREDS_GID|SD_BUS_CREDS_EGID|SD_BUS_CREDS_SGID|SD_BUS_CREDS_FSGID)) - m |= KDBUS_ATTACH_CREDS; - - if (mask & (SD_BUS_CREDS_PID|SD_BUS_CREDS_TID|SD_BUS_CREDS_PPID)) - m |= KDBUS_ATTACH_PIDS; - - if (mask & SD_BUS_CREDS_COMM) - m |= KDBUS_ATTACH_PID_COMM; - - if (mask & SD_BUS_CREDS_TID_COMM) - m |= KDBUS_ATTACH_TID_COMM; - - if (mask & SD_BUS_CREDS_EXE) - m |= KDBUS_ATTACH_EXE; - - if (mask & SD_BUS_CREDS_CMDLINE) - m |= KDBUS_ATTACH_CMDLINE; - - if (mask & (SD_BUS_CREDS_CGROUP|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_USER_UNIT|SD_BUS_CREDS_SLICE|SD_BUS_CREDS_SESSION|SD_BUS_CREDS_OWNER_UID)) - m |= KDBUS_ATTACH_CGROUP; - - if (mask & (SD_BUS_CREDS_EFFECTIVE_CAPS|SD_BUS_CREDS_PERMITTED_CAPS|SD_BUS_CREDS_INHERITABLE_CAPS|SD_BUS_CREDS_BOUNDING_CAPS)) - m |= KDBUS_ATTACH_CAPS; - - if (mask & SD_BUS_CREDS_SELINUX_CONTEXT) - m |= KDBUS_ATTACH_SECLABEL; - - if (mask & (SD_BUS_CREDS_AUDIT_SESSION_ID|SD_BUS_CREDS_AUDIT_LOGIN_UID)) - m |= KDBUS_ATTACH_AUDIT; - - if (mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) - m |= KDBUS_ATTACH_NAMES; - - if (mask & SD_BUS_CREDS_DESCRIPTION) - m |= KDBUS_ATTACH_CONN_DESCRIPTION; - - if (mask & SD_BUS_CREDS_SUPPLEMENTARY_GIDS) - m |= KDBUS_ATTACH_AUXGROUPS; - - return m; -} - -int bus_kernel_create_bus(const char *name, bool world, char **s) { - struct kdbus_cmd *make; - struct kdbus_item *n; - size_t l; - int fd; - - assert(name); - assert(s); - - fd = open("/sys/fs/kdbus/control", O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return -errno; - - l = strlen(name); - make = alloca0_align(offsetof(struct kdbus_cmd, items) + - ALIGN8(offsetof(struct kdbus_item, bloom_parameter) + sizeof(struct kdbus_bloom_parameter)) + - ALIGN8(offsetof(struct kdbus_item, data64) + sizeof(uint64_t)) + - ALIGN8(offsetof(struct kdbus_item, str) + DECIMAL_STR_MAX(uid_t) + 1 + l + 1), - 8); - - make->size = offsetof(struct kdbus_cmd, items); - - /* Set the bloom parameters */ - n = make->items; - n->size = offsetof(struct kdbus_item, bloom_parameter) + - sizeof(struct kdbus_bloom_parameter); - n->type = KDBUS_ITEM_BLOOM_PARAMETER; - n->bloom_parameter.size = DEFAULT_BLOOM_SIZE; - n->bloom_parameter.n_hash = DEFAULT_BLOOM_N_HASH; - - assert_cc(DEFAULT_BLOOM_SIZE > 0); - assert_cc(DEFAULT_BLOOM_N_HASH > 0); - - make->size += ALIGN8(n->size); - - /* Provide all metadata via bus-owner queries */ - n = KDBUS_ITEM_NEXT(n); - n->type = KDBUS_ITEM_ATTACH_FLAGS_SEND; - n->size = offsetof(struct kdbus_item, data64) + sizeof(uint64_t); - n->data64[0] = _KDBUS_ATTACH_ANY; - make->size += ALIGN8(n->size); - - /* Set the a good name */ - n = KDBUS_ITEM_NEXT(n); - sprintf(n->str, UID_FMT "-%s", getuid(), name); - n->size = offsetof(struct kdbus_item, str) + strlen(n->str) + 1; - n->type = KDBUS_ITEM_MAKE_NAME; - make->size += ALIGN8(n->size); - - make->flags = world ? KDBUS_MAKE_ACCESS_WORLD : 0; - - if (ioctl(fd, KDBUS_CMD_BUS_MAKE, make) < 0) { - safe_close(fd); - - /* Major API change? then the ioctls got shuffled around. */ - if (errno == ENOTTY) - return -ESOCKTNOSUPPORT; - - return -errno; - } - - if (s) { - char *p; - - p = strjoin("/sys/fs/kdbus/", n->str, "/bus", NULL); - if (!p) { - safe_close(fd); - return -ENOMEM; - } - - *s = p; - } - - return fd; -} - -int bus_kernel_open_bus_fd(const char *bus, char **path) { - char *p; - int fd; - size_t len; - - assert(bus); - - len = strlen("/sys/fs/kdbus/") + DECIMAL_STR_MAX(uid_t) + 1 + strlen(bus) + strlen("/bus") + 1; - - if (path) { - p = new(char, len); - if (!p) - return -ENOMEM; - } else - p = newa(char, len); - - sprintf(p, "/sys/fs/kdbus/" UID_FMT "-%s/bus", getuid(), bus); - - fd = open(p, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (fd < 0) { - if (path) - free(p); - - return -errno; - } - - if (path) - *path = p; - - return fd; -} - -int bus_kernel_create_endpoint(const char *bus_name, const char *ep_name, char **ep_path) { - _cleanup_free_ char *path = NULL; - struct kdbus_cmd *make; - struct kdbus_item *n; - const char *name; - int fd; - - fd = bus_kernel_open_bus_fd(bus_name, &path); - if (fd < 0) - return fd; - - make = alloca0_align(ALIGN8(offsetof(struct kdbus_cmd, items)) + - ALIGN8(offsetof(struct kdbus_item, str) + DECIMAL_STR_MAX(uid_t) + 1 + strlen(ep_name) + 1), - 8); - make->size = ALIGN8(offsetof(struct kdbus_cmd, items)); - make->flags = KDBUS_MAKE_ACCESS_WORLD; - - n = make->items; - sprintf(n->str, UID_FMT "-%s", getuid(), ep_name); - n->size = offsetof(struct kdbus_item, str) + strlen(n->str) + 1; - n->type = KDBUS_ITEM_MAKE_NAME; - make->size += ALIGN8(n->size); - name = n->str; - - if (ioctl(fd, KDBUS_CMD_ENDPOINT_MAKE, make) < 0) { - safe_close(fd); - return -errno; - } - - if (ep_path) { - char *p; - - p = strjoin(dirname(path), "/", name, NULL); - if (!p) { - safe_close(fd); - return -ENOMEM; - } - - *ep_path = p; - } - - return fd; -} - -int bus_kernel_try_close(sd_bus *bus) { - struct kdbus_cmd byebye = { .size = sizeof(byebye) }; - - assert(bus); - assert(bus->is_kernel); - - if (ioctl(bus->input_fd, KDBUS_CMD_BYEBYE, &byebye) < 0) - return -errno; - - return 0; -} - -int bus_kernel_drop_one(int fd) { - struct kdbus_cmd_recv recv = { - .size = sizeof(recv), - .flags = KDBUS_RECV_DROP, - }; - - assert(fd >= 0); - - if (ioctl(fd, KDBUS_CMD_RECV, &recv) < 0) - return -errno; - - return 0; -} - -int bus_kernel_realize_attach_flags(sd_bus *bus) { - struct kdbus_cmd *update; - struct kdbus_item *n; - - assert(bus); - assert(bus->is_kernel); - - update = alloca0_align(offsetof(struct kdbus_cmd, items) + - ALIGN8(offsetof(struct kdbus_item, data64) + sizeof(uint64_t)), - 8); - - n = update->items; - n->type = KDBUS_ITEM_ATTACH_FLAGS_RECV; - n->size = offsetof(struct kdbus_item, data64) + sizeof(uint64_t); - n->data64[0] = bus->attach_flags; - - update->size = - offsetof(struct kdbus_cmd, items) + - ALIGN8(n->size); - - if (ioctl(bus->input_fd, KDBUS_CMD_UPDATE, update) < 0) - return -errno; - - return 0; -} - -int bus_kernel_get_bus_name(sd_bus *bus, char **name) { - struct kdbus_cmd_info cmd = { - .size = sizeof(struct kdbus_cmd_info), - }; - struct kdbus_info *info; - struct kdbus_item *item; - char *n = NULL; - int r; - - assert(bus); - assert(name); - assert(bus->is_kernel); - - r = ioctl(bus->input_fd, KDBUS_CMD_BUS_CREATOR_INFO, &cmd); - if (r < 0) - return -errno; - - info = (struct kdbus_info*) ((uint8_t*) bus->kdbus_buffer + cmd.offset); - - KDBUS_ITEM_FOREACH(item, info, items) - if (item->type == KDBUS_ITEM_MAKE_NAME) { - r = free_and_strdup(&n, item->str); - break; - } - - bus_kernel_cmd_free(bus, cmd.offset); - - if (r < 0) - return r; - if (!n) - return -EIO; - - *name = n; - return 0; -} diff --git a/src/libsystemd/sd-bus/bus-kernel.h b/src/libsystemd/sd-bus/bus-kernel.h deleted file mode 100644 index 53ba3bdcf3..0000000000 --- a/src/libsystemd/sd-bus/bus-kernel.h +++ /dev/null @@ -1,93 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -#include "sd-bus.h" - -#define KDBUS_ITEM_NEXT(item) \ - (typeof(item))(((uint8_t *)item) + ALIGN8((item)->size)) - -#define KDBUS_ITEM_FOREACH(part, head, first) \ - for (part = (head)->first; \ - ((uint8_t *)(part) < (uint8_t *)(head) + (head)->size) && \ - ((uint8_t *) part >= (uint8_t *) head); \ - part = KDBUS_ITEM_NEXT(part)) -#define KDBUS_FOREACH(iter, first, _size) \ - for (iter = (first); \ - ((uint8_t *)(iter) < (uint8_t *)(first) + (_size)) && \ - ((uint8_t *)(iter) >= (uint8_t *)(first)); \ - iter = (void*)(((uint8_t *)iter) + ALIGN8((iter)->size))) - -#define KDBUS_ITEM_HEADER_SIZE offsetof(struct kdbus_item, data) -#define KDBUS_ITEM_SIZE(s) ALIGN8((s) + KDBUS_ITEM_HEADER_SIZE) - -#define MEMFD_CACHE_MAX 32 - -/* When we cache a memfd block for reuse, we will truncate blocks - * longer than this in order not to keep too much data around. */ -#define MEMFD_CACHE_ITEM_SIZE_MAX (128*1024) - -/* This determines at which minimum size we prefer sending memfds over - * sending vectors */ -#define MEMFD_MIN_SIZE (512*1024) - -/* The size of the per-connection memory pool that we set up and where - * the kernel places our incoming messages */ -#define KDBUS_POOL_SIZE (16*1024*1024) - -struct memfd_cache { - int fd; - void *address; - size_t mapped; - size_t allocated; -}; - -int bus_kernel_connect(sd_bus *b); -int bus_kernel_take_fd(sd_bus *b); - -int bus_kernel_write_message(sd_bus *bus, sd_bus_message *m, bool hint_sync_call); -int bus_kernel_read_message(sd_bus *bus, bool hint_priority, int64_t priority); - -int bus_kernel_open_bus_fd(const char *bus, char **path); - -int bus_kernel_create_bus(const char *name, bool world, char **s); -int bus_kernel_create_endpoint(const char *bus_name, const char *ep_name, char **path); - -int bus_kernel_pop_memfd(sd_bus *bus, void **address, size_t *mapped, size_t *allocated); -void bus_kernel_push_memfd(sd_bus *bus, int fd, void *address, size_t mapped, size_t allocated); - -void bus_kernel_flush_memfd(sd_bus *bus); - -int bus_kernel_parse_unique_name(const char *s, uint64_t *id); - -uint64_t request_name_flags_to_kdbus(uint64_t sd_bus_flags); -uint64_t attach_flags_to_kdbus(uint64_t sd_bus_flags); - -int bus_kernel_try_close(sd_bus *bus); - -int bus_kernel_drop_one(int fd); - -int bus_kernel_realize_attach_flags(sd_bus *bus); - -int bus_kernel_get_bus_name(sd_bus *bus, char **name); - -int bus_kernel_cmd_free(sd_bus *bus, uint64_t offset); diff --git a/src/libsystemd/sd-bus/bus-match.c b/src/libsystemd/sd-bus/bus-match.c deleted file mode 100644 index 397baf6f33..0000000000 --- a/src/libsystemd/sd-bus/bus-match.c +++ /dev/null @@ -1,1218 +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 . -***/ - -#include "alloc-util.h" -#include "bus-internal.h" -#include "bus-match.h" -#include "bus-message.h" -#include "bus-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "hexdecoct.h" -#include "string-util.h" -#include "strv.h" - -/* Example: - * - * A: type=signal,sender=foo,interface=bar - * B: type=signal,sender=quux,interface=fips - * C: type=signal,sender=quux,interface=waldo - * D: type=signal,member=test - * E: sender=miau - * F: type=signal - * G: type=signal - * - * results in this tree: - * - * BUS_MATCH_ROOT - * + BUS_MATCH_MESSAGE_TYPE - * | ` BUS_MATCH_VALUE: value == signal - * | + DBUS_MATCH_SENDER - * | | + BUS_MATCH_VALUE: value == foo - * | | | ` DBUS_MATCH_INTERFACE - * | | | ` BUS_MATCH_VALUE: value == bar - * | | | ` BUS_MATCH_LEAF: A - * | | ` BUS_MATCH_VALUE: value == quux - * | | ` DBUS_MATCH_INTERFACE - * | | | BUS_MATCH_VALUE: value == fips - * | | | ` BUS_MATCH_LEAF: B - * | | ` BUS_MATCH_VALUE: value == waldo - * | | ` BUS_MATCH_LEAF: C - * | + DBUS_MATCH_MEMBER - * | | ` BUS_MATCH_VALUE: value == test - * | | ` BUS_MATCH_LEAF: D - * | + BUS_MATCH_LEAF: F - * | ` BUS_MATCH_LEAF: G - * ` BUS_MATCH_SENDER - * ` BUS_MATCH_VALUE: value == miau - * ` BUS_MATCH_LEAF: E - */ - -static inline bool BUS_MATCH_IS_COMPARE(enum bus_match_node_type t) { - return t >= BUS_MATCH_SENDER && t <= BUS_MATCH_ARG_HAS_LAST; -} - -static inline bool BUS_MATCH_CAN_HASH(enum bus_match_node_type t) { - return (t >= BUS_MATCH_MESSAGE_TYPE && t <= BUS_MATCH_PATH) || - (t >= BUS_MATCH_ARG && t <= BUS_MATCH_ARG_LAST) || - (t >= BUS_MATCH_ARG_HAS && t <= BUS_MATCH_ARG_HAS_LAST); -} - -static void bus_match_node_free(struct bus_match_node *node) { - assert(node); - assert(node->parent); - assert(!node->child); - assert(node->type != BUS_MATCH_ROOT); - assert(node->type < _BUS_MATCH_NODE_TYPE_MAX); - - if (node->parent->child) { - /* We are apparently linked into the parent's child - * list. Let's remove us from there. */ - if (node->prev) { - assert(node->prev->next == node); - node->prev->next = node->next; - } else { - assert(node->parent->child == node); - node->parent->child = node->next; - } - - if (node->next) - node->next->prev = node->prev; - } - - if (node->type == BUS_MATCH_VALUE) { - /* We might be in the parent's hash table, so clean - * this up */ - - if (node->parent->type == BUS_MATCH_MESSAGE_TYPE) - hashmap_remove(node->parent->compare.children, UINT_TO_PTR(node->value.u8)); - else if (BUS_MATCH_CAN_HASH(node->parent->type) && node->value.str) - hashmap_remove(node->parent->compare.children, node->value.str); - - free(node->value.str); - } - - if (BUS_MATCH_IS_COMPARE(node->type)) { - assert(hashmap_isempty(node->compare.children)); - hashmap_free(node->compare.children); - } - - free(node); -} - -static bool bus_match_node_maybe_free(struct bus_match_node *node) { - assert(node); - - if (node->type == BUS_MATCH_ROOT) - return false; - - if (node->child) - return false; - - if (BUS_MATCH_IS_COMPARE(node->type) && !hashmap_isempty(node->compare.children)) - return true; - - bus_match_node_free(node); - return true; -} - -static bool value_node_test( - struct bus_match_node *node, - enum bus_match_node_type parent_type, - uint8_t value_u8, - const char *value_str, - char **value_strv, - sd_bus_message *m) { - - assert(node); - assert(node->type == BUS_MATCH_VALUE); - - /* Tests parameters against this value node, doing prefix - * magic and stuff. */ - - switch (parent_type) { - - case BUS_MATCH_MESSAGE_TYPE: - return node->value.u8 == value_u8; - - case BUS_MATCH_SENDER: - if (streq_ptr(node->value.str, value_str)) - return true; - - if (m->creds.mask & SD_BUS_CREDS_WELL_KNOWN_NAMES) { - char **i; - - /* on kdbus we have the well known names list - * in the credentials, let's make use of that - * for an accurate match */ - - STRV_FOREACH(i, m->creds.well_known_names) - if (streq_ptr(node->value.str, *i)) - return true; - - } else { - - /* If we don't have kdbus, we don't know the - * well-known names of the senders. In that, - * let's just hope that dbus-daemon doesn't - * send us stuff we didn't want. */ - - if (node->value.str[0] != ':' && value_str && value_str[0] == ':') - return true; - } - - return false; - - case BUS_MATCH_DESTINATION: - case BUS_MATCH_INTERFACE: - case BUS_MATCH_MEMBER: - case BUS_MATCH_PATH: - case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: - - if (value_str) - return streq_ptr(node->value.str, value_str); - - return false; - - case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: { - char **i; - - STRV_FOREACH(i, value_strv) - if (streq_ptr(node->value.str, *i)) - return true; - - return false; - } - - case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: - if (value_str) - return namespace_simple_pattern(node->value.str, value_str); - - return false; - - case BUS_MATCH_PATH_NAMESPACE: - return path_simple_pattern(node->value.str, value_str); - - case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: - if (value_str) - return path_complex_pattern(node->value.str, value_str); - - return false; - - default: - assert_not_reached("Invalid node type"); - } -} - -static bool value_node_same( - struct bus_match_node *node, - enum bus_match_node_type parent_type, - uint8_t value_u8, - const char *value_str) { - - /* Tests parameters against this value node, not doing prefix - * magic and stuff, i.e. this one actually compares the match - * itself. */ - - assert(node); - assert(node->type == BUS_MATCH_VALUE); - - switch (parent_type) { - - case BUS_MATCH_MESSAGE_TYPE: - return node->value.u8 == value_u8; - - case BUS_MATCH_SENDER: - case BUS_MATCH_DESTINATION: - case BUS_MATCH_INTERFACE: - case BUS_MATCH_MEMBER: - case BUS_MATCH_PATH: - case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: - case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: - case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: - case BUS_MATCH_PATH_NAMESPACE: - case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: - return streq(node->value.str, value_str); - - default: - assert_not_reached("Invalid node type"); - } -} - -int bus_match_run( - sd_bus *bus, - struct bus_match_node *node, - sd_bus_message *m) { - - _cleanup_strv_free_ char **test_strv = NULL; - const char *test_str = NULL; - uint8_t test_u8 = 0; - int r; - - assert(m); - - if (!node) - return 0; - - if (bus && bus->match_callbacks_modified) - return 0; - - /* Not these special semantics: when traversing the tree we - * usually let bus_match_run() when called for a node - * recursively invoke bus_match_run(). There's are two - * exceptions here though, which are BUS_NODE_ROOT (which - * cannot have a sibling), and BUS_NODE_VALUE (whose siblings - * are invoked anyway by its parent. */ - - switch (node->type) { - - case BUS_MATCH_ROOT: - - /* Run all children. Since we cannot have any siblings - * we won't call any. The children of the root node - * are compares or leaves, they will automatically - * call their siblings. */ - return bus_match_run(bus, node->child, m); - - case BUS_MATCH_VALUE: - - /* Run all children. We don't execute any siblings, we - * assume our caller does that. The children of value - * nodes are compares or leaves, they will - * automatically call their siblings */ - - assert(node->child); - return bus_match_run(bus, node->child, m); - - case BUS_MATCH_LEAF: - - if (bus) { - if (node->leaf.callback->last_iteration == bus->iteration_counter) - return 0; - - node->leaf.callback->last_iteration = bus->iteration_counter; - } - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - /* Run the callback. And then invoke siblings. */ - if (node->leaf.callback->callback) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - sd_bus_slot *slot; - - slot = container_of(node->leaf.callback, sd_bus_slot, match_callback); - if (bus) { - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = node->leaf.callback->callback; - bus->current_userdata = slot->userdata; - } - r = node->leaf.callback->callback(m, slot->userdata, &error_buffer); - if (bus) { - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - } - - r = bus_maybe_reply_error(m, r, &error_buffer); - if (r != 0) - return r; - - if (bus && bus->match_callbacks_modified) - return 0; - } - - return bus_match_run(bus, node->next, m); - - case BUS_MATCH_MESSAGE_TYPE: - test_u8 = m->header->type; - break; - - case BUS_MATCH_SENDER: - test_str = m->sender; - /* FIXME: resolve test_str from a well-known to a unique name first */ - break; - - case BUS_MATCH_DESTINATION: - test_str = m->destination; - break; - - case BUS_MATCH_INTERFACE: - test_str = m->interface; - break; - - case BUS_MATCH_MEMBER: - test_str = m->member; - break; - - case BUS_MATCH_PATH: - case BUS_MATCH_PATH_NAMESPACE: - test_str = m->path; - break; - - case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: - (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG, &test_str); - break; - - case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: - (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG_PATH, &test_str); - break; - - case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: - (void) bus_message_get_arg(m, node->type - BUS_MATCH_ARG_NAMESPACE, &test_str); - break; - - case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: - (void) bus_message_get_arg_strv(m, node->type - BUS_MATCH_ARG_HAS, &test_strv); - break; - - default: - assert_not_reached("Unknown match type."); - } - - if (BUS_MATCH_CAN_HASH(node->type)) { - struct bus_match_node *found; - - /* Lookup via hash table, nice! So let's jump directly. */ - - if (test_str) - found = hashmap_get(node->compare.children, test_str); - else if (test_strv) { - char **i; - - STRV_FOREACH(i, test_strv) { - found = hashmap_get(node->compare.children, *i); - if (found) { - r = bus_match_run(bus, found, m); - if (r != 0) - return r; - } - } - - found = NULL; - } else if (node->type == BUS_MATCH_MESSAGE_TYPE) - found = hashmap_get(node->compare.children, UINT_TO_PTR(test_u8)); - else - found = NULL; - - if (found) { - r = bus_match_run(bus, found, m); - if (r != 0) - return r; - } - } else { - struct bus_match_node *c; - - /* No hash table, so let's iterate manually... */ - - for (c = node->child; c; c = c->next) { - if (!value_node_test(c, node->type, test_u8, test_str, test_strv, m)) - continue; - - r = bus_match_run(bus, c, m); - if (r != 0) - return r; - } - } - - if (bus && bus->match_callbacks_modified) - return 0; - - /* And now, let's invoke our siblings */ - return bus_match_run(bus, node->next, m); -} - -static int bus_match_add_compare_value( - struct bus_match_node *where, - enum bus_match_node_type t, - uint8_t value_u8, - const char *value_str, - struct bus_match_node **ret) { - - struct bus_match_node *c = NULL, *n = NULL; - int r; - - assert(where); - assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); - assert(BUS_MATCH_IS_COMPARE(t)); - assert(ret); - - for (c = where->child; c && c->type != t; c = c->next) - ; - - if (c) { - /* Comparison node already exists? Then let's see if - * the value node exists too. */ - - if (t == BUS_MATCH_MESSAGE_TYPE) - n = hashmap_get(c->compare.children, UINT_TO_PTR(value_u8)); - else if (BUS_MATCH_CAN_HASH(t)) - n = hashmap_get(c->compare.children, value_str); - else { - for (n = c->child; n && !value_node_same(n, t, value_u8, value_str); n = n->next) - ; - } - - if (n) { - *ret = n; - return 0; - } - } else { - /* Comparison node, doesn't exist yet? Then let's - * create it. */ - - c = new0(struct bus_match_node, 1); - if (!c) { - r = -ENOMEM; - goto fail; - } - - c->type = t; - c->parent = where; - c->next = where->child; - if (c->next) - c->next->prev = c; - where->child = c; - - if (t == BUS_MATCH_MESSAGE_TYPE) { - c->compare.children = hashmap_new(NULL); - if (!c->compare.children) { - r = -ENOMEM; - goto fail; - } - } else if (BUS_MATCH_CAN_HASH(t)) { - c->compare.children = hashmap_new(&string_hash_ops); - if (!c->compare.children) { - r = -ENOMEM; - goto fail; - } - } - } - - n = new0(struct bus_match_node, 1); - if (!n) { - r = -ENOMEM; - goto fail; - } - - n->type = BUS_MATCH_VALUE; - n->value.u8 = value_u8; - if (value_str) { - n->value.str = strdup(value_str); - if (!n->value.str) { - r = -ENOMEM; - goto fail; - } - } - - n->parent = c; - if (c->compare.children) { - - if (t == BUS_MATCH_MESSAGE_TYPE) - r = hashmap_put(c->compare.children, UINT_TO_PTR(value_u8), n); - else - r = hashmap_put(c->compare.children, n->value.str, n); - - if (r < 0) - goto fail; - } else { - n->next = c->child; - if (n->next) - n->next->prev = n; - c->child = n; - } - - *ret = n; - return 1; - -fail: - if (c) - bus_match_node_maybe_free(c); - - if (n) { - free(n->value.str); - free(n); - } - - return r; -} - -static int bus_match_find_compare_value( - struct bus_match_node *where, - enum bus_match_node_type t, - uint8_t value_u8, - const char *value_str, - struct bus_match_node **ret) { - - struct bus_match_node *c, *n; - - assert(where); - assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); - assert(BUS_MATCH_IS_COMPARE(t)); - assert(ret); - - for (c = where->child; c && c->type != t; c = c->next) - ; - - if (!c) - return 0; - - if (t == BUS_MATCH_MESSAGE_TYPE) - n = hashmap_get(c->compare.children, UINT_TO_PTR(value_u8)); - else if (BUS_MATCH_CAN_HASH(t)) - n = hashmap_get(c->compare.children, value_str); - else { - for (n = c->child; n && !value_node_same(n, t, value_u8, value_str); n = n->next) - ; - } - - if (n) { - *ret = n; - return 1; - } - - return 0; -} - -static int bus_match_add_leaf( - struct bus_match_node *where, - struct match_callback *callback) { - - struct bus_match_node *n; - - assert(where); - assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); - assert(callback); - - n = new0(struct bus_match_node, 1); - if (!n) - return -ENOMEM; - - n->type = BUS_MATCH_LEAF; - n->parent = where; - n->next = where->child; - if (n->next) - n->next->prev = n; - - n->leaf.callback = callback; - callback->match_node = n; - - where->child = n; - - return 1; -} - -static int bus_match_find_leaf( - struct bus_match_node *where, - sd_bus_message_handler_t callback, - void *userdata, - struct bus_match_node **ret) { - - struct bus_match_node *c; - - assert(where); - assert(where->type == BUS_MATCH_ROOT || where->type == BUS_MATCH_VALUE); - assert(ret); - - for (c = where->child; c; c = c->next) { - sd_bus_slot *s; - - s = container_of(c->leaf.callback, sd_bus_slot, match_callback); - - if (c->type == BUS_MATCH_LEAF && - c->leaf.callback->callback == callback && - s->userdata == userdata) { - *ret = c; - return 1; - } - } - - return 0; -} - -enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n) { - assert(k); - - if (n == 4 && startswith(k, "type")) - return BUS_MATCH_MESSAGE_TYPE; - if (n == 6 && startswith(k, "sender")) - return BUS_MATCH_SENDER; - if (n == 11 && startswith(k, "destination")) - return BUS_MATCH_DESTINATION; - if (n == 9 && startswith(k, "interface")) - return BUS_MATCH_INTERFACE; - if (n == 6 && startswith(k, "member")) - return BUS_MATCH_MEMBER; - if (n == 4 && startswith(k, "path")) - return BUS_MATCH_PATH; - if (n == 14 && startswith(k, "path_namespace")) - return BUS_MATCH_PATH_NAMESPACE; - - if (n == 4 && startswith(k, "arg")) { - int j; - - j = undecchar(k[3]); - if (j < 0) - return -EINVAL; - - return BUS_MATCH_ARG + j; - } - - if (n == 5 && startswith(k, "arg")) { - int a, b; - enum bus_match_node_type t; - - a = undecchar(k[3]); - b = undecchar(k[4]); - if (a <= 0 || b < 0) - return -EINVAL; - - t = BUS_MATCH_ARG + a * 10 + b; - if (t > BUS_MATCH_ARG_LAST) - return -EINVAL; - - return t; - } - - if (n == 8 && startswith(k, "arg") && startswith(k + 4, "path")) { - int j; - - j = undecchar(k[3]); - if (j < 0) - return -EINVAL; - - return BUS_MATCH_ARG_PATH + j; - } - - if (n == 9 && startswith(k, "arg") && startswith(k + 5, "path")) { - enum bus_match_node_type t; - int a, b; - - a = undecchar(k[3]); - b = undecchar(k[4]); - if (a <= 0 || b < 0) - return -EINVAL; - - t = BUS_MATCH_ARG_PATH + a * 10 + b; - if (t > BUS_MATCH_ARG_PATH_LAST) - return -EINVAL; - - return t; - } - - if (n == 13 && startswith(k, "arg") && startswith(k + 4, "namespace")) { - int j; - - j = undecchar(k[3]); - if (j < 0) - return -EINVAL; - - return BUS_MATCH_ARG_NAMESPACE + j; - } - - if (n == 14 && startswith(k, "arg") && startswith(k + 5, "namespace")) { - enum bus_match_node_type t; - int a, b; - - a = undecchar(k[3]); - b = undecchar(k[4]); - if (a <= 0 || b < 0) - return -EINVAL; - - t = BUS_MATCH_ARG_NAMESPACE + a * 10 + b; - if (t > BUS_MATCH_ARG_NAMESPACE_LAST) - return -EINVAL; - - return t; - } - - if (n == 7 && startswith(k, "arg") && startswith(k + 4, "has")) { - int j; - - j = undecchar(k[3]); - if (j < 0) - return -EINVAL; - - return BUS_MATCH_ARG_HAS + j; - } - - if (n == 8 && startswith(k, "arg") && startswith(k + 5, "has")) { - enum bus_match_node_type t; - int a, b; - - a = undecchar(k[3]); - b = undecchar(k[4]); - if (a <= 0 || b < 0) - return -EINVAL; - - t = BUS_MATCH_ARG_HAS + a * 10 + b; - if (t > BUS_MATCH_ARG_HAS_LAST) - return -EINVAL; - - return t; - } - - return -EINVAL; -} - -static int match_component_compare(const void *a, const void *b) { - const struct bus_match_component *x = a, *y = b; - - if (x->type < y->type) - return -1; - if (x->type > y->type) - return 1; - - return 0; -} - -void bus_match_parse_free(struct bus_match_component *components, unsigned n_components) { - unsigned i; - - for (i = 0; i < n_components; i++) - free(components[i].value_str); - - free(components); -} - -int bus_match_parse( - const char *match, - struct bus_match_component **_components, - unsigned *_n_components) { - - const char *p = match; - struct bus_match_component *components = NULL; - size_t components_allocated = 0; - unsigned n_components = 0, i; - _cleanup_free_ char *value = NULL; - int r; - - assert(match); - assert(_components); - assert(_n_components); - - while (*p != 0) { - const char *eq, *q; - enum bus_match_node_type t; - unsigned j = 0; - size_t value_allocated = 0; - bool escaped = false, quoted; - uint8_t u; - - /* Avahi's match rules appear to include whitespace, skip over it */ - p += strspn(p, " "); - - eq = strchr(p, '='); - if (!eq) - return -EINVAL; - - t = bus_match_node_type_from_string(p, eq - p); - if (t < 0) - return -EINVAL; - - quoted = eq[1] == '\''; - - for (q = eq + 1 + quoted;; q++) { - - if (*q == 0) { - - if (quoted) { - r = -EINVAL; - goto fail; - } else { - if (value) - value[j] = 0; - break; - } - } - - if (!escaped) { - if (*q == '\\') { - escaped = true; - continue; - } - - if (quoted) { - if (*q == '\'') { - if (value) - value[j] = 0; - break; - } - } else { - if (*q == ',') { - if (value) - value[j] = 0; - - break; - } - } - } - - if (!GREEDY_REALLOC(value, value_allocated, j + 2)) { - r = -ENOMEM; - goto fail; - } - - value[j++] = *q; - escaped = false; - } - - if (!value) { - value = strdup(""); - if (!value) { - r = -ENOMEM; - goto fail; - } - } - - if (t == BUS_MATCH_MESSAGE_TYPE) { - r = bus_message_type_from_string(value, &u); - if (r < 0) - goto fail; - - value = mfree(value); - } else - u = 0; - - if (!GREEDY_REALLOC(components, components_allocated, n_components + 1)) { - r = -ENOMEM; - goto fail; - } - - components[n_components].type = t; - components[n_components].value_str = value; - components[n_components].value_u8 = u; - n_components++; - - value = NULL; - - if (q[quoted] == 0) - break; - - if (q[quoted] != ',') { - r = -EINVAL; - goto fail; - } - - p = q + 1 + quoted; - } - - /* Order the whole thing, so that we always generate the same tree */ - qsort_safe(components, n_components, sizeof(struct bus_match_component), match_component_compare); - - /* Check for duplicates */ - for (i = 0; i+1 < n_components; i++) - if (components[i].type == components[i+1].type) { - r = -EINVAL; - goto fail; - } - - *_components = components; - *_n_components = n_components; - - return 0; - -fail: - bus_match_parse_free(components, n_components); - return r; -} - -char *bus_match_to_string(struct bus_match_component *components, unsigned n_components) { - _cleanup_fclose_ FILE *f = NULL; - char *buffer = NULL; - size_t size = 0; - unsigned i; - int r; - - if (n_components <= 0) - return strdup(""); - - assert(components); - - f = open_memstream(&buffer, &size); - if (!f) - return NULL; - - for (i = 0; i < n_components; i++) { - char buf[32]; - - if (i != 0) - fputc(',', f); - - fputs(bus_match_node_type_to_string(components[i].type, buf, sizeof(buf)), f); - fputc('=', f); - fputc('\'', f); - - if (components[i].type == BUS_MATCH_MESSAGE_TYPE) - fputs(bus_message_type_to_string(components[i].value_u8), f); - else - fputs(components[i].value_str, f); - - fputc('\'', f); - } - - r = fflush_and_check(f); - if (r < 0) - return NULL; - - return buffer; -} - -int bus_match_add( - struct bus_match_node *root, - struct bus_match_component *components, - unsigned n_components, - struct match_callback *callback) { - - unsigned i; - struct bus_match_node *n; - int r; - - assert(root); - assert(callback); - - n = root; - for (i = 0; i < n_components; i++) { - r = bus_match_add_compare_value( - n, components[i].type, - components[i].value_u8, components[i].value_str, &n); - if (r < 0) - return r; - } - - return bus_match_add_leaf(n, callback); -} - -int bus_match_remove( - struct bus_match_node *root, - struct match_callback *callback) { - - struct bus_match_node *node, *pp; - - assert(root); - assert(callback); - - node = callback->match_node; - if (!node) - return 0; - - assert(node->type == BUS_MATCH_LEAF); - - callback->match_node = NULL; - - /* Free the leaf */ - pp = node->parent; - bus_match_node_free(node); - - /* Prune the tree above */ - while (pp) { - node = pp; - pp = node->parent; - - if (!bus_match_node_maybe_free(node)) - break; - } - - return 1; -} - -int bus_match_find( - struct bus_match_node *root, - struct bus_match_component *components, - unsigned n_components, - sd_bus_message_handler_t callback, - void *userdata, - struct match_callback **ret) { - - struct bus_match_node *n, **gc; - unsigned i; - int r; - - assert(root); - assert(ret); - - gc = newa(struct bus_match_node*, n_components); - - n = root; - for (i = 0; i < n_components; i++) { - r = bus_match_find_compare_value( - n, components[i].type, - components[i].value_u8, components[i].value_str, - &n); - if (r <= 0) - return r; - - gc[i] = n; - } - - r = bus_match_find_leaf(n, callback, userdata, &n); - if (r <= 0) - return r; - - *ret = n->leaf.callback; - return 1; -} - -void bus_match_free(struct bus_match_node *node) { - struct bus_match_node *c; - - if (!node) - return; - - if (BUS_MATCH_CAN_HASH(node->type)) { - Iterator i; - - HASHMAP_FOREACH(c, node->compare.children, i) - bus_match_free(c); - - assert(hashmap_isempty(node->compare.children)); - } - - while ((c = node->child)) - bus_match_free(c); - - if (node->type != BUS_MATCH_ROOT) - bus_match_node_free(node); -} - -const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l) { - switch (t) { - - case BUS_MATCH_ROOT: - return "root"; - - case BUS_MATCH_VALUE: - return "value"; - - case BUS_MATCH_LEAF: - return "leaf"; - - case BUS_MATCH_MESSAGE_TYPE: - return "type"; - - case BUS_MATCH_SENDER: - return "sender"; - - case BUS_MATCH_DESTINATION: - return "destination"; - - case BUS_MATCH_INTERFACE: - return "interface"; - - case BUS_MATCH_MEMBER: - return "member"; - - case BUS_MATCH_PATH: - return "path"; - - case BUS_MATCH_PATH_NAMESPACE: - return "path_namespace"; - - case BUS_MATCH_ARG ... BUS_MATCH_ARG_LAST: - snprintf(buf, l, "arg%i", t - BUS_MATCH_ARG); - return buf; - - case BUS_MATCH_ARG_PATH ... BUS_MATCH_ARG_PATH_LAST: - snprintf(buf, l, "arg%ipath", t - BUS_MATCH_ARG_PATH); - return buf; - - case BUS_MATCH_ARG_NAMESPACE ... BUS_MATCH_ARG_NAMESPACE_LAST: - snprintf(buf, l, "arg%inamespace", t - BUS_MATCH_ARG_NAMESPACE); - return buf; - - case BUS_MATCH_ARG_HAS ... BUS_MATCH_ARG_HAS_LAST: - snprintf(buf, l, "arg%ihas", t - BUS_MATCH_ARG_HAS); - return buf; - - default: - return NULL; - } -} - -void bus_match_dump(struct bus_match_node *node, unsigned level) { - struct bus_match_node *c; - _cleanup_free_ char *pfx = NULL; - char buf[32]; - - if (!node) - return; - - pfx = strrep(" ", level); - printf("%s[%s]", strempty(pfx), bus_match_node_type_to_string(node->type, buf, sizeof(buf))); - - if (node->type == BUS_MATCH_VALUE) { - if (node->parent->type == BUS_MATCH_MESSAGE_TYPE) - printf(" <%u>\n", node->value.u8); - else - printf(" <%s>\n", node->value.str); - } else if (node->type == BUS_MATCH_ROOT) - puts(" root"); - else if (node->type == BUS_MATCH_LEAF) - printf(" %p/%p\n", node->leaf.callback->callback, container_of(node->leaf.callback, sd_bus_slot, match_callback)->userdata); - else - putchar('\n'); - - if (BUS_MATCH_CAN_HASH(node->type)) { - Iterator i; - - HASHMAP_FOREACH(c, node->compare.children, i) - bus_match_dump(c, level + 1); - } - - for (c = node->child; c; c = c->next) - bus_match_dump(c, level + 1); -} - -enum bus_match_scope bus_match_get_scope(const struct bus_match_component *components, unsigned n_components) { - bool found_driver = false; - unsigned i; - - if (n_components <= 0) - return BUS_MATCH_GENERIC; - - assert(components); - - /* Checks whether the specified match can only match the - * pseudo-service for local messages, which we detect by - * sender, interface or path. If a match is not restricted to - * local messages, then we check if it only matches on the - * driver. */ - - for (i = 0; i < n_components; i++) { - const struct bus_match_component *c = components + i; - - if (c->type == BUS_MATCH_SENDER) { - if (streq_ptr(c->value_str, "org.freedesktop.DBus.Local")) - return BUS_MATCH_LOCAL; - - if (streq_ptr(c->value_str, "org.freedesktop.DBus")) - found_driver = true; - } - - if (c->type == BUS_MATCH_INTERFACE && streq_ptr(c->value_str, "org.freedesktop.DBus.Local")) - return BUS_MATCH_LOCAL; - - if (c->type == BUS_MATCH_PATH && streq_ptr(c->value_str, "/org/freedesktop/DBus/Local")) - return BUS_MATCH_LOCAL; - } - - return found_driver ? BUS_MATCH_DRIVER : BUS_MATCH_GENERIC; - -} diff --git a/src/libsystemd/sd-bus/bus-match.h b/src/libsystemd/sd-bus/bus-match.h deleted file mode 100644 index 8cbbb63b11..0000000000 --- a/src/libsystemd/sd-bus/bus-match.h +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "sd-bus.h" - -#include "hashmap.h" - -enum bus_match_node_type { - BUS_MATCH_ROOT, - BUS_MATCH_VALUE, - BUS_MATCH_LEAF, - - /* The following are all different kinds of compare nodes */ - BUS_MATCH_SENDER, - BUS_MATCH_MESSAGE_TYPE, - BUS_MATCH_DESTINATION, - BUS_MATCH_INTERFACE, - BUS_MATCH_MEMBER, - BUS_MATCH_PATH, - BUS_MATCH_PATH_NAMESPACE, - BUS_MATCH_ARG, - BUS_MATCH_ARG_LAST = BUS_MATCH_ARG + 63, - BUS_MATCH_ARG_PATH, - BUS_MATCH_ARG_PATH_LAST = BUS_MATCH_ARG_PATH + 63, - BUS_MATCH_ARG_NAMESPACE, - BUS_MATCH_ARG_NAMESPACE_LAST = BUS_MATCH_ARG_NAMESPACE + 63, - BUS_MATCH_ARG_HAS, - BUS_MATCH_ARG_HAS_LAST = BUS_MATCH_ARG_HAS + 63, - _BUS_MATCH_NODE_TYPE_MAX, - _BUS_MATCH_NODE_TYPE_INVALID = -1 -}; - -struct bus_match_node { - enum bus_match_node_type type; - struct bus_match_node *parent, *next, *prev, *child; - - union { - struct { - char *str; - uint8_t u8; - } value; - struct { - struct match_callback *callback; - } leaf; - struct { - /* If this is set, then the child is NULL */ - Hashmap *children; - } compare; - }; -}; - -struct bus_match_component { - enum bus_match_node_type type; - uint8_t value_u8; - char *value_str; -}; - -enum bus_match_scope { - BUS_MATCH_GENERIC, - BUS_MATCH_LOCAL, - BUS_MATCH_DRIVER, -}; - -int bus_match_run(sd_bus *bus, struct bus_match_node *root, sd_bus_message *m); - -int bus_match_add(struct bus_match_node *root, struct bus_match_component *components, unsigned n_components, struct match_callback *callback); -int bus_match_remove(struct bus_match_node *root, struct match_callback *callback); - -int bus_match_find(struct bus_match_node *root, struct bus_match_component *components, unsigned n_components, sd_bus_message_handler_t callback, void *userdata, struct match_callback **ret); - -void bus_match_free(struct bus_match_node *node); - -void bus_match_dump(struct bus_match_node *node, unsigned level); - -const char* bus_match_node_type_to_string(enum bus_match_node_type t, char buf[], size_t l); -enum bus_match_node_type bus_match_node_type_from_string(const char *k, size_t n); - -int bus_match_parse(const char *match, struct bus_match_component **_components, unsigned *_n_components); -void bus_match_parse_free(struct bus_match_component *components, unsigned n_components); -char *bus_match_to_string(struct bus_match_component *components, unsigned n_components); - -enum bus_match_scope bus_match_get_scope(const struct bus_match_component *components, unsigned n_components); diff --git a/src/libsystemd/sd-bus/bus-message.c b/src/libsystemd/sd-bus/bus-message.c deleted file mode 100644 index 7be28c509b..0000000000 --- a/src/libsystemd/sd-bus/bus-message.c +++ /dev/null @@ -1,5949 +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 . -***/ - -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-gvariant.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-signature.h" -#include "bus-type.h" -#include "bus-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "memfd-util.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "utf8.h" -#include "util.h" - -static int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored); - -static void *adjust_pointer(const void *p, void *old_base, size_t sz, void *new_base) { - - if (p == NULL) - return NULL; - - if (old_base == new_base) - return (void*) p; - - if ((uint8_t*) p < (uint8_t*) old_base) - return (void*) p; - - if ((uint8_t*) p >= (uint8_t*) old_base + sz) - return (void*) p; - - return (uint8_t*) new_base + ((uint8_t*) p - (uint8_t*) old_base); -} - -static void message_free_part(sd_bus_message *m, struct bus_body_part *part) { - assert(m); - assert(part); - - if (part->memfd >= 0) { - /* If we can reuse the memfd, try that. For that it - * can't be sealed yet. */ - - if (!part->sealed) { - assert(part->memfd_offset == 0); - assert(part->data == part->mmap_begin); - bus_kernel_push_memfd(m->bus, part->memfd, part->data, part->mapped, part->allocated); - } else { - if (part->mapped > 0) - assert_se(munmap(part->mmap_begin, part->mapped) == 0); - - safe_close(part->memfd); - } - - } else if (part->munmap_this) - munmap(part->mmap_begin, part->mapped); - else if (part->free_this) - free(part->data); - - if (part != &m->body) - free(part); -} - -static void message_reset_parts(sd_bus_message *m) { - struct bus_body_part *part; - - assert(m); - - part = &m->body; - while (m->n_body_parts > 0) { - struct bus_body_part *next = part->next; - message_free_part(m, part); - part = next; - m->n_body_parts--; - } - - m->body_end = NULL; - - m->cached_rindex_part = NULL; - m->cached_rindex_part_begin = 0; -} - -static void message_reset_containers(sd_bus_message *m) { - unsigned i; - - assert(m); - - for (i = 0; i < m->n_containers; i++) { - free(m->containers[i].signature); - free(m->containers[i].offsets); - } - - m->containers = mfree(m->containers); - - m->n_containers = m->containers_allocated = 0; - m->root_container.index = 0; -} - -static void message_free(sd_bus_message *m) { - assert(m); - - if (m->free_header) - free(m->header); - - message_reset_parts(m); - - if (m->release_kdbus) - bus_kernel_cmd_free(m->bus, (uint8_t *) m->kdbus - (uint8_t *) m->bus->kdbus_buffer); - - if (m->free_kdbus) - free(m->kdbus); - - sd_bus_unref(m->bus); - - if (m->free_fds) { - close_many(m->fds, m->n_fds); - free(m->fds); - } - - if (m->iovec != m->iovec_fixed) - free(m->iovec); - - m->destination_ptr = mfree(m->destination_ptr); - message_reset_containers(m); - free(m->root_container.signature); - free(m->root_container.offsets); - - free(m->root_container.peeked_signature); - - bus_creds_done(&m->creds); - free(m); -} - -static void *message_extend_fields(sd_bus_message *m, size_t align, size_t sz, bool add_offset) { - void *op, *np; - size_t old_size, new_size, start; - - assert(m); - - if (m->poisoned) - return NULL; - - old_size = sizeof(struct bus_header) + m->fields_size; - start = ALIGN_TO(old_size, align); - new_size = start + sz; - - if (new_size < start || - new_size > (size_t) ((uint32_t) -1)) - goto poison; - - if (old_size == new_size) - return (uint8_t*) m->header + old_size; - - if (m->free_header) { - np = realloc(m->header, ALIGN8(new_size)); - if (!np) - goto poison; - } else { - /* Initially, the header is allocated as part of of - * the sd_bus_message itself, let's replace it by - * dynamic data */ - - np = malloc(ALIGN8(new_size)); - if (!np) - goto poison; - - memcpy(np, m->header, sizeof(struct bus_header)); - } - - /* Zero out padding */ - if (start > old_size) - memzero((uint8_t*) np + old_size, start - old_size); - - op = m->header; - m->header = np; - m->fields_size = new_size - sizeof(struct bus_header); - - /* Adjust quick access pointers */ - m->path = adjust_pointer(m->path, op, old_size, m->header); - m->interface = adjust_pointer(m->interface, op, old_size, m->header); - m->member = adjust_pointer(m->member, op, old_size, m->header); - m->destination = adjust_pointer(m->destination, op, old_size, m->header); - m->sender = adjust_pointer(m->sender, op, old_size, m->header); - m->error.name = adjust_pointer(m->error.name, op, old_size, m->header); - - m->free_header = true; - - if (add_offset) { - if (m->n_header_offsets >= ELEMENTSOF(m->header_offsets)) - goto poison; - - m->header_offsets[m->n_header_offsets++] = new_size - sizeof(struct bus_header); - } - - return (uint8_t*) np + start; - -poison: - m->poisoned = true; - return NULL; -} - -static int message_append_field_string( - sd_bus_message *m, - uint64_t h, - char type, - const char *s, - const char **ret) { - - size_t l; - uint8_t *p; - - assert(m); - - /* dbus1 only allows 8bit header field ids */ - if (h > 0xFF) - return -EINVAL; - - /* dbus1 doesn't allow strings over 32bit, let's enforce this - * globally, to not risk convertability */ - l = strlen(s); - if (l > (size_t) (uint32_t) -1) - return -EINVAL; - - /* Signature "(yv)" where the variant contains "s" */ - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - - /* (field id 64bit, ((string + NUL) + NUL + signature string 's') */ - p = message_extend_fields(m, 8, 8 + l + 1 + 1 + 1, true); - if (!p) - return -ENOMEM; - - *((uint64_t*) p) = h; - memcpy(p+8, s, l); - p[8+l] = 0; - p[8+l+1] = 0; - p[8+l+2] = type; - - if (ret) - *ret = (char*) p + 8; - - } else { - /* (field id byte + (signature length + signature 's' + NUL) + (string length + string + NUL)) */ - p = message_extend_fields(m, 8, 4 + 4 + l + 1, false); - if (!p) - return -ENOMEM; - - p[0] = (uint8_t) h; - p[1] = 1; - p[2] = type; - p[3] = 0; - - ((uint32_t*) p)[1] = l; - memcpy(p + 8, s, l + 1); - - if (ret) - *ret = (char*) p + 8; - } - - return 0; -} - -static int message_append_field_signature( - sd_bus_message *m, - uint64_t h, - const char *s, - const char **ret) { - - size_t l; - uint8_t *p; - - assert(m); - - /* dbus1 only allows 8bit header field ids */ - if (h > 0xFF) - return -EINVAL; - - /* dbus1 doesn't allow signatures over 8bit, let's enforce - * this globally, to not risk convertability */ - l = strlen(s); - if (l > 255) - return -EINVAL; - - /* Signature "(yv)" where the variant contains "g" */ - - if (BUS_MESSAGE_IS_GVARIANT(m)) - /* For gvariant the serialization is the same as for normal strings */ - return message_append_field_string(m, h, 'g', s, ret); - else { - /* (field id byte + (signature length + signature 'g' + NUL) + (string length + string + NUL)) */ - p = message_extend_fields(m, 8, 4 + 1 + l + 1, false); - if (!p) - return -ENOMEM; - - p[0] = (uint8_t) h; - p[1] = 1; - p[2] = SD_BUS_TYPE_SIGNATURE; - p[3] = 0; - p[4] = l; - memcpy(p + 5, s, l + 1); - - if (ret) - *ret = (const char*) p + 5; - } - - return 0; -} - -static int message_append_field_uint32(sd_bus_message *m, uint64_t h, uint32_t x) { - uint8_t *p; - - assert(m); - - /* dbus1 only allows 8bit header field ids */ - if (h > 0xFF) - return -EINVAL; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - /* (field id 64bit + ((value + NUL + signature string 'u') */ - - p = message_extend_fields(m, 8, 8 + 4 + 1 + 1, true); - if (!p) - return -ENOMEM; - - *((uint64_t*) p) = h; - *((uint32_t*) (p + 8)) = x; - p[12] = 0; - p[13] = 'u'; - } else { - /* (field id byte + (signature length + signature 'u' + NUL) + value) */ - p = message_extend_fields(m, 8, 4 + 4, false); - if (!p) - return -ENOMEM; - - p[0] = (uint8_t) h; - p[1] = 1; - p[2] = 'u'; - p[3] = 0; - - ((uint32_t*) p)[1] = x; - } - - return 0; -} - -static int message_append_field_uint64(sd_bus_message *m, uint64_t h, uint64_t x) { - uint8_t *p; - - assert(m); - - /* dbus1 only allows 8bit header field ids */ - if (h > 0xFF) - return -EINVAL; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - /* (field id 64bit + ((value + NUL + signature string 't') */ - - p = message_extend_fields(m, 8, 8 + 8 + 1 + 1, true); - if (!p) - return -ENOMEM; - - *((uint64_t*) p) = h; - *((uint64_t*) (p + 8)) = x; - p[16] = 0; - p[17] = 't'; - } else { - /* (field id byte + (signature length + signature 't' + NUL) + 4 byte padding + value) */ - p = message_extend_fields(m, 8, 4 + 4 + 8, false); - if (!p) - return -ENOMEM; - - p[0] = (uint8_t) h; - p[1] = 1; - p[2] = 't'; - p[3] = 0; - p[4] = 0; - p[5] = 0; - p[6] = 0; - p[7] = 0; - - ((uint64_t*) p)[1] = x; - } - - return 0; -} - -static int message_append_reply_cookie(sd_bus_message *m, uint64_t cookie) { - assert(m); - - if (BUS_MESSAGE_IS_GVARIANT(m)) - return message_append_field_uint64(m, BUS_MESSAGE_HEADER_REPLY_SERIAL, cookie); - else { - /* 64bit cookies are not supported on dbus1 */ - if (cookie > 0xffffffffUL) - return -EOPNOTSUPP; - - return message_append_field_uint32(m, BUS_MESSAGE_HEADER_REPLY_SERIAL, (uint32_t) cookie); - } -} - -int bus_message_from_header( - sd_bus *bus, - void *header, - size_t header_accessible, - void *footer, - size_t footer_accessible, - size_t message_size, - int *fds, - unsigned n_fds, - const char *label, - size_t extra, - sd_bus_message **ret) { - - _cleanup_free_ sd_bus_message *m = NULL; - struct bus_header *h; - size_t a, label_sz; - - assert(bus); - assert(header || header_accessible <= 0); - assert(footer || footer_accessible <= 0); - assert(fds || n_fds <= 0); - assert(ret); - - if (header_accessible < sizeof(struct bus_header)) - return -EBADMSG; - - if (header_accessible > message_size) - return -EBADMSG; - if (footer_accessible > message_size) - return -EBADMSG; - - h = header; - if (!IN_SET(h->version, 1, 2)) - return -EBADMSG; - - if (h->type == _SD_BUS_MESSAGE_TYPE_INVALID) - return -EBADMSG; - - if (!IN_SET(h->endian, BUS_LITTLE_ENDIAN, BUS_BIG_ENDIAN)) - return -EBADMSG; - - /* Note that we are happy with unknown flags in the flags header! */ - - a = ALIGN(sizeof(sd_bus_message)) + ALIGN(extra); - - if (label) { - label_sz = strlen(label); - a += label_sz + 1; - } - - m = malloc0(a); - if (!m) - return -ENOMEM; - - m->n_ref = 1; - m->sealed = true; - m->header = header; - m->header_accessible = header_accessible; - m->footer = footer; - m->footer_accessible = footer_accessible; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - size_t ws; - - if (h->dbus2.cookie == 0) - return -EBADMSG; - - /* dbus2 derives the sizes from the message size and - the offset table at the end, since it is formatted as - gvariant "yyyyuta{tv}v". Since the message itself is a - structure with precisely to variable sized entries, - there's only one offset in the table, which marks the - end of the fields array. */ - - ws = bus_gvariant_determine_word_size(message_size, 0); - if (footer_accessible < ws) - return -EBADMSG; - - m->fields_size = bus_gvariant_read_word_le((uint8_t*) footer + footer_accessible - ws, ws); - if (ALIGN8(m->fields_size) > message_size - ws) - return -EBADMSG; - if (m->fields_size < sizeof(struct bus_header)) - return -EBADMSG; - - m->fields_size -= sizeof(struct bus_header); - m->body_size = message_size - (sizeof(struct bus_header) + ALIGN8(m->fields_size)); - } else { - if (h->dbus1.serial == 0) - return -EBADMSG; - - /* dbus1 has the sizes in the header */ - m->fields_size = BUS_MESSAGE_BSWAP32(m, h->dbus1.fields_size); - m->body_size = BUS_MESSAGE_BSWAP32(m, h->dbus1.body_size); - - if (sizeof(struct bus_header) + ALIGN8(m->fields_size) + m->body_size != message_size) - return -EBADMSG; - } - - m->fds = fds; - m->n_fds = n_fds; - - if (label) { - m->creds.label = (char*) m + ALIGN(sizeof(sd_bus_message)) + ALIGN(extra); - memcpy(m->creds.label, label, label_sz + 1); - - m->creds.mask |= SD_BUS_CREDS_SELINUX_CONTEXT; - } - - m->bus = sd_bus_ref(bus); - *ret = m; - m = NULL; - - return 0; -} - -int bus_message_from_malloc( - sd_bus *bus, - void *buffer, - size_t length, - int *fds, - unsigned n_fds, - const char *label, - sd_bus_message **ret) { - - sd_bus_message *m; - size_t sz; - int r; - - r = bus_message_from_header( - bus, - buffer, length, /* in this case the initial bytes and the final bytes are the same */ - buffer, length, - length, - fds, n_fds, - label, - 0, &m); - if (r < 0) - return r; - - sz = length - sizeof(struct bus_header) - ALIGN8(m->fields_size); - if (sz > 0) { - m->n_body_parts = 1; - m->body.data = (uint8_t*) buffer + sizeof(struct bus_header) + ALIGN8(m->fields_size); - m->body.size = sz; - m->body.sealed = true; - m->body.memfd = -1; - } - - m->n_iovec = 1; - m->iovec = m->iovec_fixed; - m->iovec[0].iov_base = buffer; - m->iovec[0].iov_len = length; - - r = bus_message_parse_fields(m); - if (r < 0) - goto fail; - - /* We take possession of the memory and fds now */ - m->free_header = true; - m->free_fds = true; - - *ret = m; - return 0; - -fail: - message_free(m); - return r; -} - -static sd_bus_message *message_new(sd_bus *bus, uint8_t type) { - sd_bus_message *m; - - assert(bus); - - m = malloc0(ALIGN(sizeof(sd_bus_message)) + sizeof(struct bus_header)); - if (!m) - return NULL; - - m->n_ref = 1; - m->header = (struct bus_header*) ((uint8_t*) m + ALIGN(sizeof(struct sd_bus_message))); - m->header->endian = BUS_NATIVE_ENDIAN; - m->header->type = type; - m->header->version = bus->message_version; - m->allow_fds = bus->can_fds || (bus->state != BUS_HELLO && bus->state != BUS_RUNNING); - m->root_container.need_offsets = BUS_MESSAGE_IS_GVARIANT(m); - m->bus = sd_bus_ref(bus); - - if (bus->allow_interactive_authorization) - m->header->flags |= BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION; - - return m; -} - -_public_ int sd_bus_message_new_signal( - sd_bus *bus, - sd_bus_message **m, - const char *path, - const char *interface, - const char *member) { - - sd_bus_message *t; - int r; - - assert_return(bus, -ENOTCONN); - assert_return(bus->state != BUS_UNSET, -ENOTCONN); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(interface_name_is_valid(interface), -EINVAL); - assert_return(member_name_is_valid(member), -EINVAL); - assert_return(m, -EINVAL); - - t = message_new(bus, SD_BUS_MESSAGE_SIGNAL); - if (!t) - return -ENOMEM; - - t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; - - r = message_append_field_string(t, BUS_MESSAGE_HEADER_PATH, SD_BUS_TYPE_OBJECT_PATH, path, &t->path); - if (r < 0) - goto fail; - r = message_append_field_string(t, BUS_MESSAGE_HEADER_INTERFACE, SD_BUS_TYPE_STRING, interface, &t->interface); - if (r < 0) - goto fail; - r = message_append_field_string(t, BUS_MESSAGE_HEADER_MEMBER, SD_BUS_TYPE_STRING, member, &t->member); - if (r < 0) - goto fail; - - *m = t; - return 0; - -fail: - sd_bus_message_unref(t); - return r; -} - -_public_ int sd_bus_message_new_method_call( - sd_bus *bus, - sd_bus_message **m, - const char *destination, - const char *path, - const char *interface, - const char *member) { - - sd_bus_message *t; - int r; - - assert_return(bus, -ENOTCONN); - assert_return(bus->state != BUS_UNSET, -ENOTCONN); - assert_return(!destination || service_name_is_valid(destination), -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!interface || interface_name_is_valid(interface), -EINVAL); - assert_return(member_name_is_valid(member), -EINVAL); - assert_return(m, -EINVAL); - - t = message_new(bus, SD_BUS_MESSAGE_METHOD_CALL); - if (!t) - return -ENOMEM; - - r = message_append_field_string(t, BUS_MESSAGE_HEADER_PATH, SD_BUS_TYPE_OBJECT_PATH, path, &t->path); - if (r < 0) - goto fail; - r = message_append_field_string(t, BUS_MESSAGE_HEADER_MEMBER, SD_BUS_TYPE_STRING, member, &t->member); - if (r < 0) - goto fail; - - if (interface) { - r = message_append_field_string(t, BUS_MESSAGE_HEADER_INTERFACE, SD_BUS_TYPE_STRING, interface, &t->interface); - if (r < 0) - goto fail; - } - - if (destination) { - r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, destination, &t->destination); - if (r < 0) - goto fail; - } - - *m = t; - return 0; - -fail: - message_free(t); - return r; -} - -static int message_new_reply( - sd_bus_message *call, - uint8_t type, - sd_bus_message **m) { - - sd_bus_message *t; - int r; - - assert_return(call, -EINVAL); - assert_return(call->sealed, -EPERM); - assert_return(call->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(call->bus->state != BUS_UNSET, -ENOTCONN); - assert_return(m, -EINVAL); - - t = message_new(call->bus, type); - if (!t) - return -ENOMEM; - - t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; - t->reply_cookie = BUS_MESSAGE_COOKIE(call); - if (t->reply_cookie == 0) - return -EOPNOTSUPP; - - r = message_append_reply_cookie(t, t->reply_cookie); - if (r < 0) - goto fail; - - if (call->sender) { - r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, call->sender, &t->destination); - if (r < 0) - goto fail; - } - - t->dont_send = !!(call->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED); - t->enforced_reply_signature = call->enforced_reply_signature; - - *m = t; - return 0; - -fail: - message_free(t); - return r; -} - -_public_ int sd_bus_message_new_method_return( - sd_bus_message *call, - sd_bus_message **m) { - - return message_new_reply(call, SD_BUS_MESSAGE_METHOD_RETURN, m); -} - -_public_ int sd_bus_message_new_method_error( - sd_bus_message *call, - sd_bus_message **m, - const sd_bus_error *e) { - - sd_bus_message *t; - int r; - - assert_return(sd_bus_error_is_set(e), -EINVAL); - assert_return(m, -EINVAL); - - r = message_new_reply(call, SD_BUS_MESSAGE_METHOD_ERROR, &t); - if (r < 0) - return r; - - r = message_append_field_string(t, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, e->name, &t->error.name); - if (r < 0) - goto fail; - - if (e->message) { - r = message_append_basic(t, SD_BUS_TYPE_STRING, e->message, (const void**) &t->error.message); - if (r < 0) - goto fail; - } - - t->error._need_free = -1; - - *m = t; - return 0; - -fail: - message_free(t); - return r; -} - -_public_ int sd_bus_message_new_method_errorf( - sd_bus_message *call, - sd_bus_message **m, - const char *name, - const char *format, - ...) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - va_list ap; - - assert_return(name, -EINVAL); - assert_return(m, -EINVAL); - - va_start(ap, format); - bus_error_setfv(&error, name, format, ap); - va_end(ap); - - return sd_bus_message_new_method_error(call, m, &error); -} - -_public_ int sd_bus_message_new_method_errno( - sd_bus_message *call, - sd_bus_message **m, - int error, - const sd_bus_error *p) { - - _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; - - if (sd_bus_error_is_set(p)) - return sd_bus_message_new_method_error(call, m, p); - - sd_bus_error_set_errno(&berror, error); - - return sd_bus_message_new_method_error(call, m, &berror); -} - -_public_ int sd_bus_message_new_method_errnof( - sd_bus_message *call, - sd_bus_message **m, - int error, - const char *format, - ...) { - - _cleanup_(sd_bus_error_free) sd_bus_error berror = SD_BUS_ERROR_NULL; - va_list ap; - - va_start(ap, format); - sd_bus_error_set_errnofv(&berror, error, format, ap); - va_end(ap); - - return sd_bus_message_new_method_error(call, m, &berror); -} - -void bus_message_set_sender_local(sd_bus *bus, sd_bus_message *m) { - assert(bus); - assert(m); - - m->sender = m->creds.unique_name = (char*) "org.freedesktop.DBus.Local"; - m->creds.well_known_names_local = true; - m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask; -} - -void bus_message_set_sender_driver(sd_bus *bus, sd_bus_message *m) { - assert(bus); - assert(m); - - m->sender = m->creds.unique_name = (char*) "org.freedesktop.DBus"; - m->creds.well_known_names_driver = true; - m->creds.mask |= (SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_WELL_KNOWN_NAMES) & bus->creds_mask; -} - -int bus_message_new_synthetic_error( - sd_bus *bus, - uint64_t cookie, - const sd_bus_error *e, - sd_bus_message **m) { - - sd_bus_message *t; - int r; - - assert(bus); - assert(sd_bus_error_is_set(e)); - assert(m); - - t = message_new(bus, SD_BUS_MESSAGE_METHOD_ERROR); - if (!t) - return -ENOMEM; - - t->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; - t->reply_cookie = cookie; - - r = message_append_reply_cookie(t, t->reply_cookie); - if (r < 0) - goto fail; - - if (bus && bus->unique_name) { - r = message_append_field_string(t, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, bus->unique_name, &t->destination); - if (r < 0) - goto fail; - } - - r = message_append_field_string(t, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, e->name, &t->error.name); - if (r < 0) - goto fail; - - if (e->message) { - r = message_append_basic(t, SD_BUS_TYPE_STRING, e->message, (const void**) &t->error.message); - if (r < 0) - goto fail; - } - - t->error._need_free = -1; - - bus_message_set_sender_driver(bus, t); - - *m = t; - return 0; - -fail: - message_free(t); - return r; -} - -_public_ sd_bus_message* sd_bus_message_ref(sd_bus_message *m) { - - if (!m) - return NULL; - - assert(m->n_ref > 0); - m->n_ref++; - - return m; -} - -_public_ sd_bus_message* sd_bus_message_unref(sd_bus_message *m) { - - if (!m) - return NULL; - - assert(m->n_ref > 0); - m->n_ref--; - - if (m->n_ref > 0) - return NULL; - - message_free(m); - return NULL; -} - -_public_ int sd_bus_message_get_type(sd_bus_message *m, uint8_t *type) { - assert_return(m, -EINVAL); - assert_return(type, -EINVAL); - - *type = m->header->type; - return 0; -} - -_public_ int sd_bus_message_get_cookie(sd_bus_message *m, uint64_t *cookie) { - uint64_t c; - - assert_return(m, -EINVAL); - assert_return(cookie, -EINVAL); - - c = BUS_MESSAGE_COOKIE(m); - if (c == 0) - return -ENODATA; - - *cookie = BUS_MESSAGE_COOKIE(m); - return 0; -} - -_public_ int sd_bus_message_get_reply_cookie(sd_bus_message *m, uint64_t *cookie) { - assert_return(m, -EINVAL); - assert_return(cookie, -EINVAL); - - if (m->reply_cookie == 0) - return -ENODATA; - - *cookie = m->reply_cookie; - return 0; -} - -_public_ int sd_bus_message_get_expect_reply(sd_bus_message *m) { - assert_return(m, -EINVAL); - - return m->header->type == SD_BUS_MESSAGE_METHOD_CALL && - !(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED); -} - -_public_ int sd_bus_message_get_auto_start(sd_bus_message *m) { - assert_return(m, -EINVAL); - - return !(m->header->flags & BUS_MESSAGE_NO_AUTO_START); -} - -_public_ int sd_bus_message_get_allow_interactive_authorization(sd_bus_message *m) { - assert_return(m, -EINVAL); - - return m->header->type == SD_BUS_MESSAGE_METHOD_CALL && - (m->header->flags & BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION); -} - -_public_ const char *sd_bus_message_get_path(sd_bus_message *m) { - assert_return(m, NULL); - - return m->path; -} - -_public_ const char *sd_bus_message_get_interface(sd_bus_message *m) { - assert_return(m, NULL); - - return m->interface; -} - -_public_ const char *sd_bus_message_get_member(sd_bus_message *m) { - assert_return(m, NULL); - - return m->member; -} - -_public_ const char *sd_bus_message_get_destination(sd_bus_message *m) { - assert_return(m, NULL); - - return m->destination; -} - -_public_ const char *sd_bus_message_get_sender(sd_bus_message *m) { - assert_return(m, NULL); - - return m->sender; -} - -_public_ const sd_bus_error *sd_bus_message_get_error(sd_bus_message *m) { - assert_return(m, NULL); - - if (!sd_bus_error_is_set(&m->error)) - return NULL; - - return &m->error; -} - -_public_ int sd_bus_message_get_monotonic_usec(sd_bus_message *m, uint64_t *usec) { - assert_return(m, -EINVAL); - assert_return(usec, -EINVAL); - - if (m->monotonic <= 0) - return -ENODATA; - - *usec = m->monotonic; - return 0; -} - -_public_ int sd_bus_message_get_realtime_usec(sd_bus_message *m, uint64_t *usec) { - assert_return(m, -EINVAL); - assert_return(usec, -EINVAL); - - if (m->realtime <= 0) - return -ENODATA; - - *usec = m->realtime; - return 0; -} - -_public_ int sd_bus_message_get_seqnum(sd_bus_message *m, uint64_t *seqnum) { - assert_return(m, -EINVAL); - assert_return(seqnum, -EINVAL); - - if (m->seqnum <= 0) - return -ENODATA; - - *seqnum = m->seqnum; - return 0; -} - -_public_ sd_bus_creds *sd_bus_message_get_creds(sd_bus_message *m) { - assert_return(m, NULL); - - if (m->creds.mask == 0) - return NULL; - - return &m->creds; -} - -_public_ int sd_bus_message_is_signal( - sd_bus_message *m, - const char *interface, - const char *member) { - - assert_return(m, -EINVAL); - - if (m->header->type != SD_BUS_MESSAGE_SIGNAL) - return 0; - - if (interface && (!m->interface || !streq(m->interface, interface))) - return 0; - - if (member && (!m->member || !streq(m->member, member))) - return 0; - - return 1; -} - -_public_ int sd_bus_message_is_method_call( - sd_bus_message *m, - const char *interface, - const char *member) { - - assert_return(m, -EINVAL); - - if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) - return 0; - - if (interface && (!m->interface || !streq(m->interface, interface))) - return 0; - - if (member && (!m->member || !streq(m->member, member))) - return 0; - - return 1; -} - -_public_ int sd_bus_message_is_method_error(sd_bus_message *m, const char *name) { - assert_return(m, -EINVAL); - - if (m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) - return 0; - - if (name && (!m->error.name || !streq(m->error.name, name))) - return 0; - - return 1; -} - -_public_ int sd_bus_message_set_expect_reply(sd_bus_message *m, int b) { - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EPERM); - - if (b) - m->header->flags &= ~BUS_MESSAGE_NO_REPLY_EXPECTED; - else - m->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; - - return 0; -} - -_public_ int sd_bus_message_set_auto_start(sd_bus_message *m, int b) { - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - if (b) - m->header->flags &= ~BUS_MESSAGE_NO_AUTO_START; - else - m->header->flags |= BUS_MESSAGE_NO_AUTO_START; - - return 0; -} - -_public_ int sd_bus_message_set_allow_interactive_authorization(sd_bus_message *m, int b) { - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - if (b) - m->header->flags |= BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION; - else - m->header->flags &= ~BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION; - - return 0; -} - -static struct bus_container *message_get_container(sd_bus_message *m) { - assert(m); - - if (m->n_containers == 0) - return &m->root_container; - - assert(m->containers); - return m->containers + m->n_containers - 1; -} - -struct bus_body_part *message_append_part(sd_bus_message *m) { - struct bus_body_part *part; - - assert(m); - - if (m->poisoned) - return NULL; - - if (m->n_body_parts <= 0) { - part = &m->body; - zero(*part); - } else { - assert(m->body_end); - - part = new0(struct bus_body_part, 1); - if (!part) { - m->poisoned = true; - return NULL; - } - - m->body_end->next = part; - } - - part->memfd = -1; - m->body_end = part; - m->n_body_parts ++; - - return part; -} - -static void part_zero(struct bus_body_part *part, size_t sz) { - assert(part); - assert(sz > 0); - assert(sz < 8); - - /* All other fields can be left in their defaults */ - assert(!part->data); - assert(part->memfd < 0); - - part->size = sz; - part->is_zero = true; - part->sealed = true; -} - -static int part_make_space( - struct sd_bus_message *m, - struct bus_body_part *part, - size_t sz, - void **q) { - - void *n; - int r; - - assert(m); - assert(part); - assert(!part->sealed); - - if (m->poisoned) - return -ENOMEM; - - if (!part->data && part->memfd < 0) { - part->memfd = bus_kernel_pop_memfd(m->bus, &part->data, &part->mapped, &part->allocated); - part->mmap_begin = part->data; - } - - if (part->memfd >= 0) { - - if (part->allocated == 0 || sz > part->allocated) { - uint64_t new_allocated; - - new_allocated = PAGE_ALIGN(sz > 0 ? 2 * sz : 1); - r = memfd_set_size(part->memfd, new_allocated); - if (r < 0) { - m->poisoned = true; - return r; - } - - part->allocated = new_allocated; - } - - if (!part->data || sz > part->mapped) { - size_t psz; - - psz = PAGE_ALIGN(sz > 0 ? sz : 1); - if (part->mapped <= 0) - n = mmap(NULL, psz, PROT_READ|PROT_WRITE, MAP_SHARED, part->memfd, 0); - else - n = mremap(part->mmap_begin, part->mapped, psz, MREMAP_MAYMOVE); - - if (n == MAP_FAILED) { - m->poisoned = true; - return -errno; - } - - part->mmap_begin = part->data = n; - part->mapped = psz; - part->memfd_offset = 0; - } - - part->munmap_this = true; - } else { - if (part->allocated == 0 || sz > part->allocated) { - size_t new_allocated; - - new_allocated = sz > 0 ? 2 * sz : 64; - n = realloc(part->data, new_allocated); - if (!n) { - m->poisoned = true; - return -ENOMEM; - } - - part->data = n; - part->allocated = new_allocated; - part->free_this = true; - } - } - - if (q) - *q = part->data ? (uint8_t*) part->data + part->size : NULL; - - part->size = sz; - return 0; -} - -static int message_add_offset(sd_bus_message *m, size_t offset) { - struct bus_container *c; - - assert(m); - assert(BUS_MESSAGE_IS_GVARIANT(m)); - - /* Add offset to current container, unless this is the first - * item in it, which will have the 0 offset, which we can - * ignore. */ - c = message_get_container(m); - - if (!c->need_offsets) - return 0; - - if (!GREEDY_REALLOC(c->offsets, c->offsets_allocated, c->n_offsets + 1)) - return -ENOMEM; - - c->offsets[c->n_offsets++] = offset; - return 0; -} - -static void message_extend_containers(sd_bus_message *m, size_t expand) { - struct bus_container *c; - - assert(m); - - if (expand <= 0) - return; - - /* Update counters */ - for (c = m->containers; c < m->containers + m->n_containers; c++) { - - if (c->array_size) - *c->array_size += expand; - } -} - -static void *message_extend_body( - sd_bus_message *m, - size_t align, - size_t sz, - bool add_offset, - bool force_inline) { - - size_t start_body, end_body, padding, added; - void *p; - int r; - - assert(m); - assert(align > 0); - assert(!m->sealed); - - if (m->poisoned) - return NULL; - - start_body = ALIGN_TO((size_t) m->body_size, align); - end_body = start_body + sz; - - padding = start_body - m->body_size; - added = padding + sz; - - /* Check for 32bit overflows */ - if (end_body > (size_t) ((uint32_t) -1) || - end_body < start_body) { - m->poisoned = true; - return NULL; - } - - if (added > 0) { - struct bus_body_part *part = NULL; - bool add_new_part; - - add_new_part = - m->n_body_parts <= 0 || - m->body_end->sealed || - (padding != ALIGN_TO(m->body_end->size, align) - m->body_end->size) || - (force_inline && m->body_end->size > MEMFD_MIN_SIZE); /* if this must be an inlined extension, let's create a new part if the previous part is large enough to be inlined */ - - if (add_new_part) { - if (padding > 0) { - part = message_append_part(m); - if (!part) - return NULL; - - part_zero(part, padding); - } - - part = message_append_part(m); - if (!part) - return NULL; - - r = part_make_space(m, part, sz, &p); - if (r < 0) - return NULL; - } else { - struct bus_container *c; - void *op; - size_t os, start_part, end_part; - - part = m->body_end; - op = part->data; - os = part->size; - - start_part = ALIGN_TO(part->size, align); - end_part = start_part + sz; - - r = part_make_space(m, part, end_part, &p); - if (r < 0) - return NULL; - - if (padding > 0) { - memzero(p, padding); - p = (uint8_t*) p + padding; - } - - /* Readjust pointers */ - for (c = m->containers; c < m->containers + m->n_containers; c++) - c->array_size = adjust_pointer(c->array_size, op, os, part->data); - - m->error.message = (const char*) adjust_pointer(m->error.message, op, os, part->data); - } - } else - /* Return something that is not NULL and is aligned */ - p = (uint8_t *) NULL + align; - - m->body_size = end_body; - message_extend_containers(m, added); - - if (add_offset) { - r = message_add_offset(m, end_body); - if (r < 0) { - m->poisoned = true; - return NULL; - } - } - - return p; -} - -static int message_push_fd(sd_bus_message *m, int fd) { - int *f, copy; - - assert(m); - - if (fd < 0) - return -EINVAL; - - if (!m->allow_fds) - return -EOPNOTSUPP; - - copy = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (copy < 0) - return -errno; - - f = realloc(m->fds, sizeof(int) * (m->n_fds + 1)); - if (!f) { - m->poisoned = true; - safe_close(copy); - return -ENOMEM; - } - - m->fds = f; - m->fds[m->n_fds] = copy; - m->free_fds = true; - - return copy; -} - -int message_append_basic(sd_bus_message *m, char type, const void *p, const void **stored) { - _cleanup_close_ int fd = -1; - struct bus_container *c; - ssize_t align, sz; - void *a; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(bus_type_is_basic(type), -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - c = message_get_container(m); - - if (c->signature && c->signature[c->index]) { - /* Container signature is already set */ - - if (c->signature[c->index] != type) - return -ENXIO; - } else { - char *e; - - /* Maybe we can append to the signature? But only if this is the top-level container */ - if (c->enclosing != 0) - return -ENXIO; - - e = strextend(&c->signature, CHAR_TO_STR(type), NULL); - if (!e) { - m->poisoned = true; - return -ENOMEM; - } - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - uint8_t u8; - uint32_t u32; - - switch (type) { - - case SD_BUS_TYPE_SIGNATURE: - case SD_BUS_TYPE_STRING: - p = strempty(p); - - /* Fall through... */ - case SD_BUS_TYPE_OBJECT_PATH: - if (!p) - return -EINVAL; - - align = 1; - sz = strlen(p) + 1; - break; - - case SD_BUS_TYPE_BOOLEAN: - - u8 = p && *(int*) p; - p = &u8; - - align = sz = 1; - break; - - case SD_BUS_TYPE_UNIX_FD: - - if (!p) - return -EINVAL; - - fd = message_push_fd(m, *(int*) p); - if (fd < 0) - return fd; - - u32 = m->n_fds; - p = &u32; - - align = sz = 4; - break; - - default: - align = bus_gvariant_get_alignment(CHAR_TO_STR(type)); - sz = bus_gvariant_get_size(CHAR_TO_STR(type)); - break; - } - - assert(align > 0); - assert(sz > 0); - - a = message_extend_body(m, align, sz, true, false); - if (!a) - return -ENOMEM; - - memcpy(a, p, sz); - - if (stored) - *stored = (const uint8_t*) a; - - } else { - uint32_t u32; - - switch (type) { - - case SD_BUS_TYPE_STRING: - /* To make things easy we'll serialize a NULL string - * into the empty string */ - p = strempty(p); - - /* Fall through... */ - case SD_BUS_TYPE_OBJECT_PATH: - - if (!p) - return -EINVAL; - - align = 4; - sz = 4 + strlen(p) + 1; - break; - - case SD_BUS_TYPE_SIGNATURE: - - p = strempty(p); - - align = 1; - sz = 1 + strlen(p) + 1; - break; - - case SD_BUS_TYPE_BOOLEAN: - - u32 = p && *(int*) p; - p = &u32; - - align = sz = 4; - break; - - case SD_BUS_TYPE_UNIX_FD: - - if (!p) - return -EINVAL; - - fd = message_push_fd(m, *(int*) p); - if (fd < 0) - return fd; - - u32 = m->n_fds; - p = &u32; - - align = sz = 4; - break; - - default: - align = bus_type_get_alignment(type); - sz = bus_type_get_size(type); - break; - } - - assert(align > 0); - assert(sz > 0); - - a = message_extend_body(m, align, sz, false, false); - if (!a) - return -ENOMEM; - - if (type == SD_BUS_TYPE_STRING || type == SD_BUS_TYPE_OBJECT_PATH) { - *(uint32_t*) a = sz - 5; - memcpy((uint8_t*) a + 4, p, sz - 4); - - if (stored) - *stored = (const uint8_t*) a + 4; - - } else if (type == SD_BUS_TYPE_SIGNATURE) { - *(uint8_t*) a = sz - 2; - memcpy((uint8_t*) a + 1, p, sz - 1); - - if (stored) - *stored = (const uint8_t*) a + 1; - } else { - memcpy(a, p, sz); - - if (stored) - *stored = a; - } - } - - if (type == SD_BUS_TYPE_UNIX_FD) - m->n_fds ++; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index++; - - fd = -1; - return 0; -} - -_public_ int sd_bus_message_append_basic(sd_bus_message *m, char type, const void *p) { - return message_append_basic(m, type, p, NULL); -} - -_public_ int sd_bus_message_append_string_space( - sd_bus_message *m, - size_t size, - char **s) { - - struct bus_container *c; - void *a; - - assert_return(m, -EINVAL); - assert_return(s, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(!m->poisoned, -ESTALE); - - c = message_get_container(m); - - if (c->signature && c->signature[c->index]) { - /* Container signature is already set */ - - if (c->signature[c->index] != SD_BUS_TYPE_STRING) - return -ENXIO; - } else { - char *e; - - /* Maybe we can append to the signature? But only if this is the top-level container */ - if (c->enclosing != 0) - return -ENXIO; - - e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRING), NULL); - if (!e) { - m->poisoned = true; - return -ENOMEM; - } - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - a = message_extend_body(m, 1, size + 1, true, false); - if (!a) - return -ENOMEM; - - *s = a; - } else { - a = message_extend_body(m, 4, 4 + size + 1, false, false); - if (!a) - return -ENOMEM; - - *(uint32_t*) a = size; - *s = (char*) a + 4; - } - - (*s)[size] = 0; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index++; - - return 0; -} - -_public_ int sd_bus_message_append_string_iovec( - sd_bus_message *m, - const struct iovec *iov, - unsigned n) { - - size_t size; - unsigned i; - char *p; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(iov || n == 0, -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - size = IOVEC_TOTAL_SIZE(iov, n); - - r = sd_bus_message_append_string_space(m, size, &p); - if (r < 0) - return r; - - for (i = 0; i < n; i++) { - - if (iov[i].iov_base) - memcpy(p, iov[i].iov_base, iov[i].iov_len); - else - memset(p, ' ', iov[i].iov_len); - - p += iov[i].iov_len; - } - - return 0; -} - -static int bus_message_open_array( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - uint32_t **array_size, - size_t *begin, - bool *need_offsets) { - - unsigned nindex; - int alignment, r; - - assert(m); - assert(c); - assert(contents); - assert(array_size); - assert(begin); - assert(need_offsets); - - if (!signature_is_single(contents, true)) - return -EINVAL; - - if (c->signature && c->signature[c->index]) { - - /* Verify the existing signature */ - - if (c->signature[c->index] != SD_BUS_TYPE_ARRAY) - return -ENXIO; - - if (!startswith(c->signature + c->index + 1, contents)) - return -ENXIO; - - nindex = c->index + 1 + strlen(contents); - } else { - char *e; - - if (c->enclosing != 0) - return -ENXIO; - - /* Extend the existing signature */ - - e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_ARRAY), contents, NULL); - if (!e) { - m->poisoned = true; - return -ENOMEM; - } - - nindex = e - c->signature; - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - alignment = bus_gvariant_get_alignment(contents); - if (alignment < 0) - return alignment; - - /* Add alignment padding and add to offset list */ - if (!message_extend_body(m, alignment, 0, false, false)) - return -ENOMEM; - - r = bus_gvariant_is_fixed_size(contents); - if (r < 0) - return r; - - *begin = m->body_size; - *need_offsets = r == 0; - } else { - void *a, *op; - size_t os; - struct bus_body_part *o; - - alignment = bus_type_get_alignment(contents[0]); - if (alignment < 0) - return alignment; - - a = message_extend_body(m, 4, 4, false, false); - if (!a) - return -ENOMEM; - - o = m->body_end; - op = m->body_end->data; - os = m->body_end->size; - - /* Add alignment between size and first element */ - if (!message_extend_body(m, alignment, 0, false, false)) - return -ENOMEM; - - /* location of array size might have changed so let's readjust a */ - if (o == m->body_end) - a = adjust_pointer(a, op, os, m->body_end->data); - - *(uint32_t*) a = 0; - *array_size = a; - } - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index = nindex; - - return 0; -} - -static int bus_message_open_variant( - sd_bus_message *m, - struct bus_container *c, - const char *contents) { - - assert(m); - assert(c); - assert(contents); - - if (!signature_is_single(contents, false)) - return -EINVAL; - - if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN) - return -EINVAL; - - if (c->signature && c->signature[c->index]) { - - if (c->signature[c->index] != SD_BUS_TYPE_VARIANT) - return -ENXIO; - - } else { - char *e; - - if (c->enclosing != 0) - return -ENXIO; - - e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_VARIANT), NULL); - if (!e) { - m->poisoned = true; - return -ENOMEM; - } - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - /* Variants are always aligned to 8 */ - - if (!message_extend_body(m, 8, 0, false, false)) - return -ENOMEM; - - } else { - size_t l; - void *a; - - l = strlen(contents); - a = message_extend_body(m, 1, 1 + l + 1, false, false); - if (!a) - return -ENOMEM; - - *(uint8_t*) a = l; - memcpy((uint8_t*) a + 1, contents, l + 1); - } - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index++; - - return 0; -} - -static int bus_message_open_struct( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - size_t *begin, - bool *need_offsets) { - - size_t nindex; - int r; - - assert(m); - assert(c); - assert(contents); - assert(begin); - assert(need_offsets); - - if (!signature_is_valid(contents, false)) - return -EINVAL; - - if (c->signature && c->signature[c->index]) { - size_t l; - - l = strlen(contents); - - if (c->signature[c->index] != SD_BUS_TYPE_STRUCT_BEGIN || - !startswith(c->signature + c->index + 1, contents) || - c->signature[c->index + 1 + l] != SD_BUS_TYPE_STRUCT_END) - return -ENXIO; - - nindex = c->index + 1 + l + 1; - } else { - char *e; - - if (c->enclosing != 0) - return -ENXIO; - - e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_BEGIN), contents, CHAR_TO_STR(SD_BUS_TYPE_STRUCT_END), NULL); - if (!e) { - m->poisoned = true; - return -ENOMEM; - } - - nindex = e - c->signature; - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - int alignment; - - alignment = bus_gvariant_get_alignment(contents); - if (alignment < 0) - return alignment; - - if (!message_extend_body(m, alignment, 0, false, false)) - return -ENOMEM; - - r = bus_gvariant_is_fixed_size(contents); - if (r < 0) - return r; - - *begin = m->body_size; - *need_offsets = r == 0; - } else { - /* Align contents to 8 byte boundary */ - if (!message_extend_body(m, 8, 0, false, false)) - return -ENOMEM; - } - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index = nindex; - - return 0; -} - -static int bus_message_open_dict_entry( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - size_t *begin, - bool *need_offsets) { - - int r; - - assert(m); - assert(c); - assert(contents); - assert(begin); - assert(need_offsets); - - if (!signature_is_pair(contents)) - return -EINVAL; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - return -ENXIO; - - if (c->signature && c->signature[c->index]) { - size_t l; - - l = strlen(contents); - - if (c->signature[c->index] != SD_BUS_TYPE_DICT_ENTRY_BEGIN || - !startswith(c->signature + c->index + 1, contents) || - c->signature[c->index + 1 + l] != SD_BUS_TYPE_DICT_ENTRY_END) - return -ENXIO; - } else - return -ENXIO; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - int alignment; - - alignment = bus_gvariant_get_alignment(contents); - if (alignment < 0) - return alignment; - - if (!message_extend_body(m, alignment, 0, false, false)) - return -ENOMEM; - - r = bus_gvariant_is_fixed_size(contents); - if (r < 0) - return r; - - *begin = m->body_size; - *need_offsets = r == 0; - } else { - /* Align contents to 8 byte boundary */ - if (!message_extend_body(m, 8, 0, false, false)) - return -ENOMEM; - } - - return 0; -} - -_public_ int sd_bus_message_open_container( - sd_bus_message *m, - char type, - const char *contents) { - - struct bus_container *c, *w; - uint32_t *array_size = NULL; - char *signature; - size_t before, begin = 0; - bool need_offsets = false; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(contents, -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - /* Make sure we have space for one more container */ - if (!GREEDY_REALLOC(m->containers, m->containers_allocated, m->n_containers + 1)) { - m->poisoned = true; - return -ENOMEM; - } - - c = message_get_container(m); - - signature = strdup(contents); - if (!signature) { - m->poisoned = true; - return -ENOMEM; - } - - /* Save old index in the parent container, in case we have to - * abort this container */ - c->saved_index = c->index; - before = m->body_size; - - if (type == SD_BUS_TYPE_ARRAY) - r = bus_message_open_array(m, c, contents, &array_size, &begin, &need_offsets); - else if (type == SD_BUS_TYPE_VARIANT) - r = bus_message_open_variant(m, c, contents); - else if (type == SD_BUS_TYPE_STRUCT) - r = bus_message_open_struct(m, c, contents, &begin, &need_offsets); - else if (type == SD_BUS_TYPE_DICT_ENTRY) - r = bus_message_open_dict_entry(m, c, contents, &begin, &need_offsets); - else - r = -EINVAL; - - if (r < 0) { - free(signature); - return r; - } - - /* OK, let's fill it in */ - w = m->containers + m->n_containers++; - w->enclosing = type; - w->signature = signature; - w->index = 0; - w->array_size = array_size; - w->before = before; - w->begin = begin; - w->n_offsets = w->offsets_allocated = 0; - w->offsets = NULL; - w->need_offsets = need_offsets; - - return 0; -} - -static int bus_message_close_array(sd_bus_message *m, struct bus_container *c) { - - assert(m); - assert(c); - - if (!BUS_MESSAGE_IS_GVARIANT(m)) - return 0; - - if (c->need_offsets) { - size_t payload, sz, i; - uint8_t *a; - - /* Variable-width arrays */ - - payload = c->n_offsets > 0 ? c->offsets[c->n_offsets-1] - c->begin : 0; - sz = bus_gvariant_determine_word_size(payload, c->n_offsets); - - a = message_extend_body(m, 1, sz * c->n_offsets, true, false); - if (!a) - return -ENOMEM; - - for (i = 0; i < c->n_offsets; i++) - bus_gvariant_write_word_le(a + sz*i, sz, c->offsets[i] - c->begin); - } else { - void *a; - - /* Fixed-width or empty arrays */ - - a = message_extend_body(m, 1, 0, true, false); /* let's add offset to parent */ - if (!a) - return -ENOMEM; - } - - return 0; -} - -static int bus_message_close_variant(sd_bus_message *m, struct bus_container *c) { - uint8_t *a; - size_t l; - - assert(m); - assert(c); - assert(c->signature); - - if (!BUS_MESSAGE_IS_GVARIANT(m)) - return 0; - - l = strlen(c->signature); - - a = message_extend_body(m, 1, 1 + l, true, false); - if (!a) - return -ENOMEM; - - a[0] = 0; - memcpy(a+1, c->signature, l); - - return 0; -} - -static int bus_message_close_struct(sd_bus_message *m, struct bus_container *c, bool add_offset) { - bool fixed_size = true; - size_t n_variable = 0; - unsigned i = 0; - const char *p; - uint8_t *a; - int r; - - assert(m); - assert(c); - - if (!BUS_MESSAGE_IS_GVARIANT(m)) - return 0; - - p = strempty(c->signature); - while (*p != 0) { - size_t n; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - else { - char t[n+1]; - - memcpy(t, p, n); - t[n] = 0; - - r = bus_gvariant_is_fixed_size(t); - if (r < 0) - return r; - } - - assert(!c->need_offsets || i <= c->n_offsets); - - /* We need to add an offset for each item that has a - * variable size and that is not the last one in the - * list */ - if (r == 0) - fixed_size = false; - if (r == 0 && p[n] != 0) - n_variable++; - - i++; - p += n; - } - - assert(!c->need_offsets || i == c->n_offsets); - assert(c->need_offsets || n_variable == 0); - - if (isempty(c->signature)) { - /* The unary type is encoded as fixed 1 byte padding */ - a = message_extend_body(m, 1, 1, add_offset, false); - if (!a) - return -ENOMEM; - - *a = 0; - } else if (n_variable <= 0) { - int alignment = 1; - - /* Structures with fixed-size members only have to be - * fixed-size themselves. But gvariant requires all fixed-size - * elements to be sized a multiple of their alignment. Hence, - * we must *always* add final padding after the last member so - * the overall size of the structure is properly aligned. */ - if (fixed_size) - alignment = bus_gvariant_get_alignment(strempty(c->signature)); - - assert(alignment > 0); - - a = message_extend_body(m, alignment, 0, add_offset, false); - if (!a) - return -ENOMEM; - } else { - size_t sz; - unsigned j; - - assert(c->offsets[c->n_offsets-1] == m->body_size); - - sz = bus_gvariant_determine_word_size(m->body_size - c->begin, n_variable); - - a = message_extend_body(m, 1, sz * n_variable, add_offset, false); - if (!a) - return -ENOMEM; - - p = strempty(c->signature); - for (i = 0, j = 0; i < c->n_offsets; i++) { - unsigned k; - size_t n; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - else { - char t[n+1]; - - memcpy(t, p, n); - t[n] = 0; - - p += n; - - r = bus_gvariant_is_fixed_size(t); - if (r < 0) - return r; - if (r > 0 || p[0] == 0) - continue; - } - - k = n_variable - 1 - j; - - bus_gvariant_write_word_le(a + k * sz, sz, c->offsets[i] - c->begin); - - j++; - } - } - - return 0; -} - -_public_ int sd_bus_message_close_container(sd_bus_message *m) { - struct bus_container *c; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(m->n_containers > 0, -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - c = message_get_container(m); - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - if (c->signature && c->signature[c->index] != 0) - return -EINVAL; - - m->n_containers--; - - if (c->enclosing == SD_BUS_TYPE_ARRAY) - r = bus_message_close_array(m, c); - else if (c->enclosing == SD_BUS_TYPE_VARIANT) - r = bus_message_close_variant(m, c); - else if (c->enclosing == SD_BUS_TYPE_STRUCT || c->enclosing == SD_BUS_TYPE_DICT_ENTRY) - r = bus_message_close_struct(m, c, true); - else - assert_not_reached("Unknown container type"); - - free(c->signature); - free(c->offsets); - - return r; -} - -typedef struct { - const char *types; - unsigned n_struct; - unsigned n_array; -} TypeStack; - -static int type_stack_push(TypeStack *stack, unsigned max, unsigned *i, const char *types, unsigned n_struct, unsigned n_array) { - assert(stack); - assert(max > 0); - - if (*i >= max) - return -EINVAL; - - stack[*i].types = types; - stack[*i].n_struct = n_struct; - stack[*i].n_array = n_array; - (*i)++; - - return 0; -} - -static int type_stack_pop(TypeStack *stack, unsigned max, unsigned *i, const char **types, unsigned *n_struct, unsigned *n_array) { - assert(stack); - assert(max > 0); - assert(types); - assert(n_struct); - assert(n_array); - - if (*i <= 0) - return 0; - - (*i)--; - *types = stack[*i].types; - *n_struct = stack[*i].n_struct; - *n_array = stack[*i].n_array; - - return 1; -} - -int bus_message_append_ap( - sd_bus_message *m, - const char *types, - va_list ap) { - - unsigned n_array, n_struct; - TypeStack stack[BUS_CONTAINER_DEPTH]; - unsigned stack_ptr = 0; - int r; - - assert(m); - - if (!types) - return 0; - - n_array = (unsigned) -1; - n_struct = strlen(types); - - for (;;) { - const char *t; - - if (n_array == 0 || (n_array == (unsigned) -1 && n_struct == 0)) { - r = type_stack_pop(stack, ELEMENTSOF(stack), &stack_ptr, &types, &n_struct, &n_array); - if (r < 0) - return r; - if (r == 0) - break; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - continue; - } - - t = types; - if (n_array != (unsigned) -1) - n_array --; - else { - types ++; - n_struct--; - } - - switch (*t) { - - case SD_BUS_TYPE_BYTE: { - uint8_t x; - - x = (uint8_t) va_arg(ap, int); - r = sd_bus_message_append_basic(m, *t, &x); - break; - } - - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_UNIX_FD: { - uint32_t x; - - /* We assume a boolean is the same as int32_t */ - assert_cc(sizeof(int32_t) == sizeof(int)); - - x = va_arg(ap, uint32_t); - r = sd_bus_message_append_basic(m, *t, &x); - break; - } - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: { - uint16_t x; - - x = (uint16_t) va_arg(ap, int); - r = sd_bus_message_append_basic(m, *t, &x); - break; - } - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: { - uint64_t x; - - x = va_arg(ap, uint64_t); - r = sd_bus_message_append_basic(m, *t, &x); - break; - } - - case SD_BUS_TYPE_DOUBLE: { - double x; - - x = va_arg(ap, double); - r = sd_bus_message_append_basic(m, *t, &x); - break; - } - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: { - const char *x; - - x = va_arg(ap, const char*); - r = sd_bus_message_append_basic(m, *t, x); - break; - } - - case SD_BUS_TYPE_ARRAY: { - size_t k; - - r = signature_element_length(t + 1, &k); - if (r < 0) - return r; - - { - char s[k + 1]; - memcpy(s, t + 1, k); - s[k] = 0; - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, s); - if (r < 0) - return r; - } - - if (n_array == (unsigned) -1) { - types += k; - n_struct -= k; - } - - r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); - if (r < 0) - return r; - - types = t + 1; - n_struct = k; - n_array = va_arg(ap, unsigned); - - break; - } - - case SD_BUS_TYPE_VARIANT: { - const char *s; - - s = va_arg(ap, const char*); - if (!s) - return -EINVAL; - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, s); - if (r < 0) - return r; - - r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); - if (r < 0) - return r; - - types = s; - n_struct = strlen(s); - n_array = (unsigned) -1; - - break; - } - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - size_t k; - - r = signature_element_length(t, &k); - if (r < 0) - return r; - - { - char s[k - 1]; - - memcpy(s, t + 1, k - 2); - s[k - 2] = 0; - - r = sd_bus_message_open_container(m, *t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); - if (r < 0) - return r; - } - - if (n_array == (unsigned) -1) { - types += k - 1; - n_struct -= k - 1; - } - - r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); - if (r < 0) - return r; - - types = t + 1; - n_struct = k - 2; - n_array = (unsigned) -1; - - break; - } - - default: - r = -EINVAL; - } - - if (r < 0) - return r; - } - - return 1; -} - -_public_ int sd_bus_message_append(sd_bus_message *m, const char *types, ...) { - va_list ap; - int r; - - assert_return(m, -EINVAL); - assert_return(types, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(!m->poisoned, -ESTALE); - - va_start(ap, types); - r = bus_message_append_ap(m, types, ap); - va_end(ap); - - return r; -} - -_public_ int sd_bus_message_append_array_space( - sd_bus_message *m, - char type, - size_t size, - void **ptr) { - - ssize_t align, sz; - void *a; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(bus_type_is_trivial(type) && type != SD_BUS_TYPE_BOOLEAN, -EINVAL); - assert_return(ptr || size == 0, -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - /* alignment and size of the trivial types (except bool) is - * identical for gvariant and dbus1 marshalling */ - align = bus_type_get_alignment(type); - sz = bus_type_get_size(type); - - assert_se(align > 0); - assert_se(sz > 0); - - if (size % sz != 0) - return -EINVAL; - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); - if (r < 0) - return r; - - a = message_extend_body(m, align, size, false, false); - if (!a) - return -ENOMEM; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - *ptr = a; - return 0; -} - -_public_ int sd_bus_message_append_array( - sd_bus_message *m, - char type, - const void *ptr, - size_t size) { - int r; - void *p; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(bus_type_is_trivial(type), -EINVAL); - assert_return(ptr || size == 0, -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - r = sd_bus_message_append_array_space(m, type, size, &p); - if (r < 0) - return r; - - if (size > 0) - memcpy(p, ptr, size); - - return 0; -} - -_public_ int sd_bus_message_append_array_iovec( - sd_bus_message *m, - char type, - const struct iovec *iov, - unsigned n) { - - size_t size; - unsigned i; - void *p; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(bus_type_is_trivial(type), -EINVAL); - assert_return(iov || n == 0, -EINVAL); - assert_return(!m->poisoned, -ESTALE); - - size = IOVEC_TOTAL_SIZE(iov, n); - - r = sd_bus_message_append_array_space(m, type, size, &p); - if (r < 0) - return r; - - for (i = 0; i < n; i++) { - - if (iov[i].iov_base) - memcpy(p, iov[i].iov_base, iov[i].iov_len); - else - memzero(p, iov[i].iov_len); - - p = (uint8_t*) p + iov[i].iov_len; - } - - return 0; -} - -_public_ int sd_bus_message_append_array_memfd( - sd_bus_message *m, - char type, - int memfd, - uint64_t offset, - uint64_t size) { - - _cleanup_close_ int copy_fd = -1; - struct bus_body_part *part; - ssize_t align, sz; - uint64_t real_size; - void *a; - int r; - - assert_return(m, -EINVAL); - assert_return(memfd >= 0, -EBADF); - assert_return(bus_type_is_trivial(type), -EINVAL); - assert_return(size > 0, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(!m->poisoned, -ESTALE); - - r = memfd_set_sealed(memfd); - if (r < 0) - return r; - - copy_fd = dup(memfd); - if (copy_fd < 0) - return copy_fd; - - r = memfd_get_size(memfd, &real_size); - if (r < 0) - return r; - - if (offset == 0 && size == (uint64_t) -1) - size = real_size; - else if (offset + size > real_size) - return -EMSGSIZE; - - align = bus_type_get_alignment(type); - sz = bus_type_get_size(type); - - assert_se(align > 0); - assert_se(sz > 0); - - if (offset % align != 0) - return -EINVAL; - - if (size % sz != 0) - return -EINVAL; - - if (size > (uint64_t) (uint32_t) -1) - return -EINVAL; - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); - if (r < 0) - return r; - - a = message_extend_body(m, align, 0, false, false); - if (!a) - return -ENOMEM; - - part = message_append_part(m); - if (!part) - return -ENOMEM; - - part->memfd = copy_fd; - part->memfd_offset = offset; - part->sealed = true; - part->size = size; - copy_fd = -1; - - m->body_size += size; - message_extend_containers(m, size); - - return sd_bus_message_close_container(m); -} - -_public_ int sd_bus_message_append_string_memfd( - sd_bus_message *m, - int memfd, - uint64_t offset, - uint64_t size) { - - _cleanup_close_ int copy_fd = -1; - struct bus_body_part *part; - struct bus_container *c; - uint64_t real_size; - void *a; - int r; - - assert_return(m, -EINVAL); - assert_return(memfd >= 0, -EBADF); - assert_return(size > 0, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(!m->poisoned, -ESTALE); - - r = memfd_set_sealed(memfd); - if (r < 0) - return r; - - copy_fd = dup(memfd); - if (copy_fd < 0) - return copy_fd; - - r = memfd_get_size(memfd, &real_size); - if (r < 0) - return r; - - if (offset == 0 && size == (uint64_t) -1) - size = real_size; - else if (offset + size > real_size) - return -EMSGSIZE; - - /* We require this to be NUL terminated */ - if (size == 0) - return -EINVAL; - - if (size > (uint64_t) (uint32_t) -1) - return -EINVAL; - - c = message_get_container(m); - if (c->signature && c->signature[c->index]) { - /* Container signature is already set */ - - if (c->signature[c->index] != SD_BUS_TYPE_STRING) - return -ENXIO; - } else { - char *e; - - /* Maybe we can append to the signature? But only if this is the top-level container */ - if (c->enclosing != 0) - return -ENXIO; - - e = strextend(&c->signature, CHAR_TO_STR(SD_BUS_TYPE_STRING), NULL); - if (!e) { - m->poisoned = true; - return -ENOMEM; - } - } - - if (!BUS_MESSAGE_IS_GVARIANT(m)) { - a = message_extend_body(m, 4, 4, false, false); - if (!a) - return -ENOMEM; - - *(uint32_t*) a = size - 1; - } - - part = message_append_part(m); - if (!part) - return -ENOMEM; - - part->memfd = copy_fd; - part->memfd_offset = offset; - part->sealed = true; - part->size = size; - copy_fd = -1; - - m->body_size += size; - message_extend_containers(m, size); - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - r = message_add_offset(m, m->body_size); - if (r < 0) { - m->poisoned = true; - return -ENOMEM; - } - } - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index++; - - return 0; -} - -_public_ int sd_bus_message_append_strv(sd_bus_message *m, char **l) { - char **i; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(!m->poisoned, -ESTALE); - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return r; - - STRV_FOREACH(i, l) { - r = sd_bus_message_append_basic(m, 's', *i); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(m); -} - -static int bus_message_close_header(sd_bus_message *m) { - - assert(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 - put the outer container end. */ - m->user_body_size = m->body_size; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - const char *signature; - size_t sz, l; - void *d; - - /* Add offset table to end of fields array */ - if (m->n_header_offsets >= 1) { - uint8_t *a; - unsigned i; - - assert(m->fields_size == m->header_offsets[m->n_header_offsets-1]); - - sz = bus_gvariant_determine_word_size(m->fields_size, m->n_header_offsets); - a = message_extend_fields(m, 1, sz * m->n_header_offsets, false); - if (!a) - return -ENOMEM; - - for (i = 0; i < m->n_header_offsets; i++) - bus_gvariant_write_word_le(a + sz*i, sz, m->header_offsets[i]); - } - - /* Add gvariant NUL byte plus signature to the end of - * the body, followed by the final offset pointing to - * the end of the fields array */ - - signature = strempty(m->root_container.signature); - l = strlen(signature); - - sz = bus_gvariant_determine_word_size(sizeof(struct bus_header) + ALIGN8(m->fields_size) + m->body_size + 1 + l + 2, 1); - d = message_extend_body(m, 1, 1 + l + 2 + sz, false, true); - if (!d) - return -ENOMEM; - - *(uint8_t*) d = 0; - *((uint8_t*) d + 1) = SD_BUS_TYPE_STRUCT_BEGIN; - memcpy((uint8_t*) d + 2, signature, l); - *((uint8_t*) d + 1 + l + 1) = SD_BUS_TYPE_STRUCT_END; - - bus_gvariant_write_word_le((uint8_t*) d + 1 + l + 2, sz, sizeof(struct bus_header) + m->fields_size); - - m->footer = d; - m->footer_accessible = 1 + l + 2 + sz; - } else { - m->header->dbus1.fields_size = m->fields_size; - m->header->dbus1.body_size = m->body_size; - } - - return 0; -} - -int bus_message_seal(sd_bus_message *m, uint64_t cookie, usec_t timeout) { - struct bus_body_part *part; - size_t a; - unsigned i; - int r; - - assert(m); - - if (m->sealed) - return -EPERM; - - if (m->n_containers > 0) - return -EBADMSG; - - if (m->poisoned) - return -ESTALE; - - if (cookie > 0xffffffffULL && - !BUS_MESSAGE_IS_GVARIANT(m)) - return -EOPNOTSUPP; - - /* In vtables the return signature of method calls is listed, - * let's check if they match if this is a response */ - if (m->header->type == SD_BUS_MESSAGE_METHOD_RETURN && - m->enforced_reply_signature && - !streq(strempty(m->root_container.signature), m->enforced_reply_signature)) - return -ENOMSG; - - /* If gvariant marshalling is used we need to close the body structure */ - r = bus_message_close_struct(m, &m->root_container, false); - if (r < 0) - return r; - - /* If there's a non-trivial signature set, then add it in - * here, but only on dbus1 */ - if (!isempty(m->root_container.signature) && !BUS_MESSAGE_IS_GVARIANT(m)) { - r = message_append_field_signature(m, BUS_MESSAGE_HEADER_SIGNATURE, m->root_container.signature, NULL); - if (r < 0) - return r; - } - - if (m->n_fds > 0) { - r = message_append_field_uint32(m, BUS_MESSAGE_HEADER_UNIX_FDS, m->n_fds); - if (r < 0) - return r; - } - - r = bus_message_close_header(m); - if (r < 0) - return r; - - if (BUS_MESSAGE_IS_GVARIANT(m)) - m->header->dbus2.cookie = cookie; - else - m->header->dbus1.serial = (uint32_t) cookie; - - m->timeout = m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED ? 0 : timeout; - - /* Add padding at the end of the fields part, since we know - * the body needs to start at an 8 byte alignment. We made - * sure we allocated enough space for this, so all we need to - * do here is to zero it out. */ - a = ALIGN8(m->fields_size) - m->fields_size; - if (a > 0) - memzero((uint8_t*) BUS_MESSAGE_FIELDS(m) + m->fields_size, a); - - /* If this is something we can send as memfd, then let's seal - the memfd now. Note that we can send memfds as payload only - for directed messages, and not for broadcasts. */ - if (m->destination && m->bus->use_memfd) { - MESSAGE_FOREACH_PART(part, i, m) - if (part->memfd >= 0 && - !part->sealed && - (part->size > MEMFD_MIN_SIZE || m->bus->use_memfd < 0) && - part != m->body_end) { /* The last part may never be sent as memfd */ - uint64_t sz; - - /* Try to seal it if that makes - * sense. First, unmap our own map to - * make sure we don't keep it busy. */ - bus_body_part_unmap(part); - - /* Then, sync up real memfd size */ - sz = part->size; - r = memfd_set_size(part->memfd, sz); - if (r < 0) - return r; - - /* Finally, try to seal */ - if (memfd_set_sealed(part->memfd) >= 0) - part->sealed = true; - } - } - - m->root_container.end = m->user_body_size; - m->root_container.index = 0; - m->root_container.offset_index = 0; - m->root_container.item_size = m->root_container.n_offsets > 0 ? m->root_container.offsets[0] : 0; - - m->sealed = true; - - return 0; -} - -int bus_body_part_map(struct bus_body_part *part) { - void *p; - size_t psz, shift; - - assert_se(part); - - if (part->data) - return 0; - - if (part->size <= 0) - return 0; - - /* For smaller zero parts (as used for padding) we don't need to map anything... */ - if (part->memfd < 0 && part->is_zero && part->size < 8) { - static const uint8_t zeroes[7] = { }; - part->data = (void*) zeroes; - return 0; - } - - shift = part->memfd_offset - ((part->memfd_offset / page_size()) * page_size()); - psz = PAGE_ALIGN(part->size + shift); - - if (part->memfd >= 0) - p = mmap(NULL, psz, PROT_READ, MAP_PRIVATE, part->memfd, part->memfd_offset - shift); - else if (part->is_zero) - p = mmap(NULL, psz, PROT_READ, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); - else - return -EINVAL; - - if (p == MAP_FAILED) - return -errno; - - part->mapped = psz; - part->mmap_begin = p; - part->data = (uint8_t*) p + shift; - part->munmap_this = true; - - return 0; -} - -void bus_body_part_unmap(struct bus_body_part *part) { - - assert_se(part); - - if (part->memfd < 0) - return; - - if (!part->mmap_begin) - return; - - if (!part->munmap_this) - return; - - assert_se(munmap(part->mmap_begin, part->mapped) == 0); - - part->mmap_begin = NULL; - part->data = NULL; - part->mapped = 0; - part->munmap_this = false; - - return; -} - -static int buffer_peek(const void *p, uint32_t sz, size_t *rindex, size_t align, size_t nbytes, void **r) { - size_t k, start, end; - - assert(rindex); - assert(align > 0); - - start = ALIGN_TO((size_t) *rindex, align); - end = start + nbytes; - - if (end > sz) - return -EBADMSG; - - /* Verify that padding is 0 */ - for (k = *rindex; k < start; k++) - if (((const uint8_t*) p)[k] != 0) - return -EBADMSG; - - if (r) - *r = (uint8_t*) p + start; - - *rindex = end; - - return 1; -} - -static bool message_end_of_signature(sd_bus_message *m) { - struct bus_container *c; - - assert(m); - - c = message_get_container(m); - return !c->signature || c->signature[c->index] == 0; -} - -static bool message_end_of_array(sd_bus_message *m, size_t index) { - struct bus_container *c; - - assert(m); - - c = message_get_container(m); - if (c->enclosing != SD_BUS_TYPE_ARRAY) - return false; - - if (BUS_MESSAGE_IS_GVARIANT(m)) - return index >= c->end; - else { - assert(c->array_size); - return index >= c->begin + BUS_MESSAGE_BSWAP32(m, *c->array_size); - } -} - -_public_ int sd_bus_message_at_end(sd_bus_message *m, int complete) { - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - - if (complete && m->n_containers > 0) - return false; - - if (message_end_of_signature(m)) - return true; - - if (message_end_of_array(m, m->rindex)) - return true; - - return false; -} - -static struct bus_body_part* find_part(sd_bus_message *m, size_t index, size_t sz, void **p) { - struct bus_body_part *part; - size_t begin; - int r; - - assert(m); - - if (m->cached_rindex_part && index >= m->cached_rindex_part_begin) { - part = m->cached_rindex_part; - begin = m->cached_rindex_part_begin; - } else { - part = &m->body; - begin = 0; - } - - while (part) { - if (index < begin) - return NULL; - - if (index + sz <= begin + part->size) { - - r = bus_body_part_map(part); - if (r < 0) - return NULL; - - if (p) - *p = (uint8_t*) part->data + index - begin; - - m->cached_rindex_part = part; - m->cached_rindex_part_begin = begin; - - return part; - } - - begin += part->size; - part = part->next; - } - - return NULL; -} - -static int container_next_item(sd_bus_message *m, struct bus_container *c, size_t *rindex) { - int r; - - assert(m); - assert(c); - assert(rindex); - - if (!BUS_MESSAGE_IS_GVARIANT(m)) - return 0; - - if (c->enclosing == SD_BUS_TYPE_ARRAY) { - int sz; - - sz = bus_gvariant_get_size(c->signature); - if (sz < 0) { - int alignment; - - if (c->offset_index+1 >= c->n_offsets) - goto end; - - /* Variable-size array */ - - alignment = bus_gvariant_get_alignment(c->signature); - assert(alignment > 0); - - *rindex = ALIGN_TO(c->offsets[c->offset_index], alignment); - c->item_size = c->offsets[c->offset_index+1] - *rindex; - } else { - - if (c->offset_index+1 >= (c->end-c->begin)/sz) - goto end; - - /* Fixed-size array */ - *rindex = c->begin + (c->offset_index+1) * sz; - c->item_size = sz; - } - - c->offset_index++; - - } else if (c->enclosing == 0 || - c->enclosing == SD_BUS_TYPE_STRUCT || - c->enclosing == SD_BUS_TYPE_DICT_ENTRY) { - - int alignment; - size_t n, j; - - if (c->offset_index+1 >= c->n_offsets) - goto end; - - r = signature_element_length(c->signature + c->index, &n); - if (r < 0) - return r; - - r = signature_element_length(c->signature + c->index + n, &j); - if (r < 0) - return r; - else { - char t[j+1]; - memcpy(t, c->signature + c->index + n, j); - t[j] = 0; - - alignment = bus_gvariant_get_alignment(t); - } - - assert(alignment > 0); - - *rindex = ALIGN_TO(c->offsets[c->offset_index], alignment); - c->item_size = c->offsets[c->offset_index+1] - *rindex; - - c->offset_index++; - - } else if (c->enclosing == SD_BUS_TYPE_VARIANT) - goto end; - else - assert_not_reached("Unknown container type"); - - return 0; - -end: - /* Reached the end */ - *rindex = c->end; - c->item_size = 0; - return 0; -} - - -static int message_peek_body( - sd_bus_message *m, - size_t *rindex, - size_t align, - size_t nbytes, - void **ret) { - - size_t k, start, end, padding; - struct bus_body_part *part; - uint8_t *q; - - assert(m); - assert(rindex); - assert(align > 0); - - start = ALIGN_TO((size_t) *rindex, align); - padding = start - *rindex; - end = start + nbytes; - - if (end > m->user_body_size) - return -EBADMSG; - - part = find_part(m, *rindex, padding, (void**) &q); - if (!part) - return -EBADMSG; - - if (q) { - /* Verify padding */ - for (k = 0; k < padding; k++) - if (q[k] != 0) - return -EBADMSG; - } - - part = find_part(m, start, nbytes, (void**) &q); - if (!part || (nbytes > 0 && !q)) - return -EBADMSG; - - *rindex = end; - - if (ret) - *ret = q; - - return 0; -} - -static bool validate_nul(const char *s, size_t l) { - - /* Check for NUL chars in the string */ - if (memchr(s, 0, l)) - return false; - - /* Check for NUL termination */ - if (s[l] != 0) - return false; - - return true; -} - -static bool validate_string(const char *s, size_t l) { - - if (!validate_nul(s, l)) - return false; - - /* Check if valid UTF8 */ - if (!utf8_is_valid(s)) - return false; - - return true; -} - -static bool validate_signature(const char *s, size_t l) { - - if (!validate_nul(s, l)) - return false; - - /* Check if valid signature */ - if (!signature_is_valid(s, true)) - return false; - - return true; -} - -static bool validate_object_path(const char *s, size_t l) { - - if (!validate_nul(s, l)) - return false; - - if (!object_path_is_valid(s)) - return false; - - return true; -} - -_public_ int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p) { - struct bus_container *c; - size_t rindex; - void *q; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(bus_type_is_basic(type), -EINVAL); - - if (message_end_of_signature(m)) - return -ENXIO; - - if (message_end_of_array(m, m->rindex)) - return 0; - - c = message_get_container(m); - if (c->signature[c->index] != type) - return -ENXIO; - - rindex = m->rindex; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - - if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE)) { - bool ok; - - r = message_peek_body(m, &rindex, 1, c->item_size, &q); - if (r < 0) - return r; - - if (type == SD_BUS_TYPE_STRING) - ok = validate_string(q, c->item_size-1); - else if (type == SD_BUS_TYPE_OBJECT_PATH) - ok = validate_object_path(q, c->item_size-1); - else - ok = validate_signature(q, c->item_size-1); - - if (!ok) - return -EBADMSG; - - if (p) - *(const char**) p = q; - } else { - int sz, align; - - sz = bus_gvariant_get_size(CHAR_TO_STR(type)); - assert(sz > 0); - if ((size_t) sz != c->item_size) - return -EBADMSG; - - align = bus_gvariant_get_alignment(CHAR_TO_STR(type)); - assert(align > 0); - - r = message_peek_body(m, &rindex, align, c->item_size, &q); - if (r < 0) - return r; - - switch (type) { - - case SD_BUS_TYPE_BYTE: - if (p) - *(uint8_t*) p = *(uint8_t*) q; - break; - - case SD_BUS_TYPE_BOOLEAN: - if (p) - *(int*) p = !!*(uint8_t*) q; - break; - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - if (p) - *(uint16_t*) p = BUS_MESSAGE_BSWAP16(m, *(uint16_t*) q); - break; - - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - if (p) - *(uint32_t*) p = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); - break; - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - if (p) - *(uint64_t*) p = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q); - break; - - case SD_BUS_TYPE_UNIX_FD: { - uint32_t j; - - j = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); - if (j >= m->n_fds) - return -EBADMSG; - - if (p) - *(int*) p = m->fds[j]; - - break; - } - - default: - assert_not_reached("unexpected type"); - } - } - - r = container_next_item(m, c, &rindex); - if (r < 0) - return r; - } else { - - if (IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH)) { - uint32_t l; - bool ok; - - r = message_peek_body(m, &rindex, 4, 4, &q); - if (r < 0) - return r; - - l = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); - r = message_peek_body(m, &rindex, 1, l+1, &q); - if (r < 0) - return r; - - if (type == SD_BUS_TYPE_OBJECT_PATH) - ok = validate_object_path(q, l); - else - ok = validate_string(q, l); - if (!ok) - return -EBADMSG; - - if (p) - *(const char**) p = q; - - } else if (type == SD_BUS_TYPE_SIGNATURE) { - uint8_t l; - - r = message_peek_body(m, &rindex, 1, 1, &q); - if (r < 0) - return r; - - l = *(uint8_t*) q; - r = message_peek_body(m, &rindex, 1, l+1, &q); - if (r < 0) - return r; - - if (!validate_signature(q, l)) - return -EBADMSG; - - if (p) - *(const char**) p = q; - - } else { - ssize_t sz, align; - - align = bus_type_get_alignment(type); - assert(align > 0); - - sz = bus_type_get_size(type); - assert(sz > 0); - - r = message_peek_body(m, &rindex, align, sz, &q); - if (r < 0) - return r; - - switch (type) { - - case SD_BUS_TYPE_BYTE: - if (p) - *(uint8_t*) p = *(uint8_t*) q; - break; - - case SD_BUS_TYPE_BOOLEAN: - if (p) - *(int*) p = !!*(uint32_t*) q; - break; - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - if (p) - *(uint16_t*) p = BUS_MESSAGE_BSWAP16(m, *(uint16_t*) q); - break; - - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - if (p) - *(uint32_t*) p = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); - break; - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - if (p) - *(uint64_t*) p = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q); - break; - - case SD_BUS_TYPE_UNIX_FD: { - uint32_t j; - - j = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); - if (j >= m->n_fds) - return -EBADMSG; - - if (p) - *(int*) p = m->fds[j]; - break; - } - - default: - assert_not_reached("Unknown basic type..."); - } - } - } - - m->rindex = rindex; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index++; - - return 1; -} - -static int bus_message_enter_array( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - uint32_t **array_size, - size_t *item_size, - size_t **offsets, - size_t *n_offsets) { - - size_t rindex; - void *q; - int r, alignment; - - assert(m); - assert(c); - assert(contents); - assert(array_size); - assert(item_size); - assert(offsets); - assert(n_offsets); - - if (!signature_is_single(contents, true)) - return -EINVAL; - - if (!c->signature || c->signature[c->index] == 0) - return -ENXIO; - - if (c->signature[c->index] != SD_BUS_TYPE_ARRAY) - return -ENXIO; - - if (!startswith(c->signature + c->index + 1, contents)) - return -ENXIO; - - rindex = m->rindex; - - if (!BUS_MESSAGE_IS_GVARIANT(m)) { - /* dbus1 */ - - r = message_peek_body(m, &rindex, 4, 4, &q); - if (r < 0) - return r; - - if (BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q) > BUS_ARRAY_MAX_SIZE) - return -EBADMSG; - - alignment = bus_type_get_alignment(contents[0]); - if (alignment < 0) - return alignment; - - r = message_peek_body(m, &rindex, alignment, 0, NULL); - if (r < 0) - return r; - - *array_size = (uint32_t*) q; - - } else if (c->item_size <= 0) { - - /* gvariant: empty array */ - *item_size = 0; - *offsets = NULL; - *n_offsets = 0; - - } else if (bus_gvariant_is_fixed_size(contents)) { - - /* gvariant: fixed length array */ - *item_size = bus_gvariant_get_size(contents); - *offsets = NULL; - *n_offsets = 0; - - } else { - size_t where, p = 0, framing, sz; - unsigned i; - - /* gvariant: variable length array */ - sz = bus_gvariant_determine_word_size(c->item_size, 0); - - where = rindex + c->item_size - sz; - r = message_peek_body(m, &where, 1, sz, &q); - if (r < 0) - return r; - - framing = bus_gvariant_read_word_le(q, sz); - if (framing > c->item_size - sz) - return -EBADMSG; - if ((c->item_size - framing) % sz != 0) - return -EBADMSG; - - *n_offsets = (c->item_size - framing) / sz; - - where = rindex + framing; - r = message_peek_body(m, &where, 1, *n_offsets * sz, &q); - if (r < 0) - return r; - - *offsets = new(size_t, *n_offsets); - if (!*offsets) - return -ENOMEM; - - for (i = 0; i < *n_offsets; i++) { - size_t x; - - x = bus_gvariant_read_word_le((uint8_t*) q + i * sz, sz); - if (x > c->item_size - sz) - return -EBADMSG; - if (x < p) - return -EBADMSG; - - (*offsets)[i] = rindex + x; - p = x; - } - - *item_size = (*offsets)[0] - rindex; - } - - m->rindex = rindex; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index += 1 + strlen(contents); - - return 1; -} - -static int bus_message_enter_variant( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - size_t *item_size) { - - size_t rindex; - uint8_t l; - void *q; - int r; - - assert(m); - assert(c); - assert(contents); - assert(item_size); - - if (!signature_is_single(contents, false)) - return -EINVAL; - - if (*contents == SD_BUS_TYPE_DICT_ENTRY_BEGIN) - return -EINVAL; - - if (!c->signature || c->signature[c->index] == 0) - return -ENXIO; - - if (c->signature[c->index] != SD_BUS_TYPE_VARIANT) - return -ENXIO; - - rindex = m->rindex; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - size_t k, where; - - k = strlen(contents); - if (1+k > c->item_size) - return -EBADMSG; - - where = rindex + c->item_size - (1+k); - r = message_peek_body(m, &where, 1, 1+k, &q); - if (r < 0) - return r; - - if (*(char*) q != 0) - return -EBADMSG; - - if (memcmp((uint8_t*) q+1, contents, k)) - return -ENXIO; - - *item_size = c->item_size - (1+k); - - } else { - r = message_peek_body(m, &rindex, 1, 1, &q); - if (r < 0) - return r; - - l = *(uint8_t*) q; - r = message_peek_body(m, &rindex, 1, l+1, &q); - if (r < 0) - return r; - - if (!validate_signature(q, l)) - return -EBADMSG; - - if (!streq(q, contents)) - return -ENXIO; - } - - m->rindex = rindex; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index++; - - return 1; -} - -static int build_struct_offsets( - sd_bus_message *m, - const char *signature, - size_t size, - size_t *item_size, - size_t **offsets, - size_t *n_offsets) { - - unsigned n_variable = 0, n_total = 0, v; - size_t previous = 0, where; - const char *p; - size_t sz; - void *q; - int r; - - assert(m); - assert(item_size); - assert(offsets); - assert(n_offsets); - - if (isempty(signature)) { - /* Unary type is encoded as *fixed* 1 byte padding */ - r = message_peek_body(m, &m->rindex, 1, 1, &q); - if (r < 0) - return r; - - if (*(uint8_t *) q != 0) - return -EBADMSG; - - *item_size = 0; - *offsets = NULL; - *n_offsets = 0; - return 0; - } - - sz = bus_gvariant_determine_word_size(size, 0); - if (sz <= 0) - return -EBADMSG; - - /* First, loop over signature and count variable elements and - * elements in general. We use this to know how large the - * offset array is at the end of the structure. Note that - * GVariant only stores offsets for all variable size elements - * that are not the last item. */ - - p = signature; - while (*p != 0) { - size_t n; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - else { - char t[n+1]; - - memcpy(t, p, n); - t[n] = 0; - - r = bus_gvariant_is_fixed_size(t); - } - - if (r < 0) - return r; - if (r == 0 && p[n] != 0) /* except the last item */ - n_variable ++; - n_total++; - - p += n; - } - - if (size < n_variable * sz) - return -EBADMSG; - - where = m->rindex + size - (n_variable * sz); - r = message_peek_body(m, &where, 1, n_variable * sz, &q); - if (r < 0) - return r; - - v = n_variable; - - *offsets = new(size_t, n_total); - if (!*offsets) - return -ENOMEM; - - *n_offsets = 0; - - /* Second, loop again and build an offset table */ - p = signature; - while (*p != 0) { - size_t n, offset; - int k; - - r = signature_element_length(p, &n); - if (r < 0) - return r; - else { - char t[n+1]; - - memcpy(t, p, n); - t[n] = 0; - - k = bus_gvariant_get_size(t); - if (k < 0) { - size_t x; - - /* variable size */ - if (v > 0) { - v--; - - x = bus_gvariant_read_word_le((uint8_t*) q + v*sz, sz); - if (x >= size) - return -EBADMSG; - if (m->rindex + x < previous) - return -EBADMSG; - } else - /* The last item's end - * is determined from - * the start of the - * offset array */ - x = size - (n_variable * sz); - - offset = m->rindex + x; - - } else { - size_t align; - - /* fixed size */ - align = bus_gvariant_get_alignment(t); - assert(align > 0); - - offset = (*n_offsets == 0 ? m->rindex : ALIGN_TO((*offsets)[*n_offsets-1], align)) + k; - } - } - - previous = (*offsets)[(*n_offsets)++] = offset; - p += n; - } - - assert(v == 0); - assert(*n_offsets == n_total); - - *item_size = (*offsets)[0] - m->rindex; - return 0; -} - -static int enter_struct_or_dict_entry( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - size_t *item_size, - size_t **offsets, - size_t *n_offsets) { - - int r; - - assert(m); - assert(c); - assert(contents); - assert(item_size); - assert(offsets); - assert(n_offsets); - - if (!BUS_MESSAGE_IS_GVARIANT(m)) { - - /* dbus1 */ - r = message_peek_body(m, &m->rindex, 8, 0, NULL); - if (r < 0) - return r; - - } else - /* gvariant with contents */ - return build_struct_offsets(m, contents, c->item_size, item_size, offsets, n_offsets); - - return 0; -} - -static int bus_message_enter_struct( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - size_t *item_size, - size_t **offsets, - size_t *n_offsets) { - - size_t l; - int r; - - assert(m); - assert(c); - assert(contents); - assert(item_size); - assert(offsets); - assert(n_offsets); - - if (!signature_is_valid(contents, false)) - return -EINVAL; - - if (!c->signature || c->signature[c->index] == 0) - return -ENXIO; - - l = strlen(contents); - - if (c->signature[c->index] != SD_BUS_TYPE_STRUCT_BEGIN || - !startswith(c->signature + c->index + 1, contents) || - c->signature[c->index + 1 + l] != SD_BUS_TYPE_STRUCT_END) - return -ENXIO; - - r = enter_struct_or_dict_entry(m, c, contents, item_size, offsets, n_offsets); - if (r < 0) - return r; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index += 1 + l + 1; - - return 1; -} - -static int bus_message_enter_dict_entry( - sd_bus_message *m, - struct bus_container *c, - const char *contents, - size_t *item_size, - size_t **offsets, - size_t *n_offsets) { - - size_t l; - int r; - - assert(m); - assert(c); - assert(contents); - - if (!signature_is_pair(contents)) - return -EINVAL; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - return -ENXIO; - - if (!c->signature || c->signature[c->index] == 0) - return 0; - - l = strlen(contents); - - if (c->signature[c->index] != SD_BUS_TYPE_DICT_ENTRY_BEGIN || - !startswith(c->signature + c->index + 1, contents) || - c->signature[c->index + 1 + l] != SD_BUS_TYPE_DICT_ENTRY_END) - return -ENXIO; - - r = enter_struct_or_dict_entry(m, c, contents, item_size, offsets, n_offsets); - if (r < 0) - return r; - - if (c->enclosing != SD_BUS_TYPE_ARRAY) - c->index += 1 + l + 1; - - return 1; -} - -_public_ int sd_bus_message_enter_container(sd_bus_message *m, - char type, - const char *contents) { - struct bus_container *c, *w; - uint32_t *array_size = NULL; - char *signature; - size_t before; - size_t *offsets = NULL; - size_t n_offsets = 0, item_size = 0; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(type != 0 || !contents, -EINVAL); - - if (type == 0 || !contents) { - const char *cc; - char tt; - - /* Allow entering into anonymous containers */ - r = sd_bus_message_peek_type(m, &tt, &cc); - if (r < 0) - return r; - - if (type != 0 && type != tt) - return -ENXIO; - - if (contents && !streq(contents, cc)) - return -ENXIO; - - type = tt; - contents = cc; - } - - /* - * We enforce a global limit on container depth, that is much - * higher than the 32 structs and 32 arrays the specification - * mandates. This is simpler to implement for us, and we need - * this only to ensure our container array doesn't grow - * without bounds. We are happy to return any data from a - * message as long as the data itself is valid, even if the - * overall message might be not. - * - * Note that the message signature is validated when - * parsing the headers, and that validation does check the - * 32/32 limit. - * - * Note that the specification defines no limits on the depth - * of stacked variants, but we do. - */ - if (m->n_containers >= BUS_CONTAINER_DEPTH) - return -EBADMSG; - - if (!GREEDY_REALLOC(m->containers, m->containers_allocated, m->n_containers + 1)) - return -ENOMEM; - - if (message_end_of_signature(m)) - return -ENXIO; - - if (message_end_of_array(m, m->rindex)) - return 0; - - c = message_get_container(m); - - signature = strdup(contents); - if (!signature) - return -ENOMEM; - - c->saved_index = c->index; - before = m->rindex; - - if (type == SD_BUS_TYPE_ARRAY) - r = bus_message_enter_array(m, c, contents, &array_size, &item_size, &offsets, &n_offsets); - else if (type == SD_BUS_TYPE_VARIANT) - r = bus_message_enter_variant(m, c, contents, &item_size); - else if (type == SD_BUS_TYPE_STRUCT) - r = bus_message_enter_struct(m, c, contents, &item_size, &offsets, &n_offsets); - else if (type == SD_BUS_TYPE_DICT_ENTRY) - r = bus_message_enter_dict_entry(m, c, contents, &item_size, &offsets, &n_offsets); - else - r = -EINVAL; - - if (r <= 0) { - free(signature); - free(offsets); - return r; - } - - /* OK, let's fill it in */ - w = m->containers + m->n_containers++; - w->enclosing = type; - w->signature = signature; - w->peeked_signature = NULL; - w->index = 0; - - w->before = before; - w->begin = m->rindex; - - /* Unary type has fixed size of 1, but virtual size of 0 */ - if (BUS_MESSAGE_IS_GVARIANT(m) && - type == SD_BUS_TYPE_STRUCT && - isempty(signature)) - w->end = m->rindex + 0; - else - w->end = m->rindex + c->item_size; - - w->array_size = array_size; - w->item_size = item_size; - w->offsets = offsets; - w->n_offsets = n_offsets; - w->offset_index = 0; - - return 1; -} - -_public_ int sd_bus_message_exit_container(sd_bus_message *m) { - struct bus_container *c; - unsigned saved; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(m->n_containers > 0, -ENXIO); - - c = message_get_container(m); - - if (c->enclosing != SD_BUS_TYPE_ARRAY) { - if (c->signature && c->signature[c->index] != 0) - return -EBUSY; - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - if (m->rindex < c->end) - return -EBUSY; - - } else if (c->enclosing == SD_BUS_TYPE_ARRAY) { - uint32_t l; - - l = BUS_MESSAGE_BSWAP32(m, *c->array_size); - if (c->begin + l != m->rindex) - return -EBUSY; - } - - free(c->signature); - free(c->peeked_signature); - free(c->offsets); - m->n_containers--; - - c = message_get_container(m); - - saved = c->index; - c->index = c->saved_index; - r = container_next_item(m, c, &m->rindex); - c->index = saved; - if (r < 0) - return r; - - return 1; -} - -static void message_quit_container(sd_bus_message *m) { - struct bus_container *c; - - assert(m); - assert(m->sealed); - assert(m->n_containers > 0); - - c = message_get_container(m); - - /* Undo seeks */ - assert(m->rindex >= c->before); - m->rindex = c->before; - - /* Free container */ - free(c->signature); - free(c->offsets); - m->n_containers--; - - /* Correct index of new top-level container */ - c = message_get_container(m); - c->index = c->saved_index; -} - -_public_ int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char **contents) { - struct bus_container *c; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - - if (message_end_of_signature(m)) - goto eof; - - if (message_end_of_array(m, m->rindex)) - goto eof; - - c = message_get_container(m); - - if (bus_type_is_basic(c->signature[c->index])) { - if (contents) - *contents = NULL; - if (type) - *type = c->signature[c->index]; - return 1; - } - - if (c->signature[c->index] == SD_BUS_TYPE_ARRAY) { - - if (contents) { - size_t l; - char *sig; - - r = signature_element_length(c->signature+c->index+1, &l); - if (r < 0) - return r; - - assert(l >= 1); - - sig = strndup(c->signature + c->index + 1, l); - if (!sig) - return -ENOMEM; - - free(c->peeked_signature); - *contents = c->peeked_signature = sig; - } - - if (type) - *type = SD_BUS_TYPE_ARRAY; - - return 1; - } - - if (c->signature[c->index] == SD_BUS_TYPE_STRUCT_BEGIN || - c->signature[c->index] == SD_BUS_TYPE_DICT_ENTRY_BEGIN) { - - if (contents) { - size_t l; - char *sig; - - r = signature_element_length(c->signature+c->index, &l); - if (r < 0) - return r; - - assert(l >= 2); - sig = strndup(c->signature + c->index + 1, l - 2); - if (!sig) - return -ENOMEM; - - free(c->peeked_signature); - *contents = c->peeked_signature = sig; - } - - if (type) - *type = c->signature[c->index] == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY; - - return 1; - } - - if (c->signature[c->index] == SD_BUS_TYPE_VARIANT) { - if (contents) { - void *q; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - size_t k; - - if (c->item_size < 2) - return -EBADMSG; - - /* Look for the NUL delimiter that - separates the payload from the - signature. Since the body might be - in a different part that then the - signature we map byte by byte. */ - - for (k = 2; k <= c->item_size; k++) { - size_t where; - - where = m->rindex + c->item_size - k; - r = message_peek_body(m, &where, 1, k, &q); - if (r < 0) - return r; - - if (*(char*) q == 0) - break; - } - - if (k > c->item_size) - return -EBADMSG; - - free(c->peeked_signature); - c->peeked_signature = strndup((char*) q + 1, k - 1); - if (!c->peeked_signature) - return -ENOMEM; - - if (!signature_is_valid(c->peeked_signature, true)) - return -EBADMSG; - - *contents = c->peeked_signature; - } else { - size_t rindex, l; - - rindex = m->rindex; - r = message_peek_body(m, &rindex, 1, 1, &q); - if (r < 0) - return r; - - l = *(uint8_t*) q; - r = message_peek_body(m, &rindex, 1, l+1, &q); - if (r < 0) - return r; - - if (!validate_signature(q, l)) - return -EBADMSG; - - *contents = q; - } - } - - if (type) - *type = SD_BUS_TYPE_VARIANT; - - return 1; - } - - return -EINVAL; - -eof: - if (type) - *type = 0; - if (contents) - *contents = NULL; - return 0; -} - -_public_ int sd_bus_message_rewind(sd_bus_message *m, int complete) { - struct bus_container *c; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - - if (complete) { - message_reset_containers(m); - m->rindex = 0; - - c = message_get_container(m); - } else { - c = message_get_container(m); - - c->offset_index = 0; - c->index = 0; - m->rindex = c->begin; - } - - c->offset_index = 0; - c->item_size = (c->n_offsets > 0 ? c->offsets[0] : c->end) - c->begin; - - return !isempty(c->signature); -} - -static int message_read_ap( - sd_bus_message *m, - const char *types, - va_list ap) { - - unsigned n_array, n_struct; - TypeStack stack[BUS_CONTAINER_DEPTH]; - unsigned stack_ptr = 0; - unsigned n_loop = 0; - int r; - - assert(m); - - if (isempty(types)) - return 0; - - /* Ideally, we'd just call ourselves recursively on every - * complex type. However, the state of a va_list that is - * passed to a function is undefined after that function - * returns. This means we need to docode the va_list linearly - * in a single stackframe. We hence implement our own - * home-grown stack in an array. */ - - n_array = (unsigned) -1; /* length of current array entries */ - n_struct = strlen(types); /* length of current struct contents signature */ - - for (;;) { - const char *t; - - n_loop++; - - if (n_array == 0 || (n_array == (unsigned) -1 && n_struct == 0)) { - r = type_stack_pop(stack, ELEMENTSOF(stack), &stack_ptr, &types, &n_struct, &n_array); - if (r < 0) - return r; - if (r == 0) - break; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - continue; - } - - t = types; - if (n_array != (unsigned) -1) - n_array --; - else { - types ++; - n_struct--; - } - - switch (*t) { - - case SD_BUS_TYPE_BYTE: - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: - case SD_BUS_TYPE_UNIX_FD: { - void *p; - - p = va_arg(ap, void*); - r = sd_bus_message_read_basic(m, *t, p); - if (r < 0) - return r; - if (r == 0) { - if (n_loop <= 1) - return 0; - - return -ENXIO; - } - - break; - } - - case SD_BUS_TYPE_ARRAY: { - size_t k; - - r = signature_element_length(t + 1, &k); - if (r < 0) - return r; - - { - char s[k + 1]; - memcpy(s, t + 1, k); - s[k] = 0; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, s); - if (r < 0) - return r; - if (r == 0) { - if (n_loop <= 1) - return 0; - - return -ENXIO; - } - } - - if (n_array == (unsigned) -1) { - types += k; - n_struct -= k; - } - - r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); - if (r < 0) - return r; - - types = t + 1; - n_struct = k; - n_array = va_arg(ap, unsigned); - - break; - } - - case SD_BUS_TYPE_VARIANT: { - const char *s; - - s = va_arg(ap, const char *); - if (!s) - return -EINVAL; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, s); - if (r < 0) - return r; - if (r == 0) { - if (n_loop <= 1) - return 0; - - return -ENXIO; - } - - r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); - if (r < 0) - return r; - - types = s; - n_struct = strlen(s); - n_array = (unsigned) -1; - - break; - } - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - size_t k; - - r = signature_element_length(t, &k); - if (r < 0) - return r; - - { - char s[k - 1]; - memcpy(s, t + 1, k - 2); - s[k - 2] = 0; - - r = sd_bus_message_enter_container(m, *t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); - if (r < 0) - return r; - if (r == 0) { - if (n_loop <= 1) - return 0; - return -ENXIO; - } - } - - if (n_array == (unsigned) -1) { - types += k - 1; - n_struct -= k - 1; - } - - r = type_stack_push(stack, ELEMENTSOF(stack), &stack_ptr, types, n_struct, n_array); - if (r < 0) - return r; - - types = t + 1; - n_struct = k - 2; - n_array = (unsigned) -1; - - break; - } - - default: - return -EINVAL; - } - } - - return 1; -} - -_public_ int sd_bus_message_read(sd_bus_message *m, const char *types, ...) { - va_list ap; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(types, -EINVAL); - - va_start(ap, types); - r = message_read_ap(m, types, ap); - va_end(ap); - - return r; -} - -_public_ int sd_bus_message_skip(sd_bus_message *m, const char *types) { - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - - /* If types is NULL, read exactly one element */ - if (!types) { - struct bus_container *c; - size_t l; - - if (message_end_of_signature(m)) - return -ENXIO; - - if (message_end_of_array(m, m->rindex)) - return 0; - - c = message_get_container(m); - - r = signature_element_length(c->signature + c->index, &l); - if (r < 0) - return r; - - types = strndupa(c->signature + c->index, l); - } - - switch (*types) { - - case 0: /* Nothing to drop */ - return 0; - - case SD_BUS_TYPE_BYTE: - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: - case SD_BUS_TYPE_UNIX_FD: - - r = sd_bus_message_read_basic(m, *types, NULL); - if (r <= 0) - return r; - - r = sd_bus_message_skip(m, types + 1); - if (r < 0) - return r; - - return 1; - - case SD_BUS_TYPE_ARRAY: { - size_t k; - - r = signature_element_length(types + 1, &k); - if (r < 0) - return r; - - { - char s[k+1]; - memcpy(s, types+1, k); - s[k] = 0; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, s); - if (r <= 0) - return r; - - for (;;) { - r = sd_bus_message_skip(m, s); - if (r < 0) - return r; - if (r == 0) - break; - } - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - } - - r = sd_bus_message_skip(m, types + 1 + k); - if (r < 0) - return r; - - return 1; - } - - case SD_BUS_TYPE_VARIANT: { - const char *contents; - char x; - - r = sd_bus_message_peek_type(m, &x, &contents); - if (r <= 0) - return r; - - if (x != SD_BUS_TYPE_VARIANT) - return -ENXIO; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); - if (r <= 0) - return r; - - r = sd_bus_message_skip(m, contents); - if (r < 0) - return r; - assert(r != 0); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - r = sd_bus_message_skip(m, types + 1); - if (r < 0) - return r; - - return 1; - } - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - size_t k; - - r = signature_element_length(types, &k); - if (r < 0) - return r; - - { - char s[k-1]; - memcpy(s, types+1, k-2); - s[k-2] = 0; - - r = sd_bus_message_enter_container(m, *types == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); - if (r <= 0) - return r; - - r = sd_bus_message_skip(m, s); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - } - - r = sd_bus_message_skip(m, types + k); - if (r < 0) - return r; - - return 1; - } - - default: - return -EINVAL; - } -} - -_public_ int sd_bus_message_read_array( - sd_bus_message *m, - char type, - const void **ptr, - size_t *size) { - - struct bus_container *c; - void *p; - size_t sz; - ssize_t align; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(bus_type_is_trivial(type), -EINVAL); - assert_return(ptr, -EINVAL); - assert_return(size, -EINVAL); - assert_return(!BUS_MESSAGE_NEED_BSWAP(m), -EOPNOTSUPP); - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, CHAR_TO_STR(type)); - if (r <= 0) - return r; - - c = message_get_container(m); - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - align = bus_gvariant_get_alignment(CHAR_TO_STR(type)); - if (align < 0) - return align; - - sz = c->end - c->begin; - } else { - align = bus_type_get_alignment(type); - if (align < 0) - return align; - - sz = BUS_MESSAGE_BSWAP32(m, *c->array_size); - } - - if (sz == 0) - /* Zero length array, let's return some aligned - * pointer that is not NULL */ - p = (uint8_t*) NULL + align; - else { - r = message_peek_body(m, &m->rindex, align, sz, &p); - if (r < 0) - goto fail; - } - - r = sd_bus_message_exit_container(m); - if (r < 0) - goto fail; - - *ptr = (const void*) p; - *size = sz; - - return 1; - -fail: - message_quit_container(m); - return r; -} - -static int message_peek_fields( - sd_bus_message *m, - size_t *rindex, - size_t align, - size_t nbytes, - void **ret) { - - assert(m); - assert(rindex); - assert(align > 0); - - return buffer_peek(BUS_MESSAGE_FIELDS(m), m->fields_size, rindex, align, nbytes, ret); -} - -static int message_peek_field_uint32( - sd_bus_message *m, - size_t *ri, - size_t item_size, - uint32_t *ret) { - - int r; - void *q; - - assert(m); - assert(ri); - - if (BUS_MESSAGE_IS_GVARIANT(m) && item_size != 4) - return -EBADMSG; - - /* identical for gvariant and dbus1 */ - - r = message_peek_fields(m, ri, 4, 4, &q); - if (r < 0) - return r; - - if (ret) - *ret = BUS_MESSAGE_BSWAP32(m, *(uint32_t*) q); - - return 0; -} - -static int message_peek_field_uint64( - sd_bus_message *m, - size_t *ri, - size_t item_size, - uint64_t *ret) { - - int r; - void *q; - - assert(m); - assert(ri); - - if (BUS_MESSAGE_IS_GVARIANT(m) && item_size != 8) - return -EBADMSG; - - /* identical for gvariant and dbus1 */ - - r = message_peek_fields(m, ri, 8, 8, &q); - if (r < 0) - return r; - - if (ret) - *ret = BUS_MESSAGE_BSWAP64(m, *(uint64_t*) q); - - return 0; -} - -static int message_peek_field_string( - sd_bus_message *m, - bool (*validate)(const char *p), - size_t *ri, - size_t item_size, - const char **ret) { - - uint32_t l; - int r; - void *q; - - assert(m); - assert(ri); - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - - if (item_size <= 0) - return -EBADMSG; - - r = message_peek_fields(m, ri, 1, item_size, &q); - if (r < 0) - return r; - - l = item_size - 1; - } else { - r = message_peek_field_uint32(m, ri, 4, &l); - if (r < 0) - return r; - - r = message_peek_fields(m, ri, 1, l+1, &q); - if (r < 0) - return r; - } - - if (validate) { - if (!validate_nul(q, l)) - return -EBADMSG; - - if (!validate(q)) - return -EBADMSG; - } else { - if (!validate_string(q, l)) - return -EBADMSG; - } - - if (ret) - *ret = q; - - return 0; -} - -static int message_peek_field_signature( - sd_bus_message *m, - size_t *ri, - size_t item_size, - const char **ret) { - - size_t l; - int r; - void *q; - - assert(m); - assert(ri); - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - - if (item_size <= 0) - return -EBADMSG; - - r = message_peek_fields(m, ri, 1, item_size, &q); - if (r < 0) - return r; - - l = item_size - 1; - } else { - r = message_peek_fields(m, ri, 1, 1, &q); - if (r < 0) - return r; - - l = *(uint8_t*) q; - r = message_peek_fields(m, ri, 1, l+1, &q); - if (r < 0) - return r; - } - - if (!validate_signature(q, l)) - return -EBADMSG; - - if (ret) - *ret = q; - - return 0; -} - -static int message_skip_fields( - sd_bus_message *m, - size_t *ri, - uint32_t array_size, - const char **signature) { - - size_t original_index; - int r; - - assert(m); - assert(ri); - assert(signature); - assert(!BUS_MESSAGE_IS_GVARIANT(m)); - - original_index = *ri; - - for (;;) { - char t; - size_t l; - - if (array_size != (uint32_t) -1 && - array_size <= *ri - original_index) - return 0; - - t = **signature; - if (!t) - return 0; - - if (t == SD_BUS_TYPE_STRING) { - - r = message_peek_field_string(m, NULL, ri, 0, NULL); - if (r < 0) - return r; - - (*signature)++; - - } else if (t == SD_BUS_TYPE_OBJECT_PATH) { - - r = message_peek_field_string(m, object_path_is_valid, ri, 0, NULL); - if (r < 0) - return r; - - (*signature)++; - - } else if (t == SD_BUS_TYPE_SIGNATURE) { - - r = message_peek_field_signature(m, ri, 0, NULL); - if (r < 0) - return r; - - (*signature)++; - - } else if (bus_type_is_basic(t)) { - ssize_t align, k; - - align = bus_type_get_alignment(t); - k = bus_type_get_size(t); - assert(align > 0 && k > 0); - - r = message_peek_fields(m, ri, align, k, NULL); - if (r < 0) - return r; - - (*signature)++; - - } else if (t == SD_BUS_TYPE_ARRAY) { - - r = signature_element_length(*signature+1, &l); - if (r < 0) - return r; - - assert(l >= 1); - { - char sig[l-1], *s; - uint32_t nas; - int alignment; - - strncpy(sig, *signature + 1, l-1); - s = sig; - - alignment = bus_type_get_alignment(sig[0]); - if (alignment < 0) - return alignment; - - r = message_peek_field_uint32(m, ri, 0, &nas); - if (r < 0) - return r; - if (nas > BUS_ARRAY_MAX_SIZE) - return -EBADMSG; - - r = message_peek_fields(m, ri, alignment, 0, NULL); - if (r < 0) - return r; - - r = message_skip_fields(m, ri, nas, (const char**) &s); - if (r < 0) - return r; - } - - (*signature) += 1 + l; - - } else if (t == SD_BUS_TYPE_VARIANT) { - const char *s; - - r = message_peek_field_signature(m, ri, 0, &s); - if (r < 0) - return r; - - r = message_skip_fields(m, ri, (uint32_t) -1, (const char**) &s); - if (r < 0) - return r; - - (*signature)++; - - } else if (t == SD_BUS_TYPE_STRUCT || - t == SD_BUS_TYPE_DICT_ENTRY) { - - r = signature_element_length(*signature, &l); - if (r < 0) - return r; - - assert(l >= 2); - { - char sig[l-1], *s; - strncpy(sig, *signature + 1, l-1); - s = sig; - - r = message_skip_fields(m, ri, (uint32_t) -1, (const char**) &s); - if (r < 0) - return r; - } - - *signature += l; - } else - return -EINVAL; - } -} - -int bus_message_parse_fields(sd_bus_message *m) { - size_t ri; - int r; - uint32_t unix_fds = 0; - bool unix_fds_set = false; - void *offsets = NULL; - unsigned n_offsets = 0; - size_t sz = 0; - unsigned i = 0; - - assert(m); - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - char *p; - - /* Read the signature from the end of the body variant first */ - sz = bus_gvariant_determine_word_size(BUS_MESSAGE_SIZE(m), 0); - if (m->footer_accessible < 1 + sz) - return -EBADMSG; - - p = (char*) m->footer + m->footer_accessible - (1 + sz); - for (;;) { - if (p < (char*) m->footer) - return -EBADMSG; - - if (*p == 0) { - size_t l; - char *c; - - /* We found the beginning of the signature - * string, yay! We require the body to be a - * structure, so verify it and then strip the - * opening/closing brackets. */ - - l = ((char*) m->footer + m->footer_accessible) - p - (1 + sz); - if (l < 2 || - p[1] != SD_BUS_TYPE_STRUCT_BEGIN || - p[1 + l - 1] != SD_BUS_TYPE_STRUCT_END) - return -EBADMSG; - - c = strndup(p + 1 + 1, l - 2); - if (!c) - return -ENOMEM; - - free(m->root_container.signature); - m->root_container.signature = c; - break; - } - - p--; - } - - /* Calculate the actual user body size, by removing - * the trailing variant signature and struct offset - * table */ - m->user_body_size = m->body_size - ((char*) m->footer + m->footer_accessible - p); - - /* Pull out the offset table for the fields array */ - sz = bus_gvariant_determine_word_size(m->fields_size, 0); - if (sz > 0) { - size_t framing; - void *q; - - ri = m->fields_size - sz; - r = message_peek_fields(m, &ri, 1, sz, &q); - if (r < 0) - return r; - - framing = bus_gvariant_read_word_le(q, sz); - if (framing >= m->fields_size - sz) - return -EBADMSG; - if ((m->fields_size - framing) % sz != 0) - return -EBADMSG; - - ri = framing; - r = message_peek_fields(m, &ri, 1, m->fields_size - framing, &offsets); - if (r < 0) - return r; - - n_offsets = (m->fields_size - framing) / sz; - } - } else - m->user_body_size = m->body_size; - - ri = 0; - while (ri < m->fields_size) { - _cleanup_free_ char *sig = NULL; - const char *signature; - uint64_t field_type; - size_t item_size = (size_t) -1; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - uint64_t *u64; - - if (i >= n_offsets) - break; - - if (i == 0) - ri = 0; - else - ri = ALIGN_TO(bus_gvariant_read_word_le((uint8_t*) offsets + (i-1)*sz, sz), 8); - - r = message_peek_fields(m, &ri, 8, 8, (void**) &u64); - if (r < 0) - return r; - - field_type = BUS_MESSAGE_BSWAP64(m, *u64); - } else { - uint8_t *u8; - - r = message_peek_fields(m, &ri, 8, 1, (void**) &u8); - if (r < 0) - return r; - - field_type = *u8; - } - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - size_t where, end; - char *b; - void *q; - - end = bus_gvariant_read_word_le((uint8_t*) offsets + i*sz, sz); - - if (end < ri) - return -EBADMSG; - - where = ri = ALIGN_TO(ri, 8); - item_size = end - ri; - r = message_peek_fields(m, &where, 1, item_size, &q); - if (r < 0) - return r; - - b = memrchr(q, 0, item_size); - if (!b) - return -EBADMSG; - - sig = strndup(b+1, item_size - (b+1-(char*) q)); - if (!sig) - return -ENOMEM; - - signature = sig; - item_size = b - (char*) q; - } else { - r = message_peek_field_signature(m, &ri, 0, &signature); - if (r < 0) - return r; - } - - switch (field_type) { - - case _BUS_MESSAGE_HEADER_INVALID: - return -EBADMSG; - - case BUS_MESSAGE_HEADER_PATH: - - if (m->path) - return -EBADMSG; - - if (!streq(signature, "o")) - return -EBADMSG; - - r = message_peek_field_string(m, object_path_is_valid, &ri, item_size, &m->path); - break; - - case BUS_MESSAGE_HEADER_INTERFACE: - - if (m->interface) - return -EBADMSG; - - if (!streq(signature, "s")) - return -EBADMSG; - - r = message_peek_field_string(m, interface_name_is_valid, &ri, item_size, &m->interface); - break; - - case BUS_MESSAGE_HEADER_MEMBER: - - if (m->member) - return -EBADMSG; - - if (!streq(signature, "s")) - return -EBADMSG; - - r = message_peek_field_string(m, member_name_is_valid, &ri, item_size, &m->member); - break; - - case BUS_MESSAGE_HEADER_ERROR_NAME: - - if (m->error.name) - return -EBADMSG; - - if (!streq(signature, "s")) - return -EBADMSG; - - r = message_peek_field_string(m, error_name_is_valid, &ri, item_size, &m->error.name); - if (r >= 0) - m->error._need_free = -1; - - break; - - case BUS_MESSAGE_HEADER_DESTINATION: - - if (m->destination) - return -EBADMSG; - - if (!streq(signature, "s")) - return -EBADMSG; - - r = message_peek_field_string(m, service_name_is_valid, &ri, item_size, &m->destination); - break; - - case BUS_MESSAGE_HEADER_SENDER: - - if (m->sender) - return -EBADMSG; - - if (!streq(signature, "s")) - return -EBADMSG; - - r = message_peek_field_string(m, service_name_is_valid, &ri, item_size, &m->sender); - - if (r >= 0 && m->sender[0] == ':' && m->bus->bus_client && !m->bus->is_kernel) { - m->creds.unique_name = (char*) m->sender; - m->creds.mask |= SD_BUS_CREDS_UNIQUE_NAME & m->bus->creds_mask; - } - - break; - - - case BUS_MESSAGE_HEADER_SIGNATURE: { - const char *s; - char *c; - - if (BUS_MESSAGE_IS_GVARIANT(m)) /* only applies to dbus1 */ - return -EBADMSG; - - if (m->root_container.signature) - return -EBADMSG; - - if (!streq(signature, "g")) - return -EBADMSG; - - r = message_peek_field_signature(m, &ri, item_size, &s); - if (r < 0) - return r; - - c = strdup(s); - if (!c) - return -ENOMEM; - - free(m->root_container.signature); - m->root_container.signature = c; - break; - } - - case BUS_MESSAGE_HEADER_REPLY_SERIAL: - - if (m->reply_cookie != 0) - return -EBADMSG; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - /* 64bit on dbus2 */ - - if (!streq(signature, "t")) - return -EBADMSG; - - r = message_peek_field_uint64(m, &ri, item_size, &m->reply_cookie); - if (r < 0) - return r; - } else { - /* 32bit on dbus1 */ - uint32_t serial; - - if (!streq(signature, "u")) - return -EBADMSG; - - r = message_peek_field_uint32(m, &ri, item_size, &serial); - if (r < 0) - return r; - - m->reply_cookie = serial; - } - - if (m->reply_cookie == 0) - return -EBADMSG; - - break; - - case BUS_MESSAGE_HEADER_UNIX_FDS: - if (unix_fds_set) - return -EBADMSG; - - if (!streq(signature, "u")) - return -EBADMSG; - - r = message_peek_field_uint32(m, &ri, item_size, &unix_fds); - if (r < 0) - return -EBADMSG; - - unix_fds_set = true; - break; - - default: - if (!BUS_MESSAGE_IS_GVARIANT(m)) - r = message_skip_fields(m, &ri, (uint32_t) -1, (const char **) &signature); - } - - if (r < 0) - return r; - - i++; - } - - if (m->n_fds != unix_fds) - return -EBADMSG; - - switch (m->header->type) { - - case SD_BUS_MESSAGE_SIGNAL: - if (!m->path || !m->interface || !m->member) - return -EBADMSG; - - if (m->reply_cookie != 0) - return -EBADMSG; - - break; - - case SD_BUS_MESSAGE_METHOD_CALL: - - if (!m->path || !m->member) - return -EBADMSG; - - if (m->reply_cookie != 0) - return -EBADMSG; - - break; - - case SD_BUS_MESSAGE_METHOD_RETURN: - - if (m->reply_cookie == 0) - return -EBADMSG; - break; - - case SD_BUS_MESSAGE_METHOD_ERROR: - - if (m->reply_cookie == 0 || !m->error.name) - return -EBADMSG; - break; - } - - /* Refuse non-local messages that claim they are local */ - if (streq_ptr(m->path, "/org/freedesktop/DBus/Local")) - return -EBADMSG; - if (streq_ptr(m->interface, "org.freedesktop.DBus.Local")) - return -EBADMSG; - if (streq_ptr(m->sender, "org.freedesktop.DBus.Local")) - return -EBADMSG; - - m->root_container.end = m->user_body_size; - - if (BUS_MESSAGE_IS_GVARIANT(m)) { - r = build_struct_offsets( - m, - m->root_container.signature, - m->user_body_size, - &m->root_container.item_size, - &m->root_container.offsets, - &m->root_container.n_offsets); - if (r < 0) - return r; - } - - /* Try to read the error message, but if we can't it's a non-issue */ - if (m->header->type == SD_BUS_MESSAGE_METHOD_ERROR) - (void) sd_bus_message_read(m, "s", &m->error.message); - - return 0; -} - -_public_ int sd_bus_message_set_destination(sd_bus_message *m, const char *destination) { - assert_return(m, -EINVAL); - assert_return(destination, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(!m->destination, -EEXIST); - - return message_append_field_string(m, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, destination, &m->destination); -} - -int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz) { - size_t total; - void *p, *e; - unsigned i; - struct bus_body_part *part; - - assert(m); - assert(buffer); - assert(sz); - - total = BUS_MESSAGE_SIZE(m); - - p = malloc(total); - if (!p) - return -ENOMEM; - - e = mempcpy(p, m->header, BUS_MESSAGE_BODY_BEGIN(m)); - MESSAGE_FOREACH_PART(part, i, m) - e = mempcpy(e, part->data, part->size); - - assert(total == (size_t) ((uint8_t*) e - (uint8_t*) p)); - - *buffer = p; - *sz = total; - - return 0; -} - -int bus_message_read_strv_extend(sd_bus_message *m, char ***l) { - const char *s; - int r; - - assert(m); - assert(l); - - r = sd_bus_message_enter_container(m, 'a', "s"); - if (r <= 0) - return r; - - while ((r = sd_bus_message_read_basic(m, 's', &s)) > 0) { - r = strv_extend(l, s); - if (r < 0) - return r; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - return 1; -} - -_public_ int sd_bus_message_read_strv(sd_bus_message *m, char ***l) { - char **strv = NULL; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(l, -EINVAL); - - r = bus_message_read_strv_extend(m, &strv); - if (r <= 0) { - strv_free(strv); - return r; - } - - *l = strv; - return 1; -} - -static int bus_message_get_arg_skip( - sd_bus_message *m, - unsigned i, - char *_type, - const char **_contents) { - - unsigned j; - int r; - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - for (j = 0;; j++) { - const char *contents; - char type; - - r = sd_bus_message_peek_type(m, &type, &contents); - if (r < 0) - return r; - if (r == 0) - return -ENXIO; - - /* Don't match against arguments after the first one we don't understand */ - if (!IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE) && - !(type == SD_BUS_TYPE_ARRAY && STR_IN_SET(contents, "s", "o", "g"))) - return -ENXIO; - - if (j >= i) { - if (_contents) - *_contents = contents; - if (_type) - *_type = type; - return 0; - } - - r = sd_bus_message_skip(m, NULL); - if (r < 0) - return r; - } - -} - -int bus_message_get_arg(sd_bus_message *m, unsigned i, const char **str) { - char type; - int r; - - assert(m); - assert(str); - - r = bus_message_get_arg_skip(m, i, &type, NULL); - if (r < 0) - return r; - - if (!IN_SET(type, SD_BUS_TYPE_STRING, SD_BUS_TYPE_OBJECT_PATH, SD_BUS_TYPE_SIGNATURE)) - return -ENXIO; - - return sd_bus_message_read_basic(m, type, str); -} - -int bus_message_get_arg_strv(sd_bus_message *m, unsigned i, char ***strv) { - const char *contents; - char type; - int r; - - assert(m); - assert(strv); - - r = bus_message_get_arg_skip(m, i, &type, &contents); - if (r < 0) - return r; - - if (type != SD_BUS_TYPE_ARRAY) - return -ENXIO; - if (!STR_IN_SET(contents, "s", "o", "g")) - return -ENXIO; - - return sd_bus_message_read_strv(m, strv); -} - -_public_ int sd_bus_message_get_errno(sd_bus_message *m) { - assert_return(m, EINVAL); - - if (m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) - return 0; - - return sd_bus_error_get_errno(&m->error); -} - -_public_ const char* sd_bus_message_get_signature(sd_bus_message *m, int complete) { - struct bus_container *c; - - assert_return(m, NULL); - - c = complete ? &m->root_container : message_get_container(m); - return strempty(c->signature); -} - -_public_ int sd_bus_message_is_empty(sd_bus_message *m) { - assert_return(m, -EINVAL); - - return isempty(m->root_container.signature); -} - -_public_ int sd_bus_message_has_signature(sd_bus_message *m, const char *signature) { - assert_return(m, -EINVAL); - - return streq(strempty(m->root_container.signature), strempty(signature)); -} - -_public_ int sd_bus_message_copy(sd_bus_message *m, sd_bus_message *source, int all) { - bool done_something = false; - int r; - - assert_return(m, -EINVAL); - assert_return(source, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(source->sealed, -EPERM); - - do { - const char *contents; - char type; - union { - uint8_t u8; - uint16_t u16; - int16_t s16; - uint32_t u32; - int32_t s32; - uint64_t u64; - int64_t s64; - double d64; - const char *string; - int i; - } basic; - - r = sd_bus_message_peek_type(source, &type, &contents); - if (r < 0) - return r; - if (r == 0) - break; - - done_something = true; - - if (bus_type_is_container(type) > 0) { - - r = sd_bus_message_enter_container(source, type, contents); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, type, contents); - if (r < 0) - return r; - - r = sd_bus_message_copy(m, source, true); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(source); - if (r < 0) - return r; - - continue; - } - - r = sd_bus_message_read_basic(source, type, &basic); - if (r < 0) - return r; - - assert(r > 0); - - if (type == SD_BUS_TYPE_OBJECT_PATH || - type == SD_BUS_TYPE_SIGNATURE || - type == SD_BUS_TYPE_STRING) - r = sd_bus_message_append_basic(m, type, basic.string); - else - r = sd_bus_message_append_basic(m, type, &basic); - - if (r < 0) - return r; - - } while (all); - - return done_something; -} - -_public_ int sd_bus_message_verify_type(sd_bus_message *m, char type, const char *contents) { - const char *c; - char t; - int r; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(!type || bus_type_is_valid(type), -EINVAL); - assert_return(!contents || signature_is_valid(contents, true), -EINVAL); - assert_return(type || contents, -EINVAL); - assert_return(!contents || !type || bus_type_is_container(type), -EINVAL); - - r = sd_bus_message_peek_type(m, &t, &c); - if (r <= 0) - return r; - - if (type != 0 && type != t) - return 0; - - if (contents && !streq_ptr(contents, c)) - return 0; - - return 1; -} - -_public_ sd_bus *sd_bus_message_get_bus(sd_bus_message *m) { - assert_return(m, NULL); - - return m->bus; -} - -int bus_message_remarshal(sd_bus *bus, sd_bus_message **m) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *n = NULL; - usec_t timeout; - int r; - - assert(bus); - assert(m); - assert(*m); - - switch ((*m)->header->type) { - - case SD_BUS_MESSAGE_SIGNAL: - r = sd_bus_message_new_signal(bus, &n, (*m)->path, (*m)->interface, (*m)->member); - if (r < 0) - return r; - - break; - - case SD_BUS_MESSAGE_METHOD_CALL: - r = sd_bus_message_new_method_call(bus, &n, (*m)->destination, (*m)->path, (*m)->interface, (*m)->member); - if (r < 0) - return r; - - break; - - case SD_BUS_MESSAGE_METHOD_RETURN: - case SD_BUS_MESSAGE_METHOD_ERROR: - - n = message_new(bus, (*m)->header->type); - if (!n) - return -ENOMEM; - - n->reply_cookie = (*m)->reply_cookie; - - r = message_append_reply_cookie(n, n->reply_cookie); - if (r < 0) - return r; - - if ((*m)->header->type == SD_BUS_MESSAGE_METHOD_ERROR && (*m)->error.name) { - r = message_append_field_string(n, BUS_MESSAGE_HEADER_ERROR_NAME, SD_BUS_TYPE_STRING, (*m)->error.name, &n->error.message); - if (r < 0) - return r; - - n->error._need_free = -1; - } - - break; - - default: - return -EINVAL; - } - - if ((*m)->destination && !n->destination) { - r = message_append_field_string(n, BUS_MESSAGE_HEADER_DESTINATION, SD_BUS_TYPE_STRING, (*m)->destination, &n->destination); - if (r < 0) - return r; - } - - if ((*m)->sender && !n->sender) { - r = message_append_field_string(n, BUS_MESSAGE_HEADER_SENDER, SD_BUS_TYPE_STRING, (*m)->sender, &n->sender); - if (r < 0) - return r; - } - - n->header->flags |= (*m)->header->flags & (BUS_MESSAGE_NO_REPLY_EXPECTED|BUS_MESSAGE_NO_AUTO_START); - - r = sd_bus_message_copy(n, *m, true); - if (r < 0) - return r; - - timeout = (*m)->timeout; - if (timeout == 0 && !((*m)->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED)) - timeout = BUS_DEFAULT_TIMEOUT; - - r = bus_message_seal(n, BUS_MESSAGE_COOKIE(*m), timeout); - if (r < 0) - return r; - - sd_bus_message_unref(*m); - *m = n; - n = NULL; - - return 0; -} - -int bus_message_append_sender(sd_bus_message *m, const char *sender) { - assert(m); - assert(sender); - - assert_return(!m->sealed, -EPERM); - assert_return(!m->sender, -EPERM); - - return message_append_field_string(m, BUS_MESSAGE_HEADER_SENDER, SD_BUS_TYPE_STRING, sender, &m->sender); -} - -_public_ int sd_bus_message_get_priority(sd_bus_message *m, int64_t *priority) { - assert_return(m, -EINVAL); - assert_return(priority, -EINVAL); - - *priority = m->priority; - return 0; -} - -_public_ int sd_bus_message_set_priority(sd_bus_message *m, int64_t priority) { - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - m->priority = priority; - return 0; -} diff --git a/src/libsystemd/sd-bus/bus-message.h b/src/libsystemd/sd-bus/bus-message.h deleted file mode 100644 index 4710c106b9..0000000000 --- a/src/libsystemd/sd-bus/bus-message.h +++ /dev/null @@ -1,244 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include -#include -#include - -#include "sd-bus.h" - -#include "bus-creds.h" -#include "bus-protocol.h" -#include "macro.h" -#include "time-util.h" - -struct bus_container { - char enclosing; - bool need_offsets:1; - - /* Indexes into the signature string */ - unsigned index, saved_index; - char *signature; - - size_t before, begin, end; - - /* dbus1: pointer to the array size value, if this is a value */ - uint32_t *array_size; - - /* gvariant: list of offsets to end of children if this is struct/dict entry/array */ - size_t *offsets, n_offsets, offsets_allocated, offset_index; - size_t item_size; - - char *peeked_signature; -}; - -struct bus_body_part { - struct bus_body_part *next; - void *data; - void *mmap_begin; - size_t size; - size_t mapped; - size_t allocated; - uint64_t memfd_offset; - int memfd; - bool free_this:1; - bool munmap_this:1; - bool sealed:1; - bool is_zero:1; -}; - -struct sd_bus_message { - unsigned n_ref; - - sd_bus *bus; - - uint64_t reply_cookie; - - const char *path; - const char *interface; - const char *member; - const char *destination; - const char *sender; - - sd_bus_error error; - - sd_bus_creds creds; - - usec_t monotonic; - usec_t realtime; - uint64_t seqnum; - int64_t priority; - uint64_t verify_destination_id; - - bool sealed:1; - bool dont_send:1; - bool allow_fds:1; - bool free_header:1; - bool free_kdbus:1; - bool free_fds:1; - bool release_kdbus:1; - bool poisoned:1; - - /* The first and last bytes of the message */ - struct bus_header *header; - void *footer; - - /* How many bytes are accessible in the above pointers */ - size_t header_accessible; - size_t footer_accessible; - - size_t fields_size; - size_t body_size; - size_t user_body_size; - - struct bus_body_part body; - struct bus_body_part *body_end; - unsigned n_body_parts; - - size_t rindex; - struct bus_body_part *cached_rindex_part; - size_t cached_rindex_part_begin; - - uint32_t n_fds; - int *fds; - - struct bus_container root_container, *containers; - size_t n_containers; - size_t containers_allocated; - - struct iovec *iovec; - struct iovec iovec_fixed[2]; - unsigned n_iovec; - - struct kdbus_msg *kdbus; - - char *peeked_signature; - - /* If set replies to this message must carry the signature - * specified here to successfully seal. This is initialized - * from the vtable data */ - const char *enforced_reply_signature; - - usec_t timeout; - - char sender_buffer[3 + DECIMAL_STR_MAX(uint64_t) + 1]; - char destination_buffer[3 + DECIMAL_STR_MAX(uint64_t) + 1]; - char *destination_ptr; - - size_t header_offsets[_BUS_MESSAGE_HEADER_MAX]; - unsigned n_header_offsets; -}; - -static inline bool BUS_MESSAGE_NEED_BSWAP(sd_bus_message *m) { - return m->header->endian != BUS_NATIVE_ENDIAN; -} - -static inline uint16_t BUS_MESSAGE_BSWAP16(sd_bus_message *m, uint16_t u) { - return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_16(u) : u; -} - -static inline uint32_t BUS_MESSAGE_BSWAP32(sd_bus_message *m, uint32_t u) { - return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_32(u) : u; -} - -static inline uint64_t BUS_MESSAGE_BSWAP64(sd_bus_message *m, uint64_t u) { - return BUS_MESSAGE_NEED_BSWAP(m) ? bswap_64(u) : u; -} - -static inline uint64_t BUS_MESSAGE_COOKIE(sd_bus_message *m) { - if (m->header->version == 2) - return BUS_MESSAGE_BSWAP64(m, m->header->dbus2.cookie); - - return BUS_MESSAGE_BSWAP32(m, m->header->dbus1.serial); -} - -static inline size_t BUS_MESSAGE_SIZE(sd_bus_message *m) { - return - sizeof(struct bus_header) + - ALIGN8(m->fields_size) + - m->body_size; -} - -static inline size_t BUS_MESSAGE_BODY_BEGIN(sd_bus_message *m) { - return - sizeof(struct bus_header) + - ALIGN8(m->fields_size); -} - -static inline void* BUS_MESSAGE_FIELDS(sd_bus_message *m) { - return (uint8_t*) m->header + sizeof(struct bus_header); -} - -static inline bool BUS_MESSAGE_IS_GVARIANT(sd_bus_message *m) { - return m->header->version == 2; -} - -int bus_message_seal(sd_bus_message *m, uint64_t serial, usec_t timeout); -int bus_message_get_blob(sd_bus_message *m, void **buffer, size_t *sz); -int bus_message_read_strv_extend(sd_bus_message *m, char ***l); - -int bus_message_from_header( - sd_bus *bus, - void *header, - size_t header_accessible, - void *footer, - size_t footer_accessible, - size_t message_size, - int *fds, - unsigned n_fds, - const char *label, - size_t extra, - sd_bus_message **ret); - -int bus_message_from_malloc( - sd_bus *bus, - void *buffer, - size_t length, - int *fds, - unsigned n_fds, - const char *label, - sd_bus_message **ret); - -int bus_message_get_arg(sd_bus_message *m, unsigned i, const char **str); -int bus_message_get_arg_strv(sd_bus_message *m, unsigned i, char ***strv); - -int bus_message_append_ap(sd_bus_message *m, const char *types, va_list ap); - -int bus_message_parse_fields(sd_bus_message *m); - -struct bus_body_part *message_append_part(sd_bus_message *m); - -#define MESSAGE_FOREACH_PART(part, i, m) \ - for ((i) = 0, (part) = &(m)->body; (i) < (m)->n_body_parts; (i)++, (part) = (part)->next) - -int bus_body_part_map(struct bus_body_part *part); -void bus_body_part_unmap(struct bus_body_part *part); - -int bus_message_to_errno(sd_bus_message *m); - -int bus_message_new_synthetic_error(sd_bus *bus, uint64_t serial, const sd_bus_error *e, sd_bus_message **m); - -int bus_message_remarshal(sd_bus *bus, sd_bus_message **m); - -int bus_message_append_sender(sd_bus_message *m, const char *sender); - -void bus_message_set_sender_driver(sd_bus *bus, sd_bus_message *m); -void bus_message_set_sender_local(sd_bus *bus, sd_bus_message *m); diff --git a/src/libsystemd/sd-bus/bus-objects.c b/src/libsystemd/sd-bus/bus-objects.c deleted file mode 100644 index 1f285ae8a6..0000000000 --- a/src/libsystemd/sd-bus/bus-objects.c +++ /dev/null @@ -1,2806 +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 . -***/ - -#include "alloc-util.h" -#include "bus-internal.h" -#include "bus-introspect.h" -#include "bus-message.h" -#include "bus-objects.h" -#include "bus-signature.h" -#include "bus-slot.h" -#include "bus-type.h" -#include "bus-util.h" -#include "set.h" -#include "string-util.h" -#include "strv.h" - -static int node_vtable_get_userdata( - sd_bus *bus, - const char *path, - struct node_vtable *c, - void **userdata, - sd_bus_error *error) { - - sd_bus_slot *s; - void *u; - int r; - - assert(bus); - assert(path); - assert(c); - - s = container_of(c, sd_bus_slot, node_vtable); - u = s->userdata; - if (c->find) { - bus->current_slot = sd_bus_slot_ref(s); - bus->current_userdata = u; - r = c->find(bus, path, c->interface, u, &u, error); - bus->current_userdata = NULL; - bus->current_slot = sd_bus_slot_unref(s); - - if (r < 0) - return r; - if (sd_bus_error_is_set(error)) - return -sd_bus_error_get_errno(error); - if (r == 0) - return r; - } - - if (userdata) - *userdata = u; - - return 1; -} - -static void *vtable_method_convert_userdata(const sd_bus_vtable *p, void *u) { - assert(p); - - return (uint8_t*) u + p->x.method.offset; -} - -static void *vtable_property_convert_userdata(const sd_bus_vtable *p, void *u) { - assert(p); - - return (uint8_t*) u + p->x.property.offset; -} - -static int vtable_property_get_userdata( - sd_bus *bus, - const char *path, - struct vtable_member *p, - void **userdata, - sd_bus_error *error) { - - void *u; - int r; - - assert(bus); - assert(path); - assert(p); - assert(userdata); - - r = node_vtable_get_userdata(bus, path, p->parent, &u, error); - if (r <= 0) - return r; - if (bus->nodes_modified) - return 0; - - *userdata = vtable_property_convert_userdata(p->vtable, u); - return 1; -} - -static int add_enumerated_to_set( - sd_bus *bus, - const char *prefix, - struct node_enumerator *first, - Set *s, - sd_bus_error *error) { - - struct node_enumerator *c; - int r; - - assert(bus); - assert(prefix); - assert(s); - - LIST_FOREACH(enumerators, c, first) { - char **children = NULL, **k; - sd_bus_slot *slot; - - if (bus->nodes_modified) - return 0; - - slot = container_of(c, sd_bus_slot, node_enumerator); - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_userdata = slot->userdata; - r = c->callback(bus, prefix, slot->userdata, &children, error); - bus->current_userdata = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - - if (r < 0) - return r; - if (sd_bus_error_is_set(error)) - return -sd_bus_error_get_errno(error); - - STRV_FOREACH(k, children) { - if (r < 0) { - free(*k); - continue; - } - - if (!object_path_is_valid(*k)){ - free(*k); - r = -EINVAL; - continue; - } - - if (!object_path_startswith(*k, prefix)) { - free(*k); - continue; - } - - r = set_consume(s, *k); - if (r == -EEXIST) - r = 0; - } - - free(children); - if (r < 0) - return r; - } - - return 0; -} - -enum { - /* if set, add_subtree() works recursively */ - CHILDREN_RECURSIVE = (1U << 1), - /* if set, add_subtree() scans object-manager hierarchies recursively */ - CHILDREN_SUBHIERARCHIES = (1U << 0), -}; - -static int add_subtree_to_set( - sd_bus *bus, - const char *prefix, - struct node *n, - unsigned int flags, - Set *s, - sd_bus_error *error) { - - struct node *i; - int r; - - assert(bus); - assert(prefix); - assert(n); - assert(s); - - r = add_enumerated_to_set(bus, prefix, n->enumerators, s, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - LIST_FOREACH(siblings, i, n->child) { - char *t; - - if (!object_path_startswith(i->path, prefix)) - continue; - - t = strdup(i->path); - if (!t) - return -ENOMEM; - - r = set_consume(s, t); - if (r < 0 && r != -EEXIST) - return r; - - if ((flags & CHILDREN_RECURSIVE) && - ((flags & CHILDREN_SUBHIERARCHIES) || !i->object_managers)) { - r = add_subtree_to_set(bus, prefix, i, flags, s, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - } - - return 0; -} - -static int get_child_nodes( - sd_bus *bus, - const char *prefix, - struct node *n, - unsigned int flags, - Set **_s, - sd_bus_error *error) { - - Set *s = NULL; - int r; - - assert(bus); - assert(prefix); - assert(n); - assert(_s); - - s = set_new(&string_hash_ops); - if (!s) - return -ENOMEM; - - r = add_subtree_to_set(bus, prefix, n, flags, s, error); - if (r < 0) { - set_free_free(s); - return r; - } - - *_s = s; - return 0; -} - -static int node_callbacks_run( - sd_bus *bus, - sd_bus_message *m, - struct node_callback *first, - bool require_fallback, - bool *found_object) { - - struct node_callback *c; - int r; - - assert(bus); - assert(m); - assert(found_object); - - LIST_FOREACH(callbacks, c, first) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - sd_bus_slot *slot; - - if (bus->nodes_modified) - return 0; - - if (require_fallback && !c->is_fallback) - continue; - - *found_object = true; - - if (c->last_iteration == bus->iteration_counter) - continue; - - c->last_iteration = bus->iteration_counter; - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - slot = container_of(c, sd_bus_slot, node_callback); - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = c->callback; - bus->current_userdata = slot->userdata; - r = c->callback(m, slot->userdata, &error_buffer); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - - r = bus_maybe_reply_error(m, r, &error_buffer); - if (r != 0) - return r; - } - - return 0; -} - -#define CAPABILITY_SHIFT(x) (((x) >> __builtin_ctzll(_SD_BUS_VTABLE_CAPABILITY_MASK)) & 0xFFFF) - -static int check_access(sd_bus *bus, sd_bus_message *m, struct vtable_member *c, sd_bus_error *error) { - uint64_t cap; - int r; - - assert(bus); - assert(m); - assert(c); - - /* If the entire bus is trusted let's grant access */ - if (bus->trusted) - return 0; - - /* If the member is marked UNPRIVILEGED let's grant access */ - if (c->vtable->flags & SD_BUS_VTABLE_UNPRIVILEGED) - return 0; - - /* Check have the caller has the requested capability - * set. Note that the flags value contains the capability - * number plus one, which we need to subtract here. We do this - * so that we have 0 as special value for "default - * capability". */ - cap = CAPABILITY_SHIFT(c->vtable->flags); - if (cap == 0) - cap = CAPABILITY_SHIFT(c->parent->vtable[0].flags); - if (cap == 0) - cap = CAP_SYS_ADMIN; - else - cap --; - - r = sd_bus_query_sender_privilege(m, cap); - if (r < 0) - return r; - if (r > 0) - return 0; - - return sd_bus_error_setf(error, SD_BUS_ERROR_ACCESS_DENIED, "Access to %s.%s() not permitted.", c->interface, c->member); -} - -static int method_callbacks_run( - sd_bus *bus, - sd_bus_message *m, - struct vtable_member *c, - bool require_fallback, - bool *found_object) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *signature; - void *u; - int r; - - assert(bus); - assert(m); - assert(c); - assert(found_object); - - if (require_fallback && !c->parent->is_fallback) - return 0; - - r = check_access(bus, m, c, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - - r = node_vtable_get_userdata(bus, m->path, c->parent, &u, &error); - if (r <= 0) - return bus_maybe_reply_error(m, r, &error); - if (bus->nodes_modified) - return 0; - - u = vtable_method_convert_userdata(c->vtable, u); - - *found_object = true; - - if (c->last_iteration == bus->iteration_counter) - return 0; - - c->last_iteration = bus->iteration_counter; - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - signature = sd_bus_message_get_signature(m, true); - if (!signature) - return -EINVAL; - - if (!streq(strempty(c->vtable->x.method.signature), signature)) - return sd_bus_reply_method_errorf( - m, - SD_BUS_ERROR_INVALID_ARGS, - "Invalid arguments '%s' to call %s.%s(), expecting '%s'.", - signature, c->interface, c->member, strempty(c->vtable->x.method.signature)); - - /* Keep track what the signature of the reply to this message - * should be, so that this can be enforced when sealing the - * reply. */ - m->enforced_reply_signature = strempty(c->vtable->x.method.result); - - if (c->vtable->x.method.handler) { - sd_bus_slot *slot; - - slot = container_of(c->parent, sd_bus_slot, node_vtable); - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = c->vtable->x.method.handler; - bus->current_userdata = u; - r = c->vtable->x.method.handler(m, u, &error); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - - return bus_maybe_reply_error(m, r, &error); - } - - /* If the method callback is NULL, make this a successful NOP */ - r = sd_bus_reply_method_return(m, NULL); - if (r < 0) - return r; - - return 1; -} - -static int invoke_property_get( - sd_bus *bus, - sd_bus_slot *slot, - const sd_bus_vtable *v, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - const void *p; - int r; - - assert(bus); - assert(slot); - assert(v); - assert(path); - assert(interface); - assert(property); - assert(reply); - - if (v->x.property.get) { - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_userdata = userdata; - r = v->x.property.get(bus, path, interface, property, reply, userdata, error); - bus->current_userdata = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - - if (r < 0) - return r; - if (sd_bus_error_is_set(error)) - return -sd_bus_error_get_errno(error); - return r; - } - - /* Automatic handling if no callback is defined. */ - - if (streq(v->x.property.signature, "as")) - return sd_bus_message_append_strv(reply, *(char***) userdata); - - assert(signature_is_single(v->x.property.signature, false)); - assert(bus_type_is_basic(v->x.property.signature[0])); - - switch (v->x.property.signature[0]) { - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_SIGNATURE: - p = strempty(*(char**) userdata); - break; - - case SD_BUS_TYPE_OBJECT_PATH: - p = *(char**) userdata; - assert(p); - break; - - default: - p = userdata; - break; - } - - return sd_bus_message_append_basic(reply, v->x.property.signature[0], p); -} - -static int invoke_property_set( - sd_bus *bus, - sd_bus_slot *slot, - const sd_bus_vtable *v, - const char *path, - const char *interface, - const char *property, - sd_bus_message *value, - void *userdata, - sd_bus_error *error) { - - int r; - - assert(bus); - assert(slot); - assert(v); - assert(path); - assert(interface); - assert(property); - assert(value); - - if (v->x.property.set) { - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_userdata = userdata; - r = v->x.property.set(bus, path, interface, property, value, userdata, error); - bus->current_userdata = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - - if (r < 0) - return r; - if (sd_bus_error_is_set(error)) - return -sd_bus_error_get_errno(error); - return r; - } - - /* Automatic handling if no callback is defined. */ - - assert(signature_is_single(v->x.property.signature, false)); - assert(bus_type_is_basic(v->x.property.signature[0])); - - switch (v->x.property.signature[0]) { - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: { - const char *p; - char *n; - - r = sd_bus_message_read_basic(value, v->x.property.signature[0], &p); - if (r < 0) - return r; - - n = strdup(p); - if (!n) - return -ENOMEM; - - free(*(char**) userdata); - *(char**) userdata = n; - - break; - } - - default: - r = sd_bus_message_read_basic(value, v->x.property.signature[0], userdata); - if (r < 0) - return r; - - break; - } - - return 1; -} - -static int property_get_set_callbacks_run( - sd_bus *bus, - sd_bus_message *m, - struct vtable_member *c, - bool require_fallback, - bool is_get, - bool *found_object) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - sd_bus_slot *slot; - void *u = NULL; - int r; - - assert(bus); - assert(m); - assert(c); - assert(found_object); - - if (require_fallback && !c->parent->is_fallback) - return 0; - - r = vtable_property_get_userdata(bus, m->path, c, &u, &error); - if (r <= 0) - return bus_maybe_reply_error(m, r, &error); - if (bus->nodes_modified) - return 0; - - slot = container_of(c->parent, sd_bus_slot, node_vtable); - - *found_object = true; - - r = sd_bus_message_new_method_return(m, &reply); - if (r < 0) - return r; - - if (is_get) { - /* Note that we do not protect against reexecution - * here (using the last_iteration check, see below), - * should the node tree have changed and we got called - * again. We assume that property Get() calls are - * ultimately without side-effects or if they aren't - * then at least idempotent. */ - - r = sd_bus_message_open_container(reply, 'v', c->vtable->x.property.signature); - if (r < 0) - return r; - - /* Note that we do not do an access check here. Read - * access to properties is always unrestricted, since - * PropertiesChanged signals broadcast contents - * anyway. */ - - r = invoke_property_get(bus, slot, c->vtable, m->path, c->interface, c->member, reply, u, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - - if (bus->nodes_modified) - return 0; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - } else { - const char *signature = NULL; - char type = 0; - - if (c->vtable->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_PROPERTY_READ_ONLY, "Property '%s' is not writable.", c->member); - - /* Avoid that we call the set routine more than once - * if the processing of this message got restarted - * because the node tree changed. */ - if (c->last_iteration == bus->iteration_counter) - return 0; - - c->last_iteration = bus->iteration_counter; - - r = sd_bus_message_peek_type(m, &type, &signature); - if (r < 0) - return r; - - if (type != 'v' || !streq(strempty(signature), strempty(c->vtable->x.property.signature))) - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Incorrect parameters for property '%s', expected '%s', got '%s'.", c->member, strempty(c->vtable->x.property.signature), strempty(signature)); - - r = sd_bus_message_enter_container(m, 'v', c->vtable->x.property.signature); - if (r < 0) - return r; - - r = check_access(bus, m, c, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - - r = invoke_property_set(bus, slot, c->vtable, m->path, c->interface, c->member, m, u, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - - if (bus->nodes_modified) - return 0; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - } - - r = sd_bus_send(bus, reply, NULL); - if (r < 0) - return r; - - return 1; -} - -static int vtable_append_one_property( - sd_bus *bus, - sd_bus_message *reply, - const char *path, - struct node_vtable *c, - const sd_bus_vtable *v, - void *userdata, - sd_bus_error *error) { - - sd_bus_slot *slot; - int r; - - assert(bus); - assert(reply); - assert(path); - assert(c); - assert(v); - - r = sd_bus_message_open_container(reply, 'e', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "s", v->x.property.member); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'v', v->x.property.signature); - if (r < 0) - return r; - - slot = container_of(c, sd_bus_slot, node_vtable); - - r = invoke_property_get(bus, slot, v, path, c->interface, v->x.property.member, reply, vtable_property_convert_userdata(v, userdata), error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return 0; -} - -static int vtable_append_all_properties( - sd_bus *bus, - sd_bus_message *reply, - const char *path, - struct node_vtable *c, - void *userdata, - sd_bus_error *error) { - - const sd_bus_vtable *v; - int r; - - assert(bus); - assert(reply); - assert(path); - assert(c); - - if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN) - return 1; - - for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { - if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) - continue; - - if (v->flags & SD_BUS_VTABLE_HIDDEN) - continue; - - if (v->flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT) - continue; - - r = vtable_append_one_property(bus, reply, path, c, v, userdata, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - - return 1; -} - -static int property_get_all_callbacks_run( - sd_bus *bus, - sd_bus_message *m, - struct node_vtable *first, - bool require_fallback, - const char *iface, - bool *found_object) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - struct node_vtable *c; - bool found_interface; - int r; - - assert(bus); - assert(m); - assert(found_object); - - r = sd_bus_message_new_method_return(m, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "{sv}"); - if (r < 0) - return r; - - found_interface = !iface || - streq(iface, "org.freedesktop.DBus.Properties") || - streq(iface, "org.freedesktop.DBus.Peer") || - streq(iface, "org.freedesktop.DBus.Introspectable"); - - LIST_FOREACH(vtables, c, first) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - void *u; - - if (require_fallback && !c->is_fallback) - continue; - - r = node_vtable_get_userdata(bus, m->path, c, &u, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - *found_object = true; - - if (iface && !streq(c->interface, iface)) - continue; - found_interface = true; - - r = vtable_append_all_properties(bus, reply, m->path, c, u, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - if (bus->nodes_modified) - return 0; - } - - if (!found_interface) { - r = sd_bus_reply_method_errorf( - m, - SD_BUS_ERROR_UNKNOWN_INTERFACE, - "Unknown interface '%s'.", iface); - if (r < 0) - return r; - - return 1; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_send(bus, reply, NULL); - if (r < 0) - return r; - - return 1; -} - -static int bus_node_exists( - sd_bus *bus, - struct node *n, - const char *path, - bool require_fallback) { - - struct node_vtable *c; - struct node_callback *k; - int r; - - assert(bus); - assert(n); - assert(path); - - /* Tests if there's anything attached directly to this node - * for the specified path */ - - if (!require_fallback && (n->enumerators || n->object_managers)) - return true; - - LIST_FOREACH(callbacks, k, n->callbacks) { - if (require_fallback && !k->is_fallback) - continue; - - return 1; - } - - LIST_FOREACH(vtables, c, n->vtables) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - if (require_fallback && !c->is_fallback) - continue; - - r = node_vtable_get_userdata(bus, path, c, NULL, &error); - if (r != 0) - return r; - if (bus->nodes_modified) - return 0; - } - - return 0; -} - -static int process_introspect( - sd_bus *bus, - sd_bus_message *m, - struct node *n, - bool require_fallback, - bool *found_object) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_set_free_free_ Set *s = NULL; - const char *previous_interface = NULL; - struct introspect intro; - struct node_vtable *c; - bool empty; - int r; - - assert(bus); - assert(m); - assert(n); - assert(found_object); - - r = get_child_nodes(bus, m->path, n, 0, &s, &error); - if (r < 0) - return bus_maybe_reply_error(m, r, &error); - if (bus->nodes_modified) - return 0; - - r = introspect_begin(&intro, bus->trusted); - if (r < 0) - return r; - - r = introspect_write_default_interfaces(&intro, !require_fallback && n->object_managers); - if (r < 0) - return r; - - empty = set_isempty(s); - - LIST_FOREACH(vtables, c, n->vtables) { - if (require_fallback && !c->is_fallback) - continue; - - r = node_vtable_get_userdata(bus, m->path, c, NULL, &error); - if (r < 0) { - r = bus_maybe_reply_error(m, r, &error); - goto finish; - } - if (bus->nodes_modified) { - r = 0; - goto finish; - } - if (r == 0) - continue; - - empty = false; - - if (c->vtable[0].flags & SD_BUS_VTABLE_HIDDEN) - continue; - - if (!streq_ptr(previous_interface, c->interface)) { - - if (previous_interface) - fputs(" \n", intro.f); - - fprintf(intro.f, " \n", c->interface); - } - - r = introspect_write_interface(&intro, c->vtable); - if (r < 0) - goto finish; - - previous_interface = c->interface; - } - - if (previous_interface) - fputs(" \n", intro.f); - - if (empty) { - /* Nothing?, let's see if we exist at all, and if not - * refuse to do anything */ - r = bus_node_exists(bus, n, m->path, require_fallback); - if (r <= 0) - goto finish; - if (bus->nodes_modified) { - r = 0; - goto finish; - } - } - - *found_object = true; - - r = introspect_write_child_nodes(&intro, s, m->path); - if (r < 0) - goto finish; - - r = introspect_finish(&intro, bus, m, &reply); - if (r < 0) - goto finish; - - r = sd_bus_send(bus, reply, NULL); - if (r < 0) - goto finish; - - r = 1; - -finish: - introspect_free(&intro); - return r; -} - -static int object_manager_serialize_path( - sd_bus *bus, - sd_bus_message *reply, - const char *prefix, - const char *path, - bool require_fallback, - sd_bus_error *error) { - - const char *previous_interface = NULL; - bool found_something = false; - struct node_vtable *i; - struct node *n; - int r; - - assert(bus); - assert(reply); - assert(prefix); - assert(path); - assert(error); - - n = hashmap_get(bus->nodes, prefix); - if (!n) - return 0; - - LIST_FOREACH(vtables, i, n->vtables) { - void *u; - - if (require_fallback && !i->is_fallback) - continue; - - r = node_vtable_get_userdata(bus, path, i, &u, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - if (!found_something) { - - /* Open the object part */ - - r = sd_bus_message_open_container(reply, 'e', "oa{sa{sv}}"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "o", path); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "{sa{sv}}"); - if (r < 0) - return r; - - found_something = true; - } - - if (!streq_ptr(previous_interface, i->interface)) { - - /* Maybe close the previous interface part */ - - if (previous_interface) { - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - } - - /* Open the new interface part */ - - r = sd_bus_message_open_container(reply, 'e', "sa{sv}"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "s", i->interface); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "{sv}"); - if (r < 0) - return r; - } - - r = vtable_append_all_properties(bus, reply, path, i, u, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - previous_interface = i->interface; - } - - if (previous_interface) { - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - } - - if (found_something) { - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - } - - return 1; -} - -static int object_manager_serialize_path_and_fallbacks( - sd_bus *bus, - sd_bus_message *reply, - const char *path, - sd_bus_error *error) { - - char *prefix; - int r; - - assert(bus); - assert(reply); - assert(path); - assert(error); - - /* First, add all vtables registered for this path */ - r = object_manager_serialize_path(bus, reply, path, path, false, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - /* Second, add fallback vtables registered for any of the prefixes */ - prefix = alloca(strlen(path) + 1); - OBJECT_PATH_FOREACH_PREFIX(prefix, path) { - r = object_manager_serialize_path(bus, reply, prefix, path, true, error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - - return 0; -} - -static int process_get_managed_objects( - sd_bus *bus, - sd_bus_message *m, - struct node *n, - bool require_fallback, - bool *found_object) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_set_free_free_ Set *s = NULL; - Iterator i; - char *path; - int r; - - assert(bus); - assert(m); - assert(n); - assert(found_object); - - /* Spec says, GetManagedObjects() is only implemented on the root of a - * sub-tree. Therefore, we require a registered object-manager on - * exactly the queried path, otherwise, we refuse to respond. */ - - if (require_fallback || !n->object_managers) - return 0; - - r = get_child_nodes(bus, m->path, n, CHILDREN_RECURSIVE, &s, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - r = sd_bus_message_new_method_return(m, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "{oa{sa{sv}}}"); - if (r < 0) - return r; - - SET_FOREACH(path, s, i) { - r = object_manager_serialize_path_and_fallbacks(bus, reply, path, &error); - if (r < 0) - return r; - - if (bus->nodes_modified) - return 0; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - r = sd_bus_send(bus, reply, NULL); - if (r < 0) - return r; - - return 1; -} - -static int object_find_and_run( - sd_bus *bus, - sd_bus_message *m, - const char *p, - bool require_fallback, - bool *found_object) { - - struct node *n; - struct vtable_member vtable_key, *v; - int r; - - assert(bus); - assert(m); - assert(p); - assert(found_object); - - n = hashmap_get(bus->nodes, p); - if (!n) - return 0; - - /* First, try object callbacks */ - r = node_callbacks_run(bus, m, n->callbacks, require_fallback, found_object); - if (r != 0) - return r; - if (bus->nodes_modified) - return 0; - - if (!m->interface || !m->member) - return 0; - - /* Then, look for a known method */ - vtable_key.path = (char*) p; - vtable_key.interface = m->interface; - vtable_key.member = m->member; - - v = hashmap_get(bus->vtable_methods, &vtable_key); - if (v) { - r = method_callbacks_run(bus, m, v, require_fallback, found_object); - if (r != 0) - return r; - if (bus->nodes_modified) - return 0; - } - - /* Then, look for a known property */ - if (streq(m->interface, "org.freedesktop.DBus.Properties")) { - bool get = false; - - get = streq(m->member, "Get"); - - if (get || streq(m->member, "Set")) { - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - vtable_key.path = (char*) p; - - r = sd_bus_message_read(m, "ss", &vtable_key.interface, &vtable_key.member); - if (r < 0) - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface and member parameters"); - - v = hashmap_get(bus->vtable_properties, &vtable_key); - if (v) { - r = property_get_set_callbacks_run(bus, m, v, require_fallback, get, found_object); - if (r != 0) - return r; - } - - } else if (streq(m->member, "GetAll")) { - const char *iface; - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - r = sd_bus_message_read(m, "s", &iface); - if (r < 0) - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected interface parameter"); - - if (iface[0] == 0) - iface = NULL; - - r = property_get_all_callbacks_run(bus, m, n->vtables, require_fallback, iface, found_object); - if (r != 0) - return r; - } - - } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Introspectable", "Introspect")) { - - if (!isempty(sd_bus_message_get_signature(m, true))) - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters"); - - r = process_introspect(bus, m, n, require_fallback, found_object); - if (r != 0) - return r; - - } else if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.ObjectManager", "GetManagedObjects")) { - - if (!isempty(sd_bus_message_get_signature(m, true))) - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INVALID_ARGS, "Expected no parameters"); - - r = process_get_managed_objects(bus, m, n, require_fallback, found_object); - if (r != 0) - return r; - } - - if (bus->nodes_modified) - return 0; - - if (!*found_object) { - r = bus_node_exists(bus, n, m->path, require_fallback); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r > 0) - *found_object = true; - } - - return 0; -} - -int bus_process_object(sd_bus *bus, sd_bus_message *m) { - int r; - size_t pl; - bool found_object = false; - - assert(bus); - assert(m); - - if (bus->hello_flags & KDBUS_HELLO_MONITOR) - return 0; - - if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) - return 0; - - if (hashmap_isempty(bus->nodes)) - return 0; - - /* Never respond to broadcast messages */ - if (bus->bus_client && !m->destination) - return 0; - - assert(m->path); - assert(m->member); - - pl = strlen(m->path); - do { - char prefix[pl+1]; - - bus->nodes_modified = false; - - r = object_find_and_run(bus, m, m->path, false, &found_object); - if (r != 0) - return r; - - /* Look for fallback prefixes */ - OBJECT_PATH_FOREACH_PREFIX(prefix, m->path) { - - if (bus->nodes_modified) - break; - - r = object_find_and_run(bus, m, prefix, true, &found_object); - if (r != 0) - return r; - } - - } while (bus->nodes_modified); - - if (!found_object) - return 0; - - if (sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Get") || - sd_bus_message_is_method_call(m, "org.freedesktop.DBus.Properties", "Set")) - r = sd_bus_reply_method_errorf( - m, - SD_BUS_ERROR_UNKNOWN_PROPERTY, - "Unknown property or interface."); - else - r = sd_bus_reply_method_errorf( - m, - SD_BUS_ERROR_UNKNOWN_METHOD, - "Unknown method '%s' or interface '%s'.", m->member, m->interface); - - if (r < 0) - return r; - - return 1; -} - -static struct node *bus_node_allocate(sd_bus *bus, const char *path) { - struct node *n, *parent; - const char *e; - _cleanup_free_ char *s = NULL; - char *p; - int r; - - assert(bus); - assert(path); - assert(path[0] == '/'); - - n = hashmap_get(bus->nodes, path); - if (n) - return n; - - r = hashmap_ensure_allocated(&bus->nodes, &string_hash_ops); - if (r < 0) - return NULL; - - s = strdup(path); - if (!s) - return NULL; - - if (streq(path, "/")) - parent = NULL; - else { - e = strrchr(path, '/'); - assert(e); - - p = strndupa(path, MAX(1, e - path)); - - parent = bus_node_allocate(bus, p); - if (!parent) - return NULL; - } - - n = new0(struct node, 1); - if (!n) - return NULL; - - n->parent = parent; - n->path = s; - s = NULL; /* do not free */ - - r = hashmap_put(bus->nodes, n->path, n); - if (r < 0) { - free(n->path); - free(n); - return NULL; - } - - if (parent) - LIST_PREPEND(siblings, parent->child, n); - - return n; -} - -void bus_node_gc(sd_bus *b, struct node *n) { - assert(b); - - if (!n) - return; - - if (n->child || - n->callbacks || - n->vtables || - n->enumerators || - n->object_managers) - return; - - assert(hashmap_remove(b->nodes, n->path) == n); - - if (n->parent) - LIST_REMOVE(siblings, n->parent->child, n); - - free(n->path); - bus_node_gc(b, n->parent); - free(n); -} - -static int bus_find_parent_object_manager(sd_bus *bus, struct node **out, const char *path) { - struct node *n; - - assert(bus); - assert(path); - - n = hashmap_get(bus->nodes, path); - if (!n) { - char *prefix; - - prefix = alloca(strlen(path) + 1); - OBJECT_PATH_FOREACH_PREFIX(prefix, path) { - n = hashmap_get(bus->nodes, prefix); - if (n) - break; - } - } - - while (n && !n->object_managers) - n = n->parent; - - if (out) - *out = n; - return !!n; -} - -static int bus_add_object( - sd_bus *bus, - sd_bus_slot **slot, - bool fallback, - const char *path, - sd_bus_message_handler_t callback, - void *userdata) { - - sd_bus_slot *s; - struct node *n; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - n = bus_node_allocate(bus, path); - if (!n) - return -ENOMEM; - - s = bus_slot_allocate(bus, !slot, BUS_NODE_CALLBACK, sizeof(struct node_callback), userdata); - if (!s) { - r = -ENOMEM; - goto fail; - } - - s->node_callback.callback = callback; - s->node_callback.is_fallback = fallback; - - s->node_callback.node = n; - LIST_PREPEND(callbacks, n->callbacks, &s->node_callback); - bus->nodes_modified = true; - - if (slot) - *slot = s; - - return 0; - -fail: - sd_bus_slot_unref(s); - bus_node_gc(bus, n); - - return r; -} - -_public_ int sd_bus_add_object( - sd_bus *bus, - sd_bus_slot **slot, - const char *path, - sd_bus_message_handler_t callback, - void *userdata) { - - return bus_add_object(bus, slot, false, path, callback, userdata); -} - -_public_ int sd_bus_add_fallback( - sd_bus *bus, - sd_bus_slot **slot, - const char *prefix, - sd_bus_message_handler_t callback, - void *userdata) { - - return bus_add_object(bus, slot, true, prefix, callback, userdata); -} - -static void vtable_member_hash_func(const void *a, struct siphash *state) { - const struct vtable_member *m = a; - - assert(m); - - string_hash_func(m->path, state); - string_hash_func(m->interface, state); - string_hash_func(m->member, state); -} - -static int vtable_member_compare_func(const void *a, const void *b) { - const struct vtable_member *x = a, *y = b; - int r; - - assert(x); - assert(y); - - r = strcmp(x->path, y->path); - if (r != 0) - return r; - - r = strcmp(x->interface, y->interface); - if (r != 0) - return r; - - return strcmp(x->member, y->member); -} - -static const struct hash_ops vtable_member_hash_ops = { - .hash = vtable_member_hash_func, - .compare = vtable_member_compare_func -}; - -static int add_object_vtable_internal( - sd_bus *bus, - sd_bus_slot **slot, - const char *path, - const char *interface, - const sd_bus_vtable *vtable, - bool fallback, - sd_bus_object_find_t find, - void *userdata) { - - sd_bus_slot *s = NULL; - struct node_vtable *i, *existing = NULL; - const sd_bus_vtable *v; - struct node *n; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(interface_name_is_valid(interface), -EINVAL); - assert_return(vtable, -EINVAL); - assert_return(vtable[0].type == _SD_BUS_VTABLE_START, -EINVAL); - assert_return(vtable[0].x.start.element_size == sizeof(struct sd_bus_vtable), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(!streq(interface, "org.freedesktop.DBus.Properties") && - !streq(interface, "org.freedesktop.DBus.Introspectable") && - !streq(interface, "org.freedesktop.DBus.Peer") && - !streq(interface, "org.freedesktop.DBus.ObjectManager"), -EINVAL); - - r = hashmap_ensure_allocated(&bus->vtable_methods, &vtable_member_hash_ops); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&bus->vtable_properties, &vtable_member_hash_ops); - if (r < 0) - return r; - - n = bus_node_allocate(bus, path); - if (!n) - return -ENOMEM; - - LIST_FOREACH(vtables, i, n->vtables) { - if (i->is_fallback != fallback) { - r = -EPROTOTYPE; - goto fail; - } - - if (streq(i->interface, interface)) { - - if (i->vtable == vtable) { - r = -EEXIST; - goto fail; - } - - existing = i; - } - } - - s = bus_slot_allocate(bus, !slot, BUS_NODE_VTABLE, sizeof(struct node_vtable), userdata); - if (!s) { - r = -ENOMEM; - goto fail; - } - - s->node_vtable.is_fallback = fallback; - s->node_vtable.vtable = vtable; - s->node_vtable.find = find; - - s->node_vtable.interface = strdup(interface); - if (!s->node_vtable.interface) { - r = -ENOMEM; - goto fail; - } - - for (v = s->node_vtable.vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { - - switch (v->type) { - - case _SD_BUS_VTABLE_METHOD: { - struct vtable_member *m; - - if (!member_name_is_valid(v->x.method.member) || - !signature_is_valid(strempty(v->x.method.signature), false) || - !signature_is_valid(strempty(v->x.method.result), false) || - !(v->x.method.handler || (isempty(v->x.method.signature) && isempty(v->x.method.result))) || - v->flags & (SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) { - r = -EINVAL; - goto fail; - } - - m = new0(struct vtable_member, 1); - if (!m) { - r = -ENOMEM; - goto fail; - } - - m->parent = &s->node_vtable; - m->path = n->path; - m->interface = s->node_vtable.interface; - m->member = v->x.method.member; - m->vtable = v; - - r = hashmap_put(bus->vtable_methods, m, m); - if (r < 0) { - free(m); - goto fail; - } - - break; - } - - case _SD_BUS_VTABLE_WRITABLE_PROPERTY: - - if (!(v->x.property.set || bus_type_is_basic(v->x.property.signature[0]))) { - r = -EINVAL; - goto fail; - } - - if (v->flags & SD_BUS_VTABLE_PROPERTY_CONST) { - r = -EINVAL; - goto fail; - } - - /* Fall through */ - - case _SD_BUS_VTABLE_PROPERTY: { - struct vtable_member *m; - - if (!member_name_is_valid(v->x.property.member) || - !signature_is_single(v->x.property.signature, false) || - !(v->x.property.get || bus_type_is_basic(v->x.property.signature[0]) || streq(v->x.property.signature, "as")) || - (v->flags & SD_BUS_VTABLE_METHOD_NO_REPLY) || - (!!(v->flags & SD_BUS_VTABLE_PROPERTY_CONST) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) + !!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) > 1 || - ((v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) && (v->flags & SD_BUS_VTABLE_PROPERTY_EXPLICIT)) || - (v->flags & SD_BUS_VTABLE_UNPRIVILEGED && v->type == _SD_BUS_VTABLE_PROPERTY)) { - r = -EINVAL; - goto fail; - } - - m = new0(struct vtable_member, 1); - if (!m) { - r = -ENOMEM; - goto fail; - } - - m->parent = &s->node_vtable; - m->path = n->path; - m->interface = s->node_vtable.interface; - m->member = v->x.property.member; - m->vtable = v; - - r = hashmap_put(bus->vtable_properties, m, m); - if (r < 0) { - free(m); - goto fail; - } - - break; - } - - case _SD_BUS_VTABLE_SIGNAL: - - if (!member_name_is_valid(v->x.signal.member) || - !signature_is_valid(strempty(v->x.signal.signature), false) || - v->flags & SD_BUS_VTABLE_UNPRIVILEGED) { - r = -EINVAL; - goto fail; - } - - break; - - default: - r = -EINVAL; - goto fail; - } - } - - s->node_vtable.node = n; - LIST_INSERT_AFTER(vtables, n->vtables, existing, &s->node_vtable); - bus->nodes_modified = true; - - if (slot) - *slot = s; - - return 0; - -fail: - sd_bus_slot_unref(s); - bus_node_gc(bus, n); - - return r; -} - -_public_ int sd_bus_add_object_vtable( - sd_bus *bus, - sd_bus_slot **slot, - const char *path, - const char *interface, - const sd_bus_vtable *vtable, - void *userdata) { - - return add_object_vtable_internal(bus, slot, path, interface, vtable, false, NULL, userdata); -} - -_public_ int sd_bus_add_fallback_vtable( - sd_bus *bus, - sd_bus_slot **slot, - const char *prefix, - const char *interface, - const sd_bus_vtable *vtable, - sd_bus_object_find_t find, - void *userdata) { - - return add_object_vtable_internal(bus, slot, prefix, interface, vtable, true, find, userdata); -} - -_public_ int sd_bus_add_node_enumerator( - sd_bus *bus, - sd_bus_slot **slot, - const char *path, - sd_bus_node_enumerator_t callback, - void *userdata) { - - sd_bus_slot *s; - struct node *n; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - n = bus_node_allocate(bus, path); - if (!n) - return -ENOMEM; - - s = bus_slot_allocate(bus, !slot, BUS_NODE_ENUMERATOR, sizeof(struct node_enumerator), userdata); - if (!s) { - r = -ENOMEM; - goto fail; - } - - s->node_enumerator.callback = callback; - - s->node_enumerator.node = n; - LIST_PREPEND(enumerators, n->enumerators, &s->node_enumerator); - bus->nodes_modified = true; - - if (slot) - *slot = s; - - return 0; - -fail: - sd_bus_slot_unref(s); - bus_node_gc(bus, n); - - return r; -} - -static int emit_properties_changed_on_interface( - sd_bus *bus, - const char *prefix, - const char *path, - const char *interface, - bool require_fallback, - bool *found_interface, - char **names) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - bool has_invalidating = false, has_changing = false; - struct vtable_member key = {}; - struct node_vtable *c; - struct node *n; - char **property; - void *u = NULL; - int r; - - assert(bus); - assert(prefix); - assert(path); - assert(interface); - assert(found_interface); - - n = hashmap_get(bus->nodes, prefix); - if (!n) - return 0; - - r = sd_bus_message_new_signal(bus, &m, path, "org.freedesktop.DBus.Properties", "PropertiesChanged"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "s", interface); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "{sv}"); - if (r < 0) - return r; - - key.path = prefix; - key.interface = interface; - - LIST_FOREACH(vtables, c, n->vtables) { - if (require_fallback && !c->is_fallback) - continue; - - if (!streq(c->interface, interface)) - continue; - - r = node_vtable_get_userdata(bus, path, c, &u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - *found_interface = true; - - if (names) { - /* If the caller specified a list of - * properties we include exactly those in the - * PropertiesChanged message */ - - STRV_FOREACH(property, names) { - struct vtable_member *v; - - assert_return(member_name_is_valid(*property), -EINVAL); - - key.member = *property; - v = hashmap_get(bus->vtable_properties, &key); - if (!v) - return -ENOENT; - - /* If there are two vtables for the same - * interface, let's handle this property when - * we come to that vtable. */ - if (c != v->parent) - continue; - - assert_return(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE || - v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION, -EDOM); - - assert_return(!(v->vtable->flags & SD_BUS_VTABLE_HIDDEN), -EDOM); - - if (v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) { - has_invalidating = true; - continue; - } - - has_changing = true; - - r = vtable_append_one_property(bus, m, m->path, c, v->vtable, u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - } else { - const sd_bus_vtable *v; - - /* If the caller specified no properties list - * we include all properties that are marked - * as changing in the message. */ - - for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { - if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) - continue; - - if (v->flags & SD_BUS_VTABLE_HIDDEN) - continue; - - if (v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) { - has_invalidating = true; - continue; - } - - if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) - continue; - - has_changing = true; - - r = vtable_append_one_property(bus, m, m->path, c, v, u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - } - } - - if (!has_invalidating && !has_changing) - return 0; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return r; - - if (has_invalidating) { - LIST_FOREACH(vtables, c, n->vtables) { - if (require_fallback && !c->is_fallback) - continue; - - if (!streq(c->interface, interface)) - continue; - - r = node_vtable_get_userdata(bus, path, c, &u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - if (names) { - STRV_FOREACH(property, names) { - struct vtable_member *v; - - key.member = *property; - assert_se(v = hashmap_get(bus->vtable_properties, &key)); - assert(c == v->parent); - - if (!(v->vtable->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) - continue; - - r = sd_bus_message_append(m, "s", *property); - if (r < 0) - return r; - } - } else { - const sd_bus_vtable *v; - - for (v = c->vtable+1; v->type != _SD_BUS_VTABLE_END; v++) { - if (v->type != _SD_BUS_VTABLE_PROPERTY && v->type != _SD_BUS_VTABLE_WRITABLE_PROPERTY) - continue; - - if (v->flags & SD_BUS_VTABLE_HIDDEN) - continue; - - if (!(v->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION)) - continue; - - r = sd_bus_message_append(m, "s", v->x.property.member); - if (r < 0) - return r; - } - } - } - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_send(bus, m, NULL); - if (r < 0) - return r; - - return 1; -} - -_public_ int sd_bus_emit_properties_changed_strv( - sd_bus *bus, - const char *path, - const char *interface, - char **names) { - - BUS_DONT_DESTROY(bus); - bool found_interface = false; - char *prefix; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(interface_name_is_valid(interface), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - /* A non-NULL but empty names list means nothing needs to be - generated. A NULL list OTOH indicates that all properties - that are set to EMITS_CHANGE or EMITS_INVALIDATION shall be - included in the PropertiesChanged message. */ - if (names && names[0] == NULL) - return 0; - - do { - bus->nodes_modified = false; - - r = emit_properties_changed_on_interface(bus, path, path, interface, false, &found_interface, names); - if (r != 0) - return r; - if (bus->nodes_modified) - continue; - - prefix = alloca(strlen(path) + 1); - OBJECT_PATH_FOREACH_PREFIX(prefix, path) { - r = emit_properties_changed_on_interface(bus, prefix, path, interface, true, &found_interface, names); - if (r != 0) - return r; - if (bus->nodes_modified) - break; - } - - } while (bus->nodes_modified); - - return found_interface ? 0 : -ENOENT; -} - -_public_ int sd_bus_emit_properties_changed( - sd_bus *bus, - const char *path, - const char *interface, - const char *name, ...) { - - char **names; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(interface_name_is_valid(interface), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (!name) - return 0; - - names = strv_from_stdarg_alloca(name); - - return sd_bus_emit_properties_changed_strv(bus, path, interface, names); -} - -static int object_added_append_all_prefix( - sd_bus *bus, - sd_bus_message *m, - Set *s, - const char *prefix, - const char *path, - bool require_fallback) { - - const char *previous_interface = NULL; - struct node_vtable *c; - struct node *n; - int r; - - assert(bus); - assert(m); - assert(s); - assert(prefix); - assert(path); - - n = hashmap_get(bus->nodes, prefix); - if (!n) - return 0; - - LIST_FOREACH(vtables, c, n->vtables) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - void *u = NULL; - - if (require_fallback && !c->is_fallback) - continue; - - r = node_vtable_get_userdata(bus, path, c, &u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - if (!streq_ptr(c->interface, previous_interface)) { - /* If a child-node already handled this interface, we - * skip it on any of its parents. The child vtables - * always fully override any conflicting vtables of - * any parent node. */ - if (set_get(s, c->interface)) - continue; - - r = set_put(s, c->interface); - if (r < 0) - return r; - - if (previous_interface) { - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - } - - r = sd_bus_message_open_container(m, 'e', "sa{sv}"); - if (r < 0) - return r; - r = sd_bus_message_append(m, "s", c->interface); - if (r < 0) - return r; - r = sd_bus_message_open_container(m, 'a', "{sv}"); - if (r < 0) - return r; - - previous_interface = c->interface; - } - - r = vtable_append_all_properties(bus, m, path, c, u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - - if (previous_interface) { - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - } - - return 0; -} - -static int object_added_append_all(sd_bus *bus, sd_bus_message *m, const char *path) { - _cleanup_set_free_ Set *s = NULL; - char *prefix; - int r; - - assert(bus); - assert(m); - assert(path); - - /* - * This appends all interfaces registered on path @path. We first add - * the builtin interfaces, which are always available and handled by - * sd-bus. Then, we add all interfaces registered on the exact node, - * followed by all fallback interfaces registered on any parent prefix. - * - * If an interface is registered multiple times on the same node with - * different vtables, we merge all the properties across all vtables. - * However, if a child node has the same interface registered as one of - * its parent nodes has as fallback, we make the child overwrite the - * parent instead of extending it. Therefore, we keep a "Set" of all - * handled interfaces during parent traversal, so we skip interfaces on - * a parent that were overwritten by a child. - */ - - s = set_new(&string_hash_ops); - if (!s) - return -ENOMEM; - - r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Peer", 0); - if (r < 0) - return r; - r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Introspectable", 0); - if (r < 0) - return r; - r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.Properties", 0); - if (r < 0) - return r; - r = sd_bus_message_append(m, "{sa{sv}}", "org.freedesktop.DBus.ObjectManager", 0); - if (r < 0) - return r; - - r = object_added_append_all_prefix(bus, m, s, path, path, false); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - prefix = alloca(strlen(path) + 1); - OBJECT_PATH_FOREACH_PREFIX(prefix, path) { - r = object_added_append_all_prefix(bus, m, s, prefix, path, true); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - - return 0; -} - -_public_ int sd_bus_emit_object_added(sd_bus *bus, const char *path) { - BUS_DONT_DESTROY(bus); - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - struct node *object_manager; - int r; - - /* - * This emits an InterfacesAdded signal on the given path, by iterating - * all registered vtables and fallback vtables on the path. All - * properties are queried and included in the signal. - * This call is equivalent to sd_bus_emit_interfaces_added() with an - * explicit list of registered interfaces. However, unlike - * interfaces_added(), this call can figure out the list of supported - * interfaces itself. Furthermore, it properly adds the builtin - * org.freedesktop.DBus.* interfaces. - */ - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - r = bus_find_parent_object_manager(bus, &object_manager, path); - if (r < 0) - return r; - if (r == 0) - return -ESRCH; - - do { - bus->nodes_modified = false; - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"); - if (r < 0) - return r; - - r = sd_bus_message_append_basic(m, 'o', path); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "{sa{sv}}"); - if (r < 0) - return r; - - r = object_added_append_all(bus, m, path); - if (r < 0) - return r; - - if (bus->nodes_modified) - continue; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - } while (bus->nodes_modified); - - return sd_bus_send(bus, m, NULL); -} - -static int object_removed_append_all_prefix( - sd_bus *bus, - sd_bus_message *m, - Set *s, - const char *prefix, - const char *path, - bool require_fallback) { - - const char *previous_interface = NULL; - struct node_vtable *c; - struct node *n; - int r; - - assert(bus); - assert(m); - assert(s); - assert(prefix); - assert(path); - - n = hashmap_get(bus->nodes, prefix); - if (!n) - return 0; - - LIST_FOREACH(vtables, c, n->vtables) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - void *u = NULL; - - if (require_fallback && !c->is_fallback) - continue; - if (streq_ptr(c->interface, previous_interface)) - continue; - - /* If a child-node already handled this interface, we - * skip it on any of its parents. The child vtables - * always fully override any conflicting vtables of - * any parent node. */ - if (set_get(s, c->interface)) - continue; - - r = node_vtable_get_userdata(bus, path, c, &u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - r = set_put(s, c->interface); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "s", c->interface); - if (r < 0) - return r; - - previous_interface = c->interface; - } - - return 0; -} - -static int object_removed_append_all(sd_bus *bus, sd_bus_message *m, const char *path) { - _cleanup_set_free_ Set *s = NULL; - char *prefix; - int r; - - assert(bus); - assert(m); - assert(path); - - /* see sd_bus_emit_object_added() for details */ - - s = set_new(&string_hash_ops); - if (!s) - return -ENOMEM; - - r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Peer"); - if (r < 0) - return r; - r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Introspectable"); - if (r < 0) - return r; - r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.Properties"); - if (r < 0) - return r; - r = sd_bus_message_append(m, "s", "org.freedesktop.DBus.ObjectManager"); - if (r < 0) - return r; - - r = object_removed_append_all_prefix(bus, m, s, path, path, false); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - - prefix = alloca(strlen(path) + 1); - OBJECT_PATH_FOREACH_PREFIX(prefix, path) { - r = object_removed_append_all_prefix(bus, m, s, prefix, path, true); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - - return 0; -} - -_public_ int sd_bus_emit_object_removed(sd_bus *bus, const char *path) { - BUS_DONT_DESTROY(bus); - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - struct node *object_manager; - int r; - - /* - * This is like sd_bus_emit_object_added(), but emits an - * InterfacesRemoved signal on the given path. This only includes any - * registered interfaces but skips the properties. Note that this will - * call into the find() callbacks of any registered vtable. Therefore, - * you must call this function before destroying/unlinking your object. - * Otherwise, the list of interfaces will be incomplete. However, note - * that this will *NOT* call into any property callback. Therefore, the - * object might be in an "destructed" state, as long as we can find it. - */ - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - r = bus_find_parent_object_manager(bus, &object_manager, path); - if (r < 0) - return r; - if (r == 0) - return -ESRCH; - - do { - bus->nodes_modified = false; - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"); - if (r < 0) - return r; - - r = sd_bus_message_append_basic(m, 'o', path); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return r; - - r = object_removed_append_all(bus, m, path); - if (r < 0) - return r; - - if (bus->nodes_modified) - continue; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - } while (bus->nodes_modified); - - return sd_bus_send(bus, m, NULL); -} - -static int interfaces_added_append_one_prefix( - sd_bus *bus, - sd_bus_message *m, - const char *prefix, - const char *path, - const char *interface, - bool require_fallback) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool found_interface = false; - struct node_vtable *c; - struct node *n; - void *u = NULL; - int r; - - assert(bus); - assert(m); - assert(prefix); - assert(path); - assert(interface); - - n = hashmap_get(bus->nodes, prefix); - if (!n) - return 0; - - LIST_FOREACH(vtables, c, n->vtables) { - if (require_fallback && !c->is_fallback) - continue; - - if (!streq(c->interface, interface)) - continue; - - r = node_vtable_get_userdata(bus, path, c, &u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - if (r == 0) - continue; - - if (!found_interface) { - r = sd_bus_message_append_basic(m, 's', interface); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "{sv}"); - if (r < 0) - return r; - - found_interface = true; - } - - r = vtable_append_all_properties(bus, m, path, c, u, &error); - if (r < 0) - return r; - if (bus->nodes_modified) - return 0; - } - - if (found_interface) { - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - } - - return found_interface; -} - -static int interfaces_added_append_one( - sd_bus *bus, - sd_bus_message *m, - const char *path, - const char *interface) { - - char *prefix; - int r; - - assert(bus); - assert(m); - assert(path); - assert(interface); - - r = interfaces_added_append_one_prefix(bus, m, path, path, interface, false); - if (r != 0) - return r; - if (bus->nodes_modified) - return 0; - - prefix = alloca(strlen(path) + 1); - OBJECT_PATH_FOREACH_PREFIX(prefix, path) { - r = interfaces_added_append_one_prefix(bus, m, prefix, path, interface, true); - if (r != 0) - return r; - if (bus->nodes_modified) - return 0; - } - - return -ENOENT; -} - -_public_ int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces) { - BUS_DONT_DESTROY(bus); - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - struct node *object_manager; - char **i; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (strv_isempty(interfaces)) - return 0; - - r = bus_find_parent_object_manager(bus, &object_manager, path); - if (r < 0) - return r; - if (r == 0) - return -ESRCH; - - do { - bus->nodes_modified = false; - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded"); - if (r < 0) - return r; - - r = sd_bus_message_append_basic(m, 'o', path); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "{sa{sv}}"); - if (r < 0) - return r; - - STRV_FOREACH(i, interfaces) { - assert_return(interface_name_is_valid(*i), -EINVAL); - - r = sd_bus_message_open_container(m, 'e', "sa{sv}"); - if (r < 0) - return r; - - r = interfaces_added_append_one(bus, m, path, *i); - if (r < 0) - return r; - - if (bus->nodes_modified) - break; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - } - - if (bus->nodes_modified) - continue; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - } while (bus->nodes_modified); - - return sd_bus_send(bus, m, NULL); -} - -_public_ int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interface, ...) { - char **interfaces; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - interfaces = strv_from_stdarg_alloca(interface); - - return sd_bus_emit_interfaces_added_strv(bus, path, interfaces); -} - -_public_ int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - struct node *object_manager; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (strv_isempty(interfaces)) - return 0; - - r = bus_find_parent_object_manager(bus, &object_manager, path); - if (r < 0) - return r; - if (r == 0) - return -ESRCH; - - r = sd_bus_message_new_signal(bus, &m, object_manager->path, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved"); - if (r < 0) - return r; - - r = sd_bus_message_append_basic(m, 'o', path); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(m, interfaces); - if (r < 0) - return r; - - return sd_bus_send(bus, m, NULL); -} - -_public_ int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interface, ...) { - char **interfaces; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - interfaces = strv_from_stdarg_alloca(interface); - - return sd_bus_emit_interfaces_removed_strv(bus, path, interfaces); -} - -_public_ int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path) { - sd_bus_slot *s; - struct node *n; - int r; - - assert_return(bus, -EINVAL); - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - n = bus_node_allocate(bus, path); - if (!n) - return -ENOMEM; - - s = bus_slot_allocate(bus, !slot, BUS_NODE_OBJECT_MANAGER, sizeof(struct node_object_manager), NULL); - if (!s) { - r = -ENOMEM; - goto fail; - } - - s->node_object_manager.node = n; - LIST_PREPEND(object_managers, n->object_managers, &s->node_object_manager); - bus->nodes_modified = true; - - if (slot) - *slot = s; - - return 0; - -fail: - sd_bus_slot_unref(s); - bus_node_gc(bus, n); - - return r; -} diff --git a/src/libsystemd/sd-bus/bus-objects.h b/src/libsystemd/sd-bus/bus-objects.h deleted file mode 100644 index e0b8c534ed..0000000000 --- a/src/libsystemd/sd-bus/bus-objects.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "bus-internal.h" - -int bus_process_object(sd_bus *bus, sd_bus_message *m); -void bus_node_gc(sd_bus *b, struct node *n); diff --git a/src/libsystemd/sd-bus/bus-protocol.h b/src/libsystemd/sd-bus/bus-protocol.h deleted file mode 100644 index 9d180cb284..0000000000 --- a/src/libsystemd/sd-bus/bus-protocol.h +++ /dev/null @@ -1,180 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -#include "macro.h" - -/* Packet header */ - -struct _packed_ bus_header { - /* The first four fields are identical for dbus1, and dbus2 */ - uint8_t endian; - uint8_t type; - uint8_t flags; - uint8_t version; - - union _packed_ { - /* dbus1: Used for SOCK_STREAM connections */ - struct _packed_ { - uint32_t body_size; - - /* Note that what the bus spec calls "serial" we'll call - "cookie" instead, because we don't want to imply that the - cookie was in any way monotonically increasing. */ - uint32_t serial; - uint32_t fields_size; - } dbus1; - - /* dbus2: Used for kdbus connections */ - struct _packed_ { - uint32_t _reserved; - uint64_t cookie; - } dbus2; - - /* Note that both header versions have the same size! */ - }; -}; - -/* Endianness */ - -enum { - _BUS_INVALID_ENDIAN = 0, - BUS_LITTLE_ENDIAN = 'l', - BUS_BIG_ENDIAN = 'B', -#if __BYTE_ORDER == __BIG_ENDIAN - BUS_NATIVE_ENDIAN = BUS_BIG_ENDIAN, - BUS_REVERSE_ENDIAN = BUS_LITTLE_ENDIAN -#else - BUS_NATIVE_ENDIAN = BUS_LITTLE_ENDIAN, - BUS_REVERSE_ENDIAN = BUS_BIG_ENDIAN -#endif -}; - -/* Flags */ - -enum { - BUS_MESSAGE_NO_REPLY_EXPECTED = 1, - BUS_MESSAGE_NO_AUTO_START = 2, - BUS_MESSAGE_ALLOW_INTERACTIVE_AUTHORIZATION = 4, -}; - -/* Header fields */ - -enum { - _BUS_MESSAGE_HEADER_INVALID = 0, - BUS_MESSAGE_HEADER_PATH, - BUS_MESSAGE_HEADER_INTERFACE, - BUS_MESSAGE_HEADER_MEMBER, - BUS_MESSAGE_HEADER_ERROR_NAME, - BUS_MESSAGE_HEADER_REPLY_SERIAL, - BUS_MESSAGE_HEADER_DESTINATION, - BUS_MESSAGE_HEADER_SENDER, - BUS_MESSAGE_HEADER_SIGNATURE, - BUS_MESSAGE_HEADER_UNIX_FDS, - _BUS_MESSAGE_HEADER_MAX -}; - -/* RequestName parameters */ - -enum { - BUS_NAME_ALLOW_REPLACEMENT = 1, - BUS_NAME_REPLACE_EXISTING = 2, - BUS_NAME_DO_NOT_QUEUE = 4 -}; - -/* RequestName returns */ -enum { - BUS_NAME_PRIMARY_OWNER = 1, - BUS_NAME_IN_QUEUE = 2, - BUS_NAME_EXISTS = 3, - BUS_NAME_ALREADY_OWNER = 4 -}; - -/* ReleaseName returns */ -enum { - BUS_NAME_RELEASED = 1, - BUS_NAME_NON_EXISTENT = 2, - BUS_NAME_NOT_OWNER = 3, -}; - -/* StartServiceByName returns */ -enum { - BUS_START_REPLY_SUCCESS = 1, - BUS_START_REPLY_ALREADY_RUNNING = 2, -}; - -#define BUS_INTROSPECT_DOCTYPE \ - "\n" - -#define BUS_INTROSPECT_INTERFACE_PEER \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define BUS_INTROSPECT_INTERFACE_INTROSPECTABLE \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define BUS_INTROSPECT_INTERFACE_PROPERTIES \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" - -#define BUS_INTROSPECT_INTERFACE_OBJECT_MANAGER \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" \ - " \n" diff --git a/src/libsystemd/sd-bus/bus-signature.c b/src/libsystemd/sd-bus/bus-signature.c deleted file mode 100644 index 7bc243494a..0000000000 --- a/src/libsystemd/sd-bus/bus-signature.c +++ /dev/null @@ -1,158 +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 . -***/ - -#include - -#include "bus-signature.h" -#include "bus-type.h" - -static int signature_element_length_internal( - const char *s, - bool allow_dict_entry, - unsigned array_depth, - unsigned struct_depth, - size_t *l) { - - int r; - - if (!s) - return -EINVAL; - - assert(l); - - if (bus_type_is_basic(*s) || *s == SD_BUS_TYPE_VARIANT) { - *l = 1; - return 0; - } - - if (*s == SD_BUS_TYPE_ARRAY) { - size_t t; - - if (array_depth >= 32) - return -EINVAL; - - r = signature_element_length_internal(s + 1, true, array_depth+1, struct_depth, &t); - if (r < 0) - return r; - - *l = t + 1; - return 0; - } - - if (*s == SD_BUS_TYPE_STRUCT_BEGIN) { - const char *p = s + 1; - - if (struct_depth >= 32) - return -EINVAL; - - while (*p != SD_BUS_TYPE_STRUCT_END) { - size_t t; - - r = signature_element_length_internal(p, false, array_depth, struct_depth+1, &t); - if (r < 0) - return r; - - p += t; - } - - *l = p - s + 1; - return 0; - } - - if (*s == SD_BUS_TYPE_DICT_ENTRY_BEGIN && allow_dict_entry) { - const char *p = s + 1; - unsigned n = 0; - - if (struct_depth >= 32) - return -EINVAL; - - while (*p != SD_BUS_TYPE_DICT_ENTRY_END) { - size_t t; - - if (n == 0 && !bus_type_is_basic(*p)) - return -EINVAL; - - r = signature_element_length_internal(p, false, array_depth, struct_depth+1, &t); - if (r < 0) - return r; - - p += t; - n++; - } - - if (n != 2) - return -EINVAL; - - *l = p - s + 1; - return 0; - } - - return -EINVAL; -} - - -int signature_element_length(const char *s, size_t *l) { - return signature_element_length_internal(s, true, 0, 0, l); -} - -bool signature_is_single(const char *s, bool allow_dict_entry) { - int r; - size_t t; - - if (!s) - return false; - - r = signature_element_length_internal(s, allow_dict_entry, 0, 0, &t); - if (r < 0) - return false; - - return s[t] == 0; -} - -bool signature_is_pair(const char *s) { - - if (!s) - return false; - - if (!bus_type_is_basic(*s)) - return false; - - return signature_is_single(s + 1, false); -} - -bool signature_is_valid(const char *s, bool allow_dict_entry) { - const char *p; - int r; - - if (!s) - return false; - - p = s; - while (*p) { - size_t t; - - r = signature_element_length_internal(p, allow_dict_entry, 0, 0, &t); - if (r < 0) - return false; - - p += t; - } - - return p - s <= 255; -} diff --git a/src/libsystemd/sd-bus/bus-signature.h b/src/libsystemd/sd-bus/bus-signature.h deleted file mode 100644 index 1e0cd7f587..0000000000 --- a/src/libsystemd/sd-bus/bus-signature.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -bool signature_is_single(const char *s, bool allow_dict_entry); -bool signature_is_pair(const char *s); -bool signature_is_valid(const char *s, bool allow_dict_entry); - -int signature_element_length(const char *s, size_t *l); diff --git a/src/libsystemd/sd-bus/bus-slot.c b/src/libsystemd/sd-bus/bus-slot.c deleted file mode 100644 index a8c74011bf..0000000000 --- a/src/libsystemd/sd-bus/bus-slot.c +++ /dev/null @@ -1,286 +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 . -***/ - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-control.h" -#include "bus-objects.h" -#include "bus-slot.h" -#include "string-util.h" - -sd_bus_slot *bus_slot_allocate( - sd_bus *bus, - bool floating, - BusSlotType type, - size_t extra, - void *userdata) { - - sd_bus_slot *slot; - - assert(bus); - - slot = malloc0(offsetof(sd_bus_slot, reply_callback) + extra); - if (!slot) - return NULL; - - slot->n_ref = 1; - slot->type = type; - slot->bus = bus; - slot->floating = floating; - slot->userdata = userdata; - - if (!floating) - sd_bus_ref(bus); - - LIST_PREPEND(slots, bus->slots, slot); - - return slot; -} - -_public_ sd_bus_slot* sd_bus_slot_ref(sd_bus_slot *slot) { - - if (!slot) - return NULL; - - assert(slot->n_ref > 0); - - slot->n_ref++; - return slot; -} - -void bus_slot_disconnect(sd_bus_slot *slot) { - sd_bus *bus; - - assert(slot); - - if (!slot->bus) - return; - - switch (slot->type) { - - case BUS_REPLY_CALLBACK: - - if (slot->reply_callback.cookie != 0) - ordered_hashmap_remove(slot->bus->reply_callbacks, &slot->reply_callback.cookie); - - if (slot->reply_callback.timeout != 0) - prioq_remove(slot->bus->reply_callbacks_prioq, &slot->reply_callback, &slot->reply_callback.prioq_idx); - - break; - - case BUS_FILTER_CALLBACK: - slot->bus->filter_callbacks_modified = true; - LIST_REMOVE(callbacks, slot->bus->filter_callbacks, &slot->filter_callback); - break; - - case BUS_MATCH_CALLBACK: - - if (slot->match_added) - bus_remove_match_internal(slot->bus, slot->match_callback.match_string, slot->match_callback.cookie); - - slot->bus->match_callbacks_modified = true; - bus_match_remove(&slot->bus->match_callbacks, &slot->match_callback); - - free(slot->match_callback.match_string); - - break; - - case BUS_NODE_CALLBACK: - - if (slot->node_callback.node) { - LIST_REMOVE(callbacks, slot->node_callback.node->callbacks, &slot->node_callback); - slot->bus->nodes_modified = true; - - bus_node_gc(slot->bus, slot->node_callback.node); - } - - break; - - case BUS_NODE_ENUMERATOR: - - if (slot->node_enumerator.node) { - LIST_REMOVE(enumerators, slot->node_enumerator.node->enumerators, &slot->node_enumerator); - slot->bus->nodes_modified = true; - - bus_node_gc(slot->bus, slot->node_enumerator.node); - } - - break; - - case BUS_NODE_OBJECT_MANAGER: - - if (slot->node_object_manager.node) { - LIST_REMOVE(object_managers, slot->node_object_manager.node->object_managers, &slot->node_object_manager); - slot->bus->nodes_modified = true; - - bus_node_gc(slot->bus, slot->node_object_manager.node); - } - - break; - - case BUS_NODE_VTABLE: - - if (slot->node_vtable.node && slot->node_vtable.interface && slot->node_vtable.vtable) { - const sd_bus_vtable *v; - - for (v = slot->node_vtable.vtable; v->type != _SD_BUS_VTABLE_END; v++) { - struct vtable_member *x = NULL; - - switch (v->type) { - - case _SD_BUS_VTABLE_METHOD: { - struct vtable_member key; - - key.path = slot->node_vtable.node->path; - key.interface = slot->node_vtable.interface; - key.member = v->x.method.member; - - x = hashmap_remove(slot->bus->vtable_methods, &key); - break; - } - - case _SD_BUS_VTABLE_PROPERTY: - case _SD_BUS_VTABLE_WRITABLE_PROPERTY: { - struct vtable_member key; - - key.path = slot->node_vtable.node->path; - key.interface = slot->node_vtable.interface; - key.member = v->x.method.member; - - - x = hashmap_remove(slot->bus->vtable_properties, &key); - break; - }} - - free(x); - } - } - - free(slot->node_vtable.interface); - - if (slot->node_vtable.node) { - LIST_REMOVE(vtables, slot->node_vtable.node->vtables, &slot->node_vtable); - slot->bus->nodes_modified = true; - - bus_node_gc(slot->bus, slot->node_vtable.node); - } - - break; - - default: - assert_not_reached("Wut? Unknown slot type?"); - } - - bus = slot->bus; - - slot->type = _BUS_SLOT_INVALID; - slot->bus = NULL; - LIST_REMOVE(slots, bus->slots, slot); - - if (!slot->floating) - sd_bus_unref(bus); -} - -_public_ sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot) { - - if (!slot) - return NULL; - - assert(slot->n_ref > 0); - - if (slot->n_ref > 1) { - slot->n_ref --; - return NULL; - } - - bus_slot_disconnect(slot); - free(slot->description); - free(slot); - - return NULL; -} - -_public_ sd_bus* sd_bus_slot_get_bus(sd_bus_slot *slot) { - assert_return(slot, NULL); - - return slot->bus; -} - -_public_ void *sd_bus_slot_get_userdata(sd_bus_slot *slot) { - assert_return(slot, NULL); - - return slot->userdata; -} - -_public_ void *sd_bus_slot_set_userdata(sd_bus_slot *slot, void *userdata) { - void *ret; - - assert_return(slot, NULL); - - ret = slot->userdata; - slot->userdata = userdata; - - return ret; -} - -_public_ sd_bus_message *sd_bus_slot_get_current_message(sd_bus_slot *slot) { - assert_return(slot, NULL); - assert_return(slot->type >= 0, NULL); - - if (slot->bus->current_slot != slot) - return NULL; - - return slot->bus->current_message; -} - -_public_ sd_bus_message_handler_t sd_bus_slot_get_current_handler(sd_bus_slot *slot) { - assert_return(slot, NULL); - assert_return(slot->type >= 0, NULL); - - if (slot->bus->current_slot != slot) - return NULL; - - return slot->bus->current_handler; -} - -_public_ void* sd_bus_slot_get_current_userdata(sd_bus_slot *slot) { - assert_return(slot, NULL); - assert_return(slot->type >= 0, NULL); - - if (slot->bus->current_slot != slot) - return NULL; - - return slot->bus->current_userdata; -} - -_public_ int sd_bus_slot_set_description(sd_bus_slot *slot, const char *description) { - assert_return(slot, -EINVAL); - - return free_and_strdup(&slot->description, description); -} - -_public_ int sd_bus_slot_get_description(sd_bus_slot *slot, const char **description) { - assert_return(slot, -EINVAL); - assert_return(description, -EINVAL); - assert_return(slot->description, -ENXIO); - - *description = slot->description; - return 0; -} diff --git a/src/libsystemd/sd-bus/bus-slot.h b/src/libsystemd/sd-bus/bus-slot.h deleted file mode 100644 index 3b8b94dc6b..0000000000 --- a/src/libsystemd/sd-bus/bus-slot.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "sd-bus.h" - -#include "bus-internal.h" - -sd_bus_slot *bus_slot_allocate(sd_bus *bus, bool floating, BusSlotType type, size_t extra, void *userdata); - -void bus_slot_disconnect(sd_bus_slot *slot); diff --git a/src/libsystemd/sd-bus/bus-socket.c b/src/libsystemd/sd-bus/bus-socket.c deleted file mode 100644 index a2fb391239..0000000000 --- a/src/libsystemd/sd-bus/bus-socket.c +++ /dev/null @@ -1,1064 +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 . -***/ - -#include -#include -#include -#include - -#include "sd-bus.h" -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-socket.h" -#include "fd-util.h" -#include "formats-util.h" -#include "hexdecoct.h" -#include "macro.h" -#include "missing.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "user-util.h" -#include "utf8.h" -#include "util.h" - -#define SNDBUF_SIZE (8*1024*1024) - -static void iovec_advance(struct iovec iov[], unsigned *idx, size_t size) { - - while (size > 0) { - struct iovec *i = iov + *idx; - - if (i->iov_len > size) { - i->iov_base = (uint8_t*) i->iov_base + size; - i->iov_len -= size; - return; - } - - size -= i->iov_len; - - i->iov_base = NULL; - i->iov_len = 0; - - (*idx) ++; - } -} - -static int append_iovec(sd_bus_message *m, const void *p, size_t sz) { - assert(m); - assert(p); - assert(sz > 0); - - m->iovec[m->n_iovec].iov_base = (void*) p; - m->iovec[m->n_iovec].iov_len = sz; - m->n_iovec++; - - return 0; -} - -static int bus_message_setup_iovec(sd_bus_message *m) { - struct bus_body_part *part; - unsigned n, i; - int r; - - assert(m); - assert(m->sealed); - - if (m->n_iovec > 0) - return 0; - - assert(!m->iovec); - - n = 1 + m->n_body_parts; - if (n < ELEMENTSOF(m->iovec_fixed)) - m->iovec = m->iovec_fixed; - else { - m->iovec = new(struct iovec, n); - if (!m->iovec) { - r = -ENOMEM; - goto fail; - } - } - - r = append_iovec(m, m->header, BUS_MESSAGE_BODY_BEGIN(m)); - if (r < 0) - goto fail; - - MESSAGE_FOREACH_PART(part, i, m) { - r = bus_body_part_map(part); - if (r < 0) - goto fail; - - r = append_iovec(m, part->data, part->size); - if (r < 0) - goto fail; - } - - assert(n == m->n_iovec); - - return 0; - -fail: - m->poisoned = true; - return r; -} - -bool bus_socket_auth_needs_write(sd_bus *b) { - - unsigned i; - - if (b->auth_index >= ELEMENTSOF(b->auth_iovec)) - return false; - - for (i = b->auth_index; i < ELEMENTSOF(b->auth_iovec); i++) { - struct iovec *j = b->auth_iovec + i; - - if (j->iov_len > 0) - return true; - } - - return false; -} - -static int bus_socket_write_auth(sd_bus *b) { - ssize_t k; - - assert(b); - assert(b->state == BUS_AUTHENTICATING); - - if (!bus_socket_auth_needs_write(b)) - return 0; - - if (b->prefer_writev) - k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index); - else { - struct msghdr mh; - zero(mh); - - mh.msg_iov = b->auth_iovec + b->auth_index; - mh.msg_iovlen = ELEMENTSOF(b->auth_iovec) - b->auth_index; - - k = sendmsg(b->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); - if (k < 0 && errno == ENOTSOCK) { - b->prefer_writev = true; - k = writev(b->output_fd, b->auth_iovec + b->auth_index, ELEMENTSOF(b->auth_iovec) - b->auth_index); - } - } - - if (k < 0) - return errno == EAGAIN ? 0 : -errno; - - iovec_advance(b->auth_iovec, &b->auth_index, (size_t) k); - return 1; -} - -static int bus_socket_auth_verify_client(sd_bus *b) { - char *e, *f, *start; - sd_id128_t peer; - unsigned i; - int r; - - assert(b); - - /* We expect two response lines: "OK" and possibly - * "AGREE_UNIX_FD" */ - - e = memmem_safe(b->rbuffer, b->rbuffer_size, "\r\n", 2); - if (!e) - return 0; - - if (b->hello_flags & KDBUS_HELLO_ACCEPT_FD) { - f = memmem(e + 2, b->rbuffer_size - (e - (char*) b->rbuffer) - 2, "\r\n", 2); - if (!f) - return 0; - - start = f + 2; - } else { - f = NULL; - start = e + 2; - } - - /* Nice! We got all the lines we need. First check the OK - * line */ - - if (e - (char*) b->rbuffer != 3 + 32) - return -EPERM; - - if (memcmp(b->rbuffer, "OK ", 3)) - return -EPERM; - - b->auth = b->anonymous_auth ? BUS_AUTH_ANONYMOUS : BUS_AUTH_EXTERNAL; - - for (i = 0; i < 32; i += 2) { - int x, y; - - x = unhexchar(((char*) b->rbuffer)[3 + i]); - y = unhexchar(((char*) b->rbuffer)[3 + i + 1]); - - if (x < 0 || y < 0) - return -EINVAL; - - peer.bytes[i/2] = ((uint8_t) x << 4 | (uint8_t) y); - } - - if (!sd_id128_equal(b->server_id, SD_ID128_NULL) && - !sd_id128_equal(b->server_id, peer)) - return -EPERM; - - b->server_id = peer; - - /* And possibly check the second line, too */ - - if (f) - b->can_fds = - (f - e == strlen("\r\nAGREE_UNIX_FD")) && - memcmp(e + 2, "AGREE_UNIX_FD", strlen("AGREE_UNIX_FD")) == 0; - - b->rbuffer_size -= (start - (char*) b->rbuffer); - memmove(b->rbuffer, start, b->rbuffer_size); - - r = bus_start_running(b); - if (r < 0) - return r; - - return 1; -} - -static bool line_equals(const char *s, size_t m, const char *line) { - size_t l; - - l = strlen(line); - if (l != m) - return false; - - return memcmp(s, line, l) == 0; -} - -static bool line_begins(const char *s, size_t m, const char *word) { - size_t l; - - l = strlen(word); - if (m < l) - return false; - - if (memcmp(s, word, l) != 0) - return false; - - return m == l || (m > l && s[l] == ' '); -} - -static int verify_anonymous_token(sd_bus *b, const char *p, size_t l) { - _cleanup_free_ char *token = NULL; - size_t len; - int r; - - if (!b->anonymous_auth) - return 0; - - if (l <= 0) - return 1; - - assert(p[0] == ' '); - p++; l--; - - if (l % 2 != 0) - return 0; - - r = unhexmem(p, l, (void **) &token, &len); - if (r < 0) - return 0; - - if (memchr(token, 0, len)) - return 0; - - return !!utf8_is_valid(token); -} - -static int verify_external_token(sd_bus *b, const char *p, size_t l) { - _cleanup_free_ char *token = NULL; - size_t len; - uid_t u; - int r; - - /* We don't do any real authentication here. Instead, we if - * the owner of this bus wanted authentication he should have - * checked SO_PEERCRED before even creating the bus object. */ - - if (!b->anonymous_auth && !b->ucred_valid) - return 0; - - if (l <= 0) - return 1; - - assert(p[0] == ' '); - p++; l--; - - if (l % 2 != 0) - return 0; - - r = unhexmem(p, l, (void**) &token, &len); - if (r < 0) - return 0; - - if (memchr(token, 0, len)) - return 0; - - r = parse_uid(token, &u); - if (r < 0) - return 0; - - /* We ignore the passed value if anonymous authentication is - * on anyway. */ - if (!b->anonymous_auth && u != b->ucred.uid) - return 0; - - return 1; -} - -static int bus_socket_auth_write(sd_bus *b, const char *t) { - char *p; - size_t l; - - assert(b); - assert(t); - - /* We only make use of the first iovec */ - assert(b->auth_index == 0 || b->auth_index == 1); - - l = strlen(t); - p = malloc(b->auth_iovec[0].iov_len + l); - if (!p) - return -ENOMEM; - - memcpy(p, b->auth_iovec[0].iov_base, b->auth_iovec[0].iov_len); - memcpy(p + b->auth_iovec[0].iov_len, t, l); - - b->auth_iovec[0].iov_base = p; - b->auth_iovec[0].iov_len += l; - - free(b->auth_buffer); - b->auth_buffer = p; - b->auth_index = 0; - return 0; -} - -static int bus_socket_auth_write_ok(sd_bus *b) { - char t[3 + 32 + 2 + 1]; - - assert(b); - - xsprintf(t, "OK " SD_ID128_FORMAT_STR "\r\n", SD_ID128_FORMAT_VAL(b->server_id)); - - return bus_socket_auth_write(b, t); -} - -static int bus_socket_auth_verify_server(sd_bus *b) { - char *e; - const char *line; - size_t l; - bool processed = false; - int r; - - assert(b); - - if (b->rbuffer_size < 1) - return 0; - - /* First char must be a NUL byte */ - if (*(char*) b->rbuffer != 0) - return -EIO; - - if (b->rbuffer_size < 3) - return 0; - - /* Begin with the first line */ - if (b->auth_rbegin <= 0) - b->auth_rbegin = 1; - - for (;;) { - /* Check if line is complete */ - line = (char*) b->rbuffer + b->auth_rbegin; - e = memmem(line, b->rbuffer_size - b->auth_rbegin, "\r\n", 2); - if (!e) - return processed; - - l = e - line; - - if (line_begins(line, l, "AUTH ANONYMOUS")) { - - r = verify_anonymous_token(b, line + 14, l - 14); - if (r < 0) - return r; - if (r == 0) - r = bus_socket_auth_write(b, "REJECTED\r\n"); - else { - b->auth = BUS_AUTH_ANONYMOUS; - r = bus_socket_auth_write_ok(b); - } - - } else if (line_begins(line, l, "AUTH EXTERNAL")) { - - r = verify_external_token(b, line + 13, l - 13); - if (r < 0) - return r; - if (r == 0) - r = bus_socket_auth_write(b, "REJECTED\r\n"); - else { - b->auth = BUS_AUTH_EXTERNAL; - r = bus_socket_auth_write_ok(b); - } - - } else if (line_begins(line, l, "AUTH")) - r = bus_socket_auth_write(b, "REJECTED EXTERNAL ANONYMOUS\r\n"); - else if (line_equals(line, l, "CANCEL") || - line_begins(line, l, "ERROR")) { - - b->auth = _BUS_AUTH_INVALID; - r = bus_socket_auth_write(b, "REJECTED\r\n"); - - } else if (line_equals(line, l, "BEGIN")) { - - if (b->auth == _BUS_AUTH_INVALID) - r = bus_socket_auth_write(b, "ERROR\r\n"); - else { - /* We can't leave from the auth phase - * before we haven't written - * everything queued, so let's check - * that */ - - if (bus_socket_auth_needs_write(b)) - return 1; - - b->rbuffer_size -= (e + 2 - (char*) b->rbuffer); - memmove(b->rbuffer, e + 2, b->rbuffer_size); - return bus_start_running(b); - } - - } else if (line_begins(line, l, "DATA")) { - - if (b->auth == _BUS_AUTH_INVALID) - r = bus_socket_auth_write(b, "ERROR\r\n"); - else { - if (b->auth == BUS_AUTH_ANONYMOUS) - r = verify_anonymous_token(b, line + 4, l - 4); - else - r = verify_external_token(b, line + 4, l - 4); - - if (r < 0) - return r; - if (r == 0) { - b->auth = _BUS_AUTH_INVALID; - r = bus_socket_auth_write(b, "REJECTED\r\n"); - } else - r = bus_socket_auth_write_ok(b); - } - } else if (line_equals(line, l, "NEGOTIATE_UNIX_FD")) { - if (b->auth == _BUS_AUTH_INVALID || !(b->hello_flags & KDBUS_HELLO_ACCEPT_FD)) - r = bus_socket_auth_write(b, "ERROR\r\n"); - else { - b->can_fds = true; - r = bus_socket_auth_write(b, "AGREE_UNIX_FD\r\n"); - } - } else - r = bus_socket_auth_write(b, "ERROR\r\n"); - - if (r < 0) - return r; - - b->auth_rbegin = e + 2 - (char*) b->rbuffer; - - processed = true; - } -} - -static int bus_socket_auth_verify(sd_bus *b) { - assert(b); - - if (b->is_server) - return bus_socket_auth_verify_server(b); - else - return bus_socket_auth_verify_client(b); -} - -static int bus_socket_read_auth(sd_bus *b) { - struct msghdr mh; - struct iovec iov = {}; - size_t n; - ssize_t k; - int r; - void *p; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int) * BUS_FDS_MAX)]; - } control; - bool handle_cmsg = false; - - assert(b); - assert(b->state == BUS_AUTHENTICATING); - - r = bus_socket_auth_verify(b); - if (r != 0) - return r; - - n = MAX(256u, b->rbuffer_size * 2); - - if (n > BUS_AUTH_SIZE_MAX) - n = BUS_AUTH_SIZE_MAX; - - if (b->rbuffer_size >= n) - return -ENOBUFS; - - p = realloc(b->rbuffer, n); - if (!p) - return -ENOMEM; - - b->rbuffer = p; - - iov.iov_base = (uint8_t*) b->rbuffer + b->rbuffer_size; - iov.iov_len = n - b->rbuffer_size; - - if (b->prefer_readv) - k = readv(b->input_fd, &iov, 1); - else { - zero(mh); - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_control = &control; - mh.msg_controllen = sizeof(control); - - k = recvmsg(b->input_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); - if (k < 0 && errno == ENOTSOCK) { - b->prefer_readv = true; - k = readv(b->input_fd, &iov, 1); - } else - handle_cmsg = true; - } - if (k < 0) - return errno == EAGAIN ? 0 : -errno; - if (k == 0) - return -ECONNRESET; - - b->rbuffer_size += k; - - if (handle_cmsg) { - struct cmsghdr *cmsg; - - CMSG_FOREACH(cmsg, &mh) - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS) { - int j; - - /* Whut? We received fds during the auth - * protocol? Somebody is playing games with - * us. Close them all, and fail */ - j = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - close_many((int*) CMSG_DATA(cmsg), j); - return -EIO; - } else - log_debug("Got unexpected auxiliary data with level=%d and type=%d", - cmsg->cmsg_level, cmsg->cmsg_type); - } - - r = bus_socket_auth_verify(b); - if (r != 0) - return r; - - return 1; -} - -void bus_socket_setup(sd_bus *b) { - assert(b); - - /* Increase the buffers to 8 MB */ - fd_inc_rcvbuf(b->input_fd, SNDBUF_SIZE); - fd_inc_sndbuf(b->output_fd, SNDBUF_SIZE); - - b->is_kernel = false; - b->message_version = 1; - b->message_endian = 0; -} - -static void bus_get_peercred(sd_bus *b) { - int r; - - assert(b); - - /* Get the peer for socketpair() sockets */ - b->ucred_valid = getpeercred(b->input_fd, &b->ucred) >= 0; - - /* Get the SELinux context of the peer */ - if (mac_selinux_have()) { - r = getpeersec(b->input_fd, &b->label); - if (r < 0 && r != -EOPNOTSUPP) - log_debug_errno(r, "Failed to determine peer security context: %m"); - } -} - -static int bus_socket_start_auth_client(sd_bus *b) { - size_t l; - const char *auth_suffix, *auth_prefix; - - assert(b); - - if (b->anonymous_auth) { - auth_prefix = "\0AUTH ANONYMOUS "; - - /* For ANONYMOUS auth we send some arbitrary "trace" string */ - l = 9; - b->auth_buffer = hexmem("anonymous", l); - } else { - char text[DECIMAL_STR_MAX(uid_t) + 1]; - - auth_prefix = "\0AUTH EXTERNAL "; - - xsprintf(text, UID_FMT, geteuid()); - - l = strlen(text); - b->auth_buffer = hexmem(text, l); - } - - if (!b->auth_buffer) - return -ENOMEM; - - if (b->hello_flags & KDBUS_HELLO_ACCEPT_FD) - auth_suffix = "\r\nNEGOTIATE_UNIX_FD\r\nBEGIN\r\n"; - else - auth_suffix = "\r\nBEGIN\r\n"; - - b->auth_iovec[0].iov_base = (void*) auth_prefix; - b->auth_iovec[0].iov_len = 1 + strlen(auth_prefix + 1); - b->auth_iovec[1].iov_base = (void*) b->auth_buffer; - b->auth_iovec[1].iov_len = l * 2; - b->auth_iovec[2].iov_base = (void*) auth_suffix; - b->auth_iovec[2].iov_len = strlen(auth_suffix); - - return bus_socket_write_auth(b); -} - -int bus_socket_start_auth(sd_bus *b) { - assert(b); - - bus_get_peercred(b); - - b->state = BUS_AUTHENTICATING; - b->auth_timeout = now(CLOCK_MONOTONIC) + BUS_DEFAULT_TIMEOUT; - - if (sd_is_socket(b->input_fd, AF_UNIX, 0, 0) <= 0) - b->hello_flags &= ~KDBUS_HELLO_ACCEPT_FD; - - if (b->output_fd != b->input_fd) - if (sd_is_socket(b->output_fd, AF_UNIX, 0, 0) <= 0) - b->hello_flags &= ~KDBUS_HELLO_ACCEPT_FD; - - if (b->is_server) - return bus_socket_read_auth(b); - else - return bus_socket_start_auth_client(b); -} - -int bus_socket_connect(sd_bus *b) { - int r; - - assert(b); - assert(b->input_fd < 0); - assert(b->output_fd < 0); - assert(b->sockaddr.sa.sa_family != AF_UNSPEC); - - b->input_fd = socket(b->sockaddr.sa.sa_family, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (b->input_fd < 0) - return -errno; - - b->output_fd = b->input_fd; - - bus_socket_setup(b); - - r = connect(b->input_fd, &b->sockaddr.sa, b->sockaddr_size); - if (r < 0) { - if (errno == EINPROGRESS) - return 1; - - return -errno; - } - - return bus_socket_start_auth(b); -} - -int bus_socket_exec(sd_bus *b) { - int s[2], r; - pid_t pid; - - assert(b); - assert(b->input_fd < 0); - assert(b->output_fd < 0); - assert(b->exec_path); - - r = socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, s); - if (r < 0) - return -errno; - - pid = fork(); - if (pid < 0) { - safe_close_pair(s); - return -errno; - } - if (pid == 0) { - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - close_all_fds(s+1, 1); - - assert_se(dup3(s[1], STDIN_FILENO, 0) == STDIN_FILENO); - assert_se(dup3(s[1], STDOUT_FILENO, 0) == STDOUT_FILENO); - - if (s[1] != STDIN_FILENO && s[1] != STDOUT_FILENO) - safe_close(s[1]); - - fd_cloexec(STDIN_FILENO, false); - fd_cloexec(STDOUT_FILENO, false); - fd_nonblock(STDIN_FILENO, false); - fd_nonblock(STDOUT_FILENO, false); - - if (b->exec_argv) - execvp(b->exec_path, b->exec_argv); - else { - const char *argv[] = { b->exec_path, NULL }; - execvp(b->exec_path, (char**) argv); - } - - _exit(EXIT_FAILURE); - } - - safe_close(s[1]); - b->output_fd = b->input_fd = s[0]; - - bus_socket_setup(b); - - return bus_socket_start_auth(b); -} - -int bus_socket_take_fd(sd_bus *b) { - assert(b); - - bus_socket_setup(b); - - return bus_socket_start_auth(b); -} - -int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx) { - struct iovec *iov; - ssize_t k; - size_t n; - unsigned j; - int r; - - assert(bus); - assert(m); - assert(idx); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - if (*idx >= BUS_MESSAGE_SIZE(m)) - return 0; - - r = bus_message_setup_iovec(m); - if (r < 0) - return r; - - n = m->n_iovec * sizeof(struct iovec); - iov = alloca(n); - memcpy(iov, m->iovec, n); - - j = 0; - iovec_advance(iov, &j, *idx); - - if (bus->prefer_writev) - k = writev(bus->output_fd, iov, m->n_iovec); - else { - struct msghdr mh = { - .msg_iov = iov, - .msg_iovlen = m->n_iovec, - }; - - if (m->n_fds > 0) { - struct cmsghdr *control; - - mh.msg_control = control = alloca(CMSG_SPACE(sizeof(int) * m->n_fds)); - mh.msg_controllen = control->cmsg_len = CMSG_LEN(sizeof(int) * m->n_fds); - control->cmsg_level = SOL_SOCKET; - control->cmsg_type = SCM_RIGHTS; - memcpy(CMSG_DATA(control), m->fds, sizeof(int) * m->n_fds); - } - - k = sendmsg(bus->output_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL); - if (k < 0 && errno == ENOTSOCK) { - bus->prefer_writev = true; - k = writev(bus->output_fd, iov, m->n_iovec); - } - } - - if (k < 0) - return errno == EAGAIN ? 0 : -errno; - - *idx += (size_t) k; - return 1; -} - -static int bus_socket_read_message_need(sd_bus *bus, size_t *need) { - uint32_t a, b; - uint8_t e; - uint64_t sum; - - assert(bus); - assert(need); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - if (bus->rbuffer_size < sizeof(struct bus_header)) { - *need = sizeof(struct bus_header) + 8; - - /* Minimum message size: - * - * Header + - * - * Method Call: +2 string headers - * Signal: +3 string headers - * Method Error: +1 string headers - * +1 uint32 headers - * Method Reply: +1 uint32 headers - * - * A string header is at least 9 bytes - * A uint32 header is at least 8 bytes - * - * Hence the minimum message size of a valid message - * is header + 8 bytes */ - - return 0; - } - - a = ((const uint32_t*) bus->rbuffer)[1]; - b = ((const uint32_t*) bus->rbuffer)[3]; - - e = ((const uint8_t*) bus->rbuffer)[0]; - if (e == BUS_LITTLE_ENDIAN) { - a = le32toh(a); - b = le32toh(b); - } else if (e == BUS_BIG_ENDIAN) { - a = be32toh(a); - b = be32toh(b); - } else - return -EBADMSG; - - sum = (uint64_t) sizeof(struct bus_header) + (uint64_t) ALIGN_TO(b, 8) + (uint64_t) a; - if (sum >= BUS_MESSAGE_SIZE_MAX) - return -ENOBUFS; - - *need = (size_t) sum; - return 0; -} - -static int bus_socket_make_message(sd_bus *bus, size_t size) { - sd_bus_message *t; - void *b; - int r; - - assert(bus); - assert(bus->rbuffer_size >= size); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - r = bus_rqueue_make_room(bus); - if (r < 0) - return r; - - if (bus->rbuffer_size > size) { - b = memdup((const uint8_t*) bus->rbuffer + size, - bus->rbuffer_size - size); - if (!b) - return -ENOMEM; - } else - b = NULL; - - r = bus_message_from_malloc(bus, - bus->rbuffer, size, - bus->fds, bus->n_fds, - NULL, - &t); - if (r < 0) { - free(b); - return r; - } - - bus->rbuffer = b; - bus->rbuffer_size -= size; - - bus->fds = NULL; - bus->n_fds = 0; - - bus->rqueue[bus->rqueue_size++] = t; - - return 1; -} - -int bus_socket_read_message(sd_bus *bus) { - struct msghdr mh; - struct iovec iov = {}; - ssize_t k; - size_t need; - int r; - void *b; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int) * BUS_FDS_MAX)]; - } control; - bool handle_cmsg = false; - - assert(bus); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - r = bus_socket_read_message_need(bus, &need); - if (r < 0) - return r; - - if (bus->rbuffer_size >= need) - return bus_socket_make_message(bus, need); - - b = realloc(bus->rbuffer, need); - if (!b) - return -ENOMEM; - - bus->rbuffer = b; - - iov.iov_base = (uint8_t*) bus->rbuffer + bus->rbuffer_size; - iov.iov_len = need - bus->rbuffer_size; - - if (bus->prefer_readv) - k = readv(bus->input_fd, &iov, 1); - else { - zero(mh); - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_control = &control; - mh.msg_controllen = sizeof(control); - - k = recvmsg(bus->input_fd, &mh, MSG_DONTWAIT|MSG_NOSIGNAL|MSG_CMSG_CLOEXEC); - if (k < 0 && errno == ENOTSOCK) { - bus->prefer_readv = true; - k = readv(bus->input_fd, &iov, 1); - } else - handle_cmsg = true; - } - if (k < 0) - return errno == EAGAIN ? 0 : -errno; - if (k == 0) - return -ECONNRESET; - - bus->rbuffer_size += k; - - if (handle_cmsg) { - struct cmsghdr *cmsg; - - CMSG_FOREACH(cmsg, &mh) - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS) { - int n, *f; - - n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int); - - if (!bus->can_fds) { - /* Whut? We received fds but this - * isn't actually enabled? Close them, - * and fail */ - - close_many((int*) CMSG_DATA(cmsg), n); - return -EIO; - } - - f = realloc(bus->fds, sizeof(int) * (bus->n_fds + n)); - if (!f) { - close_many((int*) CMSG_DATA(cmsg), n); - return -ENOMEM; - } - - memcpy(f + bus->n_fds, CMSG_DATA(cmsg), n * sizeof(int)); - bus->fds = f; - bus->n_fds += n; - } else - log_debug("Got unexpected auxiliary data with level=%d and type=%d", - cmsg->cmsg_level, cmsg->cmsg_type); - } - - r = bus_socket_read_message_need(bus, &need); - if (r < 0) - return r; - - if (bus->rbuffer_size >= need) - return bus_socket_make_message(bus, need); - - return 1; -} - -int bus_socket_process_opening(sd_bus *b) { - int error = 0; - socklen_t slen = sizeof(error); - struct pollfd p = { - .fd = b->output_fd, - .events = POLLOUT, - }; - int r; - - assert(b->state == BUS_OPENING); - - r = poll(&p, 1, 0); - if (r < 0) - return -errno; - - if (!(p.revents & (POLLOUT|POLLERR|POLLHUP))) - return 0; - - r = getsockopt(b->output_fd, SOL_SOCKET, SO_ERROR, &error, &slen); - if (r < 0) - b->last_connect_error = errno; - else if (error != 0) - b->last_connect_error = error; - else if (p.revents & (POLLERR|POLLHUP)) - b->last_connect_error = ECONNREFUSED; - else - return bus_socket_start_auth(b); - - return bus_next_address(b); -} - -int bus_socket_process_authenticating(sd_bus *b) { - int r; - - assert(b); - assert(b->state == BUS_AUTHENTICATING); - - if (now(CLOCK_MONOTONIC) >= b->auth_timeout) - return -ETIMEDOUT; - - r = bus_socket_write_auth(b); - if (r != 0) - return r; - - return bus_socket_read_auth(b); -} diff --git a/src/libsystemd/sd-bus/bus-socket.h b/src/libsystemd/sd-bus/bus-socket.h deleted file mode 100644 index 684feead74..0000000000 --- a/src/libsystemd/sd-bus/bus-socket.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "sd-bus.h" - -void bus_socket_setup(sd_bus *b); - -int bus_socket_connect(sd_bus *b); -int bus_socket_exec(sd_bus *b); -int bus_socket_take_fd(sd_bus *b); -int bus_socket_start_auth(sd_bus *b); - -int bus_socket_write_message(sd_bus *bus, sd_bus_message *m, size_t *idx); -int bus_socket_read_message(sd_bus *bus); - -int bus_socket_process_opening(sd_bus *b); -int bus_socket_process_authenticating(sd_bus *b); - -bool bus_socket_auth_needs_write(sd_bus *b); diff --git a/src/libsystemd/sd-bus/bus-track.c b/src/libsystemd/sd-bus/bus-track.c deleted file mode 100644 index bdbf7d4a85..0000000000 --- a/src/libsystemd/sd-bus/bus-track.c +++ /dev/null @@ -1,337 +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 . -***/ - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-internal.h" -#include "bus-track.h" -#include "bus-util.h" - -struct sd_bus_track { - unsigned n_ref; - sd_bus *bus; - sd_bus_track_handler_t handler; - void *userdata; - Hashmap *names; - LIST_FIELDS(sd_bus_track, queue); - Iterator iterator; - bool in_queue; - bool modified; -}; - -#define MATCH_PREFIX \ - "type='signal'," \ - "sender='org.freedesktop.DBus'," \ - "path='/org/freedesktop/DBus'," \ - "interface='org.freedesktop.DBus'," \ - "member='NameOwnerChanged'," \ - "arg0='" - -#define MATCH_SUFFIX \ - "'" - -#define MATCH_FOR_NAME(name) \ - ({ \ - char *_x; \ - size_t _l = strlen(name); \ - _x = alloca(strlen(MATCH_PREFIX)+_l+strlen(MATCH_SUFFIX)+1); \ - strcpy(stpcpy(stpcpy(_x, MATCH_PREFIX), name), MATCH_SUFFIX); \ - _x; \ - }) - -static void bus_track_add_to_queue(sd_bus_track *track) { - assert(track); - - if (track->in_queue) - return; - - if (!track->handler) - return; - - LIST_PREPEND(queue, track->bus->track_queue, track); - track->in_queue = true; -} - -static void bus_track_remove_from_queue(sd_bus_track *track) { - assert(track); - - if (!track->in_queue) - return; - - LIST_REMOVE(queue, track->bus->track_queue, track); - track->in_queue = false; -} - -_public_ int sd_bus_track_new( - sd_bus *bus, - sd_bus_track **track, - sd_bus_track_handler_t handler, - void *userdata) { - - sd_bus_track *t; - - assert_return(bus, -EINVAL); - assert_return(track, -EINVAL); - - if (!bus->bus_client) - return -EINVAL; - - t = new0(sd_bus_track, 1); - if (!t) - return -ENOMEM; - - t->n_ref = 1; - t->handler = handler; - t->userdata = userdata; - t->bus = sd_bus_ref(bus); - - bus_track_add_to_queue(t); - - *track = t; - return 0; -} - -_public_ sd_bus_track* sd_bus_track_ref(sd_bus_track *track) { - - if (!track) - return NULL; - - assert(track->n_ref > 0); - - track->n_ref++; - - return track; -} - -_public_ sd_bus_track* sd_bus_track_unref(sd_bus_track *track) { - const char *n; - - if (!track) - return NULL; - - assert(track->n_ref > 0); - - if (track->n_ref > 1) { - track->n_ref --; - return NULL; - } - - while ((n = hashmap_first_key(track->names))) - sd_bus_track_remove_name(track, n); - - bus_track_remove_from_queue(track); - hashmap_free(track->names); - sd_bus_unref(track->bus); - free(track); - - return NULL; -} - -static int on_name_owner_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - sd_bus_track *track = userdata; - const char *name, *old, *new; - int r; - - assert(message); - assert(track); - - r = sd_bus_message_read(message, "sss", &name, &old, &new); - if (r < 0) - return 0; - - sd_bus_track_remove_name(track, name); - return 0; -} - -_public_ int sd_bus_track_add_name(sd_bus_track *track, const char *name) { - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_free_ char *n = NULL; - const char *match; - int r; - - assert_return(track, -EINVAL); - assert_return(service_name_is_valid(name), -EINVAL); - - r = hashmap_ensure_allocated(&track->names, &string_hash_ops); - if (r < 0) - return r; - - n = strdup(name); - if (!n) - return -ENOMEM; - - /* First, subscribe to this name */ - match = MATCH_FOR_NAME(n); - r = sd_bus_add_match(track->bus, &slot, match, on_name_owner_changed, track); - if (r < 0) - return r; - - r = hashmap_put(track->names, n, slot); - if (r == -EEXIST) - return 0; - if (r < 0) - return r; - - /* Second, check if it is currently existing, or maybe - * doesn't, or maybe disappeared already. */ - r = sd_bus_get_name_creds(track->bus, n, 0, NULL); - if (r < 0) { - hashmap_remove(track->names, n); - return r; - } - - n = NULL; - slot = NULL; - - bus_track_remove_from_queue(track); - track->modified = true; - - return 1; -} - -_public_ int sd_bus_track_remove_name(sd_bus_track *track, const char *name) { - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_free_ char *n = NULL; - - assert_return(name, -EINVAL); - - if (!track) - return 0; - - slot = hashmap_remove2(track->names, (char*) name, (void**) &n); - if (!slot) - return 0; - - if (hashmap_isempty(track->names)) - bus_track_add_to_queue(track); - - track->modified = true; - - return 1; -} - -_public_ unsigned sd_bus_track_count(sd_bus_track *track) { - if (!track) - return 0; - - return hashmap_size(track->names); -} - -_public_ const char* sd_bus_track_contains(sd_bus_track *track, const char *name) { - assert_return(track, NULL); - assert_return(name, NULL); - - return hashmap_get(track->names, (void*) name) ? name : NULL; -} - -_public_ const char* sd_bus_track_first(sd_bus_track *track) { - const char *n = NULL; - - if (!track) - return NULL; - - track->modified = false; - track->iterator = ITERATOR_FIRST; - - hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); - return n; -} - -_public_ const char* sd_bus_track_next(sd_bus_track *track) { - const char *n = NULL; - - if (!track) - return NULL; - - if (track->modified) - return NULL; - - hashmap_iterate(track->names, &track->iterator, NULL, (const void**) &n); - return n; -} - -_public_ int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m) { - const char *sender; - - assert_return(track, -EINVAL); - assert_return(m, -EINVAL); - - sender = sd_bus_message_get_sender(m); - if (!sender) - return -EINVAL; - - return sd_bus_track_add_name(track, sender); -} - -_public_ int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m) { - const char *sender; - - assert_return(track, -EINVAL); - assert_return(m, -EINVAL); - - sender = sd_bus_message_get_sender(m); - if (!sender) - return -EINVAL; - - return sd_bus_track_remove_name(track, sender); -} - -_public_ sd_bus* sd_bus_track_get_bus(sd_bus_track *track) { - assert_return(track, NULL); - - return track->bus; -} - -void bus_track_dispatch(sd_bus_track *track) { - int r; - - assert(track); - assert(track->in_queue); - assert(track->handler); - - bus_track_remove_from_queue(track); - - sd_bus_track_ref(track); - - r = track->handler(track, track->userdata); - if (r < 0) - log_debug_errno(r, "Failed to process track handler: %m"); - else if (r == 0) - bus_track_add_to_queue(track); - - sd_bus_track_unref(track); -} - -_public_ void *sd_bus_track_get_userdata(sd_bus_track *track) { - assert_return(track, NULL); - - return track->userdata; -} - -_public_ void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata) { - void *ret; - - assert_return(track, NULL); - - ret = track->userdata; - track->userdata = userdata; - - return ret; -} diff --git a/src/libsystemd/sd-bus/bus-track.h b/src/libsystemd/sd-bus/bus-track.h deleted file mode 100644 index 7d93a727d6..0000000000 --- a/src/libsystemd/sd-bus/bus-track.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -void bus_track_dispatch(sd_bus_track *track); diff --git a/src/libsystemd/sd-bus/bus-type.c b/src/libsystemd/sd-bus/bus-type.c deleted file mode 100644 index c692afc580..0000000000 --- a/src/libsystemd/sd-bus/bus-type.c +++ /dev/null @@ -1,176 +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 . -***/ - -#include "bus-type.h" - -bool bus_type_is_valid(char c) { - static const char valid[] = { - SD_BUS_TYPE_BYTE, - SD_BUS_TYPE_BOOLEAN, - SD_BUS_TYPE_INT16, - SD_BUS_TYPE_UINT16, - SD_BUS_TYPE_INT32, - SD_BUS_TYPE_UINT32, - SD_BUS_TYPE_INT64, - SD_BUS_TYPE_UINT64, - SD_BUS_TYPE_DOUBLE, - SD_BUS_TYPE_STRING, - SD_BUS_TYPE_OBJECT_PATH, - SD_BUS_TYPE_SIGNATURE, - SD_BUS_TYPE_ARRAY, - SD_BUS_TYPE_VARIANT, - SD_BUS_TYPE_STRUCT, - SD_BUS_TYPE_DICT_ENTRY, - SD_BUS_TYPE_UNIX_FD - }; - - return !!memchr(valid, c, sizeof(valid)); -} - -bool bus_type_is_valid_in_signature(char c) { - static const char valid[] = { - SD_BUS_TYPE_BYTE, - SD_BUS_TYPE_BOOLEAN, - SD_BUS_TYPE_INT16, - SD_BUS_TYPE_UINT16, - SD_BUS_TYPE_INT32, - SD_BUS_TYPE_UINT32, - SD_BUS_TYPE_INT64, - SD_BUS_TYPE_UINT64, - SD_BUS_TYPE_DOUBLE, - SD_BUS_TYPE_STRING, - SD_BUS_TYPE_OBJECT_PATH, - SD_BUS_TYPE_SIGNATURE, - SD_BUS_TYPE_ARRAY, - SD_BUS_TYPE_VARIANT, - SD_BUS_TYPE_STRUCT_BEGIN, - SD_BUS_TYPE_STRUCT_END, - SD_BUS_TYPE_DICT_ENTRY_BEGIN, - SD_BUS_TYPE_DICT_ENTRY_END, - SD_BUS_TYPE_UNIX_FD - }; - - return !!memchr(valid, c, sizeof(valid)); -} - -bool bus_type_is_basic(char c) { - static const char valid[] = { - SD_BUS_TYPE_BYTE, - SD_BUS_TYPE_BOOLEAN, - SD_BUS_TYPE_INT16, - SD_BUS_TYPE_UINT16, - SD_BUS_TYPE_INT32, - SD_BUS_TYPE_UINT32, - SD_BUS_TYPE_INT64, - SD_BUS_TYPE_UINT64, - SD_BUS_TYPE_DOUBLE, - SD_BUS_TYPE_STRING, - SD_BUS_TYPE_OBJECT_PATH, - SD_BUS_TYPE_SIGNATURE, - SD_BUS_TYPE_UNIX_FD - }; - - return !!memchr(valid, c, sizeof(valid)); -} - -bool bus_type_is_trivial(char c) { - static const char valid[] = { - SD_BUS_TYPE_BYTE, - SD_BUS_TYPE_BOOLEAN, - SD_BUS_TYPE_INT16, - SD_BUS_TYPE_UINT16, - SD_BUS_TYPE_INT32, - SD_BUS_TYPE_UINT32, - SD_BUS_TYPE_INT64, - SD_BUS_TYPE_UINT64, - SD_BUS_TYPE_DOUBLE - }; - - return !!memchr(valid, c, sizeof(valid)); -} - -bool bus_type_is_container(char c) { - static const char valid[] = { - SD_BUS_TYPE_ARRAY, - SD_BUS_TYPE_VARIANT, - SD_BUS_TYPE_STRUCT, - SD_BUS_TYPE_DICT_ENTRY - }; - - return !!memchr(valid, c, sizeof(valid)); -} - -int bus_type_get_alignment(char c) { - - switch (c) { - case SD_BUS_TYPE_BYTE: - case SD_BUS_TYPE_SIGNATURE: - case SD_BUS_TYPE_VARIANT: - return 1; - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - return 2; - - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_ARRAY: - case SD_BUS_TYPE_UNIX_FD: - return 4; - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - case SD_BUS_TYPE_STRUCT: - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: - return 8; - } - - return -EINVAL; -} - -int bus_type_get_size(char c) { - - switch (c) { - case SD_BUS_TYPE_BYTE: - return 1; - - case SD_BUS_TYPE_INT16: - case SD_BUS_TYPE_UINT16: - return 2; - - case SD_BUS_TYPE_BOOLEAN: - case SD_BUS_TYPE_INT32: - case SD_BUS_TYPE_UINT32: - case SD_BUS_TYPE_UNIX_FD: - return 4; - - case SD_BUS_TYPE_INT64: - case SD_BUS_TYPE_UINT64: - case SD_BUS_TYPE_DOUBLE: - return 8; - } - - return -EINVAL; -} diff --git a/src/libsystemd/sd-bus/bus-type.h b/src/libsystemd/sd-bus/bus-type.h deleted file mode 100644 index 5c87eb5f08..0000000000 --- a/src/libsystemd/sd-bus/bus-type.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -#include "sd-bus.h" - -#include "macro.h" - -bool bus_type_is_valid(char c) _const_; -bool bus_type_is_valid_in_signature(char c) _const_; -bool bus_type_is_basic(char c) _const_; -/* "trivial" is systemd's term for what the D-Bus Specification calls - * a "fixed type": that is, a basic type of fixed length */ -bool bus_type_is_trivial(char c) _const_; -bool bus_type_is_container(char c) _const_; - -int bus_type_get_alignment(char c) _const_; -int bus_type_get_size(char c) _const_; diff --git a/src/libsystemd/sd-bus/busctl-introspect.c b/src/libsystemd/sd-bus/busctl-introspect.c deleted file mode 100644 index b09509f8e1..0000000000 --- a/src/libsystemd/sd-bus/busctl-introspect.c +++ /dev/null @@ -1,790 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "busctl-introspect.h" -#include "string-util.h" -#include "util.h" -#include "xml.h" - -#define NODE_DEPTH_MAX 16 - -typedef struct Context { - const XMLIntrospectOps *ops; - void *userdata; - - char *interface_name; - uint64_t interface_flags; - - char *member_name; - char *member_signature; - char *member_result; - uint64_t member_flags; - bool member_writable; - - const char *current; - void *xml_state; -} Context; - -static void context_reset_member(Context *c) { - free(c->member_name); - free(c->member_signature); - free(c->member_result); - - c->member_name = c->member_signature = c->member_result = NULL; - c->member_flags = 0; - c->member_writable = false; -} - -static void context_reset_interface(Context *c) { - c->interface_name = mfree(c->interface_name); - c->interface_flags = 0; - - context_reset_member(c); -} - -static int parse_xml_annotation(Context *context, uint64_t *flags) { - - enum { - STATE_ANNOTATION, - STATE_NAME, - STATE_VALUE - } state = STATE_ANNOTATION; - - _cleanup_free_ char *field = NULL, *value = NULL; - - assert(context); - - for (;;) { - _cleanup_free_ char *name = NULL; - - int t; - - t = xml_tokenize(&context->current, &name, &context->xml_state, NULL); - if (t < 0) { - log_error("XML parse error."); - return t; - } - - if (t == XML_END) { - log_error("Premature end of XML data."); - return -EBADMSG; - } - - switch (state) { - - case STATE_ANNOTATION: - - if (t == XML_ATTRIBUTE_NAME) { - - if (streq_ptr(name, "name")) - state = STATE_NAME; - - else if (streq_ptr(name, "value")) - state = STATE_VALUE; - - else { - log_error("Unexpected attribute %s.", name); - return -EBADMSG; - } - - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "annotation"))) { - - if (flags) { - if (streq_ptr(field, "org.freedesktop.DBus.Deprecated")) { - - if (streq_ptr(value, "true")) - *flags |= SD_BUS_VTABLE_DEPRECATED; - - } else if (streq_ptr(field, "org.freedesktop.DBus.Method.NoReply")) { - - if (streq_ptr(value, "true")) - *flags |= SD_BUS_VTABLE_METHOD_NO_REPLY; - - } else if (streq_ptr(field, "org.freedesktop.DBus.Property.EmitsChangedSignal")) { - - if (streq_ptr(value, "const")) - *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION|SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE)) | SD_BUS_VTABLE_PROPERTY_CONST; - else if (streq_ptr(value, "invalidates")) - *flags = (*flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST)) | SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION; - else if (streq_ptr(value, "false")) - *flags = *flags & ~(SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE|SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION); - } - } - - return 0; - - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in . (1)"); - return -EINVAL; - } - - break; - - case STATE_NAME: - - if (t == XML_ATTRIBUTE_VALUE) { - free(field); - field = name; - name = NULL; - - state = STATE_ANNOTATION; - } else { - log_error("Unexpected token in . (2)"); - return -EINVAL; - } - - break; - - case STATE_VALUE: - - if (t == XML_ATTRIBUTE_VALUE) { - free(value); - value = name; - name = NULL; - - state = STATE_ANNOTATION; - } else { - log_error("Unexpected token in . (3)"); - return -EINVAL; - } - - break; - - default: - assert_not_reached("Bad state"); - } - } -} - -static int parse_xml_node(Context *context, const char *prefix, unsigned n_depth) { - - enum { - STATE_NODE, - STATE_NODE_NAME, - STATE_INTERFACE, - STATE_INTERFACE_NAME, - STATE_METHOD, - STATE_METHOD_NAME, - STATE_METHOD_ARG, - STATE_METHOD_ARG_NAME, - STATE_METHOD_ARG_TYPE, - STATE_METHOD_ARG_DIRECTION, - STATE_SIGNAL, - STATE_SIGNAL_NAME, - STATE_SIGNAL_ARG, - STATE_SIGNAL_ARG_NAME, - STATE_SIGNAL_ARG_TYPE, - STATE_PROPERTY, - STATE_PROPERTY_NAME, - STATE_PROPERTY_TYPE, - STATE_PROPERTY_ACCESS, - } state = STATE_NODE; - - _cleanup_free_ char *node_path = NULL, *argument_type = NULL, *argument_direction = NULL; - const char *np = prefix; - int r; - - assert(context); - assert(prefix); - - if (n_depth > NODE_DEPTH_MAX) { - log_error(" depth too high."); - return -EINVAL; - } - - for (;;) { - _cleanup_free_ char *name = NULL; - int t; - - t = xml_tokenize(&context->current, &name, &context->xml_state, NULL); - if (t < 0) { - log_error("XML parse error."); - return t; - } - - if (t == XML_END) { - log_error("Premature end of XML data."); - return -EBADMSG; - } - - switch (state) { - - case STATE_NODE: - if (t == XML_ATTRIBUTE_NAME) { - - if (streq_ptr(name, "name")) - state = STATE_NODE_NAME; - else { - log_error("Unexpected attribute %s.", name); - return -EBADMSG; - } - - } else if (t == XML_TAG_OPEN) { - - if (streq_ptr(name, "interface")) - state = STATE_INTERFACE; - else if (streq_ptr(name, "node")) { - - r = parse_xml_node(context, np, n_depth+1); - if (r < 0) - return r; - } else { - log_error("Unexpected tag %s.", name); - return -EBADMSG; - } - - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "node"))) { - - if (context->ops->on_path) { - r = context->ops->on_path(node_path ? node_path : np, context->userdata); - if (r < 0) - return r; - } - - return 0; - - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in . (1)"); - return -EINVAL; - } - - break; - - case STATE_NODE_NAME: - - if (t == XML_ATTRIBUTE_VALUE) { - - free(node_path); - - if (name[0] == '/') { - node_path = name; - name = NULL; - } else { - - if (endswith(prefix, "/")) - node_path = strappend(prefix, name); - else - node_path = strjoin(prefix, "/", name, NULL); - if (!node_path) - return log_oom(); - } - - np = node_path; - state = STATE_NODE; - } else { - log_error("Unexpected token in . (2)"); - return -EINVAL; - } - - break; - - case STATE_INTERFACE: - - if (t == XML_ATTRIBUTE_NAME) { - if (streq_ptr(name, "name")) - state = STATE_INTERFACE_NAME; - else { - log_error("Unexpected attribute %s.", name); - return -EBADMSG; - } - - } else if (t == XML_TAG_OPEN) { - if (streq_ptr(name, "method")) - state = STATE_METHOD; - else if (streq_ptr(name, "signal")) - state = STATE_SIGNAL; - else if (streq_ptr(name, "property")) { - context->member_flags |= SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE; - state = STATE_PROPERTY; - } else if (streq_ptr(name, "annotation")) { - r = parse_xml_annotation(context, &context->interface_flags); - if (r < 0) - return r; - } else { - log_error("Unexpected tag %s.", name); - return -EINVAL; - } - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "interface"))) { - - if (n_depth == 0) { - if (context->ops->on_interface) { - r = context->ops->on_interface(context->interface_name, context->interface_flags, context->userdata); - if (r < 0) - return r; - } - - context_reset_interface(context); - } - - state = STATE_NODE; - - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in . (1)"); - return -EINVAL; - } - - break; - - case STATE_INTERFACE_NAME: - - if (t == XML_ATTRIBUTE_VALUE) { - if (n_depth == 0) { - free(context->interface_name); - context->interface_name = name; - name = NULL; - } - - state = STATE_INTERFACE; - } else { - log_error("Unexpected token in . (2)"); - return -EINVAL; - } - - break; - - case STATE_METHOD: - - if (t == XML_ATTRIBUTE_NAME) { - if (streq_ptr(name, "name")) - state = STATE_METHOD_NAME; - else { - log_error("Unexpected attribute %s", name); - return -EBADMSG; - } - } else if (t == XML_TAG_OPEN) { - if (streq_ptr(name, "arg")) - state = STATE_METHOD_ARG; - else if (streq_ptr(name, "annotation")) { - r = parse_xml_annotation(context, &context->member_flags); - if (r < 0) - return r; - } else { - log_error("Unexpected tag %s.", name); - return -EINVAL; - } - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "method"))) { - - if (n_depth == 0) { - if (context->ops->on_method) { - r = context->ops->on_method(context->interface_name, context->member_name, context->member_signature, context->member_result, context->member_flags, context->userdata); - if (r < 0) - return r; - } - - context_reset_member(context); - } - - state = STATE_INTERFACE; - - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in (1)."); - return -EINVAL; - } - - break; - - case STATE_METHOD_NAME: - - if (t == XML_ATTRIBUTE_VALUE) { - - if (n_depth == 0) { - free(context->member_name); - context->member_name = name; - name = NULL; - } - - state = STATE_METHOD; - } else { - log_error("Unexpected token in (2)."); - return -EINVAL; - } - - break; - - case STATE_METHOD_ARG: - - if (t == XML_ATTRIBUTE_NAME) { - if (streq_ptr(name, "name")) - state = STATE_METHOD_ARG_NAME; - else if (streq_ptr(name, "type")) - state = STATE_METHOD_ARG_TYPE; - else if (streq_ptr(name, "direction")) - state = STATE_METHOD_ARG_DIRECTION; - else { - log_error("Unexpected method attribute %s.", name); - return -EBADMSG; - } - } else if (t == XML_TAG_OPEN) { - if (streq_ptr(name, "annotation")) { - r = parse_xml_annotation(context, NULL); - if (r < 0) - return r; - } else { - log_error("Unexpected method tag %s.", name); - return -EINVAL; - } - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) { - - if (n_depth == 0) { - - if (argument_type) { - if (!argument_direction || streq(argument_direction, "in")) { - if (!strextend(&context->member_signature, argument_type, NULL)) - return log_oom(); - } else if (streq(argument_direction, "out")) { - if (!strextend(&context->member_result, argument_type, NULL)) - return log_oom(); - } - } - - argument_type = mfree(argument_type); - argument_direction = mfree(argument_direction); - } - - state = STATE_METHOD; - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in method . (1)"); - return -EINVAL; - } - - break; - - case STATE_METHOD_ARG_NAME: - - if (t == XML_ATTRIBUTE_VALUE) - state = STATE_METHOD_ARG; - else { - log_error("Unexpected token in method . (2)"); - return -EINVAL; - } - - break; - - case STATE_METHOD_ARG_TYPE: - - if (t == XML_ATTRIBUTE_VALUE) { - free(argument_type); - argument_type = name; - name = NULL; - - state = STATE_METHOD_ARG; - } else { - log_error("Unexpected token in method . (3)"); - return -EINVAL; - } - - break; - - case STATE_METHOD_ARG_DIRECTION: - - if (t == XML_ATTRIBUTE_VALUE) { - free(argument_direction); - argument_direction = name; - name = NULL; - - state = STATE_METHOD_ARG; - } else { - log_error("Unexpected token in method . (4)"); - return -EINVAL; - } - - break; - - case STATE_SIGNAL: - - if (t == XML_ATTRIBUTE_NAME) { - if (streq_ptr(name, "name")) - state = STATE_SIGNAL_NAME; - else { - log_error("Unexpected attribute %s.", name); - return -EBADMSG; - } - } else if (t == XML_TAG_OPEN) { - if (streq_ptr(name, "arg")) - state = STATE_SIGNAL_ARG; - else if (streq_ptr(name, "annotation")) { - r = parse_xml_annotation(context, &context->member_flags); - if (r < 0) - return r; - } else { - log_error("Unexpected tag %s.", name); - return -EINVAL; - } - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "signal"))) { - - if (n_depth == 0) { - if (context->ops->on_signal) { - r = context->ops->on_signal(context->interface_name, context->member_name, context->member_signature, context->member_flags, context->userdata); - if (r < 0) - return r; - } - - context_reset_member(context); - } - - state = STATE_INTERFACE; - - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in . (1)"); - return -EINVAL; - } - - break; - - case STATE_SIGNAL_NAME: - - if (t == XML_ATTRIBUTE_VALUE) { - - if (n_depth == 0) { - free(context->member_name); - context->member_name = name; - name = NULL; - } - - state = STATE_SIGNAL; - } else { - log_error("Unexpected token in . (2)"); - return -EINVAL; - } - - break; - - - case STATE_SIGNAL_ARG: - - if (t == XML_ATTRIBUTE_NAME) { - if (streq_ptr(name, "name")) - state = STATE_SIGNAL_ARG_NAME; - else if (streq_ptr(name, "type")) - state = STATE_SIGNAL_ARG_TYPE; - else { - log_error("Unexpected signal attribute %s.", name); - return -EBADMSG; - } - } else if (t == XML_TAG_OPEN) { - if (streq_ptr(name, "annotation")) { - r = parse_xml_annotation(context, NULL); - if (r < 0) - return r; - } else { - log_error("Unexpected signal tag %s.", name); - return -EINVAL; - } - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "arg"))) { - - if (argument_type) { - if (!strextend(&context->member_signature, argument_type, NULL)) - return log_oom(); - - argument_type = mfree(argument_type); - } - - state = STATE_SIGNAL; - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in signal (1)."); - return -EINVAL; - } - - break; - - case STATE_SIGNAL_ARG_NAME: - - if (t == XML_ATTRIBUTE_VALUE) - state = STATE_SIGNAL_ARG; - else { - log_error("Unexpected token in signal (2)."); - return -EINVAL; - } - - break; - - case STATE_SIGNAL_ARG_TYPE: - - if (t == XML_ATTRIBUTE_VALUE) { - free(argument_type); - argument_type = name; - name = NULL; - - state = STATE_SIGNAL_ARG; - } else { - log_error("Unexpected token in signal (3)."); - return -EINVAL; - } - - break; - - case STATE_PROPERTY: - - if (t == XML_ATTRIBUTE_NAME) { - if (streq_ptr(name, "name")) - state = STATE_PROPERTY_NAME; - else if (streq_ptr(name, "type")) - state = STATE_PROPERTY_TYPE; - else if (streq_ptr(name, "access")) - state = STATE_PROPERTY_ACCESS; - else { - log_error("Unexpected attribute %s.", name); - return -EBADMSG; - } - } else if (t == XML_TAG_OPEN) { - - if (streq_ptr(name, "annotation")) { - r = parse_xml_annotation(context, &context->member_flags); - if (r < 0) - return r; - } else { - log_error("Unexpected tag %s.", name); - return -EINVAL; - } - - } else if (t == XML_TAG_CLOSE_EMPTY || - (t == XML_TAG_CLOSE && streq_ptr(name, "property"))) { - - if (n_depth == 0) { - if (context->ops->on_property) { - r = context->ops->on_property(context->interface_name, context->member_name, context->member_signature, context->member_writable, context->member_flags, context->userdata); - if (r < 0) - return r; - } - - context_reset_member(context); - } - - state = STATE_INTERFACE; - - } else if (t != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token in . (1)"); - return -EINVAL; - } - - break; - - case STATE_PROPERTY_NAME: - - if (t == XML_ATTRIBUTE_VALUE) { - - if (n_depth == 0) { - free(context->member_name); - context->member_name = name; - name = NULL; - } - state = STATE_PROPERTY; - } else { - log_error("Unexpected token in . (2)"); - return -EINVAL; - } - - break; - - case STATE_PROPERTY_TYPE: - - if (t == XML_ATTRIBUTE_VALUE) { - - if (n_depth == 0) { - free(context->member_signature); - context->member_signature = name; - name = NULL; - } - - state = STATE_PROPERTY; - } else { - log_error("Unexpected token in . (3)"); - return -EINVAL; - } - - break; - - case STATE_PROPERTY_ACCESS: - - if (t == XML_ATTRIBUTE_VALUE) { - - if (streq(name, "readwrite") || streq(name, "write")) - context->member_writable = true; - - state = STATE_PROPERTY; - } else { - log_error("Unexpected token in . (4)"); - return -EINVAL; - } - - break; - } - } -} - -int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata) { - Context context = { - .ops = ops, - .userdata = userdata, - .current = xml, - }; - - int r; - - assert(prefix); - assert(xml); - assert(ops); - - for (;;) { - _cleanup_free_ char *name = NULL; - - r = xml_tokenize(&context.current, &name, &context.xml_state, NULL); - if (r < 0) { - log_error("XML parse error"); - goto finish; - } - - if (r == XML_END) { - r = 0; - break; - } - - if (r == XML_TAG_OPEN) { - - if (streq(name, "node")) { - r = parse_xml_node(&context, prefix, 0); - if (r < 0) - goto finish; - } else { - log_error("Unexpected tag '%s' in introspection data.", name); - r = -EBADMSG; - goto finish; - } - } else if (r != XML_TEXT || !in_charset(name, WHITESPACE)) { - log_error("Unexpected token."); - r = -EBADMSG; - goto finish; - } - } - -finish: - context_reset_interface(&context); - - return r; -} diff --git a/src/libsystemd/sd-bus/busctl-introspect.h b/src/libsystemd/sd-bus/busctl-introspect.h deleted file mode 100644 index d922e352db..0000000000 --- a/src/libsystemd/sd-bus/busctl-introspect.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -typedef struct XMLIntrospectOps { - int (*on_path)(const char *path, void *userdata); - int (*on_interface)(const char *name, uint64_t flags, void *userdata); - int (*on_method)(const char *interface, const char *name, const char *signature, const char *result, uint64_t flags, void *userdata); - int (*on_signal)(const char *interface, const char *name, const char *signature, uint64_t flags, void *userdata); - int (*on_property)(const char *interface, const char *name, const char *signature, bool writable, uint64_t flags, void *userdata); -} XMLIntrospectOps; - -int parse_xml_introspect(const char *prefix, const char *xml, const XMLIntrospectOps *ops, void *userdata); diff --git a/src/libsystemd/sd-bus/busctl.c b/src/libsystemd/sd-bus/busctl.c deleted file mode 100644 index 35fabf038c..0000000000 --- a/src/libsystemd/sd-bus/busctl.c +++ /dev/null @@ -1,2073 +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 . -***/ - -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-internal.h" -#include "bus-signature.h" -#include "bus-type.h" -#include "bus-util.h" -#include "busctl-introspect.h" -#include "escape.h" -#include "fd-util.h" -#include "locale-util.h" -#include "log.h" -#include "pager.h" -#include "parse-util.h" -#include "path-util.h" -#include "set.h" -#include "strv.h" -#include "terminal-util.h" -#include "user-util.h" -#include "util.h" - -static bool arg_no_pager = false; -static bool arg_legend = true; -static char *arg_address = NULL; -static bool arg_unique = false; -static bool arg_acquired = false; -static bool arg_activatable = false; -static bool arg_show_machine = false; -static char **arg_matches = NULL; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static char *arg_host = NULL; -static bool arg_user = false; -static size_t arg_snaplen = 4096; -static bool arg_list = false; -static bool arg_quiet = false; -static bool arg_verbose = false; -static bool arg_expect_reply = true; -static bool arg_auto_start = true; -static bool arg_allow_interactive_authorization = true; -static bool arg_augment_creds = true; -static usec_t arg_timeout = 0; - -static void pager_open_if_enabled(void) { - - /* Cache result before we open the pager */ - if (arg_no_pager) - return; - - pager_open(false); -} - -#define NAME_IS_ACQUIRED INT_TO_PTR(1) -#define NAME_IS_ACTIVATABLE INT_TO_PTR(2) - -static int list_bus_names(sd_bus *bus, char **argv) { - _cleanup_strv_free_ char **acquired = NULL, **activatable = NULL; - _cleanup_free_ char **merged = NULL; - _cleanup_hashmap_free_ Hashmap *names = NULL; - char **i; - int r; - size_t max_i = 0; - unsigned n = 0; - void *v; - char *k; - Iterator iterator; - - assert(bus); - - if (!arg_unique && !arg_acquired && !arg_activatable) - arg_unique = arg_acquired = arg_activatable = true; - - r = sd_bus_list_names(bus, (arg_acquired || arg_unique) ? &acquired : NULL, arg_activatable ? &activatable : NULL); - if (r < 0) - return log_error_errno(r, "Failed to list names: %m"); - - pager_open_if_enabled(); - - names = hashmap_new(&string_hash_ops); - if (!names) - return log_oom(); - - STRV_FOREACH(i, acquired) { - max_i = MAX(max_i, strlen(*i)); - - r = hashmap_put(names, *i, NAME_IS_ACQUIRED); - if (r < 0) - return log_error_errno(r, "Failed to add to hashmap: %m"); - } - - STRV_FOREACH(i, activatable) { - max_i = MAX(max_i, strlen(*i)); - - r = hashmap_put(names, *i, NAME_IS_ACTIVATABLE); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Failed to add to hashmap: %m"); - } - - merged = new(char*, hashmap_size(names) + 1); - HASHMAP_FOREACH_KEY(v, k, names, iterator) - merged[n++] = k; - - merged[n] = NULL; - strv_sort(merged); - - if (arg_legend) { - printf("%-*s %*s %-*s %-*s %-*s %-*s %-*s %-*s", - (int) max_i, "NAME", 10, "PID", 15, "PROCESS", 16, "USER", 13, "CONNECTION", 25, "UNIT", 10, "SESSION", 19, "DESCRIPTION"); - - if (arg_show_machine) - puts(" MACHINE"); - else - putchar('\n'); - } - - STRV_FOREACH(i, merged) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - sd_id128_t mid; - - if (hashmap_get(names, *i) == NAME_IS_ACTIVATABLE) { - /* Activatable */ - - printf("%-*s", (int) max_i, *i); - printf(" - - - (activatable) - - "); - if (arg_show_machine) - puts(" -"); - else - putchar('\n'); - continue; - - } - - if (!arg_unique && (*i)[0] == ':') - continue; - - if (!arg_acquired && (*i)[0] != ':') - continue; - - printf("%-*s", (int) max_i, *i); - - r = sd_bus_get_name_creds( - bus, *i, - (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | - SD_BUS_CREDS_EUID|SD_BUS_CREDS_PID|SD_BUS_CREDS_COMM| - SD_BUS_CREDS_UNIQUE_NAME|SD_BUS_CREDS_UNIT|SD_BUS_CREDS_SESSION| - SD_BUS_CREDS_DESCRIPTION, &creds); - if (r >= 0) { - const char *unique, *session, *unit, *cn; - pid_t pid; - uid_t uid; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r >= 0) { - const char *comm = NULL; - - sd_bus_creds_get_comm(creds, &comm); - - printf(" %10lu %-15s", (unsigned long) pid, strna(comm)); - } else - fputs(" - - ", stdout); - - r = sd_bus_creds_get_euid(creds, &uid); - if (r >= 0) { - _cleanup_free_ char *u = NULL; - - u = uid_to_name(uid); - if (!u) - return log_oom(); - - if (strlen(u) > 16) - u[16] = 0; - - printf(" %-16s", u); - } else - fputs(" - ", stdout); - - r = sd_bus_creds_get_unique_name(creds, &unique); - if (r >= 0) - printf(" %-13s", unique); - else - fputs(" - ", stdout); - - r = sd_bus_creds_get_unit(creds, &unit); - if (r >= 0) { - _cleanup_free_ char *e; - - e = ellipsize(unit, 25, 100); - if (!e) - return log_oom(); - - printf(" %-25s", e); - } else - fputs(" - ", stdout); - - r = sd_bus_creds_get_session(creds, &session); - if (r >= 0) - printf(" %-10s", session); - else - fputs(" - ", stdout); - - r = sd_bus_creds_get_description(creds, &cn); - if (r >= 0) - printf(" %-19s", cn); - else - fputs(" - ", stdout); - - } else - printf(" - - - - - - - "); - - if (arg_show_machine) { - r = sd_bus_get_name_machine_id(bus, *i, &mid); - if (r >= 0) { - char m[SD_ID128_STRING_MAX]; - printf(" %s\n", sd_id128_to_string(mid, m)); - } else - puts(" -"); - } else - putchar('\n'); - } - - return 0; -} - -static void print_subtree(const char *prefix, const char *path, char **l) { - const char *vertical, *space; - char **n; - - /* We assume the list is sorted. Let's first skip over the - * entry we are looking at. */ - for (;;) { - if (!*l) - return; - - if (!streq(*l, path)) - break; - - l++; - } - - vertical = strjoina(prefix, draw_special_char(DRAW_TREE_VERTICAL)); - space = strjoina(prefix, draw_special_char(DRAW_TREE_SPACE)); - - for (;;) { - bool has_more = false; - - if (!*l || !path_startswith(*l, path)) - break; - - n = l + 1; - for (;;) { - if (!*n || !path_startswith(*n, path)) - break; - - if (!path_startswith(*n, *l)) { - has_more = true; - break; - } - - n++; - } - - printf("%s%s%s\n", prefix, draw_special_char(has_more ? DRAW_TREE_BRANCH : DRAW_TREE_RIGHT), *l); - - print_subtree(has_more ? vertical : space, *l, l); - l = n; - } -} - -static void print_tree(const char *prefix, char **l) { - - pager_open_if_enabled(); - - prefix = strempty(prefix); - - if (arg_list) { - char **i; - - STRV_FOREACH(i, l) - printf("%s%s\n", prefix, *i); - return; - } - - if (strv_isempty(l)) { - printf("No objects discovered.\n"); - return; - } - - if (streq(l[0], "/") && !l[1]) { - printf("Only root object discovered.\n"); - return; - } - - print_subtree(prefix, "/", l); -} - -static int on_path(const char *path, void *userdata) { - Set *paths = userdata; - int r; - - assert(paths); - - r = set_put_strdup(paths, path); - if (r < 0) - return log_oom(); - - return 0; -} - -static int find_nodes(sd_bus *bus, const char *service, const char *path, Set *paths, bool many) { - static const XMLIntrospectOps ops = { - .on_path = on_path, - }; - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *xml; - int r; - - r = sd_bus_call_method(bus, service, path, "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); - if (r < 0) { - if (many) - printf("Failed to introspect object %s of service %s: %s\n", path, service, bus_error_message(&error, r)); - else - log_error("Failed to introspect object %s of service %s: %s", path, service, bus_error_message(&error, r)); - return r; - } - - r = sd_bus_message_read(reply, "s", &xml); - if (r < 0) - return bus_log_parse_error(r); - - return parse_xml_introspect(path, xml, &ops, paths); -} - -static int tree_one(sd_bus *bus, const char *service, const char *prefix, bool many) { - _cleanup_set_free_free_ Set *paths = NULL, *done = NULL, *failed = NULL; - _cleanup_free_ char **l = NULL; - char *m; - int r; - - paths = set_new(&string_hash_ops); - if (!paths) - return log_oom(); - - done = set_new(&string_hash_ops); - if (!done) - return log_oom(); - - failed = set_new(&string_hash_ops); - if (!failed) - return log_oom(); - - m = strdup("/"); - if (!m) - return log_oom(); - - r = set_put(paths, m); - if (r < 0) { - free(m); - return log_oom(); - } - - for (;;) { - _cleanup_free_ char *p = NULL; - int q; - - p = set_steal_first(paths); - if (!p) - break; - - if (set_contains(done, p) || - set_contains(failed, p)) - continue; - - q = find_nodes(bus, service, p, paths, many); - if (q < 0) { - if (r >= 0) - r = q; - - q = set_put(failed, p); - } else - q = set_put(done, p); - - if (q < 0) - return log_oom(); - - assert(q != 0); - p = NULL; - } - - pager_open_if_enabled(); - - l = set_get_strv(done); - if (!l) - return log_oom(); - - strv_sort(l); - print_tree(prefix, l); - - fflush(stdout); - - return r; -} - -static int tree(sd_bus *bus, char **argv) { - char **i; - int r = 0; - - if (!arg_unique && !arg_acquired) - arg_acquired = true; - - if (strv_length(argv) <= 1) { - _cleanup_strv_free_ char **names = NULL; - bool not_first = false; - - r = sd_bus_list_names(bus, &names, NULL); - if (r < 0) - return log_error_errno(r, "Failed to get name list: %m"); - - pager_open_if_enabled(); - - STRV_FOREACH(i, names) { - int q; - - if (!arg_unique && (*i)[0] == ':') - continue; - - if (!arg_acquired && (*i)[0] == ':') - continue; - - if (not_first) - printf("\n"); - - printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal()); - - q = tree_one(bus, *i, NULL, true); - if (q < 0 && r >= 0) - r = q; - - not_first = true; - } - } else { - STRV_FOREACH(i, argv+1) { - int q; - - if (i > argv+1) - printf("\n"); - - if (argv[2]) { - pager_open_if_enabled(); - printf("Service %s%s%s:\n", ansi_highlight(), *i, ansi_normal()); - } - - q = tree_one(bus, *i, NULL, !!argv[2]); - if (q < 0 && r >= 0) - r = q; - } - } - - return r; -} - -static int format_cmdline(sd_bus_message *m, FILE *f, bool needs_space) { - int r; - - for (;;) { - const char *contents = NULL; - char type; - union { - uint8_t u8; - uint16_t u16; - int16_t s16; - uint32_t u32; - int32_t s32; - uint64_t u64; - int64_t s64; - double d64; - const char *string; - int i; - } basic; - - r = sd_bus_message_peek_type(m, &type, &contents); - if (r <= 0) - return r; - - if (bus_type_is_container(type) > 0) { - - r = sd_bus_message_enter_container(m, type, contents); - if (r < 0) - return r; - - if (type == SD_BUS_TYPE_ARRAY) { - unsigned n = 0; - - /* count array entries */ - for (;;) { - - r = sd_bus_message_skip(m, contents); - if (r < 0) - return r; - if (r == 0) - break; - - n++; - } - - r = sd_bus_message_rewind(m, false); - if (r < 0) - return r; - - if (needs_space) - fputc(' ', f); - - fprintf(f, "%u", n); - } else if (type == SD_BUS_TYPE_VARIANT) { - - if (needs_space) - fputc(' ', f); - - fprintf(f, "%s", contents); - } - - r = format_cmdline(m, f, needs_space || IN_SET(type, SD_BUS_TYPE_ARRAY, SD_BUS_TYPE_VARIANT)); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - continue; - } - - r = sd_bus_message_read_basic(m, type, &basic); - if (r < 0) - return r; - - if (needs_space) - fputc(' ', f); - - switch (type) { - case SD_BUS_TYPE_BYTE: - fprintf(f, "%u", basic.u8); - break; - - case SD_BUS_TYPE_BOOLEAN: - fputs(true_false(basic.i), f); - break; - - case SD_BUS_TYPE_INT16: - fprintf(f, "%i", basic.s16); - break; - - case SD_BUS_TYPE_UINT16: - fprintf(f, "%u", basic.u16); - break; - - case SD_BUS_TYPE_INT32: - fprintf(f, "%i", basic.s32); - break; - - case SD_BUS_TYPE_UINT32: - fprintf(f, "%u", basic.u32); - break; - - case SD_BUS_TYPE_INT64: - fprintf(f, "%" PRIi64, basic.s64); - break; - - case SD_BUS_TYPE_UINT64: - fprintf(f, "%" PRIu64, basic.u64); - break; - - case SD_BUS_TYPE_DOUBLE: - fprintf(f, "%g", basic.d64); - break; - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: { - _cleanup_free_ char *b = NULL; - - b = cescape(basic.string); - if (!b) - return -ENOMEM; - - fprintf(f, "\"%s\"", b); - break; - } - - case SD_BUS_TYPE_UNIX_FD: - fprintf(f, "%i", basic.i); - break; - - default: - assert_not_reached("Unknown basic type."); - } - - needs_space = true; - } -} - -typedef struct Member { - const char *type; - char *interface; - char *name; - char *signature; - char *result; - char *value; - bool writable; - uint64_t flags; -} Member; - -static void member_hash_func(const void *p, struct siphash *state) { - const Member *m = p; - uint64_t arity = 1; - - assert(m); - assert(m->type); - - string_hash_func(m->type, state); - - arity += !!m->name + !!m->interface; - - uint64_hash_func(&arity, state); - - if (m->name) - string_hash_func(m->name, state); - - if (m->interface) - string_hash_func(m->interface, state); -} - -static int member_compare_func(const void *a, const void *b) { - const Member *x = a, *y = b; - int d; - - assert(x); - assert(y); - assert(x->type); - assert(y->type); - - d = strcmp_ptr(x->interface, y->interface); - if (d != 0) - return d; - - d = strcmp(x->type, y->type); - if (d != 0) - return d; - - return strcmp_ptr(x->name, y->name); -} - -static int member_compare_funcp(const void *a, const void *b) { - const Member *const * x = (const Member *const *) a, * const *y = (const Member *const *) b; - - return member_compare_func(*x, *y); -} - -static void member_free(Member *m) { - if (!m) - return; - - free(m->interface); - free(m->name); - free(m->signature); - free(m->result); - free(m->value); - free(m); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Member*, member_free); - -static void member_set_free(Set *s) { - Member *m; - - while ((m = set_steal_first(s))) - member_free(m); - - set_free(s); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, member_set_free); - -static int on_interface(const char *interface, uint64_t flags, void *userdata) { - _cleanup_(member_freep) Member *m; - Set *members = userdata; - int r; - - assert(interface); - assert(members); - - m = new0(Member, 1); - if (!m) - return log_oom(); - - m->type = "interface"; - m->flags = flags; - - r = free_and_strdup(&m->interface, interface); - if (r < 0) - return log_oom(); - - r = set_put(members, m); - if (r <= 0) { - log_error("Duplicate interface"); - return -EINVAL; - } - - m = NULL; - return 0; -} - -static int on_method(const char *interface, const char *name, const char *signature, const char *result, uint64_t flags, void *userdata) { - _cleanup_(member_freep) Member *m; - Set *members = userdata; - int r; - - assert(interface); - assert(name); - - m = new0(Member, 1); - if (!m) - return log_oom(); - - m->type = "method"; - m->flags = flags; - - r = free_and_strdup(&m->interface, interface); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->name, name); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->signature, signature); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->result, result); - if (r < 0) - return log_oom(); - - r = set_put(members, m); - if (r <= 0) { - log_error("Duplicate method"); - return -EINVAL; - } - - m = NULL; - return 0; -} - -static int on_signal(const char *interface, const char *name, const char *signature, uint64_t flags, void *userdata) { - _cleanup_(member_freep) Member *m; - Set *members = userdata; - int r; - - assert(interface); - assert(name); - - m = new0(Member, 1); - if (!m) - return log_oom(); - - m->type = "signal"; - m->flags = flags; - - r = free_and_strdup(&m->interface, interface); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->name, name); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->signature, signature); - if (r < 0) - return log_oom(); - - r = set_put(members, m); - if (r <= 0) { - log_error("Duplicate signal"); - return -EINVAL; - } - - m = NULL; - return 0; -} - -static int on_property(const char *interface, const char *name, const char *signature, bool writable, uint64_t flags, void *userdata) { - _cleanup_(member_freep) Member *m; - Set *members = userdata; - int r; - - assert(interface); - assert(name); - - m = new0(Member, 1); - if (!m) - return log_oom(); - - m->type = "property"; - m->flags = flags; - m->writable = writable; - - r = free_and_strdup(&m->interface, interface); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->name, name); - if (r < 0) - return log_oom(); - - r = free_and_strdup(&m->signature, signature); - if (r < 0) - return log_oom(); - - r = set_put(members, m); - if (r <= 0) { - log_error("Duplicate property"); - return -EINVAL; - } - - m = NULL; - return 0; -} - -static const char *strdash(const char *x) { - return isempty(x) ? "-" : x; -} - -static int introspect(sd_bus *bus, char **argv) { - static const struct hash_ops member_hash_ops = { - .hash = member_hash_func, - .compare = member_compare_func, - }; - - static const XMLIntrospectOps ops = { - .on_interface = on_interface, - .on_method = on_method, - .on_signal = on_signal, - .on_property = on_property, - }; - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(member_set_freep) Set *members = NULL; - Iterator i; - Member *m; - const char *xml; - int r; - unsigned name_width, type_width, signature_width, result_width; - Member **sorted = NULL; - unsigned k = 0, j, n_args; - - n_args = strv_length(argv); - if (n_args < 3) { - log_error("Requires service and object path argument."); - return -EINVAL; - } - - if (n_args > 4) { - log_error("Too many arguments."); - return -EINVAL; - } - - members = set_new(&member_hash_ops); - if (!members) - return log_oom(); - - r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); - if (r < 0) { - log_error("Failed to introspect object %s of service %s: %s", argv[2], argv[1], bus_error_message(&error, r)); - return r; - } - - r = sd_bus_message_read(reply, "s", &xml); - if (r < 0) - return bus_log_parse_error(r); - - /* First, get list of all properties */ - r = parse_xml_introspect(argv[2], xml, &ops, members); - if (r < 0) - return r; - - /* Second, find the current values for them */ - SET_FOREACH(m, members, i) { - - if (!streq(m->type, "property")) - continue; - - if (m->value) - continue; - - if (argv[3] && !streq(argv[3], m->interface)) - continue; - - r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", m->interface); - if (r < 0) { - log_error("%s", bus_error_message(&error, r)); - return r; - } - - r = sd_bus_message_enter_container(reply, 'a', "{sv}"); - if (r < 0) - return bus_log_parse_error(r); - - for (;;) { - Member *z; - _cleanup_free_ char *buf = NULL; - _cleanup_fclose_ FILE *mf = NULL; - size_t sz = 0; - const char *name; - - r = sd_bus_message_enter_container(reply, 'e', "sv"); - if (r < 0) - return bus_log_parse_error(r); - - if (r == 0) - break; - - r = sd_bus_message_read(reply, "s", &name); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_enter_container(reply, 'v', NULL); - if (r < 0) - return bus_log_parse_error(r); - - mf = open_memstream(&buf, &sz); - if (!mf) - return log_oom(); - - r = format_cmdline(reply, mf, false); - if (r < 0) - return bus_log_parse_error(r); - - fclose(mf); - mf = NULL; - - z = set_get(members, &((Member) { - .type = "property", - .interface = m->interface, - .name = (char*) name })); - if (z) { - free(z->value); - z->value = buf; - buf = NULL; - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - } - - pager_open_if_enabled(); - - name_width = strlen("NAME"); - type_width = strlen("TYPE"); - signature_width = strlen("SIGNATURE"); - result_width = strlen("RESULT/VALUE"); - - sorted = newa(Member*, set_size(members)); - - SET_FOREACH(m, members, i) { - - if (argv[3] && !streq(argv[3], m->interface)) - continue; - - if (m->interface) - name_width = MAX(name_width, strlen(m->interface)); - if (m->name) - name_width = MAX(name_width, strlen(m->name) + 1); - if (m->type) - type_width = MAX(type_width, strlen(m->type)); - if (m->signature) - signature_width = MAX(signature_width, strlen(m->signature)); - if (m->result) - result_width = MAX(result_width, strlen(m->result)); - if (m->value) - result_width = MAX(result_width, strlen(m->value)); - - sorted[k++] = m; - } - - if (result_width > 40) - result_width = 40; - - qsort(sorted, k, sizeof(Member*), member_compare_funcp); - - if (arg_legend) { - printf("%-*s %-*s %-*s %-*s %s\n", - (int) name_width, "NAME", - (int) type_width, "TYPE", - (int) signature_width, "SIGNATURE", - (int) result_width, "RESULT/VALUE", - "FLAGS"); - } - - for (j = 0; j < k; j++) { - _cleanup_free_ char *ellipsized = NULL; - const char *rv; - bool is_interface; - - m = sorted[j]; - - if (argv[3] && !streq(argv[3], m->interface)) - continue; - - is_interface = streq(m->type, "interface"); - - if (argv[3] && is_interface) - continue; - - if (m->value) { - ellipsized = ellipsize(m->value, result_width, 100); - if (!ellipsized) - return log_oom(); - - rv = ellipsized; - } else - rv = strdash(m->result); - - printf("%s%s%-*s%s %-*s %-*s %-*s%s%s%s%s%s%s\n", - is_interface ? ansi_highlight() : "", - is_interface ? "" : ".", - - !is_interface + (int) name_width, strdash(streq_ptr(m->type, "interface") ? m->interface : m->name), - is_interface ? ansi_normal() : "", - (int) type_width, strdash(m->type), - (int) signature_width, strdash(m->signature), - (int) result_width, rv, - (m->flags & SD_BUS_VTABLE_DEPRECATED) ? " deprecated" : (m->flags || m->writable ? "" : " -"), - (m->flags & SD_BUS_VTABLE_METHOD_NO_REPLY) ? " no-reply" : "", - (m->flags & SD_BUS_VTABLE_PROPERTY_CONST) ? " const" : "", - (m->flags & SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE) ? " emits-change" : "", - (m->flags & SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION) ? " emits-invalidation" : "", - m->writable ? " writable" : ""); - } - - return 0; -} - -static int message_dump(sd_bus_message *m, FILE *f) { - return bus_message_dump(m, f, BUS_MESSAGE_DUMP_WITH_HEADER); -} - -static int message_pcap(sd_bus_message *m, FILE *f) { - return bus_message_pcap_frame(m, arg_snaplen, f); -} - -static int monitor(sd_bus *bus, char *argv[], int (*dump)(sd_bus_message *m, FILE *f)) { - bool added_something = false; - char **i; - int r; - - STRV_FOREACH(i, argv+1) { - _cleanup_free_ char *m = NULL; - - if (!service_name_is_valid(*i)) { - log_error("Invalid service name '%s'", *i); - return -EINVAL; - } - - m = strjoin("sender='", *i, "'", NULL); - if (!m) - return log_oom(); - - r = sd_bus_add_match(bus, NULL, m, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - - free(m); - m = strjoin("destination='", *i, "'", NULL); - if (!m) - return log_oom(); - - r = sd_bus_add_match(bus, NULL, m, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - - added_something = true; - } - - STRV_FOREACH(i, arg_matches) { - r = sd_bus_add_match(bus, NULL, *i, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - - added_something = true; - } - - if (!added_something) { - r = sd_bus_add_match(bus, NULL, "", NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - } - - log_info("Monitoring bus message stream."); - - for (;;) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = sd_bus_process(bus, &m); - if (r < 0) - return log_error_errno(r, "Failed to process bus: %m"); - - if (m) { - dump(m, stdout); - fflush(stdout); - - if (sd_bus_message_is_signal(m, "org.freedesktop.DBus.Local", "Disconnected") > 0) { - log_info("Connection terminated, exiting."); - return 0; - } - - continue; - } - - if (r > 0) - continue; - - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) - return log_error_errno(r, "Failed to wait for bus: %m"); - } -} - -static int capture(sd_bus *bus, char *argv[]) { - int r; - - if (isatty(fileno(stdout)) > 0) { - log_error("Refusing to write message data to console, please redirect output to a file."); - return -EINVAL; - } - - bus_pcap_header(arg_snaplen, stdout); - - r = monitor(bus, argv, message_pcap); - if (r < 0) - return r; - - if (ferror(stdout)) { - log_error("Couldn't write capture file."); - return -EIO; - } - - return r; -} - -static int status(sd_bus *bus, char *argv[]) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - pid_t pid; - int r; - - assert(bus); - - if (strv_length(argv) > 2) { - log_error("Expects no or one argument."); - return -EINVAL; - } - - if (argv[1]) { - r = parse_pid(argv[1], &pid); - if (r < 0) - r = sd_bus_get_name_creds( - bus, - argv[1], - (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | _SD_BUS_CREDS_ALL, - &creds); - else - r = sd_bus_creds_new_from_pid( - &creds, - pid, - _SD_BUS_CREDS_ALL); - } else { - const char *scope, *address; - sd_id128_t bus_id; - - r = sd_bus_get_address(bus, &address); - if (r >= 0) - printf("BusAddress=%s%s%s\n", ansi_highlight(), address, ansi_normal()); - - r = sd_bus_get_scope(bus, &scope); - if (r >= 0) - printf("BusScope=%s%s%s\n", ansi_highlight(), scope, ansi_normal()); - - r = sd_bus_get_bus_id(bus, &bus_id); - if (r >= 0) - printf("BusID=%s" SD_ID128_FORMAT_STR "%s\n", ansi_highlight(), SD_ID128_FORMAT_VAL(bus_id), ansi_normal()); - - r = sd_bus_get_owner_creds( - bus, - (arg_augment_creds ? SD_BUS_CREDS_AUGMENT : 0) | _SD_BUS_CREDS_ALL, - &creds); - } - - if (r < 0) - return log_error_errno(r, "Failed to get credentials: %m"); - - bus_creds_dump(creds, NULL, false); - return 0; -} - -static int message_append_cmdline(sd_bus_message *m, const char *signature, char ***x) { - char **p; - int r; - - assert(m); - assert(signature); - assert(x); - - p = *x; - - for (;;) { - const char *v; - char t; - - t = *signature; - v = *p; - - if (t == 0) - break; - if (!v) { - log_error("Too few parameters for signature."); - return -EINVAL; - } - - signature++; - p++; - - switch (t) { - - case SD_BUS_TYPE_BOOLEAN: - - r = parse_boolean(v); - if (r < 0) { - log_error("Failed to parse as boolean: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &r); - break; - - case SD_BUS_TYPE_BYTE: { - uint8_t z; - - r = safe_atou8(v, &z); - if (r < 0) { - log_error("Failed to parse as byte (unsigned 8bit integer): %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_INT16: { - int16_t z; - - r = safe_atoi16(v, &z); - if (r < 0) { - log_error("Failed to parse as signed 16bit integer: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_UINT16: { - uint16_t z; - - r = safe_atou16(v, &z); - if (r < 0) { - log_error("Failed to parse as unsigned 16bit integer: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_INT32: { - int32_t z; - - r = safe_atoi32(v, &z); - if (r < 0) { - log_error("Failed to parse as signed 32bit integer: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_UINT32: { - uint32_t z; - - r = safe_atou32(v, &z); - if (r < 0) { - log_error("Failed to parse as unsigned 32bit integer: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_INT64: { - int64_t z; - - r = safe_atoi64(v, &z); - if (r < 0) { - log_error("Failed to parse as signed 64bit integer: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_UINT64: { - uint64_t z; - - r = safe_atou64(v, &z); - if (r < 0) { - log_error("Failed to parse as unsigned 64bit integer: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - - case SD_BUS_TYPE_DOUBLE: { - double z; - - r = safe_atod(v, &z); - if (r < 0) { - log_error("Failed to parse as double precision floating point: %s", v); - return r; - } - - r = sd_bus_message_append_basic(m, t, &z); - break; - } - - case SD_BUS_TYPE_STRING: - case SD_BUS_TYPE_OBJECT_PATH: - case SD_BUS_TYPE_SIGNATURE: - - r = sd_bus_message_append_basic(m, t, v); - break; - - case SD_BUS_TYPE_ARRAY: { - uint32_t n; - size_t k; - - r = safe_atou32(v, &n); - if (r < 0) { - log_error("Failed to parse number of array entries: %s", v); - return r; - } - - r = signature_element_length(signature, &k); - if (r < 0) { - log_error("Invalid array signature."); - return r; - } - - { - unsigned i; - char s[k + 1]; - memcpy(s, signature, k); - s[k] = 0; - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, s); - if (r < 0) - return bus_log_create_error(r); - - for (i = 0; i < n; i++) { - r = message_append_cmdline(m, s, &p); - if (r < 0) - return r; - } - } - - signature += k; - - r = sd_bus_message_close_container(m); - break; - } - - case SD_BUS_TYPE_VARIANT: - r = sd_bus_message_open_container(m, SD_BUS_TYPE_VARIANT, v); - if (r < 0) - return bus_log_create_error(r); - - r = message_append_cmdline(m, v, &p); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - break; - - case SD_BUS_TYPE_STRUCT_BEGIN: - case SD_BUS_TYPE_DICT_ENTRY_BEGIN: { - size_t k; - - signature--; - p--; - - r = signature_element_length(signature, &k); - if (r < 0) { - log_error("Invalid struct/dict entry signature."); - return r; - } - - { - char s[k-1]; - memcpy(s, signature + 1, k - 2); - s[k - 2] = 0; - - r = sd_bus_message_open_container(m, t == SD_BUS_TYPE_STRUCT_BEGIN ? SD_BUS_TYPE_STRUCT : SD_BUS_TYPE_DICT_ENTRY, s); - if (r < 0) - return bus_log_create_error(r); - - r = message_append_cmdline(m, s, &p); - if (r < 0) - return r; - } - - signature += k; - - r = sd_bus_message_close_container(m); - break; - } - - case SD_BUS_TYPE_UNIX_FD: - log_error("UNIX file descriptor not supported as type."); - return -EINVAL; - - default: - log_error("Unknown signature type %c.", t); - return -EINVAL; - } - - if (r < 0) - return bus_log_create_error(r); - } - - *x = p; - return 0; -} - -static int call(sd_bus *bus, char *argv[]) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - int r; - - assert(bus); - - if (strv_length(argv) < 5) { - log_error("Expects at least four arguments."); - return -EINVAL; - } - - r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2], argv[3], argv[4]); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_expect_reply(m, arg_expect_reply); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_auto_start(m, arg_auto_start); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_allow_interactive_authorization(m, arg_allow_interactive_authorization); - if (r < 0) - return bus_log_create_error(r); - - if (!isempty(argv[5])) { - char **p; - - p = argv+6; - - r = message_append_cmdline(m, argv[5], &p); - if (r < 0) - return r; - - if (*p) { - log_error("Too many parameters for signature."); - return -EINVAL; - } - } - - if (!arg_expect_reply) { - r = sd_bus_send(bus, m, NULL); - if (r < 0) { - log_error("Failed to send message."); - return r; - } - - return 0; - } - - r = sd_bus_call(bus, m, arg_timeout, &error, &reply); - if (r < 0) { - log_error("%s", bus_error_message(&error, r)); - return r; - } - - r = sd_bus_message_is_empty(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (r == 0 && !arg_quiet) { - - if (arg_verbose) { - pager_open_if_enabled(); - - r = bus_message_dump(reply, stdout, 0); - if (r < 0) - return r; - } else { - - fputs(sd_bus_message_get_signature(reply, true), stdout); - fputc(' ', stdout); - - r = format_cmdline(reply, stdout, false); - if (r < 0) - return bus_log_parse_error(r); - - fputc('\n', stdout); - } - } - - return 0; -} - -static int get_property(sd_bus *bus, char *argv[]) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - unsigned n; - char **i; - int r; - - assert(bus); - - n = strv_length(argv); - if (n < 5) { - log_error("Expects at least four arguments."); - return -EINVAL; - } - - STRV_FOREACH(i, argv + 4) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *contents = NULL; - char type; - - r = sd_bus_call_method(bus, argv[1], argv[2], "org.freedesktop.DBus.Properties", "Get", &error, &reply, "ss", argv[3], *i); - if (r < 0) { - log_error("%s", bus_error_message(&error, r)); - return r; - } - - r = sd_bus_message_peek_type(reply, &type, &contents); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_enter_container(reply, 'v', contents); - if (r < 0) - return bus_log_parse_error(r); - - if (arg_verbose) { - pager_open_if_enabled(); - - r = bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_SUBTREE_ONLY); - if (r < 0) - return r; - } else { - fputs(contents, stdout); - fputc(' ', stdout); - - r = format_cmdline(reply, stdout, false); - if (r < 0) - return bus_log_parse_error(r); - - fputc('\n', stdout); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - } - - return 0; -} - -static int set_property(sd_bus *bus, char *argv[]) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - unsigned n; - char **p; - int r; - - assert(bus); - - n = strv_length(argv); - if (n < 6) { - log_error("Expects at least five arguments."); - return -EINVAL; - } - - r = sd_bus_message_new_method_call(bus, &m, argv[1], argv[2], "org.freedesktop.DBus.Properties", "Set"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "ss", argv[3], argv[4]); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'v', argv[5]); - if (r < 0) - return bus_log_create_error(r); - - p = argv+6; - r = message_append_cmdline(m, argv[5], &p); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - if (*p) { - log_error("Too many parameters for signature."); - return -EINVAL; - } - - r = sd_bus_call(bus, m, arg_timeout, &error, NULL); - if (r < 0) { - log_error("%s", bus_error_message(&error, r)); - return r; - } - - return 0; -} - -static int help(void) { - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Introspect the bus.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --system Connect to system bus\n" - " --user Connect to user bus\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --address=ADDRESS Connect to bus specified by address\n" - " --show-machine Show machine ID column in list\n" - " --unique Only show unique names\n" - " --acquired Only show acquired names\n" - " --activatable Only show activatable names\n" - " --match=MATCH Only show matching messages\n" - " --size=SIZE Maximum length of captured packet\n" - " --list Don't show tree, but simple object path list\n" - " --quiet Don't show method call reply\n" - " --verbose Show result values in long format\n" - " --expect-reply=BOOL Expect a method call reply\n" - " --auto-start=BOOL Auto-start destination service\n" - " --allow-interactive-authorization=BOOL\n" - " Allow interactive authorization for operation\n" - " --timeout=SECS Maximum time to wait for method call completion\n" - " --augment-creds=BOOL Extend credential data with data read from /proc/$PID\n\n" - "Commands:\n" - " list List bus names\n" - " status [SERVICE] Show bus service, process or bus owner credentials\n" - " monitor [SERVICE...] Show bus traffic\n" - " capture [SERVICE...] Capture bus traffic as pcap\n" - " tree [SERVICE...] Show object tree of service\n" - " introspect SERVICE OBJECT [INTERFACE]\n" - " call SERVICE OBJECT INTERFACE METHOD [SIGNATURE [ARGUMENT...]]\n" - " Call a method\n" - " get-property SERVICE OBJECT INTERFACE PROPERTY...\n" - " Get property value\n" - " set-property SERVICE OBJECT INTERFACE PROPERTY SIGNATURE ARGUMENT...\n" - " Set property value\n" - " help Show this help\n" - , program_invocation_short_name); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_SYSTEM, - ARG_USER, - ARG_ADDRESS, - ARG_MATCH, - ARG_SHOW_MACHINE, - ARG_UNIQUE, - ARG_ACQUIRED, - ARG_ACTIVATABLE, - ARG_SIZE, - ARG_LIST, - ARG_VERBOSE, - ARG_EXPECT_REPLY, - ARG_AUTO_START, - ARG_ALLOW_INTERACTIVE_AUTHORIZATION, - ARG_TIMEOUT, - ARG_AUGMENT_CREDS, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "user", no_argument, NULL, ARG_USER }, - { "address", required_argument, NULL, ARG_ADDRESS }, - { "show-machine", no_argument, NULL, ARG_SHOW_MACHINE }, - { "unique", no_argument, NULL, ARG_UNIQUE }, - { "acquired", no_argument, NULL, ARG_ACQUIRED }, - { "activatable", no_argument, NULL, ARG_ACTIVATABLE }, - { "match", required_argument, NULL, ARG_MATCH }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "size", required_argument, NULL, ARG_SIZE }, - { "list", no_argument, NULL, ARG_LIST }, - { "quiet", no_argument, NULL, 'q' }, - { "verbose", no_argument, NULL, ARG_VERBOSE }, - { "expect-reply", required_argument, NULL, ARG_EXPECT_REPLY }, - { "auto-start", required_argument, NULL, ARG_AUTO_START }, - { "allow-interactive-authorization", required_argument, NULL, ARG_ALLOW_INTERACTIVE_AUTHORIZATION }, - { "timeout", required_argument, NULL, ARG_TIMEOUT }, - { "augment-creds",required_argument, NULL, ARG_AUGMENT_CREDS}, - {}, - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hH:M:q", options, NULL)) >= 0) - - switch (c) { - - case 'h': - return help(); - - case ARG_VERSION: - return version(); - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case ARG_NO_LEGEND: - arg_legend = false; - break; - - case ARG_USER: - arg_user = true; - break; - - case ARG_SYSTEM: - arg_user = false; - break; - - case ARG_ADDRESS: - arg_address = optarg; - break; - - case ARG_SHOW_MACHINE: - arg_show_machine = true; - break; - - case ARG_UNIQUE: - arg_unique = true; - break; - - case ARG_ACQUIRED: - arg_acquired = true; - break; - - case ARG_ACTIVATABLE: - arg_activatable = true; - break; - - case ARG_MATCH: - if (strv_extend(&arg_matches, optarg) < 0) - return log_oom(); - break; - - case ARG_SIZE: { - uint64_t sz; - - r = parse_size(optarg, 1024, &sz); - if (r < 0) { - log_error("Failed to parse size: %s", optarg); - return r; - } - - if ((uint64_t) (size_t) sz != sz) { - log_error("Size out of range."); - return -E2BIG; - } - - arg_snaplen = (size_t) sz; - break; - } - - case ARG_LIST: - arg_list = true; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case 'q': - arg_quiet = true; - break; - - case ARG_VERBOSE: - arg_verbose = true; - break; - - case ARG_EXPECT_REPLY: - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --expect-reply= parameter."); - return r; - } - - arg_expect_reply = !!r; - break; - - - case ARG_AUTO_START: - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --auto-start= parameter."); - return r; - } - - arg_auto_start = !!r; - break; - - - case ARG_ALLOW_INTERACTIVE_AUTHORIZATION: - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --allow-interactive-authorization= parameter."); - return r; - } - - arg_allow_interactive_authorization = !!r; - break; - - case ARG_TIMEOUT: - r = parse_sec(optarg, &arg_timeout); - if (r < 0) { - log_error("Failed to parse --timeout= parameter."); - return r; - } - - break; - - case ARG_AUGMENT_CREDS: - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --augment-creds= parameter."); - return r; - } - - arg_augment_creds = !!r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -static int busctl_main(sd_bus *bus, int argc, char *argv[]) { - assert(bus); - - if (optind >= argc || - streq(argv[optind], "list")) - return list_bus_names(bus, argv + optind); - - if (streq(argv[optind], "monitor")) - return monitor(bus, argv + optind, message_dump); - - if (streq(argv[optind], "capture")) - return capture(bus, argv + optind); - - if (streq(argv[optind], "status")) - return status(bus, argv + optind); - - if (streq(argv[optind], "tree")) - return tree(bus, argv + optind); - - if (streq(argv[optind], "introspect")) - return introspect(bus, argv + optind); - - if (streq(argv[optind], "call")) - return call(bus, argv + optind); - - if (streq(argv[optind], "get-property")) - return get_property(bus, argv + optind); - - if (streq(argv[optind], "set-property")) - return set_property(bus, argv + optind); - - if (streq(argv[optind], "help")) - return help(); - - log_error("Unknown command '%s'", argv[optind]); - return -EINVAL; -} - -int main(int argc, char *argv[]) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = sd_bus_new(&bus); - if (r < 0) { - log_error_errno(r, "Failed to allocate bus: %m"); - goto finish; - } - - if (streq_ptr(argv[optind], "monitor") || - streq_ptr(argv[optind], "capture")) { - - r = sd_bus_set_monitor(bus, true); - if (r < 0) { - log_error_errno(r, "Failed to set monitor mode: %m"); - goto finish; - } - - r = sd_bus_negotiate_creds(bus, true, _SD_BUS_CREDS_ALL); - if (r < 0) { - log_error_errno(r, "Failed to enable credentials: %m"); - goto finish; - } - - r = sd_bus_negotiate_timestamp(bus, true); - if (r < 0) { - log_error_errno(r, "Failed to enable timestamps: %m"); - goto finish; - } - - r = sd_bus_negotiate_fds(bus, true); - if (r < 0) { - log_error_errno(r, "Failed to enable fds: %m"); - goto finish; - } - } - - r = sd_bus_set_bus_client(bus, true); - if (r < 0) { - log_error_errno(r, "Failed to set bus client: %m"); - goto finish; - } - - if (arg_address) - r = sd_bus_set_address(bus, arg_address); - else { - switch (arg_transport) { - - case BUS_TRANSPORT_LOCAL: - if (arg_user) { - bus->is_user = true; - r = bus_set_address_user(bus); - } else { - bus->is_system = true; - r = bus_set_address_system(bus); - } - break; - - case BUS_TRANSPORT_REMOTE: - r = bus_set_address_system_remote(bus, arg_host); - break; - - case BUS_TRANSPORT_MACHINE: - r = bus_set_address_system_machine(bus, arg_host); - break; - - default: - assert_not_reached("Hmm, unknown transport type."); - } - } - if (r < 0) { - log_error_errno(r, "Failed to set address: %m"); - goto finish; - } - - r = sd_bus_start(bus); - if (r < 0) { - log_error_errno(r, "Failed to connect to bus: %m"); - goto finish; - } - - r = busctl_main(bus, argc, argv); - -finish: - pager_close(); - - strv_free(arg_matches); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/libsystemd/sd-bus/kdbus.h b/src/libsystemd/sd-bus/kdbus.h deleted file mode 100644 index ecffc6b13c..0000000000 --- a/src/libsystemd/sd-bus/kdbus.h +++ /dev/null @@ -1,980 +0,0 @@ -/* - * kdbus 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. - */ - -#ifndef _UAPI_KDBUS_H_ -#define _UAPI_KDBUS_H_ - -#include -#include - -#define KDBUS_IOCTL_MAGIC 0x95 -#define KDBUS_SRC_ID_KERNEL (0) -#define KDBUS_DST_ID_NAME (0) -#define KDBUS_MATCH_ID_ANY (~0ULL) -#define KDBUS_DST_ID_BROADCAST (~0ULL) -#define KDBUS_FLAG_NEGOTIATE (1ULL << 63) - -/** - * struct kdbus_notify_id_change - name registry change message - * @id: New or former owner of the name - * @flags: flags field from KDBUS_HELLO_* - * - * Sent from kernel to userspace when the owner or activator of - * a well-known name changes. - * - * Attached to: - * KDBUS_ITEM_ID_ADD - * KDBUS_ITEM_ID_REMOVE - */ -struct kdbus_notify_id_change { - __u64 id; - __u64 flags; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_notify_name_change - name registry change message - * @old_id: ID and flags of former owner of a name - * @new_id: ID and flags of new owner of a name - * @name: Well-known name - * - * Sent from kernel to userspace when the owner or activator of - * a well-known name changes. - * - * Attached to: - * KDBUS_ITEM_NAME_ADD - * KDBUS_ITEM_NAME_REMOVE - * KDBUS_ITEM_NAME_CHANGE - */ -struct kdbus_notify_name_change { - struct kdbus_notify_id_change old_id; - struct kdbus_notify_id_change new_id; - char name[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_creds - process credentials - * @uid: User ID - * @euid: Effective UID - * @suid: Saved UID - * @fsuid: Filesystem UID - * @gid: Group ID - * @egid: Effective GID - * @sgid: Saved GID - * @fsgid: Filesystem GID - * - * Attached to: - * KDBUS_ITEM_CREDS - */ -struct kdbus_creds { - __u64 uid; - __u64 euid; - __u64 suid; - __u64 fsuid; - __u64 gid; - __u64 egid; - __u64 sgid; - __u64 fsgid; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_pids - process identifiers - * @pid: Process ID - * @tid: Thread ID - * @ppid: Parent process ID - * - * The PID and TID of a process. - * - * Attached to: - * KDBUS_ITEM_PIDS - */ -struct kdbus_pids { - __u64 pid; - __u64 tid; - __u64 ppid; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_caps - process capabilities - * @last_cap: Highest currently known capability bit - * @caps: Variable number of 32-bit capabilities flags - * - * Contains a variable number of 32-bit capabilities flags. - * - * Attached to: - * KDBUS_ITEM_CAPS - */ -struct kdbus_caps { - __u32 last_cap; - __u32 caps[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_audit - audit information - * @sessionid: The audit session ID - * @loginuid: The audit login uid - * - * Attached to: - * KDBUS_ITEM_AUDIT - */ -struct kdbus_audit { - __u32 sessionid; - __u32 loginuid; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_timestamp - * @seqnum: Global per-domain message sequence number - * @monotonic_ns: Monotonic timestamp, in nanoseconds - * @realtime_ns: Realtime timestamp, in nanoseconds - * - * Attached to: - * KDBUS_ITEM_TIMESTAMP - */ -struct kdbus_timestamp { - __u64 seqnum; - __u64 monotonic_ns; - __u64 realtime_ns; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_vec - I/O vector for kdbus payload items - * @size: The size of the vector - * @address: Memory address of data buffer - * @offset: Offset in the in-message payload memory, - * relative to the message head - * - * Attached to: - * KDBUS_ITEM_PAYLOAD_VEC, KDBUS_ITEM_PAYLOAD_OFF - */ -struct kdbus_vec { - __u64 size; - union { - __u64 address; - __u64 offset; - }; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_bloom_parameter - bus-wide bloom parameters - * @size: Size of the bit field in bytes (m / 8) - * @n_hash: Number of hash functions used (k) - */ -struct kdbus_bloom_parameter { - __u64 size; - __u64 n_hash; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_bloom_filter - bloom filter containing n elements - * @generation: Generation of the element set in the filter - * @data: Bit field, multiple of 8 bytes - */ -struct kdbus_bloom_filter { - __u64 generation; - __u64 data[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_memfd - a kdbus memfd - * @start: The offset into the memfd where the segment starts - * @size: The size of the memfd segment - * @fd: The file descriptor number - * @__pad: Padding to ensure proper alignment and size - * - * Attached to: - * KDBUS_ITEM_PAYLOAD_MEMFD - */ -struct kdbus_memfd { - __u64 start; - __u64 size; - int fd; - __u32 __pad; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_name - a registered well-known name with its flags - * @flags: Flags from KDBUS_NAME_* - * @name: Well-known name - * - * Attached to: - * KDBUS_ITEM_OWNED_NAME - */ -struct kdbus_name { - __u64 flags; - char name[0]; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_policy_access_type - permissions of a policy record - * @_KDBUS_POLICY_ACCESS_NULL: Uninitialized/invalid - * @KDBUS_POLICY_ACCESS_USER: Grant access to a uid - * @KDBUS_POLICY_ACCESS_GROUP: Grant access to gid - * @KDBUS_POLICY_ACCESS_WORLD: World-accessible - */ -enum kdbus_policy_access_type { - _KDBUS_POLICY_ACCESS_NULL, - KDBUS_POLICY_ACCESS_USER, - KDBUS_POLICY_ACCESS_GROUP, - KDBUS_POLICY_ACCESS_WORLD, -}; - -/** - * enum kdbus_policy_access_flags - mode flags - * @KDBUS_POLICY_OWN: Allow to own a well-known name - * Implies KDBUS_POLICY_TALK and KDBUS_POLICY_SEE - * @KDBUS_POLICY_TALK: Allow communication to a well-known name - * Implies KDBUS_POLICY_SEE - * @KDBUS_POLICY_SEE: Allow to see a well-known name - */ -enum kdbus_policy_type { - KDBUS_POLICY_SEE = 0, - KDBUS_POLICY_TALK, - KDBUS_POLICY_OWN, -}; - -/** - * struct kdbus_policy_access - policy access item - * @type: One of KDBUS_POLICY_ACCESS_* types - * @access: Access to grant - * @id: For KDBUS_POLICY_ACCESS_USER, the uid - * For KDBUS_POLICY_ACCESS_GROUP, the gid - */ -struct kdbus_policy_access { - __u64 type; /* USER, GROUP, WORLD */ - __u64 access; /* OWN, TALK, SEE */ - __u64 id; /* uid, gid, 0 */ -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_attach_flags - flags for metadata attachments - * @KDBUS_ATTACH_TIMESTAMP: Timestamp - * @KDBUS_ATTACH_CREDS: Credentials - * @KDBUS_ATTACH_PIDS: PIDs - * @KDBUS_ATTACH_AUXGROUPS: Auxiliary groups - * @KDBUS_ATTACH_NAMES: Well-known names - * @KDBUS_ATTACH_TID_COMM: The "comm" process identifier of the TID - * @KDBUS_ATTACH_PID_COMM: The "comm" process identifier of the PID - * @KDBUS_ATTACH_EXE: The path of the executable - * @KDBUS_ATTACH_CMDLINE: The process command line - * @KDBUS_ATTACH_CGROUP: The croup membership - * @KDBUS_ATTACH_CAPS: The process capabilities - * @KDBUS_ATTACH_SECLABEL: The security label - * @KDBUS_ATTACH_AUDIT: The audit IDs - * @KDBUS_ATTACH_CONN_DESCRIPTION: The human-readable connection name - * @_KDBUS_ATTACH_ALL: All of the above - * @_KDBUS_ATTACH_ANY: Wildcard match to enable any kind of - * metatdata. - */ -enum kdbus_attach_flags { - KDBUS_ATTACH_TIMESTAMP = 1ULL << 0, - KDBUS_ATTACH_CREDS = 1ULL << 1, - KDBUS_ATTACH_PIDS = 1ULL << 2, - KDBUS_ATTACH_AUXGROUPS = 1ULL << 3, - KDBUS_ATTACH_NAMES = 1ULL << 4, - KDBUS_ATTACH_TID_COMM = 1ULL << 5, - KDBUS_ATTACH_PID_COMM = 1ULL << 6, - KDBUS_ATTACH_EXE = 1ULL << 7, - KDBUS_ATTACH_CMDLINE = 1ULL << 8, - KDBUS_ATTACH_CGROUP = 1ULL << 9, - KDBUS_ATTACH_CAPS = 1ULL << 10, - KDBUS_ATTACH_SECLABEL = 1ULL << 11, - KDBUS_ATTACH_AUDIT = 1ULL << 12, - KDBUS_ATTACH_CONN_DESCRIPTION = 1ULL << 13, - _KDBUS_ATTACH_ALL = (1ULL << 14) - 1, - _KDBUS_ATTACH_ANY = ~0ULL -}; - -/** - * enum kdbus_item_type - item types to chain data in a list - * @_KDBUS_ITEM_NULL: Uninitialized/invalid - * @_KDBUS_ITEM_USER_BASE: Start of user items - * @KDBUS_ITEM_NEGOTIATE: Negotiate supported items - * @KDBUS_ITEM_PAYLOAD_VEC: Vector to data - * @KDBUS_ITEM_PAYLOAD_OFF: Data at returned offset to message head - * @KDBUS_ITEM_PAYLOAD_MEMFD: Data as sealed memfd - * @KDBUS_ITEM_FDS: Attached file descriptors - * @KDBUS_ITEM_CANCEL_FD: FD used to cancel a synchronous - * operation by writing to it from - * userspace - * @KDBUS_ITEM_BLOOM_PARAMETER: Bus-wide bloom parameters, used with - * KDBUS_CMD_BUS_MAKE, carries a - * struct kdbus_bloom_parameter - * @KDBUS_ITEM_BLOOM_FILTER: Bloom filter carried with a message, - * used to match against a bloom mask of a - * connection, carries a struct - * kdbus_bloom_filter - * @KDBUS_ITEM_BLOOM_MASK: Bloom mask used to match against a - * message'sbloom filter - * @KDBUS_ITEM_DST_NAME: Destination's well-known name - * @KDBUS_ITEM_MAKE_NAME: Name of domain, bus, endpoint - * @KDBUS_ITEM_ATTACH_FLAGS_SEND: Attach-flags, used for updating which - * metadata a connection opts in to send - * @KDBUS_ITEM_ATTACH_FLAGS_RECV: Attach-flags, used for updating which - * metadata a connection requests to - * receive for each reeceived message - * @KDBUS_ITEM_ID: Connection ID - * @KDBUS_ITEM_NAME: Well-know name with flags - * @_KDBUS_ITEM_ATTACH_BASE: Start of metadata attach items - * @KDBUS_ITEM_TIMESTAMP: Timestamp - * @KDBUS_ITEM_CREDS: Process credentials - * @KDBUS_ITEM_PIDS: Process identifiers - * @KDBUS_ITEM_AUXGROUPS: Auxiliary process groups - * @KDBUS_ITEM_OWNED_NAME: A name owned by the associated - * connection - * @KDBUS_ITEM_TID_COMM: Thread ID "comm" identifier - * (Don't trust this, see below.) - * @KDBUS_ITEM_PID_COMM: Process ID "comm" identifier - * (Don't trust this, see below.) - * @KDBUS_ITEM_EXE: The path of the executable - * (Don't trust this, see below.) - * @KDBUS_ITEM_CMDLINE: The process command line - * (Don't trust this, see below.) - * @KDBUS_ITEM_CGROUP: The croup membership - * @KDBUS_ITEM_CAPS: The process capabilities - * @KDBUS_ITEM_SECLABEL: The security label - * @KDBUS_ITEM_AUDIT: The audit IDs - * @KDBUS_ITEM_CONN_DESCRIPTION: The connection's human-readable name - * (debugging) - * @_KDBUS_ITEM_POLICY_BASE: Start of policy items - * @KDBUS_ITEM_POLICY_ACCESS: Policy access block - * @_KDBUS_ITEM_KERNEL_BASE: Start of kernel-generated message items - * @KDBUS_ITEM_NAME_ADD: Notification in kdbus_notify_name_change - * @KDBUS_ITEM_NAME_REMOVE: Notification in kdbus_notify_name_change - * @KDBUS_ITEM_NAME_CHANGE: Notification in kdbus_notify_name_change - * @KDBUS_ITEM_ID_ADD: Notification in kdbus_notify_id_change - * @KDBUS_ITEM_ID_REMOVE: Notification in kdbus_notify_id_change - * @KDBUS_ITEM_REPLY_TIMEOUT: Timeout has been reached - * @KDBUS_ITEM_REPLY_DEAD: Destination died - * - * N.B: The process and thread COMM fields, as well as the CMDLINE and - * EXE fields may be altered by unprivileged processes und should - * hence *not* used for security decisions. Peers should make use of - * these items only for informational purposes, such as generating log - * records. - */ -enum kdbus_item_type { - _KDBUS_ITEM_NULL, - _KDBUS_ITEM_USER_BASE, - KDBUS_ITEM_NEGOTIATE = _KDBUS_ITEM_USER_BASE, - KDBUS_ITEM_PAYLOAD_VEC, - KDBUS_ITEM_PAYLOAD_OFF, - KDBUS_ITEM_PAYLOAD_MEMFD, - KDBUS_ITEM_FDS, - KDBUS_ITEM_CANCEL_FD, - KDBUS_ITEM_BLOOM_PARAMETER, - KDBUS_ITEM_BLOOM_FILTER, - KDBUS_ITEM_BLOOM_MASK, - KDBUS_ITEM_DST_NAME, - KDBUS_ITEM_MAKE_NAME, - KDBUS_ITEM_ATTACH_FLAGS_SEND, - KDBUS_ITEM_ATTACH_FLAGS_RECV, - KDBUS_ITEM_ID, - KDBUS_ITEM_NAME, - KDBUS_ITEM_DST_ID, - - /* keep these item types in sync with KDBUS_ATTACH_* flags */ - _KDBUS_ITEM_ATTACH_BASE = 0x1000, - KDBUS_ITEM_TIMESTAMP = _KDBUS_ITEM_ATTACH_BASE, - KDBUS_ITEM_CREDS, - KDBUS_ITEM_PIDS, - KDBUS_ITEM_AUXGROUPS, - KDBUS_ITEM_OWNED_NAME, - KDBUS_ITEM_TID_COMM, - KDBUS_ITEM_PID_COMM, - KDBUS_ITEM_EXE, - KDBUS_ITEM_CMDLINE, - KDBUS_ITEM_CGROUP, - KDBUS_ITEM_CAPS, - KDBUS_ITEM_SECLABEL, - KDBUS_ITEM_AUDIT, - KDBUS_ITEM_CONN_DESCRIPTION, - - _KDBUS_ITEM_POLICY_BASE = 0x2000, - KDBUS_ITEM_POLICY_ACCESS = _KDBUS_ITEM_POLICY_BASE, - - _KDBUS_ITEM_KERNEL_BASE = 0x8000, - KDBUS_ITEM_NAME_ADD = _KDBUS_ITEM_KERNEL_BASE, - KDBUS_ITEM_NAME_REMOVE, - KDBUS_ITEM_NAME_CHANGE, - KDBUS_ITEM_ID_ADD, - KDBUS_ITEM_ID_REMOVE, - KDBUS_ITEM_REPLY_TIMEOUT, - KDBUS_ITEM_REPLY_DEAD, -}; - -/** - * struct kdbus_item - chain of data blocks - * @size: Overall data record size - * @type: Kdbus_item type of data - * @data: Generic bytes - * @data32: Generic 32 bit array - * @data64: Generic 64 bit array - * @str: Generic string - * @id: Connection ID - * @vec: KDBUS_ITEM_PAYLOAD_VEC - * @creds: KDBUS_ITEM_CREDS - * @audit: KDBUS_ITEM_AUDIT - * @timestamp: KDBUS_ITEM_TIMESTAMP - * @name: KDBUS_ITEM_NAME - * @bloom_parameter: KDBUS_ITEM_BLOOM_PARAMETER - * @bloom_filter: KDBUS_ITEM_BLOOM_FILTER - * @memfd: KDBUS_ITEM_PAYLOAD_MEMFD - * @name_change: KDBUS_ITEM_NAME_ADD - * KDBUS_ITEM_NAME_REMOVE - * KDBUS_ITEM_NAME_CHANGE - * @id_change: KDBUS_ITEM_ID_ADD - * KDBUS_ITEM_ID_REMOVE - * @policy: KDBUS_ITEM_POLICY_ACCESS - */ -struct kdbus_item { - __u64 size; - __u64 type; - union { - __u8 data[0]; - __u32 data32[0]; - __u64 data64[0]; - char str[0]; - - __u64 id; - struct kdbus_vec vec; - struct kdbus_creds creds; - struct kdbus_pids pids; - struct kdbus_audit audit; - struct kdbus_caps caps; - struct kdbus_timestamp timestamp; - struct kdbus_name name; - struct kdbus_bloom_parameter bloom_parameter; - struct kdbus_bloom_filter bloom_filter; - struct kdbus_memfd memfd; - int fds[0]; - struct kdbus_notify_name_change name_change; - struct kdbus_notify_id_change id_change; - struct kdbus_policy_access policy_access; - }; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_msg_flags - type of message - * @KDBUS_MSG_EXPECT_REPLY: Expect a reply message, used for - * method calls. The userspace-supplied - * cookie identifies the message and the - * respective reply carries the cookie - * in cookie_reply - * @KDBUS_MSG_NO_AUTO_START: Do not start a service if the addressed - * name is not currently active. This flag is - * not looked at by the kernel but only - * serves as hint for userspace implementations. - * @KDBUS_MSG_SIGNAL: Treat this message as signal - */ -enum kdbus_msg_flags { - KDBUS_MSG_EXPECT_REPLY = 1ULL << 0, - KDBUS_MSG_NO_AUTO_START = 1ULL << 1, - KDBUS_MSG_SIGNAL = 1ULL << 2, -}; - -/** - * enum kdbus_payload_type - type of payload carried by message - * @KDBUS_PAYLOAD_KERNEL: Kernel-generated simple message - * @KDBUS_PAYLOAD_DBUS: D-Bus marshalling "DBusDBus" - * - * Any payload-type is accepted. Common types will get added here once - * established. - */ -enum kdbus_payload_type { - KDBUS_PAYLOAD_KERNEL, - KDBUS_PAYLOAD_DBUS = 0x4442757344427573ULL, -}; - -/** - * struct kdbus_msg - the representation of a kdbus message - * @size: Total size of the message - * @flags: Message flags (KDBUS_MSG_*), userspace → kernel - * @priority: Message queue priority value - * @dst_id: 64-bit ID of the destination connection - * @src_id: 64-bit ID of the source connection - * @payload_type: Payload type (KDBUS_PAYLOAD_*) - * @cookie: Userspace-supplied cookie, for the connection - * to identify its messages - * @timeout_ns: The time to wait for a message reply from the peer. - * If there is no reply, and the send command is - * executed asynchronously, a kernel-generated message - * with an attached KDBUS_ITEM_REPLY_TIMEOUT item - * is sent to @src_id. For synchronously executed send - * command, the value denotes the maximum time the call - * blocks to wait for a reply. The timeout is expected in - * nanoseconds and as absolute CLOCK_MONOTONIC value. - * @cookie_reply: A reply to the requesting message with the same - * cookie. The requesting connection can match its - * request and the reply with this value - * @items: A list of kdbus_items containing the message payload - */ -struct kdbus_msg { - __u64 size; - __u64 flags; - __s64 priority; - __u64 dst_id; - __u64 src_id; - __u64 payload_type; - __u64 cookie; - union { - __u64 timeout_ns; - __u64 cookie_reply; - }; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_msg_info - returned message container - * @offset: Offset of kdbus_msg slice in pool - * @msg_size: Copy of the kdbus_msg.size field - * @return_flags: Command return flags, kernel → userspace - */ -struct kdbus_msg_info { - __u64 offset; - __u64 msg_size; - __u64 return_flags; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_send_flags - flags for sending messages - * @KDBUS_SEND_SYNC_REPLY: Wait for destination connection to - * reply to this message. The - * KDBUS_CMD_SEND ioctl() will block - * until the reply is received, and - * reply in struct kdbus_cmd_send will - * yield the offset in the sender's pool - * where the reply can be found. - * This flag is only valid if - * @KDBUS_MSG_EXPECT_REPLY is set as well. - */ -enum kdbus_send_flags { - KDBUS_SEND_SYNC_REPLY = 1ULL << 0, -}; - -/** - * struct kdbus_cmd_send - send message - * @size: Overall size of this structure - * @flags: Flags to change send behavior (KDBUS_SEND_*) - * @return_flags: Command return flags, kernel → userspace - * @msg_address: Storage address of the kdbus_msg to send - * @reply: Storage for message reply if KDBUS_SEND_SYNC_REPLY - * was given - * @items: Additional items for this command - */ -struct kdbus_cmd_send { - __u64 size; - __u64 flags; - __u64 return_flags; - __u64 msg_address; - struct kdbus_msg_info reply; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_recv_flags - flags for de-queuing messages - * @KDBUS_RECV_PEEK: Return the next queued message without - * actually de-queuing it, and without installing - * any file descriptors or other resources. It is - * usually used to determine the activating - * connection of a bus name. - * @KDBUS_RECV_DROP: Drop and free the next queued message and all - * its resources without actually receiving it. - * @KDBUS_RECV_USE_PRIORITY: Only de-queue messages with the specified or - * higher priority (lowest values); if not set, - * the priority value is ignored. - */ -enum kdbus_recv_flags { - KDBUS_RECV_PEEK = 1ULL << 0, - KDBUS_RECV_DROP = 1ULL << 1, - KDBUS_RECV_USE_PRIORITY = 1ULL << 2, -}; - -/** - * enum kdbus_recv_return_flags - return flags for message receive commands - * @KDBUS_RECV_RETURN_INCOMPLETE_FDS: One or more file descriptors could not - * be installed. These descriptors in - * KDBUS_ITEM_FDS will carry the value -1. - * @KDBUS_RECV_RETURN_DROPPED_MSGS: There have been dropped messages since - * the last time a message was received. - * The 'dropped_msgs' counter contains the - * number of messages dropped pool - * overflows or other missed broadcasts. - */ -enum kdbus_recv_return_flags { - KDBUS_RECV_RETURN_INCOMPLETE_FDS = 1ULL << 0, - KDBUS_RECV_RETURN_DROPPED_MSGS = 1ULL << 1, -}; - -/** - * struct kdbus_cmd_recv - struct to de-queue a buffered message - * @size: Overall size of this object - * @flags: KDBUS_RECV_* flags, userspace → kernel - * @return_flags: Command return flags, kernel → userspace - * @priority: Minimum priority of the messages to de-queue. Lowest - * values have the highest priority. - * @dropped_msgs: In case there were any dropped messages since the last - * time a message was received, this will be set to the - * number of lost messages and - * KDBUS_RECV_RETURN_DROPPED_MSGS will be set in - * 'return_flags'. This can only happen if the ioctl - * returns 0 or EAGAIN. - * @msg: Return storage for received message. - * @items: Additional items for this command. - * - * This struct is used with the KDBUS_CMD_RECV ioctl. - */ -struct kdbus_cmd_recv { - __u64 size; - __u64 flags; - __u64 return_flags; - __s64 priority; - __u64 dropped_msgs; - struct kdbus_msg_info msg; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_cmd_free - struct to free a slice of memory in the pool - * @size: Overall size of this structure - * @flags: Flags for the free command, userspace → kernel - * @return_flags: Command return flags, kernel → userspace - * @offset: The offset of the memory slice, as returned by other - * ioctls - * @items: Additional items to modify the behavior - * - * This struct is used with the KDBUS_CMD_FREE ioctl. - */ -struct kdbus_cmd_free { - __u64 size; - __u64 flags; - __u64 return_flags; - __u64 offset; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_hello_flags - flags for struct kdbus_cmd_hello - * @KDBUS_HELLO_ACCEPT_FD: The connection allows the reception of - * any passed file descriptors - * @KDBUS_HELLO_ACTIVATOR: Special-purpose connection which registers - * a well-know name for a process to be started - * when traffic arrives - * @KDBUS_HELLO_POLICY_HOLDER: Special-purpose connection which registers - * policy entries for a name. The provided name - * is not activated and not registered with the - * name database, it only allows unprivileged - * connections to acquire a name, talk or discover - * a service - * @KDBUS_HELLO_MONITOR: Special-purpose connection to monitor - * bus traffic - */ -enum kdbus_hello_flags { - KDBUS_HELLO_ACCEPT_FD = 1ULL << 0, - KDBUS_HELLO_ACTIVATOR = 1ULL << 1, - KDBUS_HELLO_POLICY_HOLDER = 1ULL << 2, - KDBUS_HELLO_MONITOR = 1ULL << 3, -}; - -/** - * struct kdbus_cmd_hello - struct to say hello to kdbus - * @size: The total size of the structure - * @flags: Connection flags (KDBUS_HELLO_*), userspace → kernel - * @return_flags: Command return flags, kernel → userspace - * @attach_flags_send: Mask of metadata to attach to each message sent - * off by this connection (KDBUS_ATTACH_*) - * @attach_flags_recv: Mask of metadata to attach to each message receieved - * by the new connection (KDBUS_ATTACH_*) - * @bus_flags: The flags field copied verbatim from the original - * KDBUS_CMD_BUS_MAKE ioctl. It's intended to be useful - * to do negotiation of features of the payload that is - * transferred (kernel → userspace) - * @id: The ID of this connection (kernel → userspace) - * @pool_size: Size of the connection's buffer where the received - * messages are placed - * @offset: Pool offset where items are returned to report - * additional information about the bus and the newly - * created connection. - * @items_size: Size of buffer returned in the pool slice at @offset. - * @id128: Unique 128-bit ID of the bus (kernel → userspace) - * @items: A list of items - * - * This struct is used with the KDBUS_CMD_HELLO ioctl. - */ -struct kdbus_cmd_hello { - __u64 size; - __u64 flags; - __u64 return_flags; - __u64 attach_flags_send; - __u64 attach_flags_recv; - __u64 bus_flags; - __u64 id; - __u64 pool_size; - __u64 offset; - __u64 items_size; - __u8 id128[16]; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_info - connection information - * @size: total size of the struct - * @id: 64bit object ID - * @flags: object creation flags - * @items: list of items - * - * Note that the user is responsible for freeing the allocated memory with - * the KDBUS_CMD_FREE ioctl. - */ -struct kdbus_info { - __u64 size; - __u64 id; - __u64 flags; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_list_flags - what to include into the returned list - * @KDBUS_LIST_UNIQUE: active connections - * @KDBUS_LIST_ACTIVATORS: activator connections - * @KDBUS_LIST_NAMES: known well-known names - * @KDBUS_LIST_QUEUED: queued-up names - */ -enum kdbus_list_flags { - KDBUS_LIST_UNIQUE = 1ULL << 0, - KDBUS_LIST_NAMES = 1ULL << 1, - KDBUS_LIST_ACTIVATORS = 1ULL << 2, - KDBUS_LIST_QUEUED = 1ULL << 3, -}; - -/** - * struct kdbus_cmd_list - list connections - * @size: overall size of this object - * @flags: flags for the query (KDBUS_LIST_*), userspace → kernel - * @return_flags: command return flags, kernel → userspace - * @offset: Offset in the caller's pool buffer where an array of - * kdbus_info objects is stored. - * The user must use KDBUS_CMD_FREE to free the - * allocated memory. - * @list_size: size of returned list in bytes - * @items: Items for the command. Reserved for future use. - * - * This structure is used with the KDBUS_CMD_LIST ioctl. - */ -struct kdbus_cmd_list { - __u64 size; - __u64 flags; - __u64 return_flags; - __u64 offset; - __u64 list_size; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * struct kdbus_cmd_info - struct used for KDBUS_CMD_CONN_INFO ioctl - * @size: The total size of the struct - * @flags: Flags for this ioctl, userspace → kernel - * @return_flags: Command return flags, kernel → userspace - * @id: The 64-bit ID of the connection. If set to zero, passing - * @name is required. kdbus will look up the name to - * determine the ID in this case. - * @attach_flags: Set of attach flags to specify the set of information - * to receive, userspace → kernel - * @offset: Returned offset in the caller's pool buffer where the - * kdbus_info struct result is stored. The user must - * use KDBUS_CMD_FREE to free the allocated memory. - * @info_size: Output buffer to report size of data at @offset. - * @items: The optional item list, containing the - * well-known name to look up as a KDBUS_ITEM_NAME. - * Only needed in case @id is zero. - * - * On success, the KDBUS_CMD_CONN_INFO ioctl will return 0 and @offset will - * tell the user the offset in the connection pool buffer at which to find the - * result in a struct kdbus_info. - */ -struct kdbus_cmd_info { - __u64 size; - __u64 flags; - __u64 return_flags; - __u64 id; - __u64 attach_flags; - __u64 offset; - __u64 info_size; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_cmd_match_flags - flags to control the KDBUS_CMD_MATCH_ADD ioctl - * @KDBUS_MATCH_REPLACE: If entries with the supplied cookie already - * exists, remove them before installing the new - * matches. - */ -enum kdbus_cmd_match_flags { - KDBUS_MATCH_REPLACE = 1ULL << 0, -}; - -/** - * struct kdbus_cmd_match - struct to add or remove matches - * @size: The total size of the struct - * @flags: Flags for match command (KDBUS_MATCH_*), - * userspace → kernel - * @return_flags: Command return flags, kernel → userspace - * @cookie: Userspace supplied cookie. When removing, the cookie - * identifies the match to remove - * @items: A list of items for additional information - * - * This structure is used with the KDBUS_CMD_MATCH_ADD and - * KDBUS_CMD_MATCH_REMOVE ioctl. - */ -struct kdbus_cmd_match { - __u64 size; - __u64 flags; - __u64 return_flags; - __u64 cookie; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * enum kdbus_make_flags - Flags for KDBUS_CMD_{BUS,ENDPOINT}_MAKE - * @KDBUS_MAKE_ACCESS_GROUP: Make the bus or endpoint node group-accessible - * @KDBUS_MAKE_ACCESS_WORLD: Make the bus or endpoint node world-accessible - */ -enum kdbus_make_flags { - KDBUS_MAKE_ACCESS_GROUP = 1ULL << 0, - KDBUS_MAKE_ACCESS_WORLD = 1ULL << 1, -}; - -/** - * enum kdbus_name_flags - flags for KDBUS_CMD_NAME_ACQUIRE - * @KDBUS_NAME_REPLACE_EXISTING: Try to replace name of other connections - * @KDBUS_NAME_ALLOW_REPLACEMENT: Allow the replacement of the name - * @KDBUS_NAME_QUEUE: Name should be queued if busy - * @KDBUS_NAME_IN_QUEUE: Name is queued - * @KDBUS_NAME_ACTIVATOR: Name is owned by a activator connection - */ -enum kdbus_name_flags { - KDBUS_NAME_REPLACE_EXISTING = 1ULL << 0, - KDBUS_NAME_ALLOW_REPLACEMENT = 1ULL << 1, - KDBUS_NAME_QUEUE = 1ULL << 2, - KDBUS_NAME_IN_QUEUE = 1ULL << 3, - KDBUS_NAME_ACTIVATOR = 1ULL << 4, -}; - -/** - * struct kdbus_cmd - generic ioctl payload - * @size: Overall size of this structure - * @flags: Flags for this ioctl, userspace → kernel - * @return_flags: Ioctl return flags, kernel → userspace - * @items: Additional items to modify the behavior - * - * This is a generic ioctl payload object. It's used by all ioctls that only - * take flags and items as input. - */ -struct kdbus_cmd { - __u64 size; - __u64 flags; - __u64 return_flags; - struct kdbus_item items[0]; -} __attribute__((__aligned__(8))); - -/** - * Ioctl API - * - * KDBUS_CMD_BUS_MAKE: After opening the "control" node, this command - * creates a new bus with the specified - * name. The bus is immediately shut down and - * cleaned up when the opened file descriptor is - * closed. - * - * KDBUS_CMD_ENDPOINT_MAKE: Creates a new named special endpoint to talk to - * the bus. Such endpoints usually carry a more - * restrictive policy and grant restricted access - * to specific applications. - * KDBUS_CMD_ENDPOINT_UPDATE: Update the properties of a custom enpoint. Used - * to update the policy. - * - * KDBUS_CMD_HELLO: By opening the bus node, a connection is - * created. After a HELLO the opened connection - * becomes an active peer on the bus. - * KDBUS_CMD_UPDATE: Update the properties of a connection. Used to - * update the metadata subscription mask and - * policy. - * KDBUS_CMD_BYEBYE: Disconnect a connection. If there are no - * messages queued up in the connection's pool, - * the call succeeds, and the handle is rendered - * unusable. Otherwise, -EBUSY is returned without - * any further side-effects. - * KDBUS_CMD_FREE: Release the allocated memory in the receiver's - * pool. - * KDBUS_CMD_CONN_INFO: Retrieve credentials and properties of the - * initial creator of the connection. The data was - * stored at registration time and does not - * necessarily represent the connected process or - * the actual state of the process. - * KDBUS_CMD_BUS_CREATOR_INFO: Retrieve information of the creator of the bus - * a connection is attached to. - * - * KDBUS_CMD_SEND: Send a message and pass data from userspace to - * the kernel. - * KDBUS_CMD_RECV: Receive a message from the kernel which is - * placed in the receiver's pool. - * - * KDBUS_CMD_NAME_ACQUIRE: Request a well-known bus name to associate with - * the connection. Well-known names are used to - * address a peer on the bus. - * KDBUS_CMD_NAME_RELEASE: Release a well-known name the connection - * currently owns. - * KDBUS_CMD_LIST: Retrieve the list of all currently registered - * well-known and unique names. - * - * KDBUS_CMD_MATCH_ADD: Install a match which broadcast messages should - * be delivered to the connection. - * KDBUS_CMD_MATCH_REMOVE: Remove a current match for broadcast messages. - */ -enum kdbus_ioctl_type { - /* bus owner (00-0f) */ - KDBUS_CMD_BUS_MAKE = _IOW(KDBUS_IOCTL_MAGIC, 0x00, - struct kdbus_cmd), - - /* endpoint owner (10-1f) */ - KDBUS_CMD_ENDPOINT_MAKE = _IOW(KDBUS_IOCTL_MAGIC, 0x10, - struct kdbus_cmd), - KDBUS_CMD_ENDPOINT_UPDATE = _IOW(KDBUS_IOCTL_MAGIC, 0x11, - struct kdbus_cmd), - - /* connection owner (80-ff) */ - KDBUS_CMD_HELLO = _IOWR(KDBUS_IOCTL_MAGIC, 0x80, - struct kdbus_cmd_hello), - KDBUS_CMD_UPDATE = _IOW(KDBUS_IOCTL_MAGIC, 0x81, - struct kdbus_cmd), - KDBUS_CMD_BYEBYE = _IOW(KDBUS_IOCTL_MAGIC, 0x82, - struct kdbus_cmd), - KDBUS_CMD_FREE = _IOW(KDBUS_IOCTL_MAGIC, 0x83, - struct kdbus_cmd_free), - KDBUS_CMD_CONN_INFO = _IOR(KDBUS_IOCTL_MAGIC, 0x84, - struct kdbus_cmd_info), - KDBUS_CMD_BUS_CREATOR_INFO = _IOR(KDBUS_IOCTL_MAGIC, 0x85, - struct kdbus_cmd_info), - KDBUS_CMD_LIST = _IOR(KDBUS_IOCTL_MAGIC, 0x86, - struct kdbus_cmd_list), - - KDBUS_CMD_SEND = _IOW(KDBUS_IOCTL_MAGIC, 0x90, - struct kdbus_cmd_send), - KDBUS_CMD_RECV = _IOR(KDBUS_IOCTL_MAGIC, 0x91, - struct kdbus_cmd_recv), - - KDBUS_CMD_NAME_ACQUIRE = _IOW(KDBUS_IOCTL_MAGIC, 0xa0, - struct kdbus_cmd), - KDBUS_CMD_NAME_RELEASE = _IOW(KDBUS_IOCTL_MAGIC, 0xa1, - struct kdbus_cmd), - - KDBUS_CMD_MATCH_ADD = _IOW(KDBUS_IOCTL_MAGIC, 0xb0, - struct kdbus_cmd_match), - KDBUS_CMD_MATCH_REMOVE = _IOW(KDBUS_IOCTL_MAGIC, 0xb1, - struct kdbus_cmd_match), -}; - -#endif /* _UAPI_KDBUS_H_ */ diff --git a/src/libsystemd/sd-bus/sd-bus.c b/src/libsystemd/sd-bus/sd-bus.c deleted file mode 100644 index c6f626d8aa..0000000000 --- a/src/libsystemd/sd-bus/sd-bus.c +++ /dev/null @@ -1,3794 +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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-container.h" -#include "bus-control.h" -#include "bus-internal.h" -#include "bus-kernel.h" -#include "bus-label.h" -#include "bus-message.h" -#include "bus-objects.h" -#include "bus-protocol.h" -#include "bus-slot.h" -#include "bus-socket.h" -#include "bus-track.h" -#include "bus-type.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "def.h" -#include "fd-util.h" -#include "hexdecoct.h" -#include "hostname-util.h" -#include "macro.h" -#include "missing.h" -#include "parse-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -#define log_debug_bus_message(m) \ - do { \ - sd_bus_message *_mm = (m); \ - log_debug("Got message type=%s sender=%s destination=%s object=%s interface=%s member=%s cookie=%" PRIu64 " reply_cookie=%" PRIu64 " error=%s", \ - bus_message_type_to_string(_mm->header->type), \ - strna(sd_bus_message_get_sender(_mm)), \ - strna(sd_bus_message_get_destination(_mm)), \ - strna(sd_bus_message_get_path(_mm)), \ - strna(sd_bus_message_get_interface(_mm)), \ - strna(sd_bus_message_get_member(_mm)), \ - BUS_MESSAGE_COOKIE(_mm), \ - _mm->reply_cookie, \ - strna(_mm->error.message)); \ - } while (false) - -static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec); -static int attach_io_events(sd_bus *b); -static void detach_io_events(sd_bus *b); - -static thread_local sd_bus *default_system_bus = NULL; -static thread_local sd_bus *default_user_bus = NULL; -static thread_local sd_bus *default_starter_bus = NULL; - -static void bus_close_fds(sd_bus *b) { - assert(b); - - detach_io_events(b); - - if (b->input_fd != b->output_fd) - safe_close(b->output_fd); - b->output_fd = b->input_fd = safe_close(b->input_fd); -} - -static void bus_reset_queues(sd_bus *b) { - assert(b); - - while (b->rqueue_size > 0) - sd_bus_message_unref(b->rqueue[--b->rqueue_size]); - - b->rqueue = mfree(b->rqueue); - b->rqueue_allocated = 0; - - while (b->wqueue_size > 0) - sd_bus_message_unref(b->wqueue[--b->wqueue_size]); - - b->wqueue = mfree(b->wqueue); - b->wqueue_allocated = 0; -} - -static void bus_free(sd_bus *b) { - sd_bus_slot *s; - - assert(b); - assert(!b->track_queue); - - b->state = BUS_CLOSED; - - sd_bus_detach_event(b); - - while ((s = b->slots)) { - /* At this point only floating slots can still be - * around, because the non-floating ones keep a - * reference to the bus, and we thus couldn't be - * destructing right now... We forcibly disconnect the - * slots here, so that they still can be referenced by - * apps, but are dead. */ - - assert(s->floating); - bus_slot_disconnect(s); - sd_bus_slot_unref(s); - } - - if (b->default_bus_ptr) - *b->default_bus_ptr = NULL; - - bus_close_fds(b); - - if (b->kdbus_buffer) - munmap(b->kdbus_buffer, KDBUS_POOL_SIZE); - - free(b->label); - free(b->rbuffer); - free(b->unique_name); - free(b->auth_buffer); - free(b->address); - free(b->kernel); - free(b->machine); - free(b->fake_label); - free(b->cgroup_root); - free(b->description); - - free(b->exec_path); - strv_free(b->exec_argv); - - close_many(b->fds, b->n_fds); - free(b->fds); - - bus_reset_queues(b); - - ordered_hashmap_free_free(b->reply_callbacks); - prioq_free(b->reply_callbacks_prioq); - - assert(b->match_callbacks.type == BUS_MATCH_ROOT); - bus_match_free(&b->match_callbacks); - - hashmap_free_free(b->vtable_methods); - hashmap_free_free(b->vtable_properties); - - assert(hashmap_isempty(b->nodes)); - hashmap_free(b->nodes); - - bus_kernel_flush_memfd(b); - - assert_se(pthread_mutex_destroy(&b->memfd_cache_mutex) == 0); - - free(b); -} - -_public_ int sd_bus_new(sd_bus **ret) { - sd_bus *r; - - assert_return(ret, -EINVAL); - - r = new0(sd_bus, 1); - if (!r) - return -ENOMEM; - - r->n_ref = REFCNT_INIT; - r->input_fd = r->output_fd = -1; - r->message_version = 1; - r->creds_mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME; - r->hello_flags |= KDBUS_HELLO_ACCEPT_FD; - r->attach_flags |= KDBUS_ATTACH_NAMES; - r->original_pid = getpid(); - - assert_se(pthread_mutex_init(&r->memfd_cache_mutex, NULL) == 0); - - /* We guarantee that wqueue always has space for at least one - * entry */ - if (!GREEDY_REALLOC(r->wqueue, r->wqueue_allocated, 1)) { - free(r); - return -ENOMEM; - } - - *ret = r; - return 0; -} - -_public_ int sd_bus_set_address(sd_bus *bus, const char *address) { - char *a; - - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(address, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - a = strdup(address); - if (!a) - return -ENOMEM; - - free(bus->address); - bus->address = a; - - return 0; -} - -_public_ int sd_bus_set_fd(sd_bus *bus, int input_fd, int output_fd) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(input_fd >= 0, -EBADF); - assert_return(output_fd >= 0, -EBADF); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->input_fd = input_fd; - bus->output_fd = output_fd; - return 0; -} - -_public_ int sd_bus_set_exec(sd_bus *bus, const char *path, char *const argv[]) { - char *p, **a; - - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(path, -EINVAL); - assert_return(!strv_isempty(argv), -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - p = strdup(path); - if (!p) - return -ENOMEM; - - a = strv_copy(argv); - if (!a) { - free(p); - return -ENOMEM; - } - - free(bus->exec_path); - strv_free(bus->exec_argv); - - bus->exec_path = p; - bus->exec_argv = a; - - return 0; -} - -_public_ int sd_bus_set_bus_client(sd_bus *bus, int b) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->bus_client = !!b; - return 0; -} - -_public_ int sd_bus_set_monitor(sd_bus *bus, int b) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - SET_FLAG(bus->hello_flags, KDBUS_HELLO_MONITOR, b); - return 0; -} - -_public_ int sd_bus_negotiate_fds(sd_bus *bus, int b) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - SET_FLAG(bus->hello_flags, KDBUS_HELLO_ACCEPT_FD, b); - return 0; -} - -_public_ int sd_bus_negotiate_timestamp(sd_bus *bus, int b) { - uint64_t new_flags; - assert_return(bus, -EINVAL); - assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - new_flags = bus->attach_flags; - SET_FLAG(new_flags, KDBUS_ATTACH_TIMESTAMP, b); - - if (bus->attach_flags == new_flags) - return 0; - - bus->attach_flags = new_flags; - if (bus->state != BUS_UNSET && bus->is_kernel) - bus_kernel_realize_attach_flags(bus); - - return 0; -} - -_public_ int sd_bus_negotiate_creds(sd_bus *bus, int b, uint64_t mask) { - uint64_t new_flags; - - assert_return(bus, -EINVAL); - assert_return(mask <= _SD_BUS_CREDS_ALL, -EINVAL); - assert_return(!IN_SET(bus->state, BUS_CLOSING, BUS_CLOSED), -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (b) - bus->creds_mask |= mask; - else - bus->creds_mask &= ~mask; - - /* The well knowns we need unconditionally, so that matches can work */ - bus->creds_mask |= SD_BUS_CREDS_WELL_KNOWN_NAMES|SD_BUS_CREDS_UNIQUE_NAME; - - /* Make sure we don't lose the timestamp flag */ - new_flags = (bus->attach_flags & KDBUS_ATTACH_TIMESTAMP) | attach_flags_to_kdbus(bus->creds_mask); - if (bus->attach_flags == new_flags) - return 0; - - bus->attach_flags = new_flags; - if (bus->state != BUS_UNSET && bus->is_kernel) - bus_kernel_realize_attach_flags(bus); - - return 0; -} - -_public_ int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t server_id) { - assert_return(bus, -EINVAL); - assert_return(b || sd_id128_equal(server_id, SD_ID128_NULL), -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->is_server = !!b; - bus->server_id = server_id; - return 0; -} - -_public_ int sd_bus_set_anonymous(sd_bus *bus, int b) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->anonymous_auth = !!b; - return 0; -} - -_public_ int sd_bus_set_trusted(sd_bus *bus, int b) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->trusted = !!b; - return 0; -} - -_public_ int sd_bus_set_description(sd_bus *bus, const char *description) { - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return free_and_strdup(&bus->description, description); -} - -_public_ int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->allow_interactive_authorization = !!b; - return 0; -} - -_public_ int sd_bus_get_allow_interactive_authorization(sd_bus *bus) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return bus->allow_interactive_authorization; -} - -static int hello_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) { - const char *s; - sd_bus *bus; - int r; - - assert(reply); - bus = reply->bus; - assert(bus); - assert(bus->state == BUS_HELLO || bus->state == BUS_CLOSING); - - r = sd_bus_message_get_errno(reply); - if (r > 0) - return -r; - - r = sd_bus_message_read(reply, "s", &s); - if (r < 0) - return r; - - if (!service_name_is_valid(s) || s[0] != ':') - return -EBADMSG; - - bus->unique_name = strdup(s); - if (!bus->unique_name) - return -ENOMEM; - - if (bus->state == BUS_HELLO) - bus->state = BUS_RUNNING; - - return 1; -} - -static int bus_send_hello(sd_bus *bus) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert(bus); - - if (!bus->bus_client || bus->is_kernel) - return 0; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "Hello"); - if (r < 0) - return r; - - return sd_bus_call_async(bus, NULL, m, hello_callback, NULL, 0); -} - -int bus_start_running(sd_bus *bus) { - assert(bus); - - if (bus->bus_client && !bus->is_kernel) { - bus->state = BUS_HELLO; - return 1; - } - - bus->state = BUS_RUNNING; - return 1; -} - -static int parse_address_key(const char **p, const char *key, char **value) { - size_t l, n = 0, allocated = 0; - const char *a; - char *r = NULL; - - assert(p); - assert(*p); - assert(value); - - if (key) { - l = strlen(key); - if (strncmp(*p, key, l) != 0) - return 0; - - if ((*p)[l] != '=') - return 0; - - if (*value) - return -EINVAL; - - a = *p + l + 1; - } else - a = *p; - - while (*a != ';' && *a != ',' && *a != 0) { - char c; - - if (*a == '%') { - int x, y; - - x = unhexchar(a[1]); - if (x < 0) { - free(r); - return x; - } - - y = unhexchar(a[2]); - if (y < 0) { - free(r); - return y; - } - - c = (char) ((x << 4) | y); - a += 3; - } else { - c = *a; - a++; - } - - if (!GREEDY_REALLOC(r, allocated, n + 2)) - return -ENOMEM; - - r[n++] = c; - } - - if (!r) { - r = strdup(""); - if (!r) - return -ENOMEM; - } else - r[n] = 0; - - if (*a == ',') - a++; - - *p = a; - - free(*value); - *value = r; - - return 1; -} - -static void skip_address_key(const char **p) { - assert(p); - assert(*p); - - *p += strcspn(*p, ","); - - if (**p == ',') - (*p) ++; -} - -static int parse_unix_address(sd_bus *b, const char **p, char **guid) { - _cleanup_free_ char *path = NULL, *abstract = NULL; - size_t l; - int r; - - assert(b); - assert(p); - assert(*p); - assert(guid); - - while (**p != 0 && **p != ';') { - r = parse_address_key(p, "guid", guid); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "path", &path); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "abstract", &abstract); - if (r < 0) - return r; - else if (r > 0) - continue; - - skip_address_key(p); - } - - if (!path && !abstract) - return -EINVAL; - - if (path && abstract) - return -EINVAL; - - if (path) { - l = strlen(path); - if (l > sizeof(b->sockaddr.un.sun_path)) - return -E2BIG; - - b->sockaddr.un.sun_family = AF_UNIX; - strncpy(b->sockaddr.un.sun_path, path, sizeof(b->sockaddr.un.sun_path)); - b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + l; - } else if (abstract) { - l = strlen(abstract); - if (l > sizeof(b->sockaddr.un.sun_path) - 1) - return -E2BIG; - - b->sockaddr.un.sun_family = AF_UNIX; - b->sockaddr.un.sun_path[0] = 0; - strncpy(b->sockaddr.un.sun_path+1, abstract, sizeof(b->sockaddr.un.sun_path)-1); - b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + 1 + l; - } - - return 0; -} - -static int parse_tcp_address(sd_bus *b, const char **p, char **guid) { - _cleanup_free_ char *host = NULL, *port = NULL, *family = NULL; - int r; - struct addrinfo *result, hints = { - .ai_socktype = SOCK_STREAM, - .ai_flags = AI_ADDRCONFIG, - }; - - assert(b); - assert(p); - assert(*p); - assert(guid); - - while (**p != 0 && **p != ';') { - r = parse_address_key(p, "guid", guid); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "host", &host); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "port", &port); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "family", &family); - if (r < 0) - return r; - else if (r > 0) - continue; - - skip_address_key(p); - } - - if (!host || !port) - return -EINVAL; - - if (family) { - if (streq(family, "ipv4")) - hints.ai_family = AF_INET; - else if (streq(family, "ipv6")) - hints.ai_family = AF_INET6; - else - return -EINVAL; - } - - r = getaddrinfo(host, port, &hints, &result); - if (r == EAI_SYSTEM) - return -errno; - else if (r != 0) - return -EADDRNOTAVAIL; - - memcpy(&b->sockaddr, result->ai_addr, result->ai_addrlen); - b->sockaddr_size = result->ai_addrlen; - - freeaddrinfo(result); - - return 0; -} - -static int parse_exec_address(sd_bus *b, const char **p, char **guid) { - char *path = NULL; - unsigned n_argv = 0, j; - char **argv = NULL; - size_t allocated = 0; - int r; - - assert(b); - assert(p); - assert(*p); - assert(guid); - - while (**p != 0 && **p != ';') { - r = parse_address_key(p, "guid", guid); - if (r < 0) - goto fail; - else if (r > 0) - continue; - - r = parse_address_key(p, "path", &path); - if (r < 0) - goto fail; - else if (r > 0) - continue; - - if (startswith(*p, "argv")) { - unsigned ul; - - errno = 0; - ul = strtoul(*p + 4, (char**) p, 10); - if (errno > 0 || **p != '=' || ul > 256) { - r = -EINVAL; - goto fail; - } - - (*p) ++; - - if (ul >= n_argv) { - if (!GREEDY_REALLOC0(argv, allocated, ul + 2)) { - r = -ENOMEM; - goto fail; - } - - n_argv = ul + 1; - } - - r = parse_address_key(p, NULL, argv + ul); - if (r < 0) - goto fail; - - continue; - } - - skip_address_key(p); - } - - if (!path) { - r = -EINVAL; - goto fail; - } - - /* Make sure there are no holes in the array, with the - * exception of argv[0] */ - for (j = 1; j < n_argv; j++) - if (!argv[j]) { - r = -EINVAL; - goto fail; - } - - if (argv && argv[0] == NULL) { - argv[0] = strdup(path); - if (!argv[0]) { - r = -ENOMEM; - goto fail; - } - } - - b->exec_path = path; - b->exec_argv = argv; - return 0; - -fail: - for (j = 0; j < n_argv; j++) - free(argv[j]); - - free(argv); - free(path); - return r; -} - -static int parse_kernel_address(sd_bus *b, const char **p, char **guid) { - _cleanup_free_ char *path = NULL; - int r; - - assert(b); - assert(p); - assert(*p); - assert(guid); - - while (**p != 0 && **p != ';') { - r = parse_address_key(p, "guid", guid); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "path", &path); - if (r < 0) - return r; - else if (r > 0) - continue; - - skip_address_key(p); - } - - if (!path) - return -EINVAL; - - free(b->kernel); - b->kernel = path; - path = NULL; - - return 0; -} - -static int parse_container_unix_address(sd_bus *b, const char **p, char **guid) { - _cleanup_free_ char *machine = NULL, *pid = NULL; - int r; - - assert(b); - assert(p); - assert(*p); - assert(guid); - - while (**p != 0 && **p != ';') { - r = parse_address_key(p, "guid", guid); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "machine", &machine); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "pid", &pid); - if (r < 0) - return r; - else if (r > 0) - continue; - - skip_address_key(p); - } - - if (!machine == !pid) - return -EINVAL; - - if (machine) { - if (!machine_name_is_valid(machine)) - return -EINVAL; - - free(b->machine); - b->machine = machine; - machine = NULL; - } else { - b->machine = mfree(b->machine); - } - - if (pid) { - r = parse_pid(pid, &b->nspid); - if (r < 0) - return r; - } else - b->nspid = 0; - - b->sockaddr.un.sun_family = AF_UNIX; - strncpy(b->sockaddr.un.sun_path, "/var/run/dbus/system_bus_socket", sizeof(b->sockaddr.un.sun_path)); - b->sockaddr_size = offsetof(struct sockaddr_un, sun_path) + strlen("/var/run/dbus/system_bus_socket"); - - return 0; -} - -static int parse_container_kernel_address(sd_bus *b, const char **p, char **guid) { - _cleanup_free_ char *machine = NULL, *pid = NULL; - int r; - - assert(b); - assert(p); - assert(*p); - assert(guid); - - while (**p != 0 && **p != ';') { - r = parse_address_key(p, "guid", guid); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "machine", &machine); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_address_key(p, "pid", &pid); - if (r < 0) - return r; - else if (r > 0) - continue; - - skip_address_key(p); - } - - if (!machine == !pid) - return -EINVAL; - - if (machine) { - if (!machine_name_is_valid(machine)) - return -EINVAL; - - free(b->machine); - b->machine = machine; - machine = NULL; - } else { - b->machine = mfree(b->machine); - } - - if (pid) { - r = parse_pid(pid, &b->nspid); - if (r < 0) - return r; - } else - b->nspid = 0; - - r = free_and_strdup(&b->kernel, "/sys/fs/kdbus/0-system/bus"); - if (r < 0) - return r; - - return 0; -} - -static void bus_reset_parsed_address(sd_bus *b) { - assert(b); - - zero(b->sockaddr); - b->sockaddr_size = 0; - b->exec_argv = strv_free(b->exec_argv); - b->exec_path = mfree(b->exec_path); - b->server_id = SD_ID128_NULL; - b->kernel = mfree(b->kernel); - b->machine = mfree(b->machine); - b->nspid = 0; -} - -static int bus_parse_next_address(sd_bus *b) { - _cleanup_free_ char *guid = NULL; - const char *a; - int r; - - assert(b); - - if (!b->address) - return 0; - if (b->address[b->address_index] == 0) - return 0; - - bus_reset_parsed_address(b); - - a = b->address + b->address_index; - - while (*a != 0) { - - if (*a == ';') { - a++; - continue; - } - - if (startswith(a, "unix:")) { - a += 5; - - r = parse_unix_address(b, &a, &guid); - if (r < 0) - return r; - break; - - } else if (startswith(a, "tcp:")) { - - a += 4; - r = parse_tcp_address(b, &a, &guid); - if (r < 0) - return r; - - break; - - } else if (startswith(a, "unixexec:")) { - - a += 9; - r = parse_exec_address(b, &a, &guid); - if (r < 0) - return r; - - break; - - } else if (startswith(a, "kernel:")) { - - a += 7; - r = parse_kernel_address(b, &a, &guid); - if (r < 0) - return r; - - break; - } else if (startswith(a, "x-machine-unix:")) { - - a += 15; - r = parse_container_unix_address(b, &a, &guid); - if (r < 0) - return r; - - break; - } else if (startswith(a, "x-machine-kernel:")) { - - a += 17; - r = parse_container_kernel_address(b, &a, &guid); - if (r < 0) - return r; - - break; - } - - a = strchr(a, ';'); - if (!a) - return 0; - } - - if (guid) { - r = sd_id128_from_string(guid, &b->server_id); - if (r < 0) - return r; - } - - b->address_index = a - b->address; - return 1; -} - -static int bus_start_address(sd_bus *b) { - bool container_kdbus_available = false; - bool kdbus_available = false; - int r; - - assert(b); - - for (;;) { - bool skipped = false; - - bus_close_fds(b); - - /* - * Usually, if you provide multiple different bus-addresses, we - * try all of them in order. We use the first one that - * succeeds. However, if you mix kernel and unix addresses, we - * never try unix-addresses if a previous kernel address was - * tried and kdbus was available. This is required to prevent - * clients to fallback to the bus-proxy if kdbus is available - * but failed (eg., too many connections). - */ - - if (b->exec_path) - r = bus_socket_exec(b); - else if ((b->nspid > 0 || b->machine) && b->kernel) { - r = bus_container_connect_kernel(b); - if (r < 0 && !IN_SET(r, -ENOENT, -ESOCKTNOSUPPORT)) - container_kdbus_available = true; - - } else if ((b->nspid > 0 || b->machine) && b->sockaddr.sa.sa_family != AF_UNSPEC) { - if (!container_kdbus_available) - r = bus_container_connect_socket(b); - else - skipped = true; - - } else if (b->kernel) { - r = bus_kernel_connect(b); - if (r < 0 && !IN_SET(r, -ENOENT, -ESOCKTNOSUPPORT)) - kdbus_available = true; - - } else if (b->sockaddr.sa.sa_family != AF_UNSPEC) { - if (!kdbus_available) - r = bus_socket_connect(b); - else - skipped = true; - } else - skipped = true; - - if (!skipped) { - if (r >= 0) { - r = attach_io_events(b); - if (r >= 0) - return r; - } - - b->last_connect_error = -r; - } - - r = bus_parse_next_address(b); - if (r < 0) - return r; - if (r == 0) - return b->last_connect_error ? -b->last_connect_error : -ECONNREFUSED; - } -} - -int bus_next_address(sd_bus *b) { - assert(b); - - bus_reset_parsed_address(b); - return bus_start_address(b); -} - -static int bus_start_fd(sd_bus *b) { - struct stat st; - int r; - - assert(b); - assert(b->input_fd >= 0); - assert(b->output_fd >= 0); - - r = fd_nonblock(b->input_fd, true); - if (r < 0) - return r; - - r = fd_cloexec(b->input_fd, true); - if (r < 0) - return r; - - if (b->input_fd != b->output_fd) { - r = fd_nonblock(b->output_fd, true); - if (r < 0) - return r; - - r = fd_cloexec(b->output_fd, true); - if (r < 0) - return r; - } - - if (fstat(b->input_fd, &st) < 0) - return -errno; - - if (S_ISCHR(b->input_fd)) - return bus_kernel_take_fd(b); - else - return bus_socket_take_fd(b); -} - -_public_ int sd_bus_start(sd_bus *bus) { - int r; - - assert_return(bus, -EINVAL); - assert_return(bus->state == BUS_UNSET, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - bus->state = BUS_OPENING; - - if (bus->is_server && bus->bus_client) - return -EINVAL; - - if (bus->input_fd >= 0) - r = bus_start_fd(bus); - else if (bus->address || bus->sockaddr.sa.sa_family != AF_UNSPEC || bus->exec_path || bus->kernel || bus->machine) - r = bus_start_address(bus); - else - return -EINVAL; - - if (r < 0) { - sd_bus_close(bus); - return r; - } - - return bus_send_hello(bus); -} - -_public_ int sd_bus_open(sd_bus **ret) { - const char *e; - sd_bus *b; - int r; - - assert_return(ret, -EINVAL); - - /* Let's connect to the starter bus if it is set, and - * otherwise to the bus that is appropropriate for the scope - * we are running in */ - - e = secure_getenv("DBUS_STARTER_BUS_TYPE"); - if (e) { - if (streq(e, "system")) - return sd_bus_open_system(ret); - else if (STR_IN_SET(e, "session", "user")) - return sd_bus_open_user(ret); - } - - e = secure_getenv("DBUS_STARTER_ADDRESS"); - if (!e) { - if (cg_pid_get_owner_uid(0, NULL) >= 0) - return sd_bus_open_user(ret); - else - return sd_bus_open_system(ret); - } - - r = sd_bus_new(&b); - if (r < 0) - return r; - - r = sd_bus_set_address(b, e); - if (r < 0) - goto fail; - - b->bus_client = true; - - /* We don't know whether the bus is trusted or not, so better - * be safe, and authenticate everything */ - b->trusted = false; - b->attach_flags |= KDBUS_ATTACH_CAPS | KDBUS_ATTACH_CREDS; - b->creds_mask |= SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_EFFECTIVE_CAPS; - - r = sd_bus_start(b); - if (r < 0) - goto fail; - - *ret = b; - return 0; - -fail: - bus_free(b); - return r; -} - -int bus_set_address_system(sd_bus *b) { - const char *e; - assert(b); - - e = secure_getenv("DBUS_SYSTEM_BUS_ADDRESS"); - if (e) - return sd_bus_set_address(b, e); - - return sd_bus_set_address(b, DEFAULT_SYSTEM_BUS_ADDRESS); -} - -_public_ int sd_bus_open_system(sd_bus **ret) { - sd_bus *b; - int r; - - assert_return(ret, -EINVAL); - - r = sd_bus_new(&b); - if (r < 0) - return r; - - r = bus_set_address_system(b); - if (r < 0) - goto fail; - - b->bus_client = true; - b->is_system = true; - - /* Let's do per-method access control on the system bus. We - * need the caller's UID and capability set for that. */ - b->trusted = false; - b->attach_flags |= KDBUS_ATTACH_CAPS | KDBUS_ATTACH_CREDS; - b->creds_mask |= SD_BUS_CREDS_UID | SD_BUS_CREDS_EUID | SD_BUS_CREDS_EFFECTIVE_CAPS; - - r = sd_bus_start(b); - if (r < 0) - goto fail; - - *ret = b; - return 0; - -fail: - bus_free(b); - return r; -} - -int bus_set_address_user(sd_bus *b) { - const char *e; - uid_t uid; - int r; - - assert(b); - - e = secure_getenv("DBUS_SESSION_BUS_ADDRESS"); - if (e) - return sd_bus_set_address(b, e); - - r = cg_pid_get_owner_uid(0, &uid); - if (r < 0) - uid = getuid(); - - e = secure_getenv("XDG_RUNTIME_DIR"); - if (e) { - _cleanup_free_ char *ee = NULL; - - ee = bus_address_escape(e); - if (!ee) - return -ENOMEM; - - (void) asprintf(&b->address, KERNEL_USER_BUS_ADDRESS_FMT ";" UNIX_USER_BUS_ADDRESS_FMT, uid, ee); - } else - (void) asprintf(&b->address, KERNEL_USER_BUS_ADDRESS_FMT, uid); - - if (!b->address) - return -ENOMEM; - - return 0; -} - -_public_ int sd_bus_open_user(sd_bus **ret) { - sd_bus *b; - int r; - - assert_return(ret, -EINVAL); - - r = sd_bus_new(&b); - if (r < 0) - return r; - - r = bus_set_address_user(b); - if (r < 0) - return r; - - b->bus_client = true; - b->is_user = true; - - /* We don't do any per-method access control on the user - * bus. */ - b->trusted = true; - - r = sd_bus_start(b); - if (r < 0) - goto fail; - - *ret = b; - return 0; - -fail: - bus_free(b); - return r; -} - -int bus_set_address_system_remote(sd_bus *b, const char *host) { - _cleanup_free_ char *e = NULL; - char *m = NULL, *c = NULL; - - assert(b); - assert(host); - - /* Let's see if we shall enter some container */ - m = strchr(host, ':'); - if (m) { - m++; - - /* Let's make sure this is not a port of some kind, - * and is a valid machine name. */ - if (!in_charset(m, "0123456789") && machine_name_is_valid(m)) { - char *t; - - /* Cut out the host part */ - t = strndupa(host, m - host - 1); - e = bus_address_escape(t); - if (!e) - return -ENOMEM; - - c = strjoina(",argv4=--machine=", m); - } - } - - if (!e) { - e = bus_address_escape(host); - if (!e) - return -ENOMEM; - } - - b->address = strjoin("unixexec:path=ssh,argv1=-xT,argv2=", e, ",argv3=systemd-stdio-bridge", c, NULL); - if (!b->address) - return -ENOMEM; - - return 0; - } - -_public_ int sd_bus_open_system_remote(sd_bus **ret, const char *host) { - sd_bus *bus; - int r; - - assert_return(host, -EINVAL); - assert_return(ret, -EINVAL); - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - r = bus_set_address_system_remote(bus, host); - if (r < 0) - goto fail; - - bus->bus_client = true; - bus->trusted = false; - bus->is_system = true; - - r = sd_bus_start(bus); - if (r < 0) - goto fail; - - *ret = bus; - return 0; - -fail: - bus_free(bus); - return r; -} - -int bus_set_address_system_machine(sd_bus *b, const char *machine) { - _cleanup_free_ char *e = NULL; - - assert(b); - assert(machine); - - e = bus_address_escape(machine); - if (!e) - return -ENOMEM; - - b->address = strjoin("x-machine-kernel:machine=", e, ";x-machine-unix:machine=", e, NULL); - if (!b->address) - return -ENOMEM; - - return 0; -} - -_public_ int sd_bus_open_system_machine(sd_bus **ret, const char *machine) { - sd_bus *bus; - int r; - - assert_return(machine, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(machine_name_is_valid(machine), -EINVAL); - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - r = bus_set_address_system_machine(bus, machine); - if (r < 0) - goto fail; - - bus->bus_client = true; - bus->trusted = false; - bus->is_system = true; - - r = sd_bus_start(bus); - if (r < 0) - goto fail; - - *ret = bus; - return 0; - -fail: - bus_free(bus); - return r; -} - -_public_ void sd_bus_close(sd_bus *bus) { - - if (!bus) - return; - if (bus->state == BUS_CLOSED) - return; - if (bus_pid_changed(bus)) - return; - - bus->state = BUS_CLOSED; - - sd_bus_detach_event(bus); - - /* Drop all queued messages so that they drop references to - * the bus object and the bus may be freed */ - bus_reset_queues(bus); - - if (!bus->is_kernel) - bus_close_fds(bus); - - /* We'll leave the fd open in case this is a kernel bus, since - * there might still be memblocks around that reference this - * bus, and they might need to invoke the KDBUS_CMD_FREE - * ioctl on the fd when they are freed. */ -} - -_public_ sd_bus* sd_bus_flush_close_unref(sd_bus *bus) { - - if (!bus) - return NULL; - - sd_bus_flush(bus); - sd_bus_close(bus); - - return sd_bus_unref(bus); -} - -static void bus_enter_closing(sd_bus *bus) { - assert(bus); - - if (bus->state != BUS_OPENING && - bus->state != BUS_AUTHENTICATING && - bus->state != BUS_HELLO && - bus->state != BUS_RUNNING) - return; - - bus->state = BUS_CLOSING; -} - -_public_ sd_bus *sd_bus_ref(sd_bus *bus) { - - if (!bus) - return NULL; - - assert_se(REFCNT_INC(bus->n_ref) >= 2); - - return bus; -} - -_public_ sd_bus *sd_bus_unref(sd_bus *bus) { - unsigned i; - - if (!bus) - return NULL; - - i = REFCNT_DEC(bus->n_ref); - if (i > 0) - return NULL; - - bus_free(bus); - return NULL; -} - -_public_ int sd_bus_is_open(sd_bus *bus) { - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return BUS_IS_OPEN(bus->state); -} - -_public_ int sd_bus_can_send(sd_bus *bus, char type) { - int r; - - assert_return(bus, -EINVAL); - assert_return(bus->state != BUS_UNSET, -ENOTCONN); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (bus->hello_flags & KDBUS_HELLO_MONITOR) - return 0; - - if (type == SD_BUS_TYPE_UNIX_FD) { - if (!(bus->hello_flags & KDBUS_HELLO_ACCEPT_FD)) - return 0; - - r = bus_ensure_running(bus); - if (r < 0) - return r; - - return bus->can_fds; - } - - return bus_type_is_valid(type); -} - -_public_ int sd_bus_get_bus_id(sd_bus *bus, sd_id128_t *id) { - int r; - - assert_return(bus, -EINVAL); - assert_return(id, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - r = bus_ensure_running(bus); - if (r < 0) - return r; - - *id = bus->server_id; - return 0; -} - -static int bus_seal_message(sd_bus *b, sd_bus_message *m, usec_t timeout) { - assert(b); - assert(m); - - if (m->sealed) { - /* If we copy the same message to multiple - * destinations, avoid using the same cookie - * numbers. */ - b->cookie = MAX(b->cookie, BUS_MESSAGE_COOKIE(m)); - return 0; - } - - if (timeout == 0) - timeout = BUS_DEFAULT_TIMEOUT; - - return bus_message_seal(m, ++b->cookie, timeout); -} - -static int bus_remarshal_message(sd_bus *b, sd_bus_message **m) { - bool remarshal = false; - - assert(b); - - /* wrong packet version */ - if (b->message_version != 0 && b->message_version != (*m)->header->version) - remarshal = true; - - /* wrong packet endianness */ - if (b->message_endian != 0 && b->message_endian != (*m)->header->endian) - remarshal = true; - - /* TODO: kdbus-messages received from the kernel contain data which is - * not allowed to be passed to KDBUS_CMD_SEND. Therefore, we have to - * force remarshaling of the message. Technically, we could just - * recreate the kdbus message, but that is non-trivial as other parts of - * the message refer to m->kdbus already. This should be fixed! */ - if ((*m)->kdbus && (*m)->release_kdbus) - remarshal = true; - - return remarshal ? bus_message_remarshal(b, m) : 0; -} - -int bus_seal_synthetic_message(sd_bus *b, sd_bus_message *m) { - assert(b); - assert(m); - - /* Fake some timestamps, if they were requested, and not - * already initialized */ - if (b->attach_flags & KDBUS_ATTACH_TIMESTAMP) { - if (m->realtime <= 0) - m->realtime = now(CLOCK_REALTIME); - - if (m->monotonic <= 0) - m->monotonic = now(CLOCK_MONOTONIC); - } - - /* The bus specification says the serial number cannot be 0, - * hence let's fill something in for synthetic messages. Since - * synthetic messages might have a fake sender and we don't - * want to interfere with the real sender's serial numbers we - * pick a fixed, artificial one. We use (uint32_t) -1 rather - * than (uint64_t) -1 since dbus1 only had 32bit identifiers, - * even though kdbus can do 64bit. */ - return bus_message_seal(m, 0xFFFFFFFFULL, 0); -} - -static int bus_write_message(sd_bus *bus, sd_bus_message *m, bool hint_sync_call, size_t *idx) { - int r; - - assert(bus); - assert(m); - - if (bus->is_kernel) - r = bus_kernel_write_message(bus, m, hint_sync_call); - else - r = bus_socket_write_message(bus, m, idx); - - if (r <= 0) - return r; - - if (bus->is_kernel || *idx >= BUS_MESSAGE_SIZE(m)) - log_debug("Sent message type=%s sender=%s destination=%s object=%s interface=%s member=%s cookie=%" PRIu64 " reply_cookie=%" PRIu64 " error=%s", - bus_message_type_to_string(m->header->type), - strna(sd_bus_message_get_sender(m)), - strna(sd_bus_message_get_destination(m)), - strna(sd_bus_message_get_path(m)), - strna(sd_bus_message_get_interface(m)), - strna(sd_bus_message_get_member(m)), - BUS_MESSAGE_COOKIE(m), - m->reply_cookie, - strna(m->error.message)); - - return r; -} - -static int dispatch_wqueue(sd_bus *bus) { - int r, ret = 0; - - assert(bus); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - while (bus->wqueue_size > 0) { - - r = bus_write_message(bus, bus->wqueue[0], false, &bus->windex); - if (r < 0) - return r; - else if (r == 0) - /* Didn't do anything this time */ - return ret; - else if (bus->is_kernel || bus->windex >= BUS_MESSAGE_SIZE(bus->wqueue[0])) { - /* Fully written. Let's drop the entry from - * the queue. - * - * This isn't particularly optimized, but - * well, this is supposed to be our worst-case - * buffer only, and the socket buffer is - * supposed to be our primary buffer, and if - * it got full, then all bets are off - * anyway. */ - - bus->wqueue_size --; - sd_bus_message_unref(bus->wqueue[0]); - memmove(bus->wqueue, bus->wqueue + 1, sizeof(sd_bus_message*) * bus->wqueue_size); - bus->windex = 0; - - ret = 1; - } - } - - return ret; -} - -static int bus_read_message(sd_bus *bus, bool hint_priority, int64_t priority) { - assert(bus); - - if (bus->is_kernel) - return bus_kernel_read_message(bus, hint_priority, priority); - else - return bus_socket_read_message(bus); -} - -int bus_rqueue_make_room(sd_bus *bus) { - assert(bus); - - if (bus->rqueue_size >= BUS_RQUEUE_MAX) - return -ENOBUFS; - - if (!GREEDY_REALLOC(bus->rqueue, bus->rqueue_allocated, bus->rqueue_size + 1)) - return -ENOMEM; - - return 0; -} - -static int dispatch_rqueue(sd_bus *bus, bool hint_priority, int64_t priority, sd_bus_message **m) { - int r, ret = 0; - - assert(bus); - assert(m); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - /* Note that the priority logic is only available on kdbus, - * where the rqueue is unused. We check the rqueue here - * anyway, because it's simple... */ - - for (;;) { - if (bus->rqueue_size > 0) { - /* Dispatch a queued message */ - - *m = bus->rqueue[0]; - bus->rqueue_size --; - memmove(bus->rqueue, bus->rqueue + 1, sizeof(sd_bus_message*) * bus->rqueue_size); - return 1; - } - - /* Try to read a new message */ - r = bus_read_message(bus, hint_priority, priority); - if (r < 0) - return r; - if (r == 0) - return ret; - - ret = 1; - } -} - -static int bus_send_internal(sd_bus *bus, sd_bus_message *_m, uint64_t *cookie, bool hint_sync_call) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); - int r; - - assert_return(m, -EINVAL); - - if (!bus) - bus = m->bus; - - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(!bus->is_kernel || !(bus->hello_flags & KDBUS_HELLO_MONITOR), -EROFS); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (m->n_fds > 0) { - r = sd_bus_can_send(bus, SD_BUS_TYPE_UNIX_FD); - if (r < 0) - return r; - if (r == 0) - return -EOPNOTSUPP; - } - - /* If the cookie number isn't kept, then we know that no reply - * is expected */ - if (!cookie && !m->sealed) - m->header->flags |= BUS_MESSAGE_NO_REPLY_EXPECTED; - - r = bus_seal_message(bus, m, 0); - if (r < 0) - return r; - - /* Remarshall if we have to. This will possibly unref the - * message and place a replacement in m */ - r = bus_remarshal_message(bus, &m); - if (r < 0) - return r; - - /* If this is a reply and no reply was requested, then let's - * suppress this, if we can */ - if (m->dont_send) - goto finish; - - if ((bus->state == BUS_RUNNING || bus->state == BUS_HELLO) && bus->wqueue_size <= 0) { - size_t idx = 0; - - r = bus_write_message(bus, m, hint_sync_call, &idx); - if (r < 0) { - if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { - bus_enter_closing(bus); - return -ECONNRESET; - } - - return r; - } - - if (!bus->is_kernel && idx < BUS_MESSAGE_SIZE(m)) { - /* Wasn't fully written. So let's remember how - * much was written. Note that the first entry - * of the wqueue array is always allocated so - * that we always can remember how much was - * written. */ - bus->wqueue[0] = sd_bus_message_ref(m); - bus->wqueue_size = 1; - bus->windex = idx; - } - - } else { - /* Just append it to the queue. */ - - if (bus->wqueue_size >= BUS_WQUEUE_MAX) - return -ENOBUFS; - - if (!GREEDY_REALLOC(bus->wqueue, bus->wqueue_allocated, bus->wqueue_size + 1)) - return -ENOMEM; - - bus->wqueue[bus->wqueue_size ++] = sd_bus_message_ref(m); - } - -finish: - if (cookie) - *cookie = BUS_MESSAGE_COOKIE(m); - - return 1; -} - -_public_ int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie) { - return bus_send_internal(bus, m, cookie, false); -} - -_public_ int sd_bus_send_to(sd_bus *bus, sd_bus_message *m, const char *destination, uint64_t *cookie) { - int r; - - assert_return(m, -EINVAL); - - if (!bus) - bus = m->bus; - - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (!streq_ptr(m->destination, destination)) { - - if (!destination) - return -EEXIST; - - r = sd_bus_message_set_destination(m, destination); - if (r < 0) - return r; - } - - return sd_bus_send(bus, m, cookie); -} - -static usec_t calc_elapse(uint64_t usec) { - if (usec == (uint64_t) -1) - return 0; - - return now(CLOCK_MONOTONIC) + usec; -} - -static int timeout_compare(const void *a, const void *b) { - const struct reply_callback *x = a, *y = b; - - if (x->timeout != 0 && y->timeout == 0) - return -1; - - if (x->timeout == 0 && y->timeout != 0) - return 1; - - if (x->timeout < y->timeout) - return -1; - - if (x->timeout > y->timeout) - return 1; - - return 0; -} - -_public_ int sd_bus_call_async( - sd_bus *bus, - sd_bus_slot **slot, - sd_bus_message *_m, - sd_bus_message_handler_t callback, - void *userdata, - uint64_t usec) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *s = NULL; - int r; - - assert_return(m, -EINVAL); - assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL); - assert_return(!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED), -EINVAL); - assert_return(callback, -EINVAL); - - if (!bus) - bus = m->bus; - - assert_return(!bus_pid_changed(bus), -ECHILD); - assert_return(!bus->is_kernel || !(bus->hello_flags & KDBUS_HELLO_MONITOR), -EROFS); - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - r = ordered_hashmap_ensure_allocated(&bus->reply_callbacks, &uint64_hash_ops); - if (r < 0) - return r; - - r = prioq_ensure_allocated(&bus->reply_callbacks_prioq, timeout_compare); - if (r < 0) - return r; - - r = bus_seal_message(bus, m, usec); - if (r < 0) - return r; - - r = bus_remarshal_message(bus, &m); - if (r < 0) - return r; - - s = bus_slot_allocate(bus, !slot, BUS_REPLY_CALLBACK, sizeof(struct reply_callback), userdata); - if (!s) - return -ENOMEM; - - s->reply_callback.callback = callback; - - s->reply_callback.cookie = BUS_MESSAGE_COOKIE(m); - r = ordered_hashmap_put(bus->reply_callbacks, &s->reply_callback.cookie, &s->reply_callback); - if (r < 0) { - s->reply_callback.cookie = 0; - return r; - } - - s->reply_callback.timeout = calc_elapse(m->timeout); - if (s->reply_callback.timeout != 0) { - r = prioq_put(bus->reply_callbacks_prioq, &s->reply_callback, &s->reply_callback.prioq_idx); - if (r < 0) { - s->reply_callback.timeout = 0; - return r; - } - } - - r = sd_bus_send(bus, m, &s->reply_callback.cookie); - if (r < 0) - return r; - - if (slot) - *slot = s; - s = NULL; - - return r; -} - -int bus_ensure_running(sd_bus *bus) { - int r; - - assert(bus); - - if (bus->state == BUS_UNSET || bus->state == BUS_CLOSED || bus->state == BUS_CLOSING) - return -ENOTCONN; - if (bus->state == BUS_RUNNING) - return 1; - - for (;;) { - r = sd_bus_process(bus, NULL); - if (r < 0) - return r; - if (bus->state == BUS_RUNNING) - return 1; - if (r > 0) - continue; - - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) - return r; - } -} - -_public_ int sd_bus_call( - sd_bus *bus, - sd_bus_message *_m, - uint64_t usec, - sd_bus_error *error, - sd_bus_message **reply) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = sd_bus_message_ref(_m); - usec_t timeout; - uint64_t cookie; - unsigned i; - int r; - - bus_assert_return(m, -EINVAL, error); - bus_assert_return(m->header->type == SD_BUS_MESSAGE_METHOD_CALL, -EINVAL, error); - bus_assert_return(!(m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED), -EINVAL, error); - bus_assert_return(!bus_error_is_dirty(error), -EINVAL, error); - - if (!bus) - bus = m->bus; - - bus_assert_return(!bus_pid_changed(bus), -ECHILD, error); - bus_assert_return(!bus->is_kernel || !(bus->hello_flags & KDBUS_HELLO_MONITOR), -EROFS, error); - - if (!BUS_IS_OPEN(bus->state)) { - r = -ENOTCONN; - goto fail; - } - - r = bus_ensure_running(bus); - if (r < 0) - goto fail; - - i = bus->rqueue_size; - - r = bus_seal_message(bus, m, usec); - if (r < 0) - goto fail; - - r = bus_remarshal_message(bus, &m); - if (r < 0) - goto fail; - - r = bus_send_internal(bus, m, &cookie, true); - if (r < 0) - goto fail; - - timeout = calc_elapse(m->timeout); - - for (;;) { - usec_t left; - - while (i < bus->rqueue_size) { - sd_bus_message *incoming = NULL; - - incoming = bus->rqueue[i]; - - if (incoming->reply_cookie == cookie) { - /* Found a match! */ - - memmove(bus->rqueue + i, bus->rqueue + i + 1, sizeof(sd_bus_message*) * (bus->rqueue_size - i - 1)); - bus->rqueue_size--; - log_debug_bus_message(incoming); - - if (incoming->header->type == SD_BUS_MESSAGE_METHOD_RETURN) { - - if (incoming->n_fds <= 0 || (bus->hello_flags & KDBUS_HELLO_ACCEPT_FD)) { - if (reply) - *reply = incoming; - else - sd_bus_message_unref(incoming); - - return 1; - } - - r = sd_bus_error_setf(error, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Reply message contained file descriptors which I couldn't accept. Sorry."); - sd_bus_message_unref(incoming); - return r; - - } else if (incoming->header->type == SD_BUS_MESSAGE_METHOD_ERROR) { - r = sd_bus_error_copy(error, &incoming->error); - sd_bus_message_unref(incoming); - return r; - } else { - r = -EIO; - goto fail; - } - - } else if (BUS_MESSAGE_COOKIE(incoming) == cookie && - bus->unique_name && - incoming->sender && - streq(bus->unique_name, incoming->sender)) { - - memmove(bus->rqueue + i, bus->rqueue + i + 1, sizeof(sd_bus_message*) * (bus->rqueue_size - i - 1)); - bus->rqueue_size--; - - /* Our own message? Somebody is trying - * to send its own client a message, - * let's not dead-lock, let's fail - * immediately. */ - - sd_bus_message_unref(incoming); - r = -ELOOP; - goto fail; - } - - /* Try to read more, right-away */ - i++; - } - - r = bus_read_message(bus, false, 0); - if (r < 0) { - if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { - bus_enter_closing(bus); - r = -ECONNRESET; - } - - goto fail; - } - if (r > 0) - continue; - - if (timeout > 0) { - usec_t n; - - n = now(CLOCK_MONOTONIC); - if (n >= timeout) { - r = -ETIMEDOUT; - goto fail; - } - - left = timeout - n; - } else - left = (uint64_t) -1; - - r = bus_poll(bus, true, left); - if (r < 0) - goto fail; - if (r == 0) { - r = -ETIMEDOUT; - goto fail; - } - - r = dispatch_wqueue(bus); - if (r < 0) { - if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { - bus_enter_closing(bus); - r = -ECONNRESET; - } - - goto fail; - } - } - -fail: - return sd_bus_error_set_errno(error, r); -} - -_public_ int sd_bus_get_fd(sd_bus *bus) { - - assert_return(bus, -EINVAL); - assert_return(bus->input_fd == bus->output_fd, -EPERM); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return bus->input_fd; -} - -_public_ int sd_bus_get_events(sd_bus *bus) { - int flags = 0; - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state) && bus->state != BUS_CLOSING) - return -ENOTCONN; - - if (bus->state == BUS_OPENING) - flags |= POLLOUT; - else if (bus->state == BUS_AUTHENTICATING) { - - if (bus_socket_auth_needs_write(bus)) - flags |= POLLOUT; - - flags |= POLLIN; - - } else if (bus->state == BUS_RUNNING || bus->state == BUS_HELLO) { - if (bus->rqueue_size <= 0) - flags |= POLLIN; - if (bus->wqueue_size > 0) - flags |= POLLOUT; - } - - return flags; -} - -_public_ int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec) { - struct reply_callback *c; - - assert_return(bus, -EINVAL); - assert_return(timeout_usec, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!BUS_IS_OPEN(bus->state) && bus->state != BUS_CLOSING) - return -ENOTCONN; - - if (bus->track_queue) { - *timeout_usec = 0; - return 1; - } - - if (bus->state == BUS_CLOSING) { - *timeout_usec = 0; - return 1; - } - - if (bus->state == BUS_AUTHENTICATING) { - *timeout_usec = bus->auth_timeout; - return 1; - } - - if (bus->state != BUS_RUNNING && bus->state != BUS_HELLO) { - *timeout_usec = (uint64_t) -1; - return 0; - } - - if (bus->rqueue_size > 0) { - *timeout_usec = 0; - return 1; - } - - c = prioq_peek(bus->reply_callbacks_prioq); - if (!c) { - *timeout_usec = (uint64_t) -1; - return 0; - } - - if (c->timeout == 0) { - *timeout_usec = (uint64_t) -1; - return 0; - } - - *timeout_usec = c->timeout; - return 1; -} - -static int process_timeout(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message* m = NULL; - struct reply_callback *c; - sd_bus_slot *slot; - usec_t n; - int r; - - assert(bus); - - c = prioq_peek(bus->reply_callbacks_prioq); - if (!c) - return 0; - - n = now(CLOCK_MONOTONIC); - if (c->timeout > n) - return 0; - - r = bus_message_new_synthetic_error( - bus, - c->cookie, - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Method call timed out"), - &m); - if (r < 0) - return r; - - r = bus_seal_synthetic_message(bus, m); - if (r < 0) - return r; - - assert_se(prioq_pop(bus->reply_callbacks_prioq) == c); - c->timeout = 0; - - ordered_hashmap_remove(bus->reply_callbacks, &c->cookie); - c->cookie = 0; - - slot = container_of(c, sd_bus_slot, reply_callback); - - bus->iteration_counter ++; - - bus->current_message = m; - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = c->callback; - bus->current_userdata = slot->userdata; - r = c->callback(m, slot->userdata, &error_buffer); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = NULL; - bus->current_message = NULL; - - if (slot->floating) { - bus_slot_disconnect(slot); - sd_bus_slot_unref(slot); - } - - sd_bus_slot_unref(slot); - - return bus_maybe_reply_error(m, r, &error_buffer); -} - -static int process_hello(sd_bus *bus, sd_bus_message *m) { - assert(bus); - assert(m); - - if (bus->state != BUS_HELLO) - return 0; - - /* Let's make sure the first message on the bus is the HELLO - * reply. But note that we don't actually parse the message - * here (we leave that to the usual handling), we just verify - * we don't let any earlier msg through. */ - - if (m->header->type != SD_BUS_MESSAGE_METHOD_RETURN && - m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) - return -EIO; - - if (m->reply_cookie != 1) - return -EIO; - - return 0; -} - -static int process_reply(sd_bus *bus, sd_bus_message *m) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *synthetic_reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - struct reply_callback *c; - sd_bus_slot *slot; - int r; - - assert(bus); - assert(m); - - if (m->header->type != SD_BUS_MESSAGE_METHOD_RETURN && - m->header->type != SD_BUS_MESSAGE_METHOD_ERROR) - return 0; - - if (bus->is_kernel && (bus->hello_flags & KDBUS_HELLO_MONITOR)) - return 0; - - if (m->destination && bus->unique_name && !streq_ptr(m->destination, bus->unique_name)) - return 0; - - c = ordered_hashmap_remove(bus->reply_callbacks, &m->reply_cookie); - if (!c) - return 0; - - c->cookie = 0; - - slot = container_of(c, sd_bus_slot, reply_callback); - - if (m->n_fds > 0 && !(bus->hello_flags & KDBUS_HELLO_ACCEPT_FD)) { - - /* If the reply contained a file descriptor which we - * didn't want we pass an error instead. */ - - r = bus_message_new_synthetic_error( - bus, - m->reply_cookie, - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Reply message contained file descriptor"), - &synthetic_reply); - if (r < 0) - return r; - - /* Copy over original timestamp */ - synthetic_reply->realtime = m->realtime; - synthetic_reply->monotonic = m->monotonic; - synthetic_reply->seqnum = m->seqnum; - - r = bus_seal_synthetic_message(bus, synthetic_reply); - if (r < 0) - return r; - - m = synthetic_reply; - } else { - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - } - - if (c->timeout != 0) { - prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); - c->timeout = 0; - } - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = c->callback; - bus->current_userdata = slot->userdata; - r = c->callback(m, slot->userdata, &error_buffer); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = NULL; - - if (slot->floating) { - bus_slot_disconnect(slot); - sd_bus_slot_unref(slot); - } - - sd_bus_slot_unref(slot); - - return bus_maybe_reply_error(m, r, &error_buffer); -} - -static int process_filter(sd_bus *bus, sd_bus_message *m) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - struct filter_callback *l; - int r; - - assert(bus); - assert(m); - - do { - bus->filter_callbacks_modified = false; - - LIST_FOREACH(callbacks, l, bus->filter_callbacks) { - sd_bus_slot *slot; - - if (bus->filter_callbacks_modified) - break; - - /* Don't run this more than once per iteration */ - if (l->last_iteration == bus->iteration_counter) - continue; - - l->last_iteration = bus->iteration_counter; - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - slot = container_of(l, sd_bus_slot, filter_callback); - - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = l->callback; - bus->current_userdata = slot->userdata; - r = l->callback(m, slot->userdata, &error_buffer); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = sd_bus_slot_unref(slot); - - r = bus_maybe_reply_error(m, r, &error_buffer); - if (r != 0) - return r; - - } - - } while (bus->filter_callbacks_modified); - - return 0; -} - -static int process_match(sd_bus *bus, sd_bus_message *m) { - int r; - - assert(bus); - assert(m); - - do { - bus->match_callbacks_modified = false; - - r = bus_match_run(bus, &bus->match_callbacks, m); - if (r != 0) - return r; - - } while (bus->match_callbacks_modified); - - return 0; -} - -static int process_builtin(sd_bus *bus, sd_bus_message *m) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - assert(bus); - assert(m); - - if (bus->hello_flags & KDBUS_HELLO_MONITOR) - return 0; - - if (bus->manual_peer_interface) - return 0; - - if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) - return 0; - - if (!streq_ptr(m->interface, "org.freedesktop.DBus.Peer")) - return 0; - - if (m->header->flags & BUS_MESSAGE_NO_REPLY_EXPECTED) - return 1; - - if (streq_ptr(m->member, "Ping")) - r = sd_bus_message_new_method_return(m, &reply); - else if (streq_ptr(m->member, "GetMachineId")) { - sd_id128_t id; - char sid[33]; - - r = sd_id128_get_machine(&id); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(m, &reply); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "s", sd_id128_to_string(id, sid)); - } else { - r = sd_bus_message_new_method_errorf( - m, &reply, - SD_BUS_ERROR_UNKNOWN_METHOD, - "Unknown method '%s' on interface '%s'.", m->member, m->interface); - } - - if (r < 0) - return r; - - r = sd_bus_send(bus, reply, NULL); - if (r < 0) - return r; - - return 1; -} - -static int process_fd_check(sd_bus *bus, sd_bus_message *m) { - assert(bus); - assert(m); - - /* If we got a message with a file descriptor which we didn't - * want to accept, then let's drop it. How can this even - * happen? For example, when the kernel queues a message into - * an activatable names's queue which allows fds, and then is - * delivered to us later even though we ourselves did not - * negotiate it. */ - - if (bus->hello_flags & KDBUS_HELLO_MONITOR) - return 0; - - if (m->n_fds <= 0) - return 0; - - if (bus->hello_flags & KDBUS_HELLO_ACCEPT_FD) - return 0; - - if (m->header->type != SD_BUS_MESSAGE_METHOD_CALL) - return 1; /* just eat it up */ - - return sd_bus_reply_method_errorf(m, SD_BUS_ERROR_INCONSISTENT_MESSAGE, "Message contains file descriptors, which I cannot accept. Sorry."); -} - -static int process_message(sd_bus *bus, sd_bus_message *m) { - int r; - - assert(bus); - assert(m); - - bus->current_message = m; - bus->iteration_counter++; - - log_debug_bus_message(m); - - r = process_hello(bus, m); - if (r != 0) - goto finish; - - r = process_reply(bus, m); - if (r != 0) - goto finish; - - r = process_fd_check(bus, m); - if (r != 0) - goto finish; - - r = process_filter(bus, m); - if (r != 0) - goto finish; - - r = process_match(bus, m); - if (r != 0) - goto finish; - - r = process_builtin(bus, m); - if (r != 0) - goto finish; - - r = bus_process_object(bus, m); - -finish: - bus->current_message = NULL; - return r; -} - -static int dispatch_track(sd_bus *bus) { - assert(bus); - - if (!bus->track_queue) - return 0; - - bus_track_dispatch(bus->track_queue); - return 1; -} - -static int process_running(sd_bus *bus, bool hint_priority, int64_t priority, sd_bus_message **ret) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - int r; - - assert(bus); - assert(bus->state == BUS_RUNNING || bus->state == BUS_HELLO); - - r = process_timeout(bus); - if (r != 0) - goto null_message; - - r = dispatch_wqueue(bus); - if (r != 0) - goto null_message; - - r = dispatch_track(bus); - if (r != 0) - goto null_message; - - r = dispatch_rqueue(bus, hint_priority, priority, &m); - if (r < 0) - return r; - if (!m) - goto null_message; - - r = process_message(bus, m); - if (r != 0) - goto null_message; - - if (ret) { - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - *ret = m; - m = NULL; - return 1; - } - - if (m->header->type == SD_BUS_MESSAGE_METHOD_CALL) { - - log_debug("Unprocessed message call sender=%s object=%s interface=%s member=%s", - strna(sd_bus_message_get_sender(m)), - strna(sd_bus_message_get_path(m)), - strna(sd_bus_message_get_interface(m)), - strna(sd_bus_message_get_member(m))); - - r = sd_bus_reply_method_errorf( - m, - SD_BUS_ERROR_UNKNOWN_OBJECT, - "Unknown object '%s'.", m->path); - if (r < 0) - return r; - } - - return 1; - -null_message: - if (r >= 0 && ret) - *ret = NULL; - - return r; -} - -static int process_closing(sd_bus *bus, sd_bus_message **ret) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - struct reply_callback *c; - int r; - - assert(bus); - assert(bus->state == BUS_CLOSING); - - c = ordered_hashmap_first(bus->reply_callbacks); - if (c) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - sd_bus_slot *slot; - - /* First, fail all outstanding method calls */ - r = bus_message_new_synthetic_error( - bus, - c->cookie, - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_NO_REPLY, "Connection terminated"), - &m); - if (r < 0) - return r; - - r = bus_seal_synthetic_message(bus, m); - if (r < 0) - return r; - - if (c->timeout != 0) { - prioq_remove(bus->reply_callbacks_prioq, c, &c->prioq_idx); - c->timeout = 0; - } - - ordered_hashmap_remove(bus->reply_callbacks, &c->cookie); - c->cookie = 0; - - slot = container_of(c, sd_bus_slot, reply_callback); - - bus->iteration_counter++; - - bus->current_message = m; - bus->current_slot = sd_bus_slot_ref(slot); - bus->current_handler = c->callback; - bus->current_userdata = slot->userdata; - r = c->callback(m, slot->userdata, &error_buffer); - bus->current_userdata = NULL; - bus->current_handler = NULL; - bus->current_slot = NULL; - bus->current_message = NULL; - - if (slot->floating) { - bus_slot_disconnect(slot); - sd_bus_slot_unref(slot); - } - - sd_bus_slot_unref(slot); - - return bus_maybe_reply_error(m, r, &error_buffer); - } - - /* Then, synthesize a Disconnected message */ - r = sd_bus_message_new_signal( - bus, - &m, - "/org/freedesktop/DBus/Local", - "org.freedesktop.DBus.Local", - "Disconnected"); - if (r < 0) - return r; - - bus_message_set_sender_local(bus, m); - - r = bus_seal_synthetic_message(bus, m); - if (r < 0) - return r; - - sd_bus_close(bus); - - bus->current_message = m; - bus->iteration_counter++; - - r = process_filter(bus, m); - if (r != 0) - goto finish; - - r = process_match(bus, m); - if (r != 0) - goto finish; - - if (ret) { - *ret = m; - m = NULL; - } - - r = 1; - -finish: - bus->current_message = NULL; - - return r; -} - -static int bus_process_internal(sd_bus *bus, bool hint_priority, int64_t priority, sd_bus_message **ret) { - BUS_DONT_DESTROY(bus); - int r; - - /* Returns 0 when we didn't do anything. This should cause the - * caller to invoke sd_bus_wait() before returning the next - * time. Returns > 0 when we did something, which possibly - * means *ret is filled in with an unprocessed message. */ - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - /* We don't allow recursively invoking sd_bus_process(). */ - assert_return(!bus->current_message, -EBUSY); - assert(!bus->current_slot); - - switch (bus->state) { - - case BUS_UNSET: - return -ENOTCONN; - - case BUS_CLOSED: - return -ECONNRESET; - - case BUS_OPENING: - r = bus_socket_process_opening(bus); - if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { - bus_enter_closing(bus); - r = 1; - } else if (r < 0) - return r; - if (ret) - *ret = NULL; - return r; - - case BUS_AUTHENTICATING: - r = bus_socket_process_authenticating(bus); - if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { - bus_enter_closing(bus); - r = 1; - } else if (r < 0) - return r; - - if (ret) - *ret = NULL; - - return r; - - case BUS_RUNNING: - case BUS_HELLO: - r = process_running(bus, hint_priority, priority, ret); - if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { - bus_enter_closing(bus); - r = 1; - - if (ret) - *ret = NULL; - } - - return r; - - case BUS_CLOSING: - return process_closing(bus, ret); - } - - assert_not_reached("Unknown state"); -} - -_public_ int sd_bus_process(sd_bus *bus, sd_bus_message **ret) { - return bus_process_internal(bus, false, 0, ret); -} - -_public_ int sd_bus_process_priority(sd_bus *bus, int64_t priority, sd_bus_message **ret) { - return bus_process_internal(bus, true, priority, ret); -} - -static int bus_poll(sd_bus *bus, bool need_more, uint64_t timeout_usec) { - struct pollfd p[2] = {}; - int r, e, n; - struct timespec ts; - usec_t m = USEC_INFINITY; - - assert(bus); - - if (bus->state == BUS_CLOSING) - return 1; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - e = sd_bus_get_events(bus); - if (e < 0) - return e; - - if (need_more) - /* The caller really needs some more data, he doesn't - * care about what's already read, or any timeouts - * except its own. */ - e |= POLLIN; - else { - usec_t until; - /* The caller wants to process if there's something to - * process, but doesn't care otherwise */ - - r = sd_bus_get_timeout(bus, &until); - if (r < 0) - return r; - if (r > 0) { - usec_t nw; - nw = now(CLOCK_MONOTONIC); - m = until > nw ? until - nw : 0; - } - } - - if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) - m = timeout_usec; - - p[0].fd = bus->input_fd; - if (bus->output_fd == bus->input_fd) { - p[0].events = e; - n = 1; - } else { - p[0].events = e & POLLIN; - p[1].fd = bus->output_fd; - p[1].events = e & POLLOUT; - n = 2; - } - - r = ppoll(p, n, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); - if (r < 0) - return -errno; - - return r > 0 ? 1 : 0; -} - -_public_ int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec) { - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (bus->state == BUS_CLOSING) - return 0; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->rqueue_size > 0) - return 0; - - return bus_poll(bus, false, timeout_usec); -} - -_public_ int sd_bus_flush(sd_bus *bus) { - int r; - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (bus->state == BUS_CLOSING) - return 0; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - r = bus_ensure_running(bus); - if (r < 0) - return r; - - if (bus->wqueue_size <= 0) - return 0; - - for (;;) { - r = dispatch_wqueue(bus); - if (r < 0) { - if (r == -ENOTCONN || r == -ECONNRESET || r == -EPIPE || r == -ESHUTDOWN) { - bus_enter_closing(bus); - return -ECONNRESET; - } - - return r; - } - - if (bus->wqueue_size <= 0) - return 0; - - r = bus_poll(bus, false, (uint64_t) -1); - if (r < 0) - return r; - } -} - -_public_ int sd_bus_add_filter( - sd_bus *bus, - sd_bus_slot **slot, - sd_bus_message_handler_t callback, - void *userdata) { - - sd_bus_slot *s; - - assert_return(bus, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - s = bus_slot_allocate(bus, !slot, BUS_FILTER_CALLBACK, sizeof(struct filter_callback), userdata); - if (!s) - return -ENOMEM; - - s->filter_callback.callback = callback; - - bus->filter_callbacks_modified = true; - LIST_PREPEND(callbacks, bus->filter_callbacks, &s->filter_callback); - - if (slot) - *slot = s; - - return 0; -} - -_public_ int sd_bus_add_match( - sd_bus *bus, - sd_bus_slot **slot, - const char *match, - sd_bus_message_handler_t callback, - void *userdata) { - - struct bus_match_component *components = NULL; - unsigned n_components = 0; - sd_bus_slot *s = NULL; - int r = 0; - - assert_return(bus, -EINVAL); - assert_return(match, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - r = bus_match_parse(match, &components, &n_components); - if (r < 0) - goto finish; - - s = bus_slot_allocate(bus, !slot, BUS_MATCH_CALLBACK, sizeof(struct match_callback), userdata); - if (!s) { - r = -ENOMEM; - goto finish; - } - - s->match_callback.callback = callback; - s->match_callback.cookie = ++bus->match_cookie; - - if (bus->bus_client) { - enum bus_match_scope scope; - - scope = bus_match_get_scope(components, n_components); - - /* Do not install server-side matches for matches - * against the local service, interface or bus - * path. */ - if (scope != BUS_MATCH_LOCAL) { - - if (!bus->is_kernel) { - /* When this is not a kernel transport, we - * store the original match string, so that we - * can use it to remove the match again */ - - s->match_callback.match_string = strdup(match); - if (!s->match_callback.match_string) { - r = -ENOMEM; - goto finish; - } - } - - r = bus_add_match_internal(bus, s->match_callback.match_string, components, n_components, s->match_callback.cookie); - if (r < 0) - goto finish; - - s->match_added = true; - } - } - - bus->match_callbacks_modified = true; - r = bus_match_add(&bus->match_callbacks, components, n_components, &s->match_callback); - if (r < 0) - goto finish; - - if (slot) - *slot = s; - s = NULL; - -finish: - bus_match_parse_free(components, n_components); - sd_bus_slot_unref(s); - - return r; -} - -int bus_remove_match_by_string( - sd_bus *bus, - const char *match, - sd_bus_message_handler_t callback, - void *userdata) { - - struct bus_match_component *components = NULL; - unsigned n_components = 0; - struct match_callback *c; - int r = 0; - - assert_return(bus, -EINVAL); - assert_return(match, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - r = bus_match_parse(match, &components, &n_components); - if (r < 0) - goto finish; - - r = bus_match_find(&bus->match_callbacks, components, n_components, NULL, NULL, &c); - if (r <= 0) - goto finish; - - sd_bus_slot_unref(container_of(c, sd_bus_slot, match_callback)); - -finish: - bus_match_parse_free(components, n_components); - - return r; -} - -bool bus_pid_changed(sd_bus *bus) { - assert(bus); - - /* We don't support people creating a bus connection and - * keeping it around over a fork(). Let's complain. */ - - return bus->original_pid != getpid(); -} - -static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_bus *bus = userdata; - int r; - - assert(bus); - - r = sd_bus_process(bus, NULL); - if (r < 0) - return r; - - return 1; -} - -static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { - sd_bus *bus = userdata; - int r; - - assert(bus); - - r = sd_bus_process(bus, NULL); - if (r < 0) - return r; - - return 1; -} - -static int prepare_callback(sd_event_source *s, void *userdata) { - sd_bus *bus = userdata; - int r, e; - usec_t until; - - assert(s); - assert(bus); - - e = sd_bus_get_events(bus); - if (e < 0) - return e; - - if (bus->output_fd != bus->input_fd) { - - r = sd_event_source_set_io_events(bus->input_io_event_source, e & POLLIN); - if (r < 0) - return r; - - r = sd_event_source_set_io_events(bus->output_io_event_source, e & POLLOUT); - if (r < 0) - return r; - } else { - r = sd_event_source_set_io_events(bus->input_io_event_source, e); - if (r < 0) - return r; - } - - r = sd_bus_get_timeout(bus, &until); - if (r < 0) - return r; - if (r > 0) { - int j; - - j = sd_event_source_set_time(bus->time_event_source, until); - if (j < 0) - return j; - } - - r = sd_event_source_set_enabled(bus->time_event_source, r > 0); - if (r < 0) - return r; - - return 1; -} - -static int quit_callback(sd_event_source *event, void *userdata) { - sd_bus *bus = userdata; - - assert(event); - - sd_bus_flush(bus); - sd_bus_close(bus); - - return 1; -} - -static int attach_io_events(sd_bus *bus) { - int r; - - assert(bus); - - if (bus->input_fd < 0) - return 0; - - if (!bus->event) - return 0; - - if (!bus->input_io_event_source) { - r = sd_event_add_io(bus->event, &bus->input_io_event_source, bus->input_fd, 0, io_callback, bus); - if (r < 0) - return r; - - r = sd_event_source_set_prepare(bus->input_io_event_source, prepare_callback); - if (r < 0) - return r; - - r = sd_event_source_set_priority(bus->input_io_event_source, bus->event_priority); - if (r < 0) - return r; - - r = sd_event_source_set_description(bus->input_io_event_source, "bus-input"); - } else - r = sd_event_source_set_io_fd(bus->input_io_event_source, bus->input_fd); - - if (r < 0) - return r; - - if (bus->output_fd != bus->input_fd) { - assert(bus->output_fd >= 0); - - if (!bus->output_io_event_source) { - r = sd_event_add_io(bus->event, &bus->output_io_event_source, bus->output_fd, 0, io_callback, bus); - if (r < 0) - return r; - - r = sd_event_source_set_priority(bus->output_io_event_source, bus->event_priority); - if (r < 0) - return r; - - r = sd_event_source_set_description(bus->input_io_event_source, "bus-output"); - } else - r = sd_event_source_set_io_fd(bus->output_io_event_source, bus->output_fd); - - if (r < 0) - return r; - } - - return 0; -} - -static void detach_io_events(sd_bus *bus) { - assert(bus); - - if (bus->input_io_event_source) { - sd_event_source_set_enabled(bus->input_io_event_source, SD_EVENT_OFF); - bus->input_io_event_source = sd_event_source_unref(bus->input_io_event_source); - } - - if (bus->output_io_event_source) { - sd_event_source_set_enabled(bus->output_io_event_source, SD_EVENT_OFF); - bus->output_io_event_source = sd_event_source_unref(bus->output_io_event_source); - } -} - -_public_ int sd_bus_attach_event(sd_bus *bus, sd_event *event, int priority) { - int r; - - assert_return(bus, -EINVAL); - assert_return(!bus->event, -EBUSY); - - assert(!bus->input_io_event_source); - assert(!bus->output_io_event_source); - assert(!bus->time_event_source); - - if (event) - bus->event = sd_event_ref(event); - else { - r = sd_event_default(&bus->event); - if (r < 0) - return r; - } - - bus->event_priority = priority; - - r = sd_event_add_time(bus->event, &bus->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, bus); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(bus->time_event_source, priority); - if (r < 0) - goto fail; - - r = sd_event_source_set_description(bus->time_event_source, "bus-time"); - if (r < 0) - goto fail; - - r = sd_event_add_exit(bus->event, &bus->quit_event_source, quit_callback, bus); - if (r < 0) - goto fail; - - r = sd_event_source_set_description(bus->quit_event_source, "bus-exit"); - if (r < 0) - goto fail; - - r = attach_io_events(bus); - if (r < 0) - goto fail; - - return 0; - -fail: - sd_bus_detach_event(bus); - return r; -} - -_public_ int sd_bus_detach_event(sd_bus *bus) { - assert_return(bus, -EINVAL); - - if (!bus->event) - return 0; - - detach_io_events(bus); - - if (bus->time_event_source) { - sd_event_source_set_enabled(bus->time_event_source, SD_EVENT_OFF); - bus->time_event_source = sd_event_source_unref(bus->time_event_source); - } - - if (bus->quit_event_source) { - sd_event_source_set_enabled(bus->quit_event_source, SD_EVENT_OFF); - bus->quit_event_source = sd_event_source_unref(bus->quit_event_source); - } - - bus->event = sd_event_unref(bus->event); - return 1; -} - -_public_ sd_event* sd_bus_get_event(sd_bus *bus) { - assert_return(bus, NULL); - - return bus->event; -} - -_public_ sd_bus_message* sd_bus_get_current_message(sd_bus *bus) { - assert_return(bus, NULL); - - return bus->current_message; -} - -_public_ sd_bus_slot* sd_bus_get_current_slot(sd_bus *bus) { - assert_return(bus, NULL); - - return bus->current_slot; -} - -_public_ sd_bus_message_handler_t sd_bus_get_current_handler(sd_bus *bus) { - assert_return(bus, NULL); - - return bus->current_handler; -} - -_public_ void* sd_bus_get_current_userdata(sd_bus *bus) { - assert_return(bus, NULL); - - return bus->current_userdata; -} - -static int bus_default(int (*bus_open)(sd_bus **), sd_bus **default_bus, sd_bus **ret) { - sd_bus *b = NULL; - int r; - - assert(bus_open); - assert(default_bus); - - if (!ret) - return !!*default_bus; - - if (*default_bus) { - *ret = sd_bus_ref(*default_bus); - return 0; - } - - r = bus_open(&b); - if (r < 0) - return r; - - b->default_bus_ptr = default_bus; - b->tid = gettid(); - *default_bus = b; - - *ret = b; - return 1; -} - -_public_ int sd_bus_default_system(sd_bus **ret) { - return bus_default(sd_bus_open_system, &default_system_bus, ret); -} - - -_public_ int sd_bus_default_user(sd_bus **ret) { - return bus_default(sd_bus_open_user, &default_user_bus, ret); -} - -_public_ int sd_bus_default(sd_bus **ret) { - - const char *e; - - /* Let's try our best to reuse another cached connection. If - * the starter bus type is set, connect via our normal - * connection logic, ignoring $DBUS_STARTER_ADDRESS, so that - * we can share the connection with the user/system default - * bus. */ - - e = secure_getenv("DBUS_STARTER_BUS_TYPE"); - if (e) { - if (streq(e, "system")) - return sd_bus_default_system(ret); - else if (STR_IN_SET(e, "user", "session")) - return sd_bus_default_user(ret); - } - - /* No type is specified, so we have not other option than to - * use the starter address if it is set. */ - - e = secure_getenv("DBUS_STARTER_ADDRESS"); - if (e) { - - return bus_default(sd_bus_open, &default_starter_bus, ret); - } - - /* Finally, if nothing is set use the cached connection for - * the right scope */ - - if (cg_pid_get_owner_uid(0, NULL) >= 0) - return sd_bus_default_user(ret); - else - return sd_bus_default_system(ret); -} - -_public_ int sd_bus_get_tid(sd_bus *b, pid_t *tid) { - assert_return(b, -EINVAL); - assert_return(tid, -EINVAL); - assert_return(!bus_pid_changed(b), -ECHILD); - - if (b->tid != 0) { - *tid = b->tid; - return 0; - } - - if (b->event) - return sd_event_get_tid(b->event, tid); - - return -ENXIO; -} - -_public_ int sd_bus_path_encode(const char *prefix, const char *external_id, char **ret_path) { - _cleanup_free_ char *e = NULL; - char *ret; - - assert_return(object_path_is_valid(prefix), -EINVAL); - assert_return(external_id, -EINVAL); - assert_return(ret_path, -EINVAL); - - e = bus_label_escape(external_id); - if (!e) - return -ENOMEM; - - ret = strjoin(prefix, "/", e, NULL); - if (!ret) - return -ENOMEM; - - *ret_path = ret; - return 0; -} - -_public_ int sd_bus_path_decode(const char *path, const char *prefix, char **external_id) { - const char *e; - char *ret; - - assert_return(object_path_is_valid(path), -EINVAL); - assert_return(object_path_is_valid(prefix), -EINVAL); - assert_return(external_id, -EINVAL); - - e = object_path_startswith(path, prefix); - if (!e) { - *external_id = NULL; - return 0; - } - - ret = bus_label_unescape(e); - if (!ret) - return -ENOMEM; - - *external_id = ret; - return 1; -} - -_public_ int sd_bus_path_encode_many(char **out, const char *path_template, ...) { - _cleanup_strv_free_ char **labels = NULL; - char *path, *path_pos, **label_pos; - const char *sep, *template_pos; - size_t path_length; - va_list list; - int r; - - assert_return(out, -EINVAL); - assert_return(path_template, -EINVAL); - - path_length = strlen(path_template); - - va_start(list, path_template); - for (sep = strchr(path_template, '%'); sep; sep = strchr(sep + 1, '%')) { - const char *arg; - char *label; - - arg = va_arg(list, const char *); - if (!arg) { - va_end(list); - return -EINVAL; - } - - label = bus_label_escape(arg); - if (!label) { - va_end(list); - return -ENOMEM; - } - - r = strv_consume(&labels, label); - if (r < 0) { - va_end(list); - return r; - } - - /* add label length, but account for the format character */ - path_length += strlen(label) - 1; - } - va_end(list); - - path = malloc(path_length + 1); - if (!path) - return -ENOMEM; - - path_pos = path; - label_pos = labels; - - for (template_pos = path_template; *template_pos; ) { - sep = strchrnul(template_pos, '%'); - path_pos = mempcpy(path_pos, template_pos, sep - template_pos); - if (!*sep) - break; - - path_pos = stpcpy(path_pos, *label_pos++); - template_pos = sep + 1; - } - - *path_pos = 0; - *out = path; - return 0; -} - -_public_ int sd_bus_path_decode_many(const char *path, const char *path_template, ...) { - _cleanup_strv_free_ char **labels = NULL; - const char *template_pos, *path_pos; - char **label_pos; - va_list list; - int r; - - /* - * This decodes an object-path based on a template argument. The - * template consists of a verbatim path, optionally including special - * directives: - * - * - Each occurrence of '%' in the template matches an arbitrary - * substring of a label in the given path. At most one such - * directive is allowed per label. For each such directive, the - * caller must provide an output parameter (char **) via va_arg. If - * NULL is passed, the given label is verified, but not returned. - * For each matched label, the *decoded* label is stored in the - * passed output argument, and the caller is responsible to free - * it. Note that the output arguments are only modified if the - * actualy path matched the template. Otherwise, they're left - * untouched. - * - * This function returns <0 on error, 0 if the path does not match the - * template, 1 if it matched. - */ - - assert_return(path, -EINVAL); - assert_return(path_template, -EINVAL); - - path_pos = path; - - for (template_pos = path_template; *template_pos; ) { - const char *sep; - size_t length; - char *label; - - /* verify everything until the next '%' matches verbatim */ - sep = strchrnul(template_pos, '%'); - length = sep - template_pos; - if (strncmp(path_pos, template_pos, length)) - return 0; - - path_pos += length; - template_pos += length; - - if (!*template_pos) - break; - - /* We found the next '%' character. Everything up until here - * matched. We now skip ahead to the end of this label and make - * sure it matches the tail of the label in the path. Then we - * decode the string in-between and save it for later use. */ - - ++template_pos; /* skip over '%' */ - - sep = strchrnul(template_pos, '/'); - length = sep - template_pos; /* length of suffix to match verbatim */ - - /* verify the suffixes match */ - sep = strchrnul(path_pos, '/'); - if (sep - path_pos < (ssize_t)length || - strncmp(sep - length, template_pos, length)) - return 0; - - template_pos += length; /* skip over matched label */ - length = sep - path_pos - length; /* length of sub-label to decode */ - - /* store unescaped label for later use */ - label = bus_label_unescape_n(path_pos, length); - if (!label) - return -ENOMEM; - - r = strv_consume(&labels, label); - if (r < 0) - return r; - - path_pos = sep; /* skip decoded label and suffix */ - } - - /* end of template must match end of path */ - if (*path_pos) - return 0; - - /* copy the labels over to the caller */ - va_start(list, path_template); - for (label_pos = labels; label_pos && *label_pos; ++label_pos) { - char **arg; - - arg = va_arg(list, char **); - if (arg) - *arg = *label_pos; - else - free(*label_pos); - } - va_end(list); - - free(labels); - labels = NULL; - return 1; -} - -_public_ int sd_bus_try_close(sd_bus *bus) { - int r; - - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (!bus->is_kernel) - return -EOPNOTSUPP; - - if (!BUS_IS_OPEN(bus->state)) - return -ENOTCONN; - - if (bus->rqueue_size > 0) - return -EBUSY; - - if (bus->wqueue_size > 0) - return -EBUSY; - - r = bus_kernel_try_close(bus); - if (r < 0) - return r; - - sd_bus_close(bus); - return 0; -} - -_public_ int sd_bus_get_description(sd_bus *bus, const char **description) { - assert_return(bus, -EINVAL); - assert_return(description, -EINVAL); - assert_return(bus->description, -ENXIO); - assert_return(!bus_pid_changed(bus), -ECHILD); - - *description = bus->description; - return 0; -} - -int bus_get_root_path(sd_bus *bus) { - int r; - - if (bus->cgroup_root) - return 0; - - r = cg_get_root_path(&bus->cgroup_root); - if (r == -ENOENT) { - bus->cgroup_root = strdup("/"); - if (!bus->cgroup_root) - return -ENOMEM; - - r = 0; - } - - return r; -} - -_public_ int sd_bus_get_scope(sd_bus *bus, const char **scope) { - int r; - - assert_return(bus, -EINVAL); - assert_return(scope, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (bus->is_kernel) { - _cleanup_free_ char *n = NULL; - const char *dash; - - r = bus_kernel_get_bus_name(bus, &n); - if (r < 0) - return r; - - if (streq(n, "0-system")) { - *scope = "system"; - return 0; - } - - dash = strchr(n, '-'); - if (streq_ptr(dash, "-user")) { - *scope = "user"; - return 0; - } - } - - if (bus->is_user) { - *scope = "user"; - return 0; - } - - if (bus->is_system) { - *scope = "system"; - return 0; - } - - return -ENODATA; -} - -_public_ int sd_bus_get_address(sd_bus *bus, const char **address) { - - assert_return(bus, -EINVAL); - assert_return(address, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - if (bus->address) { - *address = bus->address; - return 0; - } - - return -ENODATA; -} - -_public_ int sd_bus_get_creds_mask(sd_bus *bus, uint64_t *mask) { - assert_return(bus, -EINVAL); - assert_return(mask, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - *mask = bus->creds_mask; - return 0; -} - -_public_ int sd_bus_is_bus_client(sd_bus *bus) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return bus->bus_client; -} - -_public_ int sd_bus_is_server(sd_bus *bus) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return bus->is_server; -} - -_public_ int sd_bus_is_anonymous(sd_bus *bus) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return bus->anonymous_auth; -} - -_public_ int sd_bus_is_trusted(sd_bus *bus) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return bus->trusted; -} - -_public_ int sd_bus_is_monitor(sd_bus *bus) { - assert_return(bus, -EINVAL); - assert_return(!bus_pid_changed(bus), -ECHILD); - - return !!(bus->hello_flags & KDBUS_HELLO_MONITOR); -} - -static void flush_close(sd_bus *bus) { - if (!bus) - return; - - /* Flushes and closes the specified bus. We take a ref before, - * to ensure the flushing does not cause the bus to be - * unreferenced. */ - - sd_bus_flush_close_unref(sd_bus_ref(bus)); -} - -_public_ void sd_bus_default_flush_close(void) { - flush_close(default_starter_bus); - flush_close(default_user_bus); - flush_close(default_system_bus); -} diff --git a/src/libsystemd/sd-bus/test-bus-benchmark.c b/src/libsystemd/sd-bus/test-bus-benchmark.c deleted file mode 100644 index 56ac2ab3dd..0000000000 --- a/src/libsystemd/sd-bus/test-bus-benchmark.c +++ /dev/null @@ -1,371 +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 . -***/ - -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-internal.h" -#include "bus-kernel.h" -#include "bus-util.h" -#include "def.h" -#include "fd-util.h" -#include "time-util.h" -#include "util.h" - -#define MAX_SIZE (2*1024*1024) - -static usec_t arg_loop_usec = 100 * USEC_PER_MSEC; - -typedef enum Type { - TYPE_KDBUS, - TYPE_LEGACY, - TYPE_DIRECT, -} Type; - -static void server(sd_bus *b, size_t *result) { - int r; - - for (;;) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - r = sd_bus_process(b, &m); - assert_se(r >= 0); - - if (r == 0) - assert_se(sd_bus_wait(b, USEC_INFINITY) >= 0); - if (!m) - continue; - - if (sd_bus_message_is_method_call(m, "benchmark.server", "Ping")) - assert_se(sd_bus_reply_method_return(m, NULL) >= 0); - else if (sd_bus_message_is_method_call(m, "benchmark.server", "Work")) { - const void *p; - size_t sz; - - /* Make sure the mmap is mapped */ - assert_se(sd_bus_message_read_array(m, 'y', &p, &sz) > 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - } else if (sd_bus_message_is_method_call(m, "benchmark.server", "Exit")) { - uint64_t res; - assert_se(sd_bus_message_read(m, "t", &res) > 0); - - *result = res; - return; - - } else if (!sd_bus_message_is_signal(m, NULL, NULL)) - assert_not_reached("Unknown method"); - } -} - -static void transaction(sd_bus *b, size_t sz, const char *server_name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - uint8_t *p; - - assert_se(sd_bus_message_new_method_call(b, &m, server_name, "/", "benchmark.server", "Work") >= 0); - assert_se(sd_bus_message_append_array_space(m, 'y', sz, (void**) &p) >= 0); - - memset(p, 0x80, sz); - - assert_se(sd_bus_call(b, m, 0, NULL, &reply) >= 0); -} - -static void client_bisect(const char *address, const char *server_name) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *x = NULL; - size_t lsize, rsize, csize; - sd_bus *b; - int r; - - r = sd_bus_new(&b); - assert_se(r >= 0); - - r = sd_bus_set_address(b, address); - assert_se(r >= 0); - - r = sd_bus_start(b); - assert_se(r >= 0); - - r = sd_bus_call_method(b, server_name, "/", "benchmark.server", "Ping", NULL, NULL, NULL); - assert_se(r >= 0); - - lsize = 1; - rsize = MAX_SIZE; - - printf("SIZE\tCOPY\tMEMFD\n"); - - for (;;) { - usec_t t; - unsigned n_copying, n_memfd; - - csize = (lsize + rsize) / 2; - - if (csize <= lsize) - break; - - if (csize <= 0) - break; - - printf("%zu\t", csize); - - b->use_memfd = 0; - - t = now(CLOCK_MONOTONIC); - for (n_copying = 0;; n_copying++) { - transaction(b, csize, server_name); - if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) - break; - } - printf("%u\t", (unsigned) ((n_copying * USEC_PER_SEC) / arg_loop_usec)); - - b->use_memfd = -1; - - t = now(CLOCK_MONOTONIC); - for (n_memfd = 0;; n_memfd++) { - transaction(b, csize, server_name); - if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) - break; - } - printf("%u\n", (unsigned) ((n_memfd * USEC_PER_SEC) / arg_loop_usec)); - - if (n_copying == n_memfd) - break; - - if (n_copying > n_memfd) - lsize = csize; - else - rsize = csize; - } - - b->use_memfd = 1; - assert_se(sd_bus_message_new_method_call(b, &x, server_name, "/", "benchmark.server", "Exit") >= 0); - assert_se(sd_bus_message_append(x, "t", csize) >= 0); - assert_se(sd_bus_send(b, x, NULL) >= 0); - - sd_bus_unref(b); -} - -static void client_chart(Type type, const char *address, const char *server_name, int fd) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *x = NULL; - size_t csize; - sd_bus *b; - int r; - - r = sd_bus_new(&b); - assert_se(r >= 0); - - if (type == TYPE_DIRECT) { - r = sd_bus_set_fd(b, fd, fd); - assert_se(r >= 0); - } else { - r = sd_bus_set_address(b, address); - assert_se(r >= 0); - - r = sd_bus_set_bus_client(b, true); - assert_se(r >= 0); - } - - r = sd_bus_start(b); - assert_se(r >= 0); - - r = sd_bus_call_method(b, server_name, "/", "benchmark.server", "Ping", NULL, NULL, NULL); - assert_se(r >= 0); - - switch (type) { - case TYPE_KDBUS: - printf("SIZE\tCOPY\tMEMFD\n"); - break; - case TYPE_LEGACY: - printf("SIZE\tLEGACY\n"); - break; - case TYPE_DIRECT: - printf("SIZE\tDIRECT\n"); - break; - } - - for (csize = 1; csize <= MAX_SIZE; csize *= 2) { - usec_t t; - unsigned n_copying, n_memfd; - - printf("%zu\t", csize); - - if (type == TYPE_KDBUS) { - b->use_memfd = 0; - - t = now(CLOCK_MONOTONIC); - for (n_copying = 0;; n_copying++) { - transaction(b, csize, server_name); - if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) - break; - } - - printf("%u\t", (unsigned) ((n_copying * USEC_PER_SEC) / arg_loop_usec)); - - b->use_memfd = -1; - } - - t = now(CLOCK_MONOTONIC); - for (n_memfd = 0;; n_memfd++) { - transaction(b, csize, server_name); - if (now(CLOCK_MONOTONIC) >= t + arg_loop_usec) - break; - } - - printf("%u\n", (unsigned) ((n_memfd * USEC_PER_SEC) / arg_loop_usec)); - } - - b->use_memfd = 1; - assert_se(sd_bus_message_new_method_call(b, &x, server_name, "/", "benchmark.server", "Exit") >= 0); - assert_se(sd_bus_message_append(x, "t", csize) >= 0); - assert_se(sd_bus_send(b, x, NULL) >= 0); - - sd_bus_unref(b); -} - -int main(int argc, char *argv[]) { - enum { - MODE_BISECT, - MODE_CHART, - } mode = MODE_BISECT; - Type type = TYPE_KDBUS; - int i, pair[2] = { -1, -1 }; - _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL, *server_name = NULL; - _cleanup_close_ int bus_ref = -1; - const char *unique; - cpu_set_t cpuset; - size_t result; - sd_bus *b; - pid_t pid; - int r; - - for (i = 1; i < argc; i++) { - if (streq(argv[i], "chart")) { - mode = MODE_CHART; - continue; - } else if (streq(argv[i], "legacy")) { - type = TYPE_LEGACY; - continue; - } else if (streq(argv[i], "direct")) { - type = TYPE_DIRECT; - continue; - } - - assert_se(parse_sec(argv[i], &arg_loop_usec) >= 0); - } - - assert_se(!MODE_BISECT || TYPE_KDBUS); - - assert_se(arg_loop_usec > 0); - - if (type == TYPE_KDBUS) { - assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); - - bus_ref = bus_kernel_create_bus(name, false, &bus_name); - if (bus_ref == -ENOENT) - exit(EXIT_TEST_SKIP); - - assert_se(bus_ref >= 0); - - address = strappend("kernel:path=", bus_name); - assert_se(address); - } else if (type == TYPE_LEGACY) { - const char *e; - - e = secure_getenv("DBUS_SESSION_BUS_ADDRESS"); - assert_se(e); - - address = strdup(e); - assert_se(address); - } - - r = sd_bus_new(&b); - assert_se(r >= 0); - - if (type == TYPE_DIRECT) { - assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) >= 0); - - r = sd_bus_set_fd(b, pair[0], pair[0]); - assert_se(r >= 0); - - r = sd_bus_set_server(b, true, SD_ID128_NULL); - assert_se(r >= 0); - } else { - r = sd_bus_set_address(b, address); - assert_se(r >= 0); - - r = sd_bus_set_bus_client(b, true); - assert_se(r >= 0); - } - - r = sd_bus_start(b); - assert_se(r >= 0); - - if (type != TYPE_DIRECT) { - r = sd_bus_get_unique_name(b, &unique); - assert_se(r >= 0); - - server_name = strdup(unique); - assert_se(server_name); - } - - sync(); - setpriority(PRIO_PROCESS, 0, -19); - - pid = fork(); - assert_se(pid >= 0); - - if (pid == 0) { - CPU_ZERO(&cpuset); - CPU_SET(0, &cpuset); - pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); - - safe_close(bus_ref); - sd_bus_unref(b); - - switch (mode) { - case MODE_BISECT: - client_bisect(address, server_name); - break; - - case MODE_CHART: - client_chart(type, address, server_name, pair[1]); - break; - } - - _exit(0); - } - - CPU_ZERO(&cpuset); - CPU_SET(1, &cpuset); - pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset); - - server(b, &result); - - if (mode == MODE_BISECT) - printf("Copying/memfd are equally fast at %zu bytes\n", result); - - assert_se(waitpid(pid, NULL, 0) == pid); - - safe_close(pair[1]); - sd_bus_unref(b); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-chat.c b/src/libsystemd/sd-bus/test-bus-chat.c deleted file mode 100644 index 048c0d19e2..0000000000 --- a/src/libsystemd/sd-bus/test-bus-chat.c +++ /dev/null @@ -1,560 +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 . -***/ - -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-internal.h" -#include "bus-match.h" -#include "bus-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "log.h" -#include "macro.h" -#include "util.h" - -static int match_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - log_info("Match triggered! interface=%s member=%s", strna(sd_bus_message_get_interface(m)), strna(sd_bus_message_get_member(m))); - return 0; -} - -static int object_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - int r; - - if (sd_bus_message_is_method_error(m, NULL)) - return 0; - - if (sd_bus_message_is_method_call(m, "org.object.test", "Foobar")) { - log_info("Invoked Foobar() on %s", sd_bus_message_get_path(m)); - - r = sd_bus_reply_method_return(m, NULL); - if (r < 0) - return log_error_errno(r, "Failed to send reply: %m"); - - return 1; - } - - return 0; -} - -static int server_init(sd_bus **_bus) { - sd_bus *bus = NULL; - sd_id128_t id; - int r; - const char *unique; - - assert_se(_bus); - - r = sd_bus_open_user(&bus); - if (r < 0) { - log_error_errno(r, "Failed to connect to user bus: %m"); - goto fail; - } - - r = sd_bus_get_bus_id(bus, &id); - if (r < 0) { - log_error_errno(r, "Failed to get server ID: %m"); - goto fail; - } - - r = sd_bus_get_unique_name(bus, &unique); - if (r < 0) { - log_error_errno(r, "Failed to get unique name: %m"); - goto fail; - } - - log_info("Peer ID is " SD_ID128_FORMAT_STR ".", SD_ID128_FORMAT_VAL(id)); - log_info("Unique ID: %s", unique); - log_info("Can send file handles: %i", sd_bus_can_send(bus, 'h')); - - r = sd_bus_request_name(bus, "org.freedesktop.systemd.test", 0); - if (r < 0) { - log_error_errno(r, "Failed to acquire name: %m"); - goto fail; - } - - r = sd_bus_add_fallback(bus, NULL, "/foo/bar", object_callback, NULL); - if (r < 0) { - log_error_errno(r, "Failed to add object: %m"); - goto fail; - } - - r = sd_bus_add_match(bus, NULL, "type='signal',interface='foo.bar',member='Notify'", match_callback, NULL); - if (r < 0) { - log_error_errno(r, "Failed to add match: %m"); - goto fail; - } - - r = sd_bus_add_match(bus, NULL, "type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged'", match_callback, NULL); - if (r < 0) { - log_error_errno(r, "Failed to add match: %m"); - goto fail; - } - - bus_match_dump(&bus->match_callbacks, 0); - - *_bus = bus; - return 0; - -fail: - sd_bus_unref(bus); - return r; -} - -static int server(sd_bus *bus) { - int r; - bool client1_gone = false, client2_gone = false; - - while (!client1_gone || !client2_gone) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - pid_t pid = 0; - const char *label = NULL; - - r = sd_bus_process(bus, &m); - if (r < 0) { - log_error_errno(r, "Failed to process requests: %m"); - goto fail; - } - - if (r == 0) { - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) { - log_error_errno(r, "Failed to wait: %m"); - goto fail; - } - - continue; - } - - if (!m) - continue; - - sd_bus_creds_get_pid(sd_bus_message_get_creds(m), &pid); - sd_bus_creds_get_selinux_context(sd_bus_message_get_creds(m), &label); - log_info("Got message! member=%s pid="PID_FMT" label=%s", - strna(sd_bus_message_get_member(m)), - pid, - strna(label)); - /* bus_message_dump(m); */ - /* sd_bus_message_rewind(m, true); */ - - if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "LowerCase")) { - const char *hello; - _cleanup_free_ char *lowercase = NULL; - - r = sd_bus_message_read(m, "s", &hello); - if (r < 0) { - log_error_errno(r, "Failed to get parameter: %m"); - goto fail; - } - - lowercase = strdup(hello); - if (!lowercase) { - r = log_oom(); - goto fail; - } - - ascii_strlower(lowercase); - - r = sd_bus_reply_method_return(m, "s", lowercase); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "ExitClient1")) { - - r = sd_bus_reply_method_return(m, NULL); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - - client1_gone = true; - } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "ExitClient2")) { - - r = sd_bus_reply_method_return(m, NULL); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - - client2_gone = true; - } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Slow")) { - - sleep(1); - - r = sd_bus_reply_method_return(m, NULL); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - - } else if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "FileDescriptor")) { - int fd; - static const char x = 'X'; - - r = sd_bus_message_read(m, "h", &fd); - if (r < 0) { - log_error_errno(r, "Failed to get parameter: %m"); - goto fail; - } - - log_info("Received fd=%d", fd); - - if (write(fd, &x, 1) < 0) { - log_error_errno(errno, "Failed to write to fd: %m"); - safe_close(fd); - goto fail; - } - - r = sd_bus_reply_method_return(m, NULL); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - - } else if (sd_bus_message_is_method_call(m, NULL, NULL)) { - - r = sd_bus_reply_method_error( - m, - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNKNOWN_METHOD, "Unknown method.")); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - } - } - - r = 0; - -fail: - if (bus) { - sd_bus_flush(bus); - sd_bus_unref(bus); - } - - return r; -} - -static void* client1(void*p) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *hello; - int r; - _cleanup_close_pair_ int pp[2] = { -1, -1 }; - char x; - - r = sd_bus_open_user(&bus); - if (r < 0) { - log_error_errno(r, "Failed to connect to user bus: %m"); - goto finish; - } - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "LowerCase", - &error, - &reply, - "s", - "HELLO"); - if (r < 0) { - log_error_errno(r, "Failed to issue method call: %m"); - goto finish; - } - - r = sd_bus_message_read(reply, "s", &hello); - if (r < 0) { - log_error_errno(r, "Failed to get string: %m"); - goto finish; - } - - assert_se(streq(hello, "hello")); - - if (pipe2(pp, O_CLOEXEC|O_NONBLOCK) < 0) { - log_error_errno(errno, "Failed to allocate pipe: %m"); - r = -errno; - goto finish; - } - - log_info("Sending fd=%d", pp[1]); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "FileDescriptor", - &error, - NULL, - "h", - pp[1]); - if (r < 0) { - log_error_errno(r, "Failed to issue method call: %m"); - goto finish; - } - - errno = 0; - if (read(pp[0], &x, 1) <= 0) { - log_error("Failed to read from pipe: %s", errno ? strerror(errno) : "early read"); - goto finish; - } - - r = 0; - -finish: - if (bus) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *q; - - r = sd_bus_message_new_method_call( - bus, - &q, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "ExitClient1"); - if (r < 0) - log_error_errno(r, "Failed to allocate method call: %m"); - else - sd_bus_send(bus, q, NULL); - - } - - return INT_TO_PTR(r); -} - -static int quit_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - bool *x = userdata; - - log_error("Quit callback: %s", strerror(sd_bus_message_get_errno(m))); - - *x = 1; - return 1; -} - -static void* client2(void*p) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool quit = false; - const char *mid; - int r; - - r = sd_bus_open_user(&bus); - if (r < 0) { - log_error_errno(r, "Failed to connect to user bus: %m"); - goto finish; - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd.test", - "/foo/bar/waldo/piep", - "org.object.test", - "Foobar"); - if (r < 0) { - log_error_errno(r, "Failed to allocate method call: %m"); - goto finish; - } - - r = sd_bus_send(bus, m, NULL); - if (r < 0) { - log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); - goto finish; - } - - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_signal( - bus, - &m, - "/foobar", - "foo.bar", - "Notify"); - if (r < 0) { - log_error_errno(r, "Failed to allocate signal: %m"); - goto finish; - } - - r = sd_bus_send(bus, m, NULL); - if (r < 0) { - log_error("Failed to issue signal: %s", bus_error_message(&error, -r)); - goto finish; - } - - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.DBus.Peer", - "GetMachineId"); - if (r < 0) { - log_error_errno(r, "Failed to allocate method call: %m"); - goto finish; - } - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); - goto finish; - } - - r = sd_bus_message_read(reply, "s", &mid); - if (r < 0) { - log_error_errno(r, "Failed to parse machine ID: %m"); - goto finish; - } - - log_info("Machine ID is %s.", mid); - - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "Slow"); - if (r < 0) { - log_error_errno(r, "Failed to allocate method call: %m"); - goto finish; - } - - reply = sd_bus_message_unref(reply); - - r = sd_bus_call(bus, m, 200 * USEC_PER_MSEC, &error, &reply); - if (r < 0) - log_info("Failed to issue method call: %s", bus_error_message(&error, -r)); - else - log_info("Slow call succeed."); - - m = sd_bus_message_unref(m); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "Slow"); - if (r < 0) { - log_error_errno(r, "Failed to allocate method call: %m"); - goto finish; - } - - r = sd_bus_call_async(bus, NULL, m, quit_callback, &quit, 200 * USEC_PER_MSEC); - if (r < 0) { - log_info("Failed to issue method call: %s", bus_error_message(&error, -r)); - goto finish; - } - - while (!quit) { - r = sd_bus_process(bus, NULL); - if (r < 0) { - log_error_errno(r, "Failed to process requests: %m"); - goto finish; - } - if (r == 0) { - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) { - log_error_errno(r, "Failed to wait: %m"); - goto finish; - } - } - } - - r = 0; - -finish: - if (bus) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *q; - - r = sd_bus_message_new_method_call( - bus, - &q, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "ExitClient2"); - if (r < 0) { - log_error_errno(r, "Failed to allocate method call: %m"); - goto finish; - } - - (void) sd_bus_send(bus, q, NULL); - } - - return INT_TO_PTR(r); -} - -int main(int argc, char *argv[]) { - pthread_t c1, c2; - sd_bus *bus; - void *p; - int q, r; - - r = server_init(&bus); - if (r < 0) { - log_info("Failed to connect to bus, skipping tests."); - return EXIT_TEST_SKIP; - } - - log_info("Initialized..."); - - r = pthread_create(&c1, NULL, client1, bus); - if (r != 0) - return EXIT_FAILURE; - - r = pthread_create(&c2, NULL, client2, bus); - if (r != 0) - return EXIT_FAILURE; - - r = server(bus); - - q = pthread_join(c1, &p); - if (q != 0) - return EXIT_FAILURE; - if (PTR_TO_INT(p) < 0) - return EXIT_FAILURE; - - q = pthread_join(c2, &p); - if (q != 0) - return EXIT_FAILURE; - if (PTR_TO_INT(p) < 0) - return EXIT_FAILURE; - - if (r < 0) - return EXIT_FAILURE; - - return EXIT_SUCCESS; -} diff --git a/src/libsystemd/sd-bus/test-bus-cleanup.c b/src/libsystemd/sd-bus/test-bus-cleanup.c deleted file mode 100644 index 250a5b2908..0000000000 --- a/src/libsystemd/sd-bus/test-bus-cleanup.c +++ /dev/null @@ -1,95 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#include - -#include "sd-bus.h" - -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-util.h" -#include "refcnt.h" - -static void test_bus_new(void) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - - assert_se(sd_bus_new(&bus) == 0); - printf("after new: refcount %u\n", REFCNT_GET(bus->n_ref)); -} - -static int test_bus_open(void) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - r = sd_bus_open_system(&bus); - if (r == -ECONNREFUSED || r == -ENOENT) - return r; - - assert_se(r >= 0); - printf("after open: refcount %u\n", REFCNT_GET(bus->n_ref)); - - return 0; -} - -static void test_bus_new_method_call(void) { - sd_bus *bus = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - assert_se(sd_bus_open_system(&bus) >= 0); - - assert_se(sd_bus_message_new_method_call(bus, &m, "a.service.name", "/an/object/path", "an.interface.name", "AMethodName") >= 0); - - printf("after message_new_method_call: refcount %u\n", REFCNT_GET(bus->n_ref)); - - sd_bus_flush_close_unref(bus); - printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); -} - -static void test_bus_new_signal(void) { - sd_bus *bus = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - - assert_se(sd_bus_open_system(&bus) >= 0); - - assert_se(sd_bus_message_new_signal(bus, &m, "/an/object/path", "an.interface.name", "Name") >= 0); - - printf("after message_new_signal: refcount %u\n", REFCNT_GET(bus->n_ref)); - - sd_bus_flush_close_unref(bus); - printf("after bus_flush_close_unref: refcount %u\n", m->n_ref); -} - -int main(int argc, char **argv) { - int r; - - log_parse_environment(); - log_open(); - - test_bus_new(); - r = test_bus_open(); - if (r < 0) { - log_info("Failed to connect to bus, skipping tests."); - return EXIT_TEST_SKIP; - } - - test_bus_new_method_call(); - test_bus_new_signal(); - - return EXIT_SUCCESS; -} diff --git a/src/libsystemd/sd-bus/test-bus-creds.c b/src/libsystemd/sd-bus/test-bus-creds.c deleted file mode 100644 index e9ef483bdd..0000000000 --- a/src/libsystemd/sd-bus/test-bus-creds.c +++ /dev/null @@ -1,50 +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 . -***/ - -#include "sd-bus.h" - -#include "bus-dump.h" -#include "bus-util.h" -#include "cgroup-util.h" - -int main(int argc, char *argv[]) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - int r; - - if (cg_unified() == -ENOMEDIUM) { - puts("Skipping test: /sys/fs/cgroup/ not available"); - return EXIT_TEST_SKIP; - } - - r = sd_bus_creds_new_from_pid(&creds, 0, _SD_BUS_CREDS_ALL); - assert_se(r >= 0); - - bus_creds_dump(creds, NULL, true); - - creds = sd_bus_creds_unref(creds); - - r = sd_bus_creds_new_from_pid(&creds, 1, _SD_BUS_CREDS_ALL); - if (r != -EACCES) { - assert_se(r >= 0); - putchar('\n'); - bus_creds_dump(creds, NULL, true); - } - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-error.c b/src/libsystemd/sd-bus/test-bus-error.c deleted file mode 100644 index 46d18abd29..0000000000 --- a/src/libsystemd/sd-bus/test-bus-error.c +++ /dev/null @@ -1,232 +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 . -***/ - -#include "sd-bus.h" - -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-util.h" -#include "errno-list.h" - -static void test_error(void) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL, second = SD_BUS_ERROR_NULL; - const sd_bus_error const_error = SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_FILE_EXISTS, "const error"); - const sd_bus_error temporarily_const_error = { - .name = SD_BUS_ERROR_ACCESS_DENIED, - .message = "oh! no", - ._need_free = -1 - }; - - assert_se(!sd_bus_error_is_set(&error)); - assert_se(sd_bus_error_set(&error, SD_BUS_ERROR_NOT_SUPPORTED, "xxx") == -EOPNOTSUPP); - assert_se(streq(error.name, SD_BUS_ERROR_NOT_SUPPORTED)); - assert_se(streq(error.message, "xxx")); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_NOT_SUPPORTED)); - assert_se(sd_bus_error_get_errno(&error) == EOPNOTSUPP); - assert_se(sd_bus_error_is_set(&error)); - sd_bus_error_free(&error); - - /* Check with no error */ - assert_se(!sd_bus_error_is_set(&error)); - assert_se(sd_bus_error_setf(&error, NULL, "yyy %i", -1) == 0); - assert_se(error.name == NULL); - assert_se(error.message == NULL); - assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND)); - assert_se(sd_bus_error_get_errno(&error) == 0); - assert_se(!sd_bus_error_is_set(&error)); - - assert_se(sd_bus_error_setf(&error, SD_BUS_ERROR_FILE_NOT_FOUND, "yyy %i", -1) == -ENOENT); - assert_se(streq(error.name, SD_BUS_ERROR_FILE_NOT_FOUND)); - assert_se(streq(error.message, "yyy -1")); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND)); - assert_se(sd_bus_error_get_errno(&error) == ENOENT); - assert_se(sd_bus_error_is_set(&error)); - - assert_se(!sd_bus_error_is_set(&second)); - assert_se(second._need_free == 0); - assert_se(error._need_free > 0); - assert_se(sd_bus_error_copy(&second, &error) == -ENOENT); - assert_se(second._need_free > 0); - assert_se(streq(error.name, second.name)); - assert_se(streq(error.message, second.message)); - assert_se(sd_bus_error_get_errno(&second) == ENOENT); - assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_FILE_NOT_FOUND)); - assert_se(sd_bus_error_is_set(&second)); - - sd_bus_error_free(&error); - sd_bus_error_free(&second); - - assert_se(!sd_bus_error_is_set(&second)); - assert_se(const_error._need_free == 0); - assert_se(sd_bus_error_copy(&second, &const_error) == -EEXIST); - assert_se(second._need_free == 0); - assert_se(streq(const_error.name, second.name)); - assert_se(streq(const_error.message, second.message)); - assert_se(sd_bus_error_get_errno(&second) == EEXIST); - assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_FILE_EXISTS)); - assert_se(sd_bus_error_is_set(&second)); - sd_bus_error_free(&second); - - assert_se(!sd_bus_error_is_set(&second)); - assert_se(temporarily_const_error._need_free < 0); - assert_se(sd_bus_error_copy(&second, &temporarily_const_error) == -EACCES); - assert_se(second._need_free > 0); - assert_se(streq(temporarily_const_error.name, second.name)); - assert_se(streq(temporarily_const_error.message, second.message)); - assert_se(sd_bus_error_get_errno(&second) == EACCES); - assert_se(sd_bus_error_has_name(&second, SD_BUS_ERROR_ACCESS_DENIED)); - assert_se(sd_bus_error_is_set(&second)); - - assert_se(!sd_bus_error_is_set(&error)); - assert_se(sd_bus_error_set_const(&error, "System.Error.EUCLEAN", "Hallo") == -EUCLEAN); - assert_se(streq(error.name, "System.Error.EUCLEAN")); - assert_se(streq(error.message, "Hallo")); - assert_se(sd_bus_error_has_name(&error, "System.Error.EUCLEAN")); - assert_se(sd_bus_error_get_errno(&error) == EUCLEAN); - assert_se(sd_bus_error_is_set(&error)); - sd_bus_error_free(&error); - - assert_se(!sd_bus_error_is_set(&error)); - assert_se(sd_bus_error_set_errno(&error, EBUSY) == -EBUSY); - assert_se(streq(error.name, "System.Error.EBUSY")); - assert_se(streq(error.message, strerror(EBUSY))); - assert_se(sd_bus_error_has_name(&error, "System.Error.EBUSY")); - assert_se(sd_bus_error_get_errno(&error) == EBUSY); - assert_se(sd_bus_error_is_set(&error)); - sd_bus_error_free(&error); - - assert_se(!sd_bus_error_is_set(&error)); - assert_se(sd_bus_error_set_errnof(&error, EIO, "Waldi %c", 'X') == -EIO); - assert_se(streq(error.name, SD_BUS_ERROR_IO_ERROR)); - assert_se(streq(error.message, "Waldi X")); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR)); - assert_se(sd_bus_error_get_errno(&error) == EIO); - assert_se(sd_bus_error_is_set(&error)); - sd_bus_error_free(&error); - - /* Check with no error */ - assert_se(!sd_bus_error_is_set(&error)); - assert_se(sd_bus_error_set_errnof(&error, 0, "Waldi %c", 'X') == 0); - assert_se(error.name == NULL); - assert_se(error.message == NULL); - assert_se(!sd_bus_error_has_name(&error, SD_BUS_ERROR_IO_ERROR)); - assert_se(sd_bus_error_get_errno(&error) == 0); - assert_se(!sd_bus_error_is_set(&error)); -} - -extern const sd_bus_error_map __start_BUS_ERROR_MAP[]; -extern const sd_bus_error_map __stop_BUS_ERROR_MAP[]; - -static void dump_mapping_table(void) { - const sd_bus_error_map *m; - - printf("----- errno mappings ------\n"); - m = __start_BUS_ERROR_MAP; - while (m < __stop_BUS_ERROR_MAP) { - - if (m->code == BUS_ERROR_MAP_END_MARKER) { - m = ALIGN8_PTR(m+1); - continue; - } - - printf("%s -> %i/%s\n", strna(m->name), m->code, strna(errno_to_name(m->code))); - m ++; - } - printf("---------------------------\n"); -} - -static void test_errno_mapping_standard(void) { - assert_se(sd_bus_error_set(NULL, "System.Error.EUCLEAN", NULL) == -EUCLEAN); - assert_se(sd_bus_error_set(NULL, "System.Error.EBUSY", NULL) == -EBUSY); - assert_se(sd_bus_error_set(NULL, "System.Error.EINVAL", NULL) == -EINVAL); - assert_se(sd_bus_error_set(NULL, "System.Error.WHATSIT", NULL) == -EIO); -} - -BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors[] = { - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error", 5), - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-2", 52), - SD_BUS_ERROR_MAP_END -}; - -BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map test_errors2[] = { - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-3", 33), - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-4", 44), - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-33", 333), - SD_BUS_ERROR_MAP_END -}; - -static const sd_bus_error_map test_errors3[] = { - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-88", 888), - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-99", 999), - SD_BUS_ERROR_MAP_END -}; - -static const sd_bus_error_map test_errors4[] = { - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-77", 777), - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-78", 778), - SD_BUS_ERROR_MAP_END -}; - -static const sd_bus_error_map test_errors_bad1[] = { - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", 0), - SD_BUS_ERROR_MAP_END -}; - -static const sd_bus_error_map test_errors_bad2[] = { - SD_BUS_ERROR_MAP("org.freedesktop.custom-dbus-error-1", -1), - SD_BUS_ERROR_MAP_END -}; - -static void test_errno_mapping_custom(void) { - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error", NULL) == -5); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-2", NULL) == -52); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-x", NULL) == -EIO); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-33", NULL) == -333); - - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-88", NULL) == -EIO); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-99", NULL) == -EIO); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-77", NULL) == -EIO); - - assert_se(sd_bus_error_add_map(test_errors3) > 0); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-88", NULL) == -888); - assert_se(sd_bus_error_add_map(test_errors4) > 0); - assert_se(sd_bus_error_add_map(test_errors4) == 0); - assert_se(sd_bus_error_add_map(test_errors3) == 0); - - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-99", NULL) == -999); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-77", NULL) == -777); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-78", NULL) == -778); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-2", NULL) == -52); - assert_se(sd_bus_error_set(NULL, "org.freedesktop.custom-dbus-error-y", NULL) == -EIO); - - assert_se(sd_bus_error_set(NULL, BUS_ERROR_NO_SUCH_UNIT, NULL) == -ENOENT); - - assert_se(sd_bus_error_add_map(test_errors_bad1) == -EINVAL); - assert_se(sd_bus_error_add_map(test_errors_bad2) == -EINVAL); -} - -int main(int argc, char *argv[]) { - dump_mapping_table(); - - test_error(); - test_errno_mapping_standard(); - test_errno_mapping_custom(); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-gvariant.c b/src/libsystemd/sd-bus/test-bus-gvariant.c deleted file mode 100644 index 83f114a0fe..0000000000 --- a/src/libsystemd/sd-bus/test-bus-gvariant.c +++ /dev/null @@ -1,224 +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 . -***/ - -#ifdef HAVE_GLIB -#include -#endif - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-gvariant.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-util.h" -#include "macro.h" -#include "util.h" - -static void test_bus_gvariant_is_fixed_size(void) { - assert_se(bus_gvariant_is_fixed_size("") > 0); - assert_se(bus_gvariant_is_fixed_size("()") > 0); - assert_se(bus_gvariant_is_fixed_size("y") > 0); - assert_se(bus_gvariant_is_fixed_size("u") > 0); - assert_se(bus_gvariant_is_fixed_size("b") > 0); - assert_se(bus_gvariant_is_fixed_size("n") > 0); - assert_se(bus_gvariant_is_fixed_size("q") > 0); - assert_se(bus_gvariant_is_fixed_size("i") > 0); - assert_se(bus_gvariant_is_fixed_size("t") > 0); - assert_se(bus_gvariant_is_fixed_size("d") > 0); - assert_se(bus_gvariant_is_fixed_size("s") == 0); - assert_se(bus_gvariant_is_fixed_size("o") == 0); - assert_se(bus_gvariant_is_fixed_size("g") == 0); - assert_se(bus_gvariant_is_fixed_size("h") > 0); - assert_se(bus_gvariant_is_fixed_size("ay") == 0); - assert_se(bus_gvariant_is_fixed_size("v") == 0); - assert_se(bus_gvariant_is_fixed_size("(u)") > 0); - assert_se(bus_gvariant_is_fixed_size("(uuuuy)") > 0); - assert_se(bus_gvariant_is_fixed_size("(uusuuy)") == 0); - assert_se(bus_gvariant_is_fixed_size("a{ss}") == 0); - assert_se(bus_gvariant_is_fixed_size("((u)yyy(b(iiii)))") > 0); - assert_se(bus_gvariant_is_fixed_size("((u)yyy(b(iiivi)))") == 0); -} - -static void test_bus_gvariant_get_size(void) { - assert_se(bus_gvariant_get_size("") == 0); - assert_se(bus_gvariant_get_size("()") == 1); - assert_se(bus_gvariant_get_size("y") == 1); - assert_se(bus_gvariant_get_size("u") == 4); - assert_se(bus_gvariant_get_size("b") == 1); - assert_se(bus_gvariant_get_size("n") == 2); - assert_se(bus_gvariant_get_size("q") == 2); - assert_se(bus_gvariant_get_size("i") == 4); - assert_se(bus_gvariant_get_size("t") == 8); - assert_se(bus_gvariant_get_size("d") == 8); - assert_se(bus_gvariant_get_size("s") < 0); - assert_se(bus_gvariant_get_size("o") < 0); - assert_se(bus_gvariant_get_size("g") < 0); - assert_se(bus_gvariant_get_size("h") == 4); - assert_se(bus_gvariant_get_size("ay") < 0); - assert_se(bus_gvariant_get_size("v") < 0); - assert_se(bus_gvariant_get_size("(u)") == 4); - assert_se(bus_gvariant_get_size("(uuuuy)") == 20); - assert_se(bus_gvariant_get_size("(uusuuy)") < 0); - assert_se(bus_gvariant_get_size("a{ss}") < 0); - assert_se(bus_gvariant_get_size("((u)yyy(b(iiii)))") == 28); - assert_se(bus_gvariant_get_size("((u)yyy(b(iiivi)))") < 0); - assert_se(bus_gvariant_get_size("((b)(t))") == 16); - assert_se(bus_gvariant_get_size("((b)(b)(t))") == 16); - assert_se(bus_gvariant_get_size("(bt)") == 16); - assert_se(bus_gvariant_get_size("((t)(b))") == 16); - assert_se(bus_gvariant_get_size("(tb)") == 16); - assert_se(bus_gvariant_get_size("((b)(b))") == 2); - assert_se(bus_gvariant_get_size("((t)(t))") == 16); -} - -static void test_bus_gvariant_get_alignment(void) { - assert_se(bus_gvariant_get_alignment("") == 1); - assert_se(bus_gvariant_get_alignment("()") == 1); - assert_se(bus_gvariant_get_alignment("y") == 1); - assert_se(bus_gvariant_get_alignment("b") == 1); - assert_se(bus_gvariant_get_alignment("u") == 4); - assert_se(bus_gvariant_get_alignment("s") == 1); - assert_se(bus_gvariant_get_alignment("o") == 1); - assert_se(bus_gvariant_get_alignment("g") == 1); - assert_se(bus_gvariant_get_alignment("v") == 8); - assert_se(bus_gvariant_get_alignment("h") == 4); - assert_se(bus_gvariant_get_alignment("i") == 4); - assert_se(bus_gvariant_get_alignment("t") == 8); - assert_se(bus_gvariant_get_alignment("x") == 8); - assert_se(bus_gvariant_get_alignment("q") == 2); - assert_se(bus_gvariant_get_alignment("n") == 2); - assert_se(bus_gvariant_get_alignment("d") == 8); - assert_se(bus_gvariant_get_alignment("ay") == 1); - assert_se(bus_gvariant_get_alignment("as") == 1); - assert_se(bus_gvariant_get_alignment("au") == 4); - assert_se(bus_gvariant_get_alignment("an") == 2); - assert_se(bus_gvariant_get_alignment("ans") == 2); - assert_se(bus_gvariant_get_alignment("ant") == 8); - assert_se(bus_gvariant_get_alignment("(ss)") == 1); - assert_se(bus_gvariant_get_alignment("(ssu)") == 4); - assert_se(bus_gvariant_get_alignment("a(ssu)") == 4); - assert_se(bus_gvariant_get_alignment("(u)") == 4); - assert_se(bus_gvariant_get_alignment("(uuuuy)") == 4); - assert_se(bus_gvariant_get_alignment("(uusuuy)") == 4); - assert_se(bus_gvariant_get_alignment("a{ss}") == 1); - assert_se(bus_gvariant_get_alignment("((u)yyy(b(iiii)))") == 4); - assert_se(bus_gvariant_get_alignment("((u)yyy(b(iiivi)))") == 8); - assert_se(bus_gvariant_get_alignment("((b)(t))") == 8); - assert_se(bus_gvariant_get_alignment("((b)(b)(t))") == 8); - assert_se(bus_gvariant_get_alignment("(bt)") == 8); - assert_se(bus_gvariant_get_alignment("((t)(b))") == 8); - assert_se(bus_gvariant_get_alignment("(tb)") == 8); - assert_se(bus_gvariant_get_alignment("((b)(b))") == 1); - assert_se(bus_gvariant_get_alignment("((t)(t))") == 8); -} - -static void test_marshal(void) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *n = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ void *blob; - size_t sz; - int r; - - r = sd_bus_open_system(&bus); - if (r < 0) - exit(EXIT_TEST_SKIP); - - bus->message_version = 2; /* dirty hack to enable gvariant */ - - assert_se(sd_bus_message_new_method_call(bus, &m, "a.service.name", "/an/object/path/which/is/really/really/long/so/that/we/hit/the/eight/bit/boundary/by/quite/some/margin/to/test/this/stuff/that/it/really/works", "an.interface.name", "AMethodName") >= 0); - - assert_cc(sizeof(struct bus_header) == 16); - - assert_se(sd_bus_message_append(m, - "a(usv)", 3, - 4711, "first-string-parameter", "(st)", "X", (uint64_t) 1111, - 4712, "second-string-parameter", "(a(si))", 2, "Y", 5, "Z", 6, - 4713, "third-string-parameter", "(uu)", 1, 2) >= 0); - - assert_se(bus_message_seal(m, 4711, 0) >= 0); - -#ifdef HAVE_GLIB - { - GVariant *v; - char *t; - -#if !defined(GLIB_VERSION_2_36) - g_type_init(); -#endif - - v = g_variant_new_from_data(G_VARIANT_TYPE("(yyyyuta{tv})"), m->header, sizeof(struct bus_header) + m->fields_size, false, NULL, NULL); - assert_se(g_variant_is_normal_form(v)); - t = g_variant_print(v, TRUE); - printf("%s\n", t); - g_free(t); - g_variant_unref(v); - - v = g_variant_new_from_data(G_VARIANT_TYPE("(a(usv))"), m->body.data, m->user_body_size, false, NULL, NULL); - assert_se(g_variant_is_normal_form(v)); - t = g_variant_print(v, TRUE); - printf("%s\n", t); - g_free(t); - g_variant_unref(v); - } -#endif - - assert_se(bus_message_dump(m, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0); - - assert_se(bus_message_get_blob(m, &blob, &sz) >= 0); - -#ifdef HAVE_GLIB - { - GVariant *v; - char *t; - - v = g_variant_new_from_data(G_VARIANT_TYPE("(yyyyuta{tv}v)"), blob, sz, false, NULL, NULL); - assert_se(g_variant_is_normal_form(v)); - t = g_variant_print(v, TRUE); - printf("%s\n", t); - g_free(t); - g_variant_unref(v); - } -#endif - - assert_se(bus_message_from_malloc(bus, blob, sz, NULL, 0, NULL, &n) >= 0); - blob = NULL; - - assert_se(bus_message_dump(n, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0); - - m = sd_bus_message_unref(m); - - assert_se(sd_bus_message_new_method_call(bus, &m, "a.x", "/a/x", "a.x", "Ax") >= 0); - - assert_se(sd_bus_message_append(m, "as", 0) >= 0); - - assert_se(bus_message_seal(m, 4712, 0) >= 0); - assert_se(bus_message_dump(m, NULL, BUS_MESSAGE_DUMP_WITH_HEADER) >= 0); -} - -int main(int argc, char *argv[]) { - - test_bus_gvariant_is_fixed_size(); - test_bus_gvariant_get_size(); - test_bus_gvariant_get_alignment(); - test_marshal(); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-introspect.c b/src/libsystemd/sd-bus/test-bus-introspect.c deleted file mode 100644 index 4425cfae26..0000000000 --- a/src/libsystemd/sd-bus/test-bus-introspect.c +++ /dev/null @@ -1,63 +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 . -***/ - -#include "bus-introspect.h" -#include "log.h" - -static int prop_get(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { - return -EINVAL; -} - -static int prop_set(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { - return -EINVAL; -} - -static const sd_bus_vtable vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("Hello", "ssas", "a(uu)", NULL, 0), - SD_BUS_METHOD("DeprecatedHello", "", "", NULL, SD_BUS_VTABLE_DEPRECATED), - SD_BUS_METHOD("DeprecatedHelloNoReply", "", "", NULL, SD_BUS_VTABLE_DEPRECATED|SD_BUS_VTABLE_METHOD_NO_REPLY), - SD_BUS_SIGNAL("Wowza", "sss", 0), - SD_BUS_SIGNAL("DeprecatedWowza", "ut", SD_BUS_VTABLE_DEPRECATED), - SD_BUS_WRITABLE_PROPERTY("AProperty", "s", prop_get, prop_set, 0, 0), - SD_BUS_PROPERTY("AReadOnlyDeprecatedProperty", "(ut)", prop_get, 0, SD_BUS_VTABLE_DEPRECATED), - SD_BUS_PROPERTY("ChangingProperty", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Invalidating", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_PROPERTY("Constant", "t", prop_get, 0, SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_PROPERTY_EXPLICIT), - SD_BUS_VTABLE_END -}; - -int main(int argc, char *argv[]) { - struct introspect intro; - - log_set_max_level(LOG_DEBUG); - - assert_se(introspect_begin(&intro, false) >= 0); - - fprintf(intro.f, " \n"); - assert_se(introspect_write_interface(&intro, vtable) >= 0); - fputs(" \n", intro.f); - - fflush(intro.f); - fputs(intro.introspection, stdout); - - introspect_free(&intro); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-kernel-bloom.c b/src/libsystemd/sd-bus/test-bus-kernel-bloom.c deleted file mode 100644 index eb6179d7d2..0000000000 --- a/src/libsystemd/sd-bus/test-bus-kernel-bloom.c +++ /dev/null @@ -1,141 +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 . -***/ - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-kernel.h" -#include "bus-util.h" -#include "fd-util.h" -#include "log.h" -#include "util.h" - -static int test_match(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - int *found = userdata; - - *found = 1; - - return 0; -} - -static void test_one( - const char *path, - const char *interface, - const char *member, - bool as_list, - const char *arg0, - const char *match, - bool good) { - - _cleanup_close_ int bus_ref = -1; - _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - sd_bus *a, *b; - int r, found = 0; - - assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); - - bus_ref = bus_kernel_create_bus(name, false, &bus_name); - if (bus_ref == -ENOENT) - exit(EXIT_TEST_SKIP); - - assert_se(bus_ref >= 0); - - address = strappend("kernel:path=", bus_name); - assert_se(address); - - r = sd_bus_new(&a); - assert_se(r >= 0); - - r = sd_bus_new(&b); - assert_se(r >= 0); - - r = sd_bus_set_address(a, address); - assert_se(r >= 0); - - r = sd_bus_set_address(b, address); - assert_se(r >= 0); - - r = sd_bus_start(a); - assert_se(r >= 0); - - r = sd_bus_start(b); - assert_se(r >= 0); - - log_debug("match"); - r = sd_bus_add_match(b, NULL, match, test_match, &found); - assert_se(r >= 0); - - log_debug("signal"); - - if (as_list) - r = sd_bus_emit_signal(a, path, interface, member, "as", 1, arg0); - else - r = sd_bus_emit_signal(a, path, interface, member, "s", arg0); - assert_se(r >= 0); - - r = sd_bus_process(b, &m); - assert_se(r >= 0 && good == !!found); - - sd_bus_unref(a); - sd_bus_unref(b); -} - -int main(int argc, char *argv[]) { - log_set_max_level(LOG_DEBUG); - - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo/tuut'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "interface='waldo.com'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "member='Piep'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "member='Pi_ep'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "arg0='foobar'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "arg0='foo_bar'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0='foobar'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0='foo_bar'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0has='foobar'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", true, "foobar", "arg0has='foo_bar'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo',interface='waldo.com',member='Piep',arg0='foobar'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo',interface='waldo.com',member='Piep',arg0='foobar2'", false); - - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo/quux'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/bar/waldo'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/bar'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/quux'", false); - test_one("/", "waldo.com", "Piep", false, "foobar", "path_namespace='/'", true); - - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/bar/waldo/'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path='/foo/'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/bar/waldo/'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "foobar", "path_namespace='/foo/'", true); - - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo/bar/waldo", "arg0path='/foo/'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo", "arg0path='/foo'", true); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo", "arg0path='/foo/bar/waldo'", false); - test_one("/foo/bar/waldo", "waldo.com", "Piep", false, "/foo/", "arg0path='/foo/bar/waldo'", true); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-kernel.c b/src/libsystemd/sd-bus/test-bus-kernel.c deleted file mode 100644 index 2214817312..0000000000 --- a/src/libsystemd/sd-bus/test-bus-kernel.c +++ /dev/null @@ -1,190 +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 . -***/ - -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-kernel.h" -#include "bus-util.h" -#include "fd-util.h" -#include "log.h" -#include "util.h" - -int main(int argc, char *argv[]) { - _cleanup_close_ int bus_ref = -1; - _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL, *bname = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *ua = NULL, *ub = NULL, *the_string = NULL; - sd_bus *a, *b; - int r, pipe_fds[2]; - const char *nn; - - log_set_max_level(LOG_DEBUG); - - assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); - - bus_ref = bus_kernel_create_bus(name, false, &bus_name); - if (bus_ref == -ENOENT) - return EXIT_TEST_SKIP; - - assert_se(bus_ref >= 0); - - address = strappend("kernel:path=", bus_name); - assert_se(address); - - r = sd_bus_new(&a); - assert_se(r >= 0); - - r = sd_bus_new(&b); - assert_se(r >= 0); - - r = sd_bus_set_description(a, "a"); - assert_se(r >= 0); - - r = sd_bus_set_address(a, address); - assert_se(r >= 0); - - r = sd_bus_set_address(b, address); - assert_se(r >= 0); - - assert_se(sd_bus_negotiate_timestamp(a, 1) >= 0); - assert_se(sd_bus_negotiate_creds(a, true, _SD_BUS_CREDS_ALL) >= 0); - - assert_se(sd_bus_negotiate_timestamp(b, 0) >= 0); - assert_se(sd_bus_negotiate_creds(b, true, 0) >= 0); - - r = sd_bus_start(a); - assert_se(r >= 0); - - r = sd_bus_start(b); - assert_se(r >= 0); - - assert_se(sd_bus_negotiate_timestamp(b, 1) >= 0); - assert_se(sd_bus_negotiate_creds(b, true, _SD_BUS_CREDS_ALL) >= 0); - - r = sd_bus_get_unique_name(a, &ua); - assert_se(r >= 0); - printf("unique a: %s\n", ua); - - r = sd_bus_get_description(a, &nn); - assert_se(r >= 0); - printf("name of a: %s\n", nn); - - r = sd_bus_get_unique_name(b, &ub); - assert_se(r >= 0); - printf("unique b: %s\n", ub); - - r = sd_bus_get_description(b, &nn); - assert_se(r >= 0); - printf("name of b: %s\n", nn); - - assert_se(bus_kernel_get_bus_name(b, &bname) >= 0); - assert_se(endswith(bname, name)); - - r = sd_bus_call_method(a, "this.doesnt.exist", "/foo", "meh.mah", "muh", &error, NULL, "s", "yayayay"); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_SERVICE_UNKNOWN)); - assert_se(r == -EHOSTUNREACH); - - r = sd_bus_add_match(b, NULL, "interface='waldo.com',member='Piep'", NULL, NULL); - assert_se(r >= 0); - - r = sd_bus_emit_signal(a, "/foo/bar/waldo", "waldo.com", "Piep", "sss", "I am a string", "/this/is/a/path", "and.this.a.domain.name"); - assert_se(r >= 0); - - r = sd_bus_try_close(b); - assert_se(r == -EBUSY); - - r = sd_bus_process_priority(b, -10, &m); - assert_se(r == 0); - - r = sd_bus_process(b, &m); - assert_se(r > 0); - assert_se(m); - - bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - assert_se(sd_bus_message_rewind(m, true) >= 0); - - r = sd_bus_message_read(m, "s", &the_string); - assert_se(r >= 0); - assert_se(streq(the_string, "I am a string")); - - sd_bus_message_unref(m); - m = NULL; - - r = sd_bus_request_name(a, "net.x0pointer.foobar", 0); - assert_se(r >= 0); - - r = sd_bus_message_new_method_call(b, &m, "net.x0pointer.foobar", "/a/path", "an.inter.face", "AMethod"); - assert_se(r >= 0); - - assert_se(pipe2(pipe_fds, O_CLOEXEC) >= 0); - - assert_se(write(pipe_fds[1], "x", 1) == 1); - - pipe_fds[1] = safe_close(pipe_fds[1]); - - r = sd_bus_message_append(m, "h", pipe_fds[0]); - assert_se(r >= 0); - - pipe_fds[0] = safe_close(pipe_fds[0]); - - r = sd_bus_send(b, m, NULL); - assert_se(r >= 0); - - for (;;) { - sd_bus_message_unref(m); - m = NULL; - r = sd_bus_process(a, &m); - assert_se(r > 0); - assert_se(m); - - bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - assert_se(sd_bus_message_rewind(m, true) >= 0); - - if (sd_bus_message_is_method_call(m, "an.inter.face", "AMethod")) { - int fd; - char x; - - r = sd_bus_message_read(m, "h", &fd); - assert_se(r >= 0); - - assert_se(read(fd, &x, 1) == 1); - assert_se(x == 'x'); - break; - } - } - - r = sd_bus_release_name(a, "net.x0pointer.foobar"); - assert_se(r >= 0); - - r = sd_bus_release_name(a, "net.x0pointer.foobar"); - assert_se(r == -ESRCH); - - r = sd_bus_try_close(a); - assert_se(r >= 0); - - sd_bus_unref(a); - sd_bus_unref(b); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-marshal.c b/src/libsystemd/sd-bus/test-bus-marshal.c deleted file mode 100644 index a28cc5b79e..0000000000 --- a/src/libsystemd/sd-bus/test-bus-marshal.c +++ /dev/null @@ -1,432 +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 . -***/ - -#include -#include - -#ifdef HAVE_GLIB -#include -#endif - -#ifdef HAVE_DBUS -#include -#endif - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-label.h" -#include "bus-message.h" -#include "bus-util.h" -#include "fd-util.h" -#include "hexdecoct.h" -#include "log.h" -#include "util.h" - -static void test_bus_path_encode_unique(void) { - _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL; - - assert_se(bus_path_encode_unique(NULL, "/foo/bar", "some.sender", "a.suffix", &a) >= 0 && streq_ptr(a, "/foo/bar/some_2esender/a_2esuffix")); - assert_se(bus_path_decode_unique(a, "/foo/bar", &b, &c) > 0 && streq_ptr(b, "some.sender") && streq_ptr(c, "a.suffix")); - assert_se(bus_path_decode_unique(a, "/bar/foo", &d, &d) == 0 && !d); - assert_se(bus_path_decode_unique("/foo/bar/onlyOneSuffix", "/foo/bar", &d, &d) == 0 && !d); - assert_se(bus_path_decode_unique("/foo/bar/_/_", "/foo/bar", &d, &e) > 0 && streq_ptr(d, "") && streq_ptr(e, "")); -} - -static void test_bus_path_encode(void) { - _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *f = NULL; - - assert_se(sd_bus_path_encode("/foo/bar", "waldo", &a) >= 0 && streq(a, "/foo/bar/waldo")); - assert_se(sd_bus_path_decode(a, "/waldo", &b) == 0 && b == NULL); - assert_se(sd_bus_path_decode(a, "/foo/bar", &b) > 0 && streq(b, "waldo")); - - assert_se(sd_bus_path_encode("xxxx", "waldo", &c) < 0); - assert_se(sd_bus_path_encode("/foo/", "waldo", &c) < 0); - - assert_se(sd_bus_path_encode("/foo/bar", "", &c) >= 0 && streq(c, "/foo/bar/_")); - assert_se(sd_bus_path_decode(c, "/foo/bar", &d) > 0 && streq(d, "")); - - assert_se(sd_bus_path_encode("/foo/bar", "foo.bar", &e) >= 0 && streq(e, "/foo/bar/foo_2ebar")); - assert_se(sd_bus_path_decode(e, "/foo/bar", &f) > 0 && streq(f, "foo.bar")); -} - -static void test_bus_path_encode_many(void) { - _cleanup_free_ char *a = NULL, *b = NULL, *c = NULL, *d = NULL, *e = NULL, *f = NULL; - - assert_se(sd_bus_path_decode_many("/foo/bar", "/prefix/%", NULL) == 0); - assert_se(sd_bus_path_decode_many("/prefix/bar", "/prefix/%bar", NULL) == 1); - assert_se(sd_bus_path_decode_many("/foo/bar", "/prefix/%/suffix", NULL) == 0); - assert_se(sd_bus_path_decode_many("/prefix/foobar/suffix", "/prefix/%/suffix", &a) == 1 && streq_ptr(a, "foobar")); - assert_se(sd_bus_path_decode_many("/prefix/one_foo_two/mid/three_bar_four/suffix", "/prefix/one_%_two/mid/three_%_four/suffix", &b, &c) == 1 && streq_ptr(b, "foo") && streq_ptr(c, "bar")); - assert_se(sd_bus_path_decode_many("/prefix/one_foo_two/mid/three_bar_four/suffix", "/prefix/one_%_two/mid/three_%_four/suffix", NULL, &d) == 1 && streq_ptr(d, "bar")); - - assert_se(sd_bus_path_decode_many("/foo/bar", "/foo/bar/%", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/bar%", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/bar", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%bar", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/bar/suffix") == 1); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%%/suffix", NULL, NULL) == 0); /* multiple '%' are treated verbatim */ - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/suffi", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/suffix", &e) == 1 && streq_ptr(e, "bar")); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/foo/%/%", NULL, NULL) == 1); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%/%", NULL, NULL, NULL) == 1); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "%/%/%", NULL, NULL, NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%", NULL, NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/%/", NULL, NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%/", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "/%", NULL) == 0); - assert_se(sd_bus_path_decode_many("/foo/bar/suffix", "%", NULL) == 0); - - assert_se(sd_bus_path_encode_many(&f, "/prefix/one_%_two/mid/three_%_four/suffix", "foo", "bar") >= 0 && streq_ptr(f, "/prefix/one_foo_two/mid/three_bar_four/suffix")); -} - -static void test_bus_label_escape_one(const char *a, const char *b) { - _cleanup_free_ char *t = NULL, *x = NULL, *y = NULL; - - assert_se(t = bus_label_escape(a)); - assert_se(streq(t, b)); - - assert_se(x = bus_label_unescape(t)); - assert_se(streq(a, x)); - - assert_se(y = bus_label_unescape(b)); - assert_se(streq(a, y)); -} - -static void test_bus_label_escape(void) { - test_bus_label_escape_one("foo123bar", "foo123bar"); - test_bus_label_escape_one("foo.bar", "foo_2ebar"); - test_bus_label_escape_one("foo_2ebar", "foo_5f2ebar"); - test_bus_label_escape_one("", "_"); - test_bus_label_escape_one("_", "_5f"); - test_bus_label_escape_one("1", "_31"); - test_bus_label_escape_one(":1", "_3a1"); -} - -int main(int argc, char *argv[]) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *copy = NULL; - int r, boolean; - const char *x, *x2, *y, *z, *a, *b, *c, *d, *a_signature; - uint8_t u, v; - void *buffer = NULL; - size_t sz; - char *h; - const int32_t integer_array[] = { -1, -2, 0, 1, 2 }, *return_array; - char *s; - _cleanup_free_ char *first = NULL, *second = NULL, *third = NULL; - _cleanup_fclose_ FILE *ms = NULL; - size_t first_size = 0, second_size = 0, third_size = 0; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - double dbl; - uint64_t u64; - - r = sd_bus_default_system(&bus); - if (r < 0) - return EXIT_TEST_SKIP; - - r = sd_bus_message_new_method_call(bus, &m, "foobar.waldo", "/", "foobar.waldo", "Piep"); - assert_se(r >= 0); - - r = sd_bus_message_append(m, ""); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "s", "a string"); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "s", NULL); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "asg", 2, "string #1", "string #2", "sba(tt)ss"); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "sass", "foobar", 5, "foo", "bar", "waldo", "piep", "pap", "after"); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "a{yv}", 2, 3, "s", "foo", 5, "s", "waldo"); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "y(ty)y(yt)y", 8, 777ULL, 7, 9, 77, 7777ULL, 10); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "()"); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "ba(ss)", 255, 3, "aaa", "1", "bbb", "2", "ccc", "3"); - assert_se(r >= 0); - - r = sd_bus_message_open_container(m, 'a', "s"); - assert_se(r >= 0); - - r = sd_bus_message_append_basic(m, 's', "foobar"); - assert_se(r >= 0); - - r = sd_bus_message_append_basic(m, 's', "waldo"); - assert_se(r >= 0); - - r = sd_bus_message_close_container(m); - assert_se(r >= 0); - - r = sd_bus_message_append_string_space(m, 5, &s); - assert_se(r >= 0); - strcpy(s, "hallo"); - - r = sd_bus_message_append_array(m, 'i', integer_array, sizeof(integer_array)); - assert_se(r >= 0); - - r = sd_bus_message_append_array(m, 'u', NULL, 0); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "a(stdo)", 1, "foo", 815ULL, 47.0, "/"); - assert_se(r >= 0); - - r = bus_message_seal(m, 4711, 0); - assert_se(r >= 0); - - bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - ms = open_memstream(&first, &first_size); - bus_message_dump(m, ms, 0); - fflush(ms); - assert_se(!ferror(ms)); - - r = bus_message_get_blob(m, &buffer, &sz); - assert_se(r >= 0); - - h = hexmem(buffer, sz); - assert_se(h); - - log_info("message size = %zu, contents =\n%s", sz, h); - free(h); - -#ifdef HAVE_GLIB - { - GDBusMessage *g; - char *p; - -#if !defined(GLIB_VERSION_2_36) - g_type_init(); -#endif - - g = g_dbus_message_new_from_blob(buffer, sz, 0, NULL); - p = g_dbus_message_print(g, 0); - log_info("%s", p); - g_free(p); - g_object_unref(g); - } -#endif - -#ifdef HAVE_DBUS - { - DBusMessage *w; - DBusError error; - - dbus_error_init(&error); - - w = dbus_message_demarshal(buffer, sz, &error); - if (!w) - log_error("%s", error.message); - else - dbus_message_unref(w); - - dbus_error_free(&error); - } -#endif - - m = sd_bus_message_unref(m); - - r = bus_message_from_malloc(bus, buffer, sz, NULL, 0, NULL, &m); - assert_se(r >= 0); - - bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - fclose(ms); - ms = open_memstream(&second, &second_size); - bus_message_dump(m, ms, 0); - fflush(ms); - assert_se(!ferror(ms)); - assert_se(first_size == second_size); - assert_se(memcmp(first, second, first_size) == 0); - - assert_se(sd_bus_message_rewind(m, true) >= 0); - - r = sd_bus_message_read(m, "ssasg", &x, &x2, 2, &y, &z, &a_signature); - assert_se(r > 0); - assert_se(streq(x, "a string")); - assert_se(streq(x2, "")); - assert_se(streq(y, "string #1")); - assert_se(streq(z, "string #2")); - assert_se(streq(a_signature, "sba(tt)ss")); - - r = sd_bus_message_read(m, "sass", &x, 5, &y, &z, &a, &b, &c, &d); - assert_se(r > 0); - assert_se(streq(x, "foobar")); - assert_se(streq(y, "foo")); - assert_se(streq(z, "bar")); - assert_se(streq(a, "waldo")); - assert_se(streq(b, "piep")); - assert_se(streq(c, "pap")); - assert_se(streq(d, "after")); - - r = sd_bus_message_read(m, "a{yv}", 2, &u, "s", &x, &v, "s", &y); - assert_se(r > 0); - assert_se(u == 3); - assert_se(streq(x, "foo")); - assert_se(v == 5); - assert_se(streq(y, "waldo")); - - r = sd_bus_message_read(m, "y(ty)", &v, &u64, &u); - assert_se(r > 0); - assert_se(v == 8); - assert_se(u64 == 777); - assert_se(u == 7); - - r = sd_bus_message_read(m, "y(yt)", &v, &u, &u64); - assert_se(r > 0); - assert_se(v == 9); - assert_se(u == 77); - assert_se(u64 == 7777); - - r = sd_bus_message_read(m, "y", &v); - assert_se(r > 0); - assert_se(v == 10); - - r = sd_bus_message_read(m, "()"); - assert_se(r > 0); - - r = sd_bus_message_read(m, "ba(ss)", &boolean, 3, &x, &y, &a, &b, &c, &d); - assert_se(r > 0); - assert_se(boolean); - assert_se(streq(x, "aaa")); - assert_se(streq(y, "1")); - assert_se(streq(a, "bbb")); - assert_se(streq(b, "2")); - assert_se(streq(c, "ccc")); - assert_se(streq(d, "3")); - - assert_se(sd_bus_message_verify_type(m, 'a', "s") > 0); - - r = sd_bus_message_read(m, "as", 2, &x, &y); - assert_se(r > 0); - assert_se(streq(x, "foobar")); - assert_se(streq(y, "waldo")); - - r = sd_bus_message_read_basic(m, 's', &s); - assert_se(r > 0); - assert_se(streq(s, "hallo")); - - r = sd_bus_message_read_array(m, 'i', (const void**) &return_array, &sz); - assert_se(r > 0); - assert_se(sz == sizeof(integer_array)); - assert_se(memcmp(integer_array, return_array, sz) == 0); - - r = sd_bus_message_read_array(m, 'u', (const void**) &return_array, &sz); - assert_se(r > 0); - assert_se(sz == 0); - - r = sd_bus_message_read(m, "a(stdo)", 1, &x, &u64, &dbl, &y); - assert_se(r > 0); - assert_se(streq(x, "foo")); - assert_se(u64 == 815ULL); - assert_se(fabs(dbl - 47.0) < 0.1); - assert_se(streq(y, "/")); - - r = sd_bus_message_peek_type(m, NULL, NULL); - assert_se(r == 0); - - r = sd_bus_message_new_method_call(bus, ©, "foobar.waldo", "/", "foobar.waldo", "Piep"); - assert_se(r >= 0); - - r = sd_bus_message_rewind(m, true); - assert_se(r >= 0); - - r = sd_bus_message_copy(copy, m, true); - assert_se(r >= 0); - - r = bus_message_seal(copy, 4712, 0); - assert_se(r >= 0); - - fclose(ms); - ms = open_memstream(&third, &third_size); - bus_message_dump(copy, ms, 0); - fflush(ms); - assert_se(!ferror(ms)); - - printf("<%.*s>\n", (int) first_size, first); - printf("<%.*s>\n", (int) third_size, third); - - assert_se(first_size == third_size); - assert_se(memcmp(first, third, third_size) == 0); - - r = sd_bus_message_rewind(m, true); - assert_se(r >= 0); - - assert_se(sd_bus_message_verify_type(m, 's', NULL) > 0); - - r = sd_bus_message_skip(m, "ssasg"); - assert_se(r > 0); - - assert_se(sd_bus_message_verify_type(m, 's', NULL) > 0); - - r = sd_bus_message_skip(m, "sass"); - assert_se(r >= 0); - - assert_se(sd_bus_message_verify_type(m, 'a', "{yv}") > 0); - - r = sd_bus_message_skip(m, "a{yv}y(ty)y(yt)y()"); - assert_se(r >= 0); - - assert_se(sd_bus_message_verify_type(m, 'b', NULL) > 0); - - r = sd_bus_message_read(m, "b", &boolean); - assert_se(r > 0); - assert_se(boolean); - - r = sd_bus_message_enter_container(m, 0, NULL); - assert_se(r > 0); - - r = sd_bus_message_read(m, "(ss)", &x, &y); - assert_se(r > 0); - - r = sd_bus_message_read(m, "(ss)", &a, &b); - assert_se(r > 0); - - r = sd_bus_message_read(m, "(ss)", &c, &d); - assert_se(r > 0); - - r = sd_bus_message_read(m, "(ss)", &x, &y); - assert_se(r == 0); - - r = sd_bus_message_exit_container(m); - assert_se(r >= 0); - - assert_se(streq(x, "aaa")); - assert_se(streq(y, "1")); - assert_se(streq(a, "bbb")); - assert_se(streq(b, "2")); - assert_se(streq(c, "ccc")); - assert_se(streq(d, "3")); - - test_bus_label_escape(); - test_bus_path_encode(); - test_bus_path_encode_unique(); - test_bus_path_encode_many(); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-match.c b/src/libsystemd/sd-bus/test-bus-match.c deleted file mode 100644 index 29c4529f95..0000000000 --- a/src/libsystemd/sd-bus/test-bus-match.c +++ /dev/null @@ -1,159 +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 . -***/ - -#include "bus-match.h" -#include "bus-message.h" -#include "bus-slot.h" -#include "bus-util.h" -#include "log.h" -#include "macro.h" - -static bool mask[32]; - -static int filter(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - log_info("Ran %u", PTR_TO_UINT(userdata)); - assert_se(PTR_TO_UINT(userdata) < ELEMENTSOF(mask)); - mask[PTR_TO_UINT(userdata)] = true; - return 0; -} - -static bool mask_contains(unsigned a[], unsigned n) { - unsigned i, j; - - for (i = 0; i < ELEMENTSOF(mask); i++) { - bool found = false; - - for (j = 0; j < n; j++) - if (a[j] == i) { - found = true; - break; - } - - if (found != mask[i]) - return false; - } - - return true; -} - -static int match_add(sd_bus_slot *slots, struct bus_match_node *root, const char *match, int value) { - struct bus_match_component *components = NULL; - unsigned n_components = 0; - sd_bus_slot *s; - int r; - - s = slots + value; - zero(*s); - - r = bus_match_parse(match, &components, &n_components); - if (r < 0) - return r; - - s->userdata = INT_TO_PTR(value); - s->match_callback.callback = filter; - - r = bus_match_add(root, components, n_components, &s->match_callback); - bus_match_parse_free(components, n_components); - - return r; -} - -static void test_match_scope(const char *match, enum bus_match_scope scope) { - struct bus_match_component *components = NULL; - unsigned n_components = 0; - - assert_se(bus_match_parse(match, &components, &n_components) >= 0); - assert_se(bus_match_get_scope(components, n_components) == scope); - bus_match_parse_free(components, n_components); -} - -int main(int argc, char *argv[]) { - struct bus_match_node root = { - .type = BUS_MATCH_ROOT, - }; - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - enum bus_match_node_type i; - sd_bus_slot slots[19]; - int r; - - r = sd_bus_open_system(&bus); - if (r < 0) - return EXIT_TEST_SKIP; - - assert_se(match_add(slots, &root, "arg2='wal\\'do',sender='foo',type='signal',interface='bar.x',", 1) >= 0); - assert_se(match_add(slots, &root, "arg2='wal\\'do2',sender='foo',type='signal',interface='bar.x',", 2) >= 0); - assert_se(match_add(slots, &root, "arg3='test',sender='foo',type='signal',interface='bar.x',", 3) >= 0); - assert_se(match_add(slots, &root, "arg3='test',sender='foo',type='method_call',interface='bar.x',", 4) >= 0); - assert_se(match_add(slots, &root, "", 5) >= 0); - assert_se(match_add(slots, &root, "interface='quux.x'", 6) >= 0); - assert_se(match_add(slots, &root, "interface='bar.x'", 7) >= 0); - assert_se(match_add(slots, &root, "member='waldo',path='/foo/bar'", 8) >= 0); - assert_se(match_add(slots, &root, "path='/foo/bar'", 9) >= 0); - assert_se(match_add(slots, &root, "path_namespace='/foo'", 10) >= 0); - assert_se(match_add(slots, &root, "path_namespace='/foo/quux'", 11) >= 0); - assert_se(match_add(slots, &root, "arg1='two'", 12) >= 0); - assert_se(match_add(slots, &root, "member='waldo',arg2path='/prefix/'", 13) >= 0); - assert_se(match_add(slots, &root, "member=waldo,path='/foo/bar',arg3namespace='prefix'", 14) >= 0); - assert_se(match_add(slots, &root, "arg4has='pi'", 15) >= 0); - assert_se(match_add(slots, &root, "arg4has='pa'", 16) >= 0); - assert_se(match_add(slots, &root, "arg4has='po'", 17) >= 0); - assert_se(match_add(slots, &root, "arg4='pi'", 18) >= 0); - - bus_match_dump(&root, 0); - - assert_se(sd_bus_message_new_signal(bus, &m, "/foo/bar", "bar.x", "waldo") >= 0); - assert_se(sd_bus_message_append(m, "ssssas", "one", "two", "/prefix/three", "prefix.four", 3, "pi", "pa", "po") >= 0); - assert_se(bus_message_seal(m, 1, 0) >= 0); - - zero(mask); - assert_se(bus_match_run(NULL, &root, m) == 0); - assert_se(mask_contains((unsigned[]) { 9, 8, 7, 5, 10, 12, 13, 14, 15, 16, 17 }, 11)); - - assert_se(bus_match_remove(&root, &slots[8].match_callback) >= 0); - assert_se(bus_match_remove(&root, &slots[13].match_callback) >= 0); - - bus_match_dump(&root, 0); - - zero(mask); - assert_se(bus_match_run(NULL, &root, m) == 0); - assert_se(mask_contains((unsigned[]) { 9, 5, 10, 12, 14, 7, 15, 16, 17 }, 9)); - - for (i = 0; i < _BUS_MATCH_NODE_TYPE_MAX; i++) { - char buf[32]; - const char *x; - - assert_se(x = bus_match_node_type_to_string(i, buf, sizeof(buf))); - - if (i >= BUS_MATCH_MESSAGE_TYPE) - assert_se(bus_match_node_type_from_string(x, strlen(x)) == i); - } - - bus_match_free(&root); - - test_match_scope("interface='foobar'", BUS_MATCH_GENERIC); - test_match_scope("", BUS_MATCH_GENERIC); - test_match_scope("interface='org.freedesktop.DBus.Local'", BUS_MATCH_LOCAL); - test_match_scope("sender='org.freedesktop.DBus.Local'", BUS_MATCH_LOCAL); - test_match_scope("member='gurke',path='/org/freedesktop/DBus/Local'", BUS_MATCH_LOCAL); - test_match_scope("arg2='piep',sender='org.freedesktop.DBus',member='waldo'", BUS_MATCH_DRIVER); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-objects.c b/src/libsystemd/sd-bus/test-bus-objects.c deleted file mode 100644 index f11cafd888..0000000000 --- a/src/libsystemd/sd-bus/test-bus-objects.c +++ /dev/null @@ -1,555 +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 . -***/ - -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-internal.h" -#include "bus-message.h" -#include "bus-util.h" -#include "log.h" -#include "macro.h" -#include "strv.h" -#include "util.h" - -struct context { - int fds[2]; - bool quit; - char *something; - char *automatic_string_property; - uint32_t automatic_integer_property; -}; - -static int something_handler(sd_bus_message *m, void *userdata, sd_bus_error *error) { - struct context *c = userdata; - const char *s; - char *n = NULL; - int r; - - r = sd_bus_message_read(m, "s", &s); - assert_se(r > 0); - - n = strjoin("<<<", s, ">>>", NULL); - assert_se(n); - - free(c->something); - c->something = n; - - log_info("AlterSomething() called, got %s, returning %s", s, n); - - /* This should fail, since the return type doesn't match */ - assert_se(sd_bus_reply_method_return(m, "u", 4711) == -ENOMSG); - - r = sd_bus_reply_method_return(m, "s", n); - assert_se(r >= 0); - - return 1; -} - -static int exit_handler(sd_bus_message *m, void *userdata, sd_bus_error *error) { - struct context *c = userdata; - int r; - - c->quit = true; - - log_info("Exit called"); - - r = sd_bus_reply_method_return(m, ""); - assert_se(r >= 0); - - return 1; -} - -static int get_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { - struct context *c = userdata; - int r; - - log_info("property get for %s called, returning \"%s\".", property, c->something); - - r = sd_bus_message_append(reply, "s", c->something); - assert_se(r >= 0); - - return 1; -} - -static int set_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *value, void *userdata, sd_bus_error *error) { - struct context *c = userdata; - const char *s; - char *n; - int r; - - log_info("property set for %s called", property); - - r = sd_bus_message_read(value, "s", &s); - assert_se(r >= 0); - - n = strdup(s); - assert_se(n); - - free(c->something); - c->something = n; - - return 1; -} - -static int value_handler(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *s = NULL; - const char *x; - int r; - - assert_se(asprintf(&s, "object %p, path %s", userdata, path) >= 0); - r = sd_bus_message_append(reply, "s", s); - assert_se(r >= 0); - - assert_se(x = startswith(path, "/value/")); - - assert_se(PTR_TO_UINT(userdata) == 30); - - return 1; -} - -static int notify_test(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int r; - - assert_se(sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), m->path, "org.freedesktop.systemd.ValueTest", "Value", NULL) >= 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - - return 1; -} - -static int notify_test2(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int r; - - assert_se(sd_bus_emit_properties_changed_strv(sd_bus_message_get_bus(m), m->path, "org.freedesktop.systemd.ValueTest", NULL) >= 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - - return 1; -} - -static int emit_interfaces_added(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int r; - - assert_se(sd_bus_emit_interfaces_added(sd_bus_message_get_bus(m), "/value/a/x", "org.freedesktop.systemd.ValueTest", NULL) >= 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - - return 1; -} - -static int emit_interfaces_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int r; - - assert_se(sd_bus_emit_interfaces_removed(sd_bus_message_get_bus(m), "/value/a/x", "org.freedesktop.systemd.ValueTest", NULL) >= 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - - return 1; -} - -static int emit_object_added(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int r; - - assert_se(sd_bus_emit_object_added(sd_bus_message_get_bus(m), "/value/a/x") >= 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - - return 1; -} - -static int emit_object_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int r; - - assert_se(sd_bus_emit_object_removed(sd_bus_message_get_bus(m), "/value/a/x") >= 0); - - r = sd_bus_reply_method_return(m, NULL); - assert_se(r >= 0); - - return 1; -} - -static const sd_bus_vtable vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("AlterSomething", "s", "s", something_handler, 0), - SD_BUS_METHOD("Exit", "", "", exit_handler, 0), - SD_BUS_WRITABLE_PROPERTY("Something", "s", get_handler, set_handler, 0, 0), - SD_BUS_WRITABLE_PROPERTY("AutomaticStringProperty", "s", NULL, NULL, offsetof(struct context, automatic_string_property), 0), - SD_BUS_WRITABLE_PROPERTY("AutomaticIntegerProperty", "u", NULL, NULL, offsetof(struct context, automatic_integer_property), 0), - SD_BUS_METHOD("NoOperation", NULL, NULL, NULL, 0), - SD_BUS_METHOD("EmitInterfacesAdded", NULL, NULL, emit_interfaces_added, 0), - SD_BUS_METHOD("EmitInterfacesRemoved", NULL, NULL, emit_interfaces_removed, 0), - SD_BUS_METHOD("EmitObjectAdded", NULL, NULL, emit_object_added, 0), - SD_BUS_METHOD("EmitObjectRemoved", NULL, NULL, emit_object_removed, 0), - SD_BUS_VTABLE_END -}; - -static const sd_bus_vtable vtable2[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_METHOD("NotifyTest", "", "", notify_test, 0), - SD_BUS_METHOD("NotifyTest2", "", "", notify_test2, 0), - SD_BUS_PROPERTY("Value", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("Value2", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_PROPERTY("Value3", "s", value_handler, 10, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Value4", "s", value_handler, 10, 0), - SD_BUS_PROPERTY("AnExplicitProperty", "s", NULL, offsetof(struct context, something), SD_BUS_VTABLE_PROPERTY_EXPLICIT|SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), - SD_BUS_VTABLE_END -}; - -static int enumerator_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - - if (object_path_startswith("/value", path)) - assert_se(*nodes = strv_new("/value/a", "/value/b", "/value/c", NULL)); - - return 1; -} - -static int enumerator2_callback(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - - if (object_path_startswith("/value/a", path)) - assert_se(*nodes = strv_new("/value/a/x", "/value/a/y", "/value/a/z", NULL)); - - return 1; -} - -static void *server(void *p) { - struct context *c = p; - sd_bus *bus = NULL; - sd_id128_t id; - int r; - - c->quit = false; - - assert_se(sd_id128_randomize(&id) >= 0); - - assert_se(sd_bus_new(&bus) >= 0); - assert_se(sd_bus_set_fd(bus, c->fds[0], c->fds[0]) >= 0); - assert_se(sd_bus_set_server(bus, 1, id) >= 0); - - assert_se(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.test", vtable, c) >= 0); - assert_se(sd_bus_add_object_vtable(bus, NULL, "/foo", "org.freedesktop.systemd.test2", vtable, c) >= 0); - assert_se(sd_bus_add_fallback_vtable(bus, NULL, "/value", "org.freedesktop.systemd.ValueTest", vtable2, NULL, UINT_TO_PTR(20)) >= 0); - assert_se(sd_bus_add_node_enumerator(bus, NULL, "/value", enumerator_callback, NULL) >= 0); - assert_se(sd_bus_add_node_enumerator(bus, NULL, "/value/a", enumerator2_callback, NULL) >= 0); - assert_se(sd_bus_add_object_manager(bus, NULL, "/value") >= 0); - assert_se(sd_bus_add_object_manager(bus, NULL, "/value/a") >= 0); - - assert_se(sd_bus_start(bus) >= 0); - - log_error("Entering event loop on server"); - - while (!c->quit) { - log_error("Loop!"); - - r = sd_bus_process(bus, NULL); - if (r < 0) { - log_error_errno(r, "Failed to process requests: %m"); - goto fail; - } - - if (r == 0) { - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) { - log_error_errno(r, "Failed to wait: %m"); - goto fail; - } - - continue; - } - } - - r = 0; - -fail: - if (bus) { - sd_bus_flush(bus); - sd_bus_unref(bus); - } - - return INT_TO_PTR(r); -} - -static int client(struct context *c) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *s; - int r; - - assert_se(sd_bus_new(&bus) >= 0); - assert_se(sd_bus_set_fd(bus, c->fds[1], c->fds[1]) >= 0); - assert_se(sd_bus_start(bus) >= 0); - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "NoOperation", &error, NULL, NULL); - assert_se(r >= 0); - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AlterSomething", &error, &reply, "s", "hallo"); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - assert_se(streq(s, "<<>>")); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Doesntexist", &error, &reply, ""); - assert_se(r < 0); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)); - - sd_bus_error_free(&error); - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AlterSomething", &error, &reply, "as", 1, "hallo"); - assert_se(r < 0); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_INVALID_ARGS)); - - sd_bus_error_free(&error); - - r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, &reply, "s"); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - assert_se(streq(s, "<<>>")); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, "s", "test"); - assert_se(r >= 0); - - r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Something", &error, &reply, "s"); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - assert_se(streq(s, "test")); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AutomaticIntegerProperty", &error, "u", 815); - assert_se(r >= 0); - - assert_se(c->automatic_integer_property == 815); - - r = sd_bus_set_property(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "AutomaticStringProperty", &error, "s", "Du Dödel, Du!"); - assert_se(r >= 0); - - assert_se(streq(c->automatic_string_property, "Du Dödel, Du!")); - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - fputs(s, stdout); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_get_property(bus, "org.freedesktop.systemd.test", "/value/xuzz", "org.freedesktop.systemd.ValueTest", "Value", &error, &reply, "s"); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - log_info("read %s", s); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - fputs(s, stdout); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - fputs(s, stdout); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.DBus.Introspectable", "Introspect", &error, &reply, ""); - assert_se(r >= 0); - - r = sd_bus_message_read(reply, "s", &s); - assert_se(r >= 0); - fputs(s, stdout); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", ""); - assert_se(r >= 0); - - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.DBus.Properties", "GetAll", &error, &reply, "s", "org.freedesktop.systemd.ValueTest2"); - assert_se(r < 0); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_INTERFACE)); - sd_bus_error_free(&error); - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", &error, &reply, ""); - assert_se(r < 0); - assert_se(sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_METHOD)); - sd_bus_error_free(&error); - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", &error, &reply, ""); - assert_se(r >= 0); - - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.systemd.ValueTest", "NotifyTest", &error, NULL, ""); - assert_se(r >= 0); - - r = sd_bus_process(bus, &reply); - assert_se(r > 0); - - assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.Properties", "PropertiesChanged")); - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/value/a", "org.freedesktop.systemd.ValueTest", "NotifyTest2", &error, NULL, ""); - assert_se(r >= 0); - - r = sd_bus_process(bus, &reply); - assert_se(r > 0); - - assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.Properties", "PropertiesChanged")); - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitInterfacesAdded", &error, NULL, ""); - assert_se(r >= 0); - - r = sd_bus_process(bus, &reply); - assert_se(r > 0); - - assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")); - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitInterfacesRemoved", &error, NULL, ""); - assert_se(r >= 0); - - r = sd_bus_process(bus, &reply); - assert_se(r > 0); - - assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")); - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitObjectAdded", &error, NULL, ""); - assert_se(r >= 0); - - r = sd_bus_process(bus, &reply); - assert_se(r > 0); - - assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesAdded")); - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "EmitObjectRemoved", &error, NULL, ""); - assert_se(r >= 0); - - r = sd_bus_process(bus, &reply); - assert_se(r > 0); - - assert_se(sd_bus_message_is_signal(reply, "org.freedesktop.DBus.ObjectManager", "InterfacesRemoved")); - bus_message_dump(reply, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - sd_bus_message_unref(reply); - reply = NULL; - - r = sd_bus_call_method(bus, "org.freedesktop.systemd.test", "/foo", "org.freedesktop.systemd.test", "Exit", &error, NULL, ""); - assert_se(r >= 0); - - sd_bus_flush(bus); - - return 0; -} - -int main(int argc, char *argv[]) { - struct context c = {}; - pthread_t s; - void *p; - int r, q; - - zero(c); - - c.automatic_integer_property = 4711; - assert_se(c.automatic_string_property = strdup("dudeldu")); - - assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds) >= 0); - - r = pthread_create(&s, NULL, server, &c); - if (r != 0) - return -r; - - r = client(&c); - - q = pthread_join(s, &p); - if (q != 0) - return -q; - - if (r < 0) - return r; - - if (PTR_TO_INT(p) < 0) - return PTR_TO_INT(p); - - free(c.something); - free(c.automatic_string_property); - - return EXIT_SUCCESS; -} diff --git a/src/libsystemd/sd-bus/test-bus-proxy.c b/src/libsystemd/sd-bus/test-bus-proxy.c deleted file mode 100644 index 45d0a5ffce..0000000000 --- a/src/libsystemd/sd-bus/test-bus-proxy.c +++ /dev/null @@ -1,117 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 David Herrmann - - 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 . -***/ - -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-kernel.h" -#include "bus-util.h" -#include "log.h" -#include "util.h" - -typedef struct { - const char *sender; - int matched_acquired; -} TestProxyMatch; - -static int test_proxy_acquired(sd_bus_message *m, void *userdata, sd_bus_error *error) { - TestProxyMatch *match = userdata; - const char *name; - int r; - - r = sd_bus_message_read(m, "s", &name); - assert_se(r >= 0); - - if (!streq_ptr(match->sender, name)) - return 0; - - ++match->matched_acquired; - return 1; -} - -static void test_proxy_matched(void) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *a = NULL; - _cleanup_free_ char *matchstr = NULL; - TestProxyMatch match = {}; - const char *me; - int r; - - /* open bus 'a' */ - - r = sd_bus_new(&a); - assert_se(r >= 0); - - r = sd_bus_set_address(a, "unix:path=/var/run/dbus/system_bus_socket"); - assert_se(r >= 0); - - r = sd_bus_set_bus_client(a, true); - assert_se(r >= 0); - - r = sd_bus_start(a); - assert_se(r >= 0); - - r = sd_bus_get_unique_name(a, &me); - assert_se(r >= 0); - - matchstr = strjoin("type='signal'," - "member='NameAcquired'," - "destination='", - me, - "'", - NULL); - assert_se(matchstr); - r = sd_bus_add_match(a, NULL, matchstr, test_proxy_acquired, &match); - assert_se(r >= 0); - - r = sd_bus_get_unique_name(a, &match.sender); - assert_se(r >= 0); - - /* barrier to guarantee proxy/dbus-daemon handled the previous data */ - r = sd_bus_call_method(a, - "org.freedesktop.DBus", - "/org/freedesktop/DBus", - "org.freedesktop.DBus", - "GetId", - NULL, NULL, NULL); - assert_se(r >= 0); - - /* now we can be sure the Name* signals were sent */ - do { - r = sd_bus_process(a, NULL); - } while (r > 0); - assert_se(r == 0); - - assert_se(match.matched_acquired == 1); -} - -int main(int argc, char **argv) { - if (access("/var/run/dbus/system_bus_socket", F_OK) < 0) - return EXIT_TEST_SKIP; - - log_parse_environment(); - - test_proxy_matched(); - - return EXIT_SUCCESS; -} diff --git a/src/libsystemd/sd-bus/test-bus-server.c b/src/libsystemd/sd-bus/test-bus-server.c deleted file mode 100644 index b6272efc30..0000000000 --- a/src/libsystemd/sd-bus/test-bus-server.c +++ /dev/null @@ -1,216 +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 . -***/ - -#include -#include - -#include "sd-bus.h" - -#include "bus-internal.h" -#include "bus-util.h" -#include "log.h" -#include "macro.h" -#include "util.h" - -struct context { - int fds[2]; - - bool client_negotiate_unix_fds; - bool server_negotiate_unix_fds; - - bool client_anonymous_auth; - bool server_anonymous_auth; -}; - -static void *server(void *p) { - struct context *c = p; - sd_bus *bus = NULL; - sd_id128_t id; - bool quit = false; - int r; - - assert_se(sd_id128_randomize(&id) >= 0); - - assert_se(sd_bus_new(&bus) >= 0); - assert_se(sd_bus_set_fd(bus, c->fds[0], c->fds[0]) >= 0); - assert_se(sd_bus_set_server(bus, 1, id) >= 0); - assert_se(sd_bus_set_anonymous(bus, c->server_anonymous_auth) >= 0); - assert_se(sd_bus_negotiate_fds(bus, c->server_negotiate_unix_fds) >= 0); - assert_se(sd_bus_start(bus) >= 0); - - while (!quit) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - - r = sd_bus_process(bus, &m); - if (r < 0) { - log_error_errno(r, "Failed to process requests: %m"); - goto fail; - } - - if (r == 0) { - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) { - log_error_errno(r, "Failed to wait: %m"); - goto fail; - } - - continue; - } - - if (!m) - continue; - - log_info("Got message! member=%s", strna(sd_bus_message_get_member(m))); - - if (sd_bus_message_is_method_call(m, "org.freedesktop.systemd.test", "Exit")) { - - assert_se((sd_bus_can_send(bus, 'h') >= 1) == (c->server_negotiate_unix_fds && c->client_negotiate_unix_fds)); - - r = sd_bus_message_new_method_return(m, &reply); - if (r < 0) { - log_error_errno(r, "Failed to allocate return: %m"); - goto fail; - } - - quit = true; - - } else if (sd_bus_message_is_method_call(m, NULL, NULL)) { - r = sd_bus_message_new_method_error( - m, - &reply, - &SD_BUS_ERROR_MAKE_CONST(SD_BUS_ERROR_UNKNOWN_METHOD, "Unknown method.")); - if (r < 0) { - log_error_errno(r, "Failed to allocate return: %m"); - goto fail; - } - } - - if (reply) { - r = sd_bus_send(bus, reply, NULL); - if (r < 0) { - log_error_errno(r, "Failed to send reply: %m"); - goto fail; - } - } - } - - r = 0; - -fail: - if (bus) { - sd_bus_flush(bus); - sd_bus_unref(bus); - } - - return INT_TO_PTR(r); -} - -static int client(struct context *c) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert_se(sd_bus_new(&bus) >= 0); - assert_se(sd_bus_set_fd(bus, c->fds[1], c->fds[1]) >= 0); - assert_se(sd_bus_negotiate_fds(bus, c->client_negotiate_unix_fds) >= 0); - assert_se(sd_bus_set_anonymous(bus, c->client_anonymous_auth) >= 0); - assert_se(sd_bus_start(bus) >= 0); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd.test", - "/", - "org.freedesktop.systemd.test", - "Exit"); - if (r < 0) - return log_error_errno(r, "Failed to allocate method call: %m"); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to issue method call: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int test_one(bool client_negotiate_unix_fds, bool server_negotiate_unix_fds, - bool client_anonymous_auth, bool server_anonymous_auth) { - - struct context c; - pthread_t s; - void *p; - int r, q; - - zero(c); - - assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, c.fds) >= 0); - - c.client_negotiate_unix_fds = client_negotiate_unix_fds; - c.server_negotiate_unix_fds = server_negotiate_unix_fds; - c.client_anonymous_auth = client_anonymous_auth; - c.server_anonymous_auth = server_anonymous_auth; - - r = pthread_create(&s, NULL, server, &c); - if (r != 0) - return -r; - - r = client(&c); - - q = pthread_join(s, &p); - if (q != 0) - return -q; - - if (r < 0) - return r; - - if (PTR_TO_INT(p) < 0) - return PTR_TO_INT(p); - - return 0; -} - -int main(int argc, char *argv[]) { - int r; - - r = test_one(true, true, false, false); - assert_se(r >= 0); - - r = test_one(true, false, false, false); - assert_se(r >= 0); - - r = test_one(false, true, false, false); - assert_se(r >= 0); - - r = test_one(false, false, false, false); - assert_se(r >= 0); - - r = test_one(true, true, true, true); - assert_se(r >= 0); - - r = test_one(true, true, false, true); - assert_se(r >= 0); - - r = test_one(true, true, true, false); - assert_se(r == -EPERM); - - return EXIT_SUCCESS; -} diff --git a/src/libsystemd/sd-bus/test-bus-signature.c b/src/libsystemd/sd-bus/test-bus-signature.c deleted file mode 100644 index 4f4fd093bf..0000000000 --- a/src/libsystemd/sd-bus/test-bus-signature.c +++ /dev/null @@ -1,164 +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 . -***/ - -#include "bus-internal.h" -#include "bus-signature.h" -#include "log.h" -#include "string-util.h" - -int main(int argc, char *argv[]) { - char prefix[256]; - int r; - - assert_se(signature_is_single("y", false)); - assert_se(signature_is_single("u", false)); - assert_se(signature_is_single("v", false)); - assert_se(signature_is_single("as", false)); - assert_se(signature_is_single("(ss)", false)); - assert_se(signature_is_single("()", false)); - assert_se(signature_is_single("(()()()()())", false)); - assert_se(signature_is_single("(((())))", false)); - assert_se(signature_is_single("((((s))))", false)); - assert_se(signature_is_single("{ss}", true)); - assert_se(signature_is_single("a{ss}", false)); - assert_se(!signature_is_single("uu", false)); - assert_se(!signature_is_single("", false)); - assert_se(!signature_is_single("(", false)); - assert_se(!signature_is_single(")", false)); - assert_se(!signature_is_single("())", false)); - assert_se(!signature_is_single("((())", false)); - assert_se(!signature_is_single("{)", false)); - assert_se(!signature_is_single("{}", true)); - assert_se(!signature_is_single("{sss}", true)); - assert_se(!signature_is_single("{s}", true)); - assert_se(!signature_is_single("{ss}", false)); - assert_se(!signature_is_single("{ass}", true)); - assert_se(!signature_is_single("a}", true)); - - assert_se(signature_is_pair("yy")); - assert_se(signature_is_pair("ss")); - assert_se(signature_is_pair("sas")); - assert_se(signature_is_pair("sv")); - assert_se(signature_is_pair("sa(vs)")); - assert_se(!signature_is_pair("")); - assert_se(!signature_is_pair("va")); - assert_se(!signature_is_pair("sss")); - assert_se(!signature_is_pair("{s}ss")); - - assert_se(signature_is_valid("ssa{ss}sssub", true)); - assert_se(signature_is_valid("ssa{ss}sssub", false)); - assert_se(signature_is_valid("{ss}", true)); - assert_se(!signature_is_valid("{ss}", false)); - assert_se(signature_is_valid("", true)); - assert_se(signature_is_valid("", false)); - - assert_se(signature_is_valid("sssusa(uuubbba(uu)uuuu)a{u(uuuvas)}", false)); - - assert_se(!signature_is_valid("a", false)); - assert_se(signature_is_valid("as", false)); - assert_se(signature_is_valid("aas", false)); - assert_se(signature_is_valid("aaas", false)); - assert_se(signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaad", false)); - assert_se(signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaas", false)); - assert_se(!signature_is_valid("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaau", false)); - - assert_se(signature_is_valid("(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))", false)); - assert_se(!signature_is_valid("((((((((((((((((((((((((((((((((()))))))))))))))))))))))))))))))))", false)); - - assert_se(namespace_complex_pattern("", "")); - assert_se(namespace_complex_pattern("foobar", "foobar")); - assert_se(namespace_complex_pattern("foobar.waldo", "foobar.waldo")); - assert_se(namespace_complex_pattern("foobar.", "foobar.waldo")); - assert_se(namespace_complex_pattern("foobar.waldo", "foobar.")); - assert_se(!namespace_complex_pattern("foobar.waldo", "foobar")); - assert_se(!namespace_complex_pattern("foobar", "foobar.waldo")); - assert_se(!namespace_complex_pattern("", "foo")); - assert_se(!namespace_complex_pattern("foo", "")); - assert_se(!namespace_complex_pattern("foo.", "")); - - assert_se(path_complex_pattern("", "")); - assert_se(!path_complex_pattern("", "/")); - assert_se(!path_complex_pattern("/", "")); - assert_se(path_complex_pattern("/", "/")); - assert_se(path_complex_pattern("/foobar/", "/")); - assert_se(!path_complex_pattern("/foobar/", "/foobar")); - assert_se(path_complex_pattern("/foobar", "/foobar")); - assert_se(!path_complex_pattern("/foobar", "/foobar/")); - assert_se(!path_complex_pattern("/foobar", "/foobar/waldo")); - assert_se(path_complex_pattern("/foobar/", "/foobar/waldo")); - assert_se(path_complex_pattern("/foobar/waldo", "/foobar/")); - - assert_se(path_simple_pattern("/foo/", "/foo/bar/waldo")); - - assert_se(namespace_simple_pattern("", "")); - assert_se(namespace_simple_pattern("", ".foobar")); - assert_se(namespace_simple_pattern("foobar", "foobar")); - assert_se(namespace_simple_pattern("foobar.waldo", "foobar.waldo")); - assert_se(namespace_simple_pattern("foobar", "foobar.waldo")); - assert_se(!namespace_simple_pattern("foobar.waldo", "foobar")); - assert_se(!namespace_simple_pattern("", "foo")); - assert_se(!namespace_simple_pattern("foo", "")); - assert_se(namespace_simple_pattern("foo.", "foo.bar.waldo")); - - assert_se(streq(object_path_startswith("/foo/bar", "/foo"), "bar")); - assert_se(streq(object_path_startswith("/foo", "/foo"), "")); - assert_se(streq(object_path_startswith("/foo", "/"), "foo")); - assert_se(streq(object_path_startswith("/", "/"), "")); - assert_se(!object_path_startswith("/foo", "/bar")); - assert_se(!object_path_startswith("/", "/bar")); - assert_se(!object_path_startswith("/foo", "")); - - assert_se(object_path_is_valid("/foo/bar")); - assert_se(object_path_is_valid("/foo")); - assert_se(object_path_is_valid("/")); - assert_se(object_path_is_valid("/foo5")); - assert_se(object_path_is_valid("/foo_5")); - assert_se(!object_path_is_valid("")); - assert_se(!object_path_is_valid("/foo/")); - assert_se(!object_path_is_valid("//")); - assert_se(!object_path_is_valid("//foo")); - assert_se(!object_path_is_valid("/foo//bar")); - assert_se(!object_path_is_valid("/foo/aaaäöä")); - - OBJECT_PATH_FOREACH_PREFIX(prefix, "/") { - log_info("<%s>", prefix); - assert_not_reached("???"); - } - - r = 0; - OBJECT_PATH_FOREACH_PREFIX(prefix, "/xxx") { - log_info("<%s>", prefix); - assert_se(streq(prefix, "/")); - assert_se(r == 0); - r++; - } - assert_se(r == 1); - - r = 0; - OBJECT_PATH_FOREACH_PREFIX(prefix, "/xxx/yyy/zzz") { - log_info("<%s>", prefix); - assert_se(r != 0 || streq(prefix, "/xxx/yyy")); - assert_se(r != 1 || streq(prefix, "/xxx")); - assert_se(r != 2 || streq(prefix, "/")); - r++; - } - assert_se(r == 3); - - return 0; -} diff --git a/src/libsystemd/sd-bus/test-bus-zero-copy.c b/src/libsystemd/sd-bus/test-bus-zero-copy.c deleted file mode 100644 index 3380e8500a..0000000000 --- a/src/libsystemd/sd-bus/test-bus-zero-copy.c +++ /dev/null @@ -1,210 +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 . -***/ - -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-dump.h" -#include "bus-kernel.h" -#include "bus-message.h" -#include "fd-util.h" -#include "log.h" -#include "memfd-util.h" -#include "string-util.h" -#include "util.h" - -#define FIRST_ARRAY 17 -#define SECOND_ARRAY 33 - -#define STRING_SIZE 123 - -int main(int argc, char *argv[]) { - _cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL; - const char *unique; - uint8_t *p; - sd_bus *a, *b; - int r, bus_ref; - sd_bus_message *m; - int f; - uint64_t sz; - uint32_t u32; - size_t i, l; - char *s; - _cleanup_close_ int sfd = -1; - - log_set_max_level(LOG_DEBUG); - - assert_se(asprintf(&name, "deine-mutter-%u", (unsigned) getpid()) >= 0); - - bus_ref = bus_kernel_create_bus(name, false, &bus_name); - if (bus_ref == -ENOENT) - return EXIT_TEST_SKIP; - - assert_se(bus_ref >= 0); - - address = strappend("kernel:path=", bus_name); - assert_se(address); - - r = sd_bus_new(&a); - assert_se(r >= 0); - - r = sd_bus_new(&b); - assert_se(r >= 0); - - r = sd_bus_set_address(a, address); - assert_se(r >= 0); - - r = sd_bus_set_address(b, address); - assert_se(r >= 0); - - r = sd_bus_start(a); - assert_se(r >= 0); - - r = sd_bus_start(b); - assert_se(r >= 0); - - r = sd_bus_get_unique_name(a, &unique); - assert_se(r >= 0); - - r = sd_bus_message_new_method_call(b, &m, unique, "/a/path", "an.inter.face", "AMethod"); - assert_se(r >= 0); - - r = sd_bus_message_open_container(m, 'r', "aysay"); - assert_se(r >= 0); - - r = sd_bus_message_append_array_space(m, 'y', FIRST_ARRAY, (void**) &p); - assert_se(r >= 0); - - p[0] = '<'; - memset(p+1, 'L', FIRST_ARRAY-2); - p[FIRST_ARRAY-1] = '>'; - - f = memfd_new_and_map(NULL, STRING_SIZE, (void**) &s); - assert_se(f >= 0); - - s[0] = '<'; - for (i = 1; i < STRING_SIZE-2; i++) - s[i] = '0' + (i % 10); - s[STRING_SIZE-2] = '>'; - s[STRING_SIZE-1] = 0; - munmap(s, STRING_SIZE); - - r = memfd_get_size(f, &sz); - assert_se(r >= 0); - assert_se(sz == STRING_SIZE); - - r = sd_bus_message_append_string_memfd(m, f, 0, (uint64_t) -1); - assert_se(r >= 0); - - close(f); - - f = memfd_new_and_map(NULL, SECOND_ARRAY, (void**) &p); - assert_se(f >= 0); - - p[0] = '<'; - memset(p+1, 'P', SECOND_ARRAY-2); - p[SECOND_ARRAY-1] = '>'; - munmap(p, SECOND_ARRAY); - - r = memfd_get_size(f, &sz); - assert_se(r >= 0); - assert_se(sz == SECOND_ARRAY); - - r = sd_bus_message_append_array_memfd(m, 'y', f, 0, (uint64_t) -1); - assert_se(r >= 0); - - close(f); - - r = sd_bus_message_close_container(m); - assert_se(r >= 0); - - r = sd_bus_message_append(m, "u", 4711); - assert_se(r >= 0); - - assert_se((sfd = memfd_new_and_map(NULL, 6, (void**) &p)) >= 0); - memcpy(p, "abcd\0", 6); - munmap(p, 6); - assert_se(sd_bus_message_append_string_memfd(m, sfd, 1, 4) >= 0); - - r = bus_message_seal(m, 55, 99*USEC_PER_SEC); - assert_se(r >= 0); - - bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - - r = sd_bus_send(b, m, NULL); - assert_se(r >= 0); - - sd_bus_message_unref(m); - - r = sd_bus_process(a, &m); - assert_se(r > 0); - - bus_message_dump(m, stdout, BUS_MESSAGE_DUMP_WITH_HEADER); - sd_bus_message_rewind(m, true); - - r = sd_bus_message_enter_container(m, 'r', "aysay"); - assert_se(r > 0); - - r = sd_bus_message_read_array(m, 'y', (const void**) &p, &l); - assert_se(r > 0); - assert_se(l == FIRST_ARRAY); - - assert_se(p[0] == '<'); - for (i = 1; i < l-1; i++) - assert_se(p[i] == 'L'); - assert_se(p[l-1] == '>'); - - r = sd_bus_message_read(m, "s", &s); - assert_se(r > 0); - - assert_se(s[0] == '<'); - for (i = 1; i < STRING_SIZE-2; i++) - assert_se(s[i] == (char) ('0' + (i % 10))); - assert_se(s[STRING_SIZE-2] == '>'); - assert_se(s[STRING_SIZE-1] == 0); - - r = sd_bus_message_read_array(m, 'y', (const void**) &p, &l); - assert_se(r > 0); - assert_se(l == SECOND_ARRAY); - - assert_se(p[0] == '<'); - for (i = 1; i < l-1; i++) - assert_se(p[i] == 'P'); - assert_se(p[l-1] == '>'); - - r = sd_bus_message_exit_container(m); - assert_se(r > 0); - - r = sd_bus_message_read(m, "u", &u32); - assert_se(r > 0); - assert_se(u32 == 4711); - - r = sd_bus_message_read(m, "s", &s); - assert_se(r > 0); - assert_se(streq_ptr(s, "bcd")); - - sd_bus_message_unref(m); - - sd_bus_unref(a); - sd_bus_unref(b); - - return 0; -} diff --git a/src/libsystemd/sd-daemon/Makefile b/src/libsystemd/sd-daemon/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd/sd-daemon/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-daemon/sd-daemon.c b/src/libsystemd/sd-daemon/sd-daemon.c deleted file mode 100644 index 4e50b61979..0000000000 --- a/src/libsystemd/sd-daemon/sd-daemon.c +++ /dev/null @@ -1,624 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "socket-util.h" -#include "strv.h" -#include "util.h" - -#define SNDBUF_SIZE (8*1024*1024) - -static void unsetenv_all(bool unset_environment) { - - if (!unset_environment) - return; - - unsetenv("LISTEN_PID"); - unsetenv("LISTEN_FDS"); - unsetenv("LISTEN_FDNAMES"); -} - -_public_ int sd_listen_fds(int unset_environment) { - const char *e; - int n, r, fd; - pid_t pid; - - e = getenv("LISTEN_PID"); - if (!e) { - r = 0; - goto finish; - } - - r = parse_pid(e, &pid); - if (r < 0) - goto finish; - - /* Is this for us? */ - if (getpid() != pid) { - r = 0; - goto finish; - } - - e = getenv("LISTEN_FDS"); - if (!e) { - r = 0; - goto finish; - } - - r = safe_atoi(e, &n); - if (r < 0) - goto finish; - - assert_cc(SD_LISTEN_FDS_START < INT_MAX); - if (n <= 0 || n > INT_MAX - SD_LISTEN_FDS_START) { - r = -EINVAL; - goto finish; - } - - for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd ++) { - r = fd_cloexec(fd, true); - if (r < 0) - goto finish; - } - - r = n; - -finish: - unsetenv_all(unset_environment); - return r; -} - -_public_ int sd_listen_fds_with_names(int unset_environment, char ***names) { - _cleanup_strv_free_ char **l = NULL; - bool have_names; - int n_names = 0, n_fds; - const char *e; - int r; - - if (!names) - return sd_listen_fds(unset_environment); - - e = getenv("LISTEN_FDNAMES"); - if (e) { - n_names = strv_split_extract(&l, e, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (n_names < 0) { - unsetenv_all(unset_environment); - return n_names; - } - - have_names = true; - } else - have_names = false; - - n_fds = sd_listen_fds(unset_environment); - if (n_fds <= 0) - return n_fds; - - if (have_names) { - if (n_names != n_fds) - return -EINVAL; - } else { - r = strv_extend_n(&l, "unknown", n_fds); - if (r < 0) - return r; - } - - *names = l; - l = NULL; - - return n_fds; -} - -_public_ int sd_is_fifo(int fd, const char *path) { - struct stat st_fd; - - assert_return(fd >= 0, -EBADF); - - if (fstat(fd, &st_fd) < 0) - return -errno; - - if (!S_ISFIFO(st_fd.st_mode)) - return 0; - - if (path) { - struct stat st_path; - - if (stat(path, &st_path) < 0) { - - if (errno == ENOENT || errno == ENOTDIR) - return 0; - - return -errno; - } - - return - st_path.st_dev == st_fd.st_dev && - st_path.st_ino == st_fd.st_ino; - } - - return 1; -} - -_public_ int sd_is_special(int fd, const char *path) { - struct stat st_fd; - - assert_return(fd >= 0, -EBADF); - - if (fstat(fd, &st_fd) < 0) - return -errno; - - if (!S_ISREG(st_fd.st_mode) && !S_ISCHR(st_fd.st_mode)) - return 0; - - if (path) { - struct stat st_path; - - if (stat(path, &st_path) < 0) { - - if (errno == ENOENT || errno == ENOTDIR) - return 0; - - return -errno; - } - - if (S_ISREG(st_fd.st_mode) && S_ISREG(st_path.st_mode)) - return - st_path.st_dev == st_fd.st_dev && - st_path.st_ino == st_fd.st_ino; - else if (S_ISCHR(st_fd.st_mode) && S_ISCHR(st_path.st_mode)) - return st_path.st_rdev == st_fd.st_rdev; - else - return 0; - } - - return 1; -} - -static int sd_is_socket_internal(int fd, int type, int listening) { - struct stat st_fd; - - assert_return(fd >= 0, -EBADF); - assert_return(type >= 0, -EINVAL); - - if (fstat(fd, &st_fd) < 0) - return -errno; - - if (!S_ISSOCK(st_fd.st_mode)) - return 0; - - if (type != 0) { - int other_type = 0; - socklen_t l = sizeof(other_type); - - if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0) - return -errno; - - if (l != sizeof(other_type)) - return -EINVAL; - - if (other_type != type) - return 0; - } - - if (listening >= 0) { - int accepting = 0; - socklen_t l = sizeof(accepting); - - if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0) - return -errno; - - if (l != sizeof(accepting)) - return -EINVAL; - - if (!accepting != !listening) - return 0; - } - - return 1; -} - -_public_ int sd_is_socket(int fd, int family, int type, int listening) { - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(family >= 0, -EINVAL); - - r = sd_is_socket_internal(fd, type, listening); - if (r <= 0) - return r; - - if (family > 0) { - union sockaddr_union sockaddr = {}; - socklen_t l = sizeof(sockaddr); - - if (getsockname(fd, &sockaddr.sa, &l) < 0) - return -errno; - - if (l < sizeof(sa_family_t)) - return -EINVAL; - - return sockaddr.sa.sa_family == family; - } - - return 1; -} - -_public_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) { - union sockaddr_union sockaddr = {}; - socklen_t l = sizeof(sockaddr); - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(IN_SET(family, 0, AF_INET, AF_INET6), -EINVAL); - - r = sd_is_socket_internal(fd, type, listening); - if (r <= 0) - return r; - - if (getsockname(fd, &sockaddr.sa, &l) < 0) - return -errno; - - if (l < sizeof(sa_family_t)) - return -EINVAL; - - if (sockaddr.sa.sa_family != AF_INET && - sockaddr.sa.sa_family != AF_INET6) - return 0; - - if (family != 0) - if (sockaddr.sa.sa_family != family) - return 0; - - if (port > 0) { - if (sockaddr.sa.sa_family == AF_INET) { - if (l < sizeof(struct sockaddr_in)) - return -EINVAL; - - return htons(port) == sockaddr.in.sin_port; - } else { - if (l < sizeof(struct sockaddr_in6)) - return -EINVAL; - - return htons(port) == sockaddr.in6.sin6_port; - } - } - - return 1; -} - -_public_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) { - union sockaddr_union sockaddr = {}; - socklen_t l = sizeof(sockaddr); - int r; - - assert_return(fd >= 0, -EBADF); - - r = sd_is_socket_internal(fd, type, listening); - if (r <= 0) - return r; - - if (getsockname(fd, &sockaddr.sa, &l) < 0) - return -errno; - - if (l < sizeof(sa_family_t)) - return -EINVAL; - - if (sockaddr.sa.sa_family != AF_UNIX) - return 0; - - if (path) { - if (length == 0) - length = strlen(path); - - if (length == 0) - /* Unnamed socket */ - return l == offsetof(struct sockaddr_un, sun_path); - - if (path[0]) - /* Normal path socket */ - return - (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) && - memcmp(path, sockaddr.un.sun_path, length+1) == 0; - else - /* Abstract namespace socket */ - return - (l == offsetof(struct sockaddr_un, sun_path) + length) && - memcmp(path, sockaddr.un.sun_path, length) == 0; - } - - return 1; -} - -_public_ int sd_is_mq(int fd, const char *path) { - struct mq_attr attr; - - /* Check that the fd is valid */ - assert_return(fcntl(fd, F_GETFD) >= 0, -errno); - - if (mq_getattr(fd, &attr) < 0) { - if (errno == EBADF) - /* A non-mq fd (or an invalid one, but we ruled that out above) */ - return 0; - return -errno; - } - - if (path) { - char fpath[PATH_MAX]; - struct stat a, b; - - assert_return(path_is_absolute(path), -EINVAL); - - if (fstat(fd, &a) < 0) - return -errno; - - strncpy(stpcpy(fpath, "/dev/mqueue"), path, sizeof(fpath) - 12); - fpath[sizeof(fpath)-1] = 0; - - if (stat(fpath, &b) < 0) - return -errno; - - if (a.st_dev != b.st_dev || - a.st_ino != b.st_ino) - return 0; - } - - return 1; -} - -_public_ int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds) { - union sockaddr_union sockaddr = { - .sa.sa_family = AF_UNIX, - }; - struct iovec iovec = { - .iov_base = (char*) state, - }; - struct msghdr msghdr = { - .msg_iov = &iovec, - .msg_iovlen = 1, - .msg_name = &sockaddr, - }; - _cleanup_close_ int fd = -1; - struct cmsghdr *cmsg = NULL; - const char *e; - bool have_pid; - int r; - - if (!state) { - r = -EINVAL; - goto finish; - } - - if (n_fds > 0 && !fds) { - r = -EINVAL; - goto finish; - } - - e = getenv("NOTIFY_SOCKET"); - if (!e) - return 0; - - /* Must be an abstract socket, or an absolute path */ - if ((e[0] != '@' && e[0] != '/') || e[1] == 0) { - r = -EINVAL; - goto finish; - } - - if (strlen(e) > sizeof(sockaddr.un.sun_path)) { - r = -EINVAL; - goto finish; - } - - fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); - if (fd < 0) { - r = -errno; - goto finish; - } - - fd_inc_sndbuf(fd, SNDBUF_SIZE); - - iovec.iov_len = strlen(state); - - strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path)); - if (sockaddr.un.sun_path[0] == '@') - sockaddr.un.sun_path[0] = 0; - - msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e); - if (msghdr.msg_namelen > sizeof(struct sockaddr_un)) - msghdr.msg_namelen = sizeof(struct sockaddr_un); - - have_pid = pid != 0 && pid != getpid(); - - if (n_fds > 0 || have_pid) { - /* CMSG_SPACE(0) may return value different then zero, which results in miscalculated controllen. */ - msghdr.msg_controllen = - (n_fds > 0 ? CMSG_SPACE(sizeof(int) * n_fds) : 0) + - (have_pid ? CMSG_SPACE(sizeof(struct ucred)) : 0); - - msghdr.msg_control = alloca0(msghdr.msg_controllen); - - cmsg = CMSG_FIRSTHDR(&msghdr); - if (n_fds > 0) { - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - cmsg->cmsg_len = CMSG_LEN(sizeof(int) * n_fds); - - memcpy(CMSG_DATA(cmsg), fds, sizeof(int) * n_fds); - - if (have_pid) - assert_se(cmsg = CMSG_NXTHDR(&msghdr, cmsg)); - } - - if (have_pid) { - struct ucred *ucred; - - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_CREDENTIALS; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct ucred)); - - ucred = (struct ucred*) CMSG_DATA(cmsg); - ucred->pid = pid; - ucred->uid = getuid(); - ucred->gid = getgid(); - } - } - - /* First try with fake ucred data, as requested */ - if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) >= 0) { - r = 1; - goto finish; - } - - /* If that failed, try with our own ucred instead */ - if (have_pid) { - msghdr.msg_controllen -= CMSG_SPACE(sizeof(struct ucred)); - if (msghdr.msg_controllen == 0) - msghdr.msg_control = NULL; - - if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) >= 0) { - r = 1; - goto finish; - } - } - - r = -errno; - -finish: - if (unset_environment) - unsetenv("NOTIFY_SOCKET"); - - return r; -} - -_public_ int sd_pid_notify(pid_t pid, int unset_environment, const char *state) { - return sd_pid_notify_with_fds(pid, unset_environment, state, NULL, 0); -} - -_public_ int sd_notify(int unset_environment, const char *state) { - return sd_pid_notify_with_fds(0, unset_environment, state, NULL, 0); -} - -_public_ int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) { - _cleanup_free_ char *p = NULL; - int r; - - if (format) { - va_list ap; - - va_start(ap, format); - r = vasprintf(&p, format, ap); - va_end(ap); - - if (r < 0 || !p) - return -ENOMEM; - } - - return sd_pid_notify(pid, unset_environment, p); -} - -_public_ int sd_notifyf(int unset_environment, const char *format, ...) { - _cleanup_free_ char *p = NULL; - int r; - - if (format) { - va_list ap; - - va_start(ap, format); - r = vasprintf(&p, format, ap); - va_end(ap); - - if (r < 0 || !p) - return -ENOMEM; - } - - return sd_pid_notify(0, unset_environment, p); -} - -_public_ int sd_booted(void) { - /* We test whether the runtime unit file directory has been - * created. This takes place in mount-setup.c, so is - * guaranteed to happen very early during boot. */ - - return laccess("/run/systemd/system/", F_OK) >= 0; -} - -_public_ int sd_watchdog_enabled(int unset_environment, uint64_t *usec) { - const char *s, *p = ""; /* p is set to dummy value to do unsetting */ - uint64_t u; - int r = 0; - - s = getenv("WATCHDOG_USEC"); - if (!s) - goto finish; - - r = safe_atou64(s, &u); - if (r < 0) - goto finish; - if (u <= 0 || u >= USEC_INFINITY) { - r = -EINVAL; - goto finish; - } - - p = getenv("WATCHDOG_PID"); - if (p) { - pid_t pid; - - r = parse_pid(p, &pid); - if (r < 0) - goto finish; - - /* Is this for us? */ - if (getpid() != pid) { - r = 0; - goto finish; - } - } - - if (usec) - *usec = u; - - r = 1; - -finish: - if (unset_environment && s) - unsetenv("WATCHDOG_USEC"); - if (unset_environment && p) - unsetenv("WATCHDOG_PID"); - - return r; -} diff --git a/src/libsystemd/sd-device/Makefile b/src/libsystemd/sd-device/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd/sd-device/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-device/device-enumerator-private.h b/src/libsystemd/sd-device/device-enumerator-private.h deleted file mode 100644 index eb06f9542d..0000000000 --- a/src/libsystemd/sd-device/device-enumerator-private.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Tom Gundersen - - 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 . -***/ - -#include "sd-device.h" - -int device_enumerator_scan_devices(sd_device_enumerator *enumeartor); -int device_enumerator_scan_subsystems(sd_device_enumerator *enumeartor); -int device_enumerator_add_device(sd_device_enumerator *enumerator, sd_device *device); -int device_enumerator_add_match_is_initialized(sd_device_enumerator *enumerator); -sd_device *device_enumerator_get_first(sd_device_enumerator *enumerator); -sd_device *device_enumerator_get_next(sd_device_enumerator *enumerator); - -#define FOREACH_DEVICE_AND_SUBSYSTEM(enumerator, device) \ - for (device = device_enumerator_get_first(enumerator); \ - device; \ - device = device_enumerator_get_next(enumerator)) diff --git a/src/libsystemd/sd-device/device-enumerator.c b/src/libsystemd/sd-device/device-enumerator.c deleted file mode 100644 index 4a7a8b1f9e..0000000000 --- a/src/libsystemd/sd-device/device-enumerator.c +++ /dev/null @@ -1,986 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2014-2015 Tom Gundersen - - 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 . -***/ - -#include "sd-device.h" - -#include "alloc-util.h" -#include "device-enumerator-private.h" -#include "device-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "prioq.h" -#include "set.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -#define DEVICE_ENUMERATE_MAX_DEPTH 256 - -typedef enum DeviceEnumerationType { - DEVICE_ENUMERATION_TYPE_DEVICES, - DEVICE_ENUMERATION_TYPE_SUBSYSTEMS, - _DEVICE_ENUMERATION_TYPE_MAX, - _DEVICE_ENUMERATION_TYPE_INVALID = -1, -} DeviceEnumerationType; - -struct sd_device_enumerator { - unsigned n_ref; - - DeviceEnumerationType type; - Prioq *devices; - bool scan_uptodate; - - Set *match_subsystem; - Set *nomatch_subsystem; - Hashmap *match_sysattr; - Hashmap *nomatch_sysattr; - Hashmap *match_property; - Set *match_sysname; - Set *match_tag; - sd_device *match_parent; - bool match_allow_uninitialized; -}; - -_public_ int sd_device_enumerator_new(sd_device_enumerator **ret) { - _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *enumerator = NULL; - - assert(ret); - - enumerator = new0(sd_device_enumerator, 1); - if (!enumerator) - return -ENOMEM; - - enumerator->n_ref = 1; - enumerator->type = _DEVICE_ENUMERATION_TYPE_INVALID; - - *ret = enumerator; - enumerator = NULL; - - return 0; -} - -_public_ sd_device_enumerator *sd_device_enumerator_ref(sd_device_enumerator *enumerator) { - assert_return(enumerator, NULL); - - assert_se((++ enumerator->n_ref) >= 2); - - return enumerator; -} - -_public_ sd_device_enumerator *sd_device_enumerator_unref(sd_device_enumerator *enumerator) { - if (enumerator && (-- enumerator->n_ref) == 0) { - sd_device *device; - - while ((device = prioq_pop(enumerator->devices))) - sd_device_unref(device); - - prioq_free(enumerator->devices); - - set_free_free(enumerator->match_subsystem); - set_free_free(enumerator->nomatch_subsystem); - hashmap_free_free_free(enumerator->match_sysattr); - hashmap_free_free_free(enumerator->nomatch_sysattr); - hashmap_free_free_free(enumerator->match_property); - set_free_free(enumerator->match_sysname); - set_free_free(enumerator->match_tag); - sd_device_unref(enumerator->match_parent); - - free(enumerator); - } - - return NULL; -} - -_public_ int sd_device_enumerator_add_match_subsystem(sd_device_enumerator *enumerator, const char *subsystem, int match) { - Set **set; - int r; - - assert_return(enumerator, -EINVAL); - assert_return(subsystem, -EINVAL); - - if (match) - set = &enumerator->match_subsystem; - else - set = &enumerator->nomatch_subsystem; - - r = set_ensure_allocated(set, NULL); - if (r < 0) - return r; - - r = set_put_strdup(*set, subsystem); - if (r < 0) - return r; - - enumerator->scan_uptodate = false; - - return 0; -} - -_public_ int sd_device_enumerator_add_match_sysattr(sd_device_enumerator *enumerator, const char *_sysattr, const char *_value, int match) { - _cleanup_free_ char *sysattr = NULL, *value = NULL; - Hashmap **hashmap; - int r; - - assert_return(enumerator, -EINVAL); - assert_return(_sysattr, -EINVAL); - - if (match) - hashmap = &enumerator->match_sysattr; - else - hashmap = &enumerator->nomatch_sysattr; - - r = hashmap_ensure_allocated(hashmap, NULL); - if (r < 0) - return r; - - sysattr = strdup(_sysattr); - if (!sysattr) - return -ENOMEM; - - if (_value) { - value = strdup(_value); - if (!value) - return -ENOMEM; - } - - r = hashmap_put(*hashmap, sysattr, value); - if (r < 0) - return r; - - sysattr = NULL; - value = NULL; - - enumerator->scan_uptodate = false; - - return 0; -} - -_public_ int sd_device_enumerator_add_match_property(sd_device_enumerator *enumerator, const char *_property, const char *_value) { - _cleanup_free_ char *property = NULL, *value = NULL; - int r; - - assert_return(enumerator, -EINVAL); - assert_return(_property, -EINVAL); - - r = hashmap_ensure_allocated(&enumerator->match_property, NULL); - if (r < 0) - return r; - - property = strdup(_property); - if (!property) - return -ENOMEM; - - if (_value) { - value = strdup(_value); - if (!value) - return -ENOMEM; - } - - r = hashmap_put(enumerator->match_property, property, value); - if (r < 0) - return r; - - property = NULL; - value = NULL; - - enumerator->scan_uptodate = false; - - return 0; -} - -_public_ int sd_device_enumerator_add_match_sysname(sd_device_enumerator *enumerator, const char *sysname) { - int r; - - assert_return(enumerator, -EINVAL); - assert_return(sysname, -EINVAL); - - r = set_ensure_allocated(&enumerator->match_sysname, NULL); - if (r < 0) - return r; - - r = set_put_strdup(enumerator->match_sysname, sysname); - if (r < 0) - return r; - - enumerator->scan_uptodate = false; - - return 0; -} - -_public_ int sd_device_enumerator_add_match_tag(sd_device_enumerator *enumerator, const char *tag) { - int r; - - assert_return(enumerator, -EINVAL); - assert_return(tag, -EINVAL); - - r = set_ensure_allocated(&enumerator->match_tag, NULL); - if (r < 0) - return r; - - r = set_put_strdup(enumerator->match_tag, tag); - if (r < 0) - return r; - - enumerator->scan_uptodate = false; - - return 0; -} - -_public_ int sd_device_enumerator_add_match_parent(sd_device_enumerator *enumerator, sd_device *parent) { - assert_return(enumerator, -EINVAL); - assert_return(parent, -EINVAL); - - sd_device_unref(enumerator->match_parent); - enumerator->match_parent = sd_device_ref(parent); - - enumerator->scan_uptodate = false; - - return 0; -} - -_public_ int sd_device_enumerator_allow_uninitialized(sd_device_enumerator *enumerator) { - assert_return(enumerator, -EINVAL); - - enumerator->match_allow_uninitialized = true; - - enumerator->scan_uptodate = false; - - return 0; -} - -int device_enumerator_add_match_is_initialized(sd_device_enumerator *enumerator) { - assert_return(enumerator, -EINVAL); - - enumerator->match_allow_uninitialized = false; - - enumerator->scan_uptodate = false; - - return 0; -} - -static int device_compare(const void *_a, const void *_b) { - sd_device *a = (sd_device *)_a, *b = (sd_device *)_b; - const char *devpath_a, *devpath_b, *sound_a; - bool delay_a, delay_b; - - assert_se(sd_device_get_devpath(a, &devpath_a) >= 0); - assert_se(sd_device_get_devpath(b, &devpath_b) >= 0); - - sound_a = strstr(devpath_a, "/sound/card"); - if (sound_a) { - /* For sound cards the control device must be enumerated last to - * make sure it's the final device node that gets ACLs applied. - * Applications rely on this fact and use ACL changes on the - * control node as an indicator that the ACL change of the - * entire sound card completed. The kernel makes this guarantee - * when creating those devices, and hence we should too when - * enumerating them. */ - sound_a += strlen("/sound/card"); - sound_a = strchr(sound_a, '/'); - - if (sound_a) { - unsigned prefix_len; - - prefix_len = sound_a - devpath_a; - - if (strncmp(devpath_a, devpath_b, prefix_len) == 0) { - const char *sound_b; - - sound_b = devpath_b + prefix_len; - - if (startswith(sound_a, "/controlC") && - !startswith(sound_b, "/contolC")) - return 1; - - if (!startswith(sound_a, "/controlC") && - startswith(sound_b, "/controlC")) - return -1; - } - } - } - - /* md and dm devices are enumerated after all other devices */ - delay_a = strstr(devpath_a, "/block/md") || strstr(devpath_a, "/block/dm-"); - delay_b = strstr(devpath_b, "/block/md") || strstr(devpath_b, "/block/dm-"); - if (delay_a != delay_b) - return delay_a - delay_b; - - return strcmp(devpath_a, devpath_b); -} - -int device_enumerator_add_device(sd_device_enumerator *enumerator, sd_device *device) { - int r; - - assert_return(enumerator, -EINVAL); - assert_return(device, -EINVAL); - - r = prioq_ensure_allocated(&enumerator->devices, device_compare); - if (r < 0) - return r; - - r = prioq_put(enumerator->devices, device, NULL); - if (r < 0) - return r; - - sd_device_ref(device); - - return 0; -} - -static bool match_sysattr_value(sd_device *device, const char *sysattr, const char *match_value) { - const char *value; - int r; - - assert(device); - assert(sysattr); - - r = sd_device_get_sysattr_value(device, sysattr, &value); - if (r < 0) - return false; - - if (!match_value) - return true; - - if (fnmatch(match_value, value, 0) == 0) - return true; - - return false; -} - -static bool match_sysattr(sd_device_enumerator *enumerator, sd_device *device) { - const char *sysattr; - const char *value; - Iterator i; - - assert(enumerator); - assert(device); - - HASHMAP_FOREACH_KEY(value, sysattr, enumerator->nomatch_sysattr, i) - if (match_sysattr_value(device, sysattr, value)) - return false; - - HASHMAP_FOREACH_KEY(value, sysattr, enumerator->match_sysattr, i) - if (!match_sysattr_value(device, sysattr, value)) - return false; - - return true; -} - -static bool match_property(sd_device_enumerator *enumerator, sd_device *device) { - const char *property; - const char *value; - Iterator i; - - assert(enumerator); - assert(device); - - if (hashmap_isempty(enumerator->match_property)) - return true; - - HASHMAP_FOREACH_KEY(value, property, enumerator->match_property, i) { - const char *property_dev, *value_dev; - - FOREACH_DEVICE_PROPERTY(device, property_dev, value_dev) { - if (fnmatch(property, property_dev, 0) != 0) - continue; - - if (!value && !value_dev) - return true; - - if (!value || !value_dev) - continue; - - if (fnmatch(value, value_dev, 0) == 0) - return true; - } - } - - return false; -} - -static bool match_tag(sd_device_enumerator *enumerator, sd_device *device) { - const char *tag; - Iterator i; - - assert(enumerator); - assert(device); - - SET_FOREACH(tag, enumerator->match_tag, i) - if (!sd_device_has_tag(device, tag)) - return false; - - return true; -} - -static bool match_parent(sd_device_enumerator *enumerator, sd_device *device) { - const char *devpath, *devpath_dev; - int r; - - assert(enumerator); - assert(device); - - if (!enumerator->match_parent) - return true; - - r = sd_device_get_devpath(enumerator->match_parent, &devpath); - assert(r >= 0); - - r = sd_device_get_devpath(device, &devpath_dev); - assert(r >= 0); - - return startswith(devpath_dev, devpath); -} - -static bool match_sysname(sd_device_enumerator *enumerator, const char *sysname) { - const char *sysname_match; - Iterator i; - - assert(enumerator); - assert(sysname); - - if (set_isempty(enumerator->match_sysname)) - return true; - - SET_FOREACH(sysname_match, enumerator->match_sysname, i) - if (fnmatch(sysname_match, sysname, 0) == 0) - return true; - - return false; -} - -static int enumerator_scan_dir_and_add_devices(sd_device_enumerator *enumerator, const char *basedir, const char *subdir1, const char *subdir2) { - _cleanup_closedir_ DIR *dir = NULL; - char *path; - struct dirent *dent; - int r = 0; - - assert(enumerator); - assert(basedir); - - path = strjoina("/sys/", basedir, "/"); - - if (subdir1) - path = strjoina(path, subdir1, "/"); - - if (subdir2) - path = strjoina(path, subdir2, "/"); - - dir = opendir(path); - if (!dir) - return -errno; - - FOREACH_DIRENT_ALL(dent, dir, return -errno) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - char syspath[strlen(path) + 1 + strlen(dent->d_name) + 1]; - dev_t devnum; - int ifindex, initialized, k; - - if (dent->d_name[0] == '.') - continue; - - if (!match_sysname(enumerator, dent->d_name)) - continue; - - (void)sprintf(syspath, "%s%s", path, dent->d_name); - - k = sd_device_new_from_syspath(&device, syspath); - if (k < 0) { - if (k != -ENODEV) - /* this is necessarily racey, so ignore missing devices */ - r = k; - - continue; - } - - k = sd_device_get_devnum(device, &devnum); - if (k < 0) { - r = k; - continue; - } - - k = sd_device_get_ifindex(device, &ifindex); - if (k < 0) { - r = k; - continue; - } - - k = sd_device_get_is_initialized(device, &initialized); - if (k < 0) { - r = k; - continue; - } - - /* - * All devices with a device node or network interfaces - * possibly need udev to adjust the device node permission - * or context, or rename the interface before it can be - * reliably used from other processes. - * - * For now, we can only check these types of devices, we - * might not store a database, and have no way to find out - * for all other types of devices. - */ - if (!enumerator->match_allow_uninitialized && - !initialized && - (major(devnum) > 0 || ifindex > 0)) - continue; - - if (!match_parent(enumerator, device)) - continue; - - if (!match_tag(enumerator, device)) - continue; - - if (!match_property(enumerator, device)) - continue; - - if (!match_sysattr(enumerator, device)) - continue; - - k = device_enumerator_add_device(enumerator, device); - if (k < 0) - r = k; - } - - return r; -} - -static bool match_subsystem(sd_device_enumerator *enumerator, const char *subsystem) { - const char *subsystem_match; - Iterator i; - - assert(enumerator); - - if (!subsystem) - return false; - - SET_FOREACH(subsystem_match, enumerator->nomatch_subsystem, i) - if (fnmatch(subsystem_match, subsystem, 0) == 0) - return false; - - if (set_isempty(enumerator->match_subsystem)) - return true; - - SET_FOREACH(subsystem_match, enumerator->match_subsystem, i) - if (fnmatch(subsystem_match, subsystem, 0) == 0) - return true; - - return false; -} - -static int enumerator_scan_dir(sd_device_enumerator *enumerator, const char *basedir, const char *subdir, const char *subsystem) { - _cleanup_closedir_ DIR *dir = NULL; - char *path; - struct dirent *dent; - int r = 0; - - path = strjoina("/sys/", basedir); - - dir = opendir(path); - if (!dir) - return -errno; - - log_debug(" device-enumerator: scanning %s", path); - - FOREACH_DIRENT_ALL(dent, dir, return -errno) { - int k; - - if (dent->d_name[0] == '.') - continue; - - if (!match_subsystem(enumerator, subsystem ? : dent->d_name)) - continue; - - k = enumerator_scan_dir_and_add_devices(enumerator, basedir, dent->d_name, subdir); - if (k < 0) - r = k; - } - - return r; -} - -static int enumerator_scan_devices_tag(sd_device_enumerator *enumerator, const char *tag) { - _cleanup_closedir_ DIR *dir = NULL; - char *path; - struct dirent *dent; - int r = 0; - - assert(enumerator); - assert(tag); - - path = strjoina("/run/udev/tags/", tag); - - dir = opendir(path); - if (!dir) { - if (errno == ENOENT) - return 0; - else { - log_error("sd-device-enumerator: could not open tags directory %s: %m", path); - return -errno; - } - } - - /* TODO: filter away subsystems? */ - - FOREACH_DIRENT_ALL(dent, dir, return -errno) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - const char *subsystem, *sysname; - int k; - - if (dent->d_name[0] == '.') - continue; - - k = sd_device_new_from_device_id(&device, dent->d_name); - if (k < 0) { - if (k != -ENODEV) - /* this is necessarily racy, so ignore missing devices */ - r = k; - - continue; - } - - k = sd_device_get_subsystem(device, &subsystem); - if (k < 0) { - r = k; - continue; - } - - if (!match_subsystem(enumerator, subsystem)) - continue; - - k = sd_device_get_sysname(device, &sysname); - if (k < 0) { - r = k; - continue; - } - - if (!match_sysname(enumerator, sysname)) - continue; - - if (!match_parent(enumerator, device)) - continue; - - if (!match_property(enumerator, device)) - continue; - - if (!match_sysattr(enumerator, device)) - continue; - - k = device_enumerator_add_device(enumerator, device); - if (k < 0) { - r = k; - continue; - } - } - - return r; -} - -static int enumerator_scan_devices_tags(sd_device_enumerator *enumerator) { - const char *tag; - Iterator i; - int r; - - assert(enumerator); - - SET_FOREACH(tag, enumerator->match_tag, i) { - r = enumerator_scan_devices_tag(enumerator, tag); - if (r < 0) - return r; - } - - return 0; -} - -static int parent_add_child(sd_device_enumerator *enumerator, const char *path) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - const char *subsystem, *sysname; - int r; - - r = sd_device_new_from_syspath(&device, path); - if (r == -ENODEV) - /* this is necessarily racy, so ignore missing devices */ - return 0; - else if (r < 0) - return r; - - r = sd_device_get_subsystem(device, &subsystem); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - - if (!match_subsystem(enumerator, subsystem)) - return 0; - - r = sd_device_get_sysname(device, &sysname); - if (r < 0) - return r; - - if (!match_sysname(enumerator, sysname)) - return 0; - - if (!match_property(enumerator, device)) - return 0; - - if (!match_sysattr(enumerator, device)) - return 0; - - r = device_enumerator_add_device(enumerator, device); - if (r < 0) - return r; - - return 1; -} - -static int parent_crawl_children(sd_device_enumerator *enumerator, const char *path, unsigned maxdepth) { - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *dent; - int r = 0; - - dir = opendir(path); - if (!dir) { - log_debug("sd-device-enumerate: could not open parent directory %s: %m", path); - return -errno; - } - - FOREACH_DIRENT_ALL(dent, dir, return -errno) { - _cleanup_free_ char *child = NULL; - int k; - - if (dent->d_name[0] == '.') - continue; - - if (dent->d_type != DT_DIR) - continue; - - child = strjoin(path, "/", dent->d_name, NULL); - if (!child) - return -ENOMEM; - - k = parent_add_child(enumerator, child); - if (k < 0) - r = k; - - if (maxdepth > 0) - parent_crawl_children(enumerator, child, maxdepth - 1); - else - log_debug("device-enumerate: max depth reached, %s: ignoring devices", child); - } - - return r; -} - -static int enumerator_scan_devices_children(sd_device_enumerator *enumerator) { - const char *path; - int r = 0, k; - - r = sd_device_get_syspath(enumerator->match_parent, &path); - if (r < 0) - return r; - - k = parent_add_child(enumerator, path); - if (k < 0) - r = k; - - k = parent_crawl_children(enumerator, path, DEVICE_ENUMERATE_MAX_DEPTH); - if (k < 0) - r = k; - - return r; -} - -static int enumerator_scan_devices_all(sd_device_enumerator *enumerator) { - int r = 0; - - log_debug("device-enumerator: scan all dirs"); - - if (access("/sys/subsystem", F_OK) >= 0) { - /* we have /subsystem/, forget all the old stuff */ - r = enumerator_scan_dir(enumerator, "subsystem", "devices", NULL); - if (r < 0) - return log_debug_errno(r, "device-enumerator: failed to scan /sys/subsystem: %m"); - } else { - int k; - - k = enumerator_scan_dir(enumerator, "bus", "devices", NULL); - if (k < 0) { - log_debug_errno(k, "device-enumerator: failed to scan /sys/bus: %m"); - r = k; - } - - k = enumerator_scan_dir(enumerator, "class", NULL, NULL); - if (k < 0) { - log_debug_errno(k, "device-enumerator: failed to scan /sys/class: %m"); - r = k; - } - } - - return r; -} - -int device_enumerator_scan_devices(sd_device_enumerator *enumerator) { - sd_device *device; - int r; - - assert(enumerator); - - if (enumerator->scan_uptodate && - enumerator->type == DEVICE_ENUMERATION_TYPE_DEVICES) - return 0; - - while ((device = prioq_pop(enumerator->devices))) - sd_device_unref(device); - - if (!set_isempty(enumerator->match_tag)) { - r = enumerator_scan_devices_tags(enumerator); - if (r < 0) - return r; - } else if (enumerator->match_parent) { - r = enumerator_scan_devices_children(enumerator); - if (r < 0) - return r; - } else { - r = enumerator_scan_devices_all(enumerator); - if (r < 0) - return r; - } - - enumerator->scan_uptodate = true; - - return 0; -} - -_public_ sd_device *sd_device_enumerator_get_device_first(sd_device_enumerator *enumerator) { - int r; - - assert_return(enumerator, NULL); - - r = device_enumerator_scan_devices(enumerator); - if (r < 0) - return NULL; - - enumerator->type = DEVICE_ENUMERATION_TYPE_DEVICES; - - return prioq_peek(enumerator->devices); -} - -_public_ sd_device *sd_device_enumerator_get_device_next(sd_device_enumerator *enumerator) { - assert_return(enumerator, NULL); - - if (!enumerator->scan_uptodate || - enumerator->type != DEVICE_ENUMERATION_TYPE_DEVICES) - return NULL; - - sd_device_unref(prioq_pop(enumerator->devices)); - - return prioq_peek(enumerator->devices); -} - -int device_enumerator_scan_subsystems(sd_device_enumerator *enumerator) { - sd_device *device; - const char *subsysdir; - int r = 0, k; - - assert(enumerator); - - if (enumerator->scan_uptodate && - enumerator->type == DEVICE_ENUMERATION_TYPE_SUBSYSTEMS) - return 0; - - while ((device = prioq_pop(enumerator->devices))) - sd_device_unref(device); - - /* modules */ - if (match_subsystem(enumerator, "module")) { - k = enumerator_scan_dir_and_add_devices(enumerator, "module", NULL, NULL); - if (k < 0) { - log_debug_errno(k, "device-enumerator: failed to scan modules: %m"); - r = k; - } - } - - if (access("/sys/subsystem", F_OK) >= 0) - subsysdir = "subsystem"; - else - subsysdir = "bus"; - - /* subsystems (only buses support coldplug) */ - if (match_subsystem(enumerator, "subsystem")) { - k = enumerator_scan_dir_and_add_devices(enumerator, subsysdir, NULL, NULL); - if (k < 0) { - log_debug_errno(k, "device-enumerator: failed to scan subsystems: %m"); - r = k; - } - } - - /* subsystem drivers */ - if (match_subsystem(enumerator, "drivers")) { - k = enumerator_scan_dir(enumerator, subsysdir, "drivers", "drivers"); - if (k < 0) { - log_debug_errno(k, "device-enumerator: failed to scan drivers: %m"); - r = k; - } - } - - enumerator->scan_uptodate = true; - - return r; -} - -_public_ sd_device *sd_device_enumerator_get_subsystem_first(sd_device_enumerator *enumerator) { - int r; - - assert_return(enumerator, NULL); - - r = device_enumerator_scan_subsystems(enumerator); - if (r < 0) - return NULL; - - enumerator->type = DEVICE_ENUMERATION_TYPE_SUBSYSTEMS; - - return prioq_peek(enumerator->devices); -} - -_public_ sd_device *sd_device_enumerator_get_subsystem_next(sd_device_enumerator *enumerator) { - assert_return(enumerator, NULL); - - if (enumerator->scan_uptodate || - enumerator->type != DEVICE_ENUMERATION_TYPE_SUBSYSTEMS) - return NULL; - - sd_device_unref(prioq_pop(enumerator->devices)); - - return prioq_peek(enumerator->devices); -} - -sd_device *device_enumerator_get_first(sd_device_enumerator *enumerator) { - assert_return(enumerator, NULL); - - return prioq_peek(enumerator->devices); -} - -sd_device *device_enumerator_get_next(sd_device_enumerator *enumerator) { - assert_return(enumerator, NULL); - - sd_device_unref(prioq_pop(enumerator->devices)); - - return prioq_peek(enumerator->devices); -} diff --git a/src/libsystemd/sd-device/device-internal.h b/src/libsystemd/sd-device/device-internal.h deleted file mode 100644 index b96441de56..0000000000 --- a/src/libsystemd/sd-device/device-internal.h +++ /dev/null @@ -1,126 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#pragma once - -#include "hashmap.h" -#include "set.h" - -struct sd_device { - uint64_t n_ref; - - sd_device *parent; - bool parent_set; /* no need to try to reload parent */ - - OrderedHashmap *properties; - Iterator properties_iterator; - uint64_t properties_generation; /* changes whenever the properties are changed */ - uint64_t properties_iterator_generation; /* generation when iteration was started */ - - /* the subset of the properties that should be written to the db*/ - OrderedHashmap *properties_db; - - Hashmap *sysattr_values; /* cached sysattr values */ - - Set *sysattrs; /* names of sysattrs */ - Iterator sysattrs_iterator; - bool sysattrs_read; /* don't try to re-read sysattrs once read */ - - Set *tags; - Iterator tags_iterator; - uint64_t tags_generation; /* changes whenever the tags are changed */ - uint64_t tags_iterator_generation; /* generation when iteration was started */ - bool property_tags_outdated; /* need to update TAGS= property */ - - Set *devlinks; - Iterator devlinks_iterator; - uint64_t devlinks_generation; /* changes whenever the devlinks are changed */ - uint64_t devlinks_iterator_generation; /* generation when iteration was started */ - bool property_devlinks_outdated; /* need to update DEVLINKS= property */ - int devlink_priority; - - char **properties_strv; /* the properties hashmap as a strv */ - uint8_t *properties_nulstr; /* the same as a nulstr */ - size_t properties_nulstr_len; - bool properties_buf_outdated; /* need to reread hashmap */ - - int watch_handle; - - char *syspath; - const char *devpath; - const char *sysnum; - char *sysname; - bool sysname_set; /* don't reread sysname */ - - char *devtype; - int ifindex; - char *devname; - dev_t devnum; - - char *subsystem; - bool subsystem_set; /* don't reread subsystem */ - char *driver; - bool driver_set; /* don't reread driver */ - - char *id_filename; - - bool is_initialized; - uint64_t usec_initialized; - - mode_t devmode; - uid_t devuid; - gid_t devgid; - - bool uevent_loaded; /* don't reread uevent */ - bool db_loaded; /* don't reread db */ - - bool sealed; /* don't read more information from uevent/db */ - bool db_persist; /* don't clean up the db when switching from initrd to real root */ -}; - -typedef enum DeviceAction { - DEVICE_ACTION_ADD, - DEVICE_ACTION_REMOVE, - DEVICE_ACTION_CHANGE, - DEVICE_ACTION_MOVE, - DEVICE_ACTION_ONLINE, - DEVICE_ACTION_OFFLINE, - _DEVICE_ACTION_MAX, - _DEVICE_ACTION_INVALID = -1, -} DeviceAction; - -int device_new_aux(sd_device **ret); -int device_add_property_aux(sd_device *device, const char *key, const char *value, bool db); -int device_add_property_internal(sd_device *device, const char *key, const char *value); -int device_read_uevent_file(sd_device *device); -int device_read_db_aux(sd_device *device, bool force); - -int device_set_syspath(sd_device *device, const char *_syspath, bool verify); -int device_set_ifindex(sd_device *device, const char *ifindex); -int device_set_devmode(sd_device *device, const char *devmode); -int device_set_devname(sd_device *device, const char *_devname); -int device_set_devtype(sd_device *device, const char *_devtype); -int device_set_devnum(sd_device *device, const char *major, const char *minor); -int device_set_subsystem(sd_device *device, const char *_subsystem); -int device_set_driver(sd_device *device, const char *_driver); -int device_set_usec_initialized(sd_device *device, const char *initialized); - -DeviceAction device_action_from_string(const char *s) _pure_; -const char *device_action_to_string(DeviceAction a) _const_; diff --git a/src/libsystemd/sd-device/device-private.c b/src/libsystemd/sd-device/device-private.c deleted file mode 100644 index f2af3ab3ae..0000000000 --- a/src/libsystemd/sd-device/device-private.c +++ /dev/null @@ -1,1119 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include -#include -#include - -#include "sd-device.h" - -#include "alloc-util.h" -#include "device-internal.h" -#include "device-private.h" -#include "device-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hashmap.h" -#include "macro.h" -#include "mkdir.h" -#include "parse-util.h" -#include "path-util.h" -#include "refcnt.h" -#include "set.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "strxcpyx.h" -#include "user-util.h" -#include "util.h" - -int device_add_property(sd_device *device, const char *key, const char *value) { - int r; - - assert(device); - assert(key); - - r = device_add_property_aux(device, key, value, false); - if (r < 0) - return r; - - if (key[0] != '.') { - r = device_add_property_aux(device, key, value, true); - if (r < 0) - return r; - } - - return 0; -} - -static int device_add_property_internal_from_string(sd_device *device, const char *str) { - _cleanup_free_ char *key = NULL; - char *value; - - assert(device); - assert(str); - - key = strdup(str); - if (!key) - return -ENOMEM; - - value = strchr(key, '='); - if (!value) - return -EINVAL; - - *value = '\0'; - - if (isempty(++value)) - value = NULL; - - return device_add_property_internal(device, key, value); -} - -static int handle_db_line(sd_device *device, char key, const char *value) { - char *path; - int r; - - assert(device); - assert(value); - - switch (key) { - case 'S': - path = strjoina("/dev/", value); - r = device_add_devlink(device, path); - if (r < 0) - return r; - - break; - case 'L': - r = safe_atoi(value, &device->devlink_priority); - if (r < 0) - return r; - - break; - case 'E': - r = device_add_property_internal_from_string(device, value); - if (r < 0) - return r; - - break; - case 'G': - r = device_add_tag(device, value); - if (r < 0) - return r; - - break; - case 'W': - r = safe_atoi(value, &device->watch_handle); - if (r < 0) - return r; - - break; - case 'I': - r = device_set_usec_initialized(device, value); - if (r < 0) - return r; - - break; - default: - log_debug("device db: unknown key '%c'", key); - } - - return 0; -} - -void device_set_devlink_priority(sd_device *device, int priority) { - assert(device); - - device->devlink_priority = priority; -} - -void device_set_is_initialized(sd_device *device) { - assert(device); - - device->is_initialized = true; -} - -int device_ensure_usec_initialized(sd_device *device, sd_device *device_old) { - char num[DECIMAL_STR_MAX(usec_t)]; - usec_t usec_initialized; - int r; - - assert(device); - - if (device_old && device_old->usec_initialized > 0) - usec_initialized = device_old->usec_initialized; - else - usec_initialized = now(CLOCK_MONOTONIC); - - r = snprintf(num, sizeof(num), USEC_FMT, usec_initialized); - if (r < 0) - return -errno; - - r = device_set_usec_initialized(device, num); - if (r < 0) - return r; - - return 0; -} - -static int device_read_db(sd_device *device) { - _cleanup_free_ char *db = NULL; - char *path; - const char *id, *value; - char key; - size_t db_len; - unsigned i; - int r; - - enum { - PRE_KEY, - KEY, - PRE_VALUE, - VALUE, - INVALID_LINE, - } state = PRE_KEY; - - assert(device); - - if (device->db_loaded || device->sealed) - return 0; - - r = device_get_id_filename(device, &id); - if (r < 0) - return r; - - path = strjoina("/run/udev/data/", id); - - r = read_full_file(path, &db, &db_len); - if (r < 0) { - if (r == -ENOENT) - return 0; - else - return log_debug_errno(r, "sd-device: failed to read db '%s': %m", path); - } - - /* devices with a database entry are initialized */ - device_set_is_initialized(device); - - for (i = 0; i < db_len; i++) { - switch (state) { - case PRE_KEY: - if (!strchr(NEWLINE, db[i])) { - key = db[i]; - - state = KEY; - } - - break; - case KEY: - if (db[i] != ':') { - log_debug("sd-device: ignoring invalid db entry with key '%c'", key); - - state = INVALID_LINE; - } else { - db[i] = '\0'; - - state = PRE_VALUE; - } - - break; - case PRE_VALUE: - value = &db[i]; - - state = VALUE; - - break; - case INVALID_LINE: - if (strchr(NEWLINE, db[i])) - state = PRE_KEY; - - break; - case VALUE: - if (strchr(NEWLINE, db[i])) { - db[i] = '\0'; - r = handle_db_line(device, key, value); - if (r < 0) - log_debug_errno(r, "sd-device: failed to handle db entry '%c:%s': %m", key, value); - - state = PRE_KEY; - } - - break; - default: - assert_not_reached("invalid state when parsing db"); - } - } - - device->db_loaded = true; - - return 0; -} - -uint64_t device_get_properties_generation(sd_device *device) { - assert(device); - - return device->properties_generation; -} - -uint64_t device_get_tags_generation(sd_device *device) { - assert(device); - - return device->tags_generation; -} - -uint64_t device_get_devlinks_generation(sd_device *device) { - assert(device); - - return device->devlinks_generation; -} - -int device_get_devnode_mode(sd_device *device, mode_t *mode) { - int r; - - assert(device); - assert(mode); - - r = device_read_db(device); - if (r < 0) - return r; - - *mode = device->devmode; - - return 0; -} - -int device_get_devnode_uid(sd_device *device, uid_t *uid) { - int r; - - assert(device); - assert(uid); - - r = device_read_db(device); - if (r < 0) - return r; - - *uid = device->devuid; - - return 0; -} - -static int device_set_devuid(sd_device *device, const char *uid) { - unsigned u; - int r; - - assert(device); - assert(uid); - - r = safe_atou(uid, &u); - if (r < 0) - return r; - - r = device_add_property_internal(device, "DEVUID", uid); - if (r < 0) - return r; - - device->devuid = u; - - return 0; -} - -int device_get_devnode_gid(sd_device *device, gid_t *gid) { - int r; - - assert(device); - assert(gid); - - r = device_read_db(device); - if (r < 0) - return r; - - *gid = device->devgid; - - return 0; -} - -static int device_set_devgid(sd_device *device, const char *gid) { - unsigned g; - int r; - - assert(device); - assert(gid); - - r = safe_atou(gid, &g); - if (r < 0) - return r; - - r = device_add_property_internal(device, "DEVGID", gid); - if (r < 0) - return r; - - device->devgid = g; - - return 0; -} - -static int device_amend(sd_device *device, const char *key, const char *value) { - int r; - - assert(device); - assert(key); - assert(value); - - if (streq(key, "DEVPATH")) { - char *path; - - path = strjoina("/sys", value); - - /* the caller must verify or trust this data (e.g., if it comes from the kernel) */ - r = device_set_syspath(device, path, false); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set syspath to '%s': %m", path); - } else if (streq(key, "SUBSYSTEM")) { - r = device_set_subsystem(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set subsystem to '%s': %m", value); - } else if (streq(key, "DEVTYPE")) { - r = device_set_devtype(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devtype to '%s': %m", value); - } else if (streq(key, "DEVNAME")) { - r = device_set_devname(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devname to '%s': %m", value); - } else if (streq(key, "USEC_INITIALIZED")) { - r = device_set_usec_initialized(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set usec-initialized to '%s': %m", value); - } else if (streq(key, "DRIVER")) { - r = device_set_driver(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set driver to '%s': %m", value); - } else if (streq(key, "IFINDEX")) { - r = device_set_ifindex(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set ifindex to '%s': %m", value); - } else if (streq(key, "DEVMODE")) { - r = device_set_devmode(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devmode to '%s': %m", value); - } else if (streq(key, "DEVUID")) { - r = device_set_devuid(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devuid to '%s': %m", value); - } else if (streq(key, "DEVGID")) { - r = device_set_devgid(device, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devgid to '%s': %m", value); - } else if (streq(key, "DEVLINKS")) { - const char *word, *state; - size_t l; - - FOREACH_WORD(word, l, value, state) { - char devlink[l + 1]; - - strncpy(devlink, word, l); - devlink[l] = '\0'; - - r = device_add_devlink(device, devlink); - if (r < 0) - return log_debug_errno(r, "sd-device: could not add devlink '%s': %m", devlink); - } - } else if (streq(key, "TAGS")) { - const char *word, *state; - size_t l; - - FOREACH_WORD_SEPARATOR(word, l, value, ":", state) { - char tag[l + 1]; - - (void)strncpy(tag, word, l); - tag[l] = '\0'; - - r = device_add_tag(device, tag); - if (r < 0) - return log_debug_errno(r, "sd-device: could not add tag '%s': %m", tag); - } - } else { - r = device_add_property_internal(device, key, value); - if (r < 0) - return log_debug_errno(r, "sd-device: could not add property '%s=%s': %m", key, value); - } - - return 0; -} - -static const char* const device_action_table[_DEVICE_ACTION_MAX] = { - [DEVICE_ACTION_ADD] = "add", - [DEVICE_ACTION_REMOVE] = "remove", - [DEVICE_ACTION_CHANGE] = "change", - [DEVICE_ACTION_MOVE] = "move", - [DEVICE_ACTION_ONLINE] = "online", - [DEVICE_ACTION_OFFLINE] = "offline", -}; - -DEFINE_STRING_TABLE_LOOKUP(device_action, DeviceAction); - -static int device_append(sd_device *device, char *key, const char **_major, const char **_minor, uint64_t *_seqnum, - DeviceAction *_action) { - DeviceAction action = _DEVICE_ACTION_INVALID; - uint64_t seqnum = 0; - const char *major = NULL, *minor = NULL; - char *value; - int r; - - assert(device); - assert(key); - assert(_major); - assert(_minor); - assert(_seqnum); - assert(_action); - - value = strchr(key, '='); - if (!value) { - log_debug("sd-device: not a key-value pair: '%s'", key); - return -EINVAL; - } - - *value = '\0'; - - value++; - - if (streq(key, "MAJOR")) - major = value; - else if (streq(key, "MINOR")) - minor = value; - else { - if (streq(key, "ACTION")) { - action = device_action_from_string(value); - if (action == _DEVICE_ACTION_INVALID) - return -EINVAL; - } else if (streq(key, "SEQNUM")) { - r = safe_atou64(value, &seqnum); - if (r < 0) - return r; - else if (seqnum == 0) - /* kernel only sends seqnum > 0 */ - return -EINVAL; - } - - r = device_amend(device, key, value); - if (r < 0) - return r; - } - - if (major != 0) - *_major = major; - - if (minor != 0) - *_minor = minor; - - if (action != _DEVICE_ACTION_INVALID) - *_action = action; - - if (seqnum > 0) - *_seqnum = seqnum; - - return 0; -} - -void device_seal(sd_device *device) { - assert(device); - - device->sealed = true; -} - -static int device_verify(sd_device *device, DeviceAction action, uint64_t seqnum) { - assert(device); - - if (!device->devpath || !device->subsystem || action == _DEVICE_ACTION_INVALID || seqnum == 0) { - log_debug("sd-device: device created from strv lacks devpath, subsystem, action or seqnum"); - return -EINVAL; - } - - device->sealed = true; - - return 0; -} - -int device_new_from_strv(sd_device **ret, char **strv) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - char **key; - const char *major = NULL, *minor = NULL; - DeviceAction action = _DEVICE_ACTION_INVALID; - uint64_t seqnum; - int r; - - assert(ret); - assert(strv); - - r = device_new_aux(&device); - if (r < 0) - return r; - - STRV_FOREACH(key, strv) { - r = device_append(device, *key, &major, &minor, &seqnum, &action); - if (r < 0) - return r; - } - - if (major) { - r = device_set_devnum(device, major, minor); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devnum %s:%s: %m", major, minor); - } - - r = device_verify(device, action, seqnum); - if (r < 0) - return r; - - *ret = device; - device = NULL; - - return 0; -} - -int device_new_from_nulstr(sd_device **ret, uint8_t *nulstr, size_t len) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - const char *major = NULL, *minor = NULL; - DeviceAction action = _DEVICE_ACTION_INVALID; - uint64_t seqnum; - unsigned i = 0; - int r; - - assert(ret); - assert(nulstr); - assert(len); - - r = device_new_aux(&device); - if (r < 0) - return r; - - while (i < len) { - char *key; - const char *end; - - key = (char*)&nulstr[i]; - end = memchr(key, '\0', len - i); - if (!end) { - log_debug("sd-device: failed to parse nulstr"); - return -EINVAL; - } - i += end - key + 1; - - r = device_append(device, key, &major, &minor, &seqnum, &action); - if (r < 0) - return r; - } - - if (major) { - r = device_set_devnum(device, major, minor); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set devnum %s:%s: %m", major, minor); - } - - r = device_verify(device, action, seqnum); - if (r < 0) - return r; - - *ret = device; - device = NULL; - - return 0; -} - -static int device_update_properties_bufs(sd_device *device) { - const char *val, *prop; - _cleanup_free_ char **buf_strv = NULL; - _cleanup_free_ uint8_t *buf_nulstr = NULL; - size_t allocated_nulstr = 0; - size_t nulstr_len = 0, num = 0, i = 0; - - assert(device); - - if (!device->properties_buf_outdated) - return 0; - - FOREACH_DEVICE_PROPERTY(device, prop, val) { - size_t len = 0; - - len = strlen(prop) + 1 + strlen(val); - - buf_nulstr = GREEDY_REALLOC0(buf_nulstr, allocated_nulstr, nulstr_len + len + 2); - if (!buf_nulstr) - return -ENOMEM; - - strscpyl((char *)buf_nulstr + nulstr_len, len + 1, prop, "=", val, NULL); - nulstr_len += len + 1; - ++num; - } - - /* build buf_strv from buf_nulstr */ - buf_strv = new0(char *, num + 1); - if (!buf_strv) - return -ENOMEM; - - NULSTR_FOREACH(val, (char*) buf_nulstr) { - buf_strv[i] = (char *) val; - assert(i < num); - i++; - } - - free(device->properties_nulstr); - device->properties_nulstr = buf_nulstr; - buf_nulstr = NULL; - device->properties_nulstr_len = nulstr_len; - free(device->properties_strv); - device->properties_strv = buf_strv; - buf_strv = NULL; - - device->properties_buf_outdated = false; - - return 0; -} - -int device_get_properties_nulstr(sd_device *device, const uint8_t **nulstr, size_t *len) { - int r; - - assert(device); - assert(nulstr); - assert(len); - - r = device_update_properties_bufs(device); - if (r < 0) - return r; - - *nulstr = device->properties_nulstr; - *len = device->properties_nulstr_len; - - return 0; -} - -int device_get_properties_strv(sd_device *device, char ***strv) { - int r; - - assert(device); - assert(strv); - - r = device_update_properties_bufs(device); - if (r < 0) - return r; - - *strv = device->properties_strv; - - return 0; -} - -int device_get_devlink_priority(sd_device *device, int *priority) { - int r; - - assert(device); - assert(priority); - - r = device_read_db(device); - if (r < 0) - return r; - - *priority = device->devlink_priority; - - return 0; -} - -int device_get_watch_handle(sd_device *device, int *handle) { - int r; - - assert(device); - assert(handle); - - r = device_read_db(device); - if (r < 0) - return r; - - *handle = device->watch_handle; - - return 0; -} - -void device_set_watch_handle(sd_device *device, int handle) { - assert(device); - - device->watch_handle = handle; -} - -int device_rename(sd_device *device, const char *name) { - _cleanup_free_ char *dirname = NULL; - char *new_syspath; - const char *interface; - int r; - - assert(device); - assert(name); - - dirname = dirname_malloc(device->syspath); - if (!dirname) - return -ENOMEM; - - new_syspath = strjoina(dirname, "/", name); - - /* the user must trust that the new name is correct */ - r = device_set_syspath(device, new_syspath, false); - if (r < 0) - return r; - - r = sd_device_get_property_value(device, "INTERFACE", &interface); - if (r >= 0) { - r = device_add_property_internal(device, "INTERFACE", name); - if (r < 0) - return r; - - /* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */ - r = device_add_property_internal(device, "INTERFACE_OLD", interface); - if (r < 0) - return r; - } else if (r != -ENOENT) - return r; - - return 0; -} - -int device_shallow_clone(sd_device *old_device, sd_device **new_device) { - _cleanup_(sd_device_unrefp) sd_device *ret = NULL; - int r; - - assert(old_device); - assert(new_device); - - r = device_new_aux(&ret); - if (r < 0) - return r; - - r = device_set_syspath(ret, old_device->syspath, false); - if (r < 0) - return r; - - r = device_set_subsystem(ret, old_device->subsystem); - if (r < 0) - return r; - - ret->devnum = old_device->devnum; - - *new_device = ret; - ret = NULL; - - return 0; -} - -int device_clone_with_db(sd_device *old_device, sd_device **new_device) { - _cleanup_(sd_device_unrefp) sd_device *ret = NULL; - int r; - - assert(old_device); - assert(new_device); - - r = device_shallow_clone(old_device, &ret); - if (r < 0) - return r; - - r = device_read_db(ret); - if (r < 0) - return r; - - ret->sealed = true; - - *new_device = ret; - ret = NULL; - - return 0; -} - -int device_new_from_synthetic_event(sd_device **new_device, const char *syspath, const char *action) { - _cleanup_(sd_device_unrefp) sd_device *ret = NULL; - int r; - - assert(new_device); - assert(syspath); - assert(action); - - r = sd_device_new_from_syspath(&ret, syspath); - if (r < 0) - return r; - - r = device_read_uevent_file(ret); - if (r < 0) - return r; - - r = device_add_property_internal(ret, "ACTION", action); - if (r < 0) - return r; - - *new_device = ret; - ret = NULL; - - return 0; -} - -int device_copy_properties(sd_device *device_dst, sd_device *device_src) { - const char *property, *value; - int r; - - assert(device_dst); - assert(device_src); - - FOREACH_DEVICE_PROPERTY(device_src, property, value) { - r = device_add_property(device_dst, property, value); - if (r < 0) - return r; - } - - return 0; -} - -void device_cleanup_tags(sd_device *device) { - assert(device); - - set_free_free(device->tags); - device->tags = NULL; - device->property_tags_outdated = true; - device->tags_generation ++; -} - -void device_cleanup_devlinks(sd_device *device) { - assert(device); - - set_free_free(device->devlinks); - device->devlinks = NULL; - device->property_devlinks_outdated = true; - device->devlinks_generation ++; -} - -void device_remove_tag(sd_device *device, const char *tag) { - assert(device); - assert(tag); - - free(set_remove(device->tags, tag)); - device->property_tags_outdated = true; - device->tags_generation ++; -} - -static int device_tag(sd_device *device, const char *tag, bool add) { - const char *id; - char *path; - int r; - - assert(device); - assert(tag); - - r = device_get_id_filename(device, &id); - if (r < 0) - return r; - - path = strjoina("/run/udev/tags/", tag, "/", id); - - if (add) { - r = touch_file(path, true, USEC_INFINITY, UID_INVALID, GID_INVALID, 0444); - if (r < 0) - return r; - } else { - r = unlink(path); - if (r < 0 && errno != ENOENT) - return -errno; - } - - return 0; -} - -int device_tag_index(sd_device *device, sd_device *device_old, bool add) { - const char *tag; - int r = 0, k; - - if (add && device_old) { - /* delete possible left-over tags */ - FOREACH_DEVICE_TAG(device_old, tag) { - if (!sd_device_has_tag(device, tag)) { - k = device_tag(device_old, tag, false); - if (r >= 0 && k < 0) - r = k; - } - } - } - - FOREACH_DEVICE_TAG(device, tag) { - k = device_tag(device, tag, add); - if (r >= 0 && k < 0) - r = k; - } - - return r; -} - -static bool device_has_info(sd_device *device) { - assert(device); - - if (!set_isempty(device->devlinks)) - return true; - - if (device->devlink_priority != 0) - return true; - - if (!ordered_hashmap_isempty(device->properties_db)) - return true; - - if (!set_isempty(device->tags)) - return true; - - if (device->watch_handle >= 0) - return true; - - return false; -} - -void device_set_db_persist(sd_device *device) { - assert(device); - - device->db_persist = true; -} - -int device_update_db(sd_device *device) { - const char *id; - char *path; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *path_tmp = NULL; - bool has_info; - int r; - - assert(device); - - has_info = device_has_info(device); - - r = device_get_id_filename(device, &id); - if (r < 0) - return r; - - path = strjoina("/run/udev/data/", id); - - /* do not store anything for otherwise empty devices */ - if (!has_info && major(device->devnum) == 0 && device->ifindex == 0) { - r = unlink(path); - if (r < 0 && errno != ENOENT) - return -errno; - - return 0; - } - - /* write a database file */ - r = mkdir_parents(path, 0755); - if (r < 0) - return r; - - r = fopen_temporary(path, &f, &path_tmp); - if (r < 0) - return r; - - /* - * set 'sticky' bit to indicate that we should not clean the - * database when we transition from initramfs to the real root - */ - if (device->db_persist) { - r = fchmod(fileno(f), 01644); - if (r < 0) { - r = -errno; - goto fail; - } - } else { - r = fchmod(fileno(f), 0644); - if (r < 0) { - r = -errno; - goto fail; - } - } - - if (has_info) { - const char *property, *value, *tag; - Iterator i; - - if (major(device->devnum) > 0) { - const char *devlink; - - FOREACH_DEVICE_DEVLINK(device, devlink) - fprintf(f, "S:%s\n", devlink + strlen("/dev/")); - - if (device->devlink_priority != 0) - fprintf(f, "L:%i\n", device->devlink_priority); - - if (device->watch_handle >= 0) - fprintf(f, "W:%i\n", device->watch_handle); - } - - if (device->usec_initialized > 0) - fprintf(f, "I:"USEC_FMT"\n", device->usec_initialized); - - ORDERED_HASHMAP_FOREACH_KEY(value, property, device->properties_db, i) - fprintf(f, "E:%s=%s\n", property, value); - - FOREACH_DEVICE_TAG(device, tag) - fprintf(f, "G:%s\n", tag); - } - - r = fflush_and_check(f); - if (r < 0) - goto fail; - - r = rename(path_tmp, path); - if (r < 0) { - r = -errno; - goto fail; - } - - log_debug("created %s file '%s' for '%s'", has_info ? "db" : "empty", - path, device->devpath); - - return 0; - -fail: - (void) unlink(path); - (void) unlink(path_tmp); - - return log_error_errno(r, "failed to create %s file '%s' for '%s'", has_info ? "db" : "empty", path, device->devpath); -} - -int device_delete_db(sd_device *device) { - const char *id; - char *path; - int r; - - assert(device); - - r = device_get_id_filename(device, &id); - if (r < 0) - return r; - - path = strjoina("/run/udev/data/", id); - - r = unlink(path); - if (r < 0 && errno != ENOENT) - return -errno; - - return 0; -} - -int device_read_db_force(sd_device *device) { - assert(device); - - return device_read_db_aux(device, true); -} diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h deleted file mode 100644 index 29b3e155fb..0000000000 --- a/src/libsystemd/sd-device/device-private.h +++ /dev/null @@ -1,68 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include -#include -#include - -#include "sd-device.h" - -int device_new_from_nulstr(sd_device **ret, uint8_t *nulstr, size_t len); -int device_new_from_strv(sd_device **ret, char **strv); - -int device_get_id_filename(sd_device *device, const char **ret); - -int device_get_devlink_priority(sd_device *device, int *priority); -int device_get_watch_handle(sd_device *device, int *handle); -int device_get_devnode_mode(sd_device *device, mode_t *mode); -int device_get_devnode_uid(sd_device *device, uid_t *uid); -int device_get_devnode_gid(sd_device *device, gid_t *gid); - -void device_seal(sd_device *device); -void device_set_is_initialized(sd_device *device); -void device_set_watch_handle(sd_device *device, int fd); -void device_set_db_persist(sd_device *device); -void device_set_devlink_priority(sd_device *device, int priority); -int device_ensure_usec_initialized(sd_device *device, sd_device *device_old); -int device_add_devlink(sd_device *device, const char *devlink); -int device_add_property(sd_device *device, const char *property, const char *value); -int device_add_tag(sd_device *device, const char *tag); -void device_remove_tag(sd_device *device, const char *tag); -void device_cleanup_tags(sd_device *device); -void device_cleanup_devlinks(sd_device *device); - -uint64_t device_get_properties_generation(sd_device *device); -uint64_t device_get_tags_generation(sd_device *device); -uint64_t device_get_devlinks_generation(sd_device *device); - -int device_get_properties_nulstr(sd_device *device, const uint8_t **nulstr, size_t *len); -int device_get_properties_strv(sd_device *device, char ***strv); - -int device_rename(sd_device *device, const char *name); -int device_shallow_clone(sd_device *old_device, sd_device **new_device); -int device_clone_with_db(sd_device *old_device, sd_device **new_device); -int device_copy_properties(sd_device *device_dst, sd_device *device_src); -int device_new_from_synthetic_event(sd_device **new_device, const char *syspath, const char *action); - -int device_tag_index(sd_device *dev, sd_device *dev_old, bool add); -int device_update_db(sd_device *device); -int device_delete_db(sd_device *device); -int device_read_db_force(sd_device *device); diff --git a/src/libsystemd/sd-device/device-util.h b/src/libsystemd/sd-device/device-util.h deleted file mode 100644 index 5b42e11de6..0000000000 --- a/src/libsystemd/sd-device/device-util.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014-2015 Tom Gundersen - - 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 . -***/ - -#include "util.h" - -#define FOREACH_DEVICE_PROPERTY(device, key, value) \ - for (key = sd_device_get_property_first(device, &(value)); \ - key; \ - key = sd_device_get_property_next(device, &(value))) - -#define FOREACH_DEVICE_TAG(device, tag) \ - for (tag = sd_device_get_tag_first(device); \ - tag; \ - tag = sd_device_get_tag_next(device)) - -#define FOREACH_DEVICE_SYSATTR(device, attr) \ - for (attr = sd_device_get_sysattr_first(device); \ - attr; \ - attr = sd_device_get_sysattr_next(device)) - -#define FOREACH_DEVICE_DEVLINK(device, devlink) \ - for (devlink = sd_device_get_devlink_first(device); \ - devlink; \ - devlink = sd_device_get_devlink_next(device)) - -#define FOREACH_DEVICE(enumerator, device) \ - for (device = sd_device_enumerator_get_device_first(enumerator); \ - device; \ - device = sd_device_enumerator_get_device_next(enumerator)) - -#define FOREACH_SUBSYSTEM(enumerator, device) \ - for (device = sd_device_enumerator_get_subsystem_first(enumerator); \ - device; \ - device = sd_device_enumerator_get_subsystem_next(enumerator)) diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c deleted file mode 100644 index 9633e46ce0..0000000000 --- a/src/libsystemd/sd-device/sd-device.c +++ /dev/null @@ -1,1873 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include -#include -#include - -#include "sd-device.h" - -#include "alloc-util.h" -#include "device-internal.h" -#include "device-private.h" -#include "device-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hashmap.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "set.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "strxcpyx.h" -#include "util.h" - -int device_new_aux(sd_device **ret) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - - assert(ret); - - device = new0(sd_device, 1); - if (!device) - return -ENOMEM; - - device->n_ref = 1; - device->watch_handle = -1; - - *ret = device; - device = NULL; - - return 0; -} - -_public_ sd_device *sd_device_ref(sd_device *device) { - if (device) - assert_se(++ device->n_ref >= 2); - - return device; -} - -_public_ sd_device *sd_device_unref(sd_device *device) { - if (device && -- device->n_ref == 0) { - sd_device_unref(device->parent); - free(device->syspath); - free(device->sysname); - free(device->devtype); - free(device->devname); - free(device->subsystem); - free(device->driver); - free(device->id_filename); - free(device->properties_strv); - free(device->properties_nulstr); - - ordered_hashmap_free_free_free(device->properties); - ordered_hashmap_free_free_free(device->properties_db); - hashmap_free_free_free(device->sysattr_values); - set_free_free(device->sysattrs); - set_free_free(device->tags); - set_free_free(device->devlinks); - - free(device); - } - - return NULL; -} - -int device_add_property_aux(sd_device *device, const char *_key, const char *_value, bool db) { - OrderedHashmap **properties; - - assert(device); - assert(_key); - - if (db) - properties = &device->properties_db; - else - properties = &device->properties; - - if (_value) { - _cleanup_free_ char *key = NULL, *value = NULL, *old_key = NULL, *old_value = NULL; - int r; - - r = ordered_hashmap_ensure_allocated(properties, &string_hash_ops); - if (r < 0) - return r; - - key = strdup(_key); - if (!key) - return -ENOMEM; - - value = strdup(_value); - if (!value) - return -ENOMEM; - - old_value = ordered_hashmap_get2(*properties, key, (void**) &old_key); - - r = ordered_hashmap_replace(*properties, key, value); - if (r < 0) - return r; - - key = NULL; - value = NULL; - } else { - _cleanup_free_ char *key = NULL; - _cleanup_free_ char *value = NULL; - - value = ordered_hashmap_remove2(*properties, _key, (void**) &key); - } - - if (!db) { - device->properties_generation ++; - device->properties_buf_outdated = true; - } - - return 0; -} - -int device_add_property_internal(sd_device *device, const char *key, const char *value) { - return device_add_property_aux(device, key, value, false); -} - -int device_set_syspath(sd_device *device, const char *_syspath, bool verify) { - _cleanup_free_ char *syspath = NULL; - const char *devpath; - int r; - - assert(device); - assert(_syspath); - - /* must be a subdirectory of /sys */ - if (!path_startswith(_syspath, "/sys/")) { - log_debug("sd-device: syspath '%s' is not a subdirectory of /sys", _syspath); - return -EINVAL; - } - - if (verify) { - r = readlink_and_canonicalize(_syspath, &syspath); - if (r == -ENOENT) - /* the device does not exist (any more?) */ - return -ENODEV; - else if (r == -EINVAL) { - /* not a symlink */ - syspath = canonicalize_file_name(_syspath); - if (!syspath) { - if (errno == ENOENT) - /* the device does not exist (any more?) */ - return -ENODEV; - - return log_debug_errno(errno, "sd-device: could not canonicalize '%s': %m", _syspath); - } - } else if (r < 0) { - log_debug_errno(r, "sd-device: could not get target of '%s': %m", _syspath); - return r; - } - - if (path_startswith(syspath, "/sys/devices/")) { - char *path; - - /* all 'devices' require an 'uevent' file */ - path = strjoina(syspath, "/uevent"); - r = access(path, F_OK); - if (r < 0) { - if (errno == ENOENT) - /* this is not a valid device */ - return -ENODEV; - - log_debug("sd-device: %s does not have an uevent file: %m", syspath); - return -errno; - } - } else { - /* everything else just just needs to be a directory */ - if (!is_dir(syspath, false)) - return -ENODEV; - } - } else { - syspath = strdup(_syspath); - if (!syspath) - return -ENOMEM; - } - - devpath = syspath + strlen("/sys"); - - r = device_add_property_internal(device, "DEVPATH", devpath); - if (r < 0) - return r; - - free(device->syspath); - device->syspath = syspath; - syspath = NULL; - - device->devpath = devpath; - - return 0; -} - -_public_ int sd_device_new_from_syspath(sd_device **ret, const char *syspath) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - int r; - - assert_return(ret, -EINVAL); - assert_return(syspath, -EINVAL); - - r = device_new_aux(&device); - if (r < 0) - return r; - - r = device_set_syspath(device, syspath, true); - if (r < 0) - return r; - - *ret = device; - device = NULL; - - return 0; -} - -_public_ int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum) { - char *syspath; - char id[DECIMAL_STR_MAX(unsigned) * 2 + 1]; - - assert_return(ret, -EINVAL); - assert_return(type == 'b' || type == 'c', -EINVAL); - - /* use /sys/dev/{block,char}/: link */ - snprintf(id, sizeof(id), "%u:%u", major(devnum), minor(devnum)); - - syspath = strjoina("/sys/dev/", (type == 'b' ? "block" : "char"), "/", id); - - return sd_device_new_from_syspath(ret, syspath); -} - -_public_ int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname) { - char *syspath; - - assert_return(ret, -EINVAL); - assert_return(subsystem, -EINVAL); - assert_return(sysname, -EINVAL); - - if (streq(subsystem, "subsystem")) { - syspath = strjoina("/sys/subsystem/", sysname); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - - syspath = strjoina("/sys/bus/", sysname); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - - syspath = strjoina("/sys/class/", sysname); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - } else if (streq(subsystem, "module")) { - syspath = strjoina("/sys/module/", sysname); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - } else if (streq(subsystem, "drivers")) { - char subsys[PATH_MAX]; - char *driver; - - strscpy(subsys, sizeof(subsys), sysname); - driver = strchr(subsys, ':'); - if (driver) { - driver[0] = '\0'; - driver++; - - syspath = strjoina("/sys/subsystem/", subsys, "/drivers/", driver); - if (access(syspath, F_OK) >= 0) - return sd_device_new_from_syspath(ret, syspath); - - 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] = '!'; - - 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/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); - } - - return -ENODEV; -} - -int device_set_devtype(sd_device *device, const char *_devtype) { - _cleanup_free_ char *devtype = NULL; - int r; - - assert(device); - assert(_devtype); - - devtype = strdup(_devtype); - if (!devtype) - return -ENOMEM; - - r = device_add_property_internal(device, "DEVTYPE", devtype); - if (r < 0) - return r; - - free(device->devtype); - device->devtype = devtype; - devtype = NULL; - - return 0; -} - -int device_set_ifindex(sd_device *device, const char *_ifindex) { - int ifindex, r; - - assert(device); - assert(_ifindex); - - r = parse_ifindex(_ifindex, &ifindex); - if (r < 0) - return r; - - r = device_add_property_internal(device, "IFINDEX", _ifindex); - if (r < 0) - return r; - - device->ifindex = ifindex; - - return 0; -} - -int device_set_devname(sd_device *device, const char *_devname) { - _cleanup_free_ char *devname = NULL; - int r; - - assert(device); - assert(_devname); - - if (_devname[0] != '/') { - r = asprintf(&devname, "/dev/%s", _devname); - if (r < 0) - return -ENOMEM; - } else { - devname = strdup(_devname); - if (!devname) - return -ENOMEM; - } - - r = device_add_property_internal(device, "DEVNAME", devname); - if (r < 0) - return r; - - free(device->devname); - device->devname = devname; - devname = NULL; - - return 0; -} - -int device_set_devmode(sd_device *device, const char *_devmode) { - unsigned devmode; - int r; - - assert(device); - assert(_devmode); - - r = safe_atou(_devmode, &devmode); - if (r < 0) - return r; - - if (devmode > 07777) - return -EINVAL; - - r = device_add_property_internal(device, "DEVMODE", _devmode); - if (r < 0) - return r; - - device->devmode = devmode; - - return 0; -} - -int device_set_devnum(sd_device *device, const char *major, const char *minor) { - unsigned maj = 0, min = 0; - int r; - - assert(device); - assert(major); - - r = safe_atou(major, &maj); - if (r < 0) - return r; - if (!maj) - return 0; - - if (minor) { - r = safe_atou(minor, &min); - if (r < 0) - return r; - } - - r = device_add_property_internal(device, "MAJOR", major); - if (r < 0) - return r; - - if (minor) { - r = device_add_property_internal(device, "MINOR", minor); - if (r < 0) - return r; - } - - device->devnum = makedev(maj, min); - - return 0; -} - -static int handle_uevent_line(sd_device *device, const char *key, const char *value, const char **major, const char **minor) { - int r; - - assert(device); - assert(key); - assert(value); - assert(major); - assert(minor); - - if (streq(key, "DEVTYPE")) { - r = device_set_devtype(device, value); - if (r < 0) - return r; - } else if (streq(key, "IFINDEX")) { - r = device_set_ifindex(device, value); - if (r < 0) - return r; - } else if (streq(key, "DEVNAME")) { - r = device_set_devname(device, value); - if (r < 0) - return r; - } else if (streq(key, "DEVMODE")) { - r = device_set_devmode(device, value); - if (r < 0) - return r; - } else if (streq(key, "MAJOR")) - *major = value; - else if (streq(key, "MINOR")) - *minor = value; - else { - r = device_add_property_internal(device, key, value); - if (r < 0) - return r; - } - - return 0; -} - -int device_read_uevent_file(sd_device *device) { - _cleanup_free_ char *uevent = NULL; - const char *syspath, *key = NULL, *value = NULL, *major = NULL, *minor = NULL; - char *path; - size_t uevent_len; - unsigned i; - int r; - - enum { - PRE_KEY, - KEY, - PRE_VALUE, - VALUE, - INVALID_LINE, - } state = PRE_KEY; - - assert(device); - - if (device->uevent_loaded || device->sealed) - return 0; - - device->uevent_loaded = true; - - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - - path = strjoina(syspath, "/uevent"); - - r = read_full_file(path, &uevent, &uevent_len); - if (r == -EACCES) - /* empty uevent files may be write-only */ - return 0; - else if (r == -ENOENT) - /* some devices may not have uevent files, see set_syspath() */ - return 0; - else if (r < 0) { - log_debug_errno(r, "sd-device: failed to read uevent file '%s': %m", path); - return r; - } - - for (i = 0; i < uevent_len; i++) { - switch (state) { - case PRE_KEY: - if (!strchr(NEWLINE, uevent[i])) { - key = &uevent[i]; - - state = KEY; - } - - break; - case KEY: - if (uevent[i] == '=') { - uevent[i] = '\0'; - - state = PRE_VALUE; - } else if (strchr(NEWLINE, uevent[i])) { - uevent[i] = '\0'; - log_debug("sd-device: ignoring invalid uevent line '%s'", key); - - state = PRE_KEY; - } - - break; - case PRE_VALUE: - value = &uevent[i]; - - state = VALUE; - - break; - case VALUE: - if (strchr(NEWLINE, uevent[i])) { - uevent[i] = '\0'; - - r = handle_uevent_line(device, key, value, &major, &minor); - if (r < 0) - log_debug_errno(r, "sd-device: failed to handle uevent entry '%s=%s': %m", key, value); - - state = PRE_KEY; - } - - break; - default: - assert_not_reached("invalid state when parsing uevent file"); - } - } - - if (major) { - r = device_set_devnum(device, major, minor); - if (r < 0) - log_debug_errno(r, "sd-device: could not set 'MAJOR=%s' or 'MINOR=%s' from '%s': %m", major, minor, path); - } - - return 0; -} - -_public_ int sd_device_get_ifindex(sd_device *device, int *ifindex) { - int r; - - assert_return(device, -EINVAL); - assert_return(ifindex, -EINVAL); - - r = device_read_uevent_file(device); - if (r < 0) - return r; - - *ifindex = device->ifindex; - - return 0; -} - -_public_ int sd_device_new_from_device_id(sd_device **ret, const char *id) { - int r; - - assert_return(ret, -EINVAL); - assert_return(id, -EINVAL); - - switch (id[0]) { - case 'b': - case 'c': - { - char type; - int maj, min; - - r = sscanf(id, "%c%i:%i", &type, &maj, &min); - if (r != 3) - return -EINVAL; - - return sd_device_new_from_devnum(ret, type, makedev(maj, min)); - } - case 'n': - { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - _cleanup_close_ int sk = -1; - struct ifreq ifr = {}; - int ifindex; - - r = parse_ifindex(&id[1], &ifr.ifr_ifindex); - if (r < 0) - return r; - - sk = socket(PF_INET, SOCK_DGRAM, 0); - if (sk < 0) - return -errno; - - r = ioctl(sk, SIOCGIFNAME, &ifr); - if (r < 0) - return -errno; - - r = sd_device_new_from_subsystem_sysname(&device, "net", ifr.ifr_name); - if (r < 0) - return r; - - r = sd_device_get_ifindex(device, &ifindex); - if (r < 0) - return r; - - /* this is racey, so we might end up with the wrong device */ - if (ifr.ifr_ifindex != ifindex) - return -ENODEV; - - *ret = device; - device = NULL; - - return 0; - } - case '+': - { - char subsys[PATH_MAX]; - char *sysname; - - (void)strscpy(subsys, sizeof(subsys), id + 1); - sysname = strchr(subsys, ':'); - if (!sysname) - return -EINVAL; - - sysname[0] = '\0'; - sysname ++; - - return sd_device_new_from_subsystem_sysname(ret, subsys, sysname); - } - default: - return -EINVAL; - } -} - -_public_ int sd_device_get_syspath(sd_device *device, const char **ret) { - assert_return(device, -EINVAL); - assert_return(ret, -EINVAL); - - assert(path_startswith(device->syspath, "/sys/")); - - *ret = device->syspath; - - return 0; -} - -static int device_new_from_child(sd_device **ret, sd_device *child) { - _cleanup_free_ char *path = NULL; - const char *subdir, *syspath; - int r; - - assert(ret); - assert(child); - - r = sd_device_get_syspath(child, &syspath); - if (r < 0) - return r; - - path = strdup(syspath); - if (!path) - return -ENOMEM; - subdir = path + strlen("/sys"); - - for (;;) { - char *pos; - - pos = strrchr(subdir, '/'); - if (!pos || pos < subdir + 2) - break; - - *pos = '\0'; - - r = sd_device_new_from_syspath(ret, path); - if (r < 0) - continue; - - return 0; - } - - return -ENODEV; -} - -_public_ int sd_device_get_parent(sd_device *child, sd_device **ret) { - - assert_return(ret, -EINVAL); - assert_return(child, -EINVAL); - - if (!child->parent_set) { - child->parent_set = true; - - (void)device_new_from_child(&child->parent, child); - } - - if (!child->parent) - return -ENOENT; - - *ret = child->parent; - - return 0; -} - -int device_set_subsystem(sd_device *device, const char *_subsystem) { - _cleanup_free_ char *subsystem = NULL; - int r; - - assert(device); - assert(_subsystem); - - subsystem = strdup(_subsystem); - if (!subsystem) - return -ENOMEM; - - r = device_add_property_internal(device, "SUBSYSTEM", subsystem); - if (r < 0) - return r; - - free(device->subsystem); - device->subsystem = subsystem; - subsystem = NULL; - - device->subsystem_set = true; - - return 0; -} - -_public_ int sd_device_get_subsystem(sd_device *device, const char **ret) { - assert_return(ret, -EINVAL); - assert_return(device, -EINVAL); - - 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) - r = device_set_subsystem(device, subsystem); - /* 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/")) - 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; - } - - if (!device->subsystem) - return -ENOENT; - - *ret = device->subsystem; - - return 0; -} - -_public_ int sd_device_get_devtype(sd_device *device, const char **devtype) { - int r; - - assert(devtype); - assert(device); - - r = device_read_uevent_file(device); - if (r < 0) - return r; - - *devtype = device->devtype; - - return 0; -} - -_public_ int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret) { - sd_device *parent = NULL; - int r; - - assert_return(child, -EINVAL); - assert_return(subsystem, -EINVAL); - - r = sd_device_get_parent(child, &parent); - while (r >= 0) { - const char *parent_subsystem = NULL; - const char *parent_devtype = NULL; - - (void)sd_device_get_subsystem(parent, &parent_subsystem); - if (streq_ptr(parent_subsystem, subsystem)) { - if (!devtype) - break; - - (void)sd_device_get_devtype(parent, &parent_devtype); - if (streq_ptr(parent_devtype, devtype)) - break; - } - r = sd_device_get_parent(parent, &parent); - } - - if (r < 0) - return r; - - *ret = parent; - - return 0; -} - -_public_ int sd_device_get_devnum(sd_device *device, dev_t *devnum) { - int r; - - assert_return(device, -EINVAL); - assert_return(devnum, -EINVAL); - - r = device_read_uevent_file(device); - if (r < 0) - return r; - - *devnum = device->devnum; - - return 0; -} - -int device_set_driver(sd_device *device, const char *_driver) { - _cleanup_free_ char *driver = NULL; - int r; - - assert(device); - assert(_driver); - - driver = strdup(_driver); - if (!driver) - return -ENOMEM; - - r = device_add_property_internal(device, "DRIVER", driver); - if (r < 0) - return r; - - free(device->driver); - device->driver = driver; - driver = NULL; - - device->driver_set = true; - - return 0; -} - -_public_ int sd_device_get_driver(sd_device *device, const char **ret) { - assert_return(device, -EINVAL); - assert_return(ret, -EINVAL); - - if (!device->driver_set) { - _cleanup_free_ char *driver = NULL; - const char *syspath; - char *path; - int r; - - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - - path = strjoina(syspath, "/driver"); - r = readlink_value(path, &driver); - if (r >= 0) { - r = device_set_driver(device, driver); - if (r < 0) - return log_debug_errno(r, "sd-device: could not set driver for %s: %m", device->devpath); - } else if (r == -ENOENT) - device->driver_set = true; - else - return log_debug_errno(r, "sd-device: could not set driver for %s: %m", device->devpath); - } - - if (!device->driver) - return -ENOENT; - - *ret = device->driver; - - return 0; -} - -_public_ int sd_device_get_devpath(sd_device *device, const char **devpath) { - assert_return(device, -EINVAL); - assert_return(devpath, -EINVAL); - - assert(device->devpath); - assert(device->devpath[0] == '/'); - - *devpath = device->devpath; - - return 0; -} - -_public_ int sd_device_get_devname(sd_device *device, const char **devname) { - int r; - - assert_return(device, -EINVAL); - assert_return(devname, -EINVAL); - - r = device_read_uevent_file(device); - if (r < 0) - return r; - - if (!device->devname) - return -ENOENT; - - assert(path_startswith(device->devname, "/dev/")); - - *devname = device->devname; - - return 0; -} - -static int device_set_sysname(sd_device *device) { - _cleanup_free_ char *sysname = NULL; - const char *sysnum = NULL; - const char *pos; - size_t len = 0; - - pos = strrchr(device->devpath, '/'); - if (!pos) - return -EINVAL; - pos ++; - - /* devpath is not a root directory */ - if (*pos == '\0' || pos <= device->devpath) - return -EINVAL; - - sysname = strdup(pos); - if (!sysname) - return -ENOMEM; - - /* some devices have '!' in their name, change that to '/' */ - while (sysname[len] != '\0') { - if (sysname[len] == '!') - sysname[len] = '/'; - - len ++; - } - - /* trailing number */ - while (len > 0 && isdigit(sysname[--len])) - sysnum = &sysname[len]; - - if (len == 0) - sysnum = NULL; - - free(device->sysname); - device->sysname = sysname; - sysname = NULL; - - device->sysnum = sysnum; - - device->sysname_set = true; - - return 0; -} - -_public_ int sd_device_get_sysname(sd_device *device, const char **ret) { - int r; - - assert_return(device, -EINVAL); - assert_return(ret, -EINVAL); - - if (!device->sysname_set) { - r = device_set_sysname(device); - if (r < 0) - return r; - } - - assert_return(device->sysname, -ENOENT); - - *ret = device->sysname; - - return 0; -} - -_public_ int sd_device_get_sysnum(sd_device *device, const char **ret) { - int r; - - assert_return(device, -EINVAL); - assert_return(ret, -EINVAL); - - if (!device->sysname_set) { - r = device_set_sysname(device); - if (r < 0) - return r; - } - - *ret = device->sysnum; - - return 0; -} - -static bool is_valid_tag(const char *tag) { - assert(tag); - - return !strchr(tag, ':') && !strchr(tag, ' '); -} - -int device_add_tag(sd_device *device, const char *tag) { - int r; - - assert(device); - assert(tag); - - if (!is_valid_tag(tag)) - return -EINVAL; - - r = set_ensure_allocated(&device->tags, &string_hash_ops); - if (r < 0) - return r; - - r = set_put_strdup(device->tags, tag); - if (r < 0) - return r; - - device->tags_generation ++; - device->property_tags_outdated = true; - - return 0; -} - -int device_add_devlink(sd_device *device, const char *devlink) { - int r; - - assert(device); - assert(devlink); - - r = set_ensure_allocated(&device->devlinks, &string_hash_ops); - if (r < 0) - return r; - - r = set_put_strdup(device->devlinks, devlink); - if (r < 0) - return r; - - device->devlinks_generation ++; - device->property_devlinks_outdated = true; - - return 0; -} - -static int device_add_property_internal_from_string(sd_device *device, const char *str) { - _cleanup_free_ char *key = NULL; - char *value; - - assert(device); - assert(str); - - key = strdup(str); - if (!key) - return -ENOMEM; - - value = strchr(key, '='); - if (!value) - return -EINVAL; - - *value = '\0'; - - if (isempty(++value)) - value = NULL; - - return device_add_property_internal(device, key, value); -} - -int device_set_usec_initialized(sd_device *device, const char *initialized) { - uint64_t usec_initialized; - int r; - - assert(device); - assert(initialized); - - r = safe_atou64(initialized, &usec_initialized); - if (r < 0) - return r; - - r = device_add_property_internal(device, "USEC_INITIALIZED", initialized); - if (r < 0) - return r; - - device->usec_initialized = usec_initialized; - - return 0; -} - -static int handle_db_line(sd_device *device, char key, const char *value) { - char *path; - int r; - - assert(device); - assert(value); - - switch (key) { - case 'G': - r = device_add_tag(device, value); - if (r < 0) - return r; - - break; - case 'S': - path = strjoina("/dev/", value); - r = device_add_devlink(device, path); - if (r < 0) - return r; - - break; - case 'E': - r = device_add_property_internal_from_string(device, value); - if (r < 0) - return r; - - break; - case 'I': - r = device_set_usec_initialized(device, value); - if (r < 0) - return r; - - break; - case 'L': - r = safe_atoi(value, &device->devlink_priority); - if (r < 0) - return r; - - break; - case 'W': - r = safe_atoi(value, &device->watch_handle); - if (r < 0) - return r; - - break; - default: - log_debug("device db: unknown key '%c'", key); - } - - return 0; -} - -int device_get_id_filename(sd_device *device, const char **ret) { - assert(device); - assert(ret); - - if (!device->id_filename) { - _cleanup_free_ char *id = NULL; - const char *subsystem; - dev_t devnum; - int ifindex, r; - - r = sd_device_get_subsystem(device, &subsystem); - if (r < 0) - return r; - - r = sd_device_get_devnum(device, &devnum); - if (r < 0) - return r; - - r = sd_device_get_ifindex(device, &ifindex); - if (r < 0) - return r; - - if (major(devnum) > 0) { - assert(subsystem); - - /* use dev_t -- b259:131072, c254:0 */ - r = asprintf(&id, "%c%u:%u", - streq(subsystem, "block") ? 'b' : 'c', - major(devnum), minor(devnum)); - if (r < 0) - return -ENOMEM; - } else if (ifindex > 0) { - /* use netdev ifindex -- n3 */ - r = asprintf(&id, "n%u", ifindex); - if (r < 0) - return -ENOMEM; - } else { - /* use $subsys:$sysname -- pci:0000:00:1f.2 - * sysname() has '!' translated, get it from devpath - */ - const char *sysname; - - sysname = basename(device->devpath); - if (!sysname) - return -EINVAL; - - if (!subsystem) - return -EINVAL; - - r = asprintf(&id, "+%s:%s", subsystem, sysname); - if (r < 0) - return -ENOMEM; - } - - device->id_filename = id; - id = NULL; - } - - *ret = device->id_filename; - - return 0; -} - -int device_read_db_aux(sd_device *device, bool force) { - _cleanup_free_ char *db = NULL; - char *path; - const char *id, *value; - char key; - size_t db_len; - unsigned i; - int r; - - enum { - PRE_KEY, - KEY, - PRE_VALUE, - VALUE, - INVALID_LINE, - } state = PRE_KEY; - - if (device->db_loaded || (!force && device->sealed)) - return 0; - - device->db_loaded = true; - - r = device_get_id_filename(device, &id); - if (r < 0) - return r; - - path = strjoina("/run/udev/data/", id); - - r = read_full_file(path, &db, &db_len); - if (r < 0) { - if (r == -ENOENT) - return 0; - else - return log_debug_errno(r, "sd-device: failed to read db '%s': %m", path); - } - - /* devices with a database entry are initialized */ - device->is_initialized = true; - - for (i = 0; i < db_len; i++) { - switch (state) { - case PRE_KEY: - if (!strchr(NEWLINE, db[i])) { - key = db[i]; - - state = KEY; - } - - break; - case KEY: - if (db[i] != ':') { - log_debug("sd-device: ignoring invalid db entry with key '%c'", key); - - state = INVALID_LINE; - } else { - db[i] = '\0'; - - state = PRE_VALUE; - } - - break; - case PRE_VALUE: - value = &db[i]; - - state = VALUE; - - break; - case INVALID_LINE: - if (strchr(NEWLINE, db[i])) - state = PRE_KEY; - - break; - case VALUE: - if (strchr(NEWLINE, db[i])) { - db[i] = '\0'; - r = handle_db_line(device, key, value); - if (r < 0) - log_debug_errno(r, "sd-device: failed to handle db entry '%c:%s': %m", key, value); - - state = PRE_KEY; - } - - break; - default: - assert_not_reached("invalid state when parsing db"); - } - } - - return 0; -} - -static int device_read_db(sd_device *device) { - return device_read_db_aux(device, false); -} - -_public_ int sd_device_get_is_initialized(sd_device *device, int *initialized) { - int r; - - assert_return(device, -EINVAL); - assert_return(initialized, -EINVAL); - - r = device_read_db(device); - if (r < 0) - return r; - - *initialized = device->is_initialized; - - return 0; -} - -_public_ int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *usec) { - usec_t now_ts; - int r; - - assert_return(device, -EINVAL); - assert_return(usec, -EINVAL); - - r = device_read_db(device); - if (r < 0) - return r; - - if (!device->is_initialized) - return -EBUSY; - - if (!device->usec_initialized) - return -ENODATA; - - now_ts = now(clock_boottime_or_monotonic()); - - if (now_ts < device->usec_initialized) - return -EIO; - - *usec = now_ts - device->usec_initialized; - - return 0; -} - -_public_ const char *sd_device_get_tag_first(sd_device *device) { - void *v; - - assert_return(device, NULL); - - (void) device_read_db(device); - - device->tags_iterator_generation = device->tags_generation; - device->tags_iterator = ITERATOR_FIRST; - - set_iterate(device->tags, &device->tags_iterator, &v); - return v; -} - -_public_ const char *sd_device_get_tag_next(sd_device *device) { - void *v; - - assert_return(device, NULL); - - (void) device_read_db(device); - - if (device->tags_iterator_generation != device->tags_generation) - return NULL; - - set_iterate(device->tags, &device->tags_iterator, &v); - return v; -} - -_public_ const char *sd_device_get_devlink_first(sd_device *device) { - void *v; - - assert_return(device, NULL); - - (void) device_read_db(device); - - device->devlinks_iterator_generation = device->devlinks_generation; - device->devlinks_iterator = ITERATOR_FIRST; - - set_iterate(device->devlinks, &device->devlinks_iterator, &v); - return v; -} - -_public_ const char *sd_device_get_devlink_next(sd_device *device) { - void *v; - - assert_return(device, NULL); - - (void) device_read_db(device); - - if (device->devlinks_iterator_generation != device->devlinks_generation) - return NULL; - - set_iterate(device->devlinks, &device->devlinks_iterator, &v); - return v; -} - -static int device_properties_prepare(sd_device *device) { - int r; - - assert(device); - - r = device_read_uevent_file(device); - if (r < 0) - return r; - - r = device_read_db(device); - if (r < 0) - return r; - - if (device->property_devlinks_outdated) { - char *devlinks = NULL; - const char *devlink; - - devlink = sd_device_get_devlink_first(device); - if (devlink) - devlinks = strdupa(devlink); - - while ((devlink = sd_device_get_devlink_next(device))) - devlinks = strjoina(devlinks, " ", devlink); - - r = device_add_property_internal(device, "DEVLINKS", devlinks); - if (r < 0) - return r; - - device->property_devlinks_outdated = false; - } - - if (device->property_tags_outdated) { - char *tags = NULL; - const char *tag; - - tag = sd_device_get_tag_first(device); - if (tag) - tags = strjoina(":", tag); - - while ((tag = sd_device_get_tag_next(device))) - tags = strjoina(tags, ":", tag); - - tags = strjoina(tags, ":"); - - r = device_add_property_internal(device, "TAGS", tags); - if (r < 0) - return r; - - device->property_tags_outdated = false; - } - - return 0; -} - -_public_ const char *sd_device_get_property_first(sd_device *device, const char **_value) { - const char *key; - const char *value; - int r; - - assert_return(device, NULL); - - r = device_properties_prepare(device); - if (r < 0) - return NULL; - - device->properties_iterator_generation = device->properties_generation; - device->properties_iterator = ITERATOR_FIRST; - - ordered_hashmap_iterate(device->properties, &device->properties_iterator, (void**)&value, (const void**)&key); - - if (_value) - *_value = value; - - return key; -} - -_public_ const char *sd_device_get_property_next(sd_device *device, const char **_value) { - const char *key; - const char *value; - int r; - - assert_return(device, NULL); - - r = device_properties_prepare(device); - if (r < 0) - return NULL; - - if (device->properties_iterator_generation != device->properties_generation) - return NULL; - - ordered_hashmap_iterate(device->properties, &device->properties_iterator, (void**)&value, (const void**)&key); - - if (_value) - *_value = value; - - return key; -} - -static int device_sysattrs_read_all(sd_device *device) { - _cleanup_closedir_ DIR *dir = NULL; - const char *syspath; - struct dirent *dent; - int r; - - assert(device); - - if (device->sysattrs_read) - return 0; - - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - - dir = opendir(syspath); - if (!dir) - return -errno; - - r = set_ensure_allocated(&device->sysattrs, &string_hash_ops); - if (r < 0) - return r; - - for (dent = readdir(dir); dent != NULL; dent = readdir(dir)) { - char *path; - struct stat statbuf; - - /* only handle symlinks and regular files */ - if (dent->d_type != DT_LNK && dent->d_type != DT_REG) - continue; - - path = strjoina(syspath, "/", dent->d_name); - - if (lstat(path, &statbuf) != 0) - continue; - - if (!(statbuf.st_mode & S_IRUSR)) - continue; - - r = set_put_strdup(device->sysattrs, dent->d_name); - if (r < 0) - return r; - } - - device->sysattrs_read = true; - - return 0; -} - -_public_ const char *sd_device_get_sysattr_first(sd_device *device) { - void *v; - int r; - - assert_return(device, NULL); - - if (!device->sysattrs_read) { - r = device_sysattrs_read_all(device); - if (r < 0) { - errno = -r; - return NULL; - } - } - - device->sysattrs_iterator = ITERATOR_FIRST; - - set_iterate(device->sysattrs, &device->sysattrs_iterator, &v); - return v; -} - -_public_ const char *sd_device_get_sysattr_next(sd_device *device) { - void *v; - - assert_return(device, NULL); - - if (!device->sysattrs_read) - return NULL; - - set_iterate(device->sysattrs, &device->sysattrs_iterator, &v); - return v; -} - -_public_ int sd_device_has_tag(sd_device *device, const char *tag) { - assert_return(device, -EINVAL); - assert_return(tag, -EINVAL); - - (void) device_read_db(device); - - return !!set_contains(device->tags, tag); -} - -_public_ int sd_device_get_property_value(sd_device *device, const char *key, const char **_value) { - char *value; - int r; - - assert_return(device, -EINVAL); - assert_return(key, -EINVAL); - assert_return(_value, -EINVAL); - - r = device_properties_prepare(device); - if (r < 0) - return r; - - value = ordered_hashmap_get(device->properties, key); - if (!value) - return -ENOENT; - - *_value = value; - - return 0; -} - -/* replaces the value if it already exists */ -static int device_add_sysattr_value(sd_device *device, const char *_key, char *value) { - _cleanup_free_ char *key = NULL; - _cleanup_free_ char *value_old = NULL; - int r; - - assert(device); - assert(_key); - - r = hashmap_ensure_allocated(&device->sysattr_values, &string_hash_ops); - if (r < 0) - return r; - - value_old = hashmap_remove2(device->sysattr_values, _key, (void **)&key); - if (!key) { - key = strdup(_key); - if (!key) - return -ENOMEM; - } - - r = hashmap_put(device->sysattr_values, key, value); - if (r < 0) - return r; - - key = NULL; - - return 0; -} - -static int device_get_sysattr_value(sd_device *device, const char *_key, const char **_value) { - const char *key = NULL, *value; - - assert(device); - assert(_key); - - value = hashmap_get2(device->sysattr_values, _key, (void **) &key); - if (!key) - return -ENOENT; - - if (_value) - *_value = value; - - return 0; -} - -/* We cache all sysattr lookups. If an attribute does not exist, it is stored - * with a NULL value in the cache, otherwise the returned string is stored */ -_public_ int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value) { - _cleanup_free_ char *value = NULL; - const char *syspath, *cached_value = NULL; - char *path; - struct stat statbuf; - int r; - - assert_return(device, -EINVAL); - assert_return(sysattr, -EINVAL); - - /* look for possibly already cached result */ - r = device_get_sysattr_value(device, sysattr, &cached_value); - if (r != -ENOENT) { - if (r < 0) - return r; - - if (!cached_value) - /* we looked up the sysattr before and it did not exist */ - return -ENOENT; - - if (_value) - *_value = cached_value; - - return 0; - } - - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - - path = strjoina(syspath, "/", sysattr); - r = lstat(path, &statbuf); - if (r < 0) { - /* remember that we could not access the sysattr */ - r = device_add_sysattr_value(device, sysattr, NULL); - if (r < 0) - return r; - - return -ENOENT; - } else if (S_ISLNK(statbuf.st_mode)) { - /* Some core links return only the last element of the target path, - * these are just values, the paths should not be exposed. */ - if (STR_IN_SET(sysattr, "driver", "subsystem", "module")) { - r = readlink_value(path, &value); - if (r < 0) - return r; - } else - return -EINVAL; - } else if (S_ISDIR(statbuf.st_mode)) { - /* skip directories */ - return -EINVAL; - } else if (!(statbuf.st_mode & S_IRUSR)) { - /* skip non-readable files */ - return -EPERM; - } else { - size_t size; - - /* read attribute value */ - r = read_full_file(path, &value, &size); - if (r < 0) - return r; - - /* drop trailing newlines */ - while (size > 0 && value[--size] == '\n') - value[size] = '\0'; - } - - r = device_add_sysattr_value(device, sysattr, value); - if (r < 0) - return r; - - *_value = value; - value = NULL; - - return 0; -} - -static void device_remove_sysattr_value(sd_device *device, const char *_key) { - _cleanup_free_ char *key = NULL; - _cleanup_free_ char *value = NULL; - - assert(device); - assert(_key); - - value = hashmap_remove2(device->sysattr_values, _key, (void **) &key); - - return; -} - -/* set the attribute and save it in the cache. If a NULL value is passed the - * attribute is cleared from the cache */ -_public_ int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, char *_value) { - _cleanup_close_ int fd = -1; - _cleanup_free_ char *value = NULL; - const char *syspath; - char *path; - struct stat statbuf; - size_t value_len = 0; - ssize_t size; - int r; - - assert_return(device, -EINVAL); - assert_return(sysattr, -EINVAL); - - if (!_value) { - device_remove_sysattr_value(device, sysattr); - - return 0; - } - - r = sd_device_get_syspath(device, &syspath); - if (r < 0) - return r; - - path = strjoina(syspath, "/", sysattr); - r = lstat(path, &statbuf); - if (r < 0) { - value = strdup(""); - if (!value) - return -ENOMEM; - - r = device_add_sysattr_value(device, sysattr, value); - if (r < 0) - return r; - - return -ENXIO; - } - - if (S_ISLNK(statbuf.st_mode)) - return -EINVAL; - - /* skip directories */ - if (S_ISDIR(statbuf.st_mode)) - return -EISDIR; - - /* skip non-readable files */ - if ((statbuf.st_mode & S_IRUSR) == 0) - return -EACCES; - - value_len = strlen(_value); - - /* drop trailing newlines */ - while (value_len > 0 && _value[value_len - 1] == '\n') - _value[--value_len] = '\0'; - - /* value length is limited to 4k */ - if (value_len > 4096) - return -EINVAL; - - fd = open(path, O_WRONLY | O_CLOEXEC); - if (fd < 0) - return -errno; - - value = strdup(_value); - if (!value) - return -ENOMEM; - - size = write(fd, value, value_len); - if (size < 0) - return -errno; - - if ((size_t)size != value_len) - return -EIO; - - r = device_add_sysattr_value(device, sysattr, value); - if (r < 0) - return r; - - value = NULL; - - return 0; -} diff --git a/src/libsystemd/sd-event/Makefile b/src/libsystemd/sd-event/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-event/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c deleted file mode 100644 index deef6ba9d3..0000000000 --- a/src/libsystemd/sd-event/sd-event.c +++ /dev/null @@ -1,2887 +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 . -***/ - -#include -#include -#include - -#include "sd-daemon.h" -#include "sd-event.h" -#include "sd-id128.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "hashmap.h" -#include "list.h" -#include "macro.h" -#include "missing.h" -#include "prioq.h" -#include "process-util.h" -#include "set.h" -#include "signal-util.h" -#include "string-table.h" -#include "string-util.h" -#include "time-util.h" -#include "util.h" - -#define DEFAULT_ACCURACY_USEC (250 * USEC_PER_MSEC) - -typedef enum EventSourceType { - SOURCE_IO, - SOURCE_TIME_REALTIME, - SOURCE_TIME_BOOTTIME, - SOURCE_TIME_MONOTONIC, - SOURCE_TIME_REALTIME_ALARM, - SOURCE_TIME_BOOTTIME_ALARM, - SOURCE_SIGNAL, - SOURCE_CHILD, - SOURCE_DEFER, - SOURCE_POST, - SOURCE_EXIT, - SOURCE_WATCHDOG, - _SOURCE_EVENT_SOURCE_TYPE_MAX, - _SOURCE_EVENT_SOURCE_TYPE_INVALID = -1 -} EventSourceType; - -static const char* const event_source_type_table[_SOURCE_EVENT_SOURCE_TYPE_MAX] = { - [SOURCE_IO] = "io", - [SOURCE_TIME_REALTIME] = "realtime", - [SOURCE_TIME_BOOTTIME] = "bootime", - [SOURCE_TIME_MONOTONIC] = "monotonic", - [SOURCE_TIME_REALTIME_ALARM] = "realtime-alarm", - [SOURCE_TIME_BOOTTIME_ALARM] = "boottime-alarm", - [SOURCE_SIGNAL] = "signal", - [SOURCE_CHILD] = "child", - [SOURCE_DEFER] = "defer", - [SOURCE_POST] = "post", - [SOURCE_EXIT] = "exit", - [SOURCE_WATCHDOG] = "watchdog", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(event_source_type, int); - -/* All objects we use in epoll events start with this value, so that - * we know how to dispatch it */ -typedef enum WakeupType { - WAKEUP_NONE, - WAKEUP_EVENT_SOURCE, - WAKEUP_CLOCK_DATA, - WAKEUP_SIGNAL_DATA, - _WAKEUP_TYPE_MAX, - _WAKEUP_TYPE_INVALID = -1, -} WakeupType; - -#define EVENT_SOURCE_IS_TIME(t) IN_SET((t), SOURCE_TIME_REALTIME, SOURCE_TIME_BOOTTIME, SOURCE_TIME_MONOTONIC, SOURCE_TIME_REALTIME_ALARM, SOURCE_TIME_BOOTTIME_ALARM) - -struct sd_event_source { - WakeupType wakeup; - - unsigned n_ref; - - sd_event *event; - void *userdata; - sd_event_handler_t prepare; - - char *description; - - EventSourceType type:5; - int enabled:3; - bool pending:1; - bool dispatching:1; - bool floating:1; - - int64_t priority; - unsigned pending_index; - unsigned prepare_index; - unsigned pending_iteration; - unsigned prepare_iteration; - - LIST_FIELDS(sd_event_source, sources); - - union { - struct { - sd_event_io_handler_t callback; - int fd; - uint32_t events; - uint32_t revents; - bool registered:1; - } io; - struct { - sd_event_time_handler_t callback; - usec_t next, accuracy; - unsigned earliest_index; - unsigned latest_index; - } time; - struct { - sd_event_signal_handler_t callback; - struct signalfd_siginfo siginfo; - int sig; - } signal; - struct { - sd_event_child_handler_t callback; - siginfo_t siginfo; - pid_t pid; - int options; - } child; - struct { - sd_event_handler_t callback; - } defer; - struct { - sd_event_handler_t callback; - } post; - struct { - sd_event_handler_t callback; - unsigned prioq_index; - } exit; - }; -}; - -struct clock_data { - WakeupType wakeup; - int fd; - - /* For all clocks we maintain two priority queues each, one - * ordered for the earliest times the events may be - * dispatched, and one ordered by the latest times they must - * have been dispatched. The range between the top entries in - * the two prioqs is the time window we can freely schedule - * wakeups in */ - - Prioq *earliest; - Prioq *latest; - usec_t next; - - bool needs_rearm:1; -}; - -struct signal_data { - WakeupType wakeup; - - /* For each priority we maintain one signal fd, so that we - * only have to dequeue a single event per priority at a - * time. */ - - int fd; - int64_t priority; - sigset_t sigset; - sd_event_source *current; -}; - -struct sd_event { - unsigned n_ref; - - int epoll_fd; - int watchdog_fd; - - Prioq *pending; - Prioq *prepare; - - /* timerfd_create() only supports these five clocks so far. We - * can add support for more clocks when the kernel learns to - * deal with them, too. */ - struct clock_data realtime; - struct clock_data boottime; - struct clock_data monotonic; - struct clock_data realtime_alarm; - struct clock_data boottime_alarm; - - usec_t perturb; - - sd_event_source **signal_sources; /* indexed by signal number */ - Hashmap *signal_data; /* indexed by priority */ - - Hashmap *child_sources; - unsigned n_enabled_child_sources; - - Set *post_sources; - - Prioq *exit; - - pid_t original_pid; - - unsigned iteration; - dual_timestamp timestamp; - usec_t timestamp_boottime; - int state; - - bool exit_requested:1; - bool need_process_child:1; - bool watchdog:1; - bool profile_delays:1; - - int exit_code; - - pid_t tid; - sd_event **default_event_ptr; - - usec_t watchdog_last, watchdog_period; - - unsigned n_sources; - - LIST_HEAD(sd_event_source, sources); - - usec_t last_run, last_log; - unsigned delays[sizeof(usec_t) * 8]; -}; - -static void source_disconnect(sd_event_source *s); - -static int pending_prioq_compare(const void *a, const void *b) { - const sd_event_source *x = a, *y = b; - - assert(x->pending); - assert(y->pending); - - /* Enabled ones first */ - if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) - return -1; - if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) - return 1; - - /* Lower priority values first */ - if (x->priority < y->priority) - return -1; - if (x->priority > y->priority) - return 1; - - /* Older entries first */ - if (x->pending_iteration < y->pending_iteration) - return -1; - if (x->pending_iteration > y->pending_iteration) - return 1; - - return 0; -} - -static int prepare_prioq_compare(const void *a, const void *b) { - const sd_event_source *x = a, *y = b; - - assert(x->prepare); - assert(y->prepare); - - /* Enabled ones first */ - if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) - return -1; - if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) - return 1; - - /* Move most recently prepared ones last, so that we can stop - * preparing as soon as we hit one that has already been - * prepared in the current iteration */ - if (x->prepare_iteration < y->prepare_iteration) - return -1; - if (x->prepare_iteration > y->prepare_iteration) - return 1; - - /* Lower priority values first */ - if (x->priority < y->priority) - return -1; - if (x->priority > y->priority) - return 1; - - return 0; -} - -static int earliest_time_prioq_compare(const void *a, const void *b) { - const sd_event_source *x = a, *y = b; - - assert(EVENT_SOURCE_IS_TIME(x->type)); - assert(x->type == y->type); - - /* Enabled ones first */ - if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) - return -1; - if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) - return 1; - - /* Move the pending ones to the end */ - if (!x->pending && y->pending) - return -1; - if (x->pending && !y->pending) - return 1; - - /* Order by time */ - if (x->time.next < y->time.next) - return -1; - if (x->time.next > y->time.next) - return 1; - - return 0; -} - -static usec_t time_event_source_latest(const sd_event_source *s) { - return usec_add(s->time.next, s->time.accuracy); -} - -static int latest_time_prioq_compare(const void *a, const void *b) { - const sd_event_source *x = a, *y = b; - - assert(EVENT_SOURCE_IS_TIME(x->type)); - assert(x->type == y->type); - - /* Enabled ones first */ - if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) - return -1; - if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) - return 1; - - /* Move the pending ones to the end */ - if (!x->pending && y->pending) - return -1; - if (x->pending && !y->pending) - return 1; - - /* Order by time */ - if (time_event_source_latest(x) < time_event_source_latest(y)) - return -1; - if (time_event_source_latest(x) > time_event_source_latest(y)) - return 1; - - return 0; -} - -static int exit_prioq_compare(const void *a, const void *b) { - const sd_event_source *x = a, *y = b; - - assert(x->type == SOURCE_EXIT); - assert(y->type == SOURCE_EXIT); - - /* Enabled ones first */ - if (x->enabled != SD_EVENT_OFF && y->enabled == SD_EVENT_OFF) - return -1; - if (x->enabled == SD_EVENT_OFF && y->enabled != SD_EVENT_OFF) - return 1; - - /* Lower priority values first */ - if (x->priority < y->priority) - return -1; - if (x->priority > y->priority) - return 1; - - return 0; -} - -static void free_clock_data(struct clock_data *d) { - assert(d); - assert(d->wakeup == WAKEUP_CLOCK_DATA); - - safe_close(d->fd); - prioq_free(d->earliest); - prioq_free(d->latest); -} - -static void event_free(sd_event *e) { - sd_event_source *s; - - assert(e); - - while ((s = e->sources)) { - assert(s->floating); - source_disconnect(s); - sd_event_source_unref(s); - } - - assert(e->n_sources == 0); - - if (e->default_event_ptr) - *(e->default_event_ptr) = NULL; - - safe_close(e->epoll_fd); - safe_close(e->watchdog_fd); - - free_clock_data(&e->realtime); - free_clock_data(&e->boottime); - free_clock_data(&e->monotonic); - free_clock_data(&e->realtime_alarm); - free_clock_data(&e->boottime_alarm); - - prioq_free(e->pending); - prioq_free(e->prepare); - prioq_free(e->exit); - - free(e->signal_sources); - hashmap_free(e->signal_data); - - hashmap_free(e->child_sources); - set_free(e->post_sources); - free(e); -} - -_public_ int sd_event_new(sd_event** ret) { - sd_event *e; - int r; - - assert_return(ret, -EINVAL); - - e = new0(sd_event, 1); - if (!e) - return -ENOMEM; - - e->n_ref = 1; - e->watchdog_fd = e->epoll_fd = e->realtime.fd = e->boottime.fd = e->monotonic.fd = e->realtime_alarm.fd = e->boottime_alarm.fd = -1; - e->realtime.next = e->boottime.next = e->monotonic.next = e->realtime_alarm.next = e->boottime_alarm.next = USEC_INFINITY; - e->realtime.wakeup = e->boottime.wakeup = e->monotonic.wakeup = e->realtime_alarm.wakeup = e->boottime_alarm.wakeup = WAKEUP_CLOCK_DATA; - e->original_pid = getpid(); - e->perturb = USEC_INFINITY; - - r = prioq_ensure_allocated(&e->pending, pending_prioq_compare); - if (r < 0) - goto fail; - - e->epoll_fd = epoll_create1(EPOLL_CLOEXEC); - if (e->epoll_fd < 0) { - r = -errno; - goto fail; - } - - if (secure_getenv("SD_EVENT_PROFILE_DELAYS")) { - log_debug("Event loop profiling enabled. Logarithmic histogram of event loop iterations in the range 2^0 ... 2^63 us will be logged every 5s."); - e->profile_delays = true; - } - - *ret = e; - return 0; - -fail: - event_free(e); - return r; -} - -_public_ sd_event* sd_event_ref(sd_event *e) { - - if (!e) - return NULL; - - assert(e->n_ref >= 1); - e->n_ref++; - - return e; -} - -_public_ sd_event* sd_event_unref(sd_event *e) { - - if (!e) - return NULL; - - assert(e->n_ref >= 1); - e->n_ref--; - - if (e->n_ref <= 0) - event_free(e); - - return NULL; -} - -static bool event_pid_changed(sd_event *e) { - assert(e); - - /* We don't support people creating an event loop and keeping - * it around over a fork(). Let's complain. */ - - return e->original_pid != getpid(); -} - -static void source_io_unregister(sd_event_source *s) { - int r; - - assert(s); - assert(s->type == SOURCE_IO); - - if (event_pid_changed(s->event)) - return; - - if (!s->io.registered) - return; - - r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, s->io.fd, NULL); - if (r < 0) - log_debug_errno(errno, "Failed to remove source %s (type %s) from epoll: %m", - strna(s->description), event_source_type_to_string(s->type)); - - s->io.registered = false; -} - -static int source_io_register( - sd_event_source *s, - int enabled, - uint32_t events) { - - struct epoll_event ev = {}; - int r; - - assert(s); - assert(s->type == SOURCE_IO); - assert(enabled != SD_EVENT_OFF); - - ev.events = events; - ev.data.ptr = s; - - if (enabled == SD_EVENT_ONESHOT) - ev.events |= EPOLLONESHOT; - - if (s->io.registered) - r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_MOD, s->io.fd, &ev); - else - r = epoll_ctl(s->event->epoll_fd, EPOLL_CTL_ADD, s->io.fd, &ev); - if (r < 0) - return -errno; - - s->io.registered = true; - - return 0; -} - -static clockid_t event_source_type_to_clock(EventSourceType t) { - - switch (t) { - - case SOURCE_TIME_REALTIME: - return CLOCK_REALTIME; - - case SOURCE_TIME_BOOTTIME: - return CLOCK_BOOTTIME; - - case SOURCE_TIME_MONOTONIC: - return CLOCK_MONOTONIC; - - case SOURCE_TIME_REALTIME_ALARM: - return CLOCK_REALTIME_ALARM; - - case SOURCE_TIME_BOOTTIME_ALARM: - return CLOCK_BOOTTIME_ALARM; - - default: - return (clockid_t) -1; - } -} - -static EventSourceType clock_to_event_source_type(clockid_t clock) { - - switch (clock) { - - case CLOCK_REALTIME: - return SOURCE_TIME_REALTIME; - - case CLOCK_BOOTTIME: - return SOURCE_TIME_BOOTTIME; - - case CLOCK_MONOTONIC: - return SOURCE_TIME_MONOTONIC; - - case CLOCK_REALTIME_ALARM: - return SOURCE_TIME_REALTIME_ALARM; - - case CLOCK_BOOTTIME_ALARM: - return SOURCE_TIME_BOOTTIME_ALARM; - - default: - return _SOURCE_EVENT_SOURCE_TYPE_INVALID; - } -} - -static struct clock_data* event_get_clock_data(sd_event *e, EventSourceType t) { - assert(e); - - switch (t) { - - case SOURCE_TIME_REALTIME: - return &e->realtime; - - case SOURCE_TIME_BOOTTIME: - return &e->boottime; - - case SOURCE_TIME_MONOTONIC: - return &e->monotonic; - - case SOURCE_TIME_REALTIME_ALARM: - return &e->realtime_alarm; - - case SOURCE_TIME_BOOTTIME_ALARM: - return &e->boottime_alarm; - - default: - return NULL; - } -} - -static int event_make_signal_data( - sd_event *e, - int sig, - struct signal_data **ret) { - - struct epoll_event ev = {}; - struct signal_data *d; - bool added = false; - sigset_t ss_copy; - int64_t priority; - int r; - - assert(e); - - if (event_pid_changed(e)) - return -ECHILD; - - if (e->signal_sources && e->signal_sources[sig]) - priority = e->signal_sources[sig]->priority; - else - priority = 0; - - d = hashmap_get(e->signal_data, &priority); - if (d) { - if (sigismember(&d->sigset, sig) > 0) { - if (ret) - *ret = d; - return 0; - } - } else { - r = hashmap_ensure_allocated(&e->signal_data, &uint64_hash_ops); - if (r < 0) - return r; - - d = new0(struct signal_data, 1); - if (!d) - return -ENOMEM; - - d->wakeup = WAKEUP_SIGNAL_DATA; - d->fd = -1; - d->priority = priority; - - r = hashmap_put(e->signal_data, &d->priority, d); - if (r < 0) { - free(d); - return r; - } - - added = true; - } - - ss_copy = d->sigset; - assert_se(sigaddset(&ss_copy, sig) >= 0); - - r = signalfd(d->fd, &ss_copy, SFD_NONBLOCK|SFD_CLOEXEC); - if (r < 0) { - r = -errno; - goto fail; - } - - d->sigset = ss_copy; - - if (d->fd >= 0) { - if (ret) - *ret = d; - return 0; - } - - d->fd = r; - - ev.events = EPOLLIN; - ev.data.ptr = d; - - r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, d->fd, &ev); - if (r < 0) { - r = -errno; - goto fail; - } - - if (ret) - *ret = d; - - return 0; - -fail: - if (added) { - d->fd = safe_close(d->fd); - hashmap_remove(e->signal_data, &d->priority); - free(d); - } - - return r; -} - -static void event_unmask_signal_data(sd_event *e, struct signal_data *d, int sig) { - assert(e); - assert(d); - - /* Turns off the specified signal in the signal data - * object. If the signal mask of the object becomes empty that - * way removes it. */ - - if (sigismember(&d->sigset, sig) == 0) - return; - - assert_se(sigdelset(&d->sigset, sig) >= 0); - - if (sigisemptyset(&d->sigset)) { - - /* If all the mask is all-zero we can get rid of the structure */ - hashmap_remove(e->signal_data, &d->priority); - assert(!d->current); - safe_close(d->fd); - free(d); - return; - } - - assert(d->fd >= 0); - - if (signalfd(d->fd, &d->sigset, SFD_NONBLOCK|SFD_CLOEXEC) < 0) - log_debug_errno(errno, "Failed to unset signal bit, ignoring: %m"); -} - -static void event_gc_signal_data(sd_event *e, const int64_t *priority, int sig) { - struct signal_data *d; - static const int64_t zero_priority = 0; - - assert(e); - - /* Rechecks if the specified signal is still something we are - * interested in. If not, we'll unmask it, and possibly drop - * the signalfd for it. */ - - if (sig == SIGCHLD && - e->n_enabled_child_sources > 0) - return; - - if (e->signal_sources && - e->signal_sources[sig] && - e->signal_sources[sig]->enabled != SD_EVENT_OFF) - return; - - /* - * The specified signal might be enabled in three different queues: - * - * 1) the one that belongs to the priority passed (if it is non-NULL) - * 2) the one that belongs to the priority of the event source of the signal (if there is one) - * 3) the 0 priority (to cover the SIGCHLD case) - * - * Hence, let's remove it from all three here. - */ - - if (priority) { - d = hashmap_get(e->signal_data, priority); - if (d) - event_unmask_signal_data(e, d, sig); - } - - if (e->signal_sources && e->signal_sources[sig]) { - d = hashmap_get(e->signal_data, &e->signal_sources[sig]->priority); - if (d) - event_unmask_signal_data(e, d, sig); - } - - d = hashmap_get(e->signal_data, &zero_priority); - if (d) - event_unmask_signal_data(e, d, sig); -} - -static void source_disconnect(sd_event_source *s) { - sd_event *event; - - assert(s); - - if (!s->event) - return; - - assert(s->event->n_sources > 0); - - switch (s->type) { - - case SOURCE_IO: - if (s->io.fd >= 0) - source_io_unregister(s); - - break; - - case SOURCE_TIME_REALTIME: - case SOURCE_TIME_BOOTTIME: - case SOURCE_TIME_MONOTONIC: - case SOURCE_TIME_REALTIME_ALARM: - case SOURCE_TIME_BOOTTIME_ALARM: { - struct clock_data *d; - - d = event_get_clock_data(s->event, s->type); - assert(d); - - prioq_remove(d->earliest, s, &s->time.earliest_index); - prioq_remove(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - break; - } - - case SOURCE_SIGNAL: - if (s->signal.sig > 0) { - - if (s->event->signal_sources) - s->event->signal_sources[s->signal.sig] = NULL; - - event_gc_signal_data(s->event, &s->priority, s->signal.sig); - } - - break; - - case SOURCE_CHILD: - if (s->child.pid > 0) { - if (s->enabled != SD_EVENT_OFF) { - assert(s->event->n_enabled_child_sources > 0); - s->event->n_enabled_child_sources--; - } - - (void) hashmap_remove(s->event->child_sources, PID_TO_PTR(s->child.pid)); - event_gc_signal_data(s->event, &s->priority, SIGCHLD); - } - - break; - - case SOURCE_DEFER: - /* nothing */ - break; - - case SOURCE_POST: - set_remove(s->event->post_sources, s); - break; - - case SOURCE_EXIT: - prioq_remove(s->event->exit, s, &s->exit.prioq_index); - break; - - default: - assert_not_reached("Wut? I shouldn't exist."); - } - - if (s->pending) - prioq_remove(s->event->pending, s, &s->pending_index); - - if (s->prepare) - prioq_remove(s->event->prepare, s, &s->prepare_index); - - event = s->event; - - s->type = _SOURCE_EVENT_SOURCE_TYPE_INVALID; - s->event = NULL; - LIST_REMOVE(sources, event->sources, s); - event->n_sources--; - - if (!s->floating) - sd_event_unref(event); -} - -static void source_free(sd_event_source *s) { - assert(s); - - source_disconnect(s); - free(s->description); - free(s); -} - -static int source_set_pending(sd_event_source *s, bool b) { - int r; - - assert(s); - assert(s->type != SOURCE_EXIT); - - if (s->pending == b) - return 0; - - s->pending = b; - - if (b) { - s->pending_iteration = s->event->iteration; - - r = prioq_put(s->event->pending, s, &s->pending_index); - if (r < 0) { - s->pending = false; - return r; - } - } else - assert_se(prioq_remove(s->event->pending, s, &s->pending_index)); - - if (EVENT_SOURCE_IS_TIME(s->type)) { - struct clock_data *d; - - d = event_get_clock_data(s->event, s->type); - assert(d); - - prioq_reshuffle(d->earliest, s, &s->time.earliest_index); - prioq_reshuffle(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - } - - if (s->type == SOURCE_SIGNAL && !b) { - struct signal_data *d; - - d = hashmap_get(s->event->signal_data, &s->priority); - if (d && d->current == s) - d->current = NULL; - } - - return 0; -} - -static sd_event_source *source_new(sd_event *e, bool floating, EventSourceType type) { - sd_event_source *s; - - assert(e); - - s = new0(sd_event_source, 1); - if (!s) - return NULL; - - s->n_ref = 1; - s->event = e; - s->floating = floating; - s->type = type; - s->pending_index = s->prepare_index = PRIOQ_IDX_NULL; - - if (!floating) - sd_event_ref(e); - - LIST_PREPEND(sources, e->sources, s); - e->n_sources ++; - - return s; -} - -_public_ int sd_event_add_io( - sd_event *e, - sd_event_source **ret, - int fd, - uint32_t events, - sd_event_io_handler_t callback, - void *userdata) { - - sd_event_source *s; - int r; - - assert_return(e, -EINVAL); - assert_return(fd >= 0, -EBADF); - assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL); - assert_return(callback, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - s = source_new(e, !ret, SOURCE_IO); - if (!s) - return -ENOMEM; - - s->wakeup = WAKEUP_EVENT_SOURCE; - s->io.fd = fd; - s->io.events = events; - s->io.callback = callback; - s->userdata = userdata; - s->enabled = SD_EVENT_ON; - - r = source_io_register(s, s->enabled, events); - if (r < 0) { - source_free(s); - return r; - } - - if (ret) - *ret = s; - - return 0; -} - -static void initialize_perturb(sd_event *e) { - sd_id128_t bootid = {}; - - /* When we sleep for longer, we try to realign the wakeup to - the same time wihtin each minute/second/250ms, so that - events all across the system can be coalesced into a single - CPU wakeup. However, let's take some system-specific - randomness for this value, so that in a network of systems - with synced clocks timer events are distributed a - bit. Here, we calculate a perturbation usec offset from the - boot ID. */ - - if (_likely_(e->perturb != USEC_INFINITY)) - return; - - if (sd_id128_get_boot(&bootid) >= 0) - e->perturb = (bootid.qwords[0] ^ bootid.qwords[1]) % USEC_PER_MINUTE; -} - -static int event_setup_timer_fd( - sd_event *e, - struct clock_data *d, - clockid_t clock) { - - struct epoll_event ev = {}; - int r, fd; - - assert(e); - assert(d); - - if (_likely_(d->fd >= 0)) - return 0; - - fd = timerfd_create(clock, TFD_NONBLOCK|TFD_CLOEXEC); - if (fd < 0) - return -errno; - - ev.events = EPOLLIN; - ev.data.ptr = d; - - r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, fd, &ev); - if (r < 0) { - safe_close(fd); - return -errno; - } - - d->fd = fd; - return 0; -} - -static int time_exit_callback(sd_event_source *s, uint64_t usec, void *userdata) { - assert(s); - - return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); -} - -_public_ int sd_event_add_time( - sd_event *e, - sd_event_source **ret, - clockid_t clock, - uint64_t usec, - uint64_t accuracy, - sd_event_time_handler_t callback, - void *userdata) { - - EventSourceType type; - sd_event_source *s; - struct clock_data *d; - int r; - - assert_return(e, -EINVAL); - assert_return(accuracy != (uint64_t) -1, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - 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); - - r = prioq_ensure_allocated(&d->earliest, earliest_time_prioq_compare); - if (r < 0) - return r; - - r = prioq_ensure_allocated(&d->latest, latest_time_prioq_compare); - if (r < 0) - return r; - - if (d->fd < 0) { - r = event_setup_timer_fd(e, d, clock); - if (r < 0) - return r; - } - - s = source_new(e, !ret, type); - if (!s) - return -ENOMEM; - - s->time.next = usec; - s->time.accuracy = accuracy == 0 ? DEFAULT_ACCURACY_USEC : accuracy; - s->time.callback = callback; - s->time.earliest_index = s->time.latest_index = PRIOQ_IDX_NULL; - s->userdata = userdata; - s->enabled = SD_EVENT_ONESHOT; - - d->needs_rearm = true; - - r = prioq_put(d->earliest, s, &s->time.earliest_index); - if (r < 0) - goto fail; - - r = prioq_put(d->latest, s, &s->time.latest_index); - if (r < 0) - goto fail; - - if (ret) - *ret = s; - - return 0; - -fail: - source_free(s); - return r; -} - -static int signal_exit_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - assert(s); - - return sd_event_exit(sd_event_source_get_event(s), PTR_TO_INT(userdata)); -} - -_public_ int sd_event_add_signal( - sd_event *e, - sd_event_source **ret, - int sig, - sd_event_signal_handler_t callback, - void *userdata) { - - sd_event_source *s; - struct signal_data *d; - sigset_t ss; - int r; - - assert_return(e, -EINVAL); - assert_return(sig > 0, -EINVAL); - assert_return(sig < _NSIG, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - if (!callback) - callback = signal_exit_callback; - - r = pthread_sigmask(SIG_SETMASK, NULL, &ss); - if (r != 0) - return -r; - - if (!sigismember(&ss, sig)) - return -EBUSY; - - if (!e->signal_sources) { - e->signal_sources = new0(sd_event_source*, _NSIG); - if (!e->signal_sources) - return -ENOMEM; - } else if (e->signal_sources[sig]) - return -EBUSY; - - s = source_new(e, !ret, SOURCE_SIGNAL); - if (!s) - return -ENOMEM; - - s->signal.sig = sig; - s->signal.callback = callback; - s->userdata = userdata; - s->enabled = SD_EVENT_ON; - - e->signal_sources[sig] = s; - - r = event_make_signal_data(e, sig, &d); - if (r < 0) { - source_free(s); - return r; - } - - /* Use the signal name as description for the event source by default */ - (void) sd_event_source_set_description(s, signal_to_string(sig)); - - if (ret) - *ret = s; - - return 0; -} - -_public_ int sd_event_add_child( - sd_event *e, - sd_event_source **ret, - pid_t pid, - int options, - sd_event_child_handler_t callback, - void *userdata) { - - sd_event_source *s; - int r; - - assert_return(e, -EINVAL); - assert_return(pid > 1, -EINVAL); - assert_return(!(options & ~(WEXITED|WSTOPPED|WCONTINUED)), -EINVAL); - assert_return(options != 0, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - r = hashmap_ensure_allocated(&e->child_sources, NULL); - if (r < 0) - return r; - - if (hashmap_contains(e->child_sources, PID_TO_PTR(pid))) - return -EBUSY; - - s = source_new(e, !ret, SOURCE_CHILD); - if (!s) - return -ENOMEM; - - s->child.pid = pid; - s->child.options = options; - s->child.callback = callback; - s->userdata = userdata; - s->enabled = SD_EVENT_ONESHOT; - - r = hashmap_put(e->child_sources, PID_TO_PTR(pid), s); - if (r < 0) { - source_free(s); - return r; - } - - e->n_enabled_child_sources ++; - - r = event_make_signal_data(e, SIGCHLD, NULL); - if (r < 0) { - e->n_enabled_child_sources--; - source_free(s); - return r; - } - - e->need_process_child = true; - - if (ret) - *ret = s; - - return 0; -} - -_public_ int sd_event_add_defer( - sd_event *e, - sd_event_source **ret, - sd_event_handler_t callback, - void *userdata) { - - sd_event_source *s; - int r; - - assert_return(e, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - s = source_new(e, !ret, SOURCE_DEFER); - if (!s) - return -ENOMEM; - - s->defer.callback = callback; - s->userdata = userdata; - s->enabled = SD_EVENT_ONESHOT; - - r = source_set_pending(s, true); - if (r < 0) { - source_free(s); - return r; - } - - if (ret) - *ret = s; - - return 0; -} - -_public_ int sd_event_add_post( - sd_event *e, - sd_event_source **ret, - sd_event_handler_t callback, - void *userdata) { - - sd_event_source *s; - int r; - - assert_return(e, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - r = set_ensure_allocated(&e->post_sources, NULL); - if (r < 0) - return r; - - s = source_new(e, !ret, SOURCE_POST); - if (!s) - return -ENOMEM; - - s->post.callback = callback; - s->userdata = userdata; - s->enabled = SD_EVENT_ON; - - r = set_put(e->post_sources, s); - if (r < 0) { - source_free(s); - return r; - } - - if (ret) - *ret = s; - - return 0; -} - -_public_ int sd_event_add_exit( - sd_event *e, - sd_event_source **ret, - sd_event_handler_t callback, - void *userdata) { - - sd_event_source *s; - int r; - - assert_return(e, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - r = prioq_ensure_allocated(&e->exit, exit_prioq_compare); - if (r < 0) - return r; - - s = source_new(e, !ret, SOURCE_EXIT); - if (!s) - return -ENOMEM; - - s->exit.callback = callback; - s->userdata = userdata; - s->exit.prioq_index = PRIOQ_IDX_NULL; - s->enabled = SD_EVENT_ONESHOT; - - r = prioq_put(s->event->exit, s, &s->exit.prioq_index); - if (r < 0) { - source_free(s); - return r; - } - - if (ret) - *ret = s; - - return 0; -} - -_public_ sd_event_source* sd_event_source_ref(sd_event_source *s) { - - if (!s) - return NULL; - - assert(s->n_ref >= 1); - s->n_ref++; - - return s; -} - -_public_ sd_event_source* sd_event_source_unref(sd_event_source *s) { - - if (!s) - return NULL; - - assert(s->n_ref >= 1); - s->n_ref--; - - if (s->n_ref <= 0) { - /* Here's a special hack: when we are called from a - * dispatch handler we won't free the event source - * immediately, but we will detach the fd from the - * epoll. This way it is safe for the caller to unref - * the event source and immediately close the fd, but - * we still retain a valid event source object after - * the callback. */ - - if (s->dispatching) { - if (s->type == SOURCE_IO) - source_io_unregister(s); - - source_disconnect(s); - } else - source_free(s); - } - - return NULL; -} - -_public_ int sd_event_source_set_description(sd_event_source *s, const char *description) { - assert_return(s, -EINVAL); - assert_return(!event_pid_changed(s->event), -ECHILD); - - return free_and_strdup(&s->description, description); -} - -_public_ int sd_event_source_get_description(sd_event_source *s, const char **description) { - assert_return(s, -EINVAL); - assert_return(description, -EINVAL); - assert_return(s->description, -ENXIO); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *description = s->description; - return 0; -} - -_public_ sd_event *sd_event_source_get_event(sd_event_source *s) { - assert_return(s, NULL); - - return s->event; -} - -_public_ int sd_event_source_get_pending(sd_event_source *s) { - assert_return(s, -EINVAL); - assert_return(s->type != SOURCE_EXIT, -EDOM); - assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(s->event), -ECHILD); - - return s->pending; -} - -_public_ int sd_event_source_get_io_fd(sd_event_source *s) { - assert_return(s, -EINVAL); - assert_return(s->type == SOURCE_IO, -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - return s->io.fd; -} - -_public_ int sd_event_source_set_io_fd(sd_event_source *s, int fd) { - int r; - - assert_return(s, -EINVAL); - assert_return(fd >= 0, -EBADF); - assert_return(s->type == SOURCE_IO, -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - if (s->io.fd == fd) - return 0; - - if (s->enabled == SD_EVENT_OFF) { - s->io.fd = fd; - s->io.registered = false; - } else { - int saved_fd; - - saved_fd = s->io.fd; - assert(s->io.registered); - - s->io.fd = fd; - s->io.registered = false; - - r = source_io_register(s, s->enabled, s->io.events); - if (r < 0) { - s->io.fd = saved_fd; - s->io.registered = true; - return r; - } - - epoll_ctl(s->event->epoll_fd, EPOLL_CTL_DEL, saved_fd, NULL); - } - - return 0; -} - -_public_ int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events) { - assert_return(s, -EINVAL); - assert_return(events, -EINVAL); - assert_return(s->type == SOURCE_IO, -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *events = s->io.events; - return 0; -} - -_public_ int sd_event_source_set_io_events(sd_event_source *s, uint32_t events) { - int r; - - assert_return(s, -EINVAL); - assert_return(s->type == SOURCE_IO, -EDOM); - assert_return(!(events & ~(EPOLLIN|EPOLLOUT|EPOLLRDHUP|EPOLLPRI|EPOLLERR|EPOLLHUP|EPOLLET)), -EINVAL); - assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(s->event), -ECHILD); - - /* edge-triggered updates are never skipped, so we can reset edges */ - if (s->io.events == events && !(events & EPOLLET)) - return 0; - - if (s->enabled != SD_EVENT_OFF) { - r = source_io_register(s, s->enabled, events); - if (r < 0) - return r; - } - - s->io.events = events; - source_set_pending(s, false); - - return 0; -} - -_public_ int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents) { - assert_return(s, -EINVAL); - assert_return(revents, -EINVAL); - assert_return(s->type == SOURCE_IO, -EDOM); - assert_return(s->pending, -ENODATA); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *revents = s->io.revents; - return 0; -} - -_public_ int sd_event_source_get_signal(sd_event_source *s) { - assert_return(s, -EINVAL); - assert_return(s->type == SOURCE_SIGNAL, -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - return s->signal.sig; -} - -_public_ int sd_event_source_get_priority(sd_event_source *s, int64_t *priority) { - assert_return(s, -EINVAL); - assert_return(!event_pid_changed(s->event), -ECHILD); - - return s->priority; -} - -_public_ int sd_event_source_set_priority(sd_event_source *s, int64_t priority) { - int r; - - assert_return(s, -EINVAL); - assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(s->event), -ECHILD); - - if (s->priority == priority) - return 0; - - if (s->type == SOURCE_SIGNAL && s->enabled != SD_EVENT_OFF) { - struct signal_data *old, *d; - - /* Move us from the signalfd belonging to the old - * priority to the signalfd of the new priority */ - - assert_se(old = hashmap_get(s->event->signal_data, &s->priority)); - - s->priority = priority; - - r = event_make_signal_data(s->event, s->signal.sig, &d); - if (r < 0) { - s->priority = old->priority; - return r; - } - - event_unmask_signal_data(s->event, old, s->signal.sig); - } else - s->priority = priority; - - if (s->pending) - prioq_reshuffle(s->event->pending, s, &s->pending_index); - - if (s->prepare) - prioq_reshuffle(s->event->prepare, s, &s->prepare_index); - - if (s->type == SOURCE_EXIT) - prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); - - return 0; -} - -_public_ int sd_event_source_get_enabled(sd_event_source *s, int *m) { - assert_return(s, -EINVAL); - assert_return(m, -EINVAL); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *m = s->enabled; - return 0; -} - -_public_ int sd_event_source_set_enabled(sd_event_source *s, int m) { - int r; - - assert_return(s, -EINVAL); - assert_return(m == SD_EVENT_OFF || m == SD_EVENT_ON || m == SD_EVENT_ONESHOT, -EINVAL); - assert_return(!event_pid_changed(s->event), -ECHILD); - - /* If we are dead anyway, we are fine with turning off - * sources, but everything else needs to fail. */ - if (s->event->state == SD_EVENT_FINISHED) - return m == SD_EVENT_OFF ? 0 : -ESTALE; - - if (s->enabled == m) - return 0; - - if (m == SD_EVENT_OFF) { - - switch (s->type) { - - case SOURCE_IO: - source_io_unregister(s); - s->enabled = m; - break; - - case SOURCE_TIME_REALTIME: - case SOURCE_TIME_BOOTTIME: - case SOURCE_TIME_MONOTONIC: - case SOURCE_TIME_REALTIME_ALARM: - case SOURCE_TIME_BOOTTIME_ALARM: { - struct clock_data *d; - - s->enabled = m; - d = event_get_clock_data(s->event, s->type); - assert(d); - - prioq_reshuffle(d->earliest, s, &s->time.earliest_index); - prioq_reshuffle(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - break; - } - - case SOURCE_SIGNAL: - s->enabled = m; - - event_gc_signal_data(s->event, &s->priority, s->signal.sig); - break; - - case SOURCE_CHILD: - s->enabled = m; - - assert(s->event->n_enabled_child_sources > 0); - s->event->n_enabled_child_sources--; - - event_gc_signal_data(s->event, &s->priority, SIGCHLD); - break; - - case SOURCE_EXIT: - s->enabled = m; - prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); - break; - - case SOURCE_DEFER: - case SOURCE_POST: - s->enabled = m; - break; - - default: - assert_not_reached("Wut? I shouldn't exist."); - } - - } else { - switch (s->type) { - - case SOURCE_IO: - r = source_io_register(s, m, s->io.events); - if (r < 0) - return r; - - s->enabled = m; - break; - - case SOURCE_TIME_REALTIME: - case SOURCE_TIME_BOOTTIME: - case SOURCE_TIME_MONOTONIC: - case SOURCE_TIME_REALTIME_ALARM: - case SOURCE_TIME_BOOTTIME_ALARM: { - struct clock_data *d; - - s->enabled = m; - d = event_get_clock_data(s->event, s->type); - assert(d); - - prioq_reshuffle(d->earliest, s, &s->time.earliest_index); - prioq_reshuffle(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - break; - } - - case SOURCE_SIGNAL: - - s->enabled = m; - - r = event_make_signal_data(s->event, s->signal.sig, NULL); - if (r < 0) { - s->enabled = SD_EVENT_OFF; - event_gc_signal_data(s->event, &s->priority, s->signal.sig); - return r; - } - - break; - - case SOURCE_CHILD: - - if (s->enabled == SD_EVENT_OFF) - s->event->n_enabled_child_sources++; - - s->enabled = m; - - r = event_make_signal_data(s->event, SIGCHLD, NULL); - if (r < 0) { - s->enabled = SD_EVENT_OFF; - s->event->n_enabled_child_sources--; - event_gc_signal_data(s->event, &s->priority, SIGCHLD); - return r; - } - - break; - - case SOURCE_EXIT: - s->enabled = m; - prioq_reshuffle(s->event->exit, s, &s->exit.prioq_index); - break; - - case SOURCE_DEFER: - case SOURCE_POST: - s->enabled = m; - break; - - default: - assert_not_reached("Wut? I shouldn't exist."); - } - } - - if (s->pending) - prioq_reshuffle(s->event->pending, s, &s->pending_index); - - if (s->prepare) - prioq_reshuffle(s->event->prepare, s, &s->prepare_index); - - return 0; -} - -_public_ int sd_event_source_get_time(sd_event_source *s, uint64_t *usec) { - assert_return(s, -EINVAL); - assert_return(usec, -EINVAL); - assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *usec = s->time.next; - return 0; -} - -_public_ int sd_event_source_set_time(sd_event_source *s, uint64_t usec) { - struct clock_data *d; - - assert_return(s, -EINVAL); - assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); - assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(s->event), -ECHILD); - - s->time.next = usec; - - source_set_pending(s, false); - - d = event_get_clock_data(s->event, s->type); - assert(d); - - prioq_reshuffle(d->earliest, s, &s->time.earliest_index); - prioq_reshuffle(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - - return 0; -} - -_public_ int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec) { - assert_return(s, -EINVAL); - assert_return(usec, -EINVAL); - assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *usec = s->time.accuracy; - return 0; -} - -_public_ int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec) { - struct clock_data *d; - - assert_return(s, -EINVAL); - assert_return(usec != (uint64_t) -1, -EINVAL); - assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); - assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(s->event), -ECHILD); - - if (usec == 0) - usec = DEFAULT_ACCURACY_USEC; - - s->time.accuracy = usec; - - source_set_pending(s, false); - - d = event_get_clock_data(s->event, s->type); - assert(d); - - prioq_reshuffle(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - - return 0; -} - -_public_ int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock) { - assert_return(s, -EINVAL); - assert_return(clock, -EINVAL); - assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *clock = event_source_type_to_clock(s->type); - return 0; -} - -_public_ int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid) { - assert_return(s, -EINVAL); - assert_return(pid, -EINVAL); - assert_return(s->type == SOURCE_CHILD, -EDOM); - assert_return(!event_pid_changed(s->event), -ECHILD); - - *pid = s->child.pid; - return 0; -} - -_public_ int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback) { - int r; - - assert_return(s, -EINVAL); - assert_return(s->type != SOURCE_EXIT, -EDOM); - assert_return(s->event->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(s->event), -ECHILD); - - if (s->prepare == callback) - return 0; - - if (callback && s->prepare) { - s->prepare = callback; - return 0; - } - - r = prioq_ensure_allocated(&s->event->prepare, prepare_prioq_compare); - if (r < 0) - return r; - - s->prepare = callback; - - if (callback) { - r = prioq_put(s->event->prepare, s, &s->prepare_index); - if (r < 0) - return r; - } else - prioq_remove(s->event->prepare, s, &s->prepare_index); - - return 0; -} - -_public_ void* sd_event_source_get_userdata(sd_event_source *s) { - assert_return(s, NULL); - - return s->userdata; -} - -_public_ void *sd_event_source_set_userdata(sd_event_source *s, void *userdata) { - void *ret; - - assert_return(s, NULL); - - ret = s->userdata; - s->userdata = userdata; - - return ret; -} - -static usec_t sleep_between(sd_event *e, usec_t a, usec_t b) { - usec_t c; - assert(e); - assert(a <= b); - - if (a <= 0) - return 0; - if (a >= USEC_INFINITY) - return USEC_INFINITY; - - if (b <= a + 1) - return a; - - initialize_perturb(e); - - /* - Find a good time to wake up again between times a and b. We - have two goals here: - - a) We want to wake up as seldom as possible, hence prefer - later times over earlier times. - - b) But if we have to wake up, then let's make sure to - dispatch as much as possible on the entire system. - - We implement this by waking up everywhere at the same time - within any given minute if we can, synchronised via the - perturbation value determined from the boot ID. If we can't, - then we try to find the same spot in every 10s, then 1s and - then 250ms step. Otherwise, we pick the last possible time - to wake up. - */ - - c = (b / USEC_PER_MINUTE) * USEC_PER_MINUTE + e->perturb; - if (c >= b) { - if (_unlikely_(c < USEC_PER_MINUTE)) - return b; - - c -= USEC_PER_MINUTE; - } - - if (c >= a) - return c; - - c = (b / (USEC_PER_SEC*10)) * (USEC_PER_SEC*10) + (e->perturb % (USEC_PER_SEC*10)); - if (c >= b) { - if (_unlikely_(c < USEC_PER_SEC*10)) - return b; - - c -= USEC_PER_SEC*10; - } - - if (c >= a) - return c; - - c = (b / USEC_PER_SEC) * USEC_PER_SEC + (e->perturb % USEC_PER_SEC); - if (c >= b) { - if (_unlikely_(c < USEC_PER_SEC)) - return b; - - c -= USEC_PER_SEC; - } - - if (c >= a) - return c; - - c = (b / (USEC_PER_MSEC*250)) * (USEC_PER_MSEC*250) + (e->perturb % (USEC_PER_MSEC*250)); - if (c >= b) { - if (_unlikely_(c < USEC_PER_MSEC*250)) - return b; - - c -= USEC_PER_MSEC*250; - } - - if (c >= a) - return c; - - return b; -} - -static int event_arm_timer( - sd_event *e, - struct clock_data *d) { - - struct itimerspec its = {}; - sd_event_source *a, *b; - usec_t t; - int r; - - assert(e); - assert(d); - - if (!d->needs_rearm) - return 0; - else - d->needs_rearm = false; - - a = prioq_peek(d->earliest); - if (!a || a->enabled == SD_EVENT_OFF || a->time.next == USEC_INFINITY) { - - if (d->fd < 0) - return 0; - - if (d->next == USEC_INFINITY) - return 0; - - /* disarm */ - r = timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL); - if (r < 0) - return r; - - d->next = USEC_INFINITY; - return 0; - } - - b = prioq_peek(d->latest); - assert_se(b && b->enabled != SD_EVENT_OFF); - - t = sleep_between(e, a->time.next, time_event_source_latest(b)); - if (d->next == t) - return 0; - - assert_se(d->fd >= 0); - - if (t == 0) { - /* We don' want to disarm here, just mean some time looooong ago. */ - its.it_value.tv_sec = 0; - its.it_value.tv_nsec = 1; - } else - timespec_store(&its.it_value, t); - - r = timerfd_settime(d->fd, TFD_TIMER_ABSTIME, &its, NULL); - if (r < 0) - return -errno; - - d->next = t; - return 0; -} - -static int process_io(sd_event *e, sd_event_source *s, uint32_t revents) { - assert(e); - assert(s); - assert(s->type == SOURCE_IO); - - /* If the event source was already pending, we just OR in the - * new revents, otherwise we reset the value. The ORing is - * necessary to handle EPOLLONESHOT events properly where - * readability might happen independently of writability, and - * we need to keep track of both */ - - if (s->pending) - s->io.revents |= revents; - else - s->io.revents = revents; - - return source_set_pending(s, true); -} - -static int flush_timer(sd_event *e, int fd, uint32_t events, usec_t *next) { - uint64_t x; - ssize_t ss; - - assert(e); - assert(fd >= 0); - - assert_return(events == EPOLLIN, -EIO); - - ss = read(fd, &x, sizeof(x)); - if (ss < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - if (_unlikely_(ss != sizeof(x))) - return -EIO; - - if (next) - *next = USEC_INFINITY; - - return 0; -} - -static int process_timer( - sd_event *e, - usec_t n, - struct clock_data *d) { - - sd_event_source *s; - int r; - - assert(e); - assert(d); - - for (;;) { - s = prioq_peek(d->earliest); - if (!s || - s->time.next > n || - s->enabled == SD_EVENT_OFF || - s->pending) - break; - - r = source_set_pending(s, true); - if (r < 0) - return r; - - prioq_reshuffle(d->earliest, s, &s->time.earliest_index); - prioq_reshuffle(d->latest, s, &s->time.latest_index); - d->needs_rearm = true; - } - - return 0; -} - -static int process_child(sd_event *e) { - sd_event_source *s; - Iterator i; - int r; - - assert(e); - - e->need_process_child = false; - - /* - So, this is ugly. We iteratively invoke waitid() with P_PID - + WNOHANG for each PID we wait for, instead of using - P_ALL. This is because we only want to get child - information of very specific child processes, and not all - of them. We might not have processed the SIGCHLD even of a - previous invocation and we don't want to maintain a - unbounded *per-child* event queue, hence we really don't - want anything flushed out of the kernel's queue that we - don't care about. Since this is O(n) this means that if you - have a lot of processes you probably want to handle SIGCHLD - yourself. - - We do not reap the children here (by using WNOWAIT), this - is only done after the event source is dispatched so that - the callback still sees the process as a zombie. - */ - - HASHMAP_FOREACH(s, e->child_sources, i) { - assert(s->type == SOURCE_CHILD); - - if (s->pending) - continue; - - if (s->enabled == SD_EVENT_OFF) - continue; - - zero(s->child.siginfo); - r = waitid(P_PID, s->child.pid, &s->child.siginfo, - WNOHANG | (s->child.options & WEXITED ? WNOWAIT : 0) | s->child.options); - if (r < 0) - return -errno; - - if (s->child.siginfo.si_pid != 0) { - bool zombie = - s->child.siginfo.si_code == CLD_EXITED || - s->child.siginfo.si_code == CLD_KILLED || - s->child.siginfo.si_code == CLD_DUMPED; - - if (!zombie && (s->child.options & WEXITED)) { - /* If the child isn't dead then let's - * immediately remove the state change - * from the queue, since there's no - * benefit in leaving it queued */ - - assert(s->child.options & (WSTOPPED|WCONTINUED)); - waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|(s->child.options & (WSTOPPED|WCONTINUED))); - } - - r = source_set_pending(s, true); - if (r < 0) - return r; - } - } - - return 0; -} - -static int process_signal(sd_event *e, struct signal_data *d, uint32_t events) { - bool read_one = false; - int r; - - assert(e); - assert_return(events == EPOLLIN, -EIO); - - /* If there's a signal queued on this priority and SIGCHLD is - on this priority too, then make sure to recheck the - children we watch. This is because we only ever dequeue - the first signal per priority, and if we dequeue one, and - SIGCHLD might be enqueued later we wouldn't know, but we - might have higher priority children we care about hence we - need to check that explicitly. */ - - if (sigismember(&d->sigset, SIGCHLD)) - e->need_process_child = true; - - /* If there's already an event source pending for this - * priority we don't read another */ - if (d->current) - return 0; - - for (;;) { - struct signalfd_siginfo si; - ssize_t n; - sd_event_source *s = NULL; - - n = read(d->fd, &si, sizeof(si)); - if (n < 0) { - if (errno == EAGAIN || errno == EINTR) - return read_one; - - return -errno; - } - - if (_unlikely_(n != sizeof(si))) - return -EIO; - - assert(si.ssi_signo < _NSIG); - - read_one = true; - - if (e->signal_sources) - s = e->signal_sources[si.ssi_signo]; - if (!s) - continue; - if (s->pending) - continue; - - s->signal.siginfo = si; - d->current = s; - - r = source_set_pending(s, true); - if (r < 0) - return r; - - return 1; - } -} - -static int source_dispatch(sd_event_source *s) { - int r = 0; - - assert(s); - assert(s->pending || s->type == SOURCE_EXIT); - - if (s->type != SOURCE_DEFER && s->type != SOURCE_EXIT) { - r = source_set_pending(s, false); - if (r < 0) - return r; - } - - if (s->type != SOURCE_POST) { - sd_event_source *z; - Iterator i; - - /* If we execute a non-post source, let's mark all - * post sources as pending */ - - SET_FOREACH(z, s->event->post_sources, i) { - if (z->enabled == SD_EVENT_OFF) - continue; - - r = source_set_pending(z, true); - if (r < 0) - return r; - } - } - - if (s->enabled == SD_EVENT_ONESHOT) { - r = sd_event_source_set_enabled(s, SD_EVENT_OFF); - if (r < 0) - return r; - } - - s->dispatching = true; - - switch (s->type) { - - case SOURCE_IO: - r = s->io.callback(s, s->io.fd, s->io.revents, s->userdata); - break; - - case SOURCE_TIME_REALTIME: - case SOURCE_TIME_BOOTTIME: - case SOURCE_TIME_MONOTONIC: - case SOURCE_TIME_REALTIME_ALARM: - case SOURCE_TIME_BOOTTIME_ALARM: - r = s->time.callback(s, s->time.next, s->userdata); - break; - - case SOURCE_SIGNAL: - r = s->signal.callback(s, &s->signal.siginfo, s->userdata); - break; - - case SOURCE_CHILD: { - bool zombie; - - zombie = s->child.siginfo.si_code == CLD_EXITED || - s->child.siginfo.si_code == CLD_KILLED || - s->child.siginfo.si_code == CLD_DUMPED; - - r = s->child.callback(s, &s->child.siginfo, s->userdata); - - /* Now, reap the PID for good. */ - if (zombie) - waitid(P_PID, s->child.pid, &s->child.siginfo, WNOHANG|WEXITED); - - break; - } - - case SOURCE_DEFER: - r = s->defer.callback(s, s->userdata); - break; - - case SOURCE_POST: - r = s->post.callback(s, s->userdata); - break; - - case SOURCE_EXIT: - r = s->exit.callback(s, s->userdata); - break; - - case SOURCE_WATCHDOG: - case _SOURCE_EVENT_SOURCE_TYPE_MAX: - case _SOURCE_EVENT_SOURCE_TYPE_INVALID: - assert_not_reached("Wut? I shouldn't exist."); - } - - s->dispatching = false; - - if (r < 0) - log_debug_errno(r, "Event source %s (type %s) returned error, disabling: %m", - strna(s->description), event_source_type_to_string(s->type)); - - if (s->n_ref == 0) - source_free(s); - else if (r < 0) - sd_event_source_set_enabled(s, SD_EVENT_OFF); - - return 1; -} - -static int event_prepare(sd_event *e) { - int r; - - assert(e); - - for (;;) { - sd_event_source *s; - - s = prioq_peek(e->prepare); - if (!s || s->prepare_iteration == e->iteration || s->enabled == SD_EVENT_OFF) - break; - - s->prepare_iteration = e->iteration; - r = prioq_reshuffle(e->prepare, s, &s->prepare_index); - if (r < 0) - return r; - - assert(s->prepare); - - s->dispatching = true; - r = s->prepare(s, s->userdata); - s->dispatching = false; - - if (r < 0) - log_debug_errno(r, "Prepare callback of event source %s (type %s) returned error, disabling: %m", - strna(s->description), event_source_type_to_string(s->type)); - - if (s->n_ref == 0) - source_free(s); - else if (r < 0) - sd_event_source_set_enabled(s, SD_EVENT_OFF); - } - - return 0; -} - -static int dispatch_exit(sd_event *e) { - sd_event_source *p; - int r; - - assert(e); - - p = prioq_peek(e->exit); - if (!p || p->enabled == SD_EVENT_OFF) { - e->state = SD_EVENT_FINISHED; - return 0; - } - - sd_event_ref(e); - e->iteration++; - e->state = SD_EVENT_EXITING; - - r = source_dispatch(p); - - e->state = SD_EVENT_INITIAL; - sd_event_unref(e); - - return r; -} - -static sd_event_source* event_next_pending(sd_event *e) { - sd_event_source *p; - - assert(e); - - p = prioq_peek(e->pending); - if (!p) - return NULL; - - if (p->enabled == SD_EVENT_OFF) - return NULL; - - return p; -} - -static int arm_watchdog(sd_event *e) { - struct itimerspec its = {}; - usec_t t; - int r; - - assert(e); - assert(e->watchdog_fd >= 0); - - t = sleep_between(e, - e->watchdog_last + (e->watchdog_period / 2), - e->watchdog_last + (e->watchdog_period * 3 / 4)); - - timespec_store(&its.it_value, t); - - /* Make sure we never set the watchdog to 0, which tells the - * kernel to disable it. */ - if (its.it_value.tv_sec == 0 && its.it_value.tv_nsec == 0) - its.it_value.tv_nsec = 1; - - r = timerfd_settime(e->watchdog_fd, TFD_TIMER_ABSTIME, &its, NULL); - if (r < 0) - return -errno; - - return 0; -} - -static int process_watchdog(sd_event *e) { - assert(e); - - if (!e->watchdog) - return 0; - - /* Don't notify watchdog too often */ - if (e->watchdog_last + e->watchdog_period / 4 > e->timestamp.monotonic) - return 0; - - sd_notify(false, "WATCHDOG=1"); - e->watchdog_last = e->timestamp.monotonic; - - return arm_watchdog(e); -} - -_public_ int sd_event_prepare(sd_event *e) { - int r; - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); - - if (e->exit_requested) - goto pending; - - e->iteration++; - - e->state = SD_EVENT_PREPARING; - r = event_prepare(e); - e->state = SD_EVENT_INITIAL; - if (r < 0) - return r; - - r = event_arm_timer(e, &e->realtime); - if (r < 0) - return r; - - r = event_arm_timer(e, &e->boottime); - if (r < 0) - return r; - - r = event_arm_timer(e, &e->monotonic); - if (r < 0) - return r; - - r = event_arm_timer(e, &e->realtime_alarm); - if (r < 0) - return r; - - r = event_arm_timer(e, &e->boottime_alarm); - if (r < 0) - return r; - - if (event_next_pending(e) || e->need_process_child) - goto pending; - - e->state = SD_EVENT_ARMED; - - return 0; - -pending: - e->state = SD_EVENT_ARMED; - r = sd_event_wait(e, 0); - if (r == 0) - e->state = SD_EVENT_ARMED; - - return r; -} - -_public_ int sd_event_wait(sd_event *e, uint64_t timeout) { - struct epoll_event *ev_queue; - unsigned ev_queue_max; - int r, m, i; - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(e->state == SD_EVENT_ARMED, -EBUSY); - - if (e->exit_requested) { - e->state = SD_EVENT_PENDING; - return 1; - } - - ev_queue_max = MAX(e->n_sources, 1u); - ev_queue = newa(struct epoll_event, ev_queue_max); - - m = epoll_wait(e->epoll_fd, ev_queue, ev_queue_max, - timeout == (uint64_t) -1 ? -1 : (int) ((timeout + USEC_PER_MSEC - 1) / USEC_PER_MSEC)); - if (m < 0) { - if (errno == EINTR) { - e->state = SD_EVENT_PENDING; - return 1; - } - - r = -errno; - goto finish; - } - - dual_timestamp_get(&e->timestamp); - e->timestamp_boottime = now(CLOCK_BOOTTIME); - - for (i = 0; i < m; i++) { - - if (ev_queue[i].data.ptr == INT_TO_PTR(SOURCE_WATCHDOG)) - r = flush_timer(e, e->watchdog_fd, ev_queue[i].events, NULL); - else { - WakeupType *t = ev_queue[i].data.ptr; - - switch (*t) { - - case WAKEUP_EVENT_SOURCE: - r = process_io(e, ev_queue[i].data.ptr, ev_queue[i].events); - break; - - case WAKEUP_CLOCK_DATA: { - struct clock_data *d = ev_queue[i].data.ptr; - r = flush_timer(e, d->fd, ev_queue[i].events, &d->next); - break; - } - - case WAKEUP_SIGNAL_DATA: - r = process_signal(e, ev_queue[i].data.ptr, ev_queue[i].events); - break; - - default: - assert_not_reached("Invalid wake-up pointer"); - } - } - if (r < 0) - goto finish; - } - - r = process_watchdog(e); - if (r < 0) - goto finish; - - r = process_timer(e, e->timestamp.realtime, &e->realtime); - if (r < 0) - goto finish; - - r = process_timer(e, e->timestamp_boottime, &e->boottime); - if (r < 0) - goto finish; - - r = process_timer(e, e->timestamp.monotonic, &e->monotonic); - if (r < 0) - goto finish; - - r = process_timer(e, e->timestamp.realtime, &e->realtime_alarm); - if (r < 0) - goto finish; - - r = process_timer(e, e->timestamp_boottime, &e->boottime_alarm); - if (r < 0) - goto finish; - - if (e->need_process_child) { - r = process_child(e); - if (r < 0) - goto finish; - } - - if (event_next_pending(e)) { - e->state = SD_EVENT_PENDING; - - return 1; - } - - r = 0; - -finish: - e->state = SD_EVENT_INITIAL; - - return r; -} - -_public_ int sd_event_dispatch(sd_event *e) { - sd_event_source *p; - int r; - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(e->state == SD_EVENT_PENDING, -EBUSY); - - if (e->exit_requested) - return dispatch_exit(e); - - p = event_next_pending(e); - if (p) { - sd_event_ref(e); - - e->state = SD_EVENT_RUNNING; - r = source_dispatch(p); - e->state = SD_EVENT_INITIAL; - - sd_event_unref(e); - - return r; - } - - e->state = SD_EVENT_INITIAL; - - return 1; -} - -static void event_log_delays(sd_event *e) { - char b[ELEMENTSOF(e->delays) * DECIMAL_STR_MAX(unsigned) + 1]; - unsigned i; - int o; - - for (i = o = 0; i < ELEMENTSOF(e->delays); i++) { - o += snprintf(&b[o], sizeof(b) - o, "%u ", e->delays[i]); - e->delays[i] = 0; - } - log_debug("Event loop iterations: %.*s", o, b); -} - -_public_ int sd_event_run(sd_event *e, uint64_t timeout) { - int r; - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); - - if (e->profile_delays && e->last_run) { - usec_t this_run; - unsigned l; - - this_run = now(CLOCK_MONOTONIC); - - l = u64log2(this_run - e->last_run); - assert(l < sizeof(e->delays)); - e->delays[l]++; - - if (this_run - e->last_log >= 5*USEC_PER_SEC) { - event_log_delays(e); - e->last_log = this_run; - } - } - - r = sd_event_prepare(e); - if (r == 0) - /* There was nothing? Then wait... */ - r = sd_event_wait(e, timeout); - - if (e->profile_delays) - e->last_run = now(CLOCK_MONOTONIC); - - if (r > 0) { - /* There's something now, then let's dispatch it */ - r = sd_event_dispatch(e); - if (r < 0) - return r; - - return 1; - } - - return r; -} - -_public_ int sd_event_loop(sd_event *e) { - int r; - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - assert_return(e->state == SD_EVENT_INITIAL, -EBUSY); - - sd_event_ref(e); - - while (e->state != SD_EVENT_FINISHED) { - r = sd_event_run(e, (uint64_t) -1); - if (r < 0) - goto finish; - } - - r = e->exit_code; - -finish: - sd_event_unref(e); - return r; -} - -_public_ int sd_event_get_fd(sd_event *e) { - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - - return e->epoll_fd; -} - -_public_ int sd_event_get_state(sd_event *e) { - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - - return e->state; -} - -_public_ int sd_event_get_exit_code(sd_event *e, int *code) { - assert_return(e, -EINVAL); - assert_return(code, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - - if (!e->exit_requested) - return -ENODATA; - - *code = e->exit_code; - return 0; -} - -_public_ int sd_event_exit(sd_event *e, int code) { - assert_return(e, -EINVAL); - assert_return(e->state != SD_EVENT_FINISHED, -ESTALE); - assert_return(!event_pid_changed(e), -ECHILD); - - e->exit_requested = true; - e->exit_code = code; - - return 0; -} - -_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 (!dual_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; - - default: - *usec = e->timestamp_boottime; - break; - } - - return 0; -} - -_public_ int sd_event_default(sd_event **ret) { - - static thread_local sd_event *default_event = NULL; - sd_event *e = NULL; - int r; - - if (!ret) - return !!default_event; - - if (default_event) { - *ret = sd_event_ref(default_event); - return 0; - } - - r = sd_event_new(&e); - if (r < 0) - return r; - - e->default_event_ptr = &default_event; - e->tid = gettid(); - default_event = e; - - *ret = e; - return 1; -} - -_public_ int sd_event_get_tid(sd_event *e, pid_t *tid) { - assert_return(e, -EINVAL); - assert_return(tid, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - - if (e->tid != 0) { - *tid = e->tid; - return 0; - } - - return -ENXIO; -} - -_public_ int sd_event_set_watchdog(sd_event *e, int b) { - int r; - - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - - if (e->watchdog == !!b) - return e->watchdog; - - if (b) { - struct epoll_event ev = {}; - - r = sd_watchdog_enabled(false, &e->watchdog_period); - if (r <= 0) - return r; - - /* Issue first ping immediately */ - sd_notify(false, "WATCHDOG=1"); - e->watchdog_last = now(CLOCK_MONOTONIC); - - e->watchdog_fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK|TFD_CLOEXEC); - if (e->watchdog_fd < 0) - return -errno; - - r = arm_watchdog(e); - if (r < 0) - goto fail; - - ev.events = EPOLLIN; - ev.data.ptr = INT_TO_PTR(SOURCE_WATCHDOG); - - r = epoll_ctl(e->epoll_fd, EPOLL_CTL_ADD, e->watchdog_fd, &ev); - if (r < 0) { - r = -errno; - goto fail; - } - - } else { - if (e->watchdog_fd >= 0) { - epoll_ctl(e->epoll_fd, EPOLL_CTL_DEL, e->watchdog_fd, NULL); - e->watchdog_fd = safe_close(e->watchdog_fd); - } - } - - e->watchdog = !!b; - return e->watchdog; - -fail: - e->watchdog_fd = safe_close(e->watchdog_fd); - return r; -} - -_public_ int sd_event_get_watchdog(sd_event *e) { - assert_return(e, -EINVAL); - assert_return(!event_pid_changed(e), -ECHILD); - - return e->watchdog; -} diff --git a/src/libsystemd/sd-event/test-event.c b/src/libsystemd/sd-event/test-event.c deleted file mode 100644 index daea4126f8..0000000000 --- a/src/libsystemd/sd-event/test-event.c +++ /dev/null @@ -1,357 +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 . -***/ - -#include "sd-event.h" - -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "signal-util.h" -#include "util.h" - -static int prepare_handler(sd_event_source *s, void *userdata) { - log_info("preparing %c", PTR_TO_INT(userdata)); - return 1; -} - -static bool got_a, got_b, got_c, got_unref; -static unsigned got_d; - -static int unref_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_event_source_unref(s); - got_unref = true; - return 0; -} - -static int io_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - - log_info("got IO on %c", PTR_TO_INT(userdata)); - - if (userdata == INT_TO_PTR('a')) { - assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0); - assert_se(!got_a); - got_a = true; - } else if (userdata == INT_TO_PTR('b')) { - assert_se(!got_b); - got_b = true; - } else if (userdata == INT_TO_PTR('d')) { - got_d++; - if (got_d < 2) - assert_se(sd_event_source_set_enabled(s, SD_EVENT_ONESHOT) >= 0); - else - assert_se(sd_event_source_set_enabled(s, SD_EVENT_OFF) >= 0); - } else - assert_not_reached("Yuck!"); - - return 1; -} - -static int child_handler(sd_event_source *s, const siginfo_t *si, void *userdata) { - - assert_se(s); - assert_se(si); - - log_info("got child on %c", PTR_TO_INT(userdata)); - - assert_se(userdata == INT_TO_PTR('f')); - - assert_se(sd_event_exit(sd_event_source_get_event(s), 0) >= 0); - sd_event_source_unref(s); - - return 1; -} - -static int signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - sd_event_source *p = NULL; - pid_t pid; - - assert_se(s); - assert_se(si); - - log_info("got signal on %c", PTR_TO_INT(userdata)); - - assert_se(userdata == INT_TO_PTR('e')); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); - - pid = fork(); - assert_se(pid >= 0); - - if (pid == 0) - _exit(0); - - assert_se(sd_event_add_child(sd_event_source_get_event(s), &p, pid, WEXITED, child_handler, INT_TO_PTR('f')) >= 0); - assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); - - sd_event_source_unref(s); - - return 1; -} - -static int defer_handler(sd_event_source *s, void *userdata) { - sd_event_source *p = NULL; - - assert_se(s); - - log_info("got defer on %c", PTR_TO_INT(userdata)); - - assert_se(userdata == INT_TO_PTR('d')); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGUSR1, -1) >= 0); - - assert_se(sd_event_add_signal(sd_event_source_get_event(s), &p, SIGUSR1, signal_handler, INT_TO_PTR('e')) >= 0); - assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); - raise(SIGUSR1); - - sd_event_source_unref(s); - - return 1; -} - -static bool do_quit = false; - -static int time_handler(sd_event_source *s, uint64_t usec, void *userdata) { - log_info("got timer on %c", PTR_TO_INT(userdata)); - - if (userdata == INT_TO_PTR('c')) { - - if (do_quit) { - sd_event_source *p; - - assert_se(sd_event_add_defer(sd_event_source_get_event(s), &p, defer_handler, INT_TO_PTR('d')) >= 0); - assert_se(sd_event_source_set_enabled(p, SD_EVENT_ONESHOT) >= 0); - } else { - assert_se(!got_c); - got_c = true; - } - } else - assert_not_reached("Huh?"); - - return 2; -} - -static bool got_exit = false; - -static int exit_handler(sd_event_source *s, void *userdata) { - log_info("got quit handler on %c", PTR_TO_INT(userdata)); - - got_exit = true; - - return 3; -} - -static bool got_post = false; - -static int post_handler(sd_event_source *s, void *userdata) { - log_info("got post handler"); - - got_post = true; - - return 2; -} - -static void test_basic(void) { - sd_event *e = NULL; - sd_event_source *w = NULL, *x = NULL, *y = NULL, *z = NULL, *q = NULL, *t = NULL; - static const char ch = 'x'; - int a[2] = { -1, -1 }, b[2] = { -1, -1}, d[2] = { -1, -1}, k[2] = { -1, -1 }; - uint64_t event_now; - - assert_se(pipe(a) >= 0); - assert_se(pipe(b) >= 0); - assert_se(pipe(d) >= 0); - assert_se(pipe(k) >= 0); - - assert_se(sd_event_default(&e) >= 0); - assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) > 0); - - assert_se(sd_event_set_watchdog(e, true) >= 0); - - /* Test whether we cleanly can destroy an io event source from its own handler */ - got_unref = false; - assert_se(sd_event_add_io(e, &t, k[0], EPOLLIN, unref_handler, NULL) >= 0); - assert_se(write(k[1], &ch, 1) == 1); - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(got_unref); - - got_a = false, got_b = false, got_c = false, got_d = 0; - - /* Add a oneshot handler, trigger it, re-enable it, and trigger - * it again. */ - assert_se(sd_event_add_io(e, &w, d[0], EPOLLIN, io_handler, INT_TO_PTR('d')) >= 0); - assert_se(sd_event_source_set_enabled(w, SD_EVENT_ONESHOT) >= 0); - assert_se(write(d[1], &ch, 1) >= 0); - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(got_d == 1); - assert_se(write(d[1], &ch, 1) >= 0); - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(got_d == 2); - - assert_se(sd_event_add_io(e, &x, a[0], EPOLLIN, io_handler, INT_TO_PTR('a')) >= 0); - assert_se(sd_event_add_io(e, &y, b[0], EPOLLIN, io_handler, INT_TO_PTR('b')) >= 0); - assert_se(sd_event_add_time(e, &z, CLOCK_MONOTONIC, 0, 0, time_handler, INT_TO_PTR('c')) >= 0); - assert_se(sd_event_add_exit(e, &q, exit_handler, INT_TO_PTR('g')) >= 0); - - assert_se(sd_event_source_set_priority(x, 99) >= 0); - assert_se(sd_event_source_set_enabled(y, SD_EVENT_ONESHOT) >= 0); - assert_se(sd_event_source_set_prepare(x, prepare_handler) >= 0); - assert_se(sd_event_source_set_priority(z, 50) >= 0); - assert_se(sd_event_source_set_enabled(z, SD_EVENT_ONESHOT) >= 0); - assert_se(sd_event_source_set_prepare(z, prepare_handler) >= 0); - - /* Test for floating event sources */ - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+1, -1) >= 0); - assert_se(sd_event_add_signal(e, NULL, SIGRTMIN+1, NULL, NULL) >= 0); - - assert_se(write(a[1], &ch, 1) >= 0); - assert_se(write(b[1], &ch, 1) >= 0); - - assert_se(!got_a && !got_b && !got_c); - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - - assert_se(!got_a && got_b && !got_c); - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - - assert_se(!got_a && got_b && got_c); - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - - assert_se(got_a && got_b && got_c); - - sd_event_source_unref(x); - sd_event_source_unref(y); - - do_quit = true; - assert_se(sd_event_add_post(e, NULL, post_handler, NULL) >= 0); - assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) == 0); - assert_se(sd_event_source_set_time(z, event_now + 200 * USEC_PER_MSEC) >= 0); - assert_se(sd_event_source_set_enabled(z, SD_EVENT_ONESHOT) >= 0); - - assert_se(sd_event_loop(e) >= 0); - assert_se(got_post); - assert_se(got_exit); - - sd_event_source_unref(z); - sd_event_source_unref(q); - - sd_event_source_unref(w); - - sd_event_unref(e); - - safe_close_pair(a); - safe_close_pair(b); - safe_close_pair(d); - safe_close_pair(k); -} - -static void test_sd_event_now(void) { - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - uint64_t event_now; - - assert_se(sd_event_new(&e) >= 0); - assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) > 0); - assert_se(sd_event_now(e, CLOCK_REALTIME, &event_now) > 0); - assert_se(sd_event_now(e, CLOCK_REALTIME_ALARM, &event_now) > 0); - assert_se(sd_event_now(e, CLOCK_BOOTTIME, &event_now) > 0); - assert_se(sd_event_now(e, CLOCK_BOOTTIME_ALARM, &event_now) > 0); - assert_se(sd_event_now(e, -1, &event_now) == -EOPNOTSUPP); - assert_se(sd_event_now(e, 900 /* arbitrary big number */, &event_now) == -EOPNOTSUPP); - - assert_se(sd_event_run(e, 0) == 0); - - assert_se(sd_event_now(e, CLOCK_MONOTONIC, &event_now) == 0); - assert_se(sd_event_now(e, CLOCK_REALTIME, &event_now) == 0); - assert_se(sd_event_now(e, CLOCK_REALTIME_ALARM, &event_now) == 0); - assert_se(sd_event_now(e, CLOCK_BOOTTIME, &event_now) == 0); - assert_se(sd_event_now(e, CLOCK_BOOTTIME_ALARM, &event_now) == 0); - assert_se(sd_event_now(e, -1, &event_now) == -EOPNOTSUPP); - assert_se(sd_event_now(e, 900 /* arbitrary big number */, &event_now) == -EOPNOTSUPP); -} - -static int last_rtqueue_sigval = 0; -static int n_rtqueue = 0; - -static int rtqueue_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - last_rtqueue_sigval = si->ssi_int; - n_rtqueue ++; - return 0; -} - -static void test_rtqueue(void) { - sd_event_source *u = NULL, *v = NULL, *s = NULL; - sd_event *e = NULL; - - assert_se(sd_event_default(&e) >= 0); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGRTMIN+2, SIGRTMIN+3, SIGUSR2, -1) >= 0); - assert_se(sd_event_add_signal(e, &u, SIGRTMIN+2, rtqueue_handler, NULL) >= 0); - assert_se(sd_event_add_signal(e, &v, SIGRTMIN+3, rtqueue_handler, NULL) >= 0); - assert_se(sd_event_add_signal(e, &s, SIGUSR2, rtqueue_handler, NULL) >= 0); - - assert_se(sd_event_source_set_priority(v, -10) >= 0); - - assert(sigqueue(getpid(), SIGRTMIN+2, (union sigval) { .sival_int = 1 }) >= 0); - assert(sigqueue(getpid(), SIGRTMIN+3, (union sigval) { .sival_int = 2 }) >= 0); - assert(sigqueue(getpid(), SIGUSR2, (union sigval) { .sival_int = 3 }) >= 0); - assert(sigqueue(getpid(), SIGRTMIN+3, (union sigval) { .sival_int = 4 }) >= 0); - assert(sigqueue(getpid(), SIGUSR2, (union sigval) { .sival_int = 5 }) >= 0); - - assert_se(n_rtqueue == 0); - assert_se(last_rtqueue_sigval == 0); - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(n_rtqueue == 1); - assert_se(last_rtqueue_sigval == 2); /* first SIGRTMIN+3 */ - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(n_rtqueue == 2); - assert_se(last_rtqueue_sigval == 4); /* second SIGRTMIN+3 */ - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(n_rtqueue == 3); - assert_se(last_rtqueue_sigval == 3); /* first SIGUSR2 */ - - assert_se(sd_event_run(e, (uint64_t) -1) >= 1); - assert_se(n_rtqueue == 4); - assert_se(last_rtqueue_sigval == 1); /* SIGRTMIN+2 */ - - assert_se(sd_event_run(e, 0) == 0); /* the other SIGUSR2 is dropped, because the first one was still queued */ - assert_se(n_rtqueue == 4); - assert_se(last_rtqueue_sigval == 1); - - sd_event_source_unref(u); - sd_event_source_unref(v); - sd_event_source_unref(s); - - sd_event_unref(e); -} - -int main(int argc, char *argv[]) { - - log_set_max_level(LOG_DEBUG); - log_parse_environment(); - - test_basic(); - test_sd_event_now(); - test_rtqueue(); - - return 0; -} diff --git a/src/libsystemd/sd-hwdb/Makefile b/src/libsystemd/sd-hwdb/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-hwdb/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-hwdb/hwdb-internal.h b/src/libsystemd/sd-hwdb/hwdb-internal.h deleted file mode 100644 index 13fddfc8ad..0000000000 --- a/src/libsystemd/sd-hwdb/hwdb-internal.h +++ /dev/null @@ -1,71 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ -#pragma once - -#include "sparse-endian.h" -#include "util.h" - -#define HWDB_SIG { 'K', 'S', 'L', 'P', 'H', 'H', 'R', 'H' } - -/* on-disk trie objects */ -struct trie_header_f { - uint8_t signature[8]; - - /* version of tool which created the file */ - le64_t tool_version; - le64_t file_size; - - /* size of structures to allow them to grow */ - le64_t header_size; - le64_t node_size; - le64_t child_entry_size; - le64_t value_entry_size; - - /* offset of the root trie node */ - le64_t nodes_root_off; - - /* size of the nodes and string section */ - le64_t nodes_len; - le64_t strings_len; -} _packed_; - -struct trie_node_f { - /* prefix of lookup string, shared by all children */ - le64_t prefix_off; - /* size of children entry array appended to the node */ - uint8_t children_count; - uint8_t padding[7]; - /* size of value entry array appended to the node */ - le64_t values_count; -} _packed_; - -/* array of child entries, follows directly the node record */ -struct trie_child_entry_f { - /* index of the child node */ - uint8_t c; - uint8_t padding[7]; - /* offset of the child node */ - le64_t child_off; -} _packed_; - -/* array of value entries, follows directly the node record/child array */ -struct trie_value_entry_f { - le64_t key_off; - le64_t value_off; -} _packed_; diff --git a/src/libsystemd/sd-hwdb/hwdb-util.h b/src/libsystemd/sd-hwdb/hwdb-util.h deleted file mode 100644 index 5e21e5008b..0000000000 --- a/src/libsystemd/sd-hwdb/hwdb-util.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include "sd-hwdb.h" - -#include "util.h" - -bool hwdb_validate(sd_hwdb *hwdb); diff --git a/src/libsystemd/sd-hwdb/sd-hwdb.c b/src/libsystemd/sd-hwdb/sd-hwdb.c deleted file mode 100644 index 062fa97b17..0000000000 --- a/src/libsystemd/sd-hwdb/sd-hwdb.c +++ /dev/null @@ -1,470 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Kay Sievers - Copyright 2008 Alan Jenkins - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "sd-hwdb.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "hashmap.h" -#include "hwdb-internal.h" -#include "hwdb-util.h" -#include "refcnt.h" -#include "string-util.h" - -struct sd_hwdb { - RefCount n_ref; - int refcount; - - FILE *f; - struct stat st; - union { - struct trie_header_f *head; - const char *map; - }; - - char *modalias; - - OrderedHashmap *properties; - Iterator properties_iterator; - bool properties_modified; -}; - -struct linebuf { - char bytes[LINE_MAX]; - size_t size; - size_t len; -}; - -static void linebuf_init(struct linebuf *buf) { - buf->size = 0; - buf->len = 0; -} - -static const char *linebuf_get(struct linebuf *buf) { - if (buf->len + 1 >= sizeof(buf->bytes)) - return NULL; - buf->bytes[buf->len] = '\0'; - return buf->bytes; -} - -static bool linebuf_add(struct linebuf *buf, const char *s, size_t len) { - if (buf->len + len >= sizeof(buf->bytes)) - return false; - memcpy(buf->bytes + buf->len, s, len); - buf->len += len; - return true; -} - -static bool linebuf_add_char(struct linebuf *buf, char c) { - if (buf->len + 1 >= sizeof(buf->bytes)) - return false; - buf->bytes[buf->len++] = c; - return true; -} - -static void linebuf_rem(struct linebuf *buf, size_t count) { - assert(buf->len >= count); - buf->len -= count; -} - -static void linebuf_rem_char(struct linebuf *buf) { - linebuf_rem(buf, 1); -} - -static const struct trie_child_entry_f *trie_node_children(sd_hwdb *hwdb, const struct trie_node_f *node) { - return (const struct trie_child_entry_f *)((const char *)node + le64toh(hwdb->head->node_size)); -} - -static const struct trie_value_entry_f *trie_node_values(sd_hwdb *hwdb, const struct trie_node_f *node) { - const char *base = (const char *)node; - - base += le64toh(hwdb->head->node_size); - base += node->children_count * le64toh(hwdb->head->child_entry_size); - return (const struct trie_value_entry_f *)base; -} - -static const struct trie_node_f *trie_node_from_off(sd_hwdb *hwdb, le64_t off) { - return (const struct trie_node_f *)(hwdb->map + le64toh(off)); -} - -static const char *trie_string(sd_hwdb *hwdb, le64_t off) { - return hwdb->map + le64toh(off); -} - -static int trie_children_cmp_f(const void *v1, const void *v2) { - const struct trie_child_entry_f *n1 = v1; - const struct trie_child_entry_f *n2 = v2; - - return n1->c - n2->c; -} - -static const struct trie_node_f *node_lookup_f(sd_hwdb *hwdb, const struct trie_node_f *node, uint8_t c) { - struct trie_child_entry_f *child; - struct trie_child_entry_f search; - - search.c = c; - child = bsearch(&search, trie_node_children(hwdb, node), node->children_count, - le64toh(hwdb->head->child_entry_size), trie_children_cmp_f); - if (child) - return trie_node_from_off(hwdb, child->child_off); - return NULL; -} - -static int hwdb_add_property(sd_hwdb *hwdb, const char *key, const char *value) { - int r; - - assert(hwdb); - assert(key); - assert(value); - - /* - * Silently ignore all properties which do not start with a - * space; future extensions might use additional prefixes. - */ - if (key[0] != ' ') - return 0; - - key++; - - r = ordered_hashmap_ensure_allocated(&hwdb->properties, &string_hash_ops); - if (r < 0) - return r; - - r = ordered_hashmap_replace(hwdb->properties, key, (char*)value); - if (r < 0) - return r; - - hwdb->properties_modified = true; - - return 0; -} - -static int trie_fnmatch_f(sd_hwdb *hwdb, const struct trie_node_f *node, size_t p, - struct linebuf *buf, const char *search) { - size_t len; - size_t i; - const char *prefix; - int err; - - prefix = trie_string(hwdb, node->prefix_off); - len = strlen(prefix + p); - linebuf_add(buf, prefix + p, len); - - for (i = 0; i < node->children_count; i++) { - const struct trie_child_entry_f *child = &trie_node_children(hwdb, node)[i]; - - linebuf_add_char(buf, child->c); - err = trie_fnmatch_f(hwdb, trie_node_from_off(hwdb, child->child_off), 0, buf, search); - if (err < 0) - return err; - linebuf_rem_char(buf); - } - - if (le64toh(node->values_count) && fnmatch(linebuf_get(buf), search, 0) == 0) - for (i = 0; i < le64toh(node->values_count); i++) { - err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[i].key_off), - trie_string(hwdb, trie_node_values(hwdb, node)[i].value_off)); - if (err < 0) - return err; - } - - linebuf_rem(buf, len); - return 0; -} - -static int trie_search_f(sd_hwdb *hwdb, const char *search) { - struct linebuf buf; - const struct trie_node_f *node; - size_t i = 0; - int err; - - linebuf_init(&buf); - - node = trie_node_from_off(hwdb, hwdb->head->nodes_root_off); - while (node) { - const struct trie_node_f *child; - size_t p = 0; - - if (node->prefix_off) { - uint8_t c; - - for (; (c = trie_string(hwdb, node->prefix_off)[p]); p++) { - if (c == '*' || c == '?' || c == '[') - return trie_fnmatch_f(hwdb, node, p, &buf, search + i + p); - if (c != search[i + p]) - return 0; - } - i += p; - } - - child = node_lookup_f(hwdb, node, '*'); - if (child) { - linebuf_add_char(&buf, '*'); - err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); - if (err < 0) - return err; - linebuf_rem_char(&buf); - } - - child = node_lookup_f(hwdb, node, '?'); - if (child) { - linebuf_add_char(&buf, '?'); - err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); - if (err < 0) - return err; - linebuf_rem_char(&buf); - } - - child = node_lookup_f(hwdb, node, '['); - if (child) { - linebuf_add_char(&buf, '['); - err = trie_fnmatch_f(hwdb, child, 0, &buf, search + i); - if (err < 0) - return err; - linebuf_rem_char(&buf); - } - - if (search[i] == '\0') { - size_t n; - - for (n = 0; n < le64toh(node->values_count); n++) { - err = hwdb_add_property(hwdb, trie_string(hwdb, trie_node_values(hwdb, node)[n].key_off), - trie_string(hwdb, trie_node_values(hwdb, node)[n].value_off)); - if (err < 0) - return err; - } - return 0; - } - - child = node_lookup_f(hwdb, node, search[i]); - node = child; - i++; - } - return 0; -} - -static const char hwdb_bin_paths[] = - "/etc/systemd/hwdb/hwdb.bin\0" - "/etc/udev/hwdb.bin\0" - "/usr/lib/systemd/hwdb/hwdb.bin\0" -#ifdef HAVE_SPLIT_USR - "/lib/systemd/hwdb/hwdb.bin\0" -#endif - UDEVLIBEXECDIR "/hwdb.bin\0"; - -_public_ int sd_hwdb_new(sd_hwdb **ret) { - _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb = NULL; - const char *hwdb_bin_path; - const char sig[] = HWDB_SIG; - - assert_return(ret, -EINVAL); - - hwdb = new0(sd_hwdb, 1); - if (!hwdb) - return -ENOMEM; - - hwdb->n_ref = REFCNT_INIT; - - /* find hwdb.bin in hwdb_bin_paths */ - NULSTR_FOREACH(hwdb_bin_path, hwdb_bin_paths) { - hwdb->f = fopen(hwdb_bin_path, "re"); - if (hwdb->f) - break; - else if (errno == ENOENT) - continue; - else - return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path); - } - - if (!hwdb->f) { - log_debug("hwdb.bin does not exist, please run udevadm hwdb --update"); - return -ENOENT; - } - - if (fstat(fileno(hwdb->f), &hwdb->st) < 0 || - (size_t)hwdb->st.st_size < offsetof(struct trie_header_f, strings_len) + 8) - return log_debug_errno(errno, "error reading %s: %m", hwdb_bin_path); - - hwdb->map = mmap(0, hwdb->st.st_size, PROT_READ, MAP_SHARED, fileno(hwdb->f), 0); - if (hwdb->map == MAP_FAILED) - return log_debug_errno(errno, "error mapping %s: %m", hwdb_bin_path); - - if (memcmp(hwdb->map, sig, sizeof(hwdb->head->signature)) != 0 || - (size_t)hwdb->st.st_size != le64toh(hwdb->head->file_size)) { - log_debug("error recognizing the format of %s", hwdb_bin_path); - return -EINVAL; - } - - log_debug("=== trie on-disk ==="); - log_debug("tool version: %"PRIu64, le64toh(hwdb->head->tool_version)); - log_debug("file size: %8"PRIi64" bytes", hwdb->st.st_size); - log_debug("header size %8"PRIu64" bytes", le64toh(hwdb->head->header_size)); - log_debug("strings %8"PRIu64" bytes", le64toh(hwdb->head->strings_len)); - log_debug("nodes %8"PRIu64" bytes", le64toh(hwdb->head->nodes_len)); - - *ret = hwdb; - hwdb = NULL; - - return 0; -} - -_public_ sd_hwdb *sd_hwdb_ref(sd_hwdb *hwdb) { - assert_return(hwdb, NULL); - - assert_se(REFCNT_INC(hwdb->n_ref) >= 2); - - return hwdb; -} - -_public_ sd_hwdb *sd_hwdb_unref(sd_hwdb *hwdb) { - if (hwdb && REFCNT_DEC(hwdb->n_ref) == 0) { - if (hwdb->map) - munmap((void *)hwdb->map, hwdb->st.st_size); - safe_fclose(hwdb->f); - free(hwdb->modalias); - ordered_hashmap_free(hwdb->properties); - free(hwdb); - } - - return NULL; -} - -bool hwdb_validate(sd_hwdb *hwdb) { - bool found = false; - const char* p; - struct stat st; - - if (!hwdb) - return false; - if (!hwdb->f) - return false; - - /* if hwdb.bin doesn't exist anywhere, we need to update */ - NULSTR_FOREACH(p, hwdb_bin_paths) { - if (stat(p, &st) >= 0) { - found = true; - break; - } - } - if (!found) - return true; - - if (timespec_load(&hwdb->st.st_mtim) != timespec_load(&st.st_mtim)) - return true; - return false; -} - -static int properties_prepare(sd_hwdb *hwdb, const char *modalias) { - _cleanup_free_ char *mod = NULL; - int r; - - assert(hwdb); - assert(modalias); - - if (streq_ptr(modalias, hwdb->modalias)) - return 0; - - mod = strdup(modalias); - if (!mod) - return -ENOMEM; - - ordered_hashmap_clear(hwdb->properties); - - hwdb->properties_modified = true; - - r = trie_search_f(hwdb, modalias); - if (r < 0) - return r; - - free(hwdb->modalias); - hwdb->modalias = mod; - mod = NULL; - - return 0; -} - -_public_ int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **_value) { - const char *value; - int r; - - assert_return(hwdb, -EINVAL); - assert_return(hwdb->f, -EINVAL); - assert_return(modalias, -EINVAL); - assert_return(_value, -EINVAL); - - r = properties_prepare(hwdb, modalias); - if (r < 0) - return r; - - value = ordered_hashmap_get(hwdb->properties, key); - if (!value) - return -ENOENT; - - *_value = value; - - return 0; -} - -_public_ int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias) { - int r; - - assert_return(hwdb, -EINVAL); - assert_return(hwdb->f, -EINVAL); - assert_return(modalias, -EINVAL); - - r = properties_prepare(hwdb, modalias); - if (r < 0) - return r; - - hwdb->properties_modified = false; - hwdb->properties_iterator = ITERATOR_FIRST; - - return 0; -} - -_public_ int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value) { - const void *k; - void *v; - - assert_return(hwdb, -EINVAL); - assert_return(key, -EINVAL); - assert_return(value, -EINVAL); - - if (hwdb->properties_modified) - return -EAGAIN; - - ordered_hashmap_iterate(hwdb->properties, &hwdb->properties_iterator, &v, &k); - if (!k) - return 0; - - *key = k; - *value = v; - - return 1; -} diff --git a/src/libsystemd/sd-id128/Makefile b/src/libsystemd/sd-id128/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-id128/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-id128/sd-id128.c b/src/libsystemd/sd-id128/sd-id128.c deleted file mode 100644 index d9c0116f60..0000000000 --- a/src/libsystemd/sd-id128/sd-id128.c +++ /dev/null @@ -1,225 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include - -#include "sd-id128.h" - -#include "fd-util.h" -#include "hexdecoct.h" -#include "io-util.h" -#include "macro.h" -#include "random-util.h" -#include "util.h" - -_public_ char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]) { - unsigned n; - - assert_return(s, NULL); - - for (n = 0; n < 16; n++) { - s[n*2] = hexchar(id.bytes[n] >> 4); - s[n*2+1] = hexchar(id.bytes[n] & 0xF); - } - - s[32] = 0; - - return s; -} - -_public_ int sd_id128_from_string(const char s[], sd_id128_t *ret) { - unsigned n, i; - sd_id128_t t; - bool is_guid = false; - - assert_return(s, -EINVAL); - assert_return(ret, -EINVAL); - - for (n = 0, i = 0; n < 16;) { - int a, b; - - if (s[i] == '-') { - /* Is this a GUID? Then be nice, and skip over - * the dashes */ - - if (i == 8) - is_guid = true; - else if (i == 13 || i == 18 || i == 23) { - if (!is_guid) - return -EINVAL; - } else - return -EINVAL; - - i++; - continue; - } - - a = unhexchar(s[i++]); - if (a < 0) - return -EINVAL; - - b = unhexchar(s[i++]); - if (b < 0) - return -EINVAL; - - t.bytes[n++] = (a << 4) | b; - } - - if (i != (is_guid ? 36 : 32)) - return -EINVAL; - - if (s[i] != 0) - return -EINVAL; - - *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; - 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; - - t.bytes[j] = a << 4 | b; - } - - saved_machine_id = t; - saved_machine_id_valid = true; - - *ret = t; - 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; - int r; - - assert_return(ret, -EINVAL); - - if (saved_boot_id_valid) { - *ret = saved_boot_id; - return 0; - } - - 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; - - t.bytes[j] = a << 4 | b; - - p += 2; - } - - saved_boot_id = t; - saved_boot_id_valid = true; - - *ret = t; - return 0; -} - -_public_ int sd_id128_randomize(sd_id128_t *ret) { - sd_id128_t t; - int r; - - assert_return(ret, -EINVAL); - - r = dev_urandom(&t, sizeof(t)); - if (r < 0) - return r; - - /* Turn this into a valid v4 UUID, to be nice. Note that we - * only guarantee this for newly generated UUIDs, not for - * pre-existing ones. */ - - *ret = make_v4_uuid(t); - return 0; -} diff --git a/src/libsystemd/sd-login/Makefile b/src/libsystemd/sd-login/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd/sd-login/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-login/sd-login.c b/src/libsystemd/sd-login/sd-login.c deleted file mode 100644 index 9d4f187502..0000000000 --- a/src/libsystemd/sd-login/sd-login.c +++ /dev/null @@ -1,1062 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include -#include -#include - -#include "sd-login.h" - -#include "alloc-util.h" -#include "cgroup-util.h" -#include "dirent-util.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "hostname-util.h" -#include "io-util.h" -#include "login-util.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -/* Error codes: - * - * invalid input parameters → -EINVAL - * invalid fd → -EBADF - * process does not exist → -ESRCH - * cgroup does not exist → -ENOENT - * machine, session does not exist → -ENXIO - * requested metadata on object is missing → -ENODATA - */ - -_public_ int sd_pid_get_session(pid_t pid, char **session) { - - assert_return(pid >= 0, -EINVAL); - assert_return(session, -EINVAL); - - return cg_pid_get_session(pid, session); -} - -_public_ int sd_pid_get_unit(pid_t pid, char **unit) { - - assert_return(pid >= 0, -EINVAL); - assert_return(unit, -EINVAL); - - return cg_pid_get_unit(pid, unit); -} - -_public_ int sd_pid_get_user_unit(pid_t pid, char **unit) { - - assert_return(pid >= 0, -EINVAL); - assert_return(unit, -EINVAL); - - return cg_pid_get_user_unit(pid, unit); -} - -_public_ int sd_pid_get_machine_name(pid_t pid, char **name) { - - assert_return(pid >= 0, -EINVAL); - assert_return(name, -EINVAL); - - return cg_pid_get_machine_name(pid, name); -} - -_public_ int sd_pid_get_slice(pid_t pid, char **slice) { - - assert_return(pid >= 0, -EINVAL); - assert_return(slice, -EINVAL); - - return cg_pid_get_slice(pid, slice); -} - -_public_ int sd_pid_get_user_slice(pid_t pid, char **slice) { - - assert_return(pid >= 0, -EINVAL); - assert_return(slice, -EINVAL); - - return cg_pid_get_user_slice(pid, slice); -} - -_public_ int sd_pid_get_owner_uid(pid_t pid, uid_t *uid) { - - assert_return(pid >= 0, -EINVAL); - assert_return(uid, -EINVAL); - - return cg_pid_get_owner_uid(pid, uid); -} - -_public_ int sd_pid_get_cgroup(pid_t pid, char **cgroup) { - char *c; - int r; - - assert_return(pid >= 0, -EINVAL); - assert_return(cgroup, -EINVAL); - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &c); - if (r < 0) - return r; - - /* 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. */ - if (isempty(c)) { - free(c); - c = strdup("/"); - if (!c) - return -ENOMEM; - - } - - *cgroup = c; - return 0; -} - -_public_ int sd_peer_get_session(int fd, char **session) { - struct ucred ucred = {}; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(session, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_session(ucred.pid, session); -} - -_public_ int sd_peer_get_owner_uid(int fd, uid_t *uid) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(uid, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_owner_uid(ucred.pid, uid); -} - -_public_ int sd_peer_get_unit(int fd, char **unit) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(unit, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_unit(ucred.pid, unit); -} - -_public_ int sd_peer_get_user_unit(int fd, char **unit) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(unit, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_user_unit(ucred.pid, unit); -} - -_public_ int sd_peer_get_machine_name(int fd, char **machine) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(machine, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_machine_name(ucred.pid, machine); -} - -_public_ int sd_peer_get_slice(int fd, char **slice) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(slice, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_slice(ucred.pid, slice); -} - -_public_ int sd_peer_get_user_slice(int fd, char **slice) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(slice, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return cg_pid_get_user_slice(ucred.pid, slice); -} - -_public_ int sd_peer_get_cgroup(int fd, char **cgroup) { - struct ucred ucred; - int r; - - assert_return(fd >= 0, -EBADF); - assert_return(cgroup, -EINVAL); - - r = getpeercred(fd, &ucred); - if (r < 0) - return r; - - return sd_pid_get_cgroup(ucred.pid, cgroup); -} - -static int file_of_uid(uid_t uid, char **p) { - - assert_return(uid_is_valid(uid), -EINVAL); - assert(p); - - if (asprintf(p, "/run/systemd/users/" UID_FMT, uid) < 0) - return -ENOMEM; - - return 0; -} - -_public_ int sd_uid_get_state(uid_t uid, char**state) { - _cleanup_free_ char *p = NULL; - char *s = NULL; - int r; - - assert_return(state, -EINVAL); - - r = file_of_uid(uid, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, "STATE", &s, NULL); - if (r == -ENOENT) { - free(s); - s = strdup("offline"); - if (!s) - return -ENOMEM; - - } - if (r < 0) { - free(s); - return r; - } - if (isempty(s)) { - free(s); - return -EIO; - } - - *state = s; - return 0; -} - -_public_ int sd_uid_get_display(uid_t uid, char **session) { - _cleanup_free_ char *p = NULL, *s = NULL; - int r; - - assert_return(session, -EINVAL); - - r = file_of_uid(uid, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, "DISPLAY", &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - *session = s; - s = NULL; - - return 0; -} - -static int file_of_seat(const char *seat, char **_p) { - char *p; - int r; - - assert(_p); - - if (seat) { - if (!filename_is_valid(seat)) - return -EINVAL; - - p = strappend("/run/systemd/seats/", seat); - } else { - _cleanup_free_ char *buf = NULL; - - r = sd_session_get_seat(NULL, &buf); - if (r < 0) - return r; - - p = strappend("/run/systemd/seats/", buf); - } - - if (!p) - return -ENOMEM; - - *_p = p; - p = NULL; - return 0; -} - -_public_ int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat) { - _cleanup_free_ char *t = NULL, *s = NULL, *p = NULL; - size_t l; - int r; - const char *word, *variable, *state; - - assert_return(uid_is_valid(uid), -EINVAL); - - r = file_of_seat(seat, &p); - if (r < 0) - return r; - - variable = require_active ? "ACTIVE_UID" : "UIDS"; - - r = parse_env_file(p, NEWLINE, variable, &s, NULL); - if (r == -ENOENT) - return 0; - if (r < 0) - return r; - if (isempty(s)) - return 0; - - if (asprintf(&t, UID_FMT, uid) < 0) - return -ENOMEM; - - FOREACH_WORD(word, l, s, state) - if (strneq(t, word, l)) - return 1; - - return 0; -} - -static int uid_get_array(uid_t uid, const char *variable, char ***array) { - _cleanup_free_ char *p = NULL, *s = NULL; - char **a; - int r; - - assert(variable); - - r = file_of_uid(uid, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, variable, &s, NULL); - if (r == -ENOENT || (r >= 0 && isempty(s))) { - if (array) - *array = NULL; - return 0; - } - if (r < 0) - return r; - - a = strv_split(s, " "); - if (!a) - return -ENOMEM; - - strv_uniq(a); - r = strv_length(a); - - if (array) - *array = a; - else - strv_free(a); - - return r; -} - -_public_ int sd_uid_get_sessions(uid_t uid, int require_active, char ***sessions) { - return uid_get_array( - uid, - require_active == 0 ? "ONLINE_SESSIONS" : - require_active > 0 ? "ACTIVE_SESSIONS" : - "SESSIONS", - sessions); -} - -_public_ int sd_uid_get_seats(uid_t uid, int require_active, char ***seats) { - return uid_get_array( - uid, - require_active == 0 ? "ONLINE_SEATS" : - require_active > 0 ? "ACTIVE_SEATS" : - "SEATS", - seats); -} - -static int file_of_session(const char *session, char **_p) { - char *p; - int r; - - assert(_p); - - if (session) { - if (!session_id_valid(session)) - return -EINVAL; - - p = strappend("/run/systemd/sessions/", session); - } else { - _cleanup_free_ char *buf = NULL; - - r = sd_pid_get_session(0, &buf); - if (r < 0) - return r; - - p = strappend("/run/systemd/sessions/", buf); - } - - if (!p) - return -ENOMEM; - - *_p = p; - return 0; -} - -_public_ int sd_session_is_active(const char *session) { - _cleanup_free_ char *p = NULL, *s = NULL; - int r; - - r = file_of_session(session, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, "ACTIVE", &s, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (isempty(s)) - return -EIO; - - return parse_boolean(s); -} - -_public_ int sd_session_is_remote(const char *session) { - _cleanup_free_ char *p = NULL, *s = NULL; - int r; - - r = file_of_session(session, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, "REMOTE", &s, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - return parse_boolean(s); -} - -_public_ int sd_session_get_state(const char *session, char **state) { - _cleanup_free_ char *p = NULL, *s = NULL; - int r; - - assert_return(state, -EINVAL); - - r = file_of_session(session, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, "STATE", &s, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (isempty(s)) - return -EIO; - - *state = s; - s = NULL; - - return 0; -} - -_public_ int sd_session_get_uid(const char *session, uid_t *uid) { - int r; - _cleanup_free_ char *p = NULL, *s = NULL; - - assert_return(uid, -EINVAL); - - r = file_of_session(session, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, "UID", &s, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (isempty(s)) - return -EIO; - - return parse_uid(s, uid); -} - -static int session_get_string(const char *session, const char *field, char **value) { - _cleanup_free_ char *p = NULL, *s = NULL; - int r; - - assert_return(value, -EINVAL); - assert(field); - - r = file_of_session(session, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, field, &s, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - *value = s; - s = NULL; - return 0; -} - -_public_ int sd_session_get_seat(const char *session, char **seat) { - return session_get_string(session, "SEAT", seat); -} - -_public_ int sd_session_get_tty(const char *session, char **tty) { - return session_get_string(session, "TTY", tty); -} - -_public_ int sd_session_get_vt(const char *session, unsigned *vtnr) { - _cleanup_free_ char *vtnr_string = NULL; - unsigned u; - int r; - - assert_return(vtnr, -EINVAL); - - r = session_get_string(session, "VTNR", &vtnr_string); - if (r < 0) - return r; - - r = safe_atou(vtnr_string, &u); - if (r < 0) - return r; - - *vtnr = u; - return 0; -} - -_public_ int sd_session_get_service(const char *session, char **service) { - return session_get_string(session, "SERVICE", service); -} - -_public_ int sd_session_get_type(const char *session, char **type) { - return session_get_string(session, "TYPE", type); -} - -_public_ int sd_session_get_class(const char *session, char **class) { - return session_get_string(session, "CLASS", class); -} - -_public_ int sd_session_get_desktop(const char *session, char **desktop) { - _cleanup_free_ char *escaped = NULL; - char *t; - int r; - - assert_return(desktop, -EINVAL); - - r = session_get_string(session, "DESKTOP", &escaped); - if (r < 0) - return r; - - r = cunescape(escaped, 0, &t); - if (r < 0) - return r; - - *desktop = t; - return 0; -} - -_public_ int sd_session_get_display(const char *session, char **display) { - return session_get_string(session, "DISPLAY", display); -} - -_public_ int sd_session_get_remote_user(const char *session, char **remote_user) { - return session_get_string(session, "REMOTE_USER", remote_user); -} - -_public_ int sd_session_get_remote_host(const char *session, char **remote_host) { - return session_get_string(session, "REMOTE_HOST", remote_host); -} - -_public_ int sd_seat_get_active(const char *seat, char **session, uid_t *uid) { - _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL; - int r; - - assert_return(session || uid, -EINVAL); - - r = file_of_seat(seat, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, - "ACTIVE", &s, - "ACTIVE_UID", &t, - NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - - if (session && !s) - return -ENODATA; - - if (uid && !t) - return -ENODATA; - - if (uid && t) { - r = parse_uid(t, uid); - if (r < 0) - return r; - } - - if (session && s) { - *session = s; - s = NULL; - } - - return 0; -} - -_public_ int sd_seat_get_sessions(const char *seat, char ***sessions, uid_t **uids, unsigned *n_uids) { - _cleanup_free_ char *p = NULL, *s = NULL, *t = NULL; - _cleanup_strv_free_ char **a = NULL; - _cleanup_free_ uid_t *b = NULL; - unsigned n = 0; - int r; - - r = file_of_seat(seat, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, - "SESSIONS", &s, - "ACTIVE_SESSIONS", &t, - NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - - if (s) { - a = strv_split(s, " "); - if (!a) - return -ENOMEM; - } - - if (uids && t) { - const char *word, *state; - size_t l; - - FOREACH_WORD(word, l, t, state) - n++; - - if (n > 0) { - unsigned i = 0; - - b = new(uid_t, n); - if (!b) - return -ENOMEM; - - FOREACH_WORD(word, l, t, state) { - _cleanup_free_ char *k = NULL; - - k = strndup(word, l); - if (!k) - return -ENOMEM; - - r = parse_uid(k, b + i); - if (r < 0) - continue; - - i++; - } - } - } - - r = strv_length(a); - - if (sessions) { - *sessions = a; - a = NULL; - } - - if (uids) { - *uids = b; - b = NULL; - } - - if (n_uids) - *n_uids = n; - - return r; -} - -static int seat_get_can(const char *seat, const char *variable) { - _cleanup_free_ char *p = NULL, *s = NULL; - int r; - - assert(variable); - - r = file_of_seat(seat, &p); - if (r < 0) - return r; - - r = parse_env_file(p, NEWLINE, - variable, &s, - NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - return parse_boolean(s); -} - -_public_ int sd_seat_can_multi_session(const char *seat) { - return seat_get_can(seat, "CAN_MULTI_SESSION"); -} - -_public_ int sd_seat_can_tty(const char *seat) { - return seat_get_can(seat, "CAN_TTY"); -} - -_public_ int sd_seat_can_graphical(const char *seat) { - return seat_get_can(seat, "CAN_GRAPHICAL"); -} - -_public_ int sd_get_seats(char ***seats) { - return get_files_in_directory("/run/systemd/seats/", seats); -} - -_public_ int sd_get_sessions(char ***sessions) { - return get_files_in_directory("/run/systemd/sessions/", sessions); -} - -_public_ int sd_get_uids(uid_t **users) { - _cleanup_closedir_ DIR *d; - int r = 0; - unsigned n = 0; - _cleanup_free_ uid_t *l = NULL; - - d = opendir("/run/systemd/users/"); - if (!d) - return -errno; - - for (;;) { - struct dirent *de; - int k; - uid_t uid; - - errno = 0; - de = readdir(d); - if (!de && errno > 0) - return -errno; - - if (!de) - break; - - dirent_ensure_type(d, de); - - if (!dirent_is_file(de)) - continue; - - k = parse_uid(de->d_name, &uid); - if (k < 0) - continue; - - if (users) { - if ((unsigned) r >= n) { - uid_t *t; - - n = MAX(16, 2*r); - t = realloc(l, sizeof(uid_t) * n); - if (!t) - return -ENOMEM; - - l = t; - } - - assert((unsigned) r < n); - l[r++] = uid; - } else - r++; - } - - if (users) { - *users = l; - l = NULL; - } - - return r; -} - -_public_ int sd_get_machine_names(char ***machines) { - char **l = NULL, **a, **b; - int r; - - assert_return(machines, -EINVAL); - - r = get_files_in_directory("/run/systemd/machines/", &l); - if (r < 0) - return r; - - if (l) { - r = 0; - - /* Filter out the unit: symlinks */ - for (a = l, b = l; *a; a++) { - if (startswith(*a, "unit:") || !machine_name_is_valid(*a)) - free(*a); - else { - *b = *a; - b++; - r++; - } - } - - *b = NULL; - } - - *machines = l; - return r; -} - -_public_ int sd_machine_get_class(const char *machine, char **class) { - _cleanup_free_ char *c = NULL; - const char *p; - int r; - - assert_return(machine_name_is_valid(machine), -EINVAL); - assert_return(class, -EINVAL); - - p = strjoina("/run/systemd/machines/", machine); - r = parse_env_file(p, NEWLINE, "CLASS", &c, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (!c) - return -EIO; - - *class = c; - c = NULL; - - return 0; -} - -_public_ int sd_machine_get_ifindices(const char *machine, int **ifindices) { - _cleanup_free_ char *netif = NULL; - size_t l, allocated = 0, nr = 0; - int *ni = NULL; - const char *p, *word, *state; - int r; - - assert_return(machine_name_is_valid(machine), -EINVAL); - assert_return(ifindices, -EINVAL); - - p = strjoina("/run/systemd/machines/", machine); - r = parse_env_file(p, NEWLINE, "NETIF", &netif, NULL); - if (r == -ENOENT) - return -ENXIO; - if (r < 0) - return r; - if (!netif) { - *ifindices = NULL; - return 0; - } - - FOREACH_WORD(word, l, netif, state) { - char buf[l+1]; - int ifi; - - *(char*) (mempcpy(buf, word, l)) = 0; - - if (parse_ifindex(buf, &ifi) < 0) - continue; - - if (!GREEDY_REALLOC(ni, allocated, nr+1)) { - free(ni); - return -ENOMEM; - } - - ni[nr++] = ifi; - } - - *ifindices = ni; - return nr; -} - -static inline int MONITOR_TO_FD(sd_login_monitor *m) { - return (int) (unsigned long) m - 1; -} - -static inline sd_login_monitor* FD_TO_MONITOR(int fd) { - return (sd_login_monitor*) (unsigned long) (fd + 1); -} - -_public_ int sd_login_monitor_new(const char *category, sd_login_monitor **m) { - int fd, k; - bool good = false; - - assert_return(m, -EINVAL); - - fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (fd < 0) - return -errno; - - if (!category || streq(category, "seat")) { - k = inotify_add_watch(fd, "/run/systemd/seats/", IN_MOVED_TO|IN_DELETE); - if (k < 0) { - safe_close(fd); - return -errno; - } - - good = true; - } - - if (!category || streq(category, "session")) { - k = inotify_add_watch(fd, "/run/systemd/sessions/", IN_MOVED_TO|IN_DELETE); - if (k < 0) { - safe_close(fd); - return -errno; - } - - good = true; - } - - if (!category || streq(category, "uid")) { - k = inotify_add_watch(fd, "/run/systemd/users/", IN_MOVED_TO|IN_DELETE); - if (k < 0) { - safe_close(fd); - return -errno; - } - - good = true; - } - - if (!category || streq(category, "machine")) { - k = inotify_add_watch(fd, "/run/systemd/machines/", IN_MOVED_TO|IN_DELETE); - if (k < 0) { - safe_close(fd); - return -errno; - } - - good = true; - } - - if (!good) { - close_nointr(fd); - return -EINVAL; - } - - *m = FD_TO_MONITOR(fd); - return 0; -} - -_public_ sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m) { - int fd; - - if (!m) - return NULL; - - fd = MONITOR_TO_FD(m); - close_nointr(fd); - - return NULL; -} - -_public_ int sd_login_monitor_flush(sd_login_monitor *m) { - - assert_return(m, -EINVAL); - - return flush_fd(MONITOR_TO_FD(m)); -} - -_public_ int sd_login_monitor_get_fd(sd_login_monitor *m) { - - assert_return(m, -EINVAL); - - return MONITOR_TO_FD(m); -} - -_public_ int sd_login_monitor_get_events(sd_login_monitor *m) { - - assert_return(m, -EINVAL); - - /* For now we will only return POLLIN here, since we don't - * need anything else ever for inotify. However, let's have - * this API to keep our options open should we later on need - * it. */ - return POLLIN; -} - -_public_ int sd_login_monitor_get_timeout(sd_login_monitor *m, uint64_t *timeout_usec) { - - assert_return(m, -EINVAL); - assert_return(timeout_usec, -EINVAL); - - /* For now we will only return (uint64_t) -1, since we don't - * need any timeout. However, let's have this API to keep our - * options open should we later on need it. */ - *timeout_usec = (uint64_t) -1; - return 0; -} diff --git a/src/libsystemd/sd-login/test-login.c b/src/libsystemd/sd-login/test-login.c deleted file mode 100644 index c1fd7dd33e..0000000000 --- a/src/libsystemd/sd-login/test-login.c +++ /dev/null @@ -1,264 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include - -#include "sd-login.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -static void test_login(void) { - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_free_ char *pp = NULL, *qq = NULL; - int r, k; - uid_t u, u2; - char *seat, *type, *class, *display, *remote_user, *remote_host, *display_session, *cgroup; - char *session; - char *state; - char *session2; - char *t; - char **seats, **sessions, **machines; - uid_t *uids; - unsigned n; - struct pollfd pollfd; - sd_login_monitor *m = NULL; - - assert_se(sd_pid_get_session(0, &session) == 0); - printf("session = %s\n", session); - - assert_se(sd_pid_get_owner_uid(0, &u2) == 0); - printf("user = "UID_FMT"\n", u2); - - assert_se(sd_pid_get_cgroup(0, &cgroup) == 0); - printf("cgroup = %s\n", cgroup); - free(cgroup); - - display_session = NULL; - r = sd_uid_get_display(u2, &display_session); - assert_se(r >= 0 || r == -ENODATA); - printf("user's display session = %s\n", strna(display_session)); - free(display_session); - - assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) == 0); - sd_peer_get_session(pair[0], &pp); - sd_peer_get_session(pair[1], &qq); - assert_se(streq_ptr(pp, qq)); - - r = sd_uid_get_sessions(u2, false, &sessions); - assert_se(r >= 0); - assert_se(r == (int) strv_length(sessions)); - assert_se(t = strv_join(sessions, ", ")); - strv_free(sessions); - printf("sessions = %s\n", t); - free(t); - - assert_se(r == sd_uid_get_sessions(u2, false, NULL)); - - r = sd_uid_get_seats(u2, false, &seats); - assert_se(r >= 0); - assert_se(r == (int) strv_length(seats)); - assert_se(t = strv_join(seats, ", ")); - strv_free(seats); - printf("seats = %s\n", t); - free(t); - - assert_se(r == sd_uid_get_seats(u2, false, NULL)); - - r = sd_session_is_active(session); - assert_se(r >= 0); - printf("active = %s\n", yes_no(r)); - - r = sd_session_is_remote(session); - assert_se(r >= 0); - printf("remote = %s\n", yes_no(r)); - - r = sd_session_get_state(session, &state); - assert_se(r >= 0); - printf("state = %s\n", state); - free(state); - - assert_se(sd_session_get_uid(session, &u) >= 0); - printf("uid = "UID_FMT"\n", u); - assert_se(u == u2); - - assert_se(sd_session_get_type(session, &type) >= 0); - printf("type = %s\n", type); - free(type); - - assert_se(sd_session_get_class(session, &class) >= 0); - printf("class = %s\n", class); - free(class); - - display = NULL; - r = sd_session_get_display(session, &display); - assert_se(r >= 0 || r == -ENODATA); - printf("display = %s\n", strna(display)); - free(display); - - remote_user = NULL; - r = sd_session_get_remote_user(session, &remote_user); - assert_se(r >= 0 || r == -ENODATA); - printf("remote_user = %s\n", strna(remote_user)); - free(remote_user); - - remote_host = NULL; - r = sd_session_get_remote_host(session, &remote_host); - assert_se(r >= 0 || r == -ENODATA); - printf("remote_host = %s\n", strna(remote_host)); - free(remote_host); - - assert_se(sd_session_get_seat(session, &seat) >= 0); - printf("seat = %s\n", seat); - - r = sd_seat_can_multi_session(seat); - assert_se(r >= 0); - printf("can do multi session = %s\n", yes_no(r)); - - r = sd_seat_can_tty(seat); - assert_se(r >= 0); - printf("can do tty = %s\n", yes_no(r)); - - r = sd_seat_can_graphical(seat); - assert_se(r >= 0); - printf("can do graphical = %s\n", yes_no(r)); - - assert_se(sd_uid_get_state(u, &state) >= 0); - printf("state = %s\n", state); - - assert_se(sd_uid_is_on_seat(u, 0, seat) > 0); - - k = sd_uid_is_on_seat(u, 1, seat); - assert_se(k >= 0); - assert_se(!!r == !!r); - - assert_se(sd_seat_get_active(seat, &session2, &u2) >= 0); - printf("session2 = %s\n", session2); - printf("uid2 = "UID_FMT"\n", u2); - - r = sd_seat_get_sessions(seat, &sessions, &uids, &n); - assert_se(r >= 0); - printf("n_sessions = %i\n", r); - assert_se(r == (int) strv_length(sessions)); - assert_se(t = strv_join(sessions, ", ")); - strv_free(sessions); - printf("sessions = %s\n", t); - free(t); - printf("uids ="); - for (k = 0; k < (int) n; k++) - printf(" "UID_FMT, uids[k]); - printf("\n"); - free(uids); - - assert_se(sd_seat_get_sessions(seat, NULL, NULL, NULL) == r); - - free(session); - free(state); - free(session2); - free(seat); - - r = sd_get_seats(&seats); - assert_se(r >= 0); - assert_se(r == (int) strv_length(seats)); - assert_se(t = strv_join(seats, ", ")); - strv_free(seats); - printf("n_seats = %i\n", r); - printf("seats = %s\n", t); - free(t); - - assert_se(sd_get_seats(NULL) == r); - - r = sd_seat_get_active(NULL, &t, NULL); - assert_se(r >= 0); - printf("active session on current seat = %s\n", t); - free(t); - - r = sd_get_sessions(&sessions); - assert_se(r >= 0); - assert_se(r == (int) strv_length(sessions)); - assert_se(t = strv_join(sessions, ", ")); - strv_free(sessions); - printf("n_sessions = %i\n", r); - printf("sessions = %s\n", t); - free(t); - - assert_se(sd_get_sessions(NULL) == r); - - r = sd_get_uids(&uids); - assert_se(r >= 0); - - printf("uids ="); - for (k = 0; k < r; k++) - printf(" "UID_FMT, uids[k]); - printf("\n"); - free(uids); - - printf("n_uids = %i\n", r); - assert_se(sd_get_uids(NULL) == r); - - r = sd_get_machine_names(&machines); - assert_se(r >= 0); - assert_se(r == (int) strv_length(machines)); - assert_se(t = strv_join(machines, ", ")); - strv_free(machines); - printf("n_machines = %i\n", r); - printf("machines = %s\n", t); - free(t); - - r = sd_login_monitor_new("session", &m); - assert_se(r >= 0); - - for (n = 0; n < 5; n++) { - usec_t timeout, nw; - - zero(pollfd); - assert_se((pollfd.fd = sd_login_monitor_get_fd(m)) >= 0); - assert_se((pollfd.events = sd_login_monitor_get_events(m)) >= 0); - - assert_se(sd_login_monitor_get_timeout(m, &timeout) >= 0); - - nw = now(CLOCK_MONOTONIC); - - r = poll(&pollfd, 1, - timeout == (uint64_t) -1 ? -1 : - timeout > nw ? (int) ((timeout - nw) / 1000) : - 0); - - assert_se(r >= 0); - - sd_login_monitor_flush(m); - printf("Wake!\n"); - } - - sd_login_monitor_unref(m); -} - -int main(int argc, char* argv[]) { - log_parse_environment(); - log_open(); - - test_login(); - - return 0; -} diff --git a/src/libsystemd/sd-netlink/Makefile b/src/libsystemd/sd-netlink/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-netlink/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-netlink/local-addresses.c b/src/libsystemd/sd-netlink/local-addresses.c deleted file mode 100644 index 6abd8fd0cc..0000000000 --- a/src/libsystemd/sd-netlink/local-addresses.c +++ /dev/null @@ -1,276 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2011 Lennart Poettering - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include "sd-netlink.h" - -#include "alloc-util.h" -#include "local-addresses.h" -#include "macro.h" -#include "netlink-util.h" - -static int address_compare(const void *_a, const void *_b) { - const struct local_address *a = _a, *b = _b; - - /* Order lowest scope first, IPv4 before IPv6, lowest interface index first */ - - if (a->family == AF_INET && b->family == AF_INET6) - return -1; - if (a->family == AF_INET6 && b->family == AF_INET) - return 1; - - if (a->scope < b->scope) - return -1; - if (a->scope > b->scope) - return 1; - - if (a->metric < b->metric) - return -1; - if (a->metric > b->metric) - return 1; - - if (a->ifindex < b->ifindex) - return -1; - if (a->ifindex > b->ifindex) - return 1; - - return memcmp(&a->address, &b->address, FAMILY_ADDRESS_SIZE(a->family)); -} - -int local_addresses(sd_netlink *context, int ifindex, int af, struct local_address **ret) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_free_ struct local_address *list = NULL; - size_t n_list = 0, n_allocated = 0; - sd_netlink_message *m; - int r; - - assert(ret); - - if (context) - rtnl = sd_netlink_ref(context); - else { - r = sd_netlink_open(&rtnl); - if (r < 0) - return r; - } - - r = sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, 0, af); - if (r < 0) - return r; - - r = sd_netlink_call(rtnl, req, 0, &reply); - if (r < 0) - return r; - - for (m = reply; m; m = sd_netlink_message_next(m)) { - struct local_address *a; - unsigned char flags; - uint16_t type; - int ifi, family; - - r = sd_netlink_message_get_errno(m); - if (r < 0) - return r; - - r = sd_netlink_message_get_type(m, &type); - if (r < 0) - return r; - if (type != RTM_NEWADDR) - continue; - - r = sd_rtnl_message_addr_get_ifindex(m, &ifi); - if (r < 0) - return r; - if (ifindex > 0 && ifi != ifindex) - continue; - - r = sd_rtnl_message_addr_get_family(m, &family); - if (r < 0) - return r; - if (af != AF_UNSPEC && af != family) - continue; - - r = sd_rtnl_message_addr_get_flags(m, &flags); - if (r < 0) - return r; - if (flags & IFA_F_DEPRECATED) - continue; - - if (!GREEDY_REALLOC0(list, n_allocated, n_list+1)) - return -ENOMEM; - - a = list + n_list; - - r = sd_rtnl_message_addr_get_scope(m, &a->scope); - if (r < 0) - return r; - - if (ifindex == 0 && (a->scope == RT_SCOPE_HOST || a->scope == RT_SCOPE_NOWHERE)) - continue; - - switch (family) { - - case AF_INET: - r = sd_netlink_message_read_in_addr(m, IFA_LOCAL, &a->address.in); - if (r < 0) { - r = sd_netlink_message_read_in_addr(m, IFA_ADDRESS, &a->address.in); - if (r < 0) - continue; - } - break; - - case AF_INET6: - r = sd_netlink_message_read_in6_addr(m, IFA_LOCAL, &a->address.in6); - if (r < 0) { - r = sd_netlink_message_read_in6_addr(m, IFA_ADDRESS, &a->address.in6); - if (r < 0) - continue; - } - break; - - default: - continue; - } - - a->ifindex = ifi; - a->family = family; - - n_list++; - }; - - if (n_list > 0) - qsort(list, n_list, sizeof(struct local_address), address_compare); - - *ret = list; - list = NULL; - - return (int) n_list; -} - -int local_gateways(sd_netlink *context, int ifindex, int af, struct local_address **ret) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_free_ struct local_address *list = NULL; - sd_netlink_message *m = NULL; - size_t n_list = 0, n_allocated = 0; - int r; - - assert(ret); - - if (context) - rtnl = sd_netlink_ref(context); - else { - r = sd_netlink_open(&rtnl); - if (r < 0) - return r; - } - - r = sd_rtnl_message_new_route(rtnl, &req, RTM_GETROUTE, af, RTPROT_UNSPEC); - if (r < 0) - return r; - - r = sd_netlink_message_request_dump(req, true); - if (r < 0) - return r; - - r = sd_netlink_call(rtnl, req, 0, &reply); - if (r < 0) - return r; - - for (m = reply; m; m = sd_netlink_message_next(m)) { - struct local_address *a; - uint16_t type; - unsigned char dst_len, src_len; - uint32_t ifi; - int family; - - r = sd_netlink_message_get_errno(m); - if (r < 0) - return r; - - r = sd_netlink_message_get_type(m, &type); - if (r < 0) - return r; - if (type != RTM_NEWROUTE) - continue; - - /* We only care for default routes */ - r = sd_rtnl_message_route_get_dst_prefixlen(m, &dst_len); - if (r < 0) - return r; - if (dst_len != 0) - continue; - - r = sd_rtnl_message_route_get_src_prefixlen(m, &src_len); - if (r < 0) - return r; - if (src_len != 0) - continue; - - r = sd_netlink_message_read_u32(m, RTA_OIF, &ifi); - if (r < 0) - return r; - if (ifindex > 0 && (int) ifi != ifindex) - continue; - - r = sd_rtnl_message_route_get_family(m, &family); - if (r < 0) - return r; - if (af != AF_UNSPEC && af != family) - continue; - - if (!GREEDY_REALLOC0(list, n_allocated, n_list + 1)) - return -ENOMEM; - - a = list + n_list; - - switch (family) { - case AF_INET: - r = sd_netlink_message_read_in_addr(m, RTA_GATEWAY, &a->address.in); - if (r < 0) - continue; - - break; - case AF_INET6: - r = sd_netlink_message_read_in6_addr(m, RTA_GATEWAY, &a->address.in6); - if (r < 0) - continue; - - break; - default: - continue; - } - - sd_netlink_message_read_u32(m, RTA_PRIORITY, &a->metric); - - a->ifindex = ifi; - a->family = family; - - n_list++; - } - - if (n_list > 0) - qsort(list, n_list, sizeof(struct local_address), address_compare); - - *ret = list; - list = NULL; - - return (int) n_list; -} diff --git a/src/libsystemd/sd-netlink/local-addresses.h b/src/libsystemd/sd-netlink/local-addresses.h deleted file mode 100644 index 18d71e797e..0000000000 --- a/src/libsystemd/sd-netlink/local-addresses.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2008-2011 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 . -***/ - - -#include "sd-netlink.h" - -#include "in-addr-util.h" - -struct local_address { - int family, ifindex; - unsigned char scope; - uint32_t metric; - union in_addr_union address; -}; - -int local_addresses(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); - -int local_gateways(sd_netlink *rtnl, int ifindex, int af, struct local_address **ret); diff --git a/src/libsystemd/sd-netlink/netlink-internal.h b/src/libsystemd/sd-netlink/netlink-internal.h deleted file mode 100644 index dcfb080ad3..0000000000 --- a/src/libsystemd/sd-netlink/netlink-internal.h +++ /dev/null @@ -1,137 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 . -***/ - -#include - -#include "sd-netlink.h" - -#include "list.h" -#include "netlink-types.h" -#include "prioq.h" -#include "refcnt.h" - -#define RTNL_DEFAULT_TIMEOUT ((usec_t) (25 * USEC_PER_SEC)) - -#define RTNL_WQUEUE_MAX 1024 -#define RTNL_RQUEUE_MAX 64*1024 - -#define RTNL_CONTAINER_DEPTH 32 - -struct reply_callback { - sd_netlink_message_handler_t callback; - void *userdata; - usec_t timeout; - uint64_t serial; - unsigned prioq_idx; -}; - -struct match_callback { - sd_netlink_message_handler_t callback; - uint16_t type; - void *userdata; - - LIST_FIELDS(struct match_callback, match_callbacks); -}; - -struct sd_netlink { - RefCount n_ref; - - int fd; - - union { - struct sockaddr sa; - struct sockaddr_nl nl; - } sockaddr; - - Hashmap *broadcast_group_refs; - bool broadcast_group_dont_leave:1; /* until we can rely on 4.2 */ - - sd_netlink_message **rqueue; - unsigned rqueue_size; - size_t rqueue_allocated; - - sd_netlink_message **rqueue_partial; - unsigned rqueue_partial_size; - size_t rqueue_partial_allocated; - - struct nlmsghdr *rbuffer; - size_t rbuffer_allocated; - - bool processing:1; - - uint32_t serial; - - struct Prioq *reply_callbacks_prioq; - Hashmap *reply_callbacks; - - LIST_HEAD(struct match_callback, match_callbacks); - - pid_t original_pid; - - sd_event_source *io_event_source; - sd_event_source *time_event_source; - sd_event_source *exit_event_source; - sd_event *event; -}; - -struct netlink_attribute { - size_t offset; /* offset from hdr to attribute */ - bool nested:1; - bool net_byteorder:1; -}; - -struct netlink_container { - const struct NLTypeSystem *type_system; /* the type system of the container */ - size_t offset; /* offset from hdr to the start of the container */ - struct netlink_attribute *attributes; - unsigned short n_attributes; /* number of attributes in container */ -}; - -struct sd_netlink_message { - RefCount n_ref; - - sd_netlink *rtnl; - - struct nlmsghdr *hdr; - struct netlink_container containers[RTNL_CONTAINER_DEPTH]; - unsigned n_containers; /* number of containers */ - bool sealed:1; - bool broadcast:1; - - sd_netlink_message *next; /* next in a chain of multi-part messages */ -}; - -int message_new(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t type); -int message_new_empty(sd_netlink *rtnl, sd_netlink_message **ret); - -int socket_open(int family); -int socket_bind(sd_netlink *nl); -int socket_broadcast_group_ref(sd_netlink *nl, unsigned group); -int socket_broadcast_group_unref(sd_netlink *nl, unsigned group); -int socket_write_message(sd_netlink *nl, sd_netlink_message *m); -int socket_read_message(sd_netlink *nl); - -int rtnl_rqueue_make_room(sd_netlink *rtnl); -int rtnl_rqueue_partial_make_room(sd_netlink *rtnl); - -/* Make sure callbacks don't destroy the rtnl connection */ -#define NETLINK_DONT_DESTROY(rtnl) \ - _cleanup_(sd_netlink_unrefp) _unused_ sd_netlink *_dont_destroy_##rtnl = sd_netlink_ref(rtnl) diff --git a/src/libsystemd/sd-netlink/netlink-message.c b/src/libsystemd/sd-netlink/netlink-message.c deleted file mode 100644 index 3a866fdafe..0000000000 --- a/src/libsystemd/sd-netlink/netlink-message.c +++ /dev/null @@ -1,964 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 . -***/ - -#include -#include -#include - -#include "sd-netlink.h" - -#include "alloc-util.h" -#include "formats-util.h" -#include "missing.h" -#include "netlink-internal.h" -#include "netlink-types.h" -#include "netlink-util.h" -#include "refcnt.h" -#include "socket-util.h" -#include "util.h" - -#define GET_CONTAINER(m, i) ((i) < (m)->n_containers ? (struct rtattr*)((uint8_t*)(m)->hdr + (m)->containers[i].offset) : NULL) -#define PUSH_CONTAINER(m, new) (m)->container_offsets[(m)->n_containers ++] = (uint8_t*)(new) - (uint8_t*)(m)->hdr; - -#define RTA_TYPE(rta) ((rta)->rta_type & NLA_TYPE_MASK) -#define RTA_FLAGS(rta) ((rta)->rta_type & ~NLA_TYPE_MASK) - -int message_new_empty(sd_netlink *rtnl, sd_netlink_message **ret) { - sd_netlink_message *m; - - assert_return(ret, -EINVAL); - - /* Note that 'rtnl' is currently unused, if we start using it internally - we must take care to avoid problems due to mutual references between - buses and their queued messages. See sd-bus. - */ - - m = new0(sd_netlink_message, 1); - if (!m) - return -ENOMEM; - - m->n_ref = REFCNT_INIT; - - m->sealed = false; - - *ret = m; - - return 0; -} - -int message_new(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t type) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - const NLType *nl_type; - size_t size; - int r; - - r = type_system_get_type(&type_system_root, &nl_type, type); - if (r < 0) - return r; - - if (type_get_type(nl_type) != NETLINK_TYPE_NESTED) - return -EINVAL; - - r = message_new_empty(rtnl, &m); - if (r < 0) - return r; - - size = NLMSG_SPACE(type_get_size(nl_type)); - - assert(size >= sizeof(struct nlmsghdr)); - m->hdr = malloc0(size); - if (!m->hdr) - return -ENOMEM; - - m->hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; - - type_get_type_system(nl_type, &m->containers[0].type_system); - m->hdr->nlmsg_len = size; - m->hdr->nlmsg_type = type; - - *ret = m; - m = NULL; - - return 0; -} - -int sd_netlink_message_request_dump(sd_netlink_message *m, int dump) { - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(m->hdr->nlmsg_type == RTM_GETLINK || - m->hdr->nlmsg_type == RTM_GETADDR || - m->hdr->nlmsg_type == RTM_GETROUTE || - m->hdr->nlmsg_type == RTM_GETNEIGH, - -EINVAL); - - if (dump) - m->hdr->nlmsg_flags |= NLM_F_DUMP; - else - m->hdr->nlmsg_flags &= ~NLM_F_DUMP; - - return 0; -} - -sd_netlink_message *sd_netlink_message_ref(sd_netlink_message *m) { - if (m) - assert_se(REFCNT_INC(m->n_ref) >= 2); - - return m; -} - -sd_netlink_message *sd_netlink_message_unref(sd_netlink_message *m) { - if (m && REFCNT_DEC(m->n_ref) == 0) { - unsigned i; - - free(m->hdr); - - for (i = 0; i <= m->n_containers; i++) - free(m->containers[i].attributes); - - sd_netlink_message_unref(m->next); - - free(m); - } - - return NULL; -} - -int sd_netlink_message_get_type(sd_netlink_message *m, uint16_t *type) { - assert_return(m, -EINVAL); - assert_return(type, -EINVAL); - - *type = m->hdr->nlmsg_type; - - return 0; -} - -int sd_netlink_message_set_flags(sd_netlink_message *m, uint16_t flags) { - assert_return(m, -EINVAL); - assert_return(flags, -EINVAL); - - m->hdr->nlmsg_flags = flags; - - return 0; -} - -int sd_netlink_message_is_broadcast(sd_netlink_message *m) { - assert_return(m, -EINVAL); - - return m->broadcast; -} - -/* If successful the updated message will be correctly aligned, if - unsuccessful the old message is untouched. */ -static int add_rtattr(sd_netlink_message *m, unsigned short type, const void *data, size_t data_length) { - uint32_t rta_length; - size_t message_length, padding_length; - struct nlmsghdr *new_hdr; - struct rtattr *rta; - char *padding; - unsigned i; - int offset; - - assert(m); - assert(m->hdr); - assert(!m->sealed); - assert(NLMSG_ALIGN(m->hdr->nlmsg_len) == m->hdr->nlmsg_len); - assert(!data || data_length); - - /* get offset of the new attribute */ - offset = m->hdr->nlmsg_len; - - /* get the size of the new rta attribute (with padding at the end) */ - rta_length = RTA_LENGTH(data_length); - - /* get the new message size (with padding at the end) */ - message_length = offset + RTA_ALIGN(rta_length); - - /* realloc to fit the new attribute */ - new_hdr = realloc(m->hdr, message_length); - if (!new_hdr) - return -ENOMEM; - m->hdr = new_hdr; - - /* get pointer to the attribute we are about to add */ - rta = (struct rtattr *) ((uint8_t *) m->hdr + offset); - - /* if we are inside containers, extend them */ - for (i = 0; i < m->n_containers; i++) - GET_CONTAINER(m, i)->rta_len += message_length - offset; - - /* fill in the attribute */ - rta->rta_type = type; - rta->rta_len = rta_length; - if (data) - /* we don't deal with the case where the user lies about the type - * and gives us too little data (so don't do that) - */ - padding = mempcpy(RTA_DATA(rta), data, data_length); - else { - /* if no data was passed, make sure we still initialize the padding - note that we can have data_length > 0 (used by some containers) */ - padding = RTA_DATA(rta); - } - - /* make sure also the padding at the end of the message is initialized */ - padding_length = (uint8_t*)m->hdr + message_length - (uint8_t*)padding; - memzero(padding, padding_length); - - /* update message size */ - m->hdr->nlmsg_len = message_length; - - return offset; -} - -static int message_attribute_has_type(sd_netlink_message *m, size_t *out_size, uint16_t attribute_type, uint16_t data_type) { - const NLType *type; - int r; - - assert(m); - - r = type_system_get_type(m->containers[m->n_containers].type_system, &type, attribute_type); - if (r < 0) - return r; - - if (type_get_type(type) != data_type) - return -EINVAL; - - if (out_size) - *out_size = type_get_size(type); - return 0; -} - -int sd_netlink_message_append_string(sd_netlink_message *m, unsigned short type, const char *data) { - size_t length, size; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(data, -EINVAL); - - r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_STRING); - if (r < 0) - return r; - - if (size) { - length = strnlen(data, size+1); - if (length > size) - return -EINVAL; - } else - length = strlen(data); - - r = add_rtattr(m, type, data, length + 1); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_flag(sd_netlink_message *m, unsigned short type) { - size_t size; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_FLAG); - if (r < 0) - return r; - - r = add_rtattr(m, type, NULL, 0); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_u8(sd_netlink_message *m, unsigned short type, uint8_t data) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U8); - if (r < 0) - return r; - - r = add_rtattr(m, type, &data, sizeof(uint8_t)); - if (r < 0) - return r; - - return 0; -} - - -int sd_netlink_message_append_u16(sd_netlink_message *m, unsigned short type, uint16_t data) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U16); - if (r < 0) - return r; - - r = add_rtattr(m, type, &data, sizeof(uint16_t)); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, uint32_t data) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U32); - if (r < 0) - return r; - - r = add_rtattr(m, type, &data, sizeof(uint32_t)); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - r = add_rtattr(m, type, &data, len); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_in_addr(sd_netlink_message *m, unsigned short type, const struct in_addr *data) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(data, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); - if (r < 0) - return r; - - r = add_rtattr(m, type, data, sizeof(struct in_addr)); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_in6_addr(sd_netlink_message *m, unsigned short type, const struct in6_addr *data) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(data, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); - if (r < 0) - return r; - - r = add_rtattr(m, type, data, sizeof(struct in6_addr)); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_ether_addr(sd_netlink_message *m, unsigned short type, const struct ether_addr *data) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(data, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_ETHER_ADDR); - if (r < 0) - return r; - - r = add_rtattr(m, type, data, ETH_ALEN); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_append_cache_info(sd_netlink_message *m, unsigned short type, const struct ifa_cacheinfo *info) { - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(info, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_CACHE_INFO); - if (r < 0) - return r; - - r = add_rtattr(m, type, info, sizeof(struct ifa_cacheinfo)); - if (r < 0) - return r; - - return 0; -} - -int sd_netlink_message_open_container(sd_netlink_message *m, unsigned short type) { - size_t size; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(m->n_containers < RTNL_CONTAINER_DEPTH, -ERANGE); - - r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_NESTED); - if (r < 0) { - const NLTypeSystemUnion *type_system_union; - int family; - - r = message_attribute_has_type(m, &size, type, NETLINK_TYPE_UNION); - if (r < 0) - return r; - - r = sd_rtnl_message_get_family(m, &family); - if (r < 0) - return r; - - r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, &type_system_union, type); - if (r < 0) - return r; - - r = type_system_union_protocol_get_type_system(type_system_union, - &m->containers[m->n_containers + 1].type_system, - family); - if (r < 0) - return r; - } else { - r = type_system_get_type_system(m->containers[m->n_containers].type_system, - &m->containers[m->n_containers + 1].type_system, - type); - if (r < 0) - return r; - } - - r = add_rtattr(m, type | NLA_F_NESTED, NULL, size); - if (r < 0) - return r; - - m->containers[m->n_containers ++].offset = r; - - return 0; -} - -int sd_netlink_message_open_container_union(sd_netlink_message *m, unsigned short type, const char *key) { - const NLTypeSystemUnion *type_system_union; - int r; - - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - - r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, &type_system_union, type); - if (r < 0) - return r; - - r = type_system_union_get_type_system(type_system_union, - &m->containers[m->n_containers + 1].type_system, - key); - if (r < 0) - return r; - - r = sd_netlink_message_append_string(m, type_system_union->match, key); - if (r < 0) - return r; - - /* do we evere need non-null size */ - r = add_rtattr(m, type | NLA_F_NESTED, NULL, 0); - if (r < 0) - return r; - - m->containers[m->n_containers ++].offset = r; - - return 0; -} - - -int sd_netlink_message_close_container(sd_netlink_message *m) { - assert_return(m, -EINVAL); - assert_return(!m->sealed, -EPERM); - assert_return(m->n_containers > 0, -EINVAL); - - m->containers[m->n_containers].type_system = NULL; - m->n_containers --; - - return 0; -} - -static int netlink_message_read_internal(sd_netlink_message *m, unsigned short type, void **data, bool *net_byteorder) { - struct netlink_attribute *attribute; - struct rtattr *rta; - - assert_return(m, -EINVAL); - assert_return(m->sealed, -EPERM); - assert_return(data, -EINVAL); - assert(m->n_containers < RTNL_CONTAINER_DEPTH); - assert(m->containers[m->n_containers].attributes); - assert(type < m->containers[m->n_containers].n_attributes); - - attribute = &m->containers[m->n_containers].attributes[type]; - - if(!attribute->offset) - return -ENODATA; - - rta = (struct rtattr*)((uint8_t *) m->hdr + attribute->offset); - - *data = RTA_DATA(rta); - - if (net_byteorder) - *net_byteorder = attribute->net_byteorder; - - return RTA_PAYLOAD(rta); -} - -int sd_netlink_message_read_string(sd_netlink_message *m, unsigned short type, const char **data) { - int r; - void *attr_data; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_STRING); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, NULL); - if (r < 0) - return r; - else if (strnlen(attr_data, r) >= (size_t) r) - return -EIO; - - if (data) - *data = (const char *) attr_data; - - return 0; -} - -int sd_netlink_message_read_u8(sd_netlink_message *m, unsigned short type, uint8_t *data) { - int r; - void *attr_data; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U8); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, NULL); - if (r < 0) - return r; - else if ((size_t) r < sizeof(uint8_t)) - return -EIO; - - if (data) - *data = *(uint8_t *) attr_data; - - return 0; -} - -int sd_netlink_message_read_u16(sd_netlink_message *m, unsigned short type, uint16_t *data) { - void *attr_data; - bool net_byteorder; - int r; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U16); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, &net_byteorder); - if (r < 0) - return r; - else if ((size_t) r < sizeof(uint16_t)) - return -EIO; - - if (data) { - if (net_byteorder) - *data = be16toh(*(uint16_t *) attr_data); - else - *data = *(uint16_t *) attr_data; - } - - return 0; -} - -int sd_netlink_message_read_u32(sd_netlink_message *m, unsigned short type, uint32_t *data) { - void *attr_data; - bool net_byteorder; - int r; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_U32); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, &net_byteorder); - if (r < 0) - return r; - else if ((size_t)r < sizeof(uint32_t)) - return -EIO; - - if (data) { - if (net_byteorder) - *data = be32toh(*(uint32_t *) attr_data); - else - *data = *(uint32_t *) attr_data; - } - - return 0; -} - -int sd_netlink_message_read_ether_addr(sd_netlink_message *m, unsigned short type, struct ether_addr *data) { - int r; - void *attr_data; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_ETHER_ADDR); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, NULL); - if (r < 0) - return r; - else if ((size_t)r < sizeof(struct ether_addr)) - return -EIO; - - if (data) - memcpy(data, attr_data, sizeof(struct ether_addr)); - - return 0; -} - -int sd_netlink_message_read_cache_info(sd_netlink_message *m, unsigned short type, struct ifa_cacheinfo *info) { - int r; - void *attr_data; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_CACHE_INFO); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, NULL); - if (r < 0) - return r; - else if ((size_t)r < sizeof(struct ifa_cacheinfo)) - return -EIO; - - if (info) - memcpy(info, attr_data, sizeof(struct ifa_cacheinfo)); - - return 0; -} - -int sd_netlink_message_read_in_addr(sd_netlink_message *m, unsigned short type, struct in_addr *data) { - int r; - void *attr_data; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, NULL); - if (r < 0) - return r; - else if ((size_t)r < sizeof(struct in_addr)) - return -EIO; - - if (data) - memcpy(data, attr_data, sizeof(struct in_addr)); - - return 0; -} - -int sd_netlink_message_read_in6_addr(sd_netlink_message *m, unsigned short type, struct in6_addr *data) { - int r; - void *attr_data; - - assert_return(m, -EINVAL); - - r = message_attribute_has_type(m, NULL, type, NETLINK_TYPE_IN_ADDR); - if (r < 0) - return r; - - r = netlink_message_read_internal(m, type, &attr_data, NULL); - if (r < 0) - return r; - else if ((size_t)r < sizeof(struct in6_addr)) - return -EIO; - - if (data) - memcpy(data, attr_data, sizeof(struct in6_addr)); - - return 0; -} - -static int netlink_container_parse(sd_netlink_message *m, - struct netlink_container *container, - int count, - struct rtattr *rta, - unsigned int rt_len) { - _cleanup_free_ struct netlink_attribute *attributes = NULL; - - attributes = new0(struct netlink_attribute, count); - if(!attributes) - return -ENOMEM; - - for (; RTA_OK(rta, rt_len); rta = RTA_NEXT(rta, rt_len)) { - unsigned short type; - - type = RTA_TYPE(rta); - - /* if the kernel is newer than the headers we used - when building, we ignore out-of-range attributes */ - if (type >= count) - continue; - - if (attributes[type].offset) - log_debug("rtnl: message parse - overwriting repeated attribute"); - - attributes[type].offset = (uint8_t *) rta - (uint8_t *) m->hdr; - attributes[type].nested = RTA_FLAGS(rta) & NLA_F_NESTED; - attributes[type].net_byteorder = RTA_FLAGS(rta) & NLA_F_NET_BYTEORDER; - } - - container->attributes = attributes; - attributes = NULL; - container->n_attributes = count; - - return 0; -} - -int sd_netlink_message_enter_container(sd_netlink_message *m, unsigned short type_id) { - const NLType *nl_type; - const NLTypeSystem *type_system; - void *container; - uint16_t type; - size_t size; - int r; - - assert_return(m, -EINVAL); - assert_return(m->n_containers < RTNL_CONTAINER_DEPTH, -EINVAL); - - r = type_system_get_type(m->containers[m->n_containers].type_system, - &nl_type, - type_id); - if (r < 0) - return r; - - type = type_get_type(nl_type); - - if (type == NETLINK_TYPE_NESTED) { - r = type_system_get_type_system(m->containers[m->n_containers].type_system, - &type_system, - type_id); - if (r < 0) - return r; - } else if (type == NETLINK_TYPE_UNION) { - const NLTypeSystemUnion *type_system_union; - - r = type_system_get_type_system_union(m->containers[m->n_containers].type_system, - &type_system_union, - type_id); - if (r < 0) - return r; - - switch (type_system_union->match_type) { - case NL_MATCH_SIBLING: - { - const char *key; - - r = sd_netlink_message_read_string(m, type_system_union->match, &key); - if (r < 0) - return r; - - r = type_system_union_get_type_system(type_system_union, - &type_system, - key); - if (r < 0) - return r; - - break; - } - case NL_MATCH_PROTOCOL: - { - int family; - - r = sd_rtnl_message_get_family(m, &family); - if (r < 0) - return r; - - r = type_system_union_protocol_get_type_system(type_system_union, - &type_system, - family); - if (r < 0) - return r; - - break; - } - default: - assert_not_reached("sd-netlink: invalid type system union type"); - } - } else - return -EINVAL; - - r = netlink_message_read_internal(m, type_id, &container, NULL); - if (r < 0) - return r; - else - size = (size_t)r; - - m->n_containers ++; - - r = netlink_container_parse(m, - &m->containers[m->n_containers], - type_system_get_count(type_system), - container, - size); - if (r < 0) { - m->n_containers --; - return r; - } - - m->containers[m->n_containers].type_system = type_system; - - return 0; -} - -int sd_netlink_message_exit_container(sd_netlink_message *m) { - assert_return(m, -EINVAL); - assert_return(m->sealed, -EINVAL); - assert_return(m->n_containers > 0, -EINVAL); - - m->containers[m->n_containers].attributes = mfree(m->containers[m->n_containers].attributes); - m->containers[m->n_containers].type_system = NULL; - - m->n_containers --; - - return 0; -} - -uint32_t rtnl_message_get_serial(sd_netlink_message *m) { - assert(m); - assert(m->hdr); - - return m->hdr->nlmsg_seq; -} - -int sd_netlink_message_is_error(sd_netlink_message *m) { - assert_return(m, 0); - assert_return(m->hdr, 0); - - return m->hdr->nlmsg_type == NLMSG_ERROR; -} - -int sd_netlink_message_get_errno(sd_netlink_message *m) { - struct nlmsgerr *err; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - - if (!sd_netlink_message_is_error(m)) - return 0; - - err = NLMSG_DATA(m->hdr); - - return err->error; -} - -int sd_netlink_message_rewind(sd_netlink_message *m) { - const NLType *nl_type; - uint16_t type; - size_t size; - unsigned i; - int r; - - assert_return(m, -EINVAL); - - /* don't allow appending to message once parsed */ - if (!m->sealed) - rtnl_message_seal(m); - - for (i = 1; i <= m->n_containers; i++) - m->containers[i].attributes = mfree(m->containers[i].attributes); - - m->n_containers = 0; - - if (m->containers[0].attributes) - /* top-level attributes have already been parsed */ - return 0; - - assert(m->hdr); - - r = type_system_get_type(&type_system_root, &nl_type, m->hdr->nlmsg_type); - if (r < 0) - return r; - - type = type_get_type(nl_type); - size = type_get_size(nl_type); - - if (type == NETLINK_TYPE_NESTED) { - const NLTypeSystem *type_system; - - type_get_type_system(nl_type, &type_system); - - m->containers[0].type_system = type_system; - - r = netlink_container_parse(m, - &m->containers[m->n_containers], - type_system_get_count(type_system), - (struct rtattr*)((uint8_t*)NLMSG_DATA(m->hdr) + NLMSG_ALIGN(size)), - NLMSG_PAYLOAD(m->hdr, size)); - if (r < 0) - return r; - } - - return 0; -} - -void rtnl_message_seal(sd_netlink_message *m) { - assert(m); - assert(!m->sealed); - - m->sealed = true; -} - -sd_netlink_message *sd_netlink_message_next(sd_netlink_message *m) { - assert_return(m, NULL); - - return m->next; -} diff --git a/src/libsystemd/sd-netlink/netlink-socket.c b/src/libsystemd/sd-netlink/netlink-socket.c deleted file mode 100644 index 590fc53fc7..0000000000 --- a/src/libsystemd/sd-netlink/netlink-socket.c +++ /dev/null @@ -1,474 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 . -***/ - -#include -#include -#include - -#include "sd-netlink.h" - -#include "alloc-util.h" -#include "formats-util.h" -#include "missing.h" -#include "netlink-internal.h" -#include "netlink-types.h" -#include "netlink-util.h" -#include "refcnt.h" -#include "socket-util.h" -#include "util.h" - -int socket_open(int family) { - int fd; - - fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, family); - if (fd < 0) - return -errno; - - return fd; -} - -static int broadcast_groups_get(sd_netlink *nl) { - _cleanup_free_ uint32_t *groups = NULL; - socklen_t len = 0, old_len; - unsigned i, j; - int r; - - assert(nl); - assert(nl->fd >= 0); - - r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, NULL, &len); - if (r < 0) { - if (errno == ENOPROTOOPT) { - nl->broadcast_group_dont_leave = true; - return 0; - } else - return -errno; - } - - if (len == 0) - return 0; - - groups = new0(uint32_t, len); - if (!groups) - return -ENOMEM; - - old_len = len; - - r = getsockopt(nl->fd, SOL_NETLINK, NETLINK_LIST_MEMBERSHIPS, groups, &len); - if (r < 0) - return -errno; - - if (old_len != len) - return -EIO; - - r = hashmap_ensure_allocated(&nl->broadcast_group_refs, NULL); - if (r < 0) - return r; - - for (i = 0; i < len; i++) { - for (j = 0; j < sizeof(uint32_t) * 8; j ++) { - uint32_t offset; - unsigned group; - - offset = 1U << j; - - if (!(groups[i] & offset)) - continue; - - group = i * sizeof(uint32_t) * 8 + j + 1; - - r = hashmap_put(nl->broadcast_group_refs, UINT_TO_PTR(group), UINT_TO_PTR(1)); - if (r < 0) - return r; - } - } - - return 0; -} - -int socket_bind(sd_netlink *nl) { - socklen_t addrlen; - int r, one = 1; - - r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_PKTINFO, &one, sizeof(one)); - if (r < 0) - return -errno; - - addrlen = sizeof(nl->sockaddr); - - r = bind(nl->fd, &nl->sockaddr.sa, addrlen); - /* ignore EINVAL to allow opening an already bound socket */ - if (r < 0 && errno != EINVAL) - return -errno; - - r = getsockname(nl->fd, &nl->sockaddr.sa, &addrlen); - if (r < 0) - return -errno; - - r = broadcast_groups_get(nl); - if (r < 0) - return r; - - return 0; -} - -static unsigned broadcast_group_get_ref(sd_netlink *nl, unsigned group) { - assert(nl); - - return PTR_TO_UINT(hashmap_get(nl->broadcast_group_refs, UINT_TO_PTR(group))); -} - -static int broadcast_group_set_ref(sd_netlink *nl, unsigned group, unsigned n_ref) { - int r; - - assert(nl); - - r = hashmap_replace(nl->broadcast_group_refs, UINT_TO_PTR(group), UINT_TO_PTR(n_ref)); - if (r < 0) - return r; - - return 0; -} - -static int broadcast_group_join(sd_netlink *nl, unsigned group) { - int r; - - assert(nl); - assert(nl->fd >= 0); - assert(group > 0); - - r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &group, sizeof(group)); - if (r < 0) - return -errno; - - return 0; -} - -int socket_broadcast_group_ref(sd_netlink *nl, unsigned group) { - unsigned n_ref; - int r; - - assert(nl); - - n_ref = broadcast_group_get_ref(nl, group); - - n_ref ++; - - r = hashmap_ensure_allocated(&nl->broadcast_group_refs, NULL); - if (r < 0) - return r; - - r = broadcast_group_set_ref(nl, group, n_ref); - if (r < 0) - return r; - - if (n_ref > 1) - /* not yet in the group */ - return 0; - - r = broadcast_group_join(nl, group); - if (r < 0) - return r; - - return 0; -} - -static int broadcast_group_leave(sd_netlink *nl, unsigned group) { - int r; - - assert(nl); - assert(nl->fd >= 0); - assert(group > 0); - - if (nl->broadcast_group_dont_leave) - return 0; - - r = setsockopt(nl->fd, SOL_NETLINK, NETLINK_DROP_MEMBERSHIP, &group, sizeof(group)); - if (r < 0) - return -errno; - - return 0; -} - -int socket_broadcast_group_unref(sd_netlink *nl, unsigned group) { - unsigned n_ref; - int r; - - assert(nl); - - n_ref = broadcast_group_get_ref(nl, group); - - assert(n_ref > 0); - - n_ref --; - - r = broadcast_group_set_ref(nl, group, n_ref); - if (r < 0) - return r; - - if (n_ref > 0) - /* still refs left */ - return 0; - - r = broadcast_group_leave(nl, group); - if (r < 0) - return r; - - return 0; -} - -/* returns the number of bytes sent, or a negative error code */ -int socket_write_message(sd_netlink *nl, sd_netlink_message *m) { - union { - struct sockaddr sa; - struct sockaddr_nl nl; - } addr = { - .nl.nl_family = AF_NETLINK, - }; - ssize_t k; - - assert(nl); - assert(m); - assert(m->hdr); - - k = sendto(nl->fd, m->hdr, m->hdr->nlmsg_len, - 0, &addr.sa, sizeof(addr)); - if (k < 0) - return -errno; - - return k; -} - -static int socket_recv_message(int fd, struct iovec *iov, uint32_t *_group, bool peek) { - union sockaddr_union sender; - uint8_t cmsg_buffer[CMSG_SPACE(sizeof(struct nl_pktinfo))]; - struct msghdr msg = { - .msg_iov = iov, - .msg_iovlen = 1, - .msg_name = &sender, - .msg_namelen = sizeof(sender), - .msg_control = cmsg_buffer, - .msg_controllen = sizeof(cmsg_buffer), - }; - struct cmsghdr *cmsg; - uint32_t group = 0; - int r; - - assert(fd >= 0); - assert(iov); - - r = recvmsg(fd, &msg, MSG_TRUNC | (peek ? MSG_PEEK : 0)); - if (r < 0) { - /* no data */ - if (errno == ENOBUFS) - log_debug("rtnl: kernel receive buffer overrun"); - else if (errno == EAGAIN) - log_debug("rtnl: no data in socket"); - - return (errno == EAGAIN || errno == EINTR) ? 0 : -errno; - } - - if (sender.nl.nl_pid != 0) { - /* not from the kernel, ignore */ - log_debug("rtnl: ignoring message from portid %"PRIu32, sender.nl.nl_pid); - - if (peek) { - /* drop the message */ - r = recvmsg(fd, &msg, 0); - if (r < 0) - return (errno == EAGAIN || errno == EINTR) ? 0 : -errno; - } - - return 0; - } - - CMSG_FOREACH(cmsg, &msg) { - if (cmsg->cmsg_level == SOL_NETLINK && - cmsg->cmsg_type == NETLINK_PKTINFO && - cmsg->cmsg_len == CMSG_LEN(sizeof(struct nl_pktinfo))) { - struct nl_pktinfo *pktinfo = (void *)CMSG_DATA(cmsg); - - /* multi-cast group */ - group = pktinfo->group; - } - } - - if (_group) - *_group = group; - - return r; -} - -/* On success, the number of bytes received is returned and *ret points to the received message - * which has a valid header and the correct size. - * If nothing useful was received 0 is returned. - * On failure, a negative error code is returned. - */ -int socket_read_message(sd_netlink *rtnl) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *first = NULL; - struct iovec iov = {}; - uint32_t group = 0; - bool multi_part = false, done = false; - struct nlmsghdr *new_msg; - size_t len; - int r; - unsigned i = 0; - - assert(rtnl); - assert(rtnl->rbuffer); - assert(rtnl->rbuffer_allocated >= sizeof(struct nlmsghdr)); - - /* read nothing, just get the pending message size */ - r = socket_recv_message(rtnl->fd, &iov, NULL, true); - if (r <= 0) - return r; - else - len = (size_t)r; - - /* make room for the pending message */ - if (!greedy_realloc((void **)&rtnl->rbuffer, - &rtnl->rbuffer_allocated, - len, sizeof(uint8_t))) - return -ENOMEM; - - iov.iov_base = rtnl->rbuffer; - iov.iov_len = rtnl->rbuffer_allocated; - - /* read the pending message */ - r = socket_recv_message(rtnl->fd, &iov, &group, false); - if (r <= 0) - return r; - else - len = (size_t)r; - - if (len > rtnl->rbuffer_allocated) - /* message did not fit in read buffer */ - return -EIO; - - if (NLMSG_OK(rtnl->rbuffer, len) && rtnl->rbuffer->nlmsg_flags & NLM_F_MULTI) { - multi_part = true; - - for (i = 0; i < rtnl->rqueue_partial_size; i++) { - if (rtnl_message_get_serial(rtnl->rqueue_partial[i]) == - rtnl->rbuffer->nlmsg_seq) { - first = rtnl->rqueue_partial[i]; - break; - } - } - } - - for (new_msg = rtnl->rbuffer; NLMSG_OK(new_msg, len) && !done; new_msg = NLMSG_NEXT(new_msg, len)) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - const NLType *nl_type; - - if (!group && new_msg->nlmsg_pid != rtnl->sockaddr.nl.nl_pid) - /* not broadcast and not for us */ - continue; - - if (new_msg->nlmsg_type == NLMSG_NOOP) - /* silently drop noop messages */ - continue; - - if (new_msg->nlmsg_type == NLMSG_DONE) { - /* finished reading multi-part message */ - done = true; - - /* if first is not defined, put NLMSG_DONE into the receive queue. */ - if (first) - continue; - } - - /* check that we support this message type */ - r = type_system_get_type(&type_system_root, &nl_type, new_msg->nlmsg_type); - if (r < 0) { - if (r == -EOPNOTSUPP) - log_debug("sd-netlink: ignored message with unknown type: %i", - new_msg->nlmsg_type); - - continue; - } - - /* check that the size matches the message type */ - if (new_msg->nlmsg_len < NLMSG_LENGTH(type_get_size(nl_type))) { - log_debug("sd-netlink: message larger than expected, dropping"); - continue; - } - - r = message_new_empty(rtnl, &m); - if (r < 0) - return r; - - m->broadcast = !!group; - - m->hdr = memdup(new_msg, new_msg->nlmsg_len); - if (!m->hdr) - return -ENOMEM; - - /* seal and parse the top-level message */ - r = sd_netlink_message_rewind(m); - if (r < 0) - return r; - - /* push the message onto the multi-part message stack */ - if (first) - m->next = first; - first = m; - m = NULL; - } - - if (len) - log_debug("sd-netlink: discarding %zu bytes of incoming message", len); - - if (!first) - return 0; - - if (!multi_part || done) { - /* we got a complete message, push it on the read queue */ - r = rtnl_rqueue_make_room(rtnl); - if (r < 0) - return r; - - rtnl->rqueue[rtnl->rqueue_size ++] = first; - first = NULL; - - if (multi_part && (i < rtnl->rqueue_partial_size)) { - /* remove the message form the partial read queue */ - memmove(rtnl->rqueue_partial + i,rtnl->rqueue_partial + i + 1, - sizeof(sd_netlink_message*) * (rtnl->rqueue_partial_size - i - 1)); - rtnl->rqueue_partial_size --; - } - - return 1; - } else { - /* we only got a partial multi-part message, push it on the - partial read queue */ - if (i < rtnl->rqueue_partial_size) { - rtnl->rqueue_partial[i] = first; - } else { - r = rtnl_rqueue_partial_make_room(rtnl); - if (r < 0) - return r; - - rtnl->rqueue_partial[rtnl->rqueue_partial_size ++] = first; - } - first = NULL; - - return 0; - } -} diff --git a/src/libsystemd/sd-netlink/netlink-types.c b/src/libsystemd/sd-netlink/netlink-types.c deleted file mode 100644 index a5758bb516..0000000000 --- a/src/libsystemd/sd-netlink/netlink-types.c +++ /dev/null @@ -1,653 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "macro.h" -#include "missing.h" -#include "netlink-types.h" -#include "string-table.h" -#include "util.h" - -/* Maximum ARP IP target defined in kernel */ -#define BOND_MAX_ARP_TARGETS 16 - -typedef enum { - BOND_ARP_TARGETS_0, - BOND_ARP_TARGETS_1, - BOND_ARP_TARGETS_2, - BOND_ARP_TARGETS_3, - BOND_ARP_TARGETS_4, - BOND_ARP_TARGETS_5, - BOND_ARP_TARGETS_6, - BOND_ARP_TARGETS_7, - BOND_ARP_TARGETS_8, - BOND_ARP_TARGETS_9, - BOND_ARP_TARGETS_10, - BOND_ARP_TARGETS_11, - BOND_ARP_TARGETS_12, - BOND_ARP_TARGETS_13, - BOND_ARP_TARGETS_14, - BOND_ARP_TARGETS_MAX = BOND_MAX_ARP_TARGETS, -} BondArpTargets; - -struct NLType { - uint16_t type; - size_t size; - const NLTypeSystem *type_system; - const NLTypeSystemUnion *type_system_union; -}; - -struct NLTypeSystem { - uint16_t count; - const NLType *types; -}; - -static const NLTypeSystem rtnl_link_type_system; - -static const NLType empty_types[1] = { - /* fake array to avoid .types==NULL, which denotes invalid type-systems */ -}; - -static const NLTypeSystem empty_type_system = { - .count = 0, - .types = empty_types, -}; - -static const NLType rtnl_link_info_data_veth_types[] = { - [VETH_INFO_PEER] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, -}; - -static const NLType rtnl_link_info_data_ipvlan_types[] = { - [IFLA_IPVLAN_MODE] = { .type = NETLINK_TYPE_U16 }, -}; - -static const NLType rtnl_link_info_data_macvlan_types[] = { - [IFLA_MACVLAN_MODE] = { .type = NETLINK_TYPE_U32 }, - [IFLA_MACVLAN_FLAGS] = { .type = NETLINK_TYPE_U16 }, -}; - -static const NLType rtnl_link_info_data_bridge_types[] = { - [IFLA_BR_FORWARD_DELAY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BR_HELLO_TIME] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BR_MAX_AGE] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BR_AGEING_TIME] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BR_STP_STATE] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BR_PRIORITY] = { .type = NETLINK_TYPE_U16 }, -}; - -static const NLType rtnl_link_info_data_vlan_types[] = { - [IFLA_VLAN_ID] = { .type = NETLINK_TYPE_U16 }, -/* - [IFLA_VLAN_FLAGS] = { .len = sizeof(struct ifla_vlan_flags) }, - [IFLA_VLAN_EGRESS_QOS] = { .type = NETLINK_TYPE_NESTED }, - [IFLA_VLAN_INGRESS_QOS] = { .type = NETLINK_TYPE_NESTED }, -*/ - [IFLA_VLAN_PROTOCOL] = { .type = NETLINK_TYPE_U16 }, -}; - -static const NLType rtnl_link_info_data_vxlan_types[] = { - [IFLA_VXLAN_ID] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VXLAN_GROUP] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_VXLAN_LINK] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VXLAN_LOCAL] = { .type = NETLINK_TYPE_U32}, - [IFLA_VXLAN_TTL] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_TOS] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_LEARNING] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_AGEING] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VXLAN_LIMIT] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VXLAN_PORT_RANGE] = { .type = NETLINK_TYPE_U32}, - [IFLA_VXLAN_PROXY] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_RSC] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_L2MISS] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_L3MISS] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_PORT] = { .type = NETLINK_TYPE_U16 }, - [IFLA_VXLAN_GROUP6] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_VXLAN_LOCAL6] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_VXLAN_UDP_CSUM] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_UDP_ZERO_CSUM6_TX] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_UDP_ZERO_CSUM6_RX] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_REMCSUM_TX] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_REMCSUM_RX] = { .type = NETLINK_TYPE_U8 }, - [IFLA_VXLAN_GBP] = { .type = NETLINK_TYPE_FLAG }, - [IFLA_VXLAN_REMCSUM_NOPARTIAL] = { .type = NETLINK_TYPE_FLAG }, -}; - -static const NLType rtnl_bond_arp_target_types[] = { - [BOND_ARP_TARGETS_0] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_1] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_2] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_3] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_4] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_5] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_6] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_7] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_8] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_9] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_10] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_11] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_12] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_13] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_14] = { .type = NETLINK_TYPE_U32 }, - [BOND_ARP_TARGETS_MAX] = { .type = NETLINK_TYPE_U32 }, -}; - -static const NLTypeSystem rtnl_bond_arp_type_system = { - .count = ELEMENTSOF(rtnl_bond_arp_target_types), - .types = rtnl_bond_arp_target_types, -}; - -static const NLType rtnl_link_info_data_bond_types[] = { - [IFLA_BOND_MODE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_ACTIVE_SLAVE] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_MIIMON] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_UPDELAY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_DOWNDELAY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_USE_CARRIER] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_ARP_INTERVAL] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_ARP_IP_TARGET] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_bond_arp_type_system }, - [IFLA_BOND_ARP_VALIDATE] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_ARP_ALL_TARGETS] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_PRIMARY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_PRIMARY_RESELECT] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_FAIL_OVER_MAC] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_XMIT_HASH_POLICY] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_RESEND_IGMP] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_NUM_PEER_NOTIF] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_ALL_SLAVES_ACTIVE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_MIN_LINKS] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_LP_INTERVAL] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_PACKETS_PER_SLAVE] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BOND_AD_LACP_RATE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_AD_SELECT] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BOND_AD_INFO] = { .type = NETLINK_TYPE_NESTED }, -}; - -static const NLType rtnl_link_info_data_iptun_types[] = { - [IFLA_IPTUN_LINK] = { .type = NETLINK_TYPE_U32 }, - [IFLA_IPTUN_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_IPTUN_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_IPTUN_TTL] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_TOS] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_PMTUDISC] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_FLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_IPTUN_PROTO] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_6RD_PREFIX] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_IPTUN_6RD_RELAY_PREFIX] = { .type = NETLINK_TYPE_U32 }, - [IFLA_IPTUN_6RD_PREFIXLEN] = { .type = NETLINK_TYPE_U16 }, - [IFLA_IPTUN_6RD_RELAY_PREFIXLEN] = { .type = NETLINK_TYPE_U16 }, - [IFLA_IPTUN_ENCAP_TYPE] = { .type = NETLINK_TYPE_U16 }, - [IFLA_IPTUN_ENCAP_FLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_IPTUN_ENCAP_SPORT] = { .type = NETLINK_TYPE_U16 }, - [IFLA_IPTUN_ENCAP_DPORT] = { .type = NETLINK_TYPE_U16 }, -}; - -static const NLType rtnl_link_info_data_ipgre_types[] = { - [IFLA_GRE_LINK] = { .type = NETLINK_TYPE_U32 }, - [IFLA_GRE_IFLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_GRE_OFLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_GRE_IKEY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_GRE_OKEY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_GRE_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_GRE_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_GRE_TTL] = { .type = NETLINK_TYPE_U8 }, - [IFLA_GRE_TOS] = { .type = NETLINK_TYPE_U8 }, - [IFLA_GRE_PMTUDISC] = { .type = NETLINK_TYPE_U8 }, - [IFLA_GRE_FLOWINFO] = { .type = NETLINK_TYPE_U32 }, - [IFLA_GRE_FLAGS] = { .type = NETLINK_TYPE_U32 }, - [IFLA_GRE_ENCAP_TYPE] = { .type = NETLINK_TYPE_U16 }, - [IFLA_GRE_ENCAP_FLAGS] = { .type = NETLINK_TYPE_U16 }, - [IFLA_GRE_ENCAP_SPORT] = { .type = NETLINK_TYPE_U16 }, - [IFLA_GRE_ENCAP_DPORT] = { .type = NETLINK_TYPE_U16 }, -}; - -static const NLType rtnl_link_info_data_ipvti_types[] = { - [IFLA_VTI_LINK] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VTI_IKEY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VTI_OKEY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_VTI_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_VTI_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, -}; - -static const NLType rtnl_link_info_data_ip6tnl_types[] = { - [IFLA_IPTUN_LINK] = { .type = NETLINK_TYPE_U32 }, - [IFLA_IPTUN_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_IPTUN_REMOTE] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_IPTUN_TTL] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_FLAGS] = { .type = NETLINK_TYPE_U32 }, - [IFLA_IPTUN_PROTO] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_ENCAP_LIMIT] = { .type = NETLINK_TYPE_U8 }, - [IFLA_IPTUN_FLOWINFO] = { .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", - [NL_UNION_LINK_INFO_DATA_BRIDGE] = "bridge", - [NL_UNION_LINK_INFO_DATA_VLAN] = "vlan", - [NL_UNION_LINK_INFO_DATA_VETH] = "veth", - [NL_UNION_LINK_INFO_DATA_DUMMY] = "dummy", - [NL_UNION_LINK_INFO_DATA_MACVLAN] = "macvlan", - [NL_UNION_LINK_INFO_DATA_MACVTAP] = "macvtap", - [NL_UNION_LINK_INFO_DATA_IPVLAN] = "ipvlan", - [NL_UNION_LINK_INFO_DATA_VXLAN] = "vxlan", - [NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = "ipip", - [NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = "gre", - [NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL] = "gretap", - [NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = "ip6gre", - [NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL] = "ip6gretap", - [NL_UNION_LINK_INFO_DATA_SIT_TUNNEL] = "sit", - [NL_UNION_LINK_INFO_DATA_VTI_TUNNEL] = "vti", - [NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = "vti6", - [NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = "ip6tnl", -}; - -DEFINE_STRING_TABLE_LOOKUP(nl_union_link_info_data, NLUnionLinkInfoData); - -static const NLTypeSystem rtnl_link_info_data_type_systems[] = { - [NL_UNION_LINK_INFO_DATA_BOND] = { .count = ELEMENTSOF(rtnl_link_info_data_bond_types), - .types = rtnl_link_info_data_bond_types }, - [NL_UNION_LINK_INFO_DATA_BRIDGE] = { .count = ELEMENTSOF(rtnl_link_info_data_bridge_types), - .types = rtnl_link_info_data_bridge_types }, - [NL_UNION_LINK_INFO_DATA_VLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vlan_types), - .types = rtnl_link_info_data_vlan_types }, - [NL_UNION_LINK_INFO_DATA_VETH] = { .count = ELEMENTSOF(rtnl_link_info_data_veth_types), - .types = rtnl_link_info_data_veth_types }, - [NL_UNION_LINK_INFO_DATA_MACVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), - .types = rtnl_link_info_data_macvlan_types }, - [NL_UNION_LINK_INFO_DATA_MACVTAP] = { .count = ELEMENTSOF(rtnl_link_info_data_macvlan_types), - .types = rtnl_link_info_data_macvlan_types }, - [NL_UNION_LINK_INFO_DATA_IPVLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvlan_types), - .types = rtnl_link_info_data_ipvlan_types }, - [NL_UNION_LINK_INFO_DATA_VXLAN] = { .count = ELEMENTSOF(rtnl_link_info_data_vxlan_types), - .types = rtnl_link_info_data_vxlan_types }, - [NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), - .types = rtnl_link_info_data_iptun_types }, - [NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), - .types = rtnl_link_info_data_ipgre_types }, - [NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), - .types = rtnl_link_info_data_ipgre_types }, - [NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), - .types = rtnl_link_info_data_ipgre_types }, - [NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipgre_types), - .types = rtnl_link_info_data_ipgre_types }, - [NL_UNION_LINK_INFO_DATA_SIT_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_iptun_types), - .types = rtnl_link_info_data_iptun_types }, - [NL_UNION_LINK_INFO_DATA_VTI_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), - .types = rtnl_link_info_data_ipvti_types }, - [NL_UNION_LINK_INFO_DATA_VTI6_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ipvti_types), - .types = rtnl_link_info_data_ipvti_types }, - [NL_UNION_LINK_INFO_DATA_IP6TNL_TUNNEL] = { .count = ELEMENTSOF(rtnl_link_info_data_ip6tnl_types), - .types = rtnl_link_info_data_ip6tnl_types }, - -}; - -static const NLTypeSystemUnion rtnl_link_info_data_type_system_union = { - .num = _NL_UNION_LINK_INFO_DATA_MAX, - .lookup = nl_union_link_info_data_from_string, - .type_systems = rtnl_link_info_data_type_systems, - .match_type = NL_MATCH_SIBLING, - .match = IFLA_INFO_KIND, -}; - -static const NLType rtnl_link_info_types[] = { - [IFLA_INFO_KIND] = { .type = NETLINK_TYPE_STRING }, - [IFLA_INFO_DATA] = { .type = NETLINK_TYPE_UNION, .type_system_union = &rtnl_link_info_data_type_system_union}, -/* - [IFLA_INFO_XSTATS], - [IFLA_INFO_SLAVE_KIND] = { .type = NETLINK_TYPE_STRING }, - [IFLA_INFO_SLAVE_DATA] = { .type = NETLINK_TYPE_NESTED }, -*/ -}; - -static const NLTypeSystem rtnl_link_info_type_system = { - .count = ELEMENTSOF(rtnl_link_info_types), - .types = rtnl_link_info_types, -}; - -static const struct NLType rtnl_prot_info_bridge_port_types[] = { - [IFLA_BRPORT_STATE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_COST] = { .type = NETLINK_TYPE_U32 }, - [IFLA_BRPORT_PRIORITY] = { .type = NETLINK_TYPE_U16 }, - [IFLA_BRPORT_MODE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_GUARD] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_PROTECT] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_FAST_LEAVE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_LEARNING] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_UNICAST_FLOOD] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_PROXYARP] = { .type = NETLINK_TYPE_U8 }, - [IFLA_BRPORT_LEARNING_SYNC] = { .type = NETLINK_TYPE_U8 }, -}; - -static const NLTypeSystem rtnl_prot_info_type_systems[] = { - [AF_BRIDGE] = { .count = ELEMENTSOF(rtnl_prot_info_bridge_port_types), - .types = rtnl_prot_info_bridge_port_types }, -}; - -static const NLTypeSystemUnion rtnl_prot_info_type_system_union = { - .num = AF_MAX, - .type_systems = rtnl_prot_info_type_systems, - .match_type = NL_MATCH_PROTOCOL, -}; - -static const struct NLType rtnl_af_spec_inet6_types[] = { - [IFLA_INET6_FLAGS] = { .type = NETLINK_TYPE_U32 }, -/* - IFLA_INET6_CONF, - IFLA_INET6_STATS, - IFLA_INET6_MCAST, - IFLA_INET6_CACHEINFO, - IFLA_INET6_ICMP6STATS, -*/ - [IFLA_INET6_TOKEN] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFLA_INET6_ADDR_GEN_MODE] = { .type = NETLINK_TYPE_U8 }, -}; - -static const NLTypeSystem rtnl_af_spec_inet6_type_system = { - .count = ELEMENTSOF(rtnl_af_spec_inet6_types), - .types = rtnl_af_spec_inet6_types, -}; - -static const NLType rtnl_af_spec_types[] = { - [AF_INET6] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_af_spec_inet6_type_system }, -}; - -static const NLTypeSystem rtnl_af_spec_type_system = { - .count = ELEMENTSOF(rtnl_af_spec_types), - .types = rtnl_af_spec_types, -}; - -static const NLType rtnl_link_types[] = { - [IFLA_ADDRESS] = { .type = NETLINK_TYPE_ETHER_ADDR }, - [IFLA_BROADCAST] = { .type = NETLINK_TYPE_ETHER_ADDR }, - [IFLA_IFNAME] = { .type = NETLINK_TYPE_STRING, .size = IFNAMSIZ - 1 }, - [IFLA_MTU] = { .type = NETLINK_TYPE_U32 }, - [IFLA_LINK] = { .type = NETLINK_TYPE_U32 }, -/* - [IFLA_QDISC], - [IFLA_STATS], - [IFLA_COST], - [IFLA_PRIORITY], -*/ - [IFLA_MASTER] = { .type = NETLINK_TYPE_U32 }, -/* - [IFLA_WIRELESS], -*/ - [IFLA_PROTINFO] = { .type = NETLINK_TYPE_UNION, .type_system_union = &rtnl_prot_info_type_system_union }, - [IFLA_TXQLEN] = { .type = NETLINK_TYPE_U32 }, -/* - [IFLA_MAP] = { .len = sizeof(struct rtnl_link_ifmap) }, -*/ - [IFLA_WEIGHT] = { .type = NETLINK_TYPE_U32 }, - [IFLA_OPERSTATE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_LINKMODE] = { .type = NETLINK_TYPE_U8 }, - [IFLA_LINKINFO] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_info_type_system }, - [IFLA_NET_NS_PID] = { .type = NETLINK_TYPE_U32 }, - [IFLA_IFALIAS] = { .type = NETLINK_TYPE_STRING, .size = IFALIASZ - 1 }, -/* - [IFLA_NUM_VF], - [IFLA_VFINFO_LIST] = {. type = NETLINK_TYPE_NESTED, }, - [IFLA_STATS64], - [IFLA_VF_PORTS] = { .type = NETLINK_TYPE_NESTED }, - [IFLA_PORT_SELF] = { .type = NETLINK_TYPE_NESTED }, -*/ - [IFLA_AF_SPEC] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_af_spec_type_system }, -/* - [IFLA_VF_PORTS], - [IFLA_PORT_SELF], - [IFLA_AF_SPEC], -*/ - [IFLA_GROUP] = { .type = NETLINK_TYPE_U32 }, - [IFLA_NET_NS_FD] = { .type = NETLINK_TYPE_U32 }, - [IFLA_EXT_MASK] = { .type = NETLINK_TYPE_U32 }, - [IFLA_PROMISCUITY] = { .type = NETLINK_TYPE_U32 }, - [IFLA_NUM_TX_QUEUES] = { .type = NETLINK_TYPE_U32 }, - [IFLA_NUM_RX_QUEUES] = { .type = NETLINK_TYPE_U32 }, - [IFLA_CARRIER] = { .type = NETLINK_TYPE_U8 }, -/* - [IFLA_PHYS_PORT_ID] = { .type = NETLINK_TYPE_BINARY, .len = MAX_PHYS_PORT_ID_LEN }, -*/ -}; - -static const NLTypeSystem rtnl_link_type_system = { - .count = ELEMENTSOF(rtnl_link_types), - .types = rtnl_link_types, -}; - -/* IFA_FLAGS was defined in kernel 3.14, but we still support older - * kernels where IFA_MAX is lower. */ -static const NLType rtnl_address_types[] = { - [IFA_ADDRESS] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFA_LOCAL] = { .type = NETLINK_TYPE_IN_ADDR }, - [IFA_LABEL] = { .type = NETLINK_TYPE_STRING, .size = IFNAMSIZ - 1 }, - [IFA_BROADCAST] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ - [IFA_CACHEINFO] = { .type = NETLINK_TYPE_CACHE_INFO, .size = sizeof(struct ifa_cacheinfo) }, -/* - [IFA_ANYCAST], - [IFA_MULTICAST], -*/ - [IFA_FLAGS] = { .type = NETLINK_TYPE_U32 }, -}; - -static const NLTypeSystem rtnl_address_type_system = { - .count = ELEMENTSOF(rtnl_address_types), - .types = rtnl_address_types, -}; - -static const NLType rtnl_route_types[] = { - [RTA_DST] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ - [RTA_SRC] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ - [RTA_IIF] = { .type = NETLINK_TYPE_U32 }, - [RTA_OIF] = { .type = NETLINK_TYPE_U32 }, - [RTA_GATEWAY] = { .type = NETLINK_TYPE_IN_ADDR }, - [RTA_PRIORITY] = { .type = NETLINK_TYPE_U32 }, - [RTA_PREFSRC] = { .type = NETLINK_TYPE_IN_ADDR }, /* 6? */ -/* - [RTA_METRICS] = { .type = NETLINK_TYPE_NESTED }, - [RTA_MULTIPATH] = { .len = sizeof(struct rtnexthop) }, -*/ - [RTA_FLOW] = { .type = NETLINK_TYPE_U32 }, /* 6? */ -/* - RTA_CACHEINFO, - RTA_TABLE, - RTA_MARK, - RTA_MFC_STATS, - RTA_VIA, - RTA_NEWDST, -*/ - [RTA_PREF] = { .type = NETLINK_TYPE_U8 }, - -}; - -static const NLTypeSystem rtnl_route_type_system = { - .count = ELEMENTSOF(rtnl_route_types), - .types = rtnl_route_types, -}; - -static const NLType rtnl_neigh_types[] = { - [NDA_DST] = { .type = NETLINK_TYPE_IN_ADDR }, - [NDA_LLADDR] = { .type = NETLINK_TYPE_ETHER_ADDR }, - [NDA_CACHEINFO] = { .type = NETLINK_TYPE_CACHE_INFO, .size = sizeof(struct nda_cacheinfo) }, - [NDA_PROBES] = { .type = NETLINK_TYPE_U32 }, - [NDA_VLAN] = { .type = NETLINK_TYPE_U16 }, - [NDA_PORT] = { .type = NETLINK_TYPE_U16 }, - [NDA_VNI] = { .type = NETLINK_TYPE_U32 }, - [NDA_IFINDEX] = { .type = NETLINK_TYPE_U32 }, -}; - -static const NLTypeSystem rtnl_neigh_type_system = { - .count = ELEMENTSOF(rtnl_neigh_types), - .types = rtnl_neigh_types, -}; - -static const NLType rtnl_types[] = { - [NLMSG_DONE] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = 0 }, - [NLMSG_ERROR] = { .type = NETLINK_TYPE_NESTED, .type_system = &empty_type_system, .size = sizeof(struct nlmsgerr) }, - [RTM_NEWLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, - [RTM_DELLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, - [RTM_GETLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, - [RTM_SETLINK] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_link_type_system, .size = sizeof(struct ifinfomsg) }, - [RTM_NEWADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, - [RTM_DELADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, - [RTM_GETADDR] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_address_type_system, .size = sizeof(struct ifaddrmsg) }, - [RTM_NEWROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, - [RTM_DELROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, - [RTM_GETROUTE] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_route_type_system, .size = sizeof(struct rtmsg) }, - [RTM_NEWNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, - [RTM_DELNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, - [RTM_GETNEIGH] = { .type = NETLINK_TYPE_NESTED, .type_system = &rtnl_neigh_type_system, .size = sizeof(struct ndmsg) }, -}; - -const NLTypeSystem type_system_root = { - .count = ELEMENTSOF(rtnl_types), - .types = rtnl_types, -}; - -uint16_t type_get_type(const NLType *type) { - assert(type); - return type->type; -} - -size_t type_get_size(const NLType *type) { - assert(type); - return type->size; -} - -void type_get_type_system(const NLType *nl_type, const NLTypeSystem **ret) { - assert(nl_type); - assert(ret); - assert(nl_type->type == NETLINK_TYPE_NESTED); - assert(nl_type->type_system); - - *ret = nl_type->type_system; -} - -void type_get_type_system_union(const NLType *nl_type, const NLTypeSystemUnion **ret) { - assert(nl_type); - assert(ret); - assert(nl_type->type == NETLINK_TYPE_UNION); - assert(nl_type->type_system_union); - - *ret = nl_type->type_system_union; -} - -uint16_t type_system_get_count(const NLTypeSystem *type_system) { - assert(type_system); - return type_system->count; -} - -int type_system_get_type(const NLTypeSystem *type_system, const NLType **ret, uint16_t type) { - const NLType *nl_type; - - assert(ret); - assert(type_system); - assert(type_system->types); - - if (type >= type_system->count) - return -EOPNOTSUPP; - - nl_type = &type_system->types[type]; - - if (nl_type->type == NETLINK_TYPE_UNSPEC) - return -EOPNOTSUPP; - - *ret = nl_type; - - return 0; -} - -int type_system_get_type_system(const NLTypeSystem *type_system, const NLTypeSystem **ret, uint16_t type) { - const NLType *nl_type; - int r; - - assert(ret); - - r = type_system_get_type(type_system, &nl_type, type); - if (r < 0) - return r; - - type_get_type_system(nl_type, ret); - return 0; -} - -int type_system_get_type_system_union(const NLTypeSystem *type_system, const NLTypeSystemUnion **ret, uint16_t type) { - const NLType *nl_type; - int r; - - assert(ret); - - r = type_system_get_type(type_system, &nl_type, type); - if (r < 0) - return r; - - type_get_type_system_union(nl_type, ret); - return 0; -} - -int type_system_union_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, const char *key) { - int type; - - assert(type_system_union); - assert(type_system_union->match_type == NL_MATCH_SIBLING); - assert(type_system_union->lookup); - assert(type_system_union->type_systems); - assert(ret); - assert(key); - - type = type_system_union->lookup(key); - if (type < 0) - return -EOPNOTSUPP; - - assert(type < type_system_union->num); - - *ret = &type_system_union->type_systems[type]; - - return 0; -} - -int type_system_union_protocol_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, uint16_t protocol) { - const NLTypeSystem *type_system; - - assert(type_system_union); - assert(type_system_union->type_systems); - assert(type_system_union->match_type == NL_MATCH_PROTOCOL); - assert(ret); - - if (protocol >= type_system_union->num) - return -EOPNOTSUPP; - - type_system = &type_system_union->type_systems[protocol]; - if (!type_system->types) - return -EOPNOTSUPP; - - *ret = type_system; - - return 0; -} diff --git a/src/libsystemd/sd-netlink/netlink-types.h b/src/libsystemd/sd-netlink/netlink-types.h deleted file mode 100644 index ecb20bfcdc..0000000000 --- a/src/libsystemd/sd-netlink/netlink-types.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include "macro.h" - -enum { - NETLINK_TYPE_UNSPEC, - NETLINK_TYPE_U8, /* NLA_U8 */ - NETLINK_TYPE_U16, /* NLA_U16 */ - NETLINK_TYPE_U32, /* NLA_U32 */ - NETLINK_TYPE_U64, /* NLA_U64 */ - NETLINK_TYPE_STRING, /* NLA_STRING */ - NETLINK_TYPE_FLAG, /* NLA_FLAG */ - NETLINK_TYPE_IN_ADDR, - NETLINK_TYPE_ETHER_ADDR, - NETLINK_TYPE_CACHE_INFO, - NETLINK_TYPE_NESTED, /* NLA_NESTED */ - NETLINK_TYPE_UNION, -}; - -typedef enum NLMatchType { - NL_MATCH_SIBLING, - NL_MATCH_PROTOCOL, -} NLMatchType; - -typedef struct NLTypeSystemUnion NLTypeSystemUnion; -typedef struct NLTypeSystem NLTypeSystem; -typedef struct NLType NLType; - -struct NLTypeSystemUnion { - int num; - NLMatchType match_type; - uint16_t match; - int (*lookup)(const char *); - const NLTypeSystem *type_systems; -}; - -extern const NLTypeSystem type_system_root; - -uint16_t type_get_type(const NLType *type); -size_t type_get_size(const NLType *type); -void type_get_type_system(const NLType *type, const NLTypeSystem **ret); -void type_get_type_system_union(const NLType *type, const NLTypeSystemUnion **ret); - -uint16_t type_system_get_count(const NLTypeSystem *type_system); -int type_system_get_type(const NLTypeSystem *type_system, const NLType **ret, uint16_t type); -int type_system_get_type_system(const NLTypeSystem *type_system, const NLTypeSystem **ret, uint16_t type); -int type_system_get_type_system_union(const NLTypeSystem *type_system, const NLTypeSystemUnion **ret, uint16_t type); -int type_system_union_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, const char *key); -int type_system_union_protocol_get_type_system(const NLTypeSystemUnion *type_system_union, const NLTypeSystem **ret, uint16_t protocol); - -typedef enum NLUnionLinkInfoData { - NL_UNION_LINK_INFO_DATA_BOND, - NL_UNION_LINK_INFO_DATA_BRIDGE, - NL_UNION_LINK_INFO_DATA_VLAN, - NL_UNION_LINK_INFO_DATA_VETH, - NL_UNION_LINK_INFO_DATA_DUMMY, - NL_UNION_LINK_INFO_DATA_MACVLAN, - NL_UNION_LINK_INFO_DATA_MACVTAP, - NL_UNION_LINK_INFO_DATA_IPVLAN, - NL_UNION_LINK_INFO_DATA_VXLAN, - NL_UNION_LINK_INFO_DATA_IPIP_TUNNEL, - NL_UNION_LINK_INFO_DATA_IPGRE_TUNNEL, - NL_UNION_LINK_INFO_DATA_IPGRETAP_TUNNEL, - NL_UNION_LINK_INFO_DATA_IP6GRE_TUNNEL, - NL_UNION_LINK_INFO_DATA_IP6GRETAP_TUNNEL, - NL_UNION_LINK_INFO_DATA_SIT_TUNNEL, - 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_MAX, - _NL_UNION_LINK_INFO_DATA_INVALID = -1 -} NLUnionLinkInfoData; - -const char *nl_union_link_info_data_to_string(NLUnionLinkInfoData p) _const_; -NLUnionLinkInfoData nl_union_link_info_data_from_string(const char *p) _pure_; diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c deleted file mode 100644 index 73b9ac0258..0000000000 --- a/src/libsystemd/sd-netlink/netlink-util.c +++ /dev/null @@ -1,170 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2013 Tom Gundersen - - 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 . -***/ - -#include "sd-netlink.h" - -#include "netlink-internal.h" -#include "netlink-util.h" - -int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; - int r; - - assert(rtnl); - assert(ifindex > 0); - assert(name); - - if (!*rtnl) { - r = sd_netlink_open(rtnl); - if (r < 0) - return r; - } - - r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); - if (r < 0) - return r; - - r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); - if (r < 0) - return r; - - r = sd_netlink_call(*rtnl, message, 0, NULL); - if (r < 0) - return r; - - return 0; -} - -int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias, - const struct ether_addr *mac, unsigned mtu) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; - int r; - - assert(rtnl); - assert(ifindex > 0); - - if (!alias && !mac && mtu == 0) - return 0; - - if (!*rtnl) { - r = sd_netlink_open(rtnl); - if (r < 0) - return r; - } - - r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); - if (r < 0) - return r; - - if (alias) { - r = sd_netlink_message_append_string(message, IFLA_IFALIAS, alias); - if (r < 0) - return r; - } - - if (mac) { - r = sd_netlink_message_append_ether_addr(message, IFLA_ADDRESS, mac); - if (r < 0) - return r; - } - - if (mtu > 0) { - r = sd_netlink_message_append_u32(message, IFLA_MTU, mtu); - if (r < 0) - return r; - } - - r = sd_netlink_call(*rtnl, message, 0, NULL); - if (r < 0) - return r; - - return 0; -} - -int rtnl_message_new_synthetic_error(int error, uint32_t serial, sd_netlink_message **ret) { - struct nlmsgerr *err; - int r; - - assert(error <= 0); - - r = message_new(NULL, ret, NLMSG_ERROR); - if (r < 0) - return r; - - (*ret)->hdr->nlmsg_seq = serial; - - err = NLMSG_DATA((*ret)->hdr); - - err->error = error; - - return 0; -} - -bool rtnl_message_type_is_neigh(uint16_t type) { - switch (type) { - case RTM_NEWNEIGH: - case RTM_GETNEIGH: - case RTM_DELNEIGH: - return true; - default: - return false; - } -} - -bool rtnl_message_type_is_route(uint16_t type) { - switch (type) { - case RTM_NEWROUTE: - case RTM_GETROUTE: - case RTM_DELROUTE: - return true; - default: - return false; - } -} - -bool rtnl_message_type_is_link(uint16_t type) { - switch (type) { - case RTM_NEWLINK: - case RTM_SETLINK: - case RTM_GETLINK: - case RTM_DELLINK: - return true; - default: - return false; - } -} - -bool rtnl_message_type_is_addr(uint16_t type) { - switch (type) { - case RTM_NEWADDR: - case RTM_GETADDR: - case RTM_DELADDR: - return true; - default: - return false; - } -} - -int rtnl_log_parse_error(int r) { - return log_error_errno(r, "Failed to parse netlink message: %m"); -} - -int rtnl_log_create_error(int r) { - return log_error_errno(r, "Failed to create netlink message: %m"); -} diff --git a/src/libsystemd/sd-netlink/netlink-util.h b/src/libsystemd/sd-netlink/netlink-util.h deleted file mode 100644 index f49bf4eaa6..0000000000 --- a/src/libsystemd/sd-netlink/netlink-util.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright (C) 2013 Tom Gundersen - - 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 . -***/ - -#include "sd-netlink.h" - -#include "util.h" - -int rtnl_message_new_synthetic_error(int error, uint32_t serial, sd_netlink_message **ret); -uint32_t rtnl_message_get_serial(sd_netlink_message *m); -void rtnl_message_seal(sd_netlink_message *m); - -bool rtnl_message_type_is_link(uint16_t type); -bool rtnl_message_type_is_addr(uint16_t type); -bool rtnl_message_type_is_route(uint16_t type); -bool rtnl_message_type_is_neigh(uint16_t type); - -int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name); -int rtnl_set_link_properties(sd_netlink **rtnl, int ifindex, const char *alias, const struct ether_addr *mac, unsigned mtu); - -int rtnl_log_parse_error(int r); -int rtnl_log_create_error(int r); diff --git a/src/libsystemd/sd-netlink/rtnl-message.c b/src/libsystemd/sd-netlink/rtnl-message.c deleted file mode 100644 index 090552f576..0000000000 --- a/src/libsystemd/sd-netlink/rtnl-message.c +++ /dev/null @@ -1,675 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 . -***/ - -#include -#include -#include - -#include "sd-netlink.h" - -#include "formats-util.h" -#include "missing.h" -#include "netlink-internal.h" -#include "netlink-types.h" -#include "netlink-util.h" -#include "refcnt.h" -#include "socket-util.h" -#include "util.h" - -int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - if ((rtm->rtm_family == AF_INET && prefixlen > 32) || - (rtm->rtm_family == AF_INET6 && prefixlen > 128)) - return -ERANGE; - - rtm->rtm_dst_len = prefixlen; - - return 0; -} - -int sd_rtnl_message_route_set_src_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - if ((rtm->rtm_family == AF_INET && prefixlen > 32) || - (rtm->rtm_family == AF_INET6 && prefixlen > 128)) - return -ERANGE; - - rtm->rtm_src_len = prefixlen; - - return 0; -} - -int sd_rtnl_message_route_set_scope(sd_netlink_message *m, unsigned char scope) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - rtm->rtm_scope = scope; - - return 0; -} - -int sd_rtnl_message_route_set_flags(sd_netlink_message *m, unsigned flags) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - rtm->rtm_flags = flags; - - return 0; -} - -int sd_rtnl_message_route_get_flags(sd_netlink_message *m, unsigned *flags) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(flags, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *flags = rtm->rtm_flags; - - return 0; -} - -int sd_rtnl_message_route_get_family(sd_netlink_message *m, int *family) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(family, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *family = rtm->rtm_family; - - return 0; -} - -int sd_rtnl_message_route_get_protocol(sd_netlink_message *m, unsigned char *protocol) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(protocol, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *protocol = rtm->rtm_protocol; - - return 0; -} - -int sd_rtnl_message_route_get_scope(sd_netlink_message *m, unsigned char *scope) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(scope, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *scope = rtm->rtm_scope; - - return 0; -} - -int sd_rtnl_message_route_get_tos(sd_netlink_message *m, unsigned char *tos) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(tos, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *tos = rtm->rtm_tos; - - return 0; -} - -int sd_rtnl_message_route_get_table(sd_netlink_message *m, unsigned char *table) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(table, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *table = rtm->rtm_table; - - return 0; -} - -int sd_rtnl_message_route_get_dst_prefixlen(sd_netlink_message *m, unsigned char *dst_len) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(dst_len, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *dst_len = rtm->rtm_dst_len; - - return 0; -} - -int sd_rtnl_message_route_get_src_prefixlen(sd_netlink_message *m, unsigned char *src_len) { - struct rtmsg *rtm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_route(m->hdr->nlmsg_type), -EINVAL); - assert_return(src_len, -EINVAL); - - rtm = NLMSG_DATA(m->hdr); - - *src_len = rtm->rtm_src_len; - - return 0; -} - -int sd_rtnl_message_new_route(sd_netlink *rtnl, sd_netlink_message **ret, - uint16_t nlmsg_type, int rtm_family, - unsigned char rtm_protocol) { - struct rtmsg *rtm; - int r; - - assert_return(rtnl_message_type_is_route(nlmsg_type), -EINVAL); - assert_return((nlmsg_type == RTM_GETROUTE && rtm_family == AF_UNSPEC) || - rtm_family == AF_INET || rtm_family == AF_INET6, -EINVAL); - assert_return(ret, -EINVAL); - - r = message_new(rtnl, ret, nlmsg_type); - if (r < 0) - return r; - - if (nlmsg_type == RTM_NEWROUTE) - (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; - - rtm = NLMSG_DATA((*ret)->hdr); - - rtm->rtm_family = rtm_family; - rtm->rtm_scope = RT_SCOPE_UNIVERSE; - rtm->rtm_type = RTN_UNICAST; - rtm->rtm_table = RT_TABLE_MAIN; - rtm->rtm_protocol = rtm_protocol; - - return 0; -} - -int sd_rtnl_message_neigh_set_flags(sd_netlink_message *m, uint8_t flags) { - struct ndmsg *ndm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); - - ndm = NLMSG_DATA(m->hdr); - ndm->ndm_flags |= flags; - - return 0; -} - -int sd_rtnl_message_neigh_set_state(sd_netlink_message *m, uint16_t state) { - struct ndmsg *ndm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); - - ndm = NLMSG_DATA(m->hdr); - ndm->ndm_state |= state; - - return 0; -} - -int sd_rtnl_message_neigh_get_flags(sd_netlink_message *m, uint8_t *flags) { - struct ndmsg *ndm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); - - ndm = NLMSG_DATA(m->hdr); - *flags = ndm->ndm_flags; - - return 0; -} - -int sd_rtnl_message_neigh_get_state(sd_netlink_message *m, uint16_t *state) { - struct ndmsg *ndm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); - - ndm = NLMSG_DATA(m->hdr); - *state = ndm->ndm_state; - - return 0; -} - -int sd_rtnl_message_neigh_get_family(sd_netlink_message *m, int *family) { - struct ndmsg *ndm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); - assert_return(family, -EINVAL); - - ndm = NLMSG_DATA(m->hdr); - - *family = ndm->ndm_family; - - return 0; -} - -int sd_rtnl_message_neigh_get_ifindex(sd_netlink_message *m, int *index) { - struct ndmsg *ndm; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_neigh(m->hdr->nlmsg_type), -EINVAL); - assert_return(index, -EINVAL); - - ndm = NLMSG_DATA(m->hdr); - - *index = ndm->ndm_ifindex; - - return 0; -} - -int sd_rtnl_message_new_neigh(sd_netlink *rtnl, sd_netlink_message **ret, uint16_t nlmsg_type, int index, int ndm_family) { - struct ndmsg *ndm; - int r; - - assert_return(rtnl_message_type_is_neigh(nlmsg_type), -EINVAL); - assert_return(ndm_family == AF_INET || - ndm_family == AF_INET6 || - ndm_family == PF_BRIDGE, -EINVAL); - assert_return(ret, -EINVAL); - - r = message_new(rtnl, ret, nlmsg_type); - if (r < 0) - return r; - - if (nlmsg_type == RTM_NEWNEIGH) - (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_APPEND; - - ndm = NLMSG_DATA((*ret)->hdr); - - ndm->ndm_family = ndm_family; - ndm->ndm_ifindex = index; - - return 0; -} - -int sd_rtnl_message_link_set_flags(sd_netlink_message *m, unsigned flags, unsigned change) { - struct ifinfomsg *ifi; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); - assert_return(change, -EINVAL); - - ifi = NLMSG_DATA(m->hdr); - - ifi->ifi_flags = flags; - ifi->ifi_change = change; - - return 0; -} - -int sd_rtnl_message_link_set_type(sd_netlink_message *m, unsigned type) { - struct ifinfomsg *ifi; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); - - ifi = NLMSG_DATA(m->hdr); - - ifi->ifi_type = type; - - return 0; -} - -int sd_rtnl_message_link_set_family(sd_netlink_message *m, unsigned family) { - struct ifinfomsg *ifi; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); - - ifi = NLMSG_DATA(m->hdr); - - ifi->ifi_family = family; - - return 0; -} - -int sd_rtnl_message_new_link(sd_netlink *rtnl, sd_netlink_message **ret, - uint16_t nlmsg_type, int index) { - struct ifinfomsg *ifi; - int r; - - assert_return(rtnl_message_type_is_link(nlmsg_type), -EINVAL); - assert_return(nlmsg_type != RTM_DELLINK || index > 0, -EINVAL); - assert_return(ret, -EINVAL); - - r = message_new(rtnl, ret, nlmsg_type); - if (r < 0) - return r; - - if (nlmsg_type == RTM_NEWLINK) - (*ret)->hdr->nlmsg_flags |= NLM_F_CREATE | NLM_F_EXCL; - - ifi = NLMSG_DATA((*ret)->hdr); - - ifi->ifi_family = AF_UNSPEC; - ifi->ifi_index = index; - - return 0; -} - -int sd_rtnl_message_addr_set_prefixlen(sd_netlink_message *m, unsigned char prefixlen) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - if ((ifa->ifa_family == AF_INET && prefixlen > 32) || - (ifa->ifa_family == AF_INET6 && prefixlen > 128)) - return -ERANGE; - - ifa->ifa_prefixlen = prefixlen; - - return 0; -} - -int sd_rtnl_message_addr_set_flags(sd_netlink_message *m, unsigned char flags) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - ifa->ifa_flags = flags; - - return 0; -} - -int sd_rtnl_message_addr_set_scope(sd_netlink_message *m, unsigned char scope) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - ifa->ifa_scope = scope; - - return 0; -} - -int sd_rtnl_message_addr_get_family(sd_netlink_message *m, int *family) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - assert_return(family, -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - *family = ifa->ifa_family; - - return 0; -} - -int sd_rtnl_message_addr_get_prefixlen(sd_netlink_message *m, unsigned char *prefixlen) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - assert_return(prefixlen, -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - *prefixlen = ifa->ifa_prefixlen; - - return 0; -} - -int sd_rtnl_message_addr_get_scope(sd_netlink_message *m, unsigned char *scope) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - assert_return(scope, -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - *scope = ifa->ifa_scope; - - return 0; -} - -int sd_rtnl_message_addr_get_flags(sd_netlink_message *m, unsigned char *flags) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - assert_return(flags, -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - *flags = ifa->ifa_flags; - - return 0; -} - -int sd_rtnl_message_addr_get_ifindex(sd_netlink_message *m, int *ifindex) { - struct ifaddrmsg *ifa; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_addr(m->hdr->nlmsg_type), -EINVAL); - assert_return(ifindex, -EINVAL); - - ifa = NLMSG_DATA(m->hdr); - - *ifindex = ifa->ifa_index; - - return 0; -} - -int sd_rtnl_message_new_addr(sd_netlink *rtnl, sd_netlink_message **ret, - uint16_t nlmsg_type, int index, - int family) { - struct ifaddrmsg *ifa; - int r; - - assert_return(rtnl_message_type_is_addr(nlmsg_type), -EINVAL); - assert_return((nlmsg_type == RTM_GETADDR && index == 0) || - index > 0, -EINVAL); - assert_return((nlmsg_type == RTM_GETADDR && family == AF_UNSPEC) || - family == AF_INET || family == AF_INET6, -EINVAL); - assert_return(ret, -EINVAL); - - r = message_new(rtnl, ret, nlmsg_type); - if (r < 0) - return r; - - if (nlmsg_type == RTM_GETADDR) - (*ret)->hdr->nlmsg_flags |= NLM_F_DUMP; - - ifa = NLMSG_DATA((*ret)->hdr); - - ifa->ifa_index = index; - ifa->ifa_family = family; - if (family == AF_INET) - ifa->ifa_prefixlen = 32; - else if (family == AF_INET6) - ifa->ifa_prefixlen = 128; - - return 0; -} - -int sd_rtnl_message_new_addr_update(sd_netlink *rtnl, sd_netlink_message **ret, - int index, int family) { - int r; - - r = sd_rtnl_message_new_addr(rtnl, ret, RTM_NEWADDR, index, family); - if (r < 0) - return r; - - (*ret)->hdr->nlmsg_flags |= NLM_F_REPLACE; - - return 0; -} - -int sd_rtnl_message_link_get_ifindex(sd_netlink_message *m, int *ifindex) { - struct ifinfomsg *ifi; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); - assert_return(ifindex, -EINVAL); - - ifi = NLMSG_DATA(m->hdr); - - *ifindex = ifi->ifi_index; - - return 0; -} - -int sd_rtnl_message_link_get_flags(sd_netlink_message *m, unsigned *flags) { - struct ifinfomsg *ifi; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); - assert_return(flags, -EINVAL); - - ifi = NLMSG_DATA(m->hdr); - - *flags = ifi->ifi_flags; - - return 0; -} - -int sd_rtnl_message_link_get_type(sd_netlink_message *m, unsigned *type) { - struct ifinfomsg *ifi; - - assert_return(m, -EINVAL); - assert_return(m->hdr, -EINVAL); - assert_return(rtnl_message_type_is_link(m->hdr->nlmsg_type), -EINVAL); - assert_return(type, -EINVAL); - - ifi = NLMSG_DATA(m->hdr); - - *type = ifi->ifi_type; - - return 0; -} - -int sd_rtnl_message_get_family(sd_netlink_message *m, int *family) { - assert_return(m, -EINVAL); - assert_return(family, -EINVAL); - - assert(m->hdr); - - if (rtnl_message_type_is_link(m->hdr->nlmsg_type)) { - struct ifinfomsg *ifi; - - ifi = NLMSG_DATA(m->hdr); - - *family = ifi->ifi_family; - - return 0; - } else if (rtnl_message_type_is_route(m->hdr->nlmsg_type)) { - struct rtmsg *rtm; - - rtm = NLMSG_DATA(m->hdr); - - *family = rtm->rtm_family; - - return 0; - } else if (rtnl_message_type_is_neigh(m->hdr->nlmsg_type)) { - struct ndmsg *ndm; - - ndm = NLMSG_DATA(m->hdr); - - *family = ndm->ndm_family; - - return 0; - } else if (rtnl_message_type_is_addr(m->hdr->nlmsg_type)) { - struct ifaddrmsg *ifa; - - ifa = NLMSG_DATA(m->hdr); - - *family = ifa->ifa_family; - - return 0; - } - - return -EOPNOTSUPP; -} diff --git a/src/libsystemd/sd-netlink/sd-netlink.c b/src/libsystemd/sd-netlink/sd-netlink.c deleted file mode 100644 index 4833815b43..0000000000 --- a/src/libsystemd/sd-netlink/sd-netlink.c +++ /dev/null @@ -1,954 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 . -***/ - -#include -#include - -#include "sd-netlink.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "hashmap.h" -#include "macro.h" -#include "missing.h" -#include "netlink-internal.h" -#include "netlink-util.h" -#include "socket-util.h" -#include "util.h" - -static int sd_netlink_new(sd_netlink **ret) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - - assert_return(ret, -EINVAL); - - rtnl = new0(sd_netlink, 1); - if (!rtnl) - return -ENOMEM; - - rtnl->n_ref = REFCNT_INIT; - rtnl->fd = -1; - rtnl->sockaddr.nl.nl_family = AF_NETLINK; - rtnl->original_pid = getpid(); - - LIST_HEAD_INIT(rtnl->match_callbacks); - - /* We guarantee that the read buffer has at least space for - * a message header */ - if (!greedy_realloc((void**)&rtnl->rbuffer, &rtnl->rbuffer_allocated, - sizeof(struct nlmsghdr), sizeof(uint8_t))) - return -ENOMEM; - - /* Change notification responses have sequence 0, so we must - * start our request sequence numbers at 1, or we may confuse our - * responses with notifications from the kernel */ - rtnl->serial = 1; - - *ret = rtnl; - rtnl = NULL; - - return 0; -} - -int sd_netlink_new_from_netlink(sd_netlink **ret, int fd) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - socklen_t addrlen; - int r; - - assert_return(ret, -EINVAL); - - r = sd_netlink_new(&rtnl); - if (r < 0) - return r; - - addrlen = sizeof(rtnl->sockaddr); - - r = getsockname(fd, &rtnl->sockaddr.sa, &addrlen); - if (r < 0) - return -errno; - - if (rtnl->sockaddr.nl.nl_family != AF_NETLINK) - return -EINVAL; - - rtnl->fd = fd; - - *ret = rtnl; - rtnl = NULL; - - return 0; -} - -static bool rtnl_pid_changed(sd_netlink *rtnl) { - assert(rtnl); - - /* We don't support people creating an rtnl connection and - * keeping it around over a fork(). Let's complain. */ - - return rtnl->original_pid != getpid(); -} - -int sd_netlink_open_fd(sd_netlink **ret, int fd) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int r; - - assert_return(ret, -EINVAL); - assert_return(fd >= 0, -EBADF); - - r = sd_netlink_new(&rtnl); - if (r < 0) - return r; - - rtnl->fd = fd; - - r = socket_bind(rtnl); - if (r < 0) { - rtnl->fd = -1; /* on failure, the caller remains owner of the fd, hence don't close it here */ - return r; - } - - *ret = rtnl; - rtnl = NULL; - - return 0; -} - -int sd_netlink_open(sd_netlink **ret) { - _cleanup_close_ int fd = -1; - int r; - - fd = socket_open(NETLINK_ROUTE); - if (fd < 0) - return fd; - - r = sd_netlink_open_fd(ret, fd); - if (r < 0) - return r; - - fd = -1; - - return 0; -} - -int sd_netlink_inc_rcvbuf(const sd_netlink *const rtnl, const int size) { - return fd_inc_rcvbuf(rtnl->fd, size); -} - -sd_netlink *sd_netlink_ref(sd_netlink *rtnl) { - assert_return(rtnl, NULL); - assert_return(!rtnl_pid_changed(rtnl), NULL); - - if (rtnl) - assert_se(REFCNT_INC(rtnl->n_ref) >= 2); - - return rtnl; -} - -sd_netlink *sd_netlink_unref(sd_netlink *rtnl) { - if (!rtnl) - return NULL; - - assert_return(!rtnl_pid_changed(rtnl), NULL); - - if (REFCNT_DEC(rtnl->n_ref) == 0) { - struct match_callback *f; - unsigned i; - - for (i = 0; i < rtnl->rqueue_size; i++) - sd_netlink_message_unref(rtnl->rqueue[i]); - free(rtnl->rqueue); - - for (i = 0; i < rtnl->rqueue_partial_size; i++) - sd_netlink_message_unref(rtnl->rqueue_partial[i]); - free(rtnl->rqueue_partial); - - free(rtnl->rbuffer); - - hashmap_free_free(rtnl->reply_callbacks); - prioq_free(rtnl->reply_callbacks_prioq); - - sd_event_source_unref(rtnl->io_event_source); - sd_event_source_unref(rtnl->time_event_source); - sd_event_unref(rtnl->event); - - while ((f = rtnl->match_callbacks)) { - sd_netlink_remove_match(rtnl, f->type, f->callback, f->userdata); - } - - hashmap_free(rtnl->broadcast_group_refs); - - safe_close(rtnl->fd); - free(rtnl); - } - - return NULL; -} - -static void rtnl_seal_message(sd_netlink *rtnl, sd_netlink_message *m) { - assert(rtnl); - assert(!rtnl_pid_changed(rtnl)); - assert(m); - assert(m->hdr); - - /* don't use seq == 0, as that is used for broadcasts, so we - would get confused by replies to such messages */ - m->hdr->nlmsg_seq = rtnl->serial++ ? : rtnl->serial++; - - rtnl_message_seal(m); - - return; -} - -int sd_netlink_send(sd_netlink *nl, - sd_netlink_message *message, - uint32_t *serial) { - int r; - - assert_return(nl, -EINVAL); - assert_return(!rtnl_pid_changed(nl), -ECHILD); - assert_return(message, -EINVAL); - assert_return(!message->sealed, -EPERM); - - rtnl_seal_message(nl, message); - - r = socket_write_message(nl, message); - if (r < 0) - return r; - - if (serial) - *serial = rtnl_message_get_serial(message); - - return 1; -} - -int rtnl_rqueue_make_room(sd_netlink *rtnl) { - assert(rtnl); - - if (rtnl->rqueue_size >= RTNL_RQUEUE_MAX) { - log_debug("rtnl: exhausted the read queue size (%d)", RTNL_RQUEUE_MAX); - return -ENOBUFS; - } - - if (!GREEDY_REALLOC(rtnl->rqueue, rtnl->rqueue_allocated, rtnl->rqueue_size + 1)) - return -ENOMEM; - - return 0; -} - -int rtnl_rqueue_partial_make_room(sd_netlink *rtnl) { - assert(rtnl); - - if (rtnl->rqueue_partial_size >= RTNL_RQUEUE_MAX) { - log_debug("rtnl: exhausted the partial read queue size (%d)", RTNL_RQUEUE_MAX); - return -ENOBUFS; - } - - if (!GREEDY_REALLOC(rtnl->rqueue_partial, rtnl->rqueue_partial_allocated, - rtnl->rqueue_partial_size + 1)) - return -ENOMEM; - - return 0; -} - -static int dispatch_rqueue(sd_netlink *rtnl, sd_netlink_message **message) { - int r; - - assert(rtnl); - assert(message); - - if (rtnl->rqueue_size <= 0) { - /* Try to read a new message */ - r = socket_read_message(rtnl); - if (r <= 0) - return r; - } - - /* Dispatch a queued message */ - *message = rtnl->rqueue[0]; - rtnl->rqueue_size --; - memmove(rtnl->rqueue, rtnl->rqueue + 1, sizeof(sd_netlink_message*) * rtnl->rqueue_size); - - return 1; -} - -static int process_timeout(sd_netlink *rtnl) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - struct reply_callback *c; - usec_t n; - int r; - - assert(rtnl); - - c = prioq_peek(rtnl->reply_callbacks_prioq); - if (!c) - return 0; - - n = now(CLOCK_MONOTONIC); - if (c->timeout > n) - return 0; - - r = rtnl_message_new_synthetic_error(-ETIMEDOUT, c->serial, &m); - if (r < 0) - return r; - - assert_se(prioq_pop(rtnl->reply_callbacks_prioq) == c); - hashmap_remove(rtnl->reply_callbacks, &c->serial); - - r = c->callback(rtnl, m, c->userdata); - if (r < 0) - log_debug_errno(r, "sd-netlink: timedout callback failed: %m"); - - free(c); - - return 1; -} - -static int process_reply(sd_netlink *rtnl, sd_netlink_message *m) { - _cleanup_free_ struct reply_callback *c = NULL; - uint64_t serial; - uint16_t type; - int r; - - assert(rtnl); - assert(m); - - serial = rtnl_message_get_serial(m); - c = hashmap_remove(rtnl->reply_callbacks, &serial); - if (!c) - return 0; - - if (c->timeout != 0) - prioq_remove(rtnl->reply_callbacks_prioq, c, &c->prioq_idx); - - r = sd_netlink_message_get_type(m, &type); - if (r < 0) - return 0; - - if (type == NLMSG_DONE) - m = NULL; - - r = c->callback(rtnl, m, c->userdata); - if (r < 0) - log_debug_errno(r, "sd-netlink: callback failed: %m"); - - return 1; -} - -static int process_match(sd_netlink *rtnl, sd_netlink_message *m) { - struct match_callback *c; - uint16_t type; - int r; - - assert(rtnl); - assert(m); - - r = sd_netlink_message_get_type(m, &type); - if (r < 0) - return r; - - LIST_FOREACH(match_callbacks, c, rtnl->match_callbacks) { - if (type == c->type) { - r = c->callback(rtnl, m, c->userdata); - if (r != 0) { - if (r < 0) - log_debug_errno(r, "sd-netlink: match callback failed: %m"); - - break; - } - } - } - - return 1; -} - -static int process_running(sd_netlink *rtnl, sd_netlink_message **ret) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - assert(rtnl); - - r = process_timeout(rtnl); - if (r != 0) - goto null_message; - - r = dispatch_rqueue(rtnl, &m); - if (r < 0) - return r; - if (!m) - goto null_message; - - if (sd_netlink_message_is_broadcast(m)) { - r = process_match(rtnl, m); - if (r != 0) - goto null_message; - } else { - r = process_reply(rtnl, m); - if (r != 0) - goto null_message; - } - - if (ret) { - *ret = m; - m = NULL; - - return 1; - } - - return 1; - -null_message: - if (r >= 0 && ret) - *ret = NULL; - - return r; -} - -int sd_netlink_process(sd_netlink *rtnl, sd_netlink_message **ret) { - NETLINK_DONT_DESTROY(rtnl); - int r; - - assert_return(rtnl, -EINVAL); - assert_return(!rtnl_pid_changed(rtnl), -ECHILD); - assert_return(!rtnl->processing, -EBUSY); - - rtnl->processing = true; - r = process_running(rtnl, ret); - rtnl->processing = false; - - return r; -} - -static usec_t calc_elapse(uint64_t usec) { - if (usec == (uint64_t) -1) - return 0; - - if (usec == 0) - usec = RTNL_DEFAULT_TIMEOUT; - - return now(CLOCK_MONOTONIC) + usec; -} - -static int rtnl_poll(sd_netlink *rtnl, bool need_more, uint64_t timeout_usec) { - struct pollfd p[1] = {}; - struct timespec ts; - usec_t m = USEC_INFINITY; - int r, e; - - assert(rtnl); - - e = sd_netlink_get_events(rtnl); - if (e < 0) - return e; - - if (need_more) - /* Caller wants more data, and doesn't care about - * what's been read or any other timeouts. */ - e |= POLLIN; - else { - usec_t until; - /* Caller wants to process if there is something to - * process, but doesn't care otherwise */ - - r = sd_netlink_get_timeout(rtnl, &until); - if (r < 0) - return r; - if (r > 0) { - usec_t nw; - nw = now(CLOCK_MONOTONIC); - m = until > nw ? until - nw : 0; - } - } - - if (timeout_usec != (uint64_t) -1 && (m == (uint64_t) -1 || timeout_usec < m)) - m = timeout_usec; - - p[0].fd = rtnl->fd; - p[0].events = e; - - r = ppoll(p, 1, m == (uint64_t) -1 ? NULL : timespec_store(&ts, m), NULL); - if (r < 0) - return -errno; - - return r > 0 ? 1 : 0; -} - -int sd_netlink_wait(sd_netlink *nl, uint64_t timeout_usec) { - assert_return(nl, -EINVAL); - assert_return(!rtnl_pid_changed(nl), -ECHILD); - - if (nl->rqueue_size > 0) - return 0; - - return rtnl_poll(nl, false, timeout_usec); -} - -static int timeout_compare(const void *a, const void *b) { - const struct reply_callback *x = a, *y = b; - - if (x->timeout != 0 && y->timeout == 0) - return -1; - - if (x->timeout == 0 && y->timeout != 0) - return 1; - - if (x->timeout < y->timeout) - return -1; - - if (x->timeout > y->timeout) - return 1; - - return 0; -} - -int sd_netlink_call_async(sd_netlink *nl, - sd_netlink_message *m, - sd_netlink_message_handler_t callback, - void *userdata, - uint64_t usec, - uint32_t *serial) { - struct reply_callback *c; - uint32_t s; - int r, k; - - assert_return(nl, -EINVAL); - assert_return(m, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!rtnl_pid_changed(nl), -ECHILD); - - r = hashmap_ensure_allocated(&nl->reply_callbacks, &uint64_hash_ops); - if (r < 0) - return r; - - if (usec != (uint64_t) -1) { - r = prioq_ensure_allocated(&nl->reply_callbacks_prioq, timeout_compare); - if (r < 0) - return r; - } - - c = new0(struct reply_callback, 1); - if (!c) - return -ENOMEM; - - c->callback = callback; - c->userdata = userdata; - c->timeout = calc_elapse(usec); - - k = sd_netlink_send(nl, m, &s); - if (k < 0) { - free(c); - return k; - } - - c->serial = s; - - r = hashmap_put(nl->reply_callbacks, &c->serial, c); - if (r < 0) { - free(c); - return r; - } - - if (c->timeout != 0) { - r = prioq_put(nl->reply_callbacks_prioq, c, &c->prioq_idx); - if (r > 0) { - c->timeout = 0; - sd_netlink_call_async_cancel(nl, c->serial); - return r; - } - } - - if (serial) - *serial = s; - - return k; -} - -int sd_netlink_call_async_cancel(sd_netlink *nl, uint32_t serial) { - struct reply_callback *c; - uint64_t s = serial; - - assert_return(nl, -EINVAL); - assert_return(serial != 0, -EINVAL); - assert_return(!rtnl_pid_changed(nl), -ECHILD); - - c = hashmap_remove(nl->reply_callbacks, &s); - if (!c) - return 0; - - if (c->timeout != 0) - prioq_remove(nl->reply_callbacks_prioq, c, &c->prioq_idx); - - free(c); - return 1; -} - -int sd_netlink_call(sd_netlink *rtnl, - sd_netlink_message *message, - uint64_t usec, - sd_netlink_message **ret) { - usec_t timeout; - uint32_t serial; - int r; - - assert_return(rtnl, -EINVAL); - assert_return(!rtnl_pid_changed(rtnl), -ECHILD); - assert_return(message, -EINVAL); - - r = sd_netlink_send(rtnl, message, &serial); - if (r < 0) - return r; - - timeout = calc_elapse(usec); - - for (;;) { - usec_t left; - unsigned i; - - for (i = 0; i < rtnl->rqueue_size; i++) { - uint32_t received_serial; - - received_serial = rtnl_message_get_serial(rtnl->rqueue[i]); - - if (received_serial == serial) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *incoming = NULL; - uint16_t type; - - incoming = rtnl->rqueue[i]; - - /* found a match, remove from rqueue and return it */ - memmove(rtnl->rqueue + i,rtnl->rqueue + i + 1, - sizeof(sd_netlink_message*) * (rtnl->rqueue_size - i - 1)); - rtnl->rqueue_size--; - - r = sd_netlink_message_get_errno(incoming); - if (r < 0) - return r; - - r = sd_netlink_message_get_type(incoming, &type); - if (r < 0) - return r; - - if (type == NLMSG_DONE) { - *ret = NULL; - return 0; - } - - if (ret) { - *ret = incoming; - incoming = NULL; - } - - return 1; - } - } - - r = socket_read_message(rtnl); - if (r < 0) - return r; - if (r > 0) - /* received message, so try to process straight away */ - continue; - - if (timeout > 0) { - usec_t n; - - n = now(CLOCK_MONOTONIC); - if (n >= timeout) - return -ETIMEDOUT; - - left = timeout - n; - } else - left = (uint64_t) -1; - - r = rtnl_poll(rtnl, true, left); - if (r < 0) - return r; - else if (r == 0) - return -ETIMEDOUT; - } -} - -int sd_netlink_get_events(sd_netlink *rtnl) { - assert_return(rtnl, -EINVAL); - assert_return(!rtnl_pid_changed(rtnl), -ECHILD); - - if (rtnl->rqueue_size == 0) - return POLLIN; - else - return 0; -} - -int sd_netlink_get_timeout(sd_netlink *rtnl, uint64_t *timeout_usec) { - struct reply_callback *c; - - assert_return(rtnl, -EINVAL); - assert_return(timeout_usec, -EINVAL); - assert_return(!rtnl_pid_changed(rtnl), -ECHILD); - - if (rtnl->rqueue_size > 0) { - *timeout_usec = 0; - return 1; - } - - c = prioq_peek(rtnl->reply_callbacks_prioq); - if (!c) { - *timeout_usec = (uint64_t) -1; - return 0; - } - - *timeout_usec = c->timeout; - - return 1; -} - -static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_netlink *rtnl = userdata; - int r; - - assert(rtnl); - - r = sd_netlink_process(rtnl, NULL); - if (r < 0) - return r; - - return 1; -} - -static int time_callback(sd_event_source *s, uint64_t usec, void *userdata) { - sd_netlink *rtnl = userdata; - int r; - - assert(rtnl); - - r = sd_netlink_process(rtnl, NULL); - if (r < 0) - return r; - - return 1; -} - -static int prepare_callback(sd_event_source *s, void *userdata) { - sd_netlink *rtnl = userdata; - int r, e; - usec_t until; - - assert(s); - assert(rtnl); - - e = sd_netlink_get_events(rtnl); - if (e < 0) - return e; - - r = sd_event_source_set_io_events(rtnl->io_event_source, e); - if (r < 0) - return r; - - r = sd_netlink_get_timeout(rtnl, &until); - if (r < 0) - return r; - if (r > 0) { - int j; - - j = sd_event_source_set_time(rtnl->time_event_source, until); - if (j < 0) - return j; - } - - r = sd_event_source_set_enabled(rtnl->time_event_source, r > 0); - if (r < 0) - return r; - - return 1; -} - -int sd_netlink_attach_event(sd_netlink *rtnl, sd_event *event, int priority) { - int r; - - assert_return(rtnl, -EINVAL); - assert_return(!rtnl->event, -EBUSY); - - assert(!rtnl->io_event_source); - assert(!rtnl->time_event_source); - - if (event) - rtnl->event = sd_event_ref(event); - else { - r = sd_event_default(&rtnl->event); - if (r < 0) - return r; - } - - r = sd_event_add_io(rtnl->event, &rtnl->io_event_source, rtnl->fd, 0, io_callback, rtnl); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(rtnl->io_event_source, priority); - if (r < 0) - goto fail; - - r = sd_event_source_set_description(rtnl->io_event_source, "rtnl-receive-message"); - if (r < 0) - goto fail; - - r = sd_event_source_set_prepare(rtnl->io_event_source, prepare_callback); - if (r < 0) - goto fail; - - r = sd_event_add_time(rtnl->event, &rtnl->time_event_source, CLOCK_MONOTONIC, 0, 0, time_callback, rtnl); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(rtnl->time_event_source, priority); - if (r < 0) - goto fail; - - r = sd_event_source_set_description(rtnl->time_event_source, "rtnl-timer"); - if (r < 0) - goto fail; - - return 0; - -fail: - sd_netlink_detach_event(rtnl); - return r; -} - -int sd_netlink_detach_event(sd_netlink *rtnl) { - assert_return(rtnl, -EINVAL); - assert_return(rtnl->event, -ENXIO); - - rtnl->io_event_source = sd_event_source_unref(rtnl->io_event_source); - - rtnl->time_event_source = sd_event_source_unref(rtnl->time_event_source); - - rtnl->event = sd_event_unref(rtnl->event); - - return 0; -} - -int sd_netlink_add_match(sd_netlink *rtnl, - uint16_t type, - sd_netlink_message_handler_t callback, - void *userdata) { - _cleanup_free_ struct match_callback *c = NULL; - int r; - - assert_return(rtnl, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!rtnl_pid_changed(rtnl), -ECHILD); - - c = new0(struct match_callback, 1); - if (!c) - return -ENOMEM; - - c->callback = callback; - c->type = type; - c->userdata = userdata; - - switch (type) { - case RTM_NEWLINK: - case RTM_DELLINK: - r = socket_broadcast_group_ref(rtnl, RTNLGRP_LINK); - if (r < 0) - return r; - - break; - case RTM_NEWADDR: - case RTM_DELADDR: - r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_IFADDR); - if (r < 0) - return r; - - r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_IFADDR); - if (r < 0) - return r; - - break; - case RTM_NEWROUTE: - case RTM_DELROUTE: - r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV4_ROUTE); - if (r < 0) - return r; - - r = socket_broadcast_group_ref(rtnl, RTNLGRP_IPV6_ROUTE); - if (r < 0) - return r; - break; - default: - return -EOPNOTSUPP; - } - - LIST_PREPEND(match_callbacks, rtnl->match_callbacks, c); - - c = NULL; - - return 0; -} - -int sd_netlink_remove_match(sd_netlink *rtnl, - uint16_t type, - sd_netlink_message_handler_t callback, - void *userdata) { - struct match_callback *c; - int r; - - assert_return(rtnl, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!rtnl_pid_changed(rtnl), -ECHILD); - - LIST_FOREACH(match_callbacks, c, rtnl->match_callbacks) - if (c->callback == callback && c->type == type && c->userdata == userdata) { - LIST_REMOVE(match_callbacks, rtnl->match_callbacks, c); - free(c); - - switch (type) { - case RTM_NEWLINK: - case RTM_DELLINK: - r = socket_broadcast_group_unref(rtnl, RTNLGRP_LINK); - if (r < 0) - return r; - - break; - case RTM_NEWADDR: - case RTM_DELADDR: - r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV4_IFADDR); - if (r < 0) - return r; - - r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV6_IFADDR); - if (r < 0) - return r; - - break; - case RTM_NEWROUTE: - case RTM_DELROUTE: - r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV4_ROUTE); - if (r < 0) - return r; - - r = socket_broadcast_group_unref(rtnl, RTNLGRP_IPV6_ROUTE); - if (r < 0) - return r; - break; - default: - return -EOPNOTSUPP; - } - - return 1; - } - - return 0; -} diff --git a/src/libsystemd/sd-netlink/test-local-addresses.c b/src/libsystemd/sd-netlink/test-local-addresses.c deleted file mode 100644 index e0e28cc0cc..0000000000 --- a/src/libsystemd/sd-netlink/test-local-addresses.c +++ /dev/null @@ -1,56 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "af-list.h" -#include "alloc-util.h" -#include "in-addr-util.h" -#include "local-addresses.h" - -static void print_local_addresses(struct local_address *a, unsigned n) { - unsigned i; - - for (i = 0; i < n; i++) { - _cleanup_free_ char *b = NULL; - - assert_se(in_addr_to_string(a[i].family, &a[i].address, &b) >= 0); - printf("%s if%i scope=%i metric=%u address=%s\n", af_to_name(a[i].family), a[i].ifindex, a[i].scope, a[i].metric, b); - } -} - -int main(int argc, char *argv[]) { - struct local_address *a; - int n; - - a = NULL; - n = local_addresses(NULL, 0, AF_UNSPEC, &a); - assert_se(n >= 0); - - printf("Local Addresses:\n"); - print_local_addresses(a, (unsigned) n); - a = mfree(a); - - n = local_gateways(NULL, 0, AF_UNSPEC, &a); - assert_se(n >= 0); - - printf("Local Gateways:\n"); - print_local_addresses(a, (unsigned) n); - free(a); - - return 0; -} diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c deleted file mode 100644 index de5e0ffc8f..0000000000 --- a/src/libsystemd/sd-netlink/test-netlink.c +++ /dev/null @@ -1,440 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 . -***/ - -#include -#include - -#include "sd-netlink.h" - -#include "ether-addr-util.h" -#include "macro.h" -#include "missing.h" -#include "netlink-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "util.h" - -static void test_message_link_bridge(sd_netlink *rtnl) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; - uint32_t cost; - - assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_NEWLINK, 1) >= 0); - assert_se(sd_rtnl_message_link_set_family(message, PF_BRIDGE) >= 0); - assert_se(sd_netlink_message_open_container(message, IFLA_PROTINFO) >= 0); - assert_se(sd_netlink_message_append_u32(message, IFLA_BRPORT_COST, 10) >= 0); - assert_se(sd_netlink_message_close_container(message) >= 0); - - assert_se(sd_netlink_message_rewind(message) >= 0); - - assert_se(sd_netlink_message_enter_container(message, IFLA_PROTINFO) >= 0); - assert_se(sd_netlink_message_read_u32(message, IFLA_BRPORT_COST, &cost) >= 0); - assert_se(cost == 10); - assert_se(sd_netlink_message_exit_container(message) >= 0); -} - -static void test_link_configure(sd_netlink *rtnl, int ifindex) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; - const char *mac = "98:fe:94:3f:c6:18", *name = "test"; - char buffer[ETHER_ADDR_TO_STRING_MAX]; - unsigned int mtu = 1450, mtu_out; - const char *name_out; - struct ether_addr mac_out; - - /* we'd really like to test NEWLINK, but let's not mess with the running kernel */ - assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_GETLINK, ifindex) >= 0); - assert_se(sd_netlink_message_append_string(message, IFLA_IFNAME, name) >= 0); - assert_se(sd_netlink_message_append_ether_addr(message, IFLA_ADDRESS, ether_aton(mac)) >= 0); - assert_se(sd_netlink_message_append_u32(message, IFLA_MTU, mtu) >= 0); - - assert_se(sd_netlink_call(rtnl, message, 0, NULL) == 1); - assert_se(sd_netlink_message_rewind(message) >= 0); - - assert_se(sd_netlink_message_read_string(message, IFLA_IFNAME, &name_out) >= 0); - assert_se(streq(name, name_out)); - - assert_se(sd_netlink_message_read_ether_addr(message, IFLA_ADDRESS, &mac_out) >= 0); - assert_se(streq(mac, ether_addr_to_string(&mac_out, buffer))); - - assert_se(sd_netlink_message_read_u32(message, IFLA_MTU, &mtu_out) >= 0); - assert_se(mtu == mtu_out); -} - -static void test_link_get(sd_netlink *rtnl, int ifindex) { - sd_netlink_message *m; - sd_netlink_message *r; - unsigned int mtu = 1500; - const char *str_data; - uint8_t u8_data; - uint32_t u32_data; - struct ether_addr eth_data; - - assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); - assert_se(m); - - /* u8 test cases */ - assert_se(sd_netlink_message_append_u8(m, IFLA_CARRIER, 0) >= 0); - assert_se(sd_netlink_message_append_u8(m, IFLA_OPERSTATE, 0) >= 0); - assert_se(sd_netlink_message_append_u8(m, IFLA_LINKMODE, 0) >= 0); - - /* u32 test cases */ - assert_se(sd_netlink_message_append_u32(m, IFLA_MTU, mtu) >= 0); - assert_se(sd_netlink_message_append_u32(m, IFLA_GROUP, 0) >= 0); - assert_se(sd_netlink_message_append_u32(m, IFLA_TXQLEN, 0) >= 0); - assert_se(sd_netlink_message_append_u32(m, IFLA_NUM_TX_QUEUES, 0) >= 0); - assert_se(sd_netlink_message_append_u32(m, IFLA_NUM_RX_QUEUES, 0) >= 0); - - assert_se(sd_netlink_call(rtnl, m, -1, &r) == 1); - - assert_se(sd_netlink_message_read_string(r, IFLA_IFNAME, &str_data) == 0); - - assert_se(sd_netlink_message_read_u8(r, IFLA_CARRIER, &u8_data) == 0); - assert_se(sd_netlink_message_read_u8(r, IFLA_OPERSTATE, &u8_data) == 0); - assert_se(sd_netlink_message_read_u8(r, IFLA_LINKMODE, &u8_data) == 0); - - assert_se(sd_netlink_message_read_u32(r, IFLA_MTU, &u32_data) == 0); - assert_se(sd_netlink_message_read_u32(r, IFLA_GROUP, &u32_data) == 0); - assert_se(sd_netlink_message_read_u32(r, IFLA_TXQLEN, &u32_data) == 0); - assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_TX_QUEUES, &u32_data) == 0); - assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_RX_QUEUES, &u32_data) == 0); - - assert_se(sd_netlink_message_read_ether_addr(r, IFLA_ADDRESS, ð_data) == 0); - - assert_se((m = sd_netlink_message_unref(m)) == NULL); - assert_se((r = sd_netlink_message_unref(r)) == NULL); -} - - -static void test_address_get(sd_netlink *rtnl, int ifindex) { - sd_netlink_message *m; - sd_netlink_message *r; - struct in_addr in_data; - struct ifa_cacheinfo cache; - const char *label; - - assert_se(sd_rtnl_message_new_addr(rtnl, &m, RTM_GETADDR, ifindex, AF_INET) >= 0); - assert_se(m); - - assert_se(sd_netlink_call(rtnl, m, -1, &r) == 1); - - assert_se(sd_netlink_message_read_in_addr(r, IFA_LOCAL, &in_data) == 0); - assert_se(sd_netlink_message_read_in_addr(r, IFA_ADDRESS, &in_data) == 0); - assert_se(sd_netlink_message_read_string(r, IFA_LABEL, &label) == 0); - assert_se(sd_netlink_message_read_cache_info(r, IFA_CACHEINFO, &cache) == 0); - - assert_se((m = sd_netlink_message_unref(m)) == NULL); - assert_se((r = sd_netlink_message_unref(r)) == NULL); - -} - -static void test_route(void) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req; - struct in_addr addr, addr_data; - uint32_t index = 2, u32_data; - int r; - - r = sd_rtnl_message_new_route(NULL, &req, RTM_NEWROUTE, AF_INET, RTPROT_STATIC); - if (r < 0) { - log_error_errno(r, "Could not create RTM_NEWROUTE message: %m"); - return; - } - - addr.s_addr = htonl(INADDR_LOOPBACK); - - r = sd_netlink_message_append_in_addr(req, RTA_GATEWAY, &addr); - if (r < 0) { - log_error_errno(r, "Could not append RTA_GATEWAY attribute: %m"); - return; - } - - r = sd_netlink_message_append_u32(req, RTA_OIF, index); - if (r < 0) { - log_error_errno(r, "Could not append RTA_OIF attribute: %m"); - return; - } - - assert_se(sd_netlink_message_rewind(req) >= 0); - - assert_se(sd_netlink_message_read_in_addr(req, RTA_GATEWAY, &addr_data) >= 0); - assert_se(addr_data.s_addr == addr.s_addr); - - assert_se(sd_netlink_message_read_u32(req, RTA_OIF, &u32_data) >= 0); - assert_se(u32_data == index); - - assert_se((req = sd_netlink_message_unref(req)) == NULL); -} - -static void test_multiple(void) { - sd_netlink *rtnl1, *rtnl2; - - assert_se(sd_netlink_open(&rtnl1) >= 0); - assert_se(sd_netlink_open(&rtnl2) >= 0); - - rtnl1 = sd_netlink_unref(rtnl1); - rtnl2 = sd_netlink_unref(rtnl2); -} - -static int link_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { - char *ifname = userdata; - const char *data; - - assert_se(rtnl); - assert_se(m); - - log_info("got link info about %s", ifname); - free(ifname); - - assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &data) >= 0); - assert_se(streq(data, "lo")); - - return 1; -} - -static void test_event_loop(int ifindex) { - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - char *ifname; - - ifname = strdup("lo2"); - assert_se(ifname); - - assert_se(sd_netlink_open(&rtnl) >= 0); - assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); - - assert_se(sd_netlink_call_async(rtnl, m, &link_handler, ifname, 0, NULL) >= 0); - - assert_se(sd_event_default(&event) >= 0); - - assert_se(sd_netlink_attach_event(rtnl, event, 0) >= 0); - - assert_se(sd_event_run(event, 0) >= 0); - - assert_se(sd_netlink_detach_event(rtnl) >= 0); - - assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); -} - -static int pipe_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { - int *counter = userdata; - int r; - - (*counter) --; - - r = sd_netlink_message_get_errno(m); - - log_info_errno(r, "%d left in pipe. got reply: %m", *counter); - - assert_se(r >= 0); - - return 1; -} - -static void test_async(int ifindex) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; - uint32_t serial; - char *ifname; - - ifname = strdup("lo"); - assert_se(ifname); - - assert_se(sd_netlink_open(&rtnl) >= 0); - - assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); - - assert_se(sd_netlink_call_async(rtnl, m, &link_handler, ifname, 0, &serial) >= 0); - - assert_se(sd_netlink_wait(rtnl, 0) >= 0); - assert_se(sd_netlink_process(rtnl, &r) >= 0); - - assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); -} - -static void test_pipe(int ifindex) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m1 = NULL, *m2 = NULL; - int counter = 0; - - assert_se(sd_netlink_open(&rtnl) >= 0); - - assert_se(sd_rtnl_message_new_link(rtnl, &m1, RTM_GETLINK, ifindex) >= 0); - assert_se(sd_rtnl_message_new_link(rtnl, &m2, RTM_GETLINK, ifindex) >= 0); - - counter ++; - assert_se(sd_netlink_call_async(rtnl, m1, &pipe_handler, &counter, 0, NULL) >= 0); - - counter ++; - assert_se(sd_netlink_call_async(rtnl, m2, &pipe_handler, &counter, 0, NULL) >= 0); - - while (counter > 0) { - assert_se(sd_netlink_wait(rtnl, 0) >= 0); - assert_se(sd_netlink_process(rtnl, NULL) >= 0); - } - - assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); -} - -static void test_container(void) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - uint16_t u16_data; - uint32_t u32_data; - const char *string_data; - - assert_se(sd_rtnl_message_new_link(NULL, &m, RTM_NEWLINK, 0) >= 0); - - assert_se(sd_netlink_message_open_container(m, IFLA_LINKINFO) >= 0); - assert_se(sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "vlan") >= 0); - assert_se(sd_netlink_message_append_u16(m, IFLA_VLAN_ID, 100) >= 0); - assert_se(sd_netlink_message_close_container(m) >= 0); - assert_se(sd_netlink_message_append_string(m, IFLA_INFO_KIND, "vlan") >= 0); - assert_se(sd_netlink_message_close_container(m) >= 0); - assert_se(sd_netlink_message_close_container(m) == -EINVAL); - - assert_se(sd_netlink_message_rewind(m) >= 0); - - assert_se(sd_netlink_message_enter_container(m, IFLA_LINKINFO) >= 0); - assert_se(sd_netlink_message_read_string(m, IFLA_INFO_KIND, &string_data) >= 0); - assert_se(streq("vlan", string_data)); - - assert_se(sd_netlink_message_enter_container(m, IFLA_INFO_DATA) >= 0); - assert_se(sd_netlink_message_read_u16(m, IFLA_VLAN_ID, &u16_data) >= 0); - assert_se(sd_netlink_message_exit_container(m) >= 0); - - assert_se(sd_netlink_message_read_string(m, IFLA_INFO_KIND, &string_data) >= 0); - assert_se(streq("vlan", string_data)); - assert_se(sd_netlink_message_exit_container(m) >= 0); - - assert_se(sd_netlink_message_read_u32(m, IFLA_LINKINFO, &u32_data) < 0); - - assert_se(sd_netlink_message_exit_container(m) == -EINVAL); -} - -static void test_match(void) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - - assert_se(sd_netlink_open(&rtnl) >= 0); - - assert_se(sd_netlink_add_match(rtnl, RTM_NEWLINK, &link_handler, NULL) >= 0); - assert_se(sd_netlink_add_match(rtnl, RTM_NEWLINK, &link_handler, NULL) >= 0); - - assert_se(sd_netlink_remove_match(rtnl, RTM_NEWLINK, &link_handler, NULL) == 1); - assert_se(sd_netlink_remove_match(rtnl, RTM_NEWLINK, &link_handler, NULL) == 1); - assert_se(sd_netlink_remove_match(rtnl, RTM_NEWLINK, &link_handler, NULL) == 0); - - assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); -} - -static void test_get_addresses(sd_netlink *rtnl) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; - sd_netlink_message *m; - - assert_se(sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC) >= 0); - - assert_se(sd_netlink_call(rtnl, req, 0, &reply) >= 0); - - for (m = reply; m; m = sd_netlink_message_next(m)) { - uint16_t type; - unsigned char scope, flags; - int family, ifindex; - - assert_se(sd_netlink_message_get_type(m, &type) >= 0); - assert_se(type == RTM_NEWADDR); - - assert_se(sd_rtnl_message_addr_get_ifindex(m, &ifindex) >= 0); - assert_se(sd_rtnl_message_addr_get_family(m, &family) >= 0); - assert_se(sd_rtnl_message_addr_get_scope(m, &scope) >= 0); - assert_se(sd_rtnl_message_addr_get_flags(m, &flags) >= 0); - - assert_se(ifindex > 0); - assert_se(family == AF_INET || family == AF_INET6); - - log_info("got IPv%u address on ifindex %i", family == AF_INET ? 4: 6, ifindex); - } -} - -static void test_message(void) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - - assert_se(rtnl_message_new_synthetic_error(-ETIMEDOUT, 1, &m) >= 0); - assert_se(sd_netlink_message_get_errno(m) == -ETIMEDOUT); -} - -int main(void) { - sd_netlink *rtnl; - sd_netlink_message *m; - sd_netlink_message *r; - const char *string_data; - int if_loopback; - uint16_t type; - - test_message(); - - test_match(); - - test_multiple(); - - test_route(); - - test_container(); - - assert_se(sd_netlink_open(&rtnl) >= 0); - assert_se(rtnl); - - if_loopback = (int) if_nametoindex("lo"); - assert_se(if_loopback > 0); - - test_async(if_loopback); - - test_pipe(if_loopback); - - test_event_loop(if_loopback); - - test_link_configure(rtnl, if_loopback); - - test_get_addresses(rtnl); - - test_message_link_bridge(rtnl); - - assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, if_loopback) >= 0); - assert_se(m); - - assert_se(sd_netlink_message_get_type(m, &type) >= 0); - assert_se(type == RTM_GETLINK); - - assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &string_data) == -EPERM); - - assert_se(sd_netlink_call(rtnl, m, 0, &r) == 1); - assert_se(sd_netlink_message_get_type(r, &type) >= 0); - assert_se(type == RTM_NEWLINK); - - assert_se((r = sd_netlink_message_unref(r)) == NULL); - - assert_se(sd_netlink_call(rtnl, m, -1, &r) == -EPERM); - assert_se((m = sd_netlink_message_unref(m)) == NULL); - assert_se((r = sd_netlink_message_unref(r)) == NULL); - - test_link_get(rtnl, if_loopback); - test_address_get(rtnl, if_loopback); - - assert_se((m = sd_netlink_message_unref(m)) == NULL); - assert_se((r = sd_netlink_message_unref(r)) == NULL); - assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); - - return EXIT_SUCCESS; -} diff --git a/src/libsystemd/sd-network/Makefile b/src/libsystemd/sd-network/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd/sd-network/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-network/network-util.c b/src/libsystemd/sd-network/network-util.c deleted file mode 100644 index a0d9b5f1a4..0000000000 --- a/src/libsystemd/sd-network/network-util.c +++ /dev/null @@ -1,37 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "alloc-util.h" -#include "fd-util.h" -#include "network-util.h" -#include "strv.h" - -bool network_is_online(void) { - _cleanup_free_ char *state = NULL; - int r; - - r = sd_network_get_operational_state(&state); - if (r < 0) /* if we don't know anything, we consider the system online */ - return true; - - if (STR_IN_SET(state, "routable", "degraded")) - return true; - - return false; -} diff --git a/src/libsystemd/sd-network/network-util.h b/src/libsystemd/sd-network/network-util.h deleted file mode 100644 index 26780dce28..0000000000 --- a/src/libsystemd/sd-network/network-util.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Thomas Hindø Paabøl Andersen - - 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 . -***/ - -#include "sd-network.h" - -bool network_is_online(void); diff --git a/src/libsystemd/sd-network/sd-network.c b/src/libsystemd/sd-network/sd-network.c deleted file mode 100644 index 62051992ef..0000000000 --- a/src/libsystemd/sd-network/sd-network.c +++ /dev/null @@ -1,373 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 Lennart Poettering - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include -#include -#include -#include - -#include "sd-network.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "macro.h" -#include "parse-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -_public_ int sd_network_get_operational_state(char **state) { - _cleanup_free_ char *s = NULL; - int r; - - assert_return(state, -EINVAL); - - r = parse_env_file("/run/systemd/netif/state", NEWLINE, "OPER_STATE", &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - *state = s; - s = NULL; - - return 0; -} - -static int network_get_strv(const char *key, char ***ret) { - _cleanup_strv_free_ char **a = NULL; - _cleanup_free_ char *s = NULL; - int r; - - assert_return(ret, -EINVAL); - - r = parse_env_file("/run/systemd/netif/state", NEWLINE, key, &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) { - *ret = NULL; - return 0; - } - - a = strv_split(s, " "); - if (!a) - return -ENOMEM; - - strv_uniq(a); - r = strv_length(a); - - *ret = a; - a = NULL; - - return r; -} - -_public_ int sd_network_get_dns(char ***ret) { - return network_get_strv("DNS", ret); -} - -_public_ int sd_network_get_ntp(char ***ret) { - return network_get_strv("NTP", ret); -} - -_public_ int sd_network_get_search_domains(char ***ret) { - return network_get_strv("DOMAINS", ret); -} - -_public_ int sd_network_get_route_domains(char ***ret) { - return network_get_strv("ROUTE_DOMAINS", ret); -} - -static int network_link_get_string(int ifindex, const char *field, char **ret) { - _cleanup_free_ char *s = NULL, *p = NULL; - int r; - - assert_return(ifindex > 0, -EINVAL); - assert_return(ret, -EINVAL); - - if (asprintf(&p, "/run/systemd/netif/links/%i", ifindex) < 0) - return -ENOMEM; - - r = parse_env_file(p, NEWLINE, field, &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) - return -ENODATA; - - *ret = s; - s = NULL; - - return 0; -} - -static int network_link_get_strv(int ifindex, const char *key, char ***ret) { - _cleanup_free_ char *p = NULL, *s = NULL; - _cleanup_strv_free_ char **a = NULL; - int r; - - assert_return(ifindex > 0, -EINVAL); - assert_return(ret, -EINVAL); - - if (asprintf(&p, "/run/systemd/netif/links/%d", ifindex) < 0) - return -ENOMEM; - - r = parse_env_file(p, NEWLINE, key, &s, NULL); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (isempty(s)) { - *ret = NULL; - return 0; - } - - a = strv_split(s, " "); - if (!a) - return -ENOMEM; - - strv_uniq(a); - r = strv_length(a); - - *ret = a; - a = NULL; - - return r; -} - -_public_ int sd_network_link_get_setup_state(int ifindex, char **state) { - return network_link_get_string(ifindex, "ADMIN_STATE", state); -} - -_public_ int sd_network_link_get_network_file(int ifindex, char **filename) { - return network_link_get_string(ifindex, "NETWORK_FILE", filename); -} - -_public_ int sd_network_link_get_operational_state(int ifindex, char **state) { - return network_link_get_string(ifindex, "OPER_STATE", state); -} - -_public_ int sd_network_link_get_llmnr(int ifindex, char **llmnr) { - return network_link_get_string(ifindex, "LLMNR", llmnr); -} - -_public_ int sd_network_link_get_mdns(int ifindex, char **mdns) { - return network_link_get_string(ifindex, "MDNS", mdns); -} - -_public_ int sd_network_link_get_dnssec(int ifindex, char **dnssec) { - return network_link_get_string(ifindex, "DNSSEC", dnssec); -} - -_public_ int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta) { - return network_link_get_strv(ifindex, "DNSSEC_NTA", nta); -} - -_public_ int sd_network_link_get_lldp(int ifindex, char **lldp) { - _cleanup_free_ char *s = NULL, *p = NULL; - size_t size; - int r; - - assert_return(ifindex > 0, -EINVAL); - assert_return(lldp, -EINVAL); - - if (asprintf(&p, "/run/systemd/netif/lldp/%d", ifindex) < 0) - return -ENOMEM; - - r = read_full_file(p, &s, &size); - if (r == -ENOENT) - return -ENODATA; - if (r < 0) - return r; - if (size <= 0) - return -ENODATA; - - *lldp = s; - s = NULL; - - return 0; -} - -int sd_network_link_get_timezone(int ifindex, char **ret) { - return network_link_get_string(ifindex, "TIMEZONE", ret); -} - -_public_ int sd_network_link_get_dns(int ifindex, char ***ret) { - return network_link_get_strv(ifindex, "DNS", ret); -} - -_public_ int sd_network_link_get_ntp(int ifindex, char ***ret) { - return network_link_get_strv(ifindex, "NTP", ret); -} - -_public_ int sd_network_link_get_search_domains(int ifindex, char ***ret) { - return network_link_get_strv(ifindex, "DOMAINS", ret); -} - -_public_ int sd_network_link_get_route_domains(int ifindex, char ***ret) { - return network_link_get_strv(ifindex, "ROUTE_DOMAINS", ret); -} - -_public_ int sd_network_link_get_carrier_bound_to(int ifindex, char ***ret) { - return network_link_get_strv(ifindex, "CARRIER_BOUND_TO", ret); -} - -_public_ int sd_network_link_get_carrier_bound_by(int ifindex, char ***ret) { - return network_link_get_strv(ifindex, "CARRIER_BOUND_BY", ret); -} - -static inline int MONITOR_TO_FD(sd_network_monitor *m) { - return (int) (unsigned long) m - 1; -} - -static inline sd_network_monitor* FD_TO_MONITOR(int fd) { - return (sd_network_monitor*) (unsigned long) (fd + 1); -} - -static int monitor_add_inotify_watch(int fd) { - int k; - - k = inotify_add_watch(fd, "/run/systemd/netif/links/", IN_MOVED_TO|IN_DELETE); - if (k >= 0) - return 0; - else if (errno != ENOENT) - return -errno; - - k = inotify_add_watch(fd, "/run/systemd/netif/", IN_CREATE|IN_ISDIR); - if (k >= 0) - return 0; - else if (errno != ENOENT) - return -errno; - - k = inotify_add_watch(fd, "/run/systemd/", IN_CREATE|IN_ISDIR); - if (k < 0) - return -errno; - - return 0; -} - -_public_ int sd_network_monitor_new(sd_network_monitor **m, const char *category) { - _cleanup_close_ int fd = -1; - int k; - bool good = false; - - assert_return(m, -EINVAL); - - fd = inotify_init1(IN_NONBLOCK|IN_CLOEXEC); - if (fd < 0) - return -errno; - - if (!category || streq(category, "links")) { - k = monitor_add_inotify_watch(fd); - if (k < 0) - return k; - - good = true; - } - - if (!good) - return -EINVAL; - - *m = FD_TO_MONITOR(fd); - fd = -1; - - return 0; -} - -_public_ sd_network_monitor* sd_network_monitor_unref(sd_network_monitor *m) { - int fd; - - if (m) { - fd = MONITOR_TO_FD(m); - close_nointr(fd); - } - - return NULL; -} - -_public_ int sd_network_monitor_flush(sd_network_monitor *m) { - union inotify_event_buffer buffer; - struct inotify_event *e; - ssize_t l; - int fd, k; - - assert_return(m, -EINVAL); - - fd = MONITOR_TO_FD(m); - - l = read(fd, &buffer, sizeof(buffer)); - if (l < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - FOREACH_INOTIFY_EVENT(e, buffer, l) { - if (e->mask & IN_ISDIR) { - k = monitor_add_inotify_watch(fd); - if (k < 0) - return k; - - k = inotify_rm_watch(fd, e->wd); - if (k < 0) - return -errno; - } - } - - return 0; -} - -_public_ int sd_network_monitor_get_fd(sd_network_monitor *m) { - - assert_return(m, -EINVAL); - - return MONITOR_TO_FD(m); -} - -_public_ int sd_network_monitor_get_events(sd_network_monitor *m) { - - assert_return(m, -EINVAL); - - /* For now we will only return POLLIN here, since we don't - * need anything else ever for inotify. However, let's have - * this API to keep our options open should we later on need - * it. */ - return POLLIN; -} - -_public_ int sd_network_monitor_get_timeout(sd_network_monitor *m, uint64_t *timeout_usec) { - - assert_return(m, -EINVAL); - assert_return(timeout_usec, -EINVAL); - - /* For now we will only return (uint64_t) -1, since we don't - * need any timeout. However, let's have this API to keep our - * options open should we later on need it. */ - *timeout_usec = (uint64_t) -1; - return 0; -} diff --git a/src/libsystemd/sd-path/Makefile b/src/libsystemd/sd-path/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd/sd-path/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-path/sd-path.c b/src/libsystemd/sd-path/sd-path.c deleted file mode 100644 index 480f1ad065..0000000000 --- a/src/libsystemd/sd-path/sd-path.c +++ /dev/null @@ -1,633 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "sd-path.h" - -#include "alloc-util.h" -#include "architecture.h" -#include "fd-util.h" -#include "fileio.h" -#include "missing.h" -#include "path-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -static int from_environment(const char *envname, const char *fallback, const char **ret) { - assert(ret); - - if (envname) { - const char *e; - - e = secure_getenv(envname); - if (e && path_is_absolute(e)) { - *ret = e; - return 0; - } - } - - if (fallback) { - *ret = fallback; - return 0; - } - - return -ENXIO; -} - -static int from_home_dir(const char *envname, const char *suffix, char **buffer, const char **ret) { - _cleanup_free_ char *h = NULL; - char *cc = NULL; - int r; - - assert(suffix); - assert(buffer); - assert(ret); - - if (envname) { - const char *e = NULL; - - e = secure_getenv(envname); - if (e && path_is_absolute(e)) { - *ret = e; - return 0; - } - } - - r = get_home_dir(&h); - if (r < 0) - return r; - - if (endswith(h, "/")) - cc = strappend(h, suffix); - else - cc = strjoin(h, "/", suffix, NULL); - if (!cc) - return -ENOMEM; - - *buffer = cc; - *ret = cc; - return 0; -} - -static int from_user_dir(const char *field, char **buffer, const char **ret) { - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *b = NULL; - const char *fn = NULL; - char line[LINE_MAX]; - size_t n; - int r; - - assert(field); - assert(buffer); - assert(ret); - - r = from_home_dir(NULL, ".config/user-dirs.dirs", &b, &fn); - if (r < 0) - return r; - - f = fopen(fn, "re"); - if (!f) { - if (errno == ENOENT) - goto fallback; - - return -errno; - } - - /* This is an awful parse, but it follows closely what - * xdg-user-dirs does upstream */ - - n = strlen(field); - FOREACH_LINE(line, f, return -errno) { - char *l, *p, *e; - - l = strstrip(line); - - if (!strneq(l, field, n)) - continue; - - p = l + n; - p += strspn(p, WHITESPACE); - - if (*p != '=') - continue; - p++; - - p += strspn(p, WHITESPACE); - - if (*p != '"') - continue; - p++; - - e = strrchr(p, '"'); - if (!e) - continue; - *e = 0; - - /* Three syntaxes permitted: relative to $HOME, $HOME itself, and absolute path */ - if (startswith(p, "$HOME/")) { - _cleanup_free_ char *h = NULL; - char *cc; - - r = get_home_dir(&h); - if (r < 0) - return r; - - cc = strappend(h, p+5); - if (!cc) - return -ENOMEM; - - *buffer = cc; - *ret = cc; - return 0; - } else if (streq(p, "$HOME")) { - - r = get_home_dir(buffer); - if (r < 0) - return r; - - *ret = *buffer; - return 0; - } else if (path_is_absolute(p)) { - char *copy; - - copy = strdup(p); - if (!copy) - return -ENOMEM; - - *buffer = copy; - *ret = copy; - return 0; - } - } - -fallback: - /* The desktop directory defaults to $HOME/Desktop, the others to $HOME */ - if (streq(field, "XDG_DESKTOP_DIR")) { - _cleanup_free_ char *h = NULL; - char *cc; - - r = get_home_dir(&h); - if (r < 0) - return r; - - cc = strappend(h, "/Desktop"); - if (!cc) - return -ENOMEM; - - *buffer = cc; - *ret = cc; - } else { - - r = get_home_dir(buffer); - if (r < 0) - return r; - - *ret = *buffer; - } - - return 0; -} - -static int get_path(uint64_t type, char **buffer, const char **ret) { - int r; - - assert(buffer); - assert(ret); - - switch (type) { - - case SD_PATH_TEMPORARY: - return from_environment("TMPDIR", "/tmp", ret); - - case SD_PATH_TEMPORARY_LARGE: - return from_environment("TMPDIR", "/var/tmp", ret); - - case SD_PATH_SYSTEM_BINARIES: - *ret = "/usr/bin"; - return 0; - - case SD_PATH_SYSTEM_INCLUDE: - *ret = "/usr/include"; - return 0; - - case SD_PATH_SYSTEM_LIBRARY_PRIVATE: - *ret = "/usr/lib"; - return 0; - - case SD_PATH_SYSTEM_LIBRARY_ARCH: - *ret = LIBDIR; - return 0; - - case SD_PATH_SYSTEM_SHARED: - *ret = "/usr/share"; - return 0; - - case SD_PATH_SYSTEM_CONFIGURATION_FACTORY: - *ret = "/usr/share/factory/etc"; - return 0; - - case SD_PATH_SYSTEM_STATE_FACTORY: - *ret = "/usr/share/factory/var"; - return 0; - - case SD_PATH_SYSTEM_CONFIGURATION: - *ret = "/etc"; - return 0; - - case SD_PATH_SYSTEM_RUNTIME: - *ret = "/run"; - return 0; - - case SD_PATH_SYSTEM_RUNTIME_LOGS: - *ret = "/run/log"; - return 0; - - case SD_PATH_SYSTEM_STATE_PRIVATE: - *ret = "/var/lib"; - return 0; - - case SD_PATH_SYSTEM_STATE_LOGS: - *ret = "/var/log"; - return 0; - - case SD_PATH_SYSTEM_STATE_CACHE: - *ret = "/var/cache"; - return 0; - - case SD_PATH_SYSTEM_STATE_SPOOL: - *ret = "/var/spool"; - return 0; - - case SD_PATH_USER_BINARIES: - return from_home_dir(NULL, ".local/bin", buffer, ret); - - case SD_PATH_USER_LIBRARY_PRIVATE: - return from_home_dir(NULL, ".local/lib", buffer, ret); - - case SD_PATH_USER_LIBRARY_ARCH: - return from_home_dir(NULL, ".local/lib/" LIB_ARCH_TUPLE, buffer, ret); - - case SD_PATH_USER_SHARED: - return from_home_dir("XDG_DATA_HOME", ".local/share", buffer, ret); - - case SD_PATH_USER_CONFIGURATION: - return from_home_dir("XDG_CONFIG_HOME", ".config", buffer, ret); - - case SD_PATH_USER_RUNTIME: - return from_environment("XDG_RUNTIME_DIR", NULL, ret); - - case SD_PATH_USER_STATE_CACHE: - return from_home_dir("XDG_CACHE_HOME", ".cache", buffer, ret); - - case SD_PATH_USER: - r = get_home_dir(buffer); - if (r < 0) - return r; - - *ret = *buffer; - return 0; - - case SD_PATH_USER_DOCUMENTS: - return from_user_dir("XDG_DOCUMENTS_DIR", buffer, ret); - - case SD_PATH_USER_MUSIC: - return from_user_dir("XDG_MUSIC_DIR", buffer, ret); - - case SD_PATH_USER_PICTURES: - return from_user_dir("XDG_PICTURES_DIR", buffer, ret); - - case SD_PATH_USER_VIDEOS: - return from_user_dir("XDG_VIDEOS_DIR", buffer, ret); - - case SD_PATH_USER_DOWNLOAD: - return from_user_dir("XDG_DOWNLOAD_DIR", buffer, ret); - - case SD_PATH_USER_PUBLIC: - return from_user_dir("XDG_PUBLICSHARE_DIR", buffer, ret); - - case SD_PATH_USER_TEMPLATES: - return from_user_dir("XDG_TEMPLATES_DIR", buffer, ret); - - case SD_PATH_USER_DESKTOP: - return from_user_dir("XDG_DESKTOP_DIR", buffer, ret); - } - - return -EOPNOTSUPP; -} - -_public_ int sd_path_home(uint64_t type, const char *suffix, char **path) { - char *buffer = NULL, *cc; - const char *ret; - int r; - - assert_return(path, -EINVAL); - - if (IN_SET(type, - SD_PATH_SEARCH_BINARIES, - SD_PATH_SEARCH_LIBRARY_PRIVATE, - SD_PATH_SEARCH_LIBRARY_ARCH, - SD_PATH_SEARCH_SHARED, - SD_PATH_SEARCH_CONFIGURATION_FACTORY, - SD_PATH_SEARCH_STATE_FACTORY, - SD_PATH_SEARCH_CONFIGURATION)) { - - _cleanup_strv_free_ char **l = NULL; - - r = sd_path_search(type, suffix, &l); - if (r < 0) - return r; - - buffer = strv_join(l, ":"); - if (!buffer) - return -ENOMEM; - - *path = buffer; - return 0; - } - - r = get_path(type, &buffer, &ret); - if (r < 0) - return r; - - if (!suffix) { - if (!buffer) { - buffer = strdup(ret); - if (!buffer) - return -ENOMEM; - } - - *path = buffer; - return 0; - } - - suffix += strspn(suffix, "/"); - - if (endswith(ret, "/")) - cc = strappend(ret, suffix); - else - cc = strjoin(ret, "/", suffix, NULL); - - free(buffer); - - if (!cc) - return -ENOMEM; - - *path = cc; - return 0; -} - -static int search_from_environment( - char ***list, - const char *env_home, - const char *home_suffix, - const char *env_search, - bool env_search_sufficient, - const char *first, ...) { - - const char *e; - char *h = NULL; - char **l = NULL; - int r; - - assert(list); - - if (env_search) { - e = secure_getenv(env_search); - if (e) { - l = strv_split(e, ":"); - if (!l) - return -ENOMEM; - - if (env_search_sufficient) { - *list = l; - return 0; - } - } - } - - if (!l && first) { - va_list ap; - - va_start(ap, first); - l = strv_new_ap(first, ap); - va_end(ap); - - if (!l) - return -ENOMEM; - } - - if (env_home) { - e = secure_getenv(env_home); - if (e && path_is_absolute(e)) { - h = strdup(e); - if (!h) { - strv_free(l); - return -ENOMEM; - } - } - } - - if (!h && home_suffix) { - e = secure_getenv("HOME"); - if (e && path_is_absolute(e)) { - if (endswith(e, "/")) - h = strappend(e, home_suffix); - else - h = strjoin(e, "/", home_suffix, NULL); - - if (!h) { - strv_free(l); - return -ENOMEM; - } - } - } - - if (h) { - r = strv_consume_prepend(&l, h); - if (r < 0) { - strv_free(l); - return -ENOMEM; - } - } - - *list = l; - return 0; -} - -static int get_search(uint64_t type, char ***list) { - - assert(list); - - switch(type) { - - case SD_PATH_SEARCH_BINARIES: - return search_from_environment(list, - NULL, - ".local/bin", - "PATH", - true, - "/usr/local/sbin", - "/usr/local/bin", - "/usr/sbin", - "/usr/bin", -#ifdef HAVE_SPLIT_USR - "/sbin", - "/bin", -#endif - NULL); - - case SD_PATH_SEARCH_LIBRARY_PRIVATE: - return search_from_environment(list, - NULL, - ".local/lib", - NULL, - false, - "/usr/local/lib", - "/usr/lib", -#ifdef HAVE_SPLIT_USR - "/lib", -#endif - NULL); - - case SD_PATH_SEARCH_LIBRARY_ARCH: - return search_from_environment(list, - NULL, - ".local/lib/" LIB_ARCH_TUPLE, - "LD_LIBRARY_PATH", - true, - LIBDIR, -#ifdef HAVE_SPLIT_USR - ROOTLIBDIR, -#endif - NULL); - - case SD_PATH_SEARCH_SHARED: - return search_from_environment(list, - "XDG_DATA_HOME", - ".local/share", - "XDG_DATA_DIRS", - false, - "/usr/local/share", - "/usr/share", - NULL); - - case SD_PATH_SEARCH_CONFIGURATION_FACTORY: - return search_from_environment(list, - NULL, - NULL, - NULL, - false, - "/usr/local/share/factory/etc", - "/usr/share/factory/etc", - NULL); - - case SD_PATH_SEARCH_STATE_FACTORY: - return search_from_environment(list, - NULL, - NULL, - NULL, - false, - "/usr/local/share/factory/var", - "/usr/share/factory/var", - NULL); - - case SD_PATH_SEARCH_CONFIGURATION: - return search_from_environment(list, - "XDG_CONFIG_HOME", - ".config", - "XDG_CONFIG_DIRS", - false, - "/etc", - NULL); - } - - return -EOPNOTSUPP; -} - -_public_ int sd_path_search(uint64_t type, const char *suffix, char ***paths) { - char **l, **i, **j, **n; - int r; - - assert_return(paths, -EINVAL); - - if (!IN_SET(type, - SD_PATH_SEARCH_BINARIES, - SD_PATH_SEARCH_LIBRARY_PRIVATE, - SD_PATH_SEARCH_LIBRARY_ARCH, - SD_PATH_SEARCH_SHARED, - SD_PATH_SEARCH_CONFIGURATION_FACTORY, - SD_PATH_SEARCH_STATE_FACTORY, - SD_PATH_SEARCH_CONFIGURATION)) { - - char *p; - - r = sd_path_home(type, suffix, &p); - if (r < 0) - return r; - - l = new(char*, 2); - if (!l) { - free(p); - return -ENOMEM; - } - - l[0] = p; - l[1] = NULL; - - *paths = l; - return 0; - } - - r = get_search(type, &l); - if (r < 0) - return r; - - if (!suffix) { - *paths = l; - return 0; - } - - n = new(char*, strv_length(l)+1); - if (!n) { - strv_free(l); - return -ENOMEM; - } - - j = n; - STRV_FOREACH(i, l) { - - if (endswith(*i, "/")) - *j = strappend(*i, suffix); - else - *j = strjoin(*i, "/", suffix, NULL); - - if (!*j) { - strv_free(l); - strv_free(n); - return -ENOMEM; - } - - j++; - } - - *j = NULL; - *paths = n; - return 0; -} diff --git a/src/libsystemd/sd-resolve/Makefile b/src/libsystemd/sd-resolve/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-resolve/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-resolve/sd-resolve.c b/src/libsystemd/sd-resolve/sd-resolve.c deleted file mode 100644 index 653dbfbe57..0000000000 --- a/src/libsystemd/sd-resolve/sd-resolve.c +++ /dev/null @@ -1,1245 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2005-2008 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-resolve.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "list.h" -#include "missing.h" -#include "socket-util.h" -#include "util.h" - -#define WORKERS_MIN 1U -#define WORKERS_MAX 16U -#define QUERIES_MAX 256U -#define BUFSIZE 10240U - -typedef enum { - REQUEST_ADDRINFO, - RESPONSE_ADDRINFO, - REQUEST_NAMEINFO, - RESPONSE_NAMEINFO, - REQUEST_TERMINATE, - RESPONSE_DIED -} QueryType; - -enum { - REQUEST_RECV_FD, - REQUEST_SEND_FD, - RESPONSE_RECV_FD, - RESPONSE_SEND_FD, - _FD_MAX -}; - -struct sd_resolve { - unsigned n_ref; - - bool dead:1; - pid_t original_pid; - - int fds[_FD_MAX]; - - pthread_t workers[WORKERS_MAX]; - unsigned n_valid_workers; - - unsigned current_id; - sd_resolve_query* query_array[QUERIES_MAX]; - unsigned n_queries, n_done, n_outstanding; - - sd_event_source *event_source; - sd_event *event; - - sd_resolve_query *current; - - sd_resolve **default_resolve_ptr; - pid_t tid; - - LIST_HEAD(sd_resolve_query, queries); -}; - -struct sd_resolve_query { - unsigned n_ref; - - sd_resolve *resolve; - - QueryType type:4; - bool done:1; - bool floating:1; - unsigned id; - - int ret; - int _errno; - int _h_errno; - struct addrinfo *addrinfo; - char *serv, *host; - - union { - sd_resolve_getaddrinfo_handler_t getaddrinfo_handler; - sd_resolve_getnameinfo_handler_t getnameinfo_handler; - }; - - void *userdata; - - LIST_FIELDS(sd_resolve_query, queries); -}; - -typedef struct RHeader { - QueryType type; - unsigned id; - size_t length; -} RHeader; - -typedef struct AddrInfoRequest { - struct RHeader header; - bool hints_valid; - int ai_flags; - int ai_family; - int ai_socktype; - int ai_protocol; - size_t node_len, service_len; -} AddrInfoRequest; - -typedef struct AddrInfoResponse { - struct RHeader header; - int ret; - int _errno; - int _h_errno; - /* followed by addrinfo_serialization[] */ -} AddrInfoResponse; - -typedef struct AddrInfoSerialization { - int ai_flags; - int ai_family; - int ai_socktype; - int ai_protocol; - size_t ai_addrlen; - size_t canonname_len; - /* Followed by ai_addr amd ai_canonname with variable lengths */ -} AddrInfoSerialization; - -typedef struct NameInfoRequest { - struct RHeader header; - int flags; - socklen_t sockaddr_len; - bool gethost:1, getserv:1; -} NameInfoRequest; - -typedef struct NameInfoResponse { - struct RHeader header; - size_t hostlen, servlen; - int ret; - int _errno; - int _h_errno; -} NameInfoResponse; - -typedef union Packet { - RHeader rheader; - AddrInfoRequest addrinfo_request; - AddrInfoResponse addrinfo_response; - NameInfoRequest nameinfo_request; - NameInfoResponse nameinfo_response; -} Packet; - -static int getaddrinfo_done(sd_resolve_query* q); -static int getnameinfo_done(sd_resolve_query *q); - -static void resolve_query_disconnect(sd_resolve_query *q); - -#define RESOLVE_DONT_DESTROY(resolve) \ - _cleanup_(sd_resolve_unrefp) _unused_ sd_resolve *_dont_destroy_##resolve = sd_resolve_ref(resolve) - -static int send_died(int out_fd) { - - RHeader rh = { - .type = RESPONSE_DIED, - .length = sizeof(RHeader), - }; - - assert(out_fd >= 0); - - if (send(out_fd, &rh, rh.length, MSG_NOSIGNAL) < 0) - return -errno; - - return 0; -} - -static void *serialize_addrinfo(void *p, const struct addrinfo *ai, size_t *length, size_t maxlength) { - AddrInfoSerialization s; - size_t cnl, l; - - assert(p); - assert(ai); - assert(length); - assert(*length <= maxlength); - - cnl = ai->ai_canonname ? strlen(ai->ai_canonname)+1 : 0; - l = sizeof(AddrInfoSerialization) + ai->ai_addrlen + cnl; - - if (*length + l > maxlength) - return NULL; - - s.ai_flags = ai->ai_flags; - s.ai_family = ai->ai_family; - s.ai_socktype = ai->ai_socktype; - s.ai_protocol = ai->ai_protocol; - s.ai_addrlen = ai->ai_addrlen; - s.canonname_len = cnl; - - memcpy((uint8_t*) p, &s, sizeof(AddrInfoSerialization)); - memcpy((uint8_t*) p + sizeof(AddrInfoSerialization), ai->ai_addr, ai->ai_addrlen); - - if (ai->ai_canonname) - memcpy((char*) p + sizeof(AddrInfoSerialization) + ai->ai_addrlen, ai->ai_canonname, cnl); - - *length += l; - return (uint8_t*) p + l; -} - -static int send_addrinfo_reply( - int out_fd, - unsigned id, - int ret, - struct addrinfo *ai, - int _errno, - int _h_errno) { - - AddrInfoResponse resp = { - .header.type = RESPONSE_ADDRINFO, - .header.id = id, - .header.length = sizeof(AddrInfoResponse), - .ret = ret, - ._errno = _errno, - ._h_errno = _h_errno, - }; - - struct msghdr mh = {}; - struct iovec iov[2]; - union { - AddrInfoSerialization ais; - uint8_t space[BUFSIZE]; - } buffer; - - assert(out_fd >= 0); - - if (ret == 0 && ai) { - void *p = &buffer; - struct addrinfo *k; - - for (k = ai; k; k = k->ai_next) { - p = serialize_addrinfo(p, k, &resp.header.length, (uint8_t*) &buffer + BUFSIZE - (uint8_t*) p); - if (!p) { - freeaddrinfo(ai); - return -ENOBUFS; - } - } - } - - if (ai) - freeaddrinfo(ai); - - iov[0] = (struct iovec) { .iov_base = &resp, .iov_len = sizeof(AddrInfoResponse) }; - iov[1] = (struct iovec) { .iov_base = &buffer, .iov_len = resp.header.length - sizeof(AddrInfoResponse) }; - - mh.msg_iov = iov; - mh.msg_iovlen = ELEMENTSOF(iov); - - if (sendmsg(out_fd, &mh, MSG_NOSIGNAL) < 0) - return -errno; - - return 0; -} - -static int send_nameinfo_reply( - int out_fd, - unsigned id, - int ret, - const char *host, - const char *serv, - int _errno, - int _h_errno) { - - NameInfoResponse resp = { - .header.type = RESPONSE_NAMEINFO, - .header.id = id, - .ret = ret, - ._errno = _errno, - ._h_errno = _h_errno, - }; - - struct msghdr mh = {}; - struct iovec iov[3]; - size_t hl, sl; - - assert(out_fd >= 0); - - sl = serv ? strlen(serv)+1 : 0; - hl = host ? strlen(host)+1 : 0; - - resp.header.length = sizeof(NameInfoResponse) + hl + sl; - resp.hostlen = hl; - resp.servlen = sl; - - iov[0] = (struct iovec) { .iov_base = &resp, .iov_len = sizeof(NameInfoResponse) }; - iov[1] = (struct iovec) { .iov_base = (void*) host, .iov_len = hl }; - iov[2] = (struct iovec) { .iov_base = (void*) serv, .iov_len = sl }; - - mh.msg_iov = iov; - mh.msg_iovlen = ELEMENTSOF(iov); - - if (sendmsg(out_fd, &mh, MSG_NOSIGNAL) < 0) - return -errno; - - return 0; -} - -static int handle_request(int out_fd, const Packet *packet, size_t length) { - const RHeader *req; - - assert(out_fd >= 0); - assert(packet); - - req = &packet->rheader; - - assert(length >= sizeof(RHeader)); - assert(length == req->length); - - switch (req->type) { - - case REQUEST_ADDRINFO: { - const AddrInfoRequest *ai_req = &packet->addrinfo_request; - struct addrinfo hints = {}, *result = NULL; - const char *node, *service; - int ret; - - assert(length >= sizeof(AddrInfoRequest)); - assert(length == sizeof(AddrInfoRequest) + ai_req->node_len + ai_req->service_len); - - hints.ai_flags = ai_req->ai_flags; - hints.ai_family = ai_req->ai_family; - hints.ai_socktype = ai_req->ai_socktype; - hints.ai_protocol = ai_req->ai_protocol; - - node = ai_req->node_len ? (const char*) ai_req + sizeof(AddrInfoRequest) : NULL; - service = ai_req->service_len ? (const char*) ai_req + sizeof(AddrInfoRequest) + ai_req->node_len : NULL; - - ret = getaddrinfo( - node, service, - ai_req->hints_valid ? &hints : NULL, - &result); - - /* send_addrinfo_reply() frees result */ - return send_addrinfo_reply(out_fd, req->id, ret, result, errno, h_errno); - } - - case REQUEST_NAMEINFO: { - const NameInfoRequest *ni_req = &packet->nameinfo_request; - char hostbuf[NI_MAXHOST], servbuf[NI_MAXSERV]; - union sockaddr_union sa; - int ret; - - assert(length >= sizeof(NameInfoRequest)); - assert(length == sizeof(NameInfoRequest) + ni_req->sockaddr_len); - assert(sizeof(sa) >= ni_req->sockaddr_len); - - memcpy(&sa, (const uint8_t *) ni_req + sizeof(NameInfoRequest), ni_req->sockaddr_len); - - ret = getnameinfo(&sa.sa, ni_req->sockaddr_len, - ni_req->gethost ? hostbuf : NULL, ni_req->gethost ? sizeof(hostbuf) : 0, - ni_req->getserv ? servbuf : NULL, ni_req->getserv ? sizeof(servbuf) : 0, - ni_req->flags); - - return send_nameinfo_reply(out_fd, req->id, ret, - ret == 0 && ni_req->gethost ? hostbuf : NULL, - ret == 0 && ni_req->getserv ? servbuf : NULL, - errno, h_errno); - } - - case REQUEST_TERMINATE: - /* Quit */ - return -ECONNRESET; - - default: - assert_not_reached("Unknown request"); - } - - return 0; -} - -static void* thread_worker(void *p) { - sd_resolve *resolve = p; - sigset_t fullset; - - /* No signals in this thread please */ - assert_se(sigfillset(&fullset) == 0); - assert_se(pthread_sigmask(SIG_BLOCK, &fullset, NULL) == 0); - - /* Assign a pretty name to this thread */ - prctl(PR_SET_NAME, (unsigned long) "sd-resolve"); - - while (!resolve->dead) { - union { - Packet packet; - uint8_t space[BUFSIZE]; - } buf; - ssize_t length; - - length = recv(resolve->fds[REQUEST_RECV_FD], &buf, sizeof(buf), 0); - if (length < 0) { - if (errno == EINTR) - continue; - - break; - } - if (length == 0) - break; - - if (resolve->dead) - break; - - if (handle_request(resolve->fds[RESPONSE_SEND_FD], &buf.packet, (size_t) length) < 0) - break; - } - - send_died(resolve->fds[RESPONSE_SEND_FD]); - - return NULL; -} - -static int start_threads(sd_resolve *resolve, unsigned extra) { - unsigned n; - int r; - - n = resolve->n_outstanding + extra; - n = CLAMP(n, WORKERS_MIN, WORKERS_MAX); - - while (resolve->n_valid_workers < n) { - - r = pthread_create(&resolve->workers[resolve->n_valid_workers], NULL, thread_worker, resolve); - if (r != 0) - return -r; - - resolve->n_valid_workers ++; - } - - return 0; -} - -static bool resolve_pid_changed(sd_resolve *r) { - assert(r); - - /* We don't support people creating a resolver and keeping it - * around after fork(). Let's complain. */ - - return r->original_pid != getpid(); -} - -_public_ int sd_resolve_new(sd_resolve **ret) { - sd_resolve *resolve = NULL; - int i, r; - - assert_return(ret, -EINVAL); - - resolve = new0(sd_resolve, 1); - if (!resolve) - return -ENOMEM; - - resolve->n_ref = 1; - resolve->original_pid = getpid(); - - for (i = 0; i < _FD_MAX; i++) - resolve->fds[i] = -1; - - r = socketpair(PF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, resolve->fds + REQUEST_RECV_FD); - if (r < 0) { - r = -errno; - goto fail; - } - - r = socketpair(PF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0, resolve->fds + RESPONSE_RECV_FD); - if (r < 0) { - r = -errno; - goto fail; - } - - fd_inc_sndbuf(resolve->fds[REQUEST_SEND_FD], QUERIES_MAX * BUFSIZE); - fd_inc_rcvbuf(resolve->fds[REQUEST_RECV_FD], QUERIES_MAX * BUFSIZE); - fd_inc_sndbuf(resolve->fds[RESPONSE_SEND_FD], QUERIES_MAX * BUFSIZE); - fd_inc_rcvbuf(resolve->fds[RESPONSE_RECV_FD], QUERIES_MAX * BUFSIZE); - - fd_nonblock(resolve->fds[RESPONSE_RECV_FD], true); - - *ret = resolve; - return 0; - -fail: - sd_resolve_unref(resolve); - return r; -} - -_public_ int sd_resolve_default(sd_resolve **ret) { - - static thread_local sd_resolve *default_resolve = NULL; - sd_resolve *e = NULL; - int r; - - if (!ret) - return !!default_resolve; - - if (default_resolve) { - *ret = sd_resolve_ref(default_resolve); - return 0; - } - - r = sd_resolve_new(&e); - if (r < 0) - return r; - - e->default_resolve_ptr = &default_resolve; - e->tid = gettid(); - default_resolve = e; - - *ret = e; - return 1; -} - -_public_ int sd_resolve_get_tid(sd_resolve *resolve, pid_t *tid) { - assert_return(resolve, -EINVAL); - assert_return(tid, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - if (resolve->tid != 0) { - *tid = resolve->tid; - return 0; - } - - if (resolve->event) - return sd_event_get_tid(resolve->event, tid); - - return -ENXIO; -} - -static void resolve_free(sd_resolve *resolve) { - PROTECT_ERRNO; - sd_resolve_query *q; - unsigned i; - - assert(resolve); - - while ((q = resolve->queries)) { - assert(q->floating); - resolve_query_disconnect(q); - sd_resolve_query_unref(q); - } - - if (resolve->default_resolve_ptr) - *(resolve->default_resolve_ptr) = NULL; - - resolve->dead = true; - - sd_resolve_detach_event(resolve); - - if (resolve->fds[REQUEST_SEND_FD] >= 0) { - - RHeader req = { - .type = REQUEST_TERMINATE, - .length = sizeof(req) - }; - - /* Send one termination packet for each worker */ - for (i = 0; i < resolve->n_valid_workers; i++) - (void) send(resolve->fds[REQUEST_SEND_FD], &req, req.length, MSG_NOSIGNAL); - } - - /* Now terminate them and wait until they are gone. */ - for (i = 0; i < resolve->n_valid_workers; i++) - pthread_join(resolve->workers[i], NULL); - - /* Close all communication channels */ - for (i = 0; i < _FD_MAX; i++) - safe_close(resolve->fds[i]); - - free(resolve); -} - -_public_ sd_resolve* sd_resolve_ref(sd_resolve *resolve) { - assert_return(resolve, NULL); - - assert(resolve->n_ref >= 1); - resolve->n_ref++; - - return resolve; -} - -_public_ sd_resolve* sd_resolve_unref(sd_resolve *resolve) { - - if (!resolve) - return NULL; - - assert(resolve->n_ref >= 1); - resolve->n_ref--; - - if (resolve->n_ref <= 0) - resolve_free(resolve); - - return NULL; -} - -_public_ int sd_resolve_get_fd(sd_resolve *resolve) { - assert_return(resolve, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - return resolve->fds[RESPONSE_RECV_FD]; -} - -_public_ int sd_resolve_get_events(sd_resolve *resolve) { - assert_return(resolve, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - return resolve->n_queries > resolve->n_done ? POLLIN : 0; -} - -_public_ int sd_resolve_get_timeout(sd_resolve *resolve, uint64_t *usec) { - assert_return(resolve, -EINVAL); - assert_return(usec, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - *usec = (uint64_t) -1; - return 0; -} - -static sd_resolve_query *lookup_query(sd_resolve *resolve, unsigned id) { - sd_resolve_query *q; - - assert(resolve); - - q = resolve->query_array[id % QUERIES_MAX]; - if (q) - if (q->id == id) - return q; - - return NULL; -} - -static int complete_query(sd_resolve *resolve, sd_resolve_query *q) { - int r; - - assert(q); - assert(!q->done); - assert(q->resolve == resolve); - - q->done = true; - resolve->n_done ++; - - resolve->current = sd_resolve_query_ref(q); - - switch (q->type) { - - case REQUEST_ADDRINFO: - r = getaddrinfo_done(q); - break; - - case REQUEST_NAMEINFO: - r = getnameinfo_done(q); - break; - - default: - assert_not_reached("Cannot complete unknown query type"); - } - - resolve->current = NULL; - - if (q->floating) { - resolve_query_disconnect(q); - sd_resolve_query_unref(q); - } - - sd_resolve_query_unref(q); - - return r; -} - -static int unserialize_addrinfo(const void **p, size_t *length, struct addrinfo **ret_ai) { - AddrInfoSerialization s; - size_t l; - struct addrinfo *ai; - - assert(p); - assert(*p); - assert(ret_ai); - assert(length); - - if (*length < sizeof(AddrInfoSerialization)) - return -EBADMSG; - - memcpy(&s, *p, sizeof(s)); - - l = sizeof(AddrInfoSerialization) + s.ai_addrlen + s.canonname_len; - if (*length < l) - return -EBADMSG; - - ai = new0(struct addrinfo, 1); - if (!ai) - return -ENOMEM; - - ai->ai_flags = s.ai_flags; - ai->ai_family = s.ai_family; - ai->ai_socktype = s.ai_socktype; - ai->ai_protocol = s.ai_protocol; - ai->ai_addrlen = s.ai_addrlen; - - if (s.ai_addrlen > 0) { - ai->ai_addr = memdup((const uint8_t*) *p + sizeof(AddrInfoSerialization), s.ai_addrlen); - if (!ai->ai_addr) { - free(ai); - return -ENOMEM; - } - } - - if (s.canonname_len > 0) { - ai->ai_canonname = memdup((const uint8_t*) *p + sizeof(AddrInfoSerialization) + s.ai_addrlen, s.canonname_len); - if (!ai->ai_canonname) { - free(ai->ai_addr); - free(ai); - return -ENOMEM; - } - } - - *length -= l; - *ret_ai = ai; - *p = ((const uint8_t*) *p) + l; - - return 0; -} - -static int handle_response(sd_resolve *resolve, const Packet *packet, size_t length) { - const RHeader *resp; - sd_resolve_query *q; - int r; - - assert(resolve); - - resp = &packet->rheader; - assert(resp); - assert(length >= sizeof(RHeader)); - assert(length == resp->length); - - if (resp->type == RESPONSE_DIED) { - resolve->dead = true; - return 0; - } - - assert(resolve->n_outstanding > 0); - resolve->n_outstanding--; - - q = lookup_query(resolve, resp->id); - if (!q) - return 0; - - switch (resp->type) { - - case RESPONSE_ADDRINFO: { - const AddrInfoResponse *ai_resp = &packet->addrinfo_response; - const void *p; - size_t l; - struct addrinfo *prev = NULL; - - assert(length >= sizeof(AddrInfoResponse)); - assert(q->type == REQUEST_ADDRINFO); - - q->ret = ai_resp->ret; - q->_errno = ai_resp->_errno; - q->_h_errno = ai_resp->_h_errno; - - l = length - sizeof(AddrInfoResponse); - p = (const uint8_t*) resp + sizeof(AddrInfoResponse); - - while (l > 0 && p) { - struct addrinfo *ai = NULL; - - r = unserialize_addrinfo(&p, &l, &ai); - if (r < 0) { - q->ret = EAI_SYSTEM; - q->_errno = -r; - q->_h_errno = 0; - freeaddrinfo(q->addrinfo); - q->addrinfo = NULL; - break; - } - - if (prev) - prev->ai_next = ai; - else - q->addrinfo = ai; - - prev = ai; - } - - return complete_query(resolve, q); - } - - case RESPONSE_NAMEINFO: { - const NameInfoResponse *ni_resp = &packet->nameinfo_response; - - assert(length >= sizeof(NameInfoResponse)); - assert(q->type == REQUEST_NAMEINFO); - - q->ret = ni_resp->ret; - q->_errno = ni_resp->_errno; - q->_h_errno = ni_resp->_h_errno; - - if (ni_resp->hostlen > 0) { - q->host = strndup((const char*) ni_resp + sizeof(NameInfoResponse), ni_resp->hostlen-1); - if (!q->host) { - q->ret = EAI_MEMORY; - q->_errno = ENOMEM; - q->_h_errno = 0; - } - } - - if (ni_resp->servlen > 0) { - q->serv = strndup((const char*) ni_resp + sizeof(NameInfoResponse) + ni_resp->hostlen, ni_resp->servlen-1); - if (!q->serv) { - q->ret = EAI_MEMORY; - q->_errno = ENOMEM; - q->_h_errno = 0; - } - } - - return complete_query(resolve, q); - } - - default: - return 0; - } -} - -_public_ int sd_resolve_process(sd_resolve *resolve) { - RESOLVE_DONT_DESTROY(resolve); - - union { - Packet packet; - uint8_t space[BUFSIZE]; - } buf; - ssize_t l; - int r; - - assert_return(resolve, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - /* We don't allow recursively invoking sd_resolve_process(). */ - assert_return(!resolve->current, -EBUSY); - - l = recv(resolve->fds[RESPONSE_RECV_FD], &buf, sizeof(buf), 0); - if (l < 0) { - if (errno == EAGAIN) - return 0; - - return -errno; - } - if (l == 0) - return -ECONNREFUSED; - - r = handle_response(resolve, &buf.packet, (size_t) l); - if (r < 0) - return r; - - return 1; -} - -_public_ int sd_resolve_wait(sd_resolve *resolve, uint64_t timeout_usec) { - int r; - - assert_return(resolve, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - if (resolve->n_done >= resolve->n_queries) - return 0; - - do { - r = fd_wait_for_event(resolve->fds[RESPONSE_RECV_FD], POLLIN, timeout_usec); - } while (r == -EINTR); - - if (r < 0) - return r; - - return sd_resolve_process(resolve); -} - -static int alloc_query(sd_resolve *resolve, bool floating, sd_resolve_query **_q) { - sd_resolve_query *q; - int r; - - assert(resolve); - assert(_q); - - if (resolve->n_queries >= QUERIES_MAX) - return -ENOBUFS; - - r = start_threads(resolve, 1); - if (r < 0) - return r; - - while (resolve->query_array[resolve->current_id % QUERIES_MAX]) - resolve->current_id++; - - q = resolve->query_array[resolve->current_id % QUERIES_MAX] = new0(sd_resolve_query, 1); - if (!q) - return -ENOMEM; - - q->n_ref = 1; - q->resolve = resolve; - q->floating = floating; - q->id = resolve->current_id++; - - if (!floating) - sd_resolve_ref(resolve); - - LIST_PREPEND(queries, resolve->queries, q); - resolve->n_queries++; - - *_q = q; - return 0; -} - -_public_ int sd_resolve_getaddrinfo( - sd_resolve *resolve, - sd_resolve_query **_q, - const char *node, const char *service, - const struct addrinfo *hints, - sd_resolve_getaddrinfo_handler_t callback, void *userdata) { - - AddrInfoRequest req = {}; - struct msghdr mh = {}; - struct iovec iov[3]; - sd_resolve_query *q; - int r; - - assert_return(resolve, -EINVAL); - assert_return(node || service, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - r = alloc_query(resolve, !_q, &q); - if (r < 0) - return r; - - q->type = REQUEST_ADDRINFO; - q->getaddrinfo_handler = callback; - q->userdata = userdata; - - req.node_len = node ? strlen(node)+1 : 0; - req.service_len = service ? strlen(service)+1 : 0; - - req.header.id = q->id; - req.header.type = REQUEST_ADDRINFO; - req.header.length = sizeof(AddrInfoRequest) + req.node_len + req.service_len; - - if (hints) { - req.hints_valid = true; - req.ai_flags = hints->ai_flags; - req.ai_family = hints->ai_family; - req.ai_socktype = hints->ai_socktype; - req.ai_protocol = hints->ai_protocol; - } - - iov[mh.msg_iovlen++] = (struct iovec) { .iov_base = &req, .iov_len = sizeof(AddrInfoRequest) }; - if (node) - iov[mh.msg_iovlen++] = (struct iovec) { .iov_base = (void*) node, .iov_len = req.node_len }; - if (service) - iov[mh.msg_iovlen++] = (struct iovec) { .iov_base = (void*) service, .iov_len = req.service_len }; - mh.msg_iov = iov; - - if (sendmsg(resolve->fds[REQUEST_SEND_FD], &mh, MSG_NOSIGNAL) < 0) { - sd_resolve_query_unref(q); - return -errno; - } - - resolve->n_outstanding++; - - if (_q) - *_q = q; - - return 0; -} - -static int getaddrinfo_done(sd_resolve_query* q) { - assert(q); - assert(q->done); - assert(q->getaddrinfo_handler); - - errno = q->_errno; - h_errno = q->_h_errno; - - return q->getaddrinfo_handler(q, q->ret, q->addrinfo, q->userdata); -} - -_public_ int sd_resolve_getnameinfo( - sd_resolve *resolve, - sd_resolve_query**_q, - const struct sockaddr *sa, socklen_t salen, - int flags, - uint64_t get, - sd_resolve_getnameinfo_handler_t callback, - void *userdata) { - - NameInfoRequest req = {}; - struct msghdr mh = {}; - struct iovec iov[2]; - sd_resolve_query *q; - int r; - - assert_return(resolve, -EINVAL); - assert_return(sa, -EINVAL); - assert_return(salen >= sizeof(struct sockaddr), -EINVAL); - assert_return(salen <= sizeof(union sockaddr_union), -EINVAL); - assert_return((get & ~SD_RESOLVE_GET_BOTH) == 0, -EINVAL); - assert_return(callback, -EINVAL); - assert_return(!resolve_pid_changed(resolve), -ECHILD); - - r = alloc_query(resolve, !_q, &q); - if (r < 0) - return r; - - q->type = REQUEST_NAMEINFO; - q->getnameinfo_handler = callback; - q->userdata = userdata; - - req.header.id = q->id; - req.header.type = REQUEST_NAMEINFO; - req.header.length = sizeof(NameInfoRequest) + salen; - - req.flags = flags; - req.sockaddr_len = salen; - req.gethost = !!(get & SD_RESOLVE_GET_HOST); - req.getserv = !!(get & SD_RESOLVE_GET_SERVICE); - - iov[0] = (struct iovec) { .iov_base = &req, .iov_len = sizeof(NameInfoRequest) }; - iov[1] = (struct iovec) { .iov_base = (void*) sa, .iov_len = salen }; - - mh.msg_iov = iov; - mh.msg_iovlen = 2; - - if (sendmsg(resolve->fds[REQUEST_SEND_FD], &mh, MSG_NOSIGNAL) < 0) { - sd_resolve_query_unref(q); - return -errno; - } - - resolve->n_outstanding++; - - if (_q) - *_q = q; - - return 0; -} - -static int getnameinfo_done(sd_resolve_query *q) { - - assert(q); - assert(q->done); - assert(q->getnameinfo_handler); - - errno = q->_errno; - h_errno= q->_h_errno; - - return q->getnameinfo_handler(q, q->ret, q->host, q->serv, q->userdata); -} - -_public_ sd_resolve_query* sd_resolve_query_ref(sd_resolve_query *q) { - assert_return(q, NULL); - - assert(q->n_ref >= 1); - q->n_ref++; - - return q; -} - -static void resolve_freeaddrinfo(struct addrinfo *ai) { - while (ai) { - struct addrinfo *next = ai->ai_next; - - free(ai->ai_addr); - free(ai->ai_canonname); - free(ai); - ai = next; - } -} - -static void resolve_query_disconnect(sd_resolve_query *q) { - sd_resolve *resolve; - unsigned i; - - assert(q); - - if (!q->resolve) - return; - - resolve = q->resolve; - assert(resolve->n_queries > 0); - - if (q->done) { - assert(resolve->n_done > 0); - resolve->n_done--; - } - - i = q->id % QUERIES_MAX; - assert(resolve->query_array[i] == q); - resolve->query_array[i] = NULL; - LIST_REMOVE(queries, resolve->queries, q); - resolve->n_queries--; - - q->resolve = NULL; - if (!q->floating) - sd_resolve_unref(resolve); -} - -static void resolve_query_free(sd_resolve_query *q) { - assert(q); - - resolve_query_disconnect(q); - - resolve_freeaddrinfo(q->addrinfo); - free(q->host); - free(q->serv); - free(q); -} - -_public_ sd_resolve_query* sd_resolve_query_unref(sd_resolve_query* q) { - if (!q) - return NULL; - - assert(q->n_ref >= 1); - q->n_ref--; - - if (q->n_ref <= 0) - resolve_query_free(q); - - return NULL; -} - -_public_ int sd_resolve_query_is_done(sd_resolve_query *q) { - assert_return(q, -EINVAL); - assert_return(!resolve_pid_changed(q->resolve), -ECHILD); - - return q->done; -} - -_public_ void* sd_resolve_query_set_userdata(sd_resolve_query *q, void *userdata) { - void *ret; - - assert_return(q, NULL); - assert_return(!resolve_pid_changed(q->resolve), NULL); - - ret = q->userdata; - q->userdata = userdata; - - return ret; -} - -_public_ void* sd_resolve_query_get_userdata(sd_resolve_query *q) { - assert_return(q, NULL); - assert_return(!resolve_pid_changed(q->resolve), NULL); - - return q->userdata; -} - -_public_ sd_resolve *sd_resolve_query_get_resolve(sd_resolve_query *q) { - assert_return(q, NULL); - assert_return(!resolve_pid_changed(q->resolve), NULL); - - return q->resolve; -} - -static int io_callback(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_resolve *resolve = userdata; - int r; - - assert(resolve); - - r = sd_resolve_process(resolve); - if (r < 0) - return r; - - return 1; -} - -_public_ int sd_resolve_attach_event(sd_resolve *resolve, sd_event *event, int priority) { - int r; - - assert_return(resolve, -EINVAL); - assert_return(!resolve->event, -EBUSY); - - assert(!resolve->event_source); - - if (event) - resolve->event = sd_event_ref(event); - else { - r = sd_event_default(&resolve->event); - if (r < 0) - return r; - } - - r = sd_event_add_io(resolve->event, &resolve->event_source, resolve->fds[RESPONSE_RECV_FD], POLLIN, io_callback, resolve); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(resolve->event_source, priority); - if (r < 0) - goto fail; - - return 0; - -fail: - sd_resolve_detach_event(resolve); - return r; -} - -_public_ int sd_resolve_detach_event(sd_resolve *resolve) { - assert_return(resolve, -EINVAL); - - if (!resolve->event) - return 0; - - if (resolve->event_source) { - sd_event_source_set_enabled(resolve->event_source, SD_EVENT_OFF); - resolve->event_source = sd_event_source_unref(resolve->event_source); - } - - resolve->event = sd_event_unref(resolve->event); - return 1; -} - -_public_ sd_event *sd_resolve_get_event(sd_resolve *resolve) { - assert_return(resolve, NULL); - - return resolve->event; -} diff --git a/src/libsystemd/sd-resolve/test-resolve.c b/src/libsystemd/sd-resolve/test-resolve.c deleted file mode 100644 index 33ef6fc0f7..0000000000 --- a/src/libsystemd/sd-resolve/test-resolve.c +++ /dev/null @@ -1,114 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2005-2008 Lennart Poettering - Copyright 2014 Daniel Buch - - 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "sd-resolve.h" - -#include "alloc-util.h" -#include "macro.h" -#include "socket-util.h" -#include "string-util.h" - -static int getaddrinfo_handler(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata) { - const struct addrinfo *i; - - assert_se(q); - - if (ret != 0) { - log_error("getaddrinfo error: %s %i", gai_strerror(ret), ret); - return 0; - } - - for (i = ai; i; i = i->ai_next) { - _cleanup_free_ char *addr = NULL; - - assert_se(sockaddr_pretty(i->ai_addr, i->ai_addrlen, false, true, &addr) == 0); - puts(addr); - } - - printf("canonical name: %s\n", strna(ai->ai_canonname)); - - return 0; -} - -static int getnameinfo_handler(sd_resolve_query *q, int ret, const char *host, const char *serv, void *userdata) { - assert_se(q); - - if (ret != 0) { - log_error("getnameinfo error: %s %i", gai_strerror(ret), ret); - return 0; - } - - printf("Host: %s -- Serv: %s\n", strna(host), strna(serv)); - return 0; -} - -int main(int argc, char *argv[]) { - _cleanup_(sd_resolve_query_unrefp) sd_resolve_query *q1 = NULL, *q2 = NULL; - _cleanup_(sd_resolve_unrefp) sd_resolve *resolve = NULL; - int r = 0; - - struct addrinfo hints = { - .ai_family = PF_UNSPEC, - .ai_socktype = SOCK_STREAM, - .ai_flags = AI_CANONNAME - }; - - struct sockaddr_in sa = { - .sin_family = AF_INET, - .sin_port = htons(80) - }; - - assert_se(sd_resolve_default(&resolve) >= 0); - - /* Test a floating resolver query */ - sd_resolve_getaddrinfo(resolve, NULL, "redhat.com", "http", NULL, getaddrinfo_handler, NULL); - - /* Make a name -> address query */ - r = sd_resolve_getaddrinfo(resolve, &q1, argc >= 2 ? argv[1] : "www.heise.de", NULL, &hints, getaddrinfo_handler, NULL); - if (r < 0) - log_error_errno(r, "sd_resolve_getaddrinfo(): %m"); - - /* Make an address -> name query */ - sa.sin_addr.s_addr = inet_addr(argc >= 3 ? argv[2] : "193.99.144.71"); - r = sd_resolve_getnameinfo(resolve, &q2, (struct sockaddr*) &sa, sizeof(sa), 0, SD_RESOLVE_GET_BOTH, getnameinfo_handler, NULL); - if (r < 0) - log_error_errno(r, "sd_resolve_getnameinfo(): %m"); - - /* Wait until all queries are completed */ - for (;;) { - r = sd_resolve_wait(resolve, (uint64_t) -1); - if (r == 0) - break; - if (r < 0) { - log_error_errno(r, "sd_resolve_wait(): %m"); - assert_not_reached("sd_resolve_wait() failed"); - } - } - - return 0; -} diff --git a/src/libsystemd/sd-utf8/Makefile b/src/libsystemd/sd-utf8/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/libsystemd/sd-utf8/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/libsystemd/sd-utf8/sd-utf8.c b/src/libsystemd/sd-utf8/sd-utf8.c deleted file mode 100644 index 33a5a04ea1..0000000000 --- a/src/libsystemd/sd-utf8/sd-utf8.c +++ /dev/null @@ -1,35 +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 . -***/ - -#include "sd-utf8.h" - -#include "utf8.h" -#include "util.h" - -_public_ const char *sd_utf8_is_valid(const char *s) { - assert_return(s, NULL); - - return utf8_is_valid(s); -} - -_public_ const char *sd_ascii_is_valid(const char *s) { - assert_return(s, NULL); - - return ascii_is_valid(s); -} diff --git a/src/libudev/.gitignore b/src/libudev/.gitignore deleted file mode 100644 index 0c8a5d5231..0000000000 --- a/src/libudev/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/libudev.pc diff --git a/src/libudev/Makefile b/src/libudev/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libudev/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libudev/Makefile b/src/libudev/Makefile new file mode 100644 index 0000000000..eb08762ae5 --- /dev/null +++ b/src/libudev/Makefile @@ -0,0 +1,39 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +test-libudev-sym.c: \ + src/libudev/libudev.sym \ + src/udev/udev.h + $(generate-sym-test) + +nodist_test_libudev_sym_SOURCES = \ + test-libudev-sym.c +test_libudev_sym_CFLAGS = \ + $(AM_CFLAGS) \ + -Wno-deprecated-declarations +test_libudev_sym_LDADD = \ + libudev.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libudev/include/libudev.h b/src/libudev/include/libudev.h new file mode 100644 index 0000000000..eb58740d26 --- /dev/null +++ b/src/libudev/include/libudev.h @@ -0,0 +1,206 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 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 . +***/ + +#ifndef _LIBUDEV_H_ +#define _LIBUDEV_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * udev - library context + * + * reads the udev config and system environment + * allows custom logging + */ +struct udev; +struct udev *udev_ref(struct udev *udev); +struct udev *udev_unref(struct udev *udev); +struct udev *udev_new(void); +void udev_set_log_fn(struct udev *udev, + void (*log_fn)(struct udev *udev, + int priority, const char *file, int line, const char *fn, + const char *format, va_list args)) __attribute__ ((deprecated)); +int udev_get_log_priority(struct udev *udev) __attribute__ ((deprecated)); +void udev_set_log_priority(struct udev *udev, int priority) __attribute__ ((deprecated)); +void *udev_get_userdata(struct udev *udev); +void udev_set_userdata(struct udev *udev, void *userdata); + +/* + * udev_list + * + * access to libudev generated lists + */ +struct udev_list_entry; +struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry); +struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name); +const char *udev_list_entry_get_name(struct udev_list_entry *list_entry); +const char *udev_list_entry_get_value(struct udev_list_entry *list_entry); +/** + * udev_list_entry_foreach: + * @list_entry: entry to store the current position + * @first_entry: first entry to start with + * + * Helper to iterate over all entries of a list. + */ +#define udev_list_entry_foreach(list_entry, first_entry) \ + for (list_entry = first_entry; \ + list_entry != NULL; \ + list_entry = udev_list_entry_get_next(list_entry)) + +/* + * udev_device + * + * access to sysfs/kernel devices + */ +struct udev_device; +struct udev_device *udev_device_ref(struct udev_device *udev_device); +struct udev_device *udev_device_unref(struct udev_device *udev_device); +struct udev *udev_device_get_udev(struct udev_device *udev_device); +struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath); +struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum); +struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname); +struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id); +struct udev_device *udev_device_new_from_environment(struct udev *udev); +/* udev_device_get_parent_*() does not take a reference on the returned device, it is automatically unref'd with the parent */ +struct udev_device *udev_device_get_parent(struct udev_device *udev_device); +struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, + const char *subsystem, const char *devtype); +/* retrieve device properties */ +const char *udev_device_get_devpath(struct udev_device *udev_device); +const char *udev_device_get_subsystem(struct udev_device *udev_device); +const char *udev_device_get_devtype(struct udev_device *udev_device); +const char *udev_device_get_syspath(struct udev_device *udev_device); +const char *udev_device_get_sysname(struct udev_device *udev_device); +const char *udev_device_get_sysnum(struct udev_device *udev_device); +const char *udev_device_get_devnode(struct udev_device *udev_device); +int udev_device_get_is_initialized(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device); +struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device); +const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key); +const char *udev_device_get_driver(struct udev_device *udev_device); +dev_t udev_device_get_devnum(struct udev_device *udev_device); +const char *udev_device_get_action(struct udev_device *udev_device); +unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device); +unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device); +const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr); +int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, char *value); +int udev_device_has_tag(struct udev_device *udev_device, const char *tag); + +/* + * udev_monitor + * + * access to kernel uevents and udev events + */ +struct udev_monitor; +struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor); +struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor); +struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor); +/* kernel and udev generated events over netlink */ +struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name); +/* bind socket */ +int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor); +int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size); +int udev_monitor_get_fd(struct udev_monitor *udev_monitor); +struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor); +/* in-kernel socket filters to select messages that get delivered to a listener */ +int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, + const char *subsystem, const char *devtype); +int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag); +int udev_monitor_filter_update(struct udev_monitor *udev_monitor); +int udev_monitor_filter_remove(struct udev_monitor *udev_monitor); + +/* + * udev_enumerate + * + * search sysfs for specific devices and provide a sorted list + */ +struct udev_enumerate; +struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate); +struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate); +struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate); +struct udev_enumerate *udev_enumerate_new(struct udev *udev); +/* device properties filter */ +int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem); +int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem); +int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); +int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); +int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value); +int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname); +int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag); +int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent); +int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate); +int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath); +/* run enumeration with active filters */ +int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate); +int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate); +/* return device list */ +struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate); + +/* + * udev_queue + * + * access to the currently running udev events + */ +struct udev_queue; +struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue); +struct udev_queue *udev_queue_unref(struct udev_queue *udev_queue); +struct udev *udev_queue_get_udev(struct udev_queue *udev_queue); +struct udev_queue *udev_queue_new(struct udev *udev); +unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) __attribute__ ((deprecated)); +unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) __attribute__ ((deprecated)); +int udev_queue_get_udev_is_active(struct udev_queue *udev_queue); +int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue); +int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) __attribute__ ((deprecated)); +int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, + unsigned long long int start, unsigned long long int end) __attribute__ ((deprecated)); +int udev_queue_get_fd(struct udev_queue *udev_queue); +int udev_queue_flush(struct udev_queue *udev_queue); +struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) __attribute__ ((deprecated)); + +/* + * udev_hwdb + * + * access to the static hardware properties database + */ +struct udev_hwdb; +struct udev_hwdb *udev_hwdb_new(struct udev *udev); +struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb); +struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb); +struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags); + +/* + * udev_util + * + * udev specific utilities + */ +int udev_util_encode_string(const char *str, char *str_enc, size_t len); + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/src/libudev/libudev-device-internal.h b/src/libudev/libudev-device-internal.h deleted file mode 100644 index 40d59201cf..0000000000 --- a/src/libudev/libudev-device-internal.h +++ /dev/null @@ -1,58 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2015 Tom Gundersen - - 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 . -***/ - -#pragma once - -#include "libudev.h" -#include "sd-device.h" - -#include "libudev-private.h" - -/** - * udev_device: - * - * Opaque object representing one kernel sys device. - */ -struct udev_device { - struct udev *udev; - - /* real device object */ - sd_device *device; - - /* legacy */ - int refcount; - - struct udev_device *parent; - bool parent_set; - - struct udev_list properties; - uint64_t properties_generation; - struct udev_list tags; - uint64_t tags_generation; - struct udev_list devlinks; - uint64_t devlinks_generation; - bool properties_read:1; - bool tags_read:1; - bool devlinks_read:1; - struct udev_list sysattrs; - bool sysattrs_read; -}; - -struct udev_device *udev_device_new(struct udev *udev); diff --git a/src/libudev/libudev-device-private.c b/src/libudev/libudev-device-private.c deleted file mode 100644 index 2aae0726c1..0000000000 --- a/src/libudev/libudev-device-private.c +++ /dev/null @@ -1,411 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2015 Tom Gundersen - - 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 . -***/ - -#include "libudev.h" - -#include "device-private.h" -#include "libudev-device-internal.h" -#include "libudev-private.h" - -int udev_device_tag_index(struct udev_device *udev_device, struct udev_device *udev_device_old, bool add) { - sd_device *device_old = NULL; - int r; - - assert(udev_device); - - if (udev_device_old) - device_old = udev_device_old->device; - - r = device_tag_index(udev_device->device, device_old, add); - if (r < 0) - return r; - - return 0; -} - -int udev_device_update_db(struct udev_device *udev_device) { - int r; - - assert(udev_device); - - r = device_update_db(udev_device->device); - if (r < 0) - return r; - - return 0; -} - -int udev_device_delete_db(struct udev_device *udev_device) { - int r; - - assert(udev_device); - - r = device_delete_db(udev_device->device); - if (r < 0) - return r; - - return 0; -} - -int udev_device_get_ifindex(struct udev_device *udev_device) { - int r, ifindex; - - assert(udev_device); - - r = sd_device_get_ifindex(udev_device->device, &ifindex); - if (r < 0) - return r; - - return ifindex; -} - -const char *udev_device_get_devpath_old(struct udev_device *udev_device) { - const char *devpath_old = NULL; - int r; - - assert(udev_device); - - r = sd_device_get_property_value(udev_device->device, "DEVPATH_OLD", &devpath_old); - if (r < 0 && r != -ENOENT) { - errno = -r; - return NULL; - } - - return devpath_old; -} - -mode_t udev_device_get_devnode_mode(struct udev_device *udev_device) { - mode_t mode; - int r; - - assert(udev_device); - - r = device_get_devnode_mode(udev_device->device, &mode); - if (r < 0) { - errno = -r; - return 0; - } - - return mode; -} - -uid_t udev_device_get_devnode_uid(struct udev_device *udev_device) { - uid_t uid; - int r; - - assert(udev_device); - - r = device_get_devnode_uid(udev_device->device, &uid); - if (r < 0) { - errno = -r; - return 0; - } - - return uid; -} - -gid_t udev_device_get_devnode_gid(struct udev_device *udev_device) { - gid_t gid; - int r; - - assert(udev_device); - - r = device_get_devnode_gid(udev_device->device, &gid); - if (r < 0) { - errno = -r; - return 0; - } - - return gid; -} - -void udev_device_ensure_usec_initialized(struct udev_device *udev_device, struct udev_device *udev_device_old) { - assert(udev_device); - - device_ensure_usec_initialized(udev_device->device, - udev_device_old ? udev_device_old->device : NULL); -} - -char **udev_device_get_properties_envp(struct udev_device *udev_device) { - char **envp; - int r; - - assert(udev_device); - - r = device_get_properties_strv(udev_device->device, &envp); - if (r < 0) { - errno = -r; - return NULL; - } - - return envp; -} - -ssize_t udev_device_get_properties_monitor_buf(struct udev_device *udev_device, const char **buf) { - const char *nulstr; - size_t len; - int r; - - assert(udev_device); - assert(buf); - - r = device_get_properties_nulstr(udev_device->device, (const uint8_t **)&nulstr, &len); - if (r < 0) - return r; - - *buf = nulstr; - - return len; -} - -int udev_device_get_devlink_priority(struct udev_device *udev_device) { - int priority, r; - - assert(udev_device); - - r = device_get_devlink_priority(udev_device->device, &priority); - if (r < 0) - return r; - - return priority; -} - -int udev_device_get_watch_handle(struct udev_device *udev_device) { - int handle, r; - - assert(udev_device); - - r = device_get_watch_handle(udev_device->device, &handle); - if (r < 0) - return r; - - return handle; -} - -void udev_device_set_is_initialized(struct udev_device *udev_device) { - assert(udev_device); - - device_set_is_initialized(udev_device->device); -} - -int udev_device_rename(struct udev_device *udev_device, const char *name) { - int r; - - assert(udev_device); - - r = device_rename(udev_device->device, name); - if (r < 0) - return r; - - return 0; -} - -struct udev_device *udev_device_shallow_clone(struct udev_device *old_device) { - struct udev_device *device; - int r; - - assert(old_device); - - device = udev_device_new(old_device->udev); - if (!device) - return NULL; - - r = device_shallow_clone(old_device->device, &device->device); - if (r < 0) { - udev_device_unref(device); - errno = -r; - return NULL; - } - - return device; -} - -struct udev_device *udev_device_clone_with_db(struct udev_device *udev_device_old) { - struct udev_device *udev_device; - int r; - - assert(udev_device_old); - - udev_device = udev_device_new(udev_device_old->udev); - if (!udev_device) - return NULL; - - r = device_clone_with_db(udev_device_old->device, &udev_device->device); - if (r < 0) { - udev_device_unref(udev_device); - errno = -r; - return NULL; - } - - return udev_device; -} - -struct udev_device *udev_device_new_from_nulstr(struct udev *udev, char *nulstr, ssize_t buflen) { - struct udev_device *device; - int r; - - device = udev_device_new(udev); - if (!device) - return NULL; - - r = device_new_from_nulstr(&device->device, (uint8_t*)nulstr, buflen); - if (r < 0) { - udev_device_unref(device); - errno = -r; - return NULL; - } - - return device; -} - -struct udev_device *udev_device_new_from_synthetic_event(struct udev *udev, const char *syspath, const char *action) { - struct udev_device *device; - int r; - - device = udev_device_new(udev); - if (!device) - return NULL; - - r = device_new_from_synthetic_event(&device->device, syspath, action); - if (r < 0) { - udev_device_unref(device); - errno = -r; - return NULL; - } - - return device; -} - -int udev_device_copy_properties(struct udev_device *udev_device_dst, struct udev_device *udev_device_src) { - int r; - - assert(udev_device_dst); - assert(udev_device_src); - - r = device_copy_properties(udev_device_dst->device, udev_device_src->device); - if (r < 0) - return r; - - return 0; -} - -const char *udev_device_get_id_filename(struct udev_device *udev_device) { - const char *filename; - int r; - - assert(udev_device); - - r = device_get_id_filename(udev_device->device, &filename); - if (r < 0) { - errno = -r; - return NULL; - } - - return filename; -} - -int udev_device_set_watch_handle(struct udev_device *udev_device, int handle) { - - assert(udev_device); - - device_set_watch_handle(udev_device->device, handle); - - return 0; -} - -void udev_device_set_db_persist(struct udev_device *udev_device) { - assert(udev_device); - - device_set_db_persist(udev_device->device); -} - -int udev_device_set_devlink_priority(struct udev_device *udev_device, int priority) { - assert(udev_device); - - device_set_devlink_priority(udev_device->device, priority); - - return 0; -} - -int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink) { - int r; - - assert(udev_device); - - r = device_add_devlink(udev_device->device, devlink); - if (r < 0) - return r; - - return 0; -} - -int udev_device_add_property(struct udev_device *udev_device, const char *property, const char *value) { - int r; - - assert(udev_device); - - r = device_add_property(udev_device->device, property, value); - if (r < 0) - return r; - - return 0; -} - -int udev_device_add_tag(struct udev_device *udev_device, const char *tag) { - int r; - - assert(udev_device); - - r = device_add_tag(udev_device->device, tag); - if (r < 0) - return r; - - return 0; -} - -void udev_device_remove_tag(struct udev_device *udev_device, const char *tag) { - assert(udev_device); - - device_remove_tag(udev_device->device, tag); -} - -void udev_device_cleanup_tags_list(struct udev_device *udev_device) { - assert(udev_device); - - device_cleanup_tags(udev_device->device); -} - -void udev_device_cleanup_devlinks_list(struct udev_device *udev_device) { - assert(udev_device); - - device_cleanup_devlinks(udev_device->device); -} - -void udev_device_set_info_loaded(struct udev_device *udev_device) { - assert(udev_device); - - device_seal(udev_device->device); -} - -void udev_device_read_db(struct udev_device *udev_device) { - assert(udev_device); - - device_read_db_force(udev_device->device); -} diff --git a/src/libudev/libudev-device.c b/src/libudev/libudev-device.c deleted file mode 100644 index 814e016800..0000000000 --- a/src/libudev/libudev-device.c +++ /dev/null @@ -1,958 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2015 Tom Gundersen - - 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libudev.h" -#include "sd-device.h" - -#include "alloc-util.h" -#include "device-private.h" -#include "device-util.h" -#include "libudev-device-internal.h" -#include "libudev-private.h" -#include "parse-util.h" - -/** - * SECTION:libudev-device - * @short_description: kernel sys devices - * - * Representation of kernel sys devices. Devices are uniquely identified - * by their syspath, every device has exactly one path in the kernel sys - * filesystem. Devices usually belong to a kernel subsystem, and have - * a unique name inside that subsystem. - */ - -/** - * udev_device_get_seqnum: - * @udev_device: udev device - * - * This is only valid if the device was received through a monitor. Devices read from - * sys do not have a sequence number. - * - * Returns: the kernel event sequence number, or 0 if there is no sequence number available. - **/ -_public_ unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device) -{ - const char *seqnum; - unsigned long long ret; - int r; - - assert_return_errno(udev_device, 0, EINVAL); - - r = sd_device_get_property_value(udev_device->device, "SEQNUM", &seqnum); - if (r == -ENOENT) - return 0; - else if (r < 0) { - errno = -r; - return 0; - } - - r = safe_atollu(seqnum, &ret); - if (r < 0) { - errno = -r; - return 0; - } - - return ret; -} - -/** - * udev_device_get_devnum: - * @udev_device: udev device - * - * Get the device major/minor number. - * - * Returns: the dev_t number. - **/ -_public_ dev_t udev_device_get_devnum(struct udev_device *udev_device) -{ - dev_t devnum; - int r; - - assert_return_errno(udev_device, makedev(0, 0), EINVAL); - - r = sd_device_get_devnum(udev_device->device, &devnum); - if (r < 0) { - errno = -r; - return makedev(0, 0); - } - - return devnum; -} - -/** - * udev_device_get_driver: - * @udev_device: udev device - * - * Get the kernel driver name. - * - * Returns: the driver name string, or #NULL if there is no driver attached. - **/ -_public_ const char *udev_device_get_driver(struct udev_device *udev_device) -{ - const char *driver; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_driver(udev_device->device, &driver); - if (r < 0) { - errno = -r; - return NULL; - } - - return driver; -} - -/** - * udev_device_get_devtype: - * @udev_device: udev device - * - * Retrieve the devtype string of the udev device. - * - * Returns: the devtype name of the udev device, or #NULL if it can not be determined - **/ -_public_ const char *udev_device_get_devtype(struct udev_device *udev_device) -{ - const char *devtype; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_devtype(udev_device->device, &devtype); - if (r < 0) { - errno = -r; - return NULL; - } - - return devtype; -} - -/** - * udev_device_get_subsystem: - * @udev_device: udev device - * - * Retrieve the subsystem string of the udev device. The string does not - * contain any "/". - * - * Returns: the subsystem name of the udev device, or #NULL if it can not be determined - **/ -_public_ const char *udev_device_get_subsystem(struct udev_device *udev_device) -{ - const char *subsystem; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_subsystem(udev_device->device, &subsystem); - if (r < 0) { - errno = -r; - return NULL; - } else if (!subsystem) - errno = ENODATA; - - return subsystem; -} - -/** - * udev_device_get_property_value: - * @udev_device: udev device - * @key: property name - * - * Get the value of a given property. - * - * Returns: the property string, or #NULL if there is no such property. - **/ -_public_ const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key) -{ - const char *value = NULL; - int r; - - assert_return_errno(udev_device && key, NULL, EINVAL); - - r = sd_device_get_property_value(udev_device->device, key, &value); - if (r < 0) { - errno = -r; - return NULL; - } - - return value; -} - -struct udev_device *udev_device_new(struct udev *udev) { - struct udev_device *udev_device; - - assert_return_errno(udev, NULL, EINVAL); - - udev_device = new0(struct udev_device, 1); - if (!udev_device) { - errno = ENOMEM; - return NULL; - } - udev_device->refcount = 1; - udev_device->udev = udev; - udev_list_init(udev, &udev_device->properties, true); - udev_list_init(udev, &udev_device->tags, true); - udev_list_init(udev, &udev_device->sysattrs, true); - udev_list_init(udev, &udev_device->devlinks, true); - - return udev_device; -} - -/** - * udev_device_new_from_syspath: - * @udev: udev library context - * @syspath: sys device path including sys directory - * - * Create new udev device, and fill in information from the sys - * device and the udev database entry. The syspath is the absolute - * path to the device, including the sys mount point. - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev device. - * - * Returns: a new udev device, or #NULL, if it does not exist - **/ -_public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath) { - struct udev_device *udev_device; - int r; - - udev_device = udev_device_new(udev); - if (!udev_device) - return NULL; - - r = sd_device_new_from_syspath(&udev_device->device, syspath); - if (r < 0) { - errno = -r; - udev_device_unref(udev_device); - return NULL; - } - - return udev_device; -} - -/** - * udev_device_new_from_devnum: - * @udev: udev library context - * @type: char or block device - * @devnum: device major/minor number - * - * Create new udev device, and fill in information from the sys - * device and the udev database entry. The device is looked-up - * by its major/minor number and type. Character and block device - * numbers are not unique across the two types. - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev device. - * - * Returns: a new udev device, or #NULL, if it does not exist - **/ -_public_ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum) -{ - struct udev_device *udev_device; - int r; - - udev_device = udev_device_new(udev); - if (!udev_device) - return NULL; - - r = sd_device_new_from_devnum(&udev_device->device, type, devnum); - if (r < 0) { - errno = -r; - udev_device_unref(udev_device); - return NULL; - } - - return udev_device; -} - -/** - * udev_device_new_from_device_id: - * @udev: udev library context - * @id: text string identifying a kernel device - * - * Create new udev device, and fill in information from the sys - * device and the udev database entry. The device is looked-up - * by a special string: - * b8:2 - block device major:minor - * c128:1 - char device major:minor - * n3 - network device ifindex - * +sound:card29 - kernel driver core subsystem:device name - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev device. - * - * Returns: a new udev device, or #NULL, if it does not exist - **/ -_public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id) -{ - struct udev_device *udev_device; - int r; - - udev_device = udev_device_new(udev); - if (!udev_device) - return NULL; - - r = sd_device_new_from_device_id(&udev_device->device, id); - if (r < 0) { - errno = -r; - udev_device_unref(udev_device); - return NULL; - } - - return udev_device; -} - -/** - * udev_device_new_from_subsystem_sysname: - * @udev: udev library context - * @subsystem: the subsystem of the device - * @sysname: the name of the device - * - * Create new udev device, and fill in information from the sys device - * and the udev database entry. The device is looked up by the subsystem - * and name string of the device, like "mem" / "zero", or "block" / "sda". - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev device. - * - * Returns: a new udev device, or #NULL, if it does not exist - **/ -_public_ struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname) -{ - struct udev_device *udev_device; - int r; - - udev_device = udev_device_new(udev); - if (!udev_device) - return NULL; - - r = sd_device_new_from_subsystem_sysname(&udev_device->device, subsystem, sysname); - if (r < 0) { - errno = -r; - udev_device_unref(udev_device); - return NULL; - } - - return udev_device; -} - -/** - * udev_device_new_from_environment - * @udev: udev library context - * - * Create new udev device, and fill in information from the - * current process environment. This only works reliable if - * the process is called from a udev rule. It is usually used - * for tools executed from IMPORT= rules. - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev device. - * - * Returns: a new udev device, or #NULL, if it does not exist - **/ -_public_ struct udev_device *udev_device_new_from_environment(struct udev *udev) -{ - struct udev_device *udev_device; - int r; - - udev_device = udev_device_new(udev); - if (!udev_device) - return NULL; - - r = device_new_from_strv(&udev_device->device, environ); - if (r < 0) { - errno = -r; - udev_device_unref(udev_device); - return NULL; - } - - return udev_device; -} - -static struct udev_device *device_new_from_parent(struct udev_device *child) -{ - struct udev_device *parent; - int r; - - assert_return_errno(child, NULL, EINVAL); - - parent = udev_device_new(child->udev); - if (!parent) - return NULL; - - r = sd_device_get_parent(child->device, &parent->device); - if (r < 0) { - errno = -r; - udev_device_unref(parent); - return NULL; - } - - /* the parent is unref'ed with the child, so take a ref from libudev as well */ - sd_device_ref(parent->device); - - return parent; -} - -/** - * udev_device_get_parent: - * @udev_device: the device to start searching from - * - * Find the next parent device, and fill in information from the sys - * device and the udev database entry. - * - * Returned device is not referenced. It is attached to the child - * device, and will be cleaned up when the child device is cleaned up. - * - * It is not necessarily just the upper level directory, empty or not - * recognized sys directories are ignored. - * - * It can be called as many times as needed, without caring about - * references. - * - * Returns: a new udev device, or #NULL, if it no parent exist. - **/ -_public_ struct udev_device *udev_device_get_parent(struct udev_device *udev_device) -{ - assert_return_errno(udev_device, NULL, EINVAL); - - if (!udev_device->parent_set) { - udev_device->parent_set = true; - udev_device->parent = device_new_from_parent(udev_device); - } - - /* TODO: errno will differ here in case parent == NULL */ - return udev_device->parent; -} - -/** - * udev_device_get_parent_with_subsystem_devtype: - * @udev_device: udev device to start searching from - * @subsystem: the subsystem of the device - * @devtype: the type (DEVTYPE) of the device - * - * Find the next parent device, with a matching subsystem and devtype - * value, and fill in information from the sys device and the udev - * database entry. - * - * If devtype is #NULL, only subsystem is checked, and any devtype will - * match. - * - * Returned device is not referenced. It is attached to the child - * device, and will be cleaned up when the child device is cleaned up. - * - * It can be called as many times as needed, without caring about - * references. - * - * Returns: a new udev device, or #NULL if no matching parent exists. - **/ -_public_ struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, const char *subsystem, const char *devtype) -{ - sd_device *parent; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - /* this relies on the fact that finding the subdevice of a parent or the - parent of a subdevice commute */ - - /* first find the correct sd_device */ - r = sd_device_get_parent_with_subsystem_devtype(udev_device->device, subsystem, devtype, &parent); - if (r < 0) { - errno = -r; - return NULL; - } - - /* then walk the chain of udev_device parents until the correspanding - one is found */ - while ((udev_device = udev_device_get_parent(udev_device))) { - if (udev_device->device == parent) - return udev_device; - } - - errno = ENOENT; - return NULL; -} - -/** - * udev_device_get_udev: - * @udev_device: udev device - * - * Retrieve the udev library context the device was created with. - * - * Returns: the udev library context - **/ -_public_ struct udev *udev_device_get_udev(struct udev_device *udev_device) -{ - assert_return_errno(udev_device, NULL, EINVAL); - - return udev_device->udev; -} - -/** - * udev_device_ref: - * @udev_device: udev device - * - * Take a reference of a udev device. - * - * Returns: the passed udev device - **/ -_public_ struct udev_device *udev_device_ref(struct udev_device *udev_device) -{ - if (udev_device) - udev_device->refcount++; - - return udev_device; -} - -/** - * udev_device_unref: - * @udev_device: udev device - * - * Drop a reference of a udev device. If the refcount reaches zero, - * the resources of the device will be released. - * - * Returns: #NULL - **/ -_public_ struct udev_device *udev_device_unref(struct udev_device *udev_device) -{ - if (udev_device && (-- udev_device->refcount) == 0) { - sd_device_unref(udev_device->device); - udev_device_unref(udev_device->parent); - - udev_list_cleanup(&udev_device->properties); - udev_list_cleanup(&udev_device->sysattrs); - udev_list_cleanup(&udev_device->tags); - udev_list_cleanup(&udev_device->devlinks); - - free(udev_device); - } - - return NULL; -} - -/** - * udev_device_get_devpath: - * @udev_device: udev device - * - * Retrieve the kernel devpath value of the udev device. The path - * does not contain the sys mount point, and starts with a '/'. - * - * Returns: the devpath of the udev device - **/ -_public_ const char *udev_device_get_devpath(struct udev_device *udev_device) -{ - const char *devpath; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_devpath(udev_device->device, &devpath); - if (r < 0) { - errno = -r; - return NULL; - } - - return devpath; -} - -/** - * udev_device_get_syspath: - * @udev_device: udev device - * - * Retrieve the sys path of the udev device. The path is an - * absolute path and starts with the sys mount point. - * - * Returns: the sys path of the udev device - **/ -_public_ const char *udev_device_get_syspath(struct udev_device *udev_device) -{ - const char *syspath; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_syspath(udev_device->device, &syspath); - if (r < 0) { - errno = -r; - return NULL; - } - - return syspath; -} - -/** - * udev_device_get_sysname: - * @udev_device: udev device - * - * Get the kernel device name in /sys. - * - * Returns: the name string of the device device - **/ -_public_ const char *udev_device_get_sysname(struct udev_device *udev_device) -{ - const char *sysname; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_sysname(udev_device->device, &sysname); - if (r < 0) { - errno = -r; - return NULL; - } - - return sysname; -} - -/** - * udev_device_get_sysnum: - * @udev_device: udev device - * - * Get the instance number of the device. - * - * Returns: the trailing number string of the device name - **/ -_public_ const char *udev_device_get_sysnum(struct udev_device *udev_device) -{ - const char *sysnum; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_sysnum(udev_device->device, &sysnum); - if (r < 0) { - errno = -r; - return NULL; - } - - return sysnum; -} - -/** - * udev_device_get_devnode: - * @udev_device: udev device - * - * Retrieve the device node file name belonging to the udev device. - * The path is an absolute path, and starts with the device directory. - * - * Returns: the device node file name of the udev device, or #NULL if no device node exists - **/ -_public_ const char *udev_device_get_devnode(struct udev_device *udev_device) -{ - const char *devnode; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_devname(udev_device->device, &devnode); - if (r < 0) { - errno = -r; - return NULL; - } - - return devnode; -} - -/** - * udev_device_get_devlinks_list_entry: - * @udev_device: udev device - * - * Retrieve the list of device links pointing to the device file of - * the udev device. The next list entry can be retrieved with - * udev_list_entry_get_next(), which returns #NULL if no more entries exist. - * The devlink path can be retrieved from the list entry by - * udev_list_entry_get_name(). The path is an absolute path, and starts with - * the device directory. - * - * Returns: the first entry of the device node link list - **/ -_public_ struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device) -{ - assert_return_errno(udev_device, NULL, EINVAL); - - if (device_get_devlinks_generation(udev_device->device) != udev_device->devlinks_generation || - !udev_device->devlinks_read) { - const char *devlink; - - udev_list_cleanup(&udev_device->devlinks); - - FOREACH_DEVICE_DEVLINK(udev_device->device, devlink) - udev_list_entry_add(&udev_device->devlinks, devlink, NULL); - - udev_device->devlinks_read = true; - udev_device->devlinks_generation = device_get_devlinks_generation(udev_device->device); - } - - return udev_list_get_entry(&udev_device->devlinks); -} - -/** - * udev_device_get_event_properties_entry: - * @udev_device: udev device - * - * Retrieve the list of key/value device properties of the udev - * device. The next list entry can be retrieved with udev_list_entry_get_next(), - * which returns #NULL if no more entries exist. The property name - * can be retrieved from the list entry by udev_list_entry_get_name(), - * the property value by udev_list_entry_get_value(). - * - * Returns: the first entry of the property list - **/ -_public_ struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device) -{ - assert_return_errno(udev_device, NULL, EINVAL); - - if (device_get_properties_generation(udev_device->device) != udev_device->properties_generation || - !udev_device->properties_read) { - const char *key, *value; - - udev_list_cleanup(&udev_device->properties); - - FOREACH_DEVICE_PROPERTY(udev_device->device, key, value) - udev_list_entry_add(&udev_device->properties, key, value); - - udev_device->properties_read = true; - udev_device->properties_generation = device_get_properties_generation(udev_device->device); - } - - return udev_list_get_entry(&udev_device->properties); -} - -/** - * udev_device_get_action: - * @udev_device: udev device - * - * This is only valid if the device was received through a monitor. Devices read from - * sys do not have an action string. Usual actions are: add, remove, change, online, - * offline. - * - * Returns: the kernel action value, or #NULL if there is no action value available. - **/ -_public_ const char *udev_device_get_action(struct udev_device *udev_device) { - const char *action = NULL; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_property_value(udev_device->device, "ACTION", &action); - if (r < 0 && r != -ENOENT) { - errno = -r; - return NULL; - } - - return action; -} - -/** - * udev_device_get_usec_since_initialized: - * @udev_device: udev device - * - * Return the number of microseconds passed since udev set up the - * device for the first time. - * - * This is only implemented for devices with need to store properties - * in the udev database. All other devices return 0 here. - * - * Returns: the number of microseconds since the device was first seen. - **/ -_public_ unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device) -{ - usec_t ts; - int r; - - assert_return(udev_device, -EINVAL); - - r = sd_device_get_usec_since_initialized(udev_device->device, &ts); - if (r < 0) { - errno = EINVAL; - return 0; - } - - return ts; -} - -/** - * udev_device_get_sysattr_value: - * @udev_device: udev device - * @sysattr: attribute name - * - * The retrieved value is cached in the device. Repeated calls will return the same - * value and not open the attribute again. - * - * Returns: the content of a sys attribute file, or #NULL if there is no sys attribute value. - **/ -_public_ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr) -{ - const char *value; - int r; - - assert_return_errno(udev_device, NULL, EINVAL); - - r = sd_device_get_sysattr_value(udev_device->device, sysattr, &value); - if (r < 0) { - errno = -r; - return NULL; - } - - return value; -} - -/** - * udev_device_set_sysattr_value: - * @udev_device: udev device - * @sysattr: attribute name - * @value: new value to be set - * - * Update the contents of the sys attribute and the cached value of the device. - * - * Returns: Negative error code on failure or 0 on success. - **/ -_public_ int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, char *value) -{ - int r; - - assert_return(udev_device, -EINVAL); - - r = sd_device_set_sysattr_value(udev_device->device, sysattr, value); - if (r < 0) - return r; - - return 0; -} - -/** - * udev_device_get_sysattr_list_entry: - * @udev_device: udev device - * - * Retrieve the list of available sysattrs, with value being empty; - * This just return all available sysfs attributes for a particular - * device without reading their values. - * - * Returns: the first entry of the property list - **/ -_public_ struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device) -{ - assert_return_errno(udev_device, NULL, EINVAL); - - if (!udev_device->sysattrs_read) { - const char *sysattr; - - udev_list_cleanup(&udev_device->sysattrs); - - FOREACH_DEVICE_SYSATTR(udev_device->device, sysattr) - udev_list_entry_add(&udev_device->sysattrs, sysattr, NULL); - - udev_device->sysattrs_read = true; - } - - return udev_list_get_entry(&udev_device->sysattrs); -} - -/** - * udev_device_get_is_initialized: - * @udev_device: udev device - * - * Check if udev has already handled the device and has set up - * device node permissions and context, or has renamed a network - * device. - * - * This is only implemented for devices with a device node - * or network interfaces. All other devices return 1 here. - * - * Returns: 1 if the device is set up. 0 otherwise. - **/ -_public_ int udev_device_get_is_initialized(struct udev_device *udev_device) -{ - int r, initialized; - - assert_return(udev_device, -EINVAL); - - r = sd_device_get_is_initialized(udev_device->device, &initialized); - if (r < 0) { - errno = -r; - - return 0; - } - - return initialized; -} - -/** - * udev_device_get_tags_list_entry: - * @udev_device: udev device - * - * Retrieve the list of tags attached to the udev device. The next - * list entry can be retrieved with udev_list_entry_get_next(), - * which returns #NULL if no more entries exist. The tag string - * can be retrieved from the list entry by udev_list_entry_get_name(). - * - * Returns: the first entry of the tag list - **/ -_public_ struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device) -{ - assert_return_errno(udev_device, NULL, EINVAL); - - if (device_get_tags_generation(udev_device->device) != udev_device->tags_generation || - !udev_device->tags_read) { - const char *tag; - - udev_list_cleanup(&udev_device->tags); - - FOREACH_DEVICE_TAG(udev_device->device, tag) - udev_list_entry_add(&udev_device->tags, tag, NULL); - - udev_device->tags_read = true; - udev_device->tags_generation = device_get_tags_generation(udev_device->device); - } - - return udev_list_get_entry(&udev_device->tags); -} - -/** - * udev_device_has_tag: - * @udev_device: udev device - * @tag: tag name - * - * Check if a given device has a certain tag associated. - * - * Returns: 1 if the tag is found. 0 otherwise. - **/ -_public_ int udev_device_has_tag(struct udev_device *udev_device, const char *tag) -{ - assert_return(udev_device, 0); - - return sd_device_has_tag(udev_device->device, tag); -} diff --git a/src/libudev/libudev-enumerate.c b/src/libudev/libudev-enumerate.c deleted file mode 100644 index e416e178b4..0000000000 --- a/src/libudev/libudev-enumerate.c +++ /dev/null @@ -1,419 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2015 Tom Gundersen - - 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libudev.h" -#include "sd-device.h" - -#include "alloc-util.h" -#include "device-enumerator-private.h" -#include "device-util.h" -#include "libudev-device-internal.h" - -/** - * SECTION:libudev-enumerate - * @short_description: lookup and sort sys devices - * - * Lookup devices in the sys filesystem, filter devices by properties, - * and return a sorted list of devices. - */ - -/** - * udev_enumerate: - * - * Opaque object representing one device lookup/sort context. - */ -struct udev_enumerate { - struct udev *udev; - int refcount; - struct udev_list devices_list; - bool devices_uptodate:1; - - sd_device_enumerator *enumerator; -}; - -/** - * udev_enumerate_new: - * @udev: udev library context - * - * Create an enumeration context to scan /sys. - * - * Returns: an enumeration context. - **/ -_public_ struct udev_enumerate *udev_enumerate_new(struct udev *udev) { - _cleanup_free_ struct udev_enumerate *udev_enumerate = NULL; - struct udev_enumerate *ret; - int r; - - assert_return_errno(udev, NULL, EINVAL); - - udev_enumerate = new0(struct udev_enumerate, 1); - if (!udev_enumerate) { - errno = ENOMEM; - return NULL; - } - - r = sd_device_enumerator_new(&udev_enumerate->enumerator); - if (r < 0) { - errno = -r; - return NULL; - } - - r = sd_device_enumerator_allow_uninitialized(udev_enumerate->enumerator); - if (r < 0) { - errno = -r; - return NULL; - } - - udev_enumerate->refcount = 1; - udev_enumerate->udev = udev; - - udev_list_init(udev, &udev_enumerate->devices_list, false); - - ret = udev_enumerate; - udev_enumerate = NULL; - - return ret; -} - -/** - * udev_enumerate_ref: - * @udev_enumerate: context - * - * Take a reference of a enumeration context. - * - * Returns: the passed enumeration context - **/ -_public_ struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate) { - if (udev_enumerate) - udev_enumerate->refcount ++; - - return udev_enumerate; -} - -/** - * udev_enumerate_unref: - * @udev_enumerate: context - * - * Drop a reference of an enumeration context. If the refcount reaches zero, - * all resources of the enumeration context will be released. - * - * Returns: #NULL - **/ -_public_ struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate) { - if (udev_enumerate && (-- udev_enumerate->refcount) == 0) { - udev_list_cleanup(&udev_enumerate->devices_list); - sd_device_enumerator_unref(udev_enumerate->enumerator); - free(udev_enumerate); - } - - return NULL; -} - -/** - * udev_enumerate_get_udev: - * @udev_enumerate: context - * - * Get the udev library context. - * - * Returns: a pointer to the context. - */ -_public_ struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate) { - assert_return_errno(udev_enumerate, NULL, EINVAL); - - return udev_enumerate->udev; -} - -/** - * udev_enumerate_get_list_entry: - * @udev_enumerate: context - * - * Get the first entry of the sorted list of device paths. - * - * Returns: a udev_list_entry. - */ -_public_ struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate) { - assert_return_errno(udev_enumerate, NULL, EINVAL); - - if (!udev_enumerate->devices_uptodate) { - sd_device *device; - - udev_list_cleanup(&udev_enumerate->devices_list); - - FOREACH_DEVICE_AND_SUBSYSTEM(udev_enumerate->enumerator, device) { - const char *syspath; - int r; - - r = sd_device_get_syspath(device, &syspath); - if (r < 0) { - errno = -r; - return NULL; - } - - udev_list_entry_add(&udev_enumerate->devices_list, syspath, NULL); - } - - udev_enumerate->devices_uptodate = true; - } - - return udev_list_get_entry(&udev_enumerate->devices_list); -} - -/** - * udev_enumerate_add_match_subsystem: - * @udev_enumerate: context - * @subsystem: filter for a subsystem of the device to include in the list - * - * Match only devices belonging to a certain kernel subsystem. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) { - assert_return(udev_enumerate, -EINVAL); - - if (!subsystem) - return 0; - - return sd_device_enumerator_add_match_subsystem(udev_enumerate->enumerator, subsystem, true); -} - -/** - * udev_enumerate_add_nomatch_subsystem: - * @udev_enumerate: context - * @subsystem: filter for a subsystem of the device to exclude from the list - * - * Match only devices not belonging to a certain kernel subsystem. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) { - assert_return(udev_enumerate, -EINVAL); - - if (!subsystem) - return 0; - - return sd_device_enumerator_add_match_subsystem(udev_enumerate->enumerator, subsystem, false); -} - -/** - * udev_enumerate_add_match_sysattr: - * @udev_enumerate: context - * @sysattr: filter for a sys attribute at the device to include in the list - * @value: optional value of the sys attribute - * - * Match only devices with a certain /sys device attribute. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value) { - assert_return(udev_enumerate, -EINVAL); - - if (!sysattr) - return 0; - - return sd_device_enumerator_add_match_sysattr(udev_enumerate->enumerator, sysattr, value, true); -} - -/** - * udev_enumerate_add_nomatch_sysattr: - * @udev_enumerate: context - * @sysattr: filter for a sys attribute at the device to exclude from the list - * @value: optional value of the sys attribute - * - * Match only devices not having a certain /sys device attribute. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value) { - assert_return(udev_enumerate, -EINVAL); - - if (!sysattr) - return 0; - - return sd_device_enumerator_add_match_sysattr(udev_enumerate->enumerator, sysattr, value, false); -} - -/** - * udev_enumerate_add_match_property: - * @udev_enumerate: context - * @property: filter for a property of the device to include in the list - * @value: value of the property - * - * Match only devices with a certain property. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value) { - assert_return(udev_enumerate, -EINVAL); - - if (!property) - return 0; - - return sd_device_enumerator_add_match_property(udev_enumerate->enumerator, property, value); -} - -/** - * udev_enumerate_add_match_tag: - * @udev_enumerate: context - * @tag: filter for a tag of the device to include in the list - * - * Match only devices with a certain tag. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag) { - assert_return(udev_enumerate, -EINVAL); - - if (!tag) - return 0; - - return sd_device_enumerator_add_match_tag(udev_enumerate->enumerator, tag); -} - -/** - * udev_enumerate_add_match_parent: - * @udev_enumerate: context - * @parent: parent device where to start searching - * - * Return the devices on the subtree of one given device. The parent - * itself is included in the list. - * - * A reference for the device is held until the udev_enumerate context - * is cleaned up. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent) { - assert_return(udev_enumerate, -EINVAL); - - if (!parent) - return 0; - - return sd_device_enumerator_add_match_parent(udev_enumerate->enumerator, parent->device); -} - -/** - * udev_enumerate_add_match_is_initialized: - * @udev_enumerate: context - * - * Match only devices which udev has set up already. This makes - * sure, that the device node permissions and context are properly set - * and that network devices are fully renamed. - * - * Usually, devices which are found in the kernel but not already - * handled by udev, have still pending events. Services should subscribe - * to monitor events and wait for these devices to become ready, instead - * of using uninitialized devices. - * - * For now, this will not affect devices which do not have a device node - * and are not network interfaces. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate) { - assert_return(udev_enumerate, -EINVAL); - - return device_enumerator_add_match_is_initialized(udev_enumerate->enumerator); -} - -/** - * udev_enumerate_add_match_sysname: - * @udev_enumerate: context - * @sysname: filter for the name of the device to include in the list - * - * Match only devices with a given /sys device name. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname) { - assert_return(udev_enumerate, -EINVAL); - - if (!sysname) - return 0; - - return sd_device_enumerator_add_match_sysname(udev_enumerate->enumerator, sysname); -} - -/** - * udev_enumerate_add_syspath: - * @udev_enumerate: context - * @syspath: path of a device - * - * Add a device to the list of devices, to retrieve it back sorted in dependency order. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath) { - _cleanup_(sd_device_unrefp) sd_device *device = NULL; - int r; - - assert_return(udev_enumerate, -EINVAL); - - if (!syspath) - return 0; - - r = sd_device_new_from_syspath(&device, syspath); - if (r < 0) - return r; - - r = device_enumerator_add_device(udev_enumerate->enumerator, device); - if (r < 0) - return r; - - return 0; -} - -/** - * udev_enumerate_scan_devices: - * @udev_enumerate: udev enumeration context - * - * Scan /sys for all devices which match the given filters. No matches - * will return all currently available devices. - * - * Returns: 0 on success, otherwise a negative error value. - **/ -_public_ int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate) { - assert_return(udev_enumerate, -EINVAL); - - return device_enumerator_scan_devices(udev_enumerate->enumerator); -} - -/** - * udev_enumerate_scan_subsystems: - * @udev_enumerate: udev enumeration context - * - * Scan /sys for all kernel subsystems, including buses, classes, drivers. - * - * Returns: 0 on success, otherwise a negative error value. - **/ -_public_ int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate) { - assert_return(udev_enumerate, -EINVAL); - - return device_enumerator_scan_subsystems(udev_enumerate->enumerator); -} diff --git a/src/libudev/libudev-hwdb.c b/src/libudev/libudev-hwdb.c deleted file mode 100644 index a53f000015..0000000000 --- a/src/libudev/libudev-hwdb.c +++ /dev/null @@ -1,146 +0,0 @@ -/*** - This file is part of systemd. - - Copyright Tom Gundersen - - 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 . -***/ - -#include "sd-hwdb.h" - -#include "alloc-util.h" -#include "hwdb-util.h" -#include "libudev-private.h" - -/** - * SECTION:libudev-hwdb - * @short_description: retrieve properties from the hardware database - * - * Libudev hardware database interface. - */ - -/** - * udev_hwdb: - * - * Opaque object representing the hardware database. - */ -struct udev_hwdb { - struct udev *udev; - int refcount; - - sd_hwdb *hwdb; - - struct udev_list properties_list; -}; - -/** - * udev_hwdb_new: - * @udev: udev library context - * - * Create a hardware database context to query properties for devices. - * - * Returns: a hwdb context. - **/ -_public_ struct udev_hwdb *udev_hwdb_new(struct udev *udev) { - _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb_internal = NULL; - struct udev_hwdb *hwdb; - int r; - - assert_return(udev, NULL); - - r = sd_hwdb_new(&hwdb_internal); - if (r < 0) - return NULL; - - hwdb = new0(struct udev_hwdb, 1); - if (!hwdb) - return NULL; - - hwdb->refcount = 1; - hwdb->hwdb = hwdb_internal; - hwdb_internal = NULL; - - udev_list_init(udev, &hwdb->properties_list, true); - - return hwdb; -} - -/** - * udev_hwdb_ref: - * @hwdb: context - * - * Take a reference of a hwdb context. - * - * Returns: the passed enumeration context - **/ -_public_ struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb) { - if (!hwdb) - return NULL; - hwdb->refcount++; - return hwdb; -} - -/** - * udev_hwdb_unref: - * @hwdb: context - * - * Drop a reference of a hwdb context. If the refcount reaches zero, - * all resources of the hwdb context will be released. - * - * Returns: #NULL - **/ -_public_ struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb) { - if (!hwdb) - return NULL; - hwdb->refcount--; - if (hwdb->refcount > 0) - return NULL; - sd_hwdb_unref(hwdb->hwdb); - udev_list_cleanup(&hwdb->properties_list); - free(hwdb); - return NULL; -} - -/** - * udev_hwdb_get_properties_list_entry: - * @hwdb: context - * @modalias: modalias string - * @flags: (unused) - * - * Lookup a matching device in the hardware database. The lookup key is a - * modalias string, whose formats are defined for the Linux kernel modules. - * Examples are: pci:v00008086d00001C2D*, usb:v04F2pB221*. The first entry - * of a list of retrieved properties is returned. - * - * Returns: a udev_list_entry. - */ -_public_ struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags) { - const char *key, *value; - - if (!hwdb || !modalias) { - errno = EINVAL; - return NULL; - } - - udev_list_cleanup(&hwdb->properties_list); - - SD_HWDB_FOREACH_PROPERTY(hwdb->hwdb, modalias, key, value) { - if (udev_list_entry_add(&hwdb->properties_list, key, value) == NULL) { - errno = ENOMEM; - return NULL; - } - } - - return udev_list_get_entry(&hwdb->properties_list); -} diff --git a/src/libudev/libudev-list.c b/src/libudev/libudev-list.c deleted file mode 100644 index da496ed456..0000000000 --- a/src/libudev/libudev-list.c +++ /dev/null @@ -1,352 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 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 . -***/ - -#include -#include -#include -#include - -#include "alloc-util.h" -#include "libudev-private.h" - -/** - * SECTION:libudev-list - * @short_description: list operation - * - * Libudev list operations. - */ - -/** - * udev_list_entry: - * - * Opaque object representing one entry in a list. An entry contains - * contains a name, and optionally a value. - */ -struct udev_list_entry { - struct udev_list_node node; - struct udev_list *list; - char *name; - char *value; - int num; -}; - -/* the list's head points to itself if empty */ -void udev_list_node_init(struct udev_list_node *list) -{ - list->next = list; - list->prev = list; -} - -int udev_list_node_is_empty(struct udev_list_node *list) -{ - return list->next == list; -} - -static void udev_list_node_insert_between(struct udev_list_node *new, - struct udev_list_node *prev, - struct udev_list_node *next) -{ - next->prev = new; - new->next = next; - new->prev = prev; - prev->next = new; -} - -void udev_list_node_append(struct udev_list_node *new, struct udev_list_node *list) -{ - udev_list_node_insert_between(new, list->prev, list); -} - -void udev_list_node_remove(struct udev_list_node *entry) -{ - struct udev_list_node *prev = entry->prev; - struct udev_list_node *next = entry->next; - - next->prev = prev; - prev->next = next; - - entry->prev = NULL; - entry->next = NULL; -} - -/* return list entry which embeds this node */ -static inline struct udev_list_entry *list_node_to_entry(struct udev_list_node *node) -{ - return container_of(node, struct udev_list_entry, node); -} - -void udev_list_init(struct udev *udev, struct udev_list *list, bool unique) -{ - memzero(list, sizeof(struct udev_list)); - list->udev = udev; - list->unique = unique; - udev_list_node_init(&list->node); -} - -/* insert entry into a list as the last element */ -static void udev_list_entry_append(struct udev_list_entry *new, struct udev_list *list) -{ - /* inserting before the list head make the node the last node in the list */ - udev_list_node_insert_between(&new->node, list->node.prev, &list->node); - new->list = list; -} - -/* insert entry into a list, before a given existing entry */ -static void udev_list_entry_insert_before(struct udev_list_entry *new, struct udev_list_entry *entry) -{ - udev_list_node_insert_between(&new->node, entry->node.prev, &entry->node); - new->list = entry->list; -} - -/* binary search in sorted array */ -static int list_search(struct udev_list *list, const char *name) -{ - unsigned int first, last; - - first = 0; - last = list->entries_cur; - while (first < last) { - unsigned int i; - int cmp; - - i = (first + last)/2; - cmp = strcmp(name, list->entries[i]->name); - if (cmp < 0) - last = i; - else if (cmp > 0) - first = i+1; - else - return i; - } - - /* not found, return negative insertion-index+1 */ - return -(first+1); -} - -struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *name, const char *value) -{ - struct udev_list_entry *entry; - int i = 0; - - if (list->unique) { - /* lookup existing name or insertion-index */ - i = list_search(list, name); - if (i >= 0) { - entry = list->entries[i]; - - free(entry->value); - if (value == NULL) { - entry->value = NULL; - return entry; - } - entry->value = strdup(value); - if (entry->value == NULL) - return NULL; - return entry; - } - } - - /* add new name */ - entry = new0(struct udev_list_entry, 1); - if (entry == NULL) - return NULL; - entry->name = strdup(name); - if (entry->name == NULL) { - free(entry); - return NULL; - } - if (value != NULL) { - entry->value = strdup(value); - if (entry->value == NULL) { - free(entry->name); - free(entry); - return NULL; - } - } - - if (list->unique) { - /* allocate or enlarge sorted array if needed */ - if (list->entries_cur >= list->entries_max) { - struct udev_list_entry **entries; - unsigned int add; - - add = list->entries_max; - if (add < 1) - add = 64; - entries = realloc(list->entries, (list->entries_max + add) * sizeof(struct udev_list_entry *)); - if (entries == NULL) { - free(entry->name); - free(entry->value); - free(entry); - return NULL; - } - list->entries = entries; - list->entries_max += add; - } - - /* the negative i returned the insertion index */ - i = (-i)-1; - - /* insert into sorted list */ - if ((unsigned int)i < list->entries_cur) - udev_list_entry_insert_before(entry, list->entries[i]); - else - udev_list_entry_append(entry, list); - - /* insert into sorted array */ - memmove(&list->entries[i+1], &list->entries[i], - (list->entries_cur - i) * sizeof(struct udev_list_entry *)); - list->entries[i] = entry; - list->entries_cur++; - } else { - udev_list_entry_append(entry, list); - } - - return entry; -} - -void udev_list_entry_delete(struct udev_list_entry *entry) -{ - if (entry->list->entries != NULL) { - int i; - struct udev_list *list = entry->list; - - /* remove entry from sorted array */ - i = list_search(list, entry->name); - if (i >= 0) { - memmove(&list->entries[i], &list->entries[i+1], - ((list->entries_cur-1) - i) * sizeof(struct udev_list_entry *)); - list->entries_cur--; - } - } - - udev_list_node_remove(&entry->node); - free(entry->name); - free(entry->value); - free(entry); -} - -void udev_list_cleanup(struct udev_list *list) -{ - struct udev_list_entry *entry_loop; - struct udev_list_entry *entry_tmp; - - list->entries = mfree(list->entries); - list->entries_cur = 0; - list->entries_max = 0; - udev_list_entry_foreach_safe(entry_loop, entry_tmp, udev_list_get_entry(list)) - udev_list_entry_delete(entry_loop); -} - -struct udev_list_entry *udev_list_get_entry(struct udev_list *list) -{ - if (udev_list_node_is_empty(&list->node)) - return NULL; - return list_node_to_entry(list->node.next); -} - -/** - * udev_list_entry_get_next: - * @list_entry: current entry - * - * Get the next entry from the list. - * - * Returns: udev_list_entry, #NULL if no more entries are available. - */ -_public_ struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry) -{ - struct udev_list_node *next; - - if (list_entry == NULL) - return NULL; - next = list_entry->node.next; - /* empty list or no more entries */ - if (next == &list_entry->list->node) - return NULL; - return list_node_to_entry(next); -} - -/** - * udev_list_entry_get_by_name: - * @list_entry: current entry - * @name: name string to match - * - * Lookup an entry in the list with a certain name. - * - * Returns: udev_list_entry, #NULL if no matching entry is found. - */ -_public_ struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name) -{ - int i; - - if (list_entry == NULL) - return NULL; - - if (!list_entry->list->unique) - return NULL; - - i = list_search(list_entry->list, name); - if (i < 0) - return NULL; - return list_entry->list->entries[i]; -} - -/** - * udev_list_entry_get_name: - * @list_entry: current entry - * - * Get the name of a list entry. - * - * Returns: the name string of this entry. - */ -_public_ const char *udev_list_entry_get_name(struct udev_list_entry *list_entry) -{ - if (list_entry == NULL) - return NULL; - return list_entry->name; -} - -/** - * udev_list_entry_get_value: - * @list_entry: current entry - * - * Get the value of list entry. - * - * Returns: the value string of this entry. - */ -_public_ const char *udev_list_entry_get_value(struct udev_list_entry *list_entry) -{ - if (list_entry == NULL) - return NULL; - return list_entry->value; -} - -int udev_list_entry_get_num(struct udev_list_entry *list_entry) -{ - if (list_entry == NULL) - return -EINVAL; - return list_entry->num; -} - -void udev_list_entry_set_num(struct udev_list_entry *list_entry, int num) -{ - if (list_entry == NULL) - return; - list_entry->num = num; -} diff --git a/src/libudev/libudev-monitor.c b/src/libudev/libudev-monitor.c deleted file mode 100644 index f870eba9eb..0000000000 --- a/src/libudev/libudev-monitor.c +++ /dev/null @@ -1,845 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "libudev.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "libudev-private.h" -#include "missing.h" -#include "mount-util.h" -#include "socket-util.h" -#include "string-util.h" - -/** - * SECTION:libudev-monitor - * @short_description: device event source - * - * Connects to a device event source. - */ - -/** - * udev_monitor: - * - * Opaque object handling an event source. - */ -struct udev_monitor { - struct udev *udev; - int refcount; - int sock; - union sockaddr_union snl; - union sockaddr_union snl_trusted_sender; - union sockaddr_union snl_destination; - socklen_t addrlen; - struct udev_list filter_subsystem_list; - struct udev_list filter_tag_list; - bool bound; -}; - -enum udev_monitor_netlink_group { - UDEV_MONITOR_NONE, - UDEV_MONITOR_KERNEL, - UDEV_MONITOR_UDEV, -}; - -#define UDEV_MONITOR_MAGIC 0xfeedcafe -struct udev_monitor_netlink_header { - /* "libudev" prefix to distinguish libudev and kernel messages */ - char prefix[8]; - /* - * magic to protect against daemon <-> library message format mismatch - * used in the kernel from socket filter rules; needs to be stored in network order - */ - unsigned int magic; - /* total length of header structure known to the sender */ - unsigned int header_size; - /* properties string buffer */ - unsigned int properties_off; - unsigned int properties_len; - /* - * hashes of primary device properties strings, to let libudev subscribers - * use in-kernel socket filters; values need to be stored in network order - */ - unsigned int filter_subsystem_hash; - unsigned int filter_devtype_hash; - unsigned int filter_tag_bloom_hi; - unsigned int filter_tag_bloom_lo; -}; - -static struct udev_monitor *udev_monitor_new(struct udev *udev) -{ - struct udev_monitor *udev_monitor; - - udev_monitor = new0(struct udev_monitor, 1); - if (udev_monitor == NULL) - return NULL; - udev_monitor->refcount = 1; - udev_monitor->udev = udev; - udev_list_init(udev, &udev_monitor->filter_subsystem_list, false); - udev_list_init(udev, &udev_monitor->filter_tag_list, true); - return udev_monitor; -} - -/* we consider udev running when /dev is on devtmpfs */ -static bool udev_has_devtmpfs(struct udev *udev) { - - union file_handle_union h = FILE_HANDLE_INIT; - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX], *e; - int mount_id; - int r; - - r = name_to_handle_at(AT_FDCWD, "/dev", &h.handle, &mount_id, 0); - if (r < 0) { - if (errno != EOPNOTSUPP) - log_debug_errno(errno, "name_to_handle_at on /dev: %m"); - return false; - } - - f = fopen("/proc/self/mountinfo", "re"); - if (!f) - return false; - - FOREACH_LINE(line, f, return false) { - int mid; - - if (sscanf(line, "%i", &mid) != 1) - continue; - - if (mid != mount_id) - continue; - - e = strstr(line, " - "); - if (!e) - continue; - - /* accept any name that starts with the currently expected type */ - if (startswith(e + 3, "devtmpfs")) - return true; - } - - return false; -} - -static void monitor_set_nl_address(struct udev_monitor *udev_monitor) { - union sockaddr_union snl; - socklen_t addrlen; - int r; - - assert(udev_monitor); - - /* get the address the kernel has assigned us - * it is usually, but not necessarily the pid - */ - addrlen = sizeof(struct sockaddr_nl); - r = getsockname(udev_monitor->sock, &snl.sa, &addrlen); - if (r >= 0) - udev_monitor->snl.nl.nl_pid = snl.nl.nl_pid; -} - -struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const char *name, int fd) -{ - struct udev_monitor *udev_monitor; - unsigned int group; - - if (udev == NULL) - return NULL; - - if (name == NULL) - group = UDEV_MONITOR_NONE; - else if (streq(name, "udev")) { - /* - * We do not support subscribing to uevents if no instance of - * udev is running. Uevents would otherwise broadcast the - * processing data of the host into containers, which is not - * desired. - * - * Containers will currently not get any udev uevents, until - * a supporting infrastructure is available. - * - * We do not set a netlink multicast group here, so the socket - * will not receive any messages. - */ - if (access("/run/udev/control", F_OK) < 0 && !udev_has_devtmpfs(udev)) { - log_debug("the udev service seems not to be active, disable the monitor"); - group = UDEV_MONITOR_NONE; - } else - group = UDEV_MONITOR_UDEV; - } else if (streq(name, "kernel")) - group = UDEV_MONITOR_KERNEL; - else - return NULL; - - udev_monitor = udev_monitor_new(udev); - if (udev_monitor == NULL) - return NULL; - - if (fd < 0) { - udev_monitor->sock = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT); - if (udev_monitor->sock < 0) { - log_debug_errno(errno, "error getting socket: %m"); - free(udev_monitor); - return NULL; - } - } else { - udev_monitor->bound = true; - udev_monitor->sock = fd; - monitor_set_nl_address(udev_monitor); - } - - udev_monitor->snl.nl.nl_family = AF_NETLINK; - udev_monitor->snl.nl.nl_groups = group; - - /* default destination for sending */ - udev_monitor->snl_destination.nl.nl_family = AF_NETLINK; - udev_monitor->snl_destination.nl.nl_groups = UDEV_MONITOR_UDEV; - - return udev_monitor; -} - -/** - * udev_monitor_new_from_netlink: - * @udev: udev library context - * @name: name of event source - * - * Create new udev monitor and connect to a specified event - * source. Valid sources identifiers are "udev" and "kernel". - * - * Applications should usually not connect directly to the - * "kernel" events, because the devices might not be useable - * at that time, before udev has configured them, and created - * device nodes. Accessing devices at the same time as udev, - * might result in unpredictable behavior. The "udev" events - * are sent out after udev has finished its event processing, - * all rules have been processed, and needed device nodes are - * created. - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev monitor. - * - * Returns: a new udev monitor, or #NULL, in case of an error - **/ -_public_ struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name) -{ - return udev_monitor_new_from_netlink_fd(udev, name, -1); -} - -static inline void bpf_stmt(struct sock_filter *inss, unsigned int *i, - unsigned short code, unsigned int data) -{ - struct sock_filter *ins = &inss[*i]; - - ins->code = code; - ins->k = data; - (*i)++; -} - -static inline void bpf_jmp(struct sock_filter *inss, unsigned int *i, - unsigned short code, unsigned int data, - unsigned short jt, unsigned short jf) -{ - struct sock_filter *ins = &inss[*i]; - - ins->code = code; - ins->jt = jt; - ins->jf = jf; - ins->k = data; - (*i)++; -} - -/** - * udev_monitor_filter_update: - * @udev_monitor: monitor - * - * Update the installed socket filter. This is only needed, - * if the filter was removed or changed. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_monitor_filter_update(struct udev_monitor *udev_monitor) -{ - struct sock_filter ins[512]; - struct sock_fprog filter; - unsigned int i; - struct udev_list_entry *list_entry; - int err; - - if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL && - udev_list_get_entry(&udev_monitor->filter_tag_list) == NULL) - return 0; - - memzero(ins, sizeof(ins)); - i = 0; - - /* load magic in A */ - bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, magic)); - /* jump if magic matches */ - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, UDEV_MONITOR_MAGIC, 1, 0); - /* wrong magic, pass packet */ - bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); - - if (udev_list_get_entry(&udev_monitor->filter_tag_list) != NULL) { - int tag_matches; - - /* count tag matches, to calculate end of tag match block */ - tag_matches = 0; - udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) - tag_matches++; - - /* add all tags matches */ - udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) { - uint64_t tag_bloom_bits = util_string_bloom64(udev_list_entry_get_name(list_entry)); - uint32_t tag_bloom_hi = tag_bloom_bits >> 32; - uint32_t tag_bloom_lo = tag_bloom_bits & 0xffffffff; - - /* load device bloom bits in A */ - bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_tag_bloom_hi)); - /* clear bits (tag bits & bloom bits) */ - bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_hi); - /* jump to next tag if it does not match */ - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_hi, 0, 3); - - /* load device bloom bits in A */ - bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_tag_bloom_lo)); - /* clear bits (tag bits & bloom bits) */ - bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_lo); - /* jump behind end of tag match block if tag matches */ - tag_matches--; - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_lo, 1 + (tag_matches * 6), 0); - } - - /* nothing matched, drop packet */ - bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); - } - - /* add all subsystem matches */ - if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) != NULL) { - udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) { - unsigned int hash = util_string_hash32(udev_list_entry_get_name(list_entry)); - - /* load device subsystem value in A */ - bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_subsystem_hash)); - if (udev_list_entry_get_value(list_entry) == NULL) { - /* jump if subsystem does not match */ - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); - } else { - /* jump if subsystem does not match */ - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 3); - - /* load device devtype value in A */ - bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_devtype_hash)); - /* jump if value does not match */ - hash = util_string_hash32(udev_list_entry_get_value(list_entry)); - bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); - } - - /* matched, pass packet */ - bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); - - if (i+1 >= ELEMENTSOF(ins)) - return -E2BIG; - } - - /* nothing matched, drop packet */ - bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); - } - - /* matched, pass packet */ - bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); - - /* install filter */ - memzero(&filter, sizeof(filter)); - filter.len = i; - filter.filter = ins; - err = setsockopt(udev_monitor->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)); - return err < 0 ? -errno : 0; -} - -int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender) -{ - udev_monitor->snl_trusted_sender.nl.nl_pid = sender->snl.nl.nl_pid; - return 0; -} - -/** - * udev_monitor_enable_receiving: - * @udev_monitor: the monitor which should receive events - * - * Binds the @udev_monitor socket to the event source. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor) -{ - int err = 0; - const int on = 1; - - udev_monitor_filter_update(udev_monitor); - - if (!udev_monitor->bound) { - err = bind(udev_monitor->sock, - &udev_monitor->snl.sa, sizeof(struct sockaddr_nl)); - if (err == 0) - udev_monitor->bound = true; - } - - if (err >= 0) - monitor_set_nl_address(udev_monitor); - else - return log_debug_errno(errno, "bind failed: %m"); - - /* enable receiving of sender credentials */ - err = setsockopt(udev_monitor->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); - if (err < 0) - log_debug_errno(errno, "setting SO_PASSCRED failed: %m"); - - return 0; -} - -/** - * udev_monitor_set_receive_buffer_size: - * @udev_monitor: the monitor which should receive events - * @size: the size in bytes - * - * Set the size of the kernel socket buffer. This call needs the - * appropriate privileges to succeed. - * - * Returns: 0 on success, otherwise -1 on error. - */ -_public_ int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size) -{ - if (udev_monitor == NULL) - return -EINVAL; - return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_RCVBUFFORCE, &size, sizeof(size)); -} - -int udev_monitor_disconnect(struct udev_monitor *udev_monitor) -{ - int err; - - err = close(udev_monitor->sock); - udev_monitor->sock = -1; - return err < 0 ? -errno : 0; -} - -/** - * udev_monitor_ref: - * @udev_monitor: udev monitor - * - * Take a reference of a udev monitor. - * - * Returns: the passed udev monitor - **/ -_public_ struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor) -{ - if (udev_monitor == NULL) - return NULL; - udev_monitor->refcount++; - return udev_monitor; -} - -/** - * udev_monitor_unref: - * @udev_monitor: udev monitor - * - * Drop a reference of a udev monitor. If the refcount reaches zero, - * the bound socket will be closed, and the resources of the monitor - * will be released. - * - * Returns: #NULL - **/ -_public_ struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor) -{ - if (udev_monitor == NULL) - return NULL; - udev_monitor->refcount--; - if (udev_monitor->refcount > 0) - return NULL; - if (udev_monitor->sock >= 0) - close(udev_monitor->sock); - udev_list_cleanup(&udev_monitor->filter_subsystem_list); - udev_list_cleanup(&udev_monitor->filter_tag_list); - free(udev_monitor); - return NULL; -} - -/** - * udev_monitor_get_udev: - * @udev_monitor: udev monitor - * - * Retrieve the udev library context the monitor was created with. - * - * Returns: the udev library context - **/ -_public_ struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor) -{ - if (udev_monitor == NULL) - return NULL; - return udev_monitor->udev; -} - -/** - * udev_monitor_get_fd: - * @udev_monitor: udev monitor - * - * Retrieve the socket file descriptor associated with the monitor. - * - * Returns: the socket file descriptor - **/ -_public_ int udev_monitor_get_fd(struct udev_monitor *udev_monitor) -{ - if (udev_monitor == NULL) - return -EINVAL; - return udev_monitor->sock; -} - -static int passes_filter(struct udev_monitor *udev_monitor, struct udev_device *udev_device) -{ - struct udev_list_entry *list_entry; - - if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL) - goto tag; - udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) { - const char *subsys = udev_list_entry_get_name(list_entry); - const char *dsubsys = udev_device_get_subsystem(udev_device); - const char *devtype; - const char *ddevtype; - - if (!streq(dsubsys, subsys)) - continue; - - devtype = udev_list_entry_get_value(list_entry); - if (devtype == NULL) - goto tag; - ddevtype = udev_device_get_devtype(udev_device); - if (ddevtype == NULL) - continue; - if (streq(ddevtype, devtype)) - goto tag; - } - return 0; - -tag: - if (udev_list_get_entry(&udev_monitor->filter_tag_list) == NULL) - return 1; - udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) { - const char *tag = udev_list_entry_get_name(list_entry); - - if (udev_device_has_tag(udev_device, tag)) - return 1; - } - return 0; -} - -/** - * udev_monitor_receive_device: - * @udev_monitor: udev monitor - * - * Receive data from the udev monitor socket, allocate a new udev - * device, fill in the received data, and return the device. - * - * Only socket connections with uid=0 are accepted. - * - * The monitor socket is by default set to NONBLOCK. A variant of poll() on - * the file descriptor returned by udev_monitor_get_fd() should to be used to - * wake up when new devices arrive, or alternatively the file descriptor - * switched into blocking mode. - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev device. - * - * Returns: a new udev device, or #NULL, in case of an error - **/ -_public_ struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor) -{ - struct udev_device *udev_device; - struct msghdr smsg; - struct iovec iov; - char cred_msg[CMSG_SPACE(sizeof(struct ucred))]; - struct cmsghdr *cmsg; - union sockaddr_union snl; - struct ucred *cred; - union { - struct udev_monitor_netlink_header nlh; - char raw[8192]; - } buf; - ssize_t buflen; - ssize_t bufpos; - bool is_initialized = false; - -retry: - if (udev_monitor == NULL) - return NULL; - iov.iov_base = &buf; - iov.iov_len = sizeof(buf); - memzero(&smsg, sizeof(struct msghdr)); - smsg.msg_iov = &iov; - smsg.msg_iovlen = 1; - smsg.msg_control = cred_msg; - smsg.msg_controllen = sizeof(cred_msg); - smsg.msg_name = &snl; - smsg.msg_namelen = sizeof(snl); - - buflen = recvmsg(udev_monitor->sock, &smsg, 0); - if (buflen < 0) { - if (errno != EINTR) - log_debug("unable to receive message"); - return NULL; - } - - if (buflen < 32 || (smsg.msg_flags & MSG_TRUNC)) { - log_debug("invalid message length"); - return NULL; - } - - if (snl.nl.nl_groups == 0) { - /* unicast message, check if we trust the sender */ - if (udev_monitor->snl_trusted_sender.nl.nl_pid == 0 || - snl.nl.nl_pid != udev_monitor->snl_trusted_sender.nl.nl_pid) { - log_debug("unicast netlink message ignored"); - return NULL; - } - } else if (snl.nl.nl_groups == UDEV_MONITOR_KERNEL) { - if (snl.nl.nl_pid > 0) { - log_debug("multicast kernel netlink message from PID %"PRIu32" ignored", - snl.nl.nl_pid); - return NULL; - } - } - - cmsg = CMSG_FIRSTHDR(&smsg); - if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) { - log_debug("no sender credentials received, message ignored"); - return NULL; - } - - cred = (struct ucred *)CMSG_DATA(cmsg); - if (cred->uid != 0) { - log_debug("sender uid="UID_FMT", message ignored", cred->uid); - return NULL; - } - - if (memcmp(buf.raw, "libudev", 8) == 0) { - /* udev message needs proper version magic */ - if (buf.nlh.magic != htonl(UDEV_MONITOR_MAGIC)) { - log_debug("unrecognized message signature (%x != %x)", - buf.nlh.magic, htonl(UDEV_MONITOR_MAGIC)); - return NULL; - } - if (buf.nlh.properties_off+32 > (size_t)buflen) { - log_debug("message smaller than expected (%u > %zd)", - buf.nlh.properties_off+32, buflen); - return NULL; - } - - bufpos = buf.nlh.properties_off; - - /* devices received from udev are always initialized */ - is_initialized = true; - } else { - /* kernel message with header */ - bufpos = strlen(buf.raw) + 1; - if ((size_t)bufpos < sizeof("a@/d") || bufpos >= buflen) { - log_debug("invalid message length"); - return NULL; - } - - /* check message header */ - if (strstr(buf.raw, "@/") == NULL) { - log_debug("unrecognized message header"); - return NULL; - } - } - - udev_device = udev_device_new_from_nulstr(udev_monitor->udev, &buf.raw[bufpos], buflen - bufpos); - if (!udev_device) { - log_debug("could not create device: %m"); - return NULL; - } - - if (is_initialized) - udev_device_set_is_initialized(udev_device); - - /* skip device, if it does not pass the current filter */ - if (!passes_filter(udev_monitor, udev_device)) { - struct pollfd pfd[1]; - int rc; - - udev_device_unref(udev_device); - - /* if something is queued, get next device */ - pfd[0].fd = udev_monitor->sock; - pfd[0].events = POLLIN; - rc = poll(pfd, 1, 0); - if (rc > 0) - goto retry; - return NULL; - } - - return udev_device; -} - -int udev_monitor_send_device(struct udev_monitor *udev_monitor, - struct udev_monitor *destination, struct udev_device *udev_device) -{ - const char *buf, *val; - ssize_t blen, count; - struct udev_monitor_netlink_header nlh = { - .prefix = "libudev", - .magic = htonl(UDEV_MONITOR_MAGIC), - .header_size = sizeof nlh, - }; - struct iovec iov[2] = { - { .iov_base = &nlh, .iov_len = sizeof nlh }, - }; - struct msghdr smsg = { - .msg_iov = iov, - .msg_iovlen = 2, - }; - struct udev_list_entry *list_entry; - uint64_t tag_bloom_bits; - - blen = udev_device_get_properties_monitor_buf(udev_device, &buf); - if (blen < 32) { - log_debug("device buffer is too small to contain a valid device"); - return -EINVAL; - } - - /* fill in versioned header */ - val = udev_device_get_subsystem(udev_device); - nlh.filter_subsystem_hash = htonl(util_string_hash32(val)); - - val = udev_device_get_devtype(udev_device); - if (val != NULL) - nlh.filter_devtype_hash = htonl(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); - } - - /* add properties list */ - nlh.properties_off = iov[0].iov_len; - nlh.properties_len = blen; - iov[1].iov_base = (char *)buf; - iov[1].iov_len = blen; - - /* - * Use custom address for target, or the default one. - * - * If we send to a multicast group, we will get - * ECONNREFUSED, which is expected. - */ - if (destination) - smsg.msg_name = &destination->snl; - else - smsg.msg_name = &udev_monitor->snl_destination; - smsg.msg_namelen = sizeof(struct sockaddr_nl); - count = sendmsg(udev_monitor->sock, &smsg, 0); - if (count < 0) { - if (!destination && errno == ECONNREFUSED) { - log_debug("passed device to netlink monitor %p", udev_monitor); - return 0; - } else - return -errno; - } - - log_debug("passed %zi byte device to netlink monitor %p", count, udev_monitor); - return count; -} - -/** - * udev_monitor_filter_add_match_subsystem_devtype: - * @udev_monitor: the monitor - * @subsystem: the subsystem value to match the incoming devices against - * @devtype: the devtype value to match the incoming devices against - * - * This filter is efficiently executed inside the kernel, and libudev subscribers - * will usually not be woken up for devices which do not match. - * - * The filter must be installed before the monitor is switched to listening mode. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype) -{ - if (udev_monitor == NULL) - return -EINVAL; - if (subsystem == NULL) - return -EINVAL; - if (udev_list_entry_add(&udev_monitor->filter_subsystem_list, subsystem, devtype) == NULL) - return -ENOMEM; - return 0; -} - -/** - * udev_monitor_filter_add_match_tag: - * @udev_monitor: the monitor - * @tag: the name of a tag - * - * This filter is efficiently executed inside the kernel, and libudev subscribers - * will usually not be woken up for devices which do not match. - * - * The filter must be installed before the monitor is switched to listening mode. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag) -{ - if (udev_monitor == NULL) - return -EINVAL; - if (tag == NULL) - return -EINVAL; - if (udev_list_entry_add(&udev_monitor->filter_tag_list, tag, NULL) == NULL) - return -ENOMEM; - return 0; -} - -/** - * udev_monitor_filter_remove: - * @udev_monitor: monitor - * - * Remove all filters from monitor. - * - * Returns: 0 on success, otherwise a negative error value. - */ -_public_ int udev_monitor_filter_remove(struct udev_monitor *udev_monitor) -{ - static struct sock_fprog filter = { 0, NULL }; - - udev_list_cleanup(&udev_monitor->filter_subsystem_list); - return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)); -} diff --git a/src/libudev/libudev-private.h b/src/libudev/libudev-private.h deleted file mode 100644 index 52c5075110..0000000000 --- a/src/libudev/libudev-private.h +++ /dev/null @@ -1,150 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 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 . -***/ - -#ifndef _LIBUDEV_PRIVATE_H_ -#define _LIBUDEV_PRIVATE_H_ - -#include -#include -#include - -#include "libudev.h" - -#include "macro.h" -#include "mkdir.h" -#include "strxcpyx.h" -#include "util.h" - -#define READ_END 0 -#define WRITE_END 1 - -/* libudev.c */ -int udev_get_rules_path(struct udev *udev, char **path[], usec_t *ts_usec[]); - -/* libudev-device.c */ -struct udev_device *udev_device_new_from_nulstr(struct udev *udev, char *nulstr, ssize_t buflen); -struct udev_device *udev_device_new_from_synthetic_event(struct udev *udev, const char *syspath, const char *action); -struct udev_device *udev_device_shallow_clone(struct udev_device *old_device); -struct udev_device *udev_device_clone_with_db(struct udev_device *old_device); -int udev_device_copy_properties(struct udev_device *dst, struct udev_device *src); -mode_t udev_device_get_devnode_mode(struct udev_device *udev_device); -uid_t udev_device_get_devnode_uid(struct udev_device *udev_device); -gid_t udev_device_get_devnode_gid(struct udev_device *udev_device); -int udev_device_rename(struct udev_device *udev_device, const char *new_name); -int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink); -void udev_device_cleanup_devlinks_list(struct udev_device *udev_device); -int udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value); -char **udev_device_get_properties_envp(struct udev_device *udev_device); -ssize_t udev_device_get_properties_monitor_buf(struct udev_device *udev_device, const char **buf); -const char *udev_device_get_devpath_old(struct udev_device *udev_device); -const char *udev_device_get_id_filename(struct udev_device *udev_device); -void udev_device_set_is_initialized(struct udev_device *udev_device); -int udev_device_add_tag(struct udev_device *udev_device, const char *tag); -void udev_device_remove_tag(struct udev_device *udev_device, const char *tag); -void udev_device_cleanup_tags_list(struct udev_device *udev_device); -usec_t udev_device_get_usec_initialized(struct udev_device *udev_device); -void udev_device_ensure_usec_initialized(struct udev_device *udev_device, struct udev_device *old_device); -int udev_device_get_devlink_priority(struct udev_device *udev_device); -int udev_device_set_devlink_priority(struct udev_device *udev_device, int prio); -int udev_device_get_watch_handle(struct udev_device *udev_device); -int udev_device_set_watch_handle(struct udev_device *udev_device, int handle); -int udev_device_get_ifindex(struct udev_device *udev_device); -void udev_device_set_info_loaded(struct udev_device *device); -bool udev_device_get_db_persist(struct udev_device *udev_device); -void udev_device_set_db_persist(struct udev_device *udev_device); -void udev_device_read_db(struct udev_device *udev_device); - -/* libudev-device-private.c */ -int udev_device_update_db(struct udev_device *udev_device); -int udev_device_delete_db(struct udev_device *udev_device); -int udev_device_tag_index(struct udev_device *dev, struct udev_device *dev_old, bool add); - -/* libudev-monitor.c - netlink/unix socket communication */ -int udev_monitor_disconnect(struct udev_monitor *udev_monitor); -int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender); -int udev_monitor_send_device(struct udev_monitor *udev_monitor, - struct udev_monitor *destination, struct udev_device *udev_device); -struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const char *name, int fd); - -/* libudev-list.c */ -struct udev_list_node { - struct udev_list_node *next, *prev; -}; -struct udev_list { - struct udev *udev; - struct udev_list_node node; - struct udev_list_entry **entries; - unsigned int entries_cur; - unsigned int entries_max; - bool unique; -}; -void udev_list_node_init(struct udev_list_node *list); -int udev_list_node_is_empty(struct udev_list_node *list); -void udev_list_node_append(struct udev_list_node *new, struct udev_list_node *list); -void udev_list_node_remove(struct udev_list_node *entry); -#define udev_list_node_foreach(node, list) \ - for (node = (list)->next; \ - node != list; \ - node = (node)->next) -#define udev_list_node_foreach_safe(node, tmp, list) \ - for (node = (list)->next, tmp = (node)->next; \ - node != list; \ - node = tmp, tmp = (tmp)->next) -void udev_list_init(struct udev *udev, struct udev_list *list, bool unique); -void udev_list_cleanup(struct udev_list *list); -struct udev_list_entry *udev_list_get_entry(struct udev_list *list); -struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *name, const char *value); -void udev_list_entry_delete(struct udev_list_entry *entry); -int udev_list_entry_get_num(struct udev_list_entry *list_entry); -void udev_list_entry_set_num(struct udev_list_entry *list_entry, int num); -#define udev_list_entry_foreach_safe(entry, tmp, first) \ - for (entry = first, tmp = udev_list_entry_get_next(entry); \ - entry != NULL; \ - entry = tmp, tmp = udev_list_entry_get_next(tmp)) - -/* libudev-queue.c */ -unsigned long long int udev_get_kernel_seqnum(struct udev *udev); -int udev_queue_read_seqnum(FILE *queue_file, unsigned long long int *seqnum); -ssize_t udev_queue_read_devpath(FILE *queue_file, char *devpath, size_t size); -ssize_t udev_queue_skip_devpath(FILE *queue_file); - -/* libudev-queue-private.c */ -struct udev_queue_export *udev_queue_export_new(struct udev *udev); -struct udev_queue_export *udev_queue_export_unref(struct udev_queue_export *udev_queue_export); -void udev_queue_export_cleanup(struct udev_queue_export *udev_queue_export); -int udev_queue_export_device_queued(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device); -int udev_queue_export_device_finished(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device); - -/* libudev-util.c */ -#define UTIL_PATH_SIZE 1024 -#define UTIL_NAME_SIZE 512 -#define UTIL_LINE_SIZE 16384 -#define UDEV_ALLOWED_CHARS_INPUT "/ $%?," -int util_log_priority(const char *priority); -size_t util_path_encode(const char *src, char *dest, size_t size); -void util_remove_trailing_chars(char *path, char c); -int util_replace_whitespace(const char *str, char *to, size_t len); -int util_replace_chars(char *str, const char *white); -unsigned int util_string_hash32(const char *key); -uint64_t util_string_bloom64(const char *str); - -/* libudev-util-private.c */ -int util_resolve_subsys_kernel(struct udev *udev, const char *string, char *result, size_t maxsize, int read_value); - -#endif diff --git a/src/libudev/libudev-queue.c b/src/libudev/libudev-queue.c deleted file mode 100644 index e3dffa6925..0000000000 --- a/src/libudev/libudev-queue.c +++ /dev/null @@ -1,268 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2009 Alan Jenkins - - 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "libudev-private.h" - -/** - * SECTION:libudev-queue - * @short_description: access to currently active events - * - * This exports the current state of the udev processing queue. - */ - -/** - * udev_queue: - * - * Opaque object representing the current event queue in the udev daemon. - */ -struct udev_queue { - struct udev *udev; - int refcount; - int fd; -}; - -/** - * udev_queue_new: - * @udev: udev library context - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev queue context. - * - * Returns: the udev queue context, or #NULL on error. - **/ -_public_ struct udev_queue *udev_queue_new(struct udev *udev) -{ - struct udev_queue *udev_queue; - - if (udev == NULL) - return NULL; - - udev_queue = new0(struct udev_queue, 1); - if (udev_queue == NULL) - return NULL; - - udev_queue->refcount = 1; - udev_queue->udev = udev; - udev_queue->fd = -1; - return udev_queue; -} - -/** - * udev_queue_ref: - * @udev_queue: udev queue context - * - * Take a reference of a udev queue context. - * - * Returns: the same udev queue context. - **/ -_public_ struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue) -{ - if (udev_queue == NULL) - return NULL; - - udev_queue->refcount++; - return udev_queue; -} - -/** - * udev_queue_unref: - * @udev_queue: udev queue context - * - * Drop a reference of a udev queue context. If the refcount reaches zero, - * the resources of the queue context will be released. - * - * Returns: #NULL - **/ -_public_ struct udev_queue *udev_queue_unref(struct udev_queue *udev_queue) -{ - if (udev_queue == NULL) - return NULL; - - udev_queue->refcount--; - if (udev_queue->refcount > 0) - return NULL; - - safe_close(udev_queue->fd); - - free(udev_queue); - return NULL; -} - -/** - * udev_queue_get_udev: - * @udev_queue: udev queue context - * - * Retrieve the udev library context the queue context was created with. - * - * Returns: the udev library context. - **/ -_public_ struct udev *udev_queue_get_udev(struct udev_queue *udev_queue) -{ - if (udev_queue == NULL) - return NULL; - return udev_queue->udev; -} - -/** - * udev_queue_get_kernel_seqnum: - * @udev_queue: udev queue context - * - * This function is deprecated. - * - * Returns: 0. - **/ -_public_ unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) -{ - return 0; -} - -/** - * udev_queue_get_udev_seqnum: - * @udev_queue: udev queue context - * - * This function is deprecated. - * - * Returns: 0. - **/ -_public_ unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) -{ - return 0; -} - -/** - * udev_queue_get_udev_is_active: - * @udev_queue: udev queue context - * - * Check if udev is active on the system. - * - * Returns: a flag indicating if udev is active. - **/ -_public_ int udev_queue_get_udev_is_active(struct udev_queue *udev_queue) -{ - return access("/run/udev/control", F_OK) >= 0; -} - -/** - * udev_queue_get_queue_is_empty: - * @udev_queue: udev queue context - * - * Check if udev is currently processing any events. - * - * Returns: a flag indicating if udev is currently handling events. - **/ -_public_ int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue) -{ - return access("/run/udev/queue", F_OK) < 0; -} - -/** - * udev_queue_get_seqnum_sequence_is_finished: - * @udev_queue: udev queue context - * @start: first event sequence number - * @end: last event sequence number - * - * This function is deprecated, it just returns the result of - * udev_queue_get_queue_is_empty(). - * - * Returns: a flag indicating if udev is currently handling events. - **/ -_public_ int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, - unsigned long long int start, unsigned long long int end) -{ - return udev_queue_get_queue_is_empty(udev_queue); -} - -/** - * udev_queue_get_seqnum_is_finished: - * @udev_queue: udev queue context - * @seqnum: sequence number - * - * This function is deprecated, it just returns the result of - * udev_queue_get_queue_is_empty(). - * - * Returns: a flag indicating if udev is currently handling events. - **/ -_public_ int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) -{ - return udev_queue_get_queue_is_empty(udev_queue); -} - -/** - * udev_queue_get_queued_list_entry: - * @udev_queue: udev queue context - * - * This function is deprecated. - * - * Returns: NULL. - **/ -_public_ struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) -{ - return NULL; -} - -/** - * udev_queue_get_fd: - * @udev_queue: udev queue context - * - * Returns: a file descriptor to watch for a queue to become empty. - */ -_public_ int udev_queue_get_fd(struct udev_queue *udev_queue) { - int fd; - int r; - - if (udev_queue->fd >= 0) - return udev_queue->fd; - - fd = inotify_init1(IN_CLOEXEC); - if (fd < 0) - return -errno; - - r = inotify_add_watch(fd, "/run/udev" , IN_DELETE); - if (r < 0) { - r = -errno; - close(fd); - return r; - } - - udev_queue->fd = fd; - return fd; -} - -/** - * udev_queue_flush: - * @udev_queue: udev queue context - * - * Returns: the result of clearing the watch for queue changes. - */ -_public_ int udev_queue_flush(struct udev_queue *udev_queue) { - if (udev_queue->fd < 0) - return -EINVAL; - - return flush_fd(udev_queue->fd); -} diff --git a/src/libudev/libudev-util.c b/src/libudev/libudev-util.c deleted file mode 100644 index 574cfeac85..0000000000 --- a/src/libudev/libudev-util.c +++ /dev/null @@ -1,268 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "libudev.h" - -#include "MurmurHash2.h" -#include "device-nodes.h" -#include "libudev-private.h" -#include "syslog-util.h" -#include "utf8.h" - -/** - * SECTION:libudev-util - * @short_description: utils - * - * Utilities useful when dealing with devices and device node names. - */ - -/* handle "[/]" format */ -int util_resolve_subsys_kernel(struct udev *udev, const char *string, - char *result, size_t maxsize, int read_value) -{ - char temp[UTIL_PATH_SIZE]; - char *subsys; - char *sysname; - struct udev_device *dev; - char *attr; - - if (string[0] != '[') - return -1; - - strscpy(temp, sizeof(temp), string); - - subsys = &temp[1]; - - sysname = strchr(subsys, '/'); - if (sysname == NULL) - return -1; - sysname[0] = '\0'; - sysname = &sysname[1]; - - attr = strchr(sysname, ']'); - if (attr == NULL) - return -1; - attr[0] = '\0'; - attr = &attr[1]; - if (attr[0] == '/') - attr = &attr[1]; - if (attr[0] == '\0') - attr = NULL; - - if (read_value && attr == NULL) - return -1; - - dev = udev_device_new_from_subsystem_sysname(udev, subsys, sysname); - if (dev == NULL) - return -1; - - if (read_value) { - const char *val; - - val = udev_device_get_sysattr_value(dev, attr); - if (val != NULL) - strscpy(result, maxsize, val); - else - result[0] = '\0'; - log_debug("value '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); - } else { - size_t l; - char *s; - - s = result; - l = strpcpyl(&s, maxsize, udev_device_get_syspath(dev), NULL); - if (attr != NULL) - strpcpyl(&s, l, "/", attr, NULL); - log_debug("path '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); - } - udev_device_unref(dev); - return 0; -} - -int util_log_priority(const char *priority) -{ - char *endptr; - int prio; - - prio = strtoul(priority, &endptr, 10); - if (endptr[0] == '\0' || isspace(endptr[0])) { - if (prio >= 0 && prio <= 7) - return prio; - else - return -ERANGE; - } - - return log_level_from_string(priority); -} - -size_t util_path_encode(const char *src, char *dest, size_t size) -{ - size_t i, j; - - for (i = 0, j = 0; src[i] != '\0'; i++) { - if (src[i] == '/') { - if (j+4 >= size) { - j = 0; - break; - } - memcpy(&dest[j], "\\x2f", 4); - j += 4; - } else if (src[i] == '\\') { - if (j+4 >= size) { - j = 0; - break; - } - memcpy(&dest[j], "\\x5c", 4); - j += 4; - } else { - if (j+1 >= size) { - j = 0; - break; - } - dest[j] = src[i]; - j++; - } - } - dest[j] = '\0'; - return j; -} - -void util_remove_trailing_chars(char *path, char c) -{ - size_t len; - - if (path == NULL) - return; - len = strlen(path); - while (len > 0 && path[len-1] == c) - path[--len] = '\0'; -} - -int util_replace_whitespace(const char *str, char *to, size_t len) -{ - size_t i, j; - - /* strip trailing whitespace */ - len = strnlen(str, len); - while (len && isspace(str[len-1])) - len--; - - /* strip leading whitespace */ - i = 0; - while ((i < len) && isspace(str[i])) - i++; - - j = 0; - while (i < len) { - /* substitute multiple whitespace with a single '_' */ - if (isspace(str[i])) { - while (isspace(str[i])) - i++; - to[j++] = '_'; - } - to[j++] = str[i++]; - } - to[j] = '\0'; - return 0; -} - -/* allow chars in whitelist, plain ascii, hex-escaping and valid utf8 */ -int util_replace_chars(char *str, const char *white) -{ - size_t i = 0; - int replaced = 0; - - while (str[i] != '\0') { - int len; - - if (whitelisted_char_for_devnode(str[i], white)) { - i++; - continue; - } - - /* accept hex encoding */ - if (str[i] == '\\' && str[i+1] == 'x') { - i += 2; - continue; - } - - /* accept valid utf8 */ - len = utf8_encoded_valid_unichar(&str[i]); - if (len > 1) { - i += len; - continue; - } - - /* if space is allowed, replace whitespace with ordinary space */ - if (isspace(str[i]) && white != NULL && strchr(white, ' ') != NULL) { - str[i] = ' '; - i++; - replaced++; - continue; - } - - /* everything else is replaced with '_' */ - str[i] = '_'; - i++; - replaced++; - } - return replaced; -} - -/** - * udev_util_encode_string: - * @str: input string to be encoded - * @str_enc: output string to store the encoded input string - * @len: maximum size of the output string, which may be - * four times as long as the input string - * - * Encode all potentially unsafe characters of a string to the - * corresponding 2 char hex value prefixed by '\x'. - * - * Returns: 0 if the entire string was copied, non-zero otherwise. - **/ -_public_ int udev_util_encode_string(const char *str, char *str_enc, size_t len) -{ - return encode_devnode_name(str, str_enc, len); -} - -unsigned int util_string_hash32(const char *str) -{ - return MurmurHash2(str, strlen(str), 0); -} - -/* get a bunch of bit numbers out of the hash, and set the bits in our bit field */ -uint64_t util_string_bloom64(const char *str) -{ - uint64_t bits = 0; - unsigned int hash = util_string_hash32(str); - - bits |= 1LLU << (hash & 63); - bits |= 1LLU << ((hash >> 6) & 63); - bits |= 1LLU << ((hash >> 12) & 63); - bits |= 1LLU << ((hash >> 18) & 63); - return bits; -} diff --git a/src/libudev/libudev.c b/src/libudev/libudev.c deleted file mode 100644 index 63fb05547d..0000000000 --- a/src/libudev/libudev.c +++ /dev/null @@ -1,253 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2014 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "libudev.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "libudev-private.h" -#include "missing.h" -#include "string-util.h" - -/** - * SECTION:libudev - * @short_description: libudev context - * - * The context contains the default values read from the udev config file, - * and is passed to all library operations. - */ - -/** - * udev: - * - * Opaque object representing the library context. - */ -struct udev { - int refcount; - void (*log_fn)(struct udev *udev, - int priority, const char *file, int line, const char *fn, - const char *format, va_list args); - void *userdata; -}; - -/** - * udev_get_userdata: - * @udev: udev library context - * - * Retrieve stored data pointer from library context. This might be useful - * to access from callbacks. - * - * Returns: stored userdata - **/ -_public_ void *udev_get_userdata(struct udev *udev) { - if (udev == NULL) - return NULL; - return udev->userdata; -} - -/** - * udev_set_userdata: - * @udev: udev library context - * @userdata: data pointer - * - * Store custom @userdata in the library context. - **/ -_public_ void udev_set_userdata(struct udev *udev, void *userdata) { - if (udev == NULL) - return; - udev->userdata = userdata; -} - -/** - * udev_new: - * - * Create udev library context. This reads the udev configuration - * file, and fills in the default values. - * - * The initial refcount is 1, and needs to be decremented to - * release the resources of the udev library context. - * - * Returns: a new udev library context - **/ -_public_ struct udev *udev_new(void) { - struct udev *udev; - _cleanup_fclose_ FILE *f = NULL; - - udev = new0(struct udev, 1); - if (udev == NULL) - return NULL; - udev->refcount = 1; - - f = fopen("/etc/udev/udev.conf", "re"); - if (f != NULL) { - char line[UTIL_LINE_SIZE]; - unsigned line_nr = 0; - - while (fgets(line, sizeof(line), f)) { - size_t len; - char *key; - char *val; - - line_nr++; - - /* find key */ - key = line; - while (isspace(key[0])) - key++; - - /* comment or empty line */ - if (key[0] == '#' || key[0] == '\0') - continue; - - /* split key/value */ - val = strchr(key, '='); - if (val == NULL) { - log_debug("/etc/udev/udev.conf:%u: missing assignment, skipping line.", line_nr); - continue; - } - val[0] = '\0'; - val++; - - /* find value */ - while (isspace(val[0])) - val++; - - /* terminate key */ - len = strlen(key); - if (len == 0) - continue; - while (isspace(key[len-1])) - len--; - key[len] = '\0'; - - /* terminate value */ - len = strlen(val); - if (len == 0) - continue; - while (isspace(val[len-1])) - len--; - val[len] = '\0'; - - if (len == 0) - continue; - - /* unquote */ - if (val[0] == '"' || val[0] == '\'') { - if (val[len-1] != val[0]) { - log_debug("/etc/udev/udev.conf:%u: inconsistent quoting, skipping line.", line_nr); - continue; - } - val[len-1] = '\0'; - val++; - } - - if (streq(key, "udev_log")) { - int prio; - - prio = util_log_priority(val); - if (prio < 0) - log_debug("/etc/udev/udev.conf:%u: invalid log level '%s', ignoring.", line_nr, val); - else - log_set_max_level(prio); - continue; - } - } - } - - return udev; -} - -/** - * udev_ref: - * @udev: udev library context - * - * Take a reference of the udev library context. - * - * Returns: the passed udev library context - **/ -_public_ struct udev *udev_ref(struct udev *udev) { - if (udev == NULL) - return NULL; - udev->refcount++; - return udev; -} - -/** - * udev_unref: - * @udev: udev library context - * - * Drop a reference of the udev library context. If the refcount - * reaches zero, the resources of the context will be released. - * - * Returns: the passed udev library context if it has still an active reference, or #NULL otherwise. - **/ -_public_ struct udev *udev_unref(struct udev *udev) { - if (udev == NULL) - return NULL; - udev->refcount--; - if (udev->refcount > 0) - return udev; - free(udev); - return NULL; -} - -/** - * udev_set_log_fn: - * @udev: udev library context - * @log_fn: function to be called for log messages - * - * This function is deprecated. - * - **/ -_public_ void udev_set_log_fn(struct udev *udev, - void (*log_fn)(struct udev *udev, - int priority, const char *file, int line, const char *fn, - const char *format, va_list args)) { - return; -} - -/** - * udev_get_log_priority: - * @udev: udev library context - * - * This function is deprecated. - * - **/ -_public_ int udev_get_log_priority(struct udev *udev) { - return log_get_max_level(); -} - -/** - * udev_set_log_priority: - * @udev: udev library context - * @priority: the new log priority - * - * This function is deprecated. - * - **/ -_public_ void udev_set_log_priority(struct udev *udev, int priority) { - log_set_max_level(priority); -} diff --git a/src/libudev/libudev.h b/src/libudev/libudev.h deleted file mode 100644 index eb58740d26..0000000000 --- a/src/libudev/libudev.h +++ /dev/null @@ -1,206 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2008-2012 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 . -***/ - -#ifndef _LIBUDEV_H_ -#define _LIBUDEV_H_ - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * udev - library context - * - * reads the udev config and system environment - * allows custom logging - */ -struct udev; -struct udev *udev_ref(struct udev *udev); -struct udev *udev_unref(struct udev *udev); -struct udev *udev_new(void); -void udev_set_log_fn(struct udev *udev, - void (*log_fn)(struct udev *udev, - int priority, const char *file, int line, const char *fn, - const char *format, va_list args)) __attribute__ ((deprecated)); -int udev_get_log_priority(struct udev *udev) __attribute__ ((deprecated)); -void udev_set_log_priority(struct udev *udev, int priority) __attribute__ ((deprecated)); -void *udev_get_userdata(struct udev *udev); -void udev_set_userdata(struct udev *udev, void *userdata); - -/* - * udev_list - * - * access to libudev generated lists - */ -struct udev_list_entry; -struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry); -struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name); -const char *udev_list_entry_get_name(struct udev_list_entry *list_entry); -const char *udev_list_entry_get_value(struct udev_list_entry *list_entry); -/** - * udev_list_entry_foreach: - * @list_entry: entry to store the current position - * @first_entry: first entry to start with - * - * Helper to iterate over all entries of a list. - */ -#define udev_list_entry_foreach(list_entry, first_entry) \ - for (list_entry = first_entry; \ - list_entry != NULL; \ - list_entry = udev_list_entry_get_next(list_entry)) - -/* - * udev_device - * - * access to sysfs/kernel devices - */ -struct udev_device; -struct udev_device *udev_device_ref(struct udev_device *udev_device); -struct udev_device *udev_device_unref(struct udev_device *udev_device); -struct udev *udev_device_get_udev(struct udev_device *udev_device); -struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath); -struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum); -struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname); -struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id); -struct udev_device *udev_device_new_from_environment(struct udev *udev); -/* udev_device_get_parent_*() does not take a reference on the returned device, it is automatically unref'd with the parent */ -struct udev_device *udev_device_get_parent(struct udev_device *udev_device); -struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, - const char *subsystem, const char *devtype); -/* retrieve device properties */ -const char *udev_device_get_devpath(struct udev_device *udev_device); -const char *udev_device_get_subsystem(struct udev_device *udev_device); -const char *udev_device_get_devtype(struct udev_device *udev_device); -const char *udev_device_get_syspath(struct udev_device *udev_device); -const char *udev_device_get_sysname(struct udev_device *udev_device); -const char *udev_device_get_sysnum(struct udev_device *udev_device); -const char *udev_device_get_devnode(struct udev_device *udev_device); -int udev_device_get_is_initialized(struct udev_device *udev_device); -struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device); -struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device); -struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device); -struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device); -const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key); -const char *udev_device_get_driver(struct udev_device *udev_device); -dev_t udev_device_get_devnum(struct udev_device *udev_device); -const char *udev_device_get_action(struct udev_device *udev_device); -unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device); -unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device); -const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr); -int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, char *value); -int udev_device_has_tag(struct udev_device *udev_device, const char *tag); - -/* - * udev_monitor - * - * access to kernel uevents and udev events - */ -struct udev_monitor; -struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor); -struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor); -struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor); -/* kernel and udev generated events over netlink */ -struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name); -/* bind socket */ -int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor); -int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size); -int udev_monitor_get_fd(struct udev_monitor *udev_monitor); -struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor); -/* in-kernel socket filters to select messages that get delivered to a listener */ -int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, - const char *subsystem, const char *devtype); -int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag); -int udev_monitor_filter_update(struct udev_monitor *udev_monitor); -int udev_monitor_filter_remove(struct udev_monitor *udev_monitor); - -/* - * udev_enumerate - * - * search sysfs for specific devices and provide a sorted list - */ -struct udev_enumerate; -struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate); -struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate); -struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate); -struct udev_enumerate *udev_enumerate_new(struct udev *udev); -/* device properties filter */ -int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem); -int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem); -int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); -int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value); -int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value); -int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname); -int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag); -int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent); -int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate); -int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath); -/* run enumeration with active filters */ -int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate); -int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate); -/* return device list */ -struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate); - -/* - * udev_queue - * - * access to the currently running udev events - */ -struct udev_queue; -struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue); -struct udev_queue *udev_queue_unref(struct udev_queue *udev_queue); -struct udev *udev_queue_get_udev(struct udev_queue *udev_queue); -struct udev_queue *udev_queue_new(struct udev *udev); -unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) __attribute__ ((deprecated)); -unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) __attribute__ ((deprecated)); -int udev_queue_get_udev_is_active(struct udev_queue *udev_queue); -int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue); -int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) __attribute__ ((deprecated)); -int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, - unsigned long long int start, unsigned long long int end) __attribute__ ((deprecated)); -int udev_queue_get_fd(struct udev_queue *udev_queue); -int udev_queue_flush(struct udev_queue *udev_queue); -struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) __attribute__ ((deprecated)); - -/* - * udev_hwdb - * - * access to the static hardware properties database - */ -struct udev_hwdb; -struct udev_hwdb *udev_hwdb_new(struct udev *udev); -struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb); -struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb); -struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags); - -/* - * udev_util - * - * udev specific utilities - */ -int udev_util_encode_string(const char *str, char *str_enc, size_t len); - - -#ifdef __cplusplus -} /* extern "C" */ -#endif - -#endif diff --git a/src/libudev/libudev.pc.in b/src/libudev/libudev.pc.in deleted file mode 100644 index a0f3f524e0..0000000000 --- a/src/libudev/libudev.pc.in +++ /dev/null @@ -1,17 +0,0 @@ -# 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. - -prefix=@prefix@ -exec_prefix=@exec_prefix@ -libdir=@libdir@ -includedir=@includedir@ - -Name: libudev -Description: Library to access udev device information -Version: @VERSION@ -Libs: -L${libdir} -ludev -Cflags: -I${includedir} diff --git a/src/libudev/libudev.sym b/src/libudev/libudev.sym deleted file mode 100644 index 76726fca77..0000000000 --- a/src/libudev/libudev.sym +++ /dev/null @@ -1,120 +0,0 @@ -/*** - 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. -***/ - -LIBUDEV_183 { -global: - udev_device_get_action; - udev_device_get_devlinks_list_entry; - udev_device_get_devnode; - udev_device_get_devnum; - udev_device_get_devpath; - udev_device_get_devtype; - udev_device_get_driver; - udev_device_get_is_initialized; - udev_device_get_parent; - udev_device_get_parent_with_subsystem_devtype; - udev_device_get_properties_list_entry; - udev_device_get_property_value; - udev_device_get_seqnum; - udev_device_get_subsystem; - udev_device_get_sysattr_list_entry; - udev_device_get_sysattr_value; - udev_device_get_sysname; - udev_device_get_sysnum; - udev_device_get_syspath; - udev_device_get_tags_list_entry; - udev_device_get_udev; - udev_device_get_usec_since_initialized; - udev_device_has_tag; - udev_device_new_from_devnum; - udev_device_new_from_environment; - udev_device_new_from_subsystem_sysname; - udev_device_new_from_syspath; - udev_device_ref; - udev_device_unref; - udev_enumerate_add_match_is_initialized; - udev_enumerate_add_match_parent; - udev_enumerate_add_match_property; - udev_enumerate_add_match_subsystem; - udev_enumerate_add_match_sysattr; - udev_enumerate_add_match_sysname; - udev_enumerate_add_match_tag; - udev_enumerate_add_nomatch_subsystem; - udev_enumerate_add_nomatch_sysattr; - udev_enumerate_add_syspath; - udev_enumerate_get_list_entry; - udev_enumerate_get_udev; - udev_enumerate_new; - udev_enumerate_ref; - udev_enumerate_scan_devices; - udev_enumerate_scan_subsystems; - udev_enumerate_unref; - udev_get_log_priority; - udev_get_userdata; - udev_list_entry_get_by_name; - udev_list_entry_get_name; - udev_list_entry_get_next; - udev_list_entry_get_value; - udev_monitor_enable_receiving; - udev_monitor_filter_add_match_subsystem_devtype; - udev_monitor_filter_add_match_tag; - udev_monitor_filter_remove; - udev_monitor_filter_update; - udev_monitor_get_fd; - udev_monitor_get_udev; - udev_monitor_new_from_netlink; - udev_monitor_receive_device; - udev_monitor_ref; - udev_monitor_set_receive_buffer_size; - udev_monitor_unref; - udev_new; - udev_queue_get_kernel_seqnum; - udev_queue_get_queue_is_empty; - udev_queue_get_queued_list_entry; - udev_queue_get_seqnum_is_finished; - udev_queue_get_seqnum_sequence_is_finished; - udev_queue_get_udev; - udev_queue_get_udev_is_active; - udev_queue_get_udev_seqnum; - udev_queue_new; - udev_queue_ref; - udev_queue_unref; - udev_ref; - udev_set_log_fn; - udev_set_log_priority; - udev_set_userdata; - udev_unref; - udev_util_encode_string; -local: - *; -}; - -LIBUDEV_189 { -global: - udev_device_new_from_device_id; -} LIBUDEV_183; - -LIBUDEV_196 { -global: - udev_hwdb_new; - udev_hwdb_ref; - udev_hwdb_unref; - udev_hwdb_get_properties_list_entry; -} LIBUDEV_189; - -LIBUDEV_199 { -global: - udev_device_set_sysattr_value; -} LIBUDEV_196; - -LIBUDEV_215 { -global: - udev_queue_flush; - udev_queue_get_fd; -} LIBUDEV_199; diff --git a/src/libudev/src/.gitignore b/src/libudev/src/.gitignore new file mode 100644 index 0000000000..0c8a5d5231 --- /dev/null +++ b/src/libudev/src/.gitignore @@ -0,0 +1 @@ +/libudev.pc diff --git a/src/libudev/src/Makefile b/src/libudev/src/Makefile new file mode 100644 index 0000000000..42e6357e35 --- /dev/null +++ b/src/libudev/src/Makefile @@ -0,0 +1,81 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +LIBUDEV_CURRENT=7 +LIBUDEV_REVISION=4 +LIBUDEV_AGE=6 + +include_HEADERS += \ + src/libudev/libudev.h + +lib_LTLIBRARIES += \ + libudev.la + +libudev_la_SOURCES =\ + src/libudev/libudev.sym \ + src/libudev/libudev-private.h \ + src/libudev/libudev-device-internal.h \ + src/libudev/libudev.c \ + src/libudev/libudev-list.c \ + src/libudev/libudev-util.c \ + src/libudev/libudev-device.c \ + src/libudev/libudev-device-private.c \ + src/libudev/libudev-enumerate.c \ + src/libudev/libudev-monitor.c \ + src/libudev/libudev-queue.c \ + src/libudev/libudev-hwdb.c + +libudev_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -version-info $(LIBUDEV_CURRENT):$(LIBUDEV_REVISION):$(LIBUDEV_AGE) \ + -Wl,--version-script=$(top_srcdir)/src/libudev/libudev.sym + +libudev_la_LIBADD = \ + libsystemd-internal.la + +pkgconfiglib_DATA += \ + src/libudev/libudev.pc + +EXTRA_DIST += \ + src/libudev/libudev.pc.in + +# move lib from $(libdir) to $(rootlibdir) and update devel link, if needed +libudev-install-hook: + libname=libudev.so && $(move-to-rootlibdir) + +libudev-uninstall-hook: + rm -f $(DESTDIR)$(rootlibdir)/libudev.so* + +INSTALL_EXEC_HOOKS += libudev-install-hook +UNINSTALL_EXEC_HOOKS += libudev-uninstall-hook + +# ------------------------------------------------------------------------------ +noinst_LTLIBRARIES += \ + libudev-internal.la + +libudev_internal_la_SOURCES =\ + $(libudev_la_SOURCES) + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libudev/src/libudev-device-internal.h b/src/libudev/src/libudev-device-internal.h new file mode 100644 index 0000000000..de6f680819 --- /dev/null +++ b/src/libudev/src/libudev-device-internal.h @@ -0,0 +1,58 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2015 Tom Gundersen + + 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 . +***/ + +#pragma once + +#include "libudev.h" +#include + +#include "libudev-private.h" + +/** + * udev_device: + * + * Opaque object representing one kernel sys device. + */ +struct udev_device { + struct udev *udev; + + /* real device object */ + sd_device *device; + + /* legacy */ + int refcount; + + struct udev_device *parent; + bool parent_set; + + struct udev_list properties; + uint64_t properties_generation; + struct udev_list tags; + uint64_t tags_generation; + struct udev_list devlinks; + uint64_t devlinks_generation; + bool properties_read:1; + bool tags_read:1; + bool devlinks_read:1; + struct udev_list sysattrs; + bool sysattrs_read; +}; + +struct udev_device *udev_device_new(struct udev *udev); diff --git a/src/libudev/src/libudev-device-private.c b/src/libudev/src/libudev-device-private.c new file mode 100644 index 0000000000..2aae0726c1 --- /dev/null +++ b/src/libudev/src/libudev-device-private.c @@ -0,0 +1,411 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2015 Tom Gundersen + + 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 . +***/ + +#include "libudev.h" + +#include "device-private.h" +#include "libudev-device-internal.h" +#include "libudev-private.h" + +int udev_device_tag_index(struct udev_device *udev_device, struct udev_device *udev_device_old, bool add) { + sd_device *device_old = NULL; + int r; + + assert(udev_device); + + if (udev_device_old) + device_old = udev_device_old->device; + + r = device_tag_index(udev_device->device, device_old, add); + if (r < 0) + return r; + + return 0; +} + +int udev_device_update_db(struct udev_device *udev_device) { + int r; + + assert(udev_device); + + r = device_update_db(udev_device->device); + if (r < 0) + return r; + + return 0; +} + +int udev_device_delete_db(struct udev_device *udev_device) { + int r; + + assert(udev_device); + + r = device_delete_db(udev_device->device); + if (r < 0) + return r; + + return 0; +} + +int udev_device_get_ifindex(struct udev_device *udev_device) { + int r, ifindex; + + assert(udev_device); + + r = sd_device_get_ifindex(udev_device->device, &ifindex); + if (r < 0) + return r; + + return ifindex; +} + +const char *udev_device_get_devpath_old(struct udev_device *udev_device) { + const char *devpath_old = NULL; + int r; + + assert(udev_device); + + r = sd_device_get_property_value(udev_device->device, "DEVPATH_OLD", &devpath_old); + if (r < 0 && r != -ENOENT) { + errno = -r; + return NULL; + } + + return devpath_old; +} + +mode_t udev_device_get_devnode_mode(struct udev_device *udev_device) { + mode_t mode; + int r; + + assert(udev_device); + + r = device_get_devnode_mode(udev_device->device, &mode); + if (r < 0) { + errno = -r; + return 0; + } + + return mode; +} + +uid_t udev_device_get_devnode_uid(struct udev_device *udev_device) { + uid_t uid; + int r; + + assert(udev_device); + + r = device_get_devnode_uid(udev_device->device, &uid); + if (r < 0) { + errno = -r; + return 0; + } + + return uid; +} + +gid_t udev_device_get_devnode_gid(struct udev_device *udev_device) { + gid_t gid; + int r; + + assert(udev_device); + + r = device_get_devnode_gid(udev_device->device, &gid); + if (r < 0) { + errno = -r; + return 0; + } + + return gid; +} + +void udev_device_ensure_usec_initialized(struct udev_device *udev_device, struct udev_device *udev_device_old) { + assert(udev_device); + + device_ensure_usec_initialized(udev_device->device, + udev_device_old ? udev_device_old->device : NULL); +} + +char **udev_device_get_properties_envp(struct udev_device *udev_device) { + char **envp; + int r; + + assert(udev_device); + + r = device_get_properties_strv(udev_device->device, &envp); + if (r < 0) { + errno = -r; + return NULL; + } + + return envp; +} + +ssize_t udev_device_get_properties_monitor_buf(struct udev_device *udev_device, const char **buf) { + const char *nulstr; + size_t len; + int r; + + assert(udev_device); + assert(buf); + + r = device_get_properties_nulstr(udev_device->device, (const uint8_t **)&nulstr, &len); + if (r < 0) + return r; + + *buf = nulstr; + + return len; +} + +int udev_device_get_devlink_priority(struct udev_device *udev_device) { + int priority, r; + + assert(udev_device); + + r = device_get_devlink_priority(udev_device->device, &priority); + if (r < 0) + return r; + + return priority; +} + +int udev_device_get_watch_handle(struct udev_device *udev_device) { + int handle, r; + + assert(udev_device); + + r = device_get_watch_handle(udev_device->device, &handle); + if (r < 0) + return r; + + return handle; +} + +void udev_device_set_is_initialized(struct udev_device *udev_device) { + assert(udev_device); + + device_set_is_initialized(udev_device->device); +} + +int udev_device_rename(struct udev_device *udev_device, const char *name) { + int r; + + assert(udev_device); + + r = device_rename(udev_device->device, name); + if (r < 0) + return r; + + return 0; +} + +struct udev_device *udev_device_shallow_clone(struct udev_device *old_device) { + struct udev_device *device; + int r; + + assert(old_device); + + device = udev_device_new(old_device->udev); + if (!device) + return NULL; + + r = device_shallow_clone(old_device->device, &device->device); + if (r < 0) { + udev_device_unref(device); + errno = -r; + return NULL; + } + + return device; +} + +struct udev_device *udev_device_clone_with_db(struct udev_device *udev_device_old) { + struct udev_device *udev_device; + int r; + + assert(udev_device_old); + + udev_device = udev_device_new(udev_device_old->udev); + if (!udev_device) + return NULL; + + r = device_clone_with_db(udev_device_old->device, &udev_device->device); + if (r < 0) { + udev_device_unref(udev_device); + errno = -r; + return NULL; + } + + return udev_device; +} + +struct udev_device *udev_device_new_from_nulstr(struct udev *udev, char *nulstr, ssize_t buflen) { + struct udev_device *device; + int r; + + device = udev_device_new(udev); + if (!device) + return NULL; + + r = device_new_from_nulstr(&device->device, (uint8_t*)nulstr, buflen); + if (r < 0) { + udev_device_unref(device); + errno = -r; + return NULL; + } + + return device; +} + +struct udev_device *udev_device_new_from_synthetic_event(struct udev *udev, const char *syspath, const char *action) { + struct udev_device *device; + int r; + + device = udev_device_new(udev); + if (!device) + return NULL; + + r = device_new_from_synthetic_event(&device->device, syspath, action); + if (r < 0) { + udev_device_unref(device); + errno = -r; + return NULL; + } + + return device; +} + +int udev_device_copy_properties(struct udev_device *udev_device_dst, struct udev_device *udev_device_src) { + int r; + + assert(udev_device_dst); + assert(udev_device_src); + + r = device_copy_properties(udev_device_dst->device, udev_device_src->device); + if (r < 0) + return r; + + return 0; +} + +const char *udev_device_get_id_filename(struct udev_device *udev_device) { + const char *filename; + int r; + + assert(udev_device); + + r = device_get_id_filename(udev_device->device, &filename); + if (r < 0) { + errno = -r; + return NULL; + } + + return filename; +} + +int udev_device_set_watch_handle(struct udev_device *udev_device, int handle) { + + assert(udev_device); + + device_set_watch_handle(udev_device->device, handle); + + return 0; +} + +void udev_device_set_db_persist(struct udev_device *udev_device) { + assert(udev_device); + + device_set_db_persist(udev_device->device); +} + +int udev_device_set_devlink_priority(struct udev_device *udev_device, int priority) { + assert(udev_device); + + device_set_devlink_priority(udev_device->device, priority); + + return 0; +} + +int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink) { + int r; + + assert(udev_device); + + r = device_add_devlink(udev_device->device, devlink); + if (r < 0) + return r; + + return 0; +} + +int udev_device_add_property(struct udev_device *udev_device, const char *property, const char *value) { + int r; + + assert(udev_device); + + r = device_add_property(udev_device->device, property, value); + if (r < 0) + return r; + + return 0; +} + +int udev_device_add_tag(struct udev_device *udev_device, const char *tag) { + int r; + + assert(udev_device); + + r = device_add_tag(udev_device->device, tag); + if (r < 0) + return r; + + return 0; +} + +void udev_device_remove_tag(struct udev_device *udev_device, const char *tag) { + assert(udev_device); + + device_remove_tag(udev_device->device, tag); +} + +void udev_device_cleanup_tags_list(struct udev_device *udev_device) { + assert(udev_device); + + device_cleanup_tags(udev_device->device); +} + +void udev_device_cleanup_devlinks_list(struct udev_device *udev_device) { + assert(udev_device); + + device_cleanup_devlinks(udev_device->device); +} + +void udev_device_set_info_loaded(struct udev_device *udev_device) { + assert(udev_device); + + device_seal(udev_device->device); +} + +void udev_device_read_db(struct udev_device *udev_device) { + assert(udev_device); + + device_read_db_force(udev_device->device); +} diff --git a/src/libudev/src/libudev-device.c b/src/libudev/src/libudev-device.c new file mode 100644 index 0000000000..6536f4cfbb --- /dev/null +++ b/src/libudev/src/libudev-device.c @@ -0,0 +1,958 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2015 Tom Gundersen + + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libudev.h" +#include + +#include "alloc-util.h" +#include "device-private.h" +#include "device-util.h" +#include "libudev-device-internal.h" +#include "libudev-private.h" +#include "parse-util.h" + +/** + * SECTION:libudev-device + * @short_description: kernel sys devices + * + * Representation of kernel sys devices. Devices are uniquely identified + * by their syspath, every device has exactly one path in the kernel sys + * filesystem. Devices usually belong to a kernel subsystem, and have + * a unique name inside that subsystem. + */ + +/** + * udev_device_get_seqnum: + * @udev_device: udev device + * + * This is only valid if the device was received through a monitor. Devices read from + * sys do not have a sequence number. + * + * Returns: the kernel event sequence number, or 0 if there is no sequence number available. + **/ +_public_ unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device) +{ + const char *seqnum; + unsigned long long ret; + int r; + + assert_return_errno(udev_device, 0, EINVAL); + + r = sd_device_get_property_value(udev_device->device, "SEQNUM", &seqnum); + if (r == -ENOENT) + return 0; + else if (r < 0) { + errno = -r; + return 0; + } + + r = safe_atollu(seqnum, &ret); + if (r < 0) { + errno = -r; + return 0; + } + + return ret; +} + +/** + * udev_device_get_devnum: + * @udev_device: udev device + * + * Get the device major/minor number. + * + * Returns: the dev_t number. + **/ +_public_ dev_t udev_device_get_devnum(struct udev_device *udev_device) +{ + dev_t devnum; + int r; + + assert_return_errno(udev_device, makedev(0, 0), EINVAL); + + r = sd_device_get_devnum(udev_device->device, &devnum); + if (r < 0) { + errno = -r; + return makedev(0, 0); + } + + return devnum; +} + +/** + * udev_device_get_driver: + * @udev_device: udev device + * + * Get the kernel driver name. + * + * Returns: the driver name string, or #NULL if there is no driver attached. + **/ +_public_ const char *udev_device_get_driver(struct udev_device *udev_device) +{ + const char *driver; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_driver(udev_device->device, &driver); + if (r < 0) { + errno = -r; + return NULL; + } + + return driver; +} + +/** + * udev_device_get_devtype: + * @udev_device: udev device + * + * Retrieve the devtype string of the udev device. + * + * Returns: the devtype name of the udev device, or #NULL if it can not be determined + **/ +_public_ const char *udev_device_get_devtype(struct udev_device *udev_device) +{ + const char *devtype; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_devtype(udev_device->device, &devtype); + if (r < 0) { + errno = -r; + return NULL; + } + + return devtype; +} + +/** + * udev_device_get_subsystem: + * @udev_device: udev device + * + * Retrieve the subsystem string of the udev device. The string does not + * contain any "/". + * + * Returns: the subsystem name of the udev device, or #NULL if it can not be determined + **/ +_public_ const char *udev_device_get_subsystem(struct udev_device *udev_device) +{ + const char *subsystem; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_subsystem(udev_device->device, &subsystem); + if (r < 0) { + errno = -r; + return NULL; + } else if (!subsystem) + errno = ENODATA; + + return subsystem; +} + +/** + * udev_device_get_property_value: + * @udev_device: udev device + * @key: property name + * + * Get the value of a given property. + * + * Returns: the property string, or #NULL if there is no such property. + **/ +_public_ const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key) +{ + const char *value = NULL; + int r; + + assert_return_errno(udev_device && key, NULL, EINVAL); + + r = sd_device_get_property_value(udev_device->device, key, &value); + if (r < 0) { + errno = -r; + return NULL; + } + + return value; +} + +struct udev_device *udev_device_new(struct udev *udev) { + struct udev_device *udev_device; + + assert_return_errno(udev, NULL, EINVAL); + + udev_device = new0(struct udev_device, 1); + if (!udev_device) { + errno = ENOMEM; + return NULL; + } + udev_device->refcount = 1; + udev_device->udev = udev; + udev_list_init(udev, &udev_device->properties, true); + udev_list_init(udev, &udev_device->tags, true); + udev_list_init(udev, &udev_device->sysattrs, true); + udev_list_init(udev, &udev_device->devlinks, true); + + return udev_device; +} + +/** + * udev_device_new_from_syspath: + * @udev: udev library context + * @syspath: sys device path including sys directory + * + * Create new udev device, and fill in information from the sys + * device and the udev database entry. The syspath is the absolute + * path to the device, including the sys mount point. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath) { + struct udev_device *udev_device; + int r; + + udev_device = udev_device_new(udev); + if (!udev_device) + return NULL; + + r = sd_device_new_from_syspath(&udev_device->device, syspath); + if (r < 0) { + errno = -r; + udev_device_unref(udev_device); + return NULL; + } + + return udev_device; +} + +/** + * udev_device_new_from_devnum: + * @udev: udev library context + * @type: char or block device + * @devnum: device major/minor number + * + * Create new udev device, and fill in information from the sys + * device and the udev database entry. The device is looked-up + * by its major/minor number and type. Character and block device + * numbers are not unique across the two types. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum) +{ + struct udev_device *udev_device; + int r; + + udev_device = udev_device_new(udev); + if (!udev_device) + return NULL; + + r = sd_device_new_from_devnum(&udev_device->device, type, devnum); + if (r < 0) { + errno = -r; + udev_device_unref(udev_device); + return NULL; + } + + return udev_device; +} + +/** + * udev_device_new_from_device_id: + * @udev: udev library context + * @id: text string identifying a kernel device + * + * Create new udev device, and fill in information from the sys + * device and the udev database entry. The device is looked-up + * by a special string: + * b8:2 - block device major:minor + * c128:1 - char device major:minor + * n3 - network device ifindex + * +sound:card29 - kernel driver core subsystem:device name + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_device_id(struct udev *udev, const char *id) +{ + struct udev_device *udev_device; + int r; + + udev_device = udev_device_new(udev); + if (!udev_device) + return NULL; + + r = sd_device_new_from_device_id(&udev_device->device, id); + if (r < 0) { + errno = -r; + udev_device_unref(udev_device); + return NULL; + } + + return udev_device; +} + +/** + * udev_device_new_from_subsystem_sysname: + * @udev: udev library context + * @subsystem: the subsystem of the device + * @sysname: the name of the device + * + * Create new udev device, and fill in information from the sys device + * and the udev database entry. The device is looked up by the subsystem + * and name string of the device, like "mem" / "zero", or "block" / "sda". + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname) +{ + struct udev_device *udev_device; + int r; + + udev_device = udev_device_new(udev); + if (!udev_device) + return NULL; + + r = sd_device_new_from_subsystem_sysname(&udev_device->device, subsystem, sysname); + if (r < 0) { + errno = -r; + udev_device_unref(udev_device); + return NULL; + } + + return udev_device; +} + +/** + * udev_device_new_from_environment + * @udev: udev library context + * + * Create new udev device, and fill in information from the + * current process environment. This only works reliable if + * the process is called from a udev rule. It is usually used + * for tools executed from IMPORT= rules. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, if it does not exist + **/ +_public_ struct udev_device *udev_device_new_from_environment(struct udev *udev) +{ + struct udev_device *udev_device; + int r; + + udev_device = udev_device_new(udev); + if (!udev_device) + return NULL; + + r = device_new_from_strv(&udev_device->device, environ); + if (r < 0) { + errno = -r; + udev_device_unref(udev_device); + return NULL; + } + + return udev_device; +} + +static struct udev_device *device_new_from_parent(struct udev_device *child) +{ + struct udev_device *parent; + int r; + + assert_return_errno(child, NULL, EINVAL); + + parent = udev_device_new(child->udev); + if (!parent) + return NULL; + + r = sd_device_get_parent(child->device, &parent->device); + if (r < 0) { + errno = -r; + udev_device_unref(parent); + return NULL; + } + + /* the parent is unref'ed with the child, so take a ref from libudev as well */ + sd_device_ref(parent->device); + + return parent; +} + +/** + * udev_device_get_parent: + * @udev_device: the device to start searching from + * + * Find the next parent device, and fill in information from the sys + * device and the udev database entry. + * + * Returned device is not referenced. It is attached to the child + * device, and will be cleaned up when the child device is cleaned up. + * + * It is not necessarily just the upper level directory, empty or not + * recognized sys directories are ignored. + * + * It can be called as many times as needed, without caring about + * references. + * + * Returns: a new udev device, or #NULL, if it no parent exist. + **/ +_public_ struct udev_device *udev_device_get_parent(struct udev_device *udev_device) +{ + assert_return_errno(udev_device, NULL, EINVAL); + + if (!udev_device->parent_set) { + udev_device->parent_set = true; + udev_device->parent = device_new_from_parent(udev_device); + } + + /* TODO: errno will differ here in case parent == NULL */ + return udev_device->parent; +} + +/** + * udev_device_get_parent_with_subsystem_devtype: + * @udev_device: udev device to start searching from + * @subsystem: the subsystem of the device + * @devtype: the type (DEVTYPE) of the device + * + * Find the next parent device, with a matching subsystem and devtype + * value, and fill in information from the sys device and the udev + * database entry. + * + * If devtype is #NULL, only subsystem is checked, and any devtype will + * match. + * + * Returned device is not referenced. It is attached to the child + * device, and will be cleaned up when the child device is cleaned up. + * + * It can be called as many times as needed, without caring about + * references. + * + * Returns: a new udev device, or #NULL if no matching parent exists. + **/ +_public_ struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device, const char *subsystem, const char *devtype) +{ + sd_device *parent; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + /* this relies on the fact that finding the subdevice of a parent or the + parent of a subdevice commute */ + + /* first find the correct sd_device */ + r = sd_device_get_parent_with_subsystem_devtype(udev_device->device, subsystem, devtype, &parent); + if (r < 0) { + errno = -r; + return NULL; + } + + /* then walk the chain of udev_device parents until the correspanding + one is found */ + while ((udev_device = udev_device_get_parent(udev_device))) { + if (udev_device->device == parent) + return udev_device; + } + + errno = ENOENT; + return NULL; +} + +/** + * udev_device_get_udev: + * @udev_device: udev device + * + * Retrieve the udev library context the device was created with. + * + * Returns: the udev library context + **/ +_public_ struct udev *udev_device_get_udev(struct udev_device *udev_device) +{ + assert_return_errno(udev_device, NULL, EINVAL); + + return udev_device->udev; +} + +/** + * udev_device_ref: + * @udev_device: udev device + * + * Take a reference of a udev device. + * + * Returns: the passed udev device + **/ +_public_ struct udev_device *udev_device_ref(struct udev_device *udev_device) +{ + if (udev_device) + udev_device->refcount++; + + return udev_device; +} + +/** + * udev_device_unref: + * @udev_device: udev device + * + * Drop a reference of a udev device. If the refcount reaches zero, + * the resources of the device will be released. + * + * Returns: #NULL + **/ +_public_ struct udev_device *udev_device_unref(struct udev_device *udev_device) +{ + if (udev_device && (-- udev_device->refcount) == 0) { + sd_device_unref(udev_device->device); + udev_device_unref(udev_device->parent); + + udev_list_cleanup(&udev_device->properties); + udev_list_cleanup(&udev_device->sysattrs); + udev_list_cleanup(&udev_device->tags); + udev_list_cleanup(&udev_device->devlinks); + + free(udev_device); + } + + return NULL; +} + +/** + * udev_device_get_devpath: + * @udev_device: udev device + * + * Retrieve the kernel devpath value of the udev device. The path + * does not contain the sys mount point, and starts with a '/'. + * + * Returns: the devpath of the udev device + **/ +_public_ const char *udev_device_get_devpath(struct udev_device *udev_device) +{ + const char *devpath; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_devpath(udev_device->device, &devpath); + if (r < 0) { + errno = -r; + return NULL; + } + + return devpath; +} + +/** + * udev_device_get_syspath: + * @udev_device: udev device + * + * Retrieve the sys path of the udev device. The path is an + * absolute path and starts with the sys mount point. + * + * Returns: the sys path of the udev device + **/ +_public_ const char *udev_device_get_syspath(struct udev_device *udev_device) +{ + const char *syspath; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_syspath(udev_device->device, &syspath); + if (r < 0) { + errno = -r; + return NULL; + } + + return syspath; +} + +/** + * udev_device_get_sysname: + * @udev_device: udev device + * + * Get the kernel device name in /sys. + * + * Returns: the name string of the device device + **/ +_public_ const char *udev_device_get_sysname(struct udev_device *udev_device) +{ + const char *sysname; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_sysname(udev_device->device, &sysname); + if (r < 0) { + errno = -r; + return NULL; + } + + return sysname; +} + +/** + * udev_device_get_sysnum: + * @udev_device: udev device + * + * Get the instance number of the device. + * + * Returns: the trailing number string of the device name + **/ +_public_ const char *udev_device_get_sysnum(struct udev_device *udev_device) +{ + const char *sysnum; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_sysnum(udev_device->device, &sysnum); + if (r < 0) { + errno = -r; + return NULL; + } + + return sysnum; +} + +/** + * udev_device_get_devnode: + * @udev_device: udev device + * + * Retrieve the device node file name belonging to the udev device. + * The path is an absolute path, and starts with the device directory. + * + * Returns: the device node file name of the udev device, or #NULL if no device node exists + **/ +_public_ const char *udev_device_get_devnode(struct udev_device *udev_device) +{ + const char *devnode; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_devname(udev_device->device, &devnode); + if (r < 0) { + errno = -r; + return NULL; + } + + return devnode; +} + +/** + * udev_device_get_devlinks_list_entry: + * @udev_device: udev device + * + * Retrieve the list of device links pointing to the device file of + * the udev device. The next list entry can be retrieved with + * udev_list_entry_get_next(), which returns #NULL if no more entries exist. + * The devlink path can be retrieved from the list entry by + * udev_list_entry_get_name(). The path is an absolute path, and starts with + * the device directory. + * + * Returns: the first entry of the device node link list + **/ +_public_ struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device) +{ + assert_return_errno(udev_device, NULL, EINVAL); + + if (device_get_devlinks_generation(udev_device->device) != udev_device->devlinks_generation || + !udev_device->devlinks_read) { + const char *devlink; + + udev_list_cleanup(&udev_device->devlinks); + + FOREACH_DEVICE_DEVLINK(udev_device->device, devlink) + udev_list_entry_add(&udev_device->devlinks, devlink, NULL); + + udev_device->devlinks_read = true; + udev_device->devlinks_generation = device_get_devlinks_generation(udev_device->device); + } + + return udev_list_get_entry(&udev_device->devlinks); +} + +/** + * udev_device_get_event_properties_entry: + * @udev_device: udev device + * + * Retrieve the list of key/value device properties of the udev + * device. The next list entry can be retrieved with udev_list_entry_get_next(), + * which returns #NULL if no more entries exist. The property name + * can be retrieved from the list entry by udev_list_entry_get_name(), + * the property value by udev_list_entry_get_value(). + * + * Returns: the first entry of the property list + **/ +_public_ struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device) +{ + assert_return_errno(udev_device, NULL, EINVAL); + + if (device_get_properties_generation(udev_device->device) != udev_device->properties_generation || + !udev_device->properties_read) { + const char *key, *value; + + udev_list_cleanup(&udev_device->properties); + + FOREACH_DEVICE_PROPERTY(udev_device->device, key, value) + udev_list_entry_add(&udev_device->properties, key, value); + + udev_device->properties_read = true; + udev_device->properties_generation = device_get_properties_generation(udev_device->device); + } + + return udev_list_get_entry(&udev_device->properties); +} + +/** + * udev_device_get_action: + * @udev_device: udev device + * + * This is only valid if the device was received through a monitor. Devices read from + * sys do not have an action string. Usual actions are: add, remove, change, online, + * offline. + * + * Returns: the kernel action value, or #NULL if there is no action value available. + **/ +_public_ const char *udev_device_get_action(struct udev_device *udev_device) { + const char *action = NULL; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_property_value(udev_device->device, "ACTION", &action); + if (r < 0 && r != -ENOENT) { + errno = -r; + return NULL; + } + + return action; +} + +/** + * udev_device_get_usec_since_initialized: + * @udev_device: udev device + * + * Return the number of microseconds passed since udev set up the + * device for the first time. + * + * This is only implemented for devices with need to store properties + * in the udev database. All other devices return 0 here. + * + * Returns: the number of microseconds since the device was first seen. + **/ +_public_ unsigned long long int udev_device_get_usec_since_initialized(struct udev_device *udev_device) +{ + usec_t ts; + int r; + + assert_return(udev_device, -EINVAL); + + r = sd_device_get_usec_since_initialized(udev_device->device, &ts); + if (r < 0) { + errno = EINVAL; + return 0; + } + + return ts; +} + +/** + * udev_device_get_sysattr_value: + * @udev_device: udev device + * @sysattr: attribute name + * + * The retrieved value is cached in the device. Repeated calls will return the same + * value and not open the attribute again. + * + * Returns: the content of a sys attribute file, or #NULL if there is no sys attribute value. + **/ +_public_ const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr) +{ + const char *value; + int r; + + assert_return_errno(udev_device, NULL, EINVAL); + + r = sd_device_get_sysattr_value(udev_device->device, sysattr, &value); + if (r < 0) { + errno = -r; + return NULL; + } + + return value; +} + +/** + * udev_device_set_sysattr_value: + * @udev_device: udev device + * @sysattr: attribute name + * @value: new value to be set + * + * Update the contents of the sys attribute and the cached value of the device. + * + * Returns: Negative error code on failure or 0 on success. + **/ +_public_ int udev_device_set_sysattr_value(struct udev_device *udev_device, const char *sysattr, char *value) +{ + int r; + + assert_return(udev_device, -EINVAL); + + r = sd_device_set_sysattr_value(udev_device->device, sysattr, value); + if (r < 0) + return r; + + return 0; +} + +/** + * udev_device_get_sysattr_list_entry: + * @udev_device: udev device + * + * Retrieve the list of available sysattrs, with value being empty; + * This just return all available sysfs attributes for a particular + * device without reading their values. + * + * Returns: the first entry of the property list + **/ +_public_ struct udev_list_entry *udev_device_get_sysattr_list_entry(struct udev_device *udev_device) +{ + assert_return_errno(udev_device, NULL, EINVAL); + + if (!udev_device->sysattrs_read) { + const char *sysattr; + + udev_list_cleanup(&udev_device->sysattrs); + + FOREACH_DEVICE_SYSATTR(udev_device->device, sysattr) + udev_list_entry_add(&udev_device->sysattrs, sysattr, NULL); + + udev_device->sysattrs_read = true; + } + + return udev_list_get_entry(&udev_device->sysattrs); +} + +/** + * udev_device_get_is_initialized: + * @udev_device: udev device + * + * Check if udev has already handled the device and has set up + * device node permissions and context, or has renamed a network + * device. + * + * This is only implemented for devices with a device node + * or network interfaces. All other devices return 1 here. + * + * Returns: 1 if the device is set up. 0 otherwise. + **/ +_public_ int udev_device_get_is_initialized(struct udev_device *udev_device) +{ + int r, initialized; + + assert_return(udev_device, -EINVAL); + + r = sd_device_get_is_initialized(udev_device->device, &initialized); + if (r < 0) { + errno = -r; + + return 0; + } + + return initialized; +} + +/** + * udev_device_get_tags_list_entry: + * @udev_device: udev device + * + * Retrieve the list of tags attached to the udev device. The next + * list entry can be retrieved with udev_list_entry_get_next(), + * which returns #NULL if no more entries exist. The tag string + * can be retrieved from the list entry by udev_list_entry_get_name(). + * + * Returns: the first entry of the tag list + **/ +_public_ struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device) +{ + assert_return_errno(udev_device, NULL, EINVAL); + + if (device_get_tags_generation(udev_device->device) != udev_device->tags_generation || + !udev_device->tags_read) { + const char *tag; + + udev_list_cleanup(&udev_device->tags); + + FOREACH_DEVICE_TAG(udev_device->device, tag) + udev_list_entry_add(&udev_device->tags, tag, NULL); + + udev_device->tags_read = true; + udev_device->tags_generation = device_get_tags_generation(udev_device->device); + } + + return udev_list_get_entry(&udev_device->tags); +} + +/** + * udev_device_has_tag: + * @udev_device: udev device + * @tag: tag name + * + * Check if a given device has a certain tag associated. + * + * Returns: 1 if the tag is found. 0 otherwise. + **/ +_public_ int udev_device_has_tag(struct udev_device *udev_device, const char *tag) +{ + assert_return(udev_device, 0); + + return sd_device_has_tag(udev_device->device, tag); +} diff --git a/src/libudev/src/libudev-enumerate.c b/src/libudev/src/libudev-enumerate.c new file mode 100644 index 0000000000..c784da5715 --- /dev/null +++ b/src/libudev/src/libudev-enumerate.c @@ -0,0 +1,419 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2015 Tom Gundersen + + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libudev.h" +#include + +#include "alloc-util.h" +#include "device-enumerator-private.h" +#include "device-util.h" +#include "libudev-device-internal.h" + +/** + * SECTION:libudev-enumerate + * @short_description: lookup and sort sys devices + * + * Lookup devices in the sys filesystem, filter devices by properties, + * and return a sorted list of devices. + */ + +/** + * udev_enumerate: + * + * Opaque object representing one device lookup/sort context. + */ +struct udev_enumerate { + struct udev *udev; + int refcount; + struct udev_list devices_list; + bool devices_uptodate:1; + + sd_device_enumerator *enumerator; +}; + +/** + * udev_enumerate_new: + * @udev: udev library context + * + * Create an enumeration context to scan /sys. + * + * Returns: an enumeration context. + **/ +_public_ struct udev_enumerate *udev_enumerate_new(struct udev *udev) { + _cleanup_free_ struct udev_enumerate *udev_enumerate = NULL; + struct udev_enumerate *ret; + int r; + + assert_return_errno(udev, NULL, EINVAL); + + udev_enumerate = new0(struct udev_enumerate, 1); + if (!udev_enumerate) { + errno = ENOMEM; + return NULL; + } + + r = sd_device_enumerator_new(&udev_enumerate->enumerator); + if (r < 0) { + errno = -r; + return NULL; + } + + r = sd_device_enumerator_allow_uninitialized(udev_enumerate->enumerator); + if (r < 0) { + errno = -r; + return NULL; + } + + udev_enumerate->refcount = 1; + udev_enumerate->udev = udev; + + udev_list_init(udev, &udev_enumerate->devices_list, false); + + ret = udev_enumerate; + udev_enumerate = NULL; + + return ret; +} + +/** + * udev_enumerate_ref: + * @udev_enumerate: context + * + * Take a reference of a enumeration context. + * + * Returns: the passed enumeration context + **/ +_public_ struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate) { + if (udev_enumerate) + udev_enumerate->refcount ++; + + return udev_enumerate; +} + +/** + * udev_enumerate_unref: + * @udev_enumerate: context + * + * Drop a reference of an enumeration context. If the refcount reaches zero, + * all resources of the enumeration context will be released. + * + * Returns: #NULL + **/ +_public_ struct udev_enumerate *udev_enumerate_unref(struct udev_enumerate *udev_enumerate) { + if (udev_enumerate && (-- udev_enumerate->refcount) == 0) { + udev_list_cleanup(&udev_enumerate->devices_list); + sd_device_enumerator_unref(udev_enumerate->enumerator); + free(udev_enumerate); + } + + return NULL; +} + +/** + * udev_enumerate_get_udev: + * @udev_enumerate: context + * + * Get the udev library context. + * + * Returns: a pointer to the context. + */ +_public_ struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate) { + assert_return_errno(udev_enumerate, NULL, EINVAL); + + return udev_enumerate->udev; +} + +/** + * udev_enumerate_get_list_entry: + * @udev_enumerate: context + * + * Get the first entry of the sorted list of device paths. + * + * Returns: a udev_list_entry. + */ +_public_ struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate) { + assert_return_errno(udev_enumerate, NULL, EINVAL); + + if (!udev_enumerate->devices_uptodate) { + sd_device *device; + + udev_list_cleanup(&udev_enumerate->devices_list); + + FOREACH_DEVICE_AND_SUBSYSTEM(udev_enumerate->enumerator, device) { + const char *syspath; + int r; + + r = sd_device_get_syspath(device, &syspath); + if (r < 0) { + errno = -r; + return NULL; + } + + udev_list_entry_add(&udev_enumerate->devices_list, syspath, NULL); + } + + udev_enumerate->devices_uptodate = true; + } + + return udev_list_get_entry(&udev_enumerate->devices_list); +} + +/** + * udev_enumerate_add_match_subsystem: + * @udev_enumerate: context + * @subsystem: filter for a subsystem of the device to include in the list + * + * Match only devices belonging to a certain kernel subsystem. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) { + assert_return(udev_enumerate, -EINVAL); + + if (!subsystem) + return 0; + + return sd_device_enumerator_add_match_subsystem(udev_enumerate->enumerator, subsystem, true); +} + +/** + * udev_enumerate_add_nomatch_subsystem: + * @udev_enumerate: context + * @subsystem: filter for a subsystem of the device to exclude from the list + * + * Match only devices not belonging to a certain kernel subsystem. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem) { + assert_return(udev_enumerate, -EINVAL); + + if (!subsystem) + return 0; + + return sd_device_enumerator_add_match_subsystem(udev_enumerate->enumerator, subsystem, false); +} + +/** + * udev_enumerate_add_match_sysattr: + * @udev_enumerate: context + * @sysattr: filter for a sys attribute at the device to include in the list + * @value: optional value of the sys attribute + * + * Match only devices with a certain /sys device attribute. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value) { + assert_return(udev_enumerate, -EINVAL); + + if (!sysattr) + return 0; + + return sd_device_enumerator_add_match_sysattr(udev_enumerate->enumerator, sysattr, value, true); +} + +/** + * udev_enumerate_add_nomatch_sysattr: + * @udev_enumerate: context + * @sysattr: filter for a sys attribute at the device to exclude from the list + * @value: optional value of the sys attribute + * + * Match only devices not having a certain /sys device attribute. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value) { + assert_return(udev_enumerate, -EINVAL); + + if (!sysattr) + return 0; + + return sd_device_enumerator_add_match_sysattr(udev_enumerate->enumerator, sysattr, value, false); +} + +/** + * udev_enumerate_add_match_property: + * @udev_enumerate: context + * @property: filter for a property of the device to include in the list + * @value: value of the property + * + * Match only devices with a certain property. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value) { + assert_return(udev_enumerate, -EINVAL); + + if (!property) + return 0; + + return sd_device_enumerator_add_match_property(udev_enumerate->enumerator, property, value); +} + +/** + * udev_enumerate_add_match_tag: + * @udev_enumerate: context + * @tag: filter for a tag of the device to include in the list + * + * Match only devices with a certain tag. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag) { + assert_return(udev_enumerate, -EINVAL); + + if (!tag) + return 0; + + return sd_device_enumerator_add_match_tag(udev_enumerate->enumerator, tag); +} + +/** + * udev_enumerate_add_match_parent: + * @udev_enumerate: context + * @parent: parent device where to start searching + * + * Return the devices on the subtree of one given device. The parent + * itself is included in the list. + * + * A reference for the device is held until the udev_enumerate context + * is cleaned up. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_parent(struct udev_enumerate *udev_enumerate, struct udev_device *parent) { + assert_return(udev_enumerate, -EINVAL); + + if (!parent) + return 0; + + return sd_device_enumerator_add_match_parent(udev_enumerate->enumerator, parent->device); +} + +/** + * udev_enumerate_add_match_is_initialized: + * @udev_enumerate: context + * + * Match only devices which udev has set up already. This makes + * sure, that the device node permissions and context are properly set + * and that network devices are fully renamed. + * + * Usually, devices which are found in the kernel but not already + * handled by udev, have still pending events. Services should subscribe + * to monitor events and wait for these devices to become ready, instead + * of using uninitialized devices. + * + * For now, this will not affect devices which do not have a device node + * and are not network interfaces. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_is_initialized(struct udev_enumerate *udev_enumerate) { + assert_return(udev_enumerate, -EINVAL); + + return device_enumerator_add_match_is_initialized(udev_enumerate->enumerator); +} + +/** + * udev_enumerate_add_match_sysname: + * @udev_enumerate: context + * @sysname: filter for the name of the device to include in the list + * + * Match only devices with a given /sys device name. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname) { + assert_return(udev_enumerate, -EINVAL); + + if (!sysname) + return 0; + + return sd_device_enumerator_add_match_sysname(udev_enumerate->enumerator, sysname); +} + +/** + * udev_enumerate_add_syspath: + * @udev_enumerate: context + * @syspath: path of a device + * + * Add a device to the list of devices, to retrieve it back sorted in dependency order. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath) { + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + int r; + + assert_return(udev_enumerate, -EINVAL); + + if (!syspath) + return 0; + + r = sd_device_new_from_syspath(&device, syspath); + if (r < 0) + return r; + + r = device_enumerator_add_device(udev_enumerate->enumerator, device); + if (r < 0) + return r; + + return 0; +} + +/** + * udev_enumerate_scan_devices: + * @udev_enumerate: udev enumeration context + * + * Scan /sys for all devices which match the given filters. No matches + * will return all currently available devices. + * + * Returns: 0 on success, otherwise a negative error value. + **/ +_public_ int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate) { + assert_return(udev_enumerate, -EINVAL); + + return device_enumerator_scan_devices(udev_enumerate->enumerator); +} + +/** + * udev_enumerate_scan_subsystems: + * @udev_enumerate: udev enumeration context + * + * Scan /sys for all kernel subsystems, including buses, classes, drivers. + * + * Returns: 0 on success, otherwise a negative error value. + **/ +_public_ int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate) { + assert_return(udev_enumerate, -EINVAL); + + return device_enumerator_scan_subsystems(udev_enumerate->enumerator); +} diff --git a/src/libudev/src/libudev-hwdb.c b/src/libudev/src/libudev-hwdb.c new file mode 100644 index 0000000000..8c4b488086 --- /dev/null +++ b/src/libudev/src/libudev-hwdb.c @@ -0,0 +1,146 @@ +/*** + This file is part of systemd. + + Copyright Tom Gundersen + + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "hwdb-util.h" +#include "libudev-private.h" + +/** + * SECTION:libudev-hwdb + * @short_description: retrieve properties from the hardware database + * + * Libudev hardware database interface. + */ + +/** + * udev_hwdb: + * + * Opaque object representing the hardware database. + */ +struct udev_hwdb { + struct udev *udev; + int refcount; + + sd_hwdb *hwdb; + + struct udev_list properties_list; +}; + +/** + * udev_hwdb_new: + * @udev: udev library context + * + * Create a hardware database context to query properties for devices. + * + * Returns: a hwdb context. + **/ +_public_ struct udev_hwdb *udev_hwdb_new(struct udev *udev) { + _cleanup_(sd_hwdb_unrefp) sd_hwdb *hwdb_internal = NULL; + struct udev_hwdb *hwdb; + int r; + + assert_return(udev, NULL); + + r = sd_hwdb_new(&hwdb_internal); + if (r < 0) + return NULL; + + hwdb = new0(struct udev_hwdb, 1); + if (!hwdb) + return NULL; + + hwdb->refcount = 1; + hwdb->hwdb = hwdb_internal; + hwdb_internal = NULL; + + udev_list_init(udev, &hwdb->properties_list, true); + + return hwdb; +} + +/** + * udev_hwdb_ref: + * @hwdb: context + * + * Take a reference of a hwdb context. + * + * Returns: the passed enumeration context + **/ +_public_ struct udev_hwdb *udev_hwdb_ref(struct udev_hwdb *hwdb) { + if (!hwdb) + return NULL; + hwdb->refcount++; + return hwdb; +} + +/** + * udev_hwdb_unref: + * @hwdb: context + * + * Drop a reference of a hwdb context. If the refcount reaches zero, + * all resources of the hwdb context will be released. + * + * Returns: #NULL + **/ +_public_ struct udev_hwdb *udev_hwdb_unref(struct udev_hwdb *hwdb) { + if (!hwdb) + return NULL; + hwdb->refcount--; + if (hwdb->refcount > 0) + return NULL; + sd_hwdb_unref(hwdb->hwdb); + udev_list_cleanup(&hwdb->properties_list); + free(hwdb); + return NULL; +} + +/** + * udev_hwdb_get_properties_list_entry: + * @hwdb: context + * @modalias: modalias string + * @flags: (unused) + * + * Lookup a matching device in the hardware database. The lookup key is a + * modalias string, whose formats are defined for the Linux kernel modules. + * Examples are: pci:v00008086d00001C2D*, usb:v04F2pB221*. The first entry + * of a list of retrieved properties is returned. + * + * Returns: a udev_list_entry. + */ +_public_ struct udev_list_entry *udev_hwdb_get_properties_list_entry(struct udev_hwdb *hwdb, const char *modalias, unsigned int flags) { + const char *key, *value; + + if (!hwdb || !modalias) { + errno = EINVAL; + return NULL; + } + + udev_list_cleanup(&hwdb->properties_list); + + SD_HWDB_FOREACH_PROPERTY(hwdb->hwdb, modalias, key, value) { + if (udev_list_entry_add(&hwdb->properties_list, key, value) == NULL) { + errno = ENOMEM; + return NULL; + } + } + + return udev_list_get_entry(&hwdb->properties_list); +} diff --git a/src/libudev/src/libudev-list.c b/src/libudev/src/libudev-list.c new file mode 100644 index 0000000000..da496ed456 --- /dev/null +++ b/src/libudev/src/libudev-list.c @@ -0,0 +1,352 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 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 . +***/ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "libudev-private.h" + +/** + * SECTION:libudev-list + * @short_description: list operation + * + * Libudev list operations. + */ + +/** + * udev_list_entry: + * + * Opaque object representing one entry in a list. An entry contains + * contains a name, and optionally a value. + */ +struct udev_list_entry { + struct udev_list_node node; + struct udev_list *list; + char *name; + char *value; + int num; +}; + +/* the list's head points to itself if empty */ +void udev_list_node_init(struct udev_list_node *list) +{ + list->next = list; + list->prev = list; +} + +int udev_list_node_is_empty(struct udev_list_node *list) +{ + return list->next == list; +} + +static void udev_list_node_insert_between(struct udev_list_node *new, + struct udev_list_node *prev, + struct udev_list_node *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +void udev_list_node_append(struct udev_list_node *new, struct udev_list_node *list) +{ + udev_list_node_insert_between(new, list->prev, list); +} + +void udev_list_node_remove(struct udev_list_node *entry) +{ + struct udev_list_node *prev = entry->prev; + struct udev_list_node *next = entry->next; + + next->prev = prev; + prev->next = next; + + entry->prev = NULL; + entry->next = NULL; +} + +/* return list entry which embeds this node */ +static inline struct udev_list_entry *list_node_to_entry(struct udev_list_node *node) +{ + return container_of(node, struct udev_list_entry, node); +} + +void udev_list_init(struct udev *udev, struct udev_list *list, bool unique) +{ + memzero(list, sizeof(struct udev_list)); + list->udev = udev; + list->unique = unique; + udev_list_node_init(&list->node); +} + +/* insert entry into a list as the last element */ +static void udev_list_entry_append(struct udev_list_entry *new, struct udev_list *list) +{ + /* inserting before the list head make the node the last node in the list */ + udev_list_node_insert_between(&new->node, list->node.prev, &list->node); + new->list = list; +} + +/* insert entry into a list, before a given existing entry */ +static void udev_list_entry_insert_before(struct udev_list_entry *new, struct udev_list_entry *entry) +{ + udev_list_node_insert_between(&new->node, entry->node.prev, &entry->node); + new->list = entry->list; +} + +/* binary search in sorted array */ +static int list_search(struct udev_list *list, const char *name) +{ + unsigned int first, last; + + first = 0; + last = list->entries_cur; + while (first < last) { + unsigned int i; + int cmp; + + i = (first + last)/2; + cmp = strcmp(name, list->entries[i]->name); + if (cmp < 0) + last = i; + else if (cmp > 0) + first = i+1; + else + return i; + } + + /* not found, return negative insertion-index+1 */ + return -(first+1); +} + +struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *name, const char *value) +{ + struct udev_list_entry *entry; + int i = 0; + + if (list->unique) { + /* lookup existing name or insertion-index */ + i = list_search(list, name); + if (i >= 0) { + entry = list->entries[i]; + + free(entry->value); + if (value == NULL) { + entry->value = NULL; + return entry; + } + entry->value = strdup(value); + if (entry->value == NULL) + return NULL; + return entry; + } + } + + /* add new name */ + entry = new0(struct udev_list_entry, 1); + if (entry == NULL) + return NULL; + entry->name = strdup(name); + if (entry->name == NULL) { + free(entry); + return NULL; + } + if (value != NULL) { + entry->value = strdup(value); + if (entry->value == NULL) { + free(entry->name); + free(entry); + return NULL; + } + } + + if (list->unique) { + /* allocate or enlarge sorted array if needed */ + if (list->entries_cur >= list->entries_max) { + struct udev_list_entry **entries; + unsigned int add; + + add = list->entries_max; + if (add < 1) + add = 64; + entries = realloc(list->entries, (list->entries_max + add) * sizeof(struct udev_list_entry *)); + if (entries == NULL) { + free(entry->name); + free(entry->value); + free(entry); + return NULL; + } + list->entries = entries; + list->entries_max += add; + } + + /* the negative i returned the insertion index */ + i = (-i)-1; + + /* insert into sorted list */ + if ((unsigned int)i < list->entries_cur) + udev_list_entry_insert_before(entry, list->entries[i]); + else + udev_list_entry_append(entry, list); + + /* insert into sorted array */ + memmove(&list->entries[i+1], &list->entries[i], + (list->entries_cur - i) * sizeof(struct udev_list_entry *)); + list->entries[i] = entry; + list->entries_cur++; + } else { + udev_list_entry_append(entry, list); + } + + return entry; +} + +void udev_list_entry_delete(struct udev_list_entry *entry) +{ + if (entry->list->entries != NULL) { + int i; + struct udev_list *list = entry->list; + + /* remove entry from sorted array */ + i = list_search(list, entry->name); + if (i >= 0) { + memmove(&list->entries[i], &list->entries[i+1], + ((list->entries_cur-1) - i) * sizeof(struct udev_list_entry *)); + list->entries_cur--; + } + } + + udev_list_node_remove(&entry->node); + free(entry->name); + free(entry->value); + free(entry); +} + +void udev_list_cleanup(struct udev_list *list) +{ + struct udev_list_entry *entry_loop; + struct udev_list_entry *entry_tmp; + + list->entries = mfree(list->entries); + list->entries_cur = 0; + list->entries_max = 0; + udev_list_entry_foreach_safe(entry_loop, entry_tmp, udev_list_get_entry(list)) + udev_list_entry_delete(entry_loop); +} + +struct udev_list_entry *udev_list_get_entry(struct udev_list *list) +{ + if (udev_list_node_is_empty(&list->node)) + return NULL; + return list_node_to_entry(list->node.next); +} + +/** + * udev_list_entry_get_next: + * @list_entry: current entry + * + * Get the next entry from the list. + * + * Returns: udev_list_entry, #NULL if no more entries are available. + */ +_public_ struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry) +{ + struct udev_list_node *next; + + if (list_entry == NULL) + return NULL; + next = list_entry->node.next; + /* empty list or no more entries */ + if (next == &list_entry->list->node) + return NULL; + return list_node_to_entry(next); +} + +/** + * udev_list_entry_get_by_name: + * @list_entry: current entry + * @name: name string to match + * + * Lookup an entry in the list with a certain name. + * + * Returns: udev_list_entry, #NULL if no matching entry is found. + */ +_public_ struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name) +{ + int i; + + if (list_entry == NULL) + return NULL; + + if (!list_entry->list->unique) + return NULL; + + i = list_search(list_entry->list, name); + if (i < 0) + return NULL; + return list_entry->list->entries[i]; +} + +/** + * udev_list_entry_get_name: + * @list_entry: current entry + * + * Get the name of a list entry. + * + * Returns: the name string of this entry. + */ +_public_ const char *udev_list_entry_get_name(struct udev_list_entry *list_entry) +{ + if (list_entry == NULL) + return NULL; + return list_entry->name; +} + +/** + * udev_list_entry_get_value: + * @list_entry: current entry + * + * Get the value of list entry. + * + * Returns: the value string of this entry. + */ +_public_ const char *udev_list_entry_get_value(struct udev_list_entry *list_entry) +{ + if (list_entry == NULL) + return NULL; + return list_entry->value; +} + +int udev_list_entry_get_num(struct udev_list_entry *list_entry) +{ + if (list_entry == NULL) + return -EINVAL; + return list_entry->num; +} + +void udev_list_entry_set_num(struct udev_list_entry *list_entry, int num) +{ + if (list_entry == NULL) + return; + list_entry->num = num; +} diff --git a/src/libudev/src/libudev-monitor.c b/src/libudev/src/libudev-monitor.c new file mode 100644 index 0000000000..f870eba9eb --- /dev/null +++ b/src/libudev/src/libudev-monitor.c @@ -0,0 +1,845 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libudev.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "libudev-private.h" +#include "missing.h" +#include "mount-util.h" +#include "socket-util.h" +#include "string-util.h" + +/** + * SECTION:libudev-monitor + * @short_description: device event source + * + * Connects to a device event source. + */ + +/** + * udev_monitor: + * + * Opaque object handling an event source. + */ +struct udev_monitor { + struct udev *udev; + int refcount; + int sock; + union sockaddr_union snl; + union sockaddr_union snl_trusted_sender; + union sockaddr_union snl_destination; + socklen_t addrlen; + struct udev_list filter_subsystem_list; + struct udev_list filter_tag_list; + bool bound; +}; + +enum udev_monitor_netlink_group { + UDEV_MONITOR_NONE, + UDEV_MONITOR_KERNEL, + UDEV_MONITOR_UDEV, +}; + +#define UDEV_MONITOR_MAGIC 0xfeedcafe +struct udev_monitor_netlink_header { + /* "libudev" prefix to distinguish libudev and kernel messages */ + char prefix[8]; + /* + * magic to protect against daemon <-> library message format mismatch + * used in the kernel from socket filter rules; needs to be stored in network order + */ + unsigned int magic; + /* total length of header structure known to the sender */ + unsigned int header_size; + /* properties string buffer */ + unsigned int properties_off; + unsigned int properties_len; + /* + * hashes of primary device properties strings, to let libudev subscribers + * use in-kernel socket filters; values need to be stored in network order + */ + unsigned int filter_subsystem_hash; + unsigned int filter_devtype_hash; + unsigned int filter_tag_bloom_hi; + unsigned int filter_tag_bloom_lo; +}; + +static struct udev_monitor *udev_monitor_new(struct udev *udev) +{ + struct udev_monitor *udev_monitor; + + udev_monitor = new0(struct udev_monitor, 1); + if (udev_monitor == NULL) + return NULL; + udev_monitor->refcount = 1; + udev_monitor->udev = udev; + udev_list_init(udev, &udev_monitor->filter_subsystem_list, false); + udev_list_init(udev, &udev_monitor->filter_tag_list, true); + return udev_monitor; +} + +/* we consider udev running when /dev is on devtmpfs */ +static bool udev_has_devtmpfs(struct udev *udev) { + + union file_handle_union h = FILE_HANDLE_INIT; + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX], *e; + int mount_id; + int r; + + r = name_to_handle_at(AT_FDCWD, "/dev", &h.handle, &mount_id, 0); + if (r < 0) { + if (errno != EOPNOTSUPP) + log_debug_errno(errno, "name_to_handle_at on /dev: %m"); + return false; + } + + f = fopen("/proc/self/mountinfo", "re"); + if (!f) + return false; + + FOREACH_LINE(line, f, return false) { + int mid; + + if (sscanf(line, "%i", &mid) != 1) + continue; + + if (mid != mount_id) + continue; + + e = strstr(line, " - "); + if (!e) + continue; + + /* accept any name that starts with the currently expected type */ + if (startswith(e + 3, "devtmpfs")) + return true; + } + + return false; +} + +static void monitor_set_nl_address(struct udev_monitor *udev_monitor) { + union sockaddr_union snl; + socklen_t addrlen; + int r; + + assert(udev_monitor); + + /* get the address the kernel has assigned us + * it is usually, but not necessarily the pid + */ + addrlen = sizeof(struct sockaddr_nl); + r = getsockname(udev_monitor->sock, &snl.sa, &addrlen); + if (r >= 0) + udev_monitor->snl.nl.nl_pid = snl.nl.nl_pid; +} + +struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const char *name, int fd) +{ + struct udev_monitor *udev_monitor; + unsigned int group; + + if (udev == NULL) + return NULL; + + if (name == NULL) + group = UDEV_MONITOR_NONE; + else if (streq(name, "udev")) { + /* + * We do not support subscribing to uevents if no instance of + * udev is running. Uevents would otherwise broadcast the + * processing data of the host into containers, which is not + * desired. + * + * Containers will currently not get any udev uevents, until + * a supporting infrastructure is available. + * + * We do not set a netlink multicast group here, so the socket + * will not receive any messages. + */ + if (access("/run/udev/control", F_OK) < 0 && !udev_has_devtmpfs(udev)) { + log_debug("the udev service seems not to be active, disable the monitor"); + group = UDEV_MONITOR_NONE; + } else + group = UDEV_MONITOR_UDEV; + } else if (streq(name, "kernel")) + group = UDEV_MONITOR_KERNEL; + else + return NULL; + + udev_monitor = udev_monitor_new(udev); + if (udev_monitor == NULL) + return NULL; + + if (fd < 0) { + udev_monitor->sock = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_KOBJECT_UEVENT); + if (udev_monitor->sock < 0) { + log_debug_errno(errno, "error getting socket: %m"); + free(udev_monitor); + return NULL; + } + } else { + udev_monitor->bound = true; + udev_monitor->sock = fd; + monitor_set_nl_address(udev_monitor); + } + + udev_monitor->snl.nl.nl_family = AF_NETLINK; + udev_monitor->snl.nl.nl_groups = group; + + /* default destination for sending */ + udev_monitor->snl_destination.nl.nl_family = AF_NETLINK; + udev_monitor->snl_destination.nl.nl_groups = UDEV_MONITOR_UDEV; + + return udev_monitor; +} + +/** + * udev_monitor_new_from_netlink: + * @udev: udev library context + * @name: name of event source + * + * Create new udev monitor and connect to a specified event + * source. Valid sources identifiers are "udev" and "kernel". + * + * Applications should usually not connect directly to the + * "kernel" events, because the devices might not be useable + * at that time, before udev has configured them, and created + * device nodes. Accessing devices at the same time as udev, + * might result in unpredictable behavior. The "udev" events + * are sent out after udev has finished its event processing, + * all rules have been processed, and needed device nodes are + * created. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev monitor. + * + * Returns: a new udev monitor, or #NULL, in case of an error + **/ +_public_ struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name) +{ + return udev_monitor_new_from_netlink_fd(udev, name, -1); +} + +static inline void bpf_stmt(struct sock_filter *inss, unsigned int *i, + unsigned short code, unsigned int data) +{ + struct sock_filter *ins = &inss[*i]; + + ins->code = code; + ins->k = data; + (*i)++; +} + +static inline void bpf_jmp(struct sock_filter *inss, unsigned int *i, + unsigned short code, unsigned int data, + unsigned short jt, unsigned short jf) +{ + struct sock_filter *ins = &inss[*i]; + + ins->code = code; + ins->jt = jt; + ins->jf = jf; + ins->k = data; + (*i)++; +} + +/** + * udev_monitor_filter_update: + * @udev_monitor: monitor + * + * Update the installed socket filter. This is only needed, + * if the filter was removed or changed. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_filter_update(struct udev_monitor *udev_monitor) +{ + struct sock_filter ins[512]; + struct sock_fprog filter; + unsigned int i; + struct udev_list_entry *list_entry; + int err; + + if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL && + udev_list_get_entry(&udev_monitor->filter_tag_list) == NULL) + return 0; + + memzero(ins, sizeof(ins)); + i = 0; + + /* load magic in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, magic)); + /* jump if magic matches */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, UDEV_MONITOR_MAGIC, 1, 0); + /* wrong magic, pass packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); + + if (udev_list_get_entry(&udev_monitor->filter_tag_list) != NULL) { + int tag_matches; + + /* count tag matches, to calculate end of tag match block */ + tag_matches = 0; + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) + tag_matches++; + + /* add all tags matches */ + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) { + uint64_t tag_bloom_bits = util_string_bloom64(udev_list_entry_get_name(list_entry)); + uint32_t tag_bloom_hi = tag_bloom_bits >> 32; + uint32_t tag_bloom_lo = tag_bloom_bits & 0xffffffff; + + /* load device bloom bits in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_tag_bloom_hi)); + /* clear bits (tag bits & bloom bits) */ + bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_hi); + /* jump to next tag if it does not match */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_hi, 0, 3); + + /* load device bloom bits in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_tag_bloom_lo)); + /* clear bits (tag bits & bloom bits) */ + bpf_stmt(ins, &i, BPF_ALU|BPF_AND|BPF_K, tag_bloom_lo); + /* jump behind end of tag match block if tag matches */ + tag_matches--; + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, tag_bloom_lo, 1 + (tag_matches * 6), 0); + } + + /* nothing matched, drop packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); + } + + /* add all subsystem matches */ + if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) != NULL) { + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) { + unsigned int hash = util_string_hash32(udev_list_entry_get_name(list_entry)); + + /* load device subsystem value in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_subsystem_hash)); + if (udev_list_entry_get_value(list_entry) == NULL) { + /* jump if subsystem does not match */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); + } else { + /* jump if subsystem does not match */ + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 3); + + /* load device devtype value in A */ + bpf_stmt(ins, &i, BPF_LD|BPF_W|BPF_ABS, offsetof(struct udev_monitor_netlink_header, filter_devtype_hash)); + /* jump if value does not match */ + hash = util_string_hash32(udev_list_entry_get_value(list_entry)); + bpf_jmp(ins, &i, BPF_JMP|BPF_JEQ|BPF_K, hash, 0, 1); + } + + /* matched, pass packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); + + if (i+1 >= ELEMENTSOF(ins)) + return -E2BIG; + } + + /* nothing matched, drop packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0); + } + + /* matched, pass packet */ + bpf_stmt(ins, &i, BPF_RET|BPF_K, 0xffffffff); + + /* install filter */ + memzero(&filter, sizeof(filter)); + filter.len = i; + filter.filter = ins; + err = setsockopt(udev_monitor->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)); + return err < 0 ? -errno : 0; +} + +int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender) +{ + udev_monitor->snl_trusted_sender.nl.nl_pid = sender->snl.nl.nl_pid; + return 0; +} + +/** + * udev_monitor_enable_receiving: + * @udev_monitor: the monitor which should receive events + * + * Binds the @udev_monitor socket to the event source. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor) +{ + int err = 0; + const int on = 1; + + udev_monitor_filter_update(udev_monitor); + + if (!udev_monitor->bound) { + err = bind(udev_monitor->sock, + &udev_monitor->snl.sa, sizeof(struct sockaddr_nl)); + if (err == 0) + udev_monitor->bound = true; + } + + if (err >= 0) + monitor_set_nl_address(udev_monitor); + else + return log_debug_errno(errno, "bind failed: %m"); + + /* enable receiving of sender credentials */ + err = setsockopt(udev_monitor->sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); + if (err < 0) + log_debug_errno(errno, "setting SO_PASSCRED failed: %m"); + + return 0; +} + +/** + * udev_monitor_set_receive_buffer_size: + * @udev_monitor: the monitor which should receive events + * @size: the size in bytes + * + * Set the size of the kernel socket buffer. This call needs the + * appropriate privileges to succeed. + * + * Returns: 0 on success, otherwise -1 on error. + */ +_public_ int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size) +{ + if (udev_monitor == NULL) + return -EINVAL; + return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_RCVBUFFORCE, &size, sizeof(size)); +} + +int udev_monitor_disconnect(struct udev_monitor *udev_monitor) +{ + int err; + + err = close(udev_monitor->sock); + udev_monitor->sock = -1; + return err < 0 ? -errno : 0; +} + +/** + * udev_monitor_ref: + * @udev_monitor: udev monitor + * + * Take a reference of a udev monitor. + * + * Returns: the passed udev monitor + **/ +_public_ struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor) +{ + if (udev_monitor == NULL) + return NULL; + udev_monitor->refcount++; + return udev_monitor; +} + +/** + * udev_monitor_unref: + * @udev_monitor: udev monitor + * + * Drop a reference of a udev monitor. If the refcount reaches zero, + * the bound socket will be closed, and the resources of the monitor + * will be released. + * + * Returns: #NULL + **/ +_public_ struct udev_monitor *udev_monitor_unref(struct udev_monitor *udev_monitor) +{ + if (udev_monitor == NULL) + return NULL; + udev_monitor->refcount--; + if (udev_monitor->refcount > 0) + return NULL; + if (udev_monitor->sock >= 0) + close(udev_monitor->sock); + udev_list_cleanup(&udev_monitor->filter_subsystem_list); + udev_list_cleanup(&udev_monitor->filter_tag_list); + free(udev_monitor); + return NULL; +} + +/** + * udev_monitor_get_udev: + * @udev_monitor: udev monitor + * + * Retrieve the udev library context the monitor was created with. + * + * Returns: the udev library context + **/ +_public_ struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor) +{ + if (udev_monitor == NULL) + return NULL; + return udev_monitor->udev; +} + +/** + * udev_monitor_get_fd: + * @udev_monitor: udev monitor + * + * Retrieve the socket file descriptor associated with the monitor. + * + * Returns: the socket file descriptor + **/ +_public_ int udev_monitor_get_fd(struct udev_monitor *udev_monitor) +{ + if (udev_monitor == NULL) + return -EINVAL; + return udev_monitor->sock; +} + +static int passes_filter(struct udev_monitor *udev_monitor, struct udev_device *udev_device) +{ + struct udev_list_entry *list_entry; + + if (udev_list_get_entry(&udev_monitor->filter_subsystem_list) == NULL) + goto tag; + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_subsystem_list)) { + const char *subsys = udev_list_entry_get_name(list_entry); + const char *dsubsys = udev_device_get_subsystem(udev_device); + const char *devtype; + const char *ddevtype; + + if (!streq(dsubsys, subsys)) + continue; + + devtype = udev_list_entry_get_value(list_entry); + if (devtype == NULL) + goto tag; + ddevtype = udev_device_get_devtype(udev_device); + if (ddevtype == NULL) + continue; + if (streq(ddevtype, devtype)) + goto tag; + } + return 0; + +tag: + if (udev_list_get_entry(&udev_monitor->filter_tag_list) == NULL) + return 1; + udev_list_entry_foreach(list_entry, udev_list_get_entry(&udev_monitor->filter_tag_list)) { + const char *tag = udev_list_entry_get_name(list_entry); + + if (udev_device_has_tag(udev_device, tag)) + return 1; + } + return 0; +} + +/** + * udev_monitor_receive_device: + * @udev_monitor: udev monitor + * + * Receive data from the udev monitor socket, allocate a new udev + * device, fill in the received data, and return the device. + * + * Only socket connections with uid=0 are accepted. + * + * The monitor socket is by default set to NONBLOCK. A variant of poll() on + * the file descriptor returned by udev_monitor_get_fd() should to be used to + * wake up when new devices arrive, or alternatively the file descriptor + * switched into blocking mode. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev device. + * + * Returns: a new udev device, or #NULL, in case of an error + **/ +_public_ struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor) +{ + struct udev_device *udev_device; + struct msghdr smsg; + struct iovec iov; + char cred_msg[CMSG_SPACE(sizeof(struct ucred))]; + struct cmsghdr *cmsg; + union sockaddr_union snl; + struct ucred *cred; + union { + struct udev_monitor_netlink_header nlh; + char raw[8192]; + } buf; + ssize_t buflen; + ssize_t bufpos; + bool is_initialized = false; + +retry: + if (udev_monitor == NULL) + return NULL; + iov.iov_base = &buf; + iov.iov_len = sizeof(buf); + memzero(&smsg, sizeof(struct msghdr)); + smsg.msg_iov = &iov; + smsg.msg_iovlen = 1; + smsg.msg_control = cred_msg; + smsg.msg_controllen = sizeof(cred_msg); + smsg.msg_name = &snl; + smsg.msg_namelen = sizeof(snl); + + buflen = recvmsg(udev_monitor->sock, &smsg, 0); + if (buflen < 0) { + if (errno != EINTR) + log_debug("unable to receive message"); + return NULL; + } + + if (buflen < 32 || (smsg.msg_flags & MSG_TRUNC)) { + log_debug("invalid message length"); + return NULL; + } + + if (snl.nl.nl_groups == 0) { + /* unicast message, check if we trust the sender */ + if (udev_monitor->snl_trusted_sender.nl.nl_pid == 0 || + snl.nl.nl_pid != udev_monitor->snl_trusted_sender.nl.nl_pid) { + log_debug("unicast netlink message ignored"); + return NULL; + } + } else if (snl.nl.nl_groups == UDEV_MONITOR_KERNEL) { + if (snl.nl.nl_pid > 0) { + log_debug("multicast kernel netlink message from PID %"PRIu32" ignored", + snl.nl.nl_pid); + return NULL; + } + } + + cmsg = CMSG_FIRSTHDR(&smsg); + if (cmsg == NULL || cmsg->cmsg_type != SCM_CREDENTIALS) { + log_debug("no sender credentials received, message ignored"); + return NULL; + } + + cred = (struct ucred *)CMSG_DATA(cmsg); + if (cred->uid != 0) { + log_debug("sender uid="UID_FMT", message ignored", cred->uid); + return NULL; + } + + if (memcmp(buf.raw, "libudev", 8) == 0) { + /* udev message needs proper version magic */ + if (buf.nlh.magic != htonl(UDEV_MONITOR_MAGIC)) { + log_debug("unrecognized message signature (%x != %x)", + buf.nlh.magic, htonl(UDEV_MONITOR_MAGIC)); + return NULL; + } + if (buf.nlh.properties_off+32 > (size_t)buflen) { + log_debug("message smaller than expected (%u > %zd)", + buf.nlh.properties_off+32, buflen); + return NULL; + } + + bufpos = buf.nlh.properties_off; + + /* devices received from udev are always initialized */ + is_initialized = true; + } else { + /* kernel message with header */ + bufpos = strlen(buf.raw) + 1; + if ((size_t)bufpos < sizeof("a@/d") || bufpos >= buflen) { + log_debug("invalid message length"); + return NULL; + } + + /* check message header */ + if (strstr(buf.raw, "@/") == NULL) { + log_debug("unrecognized message header"); + return NULL; + } + } + + udev_device = udev_device_new_from_nulstr(udev_monitor->udev, &buf.raw[bufpos], buflen - bufpos); + if (!udev_device) { + log_debug("could not create device: %m"); + return NULL; + } + + if (is_initialized) + udev_device_set_is_initialized(udev_device); + + /* skip device, if it does not pass the current filter */ + if (!passes_filter(udev_monitor, udev_device)) { + struct pollfd pfd[1]; + int rc; + + udev_device_unref(udev_device); + + /* if something is queued, get next device */ + pfd[0].fd = udev_monitor->sock; + pfd[0].events = POLLIN; + rc = poll(pfd, 1, 0); + if (rc > 0) + goto retry; + return NULL; + } + + return udev_device; +} + +int udev_monitor_send_device(struct udev_monitor *udev_monitor, + struct udev_monitor *destination, struct udev_device *udev_device) +{ + const char *buf, *val; + ssize_t blen, count; + struct udev_monitor_netlink_header nlh = { + .prefix = "libudev", + .magic = htonl(UDEV_MONITOR_MAGIC), + .header_size = sizeof nlh, + }; + struct iovec iov[2] = { + { .iov_base = &nlh, .iov_len = sizeof nlh }, + }; + struct msghdr smsg = { + .msg_iov = iov, + .msg_iovlen = 2, + }; + struct udev_list_entry *list_entry; + uint64_t tag_bloom_bits; + + blen = udev_device_get_properties_monitor_buf(udev_device, &buf); + if (blen < 32) { + log_debug("device buffer is too small to contain a valid device"); + return -EINVAL; + } + + /* fill in versioned header */ + val = udev_device_get_subsystem(udev_device); + nlh.filter_subsystem_hash = htonl(util_string_hash32(val)); + + val = udev_device_get_devtype(udev_device); + if (val != NULL) + nlh.filter_devtype_hash = htonl(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); + } + + /* add properties list */ + nlh.properties_off = iov[0].iov_len; + nlh.properties_len = blen; + iov[1].iov_base = (char *)buf; + iov[1].iov_len = blen; + + /* + * Use custom address for target, or the default one. + * + * If we send to a multicast group, we will get + * ECONNREFUSED, which is expected. + */ + if (destination) + smsg.msg_name = &destination->snl; + else + smsg.msg_name = &udev_monitor->snl_destination; + smsg.msg_namelen = sizeof(struct sockaddr_nl); + count = sendmsg(udev_monitor->sock, &smsg, 0); + if (count < 0) { + if (!destination && errno == ECONNREFUSED) { + log_debug("passed device to netlink monitor %p", udev_monitor); + return 0; + } else + return -errno; + } + + log_debug("passed %zi byte device to netlink monitor %p", count, udev_monitor); + return count; +} + +/** + * udev_monitor_filter_add_match_subsystem_devtype: + * @udev_monitor: the monitor + * @subsystem: the subsystem value to match the incoming devices against + * @devtype: the devtype value to match the incoming devices against + * + * This filter is efficiently executed inside the kernel, and libudev subscribers + * will usually not be woken up for devices which do not match. + * + * The filter must be installed before the monitor is switched to listening mode. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor, const char *subsystem, const char *devtype) +{ + if (udev_monitor == NULL) + return -EINVAL; + if (subsystem == NULL) + return -EINVAL; + if (udev_list_entry_add(&udev_monitor->filter_subsystem_list, subsystem, devtype) == NULL) + return -ENOMEM; + return 0; +} + +/** + * udev_monitor_filter_add_match_tag: + * @udev_monitor: the monitor + * @tag: the name of a tag + * + * This filter is efficiently executed inside the kernel, and libudev subscribers + * will usually not be woken up for devices which do not match. + * + * The filter must be installed before the monitor is switched to listening mode. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag) +{ + if (udev_monitor == NULL) + return -EINVAL; + if (tag == NULL) + return -EINVAL; + if (udev_list_entry_add(&udev_monitor->filter_tag_list, tag, NULL) == NULL) + return -ENOMEM; + return 0; +} + +/** + * udev_monitor_filter_remove: + * @udev_monitor: monitor + * + * Remove all filters from monitor. + * + * Returns: 0 on success, otherwise a negative error value. + */ +_public_ int udev_monitor_filter_remove(struct udev_monitor *udev_monitor) +{ + static struct sock_fprog filter = { 0, NULL }; + + udev_list_cleanup(&udev_monitor->filter_subsystem_list); + return setsockopt(udev_monitor->sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)); +} diff --git a/src/libudev/src/libudev-private.h b/src/libudev/src/libudev-private.h new file mode 100644 index 0000000000..52c5075110 --- /dev/null +++ b/src/libudev/src/libudev-private.h @@ -0,0 +1,150 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 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 . +***/ + +#ifndef _LIBUDEV_PRIVATE_H_ +#define _LIBUDEV_PRIVATE_H_ + +#include +#include +#include + +#include "libudev.h" + +#include "macro.h" +#include "mkdir.h" +#include "strxcpyx.h" +#include "util.h" + +#define READ_END 0 +#define WRITE_END 1 + +/* libudev.c */ +int udev_get_rules_path(struct udev *udev, char **path[], usec_t *ts_usec[]); + +/* libudev-device.c */ +struct udev_device *udev_device_new_from_nulstr(struct udev *udev, char *nulstr, ssize_t buflen); +struct udev_device *udev_device_new_from_synthetic_event(struct udev *udev, const char *syspath, const char *action); +struct udev_device *udev_device_shallow_clone(struct udev_device *old_device); +struct udev_device *udev_device_clone_with_db(struct udev_device *old_device); +int udev_device_copy_properties(struct udev_device *dst, struct udev_device *src); +mode_t udev_device_get_devnode_mode(struct udev_device *udev_device); +uid_t udev_device_get_devnode_uid(struct udev_device *udev_device); +gid_t udev_device_get_devnode_gid(struct udev_device *udev_device); +int udev_device_rename(struct udev_device *udev_device, const char *new_name); +int udev_device_add_devlink(struct udev_device *udev_device, const char *devlink); +void udev_device_cleanup_devlinks_list(struct udev_device *udev_device); +int udev_device_add_property(struct udev_device *udev_device, const char *key, const char *value); +char **udev_device_get_properties_envp(struct udev_device *udev_device); +ssize_t udev_device_get_properties_monitor_buf(struct udev_device *udev_device, const char **buf); +const char *udev_device_get_devpath_old(struct udev_device *udev_device); +const char *udev_device_get_id_filename(struct udev_device *udev_device); +void udev_device_set_is_initialized(struct udev_device *udev_device); +int udev_device_add_tag(struct udev_device *udev_device, const char *tag); +void udev_device_remove_tag(struct udev_device *udev_device, const char *tag); +void udev_device_cleanup_tags_list(struct udev_device *udev_device); +usec_t udev_device_get_usec_initialized(struct udev_device *udev_device); +void udev_device_ensure_usec_initialized(struct udev_device *udev_device, struct udev_device *old_device); +int udev_device_get_devlink_priority(struct udev_device *udev_device); +int udev_device_set_devlink_priority(struct udev_device *udev_device, int prio); +int udev_device_get_watch_handle(struct udev_device *udev_device); +int udev_device_set_watch_handle(struct udev_device *udev_device, int handle); +int udev_device_get_ifindex(struct udev_device *udev_device); +void udev_device_set_info_loaded(struct udev_device *device); +bool udev_device_get_db_persist(struct udev_device *udev_device); +void udev_device_set_db_persist(struct udev_device *udev_device); +void udev_device_read_db(struct udev_device *udev_device); + +/* libudev-device-private.c */ +int udev_device_update_db(struct udev_device *udev_device); +int udev_device_delete_db(struct udev_device *udev_device); +int udev_device_tag_index(struct udev_device *dev, struct udev_device *dev_old, bool add); + +/* libudev-monitor.c - netlink/unix socket communication */ +int udev_monitor_disconnect(struct udev_monitor *udev_monitor); +int udev_monitor_allow_unicast_sender(struct udev_monitor *udev_monitor, struct udev_monitor *sender); +int udev_monitor_send_device(struct udev_monitor *udev_monitor, + struct udev_monitor *destination, struct udev_device *udev_device); +struct udev_monitor *udev_monitor_new_from_netlink_fd(struct udev *udev, const char *name, int fd); + +/* libudev-list.c */ +struct udev_list_node { + struct udev_list_node *next, *prev; +}; +struct udev_list { + struct udev *udev; + struct udev_list_node node; + struct udev_list_entry **entries; + unsigned int entries_cur; + unsigned int entries_max; + bool unique; +}; +void udev_list_node_init(struct udev_list_node *list); +int udev_list_node_is_empty(struct udev_list_node *list); +void udev_list_node_append(struct udev_list_node *new, struct udev_list_node *list); +void udev_list_node_remove(struct udev_list_node *entry); +#define udev_list_node_foreach(node, list) \ + for (node = (list)->next; \ + node != list; \ + node = (node)->next) +#define udev_list_node_foreach_safe(node, tmp, list) \ + for (node = (list)->next, tmp = (node)->next; \ + node != list; \ + node = tmp, tmp = (tmp)->next) +void udev_list_init(struct udev *udev, struct udev_list *list, bool unique); +void udev_list_cleanup(struct udev_list *list); +struct udev_list_entry *udev_list_get_entry(struct udev_list *list); +struct udev_list_entry *udev_list_entry_add(struct udev_list *list, const char *name, const char *value); +void udev_list_entry_delete(struct udev_list_entry *entry); +int udev_list_entry_get_num(struct udev_list_entry *list_entry); +void udev_list_entry_set_num(struct udev_list_entry *list_entry, int num); +#define udev_list_entry_foreach_safe(entry, tmp, first) \ + for (entry = first, tmp = udev_list_entry_get_next(entry); \ + entry != NULL; \ + entry = tmp, tmp = udev_list_entry_get_next(tmp)) + +/* libudev-queue.c */ +unsigned long long int udev_get_kernel_seqnum(struct udev *udev); +int udev_queue_read_seqnum(FILE *queue_file, unsigned long long int *seqnum); +ssize_t udev_queue_read_devpath(FILE *queue_file, char *devpath, size_t size); +ssize_t udev_queue_skip_devpath(FILE *queue_file); + +/* libudev-queue-private.c */ +struct udev_queue_export *udev_queue_export_new(struct udev *udev); +struct udev_queue_export *udev_queue_export_unref(struct udev_queue_export *udev_queue_export); +void udev_queue_export_cleanup(struct udev_queue_export *udev_queue_export); +int udev_queue_export_device_queued(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device); +int udev_queue_export_device_finished(struct udev_queue_export *udev_queue_export, struct udev_device *udev_device); + +/* libudev-util.c */ +#define UTIL_PATH_SIZE 1024 +#define UTIL_NAME_SIZE 512 +#define UTIL_LINE_SIZE 16384 +#define UDEV_ALLOWED_CHARS_INPUT "/ $%?," +int util_log_priority(const char *priority); +size_t util_path_encode(const char *src, char *dest, size_t size); +void util_remove_trailing_chars(char *path, char c); +int util_replace_whitespace(const char *str, char *to, size_t len); +int util_replace_chars(char *str, const char *white); +unsigned int util_string_hash32(const char *key); +uint64_t util_string_bloom64(const char *str); + +/* libudev-util-private.c */ +int util_resolve_subsys_kernel(struct udev *udev, const char *string, char *result, size_t maxsize, int read_value); + +#endif diff --git a/src/libudev/src/libudev-queue.c b/src/libudev/src/libudev-queue.c new file mode 100644 index 0000000000..e3dffa6925 --- /dev/null +++ b/src/libudev/src/libudev-queue.c @@ -0,0 +1,268 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 Kay Sievers + Copyright 2009 Alan Jenkins + + 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "libudev-private.h" + +/** + * SECTION:libudev-queue + * @short_description: access to currently active events + * + * This exports the current state of the udev processing queue. + */ + +/** + * udev_queue: + * + * Opaque object representing the current event queue in the udev daemon. + */ +struct udev_queue { + struct udev *udev; + int refcount; + int fd; +}; + +/** + * udev_queue_new: + * @udev: udev library context + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev queue context. + * + * Returns: the udev queue context, or #NULL on error. + **/ +_public_ struct udev_queue *udev_queue_new(struct udev *udev) +{ + struct udev_queue *udev_queue; + + if (udev == NULL) + return NULL; + + udev_queue = new0(struct udev_queue, 1); + if (udev_queue == NULL) + return NULL; + + udev_queue->refcount = 1; + udev_queue->udev = udev; + udev_queue->fd = -1; + return udev_queue; +} + +/** + * udev_queue_ref: + * @udev_queue: udev queue context + * + * Take a reference of a udev queue context. + * + * Returns: the same udev queue context. + **/ +_public_ struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue) +{ + if (udev_queue == NULL) + return NULL; + + udev_queue->refcount++; + return udev_queue; +} + +/** + * udev_queue_unref: + * @udev_queue: udev queue context + * + * Drop a reference of a udev queue context. If the refcount reaches zero, + * the resources of the queue context will be released. + * + * Returns: #NULL + **/ +_public_ struct udev_queue *udev_queue_unref(struct udev_queue *udev_queue) +{ + if (udev_queue == NULL) + return NULL; + + udev_queue->refcount--; + if (udev_queue->refcount > 0) + return NULL; + + safe_close(udev_queue->fd); + + free(udev_queue); + return NULL; +} + +/** + * udev_queue_get_udev: + * @udev_queue: udev queue context + * + * Retrieve the udev library context the queue context was created with. + * + * Returns: the udev library context. + **/ +_public_ struct udev *udev_queue_get_udev(struct udev_queue *udev_queue) +{ + if (udev_queue == NULL) + return NULL; + return udev_queue->udev; +} + +/** + * udev_queue_get_kernel_seqnum: + * @udev_queue: udev queue context + * + * This function is deprecated. + * + * Returns: 0. + **/ +_public_ unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue) +{ + return 0; +} + +/** + * udev_queue_get_udev_seqnum: + * @udev_queue: udev queue context + * + * This function is deprecated. + * + * Returns: 0. + **/ +_public_ unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue) +{ + return 0; +} + +/** + * udev_queue_get_udev_is_active: + * @udev_queue: udev queue context + * + * Check if udev is active on the system. + * + * Returns: a flag indicating if udev is active. + **/ +_public_ int udev_queue_get_udev_is_active(struct udev_queue *udev_queue) +{ + return access("/run/udev/control", F_OK) >= 0; +} + +/** + * udev_queue_get_queue_is_empty: + * @udev_queue: udev queue context + * + * Check if udev is currently processing any events. + * + * Returns: a flag indicating if udev is currently handling events. + **/ +_public_ int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue) +{ + return access("/run/udev/queue", F_OK) < 0; +} + +/** + * udev_queue_get_seqnum_sequence_is_finished: + * @udev_queue: udev queue context + * @start: first event sequence number + * @end: last event sequence number + * + * This function is deprecated, it just returns the result of + * udev_queue_get_queue_is_empty(). + * + * Returns: a flag indicating if udev is currently handling events. + **/ +_public_ int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue, + unsigned long long int start, unsigned long long int end) +{ + return udev_queue_get_queue_is_empty(udev_queue); +} + +/** + * udev_queue_get_seqnum_is_finished: + * @udev_queue: udev queue context + * @seqnum: sequence number + * + * This function is deprecated, it just returns the result of + * udev_queue_get_queue_is_empty(). + * + * Returns: a flag indicating if udev is currently handling events. + **/ +_public_ int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum) +{ + return udev_queue_get_queue_is_empty(udev_queue); +} + +/** + * udev_queue_get_queued_list_entry: + * @udev_queue: udev queue context + * + * This function is deprecated. + * + * Returns: NULL. + **/ +_public_ struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue) +{ + return NULL; +} + +/** + * udev_queue_get_fd: + * @udev_queue: udev queue context + * + * Returns: a file descriptor to watch for a queue to become empty. + */ +_public_ int udev_queue_get_fd(struct udev_queue *udev_queue) { + int fd; + int r; + + if (udev_queue->fd >= 0) + return udev_queue->fd; + + fd = inotify_init1(IN_CLOEXEC); + if (fd < 0) + return -errno; + + r = inotify_add_watch(fd, "/run/udev" , IN_DELETE); + if (r < 0) { + r = -errno; + close(fd); + return r; + } + + udev_queue->fd = fd; + return fd; +} + +/** + * udev_queue_flush: + * @udev_queue: udev queue context + * + * Returns: the result of clearing the watch for queue changes. + */ +_public_ int udev_queue_flush(struct udev_queue *udev_queue) { + if (udev_queue->fd < 0) + return -EINVAL; + + return flush_fd(udev_queue->fd); +} diff --git a/src/libudev/src/libudev-util.c b/src/libudev/src/libudev-util.c new file mode 100644 index 0000000000..574cfeac85 --- /dev/null +++ b/src/libudev/src/libudev-util.c @@ -0,0 +1,268 @@ +/*** + This file is part of systemd. + + Copyright 2008-2012 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "libudev.h" + +#include "MurmurHash2.h" +#include "device-nodes.h" +#include "libudev-private.h" +#include "syslog-util.h" +#include "utf8.h" + +/** + * SECTION:libudev-util + * @short_description: utils + * + * Utilities useful when dealing with devices and device node names. + */ + +/* handle "[/]" format */ +int util_resolve_subsys_kernel(struct udev *udev, const char *string, + char *result, size_t maxsize, int read_value) +{ + char temp[UTIL_PATH_SIZE]; + char *subsys; + char *sysname; + struct udev_device *dev; + char *attr; + + if (string[0] != '[') + return -1; + + strscpy(temp, sizeof(temp), string); + + subsys = &temp[1]; + + sysname = strchr(subsys, '/'); + if (sysname == NULL) + return -1; + sysname[0] = '\0'; + sysname = &sysname[1]; + + attr = strchr(sysname, ']'); + if (attr == NULL) + return -1; + attr[0] = '\0'; + attr = &attr[1]; + if (attr[0] == '/') + attr = &attr[1]; + if (attr[0] == '\0') + attr = NULL; + + if (read_value && attr == NULL) + return -1; + + dev = udev_device_new_from_subsystem_sysname(udev, subsys, sysname); + if (dev == NULL) + return -1; + + if (read_value) { + const char *val; + + val = udev_device_get_sysattr_value(dev, attr); + if (val != NULL) + strscpy(result, maxsize, val); + else + result[0] = '\0'; + log_debug("value '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); + } else { + size_t l; + char *s; + + s = result; + l = strpcpyl(&s, maxsize, udev_device_get_syspath(dev), NULL); + if (attr != NULL) + strpcpyl(&s, l, "/", attr, NULL); + log_debug("path '[%s/%s]%s' is '%s'", subsys, sysname, attr, result); + } + udev_device_unref(dev); + return 0; +} + +int util_log_priority(const char *priority) +{ + char *endptr; + int prio; + + prio = strtoul(priority, &endptr, 10); + if (endptr[0] == '\0' || isspace(endptr[0])) { + if (prio >= 0 && prio <= 7) + return prio; + else + return -ERANGE; + } + + return log_level_from_string(priority); +} + +size_t util_path_encode(const char *src, char *dest, size_t size) +{ + size_t i, j; + + for (i = 0, j = 0; src[i] != '\0'; i++) { + if (src[i] == '/') { + if (j+4 >= size) { + j = 0; + break; + } + memcpy(&dest[j], "\\x2f", 4); + j += 4; + } else if (src[i] == '\\') { + if (j+4 >= size) { + j = 0; + break; + } + memcpy(&dest[j], "\\x5c", 4); + j += 4; + } else { + if (j+1 >= size) { + j = 0; + break; + } + dest[j] = src[i]; + j++; + } + } + dest[j] = '\0'; + return j; +} + +void util_remove_trailing_chars(char *path, char c) +{ + size_t len; + + if (path == NULL) + return; + len = strlen(path); + while (len > 0 && path[len-1] == c) + path[--len] = '\0'; +} + +int util_replace_whitespace(const char *str, char *to, size_t len) +{ + size_t i, j; + + /* strip trailing whitespace */ + len = strnlen(str, len); + while (len && isspace(str[len-1])) + len--; + + /* strip leading whitespace */ + i = 0; + while ((i < len) && isspace(str[i])) + i++; + + j = 0; + while (i < len) { + /* substitute multiple whitespace with a single '_' */ + if (isspace(str[i])) { + while (isspace(str[i])) + i++; + to[j++] = '_'; + } + to[j++] = str[i++]; + } + to[j] = '\0'; + return 0; +} + +/* allow chars in whitelist, plain ascii, hex-escaping and valid utf8 */ +int util_replace_chars(char *str, const char *white) +{ + size_t i = 0; + int replaced = 0; + + while (str[i] != '\0') { + int len; + + if (whitelisted_char_for_devnode(str[i], white)) { + i++; + continue; + } + + /* accept hex encoding */ + if (str[i] == '\\' && str[i+1] == 'x') { + i += 2; + continue; + } + + /* accept valid utf8 */ + len = utf8_encoded_valid_unichar(&str[i]); + if (len > 1) { + i += len; + continue; + } + + /* if space is allowed, replace whitespace with ordinary space */ + if (isspace(str[i]) && white != NULL && strchr(white, ' ') != NULL) { + str[i] = ' '; + i++; + replaced++; + continue; + } + + /* everything else is replaced with '_' */ + str[i] = '_'; + i++; + replaced++; + } + return replaced; +} + +/** + * udev_util_encode_string: + * @str: input string to be encoded + * @str_enc: output string to store the encoded input string + * @len: maximum size of the output string, which may be + * four times as long as the input string + * + * Encode all potentially unsafe characters of a string to the + * corresponding 2 char hex value prefixed by '\x'. + * + * Returns: 0 if the entire string was copied, non-zero otherwise. + **/ +_public_ int udev_util_encode_string(const char *str, char *str_enc, size_t len) +{ + return encode_devnode_name(str, str_enc, len); +} + +unsigned int util_string_hash32(const char *str) +{ + return MurmurHash2(str, strlen(str), 0); +} + +/* get a bunch of bit numbers out of the hash, and set the bits in our bit field */ +uint64_t util_string_bloom64(const char *str) +{ + uint64_t bits = 0; + unsigned int hash = util_string_hash32(str); + + bits |= 1LLU << (hash & 63); + bits |= 1LLU << ((hash >> 6) & 63); + bits |= 1LLU << ((hash >> 12) & 63); + bits |= 1LLU << ((hash >> 18) & 63); + return bits; +} diff --git a/src/libudev/src/libudev.c b/src/libudev/src/libudev.c new file mode 100644 index 0000000000..63fb05547d --- /dev/null +++ b/src/libudev/src/libudev.c @@ -0,0 +1,253 @@ +/*** + This file is part of systemd. + + Copyright 2008-2014 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include "libudev.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "libudev-private.h" +#include "missing.h" +#include "string-util.h" + +/** + * SECTION:libudev + * @short_description: libudev context + * + * The context contains the default values read from the udev config file, + * and is passed to all library operations. + */ + +/** + * udev: + * + * Opaque object representing the library context. + */ +struct udev { + int refcount; + void (*log_fn)(struct udev *udev, + int priority, const char *file, int line, const char *fn, + const char *format, va_list args); + void *userdata; +}; + +/** + * udev_get_userdata: + * @udev: udev library context + * + * Retrieve stored data pointer from library context. This might be useful + * to access from callbacks. + * + * Returns: stored userdata + **/ +_public_ void *udev_get_userdata(struct udev *udev) { + if (udev == NULL) + return NULL; + return udev->userdata; +} + +/** + * udev_set_userdata: + * @udev: udev library context + * @userdata: data pointer + * + * Store custom @userdata in the library context. + **/ +_public_ void udev_set_userdata(struct udev *udev, void *userdata) { + if (udev == NULL) + return; + udev->userdata = userdata; +} + +/** + * udev_new: + * + * Create udev library context. This reads the udev configuration + * file, and fills in the default values. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the udev library context. + * + * Returns: a new udev library context + **/ +_public_ struct udev *udev_new(void) { + struct udev *udev; + _cleanup_fclose_ FILE *f = NULL; + + udev = new0(struct udev, 1); + if (udev == NULL) + return NULL; + udev->refcount = 1; + + f = fopen("/etc/udev/udev.conf", "re"); + if (f != NULL) { + char line[UTIL_LINE_SIZE]; + unsigned line_nr = 0; + + while (fgets(line, sizeof(line), f)) { + size_t len; + char *key; + char *val; + + line_nr++; + + /* find key */ + key = line; + while (isspace(key[0])) + key++; + + /* comment or empty line */ + if (key[0] == '#' || key[0] == '\0') + continue; + + /* split key/value */ + val = strchr(key, '='); + if (val == NULL) { + log_debug("/etc/udev/udev.conf:%u: missing assignment, skipping line.", line_nr); + continue; + } + val[0] = '\0'; + val++; + + /* find value */ + while (isspace(val[0])) + val++; + + /* terminate key */ + len = strlen(key); + if (len == 0) + continue; + while (isspace(key[len-1])) + len--; + key[len] = '\0'; + + /* terminate value */ + len = strlen(val); + if (len == 0) + continue; + while (isspace(val[len-1])) + len--; + val[len] = '\0'; + + if (len == 0) + continue; + + /* unquote */ + if (val[0] == '"' || val[0] == '\'') { + if (val[len-1] != val[0]) { + log_debug("/etc/udev/udev.conf:%u: inconsistent quoting, skipping line.", line_nr); + continue; + } + val[len-1] = '\0'; + val++; + } + + if (streq(key, "udev_log")) { + int prio; + + prio = util_log_priority(val); + if (prio < 0) + log_debug("/etc/udev/udev.conf:%u: invalid log level '%s', ignoring.", line_nr, val); + else + log_set_max_level(prio); + continue; + } + } + } + + return udev; +} + +/** + * udev_ref: + * @udev: udev library context + * + * Take a reference of the udev library context. + * + * Returns: the passed udev library context + **/ +_public_ struct udev *udev_ref(struct udev *udev) { + if (udev == NULL) + return NULL; + udev->refcount++; + return udev; +} + +/** + * udev_unref: + * @udev: udev library context + * + * Drop a reference of the udev library context. If the refcount + * reaches zero, the resources of the context will be released. + * + * Returns: the passed udev library context if it has still an active reference, or #NULL otherwise. + **/ +_public_ struct udev *udev_unref(struct udev *udev) { + if (udev == NULL) + return NULL; + udev->refcount--; + if (udev->refcount > 0) + return udev; + free(udev); + return NULL; +} + +/** + * udev_set_log_fn: + * @udev: udev library context + * @log_fn: function to be called for log messages + * + * This function is deprecated. + * + **/ +_public_ void udev_set_log_fn(struct udev *udev, + void (*log_fn)(struct udev *udev, + int priority, const char *file, int line, const char *fn, + const char *format, va_list args)) { + return; +} + +/** + * udev_get_log_priority: + * @udev: udev library context + * + * This function is deprecated. + * + **/ +_public_ int udev_get_log_priority(struct udev *udev) { + return log_get_max_level(); +} + +/** + * udev_set_log_priority: + * @udev: udev library context + * @priority: the new log priority + * + * This function is deprecated. + * + **/ +_public_ void udev_set_log_priority(struct udev *udev, int priority) { + log_set_max_level(priority); +} diff --git a/src/libudev/src/libudev.pc.in b/src/libudev/src/libudev.pc.in new file mode 100644 index 0000000000..a0f3f524e0 --- /dev/null +++ b/src/libudev/src/libudev.pc.in @@ -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. + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libudev +Description: Library to access udev device information +Version: @VERSION@ +Libs: -L${libdir} -ludev +Cflags: -I${includedir} diff --git a/src/libudev/src/libudev.sym b/src/libudev/src/libudev.sym new file mode 100644 index 0000000000..76726fca77 --- /dev/null +++ b/src/libudev/src/libudev.sym @@ -0,0 +1,120 @@ +/*** + 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. +***/ + +LIBUDEV_183 { +global: + udev_device_get_action; + udev_device_get_devlinks_list_entry; + udev_device_get_devnode; + udev_device_get_devnum; + udev_device_get_devpath; + udev_device_get_devtype; + udev_device_get_driver; + udev_device_get_is_initialized; + udev_device_get_parent; + udev_device_get_parent_with_subsystem_devtype; + udev_device_get_properties_list_entry; + udev_device_get_property_value; + udev_device_get_seqnum; + udev_device_get_subsystem; + udev_device_get_sysattr_list_entry; + udev_device_get_sysattr_value; + udev_device_get_sysname; + udev_device_get_sysnum; + udev_device_get_syspath; + udev_device_get_tags_list_entry; + udev_device_get_udev; + udev_device_get_usec_since_initialized; + udev_device_has_tag; + udev_device_new_from_devnum; + udev_device_new_from_environment; + udev_device_new_from_subsystem_sysname; + udev_device_new_from_syspath; + udev_device_ref; + udev_device_unref; + udev_enumerate_add_match_is_initialized; + udev_enumerate_add_match_parent; + udev_enumerate_add_match_property; + udev_enumerate_add_match_subsystem; + udev_enumerate_add_match_sysattr; + udev_enumerate_add_match_sysname; + udev_enumerate_add_match_tag; + udev_enumerate_add_nomatch_subsystem; + udev_enumerate_add_nomatch_sysattr; + udev_enumerate_add_syspath; + udev_enumerate_get_list_entry; + udev_enumerate_get_udev; + udev_enumerate_new; + udev_enumerate_ref; + udev_enumerate_scan_devices; + udev_enumerate_scan_subsystems; + udev_enumerate_unref; + udev_get_log_priority; + udev_get_userdata; + udev_list_entry_get_by_name; + udev_list_entry_get_name; + udev_list_entry_get_next; + udev_list_entry_get_value; + udev_monitor_enable_receiving; + udev_monitor_filter_add_match_subsystem_devtype; + udev_monitor_filter_add_match_tag; + udev_monitor_filter_remove; + udev_monitor_filter_update; + udev_monitor_get_fd; + udev_monitor_get_udev; + udev_monitor_new_from_netlink; + udev_monitor_receive_device; + udev_monitor_ref; + udev_monitor_set_receive_buffer_size; + udev_monitor_unref; + udev_new; + udev_queue_get_kernel_seqnum; + udev_queue_get_queue_is_empty; + udev_queue_get_queued_list_entry; + udev_queue_get_seqnum_is_finished; + udev_queue_get_seqnum_sequence_is_finished; + udev_queue_get_udev; + udev_queue_get_udev_is_active; + udev_queue_get_udev_seqnum; + udev_queue_new; + udev_queue_ref; + udev_queue_unref; + udev_ref; + udev_set_log_fn; + udev_set_log_priority; + udev_set_userdata; + udev_unref; + udev_util_encode_string; +local: + *; +}; + +LIBUDEV_189 { +global: + udev_device_new_from_device_id; +} LIBUDEV_183; + +LIBUDEV_196 { +global: + udev_hwdb_new; + udev_hwdb_ref; + udev_hwdb_unref; + udev_hwdb_get_properties_list_entry; +} LIBUDEV_189; + +LIBUDEV_199 { +global: + udev_device_set_sysattr_value; +} LIBUDEV_196; + +LIBUDEV_215 { +global: + udev_queue_flush; + udev_queue_get_fd; +} LIBUDEV_199; diff --git a/src/locale/Makefile b/src/locale/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/locale/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/locale/Makefile b/src/locale/Makefile new file mode 100644 index 0000000000..0e6746dbdb --- /dev/null +++ b/src/locale/Makefile @@ -0,0 +1,90 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_LOCALED),) +systemd_localed_SOURCES = \ + src/locale/localed.c + +systemd_localed_LDADD = \ + libshared.la \ + $(XKBCOMMON_LIBS) + +systemd_localed_CFLAGS = \ + $(AM_CFLAGS) \ + $(XKBCOMMON_CFLAGS) + +nodist_systemunit_DATA += \ + units/systemd-localed.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.locale1.busname + +rootlibexec_PROGRAMS += \ + systemd-localed + +dist_dbuspolicy_DATA += \ + src/locale/org.freedesktop.locale1.conf + +dist_dbussystemservice_DATA += \ + src/locale/org.freedesktop.locale1.service + +polkitpolicy_files += \ + src/locale/org.freedesktop.locale1.policy + +SYSTEM_UNIT_ALIASES += \ + systemd-localed.service dbus-org.freedesktop.locale1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.locale1.busname + +dist_pkgdata_DATA = \ + src/locale/kbd-model-map \ + src/locale/language-fallback-map + +localectl_SOURCES = \ + src/locale/localectl.c + +localectl_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + localectl + +dist_bashcompletion_data += \ + shell-completion/bash/localectl + +dist_zshcompletion_data += \ + shell-completion/zsh/_localectl +endif + +.PHONY: update-kbd-model-map + +polkitpolicy_in_files += \ + src/locale/org.freedesktop.locale1.policy.in + +EXTRA_DIST += \ + units/systemd-localed.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/locale/localectl.c b/src/locale/localectl.c index 365c79aa51..c684ff7d90 100644 --- a/src/locale/localectl.c +++ b/src/locale/localectl.c @@ -25,7 +25,7 @@ #include #include -#include "sd-bus.h" +#include #include "bus-error.h" #include "bus-util.h" diff --git a/src/locale/localed.c b/src/locale/localed.c index f0fe59cc67..eb6c0565f0 100644 --- a/src/locale/localed.c +++ b/src/locale/localed.c @@ -26,7 +26,7 @@ #include #endif -#include "sd-bus.h" +#include #include "alloc-util.h" #include "bus-error.h" diff --git a/src/login/Makefile b/src/login/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/login/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/login/Makefile b/src/login/Makefile new file mode 100644 index 0000000000..5923401112 --- /dev/null +++ b/src/login/Makefile @@ -0,0 +1,243 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_LOGIND),) +systemd_logind_SOURCES = \ + src/login/logind.c \ + src/login/logind.h + +nodist_systemd_logind_SOURCES = \ + src/login/logind-gperf.c + +systemd_logind_LDADD = \ + liblogind-core.la + +liblogind_core_la_SOURCES = \ + src/login/logind-core.c \ + src/login/logind-device.c \ + src/login/logind-device.h \ + src/login/logind-button.c \ + src/login/logind-button.h \ + src/login/logind-action.c \ + src/login/logind-action.h \ + src/login/logind-seat.c \ + src/login/logind-seat.h \ + src/login/logind-session.c \ + src/login/logind-session.h \ + src/login/logind-session-device.c \ + src/login/logind-session-device.h \ + src/login/logind-user.c \ + src/login/logind-user.h \ + src/login/logind-inhibit.c \ + src/login/logind-inhibit.h \ + src/login/logind-dbus.c \ + src/login/logind-session-dbus.c \ + src/login/logind-seat-dbus.c \ + src/login/logind-user-dbus.c \ + src/login/logind-utmp.c \ + src/login/logind-acl.h + +liblogind_core_la_LIBADD = \ + libshared.la + +ifneq ($(HAVE_ACL),) +liblogind_core_la_SOURCES += \ + src/login/logind-acl.c +endif + +noinst_LTLIBRARIES += \ + liblogind-core.la + +rootlibexec_PROGRAMS += \ + systemd-logind + +loginctl_SOURCES = \ + src/login/loginctl.c \ + src/login/sysfs-show.h \ + src/login/sysfs-show.c + +loginctl_LDADD = \ + libshared.la + +rootbin_PROGRAMS += \ + loginctl + +dist_bashcompletion_data += \ + shell-completion/bash/loginctl + +dist_zshcompletion_data += \ + shell-completion/zsh/_loginctl \ + shell-completion/zsh/_systemd-inhibit + +systemd_inhibit_SOURCES = \ + src/login/inhibit.c + +systemd_inhibit_LDADD = \ + libshared.la + +rootbin_PROGRAMS += \ + systemd-inhibit + +test_login_SOURCES = \ + src/libsystemd/sd-login/test-login.c + +test_login_LDADD = \ + libshared.la + +test_login_shared_SOURCES = \ + src/login/test-login-shared.c + +test_login_shared_LDADD = \ + libshared.la + +test_inhibit_SOURCES = \ + src/login/test-inhibit.c + +test_inhibit_LDADD = \ + libshared.la + +test_login_tables_SOURCES = \ + src/login/test-login-tables.c + +test_login_tables_LDADD = \ + liblogind-core.la + +manual_tests += \ + test-login \ + test-inhibit + +tests += \ + test-login-tables \ + test-login-shared + +ifneq ($(HAVE_PAM),) +pam_systemd_la_SOURCES = \ + src/login/pam_systemd.sym \ + src/login/pam_systemd.c + +pam_systemd_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(PAM_CFLAGS) + +pam_systemd_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -Wl,--version-script=$(top_srcdir)/src/login/pam_systemd.sym + +pam_systemd_la_LIBADD = \ + libshared.la \ + $(PAM_LIBS) + +pamlib_LTLIBRARIES = \ + pam_systemd.la + +dist_pamconf_DATA = \ + src/login/systemd-user + +EXTRA_DIST += \ + src/login/systemd-user.m4 +endif + +nodist_systemunit_DATA += \ + units/systemd-logind.service + +dist_systemunit_DATA += \ + units/user.slice + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.login1.busname + +dist_dbussystemservice_DATA += \ + src/login/org.freedesktop.login1.service + +dist_dbuspolicy_DATA += \ + src/login/org.freedesktop.login1.conf + +dist_pkgsysconf_DATA += \ + src/login/logind.conf + +polkitpolicy_files += \ + src/login/org.freedesktop.login1.policy + +INSTALL_DIRS += \ + $(systemdstatedir) + +MULTI_USER_TARGET_WANTS += \ + systemd-logind.service + +SYSTEM_UNIT_ALIASES += \ + systemd-logind.service dbus-org.freedesktop.login1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.login1.busname + +dist_udevrules_DATA += \ + src/login/70-uaccess.rules \ + src/login/70-power-switch.rules + +nodist_udevrules_DATA += \ + src/login/71-seat.rules \ + src/login/73-seat-late.rules + +endif + +polkitpolicy_in_files += \ + src/login/org.freedesktop.login1.policy.in + +gperf_gperf_sources += \ + src/login/logind-gperf.gperf + +EXTRA_DIST += \ + src/login/71-seat.rules.in \ + src/login/73-seat-late.rules.in \ + units/systemd-logind.service.in + +# ------------------------------------------------------------------------------ +ifneq ($(HAVE_PAM),) + +systemd_user_sessions_SOURCES = \ + src/user-sessions/user-sessions.c + +systemd_user_sessions_LDADD = \ + libshared.la + +rootlibexec_PROGRAMS += \ + systemd-user-sessions + +nodist_systemunit_DATA += \ + units/systemd-user-sessions.service + +MULTI_USER_TARGET_WANTS += \ + systemd-user-sessions.service + +endif + +EXTRA_DIST += \ + units/systemd-user-sessions.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/login/inhibit.c b/src/login/inhibit.c index f2c37a8623..905e757a32 100644 --- a/src/login/inhibit.c +++ b/src/login/inhibit.c @@ -23,7 +23,7 @@ #include #include -#include "sd-bus.h" +#include #include "alloc-util.h" #include "bus-error.h" diff --git a/src/login/loginctl.c b/src/login/loginctl.c index 6ad3d089bd..fd8dcdc22c 100644 --- a/src/login/loginctl.c +++ b/src/login/loginctl.c @@ -23,7 +23,7 @@ #include #include -#include "sd-bus.h" +#include #include "alloc-util.h" #include "bus-error.h" diff --git a/src/login/logind-button.c b/src/login/logind-button.c index baa6b7113c..acf7504a2e 100644 --- a/src/login/logind-button.c +++ b/src/login/logind-button.c @@ -24,7 +24,7 @@ #include #include -#include "sd-messages.h" +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/login/logind-dbus.c b/src/login/logind-dbus.c index 1d3133ee25..efcc386dce 100644 --- a/src/login/logind-dbus.c +++ b/src/login/logind-dbus.c @@ -22,7 +22,7 @@ #include #include -#include "sd-messages.h" +#include #include "alloc-util.h" #include "audit-util.h" diff --git a/src/login/logind-seat.c b/src/login/logind-seat.c index b5192320e4..aec67718a1 100644 --- a/src/login/logind-seat.c +++ b/src/login/logind-seat.c @@ -22,7 +22,7 @@ #include #include -#include "sd-messages.h" +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/login/logind-session.c b/src/login/logind-session.c index 417b7f5d98..b22420deea 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -26,7 +26,7 @@ #include #include -#include "sd-messages.h" +#include #include "alloc-util.h" #include "audit-util.h" diff --git a/src/login/logind-utmp.c b/src/login/logind-utmp.c index 11a91c3947..5106821645 100644 --- a/src/login/logind-utmp.c +++ b/src/login/logind-utmp.c @@ -22,7 +22,7 @@ #include #include -#include "sd-messages.h" +#include #include "alloc-util.h" #include "audit-util.h" diff --git a/src/login/logind.c b/src/login/logind.c index 933602eb08..34d0c04a2a 100644 --- a/src/login/logind.c +++ b/src/login/logind.c @@ -23,7 +23,7 @@ #include #include "libudev.h" -#include "sd-daemon.h" +#include #include "alloc-util.h" #include "bus-error.h" diff --git a/src/login/logind.h b/src/login/logind.h index 6748af3c07..c642d56413 100644 --- a/src/login/logind.h +++ b/src/login/logind.h @@ -22,8 +22,8 @@ #include #include "libudev.h" -#include "sd-bus.h" -#include "sd-event.h" +#include +#include #include "hashmap.h" #include "list.h" diff --git a/src/login/test-inhibit.c b/src/login/test-inhibit.c index a3cf9d293b..be7145b4bc 100644 --- a/src/login/test-inhibit.c +++ b/src/login/test-inhibit.c @@ -19,7 +19,7 @@ #include -#include "sd-bus.h" +#include #include "bus-util.h" #include "fd-util.h" diff --git a/src/machine-id-setup/Makefile b/src/machine-id-setup/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/machine-id-setup/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/machine-id-setup/Makefile b/src/machine-id-setup/Makefile new file mode 100644 index 0000000000..125471d17a --- /dev/null +++ b/src/machine-id-setup/Makefile @@ -0,0 +1,37 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_machine_id_setup_SOURCES = \ + src/machine-id-setup/machine-id-setup-main.c \ + src/core/machine-id-setup.c \ + src/core/machine-id-setup.h + +systemd_machine_id_setup_LDADD = \ + libshared.la + +SYSINIT_TARGET_WANTS += \ + systemd-machine-id-commit.service + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/machine/.gitignore b/src/machine/.gitignore deleted file mode 100644 index e1065b5894..0000000000 --- a/src/machine/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/org.freedesktop.machine1.policy diff --git a/src/machine/Makefile b/src/machine/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/machine/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c deleted file mode 100644 index 73f5112c4d..0000000000 --- a/src/machine/image-dbus.c +++ /dev/null @@ -1,361 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "alloc-util.h" -#include "bus-label.h" -#include "bus-util.h" -#include "image-dbus.h" -#include "io-util.h" -#include "machine-image.h" -#include "strv.h" -#include "user-util.h" - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType); - -int bus_image_method_remove( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - Image *image = userdata; - Manager *m = image->userdata; - int r; - - assert(message); - assert(image); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = image_remove(image); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_image_method_rename( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - Image *image = userdata; - Manager *m = image->userdata; - const char *new_name; - int r; - - assert(message); - assert(image); - - r = sd_bus_message_read(message, "s", &new_name); - if (r < 0) - return r; - - if (!image_name_is_valid(new_name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = image_rename(image, new_name); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_image_method_clone( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - Image *image = userdata; - Manager *m = image->userdata; - const char *new_name; - int r, read_only; - - assert(message); - assert(image); - - r = sd_bus_message_read(message, "sb", &new_name, &read_only); - if (r < 0) - return r; - - if (!image_name_is_valid(new_name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = image_clone(image, new_name, read_only); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_image_method_mark_read_only( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - Image *image = userdata; - Manager *m = image->userdata; - int r, read_only; - - assert(message); - - r = sd_bus_message_read(message, "b", &read_only); - if (r < 0) - return r; - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = image_read_only(image, read_only); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_image_method_set_limit( - sd_bus_message *message, - void *userdata, - sd_bus_error *error) { - - Image *image = userdata; - Manager *m = image->userdata; - uint64_t limit; - int r; - - assert(message); - - r = sd_bus_message_read(message, "t", &limit); - if (r < 0) - return r; - if (!FILE_SIZE_VALID_OR_INFINITY(limit)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range"); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-images", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = image_set_limit(image, limit); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -const sd_bus_vtable image_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0), - SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0), - SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0), - SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0), - SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0), - SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0), - SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0), - SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0), - SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0), - SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0), - SD_BUS_METHOD("Remove", NULL, NULL, bus_image_method_remove, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Rename", "s", NULL, bus_image_method_rename, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Clone", "sb", NULL, bus_image_method_clone, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MarkReadOnly", "b", NULL, bus_image_method_mark_read_only, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetLimit", "t", NULL, bus_image_method_set_limit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END -}; - -static int image_flush_cache(sd_event_source *s, void *userdata) { - Manager *m = userdata; - Image *i; - - assert(s); - assert(m); - - while ((i = hashmap_steal_first(m->image_cache))) - image_unref(i); - - return 0; -} - -int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - _cleanup_free_ char *e = NULL; - Manager *m = userdata; - Image *image = NULL; - const char *p; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - - p = startswith(path, "/org/freedesktop/machine1/image/"); - if (!p) - return 0; - - e = bus_label_unescape(p); - if (!e) - return -ENOMEM; - - image = hashmap_get(m->image_cache, e); - if (image) { - *found = image; - return 1; - } - - r = hashmap_ensure_allocated(&m->image_cache, &string_hash_ops); - if (r < 0) - return r; - - if (!m->image_cache_defer_event) { - r = sd_event_add_defer(m->event, &m->image_cache_defer_event, image_flush_cache, m); - if (r < 0) - return r; - - r = sd_event_source_set_priority(m->image_cache_defer_event, SD_EVENT_PRIORITY_IDLE); - if (r < 0) - return r; - } - - r = sd_event_source_set_enabled(m->image_cache_defer_event, SD_EVENT_ONESHOT); - if (r < 0) - return r; - - r = image_find(e, &image); - if (r <= 0) - return r; - - image->userdata = m; - - r = hashmap_put(m->image_cache, image->name, image); - if (r < 0) { - image_unref(image); - return r; - } - - *found = image; - return 1; -} - -char *image_bus_path(const char *name) { - _cleanup_free_ char *e = NULL; - - assert(name); - - e = bus_label_escape(name); - if (!e) - return NULL; - - return strappend("/org/freedesktop/machine1/image/", e); -} - -int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - _cleanup_(image_hashmap_freep) Hashmap *images = NULL; - _cleanup_strv_free_ char **l = NULL; - Image *image; - Iterator i; - int r; - - assert(bus); - assert(path); - assert(nodes); - - images = hashmap_new(&string_hash_ops); - if (!images) - return -ENOMEM; - - r = image_discover(images); - if (r < 0) - return r; - - HASHMAP_FOREACH(image, images, i) { - char *p; - - p = image_bus_path(image->name); - if (!p) - return -ENOMEM; - - r = strv_consume(&l, p); - if (r < 0) - return r; - } - - *nodes = l; - l = NULL; - - return 1; -} diff --git a/src/machine/image-dbus.h b/src/machine/image-dbus.h deleted file mode 100644 index b62da996c6..0000000000 --- a/src/machine/image-dbus.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "machined.h" - -extern const sd_bus_vtable image_vtable[]; - -char *image_bus_path(const char *name); - -int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); -int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); - -int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_image_method_rename(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_image_method_clone(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/machine/machine-dbus.c b/src/machine/machine-dbus.c deleted file mode 100644 index 71f20b3f07..0000000000 --- a/src/machine/machine-dbus.c +++ /dev/null @@ -1,1435 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include - -/* When we include libgen.h because we need dirname() we immediately - * undefine basename() since libgen.h defines it as a macro to the POSIX - * version which is really broken. We prefer GNU basename(). */ -#include -#undef basename - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-internal.h" -#include "bus-label.h" -#include "bus-util.h" -#include "copy.h" -#include "env-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "in-addr-util.h" -#include "local-addresses.h" -#include "machine-dbus.h" -#include "machine.h" -#include "mkdir.h" -#include "path-util.h" -#include "process-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "user-util.h" - -static int property_get_id( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Machine *m = userdata; - - assert(bus); - assert(reply); - assert(m); - - return sd_bus_message_append_array(reply, 'y', &m->id, 16); -} - -static int property_get_state( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Machine *m = userdata; - const char *state; - int r; - - assert(bus); - assert(reply); - assert(m); - - state = machine_state_to_string(machine_get_state(m)); - - r = sd_bus_message_append_basic(reply, 's', state); - if (r < 0) - return r; - - return 1; -} - -static int property_get_netif( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - Machine *m = userdata; - - assert(bus); - assert(reply); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - return sd_bus_message_append_array(reply, 'i', m->netif, m->n_netif * sizeof(int)); -} - -static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_class, machine_class, MachineClass); - -int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Machine *m = userdata; - int r; - - assert(message); - assert(m); - - r = bus_verify_polkit_async( - message, - CAP_KILL, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = machine_stop(m); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Machine *m = userdata; - const char *swho; - int32_t signo; - KillWho who; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "si", &swho, &signo); - if (r < 0) - return r; - - if (isempty(swho)) - who = KILL_ALL; - else { - who = kill_who_from_string(swho); - if (who < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid kill parameter '%s'", swho); - } - - if (signo <= 0 || signo >= _NSIG) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid signal %i", signo); - - r = bus_verify_polkit_async( - message, - CAP_KILL, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - r = machine_kill(m, who, signo); - if (r < 0) - return r; - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Machine *m = userdata; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(iay)"); - if (r < 0) - return r; - - switch (m->class) { - - case MACHINE_HOST: { - _cleanup_free_ struct local_address *addresses = NULL; - struct local_address *a; - int n, i; - - n = local_addresses(NULL, 0, AF_UNSPEC, &addresses); - if (n < 0) - return n; - - for (a = addresses, i = 0; i < n; a++, i++) { - - r = sd_bus_message_open_container(reply, 'r', "iay"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "i", addresses[i].family); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &addresses[i].address, FAMILY_ADDRESS_SIZE(addresses[i].family)); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - } - - break; - } - - case MACHINE_CONTAINER: { - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_free_ char *us = NULL, *them = NULL; - _cleanup_close_ int netns_fd = -1; - const char *p; - siginfo_t si; - pid_t child; - - r = readlink_malloc("/proc/self/ns/net", &us); - if (r < 0) - return r; - - p = procfs_file_alloca(m->leader, "ns/net"); - r = readlink_malloc(p, &them); - if (r < 0) - return r; - - if (streq(us, them)) - return sd_bus_error_setf(error, BUS_ERROR_NO_PRIVATE_NETWORKING, "Machine %s does not use private networking", m->name); - - r = namespace_open(m->leader, NULL, NULL, &netns_fd, NULL, NULL); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - - if (child == 0) { - _cleanup_free_ struct local_address *addresses = NULL; - struct local_address *a; - int i, n; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(-1, -1, netns_fd, -1, -1); - if (r < 0) - _exit(EXIT_FAILURE); - - n = local_addresses(NULL, 0, AF_UNSPEC, &addresses); - if (n < 0) - _exit(EXIT_FAILURE); - - for (a = addresses, i = 0; i < n; a++, i++) { - struct iovec iov[2] = { - { .iov_base = &a->family, .iov_len = sizeof(a->family) }, - { .iov_base = &a->address, .iov_len = FAMILY_ADDRESS_SIZE(a->family) }, - }; - - r = writev(pair[1], iov, 2); - if (r < 0) - _exit(EXIT_FAILURE); - } - - pair[1] = safe_close(pair[1]); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - for (;;) { - int family; - ssize_t n; - union in_addr_union in_addr; - struct iovec iov[2]; - struct msghdr mh = { - .msg_iov = iov, - .msg_iovlen = 2, - }; - - iov[0] = (struct iovec) { .iov_base = &family, .iov_len = sizeof(family) }; - iov[1] = (struct iovec) { .iov_base = &in_addr, .iov_len = sizeof(in_addr) }; - - n = recvmsg(pair[0], &mh, 0); - if (n < 0) - return -errno; - if ((size_t) n < sizeof(family)) - break; - - r = sd_bus_message_open_container(reply, 'r', "iay"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "i", family); - if (r < 0) - return r; - - switch (family) { - - case AF_INET: - if (n != sizeof(struct in_addr) + sizeof(family)) - return -EIO; - - r = sd_bus_message_append_array(reply, 'y', &in_addr.in, sizeof(in_addr.in)); - break; - - case AF_INET6: - if (n != sizeof(struct in6_addr) + sizeof(family)) - return -EIO; - - r = sd_bus_message_append_array(reply, 'y', &in_addr.in6, sizeof(in_addr.in6)); - break; - } - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - } - - r = wait_for_terminate(child, &si); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - break; - } - - default: - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting IP address data is only supported on container machines."); - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_strv_free_ char **l = NULL; - Machine *m = userdata; - char **k, **v; - int r; - - assert(message); - assert(m); - - switch (m->class) { - - case MACHINE_HOST: - r = load_env_file_pairs(NULL, "/etc/os-release", NULL, &l); - if (r < 0) - return r; - - break; - - case MACHINE_CONTAINER: { - _cleanup_close_ int mntns_fd = -1, root_fd = -1; - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_fclose_ FILE *f = NULL; - siginfo_t si; - pid_t child; - - r = namespace_open(m->leader, NULL, &mntns_fd, NULL, NULL, &root_fd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - - if (child == 0) { - _cleanup_close_ int fd = -1; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(-1, mntns_fd, -1, -1, root_fd); - if (r < 0) - _exit(EXIT_FAILURE); - - fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC); - if (fd < 0) { - fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC); - if (fd < 0) - _exit(EXIT_FAILURE); - } - - r = copy_bytes(fd, pair[1], (uint64_t) -1, false); - if (r < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - f = fdopen(pair[0], "re"); - if (!f) - return -errno; - - pair[0] = -1; - - r = load_env_file_pairs(f, "/etc/os-release", NULL, &l); - if (r < 0) - return r; - - r = wait_for_terminate(child, &si); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); - if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - - break; - } - - default: - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting OS release data is only supported on container machines."); - } - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "{ss}"); - if (r < 0) - return r; - - STRV_FOREACH_PAIR(k, v, l) { - r = sd_bus_message_append(reply, "{ss}", *k, *v); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *pty_name = NULL; - _cleanup_close_ int master = -1; - Machine *m = userdata; - int r; - - assert(message); - assert(m); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-open-pty" : "org.freedesktop.machine1.open-pty", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (master < 0) - return master; - - r = ptsname_namespace(master, &pty_name); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "hs", master, pty_name); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -static int container_bus_new(Machine *m, sd_bus_error *error, sd_bus **ret) { - int r; - - assert(m); - assert(ret); - - switch (m->class) { - - case MACHINE_HOST: - *ret = NULL; - break; - - case MACHINE_CONTAINER: { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - char *address; - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - if (asprintf(&address, "x-machine-kernel:pid=%1$" PID_PRI ";x-machine-unix:pid=%1$" PID_PRI, m->leader) < 0) - return -ENOMEM; - - bus->address = address; - bus->bus_client = true; - bus->trusted = false; - bus->is_system = true; - - r = sd_bus_start(bus); - if (r == -ENOENT) - return sd_bus_error_set_errnof(error, r, "There is no system bus in container %s.", m->name); - if (r < 0) - return r; - - *ret = bus; - bus = NULL; - break; - } - - default: - return -EOPNOTSUPP; - } - - return 0; -} - -int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *pty_name = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; - _cleanup_close_ int master = -1; - sd_bus *container_bus = NULL; - Machine *m = userdata; - const char *p, *getty; - int r; - - assert(message); - assert(m); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-login" : "org.freedesktop.machine1.login", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (master < 0) - return master; - - r = ptsname_namespace(master, &pty_name); - if (r < 0) - return r; - - p = path_startswith(pty_name, "/dev/pts/"); - if (!p) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "PTS name %s is invalid", pty_name); - - r = container_bus_new(m, error, &allocated_bus); - if (r < 0) - return r; - - container_bus = allocated_bus ?: m->manager->bus; - - getty = strjoina("container-getty@", p, ".service"); - - r = sd_bus_call_method( - container_bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartUnit", - error, NULL, - "ss", getty, "replace"); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "hs", master, pty_name); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *tm = NULL; - _cleanup_free_ char *pty_name = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; - sd_bus *container_bus = NULL; - _cleanup_close_ int master = -1, slave = -1; - _cleanup_strv_free_ char **env = NULL, **args = NULL; - Machine *m = userdata; - const char *p, *unit, *user, *path, *description, *utmp_id; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "ss", &user, &path); - if (r < 0) - return r; - if (isempty(user)) - user = NULL; - if (isempty(path)) - path = "/bin/sh"; - if (!path_is_absolute(path)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified path '%s' is not absolute", path); - - r = sd_bus_message_read_strv(message, &args); - if (r < 0) - return r; - if (strv_isempty(args)) { - args = strv_free(args); - - args = strv_new(path, NULL); - if (!args) - return -ENOMEM; - - args[0][0] = '-'; /* Tell /bin/sh that this shall be a login shell */ - } - - r = sd_bus_message_read_strv(message, &env); - if (r < 0) - return r; - if (!strv_env_is_valid(env)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-shell" : "org.freedesktop.machine1.shell", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (master < 0) - return master; - - r = ptsname_namespace(master, &pty_name); - if (r < 0) - return r; - - p = path_startswith(pty_name, "/dev/pts/"); - assert(p); - - slave = machine_open_terminal(m, pty_name, O_RDWR|O_NOCTTY|O_CLOEXEC); - if (slave < 0) - return slave; - - utmp_id = path_startswith(pty_name, "/dev/"); - assert(utmp_id); - - r = container_bus_new(m, error, &allocated_bus); - if (r < 0) - return r; - - container_bus = allocated_bus ?: m->manager->bus; - - r = sd_bus_message_new_method_call( - container_bus, - &tm, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartTransientUnit"); - if (r < 0) - return r; - - /* Name and mode */ - unit = strjoina("container-shell@", p, ".service", NULL); - r = sd_bus_message_append(tm, "ss", unit, "fail"); - if (r < 0) - return r; - - /* Properties */ - r = sd_bus_message_open_container(tm, 'a', "(sv)"); - if (r < 0) - return r; - - description = strjoina("Shell for User ", isempty(user) ? "root" : user); - r = sd_bus_message_append(tm, - "(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)", - "Description", "s", description, - "StandardInputFileDescriptor", "h", slave, - "StandardOutputFileDescriptor", "h", slave, - "StandardErrorFileDescriptor", "h", slave, - "SendSIGHUP", "b", true, - "IgnoreSIGPIPE", "b", false, - "KillMode", "s", "mixed", - "TTYReset", "b", true, - "UtmpIdentifier", "s", utmp_id, - "UtmpMode", "s", "user", - "PAMName", "s", "login", - "WorkingDirectory", "s", "-~"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "(sv)", "User", "s", isempty(user) ? "root" : user); - if (r < 0) - return r; - - if (!strv_isempty(env)) { - r = sd_bus_message_open_container(tm, 'r', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "s", "Environment"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'v', "as"); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(tm, env); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - } - - /* Exec container */ - r = sd_bus_message_open_container(tm, 'r', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "s", "ExecStart"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'v', "a(sasb)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'a', "(sasb)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(tm, 'r', "sasb"); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "s", path); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(tm, args); - if (r < 0) - return r; - - r = sd_bus_message_append(tm, "b", true); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - r = sd_bus_message_close_container(tm); - if (r < 0) - return r; - - /* Auxiliary units */ - r = sd_bus_message_append(tm, "a(sa(sv))", 0); - if (r < 0) - return r; - - r = sd_bus_call(container_bus, tm, 0, error, NULL); - if (r < 0) - return r; - - slave = safe_close(slave); - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "hs", master, pty_name); - if (r < 0) - return r; - - return sd_bus_send(NULL, reply, NULL); -} - -int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; - char mount_slave[] = "/tmp/propagate.XXXXXX", *mount_tmp, *mount_outside, *p; - bool mount_slave_created = false, mount_slave_mounted = false, - mount_tmp_created = false, mount_tmp_mounted = false, - mount_outside_created = false, mount_outside_mounted = false; - const char *dest, *src; - Machine *m = userdata; - int read_only, make_directory; - pid_t child; - siginfo_t si; - int r; - - assert(message); - assert(m); - - if (m->class != MACHINE_CONTAINER) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Bind mounting is only supported on container machines."); - - r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_directory); - if (r < 0) - return r; - - if (!path_is_absolute(src) || !path_is_safe(src)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and not contain ../."); - - if (isempty(dest)) - dest = src; - else if (!path_is_absolute(dest) || !path_is_safe(dest)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and not contain ../."); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - /* One day, when bind mounting /proc/self/fd/n works across - * namespace boundaries we should rework this logic to make - * use of it... */ - - p = strjoina("/run/systemd/nspawn/propagate/", m->name, "/"); - if (laccess(p, F_OK) < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Container does not allow propagation of mount points."); - - /* Our goal is to install a new bind mount into the container, - possibly read-only. This is irritatingly complex - unfortunately, currently. - - First, we start by creating a private playground in /tmp, - that we can mount MS_SLAVE. (Which is necessary, since - MS_MOVE cannot be applied to mounts with MS_SHARED parent - mounts.) */ - - if (!mkdtemp(mount_slave)) - return sd_bus_error_set_errnof(error, errno, "Failed to create playground %s: %m", mount_slave); - - mount_slave_created = true; - - if (mount(mount_slave, mount_slave, NULL, MS_BIND, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to make bind mount %s: %m", mount_slave); - goto finish; - } - - mount_slave_mounted = true; - - if (mount(NULL, mount_slave, NULL, MS_SLAVE, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to remount slave %s: %m", mount_slave); - goto finish; - } - - /* Second, we mount the source directory to a directory inside - of our MS_SLAVE playground. */ - mount_tmp = strjoina(mount_slave, "/mount"); - if (mkdir(mount_tmp, 0700) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount point %s: %m", mount_tmp); - goto finish; - } - - mount_tmp_created = true; - - if (mount(src, mount_tmp, NULL, MS_BIND, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to overmount %s: %m", mount_tmp); - goto finish; - } - - mount_tmp_mounted = true; - - /* Third, we remount the new bind mount read-only if requested. */ - if (read_only) - if (mount(NULL, mount_tmp, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to remount read-only %s: %m", mount_tmp); - goto finish; - } - - /* Fourth, we move the new bind mount into the propagation - * directory. This way it will appear there read-only - * right-away. */ - - mount_outside = strjoina("/run/systemd/nspawn/propagate/", m->name, "/XXXXXX"); - if (!mkdtemp(mount_outside)) { - r = sd_bus_error_set_errnof(error, errno, "Cannot create propagation directory %s: %m", mount_outside); - goto finish; - } - - mount_outside_created = true; - - if (mount(mount_tmp, mount_outside, NULL, MS_MOVE, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to move %s to %s: %m", mount_tmp, mount_outside); - goto finish; - } - - mount_outside_mounted = true; - mount_tmp_mounted = false; - - (void) rmdir(mount_tmp); - mount_tmp_created = false; - - (void) umount(mount_slave); - mount_slave_mounted = false; - - (void) rmdir(mount_slave); - mount_slave_created = false; - - if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); - goto finish; - } - - child = fork(); - if (child < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - goto finish; - } - - if (child == 0) { - const char *mount_inside; - int mntfd; - const char *q; - - errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); - - q = procfs_file_alloca(m->leader, "ns/mnt"); - mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (mntfd < 0) { - r = log_error_errno(errno, "Failed to open mount namespace of leader: %m"); - goto child_fail; - } - - if (setns(mntfd, CLONE_NEWNS) < 0) { - r = log_error_errno(errno, "Failed to join namespace of leader: %m"); - goto child_fail; - } - - if (make_directory) - (void) mkdir_p(dest, 0755); - - /* Fifth, move the mount to the right place inside */ - mount_inside = strjoina("/run/systemd/nspawn/incoming/", basename(mount_outside)); - if (mount(mount_inside, dest, NULL, MS_MOVE, NULL) < 0) { - r = log_error_errno(errno, "Failed to mount: %m"); - goto child_fail; - } - - _exit(EXIT_SUCCESS); - - child_fail: - (void) write(errno_pipe_fd[1], &r, sizeof(r)); - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - _exit(EXIT_FAILURE); - } - - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - r = wait_for_terminate(child, &si); - if (r < 0) { - r = sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); - goto finish; - } - if (si.si_code != CLD_EXITED) { - r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - goto finish; - } - if (si.si_status != EXIT_SUCCESS) { - - if (read(errno_pipe_fd[0], &r, sizeof(r)) == sizeof(r)) - r = sd_bus_error_set_errnof(error, r, "Failed to mount: %m"); - else - r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child failed."); - goto finish; - } - - r = sd_bus_reply_method_return(message, NULL); - -finish: - if (mount_outside_mounted) - umount(mount_outside); - if (mount_outside_created) - rmdir(mount_outside); - - if (mount_tmp_mounted) - umount(mount_tmp); - if (mount_tmp_created) - rmdir(mount_tmp); - - if (mount_slave_mounted) - umount(mount_slave); - if (mount_slave_created) - rmdir(mount_slave); - - return r; -} - -static int machine_operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - MachineOperation *o = userdata; - int r; - - assert(o); - assert(si); - - o->pid = 0; - - if (si->si_code != CLD_EXITED) { - r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child died abnormally."); - 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."); - - goto fail; - } - - r = sd_bus_reply_method_return(o->message, NULL); - if (r < 0) - log_error_errno(r, "Failed to reply to message: %m"); - - machine_operation_unref(o); - return 0; - -fail: - r = sd_bus_reply_method_error(o->message, &error); - if (r < 0) - log_error_errno(r, "Failed to reply to message: %m"); - - machine_operation_unref(o); - return 0; -} - -int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *src, *dest, *host_path, *container_path, *host_basename, *host_dirname, *container_basename, *container_dirname; - _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; - _cleanup_close_ int hostfd = -1; - Machine *m = userdata; - MachineOperation *o; - bool copy_from; - pid_t child; - char *t; - int r; - - assert(message); - assert(m); - - if (m->n_operations >= MACHINE_OPERATIONS_MAX) - return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing copies."); - - if (m->class != MACHINE_CONTAINER) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Copying files is only supported on container machines."); - - r = sd_bus_message_read(message, "ss", &src, &dest); - if (r < 0) - return r; - - if (!path_is_absolute(src)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute."); - - if (isempty(dest)) - dest = src; - else if (!path_is_absolute(dest)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute."); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->manager->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - copy_from = strstr(sd_bus_message_get_member(message), "CopyFrom"); - - if (copy_from) { - container_path = src; - host_path = dest; - } else { - host_path = src; - container_path = dest; - } - - host_basename = basename(host_path); - t = strdupa(host_path); - host_dirname = dirname(t); - - container_basename = basename(container_path); - t = strdupa(container_path); - container_dirname = dirname(t); - - hostfd = open(host_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); - if (hostfd < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to open host directory %s: %m", host_dirname); - - if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); - - child = fork(); - if (child < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); - - if (child == 0) { - int containerfd; - const char *q; - int mntfd; - - errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); - - q = procfs_file_alloca(m->leader, "ns/mnt"); - mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (mntfd < 0) { - r = log_error_errno(errno, "Failed to open mount namespace of leader: %m"); - goto child_fail; - } - - if (setns(mntfd, CLONE_NEWNS) < 0) { - r = log_error_errno(errno, "Failed to join namespace of leader: %m"); - goto child_fail; - } - - containerfd = open(container_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); - if (containerfd < 0) { - r = log_error_errno(errno, "Failed top open destination directory: %m"); - goto child_fail; - } - - if (copy_from) - r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, true); - else - r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, true); - - hostfd = safe_close(hostfd); - containerfd = safe_close(containerfd); - - if (r < 0) { - r = log_error_errno(r, "Failed to copy tree: %m"); - goto child_fail; - } - - _exit(EXIT_SUCCESS); - - child_fail: - (void) write(errno_pipe_fd[1], &r, sizeof(r)); - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - _exit(EXIT_FAILURE); - } - - errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); - - /* Copying might take a while, hence install a watch the - * child, and return */ - - o = new0(MachineOperation, 1); - if (!o) - return log_oom(); - - o->pid = child; - o->message = sd_bus_message_ref(message); - o->errno_fd = errno_pipe_fd[0]; - errno_pipe_fd[0] = -1; - - r = sd_event_add_child(m->manager->event, &o->event_source, child, WEXITED, machine_operation_done, o); - if (r < 0) { - machine_operation_unref(o); - return log_oom(); - } - - LIST_PREPEND(operations, m->operations, o); - m->n_operations++; - o->machine = m; - - return 1; -} - -const sd_bus_vtable machine_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Machine, name), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Id", "ay", property_get_id, 0, SD_BUS_VTABLE_PROPERTY_CONST), - BUS_PROPERTY_DUAL_TIMESTAMP("Timestamp", offsetof(Machine, timestamp), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Service", "s", NULL, offsetof(Machine, service), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Unit", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Scope", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), - SD_BUS_PROPERTY("Leader", "u", NULL, offsetof(Machine, leader), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Machine, class), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(Machine, root_directory), SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("NetworkInterfaces", "ai", property_get_netif, 0, SD_BUS_VTABLE_PROPERTY_CONST), - SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0), - SD_BUS_METHOD("Terminate", NULL, NULL, bus_machine_method_terminate, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("Kill", "si", NULL, bus_machine_method_kill, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetAddresses", NULL, "a(iay)", bus_machine_method_get_addresses, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_machine_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenPTY", NULL, "hs", bus_machine_method_open_pty, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenLogin", NULL, "hs", bus_machine_method_open_login, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenShell", "ssasas", "hs", bus_machine_method_open_shell, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("BindMount", "ssbb", NULL, bus_machine_method_bind_mount, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CopyFrom", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CopyTo", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END -}; - -int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - if (streq(path, "/org/freedesktop/machine1/machine/self")) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - sd_bus_message *message; - pid_t pid; - - message = sd_bus_get_current_message(bus); - if (!message) - return 0; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - - r = manager_get_machine_by_pid(m, pid, &machine); - if (r <= 0) - return 0; - } else { - _cleanup_free_ char *e = NULL; - const char *p; - - p = startswith(path, "/org/freedesktop/machine1/machine/"); - if (!p) - return 0; - - e = bus_label_unescape(p); - if (!e) - return -ENOMEM; - - machine = hashmap_get(m->machines, e); - if (!machine) - return 0; - } - - *found = machine; - return 1; -} - -char *machine_bus_path(Machine *m) { - _cleanup_free_ char *e = NULL; - - assert(m); - - e = bus_label_escape(m->name); - if (!e) - return NULL; - - return strappend("/org/freedesktop/machine1/machine/", e); -} - -int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - _cleanup_strv_free_ char **l = NULL; - Machine *machine = NULL; - Manager *m = userdata; - Iterator i; - int r; - - assert(bus); - assert(path); - assert(nodes); - - HASHMAP_FOREACH(machine, m->machines, i) { - char *p; - - p = machine_bus_path(machine); - if (!p) - return -ENOMEM; - - r = strv_consume(&l, p); - if (r < 0) - return r; - } - - *nodes = l; - l = NULL; - - return 1; -} - -int machine_send_signal(Machine *m, bool new_machine) { - _cleanup_free_ char *p = NULL; - - assert(m); - - p = machine_bus_path(m); - if (!p) - return -ENOMEM; - - return sd_bus_emit_signal( - m->manager->bus, - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - new_machine ? "MachineNew" : "MachineRemoved", - "so", m->name, p); -} - -int machine_send_create_reply(Machine *m, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL; - _cleanup_free_ char *p = NULL; - - assert(m); - - if (!m->create_message) - return 0; - - c = m->create_message; - m->create_message = NULL; - - if (error) - return sd_bus_reply_method_error(c, error); - - /* Update the machine state file before we notify the client - * about the result. */ - machine_save(m); - - p = machine_bus_path(m); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(c, "o", p); -} diff --git a/src/machine/machine-dbus.h b/src/machine/machine-dbus.h deleted file mode 100644 index 3a8162b171..0000000000 --- a/src/machine/machine-dbus.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "sd-bus.h" - -#include "machine.h" - -extern const sd_bus_vtable machine_vtable[]; - -char *machine_bus_path(Machine *s); -int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); -int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); - -int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error); - -int machine_send_signal(Machine *m, bool new_machine); -int machine_send_create_reply(Machine *m, sd_bus_error *error); diff --git a/src/machine/machine.c b/src/machine/machine.c deleted file mode 100644 index 406d5a4b85..0000000000 --- a/src/machine/machine.c +++ /dev/null @@ -1,659 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include - -#include "sd-messages.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "escape.h" -#include "extract-word.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "hashmap.h" -#include "machine-dbus.h" -#include "machine.h" -#include "mkdir.h" -#include "parse-util.h" -#include "process-util.h" -#include "special.h" -#include "string-table.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "util.h" - -Machine* machine_new(Manager *manager, MachineClass class, const char *name) { - Machine *m; - - assert(manager); - assert(class < _MACHINE_CLASS_MAX); - assert(name); - - /* Passing class == _MACHINE_CLASS_INVALID here is fine. It - * means as much as "we don't know yet", and that we'll figure - * it out later when loading the state file. */ - - m = new0(Machine, 1); - if (!m) - return NULL; - - m->name = strdup(name); - if (!m->name) - goto fail; - - if (class != MACHINE_HOST) { - m->state_file = strappend("/run/systemd/machines/", m->name); - if (!m->state_file) - goto fail; - } - - m->class = class; - - if (hashmap_put(manager->machines, m->name, m) < 0) - goto fail; - - m->manager = manager; - - return m; - -fail: - free(m->state_file); - free(m->name); - free(m); - - return NULL; -} - -void machine_free(Machine *m) { - assert(m); - - while (m->operations) - machine_operation_unref(m->operations); - - if (m->in_gc_queue) - LIST_REMOVE(gc_queue, m->manager->machine_gc_queue, m); - - machine_release_unit(m); - - free(m->scope_job); - - (void) hashmap_remove(m->manager->machines, m->name); - - if (m->manager->host_machine == m) - m->manager->host_machine = NULL; - - if (m->leader > 0) - (void) hashmap_remove_value(m->manager->machine_leaders, PID_TO_PTR(m->leader), m); - - sd_bus_message_unref(m->create_message); - - free(m->name); - free(m->state_file); - free(m->service); - free(m->root_directory); - free(m->netif); - free(m); -} - -int machine_save(Machine *m) { - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(m); - - if (!m->state_file) - return 0; - - if (!m->started) - return 0; - - r = mkdir_safe_label("/run/systemd/machines", 0755, 0, 0); - if (r < 0) - goto fail; - - r = fopen_temporary(m->state_file, &f, &temp_path); - if (r < 0) - goto fail; - - (void) fchmod(fileno(f), 0644); - - fprintf(f, - "# This is private data. Do not parse.\n" - "NAME=%s\n", - m->name); - - if (m->unit) { - _cleanup_free_ char *escaped; - - escaped = cescape(m->unit); - if (!escaped) { - r = -ENOMEM; - goto fail; - } - - fprintf(f, "SCOPE=%s\n", escaped); /* We continue to call this "SCOPE=" because it is internal only, and we want to stay compatible with old files */ - } - - if (m->scope_job) - fprintf(f, "SCOPE_JOB=%s\n", m->scope_job); - - if (m->service) { - _cleanup_free_ char *escaped; - - escaped = cescape(m->service); - if (!escaped) { - r = -ENOMEM; - goto fail; - } - fprintf(f, "SERVICE=%s\n", escaped); - } - - if (m->root_directory) { - _cleanup_free_ char *escaped; - - escaped = cescape(m->root_directory); - if (!escaped) { - r = -ENOMEM; - goto fail; - } - fprintf(f, "ROOT=%s\n", escaped); - } - - if (!sd_id128_equal(m->id, SD_ID128_NULL)) - fprintf(f, "ID=" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->id)); - - if (m->leader != 0) - fprintf(f, "LEADER="PID_FMT"\n", m->leader); - - if (m->class != _MACHINE_CLASS_INVALID) - fprintf(f, "CLASS=%s\n", machine_class_to_string(m->class)); - - if (dual_timestamp_is_set(&m->timestamp)) - fprintf(f, - "REALTIME="USEC_FMT"\n" - "MONOTONIC="USEC_FMT"\n", - m->timestamp.realtime, - m->timestamp.monotonic); - - if (m->n_netif > 0) { - unsigned i; - - fputs("NETIF=", f); - - for (i = 0; i < m->n_netif; i++) { - if (i != 0) - fputc(' ', f); - - fprintf(f, "%i", m->netif[i]); - } - - fputc('\n', f); - } - - r = fflush_and_check(f); - if (r < 0) - goto fail; - - if (rename(temp_path, m->state_file) < 0) { - r = -errno; - goto fail; - } - - if (m->unit) { - char *sl; - - /* Create a symlink from the unit name to the machine - * name, so that we can quickly find the machine for - * each given unit. Ignore error. */ - sl = strjoina("/run/systemd/machines/unit:", m->unit); - (void) symlink(m->name, sl); - } - - return 0; - -fail: - (void) unlink(m->state_file); - - if (temp_path) - (void) unlink(temp_path); - - return log_error_errno(r, "Failed to save machine data %s: %m", m->state_file); -} - -static void machine_unlink(Machine *m) { - assert(m); - - if (m->unit) { - - char *sl; - - sl = strjoina("/run/systemd/machines/unit:", m->unit); - (void) unlink(sl); - } - - if (m->state_file) - (void) unlink(m->state_file); -} - -int machine_load(Machine *m) { - _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *id = NULL, *leader = NULL, *class = NULL, *netif = NULL; - int r; - - assert(m); - - if (!m->state_file) - return 0; - - r = parse_env_file(m->state_file, NEWLINE, - "SCOPE", &m->unit, - "SCOPE_JOB", &m->scope_job, - "SERVICE", &m->service, - "ROOT", &m->root_directory, - "ID", &id, - "LEADER", &leader, - "CLASS", &class, - "REALTIME", &realtime, - "MONOTONIC", &monotonic, - "NETIF", &netif, - NULL); - if (r < 0) { - if (r == -ENOENT) - return 0; - - return log_error_errno(r, "Failed to read %s: %m", m->state_file); - } - - if (id) - sd_id128_from_string(id, &m->id); - - if (leader) - parse_pid(leader, &m->leader); - - if (class) { - MachineClass c; - - c = machine_class_from_string(class); - if (c >= 0) - m->class = c; - } - - if (realtime) { - unsigned long long l; - if (sscanf(realtime, "%llu", &l) > 0) - m->timestamp.realtime = l; - } - - if (monotonic) { - unsigned long long l; - if (sscanf(monotonic, "%llu", &l) > 0) - m->timestamp.monotonic = l; - } - - if (netif) { - size_t allocated = 0, nr = 0; - const char *p; - int *ni = NULL; - - p = netif; - for(;;) { - _cleanup_free_ char *word = NULL; - int ifi; - - r = extract_first_word(&p, &word, NULL, 0); - if (r == 0) - break; - if (r == -ENOMEM) - return log_oom(); - if (r < 0) { - log_warning_errno(r, "Failed to parse NETIF: %s", netif); - break; - } - - if (parse_ifindex(word, &ifi) < 0) - continue; - - if (!GREEDY_REALLOC(ni, allocated, nr+1)) { - free(ni); - return log_oom(); - } - - ni[nr++] = ifi; - } - - free(m->netif); - m->netif = ni; - m->n_netif = nr; - } - - return r; -} - -static int machine_start_scope(Machine *m, sd_bus_message *properties, sd_bus_error *error) { - int r = 0; - - assert(m); - assert(m->class != MACHINE_HOST); - - if (!m->unit) { - _cleanup_free_ char *escaped = NULL; - char *scope, *description, *job = NULL; - - escaped = unit_name_escape(m->name); - if (!escaped) - return log_oom(); - - scope = strjoin("machine-", escaped, ".scope", NULL); - if (!scope) - return log_oom(); - - description = strjoina(m->class == MACHINE_VM ? "Virtual Machine " : "Container ", m->name); - - r = manager_start_scope(m->manager, scope, m->leader, SPECIAL_MACHINE_SLICE, description, properties, error, &job); - if (r < 0) { - log_error("Failed to start machine scope: %s", bus_error_message(error, r)); - free(scope); - return r; - } else { - m->unit = scope; - - free(m->scope_job); - m->scope_job = job; - } - } - - if (m->unit) - hashmap_put(m->manager->machine_units, m->unit, m); - - return r; -} - -int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error) { - int r; - - assert(m); - - if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) - return -EOPNOTSUPP; - - if (m->started) - return 0; - - r = hashmap_put(m->manager->machine_leaders, PID_TO_PTR(m->leader), m); - if (r < 0) - return r; - - /* Create cgroup */ - r = machine_start_scope(m, properties, error); - if (r < 0) - return r; - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_START), - "NAME=%s", m->name, - "LEADER="PID_FMT, m->leader, - LOG_MESSAGE("New machine %s.", m->name), - NULL); - - if (!dual_timestamp_is_set(&m->timestamp)) - dual_timestamp_get(&m->timestamp); - - m->started = true; - - /* Save new machine data */ - machine_save(m); - - machine_send_signal(m, true); - - return 0; -} - -static int machine_stop_scope(Machine *m) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char *job = NULL; - int r; - - assert(m); - assert(m->class != MACHINE_HOST); - - if (!m->unit) - return 0; - - r = manager_stop_unit(m->manager, m->unit, &error, &job); - if (r < 0) { - log_error("Failed to stop machine scope: %s", bus_error_message(&error, r)); - return r; - } - - free(m->scope_job); - m->scope_job = job; - - return 0; -} - -int machine_stop(Machine *m) { - int r; - assert(m); - - if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) - return -EOPNOTSUPP; - - r = machine_stop_scope(m); - - m->stopping = true; - - machine_save(m); - - return r; -} - -int machine_finalize(Machine *m) { - assert(m); - - if (m->started) - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_STOP), - "NAME=%s", m->name, - "LEADER="PID_FMT, m->leader, - LOG_MESSAGE("Machine %s terminated.", m->name), - NULL); - - machine_unlink(m); - machine_add_to_gc_queue(m); - - if (m->started) { - machine_send_signal(m, false); - m->started = false; - } - - return 0; -} - -bool machine_check_gc(Machine *m, bool drop_not_started) { - assert(m); - - if (m->class == MACHINE_HOST) - return true; - - if (drop_not_started && !m->started) - return false; - - if (m->scope_job && manager_job_is_active(m->manager, m->scope_job)) - return true; - - if (m->unit && manager_unit_is_active(m->manager, m->unit)) - return true; - - return false; -} - -void machine_add_to_gc_queue(Machine *m) { - assert(m); - - if (m->in_gc_queue) - return; - - LIST_PREPEND(gc_queue, m->manager->machine_gc_queue, m); - m->in_gc_queue = true; -} - -MachineState machine_get_state(Machine *s) { - assert(s); - - if (s->class == MACHINE_HOST) - return MACHINE_RUNNING; - - if (s->stopping) - return MACHINE_CLOSING; - - if (s->scope_job) - return MACHINE_OPENING; - - return MACHINE_RUNNING; -} - -int machine_kill(Machine *m, KillWho who, int signo) { - assert(m); - - if (!IN_SET(m->class, MACHINE_VM, MACHINE_CONTAINER)) - return -EOPNOTSUPP; - - if (!m->unit) - return -ESRCH; - - if (who == KILL_LEADER) { - /* If we shall simply kill the leader, do so directly */ - - if (kill(m->leader, signo) < 0) - return -errno; - - return 0; - } - - /* Otherwise, make PID 1 do it for us, for the entire cgroup */ - return manager_kill_unit(m->manager, m->unit, signo, NULL); -} - -int machine_openpt(Machine *m, int flags) { - assert(m); - - switch (m->class) { - - case MACHINE_HOST: { - int fd; - - fd = posix_openpt(flags); - if (fd < 0) - return -errno; - - if (unlockpt(fd) < 0) - return -errno; - - return fd; - } - - case MACHINE_CONTAINER: - if (m->leader <= 0) - return -EINVAL; - - return openpt_in_namespace(m->leader, flags); - - default: - return -EOPNOTSUPP; - } -} - -int machine_open_terminal(Machine *m, const char *path, int mode) { - assert(m); - - switch (m->class) { - - case MACHINE_HOST: - return open_terminal(path, mode); - - case MACHINE_CONTAINER: - if (m->leader <= 0) - return -EINVAL; - - return open_terminal_in_namespace(m->leader, path, mode); - - default: - return -EOPNOTSUPP; - } -} - -MachineOperation *machine_operation_unref(MachineOperation *o) { - if (!o) - return NULL; - - sd_event_source_unref(o->event_source); - - safe_close(o->errno_fd); - - if (o->pid > 1) - (void) kill(o->pid, SIGKILL); - - sd_bus_message_unref(o->message); - - if (o->machine) { - LIST_REMOVE(operations, o->machine->operations, o); - o->machine->n_operations--; - } - - free(o); - return NULL; -} - -void machine_release_unit(Machine *m) { - assert(m); - - if (!m->unit) - return; - - (void) hashmap_remove(m->manager->machine_units, m->unit); - m->unit = mfree(m->unit); -} - -static const char* const machine_class_table[_MACHINE_CLASS_MAX] = { - [MACHINE_CONTAINER] = "container", - [MACHINE_VM] = "vm", - [MACHINE_HOST] = "host", -}; - -DEFINE_STRING_TABLE_LOOKUP(machine_class, MachineClass); - -static const char* const machine_state_table[_MACHINE_STATE_MAX] = { - [MACHINE_OPENING] = "opening", - [MACHINE_RUNNING] = "running", - [MACHINE_CLOSING] = "closing" -}; - -DEFINE_STRING_TABLE_LOOKUP(machine_state, MachineState); - -static const char* const kill_who_table[_KILL_WHO_MAX] = { - [KILL_LEADER] = "leader", - [KILL_ALL] = "all" -}; - -DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/machine/machine.h b/src/machine/machine.h deleted file mode 100644 index 1d8cc5911a..0000000000 --- a/src/machine/machine.h +++ /dev/null @@ -1,124 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -typedef struct Machine Machine; -typedef struct MachineOperation MachineOperation; -typedef enum KillWho KillWho; - -#include "list.h" -#include "machined.h" - -typedef enum MachineState { - MACHINE_OPENING, /* Machine is being registered */ - MACHINE_RUNNING, /* Machine is running */ - MACHINE_CLOSING, /* Machine is terminating */ - _MACHINE_STATE_MAX, - _MACHINE_STATE_INVALID = -1 -} MachineState; - -typedef enum MachineClass { - MACHINE_CONTAINER, - MACHINE_VM, - MACHINE_HOST, - _MACHINE_CLASS_MAX, - _MACHINE_CLASS_INVALID = -1 -} MachineClass; - -enum KillWho { - KILL_LEADER, - KILL_ALL, - _KILL_WHO_MAX, - _KILL_WHO_INVALID = -1 -}; - -#define MACHINE_OPERATIONS_MAX 64 - -struct MachineOperation { - Machine *machine; - pid_t pid; - sd_bus_message *message; - int errno_fd; - sd_event_source *event_source; - LIST_FIELDS(MachineOperation, operations); -}; - -struct Machine { - Manager *manager; - - char *name; - sd_id128_t id; - - MachineClass class; - - char *state_file; - char *service; - char *root_directory; - - char *unit; - char *scope_job; - - pid_t leader; - - dual_timestamp timestamp; - - bool in_gc_queue:1; - bool started:1; - bool stopping:1; - - sd_bus_message *create_message; - - int *netif; - unsigned n_netif; - - LIST_FIELDS(Machine, gc_queue); - - MachineOperation *operations; - unsigned n_operations; -}; - -Machine* machine_new(Manager *manager, MachineClass class, const char *name); -void machine_free(Machine *m); -bool machine_check_gc(Machine *m, bool drop_not_started); -void machine_add_to_gc_queue(Machine *m); -int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error); -int machine_stop(Machine *m); -int machine_finalize(Machine *m); -int machine_save(Machine *m); -int machine_load(Machine *m); -int machine_kill(Machine *m, KillWho who, int signo); - -void machine_release_unit(Machine *m); - -MachineState machine_get_state(Machine *u); - -MachineOperation *machine_operation_unref(MachineOperation *o); - -const char* machine_class_to_string(MachineClass t) _const_; -MachineClass machine_class_from_string(const char *s) _pure_; - -const char* machine_state_to_string(MachineState t) _const_; -MachineState machine_state_from_string(const char *s) _pure_; - -const char *kill_who_to_string(KillWho k) _const_; -KillWho kill_who_from_string(const char *s) _pure_; - -int machine_openpt(Machine *m, int flags); -int machine_open_terminal(Machine *m, const char *path, int mode); diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c deleted file mode 100644 index 4853139321..0000000000 --- a/src/machine/machinectl.c +++ /dev/null @@ -1,2678 +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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "cgroup-show.h" -#include "cgroup-util.h" -#include "copy.h" -#include "env-util.h" -#include "fd-util.h" -#include "hostname-util.h" -#include "import-util.h" -#include "log.h" -#include "logs-show.h" -#include "macro.h" -#include "mkdir.h" -#include "pager.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "ptyfwd.h" -#include "signal-util.h" -#include "spawn-polkit-agent.h" -#include "strv.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "util.h" -#include "verbs.h" -#include "web-util.h" - -static char **arg_property = NULL; -static bool arg_all = false; -static bool arg_full = false; -static bool arg_no_pager = false; -static bool arg_legend = true; -static const char *arg_kill_who = NULL; -static int arg_signal = SIGTERM; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static char *arg_host = NULL; -static bool arg_read_only = false; -static bool arg_mkdir = false; -static bool arg_quiet = false; -static bool arg_ask_password = true; -static unsigned arg_lines = 10; -static OutputMode arg_output = OUTPUT_SHORT; -static bool arg_force = false; -static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; -static const char* arg_format = NULL; -static const char *arg_uid = NULL; -static char **arg_setenv = NULL; - -static void pager_open_if_enabled(void) { - - if (arg_no_pager) - return; - - pager_open(false); -} - -static void polkit_agent_open_if_enabled(void) { - - /* Open the polkit agent as a child process if necessary */ - - if (!arg_ask_password) - return; - - if (arg_transport != BUS_TRANSPORT_LOCAL) - return; - - polkit_agent_open(); -} - -static OutputFlags get_output_flags(void) { - return - arg_all * OUTPUT_SHOW_ALL | - arg_full * OUTPUT_FULL_WIDTH | - (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | - colors_enabled() * OUTPUT_COLOR | - !arg_quiet * OUTPUT_WARN_CUTOFF; -} - -typedef struct MachineInfo { - const char *name; - const char *class; - const char *service; -} MachineInfo; - -static int compare_machine_info(const void *a, const void *b) { - const MachineInfo *x = a, *y = b; - - return strcmp(x->name, y->name); -} - -static int list_machines(int argc, char *argv[], void *userdata) { - - size_t max_name = strlen("MACHINE"), max_class = strlen("CLASS"), max_service = strlen("SERVICE"); - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ MachineInfo *machines = NULL; - const char *name, *class, *service, *object; - size_t n_machines = 0, n_allocated = 0, j; - sd_bus *bus = userdata; - int r; - - assert(bus); - - pager_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "ListMachines", - &error, - &reply, - NULL); - if (r < 0) { - log_error("Could not get machines: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_enter_container(reply, 'a', "(ssso)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) { - size_t l; - - if (name[0] == '.' && !arg_all) - continue; - - if (!GREEDY_REALLOC(machines, n_allocated, n_machines + 1)) - return log_oom(); - - machines[n_machines].name = name; - machines[n_machines].class = class; - machines[n_machines].service = service; - - l = strlen(name); - if (l > max_name) - max_name = l; - - l = strlen(class); - if (l > max_class) - max_class = l; - - l = strlen(service); - if (l > max_service) - max_service = l; - - n_machines ++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - qsort_safe(machines, n_machines, sizeof(MachineInfo), compare_machine_info); - - if (arg_legend) - printf("%-*s %-*s %-*s\n", - (int) max_name, "MACHINE", - (int) max_class, "CLASS", - (int) max_service, "SERVICE"); - - for (j = 0; j < n_machines; j++) - printf("%-*s %-*s %-*s\n", - (int) max_name, machines[j].name, - (int) max_class, machines[j].class, - (int) max_service, machines[j].service); - - if (arg_legend) - printf("\n%zu machines listed.\n", n_machines); - - return 0; -} - -typedef struct ImageInfo { - const char *name; - const char *type; - bool read_only; - usec_t crtime; - usec_t mtime; - uint64_t size; -} ImageInfo; - -static int compare_image_info(const void *a, const void *b) { - const ImageInfo *x = a, *y = b; - - return strcmp(x->name, y->name); -} - -static int list_images(int argc, char *argv[], void *userdata) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - size_t max_name = strlen("NAME"), max_type = strlen("TYPE"), max_size = strlen("USAGE"), max_crtime = strlen("CREATED"), max_mtime = strlen("MODIFIED"); - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ ImageInfo *images = NULL; - size_t n_images = 0, n_allocated = 0, j; - const char *name, *type, *object; - sd_bus *bus = userdata; - uint64_t crtime, mtime, size; - int read_only, r; - - assert(bus); - - pager_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "ListImages", - &error, - &reply, - ""); - if (r < 0) { - log_error("Could not get images: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &read_only, &crtime, &mtime, &size, &object)) > 0) { - char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_BYTES_MAX)]; - size_t l; - - if (name[0] == '.' && !arg_all) - continue; - - if (!GREEDY_REALLOC(images, n_allocated, n_images + 1)) - return log_oom(); - - images[n_images].name = name; - images[n_images].type = type; - images[n_images].read_only = read_only; - images[n_images].crtime = crtime; - images[n_images].mtime = mtime; - images[n_images].size = size; - - l = strlen(name); - if (l > max_name) - max_name = l; - - l = strlen(type); - if (l > max_type) - max_type = l; - - if (crtime != 0) { - l = strlen(strna(format_timestamp(buf, sizeof(buf), crtime))); - if (l > max_crtime) - max_crtime = l; - } - - if (mtime != 0) { - l = strlen(strna(format_timestamp(buf, sizeof(buf), mtime))); - if (l > max_mtime) - max_mtime = l; - } - - if (size != (uint64_t) -1) { - l = strlen(strna(format_bytes(buf, sizeof(buf), size))); - if (l > max_size) - max_size = l; - } - - n_images++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info); - - if (arg_legend) - printf("%-*s %-*s %-3s %-*s %-*s %-*s\n", - (int) max_name, "NAME", - (int) max_type, "TYPE", - "RO", - (int) max_size, "USAGE", - (int) max_crtime, "CREATED", - (int) max_mtime, "MODIFIED"); - - for (j = 0; j < n_images; j++) { - char crtime_buf[FORMAT_TIMESTAMP_MAX], mtime_buf[FORMAT_TIMESTAMP_MAX], size_buf[FORMAT_BYTES_MAX]; - - printf("%-*s %-*s %s%-3s%s %-*s %-*s %-*s\n", - (int) max_name, images[j].name, - (int) max_type, images[j].type, - images[j].read_only ? ansi_highlight_red() : "", yes_no(images[j].read_only), images[j].read_only ? ansi_normal() : "", - (int) max_size, strna(format_bytes(size_buf, sizeof(size_buf), images[j].size)), - (int) max_crtime, strna(format_timestamp(crtime_buf, sizeof(crtime_buf), images[j].crtime)), - (int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime))); - } - - if (arg_legend) - printf("\n%zu images listed.\n", n_images); - - return 0; -} - -static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) { - _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; - const char *cgroup; - int r; - unsigned c; - - assert(bus); - assert(unit); - - if (arg_transport == BUS_TRANSPORT_REMOTE) - return 0; - - path = unit_dbus_path_from_name(unit); - if (!path) - return log_oom(); - - r = sd_bus_get_property( - bus, - "org.freedesktop.systemd1", - path, - unit_dbus_interface_from_name(unit), - "ControlGroup", - &error, - &reply, - "s"); - if (r < 0) { - log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "s", &cgroup); - if (r < 0) - return bus_log_parse_error(r); - - if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup) != 0 && leader <= 0) - return 0; - - c = columns(); - if (c > 18) - c -= 18; - else - c = 0; - - show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, get_output_flags()); - return 0; -} - -static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - assert(bus); - assert(name); - assert(prefix); - assert(prefix2); - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineAddresses", - NULL, - &reply, - "s", name); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, 'a', "(iay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { - int family; - const void *a; - size_t sz; - char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)]; - - r = sd_bus_message_read(reply, "i", &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - return bus_log_parse_error(r); - - fputs(prefix, stdout); - fputs(inet_ntop(family, a, buffer, sizeof(buffer)), stdout); - if (family == AF_INET6 && ifi > 0) - printf("%%%i", ifi); - fputc('\n', stdout); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (prefix != prefix2) - prefix = prefix2; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - return 0; -} - -static int print_os_release(sd_bus *bus, const char *name, const char *prefix) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *k, *v, *pretty = NULL; - int r; - - assert(bus); - assert(name); - assert(prefix); - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineOSRelease", - NULL, - &reply, - "s", name); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, 'a', "{ss}"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) { - if (streq(k, "PRETTY_NAME")) - pretty = v; - - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (pretty) - printf("%s%s\n", prefix, pretty); - - return 0; -} - -typedef struct MachineStatusInfo { - char *name; - sd_id128_t id; - char *class; - char *service; - char *unit; - char *root_directory; - pid_t leader; - struct dual_timestamp timestamp; - int *netif; - unsigned n_netif; -} MachineStatusInfo; - -static void machine_status_info_clear(MachineStatusInfo *info) { - if (info) { - free(info->name); - free(info->class); - free(info->service); - free(info->unit); - free(info->root_directory); - free(info->netif); - zero(*info); - } -} - -static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { - char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; - char since2[FORMAT_TIMESTAMP_MAX], *s2; - int ifi = -1; - - assert(bus); - assert(i); - - fputs(strna(i->name), stdout); - - if (!sd_id128_equal(i->id, SD_ID128_NULL)) - printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id)); - else - putchar('\n'); - - s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp.realtime); - s2 = format_timestamp(since2, sizeof(since2), i->timestamp.realtime); - - if (s1) - printf("\t Since: %s; %s\n", s2, s1); - else if (s2) - printf("\t Since: %s\n", s2); - - if (i->leader > 0) { - _cleanup_free_ char *t = NULL; - - printf("\t Leader: %u", (unsigned) i->leader); - - get_process_comm(i->leader, &t); - if (t) - printf(" (%s)", t); - - putchar('\n'); - } - - if (i->service) { - printf("\t Service: %s", i->service); - - if (i->class) - printf("; class %s", i->class); - - putchar('\n'); - } else if (i->class) - printf("\t Class: %s\n", i->class); - - if (i->root_directory) - printf("\t Root: %s\n", i->root_directory); - - if (i->n_netif > 0) { - unsigned c; - - fputs("\t Iface:", stdout); - - for (c = 0; c < i->n_netif; c++) { - char name[IF_NAMESIZE+1] = ""; - - if (if_indextoname(i->netif[c], name)) { - fputc(' ', stdout); - fputs(name, stdout); - - if (ifi < 0) - ifi = i->netif[c]; - else - ifi = 0; - } else - printf(" %i", i->netif[c]); - } - - fputc('\n', stdout); - } - - print_addresses(bus, i->name, ifi, - "\t Address: ", - "\t "); - - print_os_release(bus, i->name, "\t OS: "); - - if (i->unit) { - printf("\t Unit: %s\n", i->unit); - show_unit_cgroup(bus, i->unit, i->leader); - - if (arg_transport == BUS_TRANSPORT_LOCAL) - - show_journal_by_unit( - stdout, - i->unit, - arg_output, - 0, - i->timestamp.monotonic, - arg_lines, - 0, - get_output_flags() | OUTPUT_BEGIN_NEWLINE, - SD_JOURNAL_LOCAL_ONLY, - true, - NULL); - } -} - -static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - MachineStatusInfo *i = userdata; - size_t l; - const void *v; - int r; - - assert_cc(sizeof(int32_t) == sizeof(int)); - r = sd_bus_message_read_array(m, SD_BUS_TYPE_INT32, &v, &l); - if (r < 0) - return r; - if (r == 0) - return -EBADMSG; - - i->n_netif = l / sizeof(int32_t); - i->netif = memdup(v, l); - if (!i->netif) - return -ENOMEM; - - return 0; -} - -static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) { - - static const struct bus_properties_map map[] = { - { "Name", "s", NULL, offsetof(MachineStatusInfo, name) }, - { "Class", "s", NULL, offsetof(MachineStatusInfo, class) }, - { "Service", "s", NULL, offsetof(MachineStatusInfo, service) }, - { "Unit", "s", NULL, offsetof(MachineStatusInfo, unit) }, - { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) }, - { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) }, - { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp.realtime) }, - { "TimestampMonotonic", "t", NULL, offsetof(MachineStatusInfo, timestamp.monotonic) }, - { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) }, - { "NetworkInterfaces", "ai", map_netif, 0 }, - {} - }; - - _cleanup_(machine_status_info_clear) MachineStatusInfo info = {}; - int r; - - assert(verb); - assert(bus); - assert(path); - assert(new_line); - - r = bus_map_all_properties(bus, - "org.freedesktop.machine1", - path, - map, - &info); - if (r < 0) - return log_error_errno(r, "Could not get properties: %m"); - - if (*new_line) - printf("\n"); - *new_line = true; - - print_machine_status_info(bus, &info); - - return r; -} - -static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line) { - int r; - - assert(bus); - assert(path); - assert(new_line); - - if (*new_line) - printf("\n"); - - *new_line = true; - - r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all); - if (r < 0) - log_error_errno(r, "Could not get properties: %m"); - - return r; -} - -static int show_machine(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 *reply = NULL; - bool properties, new_line = false; - sd_bus *bus = userdata; - int r = 0, i; - - assert(bus); - - properties = !strstr(argv[0], "status"); - - pager_open_if_enabled(); - - if (properties && argc <= 1) { - - /* If no argument is specified, inspect the manager - * itself */ - r = show_machine_properties(bus, "/org/freedesktop/machine1", &new_line); - if (r < 0) - return r; - } - - for (i = 1; i < argc; i++) { - const char *path = NULL; - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachine", - &error, - &reply, - "s", argv[i]); - if (r < 0) { - log_error("Could not get path to machine: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - if (properties) - r = show_machine_properties(bus, path, &new_line); - else - r = show_machine_info(argv[0], bus, path, &new_line); - } - - return r; -} - -typedef struct ImageStatusInfo { - char *name; - char *path; - char *type; - int read_only; - usec_t crtime; - usec_t mtime; - uint64_t usage; - uint64_t limit; - uint64_t usage_exclusive; - uint64_t limit_exclusive; -} ImageStatusInfo; - -static void image_status_info_clear(ImageStatusInfo *info) { - if (info) { - free(info->name); - free(info->path); - free(info->type); - zero(*info); - } -} - -static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { - char ts_relative[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; - char ts_absolute[FORMAT_TIMESTAMP_MAX], *s2; - char bs[FORMAT_BYTES_MAX], *s3; - char bs_exclusive[FORMAT_BYTES_MAX], *s4; - - assert(bus); - assert(i); - - if (i->name) { - fputs(i->name, stdout); - putchar('\n'); - } - - if (i->type) - printf("\t Type: %s\n", i->type); - - if (i->path) - printf("\t Path: %s\n", i->path); - - printf("\t RO: %s%s%s\n", - i->read_only ? ansi_highlight_red() : "", - i->read_only ? "read-only" : "writable", - i->read_only ? ansi_normal() : ""); - - s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->crtime); - s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->crtime); - if (s1 && s2) - printf("\t Created: %s; %s\n", s2, s1); - else if (s2) - printf("\t Created: %s\n", s2); - - s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->mtime); - s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->mtime); - if (s1 && s2) - printf("\tModified: %s; %s\n", s2, s1); - else if (s2) - printf("\tModified: %s\n", s2); - - s3 = format_bytes(bs, sizeof(bs), i->usage); - s4 = i->usage_exclusive != i->usage ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->usage_exclusive) : NULL; - if (s3 && s4) - printf("\t Usage: %s (exclusive: %s)\n", s3, s4); - else if (s3) - printf("\t Usage: %s\n", s3); - - s3 = format_bytes(bs, sizeof(bs), i->limit); - s4 = i->limit_exclusive != i->limit ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->limit_exclusive) : NULL; - if (s3 && s4) - printf("\t Limit: %s (exclusive: %s)\n", s3, s4); - else if (s3) - printf("\t Limit: %s\n", s3); -} - -static int show_image_info(sd_bus *bus, const char *path, bool *new_line) { - - static const struct bus_properties_map map[] = { - { "Name", "s", NULL, offsetof(ImageStatusInfo, name) }, - { "Path", "s", NULL, offsetof(ImageStatusInfo, path) }, - { "Type", "s", NULL, offsetof(ImageStatusInfo, type) }, - { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) }, - { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) }, - { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) }, - { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) }, - { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) }, - { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) }, - { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) }, - {} - }; - - _cleanup_(image_status_info_clear) ImageStatusInfo info = {}; - int r; - - assert(bus); - assert(path); - assert(new_line); - - r = bus_map_all_properties(bus, - "org.freedesktop.machine1", - path, - map, - &info); - if (r < 0) - return log_error_errno(r, "Could not get properties: %m"); - - if (*new_line) - printf("\n"); - *new_line = true; - - print_image_status_info(bus, &info); - - return r; -} - -typedef struct PoolStatusInfo { - char *path; - uint64_t usage; - uint64_t limit; -} PoolStatusInfo; - -static void pool_status_info_clear(PoolStatusInfo *info) { - if (info) { - free(info->path); - zero(*info); - info->usage = -1; - info->limit = -1; - } -} - -static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) { - char bs[FORMAT_BYTES_MAX], *s; - - if (i->path) - printf("\t Path: %s\n", i->path); - - s = format_bytes(bs, sizeof(bs), i->usage); - if (s) - printf("\t Usage: %s\n", s); - - s = format_bytes(bs, sizeof(bs), i->limit); - if (s) - printf("\t Limit: %s\n", s); -} - -static int show_pool_info(sd_bus *bus) { - - static const struct bus_properties_map map[] = { - { "PoolPath", "s", NULL, offsetof(PoolStatusInfo, path) }, - { "PoolUsage", "t", NULL, offsetof(PoolStatusInfo, usage) }, - { "PoolLimit", "t", NULL, offsetof(PoolStatusInfo, limit) }, - {} - }; - - _cleanup_(pool_status_info_clear) PoolStatusInfo info = { - .usage = (uint64_t) -1, - .limit = (uint64_t) -1, - }; - int r; - - assert(bus); - - r = bus_map_all_properties(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - map, - &info); - if (r < 0) - return log_error_errno(r, "Could not get properties: %m"); - - print_pool_status_info(bus, &info); - - return 0; -} - - -static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) { - int r; - - assert(bus); - assert(path); - assert(new_line); - - if (*new_line) - printf("\n"); - - *new_line = true; - - r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all); - if (r < 0) - log_error_errno(r, "Could not get properties: %m"); - - return r; -} - -static int show_image(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 *reply = NULL; - bool properties, new_line = false; - sd_bus *bus = userdata; - int r = 0, i; - - assert(bus); - - properties = !strstr(argv[0], "status"); - - pager_open_if_enabled(); - - if (argc <= 1) { - - /* If no argument is specified, inspect the manager - * itself */ - - if (properties) - r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line); - else - r = show_pool_info(bus); - if (r < 0) - return r; - } - - for (i = 1; i < argc; i++) { - const char *path = NULL; - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetImage", - &error, - &reply, - "s", argv[i]); - if (r < 0) { - log_error("Could not get path to image: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - if (properties) - r = show_image_properties(bus, path, &new_line); - else - r = show_image_info(bus, path, &new_line); - } - - return r; -} - -static int kill_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - if (!arg_kill_who) - arg_kill_who = "all"; - - for (i = 1; i < argc; i++) { - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "KillMachine", - &error, - NULL, - "ssi", argv[i], arg_kill_who, arg_signal); - if (r < 0) { - log_error("Could not kill machine: %s", bus_error_message(&error, -r)); - return r; - } - } - - return 0; -} - -static int reboot_machine(int argc, char *argv[], void *userdata) { - arg_kill_who = "leader"; - arg_signal = SIGINT; /* sysvinit + systemd */ - - return kill_machine(argc, argv, userdata); -} - -static int poweroff_machine(int argc, char *argv[], void *userdata) { - arg_kill_who = "leader"; - arg_signal = SIGRTMIN+4; /* only systemd */ - - return kill_machine(argc, argv, userdata); -} - -static int terminate_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - for (i = 1; i < argc; i++) { - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "TerminateMachine", - &error, - NULL, - "s", argv[i]); - if (r < 0) { - log_error("Could not terminate machine: %s", bus_error_message(&error, -r)); - return r; - } - } - - return 0; -} - -static int copy_files(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *abs_host_path = NULL; - char *dest, *host_path, *container_path; - sd_bus *bus = userdata; - bool copy_from; - int r; - - assert(bus); - - polkit_agent_open_if_enabled(); - - copy_from = streq(argv[0], "copy-from"); - dest = argv[3] ?: argv[2]; - host_path = copy_from ? dest : argv[2]; - container_path = copy_from ? argv[2] : dest; - - if (!path_is_absolute(host_path)) { - r = path_make_absolute_cwd(host_path, &abs_host_path); - if (r < 0) - return log_error_errno(r, "Failed to make path absolute: %m"); - - host_path = abs_host_path; - } - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - copy_from ? "CopyFromMachine" : "CopyToMachine", - &error, - NULL, - "sss", - argv[1], - copy_from ? container_path : host_path, - copy_from ? host_path : container_path); - if (r < 0) - return log_error_errno(r, "Failed to copy: %s", bus_error_message(&error, r)); - - return 0; -} - -static int bind_mount(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "BindMountMachine", - &error, - NULL, - "sssbb", - argv[1], - argv[2], - argv[3], - arg_read_only, - arg_mkdir); - if (r < 0) { - log_error("Failed to bind mount: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - PTYForward ** forward = (PTYForward**) userdata; - int r; - - assert(m); - assert(forward); - - if (*forward) { - /* If the forwarder is already initialized, tell it to - * exit on the next vhangup(), so that we still flush - * out what might be queued and exit then. */ - - r = pty_forward_set_ignore_vhangup(*forward, false); - if (r >= 0) - return 0; - - log_error_errno(r, "Failed to set ignore_vhangup flag: %m"); - } - - /* On error, or when the forwarder is not initialized yet, quit immediately */ - sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE); - return 0; -} - -static int process_forward(sd_event *event, PTYForward **forward, int master, PTYForwardFlags flags, const char *name) { - char last_char = 0; - bool machine_died; - int ret = 0, r; - - assert(event); - assert(master >= 0); - assert(name); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); - - if (streq(name, ".host")) - log_info("Connected to the local host. Press ^] three times within 1s to exit session."); - else - log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name); - - sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); - sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); - - r = pty_forward_new(event, master, flags, forward); - if (r < 0) - return log_error_errno(r, "Failed to create PTY forwarder: %m"); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - pty_forward_get_last_char(*forward, &last_char); - - machine_died = - (flags & PTY_FORWARD_IGNORE_VHANGUP) && - pty_forward_get_ignore_vhangup(*forward) == 0; - - *forward = pty_forward_free(*forward); - - if (last_char != '\n') - fputc('\n', stdout); - - if (machine_died) - log_info("Machine %s terminated.", name); - else if (streq(name, ".host")) - log_info("Connection to the local host terminated."); - else - log_info("Connection to machine %s terminated.", name); - - sd_event_get_exit_code(event, &ret); - return ret; -} - -static int login_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - int master = -1, r; - sd_bus *bus = userdata; - const char *pty, *match, *machine; - - assert(bus); - - if (!strv_isempty(arg_setenv) || arg_uid) { - log_error("--setenv= and --uid= are not supported for 'login'. Use 'shell' instead."); - return -EINVAL; - } - - if (arg_transport != BUS_TRANSPORT_LOCAL && - arg_transport != BUS_TRANSPORT_MACHINE) { - log_error("Login only supported on local machines."); - return -EOPNOTSUPP; - } - - polkit_agent_open_if_enabled(); - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1]; - - match = strjoina("type='signal'," - "sender='org.freedesktop.machine1'," - "path='/org/freedesktop/machine1',", - "interface='org.freedesktop.machine1.Manager'," - "member='MachineRemoved'," - "arg0='", machine, "'"); - - r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); - if (r < 0) - return log_error_errno(r, "Failed to add machine removal match: %m"); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "OpenMachineLogin", - &error, - &reply, - "s", machine); - if (r < 0) { - log_error("Failed to get login PTY: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "hs", &master, &pty); - if (r < 0) - return bus_log_parse_error(r); - - return process_forward(event, &forward, master, PTY_FORWARD_IGNORE_VHANGUP, machine); -} - -static int shell_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - int master = -1, r; - sd_bus *bus = userdata; - const char *pty, *match, *machine, *path, *uid = NULL; - - assert(bus); - - if (arg_transport != BUS_TRANSPORT_LOCAL && - arg_transport != BUS_TRANSPORT_MACHINE) { - log_error("Shell only supported on local machines."); - return -EOPNOTSUPP; - } - - /* Pass $TERM to shell session, if not explicitly specified. */ - if (!strv_find_prefix(arg_setenv, "TERM=")) { - const char *t; - - t = strv_find_prefix(environ, "TERM="); - if (t) { - if (strv_extend(&arg_setenv, t) < 0) - return log_oom(); - } - } - - polkit_agent_open_if_enabled(); - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - machine = argc < 2 || isempty(argv[1]) ? NULL : argv[1]; - - if (arg_uid) - uid = arg_uid; - else if (machine) { - const char *at; - - at = strchr(machine, '@'); - if (at) { - uid = strndupa(machine, at - machine); - machine = at + 1; - } - } - - if (isempty(machine)) - machine = ".host"; - - match = strjoina("type='signal'," - "sender='org.freedesktop.machine1'," - "path='/org/freedesktop/machine1',", - "interface='org.freedesktop.machine1.Manager'," - "member='MachineRemoved'," - "arg0='", machine, "'"); - - r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); - if (r < 0) - return log_error_errno(r, "Failed to add machine removal match: %m"); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "OpenMachineShell"); - if (r < 0) - return bus_log_create_error(r); - - path = argc < 3 || isempty(argv[2]) ? NULL : argv[2]; - - r = sd_bus_message_append(m, "sss", machine, uid, path); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, arg_setenv); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to get shell PTY: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "hs", &master, &pty); - if (r < 0) - return bus_log_parse_error(r); - - return process_forward(event, &forward, master, 0, machine); -} - -static int remove_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - for (i = 1; i < argc; i++) { - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "RemoveImage", - &error, - NULL, - "s", argv[i]); - if (r < 0) { - log_error("Could not remove image: %s", bus_error_message(&error, -r)); - return r; - } - } - - return 0; -} - -static int rename_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r; - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "RenameImage", - &error, - NULL, - "ss", argv[1], argv[2]); - if (r < 0) { - log_error("Could not rename image: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int clone_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r; - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "CloneImage", - &error, - NULL, - "ssb", argv[1], argv[2], arg_read_only); - if (r < 0) { - log_error("Could not clone image: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int read_only_image(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int b = true, r; - - if (argc > 2) { - b = parse_boolean(argv[2]); - if (b < 0) { - log_error("Failed to parse boolean argument: %s", argv[2]); - return -EINVAL; - } - } - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "MarkImageReadOnly", - &error, - NULL, - "sb", argv[1], b); - if (r < 0) { - log_error("Could not mark image read-only: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int make_service_name(const char *name, char **ret) { - _cleanup_free_ char *e = NULL; - int r; - - assert(name); - assert(ret); - - if (!machine_name_is_valid(name)) { - log_error("Invalid machine name %s.", name); - return -EINVAL; - } - - e = unit_name_escape(name); - if (!e) - return log_oom(); - - r = unit_name_build("systemd-nspawn", e, ".service", ret); - if (r < 0) - return log_error_errno(r, "Failed to build unit name: %m"); - - return 0; -} - -static int start_machine(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_oom(); - - for (i = 1; i < argc; i++) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *unit = NULL; - const char *object; - - r = make_service_name(argv[i], &unit); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartUnit", - &error, - &reply, - "ss", unit, "fail"); - if (r < 0) { - log_error("Failed to start unit: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "o", &object); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_add(w, object); - if (r < 0) - return log_oom(); - } - - r = bus_wait_for_jobs(w, arg_quiet, NULL); - if (r < 0) - return r; - - return 0; -} - -static int enable_machine(int argc, char *argv[], void *userdata) { - _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; - int carries_install_info = 0; - const char *method = NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - method = streq(argv[0], "enable") ? "EnableUnitFiles" : "DisableUnitFiles"; - - 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); - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return bus_log_create_error(r); - - for (i = 1; i < argc; i++) { - _cleanup_free_ char *unit = NULL; - - r = make_service_name(argv[i], &unit); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "s", unit); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - if (streq(argv[0], "enable")) - r = sd_bus_message_append(m, "bb", false, false); - else - r = sd_bus_message_append(m, "b", false); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to enable or disable unit: %s", bus_error_message(&error, -r)); - return r; - } - - if (streq(argv[0], "enable")) { - r = sd_bus_message_read(reply, "b", carries_install_info); - if (r < 0) - return bus_log_parse_error(r); - } - - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Reload", - &error, - NULL, - NULL); - if (r < 0) { - log_error("Failed to reload daemon: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char **our_path = userdata, *line; - unsigned priority; - int r; - - assert(m); - assert(our_path); - - r = sd_bus_message_read(m, "us", &priority, &line); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - if (!streq_ptr(*our_path, sd_bus_message_get_path(m))) - return 0; - - if (arg_quiet && LOG_PRI(priority) >= LOG_INFO) - return 0; - - log_full(priority, "%s", line); - return 0; -} - -static int match_transfer_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char **our_path = userdata, *path, *result; - uint32_t id; - int r; - - assert(m); - assert(our_path); - - r = sd_bus_message_read(m, "uos", &id, &path, &result); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - if (!streq_ptr(*our_path, path)) - return 0; - - sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), !streq_ptr(result, "done")); - return 0; -} - -static int transfer_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - assert(s); - assert(si); - - if (!arg_quiet) - log_info("Continuing download in the background. Use \"machinectl cancel-transfer %" PRIu32 "\" to abort transfer.", PTR_TO_UINT32(userdata)); - - sd_event_exit(sd_event_source_get_event(s), EINTR); - return 0; -} - -static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { - _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL; - _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_event_unrefp) sd_event* event = NULL; - const char *path = NULL; - uint32_t id; - int r; - - assert(bus); - assert(m); - - polkit_agent_open_if_enabled(); - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - r = sd_bus_add_match( - bus, - &slot_job_removed, - "type='signal'," - "sender='org.freedesktop.import1'," - "interface='org.freedesktop.import1.Manager'," - "member='TransferRemoved'," - "path='/org/freedesktop/import1'", - match_transfer_removed, &path); - if (r < 0) - return log_error_errno(r, "Failed to install match: %m"); - - r = sd_bus_add_match( - bus, - &slot_log_message, - "type='signal'," - "sender='org.freedesktop.import1'," - "interface='org.freedesktop.import1.Transfer'," - "member='LogMessage'", - match_log_message, &path); - if (r < 0) - return log_error_errno(r, "Failed to install match: %m"); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed transfer image: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "uo", &id, &path); - if (r < 0) - return bus_log_parse_error(r); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - - if (!arg_quiet) - log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id); - - sd_event_add_signal(event, NULL, SIGINT, transfer_signal_handler, UINT32_TO_PTR(id)); - sd_event_add_signal(event, NULL, SIGTERM, transfer_signal_handler, UINT32_TO_PTR(id)); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - return -r; -} - -static int import_tar(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *ll = NULL; - _cleanup_close_ int fd = -1; - const char *local = NULL, *path = NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - if (argc >= 2) - path = argv[1]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - if (argc >= 3) - local = argv[2]; - else if (path) - local = basename(path); - if (isempty(local) || streq(local, "-")) - local = NULL; - - if (!local) { - log_error("Need either path or local name."); - return -EINVAL; - } - - r = tar_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local name %s is not a suitable machine name.", local); - return -EINVAL; - } - - if (path) { - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ImportTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "hsbb", - fd >= 0 ? fd : STDIN_FILENO, - local, - arg_force, - arg_read_only); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int import_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *ll = NULL; - _cleanup_close_ int fd = -1; - const char *local = NULL, *path = NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - if (argc >= 2) - path = argv[1]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - if (argc >= 3) - local = argv[2]; - else if (path) - local = basename(path); - if (isempty(local) || streq(local, "-")) - local = NULL; - - if (!local) { - log_error("Need either path or local name."); - return -EINVAL; - } - - r = raw_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local name %s is not a suitable machine name.", local); - return -EINVAL; - } - - if (path) { - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ImportRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "hsbb", - fd >= 0 ? fd : STDIN_FILENO, - local, - arg_force, - arg_read_only); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static void determine_compression_from_filename(const char *p) { - if (arg_format) - return; - - if (!p) - return; - - if (endswith(p, ".xz")) - arg_format = "xz"; - else if (endswith(p, ".gz")) - arg_format = "gzip"; - else if (endswith(p, ".bz2")) - arg_format = "bzip2"; -} - -static int export_tar(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_close_ int fd = -1; - const char *local = NULL, *path = NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - local = argv[1]; - if (!machine_name_is_valid(local)) { - log_error("Machine name %s is not valid.", local); - return -EINVAL; - } - - if (argc >= 3) - path = argv[2]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - if (path) { - determine_compression_from_filename(path); - - fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ExportTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "shs", - local, - fd >= 0 ? fd : STDOUT_FILENO, - arg_format); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int export_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_close_ int fd = -1; - const char *local = NULL, *path = NULL; - sd_bus *bus = userdata; - int r; - - assert(bus); - - local = argv[1]; - if (!machine_name_is_valid(local)) { - log_error("Machine name %s is not valid.", local); - return -EINVAL; - } - - if (argc >= 3) - path = argv[2]; - if (isempty(path) || streq(path, "-")) - path = NULL; - - if (path) { - determine_compression_from_filename(path); - - fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", path); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ExportRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "shs", - local, - fd >= 0 ? fd : STDOUT_FILENO, - arg_format); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int pull_tar(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL, *ll = NULL; - const char *local, *remote; - sd_bus *bus = userdata; - int r; - - assert(bus); - - remote = argv[1]; - if (!http_url_is_valid(remote)) { - log_error("URL '%s' is not valid.", remote); - return -EINVAL; - } - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(remote, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of URL: %m"); - - local = l; - } - - if (isempty(local) || streq(local, "-")) - local = NULL; - - if (local) { - r = tar_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local name %s is not a suitable machine name.", local); - return -EINVAL; - } - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "PullTar"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssb", - remote, - local, - import_verify_to_string(arg_verify), - arg_force); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -static int pull_raw(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_free_ char *l = NULL, *ll = NULL; - const char *local, *remote; - sd_bus *bus = userdata; - int r; - - assert(bus); - - remote = argv[1]; - if (!http_url_is_valid(remote)) { - log_error("URL '%s' is not valid.", remote); - return -EINVAL; - } - - if (argc >= 3) - local = argv[2]; - else { - r = import_url_last_component(remote, &l); - if (r < 0) - return log_error_errno(r, "Failed to get final component of URL: %m"); - - local = l; - } - - if (isempty(local) || streq(local, "-")) - local = NULL; - - if (local) { - r = raw_strip_suffixes(local, &ll); - if (r < 0) - return log_oom(); - - local = ll; - - if (!machine_name_is_valid(local)) { - log_error("Local name %s is not a suitable machine name.", local); - return -EINVAL; - } - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "PullRaw"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sssb", - remote, - local, - import_verify_to_string(arg_verify), - arg_force); - if (r < 0) - return bus_log_create_error(r); - - return transfer_image_common(bus, m); -} - -typedef struct TransferInfo { - uint32_t id; - const char *type; - const char *remote; - const char *local; - double progress; -} TransferInfo; - -static int compare_transfer_info(const void *a, const void *b) { - const TransferInfo *x = a, *y = b; - - return strcmp(x->local, y->local); -} - -static int list_transfers(int argc, char *argv[], void *userdata) { - size_t max_type = strlen("TYPE"), max_local = strlen("LOCAL"), max_remote = strlen("REMOTE"); - _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_ TransferInfo *transfers = NULL; - size_t n_transfers = 0, n_allocated = 0, j; - const char *type, *remote, *local, *object; - sd_bus *bus = userdata; - uint32_t id, max_id = 0; - double progress; - int r; - - pager_open_if_enabled(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "ListTransfers", - &error, - &reply, - NULL); - if (r < 0) { - log_error("Could not get transfers: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_enter_container(reply, 'a', "(usssdo)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(usssdo)", &id, &type, &remote, &local, &progress, &object)) > 0) { - size_t l; - - if (!GREEDY_REALLOC(transfers, n_allocated, n_transfers + 1)) - return log_oom(); - - transfers[n_transfers].id = id; - transfers[n_transfers].type = type; - transfers[n_transfers].remote = remote; - transfers[n_transfers].local = local; - transfers[n_transfers].progress = progress; - - l = strlen(type); - if (l > max_type) - max_type = l; - - l = strlen(remote); - if (l > max_remote) - max_remote = l; - - l = strlen(local); - if (l > max_local) - max_local = l; - - if (id > max_id) - max_id = id; - - n_transfers ++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - qsort_safe(transfers, n_transfers, sizeof(TransferInfo), compare_transfer_info); - - if (arg_legend) - printf("%-*s %-*s %-*s %-*s %-*s\n", - (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID", - (int) 7, "PERCENT", - (int) max_type, "TYPE", - (int) max_local, "LOCAL", - (int) max_remote, "REMOTE"); - - for (j = 0; j < n_transfers; j++) - printf("%*" PRIu32 " %*u%% %-*s %-*s %-*s\n", - (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), transfers[j].id, - (int) 6, (unsigned) (transfers[j].progress * 100), - (int) max_type, transfers[j].type, - (int) max_local, transfers[j].local, - (int) max_remote, transfers[j].remote); - - if (arg_legend) - printf("\n%zu transfers listed.\n", n_transfers); - - return 0; -} - -static int cancel_transfer(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - int r, i; - - assert(bus); - - polkit_agent_open_if_enabled(); - - for (i = 1; i < argc; i++) { - uint32_t id; - - r = safe_atou32(argv[i], &id); - if (r < 0) - return log_error_errno(r, "Failed to parse transfer id: %s", argv[i]); - - r = sd_bus_call_method( - bus, - "org.freedesktop.import1", - "/org/freedesktop/import1", - "org.freedesktop.import1.Manager", - "CancelTransfer", - &error, - NULL, - "u", id); - if (r < 0) { - log_error("Could not cancel transfer: %s", bus_error_message(&error, -r)); - return r; - } - } - - return 0; -} - -static int set_limit(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus = userdata; - uint64_t limit; - int r; - - if (STR_IN_SET(argv[argc-1], "-", "none", "infinity")) - limit = (uint64_t) -1; - else { - r = parse_size(argv[argc-1], 1024, &limit); - if (r < 0) - return log_error("Failed to parse size: %s", argv[argc-1]); - } - - if (argc > 2) - /* With two arguments changes the quota limit of the - * specified image */ - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "SetImageLimit", - &error, - NULL, - "st", argv[1], limit); - else - /* With one argument changes the pool quota limit */ - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "SetPoolLimit", - &error, - NULL, - "t", limit); - - if (r < 0) { - log_error("Could not set limit: %s", bus_error_message(&error, -r)); - return r; - } - - return 0; -} - -static int help(int argc, char *argv[], void *userdata) { - - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Send control commands to or query the virtual machine and container\n" - "registration manager.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-legend Do not show the headers and footers\n" - " --no-ask-password Do not ask for system passwords\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " -p --property=NAME Show only properties by this name\n" - " -q --quiet Suppress output\n" - " -a --all Show all properties, including empty ones\n" - " -l --full Do not ellipsize output\n" - " --kill-who=WHO Who to send signal to\n" - " -s --signal=SIGNAL Which signal to send\n" - " --uid=USER Specify user ID to invoke shell as\n" - " --setenv=VAR=VALUE Add an environment variable for shell\n" - " --read-only Create read-only bind mount\n" - " --mkdir Create directory before bind mounting, if missing\n" - " -n --lines=INTEGER Number of journal entries to show\n" - " -o --output=STRING Change journal output mode (short,\n" - " short-monotonic, verbose, export, json,\n" - " json-pretty, json-sse, cat)\n" - " --verify=MODE Verification mode for downloaded images (no,\n" - " checksum, signature)\n" - " --force Download image even if already exists\n\n" - "Machine Commands:\n" - " list List running VMs and containers\n" - " status NAME... Show VM/container details\n" - " show [NAME...] Show properties of one or more VMs/containers\n" - " start NAME... Start container as a service\n" - " login [NAME] Get a login prompt in a container or on the\n" - " local host\n" - " shell [[USER@]NAME [COMMAND...]]\n" - " Invoke a shell (or other command) in a container\n" - " or on the local host\n" - " enable NAME... Enable automatic container start at boot\n" - " disable NAME... Disable automatic container start at boot\n" - " poweroff NAME... Power off one or more containers\n" - " reboot NAME... Reboot one or more containers\n" - " terminate NAME... Terminate one or more VMs/containers\n" - " kill NAME... Send signal to processes of a VM/container\n" - " copy-to NAME PATH [PATH] Copy files from the host to a container\n" - " copy-from NAME PATH [PATH] Copy files from a container to the host\n" - " bind NAME PATH [PATH] Bind mount a path from the host into a container\n\n" - "Image Commands:\n" - " list-images Show available container and VM images\n" - " image-status [NAME...] Show image details\n" - " show-image [NAME...] Show properties of image\n" - " clone NAME NAME Clone an image\n" - " rename NAME NAME Rename an image\n" - " read-only NAME [BOOL] Mark or unmark image read-only\n" - " remove NAME... Remove an image\n" - " set-limit [NAME] BYTES Set image or pool size limit (disk quota)\n\n" - "Image Transfer Commands:\n" - " pull-tar URL [NAME] Download a TAR container image\n" - " pull-raw URL [NAME] Download a RAW container or VM image\n" - " import-tar FILE [NAME] Import a local TAR container image\n" - " import-raw FILE [NAME] Import a local RAW container or VM image\n" - " export-tar NAME [FILE] Export a TAR container image locally\n" - " export-raw NAME [FILE] Export a RAW container or VM image locally\n" - " list-transfers Show list of downloads in progress\n" - " cancel-transfer Cancel a download\n" - , program_invocation_short_name); - - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_NO_LEGEND, - ARG_KILL_WHO, - ARG_READ_ONLY, - ARG_MKDIR, - ARG_NO_ASK_PASSWORD, - ARG_VERIFY, - ARG_FORCE, - ARG_FORMAT, - ARG_UID, - ARG_SETENV, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "property", required_argument, NULL, 'p' }, - { "all", no_argument, NULL, 'a' }, - { "full", no_argument, NULL, 'l' }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "kill-who", required_argument, NULL, ARG_KILL_WHO }, - { "signal", required_argument, NULL, 's' }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "mkdir", no_argument, NULL, ARG_MKDIR }, - { "quiet", no_argument, NULL, 'q' }, - { "lines", required_argument, NULL, 'n' }, - { "output", required_argument, NULL, 'o' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "verify", required_argument, NULL, ARG_VERIFY }, - { "force", no_argument, NULL, ARG_FORCE }, - { "format", required_argument, NULL, ARG_FORMAT }, - { "uid", required_argument, NULL, ARG_UID }, - { "setenv", required_argument, NULL, ARG_SETENV }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hp:als:H:M:qn:o:", options, NULL)) >= 0) - - switch (c) { - - case 'h': - return help(0, NULL, NULL); - - case ARG_VERSION: - return version(); - - case 'p': - r = strv_extend(&arg_property, optarg); - if (r < 0) - return log_oom(); - - /* If the user asked for a particular - * property, show it to him, even if it is - * empty. */ - arg_all = true; - break; - - case 'a': - arg_all = true; - break; - - case 'l': - arg_full = true; - break; - - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) { - log_error("Failed to parse lines '%s'", optarg); - return -EINVAL; - } - break; - - case 'o': - arg_output = output_mode_from_string(optarg); - if (arg_output < 0) { - log_error("Unknown output '%s'.", optarg); - return -EINVAL; - } - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case ARG_NO_LEGEND: - arg_legend = false; - break; - - case ARG_KILL_WHO: - arg_kill_who = optarg; - break; - - case 's': - arg_signal = signal_from_string_try_harder(optarg); - if (arg_signal < 0) { - log_error("Failed to parse signal string %s.", optarg); - return -EINVAL; - } - break; - - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case ARG_READ_ONLY: - arg_read_only = true; - break; - - case ARG_MKDIR: - arg_mkdir = true; - break; - - case 'q': - arg_quiet = true; - break; - - case ARG_VERIFY: - arg_verify = import_verify_from_string(optarg); - if (arg_verify < 0) { - log_error("Failed to parse --verify= setting: %s", optarg); - return -EINVAL; - } - break; - - case ARG_FORCE: - arg_force = true; - break; - - case ARG_FORMAT: - if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2")) { - log_error("Unknown format: %s", optarg); - return -EINVAL; - } - - arg_format = optarg; - break; - - case ARG_UID: - arg_uid = optarg; - break; - - case ARG_SETENV: - if (!env_assignment_is_valid(optarg)) { - log_error("Environment assignment invalid: %s", optarg); - return -EINVAL; - } - - r = strv_extend(&arg_setenv, optarg); - if (r < 0) - return log_oom(); - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -static int machinectl_main(int argc, char *argv[], sd_bus *bus) { - - static const Verb verbs[] = { - { "help", VERB_ANY, VERB_ANY, 0, help }, - { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines }, - { "list-images", VERB_ANY, 1, 0, list_images }, - { "status", 2, VERB_ANY, 0, show_machine }, - { "image-status", VERB_ANY, VERB_ANY, 0, show_image }, - { "show", VERB_ANY, VERB_ANY, 0, show_machine }, - { "show-image", VERB_ANY, VERB_ANY, 0, show_image }, - { "terminate", 2, VERB_ANY, 0, terminate_machine }, - { "reboot", 2, VERB_ANY, 0, reboot_machine }, - { "poweroff", 2, VERB_ANY, 0, poweroff_machine }, - { "kill", 2, VERB_ANY, 0, kill_machine }, - { "login", VERB_ANY, 2, 0, login_machine }, - { "shell", VERB_ANY, VERB_ANY, 0, shell_machine }, - { "bind", 3, 4, 0, bind_mount }, - { "copy-to", 3, 4, 0, copy_files }, - { "copy-from", 3, 4, 0, copy_files }, - { "remove", 2, VERB_ANY, 0, remove_image }, - { "rename", 3, 3, 0, rename_image }, - { "clone", 3, 3, 0, clone_image }, - { "read-only", 2, 3, 0, read_only_image }, - { "start", 2, VERB_ANY, 0, start_machine }, - { "enable", 2, VERB_ANY, 0, enable_machine }, - { "disable", 2, VERB_ANY, 0, enable_machine }, - { "import-tar", 2, 3, 0, import_tar }, - { "import-raw", 2, 3, 0, import_raw }, - { "export-tar", 2, 3, 0, export_tar }, - { "export-raw", 2, 3, 0, export_raw }, - { "pull-tar", 2, 3, 0, pull_tar }, - { "pull-raw", 2, 3, 0, pull_raw }, - { "list-transfers", VERB_ANY, 1, 0, list_transfers }, - { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer }, - { "set-limit", 2, 3, 0, set_limit }, - {} - }; - - return dispatch_verb(argc, argv, verbs, bus); -} - -int main(int argc, char*argv[]) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = bus_connect_transport(arg_transport, arg_host, false, &bus); - if (r < 0) { - log_error_errno(r, "Failed to create bus connection: %m"); - goto finish; - } - - sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); - - r = machinectl_main(argc, argv, bus); - -finish: - pager_close(); - polkit_agent_close(); - - strv_free(arg_property); - strv_free(arg_setenv); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c deleted file mode 100644 index b933099330..0000000000 --- a/src/machine/machined-dbus.c +++ /dev/null @@ -1,1551 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include - -#include "sd-id128.h" - -#include "alloc-util.h" -#include "btrfs-util.h" -#include "bus-common-errors.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "hostname-util.h" -#include "image-dbus.h" -#include "io-util.h" -#include "machine-dbus.h" -#include "machine-image.h" -#include "machine-pool.h" -#include "machined.h" -#include "path-util.h" -#include "process-util.h" -#include "stdio-util.h" -#include "strv.h" -#include "unit-name.h" -#include "user-util.h" - -static int property_get_pool_path( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - assert(bus); - assert(reply); - - return sd_bus_message_append(reply, "s", "/var/lib/machines"); -} - -static int property_get_pool_usage( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_close_ int fd = -1; - uint64_t usage = (uint64_t) -1; - struct stat st; - - assert(bus); - assert(reply); - - /* We try to read the quota info from /var/lib/machines, as - * well as the usage of the loopback file - * /var/lib/machines.raw, and pick the larger value. */ - - fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (fd >= 0) { - BtrfsQuotaInfo q; - - if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) - usage = q.referenced; - } - - if (stat("/var/lib/machines.raw", &st) >= 0) { - if (usage == (uint64_t) -1 || st.st_blocks * 512ULL > usage) - usage = st.st_blocks * 512ULL; - } - - return sd_bus_message_append(reply, "t", usage); -} - -static int property_get_pool_limit( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - _cleanup_close_ int fd = -1; - uint64_t size = (uint64_t) -1; - struct stat st; - - assert(bus); - assert(reply); - - /* We try to read the quota limit from /var/lib/machines, as - * well as the size of the loopback file - * /var/lib/machines.raw, and pick the smaller value. */ - - fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (fd >= 0) { - BtrfsQuotaInfo q; - - if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) - size = q.referenced_max; - } - - if (stat("/var/lib/machines.raw", &st) >= 0) { - if (size == (uint64_t) -1 || (uint64_t) st.st_size < size) - size = st.st_size; - } - - return sd_bus_message_append(reply, "t", size); -} - -static int method_get_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *p = NULL; - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - p = machine_bus_path(machine); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", p); -} - -static int method_get_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *p = NULL; - Manager *m = userdata; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - r = image_find(name, NULL); - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); - if (r < 0) - return r; - - p = image_bus_path(name); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", p); -} - -static int method_get_machine_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *p = NULL; - Manager *m = userdata; - Machine *machine = NULL; - pid_t pid; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(pid_t) == sizeof(uint32_t)); - - r = sd_bus_message_read(message, "u", &pid); - if (r < 0) - return r; - - if (pid < 0) - return -EINVAL; - - if (pid == 0) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - r = sd_bus_creds_get_pid(creds, &pid); - if (r < 0) - return r; - } - - r = manager_get_machine_by_pid(m, pid, &machine); - if (r < 0) - return r; - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_MACHINE_FOR_PID, "PID "PID_FMT" does not belong to any known machine", pid); - - p = machine_bus_path(machine); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", p); -} - -static int method_list_machines(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - Manager *m = userdata; - Machine *machine; - Iterator i; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - r = sd_bus_message_open_container(reply, 'a', "(ssso)"); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - HASHMAP_FOREACH(machine, m->machines, i) { - _cleanup_free_ char *p = NULL; - - p = machine_bus_path(machine); - if (!p) - return -ENOMEM; - - r = sd_bus_message_append(reply, "(ssso)", - machine->name, - strempty(machine_class_to_string(machine->class)), - machine->service, - p); - if (r < 0) - return sd_bus_error_set_errno(error, r); - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - return sd_bus_send(NULL, reply, NULL); -} - -static int method_create_or_register_machine(Manager *manager, sd_bus_message *message, bool read_network, Machine **_m, sd_bus_error *error) { - const char *name, *service, *class, *root_directory; - const int32_t *netif = NULL; - MachineClass c; - uint32_t leader; - sd_id128_t id; - const void *v; - Machine *m; - size_t n, n_netif = 0; - int r; - - assert(manager); - assert(message); - assert(_m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - if (!machine_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine name"); - - r = sd_bus_message_read_array(message, 'y', &v, &n); - if (r < 0) - return r; - if (n == 0) - id = SD_ID128_NULL; - else if (n == 16) - memcpy(&id, v, n); - else - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine ID parameter"); - - r = sd_bus_message_read(message, "ssus", &service, &class, &leader, &root_directory); - if (r < 0) - return r; - - if (read_network) { - size_t i; - - r = sd_bus_message_read_array(message, 'i', (const void**) &netif, &n_netif); - if (r < 0) - return r; - - n_netif /= sizeof(int32_t); - - for (i = 0; i < n_netif; i++) { - if (netif[i] <= 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid network interface index %i", netif[i]); - } - } - - if (isempty(class)) - c = _MACHINE_CLASS_INVALID; - else { - c = machine_class_from_string(class); - if (c < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter"); - } - - if (leader == 1) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid leader PID"); - - if (!isempty(root_directory) && !path_is_absolute(root_directory)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root directory must be empty or an absolute path"); - - if (leader == 0) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - - r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); - if (r < 0) - return r; - - assert_cc(sizeof(uint32_t) == sizeof(pid_t)); - - r = sd_bus_creds_get_pid(creds, (pid_t*) &leader); - if (r < 0) - return r; - } - - if (hashmap_get(manager->machines, name)) - return sd_bus_error_setf(error, BUS_ERROR_MACHINE_EXISTS, "Machine '%s' already exists", name); - - r = manager_add_machine(manager, name, &m); - if (r < 0) - return r; - - m->leader = leader; - m->class = c; - m->id = id; - - if (!isempty(service)) { - m->service = strdup(service); - if (!m->service) { - r = -ENOMEM; - goto fail; - } - } - - if (!isempty(root_directory)) { - m->root_directory = strdup(root_directory); - if (!m->root_directory) { - r = -ENOMEM; - goto fail; - } - } - - if (n_netif > 0) { - assert_cc(sizeof(int32_t) == sizeof(int)); - m->netif = memdup(netif, sizeof(int32_t) * n_netif); - if (!m->netif) { - r = -ENOMEM; - goto fail; - } - - m->n_netif = n_netif; - } - - *_m = m; - - return 1; - -fail: - machine_add_to_gc_queue(m); - return r; -} - -static int method_create_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) { - Manager *manager = userdata; - Machine *m = NULL; - int r; - - assert(message); - assert(manager); - - r = method_create_or_register_machine(manager, message, read_network, &m, error); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(message, 'a', "(sv)"); - if (r < 0) - goto fail; - - r = machine_start(m, message, error); - if (r < 0) - goto fail; - - m->create_message = sd_bus_message_ref(message); - return 1; - -fail: - machine_add_to_gc_queue(m); - return r; -} - -static int method_create_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_create_machine_internal(message, true, userdata, error); -} - -static int method_create_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_create_machine_internal(message, false, userdata, error); -} - -static int method_register_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) { - Manager *manager = userdata; - _cleanup_free_ char *p = NULL; - Machine *m = NULL; - int r; - - assert(message); - assert(manager); - - r = method_create_or_register_machine(manager, message, read_network, &m, error); - if (r < 0) - return r; - - r = cg_pid_get_unit(m->leader, &m->unit); - if (r < 0) { - r = sd_bus_error_set_errnof(error, r, "Failed to determine unit of process "PID_FMT" : %s", m->leader, strerror(-r)); - goto fail; - } - - r = machine_start(m, NULL, error); - if (r < 0) - goto fail; - - p = machine_bus_path(m); - if (!p) { - r = -ENOMEM; - goto fail; - } - - return sd_bus_reply_method_return(message, "o", p); - -fail: - machine_add_to_gc_queue(m); - return r; -} - -static int method_register_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_register_machine_internal(message, true, userdata, error); -} - -static int method_register_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return method_register_machine_internal(message, false, userdata, error); -} - -static int method_terminate_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_terminate(message, machine, error); -} - -static int method_kill_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_kill(message, machine, error); -} - -static int method_get_machine_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_get_addresses(message, machine, error); -} - -static int method_get_machine_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_get_os_release(message, machine, error); -} - -static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(image_hashmap_freep) Hashmap *images = NULL; - Manager *m = userdata; - Image *image; - Iterator i; - int r; - - assert(message); - assert(m); - - images = hashmap_new(&string_hash_ops); - if (!images) - return -ENOMEM; - - r = image_discover(images); - if (r < 0) - return r; - - r = sd_bus_message_new_method_return(message, &reply); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(ssbttto)"); - if (r < 0) - return r; - - HASHMAP_FOREACH(image, images, i) { - _cleanup_free_ char *p = NULL; - - p = image_bus_path(image->name); - if (!p) - return -ENOMEM; - - r = sd_bus_message_append(reply, "(ssbttto)", - image->name, - image_type_to_string(image->type), - image->read_only, - image->crtime, - image->mtime, - image->usage, - p); - 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_open_machine_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_open_pty(message, machine, error); -} - -static int method_open_machine_login(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_open_login(message, machine, error); -} - -static int method_open_machine_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_open_shell(message, machine, error); -} - -static int method_bind_mount_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_bind_mount(message, machine, error); -} - -static int method_copy_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - const char *name; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - return bus_machine_method_copy(message, machine, error); -} - -static int method_remove_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image* i = NULL; - const char *name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (!image_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); - - r = image_find(name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); - - i->userdata = userdata; - return bus_image_method_remove(message, i, error); -} - -static int method_rename_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image* i = NULL; - const char *old_name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &old_name); - if (r < 0) - return r; - - if (!image_name_is_valid(old_name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name); - - r = image_find(old_name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name); - - i->userdata = userdata; - return bus_image_method_rename(message, i, error); -} - -static int method_clone_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image *i = NULL; - const char *old_name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &old_name); - if (r < 0) - return r; - - if (!image_name_is_valid(old_name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name); - - r = image_find(old_name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name); - - i->userdata = userdata; - return bus_image_method_clone(message, i, error); -} - -static int method_mark_image_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image *i = NULL; - const char *name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (!image_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); - - r = image_find(name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); - - i->userdata = userdata; - return bus_image_method_mark_read_only(message, i, error); -} - -static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - uint64_t limit; - int r; - - assert(message); - - r = sd_bus_message_read(message, "t", &limit); - if (r < 0) - return r; - if (!FILE_SIZE_VALID_OR_INFINITY(limit)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range"); - - r = bus_verify_polkit_async( - message, - CAP_SYS_ADMIN, - "org.freedesktop.machine1.manage-machines", - NULL, - false, - UID_INVALID, - &m->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* Will call us back */ - - /* Set up the machine directory if necessary */ - r = setup_machine_directory(limit, error); - if (r < 0) - return r; - - /* Resize the backing loopback device, if there is one, except if we asked to drop any limit */ - if (limit != (uint64_t) -1) { - r = btrfs_resize_loopback("/var/lib/machines", limit, false); - if (r == -ENOTTY) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs."); - if (r < 0 && r != -ENODEV) /* ignore ENODEV, as that's what is returned if the file system is not on loopback */ - return sd_bus_error_set_errnof(error, r, "Failed to adjust loopback limit: %m"); - } - - (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, limit); - - r = btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, limit); - if (r == -ENOTTY) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs."); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to adjust quota limit: %m"); - - return sd_bus_reply_method_return(message, NULL); -} - -static int method_set_image_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(image_unrefp) Image *i = NULL; - const char *name; - int r; - - assert(message); - - r = sd_bus_message_read(message, "s", &name); - if (r < 0) - return r; - - if (!image_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); - - r = image_find(name, &i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); - - i->userdata = userdata; - return bus_image_method_set_limit(message, i, error); -} - -static int method_map_from_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_fclose_ FILE *f = NULL; - Manager *m = userdata; - const char *name, *p; - Machine *machine; - uint32_t uid; - int r; - - r = sd_bus_message_read(message, "su", &name, &uid); - if (r < 0) - return r; - - if (!uid_is_valid(uid)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - if (machine->class != MACHINE_CONTAINER) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines."); - - p = procfs_file_alloca(machine->leader, "uid_map"); - f = fopen(p, "re"); - if (!f) - return -errno; - - for (;;) { - uid_t uid_base, uid_shift, uid_range, converted; - int k; - - errno = 0; - k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); - if (k < 0 && feof(f)) - break; - if (k != 3) { - if (ferror(f) && errno > 0) - return -errno; - - return -EIO; - } - - if (uid < uid_base || uid >= uid_base + uid_range) - continue; - - converted = uid - uid_base + uid_shift; - if (!uid_is_valid(converted)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); - - return sd_bus_reply_method_return(message, "u", (uint32_t) converted); - } - - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "Machine '%s' has no matching user mappings.", name); -} - -static int method_map_to_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - uid_t uid; - Iterator i; - int r; - - r = sd_bus_message_read(message, "u", &uid); - if (r < 0) - return r; - if (!uid_is_valid(uid)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); - if (uid < 0x10000) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "User " UID_FMT " belongs to host UID range", uid); - - HASHMAP_FOREACH(machine, m->machines, i) { - _cleanup_fclose_ FILE *f = NULL; - char p[strlen("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1]; - - if (machine->class != MACHINE_CONTAINER) - continue; - - xsprintf(p, "/proc/" UID_FMT "/uid_map", machine->leader); - f = fopen(p, "re"); - if (!f) { - log_warning_errno(errno, "Failed top open %s, ignoring,", p); - continue; - } - - for (;;) { - _cleanup_free_ char *o = NULL; - uid_t uid_base, uid_shift, uid_range, converted; - int k; - - errno = 0; - k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); - if (k < 0 && feof(f)) - break; - if (k != 3) { - if (ferror(f) && errno > 0) - return -errno; - - return -EIO; - } - - if (uid < uid_shift || uid >= uid_shift + uid_range) - continue; - - converted = (uid - uid_shift + uid_base); - if (!uid_is_valid(converted)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); - - o = machine_bus_path(machine); - if (!o) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); - } - } - - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "No matching user mapping for " UID_FMT ".", uid); -} - -static int method_map_from_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { - _cleanup_fclose_ FILE *f = NULL; - Manager *m = groupdata; - const char *name, *p; - Machine *machine; - uint32_t gid; - int r; - - r = sd_bus_message_read(message, "su", &name, &gid); - if (r < 0) - return r; - - if (!gid_is_valid(gid)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); - - machine = hashmap_get(m->machines, name); - if (!machine) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); - - if (machine->class != MACHINE_CONTAINER) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines."); - - p = procfs_file_alloca(machine->leader, "gid_map"); - f = fopen(p, "re"); - if (!f) - return -errno; - - for (;;) { - gid_t gid_base, gid_shift, gid_range, converted; - int k; - - errno = 0; - k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); - if (k < 0 && feof(f)) - break; - if (k != 3) { - if (ferror(f) && errno > 0) - return -errno; - - return -EIO; - } - - if (gid < gid_base || gid >= gid_base + gid_range) - continue; - - converted = gid - gid_base + gid_shift; - if (!gid_is_valid(converted)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); - - return sd_bus_reply_method_return(message, "u", (uint32_t) converted); - } - - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Machine '%s' has no matching group mappings.", name); -} - -static int method_map_to_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { - Manager *m = groupdata; - Machine *machine; - gid_t gid; - Iterator i; - int r; - - r = sd_bus_message_read(message, "u", &gid); - if (r < 0) - return r; - if (!gid_is_valid(gid)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); - if (gid < 0x10000) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Group " GID_FMT " belongs to host GID range", gid); - - HASHMAP_FOREACH(machine, m->machines, i) { - _cleanup_fclose_ FILE *f = NULL; - char p[strlen("/proc//gid_map") + DECIMAL_STR_MAX(pid_t) + 1]; - - if (machine->class != MACHINE_CONTAINER) - continue; - - xsprintf(p, "/proc/" GID_FMT "/gid_map", machine->leader); - f = fopen(p, "re"); - if (!f) { - log_warning_errno(errno, "Failed top open %s, ignoring,", p); - continue; - } - - for (;;) { - _cleanup_free_ char *o = NULL; - gid_t gid_base, gid_shift, gid_range, converted; - int k; - - errno = 0; - k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); - if (k < 0 && feof(f)) - break; - if (k != 3) { - if (ferror(f) && errno > 0) - return -errno; - - return -EIO; - } - - if (gid < gid_shift || gid >= gid_shift + gid_range) - continue; - - converted = (gid - gid_shift + gid_base); - if (!gid_is_valid(converted)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); - - o = machine_bus_path(machine); - if (!o) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); - } - } - - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "No matching group mapping for " GID_FMT ".", gid); -} - -const sd_bus_vtable manager_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("PoolPath", "s", property_get_pool_path, 0, 0), - SD_BUS_PROPERTY("PoolUsage", "t", property_get_pool_usage, 0, 0), - SD_BUS_PROPERTY("PoolLimit", "t", property_get_pool_limit, 0, 0), - SD_BUS_METHOD("GetMachine", "s", "o", method_get_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetImage", "s", "o", method_get_image, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetMachineByPID", "u", "o", method_get_machine_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListMachines", NULL, "a(ssso)", method_list_machines, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("ListImages", NULL, "a(ssbttto)", method_list_images, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CreateMachine", "sayssusa(sv)", "o", method_create_machine, 0), - SD_BUS_METHOD("CreateMachineWithNetwork", "sayssusaia(sv)", "o", method_create_machine_with_network, 0), - SD_BUS_METHOD("RegisterMachine", "sayssus", "o", method_register_machine, 0), - SD_BUS_METHOD("RegisterMachineWithNetwork", "sayssusai", "o", method_register_machine_with_network, 0), - SD_BUS_METHOD("TerminateMachine", "s", NULL, method_terminate_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("KillMachine", "ssi", NULL, method_kill_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetMachineAddresses", "s", "a(iay)", method_get_machine_addresses, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("GetMachineOSRelease", "s", "a{ss}", method_get_machine_os_release, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenMachinePTY", "s", "hs", method_open_machine_pty, 0), - SD_BUS_METHOD("OpenMachineLogin", "s", "hs", method_open_machine_login, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("OpenMachineShell", "sssasas", "hs", method_open_machine_shell, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("BindMountMachine", "sssbb", NULL, method_bind_mount_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CopyFromMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CopyToMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetPoolLimit", "t", NULL, method_set_pool_limit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetImageLimit", "st", NULL, method_set_image_limit, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MapFromMachineUser", "su", "u", method_map_from_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MapToMachineUser", "u", "sou", method_map_to_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MapFromMachineGroup", "su", "u", method_map_from_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("MapToMachineGroup", "u", "sou", method_map_to_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_SIGNAL("MachineNew", "so", 0), - SD_BUS_SIGNAL("MachineRemoved", "so", 0), - SD_BUS_VTABLE_END -}; - -int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *path, *result, *unit; - Manager *m = userdata; - Machine *machine; - uint32_t id; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "uoss", &id, &path, &unit, &result); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - machine = hashmap_get(m->machine_units, unit); - if (!machine) - return 0; - - if (streq_ptr(path, machine->scope_job)) { - machine->scope_job = mfree(machine->scope_job); - - if (machine->started) { - if (streq(result, "done")) - machine_send_create_reply(machine, NULL); - else { - _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; - - sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED, "Start job for unit %s failed with '%s'", unit, result); - - machine_send_create_reply(machine, &e); - } - } - - machine_save(machine); - } - - machine_add_to_gc_queue(machine); - return 0; -} - -int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *unit = NULL; - const char *path; - Manager *m = userdata; - Machine *machine; - int r; - - assert(message); - assert(m); - - path = sd_bus_message_get_path(message); - if (!path) - return 0; - - r = unit_name_from_dbus_path(path, &unit); - if (r == -EINVAL) /* not for a unit */ - return 0; - if (r < 0){ - log_oom(); - return 0; - } - - machine = hashmap_get(m->machine_units, unit); - if (!machine) - return 0; - - machine_add_to_gc_queue(machine); - return 0; -} - -int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { - const char *path, *unit; - Manager *m = userdata; - Machine *machine; - int r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "so", &unit, &path); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - machine = hashmap_get(m->machine_units, unit); - if (!machine) - return 0; - - machine_add_to_gc_queue(machine); - return 0; -} - -int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - Machine *machine; - Iterator i; - int b, r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - if (b) - return 0; - - /* systemd finished reloading, let's recheck all our machines */ - log_debug("System manager has been reloaded, rechecking machines..."); - - HASHMAP_FOREACH(machine, m->machines, i) - machine_add_to_gc_queue(machine); - - return 0; -} - -int manager_start_scope( - Manager *manager, - const char *scope, - pid_t pid, - const char *slice, - const char *description, - sd_bus_message *more_properties, - sd_bus_error *error, - char **job) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - int r; - - assert(manager); - assert(scope); - assert(pid > 1); - - r = sd_bus_message_new_method_call( - manager->bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartTransientUnit"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "ss", strempty(scope), "fail"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return r; - - if (!isempty(slice)) { - r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); - if (r < 0) - return r; - } - - if (!isempty(description)) { - r = sd_bus_message_append(m, "(sv)", "Description", "s", description); - if (r < 0) - return r; - } - - r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "(sv)", "Delegate", "b", 1); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", UINT64_C(16384)); - if (r < 0) - return bus_log_create_error(r); - - if (more_properties) { - r = sd_bus_message_copy(m, more_properties, true); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "a(sa(sv))", 0); - if (r < 0) - return r; - - r = sd_bus_call(manager->bus, m, 0, error, &reply); - if (r < 0) - return r; - - if (job) { - const char *j; - char *copy; - - r = sd_bus_message_read(reply, "o", &j); - if (r < 0) - return r; - - copy = strdup(j); - if (!copy) - return -ENOMEM; - - *job = copy; - } - - return 1; -} - -int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - assert(manager); - assert(unit); - - r = sd_bus_call_method( - manager->bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StopUnit", - error, - &reply, - "ss", unit, "fail"); - if (r < 0) { - if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) || - sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) { - - if (job) - *job = NULL; - - sd_bus_error_free(error); - return 0; - } - - return r; - } - - if (job) { - const char *j; - char *copy; - - r = sd_bus_message_read(reply, "o", &j); - if (r < 0) - return r; - - copy = strdup(j); - if (!copy) - return -ENOMEM; - - *job = copy; - } - - return 1; -} - -int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error) { - assert(manager); - assert(unit); - - return sd_bus_call_method( - manager->bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "KillUnit", - error, - NULL, - "ssi", unit, "all", signo); -} - -int manager_unit_is_active(Manager *manager, const char *unit) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *path = NULL; - const char *state; - int r; - - assert(manager); - assert(unit); - - path = unit_dbus_path_from_name(unit); - if (!path) - return -ENOMEM; - - r = sd_bus_get_property( - manager->bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "ActiveState", - &error, - &reply, - "s"); - if (r < 0) { - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) || - sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED)) - return true; - - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT) || - sd_bus_error_has_name(&error, BUS_ERROR_LOAD_FAILED)) - return false; - - return r; - } - - r = sd_bus_message_read(reply, "s", &state); - if (r < 0) - return -EINVAL; - - return !STR_IN_SET(state, "inactive", "failed"); -} - -int manager_job_is_active(Manager *manager, const char *path) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int r; - - assert(manager); - assert(path); - - r = sd_bus_get_property( - manager->bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Job", - "State", - &error, - &reply, - "s"); - if (r < 0) { - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) || - sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED)) - return true; - - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_OBJECT)) - return false; - - return r; - } - - /* We don't actually care about the state really. The fact - * that we could read the job state is enough for us */ - - return true; -} - -int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine) { - Machine *mm; - int r; - - assert(m); - assert(pid >= 1); - assert(machine); - - mm = hashmap_get(m->machine_leaders, PID_TO_PTR(pid)); - if (!mm) { - _cleanup_free_ char *unit = NULL; - - r = cg_pid_get_unit(pid, &unit); - if (r >= 0) - mm = hashmap_get(m->machine_units, unit); - } - if (!mm) - return 0; - - *machine = mm; - return 1; -} - -int manager_add_machine(Manager *m, const char *name, Machine **_machine) { - Machine *machine; - - assert(m); - assert(name); - - machine = hashmap_get(m->machines, name); - if (!machine) { - machine = machine_new(m, _MACHINE_CLASS_INVALID, name); - if (!machine) - return -ENOMEM; - } - - if (_machine) - *_machine = machine; - - return 0; -} diff --git a/src/machine/machined.c b/src/machine/machined.c deleted file mode 100644 index f2c1966a6b..0000000000 --- a/src/machine/machined.c +++ /dev/null @@ -1,407 +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 . -***/ - -#include -#include -#include - -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "hostname-util.h" -#include "label.h" -#include "machine-image.h" -#include "machined.h" -#include "signal-util.h" - -Manager *manager_new(void) { - Manager *m; - int r; - - m = new0(Manager, 1); - if (!m) - return NULL; - - m->machines = hashmap_new(&string_hash_ops); - m->machine_units = hashmap_new(&string_hash_ops); - m->machine_leaders = hashmap_new(NULL); - - if (!m->machines || !m->machine_units || !m->machine_leaders) { - manager_free(m); - return NULL; - } - - r = sd_event_default(&m->event); - if (r < 0) { - manager_free(m); - return NULL; - } - - sd_event_set_watchdog(m->event, true); - - return m; -} - -void manager_free(Manager *m) { - Machine *machine; - Image *i; - - assert(m); - - while ((machine = hashmap_first(m->machines))) - machine_free(machine); - - hashmap_free(m->machines); - hashmap_free(m->machine_units); - hashmap_free(m->machine_leaders); - - while ((i = hashmap_steal_first(m->image_cache))) - image_unref(i); - - hashmap_free(m->image_cache); - - sd_event_source_unref(m->image_cache_defer_event); - - bus_verify_polkit_async_registry_free(m->polkit_registry); - - sd_bus_unref(m->bus); - sd_event_unref(m->event); - - free(m); -} - -static int manager_add_host_machine(Manager *m) { - _cleanup_free_ char *rd = NULL, *unit = NULL; - sd_id128_t mid; - Machine *t; - int r; - - if (m->host_machine) - return 0; - - r = sd_id128_get_machine(&mid); - if (r < 0) - return log_error_errno(r, "Failed to get machine ID: %m"); - - rd = strdup("/"); - if (!rd) - return log_oom(); - - unit = strdup("-.slice"); - if (!unit) - return log_oom(); - - t = machine_new(m, MACHINE_HOST, ".host"); - if (!t) - return log_oom(); - - t->leader = 1; - t->id = mid; - - t->root_directory = rd; - t->unit = unit; - rd = unit = NULL; - - dual_timestamp_from_boottime_or_monotonic(&t->timestamp, 0); - - m->host_machine = t; - - return 0; -} - -int manager_enumerate_machines(Manager *m) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r = 0; - - assert(m); - - r = manager_add_host_machine(m); - if (r < 0) - return r; - - /* Read in machine data stored on disk */ - d = opendir("/run/systemd/machines"); - if (!d) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open /run/systemd/machines: %m"); - } - - FOREACH_DIRENT(de, d, return -errno) { - struct Machine *machine; - int k; - - if (!dirent_is_file(de)) - continue; - - /* Ignore symlinks that map the unit name to the machine */ - if (startswith(de->d_name, "unit:")) - continue; - - if (!machine_name_is_valid(de->d_name)) - continue; - - k = manager_add_machine(m, de->d_name, &machine); - if (k < 0) { - r = log_error_errno(k, "Failed to add machine by file name %s: %m", de->d_name); - continue; - } - - machine_add_to_gc_queue(machine); - - k = machine_load(machine); - if (k < 0) - r = k; - } - - return r; -} - -static int manager_connect_bus(Manager *m) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(m); - assert(!m->bus); - - r = sd_bus_default_system(&m->bus); - if (r < 0) - return log_error_errno(r, "Failed to connect to system bus: %m"); - - r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", manager_vtable, m); - if (r < 0) - return log_error_errno(r, "Failed to add manager object vtable: %m"); - - r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/machine", "org.freedesktop.machine1.Machine", machine_vtable, machine_object_find, m); - if (r < 0) - return log_error_errno(r, "Failed to add machine object vtable: %m"); - - r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/machine", machine_node_enumerator, m); - if (r < 0) - return log_error_errno(r, "Failed to add machine enumerator: %m"); - - r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/image", "org.freedesktop.machine1.Image", image_vtable, image_object_find, m); - if (r < 0) - return log_error_errno(r, "Failed to add image object vtable: %m"); - - r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/image", image_node_enumerator, m); - if (r < 0) - return log_error_errno(r, "Failed to add image enumerator: %m"); - - r = sd_bus_add_match(m->bus, - NULL, - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.systemd1.Manager'," - "member='JobRemoved'," - "path='/org/freedesktop/systemd1'", - match_job_removed, - m); - if (r < 0) - return log_error_errno(r, "Failed to add match for JobRemoved: %m"); - - r = sd_bus_add_match(m->bus, - NULL, - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.systemd1.Manager'," - "member='UnitRemoved'," - "path='/org/freedesktop/systemd1'", - match_unit_removed, - m); - if (r < 0) - return log_error_errno(r, "Failed to add match for UnitRemoved: %m"); - - r = sd_bus_add_match(m->bus, - NULL, - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.DBus.Properties'," - "member='PropertiesChanged'," - "arg0='org.freedesktop.systemd1.Unit'", - match_properties_changed, - m); - if (r < 0) - return log_error_errno(r, "Failed to add match for PropertiesChanged: %m"); - - r = sd_bus_add_match(m->bus, - NULL, - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.systemd1.Manager'," - "member='Reloading'," - "path='/org/freedesktop/systemd1'", - match_reloading, - m); - if (r < 0) - return log_error_errno(r, "Failed to add match for Reloading: %m"); - - r = sd_bus_call_method( - m->bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Subscribe", - &error, - NULL, NULL); - if (r < 0) { - log_error("Failed to enable subscription: %s", bus_error_message(&error, r)); - return r; - } - - r = sd_bus_request_name(m->bus, "org.freedesktop.machine1", 0); - if (r < 0) - return log_error_errno(r, "Failed to register name: %m"); - - r = sd_bus_attach_event(m->bus, m->event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - return 0; -} - -void manager_gc(Manager *m, bool drop_not_started) { - Machine *machine; - - assert(m); - - while ((machine = m->machine_gc_queue)) { - LIST_REMOVE(gc_queue, m->machine_gc_queue, machine); - machine->in_gc_queue = false; - - /* First, if we are not closing yet, initiate stopping */ - if (!machine_check_gc(machine, drop_not_started) && - machine_get_state(machine) != MACHINE_CLOSING) - machine_stop(machine); - - /* Now, the stop 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)) { - machine_finalize(machine); - machine_free(machine); - } - } -} - -int manager_startup(Manager *m) { - Machine *machine; - Iterator i; - int r; - - assert(m); - - /* Connect to the bus */ - r = manager_connect_bus(m); - if (r < 0) - return r; - - /* Deserialize state */ - manager_enumerate_machines(m); - - /* Remove stale objects before we start them */ - manager_gc(m, false); - - /* And start everything */ - HASHMAP_FOREACH(machine, m->machines, i) - machine_start(machine, NULL, NULL); - - return 0; -} - -static bool check_idle(void *userdata) { - Manager *m = userdata; - - manager_gc(m, true); - - return hashmap_isempty(m->machines); -} - -int manager_run(Manager *m) { - assert(m); - - return bus_event_loop_with_idle( - m->event, - m->bus, - "org.freedesktop.machine1", - DEFAULT_EXIT_USEC, - check_idle, m); -} - -int main(int argc, char *argv[]) { - Manager *m = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_set_facility(LOG_AUTH); - log_parse_environment(); - log_open(); - - umask(0022); - - if (argc != 1) { - log_error("This program takes no arguments."); - r = -EINVAL; - goto finish; - } - - /* Always create the directories people can create inotify - * watches in. Note that some applications might check for the - * existence of /run/systemd/machines/ to determine whether - * machined is available, so please always make sure this - * check stays in. */ - mkdir_label("/run/systemd/machines", 0755); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); - - m = manager_new(); - if (!m) { - r = log_oom(); - goto finish; - } - - r = manager_startup(m); - if (r < 0) { - log_error_errno(r, "Failed to fully start up daemon: %m"); - goto finish; - } - - log_debug("systemd-machined running as pid "PID_FMT, getpid()); - - sd_notify(false, - "READY=1\n" - "STATUS=Processing requests..."); - - r = manager_run(m); - - log_debug("systemd-machined stopped as pid "PID_FMT, getpid()); - -finish: - manager_free(m); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/machine/machined.h b/src/machine/machined.h deleted file mode 100644 index e7d7dfdceb..0000000000 --- a/src/machine/machined.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -#include "sd-bus.h" -#include "sd-event.h" - -#include "hashmap.h" -#include "list.h" - -typedef struct Manager Manager; - -#include "image-dbus.h" -#include "machine-dbus.h" -#include "machine.h" - -struct Manager { - sd_event *event; - sd_bus *bus; - - Hashmap *machines; - Hashmap *machine_units; - Hashmap *machine_leaders; - - Hashmap *polkit_registry; - - Hashmap *image_cache; - sd_event_source *image_cache_defer_event; - - LIST_HEAD(Machine, machine_gc_queue); - - Machine *host_machine; -}; - -Manager *manager_new(void); -void manager_free(Manager *m); - -int manager_add_machine(Manager *m, const char *name, Machine **_machine); -int manager_enumerate_machines(Manager *m); - -int manager_startup(Manager *m); -int manager_run(Manager *m); - -void manager_gc(Manager *m, bool drop_not_started); - -int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine); - -extern const sd_bus_vtable manager_vtable[]; - -int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error); -int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); -int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error); -int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); - -int manager_start_scope(Manager *manager, const char *scope, pid_t pid, const char *slice, const char *description, sd_bus_message *more_properties, sd_bus_error *error, char **job); -int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job); -int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error); -int manager_unit_is_active(Manager *manager, const char *unit); -int manager_job_is_active(Manager *manager, const char *path); diff --git a/src/machine/nss-mymachines/nss-mymachines.c b/src/machine/nss-mymachines/nss-mymachines.c new file mode 100644 index 0000000000..9d401b39dd --- /dev/null +++ b/src/machine/nss-mymachines/nss-mymachines.c @@ -0,0 +1,738 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include + +#include +#include + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "hostname-util.h" +#include "in-addr-util.h" +#include "macro.h" +#include "nss-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "user-util.h" +#include "util.h" + +NSS_GETHOSTBYNAME_PROTOTYPES(mymachines); +NSS_GETPW_PROTOTYPES(mymachines); +NSS_GETGR_PROTOTYPES(mymachines); + +#define HOST_UID_LIMIT ((uid_t) UINT32_C(0x10000)) +#define HOST_GID_LIMIT ((gid_t) UINT32_C(0x10000)) + +static int count_addresses(sd_bus_message *m, int af, unsigned *ret) { + unsigned c = 0; + int r; + + assert(m); + assert(ret); + + while ((r = sd_bus_message_enter_container(m, 'r', "iay")) > 0) { + int family; + + r = sd_bus_message_read(m, "i", &family); + if (r < 0) + return r; + + r = sd_bus_message_skip(m, "ay"); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + if (af != AF_UNSPEC && family != af) + continue; + + c ++; + } + if (r < 0) + return r; + + r = sd_bus_message_rewind(m, false); + if (r < 0) + return r; + + *ret = c; + return 0; +} + +enum nss_status _nss_mymachines_gethostbyname4_r( + const char *name, + struct gaih_addrtuple **pat, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp) { + + struct gaih_addrtuple *r_tuple, *r_tuple_first = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ int *ifindices = NULL; + _cleanup_free_ char *class = NULL; + size_t l, ms, idx; + unsigned i = 0, c = 0; + char *r_name; + int n_ifindices, r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(pat); + assert(buffer); + assert(errnop); + assert(h_errnop); + + r = sd_machine_get_class(name, &class); + if (r < 0) + goto fail; + if (!streq(class, "container")) { + r = -ENOTTY; + goto fail; + } + + n_ifindices = sd_machine_get_ifindices(name, &ifindices); + if (n_ifindices < 0) { + r = n_ifindices; + goto fail; + } + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineAddresses", + NULL, + &reply, + "s", name); + if (r < 0) + goto fail; + + r = sd_bus_message_enter_container(reply, 'a', "(iay)"); + if (r < 0) + goto fail; + + r = count_addresses(reply, AF_UNSPEC, &c); + if (r < 0) + goto fail; + + if (c <= 0) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + l = strlen(name); + ms = ALIGN(l+1) + ALIGN(sizeof(struct gaih_addrtuple)) * c; + if (buflen < ms) { + *errnop = ENOMEM; + *h_errnop = TRY_AGAIN; + return NSS_STATUS_TRYAGAIN; + } + + /* First, append name */ + r_name = buffer; + memcpy(r_name, name, l+1); + idx = ALIGN(l+1); + + /* Second, append addresses */ + r_tuple_first = (struct gaih_addrtuple*) (buffer + idx); + while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { + int family; + const void *a; + size_t sz; + + r = sd_bus_message_read(reply, "i", &family); + if (r < 0) + goto fail; + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + goto fail; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + goto fail; + + if (!IN_SET(family, AF_INET, AF_INET6)) { + r = -EAFNOSUPPORT; + goto fail; + } + + if (sz != FAMILY_ADDRESS_SIZE(family)) { + r = -EINVAL; + goto fail; + } + + r_tuple = (struct gaih_addrtuple*) (buffer + idx); + r_tuple->next = i == c-1 ? NULL : (struct gaih_addrtuple*) ((char*) r_tuple + ALIGN(sizeof(struct gaih_addrtuple))); + r_tuple->name = r_name; + r_tuple->family = family; + r_tuple->scopeid = n_ifindices == 1 ? ifindices[0] : 0; + memcpy(r_tuple->addr, a, sz); + + idx += ALIGN(sizeof(struct gaih_addrtuple)); + i++; + } + + assert(i == c); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + goto fail; + + assert(idx == ms); + + if (*pat) + **pat = *r_tuple_first; + else + *pat = r_tuple_first; + + if (ttlp) + *ttlp = 0; + + /* Explicitly reset all error variables */ + *errnop = 0; + *h_errnop = NETDB_SUCCESS; + h_errno = 0; + + return NSS_STATUS_SUCCESS; + +fail: + *errnop = -r; + *h_errnop = NO_DATA; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_mymachines_gethostbyname3_r( + const char *name, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp, + char **canonp) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *class = NULL; + unsigned c = 0, i = 0; + char *r_name, *r_aliases, *r_addr, *r_addr_list; + size_t l, idx, ms, alen; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(result); + assert(buffer); + assert(errnop); + assert(h_errnop); + + if (af == AF_UNSPEC) + af = AF_INET; + + if (af != AF_INET && af != AF_INET6) { + r = -EAFNOSUPPORT; + goto fail; + } + + r = sd_machine_get_class(name, &class); + if (r < 0) + goto fail; + if (!streq(class, "container")) { + r = -ENOTTY; + goto fail; + } + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineAddresses", + NULL, + &reply, + "s", name); + if (r < 0) + goto fail; + + r = sd_bus_message_enter_container(reply, 'a', "(iay)"); + if (r < 0) + goto fail; + + r = count_addresses(reply, af, &c); + if (r < 0) + goto fail; + + if (c <= 0) { + *errnop = ENOENT; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + alen = FAMILY_ADDRESS_SIZE(af); + l = strlen(name); + + ms = ALIGN(l+1) + c * ALIGN(alen) + (c+2) * sizeof(char*); + + if (buflen < ms) { + *errnop = ENOMEM; + *h_errnop = NO_RECOVERY; + return NSS_STATUS_TRYAGAIN; + } + + /* First, append name */ + r_name = buffer; + memcpy(r_name, name, l+1); + idx = ALIGN(l+1); + + /* Second, create aliases array */ + r_aliases = buffer + idx; + ((char**) r_aliases)[0] = NULL; + idx += sizeof(char*); + + /* Third, append addresses */ + r_addr = buffer + idx; + while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { + int family; + const void *a; + size_t sz; + + r = sd_bus_message_read(reply, "i", &family); + if (r < 0) + goto fail; + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + goto fail; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + goto fail; + + if (family != af) + continue; + + if (sz != alen) { + r = -EINVAL; + goto fail; + } + + memcpy(r_addr + i*ALIGN(alen), a, alen); + i++; + } + + assert(i == c); + idx += c * ALIGN(alen); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + goto fail; + + /* Third, append address pointer array */ + r_addr_list = buffer + idx; + for (i = 0; i < c; i++) + ((char**) r_addr_list)[i] = r_addr + i*ALIGN(alen); + + ((char**) r_addr_list)[i] = NULL; + idx += (c+1) * sizeof(char*); + + assert(idx == ms); + + result->h_name = r_name; + result->h_aliases = (char**) r_aliases; + result->h_addrtype = af; + result->h_length = alen; + result->h_addr_list = (char**) r_addr_list; + + if (ttlp) + *ttlp = 0; + + if (canonp) + *canonp = r_name; + + /* Explicitly reset all error variables */ + *errnop = 0; + *h_errnop = NETDB_SUCCESS; + h_errno = 0; + + return NSS_STATUS_SUCCESS; + +fail: + *errnop = -r; + *h_errnop = NO_DATA; + return NSS_STATUS_UNAVAIL; +} + +NSS_GETHOSTBYNAME_FALLBACKS(mymachines); + +enum nss_status _nss_mymachines_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; + const char *p, *e, *machine; + uint32_t mapped; + uid_t uid; + size_t l; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(pwd); + + p = startswith(name, "vu-"); + if (!p) + goto not_found; + + e = strrchr(p, '-'); + if (!e || e == p) + goto not_found; + + if (e - p > HOST_NAME_MAX - 1) /* -1 for the last dash */ + goto not_found; + + r = parse_uid(e + 1, &uid); + if (r < 0) + goto not_found; + + machine = strndupa(p, e - p); + if (!machine_name_is_valid(machine)) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MapFromMachineUser", + &error, + &reply, + "su", + machine, (uint32_t) uid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_USER_MAPPING)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "u", &mapped); + if (r < 0) + goto fail; + + /* Refuse to work if the mapped address is in the host UID range, or if there was no mapping at all. */ + if (mapped < HOST_UID_LIMIT || mapped == uid) + goto not_found; + + 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 = mapped; + pwd->pw_gid = 65534; /* nobody */ + pwd->pw_gecos = buffer; + 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_mymachines_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 *machine, *object; + uint32_t mapped; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + if (!uid_is_valid(uid)) { + r = -EINVAL; + goto fail; + } + + /* We consider all uids < 65536 host uids */ + if (uid < HOST_UID_LIMIT) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MapToMachineUser", + &error, + &reply, + "u", + (uint32_t) uid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_USER_MAPPING)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "sou", &machine, &object, &mapped); + if (r < 0) + goto fail; + + if (mapped == uid) + goto not_found; + + if (snprintf(buffer, buflen, "vu-%s-" UID_FMT, machine, (uid_t) mapped) >= (int) buflen) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + pwd->pw_name = buffer; + pwd->pw_uid = uid; + pwd->pw_gid = 65534; /* nobody */ + pwd->pw_gecos = buffer; + 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_mymachines_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; + const char *p, *e, *machine; + uint32_t mapped; + uid_t gid; + size_t l; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(gr); + + p = startswith(name, "vg-"); + if (!p) + goto not_found; + + e = strrchr(p, '-'); + if (!e || e == p) + goto not_found; + + if (e - p > HOST_NAME_MAX - 1) /* -1 for the last dash */ + goto not_found; + + r = parse_gid(e + 1, &gid); + if (r < 0) + goto not_found; + + machine = strndupa(p, e - p); + if (!machine_name_is_valid(machine)) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MapFromMachineGroup", + &error, + &reply, + "su", + machine, (uint32_t) gid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_GROUP_MAPPING)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "u", &mapped); + if (r < 0) + goto fail; + + if (mapped < HOST_GID_LIMIT || mapped == gid) + goto not_found; + + 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; + 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_mymachines_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 *machine, *object; + uint32_t mapped; + int r; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + if (!gid_is_valid(gid)) { + r = -EINVAL; + goto fail; + } + + /* We consider all gids < 65536 host gids */ + if (gid < HOST_GID_LIMIT) + goto not_found; + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fail; + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MapToMachineGroup", + &error, + &reply, + "u", + (uint32_t) gid); + if (r < 0) { + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_GROUP_MAPPING)) + goto not_found; + + goto fail; + } + + r = sd_bus_message_read(reply, "sou", &machine, &object, &mapped); + if (r < 0) + goto fail; + + if (mapped == gid) + goto not_found; + + if (buflen < sizeof(char*) + 1) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + memzero(buffer, sizeof(char*)); + if (snprintf(buffer + sizeof(char*), buflen - sizeof(char*), "vg-%s-" GID_FMT, machine, (gid_t) mapped) >= (int) buflen) { + *errnop = ENOMEM; + return NSS_STATUS_TRYAGAIN; + } + + 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/machine/nss-mymachines/nss-mymachines.sym b/src/machine/nss-mymachines/nss-mymachines.sym new file mode 100644 index 0000000000..0728ac3ba7 --- /dev/null +++ b/src/machine/nss-mymachines/nss-mymachines.sym @@ -0,0 +1,21 @@ +/*** + 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_mymachines_gethostbyname_r; + _nss_mymachines_gethostbyname2_r; + _nss_mymachines_gethostbyname3_r; + _nss_mymachines_gethostbyname4_r; + _nss_mymachines_getpwnam_r; + _nss_mymachines_getpwuid_r; + _nss_mymachines_getgrnam_r; + _nss_mymachines_getgrgid_r; +local: *; +}; diff --git a/src/machine/org.freedesktop.machine1.conf b/src/machine/org.freedesktop.machine1.conf deleted file mode 100644 index 9d40b90151..0000000000 --- a/src/machine/org.freedesktop.machine1.conf +++ /dev/null @@ -1,194 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/machine/org.freedesktop.machine1.policy.in b/src/machine/org.freedesktop.machine1.policy.in deleted file mode 100644 index 69f78a5c25..0000000000 --- a/src/machine/org.freedesktop.machine1.policy.in +++ /dev/null @@ -1,102 +0,0 @@ - - - - - - - - The systemd Project - http://www.freedesktop.org/wiki/Software/systemd - - - <_description>Log into a local container - <_message>Authentication is required to log into a local container. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Log into the local host - <_message>Authentication is required to log into the local host. - - auth_admin - auth_admin - yes - - - - - <_description>Acquire a shell in a local container - <_message>Authentication is required to acquire a shell in a local container. - - auth_admin - auth_admin - auth_admin_keep - - org.freedesktop.login1.login - - - - <_description>Acquire a shell on the local host - <_message>Authentication is required to acquire a shell on the local host. - - auth_admin - auth_admin - auth_admin_keep - - org.freedesktop.login1.host-login - - - - <_description>Acquire a pseudo TTY in a local container - <_message>Authentication is required to acquire a pseudo TTY in a local container. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Acquire a pseudo TTY on the local host - <_message>Authentication is required to acquire a pseudo TTY on the local host. - - auth_admin - auth_admin - auth_admin_keep - - - - - <_description>Manage local virtual machines and containers - <_message>Authentication is required to manage local virtual machines and containers. - - auth_admin - auth_admin - auth_admin_keep - - org.freedesktop.login1.shell org.freedesktop.login1.login - - - - <_description>Manage local virtual machine and container images - <_message>Authentication is required to manage local virtual machine and container images. - - auth_admin - auth_admin - auth_admin_keep - - - - diff --git a/src/machine/org.freedesktop.machine1.service b/src/machine/org.freedesktop.machine1.service deleted file mode 100644 index d3dc99852b..0000000000 --- a/src/machine/org.freedesktop.machine1.service +++ /dev/null @@ -1,12 +0,0 @@ -# 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. - -[D-BUS Service] -Name=org.freedesktop.machine1 -Exec=/bin/false -User=root -SystemdService=dbus-org.freedesktop.machine1.service diff --git a/src/machine/src/.gitignore b/src/machine/src/.gitignore new file mode 100644 index 0000000000..e1065b5894 --- /dev/null +++ b/src/machine/src/.gitignore @@ -0,0 +1 @@ +/org.freedesktop.machine1.policy diff --git a/src/machine/src/Makefile b/src/machine/src/Makefile new file mode 100644 index 0000000000..43a799440d --- /dev/null +++ b/src/machine/src/Makefile @@ -0,0 +1,128 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_MACHINED),) +systemd_machined_SOURCES = \ + src/machine/machined.c \ + src/machine/machined.h + +systemd_machined_LDADD = \ + libmachine-core.la + +rootlibexec_PROGRAMS += \ + systemd-machined + +libmachine_core_la_SOURCES = \ + src/machine/machine.c \ + src/machine/machine.h \ + src/machine/machined-dbus.c \ + src/machine/machine-dbus.c \ + src/machine/machine-dbus.h \ + src/machine/image-dbus.c \ + src/machine/image-dbus.h + +libmachine_core_la_LIBADD = \ + libshared.la + +noinst_LTLIBRARIES += \ + libmachine-core.la + +machinectl_SOURCES = \ + src/machine/machinectl.c + +machinectl_LDADD = \ + libshared.la + +rootbin_PROGRAMS += \ + machinectl + +test_machine_tables_SOURCES = \ + src/machine/test-machine-tables.c + +test_machine_tables_LDADD = \ + libmachine-core.la + +tests += \ + test-machine-tables + +nodist_systemunit_DATA += \ + units/systemd-machined.service + +dist_systemunit_DATA += \ + units/machine.slice + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.machine1.busname + +dist_dbussystemservice_DATA += \ + src/machine/org.freedesktop.machine1.service + +dist_dbuspolicy_DATA += \ + src/machine/org.freedesktop.machine1.conf + +polkitpolicy_files += \ + src/machine/org.freedesktop.machine1.policy + +dist_bashcompletion_data += \ + shell-completion/bash/machinectl + +dist_zshcompletion_data += \ + shell-completion/zsh/_machinectl \ + shell-completion/zsh/_sd_machines + +SYSTEM_UNIT_ALIASES += \ + systemd-machined.service dbus-org.freedesktop.machine1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.machine1.busname + +libnss_mymachines_la_SOURCES = \ + src/nss-mymachines/nss-mymachines.sym \ + src/nss-mymachines/nss-mymachines.c + +libnss_mymachines_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -shrext .so.2 \ + -Wl,--version-script=$(top_srcdir)/src/nss-mymachines/nss-mymachines.sym + +libnss_mymachines_la_LIBADD = \ + libsystemd-internal.la + +lib_LTLIBRARIES += \ + libnss_mymachines.la + +endif + +polkitpolicy_in_files += \ + src/machine/org.freedesktop.machine1.policy.in + +EXTRA_DIST += \ + units/systemd-machined.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/machine/src/image-dbus.c b/src/machine/src/image-dbus.c new file mode 100644 index 0000000000..73f5112c4d --- /dev/null +++ b/src/machine/src/image-dbus.c @@ -0,0 +1,361 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "alloc-util.h" +#include "bus-label.h" +#include "bus-util.h" +#include "image-dbus.h" +#include "io-util.h" +#include "machine-image.h" +#include "strv.h" +#include "user-util.h" + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, image_type, ImageType); + +int bus_image_method_remove( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + Manager *m = image->userdata; + int r; + + assert(message); + assert(image); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = image_remove(image); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_image_method_rename( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + Manager *m = image->userdata; + const char *new_name; + int r; + + assert(message); + assert(image); + + r = sd_bus_message_read(message, "s", &new_name); + if (r < 0) + return r; + + if (!image_name_is_valid(new_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = image_rename(image, new_name); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_image_method_clone( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + Manager *m = image->userdata; + const char *new_name; + int r, read_only; + + assert(message); + assert(image); + + r = sd_bus_message_read(message, "sb", &new_name, &read_only); + if (r < 0) + return r; + + if (!image_name_is_valid(new_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", new_name); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = image_clone(image, new_name, read_only); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_image_method_mark_read_only( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + Manager *m = image->userdata; + int r, read_only; + + assert(message); + + r = sd_bus_message_read(message, "b", &read_only); + if (r < 0) + return r; + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = image_read_only(image, read_only); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_image_method_set_limit( + sd_bus_message *message, + void *userdata, + sd_bus_error *error) { + + Image *image = userdata; + Manager *m = image->userdata; + uint64_t limit; + int r; + + assert(message); + + r = sd_bus_message_read(message, "t", &limit); + if (r < 0) + return r; + if (!FILE_SIZE_VALID_OR_INFINITY(limit)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range"); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-images", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = image_set_limit(image, limit); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +const sd_bus_vtable image_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Image, name), 0), + SD_BUS_PROPERTY("Path", "s", NULL, offsetof(Image, path), 0), + SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Image, type), 0), + SD_BUS_PROPERTY("ReadOnly", "b", bus_property_get_bool, offsetof(Image, read_only), 0), + SD_BUS_PROPERTY("CreationTimestamp", "t", NULL, offsetof(Image, crtime), 0), + SD_BUS_PROPERTY("ModificationTimestamp", "t", NULL, offsetof(Image, mtime), 0), + SD_BUS_PROPERTY("Usage", "t", NULL, offsetof(Image, usage), 0), + SD_BUS_PROPERTY("Limit", "t", NULL, offsetof(Image, limit), 0), + SD_BUS_PROPERTY("UsageExclusive", "t", NULL, offsetof(Image, usage_exclusive), 0), + SD_BUS_PROPERTY("LimitExclusive", "t", NULL, offsetof(Image, limit_exclusive), 0), + SD_BUS_METHOD("Remove", NULL, NULL, bus_image_method_remove, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Rename", "s", NULL, bus_image_method_rename, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Clone", "sb", NULL, bus_image_method_clone, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MarkReadOnly", "b", NULL, bus_image_method_mark_read_only, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLimit", "t", NULL, bus_image_method_set_limit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END +}; + +static int image_flush_cache(sd_event_source *s, void *userdata) { + Manager *m = userdata; + Image *i; + + assert(s); + assert(m); + + while ((i = hashmap_steal_first(m->image_cache))) + image_unref(i); + + return 0; +} + +int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + _cleanup_free_ char *e = NULL; + Manager *m = userdata; + Image *image = NULL; + const char *p; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + + p = startswith(path, "/org/freedesktop/machine1/image/"); + if (!p) + return 0; + + e = bus_label_unescape(p); + if (!e) + return -ENOMEM; + + image = hashmap_get(m->image_cache, e); + if (image) { + *found = image; + return 1; + } + + r = hashmap_ensure_allocated(&m->image_cache, &string_hash_ops); + if (r < 0) + return r; + + if (!m->image_cache_defer_event) { + r = sd_event_add_defer(m->event, &m->image_cache_defer_event, image_flush_cache, m); + if (r < 0) + return r; + + r = sd_event_source_set_priority(m->image_cache_defer_event, SD_EVENT_PRIORITY_IDLE); + if (r < 0) + return r; + } + + r = sd_event_source_set_enabled(m->image_cache_defer_event, SD_EVENT_ONESHOT); + if (r < 0) + return r; + + r = image_find(e, &image); + if (r <= 0) + return r; + + image->userdata = m; + + r = hashmap_put(m->image_cache, image->name, image); + if (r < 0) { + image_unref(image); + return r; + } + + *found = image; + return 1; +} + +char *image_bus_path(const char *name) { + _cleanup_free_ char *e = NULL; + + assert(name); + + e = bus_label_escape(name); + if (!e) + return NULL; + + return strappend("/org/freedesktop/machine1/image/", e); +} + +int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_(image_hashmap_freep) Hashmap *images = NULL; + _cleanup_strv_free_ char **l = NULL; + Image *image; + Iterator i; + int r; + + assert(bus); + assert(path); + assert(nodes); + + images = hashmap_new(&string_hash_ops); + if (!images) + return -ENOMEM; + + r = image_discover(images); + if (r < 0) + return r; + + HASHMAP_FOREACH(image, images, i) { + char *p; + + p = image_bus_path(image->name); + if (!p) + return -ENOMEM; + + r = strv_consume(&l, p); + if (r < 0) + return r; + } + + *nodes = l; + l = NULL; + + return 1; +} diff --git a/src/machine/src/image-dbus.h b/src/machine/src/image-dbus.h new file mode 100644 index 0000000000..b62da996c6 --- /dev/null +++ b/src/machine/src/image-dbus.h @@ -0,0 +1,35 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "machined.h" + +extern const sd_bus_vtable image_vtable[]; + +char *image_bus_path(const char *name); + +int image_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); +int image_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); + +int bus_image_method_remove(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_rename(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_clone(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_mark_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_image_method_set_limit(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/machine/src/machine-dbus.c b/src/machine/src/machine-dbus.c new file mode 100644 index 0000000000..71f20b3f07 --- /dev/null +++ b/src/machine/src/machine-dbus.c @@ -0,0 +1,1435 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include + +/* When we include libgen.h because we need dirname() we immediately + * undefine basename() since libgen.h defines it as a macro to the POSIX + * version which is really broken. We prefer GNU basename(). */ +#include +#undef basename + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-internal.h" +#include "bus-label.h" +#include "bus-util.h" +#include "copy.h" +#include "env-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "in-addr-util.h" +#include "local-addresses.h" +#include "machine-dbus.h" +#include "machine.h" +#include "mkdir.h" +#include "path-util.h" +#include "process-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "user-util.h" + +static int property_get_id( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Machine *m = userdata; + + assert(bus); + assert(reply); + assert(m); + + return sd_bus_message_append_array(reply, 'y', &m->id, 16); +} + +static int property_get_state( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Machine *m = userdata; + const char *state; + int r; + + assert(bus); + assert(reply); + assert(m); + + state = machine_state_to_string(machine_get_state(m)); + + r = sd_bus_message_append_basic(reply, 's', state); + if (r < 0) + return r; + + return 1; +} + +static int property_get_netif( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + Machine *m = userdata; + + assert(bus); + assert(reply); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + return sd_bus_message_append_array(reply, 'i', m->netif, m->n_netif * sizeof(int)); +} + +static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_class, machine_class, MachineClass); + +int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Machine *m = userdata; + int r; + + assert(message); + assert(m); + + r = bus_verify_polkit_async( + message, + CAP_KILL, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = machine_stop(m); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Machine *m = userdata; + const char *swho; + int32_t signo; + KillWho who; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "si", &swho, &signo); + if (r < 0) + return r; + + if (isempty(swho)) + who = KILL_ALL; + else { + who = kill_who_from_string(swho); + if (who < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid kill parameter '%s'", swho); + } + + if (signo <= 0 || signo >= _NSIG) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid signal %i", signo); + + r = bus_verify_polkit_async( + message, + CAP_KILL, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + r = machine_kill(m, who, signo); + if (r < 0) + return r; + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Machine *m = userdata; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(iay)"); + if (r < 0) + return r; + + switch (m->class) { + + case MACHINE_HOST: { + _cleanup_free_ struct local_address *addresses = NULL; + struct local_address *a; + int n, i; + + n = local_addresses(NULL, 0, AF_UNSPEC, &addresses); + if (n < 0) + return n; + + for (a = addresses, i = 0; i < n; a++, i++) { + + r = sd_bus_message_open_container(reply, 'r', "iay"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "i", addresses[i].family); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &addresses[i].address, FAMILY_ADDRESS_SIZE(addresses[i].family)); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + + break; + } + + case MACHINE_CONTAINER: { + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_free_ char *us = NULL, *them = NULL; + _cleanup_close_ int netns_fd = -1; + const char *p; + siginfo_t si; + pid_t child; + + r = readlink_malloc("/proc/self/ns/net", &us); + if (r < 0) + return r; + + p = procfs_file_alloca(m->leader, "ns/net"); + r = readlink_malloc(p, &them); + if (r < 0) + return r; + + if (streq(us, them)) + return sd_bus_error_setf(error, BUS_ERROR_NO_PRIVATE_NETWORKING, "Machine %s does not use private networking", m->name); + + r = namespace_open(m->leader, NULL, NULL, &netns_fd, NULL, NULL); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + + if (child == 0) { + _cleanup_free_ struct local_address *addresses = NULL; + struct local_address *a; + int i, n; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(-1, -1, netns_fd, -1, -1); + if (r < 0) + _exit(EXIT_FAILURE); + + n = local_addresses(NULL, 0, AF_UNSPEC, &addresses); + if (n < 0) + _exit(EXIT_FAILURE); + + for (a = addresses, i = 0; i < n; a++, i++) { + struct iovec iov[2] = { + { .iov_base = &a->family, .iov_len = sizeof(a->family) }, + { .iov_base = &a->address, .iov_len = FAMILY_ADDRESS_SIZE(a->family) }, + }; + + r = writev(pair[1], iov, 2); + if (r < 0) + _exit(EXIT_FAILURE); + } + + pair[1] = safe_close(pair[1]); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + for (;;) { + int family; + ssize_t n; + union in_addr_union in_addr; + struct iovec iov[2]; + struct msghdr mh = { + .msg_iov = iov, + .msg_iovlen = 2, + }; + + iov[0] = (struct iovec) { .iov_base = &family, .iov_len = sizeof(family) }; + iov[1] = (struct iovec) { .iov_base = &in_addr, .iov_len = sizeof(in_addr) }; + + n = recvmsg(pair[0], &mh, 0); + if (n < 0) + return -errno; + if ((size_t) n < sizeof(family)) + break; + + r = sd_bus_message_open_container(reply, 'r', "iay"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "i", family); + if (r < 0) + return r; + + switch (family) { + + case AF_INET: + if (n != sizeof(struct in_addr) + sizeof(family)) + return -EIO; + + r = sd_bus_message_append_array(reply, 'y', &in_addr.in, sizeof(in_addr.in)); + break; + + case AF_INET6: + if (n != sizeof(struct in6_addr) + sizeof(family)) + return -EIO; + + r = sd_bus_message_append_array(reply, 'y', &in_addr.in6, sizeof(in_addr.in6)); + break; + } + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + } + + r = wait_for_terminate(child, &si); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + break; + } + + default: + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting IP address data is only supported on container machines."); + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_strv_free_ char **l = NULL; + Machine *m = userdata; + char **k, **v; + int r; + + assert(message); + assert(m); + + switch (m->class) { + + case MACHINE_HOST: + r = load_env_file_pairs(NULL, "/etc/os-release", NULL, &l); + if (r < 0) + return r; + + break; + + case MACHINE_CONTAINER: { + _cleanup_close_ int mntns_fd = -1, root_fd = -1; + _cleanup_close_pair_ int pair[2] = { -1, -1 }; + _cleanup_fclose_ FILE *f = NULL; + siginfo_t si; + pid_t child; + + r = namespace_open(m->leader, NULL, &mntns_fd, NULL, NULL, &root_fd); + if (r < 0) + return r; + + if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, pair) < 0) + return -errno; + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + + if (child == 0) { + _cleanup_close_ int fd = -1; + + pair[0] = safe_close(pair[0]); + + r = namespace_enter(-1, mntns_fd, -1, -1, root_fd); + if (r < 0) + _exit(EXIT_FAILURE); + + fd = open("/etc/os-release", O_RDONLY|O_CLOEXEC); + if (fd < 0) { + fd = open("/usr/lib/os-release", O_RDONLY|O_CLOEXEC); + if (fd < 0) + _exit(EXIT_FAILURE); + } + + r = copy_bytes(fd, pair[1], (uint64_t) -1, false); + if (r < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + pair[1] = safe_close(pair[1]); + + f = fdopen(pair[0], "re"); + if (!f) + return -errno; + + pair[0] = -1; + + r = load_env_file_pairs(f, "/etc/os-release", NULL, &l); + if (r < 0) + return r; + + r = wait_for_terminate(child, &si); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); + if (si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) + return sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + + break; + } + + default: + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Requesting OS release data is only supported on container machines."); + } + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "{ss}"); + if (r < 0) + return r; + + STRV_FOREACH_PAIR(k, v, l) { + r = sd_bus_message_append(reply, "{ss}", *k, *v); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *pty_name = NULL; + _cleanup_close_ int master = -1; + Machine *m = userdata; + int r; + + assert(message); + assert(m); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-open-pty" : "org.freedesktop.machine1.open-pty", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (master < 0) + return master; + + r = ptsname_namespace(master, &pty_name); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "hs", master, pty_name); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +static int container_bus_new(Machine *m, sd_bus_error *error, sd_bus **ret) { + int r; + + assert(m); + assert(ret); + + switch (m->class) { + + case MACHINE_HOST: + *ret = NULL; + break; + + case MACHINE_CONTAINER: { + _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; + char *address; + + r = sd_bus_new(&bus); + if (r < 0) + return r; + + if (asprintf(&address, "x-machine-kernel:pid=%1$" PID_PRI ";x-machine-unix:pid=%1$" PID_PRI, m->leader) < 0) + return -ENOMEM; + + bus->address = address; + bus->bus_client = true; + bus->trusted = false; + bus->is_system = true; + + r = sd_bus_start(bus); + if (r == -ENOENT) + return sd_bus_error_set_errnof(error, r, "There is no system bus in container %s.", m->name); + if (r < 0) + return r; + + *ret = bus; + bus = NULL; + break; + } + + default: + return -EOPNOTSUPP; + } + + return 0; +} + +int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *pty_name = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; + _cleanup_close_ int master = -1; + sd_bus *container_bus = NULL; + Machine *m = userdata; + const char *p, *getty; + int r; + + assert(message); + assert(m); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-login" : "org.freedesktop.machine1.login", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (master < 0) + return master; + + r = ptsname_namespace(master, &pty_name); + if (r < 0) + return r; + + p = path_startswith(pty_name, "/dev/pts/"); + if (!p) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "PTS name %s is invalid", pty_name); + + r = container_bus_new(m, error, &allocated_bus); + if (r < 0) + return r; + + container_bus = allocated_bus ?: m->manager->bus; + + getty = strjoina("container-getty@", p, ".service"); + + r = sd_bus_call_method( + container_bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit", + error, NULL, + "ss", getty, "replace"); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "hs", master, pty_name); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *tm = NULL; + _cleanup_free_ char *pty_name = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *allocated_bus = NULL; + sd_bus *container_bus = NULL; + _cleanup_close_ int master = -1, slave = -1; + _cleanup_strv_free_ char **env = NULL, **args = NULL; + Machine *m = userdata; + const char *p, *unit, *user, *path, *description, *utmp_id; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "ss", &user, &path); + if (r < 0) + return r; + if (isempty(user)) + user = NULL; + if (isempty(path)) + path = "/bin/sh"; + if (!path_is_absolute(path)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified path '%s' is not absolute", path); + + r = sd_bus_message_read_strv(message, &args); + if (r < 0) + return r; + if (strv_isempty(args)) { + args = strv_free(args); + + args = strv_new(path, NULL); + if (!args) + return -ENOMEM; + + args[0][0] = '-'; /* Tell /bin/sh that this shall be a login shell */ + } + + r = sd_bus_message_read_strv(message, &env); + if (r < 0) + return r; + if (!strv_env_is_valid(env)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid environment assignments"); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + m->class == MACHINE_HOST ? "org.freedesktop.machine1.host-shell" : "org.freedesktop.machine1.shell", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + master = machine_openpt(m, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (master < 0) + return master; + + r = ptsname_namespace(master, &pty_name); + if (r < 0) + return r; + + p = path_startswith(pty_name, "/dev/pts/"); + assert(p); + + slave = machine_open_terminal(m, pty_name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (slave < 0) + return slave; + + utmp_id = path_startswith(pty_name, "/dev/"); + assert(utmp_id); + + r = container_bus_new(m, error, &allocated_bus); + if (r < 0) + return r; + + container_bus = allocated_bus ?: m->manager->bus; + + r = sd_bus_message_new_method_call( + container_bus, + &tm, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return r; + + /* Name and mode */ + unit = strjoina("container-shell@", p, ".service", NULL); + r = sd_bus_message_append(tm, "ss", unit, "fail"); + if (r < 0) + return r; + + /* Properties */ + r = sd_bus_message_open_container(tm, 'a', "(sv)"); + if (r < 0) + return r; + + description = strjoina("Shell for User ", isempty(user) ? "root" : user); + r = sd_bus_message_append(tm, + "(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)(sv)", + "Description", "s", description, + "StandardInputFileDescriptor", "h", slave, + "StandardOutputFileDescriptor", "h", slave, + "StandardErrorFileDescriptor", "h", slave, + "SendSIGHUP", "b", true, + "IgnoreSIGPIPE", "b", false, + "KillMode", "s", "mixed", + "TTYReset", "b", true, + "UtmpIdentifier", "s", utmp_id, + "UtmpMode", "s", "user", + "PAMName", "s", "login", + "WorkingDirectory", "s", "-~"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "(sv)", "User", "s", isempty(user) ? "root" : user); + if (r < 0) + return r; + + if (!strv_isempty(env)) { + r = sd_bus_message_open_container(tm, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "s", "Environment"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'v', "as"); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(tm, env); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + } + + /* Exec container */ + r = sd_bus_message_open_container(tm, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "s", "ExecStart"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'v', "a(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'a', "(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(tm, 'r', "sasb"); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "s", path); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(tm, args); + if (r < 0) + return r; + + r = sd_bus_message_append(tm, "b", true); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + r = sd_bus_message_close_container(tm); + if (r < 0) + return r; + + /* Auxiliary units */ + r = sd_bus_message_append(tm, "a(sa(sv))", 0); + if (r < 0) + return r; + + r = sd_bus_call(container_bus, tm, 0, error, NULL); + if (r < 0) + return r; + + slave = safe_close(slave); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "hs", master, pty_name); + if (r < 0) + return r; + + return sd_bus_send(NULL, reply, NULL); +} + +int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; + char mount_slave[] = "/tmp/propagate.XXXXXX", *mount_tmp, *mount_outside, *p; + bool mount_slave_created = false, mount_slave_mounted = false, + mount_tmp_created = false, mount_tmp_mounted = false, + mount_outside_created = false, mount_outside_mounted = false; + const char *dest, *src; + Machine *m = userdata; + int read_only, make_directory; + pid_t child; + siginfo_t si; + int r; + + assert(message); + assert(m); + + if (m->class != MACHINE_CONTAINER) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Bind mounting is only supported on container machines."); + + r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_directory); + if (r < 0) + return r; + + if (!path_is_absolute(src) || !path_is_safe(src)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and not contain ../."); + + if (isempty(dest)) + dest = src; + else if (!path_is_absolute(dest) || !path_is_safe(dest)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and not contain ../."); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + /* One day, when bind mounting /proc/self/fd/n works across + * namespace boundaries we should rework this logic to make + * use of it... */ + + p = strjoina("/run/systemd/nspawn/propagate/", m->name, "/"); + if (laccess(p, F_OK) < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Container does not allow propagation of mount points."); + + /* Our goal is to install a new bind mount into the container, + possibly read-only. This is irritatingly complex + unfortunately, currently. + + First, we start by creating a private playground in /tmp, + that we can mount MS_SLAVE. (Which is necessary, since + MS_MOVE cannot be applied to mounts with MS_SHARED parent + mounts.) */ + + if (!mkdtemp(mount_slave)) + return sd_bus_error_set_errnof(error, errno, "Failed to create playground %s: %m", mount_slave); + + mount_slave_created = true; + + if (mount(mount_slave, mount_slave, NULL, MS_BIND, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to make bind mount %s: %m", mount_slave); + goto finish; + } + + mount_slave_mounted = true; + + if (mount(NULL, mount_slave, NULL, MS_SLAVE, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to remount slave %s: %m", mount_slave); + goto finish; + } + + /* Second, we mount the source directory to a directory inside + of our MS_SLAVE playground. */ + mount_tmp = strjoina(mount_slave, "/mount"); + if (mkdir(mount_tmp, 0700) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount point %s: %m", mount_tmp); + goto finish; + } + + mount_tmp_created = true; + + if (mount(src, mount_tmp, NULL, MS_BIND, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to overmount %s: %m", mount_tmp); + goto finish; + } + + mount_tmp_mounted = true; + + /* Third, we remount the new bind mount read-only if requested. */ + if (read_only) + if (mount(NULL, mount_tmp, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to remount read-only %s: %m", mount_tmp); + goto finish; + } + + /* Fourth, we move the new bind mount into the propagation + * directory. This way it will appear there read-only + * right-away. */ + + mount_outside = strjoina("/run/systemd/nspawn/propagate/", m->name, "/XXXXXX"); + if (!mkdtemp(mount_outside)) { + r = sd_bus_error_set_errnof(error, errno, "Cannot create propagation directory %s: %m", mount_outside); + goto finish; + } + + mount_outside_created = true; + + if (mount(mount_tmp, mount_outside, NULL, MS_MOVE, NULL) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to move %s to %s: %m", mount_tmp, mount_outside); + goto finish; + } + + mount_outside_mounted = true; + mount_tmp_mounted = false; + + (void) rmdir(mount_tmp); + mount_tmp_created = false; + + (void) umount(mount_slave); + mount_slave_mounted = false; + + (void) rmdir(mount_slave); + mount_slave_created = false; + + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); + goto finish; + } + + child = fork(); + if (child < 0) { + r = sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + goto finish; + } + + if (child == 0) { + const char *mount_inside; + int mntfd; + const char *q; + + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + q = procfs_file_alloca(m->leader, "ns/mnt"); + mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (mntfd < 0) { + r = log_error_errno(errno, "Failed to open mount namespace of leader: %m"); + goto child_fail; + } + + if (setns(mntfd, CLONE_NEWNS) < 0) { + r = log_error_errno(errno, "Failed to join namespace of leader: %m"); + goto child_fail; + } + + if (make_directory) + (void) mkdir_p(dest, 0755); + + /* Fifth, move the mount to the right place inside */ + mount_inside = strjoina("/run/systemd/nspawn/incoming/", basename(mount_outside)); + if (mount(mount_inside, dest, NULL, MS_MOVE, NULL) < 0) { + r = log_error_errno(errno, "Failed to mount: %m"); + goto child_fail; + } + + _exit(EXIT_SUCCESS); + + child_fail: + (void) write(errno_pipe_fd[1], &r, sizeof(r)); + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + _exit(EXIT_FAILURE); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + r = wait_for_terminate(child, &si); + if (r < 0) { + r = sd_bus_error_set_errnof(error, r, "Failed to wait for child: %m"); + goto finish; + } + if (si.si_code != CLD_EXITED) { + r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + goto finish; + } + if (si.si_status != EXIT_SUCCESS) { + + if (read(errno_pipe_fd[0], &r, sizeof(r)) == sizeof(r)) + r = sd_bus_error_set_errnof(error, r, "Failed to mount: %m"); + else + r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Child failed."); + goto finish; + } + + r = sd_bus_reply_method_return(message, NULL); + +finish: + if (mount_outside_mounted) + umount(mount_outside); + if (mount_outside_created) + rmdir(mount_outside); + + if (mount_tmp_mounted) + umount(mount_tmp); + if (mount_tmp_created) + rmdir(mount_tmp); + + if (mount_slave_mounted) + umount(mount_slave); + if (mount_slave_created) + rmdir(mount_slave); + + return r; +} + +static int machine_operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + MachineOperation *o = userdata; + int r; + + assert(o); + assert(si); + + o->pid = 0; + + if (si->si_code != CLD_EXITED) { + r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child died abnormally."); + 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."); + + goto fail; + } + + r = sd_bus_reply_method_return(o->message, NULL); + if (r < 0) + log_error_errno(r, "Failed to reply to message: %m"); + + machine_operation_unref(o); + return 0; + +fail: + r = sd_bus_reply_method_error(o->message, &error); + if (r < 0) + log_error_errno(r, "Failed to reply to message: %m"); + + machine_operation_unref(o); + return 0; +} + +int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *src, *dest, *host_path, *container_path, *host_basename, *host_dirname, *container_basename, *container_dirname; + _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 }; + _cleanup_close_ int hostfd = -1; + Machine *m = userdata; + MachineOperation *o; + bool copy_from; + pid_t child; + char *t; + int r; + + assert(message); + assert(m); + + if (m->n_operations >= MACHINE_OPERATIONS_MAX) + return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing copies."); + + if (m->class != MACHINE_CONTAINER) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Copying files is only supported on container machines."); + + r = sd_bus_message_read(message, "ss", &src, &dest); + if (r < 0) + return r; + + if (!path_is_absolute(src)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute."); + + if (isempty(dest)) + dest = src; + else if (!path_is_absolute(dest)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute."); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->manager->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + copy_from = strstr(sd_bus_message_get_member(message), "CopyFrom"); + + if (copy_from) { + container_path = src; + host_path = dest; + } else { + host_path = src; + container_path = dest; + } + + host_basename = basename(host_path); + t = strdupa(host_path); + host_dirname = dirname(t); + + container_basename = basename(container_path); + t = strdupa(container_path); + container_dirname = dirname(t); + + hostfd = open(host_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); + if (hostfd < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to open host directory %s: %m", host_dirname); + + if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m"); + + child = fork(); + if (child < 0) + return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m"); + + if (child == 0) { + int containerfd; + const char *q; + int mntfd; + + errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]); + + q = procfs_file_alloca(m->leader, "ns/mnt"); + mntfd = open(q, O_RDONLY|O_NOCTTY|O_CLOEXEC); + if (mntfd < 0) { + r = log_error_errno(errno, "Failed to open mount namespace of leader: %m"); + goto child_fail; + } + + if (setns(mntfd, CLONE_NEWNS) < 0) { + r = log_error_errno(errno, "Failed to join namespace of leader: %m"); + goto child_fail; + } + + containerfd = open(container_dirname, O_CLOEXEC|O_RDONLY|O_NOCTTY|O_DIRECTORY); + if (containerfd < 0) { + r = log_error_errno(errno, "Failed top open destination directory: %m"); + goto child_fail; + } + + if (copy_from) + r = copy_tree_at(containerfd, container_basename, hostfd, host_basename, true); + else + r = copy_tree_at(hostfd, host_basename, containerfd, container_basename, true); + + hostfd = safe_close(hostfd); + containerfd = safe_close(containerfd); + + if (r < 0) { + r = log_error_errno(r, "Failed to copy tree: %m"); + goto child_fail; + } + + _exit(EXIT_SUCCESS); + + child_fail: + (void) write(errno_pipe_fd[1], &r, sizeof(r)); + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + _exit(EXIT_FAILURE); + } + + errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]); + + /* Copying might take a while, hence install a watch the + * child, and return */ + + o = new0(MachineOperation, 1); + if (!o) + return log_oom(); + + o->pid = child; + o->message = sd_bus_message_ref(message); + o->errno_fd = errno_pipe_fd[0]; + errno_pipe_fd[0] = -1; + + r = sd_event_add_child(m->manager->event, &o->event_source, child, WEXITED, machine_operation_done, o); + if (r < 0) { + machine_operation_unref(o); + return log_oom(); + } + + LIST_PREPEND(operations, m->operations, o); + m->n_operations++; + o->machine = m; + + return 1; +} + +const sd_bus_vtable machine_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Machine, name), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Id", "ay", property_get_id, 0, SD_BUS_VTABLE_PROPERTY_CONST), + BUS_PROPERTY_DUAL_TIMESTAMP("Timestamp", offsetof(Machine, timestamp), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Service", "s", NULL, offsetof(Machine, service), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Unit", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Scope", "s", NULL, offsetof(Machine, unit), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), + SD_BUS_PROPERTY("Leader", "u", NULL, offsetof(Machine, leader), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Class", "s", property_get_class, offsetof(Machine, class), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("RootDirectory", "s", NULL, offsetof(Machine, root_directory), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("NetworkInterfaces", "ai", property_get_netif, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("State", "s", property_get_state, 0, 0), + SD_BUS_METHOD("Terminate", NULL, NULL, bus_machine_method_terminate, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Kill", "si", NULL, bus_machine_method_kill, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetAddresses", NULL, "a(iay)", bus_machine_method_get_addresses, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_machine_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenPTY", NULL, "hs", bus_machine_method_open_pty, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenLogin", NULL, "hs", bus_machine_method_open_login, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenShell", "ssasas", "hs", bus_machine_method_open_shell, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("BindMount", "ssbb", NULL, bus_machine_method_bind_mount, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CopyFrom", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CopyTo", "ss", NULL, bus_machine_method_copy, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END +}; + +int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + if (streq(path, "/org/freedesktop/machine1/machine/self")) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + sd_bus_message *message; + pid_t pid; + + message = sd_bus_get_current_message(bus); + if (!message) + return 0; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + + r = manager_get_machine_by_pid(m, pid, &machine); + if (r <= 0) + return 0; + } else { + _cleanup_free_ char *e = NULL; + const char *p; + + p = startswith(path, "/org/freedesktop/machine1/machine/"); + if (!p) + return 0; + + e = bus_label_unescape(p); + if (!e) + return -ENOMEM; + + machine = hashmap_get(m->machines, e); + if (!machine) + return 0; + } + + *found = machine; + return 1; +} + +char *machine_bus_path(Machine *m) { + _cleanup_free_ char *e = NULL; + + assert(m); + + e = bus_label_escape(m->name); + if (!e) + return NULL; + + return strappend("/org/freedesktop/machine1/machine/", e); +} + +int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + Machine *machine = NULL; + Manager *m = userdata; + Iterator i; + int r; + + assert(bus); + assert(path); + assert(nodes); + + HASHMAP_FOREACH(machine, m->machines, i) { + char *p; + + p = machine_bus_path(machine); + if (!p) + return -ENOMEM; + + r = strv_consume(&l, p); + if (r < 0) + return r; + } + + *nodes = l; + l = NULL; + + return 1; +} + +int machine_send_signal(Machine *m, bool new_machine) { + _cleanup_free_ char *p = NULL; + + assert(m); + + p = machine_bus_path(m); + if (!p) + return -ENOMEM; + + return sd_bus_emit_signal( + m->manager->bus, + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + new_machine ? "MachineNew" : "MachineRemoved", + "so", m->name, p); +} + +int machine_send_create_reply(Machine *m, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *c = NULL; + _cleanup_free_ char *p = NULL; + + assert(m); + + if (!m->create_message) + return 0; + + c = m->create_message; + m->create_message = NULL; + + if (error) + return sd_bus_reply_method_error(c, error); + + /* Update the machine state file before we notify the client + * about the result. */ + machine_save(m); + + p = machine_bus_path(m); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(c, "o", p); +} diff --git a/src/machine/src/machine-dbus.h b/src/machine/src/machine-dbus.h new file mode 100644 index 0000000000..224f36529f --- /dev/null +++ b/src/machine/src/machine-dbus.h @@ -0,0 +1,43 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include "machine.h" + +extern const sd_bus_vtable machine_vtable[]; + +char *machine_bus_path(Machine *s); +int machine_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); +int machine_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); + +int bus_machine_method_terminate(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_kill(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_get_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_get_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_open_pty(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_open_login(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error); + +int machine_send_signal(Machine *m, bool new_machine); +int machine_send_create_reply(Machine *m, sd_bus_error *error); diff --git a/src/machine/src/machine.c b/src/machine/src/machine.c new file mode 100644 index 0000000000..468fc1fecf --- /dev/null +++ b/src/machine/src/machine.c @@ -0,0 +1,659 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "escape.h" +#include "extract-word.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "hashmap.h" +#include "machine-dbus.h" +#include "machine.h" +#include "mkdir.h" +#include "parse-util.h" +#include "process-util.h" +#include "special.h" +#include "string-table.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "util.h" + +Machine* machine_new(Manager *manager, MachineClass class, const char *name) { + Machine *m; + + assert(manager); + assert(class < _MACHINE_CLASS_MAX); + assert(name); + + /* Passing class == _MACHINE_CLASS_INVALID here is fine. It + * means as much as "we don't know yet", and that we'll figure + * it out later when loading the state file. */ + + m = new0(Machine, 1); + if (!m) + return NULL; + + m->name = strdup(name); + if (!m->name) + goto fail; + + if (class != MACHINE_HOST) { + m->state_file = strappend("/run/systemd/machines/", m->name); + if (!m->state_file) + goto fail; + } + + m->class = class; + + if (hashmap_put(manager->machines, m->name, m) < 0) + goto fail; + + m->manager = manager; + + return m; + +fail: + free(m->state_file); + free(m->name); + free(m); + + return NULL; +} + +void machine_free(Machine *m) { + assert(m); + + while (m->operations) + machine_operation_unref(m->operations); + + if (m->in_gc_queue) + LIST_REMOVE(gc_queue, m->manager->machine_gc_queue, m); + + machine_release_unit(m); + + free(m->scope_job); + + (void) hashmap_remove(m->manager->machines, m->name); + + if (m->manager->host_machine == m) + m->manager->host_machine = NULL; + + if (m->leader > 0) + (void) hashmap_remove_value(m->manager->machine_leaders, PID_TO_PTR(m->leader), m); + + sd_bus_message_unref(m->create_message); + + free(m->name); + free(m->state_file); + free(m->service); + free(m->root_directory); + free(m->netif); + free(m); +} + +int machine_save(Machine *m) { + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(m); + + if (!m->state_file) + return 0; + + if (!m->started) + return 0; + + r = mkdir_safe_label("/run/systemd/machines", 0755, 0, 0); + if (r < 0) + goto fail; + + r = fopen_temporary(m->state_file, &f, &temp_path); + if (r < 0) + goto fail; + + (void) fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n" + "NAME=%s\n", + m->name); + + if (m->unit) { + _cleanup_free_ char *escaped; + + escaped = cescape(m->unit); + if (!escaped) { + r = -ENOMEM; + goto fail; + } + + fprintf(f, "SCOPE=%s\n", escaped); /* We continue to call this "SCOPE=" because it is internal only, and we want to stay compatible with old files */ + } + + if (m->scope_job) + fprintf(f, "SCOPE_JOB=%s\n", m->scope_job); + + if (m->service) { + _cleanup_free_ char *escaped; + + escaped = cescape(m->service); + if (!escaped) { + r = -ENOMEM; + goto fail; + } + fprintf(f, "SERVICE=%s\n", escaped); + } + + if (m->root_directory) { + _cleanup_free_ char *escaped; + + escaped = cescape(m->root_directory); + if (!escaped) { + r = -ENOMEM; + goto fail; + } + fprintf(f, "ROOT=%s\n", escaped); + } + + if (!sd_id128_equal(m->id, SD_ID128_NULL)) + fprintf(f, "ID=" SD_ID128_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(m->id)); + + if (m->leader != 0) + fprintf(f, "LEADER="PID_FMT"\n", m->leader); + + if (m->class != _MACHINE_CLASS_INVALID) + fprintf(f, "CLASS=%s\n", machine_class_to_string(m->class)); + + if (dual_timestamp_is_set(&m->timestamp)) + fprintf(f, + "REALTIME="USEC_FMT"\n" + "MONOTONIC="USEC_FMT"\n", + m->timestamp.realtime, + m->timestamp.monotonic); + + if (m->n_netif > 0) { + unsigned i; + + fputs("NETIF=", f); + + for (i = 0; i < m->n_netif; i++) { + if (i != 0) + fputc(' ', f); + + fprintf(f, "%i", m->netif[i]); + } + + fputc('\n', f); + } + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, m->state_file) < 0) { + r = -errno; + goto fail; + } + + if (m->unit) { + char *sl; + + /* Create a symlink from the unit name to the machine + * name, so that we can quickly find the machine for + * each given unit. Ignore error. */ + sl = strjoina("/run/systemd/machines/unit:", m->unit); + (void) symlink(m->name, sl); + } + + return 0; + +fail: + (void) unlink(m->state_file); + + if (temp_path) + (void) unlink(temp_path); + + return log_error_errno(r, "Failed to save machine data %s: %m", m->state_file); +} + +static void machine_unlink(Machine *m) { + assert(m); + + if (m->unit) { + + char *sl; + + sl = strjoina("/run/systemd/machines/unit:", m->unit); + (void) unlink(sl); + } + + if (m->state_file) + (void) unlink(m->state_file); +} + +int machine_load(Machine *m) { + _cleanup_free_ char *realtime = NULL, *monotonic = NULL, *id = NULL, *leader = NULL, *class = NULL, *netif = NULL; + int r; + + assert(m); + + if (!m->state_file) + return 0; + + r = parse_env_file(m->state_file, NEWLINE, + "SCOPE", &m->unit, + "SCOPE_JOB", &m->scope_job, + "SERVICE", &m->service, + "ROOT", &m->root_directory, + "ID", &id, + "LEADER", &leader, + "CLASS", &class, + "REALTIME", &realtime, + "MONOTONIC", &monotonic, + "NETIF", &netif, + NULL); + if (r < 0) { + if (r == -ENOENT) + return 0; + + return log_error_errno(r, "Failed to read %s: %m", m->state_file); + } + + if (id) + sd_id128_from_string(id, &m->id); + + if (leader) + parse_pid(leader, &m->leader); + + if (class) { + MachineClass c; + + c = machine_class_from_string(class); + if (c >= 0) + m->class = c; + } + + if (realtime) { + unsigned long long l; + if (sscanf(realtime, "%llu", &l) > 0) + m->timestamp.realtime = l; + } + + if (monotonic) { + unsigned long long l; + if (sscanf(monotonic, "%llu", &l) > 0) + m->timestamp.monotonic = l; + } + + if (netif) { + size_t allocated = 0, nr = 0; + const char *p; + int *ni = NULL; + + p = netif; + for(;;) { + _cleanup_free_ char *word = NULL; + int ifi; + + r = extract_first_word(&p, &word, NULL, 0); + if (r == 0) + break; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_warning_errno(r, "Failed to parse NETIF: %s", netif); + break; + } + + if (parse_ifindex(word, &ifi) < 0) + continue; + + if (!GREEDY_REALLOC(ni, allocated, nr+1)) { + free(ni); + return log_oom(); + } + + ni[nr++] = ifi; + } + + free(m->netif); + m->netif = ni; + m->n_netif = nr; + } + + return r; +} + +static int machine_start_scope(Machine *m, sd_bus_message *properties, sd_bus_error *error) { + int r = 0; + + assert(m); + assert(m->class != MACHINE_HOST); + + if (!m->unit) { + _cleanup_free_ char *escaped = NULL; + char *scope, *description, *job = NULL; + + escaped = unit_name_escape(m->name); + if (!escaped) + return log_oom(); + + scope = strjoin("machine-", escaped, ".scope", NULL); + if (!scope) + return log_oom(); + + description = strjoina(m->class == MACHINE_VM ? "Virtual Machine " : "Container ", m->name); + + r = manager_start_scope(m->manager, scope, m->leader, SPECIAL_MACHINE_SLICE, description, properties, error, &job); + if (r < 0) { + log_error("Failed to start machine scope: %s", bus_error_message(error, r)); + free(scope); + return r; + } else { + m->unit = scope; + + free(m->scope_job); + m->scope_job = job; + } + } + + if (m->unit) + hashmap_put(m->manager->machine_units, m->unit, m); + + return r; +} + +int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error) { + int r; + + assert(m); + + if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) + return -EOPNOTSUPP; + + if (m->started) + return 0; + + r = hashmap_put(m->manager->machine_leaders, PID_TO_PTR(m->leader), m); + if (r < 0) + return r; + + /* Create cgroup */ + r = machine_start_scope(m, properties, error); + if (r < 0) + return r; + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_START), + "NAME=%s", m->name, + "LEADER="PID_FMT, m->leader, + LOG_MESSAGE("New machine %s.", m->name), + NULL); + + if (!dual_timestamp_is_set(&m->timestamp)) + dual_timestamp_get(&m->timestamp); + + m->started = true; + + /* Save new machine data */ + machine_save(m); + + machine_send_signal(m, true); + + return 0; +} + +static int machine_stop_scope(Machine *m) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char *job = NULL; + int r; + + assert(m); + assert(m->class != MACHINE_HOST); + + if (!m->unit) + return 0; + + r = manager_stop_unit(m->manager, m->unit, &error, &job); + if (r < 0) { + log_error("Failed to stop machine scope: %s", bus_error_message(&error, r)); + return r; + } + + free(m->scope_job); + m->scope_job = job; + + return 0; +} + +int machine_stop(Machine *m) { + int r; + assert(m); + + if (!IN_SET(m->class, MACHINE_CONTAINER, MACHINE_VM)) + return -EOPNOTSUPP; + + r = machine_stop_scope(m); + + m->stopping = true; + + machine_save(m); + + return r; +} + +int machine_finalize(Machine *m) { + assert(m); + + if (m->started) + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_MACHINE_STOP), + "NAME=%s", m->name, + "LEADER="PID_FMT, m->leader, + LOG_MESSAGE("Machine %s terminated.", m->name), + NULL); + + machine_unlink(m); + machine_add_to_gc_queue(m); + + if (m->started) { + machine_send_signal(m, false); + m->started = false; + } + + return 0; +} + +bool machine_check_gc(Machine *m, bool drop_not_started) { + assert(m); + + if (m->class == MACHINE_HOST) + return true; + + if (drop_not_started && !m->started) + return false; + + if (m->scope_job && manager_job_is_active(m->manager, m->scope_job)) + return true; + + if (m->unit && manager_unit_is_active(m->manager, m->unit)) + return true; + + return false; +} + +void machine_add_to_gc_queue(Machine *m) { + assert(m); + + if (m->in_gc_queue) + return; + + LIST_PREPEND(gc_queue, m->manager->machine_gc_queue, m); + m->in_gc_queue = true; +} + +MachineState machine_get_state(Machine *s) { + assert(s); + + if (s->class == MACHINE_HOST) + return MACHINE_RUNNING; + + if (s->stopping) + return MACHINE_CLOSING; + + if (s->scope_job) + return MACHINE_OPENING; + + return MACHINE_RUNNING; +} + +int machine_kill(Machine *m, KillWho who, int signo) { + assert(m); + + if (!IN_SET(m->class, MACHINE_VM, MACHINE_CONTAINER)) + return -EOPNOTSUPP; + + if (!m->unit) + return -ESRCH; + + if (who == KILL_LEADER) { + /* If we shall simply kill the leader, do so directly */ + + if (kill(m->leader, signo) < 0) + return -errno; + + return 0; + } + + /* Otherwise, make PID 1 do it for us, for the entire cgroup */ + return manager_kill_unit(m->manager, m->unit, signo, NULL); +} + +int machine_openpt(Machine *m, int flags) { + assert(m); + + switch (m->class) { + + case MACHINE_HOST: { + int fd; + + fd = posix_openpt(flags); + if (fd < 0) + return -errno; + + if (unlockpt(fd) < 0) + return -errno; + + return fd; + } + + case MACHINE_CONTAINER: + if (m->leader <= 0) + return -EINVAL; + + return openpt_in_namespace(m->leader, flags); + + default: + return -EOPNOTSUPP; + } +} + +int machine_open_terminal(Machine *m, const char *path, int mode) { + assert(m); + + switch (m->class) { + + case MACHINE_HOST: + return open_terminal(path, mode); + + case MACHINE_CONTAINER: + if (m->leader <= 0) + return -EINVAL; + + return open_terminal_in_namespace(m->leader, path, mode); + + default: + return -EOPNOTSUPP; + } +} + +MachineOperation *machine_operation_unref(MachineOperation *o) { + if (!o) + return NULL; + + sd_event_source_unref(o->event_source); + + safe_close(o->errno_fd); + + if (o->pid > 1) + (void) kill(o->pid, SIGKILL); + + sd_bus_message_unref(o->message); + + if (o->machine) { + LIST_REMOVE(operations, o->machine->operations, o); + o->machine->n_operations--; + } + + free(o); + return NULL; +} + +void machine_release_unit(Machine *m) { + assert(m); + + if (!m->unit) + return; + + (void) hashmap_remove(m->manager->machine_units, m->unit); + m->unit = mfree(m->unit); +} + +static const char* const machine_class_table[_MACHINE_CLASS_MAX] = { + [MACHINE_CONTAINER] = "container", + [MACHINE_VM] = "vm", + [MACHINE_HOST] = "host", +}; + +DEFINE_STRING_TABLE_LOOKUP(machine_class, MachineClass); + +static const char* const machine_state_table[_MACHINE_STATE_MAX] = { + [MACHINE_OPENING] = "opening", + [MACHINE_RUNNING] = "running", + [MACHINE_CLOSING] = "closing" +}; + +DEFINE_STRING_TABLE_LOOKUP(machine_state, MachineState); + +static const char* const kill_who_table[_KILL_WHO_MAX] = { + [KILL_LEADER] = "leader", + [KILL_ALL] = "all" +}; + +DEFINE_STRING_TABLE_LOOKUP(kill_who, KillWho); diff --git a/src/machine/src/machine.h b/src/machine/src/machine.h new file mode 100644 index 0000000000..1d8cc5911a --- /dev/null +++ b/src/machine/src/machine.h @@ -0,0 +1,124 @@ +#pragma once + +/*** + 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 . +***/ + +typedef struct Machine Machine; +typedef struct MachineOperation MachineOperation; +typedef enum KillWho KillWho; + +#include "list.h" +#include "machined.h" + +typedef enum MachineState { + MACHINE_OPENING, /* Machine is being registered */ + MACHINE_RUNNING, /* Machine is running */ + MACHINE_CLOSING, /* Machine is terminating */ + _MACHINE_STATE_MAX, + _MACHINE_STATE_INVALID = -1 +} MachineState; + +typedef enum MachineClass { + MACHINE_CONTAINER, + MACHINE_VM, + MACHINE_HOST, + _MACHINE_CLASS_MAX, + _MACHINE_CLASS_INVALID = -1 +} MachineClass; + +enum KillWho { + KILL_LEADER, + KILL_ALL, + _KILL_WHO_MAX, + _KILL_WHO_INVALID = -1 +}; + +#define MACHINE_OPERATIONS_MAX 64 + +struct MachineOperation { + Machine *machine; + pid_t pid; + sd_bus_message *message; + int errno_fd; + sd_event_source *event_source; + LIST_FIELDS(MachineOperation, operations); +}; + +struct Machine { + Manager *manager; + + char *name; + sd_id128_t id; + + MachineClass class; + + char *state_file; + char *service; + char *root_directory; + + char *unit; + char *scope_job; + + pid_t leader; + + dual_timestamp timestamp; + + bool in_gc_queue:1; + bool started:1; + bool stopping:1; + + sd_bus_message *create_message; + + int *netif; + unsigned n_netif; + + LIST_FIELDS(Machine, gc_queue); + + MachineOperation *operations; + unsigned n_operations; +}; + +Machine* machine_new(Manager *manager, MachineClass class, const char *name); +void machine_free(Machine *m); +bool machine_check_gc(Machine *m, bool drop_not_started); +void machine_add_to_gc_queue(Machine *m); +int machine_start(Machine *m, sd_bus_message *properties, sd_bus_error *error); +int machine_stop(Machine *m); +int machine_finalize(Machine *m); +int machine_save(Machine *m); +int machine_load(Machine *m); +int machine_kill(Machine *m, KillWho who, int signo); + +void machine_release_unit(Machine *m); + +MachineState machine_get_state(Machine *u); + +MachineOperation *machine_operation_unref(MachineOperation *o); + +const char* machine_class_to_string(MachineClass t) _const_; +MachineClass machine_class_from_string(const char *s) _pure_; + +const char* machine_state_to_string(MachineState t) _const_; +MachineState machine_state_from_string(const char *s) _pure_; + +const char *kill_who_to_string(KillWho k) _const_; +KillWho kill_who_from_string(const char *s) _pure_; + +int machine_openpt(Machine *m, int flags); +int machine_open_terminal(Machine *m, const char *path, int mode); diff --git a/src/machine/src/machinectl.c b/src/machine/src/machinectl.c new file mode 100644 index 0000000000..fb743ab6cb --- /dev/null +++ b/src/machine/src/machinectl.c @@ -0,0 +1,2678 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "cgroup-show.h" +#include "cgroup-util.h" +#include "copy.h" +#include "env-util.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "import-util.h" +#include "log.h" +#include "logs-show.h" +#include "macro.h" +#include "mkdir.h" +#include "pager.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "ptyfwd.h" +#include "signal-util.h" +#include "spawn-polkit-agent.h" +#include "strv.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "util.h" +#include "verbs.h" +#include "web-util.h" + +static char **arg_property = NULL; +static bool arg_all = false; +static bool arg_full = false; +static bool arg_no_pager = false; +static bool arg_legend = true; +static const char *arg_kill_who = NULL; +static int arg_signal = SIGTERM; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_read_only = false; +static bool arg_mkdir = false; +static bool arg_quiet = false; +static bool arg_ask_password = true; +static unsigned arg_lines = 10; +static OutputMode arg_output = OUTPUT_SHORT; +static bool arg_force = false; +static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; +static const char* arg_format = NULL; +static const char *arg_uid = NULL; +static char **arg_setenv = NULL; + +static void pager_open_if_enabled(void) { + + if (arg_no_pager) + return; + + pager_open(false); +} + +static void polkit_agent_open_if_enabled(void) { + + /* Open the polkit agent as a child process if necessary */ + + if (!arg_ask_password) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + polkit_agent_open(); +} + +static OutputFlags get_output_flags(void) { + return + arg_all * OUTPUT_SHOW_ALL | + arg_full * OUTPUT_FULL_WIDTH | + (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | + colors_enabled() * OUTPUT_COLOR | + !arg_quiet * OUTPUT_WARN_CUTOFF; +} + +typedef struct MachineInfo { + const char *name; + const char *class; + const char *service; +} MachineInfo; + +static int compare_machine_info(const void *a, const void *b) { + const MachineInfo *x = a, *y = b; + + return strcmp(x->name, y->name); +} + +static int list_machines(int argc, char *argv[], void *userdata) { + + size_t max_name = strlen("MACHINE"), max_class = strlen("CLASS"), max_service = strlen("SERVICE"); + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ MachineInfo *machines = NULL; + const char *name, *class, *service, *object; + size_t n_machines = 0, n_allocated = 0, j; + sd_bus *bus = userdata; + int r; + + assert(bus); + + pager_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "ListMachines", + &error, + &reply, + NULL); + if (r < 0) { + log_error("Could not get machines: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, 'a', "(ssso)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ssso)", &name, &class, &service, &object)) > 0) { + size_t l; + + if (name[0] == '.' && !arg_all) + continue; + + if (!GREEDY_REALLOC(machines, n_allocated, n_machines + 1)) + return log_oom(); + + machines[n_machines].name = name; + machines[n_machines].class = class; + machines[n_machines].service = service; + + l = strlen(name); + if (l > max_name) + max_name = l; + + l = strlen(class); + if (l > max_class) + max_class = l; + + l = strlen(service); + if (l > max_service) + max_service = l; + + n_machines ++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + qsort_safe(machines, n_machines, sizeof(MachineInfo), compare_machine_info); + + if (arg_legend) + printf("%-*s %-*s %-*s\n", + (int) max_name, "MACHINE", + (int) max_class, "CLASS", + (int) max_service, "SERVICE"); + + for (j = 0; j < n_machines; j++) + printf("%-*s %-*s %-*s\n", + (int) max_name, machines[j].name, + (int) max_class, machines[j].class, + (int) max_service, machines[j].service); + + if (arg_legend) + printf("\n%zu machines listed.\n", n_machines); + + return 0; +} + +typedef struct ImageInfo { + const char *name; + const char *type; + bool read_only; + usec_t crtime; + usec_t mtime; + uint64_t size; +} ImageInfo; + +static int compare_image_info(const void *a, const void *b) { + const ImageInfo *x = a, *y = b; + + return strcmp(x->name, y->name); +} + +static int list_images(int argc, char *argv[], void *userdata) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + size_t max_name = strlen("NAME"), max_type = strlen("TYPE"), max_size = strlen("USAGE"), max_crtime = strlen("CREATED"), max_mtime = strlen("MODIFIED"); + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ ImageInfo *images = NULL; + size_t n_images = 0, n_allocated = 0, j; + const char *name, *type, *object; + sd_bus *bus = userdata; + uint64_t crtime, mtime, size; + int read_only, r; + + assert(bus); + + pager_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "ListImages", + &error, + &reply, + ""); + if (r < 0) { + log_error("Could not get images: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssbttto)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ssbttto)", &name, &type, &read_only, &crtime, &mtime, &size, &object)) > 0) { + char buf[MAX(FORMAT_TIMESTAMP_MAX, FORMAT_BYTES_MAX)]; + size_t l; + + if (name[0] == '.' && !arg_all) + continue; + + if (!GREEDY_REALLOC(images, n_allocated, n_images + 1)) + return log_oom(); + + images[n_images].name = name; + images[n_images].type = type; + images[n_images].read_only = read_only; + images[n_images].crtime = crtime; + images[n_images].mtime = mtime; + images[n_images].size = size; + + l = strlen(name); + if (l > max_name) + max_name = l; + + l = strlen(type); + if (l > max_type) + max_type = l; + + if (crtime != 0) { + l = strlen(strna(format_timestamp(buf, sizeof(buf), crtime))); + if (l > max_crtime) + max_crtime = l; + } + + if (mtime != 0) { + l = strlen(strna(format_timestamp(buf, sizeof(buf), mtime))); + if (l > max_mtime) + max_mtime = l; + } + + if (size != (uint64_t) -1) { + l = strlen(strna(format_bytes(buf, sizeof(buf), size))); + if (l > max_size) + max_size = l; + } + + n_images++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info); + + if (arg_legend) + printf("%-*s %-*s %-3s %-*s %-*s %-*s\n", + (int) max_name, "NAME", + (int) max_type, "TYPE", + "RO", + (int) max_size, "USAGE", + (int) max_crtime, "CREATED", + (int) max_mtime, "MODIFIED"); + + for (j = 0; j < n_images; j++) { + char crtime_buf[FORMAT_TIMESTAMP_MAX], mtime_buf[FORMAT_TIMESTAMP_MAX], size_buf[FORMAT_BYTES_MAX]; + + printf("%-*s %-*s %s%-3s%s %-*s %-*s %-*s\n", + (int) max_name, images[j].name, + (int) max_type, images[j].type, + images[j].read_only ? ansi_highlight_red() : "", yes_no(images[j].read_only), images[j].read_only ? ansi_normal() : "", + (int) max_size, strna(format_bytes(size_buf, sizeof(size_buf), images[j].size)), + (int) max_crtime, strna(format_timestamp(crtime_buf, sizeof(crtime_buf), images[j].crtime)), + (int) max_mtime, strna(format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime))); + } + + if (arg_legend) + printf("\n%zu images listed.\n", n_images); + + return 0; +} + +static int show_unit_cgroup(sd_bus *bus, const char *unit, pid_t leader) { + _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; + const char *cgroup; + int r; + unsigned c; + + assert(bus); + assert(unit); + + if (arg_transport == BUS_TRANSPORT_REMOTE) + return 0; + + path = unit_dbus_path_from_name(unit); + if (!path) + return log_oom(); + + r = sd_bus_get_property( + bus, + "org.freedesktop.systemd1", + path, + unit_dbus_interface_from_name(unit), + "ControlGroup", + &error, + &reply, + "s"); + if (r < 0) { + log_error("Failed to query ControlGroup: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "s", &cgroup); + if (r < 0) + return bus_log_parse_error(r); + + if (cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, cgroup) != 0 && leader <= 0) + return 0; + + c = columns(); + if (c > 18) + c -= 18; + else + c = 0; + + show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, cgroup, "\t\t ", c, false, &leader, leader > 0, get_output_flags()); + return 0; +} + +static int print_addresses(sd_bus *bus, const char *name, int ifi, const char *prefix, const char *prefix2) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + assert(bus); + assert(name); + assert(prefix); + assert(prefix2); + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineAddresses", + NULL, + &reply, + "s", name); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "(iay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { + int family; + const void *a; + size_t sz; + char buffer[MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN)]; + + r = sd_bus_message_read(reply, "i", &family); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + return bus_log_parse_error(r); + + fputs(prefix, stdout); + fputs(inet_ntop(family, a, buffer, sizeof(buffer)), stdout); + if (family == AF_INET6 && ifi > 0) + printf("%%%i", ifi); + fputc('\n', stdout); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (prefix != prefix2) + prefix = prefix2; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return 0; +} + +static int print_os_release(sd_bus *bus, const char *name, const char *prefix) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *k, *v, *pretty = NULL; + int r; + + assert(bus); + assert(name); + assert(prefix); + + r = sd_bus_call_method(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineOSRelease", + NULL, + &reply, + "s", name); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(reply, 'a', "{ss}"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "{ss}", &k, &v)) > 0) { + if (streq(k, "PRETTY_NAME")) + pretty = v; + + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (pretty) + printf("%s%s\n", prefix, pretty); + + return 0; +} + +typedef struct MachineStatusInfo { + char *name; + sd_id128_t id; + char *class; + char *service; + char *unit; + char *root_directory; + pid_t leader; + struct dual_timestamp timestamp; + int *netif; + unsigned n_netif; +} MachineStatusInfo; + +static void machine_status_info_clear(MachineStatusInfo *info) { + if (info) { + free(info->name); + free(info->class); + free(info->service); + free(info->unit); + free(info->root_directory); + free(info->netif); + zero(*info); + } +} + +static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) { + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; + char since2[FORMAT_TIMESTAMP_MAX], *s2; + int ifi = -1; + + assert(bus); + assert(i); + + fputs(strna(i->name), stdout); + + if (!sd_id128_equal(i->id, SD_ID128_NULL)) + printf("(" SD_ID128_FORMAT_STR ")\n", SD_ID128_FORMAT_VAL(i->id)); + else + putchar('\n'); + + s1 = format_timestamp_relative(since1, sizeof(since1), i->timestamp.realtime); + s2 = format_timestamp(since2, sizeof(since2), i->timestamp.realtime); + + if (s1) + printf("\t Since: %s; %s\n", s2, s1); + else if (s2) + printf("\t Since: %s\n", s2); + + if (i->leader > 0) { + _cleanup_free_ char *t = NULL; + + printf("\t Leader: %u", (unsigned) i->leader); + + get_process_comm(i->leader, &t); + if (t) + printf(" (%s)", t); + + putchar('\n'); + } + + if (i->service) { + printf("\t Service: %s", i->service); + + if (i->class) + printf("; class %s", i->class); + + putchar('\n'); + } else if (i->class) + printf("\t Class: %s\n", i->class); + + if (i->root_directory) + printf("\t Root: %s\n", i->root_directory); + + if (i->n_netif > 0) { + unsigned c; + + fputs("\t Iface:", stdout); + + for (c = 0; c < i->n_netif; c++) { + char name[IF_NAMESIZE+1] = ""; + + if (if_indextoname(i->netif[c], name)) { + fputc(' ', stdout); + fputs(name, stdout); + + if (ifi < 0) + ifi = i->netif[c]; + else + ifi = 0; + } else + printf(" %i", i->netif[c]); + } + + fputc('\n', stdout); + } + + print_addresses(bus, i->name, ifi, + "\t Address: ", + "\t "); + + print_os_release(bus, i->name, "\t OS: "); + + if (i->unit) { + printf("\t Unit: %s\n", i->unit); + show_unit_cgroup(bus, i->unit, i->leader); + + if (arg_transport == BUS_TRANSPORT_LOCAL) + + show_journal_by_unit( + stdout, + i->unit, + arg_output, + 0, + i->timestamp.monotonic, + arg_lines, + 0, + get_output_flags() | OUTPUT_BEGIN_NEWLINE, + SD_JOURNAL_LOCAL_ONLY, + true, + NULL); + } +} + +static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { + MachineStatusInfo *i = userdata; + size_t l; + const void *v; + int r; + + assert_cc(sizeof(int32_t) == sizeof(int)); + r = sd_bus_message_read_array(m, SD_BUS_TYPE_INT32, &v, &l); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + i->n_netif = l / sizeof(int32_t); + i->netif = memdup(v, l); + if (!i->netif) + return -ENOMEM; + + return 0; +} + +static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) { + + static const struct bus_properties_map map[] = { + { "Name", "s", NULL, offsetof(MachineStatusInfo, name) }, + { "Class", "s", NULL, offsetof(MachineStatusInfo, class) }, + { "Service", "s", NULL, offsetof(MachineStatusInfo, service) }, + { "Unit", "s", NULL, offsetof(MachineStatusInfo, unit) }, + { "RootDirectory", "s", NULL, offsetof(MachineStatusInfo, root_directory) }, + { "Leader", "u", NULL, offsetof(MachineStatusInfo, leader) }, + { "Timestamp", "t", NULL, offsetof(MachineStatusInfo, timestamp.realtime) }, + { "TimestampMonotonic", "t", NULL, offsetof(MachineStatusInfo, timestamp.monotonic) }, + { "Id", "ay", bus_map_id128, offsetof(MachineStatusInfo, id) }, + { "NetworkInterfaces", "ai", map_netif, 0 }, + {} + }; + + _cleanup_(machine_status_info_clear) MachineStatusInfo info = {}; + int r; + + assert(verb); + assert(bus); + assert(path); + assert(new_line); + + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + path, + map, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %m"); + + if (*new_line) + printf("\n"); + *new_line = true; + + print_machine_status_info(bus, &info); + + return r; +} + +static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line) { + int r; + + assert(bus); + assert(path); + assert(new_line); + + if (*new_line) + printf("\n"); + + *new_line = true; + + r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all); + if (r < 0) + log_error_errno(r, "Could not get properties: %m"); + + return r; +} + +static int show_machine(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 *reply = NULL; + bool properties, new_line = false; + sd_bus *bus = userdata; + int r = 0, i; + + assert(bus); + + properties = !strstr(argv[0], "status"); + + pager_open_if_enabled(); + + if (properties && argc <= 1) { + + /* If no argument is specified, inspect the manager + * itself */ + r = show_machine_properties(bus, "/org/freedesktop/machine1", &new_line); + if (r < 0) + return r; + } + + for (i = 1; i < argc; i++) { + const char *path = NULL; + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachine", + &error, + &reply, + "s", argv[i]); + if (r < 0) { + log_error("Could not get path to machine: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + if (properties) + r = show_machine_properties(bus, path, &new_line); + else + r = show_machine_info(argv[0], bus, path, &new_line); + } + + return r; +} + +typedef struct ImageStatusInfo { + char *name; + char *path; + char *type; + int read_only; + usec_t crtime; + usec_t mtime; + uint64_t usage; + uint64_t limit; + uint64_t usage_exclusive; + uint64_t limit_exclusive; +} ImageStatusInfo; + +static void image_status_info_clear(ImageStatusInfo *info) { + if (info) { + free(info->name); + free(info->path); + free(info->type); + zero(*info); + } +} + +static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) { + char ts_relative[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; + char ts_absolute[FORMAT_TIMESTAMP_MAX], *s2; + char bs[FORMAT_BYTES_MAX], *s3; + char bs_exclusive[FORMAT_BYTES_MAX], *s4; + + assert(bus); + assert(i); + + if (i->name) { + fputs(i->name, stdout); + putchar('\n'); + } + + if (i->type) + printf("\t Type: %s\n", i->type); + + if (i->path) + printf("\t Path: %s\n", i->path); + + printf("\t RO: %s%s%s\n", + i->read_only ? ansi_highlight_red() : "", + i->read_only ? "read-only" : "writable", + i->read_only ? ansi_normal() : ""); + + s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->crtime); + s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->crtime); + if (s1 && s2) + printf("\t Created: %s; %s\n", s2, s1); + else if (s2) + printf("\t Created: %s\n", s2); + + s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->mtime); + s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->mtime); + if (s1 && s2) + printf("\tModified: %s; %s\n", s2, s1); + else if (s2) + printf("\tModified: %s\n", s2); + + s3 = format_bytes(bs, sizeof(bs), i->usage); + s4 = i->usage_exclusive != i->usage ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->usage_exclusive) : NULL; + if (s3 && s4) + printf("\t Usage: %s (exclusive: %s)\n", s3, s4); + else if (s3) + printf("\t Usage: %s\n", s3); + + s3 = format_bytes(bs, sizeof(bs), i->limit); + s4 = i->limit_exclusive != i->limit ? format_bytes(bs_exclusive, sizeof(bs_exclusive), i->limit_exclusive) : NULL; + if (s3 && s4) + printf("\t Limit: %s (exclusive: %s)\n", s3, s4); + else if (s3) + printf("\t Limit: %s\n", s3); +} + +static int show_image_info(sd_bus *bus, const char *path, bool *new_line) { + + static const struct bus_properties_map map[] = { + { "Name", "s", NULL, offsetof(ImageStatusInfo, name) }, + { "Path", "s", NULL, offsetof(ImageStatusInfo, path) }, + { "Type", "s", NULL, offsetof(ImageStatusInfo, type) }, + { "ReadOnly", "b", NULL, offsetof(ImageStatusInfo, read_only) }, + { "CreationTimestamp", "t", NULL, offsetof(ImageStatusInfo, crtime) }, + { "ModificationTimestamp", "t", NULL, offsetof(ImageStatusInfo, mtime) }, + { "Usage", "t", NULL, offsetof(ImageStatusInfo, usage) }, + { "Limit", "t", NULL, offsetof(ImageStatusInfo, limit) }, + { "UsageExclusive", "t", NULL, offsetof(ImageStatusInfo, usage_exclusive) }, + { "LimitExclusive", "t", NULL, offsetof(ImageStatusInfo, limit_exclusive) }, + {} + }; + + _cleanup_(image_status_info_clear) ImageStatusInfo info = {}; + int r; + + assert(bus); + assert(path); + assert(new_line); + + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + path, + map, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %m"); + + if (*new_line) + printf("\n"); + *new_line = true; + + print_image_status_info(bus, &info); + + return r; +} + +typedef struct PoolStatusInfo { + char *path; + uint64_t usage; + uint64_t limit; +} PoolStatusInfo; + +static void pool_status_info_clear(PoolStatusInfo *info) { + if (info) { + free(info->path); + zero(*info); + info->usage = -1; + info->limit = -1; + } +} + +static void print_pool_status_info(sd_bus *bus, PoolStatusInfo *i) { + char bs[FORMAT_BYTES_MAX], *s; + + if (i->path) + printf("\t Path: %s\n", i->path); + + s = format_bytes(bs, sizeof(bs), i->usage); + if (s) + printf("\t Usage: %s\n", s); + + s = format_bytes(bs, sizeof(bs), i->limit); + if (s) + printf("\t Limit: %s\n", s); +} + +static int show_pool_info(sd_bus *bus) { + + static const struct bus_properties_map map[] = { + { "PoolPath", "s", NULL, offsetof(PoolStatusInfo, path) }, + { "PoolUsage", "t", NULL, offsetof(PoolStatusInfo, usage) }, + { "PoolLimit", "t", NULL, offsetof(PoolStatusInfo, limit) }, + {} + }; + + _cleanup_(pool_status_info_clear) PoolStatusInfo info = { + .usage = (uint64_t) -1, + .limit = (uint64_t) -1, + }; + int r; + + assert(bus); + + r = bus_map_all_properties(bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + map, + &info); + if (r < 0) + return log_error_errno(r, "Could not get properties: %m"); + + print_pool_status_info(bus, &info); + + return 0; +} + + +static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) { + int r; + + assert(bus); + assert(path); + assert(new_line); + + if (*new_line) + printf("\n"); + + *new_line = true; + + r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all); + if (r < 0) + log_error_errno(r, "Could not get properties: %m"); + + return r; +} + +static int show_image(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 *reply = NULL; + bool properties, new_line = false; + sd_bus *bus = userdata; + int r = 0, i; + + assert(bus); + + properties = !strstr(argv[0], "status"); + + pager_open_if_enabled(); + + if (argc <= 1) { + + /* If no argument is specified, inspect the manager + * itself */ + + if (properties) + r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line); + else + r = show_pool_info(bus); + if (r < 0) + return r; + } + + for (i = 1; i < argc; i++) { + const char *path = NULL; + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetImage", + &error, + &reply, + "s", argv[i]); + if (r < 0) { + log_error("Could not get path to image: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + if (properties) + r = show_image_properties(bus, path, &new_line); + else + r = show_image_info(bus, path, &new_line); + } + + return r; +} + +static int kill_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + if (!arg_kill_who) + arg_kill_who = "all"; + + for (i = 1; i < argc; i++) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "KillMachine", + &error, + NULL, + "ssi", argv[i], arg_kill_who, arg_signal); + if (r < 0) { + log_error("Could not kill machine: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int reboot_machine(int argc, char *argv[], void *userdata) { + arg_kill_who = "leader"; + arg_signal = SIGINT; /* sysvinit + systemd */ + + return kill_machine(argc, argv, userdata); +} + +static int poweroff_machine(int argc, char *argv[], void *userdata) { + arg_kill_who = "leader"; + arg_signal = SIGRTMIN+4; /* only systemd */ + + return kill_machine(argc, argv, userdata); +} + +static int terminate_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + for (i = 1; i < argc; i++) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "TerminateMachine", + &error, + NULL, + "s", argv[i]); + if (r < 0) { + log_error("Could not terminate machine: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int copy_files(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *abs_host_path = NULL; + char *dest, *host_path, *container_path; + sd_bus *bus = userdata; + bool copy_from; + int r; + + assert(bus); + + polkit_agent_open_if_enabled(); + + copy_from = streq(argv[0], "copy-from"); + dest = argv[3] ?: argv[2]; + host_path = copy_from ? dest : argv[2]; + container_path = copy_from ? argv[2] : dest; + + if (!path_is_absolute(host_path)) { + r = path_make_absolute_cwd(host_path, &abs_host_path); + if (r < 0) + return log_error_errno(r, "Failed to make path absolute: %m"); + + host_path = abs_host_path; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + copy_from ? "CopyFromMachine" : "CopyToMachine", + &error, + NULL, + "sss", + argv[1], + copy_from ? container_path : host_path, + copy_from ? host_path : container_path); + if (r < 0) + return log_error_errno(r, "Failed to copy: %s", bus_error_message(&error, r)); + + return 0; +} + +static int bind_mount(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "BindMountMachine", + &error, + NULL, + "sssbb", + argv[1], + argv[2], + argv[3], + arg_read_only, + arg_mkdir); + if (r < 0) { + log_error("Failed to bind mount: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int on_machine_removed(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { + PTYForward ** forward = (PTYForward**) userdata; + int r; + + assert(m); + assert(forward); + + if (*forward) { + /* If the forwarder is already initialized, tell it to + * exit on the next vhangup(), so that we still flush + * out what might be queued and exit then. */ + + r = pty_forward_set_ignore_vhangup(*forward, false); + if (r >= 0) + return 0; + + log_error_errno(r, "Failed to set ignore_vhangup flag: %m"); + } + + /* On error, or when the forwarder is not initialized yet, quit immediately */ + sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), EXIT_FAILURE); + return 0; +} + +static int process_forward(sd_event *event, PTYForward **forward, int master, PTYForwardFlags flags, const char *name) { + char last_char = 0; + bool machine_died; + int ret = 0, r; + + assert(event); + assert(master >= 0); + assert(name); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); + + if (streq(name, ".host")) + log_info("Connected to the local host. Press ^] three times within 1s to exit session."); + else + log_info("Connected to machine %s. Press ^] three times within 1s to exit session.", name); + + sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + + r = pty_forward_new(event, master, flags, forward); + if (r < 0) + return log_error_errno(r, "Failed to create PTY forwarder: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + pty_forward_get_last_char(*forward, &last_char); + + machine_died = + (flags & PTY_FORWARD_IGNORE_VHANGUP) && + pty_forward_get_ignore_vhangup(*forward) == 0; + + *forward = pty_forward_free(*forward); + + if (last_char != '\n') + fputc('\n', stdout); + + if (machine_died) + log_info("Machine %s terminated.", name); + else if (streq(name, ".host")) + log_info("Connection to the local host terminated."); + else + log_info("Connection to machine %s terminated.", name); + + sd_event_get_exit_code(event, &ret); + return ret; +} + +static int login_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int master = -1, r; + sd_bus *bus = userdata; + const char *pty, *match, *machine; + + assert(bus); + + if (!strv_isempty(arg_setenv) || arg_uid) { + log_error("--setenv= and --uid= are not supported for 'login'. Use 'shell' instead."); + return -EINVAL; + } + + if (arg_transport != BUS_TRANSPORT_LOCAL && + arg_transport != BUS_TRANSPORT_MACHINE) { + log_error("Login only supported on local machines."); + return -EOPNOTSUPP; + } + + polkit_agent_open_if_enabled(); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + machine = argc < 2 || isempty(argv[1]) ? ".host" : argv[1]; + + match = strjoina("type='signal'," + "sender='org.freedesktop.machine1'," + "path='/org/freedesktop/machine1',", + "interface='org.freedesktop.machine1.Manager'," + "member='MachineRemoved'," + "arg0='", machine, "'"); + + r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); + if (r < 0) + return log_error_errno(r, "Failed to add machine removal match: %m"); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "OpenMachineLogin", + &error, + &reply, + "s", machine); + if (r < 0) { + log_error("Failed to get login PTY: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "hs", &master, &pty); + if (r < 0) + return bus_log_parse_error(r); + + return process_forward(event, &forward, master, PTY_FORWARD_IGNORE_VHANGUP, machine); +} + +static int shell_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + int master = -1, r; + sd_bus *bus = userdata; + const char *pty, *match, *machine, *path, *uid = NULL; + + assert(bus); + + if (arg_transport != BUS_TRANSPORT_LOCAL && + arg_transport != BUS_TRANSPORT_MACHINE) { + log_error("Shell only supported on local machines."); + return -EOPNOTSUPP; + } + + /* Pass $TERM to shell session, if not explicitly specified. */ + if (!strv_find_prefix(arg_setenv, "TERM=")) { + const char *t; + + t = strv_find_prefix(environ, "TERM="); + if (t) { + if (strv_extend(&arg_setenv, t) < 0) + return log_oom(); + } + } + + polkit_agent_open_if_enabled(); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + machine = argc < 2 || isempty(argv[1]) ? NULL : argv[1]; + + if (arg_uid) + uid = arg_uid; + else if (machine) { + const char *at; + + at = strchr(machine, '@'); + if (at) { + uid = strndupa(machine, at - machine); + machine = at + 1; + } + } + + if (isempty(machine)) + machine = ".host"; + + match = strjoina("type='signal'," + "sender='org.freedesktop.machine1'," + "path='/org/freedesktop/machine1',", + "interface='org.freedesktop.machine1.Manager'," + "member='MachineRemoved'," + "arg0='", machine, "'"); + + r = sd_bus_add_match(bus, &slot, match, on_machine_removed, &forward); + if (r < 0) + return log_error_errno(r, "Failed to add machine removal match: %m"); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "OpenMachineShell"); + if (r < 0) + return bus_log_create_error(r); + + path = argc < 3 || isempty(argv[2]) ? NULL : argv[2]; + + r = sd_bus_message_append(m, "sss", machine, uid, path); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, strv_length(argv) <= 3 ? NULL : argv + 2); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, arg_setenv); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to get shell PTY: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "hs", &master, &pty); + if (r < 0) + return bus_log_parse_error(r); + + return process_forward(event, &forward, master, 0, machine); +} + +static int remove_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + for (i = 1; i < argc; i++) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "RemoveImage", + &error, + NULL, + "s", argv[i]); + if (r < 0) { + log_error("Could not remove image: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int rename_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r; + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "RenameImage", + &error, + NULL, + "ss", argv[1], argv[2]); + if (r < 0) { + log_error("Could not rename image: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int clone_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r; + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "CloneImage", + &error, + NULL, + "ssb", argv[1], argv[2], arg_read_only); + if (r < 0) { + log_error("Could not clone image: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int read_only_image(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int b = true, r; + + if (argc > 2) { + b = parse_boolean(argv[2]); + if (b < 0) { + log_error("Failed to parse boolean argument: %s", argv[2]); + return -EINVAL; + } + } + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "MarkImageReadOnly", + &error, + NULL, + "sb", argv[1], b); + if (r < 0) { + log_error("Could not mark image read-only: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int make_service_name(const char *name, char **ret) { + _cleanup_free_ char *e = NULL; + int r; + + assert(name); + assert(ret); + + if (!machine_name_is_valid(name)) { + log_error("Invalid machine name %s.", name); + return -EINVAL; + } + + e = unit_name_escape(name); + if (!e) + return log_oom(); + + r = unit_name_build("systemd-nspawn", e, ".service", ret); + if (r < 0) + return log_error_errno(r, "Failed to build unit name: %m"); + + return 0; +} + +static int start_machine(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_oom(); + + for (i = 1; i < argc; i++) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *unit = NULL; + const char *object; + + r = make_service_name(argv[i], &unit); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartUnit", + &error, + &reply, + "ss", unit, "fail"); + if (r < 0) { + log_error("Failed to start unit: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_add(w, object); + if (r < 0) + return log_oom(); + } + + r = bus_wait_for_jobs(w, arg_quiet, NULL); + if (r < 0) + return r; + + return 0; +} + +static int enable_machine(int argc, char *argv[], void *userdata) { + _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; + int carries_install_info = 0; + const char *method = NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + method = streq(argv[0], "enable") ? "EnableUnitFiles" : "DisableUnitFiles"; + + 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); + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + for (i = 1; i < argc; i++) { + _cleanup_free_ char *unit = NULL; + + r = make_service_name(argv[i], &unit); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", unit); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + if (streq(argv[0], "enable")) + r = sd_bus_message_append(m, "bb", false, false); + else + r = sd_bus_message_append(m, "b", false); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to enable or disable unit: %s", bus_error_message(&error, -r)); + return r; + } + + if (streq(argv[0], "enable")) { + r = sd_bus_message_read(reply, "b", carries_install_info); + if (r < 0) + return bus_log_parse_error(r); + } + + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Reload", + &error, + NULL, + NULL); + if (r < 0) { + log_error("Failed to reload daemon: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int match_log_message(sd_bus_message *m, void *userdata, sd_bus_error *error) { + const char **our_path = userdata, *line; + unsigned priority; + int r; + + assert(m); + assert(our_path); + + r = sd_bus_message_read(m, "us", &priority, &line); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!streq_ptr(*our_path, sd_bus_message_get_path(m))) + return 0; + + if (arg_quiet && LOG_PRI(priority) >= LOG_INFO) + return 0; + + log_full(priority, "%s", line); + return 0; +} + +static int match_transfer_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { + const char **our_path = userdata, *path, *result; + uint32_t id; + int r; + + assert(m); + assert(our_path); + + r = sd_bus_message_read(m, "uos", &id, &path, &result); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + if (!streq_ptr(*our_path, path)) + return 0; + + sd_event_exit(sd_bus_get_event(sd_bus_message_get_bus(m)), !streq_ptr(result, "done")); + return 0; +} + +static int transfer_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + assert(s); + assert(si); + + if (!arg_quiet) + log_info("Continuing download in the background. Use \"machinectl cancel-transfer %" PRIu32 "\" to abort transfer.", PTR_TO_UINT32(userdata)); + + sd_event_exit(sd_event_source_get_event(s), EINTR); + return 0; +} + +static int transfer_image_common(sd_bus *bus, sd_bus_message *m) { + _cleanup_(sd_bus_slot_unrefp) sd_bus_slot *slot_job_removed = NULL, *slot_log_message = NULL; + _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_event_unrefp) sd_event* event = NULL; + const char *path = NULL; + uint32_t id; + int r; + + assert(bus); + assert(m); + + polkit_agent_open_if_enabled(); + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + r = sd_bus_add_match( + bus, + &slot_job_removed, + "type='signal'," + "sender='org.freedesktop.import1'," + "interface='org.freedesktop.import1.Manager'," + "member='TransferRemoved'," + "path='/org/freedesktop/import1'", + match_transfer_removed, &path); + if (r < 0) + return log_error_errno(r, "Failed to install match: %m"); + + r = sd_bus_add_match( + bus, + &slot_log_message, + "type='signal'," + "sender='org.freedesktop.import1'," + "interface='org.freedesktop.import1.Transfer'," + "member='LogMessage'", + match_log_message, &path); + if (r < 0) + return log_error_errno(r, "Failed to install match: %m"); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed transfer image: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "uo", &id, &path); + if (r < 0) + return bus_log_parse_error(r); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + + if (!arg_quiet) + log_info("Enqueued transfer job %u. Press C-c to continue download in background.", id); + + sd_event_add_signal(event, NULL, SIGINT, transfer_signal_handler, UINT32_TO_PTR(id)); + sd_event_add_signal(event, NULL, SIGTERM, transfer_signal_handler, UINT32_TO_PTR(id)); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + return -r; +} + +static int import_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *ll = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + if (argc >= 2) + path = argv[1]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (argc >= 3) + local = argv[2]; + else if (path) + local = basename(path); + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (!local) { + log_error("Need either path or local name."); + return -EINVAL; + } + + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + + if (path) { + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ImportTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsbb", + fd >= 0 ? fd : STDIN_FILENO, + local, + arg_force, + arg_read_only); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int import_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *ll = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + if (argc >= 2) + path = argv[1]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (argc >= 3) + local = argv[2]; + else if (path) + local = basename(path); + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (!local) { + log_error("Need either path or local name."); + return -EINVAL; + } + + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + + if (path) { + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ImportRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "hsbb", + fd >= 0 ? fd : STDIN_FILENO, + local, + arg_force, + arg_read_only); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static void determine_compression_from_filename(const char *p) { + if (arg_format) + return; + + if (!p) + return; + + if (endswith(p, ".xz")) + arg_format = "xz"; + else if (endswith(p, ".gz")) + arg_format = "gzip"; + else if (endswith(p, ".bz2")) + arg_format = "bzip2"; +} + +static int export_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + local = argv[1]; + if (!machine_name_is_valid(local)) { + log_error("Machine name %s is not valid.", local); + return -EINVAL; + } + + if (argc >= 3) + path = argv[2]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (path) { + determine_compression_from_filename(path); + + fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ExportTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "shs", + local, + fd >= 0 ? fd : STDOUT_FILENO, + arg_format); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int export_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_close_ int fd = -1; + const char *local = NULL, *path = NULL; + sd_bus *bus = userdata; + int r; + + assert(bus); + + local = argv[1]; + if (!machine_name_is_valid(local)) { + log_error("Machine name %s is not valid.", local); + return -EINVAL; + } + + if (argc >= 3) + path = argv[2]; + if (isempty(path) || streq(path, "-")) + path = NULL; + + if (path) { + determine_compression_from_filename(path); + + fd = open(path, O_WRONLY|O_CREAT|O_TRUNC|O_CLOEXEC|O_NOCTTY, 0666); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ExportRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "shs", + local, + fd >= 0 ? fd : STDOUT_FILENO, + arg_format); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int pull_tar(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = userdata; + int r; + + assert(bus); + + remote = argv[1]; + if (!http_url_is_valid(remote)) { + log_error("URL '%s' is not valid.", remote); + return -EINVAL; + } + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (local) { + r = tar_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "PullTar"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + arg_force); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +static int pull_raw(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_free_ char *l = NULL, *ll = NULL; + const char *local, *remote; + sd_bus *bus = userdata; + int r; + + assert(bus); + + remote = argv[1]; + if (!http_url_is_valid(remote)) { + log_error("URL '%s' is not valid.", remote); + return -EINVAL; + } + + if (argc >= 3) + local = argv[2]; + else { + r = import_url_last_component(remote, &l); + if (r < 0) + return log_error_errno(r, "Failed to get final component of URL: %m"); + + local = l; + } + + if (isempty(local) || streq(local, "-")) + local = NULL; + + if (local) { + r = raw_strip_suffixes(local, &ll); + if (r < 0) + return log_oom(); + + local = ll; + + if (!machine_name_is_valid(local)) { + log_error("Local name %s is not a suitable machine name.", local); + return -EINVAL; + } + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "PullRaw"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sssb", + remote, + local, + import_verify_to_string(arg_verify), + arg_force); + if (r < 0) + return bus_log_create_error(r); + + return transfer_image_common(bus, m); +} + +typedef struct TransferInfo { + uint32_t id; + const char *type; + const char *remote; + const char *local; + double progress; +} TransferInfo; + +static int compare_transfer_info(const void *a, const void *b) { + const TransferInfo *x = a, *y = b; + + return strcmp(x->local, y->local); +} + +static int list_transfers(int argc, char *argv[], void *userdata) { + size_t max_type = strlen("TYPE"), max_local = strlen("LOCAL"), max_remote = strlen("REMOTE"); + _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_ TransferInfo *transfers = NULL; + size_t n_transfers = 0, n_allocated = 0, j; + const char *type, *remote, *local, *object; + sd_bus *bus = userdata; + uint32_t id, max_id = 0; + double progress; + int r; + + pager_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "ListTransfers", + &error, + &reply, + NULL); + if (r < 0) { + log_error("Could not get transfers: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, 'a', "(usssdo)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(usssdo)", &id, &type, &remote, &local, &progress, &object)) > 0) { + size_t l; + + if (!GREEDY_REALLOC(transfers, n_allocated, n_transfers + 1)) + return log_oom(); + + transfers[n_transfers].id = id; + transfers[n_transfers].type = type; + transfers[n_transfers].remote = remote; + transfers[n_transfers].local = local; + transfers[n_transfers].progress = progress; + + l = strlen(type); + if (l > max_type) + max_type = l; + + l = strlen(remote); + if (l > max_remote) + max_remote = l; + + l = strlen(local); + if (l > max_local) + max_local = l; + + if (id > max_id) + max_id = id; + + n_transfers ++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + qsort_safe(transfers, n_transfers, sizeof(TransferInfo), compare_transfer_info); + + if (arg_legend) + printf("%-*s %-*s %-*s %-*s %-*s\n", + (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), "ID", + (int) 7, "PERCENT", + (int) max_type, "TYPE", + (int) max_local, "LOCAL", + (int) max_remote, "REMOTE"); + + for (j = 0; j < n_transfers; j++) + printf("%*" PRIu32 " %*u%% %-*s %-*s %-*s\n", + (int) MAX(2U, DECIMAL_STR_WIDTH(max_id)), transfers[j].id, + (int) 6, (unsigned) (transfers[j].progress * 100), + (int) max_type, transfers[j].type, + (int) max_local, transfers[j].local, + (int) max_remote, transfers[j].remote); + + if (arg_legend) + printf("\n%zu transfers listed.\n", n_transfers); + + return 0; +} + +static int cancel_transfer(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + int r, i; + + assert(bus); + + polkit_agent_open_if_enabled(); + + for (i = 1; i < argc; i++) { + uint32_t id; + + r = safe_atou32(argv[i], &id); + if (r < 0) + return log_error_errno(r, "Failed to parse transfer id: %s", argv[i]); + + r = sd_bus_call_method( + bus, + "org.freedesktop.import1", + "/org/freedesktop/import1", + "org.freedesktop.import1.Manager", + "CancelTransfer", + &error, + NULL, + "u", id); + if (r < 0) { + log_error("Could not cancel transfer: %s", bus_error_message(&error, -r)); + return r; + } + } + + return 0; +} + +static int set_limit(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus = userdata; + uint64_t limit; + int r; + + if (STR_IN_SET(argv[argc-1], "-", "none", "infinity")) + limit = (uint64_t) -1; + else { + r = parse_size(argv[argc-1], 1024, &limit); + if (r < 0) + return log_error("Failed to parse size: %s", argv[argc-1]); + } + + if (argc > 2) + /* With two arguments changes the quota limit of the + * specified image */ + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "SetImageLimit", + &error, + NULL, + "st", argv[1], limit); + else + /* With one argument changes the pool quota limit */ + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "SetPoolLimit", + &error, + NULL, + "t", limit); + + if (r < 0) { + log_error("Could not set limit: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int help(int argc, char *argv[], void *userdata) { + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Send control commands to or query the virtual machine and container\n" + "registration manager.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-legend Do not show the headers and footers\n" + " --no-ask-password Do not ask for system passwords\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " -p --property=NAME Show only properties by this name\n" + " -q --quiet Suppress output\n" + " -a --all Show all properties, including empty ones\n" + " -l --full Do not ellipsize output\n" + " --kill-who=WHO Who to send signal to\n" + " -s --signal=SIGNAL Which signal to send\n" + " --uid=USER Specify user ID to invoke shell as\n" + " --setenv=VAR=VALUE Add an environment variable for shell\n" + " --read-only Create read-only bind mount\n" + " --mkdir Create directory before bind mounting, if missing\n" + " -n --lines=INTEGER Number of journal entries to show\n" + " -o --output=STRING Change journal output mode (short,\n" + " short-monotonic, verbose, export, json,\n" + " json-pretty, json-sse, cat)\n" + " --verify=MODE Verification mode for downloaded images (no,\n" + " checksum, signature)\n" + " --force Download image even if already exists\n\n" + "Machine Commands:\n" + " list List running VMs and containers\n" + " status NAME... Show VM/container details\n" + " show [NAME...] Show properties of one or more VMs/containers\n" + " start NAME... Start container as a service\n" + " login [NAME] Get a login prompt in a container or on the\n" + " local host\n" + " shell [[USER@]NAME [COMMAND...]]\n" + " Invoke a shell (or other command) in a container\n" + " or on the local host\n" + " enable NAME... Enable automatic container start at boot\n" + " disable NAME... Disable automatic container start at boot\n" + " poweroff NAME... Power off one or more containers\n" + " reboot NAME... Reboot one or more containers\n" + " terminate NAME... Terminate one or more VMs/containers\n" + " kill NAME... Send signal to processes of a VM/container\n" + " copy-to NAME PATH [PATH] Copy files from the host to a container\n" + " copy-from NAME PATH [PATH] Copy files from a container to the host\n" + " bind NAME PATH [PATH] Bind mount a path from the host into a container\n\n" + "Image Commands:\n" + " list-images Show available container and VM images\n" + " image-status [NAME...] Show image details\n" + " show-image [NAME...] Show properties of image\n" + " clone NAME NAME Clone an image\n" + " rename NAME NAME Rename an image\n" + " read-only NAME [BOOL] Mark or unmark image read-only\n" + " remove NAME... Remove an image\n" + " set-limit [NAME] BYTES Set image or pool size limit (disk quota)\n\n" + "Image Transfer Commands:\n" + " pull-tar URL [NAME] Download a TAR container image\n" + " pull-raw URL [NAME] Download a RAW container or VM image\n" + " import-tar FILE [NAME] Import a local TAR container image\n" + " import-raw FILE [NAME] Import a local RAW container or VM image\n" + " export-tar NAME [FILE] Export a TAR container image locally\n" + " export-raw NAME [FILE] Export a RAW container or VM image locally\n" + " list-transfers Show list of downloads in progress\n" + " cancel-transfer Cancel a download\n" + , program_invocation_short_name); + + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_NO_LEGEND, + ARG_KILL_WHO, + ARG_READ_ONLY, + ARG_MKDIR, + ARG_NO_ASK_PASSWORD, + ARG_VERIFY, + ARG_FORCE, + ARG_FORMAT, + ARG_UID, + ARG_SETENV, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "property", required_argument, NULL, 'p' }, + { "all", no_argument, NULL, 'a' }, + { "full", no_argument, NULL, 'l' }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "kill-who", required_argument, NULL, ARG_KILL_WHO }, + { "signal", required_argument, NULL, 's' }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "read-only", no_argument, NULL, ARG_READ_ONLY }, + { "mkdir", no_argument, NULL, ARG_MKDIR }, + { "quiet", no_argument, NULL, 'q' }, + { "lines", required_argument, NULL, 'n' }, + { "output", required_argument, NULL, 'o' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "verify", required_argument, NULL, ARG_VERIFY }, + { "force", no_argument, NULL, ARG_FORCE }, + { "format", required_argument, NULL, ARG_FORMAT }, + { "uid", required_argument, NULL, ARG_UID }, + { "setenv", required_argument, NULL, ARG_SETENV }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hp:als:H:M:qn:o:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + return help(0, NULL, NULL); + + case ARG_VERSION: + return version(); + + case 'p': + r = strv_extend(&arg_property, optarg); + if (r < 0) + return log_oom(); + + /* If the user asked for a particular + * property, show it to him, even if it is + * empty. */ + arg_all = true; + break; + + case 'a': + arg_all = true; + break; + + case 'l': + arg_full = true; + break; + + case 'n': + if (safe_atou(optarg, &arg_lines) < 0) { + log_error("Failed to parse lines '%s'", optarg); + return -EINVAL; + } + break; + + case 'o': + arg_output = output_mode_from_string(optarg); + if (arg_output < 0) { + log_error("Unknown output '%s'.", optarg); + return -EINVAL; + } + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_NO_LEGEND: + arg_legend = false; + break; + + case ARG_KILL_WHO: + arg_kill_who = optarg; + break; + + case 's': + arg_signal = signal_from_string_try_harder(optarg); + if (arg_signal < 0) { + log_error("Failed to parse signal string %s.", optarg); + return -EINVAL; + } + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_READ_ONLY: + arg_read_only = true; + break; + + case ARG_MKDIR: + arg_mkdir = true; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_VERIFY: + arg_verify = import_verify_from_string(optarg); + if (arg_verify < 0) { + log_error("Failed to parse --verify= setting: %s", optarg); + return -EINVAL; + } + break; + + case ARG_FORCE: + arg_force = true; + break; + + case ARG_FORMAT: + if (!STR_IN_SET(optarg, "uncompressed", "xz", "gzip", "bzip2")) { + log_error("Unknown format: %s", optarg); + return -EINVAL; + } + + arg_format = optarg; + break; + + case ARG_UID: + arg_uid = optarg; + break; + + case ARG_SETENV: + if (!env_assignment_is_valid(optarg)) { + log_error("Environment assignment invalid: %s", optarg); + return -EINVAL; + } + + r = strv_extend(&arg_setenv, optarg); + if (r < 0) + return log_oom(); + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int machinectl_main(int argc, char *argv[], sd_bus *bus) { + + static const Verb verbs[] = { + { "help", VERB_ANY, VERB_ANY, 0, help }, + { "list", VERB_ANY, 1, VERB_DEFAULT, list_machines }, + { "list-images", VERB_ANY, 1, 0, list_images }, + { "status", 2, VERB_ANY, 0, show_machine }, + { "image-status", VERB_ANY, VERB_ANY, 0, show_image }, + { "show", VERB_ANY, VERB_ANY, 0, show_machine }, + { "show-image", VERB_ANY, VERB_ANY, 0, show_image }, + { "terminate", 2, VERB_ANY, 0, terminate_machine }, + { "reboot", 2, VERB_ANY, 0, reboot_machine }, + { "poweroff", 2, VERB_ANY, 0, poweroff_machine }, + { "kill", 2, VERB_ANY, 0, kill_machine }, + { "login", VERB_ANY, 2, 0, login_machine }, + { "shell", VERB_ANY, VERB_ANY, 0, shell_machine }, + { "bind", 3, 4, 0, bind_mount }, + { "copy-to", 3, 4, 0, copy_files }, + { "copy-from", 3, 4, 0, copy_files }, + { "remove", 2, VERB_ANY, 0, remove_image }, + { "rename", 3, 3, 0, rename_image }, + { "clone", 3, 3, 0, clone_image }, + { "read-only", 2, 3, 0, read_only_image }, + { "start", 2, VERB_ANY, 0, start_machine }, + { "enable", 2, VERB_ANY, 0, enable_machine }, + { "disable", 2, VERB_ANY, 0, enable_machine }, + { "import-tar", 2, 3, 0, import_tar }, + { "import-raw", 2, 3, 0, import_raw }, + { "export-tar", 2, 3, 0, export_tar }, + { "export-raw", 2, 3, 0, export_raw }, + { "pull-tar", 2, 3, 0, pull_tar }, + { "pull-raw", 2, 3, 0, pull_raw }, + { "list-transfers", VERB_ANY, 1, 0, list_transfers }, + { "cancel-transfer", 2, VERB_ANY, 0, cancel_transfer }, + { "set-limit", 2, 3, 0, set_limit }, + {} + }; + + return dispatch_verb(argc, argv, verbs, bus); +} + +int main(int argc, char*argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = bus_connect_transport(arg_transport, arg_host, false, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + sd_bus_set_allow_interactive_authorization(bus, arg_ask_password); + + r = machinectl_main(argc, argv, bus); + +finish: + pager_close(); + polkit_agent_close(); + + strv_free(arg_property); + strv_free(arg_setenv); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/machine/src/machined-dbus.c b/src/machine/src/machined-dbus.c new file mode 100644 index 0000000000..96f2c4769e --- /dev/null +++ b/src/machine/src/machined-dbus.c @@ -0,0 +1,1551 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "btrfs-util.h" +#include "bus-common-errors.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "hostname-util.h" +#include "image-dbus.h" +#include "io-util.h" +#include "machine-dbus.h" +#include "machine-image.h" +#include "machine-pool.h" +#include "machined.h" +#include "path-util.h" +#include "process-util.h" +#include "stdio-util.h" +#include "strv.h" +#include "unit-name.h" +#include "user-util.h" + +static int property_get_pool_path( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + assert(bus); + assert(reply); + + return sd_bus_message_append(reply, "s", "/var/lib/machines"); +} + +static int property_get_pool_usage( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_close_ int fd = -1; + uint64_t usage = (uint64_t) -1; + struct stat st; + + assert(bus); + assert(reply); + + /* We try to read the quota info from /var/lib/machines, as + * well as the usage of the loopback file + * /var/lib/machines.raw, and pick the larger value. */ + + fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd >= 0) { + BtrfsQuotaInfo q; + + if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) + usage = q.referenced; + } + + if (stat("/var/lib/machines.raw", &st) >= 0) { + if (usage == (uint64_t) -1 || st.st_blocks * 512ULL > usage) + usage = st.st_blocks * 512ULL; + } + + return sd_bus_message_append(reply, "t", usage); +} + +static int property_get_pool_limit( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + _cleanup_close_ int fd = -1; + uint64_t size = (uint64_t) -1; + struct stat st; + + assert(bus); + assert(reply); + + /* We try to read the quota limit from /var/lib/machines, as + * well as the size of the loopback file + * /var/lib/machines.raw, and pick the smaller value. */ + + fd = open("/var/lib/machines", O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd >= 0) { + BtrfsQuotaInfo q; + + if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) + size = q.referenced_max; + } + + if (stat("/var/lib/machines.raw", &st) >= 0) { + if (size == (uint64_t) -1 || (uint64_t) st.st_size < size) + size = st.st_size; + } + + return sd_bus_message_append(reply, "t", size); +} + +static int method_get_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *p = NULL; + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + p = machine_bus_path(machine); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", p); +} + +static int method_get_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *p = NULL; + Manager *m = userdata; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + r = image_find(name, NULL); + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); + if (r < 0) + return r; + + p = image_bus_path(name); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", p); +} + +static int method_get_machine_by_pid(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *p = NULL; + Manager *m = userdata; + Machine *machine = NULL; + pid_t pid; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(pid_t) == sizeof(uint32_t)); + + r = sd_bus_message_read(message, "u", &pid); + if (r < 0) + return r; + + if (pid < 0) + return -EINVAL; + + if (pid == 0) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + r = sd_bus_creds_get_pid(creds, &pid); + if (r < 0) + return r; + } + + r = manager_get_machine_by_pid(m, pid, &machine); + if (r < 0) + return r; + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_MACHINE_FOR_PID, "PID "PID_FMT" does not belong to any known machine", pid); + + p = machine_bus_path(machine); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", p); +} + +static int method_list_machines(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + Manager *m = userdata; + Machine *machine; + Iterator i; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + r = sd_bus_message_open_container(reply, 'a', "(ssso)"); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + HASHMAP_FOREACH(machine, m->machines, i) { + _cleanup_free_ char *p = NULL; + + p = machine_bus_path(machine); + if (!p) + return -ENOMEM; + + r = sd_bus_message_append(reply, "(ssso)", + machine->name, + strempty(machine_class_to_string(machine->class)), + machine->service, + p); + if (r < 0) + return sd_bus_error_set_errno(error, r); + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + return sd_bus_send(NULL, reply, NULL); +} + +static int method_create_or_register_machine(Manager *manager, sd_bus_message *message, bool read_network, Machine **_m, sd_bus_error *error) { + const char *name, *service, *class, *root_directory; + const int32_t *netif = NULL; + MachineClass c; + uint32_t leader; + sd_id128_t id; + const void *v; + Machine *m; + size_t n, n_netif = 0; + int r; + + assert(manager); + assert(message); + assert(_m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + if (!machine_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine name"); + + r = sd_bus_message_read_array(message, 'y', &v, &n); + if (r < 0) + return r; + if (n == 0) + id = SD_ID128_NULL; + else if (n == 16) + memcpy(&id, v, n); + else + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine ID parameter"); + + r = sd_bus_message_read(message, "ssus", &service, &class, &leader, &root_directory); + if (r < 0) + return r; + + if (read_network) { + size_t i; + + r = sd_bus_message_read_array(message, 'i', (const void**) &netif, &n_netif); + if (r < 0) + return r; + + n_netif /= sizeof(int32_t); + + for (i = 0; i < n_netif; i++) { + if (netif[i] <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid network interface index %i", netif[i]); + } + } + + if (isempty(class)) + c = _MACHINE_CLASS_INVALID; + else { + c = machine_class_from_string(class); + if (c < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid machine class parameter"); + } + + if (leader == 1) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid leader PID"); + + if (!isempty(root_directory) && !path_is_absolute(root_directory)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root directory must be empty or an absolute path"); + + if (leader == 0) { + _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; + + r = sd_bus_query_sender_creds(message, SD_BUS_CREDS_PID, &creds); + if (r < 0) + return r; + + assert_cc(sizeof(uint32_t) == sizeof(pid_t)); + + r = sd_bus_creds_get_pid(creds, (pid_t*) &leader); + if (r < 0) + return r; + } + + if (hashmap_get(manager->machines, name)) + return sd_bus_error_setf(error, BUS_ERROR_MACHINE_EXISTS, "Machine '%s' already exists", name); + + r = manager_add_machine(manager, name, &m); + if (r < 0) + return r; + + m->leader = leader; + m->class = c; + m->id = id; + + if (!isempty(service)) { + m->service = strdup(service); + if (!m->service) { + r = -ENOMEM; + goto fail; + } + } + + if (!isempty(root_directory)) { + m->root_directory = strdup(root_directory); + if (!m->root_directory) { + r = -ENOMEM; + goto fail; + } + } + + if (n_netif > 0) { + assert_cc(sizeof(int32_t) == sizeof(int)); + m->netif = memdup(netif, sizeof(int32_t) * n_netif); + if (!m->netif) { + r = -ENOMEM; + goto fail; + } + + m->n_netif = n_netif; + } + + *_m = m; + + return 1; + +fail: + machine_add_to_gc_queue(m); + return r; +} + +static int method_create_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) { + Manager *manager = userdata; + Machine *m = NULL; + int r; + + assert(message); + assert(manager); + + r = method_create_or_register_machine(manager, message, read_network, &m, error); + if (r < 0) + return r; + + r = sd_bus_message_enter_container(message, 'a', "(sv)"); + if (r < 0) + goto fail; + + r = machine_start(m, message, error); + if (r < 0) + goto fail; + + m->create_message = sd_bus_message_ref(message); + return 1; + +fail: + machine_add_to_gc_queue(m); + return r; +} + +static int method_create_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_create_machine_internal(message, true, userdata, error); +} + +static int method_create_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_create_machine_internal(message, false, userdata, error); +} + +static int method_register_machine_internal(sd_bus_message *message, bool read_network, void *userdata, sd_bus_error *error) { + Manager *manager = userdata; + _cleanup_free_ char *p = NULL; + Machine *m = NULL; + int r; + + assert(message); + assert(manager); + + r = method_create_or_register_machine(manager, message, read_network, &m, error); + if (r < 0) + return r; + + r = cg_pid_get_unit(m->leader, &m->unit); + if (r < 0) { + r = sd_bus_error_set_errnof(error, r, "Failed to determine unit of process "PID_FMT" : %s", m->leader, strerror(-r)); + goto fail; + } + + r = machine_start(m, NULL, error); + if (r < 0) + goto fail; + + p = machine_bus_path(m); + if (!p) { + r = -ENOMEM; + goto fail; + } + + return sd_bus_reply_method_return(message, "o", p); + +fail: + machine_add_to_gc_queue(m); + return r; +} + +static int method_register_machine_with_network(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_register_machine_internal(message, true, userdata, error); +} + +static int method_register_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return method_register_machine_internal(message, false, userdata, error); +} + +static int method_terminate_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_terminate(message, machine, error); +} + +static int method_kill_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_kill(message, machine, error); +} + +static int method_get_machine_addresses(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_get_addresses(message, machine, error); +} + +static int method_get_machine_os_release(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_get_os_release(message, machine, error); +} + +static int method_list_images(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(image_hashmap_freep) Hashmap *images = NULL; + Manager *m = userdata; + Image *image; + Iterator i; + int r; + + assert(message); + assert(m); + + images = hashmap_new(&string_hash_ops); + if (!images) + return -ENOMEM; + + r = image_discover(images); + if (r < 0) + return r; + + r = sd_bus_message_new_method_return(message, &reply); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(ssbttto)"); + if (r < 0) + return r; + + HASHMAP_FOREACH(image, images, i) { + _cleanup_free_ char *p = NULL; + + p = image_bus_path(image->name); + if (!p) + return -ENOMEM; + + r = sd_bus_message_append(reply, "(ssbttto)", + image->name, + image_type_to_string(image->type), + image->read_only, + image->crtime, + image->mtime, + image->usage, + p); + 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_open_machine_pty(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return sd_bus_error_set_errno(error, r); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_open_pty(message, machine, error); +} + +static int method_open_machine_login(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_open_login(message, machine, error); +} + +static int method_open_machine_shell(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_open_shell(message, machine, error); +} + +static int method_bind_mount_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_bind_mount(message, machine, error); +} + +static int method_copy_machine(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + const char *name; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + return bus_machine_method_copy(message, machine, error); +} + +static int method_remove_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image* i = NULL; + const char *name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (!image_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); + + r = image_find(name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); + + i->userdata = userdata; + return bus_image_method_remove(message, i, error); +} + +static int method_rename_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image* i = NULL; + const char *old_name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &old_name); + if (r < 0) + return r; + + if (!image_name_is_valid(old_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name); + + r = image_find(old_name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name); + + i->userdata = userdata; + return bus_image_method_rename(message, i, error); +} + +static int method_clone_image(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image *i = NULL; + const char *old_name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &old_name); + if (r < 0) + return r; + + if (!image_name_is_valid(old_name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", old_name); + + r = image_find(old_name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", old_name); + + i->userdata = userdata; + return bus_image_method_clone(message, i, error); +} + +static int method_mark_image_read_only(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image *i = NULL; + const char *name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (!image_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); + + r = image_find(name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); + + i->userdata = userdata; + return bus_image_method_mark_read_only(message, i, error); +} + +static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + uint64_t limit; + int r; + + assert(message); + + r = sd_bus_message_read(message, "t", &limit); + if (r < 0) + return r; + if (!FILE_SIZE_VALID_OR_INFINITY(limit)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New limit out of range"); + + r = bus_verify_polkit_async( + message, + CAP_SYS_ADMIN, + "org.freedesktop.machine1.manage-machines", + NULL, + false, + UID_INVALID, + &m->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* Will call us back */ + + /* Set up the machine directory if necessary */ + r = setup_machine_directory(limit, error); + if (r < 0) + return r; + + /* Resize the backing loopback device, if there is one, except if we asked to drop any limit */ + if (limit != (uint64_t) -1) { + r = btrfs_resize_loopback("/var/lib/machines", limit, false); + if (r == -ENOTTY) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs."); + if (r < 0 && r != -ENODEV) /* ignore ENODEV, as that's what is returned if the file system is not on loopback */ + return sd_bus_error_set_errnof(error, r, "Failed to adjust loopback limit: %m"); + } + + (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, limit); + + r = btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, limit); + if (r == -ENOTTY) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs."); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to adjust quota limit: %m"); + + return sd_bus_reply_method_return(message, NULL); +} + +static int method_set_image_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(image_unrefp) Image *i = NULL; + const char *name; + int r; + + assert(message); + + r = sd_bus_message_read(message, "s", &name); + if (r < 0) + return r; + + if (!image_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Image name '%s' is invalid.", name); + + r = image_find(name, &i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_IMAGE, "No image '%s' known", name); + + i->userdata = userdata; + return bus_image_method_set_limit(message, i, error); +} + +static int method_map_from_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_fclose_ FILE *f = NULL; + Manager *m = userdata; + const char *name, *p; + Machine *machine; + uint32_t uid; + int r; + + r = sd_bus_message_read(message, "su", &name, &uid); + if (r < 0) + return r; + + if (!uid_is_valid(uid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + if (machine->class != MACHINE_CONTAINER) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines."); + + p = procfs_file_alloca(machine->leader, "uid_map"); + f = fopen(p, "re"); + if (!f) + return -errno; + + for (;;) { + uid_t uid_base, uid_shift, uid_range, converted; + int k; + + errno = 0; + k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno > 0) + return -errno; + + return -EIO; + } + + if (uid < uid_base || uid >= uid_base + uid_range) + continue; + + converted = uid - uid_base + uid_shift; + if (!uid_is_valid(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + + return sd_bus_reply_method_return(message, "u", (uint32_t) converted); + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "Machine '%s' has no matching user mappings.", name); +} + +static int method_map_to_machine_user(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + uid_t uid; + Iterator i; + int r; + + r = sd_bus_message_read(message, "u", &uid); + if (r < 0) + return r; + if (!uid_is_valid(uid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + if (uid < 0x10000) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "User " UID_FMT " belongs to host UID range", uid); + + HASHMAP_FOREACH(machine, m->machines, i) { + _cleanup_fclose_ FILE *f = NULL; + char p[strlen("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1]; + + if (machine->class != MACHINE_CONTAINER) + continue; + + xsprintf(p, "/proc/" UID_FMT "/uid_map", machine->leader); + f = fopen(p, "re"); + if (!f) { + log_warning_errno(errno, "Failed top open %s, ignoring,", p); + continue; + } + + for (;;) { + _cleanup_free_ char *o = NULL; + uid_t uid_base, uid_shift, uid_range, converted; + int k; + + errno = 0; + k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT, &uid_base, &uid_shift, &uid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno > 0) + return -errno; + + return -EIO; + } + + if (uid < uid_shift || uid >= uid_shift + uid_range) + continue; + + converted = (uid - uid_shift + uid_base); + if (!uid_is_valid(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid user ID " UID_FMT, uid); + + o = machine_bus_path(machine); + if (!o) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); + } + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_USER_MAPPING, "No matching user mapping for " UID_FMT ".", uid); +} + +static int method_map_from_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { + _cleanup_fclose_ FILE *f = NULL; + Manager *m = groupdata; + const char *name, *p; + Machine *machine; + uint32_t gid; + int r; + + r = sd_bus_message_read(message, "su", &name, &gid); + if (r < 0) + return r; + + if (!gid_is_valid(gid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + + machine = hashmap_get(m->machines, name); + if (!machine) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name); + + if (machine->class != MACHINE_CONTAINER) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Not supported for non-container machines."); + + p = procfs_file_alloca(machine->leader, "gid_map"); + f = fopen(p, "re"); + if (!f) + return -errno; + + for (;;) { + gid_t gid_base, gid_shift, gid_range, converted; + int k; + + errno = 0; + k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno > 0) + return -errno; + + return -EIO; + } + + if (gid < gid_base || gid >= gid_base + gid_range) + continue; + + converted = gid - gid_base + gid_shift; + if (!gid_is_valid(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + + return sd_bus_reply_method_return(message, "u", (uint32_t) converted); + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Machine '%s' has no matching group mappings.", name); +} + +static int method_map_to_machine_group(sd_bus_message *message, void *groupdata, sd_bus_error *error) { + Manager *m = groupdata; + Machine *machine; + gid_t gid; + Iterator i; + int r; + + r = sd_bus_message_read(message, "u", &gid); + if (r < 0) + return r; + if (!gid_is_valid(gid)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + if (gid < 0x10000) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "Group " GID_FMT " belongs to host GID range", gid); + + HASHMAP_FOREACH(machine, m->machines, i) { + _cleanup_fclose_ FILE *f = NULL; + char p[strlen("/proc//gid_map") + DECIMAL_STR_MAX(pid_t) + 1]; + + if (machine->class != MACHINE_CONTAINER) + continue; + + xsprintf(p, "/proc/" GID_FMT "/gid_map", machine->leader); + f = fopen(p, "re"); + if (!f) { + log_warning_errno(errno, "Failed top open %s, ignoring,", p); + continue; + } + + for (;;) { + _cleanup_free_ char *o = NULL; + gid_t gid_base, gid_shift, gid_range, converted; + int k; + + errno = 0; + k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT, &gid_base, &gid_shift, &gid_range); + if (k < 0 && feof(f)) + break; + if (k != 3) { + if (ferror(f) && errno > 0) + return -errno; + + return -EIO; + } + + if (gid < gid_shift || gid >= gid_shift + gid_range) + continue; + + converted = (gid - gid_shift + gid_base); + if (!gid_is_valid(converted)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid group ID " GID_FMT, gid); + + o = machine_bus_path(machine); + if (!o) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "sou", machine->name, o, (uint32_t) converted); + } + } + + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_GROUP_MAPPING, "No matching group mapping for " GID_FMT ".", gid); +} + +const sd_bus_vtable manager_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("PoolPath", "s", property_get_pool_path, 0, 0), + SD_BUS_PROPERTY("PoolUsage", "t", property_get_pool_usage, 0, 0), + SD_BUS_PROPERTY("PoolLimit", "t", property_get_pool_limit, 0, 0), + SD_BUS_METHOD("GetMachine", "s", "o", method_get_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetImage", "s", "o", method_get_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetMachineByPID", "u", "o", method_get_machine_by_pid, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListMachines", NULL, "a(ssso)", method_list_machines, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("ListImages", NULL, "a(ssbttto)", method_list_images, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CreateMachine", "sayssusa(sv)", "o", method_create_machine, 0), + SD_BUS_METHOD("CreateMachineWithNetwork", "sayssusaia(sv)", "o", method_create_machine_with_network, 0), + SD_BUS_METHOD("RegisterMachine", "sayssus", "o", method_register_machine, 0), + SD_BUS_METHOD("RegisterMachineWithNetwork", "sayssusai", "o", method_register_machine_with_network, 0), + SD_BUS_METHOD("TerminateMachine", "s", NULL, method_terminate_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("KillMachine", "ssi", NULL, method_kill_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetMachineAddresses", "s", "a(iay)", method_get_machine_addresses, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetMachineOSRelease", "s", "a{ss}", method_get_machine_os_release, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenMachinePTY", "s", "hs", method_open_machine_pty, 0), + SD_BUS_METHOD("OpenMachineLogin", "s", "hs", method_open_machine_login, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("OpenMachineShell", "sssasas", "hs", method_open_machine_shell, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("BindMountMachine", "sssbb", NULL, method_bind_mount_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CopyFromMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CopyToMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetPoolLimit", "t", NULL, method_set_pool_limit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetImageLimit", "st", NULL, method_set_image_limit, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapFromMachineUser", "su", "u", method_map_from_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapToMachineUser", "u", "sou", method_map_to_machine_user, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapFromMachineGroup", "su", "u", method_map_from_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("MapToMachineGroup", "u", "sou", method_map_to_machine_group, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_SIGNAL("MachineNew", "so", 0), + SD_BUS_SIGNAL("MachineRemoved", "so", 0), + SD_BUS_VTABLE_END +}; + +int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *path, *result, *unit; + Manager *m = userdata; + Machine *machine; + uint32_t id; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "uoss", &id, &path, &unit, &result); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + machine = hashmap_get(m->machine_units, unit); + if (!machine) + return 0; + + if (streq_ptr(path, machine->scope_job)) { + machine->scope_job = mfree(machine->scope_job); + + if (machine->started) { + if (streq(result, "done")) + machine_send_create_reply(machine, NULL); + else { + _cleanup_(sd_bus_error_free) sd_bus_error e = SD_BUS_ERROR_NULL; + + sd_bus_error_setf(&e, BUS_ERROR_JOB_FAILED, "Start job for unit %s failed with '%s'", unit, result); + + machine_send_create_reply(machine, &e); + } + } + + machine_save(machine); + } + + machine_add_to_gc_queue(machine); + return 0; +} + +int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *unit = NULL; + const char *path; + Manager *m = userdata; + Machine *machine; + int r; + + assert(message); + assert(m); + + path = sd_bus_message_get_path(message); + if (!path) + return 0; + + r = unit_name_from_dbus_path(path, &unit); + if (r == -EINVAL) /* not for a unit */ + return 0; + if (r < 0){ + log_oom(); + return 0; + } + + machine = hashmap_get(m->machine_units, unit); + if (!machine) + return 0; + + machine_add_to_gc_queue(machine); + return 0; +} + +int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error) { + const char *path, *unit; + Manager *m = userdata; + Machine *machine; + int r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "so", &unit, &path); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + + machine = hashmap_get(m->machine_units, unit); + if (!machine) + return 0; + + machine_add_to_gc_queue(machine); + return 0; +} + +int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + Machine *machine; + Iterator i; + int b, r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) { + bus_log_parse_error(r); + return 0; + } + if (b) + return 0; + + /* systemd finished reloading, let's recheck all our machines */ + log_debug("System manager has been reloaded, rechecking machines..."); + + HASHMAP_FOREACH(machine, m->machines, i) + machine_add_to_gc_queue(machine); + + return 0; +} + +int manager_start_scope( + Manager *manager, + const char *scope, + pid_t pid, + const char *slice, + const char *description, + sd_bus_message *more_properties, + sd_bus_error *error, + char **job) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + int r; + + assert(manager); + assert(scope); + assert(pid > 1); + + r = sd_bus_message_new_method_call( + manager->bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "ss", strempty(scope), "fail"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return r; + + if (!isempty(slice)) { + r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); + if (r < 0) + return r; + } + + if (!isempty(description)) { + r = sd_bus_message_append(m, "(sv)", "Description", "s", description); + if (r < 0) + return r; + } + + r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, pid); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "Delegate", "b", 1); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "TasksMax", "t", UINT64_C(16384)); + if (r < 0) + return bus_log_create_error(r); + + if (more_properties) { + r = sd_bus_message_copy(m, more_properties, true); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) + return r; + + r = sd_bus_call(manager->bus, m, 0, error, &reply); + if (r < 0) + return r; + + if (job) { + const char *j; + char *copy; + + r = sd_bus_message_read(reply, "o", &j); + if (r < 0) + return r; + + copy = strdup(j); + if (!copy) + return -ENOMEM; + + *job = copy; + } + + return 1; +} + +int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + assert(manager); + assert(unit); + + r = sd_bus_call_method( + manager->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StopUnit", + error, + &reply, + "ss", unit, "fail"); + if (r < 0) { + if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) || + sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) { + + if (job) + *job = NULL; + + sd_bus_error_free(error); + return 0; + } + + return r; + } + + if (job) { + const char *j; + char *copy; + + r = sd_bus_message_read(reply, "o", &j); + if (r < 0) + return r; + + copy = strdup(j); + if (!copy) + return -ENOMEM; + + *job = copy; + } + + return 1; +} + +int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error) { + assert(manager); + assert(unit); + + return sd_bus_call_method( + manager->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KillUnit", + error, + NULL, + "ssi", unit, "all", signo); +} + +int manager_unit_is_active(Manager *manager, const char *unit) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *path = NULL; + const char *state; + int r; + + assert(manager); + assert(unit); + + path = unit_dbus_path_from_name(unit); + if (!path) + return -ENOMEM; + + r = sd_bus_get_property( + manager->bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "ActiveState", + &error, + &reply, + "s"); + if (r < 0) { + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) || + sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED)) + return true; + + if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT) || + sd_bus_error_has_name(&error, BUS_ERROR_LOAD_FAILED)) + return false; + + return r; + } + + r = sd_bus_message_read(reply, "s", &state); + if (r < 0) + return -EINVAL; + + return !STR_IN_SET(state, "inactive", "failed"); +} + +int manager_job_is_active(Manager *manager, const char *path) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + + assert(manager); + assert(path); + + r = sd_bus_get_property( + manager->bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Job", + "State", + &error, + &reply, + "s"); + if (r < 0) { + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_NO_REPLY) || + sd_bus_error_has_name(&error, SD_BUS_ERROR_DISCONNECTED)) + return true; + + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_UNKNOWN_OBJECT)) + return false; + + return r; + } + + /* We don't actually care about the state really. The fact + * that we could read the job state is enough for us */ + + return true; +} + +int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine) { + Machine *mm; + int r; + + assert(m); + assert(pid >= 1); + assert(machine); + + mm = hashmap_get(m->machine_leaders, PID_TO_PTR(pid)); + if (!mm) { + _cleanup_free_ char *unit = NULL; + + r = cg_pid_get_unit(pid, &unit); + if (r >= 0) + mm = hashmap_get(m->machine_units, unit); + } + if (!mm) + return 0; + + *machine = mm; + return 1; +} + +int manager_add_machine(Manager *m, const char *name, Machine **_machine) { + Machine *machine; + + assert(m); + assert(name); + + machine = hashmap_get(m->machines, name); + if (!machine) { + machine = machine_new(m, _MACHINE_CLASS_INVALID, name); + if (!machine) + return -ENOMEM; + } + + if (_machine) + *_machine = machine; + + return 0; +} diff --git a/src/machine/src/machined.c b/src/machine/src/machined.c new file mode 100644 index 0000000000..6ada8671f8 --- /dev/null +++ b/src/machine/src/machined.c @@ -0,0 +1,407 @@ +/*** + 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 . +***/ + +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "hostname-util.h" +#include "label.h" +#include "machine-image.h" +#include "machined.h" +#include "signal-util.h" + +Manager *manager_new(void) { + Manager *m; + int r; + + m = new0(Manager, 1); + if (!m) + return NULL; + + m->machines = hashmap_new(&string_hash_ops); + m->machine_units = hashmap_new(&string_hash_ops); + m->machine_leaders = hashmap_new(NULL); + + if (!m->machines || !m->machine_units || !m->machine_leaders) { + manager_free(m); + return NULL; + } + + r = sd_event_default(&m->event); + if (r < 0) { + manager_free(m); + return NULL; + } + + sd_event_set_watchdog(m->event, true); + + return m; +} + +void manager_free(Manager *m) { + Machine *machine; + Image *i; + + assert(m); + + while ((machine = hashmap_first(m->machines))) + machine_free(machine); + + hashmap_free(m->machines); + hashmap_free(m->machine_units); + hashmap_free(m->machine_leaders); + + while ((i = hashmap_steal_first(m->image_cache))) + image_unref(i); + + hashmap_free(m->image_cache); + + sd_event_source_unref(m->image_cache_defer_event); + + bus_verify_polkit_async_registry_free(m->polkit_registry); + + sd_bus_unref(m->bus); + sd_event_unref(m->event); + + free(m); +} + +static int manager_add_host_machine(Manager *m) { + _cleanup_free_ char *rd = NULL, *unit = NULL; + sd_id128_t mid; + Machine *t; + int r; + + if (m->host_machine) + return 0; + + r = sd_id128_get_machine(&mid); + if (r < 0) + return log_error_errno(r, "Failed to get machine ID: %m"); + + rd = strdup("/"); + if (!rd) + return log_oom(); + + unit = strdup("-.slice"); + if (!unit) + return log_oom(); + + t = machine_new(m, MACHINE_HOST, ".host"); + if (!t) + return log_oom(); + + t->leader = 1; + t->id = mid; + + t->root_directory = rd; + t->unit = unit; + rd = unit = NULL; + + dual_timestamp_from_boottime_or_monotonic(&t->timestamp, 0); + + m->host_machine = t; + + return 0; +} + +int manager_enumerate_machines(Manager *m) { + _cleanup_closedir_ DIR *d = NULL; + struct dirent *de; + int r = 0; + + assert(m); + + r = manager_add_host_machine(m); + if (r < 0) + return r; + + /* Read in machine data stored on disk */ + d = opendir("/run/systemd/machines"); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open /run/systemd/machines: %m"); + } + + FOREACH_DIRENT(de, d, return -errno) { + struct Machine *machine; + int k; + + if (!dirent_is_file(de)) + continue; + + /* Ignore symlinks that map the unit name to the machine */ + if (startswith(de->d_name, "unit:")) + continue; + + if (!machine_name_is_valid(de->d_name)) + continue; + + k = manager_add_machine(m, de->d_name, &machine); + if (k < 0) { + r = log_error_errno(k, "Failed to add machine by file name %s: %m", de->d_name); + continue; + } + + machine_add_to_gc_queue(machine); + + k = machine_load(machine); + if (k < 0) + r = k; + } + + return r; +} + +static int manager_connect_bus(Manager *m) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(m); + assert(!m->bus); + + r = sd_bus_default_system(&m->bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/machine1", "org.freedesktop.machine1.Manager", manager_vtable, m); + if (r < 0) + return log_error_errno(r, "Failed to add manager object vtable: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/machine", "org.freedesktop.machine1.Machine", machine_vtable, machine_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to add machine object vtable: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/machine", machine_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to add machine enumerator: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/machine1/image", "org.freedesktop.machine1.Image", image_vtable, image_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to add image object vtable: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/machine1/image", image_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to add image enumerator: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='JobRemoved'," + "path='/org/freedesktop/systemd1'", + match_job_removed, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for JobRemoved: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='UnitRemoved'," + "path='/org/freedesktop/systemd1'", + match_unit_removed, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for UnitRemoved: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.DBus.Properties'," + "member='PropertiesChanged'," + "arg0='org.freedesktop.systemd1.Unit'", + match_properties_changed, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for PropertiesChanged: %m"); + + r = sd_bus_add_match(m->bus, + NULL, + "type='signal'," + "sender='org.freedesktop.systemd1'," + "interface='org.freedesktop.systemd1.Manager'," + "member='Reloading'," + "path='/org/freedesktop/systemd1'", + match_reloading, + m); + if (r < 0) + return log_error_errno(r, "Failed to add match for Reloading: %m"); + + r = sd_bus_call_method( + m->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Subscribe", + &error, + NULL, NULL); + if (r < 0) { + log_error("Failed to enable subscription: %s", bus_error_message(&error, r)); + return r; + } + + r = sd_bus_request_name(m->bus, "org.freedesktop.machine1", 0); + if (r < 0) + return log_error_errno(r, "Failed to register name: %m"); + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + return 0; +} + +void manager_gc(Manager *m, bool drop_not_started) { + Machine *machine; + + assert(m); + + while ((machine = m->machine_gc_queue)) { + LIST_REMOVE(gc_queue, m->machine_gc_queue, machine); + machine->in_gc_queue = false; + + /* First, if we are not closing yet, initiate stopping */ + if (!machine_check_gc(machine, drop_not_started) && + machine_get_state(machine) != MACHINE_CLOSING) + machine_stop(machine); + + /* Now, the stop 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)) { + machine_finalize(machine); + machine_free(machine); + } + } +} + +int manager_startup(Manager *m) { + Machine *machine; + Iterator i; + int r; + + assert(m); + + /* Connect to the bus */ + r = manager_connect_bus(m); + if (r < 0) + return r; + + /* Deserialize state */ + manager_enumerate_machines(m); + + /* Remove stale objects before we start them */ + manager_gc(m, false); + + /* And start everything */ + HASHMAP_FOREACH(machine, m->machines, i) + machine_start(machine, NULL, NULL); + + return 0; +} + +static bool check_idle(void *userdata) { + Manager *m = userdata; + + manager_gc(m, true); + + return hashmap_isempty(m->machines); +} + +int manager_run(Manager *m) { + assert(m); + + return bus_event_loop_with_idle( + m->event, + m->bus, + "org.freedesktop.machine1", + DEFAULT_EXIT_USEC, + check_idle, m); +} + +int main(int argc, char *argv[]) { + Manager *m = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_set_facility(LOG_AUTH); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + /* Always create the directories people can create inotify + * watches in. Note that some applications might check for the + * existence of /run/systemd/machines/ to determine whether + * machined is available, so please always make sure this + * check stays in. */ + mkdir_label("/run/systemd/machines", 0755); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, -1) >= 0); + + m = manager_new(); + if (!m) { + r = log_oom(); + goto finish; + } + + r = manager_startup(m); + if (r < 0) { + log_error_errno(r, "Failed to fully start up daemon: %m"); + goto finish; + } + + log_debug("systemd-machined running as pid "PID_FMT, getpid()); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + r = manager_run(m); + + log_debug("systemd-machined stopped as pid "PID_FMT, getpid()); + +finish: + manager_free(m); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/machine/src/machined.h b/src/machine/src/machined.h new file mode 100644 index 0000000000..0fe50aaa66 --- /dev/null +++ b/src/machine/src/machined.h @@ -0,0 +1,78 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include +#include + +#include "hashmap.h" +#include "list.h" + +typedef struct Manager Manager; + +#include "image-dbus.h" +#include "machine-dbus.h" +#include "machine.h" + +struct Manager { + sd_event *event; + sd_bus *bus; + + Hashmap *machines; + Hashmap *machine_units; + Hashmap *machine_leaders; + + Hashmap *polkit_registry; + + Hashmap *image_cache; + sd_event_source *image_cache_defer_event; + + LIST_HEAD(Machine, machine_gc_queue); + + Machine *host_machine; +}; + +Manager *manager_new(void); +void manager_free(Manager *m); + +int manager_add_machine(Manager *m, const char *name, Machine **_machine); +int manager_enumerate_machines(Manager *m); + +int manager_startup(Manager *m); +int manager_run(Manager *m); + +void manager_gc(Manager *m, bool drop_not_started); + +int manager_get_machine_by_pid(Manager *m, pid_t pid, Machine **machine); + +extern const sd_bus_vtable manager_vtable[]; + +int match_reloading(sd_bus_message *message, void *userdata, sd_bus_error *error); +int match_unit_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); +int match_properties_changed(sd_bus_message *message, void *userdata, sd_bus_error *error); +int match_job_removed(sd_bus_message *message, void *userdata, sd_bus_error *error); + +int manager_start_scope(Manager *manager, const char *scope, pid_t pid, const char *slice, const char *description, sd_bus_message *more_properties, sd_bus_error *error, char **job); +int manager_stop_unit(Manager *manager, const char *unit, sd_bus_error *error, char **job); +int manager_kill_unit(Manager *manager, const char *unit, int signo, sd_bus_error *error); +int manager_unit_is_active(Manager *manager, const char *unit); +int manager_job_is_active(Manager *manager, const char *path); diff --git a/src/machine/src/org.freedesktop.machine1.conf b/src/machine/src/org.freedesktop.machine1.conf new file mode 100644 index 0000000000..9d40b90151 --- /dev/null +++ b/src/machine/src/org.freedesktop.machine1.conf @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/machine/src/org.freedesktop.machine1.policy.in b/src/machine/src/org.freedesktop.machine1.policy.in new file mode 100644 index 0000000000..69f78a5c25 --- /dev/null +++ b/src/machine/src/org.freedesktop.machine1.policy.in @@ -0,0 +1,102 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Log into a local container + <_message>Authentication is required to log into a local container. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Log into the local host + <_message>Authentication is required to log into the local host. + + auth_admin + auth_admin + yes + + + + + <_description>Acquire a shell in a local container + <_message>Authentication is required to acquire a shell in a local container. + + auth_admin + auth_admin + auth_admin_keep + + org.freedesktop.login1.login + + + + <_description>Acquire a shell on the local host + <_message>Authentication is required to acquire a shell on the local host. + + auth_admin + auth_admin + auth_admin_keep + + org.freedesktop.login1.host-login + + + + <_description>Acquire a pseudo TTY in a local container + <_message>Authentication is required to acquire a pseudo TTY in a local container. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Acquire a pseudo TTY on the local host + <_message>Authentication is required to acquire a pseudo TTY on the local host. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Manage local virtual machines and containers + <_message>Authentication is required to manage local virtual machines and containers. + + auth_admin + auth_admin + auth_admin_keep + + org.freedesktop.login1.shell org.freedesktop.login1.login + + + + <_description>Manage local virtual machine and container images + <_message>Authentication is required to manage local virtual machine and container images. + + auth_admin + auth_admin + auth_admin_keep + + + + diff --git a/src/machine/src/org.freedesktop.machine1.service b/src/machine/src/org.freedesktop.machine1.service new file mode 100644 index 0000000000..d3dc99852b --- /dev/null +++ b/src/machine/src/org.freedesktop.machine1.service @@ -0,0 +1,12 @@ +# 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. + +[D-BUS Service] +Name=org.freedesktop.machine1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.machine1.service diff --git a/src/machine/src/test-machine-tables.c b/src/machine/src/test-machine-tables.c new file mode 100644 index 0000000000..f851a4d37d --- /dev/null +++ b/src/machine/src/test-machine-tables.c @@ -0,0 +1,29 @@ +/*** + This file is part of systemd + + Copyright 2013 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 . +***/ + +#include "machine.h" +#include "test-tables.h" + +int main(int argc, char **argv) { + test_table(machine_class, MACHINE_CLASS); + test_table(machine_state, MACHINE_STATE); + test_table(kill_who, KILL_WHO); + + return EXIT_SUCCESS; +} diff --git a/src/machine/test-machine-tables.c b/src/machine/test-machine-tables.c deleted file mode 100644 index f851a4d37d..0000000000 --- a/src/machine/test-machine-tables.c +++ /dev/null @@ -1,29 +0,0 @@ -/*** - This file is part of systemd - - Copyright 2013 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 . -***/ - -#include "machine.h" -#include "test-tables.h" - -int main(int argc, char **argv) { - test_table(machine_class, MACHINE_CLASS); - test_table(machine_state, MACHINE_STATE); - test_table(kill_who, KILL_WHO); - - return EXIT_SUCCESS; -} diff --git a/src/modules-load/Makefile b/src/modules-load/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/modules-load/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/modules-load/Makefile b/src/modules-load/Makefile new file mode 100644 index 0000000000..f3860d89af --- /dev/null +++ b/src/modules-load/Makefile @@ -0,0 +1,60 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_KMOD),) +systemd_modules_load_SOURCES = \ + src/modules-load/modules-load.c + +systemd_modules_load_CFLAGS = \ + $(AM_CFLAGS) \ + $(KMOD_CFLAGS) + +systemd_modules_load_LDADD = \ + libshared.la \ + $(KMOD_LIBS) + +rootlibexec_PROGRAMS += \ + systemd-modules-load + +nodist_systemunit_DATA += \ + units/systemd-modules-load.service + +SYSINIT_TARGET_WANTS += \ + systemd-modules-load.service + +ifneq ($(ENABLE_TMPFILES),) +nodist_systemunit_DATA += \ + units/kmod-static-nodes.service + +SYSINIT_TARGET_WANTS += \ + kmod-static-nodes.service +endif +endif + +EXTRA_DIST += \ + units/systemd-modules-load.service.in \ + units/kmod-static-nodes.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/network/Makefile b/src/network/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/network/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/network/Makefile b/src/network/Makefile new file mode 100644 index 0000000000..3baa0d22b4 --- /dev/null +++ b/src/network/Makefile @@ -0,0 +1,200 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_NETWORKD),) +rootlibexec_PROGRAMS += \ + systemd-networkd + +systemd_networkd_SOURCES = \ + src/network/networkd.c + +systemd_networkd_LDADD = \ + libnetworkd-core.la + +ifneq ($(HAVE_LIBIPTC),) +systemd_networkd_LDADD += \ + libfirewall.la +endif + +noinst_LTLIBRARIES += \ + libnetworkd-core.la + +libnetworkd_core_la_CFLAGS = \ + $(AM_CFLAGS) + +libnetworkd_core_la_SOURCES = \ + src/libsystemd-network/network-internal.h \ + src/network/networkd.h \ + src/network/networkd-link.h \ + src/network/networkd-link.c \ + src/network/networkd-netdev.h \ + src/network/networkd-netdev.c \ + src/network/networkd-netdev-tunnel.h \ + src/network/networkd-netdev-tunnel.c \ + src/network/networkd-netdev-veth.h \ + src/network/networkd-netdev-veth.c \ + src/network/networkd-netdev-vxlan.h \ + src/network/networkd-netdev-vxlan.c \ + src/network/networkd-netdev-vlan.h \ + src/network/networkd-netdev-vlan.c \ + src/network/networkd-netdev-macvlan.h \ + src/network/networkd-netdev-macvlan.c \ + src/network/networkd-netdev-ipvlan.h \ + src/network/networkd-netdev-ipvlan.c \ + src/network/networkd-netdev-dummy.h \ + src/network/networkd-netdev-dummy.c \ + src/network/networkd-netdev-tuntap.h \ + src/network/networkd-netdev-tuntap.c \ + src/network/networkd-netdev-bond.h \ + src/network/networkd-netdev-bond.c \ + src/network/networkd-netdev-bridge.h \ + src/network/networkd-netdev-bridge.c \ + src/network/networkd-link-bus.c \ + src/network/networkd-ipv4ll.c \ + src/network/networkd-dhcp4.c \ + src/network/networkd-dhcp6.c \ + src/network/networkd-ndisc.c \ + src/network/networkd-network.h \ + src/network/networkd-network.c \ + src/network/networkd-network-bus.c \ + src/network/networkd-address.h \ + src/network/networkd-address.c \ + src/network/networkd-route.h \ + src/network/networkd-route.c \ + src/network/networkd-manager.c \ + src/network/networkd-manager-bus.c \ + src/network/networkd-fdb.h \ + src/network/networkd-fdb.c \ + src/network/networkd-address-pool.h \ + src/network/networkd-address-pool.c \ + src/network/networkd-util.h \ + src/network/networkd-util.c + +nodist_libnetworkd_core_la_SOURCES = \ + src/network/networkd-network-gperf.c \ + src/network/networkd-netdev-gperf.c + +libnetworkd_core_la_LIBADD = \ + libsystemd-network.la \ + libshared.la + +rootlibexec_PROGRAMS += \ + systemd-networkd-wait-online + +systemd_networkd_wait_online_CFLAGS = \ + $(AM_CFLAGS) + +systemd_networkd_wait_online_SOURCES = \ + src/libsystemd-network/network-internal.h \ + src/network/networkd-wait-online.h \ + src/network/networkd-wait-online-link.h \ + src/network/networkd-wait-online.c \ + src/network/networkd-wait-online-manager.c \ + src/network/networkd-wait-online-link.c + +systemd_networkd_wait_online_LDADD = \ + libsystemd-network.la \ + libshared.la + +rootbin_PROGRAMS += \ + networkctl + +networkctl_SOURCES = \ + src/network/networkctl.c + +networkctl_LDADD = \ + libshared.la \ + libsystemd-network.la + +dist_bashcompletion_data += \ + shell-completion/bash/networkctl + +test_network_SOURCES = \ + src/network/test-network.c + +test_network_LDADD = \ + libnetworkd-core.la + +ifneq ($(HAVE_LIBIPTC),) +test_network_LDADD += \ + libfirewall.la +endif + +test_network_tables_SOURCES = \ + src/network/test-network-tables.c \ + src/shared/test-tables.h + +test_network_tables_LDADD = \ + libnetworkd-core.la \ + libudev-core.la + +ifneq ($(HAVE_LIBIPTC),) +test_network_tables_LDADD += \ + libfirewall.la +endif + +tests += \ + test-network \ + test-network-tables + +dist_systemunit_DATA += \ + units/systemd-networkd.socket + +nodist_systemunit_DATA += \ + units/systemd-networkd.service \ + units/systemd-networkd-wait-online.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.network1.busname + +dist_dbussystemservice_DATA += \ + src/network/org.freedesktop.network1.service + +dist_dbuspolicy_DATA += \ + src/network/org.freedesktop.network1.conf + +GENERAL_ALIASES += \ + $(systemunitdir)/systemd-networkd.socket $(pkgsysconfdir)/system/sockets.target.wants/systemd-networkd.socket \ + $(systemunitdir)/systemd-networkd.service $(pkgsysconfdir)/system/multi-user.target.wants/systemd-networkd.service \ + $(systemunitdir)/systemd-networkd-wait-online.service $(pkgsysconfdir)/system/network-online.target.wants/systemd-networkd-wait-online.service + +SYSTEM_UNIT_ALIASES += \ + systemd-networkd.service dbus-org.freedesktop.network1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.network1.busname + +endif + +gperf_gperf_sources += \ + src/network/networkd-network-gperf.gperf \ + src/network/networkd-netdev-gperf.gperf + +EXTRA_DIST += \ + units/systemd-networkd.service.m4.in \ + units/systemd-networkd-wait-online.service.in \ + test/networkd-test.py + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/network/networkctl.c b/src/network/networkctl.c index 60724fce80..6e436d2043 100644 --- a/src/network/networkctl.c +++ b/src/network/networkctl.c @@ -21,10 +21,10 @@ #include #include -#include "sd-device.h" -#include "sd-hwdb.h" -#include "sd-netlink.h" -#include "sd-network.h" +#include +#include +#include +#include #include "alloc-util.h" #include "arphrd-list.h" diff --git a/src/network/networkd-dhcp6.c b/src/network/networkd-dhcp6.c index 5f7a005c36..bf8e826368 100644 --- a/src/network/networkd-dhcp6.c +++ b/src/network/networkd-dhcp6.c @@ -20,7 +20,7 @@ #include #include -#include "sd-dhcp6-client.h" +#include #include "network-internal.h" #include "networkd-link.h" diff --git a/src/network/networkd-link.h b/src/network/networkd-link.h index 0e6a7b6f21..30f01faf2a 100644 --- a/src/network/networkd-link.h +++ b/src/network/networkd-link.h @@ -21,12 +21,12 @@ #include -#include "sd-dhcp-client.h" -#include "sd-dhcp-server.h" -#include "sd-dhcp6-client.h" -#include "sd-ipv4ll.h" -#include "sd-lldp.h" -#include "sd-ndisc.h" +#include +#include +#include +#include +#include +#include typedef struct Link Link; diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c index b527191a5a..c73d313c85 100644 --- a/src/network/networkd-manager.c +++ b/src/network/networkd-manager.c @@ -20,8 +20,8 @@ #include #include -#include "sd-daemon.h" -#include "sd-netlink.h" +#include +#include #include "alloc-util.h" #include "bus-util.h" diff --git a/src/network/networkd-ndisc.c b/src/network/networkd-ndisc.c index f2287be20a..ae647b0293 100644 --- a/src/network/networkd-ndisc.c +++ b/src/network/networkd-ndisc.c @@ -21,7 +21,7 @@ #include #include -#include "sd-ndisc.h" +#include #include "networkd-link.h" diff --git a/src/network/networkd-netdev-bond.c b/src/network/networkd-netdev-bond.c index 106f15fabc..e04e78f870 100644 --- a/src/network/networkd-netdev-bond.c +++ b/src/network/networkd-netdev-bond.c @@ -21,7 +21,7 @@ #include #include -#include "sd-netlink.h" +#include #include "alloc-util.h" #include "conf-parser.h" diff --git a/src/network/networkd-netdev-tunnel.c b/src/network/networkd-netdev-tunnel.c index 46ff2974f4..408ceaf55e 100644 --- a/src/network/networkd-netdev-tunnel.c +++ b/src/network/networkd-netdev-tunnel.c @@ -23,7 +23,7 @@ #include #include -#include "sd-netlink.h" +#include #include "conf-parser.h" #include "missing.h" diff --git a/src/network/networkd-netdev-veth.c b/src/network/networkd-netdev-veth.c index b122a06c25..185b441c5a 100644 --- a/src/network/networkd-netdev-veth.c +++ b/src/network/networkd-netdev-veth.c @@ -20,7 +20,7 @@ #include #include -#include "sd-netlink.h" +#include #include "networkd-netdev-veth.h" diff --git a/src/network/networkd-netdev-vxlan.c b/src/network/networkd-netdev-vxlan.c index eb9a2c06b3..223d60f4db 100644 --- a/src/network/networkd-netdev-vxlan.c +++ b/src/network/networkd-netdev-vxlan.c @@ -19,7 +19,7 @@ #include -#include "sd-netlink.h" +#include #include "conf-parser.h" #include "alloc-util.h" diff --git a/src/network/networkd-wait-online-link.c b/src/network/networkd-wait-online-link.c index 5727422e3d..971545296f 100644 --- a/src/network/networkd-wait-online-link.c +++ b/src/network/networkd-wait-online-link.c @@ -18,7 +18,7 @@ along with systemd; If not, see . ***/ -#include "sd-network.h" +#include #include "alloc-util.h" #include "networkd-wait-online-link.h" diff --git a/src/network/networkd-wait-online.c b/src/network/networkd-wait-online.c index 3220c4b7ef..a08272463e 100644 --- a/src/network/networkd-wait-online.c +++ b/src/network/networkd-wait-online.c @@ -20,7 +20,7 @@ #include -#include "sd-daemon.h" +#include #include "networkd-wait-online.h" #include "signal-util.h" diff --git a/src/network/networkd-wait-online.h b/src/network/networkd-wait-online.h index 421c2bdf44..f1ea4a6494 100644 --- a/src/network/networkd-wait-online.h +++ b/src/network/networkd-wait-online.h @@ -19,9 +19,9 @@ #pragma once -#include "sd-event.h" -#include "sd-netlink.h" -#include "sd-network.h" +#include +#include +#include #include "hashmap.h" diff --git a/src/network/networkd.c b/src/network/networkd.c index 3a2615e6fd..6cf396e478 100644 --- a/src/network/networkd.c +++ b/src/network/networkd.c @@ -17,7 +17,7 @@ along with systemd; If not, see . ***/ -#include "sd-daemon.h" +#include #include "capability-util.h" #include "networkd.h" diff --git a/src/network/networkd.h b/src/network/networkd.h index 7ee922621a..efe70970c3 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -21,9 +21,9 @@ #include -#include "sd-bus.h" -#include "sd-event.h" -#include "sd-netlink.h" +#include +#include +#include #include "hashmap.h" #include "list.h" diff --git a/src/notify/Makefile b/src/notify/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/notify/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/notify/notify.c b/src/notify/notify.c deleted file mode 100644 index 49f97c61d9..0000000000 --- a/src/notify/notify.c +++ /dev/null @@ -1,203 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "env-util.h" -#include "formats-util.h" -#include "log.h" -#include "parse-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -static bool arg_ready = false; -static pid_t arg_pid = 0; -static const char *arg_status = NULL; -static bool arg_booted = false; - -static void help(void) { - printf("%s [OPTIONS...] [VARIABLE=VALUE...]\n\n" - "Notify the init system about service status updates.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --ready Inform the init system about service start-up completion\n" - " --pid[=PID] Set main pid of daemon\n" - " --status=TEXT Set status text\n" - " --booted Check if the system was booted up with systemd\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_READY = 0x100, - ARG_VERSION, - ARG_PID, - ARG_STATUS, - ARG_BOOTED, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "ready", no_argument, NULL, ARG_READY }, - { "pid", optional_argument, NULL, ARG_PID }, - { "status", required_argument, NULL, ARG_STATUS }, - { "booted", no_argument, NULL, ARG_BOOTED }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_READY: - arg_ready = true; - break; - - case ARG_PID: - - if (optarg) { - if (parse_pid(optarg, &arg_pid) < 0) { - log_error("Failed to parse PID %s.", optarg); - return -EINVAL; - } - } else - arg_pid = getppid(); - - break; - - case ARG_STATUS: - arg_status = optarg; - break; - - case ARG_BOOTED: - arg_booted = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - } - - if (optind >= argc && - !arg_ready && - !arg_status && - !arg_pid && - !arg_booted) { - help(); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char* argv[]) { - _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL; - _cleanup_strv_free_ char **final_env = NULL; - char* our_env[4]; - unsigned i = 0; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (arg_booted) - return sd_booted() <= 0; - - if (arg_ready) - our_env[i++] = (char*) "READY=1"; - - if (arg_status) { - status = strappend("STATUS=", arg_status); - if (!status) { - r = log_oom(); - goto finish; - } - - our_env[i++] = status; - } - - if (arg_pid > 0) { - if (asprintf(&cpid, "MAINPID="PID_FMT, arg_pid) < 0) { - r = log_oom(); - goto finish; - } - - our_env[i++] = cpid; - } - - our_env[i++] = NULL; - - final_env = strv_env_merge(2, our_env, argv + optind); - if (!final_env) { - r = log_oom(); - goto finish; - } - - if (strv_length(final_env) <= 0) { - r = 0; - goto finish; - } - - n = strv_join(final_env, "\n"); - if (!n) { - r = log_oom(); - goto finish; - } - - r = sd_pid_notify(arg_pid ? arg_pid : getppid(), false, n); - if (r < 0) { - log_error_errno(r, "Failed to notify init system: %m"); - goto finish; - } else if (r == 0) { - log_error("No status data could be sent: $NOTIFY_SOCKET was not set"); - r = -EOPNOTSUPP; - } - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/nspawn/.gitignore b/src/nspawn/.gitignore deleted file mode 100644 index 85c81fff24..0000000000 --- a/src/nspawn/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/nspawn-gperf.c diff --git a/src/nspawn/Makefile b/src/nspawn/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/nspawn/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/nspawn/nspawn-cgroup.c b/src/nspawn/nspawn-cgroup.c deleted file mode 100644 index 1db5ba7116..0000000000 --- a/src/nspawn/nspawn-cgroup.c +++ /dev/null @@ -1,163 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include - -#include "alloc-util.h" -#include "cgroup-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "mkdir.h" -#include "nspawn-cgroup.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -int chown_cgroup(pid_t pid, uid_t uid_shift) { - _cleanup_free_ char *path = NULL, *fs = NULL; - _cleanup_close_ int fd = -1; - const char *fn; - int r; - - r = cg_pid_get_path(NULL, pid, &path); - if (r < 0) - return log_error_errno(r, "Failed to get container cgroup path: %m"); - - r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs); - if (r < 0) - return log_error_errno(r, "Failed to get file system path for container cgroup: %m"); - - fd = open(fs, O_RDONLY|O_CLOEXEC|O_DIRECTORY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", fs); - - FOREACH_STRING(fn, - ".", - "tasks", - "notify_on_release", - "cgroup.procs", - "cgroup.events", - "cgroup.clone_children", - "cgroup.controllers", - "cgroup.subtree_control", - "cgroup.populated") - if (fchownat(fd, fn, uid_shift, uid_shift, 0) < 0) - log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, - "Failed to chown() cgroup file %s, ignoring: %m", fn); - - return 0; -} - -int sync_cgroup(pid_t pid, bool unified_requested) { - _cleanup_free_ char *cgroup = NULL; - char tree[] = "/tmp/unifiedXXXXXX", pid_string[DECIMAL_STR_MAX(pid) + 1]; - bool undo_mount = false; - const char *fn; - int unified, r; - - unified = cg_unified(); - if (unified < 0) - return log_error_errno(unified, "Failed to determine whether the unified hierachy is used: %m"); - - if ((unified > 0) == unified_requested) - return 0; - - /* When the host uses the legacy cgroup setup, but the - * container shall use the unified hierarchy, let's make sure - * we copy the path from the name=systemd hierarchy into the - * unified hierarchy. Similar for the reverse situation. */ - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup); - if (r < 0) - return log_error_errno(r, "Failed to get control group of " PID_FMT ": %m", pid); - - /* In order to access the unified hierarchy we need to mount it */ - if (!mkdtemp(tree)) - return log_error_errno(errno, "Failed to generate temporary mount point for unified hierarchy: %m"); - - if (unified) - r = mount("cgroup", tree, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "none,name=systemd,xattr"); - else - r = mount("cgroup", tree, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "__DEVEL__sane_behavior"); - if (r < 0) { - r = log_error_errno(errno, "Failed to mount unified hierarchy: %m"); - goto finish; - } - - undo_mount = true; - - fn = strjoina(tree, cgroup, "/cgroup.procs"); - (void) mkdir_parents(fn, 0755); - - sprintf(pid_string, PID_FMT, pid); - r = write_string_file(fn, pid_string, 0); - if (r < 0) - log_error_errno(r, "Failed to move process: %m"); - -finish: - if (undo_mount) - (void) umount(tree); - - (void) rmdir(tree); - return r; -} - -int create_subcgroup(pid_t pid, bool unified_requested) { - _cleanup_free_ char *cgroup = NULL; - const char *child; - int unified, r; - CGroupMask supported; - - /* In the unified hierarchy inner nodes may only 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 - * the container into two separate subcgroups. */ - - if (!unified_requested) - return 0; - - unified = cg_unified(); - if (unified < 0) - return log_error_errno(unified, "Failed to determine whether the unified hierachy is used: %m"); - if (unified == 0) - return 0; - - r = cg_mask_supported(&supported); - if (r < 0) - return log_error_errno(r, "Failed to determine supported controllers: %m"); - - r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup); - if (r < 0) - return log_error_errno(r, "Failed to get our control group: %m"); - - child = strjoina(cgroup, "/payload"); - r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, child, pid); - if (r < 0) - return log_error_errno(r, "Failed to create %s subcgroup: %m", child); - - child = strjoina(cgroup, "/supervisor"); - r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, child, 0); - if (r < 0) - return log_error_errno(r, "Failed to create %s subcgroup: %m", child); - - /* Try to enable as many controllers as possible for the new payload. */ - (void) cg_enable_everywhere(supported, supported, cgroup); - return 0; -} diff --git a/src/nspawn/nspawn-cgroup.h b/src/nspawn/nspawn-cgroup.h deleted file mode 100644 index 1ff35a299a..0000000000 --- a/src/nspawn/nspawn-cgroup.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include - -int chown_cgroup(pid_t pid, uid_t uid_shift); -int sync_cgroup(pid_t pid, bool unified_requested); -int create_subcgroup(pid_t pid, bool unified_requested); diff --git a/src/nspawn/nspawn-expose-ports.c b/src/nspawn/nspawn-expose-ports.c deleted file mode 100644 index 86124b8779..0000000000 --- a/src/nspawn/nspawn-expose-ports.c +++ /dev/null @@ -1,245 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include "sd-netlink.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "firewall-util.h" -#include "in-addr-util.h" -#include "local-addresses.h" -#include "netlink-util.h" -#include "nspawn-expose-ports.h" -#include "parse-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "util.h" - -int expose_port_parse(ExposePort **l, const char *s) { - - const char *split, *e; - uint16_t container_port, host_port; - int protocol; - ExposePort *p; - int r; - - assert(l); - assert(s); - - if ((e = startswith(s, "tcp:"))) - protocol = IPPROTO_TCP; - else if ((e = startswith(s, "udp:"))) - protocol = IPPROTO_UDP; - else { - e = s; - protocol = IPPROTO_TCP; - } - - split = strchr(e, ':'); - if (split) { - char v[split - e + 1]; - - memcpy(v, e, split - e); - v[split - e] = 0; - - r = safe_atou16(v, &host_port); - if (r < 0 || host_port <= 0) - return -EINVAL; - - r = safe_atou16(split + 1, &container_port); - } else { - r = safe_atou16(e, &container_port); - host_port = container_port; - } - - if (r < 0 || container_port <= 0) - return -EINVAL; - - LIST_FOREACH(ports, p, *l) - if (p->protocol == protocol && p->host_port == host_port) - return -EEXIST; - - p = new(ExposePort, 1); - if (!p) - return -ENOMEM; - - p->protocol = protocol; - p->host_port = host_port; - p->container_port = container_port; - - LIST_PREPEND(ports, *l, p); - - return 0; -} - -void expose_port_free_all(ExposePort *p) { - - while (p) { - ExposePort *q = p; - LIST_REMOVE(ports, p, q); - free(q); - } -} - -int expose_port_flush(ExposePort* l, union in_addr_union *exposed) { - ExposePort *p; - int r, af = AF_INET; - - assert(exposed); - - if (!l) - return 0; - - if (in_addr_is_null(af, exposed)) - return 0; - - log_debug("Lost IP address."); - - LIST_FOREACH(ports, p, l) { - r = fw_add_local_dnat(false, - af, - p->protocol, - NULL, - NULL, 0, - NULL, 0, - p->host_port, - exposed, - p->container_port, - NULL); - if (r < 0) - log_warning_errno(r, "Failed to modify firewall: %m"); - } - - *exposed = IN_ADDR_NULL; - return 0; -} - -int expose_port_execute(sd_netlink *rtnl, ExposePort *l, union in_addr_union *exposed) { - _cleanup_free_ struct local_address *addresses = NULL; - _cleanup_free_ char *pretty = NULL; - union in_addr_union new_exposed; - ExposePort *p; - bool add; - int af = AF_INET, r; - - assert(exposed); - - /* Invoked each time an address is added or removed inside the - * container */ - - if (!l) - return 0; - - r = local_addresses(rtnl, 0, af, &addresses); - if (r < 0) - return log_error_errno(r, "Failed to enumerate local addresses: %m"); - - add = r > 0 && - addresses[0].family == af && - addresses[0].scope < RT_SCOPE_LINK; - - if (!add) - return expose_port_flush(l, exposed); - - new_exposed = addresses[0].address; - if (in_addr_equal(af, exposed, &new_exposed)) - return 0; - - in_addr_to_string(af, &new_exposed, &pretty); - log_debug("New container IP is %s.", strna(pretty)); - - LIST_FOREACH(ports, p, l) { - - r = fw_add_local_dnat(true, - af, - p->protocol, - NULL, - NULL, 0, - NULL, 0, - p->host_port, - &new_exposed, - p->container_port, - in_addr_is_null(af, exposed) ? NULL : exposed); - if (r < 0) - log_warning_errno(r, "Failed to modify firewall: %m"); - } - - *exposed = new_exposed; - return 0; -} - -int expose_port_send_rtnl(int send_fd) { - _cleanup_close_ int fd = -1; - int r; - - assert(send_fd >= 0); - - fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE); - if (fd < 0) - return log_error_errno(errno, "Failed to allocate container netlink: %m"); - - /* Store away the fd in the socket, so that it stays open as - * long as we run the child */ - r = send_one_fd(send_fd, fd, 0); - if (r < 0) - return log_error_errno(r, "Failed to send netlink fd: %m"); - - return 0; -} - -int expose_port_watch_rtnl( - sd_event *event, - int recv_fd, - sd_netlink_message_handler_t handler, - union in_addr_union *exposed, - sd_netlink **ret) { - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int fd, r; - - assert(event); - assert(recv_fd >= 0); - assert(ret); - - fd = receive_one_fd(recv_fd, 0); - if (fd < 0) - return log_error_errno(fd, "Failed to recv netlink fd: %m"); - - r = sd_netlink_open_fd(&rtnl, fd); - if (r < 0) { - safe_close(fd); - return log_error_errno(r, "Failed to create rtnl object: %m"); - } - - r = sd_netlink_add_match(rtnl, RTM_NEWADDR, handler, exposed); - if (r < 0) - return log_error_errno(r, "Failed to subscribe to RTM_NEWADDR messages: %m"); - - r = sd_netlink_add_match(rtnl, RTM_DELADDR, handler, exposed); - if (r < 0) - return log_error_errno(r, "Failed to subscribe to RTM_DELADDR messages: %m"); - - r = sd_netlink_attach_event(rtnl, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to add to even loop: %m"); - - *ret = rtnl; - rtnl = NULL; - - return 0; -} diff --git a/src/nspawn/nspawn-expose-ports.h b/src/nspawn/nspawn-expose-ports.h deleted file mode 100644 index 741ad9765c..0000000000 --- a/src/nspawn/nspawn-expose-ports.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include - -#include "sd-event.h" -#include "sd-netlink.h" - -#include "in-addr-util.h" -#include "list.h" - -typedef struct ExposePort { - int protocol; - uint16_t host_port; - uint16_t container_port; - LIST_FIELDS(struct ExposePort, ports); -} ExposePort; - -void expose_port_free_all(ExposePort *p); -int expose_port_parse(ExposePort **l, const char *s); - -int expose_port_watch_rtnl(sd_event *event, int recv_fd, sd_netlink_message_handler_t handler, union in_addr_union *exposed, sd_netlink **ret); -int expose_port_send_rtnl(int send_fd); - -int expose_port_execute(sd_netlink *rtnl, ExposePort *l, union in_addr_union *exposed); -int expose_port_flush(ExposePort* l, union in_addr_union *exposed); diff --git a/src/nspawn/nspawn-gperf.gperf b/src/nspawn/nspawn-gperf.gperf deleted file mode 100644 index 116655cdd2..0000000000 --- a/src/nspawn/nspawn-gperf.gperf +++ /dev/null @@ -1,41 +0,0 @@ -%{ -#include -#include "conf-parser.h" -#include "nspawn-settings.h" -#include "nspawn-expose-ports.h" -%} -struct ConfigPerfItem; -%null_strings -%language=ANSI-C -%define slot-name section_and_lvalue -%define hash-function-name nspawn_gperf_hash -%define lookup-function-name nspawn_gperf_lookup -%readonly-tables -%omit-struct-type -%struct-type -%includes -%% -Exec.Boot, config_parse_boot, 0, 0 -Exec.ProcessTwo, config_parse_pid2, 0, 0, -Exec.Parameters, config_parse_strv, 0, offsetof(Settings, parameters) -Exec.Environment, config_parse_strv, 0, offsetof(Settings, environment) -Exec.User, config_parse_string, 0, offsetof(Settings, user) -Exec.Capability, config_parse_capability, 0, offsetof(Settings, capability) -Exec.DropCapability, config_parse_capability, 0, offsetof(Settings, drop_capability) -Exec.KillSignal, config_parse_signal, 0, offsetof(Settings, kill_signal) -Exec.Personality, config_parse_personality, 0, offsetof(Settings, personality) -Exec.MachineID, config_parse_id128, 0, offsetof(Settings, machine_id) -Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory) -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 -Files.BindReadOnly, config_parse_bind, 1, 0 -Files.TemporaryFileSystem, config_parse_tmpfs, 0, 0 -Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network) -Network.Interface, config_parse_strv, 0, offsetof(Settings, network_interfaces) -Network.MACVLAN, config_parse_strv, 0, offsetof(Settings, network_macvlan) -Network.IPVLAN, config_parse_strv, 0, offsetof(Settings, network_ipvlan) -Network.VirtualEthernet, config_parse_tristate, 0, offsetof(Settings, network_veth) -Network.VirtualEthernetExtra, config_parse_veth_extra, 0, 0 -Network.Bridge, config_parse_string, 0, offsetof(Settings, network_bridge) -Network.Port, config_parse_expose_port, 0, 0 diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c deleted file mode 100644 index 70cca15278..0000000000 --- a/src/nspawn/nspawn-mount.c +++ /dev/null @@ -1,942 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include - -#include "alloc-util.h" -#include "cgroup-util.h" -#include "escape.h" -#include "fs-util.h" -#include "label.h" -#include "mkdir.h" -#include "mount-util.h" -#include "nspawn-mount.h" -#include "parse-util.h" -#include "path-util.h" -#include "rm-rf.h" -#include "set.h" -#include "stat-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t) { - CustomMount *c, *ret; - - assert(l); - assert(n); - assert(t >= 0); - assert(t < _CUSTOM_MOUNT_TYPE_MAX); - - c = realloc(*l, (*n + 1) * sizeof(CustomMount)); - if (!c) - return NULL; - - *l = c; - ret = *l + *n; - (*n)++; - - *ret = (CustomMount) { .type = t }; - - return ret; -} - -void custom_mount_free_all(CustomMount *l, unsigned n) { - unsigned i; - - for (i = 0; i < n; i++) { - CustomMount *m = l + i; - - free(m->source); - free(m->destination); - free(m->options); - - if (m->work_dir) { - (void) rm_rf(m->work_dir, REMOVE_ROOT|REMOVE_PHYSICAL); - free(m->work_dir); - } - - strv_free(m->lower); - } - - free(l); -} - -int custom_mount_compare(const void *a, const void *b) { - const CustomMount *x = a, *y = b; - int r; - - r = path_compare(x->destination, y->destination); - if (r != 0) - return r; - - if (x->type < y->type) - return -1; - if (x->type > y->type) - return 1; - - return 0; -} - -int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only) { - _cleanup_free_ char *source = NULL, *destination = NULL, *opts = NULL; - const char *p = s; - CustomMount *m; - int r; - - assert(l); - assert(n); - - r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination, NULL); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - if (r == 1) { - destination = strdup(source); - if (!destination) - return -ENOMEM; - } - - if (r == 2 && !isempty(p)) { - opts = strdup(p); - if (!opts) - return -ENOMEM; - } - - if (!path_is_absolute(source)) - return -EINVAL; - - if (!path_is_absolute(destination)) - return -EINVAL; - - m = custom_mount_add(l, n, CUSTOM_MOUNT_BIND); - if (!m) - return log_oom(); - - m->source = source; - m->destination = destination; - m->read_only = read_only; - m->options = opts; - - source = destination = opts = NULL; - return 0; -} - -int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s) { - _cleanup_free_ char *path = NULL, *opts = NULL; - const char *p = s; - CustomMount *m; - int r; - - assert(l); - assert(n); - assert(s); - - r = extract_first_word(&p, &path, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - if (isempty(p)) - opts = strdup("mode=0755"); - else - opts = strdup(p); - if (!opts) - return -ENOMEM; - - if (!path_is_absolute(path)) - return -EINVAL; - - m = custom_mount_add(l, n, CUSTOM_MOUNT_TMPFS); - if (!m) - return -ENOMEM; - - m->destination = path; - m->options = opts; - - path = opts = NULL; - return 0; -} - -static int tmpfs_patch_options( - const char *options, - bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context, - char **ret) { - - char *buf = NULL; - - if (userns && uid_shift != 0) { - assert(uid_shift != UID_INVALID); - - if (options) - (void) asprintf(&buf, "%s,uid=" UID_FMT ",gid=" UID_FMT, options, uid_shift, uid_shift); - else - (void) asprintf(&buf, "uid=" UID_FMT ",gid=" UID_FMT, uid_shift, uid_shift); - if (!buf) - return -ENOMEM; - - options = buf; - } - -#ifdef HAVE_SELINUX - if (selinux_apifs_context) { - char *t; - - if (options) - t = strjoin(options, ",context=\"", selinux_apifs_context, "\"", NULL); - else - t = strjoin("context=\"", selinux_apifs_context, "\"", NULL); - if (!t) { - free(buf); - return -ENOMEM; - } - - free(buf); - buf = t; - } -#endif - - *ret = buf; - return !!buf; -} - -int mount_sysfs(const char *dest) { - const char *full, *top, *x; - int r; - - top = prefix_roota(dest, "/sys"); - r = path_check_fstype(top, SYSFS_MAGIC); - if (r < 0) - return log_error_errno(r, "Failed to determine filesystem type of %s: %m", top); - /* /sys might already be mounted as sysfs by the outer child in the - * !netns case. In this case, it's all good. Don't touch it because we - * don't have the right to do so, see https://github.com/systemd/systemd/issues/1555. - */ - if (r > 0) - return 0; - - full = prefix_roota(top, "/full"); - - (void) mkdir(full, 0755); - - if (mount("sysfs", full, "sysfs", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) < 0) - return log_error_errno(errno, "Failed to mount sysfs to %s: %m", full); - - FOREACH_STRING(x, "block", "bus", "class", "dev", "devices", "kernel") { - _cleanup_free_ char *from = NULL, *to = NULL; - - from = prefix_root(full, x); - if (!from) - return log_oom(); - - to = prefix_root(top, x); - if (!to) - return log_oom(); - - (void) mkdir(to, 0755); - - if (mount(from, to, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to mount /sys/%s into place: %m", x); - - if (mount(NULL, to, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL) < 0) - return log_error_errno(errno, "Failed to mount /sys/%s read-only: %m", x); - } - - if (umount(full) < 0) - return log_error_errno(errno, "Failed to unmount %s: %m", full); - - if (rmdir(full) < 0) - return log_error_errno(errno, "Failed to remove %s: %m", full); - - x = prefix_roota(top, "/fs/kdbus"); - (void) mkdir(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); - - return 0; -} - -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) { - - typedef struct MountPoint { - const char *what; - const char *where; - const char *type; - const char *options; - unsigned long flags; - bool fatal; - bool in_userns; - bool use_netns; - } 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 }, -#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 */ -#endif - }; - - unsigned k; - int r; - - for (k = 0; k < ELEMENTSOF(mount_table); k++) { - _cleanup_free_ char *where = NULL, *options = NULL; - const char *o; - - if (in_userns != mount_table[k].in_userns) - continue; - - if (!use_netns && mount_table[k].use_netns) - continue; - - where = prefix_root(dest, mount_table[k].where); - if (!where) - return log_oom(); - - r = path_is_mount_point(where, AT_SYMLINK_FOLLOW); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "Failed to detect whether %s is a mount point: %m", where); - - /* Skip this entry if it is not a remount. */ - if (mount_table[k].what && r > 0) - continue; - - r = mkdir_p(where, 0755); - if (r < 0) { - 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); - 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); - if (r < 0) - return log_oom(); - if (r > 0) - o = options; - } - - if (mount(mount_table[k].what, - where, - mount_table[k].type, - mount_table[k].flags, - o) < 0) { - - if (mount_table[k].fatal) - return log_error_errno(errno, "mount(%s) failed: %m", where); - - log_warning_errno(errno, "mount(%s) failed, ignoring: %m", where); - } - } - - return 0; -} - -static int parse_mount_bind_options(const char *options, unsigned long *mount_flags, char **mount_opts) { - const char *p = options; - unsigned long flags = *mount_flags; - char *opts = NULL; - - assert(options); - - for (;;) { - _cleanup_free_ char *word = NULL; - int r = extract_first_word(&p, &word, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to extract mount option: %m"); - if (r == 0) - break; - - if (streq(word, "rbind")) - flags |= MS_REC; - else if (streq(word, "norbind")) - flags &= ~MS_REC; - else { - log_error("Invalid bind mount option: %s", word); - return -EINVAL; - } - } - - *mount_flags = flags; - /* in the future mount_opts will hold string options for mount(2) */ - *mount_opts = opts; - - return 0; -} - -static int mount_bind(const char *dest, CustomMount *m) { - struct stat source_st, dest_st; - const char *where; - unsigned long mount_flags = MS_BIND | MS_REC; - _cleanup_free_ char *mount_opts = NULL; - int r; - - assert(m); - - if (m->options) { - r = parse_mount_bind_options(m->options, &mount_flags, &mount_opts); - if (r < 0) - return r; - } - - if (stat(m->source, &source_st) < 0) - return log_error_errno(errno, "Failed to stat %s: %m", m->source); - - where = prefix_roota(dest, m->destination); - - if (stat(where, &dest_st) >= 0) { - if (S_ISDIR(source_st.st_mode) && !S_ISDIR(dest_st.st_mode)) { - log_error("Cannot bind mount directory %s on file %s.", m->source, where); - return -EINVAL; - } - - if (!S_ISDIR(source_st.st_mode) && S_ISDIR(dest_st.st_mode)) { - log_error("Cannot bind mount file %s on directory %s.", m->source, where); - return -EINVAL; - } - - } else if (errno == ENOENT) { - r = mkdir_parents_label(where, 0755); - if (r < 0) - return log_error_errno(r, "Failed to make parents of %s: %m", where); - } else { - return log_error_errno(errno, "Failed to stat %s: %m", where); - } - - /* Create the mount point. Any non-directory file can be - * mounted on any non-directory file (regular, fifo, socket, - * char, block). - */ - if (S_ISDIR(source_st.st_mode)) - r = mkdir_label(where, 0755); - else - r = touch(where); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Failed to create mount point %s: %m", where); - - if (mount(m->source, where, NULL, mount_flags, mount_opts) < 0) - return log_error_errno(errno, "mount(%s) failed: %m", where); - - if (m->read_only) { - r = bind_remount_recursive(where, true); - if (r < 0) - return log_error_errno(r, "Read-only bind mount failed: %m"); - } - - return 0; -} - -static int mount_tmpfs( - const char *dest, - CustomMount *m, - bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context) { - - const char *where, *options; - _cleanup_free_ char *buf = NULL; - int r; - - assert(dest); - assert(m); - - where = prefix_roota(dest, m->destination); - - r = mkdir_p_label(where, 0755); - 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); - if (r < 0) - return log_oom(); - options = r > 0 ? buf : m->options; - - if (mount("tmpfs", where, "tmpfs", MS_NODEV|MS_STRICTATIME, options) < 0) - return log_error_errno(errno, "tmpfs mount to %s failed: %m", where); - - return 0; -} - -static char *joined_and_escaped_lower_dirs(char * const *lower) { - _cleanup_strv_free_ char **sv = NULL; - - sv = strv_copy(lower); - if (!sv) - return NULL; - - strv_reverse(sv); - - if (!strv_shell_escape(sv, ",:")) - return NULL; - - return strv_join(sv, ":"); -} - -static int mount_overlay(const char *dest, CustomMount *m) { - _cleanup_free_ char *lower = NULL; - const char *where, *options; - int r; - - assert(dest); - assert(m); - - where = prefix_roota(dest, m->destination); - - r = mkdir_label(where, 0755); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Creating mount point for overlay %s failed: %m", where); - - (void) mkdir_p_label(m->source, 0755); - - lower = joined_and_escaped_lower_dirs(m->lower); - if (!lower) - return log_oom(); - - if (m->read_only) { - _cleanup_free_ char *escaped_source = NULL; - - escaped_source = shell_escape(m->source, ",:"); - if (!escaped_source) - return log_oom(); - - options = strjoina("lowerdir=", escaped_source, ":", lower); - } else { - _cleanup_free_ char *escaped_source = NULL, *escaped_work_dir = NULL; - - assert(m->work_dir); - (void) mkdir_label(m->work_dir, 0700); - - escaped_source = shell_escape(m->source, ",:"); - if (!escaped_source) - return log_oom(); - escaped_work_dir = shell_escape(m->work_dir, ",:"); - if (!escaped_work_dir) - return log_oom(); - - options = strjoina("lowerdir=", lower, ",upperdir=", escaped_source, ",workdir=", escaped_work_dir); - } - - if (mount("overlay", where, "overlay", m->read_only ? MS_RDONLY : 0, options) < 0) - return log_error_errno(errno, "overlay mount to %s failed: %m", where); - - return 0; -} - -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) { - - unsigned i; - int r; - - assert(dest); - - for (i = 0; i < n; i++) { - CustomMount *m = mounts + i; - - switch (m->type) { - - case CUSTOM_MOUNT_BIND: - r = mount_bind(dest, m); - break; - - case CUSTOM_MOUNT_TMPFS: - r = mount_tmpfs(dest, m, userns, uid_shift, uid_range, selinux_apifs_context); - break; - - case CUSTOM_MOUNT_OVERLAY: - r = mount_overlay(dest, m); - break; - - default: - assert_not_reached("Unknown custom mount type"); - } - - 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; - - to = strjoina(strempty(dest), "/sys/fs/cgroup/", hierarchy); - - r = path_is_mount_point(to, 0); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "Failed to determine if %s is mounted already: %m", to); - if (r > 0) - return 0; - - mkdir_p(to, 0755); - - /* The superblock mount options of the mount point need to be - * identical to the hosts', and hence writable... */ - if (mount("cgroup", to, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, controller) < 0) - return log_error_errno(errno, "Failed to mount to %s: %m", to); - - /* ... hence let's only make the bind mount read-only, not the - * superblock. */ - if (read_only) { - if (mount(NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) - return log_error_errno(errno, "Failed to remount %s read-only: %m", to); - } - return 1; -} - -static int mount_legacy_cgroups( - 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; - - cgroup_root = prefix_roota(dest, "/sys/fs/cgroup"); - - (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; - - r = tmpfs_patch_options("mode=755", userns, uid_shift, uid_range, 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 = cg_kernel_controllers(controllers); - if (r < 0) - return log_error_errno(r, "Failed to determine cgroup controllers: %m"); - - for (;;) { - _cleanup_free_ char *controller = NULL, *origin = NULL, *combined = NULL; - - controller = set_steal_first(controllers); - if (!controller) - break; - - origin = prefix_root("/sys/fs/cgroup/", controller); - if (!origin) - return log_oom(); - - r = readlink_malloc(origin, &combined); - if (r == -EINVAL) { - /* Not a symbolic link, but directly a single cgroup hierarchy */ - - r = mount_legacy_cgroup_hierarchy(dest, controller, controller, true); - if (r < 0) - return r; - - } else if (r < 0) - return log_error_errno(r, "Failed to read link %s: %m", origin); - else { - _cleanup_free_ char *target = NULL; - - target = prefix_root(dest, origin); - if (!target) - return log_oom(); - - /* A symbolic link, a combination of controllers in one hierarchy */ - - if (!filename_is_valid(combined)) { - log_warning("Ignoring invalid combined hierarchy %s.", combined); - continue; - } - - r = mount_legacy_cgroup_hierarchy(dest, combined, combined, true); - if (r < 0) - return r; - - r = symlink_idempotent(combined, target); - if (r == -EINVAL) { - log_error("Invalid existing symlink for combined hierarchy"); - return r; - } - if (r < 0) - return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m"); - } - } - -skip_controllers: - r = mount_legacy_cgroup_hierarchy(dest, "none,name=systemd,xattr", "systemd", false); - if (r < 0) - return r; - - 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; -} - -static int mount_unified_cgroups(const char *dest) { - const char *p; - int r; - - assert(dest); - - p = prefix_roota(dest, "/sys/fs/cgroup"); - - (void) mkdir_p(p, 0755); - - r = path_is_mount_point(p, AT_SYMLINK_FOLLOW); - if (r < 0) - return log_error_errno(r, "Failed to determine if %s is mounted already: %m", p); - if (r > 0) { - p = prefix_roota(dest, "/sys/fs/cgroup/cgroup.procs"); - if (access(p, F_OK) >= 0) - return 0; - if (errno != ENOENT) - return log_error_errno(errno, "Failed to determine if mount point %s contains the unified cgroup hierarchy: %m", p); - - log_error("%s is already mounted but not a unified cgroup hierarchy. Refusing.", p); - return -EINVAL; - } - - if (mount("cgroup", p, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "__DEVEL__sane_behavior") < 0) - return log_error_errno(errno, "Failed to mount unified cgroup hierarchy to %s: %m", p); - - return 0; -} - -int mount_cgroups( - const char *dest, - bool unified_requested, - bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context) { - - if (unified_requested) - return mount_unified_cgroups(dest); - else - return mount_legacy_cgroups(dest, userns, uid_shift, uid_range, selinux_apifs_context); -} - -int mount_systemd_cgroup_writable( - const char *dest, - bool unified_requested) { - - _cleanup_free_ char *own_cgroup_path = NULL; - const char *systemd_root, *systemd_own; - int r; - - assert(dest); - - r = cg_pid_get_path(NULL, 0, &own_cgroup_path); - if (r < 0) - return log_error_errno(r, "Failed to determine our own cgroup path: %m"); - - /* If we are living in the top-level, then there's nothing to do... */ - if (path_equal(own_cgroup_path, "/")) - return 0; - - if (unified_requested) { - systemd_own = strjoina(dest, "/sys/fs/cgroup", own_cgroup_path); - systemd_root = prefix_roota(dest, "/sys/fs/cgroup"); - } else { - systemd_own = strjoina(dest, "/sys/fs/cgroup/systemd", own_cgroup_path); - systemd_root = prefix_roota(dest, "/sys/fs/cgroup/systemd"); - } - - /* Make our own cgroup a (writable) bind mount */ - if (mount(systemd_own, systemd_own, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to turn %s into a bind mount: %m", own_cgroup_path); - - /* And then remount the systemd cgroup root read-only */ - if (mount(NULL, systemd_root, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) - return log_error_errno(errno, "Failed to mount cgroup root read-only: %m"); - - return 0; -} - -int setup_volatile_state( - const char *directory, - VolatileMode mode, - bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context) { - - _cleanup_free_ char *buf = NULL; - const char *p, *options; - int r; - - assert(directory); - - if (mode != VOLATILE_STATE) - return 0; - - /* --volatile=state means we simply overmount /var - with a tmpfs, and the rest read-only. */ - - r = bind_remount_recursive(directory, true); - if (r < 0) - return log_error_errno(r, "Failed to remount %s read-only: %m", directory); - - p = prefix_roota(directory, "/var"); - r = mkdir(p, 0755); - if (r < 0 && errno != EEXIST) - 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); - if (r < 0) - return log_oom(); - if (r > 0) - options = buf; - - if (mount("tmpfs", p, "tmpfs", MS_STRICTATIME, options) < 0) - return log_error_errno(errno, "Failed to mount tmpfs to /var: %m"); - - return 0; -} - -int setup_volatile( - const char *directory, - VolatileMode mode, - bool userns, uid_t uid_shift, uid_t uid_range, - const char *selinux_apifs_context) { - - bool tmpfs_mounted = false, bind_mounted = false; - char template[] = "/tmp/nspawn-volatile-XXXXXX"; - _cleanup_free_ char *buf = NULL; - const char *f, *t, *options; - int r; - - assert(directory); - - if (mode != VOLATILE_YES) - return 0; - - /* --volatile=yes means we mount a tmpfs to the root dir, and - the original /usr to use inside it, and that read-only. */ - - if (!mkdtemp(template)) - 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); - if (r < 0) - return log_oom(); - if (r > 0) - options = buf; - - if (mount("tmpfs", template, "tmpfs", MS_STRICTATIME, options) < 0) { - r = log_error_errno(errno, "Failed to mount tmpfs for root directory: %m"); - goto fail; - } - - tmpfs_mounted = true; - - f = prefix_roota(directory, "/usr"); - t = prefix_roota(template, "/usr"); - - r = mkdir(t, 0755); - if (r < 0 && errno != EEXIST) { - r = log_error_errno(errno, "Failed to create %s: %m", t); - goto fail; - } - - if (mount(f, t, NULL, MS_BIND|MS_REC, NULL) < 0) { - r = log_error_errno(errno, "Failed to create /usr bind mount: %m"); - goto fail; - } - - bind_mounted = true; - - r = bind_remount_recursive(t, true); - if (r < 0) { - log_error_errno(r, "Failed to remount %s read-only: %m", t); - goto fail; - } - - if (mount(template, directory, NULL, MS_MOVE, NULL) < 0) { - r = log_error_errno(errno, "Failed to move root mount: %m"); - goto fail; - } - - (void) rmdir(template); - - return 0; - -fail: - if (bind_mounted) - (void) umount(t); - - if (tmpfs_mounted) - (void) umount(template); - (void) rmdir(template); - return r; -} - -VolatileMode volatile_mode_from_string(const char *s) { - int b; - - if (isempty(s)) - return _VOLATILE_MODE_INVALID; - - b = parse_boolean(s); - if (b > 0) - return VOLATILE_YES; - if (b == 0) - return VOLATILE_NO; - - if (streq(s, "state")) - return VOLATILE_STATE; - - return _VOLATILE_MODE_INVALID; -} diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h deleted file mode 100644 index 0daf145412..0000000000 --- a/src/nspawn/nspawn-mount.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include - -typedef enum VolatileMode { - VOLATILE_NO, - VOLATILE_YES, - VOLATILE_STATE, - _VOLATILE_MODE_MAX, - _VOLATILE_MODE_INVALID = -1 -} VolatileMode; - -typedef enum CustomMountType { - CUSTOM_MOUNT_BIND, - CUSTOM_MOUNT_TMPFS, - CUSTOM_MOUNT_OVERLAY, - _CUSTOM_MOUNT_TYPE_MAX, - _CUSTOM_MOUNT_TYPE_INVALID = -1 -} CustomMountType; - -typedef struct CustomMount { - CustomMountType type; - bool read_only; - char *source; /* for overlayfs this is the upper directory */ - char *destination; - char *options; - char *work_dir; - char **lower; -} CustomMount; - -CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t); - -void custom_mount_free_all(CustomMount *l, unsigned n); -int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only); -int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s); - -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_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); - -int setup_volatile(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); -int setup_volatile_state(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); - -VolatileMode volatile_mode_from_string(const char *s); diff --git a/src/nspawn/nspawn-network.c b/src/nspawn/nspawn-network.c deleted file mode 100644 index fcb1efaa74..0000000000 --- a/src/nspawn/nspawn-network.c +++ /dev/null @@ -1,540 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include - -#include "libudev.h" -#include "sd-id128.h" -#include "sd-netlink.h" - -#include "alloc-util.h" -#include "ether-addr-util.h" -#include "netlink-util.h" -#include "nspawn-network.h" -#include "siphash24.h" -#include "string-util.h" -#include "udev-util.h" -#include "util.h" - -#define HOST_HASH_KEY SD_ID128_MAKE(1a,37,6f,c7,46,ec,45,0b,ad,a3,d5,31,06,60,5d,b1) -#define CONTAINER_HASH_KEY SD_ID128_MAKE(c3,c4,f9,19,b5,57,b2,1c,e6,cf,14,27,03,9c,ee,a2) -#define VETH_EXTRA_HOST_HASH_KEY SD_ID128_MAKE(48,c7,f6,b7,ea,9d,4c,9e,b7,28,d4,de,91,d5,bf,66) -#define VETH_EXTRA_CONTAINER_HASH_KEY SD_ID128_MAKE(af,50,17,61,ce,f9,4d,35,84,0d,2b,20,54,be,ce,59) -#define MACVLAN_HASH_KEY SD_ID128_MAKE(00,13,6d,bc,66,83,44,81,bb,0c,f9,51,1f,24,a6,6f) - -static int generate_mac( - const char *machine_name, - struct ether_addr *mac, - sd_id128_t hash_key, - uint64_t idx) { - - uint64_t result; - size_t l, sz; - uint8_t *v, *i; - int r; - - l = strlen(machine_name); - sz = sizeof(sd_id128_t) + l; - if (idx > 0) - sz += sizeof(idx); - - v = alloca(sz); - - /* fetch some persistent data unique to the host */ - r = sd_id128_get_machine((sd_id128_t*) v); - if (r < 0) - return r; - - /* combine with some data unique (on this host) to this - * container instance */ - i = mempcpy(v + sizeof(sd_id128_t), machine_name, l); - if (idx > 0) { - idx = htole64(idx); - memcpy(i, &idx, sizeof(idx)); - } - - /* Let's hash the host machine ID plus the container name. We - * use a fixed, but originally randomly created hash key here. */ - result = htole64(siphash24(v, sz, hash_key.bytes)); - - assert_cc(ETH_ALEN <= sizeof(result)); - memcpy(mac->ether_addr_octet, &result, ETH_ALEN); - - /* see eth_random_addr in the kernel */ - mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ - mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ - - return 0; -} - -static int add_veth( - sd_netlink *rtnl, - pid_t pid, - const char *ifname_host, - const struct ether_addr *mac_host, - const char *ifname_container, - const struct ether_addr *mac_container) { - - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int r; - - assert(rtnl); - assert(ifname_host); - assert(mac_host); - assert(ifname_container); - assert(mac_container); - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, ifname_host); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name: %m"); - - r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, mac_host); - if (r < 0) - return log_error_errno(r, "Failed to add netlink MAC address: %m"); - - r = sd_netlink_message_open_container(m, IFLA_LINKINFO); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "veth"); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_open_container(m, VETH_INFO_PEER); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, ifname_container); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name: %m"); - - r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, mac_container); - if (r < 0) - return log_error_errno(r, "Failed to add netlink MAC address: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); - if (r < 0) - return log_error_errno(r, "Failed to add netlink namespace field: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add new veth interfaces (%s:%s): %m", ifname_host, ifname_container); - - return 0; -} - -int setup_veth(const char *machine_name, - pid_t pid, - char iface_name[IFNAMSIZ], - bool bridge) { - - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - struct ether_addr mac_host, mac_container; - int r, i; - - assert(machine_name); - assert(pid > 0); - assert(iface_name); - - /* Use two different interface name prefixes depending whether - * we are in bridge mode or not. */ - snprintf(iface_name, IFNAMSIZ - 1, "%s-%s", - bridge ? "vb" : "ve", machine_name); - - r = generate_mac(machine_name, &mac_container, CONTAINER_HASH_KEY, 0); - if (r < 0) - return log_error_errno(r, "Failed to generate predictable MAC address for container side: %m"); - - r = generate_mac(machine_name, &mac_host, HOST_HASH_KEY, 0); - if (r < 0) - return log_error_errno(r, "Failed to generate predictable MAC address for host side: %m"); - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - r = add_veth(rtnl, pid, iface_name, &mac_host, "host0", &mac_container); - if (r < 0) - return r; - - i = (int) if_nametoindex(iface_name); - if (i <= 0) - return log_error_errno(errno, "Failed to resolve interface %s: %m", iface_name); - - return i; -} - -int setup_veth_extra( - const char *machine_name, - pid_t pid, - char **pairs) { - - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - uint64_t idx = 0; - char **a, **b; - int r; - - assert(machine_name); - assert(pid > 0); - - if (strv_isempty(pairs)) - return 0; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - STRV_FOREACH_PAIR(a, b, pairs) { - struct ether_addr mac_host, mac_container; - - r = generate_mac(machine_name, &mac_container, VETH_EXTRA_CONTAINER_HASH_KEY, idx); - if (r < 0) - return log_error_errno(r, "Failed to generate predictable MAC address for container side of extra veth link: %m"); - - r = generate_mac(machine_name, &mac_host, VETH_EXTRA_HOST_HASH_KEY, idx); - if (r < 0) - return log_error_errno(r, "Failed to generate predictable MAC address for container side of extra veth link: %m"); - - r = add_veth(rtnl, pid, *a, &mac_host, *b, &mac_container); - if (r < 0) - return r; - - idx ++; - } - - return 0; -} - -int setup_bridge(const char *veth_name, const char *bridge_name) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - int r, bridge_ifi; - - assert(veth_name); - assert(bridge_name); - - bridge_ifi = (int) if_nametoindex(bridge_name); - if (bridge_ifi <= 0) - return log_error_errno(errno, "Failed to resolve interface %s: %m", bridge_name); - - 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, &m, RTM_SETLINK, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_rtnl_message_link_set_flags(m, IFF_UP, IFF_UP); - if (r < 0) - return log_error_errno(r, "Failed to set IFF_UP flag: %m"); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, veth_name); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name field: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_MASTER, bridge_ifi); - if (r < 0) - return log_error_errno(r, "Failed to add netlink master field: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add veth interface to bridge: %m"); - - return bridge_ifi; -} - -static int parse_interface(struct udev *udev, const char *name) { - _cleanup_udev_device_unref_ struct udev_device *d = NULL; - char ifi_str[2 + DECIMAL_STR_MAX(int)]; - int ifi; - - ifi = (int) if_nametoindex(name); - if (ifi <= 0) - return log_error_errno(errno, "Failed to resolve interface %s: %m", name); - - sprintf(ifi_str, "n%i", ifi); - d = udev_device_new_from_device_id(udev, ifi_str); - if (!d) - return log_error_errno(errno, "Failed to get udev device for interface %s: %m", name); - - if (udev_device_get_is_initialized(d) <= 0) { - log_error("Network interface %s is not initialized yet.", name); - return -EBUSY; - } - - return ifi; -} - -int move_network_interfaces(pid_t pid, char **ifaces) { - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - char **i; - int r; - - if (strv_isempty(ifaces)) - return 0; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - udev = udev_new(); - if (!udev) { - log_error("Failed to connect to udev."); - return -ENOMEM; - } - - STRV_FOREACH(i, ifaces) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - int ifi; - - ifi = parse_interface(udev, *i); - if (ifi < 0) - return ifi; - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, ifi); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); - if (r < 0) - return log_error_errno(r, "Failed to append namespace PID to netlink message: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to move interface %s to namespace: %m", *i); - } - - return 0; -} - -int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces) { - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - unsigned idx = 0; - char **i; - int r; - - if (strv_isempty(ifaces)) - return 0; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - udev = udev_new(); - if (!udev) { - log_error("Failed to connect to udev."); - return -ENOMEM; - } - - STRV_FOREACH(i, ifaces) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - _cleanup_free_ char *n = NULL; - struct ether_addr mac; - int ifi; - - ifi = parse_interface(udev, *i); - if (ifi < 0) - return ifi; - - r = generate_mac(machine_name, &mac, MACVLAN_HASH_KEY, idx++); - if (r < 0) - return log_error_errno(r, "Failed to create MACVLAN MAC address: %m"); - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_LINK, ifi); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface index: %m"); - - n = strappend("mv-", *i); - if (!n) - return log_oom(); - - strshorten(n, IFNAMSIZ-1); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name: %m"); - - r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, &mac); - if (r < 0) - return log_error_errno(r, "Failed to add netlink MAC address: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); - if (r < 0) - return log_error_errno(r, "Failed to add netlink namespace field: %m"); - - r = sd_netlink_message_open_container(m, IFLA_LINKINFO); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "macvlan"); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_MACVLAN_MODE, MACVLAN_MODE_BRIDGE); - if (r < 0) - return log_error_errno(r, "Failed to append macvlan mode: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add new macvlan interfaces: %m"); - } - - return 0; -} - -int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces) { - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - char **i; - int r; - - if (strv_isempty(ifaces)) - return 0; - - r = sd_netlink_open(&rtnl); - if (r < 0) - return log_error_errno(r, "Failed to connect to netlink: %m"); - - udev = udev_new(); - if (!udev) { - log_error("Failed to connect to udev."); - return -ENOMEM; - } - - STRV_FOREACH(i, ifaces) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; - _cleanup_free_ char *n = NULL; - int ifi; - - ifi = parse_interface(udev, *i); - if (ifi < 0) - return ifi; - - r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); - if (r < 0) - return log_error_errno(r, "Failed to allocate netlink message: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_LINK, ifi); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface index: %m"); - - n = strappend("iv-", *i); - if (!n) - return log_oom(); - - strshorten(n, IFNAMSIZ-1); - - r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); - if (r < 0) - return log_error_errno(r, "Failed to add netlink interface name: %m"); - - r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); - if (r < 0) - return log_error_errno(r, "Failed to add netlink namespace field: %m"); - - r = sd_netlink_message_open_container(m, IFLA_LINKINFO); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "ipvlan"); - if (r < 0) - return log_error_errno(r, "Failed to open netlink container: %m"); - - r = sd_netlink_message_append_u16(m, IFLA_IPVLAN_MODE, IPVLAN_MODE_L2); - if (r < 0) - return log_error_errno(r, "Failed to add ipvlan mode: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_message_close_container(m); - if (r < 0) - return log_error_errno(r, "Failed to close netlink container: %m"); - - r = sd_netlink_call(rtnl, m, 0, NULL); - if (r < 0) - return log_error_errno(r, "Failed to add new ipvlan interfaces: %m"); - } - - return 0; -} - -int veth_extra_parse(char ***l, const char *p) { - _cleanup_free_ char *a = NULL, *b = NULL; - int r; - - r = extract_first_word(&p, &a, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (r == 0 || isempty(a)) - return -EINVAL; - - r = extract_first_word(&p, &b, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r < 0) - return r; - if (r == 0 || isempty(b)) { - free(b); - b = strdup(a); - if (!b) - return -ENOMEM; - } - - if (p) - return -EINVAL; - - r = strv_push_pair(l, a, b); - if (r < 0) - return -ENOMEM; - - a = b = NULL; - return 0; -} diff --git a/src/nspawn/nspawn-network.h b/src/nspawn/nspawn-network.h deleted file mode 100644 index 9ab1606d1c..0000000000 --- a/src/nspawn/nspawn-network.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include -#include - -int setup_veth(const char *machine_name, pid_t pid, char iface_name[IFNAMSIZ], bool bridge); -int setup_veth_extra(const char *machine_name, pid_t pid, char **pairs); - -int setup_bridge(const char *veth_name, const char *bridge_name); - -int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces); -int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces); - -int move_network_interfaces(pid_t pid, char **ifaces); - -int veth_extra_parse(char ***l, const char *p); diff --git a/src/nspawn/nspawn-register.c b/src/nspawn/nspawn-register.c deleted file mode 100644 index 760861089d..0000000000 --- a/src/nspawn/nspawn-register.c +++ /dev/null @@ -1,235 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include "sd-bus.h" - -#include "bus-error.h" -#include "bus-util.h" -#include "nspawn-register.h" -#include "stat-util.h" -#include "strv.h" -#include "util.h" - -int register_machine( - const char *machine_name, - pid_t pid, - const char *directory, - sd_id128_t uuid, - int local_ifindex, - const char *slice, - CustomMount *mounts, - unsigned n_mounts, - int kill_signal, - char **properties, - bool keep_unit, - const char *service) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - r = sd_bus_default_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to open system bus: %m"); - - if (keep_unit) { - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "RegisterMachineWithNetwork", - &error, - NULL, - "sayssusai", - machine_name, - SD_BUS_MESSAGE_APPEND_ID128(uuid), - service, - "container", - (uint32_t) pid, - strempty(directory), - local_ifindex > 0 ? 1 : 0, local_ifindex); - } else { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - char **i; - unsigned j; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "CreateMachineWithNetwork"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append( - m, - "sayssusai", - machine_name, - SD_BUS_MESSAGE_APPEND_ID128(uuid), - service, - "container", - (uint32_t) pid, - strempty(directory), - local_ifindex > 0 ? 1 : 0, local_ifindex); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - if (!isempty(slice)) { - r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_append(m, "(sv)", "DevicePolicy", "s", "strict"); - if (r < 0) - return bus_log_create_error(r); - - /* If you make changes here, also make sure to update - * 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, - /* 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); - - for (j = 0; j < n_mounts; j++) { - CustomMount *cm = mounts + j; - - if (cm->type != CUSTOM_MOUNT_BIND) - continue; - - r = is_device_node(cm->source); - if (r < 0) - return log_error_errno(r, "Failed to stat %s: %m", cm->source); - - if (r) { - r = sd_bus_message_append(m, "(sv)", "DeviceAllow", "a(ss)", 1, - cm->source, cm->read_only ? "r" : "rw"); - if (r < 0) - return log_error_errno(r, "Failed to append message arguments: %m"); - } - } - - if (kill_signal != 0) { - r = sd_bus_message_append(m, "(sv)", "KillSignal", "i", kill_signal); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "(sv)", "KillMode", "s", "mixed"); - if (r < 0) - return bus_log_create_error(r); - } - - STRV_FOREACH(i, properties) { - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, NULL); - } - - if (r < 0) { - log_error("Failed to register machine: %s", bus_error_message(&error, r)); - return r; - } - - return 0; -} - -int terminate_machine(pid_t pid) { - _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 *path; - int r; - - r = sd_bus_default_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to open system bus: %m"); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineByPID", - &error, - &reply, - "u", - (uint32_t) pid); - if (r < 0) { - /* Note that the machine might already have been - * cleaned up automatically, hence don't consider it a - * failure if we cannot get the machine object. */ - log_debug("Failed to get machine: %s", bus_error_message(&error, r)); - return 0; - } - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_call_method( - bus, - "org.freedesktop.machine1", - path, - "org.freedesktop.machine1.Machine", - "Terminate", - &error, - NULL, - NULL); - if (r < 0) { - log_debug("Failed to terminate machine: %s", bus_error_message(&error, r)); - return 0; - } - - return 0; -} diff --git a/src/nspawn/nspawn-register.h b/src/nspawn/nspawn-register.h deleted file mode 100644 index 304c5a485b..0000000000 --- a/src/nspawn/nspawn-register.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include - -#include "sd-id128.h" - -#include "nspawn-mount.h" - -int register_machine(const char *machine_name, pid_t pid, const char *directory, sd_id128_t uuid, int local_ifindex, const char *slice, CustomMount *mounts, unsigned n_mounts, int kill_signal, char **properties, bool keep_unit, const char *service); -int terminate_machine(pid_t pid); diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c deleted file mode 100644 index 4fb0054698..0000000000 --- a/src/nspawn/nspawn-settings.c +++ /dev/null @@ -1,394 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include "alloc-util.h" -#include "cap-list.h" -#include "conf-parser.h" -#include "nspawn-network.h" -#include "nspawn-settings.h" -#include "parse-util.h" -#include "process-util.h" -#include "strv.h" -#include "util.h" - -int settings_load(FILE *f, const char *path, Settings **ret) { - _cleanup_(settings_freep) Settings *s = NULL; - int r; - - assert(path); - assert(ret); - - s = new0(Settings, 1); - if (!s) - return -ENOMEM; - - s->start_mode = _START_MODE_INVALID; - s->personality = PERSONALITY_INVALID; - - s->read_only = -1; - s->volatile_mode = _VOLATILE_MODE_INVALID; - - s->private_network = -1; - s->network_veth = -1; - - r = config_parse(NULL, path, f, - "Exec\0" - "Network\0" - "Files\0", - config_item_perf_lookup, nspawn_gperf_lookup, - false, - false, - true, - s); - if (r < 0) - return r; - - *ret = s; - s = NULL; - - return 0; -} - -Settings* settings_free(Settings *s) { - - if (!s) - return NULL; - - strv_free(s->parameters); - strv_free(s->environment); - free(s->user); - free(s->working_directory); - - strv_free(s->network_interfaces); - strv_free(s->network_macvlan); - strv_free(s->network_ipvlan); - strv_free(s->network_veth_extra); - free(s->network_bridge); - expose_port_free_all(s->expose_ports); - - custom_mount_free_all(s->custom_mounts, s->n_custom_mounts); - free(s); - - return NULL; -} - -bool settings_private_network(Settings *s) { - assert(s); - - return - s->private_network > 0 || - s->network_veth > 0 || - s->network_bridge || - s->network_interfaces || - s->network_macvlan || - s->network_ipvlan || - s->network_veth_extra; -} - -bool settings_network_veth(Settings *s) { - assert(s); - - return - s->network_veth > 0 || - s->network_bridge; -} - -DEFINE_CONFIG_PARSE_ENUM(config_parse_volatile_mode, volatile_mode, VolatileMode, "Failed to parse volatile mode"); - -int config_parse_expose_port( - 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) { - - Settings *s = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = expose_port_parse(&s->expose_ports, rvalue); - if (r == -EEXIST) { - log_syntax(unit, LOG_ERR, filename, line, r, "Duplicate port specification, ignoring: %s", rvalue); - return 0; - } - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse host port %s: %m", rvalue); - return 0; - } - - return 0; -} - -int config_parse_capability( - 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 u = 0, *result = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - for (;;) { - _cleanup_free_ char *word = NULL; - int cap; - - r = extract_first_word(&rvalue, &word, NULL, 0); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract capability string, ignoring: %s", rvalue); - return 0; - } - if (r == 0) - break; - - cap = capability_from_name(word); - if (cap < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability, ignoring: %s", word); - continue; - } - - u |= 1 << ((uint64_t) cap); - } - - if (u == 0) - return 0; - - *result |= u; - return 0; -} - -int config_parse_id128( - 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) { - - sd_id128_t t, *result = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = sd_id128_from_string(rvalue, &t); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse 128bit ID/UUID, ignoring: %s", rvalue); - return 0; - } - - *result = t; - return 0; -} - -int config_parse_bind( - 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) { - - Settings *settings = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = bind_mount_parse(&settings->custom_mounts, &settings->n_custom_mounts, rvalue, ltype); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Invalid bind mount specification %s: %m", rvalue); - return 0; - } - - return 0; -} - -int config_parse_tmpfs( - 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) { - - Settings *settings = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = tmpfs_mount_parse(&settings->custom_mounts, &settings->n_custom_mounts, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Invalid temporary file system specification %s: %m", rvalue); - return 0; - } - - return 0; -} - -int config_parse_veth_extra( - 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) { - - Settings *settings = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = veth_extra_parse(&settings->network_veth_extra, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Invalid extra virtual Ethernet link specification %s: %m", rvalue); - return 0; - } - - return 0; -} - -int config_parse_boot( - 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) { - - Settings *settings = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse Boot= parameter %s, ignoring: %m", rvalue); - return 0; - } - - if (r > 0) { - if (settings->start_mode == START_PID2) - goto conflict; - - settings->start_mode = START_BOOT; - } else { - if (settings->start_mode == START_BOOT) - goto conflict; - - if (settings->start_mode < 0) - settings->start_mode = START_PID1; - } - - return 0; - -conflict: - log_syntax(unit, LOG_ERR, filename, line, r, "Conflicting Boot= or ProcessTwo= setting found. Ignoring."); - return 0; -} - -int config_parse_pid2( - 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) { - - Settings *settings = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = parse_boolean(rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse ProcessTwo= parameter %s, ignoring: %m", rvalue); - return 0; - } - - if (r > 0) { - if (settings->start_mode == START_BOOT) - goto conflict; - - settings->start_mode = START_PID2; - } else { - if (settings->start_mode == START_PID2) - goto conflict; - - if (settings->start_mode < 0) - settings->start_mode = START_PID1; - } - - return 0; - -conflict: - log_syntax(unit, LOG_ERR, filename, line, r, "Conflicting Boot= or ProcessTwo= setting found. Ignoring."); - return 0; -} diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h deleted file mode 100644 index a017405cd9..0000000000 --- a/src/nspawn/nspawn-settings.h +++ /dev/null @@ -1,101 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include - -#include "macro.h" -#include "nspawn-expose-ports.h" -#include "nspawn-mount.h" - -typedef enum StartMode { - START_PID1, /* Run parameters as command line as process 1 */ - START_PID2, /* Use stub init process as PID 1, run parameters as command line as process 2 */ - START_BOOT, /* Search for init system, pass arguments as parameters */ - _START_MODE_MAX, - _START_MODE_INVALID = -1 -} StartMode; - -typedef enum SettingsMask { - SETTING_START_MODE = 1 << 0, - SETTING_ENVIRONMENT = 1 << 1, - SETTING_USER = 1 << 2, - SETTING_CAPABILITY = 1 << 3, - SETTING_KILL_SIGNAL = 1 << 4, - SETTING_PERSONALITY = 1 << 5, - SETTING_MACHINE_ID = 1 << 6, - SETTING_NETWORK = 1 << 7, - SETTING_EXPOSE_PORTS = 1 << 8, - SETTING_READ_ONLY = 1 << 9, - SETTING_VOLATILE_MODE = 1 << 10, - SETTING_CUSTOM_MOUNTS = 1 << 11, - SETTING_WORKING_DIRECTORY = 1 << 12, - _SETTINGS_MASK_ALL = (1 << 13) -1 -} SettingsMask; - -typedef struct Settings { - /* [Run] */ - StartMode start_mode; - char **parameters; - char **environment; - char *user; - uint64_t capability; - uint64_t drop_capability; - int kill_signal; - unsigned long personality; - sd_id128_t machine_id; - char *working_directory; - - /* [Image] */ - int read_only; - VolatileMode volatile_mode; - CustomMount *custom_mounts; - unsigned n_custom_mounts; - - /* [Network] */ - int private_network; - int network_veth; - char *network_bridge; - char **network_interfaces; - char **network_macvlan; - char **network_ipvlan; - char **network_veth_extra; - ExposePort *expose_ports; -} Settings; - -int settings_load(FILE *f, const char *path, Settings **ret); -Settings* settings_free(Settings *s); - -bool settings_network_veth(Settings *s); -bool settings_private_network(Settings *s); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Settings*, settings_free); - -const struct ConfigPerfItem* nspawn_gperf_lookup(const char *key, unsigned length); - -int config_parse_capability(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_id128(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_expose_port(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_volatile_mode(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_bind(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_tmpfs(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_veth_extra(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_boot(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_pid2(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/nspawn/nspawn-setuid.c b/src/nspawn/nspawn-setuid.c deleted file mode 100644 index ee15a47e93..0000000000 --- a/src/nspawn/nspawn-setuid.c +++ /dev/null @@ -1,273 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "mkdir.h" -#include "nspawn-setuid.h" -#include "process-util.h" -#include "signal-util.h" -#include "string-util.h" -#include "user-util.h" -#include "util.h" - -static int spawn_getent(const char *database, const char *key, pid_t *rpid) { - int pipe_fds[2]; - pid_t pid; - - assert(database); - assert(key); - assert(rpid); - - if (pipe2(pipe_fds, O_CLOEXEC) < 0) - return log_error_errno(errno, "Failed to allocate pipe: %m"); - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork getent child: %m"); - else if (pid == 0) { - int nullfd; - char *empty_env = NULL; - - if (dup3(pipe_fds[1], STDOUT_FILENO, 0) < 0) - _exit(EXIT_FAILURE); - - if (pipe_fds[0] > 2) - safe_close(pipe_fds[0]); - if (pipe_fds[1] > 2) - safe_close(pipe_fds[1]); - - nullfd = open("/dev/null", O_RDWR); - if (nullfd < 0) - _exit(EXIT_FAILURE); - - if (dup3(nullfd, STDIN_FILENO, 0) < 0) - _exit(EXIT_FAILURE); - - if (dup3(nullfd, STDERR_FILENO, 0) < 0) - _exit(EXIT_FAILURE); - - if (nullfd > 2) - safe_close(nullfd); - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - close_all_fds(NULL, 0); - - execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env); - execle("/bin/getent", "getent", database, key, NULL, &empty_env); - _exit(EXIT_FAILURE); - } - - pipe_fds[1] = safe_close(pipe_fds[1]); - - *rpid = pid; - - return pipe_fds[0]; -} - -int change_uid_gid(const char *user, char **_home) { - char line[LINE_MAX], *x, *u, *g, *h; - const char *word, *state; - _cleanup_free_ uid_t *uids = NULL; - _cleanup_free_ char *home = NULL; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_close_ int fd = -1; - unsigned n_uids = 0; - size_t sz = 0, l; - uid_t uid; - gid_t gid; - pid_t pid; - int r; - - assert(_home); - - if (!user || streq(user, "root") || streq(user, "0")) { - /* Reset everything fully to 0, just in case */ - - r = reset_uid_gid(); - if (r < 0) - return log_error_errno(r, "Failed to become root: %m"); - - *_home = NULL; - return 0; - } - - /* First, get user credentials */ - fd = spawn_getent("passwd", user, &pid); - if (fd < 0) - return fd; - - f = fdopen(fd, "r"); - if (!f) - return log_oom(); - 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; - } - - truncate_nl(line); - - wait_for_terminate_and_warn("getent passwd", pid, true); - - x = strchr(line, ':'); - if (!x) { - log_error("/etc/passwd entry has invalid user field."); - return -EIO; - } - - u = strchr(x+1, ':'); - if (!u) { - log_error("/etc/passwd entry has invalid password field."); - return -EIO; - } - - u++; - g = strchr(u, ':'); - if (!g) { - log_error("/etc/passwd entry has invalid UID field."); - return -EIO; - } - - *g = 0; - g++; - x = strchr(g, ':'); - if (!x) { - log_error("/etc/passwd entry has invalid GID field."); - return -EIO; - } - - *x = 0; - h = strchr(x+1, ':'); - if (!h) { - log_error("/etc/passwd entry has invalid GECOS field."); - return -EIO; - } - - h++; - x = strchr(h, ':'); - if (!x) { - log_error("/etc/passwd entry has invalid home directory field."); - return -EIO; - } - - *x = 0; - - r = parse_uid(u, &uid); - if (r < 0) { - log_error("Failed to parse UID of user."); - return -EIO; - } - - r = parse_gid(g, &gid); - if (r < 0) { - log_error("Failed to parse GID of user."); - return -EIO; - } - - home = strdup(h); - if (!home) - return log_oom(); - - /* Second, get group memberships */ - fd = spawn_getent("initgroups", user, &pid); - if (fd < 0) - return fd; - - fclose(f); - f = fdopen(fd, "r"); - if (!f) - return log_oom(); - 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; - } - - truncate_nl(line); - - wait_for_terminate_and_warn("getent initgroups", pid, true); - - /* Skip over the username and subsequent separator whitespace */ - x = line; - x += strcspn(x, WHITESPACE); - x += strspn(x, WHITESPACE); - - FOREACH_WORD(word, l, x, state) { - char c[l+1]; - - memcpy(c, word, l); - c[l] = 0; - - if (!GREEDY_REALLOC(uids, sz, n_uids+1)) - return log_oom(); - - r = parse_uid(c, &uids[n_uids++]); - if (r < 0) { - log_error("Failed to parse group data from getent."); - return -EIO; - } - } - - r = mkdir_parents(home, 0775); - if (r < 0) - return log_error_errno(r, "Failed to make home root directory: %m"); - - r = mkdir_safe(home, 0755, uid, gid); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Failed to make home directory: %m"); - - (void) fchown(STDIN_FILENO, uid, gid); - (void) fchown(STDOUT_FILENO, uid, gid); - (void) fchown(STDERR_FILENO, uid, gid); - - if (setgroups(n_uids, uids) < 0) - return log_error_errno(errno, "Failed to set auxiliary groups: %m"); - - if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "setresgid() failed: %m"); - - if (setresuid(uid, uid, uid) < 0) - return log_error_errno(errno, "setresuid() failed: %m"); - - if (_home) { - *_home = home; - home = NULL; - } - - return 0; -} diff --git a/src/nspawn/nspawn-setuid.h b/src/nspawn/nspawn-setuid.h deleted file mode 100644 index b4968ba1fc..0000000000 --- a/src/nspawn/nspawn-setuid.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -int change_uid_gid(const char *user, char **ret); diff --git a/src/nspawn/nspawn-stub-pid1.c b/src/nspawn/nspawn-stub-pid1.c deleted file mode 100644 index 2de87e3c63..0000000000 --- a/src/nspawn/nspawn-stub-pid1.c +++ /dev/null @@ -1,170 +0,0 @@ -/*** - 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 . -***/ - -#include -#include -#include - -#include "fd-util.h" -#include "log.h" -#include "nspawn-stub-pid1.h" -#include "process-util.h" -#include "signal-util.h" -#include "time-util.h" -#include "def.h" - -int stub_pid1(void) { - enum { - STATE_RUNNING, - STATE_REBOOT, - STATE_POWEROFF, - } state = STATE_RUNNING; - - sigset_t fullmask, oldmask, waitmask; - usec_t quit_usec = USEC_INFINITY; - pid_t pid; - int r; - - /* Implements a stub PID 1, that reaps all processes and processes a couple of standard signals. This is useful - * for allowing arbitrary processes run in a container, and still have all zombies reaped. */ - - assert_se(sigfillset(&fullmask) >= 0); - assert_se(sigprocmask(SIG_BLOCK, &fullmask, &oldmask) >= 0); - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork child pid: %m"); - - if (pid == 0) { - /* Return in the child */ - assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) >= 0); - setsid(); - return 0; - } - - reset_all_signal_handlers(); - - log_close(); - close_all_fds(NULL, 0); - log_open(); - - rename_process("STUBINIT"); - - assert_se(sigemptyset(&waitmask) >= 0); - assert_se(sigset_add_many(&waitmask, - SIGCHLD, /* posix: process died */ - SIGINT, /* sysv: ctrl-alt-del */ - SIGRTMIN+3, /* systemd: halt */ - SIGRTMIN+4, /* systemd: poweroff */ - SIGRTMIN+5, /* systemd: reboot */ - SIGRTMIN+6, /* systemd: kexec */ - SIGRTMIN+13, /* systemd: halt */ - SIGRTMIN+14, /* systemd: poweroff */ - SIGRTMIN+15, /* systemd: reboot */ - SIGRTMIN+16, /* systemd: kexec */ - -1) >= 0); - - /* Note that we ignore SIGTERM (sysv's reexec), SIGHUP (reload), and all other signals here, since we don't - * support reexec/reloading in this stub process. */ - - for (;;) { - siginfo_t si; - usec_t current_usec; - - si.si_pid = 0; - r = waitid(P_ALL, 0, &si, WEXITED|WNOHANG); - if (r < 0) { - r = log_error_errno(errno, "Failed to reap children: %m"); - goto finish; - } - - current_usec = now(CLOCK_MONOTONIC); - - if (si.si_pid == pid || current_usec >= quit_usec) { - - /* The child we started ourselves died or we reached a timeout. */ - - if (state == STATE_REBOOT) { /* dispatch a queued reboot */ - (void) reboot(RB_AUTOBOOT); - r = log_error_errno(errno, "Failed to reboot: %m"); - goto finish; - - } else if (state == STATE_POWEROFF) - (void) reboot(RB_POWER_OFF); /* if this fails, fall back to normal exit. */ - - if (si.si_pid == pid && si.si_code == CLD_EXITED) - r = si.si_status; /* pass on exit code */ - else - r = 255; /* signal, coredump, timeout, … */ - - goto finish; - } - if (si.si_pid != 0) - /* We reaped something. Retry until there's nothing more to reap. */ - continue; - - if (quit_usec == USEC_INFINITY) - r = sigwaitinfo(&waitmask, &si); - else { - struct timespec ts; - r = sigtimedwait(&waitmask, &si, timespec_store(&ts, quit_usec - current_usec)); - } - if (r < 0) { - if (errno == EINTR) /* strace -p attach can result in EINTR, let's handle this nicely. */ - continue; - if (errno == EAGAIN) /* timeout reached */ - continue; - - r = log_error_errno(errno, "Failed to wait for signal: %m"); - goto finish; - } - - if (si.si_signo == SIGCHLD) - continue; /* Let's reap this */ - - if (state != STATE_RUNNING) - continue; - - /* Would love to use a switch() statement here, but SIGRTMIN is actually a function call, not a - * constant… */ - - if (si.si_signo == SIGRTMIN+3 || - si.si_signo == SIGRTMIN+4 || - si.si_signo == SIGRTMIN+13 || - si.si_signo == SIGRTMIN+14) - - state = STATE_POWEROFF; - - else if (si.si_signo == SIGINT || - si.si_signo == SIGRTMIN+5 || - si.si_signo == SIGRTMIN+6 || - si.si_signo == SIGRTMIN+15 || - si.si_signo == SIGRTMIN+16) - - state = STATE_REBOOT; - else - assert_not_reached("Got unexpected signal"); - - /* (void) kill_and_sigcont(pid, SIGTERM); */ - quit_usec = now(CLOCK_MONOTONIC) + DEFAULT_TIMEOUT_USEC; - } - -finish: - _exit(r < 0 ? EXIT_FAILURE : r); -} diff --git a/src/nspawn/nspawn-stub-pid1.h b/src/nspawn/nspawn-stub-pid1.h deleted file mode 100644 index 36c1aaf5dd..0000000000 --- a/src/nspawn/nspawn-stub-pid1.h +++ /dev/null @@ -1,22 +0,0 @@ -#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 . -***/ - -int stub_pid1(void); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c deleted file mode 100644 index ef348c335b..0000000000 --- a/src/nspawn/nspawn.c +++ /dev/null @@ -1,3700 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#ifdef HAVE_BLKID -#include -#endif -#include -#include -#include -#include -#ifdef HAVE_SECCOMP -#include -#endif -#ifdef HAVE_SELINUX -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-daemon.h" -#include "sd-id128.h" - -#include "alloc-util.h" -#include "barrier.h" -#include "base-filesystem.h" -#include "blkid-util.h" -#include "btrfs-util.h" -#include "cap-list.h" -#include "capability-util.h" -#include "cgroup-util.h" -#include "copy.h" -#include "dev-setup.h" -#include "env-util.h" -#include "fd-util.h" -#include "fdset.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "gpt.h" -#include "hostname-util.h" -#include "log.h" -#include "loopback-setup.h" -#include "machine-image.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "mount-util.h" -#include "netlink-util.h" -#include "nspawn-cgroup.h" -#include "nspawn-expose-ports.h" -#include "nspawn-mount.h" -#include "nspawn-network.h" -#include "nspawn-register.h" -#include "nspawn-settings.h" -#include "nspawn-setuid.h" -#include "nspawn-stub-pid1.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "ptyfwd.h" -#include "random-util.h" -#include "rm-rf.h" -#ifdef HAVE_SECCOMP -#include "seccomp-util.h" -#endif -#include "signal-util.h" -#include "socket-util.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "udev-util.h" -#include "umask-util.h" -#include "user-util.h" -#include "util.h" - -typedef enum ContainerStatus { - CONTAINER_TERMINATED, - CONTAINER_REBOOTED -} ContainerStatus; - -typedef enum LinkJournal { - LINK_NO, - LINK_AUTO, - LINK_HOST, - LINK_GUEST -} LinkJournal; - -static char *arg_directory = NULL; -static char *arg_template = NULL; -static char *arg_chdir = NULL; -static char *arg_user = NULL; -static sd_id128_t arg_uuid = {}; -static char *arg_machine = NULL; -static const char *arg_selinux_context = NULL; -static const char *arg_selinux_apifs_context = NULL; -static const char *arg_slice = NULL; -static bool arg_private_network = false; -static bool arg_read_only = false; -static StartMode arg_start_mode = START_PID1; -static bool arg_ephemeral = false; -static LinkJournal arg_link_journal = LINK_AUTO; -static bool arg_link_journal_try = false; -static uint64_t arg_retain = - (1ULL << CAP_CHOWN) | - (1ULL << CAP_DAC_OVERRIDE) | - (1ULL << CAP_DAC_READ_SEARCH) | - (1ULL << CAP_FOWNER) | - (1ULL << CAP_FSETID) | - (1ULL << CAP_IPC_OWNER) | - (1ULL << CAP_KILL) | - (1ULL << CAP_LEASE) | - (1ULL << CAP_LINUX_IMMUTABLE) | - (1ULL << CAP_NET_BIND_SERVICE) | - (1ULL << CAP_NET_BROADCAST) | - (1ULL << CAP_NET_RAW) | - (1ULL << CAP_SETGID) | - (1ULL << CAP_SETFCAP) | - (1ULL << CAP_SETPCAP) | - (1ULL << CAP_SETUID) | - (1ULL << CAP_SYS_ADMIN) | - (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); -static CustomMount *arg_custom_mounts = NULL; -static unsigned arg_n_custom_mounts = 0; -static char **arg_setenv = NULL; -static bool arg_quiet = false; -static bool arg_share_system = false; -static bool arg_register = true; -static bool arg_keep_unit = false; -static char **arg_network_interfaces = NULL; -static char **arg_network_macvlan = NULL; -static char **arg_network_ipvlan = NULL; -static bool arg_network_veth = false; -static char **arg_network_veth_extra = NULL; -static char *arg_network_bridge = NULL; -static unsigned long arg_personality = PERSONALITY_INVALID; -static char *arg_image = NULL; -static VolatileMode arg_volatile_mode = VOLATILE_NO; -static ExposePort *arg_expose_ports = NULL; -static char **arg_property = NULL; -static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; -static bool arg_userns = false; -static int arg_kill_signal = 0; -static bool arg_unified_cgroup_hierarchy = false; -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 void help(void) { - printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" - "Spawn a minimal namespace container for debugging, testing and building.\n\n" - " -h --help Show this help\n" - " --version Print version string\n" - " -q --quiet Do not show status information\n" - " -D --directory=PATH Root directory for the container\n" - " --template=PATH Initialize root directory from template directory,\n" - " if missing\n" - " -x --ephemeral Run container with snapshot of root directory, and\n" - " remove it after exit\n" - " -i --image=PATH File system device or disk image for the container\n" - " -a --as-pid2 Maintain a stub init as PID1, invoke binary as PID2\n" - " -b --boot Boot up full system (i.e. invoke init)\n" - " --chdir=PATH Set working directory in the container\n" - " -u --user=USER Run the command under specified user or uid\n" - " -M --machine=NAME Set the machine name for the container\n" - " --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" - " --private-users[=UIDBASE[:NUIDS]]\n" - " Run within user namespace\n" - " --private-network Disable network in container\n" - " --network-interface=INTERFACE\n" - " Assign an existing network interface to the\n" - " container\n" - " --network-macvlan=INTERFACE\n" - " Create a macvlan network interface based on an\n" - " existing network interface to the container\n" - " --network-ipvlan=INTERFACE\n" - " Create a ipvlan network interface based on an\n" - " existing network interface to the container\n" - " -n --network-veth Add a virtual Ethernet connection between host\n" - " and container\n" - " --network-veth-extra=HOSTIF[:CONTAINERIF]\n" - " 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" - " -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n" - " Expose a container IP port on the host\n" - " -Z --selinux-context=SECLABEL\n" - " Set the SELinux security context to be used by\n" - " processes in the container\n" - " -L --selinux-apifs-context=SECLABEL\n" - " Set the SELinux security context to be used by\n" - " API/tmpfs file systems in the container\n" - " --capability=CAP In addition to the default, retain specified\n" - " capability\n" - " --drop-capability=CAP Drop the specified capability from the default set\n" - " --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n" - " --link-journal=MODE Link up guest journal, one of no, auto, guest, \n" - " host, try-guest, try-host\n" - " -j Equivalent to --link-journal=try-guest\n" - " --read-only Mount the root directory read-only\n" - " --bind=PATH[:PATH[:OPTIONS]]\n" - " Bind mount a file or directory from the host into\n" - " the container\n" - " --bind-ro=PATH[:PATH[:OPTIONS]\n" - " Similar, but creates a read-only bind mount\n" - " --tmpfs=PATH:[OPTIONS] Mount an empty tmpfs to the specified directory\n" - " --overlay=PATH[:PATH...]:PATH\n" - " Create an overlay mount from the host to \n" - " the container\n" - " --overlay-ro=PATH[:PATH...]:PATH\n" - " Similar, but creates a read-only overlay mount\n" - " --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" - , program_invocation_short_name); -} - - -static int custom_mounts_prepare(void) { - unsigned i; - int r; - - /* Ensure the mounts are applied prefix first. */ - qsort_safe(arg_custom_mounts, arg_n_custom_mounts, sizeof(CustomMount), custom_mount_compare); - - /* Allocate working directories for the overlay file systems that need it */ - for (i = 0; i < arg_n_custom_mounts; i++) { - CustomMount *m = &arg_custom_mounts[i]; - - if (arg_userns && arg_uid_shift == UID_INVALID && path_equal(m->destination, "/")) { - log_error("--private-users with automatic UID shift may not be combined with custom root mounts."); - return -EINVAL; - } - - if (m->type != CUSTOM_MOUNT_OVERLAY) - continue; - - if (m->work_dir) - continue; - - if (m->read_only) - continue; - - r = tempfn_random(m->source, NULL, &m->work_dir); - if (r < 0) - return log_error_errno(r, "Failed to generate work directory from %s: %m", m->source); - } - - return 0; -} - -static int detect_unified_cgroup_hierarchy(void) { - const char *e; - int r; - - /* Allow the user to control whether the unified hierarchy is used */ - e = getenv("UNIFIED_CGROUP_HIERARCHY"); - if (e) { - r = parse_boolean(e); - if (r < 0) - return log_error_errno(r, "Failed to parse $UNIFIED_CGROUP_HIERARCHY."); - - arg_unified_cgroup_hierarchy = r; - return 0; - } - - /* Otherwise inherit the default from the host system */ - r = cg_unified(); - if (r < 0) - return log_error_errno(r, "Failed to determine whether the unified cgroups hierarchy is used: %m"); - - arg_unified_cgroup_hierarchy = r; - return 0; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_PRIVATE_NETWORK, - ARG_UUID, - ARG_READ_ONLY, - ARG_CAPABILITY, - ARG_DROP_CAPABILITY, - ARG_LINK_JOURNAL, - ARG_BIND, - ARG_BIND_RO, - ARG_TMPFS, - ARG_OVERLAY, - ARG_OVERLAY_RO, - ARG_SETENV, - ARG_SHARE_SYSTEM, - ARG_REGISTER, - ARG_KEEP_UNIT, - ARG_NETWORK_INTERFACE, - ARG_NETWORK_MACVLAN, - ARG_NETWORK_IPVLAN, - ARG_NETWORK_BRIDGE, - ARG_NETWORK_VETH_EXTRA, - ARG_PERSONALITY, - ARG_VOLATILE, - ARG_TEMPLATE, - ARG_PROPERTY, - ARG_PRIVATE_USERS, - ARG_KILL_SIGNAL, - ARG_SETTINGS, - ARG_CHDIR, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "directory", required_argument, NULL, 'D' }, - { "template", required_argument, NULL, ARG_TEMPLATE }, - { "ephemeral", no_argument, NULL, 'x' }, - { "user", required_argument, NULL, 'u' }, - { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, - { "as-pid2", no_argument, NULL, 'a' }, - { "boot", no_argument, NULL, 'b' }, - { "uuid", required_argument, NULL, ARG_UUID }, - { "read-only", no_argument, NULL, ARG_READ_ONLY }, - { "capability", required_argument, NULL, ARG_CAPABILITY }, - { "drop-capability", required_argument, NULL, ARG_DROP_CAPABILITY }, - { "link-journal", required_argument, NULL, ARG_LINK_JOURNAL }, - { "bind", required_argument, NULL, ARG_BIND }, - { "bind-ro", required_argument, NULL, ARG_BIND_RO }, - { "tmpfs", required_argument, NULL, ARG_TMPFS }, - { "overlay", required_argument, NULL, ARG_OVERLAY }, - { "overlay-ro", required_argument, NULL, ARG_OVERLAY_RO }, - { "machine", required_argument, NULL, 'M' }, - { "slice", required_argument, NULL, 'S' }, - { "setenv", required_argument, NULL, ARG_SETENV }, - { "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 }, - { "register", required_argument, NULL, ARG_REGISTER }, - { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, - { "network-interface", required_argument, NULL, ARG_NETWORK_INTERFACE }, - { "network-macvlan", required_argument, NULL, ARG_NETWORK_MACVLAN }, - { "network-ipvlan", required_argument, NULL, ARG_NETWORK_IPVLAN }, - { "network-veth", no_argument, NULL, 'n' }, - { "network-veth-extra", required_argument, NULL, ARG_NETWORK_VETH_EXTRA}, - { "network-bridge", required_argument, NULL, ARG_NETWORK_BRIDGE }, - { "personality", required_argument, NULL, ARG_PERSONALITY }, - { "image", required_argument, NULL, 'i' }, - { "volatile", optional_argument, NULL, ARG_VOLATILE }, - { "port", required_argument, NULL, 'p' }, - { "property", required_argument, NULL, ARG_PROPERTY }, - { "private-users", optional_argument, NULL, ARG_PRIVATE_USERS }, - { "kill-signal", required_argument, NULL, ARG_KILL_SIGNAL }, - { "settings", required_argument, NULL, ARG_SETTINGS }, - { "chdir", required_argument, NULL, ARG_CHDIR }, - {} - }; - - int c, r; - const char *p, *e; - uint64_t plus = 0, minus = 0; - bool mask_all_settings = false, mask_no_settings = false; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "+hD:u:abL:M:jS:Z:qi:xp:n", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case 'D': - r = parse_path_argument_and_warn(optarg, false, &arg_directory); - if (r < 0) - return r; - break; - - case ARG_TEMPLATE: - r = parse_path_argument_and_warn(optarg, false, &arg_template); - if (r < 0) - return r; - break; - - case 'i': - r = parse_path_argument_and_warn(optarg, false, &arg_image); - if (r < 0) - return r; - break; - - case 'x': - arg_ephemeral = true; - break; - - case 'u': - r = free_and_strdup(&arg_user, optarg); - if (r < 0) - return log_oom(); - - arg_settings_mask |= SETTING_USER; - break; - - case ARG_NETWORK_BRIDGE: - r = free_and_strdup(&arg_network_bridge, optarg); - if (r < 0) - return log_oom(); - - /* fall through */ - - case 'n': - arg_network_veth = true; - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; - break; - - case ARG_NETWORK_VETH_EXTRA: - r = veth_extra_parse(&arg_network_veth_extra, optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --network-veth-extra= parameter: %s", optarg); - - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; - break; - - case ARG_NETWORK_INTERFACE: - if (strv_extend(&arg_network_interfaces, optarg) < 0) - return log_oom(); - - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; - break; - - case ARG_NETWORK_MACVLAN: - if (strv_extend(&arg_network_macvlan, optarg) < 0) - return log_oom(); - - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; - break; - - case ARG_NETWORK_IPVLAN: - if (strv_extend(&arg_network_ipvlan, optarg) < 0) - return log_oom(); - - /* fall through */ - - case ARG_PRIVATE_NETWORK: - arg_private_network = true; - arg_settings_mask |= SETTING_NETWORK; - break; - - case 'b': - if (arg_start_mode == START_PID2) { - log_error("--boot and --as-pid2 may not be combined."); - return -EINVAL; - } - - arg_start_mode = START_BOOT; - arg_settings_mask |= SETTING_START_MODE; - break; - - case 'a': - if (arg_start_mode == START_BOOT) { - log_error("--boot and --as-pid2 may not be combined."); - return -EINVAL; - } - - arg_start_mode = START_PID2; - arg_settings_mask |= SETTING_START_MODE; - break; - - case ARG_UUID: - r = sd_id128_from_string(optarg, &arg_uuid); - if (r < 0) { - log_error("Invalid UUID: %s", optarg); - return r; - } - - arg_settings_mask |= SETTING_MACHINE_ID; - break; - - case 'S': - arg_slice = optarg; - break; - - case 'M': - if (isempty(optarg)) - arg_machine = mfree(arg_machine); - else { - if (!machine_name_is_valid(optarg)) { - log_error("Invalid machine name: %s", optarg); - return -EINVAL; - } - - r = free_and_strdup(&arg_machine, optarg); - if (r < 0) - return log_oom(); - - break; - } - - case 'Z': - arg_selinux_context = optarg; - break; - - case 'L': - arg_selinux_apifs_context = optarg; - break; - - case ARG_READ_ONLY: - arg_read_only = true; - arg_settings_mask |= SETTING_READ_ONLY; - break; - - case ARG_CAPABILITY: - case ARG_DROP_CAPABILITY: { - p = optarg; - for(;;) { - _cleanup_free_ char *t = NULL; - - r = extract_first_word(&p, &t, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse capability %s.", t); - - if (r == 0) - break; - - if (streq(t, "all")) { - if (c == ARG_CAPABILITY) - plus = (uint64_t) -1; - else - minus = (uint64_t) -1; - } else { - int cap; - - cap = capability_from_name(t); - if (cap < 0) { - log_error("Failed to parse capability %s.", t); - return -EINVAL; - } - - if (c == ARG_CAPABILITY) - plus |= 1ULL << (uint64_t) cap; - else - minus |= 1ULL << (uint64_t) cap; - } - } - - arg_settings_mask |= SETTING_CAPABILITY; - break; - } - - case 'j': - arg_link_journal = LINK_GUEST; - arg_link_journal_try = true; - break; - - case ARG_LINK_JOURNAL: - if (streq(optarg, "auto")) { - arg_link_journal = LINK_AUTO; - arg_link_journal_try = false; - } else if (streq(optarg, "no")) { - arg_link_journal = LINK_NO; - arg_link_journal_try = false; - } else if (streq(optarg, "guest")) { - arg_link_journal = LINK_GUEST; - arg_link_journal_try = false; - } else if (streq(optarg, "host")) { - arg_link_journal = LINK_HOST; - arg_link_journal_try = false; - } else if (streq(optarg, "try-guest")) { - arg_link_journal = LINK_GUEST; - arg_link_journal_try = true; - } else if (streq(optarg, "try-host")) { - arg_link_journal = LINK_HOST; - arg_link_journal_try = true; - } else { - log_error("Failed to parse link journal mode %s", optarg); - return -EINVAL; - } - - break; - - case ARG_BIND: - case ARG_BIND_RO: - r = bind_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg, c == ARG_BIND_RO); - if (r < 0) - return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", optarg); - - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; - break; - - case ARG_TMPFS: - r = tmpfs_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --tmpfs= argument %s: %m", optarg); - - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; - break; - - case ARG_OVERLAY: - case ARG_OVERLAY_RO: { - _cleanup_free_ char *upper = NULL, *destination = NULL; - _cleanup_strv_free_ char **lower = NULL; - CustomMount *m; - unsigned n = 0; - char **i; - - r = strv_split_extract(&lower, optarg, ":", EXTRACT_DONT_COALESCE_SEPARATORS); - if (r == -ENOMEM) - return log_oom(); - else if (r < 0) { - log_error("Invalid overlay specification: %s", optarg); - return r; - } - - STRV_FOREACH(i, lower) { - if (!path_is_absolute(*i)) { - log_error("Overlay path %s is not absolute.", *i); - return -EINVAL; - } - - n++; - } - - if (n < 2) { - log_error("--overlay= needs at least two colon-separated directories specified."); - return -EINVAL; - } - - if (n == 2) { - /* If two parameters are specified, - * the first one is the lower, the - * second one the upper directory. And - * we'll also define the destination - * mount point the same as the upper. */ - upper = lower[1]; - lower[1] = NULL; - - destination = strdup(upper); - if (!destination) - return log_oom(); - - } else { - upper = lower[n - 2]; - destination = lower[n - 1]; - lower[n - 2] = NULL; - } - - m = custom_mount_add(&arg_custom_mounts, &arg_n_custom_mounts, CUSTOM_MOUNT_OVERLAY); - if (!m) - return log_oom(); - - m->destination = destination; - m->source = upper; - m->lower = lower; - m->read_only = c == ARG_OVERLAY_RO; - - upper = destination = NULL; - lower = NULL; - - arg_settings_mask |= SETTING_CUSTOM_MOUNTS; - break; - } - - case ARG_SETENV: { - char **n; - - if (!env_assignment_is_valid(optarg)) { - log_error("Environment variable assignment '%s' is not valid.", optarg); - return -EINVAL; - } - - n = strv_env_set(arg_setenv, optarg); - if (!n) - return log_oom(); - - strv_free(arg_setenv); - arg_setenv = n; - - arg_settings_mask |= SETTING_ENVIRONMENT; - break; - } - - case 'q': - arg_quiet = true; - break; - - case ARG_SHARE_SYSTEM: - arg_share_system = true; - break; - - case ARG_REGISTER: - r = parse_boolean(optarg); - if (r < 0) { - log_error("Failed to parse --register= argument: %s", optarg); - return r; - } - - arg_register = r; - break; - - case ARG_KEEP_UNIT: - arg_keep_unit = true; - break; - - case ARG_PERSONALITY: - - arg_personality = personality_from_string(optarg); - if (arg_personality == PERSONALITY_INVALID) { - log_error("Unknown or unsupported personality '%s'.", optarg); - return -EINVAL; - } - - arg_settings_mask |= SETTING_PERSONALITY; - break; - - case ARG_VOLATILE: - - if (!optarg) - arg_volatile_mode = VOLATILE_YES; - else { - VolatileMode m; - - m = volatile_mode_from_string(optarg); - if (m < 0) { - log_error("Failed to parse --volatile= argument: %s", optarg); - return -EINVAL; - } else - arg_volatile_mode = m; - } - - arg_settings_mask |= SETTING_VOLATILE_MODE; - break; - - case 'p': - r = expose_port_parse(&arg_expose_ports, optarg); - if (r == -EEXIST) - return log_error_errno(r, "Duplicate port specification: %s", optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse host port %s: %m", optarg); - - arg_settings_mask |= SETTING_EXPOSE_PORTS; - break; - - case ARG_PROPERTY: - if (strv_extend(&arg_property, optarg) < 0) - return log_oom(); - - break; - - case ARG_PRIVATE_USERS: - if (optarg) { - _cleanup_free_ char *buffer = NULL; - const char *range, *shift; - - range = strchr(optarg, ':'); - if (range) { - buffer = strndup(optarg, range - optarg); - if (!buffer) - return log_oom(); - shift = buffer; - - range++; - if (safe_atou32(range, &arg_uid_range) < 0 || arg_uid_range <= 0) { - log_error("Failed to parse UID range: %s", range); - return -EINVAL; - } - } else - shift = optarg; - - if (parse_uid(shift, &arg_uid_shift) < 0) { - log_error("Failed to parse UID: %s", optarg); - return -EINVAL; - } - } - - arg_userns = true; - break; - - case ARG_KILL_SIGNAL: - arg_kill_signal = signal_from_string_try_harder(optarg); - if (arg_kill_signal < 0) { - log_error("Cannot parse signal: %s", optarg); - return -EINVAL; - } - - arg_settings_mask |= SETTING_KILL_SIGNAL; - break; - - case ARG_SETTINGS: - - /* no → do not read files - * yes → read files, do not override cmdline, trust only subset - * override → read files, override cmdline, trust only subset - * trusted → read files, do not override cmdline, trust all - */ - - r = parse_boolean(optarg); - if (r < 0) { - if (streq(optarg, "trusted")) { - mask_all_settings = false; - mask_no_settings = false; - arg_settings_trusted = true; - - } else if (streq(optarg, "override")) { - mask_all_settings = false; - mask_no_settings = true; - arg_settings_trusted = -1; - } else - return log_error_errno(r, "Failed to parse --settings= argument: %s", optarg); - } else if (r > 0) { - /* yes */ - mask_all_settings = false; - mask_no_settings = false; - arg_settings_trusted = -1; - } else { - /* no */ - mask_all_settings = true; - mask_no_settings = false; - arg_settings_trusted = false; - } - - break; - - case ARG_CHDIR: - if (!path_is_absolute(optarg)) { - log_error("Working directory %s is not an absolute path.", optarg); - return -EINVAL; - } - - r = free_and_strdup(&arg_chdir, optarg); - if (r < 0) - return log_oom(); - - arg_settings_mask |= SETTING_WORKING_DIRECTORY; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (arg_share_system) - arg_register = false; - - if (arg_start_mode != START_PID1 && arg_share_system) { - log_error("--boot and --share-system may not be combined."); - return -EINVAL; - } - - if (arg_keep_unit && cg_pid_get_owner_uid(0, NULL) >= 0) { - log_error("--keep-unit may not be used when invoked from a user session."); - return -EINVAL; - } - - if (arg_directory && arg_image) { - log_error("--directory= and --image= may not be combined."); - return -EINVAL; - } - - if (arg_template && arg_image) { - log_error("--template= and --image= may not be combined."); - return -EINVAL; - } - - if (arg_template && !(arg_directory || arg_machine)) { - log_error("--template= needs --directory= or --machine=."); - return -EINVAL; - } - - if (arg_ephemeral && arg_template) { - log_error("--ephemeral and --template= may not be combined."); - return -EINVAL; - } - - if (arg_ephemeral && arg_image) { - log_error("--ephemeral and --image= may not be combined."); - return -EINVAL; - } - - if (arg_ephemeral && !IN_SET(arg_link_journal, LINK_NO, LINK_AUTO)) { - log_error("--ephemeral and --link-journal= may not be combined."); - return -EINVAL; - } - - if (arg_userns && access("/proc/self/uid_map", F_OK) < 0) - return log_error_errno(EOPNOTSUPP, "--private-users= is not supported, kernel compiled without user namespace support."); - - if (argc > optind) { - arg_parameters = strv_copy(argv + optind); - if (!arg_parameters) - return log_oom(); - - arg_settings_mask |= SETTING_START_MODE; - } - - /* Load all settings from .nspawn files */ - if (mask_no_settings) - arg_settings_mask = 0; - - /* Don't load any settings from .nspawn files */ - if (mask_all_settings) - arg_settings_mask = _SETTINGS_MASK_ALL; - - arg_retain = (arg_retain | plus | (arg_private_network ? 1ULL << CAP_NET_ADMIN : 0)) & ~minus; - - r = detect_unified_cgroup_hierarchy(); - if (r < 0) - return r; - - e = getenv("SYSTEMD_NSPAWN_CONTAINER_SERVICE"); - if (e) - arg_container_service_name = e; - - return 1; -} - -static int verify_arguments(void) { - - if (arg_volatile_mode != VOLATILE_NO && arg_read_only) { - log_error("Cannot combine --read-only with --volatile. Note that --volatile already implies a read-only base hierarchy."); - return -EINVAL; - } - - if (arg_expose_ports && !arg_private_network) { - log_error("Cannot use --port= without private networking."); - return -EINVAL; - } - - if (arg_start_mode == START_BOOT && arg_kill_signal <= 0) - arg_kill_signal = SIGRTMIN+3; - - return 0; -} - -static int userns_lchown(const char *p, uid_t uid, gid_t gid) { - assert(p); - - if (!arg_userns) - return 0; - - if (uid == UID_INVALID && gid == GID_INVALID) - return 0; - - if (uid != UID_INVALID) { - uid += arg_uid_shift; - - if (uid < arg_uid_shift || uid >= arg_uid_shift + arg_uid_range) - return -EOVERFLOW; - } - - if (gid != GID_INVALID) { - gid += (gid_t) arg_uid_shift; - - if (gid < (gid_t) arg_uid_shift || gid >= (gid_t) (arg_uid_shift + arg_uid_range)) - return -EOVERFLOW; - } - - if (lchown(p, uid, gid) < 0) - return -errno; - - return 0; -} - -static int userns_mkdir(const char *root, const char *path, mode_t mode, uid_t uid, gid_t gid) { - const char *q; - - q = prefix_roota(root, path); - if (mkdir(q, mode) < 0) { - if (errno == EEXIST) - return 0; - return -errno; - } - - return userns_lchown(q, uid, gid); -} - -static int setup_timezone(const char *dest) { - _cleanup_free_ char *p = NULL, *q = NULL; - const char *where, *check, *what; - char *z, *y; - int r; - - assert(dest); - - /* Fix the timezone, if possible */ - r = readlink_malloc("/etc/localtime", &p); - if (r < 0) { - log_warning("/etc/localtime is not a symlink, not updating container timezone."); - return 0; - } - - z = path_startswith(p, "../usr/share/zoneinfo/"); - if (!z) - z = path_startswith(p, "/usr/share/zoneinfo/"); - if (!z) { - log_warning("/etc/localtime does not point into /usr/share/zoneinfo/, not updating container timezone."); - return 0; - } - - where = prefix_roota(dest, "/etc/localtime"); - r = readlink_malloc(where, &q); - if (r >= 0) { - y = path_startswith(q, "../usr/share/zoneinfo/"); - if (!y) - y = path_startswith(q, "/usr/share/zoneinfo/"); - - /* Already pointing to the right place? Then do nothing .. */ - if (y && streq(y, z)) - return 0; - } - - check = strjoina("/usr/share/zoneinfo/", z); - check = prefix_roota(dest, check); - if (laccess(check, F_OK) < 0) { - log_warning("Timezone %s does not exist in container, not updating container timezone.", z); - return 0; - } - - r = unlink(where); - if (r < 0 && errno != ENOENT) { - log_error_errno(errno, "Failed to remove existing timezone info %s in container: %m", where); - return 0; - } - - what = strjoina("../usr/share/zoneinfo/", z); - if (symlink(what, where) < 0) { - log_error_errno(errno, "Failed to correct timezone of container: %m"); - return 0; - } - - r = userns_lchown(where, 0, 0); - if (r < 0) - return log_warning_errno(r, "Failed to chown /etc/localtime: %m"); - - return 0; -} - -static int setup_resolv_conf(const char *dest) { - const char *where = NULL; - int r; - - assert(dest); - - if (arg_private_network) - return 0; - - /* Fix resolv.conf, if possible */ - where = prefix_roota(dest, "/etc/resolv.conf"); - - 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 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); - return 0; - } - - r = userns_lchown(where, 0, 0); - if (r < 0) - log_warning_errno(r, "Failed to chown /etc/resolv.conf: %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) { - const char *from, *to; - sd_id128_t rnd = {}; - char as_uuid[37]; - int r; - - if (arg_share_system) - return 0; - - /* Generate a new randomized boot ID, so that each boot-up of - * the container gets a new one */ - - from = prefix_roota(dest, "/run/proc-sys-kernel-random-boot-id"); - to = prefix_roota(dest, "/proc/sys/kernel/random/boot_id"); - - r = sd_id128_randomize(&rnd); - 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); - 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"); - - unlink(from); - return r; -} - -static int copy_devnodes(const char *dest) { - - static const char devnodes[] = - "null\0" - "zero\0" - "full\0" - "random\0" - "urandom\0" - "tty\0" - "net/tun\0"; - - const char *d; - int r = 0; - _cleanup_umask_ mode_t u; - - assert(dest); - - u = umask(0000); - - /* Create /dev/net, so that we can create /dev/net/tun in it */ - if (userns_mkdir(dest, "/dev/net", 0755, 0, 0) < 0) - return log_error_errno(r, "Failed to create /dev/net directory: %m"); - - NULSTR_FOREACH(d, devnodes) { - _cleanup_free_ char *from = NULL, *to = NULL; - struct stat st; - - from = strappend("/dev/", d); - to = prefix_root(dest, from); - - if (stat(from, &st) < 0) { - - if (errno != ENOENT) - return log_error_errno(errno, "Failed to stat %s: %m", from); - - } else if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { - - log_error("%s is not a char or block device, cannot copy.", from); - return -EIO; - - } else { - if (mknod(to, st.st_mode, st.st_rdev) < 0) { - if (errno != EPERM) - return log_error_errno(errno, "mknod(%s) failed: %m", to); - - /* Some systems abusively restrict mknod but - * allow bind mounts. */ - r = touch(to); - if (r < 0) - return log_error_errno(r, "touch (%s) failed: %m", to); - if (mount(from, to, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Both mknod and bind mount (%s) failed: %m", to); - } - - r = userns_lchown(to, 0, 0); - if (r < 0) - return log_error_errno(r, "chown() of device node %s failed: %m", to); - } - } - - return r; -} - -static int setup_pts(const char *dest) { - _cleanup_free_ char *options = NULL; - const char *p; - int r; - -#ifdef HAVE_SELINUX - if (arg_selinux_apifs_context) - (void) asprintf(&options, - "newinstance,ptmxmode=0666,mode=620,gid=" GID_FMT ",context=\"%s\"", - arg_uid_shift + TTY_GID, - arg_selinux_apifs_context); - else -#endif - (void) asprintf(&options, - "newinstance,ptmxmode=0666,mode=620,gid=" GID_FMT, - arg_uid_shift + TTY_GID); - - if (!options) - return log_oom(); - - /* Mount /dev/pts itself */ - p = prefix_roota(dest, "/dev/pts"); - if (mkdir(p, 0755) < 0) - return log_error_errno(errno, "Failed to create /dev/pts: %m"); - if (mount("devpts", p, "devpts", MS_NOSUID|MS_NOEXEC, options) < 0) - return log_error_errno(errno, "Failed to mount /dev/pts: %m"); - r = userns_lchown(p, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to chown /dev/pts: %m"); - - /* Create /dev/ptmx symlink */ - p = prefix_roota(dest, "/dev/ptmx"); - if (symlink("pts/ptmx", p) < 0) - return log_error_errno(errno, "Failed to create /dev/ptmx symlink: %m"); - r = userns_lchown(p, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to chown /dev/ptmx: %m"); - - /* And fix /dev/pts/ptmx ownership */ - p = prefix_roota(dest, "/dev/pts/ptmx"); - r = userns_lchown(p, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to chown /dev/pts/ptmx: %m"); - - return 0; -} - -static int setup_dev_console(const char *dest, const char *console) { - _cleanup_umask_ mode_t u; - const char *to; - int r; - - assert(dest); - assert(console); - - u = umask(0000); - - r = chmod_and_chown(console, 0600, arg_uid_shift, arg_uid_shift); - if (r < 0) - return log_error_errno(r, "Failed to correct access mode for TTY: %m"); - - /* We need to bind mount the right tty to /dev/console since - * ptys can only exist on pts file systems. To have something - * to bind mount things on we create a empty regular file. */ - - to = prefix_roota(dest, "/dev/console"); - r = touch(to); - if (r < 0) - return log_error_errno(r, "touch() for /dev/console failed: %m"); - - if (mount(console, to, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Bind mount for /dev/console failed: %m"); - - return 0; -} - -static int setup_kmsg(const char *dest, int kmsg_socket) { - const char *from, *to; - _cleanup_umask_ mode_t u; - int fd, r; - - assert(kmsg_socket >= 0); - - u = umask(0000); - - /* We create the kmsg FIFO as /run/kmsg, but immediately - * delete it after bind mounting it to /proc/kmsg. While FIFOs - * on the reading side behave very similar to /proc/kmsg, - * their writing side behaves differently from /dev/kmsg in - * that writing blocks when nothing is reading. In order to - * avoid any problems with containers deadlocking due to this - * we simply make /dev/kmsg unavailable to the container. */ - from = prefix_roota(dest, "/run/kmsg"); - to = prefix_roota(dest, "/proc/kmsg"); - - if (mkfifo(from, 0600) < 0) - return log_error_errno(errno, "mkfifo() for /run/kmsg failed: %m"); - if (mount(from, to, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Bind mount for /proc/kmsg failed: %m"); - - fd = open(from, O_RDWR|O_NDELAY|O_CLOEXEC); - if (fd < 0) - return log_error_errno(errno, "Failed to open fifo: %m"); - - /* Store away the fd in the socket, so that it stays open as - * long as we run the child */ - r = send_one_fd(kmsg_socket, fd, 0); - safe_close(fd); - - if (r < 0) - return log_error_errno(r, "Failed to send FIFO fd: %m"); - - /* And now make the FIFO unavailable as /run/kmsg... */ - (void) unlink(from); - - return 0; -} - -static int on_address_change(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { - union in_addr_union *exposed = userdata; - - assert(rtnl); - assert(m); - assert(exposed); - - expose_port_execute(rtnl, arg_expose_ports, exposed); - return 0; -} - -static int setup_hostname(void) { - - if (arg_share_system) - return 0; - - if (sethostname_idempotent(arg_machine) < 0) - return -errno; - - return 0; -} - -static int setup_journal(const char *directory) { - sd_id128_t machine_id, this_id; - _cleanup_free_ char *b = NULL, *d = NULL; - const char *etc_machine_id, *p, *q; - bool try; - char *id; - int r; - - /* Don't link journals in ephemeral mode */ - if (arg_ephemeral) - return 0; - - if (arg_link_journal == LINK_NO) - return 0; - - try = arg_link_journal_try || arg_link_journal == LINK_AUTO; - - etc_machine_id = prefix_roota(directory, "/etc/machine-id"); - - r = read_one_line_file(etc_machine_id, &b); - if (r == -ENOENT && try) - return 0; - else if (r < 0) - return log_error_errno(r, "Failed to read machine ID from %s: %m", etc_machine_id); - - id = strstrip(b); - if (isempty(id) && try) - return 0; - - /* Verify validity */ - r = sd_id128_from_string(id, &machine_id); - if (r < 0) - return log_error_errno(r, "Failed to parse machine ID from %s: %m", etc_machine_id); - - r = sd_id128_get_machine(&this_id); - if (r < 0) - return log_error_errno(r, "Failed to retrieve machine ID: %m"); - - if (sd_id128_equal(machine_id, this_id)) { - log_full(try ? LOG_WARNING : LOG_ERR, - "Host and machine ids are equal (%s): refusing to link journals", id); - if (try) - return 0; - return -EEXIST; - } - - r = userns_mkdir(directory, "/var", 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /var: %m"); - - r = userns_mkdir(directory, "/var/log", 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /var/log: %m"); - - r = userns_mkdir(directory, "/var/log/journal", 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /var/log/journal: %m"); - - p = strjoina("/var/log/journal/", id); - q = prefix_roota(directory, p); - - if (path_is_mount_point(p, 0) > 0) { - if (try) - return 0; - - log_error("%s: already a mount point, refusing to use for journal", p); - return -EEXIST; - } - - if (path_is_mount_point(q, 0) > 0) { - if (try) - return 0; - - log_error("%s: already a mount point, refusing to use for journal", q); - return -EEXIST; - } - - r = readlink_and_make_absolute(p, &d); - if (r >= 0) { - if ((arg_link_journal == LINK_GUEST || - arg_link_journal == LINK_AUTO) && - path_equal(d, q)) { - - r = userns_mkdir(directory, p, 0755, 0, 0); - if (r < 0) - log_warning_errno(r, "Failed to create directory %s: %m", q); - return 0; - } - - if (unlink(p) < 0) - return log_error_errno(errno, "Failed to remove symlink %s: %m", p); - } else if (r == -EINVAL) { - - if (arg_link_journal == LINK_GUEST && - rmdir(p) < 0) { - - if (errno == ENOTDIR) { - log_error("%s already exists and is neither a symlink nor a directory", p); - return r; - } else - return log_error_errno(errno, "Failed to remove %s: %m", p); - } - } else if (r != -ENOENT) - return log_error_errno(r, "readlink(%s) failed: %m", p); - - if (arg_link_journal == LINK_GUEST) { - - if (symlink(q, p) < 0) { - if (try) { - log_debug_errno(errno, "Failed to symlink %s to %s, skipping journal setup: %m", q, p); - return 0; - } else - return log_error_errno(errno, "Failed to symlink %s to %s: %m", q, p); - } - - r = userns_mkdir(directory, p, 0755, 0, 0); - if (r < 0) - log_warning_errno(r, "Failed to create directory %s: %m", q); - return 0; - } - - if (arg_link_journal == LINK_HOST) { - /* don't create parents here -- if the host doesn't have - * permanent journal set up, don't force it here */ - - if (mkdir(p, 0755) < 0 && errno != EEXIST) { - if (try) { - log_debug_errno(errno, "Failed to create %s, skipping journal setup: %m", p); - return 0; - } else - return log_error_errno(errno, "Failed to create %s: %m", p); - } - - } else if (access(p, F_OK) < 0) - return 0; - - if (dir_is_empty(q) == 0) - log_warning("%s is not empty, proceeding anyway.", q); - - r = userns_mkdir(directory, p, 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create %s: %m", q); - - if (mount(p, q, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to bind mount journal from host into guest: %m"); - - return 0; -} - -static int drop_capabilities(void) { - return capability_bounding_set_drop(arg_retain, false); -} - -static int reset_audit_loginuid(void) { - _cleanup_free_ char *p = NULL; - int r; - - if (arg_share_system) - return 0; - - r = read_one_line_file("/proc/self/loginuid", &p); - if (r == -ENOENT) - return 0; - if (r < 0) - return log_error_errno(r, "Failed to read /proc/self/loginuid: %m"); - - /* Already reset? */ - if (streq(p, "4294967295")) - return 0; - - r = write_string_file("/proc/self/loginuid", "4294967295", 0); - if (r < 0) { - log_error_errno(r, - "Failed to reset audit login UID. This probably means that your kernel is too\n" - "old and you have audit enabled. Note that the auditing subsystem is known to\n" - "be incompatible with containers on old kernels. Please make sure to upgrade\n" - "your kernel or to off auditing with 'audit=0' on the kernel command line before\n" - "using systemd-nspawn. Sleeping for 5s... (%m)"); - - sleep(5); - } - - return 0; -} - -static int setup_seccomp(void) { - -#ifdef HAVE_SECCOMP - static const struct { - 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) }, - }; - - scmp_filter_ctx seccomp; - unsigned i; - int r; - - seccomp = seccomp_init(SCMP_ACT_ALLOW); - if (!seccomp) - return log_oom(); - - r = seccomp_add_secondary_archs(seccomp); - if (r < 0) { - log_error_errno(r, "Failed to add secondary archs to seccomp filter: %m"); - goto finish; - } - - for (i = 0; i < ELEMENTSOF(blacklist); i++) { - if (arg_retain & (1ULL << blacklist[i].capability)) - continue; - - r = seccomp_rule_add(seccomp, 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"); - goto finish; - } - } - - - /* - Audit is broken in containers, much of the userspace audit - hookup will fail if running inside a container. We don't - care and just turn off creation of audit sockets. - - This will make socket(AF_NETLINK, *, NETLINK_AUDIT) fail - with EAFNOSUPPORT which audit userspace uses as indication - that audit is disabled in the kernel. - */ - - r = seccomp_rule_add( - seccomp, - SCMP_ACT_ERRNO(EAFNOSUPPORT), - SCMP_SYS(socket), - 2, - SCMP_A0(SCMP_CMP_EQ, AF_NETLINK), - SCMP_A2(SCMP_CMP_EQ, NETLINK_AUDIT)); - if (r < 0) { - log_error_errno(r, "Failed to add audit seccomp rule: %m"); - goto finish; - } - - r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); - if (r < 0) { - log_error_errno(r, "Failed to unset NO_NEW_PRIVS: %m"); - goto finish; - } - - r = seccomp_load(seccomp); - if (r == -EINVAL) { - log_debug_errno(r, "Kernel is probably not configured with CONFIG_SECCOMP. Disabling seccomp audit filter: %m"); - r = 0; - goto finish; - } - if (r < 0) { - log_error_errno(r, "Failed to install seccomp audit filter: %m"); - goto finish; - } - -finish: - seccomp_release(seccomp); - return r; -#else - return 0; -#endif - -} - -static int setup_propagate(const char *root) { - const char *p, *q; - int r; - - (void) mkdir_p("/run/systemd/nspawn/", 0755); - (void) mkdir_p("/run/systemd/nspawn/propagate", 0600); - p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); - (void) mkdir_p(p, 0600); - - r = userns_mkdir(root, "/run/systemd", 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /run/systemd: %m"); - - r = userns_mkdir(root, "/run/systemd/nspawn", 0755, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /run/systemd/nspawn: %m"); - - r = userns_mkdir(root, "/run/systemd/nspawn/incoming", 0600, 0, 0); - if (r < 0) - return log_error_errno(r, "Failed to create /run/systemd/nspawn/incoming: %m"); - - q = prefix_roota(root, "/run/systemd/nspawn/incoming"); - if (mount(p, q, NULL, MS_BIND, NULL) < 0) - return log_error_errno(errno, "Failed to install propagation bind mount."); - - if (mount(NULL, q, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) - return log_error_errno(errno, "Failed to make propagation mount read-only"); - - return 0; -} - -static int setup_image(char **device_path, int *loop_nr) { - struct loop_info64 info = { - .lo_flags = LO_FLAGS_AUTOCLEAR|LO_FLAGS_PARTSCAN - }; - _cleanup_close_ int fd = -1, control = -1, loop = -1; - _cleanup_free_ char* loopdev = NULL; - struct stat st; - int r, nr; - - assert(device_path); - assert(loop_nr); - assert(arg_image); - - fd = open(arg_image, O_CLOEXEC|(arg_read_only ? O_RDONLY : O_RDWR)|O_NONBLOCK|O_NOCTTY); - if (fd < 0) - return log_error_errno(errno, "Failed to open %s: %m", arg_image); - - if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Failed to stat %s: %m", arg_image); - - if (S_ISBLK(st.st_mode)) { - char *p; - - p = strdup(arg_image); - if (!p) - return log_oom(); - - *device_path = p; - - *loop_nr = -1; - - r = fd; - fd = -1; - - return r; - } - - if (!S_ISREG(st.st_mode)) { - log_error("%s is not a regular file or block device.", arg_image); - return -EINVAL; - } - - control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); - if (control < 0) - return log_error_errno(errno, "Failed to open /dev/loop-control: %m"); - - nr = ioctl(control, LOOP_CTL_GET_FREE); - if (nr < 0) - return log_error_errno(errno, "Failed to allocate loop device: %m"); - - if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) - return log_oom(); - - loop = open(loopdev, O_CLOEXEC|(arg_read_only ? O_RDONLY : O_RDWR)|O_NONBLOCK|O_NOCTTY); - if (loop < 0) - return log_error_errno(errno, "Failed to open loop device %s: %m", loopdev); - - if (ioctl(loop, LOOP_SET_FD, fd) < 0) - return log_error_errno(errno, "Failed to set loopback file descriptor on %s: %m", loopdev); - - if (arg_read_only) - info.lo_flags |= LO_FLAGS_READ_ONLY; - - if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) - return log_error_errno(errno, "Failed to set loopback settings on %s: %m", loopdev); - - *device_path = loopdev; - loopdev = NULL; - - *loop_nr = nr; - - r = loop; - loop = -1; - - return r; -} - -#define PARTITION_TABLE_BLURB \ - "Note that the disk image needs to either contain only a single MBR partition of\n" \ - "type 0x83 that is marked bootable, or a single GPT partition of type " \ - "0FC63DAF-8483-4772-8E79-3D69D8477DE4 or follow\n" \ - " http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/\n" \ - "to be bootable with systemd-nspawn." - -static int dissect_image( - int fd, - char **root_device, bool *root_device_rw, - char **home_device, bool *home_device_rw, - char **srv_device, bool *srv_device_rw, - bool *secondary) { - -#ifdef HAVE_BLKID - int home_nr = -1, srv_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_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; - _cleanup_udev_unref_ struct udev *udev = NULL; - struct udev_list_entry *first, *item; - bool home_rw = true, root_rw = true, secondary_root_rw = true, srv_rw = true, generic_rw = true; - bool is_gpt, is_mbr, multiple_generic = false; - const char *pttype = NULL; - blkid_partlist pl; - struct stat st; - unsigned i; - int r; - - assert(fd >= 0); - assert(root_device); - assert(home_device); - assert(srv_device); - assert(secondary); - assert(arg_image); - - b = blkid_new_probe(); - if (!b) - return log_oom(); - - errno = 0; - r = blkid_probe_set_device(b, fd, 0, 0); - if (r != 0) { - if (errno == 0) - return log_oom(); - - return log_error_errno(errno, "Failed to set device on blkid probe: %m"); - } - - blkid_probe_enable_partitions(b, 1); - blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); - - errno = 0; - r = blkid_do_safeprobe(b); - if (r == -2 || r == 1) { - log_error("Failed to identify any partition table on\n" - " %s\n" - PARTITION_TABLE_BLURB, arg_image); - return -EINVAL; - } else if (r != 0) { - if (errno == 0) - errno = EIO; - return log_error_errno(errno, "Failed to probe: %m"); - } - - (void) blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL); - - is_gpt = streq_ptr(pttype, "gpt"); - is_mbr = streq_ptr(pttype, "dos"); - - if (!is_gpt && !is_mbr) { - log_error("No GPT or MBR partition table discovered on\n" - " %s\n" - PARTITION_TABLE_BLURB, arg_image); - return -EINVAL; - } - - errno = 0; - pl = blkid_probe_get_partitions(b); - if (!pl) { - if (errno == 0) - return log_oom(); - - log_error("Failed to list partitions of %s", arg_image); - return -errno; - } - - udev = udev_new(); - if (!udev) - return log_oom(); - - if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Failed to stat block device: %m"); - - d = udev_device_new_from_devnum(udev, 'b', st.st_rdev); - if (!d) - return log_oom(); - - for (i = 0;; i++) { - int n, m; - - if (i >= 10) { - log_error("Kernel partitions never appeared."); - return -ENXIO; - } - - e = udev_enumerate_new(udev); - if (!e) - return log_oom(); - - r = udev_enumerate_add_match_parent(e, d); - if (r < 0) - return log_oom(); - - r = udev_enumerate_scan_devices(e); - if (r < 0) - return log_error_errno(r, "Failed to scan for partition devices of %s: %m", arg_image); - - /* Count the partitions enumerated by the kernel */ - n = 0; - first = udev_enumerate_get_list_entry(e); - udev_list_entry_foreach(item, first) - n++; - - /* Count the partitions enumerated by blkid */ - m = blkid_partlist_numof_partitions(pl); - if (n == m + 1) - break; - if (n > m + 1) { - log_error("blkid and kernel partition list do not match."); - return -EIO; - } - if (n < m + 1) { - unsigned j; - - /* The kernel has probed fewer partitions than - * blkid? Maybe the kernel prober is still - * running or it got EBUSY because udev - * already opened the device. Let's reprobe - * the device, which is a synchronous call - * that waits until probing is complete. */ - - for (j = 0; j < 20; j++) { - - r = ioctl(fd, BLKRRPART, 0); - if (r < 0) - r = -errno; - if (r >= 0 || r != -EBUSY) - break; - - /* If something else has the device - * open, such as an udev rule, the - * ioctl will return EBUSY. Since - * there's no way to wait until it - * isn't busy anymore, let's just wait - * a bit, and try again. - * - * This is really something they - * should fix in the kernel! */ - - usleep(50 * USEC_PER_MSEC); - } - - if (r < 0) - return log_error_errno(r, "Failed to reread partition table: %m"); - } - - e = udev_enumerate_unref(e); - } - - first = udev_enumerate_get_list_entry(e); - udev_list_entry_foreach(item, first) { - _cleanup_udev_device_unref_ struct udev_device *q; - const char *node; - unsigned long long flags; - blkid_partition pp; - dev_t qn; - int nr; - - errno = 0; - q = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); - if (!q) { - if (!errno) - errno = ENOMEM; - - return log_error_errno(errno, "Failed to get partition device of %s: %m", arg_image); - } - - qn = udev_device_get_devnum(q); - if (major(qn) == 0) - continue; - - if (st.st_rdev == qn) - continue; - - node = udev_device_get_devnode(q); - if (!node) - continue; - - pp = blkid_partlist_devno_to_partition(pl, qn); - if (!pp) - continue; - - flags = blkid_partition_get_flags(pp); - - nr = blkid_partition_get_partno(pp); - if (nr < 0) - continue; - - if (is_gpt) { - sd_id128_t type_id; - const char *stype; - - if (flags & GPT_FLAG_NO_AUTO) - continue; - - stype = blkid_partition_get_type_string(pp); - if (!stype) - continue; - - if (sd_id128_from_string(stype, &type_id) < 0) - continue; - - if (sd_id128_equal(type_id, GPT_HOME)) { - - if (home && nr >= home_nr) - continue; - - home_nr = nr; - home_rw = !(flags & GPT_FLAG_READ_ONLY); - - r = free_and_strdup(&home, node); - if (r < 0) - return log_oom(); - - } else if (sd_id128_equal(type_id, GPT_SRV)) { - - if (srv && nr >= srv_nr) - continue; - - srv_nr = nr; - srv_rw = !(flags & GPT_FLAG_READ_ONLY); - - r = free_and_strdup(&srv, node); - if (r < 0) - return log_oom(); - } -#ifdef GPT_ROOT_NATIVE - else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) { - - if (root && nr >= root_nr) - continue; - - root_nr = nr; - root_rw = !(flags & GPT_FLAG_READ_ONLY); - - r = free_and_strdup(&root, node); - if (r < 0) - return log_oom(); - } -#endif -#ifdef GPT_ROOT_SECONDARY - else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) { - - if (secondary_root && nr >= secondary_root_nr) - continue; - - secondary_root_nr = nr; - secondary_root_rw = !(flags & GPT_FLAG_READ_ONLY); - - r = free_and_strdup(&secondary_root, node); - if (r < 0) - return log_oom(); - } -#endif - else if (sd_id128_equal(type_id, GPT_LINUX_GENERIC)) { - - if (generic) - multiple_generic = true; - else { - generic_rw = !(flags & GPT_FLAG_READ_ONLY); - - r = free_and_strdup(&generic, node); - if (r < 0) - return log_oom(); - } - } - - } else if (is_mbr) { - int type; - - if (flags != 0x80) /* Bootable flag */ - continue; - - type = blkid_partition_get_type(pp); - if (type != 0x83) /* Linux partition */ - continue; - - if (generic) - multiple_generic = true; - else { - generic_rw = true; - - r = free_and_strdup(&root, node); - if (r < 0) - return log_oom(); - } - } - } - - if (root) { - *root_device = root; - root = NULL; - - *root_device_rw = root_rw; - *secondary = false; - } else if (secondary_root) { - *root_device = secondary_root; - secondary_root = NULL; - - *root_device_rw = secondary_root_rw; - *secondary = true; - } else if (generic) { - - /* There were no partitions with precise meanings - * around, but we found generic partitions. In this - * case, if there's only one, we can go ahead and boot - * it, otherwise we bail out, because we really cannot - * make any sense of it. */ - - if (multiple_generic) { - log_error("Identified multiple bootable Linux partitions on\n" - " %s\n" - PARTITION_TABLE_BLURB, arg_image); - return -EINVAL; - } - - *root_device = generic; - generic = NULL; - - *root_device_rw = generic_rw; - *secondary = false; - } else { - log_error("Failed to identify root partition in disk image\n" - " %s\n" - PARTITION_TABLE_BLURB, arg_image); - return -EINVAL; - } - - if (home) { - *home_device = home; - home = NULL; - - *home_device_rw = home_rw; - } - - if (srv) { - *srv_device = srv; - srv = NULL; - - *srv_device_rw = srv_rw; - } - - return 0; -#else - log_error("--image= is not supported, compiled without blkid support."); - return -EOPNOTSUPP; -#endif -} - -static int mount_device(const char *what, const char *where, const char *directory, bool rw) { -#ifdef HAVE_BLKID - _cleanup_blkid_free_probe_ blkid_probe b = NULL; - const char *fstype, *p; - int r; - - assert(what); - assert(where); - - if (arg_read_only) - rw = false; - - if (directory) - p = strjoina(where, directory); - else - p = where; - - 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 for %s: %m", what); - } - - blkid_probe_enable_superblocks(b, 1); - blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); - - errno = 0; - r = blkid_do_safeprobe(b); - if (r == -1 || r == 1) { - log_error("Cannot determine file system type of %s", what); - return -EINVAL; - } else if (r != 0) { - if (errno == 0) - errno = EIO; - return log_error_errno(errno, "Failed to probe %s: %m", what); - } - - errno = 0; - if (blkid_probe_lookup_value(b, "TYPE", &fstype, NULL) < 0) { - if (errno == 0) - errno = EINVAL; - log_error("Failed to determine file system type of %s", what); - return -errno; - } - - if (streq(fstype, "crypto_LUKS")) { - log_error("nspawn currently does not support LUKS disk images."); - return -EOPNOTSUPP; - } - - if (mount(what, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), NULL) < 0) - return log_error_errno(errno, "Failed to mount %s: %m", what); - - return 0; -#else - log_error("--image= is not supported, compiled without blkid support."); - return -EOPNOTSUPP; -#endif -} - -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) { - int r; - - assert(where); - - if (root_device) { - r = mount_device(root_device, arg_directory, NULL, root_device_rw); - if (r < 0) - return log_error_errno(r, "Failed to mount root directory: %m"); - } - - if (home_device) { - r = mount_device(home_device, arg_directory, "/home", home_device_rw); - if (r < 0) - return log_error_errno(r, "Failed to mount home directory: %m"); - } - - if (srv_device) { - r = mount_device(srv_device, arg_directory, "/srv", srv_device_rw); - if (r < 0) - return log_error_errno(r, "Failed to mount server data directory: %m"); - } - - return 0; -} - -static void loop_remove(int nr, int *image_fd) { - _cleanup_close_ int control = -1; - int r; - - if (nr < 0) - return; - - if (image_fd && *image_fd >= 0) { - r = ioctl(*image_fd, LOOP_CLR_FD); - if (r < 0) - log_debug_errno(errno, "Failed to close loop image: %m"); - *image_fd = safe_close(*image_fd); - } - - control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); - if (control < 0) { - log_warning_errno(errno, "Failed to open /dev/loop-control: %m"); - return; - } - - r = ioctl(control, LOOP_CTL_REMOVE, nr); - if (r < 0) - log_debug_errno(errno, "Failed to remove loop %d: %m", nr); -} - -/* - * Return values: - * < 0 : wait_for_terminate() failed to get the state of the - * container, the container was terminated by a signal, or - * failed for an unknown reason. No change is made to the - * container argument. - * > 0 : The program executed in the container terminated with an - * error. The exit code of the program executed in the - * container is returned. The container argument has been set - * to CONTAINER_TERMINATED. - * 0 : The container is being rebooted, has been shut down or exited - * successfully. The container argument has been set to either - * CONTAINER_TERMINATED or CONTAINER_REBOOTED. - * - * That is, success is indicated by a return value of zero, and an - * error is indicated by a non-zero value. - */ -static int wait_for_container(pid_t pid, ContainerStatus *container) { - siginfo_t status; - int r; - - r = wait_for_terminate(pid, &status); - if (r < 0) - return log_warning_errno(r, "Failed to wait for container: %m"); - - switch (status.si_code) { - - case CLD_EXITED: - if (status.si_status == 0) { - log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s exited successfully.", arg_machine); - - } else - log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s failed with error code %i.", arg_machine, status.si_status); - - *container = CONTAINER_TERMINATED; - return status.si_status; - - case CLD_KILLED: - if (status.si_status == SIGINT) { - - log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s has been shut down.", arg_machine); - *container = CONTAINER_TERMINATED; - return 0; - - } else if (status.si_status == SIGHUP) { - - log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s is being rebooted.", arg_machine); - *container = CONTAINER_REBOOTED; - return 0; - } - - /* CLD_KILLED fallthrough */ - - case CLD_DUMPED: - log_error("Container %s terminated by signal %s.", arg_machine, signal_to_string(status.si_status)); - return -EIO; - - default: - log_error("Container %s failed due to unknown reason.", arg_machine); - return -EIO; - } - - return r; -} - -static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - pid_t pid; - - pid = PTR_TO_PID(userdata); - if (pid > 0) { - if (kill(pid, arg_kill_signal) >= 0) { - log_info("Trying to halt container. Send SIGTERM again to trigger immediate termination."); - sd_event_source_set_userdata(s, NULL); - return 0; - } - } - - sd_event_exit(sd_event_source_get_event(s), 0); - return 0; -} - -static int determine_names(void) { - int r; - - if (arg_template && !arg_directory && arg_machine) { - - /* If --template= was specified then we should not - * search for a machine, but instead create a new one - * in /var/lib/machine. */ - - arg_directory = strjoin("/var/lib/machines/", arg_machine, NULL); - if (!arg_directory) - return log_oom(); - } - - if (!arg_image && !arg_directory) { - if (arg_machine) { - _cleanup_(image_unrefp) Image *i = NULL; - - r = image_find(arg_machine, &i); - if (r < 0) - return log_error_errno(r, "Failed to find image for machine '%s': %m", arg_machine); - else if (r == 0) { - log_error("No image for machine '%s': %m", arg_machine); - return -ENOENT; - } - - if (i->type == IMAGE_RAW) - r = free_and_strdup(&arg_image, i->path); - else - r = free_and_strdup(&arg_directory, i->path); - if (r < 0) - return log_error_errno(r, "Invalid image directory: %m"); - - if (!arg_ephemeral) - arg_read_only = arg_read_only || i->read_only; - } else - arg_directory = get_current_dir_name(); - - if (!arg_directory && !arg_machine) { - log_error("Failed to determine path, please use -D or -i."); - return -EINVAL; - } - } - - if (!arg_machine) { - if (arg_directory && path_equal(arg_directory, "/")) - arg_machine = gethostname_malloc(); - else - arg_machine = strdup(basename(arg_image ?: arg_directory)); - - if (!arg_machine) - return log_oom(); - - hostname_cleanup(arg_machine); - if (!machine_name_is_valid(arg_machine)) { - log_error("Failed to determine machine name automatically, please use -M."); - return -EINVAL; - } - - if (arg_ephemeral) { - char *b; - - /* Add a random suffix when this is an - * ephemeral machine, so that we can run many - * instances at once without manually having - * to specify -M each time. */ - - if (asprintf(&b, "%s-%016" PRIx64, arg_machine, random_u64()) < 0) - return log_oom(); - - free(arg_machine); - arg_machine = b; - } - } - - return 0; -} - -static int determine_uid_shift(const char *directory) { - int r; - - if (!arg_userns) { - arg_uid_shift = 0; - return 0; - } - - if (arg_uid_shift == UID_INVALID) { - struct stat st; - - r = stat(directory, &st); - if (r < 0) - return log_error_errno(errno, "Failed to determine UID base of %s: %m", directory); - - arg_uid_shift = st.st_uid & UINT32_C(0xffff0000); - - if (arg_uid_shift != (st.st_gid & UINT32_C(0xffff0000))) { - log_error("UID and GID base of %s don't match.", directory); - return -EINVAL; - } - - arg_uid_range = UINT32_C(0x10000); - } - - if (arg_uid_shift > (uid_t) -1 - arg_uid_range) { - log_error("UID base too high for UID range."); - return -EINVAL; - } - - log_info("Using user namespaces with base " UID_FMT " and range " UID_FMT ".", arg_uid_shift, arg_uid_range); - return 0; -} - -static int inner_child( - Barrier *barrier, - const char *directory, - bool secondary, - int kmsg_socket, - int rtnl_socket, - FDSet *fds) { - - _cleanup_free_ char *home = NULL; - unsigned n_env = 1; - const char *envp[] = { - "PATH=" DEFAULT_PATH_SPLIT_USR, - NULL, /* container */ - NULL, /* TERM */ - NULL, /* HOME */ - NULL, /* USER */ - NULL, /* LOGNAME */ - NULL, /* container_uuid */ - NULL, /* LISTEN_FDS */ - NULL, /* LISTEN_PID */ - NULL - }; - - _cleanup_strv_free_ char **env_use = NULL; - int r; - - assert(barrier); - assert(directory); - assert(kmsg_socket >= 0); - - cg_unified_flush(); - - if (arg_userns) { - /* Tell the parent, that it now can write the UID map. */ - (void) barrier_place(barrier); /* #1 */ - - /* Wait until the parent wrote the UID map */ - if (!barrier_place_and_sync(barrier)) { /* #2 */ - log_error("Parent died too early"); - return -ESRCH; - } - } - - r = mount_all(NULL, arg_userns, true, arg_uid_shift, arg_private_network, arg_uid_range, arg_selinux_apifs_context); - if (r < 0) - return r; - - r = mount_sysfs(NULL); - if (r < 0) - return r; - - /* Wait until we are cgroup-ified, so that we - * can mount the right cgroup path writable */ - if (!barrier_place_and_sync(barrier)) { /* #3 */ - log_error("Parent died too early"); - return -ESRCH; - } - - r = mount_systemd_cgroup_writable("", arg_unified_cgroup_hierarchy); - if (r < 0) - return r; - - r = reset_uid_gid(); - if (r < 0) - return log_error_errno(r, "Couldn't become new root: %m"); - - r = setup_boot_id(NULL); - if (r < 0) - return r; - - r = setup_kmsg(NULL, kmsg_socket); - if (r < 0) - return r; - kmsg_socket = safe_close(kmsg_socket); - - umask(0022); - - if (setsid() < 0) - return log_error_errno(errno, "setsid() failed: %m"); - - if (arg_private_network) - loopback_setup(); - - if (arg_expose_ports) { - r = expose_port_send_rtnl(rtnl_socket); - if (r < 0) - return r; - rtnl_socket = safe_close(rtnl_socket); - } - - r = drop_capabilities(); - if (r < 0) - return log_error_errno(r, "drop_capabilities() failed: %m"); - - setup_hostname(); - - if (arg_personality != PERSONALITY_INVALID) { - if (personality(arg_personality) < 0) - return log_error_errno(errno, "personality() failed: %m"); - } else if (secondary) { - if (personality(PER_LINUX32) < 0) - return log_error_errno(errno, "personality() failed: %m"); - } - -#ifdef HAVE_SELINUX - if (arg_selinux_context) - if (setexeccon((security_context_t) arg_selinux_context) < 0) - return log_error_errno(errno, "setexeccon(\"%s\") failed: %m", arg_selinux_context); -#endif - - r = change_uid_gid(arg_user, &home); - if (r < 0) - return r; - - /* LXC sets container=lxc, so follow the scheme here */ - envp[n_env++] = strjoina("container=", arg_container_service_name); - - envp[n_env] = strv_find_prefix(environ, "TERM="); - if (envp[n_env]) - n_env ++; - - if ((asprintf((char**)(envp + n_env++), "HOME=%s", home ? home: "/root") < 0) || - (asprintf((char**)(envp + n_env++), "USER=%s", arg_user ? arg_user : "root") < 0) || - (asprintf((char**)(envp + n_env++), "LOGNAME=%s", arg_user ? arg_user : "root") < 0)) - return log_oom(); - - if (!sd_id128_equal(arg_uuid, SD_ID128_NULL)) { - char as_uuid[37]; - - if (asprintf((char**)(envp + n_env++), "container_uuid=%s", id128_format_as_uuid(arg_uuid, as_uuid)) < 0) - return log_oom(); - } - - if (fdset_size(fds) > 0) { - r = fdset_cloexec(fds, false); - if (r < 0) - return log_error_errno(r, "Failed to unset O_CLOEXEC for file descriptors."); - - if ((asprintf((char **)(envp + n_env++), "LISTEN_FDS=%u", fdset_size(fds)) < 0) || - (asprintf((char **)(envp + n_env++), "LISTEN_PID=1") < 0)) - return log_oom(); - } - - env_use = strv_env_merge(2, envp, arg_setenv); - if (!env_use) - return log_oom(); - - /* Let the parent know that we are ready and - * wait until the parent is ready with the - * setup, too... */ - if (!barrier_place_and_sync(barrier)) { /* #4 */ - log_error("Parent died too early"); - return -ESRCH; - } - - if (arg_chdir) - if (chdir(arg_chdir) < 0) - return log_error_errno(errno, "Failed to change to specified working directory %s: %m", arg_chdir); - - if (arg_start_mode == START_PID2) { - r = stub_pid1(); - if (r < 0) - return r; - } - - /* Now, explicitly close the log, so that we - * then can close all remaining fds. Closing - * the log explicitly first has the benefit - * that the logging subsystem knows about it, - * and is thus ready to be reopened should we - * need it again. Note that the other fds - * closed here are at least the locking and - * barrier fds. */ - log_close(); - (void) fdset_close_others(fds); - - if (arg_start_mode == START_BOOT) { - char **a; - size_t m; - - /* Automatically search for the init system */ - - m = 1 + strv_length(arg_parameters); - a = newa(char*, m + 1); - if (strv_isempty(arg_parameters)) - a[1] = NULL; - else - memcpy(a + 1, arg_parameters, m * sizeof(char*)); - - a[0] = (char*) "/usr/lib/systemd/systemd"; - execve(a[0], a, env_use); - - a[0] = (char*) "/lib/systemd/systemd"; - execve(a[0], a, env_use); - - a[0] = (char*) "/sbin/init"; - execve(a[0], a, env_use); - } else if (!strv_isempty(arg_parameters)) - execvpe(arg_parameters[0], arg_parameters, env_use); - else { - if (!arg_chdir) - chdir(home ?: "/root"); - - execle("/bin/bash", "-bash", NULL, env_use); - execle("/bin/sh", "-sh", NULL, env_use); - } - - r = -errno; - (void) log_open(); - return log_error_errno(r, "execv() failed: %m"); -} - -static int outer_child( - Barrier *barrier, - const char *directory, - const char *console, - const char *root_device, bool root_device_rw, - const char *home_device, bool home_device_rw, - const char *srv_device, bool srv_device_rw, - bool interactive, - bool secondary, - int pid_socket, - int kmsg_socket, - int rtnl_socket, - int uid_shift_socket, - FDSet *fds) { - - pid_t pid; - ssize_t l; - int r; - - assert(barrier); - assert(directory); - assert(console); - assert(pid_socket >= 0); - assert(kmsg_socket >= 0); - - cg_unified_flush(); - - if (prctl(PR_SET_PDEATHSIG, SIGKILL) < 0) - return log_error_errno(errno, "PR_SET_PDEATHSIG failed: %m"); - - if (interactive) { - close_nointr(STDIN_FILENO); - close_nointr(STDOUT_FILENO); - close_nointr(STDERR_FILENO); - - r = open_terminal(console, O_RDWR); - if (r != STDIN_FILENO) { - if (r >= 0) { - safe_close(r); - r = -EINVAL; - } - - return log_error_errno(r, "Failed to open console: %m"); - } - - if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO || - dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO) - return log_error_errno(errno, "Failed to duplicate console: %m"); - } - - r = reset_audit_loginuid(); - if (r < 0) - return r; - - /* Mark everything as slave, so that we still - * receive mounts from the real root, but don't - * propagate mounts to the real root. */ - if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) - return log_error_errno(errno, "MS_SLAVE|MS_REC failed: %m"); - - r = mount_devices(directory, - root_device, root_device_rw, - home_device, home_device_rw, - srv_device, srv_device_rw); - if (r < 0) - return r; - - r = determine_uid_shift(directory); - if (r < 0) - return r; - - if (arg_userns) { - l = send(uid_shift_socket, &arg_uid_shift, sizeof(arg_uid_shift), MSG_NOSIGNAL); - if (l < 0) - return log_error_errno(errno, "Failed to send UID shift: %m"); - if (l != sizeof(arg_uid_shift)) { - log_error("Short write while sending UID shift."); - return -EIO; - } - } - - /* Turn directory into bind mount */ - if (mount(directory, directory, NULL, MS_BIND|MS_REC, NULL) < 0) - return log_error_errno(errno, "Failed to make bind mount: %m"); - - r = setup_volatile(directory, arg_volatile_mode, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_context); - if (r < 0) - return r; - - r = setup_volatile_state(directory, arg_volatile_mode, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_context); - if (r < 0) - return r; - - r = base_filesystem_create(directory, arg_uid_shift, (gid_t) arg_uid_shift); - if (r < 0) - return r; - - if (arg_read_only) { - r = bind_remount_recursive(directory, true); - if (r < 0) - return log_error_errno(r, "Failed to make tree read-only: %m"); - } - - r = mount_all(directory, arg_userns, false, arg_private_network, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); - if (r < 0) - return r; - - r = copy_devnodes(directory); - if (r < 0) - return r; - - dev_setup(directory, arg_uid_shift, arg_uid_shift); - - r = setup_pts(directory); - if (r < 0) - return r; - - r = setup_propagate(directory); - if (r < 0) - return r; - - r = setup_dev_console(directory, console); - if (r < 0) - return r; - - r = setup_seccomp(); - if (r < 0) - return r; - - r = setup_timezone(directory); - if (r < 0) - return r; - - r = setup_resolv_conf(directory); - if (r < 0) - return r; - - r = setup_journal(directory); - if (r < 0) - return r; - - r = mount_custom(directory, arg_custom_mounts, arg_n_custom_mounts, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); - if (r < 0) - return r; - - r = mount_cgroups(directory, arg_unified_cgroup_hierarchy, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); - if (r < 0) - return r; - - r = mount_move_root(directory); - if (r < 0) - return log_error_errno(r, "Failed to move root directory: %m"); - - pid = raw_clone(SIGCHLD|CLONE_NEWNS| - (arg_share_system ? 0 : CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS) | - (arg_private_network ? CLONE_NEWNET : 0) | - (arg_userns ? CLONE_NEWUSER : 0), - NULL); - if (pid < 0) - return log_error_errno(errno, "Failed to fork inner child: %m"); - if (pid == 0) { - pid_socket = safe_close(pid_socket); - uid_shift_socket = safe_close(uid_shift_socket); - - /* The inner child has all namespaces that are - * requested, so that we all are owned by the user if - * user namespaces are turned on. */ - - r = inner_child(barrier, directory, secondary, kmsg_socket, rtnl_socket, fds); - if (r < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - l = send(pid_socket, &pid, sizeof(pid), MSG_NOSIGNAL); - if (l < 0) - return log_error_errno(errno, "Failed to send PID: %m"); - if (l != sizeof(pid)) { - log_error("Short write while sending PID."); - return -EIO; - } - - pid_socket = safe_close(pid_socket); - kmsg_socket = safe_close(kmsg_socket); - rtnl_socket = safe_close(rtnl_socket); - - return 0; -} - -static int setup_uid_map(pid_t pid) { - char uid_map[strlen("/proc//uid_map") + DECIMAL_STR_MAX(uid_t) + 1], line[DECIMAL_STR_MAX(uid_t)*3+3+1]; - int r; - - assert(pid > 1); - - xsprintf(uid_map, "/proc/" PID_FMT "/uid_map", pid); - xsprintf(line, UID_FMT " " UID_FMT " " UID_FMT "\n", 0, arg_uid_shift, arg_uid_range); - r = write_string_file(uid_map, line, 0); - if (r < 0) - return log_error_errno(r, "Failed to write UID map: %m"); - - /* We always assign the same UID and GID ranges */ - xsprintf(uid_map, "/proc/" PID_FMT "/gid_map", pid); - r = write_string_file(uid_map, line, 0); - if (r < 0) - return log_error_errno(r, "Failed to write GID map: %m"); - - return 0; -} - -static int load_settings(void) { - _cleanup_(settings_freep) Settings *settings = NULL; - _cleanup_fclose_ FILE *f = NULL; - _cleanup_free_ char *p = NULL; - const char *fn, *i; - int r; - - /* If all settings are masked, there's no point in looking for - * the settings file */ - if ((arg_settings_mask & _SETTINGS_MASK_ALL) == _SETTINGS_MASK_ALL) - return 0; - - fn = strjoina(arg_machine, ".nspawn"); - - /* We first look in the admin's directories in /etc and /run */ - FOREACH_STRING(i, "/etc/systemd/nspawn", "/run/systemd/nspawn") { - _cleanup_free_ char *j = NULL; - - j = strjoin(i, "/", fn, NULL); - if (!j) - return log_oom(); - - f = fopen(j, "re"); - if (f) { - p = j; - j = NULL; - - /* By default, we trust configuration from /etc and /run */ - if (arg_settings_trusted < 0) - arg_settings_trusted = true; - - break; - } - - if (errno != ENOENT) - return log_error_errno(errno, "Failed to open %s: %m", j); - } - - if (!f) { - /* After that, let's look for a file next to the - * actual image we shall boot. */ - - if (arg_image) { - p = file_in_same_dir(arg_image, fn); - if (!p) - return log_oom(); - } else if (arg_directory) { - p = file_in_same_dir(arg_directory, fn); - if (!p) - return log_oom(); - } - - if (p) { - f = fopen(p, "re"); - if (!f && errno != ENOENT) - return log_error_errno(errno, "Failed to open %s: %m", p); - - /* By default, we do not trust configuration from /var/lib/machines */ - if (arg_settings_trusted < 0) - arg_settings_trusted = false; - } - } - - if (!f) - return 0; - - log_debug("Settings are trusted: %s", yes_no(arg_settings_trusted)); - - r = settings_load(f, p, &settings); - if (r < 0) - return r; - - /* Copy over bits from the settings, unless they have been - * explicitly masked by command line switches. */ - - if ((arg_settings_mask & SETTING_START_MODE) == 0 && - settings->start_mode >= 0) { - arg_start_mode = settings->start_mode; - - strv_free(arg_parameters); - arg_parameters = settings->parameters; - settings->parameters = NULL; - } - - if ((arg_settings_mask & SETTING_WORKING_DIRECTORY) == 0 && - settings->working_directory) { - free(arg_chdir); - arg_chdir = settings->working_directory; - settings->working_directory = NULL; - } - - if ((arg_settings_mask & SETTING_ENVIRONMENT) == 0 && - settings->environment) { - strv_free(arg_setenv); - arg_setenv = settings->environment; - settings->environment = NULL; - } - - if ((arg_settings_mask & SETTING_USER) == 0 && - settings->user) { - free(arg_user); - arg_user = settings->user; - settings->user = NULL; - } - - if ((arg_settings_mask & SETTING_CAPABILITY) == 0) { - uint64_t plus; - - plus = settings->capability; - if (settings_private_network(settings)) - plus |= (1ULL << CAP_NET_ADMIN); - - if (!arg_settings_trusted && plus != 0) { - if (settings->capability != 0) - log_warning("Ignoring Capability= setting, file %s is not trusted.", p); - } else - arg_retain |= plus; - - arg_retain &= ~settings->drop_capability; - } - - if ((arg_settings_mask & SETTING_KILL_SIGNAL) == 0 && - settings->kill_signal > 0) - arg_kill_signal = settings->kill_signal; - - if ((arg_settings_mask & SETTING_PERSONALITY) == 0 && - settings->personality != PERSONALITY_INVALID) - arg_personality = settings->personality; - - if ((arg_settings_mask & SETTING_MACHINE_ID) == 0 && - !sd_id128_is_null(settings->machine_id)) { - - if (!arg_settings_trusted) - log_warning("Ignoring MachineID= setting, file %s is not trusted.", p); - else - arg_uuid = settings->machine_id; - } - - if ((arg_settings_mask & SETTING_READ_ONLY) == 0 && - settings->read_only >= 0) - arg_read_only = settings->read_only; - - if ((arg_settings_mask & SETTING_VOLATILE_MODE) == 0 && - settings->volatile_mode != _VOLATILE_MODE_INVALID) - arg_volatile_mode = settings->volatile_mode; - - if ((arg_settings_mask & SETTING_CUSTOM_MOUNTS) == 0 && - settings->n_custom_mounts > 0) { - - if (!arg_settings_trusted) - log_warning("Ignoring TemporaryFileSystem=, Bind= and BindReadOnly= settings, file %s is not trusted.", p); - else { - custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts); - arg_custom_mounts = settings->custom_mounts; - arg_n_custom_mounts = settings->n_custom_mounts; - - settings->custom_mounts = NULL; - settings->n_custom_mounts = 0; - } - } - - if ((arg_settings_mask & SETTING_NETWORK) == 0 && - (settings->private_network >= 0 || - settings->network_veth >= 0 || - settings->network_bridge || - settings->network_interfaces || - settings->network_macvlan || - settings->network_ipvlan || - settings->network_veth_extra)) { - - if (!arg_settings_trusted) - log_warning("Ignoring network settings, file %s is not trusted.", p); - else { - arg_network_veth = settings_network_veth(settings); - arg_private_network = settings_private_network(settings); - - strv_free(arg_network_interfaces); - arg_network_interfaces = settings->network_interfaces; - settings->network_interfaces = NULL; - - strv_free(arg_network_macvlan); - arg_network_macvlan = settings->network_macvlan; - settings->network_macvlan = NULL; - - strv_free(arg_network_ipvlan); - arg_network_ipvlan = settings->network_ipvlan; - settings->network_ipvlan = NULL; - - strv_free(arg_network_veth_extra); - arg_network_veth_extra = settings->network_veth_extra; - settings->network_veth_extra = NULL; - - free(arg_network_bridge); - arg_network_bridge = settings->network_bridge; - settings->network_bridge = NULL; - } - } - - if ((arg_settings_mask & SETTING_EXPOSE_PORTS) == 0 && - settings->expose_ports) { - - if (!arg_settings_trusted) - log_warning("Ignoring Port= setting, file %s is not trusted.", p); - else { - expose_port_free_all(arg_expose_ports); - arg_expose_ports = settings->expose_ports; - settings->expose_ports = NULL; - } - } - - 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; - 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; - int r, n_fd_passed, loop_nr = -1; - char veth_name[IFNAMSIZ]; - bool secondary = false, remove_subvol = false; - sigset_t mask_chld; - pid_t pid = 0; - int ret = EXIT_SUCCESS; - union in_addr_union exposed = {}; - _cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT; - bool interactive; - - log_parse_environment(); - log_open(); - - /* Make sure rename_process() in the stub init process can work */ - saved_argv = argv; - saved_argc = argc; - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (geteuid() != 0) { - log_error("Need to be root."); - r = -EPERM; - goto finish; - } - r = determine_names(); - if (r < 0) - goto finish; - - r = load_settings(); - if (r < 0) - goto finish; - - r = verify_arguments(); - if (r < 0) - goto finish; - - n_fd_passed = sd_listen_fds(false); - if (n_fd_passed > 0) { - r = fdset_new_listen_fds(&fds, false); - if (r < 0) { - log_error_errno(r, "Failed to collect file descriptors: %m"); - goto finish; - } - } - - if (arg_directory) { - assert(!arg_image); - - if (path_equal(arg_directory, "/") && !arg_ephemeral) { - log_error("Spawning container on root directory is not supported. Consider using --ephemeral."); - r = -EINVAL; - goto finish; - } - - if (arg_ephemeral) { - _cleanup_free_ char *np = NULL; - - /* If the specified path is a mount point we - * generate the new snapshot immediately - * inside it under a random name. However if - * the specified is not a mount point we - * create the new snapshot in the parent - * directory, just next to it. */ - r = path_is_mount_point(arg_directory, 0); - if (r < 0) { - log_error_errno(r, "Failed to determine whether directory %s is mount point: %m", arg_directory); - goto finish; - } - if (r > 0) - r = tempfn_random_child(arg_directory, "machine.", &np); - else - r = tempfn_random(arg_directory, "machine.", &np); - if (r < 0) { - log_error_errno(r, "Failed to generate name for snapshot: %m"); - goto finish; - } - - r = image_path_lock(np, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); - if (r < 0) { - log_error_errno(r, "Failed to lock %s: %m", np); - goto finish; - } - - r = btrfs_subvol_snapshot(arg_directory, np, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); - if (r < 0) { - log_error_errno(r, "Failed to create snapshot %s from %s: %m", np, arg_directory); - goto finish; - } - - free(arg_directory); - arg_directory = np; - np = NULL; - - remove_subvol = true; - - } else { - r = image_path_lock(arg_directory, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); - if (r == -EBUSY) { - log_error_errno(r, "Directory tree %s is currently busy.", arg_directory); - goto finish; - } - if (r < 0) { - log_error_errno(r, "Failed to lock %s: %m", arg_directory); - return r; - } - - if (arg_template) { - r = btrfs_subvol_snapshot(arg_template, arg_directory, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); - if (r == -EEXIST) { - if (!arg_quiet) - log_info("Directory %s already exists, not populating from template %s.", arg_directory, arg_template); - } else if (r < 0) { - log_error_errno(r, "Couldn't create snapshot %s from %s: %m", arg_directory, arg_template); - goto finish; - } else { - if (!arg_quiet) - log_info("Populated %s from template %s.", arg_directory, arg_template); - } - } - } - - if (arg_start_mode == START_BOOT) { - if (path_is_os_tree(arg_directory) <= 0) { - log_error("Directory %s doesn't look like an OS root directory (os-release file is missing). Refusing.", arg_directory); - r = -EINVAL; - goto finish; - } - } else { - const char *p; - - p = strjoina(arg_directory, "/usr/"); - if (laccess(p, F_OK) < 0) { - log_error("Directory %s doesn't look like it has an OS tree. Refusing.", arg_directory); - r = -EINVAL; - goto finish; - } - } - - } else { - char template[] = "/tmp/nspawn-root-XXXXXX"; - - assert(arg_image); - assert(!arg_template); - - r = image_path_lock(arg_image, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); - if (r == -EBUSY) { - r = log_error_errno(r, "Disk image %s is currently busy.", arg_image); - goto finish; - } - if (r < 0) { - r = log_error_errno(r, "Failed to create image lock: %m"); - goto finish; - } - - if (!mkdtemp(template)) { - log_error_errno(errno, "Failed to create temporary directory: %m"); - r = -errno; - goto finish; - } - - arg_directory = strdup(template); - if (!arg_directory) { - r = log_oom(); - goto finish; - } - - image_fd = setup_image(&device_path, &loop_nr); - if (image_fd < 0) { - r = image_fd; - goto finish; - } - - r = dissect_image(image_fd, - &root_device, &root_device_rw, - &home_device, &home_device_rw, - &srv_device, &srv_device_rw, - &secondary); - if (r < 0) - goto finish; - } - - r = custom_mounts_prepare(); - if (r < 0) - goto finish; - - interactive = - isatty(STDIN_FILENO) > 0 && - isatty(STDOUT_FILENO) > 0; - - master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY); - if (master < 0) { - r = log_error_errno(errno, "Failed to acquire pseudo tty: %m"); - goto finish; - } - - r = ptsname_malloc(master, &console); - if (r < 0) { - r = log_error_errno(r, "Failed to determine tty name: %m"); - goto finish; - } - - if (unlockpt(master) < 0) { - r = log_error_errno(errno, "Failed to unlock tty: %m"); - goto finish; - } - - if (!arg_quiet) - log_info("Spawning container %s on %s.\nPress ^] three times within 1s to kill container.", - arg_machine, arg_image ?: arg_directory); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); - - assert_se(sigemptyset(&mask_chld) == 0); - assert_se(sigaddset(&mask_chld, SIGCHLD) == 0); - - if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) { - r = log_error_errno(errno, "Failed to become subreaper: %m"); - goto finish; - } - - for (;;) { - _cleanup_close_pair_ int kmsg_socket_pair[2] = { -1, -1 }, rtnl_socket_pair[2] = { -1, -1 }, pid_socket_pair[2] = { -1, -1 }, uid_shift_socket_pair[2] = { -1, -1 }; - ContainerStatus container_status; - _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; - static const struct sigaction sa = { - .sa_handler = nop_signal_handler, - .sa_flags = SA_NOCLDSTOP, - }; - int ifi = 0; - ssize_t l; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - char last_char = 0; - - r = barrier_create(&barrier); - if (r < 0) { - log_error_errno(r, "Cannot initialize IPC barrier: %m"); - goto finish; - } - - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, kmsg_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create kmsg socket pair: %m"); - goto finish; - } - - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, rtnl_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create rtnl socket pair: %m"); - goto finish; - } - - if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pid_socket_pair) < 0) { - r = log_error_errno(errno, "Failed to create pid socket pair: %m"); - goto finish; - } - - if (arg_userns) - 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"); - goto finish; - } - - /* Child can be killed before execv(), so handle SIGCHLD - * in order to interrupt parent's blocking calls and - * give it a chance to call wait() and terminate. */ - r = sigprocmask(SIG_UNBLOCK, &mask_chld, NULL); - if (r < 0) { - r = log_error_errno(errno, "Failed to change the signal mask: %m"); - goto finish; - } - - r = sigaction(SIGCHLD, &sa, NULL); - if (r < 0) { - r = log_error_errno(errno, "Failed to install SIGCHLD handler: %m"); - goto finish; - } - - pid = raw_clone(SIGCHLD|CLONE_NEWNS, NULL); - if (pid < 0) { - if (errno == EINVAL) - r = log_error_errno(errno, "clone() failed, do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in): %m"); - else - r = log_error_errno(errno, "clone() failed: %m"); - - goto finish; - } - - if (pid == 0) { - /* The outer child only has a file system namespace. */ - barrier_set_role(&barrier, BARRIER_CHILD); - - master = safe_close(master); - - kmsg_socket_pair[0] = safe_close(kmsg_socket_pair[0]); - rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); - pid_socket_pair[0] = safe_close(pid_socket_pair[0]); - uid_shift_socket_pair[0] = safe_close(uid_shift_socket_pair[0]); - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - r = outer_child(&barrier, - arg_directory, - console, - root_device, root_device_rw, - home_device, home_device_rw, - srv_device, srv_device_rw, - interactive, - secondary, - pid_socket_pair[1], - kmsg_socket_pair[1], - rtnl_socket_pair[1], - uid_shift_socket_pair[1], - fds); - if (r < 0) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - barrier_set_role(&barrier, BARRIER_PARENT); - - fds = fdset_free(fds); - - kmsg_socket_pair[1] = safe_close(kmsg_socket_pair[1]); - rtnl_socket_pair[1] = safe_close(rtnl_socket_pair[1]); - pid_socket_pair[1] = safe_close(pid_socket_pair[1]); - uid_shift_socket_pair[1] = safe_close(uid_shift_socket_pair[1]); - - /* Wait for the outer child. */ - r = wait_for_terminate_and_warn("namespace helper", pid, NULL); - if (r < 0) - goto finish; - if (r != 0) { - r = -EIO; - goto finish; - } - pid = 0; - - /* And now retrieve the PID of the inner child. */ - l = recv(pid_socket_pair[0], &pid, sizeof(pid), 0); - if (l < 0) { - r = log_error_errno(errno, "Failed to read inner child PID: %m"); - goto finish; - } - if (l != sizeof(pid)) { - log_error("Short read while reading inner child PID."); - r = EIO; - goto finish; - } - - log_debug("Init process invoked as PID " PID_FMT, pid); - - if (arg_userns) { - if (!barrier_place_and_sync(&barrier)) { /* #1 */ - log_error("Child died too early."); - r = -ESRCH; - goto finish; - } - - l = recv(uid_shift_socket_pair[0], &arg_uid_shift, sizeof(arg_uid_shift), 0); - if (l < 0) { - r = log_error_errno(errno, "Failed to read UID shift: %m"); - goto finish; - } - if (l != sizeof(arg_uid_shift)) { - log_error("Short read while reading UID shift."); - r = EIO; - goto finish; - } - - r = setup_uid_map(pid); - if (r < 0) - goto finish; - - (void) barrier_place(&barrier); /* #2 */ - } - - if (arg_private_network) { - - r = move_network_interfaces(pid, arg_network_interfaces); - if (r < 0) - goto finish; - - if (arg_network_veth) { - r = setup_veth(arg_machine, pid, veth_name, !!arg_network_bridge); - if (r < 0) - goto finish; - else if (r > 0) - ifi = r; - - if (arg_network_bridge) { - r = setup_bridge(veth_name, arg_network_bridge); - if (r < 0) - goto finish; - if (r > 0) - ifi = r; - } - } - - r = setup_veth_extra(arg_machine, pid, arg_network_veth_extra); - if (r < 0) - goto finish; - - r = setup_macvlan(arg_machine, pid, arg_network_macvlan); - if (r < 0) - goto finish; - - r = setup_ipvlan(arg_machine, pid, arg_network_ipvlan); - if (r < 0) - goto finish; - } - - if (arg_register) { - r = register_machine( - arg_machine, - pid, - arg_directory, - arg_uuid, - ifi, - arg_slice, - arg_custom_mounts, arg_n_custom_mounts, - arg_kill_signal, - arg_property, - arg_keep_unit, - arg_container_service_name); - if (r < 0) - goto finish; - } - - r = sync_cgroup(pid, arg_unified_cgroup_hierarchy); - if (r < 0) - goto finish; - - if (arg_keep_unit) { - r = create_subcgroup(pid, arg_unified_cgroup_hierarchy); - if (r < 0) - goto finish; - } - - r = chown_cgroup(pid, arg_uid_shift); - if (r < 0) - goto finish; - - /* Notify the child that the parent is ready with all - * its setup (including cgroup-ification), and that - * the child can now hand over control to the code to - * run inside the container. */ - (void) barrier_place(&barrier); /* #3 */ - - /* Block SIGCHLD here, before notifying child. - * process_pty() will handle it with the other signals. */ - assert_se(sigprocmask(SIG_BLOCK, &mask_chld, NULL) >= 0); - - /* Reset signal to default */ - r = default_signals(SIGCHLD, -1); - if (r < 0) { - log_error_errno(r, "Failed to reset SIGCHLD: %m"); - 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."); - r = -ESRCH; - goto finish; - } - - 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_kill_signal > 0) { - /* Try to kill the init system on SIGINT or SIGTERM */ - sd_event_add_signal(event, NULL, SIGINT, on_orderly_shutdown, PID_TO_PTR(pid)); - sd_event_add_signal(event, NULL, SIGTERM, on_orderly_shutdown, PID_TO_PTR(pid)); - } else { - /* Immediately exit */ - sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); - sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); - } - - /* simply exit on sigchld */ - sd_event_add_signal(event, NULL, SIGCHLD, NULL, NULL); - - if (arg_expose_ports) { - r = expose_port_watch_rtnl(event, rtnl_socket_pair[0], on_address_change, &exposed, &rtnl); - if (r < 0) - goto finish; - - (void) expose_port_execute(rtnl, arg_expose_ports, &exposed); - } - - rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); - - r = pty_forward_new(event, master, PTY_FORWARD_IGNORE_VHANGUP | (interactive ? 0 : PTY_FORWARD_READ_ONLY), &forward); - if (r < 0) { - log_error_errno(r, "Failed to create PTY forwarder: %m"); - goto finish; - } - - r = sd_event_loop(event); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finish; - } - - pty_forward_get_last_char(forward, &last_char); - - forward = pty_forward_free(forward); - - if (!arg_quiet && last_char != '\n') - putc('\n', stdout); - - /* Kill if it is not dead yet anyway */ - if (arg_register && !arg_keep_unit) - terminate_machine(pid); - - /* Normally redundant, but better safe than sorry */ - kill(pid, SIGKILL); - - r = wait_for_container(pid, &container_status); - pid = 0; - - if (r < 0) - /* We failed to wait for the container, or the - * container exited abnormally */ - goto finish; - else if (r > 0 || container_status == CONTAINER_TERMINATED){ - /* The container exited with a non-zero - * status, or with zero status and no reboot - * was requested. */ - ret = r; - break; - } - - /* CONTAINER_REBOOTED, loop again */ - - if (arg_keep_unit) { - /* Special handling if we are running as a - * service: instead of simply restarting the - * machine we want to restart the entire - * service, so let's inform systemd about this - * with the special exit code 133. The service - * file uses RestartForceExitStatus=133 so - * that this results in a full nspawn - * restart. This is necessary since we might - * have cgroup parameters set we want to have - * flushed out. */ - ret = 133; - r = 0; - break; - } - - expose_port_flush(arg_expose_ports, &exposed); - } - -finish: - sd_notify(false, - "STOPPING=1\n" - "STATUS=Terminating..."); - - if (pid > 0) - kill(pid, SIGKILL); - - /* Try to flush whatever is still queued in the pty */ - if (master >= 0) - (void) copy_bytes(master, STDOUT_FILENO, (uint64_t) -1, false); - - loop_remove(loop_nr, &image_fd); - - if (remove_subvol && arg_directory) { - int k; - - k = btrfs_subvol_remove(arg_directory, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); - if (k < 0) - log_warning_errno(k, "Cannot remove subvolume '%s', ignoring: %m", arg_directory); - } - - if (arg_machine) { - const char *p; - - p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); - (void) rm_rf(p, REMOVE_ROOT); - } - - expose_port_flush(arg_expose_ports, &exposed); - - free(arg_directory); - free(arg_template); - free(arg_image); - free(arg_machine); - free(arg_user); - free(arg_chdir); - strv_free(arg_setenv); - free(arg_network_bridge); - strv_free(arg_network_interfaces); - strv_free(arg_network_macvlan); - strv_free(arg_network_ipvlan); - strv_free(arg_network_veth_extra); - strv_free(arg_parameters); - custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts); - expose_port_free_all(arg_expose_ports); - - return r < 0 ? EXIT_FAILURE : ret; -} diff --git a/src/nss-myhostname/Makefile b/src/nss-myhostname/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/nss-myhostname/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/nss-myhostname/Makefile b/src/nss-myhostname/Makefile new file mode 100644 index 0000000000..57860590c6 --- /dev/null +++ b/src/nss-myhostname/Makefile @@ -0,0 +1,47 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_MYHOSTNAME),) +libnss_myhostname_la_SOURCES = \ + src/nss-myhostname/nss-myhostname.sym \ + src/nss-myhostname/nss-myhostname.c + +libnss_myhostname_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -shrext .so.2 \ + -Wl,--version-script=$(top_srcdir)/src/nss-myhostname/nss-myhostname.sym + +libnss_myhostname_la_LIBADD = \ + libsystemd-internal.la + +lib_LTLIBRARIES += \ + libnss_myhostname.la +endif + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/nss-mymachines/Makefile b/src/nss-mymachines/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/nss-mymachines/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/nss-mymachines/nss-mymachines.c b/src/nss-mymachines/nss-mymachines.c deleted file mode 100644 index 1582d702f8..0000000000 --- a/src/nss-mymachines/nss-mymachines.c +++ /dev/null @@ -1,738 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include - -#include "sd-bus.h" -#include "sd-login.h" - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "hostname-util.h" -#include "in-addr-util.h" -#include "macro.h" -#include "nss-util.h" -#include "signal-util.h" -#include "string-util.h" -#include "user-util.h" -#include "util.h" - -NSS_GETHOSTBYNAME_PROTOTYPES(mymachines); -NSS_GETPW_PROTOTYPES(mymachines); -NSS_GETGR_PROTOTYPES(mymachines); - -#define HOST_UID_LIMIT ((uid_t) UINT32_C(0x10000)) -#define HOST_GID_LIMIT ((gid_t) UINT32_C(0x10000)) - -static int count_addresses(sd_bus_message *m, int af, unsigned *ret) { - unsigned c = 0; - int r; - - assert(m); - assert(ret); - - while ((r = sd_bus_message_enter_container(m, 'r', "iay")) > 0) { - int family; - - r = sd_bus_message_read(m, "i", &family); - if (r < 0) - return r; - - r = sd_bus_message_skip(m, "ay"); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - if (af != AF_UNSPEC && family != af) - continue; - - c ++; - } - if (r < 0) - return r; - - r = sd_bus_message_rewind(m, false); - if (r < 0) - return r; - - *ret = c; - return 0; -} - -enum nss_status _nss_mymachines_gethostbyname4_r( - const char *name, - struct gaih_addrtuple **pat, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp) { - - struct gaih_addrtuple *r_tuple, *r_tuple_first = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ int *ifindices = NULL; - _cleanup_free_ char *class = NULL; - size_t l, ms, idx; - unsigned i = 0, c = 0; - char *r_name; - int n_ifindices, r; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(name); - assert(pat); - assert(buffer); - assert(errnop); - assert(h_errnop); - - r = sd_machine_get_class(name, &class); - if (r < 0) - goto fail; - if (!streq(class, "container")) { - r = -ENOTTY; - goto fail; - } - - n_ifindices = sd_machine_get_ifindices(name, &ifindices); - if (n_ifindices < 0) { - r = n_ifindices; - goto fail; - } - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineAddresses", - NULL, - &reply, - "s", name); - if (r < 0) - goto fail; - - r = sd_bus_message_enter_container(reply, 'a', "(iay)"); - if (r < 0) - goto fail; - - r = count_addresses(reply, AF_UNSPEC, &c); - if (r < 0) - goto fail; - - if (c <= 0) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - l = strlen(name); - ms = ALIGN(l+1) + ALIGN(sizeof(struct gaih_addrtuple)) * c; - if (buflen < ms) { - *errnop = ENOMEM; - *h_errnop = TRY_AGAIN; - return NSS_STATUS_TRYAGAIN; - } - - /* First, append name */ - r_name = buffer; - memcpy(r_name, name, l+1); - idx = ALIGN(l+1); - - /* Second, append addresses */ - r_tuple_first = (struct gaih_addrtuple*) (buffer + idx); - while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { - int family; - const void *a; - size_t sz; - - r = sd_bus_message_read(reply, "i", &family); - if (r < 0) - goto fail; - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - goto fail; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto fail; - - if (!IN_SET(family, AF_INET, AF_INET6)) { - r = -EAFNOSUPPORT; - goto fail; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - r = -EINVAL; - goto fail; - } - - r_tuple = (struct gaih_addrtuple*) (buffer + idx); - r_tuple->next = i == c-1 ? NULL : (struct gaih_addrtuple*) ((char*) r_tuple + ALIGN(sizeof(struct gaih_addrtuple))); - r_tuple->name = r_name; - r_tuple->family = family; - r_tuple->scopeid = n_ifindices == 1 ? ifindices[0] : 0; - memcpy(r_tuple->addr, a, sz); - - idx += ALIGN(sizeof(struct gaih_addrtuple)); - i++; - } - - assert(i == c); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto fail; - - assert(idx == ms); - - if (*pat) - **pat = *r_tuple_first; - else - *pat = r_tuple_first; - - if (ttlp) - *ttlp = 0; - - /* Explicitly reset all error variables */ - *errnop = 0; - *h_errnop = NETDB_SUCCESS; - h_errno = 0; - - return NSS_STATUS_SUCCESS; - -fail: - *errnop = -r; - *h_errnop = NO_DATA; - return NSS_STATUS_UNAVAIL; -} - -enum nss_status _nss_mymachines_gethostbyname3_r( - const char *name, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp, - char **canonp) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message* reply = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ char *class = NULL; - unsigned c = 0, i = 0; - char *r_name, *r_aliases, *r_addr, *r_addr_list; - size_t l, idx, ms, alen; - int r; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(name); - assert(result); - assert(buffer); - assert(errnop); - assert(h_errnop); - - if (af == AF_UNSPEC) - af = AF_INET; - - if (af != AF_INET && af != AF_INET6) { - r = -EAFNOSUPPORT; - goto fail; - } - - r = sd_machine_get_class(name, &class); - if (r < 0) - goto fail; - if (!streq(class, "container")) { - r = -ENOTTY; - goto fail; - } - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "GetMachineAddresses", - NULL, - &reply, - "s", name); - if (r < 0) - goto fail; - - r = sd_bus_message_enter_container(reply, 'a', "(iay)"); - if (r < 0) - goto fail; - - r = count_addresses(reply, af, &c); - if (r < 0) - goto fail; - - if (c <= 0) { - *errnop = ENOENT; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - alen = FAMILY_ADDRESS_SIZE(af); - l = strlen(name); - - ms = ALIGN(l+1) + c * ALIGN(alen) + (c+2) * sizeof(char*); - - if (buflen < ms) { - *errnop = ENOMEM; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_TRYAGAIN; - } - - /* First, append name */ - r_name = buffer; - memcpy(r_name, name, l+1); - idx = ALIGN(l+1); - - /* Second, create aliases array */ - r_aliases = buffer + idx; - ((char**) r_aliases)[0] = NULL; - idx += sizeof(char*); - - /* Third, append addresses */ - r_addr = buffer + idx; - while ((r = sd_bus_message_enter_container(reply, 'r', "iay")) > 0) { - int family; - const void *a; - size_t sz; - - r = sd_bus_message_read(reply, "i", &family); - if (r < 0) - goto fail; - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - goto fail; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto fail; - - if (family != af) - continue; - - if (sz != alen) { - r = -EINVAL; - goto fail; - } - - memcpy(r_addr + i*ALIGN(alen), a, alen); - i++; - } - - assert(i == c); - idx += c * ALIGN(alen); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto fail; - - /* Third, append address pointer array */ - r_addr_list = buffer + idx; - for (i = 0; i < c; i++) - ((char**) r_addr_list)[i] = r_addr + i*ALIGN(alen); - - ((char**) r_addr_list)[i] = NULL; - idx += (c+1) * sizeof(char*); - - assert(idx == ms); - - result->h_name = r_name; - result->h_aliases = (char**) r_aliases; - result->h_addrtype = af; - result->h_length = alen; - result->h_addr_list = (char**) r_addr_list; - - if (ttlp) - *ttlp = 0; - - if (canonp) - *canonp = r_name; - - /* Explicitly reset all error variables */ - *errnop = 0; - *h_errnop = NETDB_SUCCESS; - h_errno = 0; - - return NSS_STATUS_SUCCESS; - -fail: - *errnop = -r; - *h_errnop = NO_DATA; - return NSS_STATUS_UNAVAIL; -} - -NSS_GETHOSTBYNAME_FALLBACKS(mymachines); - -enum nss_status _nss_mymachines_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; - const char *p, *e, *machine; - uint32_t mapped; - uid_t uid; - size_t l; - int r; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(name); - assert(pwd); - - p = startswith(name, "vu-"); - if (!p) - goto not_found; - - e = strrchr(p, '-'); - if (!e || e == p) - goto not_found; - - if (e - p > HOST_NAME_MAX - 1) /* -1 for the last dash */ - goto not_found; - - r = parse_uid(e + 1, &uid); - if (r < 0) - goto not_found; - - machine = strndupa(p, e - p); - if (!machine_name_is_valid(machine)) - goto not_found; - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "MapFromMachineUser", - &error, - &reply, - "su", - machine, (uint32_t) uid); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_USER_MAPPING)) - goto not_found; - - goto fail; - } - - r = sd_bus_message_read(reply, "u", &mapped); - if (r < 0) - goto fail; - - /* Refuse to work if the mapped address is in the host UID range, or if there was no mapping at all. */ - if (mapped < HOST_UID_LIMIT || mapped == uid) - goto not_found; - - 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 = mapped; - pwd->pw_gid = 65534; /* nobody */ - pwd->pw_gecos = buffer; - 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_mymachines_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 *machine, *object; - uint32_t mapped; - int r; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - if (!uid_is_valid(uid)) { - r = -EINVAL; - goto fail; - } - - /* We consider all uids < 65536 host uids */ - if (uid < HOST_UID_LIMIT) - goto not_found; - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "MapToMachineUser", - &error, - &reply, - "u", - (uint32_t) uid); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_USER_MAPPING)) - goto not_found; - - goto fail; - } - - r = sd_bus_message_read(reply, "sou", &machine, &object, &mapped); - if (r < 0) - goto fail; - - if (mapped == uid) - goto not_found; - - if (snprintf(buffer, buflen, "vu-%s-" UID_FMT, machine, (uid_t) mapped) >= (int) buflen) { - *errnop = ENOMEM; - return NSS_STATUS_TRYAGAIN; - } - - pwd->pw_name = buffer; - pwd->pw_uid = uid; - pwd->pw_gid = 65534; /* nobody */ - pwd->pw_gecos = buffer; - 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_mymachines_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; - const char *p, *e, *machine; - uint32_t mapped; - uid_t gid; - size_t l; - int r; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(name); - assert(gr); - - p = startswith(name, "vg-"); - if (!p) - goto not_found; - - e = strrchr(p, '-'); - if (!e || e == p) - goto not_found; - - if (e - p > HOST_NAME_MAX - 1) /* -1 for the last dash */ - goto not_found; - - r = parse_gid(e + 1, &gid); - if (r < 0) - goto not_found; - - machine = strndupa(p, e - p); - if (!machine_name_is_valid(machine)) - goto not_found; - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "MapFromMachineGroup", - &error, - &reply, - "su", - machine, (uint32_t) gid); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_GROUP_MAPPING)) - goto not_found; - - goto fail; - } - - r = sd_bus_message_read(reply, "u", &mapped); - if (r < 0) - goto fail; - - if (mapped < HOST_GID_LIMIT || mapped == gid) - goto not_found; - - 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; - 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_mymachines_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 *machine, *object; - uint32_t mapped; - int r; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - if (!gid_is_valid(gid)) { - r = -EINVAL; - goto fail; - } - - /* We consider all gids < 65536 host gids */ - if (gid < HOST_GID_LIMIT) - goto not_found; - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fail; - - r = sd_bus_call_method(bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "MapToMachineGroup", - &error, - &reply, - "u", - (uint32_t) gid); - if (r < 0) { - if (sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_GROUP_MAPPING)) - goto not_found; - - goto fail; - } - - r = sd_bus_message_read(reply, "sou", &machine, &object, &mapped); - if (r < 0) - goto fail; - - if (mapped == gid) - goto not_found; - - if (buflen < sizeof(char*) + 1) { - *errnop = ENOMEM; - return NSS_STATUS_TRYAGAIN; - } - - memzero(buffer, sizeof(char*)); - if (snprintf(buffer + sizeof(char*), buflen - sizeof(char*), "vg-%s-" GID_FMT, machine, (gid_t) mapped) >= (int) buflen) { - *errnop = ENOMEM; - return NSS_STATUS_TRYAGAIN; - } - - 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-mymachines/nss-mymachines.sym b/src/nss-mymachines/nss-mymachines.sym deleted file mode 100644 index 0728ac3ba7..0000000000 --- a/src/nss-mymachines/nss-mymachines.sym +++ /dev/null @@ -1,21 +0,0 @@ -/*** - 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_mymachines_gethostbyname_r; - _nss_mymachines_gethostbyname2_r; - _nss_mymachines_gethostbyname3_r; - _nss_mymachines_gethostbyname4_r; - _nss_mymachines_getpwnam_r; - _nss_mymachines_getpwuid_r; - _nss_mymachines_getgrnam_r; - _nss_mymachines_getgrgid_r; -local: *; -}; diff --git a/src/nss-resolve/Makefile b/src/nss-resolve/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/nss-resolve/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/nss-resolve/nss-resolve.c b/src/nss-resolve/nss-resolve.c deleted file mode 100644 index 69c0d9bdc1..0000000000 --- a/src/nss-resolve/nss-resolve.c +++ /dev/null @@ -1,704 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "bus-common-errors.h" -#include "in-addr-util.h" -#include "macro.h" -#include "nss-util.h" -#include "string-util.h" -#include "util.h" -#include "signal-util.h" - -NSS_GETHOSTBYNAME_PROTOTYPES(resolve); -NSS_GETHOSTBYADDR_PROTOTYPES(resolve); - -#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) - -typedef void (*voidfunc_t)(void); - -static voidfunc_t find_fallback(const char *module, const char *symbol) { - void *dl; - - /* Try to find a fallback NSS module symbol */ - - dl = dlopen(module, RTLD_LAZY|RTLD_NODELETE); - if (!dl) - return NULL; - - return dlsym(dl, symbol); -} - -static bool bus_error_shall_fallback(sd_bus_error *e) { - return sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN) || - sd_bus_error_has_name(e, SD_BUS_ERROR_NAME_HAS_NO_OWNER) || - sd_bus_error_has_name(e, SD_BUS_ERROR_NO_REPLY) || - sd_bus_error_has_name(e, SD_BUS_ERROR_ACCESS_DENIED); -} - -static int count_addresses(sd_bus_message *m, int af, const char **canonical) { - int c = 0, r; - - assert(m); - assert(canonical); - - r = sd_bus_message_enter_container(m, 'a', "(iiay)"); - if (r < 0) - return r; - - while ((r = sd_bus_message_enter_container(m, 'r', "iiay")) > 0) { - int family, ifindex; - - assert_cc(sizeof(int32_t) == sizeof(int)); - - r = sd_bus_message_read(m, "ii", &ifindex, &family); - if (r < 0) - return r; - - r = sd_bus_message_skip(m, "ay"); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - if (af != AF_UNSPEC && family != af) - continue; - - c ++; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - r = sd_bus_message_read(m, "s", canonical); - if (r < 0) - return r; - - r = sd_bus_message_rewind(m, true); - if (r < 0) - return r; - - return c; -} - -enum nss_status _nss_resolve_gethostbyname4_r( - const char *name, - struct gaih_addrtuple **pat, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp) { - - enum nss_status (*fallback)( - const char *name, - struct gaih_addrtuple **pat, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp); - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - struct gaih_addrtuple *r_tuple, *r_tuple_first = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - const char *canonical = NULL; - size_t l, ms, idx; - char *r_name; - int c, r, i = 0; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(name); - assert(pat); - assert(buffer); - assert(errnop); - assert(h_errnop); - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fallback; - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveHostname"); - if (r < 0) - goto fail; - - r = sd_bus_message_set_auto_start(req, false); - if (r < 0) - goto fail; - - r = sd_bus_message_append(req, "isit", 0, name, AF_UNSPEC, (uint64_t) 0); - if (r < 0) - goto fail; - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN")) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - if (bus_error_shall_fallback(&error)) - goto fallback; - - goto fail; - } - - c = count_addresses(reply, AF_UNSPEC, &canonical); - if (c < 0) { - r = c; - goto fail; - } - if (c == 0) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - if (isempty(canonical)) - canonical = name; - - l = strlen(canonical); - ms = ALIGN(l+1) + ALIGN(sizeof(struct gaih_addrtuple)) * c; - if (buflen < ms) { - *errnop = ENOMEM; - *h_errnop = TRY_AGAIN; - return NSS_STATUS_TRYAGAIN; - } - - /* First, append name */ - r_name = buffer; - memcpy(r_name, canonical, l+1); - idx = ALIGN(l+1); - - /* Second, append addresses */ - r_tuple_first = (struct gaih_addrtuple*) (buffer + idx); - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - goto fail; - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - int family, ifindex; - const void *a; - size_t sz; - - assert_cc(sizeof(int32_t) == sizeof(int)); - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - goto fail; - - if (ifindex < 0) { - r = -EINVAL; - goto fail; - } - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - goto fail; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto fail; - - if (!IN_SET(family, AF_INET, AF_INET6)) - continue; - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - r = -EINVAL; - goto fail; - } - - r_tuple = (struct gaih_addrtuple*) (buffer + idx); - r_tuple->next = i == c-1 ? NULL : (struct gaih_addrtuple*) ((char*) r_tuple + ALIGN(sizeof(struct gaih_addrtuple))); - r_tuple->name = r_name; - r_tuple->family = family; - r_tuple->scopeid = ifindex; - memcpy(r_tuple->addr, a, sz); - - idx += ALIGN(sizeof(struct gaih_addrtuple)); - i++; - } - if (r < 0) - goto fail; - - assert(i == c); - assert(idx == ms); - - if (*pat) - **pat = *r_tuple_first; - else - *pat = r_tuple_first; - - if (ttlp) - *ttlp = 0; - - /* Explicitly reset all error variables */ - *errnop = 0; - *h_errnop = NETDB_SUCCESS; - h_errno = 0; - - return NSS_STATUS_SUCCESS; - -fallback: - fallback = (enum nss_status (*)(const char *name, - struct gaih_addrtuple **pat, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp)) - find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyname4_r"); - - if (fallback) - return fallback(name, pat, buffer, buflen, errnop, h_errnop, ttlp); - -fail: - *errnop = -r; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; -} - -enum nss_status _nss_resolve_gethostbyname3_r( - const char *name, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp, - char **canonp) { - - enum nss_status (*fallback)( - const char *name, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp, - char **canonp); - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char *r_name, *r_aliases, *r_addr, *r_addr_list; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - size_t l, idx, ms, alen; - const char *canonical; - int c, r, i = 0; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(name); - assert(result); - assert(buffer); - assert(errnop); - assert(h_errnop); - - if (af == AF_UNSPEC) - af = AF_INET; - - if (af != AF_INET && af != AF_INET6) { - r = -EAFNOSUPPORT; - goto fail; - } - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fallback; - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveHostname"); - if (r < 0) - goto fail; - - r = sd_bus_message_set_auto_start(req, false); - if (r < 0) - goto fail; - - r = sd_bus_message_append(req, "isit", 0, name, af, (uint64_t) 0); - if (r < 0) - goto fail; - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN")) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - if (bus_error_shall_fallback(&error)) - goto fallback; - - goto fail; - } - - c = count_addresses(reply, af, &canonical); - if (c < 0) { - r = c; - goto fail; - } - if (c == 0) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - if (isempty(canonical)) - canonical = name; - - alen = FAMILY_ADDRESS_SIZE(af); - l = strlen(canonical); - - ms = ALIGN(l+1) + c * ALIGN(alen) + (c+2) * sizeof(char*); - - if (buflen < ms) { - *errnop = ENOMEM; - *h_errnop = TRY_AGAIN; - return NSS_STATUS_TRYAGAIN; - } - - /* First, append name */ - r_name = buffer; - memcpy(r_name, canonical, l+1); - idx = ALIGN(l+1); - - /* Second, create empty aliases array */ - r_aliases = buffer + idx; - ((char**) r_aliases)[0] = NULL; - idx += sizeof(char*); - - /* Third, append addresses */ - r_addr = buffer + idx; - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - goto fail; - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - int ifindex, family; - const void *a; - size_t sz; - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - goto fail; - - if (ifindex < 0) { - r = -EINVAL; - goto fail; - } - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - goto fail; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - goto fail; - - if (family != af) - continue; - - if (sz != alen) { - r = -EINVAL; - goto fail; - } - - memcpy(r_addr + i*ALIGN(alen), a, alen); - i++; - } - if (r < 0) - goto fail; - - assert(i == c); - idx += c * ALIGN(alen); - - /* Fourth, append address pointer array */ - r_addr_list = buffer + idx; - for (i = 0; i < c; i++) - ((char**) r_addr_list)[i] = r_addr + i*ALIGN(alen); - - ((char**) r_addr_list)[i] = NULL; - idx += (c+1) * sizeof(char*); - - assert(idx == ms); - - result->h_name = r_name; - result->h_aliases = (char**) r_aliases; - result->h_addrtype = af; - result->h_length = alen; - result->h_addr_list = (char**) r_addr_list; - - /* Explicitly reset all error variables */ - *errnop = 0; - *h_errnop = NETDB_SUCCESS; - h_errno = 0; - - if (ttlp) - *ttlp = 0; - - if (canonp) - *canonp = r_name; - - return NSS_STATUS_SUCCESS; - -fallback: - fallback = (enum nss_status (*)(const char *name, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp, - char **canonp)) - find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyname3_r"); - if (fallback) - return fallback(name, af, result, buffer, buflen, errnop, h_errnop, ttlp, canonp); - -fail: - *errnop = -r; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; -} - -enum nss_status _nss_resolve_gethostbyaddr2_r( - const void* addr, socklen_t len, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp) { - - enum nss_status (*fallback)( - const void* addr, socklen_t len, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp); - - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char *r_name, *r_aliases, *r_addr, *r_addr_list; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - unsigned c = 0, i = 0; - size_t ms = 0, idx; - const char *n; - int r, ifindex; - - BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); - - assert(addr); - assert(result); - assert(buffer); - assert(errnop); - assert(h_errnop); - - if (!IN_SET(af, AF_INET, AF_INET6)) { - *errnop = EAFNOSUPPORT; - *h_errnop = NO_DATA; - return NSS_STATUS_UNAVAIL; - } - - if (len != FAMILY_ADDRESS_SIZE(af)) { - *errnop = EINVAL; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; - } - - r = sd_bus_open_system(&bus); - if (r < 0) - goto fallback; - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveAddress"); - if (r < 0) - goto fail; - - r = sd_bus_message_set_auto_start(req, false); - if (r < 0) - goto fail; - - r = sd_bus_message_append(req, "ii", 0, af); - if (r < 0) - goto fail; - - r = sd_bus_message_append_array(req, 'y', addr, len); - if (r < 0) - goto fail; - - r = sd_bus_message_append(req, "t", (uint64_t) 0); - if (r < 0) - goto fail; - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN")) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - if (bus_error_shall_fallback(&error)) - goto fallback; - - - *errnop = -r; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; - } - - r = sd_bus_message_enter_container(reply, 'a', "(is)"); - if (r < 0) - goto fail; - - while ((r = sd_bus_message_read(reply, "(is)", &ifindex, &n)) > 0) { - - if (ifindex < 0) { - r = -EINVAL; - goto fail; - } - - c++; - ms += ALIGN(strlen(n) + 1); - } - if (r < 0) - goto fail; - - r = sd_bus_message_rewind(reply, false); - if (r < 0) - return r; - - if (c <= 0) { - *errnop = ESRCH; - *h_errnop = HOST_NOT_FOUND; - return NSS_STATUS_NOTFOUND; - } - - ms += ALIGN(len) + /* the address */ - 2 * sizeof(char*) + /* pointers to the address, plus trailing NULL */ - c * sizeof(char*); /* pointers to aliases, plus trailing NULL */ - - if (buflen < ms) { - *errnop = ENOMEM; - *h_errnop = TRY_AGAIN; - return NSS_STATUS_TRYAGAIN; - } - - /* First, place address */ - r_addr = buffer; - memcpy(r_addr, addr, len); - idx = ALIGN(len); - - /* Second, place address list */ - r_addr_list = buffer + idx; - ((char**) r_addr_list)[0] = r_addr; - ((char**) r_addr_list)[1] = NULL; - idx += sizeof(char*) * 2; - - /* Third, reserve space for the aliases array */ - r_aliases = buffer + idx; - idx += sizeof(char*) * c; - - /* Fourth, place aliases */ - i = 0; - r_name = buffer + idx; - while ((r = sd_bus_message_read(reply, "(is)", &ifindex, &n)) > 0) { - char *p; - size_t l; - - l = strlen(n); - p = buffer + idx; - memcpy(p, n, l+1); - - if (i > 0) - ((char**) r_aliases)[i-1] = p; - i++; - - idx += ALIGN(l+1); - } - if (r < 0) - goto fail; - - ((char**) r_aliases)[c-1] = NULL; - assert(idx == ms); - - result->h_name = r_name; - result->h_aliases = (char**) r_aliases; - result->h_addrtype = af; - result->h_length = len; - result->h_addr_list = (char**) r_addr_list; - - if (ttlp) - *ttlp = 0; - - /* Explicitly reset all error variables */ - *errnop = 0; - *h_errnop = NETDB_SUCCESS; - h_errno = 0; - - return NSS_STATUS_SUCCESS; - -fallback: - fallback = (enum nss_status (*)( - const void* addr, socklen_t len, - int af, - struct hostent *result, - char *buffer, size_t buflen, - int *errnop, int *h_errnop, - int32_t *ttlp)) - find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyaddr2_r"); - - if (fallback) - return fallback(addr, len, af, result, buffer, buflen, errnop, h_errnop, ttlp); - -fail: - *errnop = -r; - *h_errnop = NO_RECOVERY; - return NSS_STATUS_UNAVAIL; -} - -NSS_GETHOSTBYNAME_FALLBACKS(resolve); -NSS_GETHOSTBYADDR_FALLBACKS(resolve); diff --git a/src/nss-resolve/nss-resolve.sym b/src/nss-resolve/nss-resolve.sym deleted file mode 100644 index df8dff2a20..0000000000 --- a/src/nss-resolve/nss-resolve.sym +++ /dev/null @@ -1,19 +0,0 @@ -/*** - 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_resolve_gethostbyname_r; - _nss_resolve_gethostbyname2_r; - _nss_resolve_gethostbyname3_r; - _nss_resolve_gethostbyname4_r; - _nss_resolve_gethostbyaddr_r; - _nss_resolve_gethostbyaddr2_r; -local: *; -}; diff --git a/src/path/Makefile b/src/path/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/path/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/path/path.c b/src/path/path.c deleted file mode 100644 index 61d877fcf8..0000000000 --- a/src/path/path.c +++ /dev/null @@ -1,198 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include - -#include "sd-path.h" - -#include "alloc-util.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "util.h" - -static const char *arg_suffix = NULL; - -static const char* const path_table[_SD_PATH_MAX] = { - [SD_PATH_TEMPORARY] = "temporary", - [SD_PATH_TEMPORARY_LARGE] = "temporary-large", - [SD_PATH_SYSTEM_BINARIES] = "system-binaries", - [SD_PATH_SYSTEM_INCLUDE] = "system-include", - [SD_PATH_SYSTEM_LIBRARY_PRIVATE] = "system-library-private", - [SD_PATH_SYSTEM_LIBRARY_ARCH] = "system-library-arch", - [SD_PATH_SYSTEM_SHARED] = "system-shared", - [SD_PATH_SYSTEM_CONFIGURATION_FACTORY] = "system-configuration-factory", - [SD_PATH_SYSTEM_STATE_FACTORY] = "system-state-factory", - [SD_PATH_SYSTEM_CONFIGURATION] = "system-configuration", - [SD_PATH_SYSTEM_RUNTIME] = "system-runtime", - [SD_PATH_SYSTEM_RUNTIME_LOGS] = "system-runtime-logs", - [SD_PATH_SYSTEM_STATE_PRIVATE] = "system-state-private", - [SD_PATH_SYSTEM_STATE_LOGS] = "system-state-logs", - [SD_PATH_SYSTEM_STATE_CACHE] = "system-state-cache", - [SD_PATH_SYSTEM_STATE_SPOOL] = "system-state-spool", - [SD_PATH_USER_BINARIES] = "user-binaries", - [SD_PATH_USER_LIBRARY_PRIVATE] = "user-library-private", - [SD_PATH_USER_LIBRARY_ARCH] = "user-library-arch", - [SD_PATH_USER_SHARED] = "user-shared", - [SD_PATH_USER_CONFIGURATION] = "user-configuration", - [SD_PATH_USER_RUNTIME] = "user-runtime", - [SD_PATH_USER_STATE_CACHE] = "user-state-cache", - [SD_PATH_USER] = "user", - [SD_PATH_USER_DOCUMENTS] = "user-documents", - [SD_PATH_USER_MUSIC] = "user-music", - [SD_PATH_USER_PICTURES] = "user-pictures", - [SD_PATH_USER_VIDEOS] = "user-videos", - [SD_PATH_USER_DOWNLOAD] = "user-download", - [SD_PATH_USER_PUBLIC] = "user-public", - [SD_PATH_USER_TEMPLATES] = "user-templates", - [SD_PATH_USER_DESKTOP] = "user-desktop", - [SD_PATH_SEARCH_BINARIES] = "search-binaries", - [SD_PATH_SEARCH_LIBRARY_PRIVATE] = "search-library-private", - [SD_PATH_SEARCH_LIBRARY_ARCH] = "search-library-arch", - [SD_PATH_SEARCH_SHARED] = "search-shared", - [SD_PATH_SEARCH_CONFIGURATION_FACTORY] = "search-configuration-factory", - [SD_PATH_SEARCH_STATE_FACTORY] = "search-state-factory", - [SD_PATH_SEARCH_CONFIGURATION] = "search-configuration", -}; - -static int list_homes(void) { - uint64_t i = 0; - int r = 0; - - for (i = 0; i < ELEMENTSOF(path_table); i++) { - _cleanup_free_ char *p = NULL; - int q; - - q = sd_path_home(i, arg_suffix, &p); - if (q == -ENXIO) - continue; - if (q < 0) { - log_error_errno(r, "Failed to query %s: %m", path_table[i]); - r = q; - continue; - } - - printf("%s: %s\n", path_table[i], p); - } - - return r; -} - -static int print_home(const char *n) { - uint64_t i = 0; - int r; - - for (i = 0; i < ELEMENTSOF(path_table); i++) { - if (streq(path_table[i], n)) { - _cleanup_free_ char *p = NULL; - - r = sd_path_home(i, arg_suffix, &p); - if (r < 0) - return log_error_errno(r, "Failed to query %s: %m", n); - - printf("%s\n", p); - return 0; - } - } - - log_error("Path %s not known.", n); - return -EOPNOTSUPP; -} - -static void help(void) { - printf("%s [OPTIONS...] [NAME...]\n\n" - "Show system and user paths.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --suffix=SUFFIX Suffix to append to paths\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_SUFFIX, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "suffix", required_argument, NULL, ARG_SUFFIX }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_SUFFIX: - arg_suffix = optarg; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -int main(int argc, char* argv[]) { - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (argc > optind) { - int i, q; - - for (i = optind; i < argc; i++) { - q = print_home(argv[i]); - if (q < 0) - r = q; - } - } else - r = list_homes(); - - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/quotacheck/Makefile b/src/quotacheck/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/quotacheck/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/quotacheck/quotacheck.c b/src/quotacheck/quotacheck.c deleted file mode 100644 index 6d8c05f046..0000000000 --- a/src/quotacheck/quotacheck.c +++ /dev/null @@ -1,124 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "proc-cmdline.h" -#include "process-util.h" -#include "signal-util.h" -#include "string-util.h" -#include "util.h" - -static bool arg_skip = false; -static bool arg_force = false; - -static int parse_proc_cmdline_item(const char *key, const char *value) { - - if (streq(key, "quotacheck.mode") && value) { - - if (streq(value, "auto")) - arg_force = arg_skip = false; - else if (streq(value, "force")) - arg_force = true; - else if (streq(value, "skip")) - arg_skip = true; - else - log_warning("Invalid quotacheck.mode= parameter '%s'. Ignoring.", value); - } - -#ifdef HAVE_SYSV_COMPAT - else if (streq(key, "forcequotacheck") && !value) { - log_warning("Please use 'quotacheck.mode=force' rather than 'forcequotacheck' on the kernel command line."); - arg_force = true; - } -#endif - - return 0; -} - -static void test_files(void) { - -#ifdef HAVE_SYSV_COMPAT - if (access("/forcequotacheck", F_OK) >= 0) { - log_error("Please pass 'quotacheck.mode=force' on the kernel command line rather than creating /forcequotacheck on the root file system."); - arg_force = true; - } -#endif -} - -int main(int argc, char *argv[]) { - - static const char * const cmdline[] = { - QUOTACHECK, - "-anug", - NULL - }; - - pid_t pid; - int r; - - if (argc > 1) { - log_error("This program takes no arguments."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - r = parse_proc_cmdline(parse_proc_cmdline_item); - if (r < 0) - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - - test_files(); - - if (!arg_force) { - if (arg_skip) - return EXIT_SUCCESS; - - if (access("/run/systemd/quotacheck", F_OK) < 0) - return EXIT_SUCCESS; - } - - pid = fork(); - if (pid < 0) { - log_error_errno(errno, "fork(): %m"); - return EXIT_FAILURE; - } else if (pid == 0) { - - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - execv(cmdline[0], (char**) cmdline); - _exit(1); /* Operational error */ - } - - r = wait_for_terminate_and_warn("quotacheck", pid, true); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/random-seed/Makefile b/src/random-seed/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/random-seed/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/random-seed/random-seed.c b/src/random-seed/random-seed.c deleted file mode 100644 index 6748bb9dd3..0000000000 --- a/src/random-seed/random-seed.c +++ /dev/null @@ -1,176 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "log.h" -#include "mkdir.h" -#include "string-util.h" -#include "util.h" - -#define POOL_SIZE_MIN 512 - -int main(int argc, char *argv[]) { - _cleanup_close_ int seed_fd = -1, random_fd = -1; - _cleanup_free_ void* buf = NULL; - size_t buf_size = 0; - ssize_t k; - int r, open_rw_error; - FILE *f; - bool refresh_seed_file = true; - - if (argc != 2) { - log_error("This program requires one argument."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - /* Read pool size, if possible */ - f = fopen("/proc/sys/kernel/random/poolsize", "re"); - if (f) { - if (fscanf(f, "%zu", &buf_size) > 0) - /* poolsize is in bits on 2.6, but we want bytes */ - buf_size /= 8; - - fclose(f); - } - - if (buf_size <= POOL_SIZE_MIN) - buf_size = POOL_SIZE_MIN; - - buf = malloc(buf_size); - if (!buf) { - r = log_oom(); - goto finish; - } - - r = mkdir_parents_label(RANDOM_SEED, 0755); - if (r < 0) { - log_error_errno(r, "Failed to create directory " RANDOM_SEED_DIR ": %m"); - goto finish; - } - - /* When we load the seed we read it and write it to the device - * and then immediately update the saved seed with new data, - * to make sure the next boot gets seeded differently. */ - - if (streq(argv[1], "load")) { - - seed_fd = open(RANDOM_SEED, O_RDWR|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600); - open_rw_error = -errno; - if (seed_fd < 0) { - refresh_seed_file = false; - - seed_fd = open(RANDOM_SEED, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (seed_fd < 0) { - bool missing = errno == ENOENT; - - log_full_errno(missing ? LOG_DEBUG : LOG_ERR, - open_rw_error, "Failed to open " RANDOM_SEED " for writing: %m"); - r = log_full_errno(missing ? LOG_DEBUG : LOG_ERR, - errno, "Failed to open " RANDOM_SEED " for reading: %m"); - if (missing) - r = 0; - - goto finish; - } - } - - random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY, 0600); - if (random_fd < 0) { - random_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY, 0600); - if (random_fd < 0) { - r = log_error_errno(errno, "Failed to open /dev/urandom: %m"); - goto finish; - } - } - - k = loop_read(seed_fd, buf, buf_size, false); - if (k < 0) - r = log_error_errno(k, "Failed to read seed from " RANDOM_SEED ": %m"); - else if (k == 0) { - r = 0; - log_debug("Seed file " RANDOM_SEED " not yet initialized, proceeding."); - } else { - (void) lseek(seed_fd, 0, SEEK_SET); - - r = loop_write(random_fd, buf, (size_t) k, false); - if (r < 0) - log_error_errno(r, "Failed to write seed to /dev/urandom: %m"); - } - - } else if (streq(argv[1], "save")) { - - seed_fd = open(RANDOM_SEED, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600); - if (seed_fd < 0) { - r = log_error_errno(errno, "Failed to open " RANDOM_SEED ": %m"); - goto finish; - } - - random_fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (random_fd < 0) { - r = log_error_errno(errno, "Failed to open /dev/urandom: %m"); - goto finish; - } - - } else { - log_error("Unknown verb '%s'.", argv[1]); - r = -EINVAL; - goto finish; - } - - if (refresh_seed_file) { - - /* This is just a safety measure. Given that we are root and - * most likely created the file ourselves the mode and owner - * should be correct anyway. */ - (void) fchmod(seed_fd, 0600); - (void) fchown(seed_fd, 0, 0); - - k = loop_read(random_fd, buf, buf_size, false); - if (k < 0) { - r = log_error_errno(k, "Failed to read new seed from /dev/urandom: %m"); - goto finish; - } - if (k == 0) { - log_error("Got EOF while reading from /dev/urandom."); - r = -EIO; - goto finish; - } - - r = loop_write(seed_fd, buf, (size_t) k, false); - if (r < 0) - log_error_errno(r, "Failed to write new random seed file: %m"); - } - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/rc-local-generator/Makefile b/src/rc-local-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/rc-local-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/rc-local-generator/Makefile b/src/rc-local-generator/Makefile new file mode 100644 index 0000000000..2e9b3e7a64 --- /dev/null +++ b/src/rc-local-generator/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_rc_local_generator_SOURCES = \ + src/rc-local-generator/rc-local-generator.c + +systemd_rc_local_generator_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/remount-fs/Makefile b/src/remount-fs/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/remount-fs/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/remount-fs/remount-fs.c b/src/remount-fs/remount-fs.c deleted file mode 100644 index 6468d1eecd..0000000000 --- a/src/remount-fs/remount-fs.c +++ /dev/null @@ -1,155 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "exit-status.h" -#include "log.h" -#include "mount-setup.h" -#include "mount-util.h" -#include "path-util.h" -#include "process-util.h" -#include "signal-util.h" -#include "strv.h" -#include "util.h" - -/* Goes through /etc/fstab and remounts all API file systems, applying - * options that are in /etc/fstab that systemd might not have - * respected */ - -int main(int argc, char *argv[]) { - _cleanup_hashmap_free_free_ Hashmap *pids = NULL; - _cleanup_endmntent_ FILE *f = NULL; - struct mntent* me; - int r; - - if (argc > 1) { - log_error("This program takes no argument."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - f = setmntent("/etc/fstab", "r"); - if (!f) { - if (errno == ENOENT) { - r = 0; - goto finish; - } - - r = log_error_errno(errno, "Failed to open /etc/fstab: %m"); - goto finish; - } - - pids = hashmap_new(NULL); - if (!pids) { - r = log_oom(); - goto finish; - } - - while ((me = getmntent(f))) { - pid_t pid; - int k; - char *s; - - /* Remount the root fs, /usr and all API VFS */ - if (!mount_point_is_api(me->mnt_dir) && - !path_equal(me->mnt_dir, "/") && - !path_equal(me->mnt_dir, "/usr")) - continue; - - log_debug("Remounting %s", me->mnt_dir); - - pid = fork(); - if (pid < 0) { - r = log_error_errno(errno, "Failed to fork: %m"); - goto finish; - } - - if (pid == 0) { - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - (void) prctl(PR_SET_PDEATHSIG, SIGTERM); - - execv(MOUNT_PATH, STRV_MAKE(MOUNT_PATH, me->mnt_dir, "-o", "remount")); - - log_error_errno(errno, "Failed to execute " MOUNT_PATH ": %m"); - _exit(EXIT_FAILURE); - } - - /* Parent */ - - s = strdup(me->mnt_dir); - if (!s) { - r = log_oom(); - goto finish; - } - - k = hashmap_put(pids, PID_TO_PTR(pid), s); - if (k < 0) { - free(s); - r = log_oom(); - goto finish; - } - } - - r = 0; - while (!hashmap_isempty(pids)) { - siginfo_t si = {}; - char *s; - - if (waitid(P_ALL, 0, &si, WEXITED) < 0) { - - if (errno == EINTR) - continue; - - r = log_error_errno(errno, "waitid() failed: %m"); - goto finish; - } - - s = hashmap_remove(pids, PID_TO_PTR(si.si_pid)); - if (s) { - if (!is_clean_exit(si.si_code, si.si_status, NULL)) { - if (si.si_code == CLD_EXITED) - log_error(MOUNT_PATH " for %s exited with exit status %i.", s, si.si_status); - else - log_error(MOUNT_PATH " for %s terminated by signal %s.", s, signal_to_string(si.si_status)); - - r = -ENOEXEC; - } - - free(s); - } - } - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/reply-password/Makefile b/src/reply-password/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/reply-password/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/reply-password/reply-password.c b/src/reply-password/reply-password.c deleted file mode 100644 index e291758969..0000000000 --- a/src/reply-password/reply-password.c +++ /dev/null @@ -1,98 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "util.h" - -static int send_on_socket(int fd, const char *socket_name, const void *packet, size_t size) { - union { - struct sockaddr sa; - struct sockaddr_un un; - } sa = { - .un.sun_family = AF_UNIX, - }; - - assert(fd >= 0); - assert(socket_name); - assert(packet); - - strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path)); - - if (sendto(fd, packet, size, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) - return log_error_errno(errno, "Failed to send: %m"); - - return 0; -} - -int main(int argc, char *argv[]) { - _cleanup_close_ int fd = -1; - char packet[LINE_MAX]; - size_t length; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - if (argc != 3) { - log_error("Wrong number of arguments."); - return EXIT_FAILURE; - } - - if (streq(argv[1], "1")) { - - packet[0] = '+'; - if (!fgets(packet+1, sizeof(packet)-1, stdin)) { - r = log_error_errno(errno, "Failed to read password: %m"); - goto finish; - } - - truncate_nl(packet+1); - length = 1 + strlen(packet+1) + 1; - } else if (streq(argv[1], "0")) { - packet[0] = '-'; - length = 1; - } else { - log_error("Invalid first argument %s", argv[1]); - r = -EINVAL; - goto finish; - } - - fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) { - r = log_error_errno(errno, "socket() failed: %m"); - goto finish; - } - - r = send_on_socket(fd, argv[2], packet, length); - -finish: - memory_erase(packet, sizeof(packet)); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/resolve/.gitignore b/src/resolve/.gitignore deleted file mode 100644 index f0835923b7..0000000000 --- a/src/resolve/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -/resolved-gperf.c -/resolved.conf -/dns_type-from-name.gperf -/dns_type-from-name.h -/dns_type-list.txt -/dns_type-to-name.h diff --git a/src/resolve/Makefile b/src/resolve/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/resolve/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/resolve/RFCs b/src/resolve/RFCs deleted file mode 100644 index 22004a00cd..0000000000 --- a/src/resolve/RFCs +++ /dev/null @@ -1,59 +0,0 @@ -Y = Comprehensively Implemented, to the point appropriate for resolved -D = Comprehensively Implemented, by a dependency of resolved -! = Missing and something we might want to implement -~ = Needs no explicit support or doesn't apply -? = Is this relevant today? - = We are working on this - -Y https://tools.ietf.org/html/rfc1034 → DOMAIN NAMES - CONCEPTS AND FACILITIES -Y https://tools.ietf.org/html/rfc1035 → DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION -? https://tools.ietf.org/html/rfc1101 → DNS Encoding of Network Names and Other Types -Y https://tools.ietf.org/html/rfc1123 → Requirements for Internet Hosts -- Application and Support -~ https://tools.ietf.org/html/rfc1464 → Using the Domain Name System To Store Arbitrary String Attributes -Y https://tools.ietf.org/html/rfc1536 → Common DNS Implementation Errors and Suggested Fixes -Y https://tools.ietf.org/html/rfc1876 → A Means for Expressing Location Information in the Domain Name System -Y https://tools.ietf.org/html/rfc2181 → Clarifications to the DNS Specification -Y https://tools.ietf.org/html/rfc2308 → Negative Caching of DNS Queries (DNS NCACHE) -Y https://tools.ietf.org/html/rfc2782 → A DNS RR for specifying the location of services (DNS SRV) -D https://tools.ietf.org/html/rfc3492 → Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA) -Y https://tools.ietf.org/html/rfc3596 → DNS Extensions to Support IP Version 6 -Y https://tools.ietf.org/html/rfc3597 → Handling of Unknown DNS Resource Record (RR) Types -Y https://tools.ietf.org/html/rfc4033 → DNS Security Introduction and Requirements -Y https://tools.ietf.org/html/rfc4034 → Resource Records for the DNS Security Extensions -Y https://tools.ietf.org/html/rfc4035 → Protocol Modifications for the DNS Security Extensions -! https://tools.ietf.org/html/rfc4183 → A Suggested Scheme for DNS Resolution of Networks and Gateways -Y https://tools.ietf.org/html/rfc4255 → Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints -Y https://tools.ietf.org/html/rfc4343 → Domain Name System (DNS) Case Insensitivity Clarification -~ https://tools.ietf.org/html/rfc4470 → Minimally Covering NSEC Records and DNSSEC On-line Signing -Y https://tools.ietf.org/html/rfc4501 → Domain Name System Uniform Resource Identifiers -Y https://tools.ietf.org/html/rfc4509 → Use of SHA-256 in DNSSEC Delegation Signer (DS) Resource Records (RRs) -~ https://tools.ietf.org/html/rfc4592 → The Role of Wildcards in the Domain Name System -~ https://tools.ietf.org/html/rfc4697 → Observed DNS Resolution Misbehavior -Y https://tools.ietf.org/html/rfc4795 → Link-Local Multicast Name Resolution (LLMNR) -Y https://tools.ietf.org/html/rfc5011 → Automated Updates of DNS Security (DNSSEC) Trust Anchors -Y https://tools.ietf.org/html/rfc5155 → DNS Security (DNSSEC) Hashed Authenticated Denial of Existence -Y https://tools.ietf.org/html/rfc5452 → Measures for Making DNS More Resilient against Forged Answers -Y https://tools.ietf.org/html/rfc5702 → Use of SHA-2 Algorithms with RSA in DNSKEY and RRSIG Resource Records for DNSSEC -Y https://tools.ietf.org/html/rfc5890 → Internationalized Domain Names for Applications (IDNA): Definitions and Document Framework -Y https://tools.ietf.org/html/rfc5891 → Internationalized Domain Names in Applications (IDNA): Protocol -Y https://tools.ietf.org/html/rfc5966 → DNS Transport over TCP - Implementation Requirements -Y https://tools.ietf.org/html/rfc6303 → Locally Served DNS Zones -Y https://tools.ietf.org/html/rfc6604 → xNAME RCODE and Status Bits Clarification -Y https://tools.ietf.org/html/rfc6605 → Elliptic Curve Digital Signature Algorithm (DSA) for DNSSEC - https://tools.ietf.org/html/rfc6672 → DNAME Redirection in the DNS -! https://tools.ietf.org/html/rfc6731 → Improved Recursive DNS Server Selection for Multi-Interfaced Nodes -Y https://tools.ietf.org/html/rfc6761 → Special-Use Domain Names - https://tools.ietf.org/html/rfc6762 → Multicast DNS - https://tools.ietf.org/html/rfc6763 → DNS-Based Service Discovery -~ https://tools.ietf.org/html/rfc6781 → DNSSEC Operational Practices, Version 2 -Y https://tools.ietf.org/html/rfc6840 → Clarifications and Implementation Notes for DNS Security (DNSSEC) -Y https://tools.ietf.org/html/rfc6891 → Extension Mechanisms for DNS (EDNS(0)) -Y https://tools.ietf.org/html/rfc6944 → Applicability Statement: DNS Security (DNSSEC) DNSKEY Algorithm Implementation Status -Y https://tools.ietf.org/html/rfc6975 → Signaling Cryptographic Algorithm Understanding in DNS Security Extensions (DNSSEC) -Y https://tools.ietf.org/html/rfc7129 → Authenticated Denial of Existence in the DNS -Y https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors -~ https://tools.ietf.org/html/rfc7719 → DNS Terminology - -Also relevant: - - https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-statement-dotless-domains-considered-harmful/ diff --git a/src/resolve/dns-type.c b/src/resolve/dns-type.c deleted file mode 100644 index b2f479cae5..0000000000 --- a/src/resolve/dns-type.c +++ /dev/null @@ -1,306 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "dns-type.h" -#include "parse-util.h" -#include "string-util.h" - -typedef const struct { - uint16_t type; - const char *name; -} dns_type; - -static const struct dns_type_name * -lookup_dns_type (register const char *str, register unsigned int len); - -#include "dns_type-from-name.h" -#include "dns_type-to-name.h" - -int dns_type_from_string(const char *s) { - const struct dns_type_name *sc; - - assert(s); - - sc = lookup_dns_type(s, strlen(s)); - if (sc) - return sc->id; - - s = startswith_no_case(s, "TYPE"); - if (s) { - unsigned x; - - if (safe_atou(s, &x) >= 0 && - x <= UINT16_MAX) - return (int) x; - } - - return _DNS_TYPE_INVALID; -} - -bool dns_type_is_pseudo(uint16_t type) { - - /* Checks whether the specified type is a "pseudo-type". What - * a "pseudo-type" precisely is, is defined only very weakly, - * but apparently entails all RR types that are not actually - * stored as RRs on the server and should hence also not be - * cached. We use this list primarily to validate NSEC type - * bitfields, and to verify what to cache. */ - - return IN_SET(type, - 0, /* A Pseudo RR type, according to RFC 2931 */ - DNS_TYPE_ANY, - DNS_TYPE_AXFR, - DNS_TYPE_IXFR, - DNS_TYPE_OPT, - DNS_TYPE_TSIG, - DNS_TYPE_TKEY - ); -} - -bool dns_class_is_pseudo(uint16_t class) { - return class == DNS_TYPE_ANY; -} - -bool dns_type_is_valid_query(uint16_t type) { - - /* The types valid as questions in packets */ - - return !IN_SET(type, - 0, - DNS_TYPE_OPT, - DNS_TYPE_TSIG, - DNS_TYPE_TKEY, - - /* RRSIG are technically valid as questions, but we refuse doing explicit queries for them, as - * they aren't really payload, but signatures for payload, and cannot be validated on their - * own. After all they are the signatures, and have no signatures of their own validating - * them. */ - DNS_TYPE_RRSIG); -} - -bool dns_type_is_valid_rr(uint16_t type) { - - /* The types valid as RR in packets (but not necessarily - * stored on servers). */ - - return !IN_SET(type, - DNS_TYPE_ANY, - DNS_TYPE_AXFR, - DNS_TYPE_IXFR); -} - -bool dns_class_is_valid_rr(uint16_t class) { - return class != DNS_CLASS_ANY; -} - -bool dns_type_may_redirect(uint16_t type) { - /* The following record types should never be redirected using - * CNAME/DNAME RRs. See - * . */ - - if (dns_type_is_pseudo(type)) - return false; - - return !IN_SET(type, - DNS_TYPE_CNAME, - DNS_TYPE_DNAME, - DNS_TYPE_NSEC3, - DNS_TYPE_NSEC, - DNS_TYPE_RRSIG, - DNS_TYPE_NXT, - DNS_TYPE_SIG, - DNS_TYPE_KEY); -} - -bool dns_type_may_wildcard(uint16_t type) { - - /* The following records may not be expanded from wildcard RRsets */ - - if (dns_type_is_pseudo(type)) - return false; - - return !IN_SET(type, - DNS_TYPE_NSEC3, - DNS_TYPE_SOA, - - /* Prohibited by https://tools.ietf.org/html/rfc4592#section-4.4 */ - DNS_TYPE_DNAME); -} - -bool dns_type_apex_only(uint16_t type) { - - /* Returns true for all RR types that may only appear signed in a zone apex */ - - return IN_SET(type, - DNS_TYPE_SOA, - DNS_TYPE_NS, /* this one can appear elsewhere, too, but not signed */ - DNS_TYPE_DNSKEY, - DNS_TYPE_NSEC3PARAM); -} - -bool dns_type_is_dnssec(uint16_t type) { - return IN_SET(type, - DNS_TYPE_DS, - DNS_TYPE_DNSKEY, - DNS_TYPE_RRSIG, - DNS_TYPE_NSEC, - DNS_TYPE_NSEC3, - DNS_TYPE_NSEC3PARAM); -} - -bool dns_type_is_obsolete(uint16_t type) { - return IN_SET(type, - /* Obsoleted by RFC 973 */ - DNS_TYPE_MD, - DNS_TYPE_MF, - DNS_TYPE_MAILA, - - /* Kinda obsoleted by RFC 2505 */ - DNS_TYPE_MB, - DNS_TYPE_MG, - DNS_TYPE_MR, - DNS_TYPE_MINFO, - DNS_TYPE_MAILB, - - /* RFC1127 kinda obsoleted this by recommending against its use */ - DNS_TYPE_WKS, - - /* Declared historical by RFC 6563 */ - DNS_TYPE_A6, - - /* Obsoleted by DNSSEC-bis */ - DNS_TYPE_NXT, - - /* RFC 1035 removed support for concepts that needed this from RFC 883 */ - DNS_TYPE_NULL); -} - -int dns_type_to_af(uint16_t t) { - switch (t) { - - case DNS_TYPE_A: - return AF_INET; - - case DNS_TYPE_AAAA: - return AF_INET6; - - case DNS_TYPE_ANY: - return AF_UNSPEC; - - default: - return -EINVAL; - } -} - -const char *dns_class_to_string(uint16_t class) { - - switch (class) { - - case DNS_CLASS_IN: - return "IN"; - - case DNS_CLASS_ANY: - return "ANY"; - } - - return NULL; -} - -int dns_class_from_string(const char *s) { - - if (!s) - return _DNS_CLASS_INVALID; - - if (strcaseeq(s, "IN")) - return DNS_CLASS_IN; - else if (strcaseeq(s, "ANY")) - return DNS_CLASS_ANY; - - return _DNS_CLASS_INVALID; -} - -const char* tlsa_cert_usage_to_string(uint8_t cert_usage) { - - switch (cert_usage) { - - case 0: - return "CA constraint"; - - case 1: - return "Service certificate constraint"; - - case 2: - return "Trust anchor assertion"; - - case 3: - return "Domain-issued certificate"; - - case 4 ... 254: - return "Unassigned"; - - case 255: - return "Private use"; - } - - return NULL; /* clang cannot count that we covered everything */ -} - -const char* tlsa_selector_to_string(uint8_t selector) { - switch (selector) { - - case 0: - return "Full Certificate"; - - case 1: - return "SubjectPublicKeyInfo"; - - case 2 ... 254: - return "Unassigned"; - - case 255: - return "Private use"; - } - - return NULL; -} - -const char* tlsa_matching_type_to_string(uint8_t selector) { - - switch (selector) { - - case 0: - return "No hash used"; - - case 1: - return "SHA-256"; - - case 2: - return "SHA-512"; - - case 3 ... 254: - return "Unassigned"; - - case 255: - return "Private use"; - } - - return NULL; -} diff --git a/src/resolve/dns-type.h b/src/resolve/dns-type.h deleted file mode 100644 index a6c1630021..0000000000 --- a/src/resolve/dns-type.h +++ /dev/null @@ -1,154 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#pragma once - -#include "macro.h" - -/* DNS record types, taken from - * http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. - */ -enum { - /* Normal records */ - DNS_TYPE_A = 0x01, - DNS_TYPE_NS, - DNS_TYPE_MD, - DNS_TYPE_MF, - DNS_TYPE_CNAME, - DNS_TYPE_SOA, - DNS_TYPE_MB, - DNS_TYPE_MG, - DNS_TYPE_MR, - DNS_TYPE_NULL, - DNS_TYPE_WKS, - DNS_TYPE_PTR, - DNS_TYPE_HINFO, - DNS_TYPE_MINFO, - DNS_TYPE_MX, - DNS_TYPE_TXT, - DNS_TYPE_RP, - DNS_TYPE_AFSDB, - DNS_TYPE_X25, - DNS_TYPE_ISDN, - DNS_TYPE_RT, - DNS_TYPE_NSAP, - DNS_TYPE_NSAP_PTR, - DNS_TYPE_SIG, - DNS_TYPE_KEY, - DNS_TYPE_PX, - DNS_TYPE_GPOS, - DNS_TYPE_AAAA, - DNS_TYPE_LOC, - DNS_TYPE_NXT, - DNS_TYPE_EID, - DNS_TYPE_NIMLOC, - DNS_TYPE_SRV, - DNS_TYPE_ATMA, - DNS_TYPE_NAPTR, - DNS_TYPE_KX, - DNS_TYPE_CERT, - DNS_TYPE_A6, - DNS_TYPE_DNAME, - DNS_TYPE_SINK, - DNS_TYPE_OPT, /* EDNS0 option */ - DNS_TYPE_APL, - DNS_TYPE_DS, - DNS_TYPE_SSHFP, - DNS_TYPE_IPSECKEY, - DNS_TYPE_RRSIG, - DNS_TYPE_NSEC, - DNS_TYPE_DNSKEY, - DNS_TYPE_DHCID, - DNS_TYPE_NSEC3, - DNS_TYPE_NSEC3PARAM, - DNS_TYPE_TLSA, - - DNS_TYPE_HIP = 0x37, - DNS_TYPE_NINFO, - DNS_TYPE_RKEY, - DNS_TYPE_TALINK, - DNS_TYPE_CDS, - DNS_TYPE_CDNSKEY, - DNS_TYPE_OPENPGPKEY, - - DNS_TYPE_SPF = 0x63, - DNS_TYPE_NID, - DNS_TYPE_L32, - DNS_TYPE_L64, - DNS_TYPE_LP, - DNS_TYPE_EUI48, - DNS_TYPE_EUI64, - - DNS_TYPE_TKEY = 0xF9, - DNS_TYPE_TSIG, - DNS_TYPE_IXFR, - DNS_TYPE_AXFR, - DNS_TYPE_MAILB, - DNS_TYPE_MAILA, - DNS_TYPE_ANY, - DNS_TYPE_URI, - DNS_TYPE_CAA, - DNS_TYPE_TA = 0x8000, - DNS_TYPE_DLV, - - _DNS_TYPE_MAX, - _DNS_TYPE_INVALID = -1 -}; - -assert_cc(DNS_TYPE_SSHFP == 44); -assert_cc(DNS_TYPE_TLSA == 52); -assert_cc(DNS_TYPE_ANY == 255); - -/* DNS record classes, see RFC 1035 */ -enum { - DNS_CLASS_IN = 0x01, - DNS_CLASS_ANY = 0xFF, - - _DNS_CLASS_MAX, - _DNS_CLASS_INVALID = -1 -}; - -bool dns_type_is_pseudo(uint16_t type); -bool dns_type_is_valid_query(uint16_t type); -bool dns_type_is_valid_rr(uint16_t type); -bool dns_type_may_redirect(uint16_t type); -bool dns_type_is_dnssec(uint16_t type); -bool dns_type_is_obsolete(uint16_t type); -bool dns_type_may_wildcard(uint16_t type); -bool dns_type_apex_only(uint16_t type); -int dns_type_to_af(uint16_t t); - -bool dns_class_is_pseudo(uint16_t class); -bool dns_class_is_valid_rr(uint16_t class); - -/* TYPE?? follows http://tools.ietf.org/html/rfc3597#section-5 */ -const char *dns_type_to_string(int type); -int dns_type_from_string(const char *s); - -const char *dns_class_to_string(uint16_t type); -int dns_class_from_string(const char *name); - -/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.2 */ -const char *tlsa_cert_usage_to_string(uint8_t cert_usage); - -/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.3 */ -const char *tlsa_selector_to_string(uint8_t selector); - -/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.4 */ -const char *tlsa_matching_type_to_string(uint8_t selector); diff --git a/src/resolve/nss-resolve/nss-resolve.c b/src/resolve/nss-resolve/nss-resolve.c new file mode 100644 index 0000000000..d369f1b0c8 --- /dev/null +++ b/src/resolve/nss-resolve/nss-resolve.c @@ -0,0 +1,704 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include + +#include "bus-common-errors.h" +#include "in-addr-util.h" +#include "macro.h" +#include "nss-util.h" +#include "string-util.h" +#include "util.h" +#include "signal-util.h" + +NSS_GETHOSTBYNAME_PROTOTYPES(resolve); +NSS_GETHOSTBYADDR_PROTOTYPES(resolve); + +#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) + +typedef void (*voidfunc_t)(void); + +static voidfunc_t find_fallback(const char *module, const char *symbol) { + void *dl; + + /* Try to find a fallback NSS module symbol */ + + dl = dlopen(module, RTLD_LAZY|RTLD_NODELETE); + if (!dl) + return NULL; + + return dlsym(dl, symbol); +} + +static bool bus_error_shall_fallback(sd_bus_error *e) { + return sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN) || + sd_bus_error_has_name(e, SD_BUS_ERROR_NAME_HAS_NO_OWNER) || + sd_bus_error_has_name(e, SD_BUS_ERROR_NO_REPLY) || + sd_bus_error_has_name(e, SD_BUS_ERROR_ACCESS_DENIED); +} + +static int count_addresses(sd_bus_message *m, int af, const char **canonical) { + int c = 0, r; + + assert(m); + assert(canonical); + + r = sd_bus_message_enter_container(m, 'a', "(iiay)"); + if (r < 0) + return r; + + while ((r = sd_bus_message_enter_container(m, 'r', "iiay")) > 0) { + int family, ifindex; + + assert_cc(sizeof(int32_t) == sizeof(int)); + + r = sd_bus_message_read(m, "ii", &ifindex, &family); + if (r < 0) + return r; + + r = sd_bus_message_skip(m, "ay"); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + if (af != AF_UNSPEC && family != af) + continue; + + c ++; + } + if (r < 0) + return r; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + r = sd_bus_message_read(m, "s", canonical); + if (r < 0) + return r; + + r = sd_bus_message_rewind(m, true); + if (r < 0) + return r; + + return c; +} + +enum nss_status _nss_resolve_gethostbyname4_r( + const char *name, + struct gaih_addrtuple **pat, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp) { + + enum nss_status (*fallback)( + const char *name, + struct gaih_addrtuple **pat, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + struct gaih_addrtuple *r_tuple, *r_tuple_first = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + const char *canonical = NULL; + size_t l, ms, idx; + char *r_name; + int c, r, i = 0; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(pat); + assert(buffer); + assert(errnop); + assert(h_errnop); + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fallback; + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveHostname"); + if (r < 0) + goto fail; + + r = sd_bus_message_set_auto_start(req, false); + if (r < 0) + goto fail; + + r = sd_bus_message_append(req, "isit", 0, name, AF_UNSPEC, (uint64_t) 0); + if (r < 0) + goto fail; + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN")) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + if (bus_error_shall_fallback(&error)) + goto fallback; + + goto fail; + } + + c = count_addresses(reply, AF_UNSPEC, &canonical); + if (c < 0) { + r = c; + goto fail; + } + if (c == 0) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + if (isempty(canonical)) + canonical = name; + + l = strlen(canonical); + ms = ALIGN(l+1) + ALIGN(sizeof(struct gaih_addrtuple)) * c; + if (buflen < ms) { + *errnop = ENOMEM; + *h_errnop = TRY_AGAIN; + return NSS_STATUS_TRYAGAIN; + } + + /* First, append name */ + r_name = buffer; + memcpy(r_name, canonical, l+1); + idx = ALIGN(l+1); + + /* Second, append addresses */ + r_tuple_first = (struct gaih_addrtuple*) (buffer + idx); + + r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (r < 0) + goto fail; + + while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + int family, ifindex; + const void *a; + size_t sz; + + assert_cc(sizeof(int32_t) == sizeof(int)); + + r = sd_bus_message_read(reply, "ii", &ifindex, &family); + if (r < 0) + goto fail; + + if (ifindex < 0) { + r = -EINVAL; + goto fail; + } + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + goto fail; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + goto fail; + + if (!IN_SET(family, AF_INET, AF_INET6)) + continue; + + if (sz != FAMILY_ADDRESS_SIZE(family)) { + r = -EINVAL; + goto fail; + } + + r_tuple = (struct gaih_addrtuple*) (buffer + idx); + r_tuple->next = i == c-1 ? NULL : (struct gaih_addrtuple*) ((char*) r_tuple + ALIGN(sizeof(struct gaih_addrtuple))); + r_tuple->name = r_name; + r_tuple->family = family; + r_tuple->scopeid = ifindex; + memcpy(r_tuple->addr, a, sz); + + idx += ALIGN(sizeof(struct gaih_addrtuple)); + i++; + } + if (r < 0) + goto fail; + + assert(i == c); + assert(idx == ms); + + if (*pat) + **pat = *r_tuple_first; + else + *pat = r_tuple_first; + + if (ttlp) + *ttlp = 0; + + /* Explicitly reset all error variables */ + *errnop = 0; + *h_errnop = NETDB_SUCCESS; + h_errno = 0; + + return NSS_STATUS_SUCCESS; + +fallback: + fallback = (enum nss_status (*)(const char *name, + struct gaih_addrtuple **pat, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp)) + find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyname4_r"); + + if (fallback) + return fallback(name, pat, buffer, buflen, errnop, h_errnop, ttlp); + +fail: + *errnop = -r; + *h_errnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_resolve_gethostbyname3_r( + const char *name, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp, + char **canonp) { + + enum nss_status (*fallback)( + const char *name, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp, + char **canonp); + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char *r_name, *r_aliases, *r_addr, *r_addr_list; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + size_t l, idx, ms, alen; + const char *canonical; + int c, r, i = 0; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(name); + assert(result); + assert(buffer); + assert(errnop); + assert(h_errnop); + + if (af == AF_UNSPEC) + af = AF_INET; + + if (af != AF_INET && af != AF_INET6) { + r = -EAFNOSUPPORT; + goto fail; + } + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fallback; + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveHostname"); + if (r < 0) + goto fail; + + r = sd_bus_message_set_auto_start(req, false); + if (r < 0) + goto fail; + + r = sd_bus_message_append(req, "isit", 0, name, af, (uint64_t) 0); + if (r < 0) + goto fail; + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN")) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + if (bus_error_shall_fallback(&error)) + goto fallback; + + goto fail; + } + + c = count_addresses(reply, af, &canonical); + if (c < 0) { + r = c; + goto fail; + } + if (c == 0) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + if (isempty(canonical)) + canonical = name; + + alen = FAMILY_ADDRESS_SIZE(af); + l = strlen(canonical); + + ms = ALIGN(l+1) + c * ALIGN(alen) + (c+2) * sizeof(char*); + + if (buflen < ms) { + *errnop = ENOMEM; + *h_errnop = TRY_AGAIN; + return NSS_STATUS_TRYAGAIN; + } + + /* First, append name */ + r_name = buffer; + memcpy(r_name, canonical, l+1); + idx = ALIGN(l+1); + + /* Second, create empty aliases array */ + r_aliases = buffer + idx; + ((char**) r_aliases)[0] = NULL; + idx += sizeof(char*); + + /* Third, append addresses */ + r_addr = buffer + idx; + + r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (r < 0) + goto fail; + + while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + int ifindex, family; + const void *a; + size_t sz; + + r = sd_bus_message_read(reply, "ii", &ifindex, &family); + if (r < 0) + goto fail; + + if (ifindex < 0) { + r = -EINVAL; + goto fail; + } + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + goto fail; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + goto fail; + + if (family != af) + continue; + + if (sz != alen) { + r = -EINVAL; + goto fail; + } + + memcpy(r_addr + i*ALIGN(alen), a, alen); + i++; + } + if (r < 0) + goto fail; + + assert(i == c); + idx += c * ALIGN(alen); + + /* Fourth, append address pointer array */ + r_addr_list = buffer + idx; + for (i = 0; i < c; i++) + ((char**) r_addr_list)[i] = r_addr + i*ALIGN(alen); + + ((char**) r_addr_list)[i] = NULL; + idx += (c+1) * sizeof(char*); + + assert(idx == ms); + + result->h_name = r_name; + result->h_aliases = (char**) r_aliases; + result->h_addrtype = af; + result->h_length = alen; + result->h_addr_list = (char**) r_addr_list; + + /* Explicitly reset all error variables */ + *errnop = 0; + *h_errnop = NETDB_SUCCESS; + h_errno = 0; + + if (ttlp) + *ttlp = 0; + + if (canonp) + *canonp = r_name; + + return NSS_STATUS_SUCCESS; + +fallback: + fallback = (enum nss_status (*)(const char *name, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp, + char **canonp)) + find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyname3_r"); + if (fallback) + return fallback(name, af, result, buffer, buflen, errnop, h_errnop, ttlp, canonp); + +fail: + *errnop = -r; + *h_errnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; +} + +enum nss_status _nss_resolve_gethostbyaddr2_r( + const void* addr, socklen_t len, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp) { + + enum nss_status (*fallback)( + const void* addr, socklen_t len, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp); + + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char *r_name, *r_aliases, *r_addr, *r_addr_list; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + unsigned c = 0, i = 0; + size_t ms = 0, idx; + const char *n; + int r, ifindex; + + BLOCK_SIGNALS(NSS_SIGNALS_BLOCK); + + assert(addr); + assert(result); + assert(buffer); + assert(errnop); + assert(h_errnop); + + if (!IN_SET(af, AF_INET, AF_INET6)) { + *errnop = EAFNOSUPPORT; + *h_errnop = NO_DATA; + return NSS_STATUS_UNAVAIL; + } + + if (len != FAMILY_ADDRESS_SIZE(af)) { + *errnop = EINVAL; + *h_errnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; + } + + r = sd_bus_open_system(&bus); + if (r < 0) + goto fallback; + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveAddress"); + if (r < 0) + goto fail; + + r = sd_bus_message_set_auto_start(req, false); + if (r < 0) + goto fail; + + r = sd_bus_message_append(req, "ii", 0, af); + if (r < 0) + goto fail; + + r = sd_bus_message_append_array(req, 'y', addr, len); + if (r < 0) + goto fail; + + r = sd_bus_message_append(req, "t", (uint64_t) 0); + if (r < 0) + goto fail; + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + if (sd_bus_error_has_name(&error, _BUS_ERROR_DNS "NXDOMAIN")) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + if (bus_error_shall_fallback(&error)) + goto fallback; + + + *errnop = -r; + *h_errnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; + } + + r = sd_bus_message_enter_container(reply, 'a', "(is)"); + if (r < 0) + goto fail; + + while ((r = sd_bus_message_read(reply, "(is)", &ifindex, &n)) > 0) { + + if (ifindex < 0) { + r = -EINVAL; + goto fail; + } + + c++; + ms += ALIGN(strlen(n) + 1); + } + if (r < 0) + goto fail; + + r = sd_bus_message_rewind(reply, false); + if (r < 0) + return r; + + if (c <= 0) { + *errnop = ESRCH; + *h_errnop = HOST_NOT_FOUND; + return NSS_STATUS_NOTFOUND; + } + + ms += ALIGN(len) + /* the address */ + 2 * sizeof(char*) + /* pointers to the address, plus trailing NULL */ + c * sizeof(char*); /* pointers to aliases, plus trailing NULL */ + + if (buflen < ms) { + *errnop = ENOMEM; + *h_errnop = TRY_AGAIN; + return NSS_STATUS_TRYAGAIN; + } + + /* First, place address */ + r_addr = buffer; + memcpy(r_addr, addr, len); + idx = ALIGN(len); + + /* Second, place address list */ + r_addr_list = buffer + idx; + ((char**) r_addr_list)[0] = r_addr; + ((char**) r_addr_list)[1] = NULL; + idx += sizeof(char*) * 2; + + /* Third, reserve space for the aliases array */ + r_aliases = buffer + idx; + idx += sizeof(char*) * c; + + /* Fourth, place aliases */ + i = 0; + r_name = buffer + idx; + while ((r = sd_bus_message_read(reply, "(is)", &ifindex, &n)) > 0) { + char *p; + size_t l; + + l = strlen(n); + p = buffer + idx; + memcpy(p, n, l+1); + + if (i > 0) + ((char**) r_aliases)[i-1] = p; + i++; + + idx += ALIGN(l+1); + } + if (r < 0) + goto fail; + + ((char**) r_aliases)[c-1] = NULL; + assert(idx == ms); + + result->h_name = r_name; + result->h_aliases = (char**) r_aliases; + result->h_addrtype = af; + result->h_length = len; + result->h_addr_list = (char**) r_addr_list; + + if (ttlp) + *ttlp = 0; + + /* Explicitly reset all error variables */ + *errnop = 0; + *h_errnop = NETDB_SUCCESS; + h_errno = 0; + + return NSS_STATUS_SUCCESS; + +fallback: + fallback = (enum nss_status (*)( + const void* addr, socklen_t len, + int af, + struct hostent *result, + char *buffer, size_t buflen, + int *errnop, int *h_errnop, + int32_t *ttlp)) + find_fallback("libnss_dns.so.2", "_nss_dns_gethostbyaddr2_r"); + + if (fallback) + return fallback(addr, len, af, result, buffer, buflen, errnop, h_errnop, ttlp); + +fail: + *errnop = -r; + *h_errnop = NO_RECOVERY; + return NSS_STATUS_UNAVAIL; +} + +NSS_GETHOSTBYNAME_FALLBACKS(resolve); +NSS_GETHOSTBYADDR_FALLBACKS(resolve); diff --git a/src/resolve/nss-resolve/nss-resolve.sym b/src/resolve/nss-resolve/nss-resolve.sym new file mode 100644 index 0000000000..df8dff2a20 --- /dev/null +++ b/src/resolve/nss-resolve/nss-resolve.sym @@ -0,0 +1,19 @@ +/*** + 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_resolve_gethostbyname_r; + _nss_resolve_gethostbyname2_r; + _nss_resolve_gethostbyname3_r; + _nss_resolve_gethostbyname4_r; + _nss_resolve_gethostbyaddr_r; + _nss_resolve_gethostbyaddr2_r; +local: *; +}; diff --git a/src/resolve/org.freedesktop.resolve1.conf b/src/resolve/org.freedesktop.resolve1.conf deleted file mode 100644 index 25b09774e5..0000000000 --- a/src/resolve/org.freedesktop.resolve1.conf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/resolve/org.freedesktop.resolve1.service b/src/resolve/org.freedesktop.resolve1.service deleted file mode 100644 index 7ac5c323f0..0000000000 --- a/src/resolve/org.freedesktop.resolve1.service +++ /dev/null @@ -1,12 +0,0 @@ -# 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. - -[D-BUS Service] -Name=org.freedesktop.resolve1 -Exec=/bin/false -User=root -SystemdService=dbus-org.freedesktop.resolve1.service diff --git a/src/resolve/resolve-tool.c b/src/resolve/resolve-tool.c deleted file mode 100644 index 824cb267b5..0000000000 --- a/src/resolve/resolve-tool.c +++ /dev/null @@ -1,1272 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include - -#include "sd-bus.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 "parse-util.h" -#include "resolved-def.h" -#include "resolved-dns-packet.h" -#include "terminal-util.h" - -#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) - -static int arg_family = AF_UNSPEC; -static int arg_ifindex = 0; -static uint16_t arg_type = 0; -static uint16_t arg_class = 0; -static bool arg_legend = true; -static uint64_t arg_flags = 0; - -static enum { - MODE_RESOLVE_HOST, - MODE_RESOLVE_RECORD, - MODE_RESOLVE_SERVICE, - MODE_STATISTICS, - MODE_RESET_STATISTICS, -} arg_mode = MODE_RESOLVE_HOST; - -static void print_source(uint64_t flags, usec_t rtt) { - char rtt_str[FORMAT_TIMESTAMP_MAX]; - - if (!arg_legend) - return; - - if (flags == 0) - return; - - fputs("\n-- Information acquired via", stdout); - - if (flags != 0) - printf(" protocol%s%s%s%s%s", - flags & SD_RESOLVED_DNS ? " DNS" :"", - flags & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "", - flags & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "", - flags & SD_RESOLVED_MDNS_IPV4 ? "mDNS/IPv4" : "", - flags & SD_RESOLVED_MDNS_IPV6 ? "mDNS/IPv6" : ""); - - assert_se(format_timespan(rtt_str, sizeof(rtt_str), rtt, 100)); - - printf(" in %s", rtt_str); - - fputc('.', stdout); - fputc('\n', stdout); - - printf("-- Data is authenticated: %s\n", yes_no(flags & SD_RESOLVED_AUTHENTICATED)); -} - -static int resolve_host(sd_bus *bus, const char *name) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *canonical = NULL; - char ifname[IF_NAMESIZE] = ""; - unsigned c = 0; - int r; - uint64_t flags; - usec_t ts; - - assert(name); - - if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - - log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveHostname"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(&error, r)); - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - _cleanup_free_ char *pretty = NULL; - int ifindex, family; - const void *a; - size_t sz; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (!IN_SET(family, AF_INET, AF_INET6)) { - log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); - continue; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); - return -EINVAL; - } - - ifname[0] = 0; - 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); - if (r < 0) - return log_error_errno(r, "Failed to print address for %s: %m", name); - - printf("%*s%s %s%s%s\n", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - pretty, - isempty(ifname) ? "" : "%", ifname); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "st", &canonical, &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(name, canonical)) - printf("%*s%s (%s)\n", - (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", - canonical); - - if (c == 0) { - log_error("%s: no addresses found", name); - return -ESRCH; - } - - print_source(flags, ts); - - return 0; -} - -static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *pretty = NULL; - char ifname[IF_NAMESIZE] = ""; - uint64_t flags; - unsigned c = 0; - usec_t ts; - int r; - - assert(bus); - assert(IN_SET(family, AF_INET, AF_INET6)); - assert(address); - - if (ifindex <= 0) - ifindex = arg_ifindex; - - r = in_addr_to_string(family, address, &pretty); - if (r < 0) - return log_oom(); - - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveAddress"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "ii", ifindex, family); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family)); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "t", arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r)); - return r; - } - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(is)"); - if (r < 0) - return bus_log_create_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "is")) > 0) { - const char *n; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "is", &ifindex, &n); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return r; - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - printf("%*s%*s%*s%s %s\n", - (int) strlen(pretty), c == 0 ? pretty : "", - isempty(ifname) ? 0 : 1, c > 0 || isempty(ifname) ? "" : "%", - (int) strlen(ifname), c == 0 ? ifname : "", - c == 0 ? ":" : " ", - n); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (c == 0) { - log_error("%s: no names found", pretty); - return -ESRCH; - } - - print_source(flags, ts); - - 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 resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char ifname[IF_NAMESIZE] = ""; - unsigned n = 0; - uint64_t flags; - int r; - usec_t ts; - - assert(name); - - if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - - log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(class), dns_type_to_string(type), isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveRecord"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) { - log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r)); - return r; - } - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(iqqay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - const char *s; - uint16_t c, t; - int ifindex; - const void *d; - size_t l; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "iqq", &ifindex, &c, &t); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &d, &l); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); - if (r < 0) - return log_oom(); - - p->refuse_compression = true; - - r = dns_packet_append_blob(p, d, l, NULL); - if (r < 0) - return log_oom(); - - r = dns_packet_read_rr(p, &rr, NULL, NULL); - if (r < 0) - return log_error_errno(r, "Failed to parse RR: %m"); - - s = dns_resource_record_to_string(rr); - if (!s) - return log_oom(); - - ifname[0] = 0; - if (ifindex > 0 && !if_indextoname(ifindex, ifname)) - log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); - - printf("%s%s%s\n", s, isempty(ifname) ? "" : " # interface ", ifname); - n++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "t", &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (n == 0) { - log_error("%s: no records found", name); - return -ESRCH; - } - - print_source(flags, ts); - - return 0; -} - -static int resolve_rfc4501(sd_bus *bus, const char *name) { - uint16_t type = 0, class = 0; - const char *p, *q, *n; - int r; - - assert(bus); - assert(name); - assert(startswith(name, "dns:")); - - /* Parse RFC 4501 dns: URIs */ - - p = name + 4; - - if (p[0] == '/') { - const char *e; - - if (p[1] != '/') - goto invalid; - - e = strchr(p + 2, '/'); - if (!e) - goto invalid; - - if (e != p + 2) - log_warning("DNS authority specification not supported; ignoring specified authority."); - - p = e + 1; - } - - q = strchr(p, '?'); - if (q) { - n = strndupa(p, q - p); - q++; - - for (;;) { - const char *f; - - f = startswith_no_case(q, "class="); - if (f) { - _cleanup_free_ char *t = NULL; - const char *e; - - if (class != 0) { - log_error("DNS class specified twice."); - return -EINVAL; - } - - e = strchrnul(f, ';'); - t = strndup(f, e - f); - if (!t) - return log_oom(); - - r = dns_class_from_string(t); - if (r < 0) { - log_error("Unknown DNS class %s.", t); - return -EINVAL; - } - - class = r; - - if (*e == ';') { - q = e + 1; - continue; - } - - break; - } - - f = startswith_no_case(q, "type="); - if (f) { - _cleanup_free_ char *t = NULL; - const char *e; - - if (type != 0) { - log_error("DNS type specified twice."); - return -EINVAL; - } - - e = strchrnul(f, ';'); - t = strndup(f, e - f); - if (!t) - return log_oom(); - - r = dns_type_from_string(t); - if (r < 0) { - log_error("Unknown DNS type %s.", t); - return -EINVAL; - } - - type = r; - - if (*e == ';') { - q = e + 1; - continue; - } - - break; - } - - goto invalid; - } - } else - n = p; - - if (type == 0) - type = arg_type; - if (type == 0) - type = DNS_TYPE_A; - - if (class == 0) - class = arg_class; - if (class == 0) - class = DNS_CLASS_IN; - - return resolve_record(bus, n, class, type); - -invalid: - log_error("Invalid DNS URI: %s", name); - return -EINVAL; -} - -static int resolve_service(sd_bus *bus, const char *name, const char *type, const char *domain) { - const char *canonical_name, *canonical_type, *canonical_domain; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char ifname[IF_NAMESIZE] = ""; - size_t indent, sz; - uint64_t flags; - const char *p; - unsigned c; - usec_t ts; - int r; - - assert(bus); - assert(domain); - - if (isempty(name)) - name = NULL; - if (isempty(type)) - type = NULL; - - if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) - return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); - - if (name) - log_debug("Resolving service \"%s\" of type %s in %s (family %s, interface %s).", name, type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - else if (type) - log_debug("Resolving service type %s of %s (family %s, interface %s).", type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - else - log_debug("Resolving service type %s (family %s, interface %s).", domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); - - r = sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveService"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(req, "isssit", arg_ifindex, name, type, domain, arg_family, arg_flags); - if (r < 0) - return bus_log_create_error(r); - - ts = now(CLOCK_MONOTONIC); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - if (r < 0) - return log_error_errno(r, "Resolve call failed: %s", bus_error_message(&error, r)); - - ts = now(CLOCK_MONOTONIC) - ts; - - r = sd_bus_message_enter_container(reply, 'a', "(qqqsa(iiay)s)"); - if (r < 0) - return bus_log_parse_error(r); - - indent = - (name ? strlen(name) + 1 : 0) + - (type ? strlen(type) + 1 : 0) + - strlen(domain) + 2; - - c = 0; - while ((r = sd_bus_message_enter_container(reply, 'r', "qqqsa(iiay)s")) > 0) { - uint16_t priority, weight, port; - const char *hostname, *canonical; - - r = sd_bus_message_read(reply, "qqqs", &priority, &weight, &port, &hostname); - if (r < 0) - return bus_log_parse_error(r); - - if (name) - printf("%*s%s", (int) strlen(name), c == 0 ? name : "", c == 0 ? "/" : " "); - if (type) - printf("%*s%s", (int) strlen(type), c == 0 ? type : "", c == 0 ? "/" : " "); - - printf("%*s%s %s:%u [priority=%u, weight=%u]\n", - (int) strlen(domain), c == 0 ? domain : "", - c == 0 ? ":" : " ", - hostname, port, - priority, weight); - - r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { - _cleanup_free_ char *pretty = NULL; - int ifindex, family; - const void *a; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(reply, "ii", &ifindex, &family); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_array(reply, 'y', &a, &sz); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - if (!IN_SET(family, AF_INET, AF_INET6)) { - log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); - continue; - } - - if (sz != FAMILY_ADDRESS_SIZE(family)) { - log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); - return -EINVAL; - } - - ifname[0] = 0; - 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); - if (r < 0) - return log_error_errno(r, "Failed to print address for %s: %m", name); - - printf("%*s%s%s%s\n", (int) indent, "", pretty, isempty(ifname) ? "" : "%s", ifname); - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "s", &canonical); - if (r < 0) - return bus_log_parse_error(r); - - if (!streq(hostname, canonical)) - printf("%*s(%s)\n", (int) indent, "", canonical); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_enter_container(reply, 'a', "ay"); - if (r < 0) - return bus_log_parse_error(r); - - c = 0; - while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) { - _cleanup_free_ char *escaped = NULL; - - escaped = cescape_length(p, sz); - if (!escaped) - return log_oom(); - - printf("%*s%s\n", (int) indent, "", escaped); - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(reply, "ssst", &canonical_name, &canonical_type, &canonical_domain, &flags); - if (r < 0) - return bus_log_parse_error(r); - - if (isempty(canonical_name)) - canonical_name = NULL; - if (isempty(canonical_type)) - canonical_type = NULL; - - if (!streq_ptr(name, canonical_name) || - !streq_ptr(type, canonical_type) || - !streq_ptr(domain, canonical_domain)) { - - printf("%*s(", (int) indent, ""); - - if (canonical_name) - printf("%s/", canonical_name); - if (canonical_type) - printf("%s/", canonical_type); - - printf("%s)\n", canonical_domain); - } - - print_source(flags, ts); - - return 0; -} - -static int show_statistics(sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - uint64_t n_current_transactions, n_total_transactions, - cache_size, n_cache_hit, n_cache_miss, - n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate; - int r, dnssec_supported; - - assert(bus); - - r = sd_bus_get_property_trivial(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "DNSSECSupported", - &error, - 'b', - &dnssec_supported); - if (r < 0) - return log_error_errno(r, "Failed to get DNSSEC supported state: %s", bus_error_message(&error, r)); - - printf("DNSSEC supported by current servers: %s%s%s\n\n", - ansi_highlight(), - yes_no(dnssec_supported), - ansi_normal()); - - r = sd_bus_get_property(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "TransactionStatistics", - &error, - &reply, - "(tt)"); - if (r < 0) - return log_error_errno(r, "Failed to get transaction statistics: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "(tt)", - &n_current_transactions, - &n_total_transactions); - if (r < 0) - return bus_log_parse_error(r); - - printf("%sTransactions%s\n" - "Current Transactions: %" PRIu64 "\n" - " Total Transactions: %" PRIu64 "\n", - ansi_highlight(), - ansi_normal(), - n_current_transactions, - n_total_transactions); - - reply = sd_bus_message_unref(reply); - - r = sd_bus_get_property(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "CacheStatistics", - &error, - &reply, - "(ttt)"); - if (r < 0) - return log_error_errno(r, "Failed to get cache statistics: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "(ttt)", - &cache_size, - &n_cache_hit, - &n_cache_miss); - if (r < 0) - return bus_log_parse_error(r); - - printf("\n%sCache%s\n" - " Current Cache Size: %" PRIu64 "\n" - " Cache Hits: %" PRIu64 "\n" - " Cache Misses: %" PRIu64 "\n", - ansi_highlight(), - ansi_normal(), - cache_size, - n_cache_hit, - n_cache_miss); - - reply = sd_bus_message_unref(reply); - - r = sd_bus_get_property(bus, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "DNSSECStatistics", - &error, - &reply, - "(tttt)"); - if (r < 0) - return log_error_errno(r, "Failed to get DNSSEC statistics: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "(tttt)", - &n_dnssec_secure, - &n_dnssec_insecure, - &n_dnssec_bogus, - &n_dnssec_indeterminate); - if (r < 0) - return bus_log_parse_error(r); - - printf("\n%sDNSSEC Verdicts%s\n" - " Secure: %" PRIu64 "\n" - " Insecure: %" PRIu64 "\n" - " Bogus: %" PRIu64 "\n" - " Indeterminate: %" PRIu64 "\n", - ansi_highlight(), - ansi_normal(), - n_dnssec_secure, - n_dnssec_insecure, - n_dnssec_bogus, - n_dnssec_indeterminate); - - return 0; -} - -static int reset_statistics(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", - "ResetStatistics", - &error, - NULL, - NULL); - if (r < 0) - return log_error_errno(r, "Failed to reset statistics: %s", bus_error_message(&error, r)); - - return 0; -} - -static void help_protocol_types(void) { - if (arg_legend) - puts("Known protocol types:"); - puts("dns\nllmnr\nllmnr-ipv4\nllmnr-ipv6"); -} - -static void help_dns_types(void) { - int i; - const char *t; - - if (arg_legend) - puts("Known DNS RR types:"); - for (i = 0; i < _DNS_TYPE_MAX; i++) { - t = dns_type_to_string(i); - if (t) - puts(t); - } -} - -static void help_dns_classes(void) { - int i; - const char *t; - - if (arg_legend) - puts("Known DNS RR classes:"); - for (i = 0; i < _DNS_CLASS_MAX; i++) { - t = dns_class_to_string(i); - if (t) - puts(t); - } -} - -static void help(void) { - printf("%s [OPTIONS...] NAME...\n" - "%s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n\n" - "Resolve domain names, IPv4 and IPv6 addresses, DNS resource records, and services.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " -4 Resolve IPv4 addresses\n" - " -6 Resolve IPv6 addresses\n" - " -i --interface=INTERFACE Look on interface\n" - " -p --protocol=PROTOCOL|help Look via protocol\n" - " -t --type=TYPE|help Query RR with DNS type\n" - " -c --class=CLASS|help Query RR with DNS class\n" - " --service Resolve service (SRV)\n" - " --service-address=BOOL Do [not] resolve address for services\n" - " --service-txt=BOOL Do [not] resolve TXT records for services\n" - " --cname=BOOL Do [not] follow CNAME redirects\n" - " --search=BOOL Do [not] use search domains\n" - " --legend=BOOL Do [not] print column headers and meta information\n" - " --statistics Show resolver statistics\n" - " --reset-statistics Reset resolver statistics\n" - , program_invocation_short_name, program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - enum { - ARG_VERSION = 0x100, - ARG_LEGEND, - ARG_SERVICE, - ARG_CNAME, - ARG_SERVICE_ADDRESS, - ARG_SERVICE_TXT, - ARG_SEARCH, - ARG_STATISTICS, - ARG_RESET_STATISTICS, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "class", required_argument, NULL, 'c' }, - { "legend", required_argument, NULL, ARG_LEGEND }, - { "interface", required_argument, NULL, 'i' }, - { "protocol", required_argument, NULL, 'p' }, - { "cname", required_argument, NULL, ARG_CNAME }, - { "service", no_argument, NULL, ARG_SERVICE }, - { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, - { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, - { "search", required_argument, NULL, ARG_SEARCH }, - { "statistics", no_argument, NULL, ARG_STATISTICS, }, - { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0) - switch(c) { - - case 'h': - help(); - return 0; /* done */; - - case ARG_VERSION: - return version(); - - case '4': - arg_family = AF_INET; - break; - - case '6': - arg_family = AF_INET6; - break; - - case 'i': { - int ifi; - - if (parse_ifindex(optarg, &ifi) >= 0) - arg_ifindex = ifi; - else { - ifi = if_nametoindex(optarg); - if (ifi <= 0) - return log_error_errno(errno, "Unknown interface %s: %m", optarg); - - arg_ifindex = ifi; - } - - break; - } - - case 't': - if (streq(optarg, "help")) { - help_dns_types(); - return 0; - } - - r = dns_type_from_string(optarg); - if (r < 0) { - log_error("Failed to parse RR record type %s", optarg); - return r; - } - arg_type = (uint16_t) r; - assert((int) arg_type == r); - - arg_mode = MODE_RESOLVE_RECORD; - break; - - case 'c': - if (streq(optarg, "help")) { - help_dns_classes(); - return 0; - } - - r = dns_class_from_string(optarg); - if (r < 0) { - log_error("Failed to parse RR record class %s", optarg); - return r; - } - arg_class = (uint16_t) r; - assert((int) arg_class == r); - - break; - - case ARG_LEGEND: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --legend= argument"); - - arg_legend = r; - break; - - case 'p': - if (streq(optarg, "help")) { - help_protocol_types(); - return 0; - } else if (streq(optarg, "dns")) - arg_flags |= SD_RESOLVED_DNS; - else if (streq(optarg, "llmnr")) - arg_flags |= SD_RESOLVED_LLMNR; - else if (streq(optarg, "llmnr-ipv4")) - arg_flags |= SD_RESOLVED_LLMNR_IPV4; - else if (streq(optarg, "llmnr-ipv6")) - arg_flags |= SD_RESOLVED_LLMNR_IPV6; - else { - log_error("Unknown protocol specifier: %s", optarg); - return -EINVAL; - } - - break; - - case ARG_SERVICE: - arg_mode = MODE_RESOLVE_SERVICE; - break; - - case ARG_CNAME: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --cname= argument."); - if (r == 0) - arg_flags |= SD_RESOLVED_NO_CNAME; - else - arg_flags &= ~SD_RESOLVED_NO_CNAME; - break; - - case ARG_SERVICE_ADDRESS: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --service-address= argument."); - if (r == 0) - arg_flags |= SD_RESOLVED_NO_ADDRESS; - else - arg_flags &= ~SD_RESOLVED_NO_ADDRESS; - break; - - case ARG_SERVICE_TXT: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --service-txt= argument."); - if (r == 0) - arg_flags |= SD_RESOLVED_NO_TXT; - else - arg_flags &= ~SD_RESOLVED_NO_TXT; - break; - - case ARG_SEARCH: - r = parse_boolean(optarg); - if (r < 0) - return log_error_errno(r, "Failed to parse --search argument."); - if (r == 0) - arg_flags |= SD_RESOLVED_NO_SEARCH; - else - arg_flags &= ~SD_RESOLVED_NO_SEARCH; - break; - - case ARG_STATISTICS: - arg_mode = MODE_STATISTICS; - break; - - case ARG_RESET_STATISTICS: - arg_mode = MODE_RESET_STATISTICS; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (arg_type == 0 && arg_class != 0) { - log_error("--class= may only be used in conjunction with --type=."); - return -EINVAL; - } - - if (arg_type != 0 && arg_mode != MODE_RESOLVE_RECORD) { - log_error("--service and --type= may not be combined."); - return -EINVAL; - } - - if (arg_type != 0 && arg_class == 0) - arg_class = DNS_CLASS_IN; - - if (arg_class != 0 && arg_type == 0) - arg_type = DNS_TYPE_A; - - return 1 /* work to do */; -} - -int main(int argc, char **argv) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = sd_bus_open_system(&bus); - if (r < 0) { - log_error_errno(r, "sd_bus_open_system: %m"); - goto finish; - } - - switch (arg_mode) { - - case MODE_RESOLVE_HOST: - if (optind >= argc) { - log_error("No arguments passed."); - r = -EINVAL; - goto finish; - } - - while (argv[optind]) { - int family, ifindex, k; - union in_addr_union a; - - if (startswith(argv[optind], "dns:")) - k = resolve_rfc4501(bus, argv[optind]); - else { - k = parse_address(argv[optind], &family, &a, &ifindex); - if (k >= 0) - k = resolve_address(bus, family, &a, ifindex); - else - k = resolve_host(bus, argv[optind]); - } - - if (r == 0) - r = k; - - optind++; - } - break; - - case MODE_RESOLVE_RECORD: - if (optind >= argc) { - log_error("No arguments passed."); - r = -EINVAL; - goto finish; - } - - while (argv[optind]) { - int k; - - k = resolve_record(bus, argv[optind], arg_class, arg_type); - if (r == 0) - r = k; - - optind++; - } - break; - - case MODE_RESOLVE_SERVICE: - if (argc < optind + 1) { - log_error("Domain specification required."); - r = -EINVAL; - goto finish; - - } else if (argc == optind + 1) - r = resolve_service(bus, NULL, NULL, argv[optind]); - else if (argc == optind + 2) - r = resolve_service(bus, NULL, argv[optind], argv[optind+1]); - else if (argc == optind + 3) - r = resolve_service(bus, argv[optind], argv[optind+1], argv[optind+2]); - else { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - break; - - case MODE_STATISTICS: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = show_statistics(bus); - break; - - case MODE_RESET_STATISTICS: - if (argc > optind) { - log_error("Too many arguments."); - r = -EINVAL; - goto finish; - } - - r = reset_statistics(bus); - break; - } - -finish: - return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/src/resolve/resolved-bus.c b/src/resolve/resolved-bus.c deleted file mode 100644 index fc5e6beca0..0000000000 --- a/src/resolve/resolved-bus.c +++ /dev/null @@ -1,1565 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-util.h" -#include "dns-domain.h" -#include "resolved-bus.h" -#include "resolved-def.h" -#include "resolved-link-bus.h" - -static int reply_query_state(DnsQuery *q) { - - switch (q->state) { - - case DNS_TRANSACTION_NO_SERVERS: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); - - case DNS_TRANSACTION_TIMEOUT: - return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "Query timed out"); - - case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED: - return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "All attempts to contact name servers or networks failed"); - - case DNS_TRANSACTION_INVALID_REPLY: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); - - case DNS_TRANSACTION_ERRNO: - return sd_bus_reply_method_errnof(q->request, q->answer_errno, "Lookup failed due to system error: %m"); - - case DNS_TRANSACTION_ABORTED: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted"); - - case DNS_TRANSACTION_DNSSEC_FAILED: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s", - dnssec_result_to_string(q->answer_dnssec_result)); - - case DNS_TRANSACTION_NO_TRUST_ANCHOR: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known"); - - case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_RR_TYPE_UNSUPPORTED, "Server does not support requested resource record type"); - - case DNS_TRANSACTION_NETWORK_DOWN: - return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NETWORK_DOWN, "Network is down"); - - case DNS_TRANSACTION_NOT_FOUND: - /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we - * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ - return sd_bus_reply_method_errorf(q->request, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); - - case DNS_TRANSACTION_RCODE_FAILURE: { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - if (q->answer_rcode == DNS_RCODE_NXDOMAIN) - sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); - else { - const char *rc, *n; - char p[DECIMAL_STR_MAX(q->answer_rcode)]; - - rc = dns_rcode_to_string(q->answer_rcode); - if (!rc) { - sprintf(p, "%i", q->answer_rcode); - rc = p; - } - - n = strjoina(_BUS_ERROR_DNS, rc); - sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", dns_query_string(q), rc); - } - - return sd_bus_reply_method_error(q->request, &error); - } - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - case DNS_TRANSACTION_SUCCESS: - default: - assert_not_reached("Impossible state"); - } -} - -static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifindex) { - int r; - - assert(reply); - assert(rr); - - r = sd_bus_message_open_container(reply, 'r', "iiay"); - if (r < 0) - return r; - - r = sd_bus_message_append(reply, "i", ifindex); - if (r < 0) - return r; - - if (rr->key->type == DNS_TYPE_A) { - r = sd_bus_message_append(reply, "i", AF_INET); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &rr->a.in_addr, sizeof(struct in_addr)); - - } else if (rr->key->type == DNS_TYPE_AAAA) { - r = sd_bus_message_append(reply, "i", AF_INET6); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &rr->aaaa.in6_addr, sizeof(struct in6_addr)); - } else - return -EAFNOSUPPORT; - - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return 0; -} - -static void bus_method_resolve_hostname_complete(DnsQuery *q) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - DnsResourceRecord *rr; - unsigned added = 0; - int ifindex, r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname(q); - if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - goto finish; - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - DnsQuestion *question; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = append_address(reply, rr, ifindex); - if (r < 0) - goto finish; - - if (!canonical) - canonical = dns_resource_record_ref(rr); - - added ++; - } - - if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - /* Return the precise spelling and uppercasing and CNAME target reported by the server */ - assert(canonical); - r = sd_bus_message_append( - reply, "st", - DNS_RESOURCE_KEY_NAME(canonical->key), - SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send hostname reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int check_ifindex_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus_error *error) { - assert(flags); - - if (ifindex < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); - - if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); - - if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ - *flags |= SD_RESOLVED_PROTOCOLS_ALL; - - return 0; -} - -static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; - Manager *m = userdata; - const char *hostname; - int family, ifindex; - uint64_t flags; - DnsQuery *q; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "isit", &ifindex, &hostname, &family, &flags); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - r = dns_name_is_valid(hostname); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", hostname); - - r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_SEARCH, error); - if (r < 0) - return r; - - r = dns_question_new_address(&question_utf8, family, hostname, false); - if (r < 0) - return r; - - r = dns_question_new_address(&question_idna, family, hostname, true); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->request_family = family; - q->complete = bus_method_resolve_hostname_complete; - q->suppress_unroutable_family = family == AF_UNSPEC; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -static void bus_method_resolve_address_complete(DnsQuery *q) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - DnsQuestion *question; - DnsResourceRecord *rr; - unsigned added = 0; - int ifindex, r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname(q); - if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(is)"); - if (r < 0) - goto finish; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = sd_bus_message_append(reply, "(is)", ifindex, rr->ptr.name); - if (r < 0) - goto finish; - - added ++; - } - - if (added <= 0) { - _cleanup_free_ char *ip = NULL; - - in_addr_to_string(q->request_family, &q->request_address, &ip); - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Address '%s' does not have any RR of requested type", strna(ip)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send address reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int bus_method_resolve_address(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - Manager *m = userdata; - int family, ifindex; - uint64_t flags; - const void *d; - DnsQuery *q; - size_t sz; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "ii", &ifindex, &family); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - r = sd_bus_message_read_array(message, 'y', &d, &sz); - if (r < 0) - return r; - - if (sz != FAMILY_ADDRESS_SIZE(family)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size"); - - r = sd_bus_message_read(message, "t", &flags); - if (r < 0) - return r; - - r = check_ifindex_flags(ifindex, &flags, 0, error); - if (r < 0) - return r; - - r = dns_question_new_reverse(&question, family, d); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->request_family = family; - memcpy(&q->request_address, d, sz); - q->complete = bus_method_resolve_address_complete; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int ifindex) { - int r; - - assert(m); - assert(rr); - - r = sd_bus_message_open_container(m, 'r', "iqqay"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "iqq", - ifindex, - rr->key->class, - rr->key->type); - if (r < 0) - return r; - - r = dns_resource_record_to_wire_format(rr, false); - if (r < 0) - return r; - - r = sd_bus_message_append_array(m, 'y', rr->wire_format, rr->wire_format_size); - if (r < 0) - return r; - - return sd_bus_message_close_container(m); -} - -static void bus_method_resolve_record_complete(DnsQuery *q) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - DnsResourceRecord *rr; - DnsQuestion *question; - unsigned added = 0; - int ifindex; - int r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname(q); - if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(iqqay)"); - if (r < 0) - goto finish; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = bus_message_append_rr(reply, rr, ifindex); - if (r < 0) - goto finish; - - added ++; - } - - if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_query_string(q)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send record reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - Manager *m = userdata; - uint16_t class, type; - const char *name; - int r, ifindex; - uint64_t flags; - DnsQuery *q; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "isqqt", &ifindex, &name, &class, &type, &flags); - if (r < 0) - return r; - - r = dns_name_is_valid(name); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name); - - 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_obsolete(type)) - return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Specified DNS resource record type %" PRIu16 " is obsolete.", type); - - r = check_ifindex_flags(ifindex, &flags, 0, error); - if (r < 0) - return r; - - question = dns_question_new(1); - if (!question) - return -ENOMEM; - - key = dns_resource_key_new(class, type, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(question, key); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->complete = bus_method_resolve_record_complete; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; - DnsQuery *aux; - int r; - - assert(q); - assert(reply); - assert(rr); - assert(rr->key); - - if (rr->key->type != DNS_TYPE_SRV) - return 0; - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - /* First, let's see if we could find an appropriate A or AAAA - * record for the SRV record */ - LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { - DnsResourceRecord *zz; - DnsQuestion *question; - - if (aux->state != DNS_TRANSACTION_SUCCESS) - continue; - if (aux->auxiliary_result != 0) - continue; - - question = dns_query_question_for_protocol(aux, aux->answer_protocol); - - r = dns_name_equal(dns_question_first_name(question), rr->srv.name); - if (r < 0) - return r; - if (r == 0) - continue; - - DNS_ANSWER_FOREACH(zz, aux->answer) { - - r = dns_question_matches_rr(question, zz, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - canonical = dns_resource_record_ref(zz); - break; - } - - if (canonical) - break; - } - - /* Is there are successful A/AAAA lookup for this SRV RR? If not, don't add it */ - if (!canonical) - return 0; - } - - r = sd_bus_message_open_container(reply, 'r', "qqqsa(iiay)s"); - if (r < 0) - return r; - - r = sd_bus_message_append( - reply, - "qqqs", - rr->srv.priority, rr->srv.weight, rr->srv.port, rr->srv.name); - if (r < 0) - return r; - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - return r; - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { - DnsResourceRecord *zz; - DnsQuestion *question; - int ifindex; - - if (aux->state != DNS_TRANSACTION_SUCCESS) - continue; - if (aux->auxiliary_result != 0) - continue; - - question = dns_query_question_for_protocol(aux, aux->answer_protocol); - - r = dns_name_equal(dns_question_first_name(question), rr->srv.name); - if (r < 0) - return r; - if (r == 0) - continue; - - DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) { - - r = dns_question_matches_rr(question, zz, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - r = append_address(reply, zz, ifindex); - if (r < 0) - return r; - } - } - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - /* Note that above we appended the hostname as encoded in the - * SRV, and here the canonical hostname this maps to. */ - r = sd_bus_message_append(reply, "s", canonical ? DNS_RESOURCE_KEY_NAME(canonical->key) : rr->srv.name); - if (r < 0) - return r; - - r = sd_bus_message_close_container(reply); - if (r < 0) - return r; - - return 1; -} - -static int append_txt(sd_bus_message *reply, DnsResourceRecord *rr) { - DnsTxtItem *i; - int r; - - assert(reply); - assert(rr); - assert(rr->key); - - if (rr->key->type != DNS_TYPE_TXT) - return 0; - - LIST_FOREACH(items, i, rr->txt.items) { - - if (i->length <= 0) - continue; - - r = sd_bus_message_append_array(reply, 'y', i->data, i->length); - if (r < 0) - return r; - } - - return 1; -} - -static void resolve_service_all_complete(DnsQuery *q) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; - DnsQuestion *question; - DnsResourceRecord *rr; - unsigned added = 0; - DnsQuery *aux; - int r; - - assert(q); - - if (q->block_all_complete > 0) - return; - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - DnsQuery *bad = NULL; - bool have_success = false; - - LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { - - switch (aux->state) { - - case DNS_TRANSACTION_PENDING: - /* If an auxiliary query is still pending, let's wait */ - return; - - case DNS_TRANSACTION_SUCCESS: - if (aux->auxiliary_result == 0) - have_success = true; - else - bad = aux; - break; - - default: - bad = aux; - break; - } - } - - if (!have_success) { - /* We can only return one error, hence pick the last error we encountered */ - - assert(bad); - - if (bad->state == DNS_TRANSACTION_SUCCESS) { - assert(bad->auxiliary_result != 0); - - if (bad->auxiliary_result == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(bad)); - goto finish; - } - - r = bad->auxiliary_result; - goto finish; - } - - r = reply_query_state(bad); - goto finish; - } - } - - r = sd_bus_message_new_method_return(q->request, &reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "(qqqsa(iiay)s)"); - if (r < 0) - goto finish; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = append_srv(q, reply, rr); - if (r < 0) - goto finish; - if (r == 0) /* not an SRV record */ - continue; - - if (!canonical) - canonical = dns_resource_record_ref(rr); - - added++; - } - - if (added <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - r = sd_bus_message_open_container(reply, 'a', "ay"); - if (r < 0) - goto finish; - - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - r = append_txt(reply, rr); - if (r < 0) - goto finish; - } - - r = sd_bus_message_close_container(reply); - if (r < 0) - goto finish; - - assert(canonical); - r = dns_service_split(DNS_RESOURCE_KEY_NAME(canonical->key), &name, &type, &domain); - if (r < 0) - goto finish; - - r = sd_bus_message_append( - reply, - "ssst", - name, type, domain, - SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); - if (r < 0) - goto finish; - - r = sd_bus_send(q->manager->bus, reply, NULL); - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send service reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static void resolve_service_hostname_complete(DnsQuery *q) { - int r; - - assert(q); - assert(q->auxiliary_for); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - resolve_service_all_complete(q->auxiliary_for); - return; - } - - r = dns_query_process_cname(q); - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - /* This auxiliary lookup is finished or failed, let's see if all are finished now. */ - q->auxiliary_result = r; - resolve_service_all_complete(q->auxiliary_for); -} - -static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifindex) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - DnsQuery *aux; - int r; - - assert(q); - assert(rr); - assert(rr->key); - assert(rr->key->type == DNS_TYPE_SRV); - - /* OK, we found an SRV record for the service. Let's resolve - * the hostname included in it */ - - r = dns_question_new_address(&question, q->request_family, rr->srv.name, false); - if (r < 0) - return r; - - r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - aux->request_family = q->request_family; - aux->complete = resolve_service_hostname_complete; - - r = dns_query_make_auxiliary(aux, q); - if (r == -EAGAIN) { - /* Too many auxiliary lookups? If so, don't complain, - * let's just not add this one, we already have more - * than enough */ - - dns_query_free(aux); - return 0; - } - if (r < 0) - goto fail; - - /* Note that auxiliary queries do not track the original bus - * client, only the primary request does that. */ - - r = dns_query_go(aux); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(aux); - return r; -} - -static void bus_method_resolve_service_complete(DnsQuery *q) { - bool has_root_domain = false; - DnsResourceRecord *rr; - DnsQuestion *question; - unsigned found = 0; - int ifindex, r; - - assert(q); - - if (q->state != DNS_TRANSACTION_SUCCESS) { - r = reply_query_state(q); - goto finish; - } - - r = dns_query_process_cname(q); - if (r == -ELOOP) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); - goto finish; - } - if (r < 0) - goto finish; - if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ - return; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { - r = dns_question_matches_rr(question, rr, NULL); - if (r < 0) - goto finish; - if (r == 0) - continue; - - if (rr->key->type != DNS_TYPE_SRV) - continue; - - if (dns_name_is_root(rr->srv.name)) { - has_root_domain = true; - continue; - } - - if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { - q->block_all_complete ++; - r = resolve_service_hostname(q, rr, ifindex); - q->block_all_complete --; - - if (r < 0) - goto finish; - } - - found++; - } - - if (has_root_domain && found <= 0) { - /* If there's exactly one SRV RR and it uses - * the root domain as host name, then the - * service is explicitly not offered on the - * domain. Report this as a recognizable - * error. See RFC 2782, Section "Usage - * Rules". */ - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_SERVICE, "'%s' does not provide the requested service", dns_query_string(q)); - goto finish; - } - - if (found <= 0) { - r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); - goto finish; - } - - /* Maybe we are already finished? check now... */ - resolve_service_all_complete(q); - return; - -finish: - if (r < 0) { - log_error_errno(r, "Failed to send service reply: %m"); - sd_bus_reply_method_errno(q->request, r, NULL); - } - - dns_query_free(q); -} - -static int bus_method_resolve_service(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; - const char *name, *type, *domain; - _cleanup_free_ char *n = NULL; - Manager *m = userdata; - int family, ifindex; - uint64_t flags; - DnsQuery *q; - int r; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_read(message, "isssit", &ifindex, &name, &type, &domain, &family, &flags); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - if (isempty(name)) - name = NULL; - else if (!dns_service_name_is_valid(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name); - - if (isempty(type)) - type = NULL; - else if (!dns_srv_type_is_valid(type)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid SRV service type '%s'", type); - - r = dns_name_is_valid(domain); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid domain '%s'", domain); - - if (name && !type) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Service name cannot be specified without service type."); - - r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error); - if (r < 0) - return r; - - r = dns_question_new_service(&question_utf8, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), false); - if (r < 0) - return r; - - r = dns_question_new_service(&question_idna, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), true); - if (r < 0) - return r; - - r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH); - if (r < 0) - return r; - - q->request = sd_bus_message_ref(message); - q->request_family = family; - q->complete = bus_method_resolve_service_complete; - - r = dns_query_bus_track(q, message); - if (r < 0) - goto fail; - - r = dns_query_go(q); - if (r < 0) - goto fail; - - return 1; - -fail: - dns_query_free(q); - return r; -} - -int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex) { - int r; - - assert(reply); - assert(s); - - r = sd_bus_message_open_container(reply, 'r', with_ifindex ? "iiay" : "iay"); - if (r < 0) - return r; - - if (with_ifindex) { - r = sd_bus_message_append(reply, "i", s->link ? s->link->ifindex : 0); - if (r < 0) - return r; - } - - r = sd_bus_message_append(reply, "i", s->family); - if (r < 0) - return r; - - r = sd_bus_message_append_array(reply, 'y', &s->address, FAMILY_ADDRESS_SIZE(s->family)); - if (r < 0) - return r; - - return sd_bus_message_close_container(reply); -} - -static int bus_property_get_dns_servers( - 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; - unsigned c = 0; - DnsServer *s; - Iterator i; - Link *l; - int r; - - assert(reply); - assert(m); - - r = sd_bus_message_open_container(reply, 'a', "(iiay)"); - if (r < 0) - return r; - - LIST_FOREACH(servers, s, m->dns_servers) { - r = bus_dns_server_append(reply, s, true); - if (r < 0) - return r; - - c++; - } - - HASHMAP_FOREACH(l, m->links, i) { - LIST_FOREACH(servers, s, l->dns_servers) { - r = bus_dns_server_append(reply, s, true); - if (r < 0) - return r; - c++; - } - } - - if (c == 0) { - LIST_FOREACH(servers, s, m->fallback_dns_servers) { - r = bus_dns_server_append(reply, s, true); - if (r < 0) - return r; - } - } - - return sd_bus_message_close_container(reply); -} - -static int bus_property_get_search_domains( - 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; - DnsSearchDomain *d; - Iterator i; - Link *l; - int r; - - assert(reply); - assert(m); - - r = sd_bus_message_open_container(reply, 'a', "(isb)"); - if (r < 0) - return r; - - LIST_FOREACH(domains, d, m->search_domains) { - r = sd_bus_message_append(reply, "(isb)", 0, d->name, d->route_only); - if (r < 0) - return r; - } - - HASHMAP_FOREACH(l, m->links, i) { - LIST_FOREACH(domains, d, l->search_domains) { - r = sd_bus_message_append(reply, "(isb)", l->ifindex, d->name, d->route_only); - if (r < 0) - return r; - } - } - - return sd_bus_message_close_container(reply); -} - -static int bus_property_get_transaction_statistics( - 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; - - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "(tt)", - (uint64_t) hashmap_size(m->dns_transactions), - (uint64_t) m->n_transactions_total); -} - -static int bus_property_get_cache_statistics( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - uint64_t size = 0, hit = 0, miss = 0; - Manager *m = userdata; - DnsScope *s; - - assert(reply); - assert(m); - - LIST_FOREACH(scopes, s, m->dns_scopes) { - size += dns_cache_size(&s->cache); - hit += s->cache.n_hit; - miss += s->cache.n_miss; - } - - return sd_bus_message_append(reply, "(ttt)", size, hit, miss); -} - -static int bus_property_get_dnssec_statistics( - 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; - - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "(tttt)", - (uint64_t) m->n_dnssec_verdict[DNSSEC_SECURE], - (uint64_t) m->n_dnssec_verdict[DNSSEC_INSECURE], - (uint64_t) m->n_dnssec_verdict[DNSSEC_BOGUS], - (uint64_t) m->n_dnssec_verdict[DNSSEC_INDETERMINATE]); -} - -static int bus_property_get_dnssec_supported( - 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; - - assert(reply); - assert(m); - - return sd_bus_message_append(reply, "b", manager_dnssec_supported(m)); -} - -static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Manager *m = userdata; - DnsScope *s; - - assert(message); - assert(m); - - LIST_FOREACH(scopes, s, m->dns_scopes) - s->cache.n_hit = s->cache.n_miss = 0; - - m->n_transactions_total = 0; - zero(m->n_dnssec_verdict); - - return sd_bus_reply_method_return(message, NULL); -} - -static int get_any_link(Manager *m, int ifindex, Link **ret, sd_bus_error *error) { - Link *l; - - assert(m); - assert(ret); - - if (ifindex <= 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); - - l = hashmap_get(m->links, INT_TO_PTR(ifindex)); - if (!l) - return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_LINK, "Link %i not known", ifindex); - - *ret = l; - 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; - - assert(m); - assert(message); - assert(handler); - - assert_cc(sizeof(int) == sizeof(int32_t)); - r = sd_bus_message_read(message, "i", &ifindex); - if (r < 0) - return r; - - r = get_unmanaged_link(m, ifindex, &l, error); - if (r < 0) - return r; - - return handler(message, l, error); -} - -static int bus_method_set_link_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_dns_servers, error); -} - -static int bus_method_set_link_search_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_search_domains, error); -} - -static int bus_method_set_link_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_llmnr, error); -} - -static int bus_method_set_link_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_mdns, error); -} - -static int bus_method_set_link_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_dnssec, error); -} - -static int bus_method_set_link_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_set_dnssec_negative_trust_anchors, error); -} - -static int bus_method_revert_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { - return call_link_method(userdata, message, bus_link_method_revert, error); -} - -static int bus_method_get_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_free_ char *p = NULL; - Manager *m = userdata; - int r, ifindex; - Link *l; - - assert(message); - assert(m); - - assert_cc(sizeof(int) == sizeof(int32_t)); - r = sd_bus_message_read(message, "i", &ifindex); - if (r < 0) - return r; - - r = get_any_link(m, ifindex, &l, error); - if (r < 0) - return r; - - p = link_bus_path(l); - if (!p) - return -ENOMEM; - - return sd_bus_reply_method_return(message, "o", p); -} - -static const sd_bus_vtable resolve_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0), - SD_BUS_PROPERTY("DNS", "a(iiay)", bus_property_get_dns_servers, 0, 0), - SD_BUS_PROPERTY("SearchDomains", "a(isb)", bus_property_get_search_domains, 0, 0), - SD_BUS_PROPERTY("TransactionStatistics", "(tt)", bus_property_get_transaction_statistics, 0, 0), - 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_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("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_search_domains, 0), - SD_BUS_METHOD("SetLinkLLMNR", "is", NULL, bus_method_set_link_llmnr, 0), - SD_BUS_METHOD("SetLinkMulticastDNS", "is", NULL, bus_method_set_link_mdns, 0), - SD_BUS_METHOD("SetLinkDNSSEC", "is", NULL, bus_method_set_link_dnssec, 0), - SD_BUS_METHOD("SetLinkDNSSECNegativeTrustAnchors", "ias", NULL, bus_method_set_link_dnssec_negative_trust_anchors, 0), - SD_BUS_METHOD("RevertLink", "i", NULL, bus_method_revert_link, 0), - - SD_BUS_VTABLE_END, -}; - -static int on_bus_retry(sd_event_source *s, usec_t usec, void *userdata) { - Manager *m = userdata; - - assert(s); - assert(m); - - m->bus_retry_event_source = sd_event_source_unref(m->bus_retry_event_source); - - manager_connect_bus(m); - return 0; -} - -static int match_prepare_for_sleep(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { - Manager *m = userdata; - int b, r; - - assert(message); - assert(m); - - r = sd_bus_message_read(message, "b", &b); - if (r < 0) { - log_debug_errno(r, "Failed to parse PrepareForSleep signal: %m"); - return 0; - } - - if (b) - return 0; - - log_debug("Coming back from suspend, verifying all RRs..."); - - manager_verify_all(m); - return 0; -} - -int manager_connect_bus(Manager *m) { - int r; - - assert(m); - - if (m->bus) - return 0; - - r = sd_bus_default_system(&m->bus); - if (r < 0) { - /* We failed to connect? Yuck, we must be in early - * boot. Let's try in 5s again. As soon as we have - * kdbus we can stop doing this... */ - - log_debug_errno(r, "Failed to connect to bus, trying again in 5s: %m"); - - r = sd_event_add_time(m->event, &m->bus_retry_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 5*USEC_PER_SEC, 0, on_bus_retry, m); - if (r < 0) - return log_error_errno(r, "Failed to install bus reconnect time event: %m"); - - (void) sd_event_source_set_description(m->bus_retry_event_source, "bus-retry"); - return 0; - } - - r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager", resolve_vtable, m); - if (r < 0) - return log_error_errno(r, "Failed to register object: %m"); - - r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/resolve1/link", "org.freedesktop.resolve1.Link", link_vtable, link_object_find, m); - if (r < 0) - return log_error_errno(r, "Failed to register link objects: %m"); - - r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/resolve1/link", link_node_enumerator, m); - if (r < 0) - return log_error_errno(r, "Failed to register link enumerator: %m"); - - r = sd_bus_request_name(m->bus, "org.freedesktop.resolve1", 0); - if (r < 0) - return log_error_errno(r, "Failed to register name: %m"); - - r = sd_bus_attach_event(m->bus, m->event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - r = sd_bus_add_match(m->bus, &m->prepare_for_sleep_slot, - "type='signal'," - "sender='org.freedesktop.login1'," - "interface='org.freedesktop.login1.Manager'," - "member='PrepareForSleep'," - "path='/org/freedesktop/login1'", - match_prepare_for_sleep, - m); - if (r < 0) - log_error_errno(r, "Failed to add match for PrepareForSleep: %m"); - - return 0; -} diff --git a/src/resolve/resolved-bus.h b/src/resolve/resolved-bus.h deleted file mode 100644 index f49e1337d2..0000000000 --- a/src/resolve/resolved-bus.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "resolved-manager.h" - -int manager_connect_bus(Manager *m); -int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex); diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c deleted file mode 100644 index bb93fbfda2..0000000000 --- a/src/resolve/resolved-conf.c +++ /dev/null @@ -1,236 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . - ***/ - -#include "alloc-util.h" -#include "conf-parser.h" -#include "def.h" -#include "extract-word.h" -#include "parse-util.h" -#include "resolved-conf.h" -#include "string-util.h" - -int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) { - union in_addr_union address; - int family, r; - DnsServer *s; - - assert(m); - assert(word); - - r = in_addr_from_string_auto(word, &family, &address); - if (r < 0) - return r; - - /* Filter out duplicates */ - s = dns_server_find(manager_get_first_dns_server(m, type), family, &address); - if (s) { - /* - * Drop the marker. This is used to find the servers - * that ceased to exist, see - * manager_mark_dns_servers() and - * manager_flush_marked_dns_servers(). - */ - dns_server_move_back_and_unmark(s); - return 0; - } - - return dns_server_new(m, NULL, type, NULL, family, &address); -} - -int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) { - int r; - - assert(m); - assert(string); - - for(;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&string, &word, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - 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); - } - - return 0; -} - -int manager_add_search_domain_by_string(Manager *m, const char *domain) { - DnsSearchDomain *d; - bool route_only; - int r; - - assert(m); - assert(domain); - - route_only = *domain == '~'; - if (route_only) - domain++; - - if (dns_name_is_root(domain) || streq(domain, "*")) { - route_only = true; - domain = "."; - } - - r = dns_search_domain_find(m->search_domains, domain, &d); - if (r < 0) - return r; - if (r > 0) - dns_search_domain_move_back_and_unmark(d); - else { - r = dns_search_domain_new(m, &d, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain); - if (r < 0) - return r; - } - - d->route_only = route_only; - return 0; -} - -int manager_parse_search_domains_and_warn(Manager *m, const char *string) { - int r; - - assert(m); - assert(string); - - for(;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&string, &word, NULL, EXTRACT_QUOTES); - if (r < 0) - return r; - if (r == 0) - break; - - r = manager_add_search_domain_by_string(m, word); - if (r < 0) - log_warning_errno(r, "Failed to add search domain '%s', ignoring.", word); - } - - return 0; -} - -int config_parse_dns_servers( - 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) { - - Manager *m = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(m); - - if (isempty(rvalue)) - /* Empty assignment means clear the list */ - dns_server_unlink_all(manager_get_first_dns_server(m, ltype)); - else { - /* Otherwise, add to the list */ - r = manager_parse_dns_server_string_and_warn(m, ltype, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNS server string '%s'. Ignoring.", rvalue); - return 0; - } - } - - /* If we have a manual setting, then we stop reading - * /etc/resolv.conf */ - if (ltype == DNS_SERVER_SYSTEM) - m->read_resolv_conf = false; - if (ltype == DNS_SERVER_FALLBACK) - m->need_builtin_fallbacks = false; - - return 0; -} - -int config_parse_search_domains( - 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) { - - Manager *m = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(m); - - if (isempty(rvalue)) - /* Empty assignment means clear the list */ - dns_search_domain_unlink_all(m->search_domains); - else { - /* Otherwise, add to the list */ - r = manager_parse_search_domains_and_warn(m, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse search domains string '%s'. Ignoring.", rvalue); - return 0; - } - } - - /* If we have a manual setting, then we stop reading - * /etc/resolv.conf */ - m->read_resolv_conf = false; - - return 0; -} - -int manager_parse_config_file(Manager *m) { - int r; - - assert(m); - - r = config_parse_many(PKGSYSCONFDIR "/resolved.conf", - CONF_PATHS_NULSTR("systemd/resolved.conf.d"), - "Resolve\0", - config_item_perf_lookup, resolved_gperf_lookup, - false, m); - if (r < 0) - return r; - - if (m->need_builtin_fallbacks) { - r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_FALLBACK, DNS_SERVERS); - if (r < 0) - return r; - } - - return 0; - -} diff --git a/src/resolve/resolved-conf.h b/src/resolve/resolved-conf.h deleted file mode 100644 index e1fd2cceec..0000000000 --- a/src/resolve/resolved-conf.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include "resolved-manager.h" - -int manager_parse_config_file(Manager *m); - -int manager_add_search_domain_by_string(Manager *m, const char *domain); -int manager_parse_search_domains_and_warn(Manager *m, const char *string); - -int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word); -int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string); - -const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned length); - -int config_parse_dns_servers(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_search_domains(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_dnssec(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/resolve/resolved-def.h b/src/resolve/resolved-def.h deleted file mode 100644 index c4c1915b18..0000000000 --- a/src/resolve/resolved-def.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#define SD_RESOLVED_DNS (UINT64_C(1) << 0) -#define SD_RESOLVED_LLMNR_IPV4 (UINT64_C(1) << 1) -#define SD_RESOLVED_LLMNR_IPV6 (UINT64_C(1) << 2) -#define SD_RESOLVED_MDNS_IPV4 (UINT64_C(1) << 3) -#define SD_RESOLVED_MDNS_IPV6 (UINT64_C(1) << 4) -#define SD_RESOLVED_NO_CNAME (UINT64_C(1) << 5) -#define SD_RESOLVED_NO_TXT (UINT64_C(1) << 6) -#define SD_RESOLVED_NO_ADDRESS (UINT64_C(1) << 7) -#define SD_RESOLVED_NO_SEARCH (UINT64_C(1) << 8) -#define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9) - -#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) -#define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6) - -#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) diff --git a/src/resolve/resolved-dns-answer.c b/src/resolve/resolved-dns-answer.c deleted file mode 100644 index 7eb303ab95..0000000000 --- a/src/resolve/resolved-dns-answer.c +++ /dev/null @@ -1,858 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "alloc-util.h" -#include "dns-domain.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-dnssec.h" -#include "string-util.h" - -DnsAnswer *dns_answer_new(unsigned n) { - DnsAnswer *a; - - a = malloc0(offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * n); - if (!a) - return NULL; - - a->n_ref = 1; - a->n_allocated = n; - - return a; -} - -DnsAnswer *dns_answer_ref(DnsAnswer *a) { - if (!a) - return NULL; - - assert(a->n_ref > 0); - a->n_ref++; - return a; -} - -static void dns_answer_flush(DnsAnswer *a) { - DnsResourceRecord *rr; - - if (!a) - return; - - DNS_ANSWER_FOREACH(rr, a) - dns_resource_record_unref(rr); - - a->n_rrs = 0; -} - -DnsAnswer *dns_answer_unref(DnsAnswer *a) { - if (!a) - return NULL; - - assert(a->n_ref > 0); - - if (a->n_ref == 1) { - dns_answer_flush(a); - free(a); - } else - a->n_ref--; - - return NULL; -} - -static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { - assert(rr); - - if (!a) - return -ENOSPC; - - if (a->n_rrs >= a->n_allocated) - return -ENOSPC; - - a->items[a->n_rrs++] = (DnsAnswerItem) { - .rr = dns_resource_record_ref(rr), - .ifindex = ifindex, - .flags = flags, - }; - - return 1; -} - -static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int ifindex, r; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, source) { - r = dns_answer_add_raw(a, rr, ifindex, flags); - if (r < 0) - return r; - } - - return 0; -} - -int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { - unsigned i; - int r; - - assert(rr); - - if (!a) - return -ENOSPC; - if (a->n_ref > 1) - return -EBUSY; - - for (i = 0; i < a->n_rrs; i++) { - if (a->items[i].ifindex != ifindex) - continue; - - r = dns_resource_record_equal(a->items[i].rr, rr); - if (r < 0) - return r; - if (r > 0) { - /* Don't mix contradicting TTLs (see below) */ - if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) - return -EINVAL; - - /* Entry already exists, keep the entry with - * the higher RR. */ - if (rr->ttl > a->items[i].rr->ttl) { - dns_resource_record_ref(rr); - dns_resource_record_unref(a->items[i].rr); - a->items[i].rr = rr; - } - - a->items[i].flags |= flags; - return 0; - } - - r = dns_resource_key_equal(a->items[i].rr->key, rr->key); - if (r < 0) - return r; - if (r > 0) { - /* There's already an RR of the same RRset in - * place! Let's see if the TTLs more or less - * match. We don't really care if they match - * precisely, but we do care whether one is 0 - * and the other is not. See RFC 2181, Section - * 5.2.*/ - - if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) - return -EINVAL; - } - } - - return dns_answer_add_raw(a, rr, ifindex, flags); -} - -static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int ifindex, r; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, b) { - r = dns_answer_add(a, rr, ifindex, flags); - if (r < 0) - return r; - } - - return 0; -} - -int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { - int r; - - assert(a); - assert(rr); - - r = dns_answer_reserve_or_clone(a, 1); - if (r < 0) - return r; - - return dns_answer_add(*a, rr, ifindex, flags); -} - -int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *soa = NULL; - - soa = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SOA, name); - if (!soa) - return -ENOMEM; - - soa->ttl = ttl; - - soa->soa.mname = strdup(name); - if (!soa->soa.mname) - return -ENOMEM; - - soa->soa.rname = strappend("root.", name); - if (!soa->soa.rname) - return -ENOMEM; - - soa->soa.serial = 1; - soa->soa.refresh = 1; - soa->soa.retry = 1; - soa->soa.expire = 1; - soa->soa.minimum = ttl; - - return dns_answer_add(a, soa, 0, DNS_ANSWER_AUTHENTICATED); -} - -int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { - DnsAnswerFlags flags = 0, i_flags; - DnsResourceRecord *i; - bool found = false; - int r; - - assert(key); - - DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { - r = dns_resource_key_match_rr(key, i, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - if (!ret_flags) - return 1; - - if (found) - flags &= i_flags; - else { - flags = i_flags; - found = true; - } - } - - if (ret_flags) - *ret_flags = flags; - - return found; -} - -int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *ret_flags) { - DnsAnswerFlags flags = 0, i_flags; - DnsResourceRecord *i; - bool found = false; - int r; - - assert(rr); - - DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { - r = dns_resource_record_equal(i, rr); - if (r < 0) - return r; - if (r == 0) - continue; - - if (!ret_flags) - return 1; - - if (found) - flags &= i_flags; - else { - flags = i_flags; - found = true; - } - } - - if (ret_flags) - *ret_flags = flags; - - return found; -} - -int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { - DnsAnswerFlags flags = 0, i_flags; - DnsResourceRecord *i; - bool found = false; - int r; - - assert(key); - - DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { - r = dns_resource_key_equal(i->key, key); - if (r < 0) - return r; - if (r == 0) - continue; - - if (!ret_flags) - return true; - - if (found) - flags &= i_flags; - else { - flags = i_flags; - found = true; - } - } - - if (ret_flags) - *ret_flags = flags; - - return found; -} - -int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) { - DnsResourceRecord *i; - - DNS_ANSWER_FOREACH(i, a) { - if (IN_SET(i->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3)) - return true; - } - - return false; -} - -int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone) { - DnsResourceRecord *rr; - int r; - - /* Checks whether the specified answer contains at least one NSEC3 RR in the specified zone */ - - DNS_ANSWER_FOREACH(rr, answer) { - const char *p; - - if (rr->key->type != DNS_TYPE_NSEC3) - continue; - - p = DNS_RESOURCE_KEY_NAME(rr->key); - r = dns_name_parent(&p); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_name_equal(p, zone); - if (r != 0) - return r; - } - - return false; -} - -int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { - DnsResourceRecord *rr, *soa = NULL; - DnsAnswerFlags rr_flags, soa_flags = 0; - int r; - - assert(key); - - /* For a SOA record we can never find a matching SOA record */ - if (key->type == DNS_TYPE_SOA) - return 0; - - DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { - r = dns_resource_key_match_soa(key, rr->key); - if (r < 0) - return r; - if (r > 0) { - - if (soa) { - r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(soa->key)); - if (r < 0) - return r; - if (r > 0) - continue; - } - - soa = rr; - soa_flags = rr_flags; - } - } - - if (!soa) - return 0; - - if (ret) - *ret = soa; - if (flags) - *flags = soa_flags; - - return 1; -} - -int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { - DnsResourceRecord *rr; - DnsAnswerFlags rr_flags; - int r; - - assert(key); - - /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ - if (!dns_type_may_redirect(key->type)) - return 0; - - DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { - r = dns_resource_key_match_cname_or_dname(key, rr->key, NULL); - if (r < 0) - return r; - if (r > 0) { - if (ret) - *ret = rr; - if (flags) - *flags = rr_flags; - return 1; - } - } - - return 0; -} - -int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret) { - _cleanup_(dns_answer_unrefp) DnsAnswer *k = NULL; - int r; - - assert(ret); - - if (dns_answer_size(a) <= 0) { - *ret = dns_answer_ref(b); - return 0; - } - - if (dns_answer_size(b) <= 0) { - *ret = dns_answer_ref(a); - return 0; - } - - k = dns_answer_new(a->n_rrs + b->n_rrs); - if (!k) - return -ENOMEM; - - r = dns_answer_add_raw_all(k, a); - if (r < 0) - return r; - - r = dns_answer_add_all(k, b); - if (r < 0) - return r; - - *ret = k; - k = NULL; - - return 0; -} - -int dns_answer_extend(DnsAnswer **a, DnsAnswer *b) { - DnsAnswer *merged; - int r; - - assert(a); - - r = dns_answer_merge(*a, b, &merged); - if (r < 0) - return r; - - dns_answer_unref(*a); - *a = merged; - - return 0; -} - -int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { - bool found = false, other = false; - DnsResourceRecord *rr; - unsigned i; - int r; - - assert(a); - assert(key); - - /* Remove all entries matching the specified key from *a */ - - DNS_ANSWER_FOREACH(rr, *a) { - r = dns_resource_key_equal(rr->key, key); - if (r < 0) - return r; - if (r > 0) - found = true; - else - other = true; - - if (found && other) - break; - } - - if (!found) - return 0; - - if (!other) { - *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ - return 1; - } - - if ((*a)->n_ref > 1) { - _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; - DnsAnswerFlags flags; - int ifindex; - - copy = dns_answer_new((*a)->n_rrs); - if (!copy) - return -ENOMEM; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { - r = dns_resource_key_equal(rr->key, key); - if (r < 0) - return r; - if (r > 0) - continue; - - r = dns_answer_add_raw(copy, rr, ifindex, flags); - if (r < 0) - return r; - } - - dns_answer_unref(*a); - *a = copy; - copy = NULL; - - return 1; - } - - /* Only a single reference, edit in-place */ - - i = 0; - for (;;) { - if (i >= (*a)->n_rrs) - break; - - r = dns_resource_key_equal((*a)->items[i].rr->key, key); - if (r < 0) - return r; - if (r > 0) { - /* Kill this entry */ - - dns_resource_record_unref((*a)->items[i].rr); - memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); - (*a)->n_rrs --; - continue; - - } else - /* Keep this entry */ - i++; - } - - return 1; -} - -int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) { - bool found = false, other = false; - DnsResourceRecord *rr; - unsigned i; - int r; - - assert(a); - assert(rm); - - /* Remove all entries matching the specified RR from *a */ - - DNS_ANSWER_FOREACH(rr, *a) { - r = dns_resource_record_equal(rr, rm); - if (r < 0) - return r; - if (r > 0) - found = true; - else - other = true; - - if (found && other) - break; - } - - if (!found) - return 0; - - if (!other) { - *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ - return 1; - } - - if ((*a)->n_ref > 1) { - _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; - DnsAnswerFlags flags; - int ifindex; - - copy = dns_answer_new((*a)->n_rrs); - if (!copy) - return -ENOMEM; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { - r = dns_resource_record_equal(rr, rm); - if (r < 0) - return r; - if (r > 0) - continue; - - r = dns_answer_add_raw(copy, rr, ifindex, flags); - if (r < 0) - return r; - } - - dns_answer_unref(*a); - *a = copy; - copy = NULL; - - return 1; - } - - /* Only a single reference, edit in-place */ - - i = 0; - for (;;) { - if (i >= (*a)->n_rrs) - break; - - r = dns_resource_record_equal((*a)->items[i].rr, rm); - if (r < 0) - return r; - if (r > 0) { - /* Kill this entry */ - - dns_resource_record_unref((*a)->items[i].rr); - memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); - (*a)->n_rrs --; - continue; - - } else - /* Keep this entry */ - i++; - } - - return 1; -} - -int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags) { - DnsResourceRecord *rr_source; - int ifindex_source, r; - DnsAnswerFlags flags_source; - - assert(a); - assert(key); - - /* Copy all RRs matching the specified key from source into *a */ - - DNS_ANSWER_FOREACH_FULL(rr_source, ifindex_source, flags_source, source) { - - r = dns_resource_key_equal(rr_source->key, key); - if (r < 0) - return r; - if (r == 0) - continue; - - /* Make space for at least one entry */ - r = dns_answer_reserve_or_clone(a, 1); - if (r < 0) - return r; - - r = dns_answer_add(*a, rr_source, ifindex_source, flags_source|or_flags); - if (r < 0) - return r; - } - - return 0; -} - -int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags) { - int r; - - assert(to); - assert(from); - assert(key); - - r = dns_answer_copy_by_key(to, *from, key, or_flags); - if (r < 0) - return r; - - return dns_answer_remove_by_key(from, key); -} - -void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) { - DnsAnswerItem *items; - unsigned i, start, end; - - if (!a) - return; - - if (a->n_rrs <= 1) - return; - - start = 0; - end = a->n_rrs-1; - - /* RFC 4795, Section 2.6 suggests we should order entries - * depending on whether the sender is a link-local address. */ - - items = newa(DnsAnswerItem, a->n_rrs); - for (i = 0; i < a->n_rrs; i++) { - - 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 */ - items[end--] = a->items[i]; - else - /* Order all other records to the beginning of the array */ - items[start++] = a->items[i]; - } - - assert(start == end+1); - memcpy(a->items, items, sizeof(DnsAnswerItem) * a->n_rrs); -} - -int dns_answer_reserve(DnsAnswer **a, unsigned n_free) { - DnsAnswer *n; - - assert(a); - - if (n_free <= 0) - return 0; - - if (*a) { - unsigned ns; - - if ((*a)->n_ref > 1) - return -EBUSY; - - ns = (*a)->n_rrs + n_free; - - if ((*a)->n_allocated >= ns) - return 0; - - /* Allocate more than we need */ - ns *= 2; - - n = realloc(*a, offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * ns); - if (!n) - return -ENOMEM; - - n->n_allocated = ns; - } else { - n = dns_answer_new(n_free); - if (!n) - return -ENOMEM; - } - - *a = n; - return 0; -} - -int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free) { - _cleanup_(dns_answer_unrefp) DnsAnswer *n = NULL; - int r; - - assert(a); - - /* Tries to extend the DnsAnswer object. And if that's not - * possibly, since we are not the sole owner, then allocate a - * new, appropriately sized one. Either way, after this call - * the object will only have a single reference, and has room - * for at least the specified number of RRs. */ - - r = dns_answer_reserve(a, n_free); - if (r != -EBUSY) - return r; - - assert(*a); - - n = dns_answer_new(((*a)->n_rrs + n_free) * 2); - if (!n) - return -ENOMEM; - - r = dns_answer_add_raw_all(n, *a); - if (r < 0) - return r; - - dns_answer_unref(*a); - *a = n; - n = NULL; - - return 0; -} - -void dns_answer_dump(DnsAnswer *answer, FILE *f) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int ifindex; - - if (!f) - f = stdout; - - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { - const char *t; - - fputc('\t', f); - - t = dns_resource_record_to_string(rr); - if (!t) { - log_oom(); - continue; - } - - fputs(t, f); - - if (ifindex != 0 || flags & (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE|DNS_ANSWER_SHARED_OWNER)) - fputs("\t;", f); - - if (ifindex != 0) - printf(" ifindex=%i", ifindex); - if (flags & DNS_ANSWER_AUTHENTICATED) - fputs(" authenticated", f); - if (flags & DNS_ANSWER_CACHEABLE) - fputs(" cachable", f); - if (flags & DNS_ANSWER_SHARED_OWNER) - fputs(" shared-owner", f); - - fputc('\n', f); - } -} - -bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname) { - DnsResourceRecord *rr; - int r; - - assert(cname); - - /* Checks whether the answer contains a DNAME record that indicates that the specified CNAME record is - * synthesized from it */ - - if (cname->key->type != DNS_TYPE_CNAME) - return 0; - - DNS_ANSWER_FOREACH(rr, a) { - _cleanup_free_ char *n = NULL; - - if (rr->key->type != DNS_TYPE_DNAME) - continue; - if (rr->key->class != cname->key->class) - continue; - - r = dns_name_change_suffix(cname->cname.name, rr->dname.name, DNS_RESOURCE_KEY_NAME(rr->key), &n); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_name_equal(n, DNS_RESOURCE_KEY_NAME(cname->key)); - if (r < 0) - return r; - if (r > 0) - return 1; - - } - - return 0; -} diff --git a/src/resolve/resolved-dns-answer.h b/src/resolve/resolved-dns-answer.h deleted file mode 100644 index 8f9c15eab4..0000000000 --- a/src/resolve/resolved-dns-answer.h +++ /dev/null @@ -1,143 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -typedef struct DnsAnswer DnsAnswer; -typedef struct DnsAnswerItem DnsAnswerItem; - -#include "macro.h" -#include "resolved-dns-rr.h" - -/* A simple array of resource records. We keep track of the - * originating ifindex for each RR where that makes sense, so that we - * can qualify A and AAAA RRs referring to a local link with the - * right ifindex. - * - * Note that we usually encode the the empty DnsAnswer object as a simple NULL. */ - -typedef enum DnsAnswerFlags { - DNS_ANSWER_AUTHENTICATED = 1, /* Item has been authenticated */ - DNS_ANSWER_CACHEABLE = 2, /* Item is subject to caching */ - DNS_ANSWER_SHARED_OWNER = 4, /* For mDNS: RRset may be owner by multiple peers */ -} DnsAnswerFlags; - -struct DnsAnswerItem { - DnsResourceRecord *rr; - int ifindex; - DnsAnswerFlags flags; -}; - -struct DnsAnswer { - unsigned n_ref; - unsigned n_rrs, n_allocated; - DnsAnswerItem items[0]; -}; - -DnsAnswer *dns_answer_new(unsigned n); -DnsAnswer *dns_answer_ref(DnsAnswer *a); -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_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); -int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *combined_flags); -int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); -int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a); -int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone); - -int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); -int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); - -int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret); -int dns_answer_extend(DnsAnswer **a, DnsAnswer *b); - -void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local); - -int dns_answer_reserve(DnsAnswer **a, unsigned n_free); -int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free); - -int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key); -int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr); - -int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags); -int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags); - -bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname); - -static inline unsigned dns_answer_size(DnsAnswer *a) { - return a ? a->n_rrs : 0; -} - -void dns_answer_dump(DnsAnswer *answer, FILE *f); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); - -#define _DNS_ANSWER_FOREACH(q, kk, a) \ - for (unsigned UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - 0; \ - }); \ - (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, (kk) = (UNIQ_T(i, q) < (a)->n_rrs ? (a)->items[UNIQ_T(i, q)].rr : NULL)) - -#define DNS_ANSWER_FOREACH(kk, a) _DNS_ANSWER_FOREACH(UNIQ, kk, a) - -#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifi, a) \ - for (unsigned UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ - 0; \ - }); \ - (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, \ - (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ - (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0)) - -#define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) _DNS_ANSWER_FOREACH_IFINDEX(UNIQ, kk, ifindex, a) - -#define _DNS_ANSWER_FOREACH_FLAGS(q, kk, fl, a) \ - for (unsigned UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ - 0; \ - }); \ - (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, \ - (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ - (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) - -#define DNS_ANSWER_FOREACH_FLAGS(kk, flags, a) _DNS_ANSWER_FOREACH_FLAGS(UNIQ, kk, flags, a) - -#define _DNS_ANSWER_FOREACH_FULL(q, kk, ifi, fl, a) \ - for (unsigned UNIQ_T(i, q) = ({ \ - (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ - (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ - (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ - 0; \ - }); \ - (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ - UNIQ_T(i, q)++, \ - (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ - (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0), \ - (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) - -#define DNS_ANSWER_FOREACH_FULL(kk, ifindex, flags, a) _DNS_ANSWER_FOREACH_FULL(UNIQ, kk, ifindex, flags, a) diff --git a/src/resolve/resolved-dns-cache.c b/src/resolve/resolved-dns-cache.c deleted file mode 100644 index 9bcc71724e..0000000000 --- a/src/resolve/resolved-dns-cache.c +++ /dev/null @@ -1,1088 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "alloc-util.h" -#include "dns-domain.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-cache.h" -#include "resolved-dns-packet.h" -#include "string-util.h" - -/* Never cache more than 4K entries. RFC 1536, Section 5 suggests to - * leave DNS caches unbounded, but that's crazy. */ -#define CACHE_MAX 4096 - -/* We never keep any item longer than 2h in our cache */ -#define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR) - -typedef enum DnsCacheItemType DnsCacheItemType; -typedef struct DnsCacheItem DnsCacheItem; - -enum DnsCacheItemType { - DNS_CACHE_POSITIVE, - DNS_CACHE_NODATA, - DNS_CACHE_NXDOMAIN, -}; - -struct DnsCacheItem { - DnsCacheItemType type; - DnsResourceKey *key; - DnsResourceRecord *rr; - - usec_t until; - bool authenticated:1; - bool shared_owner:1; - - int ifindex; - int owner_family; - union in_addr_union owner_address; - - unsigned prioq_idx; - LIST_FIELDS(DnsCacheItem, by_key); -}; - -static void dns_cache_item_free(DnsCacheItem *i) { - if (!i) - return; - - dns_resource_record_unref(i->rr); - dns_resource_key_unref(i->key); - free(i); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free); - -static void dns_cache_item_unlink_and_free(DnsCache *c, DnsCacheItem *i) { - DnsCacheItem *first; - - assert(c); - - if (!i) - return; - - first = hashmap_get(c->by_key, i->key); - LIST_REMOVE(by_key, first, i); - - if (first) - assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); - else - hashmap_remove(c->by_key, i->key); - - prioq_remove(c->by_expiry, i, &i->prioq_idx); - - dns_cache_item_free(i); -} - -static bool dns_cache_remove_by_rr(DnsCache *c, DnsResourceRecord *rr) { - DnsCacheItem *first, *i; - int r; - - first = hashmap_get(c->by_key, rr->key); - LIST_FOREACH(by_key, i, first) { - r = dns_resource_record_equal(i->rr, rr); - if (r < 0) - return r; - if (r > 0) { - dns_cache_item_unlink_and_free(c, i); - return true; - } - } - - return false; -} - -static bool dns_cache_remove_by_key(DnsCache *c, DnsResourceKey *key) { - DnsCacheItem *first, *i, *n; - - assert(c); - assert(key); - - first = hashmap_remove(c->by_key, key); - if (!first) - return false; - - LIST_FOREACH_SAFE(by_key, i, n, first) { - prioq_remove(c->by_expiry, i, &i->prioq_idx); - dns_cache_item_free(i); - } - - return true; -} - -void dns_cache_flush(DnsCache *c) { - DnsResourceKey *key; - - assert(c); - - while ((key = hashmap_first_key(c->by_key))) - dns_cache_remove_by_key(c, key); - - assert(hashmap_size(c->by_key) == 0); - assert(prioq_size(c->by_expiry) == 0); - - c->by_key = hashmap_free(c->by_key); - c->by_expiry = prioq_free(c->by_expiry); -} - -static void dns_cache_make_space(DnsCache *c, unsigned add) { - assert(c); - - if (add <= 0) - return; - - /* Makes space for n new entries. Note that we actually allow - * the cache to grow beyond CACHE_MAX, but only when we shall - * add more RRs to the cache than CACHE_MAX at once. In that - * case the cache will be emptied completely otherwise. */ - - for (;;) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - DnsCacheItem *i; - - if (prioq_size(c->by_expiry) <= 0) - break; - - if (prioq_size(c->by_expiry) + add < CACHE_MAX) - break; - - i = prioq_peek(c->by_expiry); - assert(i); - - /* Take an extra reference to the key so that it - * doesn't go away in the middle of the remove call */ - key = dns_resource_key_ref(i->key); - dns_cache_remove_by_key(c, key); - } -} - -void dns_cache_prune(DnsCache *c) { - usec_t t = 0; - - assert(c); - - /* Remove all entries that are past their TTL */ - - for (;;) { - DnsCacheItem *i; - - i = prioq_peek(c->by_expiry); - if (!i) - break; - - if (t <= 0) - t = now(clock_boottime_or_monotonic()); - - if (i->until > t) - break; - - /* Depending whether this is an mDNS shared entry - * either remove only this one RR or the whole - * RRset */ - if (i->shared_owner) - dns_cache_item_unlink_and_free(c, i); - else { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - /* Take an extra reference to the key so that it - * doesn't go away in the middle of the remove call */ - key = dns_resource_key_ref(i->key); - dns_cache_remove_by_key(c, key); - } - } -} - -static int dns_cache_item_prioq_compare_func(const void *a, const void *b) { - const DnsCacheItem *x = a, *y = b; - - if (x->until < y->until) - return -1; - if (x->until > y->until) - return 1; - return 0; -} - -static int dns_cache_init(DnsCache *c) { - int r; - - assert(c); - - r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&c->by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - return r; -} - -static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) { - DnsCacheItem *first; - int r; - - assert(c); - assert(i); - - r = prioq_put(c->by_expiry, i, &i->prioq_idx); - if (r < 0) - return r; - - first = hashmap_get(c->by_key, i->key); - if (first) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; - - /* Keep a reference to the original key, while we manipulate the list. */ - k = dns_resource_key_ref(first->key); - - /* Now, try to reduce the number of keys we keep */ - dns_resource_key_reduce(&first->key, &i->key); - - if (first->rr) - dns_resource_key_reduce(&first->rr->key, &i->key); - if (i->rr) - dns_resource_key_reduce(&i->rr->key, &i->key); - - LIST_PREPEND(by_key, first, i); - assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); - } else { - r = hashmap_put(c->by_key, i->key, i); - if (r < 0) { - prioq_remove(c->by_expiry, i, &i->prioq_idx); - return r; - } - } - - return 0; -} - -static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) { - DnsCacheItem *i; - - assert(c); - assert(rr); - - LIST_FOREACH(by_key, i, hashmap_get(c->by_key, rr->key)) - if (i->rr && dns_resource_record_equal(i->rr, rr) > 0) - return i; - - return NULL; -} - -static usec_t calculate_until(DnsResourceRecord *rr, uint32_t nsec_ttl, usec_t timestamp, bool use_soa_minimum) { - uint32_t ttl; - usec_t u; - - assert(rr); - - ttl = MIN(rr->ttl, nsec_ttl); - if (rr->key->type == DNS_TYPE_SOA && use_soa_minimum) { - /* If this is a SOA RR, and it is requested, clamp to - * the SOA's minimum field. This is used when we do - * negative caching, to determine the TTL for the - * negative caching entry. See RFC 2308, Section - * 5. */ - - if (ttl > rr->soa.minimum) - ttl = rr->soa.minimum; - } - - u = ttl * USEC_PER_SEC; - if (u > CACHE_TTL_MAX_USEC) - u = CACHE_TTL_MAX_USEC; - - if (rr->expiry != USEC_INFINITY) { - usec_t left; - - /* Make use of the DNSSEC RRSIG expiry info, if we - * have it */ - - left = LESS_BY(rr->expiry, now(CLOCK_REALTIME)); - if (u > left) - u = left; - } - - return timestamp + u; -} - -static void dns_cache_item_update_positive( - DnsCache *c, - DnsCacheItem *i, - DnsResourceRecord *rr, - bool authenticated, - bool shared_owner, - usec_t timestamp, - int ifindex, - int owner_family, - const union in_addr_union *owner_address) { - - assert(c); - assert(i); - assert(rr); - assert(owner_address); - - i->type = DNS_CACHE_POSITIVE; - - if (!i->by_key_prev) - /* We are the first item in the list, we need to - * update the key used in the hashmap */ - - assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0); - - dns_resource_record_ref(rr); - dns_resource_record_unref(i->rr); - i->rr = rr; - - dns_resource_key_unref(i->key); - i->key = dns_resource_key_ref(rr->key); - - i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); - i->authenticated = authenticated; - i->shared_owner = shared_owner; - - i->ifindex = ifindex; - - i->owner_family = owner_family; - i->owner_address = *owner_address; - - prioq_reshuffle(c->by_expiry, i, &i->prioq_idx); -} - -static int dns_cache_put_positive( - DnsCache *c, - DnsResourceRecord *rr, - bool authenticated, - bool shared_owner, - usec_t timestamp, - int ifindex, - int owner_family, - const union in_addr_union *owner_address) { - - _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; - _cleanup_free_ char *key_str = NULL; - DnsCacheItem *existing; - int r, k; - - assert(c); - assert(rr); - assert(owner_address); - - /* Never cache pseudo RRs */ - if (dns_class_is_pseudo(rr->key->class)) - return 0; - if (dns_type_is_pseudo(rr->key->type)) - return 0; - - /* New TTL is 0? Delete this specific entry... */ - if (rr->ttl <= 0) { - k = dns_cache_remove_by_rr(c, rr); - - if (log_get_max_level() >= LOG_DEBUG) { - r = dns_resource_key_to_string(rr->key, &key_str); - if (r < 0) - return r; - - if (k > 0) - log_debug("Removed zero TTL entry from cache: %s", key_str); - else - log_debug("Not caching zero TTL cache entry: %s", key_str); - } - - return 0; - } - - /* Entry exists already? Update TTL, timestamp and owner*/ - existing = dns_cache_get(c, rr); - if (existing) { - dns_cache_item_update_positive( - c, - existing, - rr, - authenticated, - shared_owner, - timestamp, - ifindex, - owner_family, - owner_address); - return 0; - } - - /* Otherwise, add the new RR */ - r = dns_cache_init(c); - if (r < 0) - return r; - - dns_cache_make_space(c, 1); - - i = new0(DnsCacheItem, 1); - if (!i) - return -ENOMEM; - - i->type = DNS_CACHE_POSITIVE; - i->key = dns_resource_key_ref(rr->key); - i->rr = dns_resource_record_ref(rr); - i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); - i->authenticated = authenticated; - i->shared_owner = shared_owner; - i->ifindex = ifindex; - i->owner_family = owner_family; - i->owner_address = *owner_address; - i->prioq_idx = PRIOQ_IDX_NULL; - - r = dns_cache_link_item(c, i); - if (r < 0) - return r; - - if (log_get_max_level() >= LOG_DEBUG) { - r = dns_resource_key_to_string(i->key, &key_str); - if (r < 0) - return r; - - log_debug("Added positive cache entry for %s", key_str); - } - - i = NULL; - return 0; -} - -static int dns_cache_put_negative( - DnsCache *c, - DnsResourceKey *key, - int rcode, - bool authenticated, - uint32_t nsec_ttl, - usec_t timestamp, - DnsResourceRecord *soa, - int owner_family, - const union in_addr_union *owner_address) { - - _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; - _cleanup_free_ char *key_str = NULL; - int r; - - assert(c); - assert(key); - assert(soa); - assert(owner_address); - - /* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly - * important to filter out as we use this as a pseudo-type for - * NXDOMAIN entries */ - if (dns_class_is_pseudo(key->class)) - return 0; - if (dns_type_is_pseudo(key->type)) - return 0; - - if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) { - if (log_get_max_level() >= LOG_DEBUG) { - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; - - log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", key_str); - } - - return 0; - } - - if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) - return 0; - - r = dns_cache_init(c); - if (r < 0) - return r; - - dns_cache_make_space(c, 1); - - i = new0(DnsCacheItem, 1); - if (!i) - return -ENOMEM; - - i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; - i->until = calculate_until(soa, nsec_ttl, timestamp, true); - i->authenticated = authenticated; - i->owner_family = owner_family; - i->owner_address = *owner_address; - i->prioq_idx = PRIOQ_IDX_NULL; - - if (i->type == DNS_CACHE_NXDOMAIN) { - /* NXDOMAIN entries should apply equally to all types, so we use ANY as - * a pseudo type for this purpose here. */ - i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(key)); - if (!i->key) - return -ENOMEM; - - /* Make sure to remove any previous entry for this - * specific ANY key. (For non-ANY keys the cache data - * is already cleared by the caller.) Note that we - * don't bother removing positive or NODATA cache - * items in this case, because it would either be slow - * or require explicit indexing by name */ - dns_cache_remove_by_key(c, key); - } else - i->key = dns_resource_key_ref(key); - - r = dns_cache_link_item(c, i); - if (r < 0) - return r; - - if (log_get_max_level() >= LOG_DEBUG) { - r = dns_resource_key_to_string(i->key, &key_str); - if (r < 0) - return r; - - log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str); - } - - i = NULL; - return 0; -} - -static void dns_cache_remove_previous( - DnsCache *c, - DnsResourceKey *key, - DnsAnswer *answer) { - - DnsResourceRecord *rr; - DnsAnswerFlags flags; - - assert(c); - - /* First, if we were passed a key (i.e. on LLMNR/DNS, but - * not on mDNS), delete all matching old RRs, so that we only - * keep complete by_key in place. */ - if (key) - dns_cache_remove_by_key(c, key); - - /* Second, flush all entries matching the answer, unless this - * is an RR that is explicitly marked to be "shared" between - * peers (i.e. mDNS RRs without the flush-cache bit set). */ - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - if ((flags & DNS_ANSWER_CACHEABLE) == 0) - continue; - - if (flags & DNS_ANSWER_SHARED_OWNER) - continue; - - dns_cache_remove_by_key(c, rr->key); - } -} - -static bool rr_eligible(DnsResourceRecord *rr) { - assert(rr); - - /* When we see an NSEC/NSEC3 RR, we'll only cache it if it is from the lower zone, not the upper zone, since - * that's where the interesting bits are (with exception of DS RRs). Of course, this way we cannot derive DS - * existence from any cached NSEC/NSEC3, but that should be fine. */ - - switch (rr->key->type) { - - case DNS_TYPE_NSEC: - return !bitmap_isset(rr->nsec.types, DNS_TYPE_NS) || - bitmap_isset(rr->nsec.types, DNS_TYPE_SOA); - - case DNS_TYPE_NSEC3: - return !bitmap_isset(rr->nsec3.types, DNS_TYPE_NS) || - bitmap_isset(rr->nsec3.types, DNS_TYPE_SOA); - - default: - return true; - } -} - -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) { - - DnsResourceRecord *soa = NULL, *rr; - DnsAnswerFlags flags; - unsigned cache_keys; - int r, ifindex; - - assert(c); - assert(owner_address); - - dns_cache_remove_previous(c, key, answer); - - if (dns_answer_size(answer) <= 0) { - if (log_get_max_level() >= LOG_DEBUG) { - _cleanup_free_ char *key_str = NULL; - - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; - - log_debug("Not caching negative entry without a SOA record: %s", key_str); - } - - 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 ++; - - /* Make some space for our new entries */ - dns_cache_make_space(c, cache_keys); - - if (timestamp <= 0) - timestamp = now(clock_boottime_or_monotonic()); - - /* Second, add in positive entries for all contained RRs */ - DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { - if ((flags & DNS_ANSWER_CACHEABLE) == 0) - continue; - - r = rr_eligible(rr); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_cache_put_positive( - c, - rr, - flags & DNS_ANSWER_AUTHENTICATED, - flags & DNS_ANSWER_SHARED_OWNER, - timestamp, - ifindex, - owner_family, owner_address); - if (r < 0) - goto fail; - } - - if (!key) /* mDNS doesn't know negative caching, really */ - return 0; - - /* Third, add in negative entries if the key has no RR */ - r = dns_answer_match_key(answer, key, NULL); - if (r < 0) - goto fail; - if (r > 0) - return 0; - - /* But not if it has a matching CNAME/DNAME (the negative - * caching will be done on the canonical name, not on the - * alias) */ - r = dns_answer_find_cname_or_dname(answer, key, NULL, NULL); - if (r < 0) - goto fail; - if (r > 0) - return 0; - - /* See https://tools.ietf.org/html/rfc2308, which say that a - * matching SOA record in the packet is used to to enable - * negative caching. */ - r = dns_answer_find_soa(answer, key, &soa, &flags); - if (r < 0) - goto fail; - if (r == 0) - return 0; - - /* Refuse using the SOA data if it is unsigned, but the key is - * signed */ - if (authenticated && (flags & DNS_ANSWER_AUTHENTICATED) == 0) - return 0; - - r = dns_cache_put_negative( - c, - key, - rcode, - authenticated, - nsec_ttl, - timestamp, - soa, - owner_family, owner_address); - if (r < 0) - goto fail; - - return 0; - -fail: - /* Adding all RRs failed. Let's clean up what we already - * added, just in case */ - - if (key) - dns_cache_remove_by_key(c, key); - - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - if ((flags & DNS_ANSWER_CACHEABLE) == 0) - continue; - - dns_cache_remove_by_key(c, rr->key); - } - - return r; -} - -static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) { - DnsCacheItem *i; - const char *n; - int r; - - assert(c); - assert(k); - - /* If we hit some OOM error, or suchlike, we don't care too - * much, after all this is just a cache */ - - i = hashmap_get(c->by_key, k); - if (i) - return i; - - n = DNS_RESOURCE_KEY_NAME(k); - - /* Check if we have an NXDOMAIN cache item for the name, notice that we use - * the pseudo-type ANY for NXDOMAIN cache items. */ - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_ANY, n)); - if (i && i->type == DNS_CACHE_NXDOMAIN) - return i; - - if (dns_type_may_redirect(k->type)) { - /* Check if we have a CNAME record instead */ - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n)); - if (i) - return i; - - /* OK, let's look for cached DNAME records. */ - for (;;) { - if (isempty(n)) - return NULL; - - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n)); - if (i) - return i; - - /* Jump one label ahead */ - r = dns_name_parent(&n); - if (r <= 0) - return NULL; - } - } - - if (k->type != DNS_TYPE_NSEC) { - /* Check if we have an NSEC record instead for the name. */ - i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n)); - if (i) - return i; - } - - return NULL; -} - -int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret, bool *authenticated) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - unsigned n = 0; - int r; - bool nxdomain = false; - _cleanup_free_ char *key_str = NULL; - DnsCacheItem *j, *first, *nsec = NULL; - bool have_authenticated = false, have_non_authenticated = false; - - assert(c); - assert(key); - assert(rcode); - assert(ret); - assert(authenticated); - - if (key->type == DNS_TYPE_ANY || - key->class == DNS_CLASS_ANY) { - - /* If we have ANY lookups we don't use the cache, so - * that the caller refreshes via the network. */ - - if (log_get_max_level() >= LOG_DEBUG) { - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; - - log_debug("Ignoring cache for ANY lookup: %s", key_str); - } - - c->n_miss++; - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - return 0; - } - - first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key); - if (!first) { - /* If one question cannot be answered we need to refresh */ - - if (log_get_max_level() >= LOG_DEBUG) { - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; - - log_debug("Cache miss for %s", key_str); - } - - c->n_miss++; - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - return 0; - } - - LIST_FOREACH(by_key, j, first) { - if (j->rr) { - if (j->rr->key->type == DNS_TYPE_NSEC) - nsec = j; - - n++; - } else if (j->type == DNS_CACHE_NXDOMAIN) - nxdomain = true; - - if (j->authenticated) - have_authenticated = true; - else - have_non_authenticated = true; - } - - if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) { - /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC RRs from - * the lower-zone of a zone cut, but the DS RRs are on the upper zone. */ - - if (log_get_max_level() >= LOG_DEBUG) { - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; - - log_debug("NSEC NODATA cache hit for %s", key_str); - } - - /* We only found an NSEC record that matches our name. - * If it says the type doesn't exist report - * NODATA. Otherwise report a cache miss. */ - - *ret = NULL; - *rcode = DNS_RCODE_SUCCESS; - *authenticated = nsec->authenticated; - - if (!bitmap_isset(nsec->rr->nsec.types, key->type) && - !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) && - !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME)) { - c->n_hit++; - return 1; - } - - c->n_miss++; - return 0; - } - - if (log_get_max_level() >= LOG_DEBUG) { - r = dns_resource_key_to_string(key, &key_str); - if (r < 0) - return r; - - log_debug("%s cache hit for %s", - n > 0 ? "Positive" : - nxdomain ? "NXDOMAIN" : "NODATA", - key_str); - } - - if (n <= 0) { - c->n_hit++; - - *ret = NULL; - *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; - *authenticated = have_authenticated && !have_non_authenticated; - return 1; - } - - answer = dns_answer_new(n); - if (!answer) - return -ENOMEM; - - LIST_FOREACH(by_key, j, first) { - if (!j->rr) - continue; - - r = dns_answer_add(answer, j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0); - if (r < 0) - return r; - } - - c->n_hit++; - - *ret = answer; - *rcode = DNS_RCODE_SUCCESS; - *authenticated = have_authenticated && !have_non_authenticated; - answer = NULL; - - return n; -} - -int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) { - DnsCacheItem *i, *first; - bool same_owner = true; - - assert(cache); - assert(rr); - - dns_cache_prune(cache); - - /* See if there's a cache entry for the same key. If there - * isn't there's no conflict */ - first = hashmap_get(cache->by_key, rr->key); - if (!first) - return 0; - - /* See if the RR key is owned by the same owner, if so, there - * isn't a conflict either */ - LIST_FOREACH(by_key, i, first) { - if (i->owner_family != owner_family || - !in_addr_equal(owner_family, &i->owner_address, owner_address)) { - same_owner = false; - break; - } - } - if (same_owner) - return 0; - - /* See if there's the exact same RR in the cache. If yes, then - * there's no conflict. */ - if (dns_cache_get(cache, rr)) - return 0; - - /* There's a conflict */ - return 1; -} - -int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) { - unsigned ancount = 0; - Iterator iterator; - DnsCacheItem *i; - int r; - - assert(cache); - assert(p); - - HASHMAP_FOREACH(i, cache->by_key, iterator) { - DnsCacheItem *j; - - LIST_FOREACH(by_key, j, i) { - if (!j->rr) - continue; - - if (!j->shared_owner) - continue; - - r = dns_packet_append_rr(p, j->rr, NULL, NULL); - if (r == -EMSGSIZE && p->protocol == DNS_PROTOCOL_MDNS) { - /* For mDNS, if we're unable to stuff all known answers into the given packet, - * allocate a new one, push the RR into that one and link it to the current one. - */ - - DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); - ancount = 0; - - r = dns_packet_new_query(&p->more, p->protocol, 0, true); - if (r < 0) - return r; - - /* continue with new packet */ - p = p->more; - r = dns_packet_append_rr(p, j->rr, NULL, NULL); - } - - if (r < 0) - return r; - - ancount ++; - } - } - - DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); - - return 0; -} - -void dns_cache_dump(DnsCache *cache, FILE *f) { - Iterator iterator; - DnsCacheItem *i; - int r; - - if (!cache) - return; - - if (!f) - f = stdout; - - HASHMAP_FOREACH(i, cache->by_key, iterator) { - DnsCacheItem *j; - - LIST_FOREACH(by_key, j, i) { - - fputc('\t', f); - - if (j->rr) { - const char *t; - t = dns_resource_record_to_string(j->rr); - if (!t) { - log_oom(); - continue; - } - - fputs(t, f); - fputc('\n', f); - } else { - _cleanup_free_ char *z = NULL; - r = dns_resource_key_to_string(j->key, &z); - if (r < 0) { - log_oom(); - continue; - } - - fputs(z, f); - fputs(" -- ", f); - fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f); - fputc('\n', f); - } - } - } -} - -bool dns_cache_is_empty(DnsCache *cache) { - if (!cache) - return true; - - return hashmap_isempty(cache->by_key); -} - -unsigned dns_cache_size(DnsCache *cache) { - if (!cache) - return 0; - - return hashmap_size(cache->by_key); -} diff --git a/src/resolve/resolved-dns-cache.h b/src/resolve/resolved-dns-cache.h deleted file mode 100644 index 2293718e86..0000000000 --- a/src/resolve/resolved-dns-cache.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "hashmap.h" -#include "list.h" -#include "prioq.h" -#include "time-util.h" - -typedef struct DnsCache { - Hashmap *by_key; - Prioq *by_expiry; - unsigned n_hit; - unsigned n_miss; -} DnsCache; - -#include "resolved-dns-answer.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-question.h" -#include "resolved-dns-rr.h" - -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_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address); - -void dns_cache_dump(DnsCache *cache, FILE *f); -bool dns_cache_is_empty(DnsCache *cache); - -unsigned dns_cache_size(DnsCache *cache); - -int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p); diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c deleted file mode 100644 index 7123d2d3a8..0000000000 --- a/src/resolve/resolved-dns-dnssec.c +++ /dev/null @@ -1,2211 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#ifdef HAVE_GCRYPT -#include -#endif - -#include "alloc-util.h" -#include "dns-domain.h" -#include "hexdecoct.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-packet.h" -#include "string-table.h" - -#define VERIFY_RRS_MAX 256 -#define MAX_KEY_SIZE (32*1024) - -/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */ -#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE) - -/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */ -#define NSEC3_ITERATIONS_MAX 2500 - -/* - * The DNSSEC Chain of trust: - * - * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone - * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree - * DS RRs are protected like normal RRs - * - * Example chain: - * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS - */ - -uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) { - const uint8_t *p; - uint32_t sum, f; - size_t i; - - /* The algorithm from RFC 4034, Appendix B. */ - - assert(dnskey); - assert(dnskey->key->type == DNS_TYPE_DNSKEY); - - f = (uint32_t) dnskey->dnskey.flags; - - if (mask_revoke) - f &= ~DNSKEY_FLAG_REVOKE; - - sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm); - - p = dnskey->dnskey.key; - - for (i = 0; i < dnskey->dnskey.key_size; i++) - sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i]; - - sum += (sum >> 16) & UINT32_C(0xFFFF); - - return sum & UINT32_C(0xFFFF); -} - -int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { - size_t c = 0; - int r; - - /* Converts the specified hostname into DNSSEC canonicalized - * form. */ - - if (buffer_max < 2) - return -ENOBUFS; - - for (;;) { - r = dns_label_unescape(&n, buffer, buffer_max); - if (r < 0) - return r; - if (r == 0) - break; - - if (buffer_max < (size_t) r + 2) - return -ENOBUFS; - - /* The DNSSEC canonical form is not clear on what to - * do with dots appearing in labels, the way DNS-SD - * does it. Refuse it for now. */ - - if (memchr(buffer, '.', r)) - return -EINVAL; - - ascii_strlower_n(buffer, (size_t) r); - buffer[r] = '.'; - - buffer += r + 1; - c += r + 1; - - buffer_max -= r + 1; - } - - if (c <= 0) { - /* Not even a single label: this is the root domain name */ - - assert(buffer_max > 2); - buffer[0] = '.'; - buffer[1] = 0; - - return 1; - } - - return (int) c; -} - -#ifdef HAVE_GCRYPT - -static void initialize_libgcrypt(void) { - const char *p; - - if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) - return; - - p = gcry_check_version("1.4.5"); - assert(p); - - gcry_control(GCRYCTL_DISABLE_SECMEM); - gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); -} - -static int rr_compare(const void *a, const void *b) { - DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b; - size_t m; - int r; - - /* Let's order the RRs according to RFC 4034, Section 6.3 */ - - assert(x); - assert(*x); - assert((*x)->wire_format); - assert(y); - assert(*y); - assert((*y)->wire_format); - - m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y)); - - r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m); - if (r != 0) - return r; - - if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) - return -1; - else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) - return 1; - - return 0; -} - -static int dnssec_rsa_verify_raw( - const char *hash_algorithm, - const void *signature, size_t signature_size, - const void *data, size_t data_size, - const void *exponent, size_t exponent_size, - const void *modulus, size_t modulus_size) { - - gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; - gcry_mpi_t n = NULL, e = NULL, s = NULL; - gcry_error_t ge; - int r; - - assert(hash_algorithm); - - ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&signature_sexp, - NULL, - "(sig-val (rsa (s %m)))", - s); - - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&data_sexp, - NULL, - "(data (flags pkcs1) (hash %s %b))", - hash_algorithm, - (int) data_size, - data); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&public_key_sexp, - NULL, - "(public-key (rsa (n %m) (e %m)))", - n, - e); - if (ge != 0) { - r = -EIO; - goto finish; - } - - ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); - if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) - r = 0; - else if (ge != 0) { - log_debug("RSA signature check failed: %s", gpg_strerror(ge)); - r = -EIO; - } else - r = 1; - -finish: - if (e) - gcry_mpi_release(e); - if (n) - gcry_mpi_release(n); - if (s) - gcry_mpi_release(s); - - if (public_key_sexp) - gcry_sexp_release(public_key_sexp); - if (signature_sexp) - gcry_sexp_release(signature_sexp); - if (data_sexp) - gcry_sexp_release(data_sexp); - - return r; -} - -static int dnssec_rsa_verify( - const char *hash_algorithm, - const void *hash, size_t hash_size, - DnsResourceRecord *rrsig, - DnsResourceRecord *dnskey) { - - size_t exponent_size, modulus_size; - void *exponent, *modulus; - - assert(hash_algorithm); - assert(hash); - assert(hash_size > 0); - assert(rrsig); - assert(dnskey); - - if (*(uint8_t*) dnskey->dnskey.key == 0) { - /* exponent is > 255 bytes long */ - - exponent = (uint8_t*) dnskey->dnskey.key + 3; - exponent_size = - ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) | - ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]); - - if (exponent_size < 256) - return -EINVAL; - - if (3 + exponent_size >= dnskey->dnskey.key_size) - return -EINVAL; - - modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size; - modulus_size = dnskey->dnskey.key_size - 3 - exponent_size; - - } else { - /* exponent is <= 255 bytes long */ - - exponent = (uint8_t*) dnskey->dnskey.key + 1; - exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0]; - - if (exponent_size <= 0) - return -EINVAL; - - if (1 + exponent_size >= dnskey->dnskey.key_size) - return -EINVAL; - - modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size; - modulus_size = dnskey->dnskey.key_size - 1 - exponent_size; - } - - return dnssec_rsa_verify_raw( - hash_algorithm, - rrsig->rrsig.signature, rrsig->rrsig.signature_size, - hash, hash_size, - exponent, exponent_size, - modulus, modulus_size); -} - -static int dnssec_ecdsa_verify_raw( - const char *hash_algorithm, - const char *curve, - const void *signature_r, size_t signature_r_size, - const void *signature_s, size_t signature_s_size, - const void *data, size_t data_size, - const void *key, size_t key_size) { - - gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; - gcry_mpi_t q = NULL, r = NULL, s = NULL; - gcry_error_t ge; - int k; - - assert(hash_algorithm); - - ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&signature_sexp, - NULL, - "(sig-val (ecdsa (r %m) (s %m)))", - r, - s); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&data_sexp, - NULL, - "(data (flags rfc6979) (hash %s %b))", - hash_algorithm, - (int) data_size, - data); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_sexp_build(&public_key_sexp, - NULL, - "(public-key (ecc (curve %s) (q %m)))", - curve, - q); - if (ge != 0) { - k = -EIO; - goto finish; - } - - ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); - if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) - k = 0; - else if (ge != 0) { - log_debug("ECDSA signature check failed: %s", gpg_strerror(ge)); - k = -EIO; - } else - k = 1; -finish: - if (r) - gcry_mpi_release(r); - if (s) - gcry_mpi_release(s); - if (q) - gcry_mpi_release(q); - - if (public_key_sexp) - gcry_sexp_release(public_key_sexp); - if (signature_sexp) - gcry_sexp_release(signature_sexp); - if (data_sexp) - gcry_sexp_release(data_sexp); - - return k; -} - -static int dnssec_ecdsa_verify( - const char *hash_algorithm, - int algorithm, - const void *hash, size_t hash_size, - DnsResourceRecord *rrsig, - DnsResourceRecord *dnskey) { - - const char *curve; - size_t key_size; - uint8_t *q; - - assert(hash); - assert(hash_size); - assert(rrsig); - assert(dnskey); - - if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) { - key_size = 32; - curve = "NIST P-256"; - } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) { - key_size = 48; - curve = "NIST P-384"; - } else - return -EOPNOTSUPP; - - if (dnskey->dnskey.key_size != key_size * 2) - return -EINVAL; - - if (rrsig->rrsig.signature_size != key_size * 2) - return -EINVAL; - - q = alloca(key_size*2 + 1); - q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */ - memcpy(q+1, dnskey->dnskey.key, key_size*2); - - return dnssec_ecdsa_verify_raw( - hash_algorithm, - curve, - rrsig->rrsig.signature, key_size, - (uint8_t*) rrsig->rrsig.signature + key_size, key_size, - hash, hash_size, - q, key_size*2+1); -} - -static void md_add_uint8(gcry_md_hd_t md, uint8_t v) { - gcry_md_write(md, &v, sizeof(v)); -} - -static void md_add_uint16(gcry_md_hd_t md, uint16_t v) { - v = htobe16(v); - gcry_md_write(md, &v, sizeof(v)); -} - -static void md_add_uint32(gcry_md_hd_t md, uint32_t v) { - v = htobe32(v); - gcry_md_write(md, &v, sizeof(v)); -} - -static int dnssec_rrsig_prepare(DnsResourceRecord *rrsig) { - int n_key_labels, n_signer_labels; - const char *name; - int r; - - /* Checks whether the specified RRSIG RR is somewhat valid, and initializes the .n_skip_labels_source and - * .n_skip_labels_signer fields so that we can use them later on. */ - - assert(rrsig); - assert(rrsig->key->type == DNS_TYPE_RRSIG); - - /* Check if this RRSIG RR is already prepared */ - if (rrsig->n_skip_labels_source != (unsigned) -1) - return 0; - - if (rrsig->rrsig.inception > rrsig->rrsig.expiration) - return -EINVAL; - - name = DNS_RESOURCE_KEY_NAME(rrsig->key); - - n_key_labels = dns_name_count_labels(name); - if (n_key_labels < 0) - return n_key_labels; - if (rrsig->rrsig.labels > n_key_labels) - return -EINVAL; - - n_signer_labels = dns_name_count_labels(rrsig->rrsig.signer); - if (n_signer_labels < 0) - return n_signer_labels; - if (n_signer_labels > rrsig->rrsig.labels) - return -EINVAL; - - r = dns_name_skip(name, n_key_labels - n_signer_labels, &name); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - /* Check if the signer is really a suffix of us */ - r = dns_name_equal(name, rrsig->rrsig.signer); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - rrsig->n_skip_labels_source = n_key_labels - rrsig->rrsig.labels; - rrsig->n_skip_labels_signer = n_key_labels - n_signer_labels; - - return 0; -} - -static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { - usec_t expiration, inception, skew; - - assert(rrsig); - assert(rrsig->key->type == DNS_TYPE_RRSIG); - - if (realtime == USEC_INFINITY) - realtime = now(CLOCK_REALTIME); - - expiration = rrsig->rrsig.expiration * USEC_PER_SEC; - inception = rrsig->rrsig.inception * USEC_PER_SEC; - - /* Consider inverted validity intervals as expired */ - if (inception > expiration) - return true; - - /* Permit a certain amount of clock skew of 10% of the valid - * time range. This takes inspiration from unbound's - * resolver. */ - skew = (expiration - inception) / 10; - if (skew > SKEW_MAX) - skew = SKEW_MAX; - - if (inception < skew) - inception = 0; - else - inception -= skew; - - if (expiration + skew < expiration) - expiration = USEC_INFINITY; - else - expiration += skew; - - return realtime < inception || realtime > expiration; -} - -static int algorithm_to_gcrypt_md(uint8_t algorithm) { - - /* Translates a DNSSEC signature algorithm into a gcrypt - * digest identifier. - * - * Note that we implement all algorithms listed as "Must - * implement" and "Recommended to Implement" in RFC6944. We - * don't implement any algorithms that are listed as - * "Optional" or "Must Not Implement". Specifically, we do not - * implement RSAMD5, DSASHA1, DH, DSA-NSEC3-SHA1, and - * GOST-ECC. */ - - switch (algorithm) { - - case DNSSEC_ALGORITHM_RSASHA1: - case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: - return GCRY_MD_SHA1; - - case DNSSEC_ALGORITHM_RSASHA256: - case DNSSEC_ALGORITHM_ECDSAP256SHA256: - return GCRY_MD_SHA256; - - case DNSSEC_ALGORITHM_ECDSAP384SHA384: - return GCRY_MD_SHA384; - - case DNSSEC_ALGORITHM_RSASHA512: - return GCRY_MD_SHA512; - - default: - return -EOPNOTSUPP; - } -} - -static void dnssec_fix_rrset_ttl( - DnsResourceRecord *list[], - unsigned n, - DnsResourceRecord *rrsig, - usec_t realtime) { - - unsigned k; - - assert(list); - assert(n > 0); - assert(rrsig); - - for (k = 0; k < n; k++) { - DnsResourceRecord *rr = list[k]; - - /* Pick the TTL as the minimum of the RR's TTL, the - * RR's original TTL according to the RRSIG and the - * RRSIG's own TTL, see RFC 4035, Section 5.3.3 */ - rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl); - rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; - - /* Copy over information about the signer and wildcard source of synthesis */ - rr->n_skip_labels_source = rrsig->n_skip_labels_source; - rr->n_skip_labels_signer = rrsig->n_skip_labels_signer; - } - - rrsig->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; -} - -int dnssec_verify_rrset( - DnsAnswer *a, - const DnsResourceKey *key, - DnsResourceRecord *rrsig, - DnsResourceRecord *dnskey, - usec_t realtime, - DnssecResult *result) { - - uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX]; - DnsResourceRecord **list, *rr; - const char *source, *name; - gcry_md_hd_t md = NULL; - int r, md_algorithm; - size_t k, n = 0; - size_t hash_size; - void *hash; - bool wildcard; - - assert(key); - assert(rrsig); - assert(dnskey); - assert(result); - assert(rrsig->key->type == DNS_TYPE_RRSIG); - assert(dnskey->key->type == DNS_TYPE_DNSKEY); - - /* Verifies the the RRSet matching the specified "key" in "a", - * using the signature "rrsig" and the key "dnskey". It's - * assumed the RRSIG and DNSKEY match. */ - - md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm); - if (md_algorithm == -EOPNOTSUPP) { - *result = DNSSEC_UNSUPPORTED_ALGORITHM; - return 0; - } - if (md_algorithm < 0) - return md_algorithm; - - r = dnssec_rrsig_prepare(rrsig); - if (r == -EINVAL) { - *result = DNSSEC_INVALID; - return r; - } - if (r < 0) - return r; - - r = dnssec_rrsig_expired(rrsig, realtime); - if (r < 0) - return r; - if (r > 0) { - *result = DNSSEC_SIGNATURE_EXPIRED; - return 0; - } - - name = DNS_RESOURCE_KEY_NAME(key); - - /* Some keys may only appear signed in the zone apex, and are invalid anywhere else. (SOA, NS...) */ - if (dns_type_apex_only(rrsig->rrsig.type_covered)) { - r = dns_name_equal(rrsig->rrsig.signer, name); - if (r < 0) - return r; - if (r == 0) { - *result = DNSSEC_INVALID; - return 0; - } - } - - /* OTOH DS RRs may not appear in the zone apex, but are valid everywhere else. */ - if (rrsig->rrsig.type_covered == DNS_TYPE_DS) { - r = dns_name_equal(rrsig->rrsig.signer, name); - if (r < 0) - return r; - if (r > 0) { - *result = DNSSEC_INVALID; - return 0; - } - } - - /* Determine the "Source of Synthesis" and whether this is a wildcard RRSIG */ - r = dns_name_suffix(name, rrsig->rrsig.labels, &source); - if (r < 0) - return r; - if (r > 0 && !dns_type_may_wildcard(rrsig->rrsig.type_covered)) { - /* We refuse to validate NSEC3 or SOA RRs that are synthesized from wildcards */ - *result = DNSSEC_INVALID; - return 0; - } - if (r == 1) { - /* If we stripped a single label, then let's see if that maybe was "*". If so, we are not really - * synthesized from a wildcard, we are the wildcard itself. Treat that like a normal name. */ - r = dns_name_startswith(name, "*"); - if (r < 0) - return r; - if (r > 0) - source = name; - - wildcard = r == 0; - } else - wildcard = r > 0; - - /* Collect all relevant RRs in a single array, so that we can look at the RRset */ - list = newa(DnsResourceRecord *, dns_answer_size(a)); - - DNS_ANSWER_FOREACH(rr, a) { - r = dns_resource_key_equal(key, rr->key); - if (r < 0) - return r; - if (r == 0) - continue; - - /* We need the wire format for ordering, and digest calculation */ - r = dns_resource_record_to_wire_format(rr, true); - if (r < 0) - return r; - - list[n++] = rr; - - if (n > VERIFY_RRS_MAX) - return -E2BIG; - } - - if (n <= 0) - return -ENODATA; - - /* Bring the RRs into canonical order */ - qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare); - - /* OK, the RRs are now in canonical order. Let's calculate the digest */ - initialize_libgcrypt(); - - hash_size = gcry_md_get_algo_dlen(md_algorithm); - assert(hash_size > 0); - - gcry_md_open(&md, md_algorithm, 0); - if (!md) - return -EIO; - - md_add_uint16(md, rrsig->rrsig.type_covered); - md_add_uint8(md, rrsig->rrsig.algorithm); - md_add_uint8(md, rrsig->rrsig.labels); - md_add_uint32(md, rrsig->rrsig.original_ttl); - md_add_uint32(md, rrsig->rrsig.expiration); - md_add_uint32(md, rrsig->rrsig.inception); - md_add_uint16(md, rrsig->rrsig.key_tag); - - r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true); - if (r < 0) - goto finish; - gcry_md_write(md, wire_format_name, r); - - /* Convert the source of synthesis into wire format */ - r = dns_name_to_wire_format(source, wire_format_name, sizeof(wire_format_name), true); - if (r < 0) - goto finish; - - for (k = 0; k < n; k++) { - size_t l; - - rr = list[k]; - - /* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */ - if (wildcard) - gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2); - gcry_md_write(md, wire_format_name, r); - - md_add_uint16(md, rr->key->type); - md_add_uint16(md, rr->key->class); - md_add_uint32(md, rrsig->rrsig.original_ttl); - - l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr); - assert(l <= 0xFFFF); - - md_add_uint16(md, (uint16_t) l); - gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l); - } - - hash = gcry_md_read(md, 0); - if (!hash) { - r = -EIO; - goto finish; - } - - switch (rrsig->rrsig.algorithm) { - - case DNSSEC_ALGORITHM_RSASHA1: - case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: - case DNSSEC_ALGORITHM_RSASHA256: - case DNSSEC_ALGORITHM_RSASHA512: - r = dnssec_rsa_verify( - gcry_md_algo_name(md_algorithm), - hash, hash_size, - rrsig, - dnskey); - break; - - case DNSSEC_ALGORITHM_ECDSAP256SHA256: - case DNSSEC_ALGORITHM_ECDSAP384SHA384: - r = dnssec_ecdsa_verify( - gcry_md_algo_name(md_algorithm), - rrsig->rrsig.algorithm, - hash, hash_size, - rrsig, - dnskey); - break; - } - - if (r < 0) - goto finish; - - /* Now, fix the ttl, expiry, and remember the synthesizing source and the signer */ - if (r > 0) - dnssec_fix_rrset_ttl(list, n, rrsig, realtime); - - if (r == 0) - *result = DNSSEC_INVALID; - else if (wildcard) - *result = DNSSEC_VALIDATED_WILDCARD; - else - *result = DNSSEC_VALIDATED; - - r = 0; - -finish: - gcry_md_close(md); - return r; -} - -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { - - assert(rrsig); - assert(dnskey); - - /* Checks if the specified DNSKEY RR matches the key used for - * the signature in the specified RRSIG RR */ - - if (rrsig->key->type != DNS_TYPE_RRSIG) - return -EINVAL; - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return 0; - if (dnskey->key->class != rrsig->key->class) - return 0; - if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) - return 0; - if (!revoked_ok && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) - return 0; - if (dnskey->dnskey.protocol != 3) - return 0; - if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm) - return 0; - - if (dnssec_keytag(dnskey, false) != rrsig->rrsig.key_tag) - return 0; - - return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer); -} - -int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { - assert(key); - assert(rrsig); - - /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */ - - if (rrsig->key->type != DNS_TYPE_RRSIG) - return 0; - if (rrsig->key->class != key->class) - return 0; - if (rrsig->rrsig.type_covered != key->type) - return 0; - - return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key)); -} - -int dnssec_verify_rrset_search( - DnsAnswer *a, - const DnsResourceKey *key, - DnsAnswer *validated_dnskeys, - usec_t realtime, - DnssecResult *result, - DnsResourceRecord **ret_rrsig) { - - bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false; - DnsResourceRecord *rrsig; - int r; - - assert(key); - assert(result); - - /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */ - - if (!a || a->n_rrs <= 0) - return -ENODATA; - - /* Iterate through each RRSIG RR. */ - DNS_ANSWER_FOREACH(rrsig, a) { - DnsResourceRecord *dnskey; - DnsAnswerFlags flags; - - /* Is this an RRSIG RR that applies to RRs matching our key? */ - r = dnssec_key_match_rrsig(key, rrsig); - if (r < 0) - return r; - if (r == 0) - continue; - - found_rrsig = true; - - /* Look for a matching key */ - DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) { - DnssecResult one_result; - - if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) - continue; - - /* Is this a DNSKEY RR that matches they key of our RRSIG? */ - r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false); - if (r < 0) - return r; - if (r == 0) - continue; - - /* Take the time here, if it isn't set yet, so - * that we do all validations with the same - * time. */ - if (realtime == USEC_INFINITY) - realtime = now(CLOCK_REALTIME); - - /* Yay, we found a matching RRSIG with a matching - * DNSKEY, awesome. Now let's verify all entries of - * the RRSet against the RRSIG and DNSKEY - * combination. */ - - r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result); - if (r < 0) - return r; - - switch (one_result) { - - case DNSSEC_VALIDATED: - case DNSSEC_VALIDATED_WILDCARD: - /* Yay, the RR has been validated, - * return immediately, but fix up the expiry */ - if (ret_rrsig) - *ret_rrsig = rrsig; - - *result = one_result; - return 0; - - case DNSSEC_INVALID: - /* If the signature is invalid, let's try another - key and/or signature. After all they - key_tags and stuff are not unique, and - might be shared by multiple keys. */ - found_invalid = true; - continue; - - case DNSSEC_UNSUPPORTED_ALGORITHM: - /* If the key algorithm is - unsupported, try another - RRSIG/DNSKEY pair, but remember we - encountered this, so that we can - return a proper error when we - encounter nothing better. */ - found_unsupported_algorithm = true; - continue; - - case DNSSEC_SIGNATURE_EXPIRED: - /* If the signature is expired, try - another one, but remember it, so - that we can return this */ - found_expired_rrsig = true; - continue; - - default: - assert_not_reached("Unexpected DNSSEC validation result"); - } - } - } - - if (found_expired_rrsig) - *result = DNSSEC_SIGNATURE_EXPIRED; - else if (found_unsupported_algorithm) - *result = DNSSEC_UNSUPPORTED_ALGORITHM; - else if (found_invalid) - *result = DNSSEC_INVALID; - else if (found_rrsig) - *result = DNSSEC_MISSING_KEY; - else - *result = DNSSEC_NO_SIGNATURE; - - if (ret_rrsig) - *ret_rrsig = NULL; - - return 0; -} - -int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { - DnsResourceRecord *rr; - int r; - - /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */ - - DNS_ANSWER_FOREACH(rr, a) { - r = dnssec_key_match_rrsig(key, rr); - if (r < 0) - return r; - if (r > 0) - return 1; - } - - return 0; -} - -static int digest_to_gcrypt_md(uint8_t algorithm) { - - /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */ - - switch (algorithm) { - - case DNSSEC_DIGEST_SHA1: - return GCRY_MD_SHA1; - - case DNSSEC_DIGEST_SHA256: - return GCRY_MD_SHA256; - - case DNSSEC_DIGEST_SHA384: - return GCRY_MD_SHA384; - - default: - return -EOPNOTSUPP; - } -} - -int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { - char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX]; - gcry_md_hd_t md = NULL; - size_t hash_size; - int md_algorithm, r; - void *result; - - assert(dnskey); - assert(ds); - - /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */ - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return -EINVAL; - if (ds->key->type != DNS_TYPE_DS) - return -EINVAL; - if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) - return -EKEYREJECTED; - if (!mask_revoke && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) - return -EKEYREJECTED; - if (dnskey->dnskey.protocol != 3) - return -EKEYREJECTED; - - if (dnskey->dnskey.algorithm != ds->ds.algorithm) - return 0; - if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag) - return 0; - - initialize_libgcrypt(); - - md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type); - if (md_algorithm < 0) - return md_algorithm; - - hash_size = gcry_md_get_algo_dlen(md_algorithm); - assert(hash_size > 0); - - if (ds->ds.digest_size != hash_size) - return 0; - - r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name)); - if (r < 0) - return r; - - gcry_md_open(&md, md_algorithm, 0); - if (!md) - return -EIO; - - gcry_md_write(md, owner_name, r); - if (mask_revoke) - md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE); - else - md_add_uint16(md, dnskey->dnskey.flags); - md_add_uint8(md, dnskey->dnskey.protocol); - md_add_uint8(md, dnskey->dnskey.algorithm); - gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size); - - result = gcry_md_read(md, 0); - if (!result) { - r = -EIO; - goto finish; - } - - r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0; - -finish: - gcry_md_close(md); - return r; -} - -int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { - DnsResourceRecord *ds; - DnsAnswerFlags flags; - int r; - - assert(dnskey); - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return 0; - - DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) { - - if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) - continue; - - if (ds->key->type != DNS_TYPE_DS) - continue; - if (ds->key->class != dnskey->key->class) - continue; - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(ds->key)); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_verify_dnskey_by_ds(dnskey, ds, false); - if (IN_SET(r, -EKEYREJECTED, -EOPNOTSUPP)) - return 0; /* The DNSKEY is revoked or otherwise invalid, or we don't support the digest algorithm */ - if (r < 0) - return r; - if (r > 0) - return 1; - } - - return 0; -} - -static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) { - - /* Translates a DNSSEC NSEC3 hash algorithm into a gcrypt digest identifier */ - - switch (algorithm) { - - case NSEC3_ALGORITHM_SHA1: - return GCRY_MD_SHA1; - - default: - return -EOPNOTSUPP; - } -} - -int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { - uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX]; - gcry_md_hd_t md = NULL; - size_t hash_size; - int algorithm; - void *result; - unsigned k; - int r; - - assert(nsec3); - assert(name); - assert(ret); - - if (nsec3->key->type != DNS_TYPE_NSEC3) - return -EINVAL; - - if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX) { - log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3)); - return -EOPNOTSUPP; - } - - algorithm = nsec3_hash_to_gcrypt_md(nsec3->nsec3.algorithm); - if (algorithm < 0) - return algorithm; - - initialize_libgcrypt(); - - hash_size = gcry_md_get_algo_dlen(algorithm); - assert(hash_size > 0); - - if (nsec3->nsec3.next_hashed_name_size != hash_size) - return -EINVAL; - - r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true); - if (r < 0) - return r; - - gcry_md_open(&md, algorithm, 0); - if (!md) - return -EIO; - - gcry_md_write(md, wire_format, r); - gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); - - result = gcry_md_read(md, 0); - if (!result) { - r = -EIO; - goto finish; - } - - for (k = 0; k < nsec3->nsec3.iterations; k++) { - uint8_t tmp[hash_size]; - memcpy(tmp, result, hash_size); - - gcry_md_reset(md); - gcry_md_write(md, tmp, hash_size); - gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); - - result = gcry_md_read(md, 0); - if (!result) { - r = -EIO; - goto finish; - } - } - - memcpy(ret, result, hash_size); - r = (int) hash_size; - -finish: - gcry_md_close(md); - return r; -} - -static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) { - const char *a, *b; - int r; - - assert(rr); - - if (rr->key->type != DNS_TYPE_NSEC3) - return 0; - - /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */ - if (!IN_SET(rr->nsec3.flags, 0, 1)) - return 0; - - /* Ignore NSEC3 RRs whose algorithm we don't know */ - if (nsec3_hash_to_gcrypt_md(rr->nsec3.algorithm) < 0) - return 0; - /* Ignore NSEC3 RRs with an excessive number of required iterations */ - if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX) - return 0; - - /* Ignore NSEC3 RRs generated from wildcards. If these NSEC3 RRs weren't correctly signed we can't make this - * check (since rr->n_skip_labels_source is -1), but that's OK, as we won't trust them anyway in that case. */ - if (rr->n_skip_labels_source != 0 && rr->n_skip_labels_source != (unsigned) -1) - return 0; - /* Ignore NSEC3 RRs that are located anywhere else than one label below the zone */ - if (rr->n_skip_labels_signer != 1 && rr->n_skip_labels_signer != (unsigned) -1) - return 0; - - if (!nsec3) - return 1; - - /* If a second NSEC3 RR is specified, also check if they are from the same zone. */ - - if (nsec3 == rr) /* Shortcut */ - return 1; - - if (rr->key->class != nsec3->key->class) - return 0; - if (rr->nsec3.algorithm != nsec3->nsec3.algorithm) - return 0; - if (rr->nsec3.iterations != nsec3->nsec3.iterations) - return 0; - if (rr->nsec3.salt_size != nsec3->nsec3.salt_size) - return 0; - if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0) - return 0; - - a = DNS_RESOURCE_KEY_NAME(rr->key); - r = dns_name_parent(&a); /* strip off hash */ - if (r < 0) - return r; - if (r == 0) - return 0; - - b = DNS_RESOURCE_KEY_NAME(nsec3->key); - r = dns_name_parent(&b); /* strip off hash */ - if (r < 0) - return r; - if (r == 0) - return 0; - - /* Make sure both have the same parent */ - return dns_name_equal(a, b); -} - -static int nsec3_hashed_domain_format(const uint8_t *hashed, size_t hashed_size, const char *zone, char **ret) { - _cleanup_free_ char *l = NULL; - char *j; - - assert(hashed); - assert(hashed_size > 0); - assert(zone); - assert(ret); - - l = base32hexmem(hashed, hashed_size, false); - if (!l) - return -ENOMEM; - - j = strjoin(l, ".", zone, NULL); - if (!j) - return -ENOMEM; - - *ret = j; - return (int) hashed_size; -} - -static int nsec3_hashed_domain_make(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) { - uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; - int hashed_size; - - assert(nsec3); - assert(domain); - assert(zone); - assert(ret); - - hashed_size = dnssec_nsec3_hash(nsec3, domain, hashed); - if (hashed_size < 0) - return hashed_size; - - return nsec3_hashed_domain_format(hashed, (size_t) hashed_size, zone, ret); -} - -/* See RFC 5155, Section 8 - * First try to find a NSEC3 record that matches our query precisely, if that fails, find the closest - * enclosure. Secondly, find a proof that there is no closer enclosure and either a proof that there - * is no wildcard domain as a direct descendant of the closest enclosure, or find an NSEC3 record that - * matches the wildcard domain. - * - * Based on this we can prove either the existence of the record in @key, or NXDOMAIN or NODATA, or - * that there is no proof either way. The latter is the case if a the proof of non-existence of a given - * name uses an NSEC3 record with the opt-out bit set. Lastly, if we are given insufficient NSEC3 records - * to conclude anything we indicate this by returning NO_RR. */ -static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { - _cleanup_free_ char *next_closer_domain = NULL, *wildcard_domain = NULL; - const char *zone, *p, *pp = NULL, *wildcard; - DnsResourceRecord *rr, *enclosure_rr, *zone_rr, *wildcard_rr = NULL; - DnsAnswerFlags flags; - int hashed_size, r; - bool a, no_closer = false, no_wildcard = false, optout = false; - - assert(key); - assert(result); - - /* First step, find the zone name and the NSEC3 parameters of the zone. - * it is sufficient to look for the longest common suffix we find with - * any NSEC3 RR in the response. Any NSEC3 record will do as all NSEC3 - * records from a given zone in a response must use the same - * parameters. */ - zone = DNS_RESOURCE_KEY_NAME(key); - for (;;) { - DNS_ANSWER_FOREACH_FLAGS(zone_rr, flags, answer) { - r = nsec3_is_good(zone_rr, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(zone_rr->key), 1, zone); - if (r < 0) - return r; - if (r > 0) - goto found_zone; - } - - /* Strip one label from the front */ - r = dns_name_parent(&zone); - if (r < 0) - return r; - if (r == 0) - break; - } - - *result = DNSSEC_NSEC_NO_RR; - return 0; - -found_zone: - /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */ - p = DNS_RESOURCE_KEY_NAME(key); - for (;;) { - _cleanup_free_ char *hashed_domain = NULL; - - hashed_size = nsec3_hashed_domain_make(zone_rr, p, zone, &hashed_domain); - if (hashed_size == -EOPNOTSUPP) { - *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM; - return 0; - } - if (hashed_size < 0) - return hashed_size; - - DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) { - - r = nsec3_is_good(enclosure_rr, zone_rr); - if (r < 0) - return r; - if (r == 0) - continue; - - if (enclosure_rr->nsec3.next_hashed_name_size != (size_t) hashed_size) - continue; - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(enclosure_rr->key), hashed_domain); - if (r < 0) - return r; - if (r > 0) { - a = flags & DNS_ANSWER_AUTHENTICATED; - goto found_closest_encloser; - } - } - - /* We didn't find the closest encloser with this name, - * but let's remember this domain name, it might be - * the next closer name */ - - pp = p; - - /* Strip one label from the front */ - r = dns_name_parent(&p); - if (r < 0) - return r; - if (r == 0) - break; - } - - *result = DNSSEC_NSEC_NO_RR; - return 0; - -found_closest_encloser: - /* We found a closest encloser in 'p'; next closer is 'pp' */ - - if (!pp) { - /* We have an exact match! If we area looking for a DS RR, then we must insist that we got the NSEC3 RR - * from the parent. Otherwise the one from the child. Do so, by checking whether SOA and NS are - * appropriately set. */ - - if (key->type == DNS_TYPE_DS) { - if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) - return -EBADMSG; - } else { - if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && - !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) - return -EBADMSG; - } - - /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */ - if (bitmap_isset(enclosure_rr->nsec3.types, key->type)) - *result = DNSSEC_NSEC_FOUND; - else if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_CNAME)) - *result = DNSSEC_NSEC_CNAME; - else - *result = DNSSEC_NSEC_NODATA; - - if (authenticated) - *authenticated = a; - if (ttl) - *ttl = enclosure_rr->ttl; - - return 0; - } - - /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */ - if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_DNAME)) - return -EBADMSG; - - /* Ensure that this data is from the delegated domain - * (i.e. originates from the "lower" DNS server), and isn't - * just glue records (i.e. doesn't originate from the "upper" - * DNS server). */ - if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && - !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) - return -EBADMSG; - - /* Prove that there is no next closer and whether or not there is a wildcard domain. */ - - wildcard = strjoina("*.", p); - r = nsec3_hashed_domain_make(enclosure_rr, wildcard, zone, &wildcard_domain); - if (r < 0) - return r; - if (r != hashed_size) - return -EBADMSG; - - r = nsec3_hashed_domain_make(enclosure_rr, pp, zone, &next_closer_domain); - if (r < 0) - return r; - if (r != hashed_size) - return -EBADMSG; - - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - _cleanup_free_ char *next_hashed_domain = NULL; - - r = nsec3_is_good(rr, zone_rr); - if (r < 0) - return r; - if (r == 0) - continue; - - r = nsec3_hashed_domain_format(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, zone, &next_hashed_domain); - if (r < 0) - return r; - - r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), next_closer_domain, next_hashed_domain); - if (r < 0) - return r; - if (r > 0) { - if (rr->nsec3.flags & 1) - optout = true; - - a = a && (flags & DNS_ANSWER_AUTHENTICATED); - - no_closer = true; - } - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain); - if (r < 0) - return r; - if (r > 0) { - a = a && (flags & DNS_ANSWER_AUTHENTICATED); - - wildcard_rr = rr; - } - - r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain, next_hashed_domain); - if (r < 0) - return r; - if (r > 0) { - if (rr->nsec3.flags & 1) - /* This only makes sense if we have a wildcard delegation, which is - * very unlikely, see RFC 4592, Section 4.2, but we cannot rely on - * this not happening, so hence cannot simply conclude NXDOMAIN as - * we would wish */ - optout = true; - - a = a && (flags & DNS_ANSWER_AUTHENTICATED); - - no_wildcard = true; - } - } - - if (wildcard_rr && no_wildcard) - return -EBADMSG; - - if (!no_closer) { - *result = DNSSEC_NSEC_NO_RR; - return 0; - } - - if (wildcard_rr) { - /* A wildcard exists that matches our query. */ - if (optout) - /* This is not specified in any RFC to the best of my knowledge, but - * if the next closer enclosure is covered by an opt-out NSEC3 RR - * it means that we cannot prove that the source of synthesis is - * correct, as there may be a closer match. */ - *result = DNSSEC_NSEC_OPTOUT; - else if (bitmap_isset(wildcard_rr->nsec3.types, key->type)) - *result = DNSSEC_NSEC_FOUND; - else if (bitmap_isset(wildcard_rr->nsec3.types, DNS_TYPE_CNAME)) - *result = DNSSEC_NSEC_CNAME; - else - *result = DNSSEC_NSEC_NODATA; - } else { - if (optout) - /* The RFC only specifies that we have to care for optout for NODATA for - * DS records. However, children of an insecure opt-out delegation should - * also be considered opt-out, rather than verified NXDOMAIN. - * Note that we do not require a proof of wildcard non-existence if the - * next closer domain is covered by an opt-out, as that would not provide - * any additional information. */ - *result = DNSSEC_NSEC_OPTOUT; - else if (no_wildcard) - *result = DNSSEC_NSEC_NXDOMAIN; - else { - *result = DNSSEC_NSEC_NO_RR; - - return 0; - } - } - - if (authenticated) - *authenticated = a; - - if (ttl) - *ttl = enclosure_rr->ttl; - - return 0; -} - -static int dnssec_nsec_wildcard_equal(DnsResourceRecord *rr, const char *name) { - char label[DNS_LABEL_MAX]; - const char *n; - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether the specified RR has a name beginning in "*.", and if the rest is a suffix of our name */ - - if (rr->n_skip_labels_source != 1) - return 0; - - n = DNS_RESOURCE_KEY_NAME(rr->key); - r = dns_label_unescape(&n, label, sizeof(label)); - if (r <= 0) - return r; - if (r != 1 || label[0] != '*') - return 0; - - return dns_name_endswith(name, n); -} - -static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) { - const char *nn, *common_suffix; - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether the specified nsec RR indicates that name is an empty non-terminal (ENT) - * - * A couple of examples: - * - * NSEC bar → waldo.foo.bar: indicates that foo.bar exists and is an ENT - * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that xoo.bar and zzz.xoo.bar exist and are ENTs - * NSEC yyy.zzz.xoo.bar → bar: indicates pretty much nothing about ENTs - */ - - /* First, determine parent of next domain. */ - nn = rr->nsec.next_domain_name; - r = dns_name_parent(&nn); - if (r <= 0) - return r; - - /* If the name we just determined is not equal or child of the name we are interested in, then we can't say - * anything at all. */ - r = dns_name_endswith(nn, 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. */ - r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix); - if (r < 0) - return r; - - return dns_name_endswith(name, common_suffix); -} - -static int dnssec_nsec_from_parent_zone(DnsResourceRecord *rr, const char *name) { - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether this NSEC originates to the parent zone or the child zone. */ - - r = dns_name_parent(&name); - if (r <= 0) - return r; - - r = dns_name_equal(name, DNS_RESOURCE_KEY_NAME(rr->key)); - if (r <= 0) - return r; - - /* DNAME, and NS without SOA is an indication for a delegation. */ - if (bitmap_isset(rr->nsec.types, DNS_TYPE_DNAME)) - return 1; - - if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) - return 1; - - return 0; -} - -static int dnssec_nsec_covers(DnsResourceRecord *rr, const char *name) { - const char *common_suffix, *p; - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether the "Next Closer" is witin the space covered by the specified RR. */ - - r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix); - if (r < 0) - return r; - - for (;;) { - p = name; - r = dns_name_parent(&name); - if (r < 0) - return r; - if (r == 0) - return 0; - - r = dns_name_equal(name, common_suffix); - if (r < 0) - return r; - if (r > 0) - break; - } - - /* p is now the "Next Closer". */ - - return dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), p, rr->nsec.next_domain_name); -} - -static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name) { - const char *common_suffix, *wc; - int r; - - assert(rr); - assert(rr->key->type == DNS_TYPE_NSEC); - - /* Checks whether the "Wildcard at the Closest Encloser" is within the space covered by the specified - * RR. Specifically, checks whether 'name' has the common suffix of the NSEC RR's owner and next names as - * suffix, and whether the NSEC covers the name generated by that suffix prepended with an asterisk label. - * - * NSEC bar → waldo.foo.bar: indicates that *.bar and *.foo.bar do not exist - * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that *.xoo.bar and *.zzz.xoo.bar do not exist (and more ...) - * NSEC yyy.zzz.xoo.bar → bar: indicates that a number of wildcards don#t exist either... - */ - - r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix); - if (r < 0) - return r; - - /* If the common suffix is not shared by the name we are interested in, it has nothing to say for us. */ - r = dns_name_endswith(name, common_suffix); - if (r <= 0) - return r; - - wc = strjoina("*.", common_suffix, NULL); - return dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), wc, rr->nsec.next_domain_name); -} - -int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { - bool have_nsec3 = false, covering_rr_authenticated = false, wildcard_rr_authenticated = false; - DnsResourceRecord *rr, *covering_rr = NULL, *wildcard_rr = NULL; - DnsAnswerFlags flags; - const char *name; - int r; - - assert(key); - assert(result); - - /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */ - - name = DNS_RESOURCE_KEY_NAME(key); - - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - - if (rr->key->class != key->class) - continue; - - have_nsec3 = have_nsec3 || (rr->key->type == DNS_TYPE_NSEC3); - - if (rr->key->type != DNS_TYPE_NSEC) - continue; - - /* The following checks only make sense for NSEC RRs that are not expanded from a wildcard */ - r = dns_resource_record_is_synthetic(rr); - if (r < 0) - return r; - if (r > 0) - continue; - - /* Check if this is a direct match. If so, we have encountered a NODATA case */ - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), name); - if (r < 0) - return r; - if (r == 0) { - /* If it's not a direct match, maybe it's a wild card match? */ - r = dnssec_nsec_wildcard_equal(rr, name); - if (r < 0) - return r; - } - if (r > 0) { - if (key->type == DNS_TYPE_DS) { - /* If we look for a DS RR and the server sent us the NSEC RR of the child zone - * we have a problem. For DS RRs we want the NSEC RR from the parent */ - if (bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) - continue; - } else { - /* For all RR types, ensure that if NS is set SOA is set too, so that we know - * we got the child's NSEC. */ - if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && - !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) - continue; - } - - if (bitmap_isset(rr->nsec.types, key->type)) - *result = DNSSEC_NSEC_FOUND; - else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME)) - *result = DNSSEC_NSEC_CNAME; - else - *result = DNSSEC_NSEC_NODATA; - - if (authenticated) - *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - if (ttl) - *ttl = rr->ttl; - - return 0; - } - - /* Check if the name we are looking for is an empty non-terminal within the owner or next name - * of the NSEC RR. */ - r = dnssec_nsec_in_path(rr, name); - if (r < 0) - return r; - if (r > 0) { - *result = DNSSEC_NSEC_NODATA; - - if (authenticated) - *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - if (ttl) - *ttl = rr->ttl; - - return 0; - } - - /* The following two "covering" checks, are not useful if the NSEC is from the parent */ - r = dnssec_nsec_from_parent_zone(rr, name); - if (r < 0) - return r; - if (r > 0) - continue; - - /* Check if this NSEC RR proves the absence of an explicit RR under this name */ - r = dnssec_nsec_covers(rr, name); - if (r < 0) - return r; - if (r > 0 && (!covering_rr || !covering_rr_authenticated)) { - covering_rr = rr; - covering_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; - } - - /* Check if this NSEC RR proves the absence of a wildcard RR under this name */ - r = dnssec_nsec_covers_wildcard(rr, name); - if (r < 0) - return r; - if (r > 0 && (!wildcard_rr || !wildcard_rr_authenticated)) { - wildcard_rr = rr; - wildcard_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; - } - } - - if (covering_rr && wildcard_rr) { - /* If we could prove that neither the name itself, nor the wildcard at the closest encloser exists, we - * proved the NXDOMAIN case. */ - *result = DNSSEC_NSEC_NXDOMAIN; - - if (authenticated) - *authenticated = covering_rr_authenticated && wildcard_rr_authenticated; - if (ttl) - *ttl = MIN(covering_rr->ttl, wildcard_rr->ttl); - - return 0; - } - - /* OK, this was not sufficient. Let's see if NSEC3 can help. */ - if (have_nsec3) - return dnssec_test_nsec3(answer, key, result, authenticated, ttl); - - /* No approproate NSEC RR found, report this. */ - *result = DNSSEC_NSEC_NO_RR; - return 0; -} - -static int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated) { - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int r; - - assert(name); - assert(zone); - - /* Checks whether there's an NSEC/NSEC3 that proves that the specified 'name' is non-existing in the specified - * 'zone'. The 'zone' must be a suffix of the 'name'. */ - - DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { - bool found = false; - - if (rr->key->type != type && type != DNS_TYPE_ANY) - continue; - - switch (rr->key->type) { - - case DNS_TYPE_NSEC: - - /* We only care for NSEC RRs from the indicated zone */ - r = dns_resource_record_is_signer(rr, zone); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), name, rr->nsec.next_domain_name); - if (r < 0) - return r; - - found = r > 0; - break; - - case DNS_TYPE_NSEC3: { - _cleanup_free_ char *hashed_domain = NULL, *next_hashed_domain = NULL; - - /* We only care for NSEC3 RRs from the indicated zone */ - r = dns_resource_record_is_signer(rr, zone); - if (r < 0) - return r; - if (r == 0) - continue; - - r = nsec3_is_good(rr, NULL); - if (r < 0) - return r; - if (r == 0) - break; - - /* Format the domain we are testing with the NSEC3 RR's hash function */ - r = nsec3_hashed_domain_make( - rr, - name, - zone, - &hashed_domain); - if (r < 0) - return r; - if ((size_t) r != rr->nsec3.next_hashed_name_size) - break; - - /* Format the NSEC3's next hashed name as proper domain name */ - r = nsec3_hashed_domain_format( - rr->nsec3.next_hashed_name, - rr->nsec3.next_hashed_name_size, - zone, - &next_hashed_domain); - if (r < 0) - return r; - - r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain, next_hashed_domain); - if (r < 0) - return r; - - found = r > 0; - break; - } - - default: - continue; - } - - if (found) { - if (authenticated) - *authenticated = flags & DNS_ANSWER_AUTHENTICATED; - return 1; - } - } - - return 0; -} - -static int dnssec_test_positive_wildcard_nsec3( - DnsAnswer *answer, - const char *name, - const char *source, - const char *zone, - bool *authenticated) { - - const char *next_closer = NULL; - int r; - - /* Run a positive NSEC3 wildcard proof. Specifically: - * - * A proof that the the "next closer" of the generating wildcard does not exist. - * - * Note a key difference between the NSEC3 and NSEC versions of the proof. NSEC RRs don't have to exist for - * empty non-transients. NSEC3 RRs however have to. This means it's sufficient to check if the next closer name - * exists for the NSEC3 RR and we are done. - * - * To prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f all we have to check is that - * c.d.e.f does not exist. */ - - for (;;) { - next_closer = name; - r = dns_name_parent(&name); - if (r < 0) - return r; - if (r == 0) - return 0; - - r = dns_name_equal(name, source); - if (r < 0) - return r; - if (r > 0) - break; - } - - return dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC3, next_closer, zone, authenticated); -} - -static int dnssec_test_positive_wildcard_nsec( - DnsAnswer *answer, - const char *name, - const char *source, - const char *zone, - bool *_authenticated) { - - bool authenticated = true; - int r; - - /* Run a positive NSEC wildcard proof. Specifically: - * - * A proof that there's neither a wildcard name nor a non-wildcard name that is a suffix of the name "name" and - * a prefix of the synthesizing source "source" in the zone "zone". - * - * See RFC 5155, Section 8.8 and RFC 4035, Section 5.3.4 - * - * Note that if we want to prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f, then we - * have to prove that none of the following exist: - * - * 1) a.b.c.d.e.f - * 2) *.b.c.d.e.f - * 3) b.c.d.e.f - * 4) *.c.d.e.f - * 5) c.d.e.f - * - */ - - for (;;) { - _cleanup_free_ char *wc = NULL; - bool a = false; - - /* Check if there's an NSEC or NSEC3 RR that proves that the mame we determined is really non-existing, - * i.e between the owner name and the next name of an NSEC RR. */ - r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, name, zone, &a); - if (r <= 0) - return r; - - authenticated = authenticated && a; - - /* Strip one label off */ - r = dns_name_parent(&name); - if (r <= 0) - return r; - - /* Did we reach the source of synthesis? */ - r = dns_name_equal(name, source); - if (r < 0) - return r; - if (r > 0) { - /* Successful exit */ - *_authenticated = authenticated; - return 1; - } - - /* Safety check, that the source of synthesis is still our suffix */ - r = dns_name_endswith(name, source); - if (r < 0) - return r; - if (r == 0) - return -EBADMSG; - - /* Replace the label we stripped off with an asterisk */ - wc = strappend("*.", name); - if (!wc) - return -ENOMEM; - - /* And check if the proof holds for the asterisk name, too */ - r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, wc, zone, &a); - if (r <= 0) - return r; - - authenticated = authenticated && a; - /* In the next iteration we'll check the non-asterisk-prefixed version */ - } -} - -int dnssec_test_positive_wildcard( - DnsAnswer *answer, - const char *name, - const char *source, - const char *zone, - bool *authenticated) { - - int r; - - assert(name); - assert(source); - assert(zone); - assert(authenticated); - - r = dns_answer_contains_zone_nsec3(answer, zone); - if (r < 0) - return r; - if (r > 0) - return dnssec_test_positive_wildcard_nsec3(answer, name, source, zone, authenticated); - else - return dnssec_test_positive_wildcard_nsec(answer, name, source, zone, authenticated); -} - -#else - -int dnssec_verify_rrset( - DnsAnswer *a, - const DnsResourceKey *key, - DnsResourceRecord *rrsig, - DnsResourceRecord *dnskey, - usec_t realtime, - DnssecResult *result) { - - return -EOPNOTSUPP; -} - -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { - - return -EOPNOTSUPP; -} - -int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { - - return -EOPNOTSUPP; -} - -int dnssec_verify_rrset_search( - DnsAnswer *a, - const DnsResourceKey *key, - DnsAnswer *validated_dnskeys, - usec_t realtime, - DnssecResult *result, - DnsResourceRecord **ret_rrsig) { - - return -EOPNOTSUPP; -} - -int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { - - return -EOPNOTSUPP; -} - -int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { - - return -EOPNOTSUPP; -} - -int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { - - return -EOPNOTSUPP; -} - -int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { - - return -EOPNOTSUPP; -} - -int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { - - return -EOPNOTSUPP; -} - -int dnssec_test_positive_wildcard( - DnsAnswer *answer, - const char *name, - const char *source, - const char *zone, - bool *authenticated) { - - return -EOPNOTSUPP; -} - -#endif - -static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = { - [DNSSEC_VALIDATED] = "validated", - [DNSSEC_VALIDATED_WILDCARD] = "validated-wildcard", - [DNSSEC_INVALID] = "invalid", - [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired", - [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm", - [DNSSEC_NO_SIGNATURE] = "no-signature", - [DNSSEC_MISSING_KEY] = "missing-key", - [DNSSEC_UNSIGNED] = "unsigned", - [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary", - [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch", - [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server", -}; -DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult); - -static const char* const dnssec_verdict_table[_DNSSEC_VERDICT_MAX] = { - [DNSSEC_SECURE] = "secure", - [DNSSEC_INSECURE] = "insecure", - [DNSSEC_BOGUS] = "bogus", - [DNSSEC_INDETERMINATE] = "indeterminate", -}; -DEFINE_STRING_TABLE_LOOKUP(dnssec_verdict, DnssecVerdict); diff --git a/src/resolve/resolved-dns-dnssec.h b/src/resolve/resolved-dns-dnssec.h deleted file mode 100644 index 77bd4d71bf..0000000000 --- a/src/resolve/resolved-dns-dnssec.h +++ /dev/null @@ -1,102 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -typedef enum DnssecResult DnssecResult; -typedef enum DnssecVerdict DnssecVerdict; - -#include "dns-domain.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-rr.h" - -enum DnssecResult { - /* These five are returned by dnssec_verify_rrset() */ - DNSSEC_VALIDATED, - DNSSEC_VALIDATED_WILDCARD, /* Validated via a wildcard RRSIG, further NSEC/NSEC3 checks necessary */ - DNSSEC_INVALID, - DNSSEC_SIGNATURE_EXPIRED, - DNSSEC_UNSUPPORTED_ALGORITHM, - - /* These two are added by dnssec_verify_rrset_search() */ - DNSSEC_NO_SIGNATURE, - DNSSEC_MISSING_KEY, - - /* These two are added by the DnsTransaction logic */ - DNSSEC_UNSIGNED, - DNSSEC_FAILED_AUXILIARY, - DNSSEC_NSEC_MISMATCH, - DNSSEC_INCOMPATIBLE_SERVER, - - _DNSSEC_RESULT_MAX, - _DNSSEC_RESULT_INVALID = -1 -}; - -enum DnssecVerdict { - DNSSEC_SECURE, - DNSSEC_INSECURE, - DNSSEC_BOGUS, - DNSSEC_INDETERMINATE, - - _DNSSEC_VERDICT_MAX, - _DNSSEC_VERDICT_INVALID = -1 -}; - -#define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2) - -/* The longest digest we'll ever generate, of all digest algorithms we support */ -#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32)) - -int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok); -int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig); - -int dnssec_verify_rrset(DnsAnswer *answer, const DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result); -int dnssec_verify_rrset_search(DnsAnswer *answer, const DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result, DnsResourceRecord **rrsig); - -int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke); -int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds); - -int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key); - -uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke); - -int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max); - -int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret); - -typedef enum DnssecNsecResult { - DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */ - DNSSEC_NSEC_CNAME, /* Didn't find what was asked for, but did find CNAME */ - DNSSEC_NSEC_UNSUPPORTED_ALGORITHM, - DNSSEC_NSEC_NXDOMAIN, - DNSSEC_NSEC_NODATA, - DNSSEC_NSEC_FOUND, - DNSSEC_NSEC_OPTOUT, -} DnssecNsecResult; - -int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl); - - -int dnssec_test_positive_wildcard(DnsAnswer *a, const char *name, const char *source, const char *zone, bool *authenticated); - -const char* dnssec_result_to_string(DnssecResult m) _const_; -DnssecResult dnssec_result_from_string(const char *s) _pure_; - -const char* dnssec_verdict_to_string(DnssecVerdict m) _const_; -DnssecVerdict dnssec_verdict_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-packet.c b/src/resolve/resolved-dns-packet.c deleted file mode 100644 index c940dd8929..0000000000 --- a/src/resolve/resolved-dns-packet.c +++ /dev/null @@ -1,2295 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . - ***/ - -#include "alloc-util.h" -#include "dns-domain.h" -#include "resolved-dns-packet.h" -#include "string-table.h" -#include "strv.h" -#include "unaligned.h" -#include "utf8.h" -#include "util.h" - -#define EDNS0_OPT_DO (1<<15) - -int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { - DnsPacket *p; - size_t a; - - assert(ret); - - if (mtu <= UDP_PACKET_HEADER_SIZE) - a = DNS_PACKET_SIZE_START; - else - a = mtu - UDP_PACKET_HEADER_SIZE; - - if (a < DNS_PACKET_HEADER_SIZE) - a = DNS_PACKET_HEADER_SIZE; - - /* round up to next page size */ - a = PAGE_ALIGN(ALIGN(sizeof(DnsPacket)) + a) - ALIGN(sizeof(DnsPacket)); - - /* make sure we never allocate more than useful */ - if (a > DNS_PACKET_SIZE_MAX) - a = DNS_PACKET_SIZE_MAX; - - p = malloc0(ALIGN(sizeof(DnsPacket)) + a); - if (!p) - return -ENOMEM; - - p->size = p->rindex = DNS_PACKET_HEADER_SIZE; - p->allocated = a; - p->protocol = protocol; - p->opt_start = p->opt_size = (size_t) -1; - p->n_ref = 1; - - *ret = p; - - return 0; -} - -void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated) { - - DnsPacketHeader *h; - - assert(p); - - h = DNS_PACKET_HEADER(p); - - switch(p->protocol) { - case DNS_PROTOCOL_LLMNR: - assert(!truncated); - - h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, - 0 /* opcode */, - 0 /* c */, - 0 /* tc */, - 0 /* t */, - 0 /* ra */, - 0 /* ad */, - 0 /* cd */, - 0 /* rcode */)); - break; - - case DNS_PROTOCOL_MDNS: - h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, - 0 /* opcode */, - 0 /* aa */, - truncated /* tc */, - 0 /* rd (ask for recursion) */, - 0 /* ra */, - 0 /* ad */, - 0 /* cd */, - 0 /* rcode */)); - break; - - default: - assert(!truncated); - - h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, - 0 /* opcode */, - 0 /* aa */, - 0 /* tc */, - 1 /* rd (ask for recursion) */, - 0 /* ra */, - 0 /* ad */, - dnssec_checking_disabled /* cd */, - 0 /* rcode */)); - } -} - -int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) { - DnsPacket *p; - int r; - - assert(ret); - - r = dns_packet_new(&p, protocol, mtu); - if (r < 0) - return r; - - /* Always set the TC bit to 0 initially. - * If there are multiple packets later, we'll update the bit shortly before sending. - */ - dns_packet_set_flags(p, dnssec_checking_disabled, false); - - *ret = p; - return 0; -} - -DnsPacket *dns_packet_ref(DnsPacket *p) { - - if (!p) - return NULL; - - assert(!p->on_stack); - - assert(p->n_ref > 0); - p->n_ref++; - return p; -} - -static void dns_packet_free(DnsPacket *p) { - char *s; - - assert(p); - - dns_question_unref(p->question); - dns_answer_unref(p->answer); - dns_resource_record_unref(p->opt); - - while ((s = hashmap_steal_first_key(p->names))) - free(s); - hashmap_free(p->names); - - free(p->_data); - - if (!p->on_stack) - free(p); -} - -DnsPacket *dns_packet_unref(DnsPacket *p) { - if (!p) - return NULL; - - assert(p->n_ref > 0); - - dns_packet_unref(p->more); - - if (p->n_ref == 1) - dns_packet_free(p); - else - p->n_ref--; - - return NULL; -} - -int dns_packet_validate(DnsPacket *p) { - assert(p); - - if (p->size < DNS_PACKET_HEADER_SIZE) - return -EBADMSG; - - if (p->size > DNS_PACKET_SIZE_MAX) - return -EBADMSG; - - return 1; -} - -int dns_packet_validate_reply(DnsPacket *p) { - int r; - - assert(p); - - r = dns_packet_validate(p); - if (r < 0) - return r; - - if (DNS_PACKET_QR(p) != 1) - return 0; - - if (DNS_PACKET_OPCODE(p) != 0) - return -EBADMSG; - - switch (p->protocol) { - - case DNS_PROTOCOL_LLMNR: - /* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */ - if (DNS_PACKET_QDCOUNT(p) != 1) - return -EBADMSG; - - break; - - case DNS_PROTOCOL_MDNS: - /* RFC 6762, Section 18 */ - if (DNS_PACKET_RCODE(p) != 0) - return -EBADMSG; - - break; - - default: - break; - } - - return 1; -} - -int dns_packet_validate_query(DnsPacket *p) { - int r; - - assert(p); - - r = dns_packet_validate(p); - if (r < 0) - return r; - - if (DNS_PACKET_QR(p) != 0) - return 0; - - if (DNS_PACKET_OPCODE(p) != 0) - return -EBADMSG; - - if (DNS_PACKET_TC(p)) - return -EBADMSG; - - switch (p->protocol) { - - case DNS_PROTOCOL_LLMNR: - /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ - if (DNS_PACKET_QDCOUNT(p) != 1) - return -EBADMSG; - - /* RFC 4795, Section 2.1.1. says to discard all queries with ANCOUNT != 0 */ - if (DNS_PACKET_ANCOUNT(p) > 0) - return -EBADMSG; - - /* RFC 4795, Section 2.1.1. says to discard all queries with NSCOUNT != 0 */ - if (DNS_PACKET_NSCOUNT(p) > 0) - return -EBADMSG; - - break; - - case DNS_PROTOCOL_MDNS: - /* RFC 6762, Section 18 */ - if (DNS_PACKET_AA(p) != 0 || - DNS_PACKET_RD(p) != 0 || - DNS_PACKET_RA(p) != 0 || - DNS_PACKET_AD(p) != 0 || - DNS_PACKET_CD(p) != 0 || - DNS_PACKET_RCODE(p) != 0) - return -EBADMSG; - - break; - - default: - break; - } - - return 1; -} - -static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start) { - assert(p); - - if (p->size + add > p->allocated) { - size_t a; - - a = PAGE_ALIGN((p->size + add) * 2); - if (a > DNS_PACKET_SIZE_MAX) - a = DNS_PACKET_SIZE_MAX; - - if (p->size + add > a) - return -EMSGSIZE; - - if (p->_data) { - void *d; - - d = realloc(p->_data, a); - if (!d) - return -ENOMEM; - - p->_data = d; - } else { - p->_data = malloc(a); - if (!p->_data) - return -ENOMEM; - - memcpy(p->_data, (uint8_t*) p + ALIGN(sizeof(DnsPacket)), p->size); - memzero((uint8_t*) p->_data + p->size, a - p->size); - } - - p->allocated = a; - } - - if (start) - *start = p->size; - - if (ret) - *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->size; - - p->size += add; - return 0; -} - -void dns_packet_truncate(DnsPacket *p, size_t sz) { - Iterator i; - char *s; - void *n; - - assert(p); - - if (p->size <= sz) - return; - - HASHMAP_FOREACH_KEY(n, s, p->names, i) { - - if (PTR_TO_SIZE(n) < sz) - continue; - - hashmap_remove(p->names, s); - free(s); - } - - p->size = sz; -} - -int dns_packet_append_blob(DnsPacket *p, const void *d, size_t l, size_t *start) { - void *q; - int r; - - assert(p); - - r = dns_packet_extend(p, l, &q, start); - if (r < 0) - return r; - - memcpy(q, d, l); - return 0; -} - -int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start) { - void *d; - int r; - - assert(p); - - r = dns_packet_extend(p, sizeof(uint8_t), &d, start); - if (r < 0) - return r; - - ((uint8_t*) d)[0] = v; - - return 0; -} - -int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start) { - void *d; - int r; - - assert(p); - - r = dns_packet_extend(p, sizeof(uint16_t), &d, start); - if (r < 0) - return r; - - unaligned_write_be16(d, v); - - return 0; -} - -int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) { - void *d; - int r; - - assert(p); - - r = dns_packet_extend(p, sizeof(uint32_t), &d, start); - if (r < 0) - return r; - - unaligned_write_be32(d, v); - - return 0; -} - -int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) { - assert(p); - assert(s); - - return dns_packet_append_raw_string(p, s, strlen(s), start); -} - -int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start) { - void *d; - int r; - - assert(p); - assert(s || size == 0); - - if (size > 255) - return -E2BIG; - - r = dns_packet_extend(p, 1 + size, &d, start); - if (r < 0) - return r; - - ((uint8_t*) d)[0] = (uint8_t) size; - - if (size > 0) - memcpy(((uint8_t*) d) + 1, s, size); - - return 0; -} - -int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonical_candidate, size_t *start) { - uint8_t *w; - int r; - - /* Append a label to a packet. Optionally, does this in DNSSEC - * canonical form, if this label is marked as a candidate for - * it, and the canonical form logic is enabled for the - * packet */ - - assert(p); - assert(d); - - if (l > DNS_LABEL_MAX) - return -E2BIG; - - r = dns_packet_extend(p, 1 + l, (void**) &w, start); - if (r < 0) - return r; - - *(w++) = (uint8_t) l; - - if (p->canonical_form && canonical_candidate) { - size_t i; - - /* Generate in canonical form, as defined by DNSSEC - * RFC 4034, Section 6.2, i.e. all lower-case. */ - - for (i = 0; i < l; i++) - w[i] = (uint8_t) ascii_tolower(d[i]); - } else - /* Otherwise, just copy the string unaltered. This is - * essential for DNS-SD, where the casing of labels - * matters and needs to be retained. */ - memcpy(w, d, l); - - return 0; -} - -int dns_packet_append_name( - DnsPacket *p, - const char *name, - bool allow_compression, - bool canonical_candidate, - size_t *start) { - - size_t saved_size; - int r; - - assert(p); - assert(name); - - if (p->refuse_compression) - allow_compression = false; - - saved_size = p->size; - - while (!dns_name_is_root(name)) { - const char *z = name; - char label[DNS_LABEL_MAX]; - size_t n = 0; - - if (allow_compression) - n = PTR_TO_SIZE(hashmap_get(p->names, name)); - if (n > 0) { - assert(n < p->size); - - if (n < 0x4000) { - r = dns_packet_append_uint16(p, 0xC000 | n, NULL); - if (r < 0) - goto fail; - - goto done; - } - } - - r = dns_label_unescape(&name, label, sizeof(label)); - if (r < 0) - goto fail; - - r = dns_packet_append_label(p, label, r, canonical_candidate, &n); - if (r < 0) - goto fail; - - if (allow_compression) { - _cleanup_free_ char *s = NULL; - - s = strdup(z); - if (!s) { - r = -ENOMEM; - goto fail; - } - - r = hashmap_ensure_allocated(&p->names, &dns_name_hash_ops); - if (r < 0) - goto fail; - - r = hashmap_put(p->names, s, SIZE_TO_PTR(n)); - if (r < 0) - goto fail; - - s = NULL; - } - } - - r = dns_packet_append_uint8(p, 0, NULL); - if (r < 0) - return r; - -done: - if (start) - *start = saved_size; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) { - size_t saved_size; - int r; - - assert(p); - assert(k); - - saved_size = p->size; - - r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, k->type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, k->class, NULL); - if (r < 0) - goto fail; - - if (start) - *start = saved_size; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, const uint8_t *types, size_t *start) { - size_t saved_size; - int r; - - assert(p); - assert(types); - assert(length > 0); - - saved_size = p->size; - - r = dns_packet_append_uint8(p, window, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, length, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, types, length, NULL); - if (r < 0) - goto fail; - - if (start) - *start = saved_size; - - return 0; -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { - Iterator i; - uint8_t window = 0; - uint8_t entry = 0; - uint8_t bitmaps[32] = {}; - unsigned n; - size_t saved_size; - int r; - - assert(p); - - saved_size = p->size; - - BITMAP_FOREACH(n, types, i) { - assert(n <= 0xffff); - - if ((n >> 8) != window && bitmaps[entry / 8] != 0) { - r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); - if (r < 0) - goto fail; - - zero(bitmaps); - } - - window = n >> 8; - entry = n & 255; - - bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); - } - - if (bitmaps[entry / 8] != 0) { - r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); - if (r < 0) - goto fail; - } - - if (start) - *start = saved_size; - - return 0; -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -/* 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) { - 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); - - if (p->opt_start != (size_t) -1) - return -EBUSY; - - assert(p->opt_size == (size_t) -1); - - saved_size = p->size; - - /* empty name */ - r = dns_packet_append_uint8(p, 0, NULL); - if (r < 0) - return r; - - /* type */ - r = dns_packet_append_uint16(p, DNS_TYPE_OPT, NULL); - if (r < 0) - goto fail; - - /* 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); - if (r < 0) - goto fail; - - /* flags: DNSSEC OK (DO), see RFC3225 */ - r = dns_packet_append_uint16(p, edns0_do ? EDNS0_OPT_DO : 0, NULL); - if (r < 0) - goto fail; - - /* RDLENGTH */ - - if (edns0_do) { - /* If DO is on, also append RFC6975 Algorithm data */ - - static const uint8_t rfc6975[] = { - - 0, 5, /* OPTION_CODE: DAU */ - 0, 6, /* LIST_LENGTH */ - DNSSEC_ALGORITHM_RSASHA1, - DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA256, - DNSSEC_ALGORITHM_RSASHA512, - DNSSEC_ALGORITHM_ECDSAP256SHA256, - DNSSEC_ALGORITHM_ECDSAP384SHA384, - - 0, 6, /* OPTION_CODE: DHU */ - 0, 3, /* LIST_LENGTH */ - DNSSEC_DIGEST_SHA1, - DNSSEC_DIGEST_SHA256, - DNSSEC_DIGEST_SHA384, - - 0, 7, /* OPTION_CODE: N3U */ - 0, 1, /* LIST_LENGTH */ - NSEC3_ALGORITHM_SHA1, - }; - - r = dns_packet_append_uint16(p, sizeof(rfc6975), NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL); - } else - r = dns_packet_append_uint16(p, 0, NULL); - - if (r < 0) - goto fail; - - DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) + 1); - - p->opt_start = saved_size; - p->opt_size = p->size - saved_size; - - if (start) - *start = saved_size; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -int dns_packet_truncate_opt(DnsPacket *p) { - assert(p); - - if (p->opt_start == (size_t) -1) { - assert(p->opt_size == (size_t) -1); - return 0; - } - - assert(p->opt_size != (size_t) -1); - assert(DNS_PACKET_ARCOUNT(p) > 0); - - if (p->opt_start + p->opt_size != p->size) - return -EBUSY; - - dns_packet_truncate(p, p->opt_start); - DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) - 1); - p->opt_start = p->opt_size = (size_t) -1; - - return 1; -} - -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; - - assert(p); - assert(rr); - - saved_size = p->size; - - r = dns_packet_append_key(p, rr->key, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->ttl, NULL); - if (r < 0) - goto fail; - - /* Initially we write 0 here */ - r = dns_packet_append_uint16(p, 0, &rdlength_offset); - if (r < 0) - goto fail; - - rds = p->size - saved_size; - - switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - - case DNS_TYPE_SRV: - r = dns_packet_append_uint16(p, rr->srv.priority, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->srv.weight, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->srv.port, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->srv.name, true, false, NULL); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - r = dns_packet_append_name(p, rr->ptr.name, true, false, NULL); - break; - - case DNS_TYPE_HINFO: - r = dns_packet_append_string(p, rr->hinfo.cpu, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_string(p, rr->hinfo.os, NULL); - break; - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - - if (!rr->txt.items) { - /* RFC 6763, section 6.1 suggests to generate - * single empty string for an empty array. */ - - r = dns_packet_append_raw_string(p, NULL, 0, NULL); - if (r < 0) - goto fail; - } else { - DnsTxtItem *i; - - LIST_FOREACH(items, i, rr->txt.items) { - r = dns_packet_append_raw_string(p, i->data, i->length, NULL); - if (r < 0) - goto fail; - } - } - - r = 0; - break; - - case DNS_TYPE_A: - r = dns_packet_append_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); - break; - - case DNS_TYPE_AAAA: - r = dns_packet_append_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); - break; - - case DNS_TYPE_SOA: - r = dns_packet_append_name(p, rr->soa.mname, true, false, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->soa.rname, true, false, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.serial, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.refresh, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.retry, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.expire, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->soa.minimum, NULL); - break; - - case DNS_TYPE_MX: - r = dns_packet_append_uint16(p, rr->mx.priority, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL); - break; - - case DNS_TYPE_LOC: - r = dns_packet_append_uint8(p, rr->loc.version, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->loc.size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->loc.horiz_pre, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->loc.vert_pre, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->loc.latitude, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->loc.longitude, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->loc.altitude, NULL); - break; - - case DNS_TYPE_DS: - r = dns_packet_append_uint16(p, rr->ds.key_tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->ds.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->ds.digest_type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->ds.digest, rr->ds.digest_size, NULL); - break; - - case DNS_TYPE_SSHFP: - r = dns_packet_append_uint8(p, rr->sshfp.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->sshfp.fptype, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, NULL); - break; - - case DNS_TYPE_DNSKEY: - r = dns_packet_append_uint16(p, rr->dnskey.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->dnskey.protocol, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->dnskey.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->dnskey.key, rr->dnskey.key_size, NULL); - break; - - case DNS_TYPE_RRSIG: - r = dns_packet_append_uint16(p, rr->rrsig.type_covered, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->rrsig.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->rrsig.labels, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->rrsig.original_ttl, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->rrsig.expiration, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint32(p, rr->rrsig.inception, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->rrsig.key_tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->rrsig.signature, rr->rrsig.signature_size, NULL); - break; - - case DNS_TYPE_NSEC: - r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_types(p, rr->nsec.types, NULL); - if (r < 0) - goto fail; - - break; - - case DNS_TYPE_NSEC3: - r = dns_packet_append_uint8(p, rr->nsec3.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->nsec3.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint16(p, rr->nsec3.iterations, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->nsec3.salt_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->nsec3.salt, rr->nsec3.salt_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->nsec3.next_hashed_name_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_types(p, rr->nsec3.types, NULL); - if (r < 0) - goto fail; - - break; - - case DNS_TYPE_TLSA: - r = dns_packet_append_uint8(p, rr->tlsa.cert_usage, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->tlsa.selector, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_uint8(p, rr->tlsa.matching_type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_append_blob(p, rr->tlsa.data, rr->tlsa.data_size, NULL); - break; - - case DNS_TYPE_OPT: - case DNS_TYPE_OPENPGPKEY: - case _DNS_TYPE_INVALID: /* unparseable */ - default: - - r = dns_packet_append_blob(p, rr->generic.data, rr->generic.data_size, NULL); - break; - } - if (r < 0) - goto fail; - - /* Let's calculate the actual data size and update the field */ - rdlength = p->size - rdlength_offset - sizeof(uint16_t); - if (rdlength > 0xFFFF) { - r = -ENOSPC; - goto fail; - } - - end = p->size; - p->size = rdlength_offset; - r = dns_packet_append_uint16(p, rdlength, NULL); - if (r < 0) - goto fail; - p->size = end; - - if (start) - *start = saved_size; - - if (rdata_start) - *rdata_start = rds; - - return 0; - -fail: - dns_packet_truncate(p, saved_size); - return r; -} - -int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { - assert(p); - - if (p->rindex + sz > p->size) - return -EMSGSIZE; - - if (ret) - *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->rindex; - - if (start) - *start = p->rindex; - - p->rindex += sz; - return 0; -} - -void dns_packet_rewind(DnsPacket *p, size_t idx) { - assert(p); - assert(idx <= p->size); - assert(idx >= DNS_PACKET_HEADER_SIZE); - - p->rindex = idx; -} - -int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start) { - const void *q; - int r; - - assert(p); - assert(d); - - r = dns_packet_read(p, sz, &q, start); - if (r < 0) - return r; - - memcpy(d, q, sz); - return 0; -} - -static int dns_packet_read_memdup( - DnsPacket *p, size_t size, - void **ret, size_t *ret_size, - size_t *ret_start) { - - const void *src; - size_t start; - int r; - - assert(p); - assert(ret); - - r = dns_packet_read(p, size, &src, &start); - if (r < 0) - return r; - - if (size <= 0) - *ret = NULL; - else { - void *copy; - - copy = memdup(src, size); - if (!copy) - return -ENOMEM; - - *ret = copy; - } - - if (ret_size) - *ret_size = size; - if (ret_start) - *ret_start = start; - - return 0; -} - -int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) { - const void *d; - int r; - - assert(p); - - r = dns_packet_read(p, sizeof(uint8_t), &d, start); - if (r < 0) - return r; - - *ret = ((uint8_t*) d)[0]; - return 0; -} - -int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start) { - const void *d; - int r; - - assert(p); - - r = dns_packet_read(p, sizeof(uint16_t), &d, start); - if (r < 0) - return r; - - *ret = unaligned_read_be16(d); - - return 0; -} - -int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start) { - const void *d; - int r; - - assert(p); - - r = dns_packet_read(p, sizeof(uint32_t), &d, start); - if (r < 0) - return r; - - *ret = unaligned_read_be32(d); - - return 0; -} - -int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) { - size_t saved_rindex; - const void *d; - char *t; - uint8_t c; - int r; - - assert(p); - - saved_rindex = p->rindex; - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read(p, c, &d, NULL); - if (r < 0) - goto fail; - - if (memchr(d, 0, c)) { - r = -EBADMSG; - goto fail; - } - - t = strndup(d, c); - if (!t) { - r = -ENOMEM; - goto fail; - } - - if (!utf8_is_valid(t)) { - free(t); - r = -EBADMSG; - goto fail; - } - - *ret = t; - - if (start) - *start = saved_rindex; - - return 0; - -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) { - size_t saved_rindex; - uint8_t c; - int r; - - assert(p); - - saved_rindex = p->rindex; - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read(p, c, ret, NULL); - if (r < 0) - goto fail; - - if (size) - *size = c; - if (start) - *start = saved_rindex; - - return 0; - -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -int dns_packet_read_name( - DnsPacket *p, - char **_ret, - bool allow_compression, - size_t *start) { - - size_t saved_rindex, after_rindex = 0, jump_barrier; - _cleanup_free_ char *ret = NULL; - size_t n = 0, allocated = 0; - bool first = true; - int r; - - assert(p); - assert(_ret); - - if (p->refuse_compression) - allow_compression = false; - - saved_rindex = p->rindex; - jump_barrier = p->rindex; - - for (;;) { - uint8_t c, d; - - r = dns_packet_read_uint8(p, &c, NULL); - if (r < 0) - goto fail; - - if (c == 0) - /* End of name */ - break; - else if (c <= 63) { - const char *label; - - /* Literal label */ - r = dns_packet_read(p, c, (const void**) &label, NULL); - if (r < 0) - goto fail; - - if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) { - r = -ENOMEM; - goto fail; - } - - if (first) - first = false; - else - ret[n++] = '.'; - - r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - goto fail; - - n += r; - continue; - } else if (allow_compression && (c & 0xc0) == 0xc0) { - uint16_t ptr; - - /* Pointer */ - r = dns_packet_read_uint8(p, &d, NULL); - if (r < 0) - goto fail; - - ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; - if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) { - r = -EBADMSG; - goto fail; - } - - if (after_rindex == 0) - after_rindex = p->rindex; - - /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ - jump_barrier = ptr; - p->rindex = ptr; - } else { - r = -EBADMSG; - goto fail; - } - } - - if (!GREEDY_REALLOC(ret, allocated, n + 1)) { - r = -ENOMEM; - goto fail; - } - - ret[n] = 0; - - if (after_rindex != 0) - p->rindex= after_rindex; - - *_ret = ret; - ret = NULL; - - if (start) - *start = saved_rindex; - - return 0; - -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *start) { - uint8_t window; - uint8_t length; - const uint8_t *bitmap; - uint8_t bit = 0; - unsigned i; - bool found = false; - size_t saved_rindex; - int r; - - assert(p); - assert(types); - - saved_rindex = p->rindex; - - r = bitmap_ensure_allocated(types); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &window, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &length, NULL); - if (r < 0) - goto fail; - - if (length == 0 || length > 32) - return -EBADMSG; - - r = dns_packet_read(p, length, (const void **)&bitmap, NULL); - if (r < 0) - goto fail; - - for (i = 0; i < length; i++) { - uint8_t bitmask = 1 << 7; - - if (!bitmap[i]) { - found = false; - bit += 8; - continue; - } - - found = true; - - while (bitmask) { - if (bitmap[i] & bitmask) { - uint16_t n; - - n = (uint16_t) window << 8 | (uint16_t) bit; - - /* Ignore pseudo-types. see RFC4034 section 4.1.2 */ - if (dns_type_is_pseudo(n)) - continue; - - r = bitmap_set(*types, n); - if (r < 0) - goto fail; - } - - bit ++; - bitmask >>= 1; - } - } - - if (!found) - return -EBADMSG; - - if (start) - *start = saved_rindex; - - return 0; -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) { - size_t saved_rindex; - int r; - - saved_rindex = p->rindex; - - while (p->rindex < saved_rindex + size) { - r = dns_packet_read_type_window(p, types, NULL); - if (r < 0) - goto fail; - - /* don't read past end of current RR */ - if (p->rindex > saved_rindex + size) { - r = -EBADMSG; - goto fail; - } - } - - if (p->rindex != saved_rindex + size) { - r = -EBADMSG; - goto fail; - } - - if (start) - *start = saved_rindex; - - return 0; -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { - _cleanup_free_ char *name = NULL; - bool cache_flush = false; - uint16_t class, type; - DnsResourceKey *key; - size_t saved_rindex; - int r; - - assert(p); - assert(ret); - - saved_rindex = p->rindex; - - r = dns_packet_read_name(p, &name, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint16(p, &type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint16(p, &class, NULL); - if (r < 0) - goto fail; - - if (p->protocol == DNS_PROTOCOL_MDNS) { - /* See RFC6762, Section 10.2 */ - - if (type != DNS_TYPE_OPT && (class & MDNS_RR_CACHE_FLUSH)) { - class &= ~MDNS_RR_CACHE_FLUSH; - cache_flush = true; - } - } - - key = dns_resource_key_new_consume(class, type, name); - if (!key) { - r = -ENOMEM; - goto fail; - } - - name = NULL; - *ret = key; - - if (ret_cache_flush) - *ret_cache_flush = cache_flush; - if (start) - *start = saved_rindex; - - return 0; -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -static bool loc_size_ok(uint8_t size) { - uint8_t m = size >> 4, e = size & 0xF; - - return m <= 9 && e <= 9 && (m > 0 || e == 0); -} - -int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - size_t saved_rindex, offset; - uint16_t rdlength; - bool cache_flush; - int r; - - assert(p); - assert(ret); - - saved_rindex = p->rindex; - - r = dns_packet_read_key(p, &key, &cache_flush, NULL); - if (r < 0) - goto fail; - - if (!dns_class_is_valid_rr(key->class)|| - !dns_type_is_valid_rr(key->type)) { - r = -EBADMSG; - goto fail; - } - - rr = dns_resource_record_new(key); - if (!rr) { - r = -ENOMEM; - goto fail; - } - - r = dns_packet_read_uint32(p, &rr->ttl, NULL); - if (r < 0) - goto fail; - - /* RFC 2181, Section 8, suggests to - * treat a TTL with the MSB set as a zero TTL. */ - if (rr->ttl & UINT32_C(0x80000000)) - rr->ttl = 0; - - r = dns_packet_read_uint16(p, &rdlength, NULL); - if (r < 0) - goto fail; - - if (p->rindex + rdlength > p->size) { - r = -EBADMSG; - goto fail; - } - - offset = p->rindex; - - switch (rr->key->type) { - - case DNS_TYPE_SRV: - r = dns_packet_read_uint16(p, &rr->srv.priority, NULL); - if (r < 0) - goto fail; - r = dns_packet_read_uint16(p, &rr->srv.weight, NULL); - if (r < 0) - goto fail; - r = dns_packet_read_uint16(p, &rr->srv.port, NULL); - if (r < 0) - goto fail; - r = dns_packet_read_name(p, &rr->srv.name, true, NULL); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - r = dns_packet_read_name(p, &rr->ptr.name, true, NULL); - break; - - case DNS_TYPE_HINFO: - r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_string(p, &rr->hinfo.os, NULL); - break; - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - if (rdlength <= 0) { - DnsTxtItem *i; - /* RFC 6763, section 6.1 suggests to treat - * empty TXT RRs as equivalent to a TXT record - * with a single empty string. */ - - i = malloc0(offsetof(DnsTxtItem, data) + 1); /* for safety reasons we add an extra NUL byte */ - if (!i) - return -ENOMEM; - - rr->txt.items = i; - } else { - DnsTxtItem *last = NULL; - - while (p->rindex < offset + rdlength) { - DnsTxtItem *i; - const void *data; - size_t sz; - - r = dns_packet_read_raw_string(p, &data, &sz, NULL); - if (r < 0) - return r; - - i = malloc0(offsetof(DnsTxtItem, data) + sz + 1); /* extra NUL byte at the end */ - if (!i) - return -ENOMEM; - - memcpy(i->data, data, sz); - i->length = sz; - - LIST_INSERT_AFTER(items, rr->txt.items, last, i); - last = i; - } - } - - r = 0; - break; - - case DNS_TYPE_A: - r = dns_packet_read_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); - break; - - case DNS_TYPE_AAAA: - r = dns_packet_read_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); - break; - - case DNS_TYPE_SOA: - r = dns_packet_read_name(p, &rr->soa.mname, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_name(p, &rr->soa.rname, true, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->soa.serial, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->soa.refresh, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->soa.retry, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->soa.expire, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->soa.minimum, NULL); - break; - - case DNS_TYPE_MX: - r = dns_packet_read_uint16(p, &rr->mx.priority, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_name(p, &rr->mx.exchange, true, NULL); - break; - - case DNS_TYPE_LOC: { - uint8_t t; - size_t pos; - - r = dns_packet_read_uint8(p, &t, &pos); - if (r < 0) - goto fail; - - if (t == 0) { - rr->loc.version = t; - - r = dns_packet_read_uint8(p, &rr->loc.size, NULL); - if (r < 0) - goto fail; - - if (!loc_size_ok(rr->loc.size)) { - r = -EBADMSG; - goto fail; - } - - r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); - if (r < 0) - goto fail; - - if (!loc_size_ok(rr->loc.horiz_pre)) { - r = -EBADMSG; - goto fail; - } - - r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); - if (r < 0) - goto fail; - - if (!loc_size_ok(rr->loc.vert_pre)) { - r = -EBADMSG; - goto fail; - } - - r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL); - if (r < 0) - goto fail; - - break; - } else { - dns_packet_rewind(p, pos); - rr->unparseable = true; - goto unparseable; - } - } - - case DNS_TYPE_DS: - r = dns_packet_read_uint16(p, &rr->ds.key_tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->ds.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->ds.digest_type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_memdup(p, rdlength - 4, - &rr->ds.digest, &rr->ds.digest_size, - NULL); - if (r < 0) - goto fail; - - if (rr->ds.digest_size <= 0) { - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - r = -EBADMSG; - goto fail; - } - - break; - - case DNS_TYPE_SSHFP: - r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_memdup(p, rdlength - 2, - &rr->sshfp.fingerprint, &rr->sshfp.fingerprint_size, - NULL); - - if (rr->sshfp.fingerprint_size <= 0) { - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - r = -EBADMSG; - goto fail; - } - - break; - - case DNS_TYPE_DNSKEY: - r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_memdup(p, rdlength - 4, - &rr->dnskey.key, &rr->dnskey.key_size, - NULL); - - if (rr->dnskey.key_size <= 0) { - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - r = -EBADMSG; - goto fail; - } - - break; - - case DNS_TYPE_RRSIG: - r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_memdup(p, offset + rdlength - p->rindex, - &rr->rrsig.signature, &rr->rrsig.signature_size, - NULL); - - if (rr->rrsig.signature_size <= 0) { - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - r = -EBADMSG; - goto fail; - } - - break; - - case DNS_TYPE_NSEC: { - - /* - * RFC6762, section 18.14 explictly states mDNS should use name compression. - * This contradicts RFC3845, section 2.1.1 - */ - - bool allow_compressed = p->protocol == DNS_PROTOCOL_MDNS; - - r = dns_packet_read_name(p, &rr->nsec.next_domain_name, allow_compressed, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL); - if (r < 0) - goto fail; - - /* We accept empty NSEC bitmaps. The bit indicating the presence of the NSEC record itself - * is redundant and in e.g., RFC4956 this fact is used to define a use for NSEC records - * without the NSEC bit set. */ - - break; - } - case DNS_TYPE_NSEC3: { - uint8_t size; - - r = dns_packet_read_uint8(p, &rr->nsec3.algorithm, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->nsec3.flags, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint16(p, &rr->nsec3.iterations, NULL); - if (r < 0) - goto fail; - - /* this may be zero */ - r = dns_packet_read_uint8(p, &size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_memdup(p, size, &rr->nsec3.salt, &rr->nsec3.salt_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &size, NULL); - if (r < 0) - goto fail; - - if (size <= 0) { - r = -EBADMSG; - goto fail; - } - - r = dns_packet_read_memdup(p, size, &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_type_windows(p, &rr->nsec3.types, offset + rdlength - p->rindex, NULL); - if (r < 0) - goto fail; - - /* empty non-terminals can have NSEC3 records, so empty bitmaps are allowed */ - - break; - } - - case DNS_TYPE_TLSA: - r = dns_packet_read_uint8(p, &rr->tlsa.cert_usage, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->tlsa.selector, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_uint8(p, &rr->tlsa.matching_type, NULL); - if (r < 0) - goto fail; - - r = dns_packet_read_memdup(p, rdlength - 3, - &rr->tlsa.data, &rr->tlsa.data_size, - NULL); - if (rr->tlsa.data_size <= 0) { - /* the accepted size depends on the algorithm, but for now - just ensure that the value is greater than zero */ - r = -EBADMSG; - goto fail; - } - - break; - - case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */ - case DNS_TYPE_OPENPGPKEY: - default: - unparseable: - r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.data_size, NULL); - if (r < 0) - goto fail; - break; - } - if (r < 0) - goto fail; - if (p->rindex != offset + rdlength) { - r = -EBADMSG; - goto fail; - } - - *ret = rr; - rr = NULL; - - if (ret_cache_flush) - *ret_cache_flush = cache_flush; - if (start) - *start = saved_rindex; - - return 0; -fail: - dns_packet_rewind(p, saved_rindex); - return r; -} - -static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) { - const uint8_t* p; - bool found_dau_dhu_n3u = false; - size_t l; - - /* Checks whether the specified OPT RR is well-formed and whether it contains RFC6975 data (which is not OK in - * a reply). */ - - assert(rr); - assert(rr->key->type == DNS_TYPE_OPT); - - /* Check that the version is 0 */ - if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0) - return false; - - p = rr->opt.data; - l = rr->opt.data_size; - while (l > 0) { - uint16_t option_code, option_length; - - /* At least four bytes for OPTION-CODE and OPTION-LENGTH are required */ - if (l < 4U) - return false; - - option_code = unaligned_read_be16(p); - option_length = unaligned_read_be16(p + 2); - - if (l < option_length + 4U) - return false; - - /* RFC 6975 DAU, DHU or N3U fields found. */ - if (IN_SET(option_code, 5, 6, 7)) - found_dau_dhu_n3u = true; - - p += option_length + 4U; - l -= option_length + 4U; - } - - *rfc6975 = found_dau_dhu_n3u; - return true; -} - -int dns_packet_extract(DnsPacket *p) { - _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - size_t saved_rindex; - unsigned n, i; - int r; - - if (p->extracted) - return 0; - - saved_rindex = p->rindex; - dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); - - n = DNS_PACKET_QDCOUNT(p); - if (n > 0) { - question = dns_question_new(n); - if (!question) { - r = -ENOMEM; - goto finish; - } - - for (i = 0; i < n; i++) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - bool cache_flush; - - r = dns_packet_read_key(p, &key, &cache_flush, NULL); - if (r < 0) - goto finish; - - if (cache_flush) { - r = -EBADMSG; - goto finish; - } - - if (!dns_type_is_valid_query(key->type)) { - r = -EBADMSG; - goto finish; - } - - r = dns_question_add(question, key); - if (r < 0) - goto finish; - } - } - - n = DNS_PACKET_RRCOUNT(p); - if (n > 0) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL; - bool bad_opt = false; - - answer = dns_answer_new(n); - if (!answer) { - r = -ENOMEM; - goto finish; - } - - for (i = 0; i < n; i++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - bool cache_flush; - - r = dns_packet_read_rr(p, &rr, &cache_flush, NULL); - if (r < 0) - goto finish; - - /* Try to reduce memory usage a bit */ - if (previous) - dns_resource_key_reduce(&rr->key, &previous->key); - - if (rr->key->type == DNS_TYPE_OPT) { - bool has_rfc6975; - - if (p->opt || bad_opt) { - /* Multiple OPT RRs? if so, let's ignore all, because there's something wrong - * with the server, and if one is valid we wouldn't know which one. */ - log_debug("Multiple OPT RRs detected, ignoring all."); - bad_opt = true; - continue; - } - - if (!dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key))) { - /* If the OPT RR is not owned by the root domain, then it is bad, let's ignore - * it. */ - log_debug("OPT RR is not owned by root domain, ignoring."); - bad_opt = true; - continue; - } - - if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) { - /* OPT RR is in the wrong section? Some Belkin routers do this. This is a hint - * the EDNS implementation is borked, like the Belkin one is, hence ignore - * it. */ - log_debug("OPT RR in wrong section, ignoring."); - bad_opt = true; - continue; - } - - if (!opt_is_good(rr, &has_rfc6975)) { - log_debug("Malformed OPT RR, ignoring."); - bad_opt = true; - 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; - } - - p->opt = dns_resource_record_ref(rr); - } else { - - /* According to RFC 4795, section 2.9. only the RRs from the Answer section shall be - * cached. Hence mark only those RRs as cacheable by default, but not the ones from the - * Additional or Authority sections. */ - - r = dns_answer_add(answer, rr, p->ifindex, - (i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) | - (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0)); - if (r < 0) - goto finish; - } - - /* Remember this RR, so that we potentically can merge it's ->key object with the next RR. Note - * that we only do this if we actually decided to keep the RR around. */ - dns_resource_record_unref(previous); - previous = dns_resource_record_ref(rr); - } - - if (bad_opt) - p->opt = dns_resource_record_unref(p->opt); - } - - p->question = question; - question = NULL; - - p->answer = answer; - answer = NULL; - - p->extracted = true; - - r = 0; - -finish: - p->rindex = saved_rindex; - return r; -} - -int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) { - int r; - - assert(p); - assert(key); - - /* Checks if the specified packet is a reply for the specified - * key and the specified key is the only one in the question - * section. */ - - if (DNS_PACKET_QR(p) != 1) - return 0; - - /* Let's unpack the packet, if that hasn't happened yet. */ - r = dns_packet_extract(p); - if (r < 0) - return r; - - if (p->question->n_keys != 1) - return 0; - - return dns_resource_key_equal(p->question->keys[0], key); -} - -static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { - [DNS_RCODE_SUCCESS] = "SUCCESS", - [DNS_RCODE_FORMERR] = "FORMERR", - [DNS_RCODE_SERVFAIL] = "SERVFAIL", - [DNS_RCODE_NXDOMAIN] = "NXDOMAIN", - [DNS_RCODE_NOTIMP] = "NOTIMP", - [DNS_RCODE_REFUSED] = "REFUSED", - [DNS_RCODE_YXDOMAIN] = "YXDOMAIN", - [DNS_RCODE_YXRRSET] = "YRRSET", - [DNS_RCODE_NXRRSET] = "NXRRSET", - [DNS_RCODE_NOTAUTH] = "NOTAUTH", - [DNS_RCODE_NOTZONE] = "NOTZONE", - [DNS_RCODE_BADVERS] = "BADVERS", - [DNS_RCODE_BADKEY] = "BADKEY", - [DNS_RCODE_BADTIME] = "BADTIME", - [DNS_RCODE_BADMODE] = "BADMODE", - [DNS_RCODE_BADNAME] = "BADNAME", - [DNS_RCODE_BADALG] = "BADALG", - [DNS_RCODE_BADTRUNC] = "BADTRUNC", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int); - -static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = { - [DNS_PROTOCOL_DNS] = "dns", - [DNS_PROTOCOL_MDNS] = "mdns", - [DNS_PROTOCOL_LLMNR] = "llmnr", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol); diff --git a/src/resolve/resolved-dns-packet.h b/src/resolve/resolved-dns-packet.h deleted file mode 100644 index 0bf34d270c..0000000000 --- a/src/resolve/resolved-dns-packet.h +++ /dev/null @@ -1,272 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . - ***/ - -#include -#include - -#include "hashmap.h" -#include "in-addr-util.h" -#include "macro.h" -#include "sparse-endian.h" - -typedef struct DnsPacketHeader DnsPacketHeader; -typedef struct DnsPacket DnsPacket; - -#include "resolved-def.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-dns-rr.h" - -typedef enum DnsProtocol { - DNS_PROTOCOL_DNS, - DNS_PROTOCOL_MDNS, - DNS_PROTOCOL_LLMNR, - _DNS_PROTOCOL_MAX, - _DNS_PROTOCOL_INVALID = -1 -} DnsProtocol; - -struct DnsPacketHeader { - uint16_t id; - be16_t flags; - be16_t qdcount; - be16_t ancount; - be16_t nscount; - be16_t arcount; -}; - -#define DNS_PACKET_HEADER_SIZE sizeof(DnsPacketHeader) -#define UDP_PACKET_HEADER_SIZE (sizeof(struct iphdr) + sizeof(struct udphdr)) - -/* The various DNS protocols deviate in how large a packet can grow, - but the TCP transport has a 16bit size field, hence that appears to - be the absolute maximum. */ -#define DNS_PACKET_SIZE_MAX 0xFFFF - -/* RFC 1035 say 512 is the maximum, for classic unicast DNS */ -#define DNS_PACKET_UNICAST_SIZE_MAX 512 - -/* With EDNS0 we can use larger packets, default to 4096, which is what is commonly used */ -#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 4096 - -#define DNS_PACKET_SIZE_START 512 - -struct DnsPacket { - int n_ref; - DnsProtocol protocol; - size_t size, allocated, rindex; - void *_data; /* don't access directly, use DNS_PACKET_DATA()! */ - Hashmap *names; /* For name compression */ - size_t opt_start, opt_size; - - /* Parsed data */ - DnsQuestion *question; - DnsAnswer *answer; - DnsResourceRecord *opt; - - /* Packet reception metadata */ - int ifindex; - int family, ipproto; - union in_addr_union sender, destination; - uint16_t sender_port, destination_port; - uint32_t ttl; - - /* For support of truncated packets */ - DnsPacket *more; - - bool on_stack:1; - bool extracted:1; - bool refuse_compression:1; - bool canonical_form:1; -}; - -static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { - if (_unlikely_(!p)) - return NULL; - - if (p->_data) - return p->_data; - - return ((uint8_t*) p) + ALIGN(sizeof(DnsPacket)); -} - -#define DNS_PACKET_HEADER(p) ((DnsPacketHeader*) DNS_PACKET_DATA(p)) -#define DNS_PACKET_ID(p) DNS_PACKET_HEADER(p)->id -#define DNS_PACKET_QR(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 15) & 1) -#define DNS_PACKET_OPCODE(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 11) & 15) -#define DNS_PACKET_AA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 10) & 1) -#define DNS_PACKET_TC(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 9) & 1) -#define DNS_PACKET_RD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 8) & 1) -#define DNS_PACKET_RA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 7) & 1) -#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) - -static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) { - uint16_t rcode; - - if (p->opt) - rcode = (uint16_t) (p->opt->ttl >> 24); - else - rcode = 0; - - return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 15); -} - -/* LLMNR defines some bits differently */ -#define DNS_PACKET_LLMNR_C(p) DNS_PACKET_AA(p) -#define DNS_PACKET_LLMNR_T(p) DNS_PACKET_RD(p) - -#define DNS_PACKET_QDCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->qdcount) -#define DNS_PACKET_ANCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->ancount) -#define DNS_PACKET_NSCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->nscount) -#define DNS_PACKET_ARCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->arcount) - -#define DNS_PACKET_MAKE_FLAGS(qr, opcode, aa, tc, rd, ra, ad, cd, rcode) \ - (((uint16_t) !!(qr) << 15) | \ - ((uint16_t) ((opcode) & 15) << 11) | \ - ((uint16_t) !!(aa) << 10) | /* on LLMNR: c */ \ - ((uint16_t) !!(tc) << 9) | \ - ((uint16_t) !!(rd) << 8) | /* on LLMNR: t */ \ - ((uint16_t) !!(ra) << 7) | \ - ((uint16_t) !!(ad) << 5) | \ - ((uint16_t) !!(cd) << 4) | \ - ((uint16_t) ((rcode) & 15))) - -static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) { - return - (unsigned) DNS_PACKET_ANCOUNT(p) + - (unsigned) DNS_PACKET_NSCOUNT(p) + - (unsigned) DNS_PACKET_ARCOUNT(p); -} - -int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t mtu); -int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled); - -void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated); - -DnsPacket *dns_packet_ref(DnsPacket *p); -DnsPacket *dns_packet_unref(DnsPacket *p); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsPacket*, dns_packet_unref); - -int dns_packet_validate(DnsPacket *p); -int dns_packet_validate_reply(DnsPacket *p); -int dns_packet_validate_query(DnsPacket *p); - -int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key); - -int dns_packet_append_blob(DnsPacket *p, const void *d, size_t sz, size_t *start); -int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start); -int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start); -int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start); -int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start); -int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start); -int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonical_candidate, size_t *start); -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); - -void dns_packet_truncate(DnsPacket *p, size_t sz); -int dns_packet_truncate_opt(DnsPacket *p); - -int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start); -int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start); -int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start); -int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start); -int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start); -int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start); -int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start); -int dns_packet_read_name(DnsPacket *p, char **ret, bool allow_compression, size_t *start); -int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start); -int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start); - -void dns_packet_rewind(DnsPacket *p, size_t idx); - -int dns_packet_skip_question(DnsPacket *p); -int dns_packet_extract(DnsPacket *p); - -static inline bool DNS_PACKET_SHALL_CACHE(DnsPacket *p) { - /* Never cache data originating from localhost, under the - * assumption, that it's coming from a locally DNS forwarder - * or server, that is caching on its own. */ - - return in_addr_is_localhost(p->family, &p->sender) == 0; -} - -/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */ -enum { - DNS_RCODE_SUCCESS = 0, - DNS_RCODE_FORMERR = 1, - DNS_RCODE_SERVFAIL = 2, - DNS_RCODE_NXDOMAIN = 3, - DNS_RCODE_NOTIMP = 4, - DNS_RCODE_REFUSED = 5, - DNS_RCODE_YXDOMAIN = 6, - DNS_RCODE_YXRRSET = 7, - DNS_RCODE_NXRRSET = 8, - DNS_RCODE_NOTAUTH = 9, - DNS_RCODE_NOTZONE = 10, - DNS_RCODE_BADVERS = 16, - DNS_RCODE_BADSIG = 16, /* duplicate value! */ - DNS_RCODE_BADKEY = 17, - DNS_RCODE_BADTIME = 18, - DNS_RCODE_BADMODE = 19, - DNS_RCODE_BADNAME = 20, - DNS_RCODE_BADALG = 21, - DNS_RCODE_BADTRUNC = 22, - _DNS_RCODE_MAX_DEFINED -}; - -const char* dns_rcode_to_string(int i) _const_; -int dns_rcode_from_string(const char *s) _pure_; - -const char* dns_protocol_to_string(DnsProtocol p) _const_; -DnsProtocol dns_protocol_from_string(const char *s) _pure_; - -#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) }) -#define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } }) - -#define MDNS_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 251U) }) -#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb } }) - -static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) { - uint64_t f; - - /* Converts a protocol + family into a flags field as used in queries and responses */ - - f = authenticated ? SD_RESOLVED_AUTHENTICATED : 0; - - switch (protocol) { - case DNS_PROTOCOL_DNS: - return f|SD_RESOLVED_DNS; - - case DNS_PROTOCOL_LLMNR: - return f|(family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4); - - case DNS_PROTOCOL_MDNS: - return family == AF_INET6 ? SD_RESOLVED_MDNS_IPV6 : SD_RESOLVED_MDNS_IPV4; - - default: - break; - } - - return 0; -} diff --git a/src/resolve/resolved-dns-query.c b/src/resolve/resolved-dns-query.c deleted file mode 100644 index a378b2b7f7..0000000000 --- a/src/resolve/resolved-dns-query.c +++ /dev/null @@ -1,1114 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "alloc-util.h" -#include "dns-domain.h" -#include "dns-type.h" -#include "hostname-util.h" -#include "local-addresses.h" -#include "resolved-dns-query.h" -#include "resolved-dns-synthesize.h" -#include "resolved-etc-hosts.h" -#include "string-util.h" - -/* How long to wait for the query in total */ -#define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC) - -#define CNAME_MAX 8 -#define QUERIES_MAX 2048 -#define AUXILIARY_QUERIES_MAX 64 - -static int dns_query_candidate_new(DnsQueryCandidate **ret, DnsQuery *q, DnsScope *s) { - DnsQueryCandidate *c; - - assert(ret); - assert(q); - assert(s); - - c = new0(DnsQueryCandidate, 1); - if (!c) - return -ENOMEM; - - c->query = q; - c->scope = s; - - LIST_PREPEND(candidates_by_query, q->candidates, c); - LIST_PREPEND(candidates_by_scope, s->query_candidates, c); - - *ret = c; - return 0; -} - -static void dns_query_candidate_stop(DnsQueryCandidate *c) { - DnsTransaction *t; - - assert(c); - - while ((t = set_steal_first(c->transactions))) { - set_remove(t->notify_query_candidates, c); - dns_transaction_gc(t); - } -} - -DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c) { - - if (!c) - return NULL; - - dns_query_candidate_stop(c); - - set_free(c->transactions); - dns_search_domain_unref(c->search_domain); - - if (c->query) - LIST_REMOVE(candidates_by_query, c->query->candidates, c); - - if (c->scope) - LIST_REMOVE(candidates_by_scope, c->scope->query_candidates, c); - - free(c); - - return NULL; -} - -static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { - DnsSearchDomain *next = NULL; - - assert(c); - - if (c->search_domain && c->search_domain->linked) - next = c->search_domain->domains_next; - else - next = dns_scope_get_search_domains(c->scope); - - for (;;) { - if (!next) /* We hit the end of the list */ - return 0; - - if (!next->route_only) - break; - - /* Skip over route-only domains */ - next = next->domains_next; - } - - dns_search_domain_unref(c->search_domain); - c->search_domain = dns_search_domain_ref(next); - - return 1; -} - -static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResourceKey *key) { - DnsTransaction *t; - int r; - - assert(c); - assert(key); - - t = dns_scope_find_transaction(c->scope, key, true); - if (!t) { - r = dns_transaction_new(&t, c->scope, key); - if (r < 0) - return r; - } else { - if (set_contains(c->transactions, t)) - return 0; - } - - r = set_ensure_allocated(&c->transactions, NULL); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&t->notify_query_candidates, NULL); - if (r < 0) - goto gc; - - r = set_put(t->notify_query_candidates, c); - if (r < 0) - goto gc; - - r = set_put(c->transactions, t); - if (r < 0) { - (void) set_remove(t->notify_query_candidates, c); - goto gc; - } - - return 1; - -gc: - dns_transaction_gc(t); - return r; -} - -static int dns_query_candidate_go(DnsQueryCandidate *c) { - DnsTransaction *t; - Iterator i; - int r; - unsigned n = 0; - - assert(c); - - /* Start the transactions that are not started yet */ - SET_FOREACH(t, c->transactions, i) { - if (t->state != DNS_TRANSACTION_NULL) - continue; - - r = dns_transaction_go(t); - if (r < 0) - return r; - - n++; - } - - /* If there was nothing to start, then let's proceed immediately */ - if (n == 0) - dns_query_candidate_notify(c); - - return 0; -} - -static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { - DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - DnsTransaction *t; - Iterator i; - - assert(c); - - if (c->error_code != 0) - return DNS_TRANSACTION_ERRNO; - - SET_FOREACH(t, c->transactions, i) { - - switch (t->state) { - - case DNS_TRANSACTION_NULL: - /* If there's a NULL transaction pending, then - * this means not all transactions where - * started yet, and we were called from within - * the stackframe that is supposed to start - * remaining transactions. In this case, - * simply claim the candidate is pending. */ - - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - /* If there's one transaction currently in - * VALIDATING state, then this means there's - * also one in PENDING state, hence we can - * return PENDING immediately. */ - return DNS_TRANSACTION_PENDING; - - case DNS_TRANSACTION_SUCCESS: - state = t->state; - break; - - default: - if (state != DNS_TRANSACTION_SUCCESS) - state = t->state; - - break; - } - } - - return state; -} - -static bool dns_query_candidate_is_routable(DnsQueryCandidate *c, uint16_t type) { - int family; - - assert(c); - - /* Checks whether the specified RR type matches an address family that is routable on the link(s) the scope of - * this candidate belongs to. Specifically, whether there's a routable IPv4 address on it if we query an A RR, - * or a routable IPv6 address if we query an AAAA RR. */ - - if (!c->query->suppress_unroutable_family) - return true; - - if (c->scope->protocol != DNS_PROTOCOL_DNS) - return true; - - family = dns_type_to_af(type); - if (family < 0) - return true; - - if (c->scope->link) - return link_relevant(c->scope->link, family, false); - else - return manager_routable(c->scope->manager, family); -} - -static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) { - DnsQuestion *question; - DnsResourceKey *key; - int n = 0, r; - - assert(c); - - dns_query_candidate_stop(c); - - question = dns_query_question_for_protocol(c->query, c->scope->protocol); - - /* Create one transaction per question key */ - DNS_QUESTION_FOREACH(key, question) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *new_key = NULL; - DnsResourceKey *qkey; - - if (!dns_query_candidate_is_routable(c, key->type)) - continue; - - if (c->search_domain) { - r = dns_resource_key_new_append_suffix(&new_key, key, c->search_domain->name); - if (r < 0) - goto fail; - - qkey = new_key; - } else - qkey = key; - - if (!dns_scope_good_key(c->scope, qkey)) - continue; - - r = dns_query_candidate_add_transaction(c, qkey); - if (r < 0) - goto fail; - - n++; - } - - return n; - -fail: - dns_query_candidate_stop(c); - return r; -} - -void dns_query_candidate_notify(DnsQueryCandidate *c) { - DnsTransactionState state; - int r; - - assert(c); - - state = dns_query_candidate_state(c); - - if (DNS_TRANSACTION_IS_LIVE(state)) - return; - - if (state != DNS_TRANSACTION_SUCCESS && c->search_domain) { - - r = dns_query_candidate_next_search_domain(c); - if (r < 0) - goto fail; - - if (r > 0) { - /* OK, there's another search domain to try, let's do so. */ - - r = dns_query_candidate_setup_transactions(c); - if (r < 0) - goto fail; - - if (r > 0) { - /* New transactions where queued. Start them and wait */ - - r = dns_query_candidate_go(c); - if (r < 0) - goto fail; - - return; - } - } - - } - - dns_query_ready(c->query); - return; - -fail: - log_warning_errno(r, "Failed to follow search domains: %m"); - c->error_code = r; - dns_query_ready(c->query); -} - -static void dns_query_stop(DnsQuery *q) { - DnsQueryCandidate *c; - - assert(q); - - q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); - - LIST_FOREACH(candidates_by_query, c, q->candidates) - dns_query_candidate_stop(c); -} - -static void dns_query_free_candidates(DnsQuery *q) { - assert(q); - - while (q->candidates) - dns_query_candidate_free(q->candidates); -} - -static void dns_query_reset_answer(DnsQuery *q) { - assert(q); - - q->answer = dns_answer_unref(q->answer); - q->answer_rcode = 0; - q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - q->answer_errno = 0; - q->answer_authenticated = false; - q->answer_protocol = _DNS_PROTOCOL_INVALID; - q->answer_family = AF_UNSPEC; - q->answer_search_domain = dns_search_domain_unref(q->answer_search_domain); -} - -DnsQuery *dns_query_free(DnsQuery *q) { - if (!q) - return NULL; - - while (q->auxiliary_queries) - dns_query_free(q->auxiliary_queries); - - if (q->auxiliary_for) { - assert(q->auxiliary_for->n_auxiliary_queries > 0); - q->auxiliary_for->n_auxiliary_queries--; - LIST_REMOVE(auxiliary_queries, q->auxiliary_for->auxiliary_queries, q); - } - - dns_query_free_candidates(q); - - dns_question_unref(q->question_idna); - dns_question_unref(q->question_utf8); - - dns_query_reset_answer(q); - - sd_bus_message_unref(q->request); - sd_bus_track_unref(q->bus_track); - - free(q->request_address_string); - - if (q->manager) { - LIST_REMOVE(queries, q->manager->dns_queries, q); - q->manager->n_dns_queries--; - } - - free(q); - - return NULL; -} - -int dns_query_new( - Manager *m, - DnsQuery **ret, - DnsQuestion *question_utf8, - DnsQuestion *question_idna, - int ifindex, uint64_t flags) { - - _cleanup_(dns_query_freep) DnsQuery *q = NULL; - DnsResourceKey *key; - bool good = false; - int r; - - assert(m); - - if (dns_question_size(question_utf8) > 0) { - r = dns_question_is_valid_for_query(question_utf8); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - good = true; - } - - /* If the IDNA and UTF8 questions are the same, merge their references */ - r = dns_question_is_equal(question_idna, question_utf8); - if (r < 0) - return r; - if (r > 0) - question_idna = question_utf8; - else { - if (dns_question_size(question_idna) > 0) { - r = dns_question_is_valid_for_query(question_idna); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - good = true; - } - } - - if (!good) /* don't allow empty queries */ - return -EINVAL; - - if (m->n_dns_queries >= QUERIES_MAX) - return -EBUSY; - - q = new0(DnsQuery, 1); - if (!q) - return -ENOMEM; - - q->question_utf8 = dns_question_ref(question_utf8); - q->question_idna = dns_question_ref(question_idna); - q->ifindex = ifindex; - q->flags = flags; - q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - q->answer_protocol = _DNS_PROTOCOL_INVALID; - q->answer_family = AF_UNSPEC; - - /* First dump UTF8 question */ - DNS_QUESTION_FOREACH(key, question_utf8) { - _cleanup_free_ char *p = NULL; - - r = dns_resource_key_to_string(key, &p); - if (r < 0) - return r; - - log_debug("Looking up RR for %s.", strstrip(p)); - } - - /* And then dump the IDNA question, but only what hasn't been dumped already through the UTF8 question. */ - DNS_QUESTION_FOREACH(key, question_idna) { - _cleanup_free_ char *p = NULL; - - r = dns_question_contains(question_utf8, key); - if (r < 0) - return r; - if (r > 0) - continue; - - r = dns_resource_key_to_string(key, &p); - if (r < 0) - return r; - - log_debug("Looking up IDNA RR for %s.", strstrip(p)); - } - - LIST_PREPEND(queries, m->dns_queries, q); - m->n_dns_queries++; - q->manager = m; - - if (ret) - *ret = q; - q = NULL; - - return 0; -} - -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 - * nothing else is auxiliary to it either */ - assert(!q->auxiliary_for); - assert(!q->auxiliary_queries); - - /* Ensure that the unit we shall be made auxiliary for isn't - * auxiliary itself */ - assert(!auxiliary_for->auxiliary_for); - - if (auxiliary_for->n_auxiliary_queries >= AUXILIARY_QUERIES_MAX) - return -EAGAIN; - - LIST_PREPEND(auxiliary_queries, auxiliary_for->auxiliary_queries, q); - q->auxiliary_for = auxiliary_for; - - auxiliary_for->n_auxiliary_queries++; - return 0; -} - -static void dns_query_complete(DnsQuery *q, DnsTransactionState state) { - assert(q); - assert(!DNS_TRANSACTION_IS_LIVE(state)); - assert(DNS_TRANSACTION_IS_LIVE(q->state)); - - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function. */ - - q->state = state; - - dns_query_stop(q); - if (q->complete) - q->complete(q); -} - -static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { - DnsQuery *q = userdata; - - assert(s); - assert(q); - - dns_query_complete(q, DNS_TRANSACTION_TIMEOUT); - return 0; -} - -static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) { - DnsQueryCandidate *c; - int r; - - assert(q); - assert(s); - - r = dns_query_candidate_new(&c, q, s); - if (r < 0) - return r; - - /* If this a single-label domain on DNS, we might append a suitable search domain first. */ - if ((q->flags & SD_RESOLVED_NO_SEARCH) == 0) { - r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question_idna)); - if (r < 0) - goto fail; - if (r > 0) { - /* OK, we need a search domain now. Let's find one for this scope */ - - r = dns_query_candidate_next_search_domain(c); - if (r <= 0) /* if there's no search domain, then we won't add any transaction. */ - goto fail; - } - } - - r = dns_query_candidate_setup_transactions(c); - if (r < 0) - goto fail; - - return 0; - -fail: - dns_query_candidate_free(c); - return r; -} - -static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - int r; - - assert(q); - assert(state); - - /* Tries to synthesize localhost RR replies (and others) where appropriate. Note that this is done *after* the - * the normal lookup finished. The data from the network hence takes precedence over the data we - * synthesize. (But note that many scopes refuse to resolve certain domain names) */ - - if (!IN_SET(*state, - DNS_TRANSACTION_RCODE_FAILURE, - DNS_TRANSACTION_NO_SERVERS, - DNS_TRANSACTION_TIMEOUT, - DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, - DNS_TRANSACTION_NETWORK_DOWN, - DNS_TRANSACTION_NOT_FOUND)) - return 0; - - r = dns_synthesize_answer( - q->manager, - q->question_utf8, - q->ifindex, - &answer); - - if (r <= 0) - return r; - - dns_query_reset_answer(q); - - q->answer = answer; - answer = NULL; - q->answer_rcode = DNS_RCODE_SUCCESS; - q->answer_protocol = dns_synthesize_protocol(q->flags); - q->answer_family = dns_synthesize_family(q->flags); - q->answer_authenticated = true; - - *state = DNS_TRANSACTION_SUCCESS; - - return 1; -} - -static int dns_query_try_etc_hosts(DnsQuery *q) { - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - int r; - - assert(q); - - /* Looks in /etc/hosts for matching entries. Note that this is done *before* the normal lookup is done. The - * data from /etc/hosts hence takes precedence over the network. */ - - r = manager_etc_hosts_lookup( - q->manager, - q->question_utf8, - &answer); - if (r <= 0) - return r; - - dns_query_reset_answer(q); - - q->answer = answer; - answer = NULL; - q->answer_rcode = DNS_RCODE_SUCCESS; - q->answer_protocol = dns_synthesize_protocol(q->flags); - q->answer_family = dns_synthesize_family(q->flags); - q->answer_authenticated = true; - - return 1; -} - -int dns_query_go(DnsQuery *q) { - DnsScopeMatch found = DNS_SCOPE_NO; - DnsScope *s, *first = NULL; - DnsQueryCandidate *c; - int r; - - assert(q); - - if (q->state != DNS_TRANSACTION_NULL) - return 0; - - r = dns_query_try_etc_hosts(q); - if (r < 0) - return r; - if (r > 0) { - dns_query_complete(q, DNS_TRANSACTION_SUCCESS); - return 1; - } - - LIST_FOREACH(scopes, s, q->manager->dns_scopes) { - DnsScopeMatch match; - const char *name; - - name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); - if (!name) - continue; - - match = dns_scope_good_domain(s, q->ifindex, q->flags, name); - if (match < 0) - return match; - - if (match == DNS_SCOPE_NO) - continue; - - found = match; - - if (match == DNS_SCOPE_YES) { - first = s; - break; - } else { - assert(match == DNS_SCOPE_MAYBE); - - if (!first) - first = s; - } - } - - if (found == DNS_SCOPE_NO) { - DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - - r = dns_query_synthesize_reply(q, &state); - if (r < 0) - return r; - - dns_query_complete(q, state); - return 1; - } - - r = dns_query_add_candidate(q, first); - if (r < 0) - goto fail; - - LIST_FOREACH(scopes, s, first->scopes_next) { - DnsScopeMatch match; - const char *name; - - name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); - if (!name) - continue; - - match = dns_scope_good_domain(s, q->ifindex, q->flags, name); - if (match < 0) - goto fail; - - if (match != found) - continue; - - r = dns_query_add_candidate(q, s); - if (r < 0) - goto fail; - } - - dns_query_reset_answer(q); - - r = sd_event_add_time( - q->manager->event, - &q->timeout_event_source, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + QUERY_TIMEOUT_USEC, 0, - on_query_timeout, q); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(q->timeout_event_source, "query-timeout"); - - q->state = DNS_TRANSACTION_PENDING; - q->block_ready++; - - /* Start the transactions */ - LIST_FOREACH(candidates_by_query, c, q->candidates) { - r = dns_query_candidate_go(c); - if (r < 0) { - q->block_ready--; - goto fail; - } - } - - q->block_ready--; - dns_query_ready(q); - - return 1; - -fail: - dns_query_stop(q); - return r; -} - -static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { - DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; - bool has_authenticated = false, has_non_authenticated = false; - DnssecResult dnssec_result_authenticated = _DNSSEC_RESULT_INVALID, dnssec_result_non_authenticated = _DNSSEC_RESULT_INVALID; - DnsTransaction *t; - Iterator i; - int r; - - assert(q); - - if (!c) { - r = dns_query_synthesize_reply(q, &state); - if (r < 0) - goto fail; - - dns_query_complete(q, state); - return; - } - - if (c->error_code != 0) { - /* If the candidate had an error condition of its own, start with that. */ - state = DNS_TRANSACTION_ERRNO; - q->answer = dns_answer_unref(q->answer); - q->answer_rcode = 0; - q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - q->answer_errno = c->error_code; - } - - SET_FOREACH(t, c->transactions, i) { - - switch (t->state) { - - case DNS_TRANSACTION_SUCCESS: { - /* We found a successfuly reply, merge it into the answer */ - r = dns_answer_extend(&q->answer, t->answer); - if (r < 0) - goto fail; - - q->answer_rcode = t->answer_rcode; - q->answer_errno = 0; - - if (t->answer_authenticated) { - has_authenticated = true; - dnssec_result_authenticated = t->answer_dnssec_result; - } else { - has_non_authenticated = true; - dnssec_result_non_authenticated = t->answer_dnssec_result; - } - - state = DNS_TRANSACTION_SUCCESS; - break; - } - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - case DNS_TRANSACTION_ABORTED: - /* Ignore transactions that didn't complete */ - continue; - - default: - /* Any kind of failure? Store the data away, - * if there's nothing stored yet. */ - - if (state == DNS_TRANSACTION_SUCCESS) - continue; - - q->answer = dns_answer_unref(q->answer); - q->answer_rcode = t->answer_rcode; - q->answer_dnssec_result = t->answer_dnssec_result; - q->answer_errno = t->answer_errno; - - state = t->state; - break; - } - } - - if (state == DNS_TRANSACTION_SUCCESS) { - q->answer_authenticated = has_authenticated && !has_non_authenticated; - q->answer_dnssec_result = q->answer_authenticated ? dnssec_result_authenticated : dnssec_result_non_authenticated; - } - - q->answer_protocol = c->scope->protocol; - q->answer_family = c->scope->family; - - dns_search_domain_unref(q->answer_search_domain); - q->answer_search_domain = dns_search_domain_ref(c->search_domain); - - r = dns_query_synthesize_reply(q, &state); - if (r < 0) - goto fail; - - dns_query_complete(q, state); - return; - -fail: - q->answer_errno = -r; - dns_query_complete(q, DNS_TRANSACTION_ERRNO); -} - -void dns_query_ready(DnsQuery *q) { - - DnsQueryCandidate *bad = NULL, *c; - bool pending = false; - - assert(q); - assert(DNS_TRANSACTION_IS_LIVE(q->state)); - - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function, unless the block_ready - * counter was explicitly bumped before doing so. */ - - if (q->block_ready > 0) - return; - - LIST_FOREACH(candidates_by_query, c, q->candidates) { - DnsTransactionState state; - - state = dns_query_candidate_state(c); - switch (state) { - - case DNS_TRANSACTION_SUCCESS: - /* One of the candidates is successful, - * let's use it, and copy its data out */ - dns_query_accept(q, c); - return; - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - /* One of the candidates is still going on, - * let's maybe wait for it */ - pending = true; - break; - - default: - /* Any kind of failure */ - bad = c; - break; - } - } - - if (pending) - return; - - dns_query_accept(q, bad); -} - -static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) { - _cleanup_(dns_question_unrefp) DnsQuestion *nq_idna = NULL, *nq_utf8 = NULL; - int r, k; - - assert(q); - - q->n_cname_redirects ++; - if (q->n_cname_redirects > CNAME_MAX) - return -ELOOP; - - r = dns_question_cname_redirect(q->question_idna, cname, &nq_idna); - if (r < 0) - return r; - else if (r > 0) - log_debug("Following CNAME/DNAME %s → %s.", dns_question_first_name(q->question_idna), dns_question_first_name(nq_idna)); - - k = dns_question_is_equal(q->question_idna, q->question_utf8); - if (k < 0) - return r; - if (k > 0) { - /* Same question? Shortcut new question generation */ - nq_utf8 = dns_question_ref(nq_idna); - k = r; - } else { - k = dns_question_cname_redirect(q->question_utf8, cname, &nq_utf8); - if (k < 0) - return k; - else if (k > 0) - log_debug("Following UTF8 CNAME/DNAME %s → %s.", dns_question_first_name(q->question_utf8), dns_question_first_name(nq_utf8)); - } - - if (r == 0 && k == 0) /* No actual cname happened? */ - return -ELOOP; - - if (q->answer_protocol == DNS_PROTOCOL_DNS) { - /* Don't permit CNAME redirects from unicast DNS to LLMNR or MulticastDNS, so that global resources - * cannot invade the local namespace. The opposite way we permit: local names may redirect to global - * ones. */ - - q->flags &= ~(SD_RESOLVED_LLMNR|SD_RESOLVED_MDNS); /* mask away the local protocols */ - } - - /* Turn off searching for the new name */ - q->flags |= SD_RESOLVED_NO_SEARCH; - - dns_question_unref(q->question_idna); - q->question_idna = nq_idna; - nq_idna = NULL; - - dns_question_unref(q->question_utf8); - q->question_utf8 = nq_utf8; - nq_utf8 = NULL; - - dns_query_free_candidates(q); - dns_query_reset_answer(q); - - q->state = DNS_TRANSACTION_NULL; - - return 0; -} - -int dns_query_process_cname(DnsQuery *q) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL; - DnsQuestion *question; - DnsResourceRecord *rr; - int r; - - assert(q); - - if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL)) - return DNS_QUERY_NOMATCH; - - question = dns_query_question_for_protocol(q, q->answer_protocol); - - DNS_ANSWER_FOREACH(rr, q->answer) { - r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - return r; - if (r > 0) - return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */ - - r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); - if (r < 0) - return r; - if (r > 0 && !cname) - cname = dns_resource_record_ref(rr); - } - - if (!cname) - return DNS_QUERY_NOMATCH; /* No match and no cname to follow */ - - if (q->flags & SD_RESOLVED_NO_CNAME) - return -ELOOP; - - /* OK, let's actually follow the CNAME */ - r = dns_query_cname_redirect(q, cname); - if (r < 0) - return r; - - /* Let's see if the answer can already answer the new - * redirected question */ - r = dns_query_process_cname(q); - if (r != DNS_QUERY_NOMATCH) - return r; - - /* OK, it cannot, let's begin with the new query */ - r = dns_query_go(q); - if (r < 0) - return r; - - return DNS_QUERY_RESTARTED; /* We restarted the query for a new cname */ -} - -static int on_bus_track(sd_bus_track *t, void *userdata) { - DnsQuery *q = userdata; - - assert(t); - assert(q); - - log_debug("Client of active query vanished, aborting query."); - dns_query_complete(q, DNS_TRANSACTION_ABORTED); - return 0; -} - -int dns_query_bus_track(DnsQuery *q, sd_bus_message *m) { - int r; - - assert(q); - assert(m); - - if (!q->bus_track) { - r = sd_bus_track_new(sd_bus_message_get_bus(m), &q->bus_track, on_bus_track, q); - if (r < 0) - return r; - } - - r = sd_bus_track_add_sender(q->bus_track, m); - if (r < 0) - return r; - - return 0; -} - -DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol) { - assert(q); - - switch (protocol) { - - case DNS_PROTOCOL_DNS: - return q->question_idna; - - case DNS_PROTOCOL_MDNS: - case DNS_PROTOCOL_LLMNR: - return q->question_utf8; - - default: - return NULL; - } -} - -const char *dns_query_string(DnsQuery *q) { - const char *name; - int r; - - /* Returns a somewhat useful human-readable lookup key string for this query */ - - if (q->request_address_string) - return q->request_address_string; - - if (q->request_address_valid) { - r = in_addr_to_string(q->request_family, &q->request_address, &q->request_address_string); - if (r >= 0) - return q->request_address_string; - } - - name = dns_question_first_name(q->question_utf8); - if (name) - return name; - - return dns_question_first_name(q->question_idna); -} diff --git a/src/resolve/resolved-dns-query.h b/src/resolve/resolved-dns-query.h deleted file mode 100644 index c2ac02f68b..0000000000 --- a/src/resolve/resolved-dns-query.h +++ /dev/null @@ -1,133 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - - -#include "sd-bus.h" - -#include "set.h" - -typedef struct DnsQueryCandidate DnsQueryCandidate; -typedef struct DnsQuery DnsQuery; - -#include "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-dns-stream.h" -#include "resolved-dns-search-domain.h" - -struct DnsQueryCandidate { - DnsQuery *query; - DnsScope *scope; - - DnsSearchDomain *search_domain; - - int error_code; - Set *transactions; - - LIST_FIELDS(DnsQueryCandidate, candidates_by_query); - LIST_FIELDS(DnsQueryCandidate, candidates_by_scope); -}; - -struct DnsQuery { - Manager *manager; - - /* When resolving a service, we first create a TXT+SRV query, - * and then for the hostnames we discover auxiliary A+AAAA - * queries. This pointer always points from the auxiliary - * queries back to the TXT+SRV query. */ - DnsQuery *auxiliary_for; - LIST_HEAD(DnsQuery, auxiliary_queries); - unsigned n_auxiliary_queries; - int auxiliary_result; - - /* The question, formatted in IDNA for use on classic DNS, and as UTF8 for use in LLMNR or mDNS. Note that even - * on classic DNS some labels might use UTF8 encoding. Specifically, DNS-SD service names (in contrast to their - * domain suffixes) use UTF-8 encoding even on DNS. Thus, the difference between these two fields is mostly - * relevant only for explicit *hostname* lookups as well as the domain suffixes of service lookups. */ - DnsQuestion *question_idna; - DnsQuestion *question_utf8; - - uint64_t flags; - int ifindex; - - /* If true, A or AAAA RR lookups will be suppressed on links with no routable address of the matching address - * family */ - bool suppress_unroutable_family; - - DnsTransactionState state; - unsigned n_cname_redirects; - - LIST_HEAD(DnsQueryCandidate, candidates); - sd_event_source *timeout_event_source; - - /* Discovered data */ - DnsAnswer *answer; - int answer_rcode; - DnssecResult answer_dnssec_result; - bool answer_authenticated; - DnsProtocol answer_protocol; - int answer_family; - DnsSearchDomain *answer_search_domain; - int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ - - /* Bus client information */ - sd_bus_message *request; - int request_family; - bool request_address_valid; - union in_addr_union request_address; - unsigned block_all_complete; - char *request_address_string; - - /* Completion callback */ - void (*complete)(DnsQuery* q); - unsigned block_ready; - - sd_bus_track *bus_track; - - LIST_FIELDS(DnsQuery, queries); - LIST_FIELDS(DnsQuery, auxiliary_queries); -}; - -enum { - DNS_QUERY_MATCH, - DNS_QUERY_NOMATCH, - DNS_QUERY_RESTARTED, -}; - -DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c); -void dns_query_candidate_notify(DnsQueryCandidate *c); - -int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags); -DnsQuery *dns_query_free(DnsQuery *q); - -int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for); - -int dns_query_go(DnsQuery *q); -void dns_query_ready(DnsQuery *q); - -int dns_query_process_cname(DnsQuery *q); - -int dns_query_bus_track(DnsQuery *q, sd_bus_message *m); - -DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol); - -const char *dns_query_string(DnsQuery *q); - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuery*, dns_query_free); diff --git a/src/resolve/resolved-dns-question.c b/src/resolve/resolved-dns-question.c deleted file mode 100644 index 8e452e79a4..0000000000 --- a/src/resolve/resolved-dns-question.c +++ /dev/null @@ -1,468 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "alloc-util.h" -#include "dns-domain.h" -#include "dns-type.h" -#include "resolved-dns-question.h" - -DnsQuestion *dns_question_new(unsigned n) { - DnsQuestion *q; - - assert(n > 0); - - q = malloc0(offsetof(DnsQuestion, keys) + sizeof(DnsResourceKey*) * n); - if (!q) - return NULL; - - q->n_ref = 1; - q->n_allocated = n; - - return q; -} - -DnsQuestion *dns_question_ref(DnsQuestion *q) { - if (!q) - return NULL; - - assert(q->n_ref > 0); - q->n_ref++; - return q; -} - -DnsQuestion *dns_question_unref(DnsQuestion *q) { - if (!q) - return NULL; - - assert(q->n_ref > 0); - - if (q->n_ref == 1) { - unsigned i; - - for (i = 0; i < q->n_keys; i++) - dns_resource_key_unref(q->keys[i]); - free(q); - } else - q->n_ref--; - - return NULL; -} - -int dns_question_add(DnsQuestion *q, DnsResourceKey *key) { - unsigned i; - int r; - - assert(key); - - if (!q) - return -ENOSPC; - - for (i = 0; i < q->n_keys; i++) { - r = dns_resource_key_equal(q->keys[i], key); - if (r < 0) - return r; - if (r > 0) - return 0; - } - - if (q->n_keys >= q->n_allocated) - return -ENOSPC; - - q->keys[q->n_keys++] = dns_resource_key_ref(key); - return 0; -} - -int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { - unsigned i; - int r; - - assert(rr); - - if (!q) - return 0; - - for (i = 0; i < q->n_keys; i++) { - r = dns_resource_key_match_rr(q->keys[i], rr, search_domain); - if (r != 0) - return r; - } - - return 0; -} - -int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { - unsigned i; - int r; - - assert(rr); - - if (!q) - return 0; - - if (!IN_SET(rr->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)) - return 0; - - for (i = 0; i < q->n_keys; i++) { - /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ - if (!dns_type_may_redirect(q->keys[i]->type)) - return 0; - - r = dns_resource_key_match_cname_or_dname(q->keys[i], rr->key, search_domain); - if (r != 0) - return r; - } - - return 0; -} - -int dns_question_is_valid_for_query(DnsQuestion *q) { - const char *name; - unsigned i; - int r; - - if (!q) - return 0; - - if (q->n_keys <= 0) - return 0; - - if (q->n_keys > 65535) - return 0; - - name = DNS_RESOURCE_KEY_NAME(q->keys[0]); - if (!name) - return 0; - - /* Check that all keys in this question bear the same name */ - for (i = 0; i < q->n_keys; i++) { - assert(q->keys[i]); - - if (i > 0) { - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), name); - if (r <= 0) - return r; - } - - if (!dns_type_is_valid_query(q->keys[i]->type)) - return 0; - } - - return 1; -} - -int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k) { - unsigned j; - int r; - - assert(k); - - if (!a) - return 0; - - for (j = 0; j < a->n_keys; j++) { - r = dns_resource_key_equal(a->keys[j], k); - if (r != 0) - return r; - } - - return 0; -} - -int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) { - unsigned j; - int r; - - if (a == b) - return 1; - - if (!a) - return !b || b->n_keys == 0; - if (!b) - return a->n_keys == 0; - - /* Checks if all keys in a are also contained b, and vice versa */ - - for (j = 0; j < a->n_keys; j++) { - r = dns_question_contains(b, a->keys[j]); - if (r <= 0) - return r; - } - - for (j = 0; j < b->n_keys; j++) { - r = dns_question_contains(a, b->keys[j]); - if (r <= 0) - return r; - } - - return 1; -} - -int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) { - _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL; - DnsResourceKey *key; - bool same = true; - int r; - - assert(cname); - assert(ret); - assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); - - if (dns_question_size(q) <= 0) { - *ret = NULL; - return 0; - } - - DNS_QUESTION_FOREACH(key, q) { - _cleanup_free_ char *destination = NULL; - const char *d; - - if (cname->key->type == DNS_TYPE_CNAME) - d = cname->cname.name; - else { - r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination); - if (r < 0) - return r; - if (r == 0) - continue; - - d = destination; - } - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), d); - if (r < 0) - return r; - - if (r == 0) { - same = false; - break; - } - } - - /* Fully the same, indicate we didn't do a thing */ - if (same) { - *ret = NULL; - return 0; - } - - n = dns_question_new(q->n_keys); - if (!n) - return -ENOMEM; - - /* Create a new question, and patch in the new name */ - DNS_QUESTION_FOREACH(key, q) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; - - k = dns_resource_key_new_redirect(key, cname); - if (!k) - return -ENOMEM; - - r = dns_question_add(n, k); - if (r < 0) - return r; - } - - *ret = n; - n = NULL; - - return 1; -} - -const char *dns_question_first_name(DnsQuestion *q) { - - if (!q) - return NULL; - - if (q->n_keys < 1) - return NULL; - - return DNS_RESOURCE_KEY_NAME(q->keys[0]); -} - -int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna) { - _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; - _cleanup_free_ char *buf = NULL; - int r; - - assert(ret); - assert(name); - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return -EAFNOSUPPORT; - - if (convert_idna) { - r = dns_name_apply_idna(name, &buf); - if (r < 0) - return r; - - name = buf; - } - - q = dns_question_new(family == AF_UNSPEC ? 2 : 1); - if (!q) - return -ENOMEM; - - if (family != AF_INET6) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - } - - if (family != AF_INET) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - } - - *ret = q; - q = NULL; - - return 0; -} - -int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; - _cleanup_free_ char *reverse = NULL; - int r; - - assert(ret); - assert(a); - - if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) - return -EAFNOSUPPORT; - - r = dns_name_reverse(family, a, &reverse); - if (r < 0) - return r; - - q = dns_question_new(1); - if (!q) - return -ENOMEM; - - key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse); - if (!key) - return -ENOMEM; - - reverse = NULL; - - r = dns_question_add(q, key); - if (r < 0) - return r; - - *ret = q; - q = NULL; - - return 0; -} - -int dns_question_new_service( - DnsQuestion **ret, - const char *service, - const char *type, - const char *domain, - bool with_txt, - bool convert_idna) { - - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; - _cleanup_free_ char *buf = NULL, *joined = NULL; - const char *name; - int r; - - assert(ret); - - /* We support three modes of invocation: - * - * 1. Only a domain is specified, in which case we assume a properly encoded SRV RR name, including service - * type and possibly a service name. If specified in this way we assume it's already IDNA converted if - * that's necessary. - * - * 2. Both service type and a domain specified, in which case a normal SRV RR is assumed, without a DNS-SD - * style prefix. In this case we'll IDNA convert the domain, if that's requested. - * - * 3. All three of service name, type and domain are specified, in which case a DNS-SD service is put - * together. The service name is never IDNA converted, and the domain is if requested. - * - * It's not supported to specify a service name without a type, or no domain name. - */ - - if (!domain) - return -EINVAL; - - if (type) { - if (convert_idna) { - r = dns_name_apply_idna(domain, &buf); - if (r < 0) - return r; - - domain = buf; - } - - r = dns_service_join(service, type, domain, &joined); - if (r < 0) - return r; - - name = joined; - } else { - if (service) - return -EINVAL; - - name = domain; - } - - q = dns_question_new(1 + with_txt); - if (!q) - return -ENOMEM; - - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_SRV, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - - if (with_txt) { - dns_resource_key_unref(key); - key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_TXT, name); - if (!key) - return -ENOMEM; - - r = dns_question_add(q, key); - if (r < 0) - return r; - } - - *ret = q; - q = NULL; - - return 0; -} diff --git a/src/resolve/resolved-dns-question.h b/src/resolve/resolved-dns-question.h deleted file mode 100644 index ea41478975..0000000000 --- a/src/resolve/resolved-dns-question.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -typedef struct DnsQuestion DnsQuestion; - -#include "macro.h" -#include "resolved-dns-rr.h" - -/* A simple array of resource keys */ - -struct DnsQuestion { - unsigned n_ref; - unsigned n_keys, n_allocated; - DnsResourceKey* keys[0]; -}; - -DnsQuestion *dns_question_new(unsigned n); -DnsQuestion *dns_question_ref(DnsQuestion *q); -DnsQuestion *dns_question_unref(DnsQuestion *q); - -int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna); -int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a); -int dns_question_new_service(DnsQuestion **ret, const char *service, const char *type, const char *domain, bool with_txt, bool convert_idna); - -int dns_question_add(DnsQuestion *q, DnsResourceKey *key); - -int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain); -int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char* search_domain); -int dns_question_is_valid_for_query(DnsQuestion *q); -int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k); -int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b); - -int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret); - -const char *dns_question_first_name(DnsQuestion *q); - -static inline unsigned dns_question_size(DnsQuestion *q) { - return q ? q->n_keys : 0; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref); - -#define _DNS_QUESTION_FOREACH(u, key, q) \ - for (unsigned UNIQ_T(i, u) = ({ \ - (key) = ((q) && (q)->n_keys > 0) ? (q)->keys[0] : NULL; \ - 0; \ - }); \ - (q) && (UNIQ_T(i, u) < (q)->n_keys); \ - UNIQ_T(i, u)++, (key) = (UNIQ_T(i, u) < (q)->n_keys ? (q)->keys[UNIQ_T(i, u)] : NULL)) - -#define DNS_QUESTION_FOREACH(key, q) _DNS_QUESTION_FOREACH(UNIQ, key, q) diff --git a/src/resolve/resolved-dns-rr.c b/src/resolve/resolved-dns-rr.c deleted file mode 100644 index 40f8e28dfd..0000000000 --- a/src/resolve/resolved-dns-rr.c +++ /dev/null @@ -1,1521 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "alloc-util.h" -#include "dns-domain.h" -#include "dns-type.h" -#include "hexdecoct.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-rr.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" - -DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name) { - DnsResourceKey *k; - size_t l; - - assert(name); - - l = strlen(name); - k = malloc0(sizeof(DnsResourceKey) + l + 1); - if (!k) - return NULL; - - k->n_ref = 1; - k->class = class; - k->type = type; - - strcpy((char*) k + sizeof(DnsResourceKey), name); - - return k; -} - -DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname) { - int r; - - assert(key); - assert(cname); - - assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); - - if (cname->key->type == DNS_TYPE_CNAME) - return dns_resource_key_new(key->class, key->type, cname->cname.name); - else { - DnsResourceKey *k; - char *destination = NULL; - - r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination); - if (r < 0) - return NULL; - if (r == 0) - return dns_resource_key_ref((DnsResourceKey*) key); - - k = dns_resource_key_new_consume(key->class, key->type, destination); - if (!k) { - free(destination); - return NULL; - } - - return k; - } -} - -int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name) { - DnsResourceKey *new_key; - char *joined; - int r; - - assert(ret); - assert(key); - assert(name); - - if (dns_name_is_root(name)) { - *ret = dns_resource_key_ref(key); - return 0; - } - - r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), name, &joined); - if (r < 0) - return r; - - new_key = dns_resource_key_new_consume(key->class, key->type, joined); - if (!new_key) { - free(joined); - return -ENOMEM; - } - - *ret = new_key; - return 0; -} - -DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name) { - DnsResourceKey *k; - - assert(name); - - k = new0(DnsResourceKey, 1); - if (!k) - return NULL; - - k->n_ref = 1; - k->class = class; - k->type = type; - k->_name = name; - - return k; -} - -DnsResourceKey* dns_resource_key_ref(DnsResourceKey *k) { - - if (!k) - return NULL; - - /* Static/const keys created with DNS_RESOURCE_KEY_CONST will - * set this to -1, they should not be reffed/unreffed */ - assert(k->n_ref != (unsigned) -1); - - assert(k->n_ref > 0); - k->n_ref++; - - return k; -} - -DnsResourceKey* dns_resource_key_unref(DnsResourceKey *k) { - if (!k) - return NULL; - - assert(k->n_ref != (unsigned) -1); - assert(k->n_ref > 0); - - if (k->n_ref == 1) { - free(k->_name); - free(k); - } else - k->n_ref--; - - return NULL; -} - -bool dns_resource_key_is_address(const DnsResourceKey *key) { - assert(key); - - /* Check if this is an A or AAAA resource key */ - - return key->class == DNS_CLASS_IN && IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_AAAA); -} - -int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { - int r; - - if (a == b) - return 1; - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(a), DNS_RESOURCE_KEY_NAME(b)); - if (r <= 0) - return r; - - if (a->class != b->class) - return 0; - - if (a->type != b->type) - return 0; - - return 1; -} - -int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain) { - int r; - - assert(key); - assert(rr); - - if (key == rr->key) - return 1; - - /* Checks if an rr matches the specified key. If a search - * domain is specified, it will also be checked if the key - * with the search domain suffixed might match the RR. */ - - if (rr->key->class != key->class && key->class != DNS_CLASS_ANY) - return 0; - - if (rr->key->type != key->type && key->type != DNS_TYPE_ANY) - return 0; - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); - if (r != 0) - return r; - - if (search_domain) { - _cleanup_free_ char *joined = NULL; - - r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), search_domain, &joined); - if (r < 0) - return r; - - return dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), joined); - } - - return 0; -} - -int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain) { - int r; - - assert(key); - assert(cname); - - if (cname->class != key->class && key->class != DNS_CLASS_ANY) - return 0; - - if (cname->type == DNS_TYPE_CNAME) - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname)); - else if (cname->type == DNS_TYPE_DNAME) - r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname)); - else - return 0; - - if (r != 0) - return r; - - if (search_domain) { - _cleanup_free_ char *joined = NULL; - - r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), search_domain, &joined); - if (r < 0) - return r; - - if (cname->type == DNS_TYPE_CNAME) - return dns_name_equal(joined, DNS_RESOURCE_KEY_NAME(cname)); - else if (cname->type == DNS_TYPE_DNAME) - return dns_name_endswith(joined, DNS_RESOURCE_KEY_NAME(cname)); - } - - return 0; -} - -int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa) { - assert(soa); - assert(key); - - /* Checks whether 'soa' is a SOA record for the specified key. */ - - if (soa->class != key->class) - return 0; - - if (soa->type != DNS_TYPE_SOA) - return 0; - - return dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa)); -} - -static void dns_resource_key_hash_func(const void *i, struct siphash *state) { - const DnsResourceKey *k = i; - - assert(k); - - dns_name_hash_func(DNS_RESOURCE_KEY_NAME(k), state); - siphash24_compress(&k->class, sizeof(k->class), state); - siphash24_compress(&k->type, sizeof(k->type), state); -} - -static int dns_resource_key_compare_func(const void *a, const void *b) { - const DnsResourceKey *x = a, *y = b; - int ret; - - ret = dns_name_compare_func(DNS_RESOURCE_KEY_NAME(x), DNS_RESOURCE_KEY_NAME(y)); - if (ret != 0) - return ret; - - if (x->type < y->type) - return -1; - if (x->type > y->type) - return 1; - - if (x->class < y->class) - return -1; - if (x->class > y->class) - return 1; - - return 0; -} - -const struct hash_ops dns_resource_key_hash_ops = { - .hash = dns_resource_key_hash_func, - .compare = dns_resource_key_compare_func -}; - -int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) { - char cbuf[strlen("CLASS") + DECIMAL_STR_MAX(uint16_t)], tbuf[strlen("TYPE") + DECIMAL_STR_MAX(uint16_t)]; - const char *c, *t, *n; - char *s; - - /* If we cannot convert the CLASS/TYPE into a known string, - use the format recommended by RFC 3597, Section 5. */ - - c = dns_class_to_string(key->class); - if (!c) { - sprintf(cbuf, "CLASS%u", key->class); - c = cbuf; - } - - t = dns_type_to_string(key->type); - if (!t){ - sprintf(tbuf, "TYPE%u", key->type); - t = tbuf; - } - - n = DNS_RESOURCE_KEY_NAME(key); - if (asprintf(&s, "%s%s %s %-5s", n, endswith(n, ".") ? "" : ".", c, t) < 0) - return -ENOMEM; - - *ret = s; - return 0; -} - -bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b) { - assert(a); - assert(b); - - /* Try to replace one RR key by another if they are identical, thus saving a bit of memory. Note that we do - * this only for RR keys, not for RRs themselves, as they carry a lot of additional metadata (where they come - * from, validity data, and suchlike), and cannot be replaced so easily by other RRs that have the same - * superficial data. */ - - if (!*a) - return false; - if (!*b) - return false; - - /* We refuse merging const keys */ - if ((*a)->n_ref == (unsigned) -1) - return false; - if ((*b)->n_ref == (unsigned) -1) - return false; - - /* Already the same? */ - if (*a == *b) - return true; - - /* Are they really identical? */ - if (dns_resource_key_equal(*a, *b) <= 0) - return false; - - /* Keep the one which already has more references. */ - if ((*a)->n_ref > (*b)->n_ref) { - dns_resource_key_unref(*b); - *b = dns_resource_key_ref(*a); - } else { - dns_resource_key_unref(*a); - *a = dns_resource_key_ref(*b); - } - - return true; -} - -DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) { - DnsResourceRecord *rr; - - rr = new0(DnsResourceRecord, 1); - if (!rr) - return NULL; - - rr->n_ref = 1; - rr->key = dns_resource_key_ref(key); - rr->expiry = USEC_INFINITY; - rr->n_skip_labels_signer = rr->n_skip_labels_source = (unsigned) -1; - - return rr; -} - -DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(class, type, name); - if (!key) - return NULL; - - return dns_resource_record_new(key); -} - -DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr) { - if (!rr) - return NULL; - - assert(rr->n_ref > 0); - rr->n_ref++; - - return rr; -} - -DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { - if (!rr) - return NULL; - - assert(rr->n_ref > 0); - - if (rr->n_ref > 1) { - rr->n_ref--; - return NULL; - } - - if (rr->key) { - switch(rr->key->type) { - - case DNS_TYPE_SRV: - free(rr->srv.name); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - free(rr->ptr.name); - break; - - case DNS_TYPE_HINFO: - free(rr->hinfo.cpu); - free(rr->hinfo.os); - break; - - case DNS_TYPE_TXT: - case DNS_TYPE_SPF: - dns_txt_item_free_all(rr->txt.items); - break; - - case DNS_TYPE_SOA: - free(rr->soa.mname); - free(rr->soa.rname); - break; - - case DNS_TYPE_MX: - free(rr->mx.exchange); - break; - - case DNS_TYPE_DS: - free(rr->ds.digest); - break; - - case DNS_TYPE_SSHFP: - free(rr->sshfp.fingerprint); - break; - - case DNS_TYPE_DNSKEY: - free(rr->dnskey.key); - break; - - case DNS_TYPE_RRSIG: - free(rr->rrsig.signer); - free(rr->rrsig.signature); - break; - - case DNS_TYPE_NSEC: - free(rr->nsec.next_domain_name); - bitmap_free(rr->nsec.types); - break; - - case DNS_TYPE_NSEC3: - free(rr->nsec3.next_hashed_name); - free(rr->nsec3.salt); - bitmap_free(rr->nsec3.types); - break; - - case DNS_TYPE_LOC: - case DNS_TYPE_A: - case DNS_TYPE_AAAA: - break; - - case DNS_TYPE_TLSA: - free(rr->tlsa.data); - break; - - case DNS_TYPE_OPENPGPKEY: - default: - free(rr->generic.data); - } - - free(rr->wire_format); - dns_resource_key_unref(rr->key); - } - - free(rr->to_string); - free(rr); - - return NULL; -} - -int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *hostname) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ char *ptr = NULL; - int r; - - assert(ret); - assert(address); - assert(hostname); - - r = dns_name_reverse(family, address, &ptr); - if (r < 0) - return r; - - key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, ptr); - if (!key) - return -ENOMEM; - - ptr = NULL; - - rr = dns_resource_record_new(key); - if (!rr) - return -ENOMEM; - - rr->ptr.name = strdup(hostname); - if (!rr->ptr.name) - return -ENOMEM; - - *ret = rr; - rr = NULL; - - return 0; -} - -int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name) { - DnsResourceRecord *rr; - - assert(ret); - assert(address); - assert(family); - - if (family == AF_INET) { - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, name); - if (!rr) - return -ENOMEM; - - rr->a.in_addr = address->in; - - } else if (family == AF_INET6) { - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, name); - if (!rr) - return -ENOMEM; - - rr->aaaa.in6_addr = address->in6; - } else - return -EAFNOSUPPORT; - - *ret = rr; - - return 0; -} - -#define FIELD_EQUAL(a, b, field) \ - ((a).field ## _size == (b).field ## _size && \ - memcmp((a).field, (b).field, (a).field ## _size) == 0) - -int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b) { - int r; - - assert(a); - assert(b); - - if (a == b) - return 1; - - r = dns_resource_key_equal(a->key, b->key); - if (r <= 0) - return r; - - if (a->unparseable != b->unparseable) - return 0; - - switch (a->unparseable ? _DNS_TYPE_INVALID : a->key->type) { - - case DNS_TYPE_SRV: - r = dns_name_equal(a->srv.name, b->srv.name); - if (r <= 0) - return r; - - return a->srv.priority == b->srv.priority && - a->srv.weight == b->srv.weight && - a->srv.port == b->srv.port; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - return dns_name_equal(a->ptr.name, b->ptr.name); - - case DNS_TYPE_HINFO: - return strcaseeq(a->hinfo.cpu, b->hinfo.cpu) && - strcaseeq(a->hinfo.os, b->hinfo.os); - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - return dns_txt_item_equal(a->txt.items, b->txt.items); - - case DNS_TYPE_A: - return memcmp(&a->a.in_addr, &b->a.in_addr, sizeof(struct in_addr)) == 0; - - case DNS_TYPE_AAAA: - return memcmp(&a->aaaa.in6_addr, &b->aaaa.in6_addr, sizeof(struct in6_addr)) == 0; - - case DNS_TYPE_SOA: - r = dns_name_equal(a->soa.mname, b->soa.mname); - if (r <= 0) - return r; - r = dns_name_equal(a->soa.rname, b->soa.rname); - if (r <= 0) - return r; - - return a->soa.serial == b->soa.serial && - a->soa.refresh == b->soa.refresh && - a->soa.retry == b->soa.retry && - a->soa.expire == b->soa.expire && - a->soa.minimum == b->soa.minimum; - - case DNS_TYPE_MX: - if (a->mx.priority != b->mx.priority) - return 0; - - return dns_name_equal(a->mx.exchange, b->mx.exchange); - - case DNS_TYPE_LOC: - assert(a->loc.version == b->loc.version); - - return a->loc.size == b->loc.size && - a->loc.horiz_pre == b->loc.horiz_pre && - a->loc.vert_pre == b->loc.vert_pre && - a->loc.latitude == b->loc.latitude && - a->loc.longitude == b->loc.longitude && - a->loc.altitude == b->loc.altitude; - - case DNS_TYPE_DS: - return a->ds.key_tag == b->ds.key_tag && - a->ds.algorithm == b->ds.algorithm && - a->ds.digest_type == b->ds.digest_type && - FIELD_EQUAL(a->ds, b->ds, digest); - - case DNS_TYPE_SSHFP: - return a->sshfp.algorithm == b->sshfp.algorithm && - a->sshfp.fptype == b->sshfp.fptype && - FIELD_EQUAL(a->sshfp, b->sshfp, fingerprint); - - case DNS_TYPE_DNSKEY: - return a->dnskey.flags == b->dnskey.flags && - a->dnskey.protocol == b->dnskey.protocol && - a->dnskey.algorithm == b->dnskey.algorithm && - FIELD_EQUAL(a->dnskey, b->dnskey, key); - - case DNS_TYPE_RRSIG: - /* do the fast comparisons first */ - return a->rrsig.type_covered == b->rrsig.type_covered && - a->rrsig.algorithm == b->rrsig.algorithm && - a->rrsig.labels == b->rrsig.labels && - a->rrsig.original_ttl == b->rrsig.original_ttl && - a->rrsig.expiration == b->rrsig.expiration && - a->rrsig.inception == b->rrsig.inception && - a->rrsig.key_tag == b->rrsig.key_tag && - FIELD_EQUAL(a->rrsig, b->rrsig, signature) && - dns_name_equal(a->rrsig.signer, b->rrsig.signer); - - case DNS_TYPE_NSEC: - return dns_name_equal(a->nsec.next_domain_name, b->nsec.next_domain_name) && - bitmap_equal(a->nsec.types, b->nsec.types); - - case DNS_TYPE_NSEC3: - return a->nsec3.algorithm == b->nsec3.algorithm && - a->nsec3.flags == b->nsec3.flags && - a->nsec3.iterations == b->nsec3.iterations && - FIELD_EQUAL(a->nsec3, b->nsec3, salt) && - FIELD_EQUAL(a->nsec3, b->nsec3, next_hashed_name) && - bitmap_equal(a->nsec3.types, b->nsec3.types); - - case DNS_TYPE_TLSA: - return a->tlsa.cert_usage == b->tlsa.cert_usage && - a->tlsa.selector == b->tlsa.selector && - a->tlsa.matching_type == b->tlsa.matching_type && - FIELD_EQUAL(a->tlsa, b->tlsa, data); - - default: - return FIELD_EQUAL(a->generic, b->generic, data); - } -} - -static char* format_location(uint32_t latitude, uint32_t longitude, uint32_t altitude, - uint8_t size, uint8_t horiz_pre, uint8_t vert_pre) { - char *s; - char NS = latitude >= 1U<<31 ? 'N' : 'S'; - char EW = longitude >= 1U<<31 ? 'E' : 'W'; - - int lat = latitude >= 1U<<31 ? (int) (latitude - (1U<<31)) : (int) ((1U<<31) - latitude); - int lon = longitude >= 1U<<31 ? (int) (longitude - (1U<<31)) : (int) ((1U<<31) - longitude); - double alt = altitude >= 10000000u ? altitude - 10000000u : -(double)(10000000u - altitude); - double siz = (size >> 4) * exp10((double) (size & 0xF)); - double hor = (horiz_pre >> 4) * exp10((double) (horiz_pre & 0xF)); - double ver = (vert_pre >> 4) * exp10((double) (vert_pre & 0xF)); - - if (asprintf(&s, "%d %d %.3f %c %d %d %.3f %c %.2fm %.2fm %.2fm %.2fm", - (lat / 60000 / 60), - (lat / 60000) % 60, - (lat % 60000) / 1000., - NS, - (lon / 60000 / 60), - (lon / 60000) % 60, - (lon % 60000) / 1000., - EW, - alt / 100., - siz / 100., - hor / 100., - ver / 100.) < 0) - return NULL; - - return s; -} - -static int format_timestamp_dns(char *buf, size_t l, time_t sec) { - struct tm tm; - - assert(buf); - assert(l > strlen("YYYYMMDDHHmmSS")); - - if (!gmtime_r(&sec, &tm)) - return -EINVAL; - - if (strftime(buf, l, "%Y%m%d%H%M%S", &tm) <= 0) - return -EINVAL; - - return 0; -} - -static char *format_types(Bitmap *types) { - _cleanup_strv_free_ char **strv = NULL; - _cleanup_free_ char *str = NULL; - Iterator i; - unsigned type; - int r; - - BITMAP_FOREACH(type, types, i) { - if (dns_type_to_string(type)) { - r = strv_extend(&strv, dns_type_to_string(type)); - if (r < 0) - return NULL; - } else { - char *t; - - r = asprintf(&t, "TYPE%u", type); - if (r < 0) - return NULL; - - r = strv_consume(&strv, t); - if (r < 0) - return NULL; - } - } - - str = strv_join(strv, " "); - if (!str) - return NULL; - - return strjoin("( ", str, " )", NULL); -} - -static char *format_txt(DnsTxtItem *first) { - DnsTxtItem *i; - size_t c = 1; - char *p, *s; - - LIST_FOREACH(items, i, first) - c += i->length * 4 + 3; - - p = s = new(char, c); - if (!s) - return NULL; - - LIST_FOREACH(items, i, first) { - size_t j; - - if (i != first) - *(p++) = ' '; - - *(p++) = '"'; - - for (j = 0; j < i->length; j++) { - if (i->data[j] < ' ' || i->data[j] == '"' || i->data[j] >= 127) { - *(p++) = '\\'; - *(p++) = '0' + (i->data[j] / 100); - *(p++) = '0' + ((i->data[j] / 10) % 10); - *(p++) = '0' + (i->data[j] % 10); - } else - *(p++) = i->data[j]; - } - - *(p++) = '"'; - } - - *p = 0; - return s; -} - -const char *dns_resource_record_to_string(DnsResourceRecord *rr) { - _cleanup_free_ char *k = NULL, *t = NULL; - char *s; - int r; - - assert(rr); - - if (rr->to_string) - return rr->to_string; - - r = dns_resource_key_to_string(rr->key, &k); - if (r < 0) - return NULL; - - switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - - case DNS_TYPE_SRV: - r = asprintf(&s, "%s %u %u %u %s", - k, - rr->srv.priority, - rr->srv.weight, - rr->srv.port, - strna(rr->srv.name)); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - s = strjoin(k, " ", rr->ptr.name, NULL); - if (!s) - return NULL; - - break; - - case DNS_TYPE_HINFO: - s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_SPF: /* exactly the same as TXT */ - case DNS_TYPE_TXT: - t = format_txt(rr->txt.items); - if (!t) - return NULL; - - s = strjoin(k, " ", t, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_A: { - _cleanup_free_ char *x = NULL; - - r = in_addr_to_string(AF_INET, (const union in_addr_union*) &rr->a.in_addr, &x); - if (r < 0) - return NULL; - - s = strjoin(k, " ", x, NULL); - if (!s) - return NULL; - break; - } - - case DNS_TYPE_AAAA: - r = in_addr_to_string(AF_INET6, (const union in_addr_union*) &rr->aaaa.in6_addr, &t); - if (r < 0) - return NULL; - - s = strjoin(k, " ", t, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_SOA: - r = asprintf(&s, "%s %s %s %u %u %u %u %u", - k, - strna(rr->soa.mname), - strna(rr->soa.rname), - rr->soa.serial, - rr->soa.refresh, - rr->soa.retry, - rr->soa.expire, - rr->soa.minimum); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_MX: - r = asprintf(&s, "%s %u %s", - k, - rr->mx.priority, - rr->mx.exchange); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_LOC: - assert(rr->loc.version == 0); - - t = format_location(rr->loc.latitude, - rr->loc.longitude, - rr->loc.altitude, - rr->loc.size, - rr->loc.horiz_pre, - rr->loc.vert_pre); - if (!t) - return NULL; - - s = strjoin(k, " ", t, NULL); - if (!s) - return NULL; - break; - - case DNS_TYPE_DS: - t = hexmem(rr->ds.digest, rr->ds.digest_size); - if (!t) - return NULL; - - r = asprintf(&s, "%s %u %u %u %s", - k, - rr->ds.key_tag, - rr->ds.algorithm, - rr->ds.digest_type, - t); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_SSHFP: - t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); - if (!t) - return NULL; - - r = asprintf(&s, "%s %u %u %s", - k, - rr->sshfp.algorithm, - rr->sshfp.fptype, - t); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_DNSKEY: { - _cleanup_free_ char *alg = NULL; - char *ss; - int n, n1; - uint16_t key_tag; - - key_tag = dnssec_keytag(rr, true); - - r = dnssec_algorithm_to_string_alloc(rr->dnskey.algorithm, &alg); - if (r < 0) - return NULL; - - r = asprintf(&s, "%s %n%u %u %s %n", - k, - &n1, - rr->dnskey.flags, - rr->dnskey.protocol, - alg, - &n); - if (r < 0) - return NULL; - - r = base64_append(&s, n, - rr->dnskey.key, rr->dnskey.key_size, - 8, columns()); - if (r < 0) - return NULL; - - r = asprintf(&ss, "%s\n" - "%*s-- Flags:%s%s%s\n" - "%*s-- Key tag: %u", - s, - n1, "", - rr->dnskey.flags & DNSKEY_FLAG_SEP ? " SEP" : "", - rr->dnskey.flags & DNSKEY_FLAG_REVOKE ? " REVOKE" : "", - rr->dnskey.flags & DNSKEY_FLAG_ZONE_KEY ? " ZONE_KEY" : "", - n1, "", - key_tag); - if (r < 0) - return NULL; - free(s); - s = ss; - - break; - } - - case DNS_TYPE_RRSIG: { - _cleanup_free_ char *alg = NULL; - char expiration[strlen("YYYYMMDDHHmmSS") + 1], inception[strlen("YYYYMMDDHHmmSS") + 1]; - const char *type; - int n; - - type = dns_type_to_string(rr->rrsig.type_covered); - - r = dnssec_algorithm_to_string_alloc(rr->rrsig.algorithm, &alg); - if (r < 0) - return NULL; - - r = format_timestamp_dns(expiration, sizeof(expiration), rr->rrsig.expiration); - if (r < 0) - return NULL; - - r = format_timestamp_dns(inception, sizeof(inception), rr->rrsig.inception); - if (r < 0) - return NULL; - - /* TYPE?? follows - * http://tools.ietf.org/html/rfc3597#section-5 */ - - r = asprintf(&s, "%s %s%.*u %s %u %u %s %s %u %s %n", - k, - type ?: "TYPE", - type ? 0 : 1, type ? 0u : (unsigned) rr->rrsig.type_covered, - alg, - rr->rrsig.labels, - rr->rrsig.original_ttl, - expiration, - inception, - rr->rrsig.key_tag, - rr->rrsig.signer, - &n); - if (r < 0) - return NULL; - - r = base64_append(&s, n, - rr->rrsig.signature, rr->rrsig.signature_size, - 8, columns()); - if (r < 0) - return NULL; - - break; - } - - case DNS_TYPE_NSEC: - t = format_types(rr->nsec.types); - if (!t) - return NULL; - - r = asprintf(&s, "%s %s %s", - k, - rr->nsec.next_domain_name, - t); - if (r < 0) - return NULL; - break; - - case DNS_TYPE_NSEC3: { - _cleanup_free_ char *salt = NULL, *hash = NULL; - - if (rr->nsec3.salt_size > 0) { - salt = hexmem(rr->nsec3.salt, rr->nsec3.salt_size); - if (!salt) - return NULL; - } - - hash = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); - if (!hash) - return NULL; - - t = format_types(rr->nsec3.types); - if (!t) - return NULL; - - r = asprintf(&s, "%s %"PRIu8" %"PRIu8" %"PRIu16" %s %s %s", - k, - rr->nsec3.algorithm, - rr->nsec3.flags, - rr->nsec3.iterations, - rr->nsec3.salt_size > 0 ? salt : "-", - hash, - t); - if (r < 0) - return NULL; - - break; - } - - case DNS_TYPE_TLSA: { - const char *cert_usage, *selector, *matching_type; - char *ss; - int n; - - cert_usage = tlsa_cert_usage_to_string(rr->tlsa.cert_usage); - selector = tlsa_selector_to_string(rr->tlsa.selector); - matching_type = tlsa_matching_type_to_string(rr->tlsa.matching_type); - - r = asprintf(&s, "%s %u %u %u %n", - k, - rr->tlsa.cert_usage, - rr->tlsa.selector, - rr->tlsa.matching_type, - &n); - if (r < 0) - return NULL; - - r = base64_append(&s, n, - rr->tlsa.data, rr->tlsa.data_size, - 8, columns()); - if (r < 0) - return NULL; - - r = asprintf(&ss, "%s\n" - "%*s-- Cert. usage: %s\n" - "%*s-- Selector: %s\n" - "%*s-- Matching type: %s", - s, - n - 6, "", cert_usage, - n - 6, "", selector, - n - 6, "", matching_type); - if (r < 0) - return NULL; - free(s); - s = ss; - - break; - } - - case DNS_TYPE_OPENPGPKEY: { - int n; - - r = asprintf(&s, "%s %n", - k, - &n); - if (r < 0) - return NULL; - - r = base64_append(&s, n, - rr->generic.data, rr->generic.data_size, - 8, columns()); - if (r < 0) - return NULL; - break; - } - - default: - t = hexmem(rr->generic.data, rr->generic.data_size); - if (!t) - return NULL; - - /* Format as documented in RFC 3597, Section 5 */ - r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.data_size, t); - if (r < 0) - return NULL; - break; - } - - rr->to_string = s; - return s; -} - -int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { - - DnsPacket packet = { - .n_ref = 1, - .protocol = DNS_PROTOCOL_DNS, - .on_stack = true, - .refuse_compression = true, - .canonical_form = canonical, - }; - - size_t start, rds; - int r; - - assert(rr); - - /* Generates the RR in wire-format, optionally in the - * canonical form as discussed in the DNSSEC RFC 4034, Section - * 6.2. We allocate a throw-away DnsPacket object on the stack - * here, because we need some book-keeping for memory - * management, and can reuse the DnsPacket serializer, that - * can generate the canonical form, too, but also knows label - * compression and suchlike. */ - - if (rr->wire_format && rr->wire_format_canonical == canonical) - return 0; - - r = dns_packet_append_rr(&packet, rr, &start, &rds); - if (r < 0) - return r; - - assert(start == 0); - assert(packet._data); - - free(rr->wire_format); - rr->wire_format = packet._data; - rr->wire_format_size = packet.size; - rr->wire_format_rdata_offset = rds; - rr->wire_format_canonical = canonical; - - packet._data = NULL; - dns_packet_unref(&packet); - - return 0; -} - -int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret) { - const char *n; - int r; - - assert(rr); - assert(ret); - - /* Returns the RRset's signer, if it is known. */ - - if (rr->n_skip_labels_signer == (unsigned) -1) - return -ENODATA; - - n = DNS_RESOURCE_KEY_NAME(rr->key); - r = dns_name_skip(n, rr->n_skip_labels_signer, &n); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - *ret = n; - return 0; -} - -int dns_resource_record_source(DnsResourceRecord *rr, const char **ret) { - const char *n; - int r; - - assert(rr); - assert(ret); - - /* Returns the RRset's synthesizing source, if it is known. */ - - if (rr->n_skip_labels_source == (unsigned) -1) - return -ENODATA; - - n = DNS_RESOURCE_KEY_NAME(rr->key); - r = dns_name_skip(n, rr->n_skip_labels_source, &n); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - - *ret = n; - return 0; -} - -int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) { - const char *signer; - int r; - - assert(rr); - - r = dns_resource_record_signer(rr, &signer); - if (r < 0) - return r; - - return dns_name_equal(zone, signer); -} - -int dns_resource_record_is_synthetic(DnsResourceRecord *rr) { - int r; - - assert(rr); - - /* Returns > 0 if the RR is generated from a wildcard, and is not the asterisk name itself */ - - if (rr->n_skip_labels_source == (unsigned) -1) - return -ENODATA; - - if (rr->n_skip_labels_source == 0) - return 0; - - if (rr->n_skip_labels_source > 1) - return 1; - - r = dns_name_startswith(DNS_RESOURCE_KEY_NAME(rr->key), "*"); - if (r < 0) - return r; - - return !r; -} - -static void dns_resource_record_hash_func(const void *i, struct siphash *state) { - const DnsResourceRecord *rr = i; - - assert(rr); - - dns_resource_key_hash_func(rr->key, state); - - switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { - - case DNS_TYPE_SRV: - siphash24_compress(&rr->srv.priority, sizeof(rr->srv.priority), state); - siphash24_compress(&rr->srv.weight, sizeof(rr->srv.weight), state); - siphash24_compress(&rr->srv.port, sizeof(rr->srv.port), state); - dns_name_hash_func(rr->srv.name, state); - break; - - case DNS_TYPE_PTR: - case DNS_TYPE_NS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: - dns_name_hash_func(rr->ptr.name, state); - break; - - case DNS_TYPE_HINFO: - string_hash_func(rr->hinfo.cpu, state); - string_hash_func(rr->hinfo.os, state); - break; - - case DNS_TYPE_TXT: - case DNS_TYPE_SPF: { - DnsTxtItem *j; - - LIST_FOREACH(items, j, rr->txt.items) { - siphash24_compress(j->data, j->length, state); - - /* Add an extra NUL byte, so that "a" followed by "b" doesn't result in the same hash as "ab" - * followed by "". */ - siphash24_compress_byte(0, state); - } - break; - } - - case DNS_TYPE_A: - siphash24_compress(&rr->a.in_addr, sizeof(rr->a.in_addr), state); - break; - - case DNS_TYPE_AAAA: - siphash24_compress(&rr->aaaa.in6_addr, sizeof(rr->aaaa.in6_addr), state); - break; - - case DNS_TYPE_SOA: - dns_name_hash_func(rr->soa.mname, state); - dns_name_hash_func(rr->soa.rname, state); - siphash24_compress(&rr->soa.serial, sizeof(rr->soa.serial), state); - siphash24_compress(&rr->soa.refresh, sizeof(rr->soa.refresh), state); - siphash24_compress(&rr->soa.retry, sizeof(rr->soa.retry), state); - siphash24_compress(&rr->soa.expire, sizeof(rr->soa.expire), state); - siphash24_compress(&rr->soa.minimum, sizeof(rr->soa.minimum), state); - break; - - case DNS_TYPE_MX: - siphash24_compress(&rr->mx.priority, sizeof(rr->mx.priority), state); - dns_name_hash_func(rr->mx.exchange, state); - break; - - case DNS_TYPE_LOC: - siphash24_compress(&rr->loc.version, sizeof(rr->loc.version), state); - siphash24_compress(&rr->loc.size, sizeof(rr->loc.size), state); - siphash24_compress(&rr->loc.horiz_pre, sizeof(rr->loc.horiz_pre), state); - siphash24_compress(&rr->loc.vert_pre, sizeof(rr->loc.vert_pre), state); - siphash24_compress(&rr->loc.latitude, sizeof(rr->loc.latitude), state); - siphash24_compress(&rr->loc.longitude, sizeof(rr->loc.longitude), state); - siphash24_compress(&rr->loc.altitude, sizeof(rr->loc.altitude), state); - break; - - case DNS_TYPE_SSHFP: - siphash24_compress(&rr->sshfp.algorithm, sizeof(rr->sshfp.algorithm), state); - siphash24_compress(&rr->sshfp.fptype, sizeof(rr->sshfp.fptype), state); - siphash24_compress(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, state); - break; - - case DNS_TYPE_DNSKEY: - siphash24_compress(&rr->dnskey.flags, sizeof(rr->dnskey.flags), state); - siphash24_compress(&rr->dnskey.protocol, sizeof(rr->dnskey.protocol), state); - siphash24_compress(&rr->dnskey.algorithm, sizeof(rr->dnskey.algorithm), state); - siphash24_compress(rr->dnskey.key, rr->dnskey.key_size, state); - break; - - case DNS_TYPE_RRSIG: - siphash24_compress(&rr->rrsig.type_covered, sizeof(rr->rrsig.type_covered), state); - siphash24_compress(&rr->rrsig.algorithm, sizeof(rr->rrsig.algorithm), state); - siphash24_compress(&rr->rrsig.labels, sizeof(rr->rrsig.labels), state); - siphash24_compress(&rr->rrsig.original_ttl, sizeof(rr->rrsig.original_ttl), state); - siphash24_compress(&rr->rrsig.expiration, sizeof(rr->rrsig.expiration), state); - siphash24_compress(&rr->rrsig.inception, sizeof(rr->rrsig.inception), state); - siphash24_compress(&rr->rrsig.key_tag, sizeof(rr->rrsig.key_tag), state); - dns_name_hash_func(rr->rrsig.signer, state); - siphash24_compress(rr->rrsig.signature, rr->rrsig.signature_size, state); - break; - - case DNS_TYPE_NSEC: - dns_name_hash_func(rr->nsec.next_domain_name, state); - /* FIXME: we leave out the type bitmap here. Hash - * would be better if we'd take it into account - * too. */ - break; - - case DNS_TYPE_DS: - siphash24_compress(&rr->ds.key_tag, sizeof(rr->ds.key_tag), state); - siphash24_compress(&rr->ds.algorithm, sizeof(rr->ds.algorithm), state); - siphash24_compress(&rr->ds.digest_type, sizeof(rr->ds.digest_type), state); - siphash24_compress(rr->ds.digest, rr->ds.digest_size, state); - break; - - case DNS_TYPE_NSEC3: - siphash24_compress(&rr->nsec3.algorithm, sizeof(rr->nsec3.algorithm), state); - siphash24_compress(&rr->nsec3.flags, sizeof(rr->nsec3.flags), state); - siphash24_compress(&rr->nsec3.iterations, sizeof(rr->nsec3.iterations), state); - siphash24_compress(rr->nsec3.salt, rr->nsec3.salt_size, state); - siphash24_compress(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, state); - /* FIXME: We leave the bitmaps out */ - break; - - case DNS_TYPE_TLSA: - siphash24_compress(&rr->tlsa.cert_usage, sizeof(rr->tlsa.cert_usage), state); - siphash24_compress(&rr->tlsa.selector, sizeof(rr->tlsa.selector), state); - siphash24_compress(&rr->tlsa.matching_type, sizeof(rr->tlsa.matching_type), state); - siphash24_compress(&rr->tlsa.data, rr->tlsa.data_size, state); - break; - - case DNS_TYPE_OPENPGPKEY: - default: - siphash24_compress(rr->generic.data, rr->generic.data_size, state); - break; - } -} - -static int dns_resource_record_compare_func(const void *a, const void *b) { - const DnsResourceRecord *x = a, *y = b; - int ret; - - ret = dns_resource_key_compare_func(x->key, y->key); - if (ret != 0) - return ret; - - if (dns_resource_record_equal(x, y)) - return 0; - - /* This is a bit dirty, we don't implement proper ordering, but - * the hashtable doesn't need ordering anyway, hence we don't - * care. */ - return x < y ? -1 : 1; -} - -const struct hash_ops dns_resource_record_hash_ops = { - .hash = dns_resource_record_hash_func, - .compare = dns_resource_record_compare_func, -}; - -DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) { - DnsTxtItem *n; - - if (!i) - return NULL; - - n = i->items_next; - - free(i); - return dns_txt_item_free_all(n); -} - -bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) { - - if (a == b) - return true; - - if (!a != !b) - return false; - - if (!a) - return true; - - if (a->length != b->length) - return false; - - if (memcmp(a->data, b->data, a->length) != 0) - return false; - - return dns_txt_item_equal(a->items_next, b->items_next); -} - -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", - [DNSSEC_ALGORITHM_DH] = "DH", - [DNSSEC_ALGORITHM_DSA] = "DSA", - [DNSSEC_ALGORITHM_ECC] = "ECC", - [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", - [DNSSEC_ALGORITHM_DSA_NSEC3_SHA1] = "DSA-NSEC3-SHA1", - [DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1", - [DNSSEC_ALGORITHM_RSASHA256] = "RSASHA256", - [DNSSEC_ALGORITHM_RSASHA512] = "RSASHA512", - [DNSSEC_ALGORITHM_ECC_GOST] = "ECC-GOST", - [DNSSEC_ALGORITHM_ECDSAP256SHA256] = "ECDSAP256SHA256", - [DNSSEC_ALGORITHM_ECDSAP384SHA384] = "ECDSAP384SHA384", - [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", - [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", - [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", -}; -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_algorithm, int, 255); - -static const char* const dnssec_digest_table[_DNSSEC_DIGEST_MAX_DEFINED] = { - /* Names as listed on https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ - [DNSSEC_DIGEST_SHA1] = "SHA-1", - [DNSSEC_DIGEST_SHA256] = "SHA-256", - [DNSSEC_DIGEST_GOST_R_34_11_94] = "GOST_R_34.11-94", - [DNSSEC_DIGEST_SHA384] = "SHA-384", -}; -DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_digest, int, 255); diff --git a/src/resolve/resolved-dns-rr.h b/src/resolve/resolved-dns-rr.h deleted file mode 100644 index 2e0dfbaba3..0000000000 --- a/src/resolve/resolved-dns-rr.h +++ /dev/null @@ -1,333 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . - ***/ - -#include - -#include "bitmap.h" -#include "dns-type.h" -#include "hashmap.h" -#include "in-addr-util.h" -#include "list.h" - -typedef struct DnsResourceKey DnsResourceKey; -typedef struct DnsResourceRecord DnsResourceRecord; -typedef struct DnsTxtItem DnsTxtItem; - -/* DNSKEY RR flags */ -#define DNSKEY_FLAG_SEP (UINT16_C(1) << 0) -#define DNSKEY_FLAG_REVOKE (UINT16_C(1) << 7) -#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8) - -/* mDNS RR flags */ -#define MDNS_RR_CACHE_FLUSH (UINT16_C(1) << 15) - -/* DNSSEC algorithm identifiers, see - * http://tools.ietf.org/html/rfc4034#appendix-A.1 and - * https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ -enum { - DNSSEC_ALGORITHM_RSAMD5 = 1, - DNSSEC_ALGORITHM_DH, - DNSSEC_ALGORITHM_DSA, - DNSSEC_ALGORITHM_ECC, - DNSSEC_ALGORITHM_RSASHA1, - DNSSEC_ALGORITHM_DSA_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, - DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */ - DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */ - DNSSEC_ALGORITHM_ECC_GOST = 12, /* RFC 5933 */ - DNSSEC_ALGORITHM_ECDSAP256SHA256 = 13, /* RFC 6605 */ - DNSSEC_ALGORITHM_ECDSAP384SHA384 = 14, /* RFC 6605 */ - DNSSEC_ALGORITHM_INDIRECT = 252, - DNSSEC_ALGORITHM_PRIVATEDNS, - DNSSEC_ALGORITHM_PRIVATEOID, - _DNSSEC_ALGORITHM_MAX_DEFINED -}; - -/* DNSSEC digest identifiers, see - * https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ -enum { - DNSSEC_DIGEST_SHA1 = 1, - DNSSEC_DIGEST_SHA256 = 2, /* RFC 4509 */ - DNSSEC_DIGEST_GOST_R_34_11_94 = 3, /* RFC 5933 */ - DNSSEC_DIGEST_SHA384 = 4, /* RFC 6605 */ - _DNSSEC_DIGEST_MAX_DEFINED -}; - -/* DNSSEC NSEC3 hash algorithms, see - * https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */ -enum { - NSEC3_ALGORITHM_SHA1 = 1, - _NSEC3_ALGORITHM_MAX_DEFINED -}; - -struct DnsResourceKey { - unsigned n_ref; /* (unsigned -1) for const keys, see below */ - uint16_t class, type; - char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */ -}; - -/* Creates a temporary resource key. This is only useful to quickly - * look up something, without allocating a full DnsResourceKey object - * for it. Note that it is not OK to take references to this kind of - * resource key object. */ -#define DNS_RESOURCE_KEY_CONST(c, t, n) \ - ((DnsResourceKey) { \ - .n_ref = (unsigned) -1, \ - .class = c, \ - .type = t, \ - ._name = (char*) n, \ - }) - - -struct DnsTxtItem { - size_t length; - LIST_FIELDS(DnsTxtItem, items); - uint8_t data[]; -}; - -struct DnsResourceRecord { - unsigned n_ref; - DnsResourceKey *key; - - char *to_string; - - uint32_t ttl; - usec_t expiry; /* RRSIG signature expiry */ - - /* How many labels to strip to determine "signer" of the RRSIG (aka, the zone). -1 if not signed. */ - unsigned n_skip_labels_signer; - /* How many labels to strip to determine "synthesizing source" of this RR, i.e. the wildcard's immediate parent. -1 if not signed. */ - unsigned n_skip_labels_source; - - bool unparseable:1; - - bool wire_format_canonical:1; - void *wire_format; - size_t wire_format_size; - size_t wire_format_rdata_offset; - - union { - struct { - void *data; - size_t data_size; - } generic, opt; - - struct { - uint16_t priority; - uint16_t weight; - uint16_t port; - char *name; - } srv; - - struct { - char *name; - } ptr, ns, cname, dname; - - struct { - char *cpu; - char *os; - } hinfo; - - struct { - DnsTxtItem *items; - } txt, spf; - - struct { - struct in_addr in_addr; - } a; - - struct { - struct in6_addr in6_addr; - } aaaa; - - struct { - char *mname; - char *rname; - uint32_t serial; - uint32_t refresh; - uint32_t retry; - uint32_t expire; - uint32_t minimum; - } soa; - - struct { - uint16_t priority; - char *exchange; - } mx; - - /* https://tools.ietf.org/html/rfc1876 */ - struct { - uint8_t version; - uint8_t size; - uint8_t horiz_pre; - uint8_t vert_pre; - uint32_t latitude; - uint32_t longitude; - uint32_t altitude; - } loc; - - /* https://tools.ietf.org/html/rfc4255#section-3.1 */ - struct { - uint8_t algorithm; - uint8_t fptype; - void *fingerprint; - size_t fingerprint_size; - } sshfp; - - /* http://tools.ietf.org/html/rfc4034#section-2.1 */ - struct { - uint16_t flags; - uint8_t protocol; - uint8_t algorithm; - void* key; - size_t key_size; - } dnskey; - - /* http://tools.ietf.org/html/rfc4034#section-3.1 */ - struct { - uint16_t type_covered; - uint8_t algorithm; - uint8_t labels; - uint32_t original_ttl; - uint32_t expiration; - uint32_t inception; - uint16_t key_tag; - char *signer; - void *signature; - size_t signature_size; - } rrsig; - - /* https://tools.ietf.org/html/rfc4034#section-4.1 */ - struct { - char *next_domain_name; - Bitmap *types; - } nsec; - - /* https://tools.ietf.org/html/rfc4034#section-5.1 */ - struct { - uint16_t key_tag; - uint8_t algorithm; - uint8_t digest_type; - void *digest; - size_t digest_size; - } ds; - - struct { - uint8_t algorithm; - uint8_t flags; - uint16_t iterations; - void *salt; - size_t salt_size; - void *next_hashed_name; - size_t next_hashed_name_size; - Bitmap *types; - } nsec3; - - /* https://tools.ietf.org/html/draft-ietf-dane-protocol-23 */ - struct { - uint8_t cert_usage; - uint8_t selector; - uint8_t matching_type; - void *data; - size_t data_size; - } tlsa; - }; -}; - -static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) { - if (!key) - return NULL; - - if (key->_name) - return key->_name; - - return (char*) key + sizeof(DnsResourceKey); -} - -static inline const void* DNS_RESOURCE_RECORD_RDATA(DnsResourceRecord *rr) { - if (!rr) - return NULL; - - if (!rr->wire_format) - return NULL; - - assert(rr->wire_format_rdata_offset <= rr->wire_format_size); - return (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset; -} - -static inline size_t DNS_RESOURCE_RECORD_RDATA_SIZE(DnsResourceRecord *rr) { - if (!rr) - return 0; - if (!rr->wire_format) - return 0; - - assert(rr->wire_format_rdata_offset <= rr->wire_format_size); - return rr->wire_format_size - rr->wire_format_rdata_offset; -} - -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); -DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name); -DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key); -DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key); -bool dns_resource_key_is_address(const DnsResourceKey *key); -int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b); -int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain); -int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain); -int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa); -int dns_resource_key_to_string(const DnsResourceKey *key, char **ret); -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref); - -static inline bool dns_key_is_shared(const DnsResourceKey *key) { - return IN_SET(key->type, DNS_TYPE_PTR); -} - -bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b); - -DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key); -DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name); -DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr); -DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr); -int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); -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); -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref); - -int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical); - -int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret); -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); - -DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i); -bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b); - -extern const struct hash_ops dns_resource_key_hash_ops; -extern const struct hash_ops dns_resource_record_hash_ops; - -int dnssec_algorithm_to_string_alloc(int i, char **ret); -int dnssec_algorithm_from_string(const char *s) _pure_; - -int dnssec_digest_to_string_alloc(int i, char **ret); -int dnssec_digest_from_string(const char *s) _pure_; diff --git a/src/resolve/resolved-dns-scope.c b/src/resolve/resolved-dns-scope.c deleted file mode 100644 index a406872a38..0000000000 --- a/src/resolve/resolved-dns-scope.c +++ /dev/null @@ -1,1028 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "af-list.h" -#include "alloc-util.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "hostname-util.h" -#include "missing.h" -#include "random-util.h" -#include "resolved-dns-scope.h" -#include "resolved-llmnr.h" -#include "resolved-mdns.h" -#include "socket-util.h" -#include "strv.h" - -#define MULTICAST_RATELIMIT_INTERVAL_USEC (1*USEC_PER_SEC) -#define MULTICAST_RATELIMIT_BURST 1000 - -/* After how much time to repeat LLMNR requests, see RFC 4795 Section 7 */ -#define MULTICAST_RESEND_TIMEOUT_MIN_USEC (100 * USEC_PER_MSEC) -#define MULTICAST_RESEND_TIMEOUT_MAX_USEC (1 * USEC_PER_SEC) - -int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int family) { - DnsScope *s; - - assert(m); - assert(ret); - - s = new0(DnsScope, 1); - if (!s) - return -ENOMEM; - - s->manager = m; - s->link = l; - s->protocol = protocol; - s->family = family; - s->resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC; - - if (protocol == DNS_PROTOCOL_DNS) { - /* Copy DNSSEC mode from the link if it is set there, - * otherwise take the manager's DNSSEC mode. Note that - * we copy this only at scope creation time, and do - * not update it from the on, even if the setting - * changes. */ - - if (l) - s->dnssec_mode = link_get_dnssec_mode(l); - else - s->dnssec_mode = manager_get_dnssec_mode(m); - } else - s->dnssec_mode = DNSSEC_NO; - - LIST_PREPEND(scopes, m->dns_scopes, s); - - dns_scope_llmnr_membership(s, true); - dns_scope_mdns_membership(s, true); - - log_debug("New scope on link %s, protocol %s, family %s", l ? l->name : "*", dns_protocol_to_string(protocol), family == AF_UNSPEC ? "*" : af_to_name(family)); - - /* Enforce ratelimiting for the multicast protocols */ - RATELIMIT_INIT(s->ratelimit, MULTICAST_RATELIMIT_INTERVAL_USEC, MULTICAST_RATELIMIT_BURST); - - *ret = s; - return 0; -} - -static void dns_scope_abort_transactions(DnsScope *s) { - assert(s); - - while (s->transactions) { - DnsTransaction *t = s->transactions; - - /* Abort the transaction, but make sure it is not - * freed while we still look at it */ - - t->block_gc++; - if (DNS_TRANSACTION_IS_LIVE(t->state)) - dns_transaction_complete(t, DNS_TRANSACTION_ABORTED); - t->block_gc--; - - dns_transaction_free(t); - } -} - -DnsScope* dns_scope_free(DnsScope *s) { - DnsResourceRecord *rr; - - if (!s) - return NULL; - - log_debug("Removing scope on link %s, protocol %s, family %s", s->link ? s->link->name : "*", dns_protocol_to_string(s->protocol), s->family == AF_UNSPEC ? "*" : af_to_name(s->family)); - - dns_scope_llmnr_membership(s, false); - dns_scope_mdns_membership(s, false); - dns_scope_abort_transactions(s); - - while (s->query_candidates) - dns_query_candidate_free(s->query_candidates); - - hashmap_free(s->transactions_by_key); - - while ((rr = ordered_hashmap_steal_first(s->conflict_queue))) - dns_resource_record_unref(rr); - - ordered_hashmap_free(s->conflict_queue); - sd_event_source_unref(s->conflict_event_source); - - dns_cache_flush(&s->cache); - dns_zone_flush(&s->zone); - - LIST_REMOVE(scopes, s->manager->dns_scopes, s); - free(s); - - return NULL; -} - -DnsServer *dns_scope_get_dns_server(DnsScope *s) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return NULL; - - if (s->link) - return link_get_dns_server(s->link); - else - return manager_get_dns_server(s->manager); -} - -void dns_scope_next_dns_server(DnsScope *s) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return; - - if (s->link) - link_next_dns_server(s->link); - else - manager_next_dns_server(s->manager); -} - -void dns_scope_packet_received(DnsScope *s, usec_t rtt) { - assert(s); - - if (rtt <= s->max_rtt) - return; - - s->max_rtt = rtt; - s->resend_timeout = MIN(MAX(MULTICAST_RESEND_TIMEOUT_MIN_USEC, s->max_rtt * 2), MULTICAST_RESEND_TIMEOUT_MAX_USEC); -} - -void dns_scope_packet_lost(DnsScope *s, usec_t usec) { - assert(s); - - if (s->resend_timeout <= usec) - s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC); -} - -static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) { - union in_addr_union addr; - int ifindex = 0, r; - int family; - uint32_t mtu; - - assert(s); - assert(p); - assert(p->protocol == s->protocol); - - if (s->link) { - mtu = s->link->mtu; - ifindex = s->link->ifindex; - } else - mtu = manager_find_mtu(s->manager); - - switch (s->protocol) { - - case DNS_PROTOCOL_DNS: - assert(fd >= 0); - - if (DNS_PACKET_QDCOUNT(p) > 1) - return -EOPNOTSUPP; - - if (p->size > DNS_PACKET_UNICAST_SIZE_MAX) - return -EMSGSIZE; - - if (p->size + UDP_PACKET_HEADER_SIZE > mtu) - return -EMSGSIZE; - - r = manager_write(s->manager, fd, p); - if (r < 0) - return r; - - break; - - case DNS_PROTOCOL_LLMNR: - assert(fd < 0); - - if (DNS_PACKET_QDCOUNT(p) > 1) - return -EOPNOTSUPP; - - if (!ratelimit_test(&s->ratelimit)) - return -EBUSY; - - family = s->family; - - if (family == AF_INET) { - addr.in = LLMNR_MULTICAST_IPV4_ADDRESS; - fd = manager_llmnr_ipv4_udp_fd(s->manager); - } else if (family == AF_INET6) { - addr.in6 = LLMNR_MULTICAST_IPV6_ADDRESS; - fd = manager_llmnr_ipv6_udp_fd(s->manager); - } else - return -EAFNOSUPPORT; - if (fd < 0) - return fd; - - r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, p); - if (r < 0) - return r; - - break; - - case DNS_PROTOCOL_MDNS: - assert(fd < 0); - - if (!ratelimit_test(&s->ratelimit)) - return -EBUSY; - - family = s->family; - - if (family == AF_INET) { - addr.in = MDNS_MULTICAST_IPV4_ADDRESS; - fd = manager_mdns_ipv4_fd(s->manager); - } else if (family == AF_INET6) { - addr.in6 = MDNS_MULTICAST_IPV6_ADDRESS; - fd = manager_mdns_ipv6_fd(s->manager); - } else - return -EAFNOSUPPORT; - if (fd < 0) - return fd; - - r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, p); - if (r < 0) - return r; - - break; - - default: - return -EAFNOSUPPORT; - } - - return 1; -} - -int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p) { - int r; - - assert(s); - assert(p); - assert(p->protocol == s->protocol); - assert((s->protocol == DNS_PROTOCOL_DNS) == (fd >= 0)); - - do { - /* If there are multiple linked packets, set the TC bit in all but the last of them */ - if (p->more) { - assert(p->protocol == DNS_PROTOCOL_MDNS); - dns_packet_set_flags(p, true, true); - } - - r = dns_scope_emit_one(s, fd, p); - if (r < 0) - return r; - - p = p->more; - } while (p); - - return 0; -} - -static int dns_scope_socket( - DnsScope *s, - int type, - int family, - const union in_addr_union *address, - DnsServer *server, - uint16_t port) { - - _cleanup_close_ int fd = -1; - union sockaddr_union sa = {}; - socklen_t salen; - static const int one = 1; - int ret, r; - - assert(s); - - if (server) { - assert(family == AF_UNSPEC); - assert(!address); - - sa.sa.sa_family = server->family; - if (server->family == AF_INET) { - sa.in.sin_port = htobe16(port); - sa.in.sin_addr = server->address.in; - salen = sizeof(sa.in); - } 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; - salen = sizeof(sa.in6); - } else - return -EAFNOSUPPORT; - } else { - assert(family != AF_UNSPEC); - assert(address); - - sa.sa.sa_family = family; - - if (family == AF_INET) { - sa.in.sin_port = htobe16(port); - sa.in.sin_addr = address->in; - salen = sizeof(sa.in); - } 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; - salen = sizeof(sa.in6); - } else - return -EAFNOSUPPORT; - } - - fd = socket(sa.sa.sa_family, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - if (type == SOCK_STREAM) { - r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); - if (r < 0) - return -errno; - } - - if (s->link) { - uint32_t ifindex = htobe32(s->link->ifindex); - - if (sa.sa.sa_family == AF_INET) { - r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - return -errno; - } else if (sa.sa.sa_family == AF_INET6) { - r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - return -errno; - } - } - - if (s->protocol == DNS_PROTOCOL_LLMNR) { - /* RFC 4795, section 2.5 requires the TTL to be set to 1 */ - - if (sa.sa.sa_family == AF_INET) { - r = setsockopt(fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); - if (r < 0) - return -errno; - } else if (sa.sa.sa_family == AF_INET6) { - r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); - if (r < 0) - return -errno; - } - } - - r = connect(fd, &sa.sa, salen); - if (r < 0 && errno != EINPROGRESS) - return -errno; - - ret = fd; - fd = -1; - - return ret; -} - -int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port) { - return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, server, port); -} - -int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port) { - return dns_scope_socket(s, SOCK_STREAM, family, address, server, port); -} - -DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { - DnsSearchDomain *d; - - assert(s); - assert(domain); - - /* Checks if the specified domain is something to look up on - * this scope. Note that this accepts non-qualified hostnames, - * i.e. those without any search path prefixed yet. */ - - if (ifindex != 0 && (!s->link || s->link->ifindex != ifindex)) - return DNS_SCOPE_NO; - - if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family, 0) & flags) == 0) - return DNS_SCOPE_NO; - - /* Never resolve any loopback hostname or IP address via DNS, - * LLMNR or mDNS. Instead, always rely on synthesized RRs for - * these. */ - if (is_localhost(domain) || - dns_name_endswith(domain, "127.in-addr.arpa") > 0 || - dns_name_equal(domain, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) - return DNS_SCOPE_NO; - - /* Never respond to some of the domains listed in RFC6303 */ - if (dns_name_endswith(domain, "0.in-addr.arpa") > 0 || - dns_name_equal(domain, "255.255.255.255.in-addr.arpa") > 0 || - dns_name_equal(domain, "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) - return DNS_SCOPE_NO; - - /* Never respond to some of the domains listed in RFC6761 */ - if (dns_name_endswith(domain, "invalid") > 0) - return DNS_SCOPE_NO; - - /* Always honour search domains for routing queries. Note that - * we return DNS_SCOPE_YES here, rather than just - * DNS_SCOPE_MAYBE, which means wildcard scopes won't be - * considered anymore. */ - LIST_FOREACH(domains, d, dns_scope_get_search_domains(s)) - if (dns_name_endswith(domain, d->name) > 0) - return DNS_SCOPE_YES; - - switch (s->protocol) { - - case DNS_PROTOCOL_DNS: - - /* Exclude link-local IP ranges */ - if (dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 && - dns_name_endswith(domain, "8.e.f.ip6.arpa") == 0 && - dns_name_endswith(domain, "9.e.f.ip6.arpa") == 0 && - dns_name_endswith(domain, "a.e.f.ip6.arpa") == 0 && - dns_name_endswith(domain, "b.e.f.ip6.arpa") == 0 && - /* If networks use .local in their private setups, they are supposed to also add .local to their search - * domains, which we already checked above. Otherwise, we consider .local specific to mDNS and won't - * send such queries ordinary DNS servers. */ - dns_name_endswith(domain, "local") == 0) - return DNS_SCOPE_MAYBE; - - return DNS_SCOPE_NO; - - case DNS_PROTOCOL_MDNS: - if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || - (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || - (dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */ - dns_name_equal(domain, "local") == 0 && /* but not the single-label "local" name itself */ - manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via mDNS */ - return DNS_SCOPE_MAYBE; - - return DNS_SCOPE_NO; - - case DNS_PROTOCOL_LLMNR: - if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || - (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || - (dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */ - !is_gateway_hostname(domain) && /* don't resolve "gateway" with LLMNR, let nss-myhostname handle this */ - manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via LLMNR */ - return DNS_SCOPE_MAYBE; - - return DNS_SCOPE_NO; - - default: - assert_not_reached("Unknown scope protocol"); - } -} - -bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) { - int key_family; - - assert(s); - assert(key); - - /* Check if it makes sense to resolve the specified key on - * this scope. Note that this call assumes as fully qualified - * name, i.e. the search suffixes already appended. */ - - if (key->class != DNS_CLASS_IN) - return false; - - if (s->protocol == DNS_PROTOCOL_DNS) { - - /* On classic DNS, looking up non-address RRs is always - * fine. (Specifically, we want to permit looking up - * DNSKEY and DS records on the root and top-level - * domains.) */ - if (!dns_resource_key_is_address(key)) - return true; - - /* However, we refuse to look up A and AAAA RRs on the - * root and single-label domains, under the assumption - * that those should be resolved via LLMNR or search - * path only, and should not be leaked onto the - * internet. */ - return !(dns_name_is_single_label(DNS_RESOURCE_KEY_NAME(key)) || - dns_name_is_root(DNS_RESOURCE_KEY_NAME(key))); - } - - /* On mDNS and LLMNR, send A and AAAA queries only on the - * respective scopes */ - - key_family = dns_type_to_af(key->type); - if (key_family < 0) - return true; - - return key_family == s->family; -} - -static int dns_scope_multicast_membership(DnsScope *s, bool b, struct in_addr in, struct in6_addr in6) { - int fd; - - assert(s); - assert(s->link); - - if (s->family == AF_INET) { - struct ip_mreqn mreqn = { - .imr_multiaddr = in, - .imr_ifindex = s->link->ifindex, - }; - - fd = manager_llmnr_ipv4_udp_fd(s->manager); - if (fd < 0) - return fd; - - /* Always first try to drop membership before we add - * one. This is necessary on some devices, such as - * veth. */ - if (b) - (void) setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)); - - if (setsockopt(fd, IPPROTO_IP, b ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)) < 0) - return -errno; - - } else if (s->family == AF_INET6) { - struct ipv6_mreq mreq = { - .ipv6mr_multiaddr = in6, - .ipv6mr_interface = s->link->ifindex, - }; - - fd = manager_llmnr_ipv6_udp_fd(s->manager); - if (fd < 0) - return fd; - - if (b) - (void) setsockopt(fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); - - if (setsockopt(fd, IPPROTO_IPV6, b ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) - return -errno; - } else - return -EAFNOSUPPORT; - - return 0; -} - -int dns_scope_llmnr_membership(DnsScope *s, bool b) { - - if (s->protocol != DNS_PROTOCOL_LLMNR) - return 0; - - return dns_scope_multicast_membership(s, b, LLMNR_MULTICAST_IPV4_ADDRESS, LLMNR_MULTICAST_IPV6_ADDRESS); -} - -int dns_scope_mdns_membership(DnsScope *s, bool b) { - - if (s->protocol != DNS_PROTOCOL_MDNS) - return 0; - - return dns_scope_multicast_membership(s, b, MDNS_MULTICAST_IPV4_ADDRESS, MDNS_MULTICAST_IPV6_ADDRESS); -} - -static int dns_scope_make_reply_packet( - DnsScope *s, - uint16_t id, - int rcode, - DnsQuestion *q, - DnsAnswer *answer, - DnsAnswer *soa, - bool tentative, - 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)) - return -EINVAL; - - r = dns_packet_new(&p, s->protocol, 0); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->id = id; - DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( - 1 /* qr */, - 0 /* opcode */, - 0 /* c */, - 0 /* tc */, - tentative, - 0 /* (ra) */, - 0 /* (ad) */, - 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); - } - - 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; - } - - DNS_PACKET_HEADER(p)->arcount = htobe16(soa->n_rrs); - } - - *ret = p; - p = NULL; - - return 0; -} - -static void dns_scope_verify_conflicts(DnsScope *s, DnsPacket *p) { - unsigned n; - - 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); -} - -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; - DnsResourceKey *key = NULL; - bool tentative = false; - int r, fd; - - assert(s); - assert(p); - - if (p->protocol != DNS_PROTOCOL_LLMNR) - return; - - if (p->ipproto == IPPROTO_UDP) { - /* Don't accept UDP queries directed to anything but - * the LLMNR multicast addresses. See RFC 4795, - * section 2.5. */ - - if (p->family == AF_INET && !in_addr_equal(AF_INET, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV4_ADDRESS)) - return; - - if (p->family == AF_INET6 && !in_addr_equal(AF_INET6, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV6_ADDRESS)) - return; - } - - r = dns_packet_extract(p); - if (r < 0) { - log_debug_errno(r, "Failed to extract resources from incoming packet: %m"); - return; - } - - if (DNS_PACKET_LLMNR_C(p)) { - /* Somebody notified us about a possible conflict */ - dns_scope_verify_conflicts(s, p); - return; - } - - assert(p->question->n_keys == 1); - key = p->question->keys[0]; - - r = dns_zone_lookup(&s->zone, key, &answer, &soa, &tentative); - if (r < 0) { - log_debug_errno(r, "Failed to lookup key: %m"); - return; - } - if (r == 0) - return; - - if (answer) - dns_answer_order_by_scope(answer, in_addr_is_link_local(p->family, &p->sender) > 0); - - r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, p->question, answer, soa, tentative, &reply); - if (r < 0) { - log_debug_errno(r, "Failed to build reply packet: %m"); - return; - } - - if (stream) - r = dns_stream_write_packet(stream, reply); - else { - if (!ratelimit_test(&s->ratelimit)) - return; - - if (p->family == AF_INET) - fd = manager_llmnr_ipv4_udp_fd(s->manager); - else if (p->family == AF_INET6) - fd = manager_llmnr_ipv6_udp_fd(s->manager); - else { - log_debug("Unknown protocol"); - return; - } - if (fd < 0) { - log_debug_errno(fd, "Failed to get reply socket: %m"); - return; - } - - /* Note that we always immediately reply to all LLMNR - * requests, and do not wait any time, since we - * 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; - } -} - -DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok) { - DnsTransaction *t; - - assert(scope); - assert(key); - - /* Try to find an ongoing transaction that is a equal to the - * specified question */ - t = hashmap_get(scope->transactions_by_key, key); - if (!t) - return NULL; - - /* Refuse reusing transactions that completed based on cached - * data instead of a real packet, if that's requested. */ - if (!cache_ok && - IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_RCODE_FAILURE) && - t->answer_source != DNS_TRANSACTION_NETWORK) - return NULL; - - return t; -} - -static int dns_scope_make_conflict_packet( - DnsScope *s, - DnsResourceRecord *rr, - DnsPacket **ret) { - - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - - assert(s); - assert(rr); - assert(ret); - - r = dns_packet_new(&p, s->protocol, 0); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( - 0 /* qr */, - 0 /* opcode */, - 1 /* conflict */, - 0 /* tc */, - 0 /* t */, - 0 /* (ra) */, - 0 /* (ad) */, - 0 /* (cd) */, - 0)); - - /* For mDNS, the transaction ID should always be 0 */ - if (s->protocol != DNS_PROTOCOL_MDNS) - random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t)); - - DNS_PACKET_HEADER(p)->qdcount = htobe16(1); - DNS_PACKET_HEADER(p)->arcount = htobe16(1); - - r = dns_packet_append_key(p, rr->key, NULL); - if (r < 0) - return r; - - r = dns_packet_append_rr(p, rr, NULL, NULL); - if (r < 0) - return r; - - *ret = p; - p = NULL; - - return 0; -} - -static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata) { - DnsScope *scope = userdata; - int r; - - assert(es); - assert(scope); - - scope->conflict_event_source = sd_event_source_unref(scope->conflict_event_source); - - for (;;) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - - rr = ordered_hashmap_steal_first(scope->conflict_queue); - if (!rr) - break; - - r = dns_scope_make_conflict_packet(scope, rr, &p); - if (r < 0) { - log_error_errno(r, "Failed to make conflict packet: %m"); - return 0; - } - - r = dns_scope_emit_udp(scope, -1, p); - if (r < 0) - log_debug_errno(r, "Failed to send conflict packet: %m"); - } - - return 0; -} - -int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr) { - usec_t jitter; - int r; - - assert(scope); - assert(rr); - - /* We don't send these queries immediately. Instead, we queue - * them, and send them after some jitter delay. */ - r = ordered_hashmap_ensure_allocated(&scope->conflict_queue, &dns_resource_key_hash_ops); - if (r < 0) { - log_oom(); - return r; - } - - /* We only place one RR per key in the conflict - * messages, not all of them. That should be enough to - * indicate where there might be a conflict */ - r = ordered_hashmap_put(scope->conflict_queue, rr->key, rr); - if (r == -EEXIST || r == 0) - return 0; - if (r < 0) - return log_debug_errno(r, "Failed to queue conflicting RR: %m"); - - dns_resource_record_ref(rr); - - if (scope->conflict_event_source) - return 0; - - random_bytes(&jitter, sizeof(jitter)); - jitter %= LLMNR_JITTER_INTERVAL_USEC; - - r = sd_event_add_time(scope->manager->event, - &scope->conflict_event_source, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + jitter, - LLMNR_JITTER_INTERVAL_USEC, - on_conflict_dispatch, scope); - if (r < 0) - return log_debug_errno(r, "Failed to add conflict dispatch event: %m"); - - (void) sd_event_source_set_description(scope->conflict_event_source, "scope-conflict"); - - return 0; -} - -void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p) { - unsigned i; - int r; - - assert(scope); - assert(p); - - if (p->protocol != DNS_PROTOCOL_LLMNR) - return; - - if (DNS_PACKET_RRCOUNT(p) <= 0) - return; - - if (DNS_PACKET_LLMNR_C(p) != 0) - return; - - if (DNS_PACKET_LLMNR_T(p) != 0) - return; - - if (manager_our_packet(scope->manager, p)) - return; - - r = dns_packet_extract(p); - if (r < 0) { - log_debug_errno(r, "Failed to extract packet: %m"); - return; - } - - log_debug("Checking for conflicts..."); - - for (i = 0; i < p->answer->n_rrs; i++) { - - /* Check for conflicts against the local zone. If we - * found one, we won't check any further */ - r = dns_zone_check_conflicts(&scope->zone, p->answer->items[i].rr); - if (r != 0) - continue; - - /* Check for conflicts against the local cache. If so, - * send out an advisory query, to inform everybody */ - r = dns_cache_check_conflicts(&scope->cache, p->answer->items[i].rr, p->family, &p->sender); - if (r <= 0) - continue; - - dns_scope_notify_conflict(scope, p->answer->items[i].rr); - } -} - -void dns_scope_dump(DnsScope *s, FILE *f) { - assert(s); - - if (!f) - f = stdout; - - fputs("[Scope protocol=", f); - fputs(dns_protocol_to_string(s->protocol), f); - - if (s->link) { - fputs(" interface=", f); - fputs(s->link->name, f); - } - - if (s->family != AF_UNSPEC) { - fputs(" family=", f); - fputs(af_to_name(s->family), f); - } - - fputs("]\n", f); - - if (!dns_zone_is_empty(&s->zone)) { - fputs("ZONE:\n", f); - dns_zone_dump(&s->zone, f); - } - - if (!dns_cache_is_empty(&s->cache)) { - fputs("CACHE:\n", f); - dns_cache_dump(&s->cache, f); - } -} - -DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return NULL; - - if (s->link) - return s->link->search_domains; - - return s->manager->search_domains; -} - -bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) { - assert(s); - - if (s->protocol != DNS_PROTOCOL_DNS) - return false; - - return dns_name_is_single_label(name); -} - -bool dns_scope_network_good(DnsScope *s) { - /* Checks whether the network is in good state for lookups on this scope. For mDNS/LLMNR/Classic DNS scopes - * bound to links this is easy, as they don't even exist if the link isn't in a suitable state. For the global - * DNS scope we check whether there are any links that are up and have an address. */ - - if (s->link) - return true; - - return manager_routable(s->manager, AF_UNSPEC); -} diff --git a/src/resolve/resolved-dns-scope.h b/src/resolve/resolved-dns-scope.h deleted file mode 100644 index 291e5817d0..0000000000 --- a/src/resolve/resolved-dns-scope.h +++ /dev/null @@ -1,109 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "list.h" - -typedef struct DnsScope DnsScope; - -#include "resolved-dns-cache.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-server.h" -#include "resolved-dns-zone.h" -#include "resolved-link.h" - -typedef enum DnsScopeMatch { - DNS_SCOPE_NO, - DNS_SCOPE_MAYBE, - DNS_SCOPE_YES, - _DNS_SCOPE_MATCH_MAX, - _DNS_SCOPE_INVALID = -1 -} DnsScopeMatch; - -struct DnsScope { - Manager *manager; - - DnsProtocol protocol; - int family; - DnssecMode dnssec_mode; - - Link *link; - - DnsCache cache; - DnsZone zone; - - OrderedHashmap *conflict_queue; - sd_event_source *conflict_event_source; - - RateLimit ratelimit; - - usec_t resend_timeout; - usec_t max_rtt; - - LIST_HEAD(DnsQueryCandidate, query_candidates); - - /* Note that we keep track of ongoing transactions in two - * ways: once in a hashmap, indexed by the rr key, and once in - * a linked list. We use the hashmap to quickly find - * transactions we can reuse for a key. But note that there - * might be multiple transactions for the same key (because - * the zone probing can't reuse a transaction answered from - * the zone or the cache), and the hashmap only tracks the - * most recent entry. */ - Hashmap *transactions_by_key; - LIST_HEAD(DnsTransaction, transactions); - - LIST_FIELDS(DnsScope, scopes); -}; - -int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol p, int family); -DnsScope* dns_scope_free(DnsScope *s); - -void dns_scope_packet_received(DnsScope *s, usec_t rtt); -void dns_scope_packet_lost(DnsScope *s, usec_t usec); - -int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p); -int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port); -int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port); - -DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain); -bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key); - -DnsServer *dns_scope_get_dns_server(DnsScope *s); -void dns_scope_next_dns_server(DnsScope *s); - -int dns_scope_llmnr_membership(DnsScope *s, bool b); -int dns_scope_mdns_membership(DnsScope *s, bool b); - -void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); - -DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok); - -int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr); -void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p); - -void dns_scope_dump(DnsScope *s, FILE *f); - -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); diff --git a/src/resolve/resolved-dns-search-domain.c b/src/resolve/resolved-dns-search-domain.c deleted file mode 100644 index 732471027b..0000000000 --- a/src/resolve/resolved-dns-search-domain.c +++ /dev/null @@ -1,227 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include "alloc-util.h" -#include "dns-domain.h" -#include "resolved-dns-search-domain.h" - -int dns_search_domain_new( - Manager *m, - DnsSearchDomain **ret, - DnsSearchDomainType type, - Link *l, - const char *name) { - - _cleanup_free_ char *normalized = NULL; - DnsSearchDomain *d; - int r; - - assert(m); - assert((type == DNS_SEARCH_DOMAIN_LINK) == !!l); - assert(name); - - r = dns_name_normalize(name, &normalized); - if (r < 0) - return r; - - if (l) { - if (l->n_search_domains >= LINK_SEARCH_DOMAINS_MAX) - return -E2BIG; - } else { - if (m->n_search_domains >= MANAGER_SEARCH_DOMAINS_MAX) - return -E2BIG; - } - - d = new0(DnsSearchDomain, 1); - if (!d) - return -ENOMEM; - - d->n_ref = 1; - d->manager = m; - d->type = type; - d->name = normalized; - normalized = NULL; - - switch (type) { - - case DNS_SEARCH_DOMAIN_LINK: - d->link = l; - LIST_APPEND(domains, l->search_domains, d); - l->n_search_domains++; - break; - - case DNS_SERVER_SYSTEM: - LIST_APPEND(domains, m->search_domains, d); - m->n_search_domains++; - break; - - default: - assert_not_reached("Unknown search domain type"); - } - - d->linked = true; - - if (ret) - *ret = d; - - return 0; -} - -DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d) { - if (!d) - return NULL; - - assert(d->n_ref > 0); - d->n_ref++; - - return d; -} - -DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d) { - if (!d) - return NULL; - - assert(d->n_ref > 0); - d->n_ref--; - - if (d->n_ref > 0) - return NULL; - - free(d->name); - free(d); - - return NULL; -} - -void dns_search_domain_unlink(DnsSearchDomain *d) { - assert(d); - assert(d->manager); - - if (!d->linked) - return; - - switch (d->type) { - - case DNS_SEARCH_DOMAIN_LINK: - assert(d->link); - assert(d->link->n_search_domains > 0); - LIST_REMOVE(domains, d->link->search_domains, d); - d->link->n_search_domains--; - break; - - case DNS_SEARCH_DOMAIN_SYSTEM: - assert(d->manager->n_search_domains > 0); - LIST_REMOVE(domains, d->manager->search_domains, d); - d->manager->n_search_domains--; - break; - } - - d->linked = false; - - dns_search_domain_unref(d); -} - -void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d) { - DnsSearchDomain *tail; - - assert(d); - - if (!d->marked) - return; - - d->marked = false; - - if (!d->linked || !d->domains_next) - return; - - switch (d->type) { - - case DNS_SEARCH_DOMAIN_LINK: - assert(d->link); - LIST_FIND_TAIL(domains, d, tail); - LIST_REMOVE(domains, d->link->search_domains, d); - LIST_INSERT_AFTER(domains, d->link->search_domains, tail, d); - break; - - case DNS_SEARCH_DOMAIN_SYSTEM: - LIST_FIND_TAIL(domains, d, tail); - LIST_REMOVE(domains, d->manager->search_domains, d); - LIST_INSERT_AFTER(domains, d->manager->search_domains, tail, d); - break; - - default: - assert_not_reached("Unknown search domain type"); - } -} - -void dns_search_domain_unlink_all(DnsSearchDomain *first) { - DnsSearchDomain *next; - - if (!first) - return; - - next = first->domains_next; - dns_search_domain_unlink(first); - - dns_search_domain_unlink_all(next); -} - -void dns_search_domain_unlink_marked(DnsSearchDomain *first) { - DnsSearchDomain *next; - - if (!first) - return; - - next = first->domains_next; - - if (first->marked) - dns_search_domain_unlink(first); - - dns_search_domain_unlink_marked(next); -} - -void dns_search_domain_mark_all(DnsSearchDomain *first) { - if (!first) - return; - - first->marked = true; - dns_search_domain_mark_all(first->domains_next); -} - -int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret) { - DnsSearchDomain *d; - int r; - - assert(name); - assert(ret); - - LIST_FOREACH(domains, d, first) { - - r = dns_name_equal(name, d->name); - if (r < 0) - return r; - if (r > 0) { - *ret = d; - return 1; - } - } - - *ret = NULL; - return 0; -} diff --git a/src/resolve/resolved-dns-search-domain.h b/src/resolve/resolved-dns-search-domain.h deleted file mode 100644 index eaacef4edc..0000000000 --- a/src/resolve/resolved-dns-search-domain.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include "macro.h" - -typedef struct DnsSearchDomain DnsSearchDomain; - -typedef enum DnsSearchDomainType { - DNS_SEARCH_DOMAIN_SYSTEM, - DNS_SEARCH_DOMAIN_LINK, -} DnsSearchDomainType; - -#include "resolved-link.h" -#include "resolved-manager.h" - -struct DnsSearchDomain { - Manager *manager; - - unsigned n_ref; - - DnsSearchDomainType type; - Link *link; - - char *name; - - bool marked:1; - bool route_only:1; - - bool linked:1; - LIST_FIELDS(DnsSearchDomain, domains); -}; - -int dns_search_domain_new( - Manager *m, - DnsSearchDomain **ret, - DnsSearchDomainType type, - Link *link, - const char *name); - -DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d); -DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d); - -void dns_search_domain_unlink(DnsSearchDomain *d); -void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d); - -void dns_search_domain_unlink_all(DnsSearchDomain *first); -void dns_search_domain_unlink_marked(DnsSearchDomain *first); -void dns_search_domain_mark_all(DnsSearchDomain *first); - -int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret); - -static inline const char* DNS_SEARCH_DOMAIN_NAME(DnsSearchDomain *d) { - return d ? d->name : NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsSearchDomain*, dns_search_domain_unref); diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c deleted file mode 100644 index 27342a0e04..0000000000 --- a/src/resolve/resolved-dns-server.c +++ /dev/null @@ -1,740 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "alloc-util.h" -#include "resolved-dns-server.h" -#include "resolved-resolv-conf.h" -#include "siphash24.h" -#include "string-table.h" -#include "string-util.h" - -/* After how much time to repeat classic DNS requests */ -#define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC) -#define DNS_TIMEOUT_MAX_USEC (5 * USEC_PER_SEC) - -/* The amount of time to wait before retrying with a full feature set */ -#define DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC (6 * USEC_PER_HOUR) -#define DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC (5 * USEC_PER_MINUTE) - -/* The number of times we will attempt a certain feature set before degrading */ -#define DNS_SERVER_FEATURE_RETRY_ATTEMPTS 3 - -int dns_server_new( - Manager *m, - DnsServer **ret, - DnsServerType type, - Link *l, - int family, - const union in_addr_union *in_addr) { - - DnsServer *s; - - assert(m); - assert((type == DNS_SERVER_LINK) == !!l); - assert(in_addr); - - if (!IN_SET(family, AF_INET, AF_INET6)) - return -EAFNOSUPPORT; - - if (l) { - if (l->n_dns_servers >= LINK_DNS_SERVERS_MAX) - return -E2BIG; - } else { - if (m->n_dns_servers >= MANAGER_DNS_SERVERS_MAX) - return -E2BIG; - } - - s = new0(DnsServer, 1); - if (!s) - return -ENOMEM; - - s->n_ref = 1; - s->manager = m; - s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; - s->features_grace_period_usec = DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC; - s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX; - s->type = type; - s->family = family; - s->address = *in_addr; - s->resend_timeout = DNS_TIMEOUT_MIN_USEC; - - switch (type) { - - case DNS_SERVER_LINK: - s->link = l; - LIST_APPEND(servers, l->dns_servers, s); - l->n_dns_servers++; - break; - - case DNS_SERVER_SYSTEM: - LIST_APPEND(servers, m->dns_servers, s); - m->n_dns_servers++; - break; - - case DNS_SERVER_FALLBACK: - LIST_APPEND(servers, m->fallback_dns_servers, s); - m->n_dns_servers++; - break; - - default: - assert_not_reached("Unknown server type"); - } - - s->linked = true; - - /* A new DNS server that isn't fallback is added and the one - * we used so far was a fallback one? Then let's try to pick - * the new one */ - if (type != DNS_SERVER_FALLBACK && - m->current_dns_server && - m->current_dns_server->type == DNS_SERVER_FALLBACK) - manager_set_dns_server(m, NULL); - - if (ret) - *ret = s; - - return 0; -} - -DnsServer* dns_server_ref(DnsServer *s) { - if (!s) - return NULL; - - assert(s->n_ref > 0); - s->n_ref ++; - - return s; -} - -DnsServer* dns_server_unref(DnsServer *s) { - if (!s) - return NULL; - - assert(s->n_ref > 0); - s->n_ref --; - - if (s->n_ref > 0) - return NULL; - - free(s->server_string); - free(s); - return NULL; -} - -void dns_server_unlink(DnsServer *s) { - assert(s); - assert(s->manager); - - /* This removes the specified server from the linked list of - * servers, but any server might still stay around if it has - * refs, for example from an ongoing transaction. */ - - if (!s->linked) - return; - - switch (s->type) { - - case DNS_SERVER_LINK: - assert(s->link); - assert(s->link->n_dns_servers > 0); - LIST_REMOVE(servers, s->link->dns_servers, s); - break; - - case DNS_SERVER_SYSTEM: - assert(s->manager->n_dns_servers > 0); - LIST_REMOVE(servers, s->manager->dns_servers, s); - s->manager->n_dns_servers--; - break; - - case DNS_SERVER_FALLBACK: - assert(s->manager->n_dns_servers > 0); - LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); - s->manager->n_dns_servers--; - break; - } - - s->linked = false; - - if (s->link && s->link->current_dns_server == s) - link_set_dns_server(s->link, NULL); - - if (s->manager->current_dns_server == s) - manager_set_dns_server(s->manager, NULL); - - dns_server_unref(s); -} - -void dns_server_move_back_and_unmark(DnsServer *s) { - DnsServer *tail; - - assert(s); - - if (!s->marked) - return; - - s->marked = false; - - if (!s->linked || !s->servers_next) - return; - - /* Move us to the end of the list, so that the order is - * strictly kept, if we are not at the end anyway. */ - - switch (s->type) { - - case DNS_SERVER_LINK: - assert(s->link); - LIST_FIND_TAIL(servers, s, tail); - LIST_REMOVE(servers, s->link->dns_servers, s); - LIST_INSERT_AFTER(servers, s->link->dns_servers, tail, s); - break; - - case DNS_SERVER_SYSTEM: - LIST_FIND_TAIL(servers, s, tail); - LIST_REMOVE(servers, s->manager->dns_servers, s); - LIST_INSERT_AFTER(servers, s->manager->dns_servers, tail, s); - break; - - case DNS_SERVER_FALLBACK: - LIST_FIND_TAIL(servers, s, tail); - LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); - LIST_INSERT_AFTER(servers, s->manager->fallback_dns_servers, tail, s); - break; - - default: - assert_not_reached("Unknown server type"); - } -} - -static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - if (s->verified_feature_level > level) - return; - - if (s->verified_feature_level != level) { - log_debug("Verified we get a response at feature level %s from DNS server %s.", - dns_server_feature_level_to_string(level), - dns_server_string(s)); - s->verified_feature_level = level; - } - - assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); -} - -void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size) { - assert(s); - - if (protocol == IPPROTO_UDP) { - if (s->possible_feature_level == level) - s->n_failed_udp = 0; - - /* If the RRSIG data is missing, then we can only validate EDNS0 at max */ - if (s->packet_rrsig_missing && level >= DNS_SERVER_FEATURE_LEVEL_DO) - level = DNS_SERVER_FEATURE_LEVEL_DO - 1; - - /* If the OPT RR got lost, then we can only validate UDP at max */ - if (s->packet_bad_opt && level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) - level = DNS_SERVER_FEATURE_LEVEL_EDNS0 - 1; - - /* Even if we successfully receive a reply to a request announcing support for large packets, - that does not mean we can necessarily receive large packets. */ - if (level == DNS_SERVER_FEATURE_LEVEL_LARGE) - level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1; - - } else if (protocol == IPPROTO_TCP) { - - if (s->possible_feature_level == level) - s->n_failed_tcp = 0; - - /* Successful TCP connections are only useful to verify the TCP feature level. */ - level = DNS_SERVER_FEATURE_LEVEL_TCP; - } - - dns_server_verified(s, level); - - /* Remember the size of the largest UDP packet we received from a server, - we know that we can always announce support for packets with at least - this size. */ - if (protocol == IPPROTO_UDP && s->received_udp_packet_max < size) - s->received_udp_packet_max = size; - - if (s->max_rtt < rtt) { - s->max_rtt = rtt; - s->resend_timeout = CLAMP(s->max_rtt * 2, DNS_TIMEOUT_MIN_USEC, DNS_TIMEOUT_MAX_USEC); - } -} - -void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec) { - assert(s); - assert(s->manager); - - if (s->possible_feature_level == level) { - if (protocol == IPPROTO_UDP) - s->n_failed_udp ++; - else if (protocol == IPPROTO_TCP) - s->n_failed_tcp ++; - } - - if (s->resend_timeout > usec) - return; - - 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); - - /* Invoked whenever we get a packet with TC bit set. */ - - if (s->possible_feature_level != level) - return; - - s->packet_truncated = true; -} - -void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - if (level < DNS_SERVER_FEATURE_LEVEL_DO) - return; - - /* If the RRSIG RRs are missing, we have to downgrade what we previously verified */ - if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) - s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_DO-1; - - s->packet_rrsig_missing = true; -} - -void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level) { - assert(s); - - if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) - return; - - /* If the OPT RR got lost, we have to downgrade what we previously verified */ - if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) - s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0-1; - - s->packet_bad_opt = true; -} - -static bool dns_server_grace_period_expired(DnsServer *s) { - usec_t ts; - - assert(s); - assert(s->manager); - - if (s->verified_usec == 0) - return false; - - assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - if (s->verified_usec + s->features_grace_period_usec > ts) - return false; - - s->features_grace_period_usec = MIN(s->features_grace_period_usec * 2, DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC); - - 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); - - if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST && - dns_server_grace_period_expired(s)) { - - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; - - dns_server_reset_counters(s); - - s->packet_bad_opt = false; - s->packet_rrsig_missing = false; - - log_info("Grace period over, resuming full feature set (%s) for DNS server %s.", - dns_server_feature_level_to_string(s->possible_feature_level), - dns_server_string(s)); - - } else if (s->possible_feature_level <= s->verified_feature_level) - s->possible_feature_level = s->verified_feature_level; - else { - DnsServerFeatureLevel p = s->possible_feature_level; - - if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->possible_feature_level == DNS_SERVER_FEATURE_LEVEL_TCP) { - - /* We are at the TCP (lowest) level, and we tried a couple of TCP connections, and it didn't - * work. Upgrade back to UDP again. */ - log_debug("Reached maximum number of failed TCP connection attempts, trying UDP again..."); - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; - - } else if (s->packet_bad_opt && - s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) { - - /* A reply to one of our EDNS0 queries didn't carry a valid OPT RR, then downgrade to below - * EDNS0 levels. After all, some records generate different responses with and without OPT RR - * in the request. Example: - * https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ - - log_debug("Server doesn't support EDNS(0) properly, downgrading feature level..."); - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; - - } else if (s->packet_rrsig_missing && - s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) { - - /* RRSIG data was missing on a EDNS0 packet with DO bit set. This means the server doesn't - * augment responses with DNSSEC RRs. If so, let's better not ask the server for it anymore, - * after all some servers generate different replies depending if an OPT RR is in the query or - * not. */ - - log_debug("Detected server responses lack RRSIG records, downgrading feature level..."); - s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0; - - } else if (s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && - s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) { - - /* We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If the - * packets are lost, maybe the server cannot parse them, hence downgrading sounds like a good - * idea. We might downgrade all the way down to TCP this way. */ - - 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) { - - /* We got too many TCP connection failures in a row, we had at least one truncated packet, and - * are on a feature level above UDP. By downgrading things and getting rid of DNSSEC or EDNS0 - * data we hope to make the packet smaller, so that it still works via UDP given that TCP - * appears not to be a fallback. Note that if we are already at the lowest UDP level, we don't - * go further down, since that's TCP, and TCP failed too often after all. */ - - log_debug("Got too many failed TCP connection failures and truncated UDP packets, downgrading feature level..."); - s->possible_feature_level--; - } - - if (p != s->possible_feature_level) { - - /* We changed the feature level, reset the counting */ - dns_server_reset_counters(s); - - log_warning("Using degraded feature set (%s) for DNS server %s.", - dns_server_feature_level_to_string(s->possible_feature_level), - dns_server_string(s)); - } - } - - return s->possible_feature_level; -} - -int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level) { - size_t packet_size; - bool edns_do; - int r; - - assert(server); - assert(packet); - assert(packet->protocol == DNS_PROTOCOL_DNS); - - /* Fix the OPT field in the packet to match our current feature level. */ - - r = dns_packet_truncate_opt(packet); - if (r < 0) - return r; - - if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) - return 0; - - edns_do = level >= DNS_SERVER_FEATURE_LEVEL_DO; - - if (level >= DNS_SERVER_FEATURE_LEVEL_LARGE) - packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX; - else - packet_size = server->received_udp_packet_max; - - return dns_packet_append_opt(packet, packet_size, edns_do, NULL); -} - -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); - - return strna(server->server_string); -} - -bool dns_server_dnssec_supported(DnsServer *server) { - assert(server); - - /* Returns whether the server supports DNSSEC according to what we know about it */ - - if (server->possible_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) - return false; - - if (server->packet_bad_opt) - return false; - - if (server->packet_rrsig_missing) - return false; - - /* DNSSEC servers need to support TCP properly (see RFC5966), if they don't, we assume DNSSEC is borked too */ - if (server->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS) - return false; - - return true; -} - -void dns_server_warn_downgrade(DnsServer *server) { - assert(server); - - if (server->warned_downgrade) - return; - - log_struct(LOG_NOTICE, - LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_DOWNGRADE), - LOG_MESSAGE("Server %s does not support DNSSEC, downgrading to non-DNSSEC mode.", dns_server_string(server)), - "DNS_SERVER=%s", dns_server_string(server), - "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(server->possible_feature_level), - NULL); - - server->warned_downgrade = true; -} - -static void dns_server_hash_func(const void *p, struct siphash *state) { - const DnsServer *s = p; - - assert(s); - - siphash24_compress(&s->family, sizeof(s->family), state); - siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state); -} - -static int dns_server_compare_func(const void *a, const void *b) { - const DnsServer *x = a, *y = b; - - 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)); -} - -const struct hash_ops dns_server_hash_ops = { - .hash = dns_server_hash_func, - .compare = dns_server_compare_func -}; - -void dns_server_unlink_all(DnsServer *first) { - DnsServer *next; - - if (!first) - return; - - next = first->servers_next; - dns_server_unlink(first); - - dns_server_unlink_all(next); -} - -void dns_server_unlink_marked(DnsServer *first) { - DnsServer *next; - - if (!first) - return; - - next = first->servers_next; - - if (first->marked) - dns_server_unlink(first); - - dns_server_unlink_marked(next); -} - -void dns_server_mark_all(DnsServer *first) { - if (!first) - return; - - first->marked = true; - dns_server_mark_all(first->servers_next); -} - -DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr) { - DnsServer *s; - - LIST_FOREACH(servers, s, first) - if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0) - return s; - - return NULL; -} - -DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t) { - assert(m); - - switch (t) { - - case DNS_SERVER_SYSTEM: - return m->dns_servers; - - case DNS_SERVER_FALLBACK: - return m->fallback_dns_servers; - - default: - return NULL; - } -} - -DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { - assert(m); - - if (m->current_dns_server == s) - return s; - - if (s) - log_info("Switching to %s DNS server %s.", - dns_server_type_to_string(s->type), - dns_server_string(s)); - - dns_server_unref(m->current_dns_server); - m->current_dns_server = dns_server_ref(s); - - if (m->unicast_scope) - dns_cache_flush(&m->unicast_scope->cache); - - return s; -} - -DnsServer *manager_get_dns_server(Manager *m) { - Link *l; - assert(m); - - /* Try to read updates resolv.conf */ - manager_read_resolv_conf(m); - - /* If no DNS server was chosen so far, pick the first one */ - if (!m->current_dns_server) - manager_set_dns_server(m, m->dns_servers); - - if (!m->current_dns_server) { - bool found = false; - Iterator i; - - /* No DNS servers configured, let's see if there are - * any on any links. If not, we use the fallback - * servers */ - - HASHMAP_FOREACH(l, m->links, i) - if (l->dns_servers) { - found = true; - break; - } - - if (!found) - manager_set_dns_server(m, m->fallback_dns_servers); - } - - return m->current_dns_server; -} - -void manager_next_dns_server(Manager *m) { - assert(m); - - /* If there's currently no DNS server set, then the next - * manager_get_dns_server() will find one */ - if (!m->current_dns_server) - return; - - /* Change to the next one, but make sure to follow the linked - * list only if the server is still linked. */ - if (m->current_dns_server->linked && m->current_dns_server->servers_next) { - manager_set_dns_server(m, m->current_dns_server->servers_next); - return; - } - - /* If there was no next one, then start from the beginning of - * the list */ - if (m->current_dns_server->type == DNS_SERVER_FALLBACK) - manager_set_dns_server(m, m->fallback_dns_servers); - else - manager_set_dns_server(m, m->dns_servers); -} - -static const char* const dns_server_type_table[_DNS_SERVER_TYPE_MAX] = { - [DNS_SERVER_SYSTEM] = "system", - [DNS_SERVER_FALLBACK] = "fallback", - [DNS_SERVER_LINK] = "link", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_server_type, DnsServerType); - -static const char* const dns_server_feature_level_table[_DNS_SERVER_FEATURE_LEVEL_MAX] = { - [DNS_SERVER_FEATURE_LEVEL_TCP] = "TCP", - [DNS_SERVER_FEATURE_LEVEL_UDP] = "UDP", - [DNS_SERVER_FEATURE_LEVEL_EDNS0] = "UDP+EDNS0", - [DNS_SERVER_FEATURE_LEVEL_DO] = "UDP+EDNS0+DO", - [DNS_SERVER_FEATURE_LEVEL_LARGE] = "UDP+EDNS0+DO+LARGE", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_server_feature_level, DnsServerFeatureLevel); diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h deleted file mode 100644 index 9f4a69c37a..0000000000 --- a/src/resolve/resolved-dns-server.h +++ /dev/null @@ -1,143 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "in-addr-util.h" - -typedef struct DnsServer DnsServer; - -typedef enum DnsServerType { - DNS_SERVER_SYSTEM, - DNS_SERVER_FALLBACK, - DNS_SERVER_LINK, -} DnsServerType; -#define _DNS_SERVER_TYPE_MAX (DNS_SERVER_LINK + 1) - -const char* dns_server_type_to_string(DnsServerType i) _const_; -DnsServerType dns_server_type_from_string(const char *s) _pure_; - -typedef enum DnsServerFeatureLevel { - DNS_SERVER_FEATURE_LEVEL_TCP, - DNS_SERVER_FEATURE_LEVEL_UDP, - DNS_SERVER_FEATURE_LEVEL_EDNS0, - DNS_SERVER_FEATURE_LEVEL_DO, - DNS_SERVER_FEATURE_LEVEL_LARGE, - _DNS_SERVER_FEATURE_LEVEL_MAX, - _DNS_SERVER_FEATURE_LEVEL_INVALID = -1 -} DnsServerFeatureLevel; - -#define DNS_SERVER_FEATURE_LEVEL_WORST 0 -#define DNS_SERVER_FEATURE_LEVEL_BEST (_DNS_SERVER_FEATURE_LEVEL_MAX - 1) - -const char* dns_server_feature_level_to_string(int i) _const_; -int dns_server_feature_level_from_string(const char *s) _pure_; - -#include "resolved-link.h" -#include "resolved-manager.h" - -struct DnsServer { - Manager *manager; - - unsigned n_ref; - - DnsServerType type; - Link *link; - - int family; - union in_addr_union address; - - char *server_string; - - usec_t resend_timeout; - usec_t max_rtt; - - DnsServerFeatureLevel verified_feature_level; - DnsServerFeatureLevel possible_feature_level; - - size_t received_udp_packet_max; - - 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; - - usec_t verified_usec; - usec_t features_grace_period_usec; - - /* Whether we already warned about downgrading to non-DNSSEC mode for this server */ - bool warned_downgrade:1; - - /* Used when GC'ing old DNS servers when configuration changes. */ - bool marked:1; - - /* If linked is set, then this server appears in the servers linked list */ - bool linked:1; - LIST_FIELDS(DnsServer, servers); -}; - -int dns_server_new( - Manager *m, - DnsServer **ret, - DnsServerType type, - Link *link, - int family, - const union in_addr_union *address); - -DnsServer* dns_server_ref(DnsServer *s); -DnsServer* dns_server_unref(DnsServer *s); - -void dns_server_unlink(DnsServer *s); -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); - -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); - -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); - -void dns_server_unlink_all(DnsServer *first); -void dns_server_unlink_marked(DnsServer *first); -void dns_server_mark_all(DnsServer *first); - -DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t); - -DnsServer *manager_set_dns_server(Manager *m, DnsServer *s); -DnsServer *manager_get_dns_server(Manager *m); -void manager_next_dns_server(Manager *m); - -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 deleted file mode 100644 index a1040aeff4..0000000000 --- a/src/resolve/resolved-dns-stream.c +++ /dev/null @@ -1,403 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "missing.h" -#include "resolved-dns-stream.h" - -#define DNS_STREAM_TIMEOUT_USEC (10 * USEC_PER_SEC) -#define DNS_STREAMS_MAX 128 - -static void dns_stream_stop(DnsStream *s) { - assert(s); - - s->io_event_source = sd_event_source_unref(s->io_event_source); - s->timeout_event_source = sd_event_source_unref(s->timeout_event_source); - s->fd = safe_close(s->fd); -} - -static int dns_stream_update_io(DnsStream *s) { - int f = 0; - - assert(s); - - if (s->write_packet && s->n_written < sizeof(s->write_size) + s->write_packet->size) - f |= EPOLLOUT; - if (!s->read_packet || s->n_read < sizeof(s->read_size) + s->read_packet->size) - f |= EPOLLIN; - - return sd_event_source_set_io_events(s->io_event_source, f); -} - -static int dns_stream_complete(DnsStream *s, int error) { - assert(s); - - dns_stream_stop(s); - - if (s->complete) - s->complete(s, error); - else - dns_stream_free(s); - - return 0; -} - -static int dns_stream_identify(DnsStream *s) { - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) - + EXTRA_CMSG_SPACE /* kernel appears to require extra space */]; - } control; - struct msghdr mh = {}; - struct cmsghdr *cmsg; - socklen_t sl; - int r; - - assert(s); - - if (s->identified) - return 0; - - /* Query the local side */ - s->local_salen = sizeof(s->local); - r = getsockname(s->fd, &s->local.sa, &s->local_salen); - if (r < 0) - return -errno; - if (s->local.sa.sa_family == AF_INET6 && s->ifindex <= 0) - s->ifindex = s->local.in6.sin6_scope_id; - - /* Query the remote side */ - s->peer_salen = sizeof(s->peer); - r = getpeername(s->fd, &s->peer.sa, &s->peer_salen); - if (r < 0) - return -errno; - if (s->peer.sa.sa_family == AF_INET6 && s->ifindex <= 0) - s->ifindex = s->peer.in6.sin6_scope_id; - - /* Check consistency */ - assert(s->peer.sa.sa_family == s->local.sa.sa_family); - assert(IN_SET(s->peer.sa.sa_family, AF_INET, AF_INET6)); - - /* Query connection meta information */ - sl = sizeof(control); - if (s->peer.sa.sa_family == AF_INET) { - r = getsockopt(s->fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl); - if (r < 0) - return -errno; - } else if (s->peer.sa.sa_family == AF_INET6) { - - r = getsockopt(s->fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, &control, &sl); - if (r < 0) - return -errno; - } else - return -EAFNOSUPPORT; - - mh.msg_control = &control; - mh.msg_controllen = sl; - - CMSG_FOREACH(cmsg, &mh) { - - if (cmsg->cmsg_level == IPPROTO_IPV6) { - assert(s->peer.sa.sa_family == AF_INET6); - - switch (cmsg->cmsg_type) { - - case IPV6_PKTINFO: { - struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); - - if (s->ifindex <= 0) - s->ifindex = i->ipi6_ifindex; - break; - } - - case IPV6_HOPLIMIT: - s->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - - } else if (cmsg->cmsg_level == IPPROTO_IP) { - assert(s->peer.sa.sa_family == AF_INET); - - switch (cmsg->cmsg_type) { - - case IP_PKTINFO: { - struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); - - if (s->ifindex <= 0) - s->ifindex = i->ipi_ifindex; - break; - } - - case IP_TTL: - s->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - } - } - - /* The Linux kernel sets the interface index to the loopback - * device if the connection came from the local host since it - * avoids the routing table in such a case. Let's unset the - * interface index in such a case. */ - if (s->ifindex == LOOPBACK_IFINDEX) - s->ifindex = 0; - - /* If we don't know the interface index still, we look for the - * first local interface with a matching address. Yuck! */ - if (s->ifindex <= 0) - s->ifindex = manager_find_ifindex(s->manager, s->local.sa.sa_family, s->local.sa.sa_family == AF_INET ? (union in_addr_union*) &s->local.in.sin_addr : (union in_addr_union*) &s->local.in6.sin6_addr); - - if (s->protocol == DNS_PROTOCOL_LLMNR && s->ifindex > 0) { - uint32_t ifindex = htobe32(s->ifindex); - - /* Make sure all packets for this connection are sent on the same interface */ - if (s->local.sa.sa_family == AF_INET) { - r = setsockopt(s->fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - log_debug_errno(errno, "Failed to invoke IP_UNICAST_IF: %m"); - } else if (s->local.sa.sa_family == AF_INET6) { - r = setsockopt(s->fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); - if (r < 0) - log_debug_errno(errno, "Failed to invoke IPV6_UNICAST_IF: %m"); - } - } - - s->identified = true; - - return 0; -} - -static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) { - DnsStream *s = userdata; - - assert(s); - - return dns_stream_complete(s, ETIMEDOUT); -} - -static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - DnsStream *s = userdata; - int r; - - assert(s); - - r = dns_stream_identify(s); - if (r < 0) - return dns_stream_complete(s, -r); - - if ((revents & EPOLLOUT) && - s->write_packet && - s->n_written < sizeof(s->write_size) + s->write_packet->size) { - - struct iovec iov[2]; - ssize_t ss; - - iov[0].iov_base = &s->write_size; - iov[0].iov_len = sizeof(s->write_size); - iov[1].iov_base = DNS_PACKET_DATA(s->write_packet); - iov[1].iov_len = s->write_packet->size; - - IOVEC_INCREMENT(iov, 2, s->n_written); - - ss = writev(fd, iov, 2); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return dns_stream_complete(s, errno); - } else - s->n_written += ss; - - /* Are we done? If so, disable the event source for EPOLLOUT */ - if (s->n_written >= sizeof(s->write_size) + s->write_packet->size) { - r = dns_stream_update_io(s); - if (r < 0) - return dns_stream_complete(s, -r); - } - } - - if ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) && - (!s->read_packet || - s->n_read < sizeof(s->read_size) + s->read_packet->size)) { - - if (s->n_read < sizeof(s->read_size)) { - ssize_t ss; - - ss = read(fd, (uint8_t*) &s->read_size + s->n_read, sizeof(s->read_size) - s->n_read); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return dns_stream_complete(s, errno); - } else if (ss == 0) - return dns_stream_complete(s, ECONNRESET); - else - s->n_read += ss; - } - - if (s->n_read >= sizeof(s->read_size)) { - - if (be16toh(s->read_size) < DNS_PACKET_HEADER_SIZE) - return dns_stream_complete(s, EBADMSG); - - if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size)) { - ssize_t ss; - - if (!s->read_packet) { - r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size)); - if (r < 0) - return dns_stream_complete(s, -r); - - s->read_packet->size = be16toh(s->read_size); - s->read_packet->ipproto = IPPROTO_TCP; - s->read_packet->family = s->peer.sa.sa_family; - s->read_packet->ttl = s->ttl; - s->read_packet->ifindex = s->ifindex; - - if (s->read_packet->family == AF_INET) { - s->read_packet->sender.in = s->peer.in.sin_addr; - s->read_packet->sender_port = be16toh(s->peer.in.sin_port); - s->read_packet->destination.in = s->local.in.sin_addr; - s->read_packet->destination_port = be16toh(s->local.in.sin_port); - } else { - assert(s->read_packet->family == AF_INET6); - s->read_packet->sender.in6 = s->peer.in6.sin6_addr; - s->read_packet->sender_port = be16toh(s->peer.in6.sin6_port); - s->read_packet->destination.in6 = s->local.in6.sin6_addr; - s->read_packet->destination_port = be16toh(s->local.in6.sin6_port); - - if (s->read_packet->ifindex == 0) - s->read_packet->ifindex = s->peer.in6.sin6_scope_id; - if (s->read_packet->ifindex == 0) - s->read_packet->ifindex = s->local.in6.sin6_scope_id; - } - } - - ss = read(fd, - (uint8_t*) DNS_PACKET_DATA(s->read_packet) + s->n_read - sizeof(s->read_size), - sizeof(s->read_size) + be16toh(s->read_size) - s->n_read); - if (ss < 0) { - if (errno != EINTR && errno != EAGAIN) - return dns_stream_complete(s, errno); - } else if (ss == 0) - return dns_stream_complete(s, ECONNRESET); - else - s->n_read += ss; - } - - /* Are we done? If so, disable the event source for EPOLLIN */ - if (s->n_read >= sizeof(s->read_size) + be16toh(s->read_size)) { - r = dns_stream_update_io(s); - if (r < 0) - return dns_stream_complete(s, -r); - - /* If there's a packet handler - * installed, call that. Note that - * this is optional... */ - if (s->on_packet) - return s->on_packet(s); - } - } - } - - if ((s->write_packet && s->n_written >= sizeof(s->write_size) + s->write_packet->size) && - (s->read_packet && s->n_read >= sizeof(s->read_size) + s->read_packet->size)) - return dns_stream_complete(s, 0); - - return 0; -} - -DnsStream *dns_stream_free(DnsStream *s) { - if (!s) - return NULL; - - dns_stream_stop(s); - - if (s->manager) { - LIST_REMOVE(streams, s->manager->dns_streams, s); - s->manager->n_dns_streams--; - } - - dns_packet_unref(s->write_packet); - dns_packet_unref(s->read_packet); - - free(s); - - return 0; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free); - -int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { - _cleanup_(dns_stream_freep) DnsStream *s = NULL; - int r; - - assert(m); - assert(fd >= 0); - - if (m->n_dns_streams > DNS_STREAMS_MAX) - return -EBUSY; - - s = new0(DnsStream, 1); - if (!s) - return -ENOMEM; - - s->fd = -1; - s->protocol = protocol; - - r = sd_event_add_io(m->event, &s->io_event_source, fd, EPOLLIN, on_stream_io, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->io_event_source, "dns-stream-io"); - - r = sd_event_add_time( - m->event, - &s->timeout_event_source, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + DNS_STREAM_TIMEOUT_USEC, 0, - on_stream_timeout, s); - if (r < 0) - return r; - - (void) sd_event_source_set_description(s->timeout_event_source, "dns-stream-timeout"); - - LIST_PREPEND(streams, m->dns_streams, s); - s->manager = m; - s->fd = fd; - m->n_dns_streams++; - - *ret = s; - s = NULL; - - return 0; -} - -int dns_stream_write_packet(DnsStream *s, DnsPacket *p) { - assert(s); - - if (s->write_packet) - return -EBUSY; - - s->write_packet = dns_packet_ref(p); - s->write_size = htobe16(p->size); - s->n_written = 0; - - return dns_stream_update_io(s); -} diff --git a/src/resolve/resolved-dns-stream.h b/src/resolve/resolved-dns-stream.h deleted file mode 100644 index 5ccc842249..0000000000 --- a/src/resolve/resolved-dns-stream.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "socket-util.h" - -typedef struct DnsStream DnsStream; - -#include "resolved-dns-packet.h" -#include "resolved-dns-transaction.h" - -struct DnsStream { - Manager *manager; - - DnsProtocol protocol; - - int fd; - union sockaddr_union peer; - socklen_t peer_salen; - union sockaddr_union local; - socklen_t local_salen; - int ifindex; - uint32_t ttl; - bool identified; - - sd_event_source *io_event_source; - sd_event_source *timeout_event_source; - - be16_t write_size, read_size; - DnsPacket *write_packet, *read_packet; - size_t n_written, n_read; - - int (*on_packet)(DnsStream *s); - int (*complete)(DnsStream *s, int error); - - DnsTransaction *transaction; - - LIST_FIELDS(DnsStream, streams); -}; - -int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd); -DnsStream *dns_stream_free(DnsStream *s); - -int dns_stream_write_packet(DnsStream *s, DnsPacket *p); diff --git a/src/resolve/resolved-dns-synthesize.c b/src/resolve/resolved-dns-synthesize.c deleted file mode 100644 index f4a43dee8c..0000000000 --- a/src/resolve/resolved-dns-synthesize.c +++ /dev/null @@ -1,413 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "alloc-util.h" -#include "hostname-util.h" -#include "local-addresses.h" -#include "resolved-dns-synthesize.h" - -int dns_synthesize_ifindex(int ifindex) { - - /* When the caller asked for resolving on a specific - * interface, we synthesize the answer for that - * interface. However, if nothing specific was claimed and we - * only return localhost RRs, we synthesize the answer for - * localhost. */ - - if (ifindex > 0) - return ifindex; - - return LOOPBACK_IFINDEX; -} - -int dns_synthesize_family(uint64_t flags) { - - /* Picks an address family depending on set flags. This is - * purely for synthesized answers, where the family we return - * for the reply should match what was requested in the - * question, even though we are synthesizing the answer - * here. */ - - if (!(flags & SD_RESOLVED_DNS)) { - if (flags & (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_MDNS_IPV4)) - return AF_INET; - if (flags & (SD_RESOLVED_LLMNR_IPV6|SD_RESOLVED_MDNS_IPV6)) - return AF_INET6; - } - - return AF_UNSPEC; -} - -DnsProtocol dns_synthesize_protocol(uint64_t flags) { - - /* Similar as dns_synthesize_family() but does this for the - * protocol. If resolving via DNS was requested, we claim it - * was DNS. Similar, if nothing specific was - * requested. However, if only resolving via LLMNR was - * requested we return that. */ - - if (flags & SD_RESOLVED_DNS) - return DNS_PROTOCOL_DNS; - if (flags & SD_RESOLVED_LLMNR) - return DNS_PROTOCOL_LLMNR; - if (flags & SD_RESOLVED_MDNS) - return DNS_PROTOCOL_MDNS; - - return DNS_PROTOCOL_DNS; -} - -static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { - int r; - - assert(m); - assert(key); - assert(answer); - - r = dns_answer_reserve(answer, 2); - if (r < 0) - return r; - - if (IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_ANY)) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, DNS_RESOURCE_KEY_NAME(key)); - if (!rr) - return -ENOMEM; - - rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); - - r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, DNS_RESOURCE_KEY_NAME(key)); - if (!rr) - return -ENOMEM; - - rr->aaaa.in6_addr = in6addr_loopback; - - r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex, DnsAnswerFlags flags) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, from); - if (!rr) - return -ENOMEM; - - rr->ptr.name = strdup(to); - if (!rr->ptr.name) - return -ENOMEM; - - return dns_answer_add(*answer, rr, ifindex, flags); -} - -static int synthesize_localhost_ptr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { - int r; - - assert(m); - assert(key); - assert(answer); - - if (IN_SET(key->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) { - r = dns_answer_reserve(answer, 1); - if (r < 0) - return r; - - r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -static int answer_add_addresses_rr( - DnsAnswer **answer, - const char *name, - struct local_address *addresses, - unsigned n_addresses) { - - unsigned j; - int r; - - assert(answer); - assert(name); - - r = dns_answer_reserve(answer, n_addresses); - if (r < 0) - return r; - - for (j = 0; j < n_addresses; j++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - r = dns_resource_record_new_address(&rr, addresses[j].family, &addresses[j].address, name); - if (r < 0) - return r; - - r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -static int answer_add_addresses_ptr( - DnsAnswer **answer, - const char *name, - struct local_address *addresses, - unsigned n_addresses, - int af, const union in_addr_union *match) { - - unsigned j; - int r; - - assert(answer); - assert(name); - - for (j = 0; j < n_addresses; j++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - if (af != AF_UNSPEC) { - - if (addresses[j].family != af) - continue; - - if (match && !in_addr_equal(af, match, &addresses[j].address)) - continue; - } - - r = dns_answer_reserve(answer, 1); - if (r < 0) - return r; - - r = dns_resource_record_new_reverse(&rr, addresses[j].family, &addresses[j].address, name); - if (r < 0) - return r; - - r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -static int synthesize_system_hostname_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n = 0, af; - - assert(m); - assert(key); - assert(answer); - - af = dns_type_to_af(key->type); - if (af >= 0) { - n = local_addresses(m->rtnl, ifindex, af, &addresses); - if (n < 0) - return n; - - if (n == 0) { - struct local_address buffer[2]; - - /* If we have no local addresses then use ::1 - * and 127.0.0.2 as local ones. */ - - if (af == AF_INET || af == AF_UNSPEC) - buffer[n++] = (struct local_address) { - .family = AF_INET, - .ifindex = dns_synthesize_ifindex(ifindex), - .address.in.s_addr = htobe32(0x7F000002), - }; - - if (af == AF_INET6 || af == AF_UNSPEC) - buffer[n++] = (struct local_address) { - .family = AF_INET6, - .ifindex = dns_synthesize_ifindex(ifindex), - .address.in6 = in6addr_loopback, - }; - - return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), buffer, n); - } - } - - return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); -} - -static int synthesize_system_hostname_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n, r; - - assert(m); - assert(address); - assert(answer); - - if (af == AF_INET && address->in.s_addr == htobe32(0x7F000002)) { - - /* Always map the IPv4 address 127.0.0.2 to the local - * hostname, in addition to "localhost": */ - - r = dns_answer_reserve(answer, 3); - if (r < 0) - return r; - - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->llmnr_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->mdns_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - return 0; - } - - n = local_addresses(m->rtnl, ifindex, af, &addresses); - if (n < 0) - return n; - - r = answer_add_addresses_ptr(answer, m->llmnr_hostname, addresses, n, af, address); - if (r < 0) - return r; - - return answer_add_addresses_ptr(answer, m->mdns_hostname, addresses, n, af, address); -} - -static int synthesize_gateway_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n = 0, af; - - assert(m); - assert(key); - assert(answer); - - af = dns_type_to_af(key->type); - if (af >= 0) { - n = local_gateways(m->rtnl, ifindex, af, &addresses); - if (n < 0) - return n; - } - - return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); -} - -static int synthesize_gateway_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) { - _cleanup_free_ struct local_address *addresses = NULL; - int n; - - assert(m); - assert(address); - assert(answer); - - n = local_gateways(m->rtnl, ifindex, af, &addresses); - if (n < 0) - return n; - - return answer_add_addresses_ptr(answer, "gateway", addresses, n, af, address); -} - -int dns_synthesize_answer( - Manager *m, - DnsQuestion *q, - int ifindex, - DnsAnswer **ret) { - - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnsResourceKey *key; - bool found = false; - int r; - - assert(m); - assert(q); - - DNS_QUESTION_FOREACH(key, q) { - union in_addr_union address; - const char *name; - int af; - - if (key->class != DNS_CLASS_IN && - key->class != DNS_CLASS_ANY) - continue; - - name = DNS_RESOURCE_KEY_NAME(key); - - if (is_localhost(name)) { - - r = synthesize_localhost_rr(m, key, ifindex, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize localhost RRs: %m"); - - } else if (manager_is_own_hostname(m, name)) { - - r = synthesize_system_hostname_rr(m, key, ifindex, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize system hostname RRs: %m"); - - } else if (is_gateway_hostname(name)) { - - r = synthesize_gateway_rr(m, key, ifindex, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize gateway RRs: %m"); - - } else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0) || - dns_name_equal(name, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) { - - r = synthesize_localhost_ptr(m, key, ifindex, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m"); - - } else if (dns_name_address(name, &af, &address) > 0) { - - r = synthesize_system_hostname_ptr(m, af, &address, ifindex, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize system hostname PTR RR: %m"); - - r = synthesize_gateway_ptr(m, af, &address, ifindex, &answer); - if (r < 0) - return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m"); - } else - continue; - - found = true; - } - - r = found; - - if (ret) { - *ret = answer; - answer = NULL; - } - - return r; -} diff --git a/src/resolve/resolved-dns-synthesize.h b/src/resolve/resolved-dns-synthesize.h deleted file mode 100644 index 5d829bb2e7..0000000000 --- a/src/resolve/resolved-dns-synthesize.h +++ /dev/null @@ -1,30 +0,0 @@ -#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 . -***/ - -#include "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-manager.h" - -int dns_synthesize_ifindex(int ifindex); -int dns_synthesize_family(uint64_t flags); -DnsProtocol dns_synthesize_protocol(uint64_t flags); - -int dns_synthesize_answer(Manager *m, DnsQuestion *q, int ifindex, DnsAnswer **ret); diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c deleted file mode 100644 index d48fdd1281..0000000000 --- a/src/resolve/resolved-dns-transaction.c +++ /dev/null @@ -1,3026 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "af-list.h" -#include "alloc-util.h" -#include "dns-domain.h" -#include "errno-list.h" -#include "fd-util.h" -#include "random-util.h" -#include "resolved-dns-cache.h" -#include "resolved-dns-transaction.h" -#include "resolved-llmnr.h" -#include "string-table.h" - -#define TRANSACTIONS_MAX 4096 - -static void dns_transaction_reset_answer(DnsTransaction *t) { - assert(t); - - t->received = dns_packet_unref(t->received); - t->answer = dns_answer_unref(t->answer); - t->answer_rcode = 0; - t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; - t->answer_authenticated = false; - t->answer_nsec_ttl = (uint32_t) -1; - t->answer_errno = 0; -} - -static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) { - DnsTransaction *z; - - assert(t); - - while ((z = set_steal_first(t->dnssec_transactions))) { - set_remove(z->notify_transactions, t); - dns_transaction_gc(z); - } -} - -static void dns_transaction_close_connection(DnsTransaction *t) { - assert(t); - - t->stream = dns_stream_free(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); -} - -static void dns_transaction_stop_timeout(DnsTransaction *t) { - assert(t); - - t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); -} - -DnsTransaction* dns_transaction_free(DnsTransaction *t) { - DnsQueryCandidate *c; - DnsZoneItem *i; - DnsTransaction *z; - - if (!t) - return NULL; - - log_debug("Freeing transaction %" PRIu16 ".", t->id); - - dns_transaction_close_connection(t); - dns_transaction_stop_timeout(t); - - dns_packet_unref(t->sent); - dns_transaction_reset_answer(t); - - dns_server_unref(t->server); - - if (t->scope) { - hashmap_remove_value(t->scope->transactions_by_key, t->key, t); - LIST_REMOVE(transactions_by_scope, t->scope->transactions, t); - - if (t->id != 0) - hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); - } - - while ((c = set_steal_first(t->notify_query_candidates))) - set_remove(c->transactions, t); - set_free(t->notify_query_candidates); - - while ((i = set_steal_first(t->notify_zone_items))) - i->probe_transaction = NULL; - set_free(t->notify_zone_items); - - while ((z = set_steal_first(t->notify_transactions))) - set_remove(z->dnssec_transactions, t); - set_free(t->notify_transactions); - - dns_transaction_flush_dnssec_transactions(t); - set_free(t->dnssec_transactions); - - dns_answer_unref(t->validated_keys); - dns_resource_key_unref(t->key); - free(t->key_string); - - free(t); - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free); - -bool dns_transaction_gc(DnsTransaction *t) { - assert(t); - - if (t->block_gc > 0) - return true; - - if (set_isempty(t->notify_query_candidates) && - set_isempty(t->notify_zone_items) && - set_isempty(t->notify_transactions)) { - dns_transaction_free(t); - return false; - } - - return true; -} - -static uint16_t pick_new_id(Manager *m) { - uint16_t new_id; - - /* Find a fresh, unused transaction id. Note that this loop is bounded because there's a limit on the number of - * transactions, and it's much lower than the space of IDs. */ - - assert_cc(TRANSACTIONS_MAX < 0xFFFF); - - do - random_bytes(&new_id, sizeof(new_id)); - while (new_id == 0 || - hashmap_get(m->dns_transactions, UINT_TO_PTR(new_id))); - - return new_id; -} - -int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) { - _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL; - int r; - - assert(ret); - assert(s); - assert(key); - - /* Don't allow looking up invalid or pseudo RRs */ - if (!dns_type_is_valid_query(key->type)) - return -EINVAL; - if (dns_type_is_obsolete(key->type)) - return -EOPNOTSUPP; - - /* We only support the IN class */ - if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY) - return -EOPNOTSUPP; - - if (hashmap_size(s->manager->dns_transactions) >= TRANSACTIONS_MAX) - return -EBUSY; - - r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&s->transactions_by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - t = new0(DnsTransaction, 1); - if (!t) - return -ENOMEM; - - t->dns_udp_fd = -1; - t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; - t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - t->answer_nsec_ttl = (uint32_t) -1; - t->key = dns_resource_key_ref(key); - t->current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; - - t->id = pick_new_id(s->manager); - - r = hashmap_put(s->manager->dns_transactions, UINT_TO_PTR(t->id), t); - if (r < 0) { - t->id = 0; - return r; - } - - r = hashmap_replace(s->transactions_by_key, t->key, t); - if (r < 0) { - hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id)); - return r; - } - - LIST_PREPEND(transactions_by_scope, s->transactions, t); - t->scope = s; - - s->manager->n_transactions_total ++; - - if (ret) - *ret = t; - - t = NULL; - - return 0; -} - -static void dns_transaction_shuffle_id(DnsTransaction *t) { - uint16_t new_id; - assert(t); - - /* Pick a new ID for this transaction. */ - - new_id = pick_new_id(t->scope->manager); - assert_se(hashmap_remove_and_put(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id), UINT_TO_PTR(new_id), t) >= 0); - - log_debug("Transaction %" PRIu16 " is now %" PRIu16 ".", t->id, new_id); - t->id = new_id; - - /* Make sure we generate a new packet with the new ID */ - t->sent = dns_packet_unref(t->sent); -} - -static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { - _cleanup_free_ char *pretty = NULL; - DnsZoneItem *z; - - assert(t); - assert(p); - - if (manager_our_packet(t->scope->manager, p) != 0) - return; - - in_addr_to_string(p->family, &p->sender, &pretty); - - log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s got tentative packet from %s.", - t->id, - dns_transaction_key_string(t), - dns_protocol_to_string(t->scope->protocol), - t->scope->link ? t->scope->link->name : "*", - t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), - pretty); - - /* RFC 4795, Section 4.1 says that the peer with the - * lexicographically smaller IP address loses */ - if (memcmp(&p->sender, &p->destination, FAMILY_ADDRESS_SIZE(p->family)) >= 0) { - log_debug("Peer has lexicographically larger IP address and thus lost in the conflict."); - return; - } - - log_debug("We have the lexicographically larger IP address and thus lost in the conflict."); - - t->block_gc++; - while ((z = set_first(t->notify_zone_items))) { - /* First, make sure the zone item drops the reference - * to us */ - dns_zone_item_probe_stop(z); - - /* Secondly, report this as conflict, so that we might - * look for a different hostname */ - dns_zone_item_conflict(z); - } - t->block_gc--; - - dns_transaction_gc(t); -} - -void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { - DnsQueryCandidate *c; - DnsZoneItem *z; - DnsTransaction *d; - Iterator i; - const char *st; - - assert(t); - assert(!DNS_TRANSACTION_IS_LIVE(state)); - - if (state == DNS_TRANSACTION_DNSSEC_FAILED) - log_struct(LOG_NOTICE, - LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_FAILURE), - LOG_MESSAGE("DNSSEC validation failed for question %s: %s", dns_transaction_key_string(t), dnssec_result_to_string(t->answer_dnssec_result)), - "DNS_TRANSACTION=%" PRIu16, t->id, - "DNS_QUESTION=%s", dns_transaction_key_string(t), - "DNSSEC_RESULT=%s", dnssec_result_to_string(t->answer_dnssec_result), - "DNS_SERVER=%s", dns_server_string(t->server), - "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(t->server->possible_feature_level), - NULL); - - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function. */ - - if (state == DNS_TRANSACTION_ERRNO) - st = errno_to_name(t->answer_errno); - else - st = dns_transaction_state_to_string(state); - - log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).", - t->id, - dns_transaction_key_string(t), - dns_protocol_to_string(t->scope->protocol), - t->scope->link ? t->scope->link->name : "*", - t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), - st, - t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source), - t->answer_authenticated ? "authenticated" : "unsigned"); - - t->state = state; - - dns_transaction_close_connection(t); - dns_transaction_stop_timeout(t); - - /* Notify all queries that are interested, but make sure the - * transaction isn't freed while we are still looking at it */ - t->block_gc++; - - SET_FOREACH(c, t->notify_query_candidates, i) - dns_query_candidate_notify(c); - SET_FOREACH(z, t->notify_zone_items, i) - dns_zone_item_notify(z); - - if (!set_isempty(t->notify_transactions)) { - DnsTransaction **nt; - unsigned j, n = 0; - - /* We need to be careful when notifying other - * transactions, as that might destroy other - * transactions in our list. Hence, in order to be - * able to safely iterate through the list of - * transactions, take a GC lock on all of them - * first. Then, in a second loop, notify them, but - * first unlock that specific transaction. */ - - nt = newa(DnsTransaction*, set_size(t->notify_transactions)); - SET_FOREACH(d, t->notify_transactions, i) { - nt[n++] = d; - d->block_gc++; - } - - assert(n == set_size(t->notify_transactions)); - - for (j = 0; j < n; j++) { - if (set_contains(t->notify_transactions, nt[j])) - dns_transaction_notify(nt[j], t); - - nt[j]->block_gc--; - dns_transaction_gc(nt[j]); - } - } - - t->block_gc--; - dns_transaction_gc(t); -} - -static int dns_transaction_pick_server(DnsTransaction *t) { - DnsServer *server; - - assert(t); - assert(t->scope->protocol == DNS_PROTOCOL_DNS); - - server = dns_scope_get_dns_server(t->scope); - if (!server) - return -ESRCH; - - t->current_feature_level = dns_server_possible_feature_level(server); - - if (server == t->server) - return 0; - - dns_server_unref(t->server); - t->server = dns_server_ref(server); - - return 1; -} - -static void dns_transaction_retry(DnsTransaction *t) { - int r; - - assert(t); - - log_debug("Retrying transaction %" PRIu16 ".", t->id); - - /* Before we try again, switch to a new server. */ - dns_scope_next_dns_server(t->scope); - - r = dns_transaction_go(t); - if (r < 0) { - t->answer_errno = -r; - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); - } -} - -static int dns_transaction_maybe_restart(DnsTransaction *t) { - assert(t); - - if (!t->server) - return 0; - - if (t->current_feature_level <= dns_server_possible_feature_level(t->server)) - return 0; - - /* The server's current feature level is lower than when we sent the original query. We learnt something from - the response or possibly an auxiliary DNSSEC response that we didn't know before. We take that as reason to - restart the whole transaction. This is a good idea to deal with servers that respond rubbish if we include - OPT RR or DO bit. One of these cases is documented here, for example: - https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ - - 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); -} - -static int on_stream_complete(DnsStream *s, int error) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsTransaction *t; - - assert(s); - assert(s->transaction); - - /* Copy the data we care about out of the stream before we - * destroy it. */ - t = s->transaction; - p = dns_packet_ref(s->read_packet); - - t->stream = dns_stream_free(t->stream); - - if (ERRNO_IS_DISCONNECT(error)) { - usec_t usec; - - if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { - /* If the LLMNR/TCP connection failed, the host doesn't support LLMNR, and we cannot answer the - * question on this scope. */ - dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); - return 0; - } - - log_debug_errno(error, "Connection failure for DNS TCP stream: %m"); - 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); - return 0; - } - if (error != 0) { - t->answer_errno = error; - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); - return 0; - } - - if (dns_packet_validate_reply(p) <= 0) { - log_debug("Invalid TCP reply packet."); - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return 0; - } - - dns_scope_check_conflicts(t->scope, p); - - t->block_gc++; - dns_transaction_process_reply(t, p); - t->block_gc--; - - /* If the response wasn't useful, then complete the transition - * now. After all, we are the worst feature set now with TCP - * sockets, and there's really no point in retrying. */ - if (t->state == DNS_TRANSACTION_PENDING) - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - else - dns_transaction_gc(t); - - return 0; -} - -static int dns_transaction_open_tcp(DnsTransaction *t) { - _cleanup_close_ int fd = -1; - int r; - - assert(t); - - dns_transaction_close_connection(t); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - r = dns_transaction_pick_server(t); - if (r < 0) - return r; - - if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) - return -EOPNOTSUPP; - - r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); - if (r < 0) - return r; - - fd = dns_scope_socket_tcp(t->scope, AF_UNSPEC, NULL, t->server, 53); - break; - - case DNS_PROTOCOL_LLMNR: - /* When we already received a reply to this (but it was truncated), send to its sender address */ - if (t->received) - fd = dns_scope_socket_tcp(t->scope, t->received->family, &t->received->sender, NULL, t->received->sender_port); - else { - union in_addr_union address; - int family = AF_UNSPEC; - - /* Otherwise, try to talk to the owner of a - * the IP address, in case this is a reverse - * PTR lookup */ - - r = dns_name_address(DNS_RESOURCE_KEY_NAME(t->key), &family, &address); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - if (family != t->scope->family) - return -ESRCH; - - fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT); - } - - break; - - default: - return -EAFNOSUPPORT; - } - - if (fd < 0) - return fd; - - r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd); - if (r < 0) - return r; - fd = -1; - - r = dns_stream_write_packet(t->stream, t->sent); - if (r < 0) { - t->stream = dns_stream_free(t->stream); - return r; - } - - t->stream->complete = on_stream_complete; - t->stream->transaction = 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; - - dns_transaction_reset_answer(t); - - t->tried_stream = true; - - return 0; -} - -static void dns_transaction_cache_answer(DnsTransaction *t) { - assert(t); - - /* For mDNS we cache whenever we get the packet, rather than - * in each transaction. */ - if (!IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) - 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 - * the cache then we could. */ - if (!DNS_PACKET_SHALL_CACHE(t->received)) - return; - - dns_cache_put(&t->scope->cache, - t->key, - t->answer_rcode, - t->answer, - t->answer_authenticated, - t->answer_nsec_ttl, - 0, - t->received->family, - &t->received->sender); -} - -static bool dns_transaction_dnssec_is_live(DnsTransaction *t) { - DnsTransaction *dt; - Iterator i; - - assert(t); - - SET_FOREACH(dt, t->dnssec_transactions, i) - if (DNS_TRANSACTION_IS_LIVE(dt->state)) - return true; - - return false; -} - -static int dns_transaction_dnssec_ready(DnsTransaction *t) { - DnsTransaction *dt; - Iterator i; - - assert(t); - - /* Checks whether the auxiliary DNSSEC transactions of our transaction have completed, or are still - * ongoing. Returns 0, if we aren't ready for the DNSSEC validation, positive if we are. */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - switch (dt->state) { - - case DNS_TRANSACTION_NULL: - case DNS_TRANSACTION_PENDING: - case DNS_TRANSACTION_VALIDATING: - /* Still ongoing */ - return 0; - - case DNS_TRANSACTION_RCODE_FAILURE: - if (dt->answer_rcode != DNS_RCODE_NXDOMAIN) { - 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. */ - - case DNS_TRANSACTION_SUCCESS: - /* All good. */ - break; - - case DNS_TRANSACTION_DNSSEC_FAILED: - /* We handle DNSSEC failures different from other errors, as we care about the DNSSEC - * validationr result */ - - log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result)); - t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */ - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); - return 0; - - - default: - log_debug("Auxiliary DNSSEC RR query failed with %s", dns_transaction_state_to_string(dt->state)); - goto fail; - } - } - - /* All is ready, we can go and validate */ - return 1; - -fail: - t->answer_dnssec_result = DNSSEC_FAILED_AUXILIARY; - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); - return 0; -} - -static void dns_transaction_process_dnssec(DnsTransaction *t) { - int r; - - assert(t); - - /* Are there ongoing DNSSEC transactions? If so, let's wait for them. */ - r = dns_transaction_dnssec_ready(t); - if (r < 0) - goto fail; - if (r == 0) /* We aren't ready yet (or one of our auxiliary transactions failed, and we shouldn't validate now */ - return; - - /* See if we learnt things from the additional DNSSEC transactions, that we didn't know before, and better - * restart the lookup immediately. */ - r = dns_transaction_maybe_restart(t); - if (r < 0) - goto fail; - if (r > 0) /* Transaction got restarted... */ - return; - - /* All our auxiliary DNSSEC transactions are complete now. Try - * to validate our RRset now. */ - r = dns_transaction_validate_dnssec(t); - if (r == -EBADMSG) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - if (r < 0) - goto fail; - - if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER && - t->scope->dnssec_mode == DNSSEC_YES) { - /* We are not in automatic downgrade mode, and the - * server is bad, refuse operation. */ - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); - return; - } - - if (!IN_SET(t->answer_dnssec_result, - _DNSSEC_RESULT_INVALID, /* No DNSSEC validation enabled */ - DNSSEC_VALIDATED, /* Answer is signed and validated successfully */ - DNSSEC_UNSIGNED, /* Answer is right-fully unsigned */ - DNSSEC_INCOMPATIBLE_SERVER)) { /* Server does not do DNSSEC (Yay, we are downgrade attack vulnerable!) */ - dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); - return; - } - - if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER) - dns_server_warn_downgrade(t->server); - - dns_transaction_cache_answer(t); - - if (t->answer_rcode == DNS_RCODE_SUCCESS) - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - else - dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); - - return; - -fail: - t->answer_errno = -r; - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); -} - -static int dns_transaction_has_positive_answer(DnsTransaction *t, DnsAnswerFlags *flags) { - int r; - - assert(t); - - /* Checks whether the answer is positive, i.e. either a direct - * answer to the question, or a CNAME/DNAME for it */ - - r = dns_answer_match_key(t->answer, t->key, flags); - if (r != 0) - return r; - - r = dns_answer_find_cname_or_dname(t->answer, t->key, NULL, flags); - if (r != 0) - return r; - - return false; -} - -static int dns_transaction_fix_rcode(DnsTransaction *t) { - int r; - - assert(t); - - /* Fix up the RCODE to SUCCESS if we get at least one matching RR in a response. Note that this contradicts the - * DNS RFCs a bit. Specifically, RFC 6604 Section 3 clarifies that the RCODE shall say something about a - * CNAME/DNAME chain element coming after the last chain element contained in the message, and not the first - * one included. However, it also indicates that not all DNS servers implement this correctly. Moreover, when - * using DNSSEC we usually only can prove the first element of a CNAME/DNAME chain anyway, hence let's settle - * on always processing the RCODE as referring to the immediate look-up we do, i.e. the first element of a - * CNAME/DNAME chain. This way, we uniformly handle CNAME/DNAME chains, regardless if the DNS server - * incorrectly implements RCODE, whether DNSSEC is in use, or whether the DNS server only supplied us with an - * incomplete CNAME/DNAME chain. - * - * Or in other words: if we get at least one positive reply in a message we patch NXDOMAIN to become SUCCESS, - * and then rely on the CNAME chasing logic to figure out that there's actually a CNAME error with a new - * lookup. */ - - if (t->answer_rcode != DNS_RCODE_NXDOMAIN) - return 0; - - r = dns_transaction_has_positive_answer(t, NULL); - if (r <= 0) - return r; - - t->answer_rcode = DNS_RCODE_SUCCESS; - return 0; -} - -void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { - usec_t ts; - int r; - - assert(t); - assert(p); - assert(t->scope); - assert(t->scope->manager); - - if (t->state != DNS_TRANSACTION_PENDING) - return; - - /* Note that this call might invalidate the query. Callers - * should hence not attempt to access the query or transaction - * after calling this function. */ - - log_debug("Processing incoming packet on transaction %" PRIu16".", t->id); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_LLMNR: - assert(t->scope->link); - - /* For LLMNR we will not accept any packets from other - * interfaces */ - - if (p->ifindex != t->scope->link->ifindex) - return; - - if (p->family != t->scope->family) - return; - - /* Tentative packets are not full responses but still - * useful for identifying uniqueness conflicts during - * probing. */ - if (DNS_PACKET_LLMNR_T(p)) { - dns_transaction_tentative(t, p); - return; - } - - 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) - return; - - if (p->family != t->scope->family) - return; - - break; - - case DNS_PROTOCOL_DNS: - /* Note that we do not need to verify the - * addresses/port numbers of incoming traffic, as we - * invoked connect() on our UDP socket in which case - * the kernel already does the needed verification for - * us. */ - break; - - default: - assert_not_reached("Invalid DNS protocol."); - } - - if (t->received != p) { - dns_packet_unref(t->received); - t->received = dns_packet_ref(p); - } - - t->answer_source = DNS_TRANSACTION_NETWORK; - - if (p->ipproto == IPPROTO_TCP) { - if (DNS_PACKET_TC(p)) { - /* Truncated via TCP? Somebody must be fucking with us */ - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - if (DNS_PACKET_ID(p) != t->id) { - /* Not the reply to our query? Somebody must be fucking with us */ - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - } - - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - assert(t->server); - - 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); - return; - } else if (DNS_PACKET_TC(p)) - dns_server_packet_truncated(t->server, t->current_feature_level); - - break; - - case DNS_PROTOCOL_LLMNR: - case DNS_PROTOCOL_MDNS: - dns_scope_packet_received(t->scope, ts - t->start_usec); - break; - - default: - assert_not_reached("Invalid DNS protocol."); - } - - if (DNS_PACKET_TC(p)) { - - /* Truncated packets for mDNS are not allowed. Give up immediately. */ - if (t->scope->protocol == DNS_PROTOCOL_MDNS) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - log_debug("Reply truncated, retrying via TCP."); - - /* Response was truncated, let's try again with good old TCP */ - r = dns_transaction_open_tcp(t); - if (r == -ESRCH) { - /* No servers found? Damn! */ - dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); - return; - } - if (r == -EOPNOTSUPP) { - /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ - dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); - return; - } - if (r < 0) { - /* On LLMNR, if we cannot connect to the host, - * we immediately give up */ - if (t->scope->protocol != DNS_PROTOCOL_DNS) - goto fail; - - /* On DNS, couldn't send? Try immediately again, with a new server */ - dns_transaction_retry(t); - } - - return; - } - - /* After the superficial checks, actually parse the message. */ - r = dns_packet_extract(p); - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - /* Report that the OPT RR was missing */ - if (t->server) { - if (!p->opt) - dns_server_packet_bad_opt(t->server, t->current_feature_level); - - dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, ts - t->start_usec, p->size); - } - - /* See if we know things we didn't know before that indicate we better restart the lookup immediately. */ - r = dns_transaction_maybe_restart(t); - if (r < 0) - goto fail; - if (r > 0) /* Transaction got restarted... */ - return; - - if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) { - - /* Only consider responses with equivalent query section to the request */ - r = dns_packet_is_reply_for(p, t->key); - if (r < 0) - goto fail; - if (r == 0) { - dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); - return; - } - - /* Install the answer as answer to the transaction */ - dns_answer_unref(t->answer); - t->answer = dns_answer_ref(p->answer); - t->answer_rcode = DNS_PACKET_RCODE(p); - t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; - t->answer_authenticated = false; - - r = dns_transaction_fix_rcode(t); - if (r < 0) - goto fail; - - /* Block GC while starting requests for additional DNSSEC RRs */ - t->block_gc++; - r = dns_transaction_request_dnssec_keys(t); - t->block_gc--; - - /* Maybe the transaction is ready for GC'ing now? If so, free it and return. */ - if (!dns_transaction_gc(t)) - return; - - /* Requesting additional keys might have resulted in - * this transaction to fail, since the auxiliary - * request failed for some reason. If so, we are not - * in pending state anymore, and we should exit - * quickly. */ - if (t->state != DNS_TRANSACTION_PENDING) - return; - if (r < 0) - goto fail; - if (r > 0) { - /* There are DNSSEC transactions pending now. Update the state accordingly. */ - t->state = DNS_TRANSACTION_VALIDATING; - dns_transaction_close_connection(t); - dns_transaction_stop_timeout(t); - return; - } - } - - dns_transaction_process_dnssec(t); - return; - -fail: - t->answer_errno = -r; - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); -} - -static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsTransaction *t = userdata; - int r; - - assert(t); - assert(t->scope); - - r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p); - if (ERRNO_IS_DISCONNECT(-r)) { - usec_t usec; - - /* UDP connection failure get reported via ICMP and then are possible delivered to us on the next - * recvmsg(). Treat this like a lost packet. */ - - log_debug_errno(r, "Connection failure for DNS UDP packet: %m"); - 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); - return 0; - } - if (r < 0) { - dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); - t->answer_errno = -r; - return 0; - } - - r = dns_packet_validate_reply(p); - if (r < 0) { - log_debug_errno(r, "Received invalid DNS packet as response, ignoring: %m"); - return 0; - } - if (r == 0) { - log_debug("Received inappropriate DNS packet as response, ignoring."); - return 0; - } - - if (DNS_PACKET_ID(p) != t->id) { - log_debug("Received packet with incorrect transaction ID, ignoring."); - return 0; - } - - dns_transaction_process_reply(t, p); - return 0; -} - -static int dns_transaction_emit_udp(DnsTransaction *t) { - int r; - - assert(t); - - if (t->scope->protocol == DNS_PROTOCOL_DNS) { - - r = dns_transaction_pick_server(t); - if (r < 0) - return r; - - if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP) - return -EAGAIN; - - if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) - return -EOPNOTSUPP; - - if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */ - int fd; - - dns_transaction_close_connection(t); - - fd = dns_scope_socket_udp(t->scope, t->server, 53); - if (fd < 0) - return fd; - - r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t); - if (r < 0) { - safe_close(fd); - return r; - } - - (void) sd_event_source_set_description(t->dns_udp_event_source, "dns-transaction-udp"); - t->dns_udp_fd = fd; - } - - r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); - if (r < 0) - return r; - } else - dns_transaction_close_connection(t); - - r = dns_scope_emit_udp(t->scope, t->dns_udp_fd, t->sent); - if (r < 0) - return r; - - dns_transaction_reset_answer(t); - - return 0; -} - -static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { - DnsTransaction *t = userdata; - - assert(s); - assert(t); - - if (!t->initial_jitter_scheduled || t->initial_jitter_elapsed) { - /* Timeout reached? Increase the timeout for the server used */ - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - assert(t->server); - dns_server_packet_lost(t->server, t->stream ? IPPROTO_TCP : IPPROTO_UDP, t->current_feature_level, usec - t->start_usec); - break; - - case DNS_PROTOCOL_LLMNR: - case DNS_PROTOCOL_MDNS: - dns_scope_packet_lost(t->scope, usec - t->start_usec); - break; - - default: - assert_not_reached("Invalid DNS protocol."); - } - - if (t->initial_jitter_scheduled) - t->initial_jitter_elapsed = true; - } - - log_debug("Timeout reached on transaction %" PRIu16 ".", t->id); - - dns_transaction_retry(t); - return 0; -} - -static usec_t transaction_get_resend_timeout(DnsTransaction *t) { - assert(t); - assert(t->scope); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_DNS: - assert(t->server); - return t->server->resend_timeout; - - case DNS_PROTOCOL_MDNS: - assert(t->n_attempts > 0); - return (1 << (t->n_attempts - 1)) * USEC_PER_SEC; - - case DNS_PROTOCOL_LLMNR: - return t->scope->resend_timeout; - - default: - assert_not_reached("Invalid DNS protocol."); - } -} - -static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { - int r; - - assert(t); - - dns_transaction_stop_timeout(t); - - r = dns_scope_network_good(t->scope); - if (r < 0) - return r; - if (r == 0) { - dns_transaction_complete(t, DNS_TRANSACTION_NETWORK_DOWN); - return 0; - } - - if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { - dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); - return 0; - } - - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && t->tried_stream) { - /* If we already tried via a stream, then we don't - * retry on LLMNR. See RFC 4795, Section 2.7. */ - dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); - return 0; - } - - t->n_attempts++; - t->start_usec = ts; - - dns_transaction_reset_answer(t); - dns_transaction_flush_dnssec_transactions(t); - - /* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */ - if (t->scope->protocol == DNS_PROTOCOL_DNS) { - r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, t->key, &t->answer); - if (r < 0) - return r; - if (r > 0) { - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; - t->answer_authenticated = true; - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - return 0; - } - - if (dns_name_is_root(DNS_RESOURCE_KEY_NAME(t->key)) && - t->key->type == DNS_TYPE_DS) { - - /* Hmm, this is a request for the root DS? A - * DS RR doesn't exist in the root zone, and - * if our trust anchor didn't know it either, - * this means we cannot do any DNSSEC logic - * anymore. */ - - if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { - /* We are in downgrade mode. In this - * case, synthesize an unsigned empty - * response, so that the any lookup - * depending on this one can continue - * assuming there was no DS, and hence - * the root zone was unsigned. */ - - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; - t->answer_authenticated = false; - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - } else - /* If we are not in downgrade mode, - * then fail the lookup, because we - * cannot reasonably answer it. There - * might be DS RRs, but we don't know - * them, and the DNS server won't tell - * them to us (and even if it would, - * we couldn't validate it and trust - * it). */ - dns_transaction_complete(t, DNS_TRANSACTION_NO_TRUST_ANCHOR); - - return 0; - } - } - - /* Check the zone, but only if this transaction is not used - * 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); - if (r < 0) - return r; - if (r > 0) { - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_source = DNS_TRANSACTION_ZONE; - t->answer_authenticated = true; - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - return 0; - } - } - - /* Check the cache, but only if this transaction is not used - * for probing or verifying a zone item. */ - if (set_isempty(t->notify_zone_items)) { - - /* Before trying the cache, let's make sure we figured out a - * server to use. Should this cause a change of server this - * might flush the cache. */ - dns_scope_get_dns_server(t->scope); - - /* 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); - if (r < 0) - return r; - if (r > 0) { - t->answer_source = DNS_TRANSACTION_CACHE; - if (t->answer_rcode == DNS_RCODE_SUCCESS) - dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); - else - dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); - return 0; - } - } - - return 1; -} - -static int dns_transaction_make_packet_mdns(DnsTransaction *t) { - - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - bool add_known_answers = false; - DnsTransaction *other; - unsigned qdcount; - usec_t ts; - int r; - - assert(t); - assert(t->scope->protocol == DNS_PROTOCOL_MDNS); - - /* Discard any previously prepared packet, so we can start over and coalesce again */ - t->sent = dns_packet_unref(t->sent); - - r = dns_packet_new_query(&p, t->scope->protocol, 0, false); - if (r < 0) - return r; - - r = dns_packet_append_key(p, t->key, NULL); - if (r < 0) - return r; - - qdcount = 1; - - if (dns_key_is_shared(t->key)) - add_known_answers = true; - - /* - * For mDNS, we want to coalesce as many open queries in pending transactions into one single - * query packet on the wire as possible. To achieve that, we iterate through all pending transactions - * in our current scope, and see whether their timing contraints allow them to be sent. - */ - - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - LIST_FOREACH(transactions_by_scope, other, t->scope->transactions) { - - /* Skip ourselves */ - if (other == t) - continue; - - if (other->state != DNS_TRANSACTION_PENDING) - continue; - - if (other->next_attempt_after > ts) - continue; - - if (qdcount >= UINT16_MAX) - break; - - r = dns_packet_append_key(p, other->key, NULL); - - /* - * If we can't stuff more questions into the packet, just give up. - * One of the 'other' transactions will fire later and take care of the rest. - */ - if (r == -EMSGSIZE) - break; - - if (r < 0) - return r; - - r = dns_transaction_prepare(other, ts); - if (r <= 0) - continue; - - ts += transaction_get_resend_timeout(other); - - r = sd_event_add_time( - other->scope->manager->event, - &other->timeout_event_source, - clock_boottime_or_monotonic(), - ts, 0, - on_transaction_timeout, other); - if (r < 0) - return r; - - (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); - - other->state = DNS_TRANSACTION_PENDING; - other->next_attempt_after = ts; - - qdcount ++; - - if (dns_key_is_shared(other->key)) - add_known_answers = true; - } - - DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount); - - /* Append known answer section if we're asking for any shared record */ - if (add_known_answers) { - r = dns_cache_export_shared_to_packet(&t->scope->cache, p); - if (r < 0) - return r; - } - - t->sent = p; - p = NULL; - - return 0; -} - -static int dns_transaction_make_packet(DnsTransaction *t) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - int r; - - assert(t); - - if (t->scope->protocol == DNS_PROTOCOL_MDNS) - return dns_transaction_make_packet_mdns(t); - - if (t->sent) - return 0; - - r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO); - if (r < 0) - return r; - - r = dns_packet_append_key(p, t->key, NULL); - if (r < 0) - return r; - - DNS_PACKET_HEADER(p)->qdcount = htobe16(1); - DNS_PACKET_HEADER(p)->id = t->id; - - t->sent = p; - p = NULL; - - return 0; -} - -int dns_transaction_go(DnsTransaction *t) { - usec_t ts; - int r; - - assert(t); - - assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); - - r = dns_transaction_prepare(t, ts); - if (r <= 0) - return r; - - log_debug("Excercising transaction %" PRIu16 " for <%s> on scope %s on %s/%s.", - t->id, - dns_transaction_key_string(t), - dns_protocol_to_string(t->scope->protocol), - t->scope->link ? t->scope->link->name : "*", - t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family)); - - if (!t->initial_jitter_scheduled && - (t->scope->protocol == DNS_PROTOCOL_LLMNR || - t->scope->protocol == DNS_PROTOCOL_MDNS)) { - usec_t jitter, accuracy; - - /* RFC 4795 Section 2.7 suggests all queries should be - * delayed by a random time from 0 to JITTER_INTERVAL. */ - - t->initial_jitter_scheduled = true; - - random_bytes(&jitter, sizeof(jitter)); - - switch (t->scope->protocol) { - - case DNS_PROTOCOL_LLMNR: - jitter %= LLMNR_JITTER_INTERVAL_USEC; - accuracy = LLMNR_JITTER_INTERVAL_USEC; - break; - - case DNS_PROTOCOL_MDNS: - jitter %= MDNS_JITTER_RANGE_USEC; - jitter += MDNS_JITTER_MIN_USEC; - accuracy = MDNS_JITTER_RANGE_USEC; - break; - default: - assert_not_reached("bad protocol"); - } - - r = sd_event_add_time( - t->scope->manager->event, - &t->timeout_event_source, - clock_boottime_or_monotonic(), - ts + jitter, accuracy, - on_transaction_timeout, t); - if (r < 0) - return r; - - (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); - - t->n_attempts = 0; - t->next_attempt_after = ts; - t->state = DNS_TRANSACTION_PENDING; - - log_debug("Delaying %s transaction for " USEC_FMT "us.", dns_protocol_to_string(t->scope->protocol), jitter); - return 0; - } - - /* Otherwise, we need to ask the network */ - r = dns_transaction_make_packet(t); - if (r < 0) - return r; - - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && - (dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "in-addr.arpa") > 0 || - dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "ip6.arpa") > 0)) { - - /* RFC 4795, Section 2.4. says reverse lookups shall - * always be made via TCP on LLMNR */ - r = dns_transaction_open_tcp(t); - } else { - /* Try via UDP, and if that fails due to large size or lack of - * support try via TCP */ - r = dns_transaction_emit_udp(t); - if (r == -EMSGSIZE) - log_debug("Sending query via TCP since it is too large."); - if (r == -EAGAIN) - log_debug("Sending query via TCP since server doesn't support UDP."); - if (r == -EMSGSIZE || r == -EAGAIN) - r = dns_transaction_open_tcp(t); - } - - if (r == -ESRCH) { - /* No servers to send this to? */ - dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); - return 0; - } - if (r == -EOPNOTSUPP) { - /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ - dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); - return 0; - } - if (t->scope->protocol == DNS_PROTOCOL_LLMNR && ERRNO_IS_DISCONNECT(-r)) { - /* On LLMNR, if we cannot connect to a host via TCP when doing reverse lookups. This means we cannot - * answer this request with this protocol. */ - dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); - return 0; - } - if (r < 0) { - if (t->scope->protocol != DNS_PROTOCOL_DNS) - return r; - - /* Couldn't send? Try immediately again, with a new server */ - dns_scope_next_dns_server(t->scope); - - return dns_transaction_go(t); - } - - ts += transaction_get_resend_timeout(t); - - r = sd_event_add_time( - t->scope->manager->event, - &t->timeout_event_source, - clock_boottime_or_monotonic(), - ts, 0, - on_transaction_timeout, t); - if (r < 0) - return r; - - (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); - - t->state = DNS_TRANSACTION_PENDING; - t->next_attempt_after = ts; - - return 1; -} - -static int dns_transaction_find_cyclic(DnsTransaction *t, DnsTransaction *aux) { - DnsTransaction *n; - Iterator i; - int r; - - assert(t); - assert(aux); - - /* Try to find cyclic dependencies between transaction objects */ - - if (t == aux) - return 1; - - SET_FOREACH(n, aux->dnssec_transactions, i) { - r = dns_transaction_find_cyclic(t, n); - if (r != 0) - return r; - } - - return 0; -} - -static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) { - DnsTransaction *aux; - int r; - - assert(t); - assert(ret); - assert(key); - - aux = dns_scope_find_transaction(t->scope, key, true); - if (!aux) { - r = dns_transaction_new(&aux, t->scope, key); - if (r < 0) - return r; - } else { - if (set_contains(t->dnssec_transactions, aux)) { - *ret = aux; - return 0; - } - - r = dns_transaction_find_cyclic(t, aux); - if (r < 0) - return r; - if (r > 0) { - log_debug("Detected potential cyclic dependency, refusing to add transaction %" PRIu16 " (%s) as dependency for %" PRIu16 " (%s).", - aux->id, - strna(dns_transaction_key_string(aux)), - t->id, - strna(dns_transaction_key_string(t))); - return -ELOOP; - } - } - - r = set_ensure_allocated(&t->dnssec_transactions, NULL); - if (r < 0) - goto gc; - - r = set_ensure_allocated(&aux->notify_transactions, NULL); - if (r < 0) - goto gc; - - r = set_put(t->dnssec_transactions, aux); - if (r < 0) - goto gc; - - r = set_put(aux->notify_transactions, t); - if (r < 0) { - (void) set_remove(t->dnssec_transactions, aux); - goto gc; - } - - *ret = aux; - return 1; - -gc: - dns_transaction_gc(aux); - return r; -} - -static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) { - _cleanup_(dns_answer_unrefp) DnsAnswer *a = NULL; - DnsTransaction *aux; - int r; - - assert(t); - assert(key); - - /* Try to get the data from the trust anchor */ - r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, key, &a); - if (r < 0) - return r; - if (r > 0) { - r = dns_answer_extend(&t->validated_keys, a); - if (r < 0) - return r; - - return 0; - } - - /* This didn't work, ask for it via the network/cache then. */ - r = dns_transaction_add_dnssec_transaction(t, key, &aux); - if (r == -ELOOP) /* This would result in a cyclic dependency */ - return 0; - if (r < 0) - return r; - - if (aux->state == DNS_TRANSACTION_NULL) { - r = dns_transaction_go(aux); - if (r < 0) - return r; - } - - return 1; -} - -static int dns_transaction_negative_trust_anchor_lookup(DnsTransaction *t, const char *name) { - int r; - - assert(t); - - /* Check whether the specified name is in the the NTA - * database, either in the global one, or the link-local - * one. */ - - r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, name); - if (r != 0) - return r; - - if (!t->scope->link) - return 0; - - return set_contains(t->scope->link->dnssec_negative_trust_anchors, name); -} - -static int dns_transaction_has_unsigned_negative_answer(DnsTransaction *t) { - int r; - - assert(t); - - /* Checks whether the answer is negative, and lacks NSEC/NSEC3 - * RRs to prove it */ - - r = dns_transaction_has_positive_answer(t, NULL); - if (r < 0) - return r; - if (r > 0) - return false; - - /* Is this key explicitly listed as a negative trust anchor? - * If so, it's nothing we need to care about */ - r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key)); - if (r < 0) - return r; - if (r > 0) - return false; - - /* The answer does not contain any RRs that match to the - * question. If so, let's see if there are any NSEC/NSEC3 RRs - * included. If not, the answer is unsigned. */ - - r = dns_answer_contains_nsec_or_nsec3(t->answer); - if (r < 0) - return r; - if (r > 0) - return false; - - return true; -} - -static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRecord *rr) { - int r; - - assert(t); - assert(rr); - - /* Check if the specified RR is the "primary" response, - * i.e. either matches the question precisely or is a - * CNAME/DNAME for it. */ - - r = dns_resource_key_match_rr(t->key, rr, NULL); - if (r != 0) - return r; - - return dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); -} - -static bool dns_transaction_dnssec_supported(DnsTransaction *t) { - assert(t); - - /* Checks whether our transaction's DNS server is assumed to be compatible with DNSSEC. Returns false as soon - * as we changed our mind about a server, and now believe it is incompatible with DNSSEC. */ - - if (t->scope->protocol != DNS_PROTOCOL_DNS) - return false; - - /* If we have picked no server, then we are working from the cache or some other source, and DNSSEC might well - * be supported, hence return true. */ - if (!t->server) - return true; - - if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) - return false; - - return dns_server_dnssec_supported(t->server); -} - -static bool dns_transaction_dnssec_supported_full(DnsTransaction *t) { - DnsTransaction *dt; - Iterator i; - - assert(t); - - /* Checks whether our transaction our any of the auxiliary transactions couldn't do DNSSEC. */ - - if (!dns_transaction_dnssec_supported(t)) - return false; - - SET_FOREACH(dt, t->dnssec_transactions, i) - if (!dns_transaction_dnssec_supported(dt)) - return false; - - return true; -} - -int dns_transaction_request_dnssec_keys(DnsTransaction *t) { - DnsResourceRecord *rr; - - int r; - - assert(t); - - /* - * Retrieve all auxiliary RRs for the answer we got, so that - * we can verify signatures or prove that RRs are rightfully - * unsigned. Specifically: - * - * - For RRSIG we get the matching DNSKEY - * - For DNSKEY we get the matching DS - * - For unsigned SOA/NS we get the matching DS - * - For unsigned CNAME/DNAME/DS we get the parent SOA RR - * - For other unsigned RRs we get the matching SOA RR - * - For SOA/NS/DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's SOA RR - * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR - */ - - if (t->scope->dnssec_mode == DNSSEC_NO) - return 0; - if (t->answer_source != DNS_TRANSACTION_NETWORK) - return 0; /* We only need to validate stuff from the network */ - if (!dns_transaction_dnssec_supported(t)) - return 0; /* If we can't do DNSSEC anyway there's no point in geting the auxiliary RRs */ - - DNS_ANSWER_FOREACH(rr, t->answer) { - - if (dns_type_is_pseudo(rr->key->type)) - continue; - - /* If this RR is in the negative trust anchor, we don't need to validate it. */ - r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key)); - if (r < 0) - return r; - if (r > 0) - continue; - - switch (rr->key->type) { - - case DNS_TYPE_RRSIG: { - /* For each RRSIG we request the matching DNSKEY */ - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dnskey = NULL; - - /* If this RRSIG is about a DNSKEY RR and the - * signer is the same as the owner, then we - * already have the DNSKEY, and we don't have - * to look for more. */ - if (rr->rrsig.type_covered == DNS_TYPE_DNSKEY) { - r = dns_name_equal(rr->rrsig.signer, DNS_RESOURCE_KEY_NAME(rr->key)); - if (r < 0) - return r; - if (r > 0) - continue; - } - - /* If the signer is not a parent of our - * original query, then this is about an - * auxiliary RRset, but not anything we asked - * for. In this case we aren't interested, - * because we don't want to request additional - * RRs for stuff we didn't really ask for, and - * also to avoid request loops, where - * additional RRs from one transaction result - * in another transaction whose additonal RRs - * point back to the original transaction, and - * we deadlock. */ - r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), rr->rrsig.signer); - if (r < 0) - return r; - if (r == 0) - continue; - - dnskey = dns_resource_key_new(rr->key->class, DNS_TYPE_DNSKEY, rr->rrsig.signer); - if (!dnskey) - return -ENOMEM; - - log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (%s, RRSIG with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), rr->rrsig.key_tag); - r = dns_transaction_request_dnssec_rr(t, dnskey); - if (r < 0) - return r; - break; - } - - case DNS_TYPE_DNSKEY: { - /* For each DNSKEY we request the matching DS */ - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; - - /* If the DNSKEY we are looking at is not for - * zone we are interested in, nor any of its - * parents, we aren't interested, and don't - * request it. After all, we don't want to end - * up in request loops, and want to keep - * additional traffic down. */ - - r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), DNS_RESOURCE_KEY_NAME(rr->key)); - if (r < 0) - return r; - if (r == 0) - continue; - - ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key)); - if (!ds) - return -ENOMEM; - - log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dnssec_keytag(rr, false)); - r = dns_transaction_request_dnssec_rr(t, ds); - if (r < 0) - return r; - - break; - } - - case DNS_TYPE_SOA: - case DNS_TYPE_NS: { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; - - /* For an unsigned SOA or NS, try to acquire - * the matching DS RR, as we are at a zone cut - * then, and whether a DS exists tells us - * whether the zone is signed. Do so only if - * this RR matches our original question, - * however. */ - - r = dns_resource_key_match_rr(t->key, rr, NULL); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_has_rrsig(t->answer, rr->key); - if (r < 0) - return r; - if (r > 0) - continue; - - ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key)); - if (!ds) - return -ENOMEM; - - log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned SOA/NS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key)); - r = dns_transaction_request_dnssec_rr(t, ds); - if (r < 0) - return r; - - break; - } - - case DNS_TYPE_DS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; - const char *name; - - /* CNAMEs and DNAMEs cannot be located at a - * zone apex, hence ask for the parent SOA for - * unsigned CNAME/DNAME RRs, maybe that's the - * apex. But do all that only if this is - * actually a response to our original - * question. - * - * Similar for DS RRs, which are signed when - * the parent SOA is signed. */ - - r = dns_transaction_is_primary_response(t, rr); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_has_rrsig(t->answer, rr->key); - if (r < 0) - return r; - if (r > 0) - continue; - - r = dns_answer_has_dname_for_cname(t->answer, rr); - if (r < 0) - return r; - if (r > 0) - continue; - - name = DNS_RESOURCE_KEY_NAME(rr->key); - r = dns_name_parent(&name); - if (r < 0) - return r; - if (r == 0) - continue; - - soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, name); - if (!soa) - return -ENOMEM; - - log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key)); - r = dns_transaction_request_dnssec_rr(t, soa); - if (r < 0) - return r; - - break; - } - - default: { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; - - /* For other unsigned RRsets (including - * NSEC/NSEC3!), look for proof the zone is - * unsigned, by requesting the SOA RR of the - * zone. However, do so only if they are - * directly relevant to our original - * question. */ - - r = dns_transaction_is_primary_response(t, rr); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_has_rrsig(t->answer, rr->key); - if (r < 0) - return r; - if (r > 0) - continue; - - soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, DNS_RESOURCE_KEY_NAME(rr->key)); - if (!soa) - return -ENOMEM; - - log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dns_resource_record_to_string(rr)); - r = dns_transaction_request_dnssec_rr(t, soa); - if (r < 0) - return r; - break; - }} - } - - /* Above, we requested everything necessary to validate what - * we got. Now, let's request what we need to validate what we - * didn't get... */ - - r = dns_transaction_has_unsigned_negative_answer(t); - if (r < 0) - return r; - if (r > 0) { - const char *name; - - name = DNS_RESOURCE_KEY_NAME(t->key); - - /* If this was a SOA or NS request, then this - * indicates that we are not at a zone apex, hence ask - * the parent name instead. If this was a DS request, - * then it's signed when the parent zone is signed, - * hence ask the parent in that case, too. */ - - if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS, DNS_TYPE_DS)) { - r = dns_name_parent(&name); - if (r < 0) - return r; - if (r > 0) - log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned empty SOA/NS/DS response).", t->id, DNS_RESOURCE_KEY_NAME(t->key)); - else - name = NULL; - } else - log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned empty non-SOA/NS/DS response).", t->id, DNS_RESOURCE_KEY_NAME(t->key)); - - if (name) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; - - soa = dns_resource_key_new(t->key->class, DNS_TYPE_SOA, name); - if (!soa) - return -ENOMEM; - - r = dns_transaction_request_dnssec_rr(t, soa); - if (r < 0) - return r; - } - } - - return dns_transaction_dnssec_is_live(t); -} - -void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) { - assert(t); - assert(source); - - /* Invoked whenever any of our auxiliary DNSSEC transactions completed its work. If the state is still PENDING, - we are still in the loop that adds further DNSSEC transactions, hence don't check if we are ready yet. If - the state is VALIDATING however, we should check if we are complete now. */ - - if (t->state == DNS_TRANSACTION_VALIDATING) - dns_transaction_process_dnssec(t); -} - -static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { - DnsResourceRecord *rr; - int ifindex, r; - - assert(t); - - /* Add all DNSKEY RRs from the answer that are validated by DS - * RRs from the list of validated keys to the list of - * validated keys. */ - - DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { - - r = dnssec_verify_dnskey_by_ds_search(rr, t->validated_keys); - if (r < 0) - return r; - if (r == 0) - continue; - - /* If so, the DNSKEY is validated too. */ - r = dns_answer_add_extend(&t->validated_keys, rr, ifindex, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 0; -} - -static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *rr) { - int r; - - assert(t); - assert(rr); - - /* Checks if the RR we are looking for must be signed with an - * RRSIG. This is used for positive responses. */ - - if (t->scope->dnssec_mode == DNSSEC_NO) - return false; - - if (dns_type_is_pseudo(rr->key->type)) - return -EINVAL; - - r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key)); - if (r < 0) - return r; - if (r > 0) - return false; - - switch (rr->key->type) { - - case DNS_TYPE_RRSIG: - /* RRSIGs are the signatures themselves, they need no signing. */ - return false; - - case DNS_TYPE_SOA: - case DNS_TYPE_NS: { - DnsTransaction *dt; - Iterator i; - - /* For SOA or NS RRs we look for a matching DS transaction */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != rr->key->class) - continue; - if (dt->key->type != DNS_TYPE_DS) - continue; - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), DNS_RESOURCE_KEY_NAME(rr->key)); - if (r < 0) - return r; - if (r == 0) - continue; - - /* We found a DS transactions for the SOA/NS - * RRs we are looking at. If it discovered signed DS - * RRs, then we need to be signed, too. */ - - if (!dt->answer_authenticated) - return false; - - return dns_answer_match_key(dt->answer, dt->key, NULL); - } - - /* We found nothing that proves this is safe to leave - * this unauthenticated, hence ask inist on - * authentication. */ - return true; - } - - case DNS_TYPE_DS: - case DNS_TYPE_CNAME: - case DNS_TYPE_DNAME: { - const char *parent = NULL; - DnsTransaction *dt; - Iterator i; - - /* - * CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent SOA. - * - * DS RRs are signed if the parent is signed, hence also look at the parent SOA - */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != rr->key->class) - continue; - if (dt->key->type != DNS_TYPE_SOA) - continue; - - if (!parent) { - parent = DNS_RESOURCE_KEY_NAME(rr->key); - r = dns_name_parent(&parent); - if (r < 0) - return r; - if (r == 0) { - if (rr->key->type == DNS_TYPE_DS) - return true; - - /* A CNAME/DNAME without a parent? That's sooo weird. */ - log_debug("Transaction %" PRIu16 " claims CNAME/DNAME at root. Refusing.", t->id); - return -EBADMSG; - } - } - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), parent); - if (r < 0) - return r; - if (r == 0) - continue; - - return t->answer_authenticated; - } - - return true; - } - - default: { - DnsTransaction *dt; - Iterator i; - - /* Any other kind of RR (including DNSKEY/NSEC/NSEC3). Let's see if our SOA lookup was authenticated */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != rr->key->class) - continue; - if (dt->key->type != DNS_TYPE_SOA) - continue; - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), DNS_RESOURCE_KEY_NAME(rr->key)); - if (r < 0) - return r; - if (r == 0) - continue; - - /* We found the transaction that was supposed to find - * the SOA RR for us. It was successful, but found no - * RR for us. This means we are not at a zone cut. In - * this case, we require authentication if the SOA - * lookup was authenticated too. */ - return t->answer_authenticated; - } - - return true; - }} -} - -static int dns_transaction_in_private_tld(DnsTransaction *t, const DnsResourceKey *key) { - DnsTransaction *dt; - const char *tld; - Iterator i; - int r; - - /* If DNSSEC downgrade mode is on, checks whether the - * specified RR is one level below a TLD we have proven not to - * exist. In such a case we assume that this is a private - * domain, and permit it. - * - * This detects cases like the Fritz!Box router networks. Each - * Fritz!Box router serves a private "fritz.box" zone, in the - * non-existing TLD "box". Requests for the "fritz.box" domain - * are served by the router itself, while requests for the - * "box" domain will result in NXDOMAIN. - * - * Note that this logic is unable to detect cases where a - * router serves a private DNS zone directly under - * non-existing TLD. In such a case we cannot detect whether - * the TLD is supposed to exist or not, as all requests we - * make for it will be answered by the router's zone, and not - * by the root zone. */ - - assert(t); - - if (t->scope->dnssec_mode != DNSSEC_ALLOW_DOWNGRADE) - return false; /* In strict DNSSEC mode what doesn't exist, doesn't exist */ - - tld = DNS_RESOURCE_KEY_NAME(key); - r = dns_name_parent(&tld); - if (r < 0) - return r; - if (r == 0) - return false; /* Already the root domain */ - - if (!dns_name_is_single_label(tld)) - return false; - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != key->class) - continue; - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), tld); - if (r < 0) - return r; - if (r == 0) - continue; - - /* We found an auxiliary lookup we did for the TLD. If - * that returned with NXDOMAIN, we know the TLD didn't - * exist, and hence this might be a private zone. */ - - return dt->answer_rcode == DNS_RCODE_NXDOMAIN; - } - - return false; -} - -static int dns_transaction_requires_nsec(DnsTransaction *t) { - DnsTransaction *dt; - const char *name; - Iterator i; - int r; - - assert(t); - - /* Checks if we need to insist on NSEC/NSEC3 RRs for proving - * this negative reply */ - - if (t->scope->dnssec_mode == DNSSEC_NO) - return false; - - if (dns_type_is_pseudo(t->key->type)) - return -EINVAL; - - r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key)); - if (r < 0) - return r; - if (r > 0) - return false; - - r = dns_transaction_in_private_tld(t, t->key); - if (r < 0) - return r; - if (r > 0) { - /* The lookup is from a TLD that is proven not to - * exist, and we are in downgrade mode, hence ignore - * that fact that we didn't get any NSEC RRs.*/ - - log_info("Detected a negative query %s in a private DNS zone, permitting unsigned response.", dns_transaction_key_string(t)); - return false; - } - - name = DNS_RESOURCE_KEY_NAME(t->key); - - if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS, DNS_TYPE_DS)) { - - /* We got a negative reply for this SOA/NS lookup? If - * so, then we are not at a zone apex, and thus should - * look at the result of the parent SOA lookup. - * - * We got a negative reply for this DS lookup? DS RRs - * are signed when their parent zone is signed, hence - * also check the parent SOA in this case. */ - - r = dns_name_parent(&name); - if (r < 0) - return r; - if (r == 0) - return true; - } - - /* For all other RRs we check the SOA on the same level to see - * if it's signed. */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != t->key->class) - continue; - if (dt->key->type != DNS_TYPE_SOA) - continue; - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), name); - if (r < 0) - return r; - if (r == 0) - continue; - - return dt->answer_authenticated; - } - - /* If in doubt, require NSEC/NSEC3 */ - return true; -} - -static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRecord *rr) { - DnsResourceRecord *rrsig; - bool found = false; - int r; - - /* Checks whether any of the DNSKEYs used for the RRSIGs for - * the specified RRset is authenticated (i.e. has a matching - * DS RR). */ - - r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key)); - if (r < 0) - return r; - if (r > 0) - return false; - - DNS_ANSWER_FOREACH(rrsig, t->answer) { - DnsTransaction *dt; - Iterator i; - - r = dnssec_key_match_rrsig(rr->key, rrsig); - if (r < 0) - return r; - if (r == 0) - continue; - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (dt->key->class != rr->key->class) - continue; - - if (dt->key->type == DNS_TYPE_DNSKEY) { - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer); - if (r < 0) - return r; - if (r == 0) - continue; - - /* OK, we found an auxiliary DNSKEY - * lookup. If that lookup is - * authenticated, report this. */ - - if (dt->answer_authenticated) - return true; - - found = true; - - } else if (dt->key->type == DNS_TYPE_DS) { - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer); - if (r < 0) - return r; - if (r == 0) - continue; - - /* OK, we found an auxiliary DS - * lookup. If that lookup is - * authenticated and non-zero, we - * won! */ - - if (!dt->answer_authenticated) - return false; - - return dns_answer_match_key(dt->answer, dt->key, NULL); - } - } - } - - return found ? false : -ENXIO; -} - -static int dns_transaction_known_signed(DnsTransaction *t, DnsResourceRecord *rr) { - assert(t); - assert(rr); - - /* We know that the root domain is signed, hence if it appears - * not to be signed, there's a problem with the DNS server */ - - return rr->key->class == DNS_CLASS_IN && - dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key)); -} - -static int dns_transaction_check_revoked_trust_anchors(DnsTransaction *t) { - DnsResourceRecord *rr; - int r; - - assert(t); - - /* Maybe warn the user that we encountered a revoked DNSKEY - * for a key from our trust anchor. Note that we don't care - * whether the DNSKEY can be authenticated or not. It's - * sufficient if it is self-signed. */ - - DNS_ANSWER_FOREACH(rr, t->answer) { - r = dns_trust_anchor_check_revoked(&t->scope->manager->trust_anchor, rr, t->answer); - if (r < 0) - return r; - } - - return 0; -} - -static int dns_transaction_invalidate_revoked_keys(DnsTransaction *t) { - bool changed; - int r; - - assert(t); - - /* Removes all DNSKEY/DS objects from t->validated_keys that - * our trust anchors database considers revoked. */ - - do { - DnsResourceRecord *rr; - - changed = false; - - DNS_ANSWER_FOREACH(rr, t->validated_keys) { - r = dns_trust_anchor_is_revoked(&t->scope->manager->trust_anchor, rr); - if (r < 0) - return r; - if (r > 0) { - r = dns_answer_remove_by_rr(&t->validated_keys, rr); - if (r < 0) - return r; - - assert(r > 0); - changed = true; - break; - } - } - } while (changed); - - return 0; -} - -static int dns_transaction_copy_validated(DnsTransaction *t) { - DnsTransaction *dt; - Iterator i; - int r; - - assert(t); - - /* Copy all validated RRs from the auxiliary DNSSEC transactions into our set of validated RRs */ - - SET_FOREACH(dt, t->dnssec_transactions, i) { - - if (DNS_TRANSACTION_IS_LIVE(dt->state)) - continue; - - if (!dt->answer_authenticated) - continue; - - r = dns_answer_extend(&t->validated_keys, dt->answer); - if (r < 0) - return r; - } - - return 0; -} - -int dns_transaction_validate_dnssec(DnsTransaction *t) { - _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL; - enum { - PHASE_DNSKEY, /* Phase #1, only validate DNSKEYs */ - PHASE_NSEC, /* Phase #2, only validate NSEC+NSEC3 */ - PHASE_ALL, /* Phase #3, validate everything else */ - } phase; - DnsResourceRecord *rr; - DnsAnswerFlags flags; - int r; - - assert(t); - - /* We have now collected all DS and DNSKEY RRs in - * t->validated_keys, let's see which RRs we can now - * authenticate with that. */ - - if (t->scope->dnssec_mode == DNSSEC_NO) - return 0; - - /* Already validated */ - if (t->answer_dnssec_result != _DNSSEC_RESULT_INVALID) - return 0; - - /* Our own stuff needs no validation */ - if (IN_SET(t->answer_source, DNS_TRANSACTION_ZONE, DNS_TRANSACTION_TRUST_ANCHOR)) { - t->answer_dnssec_result = DNSSEC_VALIDATED; - t->answer_authenticated = true; - return 0; - } - - /* Cached stuff is not affected by validation. */ - if (t->answer_source != DNS_TRANSACTION_NETWORK) - return 0; - - 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); - return 0; - } - - log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, dns_transaction_key_string(t)); - - /* First, see if this response contains any revoked trust - * anchors we care about */ - r = dns_transaction_check_revoked_trust_anchors(t); - if (r < 0) - return r; - - /* Third, copy all RRs we acquired successfully from auxiliary RRs over. */ - r = dns_transaction_copy_validated(t); - if (r < 0) - return r; - - /* Second, see if there are DNSKEYs we already know a - * validated DS for. */ - r = dns_transaction_validate_dnskey_by_ds(t); - if (r < 0) - return r; - - /* Fourth, remove all DNSKEY and DS RRs again that our trust - * anchor says are revoked. After all we might have marked - * some keys revoked above, but they might still be lingering - * in our validated_keys list. */ - r = dns_transaction_invalidate_revoked_keys(t); - if (r < 0) - return r; - - phase = PHASE_DNSKEY; - for (;;) { - bool changed = false, have_nsec = false; - - DNS_ANSWER_FOREACH(rr, t->answer) { - DnsResourceRecord *rrsig = NULL; - DnssecResult result; - - switch (rr->key->type) { - - case DNS_TYPE_RRSIG: - continue; - - case DNS_TYPE_DNSKEY: - /* We validate DNSKEYs only in the DNSKEY and ALL phases */ - if (phase == PHASE_NSEC) - continue; - break; - - case DNS_TYPE_NSEC: - case DNS_TYPE_NSEC3: - have_nsec = true; - - /* We validate NSEC/NSEC3 only in the NSEC and ALL phases */ - if (phase == PHASE_DNSKEY) - continue; - - break; - - default: - /* We validate all other RRs only in the ALL phases */ - if (phase != PHASE_ALL) - continue; - - break; - } - - r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result, &rrsig); - if (r < 0) - return r; - - log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result)); - - if (result == DNSSEC_VALIDATED) { - - if (rr->key->type == DNS_TYPE_DNSKEY) { - /* If we just validated a - * DNSKEY RRset, then let's - * add these keys to the set - * of validated keys for this - * transaction. */ - - r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - /* some of the DNSKEYs we just - * added might already have - * been revoked, remove them - * again in that case. */ - r = dns_transaction_invalidate_revoked_keys(t); - if (r < 0) - return r; - } - - /* Add the validated RRset to the new - * list of validated RRsets, and - * remove it from the unvalidated - * RRsets. We mark the RRset as - * authenticated and cacheable. */ - r = dns_answer_move_by_key(&validated, &t->answer, rr->key, DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_SECURE, rr->key); - - /* Exit the loop, we dropped something from the answer, start from the beginning */ - changed = true; - break; - } - - /* If we haven't read all DNSKEYs yet a negative result of the validation is irrelevant, as - * there might be more DNSKEYs coming. Similar, if we haven't read all NSEC/NSEC3 RRs yet, we - * cannot do positive wildcard proofs yet, as those require the NSEC/NSEC3 RRs. */ - if (phase != PHASE_ALL) - continue; - - if (result == DNSSEC_VALIDATED_WILDCARD) { - bool authenticated = false; - const char *source; - - /* This RRset validated, but as a wildcard. This means we need to prove via NSEC/NSEC3 - * that no matching non-wildcard RR exists.*/ - - /* First step, determine the source of synthesis */ - r = dns_resource_record_source(rrsig, &source); - if (r < 0) - return r; - - r = dnssec_test_positive_wildcard( - validated, - DNS_RESOURCE_KEY_NAME(rr->key), - source, - rrsig->rrsig.signer, - &authenticated); - - /* Unless the NSEC proof showed that the key really doesn't exist something is off. */ - if (r == 0) - result = DNSSEC_INVALID; - else { - r = dns_answer_move_by_key(&validated, &t->answer, rr->key, authenticated ? (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE) : 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, rr->key); - - /* Exit the loop, we dropped something from the answer, start from the beginning */ - changed = true; - break; - } - } - - if (result == DNSSEC_NO_SIGNATURE) { - r = dns_transaction_requires_rrsig(t, rr); - if (r < 0) - return r; - if (r == 0) { - /* Data does not require signing. In that case, just copy it over, - * but remember that this is by no means authenticated.*/ - r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); - changed = true; - break; - } - - r = dns_transaction_known_signed(t, rr); - if (r < 0) - return r; - if (r > 0) { - /* This is an RR we know has to be signed. If it isn't this means - * the server is not attaching RRSIGs, hence complain. */ - - dns_server_packet_rrsig_missing(t->server, t->current_feature_level); - - if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { - - /* Downgrading is OK? If so, just consider the information unsigned */ - - r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); - changed = true; - break; - } - - /* Otherwise, fail */ - t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; - return 0; - } - - r = dns_transaction_in_private_tld(t, rr->key); - if (r < 0) - return r; - if (r > 0) { - _cleanup_free_ char *s = NULL; - - /* The data is from a TLD that is proven not to exist, and we are in downgrade - * mode, hence ignore the fact that this was not signed. */ - - (void) dns_resource_key_to_string(rr->key, &s); - log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.", strna(s ? strstrip(s) : NULL)); - - r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); - changed = true; - break; - } - } - - if (IN_SET(result, - DNSSEC_MISSING_KEY, - DNSSEC_SIGNATURE_EXPIRED, - DNSSEC_UNSUPPORTED_ALGORITHM)) { - - r = dns_transaction_dnskey_authenticated(t, rr); - if (r < 0 && r != -ENXIO) - return r; - if (r == 0) { - /* The DNSKEY transaction was not authenticated, this means there's - * no DS for this, which means it's OK if no keys are found for this signature. */ - - r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); - if (r < 0) - return r; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); - changed = true; - break; - } - } - - r = dns_transaction_is_primary_response(t, rr); - if (r < 0) - return r; - if (r > 0) { - - /* Look for a matching DNAME for this CNAME */ - r = dns_answer_has_dname_for_cname(t->answer, rr); - if (r < 0) - return r; - if (r == 0) { - /* Also look among the stuff we already validated */ - r = dns_answer_has_dname_for_cname(validated, rr); - if (r < 0) - return r; - } - - if (r == 0) { - if (IN_SET(result, - DNSSEC_INVALID, - DNSSEC_SIGNATURE_EXPIRED, - DNSSEC_NO_SIGNATURE)) - manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, rr->key); - else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */ - manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, rr->key); - - /* This is a primary response to our question, and it failed validation. That's - * fatal. */ - t->answer_dnssec_result = result; - return 0; - } - - /* This is a primary response, but we do have a DNAME RR in the RR that can replay this - * CNAME, hence rely on that, and we can remove the CNAME in favour of it. */ - } - - /* This is just some auxiliary data. Just remove the RRset and continue. */ - r = dns_answer_remove_by_key(&t->answer, rr->key); - if (r < 0) - return r; - - /* Exit the loop, we dropped something from the answer, start from the beginning */ - changed = true; - break; - } - - /* Restart the inner loop as long as we managed to achieve something */ - if (changed) - continue; - - if (phase == PHASE_DNSKEY && have_nsec) { - /* OK, we processed all DNSKEYs, and there are NSEC/NSEC3 RRs, look at those now. */ - phase = PHASE_NSEC; - continue; - } - - if (phase != PHASE_ALL) { - /* OK, we processed all DNSKEYs and NSEC/NSEC3 RRs, look at all the rest now. Note that in this - * third phase we start to remove RRs we couldn't validate. */ - phase = PHASE_ALL; - continue; - } - - /* We're done */ - break; - } - - dns_answer_unref(t->answer); - t->answer = validated; - validated = NULL; - - /* At this point the answer only contains validated - * RRsets. Now, let's see if it actually answers the question - * we asked. If so, great! If it doesn't, then see if - * NSEC/NSEC3 can prove this. */ - r = dns_transaction_has_positive_answer(t, &flags); - if (r > 0) { - /* Yes, it answers the question! */ - - if (flags & DNS_ANSWER_AUTHENTICATED) { - /* The answer is fully authenticated, yay. */ - t->answer_dnssec_result = DNSSEC_VALIDATED; - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_authenticated = true; - } else { - /* The answer is not fully authenticated. */ - t->answer_dnssec_result = DNSSEC_UNSIGNED; - t->answer_authenticated = false; - } - - } else if (r == 0) { - DnssecNsecResult nr; - bool authenticated = false; - - /* Bummer! Let's check NSEC/NSEC3 */ - r = dnssec_nsec_test(t->answer, t->key, &nr, &authenticated, &t->answer_nsec_ttl); - if (r < 0) - return r; - - switch (nr) { - - case DNSSEC_NSEC_NXDOMAIN: - /* NSEC proves the domain doesn't exist. Very good. */ - log_debug("Proved NXDOMAIN via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t)); - t->answer_dnssec_result = DNSSEC_VALIDATED; - t->answer_rcode = DNS_RCODE_NXDOMAIN; - t->answer_authenticated = authenticated; - - manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); - break; - - case DNSSEC_NSEC_NODATA: - /* NSEC proves that there's no data here, very good. */ - log_debug("Proved NODATA via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t)); - t->answer_dnssec_result = DNSSEC_VALIDATED; - t->answer_rcode = DNS_RCODE_SUCCESS; - t->answer_authenticated = authenticated; - - manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); - break; - - case DNSSEC_NSEC_OPTOUT: - /* NSEC3 says the data might not be signed */ - log_debug("Data is NSEC3 opt-out via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t)); - t->answer_dnssec_result = DNSSEC_UNSIGNED; - t->answer_authenticated = false; - - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); - break; - - case DNSSEC_NSEC_NO_RR: - /* No NSEC data? Bummer! */ - - r = dns_transaction_requires_nsec(t); - if (r < 0) - return r; - if (r > 0) { - t->answer_dnssec_result = DNSSEC_NO_SIGNATURE; - manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); - } else { - t->answer_dnssec_result = DNSSEC_UNSIGNED; - t->answer_authenticated = false; - manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); - } - - break; - - case DNSSEC_NSEC_UNSUPPORTED_ALGORITHM: - /* We don't know the NSEC3 algorithm used? */ - t->answer_dnssec_result = DNSSEC_UNSUPPORTED_ALGORITHM; - manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, t->key); - break; - - case DNSSEC_NSEC_FOUND: - case DNSSEC_NSEC_CNAME: - /* NSEC says it needs to be there, but we couldn't find it? Bummer! */ - t->answer_dnssec_result = DNSSEC_NSEC_MISMATCH; - manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); - break; - - default: - assert_not_reached("Unexpected NSEC result."); - } - } - - return 1; -} - -const char *dns_transaction_key_string(DnsTransaction *t) { - assert(t); - - if (!t->key_string) { - if (dns_resource_key_to_string(t->key, &t->key_string) < 0) - return "n/a"; - } - - return strstrip(t->key_string); -} - -static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = { - [DNS_TRANSACTION_NULL] = "null", - [DNS_TRANSACTION_PENDING] = "pending", - [DNS_TRANSACTION_VALIDATING] = "validating", - [DNS_TRANSACTION_RCODE_FAILURE] = "rcode-failure", - [DNS_TRANSACTION_SUCCESS] = "success", - [DNS_TRANSACTION_NO_SERVERS] = "no-servers", - [DNS_TRANSACTION_TIMEOUT] = "timeout", - [DNS_TRANSACTION_ATTEMPTS_MAX_REACHED] = "attempts-max-reached", - [DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply", - [DNS_TRANSACTION_ERRNO] = "errno", - [DNS_TRANSACTION_ABORTED] = "aborted", - [DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed", - [DNS_TRANSACTION_NO_TRUST_ANCHOR] = "no-trust-anchor", - [DNS_TRANSACTION_RR_TYPE_UNSUPPORTED] = "rr-type-unsupported", - [DNS_TRANSACTION_NETWORK_DOWN] = "network-down", - [DNS_TRANSACTION_NOT_FOUND] = "not-found", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState); - -static const char* const dns_transaction_source_table[_DNS_TRANSACTION_SOURCE_MAX] = { - [DNS_TRANSACTION_NETWORK] = "network", - [DNS_TRANSACTION_CACHE] = "cache", - [DNS_TRANSACTION_ZONE] = "zone", - [DNS_TRANSACTION_TRUST_ANCHOR] = "trust-anchor", -}; -DEFINE_STRING_TABLE_LOOKUP(dns_transaction_source, DnsTransactionSource); diff --git a/src/resolve/resolved-dns-transaction.h b/src/resolve/resolved-dns-transaction.h deleted file mode 100644 index 4617194711..0000000000 --- a/src/resolve/resolved-dns-transaction.h +++ /dev/null @@ -1,177 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -typedef struct DnsTransaction DnsTransaction; -typedef enum DnsTransactionState DnsTransactionState; -typedef enum DnsTransactionSource DnsTransactionSource; - -enum DnsTransactionState { - DNS_TRANSACTION_NULL, - DNS_TRANSACTION_PENDING, - DNS_TRANSACTION_VALIDATING, - DNS_TRANSACTION_RCODE_FAILURE, - DNS_TRANSACTION_SUCCESS, - DNS_TRANSACTION_NO_SERVERS, - DNS_TRANSACTION_TIMEOUT, - DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, - DNS_TRANSACTION_INVALID_REPLY, - DNS_TRANSACTION_ERRNO, - DNS_TRANSACTION_ABORTED, - DNS_TRANSACTION_DNSSEC_FAILED, - DNS_TRANSACTION_NO_TRUST_ANCHOR, - DNS_TRANSACTION_RR_TYPE_UNSUPPORTED, - DNS_TRANSACTION_NETWORK_DOWN, - DNS_TRANSACTION_NOT_FOUND, /* like NXDOMAIN, but when LLMNR/TCP connections fail */ - _DNS_TRANSACTION_STATE_MAX, - _DNS_TRANSACTION_STATE_INVALID = -1 -}; - -#define DNS_TRANSACTION_IS_LIVE(state) IN_SET((state), DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING) - -enum DnsTransactionSource { - DNS_TRANSACTION_NETWORK, - DNS_TRANSACTION_CACHE, - DNS_TRANSACTION_ZONE, - DNS_TRANSACTION_TRUST_ANCHOR, - _DNS_TRANSACTION_SOURCE_MAX, - _DNS_TRANSACTION_SOURCE_INVALID = -1 -}; - -#include "resolved-dns-answer.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-question.h" -#include "resolved-dns-scope.h" - -struct DnsTransaction { - DnsScope *scope; - - DnsResourceKey *key; - char *key_string; - - DnsTransactionState state; - - uint16_t id; - - bool tried_stream:1; - - bool initial_jitter_scheduled:1; - bool initial_jitter_elapsed:1; - - DnsPacket *sent, *received; - - DnsAnswer *answer; - int answer_rcode; - DnssecResult answer_dnssec_result; - DnsTransactionSource answer_source; - uint32_t answer_nsec_ttl; - int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ - - /* Indicates whether the primary answer is authenticated, - * i.e. whether the RRs from answer which directly match the - * question are authenticated, or, if there are none, whether - * the NODATA or NXDOMAIN case is. It says nothing about - * additional RRs listed in the answer, however they have - * their own DNS_ANSWER_AUTHORIZED FLAGS. Note that this bit - * is defined different than the AD bit in DNS packets, as - * that covers more than just the actual primary answer. */ - bool answer_authenticated; - - /* Contains DNSKEY, DS, SOA RRs we already verified and need - * to authenticate this reply */ - DnsAnswer *validated_keys; - - usec_t start_usec; - usec_t next_attempt_after; - sd_event_source *timeout_event_source; - unsigned n_attempts; - - /* UDP connection logic, if we need it */ - int dns_udp_fd; - sd_event_source *dns_udp_event_source; - - /* TCP connection logic, if we need it */ - DnsStream *stream; - - /* The active server */ - DnsServer *server; - - /* The features of the DNS server at time of transaction start */ - DnsServerFeatureLevel current_feature_level; - - /* Query candidates this transaction is referenced by and that - * shall be notified about this specific transaction - * completing. */ - Set *notify_query_candidates; - - /* Zone items this transaction is referenced by and that shall - * be notified about completion. */ - Set *notify_zone_items; - - /* Other transactions that this transactions is referenced by - * and that shall be notified about completion. This is used - * when transactions want to validate their RRsets, but need - * another DNSKEY or DS RR to do so. */ - Set *notify_transactions; - - /* The opposite direction: the transactions this transaction - * created in order to request DNSKEY or DS RRs. */ - Set *dnssec_transactions; - - unsigned block_gc; - - LIST_FIELDS(DnsTransaction, transactions_by_scope); -}; - -int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key); -DnsTransaction* dns_transaction_free(DnsTransaction *t); - -bool dns_transaction_gc(DnsTransaction *t); -int dns_transaction_go(DnsTransaction *t); - -void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p); -void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state); - -void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source); -int dns_transaction_validate_dnssec(DnsTransaction *t); -int dns_transaction_request_dnssec_keys(DnsTransaction *t); - -const char *dns_transaction_key_string(DnsTransaction *t); - -const char* dns_transaction_state_to_string(DnsTransactionState p) _const_; -DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; - -const char* dns_transaction_source_to_string(DnsTransactionSource p) _const_; -DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_; - -/* LLMNR Jitter interval, see RFC 4795 Section 7 */ -#define LLMNR_JITTER_INTERVAL_USEC (100 * USEC_PER_MSEC) - -/* mDNS Jitter interval, see RFC 6762 Section 5.2 */ -#define MDNS_JITTER_MIN_USEC (20 * USEC_PER_MSEC) -#define MDNS_JITTER_RANGE_USEC (100 * USEC_PER_MSEC) - -/* Maximum attempts to send DNS requests, across all DNS servers */ -#define DNS_TRANSACTION_ATTEMPTS_MAX 16 - -/* Maximum attempts to send LLMNR requests, see RFC 4795 Section 2.7 */ -#define LLMNR_TRANSACTION_ATTEMPTS_MAX 3 - -#define TRANSACTION_ATTEMPTS_MAX(p) ((p) == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_ATTEMPTS_MAX : DNS_TRANSACTION_ATTEMPTS_MAX) diff --git a/src/resolve/resolved-dns-trust-anchor.c b/src/resolve/resolved-dns-trust-anchor.c deleted file mode 100644 index a75337eb6a..0000000000 --- a/src/resolve/resolved-dns-trust-anchor.c +++ /dev/null @@ -1,743 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include - -#include "alloc-util.h" -#include "conf-files.h" -#include "def.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "fileio.h" -#include "hexdecoct.h" -#include "parse-util.h" -#include "resolved-dns-trust-anchor.h" -#include "resolved-dns-dnssec.h" -#include "set.h" -#include "string-util.h" -#include "strv.h" - -static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("dnssec-trust-anchors.d"); - -/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml, retrieved December 2015 */ -static const uint8_t root_digest[] = - { 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60, - 0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 }; - -static bool dns_trust_anchor_knows_domain_positive(DnsTrustAnchor *d, const char *name) { - assert(d); - - /* Returns true if there's an entry for the specified domain - * name in our trust anchor */ - - return - hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DNSKEY, name)) || - hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, name)); -} - -static int dns_trust_anchor_add_builtin_positive(DnsTrustAnchor *d) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - int r; - - assert(d); - - r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - /* Only add the built-in trust anchor if there's neither a DS - * nor a DNSKEY defined for the root domain. That way users - * have an easy way to override the root domain DS/DNSKEY - * data. */ - if (dns_trust_anchor_knows_domain_positive(d, ".")) - return 0; - - /* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */ - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, ""); - if (!rr) - return -ENOMEM; - - rr->ds.key_tag = 19036; - rr->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; - rr->ds.digest_type = DNSSEC_DIGEST_SHA256; - rr->ds.digest_size = sizeof(root_digest); - rr->ds.digest = memdup(root_digest, rr->ds.digest_size); - if (!rr->ds.digest) - return -ENOMEM; - - answer = dns_answer_new(1); - if (!answer) - return -ENOMEM; - - r = dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - r = hashmap_put(d->positive_by_key, rr->key, answer); - if (r < 0) - return r; - - answer = NULL; - return 0; -} - -static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) { - - static const char private_domains[] = - /* RFC 6761 says that .test is a special domain for - * testing and not to be installed in the root zone */ - "test\0" - - /* RFC 6761 says that these reverse IP lookup ranges - * are for private addresses, and hence should not - * show up in the root zone */ - "10.in-addr.arpa\0" - "16.172.in-addr.arpa\0" - "17.172.in-addr.arpa\0" - "18.172.in-addr.arpa\0" - "19.172.in-addr.arpa\0" - "20.172.in-addr.arpa\0" - "21.172.in-addr.arpa\0" - "22.172.in-addr.arpa\0" - "23.172.in-addr.arpa\0" - "24.172.in-addr.arpa\0" - "25.172.in-addr.arpa\0" - "26.172.in-addr.arpa\0" - "27.172.in-addr.arpa\0" - "28.172.in-addr.arpa\0" - "29.172.in-addr.arpa\0" - "30.172.in-addr.arpa\0" - "31.172.in-addr.arpa\0" - "168.192.in-addr.arpa\0" - - /* RFC 6762 reserves the .local domain for Multicast - * DNS, it hence cannot appear in the root zone. (Note - * that we by default do not route .local traffic to - * DNS anyway, except when a configured search domain - * suggests so.) */ - "local\0" - - /* These two are well known, popular private zone - * TLDs, that are blocked from delegation, according - * to: - * http://icannwiki.com/Name_Collision#NGPC_Resolution - * - * There's also ongoing work on making this official - * in an RRC: - * https://www.ietf.org/archive/id/draft-chapin-additional-reserved-tlds-02.txt */ - "home\0" - "corp\0" - - /* The following four TLDs are suggested for private - * zones in RFC 6762, Appendix G, and are hence very - * unlikely to be made official TLDs any day soon */ - "lan\0" - "intranet\0" - "internal\0" - "private\0"; - - const char *name; - int r; - - assert(d); - - /* Only add the built-in trust anchor if there's no negative - * trust anchor defined at all. This enables easy overriding - * of negative trust anchors. */ - - if (set_size(d->negative_by_name) > 0) - return 0; - - r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); - if (r < 0) - return r; - - /* We add a couple of domains as default negative trust - * anchors, where it's very unlikely they will be installed in - * the root zone. If they exist they must be private, and thus - * unsigned. */ - - NULSTR_FOREACH(name, private_domains) { - - if (dns_trust_anchor_knows_domain_positive(d, name)) - continue; - - r = set_put_strdup(d->negative_by_name, name); - if (r < 0) - return r; - } - - return 0; -} - -static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - _cleanup_free_ char *domain = NULL, *class = NULL, *type = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnsAnswer *old_answer = NULL; - const char *p = s; - int r; - - assert(d); - assert(line); - - r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); - if (r < 0) - return log_warning_errno(r, "Unable to parse domain in line %s:%u: %m", path, line); - - if (!dns_name_is_valid(domain)) { - log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); - return -EINVAL; - } - - r = extract_many_words(&p, NULL, 0, &class, &type, NULL); - if (r < 0) - return log_warning_errno(r, "Unable to parse class and type in line %s:%u: %m", path, line); - if (r != 2) { - log_warning("Missing class or type in line %s:%u", path, line); - return -EINVAL; - } - - if (!strcaseeq(class, "IN")) { - log_warning("RR class %s is not supported, ignoring line %s:%u.", class, path, line); - return -EINVAL; - } - - if (strcaseeq(type, "DS")) { - _cleanup_free_ char *key_tag = NULL, *algorithm = NULL, *digest_type = NULL, *digest = NULL; - _cleanup_free_ void *dd = NULL; - uint16_t kt; - int a, dt; - size_t l; - - r = extract_many_words(&p, NULL, 0, &key_tag, &algorithm, &digest_type, &digest, NULL); - if (r < 0) { - log_warning_errno(r, "Failed to parse DS parameters on line %s:%u: %m", path, line); - return -EINVAL; - } - if (r != 4) { - log_warning("Missing DS parameters on line %s:%u", path, line); - return -EINVAL; - } - - r = safe_atou16(key_tag, &kt); - if (r < 0) - return log_warning_errno(r, "Failed to parse DS key tag %s on line %s:%u: %m", key_tag, path, line); - - a = dnssec_algorithm_from_string(algorithm); - if (a < 0) { - log_warning("Failed to parse DS algorithm %s on line %s:%u", algorithm, path, line); - return -EINVAL; - } - - dt = dnssec_digest_from_string(digest_type); - if (dt < 0) { - log_warning("Failed to parse DS digest type %s on line %s:%u", digest_type, path, line); - return -EINVAL; - } - - r = unhexmem(digest, strlen(digest), &dd, &l); - if (r < 0) { - log_warning("Failed to parse DS digest %s on line %s:%u", digest, path, line); - return -EINVAL; - } - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, domain); - if (!rr) - return log_oom(); - - rr->ds.key_tag = kt; - rr->ds.algorithm = a; - rr->ds.digest_type = dt; - rr->ds.digest_size = l; - rr->ds.digest = dd; - dd = NULL; - - } else if (strcaseeq(type, "DNSKEY")) { - _cleanup_free_ char *flags = NULL, *protocol = NULL, *algorithm = NULL, *key = NULL; - _cleanup_free_ void *k = NULL; - uint16_t f; - size_t l; - int a; - - r = extract_many_words(&p, NULL, 0, &flags, &protocol, &algorithm, &key, NULL); - if (r < 0) - return log_warning_errno(r, "Failed to parse DNSKEY parameters on line %s:%u: %m", path, line); - if (r != 4) { - log_warning("Missing DNSKEY parameters on line %s:%u", path, line); - return -EINVAL; - } - - if (!streq(protocol, "3")) { - log_warning("DNSKEY Protocol is not 3 on line %s:%u", path, line); - return -EINVAL; - } - - r = safe_atou16(flags, &f); - if (r < 0) - return log_warning_errno(r, "Failed to parse DNSKEY flags field %s on line %s:%u", flags, path, line); - if ((f & DNSKEY_FLAG_ZONE_KEY) == 0) { - log_warning("DNSKEY lacks zone key bit set on line %s:%u", path, line); - return -EINVAL; - } - if ((f & DNSKEY_FLAG_REVOKE)) { - log_warning("DNSKEY is already revoked on line %s:%u", path, line); - return -EINVAL; - } - - a = dnssec_algorithm_from_string(algorithm); - if (a < 0) { - log_warning("Failed to parse DNSKEY algorithm %s on line %s:%u", algorithm, path, line); - return -EINVAL; - } - - r = unbase64mem(key, strlen(key), &k, &l); - if (r < 0) - return log_warning_errno(r, "Failed to parse DNSKEY key data %s on line %s:%u", key, path, line); - - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, domain); - if (!rr) - return log_oom(); - - rr->dnskey.flags = f; - rr->dnskey.protocol = 3; - rr->dnskey.algorithm = a; - rr->dnskey.key_size = l; - rr->dnskey.key = k; - k = NULL; - - } else { - log_warning("RR type %s is not supported, ignoring line %s:%u.", type, path, line); - return -EINVAL; - } - - if (!isempty(p)) { - log_warning("Trailing garbage on line %s:%u, ignoring line.", path, line); - return -EINVAL; - } - - r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); - if (r < 0) - return log_oom(); - - old_answer = hashmap_get(d->positive_by_key, rr->key); - answer = dns_answer_ref(old_answer); - - r = dns_answer_add_extend(&answer, rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return log_error_errno(r, "Failed to add trust anchor RR: %m"); - - r = hashmap_replace(d->positive_by_key, rr->key, answer); - if (r < 0) - return log_error_errno(r, "Failed to add answer to trust anchor: %m"); - - old_answer = dns_answer_unref(old_answer); - answer = NULL; - - return 0; -} - -static int dns_trust_anchor_load_negative(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { - _cleanup_free_ char *domain = NULL; - const char *p = s; - int r; - - assert(d); - assert(line); - - r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); - if (r < 0) - return log_warning_errno(r, "Unable to parse line %s:%u: %m", path, line); - - if (!dns_name_is_valid(domain)) { - log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); - return -EINVAL; - } - - if (!isempty(p)) { - log_warning("Trailing garbage at line %s:%u, ignoring line.", path, line); - return -EINVAL; - } - - r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); - if (r < 0) - return log_oom(); - - r = set_put(d->negative_by_name, domain); - if (r < 0) - return log_oom(); - if (r > 0) - domain = NULL; - - return 0; -} - -static int dns_trust_anchor_load_files( - DnsTrustAnchor *d, - const char *suffix, - int (*loader)(DnsTrustAnchor *d, const char *path, unsigned n, const char *line)) { - - _cleanup_strv_free_ char **files = NULL; - char **f; - int r; - - assert(d); - assert(suffix); - assert(loader); - - r = conf_files_list_nulstr(&files, suffix, NULL, trust_anchor_dirs); - if (r < 0) - return log_error_errno(r, "Failed to enumerate %s trust anchor files: %m", suffix); - - STRV_FOREACH(f, files) { - _cleanup_fclose_ FILE *g = NULL; - char line[LINE_MAX]; - unsigned n = 0; - - g = fopen(*f, "r"); - if (!g) { - if (errno == ENOENT) - continue; - - log_warning_errno(errno, "Failed to open %s: %m", *f); - continue; - } - - FOREACH_LINE(line, g, log_warning_errno(errno, "Failed to read %s, ignoring: %m", *f)) { - char *l; - - n++; - - l = strstrip(line); - if (isempty(l)) - continue; - - if (*l == ';') - continue; - - (void) loader(d, *f, n, l); - } - } - - return 0; -} - -static int domain_name_cmp(const void *a, const void *b) { - char **x = (char**) a, **y = (char**) b; - - return dns_name_compare_func(*x, *y); -} - -static int dns_trust_anchor_dump(DnsTrustAnchor *d) { - DnsAnswer *a; - Iterator i; - - assert(d); - - if (hashmap_isempty(d->positive_by_key)) - log_info("No positive trust anchors defined."); - else { - log_info("Positive Trust Anchors:"); - HASHMAP_FOREACH(a, d->positive_by_key, i) { - DnsResourceRecord *rr; - - DNS_ANSWER_FOREACH(rr, a) - log_info("%s", dns_resource_record_to_string(rr)); - } - } - - if (set_isempty(d->negative_by_name)) - log_info("No negative trust anchors defined."); - else { - _cleanup_free_ char **l = NULL, *j = NULL; - - l = set_get_strv(d->negative_by_name); - if (!l) - return log_oom(); - - qsort_safe(l, set_size(d->negative_by_name), sizeof(char*), domain_name_cmp); - - j = strv_join(l, " "); - if (!j) - return log_oom(); - - log_info("Negative trust anchors: %s", j); - } - - return 0; -} - -int dns_trust_anchor_load(DnsTrustAnchor *d) { - int r; - - assert(d); - - /* If loading things from disk fails, we don't consider this fatal */ - (void) dns_trust_anchor_load_files(d, ".positive", dns_trust_anchor_load_positive); - (void) dns_trust_anchor_load_files(d, ".negative", dns_trust_anchor_load_negative); - - /* However, if the built-in DS fails, then we have a problem. */ - r = dns_trust_anchor_add_builtin_positive(d); - if (r < 0) - return log_error_errno(r, "Failed to add built-in positive trust anchor: %m"); - - r = dns_trust_anchor_add_builtin_negative(d); - if (r < 0) - return log_error_errno(r, "Failed to add built-in negative trust anchor: %m"); - - dns_trust_anchor_dump(d); - - return 0; -} - -void dns_trust_anchor_flush(DnsTrustAnchor *d) { - DnsAnswer *a; - DnsResourceRecord *rr; - - assert(d); - - while ((a = hashmap_steal_first(d->positive_by_key))) - dns_answer_unref(a); - d->positive_by_key = hashmap_free(d->positive_by_key); - - while ((rr = set_steal_first(d->revoked_by_rr))) - dns_resource_record_unref(rr); - d->revoked_by_rr = set_free(d->revoked_by_rr); - - d->negative_by_name = set_free_free(d->negative_by_name); -} - -int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey *key, DnsAnswer **ret) { - DnsAnswer *a; - - assert(d); - assert(key); - assert(ret); - - /* We only serve DS and DNSKEY RRs. */ - if (!IN_SET(key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) - return 0; - - a = hashmap_get(d->positive_by_key, key); - if (!a) - return 0; - - *ret = dns_answer_ref(a); - return 1; -} - -int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name) { - assert(d); - assert(name); - - return set_contains(d->negative_by_name, name); -} - -static int dns_trust_anchor_revoked_put(DnsTrustAnchor *d, DnsResourceRecord *rr) { - int r; - - assert(d); - - r = set_ensure_allocated(&d->revoked_by_rr, &dns_resource_record_hash_ops); - if (r < 0) - return r; - - r = set_put(d->revoked_by_rr, rr); - if (r < 0) - return r; - if (r > 0) - dns_resource_record_ref(rr); - - return r; -} - -static int dns_trust_anchor_remove_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { - _cleanup_(dns_answer_unrefp) DnsAnswer *new_answer = NULL; - DnsAnswer *old_answer; - int r; - - /* Remember that this is a revoked trust anchor RR */ - r = dns_trust_anchor_revoked_put(d, rr); - if (r < 0) - return r; - - /* Remove this from the positive trust anchor */ - old_answer = hashmap_get(d->positive_by_key, rr->key); - if (!old_answer) - return 0; - - new_answer = dns_answer_ref(old_answer); - - r = dns_answer_remove_by_rr(&new_answer, rr); - if (r <= 0) - return r; - - /* We found the key! Warn the user */ - log_struct(LOG_WARNING, - LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED), - LOG_MESSAGE("DNSSEC Trust anchor %s has been revoked. Please update the trust anchor, or upgrade your operating system."), strna(dns_resource_record_to_string(rr)), - "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr), - NULL); - - if (dns_answer_size(new_answer) <= 0) { - assert_se(hashmap_remove(d->positive_by_key, rr->key) == old_answer); - dns_answer_unref(old_answer); - return 1; - } - - r = hashmap_replace(d->positive_by_key, new_answer->items[0].rr->key, new_answer); - if (r < 0) - return r; - - new_answer = NULL; - dns_answer_unref(old_answer); - return 1; -} - -static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceRecord *revoked_dnskey) { - DnsAnswer *a; - int r; - - assert(d); - assert(revoked_dnskey); - assert(revoked_dnskey->key->type == DNS_TYPE_DNSKEY); - assert(revoked_dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE); - - a = hashmap_get(d->positive_by_key, revoked_dnskey->key); - if (a) { - DnsResourceRecord *anchor; - - /* First, look for the precise DNSKEY in our trust anchor database */ - - DNS_ANSWER_FOREACH(anchor, a) { - - if (anchor->dnskey.protocol != revoked_dnskey->dnskey.protocol) - continue; - - if (anchor->dnskey.algorithm != revoked_dnskey->dnskey.algorithm) - continue; - - if (anchor->dnskey.key_size != revoked_dnskey->dnskey.key_size) - continue; - - /* Note that we allow the REVOKE bit to be - * different! It will be set in the revoked - * key, but unset in our version of it */ - if (((anchor->dnskey.flags ^ revoked_dnskey->dnskey.flags) | DNSKEY_FLAG_REVOKE) != DNSKEY_FLAG_REVOKE) - continue; - - if (memcmp(anchor->dnskey.key, revoked_dnskey->dnskey.key, anchor->dnskey.key_size) != 0) - continue; - - dns_trust_anchor_remove_revoked(d, anchor); - break; - } - } - - a = hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(revoked_dnskey->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(revoked_dnskey->key))); - if (a) { - DnsResourceRecord *anchor; - - /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */ - - DNS_ANSWER_FOREACH(anchor, a) { - - /* We set mask_revoke to true here, since our - * DS fingerprint will be the one of the - * unrevoked DNSKEY, but the one we got passed - * here has the bit set. */ - r = dnssec_verify_dnskey_by_ds(revoked_dnskey, anchor, true); - if (r < 0) - return r; - if (r == 0) - continue; - - dns_trust_anchor_remove_revoked(d, anchor); - break; - } - } - - return 0; -} - -int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs) { - DnsResourceRecord *rrsig; - int r; - - assert(d); - assert(dnskey); - - /* Looks if "dnskey" is a self-signed RR that has been revoked - * and matches one of our trust anchor entries. If so, removes - * it from the trust anchor and returns > 0. */ - - if (dnskey->key->type != DNS_TYPE_DNSKEY) - return 0; - - /* Is this DNSKEY revoked? */ - if ((dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE) == 0) - return 0; - - /* Could this be interesting to us at all? If not, - * there's no point in looking for and verifying a - * self-signed RRSIG. */ - if (!dns_trust_anchor_knows_domain_positive(d, DNS_RESOURCE_KEY_NAME(dnskey->key))) - return 0; - - /* Look for a self-signed RRSIG in the other rrs belonging to this DNSKEY */ - DNS_ANSWER_FOREACH(rrsig, rrs) { - DnssecResult result; - - if (rrsig->key->type != DNS_TYPE_RRSIG) - continue; - - r = dnssec_rrsig_match_dnskey(rrsig, dnskey, true); - if (r < 0) - return r; - if (r == 0) - continue; - - r = dnssec_verify_rrset(rrs, dnskey->key, rrsig, dnskey, USEC_INFINITY, &result); - if (r < 0) - return r; - if (result != DNSSEC_VALIDATED) - continue; - - /* Bingo! This is a revoked self-signed DNSKEY. Let's - * see if this precise one exists in our trust anchor - * database, too. */ - r = dns_trust_anchor_check_revoked_one(d, dnskey); - if (r < 0) - return r; - - return 1; - } - - return 0; -} - -int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { - assert(d); - - if (!IN_SET(rr->key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) - return 0; - - return set_contains(d->revoked_by_rr, rr); -} diff --git a/src/resolve/resolved-dns-trust-anchor.h b/src/resolve/resolved-dns-trust-anchor.h deleted file mode 100644 index 635c75fde5..0000000000 --- a/src/resolve/resolved-dns-trust-anchor.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -typedef struct DnsTrustAnchor DnsTrustAnchor; - -#include "hashmap.h" -#include "resolved-dns-answer.h" -#include "resolved-dns-rr.h" - -/* This contains a fixed database mapping domain names to DS or DNSKEY records. */ - -struct DnsTrustAnchor { - Hashmap *positive_by_key; - Set *negative_by_name; - Set *revoked_by_rr; -}; - -int dns_trust_anchor_load(DnsTrustAnchor *d); -void dns_trust_anchor_flush(DnsTrustAnchor *d); - -int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey* key, DnsAnswer **answer); -int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name); - -int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs); -int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr); diff --git a/src/resolve/resolved-dns-zone.c b/src/resolve/resolved-dns-zone.c deleted file mode 100644 index f52383cfd1..0000000000 --- a/src/resolve/resolved-dns-zone.c +++ /dev/null @@ -1,656 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "alloc-util.h" -#include "dns-domain.h" -#include "list.h" -#include "resolved-dns-packet.h" -#include "resolved-dns-zone.h" -#include "string-util.h" - -/* Never allow more than 1K entries */ -#define ZONE_MAX 1024 - -void dns_zone_item_probe_stop(DnsZoneItem *i) { - DnsTransaction *t; - assert(i); - - if (!i->probe_transaction) - return; - - t = i->probe_transaction; - i->probe_transaction = NULL; - - set_remove(t->notify_zone_items, i); - dns_transaction_gc(t); -} - -static void dns_zone_item_free(DnsZoneItem *i) { - if (!i) - return; - - dns_zone_item_probe_stop(i); - dns_resource_record_unref(i->rr); - - free(i); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free); - -static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) { - DnsZoneItem *first; - - assert(z); - - if (!i) - return; - - first = hashmap_get(z->by_key, i->rr->key); - LIST_REMOVE(by_key, first, i); - if (first) - assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); - else - hashmap_remove(z->by_key, i->rr->key); - - first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key)); - LIST_REMOVE(by_name, first, i); - if (first) - assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0); - else - hashmap_remove(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key)); - - dns_zone_item_free(i); -} - -void dns_zone_flush(DnsZone *z) { - DnsZoneItem *i; - - assert(z); - - while ((i = hashmap_first(z->by_key))) - dns_zone_item_remove_and_free(z, i); - - assert(hashmap_size(z->by_key) == 0); - assert(hashmap_size(z->by_name) == 0); - - z->by_key = hashmap_free(z->by_key); - z->by_name = hashmap_free(z->by_name); -} - -static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) { - DnsZoneItem *i; - - assert(z); - assert(rr); - - LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key)) - if (dns_resource_record_equal(i->rr, rr) > 0) - return i; - - return NULL; -} - -void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) { - DnsZoneItem *i; - - assert(z); - assert(rr); - - i = dns_zone_get(z, rr); - if (i) - dns_zone_item_remove_and_free(z, i); -} - -static int dns_zone_init(DnsZone *z) { - int r; - - assert(z); - - r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops); - if (r < 0) - return r; - - r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops); - if (r < 0) - return r; - - return 0; -} - -static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) { - DnsZoneItem *first; - int r; - - first = hashmap_get(z->by_key, i->rr->key); - if (first) { - LIST_PREPEND(by_key, first, i); - assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); - } else { - r = hashmap_put(z->by_key, i->rr->key, i); - if (r < 0) - return r; - } - - first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key)); - if (first) { - LIST_PREPEND(by_name, first, i); - assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0); - } else { - r = hashmap_put(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key), i); - if (r < 0) - return r; - } - - return 0; -} - -static int dns_zone_item_probe_start(DnsZoneItem *i) { - DnsTransaction *t; - int r; - - assert(i); - - if (i->probe_transaction) - return 0; - - t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key)), false); - if (!t) { - _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; - - key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key)); - if (!key) - return -ENOMEM; - - r = dns_transaction_new(&t, i->scope, key); - if (r < 0) - return r; - } - - r = set_ensure_allocated(&t->notify_zone_items, NULL); - if (r < 0) - goto gc; - - r = set_put(t->notify_zone_items, i); - if (r < 0) - goto gc; - - i->probe_transaction = t; - - if (t->state == DNS_TRANSACTION_NULL) { - - i->block_ready++; - r = dns_transaction_go(t); - i->block_ready--; - - if (r < 0) { - dns_zone_item_probe_stop(i); - return r; - } - } - - dns_zone_item_notify(i); - return 0; - -gc: - dns_transaction_gc(t); - return r; -} - -int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) { - _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL; - DnsZoneItem *existing; - int r; - - assert(z); - assert(s); - assert(rr); - - if (dns_class_is_pseudo(rr->key->class)) - return -EINVAL; - if (dns_type_is_pseudo(rr->key->type)) - return -EINVAL; - - existing = dns_zone_get(z, rr); - if (existing) - return 0; - - r = dns_zone_init(z); - if (r < 0) - return r; - - i = new0(DnsZoneItem, 1); - if (!i) - return -ENOMEM; - - i->scope = s; - i->rr = dns_resource_record_ref(rr); - i->probing_enabled = probe; - - r = dns_zone_link_item(z, i); - if (r < 0) - return r; - - if (probe) { - DnsZoneItem *first, *j; - bool established = false; - - /* Check if there's already an RR with the same name - * established. If so, it has been probed already, and - * we don't ned to probe again. */ - - LIST_FIND_HEAD(by_name, i, first); - LIST_FOREACH(by_name, j, first) { - if (i == j) - continue; - - if (j->state == DNS_ZONE_ITEM_ESTABLISHED) - established = true; - } - - if (established) - i->state = DNS_ZONE_ITEM_ESTABLISHED; - else { - i->state = DNS_ZONE_ITEM_PROBING; - - r = dns_zone_item_probe_start(i); - if (r < 0) { - dns_zone_item_remove_and_free(z, i); - i = NULL; - return r; - } - } - } else - i->state = DNS_ZONE_ITEM_ESTABLISHED; - - i = NULL; - return 0; -} - -int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, 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; - - assert(z); - assert(key); - assert(ret_answer); - - /* First iteration, count what we have */ - - if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { - bool found = false, added = false; - int k; - - /* If this is a generic match, then we have to - * go through the list by the name and look - * for everything manually */ - - first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - - k = dns_resource_key_match_rr(key, j->rr, NULL); - if (k < 0) - return k; - if (k > 0) { - n_answer++; - added = true; - } - - } - - if (found && !added) - need_soa = true; - - } else { - bool found = false; - - /* If this is a specific match, then look for - * the right key immediately */ - - first = hashmap_get(z->by_key, key); - LIST_FOREACH(by_key, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - n_answer++; - } - - if (!found) { - first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - need_soa = true; - break; - } - } - } - - if (n_answer <= 0 && !need_soa) - goto return_empty; - - if (n_answer > 0) { - answer = dns_answer_new(n_answer); - if (!answer) - return -ENOMEM; - } - - if (need_soa) { - soa = dns_answer_new(1); - if (!soa) - return -ENOMEM; - } - - /* Second iteration, actually add the RRs to the answers */ - if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { - bool found = false, added = false; - int k; - - first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - - if (j->state != DNS_ZONE_ITEM_PROBING) - tentative = false; - - k = dns_resource_key_match_rr(key, j->rr, NULL); - if (k < 0) - return k; - if (k > 0) { - r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - - added = true; - } - } - - if (found && !added) { - r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL); - if (r < 0) - return r; - } - } else { - bool found = false; - - first = hashmap_get(z->by_key, key); - LIST_FOREACH(by_key, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - found = true; - - if (j->state != DNS_ZONE_ITEM_PROBING) - tentative = false; - - r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - if (!found) { - bool add_soa = false; - - first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); - LIST_FOREACH(by_name, j, first) { - if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) - continue; - - if (j->state != DNS_ZONE_ITEM_PROBING) - tentative = false; - - add_soa = true; - } - - if (add_soa) { - r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL); - if (r < 0) - return r; - } - } - } - - /* If the caller sets ret_tentative to NULL, then use this as - * indication to not return tentative entries */ - - if (!ret_tentative && tentative) - goto return_empty; - - *ret_answer = answer; - answer = NULL; - - if (ret_soa) { - *ret_soa = soa; - soa = NULL; - } - - if (ret_tentative) - *ret_tentative = tentative; - - return 1; - -return_empty: - *ret_answer = NULL; - - if (ret_soa) - *ret_soa = NULL; - - if (ret_tentative) - *ret_tentative = false; - - return 0; -} - -void dns_zone_item_conflict(DnsZoneItem *i) { - assert(i); - - if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED)) - return; - - log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr))); - - dns_zone_item_probe_stop(i); - - /* Withdraw the conflict item */ - i->state = DNS_ZONE_ITEM_WITHDRAWN; - - /* Maybe change the hostname */ - if (manager_is_own_hostname(i->scope->manager, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0) - manager_next_hostname(i->scope->manager); -} - -void dns_zone_item_notify(DnsZoneItem *i) { - assert(i); - assert(i->probe_transaction); - - if (i->block_ready > 0) - return; - - if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)) - return; - - if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) { - bool we_lost = false; - - /* The probe got a successful reply. If we so far - * weren't established we just give up. If we already - * were established, and the peer has the - * lexicographically larger IP address we continue - * and defend it. */ - - if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) { - log_debug("Got a successful probe for not yet established RR, we lost."); - we_lost = true; - } else { - assert(i->probe_transaction->received); - we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0; - if (we_lost) - log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost."); - } - - if (we_lost) { - dns_zone_item_conflict(i); - return; - } - - log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost."); - } - - log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr))); - - dns_zone_item_probe_stop(i); - i->state = DNS_ZONE_ITEM_ESTABLISHED; -} - -static int dns_zone_item_verify(DnsZoneItem *i) { - int r; - - assert(i); - - if (i->state != DNS_ZONE_ITEM_ESTABLISHED) - return 0; - - log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr))); - - i->state = DNS_ZONE_ITEM_VERIFYING; - r = dns_zone_item_probe_start(i); - if (r < 0) { - log_error_errno(r, "Failed to start probing for verifying RR: %m"); - i->state = DNS_ZONE_ITEM_ESTABLISHED; - return r; - } - - return 0; -} - -int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) { - DnsZoneItem *i, *first; - int c = 0; - - assert(zone); - assert(rr); - - /* This checks whether a response RR we received from somebody - * else is one that we actually thought was uniquely ours. If - * so, we'll verify our RRs. */ - - /* No conflict if we don't have the name at all. */ - first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(rr->key)); - if (!first) - return 0; - - /* No conflict if we have the exact same RR */ - if (dns_zone_get(zone, rr)) - return 0; - - /* OK, somebody else has RRs for the same name. Yuck! Let's - * start probing again */ - - LIST_FOREACH(by_name, i, first) { - if (dns_resource_record_equal(i->rr, rr)) - continue; - - dns_zone_item_verify(i); - c++; - } - - return c; -} - -int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) { - DnsZoneItem *i, *first; - int c = 0; - - assert(zone); - - /* Somebody else notified us about a possible conflict. Let's - * verify if that's true. */ - - first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(key)); - if (!first) - return 0; - - LIST_FOREACH(by_name, i, first) { - dns_zone_item_verify(i); - c++; - } - - return c; -} - -void dns_zone_verify_all(DnsZone *zone) { - DnsZoneItem *i; - Iterator iterator; - - assert(zone); - - HASHMAP_FOREACH(i, zone->by_key, iterator) { - DnsZoneItem *j; - - LIST_FOREACH(by_key, j, i) - dns_zone_item_verify(j); - } -} - -void dns_zone_dump(DnsZone *zone, FILE *f) { - Iterator iterator; - DnsZoneItem *i; - - if (!zone) - return; - - if (!f) - f = stdout; - - HASHMAP_FOREACH(i, zone->by_key, iterator) { - DnsZoneItem *j; - - LIST_FOREACH(by_key, j, i) { - const char *t; - - t = dns_resource_record_to_string(j->rr); - if (!t) { - log_oom(); - continue; - } - - fputc('\t', f); - fputs(t, f); - fputc('\n', f); - } - } -} - -bool dns_zone_is_empty(DnsZone *zone) { - if (!zone) - return true; - - return hashmap_isempty(zone->by_key); -} diff --git a/src/resolve/resolved-dns-zone.h b/src/resolve/resolved-dns-zone.h deleted file mode 100644 index 408833c359..0000000000 --- a/src/resolve/resolved-dns-zone.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "hashmap.h" - -typedef struct DnsZone { - Hashmap *by_key; - Hashmap *by_name; -} DnsZone; - -typedef struct DnsZoneItem DnsZoneItem; -typedef enum DnsZoneItemState DnsZoneItemState; - -#include "resolved-dns-answer.h" -#include "resolved-dns-question.h" -#include "resolved-dns-rr.h" -#include "resolved-dns-transaction.h" - -/* RFC 4795 Section 2.8. suggests a TTL of 30s by default */ -#define LLMNR_DEFAULT_TTL (30) - -enum DnsZoneItemState { - DNS_ZONE_ITEM_PROBING, - DNS_ZONE_ITEM_ESTABLISHED, - DNS_ZONE_ITEM_VERIFYING, - DNS_ZONE_ITEM_WITHDRAWN, -}; - -struct DnsZoneItem { - DnsScope *scope; - DnsResourceRecord *rr; - - DnsZoneItemState state; - - unsigned block_ready; - - bool probing_enabled; - - LIST_FIELDS(DnsZoneItem, by_key); - LIST_FIELDS(DnsZoneItem, by_name); - - DnsTransaction *probe_transaction; -}; - -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); - -void dns_zone_item_conflict(DnsZoneItem *i); -void dns_zone_item_notify(DnsZoneItem *i); - -int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr); -int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key); - -void dns_zone_verify_all(DnsZone *zone); - -void dns_zone_item_probe_stop(DnsZoneItem *i); - -void dns_zone_dump(DnsZone *zone, FILE *f); -bool dns_zone_is_empty(DnsZone *zone); diff --git a/src/resolve/resolved-etc-hosts.c b/src/resolve/resolved-etc-hosts.c deleted file mode 100644 index ee82c96822..0000000000 --- a/src/resolve/resolved-etc-hosts.c +++ /dev/null @@ -1,448 +0,0 @@ -/*** - 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 . -***/ - -#include "fd-util.h" -#include "fileio.h" -#include "hostname-util.h" -#include "resolved-etc-hosts.h" -#include "resolved-dns-synthesize.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" - -/* Recheck /etc/hosts at most once every 2s */ -#define ETC_HOSTS_RECHECK_USEC (2*USEC_PER_SEC) - -typedef struct EtcHostsItem { - int family; - union in_addr_union address; - - char **names; -} EtcHostsItem; - -typedef struct EtcHostsItemByName { - char *name; - - EtcHostsItem **items; - size_t n_items, n_allocated; -} EtcHostsItemByName; - -void manager_etc_hosts_flush(Manager *m) { - EtcHostsItem *item; - EtcHostsItemByName *bn; - - while ((item = set_steal_first(m->etc_hosts_by_address))) { - strv_free(item->names); - free(item); - } - - while ((bn = hashmap_steal_first(m->etc_hosts_by_name))) { - free(bn->name); - free(bn->items); - free(bn); - } - - m->etc_hosts_by_address = set_free(m->etc_hosts_by_address); - m->etc_hosts_by_name = hashmap_free(m->etc_hosts_by_name); - - m->etc_hosts_mtime = USEC_INFINITY; -} - -static void etc_hosts_item_hash_func(const void *p, struct siphash *state) { - const EtcHostsItem *item = p; - - siphash24_compress(&item->family, sizeof(item->family), state); - - if (item->family == AF_INET) - siphash24_compress(&item->address.in, sizeof(item->address.in), state); - else if (item->family == AF_INET6) - siphash24_compress(&item->address.in6, sizeof(item->address.in6), state); -} - -static int etc_hosts_item_compare_func(const void *a, const void *b) { - const EtcHostsItem *x = a, *y = b; - - if (x->family != y->family) - return x->family - y->family; - - if (x->family == AF_INET) - return memcmp(&x->address.in.s_addr, &y->address.in.s_addr, sizeof(struct in_addr)); - - if (x->family == AF_INET6) - return memcmp(&x->address.in6.s6_addr, &y->address.in6.s6_addr, sizeof(struct in6_addr)); - - return trivial_compare_func(a, b); -} - -static const struct hash_ops etc_hosts_item_ops = { - .hash = etc_hosts_item_hash_func, - .compare = etc_hosts_item_compare_func, -}; - -static int add_item(Manager *m, int family, const union in_addr_union *address, char **names) { - - EtcHostsItem key = { - .family = family, - .address = *address, - }; - EtcHostsItem *item; - char **n; - int r; - - assert(m); - assert(address); - - r = in_addr_is_null(family, address); - if (r < 0) - return r; - if (r > 0) - /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to - * nothing. */ - item = NULL; - else { - /* If this is a normal address, then, simply add entry mapping it to the specified names */ - - item = set_get(m->etc_hosts_by_address, &key); - if (item) { - r = strv_extend_strv(&item->names, names, true); - if (r < 0) - return log_oom(); - } else { - - r = set_ensure_allocated(&m->etc_hosts_by_address, &etc_hosts_item_ops); - if (r < 0) - return log_oom(); - - item = new0(EtcHostsItem, 1); - if (!item) - return log_oom(); - - item->family = family; - item->address = *address; - item->names = names; - - r = set_put(m->etc_hosts_by_address, item); - if (r < 0) { - free(item); - return log_oom(); - } - } - } - - STRV_FOREACH(n, names) { - EtcHostsItemByName *bn; - - bn = hashmap_get(m->etc_hosts_by_name, *n); - if (!bn) { - r = hashmap_ensure_allocated(&m->etc_hosts_by_name, &dns_name_hash_ops); - if (r < 0) - return log_oom(); - - bn = new0(EtcHostsItemByName, 1); - if (!bn) - return log_oom(); - - bn->name = strdup(*n); - if (!bn->name) { - free(bn); - return log_oom(); - } - - r = hashmap_put(m->etc_hosts_by_name, bn->name, bn); - if (r < 0) { - free(bn->name); - free(bn); - return log_oom(); - } - } - - if (item) { - if (!GREEDY_REALLOC(bn->items, bn->n_allocated, bn->n_items+1)) - return log_oom(); - - bn->items[bn->n_items++] = item; - } - } - - return 0; -} - -static int parse_line(Manager *m, unsigned nr, const char *line) { - _cleanup_free_ char *address = NULL; - _cleanup_strv_free_ char **names = NULL; - union in_addr_union in; - bool suppressed = false; - int family, r; - - assert(m); - assert(line); - - r = extract_first_word(&line, &address, NULL, EXTRACT_RELAX); - if (r < 0) - return log_error_errno(r, "Couldn't extract address, in line /etc/hosts:%u.", nr); - if (r == 0) { - log_error("Premature end of line, in line /etc/hosts:%u.", nr); - return -EINVAL; - } - - r = in_addr_from_string_auto(address, &family, &in); - if (r < 0) - return log_error_errno(r, "Address '%s' is invalid, in line /etc/hosts:%u.", address, nr); - - for (;;) { - _cleanup_free_ char *name = NULL; - - r = extract_first_word(&line, &name, NULL, EXTRACT_RELAX); - if (r < 0) - return log_error_errno(r, "Couldn't extract host name, in line /etc/hosts:%u.", nr); - if (r == 0) - break; - - r = dns_name_is_valid(name); - if (r <= 0) - return log_error_errno(r, "Hostname %s is not valid, ignoring, in line /etc/hosts:%u.", name, nr); - - if (is_localhost(name)) { - /* Suppress the "localhost" line that is often seen */ - suppressed = true; - continue; - } - - r = strv_push(&names, name); - if (r < 0) - return log_oom(); - - name = NULL; - } - - if (strv_isempty(names)) { - - if (suppressed) - return 0; - - log_error("Line is missing any host names, in line /etc/hosts:%u.", nr); - return -EINVAL; - } - - /* Takes possession of the names strv */ - r = add_item(m, family, &in, names); - if (r < 0) - return r; - - names = NULL; - return r; -} - -int manager_etc_hosts_read(Manager *m) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - struct stat st; - usec_t ts; - unsigned nr = 0; - int r; - - assert_se(sd_event_now(m->event, clock_boottime_or_monotonic(), &ts) >= 0); - - /* See if we checked /etc/hosts recently already */ - if (m->etc_hosts_last != USEC_INFINITY && m->etc_hosts_last + ETC_HOSTS_RECHECK_USEC > ts) - return 0; - - m->etc_hosts_last = ts; - - if (m->etc_hosts_mtime != USEC_INFINITY) { - if (stat("/etc/hosts", &st) < 0) { - if (errno == ENOENT) { - r = 0; - goto clear; - } - - return log_error_errno(errno, "Failed to stat /etc/hosts: %m"); - } - - /* Did the mtime change? If not, there's no point in re-reading the file. */ - if (timespec_load(&st.st_mtim) == m->etc_hosts_mtime) - return 0; - } - - f = fopen("/etc/hosts", "re"); - if (!f) { - if (errno == ENOENT) { - r = 0; - goto clear; - } - - return log_error_errno(errno, "Failed to open /etc/hosts: %m"); - } - - /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next - * invocation */ - r = fstat(fileno(f), &st); - if (r < 0) - return log_error_errno(errno, "Failed to fstat() /etc/hosts: %m"); - - manager_etc_hosts_flush(m); - - FOREACH_LINE(line, f, return log_error_errno(errno, "Failed to read /etc/hosts: %m")) { - char *l; - - nr ++; - - l = strstrip(line); - if (isempty(l)) - continue; - if (l[0] == '#') - continue; - - r = parse_line(m, nr, l); - if (r == -ENOMEM) /* On OOM we abandon the half-built-up structure. All other errors we ignore and proceed */ - goto clear; - } - - m->etc_hosts_mtime = timespec_load(&st.st_mtim); - m->etc_hosts_last = ts; - - return 1; - -clear: - manager_etc_hosts_flush(m); - return r; -} - -int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) { - bool found_a = false, found_aaaa = false; - EtcHostsItemByName *bn; - EtcHostsItem k = {}; - DnsResourceKey *t; - const char *name; - unsigned i; - int r; - - assert(m); - assert(q); - assert(answer); - - r = manager_etc_hosts_read(m); - if (r < 0) - return r; - - name = dns_question_first_name(q); - if (!name) - return 0; - - r = dns_name_address(name, &k.family, &k.address); - if (r > 0) { - EtcHostsItem *item; - DnsResourceKey *found_ptr = NULL; - - item = set_get(m->etc_hosts_by_address, &k); - if (!item) - return 0; - - /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data - * we'll only return if the request was for PTR. */ - - DNS_QUESTION_FOREACH(t, q) { - if (!IN_SET(t->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) - continue; - if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) - continue; - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(t), name); - if (r < 0) - return r; - if (r > 0) { - found_ptr = t; - break; - } - } - - if (found_ptr) { - char **n; - - r = dns_answer_reserve(answer, strv_length(item->names)); - if (r < 0) - return r; - - STRV_FOREACH(n, item->names) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - rr = dns_resource_record_new(found_ptr); - if (!rr) - return -ENOMEM; - - rr->ptr.name = strdup(*n); - if (!rr->ptr.name) - return -ENOMEM; - - r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - } - - return 1; - } - - bn = hashmap_get(m->etc_hosts_by_name, name); - if (!bn) - return 0; - - r = dns_answer_reserve(answer, bn->n_items); - if (r < 0) - return r; - - DNS_QUESTION_FOREACH(t, q) { - if (!IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_TYPE_ANY)) - continue; - if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) - continue; - - r = dns_name_equal(DNS_RESOURCE_KEY_NAME(t), name); - if (r < 0) - return r; - if (r == 0) - continue; - - if (IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_ANY)) - found_a = true; - if (IN_SET(t->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) - found_aaaa = true; - - if (found_a && found_aaaa) - break; - } - - for (i = 0; i < bn->n_items; i++) { - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - - if ((found_a && bn->items[i]->family != AF_INET) && - (found_aaaa && bn->items[i]->family != AF_INET6)) - continue; - - r = dns_resource_record_new_address(&rr, bn->items[i]->family, &bn->items[i]->address, bn->name); - if (r < 0) - return r; - - r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); - if (r < 0) - return r; - } - - return 1; -} diff --git a/src/resolve/resolved-etc-hosts.h b/src/resolve/resolved-etc-hosts.h deleted file mode 100644 index 9d5a175f18..0000000000 --- a/src/resolve/resolved-etc-hosts.h +++ /dev/null @@ -1,28 +0,0 @@ -#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 . -***/ - -#include "resolved-manager.h" -#include "resolved-dns-question.h" -#include "resolved-dns-answer.h" - -void manager_etc_hosts_flush(Manager *m); -int manager_etc_hosts_read(Manager *m); -int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer); diff --git a/src/resolve/resolved-gperf.gperf b/src/resolve/resolved-gperf.gperf deleted file mode 100644 index 82f26215df..0000000000 --- a/src/resolve/resolved-gperf.gperf +++ /dev/null @@ -1,21 +0,0 @@ -%{ -#include -#include "conf-parser.h" -#include "resolved-conf.h" -%} -struct ConfigPerfItem; -%null_strings -%language=ANSI-C -%define slot-name section_and_lvalue -%define hash-function-name resolved_gperf_hash -%define lookup-function-name resolved_gperf_lookup -%readonly-tables -%omit-struct-type -%struct-type -%includes -%% -Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 -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) diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c deleted file mode 100644 index df7516f4f4..0000000000 --- a/src/resolve/resolved-link-bus.c +++ /dev/null @@ -1,550 +0,0 @@ -/*** - 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 . -***/ - -#include "alloc-util.h" -#include "bus-util.h" -#include "parse-util.h" -#include "resolve-util.h" -#include "resolved-bus.h" -#include "resolved-link-bus.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_dns( - 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; - DnsServer *s; - int r; - - assert(reply); - assert(l); - - r = sd_bus_message_open_container(reply, 'a', "(iay)"); - if (r < 0) - return r; - - LIST_FOREACH(servers, s, l->dns_servers) { - r = bus_dns_server_append(reply, s, false); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_domains( - 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; - DnsSearchDomain *d; - int r; - - assert(reply); - assert(l); - - r = sd_bus_message_open_container(reply, 'a', "(sb)"); - if (r < 0) - return r; - - LIST_FOREACH(domains, d, l->search_domains) { - r = sd_bus_message_append(reply, "(sb)", d->name, d->route_only); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_scopes_mask( - 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; - uint64_t mask; - - assert(reply); - assert(l); - - mask = (l->unicast_scope ? SD_RESOLVED_DNS : 0) | - (l->llmnr_ipv4_scope ? SD_RESOLVED_LLMNR_IPV4 : 0) | - (l->llmnr_ipv6_scope ? SD_RESOLVED_LLMNR_IPV6 : 0) | - (l->mdns_ipv4_scope ? SD_RESOLVED_MDNS_IPV4 : 0) | - (l->mdns_ipv6_scope ? SD_RESOLVED_MDNS_IPV6 : 0); - - return sd_bus_message_append(reply, "t", mask); -} - -static int 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) { - - Link *l = userdata; - const char *name; - Iterator i; - int r; - - assert(reply); - assert(l); - - r = sd_bus_message_open_container(reply, 'a', "s"); - if (r < 0) - return r; - - SET_FOREACH(name, l->dnssec_negative_trust_anchors, i) { - r = sd_bus_message_append(reply, "s", name); - if (r < 0) - return r; - } - - return sd_bus_message_close_container(reply); -} - -static int property_get_dnssec_supported( - 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, "b", link_dnssec_supported(l)); -} - -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; - Link *l = userdata; - unsigned i; - int r; - - assert(message); - assert(l); - - r = sd_bus_message_enter_container(message, 'a', "(iay)"); - if (r < 0) - return r; - - for (;;) { - int family; - size_t sz; - const void *d; - - assert_cc(sizeof(int) == sizeof(int32_t)); - - r = sd_bus_message_enter_container(message, 'r', "iay"); - if (r < 0) - return r; - if (r == 0) - break; - - r = sd_bus_message_read(message, "i", &family); - if (r < 0) - return r; - - if (!IN_SET(family, AF_INET, AF_INET6)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); - - r = sd_bus_message_read_array(message, 'y', &d, &sz); - if (r < 0) - return r; - if (sz != FAMILY_ADDRESS_SIZE(family)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size"); - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - if (!GREEDY_REALLOC(dns, allocated, n+1)) - return -ENOMEM; - - dns[n].family = family; - memcpy(&dns[n].address, d, sz); - n++; - } - - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - - dns_server_mark_all(l->dns_servers); - - for (i = 0; i < n; i++) { - DnsServer *s; - - s = dns_server_find(l->dns_servers, dns[i].family, &dns[i].address); - 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); - if (r < 0) - goto clear; - } - - } - - dns_server_unlink_marked(l->dns_servers); - link_allocate_scopes(l); - - return sd_bus_reply_method_return(message, NULL); - -clear: - dns_server_unlink_all(l->dns_servers); - return r; -} - -int bus_link_method_set_search_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - int r; - - assert(message); - assert(l); - - r = sd_bus_message_enter_container(message, 'a', "(sb)"); - if (r < 0) - return r; - - for (;;) { - const char *name; - int route_only; - - r = sd_bus_message_read(message, "(sb)", &name, &route_only); - if (r < 0) - return r; - if (r == 0) - break; - - r = dns_name_is_valid(name); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search domain %s", name); - if (!route_only && dns_name_is_root(name)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root domain is not suitable as search domain"); - } - - dns_search_domain_mark_all(l->search_domains); - - r = sd_bus_message_rewind(message, false); - if (r < 0) - return r; - - for (;;) { - DnsSearchDomain *d; - const char *name; - int route_only; - - r = sd_bus_message_read(message, "(sb)", &name, &route_only); - if (r < 0) - goto clear; - if (r == 0) - break; - - r = dns_search_domain_find(l->search_domains, name, &d); - if (r < 0) - goto clear; - - if (r > 0) - dns_search_domain_move_back_and_unmark(d); - else { - r = dns_search_domain_new(l->manager, &d, DNS_SEARCH_DOMAIN_LINK, l, name); - if (r < 0) - goto clear; - } - - d->route_only = route_only; - } - - r = sd_bus_message_exit_container(message); - if (r < 0) - goto clear; - - dns_search_domain_unlink_marked(l->search_domains); - return sd_bus_reply_method_return(message, NULL); - -clear: - dns_search_domain_unlink_all(l->search_domains); - return r; -} - -int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - ResolveSupport mode; - const char *llmnr; - int r; - - assert(message); - assert(l); - - r = sd_bus_message_read(message, "s", &llmnr); - if (r < 0) - return r; - - if (isempty(llmnr)) - mode = RESOLVE_SUPPORT_YES; - else { - mode = resolve_support_from_string(llmnr); - if (mode < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid LLMNR setting: %s", llmnr); - } - - l->llmnr_support = mode; - link_allocate_scopes(l); - link_add_rrs(l, false); - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - ResolveSupport mode; - const char *mdns; - int r; - - assert(message); - assert(l); - - r = sd_bus_message_read(message, "s", &mdns); - if (r < 0) - return r; - - if (isempty(mdns)) - mode = RESOLVE_SUPPORT_NO; - else { - mode = resolve_support_from_string(mdns); - if (mode < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid MulticastDNS setting: %s", mdns); - } - - l->mdns_support = mode; - link_allocate_scopes(l); - link_add_rrs(l, false); - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { - Link *l = userdata; - const char *dnssec; - DnssecMode mode; - int r; - - assert(message); - assert(l); - - r = sd_bus_message_read(message, "s", &dnssec); - if (r < 0) - return r; - - if (isempty(dnssec)) - mode = _DNSSEC_MODE_INVALID; - else { - mode = dnssec_mode_from_string(dnssec); - if (mode < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSSEC setting: %s", dnssec); - } - - link_set_dnssec_mode(l, mode); - - return sd_bus_reply_method_return(message, NULL); -} - -int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { - _cleanup_set_free_free_ Set *ns = NULL; - _cleanup_free_ char **ntas = NULL; - Link *l = userdata; - int r; - char **i; - - assert(message); - assert(l); - - r = sd_bus_message_read_strv(message, &ntas); - if (r < 0) - return r; - - STRV_FOREACH(i, ntas) { - r = dns_name_is_valid(*i); - if (r < 0) - return r; - if (r == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid negative trust anchor domain: %s", *i); - } - - ns = set_new(&dns_name_hash_ops); - if (!ns) - return -ENOMEM; - - STRV_FOREACH(i, ntas) { - r = set_put_strdup(ns, *i); - if (r < 0) - return r; - } - - set_free_free(l->dnssec_negative_trust_anchors); - l->dnssec_negative_trust_anchors = ns; - ns = NULL; - - 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; - - assert(message); - assert(l); - - link_flush_settings(l); - link_allocate_scopes(l); - link_add_rrs(l, false); - - return sd_bus_reply_method_return(message, NULL); -} - -const sd_bus_vtable link_vtable[] = { - SD_BUS_VTABLE_START(0), - - SD_BUS_PROPERTY("ScopesMask", "t", property_get_scopes_mask, 0, 0), - SD_BUS_PROPERTY("DNS", "a(iay)", property_get_dns, 0, 0), - 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("DNSSECNegativeTrustAnchors", "as", property_get_ntas, 0, 0), - SD_BUS_PROPERTY("DNSSECSupport", "b", property_get_dnssec_supported, 0, 0), - - SD_BUS_METHOD("SetDNS", "a(iay)", NULL, bus_link_method_set_dns_servers, 0), - SD_BUS_METHOD("SetDomains", "a(sb)", NULL, bus_link_method_set_search_domains, 0), - SD_BUS_METHOD("SetLLMNR", "s", NULL, bus_link_method_set_llmnr, 0), - SD_BUS_METHOD("SetMulticastDNS", "s", NULL, bus_link_method_set_mdns, 0), - SD_BUS_METHOD("SetDNSSEC", "s", NULL, bus_link_method_set_dnssec, 0), - SD_BUS_METHOD("SetDNSSECNegativeTrustAnchors", "as", NULL, bus_link_method_set_dnssec_negative_trust_anchors, 0), - SD_BUS_METHOD("Revert", NULL, NULL, bus_link_method_revert, 0), - - SD_BUS_VTABLE_END -}; - -int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { - _cleanup_free_ char *e = NULL; - Manager *m = userdata; - int ifindex; - Link *link; - int r; - - assert(bus); - assert(path); - assert(interface); - assert(found); - assert(m); - - r = sd_bus_path_decode(path, "/org/freedesktop/resolve1/link", &e); - if (r <= 0) - return 0; - - r = parse_ifindex(e, &ifindex); - if (r < 0) - return 0; - - link = hashmap_get(m->links, INT_TO_PTR(ifindex)); - if (!link) - return 0; - - *found = link; - return 1; -} - -char *link_bus_path(Link *link) { - _cleanup_free_ char *ifindex = NULL; - char *p; - int r; - - assert(link); - - if (asprintf(&ifindex, "%i", link->ifindex) < 0) - return NULL; - - r = sd_bus_path_encode("/org/freedesktop/resolve1/link", ifindex, &p); - if (r < 0) - return NULL; - - return p; -} - -int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { - _cleanup_strv_free_ char **l = NULL; - Manager *m = userdata; - Link *link; - Iterator i; - unsigned c = 0; - - assert(bus); - assert(path); - assert(m); - assert(nodes); - - l = new0(char*, hashmap_size(m->links) + 1); - if (!l) - return -ENOMEM; - - HASHMAP_FOREACH(link, m->links, i) { - char *p; - - p = link_bus_path(link); - if (!p) - return -ENOMEM; - - l[c++] = p; - } - - l[c] = NULL; - *nodes = l; - l = NULL; - - return 1; -} diff --git a/src/resolve/resolved-link-bus.h b/src/resolve/resolved-link-bus.h deleted file mode 100644 index 31e6cd2b45..0000000000 --- a/src/resolve/resolved-link-bus.h +++ /dev/null @@ -1,38 +0,0 @@ -#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 . -***/ - -#include "sd-bus.h" - -#include "resolved-link.h" - -extern const sd_bus_vtable link_vtable[]; - -int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); -char *link_bus_path(Link *link); -int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); - -int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_search_domains(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error); -int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c deleted file mode 100644 index c5863b3aa2..0000000000 --- a/src/resolve/resolved-link.c +++ /dev/null @@ -1,840 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "sd-network.h" - -#include "alloc-util.h" -#include "missing.h" -#include "parse-util.h" -#include "resolved-link.h" -#include "string-util.h" -#include "strv.h" - -int link_new(Manager *m, Link **ret, int ifindex) { - _cleanup_(link_freep) Link *l = NULL; - int r; - - assert(m); - assert(ifindex > 0); - - r = hashmap_ensure_allocated(&m->links, NULL); - if (r < 0) - return r; - - l = new0(Link, 1); - if (!l) - return -ENOMEM; - - l->ifindex = ifindex; - l->llmnr_support = RESOLVE_SUPPORT_YES; - l->mdns_support = RESOLVE_SUPPORT_NO; - l->dnssec_mode = _DNSSEC_MODE_INVALID; - l->operstate = IF_OPER_UNKNOWN; - - r = hashmap_put(m->links, INT_TO_PTR(ifindex), l); - if (r < 0) - return r; - - l->manager = m; - - if (ret) - *ret = l; - l = NULL; - - return 0; -} - -void link_flush_settings(Link *l) { - assert(l); - - l->llmnr_support = RESOLVE_SUPPORT_YES; - l->mdns_support = RESOLVE_SUPPORT_NO; - l->dnssec_mode = _DNSSEC_MODE_INVALID; - - dns_server_unlink_all(l->dns_servers); - dns_search_domain_unlink_all(l->search_domains); - - l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); -} - -Link *link_free(Link *l) { - if (!l) - return NULL; - - link_flush_settings(l); - - while (l->addresses) - (void) link_address_free(l->addresses); - - if (l->manager) - hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); - - dns_scope_free(l->unicast_scope); - dns_scope_free(l->llmnr_ipv4_scope); - dns_scope_free(l->llmnr_ipv6_scope); - dns_scope_free(l->mdns_ipv4_scope); - dns_scope_free(l->mdns_ipv6_scope); - - free(l); - return NULL; -} - -void link_allocate_scopes(Link *l) { - int r; - - assert(l); - - if (link_relevant(l, AF_UNSPEC, false) && - l->dns_servers) { - if (!l->unicast_scope) { - r = dns_scope_new(l->manager, &l->unicast_scope, l, DNS_PROTOCOL_DNS, AF_UNSPEC); - if (r < 0) - log_warning_errno(r, "Failed to allocate DNS scope: %m"); - } - } else - l->unicast_scope = dns_scope_free(l->unicast_scope); - - if (link_relevant(l, AF_INET, true) && - l->llmnr_support != RESOLVE_SUPPORT_NO && - l->manager->llmnr_support != RESOLVE_SUPPORT_NO) { - if (!l->llmnr_ipv4_scope) { - r = dns_scope_new(l->manager, &l->llmnr_ipv4_scope, l, DNS_PROTOCOL_LLMNR, AF_INET); - if (r < 0) - log_warning_errno(r, "Failed to allocate LLMNR IPv4 scope: %m"); - } - } else - l->llmnr_ipv4_scope = dns_scope_free(l->llmnr_ipv4_scope); - - if (link_relevant(l, AF_INET6, true) && - l->llmnr_support != RESOLVE_SUPPORT_NO && - l->manager->llmnr_support != RESOLVE_SUPPORT_NO && - socket_ipv6_is_supported()) { - if (!l->llmnr_ipv6_scope) { - r = dns_scope_new(l->manager, &l->llmnr_ipv6_scope, l, DNS_PROTOCOL_LLMNR, AF_INET6); - if (r < 0) - log_warning_errno(r, "Failed to allocate LLMNR IPv6 scope: %m"); - } - } else - l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope); - - if (link_relevant(l, AF_INET, true) && - l->mdns_support != RESOLVE_SUPPORT_NO && - l->manager->mdns_support != RESOLVE_SUPPORT_NO) { - if (!l->mdns_ipv4_scope) { - r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET); - if (r < 0) - log_warning_errno(r, "Failed to allocate mDNS IPv4 scope: %m"); - } - } else - l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope); - - if (link_relevant(l, AF_INET6, true) && - l->mdns_support != RESOLVE_SUPPORT_NO && - l->manager->mdns_support != RESOLVE_SUPPORT_NO) { - if (!l->mdns_ipv6_scope) { - r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6); - if (r < 0) - log_warning_errno(r, "Failed to allocate mDNS IPv6 scope: %m"); - } - } else - l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope); -} - -void link_add_rrs(Link *l, bool force_remove) { - LinkAddress *a; - - LIST_FOREACH(addresses, a, l->addresses) - link_address_add_rrs(a, force_remove); -} - -int link_update_rtnl(Link *l, sd_netlink_message *m) { - const char *n = NULL; - int r; - - assert(l); - assert(m); - - r = sd_rtnl_message_link_get_flags(m, &l->flags); - if (r < 0) - return r; - - (void) sd_netlink_message_read_u32(m, IFLA_MTU, &l->mtu); - (void) sd_netlink_message_read_u8(m, IFLA_OPERSTATE, &l->operstate); - - if (sd_netlink_message_read_string(m, IFLA_IFNAME, &n) >= 0) { - strncpy(l->name, n, sizeof(l->name)-1); - char_array_0(l->name); - } - - link_allocate_scopes(l); - link_add_rrs(l, false); - - return 0; -} - -static int link_update_dns_servers(Link *l) { - _cleanup_strv_free_ char **nameservers = NULL; - char **nameserver; - int r; - - assert(l); - - r = sd_network_link_get_dns(l->ifindex, &nameservers); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - 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); - 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); - return 0; - -clear: - dns_server_unlink_all(l->dns_servers); - return r; -} - -static int link_update_llmnr_support(Link *l) { - _cleanup_free_ char *b = NULL; - int r; - - assert(l); - - r = sd_network_link_get_llmnr(l->ifindex, &b); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - l->llmnr_support = resolve_support_from_string(b); - if (l->llmnr_support < 0) { - r = -EINVAL; - goto clear; - } - - return 0; - -clear: - l->llmnr_support = RESOLVE_SUPPORT_YES; - return r; -} - -static int link_update_mdns_support(Link *l) { - _cleanup_free_ char *b = NULL; - int r; - - assert(l); - - r = sd_network_link_get_mdns(l->ifindex, &b); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - l->mdns_support = resolve_support_from_string(b); - if (l->mdns_support < 0) { - r = -EINVAL; - goto clear; - } - - return 0; - -clear: - l->mdns_support = RESOLVE_SUPPORT_NO; - return r; -} - -void link_set_dnssec_mode(Link *l, DnssecMode mode) { - - assert(l); - - if (l->dnssec_mode == mode) - return; - - if ((l->dnssec_mode == _DNSSEC_MODE_INVALID) || - (l->dnssec_mode == DNSSEC_NO && mode != DNSSEC_NO) || - (l->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && mode == DNSSEC_YES)) { - - /* When switching from non-DNSSEC mode to DNSSEC mode, flush the cache. Also when switching from the - * allow-downgrade mode to full DNSSEC mode, flush it too. */ - if (l->unicast_scope) - dns_cache_flush(&l->unicast_scope->cache); - } - - l->dnssec_mode = mode; -} - -static int link_update_dnssec_mode(Link *l) { - _cleanup_free_ char *m = NULL; - DnssecMode mode; - int r; - - assert(l); - - r = sd_network_link_get_dnssec(l->ifindex, &m); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - mode = dnssec_mode_from_string(m); - if (mode < 0) { - r = -EINVAL; - goto clear; - } - - link_set_dnssec_mode(l, mode); - - return 0; - -clear: - l->dnssec_mode = _DNSSEC_MODE_INVALID; - return r; -} - -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); - - r = sd_network_link_get_dnssec_negative_trust_anchors(l->ifindex, &ntas); - if (r == -ENODATA) { - r = 0; - goto clear; - } - if (r < 0) - goto clear; - - ns = set_new(&dns_name_hash_ops); - if (!ns) - return -ENOMEM; - - STRV_FOREACH(i, ntas) { - r = set_put_strdup(ns, *i); - if (r < 0) - return r; - } - - set_free_free(l->dnssec_negative_trust_anchors); - l->dnssec_negative_trust_anchors = ns; - ns = NULL; - - return 0; - -clear: - l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); - return r; -} - -static int link_update_search_domain_one(Link *l, const char *name, bool route_only) { - DnsSearchDomain *d; - int r; - - r = dns_search_domain_find(l->search_domains, name, &d); - if (r < 0) - return r; - if (r > 0) - dns_search_domain_move_back_and_unmark(d); - else { - r = dns_search_domain_new(l->manager, &d, DNS_SEARCH_DOMAIN_LINK, l, name); - if (r < 0) - return r; - } - - d->route_only = route_only; - return 0; -} - -static int link_update_search_domains(Link *l) { - _cleanup_strv_free_ char **sdomains = NULL, **rdomains = NULL; - char **i; - int r, q; - - assert(l); - - r = sd_network_link_get_search_domains(l->ifindex, &sdomains); - if (r < 0 && r != -ENODATA) - goto clear; - - q = sd_network_link_get_route_domains(l->ifindex, &rdomains); - if (q < 0 && q != -ENODATA) { - r = q; - goto clear; - } - - if (r == -ENODATA && q == -ENODATA) { - /* networkd knows nothing about this interface, and that's fine. */ - r = 0; - goto clear; - } - - dns_search_domain_mark_all(l->search_domains); - - STRV_FOREACH(i, sdomains) { - r = link_update_search_domain_one(l, *i, false); - if (r < 0) - goto clear; - } - - STRV_FOREACH(i, rdomains) { - r = link_update_search_domain_one(l, *i, true); - if (r < 0) - goto clear; - } - - dns_search_domain_unlink_marked(l->search_domains); - return 0; - -clear: - dns_search_domain_unlink_all(l->search_domains); - return r; -} - -static int link_is_unmanaged(Link *l) { - _cleanup_free_ char *state = NULL; - int r; - - assert(l); - - r = sd_network_link_get_setup_state(l->ifindex, &state); - if (r == -ENODATA) - return 1; - if (r < 0) - return r; - - return STR_IN_SET(state, "pending", "unmanaged"); -} - -static void link_read_settings(Link *l) { - int r; - - assert(l); - - /* Read settings from networkd, except when networkd is not managing this interface. */ - - r = link_is_unmanaged(l); - if (r < 0) { - log_warning_errno(r, "Failed to determine whether interface %s is managed: %m", l->name); - return; - } - 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) - link_flush_settings(l); - - l->is_managed = false; - return; - } - - l->is_managed = true; - - r = link_update_dns_servers(l); - if (r < 0) - log_warning_errno(r, "Failed to read DNS servers for interface %s, ignoring: %m", l->name); - - r = link_update_llmnr_support(l); - if (r < 0) - log_warning_errno(r, "Failed to read LLMNR support for interface %s, ignoring: %m", l->name); - - r = link_update_mdns_support(l); - if (r < 0) - log_warning_errno(r, "Failed to read mDNS support for interface %s, ignoring: %m", l->name); - - r = link_update_dnssec_mode(l); - if (r < 0) - log_warning_errno(r, "Failed to read DNSSEC mode for interface %s, ignoring: %m", l->name); - - r = link_update_dnssec_negative_trust_anchors(l); - if (r < 0) - log_warning_errno(r, "Failed to read DNSSEC negative trust anchors for interface %s, ignoring: %m", l->name); - - r = link_update_search_domains(l); - if (r < 0) - log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name); -} - -int link_update_monitor(Link *l) { - assert(l); - - link_read_settings(l); - link_allocate_scopes(l); - link_add_rrs(l, false); - - return 0; -} - -bool link_relevant(Link *l, int family, bool local_multicast) { - _cleanup_free_ char *state = NULL; - LinkAddress *a; - - assert(l); - - /* A link is relevant for local multicast traffic if it isn't a loopback or pointopoint device, has a link - * beat, can do multicast and has at least one link-local (or better) IP address. - * - * A link is relevant for non-multicast traffic if it isn't a loopback device, has a link beat, and has at - * least one routable address.*/ - - if (l->flags & (IFF_LOOPBACK|IFF_DORMANT)) - return false; - - if ((l->flags & (IFF_UP|IFF_LOWER_UP)) != (IFF_UP|IFF_LOWER_UP)) - return false; - - if (local_multicast) { - if (l->flags & IFF_POINTOPOINT) - return false; - - if ((l->flags & IFF_MULTICAST) != IFF_MULTICAST) - return false; - } - - /* Check kernel operstate - * https://www.kernel.org/doc/Documentation/networking/operstates.txt */ - if (!IN_SET(l->operstate, IF_OPER_UNKNOWN, IF_OPER_UP)) - return false; - - (void) sd_network_link_get_operational_state(l->ifindex, &state); - if (state && !STR_IN_SET(state, "unknown", "degraded", "routable")) - return false; - - LIST_FOREACH(addresses, a, l->addresses) - if ((family == AF_UNSPEC || a->family == family) && link_address_relevant(a, local_multicast)) - return true; - - return false; -} - -LinkAddress *link_find_address(Link *l, int family, const union in_addr_union *in_addr) { - LinkAddress *a; - - assert(l); - - LIST_FOREACH(addresses, a, l->addresses) - if (a->family == family && in_addr_equal(family, &a->in_addr, in_addr)) - return a; - - return NULL; -} - -DnsServer* link_set_dns_server(Link *l, DnsServer *s) { - assert(l); - - if (l->current_dns_server == s) - return s; - - if (s) - log_info("Switching to DNS server %s for interface %s.", dns_server_string(s), l->name); - - dns_server_unref(l->current_dns_server); - l->current_dns_server = dns_server_ref(s); - - if (l->unicast_scope) - dns_cache_flush(&l->unicast_scope->cache); - - return s; -} - -DnsServer *link_get_dns_server(Link *l) { - assert(l); - - if (!l->current_dns_server) - link_set_dns_server(l, l->dns_servers); - - return l->current_dns_server; -} - -void link_next_dns_server(Link *l) { - assert(l); - - if (!l->current_dns_server) - return; - - /* Change to the next one, but make sure to follow the linked - * list only if this server is actually still linked. */ - if (l->current_dns_server->linked && l->current_dns_server->servers_next) { - link_set_dns_server(l, l->current_dns_server->servers_next); - return; - } - - link_set_dns_server(l, l->dns_servers); -} - -DnssecMode link_get_dnssec_mode(Link *l) { - assert(l); - - if (l->dnssec_mode != _DNSSEC_MODE_INVALID) - return l->dnssec_mode; - - return manager_get_dnssec_mode(l->manager); -} - -bool link_dnssec_supported(Link *l) { - DnsServer *server; - - assert(l); - - if (link_get_dnssec_mode(l) == DNSSEC_NO) - return false; - - server = link_get_dns_server(l); - if (server) - return dns_server_dnssec_supported(server); - - return true; -} - -int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr) { - LinkAddress *a; - - assert(l); - assert(in_addr); - - a = new0(LinkAddress, 1); - if (!a) - return -ENOMEM; - - a->family = family; - a->in_addr = *in_addr; - - a->link = l; - LIST_PREPEND(addresses, l->addresses, a); - - if (ret) - *ret = a; - - return 0; -} - -LinkAddress *link_address_free(LinkAddress *a) { - if (!a) - return NULL; - - if (a->link) { - LIST_REMOVE(addresses, a->link->addresses, a); - - if (a->llmnr_address_rr) { - if (a->family == AF_INET && a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); - else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); - } - - if (a->llmnr_ptr_rr) { - if (a->family == AF_INET && a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); - else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); - } - } - - dns_resource_record_unref(a->llmnr_address_rr); - dns_resource_record_unref(a->llmnr_ptr_rr); - - free(a); - return NULL; -} - -void link_address_add_rrs(LinkAddress *a, bool force_remove) { - int r; - - assert(a); - - if (a->family == AF_INET) { - - if (!force_remove && - link_address_relevant(a, true) && - a->link->llmnr_ipv4_scope && - a->link->llmnr_support == RESOLVE_SUPPORT_YES && - a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { - - if (!a->link->manager->llmnr_host_ipv4_key) { - a->link->manager->llmnr_host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->llmnr_hostname); - if (!a->link->manager->llmnr_host_ipv4_key) { - r = -ENOMEM; - goto fail; - } - } - - if (!a->llmnr_address_rr) { - a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv4_key); - if (!a->llmnr_address_rr) { - r = -ENOMEM; - goto fail; - } - - a->llmnr_address_rr->a.in_addr = a->in_addr.in; - a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; - } - - if (!a->llmnr_ptr_rr) { - r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); - if (r < 0) - goto fail; - - a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; - } - - r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_address_rr, true); - if (r < 0) - log_warning_errno(r, "Failed to add A record to LLMNR zone: %m"); - - r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_ptr_rr, false); - if (r < 0) - log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); - } else { - if (a->llmnr_address_rr) { - if (a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); - a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); - } - - if (a->llmnr_ptr_rr) { - if (a->link->llmnr_ipv4_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); - a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); - } - } - } - - if (a->family == AF_INET6) { - - if (!force_remove && - link_address_relevant(a, true) && - a->link->llmnr_ipv6_scope && - a->link->llmnr_support == RESOLVE_SUPPORT_YES && - a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { - - if (!a->link->manager->llmnr_host_ipv6_key) { - a->link->manager->llmnr_host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->llmnr_hostname); - if (!a->link->manager->llmnr_host_ipv6_key) { - r = -ENOMEM; - goto fail; - } - } - - if (!a->llmnr_address_rr) { - a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv6_key); - if (!a->llmnr_address_rr) { - r = -ENOMEM; - goto fail; - } - - a->llmnr_address_rr->aaaa.in6_addr = a->in_addr.in6; - a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; - } - - if (!a->llmnr_ptr_rr) { - r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); - if (r < 0) - goto fail; - - a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; - } - - r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_address_rr, true); - if (r < 0) - log_warning_errno(r, "Failed to add AAAA record to LLMNR zone: %m"); - - r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_ptr_rr, false); - if (r < 0) - log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); - } else { - if (a->llmnr_address_rr) { - if (a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); - a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); - } - - if (a->llmnr_ptr_rr) { - if (a->link->llmnr_ipv6_scope) - dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); - a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); - } - } - } - - return; - -fail: - log_debug_errno(r, "Failed to update address RRs: %m"); -} - -int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m) { - int r; - assert(a); - assert(m); - - r = sd_rtnl_message_addr_get_flags(m, &a->flags); - if (r < 0) - return r; - - sd_rtnl_message_addr_get_scope(m, &a->scope); - - link_allocate_scopes(a->link); - link_add_rrs(a->link, false); - - return 0; -} - -bool link_address_relevant(LinkAddress *a, bool local_multicast) { - assert(a); - - if (a->flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) - return false; - - if (a->scope >= (local_multicast ? RT_SCOPE_HOST : RT_SCOPE_LINK)) - return false; - - return true; -} diff --git a/src/resolve/resolved-link.h b/src/resolve/resolved-link.h deleted file mode 100644 index f534c12824..0000000000 --- a/src/resolve/resolved-link.h +++ /dev/null @@ -1,111 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "in-addr-util.h" -#include "ratelimit.h" -#include "resolve-util.h" - -typedef struct Link Link; -typedef struct LinkAddress LinkAddress; - -#include "resolved-dns-rr.h" -#include "resolved-dns-search-domain.h" -#include "resolved-dns-server.h" -#include "resolved-manager.h" - -#define LINK_SEARCH_DOMAINS_MAX 32 -#define LINK_DNS_SERVERS_MAX 32 - -struct LinkAddress { - Link *link; - - int family; - union in_addr_union in_addr; - - unsigned char flags, scope; - - DnsResourceRecord *llmnr_address_rr; - DnsResourceRecord *llmnr_ptr_rr; - - LIST_FIELDS(LinkAddress, addresses); -}; - -struct Link { - Manager *manager; - - int ifindex; - unsigned flags; - - LIST_HEAD(LinkAddress, addresses); - - LIST_HEAD(DnsServer, dns_servers); - DnsServer *current_dns_server; - unsigned n_dns_servers; - - LIST_HEAD(DnsSearchDomain, search_domains); - unsigned n_search_domains; - - ResolveSupport llmnr_support; - ResolveSupport mdns_support; - DnssecMode dnssec_mode; - Set *dnssec_negative_trust_anchors; - - DnsScope *unicast_scope; - DnsScope *llmnr_ipv4_scope; - DnsScope *llmnr_ipv6_scope; - DnsScope *mdns_ipv4_scope; - DnsScope *mdns_ipv6_scope; - - bool is_managed; - - char name[IF_NAMESIZE]; - uint32_t mtu; - uint8_t operstate; -}; - -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); -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); - -void link_flush_settings(Link *l); -void link_set_dnssec_mode(Link *l, DnssecMode mode); -void link_allocate_scopes(Link *l); - -DnsServer* link_set_dns_server(Link *l, DnsServer *s); -DnsServer* link_get_dns_server(Link *l); -void link_next_dns_server(Link *l); - -DnssecMode link_get_dnssec_mode(Link *l); -bool link_dnssec_supported(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); -bool link_address_relevant(LinkAddress *l, bool local_multicast); -void link_address_add_rrs(LinkAddress *a, bool force_remove); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free); diff --git a/src/resolve/resolved-llmnr.c b/src/resolve/resolved-llmnr.c deleted file mode 100644 index ef12abfbb5..0000000000 --- a/src/resolve/resolved-llmnr.c +++ /dev/null @@ -1,476 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . - ***/ - -#include -#include - -#include "fd-util.h" -#include "resolved-llmnr.h" -#include "resolved-manager.h" - -void manager_llmnr_stop(Manager *m) { - assert(m); - - m->llmnr_ipv4_udp_event_source = sd_event_source_unref(m->llmnr_ipv4_udp_event_source); - m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); - - m->llmnr_ipv6_udp_event_source = sd_event_source_unref(m->llmnr_ipv6_udp_event_source); - m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); - - m->llmnr_ipv4_tcp_event_source = sd_event_source_unref(m->llmnr_ipv4_tcp_event_source); - m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); - - m->llmnr_ipv6_tcp_event_source = sd_event_source_unref(m->llmnr_ipv6_tcp_event_source); - m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); -} - -int manager_llmnr_start(Manager *m) { - int r; - - assert(m); - - if (m->llmnr_support == RESOLVE_SUPPORT_NO) - return 0; - - r = manager_llmnr_ipv4_udp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - r = manager_llmnr_ipv4_tcp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - if (socket_ipv6_is_supported()) { - r = manager_llmnr_ipv6_udp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - r = manager_llmnr_ipv6_tcp_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - } - - return 0; - -eaddrinuse: - log_warning("There appears to be another LLMNR responder running. Turning off LLMNR support."); - m->llmnr_support = RESOLVE_SUPPORT_NO; - manager_llmnr_stop(m); - - return 0; -} - -static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - DnsTransaction *t = NULL; - Manager *m = userdata; - DnsScope *scope; - int r; - - r = manager_recv(m, fd, DNS_PROTOCOL_LLMNR, &p); - if (r <= 0) - return r; - - scope = manager_find_scope(m, p); - 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)); - - dns_scope_check_conflicts(scope, p); - - t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p))); - if (t) - 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)); - - dns_scope_process_query(scope, NULL, p); - } else - log_debug("Invalid LLMNR UDP packet, ignoring."); - - return 0; -} - -int manager_llmnr_ipv4_udp_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(LLMNR_PORT), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; - int r; - - assert(m); - - if (m->llmnr_ipv4_udp_fd >= 0) - return m->llmnr_ipv4_udp_fd; - - m->llmnr_ipv4_udp_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv4_udp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv4_udp_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv4_udp_event_source, m->llmnr_ipv4_udp_fd, EPOLLIN, on_llmnr_packet, m); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->llmnr_ipv4_udp_event_source, "llmnr-ipv4-udp"); - - return m->llmnr_ipv4_udp_fd; - -fail: - m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); - return r; -} - -int manager_llmnr_ipv6_udp_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(LLMNR_PORT), - }; - static const int one = 1, ttl = 255; - int r; - - assert(m); - - if (m->llmnr_ipv6_udp_fd >= 0) - return m->llmnr_ipv6_udp_fd; - - m->llmnr_ipv6_udp_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv6_udp_fd < 0) - return -errno; - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv6_udp_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv6_udp_event_source, m->llmnr_ipv6_udp_fd, EPOLLIN, on_llmnr_packet, m); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->llmnr_ipv6_udp_event_source, "llmnr-ipv6-udp"); - - return m->llmnr_ipv6_udp_fd; - -fail: - m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); - return r; -} - -static int on_llmnr_stream_packet(DnsStream *s) { - DnsScope *scope; - - assert(s); - - scope = manager_find_scope(s->manager, s->read_packet); - if (!scope) { - log_warning("Got LLMNR TCP packet on unknown scope. Ignroing."); - 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)); - - 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."); - - dns_stream_free(s); - return 0; -} - -static int on_llmnr_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_LLMNR, cfd); - if (r < 0) { - safe_close(cfd); - return r; - } - - stream->on_packet = on_llmnr_stream_packet; - return 0; -} - -int manager_llmnr_ipv4_tcp_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(LLMNR_PORT), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT; - int r; - - assert(m); - - if (m->llmnr_ipv4_tcp_fd >= 0) - return m->llmnr_ipv4_tcp_fd; - - m->llmnr_ipv4_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv4_tcp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv4_tcp_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = listen(m->llmnr_ipv4_tcp_fd, SOMAXCONN); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv4_tcp_event_source, m->llmnr_ipv4_tcp_fd, EPOLLIN, on_llmnr_stream, m); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->llmnr_ipv4_tcp_event_source, "llmnr-ipv4-tcp"); - - return m->llmnr_ipv4_tcp_fd; - -fail: - m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); - return r; -} - -int manager_llmnr_ipv6_tcp_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(LLMNR_PORT), - }; - static const int one = 1; - int r; - - assert(m); - - if (m->llmnr_ipv6_tcp_fd >= 0) - return m->llmnr_ipv6_tcp_fd; - - m->llmnr_ipv6_tcp_fd = socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->llmnr_ipv6_tcp_fd < 0) - return -errno; - - /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->llmnr_ipv6_tcp_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = listen(m->llmnr_ipv6_tcp_fd, SOMAXCONN); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->llmnr_ipv6_tcp_event_source, m->llmnr_ipv6_tcp_fd, EPOLLIN, on_llmnr_stream, m); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(m->llmnr_ipv6_tcp_event_source, "llmnr-ipv6-tcp"); - - return m->llmnr_ipv6_tcp_fd; - -fail: - m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); - return r; -} diff --git a/src/resolve/resolved-llmnr.h b/src/resolve/resolved-llmnr.h deleted file mode 100644 index 8133582fa7..0000000000 --- a/src/resolve/resolved-llmnr.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "resolved-manager.h" - -#define LLMNR_PORT 5355 - -int manager_llmnr_ipv4_udp_fd(Manager *m); -int manager_llmnr_ipv6_udp_fd(Manager *m); -int manager_llmnr_ipv4_tcp_fd(Manager *m); -int manager_llmnr_ipv6_tcp_fd(Manager *m); - -void manager_llmnr_stop(Manager *m); -int manager_llmnr_start(Manager *m); diff --git a/src/resolve/resolved-manager.c b/src/resolve/resolved-manager.c deleted file mode 100644 index e82c6ec563..0000000000 --- a/src/resolve/resolved-manager.c +++ /dev/null @@ -1,1239 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . - ***/ - -#include -#include -#include - -#include "af-list.h" -#include "alloc-util.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "fileio-label.h" -#include "hostname-util.h" -#include "io-util.h" -#include "netlink-util.h" -#include "network-internal.h" -#include "ordered-set.h" -#include "parse-util.h" -#include "random-util.h" -#include "resolved-bus.h" -#include "resolved-conf.h" -#include "resolved-etc-hosts.h" -#include "resolved-llmnr.h" -#include "resolved-manager.h" -#include "resolved-mdns.h" -#include "resolved-resolv-conf.h" -#include "socket-util.h" -#include "string-table.h" -#include "string-util.h" -#include "utf8.h" - -#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC) - -static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { - Manager *m = userdata; - uint16_t type; - Link *l; - int ifindex, r; - - assert(rtnl); - assert(m); - assert(mm); - - r = sd_netlink_message_get_type(mm, &type); - if (r < 0) - goto fail; - - r = sd_rtnl_message_link_get_ifindex(mm, &ifindex); - if (r < 0) - goto fail; - - l = hashmap_get(m->links, INT_TO_PTR(ifindex)); - - switch (type) { - - case RTM_NEWLINK:{ - bool is_new = !l; - - if (!l) { - r = link_new(m, &l, ifindex); - if (r < 0) - goto fail; - } - - r = link_update_rtnl(l, mm); - if (r < 0) - goto fail; - - r = link_update_monitor(l); - if (r < 0) - goto fail; - - if (is_new) - log_debug("Found new link %i/%s", ifindex, l->name); - - break; - } - - case RTM_DELLINK: - if (l) { - log_debug("Removing link %i/%s", l->ifindex, l->name); - link_free(l); - } - - break; - } - - return 0; - -fail: - log_warning_errno(r, "Failed to process RTNL link message: %m"); - return 0; -} - -static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { - Manager *m = userdata; - union in_addr_union address; - uint16_t type; - int r, ifindex, family; - LinkAddress *a; - Link *l; - - assert(rtnl); - assert(mm); - assert(m); - - r = sd_netlink_message_get_type(mm, &type); - if (r < 0) - goto fail; - - r = sd_rtnl_message_addr_get_ifindex(mm, &ifindex); - if (r < 0) - goto fail; - - l = hashmap_get(m->links, INT_TO_PTR(ifindex)); - if (!l) - return 0; - - r = sd_rtnl_message_addr_get_family(mm, &family); - if (r < 0) - goto fail; - - switch (family) { - - case AF_INET: - r = sd_netlink_message_read_in_addr(mm, IFA_LOCAL, &address.in); - if (r < 0) { - r = sd_netlink_message_read_in_addr(mm, IFA_ADDRESS, &address.in); - if (r < 0) - goto fail; - } - - break; - - case AF_INET6: - r = sd_netlink_message_read_in6_addr(mm, IFA_LOCAL, &address.in6); - if (r < 0) { - r = sd_netlink_message_read_in6_addr(mm, IFA_ADDRESS, &address.in6); - if (r < 0) - goto fail; - } - - break; - - default: - return 0; - } - - a = link_find_address(l, family, &address); - - switch (type) { - - case RTM_NEWADDR: - - if (!a) { - r = link_address_new(l, &a, family, &address); - if (r < 0) - return r; - } - - r = link_address_update_rtnl(a, mm); - if (r < 0) - return r; - - break; - - case RTM_DELADDR: - link_address_free(a); - break; - } - - return 0; - -fail: - log_warning_errno(r, "Failed to process RTNL address message: %m"); - return 0; -} - -static int manager_rtnl_listen(Manager *m) { - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; - sd_netlink_message *i; - int r; - - assert(m); - - /* First, subscribe to interfaces coming and going */ - r = sd_netlink_open(&m->rtnl); - if (r < 0) - return r; - - r = sd_netlink_attach_event(m->rtnl, m->event, SD_EVENT_PRIORITY_IMPORTANT); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_NEWLINK, manager_process_link, m); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_DELLINK, manager_process_link, m); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_NEWADDR, manager_process_address, m); - if (r < 0) - return r; - - r = sd_netlink_add_match(m->rtnl, RTM_DELADDR, manager_process_address, m); - if (r < 0) - return r; - - /* Then, enumerate all links */ - r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0); - if (r < 0) - return r; - - r = sd_netlink_message_request_dump(req, true); - if (r < 0) - return r; - - r = sd_netlink_call(m->rtnl, req, 0, &reply); - if (r < 0) - return r; - - for (i = reply; i; i = sd_netlink_message_next(i)) { - r = manager_process_link(m->rtnl, i, m); - if (r < 0) - return r; - } - - req = sd_netlink_message_unref(req); - reply = sd_netlink_message_unref(reply); - - /* Finally, enumerate all addresses, too */ - r = sd_rtnl_message_new_addr(m->rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC); - if (r < 0) - return r; - - r = sd_netlink_message_request_dump(req, true); - if (r < 0) - return r; - - r = sd_netlink_call(m->rtnl, req, 0, &reply); - if (r < 0) - return r; - - for (i = reply; i; i = sd_netlink_message_next(i)) { - r = manager_process_address(m->rtnl, i, m); - if (r < 0) - return r; - } - - return r; -} - -static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - Iterator i; - Link *l; - int r; - - assert(m); - - sd_network_monitor_flush(m->network_monitor); - - HASHMAP_FOREACH(l, m->links, i) { - r = link_update_monitor(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"); - - return 0; -} - -static int manager_network_monitor_listen(Manager *m) { - int r, fd, events; - - assert(m); - - r = sd_network_monitor_new(&m->network_monitor, NULL); - if (r < 0) - return r; - - fd = sd_network_monitor_get_fd(m->network_monitor); - if (fd < 0) - return fd; - - events = sd_network_monitor_get_events(m->network_monitor); - if (events < 0) - return events; - - r = sd_event_add_io(m->event, &m->network_event_source, fd, events, &on_network_event, m); - if (r < 0) - return r; - - r = sd_event_source_set_priority(m->network_event_source, SD_EVENT_PRIORITY_IMPORTANT+5); - if (r < 0) - return r; - - (void) sd_event_source_set_description(m->network_event_source, "network-monitor"); - - return 0; -} - -static int determine_hostname(char **llmnr_hostname, char **mdns_hostname) { - _cleanup_free_ char *h = NULL, *n = NULL; - char label[DNS_LABEL_MAX]; - const char *p; - int r, k; - - assert(llmnr_hostname); - assert(mdns_hostname); - - /* Extract and normalize the first label of the locally - * configured hostname, and check it's not "localhost". */ - - h = gethostname_malloc(); - if (!h) - return log_oom(); - - p = h; - r = dns_label_unescape(&p, label, sizeof(label)); - if (r < 0) - return log_error_errno(r, "Failed to unescape host name: %m"); - if (r == 0) { - log_error("Couldn't find a single label in hosntame."); - return -EINVAL; - } - - k = dns_label_undo_idna(label, r, label, sizeof(label)); - if (k < 0) - return log_error_errno(k, "Failed to undo IDNA: %m"); - if (k > 0) - r = k; - - if (!utf8_is_valid(label)) { - log_error("System hostname is not UTF-8 clean."); - return -EINVAL; - } - - r = dns_label_escape_new(label, r, &n); - if (r < 0) - return log_error_errno(r, "Failed to escape host name: %m"); - - if (is_localhost(n)) { - log_debug("System hostname is 'localhost', ignoring."); - return -EINVAL; - } - - r = dns_name_concat(n, "local", mdns_hostname); - if (r < 0) - return log_error_errno(r, "Failed to determine mDNS hostname: %m"); - - *llmnr_hostname = n; - n = NULL; - - return 0; -} - -static int on_hostname_change(sd_event_source *es, int fd, uint32_t revents, void *userdata) { - _cleanup_free_ char *llmnr_hostname = NULL, *mdns_hostname = NULL; - Manager *m = userdata; - int r; - - assert(m); - - r = determine_hostname(&llmnr_hostname, &mdns_hostname); - if (r < 0) - return 0; /* ignore invalid hostnames */ - - if (streq(llmnr_hostname, m->llmnr_hostname) && streq(mdns_hostname, m->mdns_hostname)) - return 0; - - log_info("System hostname changed to '%s'.", llmnr_hostname); - - free(m->llmnr_hostname); - free(m->mdns_hostname); - - m->llmnr_hostname = llmnr_hostname; - m->mdns_hostname = mdns_hostname; - - llmnr_hostname = mdns_hostname = NULL; - - manager_refresh_rrs(m); - - return 0; -} - -static int manager_watch_hostname(Manager *m) { - int r; - - assert(m); - - m->hostname_fd = open("/proc/sys/kernel/hostname", O_RDONLY|O_CLOEXEC|O_NDELAY|O_NOCTTY); - if (m->hostname_fd < 0) { - log_warning_errno(errno, "Failed to watch hostname: %m"); - return 0; - } - - r = sd_event_add_io(m->event, &m->hostname_event_source, m->hostname_fd, 0, on_hostname_change, m); - if (r < 0) { - if (r == -EPERM) - /* kernels prior to 3.2 don't support polling this file. Ignore the failure. */ - m->hostname_fd = safe_close(m->hostname_fd); - else - return log_error_errno(r, "Failed to add hostname event source: %m"); - } - - (void) sd_event_source_set_description(m->hostname_event_source, "hostname"); - - r = determine_hostname(&m->llmnr_hostname, &m->mdns_hostname); - if (r < 0) { - log_info("Defaulting to hostname 'gnu-linux'."); - m->llmnr_hostname = strdup("gnu-linux"); - if (!m->llmnr_hostname) - return log_oom(); - - m->mdns_hostname = strdup("gnu-linux.local"); - if (!m->mdns_hostname) - return log_oom(); - } else - log_info("Using system hostname '%s'.", m->llmnr_hostname); - - return 0; -} - -static int manager_sigusr1(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { - _cleanup_free_ char *buffer = NULL; - _cleanup_fclose_ FILE *f = NULL; - Manager *m = userdata; - size_t size = 0; - DnsScope *scope; - - assert(s); - assert(si); - assert(m); - - f = open_memstream(&buffer, &size); - if (!f) - return log_oom(); - - LIST_FOREACH(scopes, scope, m->dns_scopes) - dns_scope_dump(scope, f); - - if (fflush_and_check(f) < 0) - return log_oom(); - - log_dump(LOG_INFO, buffer); - return 0; -} - -int manager_new(Manager **ret) { - _cleanup_(manager_freep) Manager *m = NULL; - int r; - - assert(ret); - - m = new0(Manager, 1); - if (!m) - return -ENOMEM; - - 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->hostname_fd = -1; - - m->llmnr_support = RESOLVE_SUPPORT_YES; - m->mdns_support = RESOLVE_SUPPORT_NO; - m->dnssec_mode = DNSSEC_NO; - m->read_resolv_conf = true; - m->need_builtin_fallbacks = true; - m->etc_hosts_last = m->etc_hosts_mtime = USEC_INFINITY; - - r = dns_trust_anchor_load(&m->trust_anchor); - if (r < 0) - return r; - - r = manager_parse_config_file(m); - if (r < 0) - return r; - - r = sd_event_default(&m->event); - if (r < 0) - return r; - - sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); - sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); - - sd_event_set_watchdog(m->event, true); - - r = manager_watch_hostname(m); - if (r < 0) - return r; - - r = dns_scope_new(m, &m->unicast_scope, NULL, DNS_PROTOCOL_DNS, AF_UNSPEC); - if (r < 0) - return r; - - r = manager_network_monitor_listen(m); - if (r < 0) - return r; - - r = manager_rtnl_listen(m); - if (r < 0) - return r; - - r = manager_connect_bus(m); - if (r < 0) - return r; - - (void) sd_event_add_signal(m->event, &m->sigusr1_event_source, SIGUSR1, manager_sigusr1, m); - - *ret = m; - m = NULL; - - return 0; -} - -int manager_start(Manager *m) { - int r; - - assert(m); - - r = manager_llmnr_start(m); - if (r < 0) - return r; - - r = manager_mdns_start(m); - if (r < 0) - return r; - - return 0; -} - -Manager *manager_free(Manager *m) { - Link *l; - - if (!m) - return NULL; - - dns_server_unlink_all(m->dns_servers); - dns_server_unlink_all(m->fallback_dns_servers); - dns_search_domain_unlink_all(m->search_domains); - - while ((l = hashmap_first(m->links))) - link_free(l); - - while (m->dns_queries) - dns_query_free(m->dns_queries); - - dns_scope_free(m->unicast_scope); - - hashmap_free(m->links); - hashmap_free(m->dns_transactions); - - sd_event_source_unref(m->network_event_source); - sd_network_monitor_unref(m->network_monitor); - - sd_netlink_unref(m->rtnl); - sd_event_source_unref(m->rtnl_event_source); - - manager_llmnr_stop(m); - manager_mdns_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_unref(m->event); - - dns_resource_key_unref(m->llmnr_host_ipv4_key); - dns_resource_key_unref(m->llmnr_host_ipv6_key); - - sd_event_source_unref(m->hostname_event_source); - safe_close(m->hostname_fd); - free(m->llmnr_hostname); - free(m->mdns_hostname); - - dns_trust_anchor_flush(&m->trust_anchor); - manager_etc_hosts_flush(m); - - free(m); - - return NULL; -} - -int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) - + CMSG_SPACE(int) /* ttl/hoplimit */ - + EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */]; - } control; - union sockaddr_union sa; - struct msghdr mh = {}; - struct cmsghdr *cmsg; - struct iovec iov; - ssize_t ms, l; - int r; - - assert(m); - assert(fd >= 0); - assert(ret); - - ms = next_datagram_size_fd(fd); - if (ms < 0) - return ms; - - r = dns_packet_new(&p, protocol, ms); - if (r < 0) - return r; - - iov.iov_base = DNS_PACKET_DATA(p); - iov.iov_len = p->allocated; - - mh.msg_name = &sa.sa; - mh.msg_namelen = sizeof(sa); - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_control = &control; - mh.msg_controllen = sizeof(control); - - l = recvmsg(fd, &mh, 0); - if (l < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return -errno; - } - - if (l <= 0) - return -EIO; - - assert(!(mh.msg_flags & MSG_CTRUNC)); - assert(!(mh.msg_flags & MSG_TRUNC)); - - p->size = (size_t) l; - - p->family = sa.sa.sa_family; - p->ipproto = IPPROTO_UDP; - if (p->family == AF_INET) { - p->sender.in = sa.in.sin_addr; - p->sender_port = be16toh(sa.in.sin_port); - } else if (p->family == AF_INET6) { - p->sender.in6 = sa.in6.sin6_addr; - p->sender_port = be16toh(sa.in6.sin6_port); - p->ifindex = sa.in6.sin6_scope_id; - } else - return -EAFNOSUPPORT; - - CMSG_FOREACH(cmsg, &mh) { - - if (cmsg->cmsg_level == IPPROTO_IPV6) { - assert(p->family == AF_INET6); - - switch (cmsg->cmsg_type) { - - case IPV6_PKTINFO: { - struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); - - if (p->ifindex <= 0) - p->ifindex = i->ipi6_ifindex; - - p->destination.in6 = i->ipi6_addr; - break; - } - - case IPV6_HOPLIMIT: - p->ttl = *(int *) CMSG_DATA(cmsg); - break; - - } - } else if (cmsg->cmsg_level == IPPROTO_IP) { - assert(p->family == AF_INET); - - switch (cmsg->cmsg_type) { - - case IP_PKTINFO: { - struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); - - if (p->ifindex <= 0) - p->ifindex = i->ipi_ifindex; - - p->destination.in = i->ipi_addr; - break; - } - - case IP_TTL: - p->ttl = *(int *) CMSG_DATA(cmsg); - break; - } - } - } - - /* The Linux kernel sets the interface index to the loopback - * device if the packet came from the local host since it - * avoids the routing table in such a case. Let's unset the - * interface index in such a case. */ - if (p->ifindex == LOOPBACK_IFINDEX) - p->ifindex = 0; - - if (protocol != DNS_PROTOCOL_DNS) { - /* If we don't know the interface index still, we look for the - * first local interface with a matching address. Yuck! */ - if (p->ifindex <= 0) - p->ifindex = manager_find_ifindex(m, p->family, &p->destination); - } - - *ret = p; - p = NULL; - - return 1; -} - -static int sendmsg_loop(int fd, struct msghdr *mh, int flags) { - int r; - - assert(fd >= 0); - assert(mh); - - for (;;) { - if (sendmsg(fd, mh, flags) >= 0) - return 0; - - if (errno == EINTR) - continue; - - if (errno != EAGAIN) - return -errno; - - r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); - if (r < 0) - return r; - if (r == 0) - return -ETIMEDOUT; - } -} - -static int write_loop(int fd, void *message, size_t length) { - int r; - - assert(fd >= 0); - assert(message); - - for (;;) { - if (write(fd, message, length) >= 0) - return 0; - - if (errno == EINTR) - continue; - - if (errno != EAGAIN) - return -errno; - - r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); - if (r < 0) - return r; - if (r == 0) - return -ETIMEDOUT; - } -} - -int manager_write(Manager *m, int fd, DnsPacket *p) { - int r; - - log_debug("Sending %s packet with id %" PRIu16 ".", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p)); - - r = write_loop(fd, DNS_PACKET_DATA(p), p->size); - if (r < 0) - return r; - - return 0; -} - -static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_addr *addr, uint16_t port, DnsPacket *p) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - }; - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(sizeof(struct in_pktinfo))]; - } control; - struct msghdr mh = {}; - struct iovec iov; - - assert(m); - assert(fd >= 0); - assert(addr); - 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_port = htobe16(port), - - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_name = &sa.sa; - mh.msg_namelen = sizeof(sa.in); - - if (ifindex > 0) { - struct cmsghdr *cmsg; - struct in_pktinfo *pi; - - zero(control); - - mh.msg_control = &control; - mh.msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)); - - cmsg = CMSG_FIRSTHDR(&mh); - cmsg->cmsg_len = mh.msg_controllen; - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_PKTINFO; - - pi = (struct in_pktinfo*) CMSG_DATA(cmsg); - pi->ipi_ifindex = ifindex; - } - - 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) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - }; - union { - struct cmsghdr header; /* For alignment */ - uint8_t buffer[CMSG_SPACE(sizeof(struct in6_pktinfo))]; - } control; - struct msghdr mh = {}; - struct iovec iov; - - assert(m); - assert(fd >= 0); - assert(addr); - 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_port = htobe16(port), - sa.in6.sin6_scope_id = ifindex; - - mh.msg_iov = &iov; - mh.msg_iovlen = 1; - mh.msg_name = &sa.sa; - mh.msg_namelen = sizeof(sa.in6); - - if (ifindex > 0) { - struct cmsghdr *cmsg; - struct in6_pktinfo *pi; - - zero(control); - - mh.msg_control = &control; - mh.msg_controllen = CMSG_LEN(sizeof(struct in6_pktinfo)); - - cmsg = CMSG_FIRSTHDR(&mh); - cmsg->cmsg_len = mh.msg_controllen; - cmsg->cmsg_level = IPPROTO_IPV6; - cmsg->cmsg_type = IPV6_PKTINFO; - - pi = (struct in6_pktinfo*) CMSG_DATA(cmsg); - pi->ipi6_ifindex = ifindex; - } - - 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) { - assert(m); - assert(fd >= 0); - assert(addr); - 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 -EAFNOSUPPORT; -} - -uint32_t manager_find_mtu(Manager *m) { - uint32_t mtu = 0; - Link *l; - Iterator i; - - /* If we don't know on which link a DNS packet would be - * delivered, let's find the largest MTU that works on all - * interfaces we know of */ - - HASHMAP_FOREACH(l, m->links, i) { - if (l->mtu <= 0) - continue; - - if (mtu <= 0 || l->mtu < mtu) - mtu = l->mtu; - } - - return mtu; -} - -int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr) { - LinkAddress *a; - - assert(m); - - a = manager_find_link_address(m, family, in_addr); - if (a) - return a->link->ifindex; - - return 0; -} - -void manager_refresh_rrs(Manager *m) { - Iterator i; - Link *l; - - assert(m); - - m->llmnr_host_ipv4_key = dns_resource_key_unref(m->llmnr_host_ipv4_key); - m->llmnr_host_ipv6_key = dns_resource_key_unref(m->llmnr_host_ipv6_key); - - HASHMAP_FOREACH(l, m->links, i) { - link_add_rrs(l, true); - link_add_rrs(l, false); - } -} - -int manager_next_hostname(Manager *m) { - const char *p; - uint64_t u, a; - char *h, *k; - int r; - - assert(m); - - p = strchr(m->llmnr_hostname, 0); - assert(p); - - while (p > m->llmnr_hostname) { - if (!strchr("0123456789", p[-1])) - break; - - p--; - } - - if (*p == 0 || safe_atou64(p, &u) < 0 || u <= 0) - u = 1; - - /* Add a random number to the old value. This way we can avoid - * that two hosts pick the same hostname, win on IPv4 and lose - * on IPv6 (or vice versa), and pick the same hostname - * replacement hostname, ad infinitum. We still want the - * numbers to go up monotonically, hence we just add a random - * value 1..10 */ - - random_bytes(&a, sizeof(a)); - u += 1 + a % 10; - - if (asprintf(&h, "%.*s%" PRIu64, (int) (p - m->llmnr_hostname), m->llmnr_hostname, u) < 0) - return -ENOMEM; - - r = dns_name_concat(h, "local", &k); - if (r < 0) { - free(h); - return r; - } - - log_info("Hostname conflict, changing published hostname from '%s' to '%s'.", m->llmnr_hostname, h); - - free(m->llmnr_hostname); - m->llmnr_hostname = h; - - free(m->mdns_hostname); - m->mdns_hostname = k; - - manager_refresh_rrs(m); - - return 0; -} - -LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr) { - Iterator i; - Link *l; - - assert(m); - - HASHMAP_FOREACH(l, m->links, i) { - LinkAddress *a; - - a = link_find_address(l, family, in_addr); - if (a) - return a; - } - - return NULL; -} - -bool manager_our_packet(Manager *m, DnsPacket *p) { - assert(m); - assert(p); - - return !!manager_find_link_address(m, p->family, &p->sender); -} - -DnsScope* manager_find_scope(Manager *m, DnsPacket *p) { - Link *l; - - assert(m); - assert(p); - - l = hashmap_get(m->links, INT_TO_PTR(p->ifindex)); - if (!l) - return NULL; - - switch (p->protocol) { - case DNS_PROTOCOL_LLMNR: - if (p->family == AF_INET) - return l->llmnr_ipv4_scope; - else if (p->family == AF_INET6) - return l->llmnr_ipv6_scope; - - break; - - case DNS_PROTOCOL_MDNS: - if (p->family == AF_INET) - return l->mdns_ipv4_scope; - else if (p->family == AF_INET6) - return l->mdns_ipv6_scope; - - break; - - default: - break; - } - - return NULL; -} - -void manager_verify_all(Manager *m) { - DnsScope *s; - - assert(m); - - LIST_FOREACH(scopes, s, m->dns_scopes) - dns_zone_verify_all(&s->zone); -} - -int manager_is_own_hostname(Manager *m, const char *name) { - int r; - - assert(m); - assert(name); - - if (m->llmnr_hostname) { - r = dns_name_equal(name, m->llmnr_hostname); - if (r != 0) - return r; - } - - if (m->mdns_hostname) - return dns_name_equal(name, m->mdns_hostname); - - return 0; -} - -int manager_compile_dns_servers(Manager *m, OrderedSet **dns) { - DnsServer *s; - Iterator i; - Link *l; - int r; - - assert(m); - assert(dns); - - r = ordered_set_ensure_allocated(dns, &dns_server_hash_ops); - if (r < 0) - return r; - - /* First add the system-wide servers and domains */ - LIST_FOREACH(servers, s, m->dns_servers) { - r = ordered_set_put(*dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - - /* Then, add the per-link servers */ - HASHMAP_FOREACH(l, m->links, i) { - LIST_FOREACH(servers, s, l->dns_servers) { - r = ordered_set_put(*dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - /* If we found nothing, add the fallback servers */ - if (ordered_set_isempty(*dns)) { - LIST_FOREACH(servers, s, m->fallback_dns_servers) { - r = ordered_set_put(*dns, s); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - return 0; -} - -int manager_compile_search_domains(Manager *m, OrderedSet **domains) { - DnsSearchDomain *d; - Iterator i; - Link *l; - int r; - - assert(m); - assert(domains); - - r = ordered_set_ensure_allocated(domains, &dns_name_hash_ops); - if (r < 0) - return r; - - LIST_FOREACH(domains, d, m->search_domains) { - r = ordered_set_put(*domains, d->name); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - - HASHMAP_FOREACH(l, m->links, i) { - - LIST_FOREACH(domains, d, l->search_domains) { - r = ordered_set_put(*domains, d->name); - if (r == -EEXIST) - continue; - if (r < 0) - return r; - } - } - - return 0; -} - -DnssecMode manager_get_dnssec_mode(Manager *m) { - assert(m); - - if (m->dnssec_mode != _DNSSEC_MODE_INVALID) - return m->dnssec_mode; - - return DNSSEC_NO; -} - -bool manager_dnssec_supported(Manager *m) { - DnsServer *server; - Iterator i; - Link *l; - - assert(m); - - if (manager_get_dnssec_mode(m) == DNSSEC_NO) - return false; - - server = manager_get_dns_server(m); - if (server && !dns_server_dnssec_supported(server)) - return false; - - HASHMAP_FOREACH(l, m->links, i) - if (!link_dnssec_supported(l)) - return false; - - return true; -} - -void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key) { - - assert(verdict >= 0); - assert(verdict < _DNSSEC_VERDICT_MAX); - - if (log_get_max_level() >= LOG_DEBUG) { - _cleanup_free_ char *s = NULL; - - (void) dns_resource_key_to_string(key, &s); - - log_debug("Found verdict for lookup %s: %s", s ? strstrip(s) : "n/a", dnssec_verdict_to_string(verdict)); - } - - m->n_dnssec_verdict[verdict]++; -} - -bool manager_routable(Manager *m, int family) { - Iterator i; - Link *l; - - assert(m); - - /* Returns true if the host has at least one interface with a routable address of the specified type */ - - HASHMAP_FOREACH(l, m->links, i) - if (link_relevant(l, family, false)) - return true; - - return false; -} diff --git a/src/resolve/resolved-manager.h b/src/resolve/resolved-manager.h deleted file mode 100644 index e82a824f29..0000000000 --- a/src/resolve/resolved-manager.h +++ /dev/null @@ -1,171 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include "sd-event.h" -#include "sd-netlink.h" -#include "sd-network.h" - -#include "hashmap.h" -#include "list.h" -#include "ordered-set.h" -#include "resolve-util.h" - -typedef struct Manager Manager; - -#include "resolved-dns-query.h" -#include "resolved-dns-search-domain.h" -#include "resolved-dns-server.h" -#include "resolved-dns-stream.h" -#include "resolved-dns-trust-anchor.h" -#include "resolved-link.h" - -#define MANAGER_SEARCH_DOMAINS_MAX 32 -#define MANAGER_DNS_SERVERS_MAX 32 - -struct Manager { - sd_event *event; - - ResolveSupport llmnr_support; - ResolveSupport mdns_support; - DnssecMode dnssec_mode; - - /* Network */ - Hashmap *links; - - sd_netlink *rtnl; - sd_event_source *rtnl_event_source; - - sd_network_monitor *network_monitor; - sd_event_source *network_event_source; - - /* DNS query management */ - Hashmap *dns_transactions; - LIST_HEAD(DnsQuery, dns_queries); - unsigned n_dns_queries; - - LIST_HEAD(DnsStream, dns_streams); - unsigned n_dns_streams; - - /* Unicast dns */ - LIST_HEAD(DnsServer, dns_servers); - LIST_HEAD(DnsServer, fallback_dns_servers); - unsigned n_dns_servers; /* counts both main and fallback */ - DnsServer *current_dns_server; - - LIST_HEAD(DnsSearchDomain, search_domains); - unsigned n_search_domains; - bool permit_domain_search; - - bool need_builtin_fallbacks:1; - - bool read_resolv_conf:1; - usec_t resolv_conf_mtime; - - DnsTrustAnchor trust_anchor; - - LIST_HEAD(DnsScope, dns_scopes); - DnsScope *unicast_scope; - - /* LLMNR */ - int llmnr_ipv4_udp_fd; - int llmnr_ipv6_udp_fd; - int llmnr_ipv4_tcp_fd; - int llmnr_ipv6_tcp_fd; - - sd_event_source *llmnr_ipv4_udp_event_source; - sd_event_source *llmnr_ipv6_udp_event_source; - sd_event_source *llmnr_ipv4_tcp_event_source; - sd_event_source *llmnr_ipv6_tcp_event_source; - - /* mDNS */ - int mdns_ipv4_fd; - int mdns_ipv6_fd; - - sd_event_source *mdns_ipv4_event_source; - sd_event_source *mdns_ipv6_event_source; - - /* dbus */ - sd_bus *bus; - sd_event_source *bus_retry_event_source; - - /* The hostname we publish on LLMNR and mDNS */ - char *llmnr_hostname; - char *mdns_hostname; - DnsResourceKey *llmnr_host_ipv4_key; - DnsResourceKey *llmnr_host_ipv6_key; - - /* Watch the system hostname */ - int hostname_fd; - sd_event_source *hostname_event_source; - - /* Watch for system suspends */ - sd_bus_slot *prepare_for_sleep_slot; - - sd_event_source *sigusr1_event_source; - - unsigned n_transactions_total; - unsigned n_dnssec_verdict[_DNSSEC_VERDICT_MAX]; - - /* Data from /etc/hosts */ - Set* etc_hosts_by_address; - Hashmap* etc_hosts_by_name; - usec_t etc_hosts_last, etc_hosts_mtime; -}; - -/* Manager */ - -int manager_new(Manager **ret); -Manager* manager_free(Manager *m); - -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_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret); - -int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr); -LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr); - -void manager_refresh_rrs(Manager *m); -int manager_next_hostname(Manager *m); - -bool manager_our_packet(Manager *m, DnsPacket *p); -DnsScope* manager_find_scope(Manager *m, DnsPacket *p); - -void manager_verify_all(Manager *m); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); - -#define EXTRA_CMSG_SPACE 1024 - -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); - -DnssecMode manager_get_dnssec_mode(Manager *m); -bool manager_dnssec_supported(Manager *m); - -void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key); - -bool manager_routable(Manager *m, int family); diff --git a/src/resolve/resolved-mdns.c b/src/resolve/resolved-mdns.c deleted file mode 100644 index bc8b8b809b..0000000000 --- a/src/resolve/resolved-mdns.c +++ /dev/null @@ -1,287 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 Daniel Mack - - 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 . - ***/ - -#include -#include -#include - -#include "fd-util.h" -#include "resolved-manager.h" -#include "resolved-mdns.h" - -void manager_mdns_stop(Manager *m) { - assert(m); - - m->mdns_ipv4_event_source = sd_event_source_unref(m->mdns_ipv4_event_source); - m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); - - m->mdns_ipv6_event_source = sd_event_source_unref(m->mdns_ipv6_event_source); - m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); -} - -int manager_mdns_start(Manager *m) { - int r; - - assert(m); - - if (m->mdns_support == RESOLVE_SUPPORT_NO) - return 0; - - r = manager_mdns_ipv4_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - - if (socket_ipv6_is_supported()) { - r = manager_mdns_ipv6_fd(m); - if (r == -EADDRINUSE) - goto eaddrinuse; - if (r < 0) - return r; - } - - return 0; - -eaddrinuse: - log_warning("There appears to be another mDNS responder running. Turning off mDNS support."); - m->mdns_support = RESOLVE_SUPPORT_NO; - manager_mdns_stop(m); - - return 0; -} - -static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; - Manager *m = userdata; - DnsScope *scope; - int r; - - r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p); - if (r <= 0) - return r; - - scope = manager_find_scope(m, p); - if (!scope) { - log_warning("Got mDNS UDP packet on unknown scope. Ignoring."); - return 0; - } - - if (dns_packet_validate_reply(p) > 0) { - DnsResourceRecord *rr; - - log_debug("Got mDNS reply packet"); - - /* - * mDNS is different from regular DNS and LLMNR with regard to handling responses. - * While on other protocols, we can ignore every answer that doesn't match a question - * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all - * incoming information, regardless of the DNS packet ID. - * - * Hence, extract the packet here, and try to find a transaction for answer the we got - * and complete it. Also store the new information in scope's cache. - */ - r = dns_packet_extract(p); - if (r < 0) { - log_debug("mDNS packet extraction failed."); - return 0; - } - - dns_scope_check_conflicts(scope, p); - - DNS_ANSWER_FOREACH(rr, p->answer) { - const char *name = DNS_RESOURCE_KEY_NAME(rr->key); - DnsTransaction *t; - - /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa, - * we assume someone's playing tricks on us and discard the packet completely. */ - if (!(dns_name_endswith(name, "in-addr.arpa") > 0 || - dns_name_endswith(name, "local") > 0)) - return 0; - - t = dns_scope_find_transaction(scope, rr->key, false); - if (t) - dns_transaction_process_reply(t, p); - } - - dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender); - - } else if (dns_packet_validate_query(p) > 0) { - log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); - - dns_scope_process_query(scope, NULL, p); - } else - log_debug("Invalid mDNS UDP packet."); - - return 0; -} - -int manager_mdns_ipv4_fd(Manager *m) { - union sockaddr_union sa = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(MDNS_PORT), - }; - static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; - int r; - - assert(m); - - if (m->mdns_ipv4_fd >= 0) - return m->mdns_ipv4_fd; - - m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->mdns_ipv4_fd < 0) - return -errno; - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* Disable Don't-Fragment bit in the IP header */ - r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m); - if (r < 0) - goto fail; - - return m->mdns_ipv4_fd; - -fail: - m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); - return r; -} - -int manager_mdns_ipv6_fd(Manager *m) { - union sockaddr_union sa = { - .in6.sin6_family = AF_INET6, - .in6.sin6_port = htobe16(MDNS_PORT), - }; - static const int one = 1, ttl = 255; - int r; - - assert(m); - - if (m->mdns_ipv6_fd >= 0) - return m->mdns_ipv6_fd; - - m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (m->mdns_ipv6_fd < 0) - return -errno; - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6)); - if (r < 0) { - r = -errno; - goto fail; - } - - r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m); - if (r < 0) - goto fail; - - return m->mdns_ipv6_fd; - -fail: - m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); - return r; -} diff --git a/src/resolve/resolved-mdns.h b/src/resolve/resolved-mdns.h deleted file mode 100644 index 5d274648f4..0000000000 --- a/src/resolve/resolved-mdns.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 Daniel Mack - - 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 . -***/ - -#include "resolved-manager.h" - -#define MDNS_PORT 5353 - -int manager_mdns_ipv4_fd(Manager *m); -int manager_mdns_ipv6_fd(Manager *m); - -void manager_mdns_stop(Manager *m); -int manager_mdns_start(Manager *m); diff --git a/src/resolve/resolved-resolv-conf.c b/src/resolve/resolved-resolv-conf.c deleted file mode 100644 index 065427b690..0000000000 --- a/src/resolve/resolved-resolv-conf.c +++ /dev/null @@ -1,267 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . - ***/ - -#include - -#include "alloc-util.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "fileio-label.h" -#include "fileio.h" -#include "ordered-set.h" -#include "resolved-conf.h" -#include "resolved-resolv-conf.h" -#include "string-util.h" -#include "strv.h" - -int manager_read_resolv_conf(Manager *m) { - _cleanup_fclose_ FILE *f = NULL; - struct stat st, own; - char line[LINE_MAX]; - usec_t t; - int r; - - assert(m); - - /* Reads the system /etc/resolv.conf, if it exists and is not - * symlinked to our own resolv.conf instance */ - - if (!m->read_resolv_conf) - return 0; - - r = stat("/etc/resolv.conf", &st); - if (r < 0) { - if (errno == ENOENT) - return 0; - - r = log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m"); - goto clear; - } - - /* Have we already seen the file? */ - t = timespec_load(&st.st_mtim); - if (t == m->resolv_conf_mtime) - return 0; - - /* Is it symlinked to our own file? */ - if (stat("/run/systemd/resolve/resolv.conf", &own) >= 0 && - st.st_dev == own.st_dev && - st.st_ino == own.st_ino) - return 0; - - f = fopen("/etc/resolv.conf", "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - r = log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m"); - goto clear; - } - - if (fstat(fileno(f), &st) < 0) { - r = log_error_errno(errno, "Failed to stat open file: %m"); - goto clear; - } - - dns_server_mark_all(m->dns_servers); - dns_search_domain_mark_all(m->search_domains); - - FOREACH_LINE(line, f, r = -errno; goto clear) { - const char *a; - char *l; - - l = strstrip(line); - if (*l == '#' || *l == ';') - continue; - - a = first_word(l, "nameserver"); - if (a) { - r = manager_add_dns_server_by_string(m, DNS_SERVER_SYSTEM, a); - if (r < 0) - log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a); - - continue; - } - - a = first_word(l, "domain"); - if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */ - a = first_word(l, "search"); - if (a) { - r = manager_parse_search_domains_and_warn(m, a); - if (r < 0) - log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a); - } - } - - m->resolv_conf_mtime = t; - - /* Flush out all servers and search domains that are still - * marked. Those are then ones that didn't appear in the new - * /etc/resolv.conf */ - dns_server_unlink_marked(m->dns_servers); - dns_search_domain_unlink_marked(m->search_domains); - - /* Whenever /etc/resolv.conf changes, start using the first - * DNS server of it. This is useful to deal with broken - * network managing implementations (like NetworkManager), - * that when connecting to a VPN place both the VPN DNS - * servers and the local ones in /etc/resolv.conf. Without - * resetting the DNS server to use back to the first entry we - * will continue to use the local one thus being unable to - * resolve VPN domains. */ - manager_set_dns_server(m, m->dns_servers); - - /* Unconditionally flush the cache when /etc/resolv.conf is - * modified, even if the data it contained was completely - * identical to the previous version we used. We do this - * because altering /etc/resolv.conf is typically done when - * the network configuration changes, and that should be - * enough to flush the global unicast DNS cache. */ - if (m->unicast_scope) - dns_cache_flush(&m->unicast_scope->cache); - - return 0; - -clear: - dns_server_unlink_all(m->dns_servers); - dns_search_domain_unlink_all(m->search_domains); - return r; -} - -static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { - assert(s); - assert(f); - assert(count); - - (void) dns_server_string(s); - - if (!s->server_string) { - log_warning("Our of memory, or invalid DNS address. Ignoring server."); - return; - } - - if (*count == MAXNS) - fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f); - (*count) ++; - - fprintf(f, "nameserver %s\n", s->server_string); -} - -static void write_resolv_conf_search( - const char *domain, - FILE *f, - unsigned *count, - unsigned *length) { - - assert(domain); - assert(f); - assert(length); - - if (*count >= MAXDNSRCH || - *length + strlen(domain) > 256) { - if (*count == MAXDNSRCH) - fputs(" # Too many search domains configured, remaining ones ignored.", f); - if (*length <= 256) - fputs(" # Total length of all search domains is too long, remaining ones ignored.", f); - - return; - } - - (*length) += strlen(domain); - (*count) ++; - - fputc(' ', f); - fputs(domain, f); -} - -static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) { - 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); - - if (ordered_set_isempty(dns)) - fputs("# No DNS servers known.\n", f); - else { - unsigned count = 0; - DnsServer *s; - - ORDERED_SET_FOREACH(s, dns, i) - write_resolv_conf_server(s, f, &count); - } - - if (!ordered_set_isempty(domains)) { - unsigned length = 0, count = 0; - char *domain; - - fputs("search", f); - ORDERED_SET_FOREACH(domain, domains, i) - write_resolv_conf_search(domain, f, &count, &length); - fputs("\n", f); - } - - return fflush_and_check(f); -} - -int manager_write_resolv_conf(Manager *m) { - - _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL; - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - int r; - - assert(m); - - /* Read the system /etc/resolv.conf first */ - 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; - - r = manager_compile_search_domains(m, &domains); - if (r < 0) - return r; - - r = fopen_temporary_label(PRIVATE_RESOLV_CONF, PRIVATE_RESOLV_CONF, &f, &temp_path); - if (r < 0) - return r; - - fchmod(fileno(f), 0644); - - r = write_resolv_conf_contents(f, dns, domains); - if (r < 0) - goto fail; - - if (rename(temp_path, PRIVATE_RESOLV_CONF) < 0) { - r = -errno; - goto fail; - } - - return 0; - -fail: - (void) unlink(PRIVATE_RESOLV_CONF); - (void) unlink(temp_path); - return r; -} diff --git a/src/resolve/resolved-resolv-conf.h b/src/resolve/resolved-resolv-conf.h deleted file mode 100644 index 75fa080e4c..0000000000 --- a/src/resolve/resolved-resolv-conf.h +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include "resolved-manager.h" - -#define PRIVATE_RESOLV_CONF "/run/systemd/resolve/resolv.conf" - -int manager_read_resolv_conf(Manager *m); -int manager_write_resolv_conf(Manager *m); diff --git a/src/resolve/resolved.c b/src/resolve/resolved.c deleted file mode 100644 index c7e2ab14d6..0000000000 --- a/src/resolve/resolved.c +++ /dev/null @@ -1,112 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include "sd-daemon.h" -#include "sd-event.h" - -#include "capability-util.h" -#include "mkdir.h" -#include "resolved-conf.h" -#include "resolved-manager.h" -#include "resolved-resolv-conf.h" -#include "selinux-util.h" -#include "signal-util.h" -#include "user-util.h" - -int main(int argc, char *argv[]) { - _cleanup_(manager_freep) Manager *m = NULL; - const char *user = "systemd-resolve"; - uid_t uid; - gid_t gid; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - if (argc != 1) { - log_error("This program takes no arguments."); - r = -EINVAL; - goto finish; - } - - umask(0022); - - r = mac_selinux_init(NULL); - if (r < 0) { - log_error_errno(r, "SELinux setup failed: %m"); - goto finish; - } - - r = get_user_creds(&user, &uid, &gid, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Cannot resolve user name %s: %m", user); - goto finish; - } - - /* Always create the directory where resolv.conf will live */ - r = mkdir_safe_label("/run/systemd/resolve", 0755, uid, gid); - if (r < 0) { - log_error_errno(r, "Could not create runtime directory: %m"); - goto finish; - } - - r = drop_privileges(uid, gid, 0); - if (r < 0) - goto finish; - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, -1) >= 0); - - r = manager_new(&m); - if (r < 0) { - log_error_errno(r, "Could not create manager: %m"); - goto finish; - } - - r = manager_start(m); - if (r < 0) { - log_error_errno(r, "Failed to start manager: %m"); - 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"); - - sd_notify(false, - "READY=1\n" - "STATUS=Processing requests..."); - - r = sd_event_loop(m->event); - if (r < 0) { - log_error_errno(r, "Event loop failed: %m"); - goto finish; - } - - sd_event_get_exit_code(m->event, &r); - -finish: - sd_notify(false, - "STOPPING=1\n" - "STATUS=Shutting down..."); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/resolve/resolved.conf.in b/src/resolve/resolved.conf.in deleted file mode 100644 index efc9c6733a..0000000000 --- a/src/resolve/resolved.conf.in +++ /dev/null @@ -1,19 +0,0 @@ -# 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. -# -# Entries in this file show the compile time defaults. -# You can change settings by editing this file. -# Defaults can be restored by simply deleting this file. -# -# See resolved.conf(5) for details - -[Resolve] -#DNS= -#FallbackDNS=@DNS_SERVERS@ -#Domains= -#LLMNR=yes -#DNSSEC=no diff --git a/src/resolve/src/.gitignore b/src/resolve/src/.gitignore new file mode 100644 index 0000000000..f0835923b7 --- /dev/null +++ b/src/resolve/src/.gitignore @@ -0,0 +1,6 @@ +/resolved-gperf.c +/resolved.conf +/dns_type-from-name.gperf +/dns_type-from-name.h +/dns_type-list.txt +/dns_type-to-name.h diff --git a/src/resolve/src/Makefile b/src/resolve/src/Makefile new file mode 100644 index 0000000000..6f62e7b086 --- /dev/null +++ b/src/resolve/src/Makefile @@ -0,0 +1,208 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_RESOLVED),) + +systemd_resolved_SOURCES = \ + src/resolve/resolved.c \ + src/resolve/resolved-manager.c \ + src/resolve/resolved-manager.h \ + src/resolve/resolved-conf.c \ + src/resolve/resolved-conf.h \ + src/resolve/resolved-resolv-conf.c \ + src/resolve/resolved-resolv-conf.h \ + src/resolve/resolved-bus.c \ + src/resolve/resolved-bus.h \ + src/resolve/resolved-link.h \ + src/resolve/resolved-link.c \ + src/resolve/resolved-link-bus.c \ + src/resolve/resolved-link-bus.h \ + src/resolve/resolved-llmnr.h \ + src/resolve/resolved-llmnr.c \ + src/resolve/resolved-mdns.h \ + src/resolve/resolved-mdns.c \ + src/resolve/resolved-def.h \ + src/resolve/resolved-dns-rr.h \ + src/resolve/resolved-dns-rr.c \ + src/resolve/resolved-dns-question.h \ + src/resolve/resolved-dns-question.c \ + src/resolve/resolved-dns-answer.h \ + src/resolve/resolved-dns-answer.c \ + src/resolve/resolved-dns-packet.h \ + src/resolve/resolved-dns-packet.c \ + src/resolve/resolved-dns-query.h \ + src/resolve/resolved-dns-query.c \ + src/resolve/resolved-dns-synthesize.h \ + src/resolve/resolved-dns-synthesize.c \ + src/resolve/resolved-dns-transaction.h \ + src/resolve/resolved-dns-transaction.c \ + src/resolve/resolved-dns-scope.h \ + src/resolve/resolved-dns-scope.c \ + src/resolve/resolved-dns-server.h \ + src/resolve/resolved-dns-server.c \ + src/resolve/resolved-dns-search-domain.h \ + src/resolve/resolved-dns-search-domain.c \ + src/resolve/resolved-dns-cache.h \ + src/resolve/resolved-dns-cache.c \ + src/resolve/resolved-dns-zone.h \ + src/resolve/resolved-dns-zone.c \ + src/resolve/resolved-dns-stream.h \ + src/resolve/resolved-dns-stream.c \ + src/resolve/resolved-dns-dnssec.h \ + src/resolve/resolved-dns-dnssec.c \ + src/resolve/resolved-dns-trust-anchor.h \ + src/resolve/resolved-dns-trust-anchor.c \ + src/resolve/resolved-etc-hosts.h \ + src/resolve/resolved-etc-hosts.c \ + src/resolve/dns-type.c \ + src/resolve/dns-type.h + +nodist_systemd_resolved_SOURCES = \ + src/resolve/dns_type-from-name.h \ + src/resolve/dns_type-to-name.h \ + src/resolve/resolved-gperf.c + +systemd_resolved_LDADD = \ + libsystemd-network.la \ + libshared.la + +rootlibexec_PROGRAMS += \ + systemd-resolved + +nodist_systemunit_DATA += \ + units/systemd-resolved.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.resolve1.busname + +dist_dbuspolicy_DATA += \ + src/resolve/org.freedesktop.resolve1.conf + +dist_dbussystemservice_DATA += \ + src/resolve/org.freedesktop.resolve1.service + +SYSTEM_UNIT_ALIASES += \ + systemd-resolved.service dbus-org.freedesktop.resolve1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.resolve1.busname + +GENERAL_ALIASES += \ + $(systemunitdir)/systemd-resolved.service $(pkgsysconfdir)/system/multi-user.target.wants/systemd-resolved.service + +nodist_pkgsysconf_DATA += \ + src/resolve/resolved.conf + +libnss_resolve_la_SOURCES = \ + src/nss-resolve/nss-resolve.sym \ + src/nss-resolve/nss-resolve.c + +libnss_resolve_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -module \ + -export-dynamic \ + -avoid-version \ + -shared \ + -shrext .so.2 \ + -Wl,--version-script=$(top_srcdir)/src/nss-resolve/nss-resolve.sym + +libnss_resolve_la_LIBADD = \ + libsystemd-internal.la \ + -ldl + +lib_LTLIBRARIES += \ + libnss_resolve.la + +systemd_resolve_SOURCES = \ + src/resolve/resolve-tool.c \ + src/resolve/resolved-dns-dnssec.c \ + src/resolve/resolved-dns-dnssec.h \ + src/resolve/resolved-dns-packet.c \ + src/resolve/resolved-dns-packet.h \ + src/resolve/resolved-dns-rr.c \ + src/resolve/resolved-dns-rr.h \ + src/resolve/resolved-dns-answer.c \ + src/resolve/resolved-dns-answer.h \ + src/resolve/resolved-dns-question.c \ + src/resolve/resolved-dns-question.h \ + src/resolve/dns-type.c \ + src/resolve/dns-type.h + +nodist_systemd_resolve_SOURCES = \ + src/resolve/dns_type-from-name.h \ + src/resolve/dns_type-to-name.h + +systemd_resolve_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + systemd-resolve + +tests += \ + test-dns-domain \ + test-dnssec + +manual_tests += \ + test-dnssec-complex + +test_dnssec_SOURCES = \ + src/resolve/test-dnssec.c \ + src/resolve/resolved-dns-packet.c \ + src/resolve/resolved-dns-packet.h \ + src/resolve/resolved-dns-rr.c \ + src/resolve/resolved-dns-rr.h \ + src/resolve/resolved-dns-answer.c \ + src/resolve/resolved-dns-answer.h \ + src/resolve/resolved-dns-question.c \ + src/resolve/resolved-dns-question.h \ + src/resolve/resolved-dns-dnssec.c \ + src/resolve/resolved-dns-dnssec.h \ + src/resolve/dns-type.c \ + src/resolve/dns-type.h + +test_dnssec_LDADD = \ + libshared.la + +test_dnssec_complex_SOURCES = \ + src/resolve/test-dnssec-complex.c \ + src/resolve/dns-type.c \ + src/resolve/dns-type.h + +test_dnssec_complex_LDADD = \ + libshared.la + +endif + +gperf_txt_sources += \ + src/resolve/dns_type-list.txt + +gperf_gperf_sources += \ + src/resolve/resolved-gperf.gperf + +EXTRA_DIST += \ + units/systemd-resolved.service.m4.in \ + src/resolve/resolved.conf.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/resolve/src/RFCs b/src/resolve/src/RFCs new file mode 100644 index 0000000000..22004a00cd --- /dev/null +++ b/src/resolve/src/RFCs @@ -0,0 +1,59 @@ +Y = Comprehensively Implemented, to the point appropriate for resolved +D = Comprehensively Implemented, by a dependency of resolved +! = Missing and something we might want to implement +~ = Needs no explicit support or doesn't apply +? = Is this relevant today? + = We are working on this + +Y https://tools.ietf.org/html/rfc1034 → DOMAIN NAMES - CONCEPTS AND FACILITIES +Y https://tools.ietf.org/html/rfc1035 → DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION +? https://tools.ietf.org/html/rfc1101 → DNS Encoding of Network Names and Other Types +Y https://tools.ietf.org/html/rfc1123 → Requirements for Internet Hosts -- Application and Support +~ https://tools.ietf.org/html/rfc1464 → Using the Domain Name System To Store Arbitrary String Attributes +Y https://tools.ietf.org/html/rfc1536 → Common DNS Implementation Errors and Suggested Fixes +Y https://tools.ietf.org/html/rfc1876 → A Means for Expressing Location Information in the Domain Name System +Y https://tools.ietf.org/html/rfc2181 → Clarifications to the DNS Specification +Y https://tools.ietf.org/html/rfc2308 → Negative Caching of DNS Queries (DNS NCACHE) +Y https://tools.ietf.org/html/rfc2782 → A DNS RR for specifying the location of services (DNS SRV) +D https://tools.ietf.org/html/rfc3492 → Punycode: A Bootstring encoding of Unicode for Internationalized Domain Names in Applications (IDNA) +Y https://tools.ietf.org/html/rfc3596 → DNS Extensions to Support IP Version 6 +Y https://tools.ietf.org/html/rfc3597 → Handling of Unknown DNS Resource Record (RR) Types +Y https://tools.ietf.org/html/rfc4033 → DNS Security Introduction and Requirements +Y https://tools.ietf.org/html/rfc4034 → Resource Records for the DNS Security Extensions +Y https://tools.ietf.org/html/rfc4035 → Protocol Modifications for the DNS Security Extensions +! https://tools.ietf.org/html/rfc4183 → A Suggested Scheme for DNS Resolution of Networks and Gateways +Y https://tools.ietf.org/html/rfc4255 → Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints +Y https://tools.ietf.org/html/rfc4343 → Domain Name System (DNS) Case Insensitivity Clarification +~ https://tools.ietf.org/html/rfc4470 → Minimally Covering NSEC Records and DNSSEC On-line Signing +Y https://tools.ietf.org/html/rfc4501 → Domain Name System Uniform Resource Identifiers +Y https://tools.ietf.org/html/rfc4509 → Use of SHA-256 in DNSSEC Delegation Signer (DS) Resource Records (RRs) +~ https://tools.ietf.org/html/rfc4592 → The Role of Wildcards in the Domain Name System +~ https://tools.ietf.org/html/rfc4697 → Observed DNS Resolution Misbehavior +Y https://tools.ietf.org/html/rfc4795 → Link-Local Multicast Name Resolution (LLMNR) +Y https://tools.ietf.org/html/rfc5011 → Automated Updates of DNS Security (DNSSEC) Trust Anchors +Y https://tools.ietf.org/html/rfc5155 → DNS Security (DNSSEC) Hashed Authenticated Denial of Existence +Y https://tools.ietf.org/html/rfc5452 → Measures for Making DNS More Resilient against Forged Answers +Y https://tools.ietf.org/html/rfc5702 → Use of SHA-2 Algorithms with RSA in DNSKEY and RRSIG Resource Records for DNSSEC +Y https://tools.ietf.org/html/rfc5890 → Internationalized Domain Names for Applications (IDNA): Definitions and Document Framework +Y https://tools.ietf.org/html/rfc5891 → Internationalized Domain Names in Applications (IDNA): Protocol +Y https://tools.ietf.org/html/rfc5966 → DNS Transport over TCP - Implementation Requirements +Y https://tools.ietf.org/html/rfc6303 → Locally Served DNS Zones +Y https://tools.ietf.org/html/rfc6604 → xNAME RCODE and Status Bits Clarification +Y https://tools.ietf.org/html/rfc6605 → Elliptic Curve Digital Signature Algorithm (DSA) for DNSSEC + https://tools.ietf.org/html/rfc6672 → DNAME Redirection in the DNS +! https://tools.ietf.org/html/rfc6731 → Improved Recursive DNS Server Selection for Multi-Interfaced Nodes +Y https://tools.ietf.org/html/rfc6761 → Special-Use Domain Names + https://tools.ietf.org/html/rfc6762 → Multicast DNS + https://tools.ietf.org/html/rfc6763 → DNS-Based Service Discovery +~ https://tools.ietf.org/html/rfc6781 → DNSSEC Operational Practices, Version 2 +Y https://tools.ietf.org/html/rfc6840 → Clarifications and Implementation Notes for DNS Security (DNSSEC) +Y https://tools.ietf.org/html/rfc6891 → Extension Mechanisms for DNS (EDNS(0)) +Y https://tools.ietf.org/html/rfc6944 → Applicability Statement: DNS Security (DNSSEC) DNSKEY Algorithm Implementation Status +Y https://tools.ietf.org/html/rfc6975 → Signaling Cryptographic Algorithm Understanding in DNS Security Extensions (DNSSEC) +Y https://tools.ietf.org/html/rfc7129 → Authenticated Denial of Existence in the DNS +Y https://tools.ietf.org/html/rfc7646 → Definition and Use of DNSSEC Negative Trust Anchors +~ https://tools.ietf.org/html/rfc7719 → DNS Terminology + +Also relevant: + + https://www.iab.org/documents/correspondence-reports-documents/2013-2/iab-statement-dotless-domains-considered-harmful/ diff --git a/src/resolve/src/dns-type.c b/src/resolve/src/dns-type.c new file mode 100644 index 0000000000..b2f479cae5 --- /dev/null +++ b/src/resolve/src/dns-type.c @@ -0,0 +1,306 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "dns-type.h" +#include "parse-util.h" +#include "string-util.h" + +typedef const struct { + uint16_t type; + const char *name; +} dns_type; + +static const struct dns_type_name * +lookup_dns_type (register const char *str, register unsigned int len); + +#include "dns_type-from-name.h" +#include "dns_type-to-name.h" + +int dns_type_from_string(const char *s) { + const struct dns_type_name *sc; + + assert(s); + + sc = lookup_dns_type(s, strlen(s)); + if (sc) + return sc->id; + + s = startswith_no_case(s, "TYPE"); + if (s) { + unsigned x; + + if (safe_atou(s, &x) >= 0 && + x <= UINT16_MAX) + return (int) x; + } + + return _DNS_TYPE_INVALID; +} + +bool dns_type_is_pseudo(uint16_t type) { + + /* Checks whether the specified type is a "pseudo-type". What + * a "pseudo-type" precisely is, is defined only very weakly, + * but apparently entails all RR types that are not actually + * stored as RRs on the server and should hence also not be + * cached. We use this list primarily to validate NSEC type + * bitfields, and to verify what to cache. */ + + return IN_SET(type, + 0, /* A Pseudo RR type, according to RFC 2931 */ + DNS_TYPE_ANY, + DNS_TYPE_AXFR, + DNS_TYPE_IXFR, + DNS_TYPE_OPT, + DNS_TYPE_TSIG, + DNS_TYPE_TKEY + ); +} + +bool dns_class_is_pseudo(uint16_t class) { + return class == DNS_TYPE_ANY; +} + +bool dns_type_is_valid_query(uint16_t type) { + + /* The types valid as questions in packets */ + + return !IN_SET(type, + 0, + DNS_TYPE_OPT, + DNS_TYPE_TSIG, + DNS_TYPE_TKEY, + + /* RRSIG are technically valid as questions, but we refuse doing explicit queries for them, as + * they aren't really payload, but signatures for payload, and cannot be validated on their + * own. After all they are the signatures, and have no signatures of their own validating + * them. */ + DNS_TYPE_RRSIG); +} + +bool dns_type_is_valid_rr(uint16_t type) { + + /* The types valid as RR in packets (but not necessarily + * stored on servers). */ + + return !IN_SET(type, + DNS_TYPE_ANY, + DNS_TYPE_AXFR, + DNS_TYPE_IXFR); +} + +bool dns_class_is_valid_rr(uint16_t class) { + return class != DNS_CLASS_ANY; +} + +bool dns_type_may_redirect(uint16_t type) { + /* The following record types should never be redirected using + * CNAME/DNAME RRs. See + * . */ + + if (dns_type_is_pseudo(type)) + return false; + + return !IN_SET(type, + DNS_TYPE_CNAME, + DNS_TYPE_DNAME, + DNS_TYPE_NSEC3, + DNS_TYPE_NSEC, + DNS_TYPE_RRSIG, + DNS_TYPE_NXT, + DNS_TYPE_SIG, + DNS_TYPE_KEY); +} + +bool dns_type_may_wildcard(uint16_t type) { + + /* The following records may not be expanded from wildcard RRsets */ + + if (dns_type_is_pseudo(type)) + return false; + + return !IN_SET(type, + DNS_TYPE_NSEC3, + DNS_TYPE_SOA, + + /* Prohibited by https://tools.ietf.org/html/rfc4592#section-4.4 */ + DNS_TYPE_DNAME); +} + +bool dns_type_apex_only(uint16_t type) { + + /* Returns true for all RR types that may only appear signed in a zone apex */ + + return IN_SET(type, + DNS_TYPE_SOA, + DNS_TYPE_NS, /* this one can appear elsewhere, too, but not signed */ + DNS_TYPE_DNSKEY, + DNS_TYPE_NSEC3PARAM); +} + +bool dns_type_is_dnssec(uint16_t type) { + return IN_SET(type, + DNS_TYPE_DS, + DNS_TYPE_DNSKEY, + DNS_TYPE_RRSIG, + DNS_TYPE_NSEC, + DNS_TYPE_NSEC3, + DNS_TYPE_NSEC3PARAM); +} + +bool dns_type_is_obsolete(uint16_t type) { + return IN_SET(type, + /* Obsoleted by RFC 973 */ + DNS_TYPE_MD, + DNS_TYPE_MF, + DNS_TYPE_MAILA, + + /* Kinda obsoleted by RFC 2505 */ + DNS_TYPE_MB, + DNS_TYPE_MG, + DNS_TYPE_MR, + DNS_TYPE_MINFO, + DNS_TYPE_MAILB, + + /* RFC1127 kinda obsoleted this by recommending against its use */ + DNS_TYPE_WKS, + + /* Declared historical by RFC 6563 */ + DNS_TYPE_A6, + + /* Obsoleted by DNSSEC-bis */ + DNS_TYPE_NXT, + + /* RFC 1035 removed support for concepts that needed this from RFC 883 */ + DNS_TYPE_NULL); +} + +int dns_type_to_af(uint16_t t) { + switch (t) { + + case DNS_TYPE_A: + return AF_INET; + + case DNS_TYPE_AAAA: + return AF_INET6; + + case DNS_TYPE_ANY: + return AF_UNSPEC; + + default: + return -EINVAL; + } +} + +const char *dns_class_to_string(uint16_t class) { + + switch (class) { + + case DNS_CLASS_IN: + return "IN"; + + case DNS_CLASS_ANY: + return "ANY"; + } + + return NULL; +} + +int dns_class_from_string(const char *s) { + + if (!s) + return _DNS_CLASS_INVALID; + + if (strcaseeq(s, "IN")) + return DNS_CLASS_IN; + else if (strcaseeq(s, "ANY")) + return DNS_CLASS_ANY; + + return _DNS_CLASS_INVALID; +} + +const char* tlsa_cert_usage_to_string(uint8_t cert_usage) { + + switch (cert_usage) { + + case 0: + return "CA constraint"; + + case 1: + return "Service certificate constraint"; + + case 2: + return "Trust anchor assertion"; + + case 3: + return "Domain-issued certificate"; + + case 4 ... 254: + return "Unassigned"; + + case 255: + return "Private use"; + } + + return NULL; /* clang cannot count that we covered everything */ +} + +const char* tlsa_selector_to_string(uint8_t selector) { + switch (selector) { + + case 0: + return "Full Certificate"; + + case 1: + return "SubjectPublicKeyInfo"; + + case 2 ... 254: + return "Unassigned"; + + case 255: + return "Private use"; + } + + return NULL; +} + +const char* tlsa_matching_type_to_string(uint8_t selector) { + + switch (selector) { + + case 0: + return "No hash used"; + + case 1: + return "SHA-256"; + + case 2: + return "SHA-512"; + + case 3 ... 254: + return "Unassigned"; + + case 255: + return "Private use"; + } + + return NULL; +} diff --git a/src/resolve/src/dns-type.h b/src/resolve/src/dns-type.h new file mode 100644 index 0000000000..a6c1630021 --- /dev/null +++ b/src/resolve/src/dns-type.h @@ -0,0 +1,154 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#pragma once + +#include "macro.h" + +/* DNS record types, taken from + * http://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml. + */ +enum { + /* Normal records */ + DNS_TYPE_A = 0x01, + DNS_TYPE_NS, + DNS_TYPE_MD, + DNS_TYPE_MF, + DNS_TYPE_CNAME, + DNS_TYPE_SOA, + DNS_TYPE_MB, + DNS_TYPE_MG, + DNS_TYPE_MR, + DNS_TYPE_NULL, + DNS_TYPE_WKS, + DNS_TYPE_PTR, + DNS_TYPE_HINFO, + DNS_TYPE_MINFO, + DNS_TYPE_MX, + DNS_TYPE_TXT, + DNS_TYPE_RP, + DNS_TYPE_AFSDB, + DNS_TYPE_X25, + DNS_TYPE_ISDN, + DNS_TYPE_RT, + DNS_TYPE_NSAP, + DNS_TYPE_NSAP_PTR, + DNS_TYPE_SIG, + DNS_TYPE_KEY, + DNS_TYPE_PX, + DNS_TYPE_GPOS, + DNS_TYPE_AAAA, + DNS_TYPE_LOC, + DNS_TYPE_NXT, + DNS_TYPE_EID, + DNS_TYPE_NIMLOC, + DNS_TYPE_SRV, + DNS_TYPE_ATMA, + DNS_TYPE_NAPTR, + DNS_TYPE_KX, + DNS_TYPE_CERT, + DNS_TYPE_A6, + DNS_TYPE_DNAME, + DNS_TYPE_SINK, + DNS_TYPE_OPT, /* EDNS0 option */ + DNS_TYPE_APL, + DNS_TYPE_DS, + DNS_TYPE_SSHFP, + DNS_TYPE_IPSECKEY, + DNS_TYPE_RRSIG, + DNS_TYPE_NSEC, + DNS_TYPE_DNSKEY, + DNS_TYPE_DHCID, + DNS_TYPE_NSEC3, + DNS_TYPE_NSEC3PARAM, + DNS_TYPE_TLSA, + + DNS_TYPE_HIP = 0x37, + DNS_TYPE_NINFO, + DNS_TYPE_RKEY, + DNS_TYPE_TALINK, + DNS_TYPE_CDS, + DNS_TYPE_CDNSKEY, + DNS_TYPE_OPENPGPKEY, + + DNS_TYPE_SPF = 0x63, + DNS_TYPE_NID, + DNS_TYPE_L32, + DNS_TYPE_L64, + DNS_TYPE_LP, + DNS_TYPE_EUI48, + DNS_TYPE_EUI64, + + DNS_TYPE_TKEY = 0xF9, + DNS_TYPE_TSIG, + DNS_TYPE_IXFR, + DNS_TYPE_AXFR, + DNS_TYPE_MAILB, + DNS_TYPE_MAILA, + DNS_TYPE_ANY, + DNS_TYPE_URI, + DNS_TYPE_CAA, + DNS_TYPE_TA = 0x8000, + DNS_TYPE_DLV, + + _DNS_TYPE_MAX, + _DNS_TYPE_INVALID = -1 +}; + +assert_cc(DNS_TYPE_SSHFP == 44); +assert_cc(DNS_TYPE_TLSA == 52); +assert_cc(DNS_TYPE_ANY == 255); + +/* DNS record classes, see RFC 1035 */ +enum { + DNS_CLASS_IN = 0x01, + DNS_CLASS_ANY = 0xFF, + + _DNS_CLASS_MAX, + _DNS_CLASS_INVALID = -1 +}; + +bool dns_type_is_pseudo(uint16_t type); +bool dns_type_is_valid_query(uint16_t type); +bool dns_type_is_valid_rr(uint16_t type); +bool dns_type_may_redirect(uint16_t type); +bool dns_type_is_dnssec(uint16_t type); +bool dns_type_is_obsolete(uint16_t type); +bool dns_type_may_wildcard(uint16_t type); +bool dns_type_apex_only(uint16_t type); +int dns_type_to_af(uint16_t t); + +bool dns_class_is_pseudo(uint16_t class); +bool dns_class_is_valid_rr(uint16_t class); + +/* TYPE?? follows http://tools.ietf.org/html/rfc3597#section-5 */ +const char *dns_type_to_string(int type); +int dns_type_from_string(const char *s); + +const char *dns_class_to_string(uint16_t type); +int dns_class_from_string(const char *name); + +/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.2 */ +const char *tlsa_cert_usage_to_string(uint8_t cert_usage); + +/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.3 */ +const char *tlsa_selector_to_string(uint8_t selector); + +/* https://tools.ietf.org/html/draft-ietf-dane-protocol-23#section-7.4 */ +const char *tlsa_matching_type_to_string(uint8_t selector); diff --git a/src/resolve/src/org.freedesktop.resolve1.conf b/src/resolve/src/org.freedesktop.resolve1.conf new file mode 100644 index 0000000000..25b09774e5 --- /dev/null +++ b/src/resolve/src/org.freedesktop.resolve1.conf @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/resolve/src/org.freedesktop.resolve1.service b/src/resolve/src/org.freedesktop.resolve1.service new file mode 100644 index 0000000000..7ac5c323f0 --- /dev/null +++ b/src/resolve/src/org.freedesktop.resolve1.service @@ -0,0 +1,12 @@ +# 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. + +[D-BUS Service] +Name=org.freedesktop.resolve1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.resolve1.service diff --git a/src/resolve/src/resolve-tool.c b/src/resolve/src/resolve-tool.c new file mode 100644 index 0000000000..3f1b6e32f1 --- /dev/null +++ b/src/resolve/src/resolve-tool.c @@ -0,0 +1,1272 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include + +#include + +#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 "parse-util.h" +#include "resolved-def.h" +#include "resolved-dns-packet.h" +#include "terminal-util.h" + +#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) + +static int arg_family = AF_UNSPEC; +static int arg_ifindex = 0; +static uint16_t arg_type = 0; +static uint16_t arg_class = 0; +static bool arg_legend = true; +static uint64_t arg_flags = 0; + +static enum { + MODE_RESOLVE_HOST, + MODE_RESOLVE_RECORD, + MODE_RESOLVE_SERVICE, + MODE_STATISTICS, + MODE_RESET_STATISTICS, +} arg_mode = MODE_RESOLVE_HOST; + +static void print_source(uint64_t flags, usec_t rtt) { + char rtt_str[FORMAT_TIMESTAMP_MAX]; + + if (!arg_legend) + return; + + if (flags == 0) + return; + + fputs("\n-- Information acquired via", stdout); + + if (flags != 0) + printf(" protocol%s%s%s%s%s", + flags & SD_RESOLVED_DNS ? " DNS" :"", + flags & SD_RESOLVED_LLMNR_IPV4 ? " LLMNR/IPv4" : "", + flags & SD_RESOLVED_LLMNR_IPV6 ? " LLMNR/IPv6" : "", + flags & SD_RESOLVED_MDNS_IPV4 ? "mDNS/IPv4" : "", + flags & SD_RESOLVED_MDNS_IPV6 ? "mDNS/IPv6" : ""); + + assert_se(format_timespan(rtt_str, sizeof(rtt_str), rtt, 100)); + + printf(" in %s", rtt_str); + + fputc('.', stdout); + fputc('\n', stdout); + + printf("-- Data is authenticated: %s\n", yes_no(flags & SD_RESOLVED_AUTHENTICATED)); +} + +static int resolve_host(sd_bus *bus, const char *name) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *canonical = NULL; + char ifname[IF_NAMESIZE] = ""; + unsigned c = 0; + int r; + uint64_t flags; + usec_t ts; + + assert(name); + + if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) + return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); + + log_debug("Resolving %s (family %s, interface %s).", name, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveHostname"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "isit", arg_ifindex, name, arg_family, arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) + return log_error_errno(r, "%s: resolve call failed: %s", name, bus_error_message(&error, r)); + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + _cleanup_free_ char *pretty = NULL; + int ifindex, family; + const void *a; + size_t sz; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "ii", &ifindex, &family); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (!IN_SET(family, AF_INET, AF_INET6)) { + log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); + continue; + } + + if (sz != FAMILY_ADDRESS_SIZE(family)) { + log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); + return -EINVAL; + } + + ifname[0] = 0; + 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); + if (r < 0) + return log_error_errno(r, "Failed to print address for %s: %m", name); + + printf("%*s%s %s%s%s\n", + (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", + pretty, + isempty(ifname) ? "" : "%", ifname); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "st", &canonical, &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (!streq(name, canonical)) + printf("%*s%s (%s)\n", + (int) strlen(name), c == 0 ? name : "", c == 0 ? ":" : " ", + canonical); + + if (c == 0) { + log_error("%s: no addresses found", name); + return -ESRCH; + } + + print_source(flags, ts); + + return 0; +} + +static int resolve_address(sd_bus *bus, int family, const union in_addr_union *address, int ifindex) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *pretty = NULL; + char ifname[IF_NAMESIZE] = ""; + uint64_t flags; + unsigned c = 0; + usec_t ts; + int r; + + assert(bus); + assert(IN_SET(family, AF_INET, AF_INET6)); + assert(address); + + if (ifindex <= 0) + ifindex = arg_ifindex; + + r = in_addr_to_string(family, address, &pretty); + if (r < 0) + return log_oom(); + + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + log_debug("Resolving %s%s%s.", pretty, isempty(ifname) ? "" : "%", ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveAddress"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "ii", ifindex, family); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_array(req, 'y', address, FAMILY_ADDRESS_SIZE(family)); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "t", arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + log_error("%s: resolve call failed: %s", pretty, bus_error_message(&error, r)); + return r; + } + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(is)"); + if (r < 0) + return bus_log_create_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "is")) > 0) { + const char *n; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "is", &ifindex, &n); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return r; + + ifname[0] = 0; + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + printf("%*s%*s%*s%s %s\n", + (int) strlen(pretty), c == 0 ? pretty : "", + isempty(ifname) ? 0 : 1, c > 0 || isempty(ifname) ? "" : "%", + (int) strlen(ifname), c == 0 ? ifname : "", + c == 0 ? ":" : " ", + n); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "t", &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (c == 0) { + log_error("%s: no names found", pretty); + return -ESRCH; + } + + print_source(flags, ts); + + 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 resolve_record(sd_bus *bus, const char *name, uint16_t class, uint16_t type) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char ifname[IF_NAMESIZE] = ""; + unsigned n = 0; + uint64_t flags; + int r; + usec_t ts; + + assert(name); + + if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) + return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); + + log_debug("Resolving %s %s %s (interface %s).", name, dns_class_to_string(class), dns_type_to_string(type), isempty(ifname) ? "*" : ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveRecord"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "isqqt", arg_ifindex, name, class, type, arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) { + log_error("%s: resolve call failed: %s", name, bus_error_message(&error, r)); + return r; + } + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(iqqay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iqqay")) > 0) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + const char *s; + uint16_t c, t; + int ifindex; + const void *d; + size_t l; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "iqq", &ifindex, &c, &t); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &d, &l); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = dns_packet_new(&p, DNS_PROTOCOL_DNS, 0); + if (r < 0) + return log_oom(); + + p->refuse_compression = true; + + r = dns_packet_append_blob(p, d, l, NULL); + if (r < 0) + return log_oom(); + + r = dns_packet_read_rr(p, &rr, NULL, NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse RR: %m"); + + s = dns_resource_record_to_string(rr); + if (!s) + return log_oom(); + + ifname[0] = 0; + if (ifindex > 0 && !if_indextoname(ifindex, ifname)) + log_warning_errno(errno, "Failed to resolve interface name for index %i: %m", ifindex); + + printf("%s%s%s\n", s, isempty(ifname) ? "" : " # interface ", ifname); + n++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "t", &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (n == 0) { + log_error("%s: no records found", name); + return -ESRCH; + } + + print_source(flags, ts); + + return 0; +} + +static int resolve_rfc4501(sd_bus *bus, const char *name) { + uint16_t type = 0, class = 0; + const char *p, *q, *n; + int r; + + assert(bus); + assert(name); + assert(startswith(name, "dns:")); + + /* Parse RFC 4501 dns: URIs */ + + p = name + 4; + + if (p[0] == '/') { + const char *e; + + if (p[1] != '/') + goto invalid; + + e = strchr(p + 2, '/'); + if (!e) + goto invalid; + + if (e != p + 2) + log_warning("DNS authority specification not supported; ignoring specified authority."); + + p = e + 1; + } + + q = strchr(p, '?'); + if (q) { + n = strndupa(p, q - p); + q++; + + for (;;) { + const char *f; + + f = startswith_no_case(q, "class="); + if (f) { + _cleanup_free_ char *t = NULL; + const char *e; + + if (class != 0) { + log_error("DNS class specified twice."); + return -EINVAL; + } + + e = strchrnul(f, ';'); + t = strndup(f, e - f); + if (!t) + return log_oom(); + + r = dns_class_from_string(t); + if (r < 0) { + log_error("Unknown DNS class %s.", t); + return -EINVAL; + } + + class = r; + + if (*e == ';') { + q = e + 1; + continue; + } + + break; + } + + f = startswith_no_case(q, "type="); + if (f) { + _cleanup_free_ char *t = NULL; + const char *e; + + if (type != 0) { + log_error("DNS type specified twice."); + return -EINVAL; + } + + e = strchrnul(f, ';'); + t = strndup(f, e - f); + if (!t) + return log_oom(); + + r = dns_type_from_string(t); + if (r < 0) { + log_error("Unknown DNS type %s.", t); + return -EINVAL; + } + + type = r; + + if (*e == ';') { + q = e + 1; + continue; + } + + break; + } + + goto invalid; + } + } else + n = p; + + if (type == 0) + type = arg_type; + if (type == 0) + type = DNS_TYPE_A; + + if (class == 0) + class = arg_class; + if (class == 0) + class = DNS_CLASS_IN; + + return resolve_record(bus, n, class, type); + +invalid: + log_error("Invalid DNS URI: %s", name); + return -EINVAL; +} + +static int resolve_service(sd_bus *bus, const char *name, const char *type, const char *domain) { + const char *canonical_name, *canonical_type, *canonical_domain; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char ifname[IF_NAMESIZE] = ""; + size_t indent, sz; + uint64_t flags; + const char *p; + unsigned c; + usec_t ts; + int r; + + assert(bus); + assert(domain); + + if (isempty(name)) + name = NULL; + if (isempty(type)) + type = NULL; + + if (arg_ifindex > 0 && !if_indextoname(arg_ifindex, ifname)) + return log_error_errno(errno, "Failed to resolve interface name for index %i: %m", arg_ifindex); + + if (name) + log_debug("Resolving service \"%s\" of type %s in %s (family %s, interface %s).", name, type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + else if (type) + log_debug("Resolving service type %s of %s (family %s, interface %s).", type, domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + else + log_debug("Resolving service type %s (family %s, interface %s).", domain, af_to_name(arg_family) ?: "*", isempty(ifname) ? "*" : ifname); + + r = sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveService"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(req, "isssit", arg_ifindex, name, type, domain, arg_family, arg_flags); + if (r < 0) + return bus_log_create_error(r); + + ts = now(CLOCK_MONOTONIC); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + if (r < 0) + return log_error_errno(r, "Resolve call failed: %s", bus_error_message(&error, r)); + + ts = now(CLOCK_MONOTONIC) - ts; + + r = sd_bus_message_enter_container(reply, 'a', "(qqqsa(iiay)s)"); + if (r < 0) + return bus_log_parse_error(r); + + indent = + (name ? strlen(name) + 1 : 0) + + (type ? strlen(type) + 1 : 0) + + strlen(domain) + 2; + + c = 0; + while ((r = sd_bus_message_enter_container(reply, 'r', "qqqsa(iiay)s")) > 0) { + uint16_t priority, weight, port; + const char *hostname, *canonical; + + r = sd_bus_message_read(reply, "qqqs", &priority, &weight, &port, &hostname); + if (r < 0) + return bus_log_parse_error(r); + + if (name) + printf("%*s%s", (int) strlen(name), c == 0 ? name : "", c == 0 ? "/" : " "); + if (type) + printf("%*s%s", (int) strlen(type), c == 0 ? type : "", c == 0 ? "/" : " "); + + printf("%*s%s %s:%u [priority=%u, weight=%u]\n", + (int) strlen(domain), c == 0 ? domain : "", + c == 0 ? ":" : " ", + hostname, port, + priority, weight); + + r = sd_bus_message_enter_container(reply, 'a', "(iiay)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, 'r', "iiay")) > 0) { + _cleanup_free_ char *pretty = NULL; + int ifindex, family; + const void *a; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(reply, "ii", &ifindex, &family); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_array(reply, 'y', &a, &sz); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + if (!IN_SET(family, AF_INET, AF_INET6)) { + log_debug("%s: skipping entry with family %d (%s)", name, family, af_to_name(family) ?: "unknown"); + continue; + } + + if (sz != FAMILY_ADDRESS_SIZE(family)) { + log_error("%s: systemd-resolved returned address of invalid size %zu for family %s", name, sz, af_to_name(family) ?: "unknown"); + return -EINVAL; + } + + ifname[0] = 0; + 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); + if (r < 0) + return log_error_errno(r, "Failed to print address for %s: %m", name); + + printf("%*s%s%s%s\n", (int) indent, "", pretty, isempty(ifname) ? "" : "%s", ifname); + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "s", &canonical); + if (r < 0) + return bus_log_parse_error(r); + + if (!streq(hostname, canonical)) + printf("%*s(%s)\n", (int) indent, "", canonical); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, 'a', "ay"); + if (r < 0) + return bus_log_parse_error(r); + + c = 0; + while ((r = sd_bus_message_read_array(reply, 'y', (const void**) &p, &sz)) > 0) { + _cleanup_free_ char *escaped = NULL; + + escaped = cescape_length(p, sz); + if (!escaped) + return log_oom(); + + printf("%*s%s\n", (int) indent, "", escaped); + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(reply, "ssst", &canonical_name, &canonical_type, &canonical_domain, &flags); + if (r < 0) + return bus_log_parse_error(r); + + if (isempty(canonical_name)) + canonical_name = NULL; + if (isempty(canonical_type)) + canonical_type = NULL; + + if (!streq_ptr(name, canonical_name) || + !streq_ptr(type, canonical_type) || + !streq_ptr(domain, canonical_domain)) { + + printf("%*s(", (int) indent, ""); + + if (canonical_name) + printf("%s/", canonical_name); + if (canonical_type) + printf("%s/", canonical_type); + + printf("%s)\n", canonical_domain); + } + + print_source(flags, ts); + + return 0; +} + +static int show_statistics(sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + uint64_t n_current_transactions, n_total_transactions, + cache_size, n_cache_hit, n_cache_miss, + n_dnssec_secure, n_dnssec_insecure, n_dnssec_bogus, n_dnssec_indeterminate; + int r, dnssec_supported; + + assert(bus); + + r = sd_bus_get_property_trivial(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "DNSSECSupported", + &error, + 'b', + &dnssec_supported); + if (r < 0) + return log_error_errno(r, "Failed to get DNSSEC supported state: %s", bus_error_message(&error, r)); + + printf("DNSSEC supported by current servers: %s%s%s\n\n", + ansi_highlight(), + yes_no(dnssec_supported), + ansi_normal()); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "TransactionStatistics", + &error, + &reply, + "(tt)"); + if (r < 0) + return log_error_errno(r, "Failed to get transaction statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(tt)", + &n_current_transactions, + &n_total_transactions); + if (r < 0) + return bus_log_parse_error(r); + + printf("%sTransactions%s\n" + "Current Transactions: %" PRIu64 "\n" + " Total Transactions: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + n_current_transactions, + n_total_transactions); + + reply = sd_bus_message_unref(reply); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "CacheStatistics", + &error, + &reply, + "(ttt)"); + if (r < 0) + return log_error_errno(r, "Failed to get cache statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(ttt)", + &cache_size, + &n_cache_hit, + &n_cache_miss); + if (r < 0) + return bus_log_parse_error(r); + + printf("\n%sCache%s\n" + " Current Cache Size: %" PRIu64 "\n" + " Cache Hits: %" PRIu64 "\n" + " Cache Misses: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + cache_size, + n_cache_hit, + n_cache_miss); + + reply = sd_bus_message_unref(reply); + + r = sd_bus_get_property(bus, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "DNSSECStatistics", + &error, + &reply, + "(tttt)"); + if (r < 0) + return log_error_errno(r, "Failed to get DNSSEC statistics: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "(tttt)", + &n_dnssec_secure, + &n_dnssec_insecure, + &n_dnssec_bogus, + &n_dnssec_indeterminate); + if (r < 0) + return bus_log_parse_error(r); + + printf("\n%sDNSSEC Verdicts%s\n" + " Secure: %" PRIu64 "\n" + " Insecure: %" PRIu64 "\n" + " Bogus: %" PRIu64 "\n" + " Indeterminate: %" PRIu64 "\n", + ansi_highlight(), + ansi_normal(), + n_dnssec_secure, + n_dnssec_insecure, + n_dnssec_bogus, + n_dnssec_indeterminate); + + return 0; +} + +static int reset_statistics(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", + "ResetStatistics", + &error, + NULL, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to reset statistics: %s", bus_error_message(&error, r)); + + return 0; +} + +static void help_protocol_types(void) { + if (arg_legend) + puts("Known protocol types:"); + puts("dns\nllmnr\nllmnr-ipv4\nllmnr-ipv6"); +} + +static void help_dns_types(void) { + int i; + const char *t; + + if (arg_legend) + puts("Known DNS RR types:"); + for (i = 0; i < _DNS_TYPE_MAX; i++) { + t = dns_type_to_string(i); + if (t) + puts(t); + } +} + +static void help_dns_classes(void) { + int i; + const char *t; + + if (arg_legend) + puts("Known DNS RR classes:"); + for (i = 0; i < _DNS_CLASS_MAX; i++) { + t = dns_class_to_string(i); + if (t) + puts(t); + } +} + +static void help(void) { + printf("%s [OPTIONS...] NAME...\n" + "%s [OPTIONS...] --service [[NAME] TYPE] DOMAIN\n\n" + "Resolve domain names, IPv4 and IPv6 addresses, DNS resource records, and services.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -4 Resolve IPv4 addresses\n" + " -6 Resolve IPv6 addresses\n" + " -i --interface=INTERFACE Look on interface\n" + " -p --protocol=PROTOCOL|help Look via protocol\n" + " -t --type=TYPE|help Query RR with DNS type\n" + " -c --class=CLASS|help Query RR with DNS class\n" + " --service Resolve service (SRV)\n" + " --service-address=BOOL Do [not] resolve address for services\n" + " --service-txt=BOOL Do [not] resolve TXT records for services\n" + " --cname=BOOL Do [not] follow CNAME redirects\n" + " --search=BOOL Do [not] use search domains\n" + " --legend=BOOL Do [not] print column headers and meta information\n" + " --statistics Show resolver statistics\n" + " --reset-statistics Reset resolver statistics\n" + , program_invocation_short_name, program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_LEGEND, + ARG_SERVICE, + ARG_CNAME, + ARG_SERVICE_ADDRESS, + ARG_SERVICE_TXT, + ARG_SEARCH, + ARG_STATISTICS, + ARG_RESET_STATISTICS, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "type", required_argument, NULL, 't' }, + { "class", required_argument, NULL, 'c' }, + { "legend", required_argument, NULL, ARG_LEGEND }, + { "interface", required_argument, NULL, 'i' }, + { "protocol", required_argument, NULL, 'p' }, + { "cname", required_argument, NULL, ARG_CNAME }, + { "service", no_argument, NULL, ARG_SERVICE }, + { "service-address", required_argument, NULL, ARG_SERVICE_ADDRESS }, + { "service-txt", required_argument, NULL, ARG_SERVICE_TXT }, + { "search", required_argument, NULL, ARG_SEARCH }, + { "statistics", no_argument, NULL, ARG_STATISTICS, }, + { "reset-statistics", no_argument, NULL, ARG_RESET_STATISTICS }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h46i:t:c:p:", options, NULL)) >= 0) + switch(c) { + + case 'h': + help(); + return 0; /* done */; + + case ARG_VERSION: + return version(); + + case '4': + arg_family = AF_INET; + break; + + case '6': + arg_family = AF_INET6; + break; + + case 'i': { + int ifi; + + if (parse_ifindex(optarg, &ifi) >= 0) + arg_ifindex = ifi; + else { + ifi = if_nametoindex(optarg); + if (ifi <= 0) + return log_error_errno(errno, "Unknown interface %s: %m", optarg); + + arg_ifindex = ifi; + } + + break; + } + + case 't': + if (streq(optarg, "help")) { + help_dns_types(); + return 0; + } + + r = dns_type_from_string(optarg); + if (r < 0) { + log_error("Failed to parse RR record type %s", optarg); + return r; + } + arg_type = (uint16_t) r; + assert((int) arg_type == r); + + arg_mode = MODE_RESOLVE_RECORD; + break; + + case 'c': + if (streq(optarg, "help")) { + help_dns_classes(); + return 0; + } + + r = dns_class_from_string(optarg); + if (r < 0) { + log_error("Failed to parse RR record class %s", optarg); + return r; + } + arg_class = (uint16_t) r; + assert((int) arg_class == r); + + break; + + case ARG_LEGEND: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --legend= argument"); + + arg_legend = r; + break; + + case 'p': + if (streq(optarg, "help")) { + help_protocol_types(); + return 0; + } else if (streq(optarg, "dns")) + arg_flags |= SD_RESOLVED_DNS; + else if (streq(optarg, "llmnr")) + arg_flags |= SD_RESOLVED_LLMNR; + else if (streq(optarg, "llmnr-ipv4")) + arg_flags |= SD_RESOLVED_LLMNR_IPV4; + else if (streq(optarg, "llmnr-ipv6")) + arg_flags |= SD_RESOLVED_LLMNR_IPV6; + else { + log_error("Unknown protocol specifier: %s", optarg); + return -EINVAL; + } + + break; + + case ARG_SERVICE: + arg_mode = MODE_RESOLVE_SERVICE; + break; + + case ARG_CNAME: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --cname= argument."); + if (r == 0) + arg_flags |= SD_RESOLVED_NO_CNAME; + else + arg_flags &= ~SD_RESOLVED_NO_CNAME; + break; + + case ARG_SERVICE_ADDRESS: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --service-address= argument."); + if (r == 0) + arg_flags |= SD_RESOLVED_NO_ADDRESS; + else + arg_flags &= ~SD_RESOLVED_NO_ADDRESS; + break; + + case ARG_SERVICE_TXT: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --service-txt= argument."); + if (r == 0) + arg_flags |= SD_RESOLVED_NO_TXT; + else + arg_flags &= ~SD_RESOLVED_NO_TXT; + break; + + case ARG_SEARCH: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --search argument."); + if (r == 0) + arg_flags |= SD_RESOLVED_NO_SEARCH; + else + arg_flags &= ~SD_RESOLVED_NO_SEARCH; + break; + + case ARG_STATISTICS: + arg_mode = MODE_STATISTICS; + break; + + case ARG_RESET_STATISTICS: + arg_mode = MODE_RESET_STATISTICS; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_type == 0 && arg_class != 0) { + log_error("--class= may only be used in conjunction with --type=."); + return -EINVAL; + } + + if (arg_type != 0 && arg_mode != MODE_RESOLVE_RECORD) { + log_error("--service and --type= may not be combined."); + return -EINVAL; + } + + if (arg_type != 0 && arg_class == 0) + arg_class = DNS_CLASS_IN; + + if (arg_class != 0 && arg_type == 0) + arg_type = DNS_TYPE_A; + + return 1 /* work to do */; +} + +int main(int argc, char **argv) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = sd_bus_open_system(&bus); + if (r < 0) { + log_error_errno(r, "sd_bus_open_system: %m"); + goto finish; + } + + switch (arg_mode) { + + case MODE_RESOLVE_HOST: + if (optind >= argc) { + log_error("No arguments passed."); + r = -EINVAL; + goto finish; + } + + while (argv[optind]) { + int family, ifindex, k; + union in_addr_union a; + + if (startswith(argv[optind], "dns:")) + k = resolve_rfc4501(bus, argv[optind]); + else { + k = parse_address(argv[optind], &family, &a, &ifindex); + if (k >= 0) + k = resolve_address(bus, family, &a, ifindex); + else + k = resolve_host(bus, argv[optind]); + } + + if (r == 0) + r = k; + + optind++; + } + break; + + case MODE_RESOLVE_RECORD: + if (optind >= argc) { + log_error("No arguments passed."); + r = -EINVAL; + goto finish; + } + + while (argv[optind]) { + int k; + + k = resolve_record(bus, argv[optind], arg_class, arg_type); + if (r == 0) + r = k; + + optind++; + } + break; + + case MODE_RESOLVE_SERVICE: + if (argc < optind + 1) { + log_error("Domain specification required."); + r = -EINVAL; + goto finish; + + } else if (argc == optind + 1) + r = resolve_service(bus, NULL, NULL, argv[optind]); + else if (argc == optind + 2) + r = resolve_service(bus, NULL, argv[optind], argv[optind+1]); + else if (argc == optind + 3) + r = resolve_service(bus, argv[optind], argv[optind+1], argv[optind+2]); + else { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; + } + + break; + + case MODE_STATISTICS: + if (argc > optind) { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; + } + + r = show_statistics(bus); + break; + + case MODE_RESET_STATISTICS: + if (argc > optind) { + log_error("Too many arguments."); + r = -EINVAL; + goto finish; + } + + r = reset_statistics(bus); + break; + } + +finish: + return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/resolve/src/resolved-bus.c b/src/resolve/src/resolved-bus.c new file mode 100644 index 0000000000..fc5e6beca0 --- /dev/null +++ b/src/resolve/src/resolved-bus.c @@ -0,0 +1,1565 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-util.h" +#include "dns-domain.h" +#include "resolved-bus.h" +#include "resolved-def.h" +#include "resolved-link-bus.h" + +static int reply_query_state(DnsQuery *q) { + + switch (q->state) { + + case DNS_TRANSACTION_NO_SERVERS: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_NAME_SERVERS, "No appropriate name servers or networks for name found"); + + case DNS_TRANSACTION_TIMEOUT: + return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "Query timed out"); + + case DNS_TRANSACTION_ATTEMPTS_MAX_REACHED: + return sd_bus_reply_method_errorf(q->request, SD_BUS_ERROR_TIMEOUT, "All attempts to contact name servers or networks failed"); + + case DNS_TRANSACTION_INVALID_REPLY: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_INVALID_REPLY, "Received invalid reply"); + + case DNS_TRANSACTION_ERRNO: + return sd_bus_reply_method_errnof(q->request, q->answer_errno, "Lookup failed due to system error: %m"); + + case DNS_TRANSACTION_ABORTED: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_ABORTED, "Query aborted"); + + case DNS_TRANSACTION_DNSSEC_FAILED: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_DNSSEC_FAILED, "DNSSEC validation failed: %s", + dnssec_result_to_string(q->answer_dnssec_result)); + + case DNS_TRANSACTION_NO_TRUST_ANCHOR: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_TRUST_ANCHOR, "No suitable trust anchor known"); + + case DNS_TRANSACTION_RR_TYPE_UNSUPPORTED: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_RR_TYPE_UNSUPPORTED, "Server does not support requested resource record type"); + + case DNS_TRANSACTION_NETWORK_DOWN: + return sd_bus_reply_method_errorf(q->request, BUS_ERROR_NETWORK_DOWN, "Network is down"); + + case DNS_TRANSACTION_NOT_FOUND: + /* We return this as NXDOMAIN. This is only generated when a host doesn't implement LLMNR/TCP, and we + * thus quickly know that we cannot resolve an in-addr.arpa or ip6.arpa address. */ + return sd_bus_reply_method_errorf(q->request, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); + + case DNS_TRANSACTION_RCODE_FAILURE: { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + if (q->answer_rcode == DNS_RCODE_NXDOMAIN) + sd_bus_error_setf(&error, _BUS_ERROR_DNS "NXDOMAIN", "'%s' not found", dns_query_string(q)); + else { + const char *rc, *n; + char p[DECIMAL_STR_MAX(q->answer_rcode)]; + + rc = dns_rcode_to_string(q->answer_rcode); + if (!rc) { + sprintf(p, "%i", q->answer_rcode); + rc = p; + } + + n = strjoina(_BUS_ERROR_DNS, rc); + sd_bus_error_setf(&error, n, "Could not resolve '%s', server or network returned error %s", dns_query_string(q), rc); + } + + return sd_bus_reply_method_error(q->request, &error); + } + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + case DNS_TRANSACTION_SUCCESS: + default: + assert_not_reached("Impossible state"); + } +} + +static int append_address(sd_bus_message *reply, DnsResourceRecord *rr, int ifindex) { + int r; + + assert(reply); + assert(rr); + + r = sd_bus_message_open_container(reply, 'r', "iiay"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "i", ifindex); + if (r < 0) + return r; + + if (rr->key->type == DNS_TYPE_A) { + r = sd_bus_message_append(reply, "i", AF_INET); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &rr->a.in_addr, sizeof(struct in_addr)); + + } else if (rr->key->type == DNS_TYPE_AAAA) { + r = sd_bus_message_append(reply, "i", AF_INET6); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &rr->aaaa.in6_addr, sizeof(struct in6_addr)); + } else + return -EAFNOSUPPORT; + + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return 0; +} + +static void bus_method_resolve_hostname_complete(DnsQuery *q) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + DnsResourceRecord *rr; + unsigned added = 0; + int ifindex, r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname(q); + if (r == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ + return; + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(iiay)"); + if (r < 0) + goto finish; + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + DnsQuestion *question; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = append_address(reply, rr, ifindex); + if (r < 0) + goto finish; + + if (!canonical) + canonical = dns_resource_record_ref(rr); + + added ++; + } + + if (added <= 0) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + /* Return the precise spelling and uppercasing and CNAME target reported by the server */ + assert(canonical); + r = sd_bus_message_append( + reply, "st", + DNS_RESOURCE_KEY_NAME(canonical->key), + SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send hostname reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static int check_ifindex_flags(int ifindex, uint64_t *flags, uint64_t ok, sd_bus_error *error) { + assert(flags); + + if (ifindex < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); + + if (*flags & ~(SD_RESOLVED_PROTOCOLS_ALL|SD_RESOLVED_NO_CNAME|ok)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid flags parameter"); + + if ((*flags & SD_RESOLVED_PROTOCOLS_ALL) == 0) /* If no protocol is enabled, enable all */ + *flags |= SD_RESOLVED_PROTOCOLS_ALL; + + return 0; +} + +static int bus_method_resolve_hostname(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; + Manager *m = userdata; + const char *hostname; + int family, ifindex; + uint64_t flags; + DnsQuery *q; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(message, "isit", &ifindex, &hostname, &family, &flags); + if (r < 0) + return r; + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); + + r = dns_name_is_valid(hostname); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid hostname '%s'", hostname); + + r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_SEARCH, error); + if (r < 0) + return r; + + r = dns_question_new_address(&question_utf8, family, hostname, false); + if (r < 0) + return r; + + r = dns_question_new_address(&question_idna, family, hostname, true); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->request_family = family; + q->complete = bus_method_resolve_hostname_complete; + q->suppress_unroutable_family = family == AF_UNSPEC; + + r = dns_query_bus_track(q, message); + if (r < 0) + goto fail; + + r = dns_query_go(q); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(q); + return r; +} + +static void bus_method_resolve_address_complete(DnsQuery *q) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + DnsQuestion *question; + DnsResourceRecord *rr; + unsigned added = 0; + int ifindex, r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname(q); + if (r == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ + return; + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(is)"); + if (r < 0) + goto finish; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = sd_bus_message_append(reply, "(is)", ifindex, rr->ptr.name); + if (r < 0) + goto finish; + + added ++; + } + + if (added <= 0) { + _cleanup_free_ char *ip = NULL; + + in_addr_to_string(q->request_family, &q->request_address, &ip); + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Address '%s' does not have any RR of requested type", strna(ip)); + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send address reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static int bus_method_resolve_address(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + Manager *m = userdata; + int family, ifindex; + uint64_t flags; + const void *d; + DnsQuery *q; + size_t sz; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(message, "ii", &ifindex, &family); + if (r < 0) + return r; + + if (!IN_SET(family, AF_INET, AF_INET6)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); + + r = sd_bus_message_read_array(message, 'y', &d, &sz); + if (r < 0) + return r; + + if (sz != FAMILY_ADDRESS_SIZE(family)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size"); + + r = sd_bus_message_read(message, "t", &flags); + if (r < 0) + return r; + + r = check_ifindex_flags(ifindex, &flags, 0, error); + if (r < 0) + return r; + + r = dns_question_new_reverse(&question, family, d); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->request_family = family; + memcpy(&q->request_address, d, sz); + q->complete = bus_method_resolve_address_complete; + + r = dns_query_bus_track(q, message); + if (r < 0) + goto fail; + + r = dns_query_go(q); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(q); + return r; +} + +static int bus_message_append_rr(sd_bus_message *m, DnsResourceRecord *rr, int ifindex) { + int r; + + assert(m); + assert(rr); + + r = sd_bus_message_open_container(m, 'r', "iqqay"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "iqq", + ifindex, + rr->key->class, + rr->key->type); + if (r < 0) + return r; + + r = dns_resource_record_to_wire_format(rr, false); + if (r < 0) + return r; + + r = sd_bus_message_append_array(m, 'y', rr->wire_format, rr->wire_format_size); + if (r < 0) + return r; + + return sd_bus_message_close_container(m); +} + +static void bus_method_resolve_record_complete(DnsQuery *q) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + DnsResourceRecord *rr; + DnsQuestion *question; + unsigned added = 0; + int ifindex; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname(q); + if (r == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ + return; + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(iqqay)"); + if (r < 0) + goto finish; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = bus_message_append_rr(reply, rr, ifindex); + if (r < 0) + goto finish; + + added ++; + } + + if (added <= 0) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "Name '%s' does not have any RR of the requested type", dns_query_string(q)); + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + r = sd_bus_message_append(reply, "t", SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send record reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static int bus_method_resolve_record(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + Manager *m = userdata; + uint16_t class, type; + const char *name; + int r, ifindex; + uint64_t flags; + DnsQuery *q; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(message, "isqqt", &ifindex, &name, &class, &type, &flags); + if (r < 0) + return r; + + r = dns_name_is_valid(name); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid name '%s'", name); + + 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_obsolete(type)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Specified DNS resource record type %" PRIu16 " is obsolete.", type); + + r = check_ifindex_flags(ifindex, &flags, 0, error); + if (r < 0) + return r; + + question = dns_question_new(1); + if (!question) + return -ENOMEM; + + key = dns_resource_key_new(class, type, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(question, key); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question, question, ifindex, flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->complete = bus_method_resolve_record_complete; + + r = dns_query_bus_track(q, message); + if (r < 0) + goto fail; + + r = dns_query_go(q); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(q); + return r; +} + +static int append_srv(DnsQuery *q, sd_bus_message *reply, DnsResourceRecord *rr) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + DnsQuery *aux; + int r; + + assert(q); + assert(reply); + assert(rr); + assert(rr->key); + + if (rr->key->type != DNS_TYPE_SRV) + return 0; + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + /* First, let's see if we could find an appropriate A or AAAA + * record for the SRV record */ + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + DnsResourceRecord *zz; + DnsQuestion *question; + + if (aux->state != DNS_TRANSACTION_SUCCESS) + continue; + if (aux->auxiliary_result != 0) + continue; + + question = dns_query_question_for_protocol(aux, aux->answer_protocol); + + r = dns_name_equal(dns_question_first_name(question), rr->srv.name); + if (r < 0) + return r; + if (r == 0) + continue; + + DNS_ANSWER_FOREACH(zz, aux->answer) { + + r = dns_question_matches_rr(question, zz, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + canonical = dns_resource_record_ref(zz); + break; + } + + if (canonical) + break; + } + + /* Is there are successful A/AAAA lookup for this SRV RR? If not, don't add it */ + if (!canonical) + return 0; + } + + r = sd_bus_message_open_container(reply, 'r', "qqqsa(iiay)s"); + if (r < 0) + return r; + + r = sd_bus_message_append( + reply, + "qqqs", + rr->srv.priority, rr->srv.weight, rr->srv.port, rr->srv.name); + if (r < 0) + return r; + + r = sd_bus_message_open_container(reply, 'a', "(iiay)"); + if (r < 0) + return r; + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + DnsResourceRecord *zz; + DnsQuestion *question; + int ifindex; + + if (aux->state != DNS_TRANSACTION_SUCCESS) + continue; + if (aux->auxiliary_result != 0) + continue; + + question = dns_query_question_for_protocol(aux, aux->answer_protocol); + + r = dns_name_equal(dns_question_first_name(question), rr->srv.name); + if (r < 0) + return r; + if (r == 0) + continue; + + DNS_ANSWER_FOREACH_IFINDEX(zz, ifindex, aux->answer) { + + r = dns_question_matches_rr(question, zz, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + r = append_address(reply, zz, ifindex); + if (r < 0) + return r; + } + } + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + /* Note that above we appended the hostname as encoded in the + * SRV, and here the canonical hostname this maps to. */ + r = sd_bus_message_append(reply, "s", canonical ? DNS_RESOURCE_KEY_NAME(canonical->key) : rr->srv.name); + if (r < 0) + return r; + + r = sd_bus_message_close_container(reply); + if (r < 0) + return r; + + return 1; +} + +static int append_txt(sd_bus_message *reply, DnsResourceRecord *rr) { + DnsTxtItem *i; + int r; + + assert(reply); + assert(rr); + assert(rr->key); + + if (rr->key->type != DNS_TYPE_TXT) + return 0; + + LIST_FOREACH(items, i, rr->txt.items) { + + if (i->length <= 0) + continue; + + r = sd_bus_message_append_array(reply, 'y', i->data, i->length); + if (r < 0) + return r; + } + + return 1; +} + +static void resolve_service_all_complete(DnsQuery *q) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *canonical = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; + DnsQuestion *question; + DnsResourceRecord *rr; + unsigned added = 0; + DnsQuery *aux; + int r; + + assert(q); + + if (q->block_all_complete > 0) + return; + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + DnsQuery *bad = NULL; + bool have_success = false; + + LIST_FOREACH(auxiliary_queries, aux, q->auxiliary_queries) { + + switch (aux->state) { + + case DNS_TRANSACTION_PENDING: + /* If an auxiliary query is still pending, let's wait */ + return; + + case DNS_TRANSACTION_SUCCESS: + if (aux->auxiliary_result == 0) + have_success = true; + else + bad = aux; + break; + + default: + bad = aux; + break; + } + } + + if (!have_success) { + /* We can only return one error, hence pick the last error we encountered */ + + assert(bad); + + if (bad->state == DNS_TRANSACTION_SUCCESS) { + assert(bad->auxiliary_result != 0); + + if (bad->auxiliary_result == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(bad)); + goto finish; + } + + r = bad->auxiliary_result; + goto finish; + } + + r = reply_query_state(bad); + goto finish; + } + } + + r = sd_bus_message_new_method_return(q->request, &reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "(qqqsa(iiay)s)"); + if (r < 0) + goto finish; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = append_srv(q, reply, rr); + if (r < 0) + goto finish; + if (r == 0) /* not an SRV record */ + continue; + + if (!canonical) + canonical = dns_resource_record_ref(rr); + + added++; + } + + if (added <= 0) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + r = sd_bus_message_open_container(reply, 'a', "ay"); + if (r < 0) + goto finish; + + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + r = append_txt(reply, rr); + if (r < 0) + goto finish; + } + + r = sd_bus_message_close_container(reply); + if (r < 0) + goto finish; + + assert(canonical); + r = dns_service_split(DNS_RESOURCE_KEY_NAME(canonical->key), &name, &type, &domain); + if (r < 0) + goto finish; + + r = sd_bus_message_append( + reply, + "ssst", + name, type, domain, + SD_RESOLVED_FLAGS_MAKE(q->answer_protocol, q->answer_family, q->answer_authenticated)); + if (r < 0) + goto finish; + + r = sd_bus_send(q->manager->bus, reply, NULL); + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send service reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static void resolve_service_hostname_complete(DnsQuery *q) { + int r; + + assert(q); + assert(q->auxiliary_for); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + resolve_service_all_complete(q->auxiliary_for); + return; + } + + r = dns_query_process_cname(q); + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ + return; + + /* This auxiliary lookup is finished or failed, let's see if all are finished now. */ + q->auxiliary_result = r; + resolve_service_all_complete(q->auxiliary_for); +} + +static int resolve_service_hostname(DnsQuery *q, DnsResourceRecord *rr, int ifindex) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + DnsQuery *aux; + int r; + + assert(q); + assert(rr); + assert(rr->key); + assert(rr->key->type == DNS_TYPE_SRV); + + /* OK, we found an SRV record for the service. Let's resolve + * the hostname included in it */ + + r = dns_question_new_address(&question, q->request_family, rr->srv.name, false); + if (r < 0) + return r; + + r = dns_query_new(q->manager, &aux, question, question, ifindex, q->flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + aux->request_family = q->request_family; + aux->complete = resolve_service_hostname_complete; + + r = dns_query_make_auxiliary(aux, q); + if (r == -EAGAIN) { + /* Too many auxiliary lookups? If so, don't complain, + * let's just not add this one, we already have more + * than enough */ + + dns_query_free(aux); + return 0; + } + if (r < 0) + goto fail; + + /* Note that auxiliary queries do not track the original bus + * client, only the primary request does that. */ + + r = dns_query_go(aux); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(aux); + return r; +} + +static void bus_method_resolve_service_complete(DnsQuery *q) { + bool has_root_domain = false; + DnsResourceRecord *rr; + DnsQuestion *question; + unsigned found = 0; + int ifindex, r; + + assert(q); + + if (q->state != DNS_TRANSACTION_SUCCESS) { + r = reply_query_state(q); + goto finish; + } + + r = dns_query_process_cname(q); + if (r == -ELOOP) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_CNAME_LOOP, "CNAME loop detected, or CNAME resolving disabled on '%s'", dns_query_string(q)); + goto finish; + } + if (r < 0) + goto finish; + if (r == DNS_QUERY_RESTARTED) /* This was a cname, and the query was restarted. */ + return; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, q->answer) { + r = dns_question_matches_rr(question, rr, NULL); + if (r < 0) + goto finish; + if (r == 0) + continue; + + if (rr->key->type != DNS_TYPE_SRV) + continue; + + if (dns_name_is_root(rr->srv.name)) { + has_root_domain = true; + continue; + } + + if ((q->flags & SD_RESOLVED_NO_ADDRESS) == 0) { + q->block_all_complete ++; + r = resolve_service_hostname(q, rr, ifindex); + q->block_all_complete --; + + if (r < 0) + goto finish; + } + + found++; + } + + if (has_root_domain && found <= 0) { + /* If there's exactly one SRV RR and it uses + * the root domain as host name, then the + * service is explicitly not offered on the + * domain. Report this as a recognizable + * error. See RFC 2782, Section "Usage + * Rules". */ + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_SERVICE, "'%s' does not provide the requested service", dns_query_string(q)); + goto finish; + } + + if (found <= 0) { + r = sd_bus_reply_method_errorf(q->request, BUS_ERROR_NO_SUCH_RR, "'%s' does not have any RR of the requested type", dns_query_string(q)); + goto finish; + } + + /* Maybe we are already finished? check now... */ + resolve_service_all_complete(q); + return; + +finish: + if (r < 0) { + log_error_errno(r, "Failed to send service reply: %m"); + sd_bus_reply_method_errno(q->request, r, NULL); + } + + dns_query_free(q); +} + +static int bus_method_resolve_service(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_(dns_question_unrefp) DnsQuestion *question_idna = NULL, *question_utf8 = NULL; + const char *name, *type, *domain; + _cleanup_free_ char *n = NULL; + Manager *m = userdata; + int family, ifindex; + uint64_t flags; + DnsQuery *q; + int r; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_read(message, "isssit", &ifindex, &name, &type, &domain, &family, &flags); + if (r < 0) + return r; + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); + + if (isempty(name)) + name = NULL; + else if (!dns_service_name_is_valid(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid service name '%s'", name); + + if (isempty(type)) + type = NULL; + else if (!dns_srv_type_is_valid(type)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid SRV service type '%s'", type); + + r = dns_name_is_valid(domain); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid domain '%s'", domain); + + if (name && !type) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Service name cannot be specified without service type."); + + r = check_ifindex_flags(ifindex, &flags, SD_RESOLVED_NO_TXT|SD_RESOLVED_NO_ADDRESS, error); + if (r < 0) + return r; + + r = dns_question_new_service(&question_utf8, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), false); + if (r < 0) + return r; + + r = dns_question_new_service(&question_idna, name, type, domain, !(flags & SD_RESOLVED_NO_TXT), true); + if (r < 0) + return r; + + r = dns_query_new(m, &q, question_utf8, question_idna, ifindex, flags|SD_RESOLVED_NO_SEARCH); + if (r < 0) + return r; + + q->request = sd_bus_message_ref(message); + q->request_family = family; + q->complete = bus_method_resolve_service_complete; + + r = dns_query_bus_track(q, message); + if (r < 0) + goto fail; + + r = dns_query_go(q); + if (r < 0) + goto fail; + + return 1; + +fail: + dns_query_free(q); + return r; +} + +int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex) { + int r; + + assert(reply); + assert(s); + + r = sd_bus_message_open_container(reply, 'r', with_ifindex ? "iiay" : "iay"); + if (r < 0) + return r; + + if (with_ifindex) { + r = sd_bus_message_append(reply, "i", s->link ? s->link->ifindex : 0); + if (r < 0) + return r; + } + + r = sd_bus_message_append(reply, "i", s->family); + if (r < 0) + return r; + + r = sd_bus_message_append_array(reply, 'y', &s->address, FAMILY_ADDRESS_SIZE(s->family)); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + +static int bus_property_get_dns_servers( + 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; + unsigned c = 0; + DnsServer *s; + Iterator i; + Link *l; + int r; + + assert(reply); + assert(m); + + r = sd_bus_message_open_container(reply, 'a', "(iiay)"); + if (r < 0) + return r; + + LIST_FOREACH(servers, s, m->dns_servers) { + r = bus_dns_server_append(reply, s, true); + if (r < 0) + return r; + + c++; + } + + HASHMAP_FOREACH(l, m->links, i) { + LIST_FOREACH(servers, s, l->dns_servers) { + r = bus_dns_server_append(reply, s, true); + if (r < 0) + return r; + c++; + } + } + + if (c == 0) { + LIST_FOREACH(servers, s, m->fallback_dns_servers) { + r = bus_dns_server_append(reply, s, true); + if (r < 0) + return r; + } + } + + return sd_bus_message_close_container(reply); +} + +static int bus_property_get_search_domains( + 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; + DnsSearchDomain *d; + Iterator i; + Link *l; + int r; + + assert(reply); + assert(m); + + r = sd_bus_message_open_container(reply, 'a', "(isb)"); + if (r < 0) + return r; + + LIST_FOREACH(domains, d, m->search_domains) { + r = sd_bus_message_append(reply, "(isb)", 0, d->name, d->route_only); + if (r < 0) + return r; + } + + HASHMAP_FOREACH(l, m->links, i) { + LIST_FOREACH(domains, d, l->search_domains) { + r = sd_bus_message_append(reply, "(isb)", l->ifindex, d->name, d->route_only); + if (r < 0) + return r; + } + } + + return sd_bus_message_close_container(reply); +} + +static int bus_property_get_transaction_statistics( + 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; + + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "(tt)", + (uint64_t) hashmap_size(m->dns_transactions), + (uint64_t) m->n_transactions_total); +} + +static int bus_property_get_cache_statistics( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + uint64_t size = 0, hit = 0, miss = 0; + Manager *m = userdata; + DnsScope *s; + + assert(reply); + assert(m); + + LIST_FOREACH(scopes, s, m->dns_scopes) { + size += dns_cache_size(&s->cache); + hit += s->cache.n_hit; + miss += s->cache.n_miss; + } + + return sd_bus_message_append(reply, "(ttt)", size, hit, miss); +} + +static int bus_property_get_dnssec_statistics( + 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; + + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "(tttt)", + (uint64_t) m->n_dnssec_verdict[DNSSEC_SECURE], + (uint64_t) m->n_dnssec_verdict[DNSSEC_INSECURE], + (uint64_t) m->n_dnssec_verdict[DNSSEC_BOGUS], + (uint64_t) m->n_dnssec_verdict[DNSSEC_INDETERMINATE]); +} + +static int bus_property_get_dnssec_supported( + 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; + + assert(reply); + assert(m); + + return sd_bus_message_append(reply, "b", manager_dnssec_supported(m)); +} + +static int bus_method_reset_statistics(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *m = userdata; + DnsScope *s; + + assert(message); + assert(m); + + LIST_FOREACH(scopes, s, m->dns_scopes) + s->cache.n_hit = s->cache.n_miss = 0; + + m->n_transactions_total = 0; + zero(m->n_dnssec_verdict); + + return sd_bus_reply_method_return(message, NULL); +} + +static int get_any_link(Manager *m, int ifindex, Link **ret, sd_bus_error *error) { + Link *l; + + assert(m); + assert(ret); + + if (ifindex <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid interface index"); + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!l) + return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_LINK, "Link %i not known", ifindex); + + *ret = l; + 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; + + assert(m); + assert(message); + assert(handler); + + assert_cc(sizeof(int) == sizeof(int32_t)); + r = sd_bus_message_read(message, "i", &ifindex); + if (r < 0) + return r; + + r = get_unmanaged_link(m, ifindex, &l, error); + if (r < 0) + return r; + + return handler(message, l, error); +} + +static int bus_method_set_link_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dns_servers, error); +} + +static int bus_method_set_link_search_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_search_domains, error); +} + +static int bus_method_set_link_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_llmnr, error); +} + +static int bus_method_set_link_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_mdns, error); +} + +static int bus_method_set_link_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dnssec, error); +} + +static int bus_method_set_link_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_set_dnssec_negative_trust_anchors, error); +} + +static int bus_method_revert_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { + return call_link_method(userdata, message, bus_link_method_revert, error); +} + +static int bus_method_get_link(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_free_ char *p = NULL; + Manager *m = userdata; + int r, ifindex; + Link *l; + + assert(message); + assert(m); + + assert_cc(sizeof(int) == sizeof(int32_t)); + r = sd_bus_message_read(message, "i", &ifindex); + if (r < 0) + return r; + + r = get_any_link(m, ifindex, &l, error); + if (r < 0) + return r; + + p = link_bus_path(l); + if (!p) + return -ENOMEM; + + return sd_bus_reply_method_return(message, "o", p); +} + +static const sd_bus_vtable resolve_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("LLMNRHostname", "s", NULL, offsetof(Manager, llmnr_hostname), 0), + SD_BUS_PROPERTY("DNS", "a(iiay)", bus_property_get_dns_servers, 0, 0), + SD_BUS_PROPERTY("SearchDomains", "a(isb)", bus_property_get_search_domains, 0, 0), + SD_BUS_PROPERTY("TransactionStatistics", "(tt)", bus_property_get_transaction_statistics, 0, 0), + 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_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("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_search_domains, 0), + SD_BUS_METHOD("SetLinkLLMNR", "is", NULL, bus_method_set_link_llmnr, 0), + SD_BUS_METHOD("SetLinkMulticastDNS", "is", NULL, bus_method_set_link_mdns, 0), + SD_BUS_METHOD("SetLinkDNSSEC", "is", NULL, bus_method_set_link_dnssec, 0), + SD_BUS_METHOD("SetLinkDNSSECNegativeTrustAnchors", "ias", NULL, bus_method_set_link_dnssec_negative_trust_anchors, 0), + SD_BUS_METHOD("RevertLink", "i", NULL, bus_method_revert_link, 0), + + SD_BUS_VTABLE_END, +}; + +static int on_bus_retry(sd_event_source *s, usec_t usec, void *userdata) { + Manager *m = userdata; + + assert(s); + assert(m); + + m->bus_retry_event_source = sd_event_source_unref(m->bus_retry_event_source); + + manager_connect_bus(m); + return 0; +} + +static int match_prepare_for_sleep(sd_bus_message *message, void *userdata, sd_bus_error *ret_error) { + Manager *m = userdata; + int b, r; + + assert(message); + assert(m); + + r = sd_bus_message_read(message, "b", &b); + if (r < 0) { + log_debug_errno(r, "Failed to parse PrepareForSleep signal: %m"); + return 0; + } + + if (b) + return 0; + + log_debug("Coming back from suspend, verifying all RRs..."); + + manager_verify_all(m); + return 0; +} + +int manager_connect_bus(Manager *m) { + int r; + + assert(m); + + if (m->bus) + return 0; + + r = sd_bus_default_system(&m->bus); + if (r < 0) { + /* We failed to connect? Yuck, we must be in early + * boot. Let's try in 5s again. As soon as we have + * kdbus we can stop doing this... */ + + log_debug_errno(r, "Failed to connect to bus, trying again in 5s: %m"); + + r = sd_event_add_time(m->event, &m->bus_retry_event_source, CLOCK_MONOTONIC, now(CLOCK_MONOTONIC) + 5*USEC_PER_SEC, 0, on_bus_retry, m); + if (r < 0) + return log_error_errno(r, "Failed to install bus reconnect time event: %m"); + + (void) sd_event_source_set_description(m->bus_retry_event_source, "bus-retry"); + return 0; + } + + r = sd_bus_add_object_vtable(m->bus, NULL, "/org/freedesktop/resolve1", "org.freedesktop.resolve1.Manager", resolve_vtable, m); + if (r < 0) + return log_error_errno(r, "Failed to register object: %m"); + + r = sd_bus_add_fallback_vtable(m->bus, NULL, "/org/freedesktop/resolve1/link", "org.freedesktop.resolve1.Link", link_vtable, link_object_find, m); + if (r < 0) + return log_error_errno(r, "Failed to register link objects: %m"); + + r = sd_bus_add_node_enumerator(m->bus, NULL, "/org/freedesktop/resolve1/link", link_node_enumerator, m); + if (r < 0) + return log_error_errno(r, "Failed to register link enumerator: %m"); + + r = sd_bus_request_name(m->bus, "org.freedesktop.resolve1", 0); + if (r < 0) + return log_error_errno(r, "Failed to register name: %m"); + + r = sd_bus_attach_event(m->bus, m->event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + r = sd_bus_add_match(m->bus, &m->prepare_for_sleep_slot, + "type='signal'," + "sender='org.freedesktop.login1'," + "interface='org.freedesktop.login1.Manager'," + "member='PrepareForSleep'," + "path='/org/freedesktop/login1'", + match_prepare_for_sleep, + m); + if (r < 0) + log_error_errno(r, "Failed to add match for PrepareForSleep: %m"); + + return 0; +} diff --git a/src/resolve/src/resolved-bus.h b/src/resolve/src/resolved-bus.h new file mode 100644 index 0000000000..f49e1337d2 --- /dev/null +++ b/src/resolve/src/resolved-bus.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "resolved-manager.h" + +int manager_connect_bus(Manager *m); +int bus_dns_server_append(sd_bus_message *reply, DnsServer *s, bool with_ifindex); diff --git a/src/resolve/src/resolved-conf.c b/src/resolve/src/resolved-conf.c new file mode 100644 index 0000000000..bb93fbfda2 --- /dev/null +++ b/src/resolve/src/resolved-conf.c @@ -0,0 +1,236 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . + ***/ + +#include "alloc-util.h" +#include "conf-parser.h" +#include "def.h" +#include "extract-word.h" +#include "parse-util.h" +#include "resolved-conf.h" +#include "string-util.h" + +int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word) { + union in_addr_union address; + int family, r; + DnsServer *s; + + assert(m); + assert(word); + + r = in_addr_from_string_auto(word, &family, &address); + if (r < 0) + return r; + + /* Filter out duplicates */ + s = dns_server_find(manager_get_first_dns_server(m, type), family, &address); + if (s) { + /* + * Drop the marker. This is used to find the servers + * that ceased to exist, see + * manager_mark_dns_servers() and + * manager_flush_marked_dns_servers(). + */ + dns_server_move_back_and_unmark(s); + return 0; + } + + return dns_server_new(m, NULL, type, NULL, family, &address); +} + +int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) { + int r; + + assert(m); + assert(string); + + for(;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + 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); + } + + return 0; +} + +int manager_add_search_domain_by_string(Manager *m, const char *domain) { + DnsSearchDomain *d; + bool route_only; + int r; + + assert(m); + assert(domain); + + route_only = *domain == '~'; + if (route_only) + domain++; + + if (dns_name_is_root(domain) || streq(domain, "*")) { + route_only = true; + domain = "."; + } + + r = dns_search_domain_find(m->search_domains, domain, &d); + if (r < 0) + return r; + if (r > 0) + dns_search_domain_move_back_and_unmark(d); + else { + r = dns_search_domain_new(m, &d, DNS_SEARCH_DOMAIN_SYSTEM, NULL, domain); + if (r < 0) + return r; + } + + d->route_only = route_only; + return 0; +} + +int manager_parse_search_domains_and_warn(Manager *m, const char *string) { + int r; + + assert(m); + assert(string); + + for(;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&string, &word, NULL, EXTRACT_QUOTES); + if (r < 0) + return r; + if (r == 0) + break; + + r = manager_add_search_domain_by_string(m, word); + if (r < 0) + log_warning_errno(r, "Failed to add search domain '%s', ignoring.", word); + } + + return 0; +} + +int config_parse_dns_servers( + 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) { + + Manager *m = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(m); + + if (isempty(rvalue)) + /* Empty assignment means clear the list */ + dns_server_unlink_all(manager_get_first_dns_server(m, ltype)); + else { + /* Otherwise, add to the list */ + r = manager_parse_dns_server_string_and_warn(m, ltype, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse DNS server string '%s'. Ignoring.", rvalue); + return 0; + } + } + + /* If we have a manual setting, then we stop reading + * /etc/resolv.conf */ + if (ltype == DNS_SERVER_SYSTEM) + m->read_resolv_conf = false; + if (ltype == DNS_SERVER_FALLBACK) + m->need_builtin_fallbacks = false; + + return 0; +} + +int config_parse_search_domains( + 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) { + + Manager *m = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(m); + + if (isempty(rvalue)) + /* Empty assignment means clear the list */ + dns_search_domain_unlink_all(m->search_domains); + else { + /* Otherwise, add to the list */ + r = manager_parse_search_domains_and_warn(m, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse search domains string '%s'. Ignoring.", rvalue); + return 0; + } + } + + /* If we have a manual setting, then we stop reading + * /etc/resolv.conf */ + m->read_resolv_conf = false; + + return 0; +} + +int manager_parse_config_file(Manager *m) { + int r; + + assert(m); + + r = config_parse_many(PKGSYSCONFDIR "/resolved.conf", + CONF_PATHS_NULSTR("systemd/resolved.conf.d"), + "Resolve\0", + config_item_perf_lookup, resolved_gperf_lookup, + false, m); + if (r < 0) + return r; + + if (m->need_builtin_fallbacks) { + r = manager_parse_dns_server_string_and_warn(m, DNS_SERVER_FALLBACK, DNS_SERVERS); + if (r < 0) + return r; + } + + return 0; + +} diff --git a/src/resolve/src/resolved-conf.h b/src/resolve/src/resolved-conf.h new file mode 100644 index 0000000000..e1fd2cceec --- /dev/null +++ b/src/resolve/src/resolved-conf.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include "resolved-manager.h" + +int manager_parse_config_file(Manager *m); + +int manager_add_search_domain_by_string(Manager *m, const char *domain); +int manager_parse_search_domains_and_warn(Manager *m, const char *string); + +int manager_add_dns_server_by_string(Manager *m, DnsServerType type, const char *word); +int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string); + +const struct ConfigPerfItem* resolved_gperf_lookup(const char *key, unsigned length); + +int config_parse_dns_servers(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_search_domains(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_dnssec(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/resolve/src/resolved-def.h b/src/resolve/src/resolved-def.h new file mode 100644 index 0000000000..c4c1915b18 --- /dev/null +++ b/src/resolve/src/resolved-def.h @@ -0,0 +1,38 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#define SD_RESOLVED_DNS (UINT64_C(1) << 0) +#define SD_RESOLVED_LLMNR_IPV4 (UINT64_C(1) << 1) +#define SD_RESOLVED_LLMNR_IPV6 (UINT64_C(1) << 2) +#define SD_RESOLVED_MDNS_IPV4 (UINT64_C(1) << 3) +#define SD_RESOLVED_MDNS_IPV6 (UINT64_C(1) << 4) +#define SD_RESOLVED_NO_CNAME (UINT64_C(1) << 5) +#define SD_RESOLVED_NO_TXT (UINT64_C(1) << 6) +#define SD_RESOLVED_NO_ADDRESS (UINT64_C(1) << 7) +#define SD_RESOLVED_NO_SEARCH (UINT64_C(1) << 8) +#define SD_RESOLVED_AUTHENTICATED (UINT64_C(1) << 9) + +#define SD_RESOLVED_LLMNR (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_LLMNR_IPV6) +#define SD_RESOLVED_MDNS (SD_RESOLVED_MDNS_IPV4|SD_RESOLVED_MDNS_IPV6) + +#define SD_RESOLVED_PROTOCOLS_ALL (SD_RESOLVED_MDNS|SD_RESOLVED_LLMNR|SD_RESOLVED_DNS) diff --git a/src/resolve/src/resolved-dns-answer.c b/src/resolve/src/resolved-dns-answer.c new file mode 100644 index 0000000000..7eb303ab95 --- /dev/null +++ b/src/resolve/src/resolved-dns-answer.c @@ -0,0 +1,858 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-dnssec.h" +#include "string-util.h" + +DnsAnswer *dns_answer_new(unsigned n) { + DnsAnswer *a; + + a = malloc0(offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * n); + if (!a) + return NULL; + + a->n_ref = 1; + a->n_allocated = n; + + return a; +} + +DnsAnswer *dns_answer_ref(DnsAnswer *a) { + if (!a) + return NULL; + + assert(a->n_ref > 0); + a->n_ref++; + return a; +} + +static void dns_answer_flush(DnsAnswer *a) { + DnsResourceRecord *rr; + + if (!a) + return; + + DNS_ANSWER_FOREACH(rr, a) + dns_resource_record_unref(rr); + + a->n_rrs = 0; +} + +DnsAnswer *dns_answer_unref(DnsAnswer *a) { + if (!a) + return NULL; + + assert(a->n_ref > 0); + + if (a->n_ref == 1) { + dns_answer_flush(a); + free(a); + } else + a->n_ref--; + + return NULL; +} + +static int dns_answer_add_raw(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { + assert(rr); + + if (!a) + return -ENOSPC; + + if (a->n_rrs >= a->n_allocated) + return -ENOSPC; + + a->items[a->n_rrs++] = (DnsAnswerItem) { + .rr = dns_resource_record_ref(rr), + .ifindex = ifindex, + .flags = flags, + }; + + return 1; +} + +static int dns_answer_add_raw_all(DnsAnswer *a, DnsAnswer *source) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int ifindex, r; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, source) { + r = dns_answer_add_raw(a, rr, ifindex, flags); + if (r < 0) + return r; + } + + return 0; +} + +int dns_answer_add(DnsAnswer *a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { + unsigned i; + int r; + + assert(rr); + + if (!a) + return -ENOSPC; + if (a->n_ref > 1) + return -EBUSY; + + for (i = 0; i < a->n_rrs; i++) { + if (a->items[i].ifindex != ifindex) + continue; + + r = dns_resource_record_equal(a->items[i].rr, rr); + if (r < 0) + return r; + if (r > 0) { + /* Don't mix contradicting TTLs (see below) */ + if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) + return -EINVAL; + + /* Entry already exists, keep the entry with + * the higher RR. */ + if (rr->ttl > a->items[i].rr->ttl) { + dns_resource_record_ref(rr); + dns_resource_record_unref(a->items[i].rr); + a->items[i].rr = rr; + } + + a->items[i].flags |= flags; + return 0; + } + + r = dns_resource_key_equal(a->items[i].rr->key, rr->key); + if (r < 0) + return r; + if (r > 0) { + /* There's already an RR of the same RRset in + * place! Let's see if the TTLs more or less + * match. We don't really care if they match + * precisely, but we do care whether one is 0 + * and the other is not. See RFC 2181, Section + * 5.2.*/ + + if ((rr->ttl == 0) != (a->items[i].rr->ttl == 0)) + return -EINVAL; + } + } + + return dns_answer_add_raw(a, rr, ifindex, flags); +} + +static int dns_answer_add_all(DnsAnswer *a, DnsAnswer *b) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int ifindex, r; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, b) { + r = dns_answer_add(a, rr, ifindex, flags); + if (r < 0) + return r; + } + + return 0; +} + +int dns_answer_add_extend(DnsAnswer **a, DnsResourceRecord *rr, int ifindex, DnsAnswerFlags flags) { + int r; + + assert(a); + assert(rr); + + r = dns_answer_reserve_or_clone(a, 1); + if (r < 0) + return r; + + return dns_answer_add(*a, rr, ifindex, flags); +} + +int dns_answer_add_soa(DnsAnswer *a, const char *name, uint32_t ttl) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *soa = NULL; + + soa = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_SOA, name); + if (!soa) + return -ENOMEM; + + soa->ttl = ttl; + + soa->soa.mname = strdup(name); + if (!soa->soa.mname) + return -ENOMEM; + + soa->soa.rname = strappend("root.", name); + if (!soa->soa.rname) + return -ENOMEM; + + soa->soa.serial = 1; + soa->soa.refresh = 1; + soa->soa.retry = 1; + soa->soa.expire = 1; + soa->soa.minimum = ttl; + + return dns_answer_add(a, soa, 0, DNS_ANSWER_AUTHENTICATED); +} + +int dns_answer_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { + DnsAnswerFlags flags = 0, i_flags; + DnsResourceRecord *i; + bool found = false; + int r; + + assert(key); + + DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { + r = dns_resource_key_match_rr(key, i, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + if (!ret_flags) + return 1; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } + } + + if (ret_flags) + *ret_flags = flags; + + return found; +} + +int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *ret_flags) { + DnsAnswerFlags flags = 0, i_flags; + DnsResourceRecord *i; + bool found = false; + int r; + + assert(rr); + + DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { + r = dns_resource_record_equal(i, rr); + if (r < 0) + return r; + if (r == 0) + continue; + + if (!ret_flags) + return 1; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } + } + + if (ret_flags) + *ret_flags = flags; + + return found; +} + +int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *ret_flags) { + DnsAnswerFlags flags = 0, i_flags; + DnsResourceRecord *i; + bool found = false; + int r; + + assert(key); + + DNS_ANSWER_FOREACH_FLAGS(i, i_flags, a) { + r = dns_resource_key_equal(i->key, key); + if (r < 0) + return r; + if (r == 0) + continue; + + if (!ret_flags) + return true; + + if (found) + flags &= i_flags; + else { + flags = i_flags; + found = true; + } + } + + if (ret_flags) + *ret_flags = flags; + + return found; +} + +int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a) { + DnsResourceRecord *i; + + DNS_ANSWER_FOREACH(i, a) { + if (IN_SET(i->key->type, DNS_TYPE_NSEC, DNS_TYPE_NSEC3)) + return true; + } + + return false; +} + +int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone) { + DnsResourceRecord *rr; + int r; + + /* Checks whether the specified answer contains at least one NSEC3 RR in the specified zone */ + + DNS_ANSWER_FOREACH(rr, answer) { + const char *p; + + if (rr->key->type != DNS_TYPE_NSEC3) + continue; + + p = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_equal(p, zone); + if (r != 0) + return r; + } + + return false; +} + +int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { + DnsResourceRecord *rr, *soa = NULL; + DnsAnswerFlags rr_flags, soa_flags = 0; + int r; + + assert(key); + + /* For a SOA record we can never find a matching SOA record */ + if (key->type == DNS_TYPE_SOA) + return 0; + + DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { + r = dns_resource_key_match_soa(key, rr->key); + if (r < 0) + return r; + if (r > 0) { + + if (soa) { + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(soa->key)); + if (r < 0) + return r; + if (r > 0) + continue; + } + + soa = rr; + soa_flags = rr_flags; + } + } + + if (!soa) + return 0; + + if (ret) + *ret = soa; + if (flags) + *flags = soa_flags; + + return 1; +} + +int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags) { + DnsResourceRecord *rr; + DnsAnswerFlags rr_flags; + int r; + + assert(key); + + /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ + if (!dns_type_may_redirect(key->type)) + return 0; + + DNS_ANSWER_FOREACH_FLAGS(rr, rr_flags, a) { + r = dns_resource_key_match_cname_or_dname(key, rr->key, NULL); + if (r < 0) + return r; + if (r > 0) { + if (ret) + *ret = rr; + if (flags) + *flags = rr_flags; + return 1; + } + } + + return 0; +} + +int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret) { + _cleanup_(dns_answer_unrefp) DnsAnswer *k = NULL; + int r; + + assert(ret); + + if (dns_answer_size(a) <= 0) { + *ret = dns_answer_ref(b); + return 0; + } + + if (dns_answer_size(b) <= 0) { + *ret = dns_answer_ref(a); + return 0; + } + + k = dns_answer_new(a->n_rrs + b->n_rrs); + if (!k) + return -ENOMEM; + + r = dns_answer_add_raw_all(k, a); + if (r < 0) + return r; + + r = dns_answer_add_all(k, b); + if (r < 0) + return r; + + *ret = k; + k = NULL; + + return 0; +} + +int dns_answer_extend(DnsAnswer **a, DnsAnswer *b) { + DnsAnswer *merged; + int r; + + assert(a); + + r = dns_answer_merge(*a, b, &merged); + if (r < 0) + return r; + + dns_answer_unref(*a); + *a = merged; + + return 0; +} + +int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key) { + bool found = false, other = false; + DnsResourceRecord *rr; + unsigned i; + int r; + + assert(a); + assert(key); + + /* Remove all entries matching the specified key from *a */ + + DNS_ANSWER_FOREACH(rr, *a) { + r = dns_resource_key_equal(rr->key, key); + if (r < 0) + return r; + if (r > 0) + found = true; + else + other = true; + + if (found && other) + break; + } + + if (!found) + return 0; + + if (!other) { + *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ + return 1; + } + + if ((*a)->n_ref > 1) { + _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; + DnsAnswerFlags flags; + int ifindex; + + copy = dns_answer_new((*a)->n_rrs); + if (!copy) + return -ENOMEM; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { + r = dns_resource_key_equal(rr->key, key); + if (r < 0) + return r; + if (r > 0) + continue; + + r = dns_answer_add_raw(copy, rr, ifindex, flags); + if (r < 0) + return r; + } + + dns_answer_unref(*a); + *a = copy; + copy = NULL; + + return 1; + } + + /* Only a single reference, edit in-place */ + + i = 0; + for (;;) { + if (i >= (*a)->n_rrs) + break; + + r = dns_resource_key_equal((*a)->items[i].rr->key, key); + if (r < 0) + return r; + if (r > 0) { + /* Kill this entry */ + + dns_resource_record_unref((*a)->items[i].rr); + memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); + (*a)->n_rrs --; + continue; + + } else + /* Keep this entry */ + i++; + } + + return 1; +} + +int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rm) { + bool found = false, other = false; + DnsResourceRecord *rr; + unsigned i; + int r; + + assert(a); + assert(rm); + + /* Remove all entries matching the specified RR from *a */ + + DNS_ANSWER_FOREACH(rr, *a) { + r = dns_resource_record_equal(rr, rm); + if (r < 0) + return r; + if (r > 0) + found = true; + else + other = true; + + if (found && other) + break; + } + + if (!found) + return 0; + + if (!other) { + *a = dns_answer_unref(*a); /* Return NULL for the empty answer */ + return 1; + } + + if ((*a)->n_ref > 1) { + _cleanup_(dns_answer_unrefp) DnsAnswer *copy = NULL; + DnsAnswerFlags flags; + int ifindex; + + copy = dns_answer_new((*a)->n_rrs); + if (!copy) + return -ENOMEM; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, *a) { + r = dns_resource_record_equal(rr, rm); + if (r < 0) + return r; + if (r > 0) + continue; + + r = dns_answer_add_raw(copy, rr, ifindex, flags); + if (r < 0) + return r; + } + + dns_answer_unref(*a); + *a = copy; + copy = NULL; + + return 1; + } + + /* Only a single reference, edit in-place */ + + i = 0; + for (;;) { + if (i >= (*a)->n_rrs) + break; + + r = dns_resource_record_equal((*a)->items[i].rr, rm); + if (r < 0) + return r; + if (r > 0) { + /* Kill this entry */ + + dns_resource_record_unref((*a)->items[i].rr); + memmove((*a)->items + i, (*a)->items + i + 1, sizeof(DnsAnswerItem) * ((*a)->n_rrs - i - 1)); + (*a)->n_rrs --; + continue; + + } else + /* Keep this entry */ + i++; + } + + return 1; +} + +int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags) { + DnsResourceRecord *rr_source; + int ifindex_source, r; + DnsAnswerFlags flags_source; + + assert(a); + assert(key); + + /* Copy all RRs matching the specified key from source into *a */ + + DNS_ANSWER_FOREACH_FULL(rr_source, ifindex_source, flags_source, source) { + + r = dns_resource_key_equal(rr_source->key, key); + if (r < 0) + return r; + if (r == 0) + continue; + + /* Make space for at least one entry */ + r = dns_answer_reserve_or_clone(a, 1); + if (r < 0) + return r; + + r = dns_answer_add(*a, rr_source, ifindex_source, flags_source|or_flags); + if (r < 0) + return r; + } + + return 0; +} + +int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags) { + int r; + + assert(to); + assert(from); + assert(key); + + r = dns_answer_copy_by_key(to, *from, key, or_flags); + if (r < 0) + return r; + + return dns_answer_remove_by_key(from, key); +} + +void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local) { + DnsAnswerItem *items; + unsigned i, start, end; + + if (!a) + return; + + if (a->n_rrs <= 1) + return; + + start = 0; + end = a->n_rrs-1; + + /* RFC 4795, Section 2.6 suggests we should order entries + * depending on whether the sender is a link-local address. */ + + items = newa(DnsAnswerItem, a->n_rrs); + for (i = 0; i < a->n_rrs; i++) { + + 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 */ + items[end--] = a->items[i]; + else + /* Order all other records to the beginning of the array */ + items[start++] = a->items[i]; + } + + assert(start == end+1); + memcpy(a->items, items, sizeof(DnsAnswerItem) * a->n_rrs); +} + +int dns_answer_reserve(DnsAnswer **a, unsigned n_free) { + DnsAnswer *n; + + assert(a); + + if (n_free <= 0) + return 0; + + if (*a) { + unsigned ns; + + if ((*a)->n_ref > 1) + return -EBUSY; + + ns = (*a)->n_rrs + n_free; + + if ((*a)->n_allocated >= ns) + return 0; + + /* Allocate more than we need */ + ns *= 2; + + n = realloc(*a, offsetof(DnsAnswer, items) + sizeof(DnsAnswerItem) * ns); + if (!n) + return -ENOMEM; + + n->n_allocated = ns; + } else { + n = dns_answer_new(n_free); + if (!n) + return -ENOMEM; + } + + *a = n; + return 0; +} + +int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free) { + _cleanup_(dns_answer_unrefp) DnsAnswer *n = NULL; + int r; + + assert(a); + + /* Tries to extend the DnsAnswer object. And if that's not + * possibly, since we are not the sole owner, then allocate a + * new, appropriately sized one. Either way, after this call + * the object will only have a single reference, and has room + * for at least the specified number of RRs. */ + + r = dns_answer_reserve(a, n_free); + if (r != -EBUSY) + return r; + + assert(*a); + + n = dns_answer_new(((*a)->n_rrs + n_free) * 2); + if (!n) + return -ENOMEM; + + r = dns_answer_add_raw_all(n, *a); + if (r < 0) + return r; + + dns_answer_unref(*a); + *a = n; + n = NULL; + + return 0; +} + +void dns_answer_dump(DnsAnswer *answer, FILE *f) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int ifindex; + + if (!f) + f = stdout; + + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { + const char *t; + + fputc('\t', f); + + t = dns_resource_record_to_string(rr); + if (!t) { + log_oom(); + continue; + } + + fputs(t, f); + + if (ifindex != 0 || flags & (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE|DNS_ANSWER_SHARED_OWNER)) + fputs("\t;", f); + + if (ifindex != 0) + printf(" ifindex=%i", ifindex); + if (flags & DNS_ANSWER_AUTHENTICATED) + fputs(" authenticated", f); + if (flags & DNS_ANSWER_CACHEABLE) + fputs(" cachable", f); + if (flags & DNS_ANSWER_SHARED_OWNER) + fputs(" shared-owner", f); + + fputc('\n', f); + } +} + +bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname) { + DnsResourceRecord *rr; + int r; + + assert(cname); + + /* Checks whether the answer contains a DNAME record that indicates that the specified CNAME record is + * synthesized from it */ + + if (cname->key->type != DNS_TYPE_CNAME) + return 0; + + DNS_ANSWER_FOREACH(rr, a) { + _cleanup_free_ char *n = NULL; + + if (rr->key->type != DNS_TYPE_DNAME) + continue; + if (rr->key->class != cname->key->class) + continue; + + r = dns_name_change_suffix(cname->cname.name, rr->dname.name, DNS_RESOURCE_KEY_NAME(rr->key), &n); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_equal(n, DNS_RESOURCE_KEY_NAME(cname->key)); + if (r < 0) + return r; + if (r > 0) + return 1; + + } + + return 0; +} diff --git a/src/resolve/src/resolved-dns-answer.h b/src/resolve/src/resolved-dns-answer.h new file mode 100644 index 0000000000..8f9c15eab4 --- /dev/null +++ b/src/resolve/src/resolved-dns-answer.h @@ -0,0 +1,143 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +typedef struct DnsAnswer DnsAnswer; +typedef struct DnsAnswerItem DnsAnswerItem; + +#include "macro.h" +#include "resolved-dns-rr.h" + +/* A simple array of resource records. We keep track of the + * originating ifindex for each RR where that makes sense, so that we + * can qualify A and AAAA RRs referring to a local link with the + * right ifindex. + * + * Note that we usually encode the the empty DnsAnswer object as a simple NULL. */ + +typedef enum DnsAnswerFlags { + DNS_ANSWER_AUTHENTICATED = 1, /* Item has been authenticated */ + DNS_ANSWER_CACHEABLE = 2, /* Item is subject to caching */ + DNS_ANSWER_SHARED_OWNER = 4, /* For mDNS: RRset may be owner by multiple peers */ +} DnsAnswerFlags; + +struct DnsAnswerItem { + DnsResourceRecord *rr; + int ifindex; + DnsAnswerFlags flags; +}; + +struct DnsAnswer { + unsigned n_ref; + unsigned n_rrs, n_allocated; + DnsAnswerItem items[0]; +}; + +DnsAnswer *dns_answer_new(unsigned n); +DnsAnswer *dns_answer_ref(DnsAnswer *a); +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_match_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); +int dns_answer_contains_rr(DnsAnswer *a, DnsResourceRecord *rr, DnsAnswerFlags *combined_flags); +int dns_answer_contains_key(DnsAnswer *a, const DnsResourceKey *key, DnsAnswerFlags *combined_flags); +int dns_answer_contains_nsec_or_nsec3(DnsAnswer *a); +int dns_answer_contains_zone_nsec3(DnsAnswer *answer, const char *zone); + +int dns_answer_find_soa(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); +int dns_answer_find_cname_or_dname(DnsAnswer *a, const DnsResourceKey *key, DnsResourceRecord **ret, DnsAnswerFlags *flags); + +int dns_answer_merge(DnsAnswer *a, DnsAnswer *b, DnsAnswer **ret); +int dns_answer_extend(DnsAnswer **a, DnsAnswer *b); + +void dns_answer_order_by_scope(DnsAnswer *a, bool prefer_link_local); + +int dns_answer_reserve(DnsAnswer **a, unsigned n_free); +int dns_answer_reserve_or_clone(DnsAnswer **a, unsigned n_free); + +int dns_answer_remove_by_key(DnsAnswer **a, const DnsResourceKey *key); +int dns_answer_remove_by_rr(DnsAnswer **a, DnsResourceRecord *rr); + +int dns_answer_copy_by_key(DnsAnswer **a, DnsAnswer *source, const DnsResourceKey *key, DnsAnswerFlags or_flags); +int dns_answer_move_by_key(DnsAnswer **to, DnsAnswer **from, const DnsResourceKey *key, DnsAnswerFlags or_flags); + +bool dns_answer_has_dname_for_cname(DnsAnswer *a, DnsResourceRecord *cname); + +static inline unsigned dns_answer_size(DnsAnswer *a) { + return a ? a->n_rrs : 0; +} + +void dns_answer_dump(DnsAnswer *answer, FILE *f); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsAnswer*, dns_answer_unref); + +#define _DNS_ANSWER_FOREACH(q, kk, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + 0; \ + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, (kk) = (UNIQ_T(i, q) < (a)->n_rrs ? (a)->items[UNIQ_T(i, q)].rr : NULL)) + +#define DNS_ANSWER_FOREACH(kk, a) _DNS_ANSWER_FOREACH(UNIQ, kk, a) + +#define _DNS_ANSWER_FOREACH_IFINDEX(q, kk, ifi, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ + 0; \ + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, \ + (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ + (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0)) + +#define DNS_ANSWER_FOREACH_IFINDEX(kk, ifindex, a) _DNS_ANSWER_FOREACH_IFINDEX(UNIQ, kk, ifindex, a) + +#define _DNS_ANSWER_FOREACH_FLAGS(q, kk, fl, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ + 0; \ + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, \ + (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ + (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) + +#define DNS_ANSWER_FOREACH_FLAGS(kk, flags, a) _DNS_ANSWER_FOREACH_FLAGS(UNIQ, kk, flags, a) + +#define _DNS_ANSWER_FOREACH_FULL(q, kk, ifi, fl, a) \ + for (unsigned UNIQ_T(i, q) = ({ \ + (kk) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].rr : NULL; \ + (ifi) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].ifindex : 0; \ + (fl) = ((a) && (a)->n_rrs > 0) ? (a)->items[0].flags : 0; \ + 0; \ + }); \ + (a) && (UNIQ_T(i, q) < (a)->n_rrs); \ + UNIQ_T(i, q)++, \ + (kk) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].rr : NULL), \ + (ifi) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].ifindex : 0), \ + (fl) = ((UNIQ_T(i, q) < (a)->n_rrs) ? (a)->items[UNIQ_T(i, q)].flags : 0)) + +#define DNS_ANSWER_FOREACH_FULL(kk, ifindex, flags, a) _DNS_ANSWER_FOREACH_FULL(UNIQ, kk, ifindex, flags, a) diff --git a/src/resolve/src/resolved-dns-cache.c b/src/resolve/src/resolved-dns-cache.c new file mode 100644 index 0000000000..9bcc71724e --- /dev/null +++ b/src/resolve/src/resolved-dns-cache.c @@ -0,0 +1,1088 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-cache.h" +#include "resolved-dns-packet.h" +#include "string-util.h" + +/* Never cache more than 4K entries. RFC 1536, Section 5 suggests to + * leave DNS caches unbounded, but that's crazy. */ +#define CACHE_MAX 4096 + +/* We never keep any item longer than 2h in our cache */ +#define CACHE_TTL_MAX_USEC (2 * USEC_PER_HOUR) + +typedef enum DnsCacheItemType DnsCacheItemType; +typedef struct DnsCacheItem DnsCacheItem; + +enum DnsCacheItemType { + DNS_CACHE_POSITIVE, + DNS_CACHE_NODATA, + DNS_CACHE_NXDOMAIN, +}; + +struct DnsCacheItem { + DnsCacheItemType type; + DnsResourceKey *key; + DnsResourceRecord *rr; + + usec_t until; + bool authenticated:1; + bool shared_owner:1; + + int ifindex; + int owner_family; + union in_addr_union owner_address; + + unsigned prioq_idx; + LIST_FIELDS(DnsCacheItem, by_key); +}; + +static void dns_cache_item_free(DnsCacheItem *i) { + if (!i) + return; + + dns_resource_record_unref(i->rr); + dns_resource_key_unref(i->key); + free(i); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsCacheItem*, dns_cache_item_free); + +static void dns_cache_item_unlink_and_free(DnsCache *c, DnsCacheItem *i) { + DnsCacheItem *first; + + assert(c); + + if (!i) + return; + + first = hashmap_get(c->by_key, i->key); + LIST_REMOVE(by_key, first, i); + + if (first) + assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); + else + hashmap_remove(c->by_key, i->key); + + prioq_remove(c->by_expiry, i, &i->prioq_idx); + + dns_cache_item_free(i); +} + +static bool dns_cache_remove_by_rr(DnsCache *c, DnsResourceRecord *rr) { + DnsCacheItem *first, *i; + int r; + + first = hashmap_get(c->by_key, rr->key); + LIST_FOREACH(by_key, i, first) { + r = dns_resource_record_equal(i->rr, rr); + if (r < 0) + return r; + if (r > 0) { + dns_cache_item_unlink_and_free(c, i); + return true; + } + } + + return false; +} + +static bool dns_cache_remove_by_key(DnsCache *c, DnsResourceKey *key) { + DnsCacheItem *first, *i, *n; + + assert(c); + assert(key); + + first = hashmap_remove(c->by_key, key); + if (!first) + return false; + + LIST_FOREACH_SAFE(by_key, i, n, first) { + prioq_remove(c->by_expiry, i, &i->prioq_idx); + dns_cache_item_free(i); + } + + return true; +} + +void dns_cache_flush(DnsCache *c) { + DnsResourceKey *key; + + assert(c); + + while ((key = hashmap_first_key(c->by_key))) + dns_cache_remove_by_key(c, key); + + assert(hashmap_size(c->by_key) == 0); + assert(prioq_size(c->by_expiry) == 0); + + c->by_key = hashmap_free(c->by_key); + c->by_expiry = prioq_free(c->by_expiry); +} + +static void dns_cache_make_space(DnsCache *c, unsigned add) { + assert(c); + + if (add <= 0) + return; + + /* Makes space for n new entries. Note that we actually allow + * the cache to grow beyond CACHE_MAX, but only when we shall + * add more RRs to the cache than CACHE_MAX at once. In that + * case the cache will be emptied completely otherwise. */ + + for (;;) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + DnsCacheItem *i; + + if (prioq_size(c->by_expiry) <= 0) + break; + + if (prioq_size(c->by_expiry) + add < CACHE_MAX) + break; + + i = prioq_peek(c->by_expiry); + assert(i); + + /* Take an extra reference to the key so that it + * doesn't go away in the middle of the remove call */ + key = dns_resource_key_ref(i->key); + dns_cache_remove_by_key(c, key); + } +} + +void dns_cache_prune(DnsCache *c) { + usec_t t = 0; + + assert(c); + + /* Remove all entries that are past their TTL */ + + for (;;) { + DnsCacheItem *i; + + i = prioq_peek(c->by_expiry); + if (!i) + break; + + if (t <= 0) + t = now(clock_boottime_or_monotonic()); + + if (i->until > t) + break; + + /* Depending whether this is an mDNS shared entry + * either remove only this one RR or the whole + * RRset */ + if (i->shared_owner) + dns_cache_item_unlink_and_free(c, i); + else { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + /* Take an extra reference to the key so that it + * doesn't go away in the middle of the remove call */ + key = dns_resource_key_ref(i->key); + dns_cache_remove_by_key(c, key); + } + } +} + +static int dns_cache_item_prioq_compare_func(const void *a, const void *b) { + const DnsCacheItem *x = a, *y = b; + + if (x->until < y->until) + return -1; + if (x->until > y->until) + return 1; + return 0; +} + +static int dns_cache_init(DnsCache *c) { + int r; + + assert(c); + + r = prioq_ensure_allocated(&c->by_expiry, dns_cache_item_prioq_compare_func); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&c->by_key, &dns_resource_key_hash_ops); + if (r < 0) + return r; + + return r; +} + +static int dns_cache_link_item(DnsCache *c, DnsCacheItem *i) { + DnsCacheItem *first; + int r; + + assert(c); + assert(i); + + r = prioq_put(c->by_expiry, i, &i->prioq_idx); + if (r < 0) + return r; + + first = hashmap_get(c->by_key, i->key); + if (first) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; + + /* Keep a reference to the original key, while we manipulate the list. */ + k = dns_resource_key_ref(first->key); + + /* Now, try to reduce the number of keys we keep */ + dns_resource_key_reduce(&first->key, &i->key); + + if (first->rr) + dns_resource_key_reduce(&first->rr->key, &i->key); + if (i->rr) + dns_resource_key_reduce(&i->rr->key, &i->key); + + LIST_PREPEND(by_key, first, i); + assert_se(hashmap_replace(c->by_key, first->key, first) >= 0); + } else { + r = hashmap_put(c->by_key, i->key, i); + if (r < 0) { + prioq_remove(c->by_expiry, i, &i->prioq_idx); + return r; + } + } + + return 0; +} + +static DnsCacheItem* dns_cache_get(DnsCache *c, DnsResourceRecord *rr) { + DnsCacheItem *i; + + assert(c); + assert(rr); + + LIST_FOREACH(by_key, i, hashmap_get(c->by_key, rr->key)) + if (i->rr && dns_resource_record_equal(i->rr, rr) > 0) + return i; + + return NULL; +} + +static usec_t calculate_until(DnsResourceRecord *rr, uint32_t nsec_ttl, usec_t timestamp, bool use_soa_minimum) { + uint32_t ttl; + usec_t u; + + assert(rr); + + ttl = MIN(rr->ttl, nsec_ttl); + if (rr->key->type == DNS_TYPE_SOA && use_soa_minimum) { + /* If this is a SOA RR, and it is requested, clamp to + * the SOA's minimum field. This is used when we do + * negative caching, to determine the TTL for the + * negative caching entry. See RFC 2308, Section + * 5. */ + + if (ttl > rr->soa.minimum) + ttl = rr->soa.minimum; + } + + u = ttl * USEC_PER_SEC; + if (u > CACHE_TTL_MAX_USEC) + u = CACHE_TTL_MAX_USEC; + + if (rr->expiry != USEC_INFINITY) { + usec_t left; + + /* Make use of the DNSSEC RRSIG expiry info, if we + * have it */ + + left = LESS_BY(rr->expiry, now(CLOCK_REALTIME)); + if (u > left) + u = left; + } + + return timestamp + u; +} + +static void dns_cache_item_update_positive( + DnsCache *c, + DnsCacheItem *i, + DnsResourceRecord *rr, + bool authenticated, + bool shared_owner, + usec_t timestamp, + int ifindex, + int owner_family, + const union in_addr_union *owner_address) { + + assert(c); + assert(i); + assert(rr); + assert(owner_address); + + i->type = DNS_CACHE_POSITIVE; + + if (!i->by_key_prev) + /* We are the first item in the list, we need to + * update the key used in the hashmap */ + + assert_se(hashmap_replace(c->by_key, rr->key, i) >= 0); + + dns_resource_record_ref(rr); + dns_resource_record_unref(i->rr); + i->rr = rr; + + dns_resource_key_unref(i->key); + i->key = dns_resource_key_ref(rr->key); + + i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); + i->authenticated = authenticated; + i->shared_owner = shared_owner; + + i->ifindex = ifindex; + + i->owner_family = owner_family; + i->owner_address = *owner_address; + + prioq_reshuffle(c->by_expiry, i, &i->prioq_idx); +} + +static int dns_cache_put_positive( + DnsCache *c, + DnsResourceRecord *rr, + bool authenticated, + bool shared_owner, + usec_t timestamp, + int ifindex, + int owner_family, + const union in_addr_union *owner_address) { + + _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; + _cleanup_free_ char *key_str = NULL; + DnsCacheItem *existing; + int r, k; + + assert(c); + assert(rr); + assert(owner_address); + + /* Never cache pseudo RRs */ + if (dns_class_is_pseudo(rr->key->class)) + return 0; + if (dns_type_is_pseudo(rr->key->type)) + return 0; + + /* New TTL is 0? Delete this specific entry... */ + if (rr->ttl <= 0) { + k = dns_cache_remove_by_rr(c, rr); + + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(rr->key, &key_str); + if (r < 0) + return r; + + if (k > 0) + log_debug("Removed zero TTL entry from cache: %s", key_str); + else + log_debug("Not caching zero TTL cache entry: %s", key_str); + } + + return 0; + } + + /* Entry exists already? Update TTL, timestamp and owner*/ + existing = dns_cache_get(c, rr); + if (existing) { + dns_cache_item_update_positive( + c, + existing, + rr, + authenticated, + shared_owner, + timestamp, + ifindex, + owner_family, + owner_address); + return 0; + } + + /* Otherwise, add the new RR */ + r = dns_cache_init(c); + if (r < 0) + return r; + + dns_cache_make_space(c, 1); + + i = new0(DnsCacheItem, 1); + if (!i) + return -ENOMEM; + + i->type = DNS_CACHE_POSITIVE; + i->key = dns_resource_key_ref(rr->key); + i->rr = dns_resource_record_ref(rr); + i->until = calculate_until(rr, (uint32_t) -1, timestamp, false); + i->authenticated = authenticated; + i->shared_owner = shared_owner; + i->ifindex = ifindex; + i->owner_family = owner_family; + i->owner_address = *owner_address; + i->prioq_idx = PRIOQ_IDX_NULL; + + r = dns_cache_link_item(c, i); + if (r < 0) + return r; + + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(i->key, &key_str); + if (r < 0) + return r; + + log_debug("Added positive cache entry for %s", key_str); + } + + i = NULL; + return 0; +} + +static int dns_cache_put_negative( + DnsCache *c, + DnsResourceKey *key, + int rcode, + bool authenticated, + uint32_t nsec_ttl, + usec_t timestamp, + DnsResourceRecord *soa, + int owner_family, + const union in_addr_union *owner_address) { + + _cleanup_(dns_cache_item_freep) DnsCacheItem *i = NULL; + _cleanup_free_ char *key_str = NULL; + int r; + + assert(c); + assert(key); + assert(soa); + assert(owner_address); + + /* Never cache pseudo RR keys. DNS_TYPE_ANY is particularly + * important to filter out as we use this as a pseudo-type for + * NXDOMAIN entries */ + if (dns_class_is_pseudo(key->class)) + return 0; + if (dns_type_is_pseudo(key->type)) + return 0; + + if (nsec_ttl <= 0 || soa->soa.minimum <= 0 || soa->ttl <= 0) { + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("Not caching negative entry with zero SOA/NSEC/NSEC3 TTL: %s", key_str); + } + + return 0; + } + + if (!IN_SET(rcode, DNS_RCODE_SUCCESS, DNS_RCODE_NXDOMAIN)) + return 0; + + r = dns_cache_init(c); + if (r < 0) + return r; + + dns_cache_make_space(c, 1); + + i = new0(DnsCacheItem, 1); + if (!i) + return -ENOMEM; + + i->type = rcode == DNS_RCODE_SUCCESS ? DNS_CACHE_NODATA : DNS_CACHE_NXDOMAIN; + i->until = calculate_until(soa, nsec_ttl, timestamp, true); + i->authenticated = authenticated; + i->owner_family = owner_family; + i->owner_address = *owner_address; + i->prioq_idx = PRIOQ_IDX_NULL; + + if (i->type == DNS_CACHE_NXDOMAIN) { + /* NXDOMAIN entries should apply equally to all types, so we use ANY as + * a pseudo type for this purpose here. */ + i->key = dns_resource_key_new(key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(key)); + if (!i->key) + return -ENOMEM; + + /* Make sure to remove any previous entry for this + * specific ANY key. (For non-ANY keys the cache data + * is already cleared by the caller.) Note that we + * don't bother removing positive or NODATA cache + * items in this case, because it would either be slow + * or require explicit indexing by name */ + dns_cache_remove_by_key(c, key); + } else + i->key = dns_resource_key_ref(key); + + r = dns_cache_link_item(c, i); + if (r < 0) + return r; + + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(i->key, &key_str); + if (r < 0) + return r; + + log_debug("Added %s cache entry for %s", i->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", key_str); + } + + i = NULL; + return 0; +} + +static void dns_cache_remove_previous( + DnsCache *c, + DnsResourceKey *key, + DnsAnswer *answer) { + + DnsResourceRecord *rr; + DnsAnswerFlags flags; + + assert(c); + + /* First, if we were passed a key (i.e. on LLMNR/DNS, but + * not on mDNS), delete all matching old RRs, so that we only + * keep complete by_key in place. */ + if (key) + dns_cache_remove_by_key(c, key); + + /* Second, flush all entries matching the answer, unless this + * is an RR that is explicitly marked to be "shared" between + * peers (i.e. mDNS RRs without the flush-cache bit set). */ + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; + + if (flags & DNS_ANSWER_SHARED_OWNER) + continue; + + dns_cache_remove_by_key(c, rr->key); + } +} + +static bool rr_eligible(DnsResourceRecord *rr) { + assert(rr); + + /* When we see an NSEC/NSEC3 RR, we'll only cache it if it is from the lower zone, not the upper zone, since + * that's where the interesting bits are (with exception of DS RRs). Of course, this way we cannot derive DS + * existence from any cached NSEC/NSEC3, but that should be fine. */ + + switch (rr->key->type) { + + case DNS_TYPE_NSEC: + return !bitmap_isset(rr->nsec.types, DNS_TYPE_NS) || + bitmap_isset(rr->nsec.types, DNS_TYPE_SOA); + + case DNS_TYPE_NSEC3: + return !bitmap_isset(rr->nsec3.types, DNS_TYPE_NS) || + bitmap_isset(rr->nsec3.types, DNS_TYPE_SOA); + + default: + return true; + } +} + +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) { + + DnsResourceRecord *soa = NULL, *rr; + DnsAnswerFlags flags; + unsigned cache_keys; + int r, ifindex; + + assert(c); + assert(owner_address); + + dns_cache_remove_previous(c, key, answer); + + if (dns_answer_size(answer) <= 0) { + if (log_get_max_level() >= LOG_DEBUG) { + _cleanup_free_ char *key_str = NULL; + + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("Not caching negative entry without a SOA record: %s", key_str); + } + + 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 ++; + + /* Make some space for our new entries */ + dns_cache_make_space(c, cache_keys); + + if (timestamp <= 0) + timestamp = now(clock_boottime_or_monotonic()); + + /* Second, add in positive entries for all contained RRs */ + DNS_ANSWER_FOREACH_FULL(rr, ifindex, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; + + r = rr_eligible(rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_cache_put_positive( + c, + rr, + flags & DNS_ANSWER_AUTHENTICATED, + flags & DNS_ANSWER_SHARED_OWNER, + timestamp, + ifindex, + owner_family, owner_address); + if (r < 0) + goto fail; + } + + if (!key) /* mDNS doesn't know negative caching, really */ + return 0; + + /* Third, add in negative entries if the key has no RR */ + r = dns_answer_match_key(answer, key, NULL); + if (r < 0) + goto fail; + if (r > 0) + return 0; + + /* But not if it has a matching CNAME/DNAME (the negative + * caching will be done on the canonical name, not on the + * alias) */ + r = dns_answer_find_cname_or_dname(answer, key, NULL, NULL); + if (r < 0) + goto fail; + if (r > 0) + return 0; + + /* See https://tools.ietf.org/html/rfc2308, which say that a + * matching SOA record in the packet is used to to enable + * negative caching. */ + r = dns_answer_find_soa(answer, key, &soa, &flags); + if (r < 0) + goto fail; + if (r == 0) + return 0; + + /* Refuse using the SOA data if it is unsigned, but the key is + * signed */ + if (authenticated && (flags & DNS_ANSWER_AUTHENTICATED) == 0) + return 0; + + r = dns_cache_put_negative( + c, + key, + rcode, + authenticated, + nsec_ttl, + timestamp, + soa, + owner_family, owner_address); + if (r < 0) + goto fail; + + return 0; + +fail: + /* Adding all RRs failed. Let's clean up what we already + * added, just in case */ + + if (key) + dns_cache_remove_by_key(c, key); + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + if ((flags & DNS_ANSWER_CACHEABLE) == 0) + continue; + + dns_cache_remove_by_key(c, rr->key); + } + + return r; +} + +static DnsCacheItem *dns_cache_get_by_key_follow_cname_dname_nsec(DnsCache *c, DnsResourceKey *k) { + DnsCacheItem *i; + const char *n; + int r; + + assert(c); + assert(k); + + /* If we hit some OOM error, or suchlike, we don't care too + * much, after all this is just a cache */ + + i = hashmap_get(c->by_key, k); + if (i) + return i; + + n = DNS_RESOURCE_KEY_NAME(k); + + /* Check if we have an NXDOMAIN cache item for the name, notice that we use + * the pseudo-type ANY for NXDOMAIN cache items. */ + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_ANY, n)); + if (i && i->type == DNS_CACHE_NXDOMAIN) + return i; + + if (dns_type_may_redirect(k->type)) { + /* Check if we have a CNAME record instead */ + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_CNAME, n)); + if (i) + return i; + + /* OK, let's look for cached DNAME records. */ + for (;;) { + if (isempty(n)) + return NULL; + + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_DNAME, n)); + if (i) + return i; + + /* Jump one label ahead */ + r = dns_name_parent(&n); + if (r <= 0) + return NULL; + } + } + + if (k->type != DNS_TYPE_NSEC) { + /* Check if we have an NSEC record instead for the name. */ + i = hashmap_get(c->by_key, &DNS_RESOURCE_KEY_CONST(k->class, DNS_TYPE_NSEC, n)); + if (i) + return i; + } + + return NULL; +} + +int dns_cache_lookup(DnsCache *c, DnsResourceKey *key, int *rcode, DnsAnswer **ret, bool *authenticated) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + unsigned n = 0; + int r; + bool nxdomain = false; + _cleanup_free_ char *key_str = NULL; + DnsCacheItem *j, *first, *nsec = NULL; + bool have_authenticated = false, have_non_authenticated = false; + + assert(c); + assert(key); + assert(rcode); + assert(ret); + assert(authenticated); + + if (key->type == DNS_TYPE_ANY || + key->class == DNS_CLASS_ANY) { + + /* If we have ANY lookups we don't use the cache, so + * that the caller refreshes via the network. */ + + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("Ignoring cache for ANY lookup: %s", key_str); + } + + c->n_miss++; + + *ret = NULL; + *rcode = DNS_RCODE_SUCCESS; + return 0; + } + + first = dns_cache_get_by_key_follow_cname_dname_nsec(c, key); + if (!first) { + /* If one question cannot be answered we need to refresh */ + + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("Cache miss for %s", key_str); + } + + c->n_miss++; + + *ret = NULL; + *rcode = DNS_RCODE_SUCCESS; + return 0; + } + + LIST_FOREACH(by_key, j, first) { + if (j->rr) { + if (j->rr->key->type == DNS_TYPE_NSEC) + nsec = j; + + n++; + } else if (j->type == DNS_CACHE_NXDOMAIN) + nxdomain = true; + + if (j->authenticated) + have_authenticated = true; + else + have_non_authenticated = true; + } + + if (nsec && !IN_SET(key->type, DNS_TYPE_NSEC, DNS_TYPE_DS)) { + /* Note that we won't derive information for DS RRs from an NSEC, because we only cache NSEC RRs from + * the lower-zone of a zone cut, but the DS RRs are on the upper zone. */ + + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("NSEC NODATA cache hit for %s", key_str); + } + + /* We only found an NSEC record that matches our name. + * If it says the type doesn't exist report + * NODATA. Otherwise report a cache miss. */ + + *ret = NULL; + *rcode = DNS_RCODE_SUCCESS; + *authenticated = nsec->authenticated; + + if (!bitmap_isset(nsec->rr->nsec.types, key->type) && + !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_CNAME) && + !bitmap_isset(nsec->rr->nsec.types, DNS_TYPE_DNAME)) { + c->n_hit++; + return 1; + } + + c->n_miss++; + return 0; + } + + if (log_get_max_level() >= LOG_DEBUG) { + r = dns_resource_key_to_string(key, &key_str); + if (r < 0) + return r; + + log_debug("%s cache hit for %s", + n > 0 ? "Positive" : + nxdomain ? "NXDOMAIN" : "NODATA", + key_str); + } + + if (n <= 0) { + c->n_hit++; + + *ret = NULL; + *rcode = nxdomain ? DNS_RCODE_NXDOMAIN : DNS_RCODE_SUCCESS; + *authenticated = have_authenticated && !have_non_authenticated; + return 1; + } + + answer = dns_answer_new(n); + if (!answer) + return -ENOMEM; + + LIST_FOREACH(by_key, j, first) { + if (!j->rr) + continue; + + r = dns_answer_add(answer, j->rr, j->ifindex, j->authenticated ? DNS_ANSWER_AUTHENTICATED : 0); + if (r < 0) + return r; + } + + c->n_hit++; + + *ret = answer; + *rcode = DNS_RCODE_SUCCESS; + *authenticated = have_authenticated && !have_non_authenticated; + answer = NULL; + + return n; +} + +int dns_cache_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address) { + DnsCacheItem *i, *first; + bool same_owner = true; + + assert(cache); + assert(rr); + + dns_cache_prune(cache); + + /* See if there's a cache entry for the same key. If there + * isn't there's no conflict */ + first = hashmap_get(cache->by_key, rr->key); + if (!first) + return 0; + + /* See if the RR key is owned by the same owner, if so, there + * isn't a conflict either */ + LIST_FOREACH(by_key, i, first) { + if (i->owner_family != owner_family || + !in_addr_equal(owner_family, &i->owner_address, owner_address)) { + same_owner = false; + break; + } + } + if (same_owner) + return 0; + + /* See if there's the exact same RR in the cache. If yes, then + * there's no conflict. */ + if (dns_cache_get(cache, rr)) + return 0; + + /* There's a conflict */ + return 1; +} + +int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p) { + unsigned ancount = 0; + Iterator iterator; + DnsCacheItem *i; + int r; + + assert(cache); + assert(p); + + HASHMAP_FOREACH(i, cache->by_key, iterator) { + DnsCacheItem *j; + + LIST_FOREACH(by_key, j, i) { + if (!j->rr) + continue; + + if (!j->shared_owner) + continue; + + r = dns_packet_append_rr(p, j->rr, NULL, NULL); + if (r == -EMSGSIZE && p->protocol == DNS_PROTOCOL_MDNS) { + /* For mDNS, if we're unable to stuff all known answers into the given packet, + * allocate a new one, push the RR into that one and link it to the current one. + */ + + DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); + ancount = 0; + + r = dns_packet_new_query(&p->more, p->protocol, 0, true); + if (r < 0) + return r; + + /* continue with new packet */ + p = p->more; + r = dns_packet_append_rr(p, j->rr, NULL, NULL); + } + + if (r < 0) + return r; + + ancount ++; + } + } + + DNS_PACKET_HEADER(p)->ancount = htobe16(ancount); + + return 0; +} + +void dns_cache_dump(DnsCache *cache, FILE *f) { + Iterator iterator; + DnsCacheItem *i; + int r; + + if (!cache) + return; + + if (!f) + f = stdout; + + HASHMAP_FOREACH(i, cache->by_key, iterator) { + DnsCacheItem *j; + + LIST_FOREACH(by_key, j, i) { + + fputc('\t', f); + + if (j->rr) { + const char *t; + t = dns_resource_record_to_string(j->rr); + if (!t) { + log_oom(); + continue; + } + + fputs(t, f); + fputc('\n', f); + } else { + _cleanup_free_ char *z = NULL; + r = dns_resource_key_to_string(j->key, &z); + if (r < 0) { + log_oom(); + continue; + } + + fputs(z, f); + fputs(" -- ", f); + fputs(j->type == DNS_CACHE_NODATA ? "NODATA" : "NXDOMAIN", f); + fputc('\n', f); + } + } + } +} + +bool dns_cache_is_empty(DnsCache *cache) { + if (!cache) + return true; + + return hashmap_isempty(cache->by_key); +} + +unsigned dns_cache_size(DnsCache *cache) { + if (!cache) + return 0; + + return hashmap_size(cache->by_key); +} diff --git a/src/resolve/src/resolved-dns-cache.h b/src/resolve/src/resolved-dns-cache.h new file mode 100644 index 0000000000..2293718e86 --- /dev/null +++ b/src/resolve/src/resolved-dns-cache.h @@ -0,0 +1,52 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "hashmap.h" +#include "list.h" +#include "prioq.h" +#include "time-util.h" + +typedef struct DnsCache { + Hashmap *by_key; + Prioq *by_expiry; + unsigned n_hit; + unsigned n_miss; +} DnsCache; + +#include "resolved-dns-answer.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-question.h" +#include "resolved-dns-rr.h" + +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_check_conflicts(DnsCache *cache, DnsResourceRecord *rr, int owner_family, const union in_addr_union *owner_address); + +void dns_cache_dump(DnsCache *cache, FILE *f); +bool dns_cache_is_empty(DnsCache *cache); + +unsigned dns_cache_size(DnsCache *cache); + +int dns_cache_export_shared_to_packet(DnsCache *cache, DnsPacket *p); diff --git a/src/resolve/src/resolved-dns-dnssec.c b/src/resolve/src/resolved-dns-dnssec.c new file mode 100644 index 0000000000..7123d2d3a8 --- /dev/null +++ b/src/resolve/src/resolved-dns-dnssec.c @@ -0,0 +1,2211 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#ifdef HAVE_GCRYPT +#include +#endif + +#include "alloc-util.h" +#include "dns-domain.h" +#include "hexdecoct.h" +#include "resolved-dns-dnssec.h" +#include "resolved-dns-packet.h" +#include "string-table.h" + +#define VERIFY_RRS_MAX 256 +#define MAX_KEY_SIZE (32*1024) + +/* Permit a maximum clock skew of 1h 10min. This should be enough to deal with DST confusion */ +#define SKEW_MAX (1*USEC_PER_HOUR + 10*USEC_PER_MINUTE) + +/* Maximum number of NSEC3 iterations we'll do. RFC5155 says 2500 shall be the maximum useful value */ +#define NSEC3_ITERATIONS_MAX 2500 + +/* + * The DNSSEC Chain of trust: + * + * Normal RRs are protected via RRSIG RRs in combination with DNSKEY RRs, all in the same zone + * DNSKEY RRs are either protected like normal RRs, or via a DS from a zone "higher" up the tree + * DS RRs are protected like normal RRs + * + * Example chain: + * Normal RR → RRSIG/DNSKEY+ → DS → RRSIG/DNSKEY+ → DS → ... → DS → RRSIG/DNSKEY+ → DS + */ + +uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke) { + const uint8_t *p; + uint32_t sum, f; + size_t i; + + /* The algorithm from RFC 4034, Appendix B. */ + + assert(dnskey); + assert(dnskey->key->type == DNS_TYPE_DNSKEY); + + f = (uint32_t) dnskey->dnskey.flags; + + if (mask_revoke) + f &= ~DNSKEY_FLAG_REVOKE; + + sum = f + ((((uint32_t) dnskey->dnskey.protocol) << 8) + (uint32_t) dnskey->dnskey.algorithm); + + p = dnskey->dnskey.key; + + for (i = 0; i < dnskey->dnskey.key_size; i++) + sum += (i & 1) == 0 ? (uint32_t) p[i] << 8 : (uint32_t) p[i]; + + sum += (sum >> 16) & UINT32_C(0xFFFF); + + return sum & UINT32_C(0xFFFF); +} + +int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max) { + size_t c = 0; + int r; + + /* Converts the specified hostname into DNSSEC canonicalized + * form. */ + + if (buffer_max < 2) + return -ENOBUFS; + + for (;;) { + r = dns_label_unescape(&n, buffer, buffer_max); + if (r < 0) + return r; + if (r == 0) + break; + + if (buffer_max < (size_t) r + 2) + return -ENOBUFS; + + /* The DNSSEC canonical form is not clear on what to + * do with dots appearing in labels, the way DNS-SD + * does it. Refuse it for now. */ + + if (memchr(buffer, '.', r)) + return -EINVAL; + + ascii_strlower_n(buffer, (size_t) r); + buffer[r] = '.'; + + buffer += r + 1; + c += r + 1; + + buffer_max -= r + 1; + } + + if (c <= 0) { + /* Not even a single label: this is the root domain name */ + + assert(buffer_max > 2); + buffer[0] = '.'; + buffer[1] = 0; + + return 1; + } + + return (int) c; +} + +#ifdef HAVE_GCRYPT + +static void initialize_libgcrypt(void) { + const char *p; + + if (gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P)) + return; + + p = gcry_check_version("1.4.5"); + assert(p); + + gcry_control(GCRYCTL_DISABLE_SECMEM); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); +} + +static int rr_compare(const void *a, const void *b) { + DnsResourceRecord **x = (DnsResourceRecord**) a, **y = (DnsResourceRecord**) b; + size_t m; + int r; + + /* Let's order the RRs according to RFC 4034, Section 6.3 */ + + assert(x); + assert(*x); + assert((*x)->wire_format); + assert(y); + assert(*y); + assert((*y)->wire_format); + + m = MIN(DNS_RESOURCE_RECORD_RDATA_SIZE(*x), DNS_RESOURCE_RECORD_RDATA_SIZE(*y)); + + r = memcmp(DNS_RESOURCE_RECORD_RDATA(*x), DNS_RESOURCE_RECORD_RDATA(*y), m); + if (r != 0) + return r; + + if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) < DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) + return -1; + else if (DNS_RESOURCE_RECORD_RDATA_SIZE(*x) > DNS_RESOURCE_RECORD_RDATA_SIZE(*y)) + return 1; + + return 0; +} + +static int dnssec_rsa_verify_raw( + const char *hash_algorithm, + const void *signature, size_t signature_size, + const void *data, size_t data_size, + const void *exponent, size_t exponent_size, + const void *modulus, size_t modulus_size) { + + gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; + gcry_mpi_t n = NULL, e = NULL, s = NULL; + gcry_error_t ge; + int r; + + assert(hash_algorithm); + + ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature, signature_size, NULL); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&e, GCRYMPI_FMT_USG, exponent, exponent_size, NULL); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&n, GCRYMPI_FMT_USG, modulus, modulus_size, NULL); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&signature_sexp, + NULL, + "(sig-val (rsa (s %m)))", + s); + + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&data_sexp, + NULL, + "(data (flags pkcs1) (hash %s %b))", + hash_algorithm, + (int) data_size, + data); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&public_key_sexp, + NULL, + "(public-key (rsa (n %m) (e %m)))", + n, + e); + if (ge != 0) { + r = -EIO; + goto finish; + } + + ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); + if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) + r = 0; + else if (ge != 0) { + log_debug("RSA signature check failed: %s", gpg_strerror(ge)); + r = -EIO; + } else + r = 1; + +finish: + if (e) + gcry_mpi_release(e); + if (n) + gcry_mpi_release(n); + if (s) + gcry_mpi_release(s); + + if (public_key_sexp) + gcry_sexp_release(public_key_sexp); + if (signature_sexp) + gcry_sexp_release(signature_sexp); + if (data_sexp) + gcry_sexp_release(data_sexp); + + return r; +} + +static int dnssec_rsa_verify( + const char *hash_algorithm, + const void *hash, size_t hash_size, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey) { + + size_t exponent_size, modulus_size; + void *exponent, *modulus; + + assert(hash_algorithm); + assert(hash); + assert(hash_size > 0); + assert(rrsig); + assert(dnskey); + + if (*(uint8_t*) dnskey->dnskey.key == 0) { + /* exponent is > 255 bytes long */ + + exponent = (uint8_t*) dnskey->dnskey.key + 3; + exponent_size = + ((size_t) (((uint8_t*) dnskey->dnskey.key)[1]) << 8) | + ((size_t) ((uint8_t*) dnskey->dnskey.key)[2]); + + if (exponent_size < 256) + return -EINVAL; + + if (3 + exponent_size >= dnskey->dnskey.key_size) + return -EINVAL; + + modulus = (uint8_t*) dnskey->dnskey.key + 3 + exponent_size; + modulus_size = dnskey->dnskey.key_size - 3 - exponent_size; + + } else { + /* exponent is <= 255 bytes long */ + + exponent = (uint8_t*) dnskey->dnskey.key + 1; + exponent_size = (size_t) ((uint8_t*) dnskey->dnskey.key)[0]; + + if (exponent_size <= 0) + return -EINVAL; + + if (1 + exponent_size >= dnskey->dnskey.key_size) + return -EINVAL; + + modulus = (uint8_t*) dnskey->dnskey.key + 1 + exponent_size; + modulus_size = dnskey->dnskey.key_size - 1 - exponent_size; + } + + return dnssec_rsa_verify_raw( + hash_algorithm, + rrsig->rrsig.signature, rrsig->rrsig.signature_size, + hash, hash_size, + exponent, exponent_size, + modulus, modulus_size); +} + +static int dnssec_ecdsa_verify_raw( + const char *hash_algorithm, + const char *curve, + const void *signature_r, size_t signature_r_size, + const void *signature_s, size_t signature_s_size, + const void *data, size_t data_size, + const void *key, size_t key_size) { + + gcry_sexp_t public_key_sexp = NULL, data_sexp = NULL, signature_sexp = NULL; + gcry_mpi_t q = NULL, r = NULL, s = NULL; + gcry_error_t ge; + int k; + + assert(hash_algorithm); + + ge = gcry_mpi_scan(&r, GCRYMPI_FMT_USG, signature_r, signature_r_size, NULL); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&s, GCRYMPI_FMT_USG, signature_s, signature_s_size, NULL); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_mpi_scan(&q, GCRYMPI_FMT_USG, key, key_size, NULL); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&signature_sexp, + NULL, + "(sig-val (ecdsa (r %m) (s %m)))", + r, + s); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&data_sexp, + NULL, + "(data (flags rfc6979) (hash %s %b))", + hash_algorithm, + (int) data_size, + data); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_sexp_build(&public_key_sexp, + NULL, + "(public-key (ecc (curve %s) (q %m)))", + curve, + q); + if (ge != 0) { + k = -EIO; + goto finish; + } + + ge = gcry_pk_verify(signature_sexp, data_sexp, public_key_sexp); + if (gpg_err_code(ge) == GPG_ERR_BAD_SIGNATURE) + k = 0; + else if (ge != 0) { + log_debug("ECDSA signature check failed: %s", gpg_strerror(ge)); + k = -EIO; + } else + k = 1; +finish: + if (r) + gcry_mpi_release(r); + if (s) + gcry_mpi_release(s); + if (q) + gcry_mpi_release(q); + + if (public_key_sexp) + gcry_sexp_release(public_key_sexp); + if (signature_sexp) + gcry_sexp_release(signature_sexp); + if (data_sexp) + gcry_sexp_release(data_sexp); + + return k; +} + +static int dnssec_ecdsa_verify( + const char *hash_algorithm, + int algorithm, + const void *hash, size_t hash_size, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey) { + + const char *curve; + size_t key_size; + uint8_t *q; + + assert(hash); + assert(hash_size); + assert(rrsig); + assert(dnskey); + + if (algorithm == DNSSEC_ALGORITHM_ECDSAP256SHA256) { + key_size = 32; + curve = "NIST P-256"; + } else if (algorithm == DNSSEC_ALGORITHM_ECDSAP384SHA384) { + key_size = 48; + curve = "NIST P-384"; + } else + return -EOPNOTSUPP; + + if (dnskey->dnskey.key_size != key_size * 2) + return -EINVAL; + + if (rrsig->rrsig.signature_size != key_size * 2) + return -EINVAL; + + q = alloca(key_size*2 + 1); + q[0] = 0x04; /* Prepend 0x04 to indicate an uncompressed key */ + memcpy(q+1, dnskey->dnskey.key, key_size*2); + + return dnssec_ecdsa_verify_raw( + hash_algorithm, + curve, + rrsig->rrsig.signature, key_size, + (uint8_t*) rrsig->rrsig.signature + key_size, key_size, + hash, hash_size, + q, key_size*2+1); +} + +static void md_add_uint8(gcry_md_hd_t md, uint8_t v) { + gcry_md_write(md, &v, sizeof(v)); +} + +static void md_add_uint16(gcry_md_hd_t md, uint16_t v) { + v = htobe16(v); + gcry_md_write(md, &v, sizeof(v)); +} + +static void md_add_uint32(gcry_md_hd_t md, uint32_t v) { + v = htobe32(v); + gcry_md_write(md, &v, sizeof(v)); +} + +static int dnssec_rrsig_prepare(DnsResourceRecord *rrsig) { + int n_key_labels, n_signer_labels; + const char *name; + int r; + + /* Checks whether the specified RRSIG RR is somewhat valid, and initializes the .n_skip_labels_source and + * .n_skip_labels_signer fields so that we can use them later on. */ + + assert(rrsig); + assert(rrsig->key->type == DNS_TYPE_RRSIG); + + /* Check if this RRSIG RR is already prepared */ + if (rrsig->n_skip_labels_source != (unsigned) -1) + return 0; + + if (rrsig->rrsig.inception > rrsig->rrsig.expiration) + return -EINVAL; + + name = DNS_RESOURCE_KEY_NAME(rrsig->key); + + n_key_labels = dns_name_count_labels(name); + if (n_key_labels < 0) + return n_key_labels; + if (rrsig->rrsig.labels > n_key_labels) + return -EINVAL; + + n_signer_labels = dns_name_count_labels(rrsig->rrsig.signer); + if (n_signer_labels < 0) + return n_signer_labels; + if (n_signer_labels > rrsig->rrsig.labels) + return -EINVAL; + + r = dns_name_skip(name, n_key_labels - n_signer_labels, &name); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + /* Check if the signer is really a suffix of us */ + r = dns_name_equal(name, rrsig->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + rrsig->n_skip_labels_source = n_key_labels - rrsig->rrsig.labels; + rrsig->n_skip_labels_signer = n_key_labels - n_signer_labels; + + return 0; +} + +static int dnssec_rrsig_expired(DnsResourceRecord *rrsig, usec_t realtime) { + usec_t expiration, inception, skew; + + assert(rrsig); + assert(rrsig->key->type == DNS_TYPE_RRSIG); + + if (realtime == USEC_INFINITY) + realtime = now(CLOCK_REALTIME); + + expiration = rrsig->rrsig.expiration * USEC_PER_SEC; + inception = rrsig->rrsig.inception * USEC_PER_SEC; + + /* Consider inverted validity intervals as expired */ + if (inception > expiration) + return true; + + /* Permit a certain amount of clock skew of 10% of the valid + * time range. This takes inspiration from unbound's + * resolver. */ + skew = (expiration - inception) / 10; + if (skew > SKEW_MAX) + skew = SKEW_MAX; + + if (inception < skew) + inception = 0; + else + inception -= skew; + + if (expiration + skew < expiration) + expiration = USEC_INFINITY; + else + expiration += skew; + + return realtime < inception || realtime > expiration; +} + +static int algorithm_to_gcrypt_md(uint8_t algorithm) { + + /* Translates a DNSSEC signature algorithm into a gcrypt + * digest identifier. + * + * Note that we implement all algorithms listed as "Must + * implement" and "Recommended to Implement" in RFC6944. We + * don't implement any algorithms that are listed as + * "Optional" or "Must Not Implement". Specifically, we do not + * implement RSAMD5, DSASHA1, DH, DSA-NSEC3-SHA1, and + * GOST-ECC. */ + + switch (algorithm) { + + case DNSSEC_ALGORITHM_RSASHA1: + case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: + return GCRY_MD_SHA1; + + case DNSSEC_ALGORITHM_RSASHA256: + case DNSSEC_ALGORITHM_ECDSAP256SHA256: + return GCRY_MD_SHA256; + + case DNSSEC_ALGORITHM_ECDSAP384SHA384: + return GCRY_MD_SHA384; + + case DNSSEC_ALGORITHM_RSASHA512: + return GCRY_MD_SHA512; + + default: + return -EOPNOTSUPP; + } +} + +static void dnssec_fix_rrset_ttl( + DnsResourceRecord *list[], + unsigned n, + DnsResourceRecord *rrsig, + usec_t realtime) { + + unsigned k; + + assert(list); + assert(n > 0); + assert(rrsig); + + for (k = 0; k < n; k++) { + DnsResourceRecord *rr = list[k]; + + /* Pick the TTL as the minimum of the RR's TTL, the + * RR's original TTL according to the RRSIG and the + * RRSIG's own TTL, see RFC 4035, Section 5.3.3 */ + rr->ttl = MIN3(rr->ttl, rrsig->rrsig.original_ttl, rrsig->ttl); + rr->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; + + /* Copy over information about the signer and wildcard source of synthesis */ + rr->n_skip_labels_source = rrsig->n_skip_labels_source; + rr->n_skip_labels_signer = rrsig->n_skip_labels_signer; + } + + rrsig->expiry = rrsig->rrsig.expiration * USEC_PER_SEC; +} + +int dnssec_verify_rrset( + DnsAnswer *a, + const DnsResourceKey *key, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey, + usec_t realtime, + DnssecResult *result) { + + uint8_t wire_format_name[DNS_WIRE_FOMAT_HOSTNAME_MAX]; + DnsResourceRecord **list, *rr; + const char *source, *name; + gcry_md_hd_t md = NULL; + int r, md_algorithm; + size_t k, n = 0; + size_t hash_size; + void *hash; + bool wildcard; + + assert(key); + assert(rrsig); + assert(dnskey); + assert(result); + assert(rrsig->key->type == DNS_TYPE_RRSIG); + assert(dnskey->key->type == DNS_TYPE_DNSKEY); + + /* Verifies the the RRSet matching the specified "key" in "a", + * using the signature "rrsig" and the key "dnskey". It's + * assumed the RRSIG and DNSKEY match. */ + + md_algorithm = algorithm_to_gcrypt_md(rrsig->rrsig.algorithm); + if (md_algorithm == -EOPNOTSUPP) { + *result = DNSSEC_UNSUPPORTED_ALGORITHM; + return 0; + } + if (md_algorithm < 0) + return md_algorithm; + + r = dnssec_rrsig_prepare(rrsig); + if (r == -EINVAL) { + *result = DNSSEC_INVALID; + return r; + } + if (r < 0) + return r; + + r = dnssec_rrsig_expired(rrsig, realtime); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_SIGNATURE_EXPIRED; + return 0; + } + + name = DNS_RESOURCE_KEY_NAME(key); + + /* Some keys may only appear signed in the zone apex, and are invalid anywhere else. (SOA, NS...) */ + if (dns_type_apex_only(rrsig->rrsig.type_covered)) { + r = dns_name_equal(rrsig->rrsig.signer, name); + if (r < 0) + return r; + if (r == 0) { + *result = DNSSEC_INVALID; + return 0; + } + } + + /* OTOH DS RRs may not appear in the zone apex, but are valid everywhere else. */ + if (rrsig->rrsig.type_covered == DNS_TYPE_DS) { + r = dns_name_equal(rrsig->rrsig.signer, name); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_INVALID; + return 0; + } + } + + /* Determine the "Source of Synthesis" and whether this is a wildcard RRSIG */ + r = dns_name_suffix(name, rrsig->rrsig.labels, &source); + if (r < 0) + return r; + if (r > 0 && !dns_type_may_wildcard(rrsig->rrsig.type_covered)) { + /* We refuse to validate NSEC3 or SOA RRs that are synthesized from wildcards */ + *result = DNSSEC_INVALID; + return 0; + } + if (r == 1) { + /* If we stripped a single label, then let's see if that maybe was "*". If so, we are not really + * synthesized from a wildcard, we are the wildcard itself. Treat that like a normal name. */ + r = dns_name_startswith(name, "*"); + if (r < 0) + return r; + if (r > 0) + source = name; + + wildcard = r == 0; + } else + wildcard = r > 0; + + /* Collect all relevant RRs in a single array, so that we can look at the RRset */ + list = newa(DnsResourceRecord *, dns_answer_size(a)); + + DNS_ANSWER_FOREACH(rr, a) { + r = dns_resource_key_equal(key, rr->key); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We need the wire format for ordering, and digest calculation */ + r = dns_resource_record_to_wire_format(rr, true); + if (r < 0) + return r; + + list[n++] = rr; + + if (n > VERIFY_RRS_MAX) + return -E2BIG; + } + + if (n <= 0) + return -ENODATA; + + /* Bring the RRs into canonical order */ + qsort_safe(list, n, sizeof(DnsResourceRecord*), rr_compare); + + /* OK, the RRs are now in canonical order. Let's calculate the digest */ + initialize_libgcrypt(); + + hash_size = gcry_md_get_algo_dlen(md_algorithm); + assert(hash_size > 0); + + gcry_md_open(&md, md_algorithm, 0); + if (!md) + return -EIO; + + md_add_uint16(md, rrsig->rrsig.type_covered); + md_add_uint8(md, rrsig->rrsig.algorithm); + md_add_uint8(md, rrsig->rrsig.labels); + md_add_uint32(md, rrsig->rrsig.original_ttl); + md_add_uint32(md, rrsig->rrsig.expiration); + md_add_uint32(md, rrsig->rrsig.inception); + md_add_uint16(md, rrsig->rrsig.key_tag); + + r = dns_name_to_wire_format(rrsig->rrsig.signer, wire_format_name, sizeof(wire_format_name), true); + if (r < 0) + goto finish; + gcry_md_write(md, wire_format_name, r); + + /* Convert the source of synthesis into wire format */ + r = dns_name_to_wire_format(source, wire_format_name, sizeof(wire_format_name), true); + if (r < 0) + goto finish; + + for (k = 0; k < n; k++) { + size_t l; + + rr = list[k]; + + /* Hash the source of synthesis. If this is a wildcard, then prefix it with the *. label */ + if (wildcard) + gcry_md_write(md, (uint8_t[]) { 1, '*'}, 2); + gcry_md_write(md, wire_format_name, r); + + md_add_uint16(md, rr->key->type); + md_add_uint16(md, rr->key->class); + md_add_uint32(md, rrsig->rrsig.original_ttl); + + l = DNS_RESOURCE_RECORD_RDATA_SIZE(rr); + assert(l <= 0xFFFF); + + md_add_uint16(md, (uint16_t) l); + gcry_md_write(md, DNS_RESOURCE_RECORD_RDATA(rr), l); + } + + hash = gcry_md_read(md, 0); + if (!hash) { + r = -EIO; + goto finish; + } + + switch (rrsig->rrsig.algorithm) { + + case DNSSEC_ALGORITHM_RSASHA1: + case DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1: + case DNSSEC_ALGORITHM_RSASHA256: + case DNSSEC_ALGORITHM_RSASHA512: + r = dnssec_rsa_verify( + gcry_md_algo_name(md_algorithm), + hash, hash_size, + rrsig, + dnskey); + break; + + case DNSSEC_ALGORITHM_ECDSAP256SHA256: + case DNSSEC_ALGORITHM_ECDSAP384SHA384: + r = dnssec_ecdsa_verify( + gcry_md_algo_name(md_algorithm), + rrsig->rrsig.algorithm, + hash, hash_size, + rrsig, + dnskey); + break; + } + + if (r < 0) + goto finish; + + /* Now, fix the ttl, expiry, and remember the synthesizing source and the signer */ + if (r > 0) + dnssec_fix_rrset_ttl(list, n, rrsig, realtime); + + if (r == 0) + *result = DNSSEC_INVALID; + else if (wildcard) + *result = DNSSEC_VALIDATED_WILDCARD; + else + *result = DNSSEC_VALIDATED; + + r = 0; + +finish: + gcry_md_close(md); + return r; +} + +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { + + assert(rrsig); + assert(dnskey); + + /* Checks if the specified DNSKEY RR matches the key used for + * the signature in the specified RRSIG RR */ + + if (rrsig->key->type != DNS_TYPE_RRSIG) + return -EINVAL; + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return 0; + if (dnskey->key->class != rrsig->key->class) + return 0; + if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) + return 0; + if (!revoked_ok && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) + return 0; + if (dnskey->dnskey.protocol != 3) + return 0; + if (dnskey->dnskey.algorithm != rrsig->rrsig.algorithm) + return 0; + + if (dnssec_keytag(dnskey, false) != rrsig->rrsig.key_tag) + return 0; + + return dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), rrsig->rrsig.signer); +} + +int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { + assert(key); + assert(rrsig); + + /* Checks if the specified RRSIG RR protects the RRSet of the specified RR key. */ + + if (rrsig->key->type != DNS_TYPE_RRSIG) + return 0; + if (rrsig->key->class != key->class) + return 0; + if (rrsig->rrsig.type_covered != key->type) + return 0; + + return dns_name_equal(DNS_RESOURCE_KEY_NAME(rrsig->key), DNS_RESOURCE_KEY_NAME(key)); +} + +int dnssec_verify_rrset_search( + DnsAnswer *a, + const DnsResourceKey *key, + DnsAnswer *validated_dnskeys, + usec_t realtime, + DnssecResult *result, + DnsResourceRecord **ret_rrsig) { + + bool found_rrsig = false, found_invalid = false, found_expired_rrsig = false, found_unsupported_algorithm = false; + DnsResourceRecord *rrsig; + int r; + + assert(key); + assert(result); + + /* Verifies all RRs from "a" that match the key "key" against DNSKEYs in "validated_dnskeys" */ + + if (!a || a->n_rrs <= 0) + return -ENODATA; + + /* Iterate through each RRSIG RR. */ + DNS_ANSWER_FOREACH(rrsig, a) { + DnsResourceRecord *dnskey; + DnsAnswerFlags flags; + + /* Is this an RRSIG RR that applies to RRs matching our key? */ + r = dnssec_key_match_rrsig(key, rrsig); + if (r < 0) + return r; + if (r == 0) + continue; + + found_rrsig = true; + + /* Look for a matching key */ + DNS_ANSWER_FOREACH_FLAGS(dnskey, flags, validated_dnskeys) { + DnssecResult one_result; + + if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) + continue; + + /* Is this a DNSKEY RR that matches they key of our RRSIG? */ + r = dnssec_rrsig_match_dnskey(rrsig, dnskey, false); + if (r < 0) + return r; + if (r == 0) + continue; + + /* Take the time here, if it isn't set yet, so + * that we do all validations with the same + * time. */ + if (realtime == USEC_INFINITY) + realtime = now(CLOCK_REALTIME); + + /* Yay, we found a matching RRSIG with a matching + * DNSKEY, awesome. Now let's verify all entries of + * the RRSet against the RRSIG and DNSKEY + * combination. */ + + r = dnssec_verify_rrset(a, key, rrsig, dnskey, realtime, &one_result); + if (r < 0) + return r; + + switch (one_result) { + + case DNSSEC_VALIDATED: + case DNSSEC_VALIDATED_WILDCARD: + /* Yay, the RR has been validated, + * return immediately, but fix up the expiry */ + if (ret_rrsig) + *ret_rrsig = rrsig; + + *result = one_result; + return 0; + + case DNSSEC_INVALID: + /* If the signature is invalid, let's try another + key and/or signature. After all they + key_tags and stuff are not unique, and + might be shared by multiple keys. */ + found_invalid = true; + continue; + + case DNSSEC_UNSUPPORTED_ALGORITHM: + /* If the key algorithm is + unsupported, try another + RRSIG/DNSKEY pair, but remember we + encountered this, so that we can + return a proper error when we + encounter nothing better. */ + found_unsupported_algorithm = true; + continue; + + case DNSSEC_SIGNATURE_EXPIRED: + /* If the signature is expired, try + another one, but remember it, so + that we can return this */ + found_expired_rrsig = true; + continue; + + default: + assert_not_reached("Unexpected DNSSEC validation result"); + } + } + } + + if (found_expired_rrsig) + *result = DNSSEC_SIGNATURE_EXPIRED; + else if (found_unsupported_algorithm) + *result = DNSSEC_UNSUPPORTED_ALGORITHM; + else if (found_invalid) + *result = DNSSEC_INVALID; + else if (found_rrsig) + *result = DNSSEC_MISSING_KEY; + else + *result = DNSSEC_NO_SIGNATURE; + + if (ret_rrsig) + *ret_rrsig = NULL; + + return 0; +} + +int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { + DnsResourceRecord *rr; + int r; + + /* Checks whether there's at least one RRSIG in 'a' that proctects RRs of the specified key */ + + DNS_ANSWER_FOREACH(rr, a) { + r = dnssec_key_match_rrsig(key, rr); + if (r < 0) + return r; + if (r > 0) + return 1; + } + + return 0; +} + +static int digest_to_gcrypt_md(uint8_t algorithm) { + + /* Translates a DNSSEC digest algorithm into a gcrypt digest identifier */ + + switch (algorithm) { + + case DNSSEC_DIGEST_SHA1: + return GCRY_MD_SHA1; + + case DNSSEC_DIGEST_SHA256: + return GCRY_MD_SHA256; + + case DNSSEC_DIGEST_SHA384: + return GCRY_MD_SHA384; + + default: + return -EOPNOTSUPP; + } +} + +int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { + char owner_name[DNSSEC_CANONICAL_HOSTNAME_MAX]; + gcry_md_hd_t md = NULL; + size_t hash_size; + int md_algorithm, r; + void *result; + + assert(dnskey); + assert(ds); + + /* Implements DNSKEY verification by a DS, according to RFC 4035, section 5.2 */ + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return -EINVAL; + if (ds->key->type != DNS_TYPE_DS) + return -EINVAL; + if ((dnskey->dnskey.flags & DNSKEY_FLAG_ZONE_KEY) == 0) + return -EKEYREJECTED; + if (!mask_revoke && (dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE)) + return -EKEYREJECTED; + if (dnskey->dnskey.protocol != 3) + return -EKEYREJECTED; + + if (dnskey->dnskey.algorithm != ds->ds.algorithm) + return 0; + if (dnssec_keytag(dnskey, mask_revoke) != ds->ds.key_tag) + return 0; + + initialize_libgcrypt(); + + md_algorithm = digest_to_gcrypt_md(ds->ds.digest_type); + if (md_algorithm < 0) + return md_algorithm; + + hash_size = gcry_md_get_algo_dlen(md_algorithm); + assert(hash_size > 0); + + if (ds->ds.digest_size != hash_size) + return 0; + + r = dnssec_canonicalize(DNS_RESOURCE_KEY_NAME(dnskey->key), owner_name, sizeof(owner_name)); + if (r < 0) + return r; + + gcry_md_open(&md, md_algorithm, 0); + if (!md) + return -EIO; + + gcry_md_write(md, owner_name, r); + if (mask_revoke) + md_add_uint16(md, dnskey->dnskey.flags & ~DNSKEY_FLAG_REVOKE); + else + md_add_uint16(md, dnskey->dnskey.flags); + md_add_uint8(md, dnskey->dnskey.protocol); + md_add_uint8(md, dnskey->dnskey.algorithm); + gcry_md_write(md, dnskey->dnskey.key, dnskey->dnskey.key_size); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + + r = memcmp(result, ds->ds.digest, ds->ds.digest_size) != 0; + +finish: + gcry_md_close(md); + return r; +} + +int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { + DnsResourceRecord *ds; + DnsAnswerFlags flags; + int r; + + assert(dnskey); + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return 0; + + DNS_ANSWER_FOREACH_FLAGS(ds, flags, validated_ds) { + + if ((flags & DNS_ANSWER_AUTHENTICATED) == 0) + continue; + + if (ds->key->type != DNS_TYPE_DS) + continue; + if (ds->key->class != dnskey->key->class) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dnskey->key), DNS_RESOURCE_KEY_NAME(ds->key)); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_verify_dnskey_by_ds(dnskey, ds, false); + if (IN_SET(r, -EKEYREJECTED, -EOPNOTSUPP)) + return 0; /* The DNSKEY is revoked or otherwise invalid, or we don't support the digest algorithm */ + if (r < 0) + return r; + if (r > 0) + return 1; + } + + return 0; +} + +static int nsec3_hash_to_gcrypt_md(uint8_t algorithm) { + + /* Translates a DNSSEC NSEC3 hash algorithm into a gcrypt digest identifier */ + + switch (algorithm) { + + case NSEC3_ALGORITHM_SHA1: + return GCRY_MD_SHA1; + + default: + return -EOPNOTSUPP; + } +} + +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { + uint8_t wire_format[DNS_WIRE_FOMAT_HOSTNAME_MAX]; + gcry_md_hd_t md = NULL; + size_t hash_size; + int algorithm; + void *result; + unsigned k; + int r; + + assert(nsec3); + assert(name); + assert(ret); + + if (nsec3->key->type != DNS_TYPE_NSEC3) + return -EINVAL; + + if (nsec3->nsec3.iterations > NSEC3_ITERATIONS_MAX) { + log_debug("Ignoring NSEC3 RR %s with excessive number of iterations.", dns_resource_record_to_string(nsec3)); + return -EOPNOTSUPP; + } + + algorithm = nsec3_hash_to_gcrypt_md(nsec3->nsec3.algorithm); + if (algorithm < 0) + return algorithm; + + initialize_libgcrypt(); + + hash_size = gcry_md_get_algo_dlen(algorithm); + assert(hash_size > 0); + + if (nsec3->nsec3.next_hashed_name_size != hash_size) + return -EINVAL; + + r = dns_name_to_wire_format(name, wire_format, sizeof(wire_format), true); + if (r < 0) + return r; + + gcry_md_open(&md, algorithm, 0); + if (!md) + return -EIO; + + gcry_md_write(md, wire_format, r); + gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + + for (k = 0; k < nsec3->nsec3.iterations; k++) { + uint8_t tmp[hash_size]; + memcpy(tmp, result, hash_size); + + gcry_md_reset(md); + gcry_md_write(md, tmp, hash_size); + gcry_md_write(md, nsec3->nsec3.salt, nsec3->nsec3.salt_size); + + result = gcry_md_read(md, 0); + if (!result) { + r = -EIO; + goto finish; + } + } + + memcpy(ret, result, hash_size); + r = (int) hash_size; + +finish: + gcry_md_close(md); + return r; +} + +static int nsec3_is_good(DnsResourceRecord *rr, DnsResourceRecord *nsec3) { + const char *a, *b; + int r; + + assert(rr); + + if (rr->key->type != DNS_TYPE_NSEC3) + return 0; + + /* RFC 5155, Section 8.2 says we MUST ignore NSEC3 RRs with flags != 0 or 1 */ + if (!IN_SET(rr->nsec3.flags, 0, 1)) + return 0; + + /* Ignore NSEC3 RRs whose algorithm we don't know */ + if (nsec3_hash_to_gcrypt_md(rr->nsec3.algorithm) < 0) + return 0; + /* Ignore NSEC3 RRs with an excessive number of required iterations */ + if (rr->nsec3.iterations > NSEC3_ITERATIONS_MAX) + return 0; + + /* Ignore NSEC3 RRs generated from wildcards. If these NSEC3 RRs weren't correctly signed we can't make this + * check (since rr->n_skip_labels_source is -1), but that's OK, as we won't trust them anyway in that case. */ + if (rr->n_skip_labels_source != 0 && rr->n_skip_labels_source != (unsigned) -1) + return 0; + /* Ignore NSEC3 RRs that are located anywhere else than one label below the zone */ + if (rr->n_skip_labels_signer != 1 && rr->n_skip_labels_signer != (unsigned) -1) + return 0; + + if (!nsec3) + return 1; + + /* If a second NSEC3 RR is specified, also check if they are from the same zone. */ + + if (nsec3 == rr) /* Shortcut */ + return 1; + + if (rr->key->class != nsec3->key->class) + return 0; + if (rr->nsec3.algorithm != nsec3->nsec3.algorithm) + return 0; + if (rr->nsec3.iterations != nsec3->nsec3.iterations) + return 0; + if (rr->nsec3.salt_size != nsec3->nsec3.salt_size) + return 0; + if (memcmp(rr->nsec3.salt, nsec3->nsec3.salt, rr->nsec3.salt_size) != 0) + return 0; + + a = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_parent(&a); /* strip off hash */ + if (r < 0) + return r; + if (r == 0) + return 0; + + b = DNS_RESOURCE_KEY_NAME(nsec3->key); + r = dns_name_parent(&b); /* strip off hash */ + if (r < 0) + return r; + if (r == 0) + return 0; + + /* Make sure both have the same parent */ + return dns_name_equal(a, b); +} + +static int nsec3_hashed_domain_format(const uint8_t *hashed, size_t hashed_size, const char *zone, char **ret) { + _cleanup_free_ char *l = NULL; + char *j; + + assert(hashed); + assert(hashed_size > 0); + assert(zone); + assert(ret); + + l = base32hexmem(hashed, hashed_size, false); + if (!l) + return -ENOMEM; + + j = strjoin(l, ".", zone, NULL); + if (!j) + return -ENOMEM; + + *ret = j; + return (int) hashed_size; +} + +static int nsec3_hashed_domain_make(DnsResourceRecord *nsec3, const char *domain, const char *zone, char **ret) { + uint8_t hashed[DNSSEC_HASH_SIZE_MAX]; + int hashed_size; + + assert(nsec3); + assert(domain); + assert(zone); + assert(ret); + + hashed_size = dnssec_nsec3_hash(nsec3, domain, hashed); + if (hashed_size < 0) + return hashed_size; + + return nsec3_hashed_domain_format(hashed, (size_t) hashed_size, zone, ret); +} + +/* See RFC 5155, Section 8 + * First try to find a NSEC3 record that matches our query precisely, if that fails, find the closest + * enclosure. Secondly, find a proof that there is no closer enclosure and either a proof that there + * is no wildcard domain as a direct descendant of the closest enclosure, or find an NSEC3 record that + * matches the wildcard domain. + * + * Based on this we can prove either the existence of the record in @key, or NXDOMAIN or NODATA, or + * that there is no proof either way. The latter is the case if a the proof of non-existence of a given + * name uses an NSEC3 record with the opt-out bit set. Lastly, if we are given insufficient NSEC3 records + * to conclude anything we indicate this by returning NO_RR. */ +static int dnssec_test_nsec3(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { + _cleanup_free_ char *next_closer_domain = NULL, *wildcard_domain = NULL; + const char *zone, *p, *pp = NULL, *wildcard; + DnsResourceRecord *rr, *enclosure_rr, *zone_rr, *wildcard_rr = NULL; + DnsAnswerFlags flags; + int hashed_size, r; + bool a, no_closer = false, no_wildcard = false, optout = false; + + assert(key); + assert(result); + + /* First step, find the zone name and the NSEC3 parameters of the zone. + * it is sufficient to look for the longest common suffix we find with + * any NSEC3 RR in the response. Any NSEC3 record will do as all NSEC3 + * records from a given zone in a response must use the same + * parameters. */ + zone = DNS_RESOURCE_KEY_NAME(key); + for (;;) { + DNS_ANSWER_FOREACH_FLAGS(zone_rr, flags, answer) { + r = nsec3_is_good(zone_rr, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_equal_skip(DNS_RESOURCE_KEY_NAME(zone_rr->key), 1, zone); + if (r < 0) + return r; + if (r > 0) + goto found_zone; + } + + /* Strip one label from the front */ + r = dns_name_parent(&zone); + if (r < 0) + return r; + if (r == 0) + break; + } + + *result = DNSSEC_NSEC_NO_RR; + return 0; + +found_zone: + /* Second step, find the closest encloser NSEC3 RR in 'answer' that matches 'key' */ + p = DNS_RESOURCE_KEY_NAME(key); + for (;;) { + _cleanup_free_ char *hashed_domain = NULL; + + hashed_size = nsec3_hashed_domain_make(zone_rr, p, zone, &hashed_domain); + if (hashed_size == -EOPNOTSUPP) { + *result = DNSSEC_NSEC_UNSUPPORTED_ALGORITHM; + return 0; + } + if (hashed_size < 0) + return hashed_size; + + DNS_ANSWER_FOREACH_FLAGS(enclosure_rr, flags, answer) { + + r = nsec3_is_good(enclosure_rr, zone_rr); + if (r < 0) + return r; + if (r == 0) + continue; + + if (enclosure_rr->nsec3.next_hashed_name_size != (size_t) hashed_size) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(enclosure_rr->key), hashed_domain); + if (r < 0) + return r; + if (r > 0) { + a = flags & DNS_ANSWER_AUTHENTICATED; + goto found_closest_encloser; + } + } + + /* We didn't find the closest encloser with this name, + * but let's remember this domain name, it might be + * the next closer name */ + + pp = p; + + /* Strip one label from the front */ + r = dns_name_parent(&p); + if (r < 0) + return r; + if (r == 0) + break; + } + + *result = DNSSEC_NSEC_NO_RR; + return 0; + +found_closest_encloser: + /* We found a closest encloser in 'p'; next closer is 'pp' */ + + if (!pp) { + /* We have an exact match! If we area looking for a DS RR, then we must insist that we got the NSEC3 RR + * from the parent. Otherwise the one from the child. Do so, by checking whether SOA and NS are + * appropriately set. */ + + if (key->type == DNS_TYPE_DS) { + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + } else { + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && + !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + } + + /* No next closer NSEC3 RR. That means there's a direct NSEC3 RR for our key. */ + if (bitmap_isset(enclosure_rr->nsec3.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + + if (authenticated) + *authenticated = a; + if (ttl) + *ttl = enclosure_rr->ttl; + + return 0; + } + + /* Ensure this is not a DNAME domain, see RFC5155, section 8.3. */ + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_DNAME)) + return -EBADMSG; + + /* Ensure that this data is from the delegated domain + * (i.e. originates from the "lower" DNS server), and isn't + * just glue records (i.e. doesn't originate from the "upper" + * DNS server). */ + if (bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_NS) && + !bitmap_isset(enclosure_rr->nsec3.types, DNS_TYPE_SOA)) + return -EBADMSG; + + /* Prove that there is no next closer and whether or not there is a wildcard domain. */ + + wildcard = strjoina("*.", p); + r = nsec3_hashed_domain_make(enclosure_rr, wildcard, zone, &wildcard_domain); + if (r < 0) + return r; + if (r != hashed_size) + return -EBADMSG; + + r = nsec3_hashed_domain_make(enclosure_rr, pp, zone, &next_closer_domain); + if (r < 0) + return r; + if (r != hashed_size) + return -EBADMSG; + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + _cleanup_free_ char *next_hashed_domain = NULL; + + r = nsec3_is_good(rr, zone_rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = nsec3_hashed_domain_format(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, zone, &next_hashed_domain); + if (r < 0) + return r; + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), next_closer_domain, next_hashed_domain); + if (r < 0) + return r; + if (r > 0) { + if (rr->nsec3.flags & 1) + optout = true; + + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + no_closer = true; + } + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain); + if (r < 0) + return r; + if (r > 0) { + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + wildcard_rr = rr; + } + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), wildcard_domain, next_hashed_domain); + if (r < 0) + return r; + if (r > 0) { + if (rr->nsec3.flags & 1) + /* This only makes sense if we have a wildcard delegation, which is + * very unlikely, see RFC 4592, Section 4.2, but we cannot rely on + * this not happening, so hence cannot simply conclude NXDOMAIN as + * we would wish */ + optout = true; + + a = a && (flags & DNS_ANSWER_AUTHENTICATED); + + no_wildcard = true; + } + } + + if (wildcard_rr && no_wildcard) + return -EBADMSG; + + if (!no_closer) { + *result = DNSSEC_NSEC_NO_RR; + return 0; + } + + if (wildcard_rr) { + /* A wildcard exists that matches our query. */ + if (optout) + /* This is not specified in any RFC to the best of my knowledge, but + * if the next closer enclosure is covered by an opt-out NSEC3 RR + * it means that we cannot prove that the source of synthesis is + * correct, as there may be a closer match. */ + *result = DNSSEC_NSEC_OPTOUT; + else if (bitmap_isset(wildcard_rr->nsec3.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(wildcard_rr->nsec3.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + } else { + if (optout) + /* The RFC only specifies that we have to care for optout for NODATA for + * DS records. However, children of an insecure opt-out delegation should + * also be considered opt-out, rather than verified NXDOMAIN. + * Note that we do not require a proof of wildcard non-existence if the + * next closer domain is covered by an opt-out, as that would not provide + * any additional information. */ + *result = DNSSEC_NSEC_OPTOUT; + else if (no_wildcard) + *result = DNSSEC_NSEC_NXDOMAIN; + else { + *result = DNSSEC_NSEC_NO_RR; + + return 0; + } + } + + if (authenticated) + *authenticated = a; + + if (ttl) + *ttl = enclosure_rr->ttl; + + return 0; +} + +static int dnssec_nsec_wildcard_equal(DnsResourceRecord *rr, const char *name) { + char label[DNS_LABEL_MAX]; + const char *n; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the specified RR has a name beginning in "*.", and if the rest is a suffix of our name */ + + if (rr->n_skip_labels_source != 1) + return 0; + + n = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_label_unescape(&n, label, sizeof(label)); + if (r <= 0) + return r; + if (r != 1 || label[0] != '*') + return 0; + + return dns_name_endswith(name, n); +} + +static int dnssec_nsec_in_path(DnsResourceRecord *rr, const char *name) { + const char *nn, *common_suffix; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the specified nsec RR indicates that name is an empty non-terminal (ENT) + * + * A couple of examples: + * + * NSEC bar → waldo.foo.bar: indicates that foo.bar exists and is an ENT + * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that xoo.bar and zzz.xoo.bar exist and are ENTs + * NSEC yyy.zzz.xoo.bar → bar: indicates pretty much nothing about ENTs + */ + + /* First, determine parent of next domain. */ + nn = rr->nsec.next_domain_name; + r = dns_name_parent(&nn); + if (r <= 0) + return r; + + /* If the name we just determined is not equal or child of the name we are interested in, then we can't say + * anything at all. */ + r = dns_name_endswith(nn, 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. */ + r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix); + if (r < 0) + return r; + + return dns_name_endswith(name, common_suffix); +} + +static int dnssec_nsec_from_parent_zone(DnsResourceRecord *rr, const char *name) { + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether this NSEC originates to the parent zone or the child zone. */ + + r = dns_name_parent(&name); + if (r <= 0) + return r; + + r = dns_name_equal(name, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r <= 0) + return r; + + /* DNAME, and NS without SOA is an indication for a delegation. */ + if (bitmap_isset(rr->nsec.types, DNS_TYPE_DNAME)) + return 1; + + if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) + return 1; + + return 0; +} + +static int dnssec_nsec_covers(DnsResourceRecord *rr, const char *name) { + const char *common_suffix, *p; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the "Next Closer" is witin the space covered by the specified RR. */ + + r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix); + if (r < 0) + return r; + + for (;;) { + p = name; + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + return 0; + + r = dns_name_equal(name, common_suffix); + if (r < 0) + return r; + if (r > 0) + break; + } + + /* p is now the "Next Closer". */ + + return dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), p, rr->nsec.next_domain_name); +} + +static int dnssec_nsec_covers_wildcard(DnsResourceRecord *rr, const char *name) { + const char *common_suffix, *wc; + int r; + + assert(rr); + assert(rr->key->type == DNS_TYPE_NSEC); + + /* Checks whether the "Wildcard at the Closest Encloser" is within the space covered by the specified + * RR. Specifically, checks whether 'name' has the common suffix of the NSEC RR's owner and next names as + * suffix, and whether the NSEC covers the name generated by that suffix prepended with an asterisk label. + * + * NSEC bar → waldo.foo.bar: indicates that *.bar and *.foo.bar do not exist + * NSEC waldo.foo.bar → yyy.zzz.xoo.bar: indicates that *.xoo.bar and *.zzz.xoo.bar do not exist (and more ...) + * NSEC yyy.zzz.xoo.bar → bar: indicates that a number of wildcards don#t exist either... + */ + + r = dns_name_common_suffix(DNS_RESOURCE_KEY_NAME(rr->key), rr->nsec.next_domain_name, &common_suffix); + if (r < 0) + return r; + + /* If the common suffix is not shared by the name we are interested in, it has nothing to say for us. */ + r = dns_name_endswith(name, common_suffix); + if (r <= 0) + return r; + + wc = strjoina("*.", common_suffix, NULL); + return dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), wc, rr->nsec.next_domain_name); +} + +int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { + bool have_nsec3 = false, covering_rr_authenticated = false, wildcard_rr_authenticated = false; + DnsResourceRecord *rr, *covering_rr = NULL, *wildcard_rr = NULL; + DnsAnswerFlags flags; + const char *name; + int r; + + assert(key); + assert(result); + + /* Look for any NSEC/NSEC3 RRs that say something about the specified key. */ + + name = DNS_RESOURCE_KEY_NAME(key); + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + + if (rr->key->class != key->class) + continue; + + have_nsec3 = have_nsec3 || (rr->key->type == DNS_TYPE_NSEC3); + + if (rr->key->type != DNS_TYPE_NSEC) + continue; + + /* The following checks only make sense for NSEC RRs that are not expanded from a wildcard */ + r = dns_resource_record_is_synthetic(rr); + if (r < 0) + return r; + if (r > 0) + continue; + + /* Check if this is a direct match. If so, we have encountered a NODATA case */ + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), name); + if (r < 0) + return r; + if (r == 0) { + /* If it's not a direct match, maybe it's a wild card match? */ + r = dnssec_nsec_wildcard_equal(rr, name); + if (r < 0) + return r; + } + if (r > 0) { + if (key->type == DNS_TYPE_DS) { + /* If we look for a DS RR and the server sent us the NSEC RR of the child zone + * we have a problem. For DS RRs we want the NSEC RR from the parent */ + if (bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) + continue; + } else { + /* For all RR types, ensure that if NS is set SOA is set too, so that we know + * we got the child's NSEC. */ + if (bitmap_isset(rr->nsec.types, DNS_TYPE_NS) && + !bitmap_isset(rr->nsec.types, DNS_TYPE_SOA)) + continue; + } + + if (bitmap_isset(rr->nsec.types, key->type)) + *result = DNSSEC_NSEC_FOUND; + else if (bitmap_isset(rr->nsec.types, DNS_TYPE_CNAME)) + *result = DNSSEC_NSEC_CNAME; + else + *result = DNSSEC_NSEC_NODATA; + + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + if (ttl) + *ttl = rr->ttl; + + return 0; + } + + /* Check if the name we are looking for is an empty non-terminal within the owner or next name + * of the NSEC RR. */ + r = dnssec_nsec_in_path(rr, name); + if (r < 0) + return r; + if (r > 0) { + *result = DNSSEC_NSEC_NODATA; + + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + if (ttl) + *ttl = rr->ttl; + + return 0; + } + + /* The following two "covering" checks, are not useful if the NSEC is from the parent */ + r = dnssec_nsec_from_parent_zone(rr, name); + if (r < 0) + return r; + if (r > 0) + continue; + + /* Check if this NSEC RR proves the absence of an explicit RR under this name */ + r = dnssec_nsec_covers(rr, name); + if (r < 0) + return r; + if (r > 0 && (!covering_rr || !covering_rr_authenticated)) { + covering_rr = rr; + covering_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; + } + + /* Check if this NSEC RR proves the absence of a wildcard RR under this name */ + r = dnssec_nsec_covers_wildcard(rr, name); + if (r < 0) + return r; + if (r > 0 && (!wildcard_rr || !wildcard_rr_authenticated)) { + wildcard_rr = rr; + wildcard_rr_authenticated = flags & DNS_ANSWER_AUTHENTICATED; + } + } + + if (covering_rr && wildcard_rr) { + /* If we could prove that neither the name itself, nor the wildcard at the closest encloser exists, we + * proved the NXDOMAIN case. */ + *result = DNSSEC_NSEC_NXDOMAIN; + + if (authenticated) + *authenticated = covering_rr_authenticated && wildcard_rr_authenticated; + if (ttl) + *ttl = MIN(covering_rr->ttl, wildcard_rr->ttl); + + return 0; + } + + /* OK, this was not sufficient. Let's see if NSEC3 can help. */ + if (have_nsec3) + return dnssec_test_nsec3(answer, key, result, authenticated, ttl); + + /* No approproate NSEC RR found, report this. */ + *result = DNSSEC_NSEC_NO_RR; + return 0; +} + +static int dnssec_nsec_test_enclosed(DnsAnswer *answer, uint16_t type, const char *name, const char *zone, bool *authenticated) { + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int r; + + assert(name); + assert(zone); + + /* Checks whether there's an NSEC/NSEC3 that proves that the specified 'name' is non-existing in the specified + * 'zone'. The 'zone' must be a suffix of the 'name'. */ + + DNS_ANSWER_FOREACH_FLAGS(rr, flags, answer) { + bool found = false; + + if (rr->key->type != type && type != DNS_TYPE_ANY) + continue; + + switch (rr->key->type) { + + case DNS_TYPE_NSEC: + + /* We only care for NSEC RRs from the indicated zone */ + r = dns_resource_record_is_signer(rr, zone); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), name, rr->nsec.next_domain_name); + if (r < 0) + return r; + + found = r > 0; + break; + + case DNS_TYPE_NSEC3: { + _cleanup_free_ char *hashed_domain = NULL, *next_hashed_domain = NULL; + + /* We only care for NSEC3 RRs from the indicated zone */ + r = dns_resource_record_is_signer(rr, zone); + if (r < 0) + return r; + if (r == 0) + continue; + + r = nsec3_is_good(rr, NULL); + if (r < 0) + return r; + if (r == 0) + break; + + /* Format the domain we are testing with the NSEC3 RR's hash function */ + r = nsec3_hashed_domain_make( + rr, + name, + zone, + &hashed_domain); + if (r < 0) + return r; + if ((size_t) r != rr->nsec3.next_hashed_name_size) + break; + + /* Format the NSEC3's next hashed name as proper domain name */ + r = nsec3_hashed_domain_format( + rr->nsec3.next_hashed_name, + rr->nsec3.next_hashed_name_size, + zone, + &next_hashed_domain); + if (r < 0) + return r; + + r = dns_name_between(DNS_RESOURCE_KEY_NAME(rr->key), hashed_domain, next_hashed_domain); + if (r < 0) + return r; + + found = r > 0; + break; + } + + default: + continue; + } + + if (found) { + if (authenticated) + *authenticated = flags & DNS_ANSWER_AUTHENTICATED; + return 1; + } + } + + return 0; +} + +static int dnssec_test_positive_wildcard_nsec3( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *authenticated) { + + const char *next_closer = NULL; + int r; + + /* Run a positive NSEC3 wildcard proof. Specifically: + * + * A proof that the the "next closer" of the generating wildcard does not exist. + * + * Note a key difference between the NSEC3 and NSEC versions of the proof. NSEC RRs don't have to exist for + * empty non-transients. NSEC3 RRs however have to. This means it's sufficient to check if the next closer name + * exists for the NSEC3 RR and we are done. + * + * To prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f all we have to check is that + * c.d.e.f does not exist. */ + + for (;;) { + next_closer = name; + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + return 0; + + r = dns_name_equal(name, source); + if (r < 0) + return r; + if (r > 0) + break; + } + + return dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC3, next_closer, zone, authenticated); +} + +static int dnssec_test_positive_wildcard_nsec( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *_authenticated) { + + bool authenticated = true; + int r; + + /* Run a positive NSEC wildcard proof. Specifically: + * + * A proof that there's neither a wildcard name nor a non-wildcard name that is a suffix of the name "name" and + * a prefix of the synthesizing source "source" in the zone "zone". + * + * See RFC 5155, Section 8.8 and RFC 4035, Section 5.3.4 + * + * Note that if we want to prove that a.b.c.d.e.f is rightfully synthesized from a wildcard *.d.e.f, then we + * have to prove that none of the following exist: + * + * 1) a.b.c.d.e.f + * 2) *.b.c.d.e.f + * 3) b.c.d.e.f + * 4) *.c.d.e.f + * 5) c.d.e.f + * + */ + + for (;;) { + _cleanup_free_ char *wc = NULL; + bool a = false; + + /* Check if there's an NSEC or NSEC3 RR that proves that the mame we determined is really non-existing, + * i.e between the owner name and the next name of an NSEC RR. */ + r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, name, zone, &a); + if (r <= 0) + return r; + + authenticated = authenticated && a; + + /* Strip one label off */ + r = dns_name_parent(&name); + if (r <= 0) + return r; + + /* Did we reach the source of synthesis? */ + r = dns_name_equal(name, source); + if (r < 0) + return r; + if (r > 0) { + /* Successful exit */ + *_authenticated = authenticated; + return 1; + } + + /* Safety check, that the source of synthesis is still our suffix */ + r = dns_name_endswith(name, source); + if (r < 0) + return r; + if (r == 0) + return -EBADMSG; + + /* Replace the label we stripped off with an asterisk */ + wc = strappend("*.", name); + if (!wc) + return -ENOMEM; + + /* And check if the proof holds for the asterisk name, too */ + r = dnssec_nsec_test_enclosed(answer, DNS_TYPE_NSEC, wc, zone, &a); + if (r <= 0) + return r; + + authenticated = authenticated && a; + /* In the next iteration we'll check the non-asterisk-prefixed version */ + } +} + +int dnssec_test_positive_wildcard( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *authenticated) { + + int r; + + assert(name); + assert(source); + assert(zone); + assert(authenticated); + + r = dns_answer_contains_zone_nsec3(answer, zone); + if (r < 0) + return r; + if (r > 0) + return dnssec_test_positive_wildcard_nsec3(answer, name, source, zone, authenticated); + else + return dnssec_test_positive_wildcard_nsec(answer, name, source, zone, authenticated); +} + +#else + +int dnssec_verify_rrset( + DnsAnswer *a, + const DnsResourceKey *key, + DnsResourceRecord *rrsig, + DnsResourceRecord *dnskey, + usec_t realtime, + DnssecResult *result) { + + return -EOPNOTSUPP; +} + +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok) { + + return -EOPNOTSUPP; +} + +int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig) { + + return -EOPNOTSUPP; +} + +int dnssec_verify_rrset_search( + DnsAnswer *a, + const DnsResourceKey *key, + DnsAnswer *validated_dnskeys, + usec_t realtime, + DnssecResult *result, + DnsResourceRecord **ret_rrsig) { + + return -EOPNOTSUPP; +} + +int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key) { + + return -EOPNOTSUPP; +} + +int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke) { + + return -EOPNOTSUPP; +} + +int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds) { + + return -EOPNOTSUPP; +} + +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret) { + + return -EOPNOTSUPP; +} + +int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl) { + + return -EOPNOTSUPP; +} + +int dnssec_test_positive_wildcard( + DnsAnswer *answer, + const char *name, + const char *source, + const char *zone, + bool *authenticated) { + + return -EOPNOTSUPP; +} + +#endif + +static const char* const dnssec_result_table[_DNSSEC_RESULT_MAX] = { + [DNSSEC_VALIDATED] = "validated", + [DNSSEC_VALIDATED_WILDCARD] = "validated-wildcard", + [DNSSEC_INVALID] = "invalid", + [DNSSEC_SIGNATURE_EXPIRED] = "signature-expired", + [DNSSEC_UNSUPPORTED_ALGORITHM] = "unsupported-algorithm", + [DNSSEC_NO_SIGNATURE] = "no-signature", + [DNSSEC_MISSING_KEY] = "missing-key", + [DNSSEC_UNSIGNED] = "unsigned", + [DNSSEC_FAILED_AUXILIARY] = "failed-auxiliary", + [DNSSEC_NSEC_MISMATCH] = "nsec-mismatch", + [DNSSEC_INCOMPATIBLE_SERVER] = "incompatible-server", +}; +DEFINE_STRING_TABLE_LOOKUP(dnssec_result, DnssecResult); + +static const char* const dnssec_verdict_table[_DNSSEC_VERDICT_MAX] = { + [DNSSEC_SECURE] = "secure", + [DNSSEC_INSECURE] = "insecure", + [DNSSEC_BOGUS] = "bogus", + [DNSSEC_INDETERMINATE] = "indeterminate", +}; +DEFINE_STRING_TABLE_LOOKUP(dnssec_verdict, DnssecVerdict); diff --git a/src/resolve/src/resolved-dns-dnssec.h b/src/resolve/src/resolved-dns-dnssec.h new file mode 100644 index 0000000000..77bd4d71bf --- /dev/null +++ b/src/resolve/src/resolved-dns-dnssec.h @@ -0,0 +1,102 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +typedef enum DnssecResult DnssecResult; +typedef enum DnssecVerdict DnssecVerdict; + +#include "dns-domain.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-rr.h" + +enum DnssecResult { + /* These five are returned by dnssec_verify_rrset() */ + DNSSEC_VALIDATED, + DNSSEC_VALIDATED_WILDCARD, /* Validated via a wildcard RRSIG, further NSEC/NSEC3 checks necessary */ + DNSSEC_INVALID, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_UNSUPPORTED_ALGORITHM, + + /* These two are added by dnssec_verify_rrset_search() */ + DNSSEC_NO_SIGNATURE, + DNSSEC_MISSING_KEY, + + /* These two are added by the DnsTransaction logic */ + DNSSEC_UNSIGNED, + DNSSEC_FAILED_AUXILIARY, + DNSSEC_NSEC_MISMATCH, + DNSSEC_INCOMPATIBLE_SERVER, + + _DNSSEC_RESULT_MAX, + _DNSSEC_RESULT_INVALID = -1 +}; + +enum DnssecVerdict { + DNSSEC_SECURE, + DNSSEC_INSECURE, + DNSSEC_BOGUS, + DNSSEC_INDETERMINATE, + + _DNSSEC_VERDICT_MAX, + _DNSSEC_VERDICT_INVALID = -1 +}; + +#define DNSSEC_CANONICAL_HOSTNAME_MAX (DNS_HOSTNAME_MAX + 2) + +/* The longest digest we'll ever generate, of all digest algorithms we support */ +#define DNSSEC_HASH_SIZE_MAX (MAX(20, 32)) + +int dnssec_rrsig_match_dnskey(DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, bool revoked_ok); +int dnssec_key_match_rrsig(const DnsResourceKey *key, DnsResourceRecord *rrsig); + +int dnssec_verify_rrset(DnsAnswer *answer, const DnsResourceKey *key, DnsResourceRecord *rrsig, DnsResourceRecord *dnskey, usec_t realtime, DnssecResult *result); +int dnssec_verify_rrset_search(DnsAnswer *answer, const DnsResourceKey *key, DnsAnswer *validated_dnskeys, usec_t realtime, DnssecResult *result, DnsResourceRecord **rrsig); + +int dnssec_verify_dnskey_by_ds(DnsResourceRecord *dnskey, DnsResourceRecord *ds, bool mask_revoke); +int dnssec_verify_dnskey_by_ds_search(DnsResourceRecord *dnskey, DnsAnswer *validated_ds); + +int dnssec_has_rrsig(DnsAnswer *a, const DnsResourceKey *key); + +uint16_t dnssec_keytag(DnsResourceRecord *dnskey, bool mask_revoke); + +int dnssec_canonicalize(const char *n, char *buffer, size_t buffer_max); + +int dnssec_nsec3_hash(DnsResourceRecord *nsec3, const char *name, void *ret); + +typedef enum DnssecNsecResult { + DNSSEC_NSEC_NO_RR, /* No suitable NSEC/NSEC3 RR found */ + DNSSEC_NSEC_CNAME, /* Didn't find what was asked for, but did find CNAME */ + DNSSEC_NSEC_UNSUPPORTED_ALGORITHM, + DNSSEC_NSEC_NXDOMAIN, + DNSSEC_NSEC_NODATA, + DNSSEC_NSEC_FOUND, + DNSSEC_NSEC_OPTOUT, +} DnssecNsecResult; + +int dnssec_nsec_test(DnsAnswer *answer, DnsResourceKey *key, DnssecNsecResult *result, bool *authenticated, uint32_t *ttl); + + +int dnssec_test_positive_wildcard(DnsAnswer *a, const char *name, const char *source, const char *zone, bool *authenticated); + +const char* dnssec_result_to_string(DnssecResult m) _const_; +DnssecResult dnssec_result_from_string(const char *s) _pure_; + +const char* dnssec_verdict_to_string(DnssecVerdict m) _const_; +DnssecVerdict dnssec_verdict_from_string(const char *s) _pure_; diff --git a/src/resolve/src/resolved-dns-packet.c b/src/resolve/src/resolved-dns-packet.c new file mode 100644 index 0000000000..c940dd8929 --- /dev/null +++ b/src/resolve/src/resolved-dns-packet.c @@ -0,0 +1,2295 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . + ***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "resolved-dns-packet.h" +#include "string-table.h" +#include "strv.h" +#include "unaligned.h" +#include "utf8.h" +#include "util.h" + +#define EDNS0_OPT_DO (1<<15) + +int dns_packet_new(DnsPacket **ret, DnsProtocol protocol, size_t mtu) { + DnsPacket *p; + size_t a; + + assert(ret); + + if (mtu <= UDP_PACKET_HEADER_SIZE) + a = DNS_PACKET_SIZE_START; + else + a = mtu - UDP_PACKET_HEADER_SIZE; + + if (a < DNS_PACKET_HEADER_SIZE) + a = DNS_PACKET_HEADER_SIZE; + + /* round up to next page size */ + a = PAGE_ALIGN(ALIGN(sizeof(DnsPacket)) + a) - ALIGN(sizeof(DnsPacket)); + + /* make sure we never allocate more than useful */ + if (a > DNS_PACKET_SIZE_MAX) + a = DNS_PACKET_SIZE_MAX; + + p = malloc0(ALIGN(sizeof(DnsPacket)) + a); + if (!p) + return -ENOMEM; + + p->size = p->rindex = DNS_PACKET_HEADER_SIZE; + p->allocated = a; + p->protocol = protocol; + p->opt_start = p->opt_size = (size_t) -1; + p->n_ref = 1; + + *ret = p; + + return 0; +} + +void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated) { + + DnsPacketHeader *h; + + assert(p); + + h = DNS_PACKET_HEADER(p); + + switch(p->protocol) { + case DNS_PROTOCOL_LLMNR: + assert(!truncated); + + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* c */, + 0 /* tc */, + 0 /* t */, + 0 /* ra */, + 0 /* ad */, + 0 /* cd */, + 0 /* rcode */)); + break; + + case DNS_PROTOCOL_MDNS: + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* aa */, + truncated /* tc */, + 0 /* rd (ask for recursion) */, + 0 /* ra */, + 0 /* ad */, + 0 /* cd */, + 0 /* rcode */)); + break; + + default: + assert(!truncated); + + h->flags = htobe16(DNS_PACKET_MAKE_FLAGS(0 /* qr */, + 0 /* opcode */, + 0 /* aa */, + 0 /* tc */, + 1 /* rd (ask for recursion) */, + 0 /* ra */, + 0 /* ad */, + dnssec_checking_disabled /* cd */, + 0 /* rcode */)); + } +} + +int dns_packet_new_query(DnsPacket **ret, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled) { + DnsPacket *p; + int r; + + assert(ret); + + r = dns_packet_new(&p, protocol, mtu); + if (r < 0) + return r; + + /* Always set the TC bit to 0 initially. + * If there are multiple packets later, we'll update the bit shortly before sending. + */ + dns_packet_set_flags(p, dnssec_checking_disabled, false); + + *ret = p; + return 0; +} + +DnsPacket *dns_packet_ref(DnsPacket *p) { + + if (!p) + return NULL; + + assert(!p->on_stack); + + assert(p->n_ref > 0); + p->n_ref++; + return p; +} + +static void dns_packet_free(DnsPacket *p) { + char *s; + + assert(p); + + dns_question_unref(p->question); + dns_answer_unref(p->answer); + dns_resource_record_unref(p->opt); + + while ((s = hashmap_steal_first_key(p->names))) + free(s); + hashmap_free(p->names); + + free(p->_data); + + if (!p->on_stack) + free(p); +} + +DnsPacket *dns_packet_unref(DnsPacket *p) { + if (!p) + return NULL; + + assert(p->n_ref > 0); + + dns_packet_unref(p->more); + + if (p->n_ref == 1) + dns_packet_free(p); + else + p->n_ref--; + + return NULL; +} + +int dns_packet_validate(DnsPacket *p) { + assert(p); + + if (p->size < DNS_PACKET_HEADER_SIZE) + return -EBADMSG; + + if (p->size > DNS_PACKET_SIZE_MAX) + return -EBADMSG; + + return 1; +} + +int dns_packet_validate_reply(DnsPacket *p) { + int r; + + assert(p); + + r = dns_packet_validate(p); + if (r < 0) + return r; + + if (DNS_PACKET_QR(p) != 1) + return 0; + + if (DNS_PACKET_OPCODE(p) != 0) + return -EBADMSG; + + switch (p->protocol) { + + case DNS_PROTOCOL_LLMNR: + /* RFC 4795, Section 2.1.1. says to discard all replies with QDCOUNT != 1 */ + if (DNS_PACKET_QDCOUNT(p) != 1) + return -EBADMSG; + + break; + + case DNS_PROTOCOL_MDNS: + /* RFC 6762, Section 18 */ + if (DNS_PACKET_RCODE(p) != 0) + return -EBADMSG; + + break; + + default: + break; + } + + return 1; +} + +int dns_packet_validate_query(DnsPacket *p) { + int r; + + assert(p); + + r = dns_packet_validate(p); + if (r < 0) + return r; + + if (DNS_PACKET_QR(p) != 0) + return 0; + + if (DNS_PACKET_OPCODE(p) != 0) + return -EBADMSG; + + if (DNS_PACKET_TC(p)) + return -EBADMSG; + + switch (p->protocol) { + + case DNS_PROTOCOL_LLMNR: + /* RFC 4795, Section 2.1.1. says to discard all queries with QDCOUNT != 1 */ + if (DNS_PACKET_QDCOUNT(p) != 1) + return -EBADMSG; + + /* RFC 4795, Section 2.1.1. says to discard all queries with ANCOUNT != 0 */ + if (DNS_PACKET_ANCOUNT(p) > 0) + return -EBADMSG; + + /* RFC 4795, Section 2.1.1. says to discard all queries with NSCOUNT != 0 */ + if (DNS_PACKET_NSCOUNT(p) > 0) + return -EBADMSG; + + break; + + case DNS_PROTOCOL_MDNS: + /* RFC 6762, Section 18 */ + if (DNS_PACKET_AA(p) != 0 || + DNS_PACKET_RD(p) != 0 || + DNS_PACKET_RA(p) != 0 || + DNS_PACKET_AD(p) != 0 || + DNS_PACKET_CD(p) != 0 || + DNS_PACKET_RCODE(p) != 0) + return -EBADMSG; + + break; + + default: + break; + } + + return 1; +} + +static int dns_packet_extend(DnsPacket *p, size_t add, void **ret, size_t *start) { + assert(p); + + if (p->size + add > p->allocated) { + size_t a; + + a = PAGE_ALIGN((p->size + add) * 2); + if (a > DNS_PACKET_SIZE_MAX) + a = DNS_PACKET_SIZE_MAX; + + if (p->size + add > a) + return -EMSGSIZE; + + if (p->_data) { + void *d; + + d = realloc(p->_data, a); + if (!d) + return -ENOMEM; + + p->_data = d; + } else { + p->_data = malloc(a); + if (!p->_data) + return -ENOMEM; + + memcpy(p->_data, (uint8_t*) p + ALIGN(sizeof(DnsPacket)), p->size); + memzero((uint8_t*) p->_data + p->size, a - p->size); + } + + p->allocated = a; + } + + if (start) + *start = p->size; + + if (ret) + *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->size; + + p->size += add; + return 0; +} + +void dns_packet_truncate(DnsPacket *p, size_t sz) { + Iterator i; + char *s; + void *n; + + assert(p); + + if (p->size <= sz) + return; + + HASHMAP_FOREACH_KEY(n, s, p->names, i) { + + if (PTR_TO_SIZE(n) < sz) + continue; + + hashmap_remove(p->names, s); + free(s); + } + + p->size = sz; +} + +int dns_packet_append_blob(DnsPacket *p, const void *d, size_t l, size_t *start) { + void *q; + int r; + + assert(p); + + r = dns_packet_extend(p, l, &q, start); + if (r < 0) + return r; + + memcpy(q, d, l); + return 0; +} + +int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start) { + void *d; + int r; + + assert(p); + + r = dns_packet_extend(p, sizeof(uint8_t), &d, start); + if (r < 0) + return r; + + ((uint8_t*) d)[0] = v; + + return 0; +} + +int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start) { + void *d; + int r; + + assert(p); + + r = dns_packet_extend(p, sizeof(uint16_t), &d, start); + if (r < 0) + return r; + + unaligned_write_be16(d, v); + + return 0; +} + +int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start) { + void *d; + int r; + + assert(p); + + r = dns_packet_extend(p, sizeof(uint32_t), &d, start); + if (r < 0) + return r; + + unaligned_write_be32(d, v); + + return 0; +} + +int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start) { + assert(p); + assert(s); + + return dns_packet_append_raw_string(p, s, strlen(s), start); +} + +int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start) { + void *d; + int r; + + assert(p); + assert(s || size == 0); + + if (size > 255) + return -E2BIG; + + r = dns_packet_extend(p, 1 + size, &d, start); + if (r < 0) + return r; + + ((uint8_t*) d)[0] = (uint8_t) size; + + if (size > 0) + memcpy(((uint8_t*) d) + 1, s, size); + + return 0; +} + +int dns_packet_append_label(DnsPacket *p, const char *d, size_t l, bool canonical_candidate, size_t *start) { + uint8_t *w; + int r; + + /* Append a label to a packet. Optionally, does this in DNSSEC + * canonical form, if this label is marked as a candidate for + * it, and the canonical form logic is enabled for the + * packet */ + + assert(p); + assert(d); + + if (l > DNS_LABEL_MAX) + return -E2BIG; + + r = dns_packet_extend(p, 1 + l, (void**) &w, start); + if (r < 0) + return r; + + *(w++) = (uint8_t) l; + + if (p->canonical_form && canonical_candidate) { + size_t i; + + /* Generate in canonical form, as defined by DNSSEC + * RFC 4034, Section 6.2, i.e. all lower-case. */ + + for (i = 0; i < l; i++) + w[i] = (uint8_t) ascii_tolower(d[i]); + } else + /* Otherwise, just copy the string unaltered. This is + * essential for DNS-SD, where the casing of labels + * matters and needs to be retained. */ + memcpy(w, d, l); + + return 0; +} + +int dns_packet_append_name( + DnsPacket *p, + const char *name, + bool allow_compression, + bool canonical_candidate, + size_t *start) { + + size_t saved_size; + int r; + + assert(p); + assert(name); + + if (p->refuse_compression) + allow_compression = false; + + saved_size = p->size; + + while (!dns_name_is_root(name)) { + const char *z = name; + char label[DNS_LABEL_MAX]; + size_t n = 0; + + if (allow_compression) + n = PTR_TO_SIZE(hashmap_get(p->names, name)); + if (n > 0) { + assert(n < p->size); + + if (n < 0x4000) { + r = dns_packet_append_uint16(p, 0xC000 | n, NULL); + if (r < 0) + goto fail; + + goto done; + } + } + + r = dns_label_unescape(&name, label, sizeof(label)); + if (r < 0) + goto fail; + + r = dns_packet_append_label(p, label, r, canonical_candidate, &n); + if (r < 0) + goto fail; + + if (allow_compression) { + _cleanup_free_ char *s = NULL; + + s = strdup(z); + if (!s) { + r = -ENOMEM; + goto fail; + } + + r = hashmap_ensure_allocated(&p->names, &dns_name_hash_ops); + if (r < 0) + goto fail; + + r = hashmap_put(p->names, s, SIZE_TO_PTR(n)); + if (r < 0) + goto fail; + + s = NULL; + } + } + + r = dns_packet_append_uint8(p, 0, NULL); + if (r < 0) + return r; + +done: + if (start) + *start = saved_size; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +int dns_packet_append_key(DnsPacket *p, const DnsResourceKey *k, size_t *start) { + size_t saved_size; + int r; + + assert(p); + assert(k); + + saved_size = p->size; + + r = dns_packet_append_name(p, DNS_RESOURCE_KEY_NAME(k), true, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, k->type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, k->class, NULL); + if (r < 0) + goto fail; + + if (start) + *start = saved_size; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +static int dns_packet_append_type_window(DnsPacket *p, uint8_t window, uint8_t length, const uint8_t *types, size_t *start) { + size_t saved_size; + int r; + + assert(p); + assert(types); + assert(length > 0); + + saved_size = p->size; + + r = dns_packet_append_uint8(p, window, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, length, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, types, length, NULL); + if (r < 0) + goto fail; + + if (start) + *start = saved_size; + + return 0; +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +static int dns_packet_append_types(DnsPacket *p, Bitmap *types, size_t *start) { + Iterator i; + uint8_t window = 0; + uint8_t entry = 0; + uint8_t bitmaps[32] = {}; + unsigned n; + size_t saved_size; + int r; + + assert(p); + + saved_size = p->size; + + BITMAP_FOREACH(n, types, i) { + assert(n <= 0xffff); + + if ((n >> 8) != window && bitmaps[entry / 8] != 0) { + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); + if (r < 0) + goto fail; + + zero(bitmaps); + } + + window = n >> 8; + entry = n & 255; + + bitmaps[entry / 8] |= 1 << (7 - (entry % 8)); + } + + if (bitmaps[entry / 8] != 0) { + r = dns_packet_append_type_window(p, window, entry / 8 + 1, bitmaps, NULL); + if (r < 0) + goto fail; + } + + if (start) + *start = saved_size; + + return 0; +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +/* 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) { + 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); + + if (p->opt_start != (size_t) -1) + return -EBUSY; + + assert(p->opt_size == (size_t) -1); + + saved_size = p->size; + + /* empty name */ + r = dns_packet_append_uint8(p, 0, NULL); + if (r < 0) + return r; + + /* type */ + r = dns_packet_append_uint16(p, DNS_TYPE_OPT, NULL); + if (r < 0) + goto fail; + + /* 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); + if (r < 0) + goto fail; + + /* flags: DNSSEC OK (DO), see RFC3225 */ + r = dns_packet_append_uint16(p, edns0_do ? EDNS0_OPT_DO : 0, NULL); + if (r < 0) + goto fail; + + /* RDLENGTH */ + + if (edns0_do) { + /* If DO is on, also append RFC6975 Algorithm data */ + + static const uint8_t rfc6975[] = { + + 0, 5, /* OPTION_CODE: DAU */ + 0, 6, /* LIST_LENGTH */ + DNSSEC_ALGORITHM_RSASHA1, + DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, + DNSSEC_ALGORITHM_RSASHA256, + DNSSEC_ALGORITHM_RSASHA512, + DNSSEC_ALGORITHM_ECDSAP256SHA256, + DNSSEC_ALGORITHM_ECDSAP384SHA384, + + 0, 6, /* OPTION_CODE: DHU */ + 0, 3, /* LIST_LENGTH */ + DNSSEC_DIGEST_SHA1, + DNSSEC_DIGEST_SHA256, + DNSSEC_DIGEST_SHA384, + + 0, 7, /* OPTION_CODE: N3U */ + 0, 1, /* LIST_LENGTH */ + NSEC3_ALGORITHM_SHA1, + }; + + r = dns_packet_append_uint16(p, sizeof(rfc6975), NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rfc6975, sizeof(rfc6975), NULL); + } else + r = dns_packet_append_uint16(p, 0, NULL); + + if (r < 0) + goto fail; + + DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) + 1); + + p->opt_start = saved_size; + p->opt_size = p->size - saved_size; + + if (start) + *start = saved_size; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +int dns_packet_truncate_opt(DnsPacket *p) { + assert(p); + + if (p->opt_start == (size_t) -1) { + assert(p->opt_size == (size_t) -1); + return 0; + } + + assert(p->opt_size != (size_t) -1); + assert(DNS_PACKET_ARCOUNT(p) > 0); + + if (p->opt_start + p->opt_size != p->size) + return -EBUSY; + + dns_packet_truncate(p, p->opt_start); + DNS_PACKET_HEADER(p)->arcount = htobe16(DNS_PACKET_ARCOUNT(p) - 1); + p->opt_start = p->opt_size = (size_t) -1; + + return 1; +} + +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; + + assert(p); + assert(rr); + + saved_size = p->size; + + r = dns_packet_append_key(p, rr->key, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->ttl, NULL); + if (r < 0) + goto fail; + + /* Initially we write 0 here */ + r = dns_packet_append_uint16(p, 0, &rdlength_offset); + if (r < 0) + goto fail; + + rds = p->size - saved_size; + + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_SRV: + r = dns_packet_append_uint16(p, rr->srv.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->srv.weight, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->srv.port, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->srv.name, true, false, NULL); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + r = dns_packet_append_name(p, rr->ptr.name, true, false, NULL); + break; + + case DNS_TYPE_HINFO: + r = dns_packet_append_string(p, rr->hinfo.cpu, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_string(p, rr->hinfo.os, NULL); + break; + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: + + if (!rr->txt.items) { + /* RFC 6763, section 6.1 suggests to generate + * single empty string for an empty array. */ + + r = dns_packet_append_raw_string(p, NULL, 0, NULL); + if (r < 0) + goto fail; + } else { + DnsTxtItem *i; + + LIST_FOREACH(items, i, rr->txt.items) { + r = dns_packet_append_raw_string(p, i->data, i->length, NULL); + if (r < 0) + goto fail; + } + } + + r = 0; + break; + + case DNS_TYPE_A: + r = dns_packet_append_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); + break; + + case DNS_TYPE_AAAA: + r = dns_packet_append_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); + break; + + case DNS_TYPE_SOA: + r = dns_packet_append_name(p, rr->soa.mname, true, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->soa.rname, true, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.serial, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.refresh, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.retry, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.expire, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->soa.minimum, NULL); + break; + + case DNS_TYPE_MX: + r = dns_packet_append_uint16(p, rr->mx.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->mx.exchange, true, false, NULL); + break; + + case DNS_TYPE_LOC: + r = dns_packet_append_uint8(p, rr->loc.version, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.horiz_pre, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->loc.vert_pre, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->loc.latitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->loc.longitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->loc.altitude, NULL); + break; + + case DNS_TYPE_DS: + r = dns_packet_append_uint16(p, rr->ds.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->ds.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->ds.digest_type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->ds.digest, rr->ds.digest_size, NULL); + break; + + case DNS_TYPE_SSHFP: + r = dns_packet_append_uint8(p, rr->sshfp.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->sshfp.fptype, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, NULL); + break; + + case DNS_TYPE_DNSKEY: + r = dns_packet_append_uint16(p, rr->dnskey.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->dnskey.protocol, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->dnskey.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->dnskey.key, rr->dnskey.key_size, NULL); + break; + + case DNS_TYPE_RRSIG: + r = dns_packet_append_uint16(p, rr->rrsig.type_covered, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->rrsig.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->rrsig.labels, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.original_ttl, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.expiration, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint32(p, rr->rrsig.inception, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->rrsig.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_name(p, rr->rrsig.signer, false, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->rrsig.signature, rr->rrsig.signature_size, NULL); + break; + + case DNS_TYPE_NSEC: + r = dns_packet_append_name(p, rr->nsec.next_domain_name, false, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_types(p, rr->nsec.types, NULL); + if (r < 0) + goto fail; + + break; + + case DNS_TYPE_NSEC3: + r = dns_packet_append_uint8(p, rr->nsec3.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->nsec3.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint16(p, rr->nsec3.iterations, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->nsec3.salt_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->nsec3.salt, rr->nsec3.salt_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->nsec3.next_hashed_name_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_types(p, rr->nsec3.types, NULL); + if (r < 0) + goto fail; + + break; + + case DNS_TYPE_TLSA: + r = dns_packet_append_uint8(p, rr->tlsa.cert_usage, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->tlsa.selector, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_uint8(p, rr->tlsa.matching_type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_append_blob(p, rr->tlsa.data, rr->tlsa.data_size, NULL); + break; + + case DNS_TYPE_OPT: + case DNS_TYPE_OPENPGPKEY: + case _DNS_TYPE_INVALID: /* unparseable */ + default: + + r = dns_packet_append_blob(p, rr->generic.data, rr->generic.data_size, NULL); + break; + } + if (r < 0) + goto fail; + + /* Let's calculate the actual data size and update the field */ + rdlength = p->size - rdlength_offset - sizeof(uint16_t); + if (rdlength > 0xFFFF) { + r = -ENOSPC; + goto fail; + } + + end = p->size; + p->size = rdlength_offset; + r = dns_packet_append_uint16(p, rdlength, NULL); + if (r < 0) + goto fail; + p->size = end; + + if (start) + *start = saved_size; + + if (rdata_start) + *rdata_start = rds; + + return 0; + +fail: + dns_packet_truncate(p, saved_size); + return r; +} + +int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start) { + assert(p); + + if (p->rindex + sz > p->size) + return -EMSGSIZE; + + if (ret) + *ret = (uint8_t*) DNS_PACKET_DATA(p) + p->rindex; + + if (start) + *start = p->rindex; + + p->rindex += sz; + return 0; +} + +void dns_packet_rewind(DnsPacket *p, size_t idx) { + assert(p); + assert(idx <= p->size); + assert(idx >= DNS_PACKET_HEADER_SIZE); + + p->rindex = idx; +} + +int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start) { + const void *q; + int r; + + assert(p); + assert(d); + + r = dns_packet_read(p, sz, &q, start); + if (r < 0) + return r; + + memcpy(d, q, sz); + return 0; +} + +static int dns_packet_read_memdup( + DnsPacket *p, size_t size, + void **ret, size_t *ret_size, + size_t *ret_start) { + + const void *src; + size_t start; + int r; + + assert(p); + assert(ret); + + r = dns_packet_read(p, size, &src, &start); + if (r < 0) + return r; + + if (size <= 0) + *ret = NULL; + else { + void *copy; + + copy = memdup(src, size); + if (!copy) + return -ENOMEM; + + *ret = copy; + } + + if (ret_size) + *ret_size = size; + if (ret_start) + *ret_start = start; + + return 0; +} + +int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint8_t), &d, start); + if (r < 0) + return r; + + *ret = ((uint8_t*) d)[0]; + return 0; +} + +int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint16_t), &d, start); + if (r < 0) + return r; + + *ret = unaligned_read_be16(d); + + return 0; +} + +int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start) { + const void *d; + int r; + + assert(p); + + r = dns_packet_read(p, sizeof(uint32_t), &d, start); + if (r < 0) + return r; + + *ret = unaligned_read_be32(d); + + return 0; +} + +int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start) { + size_t saved_rindex; + const void *d; + char *t; + uint8_t c; + int r; + + assert(p); + + saved_rindex = p->rindex; + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read(p, c, &d, NULL); + if (r < 0) + goto fail; + + if (memchr(d, 0, c)) { + r = -EBADMSG; + goto fail; + } + + t = strndup(d, c); + if (!t) { + r = -ENOMEM; + goto fail; + } + + if (!utf8_is_valid(t)) { + free(t); + r = -EBADMSG; + goto fail; + } + + *ret = t; + + if (start) + *start = saved_rindex; + + return 0; + +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start) { + size_t saved_rindex; + uint8_t c; + int r; + + assert(p); + + saved_rindex = p->rindex; + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read(p, c, ret, NULL); + if (r < 0) + goto fail; + + if (size) + *size = c; + if (start) + *start = saved_rindex; + + return 0; + +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +int dns_packet_read_name( + DnsPacket *p, + char **_ret, + bool allow_compression, + size_t *start) { + + size_t saved_rindex, after_rindex = 0, jump_barrier; + _cleanup_free_ char *ret = NULL; + size_t n = 0, allocated = 0; + bool first = true; + int r; + + assert(p); + assert(_ret); + + if (p->refuse_compression) + allow_compression = false; + + saved_rindex = p->rindex; + jump_barrier = p->rindex; + + for (;;) { + uint8_t c, d; + + r = dns_packet_read_uint8(p, &c, NULL); + if (r < 0) + goto fail; + + if (c == 0) + /* End of name */ + break; + else if (c <= 63) { + const char *label; + + /* Literal label */ + r = dns_packet_read(p, c, (const void**) &label, NULL); + if (r < 0) + goto fail; + + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) { + r = -ENOMEM; + goto fail; + } + + if (first) + first = false; + else + ret[n++] = '.'; + + r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + goto fail; + + n += r; + continue; + } else if (allow_compression && (c & 0xc0) == 0xc0) { + uint16_t ptr; + + /* Pointer */ + r = dns_packet_read_uint8(p, &d, NULL); + if (r < 0) + goto fail; + + ptr = (uint16_t) (c & ~0xc0) << 8 | (uint16_t) d; + if (ptr < DNS_PACKET_HEADER_SIZE || ptr >= jump_barrier) { + r = -EBADMSG; + goto fail; + } + + if (after_rindex == 0) + after_rindex = p->rindex; + + /* Jumps are limited to a "prior occurrence" (RFC-1035 4.1.4) */ + jump_barrier = ptr; + p->rindex = ptr; + } else { + r = -EBADMSG; + goto fail; + } + } + + if (!GREEDY_REALLOC(ret, allocated, n + 1)) { + r = -ENOMEM; + goto fail; + } + + ret[n] = 0; + + if (after_rindex != 0) + p->rindex= after_rindex; + + *_ret = ret; + ret = NULL; + + if (start) + *start = saved_rindex; + + return 0; + +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +static int dns_packet_read_type_window(DnsPacket *p, Bitmap **types, size_t *start) { + uint8_t window; + uint8_t length; + const uint8_t *bitmap; + uint8_t bit = 0; + unsigned i; + bool found = false; + size_t saved_rindex; + int r; + + assert(p); + assert(types); + + saved_rindex = p->rindex; + + r = bitmap_ensure_allocated(types); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &window, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &length, NULL); + if (r < 0) + goto fail; + + if (length == 0 || length > 32) + return -EBADMSG; + + r = dns_packet_read(p, length, (const void **)&bitmap, NULL); + if (r < 0) + goto fail; + + for (i = 0; i < length; i++) { + uint8_t bitmask = 1 << 7; + + if (!bitmap[i]) { + found = false; + bit += 8; + continue; + } + + found = true; + + while (bitmask) { + if (bitmap[i] & bitmask) { + uint16_t n; + + n = (uint16_t) window << 8 | (uint16_t) bit; + + /* Ignore pseudo-types. see RFC4034 section 4.1.2 */ + if (dns_type_is_pseudo(n)) + continue; + + r = bitmap_set(*types, n); + if (r < 0) + goto fail; + } + + bit ++; + bitmask >>= 1; + } + } + + if (!found) + return -EBADMSG; + + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +static int dns_packet_read_type_windows(DnsPacket *p, Bitmap **types, size_t size, size_t *start) { + size_t saved_rindex; + int r; + + saved_rindex = p->rindex; + + while (p->rindex < saved_rindex + size) { + r = dns_packet_read_type_window(p, types, NULL); + if (r < 0) + goto fail; + + /* don't read past end of current RR */ + if (p->rindex > saved_rindex + size) { + r = -EBADMSG; + goto fail; + } + } + + if (p->rindex != saved_rindex + size) { + r = -EBADMSG; + goto fail; + } + + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start) { + _cleanup_free_ char *name = NULL; + bool cache_flush = false; + uint16_t class, type; + DnsResourceKey *key; + size_t saved_rindex; + int r; + + assert(p); + assert(ret); + + saved_rindex = p->rindex; + + r = dns_packet_read_name(p, &name, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &class, NULL); + if (r < 0) + goto fail; + + if (p->protocol == DNS_PROTOCOL_MDNS) { + /* See RFC6762, Section 10.2 */ + + if (type != DNS_TYPE_OPT && (class & MDNS_RR_CACHE_FLUSH)) { + class &= ~MDNS_RR_CACHE_FLUSH; + cache_flush = true; + } + } + + key = dns_resource_key_new_consume(class, type, name); + if (!key) { + r = -ENOMEM; + goto fail; + } + + name = NULL; + *ret = key; + + if (ret_cache_flush) + *ret_cache_flush = cache_flush; + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +static bool loc_size_ok(uint8_t size) { + uint8_t m = size >> 4, e = size & 0xF; + + return m <= 9 && e <= 9 && (m > 0 || e == 0); +} + +int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + size_t saved_rindex, offset; + uint16_t rdlength; + bool cache_flush; + int r; + + assert(p); + assert(ret); + + saved_rindex = p->rindex; + + r = dns_packet_read_key(p, &key, &cache_flush, NULL); + if (r < 0) + goto fail; + + if (!dns_class_is_valid_rr(key->class)|| + !dns_type_is_valid_rr(key->type)) { + r = -EBADMSG; + goto fail; + } + + rr = dns_resource_record_new(key); + if (!rr) { + r = -ENOMEM; + goto fail; + } + + r = dns_packet_read_uint32(p, &rr->ttl, NULL); + if (r < 0) + goto fail; + + /* RFC 2181, Section 8, suggests to + * treat a TTL with the MSB set as a zero TTL. */ + if (rr->ttl & UINT32_C(0x80000000)) + rr->ttl = 0; + + r = dns_packet_read_uint16(p, &rdlength, NULL); + if (r < 0) + goto fail; + + if (p->rindex + rdlength > p->size) { + r = -EBADMSG; + goto fail; + } + + offset = p->rindex; + + switch (rr->key->type) { + + case DNS_TYPE_SRV: + r = dns_packet_read_uint16(p, &rr->srv.priority, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_uint16(p, &rr->srv.weight, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_uint16(p, &rr->srv.port, NULL); + if (r < 0) + goto fail; + r = dns_packet_read_name(p, &rr->srv.name, true, NULL); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + r = dns_packet_read_name(p, &rr->ptr.name, true, NULL); + break; + + case DNS_TYPE_HINFO: + r = dns_packet_read_string(p, &rr->hinfo.cpu, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_string(p, &rr->hinfo.os, NULL); + break; + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: + if (rdlength <= 0) { + DnsTxtItem *i; + /* RFC 6763, section 6.1 suggests to treat + * empty TXT RRs as equivalent to a TXT record + * with a single empty string. */ + + i = malloc0(offsetof(DnsTxtItem, data) + 1); /* for safety reasons we add an extra NUL byte */ + if (!i) + return -ENOMEM; + + rr->txt.items = i; + } else { + DnsTxtItem *last = NULL; + + while (p->rindex < offset + rdlength) { + DnsTxtItem *i; + const void *data; + size_t sz; + + r = dns_packet_read_raw_string(p, &data, &sz, NULL); + if (r < 0) + return r; + + i = malloc0(offsetof(DnsTxtItem, data) + sz + 1); /* extra NUL byte at the end */ + if (!i) + return -ENOMEM; + + memcpy(i->data, data, sz); + i->length = sz; + + LIST_INSERT_AFTER(items, rr->txt.items, last, i); + last = i; + } + } + + r = 0; + break; + + case DNS_TYPE_A: + r = dns_packet_read_blob(p, &rr->a.in_addr, sizeof(struct in_addr), NULL); + break; + + case DNS_TYPE_AAAA: + r = dns_packet_read_blob(p, &rr->aaaa.in6_addr, sizeof(struct in6_addr), NULL); + break; + + case DNS_TYPE_SOA: + r = dns_packet_read_name(p, &rr->soa.mname, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_name(p, &rr->soa.rname, true, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.serial, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.refresh, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.retry, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.expire, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->soa.minimum, NULL); + break; + + case DNS_TYPE_MX: + r = dns_packet_read_uint16(p, &rr->mx.priority, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_name(p, &rr->mx.exchange, true, NULL); + break; + + case DNS_TYPE_LOC: { + uint8_t t; + size_t pos; + + r = dns_packet_read_uint8(p, &t, &pos); + if (r < 0) + goto fail; + + if (t == 0) { + rr->loc.version = t; + + r = dns_packet_read_uint8(p, &rr->loc.size, NULL); + if (r < 0) + goto fail; + + if (!loc_size_ok(rr->loc.size)) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint8(p, &rr->loc.horiz_pre, NULL); + if (r < 0) + goto fail; + + if (!loc_size_ok(rr->loc.horiz_pre)) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint8(p, &rr->loc.vert_pre, NULL); + if (r < 0) + goto fail; + + if (!loc_size_ok(rr->loc.vert_pre)) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_uint32(p, &rr->loc.latitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->loc.longitude, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->loc.altitude, NULL); + if (r < 0) + goto fail; + + break; + } else { + dns_packet_rewind(p, pos); + rr->unparseable = true; + goto unparseable; + } + } + + case DNS_TYPE_DS: + r = dns_packet_read_uint16(p, &rr->ds.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->ds.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->ds.digest_type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, rdlength - 4, + &rr->ds.digest, &rr->ds.digest_size, + NULL); + if (r < 0) + goto fail; + + if (rr->ds.digest_size <= 0) { + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + r = -EBADMSG; + goto fail; + } + + break; + + case DNS_TYPE_SSHFP: + r = dns_packet_read_uint8(p, &rr->sshfp.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->sshfp.fptype, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, rdlength - 2, + &rr->sshfp.fingerprint, &rr->sshfp.fingerprint_size, + NULL); + + if (rr->sshfp.fingerprint_size <= 0) { + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + r = -EBADMSG; + goto fail; + } + + break; + + case DNS_TYPE_DNSKEY: + r = dns_packet_read_uint16(p, &rr->dnskey.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->dnskey.protocol, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->dnskey.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, rdlength - 4, + &rr->dnskey.key, &rr->dnskey.key_size, + NULL); + + if (rr->dnskey.key_size <= 0) { + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + r = -EBADMSG; + goto fail; + } + + break; + + case DNS_TYPE_RRSIG: + r = dns_packet_read_uint16(p, &rr->rrsig.type_covered, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->rrsig.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->rrsig.labels, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.original_ttl, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.expiration, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint32(p, &rr->rrsig.inception, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &rr->rrsig.key_tag, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_name(p, &rr->rrsig.signer, false, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, offset + rdlength - p->rindex, + &rr->rrsig.signature, &rr->rrsig.signature_size, + NULL); + + if (rr->rrsig.signature_size <= 0) { + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + r = -EBADMSG; + goto fail; + } + + break; + + case DNS_TYPE_NSEC: { + + /* + * RFC6762, section 18.14 explictly states mDNS should use name compression. + * This contradicts RFC3845, section 2.1.1 + */ + + bool allow_compressed = p->protocol == DNS_PROTOCOL_MDNS; + + r = dns_packet_read_name(p, &rr->nsec.next_domain_name, allow_compressed, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_type_windows(p, &rr->nsec.types, offset + rdlength - p->rindex, NULL); + if (r < 0) + goto fail; + + /* We accept empty NSEC bitmaps. The bit indicating the presence of the NSEC record itself + * is redundant and in e.g., RFC4956 this fact is used to define a use for NSEC records + * without the NSEC bit set. */ + + break; + } + case DNS_TYPE_NSEC3: { + uint8_t size; + + r = dns_packet_read_uint8(p, &rr->nsec3.algorithm, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->nsec3.flags, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint16(p, &rr->nsec3.iterations, NULL); + if (r < 0) + goto fail; + + /* this may be zero */ + r = dns_packet_read_uint8(p, &size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, size, &rr->nsec3.salt, &rr->nsec3.salt_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &size, NULL); + if (r < 0) + goto fail; + + if (size <= 0) { + r = -EBADMSG; + goto fail; + } + + r = dns_packet_read_memdup(p, size, &rr->nsec3.next_hashed_name, &rr->nsec3.next_hashed_name_size, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_type_windows(p, &rr->nsec3.types, offset + rdlength - p->rindex, NULL); + if (r < 0) + goto fail; + + /* empty non-terminals can have NSEC3 records, so empty bitmaps are allowed */ + + break; + } + + case DNS_TYPE_TLSA: + r = dns_packet_read_uint8(p, &rr->tlsa.cert_usage, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->tlsa.selector, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_uint8(p, &rr->tlsa.matching_type, NULL); + if (r < 0) + goto fail; + + r = dns_packet_read_memdup(p, rdlength - 3, + &rr->tlsa.data, &rr->tlsa.data_size, + NULL); + if (rr->tlsa.data_size <= 0) { + /* the accepted size depends on the algorithm, but for now + just ensure that the value is greater than zero */ + r = -EBADMSG; + goto fail; + } + + break; + + case DNS_TYPE_OPT: /* we only care about the header of OPT for now. */ + case DNS_TYPE_OPENPGPKEY: + default: + unparseable: + r = dns_packet_read_memdup(p, rdlength, &rr->generic.data, &rr->generic.data_size, NULL); + if (r < 0) + goto fail; + break; + } + if (r < 0) + goto fail; + if (p->rindex != offset + rdlength) { + r = -EBADMSG; + goto fail; + } + + *ret = rr; + rr = NULL; + + if (ret_cache_flush) + *ret_cache_flush = cache_flush; + if (start) + *start = saved_rindex; + + return 0; +fail: + dns_packet_rewind(p, saved_rindex); + return r; +} + +static bool opt_is_good(DnsResourceRecord *rr, bool *rfc6975) { + const uint8_t* p; + bool found_dau_dhu_n3u = false; + size_t l; + + /* Checks whether the specified OPT RR is well-formed and whether it contains RFC6975 data (which is not OK in + * a reply). */ + + assert(rr); + assert(rr->key->type == DNS_TYPE_OPT); + + /* Check that the version is 0 */ + if (((rr->ttl >> 16) & UINT32_C(0xFF)) != 0) + return false; + + p = rr->opt.data; + l = rr->opt.data_size; + while (l > 0) { + uint16_t option_code, option_length; + + /* At least four bytes for OPTION-CODE and OPTION-LENGTH are required */ + if (l < 4U) + return false; + + option_code = unaligned_read_be16(p); + option_length = unaligned_read_be16(p + 2); + + if (l < option_length + 4U) + return false; + + /* RFC 6975 DAU, DHU or N3U fields found. */ + if (IN_SET(option_code, 5, 6, 7)) + found_dau_dhu_n3u = true; + + p += option_length + 4U; + l -= option_length + 4U; + } + + *rfc6975 = found_dau_dhu_n3u; + return true; +} + +int dns_packet_extract(DnsPacket *p) { + _cleanup_(dns_question_unrefp) DnsQuestion *question = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + size_t saved_rindex; + unsigned n, i; + int r; + + if (p->extracted) + return 0; + + saved_rindex = p->rindex; + dns_packet_rewind(p, DNS_PACKET_HEADER_SIZE); + + n = DNS_PACKET_QDCOUNT(p); + if (n > 0) { + question = dns_question_new(n); + if (!question) { + r = -ENOMEM; + goto finish; + } + + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + bool cache_flush; + + r = dns_packet_read_key(p, &key, &cache_flush, NULL); + if (r < 0) + goto finish; + + if (cache_flush) { + r = -EBADMSG; + goto finish; + } + + if (!dns_type_is_valid_query(key->type)) { + r = -EBADMSG; + goto finish; + } + + r = dns_question_add(question, key); + if (r < 0) + goto finish; + } + } + + n = DNS_PACKET_RRCOUNT(p); + if (n > 0) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *previous = NULL; + bool bad_opt = false; + + answer = dns_answer_new(n); + if (!answer) { + r = -ENOMEM; + goto finish; + } + + for (i = 0; i < n; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + bool cache_flush; + + r = dns_packet_read_rr(p, &rr, &cache_flush, NULL); + if (r < 0) + goto finish; + + /* Try to reduce memory usage a bit */ + if (previous) + dns_resource_key_reduce(&rr->key, &previous->key); + + if (rr->key->type == DNS_TYPE_OPT) { + bool has_rfc6975; + + if (p->opt || bad_opt) { + /* Multiple OPT RRs? if so, let's ignore all, because there's something wrong + * with the server, and if one is valid we wouldn't know which one. */ + log_debug("Multiple OPT RRs detected, ignoring all."); + bad_opt = true; + continue; + } + + if (!dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key))) { + /* If the OPT RR is not owned by the root domain, then it is bad, let's ignore + * it. */ + log_debug("OPT RR is not owned by root domain, ignoring."); + bad_opt = true; + continue; + } + + if (i < DNS_PACKET_ANCOUNT(p) + DNS_PACKET_NSCOUNT(p)) { + /* OPT RR is in the wrong section? Some Belkin routers do this. This is a hint + * the EDNS implementation is borked, like the Belkin one is, hence ignore + * it. */ + log_debug("OPT RR in wrong section, ignoring."); + bad_opt = true; + continue; + } + + if (!opt_is_good(rr, &has_rfc6975)) { + log_debug("Malformed OPT RR, ignoring."); + bad_opt = true; + 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; + } + + p->opt = dns_resource_record_ref(rr); + } else { + + /* According to RFC 4795, section 2.9. only the RRs from the Answer section shall be + * cached. Hence mark only those RRs as cacheable by default, but not the ones from the + * Additional or Authority sections. */ + + r = dns_answer_add(answer, rr, p->ifindex, + (i < DNS_PACKET_ANCOUNT(p) ? DNS_ANSWER_CACHEABLE : 0) | + (p->protocol == DNS_PROTOCOL_MDNS && !cache_flush ? DNS_ANSWER_SHARED_OWNER : 0)); + if (r < 0) + goto finish; + } + + /* Remember this RR, so that we potentically can merge it's ->key object with the next RR. Note + * that we only do this if we actually decided to keep the RR around. */ + dns_resource_record_unref(previous); + previous = dns_resource_record_ref(rr); + } + + if (bad_opt) + p->opt = dns_resource_record_unref(p->opt); + } + + p->question = question; + question = NULL; + + p->answer = answer; + answer = NULL; + + p->extracted = true; + + r = 0; + +finish: + p->rindex = saved_rindex; + return r; +} + +int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key) { + int r; + + assert(p); + assert(key); + + /* Checks if the specified packet is a reply for the specified + * key and the specified key is the only one in the question + * section. */ + + if (DNS_PACKET_QR(p) != 1) + return 0; + + /* Let's unpack the packet, if that hasn't happened yet. */ + r = dns_packet_extract(p); + if (r < 0) + return r; + + if (p->question->n_keys != 1) + return 0; + + return dns_resource_key_equal(p->question->keys[0], key); +} + +static const char* const dns_rcode_table[_DNS_RCODE_MAX_DEFINED] = { + [DNS_RCODE_SUCCESS] = "SUCCESS", + [DNS_RCODE_FORMERR] = "FORMERR", + [DNS_RCODE_SERVFAIL] = "SERVFAIL", + [DNS_RCODE_NXDOMAIN] = "NXDOMAIN", + [DNS_RCODE_NOTIMP] = "NOTIMP", + [DNS_RCODE_REFUSED] = "REFUSED", + [DNS_RCODE_YXDOMAIN] = "YXDOMAIN", + [DNS_RCODE_YXRRSET] = "YRRSET", + [DNS_RCODE_NXRRSET] = "NXRRSET", + [DNS_RCODE_NOTAUTH] = "NOTAUTH", + [DNS_RCODE_NOTZONE] = "NOTZONE", + [DNS_RCODE_BADVERS] = "BADVERS", + [DNS_RCODE_BADKEY] = "BADKEY", + [DNS_RCODE_BADTIME] = "BADTIME", + [DNS_RCODE_BADMODE] = "BADMODE", + [DNS_RCODE_BADNAME] = "BADNAME", + [DNS_RCODE_BADALG] = "BADALG", + [DNS_RCODE_BADTRUNC] = "BADTRUNC", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_rcode, int); + +static const char* const dns_protocol_table[_DNS_PROTOCOL_MAX] = { + [DNS_PROTOCOL_DNS] = "dns", + [DNS_PROTOCOL_MDNS] = "mdns", + [DNS_PROTOCOL_LLMNR] = "llmnr", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_protocol, DnsProtocol); diff --git a/src/resolve/src/resolved-dns-packet.h b/src/resolve/src/resolved-dns-packet.h new file mode 100644 index 0000000000..0bf34d270c --- /dev/null +++ b/src/resolve/src/resolved-dns-packet.h @@ -0,0 +1,272 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . + ***/ + +#include +#include + +#include "hashmap.h" +#include "in-addr-util.h" +#include "macro.h" +#include "sparse-endian.h" + +typedef struct DnsPacketHeader DnsPacketHeader; +typedef struct DnsPacket DnsPacket; + +#include "resolved-def.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-dns-rr.h" + +typedef enum DnsProtocol { + DNS_PROTOCOL_DNS, + DNS_PROTOCOL_MDNS, + DNS_PROTOCOL_LLMNR, + _DNS_PROTOCOL_MAX, + _DNS_PROTOCOL_INVALID = -1 +} DnsProtocol; + +struct DnsPacketHeader { + uint16_t id; + be16_t flags; + be16_t qdcount; + be16_t ancount; + be16_t nscount; + be16_t arcount; +}; + +#define DNS_PACKET_HEADER_SIZE sizeof(DnsPacketHeader) +#define UDP_PACKET_HEADER_SIZE (sizeof(struct iphdr) + sizeof(struct udphdr)) + +/* The various DNS protocols deviate in how large a packet can grow, + but the TCP transport has a 16bit size field, hence that appears to + be the absolute maximum. */ +#define DNS_PACKET_SIZE_MAX 0xFFFF + +/* RFC 1035 say 512 is the maximum, for classic unicast DNS */ +#define DNS_PACKET_UNICAST_SIZE_MAX 512 + +/* With EDNS0 we can use larger packets, default to 4096, which is what is commonly used */ +#define DNS_PACKET_UNICAST_SIZE_LARGE_MAX 4096 + +#define DNS_PACKET_SIZE_START 512 + +struct DnsPacket { + int n_ref; + DnsProtocol protocol; + size_t size, allocated, rindex; + void *_data; /* don't access directly, use DNS_PACKET_DATA()! */ + Hashmap *names; /* For name compression */ + size_t opt_start, opt_size; + + /* Parsed data */ + DnsQuestion *question; + DnsAnswer *answer; + DnsResourceRecord *opt; + + /* Packet reception metadata */ + int ifindex; + int family, ipproto; + union in_addr_union sender, destination; + uint16_t sender_port, destination_port; + uint32_t ttl; + + /* For support of truncated packets */ + DnsPacket *more; + + bool on_stack:1; + bool extracted:1; + bool refuse_compression:1; + bool canonical_form:1; +}; + +static inline uint8_t* DNS_PACKET_DATA(DnsPacket *p) { + if (_unlikely_(!p)) + return NULL; + + if (p->_data) + return p->_data; + + return ((uint8_t*) p) + ALIGN(sizeof(DnsPacket)); +} + +#define DNS_PACKET_HEADER(p) ((DnsPacketHeader*) DNS_PACKET_DATA(p)) +#define DNS_PACKET_ID(p) DNS_PACKET_HEADER(p)->id +#define DNS_PACKET_QR(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 15) & 1) +#define DNS_PACKET_OPCODE(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 11) & 15) +#define DNS_PACKET_AA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 10) & 1) +#define DNS_PACKET_TC(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 9) & 1) +#define DNS_PACKET_RD(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 8) & 1) +#define DNS_PACKET_RA(p) ((be16toh(DNS_PACKET_HEADER(p)->flags) >> 7) & 1) +#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) + +static inline uint16_t DNS_PACKET_RCODE(DnsPacket *p) { + uint16_t rcode; + + if (p->opt) + rcode = (uint16_t) (p->opt->ttl >> 24); + else + rcode = 0; + + return rcode | (be16toh(DNS_PACKET_HEADER(p)->flags) & 15); +} + +/* LLMNR defines some bits differently */ +#define DNS_PACKET_LLMNR_C(p) DNS_PACKET_AA(p) +#define DNS_PACKET_LLMNR_T(p) DNS_PACKET_RD(p) + +#define DNS_PACKET_QDCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->qdcount) +#define DNS_PACKET_ANCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->ancount) +#define DNS_PACKET_NSCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->nscount) +#define DNS_PACKET_ARCOUNT(p) be16toh(DNS_PACKET_HEADER(p)->arcount) + +#define DNS_PACKET_MAKE_FLAGS(qr, opcode, aa, tc, rd, ra, ad, cd, rcode) \ + (((uint16_t) !!(qr) << 15) | \ + ((uint16_t) ((opcode) & 15) << 11) | \ + ((uint16_t) !!(aa) << 10) | /* on LLMNR: c */ \ + ((uint16_t) !!(tc) << 9) | \ + ((uint16_t) !!(rd) << 8) | /* on LLMNR: t */ \ + ((uint16_t) !!(ra) << 7) | \ + ((uint16_t) !!(ad) << 5) | \ + ((uint16_t) !!(cd) << 4) | \ + ((uint16_t) ((rcode) & 15))) + +static inline unsigned DNS_PACKET_RRCOUNT(DnsPacket *p) { + return + (unsigned) DNS_PACKET_ANCOUNT(p) + + (unsigned) DNS_PACKET_NSCOUNT(p) + + (unsigned) DNS_PACKET_ARCOUNT(p); +} + +int dns_packet_new(DnsPacket **p, DnsProtocol protocol, size_t mtu); +int dns_packet_new_query(DnsPacket **p, DnsProtocol protocol, size_t mtu, bool dnssec_checking_disabled); + +void dns_packet_set_flags(DnsPacket *p, bool dnssec_checking_disabled, bool truncated); + +DnsPacket *dns_packet_ref(DnsPacket *p); +DnsPacket *dns_packet_unref(DnsPacket *p); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsPacket*, dns_packet_unref); + +int dns_packet_validate(DnsPacket *p); +int dns_packet_validate_reply(DnsPacket *p); +int dns_packet_validate_query(DnsPacket *p); + +int dns_packet_is_reply_for(DnsPacket *p, const DnsResourceKey *key); + +int dns_packet_append_blob(DnsPacket *p, const void *d, size_t sz, size_t *start); +int dns_packet_append_uint8(DnsPacket *p, uint8_t v, size_t *start); +int dns_packet_append_uint16(DnsPacket *p, uint16_t v, size_t *start); +int dns_packet_append_uint32(DnsPacket *p, uint32_t v, size_t *start); +int dns_packet_append_string(DnsPacket *p, const char *s, size_t *start); +int dns_packet_append_raw_string(DnsPacket *p, const void *s, size_t size, size_t *start); +int dns_packet_append_label(DnsPacket *p, const char *s, size_t l, bool canonical_candidate, size_t *start); +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); + +void dns_packet_truncate(DnsPacket *p, size_t sz); +int dns_packet_truncate_opt(DnsPacket *p); + +int dns_packet_read(DnsPacket *p, size_t sz, const void **ret, size_t *start); +int dns_packet_read_blob(DnsPacket *p, void *d, size_t sz, size_t *start); +int dns_packet_read_uint8(DnsPacket *p, uint8_t *ret, size_t *start); +int dns_packet_read_uint16(DnsPacket *p, uint16_t *ret, size_t *start); +int dns_packet_read_uint32(DnsPacket *p, uint32_t *ret, size_t *start); +int dns_packet_read_string(DnsPacket *p, char **ret, size_t *start); +int dns_packet_read_raw_string(DnsPacket *p, const void **ret, size_t *size, size_t *start); +int dns_packet_read_name(DnsPacket *p, char **ret, bool allow_compression, size_t *start); +int dns_packet_read_key(DnsPacket *p, DnsResourceKey **ret, bool *ret_cache_flush, size_t *start); +int dns_packet_read_rr(DnsPacket *p, DnsResourceRecord **ret, bool *ret_cache_flush, size_t *start); + +void dns_packet_rewind(DnsPacket *p, size_t idx); + +int dns_packet_skip_question(DnsPacket *p); +int dns_packet_extract(DnsPacket *p); + +static inline bool DNS_PACKET_SHALL_CACHE(DnsPacket *p) { + /* Never cache data originating from localhost, under the + * assumption, that it's coming from a locally DNS forwarder + * or server, that is caching on its own. */ + + return in_addr_is_localhost(p->family, &p->sender) == 0; +} + +/* https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-6 */ +enum { + DNS_RCODE_SUCCESS = 0, + DNS_RCODE_FORMERR = 1, + DNS_RCODE_SERVFAIL = 2, + DNS_RCODE_NXDOMAIN = 3, + DNS_RCODE_NOTIMP = 4, + DNS_RCODE_REFUSED = 5, + DNS_RCODE_YXDOMAIN = 6, + DNS_RCODE_YXRRSET = 7, + DNS_RCODE_NXRRSET = 8, + DNS_RCODE_NOTAUTH = 9, + DNS_RCODE_NOTZONE = 10, + DNS_RCODE_BADVERS = 16, + DNS_RCODE_BADSIG = 16, /* duplicate value! */ + DNS_RCODE_BADKEY = 17, + DNS_RCODE_BADTIME = 18, + DNS_RCODE_BADMODE = 19, + DNS_RCODE_BADNAME = 20, + DNS_RCODE_BADALG = 21, + DNS_RCODE_BADTRUNC = 22, + _DNS_RCODE_MAX_DEFINED +}; + +const char* dns_rcode_to_string(int i) _const_; +int dns_rcode_from_string(const char *s) _pure_; + +const char* dns_protocol_to_string(DnsProtocol p) _const_; +DnsProtocol dns_protocol_from_string(const char *s) _pure_; + +#define LLMNR_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 252U) }) +#define LLMNR_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03 } }) + +#define MDNS_MULTICAST_IPV4_ADDRESS ((struct in_addr) { .s_addr = htobe32(224U << 24 | 251U) }) +#define MDNS_MULTICAST_IPV6_ADDRESS ((struct in6_addr) { .s6_addr = { 0xFF, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfb } }) + +static inline uint64_t SD_RESOLVED_FLAGS_MAKE(DnsProtocol protocol, int family, bool authenticated) { + uint64_t f; + + /* Converts a protocol + family into a flags field as used in queries and responses */ + + f = authenticated ? SD_RESOLVED_AUTHENTICATED : 0; + + switch (protocol) { + case DNS_PROTOCOL_DNS: + return f|SD_RESOLVED_DNS; + + case DNS_PROTOCOL_LLMNR: + return f|(family == AF_INET6 ? SD_RESOLVED_LLMNR_IPV6 : SD_RESOLVED_LLMNR_IPV4); + + case DNS_PROTOCOL_MDNS: + return family == AF_INET6 ? SD_RESOLVED_MDNS_IPV6 : SD_RESOLVED_MDNS_IPV4; + + default: + break; + } + + return 0; +} diff --git a/src/resolve/src/resolved-dns-query.c b/src/resolve/src/resolved-dns-query.c new file mode 100644 index 0000000000..a378b2b7f7 --- /dev/null +++ b/src/resolve/src/resolved-dns-query.c @@ -0,0 +1,1114 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "dns-type.h" +#include "hostname-util.h" +#include "local-addresses.h" +#include "resolved-dns-query.h" +#include "resolved-dns-synthesize.h" +#include "resolved-etc-hosts.h" +#include "string-util.h" + +/* How long to wait for the query in total */ +#define QUERY_TIMEOUT_USEC (30 * USEC_PER_SEC) + +#define CNAME_MAX 8 +#define QUERIES_MAX 2048 +#define AUXILIARY_QUERIES_MAX 64 + +static int dns_query_candidate_new(DnsQueryCandidate **ret, DnsQuery *q, DnsScope *s) { + DnsQueryCandidate *c; + + assert(ret); + assert(q); + assert(s); + + c = new0(DnsQueryCandidate, 1); + if (!c) + return -ENOMEM; + + c->query = q; + c->scope = s; + + LIST_PREPEND(candidates_by_query, q->candidates, c); + LIST_PREPEND(candidates_by_scope, s->query_candidates, c); + + *ret = c; + return 0; +} + +static void dns_query_candidate_stop(DnsQueryCandidate *c) { + DnsTransaction *t; + + assert(c); + + while ((t = set_steal_first(c->transactions))) { + set_remove(t->notify_query_candidates, c); + dns_transaction_gc(t); + } +} + +DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c) { + + if (!c) + return NULL; + + dns_query_candidate_stop(c); + + set_free(c->transactions); + dns_search_domain_unref(c->search_domain); + + if (c->query) + LIST_REMOVE(candidates_by_query, c->query->candidates, c); + + if (c->scope) + LIST_REMOVE(candidates_by_scope, c->scope->query_candidates, c); + + free(c); + + return NULL; +} + +static int dns_query_candidate_next_search_domain(DnsQueryCandidate *c) { + DnsSearchDomain *next = NULL; + + assert(c); + + if (c->search_domain && c->search_domain->linked) + next = c->search_domain->domains_next; + else + next = dns_scope_get_search_domains(c->scope); + + for (;;) { + if (!next) /* We hit the end of the list */ + return 0; + + if (!next->route_only) + break; + + /* Skip over route-only domains */ + next = next->domains_next; + } + + dns_search_domain_unref(c->search_domain); + c->search_domain = dns_search_domain_ref(next); + + return 1; +} + +static int dns_query_candidate_add_transaction(DnsQueryCandidate *c, DnsResourceKey *key) { + DnsTransaction *t; + int r; + + assert(c); + assert(key); + + t = dns_scope_find_transaction(c->scope, key, true); + if (!t) { + r = dns_transaction_new(&t, c->scope, key); + if (r < 0) + return r; + } else { + if (set_contains(c->transactions, t)) + return 0; + } + + r = set_ensure_allocated(&c->transactions, NULL); + if (r < 0) + goto gc; + + r = set_ensure_allocated(&t->notify_query_candidates, NULL); + if (r < 0) + goto gc; + + r = set_put(t->notify_query_candidates, c); + if (r < 0) + goto gc; + + r = set_put(c->transactions, t); + if (r < 0) { + (void) set_remove(t->notify_query_candidates, c); + goto gc; + } + + return 1; + +gc: + dns_transaction_gc(t); + return r; +} + +static int dns_query_candidate_go(DnsQueryCandidate *c) { + DnsTransaction *t; + Iterator i; + int r; + unsigned n = 0; + + assert(c); + + /* Start the transactions that are not started yet */ + SET_FOREACH(t, c->transactions, i) { + if (t->state != DNS_TRANSACTION_NULL) + continue; + + r = dns_transaction_go(t); + if (r < 0) + return r; + + n++; + } + + /* If there was nothing to start, then let's proceed immediately */ + if (n == 0) + dns_query_candidate_notify(c); + + return 0; +} + +static DnsTransactionState dns_query_candidate_state(DnsQueryCandidate *c) { + DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; + DnsTransaction *t; + Iterator i; + + assert(c); + + if (c->error_code != 0) + return DNS_TRANSACTION_ERRNO; + + SET_FOREACH(t, c->transactions, i) { + + switch (t->state) { + + case DNS_TRANSACTION_NULL: + /* If there's a NULL transaction pending, then + * this means not all transactions where + * started yet, and we were called from within + * the stackframe that is supposed to start + * remaining transactions. In this case, + * simply claim the candidate is pending. */ + + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + /* If there's one transaction currently in + * VALIDATING state, then this means there's + * also one in PENDING state, hence we can + * return PENDING immediately. */ + return DNS_TRANSACTION_PENDING; + + case DNS_TRANSACTION_SUCCESS: + state = t->state; + break; + + default: + if (state != DNS_TRANSACTION_SUCCESS) + state = t->state; + + break; + } + } + + return state; +} + +static bool dns_query_candidate_is_routable(DnsQueryCandidate *c, uint16_t type) { + int family; + + assert(c); + + /* Checks whether the specified RR type matches an address family that is routable on the link(s) the scope of + * this candidate belongs to. Specifically, whether there's a routable IPv4 address on it if we query an A RR, + * or a routable IPv6 address if we query an AAAA RR. */ + + if (!c->query->suppress_unroutable_family) + return true; + + if (c->scope->protocol != DNS_PROTOCOL_DNS) + return true; + + family = dns_type_to_af(type); + if (family < 0) + return true; + + if (c->scope->link) + return link_relevant(c->scope->link, family, false); + else + return manager_routable(c->scope->manager, family); +} + +static int dns_query_candidate_setup_transactions(DnsQueryCandidate *c) { + DnsQuestion *question; + DnsResourceKey *key; + int n = 0, r; + + assert(c); + + dns_query_candidate_stop(c); + + question = dns_query_question_for_protocol(c->query, c->scope->protocol); + + /* Create one transaction per question key */ + DNS_QUESTION_FOREACH(key, question) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *new_key = NULL; + DnsResourceKey *qkey; + + if (!dns_query_candidate_is_routable(c, key->type)) + continue; + + if (c->search_domain) { + r = dns_resource_key_new_append_suffix(&new_key, key, c->search_domain->name); + if (r < 0) + goto fail; + + qkey = new_key; + } else + qkey = key; + + if (!dns_scope_good_key(c->scope, qkey)) + continue; + + r = dns_query_candidate_add_transaction(c, qkey); + if (r < 0) + goto fail; + + n++; + } + + return n; + +fail: + dns_query_candidate_stop(c); + return r; +} + +void dns_query_candidate_notify(DnsQueryCandidate *c) { + DnsTransactionState state; + int r; + + assert(c); + + state = dns_query_candidate_state(c); + + if (DNS_TRANSACTION_IS_LIVE(state)) + return; + + if (state != DNS_TRANSACTION_SUCCESS && c->search_domain) { + + r = dns_query_candidate_next_search_domain(c); + if (r < 0) + goto fail; + + if (r > 0) { + /* OK, there's another search domain to try, let's do so. */ + + r = dns_query_candidate_setup_transactions(c); + if (r < 0) + goto fail; + + if (r > 0) { + /* New transactions where queued. Start them and wait */ + + r = dns_query_candidate_go(c); + if (r < 0) + goto fail; + + return; + } + } + + } + + dns_query_ready(c->query); + return; + +fail: + log_warning_errno(r, "Failed to follow search domains: %m"); + c->error_code = r; + dns_query_ready(c->query); +} + +static void dns_query_stop(DnsQuery *q) { + DnsQueryCandidate *c; + + assert(q); + + q->timeout_event_source = sd_event_source_unref(q->timeout_event_source); + + LIST_FOREACH(candidates_by_query, c, q->candidates) + dns_query_candidate_stop(c); +} + +static void dns_query_free_candidates(DnsQuery *q) { + assert(q); + + while (q->candidates) + dns_query_candidate_free(q->candidates); +} + +static void dns_query_reset_answer(DnsQuery *q) { + assert(q); + + q->answer = dns_answer_unref(q->answer); + q->answer_rcode = 0; + q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_errno = 0; + q->answer_authenticated = false; + q->answer_protocol = _DNS_PROTOCOL_INVALID; + q->answer_family = AF_UNSPEC; + q->answer_search_domain = dns_search_domain_unref(q->answer_search_domain); +} + +DnsQuery *dns_query_free(DnsQuery *q) { + if (!q) + return NULL; + + while (q->auxiliary_queries) + dns_query_free(q->auxiliary_queries); + + if (q->auxiliary_for) { + assert(q->auxiliary_for->n_auxiliary_queries > 0); + q->auxiliary_for->n_auxiliary_queries--; + LIST_REMOVE(auxiliary_queries, q->auxiliary_for->auxiliary_queries, q); + } + + dns_query_free_candidates(q); + + dns_question_unref(q->question_idna); + dns_question_unref(q->question_utf8); + + dns_query_reset_answer(q); + + sd_bus_message_unref(q->request); + sd_bus_track_unref(q->bus_track); + + free(q->request_address_string); + + if (q->manager) { + LIST_REMOVE(queries, q->manager->dns_queries, q); + q->manager->n_dns_queries--; + } + + free(q); + + return NULL; +} + +int dns_query_new( + Manager *m, + DnsQuery **ret, + DnsQuestion *question_utf8, + DnsQuestion *question_idna, + int ifindex, uint64_t flags) { + + _cleanup_(dns_query_freep) DnsQuery *q = NULL; + DnsResourceKey *key; + bool good = false; + int r; + + assert(m); + + if (dns_question_size(question_utf8) > 0) { + r = dns_question_is_valid_for_query(question_utf8); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + good = true; + } + + /* If the IDNA and UTF8 questions are the same, merge their references */ + r = dns_question_is_equal(question_idna, question_utf8); + if (r < 0) + return r; + if (r > 0) + question_idna = question_utf8; + else { + if (dns_question_size(question_idna) > 0) { + r = dns_question_is_valid_for_query(question_idna); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + good = true; + } + } + + if (!good) /* don't allow empty queries */ + return -EINVAL; + + if (m->n_dns_queries >= QUERIES_MAX) + return -EBUSY; + + q = new0(DnsQuery, 1); + if (!q) + return -ENOMEM; + + q->question_utf8 = dns_question_ref(question_utf8); + q->question_idna = dns_question_ref(question_idna); + q->ifindex = ifindex; + q->flags = flags; + q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_protocol = _DNS_PROTOCOL_INVALID; + q->answer_family = AF_UNSPEC; + + /* First dump UTF8 question */ + DNS_QUESTION_FOREACH(key, question_utf8) { + _cleanup_free_ char *p = NULL; + + r = dns_resource_key_to_string(key, &p); + if (r < 0) + return r; + + log_debug("Looking up RR for %s.", strstrip(p)); + } + + /* And then dump the IDNA question, but only what hasn't been dumped already through the UTF8 question. */ + DNS_QUESTION_FOREACH(key, question_idna) { + _cleanup_free_ char *p = NULL; + + r = dns_question_contains(question_utf8, key); + if (r < 0) + return r; + if (r > 0) + continue; + + r = dns_resource_key_to_string(key, &p); + if (r < 0) + return r; + + log_debug("Looking up IDNA RR for %s.", strstrip(p)); + } + + LIST_PREPEND(queries, m->dns_queries, q); + m->n_dns_queries++; + q->manager = m; + + if (ret) + *ret = q; + q = NULL; + + return 0; +} + +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 + * nothing else is auxiliary to it either */ + assert(!q->auxiliary_for); + assert(!q->auxiliary_queries); + + /* Ensure that the unit we shall be made auxiliary for isn't + * auxiliary itself */ + assert(!auxiliary_for->auxiliary_for); + + if (auxiliary_for->n_auxiliary_queries >= AUXILIARY_QUERIES_MAX) + return -EAGAIN; + + LIST_PREPEND(auxiliary_queries, auxiliary_for->auxiliary_queries, q); + q->auxiliary_for = auxiliary_for; + + auxiliary_for->n_auxiliary_queries++; + return 0; +} + +static void dns_query_complete(DnsQuery *q, DnsTransactionState state) { + assert(q); + assert(!DNS_TRANSACTION_IS_LIVE(state)); + assert(DNS_TRANSACTION_IS_LIVE(q->state)); + + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function. */ + + q->state = state; + + dns_query_stop(q); + if (q->complete) + q->complete(q); +} + +static int on_query_timeout(sd_event_source *s, usec_t usec, void *userdata) { + DnsQuery *q = userdata; + + assert(s); + assert(q); + + dns_query_complete(q, DNS_TRANSACTION_TIMEOUT); + return 0; +} + +static int dns_query_add_candidate(DnsQuery *q, DnsScope *s) { + DnsQueryCandidate *c; + int r; + + assert(q); + assert(s); + + r = dns_query_candidate_new(&c, q, s); + if (r < 0) + return r; + + /* If this a single-label domain on DNS, we might append a suitable search domain first. */ + if ((q->flags & SD_RESOLVED_NO_SEARCH) == 0) { + r = dns_scope_name_needs_search_domain(s, dns_question_first_name(q->question_idna)); + if (r < 0) + goto fail; + if (r > 0) { + /* OK, we need a search domain now. Let's find one for this scope */ + + r = dns_query_candidate_next_search_domain(c); + if (r <= 0) /* if there's no search domain, then we won't add any transaction. */ + goto fail; + } + } + + r = dns_query_candidate_setup_transactions(c); + if (r < 0) + goto fail; + + return 0; + +fail: + dns_query_candidate_free(c); + return r; +} + +static int dns_query_synthesize_reply(DnsQuery *q, DnsTransactionState *state) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + int r; + + assert(q); + assert(state); + + /* Tries to synthesize localhost RR replies (and others) where appropriate. Note that this is done *after* the + * the normal lookup finished. The data from the network hence takes precedence over the data we + * synthesize. (But note that many scopes refuse to resolve certain domain names) */ + + if (!IN_SET(*state, + DNS_TRANSACTION_RCODE_FAILURE, + DNS_TRANSACTION_NO_SERVERS, + DNS_TRANSACTION_TIMEOUT, + DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, + DNS_TRANSACTION_NETWORK_DOWN, + DNS_TRANSACTION_NOT_FOUND)) + return 0; + + r = dns_synthesize_answer( + q->manager, + q->question_utf8, + q->ifindex, + &answer); + + if (r <= 0) + return r; + + dns_query_reset_answer(q); + + q->answer = answer; + answer = NULL; + q->answer_rcode = DNS_RCODE_SUCCESS; + q->answer_protocol = dns_synthesize_protocol(q->flags); + q->answer_family = dns_synthesize_family(q->flags); + q->answer_authenticated = true; + + *state = DNS_TRANSACTION_SUCCESS; + + return 1; +} + +static int dns_query_try_etc_hosts(DnsQuery *q) { + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + int r; + + assert(q); + + /* Looks in /etc/hosts for matching entries. Note that this is done *before* the normal lookup is done. The + * data from /etc/hosts hence takes precedence over the network. */ + + r = manager_etc_hosts_lookup( + q->manager, + q->question_utf8, + &answer); + if (r <= 0) + return r; + + dns_query_reset_answer(q); + + q->answer = answer; + answer = NULL; + q->answer_rcode = DNS_RCODE_SUCCESS; + q->answer_protocol = dns_synthesize_protocol(q->flags); + q->answer_family = dns_synthesize_family(q->flags); + q->answer_authenticated = true; + + return 1; +} + +int dns_query_go(DnsQuery *q) { + DnsScopeMatch found = DNS_SCOPE_NO; + DnsScope *s, *first = NULL; + DnsQueryCandidate *c; + int r; + + assert(q); + + if (q->state != DNS_TRANSACTION_NULL) + return 0; + + r = dns_query_try_etc_hosts(q); + if (r < 0) + return r; + if (r > 0) { + dns_query_complete(q, DNS_TRANSACTION_SUCCESS); + return 1; + } + + LIST_FOREACH(scopes, s, q->manager->dns_scopes) { + DnsScopeMatch match; + const char *name; + + name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); + if (!name) + continue; + + match = dns_scope_good_domain(s, q->ifindex, q->flags, name); + if (match < 0) + return match; + + if (match == DNS_SCOPE_NO) + continue; + + found = match; + + if (match == DNS_SCOPE_YES) { + first = s; + break; + } else { + assert(match == DNS_SCOPE_MAYBE); + + if (!first) + first = s; + } + } + + if (found == DNS_SCOPE_NO) { + DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; + + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + return r; + + dns_query_complete(q, state); + return 1; + } + + r = dns_query_add_candidate(q, first); + if (r < 0) + goto fail; + + LIST_FOREACH(scopes, s, first->scopes_next) { + DnsScopeMatch match; + const char *name; + + name = dns_question_first_name(dns_query_question_for_protocol(q, s->protocol)); + if (!name) + continue; + + match = dns_scope_good_domain(s, q->ifindex, q->flags, name); + if (match < 0) + goto fail; + + if (match != found) + continue; + + r = dns_query_add_candidate(q, s); + if (r < 0) + goto fail; + } + + dns_query_reset_answer(q); + + r = sd_event_add_time( + q->manager->event, + &q->timeout_event_source, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + QUERY_TIMEOUT_USEC, 0, + on_query_timeout, q); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(q->timeout_event_source, "query-timeout"); + + q->state = DNS_TRANSACTION_PENDING; + q->block_ready++; + + /* Start the transactions */ + LIST_FOREACH(candidates_by_query, c, q->candidates) { + r = dns_query_candidate_go(c); + if (r < 0) { + q->block_ready--; + goto fail; + } + } + + q->block_ready--; + dns_query_ready(q); + + return 1; + +fail: + dns_query_stop(q); + return r; +} + +static void dns_query_accept(DnsQuery *q, DnsQueryCandidate *c) { + DnsTransactionState state = DNS_TRANSACTION_NO_SERVERS; + bool has_authenticated = false, has_non_authenticated = false; + DnssecResult dnssec_result_authenticated = _DNSSEC_RESULT_INVALID, dnssec_result_non_authenticated = _DNSSEC_RESULT_INVALID; + DnsTransaction *t; + Iterator i; + int r; + + assert(q); + + if (!c) { + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + goto fail; + + dns_query_complete(q, state); + return; + } + + if (c->error_code != 0) { + /* If the candidate had an error condition of its own, start with that. */ + state = DNS_TRANSACTION_ERRNO; + q->answer = dns_answer_unref(q->answer); + q->answer_rcode = 0; + q->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + q->answer_errno = c->error_code; + } + + SET_FOREACH(t, c->transactions, i) { + + switch (t->state) { + + case DNS_TRANSACTION_SUCCESS: { + /* We found a successfuly reply, merge it into the answer */ + r = dns_answer_extend(&q->answer, t->answer); + if (r < 0) + goto fail; + + q->answer_rcode = t->answer_rcode; + q->answer_errno = 0; + + if (t->answer_authenticated) { + has_authenticated = true; + dnssec_result_authenticated = t->answer_dnssec_result; + } else { + has_non_authenticated = true; + dnssec_result_non_authenticated = t->answer_dnssec_result; + } + + state = DNS_TRANSACTION_SUCCESS; + break; + } + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + case DNS_TRANSACTION_ABORTED: + /* Ignore transactions that didn't complete */ + continue; + + default: + /* Any kind of failure? Store the data away, + * if there's nothing stored yet. */ + + if (state == DNS_TRANSACTION_SUCCESS) + continue; + + q->answer = dns_answer_unref(q->answer); + q->answer_rcode = t->answer_rcode; + q->answer_dnssec_result = t->answer_dnssec_result; + q->answer_errno = t->answer_errno; + + state = t->state; + break; + } + } + + if (state == DNS_TRANSACTION_SUCCESS) { + q->answer_authenticated = has_authenticated && !has_non_authenticated; + q->answer_dnssec_result = q->answer_authenticated ? dnssec_result_authenticated : dnssec_result_non_authenticated; + } + + q->answer_protocol = c->scope->protocol; + q->answer_family = c->scope->family; + + dns_search_domain_unref(q->answer_search_domain); + q->answer_search_domain = dns_search_domain_ref(c->search_domain); + + r = dns_query_synthesize_reply(q, &state); + if (r < 0) + goto fail; + + dns_query_complete(q, state); + return; + +fail: + q->answer_errno = -r; + dns_query_complete(q, DNS_TRANSACTION_ERRNO); +} + +void dns_query_ready(DnsQuery *q) { + + DnsQueryCandidate *bad = NULL, *c; + bool pending = false; + + assert(q); + assert(DNS_TRANSACTION_IS_LIVE(q->state)); + + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function, unless the block_ready + * counter was explicitly bumped before doing so. */ + + if (q->block_ready > 0) + return; + + LIST_FOREACH(candidates_by_query, c, q->candidates) { + DnsTransactionState state; + + state = dns_query_candidate_state(c); + switch (state) { + + case DNS_TRANSACTION_SUCCESS: + /* One of the candidates is successful, + * let's use it, and copy its data out */ + dns_query_accept(q, c); + return; + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + /* One of the candidates is still going on, + * let's maybe wait for it */ + pending = true; + break; + + default: + /* Any kind of failure */ + bad = c; + break; + } + } + + if (pending) + return; + + dns_query_accept(q, bad); +} + +static int dns_query_cname_redirect(DnsQuery *q, const DnsResourceRecord *cname) { + _cleanup_(dns_question_unrefp) DnsQuestion *nq_idna = NULL, *nq_utf8 = NULL; + int r, k; + + assert(q); + + q->n_cname_redirects ++; + if (q->n_cname_redirects > CNAME_MAX) + return -ELOOP; + + r = dns_question_cname_redirect(q->question_idna, cname, &nq_idna); + if (r < 0) + return r; + else if (r > 0) + log_debug("Following CNAME/DNAME %s → %s.", dns_question_first_name(q->question_idna), dns_question_first_name(nq_idna)); + + k = dns_question_is_equal(q->question_idna, q->question_utf8); + if (k < 0) + return r; + if (k > 0) { + /* Same question? Shortcut new question generation */ + nq_utf8 = dns_question_ref(nq_idna); + k = r; + } else { + k = dns_question_cname_redirect(q->question_utf8, cname, &nq_utf8); + if (k < 0) + return k; + else if (k > 0) + log_debug("Following UTF8 CNAME/DNAME %s → %s.", dns_question_first_name(q->question_utf8), dns_question_first_name(nq_utf8)); + } + + if (r == 0 && k == 0) /* No actual cname happened? */ + return -ELOOP; + + if (q->answer_protocol == DNS_PROTOCOL_DNS) { + /* Don't permit CNAME redirects from unicast DNS to LLMNR or MulticastDNS, so that global resources + * cannot invade the local namespace. The opposite way we permit: local names may redirect to global + * ones. */ + + q->flags &= ~(SD_RESOLVED_LLMNR|SD_RESOLVED_MDNS); /* mask away the local protocols */ + } + + /* Turn off searching for the new name */ + q->flags |= SD_RESOLVED_NO_SEARCH; + + dns_question_unref(q->question_idna); + q->question_idna = nq_idna; + nq_idna = NULL; + + dns_question_unref(q->question_utf8); + q->question_utf8 = nq_utf8; + nq_utf8 = NULL; + + dns_query_free_candidates(q); + dns_query_reset_answer(q); + + q->state = DNS_TRANSACTION_NULL; + + return 0; +} + +int dns_query_process_cname(DnsQuery *q) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *cname = NULL; + DnsQuestion *question; + DnsResourceRecord *rr; + int r; + + assert(q); + + if (!IN_SET(q->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_NULL)) + return DNS_QUERY_NOMATCH; + + question = dns_query_question_for_protocol(q, q->answer_protocol); + + DNS_ANSWER_FOREACH(rr, q->answer) { + r = dns_question_matches_rr(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + return r; + if (r > 0) + return DNS_QUERY_MATCH; /* The answer matches directly, no need to follow cnames */ + + r = dns_question_matches_cname_or_dname(question, rr, DNS_SEARCH_DOMAIN_NAME(q->answer_search_domain)); + if (r < 0) + return r; + if (r > 0 && !cname) + cname = dns_resource_record_ref(rr); + } + + if (!cname) + return DNS_QUERY_NOMATCH; /* No match and no cname to follow */ + + if (q->flags & SD_RESOLVED_NO_CNAME) + return -ELOOP; + + /* OK, let's actually follow the CNAME */ + r = dns_query_cname_redirect(q, cname); + if (r < 0) + return r; + + /* Let's see if the answer can already answer the new + * redirected question */ + r = dns_query_process_cname(q); + if (r != DNS_QUERY_NOMATCH) + return r; + + /* OK, it cannot, let's begin with the new query */ + r = dns_query_go(q); + if (r < 0) + return r; + + return DNS_QUERY_RESTARTED; /* We restarted the query for a new cname */ +} + +static int on_bus_track(sd_bus_track *t, void *userdata) { + DnsQuery *q = userdata; + + assert(t); + assert(q); + + log_debug("Client of active query vanished, aborting query."); + dns_query_complete(q, DNS_TRANSACTION_ABORTED); + return 0; +} + +int dns_query_bus_track(DnsQuery *q, sd_bus_message *m) { + int r; + + assert(q); + assert(m); + + if (!q->bus_track) { + r = sd_bus_track_new(sd_bus_message_get_bus(m), &q->bus_track, on_bus_track, q); + if (r < 0) + return r; + } + + r = sd_bus_track_add_sender(q->bus_track, m); + if (r < 0) + return r; + + return 0; +} + +DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol) { + assert(q); + + switch (protocol) { + + case DNS_PROTOCOL_DNS: + return q->question_idna; + + case DNS_PROTOCOL_MDNS: + case DNS_PROTOCOL_LLMNR: + return q->question_utf8; + + default: + return NULL; + } +} + +const char *dns_query_string(DnsQuery *q) { + const char *name; + int r; + + /* Returns a somewhat useful human-readable lookup key string for this query */ + + if (q->request_address_string) + return q->request_address_string; + + if (q->request_address_valid) { + r = in_addr_to_string(q->request_family, &q->request_address, &q->request_address_string); + if (r >= 0) + return q->request_address_string; + } + + name = dns_question_first_name(q->question_utf8); + if (name) + return name; + + return dns_question_first_name(q->question_idna); +} diff --git a/src/resolve/src/resolved-dns-query.h b/src/resolve/src/resolved-dns-query.h new file mode 100644 index 0000000000..7f7c76ff20 --- /dev/null +++ b/src/resolve/src/resolved-dns-query.h @@ -0,0 +1,133 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + + +#include + +#include "set.h" + +typedef struct DnsQueryCandidate DnsQueryCandidate; +typedef struct DnsQuery DnsQuery; + +#include "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-dns-stream.h" +#include "resolved-dns-search-domain.h" + +struct DnsQueryCandidate { + DnsQuery *query; + DnsScope *scope; + + DnsSearchDomain *search_domain; + + int error_code; + Set *transactions; + + LIST_FIELDS(DnsQueryCandidate, candidates_by_query); + LIST_FIELDS(DnsQueryCandidate, candidates_by_scope); +}; + +struct DnsQuery { + Manager *manager; + + /* When resolving a service, we first create a TXT+SRV query, + * and then for the hostnames we discover auxiliary A+AAAA + * queries. This pointer always points from the auxiliary + * queries back to the TXT+SRV query. */ + DnsQuery *auxiliary_for; + LIST_HEAD(DnsQuery, auxiliary_queries); + unsigned n_auxiliary_queries; + int auxiliary_result; + + /* The question, formatted in IDNA for use on classic DNS, and as UTF8 for use in LLMNR or mDNS. Note that even + * on classic DNS some labels might use UTF8 encoding. Specifically, DNS-SD service names (in contrast to their + * domain suffixes) use UTF-8 encoding even on DNS. Thus, the difference between these two fields is mostly + * relevant only for explicit *hostname* lookups as well as the domain suffixes of service lookups. */ + DnsQuestion *question_idna; + DnsQuestion *question_utf8; + + uint64_t flags; + int ifindex; + + /* If true, A or AAAA RR lookups will be suppressed on links with no routable address of the matching address + * family */ + bool suppress_unroutable_family; + + DnsTransactionState state; + unsigned n_cname_redirects; + + LIST_HEAD(DnsQueryCandidate, candidates); + sd_event_source *timeout_event_source; + + /* Discovered data */ + DnsAnswer *answer; + int answer_rcode; + DnssecResult answer_dnssec_result; + bool answer_authenticated; + DnsProtocol answer_protocol; + int answer_family; + DnsSearchDomain *answer_search_domain; + int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ + + /* Bus client information */ + sd_bus_message *request; + int request_family; + bool request_address_valid; + union in_addr_union request_address; + unsigned block_all_complete; + char *request_address_string; + + /* Completion callback */ + void (*complete)(DnsQuery* q); + unsigned block_ready; + + sd_bus_track *bus_track; + + LIST_FIELDS(DnsQuery, queries); + LIST_FIELDS(DnsQuery, auxiliary_queries); +}; + +enum { + DNS_QUERY_MATCH, + DNS_QUERY_NOMATCH, + DNS_QUERY_RESTARTED, +}; + +DnsQueryCandidate* dns_query_candidate_free(DnsQueryCandidate *c); +void dns_query_candidate_notify(DnsQueryCandidate *c); + +int dns_query_new(Manager *m, DnsQuery **q, DnsQuestion *question_utf8, DnsQuestion *question_idna, int family, uint64_t flags); +DnsQuery *dns_query_free(DnsQuery *q); + +int dns_query_make_auxiliary(DnsQuery *q, DnsQuery *auxiliary_for); + +int dns_query_go(DnsQuery *q); +void dns_query_ready(DnsQuery *q); + +int dns_query_process_cname(DnsQuery *q); + +int dns_query_bus_track(DnsQuery *q, sd_bus_message *m); + +DnsQuestion* dns_query_question_for_protocol(DnsQuery *q, DnsProtocol protocol); + +const char *dns_query_string(DnsQuery *q); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuery*, dns_query_free); diff --git a/src/resolve/src/resolved-dns-question.c b/src/resolve/src/resolved-dns-question.c new file mode 100644 index 0000000000..8e452e79a4 --- /dev/null +++ b/src/resolve/src/resolved-dns-question.c @@ -0,0 +1,468 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "dns-type.h" +#include "resolved-dns-question.h" + +DnsQuestion *dns_question_new(unsigned n) { + DnsQuestion *q; + + assert(n > 0); + + q = malloc0(offsetof(DnsQuestion, keys) + sizeof(DnsResourceKey*) * n); + if (!q) + return NULL; + + q->n_ref = 1; + q->n_allocated = n; + + return q; +} + +DnsQuestion *dns_question_ref(DnsQuestion *q) { + if (!q) + return NULL; + + assert(q->n_ref > 0); + q->n_ref++; + return q; +} + +DnsQuestion *dns_question_unref(DnsQuestion *q) { + if (!q) + return NULL; + + assert(q->n_ref > 0); + + if (q->n_ref == 1) { + unsigned i; + + for (i = 0; i < q->n_keys; i++) + dns_resource_key_unref(q->keys[i]); + free(q); + } else + q->n_ref--; + + return NULL; +} + +int dns_question_add(DnsQuestion *q, DnsResourceKey *key) { + unsigned i; + int r; + + assert(key); + + if (!q) + return -ENOSPC; + + for (i = 0; i < q->n_keys; i++) { + r = dns_resource_key_equal(q->keys[i], key); + if (r < 0) + return r; + if (r > 0) + return 0; + } + + if (q->n_keys >= q->n_allocated) + return -ENOSPC; + + q->keys[q->n_keys++] = dns_resource_key_ref(key); + return 0; +} + +int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { + unsigned i; + int r; + + assert(rr); + + if (!q) + return 0; + + for (i = 0; i < q->n_keys; i++) { + r = dns_resource_key_match_rr(q->keys[i], rr, search_domain); + if (r != 0) + return r; + } + + return 0; +} + +int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain) { + unsigned i; + int r; + + assert(rr); + + if (!q) + return 0; + + if (!IN_SET(rr->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)) + return 0; + + for (i = 0; i < q->n_keys; i++) { + /* For a {C,D}NAME record we can never find a matching {C,D}NAME record */ + if (!dns_type_may_redirect(q->keys[i]->type)) + return 0; + + r = dns_resource_key_match_cname_or_dname(q->keys[i], rr->key, search_domain); + if (r != 0) + return r; + } + + return 0; +} + +int dns_question_is_valid_for_query(DnsQuestion *q) { + const char *name; + unsigned i; + int r; + + if (!q) + return 0; + + if (q->n_keys <= 0) + return 0; + + if (q->n_keys > 65535) + return 0; + + name = DNS_RESOURCE_KEY_NAME(q->keys[0]); + if (!name) + return 0; + + /* Check that all keys in this question bear the same name */ + for (i = 0; i < q->n_keys; i++) { + assert(q->keys[i]); + + if (i > 0) { + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(q->keys[i]), name); + if (r <= 0) + return r; + } + + if (!dns_type_is_valid_query(q->keys[i]->type)) + return 0; + } + + return 1; +} + +int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k) { + unsigned j; + int r; + + assert(k); + + if (!a) + return 0; + + for (j = 0; j < a->n_keys; j++) { + r = dns_resource_key_equal(a->keys[j], k); + if (r != 0) + return r; + } + + return 0; +} + +int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b) { + unsigned j; + int r; + + if (a == b) + return 1; + + if (!a) + return !b || b->n_keys == 0; + if (!b) + return a->n_keys == 0; + + /* Checks if all keys in a are also contained b, and vice versa */ + + for (j = 0; j < a->n_keys; j++) { + r = dns_question_contains(b, a->keys[j]); + if (r <= 0) + return r; + } + + for (j = 0; j < b->n_keys; j++) { + r = dns_question_contains(a, b->keys[j]); + if (r <= 0) + return r; + } + + return 1; +} + +int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret) { + _cleanup_(dns_question_unrefp) DnsQuestion *n = NULL; + DnsResourceKey *key; + bool same = true; + int r; + + assert(cname); + assert(ret); + assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); + + if (dns_question_size(q) <= 0) { + *ret = NULL; + return 0; + } + + DNS_QUESTION_FOREACH(key, q) { + _cleanup_free_ char *destination = NULL; + const char *d; + + if (cname->key->type == DNS_TYPE_CNAME) + d = cname->cname.name; + else { + r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination); + if (r < 0) + return r; + if (r == 0) + continue; + + d = destination; + } + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), d); + if (r < 0) + return r; + + if (r == 0) { + same = false; + break; + } + } + + /* Fully the same, indicate we didn't do a thing */ + if (same) { + *ret = NULL; + return 0; + } + + n = dns_question_new(q->n_keys); + if (!n) + return -ENOMEM; + + /* Create a new question, and patch in the new name */ + DNS_QUESTION_FOREACH(key, q) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *k = NULL; + + k = dns_resource_key_new_redirect(key, cname); + if (!k) + return -ENOMEM; + + r = dns_question_add(n, k); + if (r < 0) + return r; + } + + *ret = n; + n = NULL; + + return 1; +} + +const char *dns_question_first_name(DnsQuestion *q) { + + if (!q) + return NULL; + + if (q->n_keys < 1) + return NULL; + + return DNS_RESOURCE_KEY_NAME(q->keys[0]); +} + +int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna) { + _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; + _cleanup_free_ char *buf = NULL; + int r; + + assert(ret); + assert(name); + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return -EAFNOSUPPORT; + + if (convert_idna) { + r = dns_name_apply_idna(name, &buf); + if (r < 0) + return r; + + name = buf; + } + + q = dns_question_new(family == AF_UNSPEC ? 2 : 1); + if (!q) + return -ENOMEM; + + if (family != AF_INET6) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key); + if (r < 0) + return r; + } + + if (family != AF_INET) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key); + if (r < 0) + return r; + } + + *ret = q; + q = NULL; + + return 0; +} + +int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; + _cleanup_free_ char *reverse = NULL; + int r; + + assert(ret); + assert(a); + + if (!IN_SET(family, AF_INET, AF_INET6, AF_UNSPEC)) + return -EAFNOSUPPORT; + + r = dns_name_reverse(family, a, &reverse); + if (r < 0) + return r; + + q = dns_question_new(1); + if (!q) + return -ENOMEM; + + key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, reverse); + if (!key) + return -ENOMEM; + + reverse = NULL; + + r = dns_question_add(q, key); + if (r < 0) + return r; + + *ret = q; + q = NULL; + + return 0; +} + +int dns_question_new_service( + DnsQuestion **ret, + const char *service, + const char *type, + const char *domain, + bool with_txt, + bool convert_idna) { + + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_question_unrefp) DnsQuestion *q = NULL; + _cleanup_free_ char *buf = NULL, *joined = NULL; + const char *name; + int r; + + assert(ret); + + /* We support three modes of invocation: + * + * 1. Only a domain is specified, in which case we assume a properly encoded SRV RR name, including service + * type and possibly a service name. If specified in this way we assume it's already IDNA converted if + * that's necessary. + * + * 2. Both service type and a domain specified, in which case a normal SRV RR is assumed, without a DNS-SD + * style prefix. In this case we'll IDNA convert the domain, if that's requested. + * + * 3. All three of service name, type and domain are specified, in which case a DNS-SD service is put + * together. The service name is never IDNA converted, and the domain is if requested. + * + * It's not supported to specify a service name without a type, or no domain name. + */ + + if (!domain) + return -EINVAL; + + if (type) { + if (convert_idna) { + r = dns_name_apply_idna(domain, &buf); + if (r < 0) + return r; + + domain = buf; + } + + r = dns_service_join(service, type, domain, &joined); + if (r < 0) + return r; + + name = joined; + } else { + if (service) + return -EINVAL; + + name = domain; + } + + q = dns_question_new(1 + with_txt); + if (!q) + return -ENOMEM; + + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_SRV, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key); + if (r < 0) + return r; + + if (with_txt) { + dns_resource_key_unref(key); + key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_TXT, name); + if (!key) + return -ENOMEM; + + r = dns_question_add(q, key); + if (r < 0) + return r; + } + + *ret = q; + q = NULL; + + return 0; +} diff --git a/src/resolve/src/resolved-dns-question.h b/src/resolve/src/resolved-dns-question.h new file mode 100644 index 0000000000..ea41478975 --- /dev/null +++ b/src/resolve/src/resolved-dns-question.h @@ -0,0 +1,69 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +typedef struct DnsQuestion DnsQuestion; + +#include "macro.h" +#include "resolved-dns-rr.h" + +/* A simple array of resource keys */ + +struct DnsQuestion { + unsigned n_ref; + unsigned n_keys, n_allocated; + DnsResourceKey* keys[0]; +}; + +DnsQuestion *dns_question_new(unsigned n); +DnsQuestion *dns_question_ref(DnsQuestion *q); +DnsQuestion *dns_question_unref(DnsQuestion *q); + +int dns_question_new_address(DnsQuestion **ret, int family, const char *name, bool convert_idna); +int dns_question_new_reverse(DnsQuestion **ret, int family, const union in_addr_union *a); +int dns_question_new_service(DnsQuestion **ret, const char *service, const char *type, const char *domain, bool with_txt, bool convert_idna); + +int dns_question_add(DnsQuestion *q, DnsResourceKey *key); + +int dns_question_matches_rr(DnsQuestion *q, DnsResourceRecord *rr, const char *search_domain); +int dns_question_matches_cname_or_dname(DnsQuestion *q, DnsResourceRecord *rr, const char* search_domain); +int dns_question_is_valid_for_query(DnsQuestion *q); +int dns_question_contains(DnsQuestion *a, const DnsResourceKey *k); +int dns_question_is_equal(DnsQuestion *a, DnsQuestion *b); + +int dns_question_cname_redirect(DnsQuestion *q, const DnsResourceRecord *cname, DnsQuestion **ret); + +const char *dns_question_first_name(DnsQuestion *q); + +static inline unsigned dns_question_size(DnsQuestion *q) { + return q ? q->n_keys : 0; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsQuestion*, dns_question_unref); + +#define _DNS_QUESTION_FOREACH(u, key, q) \ + for (unsigned UNIQ_T(i, u) = ({ \ + (key) = ((q) && (q)->n_keys > 0) ? (q)->keys[0] : NULL; \ + 0; \ + }); \ + (q) && (UNIQ_T(i, u) < (q)->n_keys); \ + UNIQ_T(i, u)++, (key) = (UNIQ_T(i, u) < (q)->n_keys ? (q)->keys[UNIQ_T(i, u)] : NULL)) + +#define DNS_QUESTION_FOREACH(key, q) _DNS_QUESTION_FOREACH(UNIQ, key, q) diff --git a/src/resolve/src/resolved-dns-rr.c b/src/resolve/src/resolved-dns-rr.c new file mode 100644 index 0000000000..40f8e28dfd --- /dev/null +++ b/src/resolve/src/resolved-dns-rr.c @@ -0,0 +1,1521 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "alloc-util.h" +#include "dns-domain.h" +#include "dns-type.h" +#include "hexdecoct.h" +#include "resolved-dns-dnssec.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-rr.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" + +DnsResourceKey* dns_resource_key_new(uint16_t class, uint16_t type, const char *name) { + DnsResourceKey *k; + size_t l; + + assert(name); + + l = strlen(name); + k = malloc0(sizeof(DnsResourceKey) + l + 1); + if (!k) + return NULL; + + k->n_ref = 1; + k->class = class; + k->type = type; + + strcpy((char*) k + sizeof(DnsResourceKey), name); + + return k; +} + +DnsResourceKey* dns_resource_key_new_redirect(const DnsResourceKey *key, const DnsResourceRecord *cname) { + int r; + + assert(key); + assert(cname); + + assert(IN_SET(cname->key->type, DNS_TYPE_CNAME, DNS_TYPE_DNAME)); + + if (cname->key->type == DNS_TYPE_CNAME) + return dns_resource_key_new(key->class, key->type, cname->cname.name); + else { + DnsResourceKey *k; + char *destination = NULL; + + r = dns_name_change_suffix(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname->key), cname->dname.name, &destination); + if (r < 0) + return NULL; + if (r == 0) + return dns_resource_key_ref((DnsResourceKey*) key); + + k = dns_resource_key_new_consume(key->class, key->type, destination); + if (!k) { + free(destination); + return NULL; + } + + return k; + } +} + +int dns_resource_key_new_append_suffix(DnsResourceKey **ret, DnsResourceKey *key, char *name) { + DnsResourceKey *new_key; + char *joined; + int r; + + assert(ret); + assert(key); + assert(name); + + if (dns_name_is_root(name)) { + *ret = dns_resource_key_ref(key); + return 0; + } + + r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), name, &joined); + if (r < 0) + return r; + + new_key = dns_resource_key_new_consume(key->class, key->type, joined); + if (!new_key) { + free(joined); + return -ENOMEM; + } + + *ret = new_key; + return 0; +} + +DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name) { + DnsResourceKey *k; + + assert(name); + + k = new0(DnsResourceKey, 1); + if (!k) + return NULL; + + k->n_ref = 1; + k->class = class; + k->type = type; + k->_name = name; + + return k; +} + +DnsResourceKey* dns_resource_key_ref(DnsResourceKey *k) { + + if (!k) + return NULL; + + /* Static/const keys created with DNS_RESOURCE_KEY_CONST will + * set this to -1, they should not be reffed/unreffed */ + assert(k->n_ref != (unsigned) -1); + + assert(k->n_ref > 0); + k->n_ref++; + + return k; +} + +DnsResourceKey* dns_resource_key_unref(DnsResourceKey *k) { + if (!k) + return NULL; + + assert(k->n_ref != (unsigned) -1); + assert(k->n_ref > 0); + + if (k->n_ref == 1) { + free(k->_name); + free(k); + } else + k->n_ref--; + + return NULL; +} + +bool dns_resource_key_is_address(const DnsResourceKey *key) { + assert(key); + + /* Check if this is an A or AAAA resource key */ + + return key->class == DNS_CLASS_IN && IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_AAAA); +} + +int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b) { + int r; + + if (a == b) + return 1; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(a), DNS_RESOURCE_KEY_NAME(b)); + if (r <= 0) + return r; + + if (a->class != b->class) + return 0; + + if (a->type != b->type) + return 0; + + return 1; +} + +int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain) { + int r; + + assert(key); + assert(rr); + + if (key == rr->key) + return 1; + + /* Checks if an rr matches the specified key. If a search + * domain is specified, it will also be checked if the key + * with the search domain suffixed might match the RR. */ + + if (rr->key->class != key->class && key->class != DNS_CLASS_ANY) + return 0; + + if (rr->key->type != key->type && key->type != DNS_TYPE_ANY) + return 0; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), DNS_RESOURCE_KEY_NAME(key)); + if (r != 0) + return r; + + if (search_domain) { + _cleanup_free_ char *joined = NULL; + + r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), search_domain, &joined); + if (r < 0) + return r; + + return dns_name_equal(DNS_RESOURCE_KEY_NAME(rr->key), joined); + } + + return 0; +} + +int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain) { + int r; + + assert(key); + assert(cname); + + if (cname->class != key->class && key->class != DNS_CLASS_ANY) + return 0; + + if (cname->type == DNS_TYPE_CNAME) + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname)); + else if (cname->type == DNS_TYPE_DNAME) + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(cname)); + else + return 0; + + if (r != 0) + return r; + + if (search_domain) { + _cleanup_free_ char *joined = NULL; + + r = dns_name_concat(DNS_RESOURCE_KEY_NAME(key), search_domain, &joined); + if (r < 0) + return r; + + if (cname->type == DNS_TYPE_CNAME) + return dns_name_equal(joined, DNS_RESOURCE_KEY_NAME(cname)); + else if (cname->type == DNS_TYPE_DNAME) + return dns_name_endswith(joined, DNS_RESOURCE_KEY_NAME(cname)); + } + + return 0; +} + +int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa) { + assert(soa); + assert(key); + + /* Checks whether 'soa' is a SOA record for the specified key. */ + + if (soa->class != key->class) + return 0; + + if (soa->type != DNS_TYPE_SOA) + return 0; + + return dns_name_endswith(DNS_RESOURCE_KEY_NAME(key), DNS_RESOURCE_KEY_NAME(soa)); +} + +static void dns_resource_key_hash_func(const void *i, struct siphash *state) { + const DnsResourceKey *k = i; + + assert(k); + + dns_name_hash_func(DNS_RESOURCE_KEY_NAME(k), state); + siphash24_compress(&k->class, sizeof(k->class), state); + siphash24_compress(&k->type, sizeof(k->type), state); +} + +static int dns_resource_key_compare_func(const void *a, const void *b) { + const DnsResourceKey *x = a, *y = b; + int ret; + + ret = dns_name_compare_func(DNS_RESOURCE_KEY_NAME(x), DNS_RESOURCE_KEY_NAME(y)); + if (ret != 0) + return ret; + + if (x->type < y->type) + return -1; + if (x->type > y->type) + return 1; + + if (x->class < y->class) + return -1; + if (x->class > y->class) + return 1; + + return 0; +} + +const struct hash_ops dns_resource_key_hash_ops = { + .hash = dns_resource_key_hash_func, + .compare = dns_resource_key_compare_func +}; + +int dns_resource_key_to_string(const DnsResourceKey *key, char **ret) { + char cbuf[strlen("CLASS") + DECIMAL_STR_MAX(uint16_t)], tbuf[strlen("TYPE") + DECIMAL_STR_MAX(uint16_t)]; + const char *c, *t, *n; + char *s; + + /* If we cannot convert the CLASS/TYPE into a known string, + use the format recommended by RFC 3597, Section 5. */ + + c = dns_class_to_string(key->class); + if (!c) { + sprintf(cbuf, "CLASS%u", key->class); + c = cbuf; + } + + t = dns_type_to_string(key->type); + if (!t){ + sprintf(tbuf, "TYPE%u", key->type); + t = tbuf; + } + + n = DNS_RESOURCE_KEY_NAME(key); + if (asprintf(&s, "%s%s %s %-5s", n, endswith(n, ".") ? "" : ".", c, t) < 0) + return -ENOMEM; + + *ret = s; + return 0; +} + +bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b) { + assert(a); + assert(b); + + /* Try to replace one RR key by another if they are identical, thus saving a bit of memory. Note that we do + * this only for RR keys, not for RRs themselves, as they carry a lot of additional metadata (where they come + * from, validity data, and suchlike), and cannot be replaced so easily by other RRs that have the same + * superficial data. */ + + if (!*a) + return false; + if (!*b) + return false; + + /* We refuse merging const keys */ + if ((*a)->n_ref == (unsigned) -1) + return false; + if ((*b)->n_ref == (unsigned) -1) + return false; + + /* Already the same? */ + if (*a == *b) + return true; + + /* Are they really identical? */ + if (dns_resource_key_equal(*a, *b) <= 0) + return false; + + /* Keep the one which already has more references. */ + if ((*a)->n_ref > (*b)->n_ref) { + dns_resource_key_unref(*b); + *b = dns_resource_key_ref(*a); + } else { + dns_resource_key_unref(*a); + *a = dns_resource_key_ref(*b); + } + + return true; +} + +DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key) { + DnsResourceRecord *rr; + + rr = new0(DnsResourceRecord, 1); + if (!rr) + return NULL; + + rr->n_ref = 1; + rr->key = dns_resource_key_ref(key); + rr->expiry = USEC_INFINITY; + rr->n_skip_labels_signer = rr->n_skip_labels_source = (unsigned) -1; + + return rr; +} + +DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(class, type, name); + if (!key) + return NULL; + + return dns_resource_record_new(key); +} + +DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr) { + if (!rr) + return NULL; + + assert(rr->n_ref > 0); + rr->n_ref++; + + return rr; +} + +DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr) { + if (!rr) + return NULL; + + assert(rr->n_ref > 0); + + if (rr->n_ref > 1) { + rr->n_ref--; + return NULL; + } + + if (rr->key) { + switch(rr->key->type) { + + case DNS_TYPE_SRV: + free(rr->srv.name); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + free(rr->ptr.name); + break; + + case DNS_TYPE_HINFO: + free(rr->hinfo.cpu); + free(rr->hinfo.os); + break; + + case DNS_TYPE_TXT: + case DNS_TYPE_SPF: + dns_txt_item_free_all(rr->txt.items); + break; + + case DNS_TYPE_SOA: + free(rr->soa.mname); + free(rr->soa.rname); + break; + + case DNS_TYPE_MX: + free(rr->mx.exchange); + break; + + case DNS_TYPE_DS: + free(rr->ds.digest); + break; + + case DNS_TYPE_SSHFP: + free(rr->sshfp.fingerprint); + break; + + case DNS_TYPE_DNSKEY: + free(rr->dnskey.key); + break; + + case DNS_TYPE_RRSIG: + free(rr->rrsig.signer); + free(rr->rrsig.signature); + break; + + case DNS_TYPE_NSEC: + free(rr->nsec.next_domain_name); + bitmap_free(rr->nsec.types); + break; + + case DNS_TYPE_NSEC3: + free(rr->nsec3.next_hashed_name); + free(rr->nsec3.salt); + bitmap_free(rr->nsec3.types); + break; + + case DNS_TYPE_LOC: + case DNS_TYPE_A: + case DNS_TYPE_AAAA: + break; + + case DNS_TYPE_TLSA: + free(rr->tlsa.data); + break; + + case DNS_TYPE_OPENPGPKEY: + default: + free(rr->generic.data); + } + + free(rr->wire_format); + dns_resource_key_unref(rr->key); + } + + free(rr->to_string); + free(rr); + + return NULL; +} + +int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *hostname) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ char *ptr = NULL; + int r; + + assert(ret); + assert(address); + assert(hostname); + + r = dns_name_reverse(family, address, &ptr); + if (r < 0) + return r; + + key = dns_resource_key_new_consume(DNS_CLASS_IN, DNS_TYPE_PTR, ptr); + if (!key) + return -ENOMEM; + + ptr = NULL; + + rr = dns_resource_record_new(key); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(hostname); + if (!rr->ptr.name) + return -ENOMEM; + + *ret = rr; + rr = NULL; + + return 0; +} + +int dns_resource_record_new_address(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name) { + DnsResourceRecord *rr; + + assert(ret); + assert(address); + assert(family); + + if (family == AF_INET) { + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, name); + if (!rr) + return -ENOMEM; + + rr->a.in_addr = address->in; + + } else if (family == AF_INET6) { + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, name); + if (!rr) + return -ENOMEM; + + rr->aaaa.in6_addr = address->in6; + } else + return -EAFNOSUPPORT; + + *ret = rr; + + return 0; +} + +#define FIELD_EQUAL(a, b, field) \ + ((a).field ## _size == (b).field ## _size && \ + memcmp((a).field, (b).field, (a).field ## _size) == 0) + +int dns_resource_record_equal(const DnsResourceRecord *a, const DnsResourceRecord *b) { + int r; + + assert(a); + assert(b); + + if (a == b) + return 1; + + r = dns_resource_key_equal(a->key, b->key); + if (r <= 0) + return r; + + if (a->unparseable != b->unparseable) + return 0; + + switch (a->unparseable ? _DNS_TYPE_INVALID : a->key->type) { + + case DNS_TYPE_SRV: + r = dns_name_equal(a->srv.name, b->srv.name); + if (r <= 0) + return r; + + return a->srv.priority == b->srv.priority && + a->srv.weight == b->srv.weight && + a->srv.port == b->srv.port; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + return dns_name_equal(a->ptr.name, b->ptr.name); + + case DNS_TYPE_HINFO: + return strcaseeq(a->hinfo.cpu, b->hinfo.cpu) && + strcaseeq(a->hinfo.os, b->hinfo.os); + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: + return dns_txt_item_equal(a->txt.items, b->txt.items); + + case DNS_TYPE_A: + return memcmp(&a->a.in_addr, &b->a.in_addr, sizeof(struct in_addr)) == 0; + + case DNS_TYPE_AAAA: + return memcmp(&a->aaaa.in6_addr, &b->aaaa.in6_addr, sizeof(struct in6_addr)) == 0; + + case DNS_TYPE_SOA: + r = dns_name_equal(a->soa.mname, b->soa.mname); + if (r <= 0) + return r; + r = dns_name_equal(a->soa.rname, b->soa.rname); + if (r <= 0) + return r; + + return a->soa.serial == b->soa.serial && + a->soa.refresh == b->soa.refresh && + a->soa.retry == b->soa.retry && + a->soa.expire == b->soa.expire && + a->soa.minimum == b->soa.minimum; + + case DNS_TYPE_MX: + if (a->mx.priority != b->mx.priority) + return 0; + + return dns_name_equal(a->mx.exchange, b->mx.exchange); + + case DNS_TYPE_LOC: + assert(a->loc.version == b->loc.version); + + return a->loc.size == b->loc.size && + a->loc.horiz_pre == b->loc.horiz_pre && + a->loc.vert_pre == b->loc.vert_pre && + a->loc.latitude == b->loc.latitude && + a->loc.longitude == b->loc.longitude && + a->loc.altitude == b->loc.altitude; + + case DNS_TYPE_DS: + return a->ds.key_tag == b->ds.key_tag && + a->ds.algorithm == b->ds.algorithm && + a->ds.digest_type == b->ds.digest_type && + FIELD_EQUAL(a->ds, b->ds, digest); + + case DNS_TYPE_SSHFP: + return a->sshfp.algorithm == b->sshfp.algorithm && + a->sshfp.fptype == b->sshfp.fptype && + FIELD_EQUAL(a->sshfp, b->sshfp, fingerprint); + + case DNS_TYPE_DNSKEY: + return a->dnskey.flags == b->dnskey.flags && + a->dnskey.protocol == b->dnskey.protocol && + a->dnskey.algorithm == b->dnskey.algorithm && + FIELD_EQUAL(a->dnskey, b->dnskey, key); + + case DNS_TYPE_RRSIG: + /* do the fast comparisons first */ + return a->rrsig.type_covered == b->rrsig.type_covered && + a->rrsig.algorithm == b->rrsig.algorithm && + a->rrsig.labels == b->rrsig.labels && + a->rrsig.original_ttl == b->rrsig.original_ttl && + a->rrsig.expiration == b->rrsig.expiration && + a->rrsig.inception == b->rrsig.inception && + a->rrsig.key_tag == b->rrsig.key_tag && + FIELD_EQUAL(a->rrsig, b->rrsig, signature) && + dns_name_equal(a->rrsig.signer, b->rrsig.signer); + + case DNS_TYPE_NSEC: + return dns_name_equal(a->nsec.next_domain_name, b->nsec.next_domain_name) && + bitmap_equal(a->nsec.types, b->nsec.types); + + case DNS_TYPE_NSEC3: + return a->nsec3.algorithm == b->nsec3.algorithm && + a->nsec3.flags == b->nsec3.flags && + a->nsec3.iterations == b->nsec3.iterations && + FIELD_EQUAL(a->nsec3, b->nsec3, salt) && + FIELD_EQUAL(a->nsec3, b->nsec3, next_hashed_name) && + bitmap_equal(a->nsec3.types, b->nsec3.types); + + case DNS_TYPE_TLSA: + return a->tlsa.cert_usage == b->tlsa.cert_usage && + a->tlsa.selector == b->tlsa.selector && + a->tlsa.matching_type == b->tlsa.matching_type && + FIELD_EQUAL(a->tlsa, b->tlsa, data); + + default: + return FIELD_EQUAL(a->generic, b->generic, data); + } +} + +static char* format_location(uint32_t latitude, uint32_t longitude, uint32_t altitude, + uint8_t size, uint8_t horiz_pre, uint8_t vert_pre) { + char *s; + char NS = latitude >= 1U<<31 ? 'N' : 'S'; + char EW = longitude >= 1U<<31 ? 'E' : 'W'; + + int lat = latitude >= 1U<<31 ? (int) (latitude - (1U<<31)) : (int) ((1U<<31) - latitude); + int lon = longitude >= 1U<<31 ? (int) (longitude - (1U<<31)) : (int) ((1U<<31) - longitude); + double alt = altitude >= 10000000u ? altitude - 10000000u : -(double)(10000000u - altitude); + double siz = (size >> 4) * exp10((double) (size & 0xF)); + double hor = (horiz_pre >> 4) * exp10((double) (horiz_pre & 0xF)); + double ver = (vert_pre >> 4) * exp10((double) (vert_pre & 0xF)); + + if (asprintf(&s, "%d %d %.3f %c %d %d %.3f %c %.2fm %.2fm %.2fm %.2fm", + (lat / 60000 / 60), + (lat / 60000) % 60, + (lat % 60000) / 1000., + NS, + (lon / 60000 / 60), + (lon / 60000) % 60, + (lon % 60000) / 1000., + EW, + alt / 100., + siz / 100., + hor / 100., + ver / 100.) < 0) + return NULL; + + return s; +} + +static int format_timestamp_dns(char *buf, size_t l, time_t sec) { + struct tm tm; + + assert(buf); + assert(l > strlen("YYYYMMDDHHmmSS")); + + if (!gmtime_r(&sec, &tm)) + return -EINVAL; + + if (strftime(buf, l, "%Y%m%d%H%M%S", &tm) <= 0) + return -EINVAL; + + return 0; +} + +static char *format_types(Bitmap *types) { + _cleanup_strv_free_ char **strv = NULL; + _cleanup_free_ char *str = NULL; + Iterator i; + unsigned type; + int r; + + BITMAP_FOREACH(type, types, i) { + if (dns_type_to_string(type)) { + r = strv_extend(&strv, dns_type_to_string(type)); + if (r < 0) + return NULL; + } else { + char *t; + + r = asprintf(&t, "TYPE%u", type); + if (r < 0) + return NULL; + + r = strv_consume(&strv, t); + if (r < 0) + return NULL; + } + } + + str = strv_join(strv, " "); + if (!str) + return NULL; + + return strjoin("( ", str, " )", NULL); +} + +static char *format_txt(DnsTxtItem *first) { + DnsTxtItem *i; + size_t c = 1; + char *p, *s; + + LIST_FOREACH(items, i, first) + c += i->length * 4 + 3; + + p = s = new(char, c); + if (!s) + return NULL; + + LIST_FOREACH(items, i, first) { + size_t j; + + if (i != first) + *(p++) = ' '; + + *(p++) = '"'; + + for (j = 0; j < i->length; j++) { + if (i->data[j] < ' ' || i->data[j] == '"' || i->data[j] >= 127) { + *(p++) = '\\'; + *(p++) = '0' + (i->data[j] / 100); + *(p++) = '0' + ((i->data[j] / 10) % 10); + *(p++) = '0' + (i->data[j] % 10); + } else + *(p++) = i->data[j]; + } + + *(p++) = '"'; + } + + *p = 0; + return s; +} + +const char *dns_resource_record_to_string(DnsResourceRecord *rr) { + _cleanup_free_ char *k = NULL, *t = NULL; + char *s; + int r; + + assert(rr); + + if (rr->to_string) + return rr->to_string; + + r = dns_resource_key_to_string(rr->key, &k); + if (r < 0) + return NULL; + + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_SRV: + r = asprintf(&s, "%s %u %u %u %s", + k, + rr->srv.priority, + rr->srv.weight, + rr->srv.port, + strna(rr->srv.name)); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + s = strjoin(k, " ", rr->ptr.name, NULL); + if (!s) + return NULL; + + break; + + case DNS_TYPE_HINFO: + s = strjoin(k, " ", rr->hinfo.cpu, " ", rr->hinfo.os, NULL); + if (!s) + return NULL; + break; + + case DNS_TYPE_SPF: /* exactly the same as TXT */ + case DNS_TYPE_TXT: + t = format_txt(rr->txt.items); + if (!t) + return NULL; + + s = strjoin(k, " ", t, NULL); + if (!s) + return NULL; + break; + + case DNS_TYPE_A: { + _cleanup_free_ char *x = NULL; + + r = in_addr_to_string(AF_INET, (const union in_addr_union*) &rr->a.in_addr, &x); + if (r < 0) + return NULL; + + s = strjoin(k, " ", x, NULL); + if (!s) + return NULL; + break; + } + + case DNS_TYPE_AAAA: + r = in_addr_to_string(AF_INET6, (const union in_addr_union*) &rr->aaaa.in6_addr, &t); + if (r < 0) + return NULL; + + s = strjoin(k, " ", t, NULL); + if (!s) + return NULL; + break; + + case DNS_TYPE_SOA: + r = asprintf(&s, "%s %s %s %u %u %u %u %u", + k, + strna(rr->soa.mname), + strna(rr->soa.rname), + rr->soa.serial, + rr->soa.refresh, + rr->soa.retry, + rr->soa.expire, + rr->soa.minimum); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_MX: + r = asprintf(&s, "%s %u %s", + k, + rr->mx.priority, + rr->mx.exchange); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_LOC: + assert(rr->loc.version == 0); + + t = format_location(rr->loc.latitude, + rr->loc.longitude, + rr->loc.altitude, + rr->loc.size, + rr->loc.horiz_pre, + rr->loc.vert_pre); + if (!t) + return NULL; + + s = strjoin(k, " ", t, NULL); + if (!s) + return NULL; + break; + + case DNS_TYPE_DS: + t = hexmem(rr->ds.digest, rr->ds.digest_size); + if (!t) + return NULL; + + r = asprintf(&s, "%s %u %u %u %s", + k, + rr->ds.key_tag, + rr->ds.algorithm, + rr->ds.digest_type, + t); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_SSHFP: + t = hexmem(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size); + if (!t) + return NULL; + + r = asprintf(&s, "%s %u %u %s", + k, + rr->sshfp.algorithm, + rr->sshfp.fptype, + t); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_DNSKEY: { + _cleanup_free_ char *alg = NULL; + char *ss; + int n, n1; + uint16_t key_tag; + + key_tag = dnssec_keytag(rr, true); + + r = dnssec_algorithm_to_string_alloc(rr->dnskey.algorithm, &alg); + if (r < 0) + return NULL; + + r = asprintf(&s, "%s %n%u %u %s %n", + k, + &n1, + rr->dnskey.flags, + rr->dnskey.protocol, + alg, + &n); + if (r < 0) + return NULL; + + r = base64_append(&s, n, + rr->dnskey.key, rr->dnskey.key_size, + 8, columns()); + if (r < 0) + return NULL; + + r = asprintf(&ss, "%s\n" + "%*s-- Flags:%s%s%s\n" + "%*s-- Key tag: %u", + s, + n1, "", + rr->dnskey.flags & DNSKEY_FLAG_SEP ? " SEP" : "", + rr->dnskey.flags & DNSKEY_FLAG_REVOKE ? " REVOKE" : "", + rr->dnskey.flags & DNSKEY_FLAG_ZONE_KEY ? " ZONE_KEY" : "", + n1, "", + key_tag); + if (r < 0) + return NULL; + free(s); + s = ss; + + break; + } + + case DNS_TYPE_RRSIG: { + _cleanup_free_ char *alg = NULL; + char expiration[strlen("YYYYMMDDHHmmSS") + 1], inception[strlen("YYYYMMDDHHmmSS") + 1]; + const char *type; + int n; + + type = dns_type_to_string(rr->rrsig.type_covered); + + r = dnssec_algorithm_to_string_alloc(rr->rrsig.algorithm, &alg); + if (r < 0) + return NULL; + + r = format_timestamp_dns(expiration, sizeof(expiration), rr->rrsig.expiration); + if (r < 0) + return NULL; + + r = format_timestamp_dns(inception, sizeof(inception), rr->rrsig.inception); + if (r < 0) + return NULL; + + /* TYPE?? follows + * http://tools.ietf.org/html/rfc3597#section-5 */ + + r = asprintf(&s, "%s %s%.*u %s %u %u %s %s %u %s %n", + k, + type ?: "TYPE", + type ? 0 : 1, type ? 0u : (unsigned) rr->rrsig.type_covered, + alg, + rr->rrsig.labels, + rr->rrsig.original_ttl, + expiration, + inception, + rr->rrsig.key_tag, + rr->rrsig.signer, + &n); + if (r < 0) + return NULL; + + r = base64_append(&s, n, + rr->rrsig.signature, rr->rrsig.signature_size, + 8, columns()); + if (r < 0) + return NULL; + + break; + } + + case DNS_TYPE_NSEC: + t = format_types(rr->nsec.types); + if (!t) + return NULL; + + r = asprintf(&s, "%s %s %s", + k, + rr->nsec.next_domain_name, + t); + if (r < 0) + return NULL; + break; + + case DNS_TYPE_NSEC3: { + _cleanup_free_ char *salt = NULL, *hash = NULL; + + if (rr->nsec3.salt_size > 0) { + salt = hexmem(rr->nsec3.salt, rr->nsec3.salt_size); + if (!salt) + return NULL; + } + + hash = base32hexmem(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, false); + if (!hash) + return NULL; + + t = format_types(rr->nsec3.types); + if (!t) + return NULL; + + r = asprintf(&s, "%s %"PRIu8" %"PRIu8" %"PRIu16" %s %s %s", + k, + rr->nsec3.algorithm, + rr->nsec3.flags, + rr->nsec3.iterations, + rr->nsec3.salt_size > 0 ? salt : "-", + hash, + t); + if (r < 0) + return NULL; + + break; + } + + case DNS_TYPE_TLSA: { + const char *cert_usage, *selector, *matching_type; + char *ss; + int n; + + cert_usage = tlsa_cert_usage_to_string(rr->tlsa.cert_usage); + selector = tlsa_selector_to_string(rr->tlsa.selector); + matching_type = tlsa_matching_type_to_string(rr->tlsa.matching_type); + + r = asprintf(&s, "%s %u %u %u %n", + k, + rr->tlsa.cert_usage, + rr->tlsa.selector, + rr->tlsa.matching_type, + &n); + if (r < 0) + return NULL; + + r = base64_append(&s, n, + rr->tlsa.data, rr->tlsa.data_size, + 8, columns()); + if (r < 0) + return NULL; + + r = asprintf(&ss, "%s\n" + "%*s-- Cert. usage: %s\n" + "%*s-- Selector: %s\n" + "%*s-- Matching type: %s", + s, + n - 6, "", cert_usage, + n - 6, "", selector, + n - 6, "", matching_type); + if (r < 0) + return NULL; + free(s); + s = ss; + + break; + } + + case DNS_TYPE_OPENPGPKEY: { + int n; + + r = asprintf(&s, "%s %n", + k, + &n); + if (r < 0) + return NULL; + + r = base64_append(&s, n, + rr->generic.data, rr->generic.data_size, + 8, columns()); + if (r < 0) + return NULL; + break; + } + + default: + t = hexmem(rr->generic.data, rr->generic.data_size); + if (!t) + return NULL; + + /* Format as documented in RFC 3597, Section 5 */ + r = asprintf(&s, "%s \\# %zu %s", k, rr->generic.data_size, t); + if (r < 0) + return NULL; + break; + } + + rr->to_string = s; + return s; +} + +int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical) { + + DnsPacket packet = { + .n_ref = 1, + .protocol = DNS_PROTOCOL_DNS, + .on_stack = true, + .refuse_compression = true, + .canonical_form = canonical, + }; + + size_t start, rds; + int r; + + assert(rr); + + /* Generates the RR in wire-format, optionally in the + * canonical form as discussed in the DNSSEC RFC 4034, Section + * 6.2. We allocate a throw-away DnsPacket object on the stack + * here, because we need some book-keeping for memory + * management, and can reuse the DnsPacket serializer, that + * can generate the canonical form, too, but also knows label + * compression and suchlike. */ + + if (rr->wire_format && rr->wire_format_canonical == canonical) + return 0; + + r = dns_packet_append_rr(&packet, rr, &start, &rds); + if (r < 0) + return r; + + assert(start == 0); + assert(packet._data); + + free(rr->wire_format); + rr->wire_format = packet._data; + rr->wire_format_size = packet.size; + rr->wire_format_rdata_offset = rds; + rr->wire_format_canonical = canonical; + + packet._data = NULL; + dns_packet_unref(&packet); + + return 0; +} + +int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret) { + const char *n; + int r; + + assert(rr); + assert(ret); + + /* Returns the RRset's signer, if it is known. */ + + if (rr->n_skip_labels_signer == (unsigned) -1) + return -ENODATA; + + n = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_skip(n, rr->n_skip_labels_signer, &n); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + *ret = n; + return 0; +} + +int dns_resource_record_source(DnsResourceRecord *rr, const char **ret) { + const char *n; + int r; + + assert(rr); + assert(ret); + + /* Returns the RRset's synthesizing source, if it is known. */ + + if (rr->n_skip_labels_source == (unsigned) -1) + return -ENODATA; + + n = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_skip(n, rr->n_skip_labels_source, &n); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + *ret = n; + return 0; +} + +int dns_resource_record_is_signer(DnsResourceRecord *rr, const char *zone) { + const char *signer; + int r; + + assert(rr); + + r = dns_resource_record_signer(rr, &signer); + if (r < 0) + return r; + + return dns_name_equal(zone, signer); +} + +int dns_resource_record_is_synthetic(DnsResourceRecord *rr) { + int r; + + assert(rr); + + /* Returns > 0 if the RR is generated from a wildcard, and is not the asterisk name itself */ + + if (rr->n_skip_labels_source == (unsigned) -1) + return -ENODATA; + + if (rr->n_skip_labels_source == 0) + return 0; + + if (rr->n_skip_labels_source > 1) + return 1; + + r = dns_name_startswith(DNS_RESOURCE_KEY_NAME(rr->key), "*"); + if (r < 0) + return r; + + return !r; +} + +static void dns_resource_record_hash_func(const void *i, struct siphash *state) { + const DnsResourceRecord *rr = i; + + assert(rr); + + dns_resource_key_hash_func(rr->key, state); + + switch (rr->unparseable ? _DNS_TYPE_INVALID : rr->key->type) { + + case DNS_TYPE_SRV: + siphash24_compress(&rr->srv.priority, sizeof(rr->srv.priority), state); + siphash24_compress(&rr->srv.weight, sizeof(rr->srv.weight), state); + siphash24_compress(&rr->srv.port, sizeof(rr->srv.port), state); + dns_name_hash_func(rr->srv.name, state); + break; + + case DNS_TYPE_PTR: + case DNS_TYPE_NS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: + dns_name_hash_func(rr->ptr.name, state); + break; + + case DNS_TYPE_HINFO: + string_hash_func(rr->hinfo.cpu, state); + string_hash_func(rr->hinfo.os, state); + break; + + case DNS_TYPE_TXT: + case DNS_TYPE_SPF: { + DnsTxtItem *j; + + LIST_FOREACH(items, j, rr->txt.items) { + siphash24_compress(j->data, j->length, state); + + /* Add an extra NUL byte, so that "a" followed by "b" doesn't result in the same hash as "ab" + * followed by "". */ + siphash24_compress_byte(0, state); + } + break; + } + + case DNS_TYPE_A: + siphash24_compress(&rr->a.in_addr, sizeof(rr->a.in_addr), state); + break; + + case DNS_TYPE_AAAA: + siphash24_compress(&rr->aaaa.in6_addr, sizeof(rr->aaaa.in6_addr), state); + break; + + case DNS_TYPE_SOA: + dns_name_hash_func(rr->soa.mname, state); + dns_name_hash_func(rr->soa.rname, state); + siphash24_compress(&rr->soa.serial, sizeof(rr->soa.serial), state); + siphash24_compress(&rr->soa.refresh, sizeof(rr->soa.refresh), state); + siphash24_compress(&rr->soa.retry, sizeof(rr->soa.retry), state); + siphash24_compress(&rr->soa.expire, sizeof(rr->soa.expire), state); + siphash24_compress(&rr->soa.minimum, sizeof(rr->soa.minimum), state); + break; + + case DNS_TYPE_MX: + siphash24_compress(&rr->mx.priority, sizeof(rr->mx.priority), state); + dns_name_hash_func(rr->mx.exchange, state); + break; + + case DNS_TYPE_LOC: + siphash24_compress(&rr->loc.version, sizeof(rr->loc.version), state); + siphash24_compress(&rr->loc.size, sizeof(rr->loc.size), state); + siphash24_compress(&rr->loc.horiz_pre, sizeof(rr->loc.horiz_pre), state); + siphash24_compress(&rr->loc.vert_pre, sizeof(rr->loc.vert_pre), state); + siphash24_compress(&rr->loc.latitude, sizeof(rr->loc.latitude), state); + siphash24_compress(&rr->loc.longitude, sizeof(rr->loc.longitude), state); + siphash24_compress(&rr->loc.altitude, sizeof(rr->loc.altitude), state); + break; + + case DNS_TYPE_SSHFP: + siphash24_compress(&rr->sshfp.algorithm, sizeof(rr->sshfp.algorithm), state); + siphash24_compress(&rr->sshfp.fptype, sizeof(rr->sshfp.fptype), state); + siphash24_compress(rr->sshfp.fingerprint, rr->sshfp.fingerprint_size, state); + break; + + case DNS_TYPE_DNSKEY: + siphash24_compress(&rr->dnskey.flags, sizeof(rr->dnskey.flags), state); + siphash24_compress(&rr->dnskey.protocol, sizeof(rr->dnskey.protocol), state); + siphash24_compress(&rr->dnskey.algorithm, sizeof(rr->dnskey.algorithm), state); + siphash24_compress(rr->dnskey.key, rr->dnskey.key_size, state); + break; + + case DNS_TYPE_RRSIG: + siphash24_compress(&rr->rrsig.type_covered, sizeof(rr->rrsig.type_covered), state); + siphash24_compress(&rr->rrsig.algorithm, sizeof(rr->rrsig.algorithm), state); + siphash24_compress(&rr->rrsig.labels, sizeof(rr->rrsig.labels), state); + siphash24_compress(&rr->rrsig.original_ttl, sizeof(rr->rrsig.original_ttl), state); + siphash24_compress(&rr->rrsig.expiration, sizeof(rr->rrsig.expiration), state); + siphash24_compress(&rr->rrsig.inception, sizeof(rr->rrsig.inception), state); + siphash24_compress(&rr->rrsig.key_tag, sizeof(rr->rrsig.key_tag), state); + dns_name_hash_func(rr->rrsig.signer, state); + siphash24_compress(rr->rrsig.signature, rr->rrsig.signature_size, state); + break; + + case DNS_TYPE_NSEC: + dns_name_hash_func(rr->nsec.next_domain_name, state); + /* FIXME: we leave out the type bitmap here. Hash + * would be better if we'd take it into account + * too. */ + break; + + case DNS_TYPE_DS: + siphash24_compress(&rr->ds.key_tag, sizeof(rr->ds.key_tag), state); + siphash24_compress(&rr->ds.algorithm, sizeof(rr->ds.algorithm), state); + siphash24_compress(&rr->ds.digest_type, sizeof(rr->ds.digest_type), state); + siphash24_compress(rr->ds.digest, rr->ds.digest_size, state); + break; + + case DNS_TYPE_NSEC3: + siphash24_compress(&rr->nsec3.algorithm, sizeof(rr->nsec3.algorithm), state); + siphash24_compress(&rr->nsec3.flags, sizeof(rr->nsec3.flags), state); + siphash24_compress(&rr->nsec3.iterations, sizeof(rr->nsec3.iterations), state); + siphash24_compress(rr->nsec3.salt, rr->nsec3.salt_size, state); + siphash24_compress(rr->nsec3.next_hashed_name, rr->nsec3.next_hashed_name_size, state); + /* FIXME: We leave the bitmaps out */ + break; + + case DNS_TYPE_TLSA: + siphash24_compress(&rr->tlsa.cert_usage, sizeof(rr->tlsa.cert_usage), state); + siphash24_compress(&rr->tlsa.selector, sizeof(rr->tlsa.selector), state); + siphash24_compress(&rr->tlsa.matching_type, sizeof(rr->tlsa.matching_type), state); + siphash24_compress(&rr->tlsa.data, rr->tlsa.data_size, state); + break; + + case DNS_TYPE_OPENPGPKEY: + default: + siphash24_compress(rr->generic.data, rr->generic.data_size, state); + break; + } +} + +static int dns_resource_record_compare_func(const void *a, const void *b) { + const DnsResourceRecord *x = a, *y = b; + int ret; + + ret = dns_resource_key_compare_func(x->key, y->key); + if (ret != 0) + return ret; + + if (dns_resource_record_equal(x, y)) + return 0; + + /* This is a bit dirty, we don't implement proper ordering, but + * the hashtable doesn't need ordering anyway, hence we don't + * care. */ + return x < y ? -1 : 1; +} + +const struct hash_ops dns_resource_record_hash_ops = { + .hash = dns_resource_record_hash_func, + .compare = dns_resource_record_compare_func, +}; + +DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i) { + DnsTxtItem *n; + + if (!i) + return NULL; + + n = i->items_next; + + free(i); + return dns_txt_item_free_all(n); +} + +bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b) { + + if (a == b) + return true; + + if (!a != !b) + return false; + + if (!a) + return true; + + if (a->length != b->length) + return false; + + if (memcmp(a->data, b->data, a->length) != 0) + return false; + + return dns_txt_item_equal(a->items_next, b->items_next); +} + +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", + [DNSSEC_ALGORITHM_DH] = "DH", + [DNSSEC_ALGORITHM_DSA] = "DSA", + [DNSSEC_ALGORITHM_ECC] = "ECC", + [DNSSEC_ALGORITHM_RSASHA1] = "RSASHA1", + [DNSSEC_ALGORITHM_DSA_NSEC3_SHA1] = "DSA-NSEC3-SHA1", + [DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1] = "RSASHA1-NSEC3-SHA1", + [DNSSEC_ALGORITHM_RSASHA256] = "RSASHA256", + [DNSSEC_ALGORITHM_RSASHA512] = "RSASHA512", + [DNSSEC_ALGORITHM_ECC_GOST] = "ECC-GOST", + [DNSSEC_ALGORITHM_ECDSAP256SHA256] = "ECDSAP256SHA256", + [DNSSEC_ALGORITHM_ECDSAP384SHA384] = "ECDSAP384SHA384", + [DNSSEC_ALGORITHM_INDIRECT] = "INDIRECT", + [DNSSEC_ALGORITHM_PRIVATEDNS] = "PRIVATEDNS", + [DNSSEC_ALGORITHM_PRIVATEOID] = "PRIVATEOID", +}; +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_algorithm, int, 255); + +static const char* const dnssec_digest_table[_DNSSEC_DIGEST_MAX_DEFINED] = { + /* Names as listed on https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ + [DNSSEC_DIGEST_SHA1] = "SHA-1", + [DNSSEC_DIGEST_SHA256] = "SHA-256", + [DNSSEC_DIGEST_GOST_R_34_11_94] = "GOST_R_34.11-94", + [DNSSEC_DIGEST_SHA384] = "SHA-384", +}; +DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(dnssec_digest, int, 255); diff --git a/src/resolve/src/resolved-dns-rr.h b/src/resolve/src/resolved-dns-rr.h new file mode 100644 index 0000000000..2e0dfbaba3 --- /dev/null +++ b/src/resolve/src/resolved-dns-rr.h @@ -0,0 +1,333 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . + ***/ + +#include + +#include "bitmap.h" +#include "dns-type.h" +#include "hashmap.h" +#include "in-addr-util.h" +#include "list.h" + +typedef struct DnsResourceKey DnsResourceKey; +typedef struct DnsResourceRecord DnsResourceRecord; +typedef struct DnsTxtItem DnsTxtItem; + +/* DNSKEY RR flags */ +#define DNSKEY_FLAG_SEP (UINT16_C(1) << 0) +#define DNSKEY_FLAG_REVOKE (UINT16_C(1) << 7) +#define DNSKEY_FLAG_ZONE_KEY (UINT16_C(1) << 8) + +/* mDNS RR flags */ +#define MDNS_RR_CACHE_FLUSH (UINT16_C(1) << 15) + +/* DNSSEC algorithm identifiers, see + * http://tools.ietf.org/html/rfc4034#appendix-A.1 and + * https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml */ +enum { + DNSSEC_ALGORITHM_RSAMD5 = 1, + DNSSEC_ALGORITHM_DH, + DNSSEC_ALGORITHM_DSA, + DNSSEC_ALGORITHM_ECC, + DNSSEC_ALGORITHM_RSASHA1, + DNSSEC_ALGORITHM_DSA_NSEC3_SHA1, + DNSSEC_ALGORITHM_RSASHA1_NSEC3_SHA1, + DNSSEC_ALGORITHM_RSASHA256 = 8, /* RFC 5702 */ + DNSSEC_ALGORITHM_RSASHA512 = 10, /* RFC 5702 */ + DNSSEC_ALGORITHM_ECC_GOST = 12, /* RFC 5933 */ + DNSSEC_ALGORITHM_ECDSAP256SHA256 = 13, /* RFC 6605 */ + DNSSEC_ALGORITHM_ECDSAP384SHA384 = 14, /* RFC 6605 */ + DNSSEC_ALGORITHM_INDIRECT = 252, + DNSSEC_ALGORITHM_PRIVATEDNS, + DNSSEC_ALGORITHM_PRIVATEOID, + _DNSSEC_ALGORITHM_MAX_DEFINED +}; + +/* DNSSEC digest identifiers, see + * https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml */ +enum { + DNSSEC_DIGEST_SHA1 = 1, + DNSSEC_DIGEST_SHA256 = 2, /* RFC 4509 */ + DNSSEC_DIGEST_GOST_R_34_11_94 = 3, /* RFC 5933 */ + DNSSEC_DIGEST_SHA384 = 4, /* RFC 6605 */ + _DNSSEC_DIGEST_MAX_DEFINED +}; + +/* DNSSEC NSEC3 hash algorithms, see + * https://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml */ +enum { + NSEC3_ALGORITHM_SHA1 = 1, + _NSEC3_ALGORITHM_MAX_DEFINED +}; + +struct DnsResourceKey { + unsigned n_ref; /* (unsigned -1) for const keys, see below */ + uint16_t class, type; + char *_name; /* don't access directy, use DNS_RESOURCE_KEY_NAME()! */ +}; + +/* Creates a temporary resource key. This is only useful to quickly + * look up something, without allocating a full DnsResourceKey object + * for it. Note that it is not OK to take references to this kind of + * resource key object. */ +#define DNS_RESOURCE_KEY_CONST(c, t, n) \ + ((DnsResourceKey) { \ + .n_ref = (unsigned) -1, \ + .class = c, \ + .type = t, \ + ._name = (char*) n, \ + }) + + +struct DnsTxtItem { + size_t length; + LIST_FIELDS(DnsTxtItem, items); + uint8_t data[]; +}; + +struct DnsResourceRecord { + unsigned n_ref; + DnsResourceKey *key; + + char *to_string; + + uint32_t ttl; + usec_t expiry; /* RRSIG signature expiry */ + + /* How many labels to strip to determine "signer" of the RRSIG (aka, the zone). -1 if not signed. */ + unsigned n_skip_labels_signer; + /* How many labels to strip to determine "synthesizing source" of this RR, i.e. the wildcard's immediate parent. -1 if not signed. */ + unsigned n_skip_labels_source; + + bool unparseable:1; + + bool wire_format_canonical:1; + void *wire_format; + size_t wire_format_size; + size_t wire_format_rdata_offset; + + union { + struct { + void *data; + size_t data_size; + } generic, opt; + + struct { + uint16_t priority; + uint16_t weight; + uint16_t port; + char *name; + } srv; + + struct { + char *name; + } ptr, ns, cname, dname; + + struct { + char *cpu; + char *os; + } hinfo; + + struct { + DnsTxtItem *items; + } txt, spf; + + struct { + struct in_addr in_addr; + } a; + + struct { + struct in6_addr in6_addr; + } aaaa; + + struct { + char *mname; + char *rname; + uint32_t serial; + uint32_t refresh; + uint32_t retry; + uint32_t expire; + uint32_t minimum; + } soa; + + struct { + uint16_t priority; + char *exchange; + } mx; + + /* https://tools.ietf.org/html/rfc1876 */ + struct { + uint8_t version; + uint8_t size; + uint8_t horiz_pre; + uint8_t vert_pre; + uint32_t latitude; + uint32_t longitude; + uint32_t altitude; + } loc; + + /* https://tools.ietf.org/html/rfc4255#section-3.1 */ + struct { + uint8_t algorithm; + uint8_t fptype; + void *fingerprint; + size_t fingerprint_size; + } sshfp; + + /* http://tools.ietf.org/html/rfc4034#section-2.1 */ + struct { + uint16_t flags; + uint8_t protocol; + uint8_t algorithm; + void* key; + size_t key_size; + } dnskey; + + /* http://tools.ietf.org/html/rfc4034#section-3.1 */ + struct { + uint16_t type_covered; + uint8_t algorithm; + uint8_t labels; + uint32_t original_ttl; + uint32_t expiration; + uint32_t inception; + uint16_t key_tag; + char *signer; + void *signature; + size_t signature_size; + } rrsig; + + /* https://tools.ietf.org/html/rfc4034#section-4.1 */ + struct { + char *next_domain_name; + Bitmap *types; + } nsec; + + /* https://tools.ietf.org/html/rfc4034#section-5.1 */ + struct { + uint16_t key_tag; + uint8_t algorithm; + uint8_t digest_type; + void *digest; + size_t digest_size; + } ds; + + struct { + uint8_t algorithm; + uint8_t flags; + uint16_t iterations; + void *salt; + size_t salt_size; + void *next_hashed_name; + size_t next_hashed_name_size; + Bitmap *types; + } nsec3; + + /* https://tools.ietf.org/html/draft-ietf-dane-protocol-23 */ + struct { + uint8_t cert_usage; + uint8_t selector; + uint8_t matching_type; + void *data; + size_t data_size; + } tlsa; + }; +}; + +static inline const char* DNS_RESOURCE_KEY_NAME(const DnsResourceKey *key) { + if (!key) + return NULL; + + if (key->_name) + return key->_name; + + return (char*) key + sizeof(DnsResourceKey); +} + +static inline const void* DNS_RESOURCE_RECORD_RDATA(DnsResourceRecord *rr) { + if (!rr) + return NULL; + + if (!rr->wire_format) + return NULL; + + assert(rr->wire_format_rdata_offset <= rr->wire_format_size); + return (uint8_t*) rr->wire_format + rr->wire_format_rdata_offset; +} + +static inline size_t DNS_RESOURCE_RECORD_RDATA_SIZE(DnsResourceRecord *rr) { + if (!rr) + return 0; + if (!rr->wire_format) + return 0; + + assert(rr->wire_format_rdata_offset <= rr->wire_format_size); + return rr->wire_format_size - rr->wire_format_rdata_offset; +} + +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); +DnsResourceKey* dns_resource_key_new_consume(uint16_t class, uint16_t type, char *name); +DnsResourceKey* dns_resource_key_ref(DnsResourceKey *key); +DnsResourceKey* dns_resource_key_unref(DnsResourceKey *key); +bool dns_resource_key_is_address(const DnsResourceKey *key); +int dns_resource_key_equal(const DnsResourceKey *a, const DnsResourceKey *b); +int dns_resource_key_match_rr(const DnsResourceKey *key, DnsResourceRecord *rr, const char *search_domain); +int dns_resource_key_match_cname_or_dname(const DnsResourceKey *key, const DnsResourceKey *cname, const char *search_domain); +int dns_resource_key_match_soa(const DnsResourceKey *key, const DnsResourceKey *soa); +int dns_resource_key_to_string(const DnsResourceKey *key, char **ret); +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceKey*, dns_resource_key_unref); + +static inline bool dns_key_is_shared(const DnsResourceKey *key) { + return IN_SET(key->type, DNS_TYPE_PTR); +} + +bool dns_resource_key_reduce(DnsResourceKey **a, DnsResourceKey **b); + +DnsResourceRecord* dns_resource_record_new(DnsResourceKey *key); +DnsResourceRecord* dns_resource_record_new_full(uint16_t class, uint16_t type, const char *name); +DnsResourceRecord* dns_resource_record_ref(DnsResourceRecord *rr); +DnsResourceRecord* dns_resource_record_unref(DnsResourceRecord *rr); +int dns_resource_record_new_reverse(DnsResourceRecord **ret, int family, const union in_addr_union *address, const char *name); +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); +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsResourceRecord*, dns_resource_record_unref); + +int dns_resource_record_to_wire_format(DnsResourceRecord *rr, bool canonical); + +int dns_resource_record_signer(DnsResourceRecord *rr, const char **ret); +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); + +DnsTxtItem *dns_txt_item_free_all(DnsTxtItem *i); +bool dns_txt_item_equal(DnsTxtItem *a, DnsTxtItem *b); + +extern const struct hash_ops dns_resource_key_hash_ops; +extern const struct hash_ops dns_resource_record_hash_ops; + +int dnssec_algorithm_to_string_alloc(int i, char **ret); +int dnssec_algorithm_from_string(const char *s) _pure_; + +int dnssec_digest_to_string_alloc(int i, char **ret); +int dnssec_digest_from_string(const char *s) _pure_; diff --git a/src/resolve/src/resolved-dns-scope.c b/src/resolve/src/resolved-dns-scope.c new file mode 100644 index 0000000000..a406872a38 --- /dev/null +++ b/src/resolve/src/resolved-dns-scope.c @@ -0,0 +1,1028 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "af-list.h" +#include "alloc-util.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "hostname-util.h" +#include "missing.h" +#include "random-util.h" +#include "resolved-dns-scope.h" +#include "resolved-llmnr.h" +#include "resolved-mdns.h" +#include "socket-util.h" +#include "strv.h" + +#define MULTICAST_RATELIMIT_INTERVAL_USEC (1*USEC_PER_SEC) +#define MULTICAST_RATELIMIT_BURST 1000 + +/* After how much time to repeat LLMNR requests, see RFC 4795 Section 7 */ +#define MULTICAST_RESEND_TIMEOUT_MIN_USEC (100 * USEC_PER_MSEC) +#define MULTICAST_RESEND_TIMEOUT_MAX_USEC (1 * USEC_PER_SEC) + +int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol protocol, int family) { + DnsScope *s; + + assert(m); + assert(ret); + + s = new0(DnsScope, 1); + if (!s) + return -ENOMEM; + + s->manager = m; + s->link = l; + s->protocol = protocol; + s->family = family; + s->resend_timeout = MULTICAST_RESEND_TIMEOUT_MIN_USEC; + + if (protocol == DNS_PROTOCOL_DNS) { + /* Copy DNSSEC mode from the link if it is set there, + * otherwise take the manager's DNSSEC mode. Note that + * we copy this only at scope creation time, and do + * not update it from the on, even if the setting + * changes. */ + + if (l) + s->dnssec_mode = link_get_dnssec_mode(l); + else + s->dnssec_mode = manager_get_dnssec_mode(m); + } else + s->dnssec_mode = DNSSEC_NO; + + LIST_PREPEND(scopes, m->dns_scopes, s); + + dns_scope_llmnr_membership(s, true); + dns_scope_mdns_membership(s, true); + + log_debug("New scope on link %s, protocol %s, family %s", l ? l->name : "*", dns_protocol_to_string(protocol), family == AF_UNSPEC ? "*" : af_to_name(family)); + + /* Enforce ratelimiting for the multicast protocols */ + RATELIMIT_INIT(s->ratelimit, MULTICAST_RATELIMIT_INTERVAL_USEC, MULTICAST_RATELIMIT_BURST); + + *ret = s; + return 0; +} + +static void dns_scope_abort_transactions(DnsScope *s) { + assert(s); + + while (s->transactions) { + DnsTransaction *t = s->transactions; + + /* Abort the transaction, but make sure it is not + * freed while we still look at it */ + + t->block_gc++; + if (DNS_TRANSACTION_IS_LIVE(t->state)) + dns_transaction_complete(t, DNS_TRANSACTION_ABORTED); + t->block_gc--; + + dns_transaction_free(t); + } +} + +DnsScope* dns_scope_free(DnsScope *s) { + DnsResourceRecord *rr; + + if (!s) + return NULL; + + log_debug("Removing scope on link %s, protocol %s, family %s", s->link ? s->link->name : "*", dns_protocol_to_string(s->protocol), s->family == AF_UNSPEC ? "*" : af_to_name(s->family)); + + dns_scope_llmnr_membership(s, false); + dns_scope_mdns_membership(s, false); + dns_scope_abort_transactions(s); + + while (s->query_candidates) + dns_query_candidate_free(s->query_candidates); + + hashmap_free(s->transactions_by_key); + + while ((rr = ordered_hashmap_steal_first(s->conflict_queue))) + dns_resource_record_unref(rr); + + ordered_hashmap_free(s->conflict_queue); + sd_event_source_unref(s->conflict_event_source); + + dns_cache_flush(&s->cache); + dns_zone_flush(&s->zone); + + LIST_REMOVE(scopes, s->manager->dns_scopes, s); + free(s); + + return NULL; +} + +DnsServer *dns_scope_get_dns_server(DnsScope *s) { + assert(s); + + if (s->protocol != DNS_PROTOCOL_DNS) + return NULL; + + if (s->link) + return link_get_dns_server(s->link); + else + return manager_get_dns_server(s->manager); +} + +void dns_scope_next_dns_server(DnsScope *s) { + assert(s); + + if (s->protocol != DNS_PROTOCOL_DNS) + return; + + if (s->link) + link_next_dns_server(s->link); + else + manager_next_dns_server(s->manager); +} + +void dns_scope_packet_received(DnsScope *s, usec_t rtt) { + assert(s); + + if (rtt <= s->max_rtt) + return; + + s->max_rtt = rtt; + s->resend_timeout = MIN(MAX(MULTICAST_RESEND_TIMEOUT_MIN_USEC, s->max_rtt * 2), MULTICAST_RESEND_TIMEOUT_MAX_USEC); +} + +void dns_scope_packet_lost(DnsScope *s, usec_t usec) { + assert(s); + + if (s->resend_timeout <= usec) + s->resend_timeout = MIN(s->resend_timeout * 2, MULTICAST_RESEND_TIMEOUT_MAX_USEC); +} + +static int dns_scope_emit_one(DnsScope *s, int fd, DnsPacket *p) { + union in_addr_union addr; + int ifindex = 0, r; + int family; + uint32_t mtu; + + assert(s); + assert(p); + assert(p->protocol == s->protocol); + + if (s->link) { + mtu = s->link->mtu; + ifindex = s->link->ifindex; + } else + mtu = manager_find_mtu(s->manager); + + switch (s->protocol) { + + case DNS_PROTOCOL_DNS: + assert(fd >= 0); + + if (DNS_PACKET_QDCOUNT(p) > 1) + return -EOPNOTSUPP; + + if (p->size > DNS_PACKET_UNICAST_SIZE_MAX) + return -EMSGSIZE; + + if (p->size + UDP_PACKET_HEADER_SIZE > mtu) + return -EMSGSIZE; + + r = manager_write(s->manager, fd, p); + if (r < 0) + return r; + + break; + + case DNS_PROTOCOL_LLMNR: + assert(fd < 0); + + if (DNS_PACKET_QDCOUNT(p) > 1) + return -EOPNOTSUPP; + + if (!ratelimit_test(&s->ratelimit)) + return -EBUSY; + + family = s->family; + + if (family == AF_INET) { + addr.in = LLMNR_MULTICAST_IPV4_ADDRESS; + fd = manager_llmnr_ipv4_udp_fd(s->manager); + } else if (family == AF_INET6) { + addr.in6 = LLMNR_MULTICAST_IPV6_ADDRESS; + fd = manager_llmnr_ipv6_udp_fd(s->manager); + } else + return -EAFNOSUPPORT; + if (fd < 0) + return fd; + + r = manager_send(s->manager, fd, ifindex, family, &addr, LLMNR_PORT, p); + if (r < 0) + return r; + + break; + + case DNS_PROTOCOL_MDNS: + assert(fd < 0); + + if (!ratelimit_test(&s->ratelimit)) + return -EBUSY; + + family = s->family; + + if (family == AF_INET) { + addr.in = MDNS_MULTICAST_IPV4_ADDRESS; + fd = manager_mdns_ipv4_fd(s->manager); + } else if (family == AF_INET6) { + addr.in6 = MDNS_MULTICAST_IPV6_ADDRESS; + fd = manager_mdns_ipv6_fd(s->manager); + } else + return -EAFNOSUPPORT; + if (fd < 0) + return fd; + + r = manager_send(s->manager, fd, ifindex, family, &addr, MDNS_PORT, p); + if (r < 0) + return r; + + break; + + default: + return -EAFNOSUPPORT; + } + + return 1; +} + +int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p) { + int r; + + assert(s); + assert(p); + assert(p->protocol == s->protocol); + assert((s->protocol == DNS_PROTOCOL_DNS) == (fd >= 0)); + + do { + /* If there are multiple linked packets, set the TC bit in all but the last of them */ + if (p->more) { + assert(p->protocol == DNS_PROTOCOL_MDNS); + dns_packet_set_flags(p, true, true); + } + + r = dns_scope_emit_one(s, fd, p); + if (r < 0) + return r; + + p = p->more; + } while (p); + + return 0; +} + +static int dns_scope_socket( + DnsScope *s, + int type, + int family, + const union in_addr_union *address, + DnsServer *server, + uint16_t port) { + + _cleanup_close_ int fd = -1; + union sockaddr_union sa = {}; + socklen_t salen; + static const int one = 1; + int ret, r; + + assert(s); + + if (server) { + assert(family == AF_UNSPEC); + assert(!address); + + sa.sa.sa_family = server->family; + if (server->family == AF_INET) { + sa.in.sin_port = htobe16(port); + sa.in.sin_addr = server->address.in; + salen = sizeof(sa.in); + } 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; + salen = sizeof(sa.in6); + } else + return -EAFNOSUPPORT; + } else { + assert(family != AF_UNSPEC); + assert(address); + + sa.sa.sa_family = family; + + if (family == AF_INET) { + sa.in.sin_port = htobe16(port); + sa.in.sin_addr = address->in; + salen = sizeof(sa.in); + } 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; + salen = sizeof(sa.in6); + } else + return -EAFNOSUPPORT; + } + + fd = socket(sa.sa.sa_family, type|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return -errno; + + if (type == SOCK_STREAM) { + r = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); + if (r < 0) + return -errno; + } + + if (s->link) { + uint32_t ifindex = htobe32(s->link->ifindex); + + if (sa.sa.sa_family == AF_INET) { + r = setsockopt(fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + return -errno; + } else if (sa.sa.sa_family == AF_INET6) { + r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + return -errno; + } + } + + if (s->protocol == DNS_PROTOCOL_LLMNR) { + /* RFC 4795, section 2.5 requires the TTL to be set to 1 */ + + if (sa.sa.sa_family == AF_INET) { + r = setsockopt(fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); + if (r < 0) + return -errno; + } else if (sa.sa.sa_family == AF_INET6) { + r = setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); + if (r < 0) + return -errno; + } + } + + r = connect(fd, &sa.sa, salen); + if (r < 0 && errno != EINPROGRESS) + return -errno; + + ret = fd; + fd = -1; + + return ret; +} + +int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port) { + return dns_scope_socket(s, SOCK_DGRAM, AF_UNSPEC, NULL, server, port); +} + +int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port) { + return dns_scope_socket(s, SOCK_STREAM, family, address, server, port); +} + +DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain) { + DnsSearchDomain *d; + + assert(s); + assert(domain); + + /* Checks if the specified domain is something to look up on + * this scope. Note that this accepts non-qualified hostnames, + * i.e. those without any search path prefixed yet. */ + + if (ifindex != 0 && (!s->link || s->link->ifindex != ifindex)) + return DNS_SCOPE_NO; + + if ((SD_RESOLVED_FLAGS_MAKE(s->protocol, s->family, 0) & flags) == 0) + return DNS_SCOPE_NO; + + /* Never resolve any loopback hostname or IP address via DNS, + * LLMNR or mDNS. Instead, always rely on synthesized RRs for + * these. */ + if (is_localhost(domain) || + dns_name_endswith(domain, "127.in-addr.arpa") > 0 || + dns_name_equal(domain, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) + return DNS_SCOPE_NO; + + /* Never respond to some of the domains listed in RFC6303 */ + if (dns_name_endswith(domain, "0.in-addr.arpa") > 0 || + dns_name_equal(domain, "255.255.255.255.in-addr.arpa") > 0 || + dns_name_equal(domain, "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) + return DNS_SCOPE_NO; + + /* Never respond to some of the domains listed in RFC6761 */ + if (dns_name_endswith(domain, "invalid") > 0) + return DNS_SCOPE_NO; + + /* Always honour search domains for routing queries. Note that + * we return DNS_SCOPE_YES here, rather than just + * DNS_SCOPE_MAYBE, which means wildcard scopes won't be + * considered anymore. */ + LIST_FOREACH(domains, d, dns_scope_get_search_domains(s)) + if (dns_name_endswith(domain, d->name) > 0) + return DNS_SCOPE_YES; + + switch (s->protocol) { + + case DNS_PROTOCOL_DNS: + + /* Exclude link-local IP ranges */ + if (dns_name_endswith(domain, "254.169.in-addr.arpa") == 0 && + dns_name_endswith(domain, "8.e.f.ip6.arpa") == 0 && + dns_name_endswith(domain, "9.e.f.ip6.arpa") == 0 && + dns_name_endswith(domain, "a.e.f.ip6.arpa") == 0 && + dns_name_endswith(domain, "b.e.f.ip6.arpa") == 0 && + /* If networks use .local in their private setups, they are supposed to also add .local to their search + * domains, which we already checked above. Otherwise, we consider .local specific to mDNS and won't + * send such queries ordinary DNS servers. */ + dns_name_endswith(domain, "local") == 0) + return DNS_SCOPE_MAYBE; + + return DNS_SCOPE_NO; + + case DNS_PROTOCOL_MDNS: + if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || + (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || + (dns_name_endswith(domain, "local") > 0 && /* only resolve names ending in .local via mDNS */ + dns_name_equal(domain, "local") == 0 && /* but not the single-label "local" name itself */ + manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via mDNS */ + return DNS_SCOPE_MAYBE; + + return DNS_SCOPE_NO; + + case DNS_PROTOCOL_LLMNR: + if ((s->family == AF_INET && dns_name_endswith(domain, "in-addr.arpa") > 0) || + (s->family == AF_INET6 && dns_name_endswith(domain, "ip6.arpa") > 0) || + (dns_name_is_single_label(domain) && /* only resolve single label names via LLMNR */ + !is_gateway_hostname(domain) && /* don't resolve "gateway" with LLMNR, let nss-myhostname handle this */ + manager_is_own_hostname(s->manager, domain) <= 0)) /* never resolve the local hostname via LLMNR */ + return DNS_SCOPE_MAYBE; + + return DNS_SCOPE_NO; + + default: + assert_not_reached("Unknown scope protocol"); + } +} + +bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key) { + int key_family; + + assert(s); + assert(key); + + /* Check if it makes sense to resolve the specified key on + * this scope. Note that this call assumes as fully qualified + * name, i.e. the search suffixes already appended. */ + + if (key->class != DNS_CLASS_IN) + return false; + + if (s->protocol == DNS_PROTOCOL_DNS) { + + /* On classic DNS, looking up non-address RRs is always + * fine. (Specifically, we want to permit looking up + * DNSKEY and DS records on the root and top-level + * domains.) */ + if (!dns_resource_key_is_address(key)) + return true; + + /* However, we refuse to look up A and AAAA RRs on the + * root and single-label domains, under the assumption + * that those should be resolved via LLMNR or search + * path only, and should not be leaked onto the + * internet. */ + return !(dns_name_is_single_label(DNS_RESOURCE_KEY_NAME(key)) || + dns_name_is_root(DNS_RESOURCE_KEY_NAME(key))); + } + + /* On mDNS and LLMNR, send A and AAAA queries only on the + * respective scopes */ + + key_family = dns_type_to_af(key->type); + if (key_family < 0) + return true; + + return key_family == s->family; +} + +static int dns_scope_multicast_membership(DnsScope *s, bool b, struct in_addr in, struct in6_addr in6) { + int fd; + + assert(s); + assert(s->link); + + if (s->family == AF_INET) { + struct ip_mreqn mreqn = { + .imr_multiaddr = in, + .imr_ifindex = s->link->ifindex, + }; + + fd = manager_llmnr_ipv4_udp_fd(s->manager); + if (fd < 0) + return fd; + + /* Always first try to drop membership before we add + * one. This is necessary on some devices, such as + * veth. */ + if (b) + (void) setsockopt(fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)); + + if (setsockopt(fd, IPPROTO_IP, b ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, &mreqn, sizeof(mreqn)) < 0) + return -errno; + + } else if (s->family == AF_INET6) { + struct ipv6_mreq mreq = { + .ipv6mr_multiaddr = in6, + .ipv6mr_interface = s->link->ifindex, + }; + + fd = manager_llmnr_ipv6_udp_fd(s->manager); + if (fd < 0) + return fd; + + if (b) + (void) setsockopt(fd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)); + + if (setsockopt(fd, IPPROTO_IPV6, b ? IPV6_ADD_MEMBERSHIP : IPV6_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) + return -errno; + } else + return -EAFNOSUPPORT; + + return 0; +} + +int dns_scope_llmnr_membership(DnsScope *s, bool b) { + + if (s->protocol != DNS_PROTOCOL_LLMNR) + return 0; + + return dns_scope_multicast_membership(s, b, LLMNR_MULTICAST_IPV4_ADDRESS, LLMNR_MULTICAST_IPV6_ADDRESS); +} + +int dns_scope_mdns_membership(DnsScope *s, bool b) { + + if (s->protocol != DNS_PROTOCOL_MDNS) + return 0; + + return dns_scope_multicast_membership(s, b, MDNS_MULTICAST_IPV4_ADDRESS, MDNS_MULTICAST_IPV6_ADDRESS); +} + +static int dns_scope_make_reply_packet( + DnsScope *s, + uint16_t id, + int rcode, + DnsQuestion *q, + DnsAnswer *answer, + DnsAnswer *soa, + bool tentative, + 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)) + return -EINVAL; + + r = dns_packet_new(&p, s->protocol, 0); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->id = id; + DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( + 1 /* qr */, + 0 /* opcode */, + 0 /* c */, + 0 /* tc */, + tentative, + 0 /* (ra) */, + 0 /* (ad) */, + 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); + } + + 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; + } + + DNS_PACKET_HEADER(p)->arcount = htobe16(soa->n_rrs); + } + + *ret = p; + p = NULL; + + return 0; +} + +static void dns_scope_verify_conflicts(DnsScope *s, DnsPacket *p) { + unsigned n; + + 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); +} + +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; + DnsResourceKey *key = NULL; + bool tentative = false; + int r, fd; + + assert(s); + assert(p); + + if (p->protocol != DNS_PROTOCOL_LLMNR) + return; + + if (p->ipproto == IPPROTO_UDP) { + /* Don't accept UDP queries directed to anything but + * the LLMNR multicast addresses. See RFC 4795, + * section 2.5. */ + + if (p->family == AF_INET && !in_addr_equal(AF_INET, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV4_ADDRESS)) + return; + + if (p->family == AF_INET6 && !in_addr_equal(AF_INET6, &p->destination, (union in_addr_union*) &LLMNR_MULTICAST_IPV6_ADDRESS)) + return; + } + + r = dns_packet_extract(p); + if (r < 0) { + log_debug_errno(r, "Failed to extract resources from incoming packet: %m"); + return; + } + + if (DNS_PACKET_LLMNR_C(p)) { + /* Somebody notified us about a possible conflict */ + dns_scope_verify_conflicts(s, p); + return; + } + + assert(p->question->n_keys == 1); + key = p->question->keys[0]; + + r = dns_zone_lookup(&s->zone, key, &answer, &soa, &tentative); + if (r < 0) { + log_debug_errno(r, "Failed to lookup key: %m"); + return; + } + if (r == 0) + return; + + if (answer) + dns_answer_order_by_scope(answer, in_addr_is_link_local(p->family, &p->sender) > 0); + + r = dns_scope_make_reply_packet(s, DNS_PACKET_ID(p), DNS_RCODE_SUCCESS, p->question, answer, soa, tentative, &reply); + if (r < 0) { + log_debug_errno(r, "Failed to build reply packet: %m"); + return; + } + + if (stream) + r = dns_stream_write_packet(stream, reply); + else { + if (!ratelimit_test(&s->ratelimit)) + return; + + if (p->family == AF_INET) + fd = manager_llmnr_ipv4_udp_fd(s->manager); + else if (p->family == AF_INET6) + fd = manager_llmnr_ipv6_udp_fd(s->manager); + else { + log_debug("Unknown protocol"); + return; + } + if (fd < 0) { + log_debug_errno(fd, "Failed to get reply socket: %m"); + return; + } + + /* Note that we always immediately reply to all LLMNR + * requests, and do not wait any time, since we + * 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; + } +} + +DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok) { + DnsTransaction *t; + + assert(scope); + assert(key); + + /* Try to find an ongoing transaction that is a equal to the + * specified question */ + t = hashmap_get(scope->transactions_by_key, key); + if (!t) + return NULL; + + /* Refuse reusing transactions that completed based on cached + * data instead of a real packet, if that's requested. */ + if (!cache_ok && + IN_SET(t->state, DNS_TRANSACTION_SUCCESS, DNS_TRANSACTION_RCODE_FAILURE) && + t->answer_source != DNS_TRANSACTION_NETWORK) + return NULL; + + return t; +} + +static int dns_scope_make_conflict_packet( + DnsScope *s, + DnsResourceRecord *rr, + DnsPacket **ret) { + + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + + assert(s); + assert(rr); + assert(ret); + + r = dns_packet_new(&p, s->protocol, 0); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->flags = htobe16(DNS_PACKET_MAKE_FLAGS( + 0 /* qr */, + 0 /* opcode */, + 1 /* conflict */, + 0 /* tc */, + 0 /* t */, + 0 /* (ra) */, + 0 /* (ad) */, + 0 /* (cd) */, + 0)); + + /* For mDNS, the transaction ID should always be 0 */ + if (s->protocol != DNS_PROTOCOL_MDNS) + random_bytes(&DNS_PACKET_HEADER(p)->id, sizeof(uint16_t)); + + DNS_PACKET_HEADER(p)->qdcount = htobe16(1); + DNS_PACKET_HEADER(p)->arcount = htobe16(1); + + r = dns_packet_append_key(p, rr->key, NULL); + if (r < 0) + return r; + + r = dns_packet_append_rr(p, rr, NULL, NULL); + if (r < 0) + return r; + + *ret = p; + p = NULL; + + return 0; +} + +static int on_conflict_dispatch(sd_event_source *es, usec_t usec, void *userdata) { + DnsScope *scope = userdata; + int r; + + assert(es); + assert(scope); + + scope->conflict_event_source = sd_event_source_unref(scope->conflict_event_source); + + for (;;) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + + rr = ordered_hashmap_steal_first(scope->conflict_queue); + if (!rr) + break; + + r = dns_scope_make_conflict_packet(scope, rr, &p); + if (r < 0) { + log_error_errno(r, "Failed to make conflict packet: %m"); + return 0; + } + + r = dns_scope_emit_udp(scope, -1, p); + if (r < 0) + log_debug_errno(r, "Failed to send conflict packet: %m"); + } + + return 0; +} + +int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr) { + usec_t jitter; + int r; + + assert(scope); + assert(rr); + + /* We don't send these queries immediately. Instead, we queue + * them, and send them after some jitter delay. */ + r = ordered_hashmap_ensure_allocated(&scope->conflict_queue, &dns_resource_key_hash_ops); + if (r < 0) { + log_oom(); + return r; + } + + /* We only place one RR per key in the conflict + * messages, not all of them. That should be enough to + * indicate where there might be a conflict */ + r = ordered_hashmap_put(scope->conflict_queue, rr->key, rr); + if (r == -EEXIST || r == 0) + return 0; + if (r < 0) + return log_debug_errno(r, "Failed to queue conflicting RR: %m"); + + dns_resource_record_ref(rr); + + if (scope->conflict_event_source) + return 0; + + random_bytes(&jitter, sizeof(jitter)); + jitter %= LLMNR_JITTER_INTERVAL_USEC; + + r = sd_event_add_time(scope->manager->event, + &scope->conflict_event_source, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + jitter, + LLMNR_JITTER_INTERVAL_USEC, + on_conflict_dispatch, scope); + if (r < 0) + return log_debug_errno(r, "Failed to add conflict dispatch event: %m"); + + (void) sd_event_source_set_description(scope->conflict_event_source, "scope-conflict"); + + return 0; +} + +void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p) { + unsigned i; + int r; + + assert(scope); + assert(p); + + if (p->protocol != DNS_PROTOCOL_LLMNR) + return; + + if (DNS_PACKET_RRCOUNT(p) <= 0) + return; + + if (DNS_PACKET_LLMNR_C(p) != 0) + return; + + if (DNS_PACKET_LLMNR_T(p) != 0) + return; + + if (manager_our_packet(scope->manager, p)) + return; + + r = dns_packet_extract(p); + if (r < 0) { + log_debug_errno(r, "Failed to extract packet: %m"); + return; + } + + log_debug("Checking for conflicts..."); + + for (i = 0; i < p->answer->n_rrs; i++) { + + /* Check for conflicts against the local zone. If we + * found one, we won't check any further */ + r = dns_zone_check_conflicts(&scope->zone, p->answer->items[i].rr); + if (r != 0) + continue; + + /* Check for conflicts against the local cache. If so, + * send out an advisory query, to inform everybody */ + r = dns_cache_check_conflicts(&scope->cache, p->answer->items[i].rr, p->family, &p->sender); + if (r <= 0) + continue; + + dns_scope_notify_conflict(scope, p->answer->items[i].rr); + } +} + +void dns_scope_dump(DnsScope *s, FILE *f) { + assert(s); + + if (!f) + f = stdout; + + fputs("[Scope protocol=", f); + fputs(dns_protocol_to_string(s->protocol), f); + + if (s->link) { + fputs(" interface=", f); + fputs(s->link->name, f); + } + + if (s->family != AF_UNSPEC) { + fputs(" family=", f); + fputs(af_to_name(s->family), f); + } + + fputs("]\n", f); + + if (!dns_zone_is_empty(&s->zone)) { + fputs("ZONE:\n", f); + dns_zone_dump(&s->zone, f); + } + + if (!dns_cache_is_empty(&s->cache)) { + fputs("CACHE:\n", f); + dns_cache_dump(&s->cache, f); + } +} + +DnsSearchDomain *dns_scope_get_search_domains(DnsScope *s) { + assert(s); + + if (s->protocol != DNS_PROTOCOL_DNS) + return NULL; + + if (s->link) + return s->link->search_domains; + + return s->manager->search_domains; +} + +bool dns_scope_name_needs_search_domain(DnsScope *s, const char *name) { + assert(s); + + if (s->protocol != DNS_PROTOCOL_DNS) + return false; + + return dns_name_is_single_label(name); +} + +bool dns_scope_network_good(DnsScope *s) { + /* Checks whether the network is in good state for lookups on this scope. For mDNS/LLMNR/Classic DNS scopes + * bound to links this is easy, as they don't even exist if the link isn't in a suitable state. For the global + * DNS scope we check whether there are any links that are up and have an address. */ + + if (s->link) + return true; + + return manager_routable(s->manager, AF_UNSPEC); +} diff --git a/src/resolve/src/resolved-dns-scope.h b/src/resolve/src/resolved-dns-scope.h new file mode 100644 index 0000000000..291e5817d0 --- /dev/null +++ b/src/resolve/src/resolved-dns-scope.h @@ -0,0 +1,109 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "list.h" + +typedef struct DnsScope DnsScope; + +#include "resolved-dns-cache.h" +#include "resolved-dns-dnssec.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-server.h" +#include "resolved-dns-zone.h" +#include "resolved-link.h" + +typedef enum DnsScopeMatch { + DNS_SCOPE_NO, + DNS_SCOPE_MAYBE, + DNS_SCOPE_YES, + _DNS_SCOPE_MATCH_MAX, + _DNS_SCOPE_INVALID = -1 +} DnsScopeMatch; + +struct DnsScope { + Manager *manager; + + DnsProtocol protocol; + int family; + DnssecMode dnssec_mode; + + Link *link; + + DnsCache cache; + DnsZone zone; + + OrderedHashmap *conflict_queue; + sd_event_source *conflict_event_source; + + RateLimit ratelimit; + + usec_t resend_timeout; + usec_t max_rtt; + + LIST_HEAD(DnsQueryCandidate, query_candidates); + + /* Note that we keep track of ongoing transactions in two + * ways: once in a hashmap, indexed by the rr key, and once in + * a linked list. We use the hashmap to quickly find + * transactions we can reuse for a key. But note that there + * might be multiple transactions for the same key (because + * the zone probing can't reuse a transaction answered from + * the zone or the cache), and the hashmap only tracks the + * most recent entry. */ + Hashmap *transactions_by_key; + LIST_HEAD(DnsTransaction, transactions); + + LIST_FIELDS(DnsScope, scopes); +}; + +int dns_scope_new(Manager *m, DnsScope **ret, Link *l, DnsProtocol p, int family); +DnsScope* dns_scope_free(DnsScope *s); + +void dns_scope_packet_received(DnsScope *s, usec_t rtt); +void dns_scope_packet_lost(DnsScope *s, usec_t usec); + +int dns_scope_emit_udp(DnsScope *s, int fd, DnsPacket *p); +int dns_scope_socket_tcp(DnsScope *s, int family, const union in_addr_union *address, DnsServer *server, uint16_t port); +int dns_scope_socket_udp(DnsScope *s, DnsServer *server, uint16_t port); + +DnsScopeMatch dns_scope_good_domain(DnsScope *s, int ifindex, uint64_t flags, const char *domain); +bool dns_scope_good_key(DnsScope *s, const DnsResourceKey *key); + +DnsServer *dns_scope_get_dns_server(DnsScope *s); +void dns_scope_next_dns_server(DnsScope *s); + +int dns_scope_llmnr_membership(DnsScope *s, bool b); +int dns_scope_mdns_membership(DnsScope *s, bool b); + +void dns_scope_process_query(DnsScope *s, DnsStream *stream, DnsPacket *p); + +DnsTransaction *dns_scope_find_transaction(DnsScope *scope, DnsResourceKey *key, bool cache_ok); + +int dns_scope_notify_conflict(DnsScope *scope, DnsResourceRecord *rr); +void dns_scope_check_conflicts(DnsScope *scope, DnsPacket *p); + +void dns_scope_dump(DnsScope *s, FILE *f); + +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); diff --git a/src/resolve/src/resolved-dns-search-domain.c b/src/resolve/src/resolved-dns-search-domain.c new file mode 100644 index 0000000000..732471027b --- /dev/null +++ b/src/resolve/src/resolved-dns-search-domain.c @@ -0,0 +1,227 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "resolved-dns-search-domain.h" + +int dns_search_domain_new( + Manager *m, + DnsSearchDomain **ret, + DnsSearchDomainType type, + Link *l, + const char *name) { + + _cleanup_free_ char *normalized = NULL; + DnsSearchDomain *d; + int r; + + assert(m); + assert((type == DNS_SEARCH_DOMAIN_LINK) == !!l); + assert(name); + + r = dns_name_normalize(name, &normalized); + if (r < 0) + return r; + + if (l) { + if (l->n_search_domains >= LINK_SEARCH_DOMAINS_MAX) + return -E2BIG; + } else { + if (m->n_search_domains >= MANAGER_SEARCH_DOMAINS_MAX) + return -E2BIG; + } + + d = new0(DnsSearchDomain, 1); + if (!d) + return -ENOMEM; + + d->n_ref = 1; + d->manager = m; + d->type = type; + d->name = normalized; + normalized = NULL; + + switch (type) { + + case DNS_SEARCH_DOMAIN_LINK: + d->link = l; + LIST_APPEND(domains, l->search_domains, d); + l->n_search_domains++; + break; + + case DNS_SERVER_SYSTEM: + LIST_APPEND(domains, m->search_domains, d); + m->n_search_domains++; + break; + + default: + assert_not_reached("Unknown search domain type"); + } + + d->linked = true; + + if (ret) + *ret = d; + + return 0; +} + +DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d) { + if (!d) + return NULL; + + assert(d->n_ref > 0); + d->n_ref++; + + return d; +} + +DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d) { + if (!d) + return NULL; + + assert(d->n_ref > 0); + d->n_ref--; + + if (d->n_ref > 0) + return NULL; + + free(d->name); + free(d); + + return NULL; +} + +void dns_search_domain_unlink(DnsSearchDomain *d) { + assert(d); + assert(d->manager); + + if (!d->linked) + return; + + switch (d->type) { + + case DNS_SEARCH_DOMAIN_LINK: + assert(d->link); + assert(d->link->n_search_domains > 0); + LIST_REMOVE(domains, d->link->search_domains, d); + d->link->n_search_domains--; + break; + + case DNS_SEARCH_DOMAIN_SYSTEM: + assert(d->manager->n_search_domains > 0); + LIST_REMOVE(domains, d->manager->search_domains, d); + d->manager->n_search_domains--; + break; + } + + d->linked = false; + + dns_search_domain_unref(d); +} + +void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d) { + DnsSearchDomain *tail; + + assert(d); + + if (!d->marked) + return; + + d->marked = false; + + if (!d->linked || !d->domains_next) + return; + + switch (d->type) { + + case DNS_SEARCH_DOMAIN_LINK: + assert(d->link); + LIST_FIND_TAIL(domains, d, tail); + LIST_REMOVE(domains, d->link->search_domains, d); + LIST_INSERT_AFTER(domains, d->link->search_domains, tail, d); + break; + + case DNS_SEARCH_DOMAIN_SYSTEM: + LIST_FIND_TAIL(domains, d, tail); + LIST_REMOVE(domains, d->manager->search_domains, d); + LIST_INSERT_AFTER(domains, d->manager->search_domains, tail, d); + break; + + default: + assert_not_reached("Unknown search domain type"); + } +} + +void dns_search_domain_unlink_all(DnsSearchDomain *first) { + DnsSearchDomain *next; + + if (!first) + return; + + next = first->domains_next; + dns_search_domain_unlink(first); + + dns_search_domain_unlink_all(next); +} + +void dns_search_domain_unlink_marked(DnsSearchDomain *first) { + DnsSearchDomain *next; + + if (!first) + return; + + next = first->domains_next; + + if (first->marked) + dns_search_domain_unlink(first); + + dns_search_domain_unlink_marked(next); +} + +void dns_search_domain_mark_all(DnsSearchDomain *first) { + if (!first) + return; + + first->marked = true; + dns_search_domain_mark_all(first->domains_next); +} + +int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret) { + DnsSearchDomain *d; + int r; + + assert(name); + assert(ret); + + LIST_FOREACH(domains, d, first) { + + r = dns_name_equal(name, d->name); + if (r < 0) + return r; + if (r > 0) { + *ret = d; + return 1; + } + } + + *ret = NULL; + return 0; +} diff --git a/src/resolve/src/resolved-dns-search-domain.h b/src/resolve/src/resolved-dns-search-domain.h new file mode 100644 index 0000000000..eaacef4edc --- /dev/null +++ b/src/resolve/src/resolved-dns-search-domain.h @@ -0,0 +1,74 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include "macro.h" + +typedef struct DnsSearchDomain DnsSearchDomain; + +typedef enum DnsSearchDomainType { + DNS_SEARCH_DOMAIN_SYSTEM, + DNS_SEARCH_DOMAIN_LINK, +} DnsSearchDomainType; + +#include "resolved-link.h" +#include "resolved-manager.h" + +struct DnsSearchDomain { + Manager *manager; + + unsigned n_ref; + + DnsSearchDomainType type; + Link *link; + + char *name; + + bool marked:1; + bool route_only:1; + + bool linked:1; + LIST_FIELDS(DnsSearchDomain, domains); +}; + +int dns_search_domain_new( + Manager *m, + DnsSearchDomain **ret, + DnsSearchDomainType type, + Link *link, + const char *name); + +DnsSearchDomain* dns_search_domain_ref(DnsSearchDomain *d); +DnsSearchDomain* dns_search_domain_unref(DnsSearchDomain *d); + +void dns_search_domain_unlink(DnsSearchDomain *d); +void dns_search_domain_move_back_and_unmark(DnsSearchDomain *d); + +void dns_search_domain_unlink_all(DnsSearchDomain *first); +void dns_search_domain_unlink_marked(DnsSearchDomain *first); +void dns_search_domain_mark_all(DnsSearchDomain *first); + +int dns_search_domain_find(DnsSearchDomain *first, const char *name, DnsSearchDomain **ret); + +static inline const char* DNS_SEARCH_DOMAIN_NAME(DnsSearchDomain *d) { + return d ? d->name : NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsSearchDomain*, dns_search_domain_unref); diff --git a/src/resolve/src/resolved-dns-server.c b/src/resolve/src/resolved-dns-server.c new file mode 100644 index 0000000000..27342a0e04 --- /dev/null +++ b/src/resolve/src/resolved-dns-server.c @@ -0,0 +1,740 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "alloc-util.h" +#include "resolved-dns-server.h" +#include "resolved-resolv-conf.h" +#include "siphash24.h" +#include "string-table.h" +#include "string-util.h" + +/* After how much time to repeat classic DNS requests */ +#define DNS_TIMEOUT_MIN_USEC (500 * USEC_PER_MSEC) +#define DNS_TIMEOUT_MAX_USEC (5 * USEC_PER_SEC) + +/* The amount of time to wait before retrying with a full feature set */ +#define DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC (6 * USEC_PER_HOUR) +#define DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC (5 * USEC_PER_MINUTE) + +/* The number of times we will attempt a certain feature set before degrading */ +#define DNS_SERVER_FEATURE_RETRY_ATTEMPTS 3 + +int dns_server_new( + Manager *m, + DnsServer **ret, + DnsServerType type, + Link *l, + int family, + const union in_addr_union *in_addr) { + + DnsServer *s; + + assert(m); + assert((type == DNS_SERVER_LINK) == !!l); + assert(in_addr); + + if (!IN_SET(family, AF_INET, AF_INET6)) + return -EAFNOSUPPORT; + + if (l) { + if (l->n_dns_servers >= LINK_DNS_SERVERS_MAX) + return -E2BIG; + } else { + if (m->n_dns_servers >= MANAGER_DNS_SERVERS_MAX) + return -E2BIG; + } + + s = new0(DnsServer, 1); + if (!s) + return -ENOMEM; + + s->n_ref = 1; + s->manager = m; + s->verified_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; + s->features_grace_period_usec = DNS_SERVER_FEATURE_GRACE_PERIOD_MIN_USEC; + s->received_udp_packet_max = DNS_PACKET_UNICAST_SIZE_MAX; + s->type = type; + s->family = family; + s->address = *in_addr; + s->resend_timeout = DNS_TIMEOUT_MIN_USEC; + + switch (type) { + + case DNS_SERVER_LINK: + s->link = l; + LIST_APPEND(servers, l->dns_servers, s); + l->n_dns_servers++; + break; + + case DNS_SERVER_SYSTEM: + LIST_APPEND(servers, m->dns_servers, s); + m->n_dns_servers++; + break; + + case DNS_SERVER_FALLBACK: + LIST_APPEND(servers, m->fallback_dns_servers, s); + m->n_dns_servers++; + break; + + default: + assert_not_reached("Unknown server type"); + } + + s->linked = true; + + /* A new DNS server that isn't fallback is added and the one + * we used so far was a fallback one? Then let's try to pick + * the new one */ + if (type != DNS_SERVER_FALLBACK && + m->current_dns_server && + m->current_dns_server->type == DNS_SERVER_FALLBACK) + manager_set_dns_server(m, NULL); + + if (ret) + *ret = s; + + return 0; +} + +DnsServer* dns_server_ref(DnsServer *s) { + if (!s) + return NULL; + + assert(s->n_ref > 0); + s->n_ref ++; + + return s; +} + +DnsServer* dns_server_unref(DnsServer *s) { + if (!s) + return NULL; + + assert(s->n_ref > 0); + s->n_ref --; + + if (s->n_ref > 0) + return NULL; + + free(s->server_string); + free(s); + return NULL; +} + +void dns_server_unlink(DnsServer *s) { + assert(s); + assert(s->manager); + + /* This removes the specified server from the linked list of + * servers, but any server might still stay around if it has + * refs, for example from an ongoing transaction. */ + + if (!s->linked) + return; + + switch (s->type) { + + case DNS_SERVER_LINK: + assert(s->link); + assert(s->link->n_dns_servers > 0); + LIST_REMOVE(servers, s->link->dns_servers, s); + break; + + case DNS_SERVER_SYSTEM: + assert(s->manager->n_dns_servers > 0); + LIST_REMOVE(servers, s->manager->dns_servers, s); + s->manager->n_dns_servers--; + break; + + case DNS_SERVER_FALLBACK: + assert(s->manager->n_dns_servers > 0); + LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); + s->manager->n_dns_servers--; + break; + } + + s->linked = false; + + if (s->link && s->link->current_dns_server == s) + link_set_dns_server(s->link, NULL); + + if (s->manager->current_dns_server == s) + manager_set_dns_server(s->manager, NULL); + + dns_server_unref(s); +} + +void dns_server_move_back_and_unmark(DnsServer *s) { + DnsServer *tail; + + assert(s); + + if (!s->marked) + return; + + s->marked = false; + + if (!s->linked || !s->servers_next) + return; + + /* Move us to the end of the list, so that the order is + * strictly kept, if we are not at the end anyway. */ + + switch (s->type) { + + case DNS_SERVER_LINK: + assert(s->link); + LIST_FIND_TAIL(servers, s, tail); + LIST_REMOVE(servers, s->link->dns_servers, s); + LIST_INSERT_AFTER(servers, s->link->dns_servers, tail, s); + break; + + case DNS_SERVER_SYSTEM: + LIST_FIND_TAIL(servers, s, tail); + LIST_REMOVE(servers, s->manager->dns_servers, s); + LIST_INSERT_AFTER(servers, s->manager->dns_servers, tail, s); + break; + + case DNS_SERVER_FALLBACK: + LIST_FIND_TAIL(servers, s, tail); + LIST_REMOVE(servers, s->manager->fallback_dns_servers, s); + LIST_INSERT_AFTER(servers, s->manager->fallback_dns_servers, tail, s); + break; + + default: + assert_not_reached("Unknown server type"); + } +} + +static void dns_server_verified(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + if (s->verified_feature_level > level) + return; + + if (s->verified_feature_level != level) { + log_debug("Verified we get a response at feature level %s from DNS server %s.", + dns_server_feature_level_to_string(level), + dns_server_string(s)); + s->verified_feature_level = level; + } + + assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &s->verified_usec) >= 0); +} + +void dns_server_packet_received(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t rtt, size_t size) { + assert(s); + + if (protocol == IPPROTO_UDP) { + if (s->possible_feature_level == level) + s->n_failed_udp = 0; + + /* If the RRSIG data is missing, then we can only validate EDNS0 at max */ + if (s->packet_rrsig_missing && level >= DNS_SERVER_FEATURE_LEVEL_DO) + level = DNS_SERVER_FEATURE_LEVEL_DO - 1; + + /* If the OPT RR got lost, then we can only validate UDP at max */ + if (s->packet_bad_opt && level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) + level = DNS_SERVER_FEATURE_LEVEL_EDNS0 - 1; + + /* Even if we successfully receive a reply to a request announcing support for large packets, + that does not mean we can necessarily receive large packets. */ + if (level == DNS_SERVER_FEATURE_LEVEL_LARGE) + level = DNS_SERVER_FEATURE_LEVEL_LARGE - 1; + + } else if (protocol == IPPROTO_TCP) { + + if (s->possible_feature_level == level) + s->n_failed_tcp = 0; + + /* Successful TCP connections are only useful to verify the TCP feature level. */ + level = DNS_SERVER_FEATURE_LEVEL_TCP; + } + + dns_server_verified(s, level); + + /* Remember the size of the largest UDP packet we received from a server, + we know that we can always announce support for packets with at least + this size. */ + if (protocol == IPPROTO_UDP && s->received_udp_packet_max < size) + s->received_udp_packet_max = size; + + if (s->max_rtt < rtt) { + s->max_rtt = rtt; + s->resend_timeout = CLAMP(s->max_rtt * 2, DNS_TIMEOUT_MIN_USEC, DNS_TIMEOUT_MAX_USEC); + } +} + +void dns_server_packet_lost(DnsServer *s, int protocol, DnsServerFeatureLevel level, usec_t usec) { + assert(s); + assert(s->manager); + + if (s->possible_feature_level == level) { + if (protocol == IPPROTO_UDP) + s->n_failed_udp ++; + else if (protocol == IPPROTO_TCP) + s->n_failed_tcp ++; + } + + if (s->resend_timeout > usec) + return; + + 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); + + /* Invoked whenever we get a packet with TC bit set. */ + + if (s->possible_feature_level != level) + return; + + s->packet_truncated = true; +} + +void dns_server_packet_rrsig_missing(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + if (level < DNS_SERVER_FEATURE_LEVEL_DO) + return; + + /* If the RRSIG RRs are missing, we have to downgrade what we previously verified */ + if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) + s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_DO-1; + + s->packet_rrsig_missing = true; +} + +void dns_server_packet_bad_opt(DnsServer *s, DnsServerFeatureLevel level) { + assert(s); + + if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) + return; + + /* If the OPT RR got lost, we have to downgrade what we previously verified */ + if (s->verified_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) + s->verified_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0-1; + + s->packet_bad_opt = true; +} + +static bool dns_server_grace_period_expired(DnsServer *s) { + usec_t ts; + + assert(s); + assert(s->manager); + + if (s->verified_usec == 0) + return false; + + assert_se(sd_event_now(s->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + if (s->verified_usec + s->features_grace_period_usec > ts) + return false; + + s->features_grace_period_usec = MIN(s->features_grace_period_usec * 2, DNS_SERVER_FEATURE_GRACE_PERIOD_MAX_USEC); + + 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); + + if (s->possible_feature_level != DNS_SERVER_FEATURE_LEVEL_BEST && + dns_server_grace_period_expired(s)) { + + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_BEST; + + dns_server_reset_counters(s); + + s->packet_bad_opt = false; + s->packet_rrsig_missing = false; + + log_info("Grace period over, resuming full feature set (%s) for DNS server %s.", + dns_server_feature_level_to_string(s->possible_feature_level), + dns_server_string(s)); + + } else if (s->possible_feature_level <= s->verified_feature_level) + s->possible_feature_level = s->verified_feature_level; + else { + DnsServerFeatureLevel p = s->possible_feature_level; + + if (s->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && + s->possible_feature_level == DNS_SERVER_FEATURE_LEVEL_TCP) { + + /* We are at the TCP (lowest) level, and we tried a couple of TCP connections, and it didn't + * work. Upgrade back to UDP again. */ + log_debug("Reached maximum number of failed TCP connection attempts, trying UDP again..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; + + } else if (s->packet_bad_opt && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_EDNS0) { + + /* A reply to one of our EDNS0 queries didn't carry a valid OPT RR, then downgrade to below + * EDNS0 levels. After all, some records generate different responses with and without OPT RR + * in the request. Example: + * https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ + + log_debug("Server doesn't support EDNS(0) properly, downgrading feature level..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_UDP; + + } else if (s->packet_rrsig_missing && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_DO) { + + /* RRSIG data was missing on a EDNS0 packet with DO bit set. This means the server doesn't + * augment responses with DNSSEC RRs. If so, let's better not ask the server for it anymore, + * after all some servers generate different replies depending if an OPT RR is in the query or + * not. */ + + log_debug("Detected server responses lack RRSIG records, downgrading feature level..."); + s->possible_feature_level = DNS_SERVER_FEATURE_LEVEL_EDNS0; + + } else if (s->n_failed_udp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS && + s->possible_feature_level >= DNS_SERVER_FEATURE_LEVEL_UDP) { + + /* We lost too many UDP packets in a row, and are on a feature level of UDP or higher. If the + * packets are lost, maybe the server cannot parse them, hence downgrading sounds like a good + * idea. We might downgrade all the way down to TCP this way. */ + + 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) { + + /* We got too many TCP connection failures in a row, we had at least one truncated packet, and + * are on a feature level above UDP. By downgrading things and getting rid of DNSSEC or EDNS0 + * data we hope to make the packet smaller, so that it still works via UDP given that TCP + * appears not to be a fallback. Note that if we are already at the lowest UDP level, we don't + * go further down, since that's TCP, and TCP failed too often after all. */ + + log_debug("Got too many failed TCP connection failures and truncated UDP packets, downgrading feature level..."); + s->possible_feature_level--; + } + + if (p != s->possible_feature_level) { + + /* We changed the feature level, reset the counting */ + dns_server_reset_counters(s); + + log_warning("Using degraded feature set (%s) for DNS server %s.", + dns_server_feature_level_to_string(s->possible_feature_level), + dns_server_string(s)); + } + } + + return s->possible_feature_level; +} + +int dns_server_adjust_opt(DnsServer *server, DnsPacket *packet, DnsServerFeatureLevel level) { + size_t packet_size; + bool edns_do; + int r; + + assert(server); + assert(packet); + assert(packet->protocol == DNS_PROTOCOL_DNS); + + /* Fix the OPT field in the packet to match our current feature level. */ + + r = dns_packet_truncate_opt(packet); + if (r < 0) + return r; + + if (level < DNS_SERVER_FEATURE_LEVEL_EDNS0) + return 0; + + edns_do = level >= DNS_SERVER_FEATURE_LEVEL_DO; + + if (level >= DNS_SERVER_FEATURE_LEVEL_LARGE) + packet_size = DNS_PACKET_UNICAST_SIZE_LARGE_MAX; + else + packet_size = server->received_udp_packet_max; + + return dns_packet_append_opt(packet, packet_size, edns_do, NULL); +} + +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); + + return strna(server->server_string); +} + +bool dns_server_dnssec_supported(DnsServer *server) { + assert(server); + + /* Returns whether the server supports DNSSEC according to what we know about it */ + + if (server->possible_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) + return false; + + if (server->packet_bad_opt) + return false; + + if (server->packet_rrsig_missing) + return false; + + /* DNSSEC servers need to support TCP properly (see RFC5966), if they don't, we assume DNSSEC is borked too */ + if (server->n_failed_tcp >= DNS_SERVER_FEATURE_RETRY_ATTEMPTS) + return false; + + return true; +} + +void dns_server_warn_downgrade(DnsServer *server) { + assert(server); + + if (server->warned_downgrade) + return; + + log_struct(LOG_NOTICE, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_DOWNGRADE), + LOG_MESSAGE("Server %s does not support DNSSEC, downgrading to non-DNSSEC mode.", dns_server_string(server)), + "DNS_SERVER=%s", dns_server_string(server), + "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(server->possible_feature_level), + NULL); + + server->warned_downgrade = true; +} + +static void dns_server_hash_func(const void *p, struct siphash *state) { + const DnsServer *s = p; + + assert(s); + + siphash24_compress(&s->family, sizeof(s->family), state); + siphash24_compress(&s->address, FAMILY_ADDRESS_SIZE(s->family), state); +} + +static int dns_server_compare_func(const void *a, const void *b) { + const DnsServer *x = a, *y = b; + + 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)); +} + +const struct hash_ops dns_server_hash_ops = { + .hash = dns_server_hash_func, + .compare = dns_server_compare_func +}; + +void dns_server_unlink_all(DnsServer *first) { + DnsServer *next; + + if (!first) + return; + + next = first->servers_next; + dns_server_unlink(first); + + dns_server_unlink_all(next); +} + +void dns_server_unlink_marked(DnsServer *first) { + DnsServer *next; + + if (!first) + return; + + next = first->servers_next; + + if (first->marked) + dns_server_unlink(first); + + dns_server_unlink_marked(next); +} + +void dns_server_mark_all(DnsServer *first) { + if (!first) + return; + + first->marked = true; + dns_server_mark_all(first->servers_next); +} + +DnsServer *dns_server_find(DnsServer *first, int family, const union in_addr_union *in_addr) { + DnsServer *s; + + LIST_FOREACH(servers, s, first) + if (s->family == family && in_addr_equal(family, &s->address, in_addr) > 0) + return s; + + return NULL; +} + +DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t) { + assert(m); + + switch (t) { + + case DNS_SERVER_SYSTEM: + return m->dns_servers; + + case DNS_SERVER_FALLBACK: + return m->fallback_dns_servers; + + default: + return NULL; + } +} + +DnsServer *manager_set_dns_server(Manager *m, DnsServer *s) { + assert(m); + + if (m->current_dns_server == s) + return s; + + if (s) + log_info("Switching to %s DNS server %s.", + dns_server_type_to_string(s->type), + dns_server_string(s)); + + dns_server_unref(m->current_dns_server); + m->current_dns_server = dns_server_ref(s); + + if (m->unicast_scope) + dns_cache_flush(&m->unicast_scope->cache); + + return s; +} + +DnsServer *manager_get_dns_server(Manager *m) { + Link *l; + assert(m); + + /* Try to read updates resolv.conf */ + manager_read_resolv_conf(m); + + /* If no DNS server was chosen so far, pick the first one */ + if (!m->current_dns_server) + manager_set_dns_server(m, m->dns_servers); + + if (!m->current_dns_server) { + bool found = false; + Iterator i; + + /* No DNS servers configured, let's see if there are + * any on any links. If not, we use the fallback + * servers */ + + HASHMAP_FOREACH(l, m->links, i) + if (l->dns_servers) { + found = true; + break; + } + + if (!found) + manager_set_dns_server(m, m->fallback_dns_servers); + } + + return m->current_dns_server; +} + +void manager_next_dns_server(Manager *m) { + assert(m); + + /* If there's currently no DNS server set, then the next + * manager_get_dns_server() will find one */ + if (!m->current_dns_server) + return; + + /* Change to the next one, but make sure to follow the linked + * list only if the server is still linked. */ + if (m->current_dns_server->linked && m->current_dns_server->servers_next) { + manager_set_dns_server(m, m->current_dns_server->servers_next); + return; + } + + /* If there was no next one, then start from the beginning of + * the list */ + if (m->current_dns_server->type == DNS_SERVER_FALLBACK) + manager_set_dns_server(m, m->fallback_dns_servers); + else + manager_set_dns_server(m, m->dns_servers); +} + +static const char* const dns_server_type_table[_DNS_SERVER_TYPE_MAX] = { + [DNS_SERVER_SYSTEM] = "system", + [DNS_SERVER_FALLBACK] = "fallback", + [DNS_SERVER_LINK] = "link", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_server_type, DnsServerType); + +static const char* const dns_server_feature_level_table[_DNS_SERVER_FEATURE_LEVEL_MAX] = { + [DNS_SERVER_FEATURE_LEVEL_TCP] = "TCP", + [DNS_SERVER_FEATURE_LEVEL_UDP] = "UDP", + [DNS_SERVER_FEATURE_LEVEL_EDNS0] = "UDP+EDNS0", + [DNS_SERVER_FEATURE_LEVEL_DO] = "UDP+EDNS0+DO", + [DNS_SERVER_FEATURE_LEVEL_LARGE] = "UDP+EDNS0+DO+LARGE", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_server_feature_level, DnsServerFeatureLevel); diff --git a/src/resolve/src/resolved-dns-server.h b/src/resolve/src/resolved-dns-server.h new file mode 100644 index 0000000000..9f4a69c37a --- /dev/null +++ b/src/resolve/src/resolved-dns-server.h @@ -0,0 +1,143 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "in-addr-util.h" + +typedef struct DnsServer DnsServer; + +typedef enum DnsServerType { + DNS_SERVER_SYSTEM, + DNS_SERVER_FALLBACK, + DNS_SERVER_LINK, +} DnsServerType; +#define _DNS_SERVER_TYPE_MAX (DNS_SERVER_LINK + 1) + +const char* dns_server_type_to_string(DnsServerType i) _const_; +DnsServerType dns_server_type_from_string(const char *s) _pure_; + +typedef enum DnsServerFeatureLevel { + DNS_SERVER_FEATURE_LEVEL_TCP, + DNS_SERVER_FEATURE_LEVEL_UDP, + DNS_SERVER_FEATURE_LEVEL_EDNS0, + DNS_SERVER_FEATURE_LEVEL_DO, + DNS_SERVER_FEATURE_LEVEL_LARGE, + _DNS_SERVER_FEATURE_LEVEL_MAX, + _DNS_SERVER_FEATURE_LEVEL_INVALID = -1 +} DnsServerFeatureLevel; + +#define DNS_SERVER_FEATURE_LEVEL_WORST 0 +#define DNS_SERVER_FEATURE_LEVEL_BEST (_DNS_SERVER_FEATURE_LEVEL_MAX - 1) + +const char* dns_server_feature_level_to_string(int i) _const_; +int dns_server_feature_level_from_string(const char *s) _pure_; + +#include "resolved-link.h" +#include "resolved-manager.h" + +struct DnsServer { + Manager *manager; + + unsigned n_ref; + + DnsServerType type; + Link *link; + + int family; + union in_addr_union address; + + char *server_string; + + usec_t resend_timeout; + usec_t max_rtt; + + DnsServerFeatureLevel verified_feature_level; + DnsServerFeatureLevel possible_feature_level; + + size_t received_udp_packet_max; + + 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; + + usec_t verified_usec; + usec_t features_grace_period_usec; + + /* Whether we already warned about downgrading to non-DNSSEC mode for this server */ + bool warned_downgrade:1; + + /* Used when GC'ing old DNS servers when configuration changes. */ + bool marked:1; + + /* If linked is set, then this server appears in the servers linked list */ + bool linked:1; + LIST_FIELDS(DnsServer, servers); +}; + +int dns_server_new( + Manager *m, + DnsServer **ret, + DnsServerType type, + Link *link, + int family, + const union in_addr_union *address); + +DnsServer* dns_server_ref(DnsServer *s); +DnsServer* dns_server_unref(DnsServer *s); + +void dns_server_unlink(DnsServer *s); +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); + +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); + +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); + +void dns_server_unlink_all(DnsServer *first); +void dns_server_unlink_marked(DnsServer *first); +void dns_server_mark_all(DnsServer *first); + +DnsServer *manager_get_first_dns_server(Manager *m, DnsServerType t); + +DnsServer *manager_set_dns_server(Manager *m, DnsServer *s); +DnsServer *manager_get_dns_server(Manager *m); +void manager_next_dns_server(Manager *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsServer*, dns_server_unref); + +extern const struct hash_ops dns_server_hash_ops; diff --git a/src/resolve/src/resolved-dns-stream.c b/src/resolve/src/resolved-dns-stream.c new file mode 100644 index 0000000000..a1040aeff4 --- /dev/null +++ b/src/resolve/src/resolved-dns-stream.c @@ -0,0 +1,403 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "missing.h" +#include "resolved-dns-stream.h" + +#define DNS_STREAM_TIMEOUT_USEC (10 * USEC_PER_SEC) +#define DNS_STREAMS_MAX 128 + +static void dns_stream_stop(DnsStream *s) { + assert(s); + + s->io_event_source = sd_event_source_unref(s->io_event_source); + s->timeout_event_source = sd_event_source_unref(s->timeout_event_source); + s->fd = safe_close(s->fd); +} + +static int dns_stream_update_io(DnsStream *s) { + int f = 0; + + assert(s); + + if (s->write_packet && s->n_written < sizeof(s->write_size) + s->write_packet->size) + f |= EPOLLOUT; + if (!s->read_packet || s->n_read < sizeof(s->read_size) + s->read_packet->size) + f |= EPOLLIN; + + return sd_event_source_set_io_events(s->io_event_source, f); +} + +static int dns_stream_complete(DnsStream *s, int error) { + assert(s); + + dns_stream_stop(s); + + if (s->complete) + s->complete(s, error); + else + dns_stream_free(s); + + return 0; +} + +static int dns_stream_identify(DnsStream *s) { + union { + struct cmsghdr header; /* For alignment */ + uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) + + EXTRA_CMSG_SPACE /* kernel appears to require extra space */]; + } control; + struct msghdr mh = {}; + struct cmsghdr *cmsg; + socklen_t sl; + int r; + + assert(s); + + if (s->identified) + return 0; + + /* Query the local side */ + s->local_salen = sizeof(s->local); + r = getsockname(s->fd, &s->local.sa, &s->local_salen); + if (r < 0) + return -errno; + if (s->local.sa.sa_family == AF_INET6 && s->ifindex <= 0) + s->ifindex = s->local.in6.sin6_scope_id; + + /* Query the remote side */ + s->peer_salen = sizeof(s->peer); + r = getpeername(s->fd, &s->peer.sa, &s->peer_salen); + if (r < 0) + return -errno; + if (s->peer.sa.sa_family == AF_INET6 && s->ifindex <= 0) + s->ifindex = s->peer.in6.sin6_scope_id; + + /* Check consistency */ + assert(s->peer.sa.sa_family == s->local.sa.sa_family); + assert(IN_SET(s->peer.sa.sa_family, AF_INET, AF_INET6)); + + /* Query connection meta information */ + sl = sizeof(control); + if (s->peer.sa.sa_family == AF_INET) { + r = getsockopt(s->fd, IPPROTO_IP, IP_PKTOPTIONS, &control, &sl); + if (r < 0) + return -errno; + } else if (s->peer.sa.sa_family == AF_INET6) { + + r = getsockopt(s->fd, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, &control, &sl); + if (r < 0) + return -errno; + } else + return -EAFNOSUPPORT; + + mh.msg_control = &control; + mh.msg_controllen = sl; + + CMSG_FOREACH(cmsg, &mh) { + + if (cmsg->cmsg_level == IPPROTO_IPV6) { + assert(s->peer.sa.sa_family == AF_INET6); + + switch (cmsg->cmsg_type) { + + case IPV6_PKTINFO: { + struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); + + if (s->ifindex <= 0) + s->ifindex = i->ipi6_ifindex; + break; + } + + case IPV6_HOPLIMIT: + s->ttl = *(int *) CMSG_DATA(cmsg); + break; + } + + } else if (cmsg->cmsg_level == IPPROTO_IP) { + assert(s->peer.sa.sa_family == AF_INET); + + switch (cmsg->cmsg_type) { + + case IP_PKTINFO: { + struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); + + if (s->ifindex <= 0) + s->ifindex = i->ipi_ifindex; + break; + } + + case IP_TTL: + s->ttl = *(int *) CMSG_DATA(cmsg); + break; + } + } + } + + /* The Linux kernel sets the interface index to the loopback + * device if the connection came from the local host since it + * avoids the routing table in such a case. Let's unset the + * interface index in such a case. */ + if (s->ifindex == LOOPBACK_IFINDEX) + s->ifindex = 0; + + /* If we don't know the interface index still, we look for the + * first local interface with a matching address. Yuck! */ + if (s->ifindex <= 0) + s->ifindex = manager_find_ifindex(s->manager, s->local.sa.sa_family, s->local.sa.sa_family == AF_INET ? (union in_addr_union*) &s->local.in.sin_addr : (union in_addr_union*) &s->local.in6.sin6_addr); + + if (s->protocol == DNS_PROTOCOL_LLMNR && s->ifindex > 0) { + uint32_t ifindex = htobe32(s->ifindex); + + /* Make sure all packets for this connection are sent on the same interface */ + if (s->local.sa.sa_family == AF_INET) { + r = setsockopt(s->fd, IPPROTO_IP, IP_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + log_debug_errno(errno, "Failed to invoke IP_UNICAST_IF: %m"); + } else if (s->local.sa.sa_family == AF_INET6) { + r = setsockopt(s->fd, IPPROTO_IPV6, IPV6_UNICAST_IF, &ifindex, sizeof(ifindex)); + if (r < 0) + log_debug_errno(errno, "Failed to invoke IPV6_UNICAST_IF: %m"); + } + } + + s->identified = true; + + return 0; +} + +static int on_stream_timeout(sd_event_source *es, usec_t usec, void *userdata) { + DnsStream *s = userdata; + + assert(s); + + return dns_stream_complete(s, ETIMEDOUT); +} + +static int on_stream_io(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + DnsStream *s = userdata; + int r; + + assert(s); + + r = dns_stream_identify(s); + if (r < 0) + return dns_stream_complete(s, -r); + + if ((revents & EPOLLOUT) && + s->write_packet && + s->n_written < sizeof(s->write_size) + s->write_packet->size) { + + struct iovec iov[2]; + ssize_t ss; + + iov[0].iov_base = &s->write_size; + iov[0].iov_len = sizeof(s->write_size); + iov[1].iov_base = DNS_PACKET_DATA(s->write_packet); + iov[1].iov_len = s->write_packet->size; + + IOVEC_INCREMENT(iov, 2, s->n_written); + + ss = writev(fd, iov, 2); + if (ss < 0) { + if (errno != EINTR && errno != EAGAIN) + return dns_stream_complete(s, errno); + } else + s->n_written += ss; + + /* Are we done? If so, disable the event source for EPOLLOUT */ + if (s->n_written >= sizeof(s->write_size) + s->write_packet->size) { + r = dns_stream_update_io(s); + if (r < 0) + return dns_stream_complete(s, -r); + } + } + + if ((revents & (EPOLLIN|EPOLLHUP|EPOLLRDHUP)) && + (!s->read_packet || + s->n_read < sizeof(s->read_size) + s->read_packet->size)) { + + if (s->n_read < sizeof(s->read_size)) { + ssize_t ss; + + ss = read(fd, (uint8_t*) &s->read_size + s->n_read, sizeof(s->read_size) - s->n_read); + if (ss < 0) { + if (errno != EINTR && errno != EAGAIN) + return dns_stream_complete(s, errno); + } else if (ss == 0) + return dns_stream_complete(s, ECONNRESET); + else + s->n_read += ss; + } + + if (s->n_read >= sizeof(s->read_size)) { + + if (be16toh(s->read_size) < DNS_PACKET_HEADER_SIZE) + return dns_stream_complete(s, EBADMSG); + + if (s->n_read < sizeof(s->read_size) + be16toh(s->read_size)) { + ssize_t ss; + + if (!s->read_packet) { + r = dns_packet_new(&s->read_packet, s->protocol, be16toh(s->read_size)); + if (r < 0) + return dns_stream_complete(s, -r); + + s->read_packet->size = be16toh(s->read_size); + s->read_packet->ipproto = IPPROTO_TCP; + s->read_packet->family = s->peer.sa.sa_family; + s->read_packet->ttl = s->ttl; + s->read_packet->ifindex = s->ifindex; + + if (s->read_packet->family == AF_INET) { + s->read_packet->sender.in = s->peer.in.sin_addr; + s->read_packet->sender_port = be16toh(s->peer.in.sin_port); + s->read_packet->destination.in = s->local.in.sin_addr; + s->read_packet->destination_port = be16toh(s->local.in.sin_port); + } else { + assert(s->read_packet->family == AF_INET6); + s->read_packet->sender.in6 = s->peer.in6.sin6_addr; + s->read_packet->sender_port = be16toh(s->peer.in6.sin6_port); + s->read_packet->destination.in6 = s->local.in6.sin6_addr; + s->read_packet->destination_port = be16toh(s->local.in6.sin6_port); + + if (s->read_packet->ifindex == 0) + s->read_packet->ifindex = s->peer.in6.sin6_scope_id; + if (s->read_packet->ifindex == 0) + s->read_packet->ifindex = s->local.in6.sin6_scope_id; + } + } + + ss = read(fd, + (uint8_t*) DNS_PACKET_DATA(s->read_packet) + s->n_read - sizeof(s->read_size), + sizeof(s->read_size) + be16toh(s->read_size) - s->n_read); + if (ss < 0) { + if (errno != EINTR && errno != EAGAIN) + return dns_stream_complete(s, errno); + } else if (ss == 0) + return dns_stream_complete(s, ECONNRESET); + else + s->n_read += ss; + } + + /* Are we done? If so, disable the event source for EPOLLIN */ + if (s->n_read >= sizeof(s->read_size) + be16toh(s->read_size)) { + r = dns_stream_update_io(s); + if (r < 0) + return dns_stream_complete(s, -r); + + /* If there's a packet handler + * installed, call that. Note that + * this is optional... */ + if (s->on_packet) + return s->on_packet(s); + } + } + } + + if ((s->write_packet && s->n_written >= sizeof(s->write_size) + s->write_packet->size) && + (s->read_packet && s->n_read >= sizeof(s->read_size) + s->read_packet->size)) + return dns_stream_complete(s, 0); + + return 0; +} + +DnsStream *dns_stream_free(DnsStream *s) { + if (!s) + return NULL; + + dns_stream_stop(s); + + if (s->manager) { + LIST_REMOVE(streams, s->manager->dns_streams, s); + s->manager->n_dns_streams--; + } + + dns_packet_unref(s->write_packet); + dns_packet_unref(s->read_packet); + + free(s); + + return 0; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsStream*, dns_stream_free); + +int dns_stream_new(Manager *m, DnsStream **ret, DnsProtocol protocol, int fd) { + _cleanup_(dns_stream_freep) DnsStream *s = NULL; + int r; + + assert(m); + assert(fd >= 0); + + if (m->n_dns_streams > DNS_STREAMS_MAX) + return -EBUSY; + + s = new0(DnsStream, 1); + if (!s) + return -ENOMEM; + + s->fd = -1; + s->protocol = protocol; + + r = sd_event_add_io(m->event, &s->io_event_source, fd, EPOLLIN, on_stream_io, s); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s->io_event_source, "dns-stream-io"); + + r = sd_event_add_time( + m->event, + &s->timeout_event_source, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + DNS_STREAM_TIMEOUT_USEC, 0, + on_stream_timeout, s); + if (r < 0) + return r; + + (void) sd_event_source_set_description(s->timeout_event_source, "dns-stream-timeout"); + + LIST_PREPEND(streams, m->dns_streams, s); + s->manager = m; + s->fd = fd; + m->n_dns_streams++; + + *ret = s; + s = NULL; + + return 0; +} + +int dns_stream_write_packet(DnsStream *s, DnsPacket *p) { + assert(s); + + if (s->write_packet) + return -EBUSY; + + s->write_packet = dns_packet_ref(p); + s->write_size = htobe16(p->size); + s->n_written = 0; + + return dns_stream_update_io(s); +} diff --git a/src/resolve/src/resolved-dns-stream.h b/src/resolve/src/resolved-dns-stream.h new file mode 100644 index 0000000000..5ccc842249 --- /dev/null +++ b/src/resolve/src/resolved-dns-stream.h @@ -0,0 +1,61 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "socket-util.h" + +typedef struct DnsStream DnsStream; + +#include "resolved-dns-packet.h" +#include "resolved-dns-transaction.h" + +struct DnsStream { + Manager *manager; + + DnsProtocol protocol; + + int fd; + union sockaddr_union peer; + socklen_t peer_salen; + union sockaddr_union local; + socklen_t local_salen; + int ifindex; + uint32_t ttl; + bool identified; + + sd_event_source *io_event_source; + sd_event_source *timeout_event_source; + + be16_t write_size, read_size; + DnsPacket *write_packet, *read_packet; + size_t n_written, n_read; + + int (*on_packet)(DnsStream *s); + int (*complete)(DnsStream *s, int error); + + DnsTransaction *transaction; + + LIST_FIELDS(DnsStream, streams); +}; + +int dns_stream_new(Manager *m, DnsStream **s, DnsProtocol protocol, int fd); +DnsStream *dns_stream_free(DnsStream *s); + +int dns_stream_write_packet(DnsStream *s, DnsPacket *p); diff --git a/src/resolve/src/resolved-dns-synthesize.c b/src/resolve/src/resolved-dns-synthesize.c new file mode 100644 index 0000000000..f4a43dee8c --- /dev/null +++ b/src/resolve/src/resolved-dns-synthesize.c @@ -0,0 +1,413 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "alloc-util.h" +#include "hostname-util.h" +#include "local-addresses.h" +#include "resolved-dns-synthesize.h" + +int dns_synthesize_ifindex(int ifindex) { + + /* When the caller asked for resolving on a specific + * interface, we synthesize the answer for that + * interface. However, if nothing specific was claimed and we + * only return localhost RRs, we synthesize the answer for + * localhost. */ + + if (ifindex > 0) + return ifindex; + + return LOOPBACK_IFINDEX; +} + +int dns_synthesize_family(uint64_t flags) { + + /* Picks an address family depending on set flags. This is + * purely for synthesized answers, where the family we return + * for the reply should match what was requested in the + * question, even though we are synthesizing the answer + * here. */ + + if (!(flags & SD_RESOLVED_DNS)) { + if (flags & (SD_RESOLVED_LLMNR_IPV4|SD_RESOLVED_MDNS_IPV4)) + return AF_INET; + if (flags & (SD_RESOLVED_LLMNR_IPV6|SD_RESOLVED_MDNS_IPV6)) + return AF_INET6; + } + + return AF_UNSPEC; +} + +DnsProtocol dns_synthesize_protocol(uint64_t flags) { + + /* Similar as dns_synthesize_family() but does this for the + * protocol. If resolving via DNS was requested, we claim it + * was DNS. Similar, if nothing specific was + * requested. However, if only resolving via LLMNR was + * requested we return that. */ + + if (flags & SD_RESOLVED_DNS) + return DNS_PROTOCOL_DNS; + if (flags & SD_RESOLVED_LLMNR) + return DNS_PROTOCOL_LLMNR; + if (flags & SD_RESOLVED_MDNS) + return DNS_PROTOCOL_MDNS; + + return DNS_PROTOCOL_DNS; +} + +static int synthesize_localhost_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + int r; + + assert(m); + assert(key); + assert(answer); + + r = dns_answer_reserve(answer, 2); + if (r < 0) + return r; + + if (IN_SET(key->type, DNS_TYPE_A, DNS_TYPE_ANY)) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, DNS_RESOURCE_KEY_NAME(key)); + if (!rr) + return -ENOMEM; + + rr->a.in_addr.s_addr = htobe32(INADDR_LOOPBACK); + + r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + if (IN_SET(key->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_AAAA, DNS_RESOURCE_KEY_NAME(key)); + if (!rr) + return -ENOMEM; + + rr->aaaa.in6_addr = in6addr_loopback; + + r = dns_answer_add(*answer, rr, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_ptr(DnsAnswer **answer, const char *from, const char *to, int ifindex, DnsAnswerFlags flags) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_PTR, from); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(to); + if (!rr->ptr.name) + return -ENOMEM; + + return dns_answer_add(*answer, rr, ifindex, flags); +} + +static int synthesize_localhost_ptr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + int r; + + assert(m); + assert(key); + assert(answer); + + if (IN_SET(key->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) { + r = dns_answer_reserve(answer, 1); + if (r < 0) + return r; + + r = answer_add_ptr(answer, DNS_RESOURCE_KEY_NAME(key), "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_addresses_rr( + DnsAnswer **answer, + const char *name, + struct local_address *addresses, + unsigned n_addresses) { + + unsigned j; + int r; + + assert(answer); + assert(name); + + r = dns_answer_reserve(answer, n_addresses); + if (r < 0) + return r; + + for (j = 0; j < n_addresses; j++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + r = dns_resource_record_new_address(&rr, addresses[j].family, &addresses[j].address, name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int answer_add_addresses_ptr( + DnsAnswer **answer, + const char *name, + struct local_address *addresses, + unsigned n_addresses, + int af, const union in_addr_union *match) { + + unsigned j; + int r; + + assert(answer); + assert(name); + + for (j = 0; j < n_addresses; j++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + if (af != AF_UNSPEC) { + + if (addresses[j].family != af) + continue; + + if (match && !in_addr_equal(af, match, &addresses[j].address)) + continue; + } + + r = dns_answer_reserve(answer, 1); + if (r < 0) + return r; + + r = dns_resource_record_new_reverse(&rr, addresses[j].family, &addresses[j].address, name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, addresses[j].ifindex, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int synthesize_system_hostname_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n = 0, af; + + assert(m); + assert(key); + assert(answer); + + af = dns_type_to_af(key->type); + if (af >= 0) { + n = local_addresses(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + + if (n == 0) { + struct local_address buffer[2]; + + /* If we have no local addresses then use ::1 + * and 127.0.0.2 as local ones. */ + + if (af == AF_INET || af == AF_UNSPEC) + buffer[n++] = (struct local_address) { + .family = AF_INET, + .ifindex = dns_synthesize_ifindex(ifindex), + .address.in.s_addr = htobe32(0x7F000002), + }; + + if (af == AF_INET6 || af == AF_UNSPEC) + buffer[n++] = (struct local_address) { + .family = AF_INET6, + .ifindex = dns_synthesize_ifindex(ifindex), + .address.in6 = in6addr_loopback, + }; + + return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), buffer, n); + } + } + + return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); +} + +static int synthesize_system_hostname_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n, r; + + assert(m); + assert(address); + assert(answer); + + if (af == AF_INET && address->in.s_addr == htobe32(0x7F000002)) { + + /* Always map the IPv4 address 127.0.0.2 to the local + * hostname, in addition to "localhost": */ + + r = dns_answer_reserve(answer, 3); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->llmnr_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", m->mdns_hostname, dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + r = answer_add_ptr(answer, "2.0.0.127.in-addr.arpa", "localhost", dns_synthesize_ifindex(ifindex), DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + return 0; + } + + n = local_addresses(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + + r = answer_add_addresses_ptr(answer, m->llmnr_hostname, addresses, n, af, address); + if (r < 0) + return r; + + return answer_add_addresses_ptr(answer, m->mdns_hostname, addresses, n, af, address); +} + +static int synthesize_gateway_rr(Manager *m, const DnsResourceKey *key, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n = 0, af; + + assert(m); + assert(key); + assert(answer); + + af = dns_type_to_af(key->type); + if (af >= 0) { + n = local_gateways(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + } + + return answer_add_addresses_rr(answer, DNS_RESOURCE_KEY_NAME(key), addresses, n); +} + +static int synthesize_gateway_ptr(Manager *m, int af, const union in_addr_union *address, int ifindex, DnsAnswer **answer) { + _cleanup_free_ struct local_address *addresses = NULL; + int n; + + assert(m); + assert(address); + assert(answer); + + n = local_gateways(m->rtnl, ifindex, af, &addresses); + if (n < 0) + return n; + + return answer_add_addresses_ptr(answer, "gateway", addresses, n, af, address); +} + +int dns_synthesize_answer( + Manager *m, + DnsQuestion *q, + int ifindex, + DnsAnswer **ret) { + + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnsResourceKey *key; + bool found = false; + int r; + + assert(m); + assert(q); + + DNS_QUESTION_FOREACH(key, q) { + union in_addr_union address; + const char *name; + int af; + + if (key->class != DNS_CLASS_IN && + key->class != DNS_CLASS_ANY) + continue; + + name = DNS_RESOURCE_KEY_NAME(key); + + if (is_localhost(name)) { + + r = synthesize_localhost_rr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize localhost RRs: %m"); + + } else if (manager_is_own_hostname(m, name)) { + + r = synthesize_system_hostname_rr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize system hostname RRs: %m"); + + } else if (is_gateway_hostname(name)) { + + r = synthesize_gateway_rr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize gateway RRs: %m"); + + } else if ((dns_name_endswith(name, "127.in-addr.arpa") > 0 && dns_name_equal(name, "2.0.0.127.in-addr.arpa") == 0) || + dns_name_equal(name, "1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa") > 0) { + + r = synthesize_localhost_ptr(m, key, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize localhost PTR RRs: %m"); + + } else if (dns_name_address(name, &af, &address) > 0) { + + r = synthesize_system_hostname_ptr(m, af, &address, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize system hostname PTR RR: %m"); + + r = synthesize_gateway_ptr(m, af, &address, ifindex, &answer); + if (r < 0) + return log_error_errno(r, "Failed to synthesize gateway hostname PTR RR: %m"); + } else + continue; + + found = true; + } + + r = found; + + if (ret) { + *ret = answer; + answer = NULL; + } + + return r; +} diff --git a/src/resolve/src/resolved-dns-synthesize.h b/src/resolve/src/resolved-dns-synthesize.h new file mode 100644 index 0000000000..5d829bb2e7 --- /dev/null +++ b/src/resolve/src/resolved-dns-synthesize.h @@ -0,0 +1,30 @@ +#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 . +***/ + +#include "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-manager.h" + +int dns_synthesize_ifindex(int ifindex); +int dns_synthesize_family(uint64_t flags); +DnsProtocol dns_synthesize_protocol(uint64_t flags); + +int dns_synthesize_answer(Manager *m, DnsQuestion *q, int ifindex, DnsAnswer **ret); diff --git a/src/resolve/src/resolved-dns-transaction.c b/src/resolve/src/resolved-dns-transaction.c new file mode 100644 index 0000000000..d48fdd1281 --- /dev/null +++ b/src/resolve/src/resolved-dns-transaction.c @@ -0,0 +1,3026 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "af-list.h" +#include "alloc-util.h" +#include "dns-domain.h" +#include "errno-list.h" +#include "fd-util.h" +#include "random-util.h" +#include "resolved-dns-cache.h" +#include "resolved-dns-transaction.h" +#include "resolved-llmnr.h" +#include "string-table.h" + +#define TRANSACTIONS_MAX 4096 + +static void dns_transaction_reset_answer(DnsTransaction *t) { + assert(t); + + t->received = dns_packet_unref(t->received); + t->answer = dns_answer_unref(t->answer); + t->answer_rcode = 0; + t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; + t->answer_authenticated = false; + t->answer_nsec_ttl = (uint32_t) -1; + t->answer_errno = 0; +} + +static void dns_transaction_flush_dnssec_transactions(DnsTransaction *t) { + DnsTransaction *z; + + assert(t); + + while ((z = set_steal_first(t->dnssec_transactions))) { + set_remove(z->notify_transactions, t); + dns_transaction_gc(z); + } +} + +static void dns_transaction_close_connection(DnsTransaction *t) { + assert(t); + + t->stream = dns_stream_free(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); +} + +static void dns_transaction_stop_timeout(DnsTransaction *t) { + assert(t); + + t->timeout_event_source = sd_event_source_unref(t->timeout_event_source); +} + +DnsTransaction* dns_transaction_free(DnsTransaction *t) { + DnsQueryCandidate *c; + DnsZoneItem *i; + DnsTransaction *z; + + if (!t) + return NULL; + + log_debug("Freeing transaction %" PRIu16 ".", t->id); + + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); + + dns_packet_unref(t->sent); + dns_transaction_reset_answer(t); + + dns_server_unref(t->server); + + if (t->scope) { + hashmap_remove_value(t->scope->transactions_by_key, t->key, t); + LIST_REMOVE(transactions_by_scope, t->scope->transactions, t); + + if (t->id != 0) + hashmap_remove(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id)); + } + + while ((c = set_steal_first(t->notify_query_candidates))) + set_remove(c->transactions, t); + set_free(t->notify_query_candidates); + + while ((i = set_steal_first(t->notify_zone_items))) + i->probe_transaction = NULL; + set_free(t->notify_zone_items); + + while ((z = set_steal_first(t->notify_transactions))) + set_remove(z->dnssec_transactions, t); + set_free(t->notify_transactions); + + dns_transaction_flush_dnssec_transactions(t); + set_free(t->dnssec_transactions); + + dns_answer_unref(t->validated_keys); + dns_resource_key_unref(t->key); + free(t->key_string); + + free(t); + return NULL; +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsTransaction*, dns_transaction_free); + +bool dns_transaction_gc(DnsTransaction *t) { + assert(t); + + if (t->block_gc > 0) + return true; + + if (set_isempty(t->notify_query_candidates) && + set_isempty(t->notify_zone_items) && + set_isempty(t->notify_transactions)) { + dns_transaction_free(t); + return false; + } + + return true; +} + +static uint16_t pick_new_id(Manager *m) { + uint16_t new_id; + + /* Find a fresh, unused transaction id. Note that this loop is bounded because there's a limit on the number of + * transactions, and it's much lower than the space of IDs. */ + + assert_cc(TRANSACTIONS_MAX < 0xFFFF); + + do + random_bytes(&new_id, sizeof(new_id)); + while (new_id == 0 || + hashmap_get(m->dns_transactions, UINT_TO_PTR(new_id))); + + return new_id; +} + +int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key) { + _cleanup_(dns_transaction_freep) DnsTransaction *t = NULL; + int r; + + assert(ret); + assert(s); + assert(key); + + /* Don't allow looking up invalid or pseudo RRs */ + if (!dns_type_is_valid_query(key->type)) + return -EINVAL; + if (dns_type_is_obsolete(key->type)) + return -EOPNOTSUPP; + + /* We only support the IN class */ + if (key->class != DNS_CLASS_IN && key->class != DNS_CLASS_ANY) + return -EOPNOTSUPP; + + if (hashmap_size(s->manager->dns_transactions) >= TRANSACTIONS_MAX) + return -EBUSY; + + r = hashmap_ensure_allocated(&s->manager->dns_transactions, NULL); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&s->transactions_by_key, &dns_resource_key_hash_ops); + if (r < 0) + return r; + + t = new0(DnsTransaction, 1); + if (!t) + return -ENOMEM; + + t->dns_udp_fd = -1; + t->answer_source = _DNS_TRANSACTION_SOURCE_INVALID; + t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_nsec_ttl = (uint32_t) -1; + t->key = dns_resource_key_ref(key); + t->current_feature_level = _DNS_SERVER_FEATURE_LEVEL_INVALID; + + t->id = pick_new_id(s->manager); + + r = hashmap_put(s->manager->dns_transactions, UINT_TO_PTR(t->id), t); + if (r < 0) { + t->id = 0; + return r; + } + + r = hashmap_replace(s->transactions_by_key, t->key, t); + if (r < 0) { + hashmap_remove(s->manager->dns_transactions, UINT_TO_PTR(t->id)); + return r; + } + + LIST_PREPEND(transactions_by_scope, s->transactions, t); + t->scope = s; + + s->manager->n_transactions_total ++; + + if (ret) + *ret = t; + + t = NULL; + + return 0; +} + +static void dns_transaction_shuffle_id(DnsTransaction *t) { + uint16_t new_id; + assert(t); + + /* Pick a new ID for this transaction. */ + + new_id = pick_new_id(t->scope->manager); + assert_se(hashmap_remove_and_put(t->scope->manager->dns_transactions, UINT_TO_PTR(t->id), UINT_TO_PTR(new_id), t) >= 0); + + log_debug("Transaction %" PRIu16 " is now %" PRIu16 ".", t->id, new_id); + t->id = new_id; + + /* Make sure we generate a new packet with the new ID */ + t->sent = dns_packet_unref(t->sent); +} + +static void dns_transaction_tentative(DnsTransaction *t, DnsPacket *p) { + _cleanup_free_ char *pretty = NULL; + DnsZoneItem *z; + + assert(t); + assert(p); + + if (manager_our_packet(t->scope->manager, p) != 0) + return; + + in_addr_to_string(p->family, &p->sender, &pretty); + + log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s got tentative packet from %s.", + t->id, + dns_transaction_key_string(t), + dns_protocol_to_string(t->scope->protocol), + t->scope->link ? t->scope->link->name : "*", + t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), + pretty); + + /* RFC 4795, Section 4.1 says that the peer with the + * lexicographically smaller IP address loses */ + if (memcmp(&p->sender, &p->destination, FAMILY_ADDRESS_SIZE(p->family)) >= 0) { + log_debug("Peer has lexicographically larger IP address and thus lost in the conflict."); + return; + } + + log_debug("We have the lexicographically larger IP address and thus lost in the conflict."); + + t->block_gc++; + while ((z = set_first(t->notify_zone_items))) { + /* First, make sure the zone item drops the reference + * to us */ + dns_zone_item_probe_stop(z); + + /* Secondly, report this as conflict, so that we might + * look for a different hostname */ + dns_zone_item_conflict(z); + } + t->block_gc--; + + dns_transaction_gc(t); +} + +void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state) { + DnsQueryCandidate *c; + DnsZoneItem *z; + DnsTransaction *d; + Iterator i; + const char *st; + + assert(t); + assert(!DNS_TRANSACTION_IS_LIVE(state)); + + if (state == DNS_TRANSACTION_DNSSEC_FAILED) + log_struct(LOG_NOTICE, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_FAILURE), + LOG_MESSAGE("DNSSEC validation failed for question %s: %s", dns_transaction_key_string(t), dnssec_result_to_string(t->answer_dnssec_result)), + "DNS_TRANSACTION=%" PRIu16, t->id, + "DNS_QUESTION=%s", dns_transaction_key_string(t), + "DNSSEC_RESULT=%s", dnssec_result_to_string(t->answer_dnssec_result), + "DNS_SERVER=%s", dns_server_string(t->server), + "DNS_SERVER_FEATURE_LEVEL=%s", dns_server_feature_level_to_string(t->server->possible_feature_level), + NULL); + + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function. */ + + if (state == DNS_TRANSACTION_ERRNO) + st = errno_to_name(t->answer_errno); + else + st = dns_transaction_state_to_string(state); + + log_debug("Transaction %" PRIu16 " for <%s> on scope %s on %s/%s now complete with <%s> from %s (%s).", + t->id, + dns_transaction_key_string(t), + dns_protocol_to_string(t->scope->protocol), + t->scope->link ? t->scope->link->name : "*", + t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family), + st, + t->answer_source < 0 ? "none" : dns_transaction_source_to_string(t->answer_source), + t->answer_authenticated ? "authenticated" : "unsigned"); + + t->state = state; + + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); + + /* Notify all queries that are interested, but make sure the + * transaction isn't freed while we are still looking at it */ + t->block_gc++; + + SET_FOREACH(c, t->notify_query_candidates, i) + dns_query_candidate_notify(c); + SET_FOREACH(z, t->notify_zone_items, i) + dns_zone_item_notify(z); + + if (!set_isempty(t->notify_transactions)) { + DnsTransaction **nt; + unsigned j, n = 0; + + /* We need to be careful when notifying other + * transactions, as that might destroy other + * transactions in our list. Hence, in order to be + * able to safely iterate through the list of + * transactions, take a GC lock on all of them + * first. Then, in a second loop, notify them, but + * first unlock that specific transaction. */ + + nt = newa(DnsTransaction*, set_size(t->notify_transactions)); + SET_FOREACH(d, t->notify_transactions, i) { + nt[n++] = d; + d->block_gc++; + } + + assert(n == set_size(t->notify_transactions)); + + for (j = 0; j < n; j++) { + if (set_contains(t->notify_transactions, nt[j])) + dns_transaction_notify(nt[j], t); + + nt[j]->block_gc--; + dns_transaction_gc(nt[j]); + } + } + + t->block_gc--; + dns_transaction_gc(t); +} + +static int dns_transaction_pick_server(DnsTransaction *t) { + DnsServer *server; + + assert(t); + assert(t->scope->protocol == DNS_PROTOCOL_DNS); + + server = dns_scope_get_dns_server(t->scope); + if (!server) + return -ESRCH; + + t->current_feature_level = dns_server_possible_feature_level(server); + + if (server == t->server) + return 0; + + dns_server_unref(t->server); + t->server = dns_server_ref(server); + + return 1; +} + +static void dns_transaction_retry(DnsTransaction *t) { + int r; + + assert(t); + + log_debug("Retrying transaction %" PRIu16 ".", t->id); + + /* Before we try again, switch to a new server. */ + dns_scope_next_dns_server(t->scope); + + r = dns_transaction_go(t); + if (r < 0) { + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); + } +} + +static int dns_transaction_maybe_restart(DnsTransaction *t) { + assert(t); + + if (!t->server) + return 0; + + if (t->current_feature_level <= dns_server_possible_feature_level(t->server)) + return 0; + + /* The server's current feature level is lower than when we sent the original query. We learnt something from + the response or possibly an auxiliary DNSSEC response that we didn't know before. We take that as reason to + restart the whole transaction. This is a good idea to deal with servers that respond rubbish if we include + OPT RR or DO bit. One of these cases is documented here, for example: + https://open.nlnetlabs.nl/pipermail/dnssec-trigger/2014-November/000376.html */ + + 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); +} + +static int on_stream_complete(DnsStream *s, int error) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsTransaction *t; + + assert(s); + assert(s->transaction); + + /* Copy the data we care about out of the stream before we + * destroy it. */ + t = s->transaction; + p = dns_packet_ref(s->read_packet); + + t->stream = dns_stream_free(t->stream); + + if (ERRNO_IS_DISCONNECT(error)) { + usec_t usec; + + if (t->scope->protocol == DNS_PROTOCOL_LLMNR) { + /* If the LLMNR/TCP connection failed, the host doesn't support LLMNR, and we cannot answer the + * question on this scope. */ + dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); + return 0; + } + + log_debug_errno(error, "Connection failure for DNS TCP stream: %m"); + 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); + return 0; + } + if (error != 0) { + t->answer_errno = error; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); + return 0; + } + + if (dns_packet_validate_reply(p) <= 0) { + log_debug("Invalid TCP reply packet."); + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return 0; + } + + dns_scope_check_conflicts(t->scope, p); + + t->block_gc++; + dns_transaction_process_reply(t, p); + t->block_gc--; + + /* If the response wasn't useful, then complete the transition + * now. After all, we are the worst feature set now with TCP + * sockets, and there's really no point in retrying. */ + if (t->state == DNS_TRANSACTION_PENDING) + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + else + dns_transaction_gc(t); + + return 0; +} + +static int dns_transaction_open_tcp(DnsTransaction *t) { + _cleanup_close_ int fd = -1; + int r; + + assert(t); + + dns_transaction_close_connection(t); + + switch (t->scope->protocol) { + + case DNS_PROTOCOL_DNS: + r = dns_transaction_pick_server(t); + if (r < 0) + return r; + + if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) + return -EOPNOTSUPP; + + r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); + if (r < 0) + return r; + + fd = dns_scope_socket_tcp(t->scope, AF_UNSPEC, NULL, t->server, 53); + break; + + case DNS_PROTOCOL_LLMNR: + /* When we already received a reply to this (but it was truncated), send to its sender address */ + if (t->received) + fd = dns_scope_socket_tcp(t->scope, t->received->family, &t->received->sender, NULL, t->received->sender_port); + else { + union in_addr_union address; + int family = AF_UNSPEC; + + /* Otherwise, try to talk to the owner of a + * the IP address, in case this is a reverse + * PTR lookup */ + + r = dns_name_address(DNS_RESOURCE_KEY_NAME(t->key), &family, &address); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + if (family != t->scope->family) + return -ESRCH; + + fd = dns_scope_socket_tcp(t->scope, family, &address, NULL, LLMNR_PORT); + } + + break; + + default: + return -EAFNOSUPPORT; + } + + if (fd < 0) + return fd; + + r = dns_stream_new(t->scope->manager, &t->stream, t->scope->protocol, fd); + if (r < 0) + return r; + fd = -1; + + r = dns_stream_write_packet(t->stream, t->sent); + if (r < 0) { + t->stream = dns_stream_free(t->stream); + return r; + } + + t->stream->complete = on_stream_complete; + t->stream->transaction = 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; + + dns_transaction_reset_answer(t); + + t->tried_stream = true; + + return 0; +} + +static void dns_transaction_cache_answer(DnsTransaction *t) { + assert(t); + + /* For mDNS we cache whenever we get the packet, rather than + * in each transaction. */ + if (!IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) + 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 + * the cache then we could. */ + if (!DNS_PACKET_SHALL_CACHE(t->received)) + return; + + dns_cache_put(&t->scope->cache, + t->key, + t->answer_rcode, + t->answer, + t->answer_authenticated, + t->answer_nsec_ttl, + 0, + t->received->family, + &t->received->sender); +} + +static bool dns_transaction_dnssec_is_live(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + + assert(t); + + SET_FOREACH(dt, t->dnssec_transactions, i) + if (DNS_TRANSACTION_IS_LIVE(dt->state)) + return true; + + return false; +} + +static int dns_transaction_dnssec_ready(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + + assert(t); + + /* Checks whether the auxiliary DNSSEC transactions of our transaction have completed, or are still + * ongoing. Returns 0, if we aren't ready for the DNSSEC validation, positive if we are. */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + switch (dt->state) { + + case DNS_TRANSACTION_NULL: + case DNS_TRANSACTION_PENDING: + case DNS_TRANSACTION_VALIDATING: + /* Still ongoing */ + return 0; + + case DNS_TRANSACTION_RCODE_FAILURE: + if (dt->answer_rcode != DNS_RCODE_NXDOMAIN) { + 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. */ + + case DNS_TRANSACTION_SUCCESS: + /* All good. */ + break; + + case DNS_TRANSACTION_DNSSEC_FAILED: + /* We handle DNSSEC failures different from other errors, as we care about the DNSSEC + * validationr result */ + + log_debug("Auxiliary DNSSEC RR query failed validation: %s", dnssec_result_to_string(dt->answer_dnssec_result)); + t->answer_dnssec_result = dt->answer_dnssec_result; /* Copy error code over */ + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return 0; + + + default: + log_debug("Auxiliary DNSSEC RR query failed with %s", dns_transaction_state_to_string(dt->state)); + goto fail; + } + } + + /* All is ready, we can go and validate */ + return 1; + +fail: + t->answer_dnssec_result = DNSSEC_FAILED_AUXILIARY; + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return 0; +} + +static void dns_transaction_process_dnssec(DnsTransaction *t) { + int r; + + assert(t); + + /* Are there ongoing DNSSEC transactions? If so, let's wait for them. */ + r = dns_transaction_dnssec_ready(t); + if (r < 0) + goto fail; + if (r == 0) /* We aren't ready yet (or one of our auxiliary transactions failed, and we shouldn't validate now */ + return; + + /* See if we learnt things from the additional DNSSEC transactions, that we didn't know before, and better + * restart the lookup immediately. */ + r = dns_transaction_maybe_restart(t); + if (r < 0) + goto fail; + if (r > 0) /* Transaction got restarted... */ + return; + + /* All our auxiliary DNSSEC transactions are complete now. Try + * to validate our RRset now. */ + r = dns_transaction_validate_dnssec(t); + if (r == -EBADMSG) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + if (r < 0) + goto fail; + + if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER && + t->scope->dnssec_mode == DNSSEC_YES) { + /* We are not in automatic downgrade mode, and the + * server is bad, refuse operation. */ + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return; + } + + if (!IN_SET(t->answer_dnssec_result, + _DNSSEC_RESULT_INVALID, /* No DNSSEC validation enabled */ + DNSSEC_VALIDATED, /* Answer is signed and validated successfully */ + DNSSEC_UNSIGNED, /* Answer is right-fully unsigned */ + DNSSEC_INCOMPATIBLE_SERVER)) { /* Server does not do DNSSEC (Yay, we are downgrade attack vulnerable!) */ + dns_transaction_complete(t, DNS_TRANSACTION_DNSSEC_FAILED); + return; + } + + if (t->answer_dnssec_result == DNSSEC_INCOMPATIBLE_SERVER) + dns_server_warn_downgrade(t->server); + + dns_transaction_cache_answer(t); + + if (t->answer_rcode == DNS_RCODE_SUCCESS) + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + else + dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); + + return; + +fail: + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); +} + +static int dns_transaction_has_positive_answer(DnsTransaction *t, DnsAnswerFlags *flags) { + int r; + + assert(t); + + /* Checks whether the answer is positive, i.e. either a direct + * answer to the question, or a CNAME/DNAME for it */ + + r = dns_answer_match_key(t->answer, t->key, flags); + if (r != 0) + return r; + + r = dns_answer_find_cname_or_dname(t->answer, t->key, NULL, flags); + if (r != 0) + return r; + + return false; +} + +static int dns_transaction_fix_rcode(DnsTransaction *t) { + int r; + + assert(t); + + /* Fix up the RCODE to SUCCESS if we get at least one matching RR in a response. Note that this contradicts the + * DNS RFCs a bit. Specifically, RFC 6604 Section 3 clarifies that the RCODE shall say something about a + * CNAME/DNAME chain element coming after the last chain element contained in the message, and not the first + * one included. However, it also indicates that not all DNS servers implement this correctly. Moreover, when + * using DNSSEC we usually only can prove the first element of a CNAME/DNAME chain anyway, hence let's settle + * on always processing the RCODE as referring to the immediate look-up we do, i.e. the first element of a + * CNAME/DNAME chain. This way, we uniformly handle CNAME/DNAME chains, regardless if the DNS server + * incorrectly implements RCODE, whether DNSSEC is in use, or whether the DNS server only supplied us with an + * incomplete CNAME/DNAME chain. + * + * Or in other words: if we get at least one positive reply in a message we patch NXDOMAIN to become SUCCESS, + * and then rely on the CNAME chasing logic to figure out that there's actually a CNAME error with a new + * lookup. */ + + if (t->answer_rcode != DNS_RCODE_NXDOMAIN) + return 0; + + r = dns_transaction_has_positive_answer(t, NULL); + if (r <= 0) + return r; + + t->answer_rcode = DNS_RCODE_SUCCESS; + return 0; +} + +void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p) { + usec_t ts; + int r; + + assert(t); + assert(p); + assert(t->scope); + assert(t->scope->manager); + + if (t->state != DNS_TRANSACTION_PENDING) + return; + + /* Note that this call might invalidate the query. Callers + * should hence not attempt to access the query or transaction + * after calling this function. */ + + log_debug("Processing incoming packet on transaction %" PRIu16".", t->id); + + switch (t->scope->protocol) { + + case DNS_PROTOCOL_LLMNR: + assert(t->scope->link); + + /* For LLMNR we will not accept any packets from other + * interfaces */ + + if (p->ifindex != t->scope->link->ifindex) + return; + + if (p->family != t->scope->family) + return; + + /* Tentative packets are not full responses but still + * useful for identifying uniqueness conflicts during + * probing. */ + if (DNS_PACKET_LLMNR_T(p)) { + dns_transaction_tentative(t, p); + return; + } + + 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) + return; + + if (p->family != t->scope->family) + return; + + break; + + case DNS_PROTOCOL_DNS: + /* Note that we do not need to verify the + * addresses/port numbers of incoming traffic, as we + * invoked connect() on our UDP socket in which case + * the kernel already does the needed verification for + * us. */ + break; + + default: + assert_not_reached("Invalid DNS protocol."); + } + + if (t->received != p) { + dns_packet_unref(t->received); + t->received = dns_packet_ref(p); + } + + t->answer_source = DNS_TRANSACTION_NETWORK; + + if (p->ipproto == IPPROTO_TCP) { + if (DNS_PACKET_TC(p)) { + /* Truncated via TCP? Somebody must be fucking with us */ + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + if (DNS_PACKET_ID(p) != t->id) { + /* Not the reply to our query? Somebody must be fucking with us */ + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + } + + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + switch (t->scope->protocol) { + + case DNS_PROTOCOL_DNS: + assert(t->server); + + 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); + return; + } else if (DNS_PACKET_TC(p)) + dns_server_packet_truncated(t->server, t->current_feature_level); + + break; + + case DNS_PROTOCOL_LLMNR: + case DNS_PROTOCOL_MDNS: + dns_scope_packet_received(t->scope, ts - t->start_usec); + break; + + default: + assert_not_reached("Invalid DNS protocol."); + } + + if (DNS_PACKET_TC(p)) { + + /* Truncated packets for mDNS are not allowed. Give up immediately. */ + if (t->scope->protocol == DNS_PROTOCOL_MDNS) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + log_debug("Reply truncated, retrying via TCP."); + + /* Response was truncated, let's try again with good old TCP */ + r = dns_transaction_open_tcp(t); + if (r == -ESRCH) { + /* No servers found? Damn! */ + dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); + return; + } + if (r == -EOPNOTSUPP) { + /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ + dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); + return; + } + if (r < 0) { + /* On LLMNR, if we cannot connect to the host, + * we immediately give up */ + if (t->scope->protocol != DNS_PROTOCOL_DNS) + goto fail; + + /* On DNS, couldn't send? Try immediately again, with a new server */ + dns_transaction_retry(t); + } + + return; + } + + /* After the superficial checks, actually parse the message. */ + r = dns_packet_extract(p); + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + /* Report that the OPT RR was missing */ + if (t->server) { + if (!p->opt) + dns_server_packet_bad_opt(t->server, t->current_feature_level); + + dns_server_packet_received(t->server, p->ipproto, t->current_feature_level, ts - t->start_usec, p->size); + } + + /* See if we know things we didn't know before that indicate we better restart the lookup immediately. */ + r = dns_transaction_maybe_restart(t); + if (r < 0) + goto fail; + if (r > 0) /* Transaction got restarted... */ + return; + + if (IN_SET(t->scope->protocol, DNS_PROTOCOL_DNS, DNS_PROTOCOL_LLMNR)) { + + /* Only consider responses with equivalent query section to the request */ + r = dns_packet_is_reply_for(p, t->key); + if (r < 0) + goto fail; + if (r == 0) { + dns_transaction_complete(t, DNS_TRANSACTION_INVALID_REPLY); + return; + } + + /* Install the answer as answer to the transaction */ + dns_answer_unref(t->answer); + t->answer = dns_answer_ref(p->answer); + t->answer_rcode = DNS_PACKET_RCODE(p); + t->answer_dnssec_result = _DNSSEC_RESULT_INVALID; + t->answer_authenticated = false; + + r = dns_transaction_fix_rcode(t); + if (r < 0) + goto fail; + + /* Block GC while starting requests for additional DNSSEC RRs */ + t->block_gc++; + r = dns_transaction_request_dnssec_keys(t); + t->block_gc--; + + /* Maybe the transaction is ready for GC'ing now? If so, free it and return. */ + if (!dns_transaction_gc(t)) + return; + + /* Requesting additional keys might have resulted in + * this transaction to fail, since the auxiliary + * request failed for some reason. If so, we are not + * in pending state anymore, and we should exit + * quickly. */ + if (t->state != DNS_TRANSACTION_PENDING) + return; + if (r < 0) + goto fail; + if (r > 0) { + /* There are DNSSEC transactions pending now. Update the state accordingly. */ + t->state = DNS_TRANSACTION_VALIDATING; + dns_transaction_close_connection(t); + dns_transaction_stop_timeout(t); + return; + } + } + + dns_transaction_process_dnssec(t); + return; + +fail: + t->answer_errno = -r; + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); +} + +static int on_dns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsTransaction *t = userdata; + int r; + + assert(t); + assert(t->scope); + + r = manager_recv(t->scope->manager, fd, DNS_PROTOCOL_DNS, &p); + if (ERRNO_IS_DISCONNECT(-r)) { + usec_t usec; + + /* UDP connection failure get reported via ICMP and then are possible delivered to us on the next + * recvmsg(). Treat this like a lost packet. */ + + log_debug_errno(r, "Connection failure for DNS UDP packet: %m"); + 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); + return 0; + } + if (r < 0) { + dns_transaction_complete(t, DNS_TRANSACTION_ERRNO); + t->answer_errno = -r; + return 0; + } + + r = dns_packet_validate_reply(p); + if (r < 0) { + log_debug_errno(r, "Received invalid DNS packet as response, ignoring: %m"); + return 0; + } + if (r == 0) { + log_debug("Received inappropriate DNS packet as response, ignoring."); + return 0; + } + + if (DNS_PACKET_ID(p) != t->id) { + log_debug("Received packet with incorrect transaction ID, ignoring."); + return 0; + } + + dns_transaction_process_reply(t, p); + return 0; +} + +static int dns_transaction_emit_udp(DnsTransaction *t) { + int r; + + assert(t); + + if (t->scope->protocol == DNS_PROTOCOL_DNS) { + + r = dns_transaction_pick_server(t); + if (r < 0) + return r; + + if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_UDP) + return -EAGAIN; + + if (!dns_server_dnssec_supported(t->server) && dns_type_is_dnssec(t->key->type)) + return -EOPNOTSUPP; + + if (r > 0 || t->dns_udp_fd < 0) { /* Server changed, or no connection yet. */ + int fd; + + dns_transaction_close_connection(t); + + fd = dns_scope_socket_udp(t->scope, t->server, 53); + if (fd < 0) + return fd; + + r = sd_event_add_io(t->scope->manager->event, &t->dns_udp_event_source, fd, EPOLLIN, on_dns_packet, t); + if (r < 0) { + safe_close(fd); + return r; + } + + (void) sd_event_source_set_description(t->dns_udp_event_source, "dns-transaction-udp"); + t->dns_udp_fd = fd; + } + + r = dns_server_adjust_opt(t->server, t->sent, t->current_feature_level); + if (r < 0) + return r; + } else + dns_transaction_close_connection(t); + + r = dns_scope_emit_udp(t->scope, t->dns_udp_fd, t->sent); + if (r < 0) + return r; + + dns_transaction_reset_answer(t); + + return 0; +} + +static int on_transaction_timeout(sd_event_source *s, usec_t usec, void *userdata) { + DnsTransaction *t = userdata; + + assert(s); + assert(t); + + if (!t->initial_jitter_scheduled || t->initial_jitter_elapsed) { + /* Timeout reached? Increase the timeout for the server used */ + switch (t->scope->protocol) { + + case DNS_PROTOCOL_DNS: + assert(t->server); + dns_server_packet_lost(t->server, t->stream ? IPPROTO_TCP : IPPROTO_UDP, t->current_feature_level, usec - t->start_usec); + break; + + case DNS_PROTOCOL_LLMNR: + case DNS_PROTOCOL_MDNS: + dns_scope_packet_lost(t->scope, usec - t->start_usec); + break; + + default: + assert_not_reached("Invalid DNS protocol."); + } + + if (t->initial_jitter_scheduled) + t->initial_jitter_elapsed = true; + } + + log_debug("Timeout reached on transaction %" PRIu16 ".", t->id); + + dns_transaction_retry(t); + return 0; +} + +static usec_t transaction_get_resend_timeout(DnsTransaction *t) { + assert(t); + assert(t->scope); + + switch (t->scope->protocol) { + + case DNS_PROTOCOL_DNS: + assert(t->server); + return t->server->resend_timeout; + + case DNS_PROTOCOL_MDNS: + assert(t->n_attempts > 0); + return (1 << (t->n_attempts - 1)) * USEC_PER_SEC; + + case DNS_PROTOCOL_LLMNR: + return t->scope->resend_timeout; + + default: + assert_not_reached("Invalid DNS protocol."); + } +} + +static int dns_transaction_prepare(DnsTransaction *t, usec_t ts) { + int r; + + assert(t); + + dns_transaction_stop_timeout(t); + + r = dns_scope_network_good(t->scope); + if (r < 0) + return r; + if (r == 0) { + dns_transaction_complete(t, DNS_TRANSACTION_NETWORK_DOWN); + return 0; + } + + if (t->n_attempts >= TRANSACTION_ATTEMPTS_MAX(t->scope->protocol)) { + dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); + return 0; + } + + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && t->tried_stream) { + /* If we already tried via a stream, then we don't + * retry on LLMNR. See RFC 4795, Section 2.7. */ + dns_transaction_complete(t, DNS_TRANSACTION_ATTEMPTS_MAX_REACHED); + return 0; + } + + t->n_attempts++; + t->start_usec = ts; + + dns_transaction_reset_answer(t); + dns_transaction_flush_dnssec_transactions(t); + + /* Check the trust anchor. Do so only on classic DNS, since DNSSEC does not apply otherwise. */ + if (t->scope->protocol == DNS_PROTOCOL_DNS) { + r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, t->key, &t->answer); + if (r < 0) + return r; + if (r > 0) { + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; + t->answer_authenticated = true; + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + return 0; + } + + if (dns_name_is_root(DNS_RESOURCE_KEY_NAME(t->key)) && + t->key->type == DNS_TYPE_DS) { + + /* Hmm, this is a request for the root DS? A + * DS RR doesn't exist in the root zone, and + * if our trust anchor didn't know it either, + * this means we cannot do any DNSSEC logic + * anymore. */ + + if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { + /* We are in downgrade mode. In this + * case, synthesize an unsigned empty + * response, so that the any lookup + * depending on this one can continue + * assuming there was no DS, and hence + * the root zone was unsigned. */ + + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_source = DNS_TRANSACTION_TRUST_ANCHOR; + t->answer_authenticated = false; + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + } else + /* If we are not in downgrade mode, + * then fail the lookup, because we + * cannot reasonably answer it. There + * might be DS RRs, but we don't know + * them, and the DNS server won't tell + * them to us (and even if it would, + * we couldn't validate it and trust + * it). */ + dns_transaction_complete(t, DNS_TRANSACTION_NO_TRUST_ANCHOR); + + return 0; + } + } + + /* Check the zone, but only if this transaction is not used + * 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); + if (r < 0) + return r; + if (r > 0) { + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_source = DNS_TRANSACTION_ZONE; + t->answer_authenticated = true; + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + return 0; + } + } + + /* Check the cache, but only if this transaction is not used + * for probing or verifying a zone item. */ + if (set_isempty(t->notify_zone_items)) { + + /* Before trying the cache, let's make sure we figured out a + * server to use. Should this cause a change of server this + * might flush the cache. */ + dns_scope_get_dns_server(t->scope); + + /* 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); + if (r < 0) + return r; + if (r > 0) { + t->answer_source = DNS_TRANSACTION_CACHE; + if (t->answer_rcode == DNS_RCODE_SUCCESS) + dns_transaction_complete(t, DNS_TRANSACTION_SUCCESS); + else + dns_transaction_complete(t, DNS_TRANSACTION_RCODE_FAILURE); + return 0; + } + } + + return 1; +} + +static int dns_transaction_make_packet_mdns(DnsTransaction *t) { + + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + bool add_known_answers = false; + DnsTransaction *other; + unsigned qdcount; + usec_t ts; + int r; + + assert(t); + assert(t->scope->protocol == DNS_PROTOCOL_MDNS); + + /* Discard any previously prepared packet, so we can start over and coalesce again */ + t->sent = dns_packet_unref(t->sent); + + r = dns_packet_new_query(&p, t->scope->protocol, 0, false); + if (r < 0) + return r; + + r = dns_packet_append_key(p, t->key, NULL); + if (r < 0) + return r; + + qdcount = 1; + + if (dns_key_is_shared(t->key)) + add_known_answers = true; + + /* + * For mDNS, we want to coalesce as many open queries in pending transactions into one single + * query packet on the wire as possible. To achieve that, we iterate through all pending transactions + * in our current scope, and see whether their timing contraints allow them to be sent. + */ + + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + LIST_FOREACH(transactions_by_scope, other, t->scope->transactions) { + + /* Skip ourselves */ + if (other == t) + continue; + + if (other->state != DNS_TRANSACTION_PENDING) + continue; + + if (other->next_attempt_after > ts) + continue; + + if (qdcount >= UINT16_MAX) + break; + + r = dns_packet_append_key(p, other->key, NULL); + + /* + * If we can't stuff more questions into the packet, just give up. + * One of the 'other' transactions will fire later and take care of the rest. + */ + if (r == -EMSGSIZE) + break; + + if (r < 0) + return r; + + r = dns_transaction_prepare(other, ts); + if (r <= 0) + continue; + + ts += transaction_get_resend_timeout(other); + + r = sd_event_add_time( + other->scope->manager->event, + &other->timeout_event_source, + clock_boottime_or_monotonic(), + ts, 0, + on_transaction_timeout, other); + if (r < 0) + return r; + + (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); + + other->state = DNS_TRANSACTION_PENDING; + other->next_attempt_after = ts; + + qdcount ++; + + if (dns_key_is_shared(other->key)) + add_known_answers = true; + } + + DNS_PACKET_HEADER(p)->qdcount = htobe16(qdcount); + + /* Append known answer section if we're asking for any shared record */ + if (add_known_answers) { + r = dns_cache_export_shared_to_packet(&t->scope->cache, p); + if (r < 0) + return r; + } + + t->sent = p; + p = NULL; + + return 0; +} + +static int dns_transaction_make_packet(DnsTransaction *t) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + int r; + + assert(t); + + if (t->scope->protocol == DNS_PROTOCOL_MDNS) + return dns_transaction_make_packet_mdns(t); + + if (t->sent) + return 0; + + r = dns_packet_new_query(&p, t->scope->protocol, 0, t->scope->dnssec_mode != DNSSEC_NO); + if (r < 0) + return r; + + r = dns_packet_append_key(p, t->key, NULL); + if (r < 0) + return r; + + DNS_PACKET_HEADER(p)->qdcount = htobe16(1); + DNS_PACKET_HEADER(p)->id = t->id; + + t->sent = p; + p = NULL; + + return 0; +} + +int dns_transaction_go(DnsTransaction *t) { + usec_t ts; + int r; + + assert(t); + + assert_se(sd_event_now(t->scope->manager->event, clock_boottime_or_monotonic(), &ts) >= 0); + + r = dns_transaction_prepare(t, ts); + if (r <= 0) + return r; + + log_debug("Excercising transaction %" PRIu16 " for <%s> on scope %s on %s/%s.", + t->id, + dns_transaction_key_string(t), + dns_protocol_to_string(t->scope->protocol), + t->scope->link ? t->scope->link->name : "*", + t->scope->family == AF_UNSPEC ? "*" : af_to_name(t->scope->family)); + + if (!t->initial_jitter_scheduled && + (t->scope->protocol == DNS_PROTOCOL_LLMNR || + t->scope->protocol == DNS_PROTOCOL_MDNS)) { + usec_t jitter, accuracy; + + /* RFC 4795 Section 2.7 suggests all queries should be + * delayed by a random time from 0 to JITTER_INTERVAL. */ + + t->initial_jitter_scheduled = true; + + random_bytes(&jitter, sizeof(jitter)); + + switch (t->scope->protocol) { + + case DNS_PROTOCOL_LLMNR: + jitter %= LLMNR_JITTER_INTERVAL_USEC; + accuracy = LLMNR_JITTER_INTERVAL_USEC; + break; + + case DNS_PROTOCOL_MDNS: + jitter %= MDNS_JITTER_RANGE_USEC; + jitter += MDNS_JITTER_MIN_USEC; + accuracy = MDNS_JITTER_RANGE_USEC; + break; + default: + assert_not_reached("bad protocol"); + } + + r = sd_event_add_time( + t->scope->manager->event, + &t->timeout_event_source, + clock_boottime_or_monotonic(), + ts + jitter, accuracy, + on_transaction_timeout, t); + if (r < 0) + return r; + + (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); + + t->n_attempts = 0; + t->next_attempt_after = ts; + t->state = DNS_TRANSACTION_PENDING; + + log_debug("Delaying %s transaction for " USEC_FMT "us.", dns_protocol_to_string(t->scope->protocol), jitter); + return 0; + } + + /* Otherwise, we need to ask the network */ + r = dns_transaction_make_packet(t); + if (r < 0) + return r; + + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && + (dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "in-addr.arpa") > 0 || + dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), "ip6.arpa") > 0)) { + + /* RFC 4795, Section 2.4. says reverse lookups shall + * always be made via TCP on LLMNR */ + r = dns_transaction_open_tcp(t); + } else { + /* Try via UDP, and if that fails due to large size or lack of + * support try via TCP */ + r = dns_transaction_emit_udp(t); + if (r == -EMSGSIZE) + log_debug("Sending query via TCP since it is too large."); + if (r == -EAGAIN) + log_debug("Sending query via TCP since server doesn't support UDP."); + if (r == -EMSGSIZE || r == -EAGAIN) + r = dns_transaction_open_tcp(t); + } + + if (r == -ESRCH) { + /* No servers to send this to? */ + dns_transaction_complete(t, DNS_TRANSACTION_NO_SERVERS); + return 0; + } + if (r == -EOPNOTSUPP) { + /* Tried to ask for DNSSEC RRs, on a server that doesn't do DNSSEC */ + dns_transaction_complete(t, DNS_TRANSACTION_RR_TYPE_UNSUPPORTED); + return 0; + } + if (t->scope->protocol == DNS_PROTOCOL_LLMNR && ERRNO_IS_DISCONNECT(-r)) { + /* On LLMNR, if we cannot connect to a host via TCP when doing reverse lookups. This means we cannot + * answer this request with this protocol. */ + dns_transaction_complete(t, DNS_TRANSACTION_NOT_FOUND); + return 0; + } + if (r < 0) { + if (t->scope->protocol != DNS_PROTOCOL_DNS) + return r; + + /* Couldn't send? Try immediately again, with a new server */ + dns_scope_next_dns_server(t->scope); + + return dns_transaction_go(t); + } + + ts += transaction_get_resend_timeout(t); + + r = sd_event_add_time( + t->scope->manager->event, + &t->timeout_event_source, + clock_boottime_or_monotonic(), + ts, 0, + on_transaction_timeout, t); + if (r < 0) + return r; + + (void) sd_event_source_set_description(t->timeout_event_source, "dns-transaction-timeout"); + + t->state = DNS_TRANSACTION_PENDING; + t->next_attempt_after = ts; + + return 1; +} + +static int dns_transaction_find_cyclic(DnsTransaction *t, DnsTransaction *aux) { + DnsTransaction *n; + Iterator i; + int r; + + assert(t); + assert(aux); + + /* Try to find cyclic dependencies between transaction objects */ + + if (t == aux) + return 1; + + SET_FOREACH(n, aux->dnssec_transactions, i) { + r = dns_transaction_find_cyclic(t, n); + if (r != 0) + return r; + } + + return 0; +} + +static int dns_transaction_add_dnssec_transaction(DnsTransaction *t, DnsResourceKey *key, DnsTransaction **ret) { + DnsTransaction *aux; + int r; + + assert(t); + assert(ret); + assert(key); + + aux = dns_scope_find_transaction(t->scope, key, true); + if (!aux) { + r = dns_transaction_new(&aux, t->scope, key); + if (r < 0) + return r; + } else { + if (set_contains(t->dnssec_transactions, aux)) { + *ret = aux; + return 0; + } + + r = dns_transaction_find_cyclic(t, aux); + if (r < 0) + return r; + if (r > 0) { + log_debug("Detected potential cyclic dependency, refusing to add transaction %" PRIu16 " (%s) as dependency for %" PRIu16 " (%s).", + aux->id, + strna(dns_transaction_key_string(aux)), + t->id, + strna(dns_transaction_key_string(t))); + return -ELOOP; + } + } + + r = set_ensure_allocated(&t->dnssec_transactions, NULL); + if (r < 0) + goto gc; + + r = set_ensure_allocated(&aux->notify_transactions, NULL); + if (r < 0) + goto gc; + + r = set_put(t->dnssec_transactions, aux); + if (r < 0) + goto gc; + + r = set_put(aux->notify_transactions, t); + if (r < 0) { + (void) set_remove(t->dnssec_transactions, aux); + goto gc; + } + + *ret = aux; + return 1; + +gc: + dns_transaction_gc(aux); + return r; +} + +static int dns_transaction_request_dnssec_rr(DnsTransaction *t, DnsResourceKey *key) { + _cleanup_(dns_answer_unrefp) DnsAnswer *a = NULL; + DnsTransaction *aux; + int r; + + assert(t); + assert(key); + + /* Try to get the data from the trust anchor */ + r = dns_trust_anchor_lookup_positive(&t->scope->manager->trust_anchor, key, &a); + if (r < 0) + return r; + if (r > 0) { + r = dns_answer_extend(&t->validated_keys, a); + if (r < 0) + return r; + + return 0; + } + + /* This didn't work, ask for it via the network/cache then. */ + r = dns_transaction_add_dnssec_transaction(t, key, &aux); + if (r == -ELOOP) /* This would result in a cyclic dependency */ + return 0; + if (r < 0) + return r; + + if (aux->state == DNS_TRANSACTION_NULL) { + r = dns_transaction_go(aux); + if (r < 0) + return r; + } + + return 1; +} + +static int dns_transaction_negative_trust_anchor_lookup(DnsTransaction *t, const char *name) { + int r; + + assert(t); + + /* Check whether the specified name is in the the NTA + * database, either in the global one, or the link-local + * one. */ + + r = dns_trust_anchor_lookup_negative(&t->scope->manager->trust_anchor, name); + if (r != 0) + return r; + + if (!t->scope->link) + return 0; + + return set_contains(t->scope->link->dnssec_negative_trust_anchors, name); +} + +static int dns_transaction_has_unsigned_negative_answer(DnsTransaction *t) { + int r; + + assert(t); + + /* Checks whether the answer is negative, and lacks NSEC/NSEC3 + * RRs to prove it */ + + r = dns_transaction_has_positive_answer(t, NULL); + if (r < 0) + return r; + if (r > 0) + return false; + + /* Is this key explicitly listed as a negative trust anchor? + * If so, it's nothing we need to care about */ + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + /* The answer does not contain any RRs that match to the + * question. If so, let's see if there are any NSEC/NSEC3 RRs + * included. If not, the answer is unsigned. */ + + r = dns_answer_contains_nsec_or_nsec3(t->answer); + if (r < 0) + return r; + if (r > 0) + return false; + + return true; +} + +static int dns_transaction_is_primary_response(DnsTransaction *t, DnsResourceRecord *rr) { + int r; + + assert(t); + assert(rr); + + /* Check if the specified RR is the "primary" response, + * i.e. either matches the question precisely or is a + * CNAME/DNAME for it. */ + + r = dns_resource_key_match_rr(t->key, rr, NULL); + if (r != 0) + return r; + + return dns_resource_key_match_cname_or_dname(t->key, rr->key, NULL); +} + +static bool dns_transaction_dnssec_supported(DnsTransaction *t) { + assert(t); + + /* Checks whether our transaction's DNS server is assumed to be compatible with DNSSEC. Returns false as soon + * as we changed our mind about a server, and now believe it is incompatible with DNSSEC. */ + + if (t->scope->protocol != DNS_PROTOCOL_DNS) + return false; + + /* If we have picked no server, then we are working from the cache or some other source, and DNSSEC might well + * be supported, hence return true. */ + if (!t->server) + return true; + + if (t->current_feature_level < DNS_SERVER_FEATURE_LEVEL_DO) + return false; + + return dns_server_dnssec_supported(t->server); +} + +static bool dns_transaction_dnssec_supported_full(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + + assert(t); + + /* Checks whether our transaction our any of the auxiliary transactions couldn't do DNSSEC. */ + + if (!dns_transaction_dnssec_supported(t)) + return false; + + SET_FOREACH(dt, t->dnssec_transactions, i) + if (!dns_transaction_dnssec_supported(dt)) + return false; + + return true; +} + +int dns_transaction_request_dnssec_keys(DnsTransaction *t) { + DnsResourceRecord *rr; + + int r; + + assert(t); + + /* + * Retrieve all auxiliary RRs for the answer we got, so that + * we can verify signatures or prove that RRs are rightfully + * unsigned. Specifically: + * + * - For RRSIG we get the matching DNSKEY + * - For DNSKEY we get the matching DS + * - For unsigned SOA/NS we get the matching DS + * - For unsigned CNAME/DNAME/DS we get the parent SOA RR + * - For other unsigned RRs we get the matching SOA RR + * - For SOA/NS/DS queries with no matching response RRs, and no NSEC/NSEC3, the parent's SOA RR + * - For other queries with no matching response RRs, and no NSEC/NSEC3, the SOA RR + */ + + if (t->scope->dnssec_mode == DNSSEC_NO) + return 0; + if (t->answer_source != DNS_TRANSACTION_NETWORK) + return 0; /* We only need to validate stuff from the network */ + if (!dns_transaction_dnssec_supported(t)) + return 0; /* If we can't do DNSSEC anyway there's no point in geting the auxiliary RRs */ + + DNS_ANSWER_FOREACH(rr, t->answer) { + + if (dns_type_is_pseudo(rr->key->type)) + continue; + + /* If this RR is in the negative trust anchor, we don't need to validate it. */ + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r > 0) + continue; + + switch (rr->key->type) { + + case DNS_TYPE_RRSIG: { + /* For each RRSIG we request the matching DNSKEY */ + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *dnskey = NULL; + + /* If this RRSIG is about a DNSKEY RR and the + * signer is the same as the owner, then we + * already have the DNSKEY, and we don't have + * to look for more. */ + if (rr->rrsig.type_covered == DNS_TYPE_DNSKEY) { + r = dns_name_equal(rr->rrsig.signer, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r > 0) + continue; + } + + /* If the signer is not a parent of our + * original query, then this is about an + * auxiliary RRset, but not anything we asked + * for. In this case we aren't interested, + * because we don't want to request additional + * RRs for stuff we didn't really ask for, and + * also to avoid request loops, where + * additional RRs from one transaction result + * in another transaction whose additonal RRs + * point back to the original transaction, and + * we deadlock. */ + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), rr->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + continue; + + dnskey = dns_resource_key_new(rr->key->class, DNS_TYPE_DNSKEY, rr->rrsig.signer); + if (!dnskey) + return -ENOMEM; + + log_debug("Requesting DNSKEY to validate transaction %" PRIu16" (%s, RRSIG with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), rr->rrsig.key_tag); + r = dns_transaction_request_dnssec_rr(t, dnskey); + if (r < 0) + return r; + break; + } + + case DNS_TYPE_DNSKEY: { + /* For each DNSKEY we request the matching DS */ + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; + + /* If the DNSKEY we are looking at is not for + * zone we are interested in, nor any of its + * parents, we aren't interested, and don't + * request it. After all, we don't want to end + * up in request loops, and want to keep + * additional traffic down. */ + + r = dns_name_endswith(DNS_RESOURCE_KEY_NAME(t->key), DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r == 0) + continue; + + ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key)); + if (!ds) + return -ENOMEM; + + log_debug("Requesting DS to validate transaction %" PRIu16" (%s, DNSKEY with key tag: %" PRIu16 ").", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dnssec_keytag(rr, false)); + r = dns_transaction_request_dnssec_rr(t, ds); + if (r < 0) + return r; + + break; + } + + case DNS_TYPE_SOA: + case DNS_TYPE_NS: { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *ds = NULL; + + /* For an unsigned SOA or NS, try to acquire + * the matching DS RR, as we are at a zone cut + * then, and whether a DS exists tells us + * whether the zone is signed. Do so only if + * this RR matches our original question, + * however. */ + + r = dns_resource_key_match_rr(t->key, rr, NULL); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_has_rrsig(t->answer, rr->key); + if (r < 0) + return r; + if (r > 0) + continue; + + ds = dns_resource_key_new(rr->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(rr->key)); + if (!ds) + return -ENOMEM; + + log_debug("Requesting DS to validate transaction %" PRIu16 " (%s, unsigned SOA/NS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key)); + r = dns_transaction_request_dnssec_rr(t, ds); + if (r < 0) + return r; + + break; + } + + case DNS_TYPE_DS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + const char *name; + + /* CNAMEs and DNAMEs cannot be located at a + * zone apex, hence ask for the parent SOA for + * unsigned CNAME/DNAME RRs, maybe that's the + * apex. But do all that only if this is + * actually a response to our original + * question. + * + * Similar for DS RRs, which are signed when + * the parent SOA is signed. */ + + r = dns_transaction_is_primary_response(t, rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_has_rrsig(t->answer, rr->key); + if (r < 0) + return r; + if (r > 0) + continue; + + r = dns_answer_has_dname_for_cname(t->answer, rr); + if (r < 0) + return r; + if (r > 0) + continue; + + name = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + continue; + + soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, name); + if (!soa) + return -ENOMEM; + + log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned CNAME/DNAME/DS RRset).", t->id, DNS_RESOURCE_KEY_NAME(rr->key)); + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + + break; + } + + default: { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + + /* For other unsigned RRsets (including + * NSEC/NSEC3!), look for proof the zone is + * unsigned, by requesting the SOA RR of the + * zone. However, do so only if they are + * directly relevant to our original + * question. */ + + r = dns_transaction_is_primary_response(t, rr); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_has_rrsig(t->answer, rr->key); + if (r < 0) + return r; + if (r > 0) + continue; + + soa = dns_resource_key_new(rr->key->class, DNS_TYPE_SOA, DNS_RESOURCE_KEY_NAME(rr->key)); + if (!soa) + return -ENOMEM; + + log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned non-SOA/NS RRset <%s>).", t->id, DNS_RESOURCE_KEY_NAME(rr->key), dns_resource_record_to_string(rr)); + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + break; + }} + } + + /* Above, we requested everything necessary to validate what + * we got. Now, let's request what we need to validate what we + * didn't get... */ + + r = dns_transaction_has_unsigned_negative_answer(t); + if (r < 0) + return r; + if (r > 0) { + const char *name; + + name = DNS_RESOURCE_KEY_NAME(t->key); + + /* If this was a SOA or NS request, then this + * indicates that we are not at a zone apex, hence ask + * the parent name instead. If this was a DS request, + * then it's signed when the parent zone is signed, + * hence ask the parent in that case, too. */ + + if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS, DNS_TYPE_DS)) { + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r > 0) + log_debug("Requesting parent SOA to validate transaction %" PRIu16 " (%s, unsigned empty SOA/NS/DS response).", t->id, DNS_RESOURCE_KEY_NAME(t->key)); + else + name = NULL; + } else + log_debug("Requesting SOA to validate transaction %" PRIu16 " (%s, unsigned empty non-SOA/NS/DS response).", t->id, DNS_RESOURCE_KEY_NAME(t->key)); + + if (name) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *soa = NULL; + + soa = dns_resource_key_new(t->key->class, DNS_TYPE_SOA, name); + if (!soa) + return -ENOMEM; + + r = dns_transaction_request_dnssec_rr(t, soa); + if (r < 0) + return r; + } + } + + return dns_transaction_dnssec_is_live(t); +} + +void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source) { + assert(t); + assert(source); + + /* Invoked whenever any of our auxiliary DNSSEC transactions completed its work. If the state is still PENDING, + we are still in the loop that adds further DNSSEC transactions, hence don't check if we are ready yet. If + the state is VALIDATING however, we should check if we are complete now. */ + + if (t->state == DNS_TRANSACTION_VALIDATING) + dns_transaction_process_dnssec(t); +} + +static int dns_transaction_validate_dnskey_by_ds(DnsTransaction *t) { + DnsResourceRecord *rr; + int ifindex, r; + + assert(t); + + /* Add all DNSKEY RRs from the answer that are validated by DS + * RRs from the list of validated keys to the list of + * validated keys. */ + + DNS_ANSWER_FOREACH_IFINDEX(rr, ifindex, t->answer) { + + r = dnssec_verify_dnskey_by_ds_search(rr, t->validated_keys); + if (r < 0) + return r; + if (r == 0) + continue; + + /* If so, the DNSKEY is validated too. */ + r = dns_answer_add_extend(&t->validated_keys, rr, ifindex, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord *rr) { + int r; + + assert(t); + assert(rr); + + /* Checks if the RR we are looking for must be signed with an + * RRSIG. This is used for positive responses. */ + + if (t->scope->dnssec_mode == DNSSEC_NO) + return false; + + if (dns_type_is_pseudo(rr->key->type)) + return -EINVAL; + + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + switch (rr->key->type) { + + case DNS_TYPE_RRSIG: + /* RRSIGs are the signatures themselves, they need no signing. */ + return false; + + case DNS_TYPE_SOA: + case DNS_TYPE_NS: { + DnsTransaction *dt; + Iterator i; + + /* For SOA or NS RRs we look for a matching DS transaction */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + if (dt->key->type != DNS_TYPE_DS) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We found a DS transactions for the SOA/NS + * RRs we are looking at. If it discovered signed DS + * RRs, then we need to be signed, too. */ + + if (!dt->answer_authenticated) + return false; + + return dns_answer_match_key(dt->answer, dt->key, NULL); + } + + /* We found nothing that proves this is safe to leave + * this unauthenticated, hence ask inist on + * authentication. */ + return true; + } + + case DNS_TYPE_DS: + case DNS_TYPE_CNAME: + case DNS_TYPE_DNAME: { + const char *parent = NULL; + DnsTransaction *dt; + Iterator i; + + /* + * CNAME/DNAME RRs cannot be located at a zone apex, hence look directly for the parent SOA. + * + * DS RRs are signed if the parent is signed, hence also look at the parent SOA + */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + if (dt->key->type != DNS_TYPE_SOA) + continue; + + if (!parent) { + parent = DNS_RESOURCE_KEY_NAME(rr->key); + r = dns_name_parent(&parent); + if (r < 0) + return r; + if (r == 0) { + if (rr->key->type == DNS_TYPE_DS) + return true; + + /* A CNAME/DNAME without a parent? That's sooo weird. */ + log_debug("Transaction %" PRIu16 " claims CNAME/DNAME at root. Refusing.", t->id); + return -EBADMSG; + } + } + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), parent); + if (r < 0) + return r; + if (r == 0) + continue; + + return t->answer_authenticated; + } + + return true; + } + + default: { + DnsTransaction *dt; + Iterator i; + + /* Any other kind of RR (including DNSKEY/NSEC/NSEC3). Let's see if our SOA lookup was authenticated */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + if (dt->key->type != DNS_TYPE_SOA) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We found the transaction that was supposed to find + * the SOA RR for us. It was successful, but found no + * RR for us. This means we are not at a zone cut. In + * this case, we require authentication if the SOA + * lookup was authenticated too. */ + return t->answer_authenticated; + } + + return true; + }} +} + +static int dns_transaction_in_private_tld(DnsTransaction *t, const DnsResourceKey *key) { + DnsTransaction *dt; + const char *tld; + Iterator i; + int r; + + /* If DNSSEC downgrade mode is on, checks whether the + * specified RR is one level below a TLD we have proven not to + * exist. In such a case we assume that this is a private + * domain, and permit it. + * + * This detects cases like the Fritz!Box router networks. Each + * Fritz!Box router serves a private "fritz.box" zone, in the + * non-existing TLD "box". Requests for the "fritz.box" domain + * are served by the router itself, while requests for the + * "box" domain will result in NXDOMAIN. + * + * Note that this logic is unable to detect cases where a + * router serves a private DNS zone directly under + * non-existing TLD. In such a case we cannot detect whether + * the TLD is supposed to exist or not, as all requests we + * make for it will be answered by the router's zone, and not + * by the root zone. */ + + assert(t); + + if (t->scope->dnssec_mode != DNSSEC_ALLOW_DOWNGRADE) + return false; /* In strict DNSSEC mode what doesn't exist, doesn't exist */ + + tld = DNS_RESOURCE_KEY_NAME(key); + r = dns_name_parent(&tld); + if (r < 0) + return r; + if (r == 0) + return false; /* Already the root domain */ + + if (!dns_name_is_single_label(tld)) + return false; + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != key->class) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), tld); + if (r < 0) + return r; + if (r == 0) + continue; + + /* We found an auxiliary lookup we did for the TLD. If + * that returned with NXDOMAIN, we know the TLD didn't + * exist, and hence this might be a private zone. */ + + return dt->answer_rcode == DNS_RCODE_NXDOMAIN; + } + + return false; +} + +static int dns_transaction_requires_nsec(DnsTransaction *t) { + DnsTransaction *dt; + const char *name; + Iterator i; + int r; + + assert(t); + + /* Checks if we need to insist on NSEC/NSEC3 RRs for proving + * this negative reply */ + + if (t->scope->dnssec_mode == DNSSEC_NO) + return false; + + if (dns_type_is_pseudo(t->key->type)) + return -EINVAL; + + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(t->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + r = dns_transaction_in_private_tld(t, t->key); + if (r < 0) + return r; + if (r > 0) { + /* The lookup is from a TLD that is proven not to + * exist, and we are in downgrade mode, hence ignore + * that fact that we didn't get any NSEC RRs.*/ + + log_info("Detected a negative query %s in a private DNS zone, permitting unsigned response.", dns_transaction_key_string(t)); + return false; + } + + name = DNS_RESOURCE_KEY_NAME(t->key); + + if (IN_SET(t->key->type, DNS_TYPE_SOA, DNS_TYPE_NS, DNS_TYPE_DS)) { + + /* We got a negative reply for this SOA/NS lookup? If + * so, then we are not at a zone apex, and thus should + * look at the result of the parent SOA lookup. + * + * We got a negative reply for this DS lookup? DS RRs + * are signed when their parent zone is signed, hence + * also check the parent SOA in this case. */ + + r = dns_name_parent(&name); + if (r < 0) + return r; + if (r == 0) + return true; + } + + /* For all other RRs we check the SOA on the same level to see + * if it's signed. */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != t->key->class) + continue; + if (dt->key->type != DNS_TYPE_SOA) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), name); + if (r < 0) + return r; + if (r == 0) + continue; + + return dt->answer_authenticated; + } + + /* If in doubt, require NSEC/NSEC3 */ + return true; +} + +static int dns_transaction_dnskey_authenticated(DnsTransaction *t, DnsResourceRecord *rr) { + DnsResourceRecord *rrsig; + bool found = false; + int r; + + /* Checks whether any of the DNSKEYs used for the RRSIGs for + * the specified RRset is authenticated (i.e. has a matching + * DS RR). */ + + r = dns_transaction_negative_trust_anchor_lookup(t, DNS_RESOURCE_KEY_NAME(rr->key)); + if (r < 0) + return r; + if (r > 0) + return false; + + DNS_ANSWER_FOREACH(rrsig, t->answer) { + DnsTransaction *dt; + Iterator i; + + r = dnssec_key_match_rrsig(rr->key, rrsig); + if (r < 0) + return r; + if (r == 0) + continue; + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (dt->key->class != rr->key->class) + continue; + + if (dt->key->type == DNS_TYPE_DNSKEY) { + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + continue; + + /* OK, we found an auxiliary DNSKEY + * lookup. If that lookup is + * authenticated, report this. */ + + if (dt->answer_authenticated) + return true; + + found = true; + + } else if (dt->key->type == DNS_TYPE_DS) { + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(dt->key), rrsig->rrsig.signer); + if (r < 0) + return r; + if (r == 0) + continue; + + /* OK, we found an auxiliary DS + * lookup. If that lookup is + * authenticated and non-zero, we + * won! */ + + if (!dt->answer_authenticated) + return false; + + return dns_answer_match_key(dt->answer, dt->key, NULL); + } + } + } + + return found ? false : -ENXIO; +} + +static int dns_transaction_known_signed(DnsTransaction *t, DnsResourceRecord *rr) { + assert(t); + assert(rr); + + /* We know that the root domain is signed, hence if it appears + * not to be signed, there's a problem with the DNS server */ + + return rr->key->class == DNS_CLASS_IN && + dns_name_is_root(DNS_RESOURCE_KEY_NAME(rr->key)); +} + +static int dns_transaction_check_revoked_trust_anchors(DnsTransaction *t) { + DnsResourceRecord *rr; + int r; + + assert(t); + + /* Maybe warn the user that we encountered a revoked DNSKEY + * for a key from our trust anchor. Note that we don't care + * whether the DNSKEY can be authenticated or not. It's + * sufficient if it is self-signed. */ + + DNS_ANSWER_FOREACH(rr, t->answer) { + r = dns_trust_anchor_check_revoked(&t->scope->manager->trust_anchor, rr, t->answer); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_transaction_invalidate_revoked_keys(DnsTransaction *t) { + bool changed; + int r; + + assert(t); + + /* Removes all DNSKEY/DS objects from t->validated_keys that + * our trust anchors database considers revoked. */ + + do { + DnsResourceRecord *rr; + + changed = false; + + DNS_ANSWER_FOREACH(rr, t->validated_keys) { + r = dns_trust_anchor_is_revoked(&t->scope->manager->trust_anchor, rr); + if (r < 0) + return r; + if (r > 0) { + r = dns_answer_remove_by_rr(&t->validated_keys, rr); + if (r < 0) + return r; + + assert(r > 0); + changed = true; + break; + } + } + } while (changed); + + return 0; +} + +static int dns_transaction_copy_validated(DnsTransaction *t) { + DnsTransaction *dt; + Iterator i; + int r; + + assert(t); + + /* Copy all validated RRs from the auxiliary DNSSEC transactions into our set of validated RRs */ + + SET_FOREACH(dt, t->dnssec_transactions, i) { + + if (DNS_TRANSACTION_IS_LIVE(dt->state)) + continue; + + if (!dt->answer_authenticated) + continue; + + r = dns_answer_extend(&t->validated_keys, dt->answer); + if (r < 0) + return r; + } + + return 0; +} + +int dns_transaction_validate_dnssec(DnsTransaction *t) { + _cleanup_(dns_answer_unrefp) DnsAnswer *validated = NULL; + enum { + PHASE_DNSKEY, /* Phase #1, only validate DNSKEYs */ + PHASE_NSEC, /* Phase #2, only validate NSEC+NSEC3 */ + PHASE_ALL, /* Phase #3, validate everything else */ + } phase; + DnsResourceRecord *rr; + DnsAnswerFlags flags; + int r; + + assert(t); + + /* We have now collected all DS and DNSKEY RRs in + * t->validated_keys, let's see which RRs we can now + * authenticate with that. */ + + if (t->scope->dnssec_mode == DNSSEC_NO) + return 0; + + /* Already validated */ + if (t->answer_dnssec_result != _DNSSEC_RESULT_INVALID) + return 0; + + /* Our own stuff needs no validation */ + if (IN_SET(t->answer_source, DNS_TRANSACTION_ZONE, DNS_TRANSACTION_TRUST_ANCHOR)) { + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_authenticated = true; + return 0; + } + + /* Cached stuff is not affected by validation. */ + if (t->answer_source != DNS_TRANSACTION_NETWORK) + return 0; + + 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); + return 0; + } + + log_debug("Validating response from transaction %" PRIu16 " (%s).", t->id, dns_transaction_key_string(t)); + + /* First, see if this response contains any revoked trust + * anchors we care about */ + r = dns_transaction_check_revoked_trust_anchors(t); + if (r < 0) + return r; + + /* Third, copy all RRs we acquired successfully from auxiliary RRs over. */ + r = dns_transaction_copy_validated(t); + if (r < 0) + return r; + + /* Second, see if there are DNSKEYs we already know a + * validated DS for. */ + r = dns_transaction_validate_dnskey_by_ds(t); + if (r < 0) + return r; + + /* Fourth, remove all DNSKEY and DS RRs again that our trust + * anchor says are revoked. After all we might have marked + * some keys revoked above, but they might still be lingering + * in our validated_keys list. */ + r = dns_transaction_invalidate_revoked_keys(t); + if (r < 0) + return r; + + phase = PHASE_DNSKEY; + for (;;) { + bool changed = false, have_nsec = false; + + DNS_ANSWER_FOREACH(rr, t->answer) { + DnsResourceRecord *rrsig = NULL; + DnssecResult result; + + switch (rr->key->type) { + + case DNS_TYPE_RRSIG: + continue; + + case DNS_TYPE_DNSKEY: + /* We validate DNSKEYs only in the DNSKEY and ALL phases */ + if (phase == PHASE_NSEC) + continue; + break; + + case DNS_TYPE_NSEC: + case DNS_TYPE_NSEC3: + have_nsec = true; + + /* We validate NSEC/NSEC3 only in the NSEC and ALL phases */ + if (phase == PHASE_DNSKEY) + continue; + + break; + + default: + /* We validate all other RRs only in the ALL phases */ + if (phase != PHASE_ALL) + continue; + + break; + } + + r = dnssec_verify_rrset_search(t->answer, rr->key, t->validated_keys, USEC_INFINITY, &result, &rrsig); + if (r < 0) + return r; + + log_debug("Looking at %s: %s", strna(dns_resource_record_to_string(rr)), dnssec_result_to_string(result)); + + if (result == DNSSEC_VALIDATED) { + + if (rr->key->type == DNS_TYPE_DNSKEY) { + /* If we just validated a + * DNSKEY RRset, then let's + * add these keys to the set + * of validated keys for this + * transaction. */ + + r = dns_answer_copy_by_key(&t->validated_keys, t->answer, rr->key, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + /* some of the DNSKEYs we just + * added might already have + * been revoked, remove them + * again in that case. */ + r = dns_transaction_invalidate_revoked_keys(t); + if (r < 0) + return r; + } + + /* Add the validated RRset to the new + * list of validated RRsets, and + * remove it from the unvalidated + * RRsets. We mark the RRset as + * authenticated and cacheable. */ + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_SECURE, rr->key); + + /* Exit the loop, we dropped something from the answer, start from the beginning */ + changed = true; + break; + } + + /* If we haven't read all DNSKEYs yet a negative result of the validation is irrelevant, as + * there might be more DNSKEYs coming. Similar, if we haven't read all NSEC/NSEC3 RRs yet, we + * cannot do positive wildcard proofs yet, as those require the NSEC/NSEC3 RRs. */ + if (phase != PHASE_ALL) + continue; + + if (result == DNSSEC_VALIDATED_WILDCARD) { + bool authenticated = false; + const char *source; + + /* This RRset validated, but as a wildcard. This means we need to prove via NSEC/NSEC3 + * that no matching non-wildcard RR exists.*/ + + /* First step, determine the source of synthesis */ + r = dns_resource_record_source(rrsig, &source); + if (r < 0) + return r; + + r = dnssec_test_positive_wildcard( + validated, + DNS_RESOURCE_KEY_NAME(rr->key), + source, + rrsig->rrsig.signer, + &authenticated); + + /* Unless the NSEC proof showed that the key really doesn't exist something is off. */ + if (r == 0) + result = DNSSEC_INVALID; + else { + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, authenticated ? (DNS_ANSWER_AUTHENTICATED|DNS_ANSWER_CACHEABLE) : 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, rr->key); + + /* Exit the loop, we dropped something from the answer, start from the beginning */ + changed = true; + break; + } + } + + if (result == DNSSEC_NO_SIGNATURE) { + r = dns_transaction_requires_rrsig(t, rr); + if (r < 0) + return r; + if (r == 0) { + /* Data does not require signing. In that case, just copy it over, + * but remember that this is by no means authenticated.*/ + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); + changed = true; + break; + } + + r = dns_transaction_known_signed(t, rr); + if (r < 0) + return r; + if (r > 0) { + /* This is an RR we know has to be signed. If it isn't this means + * the server is not attaching RRSIGs, hence complain. */ + + dns_server_packet_rrsig_missing(t->server, t->current_feature_level); + + if (t->scope->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE) { + + /* Downgrading is OK? If so, just consider the information unsigned */ + + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); + changed = true; + break; + } + + /* Otherwise, fail */ + t->answer_dnssec_result = DNSSEC_INCOMPATIBLE_SERVER; + return 0; + } + + r = dns_transaction_in_private_tld(t, rr->key); + if (r < 0) + return r; + if (r > 0) { + _cleanup_free_ char *s = NULL; + + /* The data is from a TLD that is proven not to exist, and we are in downgrade + * mode, hence ignore the fact that this was not signed. */ + + (void) dns_resource_key_to_string(rr->key, &s); + log_info("Detected RRset %s is in a private DNS zone, permitting unsigned RRs.", strna(s ? strstrip(s) : NULL)); + + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); + changed = true; + break; + } + } + + if (IN_SET(result, + DNSSEC_MISSING_KEY, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_UNSUPPORTED_ALGORITHM)) { + + r = dns_transaction_dnskey_authenticated(t, rr); + if (r < 0 && r != -ENXIO) + return r; + if (r == 0) { + /* The DNSKEY transaction was not authenticated, this means there's + * no DS for this, which means it's OK if no keys are found for this signature. */ + + r = dns_answer_move_by_key(&validated, &t->answer, rr->key, 0); + if (r < 0) + return r; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, rr->key); + changed = true; + break; + } + } + + r = dns_transaction_is_primary_response(t, rr); + if (r < 0) + return r; + if (r > 0) { + + /* Look for a matching DNAME for this CNAME */ + r = dns_answer_has_dname_for_cname(t->answer, rr); + if (r < 0) + return r; + if (r == 0) { + /* Also look among the stuff we already validated */ + r = dns_answer_has_dname_for_cname(validated, rr); + if (r < 0) + return r; + } + + if (r == 0) { + if (IN_SET(result, + DNSSEC_INVALID, + DNSSEC_SIGNATURE_EXPIRED, + DNSSEC_NO_SIGNATURE)) + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, rr->key); + else /* DNSSEC_MISSING_KEY or DNSSEC_UNSUPPORTED_ALGORITHM */ + manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, rr->key); + + /* This is a primary response to our question, and it failed validation. That's + * fatal. */ + t->answer_dnssec_result = result; + return 0; + } + + /* This is a primary response, but we do have a DNAME RR in the RR that can replay this + * CNAME, hence rely on that, and we can remove the CNAME in favour of it. */ + } + + /* This is just some auxiliary data. Just remove the RRset and continue. */ + r = dns_answer_remove_by_key(&t->answer, rr->key); + if (r < 0) + return r; + + /* Exit the loop, we dropped something from the answer, start from the beginning */ + changed = true; + break; + } + + /* Restart the inner loop as long as we managed to achieve something */ + if (changed) + continue; + + if (phase == PHASE_DNSKEY && have_nsec) { + /* OK, we processed all DNSKEYs, and there are NSEC/NSEC3 RRs, look at those now. */ + phase = PHASE_NSEC; + continue; + } + + if (phase != PHASE_ALL) { + /* OK, we processed all DNSKEYs and NSEC/NSEC3 RRs, look at all the rest now. Note that in this + * third phase we start to remove RRs we couldn't validate. */ + phase = PHASE_ALL; + continue; + } + + /* We're done */ + break; + } + + dns_answer_unref(t->answer); + t->answer = validated; + validated = NULL; + + /* At this point the answer only contains validated + * RRsets. Now, let's see if it actually answers the question + * we asked. If so, great! If it doesn't, then see if + * NSEC/NSEC3 can prove this. */ + r = dns_transaction_has_positive_answer(t, &flags); + if (r > 0) { + /* Yes, it answers the question! */ + + if (flags & DNS_ANSWER_AUTHENTICATED) { + /* The answer is fully authenticated, yay. */ + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_authenticated = true; + } else { + /* The answer is not fully authenticated. */ + t->answer_dnssec_result = DNSSEC_UNSIGNED; + t->answer_authenticated = false; + } + + } else if (r == 0) { + DnssecNsecResult nr; + bool authenticated = false; + + /* Bummer! Let's check NSEC/NSEC3 */ + r = dnssec_nsec_test(t->answer, t->key, &nr, &authenticated, &t->answer_nsec_ttl); + if (r < 0) + return r; + + switch (nr) { + + case DNSSEC_NSEC_NXDOMAIN: + /* NSEC proves the domain doesn't exist. Very good. */ + log_debug("Proved NXDOMAIN via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t)); + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_NXDOMAIN; + t->answer_authenticated = authenticated; + + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); + break; + + case DNSSEC_NSEC_NODATA: + /* NSEC proves that there's no data here, very good. */ + log_debug("Proved NODATA via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t)); + t->answer_dnssec_result = DNSSEC_VALIDATED; + t->answer_rcode = DNS_RCODE_SUCCESS; + t->answer_authenticated = authenticated; + + manager_dnssec_verdict(t->scope->manager, authenticated ? DNSSEC_SECURE : DNSSEC_INSECURE, t->key); + break; + + case DNSSEC_NSEC_OPTOUT: + /* NSEC3 says the data might not be signed */ + log_debug("Data is NSEC3 opt-out via NSEC/NSEC3 for transaction %u (%s)", t->id, dns_transaction_key_string(t)); + t->answer_dnssec_result = DNSSEC_UNSIGNED; + t->answer_authenticated = false; + + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); + break; + + case DNSSEC_NSEC_NO_RR: + /* No NSEC data? Bummer! */ + + r = dns_transaction_requires_nsec(t); + if (r < 0) + return r; + if (r > 0) { + t->answer_dnssec_result = DNSSEC_NO_SIGNATURE; + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); + } else { + t->answer_dnssec_result = DNSSEC_UNSIGNED; + t->answer_authenticated = false; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INSECURE, t->key); + } + + break; + + case DNSSEC_NSEC_UNSUPPORTED_ALGORITHM: + /* We don't know the NSEC3 algorithm used? */ + t->answer_dnssec_result = DNSSEC_UNSUPPORTED_ALGORITHM; + manager_dnssec_verdict(t->scope->manager, DNSSEC_INDETERMINATE, t->key); + break; + + case DNSSEC_NSEC_FOUND: + case DNSSEC_NSEC_CNAME: + /* NSEC says it needs to be there, but we couldn't find it? Bummer! */ + t->answer_dnssec_result = DNSSEC_NSEC_MISMATCH; + manager_dnssec_verdict(t->scope->manager, DNSSEC_BOGUS, t->key); + break; + + default: + assert_not_reached("Unexpected NSEC result."); + } + } + + return 1; +} + +const char *dns_transaction_key_string(DnsTransaction *t) { + assert(t); + + if (!t->key_string) { + if (dns_resource_key_to_string(t->key, &t->key_string) < 0) + return "n/a"; + } + + return strstrip(t->key_string); +} + +static const char* const dns_transaction_state_table[_DNS_TRANSACTION_STATE_MAX] = { + [DNS_TRANSACTION_NULL] = "null", + [DNS_TRANSACTION_PENDING] = "pending", + [DNS_TRANSACTION_VALIDATING] = "validating", + [DNS_TRANSACTION_RCODE_FAILURE] = "rcode-failure", + [DNS_TRANSACTION_SUCCESS] = "success", + [DNS_TRANSACTION_NO_SERVERS] = "no-servers", + [DNS_TRANSACTION_TIMEOUT] = "timeout", + [DNS_TRANSACTION_ATTEMPTS_MAX_REACHED] = "attempts-max-reached", + [DNS_TRANSACTION_INVALID_REPLY] = "invalid-reply", + [DNS_TRANSACTION_ERRNO] = "errno", + [DNS_TRANSACTION_ABORTED] = "aborted", + [DNS_TRANSACTION_DNSSEC_FAILED] = "dnssec-failed", + [DNS_TRANSACTION_NO_TRUST_ANCHOR] = "no-trust-anchor", + [DNS_TRANSACTION_RR_TYPE_UNSUPPORTED] = "rr-type-unsupported", + [DNS_TRANSACTION_NETWORK_DOWN] = "network-down", + [DNS_TRANSACTION_NOT_FOUND] = "not-found", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_transaction_state, DnsTransactionState); + +static const char* const dns_transaction_source_table[_DNS_TRANSACTION_SOURCE_MAX] = { + [DNS_TRANSACTION_NETWORK] = "network", + [DNS_TRANSACTION_CACHE] = "cache", + [DNS_TRANSACTION_ZONE] = "zone", + [DNS_TRANSACTION_TRUST_ANCHOR] = "trust-anchor", +}; +DEFINE_STRING_TABLE_LOOKUP(dns_transaction_source, DnsTransactionSource); diff --git a/src/resolve/src/resolved-dns-transaction.h b/src/resolve/src/resolved-dns-transaction.h new file mode 100644 index 0000000000..4617194711 --- /dev/null +++ b/src/resolve/src/resolved-dns-transaction.h @@ -0,0 +1,177 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +typedef struct DnsTransaction DnsTransaction; +typedef enum DnsTransactionState DnsTransactionState; +typedef enum DnsTransactionSource DnsTransactionSource; + +enum DnsTransactionState { + DNS_TRANSACTION_NULL, + DNS_TRANSACTION_PENDING, + DNS_TRANSACTION_VALIDATING, + DNS_TRANSACTION_RCODE_FAILURE, + DNS_TRANSACTION_SUCCESS, + DNS_TRANSACTION_NO_SERVERS, + DNS_TRANSACTION_TIMEOUT, + DNS_TRANSACTION_ATTEMPTS_MAX_REACHED, + DNS_TRANSACTION_INVALID_REPLY, + DNS_TRANSACTION_ERRNO, + DNS_TRANSACTION_ABORTED, + DNS_TRANSACTION_DNSSEC_FAILED, + DNS_TRANSACTION_NO_TRUST_ANCHOR, + DNS_TRANSACTION_RR_TYPE_UNSUPPORTED, + DNS_TRANSACTION_NETWORK_DOWN, + DNS_TRANSACTION_NOT_FOUND, /* like NXDOMAIN, but when LLMNR/TCP connections fail */ + _DNS_TRANSACTION_STATE_MAX, + _DNS_TRANSACTION_STATE_INVALID = -1 +}; + +#define DNS_TRANSACTION_IS_LIVE(state) IN_SET((state), DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING) + +enum DnsTransactionSource { + DNS_TRANSACTION_NETWORK, + DNS_TRANSACTION_CACHE, + DNS_TRANSACTION_ZONE, + DNS_TRANSACTION_TRUST_ANCHOR, + _DNS_TRANSACTION_SOURCE_MAX, + _DNS_TRANSACTION_SOURCE_INVALID = -1 +}; + +#include "resolved-dns-answer.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-question.h" +#include "resolved-dns-scope.h" + +struct DnsTransaction { + DnsScope *scope; + + DnsResourceKey *key; + char *key_string; + + DnsTransactionState state; + + uint16_t id; + + bool tried_stream:1; + + bool initial_jitter_scheduled:1; + bool initial_jitter_elapsed:1; + + DnsPacket *sent, *received; + + DnsAnswer *answer; + int answer_rcode; + DnssecResult answer_dnssec_result; + DnsTransactionSource answer_source; + uint32_t answer_nsec_ttl; + int answer_errno; /* if state is DNS_TRANSACTION_ERRNO */ + + /* Indicates whether the primary answer is authenticated, + * i.e. whether the RRs from answer which directly match the + * question are authenticated, or, if there are none, whether + * the NODATA or NXDOMAIN case is. It says nothing about + * additional RRs listed in the answer, however they have + * their own DNS_ANSWER_AUTHORIZED FLAGS. Note that this bit + * is defined different than the AD bit in DNS packets, as + * that covers more than just the actual primary answer. */ + bool answer_authenticated; + + /* Contains DNSKEY, DS, SOA RRs we already verified and need + * to authenticate this reply */ + DnsAnswer *validated_keys; + + usec_t start_usec; + usec_t next_attempt_after; + sd_event_source *timeout_event_source; + unsigned n_attempts; + + /* UDP connection logic, if we need it */ + int dns_udp_fd; + sd_event_source *dns_udp_event_source; + + /* TCP connection logic, if we need it */ + DnsStream *stream; + + /* The active server */ + DnsServer *server; + + /* The features of the DNS server at time of transaction start */ + DnsServerFeatureLevel current_feature_level; + + /* Query candidates this transaction is referenced by and that + * shall be notified about this specific transaction + * completing. */ + Set *notify_query_candidates; + + /* Zone items this transaction is referenced by and that shall + * be notified about completion. */ + Set *notify_zone_items; + + /* Other transactions that this transactions is referenced by + * and that shall be notified about completion. This is used + * when transactions want to validate their RRsets, but need + * another DNSKEY or DS RR to do so. */ + Set *notify_transactions; + + /* The opposite direction: the transactions this transaction + * created in order to request DNSKEY or DS RRs. */ + Set *dnssec_transactions; + + unsigned block_gc; + + LIST_FIELDS(DnsTransaction, transactions_by_scope); +}; + +int dns_transaction_new(DnsTransaction **ret, DnsScope *s, DnsResourceKey *key); +DnsTransaction* dns_transaction_free(DnsTransaction *t); + +bool dns_transaction_gc(DnsTransaction *t); +int dns_transaction_go(DnsTransaction *t); + +void dns_transaction_process_reply(DnsTransaction *t, DnsPacket *p); +void dns_transaction_complete(DnsTransaction *t, DnsTransactionState state); + +void dns_transaction_notify(DnsTransaction *t, DnsTransaction *source); +int dns_transaction_validate_dnssec(DnsTransaction *t); +int dns_transaction_request_dnssec_keys(DnsTransaction *t); + +const char *dns_transaction_key_string(DnsTransaction *t); + +const char* dns_transaction_state_to_string(DnsTransactionState p) _const_; +DnsTransactionState dns_transaction_state_from_string(const char *s) _pure_; + +const char* dns_transaction_source_to_string(DnsTransactionSource p) _const_; +DnsTransactionSource dns_transaction_source_from_string(const char *s) _pure_; + +/* LLMNR Jitter interval, see RFC 4795 Section 7 */ +#define LLMNR_JITTER_INTERVAL_USEC (100 * USEC_PER_MSEC) + +/* mDNS Jitter interval, see RFC 6762 Section 5.2 */ +#define MDNS_JITTER_MIN_USEC (20 * USEC_PER_MSEC) +#define MDNS_JITTER_RANGE_USEC (100 * USEC_PER_MSEC) + +/* Maximum attempts to send DNS requests, across all DNS servers */ +#define DNS_TRANSACTION_ATTEMPTS_MAX 16 + +/* Maximum attempts to send LLMNR requests, see RFC 4795 Section 2.7 */ +#define LLMNR_TRANSACTION_ATTEMPTS_MAX 3 + +#define TRANSACTION_ATTEMPTS_MAX(p) ((p) == DNS_PROTOCOL_LLMNR ? LLMNR_TRANSACTION_ATTEMPTS_MAX : DNS_TRANSACTION_ATTEMPTS_MAX) diff --git a/src/resolve/src/resolved-dns-trust-anchor.c b/src/resolve/src/resolved-dns-trust-anchor.c new file mode 100644 index 0000000000..a75337eb6a --- /dev/null +++ b/src/resolve/src/resolved-dns-trust-anchor.c @@ -0,0 +1,743 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include + +#include "alloc-util.h" +#include "conf-files.h" +#include "def.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "fileio.h" +#include "hexdecoct.h" +#include "parse-util.h" +#include "resolved-dns-trust-anchor.h" +#include "resolved-dns-dnssec.h" +#include "set.h" +#include "string-util.h" +#include "strv.h" + +static const char trust_anchor_dirs[] = CONF_PATHS_NULSTR("dnssec-trust-anchors.d"); + +/* The DS RR from https://data.iana.org/root-anchors/root-anchors.xml, retrieved December 2015 */ +static const uint8_t root_digest[] = + { 0x49, 0xAA, 0xC1, 0x1D, 0x7B, 0x6F, 0x64, 0x46, 0x70, 0x2E, 0x54, 0xA1, 0x60, 0x73, 0x71, 0x60, + 0x7A, 0x1A, 0x41, 0x85, 0x52, 0x00, 0xFD, 0x2C, 0xE1, 0xCD, 0xDE, 0x32, 0xF2, 0x4E, 0x8F, 0xB5 }; + +static bool dns_trust_anchor_knows_domain_positive(DnsTrustAnchor *d, const char *name) { + assert(d); + + /* Returns true if there's an entry for the specified domain + * name in our trust anchor */ + + return + hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DNSKEY, name)) || + hashmap_contains(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(DNS_CLASS_IN, DNS_TYPE_DS, name)); +} + +static int dns_trust_anchor_add_builtin_positive(DnsTrustAnchor *d) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + int r; + + assert(d); + + r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); + if (r < 0) + return r; + + /* Only add the built-in trust anchor if there's neither a DS + * nor a DNSKEY defined for the root domain. That way users + * have an easy way to override the root domain DS/DNSKEY + * data. */ + if (dns_trust_anchor_knows_domain_positive(d, ".")) + return 0; + + /* Add the RR from https://data.iana.org/root-anchors/root-anchors.xml */ + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, ""); + if (!rr) + return -ENOMEM; + + rr->ds.key_tag = 19036; + rr->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rr->ds.digest_type = DNSSEC_DIGEST_SHA256; + rr->ds.digest_size = sizeof(root_digest); + rr->ds.digest = memdup(root_digest, rr->ds.digest_size); + if (!rr->ds.digest) + return -ENOMEM; + + answer = dns_answer_new(1); + if (!answer) + return -ENOMEM; + + r = dns_answer_add(answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + r = hashmap_put(d->positive_by_key, rr->key, answer); + if (r < 0) + return r; + + answer = NULL; + return 0; +} + +static int dns_trust_anchor_add_builtin_negative(DnsTrustAnchor *d) { + + static const char private_domains[] = + /* RFC 6761 says that .test is a special domain for + * testing and not to be installed in the root zone */ + "test\0" + + /* RFC 6761 says that these reverse IP lookup ranges + * are for private addresses, and hence should not + * show up in the root zone */ + "10.in-addr.arpa\0" + "16.172.in-addr.arpa\0" + "17.172.in-addr.arpa\0" + "18.172.in-addr.arpa\0" + "19.172.in-addr.arpa\0" + "20.172.in-addr.arpa\0" + "21.172.in-addr.arpa\0" + "22.172.in-addr.arpa\0" + "23.172.in-addr.arpa\0" + "24.172.in-addr.arpa\0" + "25.172.in-addr.arpa\0" + "26.172.in-addr.arpa\0" + "27.172.in-addr.arpa\0" + "28.172.in-addr.arpa\0" + "29.172.in-addr.arpa\0" + "30.172.in-addr.arpa\0" + "31.172.in-addr.arpa\0" + "168.192.in-addr.arpa\0" + + /* RFC 6762 reserves the .local domain for Multicast + * DNS, it hence cannot appear in the root zone. (Note + * that we by default do not route .local traffic to + * DNS anyway, except when a configured search domain + * suggests so.) */ + "local\0" + + /* These two are well known, popular private zone + * TLDs, that are blocked from delegation, according + * to: + * http://icannwiki.com/Name_Collision#NGPC_Resolution + * + * There's also ongoing work on making this official + * in an RRC: + * https://www.ietf.org/archive/id/draft-chapin-additional-reserved-tlds-02.txt */ + "home\0" + "corp\0" + + /* The following four TLDs are suggested for private + * zones in RFC 6762, Appendix G, and are hence very + * unlikely to be made official TLDs any day soon */ + "lan\0" + "intranet\0" + "internal\0" + "private\0"; + + const char *name; + int r; + + assert(d); + + /* Only add the built-in trust anchor if there's no negative + * trust anchor defined at all. This enables easy overriding + * of negative trust anchors. */ + + if (set_size(d->negative_by_name) > 0) + return 0; + + r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); + if (r < 0) + return r; + + /* We add a couple of domains as default negative trust + * anchors, where it's very unlikely they will be installed in + * the root zone. If they exist they must be private, and thus + * unsigned. */ + + NULSTR_FOREACH(name, private_domains) { + + if (dns_trust_anchor_knows_domain_positive(d, name)) + continue; + + r = set_put_strdup(d->negative_by_name, name); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_trust_anchor_load_positive(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + _cleanup_free_ char *domain = NULL, *class = NULL, *type = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnsAnswer *old_answer = NULL; + const char *p = s; + int r; + + assert(d); + assert(line); + + r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); + if (r < 0) + return log_warning_errno(r, "Unable to parse domain in line %s:%u: %m", path, line); + + if (!dns_name_is_valid(domain)) { + log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); + return -EINVAL; + } + + r = extract_many_words(&p, NULL, 0, &class, &type, NULL); + if (r < 0) + return log_warning_errno(r, "Unable to parse class and type in line %s:%u: %m", path, line); + if (r != 2) { + log_warning("Missing class or type in line %s:%u", path, line); + return -EINVAL; + } + + if (!strcaseeq(class, "IN")) { + log_warning("RR class %s is not supported, ignoring line %s:%u.", class, path, line); + return -EINVAL; + } + + if (strcaseeq(type, "DS")) { + _cleanup_free_ char *key_tag = NULL, *algorithm = NULL, *digest_type = NULL, *digest = NULL; + _cleanup_free_ void *dd = NULL; + uint16_t kt; + int a, dt; + size_t l; + + r = extract_many_words(&p, NULL, 0, &key_tag, &algorithm, &digest_type, &digest, NULL); + if (r < 0) { + log_warning_errno(r, "Failed to parse DS parameters on line %s:%u: %m", path, line); + return -EINVAL; + } + if (r != 4) { + log_warning("Missing DS parameters on line %s:%u", path, line); + return -EINVAL; + } + + r = safe_atou16(key_tag, &kt); + if (r < 0) + return log_warning_errno(r, "Failed to parse DS key tag %s on line %s:%u: %m", key_tag, path, line); + + a = dnssec_algorithm_from_string(algorithm); + if (a < 0) { + log_warning("Failed to parse DS algorithm %s on line %s:%u", algorithm, path, line); + return -EINVAL; + } + + dt = dnssec_digest_from_string(digest_type); + if (dt < 0) { + log_warning("Failed to parse DS digest type %s on line %s:%u", digest_type, path, line); + return -EINVAL; + } + + r = unhexmem(digest, strlen(digest), &dd, &l); + if (r < 0) { + log_warning("Failed to parse DS digest %s on line %s:%u", digest, path, line); + return -EINVAL; + } + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, domain); + if (!rr) + return log_oom(); + + rr->ds.key_tag = kt; + rr->ds.algorithm = a; + rr->ds.digest_type = dt; + rr->ds.digest_size = l; + rr->ds.digest = dd; + dd = NULL; + + } else if (strcaseeq(type, "DNSKEY")) { + _cleanup_free_ char *flags = NULL, *protocol = NULL, *algorithm = NULL, *key = NULL; + _cleanup_free_ void *k = NULL; + uint16_t f; + size_t l; + int a; + + r = extract_many_words(&p, NULL, 0, &flags, &protocol, &algorithm, &key, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to parse DNSKEY parameters on line %s:%u: %m", path, line); + if (r != 4) { + log_warning("Missing DNSKEY parameters on line %s:%u", path, line); + return -EINVAL; + } + + if (!streq(protocol, "3")) { + log_warning("DNSKEY Protocol is not 3 on line %s:%u", path, line); + return -EINVAL; + } + + r = safe_atou16(flags, &f); + if (r < 0) + return log_warning_errno(r, "Failed to parse DNSKEY flags field %s on line %s:%u", flags, path, line); + if ((f & DNSKEY_FLAG_ZONE_KEY) == 0) { + log_warning("DNSKEY lacks zone key bit set on line %s:%u", path, line); + return -EINVAL; + } + if ((f & DNSKEY_FLAG_REVOKE)) { + log_warning("DNSKEY is already revoked on line %s:%u", path, line); + return -EINVAL; + } + + a = dnssec_algorithm_from_string(algorithm); + if (a < 0) { + log_warning("Failed to parse DNSKEY algorithm %s on line %s:%u", algorithm, path, line); + return -EINVAL; + } + + r = unbase64mem(key, strlen(key), &k, &l); + if (r < 0) + return log_warning_errno(r, "Failed to parse DNSKEY key data %s on line %s:%u", key, path, line); + + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, domain); + if (!rr) + return log_oom(); + + rr->dnskey.flags = f; + rr->dnskey.protocol = 3; + rr->dnskey.algorithm = a; + rr->dnskey.key_size = l; + rr->dnskey.key = k; + k = NULL; + + } else { + log_warning("RR type %s is not supported, ignoring line %s:%u.", type, path, line); + return -EINVAL; + } + + if (!isempty(p)) { + log_warning("Trailing garbage on line %s:%u, ignoring line.", path, line); + return -EINVAL; + } + + r = hashmap_ensure_allocated(&d->positive_by_key, &dns_resource_key_hash_ops); + if (r < 0) + return log_oom(); + + old_answer = hashmap_get(d->positive_by_key, rr->key); + answer = dns_answer_ref(old_answer); + + r = dns_answer_add_extend(&answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return log_error_errno(r, "Failed to add trust anchor RR: %m"); + + r = hashmap_replace(d->positive_by_key, rr->key, answer); + if (r < 0) + return log_error_errno(r, "Failed to add answer to trust anchor: %m"); + + old_answer = dns_answer_unref(old_answer); + answer = NULL; + + return 0; +} + +static int dns_trust_anchor_load_negative(DnsTrustAnchor *d, const char *path, unsigned line, const char *s) { + _cleanup_free_ char *domain = NULL; + const char *p = s; + int r; + + assert(d); + assert(line); + + r = extract_first_word(&p, &domain, NULL, EXTRACT_QUOTES); + if (r < 0) + return log_warning_errno(r, "Unable to parse line %s:%u: %m", path, line); + + if (!dns_name_is_valid(domain)) { + log_warning("Domain name %s is invalid, at line %s:%u, ignoring line.", domain, path, line); + return -EINVAL; + } + + if (!isempty(p)) { + log_warning("Trailing garbage at line %s:%u, ignoring line.", path, line); + return -EINVAL; + } + + r = set_ensure_allocated(&d->negative_by_name, &dns_name_hash_ops); + if (r < 0) + return log_oom(); + + r = set_put(d->negative_by_name, domain); + if (r < 0) + return log_oom(); + if (r > 0) + domain = NULL; + + return 0; +} + +static int dns_trust_anchor_load_files( + DnsTrustAnchor *d, + const char *suffix, + int (*loader)(DnsTrustAnchor *d, const char *path, unsigned n, const char *line)) { + + _cleanup_strv_free_ char **files = NULL; + char **f; + int r; + + assert(d); + assert(suffix); + assert(loader); + + r = conf_files_list_nulstr(&files, suffix, NULL, trust_anchor_dirs); + if (r < 0) + return log_error_errno(r, "Failed to enumerate %s trust anchor files: %m", suffix); + + STRV_FOREACH(f, files) { + _cleanup_fclose_ FILE *g = NULL; + char line[LINE_MAX]; + unsigned n = 0; + + g = fopen(*f, "r"); + if (!g) { + if (errno == ENOENT) + continue; + + log_warning_errno(errno, "Failed to open %s: %m", *f); + continue; + } + + FOREACH_LINE(line, g, log_warning_errno(errno, "Failed to read %s, ignoring: %m", *f)) { + char *l; + + n++; + + l = strstrip(line); + if (isempty(l)) + continue; + + if (*l == ';') + continue; + + (void) loader(d, *f, n, l); + } + } + + return 0; +} + +static int domain_name_cmp(const void *a, const void *b) { + char **x = (char**) a, **y = (char**) b; + + return dns_name_compare_func(*x, *y); +} + +static int dns_trust_anchor_dump(DnsTrustAnchor *d) { + DnsAnswer *a; + Iterator i; + + assert(d); + + if (hashmap_isempty(d->positive_by_key)) + log_info("No positive trust anchors defined."); + else { + log_info("Positive Trust Anchors:"); + HASHMAP_FOREACH(a, d->positive_by_key, i) { + DnsResourceRecord *rr; + + DNS_ANSWER_FOREACH(rr, a) + log_info("%s", dns_resource_record_to_string(rr)); + } + } + + if (set_isempty(d->negative_by_name)) + log_info("No negative trust anchors defined."); + else { + _cleanup_free_ char **l = NULL, *j = NULL; + + l = set_get_strv(d->negative_by_name); + if (!l) + return log_oom(); + + qsort_safe(l, set_size(d->negative_by_name), sizeof(char*), domain_name_cmp); + + j = strv_join(l, " "); + if (!j) + return log_oom(); + + log_info("Negative trust anchors: %s", j); + } + + return 0; +} + +int dns_trust_anchor_load(DnsTrustAnchor *d) { + int r; + + assert(d); + + /* If loading things from disk fails, we don't consider this fatal */ + (void) dns_trust_anchor_load_files(d, ".positive", dns_trust_anchor_load_positive); + (void) dns_trust_anchor_load_files(d, ".negative", dns_trust_anchor_load_negative); + + /* However, if the built-in DS fails, then we have a problem. */ + r = dns_trust_anchor_add_builtin_positive(d); + if (r < 0) + return log_error_errno(r, "Failed to add built-in positive trust anchor: %m"); + + r = dns_trust_anchor_add_builtin_negative(d); + if (r < 0) + return log_error_errno(r, "Failed to add built-in negative trust anchor: %m"); + + dns_trust_anchor_dump(d); + + return 0; +} + +void dns_trust_anchor_flush(DnsTrustAnchor *d) { + DnsAnswer *a; + DnsResourceRecord *rr; + + assert(d); + + while ((a = hashmap_steal_first(d->positive_by_key))) + dns_answer_unref(a); + d->positive_by_key = hashmap_free(d->positive_by_key); + + while ((rr = set_steal_first(d->revoked_by_rr))) + dns_resource_record_unref(rr); + d->revoked_by_rr = set_free(d->revoked_by_rr); + + d->negative_by_name = set_free_free(d->negative_by_name); +} + +int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey *key, DnsAnswer **ret) { + DnsAnswer *a; + + assert(d); + assert(key); + assert(ret); + + /* We only serve DS and DNSKEY RRs. */ + if (!IN_SET(key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) + return 0; + + a = hashmap_get(d->positive_by_key, key); + if (!a) + return 0; + + *ret = dns_answer_ref(a); + return 1; +} + +int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name) { + assert(d); + assert(name); + + return set_contains(d->negative_by_name, name); +} + +static int dns_trust_anchor_revoked_put(DnsTrustAnchor *d, DnsResourceRecord *rr) { + int r; + + assert(d); + + r = set_ensure_allocated(&d->revoked_by_rr, &dns_resource_record_hash_ops); + if (r < 0) + return r; + + r = set_put(d->revoked_by_rr, rr); + if (r < 0) + return r; + if (r > 0) + dns_resource_record_ref(rr); + + return r; +} + +static int dns_trust_anchor_remove_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { + _cleanup_(dns_answer_unrefp) DnsAnswer *new_answer = NULL; + DnsAnswer *old_answer; + int r; + + /* Remember that this is a revoked trust anchor RR */ + r = dns_trust_anchor_revoked_put(d, rr); + if (r < 0) + return r; + + /* Remove this from the positive trust anchor */ + old_answer = hashmap_get(d->positive_by_key, rr->key); + if (!old_answer) + return 0; + + new_answer = dns_answer_ref(old_answer); + + r = dns_answer_remove_by_rr(&new_answer, rr); + if (r <= 0) + return r; + + /* We found the key! Warn the user */ + log_struct(LOG_WARNING, + LOG_MESSAGE_ID(SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED), + LOG_MESSAGE("DNSSEC Trust anchor %s has been revoked. Please update the trust anchor, or upgrade your operating system."), strna(dns_resource_record_to_string(rr)), + "TRUST_ANCHOR=%s", dns_resource_record_to_string(rr), + NULL); + + if (dns_answer_size(new_answer) <= 0) { + assert_se(hashmap_remove(d->positive_by_key, rr->key) == old_answer); + dns_answer_unref(old_answer); + return 1; + } + + r = hashmap_replace(d->positive_by_key, new_answer->items[0].rr->key, new_answer); + if (r < 0) + return r; + + new_answer = NULL; + dns_answer_unref(old_answer); + return 1; +} + +static int dns_trust_anchor_check_revoked_one(DnsTrustAnchor *d, DnsResourceRecord *revoked_dnskey) { + DnsAnswer *a; + int r; + + assert(d); + assert(revoked_dnskey); + assert(revoked_dnskey->key->type == DNS_TYPE_DNSKEY); + assert(revoked_dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE); + + a = hashmap_get(d->positive_by_key, revoked_dnskey->key); + if (a) { + DnsResourceRecord *anchor; + + /* First, look for the precise DNSKEY in our trust anchor database */ + + DNS_ANSWER_FOREACH(anchor, a) { + + if (anchor->dnskey.protocol != revoked_dnskey->dnskey.protocol) + continue; + + if (anchor->dnskey.algorithm != revoked_dnskey->dnskey.algorithm) + continue; + + if (anchor->dnskey.key_size != revoked_dnskey->dnskey.key_size) + continue; + + /* Note that we allow the REVOKE bit to be + * different! It will be set in the revoked + * key, but unset in our version of it */ + if (((anchor->dnskey.flags ^ revoked_dnskey->dnskey.flags) | DNSKEY_FLAG_REVOKE) != DNSKEY_FLAG_REVOKE) + continue; + + if (memcmp(anchor->dnskey.key, revoked_dnskey->dnskey.key, anchor->dnskey.key_size) != 0) + continue; + + dns_trust_anchor_remove_revoked(d, anchor); + break; + } + } + + a = hashmap_get(d->positive_by_key, &DNS_RESOURCE_KEY_CONST(revoked_dnskey->key->class, DNS_TYPE_DS, DNS_RESOURCE_KEY_NAME(revoked_dnskey->key))); + if (a) { + DnsResourceRecord *anchor; + + /* Second, look for DS RRs matching this DNSKEY in our trust anchor database */ + + DNS_ANSWER_FOREACH(anchor, a) { + + /* We set mask_revoke to true here, since our + * DS fingerprint will be the one of the + * unrevoked DNSKEY, but the one we got passed + * here has the bit set. */ + r = dnssec_verify_dnskey_by_ds(revoked_dnskey, anchor, true); + if (r < 0) + return r; + if (r == 0) + continue; + + dns_trust_anchor_remove_revoked(d, anchor); + break; + } + } + + return 0; +} + +int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs) { + DnsResourceRecord *rrsig; + int r; + + assert(d); + assert(dnskey); + + /* Looks if "dnskey" is a self-signed RR that has been revoked + * and matches one of our trust anchor entries. If so, removes + * it from the trust anchor and returns > 0. */ + + if (dnskey->key->type != DNS_TYPE_DNSKEY) + return 0; + + /* Is this DNSKEY revoked? */ + if ((dnskey->dnskey.flags & DNSKEY_FLAG_REVOKE) == 0) + return 0; + + /* Could this be interesting to us at all? If not, + * there's no point in looking for and verifying a + * self-signed RRSIG. */ + if (!dns_trust_anchor_knows_domain_positive(d, DNS_RESOURCE_KEY_NAME(dnskey->key))) + return 0; + + /* Look for a self-signed RRSIG in the other rrs belonging to this DNSKEY */ + DNS_ANSWER_FOREACH(rrsig, rrs) { + DnssecResult result; + + if (rrsig->key->type != DNS_TYPE_RRSIG) + continue; + + r = dnssec_rrsig_match_dnskey(rrsig, dnskey, true); + if (r < 0) + return r; + if (r == 0) + continue; + + r = dnssec_verify_rrset(rrs, dnskey->key, rrsig, dnskey, USEC_INFINITY, &result); + if (r < 0) + return r; + if (result != DNSSEC_VALIDATED) + continue; + + /* Bingo! This is a revoked self-signed DNSKEY. Let's + * see if this precise one exists in our trust anchor + * database, too. */ + r = dns_trust_anchor_check_revoked_one(d, dnskey); + if (r < 0) + return r; + + return 1; + } + + return 0; +} + +int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr) { + assert(d); + + if (!IN_SET(rr->key->type, DNS_TYPE_DS, DNS_TYPE_DNSKEY)) + return 0; + + return set_contains(d->revoked_by_rr, rr); +} diff --git a/src/resolve/src/resolved-dns-trust-anchor.h b/src/resolve/src/resolved-dns-trust-anchor.h new file mode 100644 index 0000000000..635c75fde5 --- /dev/null +++ b/src/resolve/src/resolved-dns-trust-anchor.h @@ -0,0 +1,43 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +typedef struct DnsTrustAnchor DnsTrustAnchor; + +#include "hashmap.h" +#include "resolved-dns-answer.h" +#include "resolved-dns-rr.h" + +/* This contains a fixed database mapping domain names to DS or DNSKEY records. */ + +struct DnsTrustAnchor { + Hashmap *positive_by_key; + Set *negative_by_name; + Set *revoked_by_rr; +}; + +int dns_trust_anchor_load(DnsTrustAnchor *d); +void dns_trust_anchor_flush(DnsTrustAnchor *d); + +int dns_trust_anchor_lookup_positive(DnsTrustAnchor *d, const DnsResourceKey* key, DnsAnswer **answer); +int dns_trust_anchor_lookup_negative(DnsTrustAnchor *d, const char *name); + +int dns_trust_anchor_check_revoked(DnsTrustAnchor *d, DnsResourceRecord *dnskey, DnsAnswer *rrs); +int dns_trust_anchor_is_revoked(DnsTrustAnchor *d, DnsResourceRecord *rr); diff --git a/src/resolve/src/resolved-dns-zone.c b/src/resolve/src/resolved-dns-zone.c new file mode 100644 index 0000000000..f52383cfd1 --- /dev/null +++ b/src/resolve/src/resolved-dns-zone.c @@ -0,0 +1,656 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "alloc-util.h" +#include "dns-domain.h" +#include "list.h" +#include "resolved-dns-packet.h" +#include "resolved-dns-zone.h" +#include "string-util.h" + +/* Never allow more than 1K entries */ +#define ZONE_MAX 1024 + +void dns_zone_item_probe_stop(DnsZoneItem *i) { + DnsTransaction *t; + assert(i); + + if (!i->probe_transaction) + return; + + t = i->probe_transaction; + i->probe_transaction = NULL; + + set_remove(t->notify_zone_items, i); + dns_transaction_gc(t); +} + +static void dns_zone_item_free(DnsZoneItem *i) { + if (!i) + return; + + dns_zone_item_probe_stop(i); + dns_resource_record_unref(i->rr); + + free(i); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DnsZoneItem*, dns_zone_item_free); + +static void dns_zone_item_remove_and_free(DnsZone *z, DnsZoneItem *i) { + DnsZoneItem *first; + + assert(z); + + if (!i) + return; + + first = hashmap_get(z->by_key, i->rr->key); + LIST_REMOVE(by_key, first, i); + if (first) + assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); + else + hashmap_remove(z->by_key, i->rr->key); + + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key)); + LIST_REMOVE(by_name, first, i); + if (first) + assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0); + else + hashmap_remove(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key)); + + dns_zone_item_free(i); +} + +void dns_zone_flush(DnsZone *z) { + DnsZoneItem *i; + + assert(z); + + while ((i = hashmap_first(z->by_key))) + dns_zone_item_remove_and_free(z, i); + + assert(hashmap_size(z->by_key) == 0); + assert(hashmap_size(z->by_name) == 0); + + z->by_key = hashmap_free(z->by_key); + z->by_name = hashmap_free(z->by_name); +} + +static DnsZoneItem* dns_zone_get(DnsZone *z, DnsResourceRecord *rr) { + DnsZoneItem *i; + + assert(z); + assert(rr); + + LIST_FOREACH(by_key, i, hashmap_get(z->by_key, rr->key)) + if (dns_resource_record_equal(i->rr, rr) > 0) + return i; + + return NULL; +} + +void dns_zone_remove_rr(DnsZone *z, DnsResourceRecord *rr) { + DnsZoneItem *i; + + assert(z); + assert(rr); + + i = dns_zone_get(z, rr); + if (i) + dns_zone_item_remove_and_free(z, i); +} + +static int dns_zone_init(DnsZone *z) { + int r; + + assert(z); + + r = hashmap_ensure_allocated(&z->by_key, &dns_resource_key_hash_ops); + if (r < 0) + return r; + + r = hashmap_ensure_allocated(&z->by_name, &dns_name_hash_ops); + if (r < 0) + return r; + + return 0; +} + +static int dns_zone_link_item(DnsZone *z, DnsZoneItem *i) { + DnsZoneItem *first; + int r; + + first = hashmap_get(z->by_key, i->rr->key); + if (first) { + LIST_PREPEND(by_key, first, i); + assert_se(hashmap_replace(z->by_key, first->rr->key, first) >= 0); + } else { + r = hashmap_put(z->by_key, i->rr->key, i); + if (r < 0) + return r; + } + + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key)); + if (first) { + LIST_PREPEND(by_name, first, i); + assert_se(hashmap_replace(z->by_name, DNS_RESOURCE_KEY_NAME(first->rr->key), first) >= 0); + } else { + r = hashmap_put(z->by_name, DNS_RESOURCE_KEY_NAME(i->rr->key), i); + if (r < 0) + return r; + } + + return 0; +} + +static int dns_zone_item_probe_start(DnsZoneItem *i) { + DnsTransaction *t; + int r; + + assert(i); + + if (i->probe_transaction) + return 0; + + t = dns_scope_find_transaction(i->scope, &DNS_RESOURCE_KEY_CONST(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key)), false); + if (!t) { + _cleanup_(dns_resource_key_unrefp) DnsResourceKey *key = NULL; + + key = dns_resource_key_new(i->rr->key->class, DNS_TYPE_ANY, DNS_RESOURCE_KEY_NAME(i->rr->key)); + if (!key) + return -ENOMEM; + + r = dns_transaction_new(&t, i->scope, key); + if (r < 0) + return r; + } + + r = set_ensure_allocated(&t->notify_zone_items, NULL); + if (r < 0) + goto gc; + + r = set_put(t->notify_zone_items, i); + if (r < 0) + goto gc; + + i->probe_transaction = t; + + if (t->state == DNS_TRANSACTION_NULL) { + + i->block_ready++; + r = dns_transaction_go(t); + i->block_ready--; + + if (r < 0) { + dns_zone_item_probe_stop(i); + return r; + } + } + + dns_zone_item_notify(i); + return 0; + +gc: + dns_transaction_gc(t); + return r; +} + +int dns_zone_put(DnsZone *z, DnsScope *s, DnsResourceRecord *rr, bool probe) { + _cleanup_(dns_zone_item_freep) DnsZoneItem *i = NULL; + DnsZoneItem *existing; + int r; + + assert(z); + assert(s); + assert(rr); + + if (dns_class_is_pseudo(rr->key->class)) + return -EINVAL; + if (dns_type_is_pseudo(rr->key->type)) + return -EINVAL; + + existing = dns_zone_get(z, rr); + if (existing) + return 0; + + r = dns_zone_init(z); + if (r < 0) + return r; + + i = new0(DnsZoneItem, 1); + if (!i) + return -ENOMEM; + + i->scope = s; + i->rr = dns_resource_record_ref(rr); + i->probing_enabled = probe; + + r = dns_zone_link_item(z, i); + if (r < 0) + return r; + + if (probe) { + DnsZoneItem *first, *j; + bool established = false; + + /* Check if there's already an RR with the same name + * established. If so, it has been probed already, and + * we don't ned to probe again. */ + + LIST_FIND_HEAD(by_name, i, first); + LIST_FOREACH(by_name, j, first) { + if (i == j) + continue; + + if (j->state == DNS_ZONE_ITEM_ESTABLISHED) + established = true; + } + + if (established) + i->state = DNS_ZONE_ITEM_ESTABLISHED; + else { + i->state = DNS_ZONE_ITEM_PROBING; + + r = dns_zone_item_probe_start(i); + if (r < 0) { + dns_zone_item_remove_and_free(z, i); + i = NULL; + return r; + } + } + } else + i->state = DNS_ZONE_ITEM_ESTABLISHED; + + i = NULL; + return 0; +} + +int dns_zone_lookup(DnsZone *z, DnsResourceKey *key, 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; + + assert(z); + assert(key); + assert(ret_answer); + + /* First iteration, count what we have */ + + if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { + bool found = false, added = false; + int k; + + /* If this is a generic match, then we have to + * go through the list by the name and look + * for everything manually */ + + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); + LIST_FOREACH(by_name, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + found = true; + + k = dns_resource_key_match_rr(key, j->rr, NULL); + if (k < 0) + return k; + if (k > 0) { + n_answer++; + added = true; + } + + } + + if (found && !added) + need_soa = true; + + } else { + bool found = false; + + /* If this is a specific match, then look for + * the right key immediately */ + + first = hashmap_get(z->by_key, key); + LIST_FOREACH(by_key, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + found = true; + n_answer++; + } + + if (!found) { + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); + LIST_FOREACH(by_name, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + need_soa = true; + break; + } + } + } + + if (n_answer <= 0 && !need_soa) + goto return_empty; + + if (n_answer > 0) { + answer = dns_answer_new(n_answer); + if (!answer) + return -ENOMEM; + } + + if (need_soa) { + soa = dns_answer_new(1); + if (!soa) + return -ENOMEM; + } + + /* Second iteration, actually add the RRs to the answers */ + if (key->type == DNS_TYPE_ANY || key->class == DNS_CLASS_ANY) { + bool found = false, added = false; + int k; + + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); + LIST_FOREACH(by_name, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + found = true; + + if (j->state != DNS_ZONE_ITEM_PROBING) + tentative = false; + + k = dns_resource_key_match_rr(key, j->rr, NULL); + if (k < 0) + return k; + if (k > 0) { + r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + + added = true; + } + } + + if (found && !added) { + r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL); + if (r < 0) + return r; + } + } else { + bool found = false; + + first = hashmap_get(z->by_key, key); + LIST_FOREACH(by_key, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + found = true; + + if (j->state != DNS_ZONE_ITEM_PROBING) + tentative = false; + + r = dns_answer_add(answer, j->rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + if (!found) { + bool add_soa = false; + + first = hashmap_get(z->by_name, DNS_RESOURCE_KEY_NAME(key)); + LIST_FOREACH(by_name, j, first) { + if (!IN_SET(j->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) + continue; + + if (j->state != DNS_ZONE_ITEM_PROBING) + tentative = false; + + add_soa = true; + } + + if (add_soa) { + r = dns_answer_add_soa(soa, DNS_RESOURCE_KEY_NAME(key), LLMNR_DEFAULT_TTL); + if (r < 0) + return r; + } + } + } + + /* If the caller sets ret_tentative to NULL, then use this as + * indication to not return tentative entries */ + + if (!ret_tentative && tentative) + goto return_empty; + + *ret_answer = answer; + answer = NULL; + + if (ret_soa) { + *ret_soa = soa; + soa = NULL; + } + + if (ret_tentative) + *ret_tentative = tentative; + + return 1; + +return_empty: + *ret_answer = NULL; + + if (ret_soa) + *ret_soa = NULL; + + if (ret_tentative) + *ret_tentative = false; + + return 0; +} + +void dns_zone_item_conflict(DnsZoneItem *i) { + assert(i); + + if (!IN_SET(i->state, DNS_ZONE_ITEM_PROBING, DNS_ZONE_ITEM_VERIFYING, DNS_ZONE_ITEM_ESTABLISHED)) + return; + + log_info("Detected conflict on %s", strna(dns_resource_record_to_string(i->rr))); + + dns_zone_item_probe_stop(i); + + /* Withdraw the conflict item */ + i->state = DNS_ZONE_ITEM_WITHDRAWN; + + /* Maybe change the hostname */ + if (manager_is_own_hostname(i->scope->manager, DNS_RESOURCE_KEY_NAME(i->rr->key)) > 0) + manager_next_hostname(i->scope->manager); +} + +void dns_zone_item_notify(DnsZoneItem *i) { + assert(i); + assert(i->probe_transaction); + + if (i->block_ready > 0) + return; + + if (IN_SET(i->probe_transaction->state, DNS_TRANSACTION_NULL, DNS_TRANSACTION_PENDING, DNS_TRANSACTION_VALIDATING)) + return; + + if (i->probe_transaction->state == DNS_TRANSACTION_SUCCESS) { + bool we_lost = false; + + /* The probe got a successful reply. If we so far + * weren't established we just give up. If we already + * were established, and the peer has the + * lexicographically larger IP address we continue + * and defend it. */ + + if (!IN_SET(i->state, DNS_ZONE_ITEM_ESTABLISHED, DNS_ZONE_ITEM_VERIFYING)) { + log_debug("Got a successful probe for not yet established RR, we lost."); + we_lost = true; + } else { + assert(i->probe_transaction->received); + we_lost = memcmp(&i->probe_transaction->received->sender, &i->probe_transaction->received->destination, FAMILY_ADDRESS_SIZE(i->probe_transaction->received->family)) < 0; + if (we_lost) + log_debug("Got a successful probe reply for an established RR, and we have a lexicographically larger IP address and thus lost."); + } + + if (we_lost) { + dns_zone_item_conflict(i); + return; + } + + log_debug("Got a successful probe reply, but peer has lexicographically lower IP address and thus lost."); + } + + log_debug("Record %s successfully probed.", strna(dns_resource_record_to_string(i->rr))); + + dns_zone_item_probe_stop(i); + i->state = DNS_ZONE_ITEM_ESTABLISHED; +} + +static int dns_zone_item_verify(DnsZoneItem *i) { + int r; + + assert(i); + + if (i->state != DNS_ZONE_ITEM_ESTABLISHED) + return 0; + + log_debug("Verifying RR %s", strna(dns_resource_record_to_string(i->rr))); + + i->state = DNS_ZONE_ITEM_VERIFYING; + r = dns_zone_item_probe_start(i); + if (r < 0) { + log_error_errno(r, "Failed to start probing for verifying RR: %m"); + i->state = DNS_ZONE_ITEM_ESTABLISHED; + return r; + } + + return 0; +} + +int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr) { + DnsZoneItem *i, *first; + int c = 0; + + assert(zone); + assert(rr); + + /* This checks whether a response RR we received from somebody + * else is one that we actually thought was uniquely ours. If + * so, we'll verify our RRs. */ + + /* No conflict if we don't have the name at all. */ + first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(rr->key)); + if (!first) + return 0; + + /* No conflict if we have the exact same RR */ + if (dns_zone_get(zone, rr)) + return 0; + + /* OK, somebody else has RRs for the same name. Yuck! Let's + * start probing again */ + + LIST_FOREACH(by_name, i, first) { + if (dns_resource_record_equal(i->rr, rr)) + continue; + + dns_zone_item_verify(i); + c++; + } + + return c; +} + +int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key) { + DnsZoneItem *i, *first; + int c = 0; + + assert(zone); + + /* Somebody else notified us about a possible conflict. Let's + * verify if that's true. */ + + first = hashmap_get(zone->by_name, DNS_RESOURCE_KEY_NAME(key)); + if (!first) + return 0; + + LIST_FOREACH(by_name, i, first) { + dns_zone_item_verify(i); + c++; + } + + return c; +} + +void dns_zone_verify_all(DnsZone *zone) { + DnsZoneItem *i; + Iterator iterator; + + assert(zone); + + HASHMAP_FOREACH(i, zone->by_key, iterator) { + DnsZoneItem *j; + + LIST_FOREACH(by_key, j, i) + dns_zone_item_verify(j); + } +} + +void dns_zone_dump(DnsZone *zone, FILE *f) { + Iterator iterator; + DnsZoneItem *i; + + if (!zone) + return; + + if (!f) + f = stdout; + + HASHMAP_FOREACH(i, zone->by_key, iterator) { + DnsZoneItem *j; + + LIST_FOREACH(by_key, j, i) { + const char *t; + + t = dns_resource_record_to_string(j->rr); + if (!t) { + log_oom(); + continue; + } + + fputc('\t', f); + fputs(t, f); + fputc('\n', f); + } + } +} + +bool dns_zone_is_empty(DnsZone *zone) { + if (!zone) + return true; + + return hashmap_isempty(zone->by_key); +} diff --git a/src/resolve/src/resolved-dns-zone.h b/src/resolve/src/resolved-dns-zone.h new file mode 100644 index 0000000000..408833c359 --- /dev/null +++ b/src/resolve/src/resolved-dns-zone.h @@ -0,0 +1,81 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "hashmap.h" + +typedef struct DnsZone { + Hashmap *by_key; + Hashmap *by_name; +} DnsZone; + +typedef struct DnsZoneItem DnsZoneItem; +typedef enum DnsZoneItemState DnsZoneItemState; + +#include "resolved-dns-answer.h" +#include "resolved-dns-question.h" +#include "resolved-dns-rr.h" +#include "resolved-dns-transaction.h" + +/* RFC 4795 Section 2.8. suggests a TTL of 30s by default */ +#define LLMNR_DEFAULT_TTL (30) + +enum DnsZoneItemState { + DNS_ZONE_ITEM_PROBING, + DNS_ZONE_ITEM_ESTABLISHED, + DNS_ZONE_ITEM_VERIFYING, + DNS_ZONE_ITEM_WITHDRAWN, +}; + +struct DnsZoneItem { + DnsScope *scope; + DnsResourceRecord *rr; + + DnsZoneItemState state; + + unsigned block_ready; + + bool probing_enabled; + + LIST_FIELDS(DnsZoneItem, by_key); + LIST_FIELDS(DnsZoneItem, by_name); + + DnsTransaction *probe_transaction; +}; + +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); + +void dns_zone_item_conflict(DnsZoneItem *i); +void dns_zone_item_notify(DnsZoneItem *i); + +int dns_zone_check_conflicts(DnsZone *zone, DnsResourceRecord *rr); +int dns_zone_verify_conflicts(DnsZone *zone, DnsResourceKey *key); + +void dns_zone_verify_all(DnsZone *zone); + +void dns_zone_item_probe_stop(DnsZoneItem *i); + +void dns_zone_dump(DnsZone *zone, FILE *f); +bool dns_zone_is_empty(DnsZone *zone); diff --git a/src/resolve/src/resolved-etc-hosts.c b/src/resolve/src/resolved-etc-hosts.c new file mode 100644 index 0000000000..ee82c96822 --- /dev/null +++ b/src/resolve/src/resolved-etc-hosts.c @@ -0,0 +1,448 @@ +/*** + 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 . +***/ + +#include "fd-util.h" +#include "fileio.h" +#include "hostname-util.h" +#include "resolved-etc-hosts.h" +#include "resolved-dns-synthesize.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" + +/* Recheck /etc/hosts at most once every 2s */ +#define ETC_HOSTS_RECHECK_USEC (2*USEC_PER_SEC) + +typedef struct EtcHostsItem { + int family; + union in_addr_union address; + + char **names; +} EtcHostsItem; + +typedef struct EtcHostsItemByName { + char *name; + + EtcHostsItem **items; + size_t n_items, n_allocated; +} EtcHostsItemByName; + +void manager_etc_hosts_flush(Manager *m) { + EtcHostsItem *item; + EtcHostsItemByName *bn; + + while ((item = set_steal_first(m->etc_hosts_by_address))) { + strv_free(item->names); + free(item); + } + + while ((bn = hashmap_steal_first(m->etc_hosts_by_name))) { + free(bn->name); + free(bn->items); + free(bn); + } + + m->etc_hosts_by_address = set_free(m->etc_hosts_by_address); + m->etc_hosts_by_name = hashmap_free(m->etc_hosts_by_name); + + m->etc_hosts_mtime = USEC_INFINITY; +} + +static void etc_hosts_item_hash_func(const void *p, struct siphash *state) { + const EtcHostsItem *item = p; + + siphash24_compress(&item->family, sizeof(item->family), state); + + if (item->family == AF_INET) + siphash24_compress(&item->address.in, sizeof(item->address.in), state); + else if (item->family == AF_INET6) + siphash24_compress(&item->address.in6, sizeof(item->address.in6), state); +} + +static int etc_hosts_item_compare_func(const void *a, const void *b) { + const EtcHostsItem *x = a, *y = b; + + if (x->family != y->family) + return x->family - y->family; + + if (x->family == AF_INET) + return memcmp(&x->address.in.s_addr, &y->address.in.s_addr, sizeof(struct in_addr)); + + if (x->family == AF_INET6) + return memcmp(&x->address.in6.s6_addr, &y->address.in6.s6_addr, sizeof(struct in6_addr)); + + return trivial_compare_func(a, b); +} + +static const struct hash_ops etc_hosts_item_ops = { + .hash = etc_hosts_item_hash_func, + .compare = etc_hosts_item_compare_func, +}; + +static int add_item(Manager *m, int family, const union in_addr_union *address, char **names) { + + EtcHostsItem key = { + .family = family, + .address = *address, + }; + EtcHostsItem *item; + char **n; + int r; + + assert(m); + assert(address); + + r = in_addr_is_null(family, address); + if (r < 0) + return r; + if (r > 0) + /* This is an 0.0.0.0 or :: item, which we assume means that we shall map the specified hostname to + * nothing. */ + item = NULL; + else { + /* If this is a normal address, then, simply add entry mapping it to the specified names */ + + item = set_get(m->etc_hosts_by_address, &key); + if (item) { + r = strv_extend_strv(&item->names, names, true); + if (r < 0) + return log_oom(); + } else { + + r = set_ensure_allocated(&m->etc_hosts_by_address, &etc_hosts_item_ops); + if (r < 0) + return log_oom(); + + item = new0(EtcHostsItem, 1); + if (!item) + return log_oom(); + + item->family = family; + item->address = *address; + item->names = names; + + r = set_put(m->etc_hosts_by_address, item); + if (r < 0) { + free(item); + return log_oom(); + } + } + } + + STRV_FOREACH(n, names) { + EtcHostsItemByName *bn; + + bn = hashmap_get(m->etc_hosts_by_name, *n); + if (!bn) { + r = hashmap_ensure_allocated(&m->etc_hosts_by_name, &dns_name_hash_ops); + if (r < 0) + return log_oom(); + + bn = new0(EtcHostsItemByName, 1); + if (!bn) + return log_oom(); + + bn->name = strdup(*n); + if (!bn->name) { + free(bn); + return log_oom(); + } + + r = hashmap_put(m->etc_hosts_by_name, bn->name, bn); + if (r < 0) { + free(bn->name); + free(bn); + return log_oom(); + } + } + + if (item) { + if (!GREEDY_REALLOC(bn->items, bn->n_allocated, bn->n_items+1)) + return log_oom(); + + bn->items[bn->n_items++] = item; + } + } + + return 0; +} + +static int parse_line(Manager *m, unsigned nr, const char *line) { + _cleanup_free_ char *address = NULL; + _cleanup_strv_free_ char **names = NULL; + union in_addr_union in; + bool suppressed = false; + int family, r; + + assert(m); + assert(line); + + r = extract_first_word(&line, &address, NULL, EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Couldn't extract address, in line /etc/hosts:%u.", nr); + if (r == 0) { + log_error("Premature end of line, in line /etc/hosts:%u.", nr); + return -EINVAL; + } + + r = in_addr_from_string_auto(address, &family, &in); + if (r < 0) + return log_error_errno(r, "Address '%s' is invalid, in line /etc/hosts:%u.", address, nr); + + for (;;) { + _cleanup_free_ char *name = NULL; + + r = extract_first_word(&line, &name, NULL, EXTRACT_RELAX); + if (r < 0) + return log_error_errno(r, "Couldn't extract host name, in line /etc/hosts:%u.", nr); + if (r == 0) + break; + + r = dns_name_is_valid(name); + if (r <= 0) + return log_error_errno(r, "Hostname %s is not valid, ignoring, in line /etc/hosts:%u.", name, nr); + + if (is_localhost(name)) { + /* Suppress the "localhost" line that is often seen */ + suppressed = true; + continue; + } + + r = strv_push(&names, name); + if (r < 0) + return log_oom(); + + name = NULL; + } + + if (strv_isempty(names)) { + + if (suppressed) + return 0; + + log_error("Line is missing any host names, in line /etc/hosts:%u.", nr); + return -EINVAL; + } + + /* Takes possession of the names strv */ + r = add_item(m, family, &in, names); + if (r < 0) + return r; + + names = NULL; + return r; +} + +int manager_etc_hosts_read(Manager *m) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + struct stat st; + usec_t ts; + unsigned nr = 0; + int r; + + assert_se(sd_event_now(m->event, clock_boottime_or_monotonic(), &ts) >= 0); + + /* See if we checked /etc/hosts recently already */ + if (m->etc_hosts_last != USEC_INFINITY && m->etc_hosts_last + ETC_HOSTS_RECHECK_USEC > ts) + return 0; + + m->etc_hosts_last = ts; + + if (m->etc_hosts_mtime != USEC_INFINITY) { + if (stat("/etc/hosts", &st) < 0) { + if (errno == ENOENT) { + r = 0; + goto clear; + } + + return log_error_errno(errno, "Failed to stat /etc/hosts: %m"); + } + + /* Did the mtime change? If not, there's no point in re-reading the file. */ + if (timespec_load(&st.st_mtim) == m->etc_hosts_mtime) + return 0; + } + + f = fopen("/etc/hosts", "re"); + if (!f) { + if (errno == ENOENT) { + r = 0; + goto clear; + } + + return log_error_errno(errno, "Failed to open /etc/hosts: %m"); + } + + /* Take the timestamp at the beginning of processing, so that any changes made later are read on the next + * invocation */ + r = fstat(fileno(f), &st); + if (r < 0) + return log_error_errno(errno, "Failed to fstat() /etc/hosts: %m"); + + manager_etc_hosts_flush(m); + + FOREACH_LINE(line, f, return log_error_errno(errno, "Failed to read /etc/hosts: %m")) { + char *l; + + nr ++; + + l = strstrip(line); + if (isempty(l)) + continue; + if (l[0] == '#') + continue; + + r = parse_line(m, nr, l); + if (r == -ENOMEM) /* On OOM we abandon the half-built-up structure. All other errors we ignore and proceed */ + goto clear; + } + + m->etc_hosts_mtime = timespec_load(&st.st_mtim); + m->etc_hosts_last = ts; + + return 1; + +clear: + manager_etc_hosts_flush(m); + return r; +} + +int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer) { + bool found_a = false, found_aaaa = false; + EtcHostsItemByName *bn; + EtcHostsItem k = {}; + DnsResourceKey *t; + const char *name; + unsigned i; + int r; + + assert(m); + assert(q); + assert(answer); + + r = manager_etc_hosts_read(m); + if (r < 0) + return r; + + name = dns_question_first_name(q); + if (!name) + return 0; + + r = dns_name_address(name, &k.family, &k.address); + if (r > 0) { + EtcHostsItem *item; + DnsResourceKey *found_ptr = NULL; + + item = set_get(m->etc_hosts_by_address, &k); + if (!item) + return 0; + + /* We have an address in /etc/hosts that matches the queried name. Let's return successful. Actual data + * we'll only return if the request was for PTR. */ + + DNS_QUESTION_FOREACH(t, q) { + if (!IN_SET(t->type, DNS_TYPE_PTR, DNS_TYPE_ANY)) + continue; + if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(t), name); + if (r < 0) + return r; + if (r > 0) { + found_ptr = t; + break; + } + } + + if (found_ptr) { + char **n; + + r = dns_answer_reserve(answer, strv_length(item->names)); + if (r < 0) + return r; + + STRV_FOREACH(n, item->names) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + rr = dns_resource_record_new(found_ptr); + if (!rr) + return -ENOMEM; + + rr->ptr.name = strdup(*n); + if (!rr->ptr.name) + return -ENOMEM; + + r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + } + + return 1; + } + + bn = hashmap_get(m->etc_hosts_by_name, name); + if (!bn) + return 0; + + r = dns_answer_reserve(answer, bn->n_items); + if (r < 0) + return r; + + DNS_QUESTION_FOREACH(t, q) { + if (!IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_AAAA, DNS_TYPE_ANY)) + continue; + if (!IN_SET(t->class, DNS_CLASS_IN, DNS_CLASS_ANY)) + continue; + + r = dns_name_equal(DNS_RESOURCE_KEY_NAME(t), name); + if (r < 0) + return r; + if (r == 0) + continue; + + if (IN_SET(t->type, DNS_TYPE_A, DNS_TYPE_ANY)) + found_a = true; + if (IN_SET(t->type, DNS_TYPE_AAAA, DNS_TYPE_ANY)) + found_aaaa = true; + + if (found_a && found_aaaa) + break; + } + + for (i = 0; i < bn->n_items; i++) { + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + + if ((found_a && bn->items[i]->family != AF_INET) && + (found_aaaa && bn->items[i]->family != AF_INET6)) + continue; + + r = dns_resource_record_new_address(&rr, bn->items[i]->family, &bn->items[i]->address, bn->name); + if (r < 0) + return r; + + r = dns_answer_add(*answer, rr, 0, DNS_ANSWER_AUTHENTICATED); + if (r < 0) + return r; + } + + return 1; +} diff --git a/src/resolve/src/resolved-etc-hosts.h b/src/resolve/src/resolved-etc-hosts.h new file mode 100644 index 0000000000..9d5a175f18 --- /dev/null +++ b/src/resolve/src/resolved-etc-hosts.h @@ -0,0 +1,28 @@ +#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 . +***/ + +#include "resolved-manager.h" +#include "resolved-dns-question.h" +#include "resolved-dns-answer.h" + +void manager_etc_hosts_flush(Manager *m); +int manager_etc_hosts_read(Manager *m); +int manager_etc_hosts_lookup(Manager *m, DnsQuestion* q, DnsAnswer **answer); diff --git a/src/resolve/src/resolved-gperf.gperf b/src/resolve/src/resolved-gperf.gperf new file mode 100644 index 0000000000..82f26215df --- /dev/null +++ b/src/resolve/src/resolved-gperf.gperf @@ -0,0 +1,21 @@ +%{ +#include +#include "conf-parser.h" +#include "resolved-conf.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name resolved_gperf_hash +%define lookup-function-name resolved_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Resolve.DNS, config_parse_dns_servers, DNS_SERVER_SYSTEM, 0 +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) diff --git a/src/resolve/src/resolved-link-bus.c b/src/resolve/src/resolved-link-bus.c new file mode 100644 index 0000000000..df7516f4f4 --- /dev/null +++ b/src/resolve/src/resolved-link-bus.c @@ -0,0 +1,550 @@ +/*** + 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 . +***/ + +#include "alloc-util.h" +#include "bus-util.h" +#include "parse-util.h" +#include "resolve-util.h" +#include "resolved-bus.h" +#include "resolved-link-bus.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_dns( + 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; + DnsServer *s; + int r; + + assert(reply); + assert(l); + + r = sd_bus_message_open_container(reply, 'a', "(iay)"); + if (r < 0) + return r; + + LIST_FOREACH(servers, s, l->dns_servers) { + r = bus_dns_server_append(reply, s, false); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_domains( + 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; + DnsSearchDomain *d; + int r; + + assert(reply); + assert(l); + + r = sd_bus_message_open_container(reply, 'a', "(sb)"); + if (r < 0) + return r; + + LIST_FOREACH(domains, d, l->search_domains) { + r = sd_bus_message_append(reply, "(sb)", d->name, d->route_only); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_scopes_mask( + 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; + uint64_t mask; + + assert(reply); + assert(l); + + mask = (l->unicast_scope ? SD_RESOLVED_DNS : 0) | + (l->llmnr_ipv4_scope ? SD_RESOLVED_LLMNR_IPV4 : 0) | + (l->llmnr_ipv6_scope ? SD_RESOLVED_LLMNR_IPV6 : 0) | + (l->mdns_ipv4_scope ? SD_RESOLVED_MDNS_IPV4 : 0) | + (l->mdns_ipv6_scope ? SD_RESOLVED_MDNS_IPV6 : 0); + + return sd_bus_message_append(reply, "t", mask); +} + +static int 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) { + + Link *l = userdata; + const char *name; + Iterator i; + int r; + + assert(reply); + assert(l); + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + return r; + + SET_FOREACH(name, l->dnssec_negative_trust_anchors, i) { + r = sd_bus_message_append(reply, "s", name); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int property_get_dnssec_supported( + 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, "b", link_dnssec_supported(l)); +} + +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; + Link *l = userdata; + unsigned i; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_enter_container(message, 'a', "(iay)"); + if (r < 0) + return r; + + for (;;) { + int family; + size_t sz; + const void *d; + + assert_cc(sizeof(int) == sizeof(int32_t)); + + r = sd_bus_message_enter_container(message, 'r', "iay"); + if (r < 0) + return r; + if (r == 0) + break; + + r = sd_bus_message_read(message, "i", &family); + if (r < 0) + return r; + + if (!IN_SET(family, AF_INET, AF_INET6)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown address family %i", family); + + r = sd_bus_message_read_array(message, 'y', &d, &sz); + if (r < 0) + return r; + if (sz != FAMILY_ADDRESS_SIZE(family)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid address size"); + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (!GREEDY_REALLOC(dns, allocated, n+1)) + return -ENOMEM; + + dns[n].family = family; + memcpy(&dns[n].address, d, sz); + n++; + } + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + dns_server_mark_all(l->dns_servers); + + for (i = 0; i < n; i++) { + DnsServer *s; + + s = dns_server_find(l->dns_servers, dns[i].family, &dns[i].address); + 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); + if (r < 0) + goto clear; + } + + } + + dns_server_unlink_marked(l->dns_servers); + link_allocate_scopes(l); + + return sd_bus_reply_method_return(message, NULL); + +clear: + dns_server_unlink_all(l->dns_servers); + return r; +} + +int bus_link_method_set_search_domains(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_enter_container(message, 'a', "(sb)"); + if (r < 0) + return r; + + for (;;) { + const char *name; + int route_only; + + r = sd_bus_message_read(message, "(sb)", &name, &route_only); + if (r < 0) + return r; + if (r == 0) + break; + + r = dns_name_is_valid(name); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid search domain %s", name); + if (!route_only && dns_name_is_root(name)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Root domain is not suitable as search domain"); + } + + dns_search_domain_mark_all(l->search_domains); + + r = sd_bus_message_rewind(message, false); + if (r < 0) + return r; + + for (;;) { + DnsSearchDomain *d; + const char *name; + int route_only; + + r = sd_bus_message_read(message, "(sb)", &name, &route_only); + if (r < 0) + goto clear; + if (r == 0) + break; + + r = dns_search_domain_find(l->search_domains, name, &d); + if (r < 0) + goto clear; + + if (r > 0) + dns_search_domain_move_back_and_unmark(d); + else { + r = dns_search_domain_new(l->manager, &d, DNS_SEARCH_DOMAIN_LINK, l, name); + if (r < 0) + goto clear; + } + + d->route_only = route_only; + } + + r = sd_bus_message_exit_container(message); + if (r < 0) + goto clear; + + dns_search_domain_unlink_marked(l->search_domains); + return sd_bus_reply_method_return(message, NULL); + +clear: + dns_search_domain_unlink_all(l->search_domains); + return r; +} + +int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + ResolveSupport mode; + const char *llmnr; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_read(message, "s", &llmnr); + if (r < 0) + return r; + + if (isempty(llmnr)) + mode = RESOLVE_SUPPORT_YES; + else { + mode = resolve_support_from_string(llmnr); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid LLMNR setting: %s", llmnr); + } + + l->llmnr_support = mode; + link_allocate_scopes(l); + link_add_rrs(l, false); + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + ResolveSupport mode; + const char *mdns; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_read(message, "s", &mdns); + if (r < 0) + return r; + + if (isempty(mdns)) + mode = RESOLVE_SUPPORT_NO; + else { + mode = resolve_support_from_string(mdns); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid MulticastDNS setting: %s", mdns); + } + + l->mdns_support = mode; + link_allocate_scopes(l); + link_add_rrs(l, false); + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Link *l = userdata; + const char *dnssec; + DnssecMode mode; + int r; + + assert(message); + assert(l); + + r = sd_bus_message_read(message, "s", &dnssec); + if (r < 0) + return r; + + if (isempty(dnssec)) + mode = _DNSSEC_MODE_INVALID; + else { + mode = dnssec_mode_from_string(dnssec); + if (mode < 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid DNSSEC setting: %s", dnssec); + } + + link_set_dnssec_mode(l, mode); + + return sd_bus_reply_method_return(message, NULL); +} + +int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error) { + _cleanup_set_free_free_ Set *ns = NULL; + _cleanup_free_ char **ntas = NULL; + Link *l = userdata; + int r; + char **i; + + assert(message); + assert(l); + + r = sd_bus_message_read_strv(message, &ntas); + if (r < 0) + return r; + + STRV_FOREACH(i, ntas) { + r = dns_name_is_valid(*i); + if (r < 0) + return r; + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid negative trust anchor domain: %s", *i); + } + + ns = set_new(&dns_name_hash_ops); + if (!ns) + return -ENOMEM; + + STRV_FOREACH(i, ntas) { + r = set_put_strdup(ns, *i); + if (r < 0) + return r; + } + + set_free_free(l->dnssec_negative_trust_anchors); + l->dnssec_negative_trust_anchors = ns; + ns = NULL; + + 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; + + assert(message); + assert(l); + + link_flush_settings(l); + link_allocate_scopes(l); + link_add_rrs(l, false); + + return sd_bus_reply_method_return(message, NULL); +} + +const sd_bus_vtable link_vtable[] = { + SD_BUS_VTABLE_START(0), + + SD_BUS_PROPERTY("ScopesMask", "t", property_get_scopes_mask, 0, 0), + SD_BUS_PROPERTY("DNS", "a(iay)", property_get_dns, 0, 0), + 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("DNSSECNegativeTrustAnchors", "as", property_get_ntas, 0, 0), + SD_BUS_PROPERTY("DNSSECSupport", "b", property_get_dnssec_supported, 0, 0), + + SD_BUS_METHOD("SetDNS", "a(iay)", NULL, bus_link_method_set_dns_servers, 0), + SD_BUS_METHOD("SetDomains", "a(sb)", NULL, bus_link_method_set_search_domains, 0), + SD_BUS_METHOD("SetLLMNR", "s", NULL, bus_link_method_set_llmnr, 0), + SD_BUS_METHOD("SetMulticastDNS", "s", NULL, bus_link_method_set_mdns, 0), + SD_BUS_METHOD("SetDNSSEC", "s", NULL, bus_link_method_set_dnssec, 0), + SD_BUS_METHOD("SetDNSSECNegativeTrustAnchors", "as", NULL, bus_link_method_set_dnssec_negative_trust_anchors, 0), + SD_BUS_METHOD("Revert", NULL, NULL, bus_link_method_revert, 0), + + SD_BUS_VTABLE_END +}; + +int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error) { + _cleanup_free_ char *e = NULL; + Manager *m = userdata; + int ifindex; + Link *link; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(found); + assert(m); + + r = sd_bus_path_decode(path, "/org/freedesktop/resolve1/link", &e); + if (r <= 0) + return 0; + + r = parse_ifindex(e, &ifindex); + if (r < 0) + return 0; + + link = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!link) + return 0; + + *found = link; + return 1; +} + +char *link_bus_path(Link *link) { + _cleanup_free_ char *ifindex = NULL; + char *p; + int r; + + assert(link); + + if (asprintf(&ifindex, "%i", link->ifindex) < 0) + return NULL; + + r = sd_bus_path_encode("/org/freedesktop/resolve1/link", ifindex, &p); + if (r < 0) + return NULL; + + return p; +} + +int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error) { + _cleanup_strv_free_ char **l = NULL; + Manager *m = userdata; + Link *link; + Iterator i; + unsigned c = 0; + + assert(bus); + assert(path); + assert(m); + assert(nodes); + + l = new0(char*, hashmap_size(m->links) + 1); + if (!l) + return -ENOMEM; + + HASHMAP_FOREACH(link, m->links, i) { + char *p; + + p = link_bus_path(link); + if (!p) + return -ENOMEM; + + l[c++] = p; + } + + l[c] = NULL; + *nodes = l; + l = NULL; + + return 1; +} diff --git a/src/resolve/src/resolved-link-bus.h b/src/resolve/src/resolved-link-bus.h new file mode 100644 index 0000000000..5a8ee08ec7 --- /dev/null +++ b/src/resolve/src/resolved-link-bus.h @@ -0,0 +1,38 @@ +#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 . +***/ + +#include + +#include "resolved-link.h" + +extern const sd_bus_vtable link_vtable[]; + +int link_object_find(sd_bus *bus, const char *path, const char *interface, void *userdata, void **found, sd_bus_error *error); +char *link_bus_path(Link *link); +int link_node_enumerator(sd_bus *bus, const char *path, void *userdata, char ***nodes, sd_bus_error *error); + +int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_search_domains(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_llmnr(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_mdns(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_dnssec(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_set_dnssec_negative_trust_anchors(sd_bus_message *message, void *userdata, sd_bus_error *error); +int bus_link_method_revert(sd_bus_message *message, void *userdata, sd_bus_error *error); diff --git a/src/resolve/src/resolved-link.c b/src/resolve/src/resolved-link.c new file mode 100644 index 0000000000..65df95bb1f --- /dev/null +++ b/src/resolve/src/resolved-link.c @@ -0,0 +1,840 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include + +#include "alloc-util.h" +#include "missing.h" +#include "parse-util.h" +#include "resolved-link.h" +#include "string-util.h" +#include "strv.h" + +int link_new(Manager *m, Link **ret, int ifindex) { + _cleanup_(link_freep) Link *l = NULL; + int r; + + assert(m); + assert(ifindex > 0); + + r = hashmap_ensure_allocated(&m->links, NULL); + if (r < 0) + return r; + + l = new0(Link, 1); + if (!l) + return -ENOMEM; + + l->ifindex = ifindex; + l->llmnr_support = RESOLVE_SUPPORT_YES; + l->mdns_support = RESOLVE_SUPPORT_NO; + l->dnssec_mode = _DNSSEC_MODE_INVALID; + l->operstate = IF_OPER_UNKNOWN; + + r = hashmap_put(m->links, INT_TO_PTR(ifindex), l); + if (r < 0) + return r; + + l->manager = m; + + if (ret) + *ret = l; + l = NULL; + + return 0; +} + +void link_flush_settings(Link *l) { + assert(l); + + l->llmnr_support = RESOLVE_SUPPORT_YES; + l->mdns_support = RESOLVE_SUPPORT_NO; + l->dnssec_mode = _DNSSEC_MODE_INVALID; + + dns_server_unlink_all(l->dns_servers); + dns_search_domain_unlink_all(l->search_domains); + + l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); +} + +Link *link_free(Link *l) { + if (!l) + return NULL; + + link_flush_settings(l); + + while (l->addresses) + (void) link_address_free(l->addresses); + + if (l->manager) + hashmap_remove(l->manager->links, INT_TO_PTR(l->ifindex)); + + dns_scope_free(l->unicast_scope); + dns_scope_free(l->llmnr_ipv4_scope); + dns_scope_free(l->llmnr_ipv6_scope); + dns_scope_free(l->mdns_ipv4_scope); + dns_scope_free(l->mdns_ipv6_scope); + + free(l); + return NULL; +} + +void link_allocate_scopes(Link *l) { + int r; + + assert(l); + + if (link_relevant(l, AF_UNSPEC, false) && + l->dns_servers) { + if (!l->unicast_scope) { + r = dns_scope_new(l->manager, &l->unicast_scope, l, DNS_PROTOCOL_DNS, AF_UNSPEC); + if (r < 0) + log_warning_errno(r, "Failed to allocate DNS scope: %m"); + } + } else + l->unicast_scope = dns_scope_free(l->unicast_scope); + + if (link_relevant(l, AF_INET, true) && + l->llmnr_support != RESOLVE_SUPPORT_NO && + l->manager->llmnr_support != RESOLVE_SUPPORT_NO) { + if (!l->llmnr_ipv4_scope) { + r = dns_scope_new(l->manager, &l->llmnr_ipv4_scope, l, DNS_PROTOCOL_LLMNR, AF_INET); + if (r < 0) + log_warning_errno(r, "Failed to allocate LLMNR IPv4 scope: %m"); + } + } else + l->llmnr_ipv4_scope = dns_scope_free(l->llmnr_ipv4_scope); + + if (link_relevant(l, AF_INET6, true) && + l->llmnr_support != RESOLVE_SUPPORT_NO && + l->manager->llmnr_support != RESOLVE_SUPPORT_NO && + socket_ipv6_is_supported()) { + if (!l->llmnr_ipv6_scope) { + r = dns_scope_new(l->manager, &l->llmnr_ipv6_scope, l, DNS_PROTOCOL_LLMNR, AF_INET6); + if (r < 0) + log_warning_errno(r, "Failed to allocate LLMNR IPv6 scope: %m"); + } + } else + l->llmnr_ipv6_scope = dns_scope_free(l->llmnr_ipv6_scope); + + if (link_relevant(l, AF_INET, true) && + l->mdns_support != RESOLVE_SUPPORT_NO && + l->manager->mdns_support != RESOLVE_SUPPORT_NO) { + if (!l->mdns_ipv4_scope) { + r = dns_scope_new(l->manager, &l->mdns_ipv4_scope, l, DNS_PROTOCOL_MDNS, AF_INET); + if (r < 0) + log_warning_errno(r, "Failed to allocate mDNS IPv4 scope: %m"); + } + } else + l->mdns_ipv4_scope = dns_scope_free(l->mdns_ipv4_scope); + + if (link_relevant(l, AF_INET6, true) && + l->mdns_support != RESOLVE_SUPPORT_NO && + l->manager->mdns_support != RESOLVE_SUPPORT_NO) { + if (!l->mdns_ipv6_scope) { + r = dns_scope_new(l->manager, &l->mdns_ipv6_scope, l, DNS_PROTOCOL_MDNS, AF_INET6); + if (r < 0) + log_warning_errno(r, "Failed to allocate mDNS IPv6 scope: %m"); + } + } else + l->mdns_ipv6_scope = dns_scope_free(l->mdns_ipv6_scope); +} + +void link_add_rrs(Link *l, bool force_remove) { + LinkAddress *a; + + LIST_FOREACH(addresses, a, l->addresses) + link_address_add_rrs(a, force_remove); +} + +int link_update_rtnl(Link *l, sd_netlink_message *m) { + const char *n = NULL; + int r; + + assert(l); + assert(m); + + r = sd_rtnl_message_link_get_flags(m, &l->flags); + if (r < 0) + return r; + + (void) sd_netlink_message_read_u32(m, IFLA_MTU, &l->mtu); + (void) sd_netlink_message_read_u8(m, IFLA_OPERSTATE, &l->operstate); + + if (sd_netlink_message_read_string(m, IFLA_IFNAME, &n) >= 0) { + strncpy(l->name, n, sizeof(l->name)-1); + char_array_0(l->name); + } + + link_allocate_scopes(l); + link_add_rrs(l, false); + + return 0; +} + +static int link_update_dns_servers(Link *l) { + _cleanup_strv_free_ char **nameservers = NULL; + char **nameserver; + int r; + + assert(l); + + r = sd_network_link_get_dns(l->ifindex, &nameservers); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + 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); + 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); + return 0; + +clear: + dns_server_unlink_all(l->dns_servers); + return r; +} + +static int link_update_llmnr_support(Link *l) { + _cleanup_free_ char *b = NULL; + int r; + + assert(l); + + r = sd_network_link_get_llmnr(l->ifindex, &b); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + l->llmnr_support = resolve_support_from_string(b); + if (l->llmnr_support < 0) { + r = -EINVAL; + goto clear; + } + + return 0; + +clear: + l->llmnr_support = RESOLVE_SUPPORT_YES; + return r; +} + +static int link_update_mdns_support(Link *l) { + _cleanup_free_ char *b = NULL; + int r; + + assert(l); + + r = sd_network_link_get_mdns(l->ifindex, &b); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + l->mdns_support = resolve_support_from_string(b); + if (l->mdns_support < 0) { + r = -EINVAL; + goto clear; + } + + return 0; + +clear: + l->mdns_support = RESOLVE_SUPPORT_NO; + return r; +} + +void link_set_dnssec_mode(Link *l, DnssecMode mode) { + + assert(l); + + if (l->dnssec_mode == mode) + return; + + if ((l->dnssec_mode == _DNSSEC_MODE_INVALID) || + (l->dnssec_mode == DNSSEC_NO && mode != DNSSEC_NO) || + (l->dnssec_mode == DNSSEC_ALLOW_DOWNGRADE && mode == DNSSEC_YES)) { + + /* When switching from non-DNSSEC mode to DNSSEC mode, flush the cache. Also when switching from the + * allow-downgrade mode to full DNSSEC mode, flush it too. */ + if (l->unicast_scope) + dns_cache_flush(&l->unicast_scope->cache); + } + + l->dnssec_mode = mode; +} + +static int link_update_dnssec_mode(Link *l) { + _cleanup_free_ char *m = NULL; + DnssecMode mode; + int r; + + assert(l); + + r = sd_network_link_get_dnssec(l->ifindex, &m); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + mode = dnssec_mode_from_string(m); + if (mode < 0) { + r = -EINVAL; + goto clear; + } + + link_set_dnssec_mode(l, mode); + + return 0; + +clear: + l->dnssec_mode = _DNSSEC_MODE_INVALID; + return r; +} + +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); + + r = sd_network_link_get_dnssec_negative_trust_anchors(l->ifindex, &ntas); + if (r == -ENODATA) { + r = 0; + goto clear; + } + if (r < 0) + goto clear; + + ns = set_new(&dns_name_hash_ops); + if (!ns) + return -ENOMEM; + + STRV_FOREACH(i, ntas) { + r = set_put_strdup(ns, *i); + if (r < 0) + return r; + } + + set_free_free(l->dnssec_negative_trust_anchors); + l->dnssec_negative_trust_anchors = ns; + ns = NULL; + + return 0; + +clear: + l->dnssec_negative_trust_anchors = set_free_free(l->dnssec_negative_trust_anchors); + return r; +} + +static int link_update_search_domain_one(Link *l, const char *name, bool route_only) { + DnsSearchDomain *d; + int r; + + r = dns_search_domain_find(l->search_domains, name, &d); + if (r < 0) + return r; + if (r > 0) + dns_search_domain_move_back_and_unmark(d); + else { + r = dns_search_domain_new(l->manager, &d, DNS_SEARCH_DOMAIN_LINK, l, name); + if (r < 0) + return r; + } + + d->route_only = route_only; + return 0; +} + +static int link_update_search_domains(Link *l) { + _cleanup_strv_free_ char **sdomains = NULL, **rdomains = NULL; + char **i; + int r, q; + + assert(l); + + r = sd_network_link_get_search_domains(l->ifindex, &sdomains); + if (r < 0 && r != -ENODATA) + goto clear; + + q = sd_network_link_get_route_domains(l->ifindex, &rdomains); + if (q < 0 && q != -ENODATA) { + r = q; + goto clear; + } + + if (r == -ENODATA && q == -ENODATA) { + /* networkd knows nothing about this interface, and that's fine. */ + r = 0; + goto clear; + } + + dns_search_domain_mark_all(l->search_domains); + + STRV_FOREACH(i, sdomains) { + r = link_update_search_domain_one(l, *i, false); + if (r < 0) + goto clear; + } + + STRV_FOREACH(i, rdomains) { + r = link_update_search_domain_one(l, *i, true); + if (r < 0) + goto clear; + } + + dns_search_domain_unlink_marked(l->search_domains); + return 0; + +clear: + dns_search_domain_unlink_all(l->search_domains); + return r; +} + +static int link_is_unmanaged(Link *l) { + _cleanup_free_ char *state = NULL; + int r; + + assert(l); + + r = sd_network_link_get_setup_state(l->ifindex, &state); + if (r == -ENODATA) + return 1; + if (r < 0) + return r; + + return STR_IN_SET(state, "pending", "unmanaged"); +} + +static void link_read_settings(Link *l) { + int r; + + assert(l); + + /* Read settings from networkd, except when networkd is not managing this interface. */ + + r = link_is_unmanaged(l); + if (r < 0) { + log_warning_errno(r, "Failed to determine whether interface %s is managed: %m", l->name); + return; + } + 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) + link_flush_settings(l); + + l->is_managed = false; + return; + } + + l->is_managed = true; + + r = link_update_dns_servers(l); + if (r < 0) + log_warning_errno(r, "Failed to read DNS servers for interface %s, ignoring: %m", l->name); + + r = link_update_llmnr_support(l); + if (r < 0) + log_warning_errno(r, "Failed to read LLMNR support for interface %s, ignoring: %m", l->name); + + r = link_update_mdns_support(l); + if (r < 0) + log_warning_errno(r, "Failed to read mDNS support for interface %s, ignoring: %m", l->name); + + r = link_update_dnssec_mode(l); + if (r < 0) + log_warning_errno(r, "Failed to read DNSSEC mode for interface %s, ignoring: %m", l->name); + + r = link_update_dnssec_negative_trust_anchors(l); + if (r < 0) + log_warning_errno(r, "Failed to read DNSSEC negative trust anchors for interface %s, ignoring: %m", l->name); + + r = link_update_search_domains(l); + if (r < 0) + log_warning_errno(r, "Failed to read search domains for interface %s, ignoring: %m", l->name); +} + +int link_update_monitor(Link *l) { + assert(l); + + link_read_settings(l); + link_allocate_scopes(l); + link_add_rrs(l, false); + + return 0; +} + +bool link_relevant(Link *l, int family, bool local_multicast) { + _cleanup_free_ char *state = NULL; + LinkAddress *a; + + assert(l); + + /* A link is relevant for local multicast traffic if it isn't a loopback or pointopoint device, has a link + * beat, can do multicast and has at least one link-local (or better) IP address. + * + * A link is relevant for non-multicast traffic if it isn't a loopback device, has a link beat, and has at + * least one routable address.*/ + + if (l->flags & (IFF_LOOPBACK|IFF_DORMANT)) + return false; + + if ((l->flags & (IFF_UP|IFF_LOWER_UP)) != (IFF_UP|IFF_LOWER_UP)) + return false; + + if (local_multicast) { + if (l->flags & IFF_POINTOPOINT) + return false; + + if ((l->flags & IFF_MULTICAST) != IFF_MULTICAST) + return false; + } + + /* Check kernel operstate + * https://www.kernel.org/doc/Documentation/networking/operstates.txt */ + if (!IN_SET(l->operstate, IF_OPER_UNKNOWN, IF_OPER_UP)) + return false; + + (void) sd_network_link_get_operational_state(l->ifindex, &state); + if (state && !STR_IN_SET(state, "unknown", "degraded", "routable")) + return false; + + LIST_FOREACH(addresses, a, l->addresses) + if ((family == AF_UNSPEC || a->family == family) && link_address_relevant(a, local_multicast)) + return true; + + return false; +} + +LinkAddress *link_find_address(Link *l, int family, const union in_addr_union *in_addr) { + LinkAddress *a; + + assert(l); + + LIST_FOREACH(addresses, a, l->addresses) + if (a->family == family && in_addr_equal(family, &a->in_addr, in_addr)) + return a; + + return NULL; +} + +DnsServer* link_set_dns_server(Link *l, DnsServer *s) { + assert(l); + + if (l->current_dns_server == s) + return s; + + if (s) + log_info("Switching to DNS server %s for interface %s.", dns_server_string(s), l->name); + + dns_server_unref(l->current_dns_server); + l->current_dns_server = dns_server_ref(s); + + if (l->unicast_scope) + dns_cache_flush(&l->unicast_scope->cache); + + return s; +} + +DnsServer *link_get_dns_server(Link *l) { + assert(l); + + if (!l->current_dns_server) + link_set_dns_server(l, l->dns_servers); + + return l->current_dns_server; +} + +void link_next_dns_server(Link *l) { + assert(l); + + if (!l->current_dns_server) + return; + + /* Change to the next one, but make sure to follow the linked + * list only if this server is actually still linked. */ + if (l->current_dns_server->linked && l->current_dns_server->servers_next) { + link_set_dns_server(l, l->current_dns_server->servers_next); + return; + } + + link_set_dns_server(l, l->dns_servers); +} + +DnssecMode link_get_dnssec_mode(Link *l) { + assert(l); + + if (l->dnssec_mode != _DNSSEC_MODE_INVALID) + return l->dnssec_mode; + + return manager_get_dnssec_mode(l->manager); +} + +bool link_dnssec_supported(Link *l) { + DnsServer *server; + + assert(l); + + if (link_get_dnssec_mode(l) == DNSSEC_NO) + return false; + + server = link_get_dns_server(l); + if (server) + return dns_server_dnssec_supported(server); + + return true; +} + +int link_address_new(Link *l, LinkAddress **ret, int family, const union in_addr_union *in_addr) { + LinkAddress *a; + + assert(l); + assert(in_addr); + + a = new0(LinkAddress, 1); + if (!a) + return -ENOMEM; + + a->family = family; + a->in_addr = *in_addr; + + a->link = l; + LIST_PREPEND(addresses, l->addresses, a); + + if (ret) + *ret = a; + + return 0; +} + +LinkAddress *link_address_free(LinkAddress *a) { + if (!a) + return NULL; + + if (a->link) { + LIST_REMOVE(addresses, a->link->addresses, a); + + if (a->llmnr_address_rr) { + if (a->family == AF_INET && a->link->llmnr_ipv4_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); + else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); + } + + if (a->llmnr_ptr_rr) { + if (a->family == AF_INET && a->link->llmnr_ipv4_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); + else if (a->family == AF_INET6 && a->link->llmnr_ipv6_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); + } + } + + dns_resource_record_unref(a->llmnr_address_rr); + dns_resource_record_unref(a->llmnr_ptr_rr); + + free(a); + return NULL; +} + +void link_address_add_rrs(LinkAddress *a, bool force_remove) { + int r; + + assert(a); + + if (a->family == AF_INET) { + + if (!force_remove && + link_address_relevant(a, true) && + a->link->llmnr_ipv4_scope && + a->link->llmnr_support == RESOLVE_SUPPORT_YES && + a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { + + if (!a->link->manager->llmnr_host_ipv4_key) { + a->link->manager->llmnr_host_ipv4_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_A, a->link->manager->llmnr_hostname); + if (!a->link->manager->llmnr_host_ipv4_key) { + r = -ENOMEM; + goto fail; + } + } + + if (!a->llmnr_address_rr) { + a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv4_key); + if (!a->llmnr_address_rr) { + r = -ENOMEM; + goto fail; + } + + a->llmnr_address_rr->a.in_addr = a->in_addr.in; + a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; + } + + if (!a->llmnr_ptr_rr) { + r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); + if (r < 0) + goto fail; + + a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; + } + + r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_address_rr, true); + if (r < 0) + log_warning_errno(r, "Failed to add A record to LLMNR zone: %m"); + + r = dns_zone_put(&a->link->llmnr_ipv4_scope->zone, a->link->llmnr_ipv4_scope, a->llmnr_ptr_rr, false); + if (r < 0) + log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); + } else { + if (a->llmnr_address_rr) { + if (a->link->llmnr_ipv4_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_address_rr); + a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); + } + + if (a->llmnr_ptr_rr) { + if (a->link->llmnr_ipv4_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv4_scope->zone, a->llmnr_ptr_rr); + a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); + } + } + } + + if (a->family == AF_INET6) { + + if (!force_remove && + link_address_relevant(a, true) && + a->link->llmnr_ipv6_scope && + a->link->llmnr_support == RESOLVE_SUPPORT_YES && + a->link->manager->llmnr_support == RESOLVE_SUPPORT_YES) { + + if (!a->link->manager->llmnr_host_ipv6_key) { + a->link->manager->llmnr_host_ipv6_key = dns_resource_key_new(DNS_CLASS_IN, DNS_TYPE_AAAA, a->link->manager->llmnr_hostname); + if (!a->link->manager->llmnr_host_ipv6_key) { + r = -ENOMEM; + goto fail; + } + } + + if (!a->llmnr_address_rr) { + a->llmnr_address_rr = dns_resource_record_new(a->link->manager->llmnr_host_ipv6_key); + if (!a->llmnr_address_rr) { + r = -ENOMEM; + goto fail; + } + + a->llmnr_address_rr->aaaa.in6_addr = a->in_addr.in6; + a->llmnr_address_rr->ttl = LLMNR_DEFAULT_TTL; + } + + if (!a->llmnr_ptr_rr) { + r = dns_resource_record_new_reverse(&a->llmnr_ptr_rr, a->family, &a->in_addr, a->link->manager->llmnr_hostname); + if (r < 0) + goto fail; + + a->llmnr_ptr_rr->ttl = LLMNR_DEFAULT_TTL; + } + + r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_address_rr, true); + if (r < 0) + log_warning_errno(r, "Failed to add AAAA record to LLMNR zone: %m"); + + r = dns_zone_put(&a->link->llmnr_ipv6_scope->zone, a->link->llmnr_ipv6_scope, a->llmnr_ptr_rr, false); + if (r < 0) + log_warning_errno(r, "Failed to add IPv6 PTR record to LLMNR zone: %m"); + } else { + if (a->llmnr_address_rr) { + if (a->link->llmnr_ipv6_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_address_rr); + a->llmnr_address_rr = dns_resource_record_unref(a->llmnr_address_rr); + } + + if (a->llmnr_ptr_rr) { + if (a->link->llmnr_ipv6_scope) + dns_zone_remove_rr(&a->link->llmnr_ipv6_scope->zone, a->llmnr_ptr_rr); + a->llmnr_ptr_rr = dns_resource_record_unref(a->llmnr_ptr_rr); + } + } + } + + return; + +fail: + log_debug_errno(r, "Failed to update address RRs: %m"); +} + +int link_address_update_rtnl(LinkAddress *a, sd_netlink_message *m) { + int r; + assert(a); + assert(m); + + r = sd_rtnl_message_addr_get_flags(m, &a->flags); + if (r < 0) + return r; + + sd_rtnl_message_addr_get_scope(m, &a->scope); + + link_allocate_scopes(a->link); + link_add_rrs(a->link, false); + + return 0; +} + +bool link_address_relevant(LinkAddress *a, bool local_multicast) { + assert(a); + + if (a->flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE)) + return false; + + if (a->scope >= (local_multicast ? RT_SCOPE_HOST : RT_SCOPE_LINK)) + return false; + + return true; +} diff --git a/src/resolve/src/resolved-link.h b/src/resolve/src/resolved-link.h new file mode 100644 index 0000000000..f534c12824 --- /dev/null +++ b/src/resolve/src/resolved-link.h @@ -0,0 +1,111 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "in-addr-util.h" +#include "ratelimit.h" +#include "resolve-util.h" + +typedef struct Link Link; +typedef struct LinkAddress LinkAddress; + +#include "resolved-dns-rr.h" +#include "resolved-dns-search-domain.h" +#include "resolved-dns-server.h" +#include "resolved-manager.h" + +#define LINK_SEARCH_DOMAINS_MAX 32 +#define LINK_DNS_SERVERS_MAX 32 + +struct LinkAddress { + Link *link; + + int family; + union in_addr_union in_addr; + + unsigned char flags, scope; + + DnsResourceRecord *llmnr_address_rr; + DnsResourceRecord *llmnr_ptr_rr; + + LIST_FIELDS(LinkAddress, addresses); +}; + +struct Link { + Manager *manager; + + int ifindex; + unsigned flags; + + LIST_HEAD(LinkAddress, addresses); + + LIST_HEAD(DnsServer, dns_servers); + DnsServer *current_dns_server; + unsigned n_dns_servers; + + LIST_HEAD(DnsSearchDomain, search_domains); + unsigned n_search_domains; + + ResolveSupport llmnr_support; + ResolveSupport mdns_support; + DnssecMode dnssec_mode; + Set *dnssec_negative_trust_anchors; + + DnsScope *unicast_scope; + DnsScope *llmnr_ipv4_scope; + DnsScope *llmnr_ipv6_scope; + DnsScope *mdns_ipv4_scope; + DnsScope *mdns_ipv6_scope; + + bool is_managed; + + char name[IF_NAMESIZE]; + uint32_t mtu; + uint8_t operstate; +}; + +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); +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); + +void link_flush_settings(Link *l); +void link_set_dnssec_mode(Link *l, DnssecMode mode); +void link_allocate_scopes(Link *l); + +DnsServer* link_set_dns_server(Link *l, DnsServer *s); +DnsServer* link_get_dns_server(Link *l); +void link_next_dns_server(Link *l); + +DnssecMode link_get_dnssec_mode(Link *l); +bool link_dnssec_supported(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); +bool link_address_relevant(LinkAddress *l, bool local_multicast); +void link_address_add_rrs(LinkAddress *a, bool force_remove); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Link*, link_free); diff --git a/src/resolve/src/resolved-llmnr.c b/src/resolve/src/resolved-llmnr.c new file mode 100644 index 0000000000..ef12abfbb5 --- /dev/null +++ b/src/resolve/src/resolved-llmnr.c @@ -0,0 +1,476 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . + ***/ + +#include +#include + +#include "fd-util.h" +#include "resolved-llmnr.h" +#include "resolved-manager.h" + +void manager_llmnr_stop(Manager *m) { + assert(m); + + m->llmnr_ipv4_udp_event_source = sd_event_source_unref(m->llmnr_ipv4_udp_event_source); + m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); + + m->llmnr_ipv6_udp_event_source = sd_event_source_unref(m->llmnr_ipv6_udp_event_source); + m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); + + m->llmnr_ipv4_tcp_event_source = sd_event_source_unref(m->llmnr_ipv4_tcp_event_source); + m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); + + m->llmnr_ipv6_tcp_event_source = sd_event_source_unref(m->llmnr_ipv6_tcp_event_source); + m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); +} + +int manager_llmnr_start(Manager *m) { + int r; + + assert(m); + + if (m->llmnr_support == RESOLVE_SUPPORT_NO) + return 0; + + r = manager_llmnr_ipv4_udp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + r = manager_llmnr_ipv4_tcp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + if (socket_ipv6_is_supported()) { + r = manager_llmnr_ipv6_udp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + r = manager_llmnr_ipv6_tcp_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + } + + return 0; + +eaddrinuse: + log_warning("There appears to be another LLMNR responder running. Turning off LLMNR support."); + m->llmnr_support = RESOLVE_SUPPORT_NO; + manager_llmnr_stop(m); + + return 0; +} + +static int on_llmnr_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + DnsTransaction *t = NULL; + Manager *m = userdata; + DnsScope *scope; + int r; + + r = manager_recv(m, fd, DNS_PROTOCOL_LLMNR, &p); + if (r <= 0) + return r; + + scope = manager_find_scope(m, p); + 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)); + + dns_scope_check_conflicts(scope, p); + + t = hashmap_get(m->dns_transactions, UINT_TO_PTR(DNS_PACKET_ID(p))); + if (t) + 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)); + + dns_scope_process_query(scope, NULL, p); + } else + log_debug("Invalid LLMNR UDP packet, ignoring."); + + return 0; +} + +int manager_llmnr_ipv4_udp_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(LLMNR_PORT), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; + int r; + + assert(m); + + if (m->llmnr_ipv4_udp_fd >= 0) + return m->llmnr_ipv4_udp_fd; + + m->llmnr_ipv4_udp_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv4_udp_fd < 0) + return -errno; + + /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Disable Don't-Fragment bit in the IP header */ + r = setsockopt(m->llmnr_ipv4_udp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv4_udp_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv4_udp_event_source, m->llmnr_ipv4_udp_fd, EPOLLIN, on_llmnr_packet, m); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(m->llmnr_ipv4_udp_event_source, "llmnr-ipv4-udp"); + + return m->llmnr_ipv4_udp_fd; + +fail: + m->llmnr_ipv4_udp_fd = safe_close(m->llmnr_ipv4_udp_fd); + return r; +} + +int manager_llmnr_ipv6_udp_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(LLMNR_PORT), + }; + static const int one = 1, ttl = 255; + int r; + + assert(m); + + if (m->llmnr_ipv6_udp_fd >= 0) + return m->llmnr_ipv6_udp_fd; + + m->llmnr_ipv6_udp_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv6_udp_fd < 0) + return -errno; + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_udp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv6_udp_fd, &sa.sa, sizeof(sa.in6)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv6_udp_event_source, m->llmnr_ipv6_udp_fd, EPOLLIN, on_llmnr_packet, m); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(m->llmnr_ipv6_udp_event_source, "llmnr-ipv6-udp"); + + return m->llmnr_ipv6_udp_fd; + +fail: + m->llmnr_ipv6_udp_fd = safe_close(m->llmnr_ipv6_udp_fd); + return r; +} + +static int on_llmnr_stream_packet(DnsStream *s) { + DnsScope *scope; + + assert(s); + + scope = manager_find_scope(s->manager, s->read_packet); + if (!scope) { + log_warning("Got LLMNR TCP packet on unknown scope. Ignroing."); + 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)); + + 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."); + + dns_stream_free(s); + return 0; +} + +static int on_llmnr_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_LLMNR, cfd); + if (r < 0) { + safe_close(cfd); + return r; + } + + stream->on_packet = on_llmnr_stream_packet; + return 0; +} + +int manager_llmnr_ipv4_tcp_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(LLMNR_PORT), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT; + int r; + + assert(m); + + if (m->llmnr_ipv4_tcp_fd >= 0) + return m->llmnr_ipv4_tcp_fd; + + m->llmnr_ipv4_tcp_fd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv4_tcp_fd < 0) + return -errno; + + /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_TTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Disable Don't-Fragment bit in the IP header */ + r = setsockopt(m->llmnr_ipv4_tcp_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv4_tcp_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = listen(m->llmnr_ipv4_tcp_fd, SOMAXCONN); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv4_tcp_event_source, m->llmnr_ipv4_tcp_fd, EPOLLIN, on_llmnr_stream, m); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(m->llmnr_ipv4_tcp_event_source, "llmnr-ipv4-tcp"); + + return m->llmnr_ipv4_tcp_fd; + +fail: + m->llmnr_ipv4_tcp_fd = safe_close(m->llmnr_ipv4_tcp_fd); + return r; +} + +int manager_llmnr_ipv6_tcp_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(LLMNR_PORT), + }; + static const int one = 1; + int r; + + assert(m); + + if (m->llmnr_ipv6_tcp_fd >= 0) + return m->llmnr_ipv6_tcp_fd; + + m->llmnr_ipv6_tcp_fd = socket(AF_INET6, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->llmnr_ipv6_tcp_fd < 0) + return -errno; + + /* RFC 4795, section 2.5. requires setting the TTL of TCP streams to 1 */ + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->llmnr_ipv6_tcp_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->llmnr_ipv6_tcp_fd, &sa.sa, sizeof(sa.in6)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = listen(m->llmnr_ipv6_tcp_fd, SOMAXCONN); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->llmnr_ipv6_tcp_event_source, m->llmnr_ipv6_tcp_fd, EPOLLIN, on_llmnr_stream, m); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(m->llmnr_ipv6_tcp_event_source, "llmnr-ipv6-tcp"); + + return m->llmnr_ipv6_tcp_fd; + +fail: + m->llmnr_ipv6_tcp_fd = safe_close(m->llmnr_ipv6_tcp_fd); + return r; +} diff --git a/src/resolve/src/resolved-llmnr.h b/src/resolve/src/resolved-llmnr.h new file mode 100644 index 0000000000..8133582fa7 --- /dev/null +++ b/src/resolve/src/resolved-llmnr.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "resolved-manager.h" + +#define LLMNR_PORT 5355 + +int manager_llmnr_ipv4_udp_fd(Manager *m); +int manager_llmnr_ipv6_udp_fd(Manager *m); +int manager_llmnr_ipv4_tcp_fd(Manager *m); +int manager_llmnr_ipv6_tcp_fd(Manager *m); + +void manager_llmnr_stop(Manager *m); +int manager_llmnr_start(Manager *m); diff --git a/src/resolve/src/resolved-manager.c b/src/resolve/src/resolved-manager.c new file mode 100644 index 0000000000..e82c6ec563 --- /dev/null +++ b/src/resolve/src/resolved-manager.c @@ -0,0 +1,1239 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . + ***/ + +#include +#include +#include + +#include "af-list.h" +#include "alloc-util.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "fileio-label.h" +#include "hostname-util.h" +#include "io-util.h" +#include "netlink-util.h" +#include "network-internal.h" +#include "ordered-set.h" +#include "parse-util.h" +#include "random-util.h" +#include "resolved-bus.h" +#include "resolved-conf.h" +#include "resolved-etc-hosts.h" +#include "resolved-llmnr.h" +#include "resolved-manager.h" +#include "resolved-mdns.h" +#include "resolved-resolv-conf.h" +#include "socket-util.h" +#include "string-table.h" +#include "string-util.h" +#include "utf8.h" + +#define SEND_TIMEOUT_USEC (200 * USEC_PER_MSEC) + +static int manager_process_link(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { + Manager *m = userdata; + uint16_t type; + Link *l; + int ifindex, r; + + assert(rtnl); + assert(m); + assert(mm); + + r = sd_netlink_message_get_type(mm, &type); + if (r < 0) + goto fail; + + r = sd_rtnl_message_link_get_ifindex(mm, &ifindex); + if (r < 0) + goto fail; + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + + switch (type) { + + case RTM_NEWLINK:{ + bool is_new = !l; + + if (!l) { + r = link_new(m, &l, ifindex); + if (r < 0) + goto fail; + } + + r = link_update_rtnl(l, mm); + if (r < 0) + goto fail; + + r = link_update_monitor(l); + if (r < 0) + goto fail; + + if (is_new) + log_debug("Found new link %i/%s", ifindex, l->name); + + break; + } + + case RTM_DELLINK: + if (l) { + log_debug("Removing link %i/%s", l->ifindex, l->name); + link_free(l); + } + + break; + } + + return 0; + +fail: + log_warning_errno(r, "Failed to process RTNL link message: %m"); + return 0; +} + +static int manager_process_address(sd_netlink *rtnl, sd_netlink_message *mm, void *userdata) { + Manager *m = userdata; + union in_addr_union address; + uint16_t type; + int r, ifindex, family; + LinkAddress *a; + Link *l; + + assert(rtnl); + assert(mm); + assert(m); + + r = sd_netlink_message_get_type(mm, &type); + if (r < 0) + goto fail; + + r = sd_rtnl_message_addr_get_ifindex(mm, &ifindex); + if (r < 0) + goto fail; + + l = hashmap_get(m->links, INT_TO_PTR(ifindex)); + if (!l) + return 0; + + r = sd_rtnl_message_addr_get_family(mm, &family); + if (r < 0) + goto fail; + + switch (family) { + + case AF_INET: + r = sd_netlink_message_read_in_addr(mm, IFA_LOCAL, &address.in); + if (r < 0) { + r = sd_netlink_message_read_in_addr(mm, IFA_ADDRESS, &address.in); + if (r < 0) + goto fail; + } + + break; + + case AF_INET6: + r = sd_netlink_message_read_in6_addr(mm, IFA_LOCAL, &address.in6); + if (r < 0) { + r = sd_netlink_message_read_in6_addr(mm, IFA_ADDRESS, &address.in6); + if (r < 0) + goto fail; + } + + break; + + default: + return 0; + } + + a = link_find_address(l, family, &address); + + switch (type) { + + case RTM_NEWADDR: + + if (!a) { + r = link_address_new(l, &a, family, &address); + if (r < 0) + return r; + } + + r = link_address_update_rtnl(a, mm); + if (r < 0) + return r; + + break; + + case RTM_DELADDR: + link_address_free(a); + break; + } + + return 0; + +fail: + log_warning_errno(r, "Failed to process RTNL address message: %m"); + return 0; +} + +static int manager_rtnl_listen(Manager *m) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; + sd_netlink_message *i; + int r; + + assert(m); + + /* First, subscribe to interfaces coming and going */ + r = sd_netlink_open(&m->rtnl); + if (r < 0) + return r; + + r = sd_netlink_attach_event(m->rtnl, m->event, SD_EVENT_PRIORITY_IMPORTANT); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_NEWLINK, manager_process_link, m); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_DELLINK, manager_process_link, m); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_NEWADDR, manager_process_address, m); + if (r < 0) + return r; + + r = sd_netlink_add_match(m->rtnl, RTM_DELADDR, manager_process_address, m); + if (r < 0) + return r; + + /* Then, enumerate all links */ + r = sd_rtnl_message_new_link(m->rtnl, &req, RTM_GETLINK, 0); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(m->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (i = reply; i; i = sd_netlink_message_next(i)) { + r = manager_process_link(m->rtnl, i, m); + if (r < 0) + return r; + } + + req = sd_netlink_message_unref(req); + reply = sd_netlink_message_unref(reply); + + /* Finally, enumerate all addresses, too */ + r = sd_rtnl_message_new_addr(m->rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC); + if (r < 0) + return r; + + r = sd_netlink_message_request_dump(req, true); + if (r < 0) + return r; + + r = sd_netlink_call(m->rtnl, req, 0, &reply); + if (r < 0) + return r; + + for (i = reply; i; i = sd_netlink_message_next(i)) { + r = manager_process_address(m->rtnl, i, m); + if (r < 0) + return r; + } + + return r; +} + +static int on_network_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + Iterator i; + Link *l; + int r; + + assert(m); + + sd_network_monitor_flush(m->network_monitor); + + HASHMAP_FOREACH(l, m->links, i) { + r = link_update_monitor(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"); + + return 0; +} + +static int manager_network_monitor_listen(Manager *m) { + int r, fd, events; + + assert(m); + + r = sd_network_monitor_new(&m->network_monitor, NULL); + if (r < 0) + return r; + + fd = sd_network_monitor_get_fd(m->network_monitor); + if (fd < 0) + return fd; + + events = sd_network_monitor_get_events(m->network_monitor); + if (events < 0) + return events; + + r = sd_event_add_io(m->event, &m->network_event_source, fd, events, &on_network_event, m); + if (r < 0) + return r; + + r = sd_event_source_set_priority(m->network_event_source, SD_EVENT_PRIORITY_IMPORTANT+5); + if (r < 0) + return r; + + (void) sd_event_source_set_description(m->network_event_source, "network-monitor"); + + return 0; +} + +static int determine_hostname(char **llmnr_hostname, char **mdns_hostname) { + _cleanup_free_ char *h = NULL, *n = NULL; + char label[DNS_LABEL_MAX]; + const char *p; + int r, k; + + assert(llmnr_hostname); + assert(mdns_hostname); + + /* Extract and normalize the first label of the locally + * configured hostname, and check it's not "localhost". */ + + h = gethostname_malloc(); + if (!h) + return log_oom(); + + p = h; + r = dns_label_unescape(&p, label, sizeof(label)); + if (r < 0) + return log_error_errno(r, "Failed to unescape host name: %m"); + if (r == 0) { + log_error("Couldn't find a single label in hosntame."); + return -EINVAL; + } + + k = dns_label_undo_idna(label, r, label, sizeof(label)); + if (k < 0) + return log_error_errno(k, "Failed to undo IDNA: %m"); + if (k > 0) + r = k; + + if (!utf8_is_valid(label)) { + log_error("System hostname is not UTF-8 clean."); + return -EINVAL; + } + + r = dns_label_escape_new(label, r, &n); + if (r < 0) + return log_error_errno(r, "Failed to escape host name: %m"); + + if (is_localhost(n)) { + log_debug("System hostname is 'localhost', ignoring."); + return -EINVAL; + } + + r = dns_name_concat(n, "local", mdns_hostname); + if (r < 0) + return log_error_errno(r, "Failed to determine mDNS hostname: %m"); + + *llmnr_hostname = n; + n = NULL; + + return 0; +} + +static int on_hostname_change(sd_event_source *es, int fd, uint32_t revents, void *userdata) { + _cleanup_free_ char *llmnr_hostname = NULL, *mdns_hostname = NULL; + Manager *m = userdata; + int r; + + assert(m); + + r = determine_hostname(&llmnr_hostname, &mdns_hostname); + if (r < 0) + return 0; /* ignore invalid hostnames */ + + if (streq(llmnr_hostname, m->llmnr_hostname) && streq(mdns_hostname, m->mdns_hostname)) + return 0; + + log_info("System hostname changed to '%s'.", llmnr_hostname); + + free(m->llmnr_hostname); + free(m->mdns_hostname); + + m->llmnr_hostname = llmnr_hostname; + m->mdns_hostname = mdns_hostname; + + llmnr_hostname = mdns_hostname = NULL; + + manager_refresh_rrs(m); + + return 0; +} + +static int manager_watch_hostname(Manager *m) { + int r; + + assert(m); + + m->hostname_fd = open("/proc/sys/kernel/hostname", O_RDONLY|O_CLOEXEC|O_NDELAY|O_NOCTTY); + if (m->hostname_fd < 0) { + log_warning_errno(errno, "Failed to watch hostname: %m"); + return 0; + } + + r = sd_event_add_io(m->event, &m->hostname_event_source, m->hostname_fd, 0, on_hostname_change, m); + if (r < 0) { + if (r == -EPERM) + /* kernels prior to 3.2 don't support polling this file. Ignore the failure. */ + m->hostname_fd = safe_close(m->hostname_fd); + else + return log_error_errno(r, "Failed to add hostname event source: %m"); + } + + (void) sd_event_source_set_description(m->hostname_event_source, "hostname"); + + r = determine_hostname(&m->llmnr_hostname, &m->mdns_hostname); + if (r < 0) { + log_info("Defaulting to hostname 'gnu-linux'."); + m->llmnr_hostname = strdup("gnu-linux"); + if (!m->llmnr_hostname) + return log_oom(); + + m->mdns_hostname = strdup("gnu-linux.local"); + if (!m->mdns_hostname) + return log_oom(); + } else + log_info("Using system hostname '%s'.", m->llmnr_hostname); + + return 0; +} + +static int manager_sigusr1(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + _cleanup_free_ char *buffer = NULL; + _cleanup_fclose_ FILE *f = NULL; + Manager *m = userdata; + size_t size = 0; + DnsScope *scope; + + assert(s); + assert(si); + assert(m); + + f = open_memstream(&buffer, &size); + if (!f) + return log_oom(); + + LIST_FOREACH(scopes, scope, m->dns_scopes) + dns_scope_dump(scope, f); + + if (fflush_and_check(f) < 0) + return log_oom(); + + log_dump(LOG_INFO, buffer); + return 0; +} + +int manager_new(Manager **ret) { + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + assert(ret); + + m = new0(Manager, 1); + if (!m) + return -ENOMEM; + + 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->hostname_fd = -1; + + m->llmnr_support = RESOLVE_SUPPORT_YES; + m->mdns_support = RESOLVE_SUPPORT_NO; + m->dnssec_mode = DNSSEC_NO; + m->read_resolv_conf = true; + m->need_builtin_fallbacks = true; + m->etc_hosts_last = m->etc_hosts_mtime = USEC_INFINITY; + + r = dns_trust_anchor_load(&m->trust_anchor); + if (r < 0) + return r; + + r = manager_parse_config_file(m); + if (r < 0) + return r; + + r = sd_event_default(&m->event); + if (r < 0) + return r; + + sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); + sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); + + sd_event_set_watchdog(m->event, true); + + r = manager_watch_hostname(m); + if (r < 0) + return r; + + r = dns_scope_new(m, &m->unicast_scope, NULL, DNS_PROTOCOL_DNS, AF_UNSPEC); + if (r < 0) + return r; + + r = manager_network_monitor_listen(m); + if (r < 0) + return r; + + r = manager_rtnl_listen(m); + if (r < 0) + return r; + + r = manager_connect_bus(m); + if (r < 0) + return r; + + (void) sd_event_add_signal(m->event, &m->sigusr1_event_source, SIGUSR1, manager_sigusr1, m); + + *ret = m; + m = NULL; + + return 0; +} + +int manager_start(Manager *m) { + int r; + + assert(m); + + r = manager_llmnr_start(m); + if (r < 0) + return r; + + r = manager_mdns_start(m); + if (r < 0) + return r; + + return 0; +} + +Manager *manager_free(Manager *m) { + Link *l; + + if (!m) + return NULL; + + dns_server_unlink_all(m->dns_servers); + dns_server_unlink_all(m->fallback_dns_servers); + dns_search_domain_unlink_all(m->search_domains); + + while ((l = hashmap_first(m->links))) + link_free(l); + + while (m->dns_queries) + dns_query_free(m->dns_queries); + + dns_scope_free(m->unicast_scope); + + hashmap_free(m->links); + hashmap_free(m->dns_transactions); + + sd_event_source_unref(m->network_event_source); + sd_network_monitor_unref(m->network_monitor); + + sd_netlink_unref(m->rtnl); + sd_event_source_unref(m->rtnl_event_source); + + manager_llmnr_stop(m); + manager_mdns_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_unref(m->event); + + dns_resource_key_unref(m->llmnr_host_ipv4_key); + dns_resource_key_unref(m->llmnr_host_ipv6_key); + + sd_event_source_unref(m->hostname_event_source); + safe_close(m->hostname_fd); + free(m->llmnr_hostname); + free(m->mdns_hostname); + + dns_trust_anchor_flush(&m->trust_anchor); + manager_etc_hosts_flush(m); + + free(m); + + return NULL; +} + +int manager_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + union { + struct cmsghdr header; /* For alignment */ + uint8_t buffer[CMSG_SPACE(MAXSIZE(struct in_pktinfo, struct in6_pktinfo)) + + CMSG_SPACE(int) /* ttl/hoplimit */ + + EXTRA_CMSG_SPACE /* kernel appears to require extra buffer space */]; + } control; + union sockaddr_union sa; + struct msghdr mh = {}; + struct cmsghdr *cmsg; + struct iovec iov; + ssize_t ms, l; + int r; + + assert(m); + assert(fd >= 0); + assert(ret); + + ms = next_datagram_size_fd(fd); + if (ms < 0) + return ms; + + r = dns_packet_new(&p, protocol, ms); + if (r < 0) + return r; + + iov.iov_base = DNS_PACKET_DATA(p); + iov.iov_len = p->allocated; + + mh.msg_name = &sa.sa; + mh.msg_namelen = sizeof(sa); + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_control = &control; + mh.msg_controllen = sizeof(control); + + l = recvmsg(fd, &mh, 0); + if (l < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } + + if (l <= 0) + return -EIO; + + assert(!(mh.msg_flags & MSG_CTRUNC)); + assert(!(mh.msg_flags & MSG_TRUNC)); + + p->size = (size_t) l; + + p->family = sa.sa.sa_family; + p->ipproto = IPPROTO_UDP; + if (p->family == AF_INET) { + p->sender.in = sa.in.sin_addr; + p->sender_port = be16toh(sa.in.sin_port); + } else if (p->family == AF_INET6) { + p->sender.in6 = sa.in6.sin6_addr; + p->sender_port = be16toh(sa.in6.sin6_port); + p->ifindex = sa.in6.sin6_scope_id; + } else + return -EAFNOSUPPORT; + + CMSG_FOREACH(cmsg, &mh) { + + if (cmsg->cmsg_level == IPPROTO_IPV6) { + assert(p->family == AF_INET6); + + switch (cmsg->cmsg_type) { + + case IPV6_PKTINFO: { + struct in6_pktinfo *i = (struct in6_pktinfo*) CMSG_DATA(cmsg); + + if (p->ifindex <= 0) + p->ifindex = i->ipi6_ifindex; + + p->destination.in6 = i->ipi6_addr; + break; + } + + case IPV6_HOPLIMIT: + p->ttl = *(int *) CMSG_DATA(cmsg); + break; + + } + } else if (cmsg->cmsg_level == IPPROTO_IP) { + assert(p->family == AF_INET); + + switch (cmsg->cmsg_type) { + + case IP_PKTINFO: { + struct in_pktinfo *i = (struct in_pktinfo*) CMSG_DATA(cmsg); + + if (p->ifindex <= 0) + p->ifindex = i->ipi_ifindex; + + p->destination.in = i->ipi_addr; + break; + } + + case IP_TTL: + p->ttl = *(int *) CMSG_DATA(cmsg); + break; + } + } + } + + /* The Linux kernel sets the interface index to the loopback + * device if the packet came from the local host since it + * avoids the routing table in such a case. Let's unset the + * interface index in such a case. */ + if (p->ifindex == LOOPBACK_IFINDEX) + p->ifindex = 0; + + if (protocol != DNS_PROTOCOL_DNS) { + /* If we don't know the interface index still, we look for the + * first local interface with a matching address. Yuck! */ + if (p->ifindex <= 0) + p->ifindex = manager_find_ifindex(m, p->family, &p->destination); + } + + *ret = p; + p = NULL; + + return 1; +} + +static int sendmsg_loop(int fd, struct msghdr *mh, int flags) { + int r; + + assert(fd >= 0); + assert(mh); + + for (;;) { + if (sendmsg(fd, mh, flags) >= 0) + return 0; + + if (errno == EINTR) + continue; + + if (errno != EAGAIN) + return -errno; + + r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); + if (r < 0) + return r; + if (r == 0) + return -ETIMEDOUT; + } +} + +static int write_loop(int fd, void *message, size_t length) { + int r; + + assert(fd >= 0); + assert(message); + + for (;;) { + if (write(fd, message, length) >= 0) + return 0; + + if (errno == EINTR) + continue; + + if (errno != EAGAIN) + return -errno; + + r = fd_wait_for_event(fd, POLLOUT, SEND_TIMEOUT_USEC); + if (r < 0) + return r; + if (r == 0) + return -ETIMEDOUT; + } +} + +int manager_write(Manager *m, int fd, DnsPacket *p) { + int r; + + log_debug("Sending %s packet with id %" PRIu16 ".", DNS_PACKET_QR(p) ? "response" : "query", DNS_PACKET_ID(p)); + + r = write_loop(fd, DNS_PACKET_DATA(p), p->size); + if (r < 0) + return r; + + return 0; +} + +static int manager_ipv4_send(Manager *m, int fd, int ifindex, const struct in_addr *addr, uint16_t port, DnsPacket *p) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + }; + union { + struct cmsghdr header; /* For alignment */ + uint8_t buffer[CMSG_SPACE(sizeof(struct in_pktinfo))]; + } control; + struct msghdr mh = {}; + struct iovec iov; + + assert(m); + assert(fd >= 0); + assert(addr); + 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_port = htobe16(port), + + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_name = &sa.sa; + mh.msg_namelen = sizeof(sa.in); + + if (ifindex > 0) { + struct cmsghdr *cmsg; + struct in_pktinfo *pi; + + zero(control); + + mh.msg_control = &control; + mh.msg_controllen = CMSG_LEN(sizeof(struct in_pktinfo)); + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_len = mh.msg_controllen; + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + + pi = (struct in_pktinfo*) CMSG_DATA(cmsg); + pi->ipi_ifindex = ifindex; + } + + 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) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + }; + union { + struct cmsghdr header; /* For alignment */ + uint8_t buffer[CMSG_SPACE(sizeof(struct in6_pktinfo))]; + } control; + struct msghdr mh = {}; + struct iovec iov; + + assert(m); + assert(fd >= 0); + assert(addr); + 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_port = htobe16(port), + sa.in6.sin6_scope_id = ifindex; + + mh.msg_iov = &iov; + mh.msg_iovlen = 1; + mh.msg_name = &sa.sa; + mh.msg_namelen = sizeof(sa.in6); + + if (ifindex > 0) { + struct cmsghdr *cmsg; + struct in6_pktinfo *pi; + + zero(control); + + mh.msg_control = &control; + mh.msg_controllen = CMSG_LEN(sizeof(struct in6_pktinfo)); + + cmsg = CMSG_FIRSTHDR(&mh); + cmsg->cmsg_len = mh.msg_controllen; + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + + pi = (struct in6_pktinfo*) CMSG_DATA(cmsg); + pi->ipi6_ifindex = ifindex; + } + + 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) { + assert(m); + assert(fd >= 0); + assert(addr); + 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 -EAFNOSUPPORT; +} + +uint32_t manager_find_mtu(Manager *m) { + uint32_t mtu = 0; + Link *l; + Iterator i; + + /* If we don't know on which link a DNS packet would be + * delivered, let's find the largest MTU that works on all + * interfaces we know of */ + + HASHMAP_FOREACH(l, m->links, i) { + if (l->mtu <= 0) + continue; + + if (mtu <= 0 || l->mtu < mtu) + mtu = l->mtu; + } + + return mtu; +} + +int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr) { + LinkAddress *a; + + assert(m); + + a = manager_find_link_address(m, family, in_addr); + if (a) + return a->link->ifindex; + + return 0; +} + +void manager_refresh_rrs(Manager *m) { + Iterator i; + Link *l; + + assert(m); + + m->llmnr_host_ipv4_key = dns_resource_key_unref(m->llmnr_host_ipv4_key); + m->llmnr_host_ipv6_key = dns_resource_key_unref(m->llmnr_host_ipv6_key); + + HASHMAP_FOREACH(l, m->links, i) { + link_add_rrs(l, true); + link_add_rrs(l, false); + } +} + +int manager_next_hostname(Manager *m) { + const char *p; + uint64_t u, a; + char *h, *k; + int r; + + assert(m); + + p = strchr(m->llmnr_hostname, 0); + assert(p); + + while (p > m->llmnr_hostname) { + if (!strchr("0123456789", p[-1])) + break; + + p--; + } + + if (*p == 0 || safe_atou64(p, &u) < 0 || u <= 0) + u = 1; + + /* Add a random number to the old value. This way we can avoid + * that two hosts pick the same hostname, win on IPv4 and lose + * on IPv6 (or vice versa), and pick the same hostname + * replacement hostname, ad infinitum. We still want the + * numbers to go up monotonically, hence we just add a random + * value 1..10 */ + + random_bytes(&a, sizeof(a)); + u += 1 + a % 10; + + if (asprintf(&h, "%.*s%" PRIu64, (int) (p - m->llmnr_hostname), m->llmnr_hostname, u) < 0) + return -ENOMEM; + + r = dns_name_concat(h, "local", &k); + if (r < 0) { + free(h); + return r; + } + + log_info("Hostname conflict, changing published hostname from '%s' to '%s'.", m->llmnr_hostname, h); + + free(m->llmnr_hostname); + m->llmnr_hostname = h; + + free(m->mdns_hostname); + m->mdns_hostname = k; + + manager_refresh_rrs(m); + + return 0; +} + +LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr) { + Iterator i; + Link *l; + + assert(m); + + HASHMAP_FOREACH(l, m->links, i) { + LinkAddress *a; + + a = link_find_address(l, family, in_addr); + if (a) + return a; + } + + return NULL; +} + +bool manager_our_packet(Manager *m, DnsPacket *p) { + assert(m); + assert(p); + + return !!manager_find_link_address(m, p->family, &p->sender); +} + +DnsScope* manager_find_scope(Manager *m, DnsPacket *p) { + Link *l; + + assert(m); + assert(p); + + l = hashmap_get(m->links, INT_TO_PTR(p->ifindex)); + if (!l) + return NULL; + + switch (p->protocol) { + case DNS_PROTOCOL_LLMNR: + if (p->family == AF_INET) + return l->llmnr_ipv4_scope; + else if (p->family == AF_INET6) + return l->llmnr_ipv6_scope; + + break; + + case DNS_PROTOCOL_MDNS: + if (p->family == AF_INET) + return l->mdns_ipv4_scope; + else if (p->family == AF_INET6) + return l->mdns_ipv6_scope; + + break; + + default: + break; + } + + return NULL; +} + +void manager_verify_all(Manager *m) { + DnsScope *s; + + assert(m); + + LIST_FOREACH(scopes, s, m->dns_scopes) + dns_zone_verify_all(&s->zone); +} + +int manager_is_own_hostname(Manager *m, const char *name) { + int r; + + assert(m); + assert(name); + + if (m->llmnr_hostname) { + r = dns_name_equal(name, m->llmnr_hostname); + if (r != 0) + return r; + } + + if (m->mdns_hostname) + return dns_name_equal(name, m->mdns_hostname); + + return 0; +} + +int manager_compile_dns_servers(Manager *m, OrderedSet **dns) { + DnsServer *s; + Iterator i; + Link *l; + int r; + + assert(m); + assert(dns); + + r = ordered_set_ensure_allocated(dns, &dns_server_hash_ops); + if (r < 0) + return r; + + /* First add the system-wide servers and domains */ + LIST_FOREACH(servers, s, m->dns_servers) { + r = ordered_set_put(*dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + + /* Then, add the per-link servers */ + HASHMAP_FOREACH(l, m->links, i) { + LIST_FOREACH(servers, s, l->dns_servers) { + r = ordered_set_put(*dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + /* If we found nothing, add the fallback servers */ + if (ordered_set_isempty(*dns)) { + LIST_FOREACH(servers, s, m->fallback_dns_servers) { + r = ordered_set_put(*dns, s); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + return 0; +} + +int manager_compile_search_domains(Manager *m, OrderedSet **domains) { + DnsSearchDomain *d; + Iterator i; + Link *l; + int r; + + assert(m); + assert(domains); + + r = ordered_set_ensure_allocated(domains, &dns_name_hash_ops); + if (r < 0) + return r; + + LIST_FOREACH(domains, d, m->search_domains) { + r = ordered_set_put(*domains, d->name); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + + HASHMAP_FOREACH(l, m->links, i) { + + LIST_FOREACH(domains, d, l->search_domains) { + r = ordered_set_put(*domains, d->name); + if (r == -EEXIST) + continue; + if (r < 0) + return r; + } + } + + return 0; +} + +DnssecMode manager_get_dnssec_mode(Manager *m) { + assert(m); + + if (m->dnssec_mode != _DNSSEC_MODE_INVALID) + return m->dnssec_mode; + + return DNSSEC_NO; +} + +bool manager_dnssec_supported(Manager *m) { + DnsServer *server; + Iterator i; + Link *l; + + assert(m); + + if (manager_get_dnssec_mode(m) == DNSSEC_NO) + return false; + + server = manager_get_dns_server(m); + if (server && !dns_server_dnssec_supported(server)) + return false; + + HASHMAP_FOREACH(l, m->links, i) + if (!link_dnssec_supported(l)) + return false; + + return true; +} + +void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key) { + + assert(verdict >= 0); + assert(verdict < _DNSSEC_VERDICT_MAX); + + if (log_get_max_level() >= LOG_DEBUG) { + _cleanup_free_ char *s = NULL; + + (void) dns_resource_key_to_string(key, &s); + + log_debug("Found verdict for lookup %s: %s", s ? strstrip(s) : "n/a", dnssec_verdict_to_string(verdict)); + } + + m->n_dnssec_verdict[verdict]++; +} + +bool manager_routable(Manager *m, int family) { + Iterator i; + Link *l; + + assert(m); + + /* Returns true if the host has at least one interface with a routable address of the specified type */ + + HASHMAP_FOREACH(l, m->links, i) + if (link_relevant(l, family, false)) + return true; + + return false; +} diff --git a/src/resolve/src/resolved-manager.h b/src/resolve/src/resolved-manager.h new file mode 100644 index 0000000000..8bef2d2b28 --- /dev/null +++ b/src/resolve/src/resolved-manager.h @@ -0,0 +1,171 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include +#include +#include + +#include "hashmap.h" +#include "list.h" +#include "ordered-set.h" +#include "resolve-util.h" + +typedef struct Manager Manager; + +#include "resolved-dns-query.h" +#include "resolved-dns-search-domain.h" +#include "resolved-dns-server.h" +#include "resolved-dns-stream.h" +#include "resolved-dns-trust-anchor.h" +#include "resolved-link.h" + +#define MANAGER_SEARCH_DOMAINS_MAX 32 +#define MANAGER_DNS_SERVERS_MAX 32 + +struct Manager { + sd_event *event; + + ResolveSupport llmnr_support; + ResolveSupport mdns_support; + DnssecMode dnssec_mode; + + /* Network */ + Hashmap *links; + + sd_netlink *rtnl; + sd_event_source *rtnl_event_source; + + sd_network_monitor *network_monitor; + sd_event_source *network_event_source; + + /* DNS query management */ + Hashmap *dns_transactions; + LIST_HEAD(DnsQuery, dns_queries); + unsigned n_dns_queries; + + LIST_HEAD(DnsStream, dns_streams); + unsigned n_dns_streams; + + /* Unicast dns */ + LIST_HEAD(DnsServer, dns_servers); + LIST_HEAD(DnsServer, fallback_dns_servers); + unsigned n_dns_servers; /* counts both main and fallback */ + DnsServer *current_dns_server; + + LIST_HEAD(DnsSearchDomain, search_domains); + unsigned n_search_domains; + bool permit_domain_search; + + bool need_builtin_fallbacks:1; + + bool read_resolv_conf:1; + usec_t resolv_conf_mtime; + + DnsTrustAnchor trust_anchor; + + LIST_HEAD(DnsScope, dns_scopes); + DnsScope *unicast_scope; + + /* LLMNR */ + int llmnr_ipv4_udp_fd; + int llmnr_ipv6_udp_fd; + int llmnr_ipv4_tcp_fd; + int llmnr_ipv6_tcp_fd; + + sd_event_source *llmnr_ipv4_udp_event_source; + sd_event_source *llmnr_ipv6_udp_event_source; + sd_event_source *llmnr_ipv4_tcp_event_source; + sd_event_source *llmnr_ipv6_tcp_event_source; + + /* mDNS */ + int mdns_ipv4_fd; + int mdns_ipv6_fd; + + sd_event_source *mdns_ipv4_event_source; + sd_event_source *mdns_ipv6_event_source; + + /* dbus */ + sd_bus *bus; + sd_event_source *bus_retry_event_source; + + /* The hostname we publish on LLMNR and mDNS */ + char *llmnr_hostname; + char *mdns_hostname; + DnsResourceKey *llmnr_host_ipv4_key; + DnsResourceKey *llmnr_host_ipv6_key; + + /* Watch the system hostname */ + int hostname_fd; + sd_event_source *hostname_event_source; + + /* Watch for system suspends */ + sd_bus_slot *prepare_for_sleep_slot; + + sd_event_source *sigusr1_event_source; + + unsigned n_transactions_total; + unsigned n_dnssec_verdict[_DNSSEC_VERDICT_MAX]; + + /* Data from /etc/hosts */ + Set* etc_hosts_by_address; + Hashmap* etc_hosts_by_name; + usec_t etc_hosts_last, etc_hosts_mtime; +}; + +/* Manager */ + +int manager_new(Manager **ret); +Manager* manager_free(Manager *m); + +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_recv(Manager *m, int fd, DnsProtocol protocol, DnsPacket **ret); + +int manager_find_ifindex(Manager *m, int family, const union in_addr_union *in_addr); +LinkAddress* manager_find_link_address(Manager *m, int family, const union in_addr_union *in_addr); + +void manager_refresh_rrs(Manager *m); +int manager_next_hostname(Manager *m); + +bool manager_our_packet(Manager *m, DnsPacket *p); +DnsScope* manager_find_scope(Manager *m, DnsPacket *p); + +void manager_verify_all(Manager *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); + +#define EXTRA_CMSG_SPACE 1024 + +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); + +DnssecMode manager_get_dnssec_mode(Manager *m); +bool manager_dnssec_supported(Manager *m); + +void manager_dnssec_verdict(Manager *m, DnssecVerdict verdict, const DnsResourceKey *key); + +bool manager_routable(Manager *m, int family); diff --git a/src/resolve/src/resolved-mdns.c b/src/resolve/src/resolved-mdns.c new file mode 100644 index 0000000000..bc8b8b809b --- /dev/null +++ b/src/resolve/src/resolved-mdns.c @@ -0,0 +1,287 @@ +/*** + This file is part of systemd. + + Copyright 2015 Daniel Mack + + 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 . + ***/ + +#include +#include +#include + +#include "fd-util.h" +#include "resolved-manager.h" +#include "resolved-mdns.h" + +void manager_mdns_stop(Manager *m) { + assert(m); + + m->mdns_ipv4_event_source = sd_event_source_unref(m->mdns_ipv4_event_source); + m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); + + m->mdns_ipv6_event_source = sd_event_source_unref(m->mdns_ipv6_event_source); + m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); +} + +int manager_mdns_start(Manager *m) { + int r; + + assert(m); + + if (m->mdns_support == RESOLVE_SUPPORT_NO) + return 0; + + r = manager_mdns_ipv4_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + + if (socket_ipv6_is_supported()) { + r = manager_mdns_ipv6_fd(m); + if (r == -EADDRINUSE) + goto eaddrinuse; + if (r < 0) + return r; + } + + return 0; + +eaddrinuse: + log_warning("There appears to be another mDNS responder running. Turning off mDNS support."); + m->mdns_support = RESOLVE_SUPPORT_NO; + manager_mdns_stop(m); + + return 0; +} + +static int on_mdns_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(dns_packet_unrefp) DnsPacket *p = NULL; + Manager *m = userdata; + DnsScope *scope; + int r; + + r = manager_recv(m, fd, DNS_PROTOCOL_MDNS, &p); + if (r <= 0) + return r; + + scope = manager_find_scope(m, p); + if (!scope) { + log_warning("Got mDNS UDP packet on unknown scope. Ignoring."); + return 0; + } + + if (dns_packet_validate_reply(p) > 0) { + DnsResourceRecord *rr; + + log_debug("Got mDNS reply packet"); + + /* + * mDNS is different from regular DNS and LLMNR with regard to handling responses. + * While on other protocols, we can ignore every answer that doesn't match a question + * we broadcast earlier, RFC6762, section 18.1 recommends looking at and caching all + * incoming information, regardless of the DNS packet ID. + * + * Hence, extract the packet here, and try to find a transaction for answer the we got + * and complete it. Also store the new information in scope's cache. + */ + r = dns_packet_extract(p); + if (r < 0) { + log_debug("mDNS packet extraction failed."); + return 0; + } + + dns_scope_check_conflicts(scope, p); + + DNS_ANSWER_FOREACH(rr, p->answer) { + const char *name = DNS_RESOURCE_KEY_NAME(rr->key); + DnsTransaction *t; + + /* If the received reply packet contains ANY record that is not .local or .in-addr.arpa, + * we assume someone's playing tricks on us and discard the packet completely. */ + if (!(dns_name_endswith(name, "in-addr.arpa") > 0 || + dns_name_endswith(name, "local") > 0)) + return 0; + + t = dns_scope_find_transaction(scope, rr->key, false); + if (t) + dns_transaction_process_reply(t, p); + } + + dns_cache_put(&scope->cache, NULL, DNS_PACKET_RCODE(p), p->answer, false, (uint32_t) -1, 0, p->family, &p->sender); + + } else if (dns_packet_validate_query(p) > 0) { + log_debug("Got mDNS query packet for id %u", DNS_PACKET_ID(p)); + + dns_scope_process_query(scope, NULL, p); + } else + log_debug("Invalid mDNS UDP packet."); + + return 0; +} + +int manager_mdns_ipv4_fd(Manager *m) { + union sockaddr_union sa = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(MDNS_PORT), + }; + static const int one = 1, pmtu = IP_PMTUDISC_DONT, ttl = 255; + int r; + + assert(m); + + if (m->mdns_ipv4_fd >= 0) + return m->mdns_ipv4_fd; + + m->mdns_ipv4_fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->mdns_ipv4_fd < 0) + return -errno; + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_PKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_RECVTTL, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* Disable Don't-Fragment bit in the IP header */ + r = setsockopt(m->mdns_ipv4_fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->mdns_ipv4_fd, &sa.sa, sizeof(sa.in)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->mdns_ipv4_event_source, m->mdns_ipv4_fd, EPOLLIN, on_mdns_packet, m); + if (r < 0) + goto fail; + + return m->mdns_ipv4_fd; + +fail: + m->mdns_ipv4_fd = safe_close(m->mdns_ipv4_fd); + return r; +} + +int manager_mdns_ipv6_fd(Manager *m) { + union sockaddr_union sa = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(MDNS_PORT), + }; + static const int one = 1, ttl = 255; + int r; + + assert(m); + + if (m->mdns_ipv6_fd >= 0) + return m->mdns_ipv6_fd; + + m->mdns_ipv6_fd = socket(AF_INET6, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (m->mdns_ipv6_fd < 0) + return -errno; + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + /* RFC 4795, section 2.5 recommends setting the TTL of UDP packets to 255. */ + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_V6ONLY, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = setsockopt(m->mdns_ipv6_fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = bind(m->mdns_ipv6_fd, &sa.sa, sizeof(sa.in6)); + if (r < 0) { + r = -errno; + goto fail; + } + + r = sd_event_add_io(m->event, &m->mdns_ipv6_event_source, m->mdns_ipv6_fd, EPOLLIN, on_mdns_packet, m); + if (r < 0) + goto fail; + + return m->mdns_ipv6_fd; + +fail: + m->mdns_ipv6_fd = safe_close(m->mdns_ipv6_fd); + return r; +} diff --git a/src/resolve/src/resolved-mdns.h b/src/resolve/src/resolved-mdns.h new file mode 100644 index 0000000000..5d274648f4 --- /dev/null +++ b/src/resolve/src/resolved-mdns.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 Daniel Mack + + 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 . +***/ + +#include "resolved-manager.h" + +#define MDNS_PORT 5353 + +int manager_mdns_ipv4_fd(Manager *m); +int manager_mdns_ipv6_fd(Manager *m); + +void manager_mdns_stop(Manager *m); +int manager_mdns_start(Manager *m); diff --git a/src/resolve/src/resolved-resolv-conf.c b/src/resolve/src/resolved-resolv-conf.c new file mode 100644 index 0000000000..065427b690 --- /dev/null +++ b/src/resolve/src/resolved-resolv-conf.c @@ -0,0 +1,267 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . + ***/ + +#include + +#include "alloc-util.h" +#include "dns-domain.h" +#include "fd-util.h" +#include "fileio-label.h" +#include "fileio.h" +#include "ordered-set.h" +#include "resolved-conf.h" +#include "resolved-resolv-conf.h" +#include "string-util.h" +#include "strv.h" + +int manager_read_resolv_conf(Manager *m) { + _cleanup_fclose_ FILE *f = NULL; + struct stat st, own; + char line[LINE_MAX]; + usec_t t; + int r; + + assert(m); + + /* Reads the system /etc/resolv.conf, if it exists and is not + * symlinked to our own resolv.conf instance */ + + if (!m->read_resolv_conf) + return 0; + + r = stat("/etc/resolv.conf", &st); + if (r < 0) { + if (errno == ENOENT) + return 0; + + r = log_warning_errno(errno, "Failed to stat /etc/resolv.conf: %m"); + goto clear; + } + + /* Have we already seen the file? */ + t = timespec_load(&st.st_mtim); + if (t == m->resolv_conf_mtime) + return 0; + + /* Is it symlinked to our own file? */ + if (stat("/run/systemd/resolve/resolv.conf", &own) >= 0 && + st.st_dev == own.st_dev && + st.st_ino == own.st_ino) + return 0; + + f = fopen("/etc/resolv.conf", "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + r = log_warning_errno(errno, "Failed to open /etc/resolv.conf: %m"); + goto clear; + } + + if (fstat(fileno(f), &st) < 0) { + r = log_error_errno(errno, "Failed to stat open file: %m"); + goto clear; + } + + dns_server_mark_all(m->dns_servers); + dns_search_domain_mark_all(m->search_domains); + + FOREACH_LINE(line, f, r = -errno; goto clear) { + const char *a; + char *l; + + l = strstrip(line); + if (*l == '#' || *l == ';') + continue; + + a = first_word(l, "nameserver"); + if (a) { + r = manager_add_dns_server_by_string(m, DNS_SERVER_SYSTEM, a); + if (r < 0) + log_warning_errno(r, "Failed to parse DNS server address '%s', ignoring.", a); + + continue; + } + + a = first_word(l, "domain"); + if (!a) /* We treat "domain" lines, and "search" lines as equivalent, and add both to our list. */ + a = first_word(l, "search"); + if (a) { + r = manager_parse_search_domains_and_warn(m, a); + if (r < 0) + log_warning_errno(r, "Failed to parse search domain string '%s', ignoring.", a); + } + } + + m->resolv_conf_mtime = t; + + /* Flush out all servers and search domains that are still + * marked. Those are then ones that didn't appear in the new + * /etc/resolv.conf */ + dns_server_unlink_marked(m->dns_servers); + dns_search_domain_unlink_marked(m->search_domains); + + /* Whenever /etc/resolv.conf changes, start using the first + * DNS server of it. This is useful to deal with broken + * network managing implementations (like NetworkManager), + * that when connecting to a VPN place both the VPN DNS + * servers and the local ones in /etc/resolv.conf. Without + * resetting the DNS server to use back to the first entry we + * will continue to use the local one thus being unable to + * resolve VPN domains. */ + manager_set_dns_server(m, m->dns_servers); + + /* Unconditionally flush the cache when /etc/resolv.conf is + * modified, even if the data it contained was completely + * identical to the previous version we used. We do this + * because altering /etc/resolv.conf is typically done when + * the network configuration changes, and that should be + * enough to flush the global unicast DNS cache. */ + if (m->unicast_scope) + dns_cache_flush(&m->unicast_scope->cache); + + return 0; + +clear: + dns_server_unlink_all(m->dns_servers); + dns_search_domain_unlink_all(m->search_domains); + return r; +} + +static void write_resolv_conf_server(DnsServer *s, FILE *f, unsigned *count) { + assert(s); + assert(f); + assert(count); + + (void) dns_server_string(s); + + if (!s->server_string) { + log_warning("Our of memory, or invalid DNS address. Ignoring server."); + return; + } + + if (*count == MAXNS) + fputs("# Too many DNS servers configured, the following entries may be ignored.\n", f); + (*count) ++; + + fprintf(f, "nameserver %s\n", s->server_string); +} + +static void write_resolv_conf_search( + const char *domain, + FILE *f, + unsigned *count, + unsigned *length) { + + assert(domain); + assert(f); + assert(length); + + if (*count >= MAXDNSRCH || + *length + strlen(domain) > 256) { + if (*count == MAXDNSRCH) + fputs(" # Too many search domains configured, remaining ones ignored.", f); + if (*length <= 256) + fputs(" # Total length of all search domains is too long, remaining ones ignored.", f); + + return; + } + + (*length) += strlen(domain); + (*count) ++; + + fputc(' ', f); + fputs(domain, f); +} + +static int write_resolv_conf_contents(FILE *f, OrderedSet *dns, OrderedSet *domains) { + 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); + + if (ordered_set_isempty(dns)) + fputs("# No DNS servers known.\n", f); + else { + unsigned count = 0; + DnsServer *s; + + ORDERED_SET_FOREACH(s, dns, i) + write_resolv_conf_server(s, f, &count); + } + + if (!ordered_set_isempty(domains)) { + unsigned length = 0, count = 0; + char *domain; + + fputs("search", f); + ORDERED_SET_FOREACH(domain, domains, i) + write_resolv_conf_search(domain, f, &count, &length); + fputs("\n", f); + } + + return fflush_and_check(f); +} + +int manager_write_resolv_conf(Manager *m) { + + _cleanup_ordered_set_free_ OrderedSet *dns = NULL, *domains = NULL; + _cleanup_free_ char *temp_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(m); + + /* Read the system /etc/resolv.conf first */ + 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; + + r = manager_compile_search_domains(m, &domains); + if (r < 0) + return r; + + r = fopen_temporary_label(PRIVATE_RESOLV_CONF, PRIVATE_RESOLV_CONF, &f, &temp_path); + if (r < 0) + return r; + + fchmod(fileno(f), 0644); + + r = write_resolv_conf_contents(f, dns, domains); + if (r < 0) + goto fail; + + if (rename(temp_path, PRIVATE_RESOLV_CONF) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + (void) unlink(PRIVATE_RESOLV_CONF); + (void) unlink(temp_path); + return r; +} diff --git a/src/resolve/src/resolved-resolv-conf.h b/src/resolve/src/resolved-resolv-conf.h new file mode 100644 index 0000000000..75fa080e4c --- /dev/null +++ b/src/resolve/src/resolved-resolv-conf.h @@ -0,0 +1,27 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include "resolved-manager.h" + +#define PRIVATE_RESOLV_CONF "/run/systemd/resolve/resolv.conf" + +int manager_read_resolv_conf(Manager *m); +int manager_write_resolv_conf(Manager *m); diff --git a/src/resolve/src/resolved.c b/src/resolve/src/resolved.c new file mode 100644 index 0000000000..07e6bfb54a --- /dev/null +++ b/src/resolve/src/resolved.c @@ -0,0 +1,112 @@ +/*** + This file is part of systemd. + + Copyright 2014 Tom Gundersen + + 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 . +***/ + +#include +#include + +#include "capability-util.h" +#include "mkdir.h" +#include "resolved-conf.h" +#include "resolved-manager.h" +#include "resolved-resolv-conf.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "user-util.h" + +int main(int argc, char *argv[]) { + _cleanup_(manager_freep) Manager *m = NULL; + const char *user = "systemd-resolve"; + uid_t uid; + gid_t gid; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + umask(0022); + + r = mac_selinux_init(NULL); + if (r < 0) { + log_error_errno(r, "SELinux setup failed: %m"); + goto finish; + } + + r = get_user_creds(&user, &uid, &gid, NULL, NULL); + if (r < 0) { + log_error_errno(r, "Cannot resolve user name %s: %m", user); + goto finish; + } + + /* Always create the directory where resolv.conf will live */ + r = mkdir_safe_label("/run/systemd/resolve", 0755, uid, gid); + if (r < 0) { + log_error_errno(r, "Could not create runtime directory: %m"); + goto finish; + } + + r = drop_privileges(uid, gid, 0); + if (r < 0) + goto finish; + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, SIGUSR1, -1) >= 0); + + r = manager_new(&m); + if (r < 0) { + log_error_errno(r, "Could not create manager: %m"); + goto finish; + } + + r = manager_start(m); + if (r < 0) { + log_error_errno(r, "Failed to start manager: %m"); + 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"); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + r = sd_event_loop(m->event); + if (r < 0) { + log_error_errno(r, "Event loop failed: %m"); + goto finish; + } + + sd_event_get_exit_code(m->event, &r); + +finish: + sd_notify(false, + "STOPPING=1\n" + "STATUS=Shutting down..."); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/resolve/src/resolved.conf.in b/src/resolve/src/resolved.conf.in new file mode 100644 index 0000000000..efc9c6733a --- /dev/null +++ b/src/resolve/src/resolved.conf.in @@ -0,0 +1,19 @@ +# 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. +# +# Entries in this file show the compile time defaults. +# You can change settings by editing this file. +# Defaults can be restored by simply deleting this file. +# +# See resolved.conf(5) for details + +[Resolve] +#DNS= +#FallbackDNS=@DNS_SERVERS@ +#Domains= +#LLMNR=yes +#DNSSEC=no diff --git a/src/resolve/src/test-dnssec-complex.c b/src/resolve/src/test-dnssec-complex.c new file mode 100644 index 0000000000..568400ac77 --- /dev/null +++ b/src/resolve/src/test-dnssec-complex.c @@ -0,0 +1,236 @@ +/*** + 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 . +***/ + +#include + +#include + +#include "af-list.h" +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "dns-type.h" +#include "random-util.h" +#include "string-util.h" +#include "time-util.h" + +#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) + +static void prefix_random(const char *name, char **ret) { + uint64_t i, u; + char *m = NULL; + + u = 1 + (random_u64() & 3); + + for (i = 0; i < u; i++) { + _cleanup_free_ char *b = NULL; + char *x; + + assert_se(asprintf(&b, "x%" PRIu64 "x", random_u64())); + x = strjoin(b, ".", name, NULL); + assert_se(x); + + free(m); + m = x; + } + + *ret = m; + } + +static void test_rr_lookup(sd_bus *bus, const char *name, uint16_t type, const char *result) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *m = NULL; + int r; + + /* If the name starts with a dot, we prefix one to three random labels */ + if (startswith(name, ".")) { + prefix_random(name + 1, &m); + name = m; + } + + assert_se(sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveRecord") >= 0); + + assert_se(sd_bus_message_append(req, "isqqt", 0, name, DNS_CLASS_IN, type, UINT64_C(0)) >= 0); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + + if (r < 0) { + assert_se(result); + assert_se(sd_bus_error_has_name(&error, result)); + log_info("[OK] %s/%s resulted in <%s>.", name, dns_type_to_string(type), error.name); + } else { + assert_se(!result); + log_info("[OK] %s/%s succeeded.", name, dns_type_to_string(type)); + } +} + +static void test_hostname_lookup(sd_bus *bus, const char *name, int family, const char *result) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *m = NULL; + const char *af; + int r; + + af = family == AF_UNSPEC ? "AF_UNSPEC" : af_to_name(family); + + /* If the name starts with a dot, we prefix one to three random labels */ + if (startswith(name, ".")) { + prefix_random(name + 1, &m); + name = m; + } + + assert_se(sd_bus_message_new_method_call( + bus, + &req, + "org.freedesktop.resolve1", + "/org/freedesktop/resolve1", + "org.freedesktop.resolve1.Manager", + "ResolveHostname") >= 0); + + assert_se(sd_bus_message_append(req, "isit", 0, name, family, UINT64_C(0)) >= 0); + + r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); + + if (r < 0) { + assert_se(result); + assert_se(sd_bus_error_has_name(&error, result)); + log_info("[OK] %s/%s resulted in <%s>.", name, af, error.name); + } else { + assert_se(!result); + log_info("[OK] %s/%s succeeded.", name, af); + } + +} + +int main(int argc, char* argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + + /* Note that this is a manual test as it requires: + * + * Full network access + * A DNSSEC capable DNS server + * That zones contacted are still set up as they were when I wrote this. + */ + + assert_se(sd_bus_open_system(&bus) >= 0); + + /* Normally signed */ + test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, "www.eurid.eu", AF_UNSPEC, NULL); + + test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, "sigok.verteiltesysteme.net", AF_UNSPEC, NULL); + + /* Normally signed, NODATA */ + test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* Invalid signature */ + test_rr_lookup(bus, "sigfail.verteiltesysteme.net", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); + test_hostname_lookup(bus, "sigfail.verteiltesysteme.net", AF_INET, BUS_ERROR_DNSSEC_FAILED); + + /* Invalid signature, RSA, wildcard */ + test_rr_lookup(bus, ".wilda.rhybar.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); + test_hostname_lookup(bus, ".wilda.rhybar.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED); + + /* Invalid signature, ECDSA, wildcard */ + test_rr_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); + test_hostname_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED); + + /* NXDOMAIN in NSEC domain */ + test_rr_lookup(bus, "hhh.nasa.gov", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "hhh.nasa.gov", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); + + /* wildcard, NSEC zone */ + test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, ".wilda.nsec.0skar.cz", AF_INET, NULL); + + /* wildcard, NSEC zone, NODATA */ + test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* wildcard, NSEC3 zone */ + test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, ".wilda.0skar.cz", AF_INET, NULL); + + /* wildcard, NSEC3 zone, NODATA */ + test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* wildcard, NSEC zone, CNAME */ + test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_UNSPEC, NULL); + test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_INET, NULL); + + /* wildcard, NSEC zone, NODATA, CNAME */ + test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* wildcard, NSEC3 zone, CNAME */ + test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_A, NULL); + test_hostname_lookup(bus, ".wild.0skar.cz", AF_UNSPEC, NULL); + test_hostname_lookup(bus, ".wild.0skar.cz", AF_INET, NULL); + + /* wildcard, NSEC3 zone, NODATA, CNAME */ + test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); + + /* NODATA due to empty non-terminal in NSEC domain */ + test_rr_lookup(bus, "herndon.nasa.gov", DNS_TYPE_A, BUS_ERROR_NO_SUCH_RR); + test_hostname_lookup(bus, "herndon.nasa.gov", AF_UNSPEC, BUS_ERROR_NO_SUCH_RR); + test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET, BUS_ERROR_NO_SUCH_RR); + test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET6, BUS_ERROR_NO_SUCH_RR); + + /* NXDOMAIN in NSEC root zone: */ + test_rr_lookup(bus, "jasdhjas.kjkfgjhfjg", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); + + /* NXDOMAIN in NSEC3 .com zone: */ + test_rr_lookup(bus, "kjkfgjhfjgsdfdsfd.com", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); + + /* Unsigned A */ + test_rr_lookup(bus, "poettering.de", DNS_TYPE_A, NULL); + test_rr_lookup(bus, "poettering.de", DNS_TYPE_AAAA, NULL); + test_hostname_lookup(bus, "poettering.de", AF_UNSPEC, NULL); + test_hostname_lookup(bus, "poettering.de", AF_INET, NULL); + test_hostname_lookup(bus, "poettering.de", AF_INET6, NULL); + +#ifdef HAVE_LIBIDN + /* Unsigned A with IDNA conversion necessary */ + test_hostname_lookup(bus, "pöttering.de", AF_UNSPEC, NULL); + test_hostname_lookup(bus, "pöttering.de", AF_INET, NULL); + test_hostname_lookup(bus, "pöttering.de", AF_INET6, NULL); +#endif + + /* DNAME, pointing to NXDOMAIN */ + test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); + test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_RP, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); + test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); + + return 0; +} diff --git a/src/resolve/src/test-dnssec.c b/src/resolve/src/test-dnssec.c new file mode 100644 index 0000000000..a093d86a91 --- /dev/null +++ b/src/resolve/src/test-dnssec.c @@ -0,0 +1,336 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include +#include + +#include "alloc-util.h" +#include "resolved-dns-dnssec.h" +#include "resolved-dns-rr.h" +#include "string-util.h" +#include "hexdecoct.h" + +static void test_dnssec_verify_rrset2(void) { + + static const uint8_t signature_blob[] = { + 0x48, 0x45, 0xc8, 0x8b, 0xc0, 0x14, 0x92, 0xf5, 0x15, 0xc6, 0x84, 0x9d, 0x2f, 0xe3, 0x32, 0x11, + 0x7d, 0xf1, 0xe6, 0x87, 0xb9, 0x42, 0xd3, 0x8b, 0x9e, 0xaf, 0x92, 0x31, 0x0a, 0x53, 0xad, 0x8b, + 0xa7, 0x5c, 0x83, 0x39, 0x8c, 0x28, 0xac, 0xce, 0x6e, 0x9c, 0x18, 0xe3, 0x31, 0x16, 0x6e, 0xca, + 0x38, 0x31, 0xaf, 0xd9, 0x94, 0xf1, 0x84, 0xb1, 0xdf, 0x5a, 0xc2, 0x73, 0x22, 0xf6, 0xcb, 0xa2, + 0xe7, 0x8c, 0x77, 0x0c, 0x74, 0x2f, 0xc2, 0x13, 0xb0, 0x93, 0x51, 0xa9, 0x4f, 0xae, 0x0a, 0xda, + 0x45, 0xcc, 0xfd, 0x43, 0x99, 0x36, 0x9a, 0x0d, 0x21, 0xe0, 0xeb, 0x30, 0x65, 0xd4, 0xa0, 0x27, + 0x37, 0x3b, 0xe4, 0xc1, 0xc5, 0xa1, 0x2a, 0xd1, 0x76, 0xc4, 0x7e, 0x64, 0x0e, 0x5a, 0xa6, 0x50, + 0x24, 0xd5, 0x2c, 0xcc, 0x6d, 0xe5, 0x37, 0xea, 0xbd, 0x09, 0x34, 0xed, 0x24, 0x06, 0xa1, 0x22, + }; + + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0xc3, 0x7f, 0x1d, 0xd1, 0x1c, 0x97, 0xb1, 0x13, 0x34, 0x3a, 0x9a, 0xea, + 0xee, 0xd9, 0x5a, 0x11, 0x1b, 0x17, 0xc7, 0xe3, 0xd4, 0xda, 0x20, 0xbc, 0x5d, 0xba, 0x74, 0xe3, + 0x37, 0x99, 0xec, 0x25, 0xce, 0x93, 0x7f, 0xbd, 0x22, 0x73, 0x7e, 0x14, 0x71, 0xe0, 0x60, 0x07, + 0xd4, 0x39, 0x8b, 0x5e, 0xe9, 0xba, 0x25, 0xe8, 0x49, 0xe9, 0x34, 0xef, 0xfe, 0x04, 0x5c, 0xa5, + 0x27, 0xcd, 0xa9, 0xda, 0x70, 0x05, 0x21, 0xab, 0x15, 0x82, 0x24, 0xc3, 0x94, 0xf5, 0xd7, 0xb7, + 0xc4, 0x66, 0xcb, 0x32, 0x6e, 0x60, 0x2b, 0x55, 0x59, 0x28, 0x89, 0x8a, 0x72, 0xde, 0x88, 0x56, + 0x27, 0x95, 0xd9, 0xac, 0x88, 0x4f, 0x65, 0x2b, 0x68, 0xfc, 0xe6, 0x41, 0xc1, 0x1b, 0xef, 0x4e, + 0xd6, 0xc2, 0x0f, 0x64, 0x88, 0x95, 0x5e, 0xdd, 0x3a, 0x02, 0x07, 0x50, 0xa9, 0xda, 0xa4, 0x49, + 0x74, 0x62, 0xfe, 0xd7, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *nsec = NULL, *rrsig = NULL, *dnskey = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnssecResult result; + + nsec = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC, "nasa.gov"); + assert_se(nsec); + + nsec->nsec.next_domain_name = strdup("3D-Printing.nasa.gov"); + assert_se(nsec->nsec.next_domain_name); + + nsec->nsec.types = bitmap_new(); + assert_se(nsec->nsec.types); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_A) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NS) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_SOA) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_MX) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_TXT) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_RRSIG) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NSEC) >= 0); + assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_DNSKEY) >= 0); + assert_se(bitmap_set(nsec->nsec.types, 65534) >= 0); + + log_info("NSEC: %s", strna(dns_resource_record_to_string(nsec))); + + rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); + assert_se(rrsig); + + rrsig->rrsig.type_covered = DNS_TYPE_NSEC; + rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rrsig->rrsig.labels = 2; + rrsig->rrsig.original_ttl = 300; + rrsig->rrsig.expiration = 0x5689002f; + rrsig->rrsig.inception = 0x56617230; + rrsig->rrsig.key_tag = 30390; + rrsig->rrsig.signer = strdup("Nasa.Gov."); + assert_se(rrsig->rrsig.signer); + rrsig->rrsig.signature_size = sizeof(signature_blob); + rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); + assert_se(rrsig->rrsig.signature); + + log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); + + dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); + assert_se(dnskey); + + dnskey->dnskey.flags = 256; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; + dnskey->dnskey.key_size = sizeof(dnskey_blob); + dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); + assert_se(dnskey->dnskey.key); + + log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); + + assert_se(dnssec_key_match_rrsig(nsec->key, rrsig) > 0); + assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); + + answer = dns_answer_new(1); + assert_se(answer); + assert_se(dns_answer_add(answer, nsec, 0, DNS_ANSWER_AUTHENTICATED) >= 0); + + /* Validate the RR as it if was 2015-12-11 today */ + assert_se(dnssec_verify_rrset(answer, nsec->key, rrsig, dnskey, 1449849318*USEC_PER_SEC, &result) >= 0); + assert_se(result == DNSSEC_VALIDATED); +} + +static void test_dnssec_verify_rrset(void) { + + static const uint8_t signature_blob[] = { + 0x7f, 0x79, 0xdd, 0x5e, 0x89, 0x79, 0x18, 0xd0, 0x34, 0x86, 0x8c, 0x72, 0x77, 0x75, 0x48, 0x4d, + 0xc3, 0x7d, 0x38, 0x04, 0xab, 0xcd, 0x9e, 0x4c, 0x82, 0xb0, 0x92, 0xca, 0xe9, 0x66, 0xe9, 0x6e, + 0x47, 0xc7, 0x68, 0x8c, 0x94, 0xf6, 0x69, 0xcb, 0x75, 0x94, 0xe6, 0x30, 0xa6, 0xfb, 0x68, 0x64, + 0x96, 0x1a, 0x84, 0xe1, 0xdc, 0x16, 0x4c, 0x83, 0x6c, 0x44, 0xf2, 0x74, 0x4d, 0x74, 0x79, 0x8f, + 0xf3, 0xf4, 0x63, 0x0d, 0xef, 0x5a, 0xe7, 0xe2, 0xfd, 0xf2, 0x2b, 0x38, 0x7c, 0x28, 0x96, 0x9d, + 0xb6, 0xcd, 0x5c, 0x3b, 0x57, 0xe2, 0x24, 0x78, 0x65, 0xd0, 0x9e, 0x77, 0x83, 0x09, 0x6c, 0xff, + 0x3d, 0x52, 0x3f, 0x6e, 0xd1, 0xed, 0x2e, 0xf9, 0xee, 0x8e, 0xa6, 0xbe, 0x9a, 0xa8, 0x87, 0x76, + 0xd8, 0x77, 0xcc, 0x96, 0xa0, 0x98, 0xa1, 0xd1, 0x68, 0x09, 0x43, 0xcf, 0x56, 0xd9, 0xd1, 0x66, + }; + + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0x9b, 0x49, 0x9b, 0xc1, 0xf9, 0x9a, 0xe0, 0x4e, 0xcf, 0xcb, 0x14, 0x45, + 0x2e, 0xc9, 0xf9, 0x74, 0xa7, 0x18, 0xb5, 0xf3, 0xde, 0x39, 0x49, 0xdf, 0x63, 0x33, 0x97, 0x52, + 0xe0, 0x8e, 0xac, 0x50, 0x30, 0x8e, 0x09, 0xd5, 0x24, 0x3d, 0x26, 0xa4, 0x49, 0x37, 0x2b, 0xb0, + 0x6b, 0x1b, 0xdf, 0xde, 0x85, 0x83, 0xcb, 0x22, 0x4e, 0x60, 0x0a, 0x91, 0x1a, 0x1f, 0xc5, 0x40, + 0xb1, 0xc3, 0x15, 0xc1, 0x54, 0x77, 0x86, 0x65, 0x53, 0xec, 0x10, 0x90, 0x0c, 0x91, 0x00, 0x5e, + 0x15, 0xdc, 0x08, 0x02, 0x4c, 0x8c, 0x0d, 0xc0, 0xac, 0x6e, 0xc4, 0x3e, 0x1b, 0x80, 0x19, 0xe4, + 0xf7, 0x5f, 0x77, 0x51, 0x06, 0x87, 0x61, 0xde, 0xa2, 0x18, 0x0f, 0x40, 0x8b, 0x79, 0x72, 0xfa, + 0x8d, 0x1a, 0x44, 0x47, 0x0d, 0x8e, 0x3a, 0x2d, 0xc7, 0x39, 0xbf, 0x56, 0x28, 0x97, 0xd9, 0x20, + 0x4f, 0x00, 0x51, 0x3b, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL; + _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; + DnssecResult result; + + a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov"); + assert_se(a); + + a->a.in_addr.s_addr = inet_addr("52.0.14.116"); + + log_info("A: %s", strna(dns_resource_record_to_string(a))); + + rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); + assert_se(rrsig); + + rrsig->rrsig.type_covered = DNS_TYPE_A; + rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; + rrsig->rrsig.labels = 2; + rrsig->rrsig.original_ttl = 600; + rrsig->rrsig.expiration = 0x5683135c; + rrsig->rrsig.inception = 0x565b7da8; + rrsig->rrsig.key_tag = 63876; + rrsig->rrsig.signer = strdup("Nasa.Gov."); + assert_se(rrsig->rrsig.signer); + rrsig->rrsig.signature_size = sizeof(signature_blob); + rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); + assert_se(rrsig->rrsig.signature); + + log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); + + dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); + assert_se(dnskey); + + dnskey->dnskey.flags = 256; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; + dnskey->dnskey.key_size = sizeof(dnskey_blob); + dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); + assert_se(dnskey->dnskey.key); + + log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); + + assert_se(dnssec_key_match_rrsig(a->key, rrsig) > 0); + assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); + + answer = dns_answer_new(1); + assert_se(answer); + assert_se(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED) >= 0); + + /* Validate the RR as it if was 2015-12-2 today */ + assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC, &result) >= 0); + assert_se(result == DNSSEC_VALIDATED); +} + +static void test_dnssec_verify_dns_key(void) { + + static const uint8_t ds1_fprint[] = { + 0x46, 0x8B, 0xC8, 0xDD, 0xC7, 0xE8, 0x27, 0x03, 0x40, 0xBB, 0x8A, 0x1F, 0x3B, 0x2E, 0x45, 0x9D, + 0x80, 0x67, 0x14, 0x01, + }; + static const uint8_t ds2_fprint[] = { + 0x8A, 0xEE, 0x80, 0x47, 0x05, 0x5F, 0x83, 0xD1, 0x48, 0xBA, 0x8F, 0xF6, 0xDD, 0xA7, 0x60, 0xCE, + 0x94, 0xF7, 0xC7, 0x5E, 0x52, 0x4C, 0xF2, 0xE9, 0x50, 0xB9, 0x2E, 0xCB, 0xEF, 0x96, 0xB9, 0x98, + }; + static const uint8_t dnskey_blob[] = { + 0x03, 0x01, 0x00, 0x01, 0xa8, 0x12, 0xda, 0x4f, 0xd2, 0x7d, 0x54, 0x14, 0x0e, 0xcc, 0x5b, 0x5e, + 0x45, 0x9c, 0x96, 0x98, 0xc0, 0xc0, 0x85, 0x81, 0xb1, 0x47, 0x8c, 0x7d, 0xe8, 0x39, 0x50, 0xcc, + 0xc5, 0xd0, 0xf2, 0x00, 0x81, 0x67, 0x79, 0xf6, 0xcc, 0x9d, 0xad, 0x6c, 0xbb, 0x7b, 0x6f, 0x48, + 0x97, 0x15, 0x1c, 0xfd, 0x0b, 0xfe, 0xd3, 0xd7, 0x7d, 0x9f, 0x81, 0x26, 0xd3, 0xc5, 0x65, 0x49, + 0xcf, 0x46, 0x62, 0xb0, 0x55, 0x6e, 0x47, 0xc7, 0x30, 0xef, 0x51, 0xfb, 0x3e, 0xc6, 0xef, 0xde, + 0x27, 0x3f, 0xfa, 0x57, 0x2d, 0xa7, 0x1d, 0x80, 0x46, 0x9a, 0x5f, 0x14, 0xb3, 0xb0, 0x2c, 0xbe, + 0x72, 0xca, 0xdf, 0xb2, 0xff, 0x36, 0x5b, 0x4f, 0xec, 0x58, 0x8e, 0x8d, 0x01, 0xe9, 0xa9, 0xdf, + 0xb5, 0x60, 0xad, 0x52, 0x4d, 0xfc, 0xa9, 0x3e, 0x8d, 0x35, 0x95, 0xb3, 0x4e, 0x0f, 0xca, 0x45, + 0x1b, 0xf7, 0xef, 0x3a, 0x88, 0x25, 0x08, 0xc7, 0x4e, 0x06, 0xc1, 0x62, 0x1a, 0xce, 0xd8, 0x77, + 0xbd, 0x02, 0x65, 0xf8, 0x49, 0xfb, 0xce, 0xf6, 0xa8, 0x09, 0xfc, 0xde, 0xb2, 0x09, 0x9d, 0x39, + 0xf8, 0x63, 0x9c, 0x32, 0x42, 0x7c, 0xa0, 0x30, 0x86, 0x72, 0x7a, 0x4a, 0xc6, 0xd4, 0xb3, 0x2d, + 0x24, 0xef, 0x96, 0x3f, 0xc2, 0xda, 0xd3, 0xf2, 0x15, 0x6f, 0xda, 0x65, 0x4b, 0x81, 0x28, 0x68, + 0xf4, 0xfe, 0x3e, 0x71, 0x4f, 0x50, 0x96, 0x72, 0x58, 0xa1, 0x89, 0xdd, 0x01, 0x61, 0x39, 0x39, + 0xc6, 0x76, 0xa4, 0xda, 0x02, 0x70, 0x3d, 0xc0, 0xdc, 0x8d, 0x70, 0x72, 0x04, 0x90, 0x79, 0xd4, + 0xec, 0x65, 0xcf, 0x49, 0x35, 0x25, 0x3a, 0x14, 0x1a, 0x45, 0x20, 0xeb, 0x31, 0xaf, 0x92, 0xba, + 0x20, 0xd3, 0xcd, 0xa7, 0x13, 0x44, 0xdc, 0xcf, 0xf0, 0x27, 0x34, 0xb9, 0xe7, 0x24, 0x6f, 0x73, + 0xe7, 0xea, 0x77, 0x03, + }; + + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds1 = NULL, *ds2 = NULL; + + /* The two DS RRs in effect for nasa.gov on 2015-12-01. */ + ds1 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "nasa.gov"); + assert_se(ds1); + + ds1->ds.key_tag = 47857; + ds1->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; + ds1->ds.digest_type = DNSSEC_DIGEST_SHA1; + ds1->ds.digest_size = sizeof(ds1_fprint); + ds1->ds.digest = memdup(ds1_fprint, ds1->ds.digest_size); + assert_se(ds1->ds.digest); + + log_info("DS1: %s", strna(dns_resource_record_to_string(ds1))); + + ds2 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "NASA.GOV"); + assert_se(ds2); + + ds2->ds.key_tag = 47857; + ds2->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; + ds2->ds.digest_type = DNSSEC_DIGEST_SHA256; + ds2->ds.digest_size = sizeof(ds2_fprint); + ds2->ds.digest = memdup(ds2_fprint, ds2->ds.digest_size); + assert_se(ds2->ds.digest); + + log_info("DS2: %s", strna(dns_resource_record_to_string(ds2))); + + dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nasa.GOV"); + assert_se(dnskey); + + dnskey->dnskey.flags = 257; + dnskey->dnskey.protocol = 3; + dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; + dnskey->dnskey.key_size = sizeof(dnskey_blob); + dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); + assert_se(dnskey->dnskey.key); + + log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); + log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); + + assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds1, false) > 0); + assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds2, false) > 0); +} + +static void test_dnssec_canonicalize_one(const char *original, const char *canonical, int r) { + char canonicalized[DNSSEC_CANONICAL_HOSTNAME_MAX]; + + assert_se(dnssec_canonicalize(original, canonicalized, sizeof(canonicalized)) == r); + if (r < 0) + return; + + assert_se(streq(canonicalized, canonical)); +} + +static void test_dnssec_canonicalize(void) { + test_dnssec_canonicalize_one("", ".", 1); + test_dnssec_canonicalize_one(".", ".", 1); + test_dnssec_canonicalize_one("foo", "foo.", 4); + test_dnssec_canonicalize_one("foo.", "foo.", 4); + test_dnssec_canonicalize_one("FOO.", "foo.", 4); + test_dnssec_canonicalize_one("FOO.bar.", "foo.bar.", 8); + test_dnssec_canonicalize_one("FOO..bar.", NULL, -EINVAL); +} + +static void test_dnssec_nsec3_hash(void) { + static const uint8_t salt[] = { 0xB0, 0x1D, 0xFA, 0xCE }; + static const uint8_t next_hashed_name[] = { 0x84, 0x10, 0x26, 0x53, 0xc9, 0xfa, 0x4d, 0x85, 0x6c, 0x97, 0x82, 0xe2, 0x8f, 0xdf, 0x2d, 0x5e, 0x87, 0x69, 0xc4, 0x52 }; + _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; + uint8_t h[DNSSEC_HASH_SIZE_MAX]; + _cleanup_free_ char *b = NULL; + int k; + + /* The NSEC3 RR for eurid.eu on 2015-12-14. */ + rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC3, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM.eurid.eu."); + assert_se(rr); + + rr->nsec3.algorithm = DNSSEC_DIGEST_SHA1; + rr->nsec3.flags = 1; + rr->nsec3.iterations = 1; + rr->nsec3.salt = memdup(salt, sizeof(salt)); + assert_se(rr->nsec3.salt); + rr->nsec3.salt_size = sizeof(salt); + rr->nsec3.next_hashed_name = memdup(next_hashed_name, sizeof(next_hashed_name)); + assert_se(rr->nsec3.next_hashed_name); + rr->nsec3.next_hashed_name_size = sizeof(next_hashed_name); + + log_info("NSEC3: %s", strna(dns_resource_record_to_string(rr))); + + k = dnssec_nsec3_hash(rr, "eurid.eu", &h); + assert_se(k >= 0); + + b = base32hexmem(h, k, false); + assert_se(b); + assert_se(strcasecmp(b, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM") == 0); +} + +int main(int argc, char*argv[]) { + + test_dnssec_canonicalize(); + test_dnssec_verify_dns_key(); + test_dnssec_verify_rrset(); + test_dnssec_verify_rrset2(); + test_dnssec_nsec3_hash(); + + return 0; +} diff --git a/src/resolve/src/test-resolve-tables.c b/src/resolve/src/test-resolve-tables.c new file mode 100644 index 0000000000..63660afc87 --- /dev/null +++ b/src/resolve/src/test-resolve-tables.c @@ -0,0 +1,27 @@ +/*** + This file is part of systemd + + Copyright 2013 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 . +***/ + +#include "dns-type.h" +#include "test-tables.h" + +int main(int argc, char **argv) { + test_table_sparse(dns_type, DNS_TYPE); + + return EXIT_SUCCESS; +} diff --git a/src/resolve/test-dnssec-complex.c b/src/resolve/test-dnssec-complex.c deleted file mode 100644 index 58c089eb40..0000000000 --- a/src/resolve/test-dnssec-complex.c +++ /dev/null @@ -1,236 +0,0 @@ -/*** - 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 . -***/ - -#include - -#include "sd-bus.h" - -#include "af-list.h" -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "dns-type.h" -#include "random-util.h" -#include "string-util.h" -#include "time-util.h" - -#define DNS_CALL_TIMEOUT_USEC (45*USEC_PER_SEC) - -static void prefix_random(const char *name, char **ret) { - uint64_t i, u; - char *m = NULL; - - u = 1 + (random_u64() & 3); - - for (i = 0; i < u; i++) { - _cleanup_free_ char *b = NULL; - char *x; - - assert_se(asprintf(&b, "x%" PRIu64 "x", random_u64())); - x = strjoin(b, ".", name, NULL); - assert_se(x); - - free(m); - m = x; - } - - *ret = m; - } - -static void test_rr_lookup(sd_bus *bus, const char *name, uint16_t type, const char *result) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *m = NULL; - int r; - - /* If the name starts with a dot, we prefix one to three random labels */ - if (startswith(name, ".")) { - prefix_random(name + 1, &m); - name = m; - } - - assert_se(sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveRecord") >= 0); - - assert_se(sd_bus_message_append(req, "isqqt", 0, name, DNS_CLASS_IN, type, UINT64_C(0)) >= 0); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - - if (r < 0) { - assert_se(result); - assert_se(sd_bus_error_has_name(&error, result)); - log_info("[OK] %s/%s resulted in <%s>.", name, dns_type_to_string(type), error.name); - } else { - assert_se(!result); - log_info("[OK] %s/%s succeeded.", name, dns_type_to_string(type)); - } -} - -static void test_hostname_lookup(sd_bus *bus, const char *name, int family, const char *result) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *req = NULL, *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *m = NULL; - const char *af; - int r; - - af = family == AF_UNSPEC ? "AF_UNSPEC" : af_to_name(family); - - /* If the name starts with a dot, we prefix one to three random labels */ - if (startswith(name, ".")) { - prefix_random(name + 1, &m); - name = m; - } - - assert_se(sd_bus_message_new_method_call( - bus, - &req, - "org.freedesktop.resolve1", - "/org/freedesktop/resolve1", - "org.freedesktop.resolve1.Manager", - "ResolveHostname") >= 0); - - assert_se(sd_bus_message_append(req, "isit", 0, name, family, UINT64_C(0)) >= 0); - - r = sd_bus_call(bus, req, DNS_CALL_TIMEOUT_USEC, &error, &reply); - - if (r < 0) { - assert_se(result); - assert_se(sd_bus_error_has_name(&error, result)); - log_info("[OK] %s/%s resulted in <%s>.", name, af, error.name); - } else { - assert_se(!result); - log_info("[OK] %s/%s succeeded.", name, af); - } - -} - -int main(int argc, char* argv[]) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - - /* Note that this is a manual test as it requires: - * - * Full network access - * A DNSSEC capable DNS server - * That zones contacted are still set up as they were when I wrote this. - */ - - assert_se(sd_bus_open_system(&bus) >= 0); - - /* Normally signed */ - test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, "www.eurid.eu", AF_UNSPEC, NULL); - - test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, "sigok.verteiltesysteme.net", AF_UNSPEC, NULL); - - /* Normally signed, NODATA */ - test_rr_lookup(bus, "www.eurid.eu", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - test_rr_lookup(bus, "sigok.verteiltesysteme.net", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* Invalid signature */ - test_rr_lookup(bus, "sigfail.verteiltesysteme.net", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); - test_hostname_lookup(bus, "sigfail.verteiltesysteme.net", AF_INET, BUS_ERROR_DNSSEC_FAILED); - - /* Invalid signature, RSA, wildcard */ - test_rr_lookup(bus, ".wilda.rhybar.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); - test_hostname_lookup(bus, ".wilda.rhybar.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED); - - /* Invalid signature, ECDSA, wildcard */ - test_rr_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", DNS_TYPE_A, BUS_ERROR_DNSSEC_FAILED); - test_hostname_lookup(bus, ".wilda.rhybar.ecdsa.0skar.cz", AF_INET, BUS_ERROR_DNSSEC_FAILED); - - /* NXDOMAIN in NSEC domain */ - test_rr_lookup(bus, "hhh.nasa.gov", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "hhh.nasa.gov", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); - - /* wildcard, NSEC zone */ - test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, ".wilda.nsec.0skar.cz", AF_INET, NULL); - - /* wildcard, NSEC zone, NODATA */ - test_rr_lookup(bus, ".wilda.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* wildcard, NSEC3 zone */ - test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, ".wilda.0skar.cz", AF_INET, NULL); - - /* wildcard, NSEC3 zone, NODATA */ - test_rr_lookup(bus, ".wilda.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* wildcard, NSEC zone, CNAME */ - test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_UNSPEC, NULL); - test_hostname_lookup(bus, ".wild.nsec.0skar.cz", AF_INET, NULL); - - /* wildcard, NSEC zone, NODATA, CNAME */ - test_rr_lookup(bus, ".wild.nsec.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* wildcard, NSEC3 zone, CNAME */ - test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_A, NULL); - test_hostname_lookup(bus, ".wild.0skar.cz", AF_UNSPEC, NULL); - test_hostname_lookup(bus, ".wild.0skar.cz", AF_INET, NULL); - - /* wildcard, NSEC3 zone, NODATA, CNAME */ - test_rr_lookup(bus, ".wild.0skar.cz", DNS_TYPE_RP, BUS_ERROR_NO_SUCH_RR); - - /* NODATA due to empty non-terminal in NSEC domain */ - test_rr_lookup(bus, "herndon.nasa.gov", DNS_TYPE_A, BUS_ERROR_NO_SUCH_RR); - test_hostname_lookup(bus, "herndon.nasa.gov", AF_UNSPEC, BUS_ERROR_NO_SUCH_RR); - test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET, BUS_ERROR_NO_SUCH_RR); - test_hostname_lookup(bus, "herndon.nasa.gov", AF_INET6, BUS_ERROR_NO_SUCH_RR); - - /* NXDOMAIN in NSEC root zone: */ - test_rr_lookup(bus, "jasdhjas.kjkfgjhfjg", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "jasdhjas.kjkfgjhfjg", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); - - /* NXDOMAIN in NSEC3 .com zone: */ - test_rr_lookup(bus, "kjkfgjhfjgsdfdsfd.com", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, "kjkfgjhfjgsdfdsfd.com", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); - - /* Unsigned A */ - test_rr_lookup(bus, "poettering.de", DNS_TYPE_A, NULL); - test_rr_lookup(bus, "poettering.de", DNS_TYPE_AAAA, NULL); - test_hostname_lookup(bus, "poettering.de", AF_UNSPEC, NULL); - test_hostname_lookup(bus, "poettering.de", AF_INET, NULL); - test_hostname_lookup(bus, "poettering.de", AF_INET6, NULL); - -#ifdef HAVE_LIBIDN - /* Unsigned A with IDNA conversion necessary */ - test_hostname_lookup(bus, "pöttering.de", AF_UNSPEC, NULL); - test_hostname_lookup(bus, "pöttering.de", AF_INET, NULL); - test_hostname_lookup(bus, "pöttering.de", AF_INET6, NULL); -#endif - - /* DNAME, pointing to NXDOMAIN */ - test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_A, _BUS_ERROR_DNS "NXDOMAIN"); - test_rr_lookup(bus, ".ireallyhpoethisdoesnexist.xn--kprw13d.", DNS_TYPE_RP, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_UNSPEC, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET, _BUS_ERROR_DNS "NXDOMAIN"); - test_hostname_lookup(bus, ".ireallyhpoethisdoesntexist.xn--kprw13d.", AF_INET6, _BUS_ERROR_DNS "NXDOMAIN"); - - return 0; -} diff --git a/src/resolve/test-dnssec.c b/src/resolve/test-dnssec.c deleted file mode 100644 index a093d86a91..0000000000 --- a/src/resolve/test-dnssec.c +++ /dev/null @@ -1,336 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include -#include - -#include "alloc-util.h" -#include "resolved-dns-dnssec.h" -#include "resolved-dns-rr.h" -#include "string-util.h" -#include "hexdecoct.h" - -static void test_dnssec_verify_rrset2(void) { - - static const uint8_t signature_blob[] = { - 0x48, 0x45, 0xc8, 0x8b, 0xc0, 0x14, 0x92, 0xf5, 0x15, 0xc6, 0x84, 0x9d, 0x2f, 0xe3, 0x32, 0x11, - 0x7d, 0xf1, 0xe6, 0x87, 0xb9, 0x42, 0xd3, 0x8b, 0x9e, 0xaf, 0x92, 0x31, 0x0a, 0x53, 0xad, 0x8b, - 0xa7, 0x5c, 0x83, 0x39, 0x8c, 0x28, 0xac, 0xce, 0x6e, 0x9c, 0x18, 0xe3, 0x31, 0x16, 0x6e, 0xca, - 0x38, 0x31, 0xaf, 0xd9, 0x94, 0xf1, 0x84, 0xb1, 0xdf, 0x5a, 0xc2, 0x73, 0x22, 0xf6, 0xcb, 0xa2, - 0xe7, 0x8c, 0x77, 0x0c, 0x74, 0x2f, 0xc2, 0x13, 0xb0, 0x93, 0x51, 0xa9, 0x4f, 0xae, 0x0a, 0xda, - 0x45, 0xcc, 0xfd, 0x43, 0x99, 0x36, 0x9a, 0x0d, 0x21, 0xe0, 0xeb, 0x30, 0x65, 0xd4, 0xa0, 0x27, - 0x37, 0x3b, 0xe4, 0xc1, 0xc5, 0xa1, 0x2a, 0xd1, 0x76, 0xc4, 0x7e, 0x64, 0x0e, 0x5a, 0xa6, 0x50, - 0x24, 0xd5, 0x2c, 0xcc, 0x6d, 0xe5, 0x37, 0xea, 0xbd, 0x09, 0x34, 0xed, 0x24, 0x06, 0xa1, 0x22, - }; - - static const uint8_t dnskey_blob[] = { - 0x03, 0x01, 0x00, 0x01, 0xc3, 0x7f, 0x1d, 0xd1, 0x1c, 0x97, 0xb1, 0x13, 0x34, 0x3a, 0x9a, 0xea, - 0xee, 0xd9, 0x5a, 0x11, 0x1b, 0x17, 0xc7, 0xe3, 0xd4, 0xda, 0x20, 0xbc, 0x5d, 0xba, 0x74, 0xe3, - 0x37, 0x99, 0xec, 0x25, 0xce, 0x93, 0x7f, 0xbd, 0x22, 0x73, 0x7e, 0x14, 0x71, 0xe0, 0x60, 0x07, - 0xd4, 0x39, 0x8b, 0x5e, 0xe9, 0xba, 0x25, 0xe8, 0x49, 0xe9, 0x34, 0xef, 0xfe, 0x04, 0x5c, 0xa5, - 0x27, 0xcd, 0xa9, 0xda, 0x70, 0x05, 0x21, 0xab, 0x15, 0x82, 0x24, 0xc3, 0x94, 0xf5, 0xd7, 0xb7, - 0xc4, 0x66, 0xcb, 0x32, 0x6e, 0x60, 0x2b, 0x55, 0x59, 0x28, 0x89, 0x8a, 0x72, 0xde, 0x88, 0x56, - 0x27, 0x95, 0xd9, 0xac, 0x88, 0x4f, 0x65, 0x2b, 0x68, 0xfc, 0xe6, 0x41, 0xc1, 0x1b, 0xef, 0x4e, - 0xd6, 0xc2, 0x0f, 0x64, 0x88, 0x95, 0x5e, 0xdd, 0x3a, 0x02, 0x07, 0x50, 0xa9, 0xda, 0xa4, 0x49, - 0x74, 0x62, 0xfe, 0xd7, - }; - - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *nsec = NULL, *rrsig = NULL, *dnskey = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnssecResult result; - - nsec = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC, "nasa.gov"); - assert_se(nsec); - - nsec->nsec.next_domain_name = strdup("3D-Printing.nasa.gov"); - assert_se(nsec->nsec.next_domain_name); - - nsec->nsec.types = bitmap_new(); - assert_se(nsec->nsec.types); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_A) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NS) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_SOA) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_MX) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_TXT) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_RRSIG) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_NSEC) >= 0); - assert_se(bitmap_set(nsec->nsec.types, DNS_TYPE_DNSKEY) >= 0); - assert_se(bitmap_set(nsec->nsec.types, 65534) >= 0); - - log_info("NSEC: %s", strna(dns_resource_record_to_string(nsec))); - - rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); - assert_se(rrsig); - - rrsig->rrsig.type_covered = DNS_TYPE_NSEC; - rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; - rrsig->rrsig.labels = 2; - rrsig->rrsig.original_ttl = 300; - rrsig->rrsig.expiration = 0x5689002f; - rrsig->rrsig.inception = 0x56617230; - rrsig->rrsig.key_tag = 30390; - rrsig->rrsig.signer = strdup("Nasa.Gov."); - assert_se(rrsig->rrsig.signer); - rrsig->rrsig.signature_size = sizeof(signature_blob); - rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); - assert_se(rrsig->rrsig.signature); - - log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); - - dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); - assert_se(dnskey); - - dnskey->dnskey.flags = 256; - dnskey->dnskey.protocol = 3; - dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; - dnskey->dnskey.key_size = sizeof(dnskey_blob); - dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); - assert_se(dnskey->dnskey.key); - - log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); - - assert_se(dnssec_key_match_rrsig(nsec->key, rrsig) > 0); - assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); - - answer = dns_answer_new(1); - assert_se(answer); - assert_se(dns_answer_add(answer, nsec, 0, DNS_ANSWER_AUTHENTICATED) >= 0); - - /* Validate the RR as it if was 2015-12-11 today */ - assert_se(dnssec_verify_rrset(answer, nsec->key, rrsig, dnskey, 1449849318*USEC_PER_SEC, &result) >= 0); - assert_se(result == DNSSEC_VALIDATED); -} - -static void test_dnssec_verify_rrset(void) { - - static const uint8_t signature_blob[] = { - 0x7f, 0x79, 0xdd, 0x5e, 0x89, 0x79, 0x18, 0xd0, 0x34, 0x86, 0x8c, 0x72, 0x77, 0x75, 0x48, 0x4d, - 0xc3, 0x7d, 0x38, 0x04, 0xab, 0xcd, 0x9e, 0x4c, 0x82, 0xb0, 0x92, 0xca, 0xe9, 0x66, 0xe9, 0x6e, - 0x47, 0xc7, 0x68, 0x8c, 0x94, 0xf6, 0x69, 0xcb, 0x75, 0x94, 0xe6, 0x30, 0xa6, 0xfb, 0x68, 0x64, - 0x96, 0x1a, 0x84, 0xe1, 0xdc, 0x16, 0x4c, 0x83, 0x6c, 0x44, 0xf2, 0x74, 0x4d, 0x74, 0x79, 0x8f, - 0xf3, 0xf4, 0x63, 0x0d, 0xef, 0x5a, 0xe7, 0xe2, 0xfd, 0xf2, 0x2b, 0x38, 0x7c, 0x28, 0x96, 0x9d, - 0xb6, 0xcd, 0x5c, 0x3b, 0x57, 0xe2, 0x24, 0x78, 0x65, 0xd0, 0x9e, 0x77, 0x83, 0x09, 0x6c, 0xff, - 0x3d, 0x52, 0x3f, 0x6e, 0xd1, 0xed, 0x2e, 0xf9, 0xee, 0x8e, 0xa6, 0xbe, 0x9a, 0xa8, 0x87, 0x76, - 0xd8, 0x77, 0xcc, 0x96, 0xa0, 0x98, 0xa1, 0xd1, 0x68, 0x09, 0x43, 0xcf, 0x56, 0xd9, 0xd1, 0x66, - }; - - static const uint8_t dnskey_blob[] = { - 0x03, 0x01, 0x00, 0x01, 0x9b, 0x49, 0x9b, 0xc1, 0xf9, 0x9a, 0xe0, 0x4e, 0xcf, 0xcb, 0x14, 0x45, - 0x2e, 0xc9, 0xf9, 0x74, 0xa7, 0x18, 0xb5, 0xf3, 0xde, 0x39, 0x49, 0xdf, 0x63, 0x33, 0x97, 0x52, - 0xe0, 0x8e, 0xac, 0x50, 0x30, 0x8e, 0x09, 0xd5, 0x24, 0x3d, 0x26, 0xa4, 0x49, 0x37, 0x2b, 0xb0, - 0x6b, 0x1b, 0xdf, 0xde, 0x85, 0x83, 0xcb, 0x22, 0x4e, 0x60, 0x0a, 0x91, 0x1a, 0x1f, 0xc5, 0x40, - 0xb1, 0xc3, 0x15, 0xc1, 0x54, 0x77, 0x86, 0x65, 0x53, 0xec, 0x10, 0x90, 0x0c, 0x91, 0x00, 0x5e, - 0x15, 0xdc, 0x08, 0x02, 0x4c, 0x8c, 0x0d, 0xc0, 0xac, 0x6e, 0xc4, 0x3e, 0x1b, 0x80, 0x19, 0xe4, - 0xf7, 0x5f, 0x77, 0x51, 0x06, 0x87, 0x61, 0xde, 0xa2, 0x18, 0x0f, 0x40, 0x8b, 0x79, 0x72, 0xfa, - 0x8d, 0x1a, 0x44, 0x47, 0x0d, 0x8e, 0x3a, 0x2d, 0xc7, 0x39, 0xbf, 0x56, 0x28, 0x97, 0xd9, 0x20, - 0x4f, 0x00, 0x51, 0x3b, - }; - - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *a = NULL, *rrsig = NULL, *dnskey = NULL; - _cleanup_(dns_answer_unrefp) DnsAnswer *answer = NULL; - DnssecResult result; - - a = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_A, "nAsA.gov"); - assert_se(a); - - a->a.in_addr.s_addr = inet_addr("52.0.14.116"); - - log_info("A: %s", strna(dns_resource_record_to_string(a))); - - rrsig = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_RRSIG, "NaSa.GOV."); - assert_se(rrsig); - - rrsig->rrsig.type_covered = DNS_TYPE_A; - rrsig->rrsig.algorithm = DNSSEC_ALGORITHM_RSASHA256; - rrsig->rrsig.labels = 2; - rrsig->rrsig.original_ttl = 600; - rrsig->rrsig.expiration = 0x5683135c; - rrsig->rrsig.inception = 0x565b7da8; - rrsig->rrsig.key_tag = 63876; - rrsig->rrsig.signer = strdup("Nasa.Gov."); - assert_se(rrsig->rrsig.signer); - rrsig->rrsig.signature_size = sizeof(signature_blob); - rrsig->rrsig.signature = memdup(signature_blob, rrsig->rrsig.signature_size); - assert_se(rrsig->rrsig.signature); - - log_info("RRSIG: %s", strna(dns_resource_record_to_string(rrsig))); - - dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nASA.gOV"); - assert_se(dnskey); - - dnskey->dnskey.flags = 256; - dnskey->dnskey.protocol = 3; - dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; - dnskey->dnskey.key_size = sizeof(dnskey_blob); - dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); - assert_se(dnskey->dnskey.key); - - log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); - - assert_se(dnssec_key_match_rrsig(a->key, rrsig) > 0); - assert_se(dnssec_rrsig_match_dnskey(rrsig, dnskey, false) > 0); - - answer = dns_answer_new(1); - assert_se(answer); - assert_se(dns_answer_add(answer, a, 0, DNS_ANSWER_AUTHENTICATED) >= 0); - - /* Validate the RR as it if was 2015-12-2 today */ - assert_se(dnssec_verify_rrset(answer, a->key, rrsig, dnskey, 1449092754*USEC_PER_SEC, &result) >= 0); - assert_se(result == DNSSEC_VALIDATED); -} - -static void test_dnssec_verify_dns_key(void) { - - static const uint8_t ds1_fprint[] = { - 0x46, 0x8B, 0xC8, 0xDD, 0xC7, 0xE8, 0x27, 0x03, 0x40, 0xBB, 0x8A, 0x1F, 0x3B, 0x2E, 0x45, 0x9D, - 0x80, 0x67, 0x14, 0x01, - }; - static const uint8_t ds2_fprint[] = { - 0x8A, 0xEE, 0x80, 0x47, 0x05, 0x5F, 0x83, 0xD1, 0x48, 0xBA, 0x8F, 0xF6, 0xDD, 0xA7, 0x60, 0xCE, - 0x94, 0xF7, 0xC7, 0x5E, 0x52, 0x4C, 0xF2, 0xE9, 0x50, 0xB9, 0x2E, 0xCB, 0xEF, 0x96, 0xB9, 0x98, - }; - static const uint8_t dnskey_blob[] = { - 0x03, 0x01, 0x00, 0x01, 0xa8, 0x12, 0xda, 0x4f, 0xd2, 0x7d, 0x54, 0x14, 0x0e, 0xcc, 0x5b, 0x5e, - 0x45, 0x9c, 0x96, 0x98, 0xc0, 0xc0, 0x85, 0x81, 0xb1, 0x47, 0x8c, 0x7d, 0xe8, 0x39, 0x50, 0xcc, - 0xc5, 0xd0, 0xf2, 0x00, 0x81, 0x67, 0x79, 0xf6, 0xcc, 0x9d, 0xad, 0x6c, 0xbb, 0x7b, 0x6f, 0x48, - 0x97, 0x15, 0x1c, 0xfd, 0x0b, 0xfe, 0xd3, 0xd7, 0x7d, 0x9f, 0x81, 0x26, 0xd3, 0xc5, 0x65, 0x49, - 0xcf, 0x46, 0x62, 0xb0, 0x55, 0x6e, 0x47, 0xc7, 0x30, 0xef, 0x51, 0xfb, 0x3e, 0xc6, 0xef, 0xde, - 0x27, 0x3f, 0xfa, 0x57, 0x2d, 0xa7, 0x1d, 0x80, 0x46, 0x9a, 0x5f, 0x14, 0xb3, 0xb0, 0x2c, 0xbe, - 0x72, 0xca, 0xdf, 0xb2, 0xff, 0x36, 0x5b, 0x4f, 0xec, 0x58, 0x8e, 0x8d, 0x01, 0xe9, 0xa9, 0xdf, - 0xb5, 0x60, 0xad, 0x52, 0x4d, 0xfc, 0xa9, 0x3e, 0x8d, 0x35, 0x95, 0xb3, 0x4e, 0x0f, 0xca, 0x45, - 0x1b, 0xf7, 0xef, 0x3a, 0x88, 0x25, 0x08, 0xc7, 0x4e, 0x06, 0xc1, 0x62, 0x1a, 0xce, 0xd8, 0x77, - 0xbd, 0x02, 0x65, 0xf8, 0x49, 0xfb, 0xce, 0xf6, 0xa8, 0x09, 0xfc, 0xde, 0xb2, 0x09, 0x9d, 0x39, - 0xf8, 0x63, 0x9c, 0x32, 0x42, 0x7c, 0xa0, 0x30, 0x86, 0x72, 0x7a, 0x4a, 0xc6, 0xd4, 0xb3, 0x2d, - 0x24, 0xef, 0x96, 0x3f, 0xc2, 0xda, 0xd3, 0xf2, 0x15, 0x6f, 0xda, 0x65, 0x4b, 0x81, 0x28, 0x68, - 0xf4, 0xfe, 0x3e, 0x71, 0x4f, 0x50, 0x96, 0x72, 0x58, 0xa1, 0x89, 0xdd, 0x01, 0x61, 0x39, 0x39, - 0xc6, 0x76, 0xa4, 0xda, 0x02, 0x70, 0x3d, 0xc0, 0xdc, 0x8d, 0x70, 0x72, 0x04, 0x90, 0x79, 0xd4, - 0xec, 0x65, 0xcf, 0x49, 0x35, 0x25, 0x3a, 0x14, 0x1a, 0x45, 0x20, 0xeb, 0x31, 0xaf, 0x92, 0xba, - 0x20, 0xd3, 0xcd, 0xa7, 0x13, 0x44, 0xdc, 0xcf, 0xf0, 0x27, 0x34, 0xb9, 0xe7, 0x24, 0x6f, 0x73, - 0xe7, 0xea, 0x77, 0x03, - }; - - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *dnskey = NULL, *ds1 = NULL, *ds2 = NULL; - - /* The two DS RRs in effect for nasa.gov on 2015-12-01. */ - ds1 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "nasa.gov"); - assert_se(ds1); - - ds1->ds.key_tag = 47857; - ds1->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; - ds1->ds.digest_type = DNSSEC_DIGEST_SHA1; - ds1->ds.digest_size = sizeof(ds1_fprint); - ds1->ds.digest = memdup(ds1_fprint, ds1->ds.digest_size); - assert_se(ds1->ds.digest); - - log_info("DS1: %s", strna(dns_resource_record_to_string(ds1))); - - ds2 = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DS, "NASA.GOV"); - assert_se(ds2); - - ds2->ds.key_tag = 47857; - ds2->ds.algorithm = DNSSEC_ALGORITHM_RSASHA256; - ds2->ds.digest_type = DNSSEC_DIGEST_SHA256; - ds2->ds.digest_size = sizeof(ds2_fprint); - ds2->ds.digest = memdup(ds2_fprint, ds2->ds.digest_size); - assert_se(ds2->ds.digest); - - log_info("DS2: %s", strna(dns_resource_record_to_string(ds2))); - - dnskey = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_DNSKEY, "nasa.GOV"); - assert_se(dnskey); - - dnskey->dnskey.flags = 257; - dnskey->dnskey.protocol = 3; - dnskey->dnskey.algorithm = DNSSEC_ALGORITHM_RSASHA256; - dnskey->dnskey.key_size = sizeof(dnskey_blob); - dnskey->dnskey.key = memdup(dnskey_blob, sizeof(dnskey_blob)); - assert_se(dnskey->dnskey.key); - - log_info("DNSKEY: %s", strna(dns_resource_record_to_string(dnskey))); - log_info("DNSKEY keytag: %u", dnssec_keytag(dnskey, false)); - - assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds1, false) > 0); - assert_se(dnssec_verify_dnskey_by_ds(dnskey, ds2, false) > 0); -} - -static void test_dnssec_canonicalize_one(const char *original, const char *canonical, int r) { - char canonicalized[DNSSEC_CANONICAL_HOSTNAME_MAX]; - - assert_se(dnssec_canonicalize(original, canonicalized, sizeof(canonicalized)) == r); - if (r < 0) - return; - - assert_se(streq(canonicalized, canonical)); -} - -static void test_dnssec_canonicalize(void) { - test_dnssec_canonicalize_one("", ".", 1); - test_dnssec_canonicalize_one(".", ".", 1); - test_dnssec_canonicalize_one("foo", "foo.", 4); - test_dnssec_canonicalize_one("foo.", "foo.", 4); - test_dnssec_canonicalize_one("FOO.", "foo.", 4); - test_dnssec_canonicalize_one("FOO.bar.", "foo.bar.", 8); - test_dnssec_canonicalize_one("FOO..bar.", NULL, -EINVAL); -} - -static void test_dnssec_nsec3_hash(void) { - static const uint8_t salt[] = { 0xB0, 0x1D, 0xFA, 0xCE }; - static const uint8_t next_hashed_name[] = { 0x84, 0x10, 0x26, 0x53, 0xc9, 0xfa, 0x4d, 0x85, 0x6c, 0x97, 0x82, 0xe2, 0x8f, 0xdf, 0x2d, 0x5e, 0x87, 0x69, 0xc4, 0x52 }; - _cleanup_(dns_resource_record_unrefp) DnsResourceRecord *rr = NULL; - uint8_t h[DNSSEC_HASH_SIZE_MAX]; - _cleanup_free_ char *b = NULL; - int k; - - /* The NSEC3 RR for eurid.eu on 2015-12-14. */ - rr = dns_resource_record_new_full(DNS_CLASS_IN, DNS_TYPE_NSEC3, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM.eurid.eu."); - assert_se(rr); - - rr->nsec3.algorithm = DNSSEC_DIGEST_SHA1; - rr->nsec3.flags = 1; - rr->nsec3.iterations = 1; - rr->nsec3.salt = memdup(salt, sizeof(salt)); - assert_se(rr->nsec3.salt); - rr->nsec3.salt_size = sizeof(salt); - rr->nsec3.next_hashed_name = memdup(next_hashed_name, sizeof(next_hashed_name)); - assert_se(rr->nsec3.next_hashed_name); - rr->nsec3.next_hashed_name_size = sizeof(next_hashed_name); - - log_info("NSEC3: %s", strna(dns_resource_record_to_string(rr))); - - k = dnssec_nsec3_hash(rr, "eurid.eu", &h); - assert_se(k >= 0); - - b = base32hexmem(h, k, false); - assert_se(b); - assert_se(strcasecmp(b, "PJ8S08RR45VIQDAQGE7EN3VHKNROTBMM") == 0); -} - -int main(int argc, char*argv[]) { - - test_dnssec_canonicalize(); - test_dnssec_verify_dns_key(); - test_dnssec_verify_rrset(); - test_dnssec_verify_rrset2(); - test_dnssec_nsec3_hash(); - - return 0; -} diff --git a/src/resolve/test-resolve-tables.c b/src/resolve/test-resolve-tables.c deleted file mode 100644 index 63660afc87..0000000000 --- a/src/resolve/test-resolve-tables.c +++ /dev/null @@ -1,27 +0,0 @@ -/*** - This file is part of systemd - - Copyright 2013 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 . -***/ - -#include "dns-type.h" -#include "test-tables.h" - -int main(int argc, char **argv) { - test_table_sparse(dns_type, DNS_TYPE); - - return EXIT_SUCCESS; -} diff --git a/src/rfkill/Makefile b/src/rfkill/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/rfkill/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/rfkill/rfkill.c b/src/rfkill/rfkill.c deleted file mode 100644 index 0acdf229ed..0000000000 --- a/src/rfkill/rfkill.c +++ /dev/null @@ -1,426 +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 . -***/ - -#include -#include - -#include "libudev.h" -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "io-util.h" -#include "mkdir.h" -#include "parse-util.h" -#include "proc-cmdline.h" -#include "string-table.h" -#include "string-util.h" -#include "udev-util.h" -#include "util.h" - -#define EXIT_USEC (5 * USEC_PER_SEC) - -static const char* const rfkill_type_table[NUM_RFKILL_TYPES] = { - [RFKILL_TYPE_ALL] = "all", - [RFKILL_TYPE_WLAN] = "wlan", - [RFKILL_TYPE_BLUETOOTH] = "bluetooth", - [RFKILL_TYPE_UWB] = "uwb", - [RFKILL_TYPE_WIMAX] = "wimax", - [RFKILL_TYPE_WWAN] = "wwan", - [RFKILL_TYPE_GPS] = "gps", - [RFKILL_TYPE_FM] = "fm", - [RFKILL_TYPE_NFC] = "nfc", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(rfkill_type, int); - -static int find_device( - struct udev *udev, - const struct rfkill_event *event, - struct udev_device **ret) { - - _cleanup_free_ char *sysname = NULL; - struct udev_device *device; - const char *name; - - assert(udev); - assert(event); - assert(ret); - - if (asprintf(&sysname, "rfkill%i", event->idx) < 0) - return log_oom(); - - device = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname); - if (!device) - return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m"); - - name = udev_device_get_sysattr_value(device, "name"); - if (!name) { - log_debug("Device has no name, ignoring."); - udev_device_unref(device); - return -ENOENT; - } - - log_debug("Operating on rfkill device '%s'.", name); - - *ret = device; - return 0; -} - -static int wait_for_initialized( - struct udev *udev, - struct udev_device *device, - struct udev_device **ret) { - - _cleanup_udev_monitor_unref_ struct udev_monitor *monitor = NULL; - struct udev_device *d; - const char *sysname; - int watch_fd, r; - - assert(udev); - assert(device); - assert(ret); - - if (udev_device_get_is_initialized(device) != 0) { - *ret = udev_device_ref(device); - return 0; - } - - assert_se(sysname = udev_device_get_sysname(device)); - - /* Wait until the device is initialized, so that we can get - * access to the ID_PATH property */ - - monitor = udev_monitor_new_from_netlink(udev, "udev"); - if (!monitor) - return log_error_errno(errno, "Failed to acquire monitor: %m"); - - r = udev_monitor_filter_add_match_subsystem_devtype(monitor, "rfkill", NULL); - if (r < 0) - return log_error_errno(r, "Failed to add rfkill udev match to monitor: %m"); - - r = udev_monitor_enable_receiving(monitor); - if (r < 0) - return log_error_errno(r, "Failed to enable udev receiving: %m"); - - watch_fd = udev_monitor_get_fd(monitor); - if (watch_fd < 0) - return log_error_errno(watch_fd, "Failed to get watch fd: %m"); - - /* Check again, maybe things changed */ - d = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname); - if (!d) - return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m"); - - if (udev_device_get_is_initialized(d) != 0) { - *ret = d; - return 0; - } - - for (;;) { - _cleanup_udev_device_unref_ struct udev_device *t = NULL; - - r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY); - if (r == -EINTR) - continue; - if (r < 0) - return log_error_errno(r, "Failed to watch udev monitor: %m"); - - t = udev_monitor_receive_device(monitor); - if (!t) - continue; - - if (streq_ptr(udev_device_get_sysname(device), sysname)) { - *ret = udev_device_ref(t); - return 0; - } - } -} - -static int determine_state_file( - struct udev *udev, - const struct rfkill_event *event, - struct udev_device *d, - char **ret) { - - _cleanup_udev_device_unref_ struct udev_device *device = NULL; - const char *path_id, *type; - char *state_file; - int r; - - assert(event); - assert(d); - assert(ret); - - r = wait_for_initialized(udev, d, &device); - if (r < 0) - return r; - - assert_se(type = rfkill_type_to_string(event->type)); - - path_id = udev_device_get_property_value(device, "ID_PATH"); - if (path_id) { - _cleanup_free_ char *escaped_path_id = NULL; - - escaped_path_id = cescape(path_id); - if (!escaped_path_id) - return log_oom(); - - state_file = strjoin("/var/lib/systemd/rfkill/", escaped_path_id, ":", type, NULL); - } else - state_file = strjoin("/var/lib/systemd/rfkill/", type, NULL); - - if (!state_file) - return log_oom(); - - *ret = state_file; - return 0; -} - -static int load_state( - int rfkill_fd, - struct udev *udev, - const struct rfkill_event *event) { - - _cleanup_udev_device_unref_ struct udev_device *device = NULL; - _cleanup_free_ char *state_file = NULL, *value = NULL; - struct rfkill_event we; - ssize_t l; - int b, r; - - assert(rfkill_fd >= 0); - assert(udev); - assert(event); - - if (shall_restore_state() == 0) - return 0; - - r = find_device(udev, event, &device); - if (r < 0) - return r; - - r = determine_state_file(udev, event, device, &state_file); - if (r < 0) - return r; - - r = read_one_line_file(state_file, &value); - if (r == -ENOENT) { - /* No state file? Then save the current state */ - - r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); - if (r < 0) - return log_error_errno(r, "Failed to write state file %s: %m", state_file); - - log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to read state file %s: %m", state_file); - - b = parse_boolean(value); - if (b < 0) - return log_error_errno(b, "Failed to parse state file %s: %m", state_file); - - we = (struct rfkill_event) { - .op = RFKILL_OP_CHANGE, - .idx = event->idx, - .soft = b, - }; - - l = write(rfkill_fd, &we, sizeof(we)); - if (l < 0) - return log_error_errno(errno, "Failed to restore rfkill state for %i: %m", event->idx); - if (l != sizeof(we)) { - log_error("Couldn't write rfkill event structure, too short."); - return -EIO; - } - - log_debug("Loaded state '%s' from %s.", one_zero(b), state_file); - return 0; -} - -static int save_state( - int rfkill_fd, - struct udev *udev, - const struct rfkill_event *event) { - - _cleanup_udev_device_unref_ struct udev_device *device = NULL; - _cleanup_free_ char *state_file = NULL; - int r; - - assert(rfkill_fd >= 0); - assert(udev); - assert(event); - - r = find_device(udev, event, &device); - if (r < 0) - return r; - - r = determine_state_file(udev, event, device, &state_file); - if (r < 0) - return r; - - r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); - if (r < 0) - return log_error_errno(r, "Failed to write state file %s: %m", state_file); - - log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file); - return 0; -} - -int main(int argc, char *argv[]) { - _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_close_ int rfkill_fd = -1; - bool ready = false; - int r, n; - - if (argc > 1) { - log_error("This program requires no arguments."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - udev = udev_new(); - if (!udev) { - r = log_oom(); - goto finish; - } - - r = mkdir_p("/var/lib/systemd/rfkill", 0755); - if (r < 0) { - log_error_errno(r, "Failed to create rfkill directory: %m"); - goto finish; - } - - n = sd_listen_fds(false); - if (n < 0) { - r = log_error_errno(n, "Failed to determine whether we got any file descriptors passed: %m"); - goto finish; - } - if (n > 1) { - log_error("Got too many file descriptors."); - r = -EINVAL; - goto finish; - } - - if (n == 0) { - rfkill_fd = open("/dev/rfkill", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); - if (rfkill_fd < 0) { - if (errno == ENOENT) { - log_debug_errno(errno, "Missing rfkill subsystem, or no device present, exiting."); - r = 0; - goto finish; - } - - r = log_error_errno(errno, "Failed to open /dev/rfkill: %m"); - goto finish; - } - } else { - rfkill_fd = SD_LISTEN_FDS_START; - - r = fd_nonblock(rfkill_fd, 1); - if (r < 0) { - log_error_errno(r, "Failed to make /dev/rfkill socket non-blocking: %m"); - goto finish; - } - } - - for (;;) { - struct rfkill_event event; - const char *type; - ssize_t l; - - l = read(rfkill_fd, &event, sizeof(event)); - if (l < 0) { - if (errno == EAGAIN) { - - if (!ready) { - /* Notify manager that we are - * now finished with - * processing whatever was - * queued */ - (void) sd_notify(false, "READY=1"); - ready = true; - } - - /* Hang around for a bit, maybe there's more coming */ - - r = fd_wait_for_event(rfkill_fd, POLLIN, EXIT_USEC); - if (r == -EINTR) - continue; - if (r < 0) { - log_error_errno(r, "Failed to poll() on device: %m"); - goto finish; - } - if (r > 0) - continue; - - log_debug("All events read and idle, exiting."); - break; - } - - log_error_errno(errno, "Failed to read from /dev/rfkill: %m"); - } - - if (l != RFKILL_EVENT_SIZE_V1) { - log_error("Read event structure of invalid size."); - r = -EIO; - goto finish; - } - - type = rfkill_type_to_string(event.type); - if (!type) { - log_debug("An rfkill device of unknown type %i discovered, ignoring.", event.type); - continue; - } - - switch (event.op) { - - case RFKILL_OP_ADD: - log_debug("A new rfkill device has been added with index %i and type %s.", event.idx, type); - (void) load_state(rfkill_fd, udev, &event); - break; - - case RFKILL_OP_DEL: - log_debug("An rfkill device has been removed with index %i and type %s", event.idx, type); - break; - - case RFKILL_OP_CHANGE: - log_debug("An rfkill device has changed state with index %i and type %s", event.idx, type); - (void) save_state(rfkill_fd, udev, &event); - break; - - default: - log_debug("Unknown event %i from /dev/rfkill for index %i and type %s, ignoring.", event.op, event.idx, type); - break; - } - } - - r = 0; - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/run/Makefile b/src/run/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/run/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/run/run.c b/src/run/run.c deleted file mode 100644 index e7f4c21f73..0000000000 --- a/src/run/run.c +++ /dev/null @@ -1,1258 +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 . -***/ - -#include -#include - -#include "sd-bus.h" -#include "sd-event.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "calendarspec.h" -#include "env-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "ptyfwd.h" -#include "signal-util.h" -#include "spawn-polkit-agent.h" -#include "strv.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "user-util.h" - -static bool arg_ask_password = true; -static bool arg_scope = false; -static bool arg_remain_after_exit = false; -static bool arg_no_block = false; -static const char *arg_unit = NULL; -static const char *arg_description = NULL; -static const char *arg_slice = NULL; -static bool arg_send_sighup = false; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static const char *arg_host = NULL; -static bool arg_user = false; -static const char *arg_service_type = NULL; -static const char *arg_exec_user = NULL; -static const char *arg_exec_group = NULL; -static int arg_nice = 0; -static bool arg_nice_set = false; -static char **arg_environment = NULL; -static char **arg_property = NULL; -static bool arg_pty = false; -static usec_t arg_on_active = 0; -static usec_t arg_on_boot = 0; -static usec_t arg_on_startup = 0; -static usec_t arg_on_unit_active = 0; -static usec_t arg_on_unit_inactive = 0; -static const char *arg_on_calendar = NULL; -static char **arg_timer_property = NULL; -static bool arg_quiet = false; - -static void polkit_agent_open_if_enabled(void) { - - /* Open the polkit agent as a child process if necessary */ - if (!arg_ask_password) - return; - - if (arg_transport != BUS_TRANSPORT_LOCAL) - return; - - polkit_agent_open(); -} - -static void help(void) { - printf("%s [OPTIONS...] {COMMAND} [ARGS...]\n\n" - "Run the specified command in a transient scope or service or timer\n" - "unit. If timer option is specified and unit is exist which is\n" - "specified with --unit option then command can be omitted.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --no-ask-password Do not prompt for password\n" - " --user Run as user unit\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --scope Run this as scope rather than service\n" - " --unit=UNIT Run under the specified unit name\n" - " -p --property=NAME=VALUE Set unit property\n" - " --description=TEXT Description for unit\n" - " --slice=SLICE Run in the specified slice\n" - " --no-block Do not wait until operation finished\n" - " -r --remain-after-exit Leave service around until explicitly stopped\n" - " --send-sighup Send SIGHUP when terminating\n" - " --service-type=TYPE Service type\n" - " --uid=USER Run as system user\n" - " --gid=GROUP Run as system group\n" - " --nice=NICE Nice level\n" - " --setenv=NAME=VALUE Set environment\n" - " -t --pty Run service on pseudo tty\n" - " -q --quiet Suppress information messages during runtime\n\n" - "Timer options:\n\n" - " --on-active=SECONDS Run after SECONDS delay\n" - " --on-boot=SECONDS Run SECONDS after machine was booted up\n" - " --on-startup=SECONDS Run SECONDS after systemd activation\n" - " --on-unit-active=SECONDS Run SECONDS after the last activation\n" - " --on-unit-inactive=SECONDS Run SECONDS after the last deactivation\n" - " --on-calendar=SPEC Realtime timer\n" - " --timer-property=NAME=VALUE Set timer unit property\n", - program_invocation_short_name); -} - -static bool with_timer(void) { - return arg_on_active || arg_on_boot || arg_on_startup || arg_on_unit_active || arg_on_unit_inactive || arg_on_calendar; -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_ASK_PASSWORD, - ARG_USER, - ARG_SYSTEM, - ARG_SCOPE, - ARG_UNIT, - ARG_DESCRIPTION, - ARG_SLICE, - ARG_SEND_SIGHUP, - ARG_EXEC_USER, - ARG_EXEC_GROUP, - ARG_SERVICE_TYPE, - ARG_NICE, - ARG_SETENV, - ARG_TTY, - ARG_ON_ACTIVE, - ARG_ON_BOOT, - ARG_ON_STARTUP, - ARG_ON_UNIT_ACTIVE, - ARG_ON_UNIT_INACTIVE, - ARG_ON_CALENDAR, - ARG_TIMER_PROPERTY, - ARG_NO_BLOCK, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "scope", no_argument, NULL, ARG_SCOPE }, - { "unit", required_argument, NULL, ARG_UNIT }, - { "description", required_argument, NULL, ARG_DESCRIPTION }, - { "slice", required_argument, NULL, ARG_SLICE }, - { "remain-after-exit", no_argument, NULL, 'r' }, - { "send-sighup", no_argument, NULL, ARG_SEND_SIGHUP }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "service-type", required_argument, NULL, ARG_SERVICE_TYPE }, - { "uid", required_argument, NULL, ARG_EXEC_USER }, - { "gid", required_argument, NULL, ARG_EXEC_GROUP }, - { "nice", required_argument, NULL, ARG_NICE }, - { "setenv", required_argument, NULL, ARG_SETENV }, - { "property", required_argument, NULL, 'p' }, - { "tty", no_argument, NULL, 't' }, - { "quiet", no_argument, NULL, 'q' }, - { "on-active", required_argument, NULL, ARG_ON_ACTIVE }, - { "on-boot", required_argument, NULL, ARG_ON_BOOT }, - { "on-startup", required_argument, NULL, ARG_ON_STARTUP }, - { "on-unit-active", required_argument, NULL, ARG_ON_UNIT_ACTIVE }, - { "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE }, - { "on-calendar", required_argument, NULL, ARG_ON_CALENDAR }, - { "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - {}, - }; - - int r, c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "+hrH:M:p:tq", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case ARG_VERSION: - return version(); - - case ARG_USER: - arg_user = true; - break; - - case ARG_SYSTEM: - arg_user = false; - break; - - case ARG_SCOPE: - arg_scope = true; - break; - - case ARG_UNIT: - arg_unit = optarg; - break; - - case ARG_DESCRIPTION: - arg_description = optarg; - break; - - case ARG_SLICE: - arg_slice = optarg; - break; - - case ARG_SEND_SIGHUP: - arg_send_sighup = true; - break; - - case 'r': - arg_remain_after_exit = true; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case ARG_SERVICE_TYPE: - arg_service_type = optarg; - break; - - case ARG_EXEC_USER: - arg_exec_user = optarg; - break; - - case ARG_EXEC_GROUP: - arg_exec_group = optarg; - break; - - case ARG_NICE: - r = safe_atoi(optarg, &arg_nice); - if (r < 0 || arg_nice < PRIO_MIN || arg_nice >= PRIO_MAX) { - log_error("Failed to parse nice value"); - return -EINVAL; - } - - arg_nice_set = true; - break; - - case ARG_SETENV: - if (strv_extend(&arg_environment, optarg) < 0) - return log_oom(); - - break; - - case 'p': - if (strv_extend(&arg_property, optarg) < 0) - return log_oom(); - - break; - - case 't': - arg_pty = true; - break; - - case 'q': - arg_quiet = true; - break; - - case ARG_ON_ACTIVE: - - r = parse_sec(optarg, &arg_on_active); - if (r < 0) { - log_error("Failed to parse timer value: %s", optarg); - return r; - } - - break; - - case ARG_ON_BOOT: - - r = parse_sec(optarg, &arg_on_boot); - if (r < 0) { - log_error("Failed to parse timer value: %s", optarg); - return r; - } - - break; - - case ARG_ON_STARTUP: - - r = parse_sec(optarg, &arg_on_startup); - if (r < 0) { - log_error("Failed to parse timer value: %s", optarg); - return r; - } - - break; - - case ARG_ON_UNIT_ACTIVE: - - r = parse_sec(optarg, &arg_on_unit_active); - if (r < 0) { - log_error("Failed to parse timer value: %s", optarg); - return r; - } - - break; - - case ARG_ON_UNIT_INACTIVE: - - r = parse_sec(optarg, &arg_on_unit_inactive); - if (r < 0) { - log_error("Failed to parse timer value: %s", optarg); - return r; - } - - break; - - case ARG_ON_CALENDAR: { - CalendarSpec *spec = NULL; - - r = calendar_spec_from_string(optarg, &spec); - if (r < 0) { - log_error("Invalid calendar spec: %s", optarg); - return r; - } - - calendar_spec_free(spec); - arg_on_calendar = optarg; - break; - } - - case ARG_TIMER_PROPERTY: - - if (strv_extend(&arg_timer_property, optarg) < 0) - return log_oom(); - - break; - - case ARG_NO_BLOCK: - arg_no_block = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if ((optind >= argc) && (!arg_unit || !with_timer())) { - log_error("Command line to execute required."); - return -EINVAL; - } - - if (arg_user && arg_transport != BUS_TRANSPORT_LOCAL) { - log_error("Execution in user context is not supported on non-local systems."); - return -EINVAL; - } - - if (arg_scope && arg_transport != BUS_TRANSPORT_LOCAL) { - log_error("Scope execution is not supported on non-local systems."); - return -EINVAL; - } - - if (arg_scope && (arg_remain_after_exit || arg_service_type)) { - log_error("--remain-after-exit and --service-type= are not supported in --scope mode."); - return -EINVAL; - } - - if (arg_pty && (with_timer() || arg_scope)) { - log_error("--pty is not compatible in timer or --scope mode."); - return -EINVAL; - } - - if (arg_pty && arg_transport == BUS_TRANSPORT_REMOTE) { - log_error("--pty is only supported when connecting to the local system or containers."); - return -EINVAL; - } - - if (arg_scope && with_timer()) { - log_error("Timer options are not supported in --scope mode."); - return -EINVAL; - } - - if (arg_timer_property && !with_timer()) { - log_error("--timer-property= has no effect without any other timer options."); - return -EINVAL; - } - - return 1; -} - -static int transient_unit_set_properties(sd_bus_message *m, char **properties) { - char **i; - int r; - - r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description); - if (r < 0) - return r; - - STRV_FOREACH(i, properties) { - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - } - - return 0; -} - -static int transient_cgroup_set_properties(sd_bus_message *m) { - int r; - assert(m); - - if (!isempty(arg_slice)) { - _cleanup_free_ char *slice; - - r = unit_name_mangle_with_suffix(arg_slice, UNIT_NAME_NOGLOB, ".slice", &slice); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); - if (r < 0) - return r; - } - - return 0; -} - -static int transient_kill_set_properties(sd_bus_message *m) { - assert(m); - - if (arg_send_sighup) - return sd_bus_message_append(m, "(sv)", "SendSIGHUP", "b", arg_send_sighup); - else - return 0; -} - -static int transient_service_set_properties(sd_bus_message *m, char **argv, const char *pty_path) { - int r; - - assert(m); - - r = transient_unit_set_properties(m, arg_property); - if (r < 0) - return r; - - r = transient_kill_set_properties(m); - if (r < 0) - return r; - - r = transient_cgroup_set_properties(m); - if (r < 0) - return r; - - if (arg_remain_after_exit) { - r = sd_bus_message_append(m, "(sv)", "RemainAfterExit", "b", arg_remain_after_exit); - if (r < 0) - return r; - } - - if (arg_service_type) { - r = sd_bus_message_append(m, "(sv)", "Type", "s", arg_service_type); - if (r < 0) - return r; - } - - if (arg_exec_user) { - r = sd_bus_message_append(m, "(sv)", "User", "s", arg_exec_user); - if (r < 0) - return r; - } - - if (arg_exec_group) { - r = sd_bus_message_append(m, "(sv)", "Group", "s", arg_exec_group); - if (r < 0) - return r; - } - - if (arg_nice_set) { - r = sd_bus_message_append(m, "(sv)", "Nice", "i", arg_nice); - if (r < 0) - return r; - } - - if (pty_path) { - const char *e; - - r = sd_bus_message_append(m, - "(sv)(sv)(sv)(sv)", - "StandardInput", "s", "tty", - "StandardOutput", "s", "tty", - "StandardError", "s", "tty", - "TTYPath", "s", pty_path); - if (r < 0) - return r; - - e = getenv("TERM"); - if (e) { - char *n; - - n = strjoina("TERM=", e); - r = sd_bus_message_append(m, - "(sv)", - "Environment", "as", 1, n); - if (r < 0) - return r; - } - } - - if (!strv_isempty(arg_environment)) { - r = sd_bus_message_open_container(m, 'r', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "s", "Environment"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'v', "as"); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(m, arg_environment); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - } - - /* Exec container */ - { - r = sd_bus_message_open_container(m, 'r', "sv"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "s", "ExecStart"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'v', "a(sasb)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'a', "(sasb)"); - if (r < 0) - return r; - - r = sd_bus_message_open_container(m, 'r', "sasb"); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "s", argv[0]); - if (r < 0) - return r; - - r = sd_bus_message_append_strv(m, argv); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "b", false); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - - r = sd_bus_message_close_container(m); - if (r < 0) - return r; - } - - return 0; -} - -static int transient_scope_set_properties(sd_bus_message *m) { - int r; - - assert(m); - - r = transient_unit_set_properties(m, arg_property); - if (r < 0) - return r; - - r = transient_kill_set_properties(m); - if (r < 0) - return r; - - r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, (uint32_t) getpid()); - if (r < 0) - return r; - - return 0; -} - -static int transient_timer_set_properties(sd_bus_message *m) { - int r; - - assert(m); - - r = transient_unit_set_properties(m, arg_timer_property); - if (r < 0) - return r; - - /* Automatically clean up our transient timers */ - r = sd_bus_message_append(m, "(sv)", "RemainAfterElapse", "b", false); - if (r < 0) - return r; - - if (arg_on_active) { - r = sd_bus_message_append(m, "(sv)", "OnActiveSec", "t", arg_on_active); - if (r < 0) - return r; - } - - if (arg_on_boot) { - r = sd_bus_message_append(m, "(sv)", "OnBootSec", "t", arg_on_boot); - if (r < 0) - return r; - } - - if (arg_on_startup) { - r = sd_bus_message_append(m, "(sv)", "OnStartupSec", "t", arg_on_startup); - if (r < 0) - return r; - } - - if (arg_on_unit_active) { - r = sd_bus_message_append(m, "(sv)", "OnUnitActiveSec", "t", arg_on_unit_active); - if (r < 0) - return r; - } - - if (arg_on_unit_inactive) { - r = sd_bus_message_append(m, "(sv)", "OnUnitInactiveSec", "t", arg_on_unit_inactive); - if (r < 0) - return r; - } - - if (arg_on_calendar) { - r = sd_bus_message_append(m, "(sv)", "OnCalendar", "s", arg_on_calendar); - if (r < 0) - return r; - } - - return 0; -} - -static int make_unit_name(sd_bus *bus, UnitType t, char **ret) { - const char *unique, *id; - char *p; - int r; - - assert(bus); - assert(t >= 0); - assert(t < _UNIT_TYPE_MAX); - - r = sd_bus_get_unique_name(bus, &unique); - if (r < 0) { - sd_id128_t rnd; - - /* We couldn't get the unique name, which is a pretty - * common case if we are connected to systemd - * directly. In that case, just pick a random uuid as - * name */ - - r = sd_id128_randomize(&rnd); - if (r < 0) - return log_error_errno(r, "Failed to generate random run unit name: %m"); - - if (asprintf(ret, "run-r" SD_ID128_FORMAT_STR ".%s", SD_ID128_FORMAT_VAL(rnd), unit_type_to_string(t)) < 0) - return log_oom(); - - return 0; - } - - /* We managed to get the unique name, then let's use that to - * name our transient units. */ - - id = startswith(unique, ":1."); - if (!id) { - log_error("Unique name %s has unexpected format.", unique); - return -EINVAL; - } - - p = strjoin("run-u", id, ".", unit_type_to_string(t), NULL); - if (!p) - return log_oom(); - - *ret = p; - return 0; -} - -static int start_transient_service( - sd_bus *bus, - char **argv) { - - _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; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - _cleanup_free_ char *service = NULL, *pty_path = NULL; - _cleanup_close_ int master = -1; - int r; - - assert(bus); - assert(argv); - - if (arg_pty) { - - if (arg_transport == BUS_TRANSPORT_LOCAL) { - master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY); - if (master < 0) - return log_error_errno(errno, "Failed to acquire pseudo tty: %m"); - - r = ptsname_malloc(master, &pty_path); - if (r < 0) - return log_error_errno(r, "Failed to determine tty name: %m"); - - if (unlockpt(master) < 0) - return log_error_errno(errno, "Failed to unlock tty: %m"); - - } else if (arg_transport == BUS_TRANSPORT_MACHINE) { - _cleanup_(sd_bus_unrefp) sd_bus *system_bus = NULL; - const char *s; - - r = sd_bus_default_system(&system_bus); - if (r < 0) - return log_error_errno(r, "Failed to connect to system bus: %m"); - - r = sd_bus_call_method(system_bus, - "org.freedesktop.machine1", - "/org/freedesktop/machine1", - "org.freedesktop.machine1.Manager", - "OpenMachinePTY", - &error, - &reply, - "s", arg_host); - if (r < 0) { - log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "hs", &master, &s); - if (r < 0) - return bus_log_parse_error(r); - - reply = sd_bus_message_unref(reply); - - master = fcntl(master, F_DUPFD_CLOEXEC, 3); - if (master < 0) - return log_error_errno(errno, "Failed to duplicate master fd: %m"); - - pty_path = strdup(s); - if (!pty_path) - return log_oom(); - } else - assert_not_reached("Can't allocate tty via ssh"); - } - - if (!arg_no_block) { - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_error_errno(r, "Could not watch jobs: %m"); - } - - if (arg_unit) { - r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".service", &service); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - } else { - r = make_unit_name(bus, UNIT_SERVICE, &service); - if (r < 0) - return r; - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartTransientUnit"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - - /* Name and mode */ - r = sd_bus_message_append(m, "ss", service, "fail"); - if (r < 0) - return bus_log_create_error(r); - - /* Properties */ - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - r = transient_service_set_properties(m, argv, pty_path); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - /* Auxiliary units */ - r = sd_bus_message_append(m, "a(sa(sv))", 0); - if (r < 0) - return bus_log_create_error(r); - - polkit_agent_open_if_enabled(); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "Failed to start transient service unit: %s", bus_error_message(&error, r)); - - if (w) { - const char *object; - - r = sd_bus_message_read(reply, "o", &object); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_one(w, object, arg_quiet); - if (r < 0) - return r; - } - - if (master >= 0) { - _cleanup_(pty_forward_freep) PTYForward *forward = NULL; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - char last_char = 0; - - r = sd_event_default(&event); - if (r < 0) - return log_error_errno(r, "Failed to get event loop: %m"); - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); - - (void) sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); - (void) sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); - - if (!arg_quiet) - log_info("Running as unit %s.\nPress ^] three times within 1s to disconnect TTY.", service); - - r = pty_forward_new(event, master, PTY_FORWARD_IGNORE_INITIAL_VHANGUP, &forward); - if (r < 0) - return log_error_errno(r, "Failed to create PTY forwarder: %m"); - - r = sd_event_loop(event); - if (r < 0) - return log_error_errno(r, "Failed to run event loop: %m"); - - pty_forward_get_last_char(forward, &last_char); - - forward = pty_forward_free(forward); - - if (!arg_quiet && last_char != '\n') - fputc('\n', stdout); - - } else if (!arg_quiet) - log_info("Running as unit %s.", service); - - return 0; -} - -static int start_transient_scope( - sd_bus *bus, - char **argv) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - _cleanup_strv_free_ char **env = NULL, **user_env = NULL; - _cleanup_free_ char *scope = NULL; - const char *object = NULL; - int r; - - assert(bus); - assert(argv); - - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_oom(); - - if (arg_unit) { - r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".scope", &scope); - if (r < 0) - return log_error_errno(r, "Failed to mangle scope name: %m"); - } else { - r = make_unit_name(bus, UNIT_SCOPE, &scope); - if (r < 0) - return r; - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartTransientUnit"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - - /* Name and Mode */ - r = sd_bus_message_append(m, "ss", scope, "fail"); - if (r < 0) - return bus_log_create_error(r); - - /* Properties */ - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - r = transient_scope_set_properties(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - /* Auxiliary units */ - r = sd_bus_message_append(m, "a(sa(sv))", 0); - if (r < 0) - return bus_log_create_error(r); - - polkit_agent_open_if_enabled(); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to start transient scope unit: %s", bus_error_message(&error, -r)); - return r; - } - - if (arg_nice_set) { - if (setpriority(PRIO_PROCESS, 0, arg_nice) < 0) - return log_error_errno(errno, "Failed to set nice level: %m"); - } - - if (arg_exec_group) { - gid_t gid; - - r = get_group_creds(&arg_exec_group, &gid); - if (r < 0) - return log_error_errno(r, "Failed to resolve group %s: %m", arg_exec_group); - - if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); - } - - if (arg_exec_user) { - const char *home, *shell; - uid_t uid; - gid_t gid; - - r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell); - if (r < 0) - return log_error_errno(r, "Failed to resolve user %s: %m", arg_exec_user); - - r = strv_extendf(&user_env, "HOME=%s", home); - if (r < 0) - return log_oom(); - - r = strv_extendf(&user_env, "SHELL=%s", shell); - if (r < 0) - return log_oom(); - - r = strv_extendf(&user_env, "USER=%s", arg_exec_user); - if (r < 0) - return log_oom(); - - r = strv_extendf(&user_env, "LOGNAME=%s", arg_exec_user); - if (r < 0) - return log_oom(); - - if (!arg_exec_group) { - if (setresgid(gid, gid, gid) < 0) - return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); - } - - if (setresuid(uid, uid, uid) < 0) - return log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", uid); - } - - env = strv_env_merge(3, environ, user_env, arg_environment); - if (!env) - return log_oom(); - - r = sd_bus_message_read(reply, "o", &object); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_one(w, object, arg_quiet); - if (r < 0) - return r; - - if (!arg_quiet) - log_info("Running scope as unit %s.", scope); - - execvpe(argv[0], argv, env); - - return log_error_errno(errno, "Failed to execute: %m"); -} - -static int start_transient_timer( - sd_bus *bus, - char **argv) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - _cleanup_free_ char *timer = NULL, *service = NULL; - const char *object = NULL; - int r; - - assert(bus); - assert(argv); - - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_oom(); - - if (arg_unit) { - switch (unit_name_to_type(arg_unit)) { - - case UNIT_SERVICE: - service = strdup(arg_unit); - if (!service) - return log_oom(); - - r = unit_name_change_suffix(service, ".timer", &timer); - if (r < 0) - return log_error_errno(r, "Failed to change unit suffix: %m"); - break; - - case UNIT_TIMER: - timer = strdup(arg_unit); - if (!timer) - return log_oom(); - - r = unit_name_change_suffix(timer, ".service", &service); - if (r < 0) - return log_error_errno(r, "Failed to change unit suffix: %m"); - break; - - default: - r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".service", &service); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".timer", &timer); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - break; - } - } else { - r = make_unit_name(bus, UNIT_SERVICE, &service); - if (r < 0) - return r; - - r = unit_name_change_suffix(service, ".timer", &timer); - if (r < 0) - return log_error_errno(r, "Failed to change unit suffix: %m"); - } - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "StartTransientUnit"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); - if (r < 0) - return bus_log_create_error(r); - - /* Name and Mode */ - r = sd_bus_message_append(m, "ss", timer, "fail"); - if (r < 0) - return bus_log_create_error(r); - - /* Properties */ - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - r = transient_timer_set_properties(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "(sa(sv))"); - if (r < 0) - return bus_log_create_error(r); - - if (argv[0]) { - r = sd_bus_message_open_container(m, 'r', "sa(sv)"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "s", service); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - r = transient_service_set_properties(m, argv, NULL); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - polkit_agent_open_if_enabled(); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) { - log_error("Failed to start transient timer unit: %s", bus_error_message(&error, -r)); - return r; - } - - r = sd_bus_message_read(reply, "o", &object); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_wait_for_jobs_one(w, object, arg_quiet); - if (r < 0) - return r; - - log_info("Running timer as unit %s.", timer); - if (argv[0]) - log_info("Will run service as unit %s.", service); - - return 0; -} - -int main(int argc, char* argv[]) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - _cleanup_free_ char *description = NULL, *command = NULL; - int r; - - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (argc > optind && arg_transport == BUS_TRANSPORT_LOCAL) { - /* Patch in an absolute path */ - - r = find_binary(argv[optind], &command); - if (r < 0) { - log_error_errno(r, "Failed to find executable %s: %m", argv[optind]); - goto finish; - } - - argv[optind] = command; - } - - if (!arg_description) { - description = strv_join(argv + optind, " "); - if (!description) { - r = log_oom(); - goto finish; - } - - if (arg_unit && isempty(description)) { - r = free_and_strdup(&description, arg_unit); - if (r < 0) - goto finish; - } - - arg_description = description; - } - - r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); - if (r < 0) { - log_error_errno(r, "Failed to create bus connection: %m"); - goto finish; - } - - if (arg_scope) - r = start_transient_scope(bus, argv + optind); - else if (with_timer()) - r = start_transient_timer(bus, argv + optind); - else - r = start_transient_service(bus, argv + optind); - -finish: - strv_free(arg_environment); - strv_free(arg_property); - strv_free(arg_timer_property); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/shared/Makefile b/src/shared/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/shared/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/shared/acl-util.c b/src/shared/acl-util.c deleted file mode 100644 index 2aa951fce9..0000000000 --- a/src/shared/acl-util.c +++ /dev/null @@ -1,429 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011,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 . -***/ - -#include -#include - -#include "acl-util.h" -#include "alloc-util.h" -#include "string-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry) { - acl_entry_t i; - int r; - - assert(acl); - assert(entry); - - for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); - r > 0; - r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) { - - acl_tag_t tag; - uid_t *u; - bool b; - - if (acl_get_tag_type(i, &tag) < 0) - return -errno; - - if (tag != ACL_USER) - continue; - - u = acl_get_qualifier(i); - if (!u) - return -errno; - - b = *u == uid; - acl_free(u); - - if (b) { - *entry = i; - return 1; - } - } - if (r < 0) - return -errno; - - return 0; -} - -int calc_acl_mask_if_needed(acl_t *acl_p) { - acl_entry_t i; - int r; - bool need = false; - - assert(acl_p); - - for (r = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i); - r > 0; - r = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) { - acl_tag_t tag; - - if (acl_get_tag_type(i, &tag) < 0) - return -errno; - - if (tag == ACL_MASK) - return 0; - - if (IN_SET(tag, ACL_USER, ACL_GROUP)) - need = true; - } - if (r < 0) - return -errno; - - if (need && acl_calc_mask(acl_p) < 0) - return -errno; - - return need; -} - -int add_base_acls_if_needed(acl_t *acl_p, const char *path) { - acl_entry_t i; - int r; - bool have_user_obj = false, have_group_obj = false, have_other = false; - struct stat st; - _cleanup_(acl_freep) acl_t basic = NULL; - - assert(acl_p); - - for (r = acl_get_entry(*acl_p, ACL_FIRST_ENTRY, &i); - r > 0; - r = acl_get_entry(*acl_p, ACL_NEXT_ENTRY, &i)) { - acl_tag_t tag; - - if (acl_get_tag_type(i, &tag) < 0) - return -errno; - - if (tag == ACL_USER_OBJ) - have_user_obj = true; - else if (tag == ACL_GROUP_OBJ) - have_group_obj = true; - else if (tag == ACL_OTHER) - have_other = true; - if (have_user_obj && have_group_obj && have_other) - return 0; - } - if (r < 0) - return -errno; - - r = stat(path, &st); - if (r < 0) - return -errno; - - basic = acl_from_mode(st.st_mode); - if (!basic) - return -errno; - - for (r = acl_get_entry(basic, ACL_FIRST_ENTRY, &i); - r > 0; - r = acl_get_entry(basic, ACL_NEXT_ENTRY, &i)) { - acl_tag_t tag; - acl_entry_t dst; - - if (acl_get_tag_type(i, &tag) < 0) - return -errno; - - if ((tag == ACL_USER_OBJ && have_user_obj) || - (tag == ACL_GROUP_OBJ && have_group_obj) || - (tag == ACL_OTHER && have_other)) - continue; - - r = acl_create_entry(acl_p, &dst); - if (r < 0) - return -errno; - - r = acl_copy_entry(dst, i); - if (r < 0) - return -errno; - } - if (r < 0) - return -errno; - return 0; -} - -int acl_search_groups(const char *path, char ***ret_groups) { - _cleanup_strv_free_ char **g = NULL; - _cleanup_(acl_free) acl_t acl = NULL; - bool ret = false; - acl_entry_t entry; - int r; - - assert(path); - - acl = acl_get_file(path, ACL_TYPE_DEFAULT); - if (!acl) - return -errno; - - r = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); - for (;;) { - _cleanup_(acl_free_gid_tpp) gid_t *gid = NULL; - acl_tag_t tag; - - if (r < 0) - return -errno; - if (r == 0) - break; - - if (acl_get_tag_type(entry, &tag) < 0) - return -errno; - - if (tag != ACL_GROUP) - goto next; - - gid = acl_get_qualifier(entry); - if (!gid) - return -errno; - - if (in_gid(*gid) > 0) { - if (!ret_groups) - return true; - - ret = true; - } - - if (ret_groups) { - char *name; - - name = gid_to_name(*gid); - if (!name) - return -ENOMEM; - - r = strv_consume(&g, name); - if (r < 0) - return r; - } - - next: - r = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry); - } - - if (ret_groups) { - *ret_groups = g; - g = NULL; - } - - return ret; -} - -int parse_acl(const char *text, acl_t *acl_access, acl_t *acl_default, bool want_mask) { - _cleanup_free_ char **a = NULL, **d = NULL; /* strings are not be freed */ - _cleanup_strv_free_ char **split; - char **entry; - int r = -EINVAL; - _cleanup_(acl_freep) acl_t a_acl = NULL, d_acl = NULL; - - split = strv_split(text, ","); - if (!split) - return -ENOMEM; - - STRV_FOREACH(entry, split) { - char *p; - - p = startswith(*entry, "default:"); - if (!p) - p = startswith(*entry, "d:"); - - if (p) - r = strv_push(&d, p); - else - r = strv_push(&a, *entry); - if (r < 0) - return r; - } - - if (!strv_isempty(a)) { - _cleanup_free_ char *join; - - join = strv_join(a, ","); - if (!join) - return -ENOMEM; - - a_acl = acl_from_text(join); - if (!a_acl) - return -errno; - - if (want_mask) { - r = calc_acl_mask_if_needed(&a_acl); - if (r < 0) - return r; - } - } - - if (!strv_isempty(d)) { - _cleanup_free_ char *join; - - join = strv_join(d, ","); - if (!join) - return -ENOMEM; - - d_acl = acl_from_text(join); - if (!d_acl) - return -errno; - - if (want_mask) { - r = calc_acl_mask_if_needed(&d_acl); - if (r < 0) - return r; - } - } - - *acl_access = a_acl; - *acl_default = d_acl; - a_acl = d_acl = NULL; - - return 0; -} - -static int acl_entry_equal(acl_entry_t a, acl_entry_t b) { - acl_tag_t tag_a, tag_b; - - if (acl_get_tag_type(a, &tag_a) < 0) - return -errno; - - if (acl_get_tag_type(b, &tag_b) < 0) - return -errno; - - if (tag_a != tag_b) - return false; - - switch (tag_a) { - case ACL_USER_OBJ: - case ACL_GROUP_OBJ: - case ACL_MASK: - case ACL_OTHER: - /* can have only one of those */ - return true; - case ACL_USER: { - _cleanup_(acl_free_uid_tpp) uid_t *uid_a = NULL, *uid_b = NULL; - - uid_a = acl_get_qualifier(a); - if (!uid_a) - return -errno; - - uid_b = acl_get_qualifier(b); - if (!uid_b) - return -errno; - - return *uid_a == *uid_b; - } - case ACL_GROUP: { - _cleanup_(acl_free_gid_tpp) gid_t *gid_a = NULL, *gid_b = NULL; - - gid_a = acl_get_qualifier(a); - if (!gid_a) - return -errno; - - gid_b = acl_get_qualifier(b); - if (!gid_b) - return -errno; - - return *gid_a == *gid_b; - } - default: - assert_not_reached("Unknown acl tag type"); - } -} - -static int find_acl_entry(acl_t acl, acl_entry_t entry, acl_entry_t *out) { - acl_entry_t i; - int r; - - for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i); - r > 0; - r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) { - - r = acl_entry_equal(i, entry); - if (r < 0) - return r; - if (r > 0) { - *out = i; - return 1; - } - } - if (r < 0) - return -errno; - return 0; -} - -int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl) { - _cleanup_(acl_freep) acl_t old; - acl_entry_t i; - int r; - - old = acl_get_file(path, type); - if (!old) - return -errno; - - for (r = acl_get_entry(new, ACL_FIRST_ENTRY, &i); - r > 0; - r = acl_get_entry(new, ACL_NEXT_ENTRY, &i)) { - - acl_entry_t j; - - r = find_acl_entry(old, i, &j); - if (r < 0) - return r; - if (r == 0) - if (acl_create_entry(&old, &j) < 0) - return -errno; - - if (acl_copy_entry(j, i) < 0) - return -errno; - } - if (r < 0) - return -errno; - - *acl = old; - old = NULL; - return 0; -} - -int add_acls_for_user(int fd, uid_t uid) { - _cleanup_(acl_freep) acl_t acl = NULL; - acl_entry_t entry; - acl_permset_t permset; - int r; - - acl = acl_get_fd(fd); - if (!acl) - return -errno; - - r = acl_find_uid(acl, uid, &entry); - if (r <= 0) { - if (acl_create_entry(&acl, &entry) < 0 || - acl_set_tag_type(entry, ACL_USER) < 0 || - acl_set_qualifier(entry, &uid) < 0) - return -errno; - } - - /* We do not recalculate the mask unconditionally here, - * so that the fchmod() mask above stays intact. */ - if (acl_get_permset(entry, &permset) < 0 || - acl_add_perm(permset, ACL_READ) < 0) - return -errno; - - r = calc_acl_mask_if_needed(&acl); - if (r < 0) - return r; - - return acl_set_fd(fd, acl); -} diff --git a/src/shared/acl-util.h b/src/shared/acl-util.h deleted file mode 100644 index 396e9e067e..0000000000 --- a/src/shared/acl-util.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#ifdef HAVE_ACL - -#include -#include -#include - -#include "macro.h" - -int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry); -int calc_acl_mask_if_needed(acl_t *acl_p); -int add_base_acls_if_needed(acl_t *acl_p, const char *path); -int acl_search_groups(const char* path, char ***ret_groups); -int parse_acl(const char *text, acl_t *acl_access, acl_t *acl_default, bool want_mask); -int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl); -int add_acls_for_user(int fd, uid_t uid); - -/* acl_free takes multiple argument types. - * Multiple cleanup functions are necessary. */ -DEFINE_TRIVIAL_CLEANUP_FUNC(acl_t, acl_free); -#define acl_free_charp acl_free -DEFINE_TRIVIAL_CLEANUP_FUNC(char*, acl_free_charp); -#define acl_free_uid_tp acl_free -DEFINE_TRIVIAL_CLEANUP_FUNC(uid_t*, acl_free_uid_tp); -#define acl_free_gid_tp acl_free -DEFINE_TRIVIAL_CLEANUP_FUNC(gid_t*, acl_free_gid_tp); - -#endif diff --git a/src/shared/acpi-fpdt.c b/src/shared/acpi-fpdt.c deleted file mode 100644 index 3cb9e781fd..0000000000 --- a/src/shared/acpi-fpdt.c +++ /dev/null @@ -1,160 +0,0 @@ -/*** - This file is part of systemd. - - 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "acpi-fpdt.h" -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "time-util.h" - -struct acpi_table_header { - char signature[4]; - uint32_t length; - uint8_t revision; - uint8_t checksum; - char oem_id[6]; - char oem_table_id[8]; - uint32_t oem_revision; - char asl_compiler_id[4]; - uint32_t asl_compiler_revision; -}; - -enum { - ACPI_FPDT_TYPE_BOOT = 0, - ACPI_FPDT_TYPE_S3PERF = 1, -}; - -struct acpi_fpdt_header { - uint16_t type; - uint8_t length; - uint8_t revision; - uint8_t reserved[4]; - uint64_t ptr; -}; - -struct acpi_fpdt_boot_header { - char signature[4]; - uint32_t length; -}; - -enum { - ACPI_FPDT_S3PERF_RESUME_REC = 0, - ACPI_FPDT_S3PERF_SUSPEND_REC = 1, - ACPI_FPDT_BOOT_REC = 2, -}; - -struct acpi_fpdt_boot { - uint16_t type; - uint8_t length; - uint8_t revision; - uint8_t reserved[4]; - uint64_t reset_end; - uint64_t load_start; - uint64_t startup_start; - uint64_t exit_services_entry; - uint64_t exit_services_exit; -}; - -int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit) { - _cleanup_free_ char *buf = NULL; - struct acpi_table_header *tbl; - size_t l = 0; - struct acpi_fpdt_header *rec; - int r; - uint64_t ptr = 0; - _cleanup_close_ int fd = -1; - struct acpi_fpdt_boot_header hbrec; - struct acpi_fpdt_boot brec; - - r = read_full_file("/sys/firmware/acpi/tables/FPDT", &buf, &l); - if (r < 0) - return r; - - if (l < sizeof(struct acpi_table_header) + sizeof(struct acpi_fpdt_header)) - return -EINVAL; - - tbl = (struct acpi_table_header *)buf; - if (l != tbl->length) - return -EINVAL; - - if (memcmp(tbl->signature, "FPDT", 4) != 0) - return -EINVAL; - - /* find Firmware Basic Boot Performance Pointer Record */ - for (rec = (struct acpi_fpdt_header *)(buf + sizeof(struct acpi_table_header)); - (char *)rec < buf + l; - rec = (struct acpi_fpdt_header *)((char *)rec + rec->length)) { - if (rec->length <= 0) - break; - if (rec->type != ACPI_FPDT_TYPE_BOOT) - continue; - if (rec->length != sizeof(struct acpi_fpdt_header)) - continue; - - ptr = rec->ptr; - break; - } - - if (ptr == 0) - return -EINVAL; - - /* read Firmware Basic Boot Performance Data Record */ - fd = open("/dev/mem", O_CLOEXEC|O_RDONLY); - if (fd < 0) - return -errno; - - l = pread(fd, &hbrec, sizeof(struct acpi_fpdt_boot_header), ptr); - if (l != sizeof(struct acpi_fpdt_boot_header)) - return -EINVAL; - - if (memcmp(hbrec.signature, "FBPT", 4) != 0) - return -EINVAL; - - if (hbrec.length < sizeof(struct acpi_fpdt_boot_header) + sizeof(struct acpi_fpdt_boot)) - return -EINVAL; - - l = pread(fd, &brec, sizeof(struct acpi_fpdt_boot), ptr + sizeof(struct acpi_fpdt_boot_header)); - if (l != sizeof(struct acpi_fpdt_boot)) - return -EINVAL; - - if (brec.length != sizeof(struct acpi_fpdt_boot)) - return -EINVAL; - - if (brec.type != ACPI_FPDT_BOOT_REC) - return -EINVAL; - - if (brec.startup_start == 0 || brec.exit_services_exit < brec.startup_start) - return -EINVAL; - if (brec.exit_services_exit > NSEC_PER_HOUR) - return -EINVAL; - - if (loader_start) - *loader_start = brec.startup_start / 1000; - if (loader_exit) - *loader_exit = brec.exit_services_exit / 1000; - - return 0; -} diff --git a/src/shared/acpi-fpdt.h b/src/shared/acpi-fpdt.h deleted file mode 100644 index fc28175d0a..0000000000 --- a/src/shared/acpi-fpdt.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - 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 . -***/ - -#include - -int acpi_get_boot_usec(usec_t *loader_start, usec_t *loader_exit); diff --git a/src/shared/apparmor-util.c b/src/shared/apparmor-util.c deleted file mode 100644 index edd695fd23..0000000000 --- a/src/shared/apparmor-util.c +++ /dev/null @@ -1,39 +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 . -***/ - -#include - -#include "alloc-util.h" -#include "apparmor-util.h" -#include "fileio.h" -#include "parse-util.h" - -bool mac_apparmor_use(void) { - static int cached_use = -1; - - if (cached_use < 0) { - _cleanup_free_ char *p = NULL; - - cached_use = - read_one_line_file("/sys/module/apparmor/parameters/enabled", &p) >= 0 && - parse_boolean(p) > 0; - } - - return cached_use; -} diff --git a/src/shared/apparmor-util.h b/src/shared/apparmor-util.h deleted file mode 100644 index 524f740152..0000000000 --- a/src/shared/apparmor-util.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -bool mac_apparmor_use(void); diff --git a/src/shared/architecture.c b/src/shared/architecture.c deleted file mode 100644 index a9ecfc1cd6..0000000000 --- a/src/shared/architecture.c +++ /dev/null @@ -1,176 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "architecture.h" -#include "macro.h" -#include "string-table.h" -#include "string-util.h" - -int uname_architecture(void) { - - /* Return a sanitized enum identifying the architecture we are - * running on. This is based on uname(), and the user may - * hence control what this returns by using - * personality(). This puts the user in control on systems - * that can run binaries of multiple architectures. - * - * We do not translate the string returned by uname() - * 1:1. Instead we try to clean it up and break down the - * confusion on x86 and arm in particular. - * - * We do not try to distinguish CPUs not CPU features, but - * actual architectures, i.e. that have genuinely different - * code. */ - - static const struct { - const char *machine; - int arch; - } arch_map[] = { -#if defined(__x86_64__) || defined(__i386__) - { "x86_64", ARCHITECTURE_X86_64 }, - { "i686", ARCHITECTURE_X86 }, - { "i586", ARCHITECTURE_X86 }, - { "i486", ARCHITECTURE_X86 }, - { "i386", ARCHITECTURE_X86 }, -#elif defined(__powerpc__) || defined(__powerpc64__) - { "ppc64", ARCHITECTURE_PPC64 }, - { "ppc64le", ARCHITECTURE_PPC64_LE }, - { "ppc", ARCHITECTURE_PPC }, - { "ppcle", ARCHITECTURE_PPC_LE }, -#elif defined(__ia64__) - { "ia64", ARCHITECTURE_IA64 }, -#elif defined(__hppa__) || defined(__hppa64__) - { "parisc64", ARCHITECTURE_PARISC64 }, - { "parisc", ARCHITECTURE_PARISC }, -#elif defined(__s390__) || defined(__s390x__) - { "s390x", ARCHITECTURE_S390X }, - { "s390", ARCHITECTURE_S390 }, -#elif defined(__sparc__) || defined(__sparc64__) - { "sparc64", ARCHITECTURE_SPARC64 }, - { "sparc", ARCHITECTURE_SPARC }, -#elif defined(__mips__) || defined(__mips64__) - { "mips64", ARCHITECTURE_MIPS64 }, - { "mips", ARCHITECTURE_MIPS }, -#elif defined(__alpha__) - { "alpha" , ARCHITECTURE_ALPHA }, -#elif defined(__arm__) || defined(__aarch64__) - { "aarch64", ARCHITECTURE_ARM64 }, - { "aarch64_be", ARCHITECTURE_ARM64_BE }, - { "armv4l", ARCHITECTURE_ARM }, - { "armv4b", ARCHITECTURE_ARM_BE }, - { "armv4tl", ARCHITECTURE_ARM }, - { "armv4tb", ARCHITECTURE_ARM_BE }, - { "armv5tl", ARCHITECTURE_ARM }, - { "armv5tb", ARCHITECTURE_ARM_BE }, - { "armv5tel", ARCHITECTURE_ARM }, - { "armv5teb" , ARCHITECTURE_ARM_BE }, - { "armv5tejl", ARCHITECTURE_ARM }, - { "armv5tejb", ARCHITECTURE_ARM_BE }, - { "armv6l", ARCHITECTURE_ARM }, - { "armv6b", ARCHITECTURE_ARM_BE }, - { "armv7l", ARCHITECTURE_ARM }, - { "armv7b", ARCHITECTURE_ARM_BE }, - { "armv7ml", ARCHITECTURE_ARM }, - { "armv7mb", ARCHITECTURE_ARM_BE }, - { "armv4l", ARCHITECTURE_ARM }, - { "armv4b", ARCHITECTURE_ARM_BE }, - { "armv4tl", ARCHITECTURE_ARM }, - { "armv4tb", ARCHITECTURE_ARM_BE }, - { "armv5tl", ARCHITECTURE_ARM }, - { "armv5tb", ARCHITECTURE_ARM_BE }, - { "armv5tel", ARCHITECTURE_ARM }, - { "armv5teb", ARCHITECTURE_ARM_BE }, - { "armv5tejl", ARCHITECTURE_ARM }, - { "armv5tejb", ARCHITECTURE_ARM_BE }, - { "armv6l", ARCHITECTURE_ARM }, - { "armv6b", ARCHITECTURE_ARM_BE }, - { "armv7l", ARCHITECTURE_ARM }, - { "armv7b", ARCHITECTURE_ARM_BE }, - { "armv7ml", ARCHITECTURE_ARM }, - { "armv7mb", ARCHITECTURE_ARM_BE }, - { "armv8l", ARCHITECTURE_ARM }, - { "armv8b", ARCHITECTURE_ARM_BE }, -#elif defined(__sh__) || defined(__sh64__) - { "sh5", ARCHITECTURE_SH64 }, - { "sh2", ARCHITECTURE_SH }, - { "sh2a", ARCHITECTURE_SH }, - { "sh3", ARCHITECTURE_SH }, - { "sh4", ARCHITECTURE_SH }, - { "sh4a", ARCHITECTURE_SH }, -#elif defined(__m68k__) - { "m68k", ARCHITECTURE_M68K }, -#elif defined(__tilegx__) - { "tilegx", ARCHITECTURE_TILEGX }, -#elif defined(__cris__) - { "crisv32", ARCHITECTURE_CRIS }, -#else -#error "Please register your architecture here!" -#endif - }; - - static int cached = _ARCHITECTURE_INVALID; - struct utsname u; - unsigned i; - - if (cached != _ARCHITECTURE_INVALID) - return cached; - - assert_se(uname(&u) >= 0); - - for (i = 0; i < ELEMENTSOF(arch_map); i++) - if (streq(arch_map[i].machine, u.machine)) - return cached = arch_map[i].arch; - - assert_not_reached("Couldn't identify architecture. You need to patch systemd."); - return _ARCHITECTURE_INVALID; -} - -static const char *const architecture_table[_ARCHITECTURE_MAX] = { - [ARCHITECTURE_X86] = "x86", - [ARCHITECTURE_X86_64] = "x86-64", - [ARCHITECTURE_PPC] = "ppc", - [ARCHITECTURE_PPC_LE] = "ppc-le", - [ARCHITECTURE_PPC64] = "ppc64", - [ARCHITECTURE_PPC64_LE] = "ppc64-le", - [ARCHITECTURE_IA64] = "ia64", - [ARCHITECTURE_PARISC] = "parisc", - [ARCHITECTURE_PARISC64] = "parisc64", - [ARCHITECTURE_S390] = "s390", - [ARCHITECTURE_S390X] = "s390x", - [ARCHITECTURE_SPARC] = "sparc", - [ARCHITECTURE_SPARC64] = "sparc64", - [ARCHITECTURE_MIPS] = "mips", - [ARCHITECTURE_MIPS_LE] = "mips-le", - [ARCHITECTURE_MIPS64] = "mips64", - [ARCHITECTURE_MIPS64_LE] = "mips64-le", - [ARCHITECTURE_ALPHA] = "alpha", - [ARCHITECTURE_ARM] = "arm", - [ARCHITECTURE_ARM_BE] = "arm-be", - [ARCHITECTURE_ARM64] = "arm64", - [ARCHITECTURE_ARM64_BE] = "arm64-be", - [ARCHITECTURE_SH] = "sh", - [ARCHITECTURE_SH64] = "sh64", - [ARCHITECTURE_M68K] = "m68k", - [ARCHITECTURE_TILEGX] = "tilegx", - [ARCHITECTURE_CRIS] = "cris", -}; - -DEFINE_STRING_TABLE_LOOKUP(architecture, int); diff --git a/src/shared/architecture.h b/src/shared/architecture.h deleted file mode 100644 index 26679e28c6..0000000000 --- a/src/shared/architecture.h +++ /dev/null @@ -1,207 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "macro.h" -#include "util.h" - -/* A cleaned up architecture definition. We don't want to get lost in - * processor features, models, generations or even ABIs. Hence we - * focus on general family, and distinguish word width and - * endianness. */ - -enum { - ARCHITECTURE_X86 = 0, - ARCHITECTURE_X86_64, - ARCHITECTURE_PPC, - ARCHITECTURE_PPC_LE, - ARCHITECTURE_PPC64, - ARCHITECTURE_PPC64_LE, - ARCHITECTURE_IA64, - ARCHITECTURE_PARISC, - ARCHITECTURE_PARISC64, - ARCHITECTURE_S390, - ARCHITECTURE_S390X, - ARCHITECTURE_SPARC, - ARCHITECTURE_SPARC64, - ARCHITECTURE_MIPS, - ARCHITECTURE_MIPS_LE, - ARCHITECTURE_MIPS64, - ARCHITECTURE_MIPS64_LE, - ARCHITECTURE_ALPHA, - ARCHITECTURE_ARM, - ARCHITECTURE_ARM_BE, - ARCHITECTURE_ARM64, - ARCHITECTURE_ARM64_BE, - ARCHITECTURE_SH, - ARCHITECTURE_SH64, - ARCHITECTURE_M68K, - ARCHITECTURE_TILEGX, - ARCHITECTURE_CRIS, - _ARCHITECTURE_MAX, - _ARCHITECTURE_INVALID = -1 -}; - -int uname_architecture(void); - -/* - * LIB_ARCH_TUPLE should resolve to the local library path - * architecture tuple systemd is built for, according to the Debian - * tuple list: - * - * https://wiki.debian.org/Multiarch/Tuples - * - * This is used in library search paths that should understand - * Debian's paths on all distributions. - */ - -#if defined(__x86_64__) -# define native_architecture() ARCHITECTURE_X86_64 -# define LIB_ARCH_TUPLE "x86_64-linux-gnu" -# define PROC_CPUINFO_MODEL "model name" -#elif defined(__i386__) -# define native_architecture() ARCHITECTURE_X86 -# define LIB_ARCH_TUPLE "i386-linux-gnu" -# define PROC_CPUINFO_MODEL "model name" -#elif defined(__powerpc64__) -# if __BYTE_ORDER == __BIG_ENDIAN -# define native_architecture() ARCHITECTURE_PPC64 -# define LIB_ARCH_TUPLE "ppc64-linux-gnu" -# else -# define native_architecture() ARCHITECTURE_PPC64_LE -# define LIB_ARCH_TUPLE "powerpc64le-linux-gnu" -# endif -# define PROC_CPUINFO_MODEL "cpu" -#elif defined(__powerpc__) -# if __BYTE_ORDER == __BIG_ENDIAN -# define native_architecture() ARCHITECTURE_PPC -# define LIB_ARCH_TUPLE "powerpc-linux-gnu" -# else -# define native_architecture() ARCHITECTURE_PPC_LE -# error "Missing LIB_ARCH_TUPLE for PPCLE" -# endif -# define PROC_CPUINFO_MODEL "cpu" -#elif defined(__ia64__) -# define native_architecture() ARCHITECTURE_IA64 -# define LIB_ARCH_TUPLE "ia64-linux-gnu" -#elif defined(__hppa64__) -# define native_architecture() ARCHITECTURE_PARISC64 -# error "Missing LIB_ARCH_TUPLE for HPPA64" -# define PROC_CPUINFO_MODEL "cpu" -#elif defined(__hppa__) -# define native_architecture() ARCHITECTURE_PARISC -# define LIB_ARCH_TUPLE "hppa‑linux‑gnu" -# define PROC_CPUINFO_MODEL "cpu" -#elif defined(__s390x__) -# define native_architecture() ARCHITECTURE_S390X -# define LIB_ARCH_TUPLE "s390x-linux-gnu" -#elif defined(__s390__) -# define native_architecture() ARCHITECTURE_S390 -# define LIB_ARCH_TUPLE "s390-linux-gnu" -#elif defined(__sparc64__) -# define native_architecture() ARCHITECTURE_SPARC64 -# define LIB_ARCH_TUPLE "sparc64-linux-gnu" -# define PROC_CPUINFO_MODEL "cpu" -#elif defined(__sparc__) -# define native_architecture() ARCHITECTURE_SPARC -# define LIB_ARCH_TUPLE "sparc-linux-gnu" -# define PROC_CPUINFO_MODEL "cpu" -#elif defined(__mips64__) -# if __BYTE_ORDER == __BIG_ENDIAN -# define native_architecture() ARCHITECTURE_MIPS64 -# error "Missing LIB_ARCH_TUPLE for MIPS64" -# else -# define native_architecture() ARCHITECTURE_MIPS64_LE -# error "Missing LIB_ARCH_TUPLE for MIPS64_LE" -# endif -# define PROC_CPUINFO_MODEL "cpu model" -#elif defined(__mips__) -# if __BYTE_ORDER == __BIG_ENDIAN -# define native_architecture() ARCHITECTURE_MIPS -# define LIB_ARCH_TUPLE "mips-linux-gnu" -# else -# define native_architecture() ARCHITECTURE_MIPS_LE -# define LIB_ARCH_TUPLE "mipsel-linux-gnu" -# endif -# define PROC_CPUINFO_MODEL "cpu model" -#elif defined(__alpha__) -# define native_architecture() ARCHITECTURE_ALPHA -# define LIB_ARCH_TUPLE "alpha-linux-gnu" -#elif defined(__aarch64__) -# if __BYTE_ORDER == __BIG_ENDIAN -# define native_architecture() ARCHITECTURE_ARM64_BE -# define LIB_ARCH_TUPLE "aarch64_be-linux-gnu" -# else -# define native_architecture() ARCHITECTURE_ARM64 -# define LIB_ARCH_TUPLE "aarch64-linux-gnu" -# endif -#elif defined(__arm__) -# if __BYTE_ORDER == __BIG_ENDIAN -# define native_architecture() ARCHITECTURE_ARM_BE -# if defined(__ARM_EABI__) -# if defined(__ARM_PCS_VFP) -# define LIB_ARCH_TUPLE "armeb-linux-gnueabihf" -# else -# define LIB_ARCH_TUPLE "armeb-linux-gnueabi" -# endif -# else -# define LIB_ARCH_TUPLE "armeb-linux-gnu" -# endif -# else -# define native_architecture() ARCHITECTURE_ARM -# if defined(__ARM_EABI__) -# if defined(__ARM_PCS_VFP) -# define LIB_ARCH_TUPLE "arm-linux-gnueabihf" -# else -# define LIB_ARCH_TUPLE "arm-linux-gnueabi" -# endif -# else -# define LIB_ARCH_TUPLE "arm-linux-gnu" -# endif -# endif -# define PROC_CPUINFO_MODEL "model name" -#elif defined(__sh64__) -# define native_architecture() ARCHITECTURE_SH64 -# error "Missing LIB_ARCH_TUPLE for SH64" -#elif defined(__sh__) -# define native_architecture() ARCHITECTURE_SH -# define LIB_ARCH_TUPLE "sh4-linux-gnu" -#elif defined(__m68k__) -# define native_architecture() ARCHITECTURE_M68K -# define LIB_ARCH_TUPLE "m68k-linux-gnu" -#elif defined(__tilegx__) -# define native_architecture() ARCHITECTURE_TILEGX -# error "Missing LIB_ARCH_TUPLE for TILEGX" -#elif defined(__cris__) -# define native_architecture() ARCHITECTURE_CRIS -# error "Missing LIB_ARCH_TUPLE for CRIS" -#else -# error "Please register your architecture here!" -#endif - -#ifndef PROC_CPUINFO_MODEL -#warning "PROC_CPUINFO_MODEL not defined for your architecture" -#define PROC_CPUINFO_MODEL "model name" -#endif - -const char *architecture_to_string(int a) _const_; -int architecture_from_string(const char *s) _pure_; diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c deleted file mode 100644 index 6805873f9e..0000000000 --- a/src/shared/ask-password-api.c +++ /dev/null @@ -1,736 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "ask-password-api.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "io-util.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "random-util.h" -#include "signal-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "time-util.h" -#include "umask-util.h" -#include "utf8.h" -#include "util.h" - -#define KEYRING_TIMEOUT_USEC ((5 * USEC_PER_MINUTE) / 2) - -static int lookup_key(const char *keyname, key_serial_t *ret) { - key_serial_t serial; - - assert(keyname); - assert(ret); - - serial = request_key("user", keyname, NULL, 0); - if (serial == -1) - return negative_errno(); - - *ret = serial; - return 0; -} - -static int retrieve_key(key_serial_t serial, char ***ret) { - _cleanup_free_ char *p = NULL; - long m = 100, n; - char **l; - - assert(ret); - - for (;;) { - p = new(char, m); - if (!p) - return -ENOMEM; - - n = keyctl(KEYCTL_READ, (unsigned long) serial, (unsigned long) p, (unsigned long) m, 0); - if (n < 0) - return -errno; - - if (n < m) - break; - - memory_erase(p, n); - free(p); - m *= 2; - } - - l = strv_parse_nulstr(p, n); - if (!l) - return -ENOMEM; - - memory_erase(p, n); - - *ret = l; - return 0; -} - -static int add_to_keyring(const char *keyname, AskPasswordFlags flags, char **passwords) { - _cleanup_strv_free_erase_ char **l = NULL; - _cleanup_free_ char *p = NULL; - key_serial_t serial; - size_t n; - int r; - - assert(keyname); - assert(passwords); - - if (!(flags & ASK_PASSWORD_PUSH_CACHE)) - return 0; - - r = lookup_key(keyname, &serial); - if (r >= 0) { - r = retrieve_key(serial, &l); - if (r < 0) - return r; - } else if (r != -ENOKEY) - return r; - - r = strv_extend_strv(&l, passwords, true); - if (r <= 0) - return r; - - r = strv_make_nulstr(l, &p, &n); - 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); - memory_erase(p, n); - if (serial == -1) - return -errno; - - if (keyctl(KEYCTL_SET_TIMEOUT, - (unsigned long) serial, - (unsigned long) DIV_ROUND_UP(KEYRING_TIMEOUT_USEC, USEC_PER_SEC), 0, 0) < 0) - log_debug_errno(errno, "Failed to adjust timeout: %m"); - - log_debug("Added key to keyring as %" PRIi32 ".", serial); - - return 1; -} - -static int add_to_keyring_and_log(const char *keyname, AskPasswordFlags flags, char **passwords) { - int r; - - assert(keyname); - assert(passwords); - - r = add_to_keyring(keyname, flags, passwords); - if (r < 0) - return log_debug_errno(r, "Failed to add password to keyring: %m"); - - return 0; -} - -int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret) { - - key_serial_t serial; - int r; - - assert(keyname); - assert(ret); - - if (!(flags & ASK_PASSWORD_ACCEPT_CACHED)) - return -EUNATCH; - - r = lookup_key(keyname, &serial); - if (r == -ENOSYS) /* when retrieving the distinction doesn't matter */ - return -ENOKEY; - if (r < 0) - return r; - - return retrieve_key(serial, ret); -} - -static void backspace_chars(int ttyfd, size_t p) { - - if (ttyfd < 0) - return; - - while (p > 0) { - p--; - - loop_write(ttyfd, "\b \b", 3, false); - } -} - -int ask_password_tty( - const char *message, - const char *keyname, - usec_t until, - AskPasswordFlags flags, - const char *flag_file, - char **ret) { - - struct termios old_termios, new_termios; - char passphrase[LINE_MAX + 1] = {}, *x; - size_t p = 0, codepoint = 0; - int r; - _cleanup_close_ int ttyfd = -1, notify = -1; - struct pollfd pollfd[2]; - bool reset_tty = false; - bool dirty = false; - enum { - POLL_TTY, - POLL_INOTIFY - }; - - assert(ret); - - if (flags & ASK_PASSWORD_NO_TTY) - return -EUNATCH; - - if (!message) - message = "Password:"; - - if (flag_file) { - notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); - if (notify < 0) { - r = -errno; - goto finish; - } - - if (inotify_add_watch(notify, flag_file, IN_ATTRIB /* for the link count */) < 0) { - r = -errno; - goto finish; - } - } - - ttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC); - if (ttyfd >= 0) { - - if (tcgetattr(ttyfd, &old_termios) < 0) { - r = -errno; - goto finish; - } - - loop_write(ttyfd, ANSI_HIGHLIGHT, strlen(ANSI_HIGHLIGHT), false); - loop_write(ttyfd, message, strlen(message), false); - loop_write(ttyfd, " ", 1, false); - loop_write(ttyfd, ANSI_NORMAL, strlen(ANSI_NORMAL), false); - - new_termios = old_termios; - new_termios.c_lflag &= ~(ICANON|ECHO); - new_termios.c_cc[VMIN] = 1; - new_termios.c_cc[VTIME] = 0; - - if (tcsetattr(ttyfd, TCSADRAIN, &new_termios) < 0) { - r = -errno; - goto finish; - } - - reset_tty = true; - } - - zero(pollfd); - pollfd[POLL_TTY].fd = ttyfd >= 0 ? ttyfd : STDIN_FILENO; - pollfd[POLL_TTY].events = POLLIN; - pollfd[POLL_INOTIFY].fd = notify; - pollfd[POLL_INOTIFY].events = POLLIN; - - for (;;) { - char c; - int sleep_for = -1, k; - ssize_t n; - - if (until > 0) { - usec_t y; - - y = now(CLOCK_MONOTONIC); - - if (y > until) { - r = -ETIME; - goto finish; - } - - sleep_for = (int) ((until - y) / USEC_PER_MSEC); - } - - if (flag_file) - if (access(flag_file, F_OK) < 0) { - r = -errno; - goto finish; - } - - k = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for); - if (k < 0) { - if (errno == EINTR) - continue; - - r = -errno; - goto finish; - } else if (k == 0) { - r = -ETIME; - goto finish; - } - - if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0) - flush_fd(notify); - - if (pollfd[POLL_TTY].revents == 0) - continue; - - n = read(ttyfd >= 0 ? ttyfd : STDIN_FILENO, &c, 1); - if (n < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - - r = -errno; - goto finish; - - } else if (n == 0) - break; - - if (c == '\n') - break; - else if (c == 21) { /* C-u */ - - if (!(flags & ASK_PASSWORD_SILENT)) - backspace_chars(ttyfd, p); - p = 0; - - } else if (c == '\b' || c == 127) { - - if (p > 0) { - - if (!(flags & ASK_PASSWORD_SILENT)) - backspace_chars(ttyfd, 1); - - p--; - } else if (!dirty && !(flags & ASK_PASSWORD_SILENT)) { - - flags |= ASK_PASSWORD_SILENT; - - /* There are two ways to enter silent - * mode. Either by pressing backspace - * as first key (and only as first - * key), or ... */ - if (ttyfd >= 0) - loop_write(ttyfd, "(no echo) ", 10, false); - - } else if (ttyfd >= 0) - loop_write(ttyfd, "\a", 1, false); - - } else if (c == '\t' && !(flags & ASK_PASSWORD_SILENT)) { - - backspace_chars(ttyfd, p); - flags |= ASK_PASSWORD_SILENT; - - /* ... or by pressing TAB at any time. */ - - if (ttyfd >= 0) - loop_write(ttyfd, "(no echo) ", 10, false); - } else { - if (p >= sizeof(passphrase)-1) { - loop_write(ttyfd, "\a", 1, false); - continue; - } - - passphrase[p++] = c; - - if (!(flags & ASK_PASSWORD_SILENT) && ttyfd >= 0) { - n = utf8_encoded_valid_unichar(passphrase + codepoint); - if (n >= 0) { - codepoint = p; - loop_write(ttyfd, (flags & ASK_PASSWORD_ECHO) ? &c : "*", 1, false); - } - } - - dirty = true; - } - - c = 'x'; - } - - x = strndup(passphrase, p); - memory_erase(passphrase, p); - if (!x) { - r = -ENOMEM; - goto finish; - } - - if (keyname) - (void) add_to_keyring_and_log(keyname, flags, STRV_MAKE(x)); - - *ret = x; - r = 0; - -finish: - if (ttyfd >= 0 && reset_tty) { - loop_write(ttyfd, "\n", 1, false); - tcsetattr(ttyfd, TCSADRAIN, &old_termios); - } - - return r; -} - -static int create_socket(char **name) { - union sockaddr_union sa = { - .un.sun_family = AF_UNIX, - }; - _cleanup_close_ int fd = -1; - static const int one = 1; - char *c; - int r; - - assert(name); - - fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - snprintf(sa.un.sun_path, sizeof(sa.un.sun_path)-1, "/run/systemd/ask-password/sck.%" PRIx64, random_u64()); - - RUN_WITH_UMASK(0177) { - if (bind(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(sa.un.sun_path)) < 0) - return -errno; - } - - if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) - return -errno; - - c = strdup(sa.un.sun_path); - if (!c) - return -ENOMEM; - - *name = c; - - r = fd; - fd = -1; - - return r; -} - -int ask_password_agent( - const char *message, - const char *icon, - const char *id, - const char *keyname, - usec_t until, - AskPasswordFlags flags, - char ***ret) { - - enum { - FD_SOCKET, - FD_SIGNAL, - _FD_MAX - }; - - _cleanup_close_ int socket_fd = -1, signal_fd = -1, fd = -1; - char temp[] = "/run/systemd/ask-password/tmp.XXXXXX"; - char final[sizeof(temp)] = ""; - _cleanup_free_ char *socket_name = NULL; - _cleanup_strv_free_ char **l = NULL; - _cleanup_fclose_ FILE *f = NULL; - struct pollfd pollfd[_FD_MAX]; - sigset_t mask, oldmask; - int r; - - assert(ret); - - if (flags & ASK_PASSWORD_NO_AGENT) - return -EUNATCH; - - assert_se(sigemptyset(&mask) >= 0); - assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0); - assert_se(sigprocmask(SIG_BLOCK, &mask, &oldmask) >= 0); - - (void) mkdir_p_label("/run/systemd/ask-password", 0755); - - fd = mkostemp_safe(temp, O_WRONLY|O_CLOEXEC); - if (fd < 0) { - r = fd; - goto finish; - } - - (void) fchmod(fd, 0644); - - f = fdopen(fd, "w"); - if (!f) { - r = -errno; - goto finish; - } - - fd = -1; - - signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); - if (signal_fd < 0) { - r = -errno; - goto finish; - } - - socket_fd = create_socket(&socket_name); - if (socket_fd < 0) { - r = socket_fd; - goto finish; - } - - fprintf(f, - "[Ask]\n" - "PID="PID_FMT"\n" - "Socket=%s\n" - "AcceptCached=%i\n" - "Echo=%i\n" - "NotAfter="USEC_FMT"\n", - getpid(), - socket_name, - (flags & ASK_PASSWORD_ACCEPT_CACHED) ? 1 : 0, - (flags & ASK_PASSWORD_ECHO) ? 1 : 0, - until); - - if (message) - fprintf(f, "Message=%s\n", message); - - if (icon) - fprintf(f, "Icon=%s\n", icon); - - if (id) - fprintf(f, "Id=%s\n", id); - - r = fflush_and_check(f); - if (r < 0) - goto finish; - - memcpy(final, temp, sizeof(temp)); - - final[sizeof(final)-11] = 'a'; - final[sizeof(final)-10] = 's'; - final[sizeof(final)-9] = 'k'; - - if (rename(temp, final) < 0) { - r = -errno; - goto finish; - } - - zero(pollfd); - pollfd[FD_SOCKET].fd = socket_fd; - pollfd[FD_SOCKET].events = POLLIN; - pollfd[FD_SIGNAL].fd = signal_fd; - pollfd[FD_SIGNAL].events = POLLIN; - - for (;;) { - char passphrase[LINE_MAX+1]; - struct msghdr msghdr; - struct iovec iovec; - struct ucred *ucred; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; - } control; - ssize_t n; - int k; - usec_t t; - - t = now(CLOCK_MONOTONIC); - - if (until > 0 && until <= t) { - r = -ETIME; - goto finish; - } - - k = poll(pollfd, _FD_MAX, until > 0 ? (int) ((until-t)/USEC_PER_MSEC) : -1); - if (k < 0) { - if (errno == EINTR) - continue; - - r = -errno; - goto finish; - } - - if (k <= 0) { - r = -ETIME; - goto finish; - } - - if (pollfd[FD_SIGNAL].revents & POLLIN) { - r = -EINTR; - goto finish; - } - - if (pollfd[FD_SOCKET].revents != POLLIN) { - r = -EIO; - goto finish; - } - - zero(iovec); - iovec.iov_base = passphrase; - iovec.iov_len = sizeof(passphrase); - - zero(control); - zero(msghdr); - msghdr.msg_iov = &iovec; - msghdr.msg_iovlen = 1; - msghdr.msg_control = &control; - msghdr.msg_controllen = sizeof(control); - - n = recvmsg(socket_fd, &msghdr, 0); - if (n < 0) { - if (errno == EAGAIN || - errno == EINTR) - continue; - - r = -errno; - goto finish; - } - - cmsg_close_all(&msghdr); - - if (n <= 0) { - log_debug("Message too short"); - continue; - } - - if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) || - control.cmsghdr.cmsg_level != SOL_SOCKET || - control.cmsghdr.cmsg_type != SCM_CREDENTIALS || - control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) { - log_debug("Received message without credentials. Ignoring."); - continue; - } - - ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); - if (ucred->uid != 0) { - log_debug("Got request from unprivileged user. Ignoring."); - continue; - } - - if (passphrase[0] == '+') { - /* An empty message refers to the empty password */ - if (n == 1) - l = strv_new("", NULL); - else - l = strv_parse_nulstr(passphrase+1, n-1); - memory_erase(passphrase, n); - if (!l) { - r = -ENOMEM; - goto finish; - } - - if (strv_length(l) <= 0) { - l = strv_free(l); - log_debug("Invalid packet"); - continue; - } - - break; - } - - if (passphrase[0] == '-') { - r = -ECANCELED; - goto finish; - } - - log_debug("Invalid packet"); - } - - if (keyname) - (void) add_to_keyring_and_log(keyname, flags, l); - - *ret = l; - l = NULL; - r = 0; - -finish: - if (socket_name) - (void) unlink(socket_name); - - (void) unlink(temp); - - if (final[0]) - (void) unlink(final); - - assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) == 0); - return r; -} - -int ask_password_auto( - const char *message, - const char *icon, - const char *id, - const char *keyname, - usec_t until, - AskPasswordFlags flags, - char ***ret) { - - int r; - - assert(ret); - - if ((flags & ASK_PASSWORD_ACCEPT_CACHED) && keyname) { - r = ask_password_keyring(keyname, flags, ret); - if (r != -ENOKEY) - return r; - } - - if (!(flags & ASK_PASSWORD_NO_TTY) && isatty(STDIN_FILENO)) { - char *s = NULL, **l = NULL; - - r = ask_password_tty(message, keyname, until, flags, NULL, &s); - if (r < 0) - return r; - - r = strv_push(&l, s); - if (r < 0) { - string_erase(s); - free(s); - return -ENOMEM; - } - - *ret = l; - return 0; - } - - if (!(flags & ASK_PASSWORD_NO_AGENT)) - return ask_password_agent(message, icon, id, keyname, until, flags, ret); - - return -EUNATCH; -} diff --git a/src/shared/ask-password-api.h b/src/shared/ask-password-api.h deleted file mode 100644 index 9d7f65130c..0000000000 --- a/src/shared/ask-password-api.h +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "time-util.h" - -typedef enum AskPasswordFlags { - ASK_PASSWORD_ACCEPT_CACHED = 1, - ASK_PASSWORD_PUSH_CACHE = 2, - ASK_PASSWORD_ECHO = 4, /* show the password literally while reading, instead of "*" */ - ASK_PASSWORD_SILENT = 8, /* do no show any password at all while reading */ - ASK_PASSWORD_NO_TTY = 16, - ASK_PASSWORD_NO_AGENT = 32, -} AskPasswordFlags; - -int ask_password_tty(const char *message, const char *keyname, usec_t until, AskPasswordFlags flags, const char *flag_file, char **ret); -int ask_password_agent(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret); -int ask_password_keyring(const char *keyname, AskPasswordFlags flags, char ***ret); -int ask_password_auto(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret); diff --git a/src/shared/base-filesystem.c b/src/shared/base-filesystem.c deleted file mode 100644 index 59a34a9d11..0000000000 --- a/src/shared/base-filesystem.c +++ /dev/null @@ -1,129 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "base-filesystem.h" -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "umask-util.h" -#include "user-util.h" -#include "util.h" - -typedef struct BaseFilesystem { - const char *dir; - mode_t mode; - const char *target; - const char *exists; - bool ignore_failure; -} BaseFilesystem; - -static const BaseFilesystem table[] = { - { "bin", 0, "usr/bin\0", NULL }, - { "lib", 0, "usr/lib\0", NULL }, - { "root", 0755, NULL, NULL, true }, - { "sbin", 0, "usr/sbin\0", NULL }, - { "usr", 0755, NULL, NULL }, - { "var", 0755, NULL, NULL }, - { "etc", 0755, NULL, NULL }, -#if defined(__i386__) || defined(__x86_64__) - { "lib64", 0, "usr/lib/x86_64-linux-gnu\0" - "usr/lib64\0", "ld-linux-x86-64.so.2" }, -#endif -}; - -int base_filesystem_create(const char *root, uid_t uid, gid_t gid) { - _cleanup_close_ int fd = -1; - unsigned i; - int r = 0; - - fd = open(root, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (fd < 0) - return log_error_errno(errno, "Failed to open root file system: %m"); - - for (i = 0; i < ELEMENTSOF(table); i ++) { - if (faccessat(fd, table[i].dir, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) - continue; - - if (table[i].target) { - const char *target = NULL, *s; - - /* check if one of the targets exists */ - NULSTR_FOREACH(s, table[i].target) { - if (faccessat(fd, s, F_OK, AT_SYMLINK_NOFOLLOW) < 0) - continue; - - /* check if a specific file exists at the target path */ - if (table[i].exists) { - _cleanup_free_ char *p = NULL; - - p = strjoin(s, "/", table[i].exists, NULL); - if (!p) - return log_oom(); - - if (faccessat(fd, p, F_OK, AT_SYMLINK_NOFOLLOW) < 0) - continue; - } - - target = s; - break; - } - - if (!target) - continue; - - r = symlinkat(target, fd, table[i].dir); - if (r < 0 && errno != EEXIST) - return log_error_errno(errno, "Failed to create symlink at %s/%s: %m", root, table[i].dir); - - if (uid != UID_INVALID || gid != UID_INVALID) { - if (fchownat(fd, table[i].dir, uid, gid, AT_SYMLINK_NOFOLLOW) < 0) - return log_error_errno(errno, "Failed to chown symlink at %s/%s: %m", root, table[i].dir); - } - - continue; - } - - RUN_WITH_UMASK(0000) - r = mkdirat(fd, table[i].dir, table[i].mode); - if (r < 0 && errno != EEXIST) { - log_full_errno(table[i].ignore_failure ? LOG_DEBUG : LOG_ERR, errno, - "Failed to create directory at %s/%s: %m", root, table[i].dir); - - if (!table[i].ignore_failure) - return -errno; - } - - if (uid != UID_INVALID || gid != UID_INVALID) { - if (fchownat(fd, table[i].dir, uid, gid, AT_SYMLINK_NOFOLLOW) < 0) - return log_error_errno(errno, "Failed to chown directory at %s/%s: %m", root, table[i].dir); - } - } - - return 0; -} diff --git a/src/shared/base-filesystem.h b/src/shared/base-filesystem.h deleted file mode 100644 index 49599f0a60..0000000000 --- a/src/shared/base-filesystem.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -int base_filesystem_create(const char *root, uid_t uid, gid_t gid); diff --git a/src/shared/boot-timestamps.c b/src/shared/boot-timestamps.c deleted file mode 100644 index 7e0152761c..0000000000 --- a/src/shared/boot-timestamps.c +++ /dev/null @@ -1,64 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include "acpi-fpdt.h" -#include "boot-timestamps.h" -#include "efivars.h" -#include "macro.h" -#include "time-util.h" - -int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader) { - usec_t x = 0, y = 0, a; - int r; - dual_timestamp _n; - - assert(firmware); - assert(loader); - - if (!n) { - dual_timestamp_get(&_n); - n = &_n; - } - - r = acpi_get_boot_usec(&x, &y); - if (r < 0) { - r = efi_loader_get_boot_usec(&x, &y); - if (r < 0) - return r; - } - - /* Let's convert this to timestamps where the firmware - * began/loader began working. To make this more confusing: - * since usec_t is unsigned and the kernel's monotonic clock - * begins at kernel initialization we'll actually initialize - * the monotonic timestamps here as negative of the actual - * value. */ - - firmware->monotonic = y; - loader->monotonic = y - x; - - a = n->monotonic + firmware->monotonic; - firmware->realtime = n->realtime > a ? n->realtime - a : 0; - - a = n->monotonic + loader->monotonic; - loader->realtime = n->realtime > a ? n->realtime - a : 0; - - return 0; -} diff --git a/src/shared/boot-timestamps.h b/src/shared/boot-timestamps.h deleted file mode 100644 index 6f691026be..0000000000 --- a/src/shared/boot-timestamps.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include - -int boot_timestamps(const dual_timestamp *n, dual_timestamp *firmware, dual_timestamp *loader); diff --git a/src/shared/bus-util.c b/src/shared/bus-util.c deleted file mode 100644 index 38557f0b8d..0000000000 --- a/src/shared/bus-util.c +++ /dev/null @@ -1,2412 +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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-bus-protocol.h" -#include "sd-bus.h" -#include "sd-daemon.h" -#include "sd-event.h" -#include "sd-id128.h" - -#include "alloc-util.h" -#include "bus-internal.h" -#include "bus-label.h" -#include "bus-message.h" -#include "bus-util.h" -#include "cgroup-util.h" -#include "def.h" -#include "env-util.h" -#include "escape.h" -#include "extract-word.h" -#include "fd-util.h" -#include "hashmap.h" -#include "install.h" -#include "kdbus.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "parse-util.h" -#include "path-util.h" -#include "proc-cmdline.h" -#include "process-util.h" -#include "rlimit-util.h" -#include "set.h" -#include "signal-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "strv.h" -#include "syslog-util.h" -#include "time-util.h" -#include "unit-name.h" -#include "user-util.h" -#include "utf8.h" -#include "util.h" - -static int name_owner_change_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) { - sd_event *e = userdata; - - assert(m); - assert(e); - - sd_bus_close(sd_bus_message_get_bus(m)); - sd_event_exit(e, 0); - - return 1; -} - -int bus_async_unregister_and_exit(sd_event *e, sd_bus *bus, const char *name) { - _cleanup_free_ char *match = NULL; - const char *unique; - int r; - - assert(e); - assert(bus); - assert(name); - - /* We unregister the name here and then wait for the - * NameOwnerChanged signal for this event to arrive before we - * quit. We do this in order to make sure that any queued - * requests are still processed before we really exit. */ - - r = sd_bus_get_unique_name(bus, &unique); - if (r < 0) - return r; - - r = asprintf(&match, - "sender='org.freedesktop.DBus'," - "type='signal'," - "interface='org.freedesktop.DBus'," - "member='NameOwnerChanged'," - "path='/org/freedesktop/DBus'," - "arg0='%s'," - "arg1='%s'," - "arg2=''", name, unique); - if (r < 0) - return -ENOMEM; - - r = sd_bus_add_match(bus, NULL, match, name_owner_change_callback, e); - if (r < 0) - return r; - - r = sd_bus_release_name(bus, name); - if (r < 0) - return r; - - return 0; -} - -int bus_event_loop_with_idle( - sd_event *e, - sd_bus *bus, - const char *name, - usec_t timeout, - check_idle_t check_idle, - void *userdata) { - bool exiting = false; - int r, code; - - assert(e); - assert(bus); - assert(name); - - for (;;) { - bool idle; - - r = sd_event_get_state(e); - if (r < 0) - return r; - if (r == SD_EVENT_FINISHED) - break; - - if (check_idle) - idle = check_idle(userdata); - else - idle = true; - - r = sd_event_run(e, exiting || !idle ? (uint64_t) -1 : timeout); - if (r < 0) - return r; - - if (r == 0 && !exiting && idle) { - - r = sd_bus_try_close(bus); - if (r == -EBUSY) - continue; - - /* Fallback for dbus1 connections: we - * unregister the name and wait for the - * response to come through for it */ - if (r == -EOPNOTSUPP) { - - /* Inform the service manager that we - * are going down, so that it will - * queue all further start requests, - * instead of assuming we are already - * running. */ - sd_notify(false, "STOPPING=1"); - - r = bus_async_unregister_and_exit(e, bus, name); - if (r < 0) - return r; - - exiting = true; - continue; - } - - if (r < 0) - return r; - - sd_event_exit(e, 0); - break; - } - } - - r = sd_event_get_exit_code(e, &code); - if (r < 0) - return r; - - return code; -} - -int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *rep = NULL; - int r, has_owner = 0; - - assert(c); - assert(name); - - r = sd_bus_call_method(c, - "org.freedesktop.DBus", - "/org/freedesktop/dbus", - "org.freedesktop.DBus", - "NameHasOwner", - error, - &rep, - "s", - name); - if (r < 0) - return r; - - r = sd_bus_message_read_basic(rep, 'b', &has_owner); - if (r < 0) - return sd_bus_error_set_errno(error, r); - - return has_owner; -} - -static int check_good_user(sd_bus_message *m, uid_t good_user) { - _cleanup_(sd_bus_creds_unrefp) sd_bus_creds *creds = NULL; - uid_t sender_uid; - int r; - - assert(m); - - if (good_user == UID_INVALID) - return 0; - - r = sd_bus_query_sender_creds(m, SD_BUS_CREDS_EUID, &creds); - if (r < 0) - return r; - - /* Don't trust augmented credentials for authorization */ - assert_return((sd_bus_creds_get_augmented_mask(creds) & SD_BUS_CREDS_EUID) == 0, -EPERM); - - r = sd_bus_creds_get_euid(creds, &sender_uid); - if (r < 0) - return r; - - return sender_uid == good_user; -} - -int bus_test_polkit( - sd_bus_message *call, - int capability, - const char *action, - const char **details, - uid_t good_user, - bool *_challenge, - sd_bus_error *e) { - - int r; - - assert(call); - assert(action); - - /* Tests non-interactively! */ - - r = check_good_user(call, good_user); - if (r != 0) - return r; - - r = sd_bus_query_sender_privilege(call, capability); - if (r < 0) - return r; - else if (r > 0) - return 1; -#ifdef ENABLE_POLKIT - else { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *request = NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - int authorized = false, challenge = false; - const char *sender, **k, **v; - - sender = sd_bus_message_get_sender(call); - if (!sender) - return -EBADMSG; - - r = sd_bus_message_new_method_call( - call->bus, - &request, - "org.freedesktop.PolicyKit1", - "/org/freedesktop/PolicyKit1/Authority", - "org.freedesktop.PolicyKit1.Authority", - "CheckAuthorization"); - if (r < 0) - return r; - - r = sd_bus_message_append( - request, - "(sa{sv})s", - "system-bus-name", 1, "name", "s", sender, - action); - if (r < 0) - return r; - - r = sd_bus_message_open_container(request, 'a', "{ss}"); - if (r < 0) - return r; - - STRV_FOREACH_PAIR(k, v, details) { - r = sd_bus_message_append(request, "{ss}", *k, *v); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(request); - if (r < 0) - return r; - - r = sd_bus_message_append(request, "us", 0, NULL); - if (r < 0) - return r; - - r = sd_bus_call(call->bus, request, 0, e, &reply); - if (r < 0) { - /* Treat no PK available as access denied */ - if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN)) { - sd_bus_error_free(e); - return -EACCES; - } - - return r; - } - - r = sd_bus_message_enter_container(reply, 'r', "bba{ss}"); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "bb", &authorized, &challenge); - if (r < 0) - return r; - - if (authorized) - return 1; - - if (_challenge) { - *_challenge = challenge; - return 0; - } - } -#endif - - return -EACCES; -} - -#ifdef ENABLE_POLKIT - -typedef struct AsyncPolkitQuery { - sd_bus_message *request, *reply; - sd_bus_message_handler_t callback; - void *userdata; - sd_bus_slot *slot; - Hashmap *registry; -} AsyncPolkitQuery; - -static void async_polkit_query_free(AsyncPolkitQuery *q) { - - if (!q) - return; - - sd_bus_slot_unref(q->slot); - - if (q->registry && q->request) - hashmap_remove(q->registry, q->request); - - sd_bus_message_unref(q->request); - sd_bus_message_unref(q->reply); - - free(q); -} - -static int async_polkit_callback(sd_bus_message *reply, void *userdata, sd_bus_error *error) { - _cleanup_(sd_bus_error_free) sd_bus_error error_buffer = SD_BUS_ERROR_NULL; - AsyncPolkitQuery *q = userdata; - int r; - - assert(reply); - assert(q); - - q->slot = sd_bus_slot_unref(q->slot); - q->reply = sd_bus_message_ref(reply); - - r = sd_bus_message_rewind(q->request, true); - if (r < 0) { - r = sd_bus_reply_method_errno(q->request, r, NULL); - goto finish; - } - - r = q->callback(q->request, q->userdata, &error_buffer); - r = bus_maybe_reply_error(q->request, r, &error_buffer); - -finish: - async_polkit_query_free(q); - - return r; -} - -#endif - -int bus_verify_polkit_async( - sd_bus_message *call, - int capability, - const char *action, - const char **details, - bool interactive, - uid_t good_user, - Hashmap **registry, - sd_bus_error *error) { - -#ifdef ENABLE_POLKIT - _cleanup_(sd_bus_message_unrefp) sd_bus_message *pk = NULL; - AsyncPolkitQuery *q; - const char *sender, **k, **v; - sd_bus_message_handler_t callback; - void *userdata; - int c; -#endif - int r; - - assert(call); - assert(action); - assert(registry); - - r = check_good_user(call, good_user); - if (r != 0) - return r; - -#ifdef ENABLE_POLKIT - q = hashmap_get(*registry, call); - if (q) { - int authorized, challenge; - - /* This is the second invocation of this function, and - * there's already a response from polkit, let's - * process it */ - assert(q->reply); - - if (sd_bus_message_is_method_error(q->reply, NULL)) { - const sd_bus_error *e; - - /* Copy error from polkit reply */ - e = sd_bus_message_get_error(q->reply); - sd_bus_error_copy(error, e); - - /* Treat no PK available as access denied */ - if (sd_bus_error_has_name(e, SD_BUS_ERROR_SERVICE_UNKNOWN)) - return -EACCES; - - return -sd_bus_error_get_errno(e); - } - - r = sd_bus_message_enter_container(q->reply, 'r', "bba{ss}"); - if (r >= 0) - r = sd_bus_message_read(q->reply, "bb", &authorized, &challenge); - - if (r < 0) - return r; - - if (authorized) - return 1; - - if (challenge) - return sd_bus_error_set(error, SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED, "Interactive authentication required."); - - return -EACCES; - } -#endif - - r = sd_bus_query_sender_privilege(call, capability); - if (r < 0) - return r; - else if (r > 0) - return 1; - -#ifdef ENABLE_POLKIT - if (sd_bus_get_current_message(call->bus) != call) - return -EINVAL; - - callback = sd_bus_get_current_handler(call->bus); - if (!callback) - return -EINVAL; - - userdata = sd_bus_get_current_userdata(call->bus); - - sender = sd_bus_message_get_sender(call); - if (!sender) - return -EBADMSG; - - c = sd_bus_message_get_allow_interactive_authorization(call); - if (c < 0) - return c; - if (c > 0) - interactive = true; - - r = hashmap_ensure_allocated(registry, NULL); - if (r < 0) - return r; - - r = sd_bus_message_new_method_call( - call->bus, - &pk, - "org.freedesktop.PolicyKit1", - "/org/freedesktop/PolicyKit1/Authority", - "org.freedesktop.PolicyKit1.Authority", - "CheckAuthorization"); - if (r < 0) - return r; - - r = sd_bus_message_append( - pk, - "(sa{sv})s", - "system-bus-name", 1, "name", "s", sender, - action); - if (r < 0) - return r; - - r = sd_bus_message_open_container(pk, 'a', "{ss}"); - if (r < 0) - return r; - - STRV_FOREACH_PAIR(k, v, details) { - r = sd_bus_message_append(pk, "{ss}", *k, *v); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(pk); - if (r < 0) - return r; - - r = sd_bus_message_append(pk, "us", !!interactive, NULL); - if (r < 0) - return r; - - q = new0(AsyncPolkitQuery, 1); - if (!q) - return -ENOMEM; - - q->request = sd_bus_message_ref(call); - q->callback = callback; - q->userdata = userdata; - - r = hashmap_put(*registry, call, q); - if (r < 0) { - async_polkit_query_free(q); - return r; - } - - q->registry = *registry; - - r = sd_bus_call_async(call->bus, &q->slot, pk, async_polkit_callback, q, 0); - if (r < 0) { - async_polkit_query_free(q); - return r; - } - - return 0; -#endif - - return -EACCES; -} - -void bus_verify_polkit_async_registry_free(Hashmap *registry) { -#ifdef ENABLE_POLKIT - AsyncPolkitQuery *q; - - while ((q = hashmap_steal_first(registry))) - async_polkit_query_free(q); - - hashmap_free(registry); -#endif -} - -int bus_check_peercred(sd_bus *c) { - struct ucred ucred; - socklen_t l; - int fd; - - assert(c); - - fd = sd_bus_get_fd(c); - if (fd < 0) - return fd; - - l = sizeof(struct ucred); - if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &l) < 0) - return -errno; - - if (l != sizeof(struct ucred)) - return -E2BIG; - - if (ucred.uid != 0 && ucred.uid != geteuid()) - return -EPERM; - - return 1; -} - -int bus_connect_system_systemd(sd_bus **_bus) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - int r; - - assert(_bus); - - if (geteuid() != 0) - return sd_bus_default_system(_bus); - - /* If we are root and kdbus is not available, then let's talk - * directly to the system instance, instead of going via the - * bus */ - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - r = sd_bus_set_address(bus, KERNEL_SYSTEM_BUS_ADDRESS); - if (r < 0) - return r; - - bus->bus_client = true; - - r = sd_bus_start(bus); - if (r >= 0) { - *_bus = bus; - bus = NULL; - return 0; - } - - bus = sd_bus_unref(bus); - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - r = sd_bus_set_address(bus, "unix:path=/run/systemd/private"); - if (r < 0) - return r; - - r = sd_bus_start(bus); - if (r < 0) - return sd_bus_default_system(_bus); - - r = bus_check_peercred(bus); - if (r < 0) - return r; - - *_bus = bus; - bus = NULL; - - return 0; -} - -int bus_connect_user_systemd(sd_bus **_bus) { - _cleanup_(sd_bus_unrefp) sd_bus *bus = NULL; - _cleanup_free_ char *ee = NULL; - const char *e; - int r; - - /* Try via kdbus first, and then directly */ - - assert(_bus); - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - if (asprintf(&bus->address, KERNEL_USER_BUS_ADDRESS_FMT, getuid()) < 0) - return -ENOMEM; - - bus->bus_client = true; - - r = sd_bus_start(bus); - if (r >= 0) { - *_bus = bus; - bus = NULL; - return 0; - } - - bus = sd_bus_unref(bus); - - e = secure_getenv("XDG_RUNTIME_DIR"); - if (!e) - return sd_bus_default_user(_bus); - - ee = bus_address_escape(e); - if (!ee) - return -ENOMEM; - - r = sd_bus_new(&bus); - if (r < 0) - return r; - - bus->address = strjoin("unix:path=", ee, "/systemd/private", NULL); - if (!bus->address) - return -ENOMEM; - - r = sd_bus_start(bus); - if (r < 0) - return sd_bus_default_user(_bus); - - r = bus_check_peercred(bus); - if (r < 0) - return r; - - *_bus = bus; - bus = NULL; - - return 0; -} - -int bus_print_property(const char *name, sd_bus_message *property, bool all) { - char type; - const char *contents; - int r; - - assert(name); - assert(property); - - r = sd_bus_message_peek_type(property, &type, &contents); - if (r < 0) - return r; - - switch (type) { - - case SD_BUS_TYPE_STRING: { - const char *s; - - r = sd_bus_message_read_basic(property, type, &s); - if (r < 0) - return r; - - if (all || !isempty(s)) { - _cleanup_free_ char *escaped = NULL; - - escaped = xescape(s, "\n"); - if (!escaped) - return -ENOMEM; - - printf("%s=%s\n", name, escaped); - } - - return 1; - } - - case SD_BUS_TYPE_BOOLEAN: { - int b; - - r = sd_bus_message_read_basic(property, type, &b); - if (r < 0) - return r; - - printf("%s=%s\n", name, yes_no(b)); - - return 1; - } - - case SD_BUS_TYPE_UINT64: { - uint64_t u; - - r = sd_bus_message_read_basic(property, type, &u); - if (r < 0) - return r; - - /* Yes, heuristics! But we can change this check - * should it turn out to not be sufficient */ - - if (endswith(name, "Timestamp")) { - char timestamp[FORMAT_TIMESTAMP_MAX], *t; - - t = format_timestamp(timestamp, sizeof(timestamp), u); - if (t || all) - printf("%s=%s\n", name, strempty(t)); - - } else if (strstr(name, "USec")) { - char timespan[FORMAT_TIMESPAN_MAX]; - - printf("%s=%s\n", name, format_timespan(timespan, sizeof(timespan), u, 0)); - } else - printf("%s=%llu\n", name, (unsigned long long) u); - - return 1; - } - - case SD_BUS_TYPE_INT64: { - int64_t i; - - r = sd_bus_message_read_basic(property, type, &i); - if (r < 0) - return r; - - printf("%s=%lld\n", name, (long long) i); - - return 1; - } - - case SD_BUS_TYPE_UINT32: { - uint32_t u; - - r = sd_bus_message_read_basic(property, type, &u); - if (r < 0) - return r; - - if (strstr(name, "UMask") || strstr(name, "Mode")) - printf("%s=%04o\n", name, u); - else - printf("%s=%u\n", name, (unsigned) u); - - return 1; - } - - case SD_BUS_TYPE_INT32: { - int32_t i; - - r = sd_bus_message_read_basic(property, type, &i); - if (r < 0) - return r; - - printf("%s=%i\n", name, (int) i); - return 1; - } - - case SD_BUS_TYPE_DOUBLE: { - double d; - - r = sd_bus_message_read_basic(property, type, &d); - if (r < 0) - return r; - - printf("%s=%g\n", name, d); - return 1; - } - - case SD_BUS_TYPE_ARRAY: - if (streq(contents, "s")) { - bool first = true; - const char *str; - - r = sd_bus_message_enter_container(property, SD_BUS_TYPE_ARRAY, contents); - if (r < 0) - return r; - - while((r = sd_bus_message_read_basic(property, SD_BUS_TYPE_STRING, &str)) > 0) { - _cleanup_free_ char *escaped = NULL; - - if (first) - printf("%s=", name); - - escaped = xescape(str, "\n "); - if (!escaped) - return -ENOMEM; - - printf("%s%s", first ? "" : " ", escaped); - - first = false; - } - if (r < 0) - return r; - - if (first && all) - printf("%s=", name); - if (!first || all) - puts(""); - - r = sd_bus_message_exit_container(property); - if (r < 0) - return r; - - return 1; - - } else if (streq(contents, "y")) { - const uint8_t *u; - size_t n; - - r = sd_bus_message_read_array(property, SD_BUS_TYPE_BYTE, (const void**) &u, &n); - if (r < 0) - return r; - - if (all || n > 0) { - unsigned int i; - - printf("%s=", name); - - for (i = 0; i < n; i++) - printf("%02x", u[i]); - - puts(""); - } - - return 1; - - } else if (streq(contents, "u")) { - uint32_t *u; - size_t n; - - r = sd_bus_message_read_array(property, SD_BUS_TYPE_UINT32, (const void**) &u, &n); - if (r < 0) - return r; - - if (all || n > 0) { - unsigned int i; - - printf("%s=", name); - - for (i = 0; i < n; i++) - printf("%08x", u[i]); - - puts(""); - } - - return 1; - } - - break; - } - - return 0; -} - -int bus_print_all_properties(sd_bus *bus, const char *dest, const char *path, char **filter, bool all) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(path); - - r = sd_bus_call_method(bus, - dest, - path, - "org.freedesktop.DBus.Properties", - "GetAll", - &error, - &reply, - "s", ""); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); - if (r < 0) - return r; - - while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { - const char *name; - const char *contents; - - r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &name); - if (r < 0) - return r; - - if (!filter || strv_find(filter, name)) { - r = sd_bus_message_peek_type(reply, NULL, &contents); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, contents); - if (r < 0) - return r; - - r = bus_print_property(name, reply, all); - if (r < 0) - return r; - if (r == 0) { - if (all) - printf("%s=[unprintable]\n", name); - /* skip what we didn't read */ - r = sd_bus_message_skip(reply, contents); - if (r < 0) - return r; - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return r; - } else { - r = sd_bus_message_skip(reply, "v"); - if (r < 0) - return r; - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return r; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return r; - - return 0; -} - -int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - sd_id128_t *p = userdata; - const void *v; - size_t n; - int r; - - r = sd_bus_message_read_array(m, SD_BUS_TYPE_BYTE, &v, &n); - if (r < 0) - return r; - - if (n == 0) - *p = SD_ID128_NULL; - else if (n == 16) - memcpy((*p).bytes, v, n); - else - return -EINVAL; - - return 0; -} - -static int map_basic(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata) { - char type; - int r; - - r = sd_bus_message_peek_type(m, &type, NULL); - if (r < 0) - return r; - - switch (type) { - case SD_BUS_TYPE_STRING: { - const char *s; - char **p = userdata; - - r = sd_bus_message_read_basic(m, type, &s); - if (r < 0) - break; - - if (isempty(s)) - break; - - r = free_and_strdup(p, s); - break; - } - - case SD_BUS_TYPE_ARRAY: { - _cleanup_strv_free_ char **l = NULL; - char ***p = userdata; - - r = bus_message_read_strv_extend(m, &l); - if (r < 0) - break; - - strv_free(*p); - *p = l; - l = NULL; - - break; - } - - case SD_BUS_TYPE_BOOLEAN: { - unsigned b; - bool *p = userdata; - - r = sd_bus_message_read_basic(m, type, &b); - if (r < 0) - break; - - *p = b; - - break; - } - - case SD_BUS_TYPE_UINT32: { - uint64_t u; - uint32_t *p = userdata; - - r = sd_bus_message_read_basic(m, type, &u); - if (r < 0) - break; - - *p = u; - - break; - } - - case SD_BUS_TYPE_UINT64: { - uint64_t t; - uint64_t *p = userdata; - - r = sd_bus_message_read_basic(m, type, &t); - if (r < 0) - break; - - *p = t; - - break; - } - - default: - break; - } - - return r; -} - -int bus_message_map_all_properties( - sd_bus_message *m, - const struct bus_properties_map *map, - void *userdata) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(m); - assert(map); - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "{sv}"); - if (r < 0) - return r; - - while ((r = sd_bus_message_enter_container(m, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { - const struct bus_properties_map *prop; - const char *member; - const char *contents; - void *v; - unsigned i; - - r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member); - if (r < 0) - return r; - - for (i = 0, prop = NULL; map[i].member; i++) - if (streq(map[i].member, member)) { - prop = &map[i]; - break; - } - - if (prop) { - r = sd_bus_message_peek_type(m, NULL, &contents); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_VARIANT, contents); - if (r < 0) - return r; - - v = (uint8_t *)userdata + prop->offset; - if (map[i].set) - r = prop->set(sd_bus_message_get_bus(m), member, m, &error, v); - else - r = map_basic(sd_bus_message_get_bus(m), member, m, &error, v); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - } else { - r = sd_bus_message_skip(m, "v"); - if (r < 0) - return r; - } - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - } - if (r < 0) - return r; - - return sd_bus_message_exit_container(m); -} - -int bus_message_map_properties_changed( - sd_bus_message *m, - const struct bus_properties_map *map, - void *userdata) { - - const char *member; - int r, invalidated, i; - - assert(m); - assert(map); - - r = bus_message_map_all_properties(m, map, userdata); - if (r < 0) - return r; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "s"); - if (r < 0) - return r; - - invalidated = 0; - while ((r = sd_bus_message_read_basic(m, SD_BUS_TYPE_STRING, &member)) > 0) - for (i = 0; map[i].member; i++) - if (streq(map[i].member, member)) { - ++invalidated; - break; - } - if (r < 0) - return r; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return r; - - return invalidated; -} - -int bus_map_all_properties( - sd_bus *bus, - const char *destination, - const char *path, - const struct bus_properties_map *map, - void *userdata) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(destination); - assert(path); - assert(map); - - r = sd_bus_call_method( - bus, - destination, - path, - "org.freedesktop.DBus.Properties", - "GetAll", - &error, - &m, - "s", ""); - if (r < 0) - return r; - - return bus_message_map_all_properties(m, map, userdata); -} - -int bus_connect_transport(BusTransport transport, const char *host, bool user, sd_bus **bus) { - int r; - - assert(transport >= 0); - assert(transport < _BUS_TRANSPORT_MAX); - assert(bus); - - assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL); - assert_return(transport == BUS_TRANSPORT_LOCAL || !user, -EOPNOTSUPP); - - switch (transport) { - - case BUS_TRANSPORT_LOCAL: - if (user) - r = sd_bus_default_user(bus); - else - r = sd_bus_default_system(bus); - - break; - - case BUS_TRANSPORT_REMOTE: - r = sd_bus_open_system_remote(bus, host); - break; - - case BUS_TRANSPORT_MACHINE: - r = sd_bus_open_system_machine(bus, host); - break; - - default: - assert_not_reached("Hmm, unknown transport type."); - } - - return r; -} - -int bus_connect_transport_systemd(BusTransport transport, const char *host, bool user, sd_bus **bus) { - int r; - - assert(transport >= 0); - assert(transport < _BUS_TRANSPORT_MAX); - assert(bus); - - assert_return((transport == BUS_TRANSPORT_LOCAL) == !host, -EINVAL); - assert_return(transport == BUS_TRANSPORT_LOCAL || !user, -EOPNOTSUPP); - - switch (transport) { - - case BUS_TRANSPORT_LOCAL: - if (user) - r = bus_connect_user_systemd(bus); - else - r = bus_connect_system_systemd(bus); - - break; - - case BUS_TRANSPORT_REMOTE: - r = sd_bus_open_system_remote(bus, host); - break; - - case BUS_TRANSPORT_MACHINE: - r = sd_bus_open_system_machine(bus, host); - break; - - default: - assert_not_reached("Hmm, unknown transport type."); - } - - return r; -} - -int bus_property_get_bool( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - int b = *(bool*) userdata; - - return sd_bus_message_append_basic(reply, 'b', &b); -} - -#if __SIZEOF_SIZE_T__ != 8 -int bus_property_get_size( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - uint64_t sz = *(size_t*) userdata; - - return sd_bus_message_append_basic(reply, 't', &sz); -} -#endif - -#if __SIZEOF_LONG__ != 8 -int bus_property_get_long( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - int64_t l = *(long*) userdata; - - return sd_bus_message_append_basic(reply, 'x', &l); -} - -int bus_property_get_ulong( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - uint64_t ul = *(unsigned long*) userdata; - - return sd_bus_message_append_basic(reply, 't', &ul); -} -#endif - -int bus_log_parse_error(int r) { - return log_error_errno(r, "Failed to parse bus message: %m"); -} - -int bus_log_create_error(int r) { - return log_error_errno(r, "Failed to create bus message: %m"); -} - -int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u) { - assert(message); - assert(u); - - u->machine = NULL; - - return sd_bus_message_read( - message, - "(ssssssouso)", - &u->id, - &u->description, - &u->load_state, - &u->active_state, - &u->sub_state, - &u->following, - &u->unit_path, - &u->job_id, - &u->job_type, - &u->job_path); -} - -int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment) { - const char *eq, *field; - int r, rl; - - assert(m); - assert(assignment); - - eq = strchr(assignment, '='); - if (!eq) { - log_error("Not an assignment: %s", assignment); - return -EINVAL; - } - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); - if (r < 0) - return bus_log_create_error(r); - - field = strndupa(assignment, eq - assignment); - eq ++; - - if (streq(field, "CPUQuota")) { - - 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); - 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; - } - - goto finish; - - } else if (streq(field, "EnvironmentFile")) { - - r = sd_bus_message_append(m, "sv", "EnvironmentFiles", "a(sb)", 1, - eq[0] == '-' ? eq + 1 : eq, - eq[0] == '-'); - goto finish; - - } else if (STR_IN_SET(field, "AccuracySec", "RandomizedDelaySec", "RuntimeMaxSec")) { - 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); - - l = strlen(field); - n = newa(char, l + 2); - if (!n) - return log_oom(); - - /* Change suffix Sec → USec */ - strcpy(mempcpy(n, field, l - 3), "USec"); - r = sd_bus_message_append(m, "sv", n, "t", t); - goto finish; - } - - r = sd_bus_message_append_basic(m, SD_BUS_TYPE_STRING, field); - if (r < 0) - return bus_log_create_error(r); - - rl = rlimit_from_string(field); - if (rl >= 0) { - const char *sn; - struct rlimit l; - - r = rlimit_parse(rl, eq, &l); - if (r < 0) - return log_error_errno(r, "Failed to parse resource limit: %s", eq); - - r = sd_bus_message_append(m, "v", "t", l.rlim_max); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_STRUCT, "sv"); - if (r < 0) - return bus_log_create_error(r); - - sn = strjoina(field, "Soft"); - r = sd_bus_message_append(m, "sv", sn, "t", l.rlim_cur); - - } else if (STR_IN_SET(field, - "CPUAccounting", "MemoryAccounting", "BlockIOAccounting", "TasksAccounting", - "SendSIGHUP", "SendSIGKILL", "WakeSystem", "DefaultDependencies", - "IgnoreSIGPIPE", "TTYVHangup", "TTYReset", "RemainAfterExit", - "PrivateTmp", "PrivateDevices", "PrivateNetwork", "NoNewPrivileges", - "SyslogLevelPrefix", "Delegate", "RemainAfterElapse")) { - - r = parse_boolean(eq); - if (r < 0) - return log_error_errno(r, "Failed to parse boolean assignment %s.", assignment); - - r = sd_bus_message_append(m, "v", "b", r); - - } else if (streq(field, "MemoryLimit")) { - uint64_t bytes; - - if (isempty(eq) || streq(eq, "infinity")) - bytes = (uint64_t) -1; - 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; - - r = cg_cpu_shares_parse(eq, &u); - if (r < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "t", u); - - } else if (STR_IN_SET(field, "BlockIOWeight", "StartupBlockIOWeight")) { - uint64_t u; - - r = cg_cpu_shares_parse(eq, &u); - if (r < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "t", u); - - } else if (STR_IN_SET(field, - "User", "Group", "DevicePolicy", "KillMode", - "UtmpIdentifier", "UtmpMode", "PAMName", "TTYPath", - "StandardInput", "StandardOutput", "StandardError", - "Description", "Slice", "Type", "WorkingDirectory", - "RootDirectory", "SyslogIdentifier", "ProtectSystem", - "ProtectHome")) - r = sd_bus_message_append(m, "v", "s", eq); - - else if (streq(field, "SyslogLevel")) { - int level; - - level = log_level_from_string(eq); - if (level < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "i", level); - - } else if (streq(field, "SyslogFacility")) { - int facility; - - facility = log_facility_unshifted_from_string(eq); - if (facility < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "i", facility); - - } else if (streq(field, "DeviceAllow")) { - - if (isempty(eq)) - r = sd_bus_message_append(m, "v", "a(ss)", 0); - else { - const char *path, *rwm, *e; - - e = strchr(eq, ' '); - if (e) { - path = strndupa(eq, e - eq); - rwm = e+1; - } else { - path = eq; - rwm = ""; - } - - if (!path_startswith(path, "/dev")) { - log_error("%s is not a device file in /dev.", path); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "a(ss)", 1, path, rwm); - } - - } else if (STR_IN_SET(field, "BlockIOReadBandwidth", "BlockIOWriteBandwidth")) { - - if (isempty(eq)) - r = sd_bus_message_append(m, "v", "a(st)", 0); - else { - const char *path, *bandwidth, *e; - uint64_t bytes; - - e = strchr(eq, ' '); - if (e) { - path = strndupa(eq, e - eq); - bandwidth = e+1; - } else { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - if (!path_startswith(path, "/dev")) { - log_error("%s is not a device file in /dev.", path); - return -EINVAL; - } - - r = parse_size(bandwidth, 1000, &bytes); - if (r < 0) { - log_error("Failed to parse byte value %s.", bandwidth); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "a(st)", 1, path, bytes); - } - - } else if (streq(field, "BlockIODeviceWeight")) { - - if (isempty(eq)) - r = sd_bus_message_append(m, "v", "a(st)", 0); - else { - const char *path, *weight, *e; - uint64_t u; - - e = strchr(eq, ' '); - if (e) { - path = strndupa(eq, e - eq); - weight = e+1; - } else { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - if (!path_startswith(path, "/dev")) { - log_error("%s is not a device file in /dev.", path); - return -EINVAL; - } - - r = safe_atou64(weight, &u); - if (r < 0) { - log_error("Failed to parse %s value %s.", field, weight); - return -EINVAL; - } - r = sd_bus_message_append(m, "v", "a(st)", path, u); - } - - } else if (streq(field, "Nice")) { - int32_t i; - - r = safe_atoi32(eq, &i); - if (r < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "i", i); - - } else if (STR_IN_SET(field, "Environment", "PassEnvironment")) { - const char *p; - - r = sd_bus_message_open_container(m, 'v', "as"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return bus_log_create_error(r); - - p = eq; - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE); - if (r < 0) { - log_error("Failed to parse Environment value %s", eq); - return -EINVAL; - } - if (r == 0) - break; - - if (streq(field, "Environment")) { - if (!env_assignment_is_valid(word)) { - log_error("Invalid environment assignment: %s", word); - return -EINVAL; - } - } else { /* PassEnvironment */ - if (!env_name_is_valid(word)) { - log_error("Invalid environment variable name: %s", word); - return -EINVAL; - } - } - - r = sd_bus_message_append_basic(m, 's', word); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - - } else if (streq(field, "KillSignal")) { - int sig; - - sig = signal_from_string_try_harder(eq); - if (sig < 0) { - log_error("Failed to parse %s value %s.", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "i", sig); - - } else if (streq(field, "TimerSlackNSec")) { - nsec_t n; - - r = parse_nsec(eq, &n); - if (r < 0) { - log_error("Failed to parse %s value %s", field, eq); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "t", n); - } else if (streq(field, "OOMScoreAdjust")) { - int oa; - - r = safe_atoi(eq, &oa); - if (r < 0) { - log_error("Failed to parse %s value %s", field, eq); - return -EINVAL; - } - - if (!oom_score_adjust_is_valid(oa)) { - log_error("OOM score adjust value out of range"); - return -EINVAL; - } - - r = sd_bus_message_append(m, "v", "i", oa); - } else if (STR_IN_SET(field, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) { - const char *p; - - r = sd_bus_message_open_container(m, 'v', "as"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return bus_log_create_error(r); - - p = eq; - - for (;;) { - _cleanup_free_ char *word = NULL; - int offset; - - r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES); - if (r < 0) { - log_error("Failed to parse %s value %s", field, eq); - return -EINVAL; - } - if (r == 0) - break; - - if (!utf8_is_valid(word)) { - log_error("Failed to parse %s value %s", field, eq); - return -EINVAL; - } - - offset = word[0] == '-'; - if (!path_is_absolute(word + offset)) { - log_error("Failed to parse %s value %s", field, eq); - return -EINVAL; - } - - path_kill_slashes(word + offset); - - r = sd_bus_message_append_basic(m, 's', word); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - - } else if (streq(field, "RuntimeDirectory")) { - const char *p; - - r = sd_bus_message_open_container(m, 'v', "as"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return bus_log_create_error(r); - - p = eq; - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES); - if (r < 0) - return log_error_errno(r, "Failed to parse %s value %s", field, eq); - - if (r == 0) - break; - - r = sd_bus_message_append_basic(m, 's', word); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - - } else { - log_error("Unknown assignment %s.", assignment); - return -EINVAL; - } - -finish: - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - return 0; -} - -typedef struct BusWaitForJobs { - sd_bus *bus; - Set *jobs; - - char *name; - char *result; - - sd_bus_slot *slot_job_removed; - sd_bus_slot *slot_disconnected; -} BusWaitForJobs; - -static int match_disconnected(sd_bus_message *m, void *userdata, sd_bus_error *error) { - assert(m); - - log_error("Warning! D-Bus connection terminated."); - sd_bus_close(sd_bus_message_get_bus(m)); - - return 0; -} - -static int match_job_removed(sd_bus_message *m, void *userdata, sd_bus_error *error) { - const char *path, *unit, *result; - BusWaitForJobs *d = userdata; - uint32_t id; - char *found; - int r; - - assert(m); - assert(d); - - r = sd_bus_message_read(m, "uoss", &id, &path, &unit, &result); - if (r < 0) { - bus_log_parse_error(r); - return 0; - } - - found = set_remove(d->jobs, (char*) path); - if (!found) - return 0; - - free(found); - - if (!isempty(result)) - d->result = strdup(result); - - if (!isempty(unit)) - d->name = strdup(unit); - - return 0; -} - -void bus_wait_for_jobs_free(BusWaitForJobs *d) { - if (!d) - return; - - set_free_free(d->jobs); - - sd_bus_slot_unref(d->slot_disconnected); - sd_bus_slot_unref(d->slot_job_removed); - - sd_bus_unref(d->bus); - - free(d->name); - free(d->result); - - free(d); -} - -int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret) { - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *d = NULL; - int r; - - assert(bus); - assert(ret); - - d = new0(BusWaitForJobs, 1); - if (!d) - return -ENOMEM; - - d->bus = sd_bus_ref(bus); - - /* When we are a bus client we match by sender. Direct - * connections OTOH have no initialized sender field, and - * hence we ignore the sender then */ - r = sd_bus_add_match( - bus, - &d->slot_job_removed, - bus->bus_client ? - "type='signal'," - "sender='org.freedesktop.systemd1'," - "interface='org.freedesktop.systemd1.Manager'," - "member='JobRemoved'," - "path='/org/freedesktop/systemd1'" : - "type='signal'," - "interface='org.freedesktop.systemd1.Manager'," - "member='JobRemoved'," - "path='/org/freedesktop/systemd1'", - match_job_removed, d); - if (r < 0) - return r; - - r = sd_bus_add_match( - bus, - &d->slot_disconnected, - "type='signal'," - "sender='org.freedesktop.DBus.Local'," - "interface='org.freedesktop.DBus.Local'," - "member='Disconnected'", - match_disconnected, d); - if (r < 0) - return r; - - *ret = d; - d = NULL; - - return 0; -} - -static int bus_process_wait(sd_bus *bus) { - int r; - - for (;;) { - r = sd_bus_process(bus, NULL); - if (r < 0) - return r; - if (r > 0) - return 0; - - r = sd_bus_wait(bus, (uint64_t) -1); - if (r < 0) - return r; - } -} - -static int bus_job_get_service_result(BusWaitForJobs *d, char **result) { - _cleanup_free_ char *dbus_path = NULL; - - assert(d); - assert(d->name); - assert(result); - - dbus_path = unit_dbus_path_from_name(d->name); - if (!dbus_path) - return -ENOMEM; - - return sd_bus_get_property_string(d->bus, - "org.freedesktop.systemd1", - dbus_path, - "org.freedesktop.systemd1.Service", - "Result", - NULL, - result); -} - -static const struct { - const char *result, *explanation; -} explanations [] = { - { "resources", "a configured resource limit was exceeded" }, - { "timeout", "a timeout was exceeded" }, - { "exit-code", "the control process exited with error code" }, - { "signal", "a fatal signal was delivered to the control process" }, - { "core-dump", "a fatal signal was delivered causing the control process to dump core" }, - { "watchdog", "the service failed to send watchdog ping" }, - { "start-limit", "start of the service was attempted too often" } -}; - -static void log_job_error_with_service_result(const char* service, const char *result, const char *extra_args) { - _cleanup_free_ char *service_shell_quoted = NULL, *systemctl_extra_args = NULL; - - assert(service); - - service_shell_quoted = shell_maybe_quote(service); - - systemctl_extra_args = strjoin("systemctl ", extra_args, " ", NULL); - if (!systemctl_extra_args) { - log_oom(); - return; - } - - systemctl_extra_args = strstrip(systemctl_extra_args); - - if (!isempty(result)) { - unsigned i; - - for (i = 0; i < ELEMENTSOF(explanations); ++i) - if (streq(result, explanations[i].result)) - break; - - if (i < ELEMENTSOF(explanations)) { - log_error("Job for %s failed because %s. See \"%s status %s\" and \"journalctl -xe\" for details.\n", - service, - explanations[i].explanation, - systemctl_extra_args, - strna(service_shell_quoted)); - - goto finish; - } - } - - log_error("Job for %s failed. See \"%s status %s\" and \"journalctl -xe\" for details.\n", - service, - systemctl_extra_args, - strna(service_shell_quoted)); - -finish: - /* For some results maybe additional explanation is required */ - if (streq_ptr(result, "start-limit")) - log_info("To force a start use \"%1$s reset-failed %2$s\" followed by \"%1$s start %2$s\" again.", - systemctl_extra_args, - strna(service_shell_quoted)); -} - -static int check_wait_response(BusWaitForJobs *d, bool quiet, const char *extra_args) { - int r = 0; - - assert(d->result); - - if (!quiet) { - if (streq(d->result, "canceled")) - log_error("Job for %s canceled.", strna(d->name)); - else if (streq(d->result, "timeout")) - log_error("Job for %s timed out.", strna(d->name)); - else if (streq(d->result, "dependency")) - log_error("A dependency job for %s failed. See 'journalctl -xe' for details.", strna(d->name)); - else if (streq(d->result, "invalid")) - log_error("%s is not active, cannot reload.", strna(d->name)); - else if (streq(d->result, "assert")) - log_error("Assertion failed on job for %s.", strna(d->name)); - else if (streq(d->result, "unsupported")) - log_error("Operation on or unit type of %s not supported on this system.", strna(d->name)); - else if (!streq(d->result, "done") && !streq(d->result, "skipped")) { - if (d->name) { - int q; - _cleanup_free_ char *result = NULL; - - q = bus_job_get_service_result(d, &result); - if (q < 0) - log_debug_errno(q, "Failed to get Result property of service %s: %m", d->name); - - log_job_error_with_service_result(d->name, result, extra_args); - } else - log_error("Job failed. See \"journalctl -xe\" for details."); - } - } - - if (streq(d->result, "canceled")) - r = -ECANCELED; - else if (streq(d->result, "timeout")) - r = -ETIME; - else if (streq(d->result, "dependency")) - r = -EIO; - else if (streq(d->result, "invalid")) - r = -ENOEXEC; - else if (streq(d->result, "assert")) - r = -EPROTO; - else if (streq(d->result, "unsupported")) - r = -EOPNOTSUPP; - else if (!streq(d->result, "done") && !streq(d->result, "skipped")) - r = -EIO; - - return r; -} - -int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char *extra_args) { - int r = 0; - - assert(d); - - while (!set_isempty(d->jobs)) { - int q; - - q = bus_process_wait(d->bus); - if (q < 0) - return log_error_errno(q, "Failed to wait for response: %m"); - - if (d->result) { - q = check_wait_response(d, quiet, extra_args); - /* Return the first error as it is most likely to be - * meaningful. */ - if (q < 0 && r == 0) - r = q; - - log_debug_errno(q, "Got result %s/%m for job %s", strna(d->result), strna(d->name)); - } - - d->name = mfree(d->name); - d->result = mfree(d->result); - } - - return r; -} - -int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path) { - int r; - - assert(d); - - r = set_ensure_allocated(&d->jobs, &string_hash_ops); - if (r < 0) - return r; - - return set_put_strdup(d->jobs, path); -} - -int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet) { - int r; - - r = bus_wait_for_jobs_add(d, path); - if (r < 0) - return log_oom(); - - return bus_wait_for_jobs(d, quiet, NULL); -} - -int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes) { - const char *type, *path, *source; - int r; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(sss)", &type, &path, &source)) > 0) { - if (!quiet) { - if (streq(type, "symlink")) - log_info("Created symlink from %s to %s.", path, source); - else - log_info("Removed symlink %s.", path); - } - - r = unit_file_changes_add(changes, n_changes, streq(type, "symlink") ? UNIT_FILE_SYMLINK : UNIT_FILE_UNLINK, path, source); - if (r < 0) - return r; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; -} - -/** - * bus_path_encode_unique() - encode unique object path - * @b: bus connection or NULL - * @prefix: object path prefix - * @sender_id: unique-name of client, or NULL - * @external_id: external ID to be chosen by client, or NULL - * @ret_path: storage for encoded object path pointer - * - * Whenever we provide a bus API that allows clients to create and manage - * server-side objects, we need to provide a unique name for these objects. If - * we let the server choose the name, we suffer from a race condition: If a - * client creates an object asynchronously, it cannot destroy that object until - * it received the method reply. It cannot know the name of the new object, - * thus, it cannot destroy it. Furthermore, it enforces a round-trip. - * - * Therefore, many APIs allow the client to choose the unique name for newly - * created objects. There're two problems to solve, though: - * 1) Object names are usually defined via dbus object paths, which are - * usually globally namespaced. Therefore, multiple clients must be able - * to choose unique object names without interference. - * 2) If multiple libraries share the same bus connection, they must be - * able to choose unique object names without interference. - * The first problem is solved easily by prefixing a name with the - * unique-bus-name of a connection. The server side must enforce this and - * reject any other name. The second problem is solved by providing unique - * suffixes from within sd-bus. - * - * This helper allows clients to create unique object-paths. It uses the - * template '/prefix/sender_id/external_id' and returns the new path in - * @ret_path (must be freed by the caller). - * If @sender_id is NULL, the unique-name of @b is used. If @external_id is - * NULL, this function allocates a unique suffix via @b (by requesting a new - * cookie). If both @sender_id and @external_id are given, @b can be passed as - * NULL. - * - * Returns: 0 on success, negative error code on failure. - */ -int bus_path_encode_unique(sd_bus *b, const char *prefix, const char *sender_id, const char *external_id, char **ret_path) { - _cleanup_free_ char *sender_label = NULL, *external_label = NULL; - char external_buf[DECIMAL_STR_MAX(uint64_t)], *p; - int r; - - assert_return(b || (sender_id && external_id), -EINVAL); - assert_return(object_path_is_valid(prefix), -EINVAL); - assert_return(ret_path, -EINVAL); - - if (!sender_id) { - r = sd_bus_get_unique_name(b, &sender_id); - if (r < 0) - return r; - } - - if (!external_id) { - xsprintf(external_buf, "%"PRIu64, ++b->cookie); - external_id = external_buf; - } - - sender_label = bus_label_escape(sender_id); - if (!sender_label) - return -ENOMEM; - - external_label = bus_label_escape(external_id); - if (!external_label) - return -ENOMEM; - - p = strjoin(prefix, "/", sender_label, "/", external_label, NULL); - if (!p) - return -ENOMEM; - - *ret_path = p; - return 0; -} - -/** - * bus_path_decode_unique() - decode unique object path - * @path: object path to decode - * @prefix: object path prefix - * @ret_sender: output parameter for sender-id label - * @ret_external: output parameter for external-id label - * - * This does the reverse of bus_path_encode_unique() (see its description for - * details). Both trailing labels, sender-id and external-id, are unescaped and - * returned in the given output parameters (the caller must free them). - * - * Note that this function returns 0 if the path does not match the template - * (see bus_path_encode_unique()), 1 if it matched. - * - * Returns: Negative error code on failure, 0 if the given object path does not - * match the template (return parameters are set to NULL), 1 if it was - * parsed successfully (return parameters contain allocated labels). - */ -int bus_path_decode_unique(const char *path, const char *prefix, char **ret_sender, char **ret_external) { - const char *p, *q; - char *sender, *external; - - assert(object_path_is_valid(path)); - assert(object_path_is_valid(prefix)); - assert(ret_sender); - assert(ret_external); - - p = object_path_startswith(path, prefix); - if (!p) { - *ret_sender = NULL; - *ret_external = NULL; - return 0; - } - - q = strchr(p, '/'); - if (!q) { - *ret_sender = NULL; - *ret_external = NULL; - return 0; - } - - sender = bus_label_unescape_n(p, q - p); - external = bus_label_unescape(q + 1); - if (!sender || !external) { - free(sender); - free(external); - return -ENOMEM; - } - - *ret_sender = sender; - *ret_external = external; - 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, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - struct rlimit *rl; - uint64_t u; - rlim_t x; - const char *is_soft; - - assert(bus); - assert(reply); - assert(userdata); - - is_soft = endswith(property, "Soft"); - rl = *(struct rlimit**) userdata; - if (rl) - x = is_soft ? rl->rlim_cur : rl->rlim_max; - else { - struct rlimit buf = {}; - int z; - const char *s; - - s = is_soft ? strndupa(property, is_soft - property) : property; - - z = rlimit_from_string(strstr(s, "Limit")); - assert(z >= 0); - - getrlimit(z, &buf); - x = is_soft ? buf.rlim_cur : buf.rlim_max; - } - - /* rlim_t might have different sizes, let's map - * RLIMIT_INFINITY to (uint64_t) -1, so that it is the same on - * all archs */ - u = x == RLIM_INFINITY ? (uint64_t) -1 : (uint64_t) x; - - return sd_bus_message_append(reply, "t", u); -} diff --git a/src/shared/bus-util.h b/src/shared/bus-util.h deleted file mode 100644 index 204da55682..0000000000 --- a/src/shared/bus-util.h +++ /dev/null @@ -1,196 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include -#include -#include -#include - -#include "sd-bus-vtable.h" -#include "sd-bus.h" -#include "sd-event.h" - -#include "hashmap.h" -#include "install.h" -#include "macro.h" -#include "string-util.h" -#include "time-util.h" - -typedef enum BusTransport { - BUS_TRANSPORT_LOCAL, - BUS_TRANSPORT_REMOTE, - BUS_TRANSPORT_MACHINE, - _BUS_TRANSPORT_MAX, - _BUS_TRANSPORT_INVALID = -1 -} BusTransport; - -typedef int (*bus_property_set_t) (sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata); - -struct bus_properties_map { - const char *member; - const char *signature; - bus_property_set_t set; - size_t offset; -}; - -int bus_map_id128(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_error *error, void *userdata); - -int bus_message_map_all_properties(sd_bus_message *m, const struct bus_properties_map *map, void *userdata); -int bus_message_map_properties_changed(sd_bus_message *m, const struct bus_properties_map *map, void *userdata); -int bus_map_all_properties(sd_bus *bus, const char *destination, const char *path, const struct bus_properties_map *map, void *userdata); - -int bus_async_unregister_and_exit(sd_event *e, sd_bus *bus, const char *name); - -typedef bool (*check_idle_t)(void *userdata); - -int bus_event_loop_with_idle(sd_event *e, sd_bus *bus, const char *name, usec_t timeout, check_idle_t check_idle, void *userdata); - -int bus_name_has_owner(sd_bus *c, const char *name, sd_bus_error *error); - -int bus_check_peercred(sd_bus *c); - -int bus_test_polkit(sd_bus_message *call, int capability, const char *action, const char **details, uid_t good_user, bool *_challenge, sd_bus_error *e); - -int bus_verify_polkit_async(sd_bus_message *call, int capability, const char *action, const char **details, bool interactive, uid_t good_user, Hashmap **registry, sd_bus_error *error); -void bus_verify_polkit_async_registry_free(Hashmap *registry); - -int bus_connect_system_systemd(sd_bus **_bus); -int bus_connect_user_systemd(sd_bus **_bus); - -int bus_connect_transport(BusTransport transport, const char *host, bool user, sd_bus **bus); -int bus_connect_transport_systemd(BusTransport transport, const char *host, bool user, sd_bus **bus); - -int bus_print_property(const char *name, sd_bus_message *property, bool all); -int bus_print_all_properties(sd_bus *bus, const char *dest, const char *path, char **filter, bool all); - -int bus_property_get_bool(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); - -#define bus_property_get_usec ((sd_bus_property_get_t) NULL) -#define bus_property_set_usec ((sd_bus_property_set_t) NULL) - -assert_cc(sizeof(int) == sizeof(int32_t)); -#define bus_property_get_int ((sd_bus_property_get_t) NULL) - -assert_cc(sizeof(unsigned) == sizeof(unsigned)); -#define bus_property_get_unsigned ((sd_bus_property_get_t) NULL) - -/* On 64bit machines we can use the default serializer for size_t and - * friends, otherwise we need to cast this manually */ -#if __SIZEOF_SIZE_T__ == 8 -#define bus_property_get_size ((sd_bus_property_get_t) NULL) -#else -int bus_property_get_size(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); -#endif - -#if __SIZEOF_LONG__ == 8 -#define bus_property_get_long ((sd_bus_property_get_t) NULL) -#define bus_property_get_ulong ((sd_bus_property_get_t) NULL) -#else -int bus_property_get_long(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); -int bus_property_get_ulong(sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *error); -#endif - -/* uid_t and friends on Linux 32 bit. This means we can just use the - * default serializer for 32bit unsigned, for serializing it, and map - * it to NULL here */ -assert_cc(sizeof(uid_t) == sizeof(uint32_t)); -#define bus_property_get_uid ((sd_bus_property_get_t) NULL) - -assert_cc(sizeof(gid_t) == sizeof(uint32_t)); -#define bus_property_get_gid ((sd_bus_property_get_t) NULL) - -assert_cc(sizeof(pid_t) == sizeof(uint32_t)); -#define bus_property_get_pid ((sd_bus_property_get_t) NULL) - -assert_cc(sizeof(mode_t) == sizeof(uint32_t)); -#define bus_property_get_mode ((sd_bus_property_get_t) NULL) - -int bus_log_parse_error(int r); -int bus_log_create_error(int r); - -typedef struct UnitInfo { - const char *machine; - const char *id; - const char *description; - const char *load_state; - const char *active_state; - const char *sub_state; - const char *following; - const char *unit_path; - uint32_t job_id; - const char *job_type; - const char *job_path; -} UnitInfo; - -int bus_parse_unit_info(sd_bus_message *message, UnitInfo *u); - -#define BUS_DEFINE_PROPERTY_GET_ENUM(function, name, type) \ - int function(sd_bus *bus, \ - const char *path, \ - const char *interface, \ - const char *property, \ - sd_bus_message *reply, \ - void *userdata, \ - sd_bus_error *error) { \ - \ - const char *value; \ - type *field = userdata; \ - int r; \ - \ - assert(bus); \ - assert(reply); \ - assert(field); \ - \ - value = strempty(name##_to_string(*field)); \ - \ - r = sd_bus_message_append_basic(reply, 's', value); \ - if (r < 0) \ - return r; \ - \ - return 1; \ - } \ - struct __useless_struct_to_allow_trailing_semicolon__ - -#define BUS_PROPERTY_DUAL_TIMESTAMP(name, offset, flags) \ - SD_BUS_PROPERTY(name, "t", bus_property_get_usec, (offset) + offsetof(struct dual_timestamp, realtime), (flags)), \ - SD_BUS_PROPERTY(name "Monotonic", "t", bus_property_get_usec, (offset) + offsetof(struct dual_timestamp, monotonic), (flags)) - -int bus_append_unit_property_assignment(sd_bus_message *m, const char *assignment); - -typedef struct BusWaitForJobs BusWaitForJobs; - -int bus_wait_for_jobs_new(sd_bus *bus, BusWaitForJobs **ret); -void bus_wait_for_jobs_free(BusWaitForJobs *d); -int bus_wait_for_jobs_add(BusWaitForJobs *d, const char *path); -int bus_wait_for_jobs(BusWaitForJobs *d, bool quiet, const char *extra_args); -int bus_wait_for_jobs_one(BusWaitForJobs *d, const char *path, bool quiet); - -DEFINE_TRIVIAL_CLEANUP_FUNC(BusWaitForJobs*, bus_wait_for_jobs_free); - -int bus_deserialize_and_dump_unit_file_changes(sd_bus_message *m, bool quiet, UnitFileChange **changes, unsigned *n_changes); - -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/cgroup-show.c b/src/shared/cgroup-show.c deleted file mode 100644 index f3039b23f7..0000000000 --- a/src/shared/cgroup-show.c +++ /dev/null @@ -1,278 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "cgroup-show.h" -#include "cgroup-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "locale-util.h" -#include "macro.h" -#include "output-mode.h" -#include "path-util.h" -#include "process-util.h" -#include "string-util.h" -#include "terminal-util.h" - -static int compare(const void *a, const void *b) { - const pid_t *p = a, *q = b; - - if (*p < *q) - return -1; - if (*p > *q) - return 1; - return 0; -} - -static void show_pid_array(pid_t pids[], unsigned n_pids, const char *prefix, unsigned n_columns, bool extra, bool more, bool kernel_threads, OutputFlags flags) { - unsigned i, j, pid_width; - - if (n_pids == 0) - return; - - qsort(pids, n_pids, sizeof(pid_t), compare); - - /* Filter duplicates */ - for (j = 0, i = 1; i < n_pids; i++) { - if (pids[i] == pids[j]) - continue; - pids[++j] = pids[i]; - } - n_pids = j + 1; - pid_width = DECIMAL_STR_WIDTH(pids[j]); - - if (flags & OUTPUT_FULL_WIDTH) - n_columns = 0; - else { - if (n_columns > pid_width+2) - n_columns -= pid_width+2; - else - n_columns = 20; - } - for (i = 0; i < n_pids; i++) { - _cleanup_free_ char *t = NULL; - - get_process_cmdline(pids[i], n_columns, true, &t); - - if (extra) - printf("%s%s ", prefix, draw_special_char(DRAW_TRIANGULAR_BULLET)); - else - printf("%s%s", prefix, draw_special_char(((more || i < n_pids-1) ? DRAW_TREE_BRANCH : DRAW_TREE_RIGHT))); - - printf("%*"PID_PRI" %s\n", pid_width, pids[i], strna(t)); - } -} - - -static int show_cgroup_one_by_path(const char *path, const char *prefix, unsigned n_columns, bool more, bool kernel_threads, OutputFlags flags) { - char *fn; - _cleanup_fclose_ FILE *f = NULL; - size_t n = 0, n_allocated = 0; - _cleanup_free_ pid_t *pids = NULL; - _cleanup_free_ char *p = NULL; - pid_t pid; - int r; - - r = cg_mangle_path(path, &p); - if (r < 0) - return r; - - fn = strjoina(p, "/cgroup.procs"); - f = fopen(fn, "re"); - if (!f) - return -errno; - - while ((r = cg_read_pid(f, &pid)) > 0) { - - if (!kernel_threads && is_kernel_thread(pid) > 0) - continue; - - if (!GREEDY_REALLOC(pids, n_allocated, n + 1)) - return -ENOMEM; - - assert(n < n_allocated); - pids[n++] = pid; - } - - if (r < 0) - return r; - - show_pid_array(pids, n, prefix, n_columns, false, more, kernel_threads, flags); - - return 0; -} - -int show_cgroup_by_path(const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, OutputFlags flags) { - _cleanup_free_ char *fn = NULL, *p1 = NULL, *last = NULL, *p2 = NULL; - _cleanup_closedir_ DIR *d = NULL; - char *gn = NULL; - bool shown_pids = false; - int r; - - assert(path); - - if (n_columns <= 0) - n_columns = columns(); - - if (!prefix) - prefix = ""; - - r = cg_mangle_path(path, &fn); - if (r < 0) - return r; - - d = opendir(fn); - if (!d) - return -errno; - - while ((r = cg_read_subgroup(d, &gn)) > 0) { - _cleanup_free_ char *k = NULL; - - k = strjoin(fn, "/", gn, NULL); - free(gn); - if (!k) - return -ENOMEM; - - if (!(flags & OUTPUT_SHOW_ALL) && cg_is_empty_recursive(NULL, k) > 0) - continue; - - if (!shown_pids) { - show_cgroup_one_by_path(path, prefix, n_columns, true, kernel_threads, flags); - shown_pids = true; - } - - if (last) { - printf("%s%s%s\n", prefix, draw_special_char(DRAW_TREE_BRANCH), cg_unescape(basename(last))); - - if (!p1) { - p1 = strappend(prefix, draw_special_char(DRAW_TREE_VERTICAL)); - if (!p1) - return -ENOMEM; - } - - show_cgroup_by_path(last, p1, n_columns-2, kernel_threads, flags); - free(last); - } - - last = k; - k = NULL; - } - - if (r < 0) - return r; - - if (!shown_pids) - show_cgroup_one_by_path(path, prefix, n_columns, !!last, kernel_threads, flags); - - if (last) { - printf("%s%s%s\n", prefix, draw_special_char(DRAW_TREE_RIGHT), cg_unescape(basename(last))); - - if (!p2) { - p2 = strappend(prefix, " "); - if (!p2) - return -ENOMEM; - } - - show_cgroup_by_path(last, p2, n_columns-2, kernel_threads, flags); - } - - return 0; -} - -int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, OutputFlags flags) { - _cleanup_free_ char *p = NULL; - int r; - - assert(path); - - r = cg_get_path(controller, path, NULL, &p); - if (r < 0) - return r; - - return show_cgroup_by_path(p, prefix, n_columns, kernel_threads, flags); -} - -static int show_extra_pids(const char *controller, const char *path, const char *prefix, unsigned n_columns, const pid_t pids[], unsigned n_pids, OutputFlags flags) { - _cleanup_free_ pid_t *copy = NULL; - unsigned i, j; - int r; - - assert(path); - - if (n_pids <= 0) - return 0; - - if (n_columns <= 0) - n_columns = columns(); - - prefix = strempty(prefix); - - copy = new(pid_t, n_pids); - if (!copy) - return -ENOMEM; - - for (i = 0, j = 0; i < n_pids; i++) { - _cleanup_free_ char *k = NULL; - - r = cg_pid_get_path(controller, pids[i], &k); - if (r < 0) - return r; - - if (path_startswith(k, path)) - continue; - - copy[j++] = pids[i]; - } - - show_pid_array(copy, j, prefix, n_columns, true, false, false, flags); - - return 0; -} - -int show_cgroup_and_extra(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags) { - int r; - - assert(path); - - r = show_cgroup(controller, path, prefix, n_columns, kernel_threads, flags); - if (r < 0) - return r; - - return show_extra_pids(controller, path, prefix, n_columns, extra_pids, n_extra_pids, flags); -} - -int show_cgroup_and_extra_by_spec(const char *spec, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags) { - _cleanup_free_ char *controller = NULL, *path = NULL; - int r; - - assert(spec); - - r = cg_split_spec(spec, &controller, &path); - if (r < 0) - return r; - - return show_cgroup_and_extra(controller, path, prefix, n_columns, kernel_threads, extra_pids, n_extra_pids, flags); -} diff --git a/src/shared/cgroup-show.h b/src/shared/cgroup-show.h deleted file mode 100644 index 3ab7dfb33c..0000000000 --- a/src/shared/cgroup-show.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "logs-show.h" -#include "output-mode.h" - -int show_cgroup_by_path(const char *path, const char *prefix, unsigned columns, bool kernel_threads, OutputFlags flags); -int show_cgroup(const char *controller, const char *path, const char *prefix, unsigned columns, bool kernel_threads, OutputFlags flags); - -int show_cgroup_and_extra_by_spec(const char *spec, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags); -int show_cgroup_and_extra(const char *controller, const char *path, const char *prefix, unsigned n_columns, bool kernel_threads, const pid_t extra_pids[], unsigned n_extra_pids, OutputFlags flags); diff --git a/src/shared/clean-ipc.c b/src/shared/clean-ipc.c deleted file mode 100644 index a3ac7aeb82..0000000000 --- a/src/shared/clean-ipc.c +++ /dev/null @@ -1,365 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "clean-ipc.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "strv.h" - -static int clean_sysvipc_shm(uid_t delete_uid) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - bool first = true; - int ret = 0; - - f = fopen("/proc/sysvipc/shm", "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - return log_warning_errno(errno, "Failed to open /proc/sysvipc/shm: %m"); - } - - FOREACH_LINE(line, f, goto fail) { - unsigned n_attached; - pid_t cpid, lpid; - uid_t uid, cuid; - gid_t gid, cgid; - int shmid; - - if (first) { - first = false; - continue; - } - - truncate_nl(line); - - if (sscanf(line, "%*i %i %*o %*u " PID_FMT " " PID_FMT " %u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, - &shmid, &cpid, &lpid, &n_attached, &uid, &gid, &cuid, &cgid) != 8) - continue; - - if (n_attached > 0) - continue; - - if (uid != delete_uid) - continue; - - if (shmctl(shmid, IPC_RMID, NULL) < 0) { - - /* Ignore entries that are already deleted */ - if (errno == EIDRM || errno == EINVAL) - continue; - - ret = log_warning_errno(errno, - "Failed to remove SysV shared memory segment %i: %m", - shmid); - } - } - - return ret; - -fail: - return log_warning_errno(errno, "Failed to read /proc/sysvipc/shm: %m"); -} - -static int clean_sysvipc_sem(uid_t delete_uid) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - bool first = true; - int ret = 0; - - f = fopen("/proc/sysvipc/sem", "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - return log_warning_errno(errno, "Failed to open /proc/sysvipc/sem: %m"); - } - - FOREACH_LINE(line, f, goto fail) { - uid_t uid, cuid; - gid_t gid, cgid; - int semid; - - if (first) { - first = false; - continue; - } - - truncate_nl(line); - - if (sscanf(line, "%*i %i %*o %*u " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, - &semid, &uid, &gid, &cuid, &cgid) != 5) - continue; - - if (uid != delete_uid) - continue; - - if (semctl(semid, 0, IPC_RMID) < 0) { - - /* Ignore entries that are already deleted */ - if (errno == EIDRM || errno == EINVAL) - continue; - - ret = log_warning_errno(errno, - "Failed to remove SysV semaphores object %i: %m", - semid); - } - } - - return ret; - -fail: - return log_warning_errno(errno, "Failed to read /proc/sysvipc/sem: %m"); -} - -static int clean_sysvipc_msg(uid_t delete_uid) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - bool first = true; - int ret = 0; - - f = fopen("/proc/sysvipc/msg", "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - return log_warning_errno(errno, "Failed to open /proc/sysvipc/msg: %m"); - } - - FOREACH_LINE(line, f, goto fail) { - uid_t uid, cuid; - gid_t gid, cgid; - pid_t cpid, lpid; - int msgid; - - if (first) { - first = false; - continue; - } - - truncate_nl(line); - - if (sscanf(line, "%*i %i %*o %*u %*u " PID_FMT " " PID_FMT " " UID_FMT " " GID_FMT " " UID_FMT " " GID_FMT, - &msgid, &cpid, &lpid, &uid, &gid, &cuid, &cgid) != 7) - continue; - - if (uid != delete_uid) - continue; - - if (msgctl(msgid, IPC_RMID, NULL) < 0) { - - /* Ignore entries that are already deleted */ - if (errno == EIDRM || errno == EINVAL) - continue; - - ret = log_warning_errno(errno, - "Failed to remove SysV message queue %i: %m", - msgid); - } - } - - return ret; - -fail: - return log_warning_errno(errno, "Failed to read /proc/sysvipc/msg: %m"); -} - -static int clean_posix_shm_internal(DIR *dir, uid_t uid) { - struct dirent *de; - int ret = 0, r; - - assert(dir); - - FOREACH_DIRENT(de, dir, goto fail) { - struct stat st; - - if (STR_IN_SET(de->d_name, "..", ".")) - continue; - - if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - if (errno == ENOENT) - continue; - - log_warning_errno(errno, "Failed to stat() POSIX shared memory segment %s: %m", de->d_name); - ret = -errno; - continue; - } - - if (st.st_uid != uid) - continue; - - if (S_ISDIR(st.st_mode)) { - _cleanup_closedir_ DIR *kid; - - kid = xopendirat(dirfd(dir), de->d_name, O_NOFOLLOW|O_NOATIME); - if (!kid) { - if (errno != ENOENT) { - log_warning_errno(errno, "Failed to enter shared memory directory %s: %m", de->d_name); - ret = -errno; - } - } else { - r = clean_posix_shm_internal(kid, uid); - if (r < 0) - ret = r; - } - - if (unlinkat(dirfd(dir), de->d_name, AT_REMOVEDIR) < 0) { - - if (errno == ENOENT) - continue; - - log_warning_errno(errno, "Failed to remove POSIX shared memory directory %s: %m", de->d_name); - ret = -errno; - } - } else { - - if (unlinkat(dirfd(dir), de->d_name, 0) < 0) { - - if (errno == ENOENT) - continue; - - log_warning_errno(errno, "Failed to remove POSIX shared memory segment %s: %m", de->d_name); - ret = -errno; - } - } - } - - return ret; - -fail: - log_warning_errno(errno, "Failed to read /dev/shm: %m"); - return -errno; -} - -static int clean_posix_shm(uid_t uid) { - _cleanup_closedir_ DIR *dir = NULL; - - dir = opendir("/dev/shm"); - if (!dir) { - if (errno == ENOENT) - return 0; - - return log_warning_errno(errno, "Failed to open /dev/shm: %m"); - } - - return clean_posix_shm_internal(dir, uid); -} - -static int clean_posix_mq(uid_t uid) { - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *de; - int ret = 0; - - dir = opendir("/dev/mqueue"); - if (!dir) { - if (errno == ENOENT) - return 0; - - return log_warning_errno(errno, "Failed to open /dev/mqueue: %m"); - } - - FOREACH_DIRENT(de, dir, goto fail) { - struct stat st; - char fn[1+strlen(de->d_name)+1]; - - if (STR_IN_SET(de->d_name, "..", ".")) - continue; - - if (fstatat(dirfd(dir), de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - if (errno == ENOENT) - continue; - - ret = log_warning_errno(errno, - "Failed to stat() MQ segment %s: %m", - de->d_name); - continue; - } - - if (st.st_uid != uid) - continue; - - fn[0] = '/'; - strcpy(fn+1, de->d_name); - - if (mq_unlink(fn) < 0) { - if (errno == ENOENT) - continue; - - ret = log_warning_errno(errno, - "Failed to unlink POSIX message queue %s: %m", - fn); - } - } - - return ret; - -fail: - return log_warning_errno(errno, "Failed to read /dev/mqueue: %m"); -} - -int clean_ipc(uid_t uid) { - int ret = 0, r; - - /* Refuse to clean IPC of the root and system users */ - if (uid <= SYSTEM_UID_MAX) - return 0; - - r = clean_sysvipc_shm(uid); - if (r < 0) - ret = r; - - r = clean_sysvipc_sem(uid); - if (r < 0) - ret = r; - - r = clean_sysvipc_msg(uid); - if (r < 0) - ret = r; - - r = clean_posix_shm(uid); - if (r < 0) - ret = r; - - r = clean_posix_mq(uid); - if (r < 0) - ret = r; - - return ret; -} diff --git a/src/shared/clean-ipc.h b/src/shared/clean-ipc.h deleted file mode 100644 index 44a83afcf7..0000000000 --- a/src/shared/clean-ipc.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -int clean_ipc(uid_t uid); diff --git a/src/shared/condition.c b/src/shared/condition.c deleted file mode 100644 index f93785865e..0000000000 --- a/src/shared/condition.c +++ /dev/null @@ -1,541 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-id128.h" - -#include "alloc-util.h" -#include "apparmor-util.h" -#include "architecture.h" -#include "audit-util.h" -#include "cap-list.h" -#include "condition.h" -#include "extract-word.h" -#include "fd-util.h" -#include "glob-util.h" -#include "hostname-util.h" -#include "ima-util.h" -#include "list.h" -#include "macro.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "proc-cmdline.h" -#include "selinux-util.h" -#include "smack-util.h" -#include "stat-util.h" -#include "string-table.h" -#include "string-util.h" -#include "util.h" -#include "virt.h" - -Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate) { - Condition *c; - int r; - - assert(type >= 0); - assert(type < _CONDITION_TYPE_MAX); - assert((!parameter) == (type == CONDITION_NULL)); - - c = new0(Condition, 1); - if (!c) - return NULL; - - c->type = type; - c->trigger = trigger; - c->negate = negate; - - r = free_and_strdup(&c->parameter, parameter); - if (r < 0) { - free(c); - return NULL; - } - - return c; -} - -void condition_free(Condition *c) { - assert(c); - - free(c->parameter); - free(c); -} - -Condition* condition_free_list(Condition *first) { - Condition *c, *n; - - LIST_FOREACH_SAFE(conditions, c, n, first) - condition_free(c); - - return NULL; -} - -static int condition_test_kernel_command_line(Condition *c) { - _cleanup_free_ char *line = NULL; - const char *p; - bool equal; - int r; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_KERNEL_COMMAND_LINE); - - r = proc_cmdline(&line); - if (r < 0) - return r; - - equal = !!strchr(c->parameter, '='); - p = line; - - for (;;) { - _cleanup_free_ char *word = NULL; - bool found; - - r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); - if (r < 0) - return r; - if (r == 0) - break; - - if (equal) - found = streq(word, c->parameter); - else { - const char *f; - - f = startswith(word, c->parameter); - found = f && (*f == '=' || *f == 0); - } - - if (found) - return true; - } - - return false; -} - -static int condition_test_virtualization(Condition *c) { - int b, v; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_VIRTUALIZATION); - - v = detect_virtualization(); - if (v < 0) - return v; - - /* First, compare with yes/no */ - b = parse_boolean(c->parameter); - - if (v > 0 && b > 0) - return true; - - if (v == 0 && b == 0) - return true; - - /* Then, compare categorization */ - if (VIRTUALIZATION_IS_VM(v) && streq(c->parameter, "vm")) - return true; - - if (VIRTUALIZATION_IS_CONTAINER(v) && streq(c->parameter, "container")) - return true; - - /* Finally compare id */ - return v != VIRTUALIZATION_NONE && streq(c->parameter, virtualization_to_string(v)); -} - -static int condition_test_architecture(Condition *c) { - int a, b; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_ARCHITECTURE); - - a = uname_architecture(); - if (a < 0) - return a; - - if (streq(c->parameter, "native")) - b = native_architecture(); - else - b = architecture_from_string(c->parameter); - if (b < 0) - return b; - - return a == b; -} - -static int condition_test_host(Condition *c) { - _cleanup_free_ char *h = NULL; - sd_id128_t x, y; - int r; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_HOST); - - if (sd_id128_from_string(c->parameter, &x) >= 0) { - - r = sd_id128_get_machine(&y); - if (r < 0) - return r; - - return sd_id128_equal(x, y); - } - - h = gethostname_malloc(); - if (!h) - return -ENOMEM; - - return fnmatch(c->parameter, h, FNM_CASEFOLD) == 0; -} - -static int condition_test_ac_power(Condition *c) { - int r; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_AC_POWER); - - r = parse_boolean(c->parameter); - if (r < 0) - return r; - - return (on_ac_power() != 0) == !!r; -} - -static int condition_test_security(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_SECURITY); - - if (streq(c->parameter, "selinux")) - return mac_selinux_have(); - if (streq(c->parameter, "smack")) - return mac_smack_use(); - if (streq(c->parameter, "apparmor")) - return mac_apparmor_use(); - if (streq(c->parameter, "audit")) - return use_audit(); - if (streq(c->parameter, "ima")) - return use_ima(); - - return false; -} - -static int condition_test_capability(Condition *c) { - _cleanup_fclose_ FILE *f = NULL; - int value; - char line[LINE_MAX]; - unsigned long long capabilities = -1; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_CAPABILITY); - - /* If it's an invalid capability, we don't have it */ - value = capability_from_name(c->parameter); - if (value < 0) - return -EINVAL; - - /* If it's a valid capability we default to assume - * that we have it */ - - f = fopen("/proc/self/status", "re"); - if (!f) - return -errno; - - while (fgets(line, sizeof(line), f)) { - truncate_nl(line); - - if (startswith(line, "CapBnd:")) { - (void) sscanf(line+7, "%llx", &capabilities); - break; - } - } - - return !!(capabilities & (1ULL << value)); -} - -static int condition_test_needs_update(Condition *c) { - const char *p; - struct stat usr, other; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_NEEDS_UPDATE); - - /* If the file system is read-only we shouldn't suggest an update */ - if (path_is_read_only_fs(c->parameter) > 0) - return false; - - /* Any other failure means we should allow the condition to be true, - * so that we rather invoke too many update tools then too - * few. */ - - if (!path_is_absolute(c->parameter)) - return true; - - p = strjoina(c->parameter, "/.updated"); - if (lstat(p, &other) < 0) - return true; - - if (lstat("/usr/", &usr) < 0) - return true; - - return usr.st_mtim.tv_sec > other.st_mtim.tv_sec || - (usr.st_mtim.tv_sec == other.st_mtim.tv_sec && usr.st_mtim.tv_nsec > other.st_mtim.tv_nsec); -} - -static int condition_test_first_boot(Condition *c) { - int r; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_FIRST_BOOT); - - r = parse_boolean(c->parameter); - if (r < 0) - return r; - - return (access("/run/systemd/first-boot", F_OK) >= 0) == !!r; -} - -static int condition_test_path_exists(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_PATH_EXISTS); - - return access(c->parameter, F_OK) >= 0; -} - -static int condition_test_path_exists_glob(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_PATH_EXISTS_GLOB); - - return glob_exists(c->parameter) > 0; -} - -static int condition_test_path_is_directory(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_PATH_IS_DIRECTORY); - - return is_dir(c->parameter, true) > 0; -} - -static int condition_test_path_is_symbolic_link(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_PATH_IS_SYMBOLIC_LINK); - - return is_symlink(c->parameter) > 0; -} - -static int condition_test_path_is_mount_point(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_PATH_IS_MOUNT_POINT); - - return path_is_mount_point(c->parameter, AT_SYMLINK_FOLLOW) > 0; -} - -static int condition_test_path_is_read_write(Condition *c) { - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_PATH_IS_READ_WRITE); - - return path_is_read_only_fs(c->parameter) <= 0; -} - -static int condition_test_directory_not_empty(Condition *c) { - int r; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_DIRECTORY_NOT_EMPTY); - - r = dir_is_empty(c->parameter); - return r <= 0 && r != -ENOENT; -} - -static int condition_test_file_not_empty(Condition *c) { - struct stat st; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_FILE_NOT_EMPTY); - - return (stat(c->parameter, &st) >= 0 && - S_ISREG(st.st_mode) && - st.st_size > 0); -} - -static int condition_test_file_is_executable(Condition *c) { - struct stat st; - - assert(c); - assert(c->parameter); - assert(c->type == CONDITION_FILE_IS_EXECUTABLE); - - return (stat(c->parameter, &st) >= 0 && - S_ISREG(st.st_mode) && - (st.st_mode & 0111)); -} - -static int condition_test_null(Condition *c) { - assert(c); - assert(c->type == CONDITION_NULL); - - /* Note that during parsing we already evaluate the string and - * store it in c->negate */ - return true; -} - -int condition_test(Condition *c) { - - static int (*const condition_tests[_CONDITION_TYPE_MAX])(Condition *c) = { - [CONDITION_PATH_EXISTS] = condition_test_path_exists, - [CONDITION_PATH_EXISTS_GLOB] = condition_test_path_exists_glob, - [CONDITION_PATH_IS_DIRECTORY] = condition_test_path_is_directory, - [CONDITION_PATH_IS_SYMBOLIC_LINK] = condition_test_path_is_symbolic_link, - [CONDITION_PATH_IS_MOUNT_POINT] = condition_test_path_is_mount_point, - [CONDITION_PATH_IS_READ_WRITE] = condition_test_path_is_read_write, - [CONDITION_DIRECTORY_NOT_EMPTY] = condition_test_directory_not_empty, - [CONDITION_FILE_NOT_EMPTY] = condition_test_file_not_empty, - [CONDITION_FILE_IS_EXECUTABLE] = condition_test_file_is_executable, - [CONDITION_KERNEL_COMMAND_LINE] = condition_test_kernel_command_line, - [CONDITION_VIRTUALIZATION] = condition_test_virtualization, - [CONDITION_SECURITY] = condition_test_security, - [CONDITION_CAPABILITY] = condition_test_capability, - [CONDITION_HOST] = condition_test_host, - [CONDITION_AC_POWER] = condition_test_ac_power, - [CONDITION_ARCHITECTURE] = condition_test_architecture, - [CONDITION_NEEDS_UPDATE] = condition_test_needs_update, - [CONDITION_FIRST_BOOT] = condition_test_first_boot, - [CONDITION_NULL] = condition_test_null, - }; - - int r, b; - - assert(c); - assert(c->type >= 0); - assert(c->type < _CONDITION_TYPE_MAX); - - r = condition_tests[c->type](c); - if (r < 0) { - c->result = CONDITION_ERROR; - return r; - } - - b = (r > 0) == !c->negate; - c->result = b ? CONDITION_SUCCEEDED : CONDITION_FAILED; - return b; -} - -void condition_dump(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)) { - assert(c); - assert(f); - - if (!prefix) - prefix = ""; - - fprintf(f, - "%s\t%s: %s%s%s %s\n", - prefix, - to_string(c->type), - c->trigger ? "|" : "", - c->negate ? "!" : "", - c->parameter, - condition_result_to_string(c->result)); -} - -void condition_dump_list(Condition *first, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)) { - Condition *c; - - LIST_FOREACH(conditions, c, first) - condition_dump(c, f, prefix, to_string); -} - -static const char* const condition_type_table[_CONDITION_TYPE_MAX] = { - [CONDITION_ARCHITECTURE] = "ConditionArchitecture", - [CONDITION_VIRTUALIZATION] = "ConditionVirtualization", - [CONDITION_HOST] = "ConditionHost", - [CONDITION_KERNEL_COMMAND_LINE] = "ConditionKernelCommandLine", - [CONDITION_SECURITY] = "ConditionSecurity", - [CONDITION_CAPABILITY] = "ConditionCapability", - [CONDITION_AC_POWER] = "ConditionACPower", - [CONDITION_NEEDS_UPDATE] = "ConditionNeedsUpdate", - [CONDITION_FIRST_BOOT] = "ConditionFirstBoot", - [CONDITION_PATH_EXISTS] = "ConditionPathExists", - [CONDITION_PATH_EXISTS_GLOB] = "ConditionPathExistsGlob", - [CONDITION_PATH_IS_DIRECTORY] = "ConditionPathIsDirectory", - [CONDITION_PATH_IS_SYMBOLIC_LINK] = "ConditionPathIsSymbolicLink", - [CONDITION_PATH_IS_MOUNT_POINT] = "ConditionPathIsMountPoint", - [CONDITION_PATH_IS_READ_WRITE] = "ConditionPathIsReadWrite", - [CONDITION_DIRECTORY_NOT_EMPTY] = "ConditionDirectoryNotEmpty", - [CONDITION_FILE_NOT_EMPTY] = "ConditionFileNotEmpty", - [CONDITION_FILE_IS_EXECUTABLE] = "ConditionFileIsExecutable", - [CONDITION_NULL] = "ConditionNull" -}; - -DEFINE_STRING_TABLE_LOOKUP(condition_type, ConditionType); - -static const char* const assert_type_table[_CONDITION_TYPE_MAX] = { - [CONDITION_ARCHITECTURE] = "AssertArchitecture", - [CONDITION_VIRTUALIZATION] = "AssertVirtualization", - [CONDITION_HOST] = "AssertHost", - [CONDITION_KERNEL_COMMAND_LINE] = "AssertKernelCommandLine", - [CONDITION_SECURITY] = "AssertSecurity", - [CONDITION_CAPABILITY] = "AssertCapability", - [CONDITION_AC_POWER] = "AssertACPower", - [CONDITION_NEEDS_UPDATE] = "AssertNeedsUpdate", - [CONDITION_FIRST_BOOT] = "AssertFirstBoot", - [CONDITION_PATH_EXISTS] = "AssertPathExists", - [CONDITION_PATH_EXISTS_GLOB] = "AssertPathExistsGlob", - [CONDITION_PATH_IS_DIRECTORY] = "AssertPathIsDirectory", - [CONDITION_PATH_IS_SYMBOLIC_LINK] = "AssertPathIsSymbolicLink", - [CONDITION_PATH_IS_MOUNT_POINT] = "AssertPathIsMountPoint", - [CONDITION_PATH_IS_READ_WRITE] = "AssertPathIsReadWrite", - [CONDITION_DIRECTORY_NOT_EMPTY] = "AssertDirectoryNotEmpty", - [CONDITION_FILE_NOT_EMPTY] = "AssertFileNotEmpty", - [CONDITION_FILE_IS_EXECUTABLE] = "AssertFileIsExecutable", - [CONDITION_NULL] = "AssertNull" -}; - -DEFINE_STRING_TABLE_LOOKUP(assert_type, ConditionType); - -static const char* const condition_result_table[_CONDITION_RESULT_MAX] = { - [CONDITION_UNTESTED] = "untested", - [CONDITION_SUCCEEDED] = "succeeded", - [CONDITION_FAILED] = "failed", - [CONDITION_ERROR] = "error", -}; - -DEFINE_STRING_TABLE_LOOKUP(condition_result, ConditionResult); diff --git a/src/shared/condition.h b/src/shared/condition.h deleted file mode 100644 index bdda04b770..0000000000 --- a/src/shared/condition.h +++ /dev/null @@ -1,94 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "list.h" -#include "macro.h" - -typedef enum ConditionType { - CONDITION_ARCHITECTURE, - CONDITION_VIRTUALIZATION, - CONDITION_HOST, - CONDITION_KERNEL_COMMAND_LINE, - CONDITION_SECURITY, - CONDITION_CAPABILITY, - CONDITION_AC_POWER, - - CONDITION_NEEDS_UPDATE, - CONDITION_FIRST_BOOT, - - CONDITION_PATH_EXISTS, - CONDITION_PATH_EXISTS_GLOB, - CONDITION_PATH_IS_DIRECTORY, - CONDITION_PATH_IS_SYMBOLIC_LINK, - CONDITION_PATH_IS_MOUNT_POINT, - CONDITION_PATH_IS_READ_WRITE, - CONDITION_DIRECTORY_NOT_EMPTY, - CONDITION_FILE_NOT_EMPTY, - CONDITION_FILE_IS_EXECUTABLE, - - CONDITION_NULL, - - _CONDITION_TYPE_MAX, - _CONDITION_TYPE_INVALID = -1 -} ConditionType; - -typedef enum ConditionResult { - CONDITION_UNTESTED, - CONDITION_SUCCEEDED, - CONDITION_FAILED, - CONDITION_ERROR, - _CONDITION_RESULT_MAX, - _CONDITION_RESULT_INVALID = -1 -} ConditionResult; - -typedef struct Condition { - ConditionType type:8; - - bool trigger:1; - bool negate:1; - - ConditionResult result:6; - - char *parameter; - - LIST_FIELDS(struct Condition, conditions); -} Condition; - -Condition* condition_new(ConditionType type, const char *parameter, bool trigger, bool negate); -void condition_free(Condition *c); -Condition* condition_free_list(Condition *c); - -int condition_test(Condition *c); - -void condition_dump(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)); -void condition_dump_list(Condition *c, FILE *f, const char *prefix, const char *(*to_string)(ConditionType t)); - -const char* condition_type_to_string(ConditionType t) _const_; -ConditionType condition_type_from_string(const char *s) _pure_; - -const char* assert_type_to_string(ConditionType t) _const_; -ConditionType assert_type_from_string(const char *s) _pure_; - -const char* condition_result_to_string(ConditionResult r) _const_; -ConditionResult condition_result_from_string(const char *s) _pure_; diff --git a/src/shared/conf-parser.c b/src/shared/conf-parser.c deleted file mode 100644 index e7fe9ac21e..0000000000 --- a/src/shared/conf-parser.c +++ /dev/null @@ -1,870 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "conf-files.h" -#include "conf-parser.h" -#include "extract-word.h" -#include "fd-util.h" -#include "fs-util.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "signal-util.h" -#include "string-util.h" -#include "strv.h" -#include "syslog-util.h" -#include "time-util.h" -#include "utf8.h" - -int config_item_table_lookup( - const void *table, - const char *section, - const char *lvalue, - ConfigParserCallback *func, - int *ltype, - void **data, - void *userdata) { - - const ConfigTableItem *t; - - assert(table); - assert(lvalue); - assert(func); - assert(ltype); - assert(data); - - for (t = table; t->lvalue; t++) { - - if (!streq(lvalue, t->lvalue)) - continue; - - if (!streq_ptr(section, t->section)) - continue; - - *func = t->parse; - *ltype = t->ltype; - *data = t->data; - return 1; - } - - return 0; -} - -int config_item_perf_lookup( - const void *table, - const char *section, - const char *lvalue, - ConfigParserCallback *func, - int *ltype, - void **data, - void *userdata) { - - ConfigPerfItemLookup lookup = (ConfigPerfItemLookup) table; - const ConfigPerfItem *p; - - assert(table); - assert(lvalue); - assert(func); - assert(ltype); - assert(data); - - if (!section) - p = lookup(lvalue, strlen(lvalue)); - else { - char *key; - - key = strjoin(section, ".", lvalue, NULL); - if (!key) - return -ENOMEM; - - p = lookup(key, strlen(key)); - free(key); - } - - if (!p) - return 0; - - *func = p->parse; - *ltype = p->ltype; - *data = (uint8_t*) userdata + p->offset; - return 1; -} - -/* Run the user supplied parser for an assignment */ -static int next_assignment(const char *unit, - const char *filename, - unsigned line, - ConfigItemLookup lookup, - const void *table, - const char *section, - unsigned section_line, - const char *lvalue, - const char *rvalue, - bool relaxed, - void *userdata) { - - ConfigParserCallback func = NULL; - int ltype = 0; - void *data = NULL; - int r; - - assert(filename); - assert(line > 0); - assert(lookup); - assert(lvalue); - assert(rvalue); - - r = lookup(table, section, lvalue, &func, <ype, &data, userdata); - if (r < 0) - return r; - - if (r > 0) { - if (func) - return func(unit, filename, line, section, section_line, - lvalue, ltype, rvalue, data, userdata); - - return 0; - } - - /* Warn about unknown non-extension fields. */ - if (!relaxed && !startswith(lvalue, "X-")) - log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown lvalue '%s' in section '%s'", lvalue, section); - - return 0; -} - -/* Parse a variable assignment line */ -static int parse_line(const char* unit, - const char *filename, - unsigned line, - const char *sections, - ConfigItemLookup lookup, - const void *table, - bool relaxed, - bool allow_include, - char **section, - unsigned *section_line, - bool *section_ignored, - char *l, - void *userdata) { - - char *e; - - assert(filename); - assert(line > 0); - assert(lookup); - assert(l); - - l = strstrip(l); - - if (!*l) - return 0; - - if (strchr(COMMENTS "\n", *l)) - return 0; - - if (startswith(l, ".include ")) { - _cleanup_free_ char *fn = NULL; - - /* .includes are a bad idea, we only support them here - * for historical reasons. They create cyclic include - * problems and make it difficult to detect - * configuration file changes with an easy - * stat(). Better approaches, such as .d/ drop-in - * snippets exist. - * - * Support for them should be eventually removed. */ - - if (!allow_include) { - log_syntax(unit, LOG_ERR, filename, line, 0, ".include not allowed here. Ignoring."); - return 0; - } - - fn = file_in_same_dir(filename, strstrip(l+9)); - if (!fn) - return -ENOMEM; - - return config_parse(unit, fn, NULL, sections, lookup, table, relaxed, false, false, userdata); - } - - if (*l == '[') { - size_t k; - char *n; - - k = strlen(l); - assert(k > 0); - - if (l[k-1] != ']') { - log_syntax(unit, LOG_ERR, filename, line, 0, "Invalid section header '%s'", l); - return -EBADMSG; - } - - n = strndup(l+1, k-2); - if (!n) - return -ENOMEM; - - if (sections && !nulstr_contains(sections, n)) { - - if (!relaxed && !startswith(n, "X-")) - log_syntax(unit, LOG_WARNING, filename, line, 0, "Unknown section '%s'. Ignoring.", n); - - free(n); - *section = mfree(*section); - *section_line = 0; - *section_ignored = true; - } else { - free(*section); - *section = n; - *section_line = line; - *section_ignored = false; - } - - return 0; - } - - if (sections && !*section) { - - if (!relaxed && !*section_ignored) - log_syntax(unit, LOG_WARNING, filename, line, 0, "Assignment outside of section. Ignoring."); - - return 0; - } - - e = strchr(l, '='); - if (!e) { - log_syntax(unit, LOG_WARNING, filename, line, 0, "Missing '='."); - return -EINVAL; - } - - *e = 0; - e++; - - return next_assignment(unit, - filename, - line, - lookup, - table, - *section, - *section_line, - strstrip(l), - strstrip(e), - relaxed, - userdata); -} - -/* Go through the file and parse each line */ -int config_parse(const char *unit, - const char *filename, - FILE *f, - const char *sections, - ConfigItemLookup lookup, - const void *table, - bool relaxed, - bool allow_include, - bool warn, - void *userdata) { - - _cleanup_free_ char *section = NULL, *continuation = NULL; - _cleanup_fclose_ FILE *ours = NULL; - unsigned line = 0, section_line = 0; - bool section_ignored = false; - int r; - - assert(filename); - assert(lookup); - - if (!f) { - f = ours = fopen(filename, "re"); - if (!f) { - /* Only log on request, except for ENOENT, - * since we return 0 to the caller. */ - if (warn || errno == ENOENT) - log_full(errno == ENOENT ? LOG_DEBUG : LOG_ERR, - "Failed to open configuration file '%s': %m", filename); - return errno == ENOENT ? 0 : -errno; - } - } - - fd_warn_permissions(filename, fileno(f)); - - while (!feof(f)) { - char l[LINE_MAX], *p, *c = NULL, *e; - bool escaped = false; - - if (!fgets(l, sizeof(l), f)) { - if (feof(f)) - break; - - log_error_errno(errno, "Failed to read configuration file '%s': %m", filename); - return -errno; - } - - truncate_nl(l); - - if (continuation) { - c = strappend(continuation, l); - if (!c) { - if (warn) - log_oom(); - return -ENOMEM; - } - - continuation = mfree(continuation); - p = c; - } else - p = l; - - for (e = p; *e; e++) { - if (escaped) - escaped = false; - else if (*e == '\\') - escaped = true; - } - - if (escaped) { - *(e-1) = ' '; - - if (c) - continuation = c; - else { - continuation = strdup(l); - if (!continuation) { - if (warn) - log_oom(); - return -ENOMEM; - } - } - - continue; - } - - r = parse_line(unit, - filename, - ++line, - sections, - lookup, - table, - relaxed, - allow_include, - §ion, - §ion_line, - §ion_ignored, - p, - userdata); - free(c); - - if (r < 0) { - if (warn) - log_warning_errno(r, "Failed to parse file '%s': %m", - filename); - return r; - } - } - - return 0; -} - -/* Parse each config file in the specified directories. */ -int config_parse_many(const char *conf_file, - const char *conf_file_dirs, - const char *sections, - ConfigItemLookup lookup, - const void *table, - bool relaxed, - void *userdata) { - _cleanup_strv_free_ char **files = NULL; - char **fn; - int r; - - r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs); - if (r < 0) - return r; - - if (conf_file) { - r = config_parse(NULL, conf_file, NULL, sections, lookup, table, relaxed, false, true, userdata); - if (r < 0) - return r; - } - - STRV_FOREACH(fn, files) { - r = config_parse(NULL, *fn, NULL, sections, lookup, table, relaxed, false, true, userdata); - if (r < 0) - return r; - } - - return 0; -} - -#define DEFINE_PARSER(type, vartype, conv_func) \ - int config_parse_##type( \ - 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) { \ - \ - vartype *i = data; \ - int r; \ - \ - assert(filename); \ - assert(lvalue); \ - assert(rvalue); \ - assert(data); \ - \ - r = conv_func(rvalue, i); \ - if (r < 0) \ - log_syntax(unit, LOG_ERR, filename, line, r, \ - "Failed to parse %s value, ignoring: %s", \ - #type, rvalue); \ - \ - return 0; \ - } \ - struct __useless_struct_to_allow_trailing_semicolon__ - -DEFINE_PARSER(int, int, safe_atoi); -DEFINE_PARSER(long, long, safe_atoli); -DEFINE_PARSER(uint32, uint32_t, safe_atou32); -DEFINE_PARSER(uint64, uint64_t, safe_atou64); -DEFINE_PARSER(unsigned, unsigned, safe_atou); -DEFINE_PARSER(double, double, safe_atod); -DEFINE_PARSER(nsec, nsec_t, parse_nsec); -DEFINE_PARSER(sec, usec_t, parse_sec); -DEFINE_PARSER(mode, mode_t, parse_mode); - -int config_parse_iec_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) { - - size_t *sz = data; - uint64_t v; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = parse_size(rvalue, 1024, &v); - if (r < 0 || (uint64_t) (size_t) v != v) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue); - return 0; - } - - *sz = (size_t) v; - return 0; -} - -int config_parse_si_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) { - - size_t *sz = data; - uint64_t v; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = parse_size(rvalue, 1000, &v); - if (r < 0 || (uint64_t) (size_t) v != v) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue); - return 0; - } - - *sz = (size_t) v; - return 0; -} - -int config_parse_iec_uint64(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 *bytes = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = parse_size(rvalue, 1024, bytes); - if (r < 0) - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse size value, ignoring: %s", rvalue); - - return 0; -} - -int config_parse_bool(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 k; - bool *b = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - k = parse_boolean(rvalue); - if (k < 0) { - log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue); - return 0; - } - - *b = !!k; - return 0; -} - -int config_parse_tristate( - 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 k, *t = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - /* A tristate is pretty much a boolean, except that it can - * also take the special value -1, indicating "uninitialized", - * much like NULL is for a pointer type. */ - - k = parse_boolean(rvalue); - if (k < 0) { - log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse boolean value, ignoring: %s", rvalue); - return 0; - } - - *t = !!k; - return 0; -} - -int config_parse_string( - 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 **s = data, *n; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (!utf8_is_valid(rvalue)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); - return 0; - } - - if (isempty(rvalue)) - n = NULL; - else { - n = strdup(rvalue); - if (!n) - return log_oom(); - } - - free(*s); - *s = n; - - return 0; -} - -int config_parse_path( - 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 **s = data, *n; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (!utf8_is_valid(rvalue)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); - return 0; - } - - if (!path_is_absolute(rvalue)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Not an absolute path, ignoring: %s", rvalue); - return 0; - } - - n = strdup(rvalue); - if (!n) - return log_oom(); - - path_kill_slashes(n); - - free(*s); - *s = n; - - return 0; -} - -int config_parse_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 ***sv = data; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - if (isempty(rvalue)) { - char **empty; - - /* Empty assignment resets the list. As a special rule - * 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); - 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); - 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; - } - - if (!utf8_is_valid(word)) { - log_syntax_invalid_utf8(unit, LOG_ERR, filename, line, rvalue); - free(word); - continue; - } - r = strv_consume(sv, word); - if (r < 0) - return log_oom(); - } - - return 0; -} - -int config_parse_log_facility( - 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 *o = data, x; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - x = log_facility_unshifted_from_string(rvalue); - if (x < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse log facility, ignoring: %s", rvalue); - return 0; - } - - *o = (x << 3) | LOG_PRI(*o); - - return 0; -} - -int config_parse_log_level( - 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 *o = data, x; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - x = log_level_from_string(rvalue); - if (x < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse log level, ignoring: %s", rvalue); - return 0; - } - - *o = (*o & LOG_FACMASK) | x; - return 0; -} - -int config_parse_signal( - 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 *sig = data, r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(sig); - - r = signal_from_string_try_harder(rvalue); - if (r <= 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse signal name, ignoring: %s", rvalue); - return 0; - } - - *sig = r; - return 0; -} - -int config_parse_personality( - 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) { - - unsigned long *personality = data, p; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(personality); - - p = personality_from_string(rvalue); - if (p == PERSONALITY_INVALID) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse personality, ignoring: %s", rvalue); - return 0; - } - - *personality = p; - return 0; -} diff --git a/src/shared/conf-parser.h b/src/shared/conf-parser.h deleted file mode 100644 index a91c94c322..0000000000 --- a/src/shared/conf-parser.h +++ /dev/null @@ -1,228 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "log.h" -#include "macro.h" - -/* An abstract parser for simple, line based, shallow configuration - * files consisting of variable assignments only. */ - -/* Prototype for a parser for a specific configuration setting */ -typedef int (*ConfigParserCallback)(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); - -/* Wraps information for parsing a specific configuration variable, to - * be stored in a simple array */ -typedef struct ConfigTableItem { - const char *section; /* Section */ - const char *lvalue; /* Name of the variable */ - ConfigParserCallback parse; /* Function that is called to parse the variable's value */ - int ltype; /* Distinguish different variables passed to the same callback */ - void *data; /* Where to store the variable's data */ -} ConfigTableItem; - -/* Wraps information for parsing a specific configuration variable, to - * be stored in a gperf perfect hashtable */ -typedef struct ConfigPerfItem { - const char *section_and_lvalue; /* Section + "." + name of the variable */ - ConfigParserCallback parse; /* Function that is called to parse the variable's value */ - int ltype; /* Distinguish different variables passed to the same callback */ - size_t offset; /* Offset where to store data, from the beginning of userdata */ -} ConfigPerfItem; - -/* Prototype for a low-level gperf lookup function */ -typedef const ConfigPerfItem* (*ConfigPerfItemLookup)(const char *section_and_lvalue, unsigned length); - -/* Prototype for a generic high-level lookup function */ -typedef int (*ConfigItemLookup)( - const void *table, - const char *section, - const char *lvalue, - ConfigParserCallback *func, - int *ltype, - void **data, - void *userdata); - -/* Linear table search implementation of ConfigItemLookup, based on - * ConfigTableItem arrays */ -int config_item_table_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata); - -/* gperf implementation of ConfigItemLookup, based on gperf - * ConfigPerfItem tables */ -int config_item_perf_lookup(const void *table, const char *section, const char *lvalue, ConfigParserCallback *func, int *ltype, void **data, void *userdata); - -int config_parse(const char *unit, - const char *filename, - FILE *f, - const char *sections, /* nulstr */ - ConfigItemLookup lookup, - const void *table, - bool relaxed, - bool allow_include, - bool warn, - void *userdata); - -int config_parse_many(const char *conf_file, /* possibly NULL */ - const char *conf_file_dirs, /* nulstr */ - const char *sections, /* nulstr */ - ConfigItemLookup lookup, - const void *table, - bool relaxed, - void *userdata); - -/* Generic parsers */ -int config_parse_int(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_unsigned(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_long(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_uint32(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_uint64(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_double(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_iec_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_si_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_iec_uint64(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_bool(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_tristate(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_string(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_path(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_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); -int config_parse_sec(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_nsec(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_mode(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_log_facility(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_log_level(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_signal(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_personality(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); - -#define DEFINE_CONFIG_PARSE_ENUM(function,name,type,msg) \ - int function(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) { \ - \ - type *i = data, x; \ - \ - assert(filename); \ - assert(lvalue); \ - assert(rvalue); \ - assert(data); \ - \ - if ((x = name##_from_string(rvalue)) < 0) { \ - log_syntax(unit, LOG_ERR, filename, line, -x, \ - msg ", ignoring: %s", rvalue); \ - return 0; \ - } \ - \ - *i = x; \ - return 0; \ - } - -#define DEFINE_CONFIG_PARSE_ENUMV(function,name,type,invalid,msg) \ - int function(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) { \ - \ - type **enums = data, x, *ys; \ - _cleanup_free_ type *xs = NULL; \ - const char *word, *state; \ - size_t l, i = 0; \ - \ - assert(filename); \ - assert(lvalue); \ - assert(rvalue); \ - assert(data); \ - \ - xs = new0(type, 1); \ - if(!xs) \ - return -ENOMEM; \ - \ - *xs = invalid; \ - \ - FOREACH_WORD(word, l, rvalue, state) { \ - _cleanup_free_ char *en = NULL; \ - type *new_xs; \ - \ - en = strndup(word, l); \ - if (!en) \ - return -ENOMEM; \ - \ - if ((x = name##_from_string(en)) < 0) { \ - log_syntax(unit, LOG_ERR, filename, line, \ - -x, msg ", ignoring: %s", en); \ - continue; \ - } \ - \ - for (ys = xs; x != invalid && *ys != invalid; ys++) { \ - if (*ys == x) { \ - log_syntax(unit, LOG_ERR, filename, \ - line, -x, \ - "Duplicate entry, ignoring: %s", \ - en); \ - x = invalid; \ - } \ - } \ - \ - if (x == invalid) \ - continue; \ - \ - *(xs + i) = x; \ - new_xs = realloc(xs, (++i + 1) * sizeof(type)); \ - if (new_xs) \ - xs = new_xs; \ - else \ - return -ENOMEM; \ - \ - *(xs + i) = invalid; \ - } \ - \ - free(*enums); \ - *enums = xs; \ - xs = NULL; \ - \ - return 0; \ - } diff --git a/src/shared/dev-setup.c b/src/shared/dev-setup.c deleted file mode 100644 index b2d464c117..0000000000 --- a/src/shared/dev-setup.c +++ /dev/null @@ -1,73 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-2012 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 . -***/ - -#include -#include -#include - -#include "alloc-util.h" -#include "dev-setup.h" -#include "label.h" -#include "log.h" -#include "path-util.h" -#include "user-util.h" -#include "util.h" - -int dev_setup(const char *prefix, uid_t uid, gid_t gid) { - static const char symlinks[] = - "-/proc/kcore\0" "/dev/core\0" - "/proc/self/fd\0" "/dev/fd\0" - "/proc/self/fd/0\0" "/dev/stdin\0" - "/proc/self/fd/1\0" "/dev/stdout\0" - "/proc/self/fd/2\0" "/dev/stderr\0"; - - const char *j, *k; - int r; - - NULSTR_FOREACH_PAIR(j, k, symlinks) { - _cleanup_free_ char *link_name = NULL; - const char *n; - - if (j[0] == '-') { - j++; - - if (access(j, F_OK) < 0) - continue; - } - - if (prefix) { - link_name = prefix_root(prefix, k); - if (!link_name) - return -ENOMEM; - - n = link_name; - } else - n = k; - - r = symlink_label(j, n); - if (r < 0) - log_debug_errno(r, "Failed to symlink %s to %s: %m", j, n); - - if (uid != UID_INVALID || gid != GID_INVALID) - if (lchown(n, uid, gid) < 0) - log_debug_errno(errno, "Failed to chown %s: %m", n); - } - - return 0; -} diff --git a/src/shared/dev-setup.h b/src/shared/dev-setup.h deleted file mode 100644 index 5766a62060..0000000000 --- a/src/shared/dev-setup.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-2012 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 . -***/ - -#include - -int dev_setup(const char *prefix, uid_t uid, gid_t gid); diff --git a/src/shared/dns-domain.c b/src/shared/dns-domain.c deleted file mode 100644 index 45d24c0079..0000000000 --- a/src/shared/dns-domain.c +++ /dev/null @@ -1,1322 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . - ***/ - -#ifdef HAVE_LIBIDN -#include -#include -#endif - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "dns-domain.h" -#include "hashmap.h" -#include "hexdecoct.h" -#include "in-addr-util.h" -#include "macro.h" -#include "parse-util.h" -#include "string-util.h" -#include "strv.h" -#include "utf8.h" - -int dns_label_unescape(const char **name, char *dest, size_t sz) { - const char *n; - char *d; - int r = 0; - - assert(name); - assert(*name); - - n = *name; - d = dest; - - for (;;) { - if (*n == '.') { - n++; - break; - } - - if (*n == 0) - break; - - if (r >= DNS_LABEL_MAX) - return -EINVAL; - - if (sz <= 0) - return -ENOBUFS; - - if (*n == '\\') { - /* Escaped character */ - - n++; - - if (*n == 0) - /* Ending NUL */ - return -EINVAL; - - else if (*n == '\\' || *n == '.') { - /* Escaped backslash or dot */ - - if (d) - *(d++) = *n; - sz--; - r++; - n++; - - } else if (n[0] >= '0' && n[0] <= '9') { - unsigned k; - - /* Escaped literal ASCII character */ - - if (!(n[1] >= '0' && n[1] <= '9') || - !(n[2] >= '0' && n[2] <= '9')) - return -EINVAL; - - k = ((unsigned) (n[0] - '0') * 100) + - ((unsigned) (n[1] - '0') * 10) + - ((unsigned) (n[2] - '0')); - - /* Don't allow anything that doesn't - * fit in 8bit. Note that we do allow - * control characters, as some servers - * (e.g. cloudflare) are happy to - * generate labels with them - * inside. */ - if (k > 255) - return -EINVAL; - - if (d) - *(d++) = (char) k; - sz--; - r++; - - n += 3; - } else - return -EINVAL; - - } else if ((uint8_t) *n >= (uint8_t) ' ' && *n != 127) { - - /* Normal character */ - - if (d) - *(d++) = *n; - sz--; - r++; - n++; - } else - return -EINVAL; - } - - /* Empty label that is not at the end? */ - if (r == 0 && *n) - return -EINVAL; - - if (sz >= 1 && d) - *d = 0; - - *name = n; - return r; -} - -/* @label_terminal: terminal character of a label, updated to point to the terminal character of - * the previous label (always skipping one dot) or to NULL if there are no more - * labels. */ -int dns_label_unescape_suffix(const char *name, const char **label_terminal, char *dest, size_t sz) { - const char *terminal; - int r; - - assert(name); - assert(label_terminal); - assert(dest); - - /* no more labels */ - if (!*label_terminal) { - if (sz >= 1) - *dest = 0; - - return 0; - } - - terminal = *label_terminal; - assert(*terminal == '.' || *terminal == 0); - - /* Skip current terminal character (and accept domain names ending it ".") */ - if (*terminal == 0) - terminal--; - if (terminal >= name && *terminal == '.') - terminal--; - - /* Point name to the last label, and terminal to the preceding terminal symbol (or make it a NULL pointer) */ - for (;;) { - if (terminal < name) { - /* Reached the first label, so indicate that there are no more */ - terminal = NULL; - break; - } - - /* Find the start of the last label */ - if (*terminal == '.') { - const char *y; - unsigned slashes = 0; - - for (y = terminal - 1; y >= name && *y == '\\'; y--) - slashes ++; - - if (slashes % 2 == 0) { - /* The '.' was not escaped */ - name = terminal + 1; - break; - } else { - terminal = y; - continue; - } - } - - terminal --; - } - - r = dns_label_unescape(&name, dest, sz); - if (r < 0) - return r; - - *label_terminal = terminal; - - return r; -} - -int dns_label_escape(const char *p, size_t l, char *dest, size_t sz) { - char *q; - - /* DNS labels must be between 1 and 63 characters long. A - * zero-length label does not exist. See RFC 2182, Section - * 11. */ - - if (l <= 0 || l > DNS_LABEL_MAX) - return -EINVAL; - if (sz < 1) - return -ENOBUFS; - - assert(p); - assert(dest); - - q = dest; - while (l > 0) { - - if (*p == '.' || *p == '\\') { - - /* Dot or backslash */ - - if (sz < 3) - return -ENOBUFS; - - *(q++) = '\\'; - *(q++) = *p; - - sz -= 2; - - } else if (*p == '_' || - *p == '-' || - (*p >= '0' && *p <= '9') || - (*p >= 'a' && *p <= 'z') || - (*p >= 'A' && *p <= 'Z')) { - - /* Proper character */ - - if (sz < 2) - return -ENOBUFS; - - *(q++) = *p; - sz -= 1; - - } else { - - /* Everything else */ - - if (sz < 5) - return -ENOBUFS; - - *(q++) = '\\'; - *(q++) = '0' + (char) ((uint8_t) *p / 100); - *(q++) = '0' + (char) (((uint8_t) *p / 10) % 10); - *(q++) = '0' + (char) ((uint8_t) *p % 10); - - sz -= 4; - } - - p++; - l--; - } - - *q = 0; - return (int) (q - dest); -} - -int dns_label_escape_new(const char *p, size_t l, char **ret) { - _cleanup_free_ char *s = NULL; - int r; - - assert(p); - assert(ret); - - if (l <= 0 || l > DNS_LABEL_MAX) - return -EINVAL; - - s = new(char, DNS_LABEL_ESCAPED_MAX); - if (!s) - return -ENOMEM; - - r = dns_label_escape(p, l, s, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - *ret = s; - s = NULL; - - return r; -} - -int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { -#ifdef HAVE_LIBIDN - _cleanup_free_ uint32_t *input = NULL; - size_t input_size, l; - const char *p; - bool contains_8bit = false; - char buffer[DNS_LABEL_MAX+1]; - - assert(encoded); - assert(decoded); - - /* Converts an U-label into an A-label */ - - if (encoded_size <= 0) - return -EINVAL; - - for (p = encoded; p < encoded + encoded_size; p++) - if ((uint8_t) *p > 127) - contains_8bit = true; - - if (!contains_8bit) { - if (encoded_size > DNS_LABEL_MAX) - return -EINVAL; - - return 0; - } - - input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); - if (!input) - return -ENOMEM; - - if (idna_to_ascii_4i(input, input_size, buffer, 0) != 0) - return -EINVAL; - - l = strlen(buffer); - - /* Verify that the the result is not longer than one DNS label. */ - if (l <= 0 || l > DNS_LABEL_MAX) - return -EINVAL; - if (l > decoded_max) - return -ENOBUFS; - - memcpy(decoded, buffer, l); - - /* If there's room, append a trailing NUL byte, but only then */ - if (decoded_max > l) - decoded[l] = 0; - - return (int) l; -#else - return 0; -#endif -} - -int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max) { -#ifdef HAVE_LIBIDN - size_t input_size, output_size; - _cleanup_free_ uint32_t *input = NULL; - _cleanup_free_ char *result = NULL; - uint32_t *output = NULL; - size_t w; - - /* To be invoked after unescaping. Converts an A-label into an U-label. */ - - assert(encoded); - assert(decoded); - - if (encoded_size <= 0 || encoded_size > DNS_LABEL_MAX) - return -EINVAL; - - if (encoded_size < sizeof(IDNA_ACE_PREFIX)-1) - return 0; - - if (memcmp(encoded, IDNA_ACE_PREFIX, sizeof(IDNA_ACE_PREFIX) -1) != 0) - return 0; - - input = stringprep_utf8_to_ucs4(encoded, encoded_size, &input_size); - if (!input) - return -ENOMEM; - - output_size = input_size; - output = newa(uint32_t, output_size); - - idna_to_unicode_44i(input, input_size, output, &output_size, 0); - - result = stringprep_ucs4_to_utf8(output, output_size, NULL, &w); - if (!result) - return -ENOMEM; - if (w <= 0) - return -EINVAL; - if (w > decoded_max) - return -ENOBUFS; - - memcpy(decoded, result, w); - - /* Append trailing NUL byte if there's space, but only then. */ - if (decoded_max > w) - decoded[w] = 0; - - return w; -#else - return 0; -#endif -} - -int dns_name_concat(const char *a, const char *b, char **_ret) { - _cleanup_free_ char *ret = NULL; - size_t n = 0, allocated = 0; - const char *p; - bool first = true; - int r; - - if (a) - p = a; - else if (b) { - p = b; - b = NULL; - } else - goto finish; - - for (;;) { - char label[DNS_LABEL_MAX]; - - r = dns_label_unescape(&p, label, sizeof(label)); - if (r < 0) - return r; - if (r == 0) { - if (*p != 0) - return -EINVAL; - - if (b) { - /* Now continue with the second string, if there is one */ - p = b; - b = NULL; - continue; - } - - break; - } - - if (_ret) { - if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) - return -ENOMEM; - - r = dns_label_escape(label, r, ret + n + !first, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - if (!first) - ret[n] = '.'; - } else { - char escaped[DNS_LABEL_ESCAPED_MAX]; - - r = dns_label_escape(label, r, escaped, sizeof(escaped)); - if (r < 0) - return r; - } - - if (!first) - n++; - else - first = false; - - n += r; - } - -finish: - if (n > DNS_HOSTNAME_MAX) - return -EINVAL; - - if (_ret) { - if (n == 0) { - /* Nothing appended? If so, generate at least a single dot, to indicate the DNS root domain */ - if (!GREEDY_REALLOC(ret, allocated, 2)) - return -ENOMEM; - - ret[n++] = '.'; - } else { - if (!GREEDY_REALLOC(ret, allocated, n + 1)) - return -ENOMEM; - } - - ret[n] = 0; - *_ret = ret; - ret = NULL; - } - - return 0; -} - -void dns_name_hash_func(const void *s, struct siphash *state) { - const char *p = s; - int r; - - assert(p); - - for (;;) { - char label[DNS_LABEL_MAX+1]; - - r = dns_label_unescape(&p, label, sizeof(label)); - if (r < 0) - break; - if (r == 0) - break; - - ascii_strlower_n(label, r); - siphash24_compress(label, r, state); - siphash24_compress_byte(0, state); /* make sure foobar and foo.bar result in different hashes */ - } - - /* enforce that all names are terminated by the empty label */ - string_hash_func("", state); -} - -int dns_name_compare_func(const void *a, const void *b) { - const char *x, *y; - int r, q; - - assert(a); - assert(b); - - x = (const char *) a + strlen(a); - y = (const char *) b + strlen(b); - - for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; - - if (x == NULL && y == NULL) - return 0; - - r = dns_label_unescape_suffix(a, &x, la, sizeof(la)); - q = dns_label_unescape_suffix(b, &y, lb, sizeof(lb)); - if (r < 0 || q < 0) - return r - q; - - r = ascii_strcasecmp_nn(la, r, lb, q); - if (r != 0) - return r; - } -} - -const struct hash_ops dns_name_hash_ops = { - .hash = dns_name_hash_func, - .compare = dns_name_compare_func -}; - -int dns_name_equal(const char *x, const char *y) { - int r, q; - - assert(x); - assert(y); - - for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; - - r = dns_label_unescape(&x, la, sizeof(la)); - if (r < 0) - return r; - - q = dns_label_unescape(&y, lb, sizeof(lb)); - if (q < 0) - return q; - - if (r != q) - return false; - if (r == 0) - return true; - - if (ascii_strcasecmp_n(la, lb, r) != 0) - return false; - } -} - -int dns_name_endswith(const char *name, const char *suffix) { - const char *n, *s, *saved_n = NULL; - int r, q; - - assert(name); - assert(suffix); - - n = name; - s = suffix; - - for (;;) { - char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; - - r = dns_label_unescape(&n, ln, sizeof(ln)); - if (r < 0) - return r; - - if (!saved_n) - saved_n = n; - - q = dns_label_unescape(&s, ls, sizeof(ls)); - if (q < 0) - return q; - - if (r == 0 && q == 0) - return true; - if (r == 0 && saved_n == n) - return false; - - if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { - - /* Not the same, let's jump back, and try with the next label again */ - s = suffix; - n = saved_n; - saved_n = NULL; - } - } -} - -int dns_name_startswith(const char *name, const char *prefix) { - const char *n, *p; - int r, q; - - assert(name); - assert(prefix); - - n = name; - p = prefix; - - for (;;) { - char ln[DNS_LABEL_MAX], lp[DNS_LABEL_MAX]; - - r = dns_label_unescape(&p, lp, sizeof(lp)); - if (r < 0) - return r; - if (r == 0) - return true; - - q = dns_label_unescape(&n, ln, sizeof(ln)); - if (q < 0) - return q; - - if (r != q) - return false; - if (ascii_strcasecmp_n(ln, lp, r) != 0) - return false; - } -} - -int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret) { - const char *n, *s, *saved_before = NULL, *saved_after = NULL, *prefix; - int r, q; - - assert(name); - assert(old_suffix); - assert(new_suffix); - assert(ret); - - n = name; - s = old_suffix; - - for (;;) { - char ln[DNS_LABEL_MAX], ls[DNS_LABEL_MAX]; - - if (!saved_before) - saved_before = n; - - r = dns_label_unescape(&n, ln, sizeof(ln)); - if (r < 0) - return r; - - if (!saved_after) - saved_after = n; - - q = dns_label_unescape(&s, ls, sizeof(ls)); - if (q < 0) - return q; - - if (r == 0 && q == 0) - break; - if (r == 0 && saved_after == n) { - *ret = NULL; /* doesn't match */ - return 0; - } - - if (r != q || ascii_strcasecmp_n(ln, ls, r) != 0) { - - /* Not the same, let's jump back, and try with the next label again */ - s = old_suffix; - n = saved_after; - saved_after = saved_before = NULL; - } - } - - /* Found it! Now generate the new name */ - prefix = strndupa(name, saved_before - name); - - r = dns_name_concat(prefix, new_suffix, ret); - if (r < 0) - return r; - - return 1; -} - -int dns_name_between(const char *a, const char *b, const char *c) { - int n; - - /* Determine if b is strictly greater than a and strictly smaller than c. - We consider the order of names to be circular, so that if a is - strictly greater than c, we consider b to be between them if it is - either greater than a or smaller than c. This is how the canonical - DNS name order used in NSEC records work. */ - - n = dns_name_compare_func(a, c); - if (n == 0) - return -EINVAL; - else if (n < 0) - /* a<---b--->c */ - return dns_name_compare_func(a, b) < 0 && - dns_name_compare_func(b, c) < 0; - else - /* <--b--c a--b--> */ - return dns_name_compare_func(b, c) < 0 || - dns_name_compare_func(a, b) < 0; -} - -int dns_name_reverse(int family, const union in_addr_union *a, char **ret) { - const uint8_t *p; - int r; - - assert(a); - assert(ret); - - p = (const uint8_t*) a; - - if (family == AF_INET) - r = asprintf(ret, "%u.%u.%u.%u.in-addr.arpa", p[3], p[2], p[1], p[0]); - else if (family == AF_INET6) - r = asprintf(ret, "%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.%c.ip6.arpa", - hexchar(p[15] & 0xF), hexchar(p[15] >> 4), hexchar(p[14] & 0xF), hexchar(p[14] >> 4), - hexchar(p[13] & 0xF), hexchar(p[13] >> 4), hexchar(p[12] & 0xF), hexchar(p[12] >> 4), - hexchar(p[11] & 0xF), hexchar(p[11] >> 4), hexchar(p[10] & 0xF), hexchar(p[10] >> 4), - hexchar(p[ 9] & 0xF), hexchar(p[ 9] >> 4), hexchar(p[ 8] & 0xF), hexchar(p[ 8] >> 4), - hexchar(p[ 7] & 0xF), hexchar(p[ 7] >> 4), hexchar(p[ 6] & 0xF), hexchar(p[ 6] >> 4), - hexchar(p[ 5] & 0xF), hexchar(p[ 5] >> 4), hexchar(p[ 4] & 0xF), hexchar(p[ 4] >> 4), - hexchar(p[ 3] & 0xF), hexchar(p[ 3] >> 4), hexchar(p[ 2] & 0xF), hexchar(p[ 2] >> 4), - hexchar(p[ 1] & 0xF), hexchar(p[ 1] >> 4), hexchar(p[ 0] & 0xF), hexchar(p[ 0] >> 4)); - else - return -EAFNOSUPPORT; - if (r < 0) - return -ENOMEM; - - return 0; -} - -int dns_name_address(const char *p, int *family, union in_addr_union *address) { - int r; - - assert(p); - assert(family); - assert(address); - - r = dns_name_endswith(p, "in-addr.arpa"); - if (r < 0) - return r; - if (r > 0) { - uint8_t a[4]; - unsigned i; - - for (i = 0; i < ELEMENTSOF(a); i++) { - char label[DNS_LABEL_MAX+1]; - - r = dns_label_unescape(&p, label, sizeof(label)); - if (r < 0) - return r; - if (r == 0) - return -EINVAL; - if (r > 3) - return -EINVAL; - - r = safe_atou8(label, &a[i]); - if (r < 0) - return r; - } - - r = dns_name_equal(p, "in-addr.arpa"); - if (r <= 0) - return r; - - *family = AF_INET; - address->in.s_addr = htobe32(((uint32_t) a[3] << 24) | - ((uint32_t) a[2] << 16) | - ((uint32_t) a[1] << 8) | - (uint32_t) a[0]); - - return 1; - } - - r = dns_name_endswith(p, "ip6.arpa"); - if (r < 0) - return r; - if (r > 0) { - struct in6_addr a; - unsigned i; - - for (i = 0; i < ELEMENTSOF(a.s6_addr); i++) { - char label[DNS_LABEL_MAX+1]; - int x, y; - - r = dns_label_unescape(&p, label, sizeof(label)); - if (r <= 0) - return r; - if (r != 1) - return -EINVAL; - x = unhexchar(label[0]); - if (x < 0) - return -EINVAL; - - r = dns_label_unescape(&p, label, sizeof(label)); - if (r <= 0) - return r; - if (r != 1) - return -EINVAL; - y = unhexchar(label[0]); - if (y < 0) - return -EINVAL; - - a.s6_addr[ELEMENTSOF(a.s6_addr) - i - 1] = (uint8_t) y << 4 | (uint8_t) x; - } - - r = dns_name_equal(p, "ip6.arpa"); - if (r <= 0) - return r; - - *family = AF_INET6; - address->in6 = a; - return 1; - } - - return 0; -} - -bool dns_name_is_root(const char *name) { - - assert(name); - - /* There are exactly two ways to encode the root domain name: - * as empty string, or with a single dot. */ - - return STR_IN_SET(name, "", "."); -} - -bool dns_name_is_single_label(const char *name) { - int r; - - assert(name); - - r = dns_name_parent(&name); - if (r <= 0) - return false; - - return dns_name_is_root(name); -} - -/* Encode a domain name according to RFC 1035 Section 3.1, without compression */ -int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical) { - uint8_t *label_length, *out; - int r; - - assert(domain); - assert(buffer); - - out = buffer; - - do { - /* Reserve a byte for label length */ - if (len <= 0) - return -ENOBUFS; - len--; - label_length = out; - out++; - - /* Convert and copy a single label. Note that - * dns_label_unescape() returns 0 when it hits the end - * of the domain name, which we rely on here to encode - * the trailing NUL byte. */ - r = dns_label_unescape(&domain, (char *) out, len); - if (r < 0) - return r; - - /* Optionally, output the name in DNSSEC canonical - * format, as described in RFC 4034, section 6.2. Or - * in other words: in lower-case. */ - if (canonical) - ascii_strlower_n((char*) out, (size_t) r); - - /* Fill label length, move forward */ - *label_length = r; - out += r; - len -= r; - - } while (r != 0); - - /* Verify the maximum size of the encoded name. The trailing - * dot + NUL byte account are included this time, hence - * compare against DNS_HOSTNAME_MAX + 2 (which is 255) this - * time. */ - if (out - buffer > DNS_HOSTNAME_MAX + 2) - return -EINVAL; - - return out - buffer; -} - -static bool srv_type_label_is_valid(const char *label, size_t n) { - size_t k; - - assert(label); - - if (n < 2) /* Label needs to be at least 2 chars long */ - return false; - - if (label[0] != '_') /* First label char needs to be underscore */ - return false; - - /* Second char must be a letter */ - if (!(label[1] >= 'A' && label[1] <= 'Z') && - !(label[1] >= 'a' && label[1] <= 'z')) - return false; - - /* Third and further chars must be alphanumeric or a hyphen */ - for (k = 2; k < n; k++) { - if (!(label[k] >= 'A' && label[k] <= 'Z') && - !(label[k] >= 'a' && label[k] <= 'z') && - !(label[k] >= '0' && label[k] <= '9') && - label[k] != '-') - return false; - } - - return true; -} - -bool dns_srv_type_is_valid(const char *name) { - unsigned c = 0; - int r; - - if (!name) - return false; - - for (;;) { - char label[DNS_LABEL_MAX]; - - /* This more or less implements RFC 6335, Section 5.1 */ - - r = dns_label_unescape(&name, label, sizeof(label)); - if (r < 0) - return false; - if (r == 0) - break; - - if (c >= 2) - return false; - - if (!srv_type_label_is_valid(label, r)) - return false; - - c++; - } - - return c == 2; /* exactly two labels */ -} - -bool dns_service_name_is_valid(const char *name) { - size_t l; - - /* This more or less implements RFC 6763, Section 4.1.1 */ - - if (!name) - return false; - - if (!utf8_is_valid(name)) - return false; - - if (string_has_cc(name, NULL)) - return false; - - l = strlen(name); - if (l <= 0) - return false; - if (l > 63) - return false; - - return true; -} - -int dns_service_join(const char *name, const char *type, const char *domain, char **ret) { - char escaped[DNS_LABEL_ESCAPED_MAX]; - _cleanup_free_ char *n = NULL; - int r; - - assert(type); - assert(domain); - assert(ret); - - if (!dns_srv_type_is_valid(type)) - return -EINVAL; - - if (!name) - return dns_name_concat(type, domain, ret); - - if (!dns_service_name_is_valid(name)) - return -EINVAL; - - r = dns_label_escape(name, strlen(name), escaped, sizeof(escaped)); - if (r < 0) - return r; - - r = dns_name_concat(type, domain, &n); - if (r < 0) - return r; - - return dns_name_concat(escaped, n, ret); -} - -static bool dns_service_name_label_is_valid(const char *label, size_t n) { - char *s; - - assert(label); - - if (memchr(label, 0, n)) - return false; - - s = strndupa(label, n); - return dns_service_name_is_valid(s); -} - -int dns_service_split(const char *joined, char **_name, char **_type, char **_domain) { - _cleanup_free_ char *name = NULL, *type = NULL, *domain = NULL; - const char *p = joined, *q = NULL, *d = NULL; - char a[DNS_LABEL_MAX], b[DNS_LABEL_MAX], c[DNS_LABEL_MAX]; - int an, bn, cn, r; - unsigned x = 0; - - assert(joined); - - /* Get first label from the full name */ - an = dns_label_unescape(&p, a, sizeof(a)); - if (an < 0) - return an; - - if (an > 0) { - x++; - - /* If there was a first label, try to get the second one */ - bn = dns_label_unescape(&p, b, sizeof(b)); - if (bn < 0) - return bn; - - if (bn > 0) { - x++; - - /* If there was a second label, try to get the third one */ - q = p; - cn = dns_label_unescape(&p, c, sizeof(c)); - if (cn < 0) - return cn; - - if (cn > 0) - x++; - } else - cn = 0; - } else - an = 0; - - if (x >= 2 && srv_type_label_is_valid(b, bn)) { - - if (x >= 3 && srv_type_label_is_valid(c, cn)) { - - if (dns_service_name_label_is_valid(a, an)) { - /* OK, got . . . */ - - name = strndup(a, an); - if (!name) - return -ENOMEM; - - type = strjoin(b, ".", c, NULL); - if (!type) - return -ENOMEM; - - d = p; - goto finish; - } - - } else if (srv_type_label_is_valid(a, an)) { - - /* OK, got . . */ - - name = NULL; - - type = strjoin(a, ".", b, NULL); - if (!type) - return -ENOMEM; - - d = q; - goto finish; - } - } - - name = NULL; - type = NULL; - d = joined; - -finish: - r = dns_name_normalize(d, &domain); - if (r < 0) - return r; - - if (_domain) { - *_domain = domain; - domain = NULL; - } - - if (_type) { - *_type = type; - type = NULL; - } - - if (_name) { - *_name = name; - name = NULL; - } - - return 0; -} - -static int dns_name_build_suffix_table(const char *name, const char*table[]) { - const char *p; - unsigned n = 0; - int r; - - assert(name); - assert(table); - - p = name; - for (;;) { - if (n > DNS_N_LABELS_MAX) - return -EINVAL; - - table[n] = p; - r = dns_name_parent(&p); - if (r < 0) - return r; - if (r == 0) - break; - - n++; - } - - return (int) n; -} - -int dns_name_suffix(const char *name, unsigned n_labels, const char **ret) { - const char* labels[DNS_N_LABELS_MAX+1]; - int n; - - assert(name); - assert(ret); - - n = dns_name_build_suffix_table(name, labels); - if (n < 0) - return n; - - if ((unsigned) n < n_labels) - return -EINVAL; - - *ret = labels[n - n_labels]; - return (int) (n - n_labels); -} - -int dns_name_skip(const char *a, unsigned n_labels, const char **ret) { - int r; - - assert(a); - assert(ret); - - for (; n_labels > 0; n_labels --) { - r = dns_name_parent(&a); - if (r < 0) - return r; - if (r == 0) { - *ret = ""; - return 0; - } - } - - *ret = a; - return 1; -} - -int dns_name_count_labels(const char *name) { - unsigned n = 0; - const char *p; - int r; - - assert(name); - - p = name; - for (;;) { - r = dns_name_parent(&p); - if (r < 0) - return r; - if (r == 0) - break; - - if (n >= DNS_N_LABELS_MAX) - return -EINVAL; - - n++; - } - - return (int) n; -} - -int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b) { - int r; - - assert(a); - assert(b); - - r = dns_name_skip(a, n_labels, &a); - if (r <= 0) - return r; - - return dns_name_equal(a, b); -} - -int dns_name_common_suffix(const char *a, const char *b, const char **ret) { - const char *a_labels[DNS_N_LABELS_MAX+1], *b_labels[DNS_N_LABELS_MAX+1]; - int n = 0, m = 0, k = 0, r, q; - - assert(a); - assert(b); - assert(ret); - - /* Determines the common suffix of domain names a and b */ - - n = dns_name_build_suffix_table(a, a_labels); - if (n < 0) - return n; - - m = dns_name_build_suffix_table(b, b_labels); - if (m < 0) - return m; - - for (;;) { - char la[DNS_LABEL_MAX], lb[DNS_LABEL_MAX]; - const char *x, *y; - - if (k >= n || k >= m) { - *ret = a_labels[n - k]; - return 0; - } - - x = a_labels[n - 1 - k]; - r = dns_label_unescape(&x, la, sizeof(la)); - if (r < 0) - return r; - - y = b_labels[m - 1 - k]; - q = dns_label_unescape(&y, lb, sizeof(lb)); - if (q < 0) - return q; - - if (r != q || ascii_strcasecmp_n(la, lb, r) != 0) { - *ret = a_labels[n - k]; - return 0; - } - - k++; - } -} - -int dns_name_apply_idna(const char *name, char **ret) { - _cleanup_free_ char *buf = NULL; - size_t n = 0, allocated = 0; - bool first = true; - int r, q; - - assert(name); - assert(ret); - - for (;;) { - char label[DNS_LABEL_MAX]; - - r = dns_label_unescape(&name, label, sizeof(label)); - if (r < 0) - return r; - if (r == 0) - break; - - q = dns_label_apply_idna(label, r, label, sizeof(label)); - if (q < 0) - return q; - if (q > 0) - r = q; - - if (!GREEDY_REALLOC(buf, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) - return -ENOMEM; - - r = dns_label_escape(label, r, buf + n + !first, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - if (first) - first = false; - else - buf[n++] = '.'; - - n +=r; - } - - if (n > DNS_HOSTNAME_MAX) - return -EINVAL; - - if (!GREEDY_REALLOC(buf, allocated, n + 1)) - return -ENOMEM; - - buf[n] = 0; - *ret = buf; - buf = NULL; - - return (int) n; -} diff --git a/src/shared/dns-domain.h b/src/shared/dns-domain.h deleted file mode 100644 index 2de3642cb3..0000000000 --- a/src/shared/dns-domain.h +++ /dev/null @@ -1,110 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . - ***/ - -#pragma once - - -#include -#include -#include -#include - -#include "hashmap.h" -#include "in-addr-util.h" - -/* Length of a single label, with all escaping removed, excluding any trailing dot or NUL byte */ -#define DNS_LABEL_MAX 63 - -/* Worst case length of a single label, with all escaping applied and room for a trailing NUL byte. */ -#define DNS_LABEL_ESCAPED_MAX (DNS_LABEL_MAX*4+1) - -/* Maximum length of a full hostname, consisting of a series of unescaped labels, and no trailing dot or NUL byte */ -#define DNS_HOSTNAME_MAX 253 - -/* Maximum length of a full hostname, on the wire, including the final NUL byte */ -#define DNS_WIRE_FOMAT_HOSTNAME_MAX 255 - -/* Maximum number of labels per valid hostname */ -#define DNS_N_LABELS_MAX 127 - -int dns_label_unescape(const char **name, char *dest, size_t sz); -int dns_label_unescape_suffix(const char *name, const char **label_end, char *dest, size_t sz); -int dns_label_escape(const char *p, size_t l, char *dest, size_t sz); -int dns_label_escape_new(const char *p, size_t l, char **ret); - -static inline int dns_name_parent(const char **name) { - return dns_label_unescape(name, NULL, DNS_LABEL_MAX); -} - -int dns_label_apply_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); -int dns_label_undo_idna(const char *encoded, size_t encoded_size, char *decoded, size_t decoded_max); - -int dns_name_concat(const char *a, const char *b, char **ret); - -static inline int dns_name_normalize(const char *s, char **ret) { - /* dns_name_concat() normalizes as a side-effect */ - return dns_name_concat(s, NULL, ret); -} - -static inline int dns_name_is_valid(const char *s) { - int r; - - /* dns_name_normalize() verifies as a side effect */ - r = dns_name_normalize(s, NULL); - if (r == -EINVAL) - return 0; - if (r < 0) - return r; - return 1; -} - -void dns_name_hash_func(const void *s, struct siphash *state); -int dns_name_compare_func(const void *a, const void *b); -extern const struct hash_ops dns_name_hash_ops; - -int dns_name_between(const char *a, const char *b, const char *c); -int dns_name_equal(const char *x, const char *y); -int dns_name_endswith(const char *name, const char *suffix); -int dns_name_startswith(const char *name, const char *prefix); - -int dns_name_change_suffix(const char *name, const char *old_suffix, const char *new_suffix, char **ret); - -int dns_name_reverse(int family, const union in_addr_union *a, char **ret); -int dns_name_address(const char *p, int *family, union in_addr_union *a); - -bool dns_name_is_root(const char *name); -bool dns_name_is_single_label(const char *name); - -int dns_name_to_wire_format(const char *domain, uint8_t *buffer, size_t len, bool canonical); - -bool dns_srv_type_is_valid(const char *name); -bool dns_service_name_is_valid(const char *name); - -int dns_service_join(const char *name, const char *type, const char *domain, char **ret); -int dns_service_split(const char *joined, char **name, char **type, char **domain); - -int dns_name_suffix(const char *name, unsigned n_labels, const char **ret); -int dns_name_count_labels(const char *name); - -int dns_name_skip(const char *a, unsigned n_labels, const char **ret); -int dns_name_equal_skip(const char *a, unsigned n_labels, const char *b); - -int dns_name_common_suffix(const char *a, const char *b, const char **ret); - -int dns_name_apply_idna(const char *name, char **ret); diff --git a/src/shared/dropin.c b/src/shared/dropin.c deleted file mode 100644 index cc1acd6f23..0000000000 --- a/src/shared/dropin.c +++ /dev/null @@ -1,251 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "conf-files.h" -#include "dropin.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio-label.h" -#include "hashmap.h" -#include "log.h" -#include "macro.h" -#include "mkdir.h" -#include "path-util.h" -#include "set.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" - -int drop_in_file(const char *dir, const char *unit, unsigned level, - const char *name, char **_p, char **_q) { - - _cleanup_free_ char *b = NULL; - char *p, *q; - - char prefix[DECIMAL_STR_MAX(unsigned)]; - - assert(unit); - assert(name); - assert(_p); - assert(_q); - - sprintf(prefix, "%u", level); - - b = xescape(name, "/."); - if (!b) - return -ENOMEM; - - if (!filename_is_valid(b)) - return -EINVAL; - - p = strjoin(dir, "/", unit, ".d", NULL); - if (!p) - return -ENOMEM; - - q = strjoin(p, "/", prefix, "-", b, ".conf", NULL); - if (!q) { - free(p); - return -ENOMEM; - } - - *_p = p; - *_q = q; - return 0; -} - -int write_drop_in(const char *dir, const char *unit, unsigned level, - const char *name, const char *data) { - - _cleanup_free_ char *p = NULL, *q = NULL; - int r; - - assert(dir); - assert(unit); - assert(name); - assert(data); - - r = drop_in_file(dir, unit, level, name, &p, &q); - if (r < 0) - return r; - - (void) mkdir_p(p, 0755); - return write_string_file_atomic_label(q, data); -} - -int write_drop_in_format(const char *dir, const char *unit, unsigned level, - const char *name, const char *format, ...) { - _cleanup_free_ char *p = NULL; - va_list ap; - int r; - - assert(dir); - assert(unit); - assert(name); - assert(format); - - va_start(ap, format); - r = vasprintf(&p, format, ap); - va_end(ap); - - if (r < 0) - return -ENOMEM; - - return write_drop_in(dir, unit, level, name, p); -} - -static int iterate_dir( - const char *path, - UnitDependency dependency, - dependency_consumer_t consumer, - void *arg, - char ***strv) { - - _cleanup_closedir_ DIR *d = NULL; - int r; - - assert(path); - - /* The config directories are special, since the order of the - * drop-ins matters */ - if (dependency < 0) { - r = strv_extend(strv, path); - if (r < 0) - return log_oom(); - - return 0; - } - - assert(consumer); - - d = opendir(path); - if (!d) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open directory %s: %m", path); - } - - for (;;) { - struct dirent *de; - _cleanup_free_ char *f = NULL; - - errno = 0; - de = readdir(d); - if (!de && errno > 0) - return log_error_errno(errno, "Failed to read directory %s: %m", path); - - if (!de) - break; - - if (hidden_file(de->d_name)) - continue; - - f = strjoin(path, "/", de->d_name, NULL); - if (!f) - return log_oom(); - - r = consumer(dependency, de->d_name, f, arg); - if (r < 0) - return r; - } - - return 0; -} - -int unit_file_process_dir( - Set *unit_path_cache, - const char *unit_path, - const char *name, - const char *suffix, - UnitDependency dependency, - dependency_consumer_t consumer, - void *arg, - char ***strv) { - - _cleanup_free_ char *path = NULL; - int r; - - assert(unit_path); - assert(name); - assert(suffix); - - path = strjoin(unit_path, "/", name, suffix, NULL); - if (!path) - return log_oom(); - - if (!unit_path_cache || set_get(unit_path_cache, path)) - (void) iterate_dir(path, dependency, consumer, arg, strv); - - if (unit_name_is_valid(name, UNIT_NAME_INSTANCE)) { - _cleanup_free_ char *template = NULL, *p = NULL; - /* Also try the template dir */ - - r = unit_name_template(name, &template); - if (r < 0) - return log_error_errno(r, "Failed to generate template from unit name: %m"); - - p = strjoin(unit_path, "/", template, suffix, NULL); - if (!p) - return log_oom(); - - if (!unit_path_cache || set_get(unit_path_cache, p)) - (void) iterate_dir(p, dependency, consumer, arg, strv); - } - - return 0; -} - -int unit_file_find_dropin_paths( - char **lookup_path, - Set *unit_path_cache, - Set *names, - char ***paths) { - - _cleanup_strv_free_ char **strv = NULL, **ans = NULL; - Iterator i; - char *t; - int r; - - assert(paths); - - SET_FOREACH(t, names, i) { - char **p; - - STRV_FOREACH(p, lookup_path) - unit_file_process_dir(unit_path_cache, *p, t, ".d", _UNIT_DEPENDENCY_INVALID, NULL, NULL, &strv); - } - - if (strv_isempty(strv)) - return 0; - - r = conf_files_list_strv(&ans, ".conf", NULL, (const char**) strv); - if (r < 0) - return log_warning_errno(r, "Failed to get list of configuration files: %m"); - - *paths = ans; - ans = NULL; - return 1; -} diff --git a/src/shared/dropin.h b/src/shared/dropin.h deleted file mode 100644 index c1936f397b..0000000000 --- a/src/shared/dropin.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "hashmap.h" -#include "macro.h" -#include "set.h" -#include "unit-name.h" - -int drop_in_file(const char *dir, const char *unit, unsigned level, - const char *name, char **_p, char **_q); - -int write_drop_in(const char *dir, const char *unit, unsigned level, - const char *name, const char *data); - -int write_drop_in_format(const char *dir, const char *unit, unsigned level, - const char *name, const char *format, ...) _printf_(5, 6); - -/** - * This callback will be called for each directory entry @entry, - * with @filepath being the full path to the entry. - * - * If return value is negative, loop will be aborted. - */ -typedef int (*dependency_consumer_t)(UnitDependency dependency, - const char *entry, - const char* filepath, - void *arg); - -int unit_file_process_dir( - Set * unit_path_cache, - const char *unit_path, - const char *name, - const char *suffix, - UnitDependency dependency, - dependency_consumer_t consumer, - void *arg, - char ***strv); - -int unit_file_find_dropin_paths( - char **lookup_path, - Set *unit_path_cache, - Set *names, - char ***paths); diff --git a/src/shared/efivars.c b/src/shared/efivars.c deleted file mode 100644 index 8631a5a5d9..0000000000 --- a/src/shared/efivars.c +++ /dev/null @@ -1,715 +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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-id128.h" - -#include "alloc-util.h" -#include "dirent-util.h" -#include "efivars.h" -#include "fd-util.h" -#include "io-util.h" -#include "macro.h" -#include "parse-util.h" -#include "stdio-util.h" -#include "time-util.h" -#include "utf8.h" -#include "util.h" -#include "virt.h" - -#ifdef ENABLE_EFI - -#define LOAD_OPTION_ACTIVE 0x00000001 -#define MEDIA_DEVICE_PATH 0x04 -#define MEDIA_HARDDRIVE_DP 0x01 -#define MEDIA_FILEPATH_DP 0x04 -#define SIGNATURE_TYPE_GUID 0x02 -#define MBR_TYPE_EFI_PARTITION_TABLE_HEADER 0x02 -#define END_DEVICE_PATH_TYPE 0x7f -#define END_ENTIRE_DEVICE_PATH_SUBTYPE 0xff -#define EFI_OS_INDICATIONS_BOOT_TO_FW_UI 0x0000000000000001 - -struct boot_option { - uint32_t attr; - uint16_t path_len; - uint16_t title[]; -} _packed_; - -struct drive_path { - uint32_t part_nr; - uint64_t part_start; - uint64_t part_size; - char signature[16]; - uint8_t mbr_type; - uint8_t signature_type; -} _packed_; - -struct device_path { - uint8_t type; - uint8_t sub_type; - uint16_t length; - union { - uint16_t path[0]; - struct drive_path drive; - }; -} _packed_; - -bool is_efi_boot(void) { - return access("/sys/firmware/efi", F_OK) >= 0; -} - -static int read_flag(const char *varname) { - int r; - _cleanup_free_ void *v = NULL; - size_t s; - uint8_t b; - - r = efi_get_variable(EFI_VENDOR_GLOBAL, varname, NULL, &v, &s); - if (r < 0) - return r; - - if (s != 1) - return -EINVAL; - - b = *(uint8_t *)v; - r = b > 0; - return r; -} - -bool is_efi_secure_boot(void) { - return read_flag("SecureBoot") > 0; -} - -bool is_efi_secure_boot_setup_mode(void) { - return read_flag("SetupMode") > 0; -} - -int efi_reboot_to_firmware_supported(void) { - int r; - size_t s; - uint64_t b; - _cleanup_free_ void *v = NULL; - - if (!is_efi_boot() || detect_container() > 0) - return -EOPNOTSUPP; - - r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndicationsSupported", NULL, &v, &s); - if (r < 0) - return r; - else if (s != sizeof(uint64_t)) - return -EINVAL; - - b = *(uint64_t *)v; - b &= EFI_OS_INDICATIONS_BOOT_TO_FW_UI; - return b > 0 ? 0 : -EOPNOTSUPP; -} - -static int get_os_indications(uint64_t *os_indication) { - int r; - size_t s; - _cleanup_free_ void *v = NULL; - - r = efi_reboot_to_firmware_supported(); - if (r < 0) - return r; - - r = efi_get_variable(EFI_VENDOR_GLOBAL, "OsIndications", NULL, &v, &s); - if (r == -ENOENT) { - /* Some firmware implementations that do support - * OsIndications and report that with - * OsIndicationsSupported will remove the - * OsIndications variable when it is unset. Let's - * pretend it's 0 then, to hide this implementation - * detail. Note that this call will return -ENOENT - * then only if the support for OsIndications is - * missing entirely, as determined by - * efi_reboot_to_firmware_supported() above. */ - *os_indication = 0; - return 0; - } else if (r < 0) - return r; - else if (s != sizeof(uint64_t)) - return -EINVAL; - - *os_indication = *(uint64_t *)v; - return 0; -} - -int efi_get_reboot_to_firmware(void) { - int r; - uint64_t b; - - r = get_os_indications(&b); - if (r < 0) - return r; - - return !!(b & EFI_OS_INDICATIONS_BOOT_TO_FW_UI); -} - -int efi_set_reboot_to_firmware(bool value) { - int r; - uint64_t b, b_new; - - r = get_os_indications(&b); - if (r < 0) - return r; - - if (value) - b_new = b | EFI_OS_INDICATIONS_BOOT_TO_FW_UI; - else - b_new = b & ~EFI_OS_INDICATIONS_BOOT_TO_FW_UI; - - /* Avoid writing to efi vars store if we can due to firmware bugs. */ - if (b != b_new) - return efi_set_variable(EFI_VENDOR_GLOBAL, "OsIndications", &b_new, sizeof(uint64_t)); - - return 0; -} - -int efi_get_variable( - sd_id128_t vendor, - const char *name, - uint32_t *attribute, - void **value, - size_t *size) { - - _cleanup_close_ int fd = -1; - _cleanup_free_ char *p = NULL; - uint32_t a; - ssize_t n; - struct stat st; - _cleanup_free_ void *buf = NULL; - - assert(name); - assert(value); - assert(size); - - if (asprintf(&p, - "/sys/firmware/efi/efivars/%s-%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - name, SD_ID128_FORMAT_VAL(vendor)) < 0) - return -ENOMEM; - - fd = open(p, O_RDONLY|O_NOCTTY|O_CLOEXEC); - if (fd < 0) - return -errno; - - if (fstat(fd, &st) < 0) - return -errno; - if (st.st_size < 4) - return -EIO; - if (st.st_size > 4*1024*1024 + 4) - return -E2BIG; - - n = read(fd, &a, sizeof(a)); - if (n < 0) - return -errno; - if (n != sizeof(a)) - return -EIO; - - buf = malloc(st.st_size - 4 + 2); - if (!buf) - return -ENOMEM; - - n = read(fd, buf, (size_t) st.st_size - 4); - if (n < 0) - return -errno; - if (n != (ssize_t) st.st_size - 4) - return -EIO; - - /* Always NUL terminate (2 bytes, to protect UTF-16) */ - ((char*) buf)[st.st_size - 4] = 0; - ((char*) buf)[st.st_size - 4 + 1] = 0; - - *value = buf; - buf = NULL; - *size = (size_t) st.st_size - 4; - - if (attribute) - *attribute = a; - - return 0; -} - -int efi_set_variable( - sd_id128_t vendor, - const char *name, - const void *value, - size_t size) { - - struct var { - uint32_t attr; - char buf[]; - } _packed_ * _cleanup_free_ buf = NULL; - _cleanup_free_ char *p = NULL; - _cleanup_close_ int fd = -1; - - assert(name); - - if (asprintf(&p, - "/sys/firmware/efi/efivars/%s-%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - name, SD_ID128_FORMAT_VAL(vendor)) < 0) - return -ENOMEM; - - if (size == 0) { - if (unlink(p) < 0) - return -errno; - return 0; - } - - fd = open(p, O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0644); - if (fd < 0) - return -errno; - - buf = malloc(sizeof(uint32_t) + size); - if (!buf) - return -ENOMEM; - - buf->attr = EFI_VARIABLE_NON_VOLATILE|EFI_VARIABLE_BOOTSERVICE_ACCESS|EFI_VARIABLE_RUNTIME_ACCESS; - memcpy(buf->buf, value, size); - - return loop_write(fd, buf, sizeof(uint32_t) + size, false); -} - -int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) { - _cleanup_free_ void *s = NULL; - size_t ss = 0; - int r; - char *x; - - r = efi_get_variable(vendor, name, NULL, &s, &ss); - if (r < 0) - return r; - - x = utf16_to_utf8(s, ss); - if (!x) - return -ENOMEM; - - *p = x; - return 0; -} - -static size_t utf16_size(const uint16_t *s) { - size_t l = 0; - - while (s[l] > 0) - l++; - - return (l+1) * sizeof(uint16_t); -} - -static void efi_guid_to_id128(const void *guid, sd_id128_t *id128) { - struct uuid { - uint32_t u1; - uint16_t u2; - uint16_t u3; - uint8_t u4[8]; - } _packed_; - const struct uuid *uuid = guid; - - id128->bytes[0] = (uuid->u1 >> 24) & 0xff; - id128->bytes[1] = (uuid->u1 >> 16) & 0xff; - id128->bytes[2] = (uuid->u1 >> 8) & 0xff; - id128->bytes[3] = (uuid->u1) & 0xff; - id128->bytes[4] = (uuid->u2 >> 8) & 0xff; - id128->bytes[5] = (uuid->u2) & 0xff; - id128->bytes[6] = (uuid->u3 >> 8) & 0xff; - id128->bytes[7] = (uuid->u3) & 0xff; - memcpy(&id128->bytes[8], uuid->u4, sizeof(uuid->u4)); -} - -int efi_get_boot_option( - uint16_t id, - char **title, - sd_id128_t *part_uuid, - char **path, - bool *active) { - - char boot_id[9]; - _cleanup_free_ uint8_t *buf = NULL; - size_t l; - struct boot_option *header; - size_t title_size; - _cleanup_free_ char *s = NULL, *p = NULL; - sd_id128_t p_uuid = SD_ID128_NULL; - int r; - - xsprintf(boot_id, "Boot%04X", id); - r = efi_get_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, (void **)&buf, &l); - if (r < 0) - return r; - if (l < sizeof(struct boot_option)) - return -ENOENT; - - header = (struct boot_option *)buf; - title_size = utf16_size(header->title); - if (title_size > l - offsetof(struct boot_option, title)) - return -EINVAL; - - if (title) { - s = utf16_to_utf8(header->title, title_size); - if (!s) - return -ENOMEM; - } - - if (header->path_len > 0) { - uint8_t *dbuf; - size_t dnext; - - dbuf = buf + offsetof(struct boot_option, title) + title_size; - dnext = 0; - while (dnext < header->path_len) { - struct device_path *dpath; - - dpath = (struct device_path *)(dbuf + dnext); - if (dpath->length < 4) - break; - - /* Type 0x7F – End of Hardware Device Path, Sub-Type 0xFF – End Entire Device Path */ - if (dpath->type == END_DEVICE_PATH_TYPE && dpath->sub_type == END_ENTIRE_DEVICE_PATH_SUBTYPE) - break; - - dnext += dpath->length; - - /* Type 0x04 – Media Device Path */ - if (dpath->type != MEDIA_DEVICE_PATH) - continue; - - /* Sub-Type 1 – Hard Drive */ - if (dpath->sub_type == MEDIA_HARDDRIVE_DP) { - /* 0x02 – GUID Partition Table */ - if (dpath->drive.mbr_type != MBR_TYPE_EFI_PARTITION_TABLE_HEADER) - continue; - - /* 0x02 – GUID signature */ - if (dpath->drive.signature_type != SIGNATURE_TYPE_GUID) - continue; - - if (part_uuid) - efi_guid_to_id128(dpath->drive.signature, &p_uuid); - continue; - } - - /* Sub-Type 4 – File Path */ - if (dpath->sub_type == MEDIA_FILEPATH_DP && !p && path) { - p = utf16_to_utf8(dpath->path, dpath->length-4); - efi_tilt_backslashes(p); - continue; - } - } - } - - if (title) { - *title = s; - s = NULL; - } - if (part_uuid) - *part_uuid = p_uuid; - if (path) { - *path = p; - p = NULL; - } - if (active) - *active = !!(header->attr & LOAD_OPTION_ACTIVE); - - return 0; -} - -static void to_utf16(uint16_t *dest, const char *src) { - int i; - - for (i = 0; src[i] != '\0'; i++) - dest[i] = src[i]; - dest[i] = '\0'; -} - -struct guid { - uint32_t u1; - uint16_t u2; - uint16_t u3; - uint8_t u4[8]; -} _packed_; - -static void id128_to_efi_guid(sd_id128_t id, void *guid) { - struct guid *uuid = guid; - - uuid->u1 = id.bytes[0] << 24 | id.bytes[1] << 16 | id.bytes[2] << 8 | id.bytes[3]; - uuid->u2 = id.bytes[4] << 8 | id.bytes[5]; - uuid->u3 = id.bytes[6] << 8 | id.bytes[7]; - memcpy(uuid->u4, id.bytes+8, sizeof(uuid->u4)); -} - -static uint16_t *tilt_slashes(uint16_t *s) { - uint16_t *p; - - for (p = s; *p; p++) - if (*p == '/') - *p = '\\'; - - return s; -} - -int efi_add_boot_option(uint16_t id, const char *title, - uint32_t part, uint64_t pstart, uint64_t psize, - sd_id128_t part_uuid, const char *path) { - char boot_id[9]; - size_t size; - size_t title_len; - size_t path_len; - struct boot_option *option; - struct device_path *devicep; - _cleanup_free_ char *buf = NULL; - - title_len = (strlen(title)+1) * 2; - path_len = (strlen(path)+1) * 2; - - buf = calloc(sizeof(struct boot_option) + title_len + - sizeof(struct drive_path) + - sizeof(struct device_path) + path_len, 1); - if (!buf) - return -ENOMEM; - - /* header */ - option = (struct boot_option *)buf; - option->attr = LOAD_OPTION_ACTIVE; - option->path_len = offsetof(struct device_path, drive) + sizeof(struct drive_path) + - offsetof(struct device_path, path) + path_len + - offsetof(struct device_path, path); - to_utf16(option->title, title); - size = offsetof(struct boot_option, title) + title_len; - - /* partition info */ - devicep = (struct device_path *)(buf + size); - devicep->type = MEDIA_DEVICE_PATH; - devicep->sub_type = MEDIA_HARDDRIVE_DP; - devicep->length = offsetof(struct device_path, drive) + sizeof(struct drive_path); - devicep->drive.part_nr = part; - devicep->drive.part_start = pstart; - devicep->drive.part_size = psize; - devicep->drive.signature_type = SIGNATURE_TYPE_GUID; - devicep->drive.mbr_type = MBR_TYPE_EFI_PARTITION_TABLE_HEADER; - id128_to_efi_guid(part_uuid, devicep->drive.signature); - size += devicep->length; - - /* path to loader */ - devicep = (struct device_path *)(buf + size); - devicep->type = MEDIA_DEVICE_PATH; - devicep->sub_type = MEDIA_FILEPATH_DP; - devicep->length = offsetof(struct device_path, path) + path_len; - to_utf16(devicep->path, path); - tilt_slashes(devicep->path); - size += devicep->length; - - /* end of path */ - devicep = (struct device_path *)(buf + size); - devicep->type = END_DEVICE_PATH_TYPE; - devicep->sub_type = END_ENTIRE_DEVICE_PATH_SUBTYPE; - devicep->length = offsetof(struct device_path, path); - size += devicep->length; - - xsprintf(boot_id, "Boot%04X", id); - return efi_set_variable(EFI_VENDOR_GLOBAL, boot_id, buf, size); -} - -int efi_remove_boot_option(uint16_t id) { - char boot_id[9]; - - xsprintf(boot_id, "Boot%04X", id); - return efi_set_variable(EFI_VENDOR_GLOBAL, boot_id, NULL, 0); -} - -int efi_get_boot_order(uint16_t **order) { - _cleanup_free_ void *buf = NULL; - size_t l; - int r; - - r = efi_get_variable(EFI_VENDOR_GLOBAL, "BootOrder", NULL, &buf, &l); - if (r < 0) - return r; - - if (l <= 0) - return -ENOENT; - - if (l % sizeof(uint16_t) > 0 || - l / sizeof(uint16_t) > INT_MAX) - return -EINVAL; - - *order = buf; - buf = NULL; - return (int) (l / sizeof(uint16_t)); -} - -int efi_set_boot_order(uint16_t *order, size_t n) { - return efi_set_variable(EFI_VENDOR_GLOBAL, "BootOrder", order, n * sizeof(uint16_t)); -} - -static int boot_id_hex(const char s[4]) { - int i; - int id = 0; - - for (i = 0; i < 4; i++) - if (s[i] >= '0' && s[i] <= '9') - id |= (s[i] - '0') << (3 - i) * 4; - else if (s[i] >= 'A' && s[i] <= 'F') - id |= (s[i] - 'A' + 10) << (3 - i) * 4; - else - return -EINVAL; - - return id; -} - -static int cmp_uint16(const void *_a, const void *_b) { - const uint16_t *a = _a, *b = _b; - - return (int)*a - (int)*b; -} - -int efi_get_boot_options(uint16_t **options) { - _cleanup_closedir_ DIR *dir = NULL; - struct dirent *de; - _cleanup_free_ uint16_t *list = NULL; - size_t alloc = 0; - int count = 0; - - assert(options); - - dir = opendir("/sys/firmware/efi/efivars/"); - if (!dir) - return -errno; - - FOREACH_DIRENT(de, dir, return -errno) { - int id; - - if (strncmp(de->d_name, "Boot", 4) != 0) - continue; - - if (strlen(de->d_name) != 45) - continue; - - if (strcmp(de->d_name + 8, "-8be4df61-93ca-11d2-aa0d-00e098032b8c") != 0) - continue; - - id = boot_id_hex(de->d_name + 4); - if (id < 0) - continue; - - if (!GREEDY_REALLOC(list, alloc, count + 1)) - return -ENOMEM; - - list[count++] = id; - } - - qsort_safe(list, count, sizeof(uint16_t), cmp_uint16); - - *options = list; - list = NULL; - return count; -} - -static int read_usec(sd_id128_t vendor, const char *name, usec_t *u) { - _cleanup_free_ char *j = NULL; - int r; - uint64_t x = 0; - - assert(name); - assert(u); - - r = efi_get_variable_string(EFI_VENDOR_LOADER, name, &j); - if (r < 0) - return r; - - r = safe_atou64(j, &x); - if (r < 0) - return r; - - *u = x; - return 0; -} - -int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) { - uint64_t x, y; - int r; - - assert(firmware); - assert(loader); - - r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeInitUSec", &x); - if (r < 0) - return r; - - r = read_usec(EFI_VENDOR_LOADER, "LoaderTimeExecUSec", &y); - if (r < 0) - return r; - - if (y == 0 || y < x) - return -EIO; - - if (y > USEC_PER_HOUR) - return -EIO; - - *firmware = x; - *loader = y; - - return 0; -} - -int efi_loader_get_device_part_uuid(sd_id128_t *u) { - _cleanup_free_ char *p = NULL; - int r, parsed[16]; - - r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderDevicePartUUID", &p); - if (r < 0) - return r; - - if (sscanf(p, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", - &parsed[0], &parsed[1], &parsed[2], &parsed[3], - &parsed[4], &parsed[5], &parsed[6], &parsed[7], - &parsed[8], &parsed[9], &parsed[10], &parsed[11], - &parsed[12], &parsed[13], &parsed[14], &parsed[15]) != 16) - return -EIO; - - if (u) { - unsigned i; - - for (i = 0; i < ELEMENTSOF(parsed); i++) - u->bytes[i] = parsed[i]; - } - - return 0; -} - -#endif - -char *efi_tilt_backslashes(char *s) { - char *p; - - for (p = s; *p; p++) - if (*p == '\\') - *p = '/'; - - return s; -} diff --git a/src/shared/efivars.h b/src/shared/efivars.h deleted file mode 100644 index b61d14c4ec..0000000000 --- a/src/shared/efivars.h +++ /dev/null @@ -1,131 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include -#include -#include - -#include "sd-id128.h" - -#include "time-util.h" - -#define EFI_VENDOR_LOADER SD_ID128_MAKE(4a,67,b0,82,0a,4c,41,cf,b6,c7,44,0b,29,bb,8c,4f) -#define EFI_VENDOR_GLOBAL SD_ID128_MAKE(8b,e4,df,61,93,ca,11,d2,aa,0d,00,e0,98,03,2b,8c) -#define EFI_VARIABLE_NON_VOLATILE 0x0000000000000001 -#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x0000000000000002 -#define EFI_VARIABLE_RUNTIME_ACCESS 0x0000000000000004 - -#ifdef ENABLE_EFI - -bool is_efi_boot(void); -bool is_efi_secure_boot(void); -bool is_efi_secure_boot_setup_mode(void); -int efi_reboot_to_firmware_supported(void); -int efi_get_reboot_to_firmware(void); -int efi_set_reboot_to_firmware(bool value); - -int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size); -int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size); -int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p); - -int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *part_uuid, char **path, bool *active); -int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path); -int efi_remove_boot_option(uint16_t id); -int efi_get_boot_order(uint16_t **order); -int efi_set_boot_order(uint16_t *order, size_t n); -int efi_get_boot_options(uint16_t **options); - -int efi_loader_get_device_part_uuid(sd_id128_t *u); -int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader); - -#else - -static inline bool is_efi_boot(void) { - return false; -} - -static inline bool is_efi_secure_boot(void) { - return false; -} - -static inline bool is_efi_secure_boot_setup_mode(void) { - return false; -} - -static inline int efi_reboot_to_firmware_supported(void) { - return -EOPNOTSUPP; -} - -static inline int efi_get_reboot_to_firmware(void) { - return -EOPNOTSUPP; -} - -static inline int efi_set_reboot_to_firmware(bool value) { - return -EOPNOTSUPP; -} - -static inline int efi_get_variable(sd_id128_t vendor, const char *name, uint32_t *attribute, void **value, size_t *size) { - return -EOPNOTSUPP; -} - -static inline int efi_set_variable(sd_id128_t vendor, const char *name, const void *value, size_t size) { - return -EOPNOTSUPP; -} - -static inline int efi_get_variable_string(sd_id128_t vendor, const char *name, char **p) { - return -EOPNOTSUPP; -} - -static inline int efi_get_boot_option(uint16_t nr, char **title, sd_id128_t *part_uuid, char **path, bool *active) { - return -EOPNOTSUPP; -} - -static inline int efi_add_boot_option(uint16_t id, const char *title, uint32_t part, uint64_t pstart, uint64_t psize, sd_id128_t part_uuid, const char *path) { - return -EOPNOTSUPP; -} - -static inline int efi_remove_boot_option(uint16_t id) { - return -EOPNOTSUPP; -} - -static inline int efi_get_boot_order(uint16_t **order) { - return -EOPNOTSUPP; -} - -static inline int efi_set_boot_order(uint16_t *order, size_t n) { - return -EOPNOTSUPP; -} - -static inline int efi_get_boot_options(uint16_t **options) { - return -EOPNOTSUPP; -} - -static inline int efi_loader_get_device_part_uuid(sd_id128_t *u) { - return -EOPNOTSUPP; -} - -static inline int efi_loader_get_boot_usec(usec_t *firmware, usec_t *loader) { - return -EOPNOTSUPP; -} - -#endif - -char *efi_tilt_backslashes(char *s); diff --git a/src/shared/firewall-util.c b/src/shared/firewall-util.c deleted file mode 100644 index 0d3da2e6d2..0000000000 --- a/src/shared/firewall-util.c +++ /dev/null @@ -1,349 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "firewall-util.h" -#include "in-addr-util.h" -#include "macro.h" - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct xtc_handle*, iptc_free); - -static int entry_fill_basics( - struct ipt_entry *entry, - int protocol, - const char *in_interface, - const union in_addr_union *source, - unsigned source_prefixlen, - const char *out_interface, - const union in_addr_union *destination, - unsigned destination_prefixlen) { - - assert(entry); - - if (out_interface && strlen(out_interface) >= IFNAMSIZ) - return -EINVAL; - - if (in_interface && strlen(in_interface) >= IFNAMSIZ) - return -EINVAL; - - entry->ip.proto = protocol; - - if (in_interface) { - strcpy(entry->ip.iniface, in_interface); - memset(entry->ip.iniface_mask, 0xFF, strlen(in_interface)+1); - } - if (source) { - entry->ip.src = source->in; - in_addr_prefixlen_to_netmask(&entry->ip.smsk, source_prefixlen); - } - - if (out_interface) { - strcpy(entry->ip.outiface, out_interface); - memset(entry->ip.outiface_mask, 0xFF, strlen(out_interface)+1); - } - if (destination) { - entry->ip.dst = destination->in; - in_addr_prefixlen_to_netmask(&entry->ip.dmsk, destination_prefixlen); - } - - return 0; -} - -int fw_add_masquerade( - bool add, - int af, - int protocol, - const union in_addr_union *source, - unsigned source_prefixlen, - const char *out_interface, - const union in_addr_union *destination, - unsigned destination_prefixlen) { - - _cleanup_(iptc_freep) struct xtc_handle *h = NULL; - struct ipt_entry *entry, *mask; - struct ipt_entry_target *t; - size_t sz; - struct nf_nat_ipv4_multi_range_compat *mr; - int r; - - if (af != AF_INET) - return -EOPNOTSUPP; - - if (protocol != 0 && protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) - return -EOPNOTSUPP; - - h = iptc_init("nat"); - if (!h) - return -errno; - - sz = XT_ALIGN(sizeof(struct ipt_entry)) + - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - - /* Put together the entry we want to add or remove */ - entry = alloca0(sz); - entry->next_offset = sz; - entry->target_offset = XT_ALIGN(sizeof(struct ipt_entry)); - r = entry_fill_basics(entry, protocol, NULL, source, source_prefixlen, out_interface, destination, destination_prefixlen); - if (r < 0) - return r; - - /* Fill in target part */ - t = ipt_get_target(entry); - t->u.target_size = - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - strncpy(t->u.user.name, "MASQUERADE", sizeof(t->u.user.name)); - mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; - mr->rangesize = 1; - - /* Create a search mask entry */ - mask = alloca(sz); - memset(mask, 0xFF, sz); - - if (add) { - if (iptc_check_entry("POSTROUTING", entry, (unsigned char*) mask, h)) - return 0; - if (errno != ENOENT) /* if other error than not existing yet, fail */ - return -errno; - - if (!iptc_insert_entry("POSTROUTING", entry, 0, h)) - return -errno; - } else { - if (!iptc_delete_entry("POSTROUTING", entry, (unsigned char*) mask, h)) { - if (errno == ENOENT) /* if it's already gone, all is good! */ - return 0; - - return -errno; - } - } - - if (!iptc_commit(h)) - return -errno; - - return 0; -} - -int fw_add_local_dnat( - bool add, - int af, - int protocol, - const char *in_interface, - const union in_addr_union *source, - unsigned source_prefixlen, - const union in_addr_union *destination, - unsigned destination_prefixlen, - uint16_t local_port, - const union in_addr_union *remote, - uint16_t remote_port, - const union in_addr_union *previous_remote) { - - - _cleanup_(iptc_freep) struct xtc_handle *h = NULL; - struct ipt_entry *entry, *mask; - struct ipt_entry_target *t; - struct ipt_entry_match *m; - struct xt_addrtype_info_v1 *at; - struct nf_nat_ipv4_multi_range_compat *mr; - size_t sz, msz; - int r; - - assert(add || !previous_remote); - - if (af != AF_INET) - return -EOPNOTSUPP; - - if (protocol != IPPROTO_TCP && protocol != IPPROTO_UDP) - return -EOPNOTSUPP; - - if (local_port <= 0) - return -EINVAL; - - if (remote_port <= 0) - return -EINVAL; - - h = iptc_init("nat"); - if (!h) - return -errno; - - sz = XT_ALIGN(sizeof(struct ipt_entry)) + - XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - - if (protocol == IPPROTO_TCP) - msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_tcp)); - else - msz = XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_udp)); - - sz += msz; - - /* Fill in basic part */ - entry = alloca0(sz); - entry->next_offset = sz; - entry->target_offset = - XT_ALIGN(sizeof(struct ipt_entry)) + - XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_addrtype_info_v1)) + - msz; - r = entry_fill_basics(entry, protocol, in_interface, source, source_prefixlen, NULL, destination, destination_prefixlen); - if (r < 0) - return r; - - /* Fill in first match */ - m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry))); - m->u.match_size = msz; - if (protocol == IPPROTO_TCP) { - struct xt_tcp *tcp; - - strncpy(m->u.user.name, "tcp", sizeof(m->u.user.name)); - tcp = (struct xt_tcp*) m->data; - tcp->dpts[0] = tcp->dpts[1] = local_port; - tcp->spts[0] = 0; - tcp->spts[1] = 0xFFFF; - - } else { - struct xt_udp *udp; - - strncpy(m->u.user.name, "udp", sizeof(m->u.user.name)); - udp = (struct xt_udp*) m->data; - udp->dpts[0] = udp->dpts[1] = local_port; - udp->spts[0] = 0; - udp->spts[1] = 0xFFFF; - } - - /* Fill in second match */ - m = (struct ipt_entry_match*) ((uint8_t*) entry + XT_ALIGN(sizeof(struct ipt_entry)) + msz); - m->u.match_size = - XT_ALIGN(sizeof(struct ipt_entry_match)) + - XT_ALIGN(sizeof(struct xt_addrtype_info_v1)); - strncpy(m->u.user.name, "addrtype", sizeof(m->u.user.name)); - m->u.user.revision = 1; - at = (struct xt_addrtype_info_v1*) m->data; - at->dest = XT_ADDRTYPE_LOCAL; - - /* Fill in target part */ - t = ipt_get_target(entry); - t->u.target_size = - XT_ALIGN(sizeof(struct ipt_entry_target)) + - XT_ALIGN(sizeof(struct nf_nat_ipv4_multi_range_compat)); - strncpy(t->u.user.name, "DNAT", sizeof(t->u.user.name)); - mr = (struct nf_nat_ipv4_multi_range_compat*) t->data; - mr->rangesize = 1; - mr->range[0].flags = NF_NAT_RANGE_PROTO_SPECIFIED|NF_NAT_RANGE_MAP_IPS; - mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; - if (protocol == IPPROTO_TCP) - mr->range[0].min.tcp.port = mr->range[0].max.tcp.port = htons(remote_port); - else - mr->range[0].min.udp.port = mr->range[0].max.udp.port = htons(remote_port); - - mask = alloca0(sz); - memset(mask, 0xFF, sz); - - if (add) { - /* Add the PREROUTING rule, if it is missing so far */ - if (!iptc_check_entry("PREROUTING", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -EINVAL; - - if (!iptc_insert_entry("PREROUTING", entry, 0, h)) - return -errno; - } - - /* If a previous remote is set, remove its entry */ - if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { - mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; - - if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - - mr->range[0].min_ip = mr->range[0].max_ip = remote->in.s_addr; - } - - /* Add the OUTPUT rule, if it is missing so far */ - if (!in_interface) { - - /* Don't apply onto loopback addresses */ - if (!destination) { - entry->ip.dst.s_addr = htobe32(0x7F000000); - entry->ip.dmsk.s_addr = htobe32(0xFF000000); - entry->ip.invflags = IPT_INV_DSTIP; - } - - if (!iptc_check_entry("OUTPUT", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - - if (!iptc_insert_entry("OUTPUT", entry, 0, h)) - return -errno; - } - - /* If a previous remote is set, remove its entry */ - if (previous_remote && previous_remote->in.s_addr != remote->in.s_addr) { - mr->range[0].min_ip = mr->range[0].max_ip = previous_remote->in.s_addr; - - if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - } - } - } else { - if (!iptc_delete_entry("PREROUTING", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - - if (!in_interface) { - if (!destination) { - entry->ip.dst.s_addr = htobe32(0x7F000000); - entry->ip.dmsk.s_addr = htobe32(0xFF000000); - entry->ip.invflags = IPT_INV_DSTIP; - } - - if (!iptc_delete_entry("OUTPUT", entry, (unsigned char*) mask, h)) { - if (errno != ENOENT) - return -errno; - } - } - } - - if (!iptc_commit(h)) - return -errno; - - return 0; -} diff --git a/src/shared/firewall-util.h b/src/shared/firewall-util.h deleted file mode 100644 index c39b34cf8f..0000000000 --- a/src/shared/firewall-util.h +++ /dev/null @@ -1,83 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include - -#include "in-addr-util.h" - -#ifdef HAVE_LIBIPTC - -int fw_add_masquerade( - bool add, - int af, - int protocol, - const union in_addr_union *source, - unsigned source_prefixlen, - const char *out_interface, - const union in_addr_union *destination, - unsigned destination_prefixlen); - -int fw_add_local_dnat( - bool add, - int af, - int protocol, - const char *in_interface, - const union in_addr_union *source, - unsigned source_prefixlen, - const union in_addr_union *destination, - unsigned destination_prefixlen, - uint16_t local_port, - const union in_addr_union *remote, - uint16_t remote_port, - const union in_addr_union *previous_remote); - -#else - -static inline int fw_add_masquerade( - bool add, - int af, - int protocol, - const union in_addr_union *source, - unsigned source_prefixlen, - const char *out_interface, - const union in_addr_union *destination, - unsigned destination_prefixlen) { - return -EOPNOTSUPP; -} - -static inline int fw_add_local_dnat( - bool add, - int af, - int protocol, - const char *in_interface, - const union in_addr_union *source, - unsigned source_prefixlen, - const union in_addr_union *destination, - unsigned destination_prefixlen, - uint16_t local_port, - const union in_addr_union *remote, - uint16_t remote_port, - const union in_addr_union *previous_remote) { - return -EOPNOTSUPP; -} - -#endif diff --git a/src/shared/fstab-util.c b/src/shared/fstab-util.c deleted file mode 100644 index a4e0cd3267..0000000000 --- a/src/shared/fstab-util.c +++ /dev/null @@ -1,263 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "device-nodes.h" -#include "fstab-util.h" -#include "macro.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -bool fstab_is_mount_point(const char *mount) { - _cleanup_endmntent_ FILE *f = NULL; - struct mntent *m; - - f = setmntent("/etc/fstab", "r"); - if (!f) - return false; - - while ((m = getmntent(f))) - if (path_equal(m->mnt_dir, mount)) - return true; - - return false; -} - -int fstab_filter_options(const char *opts, const char *names, - const char **namefound, char **value, char **filtered) { - const char *name, *n = NULL, *x; - _cleanup_strv_free_ char **stor = NULL; - _cleanup_free_ char *v = NULL, **strv = NULL; - - assert(names && *names); - - if (!opts) - goto answer; - - /* If !value and !filtered, this function is not allowed to fail. */ - - if (!filtered) { - const char *word, *state; - size_t l; - - FOREACH_WORD_SEPARATOR(word, l, opts, ",", state) - NULSTR_FOREACH(name, names) { - if (l < strlen(name)) - continue; - if (!strneq(word, name, strlen(name))) - continue; - - /* we know that the string is NUL - * terminated, so *x is valid */ - x = word + strlen(name); - if (IN_SET(*x, '\0', '=', ',')) { - n = name; - if (value) { - free(v); - if (IN_SET(*x, '\0', ',')) - v = NULL; - else { - assert(*x == '='); - x++; - v = strndup(x, l - strlen(name) - 1); - if (!v) - return -ENOMEM; - } - } - } - } - } else { - char **t, **s; - - stor = strv_split(opts, ","); - if (!stor) - return -ENOMEM; - strv = memdup(stor, sizeof(char*) * (strv_length(stor) + 1)); - if (!strv) - return -ENOMEM; - - for (s = t = strv; *s; s++) { - NULSTR_FOREACH(name, names) { - x = startswith(*s, name); - if (x && IN_SET(*x, '\0', '=')) - goto found; - } - - *t = *s; - t++; - continue; - found: - /* Keep the last occurence found */ - n = name; - if (value) { - free(v); - if (*x == '\0') - v = NULL; - else { - assert(*x == '='); - x++; - v = strdup(x); - if (!v) - return -ENOMEM; - } - } - } - *t = NULL; - } - -answer: - if (namefound) - *namefound = n; - if (filtered) { - char *f; - - f = strv_join(strv, ","); - if (!f) - return -ENOMEM; - - *filtered = f; - } - if (value) { - *value = v; - v = NULL; - } - - return !!n; -} - -int fstab_extract_values(const char *opts, const char *name, char ***values) { - _cleanup_strv_free_ char **optsv = NULL, **res = NULL; - char **s; - - assert(opts); - assert(name); - assert(values); - - optsv = strv_split(opts, ","); - if (!optsv) - return -ENOMEM; - - STRV_FOREACH(s, optsv) { - char *arg; - int r; - - arg = startswith(*s, name); - if (!arg || *arg != '=') - continue; - r = strv_extend(&res, arg + 1); - if (r < 0) - return r; - } - - *values = res; - res = NULL; - - return !!*values; -} - -int fstab_find_pri(const char *options, int *ret) { - _cleanup_free_ char *opt = NULL; - int r; - unsigned pri; - - assert(ret); - - r = fstab_filter_options(options, "pri\0", NULL, &opt, NULL); - if (r < 0) - return r; - if (r == 0 || !opt) - return 0; - - r = safe_atou(opt, &pri); - if (r < 0) - return r; - - if ((int) pri < 0) - return -ERANGE; - - *ret = (int) pri; - return 1; -} - -static char *unquote(const char *s, const char* quotes) { - size_t l; - assert(s); - - /* This is rather stupid, simply removes the heading and - * trailing quotes if there is one. Doesn't care about - * escaping or anything. - * - * DON'T USE THIS FOR NEW CODE ANYMORE!*/ - - l = strlen(s); - if (l < 2) - return strdup(s); - - if (strchr(quotes, s[0]) && s[l-1] == s[0]) - return strndup(s+1, l-2); - - return strdup(s); -} - -static char *tag_to_udev_node(const char *tagvalue, const char *by) { - _cleanup_free_ char *t = NULL, *u = NULL; - size_t enc_len; - - u = unquote(tagvalue, QUOTES); - if (!u) - return NULL; - - enc_len = strlen(u) * 4 + 1; - t = new(char, enc_len); - if (!t) - return NULL; - - if (encode_devnode_name(u, t, enc_len) < 0) - return NULL; - - return strjoin("/dev/disk/by-", by, "/", t, NULL); -} - -char *fstab_node_to_udev_node(const char *p) { - assert(p); - - if (startswith(p, "LABEL=")) - return tag_to_udev_node(p+6, "label"); - - if (startswith(p, "UUID=")) - return tag_to_udev_node(p+5, "uuid"); - - if (startswith(p, "PARTUUID=")) - return tag_to_udev_node(p+9, "partuuid"); - - if (startswith(p, "PARTLABEL=")) - return tag_to_udev_node(p+10, "partlabel"); - - return strdup(p); -} diff --git a/src/shared/fstab-util.h b/src/shared/fstab-util.h deleted file mode 100644 index 679f6902f7..0000000000 --- a/src/shared/fstab-util.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include - -#include "macro.h" - -bool fstab_is_mount_point(const char *mount); - -int fstab_filter_options(const char *opts, const char *names, const char **namefound, char **value, char **filtered); - -int fstab_extract_values(const char *opts, const char *name, char ***values); - -static inline bool fstab_test_option(const char *opts, const char *names) { - return !!fstab_filter_options(opts, names, NULL, NULL, NULL); -} - -int fstab_find_pri(const char *options, int *ret); - -static inline bool fstab_test_yes_no_option(const char *opts, const char *yes_no) { - int r; - const char *opt; - - /* If first name given is last, return 1. - * If second name given is last or neither is found, return 0. */ - - r = fstab_filter_options(opts, yes_no, &opt, NULL, NULL); - assert(r >= 0); - - return opt == yes_no; -} - -char *fstab_node_to_udev_node(const char *p); diff --git a/src/shared/generator.c b/src/shared/generator.c deleted file mode 100644 index cd3c35cd55..0000000000 --- a/src/shared/generator.c +++ /dev/null @@ -1,193 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include - -#include "alloc-util.h" -#include "dropin.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "fstab-util.h" -#include "generator.h" -#include "log.h" -#include "macro.h" -#include "mkdir.h" -#include "path-util.h" -#include "special.h" -#include "string-util.h" -#include "time-util.h" -#include "unit-name.h" -#include "util.h" - -static int write_fsck_sysroot_service(const char *dir, const char *what) { - _cleanup_free_ char *device = NULL, *escaped = NULL; - _cleanup_fclose_ FILE *f = NULL; - const char *unit; - int r; - - escaped = cescape(what); - if (!escaped) - return log_oom(); - - unit = strjoina(dir, "/systemd-fsck-root.service"); - log_debug("Creating %s", unit); - - r = unit_name_from_path(what, ".device", &device); - if (r < 0) - return log_error_errno(r, "Failed to convert device \"%s\" to unit name: %m", what); - - f = fopen(unit, "wxe"); - if (!f) - return log_error_errno(errno, "Failed to create unit file %s: %m", unit); - - fprintf(f, - "# Automatically generated by %1$s\n\n" - "[Unit]\n" - "Documentation=man:systemd-fsck-root.service(8)\n" - "Description=File System Check on %2$s\n" - "DefaultDependencies=no\n" - "BindsTo=%3$s\n" - "After=%3$s local-fs-pre.target\n" - "Before=shutdown.target\n" - "\n" - "[Service]\n" - "Type=oneshot\n" - "RemainAfterExit=yes\n" - "ExecStart=" SYSTEMD_FSCK_PATH " %4$s\n" - "TimeoutSec=0\n", - program_invocation_short_name, - what, - device, - escaped); - - r = fflush_and_check(f); - if (r < 0) - return log_error_errno(r, "Failed to write unit file %s: %m", unit); - - return 0; -} - -int generator_write_fsck_deps( - FILE *f, - const char *dir, - const char *what, - const char *where, - const char *fstype) { - - int r; - - assert(f); - assert(dir); - assert(what); - assert(where); - - if (!is_device_path(what)) { - log_warning("Checking was requested for \"%s\", but it is not a device.", what); - return 0; - } - - if (!isempty(fstype) && !streq(fstype, "auto")) { - r = fsck_exists(fstype); - if (r < 0) - log_warning_errno(r, "Checking was requested for %s, but couldn't detect if fsck.%s may be used, proceeding: %m", what, fstype); - else if (r == 0) { - /* treat missing check as essentially OK */ - log_debug("Checking was requested for %s, but fsck.%s does not exist.", what, fstype); - return 0; - } - } - - if (path_equal(where, "/")) { - const char *lnk; - - lnk = strjoina(dir, "/" SPECIAL_LOCAL_FS_TARGET ".wants/systemd-fsck-root.service"); - - mkdir_parents(lnk, 0755); - if (symlink(SYSTEM_DATA_UNIT_PATH "/systemd-fsck-root.service", lnk) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", lnk); - - } else { - _cleanup_free_ char *_fsck = NULL; - const char *fsck; - - if (in_initrd() && path_equal(where, "/sysroot")) { - r = write_fsck_sysroot_service(dir, what); - if (r < 0) - return r; - - fsck = "systemd-fsck-root.service"; - } else { - r = unit_name_from_path_instance("systemd-fsck", what, ".service", &_fsck); - if (r < 0) - return log_error_errno(r, "Failed to create fsck service name: %m"); - - fsck = _fsck; - } - - fprintf(f, - "Requires=%1$s\n" - "After=%1$s\n", - fsck); - } - - return 0; -} - -int generator_write_timeouts( - const char *dir, - const char *what, - const char *where, - const char *opts, - char **filtered) { - - /* Allow configuration how long we wait for a device that - * backs a mount point to show up. This is useful to support - * endless device timeouts for devices that show up only after - * user input, like crypto devices. */ - - _cleanup_free_ char *node = NULL, *unit = NULL, *timeout = NULL; - usec_t u; - int r; - - r = fstab_filter_options(opts, "comment=systemd.device-timeout\0" "x-systemd.device-timeout\0", - NULL, &timeout, filtered); - if (r <= 0) - return r; - - r = parse_sec(timeout, &u); - if (r < 0) { - log_warning("Failed to parse timeout for %s, ignoring: %s", where, timeout); - return 0; - } - - node = fstab_node_to_udev_node(what); - if (!node) - return log_oom(); - - r = unit_name_from_path(node, ".device", &unit); - if (r < 0) - return log_error_errno(r, "Failed to make unit name from path: %m"); - - return write_drop_in_format(dir, unit, 50, "device-timeout", - "# Automatically generated by %s\n\n" - "[Unit]\nJobTimeoutSec=%s", - program_invocation_short_name, timeout); -} diff --git a/src/shared/generator.h b/src/shared/generator.h deleted file mode 100644 index a734e13970..0000000000 --- a/src/shared/generator.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -int generator_write_fsck_deps( - FILE *f, - const char *dir, - const char *what, - const char *where, - const char *type); - -int generator_write_timeouts( - const char *dir, - const char *what, - const char *where, - const char *opts, - char **filtered); diff --git a/src/shared/gpt.h b/src/shared/gpt.h deleted file mode 100644 index 52ab29ed5f..0000000000 --- a/src/shared/gpt.h +++ /dev/null @@ -1,66 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#pragma once - -#include - -#include "sd-id128.h" - -/* We only support root disk discovery for x86, x86-64, Itanium and ARM for - * now, since EFI for anything else doesn't really exist, and we only - * care for root partitions on the same disk as the EFI ESP. */ - -#define GPT_ROOT_X86 SD_ID128_MAKE(44,47,95,40,f2,97,41,b2,9a,f7,d1,31,d5,f0,45,8a) -#define GPT_ROOT_X86_64 SD_ID128_MAKE(4f,68,bc,e3,e8,cd,4d,b1,96,e7,fb,ca,f9,84,b7,09) -#define GPT_ROOT_ARM SD_ID128_MAKE(69,da,d7,10,2c,e4,4e,3c,b1,6c,21,a1,d4,9a,be,d3) -#define GPT_ROOT_ARM_64 SD_ID128_MAKE(b9,21,b0,45,1d,f0,41,c3,af,44,4c,6f,28,0d,3f,ae) -#define GPT_ROOT_IA64 SD_ID128_MAKE(99,3d,8d,3d,f8,0e,42,25,85,5a,9d,af,8e,d7,ea,97) - -#define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b) -#define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f) -#define GPT_HOME SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15) -#define GPT_SRV SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8) - -#if defined(__x86_64__) -# define GPT_ROOT_NATIVE GPT_ROOT_X86_64 -# define GPT_ROOT_SECONDARY GPT_ROOT_X86 -#elif defined(__i386__) -# define GPT_ROOT_NATIVE GPT_ROOT_X86 -#endif - -#if defined(__ia64__) -# define GPT_ROOT_NATIVE GPT_ROOT_IA64 -#endif - -#if defined(__aarch64__) && (__BYTE_ORDER != __BIG_ENDIAN) -# define GPT_ROOT_NATIVE GPT_ROOT_ARM_64 -# define GPT_ROOT_SECONDARY GPT_ROOT_ARM -#elif defined(__arm__) && (__BYTE_ORDER != __BIG_ENDIAN) -# define GPT_ROOT_NATIVE GPT_ROOT_ARM -#endif - -/* Flags we recognize on the root, swap, home and srv partitions when - * doing auto-discovery. These happen to be identical to what - * Microsoft defines for its own Basic Data Partitions, but that's - * just because we saw no point in defining any other values here. */ -#define GPT_FLAG_READ_ONLY (1ULL << 60) -#define GPT_FLAG_NO_AUTO (1ULL << 63) - -#define GPT_LINUX_GENERIC SD_ID128_MAKE(0f,c6,3d,af,84,83,47,72,8e,79,3d,69,d8,47,7d,e4) diff --git a/src/shared/ima-util.c b/src/shared/ima-util.c deleted file mode 100644 index 789064d653..0000000000 --- a/src/shared/ima-util.c +++ /dev/null @@ -1,32 +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 . -***/ - -#include - -#include "ima-util.h" - -static int use_ima_cached = -1; - -bool use_ima(void) { - - if (use_ima_cached < 0) - use_ima_cached = access("/sys/kernel/security/ima/", F_OK) >= 0; - - return use_ima_cached; -} diff --git a/src/shared/ima-util.h b/src/shared/ima-util.h deleted file mode 100644 index 5be94761fd..0000000000 --- a/src/shared/ima-util.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include - -bool use_ima(void); diff --git a/src/shared/import-util.c b/src/shared/import-util.c deleted file mode 100644 index ab701ad8b2..0000000000 --- a/src/shared/import-util.c +++ /dev/null @@ -1,185 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include - -#include "alloc-util.h" -#include "btrfs-util.h" -#include "import-util.h" -#include "log.h" -#include "macro.h" -#include "path-util.h" -#include "string-table.h" -#include "string-util.h" -#include "util.h" - -int import_url_last_component(const char *url, char **ret) { - const char *e, *p; - char *s; - - e = strchrnul(url, '?'); - - while (e > url && e[-1] == '/') - e--; - - p = e; - while (p > url && p[-1] != '/') - p--; - - if (e <= p) - return -EINVAL; - - s = strndup(p, e - p); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - - -int import_url_change_last_component(const char *url, const char *suffix, char **ret) { - const char *e; - char *s; - - assert(url); - assert(ret); - - e = strchrnul(url, '?'); - - while (e > url && e[-1] == '/') - e--; - - while (e > url && e[-1] != '/') - e--; - - if (e <= url) - return -EINVAL; - - s = new(char, (e - url) + strlen(suffix) + 1); - if (!s) - return -ENOMEM; - - strcpy(mempcpy(s, url, e - url), suffix); - *ret = s; - return 0; -} - -static const char* const import_verify_table[_IMPORT_VERIFY_MAX] = { - [IMPORT_VERIFY_NO] = "no", - [IMPORT_VERIFY_CHECKSUM] = "checksum", - [IMPORT_VERIFY_SIGNATURE] = "signature", -}; - -DEFINE_STRING_TABLE_LOOKUP(import_verify, ImportVerify); - -int tar_strip_suffixes(const char *name, char **ret) { - const char *e; - char *s; - - e = endswith(name, ".tar"); - if (!e) - e = endswith(name, ".tar.xz"); - if (!e) - e = endswith(name, ".tar.gz"); - if (!e) - e = endswith(name, ".tar.bz2"); - if (!e) - e = endswith(name, ".tgz"); - if (!e) - e = strchr(name, 0); - - if (e <= name) - return -EINVAL; - - s = strndup(name, e - name); - if (!s) - return -ENOMEM; - - *ret = s; - return 0; -} - -int raw_strip_suffixes(const char *p, char **ret) { - - static const char suffixes[] = - ".xz\0" - ".gz\0" - ".bz2\0" - ".raw\0" - ".qcow2\0" - ".img\0" - ".bin\0"; - - _cleanup_free_ char *q = NULL; - - q = strdup(p); - if (!q) - return -ENOMEM; - - for (;;) { - const char *sfx; - bool changed = false; - - NULSTR_FOREACH(sfx, suffixes) { - char *e; - - e = endswith(q, sfx); - if (e) { - *e = 0; - changed = true; - } - } - - if (!changed) - break; - } - - *ret = q; - q = NULL; - - return 0; -} - -int import_assign_pool_quota_and_warn(const char *path) { - int r; - - r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true); - if (r == -ENOTTY) { - log_debug_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, as directory is not on btrfs or not a subvolume. Ignoring."); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines: %m"); - if (r > 0) - log_info("Set up default quota hierarchy for /var/lib/machines."); - - r = btrfs_subvol_auto_qgroup(path, 0, true); - if (r == -ENOTTY) { - log_debug_errno(r, "Failed to set up quota hierarchy for %s, as directory is not on btrfs or not a subvolume. Ignoring.", path); - return 0; - } - if (r < 0) - return log_error_errno(r, "Failed to set up default quota hierarchy for %s: %m", path); - if (r > 0) - log_info("Set up default quota hierarchy for %s.", path); - - return 0; -} diff --git a/src/shared/import-util.h b/src/shared/import-util.h deleted file mode 100644 index 77b17d91f3..0000000000 --- a/src/shared/import-util.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include - -#include "macro.h" - -typedef enum ImportVerify { - IMPORT_VERIFY_NO, - IMPORT_VERIFY_CHECKSUM, - IMPORT_VERIFY_SIGNATURE, - _IMPORT_VERIFY_MAX, - _IMPORT_VERIFY_INVALID = -1, -} ImportVerify; - -int import_url_last_component(const char *url, char **ret); -int import_url_change_last_component(const char *url, const char *suffix, char **ret); - -const char* import_verify_to_string(ImportVerify v) _const_; -ImportVerify import_verify_from_string(const char *s) _pure_; - -int tar_strip_suffixes(const char *name, char **ret); -int raw_strip_suffixes(const char *name, char **ret); - -int import_assign_pool_quota_and_warn(const char *path); diff --git a/src/shared/initreq.h b/src/shared/initreq.h deleted file mode 100644 index 710037d84b..0000000000 --- a/src/shared/initreq.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - * initreq.h Interface to talk to init through /dev/initctl. - * - * Copyright (C) 1995-2004 Miquel van Smoorenburg - * - * This library 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 of the License, or (at your option) any later version. - * - * Version: @(#)initreq.h 1.28 31-Mar-2004 MvS - * - */ -#ifndef _INITREQ_H -#define _INITREQ_H - -#include - -#if defined(__FreeBSD_kernel__) -# define INIT_FIFO "/etc/.initctl" -#else -# define INIT_FIFO "/dev/initctl" -#endif - -#define INIT_MAGIC 0x03091969 -#define INIT_CMD_START 0 -#define INIT_CMD_RUNLVL 1 -#define INIT_CMD_POWERFAIL 2 -#define INIT_CMD_POWERFAILNOW 3 -#define INIT_CMD_POWEROK 4 -#define INIT_CMD_BSD 5 -#define INIT_CMD_SETENV 6 -#define INIT_CMD_UNSETENV 7 - -#define INIT_CMD_CHANGECONS 12345 - -#ifdef MAXHOSTNAMELEN -# define INITRQ_HLEN MAXHOSTNAMELEN -#else -# define INITRQ_HLEN 64 -#endif - -/* - * This is what BSD 4.4 uses when talking to init. - * Linux doesn't use this right now. - */ -struct init_request_bsd { - char gen_id[8]; /* Beats me.. telnetd uses "fe" */ - char tty_id[16]; /* Tty name minus /dev/tty */ - char host[INITRQ_HLEN]; /* Hostname */ - char term_type[16]; /* Terminal type */ - int signal; /* Signal to send */ - int pid; /* Process to send to */ - char exec_name[128]; /* Program to execute */ - char reserved[128]; /* For future expansion. */ -}; - - -/* - * Because of legacy interfaces, "runlevel" and "sleeptime" - * aren't in a separate struct in the union. - * - * The weird sizes are because init expects the whole - * struct to be 384 bytes. - */ -struct init_request { - int magic; /* Magic number */ - int cmd; /* What kind of request */ - int runlevel; /* Runlevel to change to */ - int sleeptime; /* Time between TERM and KILL */ - union { - struct init_request_bsd bsd; - char data[368]; - } i; -}; - -#endif diff --git a/src/shared/install-printf.c b/src/shared/install-printf.c deleted file mode 100644 index 88143361da..0000000000 --- a/src/shared/install-printf.c +++ /dev/null @@ -1,133 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#include -#include -#include -#include - -#include "formats-util.h" -#include "install-printf.h" -#include "install.h" -#include "macro.h" -#include "specifier.h" -#include "unit-name.h" -#include "user-util.h" - -static int specifier_prefix_and_instance(char specifier, void *data, void *userdata, char **ret) { - UnitFileInstallInfo *i = userdata; - - assert(i); - - return unit_name_to_prefix_and_instance(i->name, ret); -} - -static int specifier_prefix(char specifier, void *data, void *userdata, char **ret) { - UnitFileInstallInfo *i = userdata; - - assert(i); - - return unit_name_to_prefix(i->name, ret); -} - -static int specifier_instance(char specifier, void *data, void *userdata, char **ret) { - UnitFileInstallInfo *i = userdata; - char *instance; - int r; - - assert(i); - - r = unit_name_to_instance(i->name, &instance); - if (r < 0) - return r; - - if (!instance) { - instance = strdup(""); - if (!instance) - return -ENOMEM; - } - - *ret = instance; - return 0; -} - -static int specifier_user_name(char specifier, void *data, void *userdata, char **ret) { - char *t; - - /* If we are UID 0 (root), this will not result in NSS, - * otherwise it might. This is good, as we want to be able to - * run this in PID 1, where our user ID is 0, but where NSS - * lookups are not allowed. */ - - t = getusername_malloc(); - if (!t) - return -ENOMEM; - - *ret = t; - return 0; -} - -static int specifier_user_id(char specifier, void *data, void *userdata, char **ret) { - - if (asprintf(ret, UID_FMT, getuid()) < 0) - return -ENOMEM; - - return 0; -} - -int install_full_printf(UnitFileInstallInfo *i, const char *format, char **ret) { - - /* This is similar to unit_full_printf() but does not support - * anything path-related. - * - * %n: the full id of the unit (foo@bar.waldo) - * %N: the id of the unit without the suffix (foo@bar) - * %p: the prefix (foo) - * %i: the instance (bar) - - * %U the UID of the running user - * %u the username of running user - * %m the machine ID of the running system - * %H the host name of the running system - * %b the boot ID of the running system - * %v `uname -r` of the running system - */ - - const Specifier table[] = { - { 'n', specifier_string, i->name }, - { 'N', specifier_prefix_and_instance, NULL }, - { 'p', specifier_prefix, NULL }, - { 'i', specifier_instance, NULL }, - - { 'U', specifier_user_id, NULL }, - { 'u', specifier_user_name, NULL }, - - { 'm', specifier_machine_id, NULL }, - { 'H', specifier_host_name, NULL }, - { 'b', specifier_boot_id, NULL }, - { 'v', specifier_kernel_release, NULL }, - {} - }; - - assert(i); - assert(format); - assert(ret); - - return specifier_printf(format, table, i, ret); -} diff --git a/src/shared/install-printf.h b/src/shared/install-printf.h deleted file mode 100644 index acf519f4f7..0000000000 --- a/src/shared/install-printf.h +++ /dev/null @@ -1,24 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#pragma once - -#include "install.h" - -int install_full_printf(UnitFileInstallInfo *i, const char *format, char **ret); diff --git a/src/shared/install.c b/src/shared/install.c deleted file mode 100644 index ef8f485cae..0000000000 --- a/src/shared/install.c +++ /dev/null @@ -1,2474 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "conf-files.h" -#include "conf-parser.h" -#include "dirent-util.h" -#include "extract-word.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "hashmap.h" -#include "install-printf.h" -#include "install.h" -#include "log.h" -#include "macro.h" -#include "mkdir.h" -#include "path-lookup.h" -#include "path-util.h" -#include "set.h" -#include "special.h" -#include "stat-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "unit-name.h" - -#define UNIT_FILE_FOLLOW_SYMLINK_MAX 64 - -typedef enum SearchFlags { - SEARCH_LOAD = 1, - SEARCH_FOLLOW_CONFIG_SYMLINKS = 2, -} SearchFlags; - -typedef struct { - OrderedHashmap *will_process; - OrderedHashmap *have_processed; -} InstallContext; - -static int in_search_path(const char *path, char **search) { - _cleanup_free_ char *parent = NULL; - char **i; - - assert(path); - - parent = dirname_malloc(path); - if (!parent) - return -ENOMEM; - - STRV_FOREACH(i, search) - if (path_equal(parent, *i)) - return true; - - return false; -} - -static int get_config_path(UnitFileScope scope, bool runtime, const char *root_dir, char **ret) { - char *p = NULL; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(ret); - - /* This determines where we shall create or remove our - * installation ("configuration") symlinks */ - - switch (scope) { - - case UNIT_FILE_SYSTEM: - - if (runtime) - p = path_join(root_dir, "/run/systemd/system", NULL); - else - p = path_join(root_dir, SYSTEM_CONFIG_UNIT_PATH, NULL); - break; - - case UNIT_FILE_GLOBAL: - - if (root_dir) - return -EINVAL; - - if (runtime) - p = strdup("/run/systemd/user"); - else - p = strdup(USER_CONFIG_UNIT_PATH); - break; - - case UNIT_FILE_USER: - - if (root_dir) - return -EINVAL; - - if (runtime) - r = user_runtime_dir(&p); - else - r = user_config_home(&p); - if (r < 0) - return r; - if (r == 0) - return -ENOENT; - - break; - - default: - assert_not_reached("Bad scope"); - } - - if (!p) - return -ENOMEM; - - *ret = p; - return 0; -} - -static bool is_config_path(UnitFileScope scope, const char *path) { - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(path); - - /* Checks whether the specified path is intended for - * configuration or is outside of it */ - - switch (scope) { - - case UNIT_FILE_SYSTEM: - case UNIT_FILE_GLOBAL: - return path_startswith(path, "/etc") || - path_startswith(path, SYSTEM_CONFIG_UNIT_PATH) || - path_startswith(path, "/run"); - - - case UNIT_FILE_USER: { - _cleanup_free_ char *p = NULL; - - r = user_config_home(&p); - if (r < 0) - return r; - if (r > 0 && path_startswith(path, p)) - return true; - - p = mfree(p); - - r = user_runtime_dir(&p); - if (r < 0) - return r; - if (r > 0 && path_startswith(path, p)) - return true; - - return false; - } - - default: - assert_not_reached("Bad scope"); - } -} - - -static int verify_root_dir(UnitFileScope scope, const char **root_dir) { - int r; - - assert(root_dir); - - /* Verifies that the specified root directory to operate on - * makes sense. Reset it to NULL if it is the root directory - * or set to empty */ - - if (isempty(*root_dir) || path_equal(*root_dir, "/")) { - *root_dir = NULL; - return 0; - } - - if (scope != UNIT_FILE_SYSTEM) - return -EINVAL; - - r = is_dir(*root_dir, true); - if (r < 0) - return r; - if (r == 0) - return -ENOTDIR; - - return 0; -} - -int unit_file_changes_add( - UnitFileChange **changes, - unsigned *n_changes, - UnitFileChangeType type, - const char *path, - const char *source) { - - UnitFileChange *c; - unsigned i; - - assert(path); - assert(!changes == !n_changes); - - if (!changes) - return 0; - - c = realloc(*changes, (*n_changes + 1) * sizeof(UnitFileChange)); - if (!c) - return -ENOMEM; - - *changes = c; - i = *n_changes; - - c[i].type = type; - c[i].path = strdup(path); - if (!c[i].path) - return -ENOMEM; - - path_kill_slashes(c[i].path); - - if (source) { - c[i].source = strdup(source); - if (!c[i].source) { - free(c[i].path); - return -ENOMEM; - } - - path_kill_slashes(c[i].path); - } else - c[i].source = NULL; - - *n_changes = i+1; - return 0; -} - -void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes) { - unsigned i; - - assert(changes || n_changes == 0); - - if (!changes) - return; - - for (i = 0; i < n_changes; i++) { - free(changes[i].path); - free(changes[i].source); - } - - free(changes); -} - -static int create_symlink( - const char *old_path, - const char *new_path, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_free_ char *dest = NULL; - int r; - - assert(old_path); - assert(new_path); - - /* Actually create a symlink, and remember that we did. Is - * smart enough to check if there's already a valid symlink in - * place. */ - - mkdir_parents_label(new_path, 0755); - - if (symlink(old_path, new_path) >= 0) { - unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); - return 0; - } - - if (errno != EEXIST) - return -errno; - - r = readlink_malloc(new_path, &dest); - if (r < 0) - return r; - - if (path_equal(dest, old_path)) - return 0; - - if (!force) - return -EEXIST; - - r = symlink_atomic(old_path, new_path); - if (r < 0) - return r; - - unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, new_path, NULL); - unit_file_changes_add(changes, n_changes, UNIT_FILE_SYMLINK, new_path, old_path); - - return 0; -} - -static int mark_symlink_for_removal( - Set **remove_symlinks_to, - const char *p) { - - char *n; - int r; - - assert(p); - - r = set_ensure_allocated(remove_symlinks_to, &string_hash_ops); - if (r < 0) - return r; - - n = strdup(p); - if (!n) - return -ENOMEM; - - path_kill_slashes(n); - - r = set_consume(*remove_symlinks_to, n); - if (r == -EEXIST) - return 0; - if (r < 0) - return r; - - return 1; -} - -static int remove_marked_symlinks_fd( - Set *remove_symlinks_to, - int fd, - const char *path, - const char *config_path, - bool *restart, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r = 0; - - assert(remove_symlinks_to); - assert(fd >= 0); - assert(path); - assert(config_path); - assert(restart); - - d = fdopendir(fd); - if (!d) { - safe_close(fd); - return -errno; - } - - rewinddir(d); - - FOREACH_DIRENT(de, d, return -errno) { - - dirent_ensure_type(d, de); - - if (de->d_type == DT_DIR) { - _cleanup_free_ char *p = NULL; - int nfd, q; - - nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (nfd < 0) { - if (errno == ENOENT) - continue; - - if (r == 0) - r = -errno; - continue; - } - - p = path_make_absolute(de->d_name, path); - if (!p) { - safe_close(nfd); - return -ENOMEM; - } - - /* This will close nfd, regardless whether it succeeds or not */ - q = remove_marked_symlinks_fd(remove_symlinks_to, nfd, p, config_path, restart, changes, n_changes); - if (q < 0 && r == 0) - r = q; - - } else if (de->d_type == DT_LNK) { - _cleanup_free_ char *p = NULL, *dest = NULL; - bool found; - int q; - - if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) - continue; - - p = path_make_absolute(de->d_name, path); - if (!p) - return -ENOMEM; - - q = readlink_malloc(p, &dest); - if (q < 0) { - if (q == -ENOENT) - continue; - - if (r == 0) - r = q; - continue; - } - - /* We remove all links pointing to a file or - * path that is marked, as well as all files - * sharing the same name as a file that is - * marked. */ - - found = - set_contains(remove_symlinks_to, dest) || - set_contains(remove_symlinks_to, basename(dest)) || - set_contains(remove_symlinks_to, de->d_name); - - if (!found) - continue; - - if (unlink(p) < 0 && errno != ENOENT) { - if (r == 0) - r = -errno; - continue; - } - - path_kill_slashes(p); - (void) rmdir_parents(p, config_path); - - unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, p, NULL); - - q = mark_symlink_for_removal(&remove_symlinks_to, p); - if (q < 0) - return q; - if (q > 0) - *restart = true; - } - } - - return r; -} - -static int remove_marked_symlinks( - Set *remove_symlinks_to, - const char *config_path, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_close_ int fd = -1; - bool restart; - int r = 0; - - assert(config_path); - - if (set_size(remove_symlinks_to) <= 0) - return 0; - - fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (fd < 0) - return -errno; - - do { - int q, cfd; - restart = false; - - cfd = fcntl(fd, F_DUPFD_CLOEXEC, 3); - if (cfd < 0) - return -errno; - - /* This takes possession of cfd and closes it */ - q = remove_marked_symlinks_fd(remove_symlinks_to, cfd, config_path, config_path, &restart, changes, n_changes); - if (r == 0) - r = q; - } while (restart); - - return r; -} - -static int find_symlinks_fd( - const char *root_dir, - const char *name, - int fd, - const char *path, - const char *config_path, - bool *same_name_link) { - - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - int r = 0; - - assert(name); - assert(fd >= 0); - assert(path); - assert(config_path); - assert(same_name_link); - - d = fdopendir(fd); - if (!d) { - safe_close(fd); - return -errno; - } - - FOREACH_DIRENT(de, d, return -errno) { - - dirent_ensure_type(d, de); - - if (de->d_type == DT_DIR) { - _cleanup_free_ char *p = NULL; - int nfd, q; - - nfd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (nfd < 0) { - if (errno == ENOENT) - continue; - - if (r == 0) - r = -errno; - continue; - } - - p = path_make_absolute(de->d_name, path); - if (!p) { - safe_close(nfd); - return -ENOMEM; - } - - /* This will close nfd, regardless whether it succeeds or not */ - q = find_symlinks_fd(root_dir, name, nfd, p, config_path, same_name_link); - if (q > 0) - return 1; - if (r == 0) - r = q; - - } else if (de->d_type == DT_LNK) { - _cleanup_free_ char *p = NULL, *dest = NULL; - bool found_path, found_dest, b = false; - int q; - - /* Acquire symlink name */ - p = path_make_absolute(de->d_name, path); - if (!p) - return -ENOMEM; - - /* Acquire symlink destination */ - q = readlink_malloc(p, &dest); - if (q == -ENOENT) - continue; - if (q < 0) { - if (r == 0) - r = q; - continue; - } - - /* Make absolute */ - if (!path_is_absolute(dest)) { - char *x; - - x = prefix_root(root_dir, dest); - if (!x) - return -ENOMEM; - - free(dest); - dest = x; - } - - /* Check if the symlink itself matches what we - * are looking for */ - if (path_is_absolute(name)) - found_path = path_equal(p, name); - else - found_path = streq(de->d_name, name); - - /* Check if what the symlink points to - * matches what we are looking for */ - if (path_is_absolute(name)) - found_dest = path_equal(dest, name); - else - found_dest = streq(basename(dest), name); - - if (found_path && found_dest) { - _cleanup_free_ char *t = NULL; - - /* Filter out same name links in the main - * config path */ - t = path_make_absolute(name, config_path); - if (!t) - return -ENOMEM; - - b = path_equal(t, p); - } - - if (b) - *same_name_link = true; - else if (found_path || found_dest) - return 1; - } - } - - return r; -} - -static int find_symlinks( - const char *root_dir, - const char *name, - const char *config_path, - bool *same_name_link) { - - int fd; - - assert(name); - assert(config_path); - assert(same_name_link); - - fd = open(config_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); - if (fd < 0) { - if (errno == ENOENT) - return 0; - return -errno; - } - - /* This takes possession of fd and closes it */ - return find_symlinks_fd(root_dir, name, fd, config_path, config_path, same_name_link); -} - -static int find_symlinks_in_scope( - UnitFileScope scope, - const char *root_dir, - const char *name, - UnitFileState *state) { - - _cleanup_free_ char *normal_path = NULL, *runtime_path = NULL; - bool same_name_link_runtime = false, same_name_link = false; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(name); - - /* First look in the normal config path */ - r = get_config_path(scope, false, root_dir, &normal_path); - if (r < 0) - return r; - - r = find_symlinks(root_dir, name, normal_path, &same_name_link); - if (r < 0) - return r; - if (r > 0) { - *state = UNIT_FILE_ENABLED; - return r; - } - - /* Then look in runtime config path */ - r = get_config_path(scope, true, root_dir, &runtime_path); - if (r < 0) - return r; - - r = find_symlinks(root_dir, name, runtime_path, &same_name_link_runtime); - if (r < 0) - return r; - if (r > 0) { - *state = UNIT_FILE_ENABLED_RUNTIME; - return r; - } - - /* Hmm, we didn't find it, but maybe we found the same name - * link? */ - if (same_name_link) { - *state = UNIT_FILE_LINKED; - return 1; - } - if (same_name_link_runtime) { - *state = UNIT_FILE_LINKED_RUNTIME; - return 1; - } - - return 0; -} - -static void install_info_free(UnitFileInstallInfo *i) { - - if (!i) - return; - - free(i->name); - free(i->path); - strv_free(i->aliases); - strv_free(i->wanted_by); - strv_free(i->required_by); - strv_free(i->also); - free(i->default_instance); - free(i->symlink_target); - free(i); -} - -static OrderedHashmap* install_info_hashmap_free(OrderedHashmap *m) { - UnitFileInstallInfo *i; - - if (!m) - return NULL; - - while ((i = ordered_hashmap_steal_first(m))) - install_info_free(i); - - return ordered_hashmap_free(m); -} - -static void install_context_done(InstallContext *c) { - assert(c); - - c->will_process = install_info_hashmap_free(c->will_process); - c->have_processed = install_info_hashmap_free(c->have_processed); -} - -static UnitFileInstallInfo *install_info_find(InstallContext *c, const char *name) { - UnitFileInstallInfo *i; - - i = ordered_hashmap_get(c->have_processed, name); - if (i) - return i; - - return ordered_hashmap_get(c->will_process, name); -} - -static int install_info_add( - InstallContext *c, - const char *name, - const char *path, - UnitFileInstallInfo **ret) { - - UnitFileInstallInfo *i = NULL; - int r; - - assert(c); - assert(name || path); - - if (!name) - name = basename(path); - - if (!unit_name_is_valid(name, UNIT_NAME_ANY)) - return -EINVAL; - - i = install_info_find(c, name); - if (i) { - if (ret) - *ret = i; - return 0; - } - - r = ordered_hashmap_ensure_allocated(&c->will_process, &string_hash_ops); - if (r < 0) - return r; - - i = new0(UnitFileInstallInfo, 1); - if (!i) - return -ENOMEM; - i->type = _UNIT_FILE_TYPE_INVALID; - - i->name = strdup(name); - if (!i->name) { - r = -ENOMEM; - goto fail; - } - - if (path) { - i->path = strdup(path); - if (!i->path) { - r = -ENOMEM; - goto fail; - } - } - - r = ordered_hashmap_put(c->will_process, i->name, i); - if (r < 0) - goto fail; - - if (ret) - *ret = i; - - return 0; - -fail: - install_info_free(i); - return r; -} - -static int install_info_add_auto( - InstallContext *c, - const char *name_or_path, - UnitFileInstallInfo **ret) { - - assert(c); - assert(name_or_path); - - if (path_is_absolute(name_or_path)) - return install_info_add(c, NULL, name_or_path, ret); - else - return install_info_add(c, name_or_path, NULL, ret); -} - -static int config_parse_also( - 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) { - - UnitFileInstallInfo *i = userdata; - InstallContext *c = data; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&rvalue, &word, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - r = install_info_add(c, word, NULL, NULL); - if (r < 0) - return r; - - r = strv_push(&i->also, word); - if (r < 0) - return r; - - word = NULL; - } - - return 0; -} - -static int config_parse_default_instance( - 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) { - - UnitFileInstallInfo *i = data; - char *printed; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - r = install_full_printf(i, rvalue, &printed); - if (r < 0) - return r; - - if (!unit_instance_is_valid(printed)) { - free(printed); - return -EINVAL; - } - - free(i->default_instance); - i->default_instance = printed; - - return 0; -} - -static int unit_file_load( - InstallContext *c, - UnitFileInstallInfo *info, - const char *path, - const char *root_dir, - SearchFlags flags) { - - const ConfigTableItem items[] = { - { "Install", "Alias", config_parse_strv, 0, &info->aliases }, - { "Install", "WantedBy", config_parse_strv, 0, &info->wanted_by }, - { "Install", "RequiredBy", config_parse_strv, 0, &info->required_by }, - { "Install", "DefaultInstance", config_parse_default_instance, 0, info }, - { "Install", "Also", config_parse_also, 0, c }, - {} - }; - - _cleanup_fclose_ FILE *f = NULL; - _cleanup_close_ int fd = -1; - struct stat st; - int r; - - assert(c); - assert(info); - assert(path); - - path = prefix_roota(root_dir, path); - - if (!(flags & SEARCH_LOAD)) { - r = lstat(path, &st); - if (r < 0) - return -errno; - - if (null_or_empty(&st)) - info->type = UNIT_FILE_TYPE_MASKED; - else if (S_ISREG(st.st_mode)) - info->type = UNIT_FILE_TYPE_REGULAR; - else if (S_ISLNK(st.st_mode)) - return -ELOOP; - else if (S_ISDIR(st.st_mode)) - return -EISDIR; - else - return -ENOTTY; - - return 0; - } - - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); - if (fd < 0) - return -errno; - if (fstat(fd, &st) < 0) - return -errno; - if (null_or_empty(&st)) { - info->type = UNIT_FILE_TYPE_MASKED; - return 0; - } - if (S_ISDIR(st.st_mode)) - return -EISDIR; - if (!S_ISREG(st.st_mode)) - return -ENOTTY; - - f = fdopen(fd, "re"); - if (!f) - return -errno; - fd = -1; - - r = config_parse(NULL, path, f, - NULL, - config_item_table_lookup, items, - true, true, false, info); - if (r < 0) - return r; - - info->type = UNIT_FILE_TYPE_REGULAR; - - return - (int) strv_length(info->aliases) + - (int) strv_length(info->wanted_by) + - (int) strv_length(info->required_by); -} - -static int unit_file_load_or_readlink( - InstallContext *c, - UnitFileInstallInfo *info, - const char *path, - const char *root_dir, - SearchFlags flags) { - - _cleanup_free_ char *np = NULL; - int r; - - r = unit_file_load(c, info, path, root_dir, flags); - if (r != -ELOOP) - return r; - - /* This is a symlink, let's read it. */ - - r = readlink_and_make_absolute_root(root_dir, path, &np); - if (r < 0) - return r; - - if (path_equal(np, "/dev/null")) - info->type = UNIT_FILE_TYPE_MASKED; - else { - const char *bn; - UnitType a, b; - - bn = basename(np); - - if (unit_name_is_valid(info->name, UNIT_NAME_PLAIN)) { - - if (!unit_name_is_valid(bn, UNIT_NAME_PLAIN)) - return -EINVAL; - - } else if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) { - - if (!unit_name_is_valid(bn, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) - return -EINVAL; - - } else if (unit_name_is_valid(info->name, UNIT_NAME_TEMPLATE)) { - - if (!unit_name_is_valid(bn, UNIT_NAME_TEMPLATE)) - return -EINVAL; - } else - return -EINVAL; - - /* Enforce that the symlink destination does not - * change the unit file type. */ - - a = unit_name_to_type(info->name); - b = unit_name_to_type(bn); - if (a < 0 || b < 0 || a != b) - return -EINVAL; - - info->type = UNIT_FILE_TYPE_SYMLINK; - info->symlink_target = np; - np = NULL; - } - - return 0; -} - -static int unit_file_search( - InstallContext *c, - UnitFileInstallInfo *info, - const LookupPaths *paths, - const char *root_dir, - SearchFlags flags) { - - char **p; - int r; - - assert(c); - assert(info); - assert(paths); - - /* Was this unit already loaded? */ - if (info->type != _UNIT_FILE_TYPE_INVALID) - return 0; - - if (info->path) - return unit_file_load_or_readlink(c, info, info->path, root_dir, flags); - - assert(info->name); - - STRV_FOREACH(p, paths->unit_path) { - _cleanup_free_ char *path = NULL; - - path = strjoin(*p, "/", info->name, NULL); - if (!path) - return -ENOMEM; - - r = unit_file_load_or_readlink(c, info, path, root_dir, flags); - if (r < 0) { - if (r != -ENOENT) - return r; - } else { - info->path = path; - path = NULL; - return r; - } - } - - if (unit_name_is_valid(info->name, UNIT_NAME_INSTANCE)) { - - /* Unit file doesn't exist, however instance - * enablement was requested. We will check if it is - * possible to load template unit file. */ - - _cleanup_free_ char *template = NULL; - - r = unit_name_template(info->name, &template); - if (r < 0) - return r; - - STRV_FOREACH(p, paths->unit_path) { - _cleanup_free_ char *path = NULL; - - path = strjoin(*p, "/", template, NULL); - if (!path) - return -ENOMEM; - - r = unit_file_load_or_readlink(c, info, path, root_dir, flags); - if (r < 0) { - if (r != -ENOENT) - return r; - } else { - info->path = path; - path = NULL; - return r; - } - } - } - - return -ENOENT; -} - -static int install_info_follow( - InstallContext *c, - UnitFileInstallInfo *i, - const char *root_dir, - SearchFlags flags) { - - assert(c); - assert(i); - - if (i->type != UNIT_FILE_TYPE_SYMLINK) - return -EINVAL; - if (!i->symlink_target) - return -EINVAL; - - /* If the basename doesn't match, the caller should add a - * complete new entry for this. */ - - if (!streq(basename(i->symlink_target), i->name)) - return -EXDEV; - - free(i->path); - i->path = i->symlink_target; - i->symlink_target = NULL; - i->type = _UNIT_FILE_TYPE_INVALID; - - return unit_file_load_or_readlink(c, i, i->path, root_dir, flags); -} - -static int install_info_traverse( - UnitFileScope scope, - InstallContext *c, - const char *root_dir, - const LookupPaths *paths, - UnitFileInstallInfo *start, - SearchFlags flags, - UnitFileInstallInfo **ret) { - - UnitFileInstallInfo *i; - unsigned k = 0; - int r; - - assert(paths); - assert(start); - assert(c); - - r = unit_file_search(c, start, paths, root_dir, flags); - if (r < 0) - return r; - - i = start; - while (i->type == UNIT_FILE_TYPE_SYMLINK) { - /* Follow the symlink */ - - if (++k > UNIT_FILE_FOLLOW_SYMLINK_MAX) - return -ELOOP; - - if (!(flags & SEARCH_FOLLOW_CONFIG_SYMLINKS) && is_config_path(scope, i->path)) - return -ELOOP; - - r = install_info_follow(c, i, root_dir, flags); - if (r < 0) { - _cleanup_free_ char *buffer = NULL; - const char *bn; - - if (r != -EXDEV) - return r; - - /* Target has a different name, create a new - * install info object for that, and continue - * with that. */ - - bn = basename(i->symlink_target); - - if (unit_name_is_valid(i->name, UNIT_NAME_INSTANCE) && - unit_name_is_valid(bn, UNIT_NAME_TEMPLATE)) { - - _cleanup_free_ char *instance = NULL; - - r = unit_name_to_instance(i->name, &instance); - if (r < 0) - return r; - - r = unit_name_replace_instance(bn, instance, &buffer); - if (r < 0) - return r; - - bn = buffer; - } - - r = install_info_add(c, bn, NULL, &i); - if (r < 0) - return r; - - r = unit_file_search(c, i, paths, root_dir, flags); - if (r < 0) - return r; - } - - /* Try again, with the new target we found. */ - } - - if (ret) - *ret = i; - - return 0; -} - -static int install_info_discover( - UnitFileScope scope, - InstallContext *c, - const char *root_dir, - const LookupPaths *paths, - const char *name, - SearchFlags flags, - UnitFileInstallInfo **ret) { - - UnitFileInstallInfo *i; - int r; - - assert(c); - assert(paths); - assert(name); - - r = install_info_add_auto(c, name, &i); - if (r < 0) - return r; - - return install_info_traverse(scope, c, root_dir, paths, i, flags, ret); -} - -static int install_info_symlink_alias( - UnitFileInstallInfo *i, - const char *config_path, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - char **s; - int r = 0, q; - - assert(i); - assert(config_path); - - STRV_FOREACH(s, i->aliases) { - _cleanup_free_ char *alias_path = NULL, *dst = NULL; - - q = install_full_printf(i, *s, &dst); - if (q < 0) - return q; - - alias_path = path_make_absolute(dst, config_path); - if (!alias_path) - return -ENOMEM; - - q = create_symlink(i->path, alias_path, force, changes, n_changes); - if (r == 0) - r = q; - } - - return r; -} - -static int install_info_symlink_wants( - UnitFileInstallInfo *i, - const char *config_path, - char **list, - const char *suffix, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_free_ char *buf = NULL; - const char *n; - char **s; - int r = 0, q; - - assert(i); - assert(config_path); - - if (unit_name_is_valid(i->name, UNIT_NAME_TEMPLATE)) { - - /* Don't install any symlink if there's no default - * instance configured */ - - if (!i->default_instance) - return 0; - - r = unit_name_replace_instance(i->name, i->default_instance, &buf); - if (r < 0) - return r; - - n = buf; - } else - n = i->name; - - STRV_FOREACH(s, list) { - _cleanup_free_ char *path = NULL, *dst = NULL; - - q = install_full_printf(i, *s, &dst); - if (q < 0) - return q; - - if (!unit_name_is_valid(dst, UNIT_NAME_ANY)) { - r = -EINVAL; - continue; - } - - path = strjoin(config_path, "/", dst, suffix, n, NULL); - if (!path) - return -ENOMEM; - - q = create_symlink(i->path, path, force, changes, n_changes); - if (r == 0) - r = q; - } - - return r; -} - -static int install_info_symlink_link( - UnitFileInstallInfo *i, - const LookupPaths *paths, - const char *config_path, - const char *root_dir, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_free_ char *path = NULL; - int r; - - assert(i); - assert(paths); - assert(config_path); - assert(i->path); - - r = in_search_path(i->path, paths->unit_path); - if (r != 0) - return r; - - path = strjoin(config_path, "/", i->name, NULL); - if (!path) - return -ENOMEM; - - return create_symlink(i->path, path, force, changes, n_changes); -} - -static int install_info_apply( - UnitFileInstallInfo *i, - const LookupPaths *paths, - const char *config_path, - const char *root_dir, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - int r, q; - - assert(i); - assert(paths); - assert(config_path); - - if (i->type != UNIT_FILE_TYPE_REGULAR) - return 0; - - r = install_info_symlink_alias(i, config_path, force, changes, n_changes); - - q = install_info_symlink_wants(i, config_path, i->wanted_by, ".wants/", force, changes, n_changes); - if (r == 0) - r = q; - - q = install_info_symlink_wants(i, config_path, i->required_by, ".requires/", force, changes, n_changes); - if (r == 0) - r = q; - - q = install_info_symlink_link(i, paths, config_path, root_dir, force, changes, n_changes); - if (r == 0) - r = q; - - return r; -} - -static int install_context_apply( - UnitFileScope scope, - InstallContext *c, - const LookupPaths *paths, - const char *config_path, - const char *root_dir, - bool force, - SearchFlags flags, - UnitFileChange **changes, - unsigned *n_changes) { - - UnitFileInstallInfo *i; - int r; - - assert(c); - assert(paths); - assert(config_path); - - if (ordered_hashmap_isempty(c->will_process)) - return 0; - - r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops); - if (r < 0) - return r; - - r = 0; - while ((i = ordered_hashmap_first(c->will_process))) { - int q; - - q = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name); - if (q < 0) - return q; - - r = install_info_traverse(scope, c, root_dir, paths, i, flags, NULL); - if (r < 0) - return r; - - if (i->type != UNIT_FILE_TYPE_REGULAR) - continue; - - q = install_info_apply(i, paths, config_path, root_dir, force, changes, n_changes); - if (r >= 0) { - if (q < 0) - r = q; - else - r+= q; - } - } - - return r; -} - -static int install_context_mark_for_removal( - UnitFileScope scope, - InstallContext *c, - const LookupPaths *paths, - Set **remove_symlinks_to, - const char *config_path, - const char *root_dir) { - - UnitFileInstallInfo *i; - int r; - - assert(c); - assert(paths); - assert(config_path); - - /* Marks all items for removal */ - - if (ordered_hashmap_isempty(c->will_process)) - return 0; - - r = ordered_hashmap_ensure_allocated(&c->have_processed, &string_hash_ops); - if (r < 0) - return r; - - while ((i = ordered_hashmap_first(c->will_process))) { - - r = ordered_hashmap_move_one(c->have_processed, c->will_process, i->name); - if (r < 0) - return r; - - r = install_info_traverse(scope, c, root_dir, paths, i, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, NULL); - if (r < 0) - return r; - - if (i->type != UNIT_FILE_TYPE_REGULAR) - continue; - - r = mark_symlink_for_removal(remove_symlinks_to, i->name); - if (r < 0) - return r; - } - - return 0; -} - -int unit_file_mask( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_free_ char *prefix = NULL; - char **i; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = get_config_path(scope, runtime, root_dir, &prefix); - if (r < 0) - return r; - - STRV_FOREACH(i, files) { - _cleanup_free_ char *path = NULL; - int q; - - if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) { - if (r == 0) - r = -EINVAL; - continue; - } - - path = path_make_absolute(*i, prefix); - if (!path) - return -ENOMEM; - - q = create_symlink("/dev/null", path, force, changes, n_changes); - if (q < 0 && r >= 0) - r = q; - } - - return r; -} - -int unit_file_unmask( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - _cleanup_free_ char *config_path = NULL; - _cleanup_free_ char **todo = NULL; - size_t n_todo = 0, n_allocated = 0; - char **i; - int r, q; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = get_config_path(scope, runtime, root_dir, &config_path); - if (r < 0) - return r; - - STRV_FOREACH(i, files) { - _cleanup_free_ char *path = NULL; - - if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) - return -EINVAL; - - path = path_make_absolute(*i, config_path); - if (!path) - return -ENOMEM; - - r = null_or_empty_path(path); - if (r == -ENOENT) - continue; - if (r < 0) - return r; - if (r == 0) - continue; - - if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2)) - return -ENOMEM; - - todo[n_todo++] = *i; - } - - strv_uniq(todo); - - r = 0; - STRV_FOREACH(i, todo) { - _cleanup_free_ char *path = NULL; - - path = path_make_absolute(*i, config_path); - if (!path) - return -ENOMEM; - - if (unlink(path) < 0) { - if (errno != -ENOENT && r >= 0) - r = -errno; - } else { - q = mark_symlink_for_removal(&remove_symlinks_to, path); - if (q < 0) - return q; - - unit_file_changes_add(changes, n_changes, UNIT_FILE_UNLINK, path, NULL); - } - } - - q = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); - if (r >= 0) - r = q; - - return r; -} - -int unit_file_link( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_free_ char *config_path = NULL; - _cleanup_free_ char **todo = NULL; - size_t n_todo = 0, n_allocated = 0; - char **i; - int r, q; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; - - r = get_config_path(scope, runtime, root_dir, &config_path); - if (r < 0) - return r; - - STRV_FOREACH(i, files) { - _cleanup_free_ char *full = NULL; - struct stat st; - char *fn; - - if (!path_is_absolute(*i)) - return -EINVAL; - - fn = basename(*i); - if (!unit_name_is_valid(fn, UNIT_NAME_ANY)) - return -EINVAL; - - full = prefix_root(root_dir, *i); - if (!full) - return -ENOMEM; - - if (lstat(full, &st) < 0) - return -errno; - if (S_ISLNK(st.st_mode)) - return -ELOOP; - if (S_ISDIR(st.st_mode)) - return -EISDIR; - if (!S_ISREG(st.st_mode)) - return -ENOTTY; - - q = in_search_path(*i, paths.unit_path); - if (q < 0) - return q; - if (q > 0) - continue; - - if (!GREEDY_REALLOC0(todo, n_allocated, n_todo + 2)) - return -ENOMEM; - - todo[n_todo++] = *i; - } - - strv_uniq(todo); - - r = 0; - STRV_FOREACH(i, todo) { - _cleanup_free_ char *path = NULL; - - path = path_make_absolute(basename(*i), config_path); - if (!path) - return -ENOMEM; - - q = create_symlink(*i, path, force, changes, n_changes); - if (q < 0 && r >= 0) - r = q; - } - - return r; -} - -int unit_file_add_dependency( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - const char *target, - UnitDependency dep, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_(install_context_done) InstallContext c = {}; - _cleanup_free_ char *config_path = NULL; - UnitFileInstallInfo *i, *target_info; - char **f; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(target); - - if (!IN_SET(dep, UNIT_WANTS, UNIT_REQUIRES)) - return -EINVAL; - - if (!unit_name_is_valid(target, UNIT_NAME_ANY)) - return -EINVAL; - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; - - r = get_config_path(scope, runtime, root_dir, &config_path); - if (r < 0) - return r; - - r = install_info_discover(scope, &c, root_dir, &paths, target, SEARCH_FOLLOW_CONFIG_SYMLINKS, &target_info); - if (r < 0) - return r; - if (target_info->type == UNIT_FILE_TYPE_MASKED) - return -ESHUTDOWN; - - assert(target_info->type == UNIT_FILE_TYPE_REGULAR); - - STRV_FOREACH(f, files) { - char ***l; - - r = install_info_discover(scope, &c, root_dir, &paths, *f, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); - if (r < 0) - return r; - if (i->type == UNIT_FILE_TYPE_MASKED) - return -ESHUTDOWN; - - assert(i->type == UNIT_FILE_TYPE_REGULAR); - - /* We didn't actually load anything from the unit - * file, but instead just add in our new symlink to - * create. */ - - if (dep == UNIT_WANTS) - l = &i->wanted_by; - else - l = &i->required_by; - - strv_free(*l); - *l = strv_new(target_info->name, NULL); - if (!*l) - return -ENOMEM; - } - - return install_context_apply(scope, &c, &paths, config_path, root_dir, force, SEARCH_FOLLOW_CONFIG_SYMLINKS, changes, n_changes); -} - -int unit_file_enable( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_(install_context_done) InstallContext c = {}; - _cleanup_free_ char *config_path = NULL; - UnitFileInstallInfo *i; - char **f; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; - - r = get_config_path(scope, runtime, root_dir, &config_path); - if (r < 0) - return r; - - STRV_FOREACH(f, files) { - r = install_info_discover(scope, &c, root_dir, &paths, *f, SEARCH_LOAD, &i); - if (r < 0) - return r; - if (i->type == UNIT_FILE_TYPE_MASKED) - return -ESHUTDOWN; - - assert(i->type == UNIT_FILE_TYPE_REGULAR); - } - - /* This will return the number of symlink rules that were - supposed to be created, not the ones actually created. This - is useful to determine whether the passed files had any - installation data at all. */ - - return install_context_apply(scope, &c, &paths, config_path, root_dir, force, SEARCH_LOAD, changes, n_changes); -} - -int unit_file_disable( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_(install_context_done) InstallContext c = {}; - _cleanup_free_ char *config_path = NULL; - _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - char **i; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; - - r = get_config_path(scope, runtime, root_dir, &config_path); - if (r < 0) - return r; - - STRV_FOREACH(i, files) { - if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) - return -EINVAL; - - r = install_info_add(&c, *i, NULL, NULL); - if (r < 0) - return r; - } - - r = install_context_mark_for_removal(scope, &c, &paths, &remove_symlinks_to, config_path, root_dir); - if (r < 0) - return r; - - return remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); -} - -int unit_file_reenable( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - char **n; - int r; - size_t l, i; - - /* First, we invoke the disable command with only the basename... */ - l = strv_length(files); - n = newa(char*, l+1); - for (i = 0; i < l; i++) - n[i] = basename(files[i]); - n[i] = NULL; - - r = unit_file_disable(scope, runtime, root_dir, n, changes, n_changes); - if (r < 0) - return r; - - /* But the enable command with the full name */ - return unit_file_enable(scope, runtime, root_dir, files, force, changes, n_changes); -} - -int unit_file_set_default( - UnitFileScope scope, - const char *root_dir, - const char *name, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_(install_context_done) InstallContext c = {}; - _cleanup_free_ char *config_path = NULL; - UnitFileInstallInfo *i; - const char *path; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(name); - - if (unit_name_to_type(name) != UNIT_TARGET) - return -EINVAL; - if (streq(name, SPECIAL_DEFAULT_TARGET)) - return -EINVAL; - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; - - r = get_config_path(scope, false, root_dir, &config_path); - if (r < 0) - return r; - - r = install_info_discover(scope, &c, root_dir, &paths, name, 0, &i); - if (r < 0) - return r; - if (i->type == UNIT_FILE_TYPE_MASKED) - return -ESHUTDOWN; - - path = strjoina(config_path, "/" SPECIAL_DEFAULT_TARGET); - - return create_symlink(i->path, path, force, changes, n_changes); -} - -int unit_file_get_default( - UnitFileScope scope, - const char *root_dir, - char **name) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_(install_context_done) InstallContext c = {}; - UnitFileInstallInfo *i; - char *n; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(name); - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; - - r = install_info_discover(scope, &c, root_dir, &paths, SPECIAL_DEFAULT_TARGET, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); - if (r < 0) - return r; - if (i->type == UNIT_FILE_TYPE_MASKED) - return -ESHUTDOWN; - - n = strdup(i->name); - if (!n) - return -ENOMEM; - - *name = n; - return 0; -} - -int unit_file_lookup_state( - UnitFileScope scope, - const char *root_dir, - const LookupPaths *paths, - const char *name, - UnitFileState *ret) { - - _cleanup_(install_context_done) InstallContext c = {}; - UnitFileInstallInfo *i; - UnitFileState state; - int r; - - assert(paths); - assert(name); - - if (!unit_name_is_valid(name, UNIT_NAME_ANY)) - return -EINVAL; - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = install_info_discover(scope, &c, root_dir, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); - if (r < 0) - return r; - - /* Shortcut things, if the caller just wants to know if this unit exists. */ - if (!ret) - return 0; - - switch (i->type) { - - case UNIT_FILE_TYPE_MASKED: - state = path_startswith(i->path, "/run") ? UNIT_FILE_MASKED_RUNTIME : UNIT_FILE_MASKED; - break; - - case UNIT_FILE_TYPE_REGULAR: - r = find_symlinks_in_scope(scope, root_dir, i->name, &state); - if (r < 0) - return r; - if (r == 0) { - if (UNIT_FILE_INSTALL_INFO_HAS_RULES(i)) - state = UNIT_FILE_DISABLED; - else if (UNIT_FILE_INSTALL_INFO_HAS_ALSO(i)) - state = UNIT_FILE_INDIRECT; - else - state = UNIT_FILE_STATIC; - } - - break; - - default: - assert_not_reached("Unexpect unit file type."); - } - - *ret = state; - return 0; -} - -int unit_file_get_state( - UnitFileScope scope, - const char *root_dir, - const char *name, - UnitFileState *ret) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(name); - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; - - return unit_file_lookup_state(scope, root_dir, &paths, name, ret); -} - -int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name) { - _cleanup_strv_free_ char **files = NULL; - char **p; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(name); - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - if (!unit_name_is_valid(name, UNIT_NAME_ANY)) - return -EINVAL; - - if (scope == UNIT_FILE_SYSTEM) - r = conf_files_list(&files, ".preset", root_dir, - "/etc/systemd/system-preset", - "/usr/local/lib/systemd/system-preset", - "/usr/lib/systemd/system-preset", -#ifdef HAVE_SPLIT_USR - "/lib/systemd/system-preset", -#endif - NULL); - else if (scope == UNIT_FILE_GLOBAL) - r = conf_files_list(&files, ".preset", root_dir, - "/etc/systemd/user-preset", - "/usr/local/lib/systemd/user-preset", - "/usr/lib/systemd/user-preset", - NULL); - else - return 1; /* Default is "enable" */ - - if (r < 0) - return r; - - STRV_FOREACH(p, files) { - _cleanup_fclose_ FILE *f; - char line[LINE_MAX]; - - f = fopen(*p, "re"); - if (!f) { - if (errno == ENOENT) - continue; - - return -errno; - } - - FOREACH_LINE(line, f, return -errno) { - const char *parameter; - char *l; - - l = strstrip(line); - - if (isempty(l)) - continue; - if (strchr(COMMENTS, *l)) - continue; - - parameter = first_word(l, "enable"); - if (parameter) { - if (fnmatch(parameter, name, FNM_NOESCAPE) == 0) { - log_debug("Preset file says enable %s.", name); - return 1; - } - - continue; - } - - parameter = first_word(l, "disable"); - if (parameter) { - if (fnmatch(parameter, name, FNM_NOESCAPE) == 0) { - log_debug("Preset file says disable %s.", name); - return 0; - } - - continue; - } - - log_debug("Couldn't parse line '%s'", l); - } - } - - /* Default is "enable" */ - log_debug("Preset file doesn't say anything about %s, enabling.", name); - return 1; -} - -static int execute_preset( - UnitFileScope scope, - InstallContext *plus, - InstallContext *minus, - const LookupPaths *paths, - const char *config_path, - const char *root_dir, - char **files, - UnitFilePresetMode mode, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - int r; - - assert(plus); - assert(minus); - assert(paths); - assert(config_path); - - if (mode != UNIT_FILE_PRESET_ENABLE_ONLY) { - _cleanup_set_free_free_ Set *remove_symlinks_to = NULL; - - r = install_context_mark_for_removal(scope, minus, paths, &remove_symlinks_to, config_path, root_dir); - if (r < 0) - return r; - - r = remove_marked_symlinks(remove_symlinks_to, config_path, changes, n_changes); - } else - r = 0; - - if (mode != UNIT_FILE_PRESET_DISABLE_ONLY) { - int q; - - /* Returns number of symlinks that where supposed to be installed. */ - q = install_context_apply(scope, plus, paths, config_path, root_dir, force, SEARCH_LOAD, changes, n_changes); - if (r >= 0) { - if (q < 0) - r = q; - else - r+= q; - } - } - - return r; -} - -static int preset_prepare_one( - UnitFileScope scope, - InstallContext *plus, - InstallContext *minus, - LookupPaths *paths, - const char *root_dir, - UnitFilePresetMode mode, - const char *name) { - - UnitFileInstallInfo *i; - int r; - - if (install_info_find(plus, name) || - install_info_find(minus, name)) - return 0; - - r = unit_file_query_preset(scope, root_dir, name); - if (r < 0) - return r; - - if (r > 0) { - r = install_info_discover(scope, plus, root_dir, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); - if (r < 0) - return r; - - if (i->type == UNIT_FILE_TYPE_MASKED) - return -ESHUTDOWN; - } else - r = install_info_discover(scope, minus, root_dir, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS, &i); - - return r; -} - -int unit_file_preset( - UnitFileScope scope, - bool runtime, - const char *root_dir, - char **files, - UnitFilePresetMode mode, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_free_ char *config_path = NULL; - char **i; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(mode < _UNIT_FILE_PRESET_MAX); - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; - - r = get_config_path(scope, runtime, root_dir, &config_path); - if (r < 0) - return r; - - STRV_FOREACH(i, files) { - if (!unit_name_is_valid(*i, UNIT_NAME_ANY)) - return -EINVAL; - - r = preset_prepare_one(scope, &plus, &minus, &paths, root_dir, mode, *i); - if (r < 0) - return r; - } - - return execute_preset(scope, &plus, &minus, &paths, config_path, root_dir, files, mode, force, changes, n_changes); -} - -int unit_file_preset_all( - UnitFileScope scope, - bool runtime, - const char *root_dir, - UnitFilePresetMode mode, - bool force, - UnitFileChange **changes, - unsigned *n_changes) { - - _cleanup_(install_context_done) InstallContext plus = {}, minus = {}; - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - _cleanup_free_ char *config_path = NULL; - char **i; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(mode < _UNIT_FILE_PRESET_MAX); - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; - - r = get_config_path(scope, runtime, root_dir, &config_path); - if (r < 0) - return r; - - STRV_FOREACH(i, paths.unit_path) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_free_ char *units_dir; - struct dirent *de; - - units_dir = path_join(root_dir, *i, NULL); - if (!units_dir) - return -ENOMEM; - - d = opendir(units_dir); - if (!d) { - if (errno == ENOENT) - continue; - - return -errno; - } - - FOREACH_DIRENT(de, d, return -errno) { - - if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) - continue; - - dirent_ensure_type(d, de); - - if (!IN_SET(de->d_type, DT_LNK, DT_REG)) - continue; - - r = preset_prepare_one(scope, &plus, &minus, &paths, root_dir, mode, de->d_name); - if (r < 0) - return r; - } - } - - return execute_preset(scope, &plus, &minus, &paths, config_path, root_dir, NULL, mode, force, changes, n_changes); -} - -static void unit_file_list_free_one(UnitFileList *f) { - if (!f) - return; - - free(f->path); - free(f); -} - -Hashmap* unit_file_list_free(Hashmap *h) { - UnitFileList *i; - - while ((i = hashmap_steal_first(h))) - unit_file_list_free_one(i); - - return hashmap_free(h); -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(UnitFileList*, unit_file_list_free_one); - -int unit_file_get_list( - UnitFileScope scope, - const char *root_dir, - Hashmap *h) { - - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - char **i; - int r; - - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - assert(h); - - r = verify_root_dir(scope, &root_dir); - if (r < 0) - return r; - - r = lookup_paths_init_from_scope(&paths, scope, root_dir); - if (r < 0) - return r; - - STRV_FOREACH(i, paths.unit_path) { - _cleanup_closedir_ DIR *d = NULL; - _cleanup_free_ char *units_dir; - struct dirent *de; - - units_dir = path_join(root_dir, *i, NULL); - if (!units_dir) - return -ENOMEM; - - d = opendir(units_dir); - if (!d) { - if (errno == ENOENT) - continue; - - return -errno; - } - - FOREACH_DIRENT(de, d, return -errno) { - _cleanup_(unit_file_list_free_onep) UnitFileList *f = NULL; - - if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) - continue; - - if (hashmap_get(h, de->d_name)) - continue; - - dirent_ensure_type(d, de); - - if (!IN_SET(de->d_type, DT_LNK, DT_REG)) - continue; - - f = new0(UnitFileList, 1); - if (!f) - return -ENOMEM; - - f->path = path_make_absolute(de->d_name, units_dir); - if (!f->path) - return -ENOMEM; - - r = unit_file_lookup_state(scope, root_dir, &paths, basename(f->path), &f->state); - if (r < 0) - f->state = UNIT_FILE_BAD; - - r = hashmap_put(h, basename(f->path), f); - if (r < 0) - return r; - - f = NULL; /* prevent cleanup */ - } - } - - return 0; -} - -static const char* const unit_file_state_table[_UNIT_FILE_STATE_MAX] = { - [UNIT_FILE_ENABLED] = "enabled", - [UNIT_FILE_ENABLED_RUNTIME] = "enabled-runtime", - [UNIT_FILE_LINKED] = "linked", - [UNIT_FILE_LINKED_RUNTIME] = "linked-runtime", - [UNIT_FILE_MASKED] = "masked", - [UNIT_FILE_MASKED_RUNTIME] = "masked-runtime", - [UNIT_FILE_STATIC] = "static", - [UNIT_FILE_DISABLED] = "disabled", - [UNIT_FILE_INDIRECT] = "indirect", - [UNIT_FILE_BAD] = "bad", -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_file_state, UnitFileState); - -static const char* const unit_file_change_type_table[_UNIT_FILE_CHANGE_TYPE_MAX] = { - [UNIT_FILE_SYMLINK] = "symlink", - [UNIT_FILE_UNLINK] = "unlink", -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_file_change_type, UnitFileChangeType); - -static const char* const unit_file_preset_mode_table[_UNIT_FILE_PRESET_MAX] = { - [UNIT_FILE_PRESET_FULL] = "full", - [UNIT_FILE_PRESET_ENABLE_ONLY] = "enable-only", - [UNIT_FILE_PRESET_DISABLE_ONLY] = "disable-only", -}; - -DEFINE_STRING_TABLE_LOOKUP(unit_file_preset_mode, UnitFilePresetMode); diff --git a/src/shared/install.h b/src/shared/install.h deleted file mode 100644 index c1a43e23e7..0000000000 --- a/src/shared/install.h +++ /dev/null @@ -1,156 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -typedef enum UnitFileScope UnitFileScope; -typedef enum UnitFileState UnitFileState; -typedef enum UnitFilePresetMode UnitFilePresetMode; -typedef enum UnitFileChangeType UnitFileChangeType; -typedef enum UnitFileType UnitFileType; -typedef struct UnitFileChange UnitFileChange; -typedef struct UnitFileList UnitFileList; -typedef struct UnitFileInstallInfo UnitFileInstallInfo; - -#include - -#include "hashmap.h" -#include "macro.h" -#include "path-lookup.h" -#include "strv.h" -#include "unit-name.h" - -enum UnitFileScope { - UNIT_FILE_SYSTEM, - UNIT_FILE_GLOBAL, - UNIT_FILE_USER, - _UNIT_FILE_SCOPE_MAX, - _UNIT_FILE_SCOPE_INVALID = -1 -}; - -enum UnitFileState { - UNIT_FILE_ENABLED, - UNIT_FILE_ENABLED_RUNTIME, - UNIT_FILE_LINKED, - UNIT_FILE_LINKED_RUNTIME, - UNIT_FILE_MASKED, - UNIT_FILE_MASKED_RUNTIME, - UNIT_FILE_STATIC, - UNIT_FILE_DISABLED, - UNIT_FILE_INDIRECT, - UNIT_FILE_BAD, - _UNIT_FILE_STATE_MAX, - _UNIT_FILE_STATE_INVALID = -1 -}; - -enum UnitFilePresetMode { - UNIT_FILE_PRESET_FULL, - UNIT_FILE_PRESET_ENABLE_ONLY, - UNIT_FILE_PRESET_DISABLE_ONLY, - _UNIT_FILE_PRESET_MAX, - _UNIT_FILE_PRESET_INVALID = -1 -}; - -enum UnitFileChangeType { - UNIT_FILE_SYMLINK, - UNIT_FILE_UNLINK, - _UNIT_FILE_CHANGE_TYPE_MAX, - _UNIT_FILE_CHANGE_TYPE_INVALID = -1 -}; - -struct UnitFileChange { - UnitFileChangeType type; - char *path; - char *source; -}; - -struct UnitFileList { - char *path; - UnitFileState state; -}; - -enum UnitFileType { - UNIT_FILE_TYPE_REGULAR, - UNIT_FILE_TYPE_SYMLINK, - UNIT_FILE_TYPE_MASKED, - _UNIT_FILE_TYPE_MAX, - _UNIT_FILE_TYPE_INVALID = -1, -}; - -struct UnitFileInstallInfo { - char *name; - char *path; - - char **aliases; - char **wanted_by; - char **required_by; - char **also; - - char *default_instance; - - UnitFileType type; - - char *symlink_target; -}; - -static inline bool UNIT_FILE_INSTALL_INFO_HAS_RULES(UnitFileInstallInfo *i) { - assert(i); - - return !strv_isempty(i->aliases) || - !strv_isempty(i->wanted_by) || - !strv_isempty(i->required_by); -} - -static inline bool UNIT_FILE_INSTALL_INFO_HAS_ALSO(UnitFileInstallInfo *i) { - assert(i); - - return !strv_isempty(i->also); -} - -int unit_file_enable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes); -int unit_file_disable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes); -int unit_file_reenable(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes); -int unit_file_link(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes); -int unit_file_preset(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFilePresetMode mode, bool force, UnitFileChange **changes, unsigned *n_changes); -int unit_file_preset_all(UnitFileScope scope, bool runtime, const char *root_dir, UnitFilePresetMode mode, bool force, UnitFileChange **changes, unsigned *n_changes); -int unit_file_mask(UnitFileScope scope, bool runtime, const char *root_dir, char **files, bool force, UnitFileChange **changes, unsigned *n_changes); -int unit_file_unmask(UnitFileScope scope, bool runtime, const char *root_dir, char **files, UnitFileChange **changes, unsigned *n_changes); -int unit_file_set_default(UnitFileScope scope, const char *root_dir, const char *file, bool force, UnitFileChange **changes, unsigned *n_changes); -int unit_file_get_default(UnitFileScope scope, const char *root_dir, char **name); -int unit_file_add_dependency(UnitFileScope scope, bool runtime, const char *root_dir, char **files, const char *target, UnitDependency dep, bool force, UnitFileChange **changes, unsigned *n_changes); - -int unit_file_lookup_state(UnitFileScope scope, const char *root_dir,const LookupPaths *paths, const char *name, UnitFileState *ret); -int unit_file_get_state(UnitFileScope scope, const char *root_dir, const char *filename, UnitFileState *ret); - -int unit_file_get_list(UnitFileScope scope, const char *root_dir, Hashmap *h); -Hashmap* unit_file_list_free(Hashmap *h); - -int unit_file_changes_add(UnitFileChange **changes, unsigned *n_changes, UnitFileChangeType type, const char *path, const char *source); -void unit_file_changes_free(UnitFileChange *changes, unsigned n_changes); - -int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char *name); - -const char *unit_file_state_to_string(UnitFileState s) _const_; -UnitFileState unit_file_state_from_string(const char *s) _pure_; - -const char *unit_file_change_type_to_string(UnitFileChangeType s) _const_; -UnitFileChangeType unit_file_change_type_from_string(const char *s) _pure_; - -const char *unit_file_preset_mode_to_string(UnitFilePresetMode m) _const_; -UnitFilePresetMode unit_file_preset_mode_from_string(const char *s) _pure_; diff --git a/src/shared/linux/auto_dev-ioctl.h b/src/shared/linux/auto_dev-ioctl.h deleted file mode 100644 index aeaeb3ea7a..0000000000 --- a/src/shared/linux/auto_dev-ioctl.h +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright 2008 Red Hat, Inc. All rights reserved. - * Copyright 2008 Ian Kent - * - * This file is part of the Linux kernel and is made available under - * the terms of the GNU General Public License, version 2, or at your - * option, any later version, incorporated herein by reference. - */ - -#ifndef _LINUX_AUTO_DEV_IOCTL_H -#define _LINUX_AUTO_DEV_IOCTL_H - -#include - -#ifdef __KERNEL__ -#include -#else -#include -#endif /* __KERNEL__ */ - -#define AUTOFS_DEVICE_NAME "autofs" - -#define AUTOFS_DEV_IOCTL_VERSION_MAJOR 1 -#define AUTOFS_DEV_IOCTL_VERSION_MINOR 0 - -#define AUTOFS_DEVID_LEN 16 - -#define AUTOFS_DEV_IOCTL_SIZE sizeof(struct autofs_dev_ioctl) - -/* - * An ioctl interface for autofs mount point control. - */ - -struct args_protover { - __u32 version; -}; - -struct args_protosubver { - __u32 sub_version; -}; - -struct args_openmount { - __u32 devid; -}; - -struct args_ready { - __u32 token; -}; - -struct args_fail { - __u32 token; - __s32 status; -}; - -struct args_setpipefd { - __s32 pipefd; -}; - -struct args_timeout { - __u64 timeout; -}; - -struct args_requester { - __u32 uid; - __u32 gid; -}; - -struct args_expire { - __u32 how; -}; - -struct args_askumount { - __u32 may_umount; -}; - -struct args_ismountpoint { - union { - struct args_in { - __u32 type; - } in; - struct args_out { - __u32 devid; - __u32 magic; - } out; - }; -}; - -/* - * All the ioctls use this structure. - * When sending a path size must account for the total length - * of the chunk of memory otherwise is is the size of the - * structure. - */ - -struct autofs_dev_ioctl { - __u32 ver_major; - __u32 ver_minor; - __u32 size; /* total size of data passed in - * including this struct */ - __s32 ioctlfd; /* automount command fd */ - - /* Command parameters */ - - union { - struct args_protover protover; - struct args_protosubver protosubver; - struct args_openmount openmount; - struct args_ready ready; - struct args_fail fail; - struct args_setpipefd setpipefd; - struct args_timeout timeout; - struct args_requester requester; - struct args_expire expire; - struct args_askumount askumount; - struct args_ismountpoint ismountpoint; - }; - - char path[0]; -}; - -static inline void init_autofs_dev_ioctl(struct autofs_dev_ioctl *in) { - memset(in, 0, sizeof(struct autofs_dev_ioctl)); - in->ver_major = AUTOFS_DEV_IOCTL_VERSION_MAJOR; - in->ver_minor = AUTOFS_DEV_IOCTL_VERSION_MINOR; - in->size = sizeof(struct autofs_dev_ioctl); - in->ioctlfd = -1; - return; -} - -/* - * If you change this make sure you make the corresponding change - * to autofs-dev-ioctl.c:lookup_ioctl() - */ -enum { - /* Get various version info */ - AUTOFS_DEV_IOCTL_VERSION_CMD = 0x71, - AUTOFS_DEV_IOCTL_PROTOVER_CMD, - AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, - - /* Open mount ioctl fd */ - AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, - - /* Close mount ioctl fd */ - AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, - - /* Mount/expire status returns */ - AUTOFS_DEV_IOCTL_READY_CMD, - AUTOFS_DEV_IOCTL_FAIL_CMD, - - /* Activate/deactivate autofs mount */ - AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, - AUTOFS_DEV_IOCTL_CATATONIC_CMD, - - /* Expiry timeout */ - AUTOFS_DEV_IOCTL_TIMEOUT_CMD, - - /* Get mount last requesting uid and gid */ - AUTOFS_DEV_IOCTL_REQUESTER_CMD, - - /* Check for eligible expire candidates */ - AUTOFS_DEV_IOCTL_EXPIRE_CMD, - - /* Request busy status */ - AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, - - /* Check if path is a mountpoint */ - AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, -}; - -#define AUTOFS_IOCTL 0x93 - -#define AUTOFS_DEV_IOCTL_VERSION \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_VERSION_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_PROTOVER \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_PROTOVER_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_PROTOSUBVER \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_PROTOSUBVER_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_OPENMOUNT \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_OPENMOUNT_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_CLOSEMOUNT \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_CLOSEMOUNT_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_READY \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_READY_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_FAIL \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_FAIL_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_SETPIPEFD \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_SETPIPEFD_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_CATATONIC \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_CATATONIC_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_TIMEOUT \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_TIMEOUT_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_REQUESTER \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_REQUESTER_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_EXPIRE \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_EXPIRE_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_ASKUMOUNT \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_ASKUMOUNT_CMD, struct autofs_dev_ioctl) - -#define AUTOFS_DEV_IOCTL_ISMOUNTPOINT \ - _IOWR(AUTOFS_IOCTL, \ - AUTOFS_DEV_IOCTL_ISMOUNTPOINT_CMD, struct autofs_dev_ioctl) - -#endif /* _LINUX_AUTO_DEV_IOCTL_H */ diff --git a/src/shared/logs-show.c b/src/shared/logs-show.c deleted file mode 100644 index 5eb3bd35c7..0000000000 --- a/src/shared/logs-show.c +++ /dev/null @@ -1,1305 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-id128.h" -#include "sd-journal.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "formats-util.h" -#include "hashmap.h" -#include "hostname-util.h" -#include "io-util.h" -#include "journal-internal.h" -#include "log.h" -#include "logs-show.h" -#include "macro.h" -#include "output-mode.h" -#include "parse-util.h" -#include "process-util.h" -#include "sparse-endian.h" -#include "string-table.h" -#include "string-util.h" -#include "terminal-util.h" -#include "time-util.h" -#include "utf8.h" -#include "util.h" - -/* up to three lines (each up to 100 characters) or 300 characters, whichever is less */ -#define PRINT_LINE_THRESHOLD 3 -#define PRINT_CHAR_THRESHOLD 300 - -#define JSON_THRESHOLD 4096 - -static int print_catalog(FILE *f, sd_journal *j) { - int r; - _cleanup_free_ char *t = NULL, *z = NULL; - - - r = sd_journal_get_catalog(j, &t); - if (r < 0) - return r; - - z = strreplace(strstrip(t), "\n", "\n-- "); - if (!z) - return log_oom(); - - fputs("-- ", f); - fputs(z, f); - fputc('\n', f); - - return 0; -} - -static int parse_field(const void *data, size_t length, const char *field, char **target, size_t *target_size) { - size_t fl, nl; - char *buf; - - assert(data); - assert(field); - assert(target); - - fl = strlen(field); - if (length < fl) - return 0; - - if (memcmp(data, field, fl)) - return 0; - - nl = length - fl; - buf = new(char, nl+1); - if (!buf) - return log_oom(); - - memcpy(buf, (const char*) data + fl, nl); - buf[nl] = 0; - - free(*target); - *target = buf; - - if (target_size) - *target_size = nl; - - return 1; -} - -static bool shall_print(const char *p, size_t l, OutputFlags flags) { - assert(p); - - if (flags & OUTPUT_SHOW_ALL) - return true; - - if (l >= PRINT_CHAR_THRESHOLD) - return false; - - if (!utf8_is_printable(p, l)) - return false; - - return true; -} - -static bool print_multiline(FILE *f, unsigned prefix, unsigned n_columns, OutputFlags flags, int priority, const char* message, size_t message_len) { - const char *color_on = "", *color_off = ""; - const char *pos, *end; - bool ellipsized = false; - int line = 0; - - if (flags & OUTPUT_COLOR) { - if (priority <= LOG_ERR) { - color_on = ANSI_HIGHLIGHT_RED; - color_off = ANSI_NORMAL; - } else if (priority <= LOG_NOTICE) { - color_on = ANSI_HIGHLIGHT; - color_off = ANSI_NORMAL; - } - } - - /* A special case: make sure that we print a newline when - the message is empty. */ - if (message_len == 0) - fputs("\n", f); - - for (pos = message; - pos < message + message_len; - pos = end + 1, line++) { - bool continuation = line > 0; - bool tail_line; - int len; - for (end = pos; end < message + message_len && *end != '\n'; end++) - ; - len = end - pos; - assert(len >= 0); - - /* We need to figure out when we are showing not-last line, *and* - * will skip subsequent lines. In that case, we will put the dots - * at the end of the line, instead of putting dots in the middle - * or not at all. - */ - tail_line = - line + 1 == PRINT_LINE_THRESHOLD || - end + 1 >= message + PRINT_CHAR_THRESHOLD; - - if (flags & (OUTPUT_FULL_WIDTH | OUTPUT_SHOW_ALL) || - (prefix + len + 1 < n_columns && !tail_line)) { - fprintf(f, "%*s%s%.*s%s\n", - continuation * prefix, "", - color_on, len, pos, color_off); - continue; - } - - /* Beyond this point, ellipsization will happen. */ - ellipsized = true; - - if (prefix < n_columns && n_columns - prefix >= 3) { - if (n_columns - prefix > (unsigned) len + 3) - fprintf(f, "%*s%s%.*s...%s\n", - continuation * prefix, "", - color_on, len, pos, color_off); - else { - _cleanup_free_ char *e; - - e = ellipsize_mem(pos, len, n_columns - prefix, - tail_line ? 100 : 90); - if (!e) - fprintf(f, "%*s%s%.*s%s\n", - continuation * prefix, "", - color_on, len, pos, color_off); - else - fprintf(f, "%*s%s%s%s\n", - continuation * prefix, "", - color_on, e, color_off); - } - } else - fputs("...\n", f); - - if (tail_line) - break; - } - - return ellipsized; -} - -static int output_short( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags) { - - int r; - const void *data; - size_t length; - size_t n = 0; - _cleanup_free_ char *hostname = NULL, *identifier = NULL, *comm = NULL, *pid = NULL, *fake_pid = NULL, *message = NULL, *realtime = NULL, *monotonic = NULL, *priority = NULL; - size_t hostname_len = 0, identifier_len = 0, comm_len = 0, pid_len = 0, fake_pid_len = 0, message_len = 0, realtime_len = 0, monotonic_len = 0, priority_len = 0; - int p = LOG_INFO; - bool ellipsized = false; - - assert(f); - assert(j); - - /* Set the threshold to one bigger than the actual print - * threshold, so that if the line is actually longer than what - * we're willing to print, ellipsization will occur. This way - * we won't output a misleading line without any indication of - * truncation. - */ - sd_journal_set_data_threshold(j, flags & (OUTPUT_SHOW_ALL|OUTPUT_FULL_WIDTH) ? 0 : PRINT_CHAR_THRESHOLD + 1); - - JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { - - r = parse_field(data, length, "PRIORITY=", &priority, &priority_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "_HOSTNAME=", &hostname, &hostname_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "SYSLOG_IDENTIFIER=", &identifier, &identifier_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "_COMM=", &comm, &comm_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "_PID=", &pid, &pid_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "SYSLOG_PID=", &fake_pid, &fake_pid_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &realtime, &realtime_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "_SOURCE_MONOTONIC_TIMESTAMP=", &monotonic, &monotonic_len); - if (r < 0) - return r; - else if (r > 0) - continue; - - r = parse_field(data, length, "MESSAGE=", &message, &message_len); - if (r < 0) - return r; - } - - if (r < 0) - return log_error_errno(r, "Failed to get journal fields: %m"); - - if (!message) { - log_debug("Skipping message without MESSAGE= field."); - return 0; - } - - if (!(flags & OUTPUT_SHOW_ALL)) - strip_tab_ansi(&message, &message_len); - - 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_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 (hostname && shall_print(hostname, hostname_len, flags)) { - fprintf(f, " %.*s", (int) hostname_len, hostname); - n += hostname_len + 1; - } - - if (identifier && shall_print(identifier, identifier_len, flags)) { - fprintf(f, " %.*s", (int) identifier_len, identifier); - n += identifier_len + 1; - } else if (comm && shall_print(comm, comm_len, flags)) { - fprintf(f, " %.*s", (int) comm_len, comm); - n += comm_len + 1; - } else - fputs(" unknown", f); - - if (pid && shall_print(pid, pid_len, flags)) { - fprintf(f, "[%.*s]", (int) pid_len, pid); - n += pid_len + 2; - } else if (fake_pid && shall_print(fake_pid, fake_pid_len, flags)) { - fprintf(f, "[%.*s]", (int) fake_pid_len, fake_pid); - n += fake_pid_len + 2; - } - - if (!(flags & OUTPUT_SHOW_ALL) && !utf8_is_printable(message, message_len)) { - char bytes[FORMAT_BYTES_MAX]; - fprintf(f, ": [%s blob data]\n", format_bytes(bytes, sizeof(bytes), message_len)); - } else { - fputs(": ", f); - ellipsized |= - print_multiline(f, n + 2, n_columns, flags, p, message, message_len); - } - - if (flags & OUTPUT_CATALOG) - print_catalog(f, j); - - return ellipsized; -} - -static int output_verbose( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags) { - - const void *data; - size_t length; - _cleanup_free_ char *cursor = NULL; - uint64_t realtime = 0; - char ts[FORMAT_TIMESTAMP_MAX + 7]; - int r; - - assert(f); - assert(j); - - sd_journal_set_data_threshold(j, 0); - - r = sd_journal_get_data(j, "_SOURCE_REALTIME_TIMESTAMP", &data, &length); - if (r == -ENOENT) - log_debug("Source realtime timestamp not found"); - else if (r < 0) - return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get source realtime timestamp: %m"); - else { - _cleanup_free_ char *value = NULL; - - r = parse_field(data, length, "_SOURCE_REALTIME_TIMESTAMP=", &value, NULL); - if (r < 0) - return r; - assert(r > 0); - - r = safe_atou64(value, &realtime); - if (r < 0) - log_debug_errno(r, "Failed to parse realtime timestamp: %m"); - } - - if (r < 0) { - r = sd_journal_get_realtime_usec(j, &realtime); - if (r < 0) - return log_full_errno(r == -EADDRNOTAVAIL ? LOG_DEBUG : LOG_ERR, r, "Failed to get realtime timestamp: %m"); - } - - r = sd_journal_get_cursor(j, &cursor); - if (r < 0) - return log_error_errno(r, "Failed to get cursor: %m"); - - fprintf(f, "%s [%s]\n", - flags & OUTPUT_UTC ? - format_timestamp_us_utc(ts, sizeof(ts), realtime) : - format_timestamp_us(ts, sizeof(ts), realtime), - cursor); - - JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { - const char *c; - int fieldlen; - const char *on = "", *off = ""; - - c = memchr(data, '=', length); - if (!c) { - log_error("Invalid field."); - return -EINVAL; - } - fieldlen = c - (const char*) data; - - if (flags & OUTPUT_COLOR && startswith(data, "MESSAGE=")) { - on = ANSI_HIGHLIGHT; - off = ANSI_NORMAL; - } - - 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); - print_multiline(f, 4 + fieldlen + 1, 0, OUTPUT_FULL_WIDTH, 0, c + 1, length - fieldlen - 1); - fputs(off, f); - } else { - char bytes[FORMAT_BYTES_MAX]; - - fprintf(f, " %s%.*s=[%s blob data]%s\n", - on, - (int) (c - (const char*) data), - (const char*) data, - format_bytes(bytes, sizeof(bytes), length - (c - (const char *) data) - 1), - off); - } - } - - if (r < 0) - return r; - - if (flags & OUTPUT_CATALOG) - print_catalog(f, j); - - return 0; -} - -static int output_export( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags) { - - sd_id128_t boot_id; - char sid[33]; - int r; - usec_t realtime, monotonic; - _cleanup_free_ char *cursor = NULL; - const void *data; - size_t length; - - assert(j); - - sd_journal_set_data_threshold(j, 0); - - r = sd_journal_get_realtime_usec(j, &realtime); - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); - - r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); - - r = sd_journal_get_cursor(j, &cursor); - if (r < 0) - return log_error_errno(r, "Failed to get cursor: %m"); - - fprintf(f, - "__CURSOR=%s\n" - "__REALTIME_TIMESTAMP="USEC_FMT"\n" - "__MONOTONIC_TIMESTAMP="USEC_FMT"\n" - "_BOOT_ID=%s\n", - cursor, - realtime, - monotonic, - sd_id128_to_string(boot_id, sid)); - - JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { - - /* We already printed the boot id, from the data in - * the header, hence let's suppress it here */ - if (length >= 9 && - startswith(data, "_BOOT_ID=")) - continue; - - if (utf8_is_printable_newline(data, length, false)) - fwrite(data, length, 1, f); - else { - const char *c; - uint64_t le64; - - c = memchr(data, '=', length); - if (!c) { - log_error("Invalid field."); - return -EINVAL; - } - - fwrite(data, c - (const char*) data, 1, f); - fputc('\n', f); - le64 = htole64(length - (c - (const char*) data) - 1); - fwrite(&le64, sizeof(le64), 1, f); - fwrite(c + 1, length - (c - (const char*) data) - 1, 1, f); - } - - fputc('\n', f); - } - - if (r < 0) - return r; - - fputc('\n', f); - - return 0; -} - -void json_escape( - FILE *f, - const char* p, - size_t l, - OutputFlags flags) { - - assert(f); - assert(p); - - if (!(flags & OUTPUT_SHOW_ALL) && l >= JSON_THRESHOLD) - fputs("null", f); - - else if (!utf8_is_printable(p, l)) { - bool not_first = false; - - fputs("[ ", f); - - while (l > 0) { - if (not_first) - fprintf(f, ", %u", (uint8_t) *p); - else { - not_first = true; - fprintf(f, "%u", (uint8_t) *p); - } - - p++; - l--; - } - - fputs(" ]", f); - } else { - fputc('\"', f); - - while (l > 0) { - if (*p == '"' || *p == '\\') { - fputc('\\', f); - fputc(*p, f); - } else if (*p == '\n') - fputs("\\n", f); - else if ((uint8_t) *p < ' ') - fprintf(f, "\\u%04x", (uint8_t) *p); - else - fputc(*p, f); - - p++; - l--; - } - - fputc('\"', f); - } -} - -static int output_json( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags) { - - uint64_t realtime, monotonic; - _cleanup_free_ char *cursor = NULL; - const void *data; - size_t length; - sd_id128_t boot_id; - char sid[33], *k; - int r; - Hashmap *h = NULL; - bool done, separator; - - assert(j); - - sd_journal_set_data_threshold(j, flags & OUTPUT_SHOW_ALL ? 0 : JSON_THRESHOLD); - - r = sd_journal_get_realtime_usec(j, &realtime); - if (r < 0) - return log_error_errno(r, "Failed to get realtime timestamp: %m"); - - r = sd_journal_get_monotonic_usec(j, &monotonic, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get monotonic timestamp: %m"); - - r = sd_journal_get_cursor(j, &cursor); - if (r < 0) - return log_error_errno(r, "Failed to get cursor: %m"); - - if (mode == OUTPUT_JSON_PRETTY) - fprintf(f, - "{\n" - "\t\"__CURSOR\" : \"%s\",\n" - "\t\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\",\n" - "\t\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\",\n" - "\t\"_BOOT_ID\" : \"%s\"", - cursor, - realtime, - monotonic, - sd_id128_to_string(boot_id, sid)); - else { - if (mode == OUTPUT_JSON_SSE) - fputs("data: ", f); - - fprintf(f, - "{ \"__CURSOR\" : \"%s\", " - "\"__REALTIME_TIMESTAMP\" : \""USEC_FMT"\", " - "\"__MONOTONIC_TIMESTAMP\" : \""USEC_FMT"\", " - "\"_BOOT_ID\" : \"%s\"", - cursor, - realtime, - monotonic, - sd_id128_to_string(boot_id, sid)); - } - - h = hashmap_new(&string_hash_ops); - if (!h) - return log_oom(); - - /* First round, iterate through the entry and count how often each field appears */ - JOURNAL_FOREACH_DATA_RETVAL(j, data, length, r) { - const char *eq; - char *n; - unsigned u; - - if (length >= 9 && - memcmp(data, "_BOOT_ID=", 9) == 0) - continue; - - eq = memchr(data, '=', length); - if (!eq) - continue; - - n = strndup(data, eq - (const char*) data); - if (!n) { - r = log_oom(); - goto finish; - } - - u = PTR_TO_UINT(hashmap_get(h, n)); - if (u == 0) { - r = hashmap_put(h, n, UINT_TO_PTR(1)); - if (r < 0) { - free(n); - log_oom(); - goto finish; - } - } else { - r = hashmap_update(h, n, UINT_TO_PTR(u + 1)); - free(n); - if (r < 0) { - log_oom(); - goto finish; - } - } - } - - if (r < 0) - return r; - - separator = true; - do { - done = true; - - SD_JOURNAL_FOREACH_DATA(j, data, length) { - const char *eq; - char *kk, *n; - size_t m; - unsigned u; - - /* We already printed the boot id, from the data in - * the header, hence let's suppress it here */ - if (length >= 9 && - memcmp(data, "_BOOT_ID=", 9) == 0) - continue; - - eq = memchr(data, '=', length); - if (!eq) - continue; - - if (separator) { - if (mode == OUTPUT_JSON_PRETTY) - fputs(",\n\t", f); - else - fputs(", ", f); - } - - m = eq - (const char*) data; - - n = strndup(data, m); - if (!n) { - r = log_oom(); - goto finish; - } - - u = PTR_TO_UINT(hashmap_get2(h, n, (void**) &kk)); - if (u == 0) { - /* We already printed this, let's jump to the next */ - free(n); - separator = false; - - continue; - } else if (u == 1) { - /* Field only appears once, output it directly */ - - json_escape(f, data, m, flags); - fputs(" : ", f); - - json_escape(f, eq + 1, length - m - 1, flags); - - hashmap_remove(h, n); - free(kk); - free(n); - - separator = true; - - continue; - - } else { - /* Field appears multiple times, output it as array */ - json_escape(f, data, m, flags); - fputs(" : [ ", f); - json_escape(f, eq + 1, length - m - 1, flags); - - /* Iterate through the end of the list */ - - while (sd_journal_enumerate_data(j, &data, &length) > 0) { - if (length < m + 1) - continue; - - if (memcmp(data, n, m) != 0) - continue; - - if (((const char*) data)[m] != '=') - continue; - - fputs(", ", f); - json_escape(f, (const char*) data + m + 1, length - m - 1, flags); - } - - fputs(" ]", f); - - hashmap_remove(h, n); - free(kk); - free(n); - - /* Iterate data fields form the beginning */ - done = false; - separator = true; - - break; - } - } - - } while (!done); - - if (mode == OUTPUT_JSON_PRETTY) - fputs("\n}\n", f); - else if (mode == OUTPUT_JSON_SSE) - fputs("}\n\n", f); - else - fputs(" }\n", f); - - r = 0; - -finish: - while ((k = hashmap_steal_first_key(h))) - free(k); - - hashmap_free(h); - - return r; -} - -static int output_cat( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags) { - - const void *data; - size_t l; - int r; - - assert(j); - assert(f); - - sd_journal_set_data_threshold(j, 0); - - r = sd_journal_get_data(j, "MESSAGE", &data, &l); - if (r < 0) { - /* An entry without MESSAGE=? */ - if (r == -ENOENT) - return 0; - - return log_error_errno(r, "Failed to get data: %m"); - } - - assert(l >= 8); - - fwrite((const char*) data + 8, 1, l - 8, f); - fputc('\n', f); - - return 0; -} - -static int (*output_funcs[_OUTPUT_MODE_MAX])( - FILE *f, - sd_journal*j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags) = { - - [OUTPUT_SHORT] = output_short, - [OUTPUT_SHORT_ISO] = output_short, - [OUTPUT_SHORT_PRECISE] = output_short, - [OUTPUT_SHORT_MONOTONIC] = output_short, - [OUTPUT_VERBOSE] = output_verbose, - [OUTPUT_EXPORT] = output_export, - [OUTPUT_JSON] = output_json, - [OUTPUT_JSON_PRETTY] = output_json, - [OUTPUT_JSON_SSE] = output_json, - [OUTPUT_CAT] = output_cat -}; - -int output_journal( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags, - bool *ellipsized) { - - int ret; - assert(mode >= 0); - assert(mode < _OUTPUT_MODE_MAX); - - if (n_columns <= 0) - n_columns = columns(); - - ret = output_funcs[mode](f, j, mode, n_columns, flags); - fflush(stdout); - - if (ellipsized && ret > 0) - *ellipsized = true; - - return ret; -} - -static int maybe_print_begin_newline(FILE *f, OutputFlags *flags) { - assert(f); - assert(flags); - - if (!(*flags & OUTPUT_BEGIN_NEWLINE)) - return 0; - - /* Print a beginning new line if that's request, but only once - * on the first line we print. */ - - fputc('\n', f); - *flags &= ~OUTPUT_BEGIN_NEWLINE; - return 0; -} - -static int show_journal(FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - usec_t not_before, - unsigned how_many, - OutputFlags flags, - bool *ellipsized) { - - int r; - unsigned line = 0; - bool need_seek = false; - int warn_cutoff = flags & OUTPUT_WARN_CUTOFF; - - assert(j); - assert(mode >= 0); - assert(mode < _OUTPUT_MODE_MAX); - - /* Seek to end */ - r = sd_journal_seek_tail(j); - if (r < 0) - return log_error_errno(r, "Failed to seek to tail: %m"); - - r = sd_journal_previous_skip(j, how_many); - if (r < 0) - return log_error_errno(r, "Failed to skip previous: %m"); - - for (;;) { - for (;;) { - usec_t usec; - - if (need_seek) { - r = sd_journal_next(j); - if (r < 0) - return log_error_errno(r, "Failed to iterate through journal: %m"); - } - - if (r == 0) - break; - - need_seek = true; - - if (not_before > 0) { - r = sd_journal_get_monotonic_usec(j, &usec, NULL); - - /* -ESTALE is returned if the - timestamp is not from this boot */ - if (r == -ESTALE) - continue; - else if (r < 0) - return log_error_errno(r, "Failed to get journal time: %m"); - - if (usec < not_before) - continue; - } - - line ++; - maybe_print_begin_newline(f, &flags); - - r = output_journal(f, j, mode, n_columns, flags, ellipsized); - if (r < 0) - return r; - } - - if (warn_cutoff && line < how_many && not_before > 0) { - sd_id128_t boot_id; - usec_t cutoff = 0; - - /* Check whether the cutoff line is too early */ - - r = sd_id128_get_boot(&boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get boot id: %m"); - - r = sd_journal_get_cutoff_monotonic_usec(j, boot_id, &cutoff, NULL); - if (r < 0) - return log_error_errno(r, "Failed to get journal cutoff time: %m"); - - if (r > 0 && not_before < cutoff) { - maybe_print_begin_newline(f, &flags); - fprintf(f, "Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.\n"); - } - - warn_cutoff = false; - } - - if (!(flags & OUTPUT_FOLLOW)) - break; - - r = sd_journal_wait(j, USEC_INFINITY); - if (r < 0) - return log_error_errno(r, "Failed to wait for journal: %m"); - - } - - return 0; -} - -int add_matches_for_unit(sd_journal *j, const char *unit) { - int r; - char *m1, *m2, *m3, *m4; - - assert(j); - assert(unit); - - m1 = strjoina("_SYSTEMD_UNIT=", unit); - m2 = strjoina("COREDUMP_UNIT=", unit); - m3 = strjoina("UNIT=", unit); - m4 = strjoina("OBJECT_SYSTEMD_UNIT=", unit); - - (void)( - /* Look for messages from the service itself */ - (r = sd_journal_add_match(j, m1, 0)) || - - /* Look for coredumps of the service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "MESSAGE_ID=fc2e22bc6ee647b6b90729ab34a250b1", 0)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) || - (r = sd_journal_add_match(j, m2, 0)) || - - /* Look for messages from PID 1 about this service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "_PID=1", 0)) || - (r = sd_journal_add_match(j, m3, 0)) || - - /* Look for messages from authorized daemons about this service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) || - (r = sd_journal_add_match(j, m4, 0)) - ); - - if (r == 0 && endswith(unit, ".slice")) { - char *m5 = strappend("_SYSTEMD_SLICE=", unit); - - /* Show all messages belonging to a slice */ - (void)( - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m5, 0)) - ); - } - - return r; -} - -int add_matches_for_user_unit(sd_journal *j, const char *unit, uid_t uid) { - int r; - char *m1, *m2, *m3, *m4; - char muid[sizeof("_UID=") + DECIMAL_STR_MAX(uid_t)]; - - assert(j); - assert(unit); - - m1 = strjoina("_SYSTEMD_USER_UNIT=", unit); - m2 = strjoina("USER_UNIT=", unit); - m3 = strjoina("COREDUMP_USER_UNIT=", unit); - m4 = strjoina("OBJECT_SYSTEMD_USER_UNIT=", unit); - sprintf(muid, "_UID="UID_FMT, uid); - - (void) ( - /* Look for messages from the user service itself */ - (r = sd_journal_add_match(j, m1, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || - - /* Look for messages from systemd about this service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m2, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || - - /* Look for coredumps of the service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m3, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) || - - /* Look for messages from authorized daemons about this service */ - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m4, 0)) || - (r = sd_journal_add_match(j, muid, 0)) || - (r = sd_journal_add_match(j, "_UID=0", 0)) - ); - - if (r == 0 && endswith(unit, ".slice")) { - char *m5 = strappend("_SYSTEMD_SLICE=", unit); - - /* Show all messages belonging to a slice */ - (void)( - (r = sd_journal_add_disjunction(j)) || - (r = sd_journal_add_match(j, m5, 0)) || - (r = sd_journal_add_match(j, muid, 0)) - ); - } - - return r; -} - -static int get_boot_id_for_machine(const char *machine, sd_id128_t *boot_id) { - _cleanup_close_pair_ int pair[2] = { -1, -1 }; - _cleanup_close_ int pidnsfd = -1, mntnsfd = -1, rootfd = -1; - pid_t pid, child; - siginfo_t si; - char buf[37]; - ssize_t k; - int r; - - assert(machine); - assert(boot_id); - - if (!machine_name_is_valid(machine)) - return -EINVAL; - - r = container_get_leader(machine, &pid); - if (r < 0) - return r; - - r = namespace_open(pid, &pidnsfd, &mntnsfd, NULL, NULL, &rootfd); - if (r < 0) - return r; - - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) < 0) - return -errno; - - child = fork(); - if (child < 0) - return -errno; - - if (child == 0) { - int fd; - - pair[0] = safe_close(pair[0]); - - r = namespace_enter(pidnsfd, mntnsfd, -1, -1, rootfd); - if (r < 0) - _exit(EXIT_FAILURE); - - fd = open("/proc/sys/kernel/random/boot_id", O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - _exit(EXIT_FAILURE); - - r = loop_read_exact(fd, buf, 36, false); - safe_close(fd); - if (r < 0) - _exit(EXIT_FAILURE); - - k = send(pair[1], buf, 36, MSG_NOSIGNAL); - if (k != 36) - _exit(EXIT_FAILURE); - - _exit(EXIT_SUCCESS); - } - - pair[1] = safe_close(pair[1]); - - r = wait_for_terminate(child, &si); - if (r < 0 || si.si_code != CLD_EXITED || si.si_status != EXIT_SUCCESS) - return r < 0 ? r : -EIO; - - k = recv(pair[0], buf, 36, 0); - if (k != 36) - return -EIO; - - buf[36] = 0; - r = sd_id128_from_string(buf, boot_id); - if (r < 0) - return r; - - return 0; -} - -int add_match_this_boot(sd_journal *j, const char *machine) { - char match[9+32+1] = "_BOOT_ID="; - sd_id128_t boot_id; - int r; - - assert(j); - - if (machine) { - r = get_boot_id_for_machine(machine, &boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get boot id of container %s: %m", machine); - } else { - r = sd_id128_get_boot(&boot_id); - if (r < 0) - return log_error_errno(r, "Failed to get boot id: %m"); - } - - sd_id128_to_string(boot_id, match + 9); - r = sd_journal_add_match(j, match, strlen(match)); - if (r < 0) - return log_error_errno(r, "Failed to add match: %m"); - - r = sd_journal_add_conjunction(j); - if (r < 0) - return log_error_errno(r, "Failed to add conjunction: %m"); - - return 0; -} - -int show_journal_by_unit( - FILE *f, - const char *unit, - OutputMode mode, - unsigned n_columns, - usec_t not_before, - unsigned how_many, - uid_t uid, - OutputFlags flags, - int journal_open_flags, - bool system_unit, - bool *ellipsized) { - - _cleanup_(sd_journal_closep) sd_journal *j = NULL; - int r; - - assert(mode >= 0); - assert(mode < _OUTPUT_MODE_MAX); - assert(unit); - - if (how_many <= 0) - return 0; - - r = sd_journal_open(&j, journal_open_flags); - if (r < 0) - return log_error_errno(r, "Failed to open journal: %m"); - - r = add_match_this_boot(j, NULL); - if (r < 0) - return r; - - if (system_unit) - r = add_matches_for_unit(j, unit); - else - r = add_matches_for_user_unit(j, unit, uid); - if (r < 0) - return log_error_errno(r, "Failed to add unit matches: %m"); - - if (_unlikely_(log_get_max_level() >= LOG_DEBUG)) { - _cleanup_free_ char *filter; - - filter = journal_make_match_string(j); - if (!filter) - return log_oom(); - - log_debug("Journal filter: %s", filter); - } - - return show_journal(f, j, mode, n_columns, not_before, how_many, flags, ellipsized); -} - -static const char *const output_mode_table[_OUTPUT_MODE_MAX] = { - [OUTPUT_SHORT] = "short", - [OUTPUT_SHORT_ISO] = "short-iso", - [OUTPUT_SHORT_PRECISE] = "short-precise", - [OUTPUT_SHORT_MONOTONIC] = "short-monotonic", - [OUTPUT_VERBOSE] = "verbose", - [OUTPUT_EXPORT] = "export", - [OUTPUT_JSON] = "json", - [OUTPUT_JSON_PRETTY] = "json-pretty", - [OUTPUT_JSON_SSE] = "json-sse", - [OUTPUT_CAT] = "cat" -}; - -DEFINE_STRING_TABLE_LOOKUP(output_mode, OutputMode); diff --git a/src/shared/logs-show.h b/src/shared/logs-show.h deleted file mode 100644 index 9765a24ff2..0000000000 --- a/src/shared/logs-show.h +++ /dev/null @@ -1,73 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include -#include -#include -#include - -#include "sd-journal.h" - -#include "macro.h" -#include "output-mode.h" -#include "time-util.h" -#include "util.h" - -int output_journal( - FILE *f, - sd_journal *j, - OutputMode mode, - unsigned n_columns, - OutputFlags flags, - bool *ellipsized); - -int add_match_this_boot(sd_journal *j, const char *machine); - -int add_matches_for_unit( - sd_journal *j, - const char *unit); - -int add_matches_for_user_unit( - sd_journal *j, - const char *unit, - uid_t uid); - -int show_journal_by_unit( - FILE *f, - const char *unit, - OutputMode mode, - unsigned n_columns, - usec_t not_before, - unsigned how_many, - uid_t uid, - OutputFlags flags, - int journal_open_flags, - bool system_unit, - bool *ellipsized); - -void json_escape( - FILE *f, - const char* p, - size_t l, - OutputFlags flags); - -const char* output_mode_to_string(OutputMode m) _const_; -OutputMode output_mode_from_string(const char *s) _pure_; diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c deleted file mode 100644 index ed8a29c575..0000000000 --- a/src/shared/machine-image.c +++ /dev/null @@ -1,813 +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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "alloc-util.h" -#include "btrfs-util.h" -#include "chattr-util.h" -#include "copy.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "hashmap.h" -#include "lockfile-util.h" -#include "log.h" -#include "macro.h" -#include "machine-image.h" -#include "mkdir.h" -#include "path-util.h" -#include "rm-rf.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "utf8.h" -#include "util.h" -#include "xattr-util.h" - -static const char image_search_path[] = - "/var/lib/machines\0" - "/var/lib/container\0" /* legacy */ - "/usr/local/lib/machines\0" - "/usr/lib/machines\0"; - -Image *image_unref(Image *i) { - if (!i) - return NULL; - - free(i->name); - free(i->path); - free(i); - return NULL; -} - -static char **image_settings_path(Image *image) { - _cleanup_strv_free_ char **l = NULL; - char **ret; - const char *fn, *s; - unsigned i = 0; - - assert(image); - - l = new0(char*, 4); - if (!l) - return NULL; - - fn = strjoina(image->name, ".nspawn"); - - FOREACH_STRING(s, "/etc/systemd/nspawn/", "/run/systemd/nspawn/") { - l[i] = strappend(s, fn); - if (!l[i]) - return NULL; - - i++; - } - - l[i] = file_in_same_dir(image->path, fn); - if (!l[i]) - return NULL; - - ret = l; - l = NULL; - - return ret; -} - -static int image_new( - ImageType t, - const char *pretty, - const char *path, - const char *filename, - bool read_only, - usec_t crtime, - usec_t mtime, - Image **ret) { - - _cleanup_(image_unrefp) Image *i = NULL; - - assert(t >= 0); - assert(t < _IMAGE_TYPE_MAX); - assert(pretty); - assert(filename); - assert(ret); - - i = new0(Image, 1); - if (!i) - return -ENOMEM; - - i->type = t; - i->read_only = read_only; - i->crtime = crtime; - i->mtime = mtime; - i->usage = i->usage_exclusive = (uint64_t) -1; - i->limit = i->limit_exclusive = (uint64_t) -1; - - i->name = strdup(pretty); - if (!i->name) - return -ENOMEM; - - if (path) - i->path = strjoin(path, "/", filename, NULL); - else - i->path = strdup(filename); - - if (!i->path) - return -ENOMEM; - - path_kill_slashes(i->path); - - *ret = i; - i = NULL; - - return 0; -} - -static int image_make( - const char *pretty, - int dfd, - const char *path, - const char *filename, - Image **ret) { - - struct stat st; - bool read_only; - int r; - - assert(filename); - - /* We explicitly *do* follow symlinks here, since we want to - * allow symlinking trees into /var/lib/machines/, and treat - * them normally. */ - - if (fstatat(dfd, filename, &st, 0) < 0) - return -errno; - - read_only = - (path && path_startswith(path, "/usr")) || - (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS); - - if (S_ISDIR(st.st_mode)) { - _cleanup_close_ int fd = -1; - unsigned file_attr = 0; - - if (!ret) - return 1; - - if (!pretty) - pretty = filename; - - fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY); - if (fd < 0) - return -errno; - - /* btrfs subvolumes have inode 256 */ - if (st.st_ino == 256) { - - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (r) { - BtrfsSubvolInfo info; - - /* It's a btrfs subvolume */ - - r = btrfs_subvol_get_info_fd(fd, 0, &info); - if (r < 0) - return r; - - r = image_new(IMAGE_SUBVOLUME, - pretty, - path, - filename, - info.read_only || read_only, - info.otime, - 0, - ret); - if (r < 0) - return r; - - if (btrfs_quota_scan_ongoing(fd) == 0) { - BtrfsQuotaInfo quota; - - r = btrfs_subvol_get_subtree_quota_fd(fd, 0, "a); - if (r >= 0) { - (*ret)->usage = quota.referenced; - (*ret)->usage_exclusive = quota.exclusive; - - (*ret)->limit = quota.referenced_max; - (*ret)->limit_exclusive = quota.exclusive_max; - } - } - - return 1; - } - } - - /* If the IMMUTABLE bit is set, we consider the - * directory read-only. Since the ioctl is not - * supported everywhere we ignore failures. */ - (void) read_attr_fd(fd, &file_attr); - - /* It's just a normal directory. */ - r = image_new(IMAGE_DIRECTORY, - pretty, - path, - filename, - read_only || (file_attr & FS_IMMUTABLE_FL), - 0, - 0, - ret); - if (r < 0) - return r; - - return 1; - - } else if (S_ISREG(st.st_mode) && endswith(filename, ".raw")) { - usec_t crtime = 0; - - /* It's a RAW disk image */ - - if (!ret) - return 1; - - fd_getcrtime_at(dfd, filename, &crtime, 0); - - if (!pretty) - pretty = strndupa(filename, strlen(filename) - 4); - - r = image_new(IMAGE_RAW, - pretty, - path, - filename, - !(st.st_mode & 0222) || read_only, - crtime, - timespec_load(&st.st_mtim), - ret); - if (r < 0) - return r; - - (*ret)->usage = (*ret)->usage_exclusive = st.st_blocks * 512; - (*ret)->limit = (*ret)->limit_exclusive = st.st_size; - - return 1; - } - - return 0; -} - -int image_find(const char *name, Image **ret) { - const char *path; - int r; - - assert(name); - - /* There are no images with invalid names */ - if (!image_name_is_valid(name)) - return 0; - - NULSTR_FOREACH(path, image_search_path) { - _cleanup_closedir_ DIR *d = NULL; - - d = opendir(path); - if (!d) { - if (errno == ENOENT) - continue; - - return -errno; - } - - r = image_make(NULL, dirfd(d), path, name, ret); - if (r == 0 || r == -ENOENT) { - _cleanup_free_ char *raw = NULL; - - raw = strappend(name, ".raw"); - if (!raw) - return -ENOMEM; - - r = image_make(NULL, dirfd(d), path, raw, ret); - if (r == 0 || r == -ENOENT) - continue; - } - if (r < 0) - return r; - - return 1; - } - - if (streq(name, ".host")) - return image_make(".host", AT_FDCWD, NULL, "/", ret); - - return 0; -}; - -int image_discover(Hashmap *h) { - const char *path; - int r; - - assert(h); - - NULSTR_FOREACH(path, image_search_path) { - _cleanup_closedir_ DIR *d = NULL; - struct dirent *de; - - d = opendir(path); - if (!d) { - if (errno == ENOENT) - continue; - - return -errno; - } - - FOREACH_DIRENT_ALL(de, d, return -errno) { - _cleanup_(image_unrefp) Image *image = NULL; - - if (!image_name_is_valid(de->d_name)) - continue; - - if (hashmap_contains(h, de->d_name)) - continue; - - r = image_make(NULL, dirfd(d), path, de->d_name, &image); - if (r == 0 || r == -ENOENT) - continue; - if (r < 0) - return r; - - r = hashmap_put(h, image->name, image); - if (r < 0) - return r; - - image = NULL; - } - } - - if (!hashmap_contains(h, ".host")) { - _cleanup_(image_unrefp) Image *image = NULL; - - r = image_make(".host", AT_FDCWD, NULL, "/", &image); - if (r < 0) - return r; - - r = hashmap_put(h, image->name, image); - if (r < 0) - return r; - - image = NULL; - - } - - return 0; -} - -void image_hashmap_free(Hashmap *map) { - Image *i; - - while ((i = hashmap_steal_first(map))) - image_unref(i); - - hashmap_free(map); -} - -int image_remove(Image *i) { - _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT; - _cleanup_strv_free_ char **settings = NULL; - char **j; - int r; - - assert(i); - - if (path_equal(i->path, "/") || - path_startswith(i->path, "/usr")) - return -EROFS; - - settings = image_settings_path(i); - if (!settings) - return -ENOMEM; - - /* Make sure we don't interfere with a running nspawn */ - r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); - if (r < 0) - return r; - - switch (i->type) { - - case IMAGE_SUBVOLUME: - r = btrfs_subvol_remove(i->path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); - if (r < 0) - return r; - break; - - case IMAGE_DIRECTORY: - /* Allow deletion of read-only directories */ - (void) chattr_path(i->path, false, FS_IMMUTABLE_FL); - r = rm_rf(i->path, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - if (r < 0) - return r; - - break; - - case IMAGE_RAW: - if (unlink(i->path) < 0) - return -errno; - break; - - default: - return -EOPNOTSUPP; - } - - STRV_FOREACH(j, settings) { - if (unlink(*j) < 0 && errno != ENOENT) - log_debug_errno(errno, "Failed to unlink %s, ignoring: %m", *j); - } - - return 0; -} - -static int rename_settings_file(const char *path, const char *new_name) { - _cleanup_free_ char *rs = NULL; - const char *fn; - - fn = strjoina(new_name, ".nspawn"); - - rs = file_in_same_dir(path, fn); - if (!rs) - return -ENOMEM; - - return rename_noreplace(AT_FDCWD, path, AT_FDCWD, rs); -} - -int image_rename(Image *i, const char *new_name) { - _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT, name_lock = LOCK_FILE_INIT; - _cleanup_free_ char *new_path = NULL, *nn = NULL; - _cleanup_strv_free_ char **settings = NULL; - unsigned file_attr = 0; - char **j; - int r; - - assert(i); - - if (!image_name_is_valid(new_name)) - return -EINVAL; - - if (path_equal(i->path, "/") || - path_startswith(i->path, "/usr")) - return -EROFS; - - settings = image_settings_path(i); - if (!settings) - return -ENOMEM; - - /* Make sure we don't interfere with a running nspawn */ - r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); - if (r < 0) - return r; - - /* Make sure nobody takes the new name, between the time we - * checked it is currently unused in all search paths, and the - * time we take possesion of it */ - r = image_name_lock(new_name, LOCK_EX|LOCK_NB, &name_lock); - if (r < 0) - return r; - - r = image_find(new_name, NULL); - if (r < 0) - return r; - if (r > 0) - return -EEXIST; - - switch (i->type) { - - case IMAGE_DIRECTORY: - /* Turn of the immutable bit while we rename the image, so that we can rename it */ - (void) read_attr_path(i->path, &file_attr); - - if (file_attr & FS_IMMUTABLE_FL) - (void) chattr_path(i->path, false, FS_IMMUTABLE_FL); - - /* fall through */ - - case IMAGE_SUBVOLUME: - new_path = file_in_same_dir(i->path, new_name); - break; - - case IMAGE_RAW: { - const char *fn; - - fn = strjoina(new_name, ".raw"); - new_path = file_in_same_dir(i->path, fn); - break; - } - - default: - return -EOPNOTSUPP; - } - - if (!new_path) - return -ENOMEM; - - nn = strdup(new_name); - if (!nn) - return -ENOMEM; - - r = rename_noreplace(AT_FDCWD, i->path, AT_FDCWD, new_path); - if (r < 0) - return r; - - /* Restore the immutable bit, if it was set before */ - if (file_attr & FS_IMMUTABLE_FL) - (void) chattr_path(new_path, true, FS_IMMUTABLE_FL); - - free(i->path); - i->path = new_path; - new_path = NULL; - - free(i->name); - i->name = nn; - nn = NULL; - - STRV_FOREACH(j, settings) { - r = rename_settings_file(*j, new_name); - if (r < 0 && r != -ENOENT) - log_debug_errno(r, "Failed to rename settings file %s, ignoring: %m", *j); - } - - return 0; -} - -static int clone_settings_file(const char *path, const char *new_name) { - _cleanup_free_ char *rs = NULL; - const char *fn; - - fn = strjoina(new_name, ".nspawn"); - - rs = file_in_same_dir(path, fn); - if (!rs) - return -ENOMEM; - - return copy_file_atomic(path, rs, 0664, false, 0); -} - -int image_clone(Image *i, const char *new_name, bool read_only) { - _cleanup_release_lock_file_ LockFile name_lock = LOCK_FILE_INIT; - _cleanup_strv_free_ char **settings = NULL; - const char *new_path; - char **j; - int r; - - assert(i); - - if (!image_name_is_valid(new_name)) - return -EINVAL; - - settings = image_settings_path(i); - if (!settings) - return -ENOMEM; - - /* Make sure nobody takes the new name, between the time we - * checked it is currently unused in all search paths, and the - * time we take possesion of it */ - r = image_name_lock(new_name, LOCK_EX|LOCK_NB, &name_lock); - if (r < 0) - return r; - - r = image_find(new_name, NULL); - if (r < 0) - return r; - if (r > 0) - return -EEXIST; - - switch (i->type) { - - case IMAGE_SUBVOLUME: - case IMAGE_DIRECTORY: - new_path = strjoina("/var/lib/machines/", new_name); - - r = btrfs_subvol_snapshot(i->path, new_path, (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); - - /* Enable "subtree" quotas for the copy, if we didn't - * copy any quota from the source. */ - (void) btrfs_subvol_auto_qgroup(i->path, 0, true); - - break; - - case IMAGE_RAW: - new_path = strjoina("/var/lib/machines/", new_name, ".raw"); - - r = copy_file_atomic(i->path, new_path, read_only ? 0444 : 0644, false, FS_NOCOW_FL); - break; - - default: - return -EOPNOTSUPP; - } - - if (r < 0) - return r; - - STRV_FOREACH(j, settings) { - r = clone_settings_file(*j, new_name); - if (r < 0 && r != -ENOENT) - log_debug_errno(r, "Failed to clone settings %s, ignoring: %m", *j); - } - - return 0; -} - -int image_read_only(Image *i, bool b) { - _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT; - int r; - assert(i); - - if (path_equal(i->path, "/") || - path_startswith(i->path, "/usr")) - return -EROFS; - - /* Make sure we don't interfere with a running nspawn */ - r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); - if (r < 0) - return r; - - switch (i->type) { - - case IMAGE_SUBVOLUME: - - /* Note that we set the flag only on the top-level - * subvolume of the image. */ - - r = btrfs_subvol_set_read_only(i->path, b); - if (r < 0) - return r; - - break; - - case IMAGE_DIRECTORY: - /* For simple directory trees we cannot use the access - mode of the top-level directory, since it has an - effect on the container itself. However, we can - use the "immutable" flag, to at least make the - top-level directory read-only. It's not as good as - a read-only subvolume, but at least something, and - we can read the value back.*/ - - r = chattr_path(i->path, b, FS_IMMUTABLE_FL); - if (r < 0) - return r; - - break; - - case IMAGE_RAW: { - struct stat st; - - if (stat(i->path, &st) < 0) - return -errno; - - if (chmod(i->path, (st.st_mode & 0444) | (b ? 0000 : 0200)) < 0) - return -errno; - - /* If the images is now read-only, it's a good time to - * defrag it, given that no write patterns will - * fragment it again. */ - if (b) - (void) btrfs_defrag(i->path); - break; - } - - default: - return -EOPNOTSUPP; - } - - return 0; -} - -int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local) { - _cleanup_free_ char *p = NULL; - LockFile t = LOCK_FILE_INIT; - struct stat st; - int r; - - assert(path); - assert(global); - assert(local); - - /* Locks an image path. This actually creates two locks: one - * "local" one, next to the image path itself, which might be - * shared via NFS. And another "global" one, in /run, that - * uses the device/inode number. This has the benefit that we - * can even lock a tree that is a mount point, correctly. */ - - if (path_equal(path, "/")) - return -EBUSY; - - if (!path_is_absolute(path)) - return -EINVAL; - - if (stat(path, &st) >= 0) { - if (asprintf(&p, "/run/systemd/nspawn/locks/inode-%lu:%lu", (unsigned long) st.st_dev, (unsigned long) st.st_ino) < 0) - return -ENOMEM; - } - - r = make_lock_file_for(path, operation, &t); - if (r < 0) - return r; - - if (p) { - mkdir_p("/run/systemd/nspawn/locks", 0700); - - r = make_lock_file(p, operation, global); - if (r < 0) { - release_lock_file(&t); - return r; - } - } - - *local = t; - return 0; -} - -int image_set_limit(Image *i, uint64_t referenced_max) { - assert(i); - - if (path_equal(i->path, "/") || - path_startswith(i->path, "/usr")) - return -EROFS; - - if (i->type != IMAGE_SUBVOLUME) - return -EOPNOTSUPP; - - /* We set the quota both for the subvolume as well as for the - * subtree. The latter is mostly for historical reasons, since - * we didn't use to have a concept of subtree quota, and hence - * only modified the subvolume quota. */ - - (void) btrfs_qgroup_set_limit(i->path, 0, referenced_max); - (void) btrfs_subvol_auto_qgroup(i->path, 0, true); - return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max); -} - -int image_name_lock(const char *name, int operation, LockFile *ret) { - const char *p; - - assert(name); - assert(ret); - - /* Locks an image name, regardless of the precise path used. */ - - if (!image_name_is_valid(name)) - return -EINVAL; - - if (streq(name, ".host")) - return -EBUSY; - - mkdir_p("/run/systemd/nspawn/locks", 0700); - p = strjoina("/run/systemd/nspawn/locks/name-", name); - - return make_lock_file(p, operation, ret); -} - -bool image_name_is_valid(const char *s) { - if (!filename_is_valid(s)) - return false; - - if (string_has_cc(s, NULL)) - return false; - - if (!utf8_is_valid(s)) - return false; - - /* Temporary files for atomically creating new files */ - if (startswith(s, ".#")) - return false; - - return true; -} - -static const char* const image_type_table[_IMAGE_TYPE_MAX] = { - [IMAGE_DIRECTORY] = "directory", - [IMAGE_SUBVOLUME] = "subvolume", - [IMAGE_RAW] = "raw", -}; - -DEFINE_STRING_TABLE_LOOKUP(image_type, ImageType); diff --git a/src/shared/machine-image.h b/src/shared/machine-image.h deleted file mode 100644 index 31b720d50c..0000000000 --- a/src/shared/machine-image.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include - -#include "hashmap.h" -#include "lockfile-util.h" -#include "macro.h" -#include "time-util.h" - -typedef enum ImageType { - IMAGE_DIRECTORY, - IMAGE_SUBVOLUME, - IMAGE_RAW, - _IMAGE_TYPE_MAX, - _IMAGE_TYPE_INVALID = -1 -} ImageType; - -typedef struct Image { - ImageType type; - char *name; - char *path; - bool read_only; - - usec_t crtime; - usec_t mtime; - - uint64_t usage; - uint64_t usage_exclusive; - uint64_t limit; - uint64_t limit_exclusive; - - void *userdata; -} Image; - -Image *image_unref(Image *i); -void image_hashmap_free(Hashmap *map); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Image*, image_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, image_hashmap_free); - -int image_find(const char *name, Image **ret); -int image_discover(Hashmap *map); - -int image_remove(Image *i); -int image_rename(Image *i, const char *new_name); -int image_clone(Image *i, const char *new_name, bool read_only); -int image_read_only(Image *i, bool b); - -const char* image_type_to_string(ImageType t) _const_; -ImageType image_type_from_string(const char *s) _pure_; - -bool image_name_is_valid(const char *s) _pure_; - -int image_path_lock(const char *path, int operation, LockFile *global, LockFile *local); -int image_name_lock(const char *name, int operation, LockFile *ret); - -int image_set_limit(Image *i, uint64_t referenced_max); diff --git a/src/shared/machine-pool.c b/src/shared/machine-pool.c deleted file mode 100644 index e5674e4137..0000000000 --- a/src/shared/machine-pool.c +++ /dev/null @@ -1,427 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-bus-protocol.h" -#include "sd-bus.h" - -#include "alloc-util.h" -#include "btrfs-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "fs-util.h" -#include "lockfile-util.h" -#include "log.h" -#include "machine-pool.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "process-util.h" -#include "signal-util.h" -#include "stat-util.h" -#include "string-util.h" - -#define VAR_LIB_MACHINES_SIZE_START (1024UL*1024UL*500UL) -#define VAR_LIB_MACHINES_FREE_MIN (1024UL*1024UL*750UL) - -static int check_btrfs(void) { - struct statfs sfs; - - if (statfs("/var/lib/machines", &sfs) < 0) { - if (errno != ENOENT) - return -errno; - - if (statfs("/var/lib", &sfs) < 0) - return -errno; - } - - return F_TYPE_EQUAL(sfs.f_type, BTRFS_SUPER_MAGIC); -} - -static int setup_machine_raw(uint64_t size, sd_bus_error *error) { - _cleanup_free_ char *tmp = NULL; - _cleanup_close_ int fd = -1; - struct statvfs ss; - pid_t pid = 0; - siginfo_t si; - int r; - - /* We want to be able to make use of btrfs-specific file - * system features, in particular subvolumes, reflinks and - * quota. Hence, if we detect that /var/lib/machines.raw is - * not located on btrfs, let's create a loopback file, place a - * btrfs file system into it, and mount it to - * /var/lib/machines. */ - - fd = open("/var/lib/machines.raw", O_RDWR|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (fd >= 0) { - r = fd; - fd = -1; - return r; - } - - if (errno != ENOENT) - return sd_bus_error_set_errnof(error, errno, "Failed to open /var/lib/machines.raw: %m"); - - r = tempfn_xxxxxx("/var/lib/machines.raw", NULL, &tmp); - if (r < 0) - return r; - - (void) mkdir_p_label("/var/lib", 0755); - fd = open(tmp, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0600); - if (fd < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to create /var/lib/machines.raw: %m"); - - if (fstatvfs(fd, &ss) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to determine free space on /var/lib/machines.raw: %m"); - goto fail; - } - - if (ss.f_bsize * ss.f_bavail < VAR_LIB_MACHINES_FREE_MIN) { - r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "Not enough free disk space to set up /var/lib/machines."); - goto fail; - } - - if (ftruncate(fd, size) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to enlarge /var/lib/machines.raw: %m"); - goto fail; - } - - pid = fork(); - if (pid < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to fork mkfs.btrfs: %m"); - goto fail; - } - - if (pid == 0) { - - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); - - fd = safe_close(fd); - - execlp("mkfs.btrfs", "-Lvar-lib-machines", tmp, NULL); - if (errno == ENOENT) - return 99; - - _exit(EXIT_FAILURE); - } - - r = wait_for_terminate(pid, &si); - if (r < 0) { - sd_bus_error_set_errnof(error, r, "Failed to wait for mkfs.btrfs: %m"); - goto fail; - } - - pid = 0; - - if (si.si_code != CLD_EXITED) { - r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "mkfs.btrfs died abnormally."); - goto fail; - } - if (si.si_status == 99) { - r = sd_bus_error_set_errnof(error, ENOENT, "Cannot set up /var/lib/machines, mkfs.btrfs is missing"); - goto fail; - } - if (si.si_status != 0) { - r = sd_bus_error_setf(error, SD_BUS_ERROR_FAILED, "mkfs.btrfs failed with error code %i", si.si_status); - goto fail; - } - - r = rename_noreplace(AT_FDCWD, tmp, AT_FDCWD, "/var/lib/machines.raw"); - if (r < 0) { - sd_bus_error_set_errnof(error, r, "Failed to move /var/lib/machines.raw into place: %m"); - goto fail; - } - - r = fd; - fd = -1; - - return r; - -fail: - unlink_noerrno(tmp); - - if (pid > 1) - kill_and_sigcont(pid, SIGKILL); - - return r; -} - -int setup_machine_directory(uint64_t size, sd_bus_error *error) { - _cleanup_release_lock_file_ LockFile lock_file = LOCK_FILE_INIT; - struct loop_info64 info = { - .lo_flags = LO_FLAGS_AUTOCLEAR, - }; - _cleanup_close_ int fd = -1, control = -1, loop = -1; - _cleanup_free_ char* loopdev = NULL; - char tmpdir[] = "/tmp/machine-pool.XXXXXX", *mntdir = NULL; - bool tmpdir_made = false, mntdir_made = false, mntdir_mounted = false; - char buf[FORMAT_BYTES_MAX]; - int r, nr = -1; - - /* btrfs cannot handle file systems < 16M, hence use this as minimum */ - if (size == (uint64_t) -1) - size = VAR_LIB_MACHINES_SIZE_START; - else if (size < 16*1024*1024) - size = 16*1024*1024; - - /* Make sure we only set the directory up once at a time */ - r = make_lock_file("/run/systemd/machines.lock", LOCK_EX, &lock_file); - if (r < 0) - return r; - - r = check_btrfs(); - if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to determine whether /var/lib/machines is located on btrfs: %m"); - if (r > 0) { - (void) btrfs_subvol_make_label("/var/lib/machines"); - - r = btrfs_quota_enable("/var/lib/machines", true); - if (r < 0) - log_warning_errno(r, "Failed to enable quota for /var/lib/machines, ignoring: %m"); - - r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true); - if (r < 0) - log_warning_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, ignoring: %m"); - - return 1; - } - - if (path_is_mount_point("/var/lib/machines", AT_SYMLINK_FOLLOW) > 0) { - log_debug("/var/lib/machines is already a mount point, not creating loopback file for it."); - return 0; - } - - r = dir_is_populated("/var/lib/machines"); - if (r < 0 && r != -ENOENT) - return r; - if (r > 0) { - log_debug("/var/log/machines is already populated, not creating loopback file for it."); - return 0; - } - - r = mkfs_exists("btrfs"); - if (r == -ENOENT) { - log_debug("mkfs.btrfs is missing, cannot create loopback file for /var/lib/machines."); - return 0; - } - if (r < 0) - return r; - - fd = setup_machine_raw(size, error); - if (fd < 0) - return fd; - - control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); - if (control < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to open /dev/loop-control: %m"); - - nr = ioctl(control, LOOP_CTL_GET_FREE); - if (nr < 0) - return sd_bus_error_set_errnof(error, errno, "Failed to allocate loop device: %m"); - - if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) { - r = -ENOMEM; - goto fail; - } - - loop = open(loopdev, O_CLOEXEC|O_RDWR|O_NOCTTY|O_NONBLOCK); - if (loop < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to open loopback device: %m"); - goto fail; - } - - if (ioctl(loop, LOOP_SET_FD, fd) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to bind loopback device: %m"); - goto fail; - } - - if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to enable auto-clear for loopback device: %m"); - goto fail; - } - - /* We need to make sure the new /var/lib/machines directory - * has an access mode of 0700 at the time it is first made - * available. mkfs will create it with 0755 however. Hence, - * let's mount the directory into an inaccessible directory - * below /tmp first, fix the access mode, and move it to the - * public place then. */ - - if (!mkdtemp(tmpdir)) { - r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount parent directory: %m"); - goto fail; - } - tmpdir_made = true; - - mntdir = strjoina(tmpdir, "/mnt"); - if (mkdir(mntdir, 0700) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to create temporary mount directory: %m"); - goto fail; - } - mntdir_made = true; - - if (mount(loopdev, mntdir, "btrfs", 0, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to mount loopback device: %m"); - goto fail; - } - mntdir_mounted = true; - - r = btrfs_quota_enable(mntdir, true); - if (r < 0) - log_warning_errno(r, "Failed to enable quota, ignoring: %m"); - - r = btrfs_subvol_auto_qgroup(mntdir, 0, true); - if (r < 0) - log_warning_errno(r, "Failed to set up default quota hierarchy, ignoring: %m"); - - if (chmod(mntdir, 0700) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to fix owner: %m"); - goto fail; - } - - (void) mkdir_p_label("/var/lib/machines", 0700); - - if (mount(mntdir, "/var/lib/machines", NULL, MS_BIND, NULL) < 0) { - r = sd_bus_error_set_errnof(error, errno, "Failed to mount directory into right place: %m"); - goto fail; - } - - (void) syncfs(fd); - - log_info("Set up /var/lib/machines as btrfs loopback file system of size %s mounted on /var/lib/machines.raw.", format_bytes(buf, sizeof(buf), size)); - - (void) umount2(mntdir, MNT_DETACH); - (void) rmdir(mntdir); - (void) rmdir(tmpdir); - - return 1; - -fail: - if (mntdir_mounted) - (void) umount2(mntdir, MNT_DETACH); - - if (mntdir_made) - (void) rmdir(mntdir); - if (tmpdir_made) - (void) rmdir(tmpdir); - - if (loop >= 0) { - (void) ioctl(loop, LOOP_CLR_FD); - loop = safe_close(loop); - } - - if (control >= 0 && nr >= 0) - (void) ioctl(control, LOOP_CTL_REMOVE, nr); - - return r; -} - -static int sync_path(const char *p) { - _cleanup_close_ int fd = -1; - - fd = open(p, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return -errno; - - if (syncfs(fd) < 0) - return -errno; - - return 0; -} - -int grow_machine_directory(void) { - char buf[FORMAT_BYTES_MAX]; - struct statvfs a, b; - uint64_t old_size, new_size, max_add; - int r; - - /* Ensure the disk space data is accurate */ - sync_path("/var/lib/machines"); - sync_path("/var/lib/machines.raw"); - - if (statvfs("/var/lib/machines.raw", &a) < 0) - return -errno; - - if (statvfs("/var/lib/machines", &b) < 0) - return -errno; - - /* Don't grow if not enough disk space is available on the host */ - if (((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) <= VAR_LIB_MACHINES_FREE_MIN) - return 0; - - /* Don't grow if at least 1/3th of the fs is still free */ - if (b.f_bavail > b.f_blocks / 3) - return 0; - - /* Calculate how much we are willing to add at most */ - max_add = ((uint64_t) a.f_bavail * (uint64_t) a.f_bsize) - VAR_LIB_MACHINES_FREE_MIN; - - /* Calculate the old size */ - old_size = (uint64_t) b.f_blocks * (uint64_t) b.f_bsize; - - /* Calculate the new size as three times the size of what is used right now */ - new_size = ((uint64_t) b.f_blocks - (uint64_t) b.f_bavail) * (uint64_t) b.f_bsize * 3; - - /* Always, grow at least to the start size */ - if (new_size < VAR_LIB_MACHINES_SIZE_START) - new_size = VAR_LIB_MACHINES_SIZE_START; - - /* If the new size is smaller than the old size, don't grow */ - if (new_size < old_size) - return 0; - - /* Ensure we never add more than the maximum */ - if (new_size > old_size + max_add) - new_size = old_size + max_add; - - r = btrfs_resize_loopback("/var/lib/machines", new_size, true); - if (r <= 0) - return r; - - /* Also bump the quota, of both the subvolume leaf qgroup, as - * well as of any subtree quota group by the same id but a - * higher level, if it exists. */ - (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, new_size); - (void) btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, new_size); - - log_info("Grew /var/lib/machines btrfs loopback file system to %s.", format_bytes(buf, sizeof(buf), new_size)); - return 1; -} diff --git a/src/shared/machine-pool.h b/src/shared/machine-pool.h deleted file mode 100644 index 40fe5ecb3a..0000000000 --- a/src/shared/machine-pool.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2015 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 . -***/ - -#include - -#include "sd-bus.h" - -/* Grow the /var/lib/machines directory after each 10MiB written */ -#define GROW_INTERVAL_BYTES (UINT64_C(10) * UINT64_C(1024) * UINT64_C(1024)) - -int setup_machine_directory(uint64_t size, sd_bus_error *error); -int grow_machine_directory(void); diff --git a/src/shared/output-mode.h b/src/shared/output-mode.h deleted file mode 100644 index c5470e7c1b..0000000000 --- a/src/shared/output-mode.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -typedef enum OutputMode { - OUTPUT_SHORT, - OUTPUT_SHORT_ISO, - OUTPUT_SHORT_PRECISE, - OUTPUT_SHORT_MONOTONIC, - OUTPUT_VERBOSE, - OUTPUT_EXPORT, - OUTPUT_JSON, - OUTPUT_JSON_PRETTY, - OUTPUT_JSON_SSE, - OUTPUT_CAT, - _OUTPUT_MODE_MAX, - _OUTPUT_MODE_INVALID = -1 -} OutputMode; - -typedef enum OutputFlags { - OUTPUT_SHOW_ALL = 1 << 0, - OUTPUT_FOLLOW = 1 << 1, - OUTPUT_WARN_CUTOFF = 1 << 2, - OUTPUT_FULL_WIDTH = 1 << 3, - OUTPUT_COLOR = 1 << 4, - OUTPUT_CATALOG = 1 << 5, - OUTPUT_BEGIN_NEWLINE = 1 << 6, - OUTPUT_UTC = 1 << 7, -} OutputFlags; diff --git a/src/shared/pager.c b/src/shared/pager.c deleted file mode 100644 index 05b2b15e40..0000000000 --- a/src/shared/pager.c +++ /dev/null @@ -1,223 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "copy.h" -#include "fd-util.h" -#include "locale-util.h" -#include "log.h" -#include "macro.h" -#include "pager.h" -#include "process-util.h" -#include "signal-util.h" -#include "string-util.h" -#include "terminal-util.h" - -static pid_t pager_pid = 0; - -noreturn static void pager_fallback(void) { - int r; - - r = copy_bytes(STDIN_FILENO, STDOUT_FILENO, (uint64_t) -1, false); - if (r < 0) { - log_error_errno(r, "Internal pager failed: %m"); - _exit(EXIT_FAILURE); - } - - _exit(EXIT_SUCCESS); -} - -int pager_open(bool jump_to_end) { - _cleanup_close_pair_ int fd[2] = { -1, -1 }; - const char *pager; - pid_t parent_pid; - - if (pager_pid > 0) - return 1; - - if (!on_tty()) - return 0; - - pager = getenv("SYSTEMD_PAGER"); - if (!pager) - pager = getenv("PAGER"); - - /* If the pager is explicitly turned off, honour it */ - if (pager && (pager[0] == 0 || streq(pager, "cat"))) - return 0; - - /* Determine and cache number of columns before we spawn the - * pager so that we get the value from the actual tty */ - (void) columns(); - - if (pipe(fd) < 0) - return log_error_errno(errno, "Failed to create pager pipe: %m"); - - parent_pid = getpid(); - - pager_pid = fork(); - if (pager_pid < 0) - return log_error_errno(errno, "Failed to fork pager: %m"); - - /* In the child start the pager */ - if (pager_pid == 0) { - const char* less_opts, *less_charset; - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - (void) dup2(fd[0], STDIN_FILENO); - safe_close_pair(fd); - - /* Initialize a good set of less options */ - less_opts = getenv("SYSTEMD_LESS"); - if (!less_opts) - less_opts = "FRSXMK"; - if (jump_to_end) - less_opts = strjoina(less_opts, " +G"); - setenv("LESS", less_opts, 1); - - /* Initialize a good charset for less. This is - * particularly important if we output UTF-8 - * characters. */ - less_charset = getenv("SYSTEMD_LESSCHARSET"); - if (!less_charset && is_locale_utf8()) - less_charset = "utf-8"; - if (less_charset) - setenv("LESSCHARSET", less_charset, 1); - - /* Make sure the pager goes away when the parent dies */ - if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) - _exit(EXIT_FAILURE); - - /* Check whether our parent died before we were able - * to set the death signal */ - if (getppid() != parent_pid) - _exit(EXIT_SUCCESS); - - if (pager) { - execlp(pager, pager, NULL); - execl("/bin/sh", "sh", "-c", pager, NULL); - } - - /* Debian's alternatives command for pagers is - * called 'pager'. Note that we do not call - * sensible-pagers here, since that is just a - * shell script that implements a logic that - * is similar to this one anyway, but is - * Debian-specific. */ - execlp("pager", "pager", NULL); - - execlp("less", "less", NULL); - execlp("more", "more", NULL); - - pager_fallback(); - /* not reached */ - } - - /* Return in the parent */ - if (dup2(fd[1], STDOUT_FILENO) < 0) - return log_error_errno(errno, "Failed to duplicate pager pipe: %m"); - if (dup2(fd[1], STDERR_FILENO) < 0) - return log_error_errno(errno, "Failed to duplicate pager pipe: %m"); - - return 1; -} - -void pager_close(void) { - - if (pager_pid <= 0) - return; - - /* Inform pager that we are done */ - stdout = safe_fclose(stdout); - stderr = safe_fclose(stderr); - - (void) kill(pager_pid, SIGCONT); - (void) wait_for_terminate(pager_pid, NULL); - pager_pid = 0; -} - -bool pager_have(void) { - return pager_pid > 0; -} - -int show_man_page(const char *desc, bool null_stdio) { - const char *args[4] = { "man", NULL, NULL, NULL }; - char *e = NULL; - pid_t pid; - size_t k; - int r; - siginfo_t status; - - k = strlen(desc); - - if (desc[k-1] == ')') - e = strrchr(desc, '('); - - if (e) { - char *page = NULL, *section = NULL; - - page = strndupa(desc, e - desc); - section = strndupa(e + 1, desc + k - e - 2); - - args[1] = section; - args[2] = page; - } else - args[1] = desc; - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - - if (pid == 0) { - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - if (null_stdio) { - r = make_null_stdio(); - if (r < 0) { - log_error_errno(r, "Failed to kill stdio: %m"); - _exit(EXIT_FAILURE); - } - } - - execvp(args[0], (char**) args); - log_error_errno(errno, "Failed to execute man: %m"); - _exit(EXIT_FAILURE); - } - - r = wait_for_terminate(pid, &status); - if (r < 0) - return r; - - log_debug("Exit code %i status %i", status.si_code, status.si_status); - return status.si_status; -} diff --git a/src/shared/pager.h b/src/shared/pager.h deleted file mode 100644 index 9fb05796bb..0000000000 --- a/src/shared/pager.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include - -#include "macro.h" - -int pager_open(bool jump_to_end); -void pager_close(void); -bool pager_have(void) _pure_; - -int show_man_page(const char *page, bool null_stdio); diff --git a/src/shared/path-lookup.c b/src/shared/path-lookup.c deleted file mode 100644 index 5410620725..0000000000 --- a/src/shared/path-lookup.c +++ /dev/null @@ -1,441 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include - -#include "alloc-util.h" -#include "install.h" -#include "log.h" -#include "macro.h" -#include "path-lookup.h" -#include "path-util.h" -#include "string-util.h" -#include "strv.h" -#include "util.h" - -int user_config_home(char **config_home) { - const char *e; - char *r; - - e = getenv("XDG_CONFIG_HOME"); - if (e) { - r = strappend(e, "/systemd/user"); - if (!r) - return -ENOMEM; - - *config_home = r; - return 1; - } else { - const char *home; - - home = getenv("HOME"); - if (home) { - r = strappend(home, "/.config/systemd/user"); - if (!r) - return -ENOMEM; - - *config_home = r; - return 1; - } - } - - return 0; -} - -int user_runtime_dir(char **runtime_dir) { - const char *e; - char *r; - - e = getenv("XDG_RUNTIME_DIR"); - if (e) { - r = strappend(e, "/systemd/user"); - if (!r) - return -ENOMEM; - - *runtime_dir = r; - return 1; - } - - return 0; -} - -static int user_data_home_dir(char **dir, const char *suffix) { - const char *e; - char *res; - - /* We don't treat /etc/xdg/systemd here as the spec - * suggests because we assume that that is a link to - * /etc/systemd/ anyway. */ - - e = getenv("XDG_DATA_HOME"); - if (e) - res = strappend(e, suffix); - else { - const char *home; - - home = getenv("HOME"); - if (home) - res = strjoin(home, "/.local/share", suffix, NULL); - else - return 0; - } - if (!res) - return -ENOMEM; - - *dir = res; - return 0; -} - -static char** user_dirs( - const char *generator, - const char *generator_early, - const char *generator_late) { - - const char * const config_unit_paths[] = { - USER_CONFIG_UNIT_PATH, - "/etc/systemd/user", - NULL - }; - - const char * const runtime_unit_path = "/run/systemd/user"; - - const char * const data_unit_paths[] = { - "/usr/local/lib/systemd/user", - "/usr/local/share/systemd/user", - USER_DATA_UNIT_PATH, - "/usr/lib/systemd/user", - "/usr/share/systemd/user", - NULL - }; - - const char *e; - _cleanup_free_ char *config_home = NULL, *runtime_dir = NULL, *data_home = NULL; - _cleanup_strv_free_ char **config_dirs = NULL, **data_dirs = NULL; - _cleanup_free_ char **res = NULL; - char **tmp; - int r; - - /* Implement the mechanisms defined in - * - * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html - * - * We look in both the config and the data dirs because we - * want to encourage that distributors ship their unit files - * as data, and allow overriding as configuration. - */ - - if (user_config_home(&config_home) < 0) - return NULL; - - if (user_runtime_dir(&runtime_dir) < 0) - return NULL; - - e = getenv("XDG_CONFIG_DIRS"); - if (e) { - config_dirs = strv_split(e, ":"); - if (!config_dirs) - return NULL; - } - - r = user_data_home_dir(&data_home, "/systemd/user"); - if (r < 0) - return NULL; - - e = getenv("XDG_DATA_DIRS"); - if (e) - data_dirs = strv_split(e, ":"); - else - data_dirs = strv_new("/usr/local/share", - "/usr/share", - NULL); - if (!data_dirs) - return NULL; - - /* Now merge everything we found. */ - if (generator_early) - if (strv_extend(&res, generator_early) < 0) - return NULL; - - if (config_home) - if (strv_extend(&res, config_home) < 0) - return NULL; - - if (!strv_isempty(config_dirs)) - if (strv_extend_strv_concat(&res, config_dirs, "/systemd/user") < 0) - return NULL; - - if (strv_extend_strv(&res, (char**) config_unit_paths, false) < 0) - return NULL; - - if (runtime_dir) - if (strv_extend(&res, runtime_dir) < 0) - return NULL; - - if (strv_extend(&res, runtime_unit_path) < 0) - return NULL; - - if (generator) - if (strv_extend(&res, generator) < 0) - return NULL; - - if (data_home) - if (strv_extend(&res, data_home) < 0) - return NULL; - - if (!strv_isempty(data_dirs)) - if (strv_extend_strv_concat(&res, data_dirs, "/systemd/user") < 0) - return NULL; - - if (strv_extend_strv(&res, (char**) data_unit_paths, false) < 0) - return NULL; - - if (generator_late) - if (strv_extend(&res, generator_late) < 0) - return NULL; - - if (path_strv_make_absolute_cwd(res) < 0) - return NULL; - - tmp = res; - res = NULL; - return tmp; -} - -char **generator_paths(ManagerRunningAs running_as) { - if (running_as == MANAGER_USER) - return strv_new("/run/systemd/user-generators", - "/etc/systemd/user-generators", - "/usr/local/lib/systemd/user-generators", - USER_GENERATOR_PATH, - NULL); - else - return strv_new("/run/systemd/system-generators", - "/etc/systemd/system-generators", - "/usr/local/lib/systemd/system-generators", - SYSTEM_GENERATOR_PATH, - NULL); -} - -int lookup_paths_init( - LookupPaths *p, - ManagerRunningAs running_as, - bool personal, - const char *root_dir, - const char *generator, - const char *generator_early, - const char *generator_late) { - - const char *e; - bool append = false; /* Add items from SYSTEMD_UNIT_PATH before normal directories */ - int r; - - assert(p); - - /* First priority is whatever has been passed to us via env - * vars */ - e = getenv("SYSTEMD_UNIT_PATH"); - if (e) { - if (endswith(e, ":")) { - e = strndupa(e, strlen(e) - 1); - append = true; - } - - /* FIXME: empty components in other places should be - * rejected. */ - - r = path_split_and_make_absolute(e, &p->unit_path); - if (r < 0) - return r; - } else - p->unit_path = NULL; - - if (!p->unit_path || append) { - /* Let's figure something out. */ - - _cleanup_strv_free_ char **unit_path; - - /* For the user units we include share/ in the search - * path in order to comply with the XDG basedir spec. - * For the system stuff we avoid such nonsense. OTOH - * we include /lib in the search path for the system - * stuff but avoid it for user stuff. */ - - if (running_as == MANAGER_USER) { - if (personal) - unit_path = user_dirs(generator, generator_early, generator_late); - else - unit_path = strv_new( - /* If you modify this you also want to modify - * systemduserunitpath= in systemd.pc.in, and - * the arrays in user_dirs() above! */ - STRV_IFNOTNULL(generator_early), - USER_CONFIG_UNIT_PATH, - "/etc/systemd/user", - "/run/systemd/user", - STRV_IFNOTNULL(generator), - "/usr/local/lib/systemd/user", - "/usr/local/share/systemd/user", - USER_DATA_UNIT_PATH, - "/usr/lib/systemd/user", - "/usr/share/systemd/user", - STRV_IFNOTNULL(generator_late), - NULL); - } else - unit_path = strv_new( - /* If you modify this you also want to modify - * systemdsystemunitpath= in systemd.pc.in! */ - STRV_IFNOTNULL(generator_early), - SYSTEM_CONFIG_UNIT_PATH, - "/etc/systemd/system", - "/run/systemd/system", - STRV_IFNOTNULL(generator), - "/usr/local/lib/systemd/system", - SYSTEM_DATA_UNIT_PATH, - "/usr/lib/systemd/system", -#ifdef HAVE_SPLIT_USR - "/lib/systemd/system", -#endif - STRV_IFNOTNULL(generator_late), - NULL); - - if (!unit_path) - return -ENOMEM; - - r = strv_extend_strv(&p->unit_path, unit_path, false); - if (r < 0) - return r; - } - - if (!path_strv_resolve_uniq(p->unit_path, root_dir)) - return -ENOMEM; - - if (!strv_isempty(p->unit_path)) { - _cleanup_free_ char *t = strv_join(p->unit_path, "\n\t"); - if (!t) - return -ENOMEM; - log_debug("Looking for unit files in (higher priority first):\n\t%s", t); - } else { - log_debug("Ignoring unit files."); - p->unit_path = strv_free(p->unit_path); - } - - if (running_as == MANAGER_SYSTEM) { -#ifdef HAVE_SYSV_COMPAT - /* /etc/init.d/ compatibility does not matter to users */ - - e = getenv("SYSTEMD_SYSVINIT_PATH"); - if (e) { - r = path_split_and_make_absolute(e, &p->sysvinit_path); - if (r < 0) - return r; - } else - p->sysvinit_path = NULL; - - if (strv_isempty(p->sysvinit_path)) { - strv_free(p->sysvinit_path); - - p->sysvinit_path = strv_new( - SYSTEM_SYSVINIT_PATH, /* /etc/init.d/ */ - NULL); - if (!p->sysvinit_path) - return -ENOMEM; - } - - e = getenv("SYSTEMD_SYSVRCND_PATH"); - if (e) { - r = path_split_and_make_absolute(e, &p->sysvrcnd_path); - if (r < 0) - return r; - } else - p->sysvrcnd_path = NULL; - - if (strv_isempty(p->sysvrcnd_path)) { - strv_free(p->sysvrcnd_path); - - p->sysvrcnd_path = strv_new( - SYSTEM_SYSVRCND_PATH, /* /etc/rcN.d/ */ - NULL); - if (!p->sysvrcnd_path) - return -ENOMEM; - } - - if (!path_strv_resolve_uniq(p->sysvinit_path, root_dir)) - return -ENOMEM; - - if (!path_strv_resolve_uniq(p->sysvrcnd_path, root_dir)) - return -ENOMEM; - - if (!strv_isempty(p->sysvinit_path)) { - _cleanup_free_ char *t = strv_join(p->sysvinit_path, "\n\t"); - if (!t) - return -ENOMEM; - log_debug("Looking for SysV init scripts in:\n\t%s", t); - } else { - log_debug("Ignoring SysV init scripts."); - p->sysvinit_path = strv_free(p->sysvinit_path); - } - - if (!strv_isempty(p->sysvrcnd_path)) { - _cleanup_free_ char *t = - strv_join(p->sysvrcnd_path, "\n\t"); - if (!t) - return -ENOMEM; - - log_debug("Looking for SysV rcN.d links in:\n\t%s", t); - } else { - log_debug("Ignoring SysV rcN.d links."); - p->sysvrcnd_path = strv_free(p->sysvrcnd_path); - } -#else - log_debug("SysV init scripts and rcN.d links support disabled"); -#endif - } - - return 0; -} - -void lookup_paths_free(LookupPaths *p) { - assert(p); - - p->unit_path = strv_free(p->unit_path); - -#ifdef HAVE_SYSV_COMPAT - p->sysvinit_path = strv_free(p->sysvinit_path); - p->sysvrcnd_path = strv_free(p->sysvrcnd_path); -#endif -} - -int lookup_paths_init_from_scope(LookupPaths *paths, - UnitFileScope scope, - const char *root_dir) { - assert(paths); - assert(scope >= 0); - assert(scope < _UNIT_FILE_SCOPE_MAX); - - zero(*paths); - - return lookup_paths_init(paths, - scope == UNIT_FILE_SYSTEM ? MANAGER_SYSTEM : MANAGER_USER, - scope == UNIT_FILE_USER, - root_dir, - NULL, NULL, NULL); -} diff --git a/src/shared/path-lookup.h b/src/shared/path-lookup.h deleted file mode 100644 index 26c83d6111..0000000000 --- a/src/shared/path-lookup.h +++ /dev/null @@ -1,60 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include "macro.h" - -typedef struct LookupPaths { - char **unit_path; -#ifdef HAVE_SYSV_COMPAT - char **sysvinit_path; - char **sysvrcnd_path; -#endif -} LookupPaths; - -typedef enum ManagerRunningAs { - MANAGER_SYSTEM, - MANAGER_USER, - _MANAGER_RUNNING_AS_MAX, - _MANAGER_RUNNING_AS_INVALID = -1 -} ManagerRunningAs; - -int user_config_home(char **config_home); -int user_runtime_dir(char **runtime_dir); - -char **generator_paths(ManagerRunningAs running_as); - -int lookup_paths_init(LookupPaths *p, - ManagerRunningAs running_as, - bool personal, - const char *root_dir, - const char *generator, - const char *generator_early, - const char *generator_late); - -#include "install.h" - -int lookup_paths_init_from_scope(LookupPaths *paths, - UnitFileScope scope, - const char *root_dir); - -void lookup_paths_free(LookupPaths *p); -#define _cleanup_lookup_paths_free_ _cleanup_(lookup_paths_free) diff --git a/src/shared/ptyfwd.c b/src/shared/ptyfwd.c deleted file mode 100644 index 061d31f4de..0000000000 --- a/src/shared/ptyfwd.c +++ /dev/null @@ -1,487 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010-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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-event.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "log.h" -#include "macro.h" -#include "ptyfwd.h" -#include "time-util.h" - -struct PTYForward { - sd_event *event; - - int master; - - PTYForwardFlags flags; - - sd_event_source *stdin_event_source; - sd_event_source *stdout_event_source; - sd_event_source *master_event_source; - - sd_event_source *sigwinch_event_source; - - struct termios saved_stdin_attr; - struct termios saved_stdout_attr; - - bool saved_stdin:1; - bool saved_stdout:1; - - bool stdin_readable:1; - bool stdin_hangup:1; - bool stdout_writable:1; - bool stdout_hangup:1; - bool master_readable:1; - bool master_writable:1; - bool master_hangup:1; - - bool read_from_master:1; - - bool last_char_set:1; - char last_char; - - char in_buffer[LINE_MAX], out_buffer[LINE_MAX]; - size_t in_buffer_full, out_buffer_full; - - usec_t escape_timestamp; - unsigned escape_counter; -}; - -#define ESCAPE_USEC (1*USEC_PER_SEC) - -static bool look_for_escape(PTYForward *f, const char *buffer, size_t n) { - const char *p; - - assert(f); - assert(buffer); - assert(n > 0); - - for (p = buffer; p < buffer + n; p++) { - - /* Check for ^] */ - if (*p == 0x1D) { - usec_t nw = now(CLOCK_MONOTONIC); - - if (f->escape_counter == 0 || nw > f->escape_timestamp + ESCAPE_USEC) { - f->escape_timestamp = nw; - f->escape_counter = 1; - } else { - (f->escape_counter)++; - - if (f->escape_counter >= 3) - return true; - } - } else { - f->escape_timestamp = 0; - f->escape_counter = 0; - } - } - - return false; -} - -static bool ignore_vhangup(PTYForward *f) { - assert(f); - - if (f->flags & PTY_FORWARD_IGNORE_VHANGUP) - return true; - - if ((f->flags & PTY_FORWARD_IGNORE_INITIAL_VHANGUP) && !f->read_from_master) - return true; - - return false; -} - -static int shovel(PTYForward *f) { - ssize_t k; - - assert(f); - - while ((f->stdin_readable && f->in_buffer_full <= 0) || - (f->master_writable && f->in_buffer_full > 0) || - (f->master_readable && f->out_buffer_full <= 0) || - (f->stdout_writable && f->out_buffer_full > 0)) { - - if (f->stdin_readable && f->in_buffer_full < LINE_MAX) { - - k = read(STDIN_FILENO, f->in_buffer + f->in_buffer_full, LINE_MAX - f->in_buffer_full); - if (k < 0) { - - if (errno == EAGAIN) - f->stdin_readable = false; - else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) { - f->stdin_readable = false; - f->stdin_hangup = true; - - f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); - } else { - log_error_errno(errno, "read(): %m"); - return sd_event_exit(f->event, EXIT_FAILURE); - } - } else if (k == 0) { - /* EOF on stdin */ - f->stdin_readable = false; - f->stdin_hangup = true; - - f->stdin_event_source = sd_event_source_unref(f->stdin_event_source); - } else { - /* Check if ^] has been - * pressed three times within - * one second. If we get this - * we quite immediately. */ - if (look_for_escape(f, f->in_buffer + f->in_buffer_full, k)) - return sd_event_exit(f->event, EXIT_FAILURE); - - f->in_buffer_full += (size_t) k; - } - } - - if (f->master_writable && f->in_buffer_full > 0) { - - k = write(f->master, f->in_buffer, f->in_buffer_full); - if (k < 0) { - - if (errno == EAGAIN || errno == EIO) - f->master_writable = false; - else if (errno == EPIPE || errno == ECONNRESET) { - f->master_writable = f->master_readable = false; - f->master_hangup = true; - - f->master_event_source = sd_event_source_unref(f->master_event_source); - } else { - log_error_errno(errno, "write(): %m"); - return sd_event_exit(f->event, EXIT_FAILURE); - } - } else { - assert(f->in_buffer_full >= (size_t) k); - memmove(f->in_buffer, f->in_buffer + k, f->in_buffer_full - k); - f->in_buffer_full -= k; - } - } - - if (f->master_readable && f->out_buffer_full < LINE_MAX) { - - k = read(f->master, f->out_buffer + f->out_buffer_full, LINE_MAX - f->out_buffer_full); - if (k < 0) { - - /* Note that EIO on the master device - * might be caused by vhangup() or - * temporary closing of everything on - * the other side, we treat it like - * EAGAIN here and try again, unless - * ignore_vhangup is off. */ - - if (errno == EAGAIN || (errno == EIO && ignore_vhangup(f))) - f->master_readable = false; - else if (errno == EPIPE || errno == ECONNRESET || errno == EIO) { - f->master_readable = f->master_writable = false; - f->master_hangup = true; - - f->master_event_source = sd_event_source_unref(f->master_event_source); - } else { - log_error_errno(errno, "read(): %m"); - return sd_event_exit(f->event, EXIT_FAILURE); - } - } else { - f->read_from_master = true; - f->out_buffer_full += (size_t) k; - } - } - - if (f->stdout_writable && f->out_buffer_full > 0) { - - k = write(STDOUT_FILENO, f->out_buffer, f->out_buffer_full); - if (k < 0) { - - if (errno == EAGAIN) - f->stdout_writable = false; - else if (errno == EIO || errno == EPIPE || errno == ECONNRESET) { - f->stdout_writable = false; - f->stdout_hangup = true; - f->stdout_event_source = sd_event_source_unref(f->stdout_event_source); - } else { - log_error_errno(errno, "write(): %m"); - return sd_event_exit(f->event, EXIT_FAILURE); - } - - } else { - - if (k > 0) { - f->last_char = f->out_buffer[k-1]; - f->last_char_set = true; - } - - assert(f->out_buffer_full >= (size_t) k); - memmove(f->out_buffer, f->out_buffer + k, f->out_buffer_full - k); - f->out_buffer_full -= k; - } - } - } - - if (f->stdin_hangup || f->stdout_hangup || f->master_hangup) { - /* Exit the loop if any side hung up and if there's - * nothing more to write or nothing we could write. */ - - if ((f->out_buffer_full <= 0 || f->stdout_hangup) && - (f->in_buffer_full <= 0 || f->master_hangup)) - return sd_event_exit(f->event, EXIT_SUCCESS); - } - - return 0; -} - -static int on_master_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { - PTYForward *f = userdata; - - assert(f); - assert(e); - assert(e == f->master_event_source); - assert(fd >= 0); - assert(fd == f->master); - - if (revents & (EPOLLIN|EPOLLHUP)) - f->master_readable = true; - - if (revents & (EPOLLOUT|EPOLLHUP)) - f->master_writable = true; - - return shovel(f); -} - -static int on_stdin_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { - PTYForward *f = userdata; - - assert(f); - assert(e); - assert(e == f->stdin_event_source); - assert(fd >= 0); - assert(fd == STDIN_FILENO); - - if (revents & (EPOLLIN|EPOLLHUP)) - f->stdin_readable = true; - - return shovel(f); -} - -static int on_stdout_event(sd_event_source *e, int fd, uint32_t revents, void *userdata) { - PTYForward *f = userdata; - - assert(f); - assert(e); - assert(e == f->stdout_event_source); - assert(fd >= 0); - assert(fd == STDOUT_FILENO); - - if (revents & (EPOLLOUT|EPOLLHUP)) - f->stdout_writable = true; - - return shovel(f); -} - -static int on_sigwinch_event(sd_event_source *e, const struct signalfd_siginfo *si, void *userdata) { - PTYForward *f = userdata; - struct winsize ws; - - assert(f); - assert(e); - assert(e == f->sigwinch_event_source); - - /* The window size changed, let's forward that. */ - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0) - (void) ioctl(f->master, TIOCSWINSZ, &ws); - - return 0; -} - -int pty_forward_new( - sd_event *event, - int master, - PTYForwardFlags flags, - PTYForward **ret) { - - _cleanup_(pty_forward_freep) PTYForward *f = NULL; - struct winsize ws; - int r; - - f = new0(PTYForward, 1); - if (!f) - return -ENOMEM; - - f->flags = flags; - - if (event) - f->event = sd_event_ref(event); - else { - r = sd_event_default(&f->event); - if (r < 0) - return r; - } - - if (!(flags & PTY_FORWARD_READ_ONLY)) { - r = fd_nonblock(STDIN_FILENO, true); - if (r < 0) - return r; - - r = fd_nonblock(STDOUT_FILENO, true); - if (r < 0) - return r; - } - - r = fd_nonblock(master, true); - if (r < 0) - return r; - - f->master = master; - - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) >= 0) - (void) ioctl(master, TIOCSWINSZ, &ws); - - if (!(flags & PTY_FORWARD_READ_ONLY)) { - if (tcgetattr(STDIN_FILENO, &f->saved_stdin_attr) >= 0) { - struct termios raw_stdin_attr; - - f->saved_stdin = true; - - raw_stdin_attr = f->saved_stdin_attr; - cfmakeraw(&raw_stdin_attr); - raw_stdin_attr.c_oflag = f->saved_stdin_attr.c_oflag; - tcsetattr(STDIN_FILENO, TCSANOW, &raw_stdin_attr); - } - - if (tcgetattr(STDOUT_FILENO, &f->saved_stdout_attr) >= 0) { - struct termios raw_stdout_attr; - - f->saved_stdout = true; - - raw_stdout_attr = f->saved_stdout_attr; - cfmakeraw(&raw_stdout_attr); - raw_stdout_attr.c_iflag = f->saved_stdout_attr.c_iflag; - raw_stdout_attr.c_lflag = f->saved_stdout_attr.c_lflag; - tcsetattr(STDOUT_FILENO, TCSANOW, &raw_stdout_attr); - } - - r = sd_event_add_io(f->event, &f->stdin_event_source, STDIN_FILENO, EPOLLIN|EPOLLET, on_stdin_event, f); - if (r < 0 && r != -EPERM) - return r; - } - - r = sd_event_add_io(f->event, &f->stdout_event_source, STDOUT_FILENO, EPOLLOUT|EPOLLET, on_stdout_event, f); - if (r == -EPERM) - /* stdout without epoll support. Likely redirected to regular file. */ - f->stdout_writable = true; - else if (r < 0) - return r; - - r = sd_event_add_io(f->event, &f->master_event_source, master, EPOLLIN|EPOLLOUT|EPOLLET, on_master_event, f); - if (r < 0) - return r; - - r = sd_event_add_signal(f->event, &f->sigwinch_event_source, SIGWINCH, on_sigwinch_event, f); - if (r < 0) - return r; - - *ret = f; - f = NULL; - - return 0; -} - -PTYForward *pty_forward_free(PTYForward *f) { - - if (f) { - sd_event_source_unref(f->stdin_event_source); - sd_event_source_unref(f->stdout_event_source); - sd_event_source_unref(f->master_event_source); - sd_event_source_unref(f->sigwinch_event_source); - sd_event_unref(f->event); - - if (f->saved_stdout) - tcsetattr(STDOUT_FILENO, TCSANOW, &f->saved_stdout_attr); - if (f->saved_stdin) - tcsetattr(STDIN_FILENO, TCSANOW, &f->saved_stdin_attr); - - free(f); - } - - /* STDIN/STDOUT should not be nonblocking normally, so let's - * unconditionally reset it */ - fd_nonblock(STDIN_FILENO, false); - fd_nonblock(STDOUT_FILENO, false); - - return NULL; -} - -int pty_forward_get_last_char(PTYForward *f, char *ch) { - assert(f); - assert(ch); - - if (!f->last_char_set) - return -ENXIO; - - *ch = f->last_char; - return 0; -} - -int pty_forward_set_ignore_vhangup(PTYForward *f, bool b) { - int r; - - assert(f); - - if (!!(f->flags & PTY_FORWARD_IGNORE_VHANGUP) == b) - return 0; - - if (b) - f->flags |= PTY_FORWARD_IGNORE_VHANGUP; - else - f->flags &= ~PTY_FORWARD_IGNORE_VHANGUP; - - if (!ignore_vhangup(f)) { - - /* We shall now react to vhangup()s? Let's check - * immediately if we might be in one */ - - f->master_readable = true; - r = shovel(f); - if (r < 0) - return r; - } - - return 0; -} - -int pty_forward_get_ignore_vhangup(PTYForward *f) { - assert(f); - - return !!(f->flags & PTY_FORWARD_IGNORE_VHANGUP); -} diff --git a/src/shared/ptyfwd.h b/src/shared/ptyfwd.h deleted file mode 100644 index a046eb4e5e..0000000000 --- a/src/shared/ptyfwd.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010-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 . -***/ - -#include - -#include "sd-event.h" - -#include "macro.h" - -typedef struct PTYForward PTYForward; - -typedef enum PTYForwardFlags { - PTY_FORWARD_READ_ONLY = 1, - - /* Continue reading after hangup? */ - PTY_FORWARD_IGNORE_VHANGUP = 2, - - /* Continue reading after hangup but only if we never read anything else? */ - PTY_FORWARD_IGNORE_INITIAL_VHANGUP = 4, -} PTYForwardFlags; - -int pty_forward_new(sd_event *event, int master, PTYForwardFlags flags, PTYForward **f); -PTYForward *pty_forward_free(PTYForward *f); - -int pty_forward_get_last_char(PTYForward *f, char *ch); - -int pty_forward_set_ignore_vhangup(PTYForward *f, bool ignore_vhangup); -int pty_forward_get_ignore_vhangup(PTYForward *f); - -DEFINE_TRIVIAL_CLEANUP_FUNC(PTYForward*, pty_forward_free); diff --git a/src/shared/resolve-util.c b/src/shared/resolve-util.c deleted file mode 100644 index e2da81bab7..0000000000 --- a/src/shared/resolve-util.c +++ /dev/null @@ -1,39 +0,0 @@ -/*** - 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 . -***/ - -#include "conf-parser.h" -#include "resolve-util.h" -#include "string-table.h" - -DEFINE_CONFIG_PARSE_ENUM(config_parse_resolve_support, resolve_support, ResolveSupport, "Failed to parse resolve support setting"); -DEFINE_CONFIG_PARSE_ENUM(config_parse_dnssec_mode, dnssec_mode, DnssecMode, "Failed to parse DNSSEC mode setting"); - -static const char* const resolve_support_table[_RESOLVE_SUPPORT_MAX] = { - [RESOLVE_SUPPORT_NO] = "no", - [RESOLVE_SUPPORT_YES] = "yes", - [RESOLVE_SUPPORT_RESOLVE] = "resolve", -}; -DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(resolve_support, ResolveSupport, RESOLVE_SUPPORT_YES); - -static const char* const dnssec_mode_table[_DNSSEC_MODE_MAX] = { - [DNSSEC_NO] = "no", - [DNSSEC_ALLOW_DOWNGRADE] = "allow-downgrade", - [DNSSEC_YES] = "yes", -}; -DEFINE_STRING_TABLE_LOOKUP_WITH_BOOLEAN(dnssec_mode, DnssecMode, DNSSEC_YES); diff --git a/src/shared/resolve-util.h b/src/shared/resolve-util.h deleted file mode 100644 index 8636a6c134..0000000000 --- a/src/shared/resolve-util.h +++ /dev/null @@ -1,60 +0,0 @@ -#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 . -***/ - -#include "macro.h" - -typedef enum ResolveSupport ResolveSupport; -typedef enum DnssecMode DnssecMode; - -enum ResolveSupport { - RESOLVE_SUPPORT_NO, - RESOLVE_SUPPORT_YES, - RESOLVE_SUPPORT_RESOLVE, - _RESOLVE_SUPPORT_MAX, - _RESOLVE_SUPPORT_INVALID = -1 -}; - -enum DnssecMode { - /* No DNSSEC validation is done */ - DNSSEC_NO, - - /* Validate locally, if the server knows DO, but if not, - * don't. Don't trust the AD bit. If the server doesn't do - * DNSSEC properly, downgrade to non-DNSSEC operation. Of - * course, we then are vulnerable to a downgrade attack, but - * that's life and what is configured. */ - DNSSEC_ALLOW_DOWNGRADE, - - /* Insist on DNSSEC server support, and rather fail than downgrading. */ - DNSSEC_YES, - - _DNSSEC_MODE_MAX, - _DNSSEC_MODE_INVALID = -1 -}; - -int config_parse_resolve_support(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_dnssec_mode(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); - -const char* resolve_support_to_string(ResolveSupport p) _const_; -ResolveSupport resolve_support_from_string(const char *s) _pure_; - -const char* dnssec_mode_to_string(DnssecMode p) _const_; -DnssecMode dnssec_mode_from_string(const char *s) _pure_; diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c deleted file mode 100644 index cebe0fce2a..0000000000 --- a/src/shared/seccomp-util.c +++ /dev/null @@ -1,90 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include - -#include "macro.h" -#include "seccomp-util.h" -#include "string-util.h" - -const char* seccomp_arch_to_string(uint32_t c) { - - if (c == SCMP_ARCH_NATIVE) - return "native"; - if (c == SCMP_ARCH_X86) - return "x86"; - if (c == SCMP_ARCH_X86_64) - return "x86-64"; - if (c == SCMP_ARCH_X32) - return "x32"; - if (c == SCMP_ARCH_ARM) - return "arm"; - - return NULL; -} - -int seccomp_arch_from_string(const char *n, uint32_t *ret) { - if (!n) - return -EINVAL; - - assert(ret); - - if (streq(n, "native")) - *ret = SCMP_ARCH_NATIVE; - else if (streq(n, "x86")) - *ret = SCMP_ARCH_X86; - else if (streq(n, "x86-64")) - *ret = SCMP_ARCH_X86_64; - else if (streq(n, "x32")) - *ret = SCMP_ARCH_X32; - else if (streq(n, "arm")) - *ret = SCMP_ARCH_ARM; - else - return -EINVAL; - - return 0; -} - -int seccomp_add_secondary_archs(scmp_filter_ctx *c) { - -#if defined(__i386__) || defined(__x86_64__) - int r; - - /* Add in all possible secondary archs we are aware of that - * this kernel might support. */ - - r = seccomp_arch_add(c, SCMP_ARCH_X86); - if (r < 0 && r != -EEXIST) - return r; - - r = seccomp_arch_add(c, SCMP_ARCH_X86_64); - if (r < 0 && r != -EEXIST) - return r; - - r = seccomp_arch_add(c, SCMP_ARCH_X32); - if (r < 0 && r != -EEXIST) - return r; - -#endif - - return 0; - -} diff --git a/src/shared/seccomp-util.h b/src/shared/seccomp-util.h deleted file mode 100644 index 4ed2afc1b2..0000000000 --- a/src/shared/seccomp-util.h +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include - -const char* seccomp_arch_to_string(uint32_t c); -int seccomp_arch_from_string(const char *n, uint32_t *ret); - -int seccomp_add_secondary_archs(scmp_filter_ctx *c); diff --git a/src/shared/sleep-config.c b/src/shared/sleep-config.c deleted file mode 100644 index a0aef66bc8..0000000000 --- a/src/shared/sleep-config.c +++ /dev/null @@ -1,274 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "conf-parser.h" -#include "def.h" -#include "fd-util.h" -#include "fileio.h" -#include "log.h" -#include "macro.h" -#include "parse-util.h" -#include "sleep-config.h" -#include "string-util.h" -#include "strv.h" - -#define USE(x, y) do{ (x) = (y); (y) = NULL; } while(0) - -int parse_sleep_config(const char *verb, char ***_modes, char ***_states) { - - _cleanup_strv_free_ char - **suspend_mode = NULL, **suspend_state = NULL, - **hibernate_mode = NULL, **hibernate_state = NULL, - **hybrid_mode = NULL, **hybrid_state = NULL; - char **modes, **states; - - const ConfigTableItem items[] = { - { "Sleep", "SuspendMode", config_parse_strv, 0, &suspend_mode }, - { "Sleep", "SuspendState", config_parse_strv, 0, &suspend_state }, - { "Sleep", "HibernateMode", config_parse_strv, 0, &hibernate_mode }, - { "Sleep", "HibernateState", config_parse_strv, 0, &hibernate_state }, - { "Sleep", "HybridSleepMode", config_parse_strv, 0, &hybrid_mode }, - { "Sleep", "HybridSleepState", config_parse_strv, 0, &hybrid_state }, - {} - }; - - config_parse_many(PKGSYSCONFDIR "/sleep.conf", - CONF_PATHS_NULSTR("systemd/sleep.conf.d"), - "Sleep\0", config_item_table_lookup, items, - false, NULL); - - if (streq(verb, "suspend")) { - /* empty by default */ - USE(modes, suspend_mode); - - if (suspend_state) - USE(states, suspend_state); - else - states = strv_new("mem", "standby", "freeze", NULL); - - } else if (streq(verb, "hibernate")) { - if (hibernate_mode) - USE(modes, hibernate_mode); - else - modes = strv_new("platform", "shutdown", NULL); - - if (hibernate_state) - USE(states, hibernate_state); - else - states = strv_new("disk", NULL); - - } else if (streq(verb, "hybrid-sleep")) { - if (hybrid_mode) - USE(modes, hybrid_mode); - else - modes = strv_new("suspend", "platform", "shutdown", NULL); - - if (hybrid_state) - USE(states, hybrid_state); - else - states = strv_new("disk", NULL); - - } else - assert_not_reached("what verb"); - - if ((!modes && !streq(verb, "suspend")) || !states) { - strv_free(modes); - strv_free(states); - return log_oom(); - } - - *_modes = modes; - *_states = states; - return 0; -} - -int can_sleep_state(char **types) { - char **type; - int r; - _cleanup_free_ char *p = NULL; - - if (strv_isempty(types)) - return true; - - /* If /sys is read-only we cannot sleep */ - if (access("/sys/power/state", W_OK) < 0) - return false; - - r = read_one_line_file("/sys/power/state", &p); - if (r < 0) - return false; - - STRV_FOREACH(type, types) { - const char *word, *state; - size_t l, k; - - k = strlen(*type); - FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) - if (l == k && memcmp(word, *type, l) == 0) - return true; - } - - return false; -} - -int can_sleep_disk(char **types) { - char **type; - int r; - _cleanup_free_ char *p = NULL; - - if (strv_isempty(types)) - return true; - - /* If /sys is read-only we cannot sleep */ - if (access("/sys/power/disk", W_OK) < 0) - return false; - - r = read_one_line_file("/sys/power/disk", &p); - if (r < 0) - return false; - - STRV_FOREACH(type, types) { - const char *word, *state; - size_t l, k; - - k = strlen(*type); - FOREACH_WORD_SEPARATOR(word, l, p, WHITESPACE, state) { - if (l == k && memcmp(word, *type, l) == 0) - return true; - - if (l == k + 2 && - word[0] == '[' && - memcmp(word + 1, *type, l - 2) == 0 && - word[l-1] == ']') - return true; - } - } - - return false; -} - -#define HIBERNATION_SWAP_THRESHOLD 0.98 - -static int hibernation_partition_size(size_t *size, size_t *used) { - _cleanup_fclose_ FILE *f; - unsigned i; - - assert(size); - assert(used); - - f = fopen("/proc/swaps", "re"); - if (!f) { - log_full(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, - "Failed to retrieve open /proc/swaps: %m"); - assert(errno > 0); - return -errno; - } - - (void) fscanf(f, "%*s %*s %*s %*s %*s\n"); - - for (i = 1;; i++) { - _cleanup_free_ char *dev = NULL, *type = NULL; - size_t size_field, used_field; - int k; - - k = fscanf(f, - "%ms " /* device/file */ - "%ms " /* type of swap */ - "%zu " /* swap size */ - "%zu " /* used */ - "%*i\n", /* priority */ - &dev, &type, &size_field, &used_field); - if (k != 4) { - if (k == EOF) - break; - - log_warning("Failed to parse /proc/swaps:%u", i); - continue; - } - - if (streq(type, "partition") && endswith(dev, "\\040(deleted)")) { - log_warning("Ignoring deleted swapfile '%s'.", dev); - continue; - } - - *size = size_field; - *used = used_field; - return 0; - } - - log_debug("No swap partitions were found."); - return -ENOSYS; -} - -static bool enough_memory_for_hibernation(void) { - _cleanup_free_ char *active = NULL; - unsigned long long act = 0; - size_t size = 0, used = 0; - int r; - - r = hibernation_partition_size(&size, &used); - if (r < 0) - return false; - - r = get_proc_field("/proc/meminfo", "Active(anon)", WHITESPACE, &active); - if (r < 0) { - log_error_errno(r, "Failed to retrieve Active(anon) from /proc/meminfo: %m"); - return false; - } - - r = safe_atollu(active, &act); - if (r < 0) { - log_error_errno(r, "Failed to parse Active(anon) from /proc/meminfo: %s: %m", - active); - return false; - } - - r = act <= (size - used) * HIBERNATION_SWAP_THRESHOLD; - log_debug("Hibernation is %spossible, Active(anon)=%llu kB, size=%zu kB, used=%zu kB, threshold=%.2g%%", - r ? "" : "im", act, size, used, 100*HIBERNATION_SWAP_THRESHOLD); - - return r; -} - -int can_sleep(const char *verb) { - _cleanup_strv_free_ char **modes = NULL, **states = NULL; - int r; - - assert(streq(verb, "suspend") || - streq(verb, "hibernate") || - streq(verb, "hybrid-sleep")); - - r = parse_sleep_config(verb, &modes, &states); - if (r < 0) - return false; - - if (!can_sleep_state(states) || !can_sleep_disk(modes)) - return false; - - return streq(verb, "suspend") || enough_memory_for_hibernation(); -} diff --git a/src/shared/sleep-config.h b/src/shared/sleep-config.h deleted file mode 100644 index 51f4621844..0000000000 --- a/src/shared/sleep-config.h +++ /dev/null @@ -1,26 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#pragma once - -int parse_sleep_config(const char *verb, char ***modes, char ***states); - -int can_sleep(const char *verb); -int can_sleep_disk(char **types); -int can_sleep_state(char **types); diff --git a/src/shared/spawn-ask-password-agent.c b/src/shared/spawn-ask-password-agent.c deleted file mode 100644 index a46b7525f0..0000000000 --- a/src/shared/spawn-ask-password-agent.c +++ /dev/null @@ -1,62 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include - -#include "log.h" -#include "process-util.h" -#include "spawn-ask-password-agent.h" -#include "util.h" - -static pid_t agent_pid = 0; - -int ask_password_agent_open(void) { - int r; - - if (agent_pid > 0) - return 0; - - /* We check STDIN here, not STDOUT, since this is about input, - * not output */ - if (!isatty(STDIN_FILENO)) - return 0; - - r = fork_agent(&agent_pid, - NULL, 0, - SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, - SYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH, "--watch", NULL); - if (r < 0) - return log_error_errno(r, "Failed to fork TTY ask password agent: %m"); - - return 1; -} - -void ask_password_agent_close(void) { - - if (agent_pid <= 0) - return; - - /* Inform agent that we are done */ - (void) kill(agent_pid, SIGTERM); - (void) kill(agent_pid, SIGCONT); - (void) wait_for_terminate(agent_pid, NULL); - agent_pid = 0; -} diff --git a/src/shared/spawn-ask-password-agent.h b/src/shared/spawn-ask-password-agent.h deleted file mode 100644 index fb0749b13f..0000000000 --- a/src/shared/spawn-ask-password-agent.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -int ask_password_agent_open(void); -void ask_password_agent_close(void); diff --git a/src/shared/spawn-polkit-agent.c b/src/shared/spawn-polkit-agent.c deleted file mode 100644 index cf3c8ad5a3..0000000000 --- a/src/shared/spawn-polkit-agent.c +++ /dev/null @@ -1,98 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include -#include -#include - -#include "fd-util.h" -#include "io-util.h" -#include "log.h" -#include "macro.h" -#include "process-util.h" -#include "spawn-polkit-agent.h" -#include "stdio-util.h" -#include "time-util.h" -#include "util.h" - -#ifdef ENABLE_POLKIT -static pid_t agent_pid = 0; - -int polkit_agent_open(void) { - int r; - int pipe_fd[2]; - char notify_fd[DECIMAL_STR_MAX(int) + 1]; - - if (agent_pid > 0) - return 0; - - /* We check STDIN here, not STDOUT, since this is about input, - * not output */ - if (!isatty(STDIN_FILENO)) - return 0; - - if (pipe2(pipe_fd, 0) < 0) - return -errno; - - xsprintf(notify_fd, "%i", pipe_fd[1]); - - r = fork_agent(&agent_pid, - &pipe_fd[1], 1, - POLKIT_AGENT_BINARY_PATH, - POLKIT_AGENT_BINARY_PATH, "--notify-fd", notify_fd, "--fallback", NULL); - - /* Close the writing side, because that's the one for the agent */ - safe_close(pipe_fd[1]); - - if (r < 0) - log_error_errno(r, "Failed to fork TTY ask password agent: %m"); - else - /* Wait until the agent closes the fd */ - fd_wait_for_event(pipe_fd[0], POLLHUP, USEC_INFINITY); - - safe_close(pipe_fd[0]); - - return r; -} - -void polkit_agent_close(void) { - - if (agent_pid <= 0) - return; - - /* Inform agent that we are done */ - (void) kill(agent_pid, SIGTERM); - (void) kill(agent_pid, SIGCONT); - - (void) wait_for_terminate(agent_pid, NULL); - agent_pid = 0; -} - -#else - -int polkit_agent_open(void) { - return 0; -} - -void polkit_agent_close(void) { -} - -#endif diff --git a/src/shared/spawn-polkit-agent.h b/src/shared/spawn-polkit-agent.h deleted file mode 100644 index 42b2989ded..0000000000 --- a/src/shared/spawn-polkit-agent.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -int polkit_agent_open(void); -void polkit_agent_close(void); diff --git a/src/shared/specifier.c b/src/shared/specifier.c deleted file mode 100644 index 1c17eb5251..0000000000 --- a/src/shared/specifier.c +++ /dev/null @@ -1,188 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "sd-id128.h" - -#include "alloc-util.h" -#include "hostname-util.h" -#include "macro.h" -#include "specifier.h" -#include "string-util.h" - -/* - * Generic infrastructure for replacing %x style specifiers in - * strings. Will call a callback for each replacement. - * - */ - -int specifier_printf(const char *text, const Specifier table[], void *userdata, char **_ret) { - char *ret, *t; - const char *f; - bool percent = false; - size_t l; - int r; - - assert(text); - assert(table); - - l = strlen(text); - ret = new(char, l+1); - if (!ret) - return -ENOMEM; - - t = ret; - - for (f = text; *f; f++, l--) { - - if (percent) { - if (*f == '%') - *(t++) = '%'; - else { - const Specifier *i; - - for (i = table; i->specifier; i++) - if (i->specifier == *f) - break; - - if (i->lookup) { - _cleanup_free_ char *w = NULL; - char *n; - size_t k, j; - - r = i->lookup(i->specifier, i->data, userdata, &w); - if (r < 0) { - free(ret); - return r; - } - - j = t - ret; - k = strlen(w); - - n = new(char, j + k + l + 1); - if (!n) { - free(ret); - return -ENOMEM; - } - - memcpy(n, ret, j); - memcpy(n + j, w, k); - - free(ret); - - ret = n; - t = n + j + k; - } else { - *(t++) = '%'; - *(t++) = *f; - } - } - - percent = false; - } else if (*f == '%') - percent = true; - else - *(t++) = *f; - } - - *t = 0; - *_ret = ret; - return 0; -} - -/* Generic handler for simple string replacements */ - -int specifier_string(char specifier, void *data, void *userdata, char **ret) { - char *n; - - n = strdup(strempty(data)); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} - -int specifier_machine_id(char specifier, void *data, void *userdata, char **ret) { - sd_id128_t id; - char *n; - int r; - - r = sd_id128_get_machine(&id); - if (r < 0) - return r; - - n = new(char, 33); - if (!n) - return -ENOMEM; - - *ret = sd_id128_to_string(id, n); - return 0; -} - -int specifier_boot_id(char specifier, void *data, void *userdata, char **ret) { - sd_id128_t id; - char *n; - int r; - - r = sd_id128_get_boot(&id); - if (r < 0) - return r; - - n = new(char, 33); - if (!n) - return -ENOMEM; - - *ret = sd_id128_to_string(id, n); - return 0; -} - -int specifier_host_name(char specifier, void *data, void *userdata, char **ret) { - char *n; - - n = gethostname_malloc(); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} - -int specifier_kernel_release(char specifier, void *data, void *userdata, char **ret) { - struct utsname uts; - char *n; - int r; - - r = uname(&uts); - if (r < 0) - return -errno; - - n = strdup(uts.release); - if (!n) - return -ENOMEM; - - *ret = n; - return 0; -} diff --git a/src/shared/specifier.h b/src/shared/specifier.h deleted file mode 100644 index 6b1623ee61..0000000000 --- a/src/shared/specifier.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -typedef int (*SpecifierCallback)(char specifier, void *data, void *userdata, char **ret); - -typedef struct Specifier { - const char specifier; - const SpecifierCallback lookup; - void *data; -} Specifier; - -int specifier_printf(const char *text, const Specifier table[], void *userdata, char **ret); - -int specifier_string(char specifier, void *data, void *userdata, char **ret); - -int specifier_machine_id(char specifier, void *data, void *userdata, char **ret); -int specifier_boot_id(char specifier, void *data, void *userdata, char **ret); -int specifier_host_name(char specifier, void *data, void *userdata, char **ret); -int specifier_kernel_release(char specifier, void *data, void *userdata, char **ret); diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c deleted file mode 100644 index 47d3a5a1fa..0000000000 --- a/src/shared/switch-root.c +++ /dev/null @@ -1,156 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 Harald Hoyer, 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "base-filesystem.h" -#include "fd-util.h" -#include "log.h" -#include "missing.h" -#include "mkdir.h" -#include "path-util.h" -#include "rm-rf.h" -#include "stdio-util.h" -#include "string-util.h" -#include "switch-root.h" -#include "user-util.h" -#include "util.h" - -int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags) { - - /* Don't try to unmount/move the old "/", there's no way to do it. */ - static const char move_mounts[] = - "/dev\0" - "/proc\0" - "/sys\0" - "/run\0"; - - _cleanup_close_ int old_root_fd = -1; - struct stat new_root_stat; - bool old_root_remove; - const char *i, *temporary_old_root; - - if (path_equal(new_root, "/")) - return 0; - - temporary_old_root = strjoina(new_root, oldroot); - mkdir_p_label(temporary_old_root, 0755); - - old_root_remove = in_initrd(); - - if (stat(new_root, &new_root_stat) < 0) - return log_error_errno(errno, "Failed to stat directory %s: %m", new_root); - - /* Work-around for kernel design: the kernel refuses switching - * root if any file systems are mounted MS_SHARED. Hence - * remount them MS_PRIVATE here as a work-around. - * - * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */ - if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0) - log_warning_errno(errno, "Failed to make \"/\" private mount: %m"); - - NULSTR_FOREACH(i, move_mounts) { - char new_mount[PATH_MAX]; - struct stat sb; - - xsprintf(new_mount, "%s%s", new_root, i); - - mkdir_p_label(new_mount, 0755); - - if ((stat(new_mount, &sb) < 0) || - sb.st_dev != new_root_stat.st_dev) { - - /* Mount point seems to be mounted already or - * stat failed. Unmount the old mount - * point. */ - if (umount2(i, MNT_DETACH) < 0) - log_warning_errno(errno, "Failed to unmount %s: %m", i); - continue; - } - - if (mount(i, new_mount, NULL, mountflags, NULL) < 0) { - if (mountflags & MS_MOVE) { - log_error_errno(errno, "Failed to move mount %s to %s, forcing unmount: %m", i, new_mount); - - if (umount2(i, MNT_FORCE) < 0) - log_warning_errno(errno, "Failed to unmount %s: %m", i); - } - if (mountflags & MS_BIND) - log_error_errno(errno, "Failed to bind mount %s to %s: %m", i, new_mount); - - } - } - - /* Do not fail, if base_filesystem_create() fails. Not all - * switch roots are like base_filesystem_create() wants them - * to look like. They might even boot, if they are RO and - * don't have the FS layout. Just ignore the error and - * switch_root() nevertheless. */ - (void) base_filesystem_create(new_root, UID_INVALID, GID_INVALID); - - if (chdir(new_root) < 0) - return log_error_errno(errno, "Failed to change directory to %s: %m", new_root); - - if (old_root_remove) { - old_root_fd = open("/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY|O_DIRECTORY); - if (old_root_fd < 0) - log_warning_errno(errno, "Failed to open root directory: %m"); - } - - /* We first try a pivot_root() so that we can umount the old - * root dir. In many cases (i.e. where rootfs is /), that's - * not possible however, and hence we simply overmount root */ - if (pivot_root(new_root, temporary_old_root) >= 0) { - - /* Immediately get rid of the old root, if detach_oldroot is set. - * Since we are running off it we need to do this lazily. */ - if (detach_oldroot && umount2(oldroot, MNT_DETACH) < 0) - log_error_errno(errno, "Failed to lazily umount old root dir %s, %s: %m", - oldroot, - errno == ENOENT ? "ignoring" : "leaving it around"); - - } else if (mount(new_root, "/", NULL, MS_MOVE, NULL) < 0) - return log_error_errno(errno, "Failed to mount moving %s to /: %m", new_root); - - if (chroot(".") < 0) - return log_error_errno(errno, "Failed to change root: %m"); - - if (chdir("/") < 0) - return log_error_errno(errno, "Failed to change directory: %m"); - - if (old_root_fd >= 0) { - struct stat rb; - - if (fstat(old_root_fd, &rb) < 0) - log_warning_errno(errno, "Failed to stat old root directory, leaving: %m"); - else { - (void) rm_rf_children(old_root_fd, 0, &rb); - old_root_fd = -1; - } - } - - return 0; -} diff --git a/src/shared/switch-root.h b/src/shared/switch-root.h deleted file mode 100644 index a7a080b3e8..0000000000 --- a/src/shared/switch-root.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -/*** - This file is part of systemd. - - Copyright 2012 Harald Hoyer, 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 . -***/ - -int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags); diff --git a/src/shared/sysctl-util.c b/src/shared/sysctl-util.c deleted file mode 100644 index e1ccb3294c..0000000000 --- a/src/shared/sysctl-util.c +++ /dev/null @@ -1,73 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "fileio.h" -#include "log.h" -#include "macro.h" -#include "string-util.h" -#include "sysctl-util.h" - -char *sysctl_normalize(char *s) { - char *n; - - n = strpbrk(s, "/."); - /* If the first separator is a slash, the path is - * assumed to be normalized and slashes remain slashes - * and dots remains dots. */ - if (!n || *n == '/') - return s; - - /* Otherwise, dots become slashes and slashes become - * dots. Fun. */ - while (n) { - if (*n == '.') - *n = '/'; - else - *n = '.'; - - n = strpbrk(n + 1, "/."); - } - - return s; -} - -int sysctl_write(const char *property, const char *value) { - char *p; - - assert(property); - assert(value); - - log_debug("Setting '%s' to '%s'", property, value); - - p = strjoina("/proc/sys/", property); - return write_string_file(p, value, 0); -} - -int sysctl_read(const char *property, char **content) { - char *p; - - assert(property); - assert(content); - - p = strjoina("/proc/sys/", property); - return read_full_file(p, content, NULL); -} diff --git a/src/shared/sysctl-util.h b/src/shared/sysctl-util.h deleted file mode 100644 index 2decb39f58..0000000000 --- a/src/shared/sysctl-util.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -char *sysctl_normalize(char *s); -int sysctl_read(const char *property, char **value); -int sysctl_write(const char *property, const char *value); - diff --git a/src/shared/test-tables.h b/src/shared/test-tables.h deleted file mode 100644 index 228e510104..0000000000 --- a/src/shared/test-tables.h +++ /dev/null @@ -1,60 +0,0 @@ -/*** - This file is part of systemd - - Copyright 2013 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 . -***/ - -#include -#include - -typedef const char* (*lookup_t)(int); -typedef int (*reverse_t)(const char*); - -static inline void _test_table(const char *name, - lookup_t lookup, - reverse_t reverse, - int size, - bool sparse) { - int i, boring = 0; - - for (i = -1; i < size + 1; i++) { - const char* val = lookup(i); - int rev; - - if (val) { - rev = reverse(val); - boring = 0; - } else { - rev = reverse("--no-such--value----"); - boring += i >= 0; - } - - if (boring < 1 || i == size) - printf("%s: %d → %s → %d\n", name, i, val, rev); - else if (boring == 1) - printf("%*s ...\n", (int) strlen(name), ""); - - assert_se(!(i >= 0 && i < size ? - sparse ? rev != i && rev != -1 : val == NULL || rev != i : - val != NULL || rev != -1)); - } -} - -#define test_table(lower, upper) \ - _test_table(STRINGIFY(lower), lower##_to_string, lower##_from_string, _##upper##_MAX, false) - -#define test_table_sparse(lower, upper) \ - _test_table(STRINGIFY(lower), lower##_to_string, lower##_from_string, _##upper##_MAX, true) diff --git a/src/shared/udev-util.h b/src/shared/udev-util.h deleted file mode 100644 index ca0889f8a6..0000000000 --- a/src/shared/udev-util.h +++ /dev/null @@ -1,44 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2013 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 . -***/ - -#include "udev.h" -#include "util.h" - -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev*, udev_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_device*, udev_device_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_enumerate*, udev_enumerate_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_event*, udev_event_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_rules*, udev_rules_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl*, udev_ctrl_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl_connection*, udev_ctrl_connection_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_ctrl_msg*, udev_ctrl_msg_unref); -DEFINE_TRIVIAL_CLEANUP_FUNC(struct udev_monitor*, udev_monitor_unref); - -#define _cleanup_udev_unref_ _cleanup_(udev_unrefp) -#define _cleanup_udev_device_unref_ _cleanup_(udev_device_unrefp) -#define _cleanup_udev_enumerate_unref_ _cleanup_(udev_enumerate_unrefp) -#define _cleanup_udev_event_unref_ _cleanup_(udev_event_unrefp) -#define _cleanup_udev_rules_unref_ _cleanup_(udev_rules_unrefp) -#define _cleanup_udev_ctrl_unref_ _cleanup_(udev_ctrl_unrefp) -#define _cleanup_udev_ctrl_connection_unref_ _cleanup_(udev_ctrl_connection_unrefp) -#define _cleanup_udev_ctrl_msg_unref_ _cleanup_(udev_ctrl_msg_unrefp) -#define _cleanup_udev_monitor_unref_ _cleanup_(udev_monitor_unrefp) -#define _cleanup_udev_list_cleanup_ _cleanup_(udev_list_cleanup) diff --git a/src/shared/uid-range.c b/src/shared/uid-range.c deleted file mode 100644 index eb251492c3..0000000000 --- a/src/shared/uid-range.c +++ /dev/null @@ -1,208 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include -#include - -#include "macro.h" -#include "uid-range.h" -#include "user-util.h" - -static bool uid_range_intersect(UidRange *range, uid_t start, uid_t nr) { - assert(range); - - return range->start <= start + nr && - range->start + range->nr >= start; -} - -static void uid_range_coalesce(UidRange **p, unsigned *n) { - unsigned i, j; - - assert(p); - assert(n); - - for (i = 0; i < *n; i++) { - for (j = i + 1; j < *n; j++) { - UidRange *x = (*p)+i, *y = (*p)+j; - - if (uid_range_intersect(x, y->start, y->nr)) { - uid_t begin, end; - - begin = MIN(x->start, y->start); - end = MAX(x->start + x->nr, y->start + y->nr); - - x->start = begin; - x->nr = end - begin; - - if (*n > j+1) - memmove(y, y+1, sizeof(UidRange) * (*n - j -1)); - - (*n) --; - j--; - } - } - } - -} - -static int uid_range_compare(const void *a, const void *b) { - const UidRange *x = a, *y = b; - - if (x->start < y->start) - return -1; - if (x->start > y->start) - return 1; - - if (x->nr < y->nr) - return -1; - if (x->nr > y->nr) - return 1; - - return 0; -} - -int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr) { - bool found = false; - UidRange *x; - unsigned i; - - assert(p); - assert(n); - - if (nr <= 0) - return 0; - - for (i = 0; i < *n; i++) { - x = (*p) + i; - if (uid_range_intersect(x, start, nr)) { - found = true; - break; - } - } - - if (found) { - uid_t begin, end; - - begin = MIN(x->start, start); - end = MAX(x->start + x->nr, start + nr); - - x->start = begin; - x->nr = end - begin; - } else { - UidRange *t; - - t = realloc(*p, sizeof(UidRange) * (*n + 1)); - if (!t) - return -ENOMEM; - - *p = t; - x = t + ((*n) ++); - - x->start = start; - x->nr = nr; - } - - qsort(*p, *n, sizeof(UidRange), uid_range_compare); - uid_range_coalesce(p, n); - - return *n; -} - -int uid_range_add_str(UidRange **p, unsigned *n, const char *s) { - uid_t start, nr; - const char *t; - int r; - - assert(p); - assert(n); - assert(s); - - t = strchr(s, '-'); - if (t) { - char *b; - uid_t end; - - b = strndupa(s, t - s); - r = parse_uid(b, &start); - if (r < 0) - return r; - - r = parse_uid(t+1, &end); - if (r < 0) - return r; - - if (end < start) - return -EINVAL; - - nr = end - start + 1; - } else { - r = parse_uid(s, &start); - if (r < 0) - return r; - - nr = 1; - } - - return uid_range_add(p, n, start, nr); -} - -int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid) { - uid_t closest = UID_INVALID, candidate; - unsigned i; - - assert(p); - assert(uid); - - candidate = *uid - 1; - - for (i = 0; i < n; i++) { - uid_t begin, end; - - begin = p[i].start; - end = p[i].start + p[i].nr - 1; - - if (candidate >= begin && candidate <= end) { - *uid = candidate; - return 1; - } - - if (end < candidate) - closest = end; - } - - if (closest == UID_INVALID) - return -EBUSY; - - *uid = closest; - return 1; -} - -bool uid_range_contains(const UidRange *p, unsigned n, uid_t uid) { - unsigned i; - - assert(p); - assert(uid); - - for (i = 0; i < n; i++) - if (uid >= p[i].start && uid < p[i].start + p[i].nr) - return true; - - return false; -} diff --git a/src/shared/uid-range.h b/src/shared/uid-range.h deleted file mode 100644 index 4044eb4c9c..0000000000 --- a/src/shared/uid-range.h +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include -#include - -typedef struct UidRange { - uid_t start, nr; -} UidRange; - -int uid_range_add(UidRange **p, unsigned *n, uid_t start, uid_t nr); -int uid_range_add_str(UidRange **p, unsigned *n, const char *s); - -int uid_range_next_lower(const UidRange *p, unsigned n, uid_t *uid); -bool uid_range_contains(const UidRange *p, unsigned n, uid_t uid); diff --git a/src/shared/utmp-wtmp.c b/src/shared/utmp-wtmp.c deleted file mode 100644 index 9750dcd817..0000000000 --- a/src/shared/utmp-wtmp.c +++ /dev/null @@ -1,445 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "hostname-util.h" -#include "macro.h" -#include "path-util.h" -#include "string-util.h" -#include "terminal-util.h" -#include "time-util.h" -#include "user-util.h" -#include "util.h" -#include "utmp-wtmp.h" - -int utmp_get_runlevel(int *runlevel, int *previous) { - struct utmpx *found, lookup = { .ut_type = RUN_LVL }; - int r; - const char *e; - - assert(runlevel); - - /* If these values are set in the environment this takes - * precedence. Presumably, sysvinit does this to work around a - * race condition that would otherwise exist where we'd always - * go to disk and hence might read runlevel data that might be - * very new and does not apply to the current script being - * executed. */ - - e = getenv("RUNLEVEL"); - if (e && e[0] > 0) { - *runlevel = e[0]; - - if (previous) { - /* $PREVLEVEL seems to be an Upstart thing */ - - e = getenv("PREVLEVEL"); - if (e && e[0] > 0) - *previous = e[0]; - else - *previous = 0; - } - - return 0; - } - - if (utmpxname(_PATH_UTMPX) < 0) - return -errno; - - setutxent(); - - found = getutxid(&lookup); - if (!found) - r = -errno; - else { - int a, b; - - a = found->ut_pid & 0xFF; - b = (found->ut_pid >> 8) & 0xFF; - - *runlevel = a; - if (previous) - *previous = b; - - r = 0; - } - - endutxent(); - - return r; -} - -static void init_timestamp(struct utmpx *store, usec_t t) { - assert(store); - - if (t <= 0) - t = now(CLOCK_REALTIME); - - store->ut_tv.tv_sec = t / USEC_PER_SEC; - store->ut_tv.tv_usec = t % USEC_PER_SEC; -} - -static void init_entry(struct utmpx *store, usec_t t) { - struct utsname uts = {}; - - assert(store); - - init_timestamp(store, t); - - if (uname(&uts) >= 0) - strncpy(store->ut_host, uts.release, sizeof(store->ut_host)); - - strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */ - strncpy(store->ut_id, "~~", sizeof(store->ut_id)); -} - -static int write_entry_utmp(const struct utmpx *store) { - int r; - - assert(store); - - /* utmp is similar to wtmp, but there is only one entry for - * each entry type resp. user; i.e. basically a key/value - * table. */ - - if (utmpxname(_PATH_UTMPX) < 0) - return -errno; - - setutxent(); - - if (!pututxline(store)) - r = -errno; - else - r = 0; - - endutxent(); - - return r; -} - -static int write_entry_wtmp(const struct utmpx *store) { - assert(store); - - /* wtmp is a simple append-only file where each entry is - simply appended to the end; i.e. basically a log. */ - - errno = 0; - updwtmpx(_PATH_WTMPX, store); - return -errno; -} - -static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) { - int r, s; - - r = write_entry_utmp(store_utmp); - s = write_entry_wtmp(store_wtmp); - - if (r >= 0) - r = s; - - /* If utmp/wtmp have been disabled, that's a good thing, hence - * ignore the errors */ - if (r == -ENOENT) - r = 0; - - return r; -} - -static int write_entry_both(const struct utmpx *store) { - return write_utmp_wtmp(store, store); -} - -int utmp_put_shutdown(void) { - struct utmpx store = {}; - - init_entry(&store, 0); - - store.ut_type = RUN_LVL; - strncpy(store.ut_user, "shutdown", sizeof(store.ut_user)); - - return write_entry_both(&store); -} - -int utmp_put_reboot(usec_t t) { - struct utmpx store = {}; - - init_entry(&store, t); - - store.ut_type = BOOT_TIME; - strncpy(store.ut_user, "reboot", sizeof(store.ut_user)); - - return write_entry_both(&store); -} - -_pure_ static const char *sanitize_id(const char *id) { - size_t l; - - assert(id); - l = strlen(id); - - if (l <= sizeof(((struct utmpx*) NULL)->ut_id)) - return id; - - return id + l - sizeof(((struct utmpx*) NULL)->ut_id); -} - -int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) { - struct utmpx store = { - .ut_type = INIT_PROCESS, - .ut_pid = pid, - .ut_session = sid, - }; - int r; - - assert(id); - - init_timestamp(&store, 0); - - /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */ - strncpy(store.ut_id, sanitize_id(id), sizeof(store.ut_id)); - - if (line) - strncpy(store.ut_line, basename(line), sizeof(store.ut_line)); - - r = write_entry_both(&store); - if (r < 0) - return r; - - if (ut_type == LOGIN_PROCESS || ut_type == USER_PROCESS) { - store.ut_type = LOGIN_PROCESS; - r = write_entry_both(&store); - if (r < 0) - return r; - } - - if (ut_type == USER_PROCESS) { - store.ut_type = USER_PROCESS; - strncpy(store.ut_user, user, sizeof(store.ut_user)-1); - r = write_entry_both(&store); - if (r < 0) - return r; - } - - return 0; -} - -int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) { - struct utmpx lookup = { - .ut_type = INIT_PROCESS /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */ - }, store, store_wtmp, *found; - - assert(id); - - setutxent(); - - /* ut_id needs only be nul-terminated if it is shorter than sizeof(ut_id) */ - strncpy(lookup.ut_id, sanitize_id(id), sizeof(lookup.ut_id)); - - found = getutxid(&lookup); - if (!found) - return 0; - - if (found->ut_pid != pid) - return 0; - - memcpy(&store, found, sizeof(store)); - store.ut_type = DEAD_PROCESS; - store.ut_exit.e_termination = code; - store.ut_exit.e_exit = status; - - zero(store.ut_user); - zero(store.ut_host); - zero(store.ut_tv); - - memcpy(&store_wtmp, &store, sizeof(store_wtmp)); - /* wtmp wants the current time */ - init_timestamp(&store_wtmp, 0); - - return write_utmp_wtmp(&store, &store_wtmp); -} - - -int utmp_put_runlevel(int runlevel, int previous) { - struct utmpx store = {}; - int r; - - assert(runlevel > 0); - - if (previous <= 0) { - /* Find the old runlevel automatically */ - - r = utmp_get_runlevel(&previous, NULL); - if (r < 0) { - if (r != -ESRCH) - return r; - - previous = 0; - } - } - - if (previous == runlevel) - return 0; - - init_entry(&store, 0); - - store.ut_type = RUN_LVL; - store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8); - strncpy(store.ut_user, "runlevel", sizeof(store.ut_user)); - - return write_entry_both(&store); -} - -#define TIMEOUT_MSEC 50 - -static int write_to_terminal(const char *tty, const char *message) { - _cleanup_close_ int fd = -1; - const char *p; - size_t left; - usec_t end; - - assert(tty); - assert(message); - - fd = open(tty, O_WRONLY|O_NDELAY|O_NOCTTY|O_CLOEXEC); - if (fd < 0 || !isatty(fd)) - return -errno; - - p = message; - left = strlen(message); - - end = now(CLOCK_MONOTONIC) + TIMEOUT_MSEC*USEC_PER_MSEC; - - while (left > 0) { - ssize_t n; - struct pollfd pollfd = { - .fd = fd, - .events = POLLOUT, - }; - usec_t t; - int k; - - t = now(CLOCK_MONOTONIC); - - if (t >= end) - return -ETIME; - - k = poll(&pollfd, 1, (end - t) / USEC_PER_MSEC); - if (k < 0) - return -errno; - - if (k == 0) - return -ETIME; - - n = write(fd, p, left); - if (n < 0) { - if (errno == EAGAIN) - continue; - - return -errno; - } - - assert((size_t) n <= left); - - p += n; - left -= n; - } - - return 0; -} - -int utmp_wall( - const char *message, - const char *username, - const char *origin_tty, - bool (*match_tty)(const char *tty, void *userdata), - void *userdata) { - - _cleanup_free_ char *text = NULL, *hn = NULL, *un = NULL, *stdin_tty = NULL; - char date[FORMAT_TIMESTAMP_MAX]; - struct utmpx *u; - int r; - - hn = gethostname_malloc(); - if (!hn) - return -ENOMEM; - if (!username) { - un = getlogname_malloc(); - if (!un) - return -ENOMEM; - } - - if (!origin_tty) { - getttyname_harder(STDIN_FILENO, &stdin_tty); - origin_tty = stdin_tty; - } - - if (asprintf(&text, - "\a\r\n" - "Broadcast message from %s@%s%s%s (%s):\r\n\r\n" - "%s\r\n\r\n", - un ?: username, hn, - origin_tty ? " on " : "", strempty(origin_tty), - format_timestamp(date, sizeof(date), now(CLOCK_REALTIME)), - message) < 0) - return -ENOMEM; - - setutxent(); - - r = 0; - - while ((u = getutxent())) { - _cleanup_free_ char *buf = NULL; - const char *path; - int q; - - if (u->ut_type != USER_PROCESS || u->ut_user[0] == 0) - continue; - - /* this access is fine, because strlen("/dev/") << 32 (UT_LINESIZE) */ - if (path_startswith(u->ut_line, "/dev/")) - path = u->ut_line; - else { - if (asprintf(&buf, "/dev/%.*s", (int) sizeof(u->ut_line), u->ut_line) < 0) - return -ENOMEM; - - path = buf; - } - - if (!match_tty || match_tty(path, userdata)) { - q = write_to_terminal(path, text); - if (q < 0) - r = q; - } - } - - return r; -} diff --git a/src/shared/utmp-wtmp.h b/src/shared/utmp-wtmp.h deleted file mode 100644 index 438e270a26..0000000000 --- a/src/shared/utmp-wtmp.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "time-util.h" -#include "util.h" - -#ifdef HAVE_UTMP -int utmp_get_runlevel(int *runlevel, int *previous); - -int utmp_put_shutdown(void); -int utmp_put_reboot(usec_t timestamp); -int utmp_put_runlevel(int runlevel, int previous); - -int utmp_put_dead_process(const char *id, pid_t pid, int code, int status); -int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user); - -int utmp_wall( - const char *message, - const char *username, - const char *origin_tty, - bool (*match_tty)(const char *tty, void *userdata), - void *userdata); - -#else /* HAVE_UTMP */ - -static inline int utmp_get_runlevel(int *runlevel, int *previous) { - return -ESRCH; -} -static inline int utmp_put_shutdown(void) { - return 0; -} -static inline int utmp_put_reboot(usec_t timestamp) { - return 0; -} -static inline int utmp_put_runlevel(int runlevel, int previous) { - return 0; -} -static inline int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) { - return 0; -} -static inline int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) { - return 0; -} -static inline int utmp_wall( - const char *message, - const char *username, - const char *origin_tty, - bool (*match_tty)(const char *tty, void *userdata), - void *userdata) { - return 0; -} - -#endif /* HAVE_UTMP */ diff --git a/src/shared/watchdog.c b/src/shared/watchdog.c deleted file mode 100644 index 4f3e0125f3..0000000000 --- a/src/shared/watchdog.c +++ /dev/null @@ -1,164 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include -#include -#include -#include -#include -#include - -#include "fd-util.h" -#include "log.h" -#include "time-util.h" -#include "watchdog.h" - -static int watchdog_fd = -1; -static usec_t watchdog_timeout = USEC_INFINITY; - -static int update_timeout(void) { - int r; - - if (watchdog_fd < 0) - return 0; - - if (watchdog_timeout == USEC_INFINITY) - return 0; - else if (watchdog_timeout == 0) { - int flags; - - flags = WDIOS_DISABLECARD; - r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); - if (r < 0) - return log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); - } else { - int sec, flags; - char buf[FORMAT_TIMESPAN_MAX]; - - sec = (int) ((watchdog_timeout + USEC_PER_SEC - 1) / USEC_PER_SEC); - r = ioctl(watchdog_fd, WDIOC_SETTIMEOUT, &sec); - if (r < 0) - return log_warning_errno(errno, "Failed to set timeout to %is: %m", sec); - - watchdog_timeout = (usec_t) sec * USEC_PER_SEC; - log_info("Set hardware watchdog to %s.", format_timespan(buf, sizeof(buf), watchdog_timeout, 0)); - - flags = WDIOS_ENABLECARD; - r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); - if (r < 0) { - /* ENOTTY means the watchdog is always enabled so we're fine */ - log_full(errno == ENOTTY ? LOG_DEBUG : LOG_WARNING, - "Failed to enable hardware watchdog: %m"); - if (errno != ENOTTY) - return -errno; - } - - r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); - if (r < 0) - return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); - } - - return 0; -} - -static int open_watchdog(void) { - struct watchdog_info ident; - - if (watchdog_fd >= 0) - return 0; - - watchdog_fd = open("/dev/watchdog", O_WRONLY|O_CLOEXEC); - if (watchdog_fd < 0) - return -errno; - - if (ioctl(watchdog_fd, WDIOC_GETSUPPORT, &ident) >= 0) - log_info("Hardware watchdog '%s', version %x", - ident.identity, - ident.firmware_version); - - return update_timeout(); -} - -int watchdog_set_timeout(usec_t *usec) { - int r; - - watchdog_timeout = *usec; - - /* If we didn't open the watchdog yet and didn't get any - * explicit timeout value set, don't do anything */ - if (watchdog_fd < 0 && watchdog_timeout == USEC_INFINITY) - return 0; - - if (watchdog_fd < 0) - r = open_watchdog(); - else - r = update_timeout(); - - *usec = watchdog_timeout; - - return r; -} - -int watchdog_ping(void) { - int r; - - if (watchdog_fd < 0) { - r = open_watchdog(); - if (r < 0) - return r; - } - - r = ioctl(watchdog_fd, WDIOC_KEEPALIVE, 0); - if (r < 0) - return log_warning_errno(errno, "Failed to ping hardware watchdog: %m"); - - return 0; -} - -void watchdog_close(bool disarm) { - int r; - - if (watchdog_fd < 0) - return; - - if (disarm) { - int flags; - - /* Explicitly disarm it */ - flags = WDIOS_DISABLECARD; - r = ioctl(watchdog_fd, WDIOC_SETOPTIONS, &flags); - if (r < 0) - log_warning_errno(errno, "Failed to disable hardware watchdog: %m"); - - /* To be sure, use magic close logic, too */ - for (;;) { - static const char v = 'V'; - - if (write(watchdog_fd, &v, 1) > 0) - break; - - if (errno != EINTR) { - log_error_errno(errno, "Failed to disarm watchdog timer: %m"); - break; - } - } - } - - watchdog_fd = safe_close(watchdog_fd); -} diff --git a/src/shared/watchdog.h b/src/shared/watchdog.h deleted file mode 100644 index f6ec178ea1..0000000000 --- a/src/shared/watchdog.h +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include - -#include "time-util.h" -#include "util.h" - -int watchdog_set_timeout(usec_t *usec); -int watchdog_ping(void); -void watchdog_close(bool disarm); diff --git a/src/sleep/Makefile b/src/sleep/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/sleep/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/sleep/Makefile b/src/sleep/Makefile new file mode 100644 index 0000000000..2d8854bbf9 --- /dev/null +++ b/src/sleep/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_sleep_SOURCES = \ + src/sleep/sleep.c + +systemd_sleep_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/sleep/sleep.c b/src/sleep/sleep.c index c8f0742183..7f8a95728d 100644 --- a/src/sleep/sleep.c +++ b/src/sleep/sleep.c @@ -22,7 +22,7 @@ #include #include -#include "sd-messages.h" +#include #include "def.h" #include "fd-util.h" diff --git a/src/socket-proxy/Makefile b/src/socket-proxy/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/socket-proxy/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/socket-proxy/Makefile b/src/socket-proxy/Makefile new file mode 100644 index 0000000000..cead4f37a9 --- /dev/null +++ b/src/socket-proxy/Makefile @@ -0,0 +1,33 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + + +systemd_socket_proxyd_SOURCES = \ + src/socket-proxy/socket-proxyd.c + +systemd_socket_proxyd_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/socket-proxy/socket-proxyd.c b/src/socket-proxy/socket-proxyd.c index 99d4b62139..4be8d0507f 100644 --- a/src/socket-proxy/socket-proxyd.c +++ b/src/socket-proxy/socket-proxyd.c @@ -28,9 +28,9 @@ #include #include -#include "sd-daemon.h" -#include "sd-event.h" -#include "sd-resolve.h" +#include +#include +#include #include "alloc-util.h" #include "fd-util.h" diff --git a/src/sysctl/Makefile b/src/sysctl/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/sysctl/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/sysctl/Makefile b/src/sysctl/Makefile new file mode 100644 index 0000000000..31d38d9104 --- /dev/null +++ b/src/sysctl/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_sysctl_SOURCES = \ + src/sysctl/sysctl.c + +systemd_sysctl_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/system-update-generator/Makefile b/src/system-update-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/system-update-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/system-update-generator/Makefile b/src/system-update-generator/Makefile new file mode 100644 index 0000000000..62f750c182 --- /dev/null +++ b/src/system-update-generator/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_system_update_generator_SOURCES = \ + src/system-update-generator/system-update-generator.c + +systemd_system_update_generator_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/system/systemctl/Makefile b/src/system/systemctl/Makefile new file mode 100644 index 0000000000..5cb8c922d9 --- /dev/null +++ b/src/system/systemctl/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemctl_SOURCES = \ + src/systemctl/systemctl.c + +systemctl_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/system/systemctl/systemctl.c b/src/system/systemctl/systemctl.c new file mode 100644 index 0000000000..c03fca06c8 --- /dev/null +++ b/src/system/systemctl/systemctl.c @@ -0,0 +1,7703 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + Copyright 2013 Marc-Antoine Perennou + + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "bus-message.h" +#include "bus-util.h" +#include "cgroup-show.h" +#include "cgroup-util.h" +#include "copy.h" +#include "dropin.h" +#include "efivars.h" +#include "env-util.h" +#include "exit-status.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "glob-util.h" +#include "hostname-util.h" +#include "initreq.h" +#include "install.h" +#include "io-util.h" +#include "list.h" +#include "locale-util.h" +#include "log.h" +#include "logs-show.h" +#include "macro.h" +#include "mkdir.h" +#include "pager.h" +#include "parse-util.h" +#include "path-lookup.h" +#include "path-util.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "set.h" +#include "signal-util.h" +#include "socket-util.h" +#include "spawn-ask-password-agent.h" +#include "spawn-polkit-agent.h" +#include "special.h" +#include "stat-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "user-util.h" +#include "util.h" +#include "utmp-wtmp.h" +#include "verbs.h" +#include "virt.h" + +static char **arg_types = NULL; +static char **arg_states = NULL; +static char **arg_properties = NULL; +static bool arg_all = false; +static enum dependency { + DEPENDENCY_FORWARD, + DEPENDENCY_REVERSE, + DEPENDENCY_AFTER, + DEPENDENCY_BEFORE, + _DEPENDENCY_MAX +} arg_dependency = DEPENDENCY_FORWARD; +static const char *arg_job_mode = "replace"; +static UnitFileScope arg_scope = UNIT_FILE_SYSTEM; +static bool arg_no_block = false; +static bool arg_no_legend = false; +static bool arg_no_pager = false; +static bool arg_no_wtmp = false; +static bool arg_no_wall = false; +static bool arg_no_reload = false; +static bool arg_show_types = false; +static bool arg_ignore_inhibitors = false; +static bool arg_dry = false; +static bool arg_quiet = false; +static bool arg_full = false; +static bool arg_recursive = false; +static int arg_force = 0; +static bool arg_ask_password = false; +static bool arg_runtime = false; +static UnitFilePresetMode arg_preset_mode = UNIT_FILE_PRESET_FULL; +static char **arg_wall = NULL; +static const char *arg_kill_who = NULL; +static int arg_signal = SIGTERM; +static char *arg_root = NULL; +static usec_t arg_when = 0; +static enum action { + _ACTION_INVALID, + ACTION_SYSTEMCTL, + ACTION_HALT, + ACTION_POWEROFF, + ACTION_REBOOT, + ACTION_KEXEC, + ACTION_EXIT, + ACTION_SUSPEND, + ACTION_HIBERNATE, + ACTION_HYBRID_SLEEP, + ACTION_RUNLEVEL2, + ACTION_RUNLEVEL3, + ACTION_RUNLEVEL4, + ACTION_RUNLEVEL5, + ACTION_RESCUE, + ACTION_EMERGENCY, + ACTION_DEFAULT, + ACTION_RELOAD, + ACTION_REEXEC, + ACTION_RUNLEVEL, + ACTION_CANCEL_SHUTDOWN, + _ACTION_MAX +} arg_action = ACTION_SYSTEMCTL; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static const char *arg_host = NULL; +static unsigned arg_lines = 10; +static OutputMode arg_output = OUTPUT_SHORT; +static bool arg_plain = false; +static bool arg_firmware_setup = false; +static bool arg_now = false; + +static int daemon_reload(int argc, char *argv[], void* userdata); +static int halt_now(enum action a); +static int check_one_unit(sd_bus *bus, const char *name, const char *good_states, bool quiet); + +static bool original_stdout_is_tty; + +typedef enum BusFocus { + BUS_FULL, /* The full bus indicated via --system or --user */ + BUS_MANAGER, /* The manager itself, possibly directly, possibly via the bus */ + _BUS_FOCUS_MAX +} BusFocus; + +static sd_bus *busses[_BUS_FOCUS_MAX] = {}; + +static int acquire_bus(BusFocus focus, sd_bus **ret) { + int r; + + assert(focus < _BUS_FOCUS_MAX); + assert(ret); + + /* We only go directly to the manager, if we are using a local transport */ + if (arg_transport != BUS_TRANSPORT_LOCAL) + focus = BUS_FULL; + + if (!busses[focus]) { + bool user; + + user = arg_scope != UNIT_FILE_SYSTEM; + + if (focus == BUS_MANAGER) + r = bus_connect_transport_systemd(arg_transport, arg_host, user, &busses[focus]); + else + r = bus_connect_transport(arg_transport, arg_host, user, &busses[focus]); + if (r < 0) + return log_error_errno(r, "Failed to connect to bus: %m"); + + (void) sd_bus_set_allow_interactive_authorization(busses[focus], arg_ask_password); + } + + *ret = busses[focus]; + return 0; +} + +static void release_busses(void) { + BusFocus w; + + for (w = 0; w < _BUS_FOCUS_MAX; w++) + busses[w] = sd_bus_flush_close_unref(busses[w]); +} + +static void pager_open_if_enabled(void) { + + if (arg_no_pager) + return; + + pager_open(false); +} + +static void ask_password_agent_open_if_enabled(void) { + + /* Open the password agent as a child process if necessary */ + + if (!arg_ask_password) + return; + + if (arg_scope != UNIT_FILE_SYSTEM) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + ask_password_agent_open(); +} + +static void polkit_agent_open_if_enabled(void) { + + /* Open the polkit agent as a child process if necessary */ + + if (!arg_ask_password) + return; + + if (arg_scope != UNIT_FILE_SYSTEM) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + polkit_agent_open(); +} + +static OutputFlags get_output_flags(void) { + return + arg_all * OUTPUT_SHOW_ALL | + arg_full * OUTPUT_FULL_WIDTH | + (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | + colors_enabled() * OUTPUT_COLOR | + !arg_quiet * OUTPUT_WARN_CUTOFF; +} + +static int translate_bus_error_to_exit_status(int r, const sd_bus_error *error) { + assert(error); + + if (!sd_bus_error_is_set(error)) + return r; + + if (sd_bus_error_has_name(error, SD_BUS_ERROR_ACCESS_DENIED) || + sd_bus_error_has_name(error, BUS_ERROR_ONLY_BY_DEPENDENCY) || + sd_bus_error_has_name(error, BUS_ERROR_NO_ISOLATION) || + sd_bus_error_has_name(error, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE)) + return EXIT_NOPERMISSION; + + if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT)) + return EXIT_NOTINSTALLED; + + if (sd_bus_error_has_name(error, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE) || + sd_bus_error_has_name(error, SD_BUS_ERROR_NOT_SUPPORTED)) + return EXIT_NOTIMPLEMENTED; + + if (sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) + return EXIT_NOTCONFIGURED; + + if (r != 0) + return r; + + return EXIT_FAILURE; +} + +static bool install_client_side(void) { + + /* Decides when to execute enable/disable/... operations + * client-side rather than server-side. */ + + if (running_in_chroot() > 0) + return true; + + if (sd_booted() <= 0) + return true; + + if (!isempty(arg_root)) + return true; + + if (arg_scope == UNIT_FILE_GLOBAL) + return true; + + /* Unsupported environment variable, mostly for debugging purposes */ + if (getenv_bool("SYSTEMCTL_INSTALL_CLIENT_SIDE") > 0) + return true; + + return false; +} + +static int compare_unit_info(const void *a, const void *b) { + const UnitInfo *u = a, *v = b; + const char *d1, *d2; + int r; + + /* First, order by machine */ + if (!u->machine && v->machine) + return -1; + if (u->machine && !v->machine) + return 1; + if (u->machine && v->machine) { + r = strcasecmp(u->machine, v->machine); + if (r != 0) + return r; + } + + /* Second, order by unit type */ + d1 = strrchr(u->id, '.'); + d2 = strrchr(v->id, '.'); + if (d1 && d2) { + r = strcasecmp(d1, d2); + if (r != 0) + return r; + } + + /* Third, order by name */ + return strcasecmp(u->id, v->id); +} + +static bool output_show_unit(const UnitInfo *u, char **patterns) { + if (!strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE)) + return false; + + if (arg_types) { + const char *dot; + + dot = strrchr(u->id, '.'); + if (!dot) + return false; + + if (!strv_find(arg_types, dot+1)) + return false; + } + + if (arg_all) + return true; + + if (u->job_id > 0) + return true; + + if (streq(u->active_state, "inactive") || u->following[0]) + return false; + + return true; +} + +static int output_units_list(const UnitInfo *unit_infos, unsigned c) { + unsigned circle_len = 0, id_len, max_id_len, load_len, active_len, sub_len, job_len, desc_len; + const UnitInfo *u; + unsigned n_shown = 0; + int job_count = 0; + + max_id_len = strlen("UNIT"); + load_len = strlen("LOAD"); + active_len = strlen("ACTIVE"); + sub_len = strlen("SUB"); + job_len = strlen("JOB"); + desc_len = 0; + + for (u = unit_infos; u < unit_infos + c; u++) { + max_id_len = MAX(max_id_len, strlen(u->id) + (u->machine ? strlen(u->machine)+1 : 0)); + load_len = MAX(load_len, strlen(u->load_state)); + active_len = MAX(active_len, strlen(u->active_state)); + sub_len = MAX(sub_len, strlen(u->sub_state)); + + if (u->job_id != 0) { + job_len = MAX(job_len, strlen(u->job_type)); + job_count++; + } + + if (!arg_no_legend && + (streq(u->active_state, "failed") || + STR_IN_SET(u->load_state, "error", "not-found", "masked"))) + circle_len = 2; + } + + if (!arg_full && original_stdout_is_tty) { + unsigned basic_len; + + id_len = MIN(max_id_len, 25u); + basic_len = circle_len + 5 + id_len + 5 + active_len + sub_len; + + if (job_count) + basic_len += job_len + 1; + + if (basic_len < (unsigned) columns()) { + unsigned extra_len, incr; + extra_len = columns() - basic_len; + + /* Either UNIT already got 25, or is fully satisfied. + * Grant up to 25 to DESC now. */ + incr = MIN(extra_len, 25u); + desc_len += incr; + extra_len -= incr; + + /* split the remaining space between UNIT and DESC, + * but do not give UNIT more than it needs. */ + if (extra_len > 0) { + incr = MIN(extra_len / 2, max_id_len - id_len); + id_len += incr; + desc_len += extra_len - incr; + } + } + } else + id_len = max_id_len; + + for (u = unit_infos; u < unit_infos + c; u++) { + _cleanup_free_ char *e = NULL, *j = NULL; + const char *on_loaded = "", *off_loaded = ""; + const char *on_active = "", *off_active = ""; + const char *on_circle = "", *off_circle = ""; + const char *id; + bool circle = false; + + if (!n_shown && !arg_no_legend) { + + if (circle_len > 0) + fputs(" ", stdout); + + printf("%-*s %-*s %-*s %-*s ", + id_len, "UNIT", + load_len, "LOAD", + active_len, "ACTIVE", + sub_len, "SUB"); + + if (job_count) + printf("%-*s ", job_len, "JOB"); + + if (!arg_full && arg_no_pager) + printf("%.*s\n", desc_len, "DESCRIPTION"); + else + printf("%s\n", "DESCRIPTION"); + } + + n_shown++; + + if (STR_IN_SET(u->load_state, "error", "not-found", "masked") && !arg_plain) { + on_loaded = ansi_highlight_red(); + on_circle = ansi_highlight_yellow(); + off_loaded = off_circle = ansi_normal(); + circle = true; + } else if (streq(u->active_state, "failed") && !arg_plain) { + on_circle = on_active = ansi_highlight_red(); + off_circle = off_active = ansi_normal(); + circle = true; + } + + if (u->machine) { + j = strjoin(u->machine, ":", u->id, NULL); + if (!j) + return log_oom(); + + id = j; + } else + id = u->id; + + if (arg_full) { + e = ellipsize(id, id_len, 33); + if (!e) + return log_oom(); + + id = e; + } + + if (circle_len > 0) + printf("%s%s%s ", on_circle, circle ? draw_special_char(DRAW_BLACK_CIRCLE) : " ", off_circle); + + printf("%s%-*s%s %s%-*s%s %s%-*s %-*s%s %-*s", + on_active, id_len, id, off_active, + on_loaded, load_len, u->load_state, off_loaded, + on_active, active_len, u->active_state, + sub_len, u->sub_state, off_active, + job_count ? job_len + 1 : 0, u->job_id ? u->job_type : ""); + + if (desc_len > 0) + printf("%.*s\n", desc_len, u->description); + else + printf("%s\n", u->description); + } + + if (!arg_no_legend) { + const char *on, *off; + + if (n_shown) { + puts("\n" + "LOAD = Reflects whether the unit definition was properly loaded.\n" + "ACTIVE = The high-level unit activation state, i.e. generalization of SUB.\n" + "SUB = The low-level unit activation state, values depend on unit type."); + puts(job_count ? "JOB = Pending job for the unit.\n" : ""); + on = ansi_highlight(); + off = ansi_normal(); + } else { + on = ansi_highlight_red(); + off = ansi_normal(); + } + + if (arg_all) + printf("%s%u loaded units listed.%s\n" + "To show all installed unit files use 'systemctl list-unit-files'.\n", + on, n_shown, off); + else + printf("%s%u loaded units listed.%s Pass --all to see loaded but inactive units, too.\n" + "To show all installed unit files use 'systemctl list-unit-files'.\n", + on, n_shown, off); + } + + return 0; +} + +static int get_unit_list( + sd_bus *bus, + const char *machine, + char **patterns, + UnitInfo **unit_infos, + int c, + sd_bus_message **_reply) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + size_t size = c; + int r; + UnitInfo u; + + assert(bus); + assert(unit_infos); + assert(_reply); + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnitsFiltered"); + + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, arg_states); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = bus_parse_unit_info(reply, &u)) > 0) { + u.machine = machine; + + if (!output_show_unit(&u, patterns)) + continue; + + if (!GREEDY_REALLOC(*unit_infos, size, c+1)) + return log_oom(); + + (*unit_infos)[c++] = u; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + *_reply = reply; + reply = NULL; + + return c; +} + +static void message_set_freep(Set **set) { + sd_bus_message *m; + + while ((m = set_steal_first(*set))) + sd_bus_message_unref(m); + + set_free(*set); +} + +static int get_unit_list_recursive( + sd_bus *bus, + char **patterns, + UnitInfo **_unit_infos, + Set **_replies, + char ***_machines) { + + _cleanup_free_ UnitInfo *unit_infos = NULL; + _cleanup_(message_set_freep) Set *replies; + sd_bus_message *reply; + int c, r; + + assert(bus); + assert(_replies); + assert(_unit_infos); + assert(_machines); + + replies = set_new(NULL); + if (!replies) + return log_oom(); + + c = get_unit_list(bus, NULL, patterns, &unit_infos, 0, &reply); + if (c < 0) + return c; + + r = set_put(replies, reply); + if (r < 0) { + sd_bus_message_unref(reply); + return log_oom(); + } + + if (arg_recursive) { + _cleanup_strv_free_ char **machines = NULL; + char **i; + + r = sd_get_machine_names(&machines); + if (r < 0) + return log_error_errno(r, "Failed to get machine names: %m"); + + STRV_FOREACH(i, machines) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL; + int k; + + r = sd_bus_open_system_machine(&container, *i); + if (r < 0) { + log_warning_errno(r, "Failed to connect to container %s, ignoring: %m", *i); + continue; + } + + k = get_unit_list(container, *i, patterns, &unit_infos, c, &reply); + if (k < 0) + return k; + + c = k; + + r = set_put(replies, reply); + if (r < 0) { + sd_bus_message_unref(reply); + return log_oom(); + } + } + + *_machines = machines; + machines = NULL; + } else + *_machines = NULL; + + *_unit_infos = unit_infos; + unit_infos = NULL; + + *_replies = replies; + replies = NULL; + + return c; +} + +static int list_units(int argc, char *argv[], void *userdata) { + _cleanup_free_ UnitInfo *unit_infos = NULL; + _cleanup_(message_set_freep) Set *replies = NULL; + _cleanup_strv_free_ char **machines = NULL; + sd_bus *bus; + int r; + + pager_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); + if (r < 0) + return r; + + qsort_safe(unit_infos, r, sizeof(UnitInfo), compare_unit_info); + return output_units_list(unit_infos, r); +} + +static int get_triggered_units( + sd_bus *bus, + const char* path, + char*** ret) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + assert(ret); + + r = sd_bus_get_property_strv( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "Triggers", + &error, + ret); + if (r < 0) + return log_error_errno(r, "Failed to determine triggers: %s", bus_error_message(&error, r)); + + return 0; +} + +static int get_listening( + sd_bus *bus, + const char* unit_path, + char*** listening) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *type, *path; + int r, n = 0; + + r = sd_bus_get_property( + bus, + "org.freedesktop.systemd1", + unit_path, + "org.freedesktop.systemd1.Socket", + "Listen", + &error, + &reply, + "a(ss)"); + if (r < 0) + return log_error_errno(r, "Failed to get list of listening sockets: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ss)", &type, &path)) > 0) { + + r = strv_extend(listening, type); + if (r < 0) + return log_oom(); + + r = strv_extend(listening, path); + if (r < 0) + return log_oom(); + + n++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return n; +} + +struct socket_info { + const char *machine; + const char* id; + + char* type; + char* path; + + /* Note: triggered is a list here, although it almost certainly + * will always be one unit. Nevertheless, dbus API allows for multiple + * values, so let's follow that. */ + char** triggered; + + /* The strv above is shared. free is set only in the first one. */ + bool own_triggered; +}; + +static int socket_info_compare(const struct socket_info *a, const struct socket_info *b) { + int o; + + assert(a); + assert(b); + + if (!a->machine && b->machine) + return -1; + if (a->machine && !b->machine) + return 1; + if (a->machine && b->machine) { + o = strcasecmp(a->machine, b->machine); + if (o != 0) + return o; + } + + o = strcmp(a->path, b->path); + if (o == 0) + o = strcmp(a->type, b->type); + + return o; +} + +static int output_sockets_list(struct socket_info *socket_infos, unsigned cs) { + struct socket_info *s; + unsigned pathlen = strlen("LISTEN"), + typelen = strlen("TYPE") * arg_show_types, + socklen = strlen("UNIT"), + servlen = strlen("ACTIVATES"); + const char *on, *off; + + for (s = socket_infos; s < socket_infos + cs; s++) { + unsigned tmp = 0; + char **a; + + socklen = MAX(socklen, strlen(s->id)); + if (arg_show_types) + typelen = MAX(typelen, strlen(s->type)); + pathlen = MAX(pathlen, strlen(s->path) + (s->machine ? strlen(s->machine)+1 : 0)); + + STRV_FOREACH(a, s->triggered) + tmp += strlen(*a) + 2*(a != s->triggered); + servlen = MAX(servlen, tmp); + } + + if (cs) { + if (!arg_no_legend) + printf("%-*s %-*.*s%-*s %s\n", + pathlen, "LISTEN", + typelen + arg_show_types, typelen + arg_show_types, "TYPE ", + socklen, "UNIT", + "ACTIVATES"); + + for (s = socket_infos; s < socket_infos + cs; s++) { + _cleanup_free_ char *j = NULL; + const char *path; + char **a; + + if (s->machine) { + j = strjoin(s->machine, ":", s->path, NULL); + if (!j) + return log_oom(); + path = j; + } else + path = s->path; + + if (arg_show_types) + printf("%-*s %-*s %-*s", + pathlen, path, typelen, s->type, socklen, s->id); + else + printf("%-*s %-*s", + pathlen, path, socklen, s->id); + STRV_FOREACH(a, s->triggered) + printf("%s %s", + a == s->triggered ? "" : ",", *a); + printf("\n"); + } + + on = ansi_highlight(); + off = ansi_normal(); + if (!arg_no_legend) + printf("\n"); + } else { + on = ansi_highlight_red(); + off = ansi_normal(); + } + + if (!arg_no_legend) { + printf("%s%u sockets listed.%s\n", on, cs, off); + if (!arg_all) + printf("Pass --all to see loaded but inactive sockets, too.\n"); + } + + return 0; +} + +static int list_sockets(int argc, char *argv[], void *userdata) { + _cleanup_(message_set_freep) Set *replies = NULL; + _cleanup_strv_free_ char **machines = NULL; + _cleanup_free_ UnitInfo *unit_infos = NULL; + _cleanup_free_ struct socket_info *socket_infos = NULL; + const UnitInfo *u; + struct socket_info *s; + unsigned cs = 0; + size_t size = 0; + int r = 0, n; + sd_bus *bus; + + pager_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + n = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); + if (n < 0) + return n; + + for (u = unit_infos; u < unit_infos + n; u++) { + _cleanup_strv_free_ char **listening = NULL, **triggered = NULL; + int i, c; + + if (!endswith(u->id, ".socket")) + continue; + + r = get_triggered_units(bus, u->unit_path, &triggered); + if (r < 0) + goto cleanup; + + c = get_listening(bus, u->unit_path, &listening); + if (c < 0) { + r = c; + goto cleanup; + } + + if (!GREEDY_REALLOC(socket_infos, size, cs + c)) { + r = log_oom(); + goto cleanup; + } + + for (i = 0; i < c; i++) + socket_infos[cs + i] = (struct socket_info) { + .machine = u->machine, + .id = u->id, + .type = listening[i*2], + .path = listening[i*2 + 1], + .triggered = triggered, + .own_triggered = i==0, + }; + + /* from this point on we will cleanup those socket_infos */ + cs += c; + free(listening); + listening = triggered = NULL; /* avoid cleanup */ + } + + qsort_safe(socket_infos, cs, sizeof(struct socket_info), + (__compar_fn_t) socket_info_compare); + + output_sockets_list(socket_infos, cs); + + cleanup: + assert(cs == 0 || socket_infos); + for (s = socket_infos; s < socket_infos + cs; s++) { + free(s->type); + free(s->path); + if (s->own_triggered) + strv_free(s->triggered); + } + + return r; +} + +static int get_next_elapse( + sd_bus *bus, + const char *path, + dual_timestamp *next) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + dual_timestamp t; + int r; + + assert(bus); + assert(path); + assert(next); + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Timer", + "NextElapseUSecMonotonic", + &error, + 't', + &t.monotonic); + if (r < 0) + return log_error_errno(r, "Failed to get next elapsation time: %s", bus_error_message(&error, r)); + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Timer", + "NextElapseUSecRealtime", + &error, + 't', + &t.realtime); + if (r < 0) + return log_error_errno(r, "Failed to get next elapsation time: %s", bus_error_message(&error, r)); + + *next = t; + return 0; +} + +static int get_last_trigger( + sd_bus *bus, + const char *path, + usec_t *last) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + assert(last); + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Timer", + "LastTriggerUSec", + &error, + 't', + last); + if (r < 0) + return log_error_errno(r, "Failed to get last trigger time: %s", bus_error_message(&error, r)); + + return 0; +} + +struct timer_info { + const char* machine; + const char* id; + usec_t next_elapse; + usec_t last_trigger; + char** triggered; +}; + +static int timer_info_compare(const struct timer_info *a, const struct timer_info *b) { + int o; + + assert(a); + assert(b); + + if (!a->machine && b->machine) + return -1; + if (a->machine && !b->machine) + return 1; + if (a->machine && b->machine) { + o = strcasecmp(a->machine, b->machine); + if (o != 0) + return o; + } + + if (a->next_elapse < b->next_elapse) + return -1; + if (a->next_elapse > b->next_elapse) + return 1; + + return strcmp(a->id, b->id); +} + +static int output_timers_list(struct timer_info *timer_infos, unsigned n) { + struct timer_info *t; + unsigned + nextlen = strlen("NEXT"), + leftlen = strlen("LEFT"), + lastlen = strlen("LAST"), + passedlen = strlen("PASSED"), + unitlen = strlen("UNIT"), + activatelen = strlen("ACTIVATES"); + + const char *on, *off; + + assert(timer_infos || n == 0); + + for (t = timer_infos; t < timer_infos + n; t++) { + unsigned ul = 0; + char **a; + + if (t->next_elapse > 0) { + char tstamp[FORMAT_TIMESTAMP_MAX] = "", trel[FORMAT_TIMESTAMP_RELATIVE_MAX] = ""; + + format_timestamp(tstamp, sizeof(tstamp), t->next_elapse); + nextlen = MAX(nextlen, strlen(tstamp) + 1); + + format_timestamp_relative(trel, sizeof(trel), t->next_elapse); + leftlen = MAX(leftlen, strlen(trel)); + } + + if (t->last_trigger > 0) { + char tstamp[FORMAT_TIMESTAMP_MAX] = "", trel[FORMAT_TIMESTAMP_RELATIVE_MAX] = ""; + + format_timestamp(tstamp, sizeof(tstamp), t->last_trigger); + lastlen = MAX(lastlen, strlen(tstamp) + 1); + + format_timestamp_relative(trel, sizeof(trel), t->last_trigger); + passedlen = MAX(passedlen, strlen(trel)); + } + + unitlen = MAX(unitlen, strlen(t->id) + (t->machine ? strlen(t->machine)+1 : 0)); + + STRV_FOREACH(a, t->triggered) + ul += strlen(*a) + 2*(a != t->triggered); + + activatelen = MAX(activatelen, ul); + } + + if (n > 0) { + if (!arg_no_legend) + printf("%-*s %-*s %-*s %-*s %-*s %s\n", + nextlen, "NEXT", + leftlen, "LEFT", + lastlen, "LAST", + passedlen, "PASSED", + unitlen, "UNIT", + "ACTIVATES"); + + for (t = timer_infos; t < timer_infos + n; t++) { + _cleanup_free_ char *j = NULL; + const char *unit; + char tstamp1[FORMAT_TIMESTAMP_MAX] = "n/a", trel1[FORMAT_TIMESTAMP_RELATIVE_MAX] = "n/a"; + char tstamp2[FORMAT_TIMESTAMP_MAX] = "n/a", trel2[FORMAT_TIMESTAMP_RELATIVE_MAX] = "n/a"; + char **a; + + format_timestamp(tstamp1, sizeof(tstamp1), t->next_elapse); + format_timestamp_relative(trel1, sizeof(trel1), t->next_elapse); + + format_timestamp(tstamp2, sizeof(tstamp2), t->last_trigger); + format_timestamp_relative(trel2, sizeof(trel2), t->last_trigger); + + if (t->machine) { + j = strjoin(t->machine, ":", t->id, NULL); + if (!j) + return log_oom(); + unit = j; + } else + unit = t->id; + + printf("%-*s %-*s %-*s %-*s %-*s", + nextlen, tstamp1, leftlen, trel1, lastlen, tstamp2, passedlen, trel2, unitlen, unit); + + STRV_FOREACH(a, t->triggered) + printf("%s %s", + a == t->triggered ? "" : ",", *a); + printf("\n"); + } + + on = ansi_highlight(); + off = ansi_normal(); + if (!arg_no_legend) + printf("\n"); + } else { + on = ansi_highlight_red(); + off = ansi_normal(); + } + + if (!arg_no_legend) { + printf("%s%u timers listed.%s\n", on, n, off); + if (!arg_all) + printf("Pass --all to see loaded but inactive timers, too.\n"); + } + + return 0; +} + +static usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next) { + usec_t next_elapse; + + assert(nw); + assert(next); + + if (next->monotonic != USEC_INFINITY && next->monotonic > 0) { + usec_t converted; + + if (next->monotonic > nw->monotonic) + converted = nw->realtime + (next->monotonic - nw->monotonic); + else + converted = nw->realtime - (nw->monotonic - next->monotonic); + + if (next->realtime != USEC_INFINITY && next->realtime > 0) + next_elapse = MIN(converted, next->realtime); + else + next_elapse = converted; + + } else + next_elapse = next->realtime; + + return next_elapse; +} + +static int list_timers(int argc, char *argv[], void *userdata) { + _cleanup_(message_set_freep) Set *replies = NULL; + _cleanup_strv_free_ char **machines = NULL; + _cleanup_free_ struct timer_info *timer_infos = NULL; + _cleanup_free_ UnitInfo *unit_infos = NULL; + struct timer_info *t; + const UnitInfo *u; + size_t size = 0; + int n, c = 0; + dual_timestamp nw; + sd_bus *bus; + int r = 0; + + pager_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + n = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); + if (n < 0) + return n; + + dual_timestamp_get(&nw); + + for (u = unit_infos; u < unit_infos + n; u++) { + _cleanup_strv_free_ char **triggered = NULL; + dual_timestamp next = DUAL_TIMESTAMP_NULL; + usec_t m, last = 0; + + if (!endswith(u->id, ".timer")) + continue; + + r = get_triggered_units(bus, u->unit_path, &triggered); + if (r < 0) + goto cleanup; + + r = get_next_elapse(bus, u->unit_path, &next); + if (r < 0) + goto cleanup; + + get_last_trigger(bus, u->unit_path, &last); + + if (!GREEDY_REALLOC(timer_infos, size, c+1)) { + r = log_oom(); + goto cleanup; + } + + m = calc_next_elapse(&nw, &next); + + timer_infos[c++] = (struct timer_info) { + .machine = u->machine, + .id = u->id, + .next_elapse = m, + .last_trigger = last, + .triggered = triggered, + }; + + triggered = NULL; /* avoid cleanup */ + } + + qsort_safe(timer_infos, c, sizeof(struct timer_info), + (__compar_fn_t) timer_info_compare); + + output_timers_list(timer_infos, c); + + cleanup: + for (t = timer_infos; t < timer_infos + c; t++) + strv_free(t->triggered); + + return r; +} + +static int compare_unit_file_list(const void *a, const void *b) { + const char *d1, *d2; + const UnitFileList *u = a, *v = b; + + d1 = strrchr(u->path, '.'); + d2 = strrchr(v->path, '.'); + + if (d1 && d2) { + int r; + + r = strcasecmp(d1, d2); + if (r != 0) + return r; + } + + return strcasecmp(basename(u->path), basename(v->path)); +} + +static bool output_show_unit_file(const UnitFileList *u, char **patterns) { + if (!strv_fnmatch_or_empty(patterns, basename(u->path), FNM_NOESCAPE)) + return false; + + if (!strv_isempty(arg_types)) { + const char *dot; + + dot = strrchr(u->path, '.'); + if (!dot) + return false; + + if (!strv_find(arg_types, dot+1)) + return false; + } + + if (!strv_isempty(arg_states) && + !strv_find(arg_states, unit_file_state_to_string(u->state))) + return false; + + return true; +} + +static void output_unit_file_list(const UnitFileList *units, unsigned c) { + unsigned max_id_len, id_cols, state_cols; + const UnitFileList *u; + + max_id_len = strlen("UNIT FILE"); + state_cols = strlen("STATE"); + + for (u = units; u < units + c; u++) { + max_id_len = MAX(max_id_len, strlen(basename(u->path))); + state_cols = MAX(state_cols, strlen(unit_file_state_to_string(u->state))); + } + + if (!arg_full) { + unsigned basic_cols; + + id_cols = MIN(max_id_len, 25u); + basic_cols = 1 + id_cols + state_cols; + if (basic_cols < (unsigned) columns()) + id_cols += MIN(columns() - basic_cols, max_id_len - id_cols); + } else + id_cols = max_id_len; + + if (!arg_no_legend) + printf("%-*s %-*s\n", + id_cols, "UNIT FILE", + state_cols, "STATE"); + + for (u = units; u < units + c; u++) { + _cleanup_free_ char *e = NULL; + const char *on, *off; + const char *id; + + if (IN_SET(u->state, + UNIT_FILE_MASKED, + UNIT_FILE_MASKED_RUNTIME, + UNIT_FILE_DISABLED, + UNIT_FILE_BAD)) { + on = ansi_highlight_red(); + off = ansi_normal(); + } else if (u->state == UNIT_FILE_ENABLED) { + on = ansi_highlight_green(); + off = ansi_normal(); + } else + on = off = ""; + + id = basename(u->path); + + e = arg_full ? NULL : ellipsize(id, id_cols, 33); + + printf("%-*s %s%-*s%s\n", + id_cols, e ? e : id, + on, state_cols, unit_file_state_to_string(u->state), off); + } + + if (!arg_no_legend) + printf("\n%u unit files listed.\n", c); +} + +static int list_unit_files(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ UnitFileList *units = NULL; + UnitFileList *unit; + size_t size = 0; + unsigned c = 0; + const char *state; + char *path; + int r; + + pager_open_if_enabled(); + + if (install_client_side()) { + Hashmap *h; + UnitFileList *u; + Iterator i; + unsigned n_units; + + h = hashmap_new(&string_hash_ops); + if (!h) + return log_oom(); + + r = unit_file_get_list(arg_scope, arg_root, h); + if (r < 0) { + unit_file_list_free(h); + return log_error_errno(r, "Failed to get unit file list: %m"); + } + + n_units = hashmap_size(h); + + units = new(UnitFileList, n_units); + if (!units && n_units > 0) { + unit_file_list_free(h); + return log_oom(); + } + + HASHMAP_FOREACH(u, h, i) { + if (!output_show_unit_file(u, strv_skip(argv, 1))) + continue; + + units[c++] = *u; + free(u); + } + + assert(c <= n_units); + hashmap_free(h); + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnitFiles", + &error, + &reply, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to list unit files: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ss)", &path, &state)) > 0) { + + if (!GREEDY_REALLOC(units, size, c + 1)) + return log_oom(); + + units[c] = (struct UnitFileList) { + path, + unit_file_state_from_string(state) + }; + + if (output_show_unit_file(&units[c], strv_skip(argv, 1))) + c ++; + + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + qsort_safe(units, c, sizeof(UnitFileList), compare_unit_file_list); + output_unit_file_list(units, c); + + if (install_client_side()) { + for (unit = units; unit < units + c; unit++) + free(unit->path); + } + + return 0; +} + +static int list_dependencies_print(const char *name, int level, unsigned int branches, bool last) { + _cleanup_free_ char *n = NULL; + size_t max_len = MAX(columns(),20u); + size_t len = 0; + int i; + + if (!arg_plain) { + + for (i = level - 1; i >= 0; i--) { + len += 2; + if (len > max_len - 3 && !arg_full) { + printf("%s...\n",max_len % 2 ? "" : " "); + return 0; + } + printf("%s", draw_special_char(branches & (1 << i) ? DRAW_TREE_VERTICAL : DRAW_TREE_SPACE)); + } + len += 2; + + if (len > max_len - 3 && !arg_full) { + printf("%s...\n",max_len % 2 ? "" : " "); + return 0; + } + + printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH)); + } + + if (arg_full){ + printf("%s\n", name); + return 0; + } + + n = ellipsize(name, max_len-len, 100); + if (!n) + return log_oom(); + + printf("%s\n", n); + return 0; +} + +static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) { + + static const char *dependencies[_DEPENDENCY_MAX] = { + [DEPENDENCY_FORWARD] = "Requires\0" + "Requisite\0" + "Wants\0" + "ConsistsOf\0" + "BindsTo\0", + [DEPENDENCY_REVERSE] = "RequiredBy\0" + "RequisiteOf\0" + "WantedBy\0" + "PartOf\0" + "BoundBy\0", + [DEPENDENCY_AFTER] = "After\0", + [DEPENDENCY_BEFORE] = "Before\0", + }; + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_strv_free_ char **ret = NULL; + _cleanup_free_ char *path = NULL; + int r; + + assert(bus); + assert(name); + assert(deps); + assert_cc(ELEMENTSOF(dependencies) == _DEPENDENCY_MAX); + + path = unit_dbus_path_from_name(name); + if (!path) + return log_oom(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.DBus.Properties", + "GetAll", + &error, + &reply, + "s", "org.freedesktop.systemd1.Unit"); + if (r < 0) + return log_error_errno(r, "Failed to get properties of %s: %s", name, bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const char *prop; + + r = sd_bus_message_read(reply, "s", &prop); + if (r < 0) + return bus_log_parse_error(r); + + if (!nulstr_contains(dependencies[arg_dependency], prop)) { + r = sd_bus_message_skip(reply, "v"); + if (r < 0) + return bus_log_parse_error(r); + } else { + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, "as"); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_message_read_strv_extend(reply, &ret); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + *deps = ret; + ret = NULL; + + return 0; +} + +static int list_dependencies_compare(const void *_a, const void *_b) { + const char **a = (const char**) _a, **b = (const char**) _b; + + if (unit_name_to_type(*a) == UNIT_TARGET && unit_name_to_type(*b) != UNIT_TARGET) + return 1; + if (unit_name_to_type(*a) != UNIT_TARGET && unit_name_to_type(*b) == UNIT_TARGET) + return -1; + + return strcasecmp(*a, *b); +} + +static int list_dependencies_one( + sd_bus *bus, + const char *name, + int level, + char ***units, + unsigned int branches) { + + _cleanup_strv_free_ char **deps = NULL; + char **c; + int r = 0; + + assert(bus); + assert(name); + assert(units); + + r = strv_extend(units, name); + if (r < 0) + return log_oom(); + + r = list_dependencies_get_dependencies(bus, name, &deps); + if (r < 0) + return r; + + qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare); + + STRV_FOREACH(c, deps) { + if (strv_contains(*units, *c)) { + if (!arg_plain) { + r = list_dependencies_print("...", level + 1, (branches << 1) | (c[1] == NULL ? 0 : 1), 1); + if (r < 0) + return r; + } + continue; + } + + if (arg_plain) + printf(" "); + else { + int state; + const char *on; + + state = check_one_unit(bus, *c, "activating\0active\0reloading\0", true); + on = state > 0 ? ansi_highlight_green() : ansi_highlight_red(); + printf("%s%s%s ", on, draw_special_char(DRAW_BLACK_CIRCLE), ansi_normal()); + } + + r = list_dependencies_print(*c, level, branches, c[1] == NULL); + if (r < 0) + return r; + + if (arg_all || unit_name_to_type(*c) == UNIT_TARGET) { + r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (c[1] == NULL ? 0 : 1)); + if (r < 0) + return r; + } + } + + if (!arg_plain) + strv_remove(*units, name); + + return 0; +} + +static int list_dependencies(int argc, char *argv[], void *userdata) { + _cleanup_strv_free_ char **units = NULL; + _cleanup_free_ char *unit = NULL; + const char *u; + sd_bus *bus; + int r; + + if (argv[1]) { + r = unit_name_mangle(argv[1], UNIT_NAME_NOGLOB, &unit); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + u = unit; + } else + u = SPECIAL_DEFAULT_TARGET; + + pager_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + puts(u); + + return list_dependencies_one(bus, u, 0, &units, 0); +} + +struct machine_info { + bool is_host; + char *name; + char *state; + char *control_group; + uint32_t n_failed_units; + uint32_t n_jobs; + usec_t timestamp; +}; + +static const struct bus_properties_map machine_info_property_map[] = { + { "SystemState", "s", NULL, offsetof(struct machine_info, state) }, + { "NJobs", "u", NULL, offsetof(struct machine_info, n_jobs) }, + { "NFailedUnits", "u", NULL, offsetof(struct machine_info, n_failed_units) }, + { "ControlGroup", "s", NULL, offsetof(struct machine_info, control_group) }, + { "UserspaceTimestamp", "t", NULL, offsetof(struct machine_info, timestamp) }, + {} +}; + +static void machine_info_clear(struct machine_info *info) { + if (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) { + int i; + + if (!machine_infos) + return; + + for (i = 0; i < n; i++) + machine_info_clear(&machine_infos[i]); + + free(machine_infos); +} + +static int compare_machine_info(const void *a, const void *b) { + const struct machine_info *u = a, *v = b; + + if (u->is_host != v->is_host) + return u->is_host > v->is_host ? -1 : 1; + + return strcasecmp(u->name, v->name); +} + +static int get_machine_properties(sd_bus *bus, struct machine_info *mi) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL; + int r; + + assert(mi); + + if (!bus) { + r = sd_bus_open_system_machine(&container, mi->name); + if (r < 0) + return r; + + bus = container; + } + + r = bus_map_all_properties(bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", machine_info_property_map, mi); + if (r < 0) + return r; + + return 0; +} + +static bool output_show_machine(const char *name, char **patterns) { + return strv_fnmatch_or_empty(patterns, name, FNM_NOESCAPE); +} + +static int get_machine_list( + sd_bus *bus, + struct machine_info **_machine_infos, + char **patterns) { + + struct machine_info *machine_infos = NULL; + _cleanup_strv_free_ char **m = NULL; + _cleanup_free_ char *hn = NULL; + size_t sz = 0; + char **i; + int c = 0, r; + + hn = gethostname_malloc(); + if (!hn) + return log_oom(); + + if (output_show_machine(hn, patterns)) { + if (!GREEDY_REALLOC0(machine_infos, sz, c+1)) + return log_oom(); + + machine_infos[c].is_host = true; + machine_infos[c].name = hn; + hn = NULL; + + get_machine_properties(bus, &machine_infos[c]); + c++; + } + + r = sd_get_machine_names(&m); + if (r < 0) + return log_error_errno(r, "Failed to get machine list: %m"); + + STRV_FOREACH(i, m) { + _cleanup_free_ char *class = NULL; + + if (!output_show_machine(*i, patterns)) + continue; + + sd_machine_get_class(*i, &class); + if (!streq_ptr(class, "container")) + continue; + + if (!GREEDY_REALLOC0(machine_infos, sz, c+1)) { + free_machines_list(machine_infos, c); + return log_oom(); + } + + machine_infos[c].is_host = false; + machine_infos[c].name = strdup(*i); + if (!machine_infos[c].name) { + free_machines_list(machine_infos, c); + return log_oom(); + } + + get_machine_properties(NULL, &machine_infos[c]); + c++; + } + + *_machine_infos = machine_infos; + return c; +} + +static void output_machines_list(struct machine_info *machine_infos, unsigned n) { + struct machine_info *m; + unsigned + circle_len = 0, + namelen = sizeof("NAME") - 1, + statelen = sizeof("STATE") - 1, + failedlen = sizeof("FAILED") - 1, + jobslen = sizeof("JOBS") - 1; + + assert(machine_infos || n == 0); + + for (m = machine_infos; m < machine_infos + n; m++) { + namelen = MAX(namelen, strlen(m->name) + (m->is_host ? sizeof(" (host)") - 1 : 0)); + statelen = MAX(statelen, m->state ? strlen(m->state) : 0); + failedlen = MAX(failedlen, DECIMAL_STR_WIDTH(m->n_failed_units)); + jobslen = MAX(jobslen, DECIMAL_STR_WIDTH(m->n_jobs)); + + if (!arg_plain && !streq_ptr(m->state, "running")) + circle_len = 2; + } + + if (!arg_no_legend) { + if (circle_len > 0) + fputs(" ", stdout); + + printf("%-*s %-*s %-*s %-*s\n", + namelen, "NAME", + statelen, "STATE", + failedlen, "FAILED", + jobslen, "JOBS"); + } + + for (m = machine_infos; m < machine_infos + n; m++) { + const char *on_state = "", *off_state = ""; + const char *on_failed = "", *off_failed = ""; + bool circle = false; + + if (streq_ptr(m->state, "degraded")) { + on_state = ansi_highlight_red(); + off_state = ansi_normal(); + circle = true; + } else if (!streq_ptr(m->state, "running")) { + on_state = ansi_highlight_yellow(); + off_state = ansi_normal(); + circle = true; + } + + if (m->n_failed_units > 0) { + on_failed = ansi_highlight_red(); + off_failed = ansi_normal(); + } else + on_failed = off_failed = ""; + + if (circle_len > 0) + printf("%s%s%s ", on_state, circle ? draw_special_char(DRAW_BLACK_CIRCLE) : " ", off_state); + + if (m->is_host) + printf("%-*s (host) %s%-*s%s %s%*u%s %*u\n", + (int) (namelen - (sizeof(" (host)")-1)), strna(m->name), + on_state, statelen, strna(m->state), off_state, + on_failed, failedlen, m->n_failed_units, off_failed, + jobslen, m->n_jobs); + else + printf("%-*s %s%-*s%s %s%*u%s %*u\n", + namelen, strna(m->name), + on_state, statelen, strna(m->state), off_state, + on_failed, failedlen, m->n_failed_units, off_failed, + jobslen, m->n_jobs); + } + + if (!arg_no_legend) + printf("\n%u machines listed.\n", n); +} + +static int list_machines(int argc, char *argv[], void *userdata) { + struct machine_info *machine_infos = NULL; + sd_bus *bus; + int r; + + if (geteuid() != 0) { + log_error("Must be root."); + return -EPERM; + } + + pager_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = get_machine_list(bus, &machine_infos, strv_skip(argv, 1)); + if (r < 0) + return r; + + qsort_safe(machine_infos, r, sizeof(struct machine_info), compare_machine_info); + output_machines_list(machine_infos, r); + free_machines_list(machine_infos, r); + + return 0; +} + +static int get_default(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *_path = NULL; + const char *path; + int r; + + if (install_client_side()) { + r = unit_file_get_default(arg_scope, arg_root, &_path); + if (r < 0) + return log_error_errno(r, "Failed to get default target: %m"); + path = _path; + + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetDefaultTarget", + &error, + &reply, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to get default target: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "s", &path); + if (r < 0) + return bus_log_parse_error(r); + } + + if (path) + printf("%s\n", path); + + return 0; +} + +static void dump_unit_file_changes(const UnitFileChange *changes, unsigned n_changes) { + unsigned i; + + assert(changes || n_changes == 0); + + for (i = 0; i < n_changes; i++) { + if (changes[i].type == UNIT_FILE_SYMLINK) + log_info("Created symlink %s, pointing to %s.", changes[i].path, changes[i].source); + else + log_info("Removed symlink %s.", changes[i].path); + } +} + +static int set_default(int argc, char *argv[], void *userdata) { + _cleanup_free_ char *unit = NULL; + int r; + + assert(argc >= 2); + assert(argv); + + r = unit_name_mangle_with_suffix(argv[1], UNIT_NAME_NOGLOB, ".target", &unit); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + if (install_client_side()) { + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + + r = unit_file_set_default(arg_scope, arg_root, unit, true, &changes, &n_changes); + if (r < 0) + return log_error_errno(r, "Failed to set default target: %m"); + + if (!arg_quiet) + dump_unit_file_changes(changes, n_changes); + + unit_file_changes_free(changes, n_changes); + r = 0; + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _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; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SetDefaultTarget", + &error, + &reply, + "sb", unit, 1); + if (r < 0) + return log_error_errno(r, "Failed to set default target: %s", bus_error_message(&error, r)); + + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); + if (r < 0) + return r; + + /* Try to reload if enabled */ + if (!arg_no_reload) + r = daemon_reload(argc, argv, userdata); + else + r = 0; + } + + return r; +} + +struct job_info { + uint32_t id; + const char *name, *type, *state; +}; + +static void output_jobs_list(const struct job_info* jobs, unsigned n, bool skipped) { + unsigned id_len, unit_len, type_len, state_len; + const struct job_info *j; + const char *on, *off; + bool shorten = false; + + assert(n == 0 || jobs); + + if (n == 0) { + if (!arg_no_legend) { + on = ansi_highlight_green(); + off = ansi_normal(); + + printf("%sNo jobs %s.%s\n", on, skipped ? "listed" : "running", off); + } + return; + } + + pager_open_if_enabled(); + + id_len = strlen("JOB"); + unit_len = strlen("UNIT"); + type_len = strlen("TYPE"); + state_len = strlen("STATE"); + + for (j = jobs; j < jobs + n; j++) { + uint32_t id = j->id; + assert(j->name && j->type && j->state); + + id_len = MAX(id_len, DECIMAL_STR_WIDTH(id)); + unit_len = MAX(unit_len, strlen(j->name)); + type_len = MAX(type_len, strlen(j->type)); + state_len = MAX(state_len, strlen(j->state)); + } + + if (!arg_full && id_len + 1 + unit_len + type_len + 1 + state_len > columns()) { + unit_len = MAX(33u, columns() - id_len - type_len - state_len - 3); + shorten = true; + } + + if (!arg_no_legend) + printf("%*s %-*s %-*s %-*s\n", + id_len, "JOB", + unit_len, "UNIT", + type_len, "TYPE", + state_len, "STATE"); + + for (j = jobs; j < jobs + n; j++) { + _cleanup_free_ char *e = NULL; + + if (streq(j->state, "running")) { + on = ansi_highlight(); + off = ansi_normal(); + } else + on = off = ""; + + e = shorten ? ellipsize(j->name, unit_len, 33) : NULL; + printf("%*u %s%-*s%s %-*s %s%-*s%s\n", + id_len, j->id, + on, unit_len, e ? e : j->name, off, + type_len, j->type, + on, state_len, j->state, off); + } + + if (!arg_no_legend) { + on = ansi_highlight(); + off = ansi_normal(); + + printf("\n%s%u jobs listed%s.\n", on, n, off); + } +} + +static bool output_show_job(struct job_info *job, char **patterns) { + return strv_fnmatch_or_empty(patterns, job->name, FNM_NOESCAPE); +} + +static int list_jobs(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 *reply = NULL; + const char *name, *type, *state, *job_path, *unit_path; + _cleanup_free_ struct job_info *jobs = NULL; + size_t size = 0; + unsigned c = 0; + sd_bus *bus; + uint32_t id; + int r; + bool skipped = false; + + pager_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListJobs", + &error, + &reply, + NULL); + if (r < 0) + return log_error_errno(r, "Failed to list jobs: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, 'a', "(usssoo)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(usssoo)", &id, &name, &type, &state, &job_path, &unit_path)) > 0) { + struct job_info job = { id, name, type, state }; + + if (!output_show_job(&job, strv_skip(argv, 1))) { + skipped = true; + continue; + } + + if (!GREEDY_REALLOC(jobs, size, c + 1)) + return log_oom(); + + jobs[c++] = job; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + output_jobs_list(jobs, c, skipped); + return 0; +} + +static int cancel_job(int argc, char *argv[], void *userdata) { + sd_bus *bus; + char **name; + int r = 0; + + if (argc <= 1) + return daemon_reload(argc, argv, userdata); + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + uint32_t id; + int q; + + q = safe_atou32(*name, &id); + if (q < 0) + return log_error_errno(q, "Failed to parse job id \"%s\": %m", *name); + + q = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "CancelJob", + &error, + NULL, + "u", id); + if (q < 0) { + log_error_errno(q, "Failed to cancel job %"PRIu32": %s", id, bus_error_message(&error, q)); + if (r == 0) + r = q; + } + } + + return r; +} + +static int need_daemon_reload(sd_bus *bus, const char *unit) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *path; + int b, r; + + /* We ignore all errors here, since this is used to show a + * warning only */ + + /* We don't use unit_dbus_path_from_name() directly since we + * don't want to load the unit if it isn't loaded. */ + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnit", + NULL, + &reply, + "s", unit); + if (r < 0) + return r; + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return r; + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "NeedDaemonReload", + NULL, + 'b', &b); + if (r < 0) + return r; + + return b; +} + +static void warn_unit_file_changed(const char *name) { + log_warning("%sWarning:%s %s changed on disk. Run 'systemctl%s daemon-reload' to reload units.", + ansi_highlight_red(), + ansi_normal(), + name, + arg_scope == UNIT_FILE_SYSTEM ? "" : " --user"); +} + +static int unit_file_find_path(LookupPaths *lp, const char *unit_name, char **unit_path) { + char **p; + + assert(lp); + assert(unit_name); + assert(unit_path); + + STRV_FOREACH(p, lp->unit_path) { + _cleanup_free_ char *path; + + path = path_join(arg_root, *p, unit_name); + if (!path) + return log_oom(); + + if (access(path, F_OK) == 0) { + *unit_path = path; + path = NULL; + return 1; + } + } + + return 0; +} + +static int unit_find_paths( + sd_bus *bus, + const char *unit_name, + LookupPaths *lp, + char **fragment_path, + char ***dropin_paths) { + + _cleanup_free_ char *path = NULL; + _cleanup_strv_free_ char **dropins = NULL; + int r; + + /** + * Finds where the unit is defined on disk. Returns 0 if the unit + * is not found. Returns 1 if it is found, and sets + * - the path to the unit in *path, if it exists on disk, + * - and a strv of existing drop-ins in *dropins, + * if the arg is not NULL and any dropins were found. + */ + + assert(unit_name); + assert(fragment_path); + assert(lp); + + if (!install_client_side() && !unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *unit = NULL; + + unit = unit_dbus_path_from_name(unit_name); + if (!unit) + return log_oom(); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + unit, + "org.freedesktop.systemd1.Unit", + "FragmentPath", + &error, + &path); + if (r < 0) + return log_error_errno(r, "Failed to get FragmentPath: %s", bus_error_message(&error, r)); + + if (dropin_paths) { + r = sd_bus_get_property_strv( + bus, + "org.freedesktop.systemd1", + unit, + "org.freedesktop.systemd1.Unit", + "DropInPaths", + &error, + &dropins); + if (r < 0) + return log_error_errno(r, "Failed to get DropInPaths: %s", bus_error_message(&error, r)); + } + } else { + _cleanup_set_free_ Set *names; + + names = set_new(NULL); + if (!names) + return log_oom(); + + r = set_put(names, unit_name); + if (r < 0) + return log_error_errno(r, "Failed to add unit name: %m"); + + r = unit_file_find_path(lp, unit_name, &path); + if (r < 0) + return r; + + if (r == 0) { + _cleanup_free_ char *template = NULL; + + r = unit_name_template(unit_name, &template); + if (r < 0 && r != -EINVAL) + return log_error_errno(r, "Failed to determine template name: %m"); + if (r >= 0) { + r = unit_file_find_path(lp, template, &path); + if (r < 0) + return r; + } + } + + if (dropin_paths) { + r = unit_file_find_dropin_paths(lp->unit_path, NULL, names, &dropins); + if (r < 0) + return r; + } + } + + r = 0; + + if (!isempty(path)) { + *fragment_path = path; + path = NULL; + r = 1; + } + + if (dropin_paths && !strv_isempty(dropins)) { + *dropin_paths = dropins; + dropins = NULL; + r = 1; + } + + if (r == 0) + log_error("No files found for %s.", unit_name); + + return r; +} + +static int check_one_unit(sd_bus *bus, const char *name, const char *good_states, bool quiet) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ char *buf = NULL; + const char *path, *state; + int r; + + assert(name); + + /* We don't use unit_dbus_path_from_name() directly since we don't want to load the unit unnecessarily, if it + * isn't loaded. */ + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnit", + &error, + &reply, + "s", name); + if (r < 0) { + if (!sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT)) + return log_error_errno(r, "Failed to retrieve unit: %s", bus_error_message(&error, r)); + + /* The unit is currently not loaded, hence say it's "inactive", since all units that aren't loaded are + * considered inactive. */ + state = "inactive"; + + } else { + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "ActiveState", + &error, + &buf); + if (r < 0) + return log_error_errno(r, "Failed to retrieve unit state: %s", bus_error_message(&error, r)); + + state = buf; + } + + if (!quiet) + puts(state); + + return nulstr_contains(good_states, state); +} + +static int check_triggering_units( + sd_bus *bus, + const char *name) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *path = NULL, *n = NULL, *state = NULL; + _cleanup_strv_free_ char **triggered_by = NULL; + bool print_warning_label = true; + char **i; + int r; + + r = unit_name_mangle(name, UNIT_NAME_NOGLOB, &n); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + path = unit_dbus_path_from_name(n); + if (!path) + return log_oom(); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "LoadState", + &error, + &state); + if (r < 0) + return log_error_errno(r, "Failed to get load state of %s: %s", n, bus_error_message(&error, r)); + + if (streq(state, "masked")) + return 0; + + r = sd_bus_get_property_strv( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "TriggeredBy", + &error, + &triggered_by); + if (r < 0) + return log_error_errno(r, "Failed to get triggered by array of %s: %s", n, bus_error_message(&error, r)); + + STRV_FOREACH(i, triggered_by) { + r = check_one_unit(bus, *i, "active\0reloading\0", true); + if (r < 0) + return log_error_errno(r, "Failed to check unit: %m"); + + if (r == 0) + continue; + + if (print_warning_label) { + log_warning("Warning: Stopping %s, but it can still be activated by:", n); + print_warning_label = false; + } + + log_warning(" %s", *i); + } + + return 0; +} + +static const struct { + const char *verb; + const char *method; +} unit_actions[] = { + { "start", "StartUnit" }, + { "stop", "StopUnit" }, + { "condstop", "StopUnit" }, + { "reload", "ReloadUnit" }, + { "restart", "RestartUnit" }, + { "try-restart", "TryRestartUnit" }, + { "condrestart", "TryRestartUnit" }, + { "reload-or-restart", "ReloadOrRestartUnit" }, + { "try-reload-or-restart", "ReloadOrTryRestartUnit" }, + { "reload-or-try-restart", "ReloadOrTryRestartUnit" }, + { "condreload", "ReloadOrTryRestartUnit" }, + { "force-reload", "ReloadOrTryRestartUnit" } +}; + +static const char *verb_to_method(const char *verb) { + uint i; + + for (i = 0; i < ELEMENTSOF(unit_actions); i++) + if (streq_ptr(unit_actions[i].verb, verb)) + return unit_actions[i].method; + + return "StartUnit"; +} + +static const char *method_to_verb(const char *method) { + uint i; + + for (i = 0; i < ELEMENTSOF(unit_actions); i++) + if (streq_ptr(unit_actions[i].method, method)) + return unit_actions[i].verb; + + return "n/a"; +} + +static int start_unit_one( + sd_bus *bus, + const char *method, + const char *name, + const char *mode, + sd_bus_error *error, + BusWaitForJobs *w) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *path; + int r; + + assert(method); + assert(name); + assert(mode); + assert(error); + + log_debug("Calling manager for %s on %s, %s", method, name, mode); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method, + error, + &reply, + "ss", name, mode); + if (r < 0) { + const char *verb; + + if (r == -ENOENT && arg_action != ACTION_SYSTEMCTL) + /* There's always a fallback possible for + * legacy actions. */ + return -EADDRNOTAVAIL; + + verb = method_to_verb(method); + + log_error("Failed to %s %s: %s", verb, name, bus_error_message(error, r)); + + if (!sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) && + !sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED)) + log_error("See system logs and 'systemctl status %s' for details.", name); + + return r; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + if (need_daemon_reload(bus, name) > 0) + warn_unit_file_changed(name); + + if (w) { + log_debug("Adding %s to the set", path); + r = bus_wait_for_jobs_add(w, path); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int expand_names(sd_bus *bus, char **names, const char* suffix, char ***ret) { + _cleanup_strv_free_ char **mangled = NULL, **globs = NULL; + char **name; + int r, i; + + assert(bus); + assert(ret); + + STRV_FOREACH(name, names) { + char *t; + + if (suffix) + r = unit_name_mangle_with_suffix(*name, UNIT_NAME_GLOB, suffix, &t); + else + r = unit_name_mangle(*name, UNIT_NAME_GLOB, &t); + if (r < 0) + return log_error_errno(r, "Failed to mangle name: %m"); + + if (string_is_glob(t)) + r = strv_consume(&globs, t); + else + r = strv_consume(&mangled, t); + if (r < 0) + return log_oom(); + } + + /* Query the manager only if any of the names are a glob, since + * this is fairly expensive */ + if (!strv_isempty(globs)) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ UnitInfo *unit_infos = NULL; + size_t allocated, n; + + r = get_unit_list(bus, NULL, globs, &unit_infos, 0, &reply); + if (r < 0) + return r; + + n = strv_length(mangled); + allocated = n + 1; + + for (i = 0; i < r; i++) { + if (!GREEDY_REALLOC(mangled, allocated, n+2)) + return log_oom(); + + mangled[n] = strdup(unit_infos[i].id); + if (!mangled[n]) + return log_oom(); + + mangled[++n] = NULL; + } + } + + *ret = mangled; + mangled = NULL; /* do not free */ + + return 0; +} + +static const struct { + const char *target; + const char *verb; + const char *mode; +} action_table[_ACTION_MAX] = { + [ACTION_HALT] = { SPECIAL_HALT_TARGET, "halt", "replace-irreversibly" }, + [ACTION_POWEROFF] = { SPECIAL_POWEROFF_TARGET, "poweroff", "replace-irreversibly" }, + [ACTION_REBOOT] = { SPECIAL_REBOOT_TARGET, "reboot", "replace-irreversibly" }, + [ACTION_KEXEC] = { SPECIAL_KEXEC_TARGET, "kexec", "replace-irreversibly" }, + [ACTION_RUNLEVEL2] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, + [ACTION_RUNLEVEL3] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, + [ACTION_RUNLEVEL4] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, + [ACTION_RUNLEVEL5] = { SPECIAL_GRAPHICAL_TARGET, NULL, "isolate" }, + [ACTION_RESCUE] = { SPECIAL_RESCUE_TARGET, "rescue", "isolate" }, + [ACTION_EMERGENCY] = { SPECIAL_EMERGENCY_TARGET, "emergency", "isolate" }, + [ACTION_DEFAULT] = { SPECIAL_DEFAULT_TARGET, "default", "isolate" }, + [ACTION_EXIT] = { SPECIAL_EXIT_TARGET, "exit", "replace-irreversibly" }, + [ACTION_SUSPEND] = { SPECIAL_SUSPEND_TARGET, "suspend", "replace-irreversibly" }, + [ACTION_HIBERNATE] = { SPECIAL_HIBERNATE_TARGET, "hibernate", "replace-irreversibly" }, + [ACTION_HYBRID_SLEEP] = { SPECIAL_HYBRID_SLEEP_TARGET, "hybrid-sleep", "replace-irreversibly" }, +}; + +static enum action verb_to_action(const char *verb) { + enum action i; + + for (i = _ACTION_INVALID; i < _ACTION_MAX; i++) + if (streq_ptr(action_table[i].verb, verb)) + return i; + + return _ACTION_INVALID; +} + +static int start_unit(int argc, char *argv[], void *userdata) { + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + const char *method, *mode, *one_name, *suffix = NULL; + _cleanup_strv_free_ char **names = NULL; + sd_bus *bus; + 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; + + if (arg_action == ACTION_SYSTEMCTL) { + enum action action; + + method = verb_to_method(argv[0]); + action = verb_to_action(argv[0]); + + if (streq(argv[0], "isolate")) { + mode = "isolate"; + suffix = ".target"; + } else + mode = action_table[action].mode ?: arg_job_mode; + + one_name = action_table[action].target; + } else { + assert(arg_action < ELEMENTSOF(action_table)); + assert(action_table[arg_action].target); + + method = "StartUnit"; + + mode = action_table[arg_action].mode; + one_name = action_table[arg_action].target; + } + + if (one_name) + names = strv_new(one_name, NULL); + else { + r = expand_names(bus, strv_skip(argv, 1), suffix, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + } + + if (!arg_no_block) { + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + } + + STRV_FOREACH(name, names) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int q; + + q = start_unit_one(bus, method, *name, mode, &error, w); + if (r >= 0 && q < 0) + r = translate_bus_error_to_exit_status(q, &error); + } + + if (!arg_no_block) { + int q; + + q = bus_wait_for_jobs(w, arg_quiet, arg_scope != UNIT_FILE_SYSTEM ? "--user" : NULL); + if (q < 0) + return q; + + /* When stopping units, warn if they can still be triggered by + * another active unit (socket, path, timer) */ + if (!arg_quiet && streq(method, "StopUnit")) + STRV_FOREACH(name, names) + check_triggering_units(bus, *name); + } + + return r; +} + +static int logind_set_wall_message(void) { +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + _cleanup_free_ char *m = NULL; + int r; + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + m = strv_join(arg_wall, " "); + if (!m) + return log_oom(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "SetWallMessage", + &error, + NULL, + "sb", + m, + !arg_no_wall); + + if (r < 0) + return log_warning_errno(r, "Failed to set wall message, ignoring: %s", bus_error_message(&error, r)); + +#endif + return 0; +} + +/* Ask systemd-logind, which might grant access to unprivileged users + * through PolicyKit */ +static int logind_reboot(enum action a) { +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *method, *description; + 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; + + switch (a) { + + case ACTION_REBOOT: + method = "Reboot"; + description = "reboot system"; + break; + + case ACTION_POWEROFF: + method = "PowerOff"; + description = "power off system"; + break; + + case ACTION_SUSPEND: + method = "Suspend"; + description = "suspend system"; + break; + + case ACTION_HIBERNATE: + method = "Hibernate"; + description = "hibernate system"; + break; + + case ACTION_HYBRID_SLEEP: + method = "HybridSleep"; + description = "put system into hybrid sleep"; + break; + + default: + return -EINVAL; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + method, + &error, + NULL, + "b", arg_ask_password); + if (r < 0) + return log_error_errno(r, "Failed to %s via logind: %s", description, bus_error_message(&error, r)); + + return 0; +#else + return -ENOSYS; +#endif +} + +static int logind_check_inhibitors(enum action a) { +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_strv_free_ char **sessions = NULL; + const char *what, *who, *why, *mode; + uint32_t uid, pid; + sd_bus *bus; + unsigned c = 0; + char **s; + int r; + + if (arg_ignore_inhibitors || arg_force > 0) + return 0; + + if (arg_when > 0) + return 0; + + if (geteuid() == 0) + return 0; + + if (!on_tty()) + return 0; + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "ListInhibitors", + NULL, + &reply, + NULL); + if (r < 0) + /* If logind is not around, then there are no inhibitors... */ + return 0; + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssuu)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(reply, "(ssssuu)", &what, &who, &why, &mode, &uid, &pid)) > 0) { + _cleanup_free_ char *comm = NULL, *user = NULL; + _cleanup_strv_free_ char **sv = NULL; + + if (!streq(mode, "block")) + continue; + + sv = strv_split(what, ":"); + if (!sv) + return log_oom(); + + if ((pid_t) pid < 0) + return log_error_errno(ERANGE, "Bad PID %"PRIu32": %m", pid); + + if (!strv_contains(sv, + IN_SET(a, + ACTION_HALT, + ACTION_POWEROFF, + ACTION_REBOOT, + ACTION_KEXEC) ? "shutdown" : "sleep")) + continue; + + get_process_comm(pid, &comm); + user = uid_to_name(uid); + + log_warning("Operation inhibited by \"%s\" (PID "PID_FMT" \"%s\", user %s), reason is \"%s\".", + who, (pid_t) pid, strna(comm), strna(user), why); + + c++; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + /* Check for current sessions */ + sd_get_sessions(&sessions); + STRV_FOREACH(s, sessions) { + _cleanup_free_ char *type = NULL, *tty = NULL, *seat = NULL, *user = NULL, *service = NULL, *class = NULL; + + if (sd_session_get_uid(*s, &uid) < 0 || uid == getuid()) + continue; + + if (sd_session_get_class(*s, &class) < 0 || !streq(class, "user")) + continue; + + if (sd_session_get_type(*s, &type) < 0 || (!streq(type, "x11") && !streq(type, "tty"))) + continue; + + sd_session_get_tty(*s, &tty); + sd_session_get_seat(*s, &seat); + sd_session_get_service(*s, &service); + user = uid_to_name(uid); + + log_warning("User %s is logged in on %s.", strna(user), isempty(tty) ? (isempty(seat) ? strna(service) : seat) : tty); + c++; + } + + if (c <= 0) + return 0; + + log_error("Please retry operation after closing inhibitors and logging out other users.\nAlternatively, ignore inhibitors and users with 'systemctl %s -i'.", + action_table[a].verb); + + return -EPERM; +#else + return 0; +#endif +} + +static int logind_prepare_firmware_setup(void) { +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + int r; + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "SetRebootToFirmwareSetup", + &error, + NULL, + "b", true); + if (r < 0) + return log_error_errno(r, "Cannot indicate to EFI to boot into setup mode: %s", bus_error_message(&error, r)); + + return 0; +#else + log_error("Cannot remotely indicate to EFI to boot into setup mode."); + return -ENOSYS; +#endif +} + +static int prepare_firmware_setup(void) { + int r; + + if (!arg_firmware_setup) + return 0; + + if (arg_transport == BUS_TRANSPORT_LOCAL) { + + r = efi_set_reboot_to_firmware(true); + if (r < 0) + log_debug_errno(r, "Cannot indicate to EFI to boot into setup mode, will retry via logind: %m"); + else + return r; + } + + return logind_prepare_firmware_setup(); +} + +static int set_exit_code(uint8_t code) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + int r; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SetExitCode", + &error, + NULL, + "y", code); + if (r < 0) + return log_error_errno(r, "Failed to execute operation: %s", bus_error_message(&error, r)); + + return 0; +} + +static int start_special(int argc, char *argv[], void *userdata) { + enum action a; + int r; + + assert(argv); + + a = verb_to_action(argv[0]); + + r = logind_check_inhibitors(a); + if (r < 0) + return r; + + if (arg_force >= 2 && geteuid() != 0) { + log_error("Must be root."); + return -EPERM; + } + + r = prepare_firmware_setup(); + if (r < 0) + return r; + + if (a == ACTION_REBOOT && argc > 1) { + r = update_reboot_param_file(argv[1]); + if (r < 0) + return r; + + } else if (a == ACTION_EXIT && argc > 1) { + uint8_t code; + + /* If the exit code is not given on the command line, + * don't reset it to zero: just keep it as it might + * have been set previously. */ + + r = safe_atou8(argv[1], &code); + if (r < 0) + return log_error_errno(r, "Invalid exit code."); + + r = set_exit_code(code); + if (r < 0) + return r; + } + + if (arg_force >= 2 && + IN_SET(a, + ACTION_HALT, + ACTION_POWEROFF, + ACTION_REBOOT)) + return halt_now(a); + + if (arg_force >= 1 && + IN_SET(a, + ACTION_HALT, + ACTION_POWEROFF, + ACTION_REBOOT, + ACTION_KEXEC, + ACTION_EXIT)) + return daemon_reload(argc, argv, userdata); + + /* First try logind, to allow authentication with polkit */ + if (IN_SET(a, + ACTION_POWEROFF, + ACTION_REBOOT, + ACTION_SUSPEND, + ACTION_HIBERNATE, + ACTION_HYBRID_SLEEP)) { + r = logind_reboot(a); + if (r >= 0) + return r; + if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) + /* requested operation is not supported or already in progress */ + return r; + + /* On all other errors, try low-level operation */ + } + + return start_unit(argc, argv, userdata); +} + +static int check_unit_generic(int code, const char *good_states, char **args) { + _cleanup_strv_free_ char **names = NULL; + sd_bus *bus; + char **name; + int r; + bool found = false; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = expand_names(bus, args, NULL, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + + STRV_FOREACH(name, names) { + int state; + + state = check_one_unit(bus, *name, good_states, arg_quiet); + if (state < 0) + return state; + if (state > 0) + found = true; + } + + /* use the given return code for the case that we won't find + * any unit which matches the list */ + return found ? 0 : code; +} + +static int check_unit_active(int argc, char *argv[], void *userdata) { + /* According to LSB: 3, "program is not running" */ + return check_unit_generic(3, "active\0reloading\0", strv_skip(argv, 1)); +} + +static int check_unit_failed(int argc, char *argv[], void *userdata) { + return check_unit_generic(1, "failed\0", strv_skip(argv, 1)); +} + +static int kill_unit(int argc, char *argv[], void *userdata) { + _cleanup_strv_free_ char **names = NULL; + char *kill_who = NULL, **name; + sd_bus *bus; + int r, q; + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + if (!arg_kill_who) + arg_kill_who = "all"; + + /* --fail was specified */ + if (streq(arg_job_mode, "fail")) + kill_who = strjoina(arg_kill_who, "-fail", NULL); + + r = expand_names(bus, strv_skip(argv, 1), NULL, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + + STRV_FOREACH(name, names) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + q = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KillUnit", + &error, + NULL, + "ssi", *names, kill_who ? kill_who : arg_kill_who, arg_signal); + if (q < 0) { + log_error_errno(q, "Failed to kill unit %s: %s", *names, bus_error_message(&error, q)); + if (r == 0) + r = q; + } + } + + return r; +} + +typedef struct ExecStatusInfo { + char *name; + + char *path; + char **argv; + + bool ignore; + + usec_t start_timestamp; + usec_t exit_timestamp; + pid_t pid; + int code; + int status; + + LIST_FIELDS(struct ExecStatusInfo, exec); +} ExecStatusInfo; + +static void exec_status_info_free(ExecStatusInfo *i) { + assert(i); + + free(i->name); + free(i->path); + strv_free(i->argv); + free(i); +} + +static int exec_status_info_deserialize(sd_bus_message *m, ExecStatusInfo *i) { + uint64_t start_timestamp, exit_timestamp, start_timestamp_monotonic, exit_timestamp_monotonic; + const char *path; + uint32_t pid; + int32_t code, status; + int ignore, r; + + assert(m); + assert(i); + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_STRUCT, "sasbttttuii"); + if (r < 0) + return bus_log_parse_error(r); + else if (r == 0) + return 0; + + r = sd_bus_message_read(m, "s", &path); + if (r < 0) + return bus_log_parse_error(r); + + i->path = strdup(path); + if (!i->path) + return log_oom(); + + r = sd_bus_message_read_strv(m, &i->argv); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(m, + "bttttuii", + &ignore, + &start_timestamp, &start_timestamp_monotonic, + &exit_timestamp, &exit_timestamp_monotonic, + &pid, + &code, &status); + if (r < 0) + return bus_log_parse_error(r); + + i->ignore = ignore; + i->start_timestamp = (usec_t) start_timestamp; + i->exit_timestamp = (usec_t) exit_timestamp; + i->pid = (pid_t) pid; + i->code = code; + i->status = status; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 1; +} + +typedef struct UnitStatusInfo { + const char *id; + const char *load_state; + const char *active_state; + const char *sub_state; + const char *unit_file_state; + const char *unit_file_preset; + + const char *description; + const char *following; + + char **documentation; + + const char *fragment_path; + const char *source_path; + const char *control_group; + + char **dropin_paths; + + const char *load_error; + const char *result; + + usec_t inactive_exit_timestamp; + usec_t inactive_exit_timestamp_monotonic; + usec_t active_enter_timestamp; + usec_t active_exit_timestamp; + usec_t inactive_enter_timestamp; + + bool need_daemon_reload; + bool transient; + + /* Service */ + pid_t main_pid; + pid_t control_pid; + const char *status_text; + const char *pid_file; + bool running:1; + int status_errno; + + usec_t start_timestamp; + usec_t exit_timestamp; + + int exit_code, exit_status; + + usec_t condition_timestamp; + bool condition_result; + bool failed_condition_trigger; + bool failed_condition_negate; + const char *failed_condition; + const char *failed_condition_parameter; + + usec_t assert_timestamp; + bool assert_result; + bool failed_assert_trigger; + bool failed_assert_negate; + const char *failed_assert; + const char *failed_assert_parameter; + + /* Socket */ + unsigned n_accepted; + unsigned n_connections; + bool accept; + + /* Pairs of type, path */ + char **listen; + + /* Device */ + const char *sysfs_path; + + /* Mount, Automount */ + const char *where; + + /* Swap */ + const char *what; + + /* CGroup */ + uint64_t memory_current; + uint64_t memory_limit; + uint64_t cpu_usage_nsec; + uint64_t tasks_current; + uint64_t tasks_max; + + LIST_HEAD(ExecStatusInfo, exec); +} UnitStatusInfo; + +static void print_status_info( + UnitStatusInfo *i, + bool *ellipsized) { + + ExecStatusInfo *p; + const char *active_on, *active_off, *on, *off, *ss; + usec_t timestamp; + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; + char since2[FORMAT_TIMESTAMP_MAX], *s2; + const char *path; + char **t, **t2; + + assert(i); + + /* This shows pretty information about a unit. See + * print_property() for a low-level property printer */ + + if (streq_ptr(i->active_state, "failed")) { + active_on = ansi_highlight_red(); + active_off = ansi_normal(); + } else if (streq_ptr(i->active_state, "active") || streq_ptr(i->active_state, "reloading")) { + active_on = ansi_highlight_green(); + active_off = ansi_normal(); + } else + active_on = active_off = ""; + + printf("%s%s%s %s", active_on, draw_special_char(DRAW_BLACK_CIRCLE), active_off, strna(i->id)); + + if (i->description && !streq_ptr(i->id, i->description)) + printf(" - %s", i->description); + + printf("\n"); + + if (i->following) + printf(" Follow: unit currently follows state of %s\n", i->following); + + if (streq_ptr(i->load_state, "error")) { + on = ansi_highlight_red(); + off = ansi_normal(); + } else + on = off = ""; + + path = i->source_path ? i->source_path : i->fragment_path; + + if (i->load_error != 0) + printf(" Loaded: %s%s%s (Reason: %s)\n", + on, strna(i->load_state), off, i->load_error); + else if (path && !isempty(i->unit_file_state) && !isempty(i->unit_file_preset)) + printf(" Loaded: %s%s%s (%s; %s; vendor preset: %s)\n", + on, strna(i->load_state), off, path, i->unit_file_state, i->unit_file_preset); + else if (path && !isempty(i->unit_file_state)) + printf(" Loaded: %s%s%s (%s; %s)\n", + on, strna(i->load_state), off, path, i->unit_file_state); + else if (path) + printf(" Loaded: %s%s%s (%s)\n", + on, strna(i->load_state), off, path); + else + printf(" Loaded: %s%s%s\n", + on, strna(i->load_state), off); + + if (i->transient) + printf("Transient: yes\n"); + + if (!strv_isempty(i->dropin_paths)) { + _cleanup_free_ char *dir = NULL; + bool last = false; + char ** dropin; + + STRV_FOREACH(dropin, i->dropin_paths) { + if (! dir || last) { + printf(dir ? " " : " Drop-In: "); + + dir = mfree(dir); + + dir = dirname_malloc(*dropin); + if (!dir) { + log_oom(); + return; + } + + printf("%s\n %s", dir, + draw_special_char(DRAW_TREE_RIGHT)); + } + + last = ! (*(dropin + 1) && startswith(*(dropin + 1), dir)); + + printf("%s%s", basename(*dropin), last ? "\n" : ", "); + } + } + + ss = streq_ptr(i->active_state, i->sub_state) ? NULL : i->sub_state; + if (ss) + printf(" Active: %s%s (%s)%s", + active_on, strna(i->active_state), ss, active_off); + else + printf(" Active: %s%s%s", + active_on, strna(i->active_state), active_off); + + if (!isempty(i->result) && !streq(i->result, "success")) + printf(" (Result: %s)", i->result); + + timestamp = (streq_ptr(i->active_state, "active") || + streq_ptr(i->active_state, "reloading")) ? i->active_enter_timestamp : + (streq_ptr(i->active_state, "inactive") || + streq_ptr(i->active_state, "failed")) ? i->inactive_enter_timestamp : + streq_ptr(i->active_state, "activating") ? i->inactive_exit_timestamp : + i->active_exit_timestamp; + + s1 = format_timestamp_relative(since1, sizeof(since1), timestamp); + s2 = format_timestamp(since2, sizeof(since2), timestamp); + + if (s1) + printf(" since %s; %s\n", s2, s1); + else if (s2) + printf(" since %s\n", s2); + else + printf("\n"); + + if (!i->condition_result && i->condition_timestamp > 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); + } + + if (!i->assert_result && i->assert_timestamp > 0) { + s1 = format_timestamp_relative(since1, sizeof(since1), i->assert_timestamp); + s2 = format_timestamp(since2, sizeof(since2), i->assert_timestamp); + + printf(" Assert: start %sassertion failed%s at %s%s%s\n", + ansi_highlight_red(), ansi_normal(), + s2, s1 ? "; " : "", strempty(s1)); + if (i->failed_assert_trigger) + printf(" none of the trigger assertions were met\n"); + else if (i->failed_assert) + printf(" %s=%s%s was not met\n", + i->failed_assert, + i->failed_assert_negate ? "!" : "", + i->failed_assert_parameter); + } + + if (i->sysfs_path) + printf(" Device: %s\n", i->sysfs_path); + if (i->where) + printf(" Where: %s\n", i->where); + if (i->what) + printf(" What: %s\n", i->what); + + STRV_FOREACH(t, i->documentation) + printf(" %*s %s\n", 9, t == i->documentation ? "Docs:" : "", *t); + + STRV_FOREACH_PAIR(t, t2, i->listen) + printf(" %*s %s (%s)\n", 9, t == i->listen ? "Listen:" : "", *t2, *t); + + if (i->accept) + printf(" Accepted: %u; Connected: %u\n", i->n_accepted, i->n_connections); + + LIST_FOREACH(exec, p, i->exec) { + _cleanup_free_ char *argv = NULL; + bool good; + + /* Only show exited processes here */ + if (p->code == 0) + continue; + + argv = strv_join(p->argv, " "); + printf(" Process: "PID_FMT" %s=%s ", p->pid, p->name, strna(argv)); + + good = is_clean_exit_lsb(p->code, p->status, NULL); + if (!good) { + on = ansi_highlight_red(); + off = ansi_normal(); + } else + on = off = ""; + + printf("%s(code=%s, ", on, sigchld_code_to_string(p->code)); + + if (p->code == CLD_EXITED) { + const char *c; + + printf("status=%i", p->status); + + c = exit_status_to_string(p->status, EXIT_STATUS_SYSTEMD); + if (c) + printf("/%s", c); + + } else + printf("signal=%s", signal_to_string(p->status)); + + printf(")%s\n", off); + + if (i->main_pid == p->pid && + i->start_timestamp == p->start_timestamp && + i->exit_timestamp == p->start_timestamp) + /* Let's not show this twice */ + i->main_pid = 0; + + if (p->pid == i->control_pid) + i->control_pid = 0; + } + + if (i->main_pid > 0 || i->control_pid > 0) { + if (i->main_pid > 0) { + printf(" Main PID: "PID_FMT, i->main_pid); + + if (i->running) { + _cleanup_free_ char *comm = NULL; + get_process_comm(i->main_pid, &comm); + if (comm) + printf(" (%s)", comm); + } else if (i->exit_code > 0) { + printf(" (code=%s, ", sigchld_code_to_string(i->exit_code)); + + if (i->exit_code == CLD_EXITED) { + const char *c; + + printf("status=%i", i->exit_status); + + c = exit_status_to_string(i->exit_status, EXIT_STATUS_SYSTEMD); + if (c) + printf("/%s", c); + + } else + 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); + + get_process_comm(i->control_pid, &c); + if (c) + printf(" (%s)", c); + } + + printf("\n"); + } + + if (i->status_text) + printf(" Status: \"%s\"\n", i->status_text); + if (i->status_errno > 0) + printf(" Error: %i (%s)\n", i->status_errno, strerror(i->status_errno)); + + if (i->tasks_current != (uint64_t) -1) { + printf(" Tasks: %" PRIu64, i->tasks_current); + + if (i->tasks_max != (uint64_t) -1) + printf(" (limit: %" PRIi64 ")\n", i->tasks_max); + else + printf("\n"); + } + + if (i->memory_current != (uint64_t) -1) { + char buf[FORMAT_BYTES_MAX]; + + printf(" Memory: %s", format_bytes(buf, sizeof(buf), i->memory_current)); + + if (i->memory_limit != (uint64_t) -1) + printf(" (limit: %s)\n", format_bytes(buf, sizeof(buf), i->memory_limit)); + else + printf("\n"); + } + + if (i->cpu_usage_nsec != (uint64_t) -1) { + char buf[FORMAT_TIMESPAN_MAX]; + printf(" CPU: %s\n", format_timespan(buf, sizeof(buf), i->cpu_usage_nsec / NSEC_PER_USEC, USEC_PER_MSEC)); + } + + if (i->control_group && + (i->main_pid > 0 || i->control_pid > 0 || + (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE) || cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, i->control_group) == 0))) { + unsigned c; + + printf(" CGroup: %s\n", i->control_group); + + if (IN_SET(arg_transport, + BUS_TRANSPORT_LOCAL, + BUS_TRANSPORT_MACHINE)) { + unsigned k = 0; + pid_t extra[2]; + static const char prefix[] = " "; + + c = columns(); + if (c > sizeof(prefix) - 1) + c -= sizeof(prefix) - 1; + else + c = 0; + + if (i->main_pid > 0) + extra[k++] = i->main_pid; + + if (i->control_pid > 0) + extra[k++] = i->control_pid; + + show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, i->control_group, prefix, c, false, extra, k, get_output_flags()); + } + } + + if (i->id && arg_transport == BUS_TRANSPORT_LOCAL) + show_journal_by_unit( + stdout, + i->id, + arg_output, + 0, + i->inactive_exit_timestamp_monotonic, + arg_lines, + getuid(), + get_output_flags() | OUTPUT_BEGIN_NEWLINE, + SD_JOURNAL_LOCAL_ONLY, + arg_scope == UNIT_FILE_SYSTEM, + ellipsized); + + if (i->need_daemon_reload) + warn_unit_file_changed(i->id); +} + +static void show_unit_help(UnitStatusInfo *i) { + char **p; + + assert(i); + + if (!i->documentation) { + log_info("Documentation for %s not known.", i->id); + return; + } + + STRV_FOREACH(p, i->documentation) + if (startswith(*p, "man:")) + show_man_page(*p + 4, false); + else + log_info("Can't show: %s", *p); +} + +static int status_property(const char *name, sd_bus_message *m, UnitStatusInfo *i, const char *contents) { + int r; + + assert(name); + assert(m); + assert(i); + + switch (contents[0]) { + + case SD_BUS_TYPE_STRING: { + const char *s; + + r = sd_bus_message_read(m, "s", &s); + if (r < 0) + return bus_log_parse_error(r); + + if (!isempty(s)) { + if (streq(name, "Id")) + i->id = s; + else if (streq(name, "LoadState")) + i->load_state = s; + else if (streq(name, "ActiveState")) + i->active_state = s; + else if (streq(name, "SubState")) + i->sub_state = s; + else if (streq(name, "Description")) + i->description = s; + else if (streq(name, "FragmentPath")) + i->fragment_path = s; + else if (streq(name, "SourcePath")) + i->source_path = s; +#ifndef NOLEGACY + else if (streq(name, "DefaultControlGroup")) { + const char *e; + e = startswith(s, SYSTEMD_CGROUP_CONTROLLER ":"); + if (e) + i->control_group = e; + } +#endif + else if (streq(name, "ControlGroup")) + i->control_group = s; + else if (streq(name, "StatusText")) + i->status_text = s; + else if (streq(name, "PIDFile")) + i->pid_file = s; + else if (streq(name, "SysFSPath")) + i->sysfs_path = s; + else if (streq(name, "Where")) + i->where = s; + else if (streq(name, "What")) + i->what = s; + else if (streq(name, "Following")) + i->following = s; + else if (streq(name, "UnitFileState")) + i->unit_file_state = s; + else if (streq(name, "UnitFilePreset")) + i->unit_file_preset = s; + else if (streq(name, "Result")) + i->result = s; + } + + break; + } + + case SD_BUS_TYPE_BOOLEAN: { + int b; + + r = sd_bus_message_read(m, "b", &b); + if (r < 0) + return bus_log_parse_error(r); + + if (streq(name, "Accept")) + i->accept = b; + else if (streq(name, "NeedDaemonReload")) + i->need_daemon_reload = b; + else if (streq(name, "ConditionResult")) + i->condition_result = b; + else if (streq(name, "AssertResult")) + i->assert_result = b; + else if (streq(name, "Transient")) + i->transient = b; + + break; + } + + case SD_BUS_TYPE_UINT32: { + uint32_t u; + + r = sd_bus_message_read(m, "u", &u); + if (r < 0) + return bus_log_parse_error(r); + + if (streq(name, "MainPID")) { + if (u > 0) { + i->main_pid = (pid_t) u; + i->running = true; + } + } else if (streq(name, "ControlPID")) + i->control_pid = (pid_t) u; + else if (streq(name, "ExecMainPID")) { + if (u > 0) + i->main_pid = (pid_t) u; + } else if (streq(name, "NAccepted")) + i->n_accepted = u; + else if (streq(name, "NConnections")) + i->n_connections = u; + + break; + } + + case SD_BUS_TYPE_INT32: { + int32_t j; + + r = sd_bus_message_read(m, "i", &j); + if (r < 0) + return bus_log_parse_error(r); + + if (streq(name, "ExecMainCode")) + i->exit_code = (int) j; + else if (streq(name, "ExecMainStatus")) + i->exit_status = (int) j; + else if (streq(name, "StatusErrno")) + i->status_errno = (int) j; + + break; + } + + case SD_BUS_TYPE_UINT64: { + uint64_t u; + + r = sd_bus_message_read(m, "t", &u); + if (r < 0) + return bus_log_parse_error(r); + + if (streq(name, "ExecMainStartTimestamp")) + i->start_timestamp = (usec_t) u; + else if (streq(name, "ExecMainExitTimestamp")) + i->exit_timestamp = (usec_t) u; + else if (streq(name, "ActiveEnterTimestamp")) + i->active_enter_timestamp = (usec_t) u; + else if (streq(name, "InactiveEnterTimestamp")) + i->inactive_enter_timestamp = (usec_t) u; + else if (streq(name, "InactiveExitTimestamp")) + i->inactive_exit_timestamp = (usec_t) u; + else if (streq(name, "InactiveExitTimestampMonotonic")) + i->inactive_exit_timestamp_monotonic = (usec_t) u; + else if (streq(name, "ActiveExitTimestamp")) + i->active_exit_timestamp = (usec_t) u; + else if (streq(name, "ConditionTimestamp")) + i->condition_timestamp = (usec_t) u; + else if (streq(name, "AssertTimestamp")) + i->assert_timestamp = (usec_t) u; + else if (streq(name, "MemoryCurrent")) + i->memory_current = u; + else if (streq(name, "MemoryLimit")) + i->memory_limit = u; + else if (streq(name, "TasksCurrent")) + i->tasks_current = u; + else if (streq(name, "TasksMax")) + i->tasks_max = u; + else if (streq(name, "CPUUsageNSec")) + i->cpu_usage_nsec = u; + + break; + } + + case SD_BUS_TYPE_ARRAY: + + if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && startswith(name, "Exec")) { + _cleanup_free_ ExecStatusInfo *info = NULL; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sasbttttuii)"); + if (r < 0) + return bus_log_parse_error(r); + + info = new0(ExecStatusInfo, 1); + if (!info) + return log_oom(); + + while ((r = exec_status_info_deserialize(m, info)) > 0) { + + info->name = strdup(name); + if (!info->name) + return log_oom(); + + LIST_PREPEND(exec, i->exec, info); + + info = new0(ExecStatusInfo, 1); + if (!info) + return log_oom(); + } + + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Listen")) { + const char *type, *path; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) { + + r = strv_extend(&i->listen, type); + if (r < 0) + return r; + + r = strv_extend(&i->listen, path); + if (r < 0) + return r; + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "DropInPaths")) { + + r = sd_bus_message_read_strv(m, &i->dropin_paths); + if (r < 0) + return bus_log_parse_error(r); + + } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "Documentation")) { + + r = sd_bus_message_read_strv(m, &i->documentation); + if (r < 0) + return bus_log_parse_error(r); + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Conditions")) { + const char *cond, *param; + int trigger, negate; + int32_t state; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sbbsi)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(sbbsi)", &cond, &trigger, &negate, ¶m, &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; + } + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Asserts")) { + const char *cond, *param; + int trigger, negate; + int32_t state; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sbbsi)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(sbbsi)", &cond, &trigger, &negate, ¶m, &state)) > 0) { + log_debug("%s %d %d %s %d", cond, trigger, negate, param, state); + if (state < 0 && (!trigger || !i->failed_assert)) { + i->failed_assert = cond; + i->failed_assert_trigger = trigger; + i->failed_assert_negate = negate; + i->failed_assert_parameter = param; + } + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + } else + goto skip; + + break; + + case SD_BUS_TYPE_STRUCT_BEGIN: + + if (streq(name, "LoadError")) { + const char *n, *message; + + r = sd_bus_message_read(m, "(ss)", &n, &message); + if (r < 0) + return bus_log_parse_error(r); + + if (!isempty(message)) + i->load_error = message; + } else + goto skip; + + break; + + default: + goto skip; + } + + return 0; + +skip: + r = sd_bus_message_skip(m, contents); + if (r < 0) + return bus_log_parse_error(r); + + return 0; +} + +static int print_property(const char *name, sd_bus_message *m, const char *contents) { + int r; + + assert(name); + assert(m); + + /* This is a low-level property printer, see + * print_status_info() for the nicer output */ + + if (arg_properties && !strv_find(arg_properties, name)) { + /* skip what we didn't read */ + r = sd_bus_message_skip(m, contents); + return r; + } + + switch (contents[0]) { + + case SD_BUS_TYPE_STRUCT_BEGIN: + + if (contents[1] == SD_BUS_TYPE_UINT32 && streq(name, "Job")) { + uint32_t u; + + r = sd_bus_message_read(m, "(uo)", &u, NULL); + if (r < 0) + return bus_log_parse_error(r); + + if (u > 0) + printf("%s=%"PRIu32"\n", name, u); + else if (arg_all) + printf("%s=\n", name); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "Unit")) { + const char *s; + + r = sd_bus_message_read(m, "(so)", &s, NULL); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_all || !isempty(s)) + printf("%s=%s\n", name, s); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "LoadError")) { + const char *a = NULL, *b = NULL; + + r = sd_bus_message_read(m, "(ss)", &a, &b); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_all || !isempty(a) || !isempty(b)) + printf("%s=%s \"%s\"\n", name, strempty(a), strempty(b)); + + return 0; + } else if (streq_ptr(name, "SystemCallFilter")) { + _cleanup_strv_free_ char **l = NULL; + int whitelist; + + r = sd_bus_message_enter_container(m, 'r', "bas"); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read(m, "b", &whitelist); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_read_strv(m, &l); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_all || whitelist || !strv_isempty(l)) { + bool first = true; + char **i; + + fputs(name, stdout); + fputc('=', stdout); + + if (!whitelist) + fputc('~', stdout); + + STRV_FOREACH(i, l) { + if (first) + first = false; + else + fputc(' ', stdout); + + fputs(*i, stdout); + } + fputc('\n', stdout); + } + + return 0; + } + + break; + + case SD_BUS_TYPE_ARRAY: + + if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "EnvironmentFiles")) { + const char *path; + int ignore; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sb)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(sb)", &path, &ignore)) > 0) + printf("EnvironmentFile=%s (ignore_errors=%s)\n", path, yes_no(ignore)); + + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Paths")) { + const char *type, *path; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) + printf("%s=%s\n", type, path); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Listen")) { + const char *type, *path; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) + printf("Listen%s=%s\n", type, path); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Timers")) { + const char *base; + uint64_t value, next_elapse; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(stt)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(stt)", &base, &value, &next_elapse)) > 0) { + char timespan1[FORMAT_TIMESPAN_MAX], timespan2[FORMAT_TIMESPAN_MAX]; + + printf("%s={ value=%s ; next_elapse=%s }\n", + base, + format_timespan(timespan1, sizeof(timespan1), value, 0), + format_timespan(timespan2, sizeof(timespan2), next_elapse, 0)); + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && startswith(name, "Exec")) { + ExecStatusInfo info = {}; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sasbttttuii)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = exec_status_info_deserialize(m, &info)) > 0) { + char timestamp1[FORMAT_TIMESTAMP_MAX], timestamp2[FORMAT_TIMESTAMP_MAX]; + _cleanup_free_ char *tt; + + tt = strv_join(info.argv, " "); + + printf("%s={ path=%s ; argv[]=%s ; ignore_errors=%s ; start_time=[%s] ; stop_time=[%s] ; pid="PID_FMT" ; code=%s ; status=%i%s%s }\n", + name, + strna(info.path), + strna(tt), + yes_no(info.ignore), + strna(format_timestamp(timestamp1, sizeof(timestamp1), info.start_timestamp)), + strna(format_timestamp(timestamp2, sizeof(timestamp2), info.exit_timestamp)), + info.pid, + sigchld_code_to_string(info.code), + info.status, + info.code == CLD_EXITED ? "" : "/", + strempty(info.code == CLD_EXITED ? NULL : signal_to_string(info.status))); + + free(info.path); + strv_free(info.argv); + zero(info); + } + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "DeviceAllow")) { + const char *path, *rwm; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(ss)", &path, &rwm)) > 0) + printf("%s=%s %s\n", name, strna(path), strna(rwm)); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "BlockIODeviceWeight")) { + const char *path; + uint64_t weight; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(st)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(st)", &path, &weight)) > 0) + printf("%s=%s %" PRIu64 "\n", name, strna(path), weight); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + + } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && (streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth"))) { + const char *path; + uint64_t bandwidth; + + r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(st)"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read(m, "(st)", &path, &bandwidth)) > 0) + printf("%s=%s %" PRIu64 "\n", name, strna(path), bandwidth); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(m); + if (r < 0) + return bus_log_parse_error(r); + + return 0; + } + + break; + } + + r = bus_print_property(name, m, arg_all); + if (r < 0) + return bus_log_parse_error(r); + + if (r == 0) { + r = sd_bus_message_skip(m, contents); + if (r < 0) + return bus_log_parse_error(r); + + if (arg_all) + printf("%s=[unprintable]\n", name); + } + + return 0; +} + +static int show_one( + const char *verb, + sd_bus *bus, + const char *path, + bool show_properties, + bool *new_line, + bool *ellipsized) { + + _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 = { + .memory_current = (uint64_t) -1, + .memory_limit = (uint64_t) -1, + .cpu_usage_nsec = (uint64_t) -1, + .tasks_current = (uint64_t) -1, + .tasks_max = (uint64_t) -1, + }; + ExecStatusInfo *p; + int r; + + assert(path); + assert(new_line); + + log_debug("Showing one %s", path); + + 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 = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); + if (r < 0) + return bus_log_parse_error(r); + + if (*new_line) + printf("\n"); + + *new_line = true; + + while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { + const char *name, *contents; + + r = sd_bus_message_read(reply, "s", &name); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_peek_type(reply, NULL, &contents); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, contents); + if (r < 0) + return bus_log_parse_error(r); + + if (show_properties) + r = print_property(name, reply, contents); + else + r = status_property(name, reply, &info, contents); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + } + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + r = 0; + + if (!show_properties) { + if (streq(verb, "help")) + show_unit_help(&info); + else + print_status_info(&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 = 1; + else + r = 3; + } + + while ((p = info.exec)) { + LIST_REMOVE(exec, info.exec, p); + exec_status_info_free(p); + } + + return r; +} + +static int get_unit_dbus_path_by_pid( + sd_bus *bus, + uint32_t pid, + char **unit) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + char *u; + int r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnitByPID", + &error, + &reply, + "u", pid); + if (r < 0) + return log_error_errno(r, "Failed to get unit for PID %"PRIu32": %s", pid, bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "o", &u); + if (r < 0) + return bus_log_parse_error(r); + + u = strdup(u); + if (!u) + return log_oom(); + + *unit = u; + return 0; +} + +static int show_all( + const char* verb, + sd_bus *bus, + bool show_properties, + bool *new_line, + bool *ellipsized) { + + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_free_ UnitInfo *unit_infos = NULL; + const UnitInfo *u; + unsigned c; + int r, ret = 0; + + r = get_unit_list(bus, NULL, NULL, &unit_infos, 0, &reply); + if (r < 0) + return r; + + pager_open_if_enabled(); + + c = (unsigned) r; + + qsort_safe(unit_infos, c, sizeof(UnitInfo), compare_unit_info); + + for (u = unit_infos; u < unit_infos + c; u++) { + _cleanup_free_ char *p = NULL; + + p = unit_dbus_path_from_name(u->id); + if (!p) + return log_oom(); + + r = show_one(verb, bus, p, show_properties, new_line, ellipsized); + if (r < 0) + return r; + else if (r > 0 && ret == 0) + ret = r; + } + + return ret; +} + +static int show_system_status(sd_bus *bus) { + char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], since2[FORMAT_TIMESTAMP_MAX]; + _cleanup_free_ char *hn = NULL; + _cleanup_(machine_info_clear) struct machine_info mi = {}; + const char *on, *off; + int r; + + hn = gethostname_malloc(); + if (!hn) + return log_oom(); + + r = bus_map_all_properties(bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", machine_info_property_map, &mi); + if (r < 0) + return log_error_errno(r, "Failed to read server status: %m"); + + if (streq_ptr(mi.state, "degraded")) { + on = ansi_highlight_red(); + off = ansi_normal(); + } else if (!streq_ptr(mi.state, "running")) { + on = ansi_highlight_yellow(); + off = ansi_normal(); + } else + on = off = ""; + + printf("%s%s%s %s\n", on, draw_special_char(DRAW_BLACK_CIRCLE), off, arg_host ? arg_host : hn); + + printf(" State: %s%s%s\n", + on, strna(mi.state), off); + + printf(" Jobs: %u queued\n", mi.n_jobs); + printf(" Failed: %u units\n", mi.n_failed_units); + + printf(" Since: %s; %s\n", + format_timestamp(since2, sizeof(since2), mi.timestamp), + format_timestamp_relative(since1, sizeof(since1), mi.timestamp)); + + printf(" CGroup: %s\n", mi.control_group ?: "/"); + if (IN_SET(arg_transport, + BUS_TRANSPORT_LOCAL, + BUS_TRANSPORT_MACHINE)) { + static const char prefix[] = " "; + unsigned c; + + c = columns(); + if (c > sizeof(prefix) - 1) + c -= sizeof(prefix) - 1; + else + c = 0; + + show_cgroup(SYSTEMD_CGROUP_CONTROLLER, strempty(mi.control_group), prefix, c, false, get_output_flags()); + } + + return 0; +} + +static int show(int argc, char *argv[], void *userdata) { + bool show_properties, show_status, show_help, new_line = false; + bool ellipsized = false; + int r, ret = 0; + sd_bus *bus; + + assert(argv); + + show_properties = streq(argv[0], "show"); + show_status = streq(argv[0], "status"); + show_help = streq(argv[0], "help"); + + if (show_help && argc <= 1) { + log_error("This command expects one or more unit names. Did you mean --help?"); + return -EINVAL; + } + + pager_open_if_enabled(); + + if (show_status) + /* Increase max number of open files to 16K if we can, we + * might needs this when browsing journal files, which might + * 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); + + if (show_status && argc <= 1) { + + pager_open_if_enabled(); + show_system_status(bus); + new_line = true; + + if (arg_all) + ret = show_all(argv[0], bus, false, &new_line, &ellipsized); + } else { + _cleanup_free_ char **patterns = NULL; + char **name; + + STRV_FOREACH(name, strv_skip(argv, 1)) { + _cleanup_free_ char *unit = NULL; + uint32_t id; + + if (safe_atou32(*name, &id) < 0) { + if (strv_push(&patterns, *name) < 0) + return log_oom(); + + continue; + } else if (show_properties) { + /* Interpret as job id */ + if (asprintf(&unit, "/org/freedesktop/systemd1/job/%u", id) < 0) + return log_oom(); + + } else { + /* Interpret as PID */ + r = get_unit_dbus_path_by_pid(bus, id, &unit); + if (r < 0) { + ret = r; + continue; + } + } + + r = show_one(argv[0], bus, unit, show_properties, &new_line, &ellipsized); + if (r < 0) + return r; + else if (r > 0 && ret == 0) + ret = r; + } + + if (!strv_isempty(patterns)) { + _cleanup_strv_free_ char **names = NULL; + + r = expand_names(bus, patterns, NULL, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + + STRV_FOREACH(name, names) { + _cleanup_free_ char *unit; + + unit = unit_dbus_path_from_name(*name); + if (!unit) + return log_oom(); + + r = show_one(argv[0], bus, unit, show_properties, &new_line, &ellipsized); + if (r < 0) + return r; + else if (r > 0 && ret == 0) + ret = r; + } + } + } + + if (ellipsized && !arg_quiet) + printf("Hint: Some lines were ellipsized, use -l to show in full.\n"); + + return ret; +} + +static int init_home_and_lookup_paths(char **user_home, char **user_runtime, LookupPaths *lp) { + int r; + + assert(user_home); + assert(user_runtime); + assert(lp); + + if (arg_scope == UNIT_FILE_USER) { + r = user_config_home(user_home); + if (r < 0) + return log_error_errno(r, "Failed to query XDG_CONFIG_HOME: %m"); + else if (r == 0) + return log_error_errno(ENOTDIR, "Cannot find units: $XDG_CONFIG_HOME and $HOME are not set."); + + r = user_runtime_dir(user_runtime); + if (r < 0) + return log_error_errno(r, "Failed to query XDG_CONFIG_HOME: %m"); + else if (r == 0) + return log_error_errno(ENOTDIR, "Cannot find units: $XDG_RUNTIME_DIR is not set."); + } + + r = lookup_paths_init_from_scope(lp, arg_scope, arg_root); + if (r < 0) + return log_error_errno(r, "Failed to query unit lookup paths: %m"); + + return 0; +} + +static int cat_file(const char *filename, bool newline) { + _cleanup_close_ int fd; + + fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + printf("%s%s# %s%s\n", + newline ? "\n" : "", + ansi_highlight_blue(), + filename, + ansi_normal()); + fflush(stdout); + + return copy_bytes(fd, STDOUT_FILENO, (uint64_t) -1, false); +} + +static int cat(int argc, char *argv[], void *userdata) { + _cleanup_free_ char *user_home = NULL; + _cleanup_free_ char *user_runtime = NULL; + _cleanup_lookup_paths_free_ LookupPaths lp = {}; + _cleanup_strv_free_ char **names = NULL; + char **name; + sd_bus *bus; + bool first = true; + int r; + + if (arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Cannot remotely cat units."); + return -EINVAL; + } + + r = init_home_and_lookup_paths(&user_home, &user_runtime, &lp); + if (r < 0) + return r; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = expand_names(bus, strv_skip(argv, 1), NULL, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + + pager_open_if_enabled(); + + STRV_FOREACH(name, names) { + _cleanup_free_ char *fragment_path = NULL; + _cleanup_strv_free_ char **dropin_paths = NULL; + char **path; + + r = unit_find_paths(bus, *name, &lp, &fragment_path, &dropin_paths); + if (r < 0) + return r; + else if (r == 0) + return -ENOENT; + + if (first) + first = false; + else + puts(""); + + if (fragment_path) { + r = cat_file(fragment_path, false); + if (r < 0) + return log_warning_errno(r, "Failed to cat %s: %m", fragment_path); + } + + STRV_FOREACH(path, dropin_paths) { + r = cat_file(*path, path == dropin_paths); + if (r < 0) + return log_warning_errno(r, "Failed to cat %s: %m", *path); + } + } + + return 0; +} + +static int set_property(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *n = NULL; + sd_bus *bus; + char **i; + int r; + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SetUnitProperties"); + if (r < 0) + return bus_log_create_error(r); + + r = unit_name_mangle(argv[1], UNIT_NAME_NOGLOB, &n); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + r = sd_bus_message_append(m, "sb", n, arg_runtime); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + STRV_FOREACH(i, strv_skip(argv, 2)) { + r = bus_append_unit_property_assignment(m, *i); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, NULL); + if (r < 0) + return log_error_errno(r, "Failed to set unit properties on %s: %s", n, bus_error_message(&error, r)); + + return 0; +} + +static int daemon_reload(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; + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + if (arg_action == ACTION_RELOAD) + method = "Reload"; + else if (arg_action == ACTION_REEXEC) + method = "Reexecute"; + else { + assert(arg_action == ACTION_SYSTEMCTL); + + 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"; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + method, + &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 execute operation: %s", bus_error_message(&error, r)); + + return r < 0 ? r : 0; +} + +static int reset_failed(int argc, char *argv[], void *userdata) { + _cleanup_strv_free_ char **names = NULL; + sd_bus *bus; + char **name; + int r, q; + + if (argc <= 1) + return daemon_reload(argc, argv, userdata); + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = expand_names(bus, strv_skip(argv, 1), NULL, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + + STRV_FOREACH(name, names) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + q = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ResetFailedUnit", + &error, + NULL, + "s", *name); + if (q < 0) { + log_error_errno(q, "Failed to reset failed state of unit %s: %s", *name, bus_error_message(&error, q)); + if (r == 0) + r = q; + } + } + + return r; +} + +static int show_environment(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 *reply = NULL; + const char *text; + sd_bus *bus; + int r; + + pager_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_get_property( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Environment", + &error, + &reply, + "as"); + if (r < 0) + return log_error_errno(r, "Failed to get environment: %s", bus_error_message(&error, r)); + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "s"); + if (r < 0) + return bus_log_parse_error(r); + + while ((r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &text)) > 0) + puts(text); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_message_exit_container(reply); + if (r < 0) + return bus_log_parse_error(r); + + return 0; +} + +static int switch_root(int argc, char *argv[], void *userdata) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *cmdline_init = NULL; + const char *root, *init; + sd_bus *bus; + int r; + + if (arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Cannot switch root remotely."); + return -EINVAL; + } + + if (argc < 2 || argc > 3) { + log_error("Wrong number of arguments."); + return -EINVAL; + } + + root = argv[1]; + + if (argc >= 3) + init = argv[2]; + else { + r = parse_env_file("/proc/cmdline", WHITESPACE, + "init", &cmdline_init, + NULL); + if (r < 0) + log_debug_errno(r, "Failed to parse /proc/cmdline: %m"); + + init = cmdline_init; + } + + if (isempty(init)) + init = NULL; + + if (init) { + const char *root_systemd_path = NULL, *root_init_path = NULL; + + root_systemd_path = strjoina(root, "/" SYSTEMD_BINARY_PATH); + root_init_path = strjoina(root, "/", init); + + /* If the passed init is actually the same as the + * systemd binary, then let's suppress it. */ + if (files_same(root_init_path, root_systemd_path) > 0) + init = NULL; + } + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + log_debug("Switching root - root: %s; init: %s", root, strna(init)); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SwitchRoot", + &error, + NULL, + "ss", root, init); + if (r < 0) + return log_error_errno(r, "Failed to switch root: %s", bus_error_message(&error, r)); + + return 0; +} + +static int set_environment(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; + + assert(argc > 1); + assert(argv); + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + method = streq(argv[0], "set-environment") + ? "SetEnvironment" + : "UnsetEnvironment"; + + 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); + + r = sd_bus_message_append_strv(m, strv_skip(argv, 1)); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, NULL); + if (r < 0) + return log_error_errno(r, "Failed to set environment: %s", bus_error_message(&error, r)); + + return 0; +} + +static int import_environment(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; + sd_bus *bus; + int r; + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SetEnvironment"); + if (r < 0) + return bus_log_create_error(r); + + if (argc < 2) + r = sd_bus_message_append_strv(m, environ); + else { + char **a, **b; + + r = sd_bus_message_open_container(m, 'a', "s"); + if (r < 0) + return bus_log_create_error(r); + + STRV_FOREACH(a, strv_skip(argv, 1)) { + + if (!env_name_is_valid(*a)) { + log_error("Not a valid environment variable name: %s", *a); + return -EINVAL; + } + + STRV_FOREACH(b, environ) { + const char *eq; + + eq = startswith(*b, *a); + if (eq && *eq == '=') { + + r = sd_bus_message_append(m, "s", *b); + if (r < 0) + return bus_log_create_error(r); + + break; + } + } + } + + r = sd_bus_message_close_container(m); + } + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, NULL); + if (r < 0) + return log_error_errno(r, "Failed to import environment: %s", bus_error_message(&error, r)); + + return 0; +} + +static int enable_sysv_units(const char *verb, char **args) { + int r = 0; + +#if defined(HAVE_SYSV_COMPAT) + unsigned f = 0; + _cleanup_lookup_paths_free_ LookupPaths paths = {}; + + if (arg_scope != UNIT_FILE_SYSTEM) + return 0; + + if (getenv_bool("SYSTEMCTL_SKIP_SYSV") > 0) + return 0; + + if (!STR_IN_SET(verb, + "enable", + "disable", + "is-enabled")) + return 0; + + /* Processes all SysV units, and reshuffles the array so that + * afterwards only the native units remain */ + + r = lookup_paths_init(&paths, MANAGER_SYSTEM, false, arg_root, NULL, NULL, NULL); + if (r < 0) + return r; + + r = 0; + while (args[f]) { + const char *name; + _cleanup_free_ char *p = NULL, *q = NULL, *l = NULL; + bool found_native = false, found_sysv; + unsigned c = 1; + const char *argv[6] = { ROOTLIBEXECDIR "/systemd-sysv-install", NULL, NULL, NULL, NULL }; + char **k; + int j; + pid_t pid; + siginfo_t status; + + name = args[f++]; + + if (!endswith(name, ".service")) + continue; + + if (path_is_absolute(name)) + continue; + + STRV_FOREACH(k, paths.unit_path) { + _cleanup_free_ char *path = NULL; + + path = path_join(arg_root, *k, name); + if (!path) + return log_oom(); + + found_native = access(path, F_OK) >= 0; + if (found_native) + break; + } + + /* If we have both a native unit and a SysV script, + * enable/disable them both (below); for is-enabled, prefer the + * native unit */ + if (found_native && streq(verb, "is-enabled")) + continue; + + p = path_join(arg_root, SYSTEM_SYSVINIT_PATH, name); + if (!p) + return log_oom(); + + p[strlen(p) - strlen(".service")] = 0; + found_sysv = access(p, F_OK) >= 0; + if (!found_sysv) + continue; + + if (found_native) + log_info("Synchronizing state of %s with SysV init 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); + + argv[c++] = verb; + argv[c++] = basename(p); + argv[c] = NULL; + + l = strv_join((char**)argv, " "); + if (!l) + return log_oom(); + + log_info("Executing %s", l); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + else if (pid == 0) { + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + execv(argv[0], (char**) argv); + log_error_errno(r, "Failed to execute %s: %m", argv[0]); + _exit(EXIT_FAILURE); + } + + j = wait_for_terminate(pid, &status); + if (j < 0) { + log_error_errno(j, "Failed to wait for child: %m"); + return j; + } + + if (status.si_code == CLD_EXITED) { + if (streq(verb, "is-enabled")) { + if (status.si_status == 0) { + if (!arg_quiet) + puts("enabled"); + r = 1; + } else { + if (!arg_quiet) + puts("disabled"); + } + + } else if (status.si_status != 0) + return -EINVAL; + } else + return -EPROTO; + + if (found_native) + continue; + + /* Remove this entry, so that we don't try enabling it as native unit */ + assert(f > 0); + f--; + assert(args[f] == name); + strv_remove(args, name); + } + +#endif + return r; +} + +static int mangle_names(char **original_names, char ***mangled_names) { + char **i, **l, **name; + int r; + + l = i = new(char*, strv_length(original_names) + 1); + if (!l) + return log_oom(); + + STRV_FOREACH(name, original_names) { + + /* When enabling units qualified path names are OK, + * too, hence allow them explicitly. */ + + if (is_path(*name)) { + *i = strdup(*name); + if (!*i) { + strv_free(l); + return log_oom(); + } + } else { + r = unit_name_mangle(*name, UNIT_NAME_NOGLOB, i); + if (r < 0) { + strv_free(l); + return log_error_errno(r, "Failed to mangle unit name: %m"); + } + } + + i++; + } + + *i = NULL; + *mangled_names = l; + + return 0; +} + +static int enable_unit(int argc, char *argv[], void *userdata) { + _cleanup_strv_free_ char **names = NULL; + const char *verb = argv[0]; + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + int carries_install_info = -1; + int r; + + if (!argv[1]) + return 0; + + r = mangle_names(strv_skip(argv, 1), &names); + if (r < 0) + return r; + + r = enable_sysv_units(verb, names); + if (r < 0) + return r; + + /* If the operation was fully executed by the SysV compat, + * let's finish early */ + if (strv_isempty(names)) + return 0; + + if (install_client_side()) { + if (streq(verb, "enable")) { + r = unit_file_enable(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(verb, "disable")) + r = unit_file_disable(arg_scope, arg_runtime, arg_root, names, &changes, &n_changes); + else if (streq(verb, "reenable")) { + r = unit_file_reenable(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(verb, "link")) + r = unit_file_link(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); + else if (streq(verb, "preset")) { + r = unit_file_preset(arg_scope, arg_runtime, arg_root, names, arg_preset_mode, arg_force, &changes, &n_changes); + carries_install_info = r; + } else if (streq(verb, "mask")) + r = unit_file_mask(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); + else if (streq(verb, "unmask")) + r = unit_file_unmask(arg_scope, arg_runtime, arg_root, names, &changes, &n_changes); + else + assert_not_reached("Unknown verb"); + + if (r == -ESHUTDOWN) + return log_error_errno(r, "Unit file is masked."); + if (r < 0) + return log_error_errno(r, "Operation failed: %m"); + + if (!arg_quiet) + dump_unit_file_changes(changes, n_changes); + + r = 0; + } else { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int expect_carries_install_info = false; + bool send_force = true, send_preset_mode = false; + const char *method; + sd_bus *bus; + + polkit_agent_open_if_enabled(); + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + if (streq(verb, "enable")) { + method = "EnableUnitFiles"; + expect_carries_install_info = true; + } else if (streq(verb, "disable")) { + method = "DisableUnitFiles"; + send_force = false; + } else if (streq(verb, "reenable")) { + method = "ReenableUnitFiles"; + expect_carries_install_info = true; + } else if (streq(verb, "link")) + method = "LinkUnitFiles"; + else if (streq(verb, "preset")) { + + if (arg_preset_mode != UNIT_FILE_PRESET_FULL) { + method = "PresetUnitFilesWithMode"; + send_preset_mode = true; + } else + method = "PresetUnitFiles"; + + expect_carries_install_info = true; + } else if (streq(verb, "mask")) + method = "MaskUnitFiles"; + else if (streq(verb, "unmask")) { + method = "UnmaskUnitFiles"; + send_force = false; + } else + assert_not_reached("Unknown verb"); + + 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); + + r = sd_bus_message_append_strv(m, names); + if (r < 0) + return bus_log_create_error(r); + + if (send_preset_mode) { + r = sd_bus_message_append(m, "s", unit_file_preset_mode_to_string(arg_preset_mode)); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_append(m, "b", arg_runtime); + if (r < 0) + return bus_log_create_error(r); + + if (send_force) { + r = sd_bus_message_append(m, "b", arg_force); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to execute operation: %s", bus_error_message(&error, r)); + + if (expect_carries_install_info) { + r = sd_bus_message_read(reply, "b", &carries_install_info); + if (r < 0) + return bus_log_parse_error(r); + } + + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); + if (r < 0) + return r; + + /* Try to reload if enabled */ + if (!arg_no_reload) + r = daemon_reload(argc, argv, userdata); + else + r = 0; + } + + if (carries_install_info == 0) + log_warning("The unit files have no [Install] section. They are not meant to be enabled\n" + "using systemctl.\n" + "Possible reasons for having this kind of units are:\n" + "1) A unit may be statically enabled by being symlinked from another unit's\n" + " .wants/ or .requires/ directory.\n" + "2) A unit's purpose may be to act as a helper for some other unit which has\n" + " a requirement dependency on it.\n" + "3) A unit may be started when needed via activation (socket, path, timer,\n" + " D-Bus, udev, scripted systemctl call, ...).\n"); + + if (arg_now && n_changes > 0 && STR_IN_SET(argv[0], "enable", "disable", "mask")) { + char *new_args[n_changes + 2]; + sd_bus *bus; + unsigned i; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + goto finish; + + new_args[0] = (char*) (streq(argv[0], "enable") ? "start" : "stop"); + for (i = 0; i < n_changes; i++) + new_args[i + 1] = basename(changes[i].path); + new_args[i + 1] = NULL; + + r = start_unit(strv_length(new_args), new_args, userdata); + } + +finish: + unit_file_changes_free(changes, n_changes); + + return r; +} + +static int add_dependency(int argc, char *argv[], void *userdata) { + _cleanup_strv_free_ char **names = NULL; + _cleanup_free_ char *target = NULL; + const char *verb = argv[0]; + UnitDependency dep; + int r = 0; + + if (!argv[1]) + return 0; + + r = unit_name_mangle_with_suffix(argv[1], UNIT_NAME_NOGLOB, ".target", &target); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + r = mangle_names(strv_skip(argv, 2), &names); + if (r < 0) + return r; + + if (streq(verb, "add-wants")) + dep = UNIT_WANTS; + else if (streq(verb, "add-requires")) + dep = UNIT_REQUIRES; + else + assert_not_reached("Unknown verb"); + + if (install_client_side()) { + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + + r = unit_file_add_dependency(arg_scope, arg_runtime, arg_root, names, target, dep, arg_force, &changes, &n_changes); + if (r == -ESHUTDOWN) + return log_error_errno(r, "Unit file is masked."); + if (r < 0) + return log_error_errno(r, "Can't add dependency: %m"); + + if (!arg_quiet) + dump_unit_file_changes(changes, n_changes); + + unit_file_changes_free(changes, n_changes); + + } else { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; + _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; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "AddDependencyUnitFiles"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append_strv(m, names); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "ssbb", target, unit_dependency_to_string(dep), arg_runtime, arg_force); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to execute operation: %s", bus_error_message(&error, r)); + + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); + if (r < 0) + return r; + + if (!arg_no_reload) + r = daemon_reload(argc, argv, userdata); + else + r = 0; + } + + return r; +} + +static int preset_all(int argc, char *argv[], void *userdata) { + UnitFileChange *changes = NULL; + unsigned n_changes = 0; + int r; + + if (install_client_side()) { + + r = unit_file_preset_all(arg_scope, arg_runtime, arg_root, arg_preset_mode, arg_force, &changes, &n_changes); + if (r < 0) { + log_error_errno(r, "Operation failed: %m"); + goto finish; + } + + if (!arg_quiet) + dump_unit_file_changes(changes, n_changes); + + r = 0; + + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _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; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "PresetAllUnitFiles", + &error, + &reply, + "sbb", + unit_file_preset_mode_to_string(arg_preset_mode), + arg_runtime, + arg_force); + if (r < 0) + return log_error_errno(r, "Failed to execute operation: %s", bus_error_message(&error, r)); + + r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); + if (r < 0) + return r; + + if (!arg_no_reload) + r = daemon_reload(argc, argv, userdata); + else + r = 0; + } + +finish: + unit_file_changes_free(changes, n_changes); + + return r; +} + +static int unit_is_enabled(int argc, char *argv[], void *userdata) { + + _cleanup_strv_free_ char **names = NULL; + bool enabled; + char **name; + int r; + + r = mangle_names(strv_skip(argv, 1), &names); + if (r < 0) + return r; + + r = enable_sysv_units(argv[0], names); + if (r < 0) + return r; + + enabled = r > 0; + + if (install_client_side()) { + + STRV_FOREACH(name, names) { + UnitFileState state; + + r = unit_file_get_state(arg_scope, arg_root, *name, &state); + if (r < 0) + return log_error_errno(state, "Failed to get unit file state for %s: %m", *name); + + if (IN_SET(state, + UNIT_FILE_ENABLED, + UNIT_FILE_ENABLED_RUNTIME, + UNIT_FILE_STATIC, + UNIT_FILE_INDIRECT)) + enabled = true; + + if (!arg_quiet) + puts(unit_file_state_to_string(state)); + } + + } else { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + STRV_FOREACH(name, names) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *s; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnitFileState", + &error, + &reply, + "s", *name); + if (r < 0) + return log_error_errno(r, "Failed to get unit file state for %s: %s", *name, bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "s", &s); + if (r < 0) + return bus_log_parse_error(r); + + if (STR_IN_SET(s, "enabled", "enabled-runtime", "static", "indirect")) + enabled = true; + + if (!arg_quiet) + puts(s); + } + } + + return !enabled; +} + +static int is_system_running(int argc, char *argv[], void *userdata) { + _cleanup_free_ char *state = NULL; + sd_bus *bus; + int r; + + if (running_in_chroot() > 0 || (arg_transport == BUS_TRANSPORT_LOCAL && !sd_booted())) { + if (!arg_quiet) + puts("offline"); + return EXIT_FAILURE; + } + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SystemState", + NULL, + &state); + if (r < 0) { + if (!arg_quiet) + puts("unknown"); + return 0; + } + + if (!arg_quiet) + puts(state); + + return streq(state, "running") ? EXIT_SUCCESS : EXIT_FAILURE; +} + +static int create_edit_temp_file(const char *new_path, const char *original_path, char **ret_tmp_fn) { + _cleanup_free_ char *t = NULL; + int r; + + assert(new_path); + assert(original_path); + assert(ret_tmp_fn); + + r = tempfn_random(new_path, NULL, &t); + if (r < 0) + return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", new_path); + + r = mkdir_parents(new_path, 0755); + if (r < 0) + return log_error_errno(r, "Failed to create directories for \"%s\": %m", new_path); + + r = copy_file(original_path, t, 0, 0644, 0); + if (r == -ENOENT) { + + r = touch(t); + if (r < 0) + 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); + + *ret_tmp_fn = t; + t = NULL; + + return 0; +} + +static int get_file_to_edit(const char *name, const char *user_home, const char *user_runtime, char **ret_path) { + _cleanup_free_ char *path = NULL, *path2 = NULL, *run = NULL; + + assert(name); + assert(ret_path); + + switch (arg_scope) { + case UNIT_FILE_SYSTEM: + path = path_join(arg_root, SYSTEM_CONFIG_UNIT_PATH, name); + if (arg_runtime) + run = path_join(arg_root, "/run/systemd/system/", name); + break; + case UNIT_FILE_GLOBAL: + path = path_join(arg_root, USER_CONFIG_UNIT_PATH, name); + if (arg_runtime) + run = path_join(arg_root, "/run/systemd/user/", name); + break; + case UNIT_FILE_USER: + assert(user_home); + assert(user_runtime); + + path = path_join(arg_root, user_home, name); + if (arg_runtime) { + path2 = path_join(arg_root, USER_CONFIG_UNIT_PATH, name); + if (!path2) + return log_oom(); + run = path_join(arg_root, user_runtime, name); + } + break; + default: + assert_not_reached("Invalid scope"); + } + if (!path || (arg_runtime && !run)) + return log_oom(); + + if (arg_runtime) { + if (access(path, F_OK) >= 0) { + log_error("Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.", run, path); + return -EEXIST; + } + + if (path2 && access(path2, F_OK) >= 0) { + log_error("Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.", run, path2); + return -EEXIST; + } + + *ret_path = run; + run = NULL; + } else { + *ret_path = path; + path = NULL; + } + + return 0; +} + +static int unit_file_create_dropin(const char *unit_name, const char *user_home, const char *user_runtime, char **ret_new_path, char **ret_tmp_path) { + char *tmp_new_path, *tmp_tmp_path, *ending; + int r; + + assert(unit_name); + assert(ret_new_path); + assert(ret_tmp_path); + + ending = strjoina(unit_name, ".d/override.conf"); + r = get_file_to_edit(ending, user_home, user_runtime, &tmp_new_path); + if (r < 0) + return r; + + r = create_edit_temp_file(tmp_new_path, tmp_new_path, &tmp_tmp_path); + if (r < 0) { + free(tmp_new_path); + return r; + } + + *ret_new_path = tmp_new_path; + *ret_tmp_path = tmp_tmp_path; + + return 0; +} + +static int unit_file_create_copy( + const char *unit_name, + const char *fragment_path, + const char *user_home, + const char *user_runtime, + char **ret_new_path, + char **ret_tmp_path) { + + char *tmp_new_path, *tmp_tmp_path; + int r; + + assert(fragment_path); + assert(unit_name); + assert(ret_new_path); + assert(ret_tmp_path); + + r = get_file_to_edit(unit_name, user_home, user_runtime, &tmp_new_path); + if (r < 0) + return r; + + if (!path_equal(fragment_path, tmp_new_path) && access(tmp_new_path, F_OK) == 0) { + char response; + + r = ask_char(&response, "yn", "\"%s\" already exists. Overwrite with \"%s\"? [(y)es, (n)o] ", tmp_new_path, fragment_path); + if (r < 0) { + free(tmp_new_path); + return r; + } + if (response != 'y') { + log_warning("%s ignored", unit_name); + free(tmp_new_path); + return -1; + } + } + + 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; + } + + *ret_new_path = tmp_new_path; + *ret_tmp_path = tmp_tmp_path; + + return 0; +} + +static int run_editor(char **paths) { + pid_t pid; + int r; + + assert(paths); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + + if (pid == 0) { + const char **args; + char *editor, **editor_args = NULL; + char **tmp_path, **original_path, *p; + unsigned n_editor_args = 0, i = 1; + size_t argc; + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + argc = strv_length(paths)/2 + 1; + + /* SYSTEMD_EDITOR takes precedence over EDITOR which takes precedence over VISUAL + * If neither SYSTEMD_EDITOR nor EDITOR nor VISUAL are present, + * we try to execute well known editors + */ + editor = getenv("SYSTEMD_EDITOR"); + if (!editor) + editor = getenv("EDITOR"); + if (!editor) + editor = getenv("VISUAL"); + + if (!isempty(editor)) { + editor_args = strv_split(editor, WHITESPACE); + if (!editor_args) { + (void) log_oom(); + _exit(EXIT_FAILURE); + } + n_editor_args = strv_length(editor_args); + argc += n_editor_args - 1; + } + args = newa(const char*, argc + 1); + + if (n_editor_args > 0) { + args[0] = editor_args[0]; + for (; i < n_editor_args; i++) + args[i] = editor_args[i]; + } + + STRV_FOREACH_PAIR(original_path, tmp_path, paths) { + args[i] = *tmp_path; + i++; + } + args[i] = NULL; + + if (n_editor_args > 0) + execvp(args[0], (char* const*) args); + + FOREACH_STRING(p, "editor", "nano", "vim", "vi") { + args[0] = p; + execvp(p, (char* const*) args); + /* We do not fail if the editor doesn't exist + * because we want to try each one of them before + * failing. + */ + if (errno != ENOENT) { + log_error_errno(errno, "Failed to execute %s: %m", editor); + _exit(EXIT_FAILURE); + } + } + + log_error("Cannot edit unit(s), no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL."); + _exit(EXIT_FAILURE); + } + + r = wait_for_terminate_and_warn("editor", pid, true); + if (r < 0) + return log_error_errno(r, "Failed to wait for child: %m"); + + return 0; +} + +static int find_paths_to_edit(sd_bus *bus, char **names, char ***paths) { + _cleanup_free_ char *user_home = NULL; + _cleanup_free_ char *user_runtime = NULL; + _cleanup_lookup_paths_free_ LookupPaths lp = {}; + char **name; + int r; + + assert(names); + assert(paths); + + r = init_home_and_lookup_paths(&user_home, &user_runtime, &lp); + if (r < 0) + return r; + + STRV_FOREACH(name, names) { + _cleanup_free_ char *path = NULL; + char *new_path, *tmp_path; + + 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; + } + + if (arg_full) + r = unit_file_create_copy(*name, path, user_home, user_runtime, &new_path, &tmp_path); + else + r = unit_file_create_dropin(*name, user_home, user_runtime, &new_path, &tmp_path); + if (r < 0) + return r; + + r = strv_push_pair(paths, new_path, tmp_path); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int edit(int argc, char *argv[], void *userdata) { + _cleanup_strv_free_ char **names = NULL; + _cleanup_strv_free_ char **paths = NULL; + char **original, **tmp; + sd_bus *bus; + int r; + + if (!on_tty()) { + log_error("Cannot edit units if not on a tty."); + return -EINVAL; + } + + if (arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Cannot edit units remotely."); + return -EINVAL; + } + + r = acquire_bus(BUS_MANAGER, &bus); + if (r < 0) + return r; + + r = expand_names(bus, strv_skip(argv, 1), NULL, &names); + if (r < 0) + return log_error_errno(r, "Failed to expand names: %m"); + + r = find_paths_to_edit(bus, names, &paths); + if (r < 0) + return r; + + if (strv_isempty(paths)) + return -ENOENT; + + r = run_editor(paths); + if (r < 0) + goto end; + + STRV_FOREACH_PAIR(original, tmp, paths) { + /* If the temporary file is empty we ignore it. It's + * useful if the user wants to cancel its modification + */ + if (null_or_empty_path(*tmp)) { + log_warning("Editing \"%s\" canceled: temporary file is empty.", *original); + continue; + } + + r = rename(*tmp, *original); + if (r < 0) { + r = log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", *tmp, *original); + goto end; + } + } + + r = 0; + + if (!arg_no_reload && !install_client_side()) + r = daemon_reload(argc, argv, userdata); + +end: + STRV_FOREACH_PAIR(original, tmp, paths) + (void) unlink(*tmp); + + return r; +} + +static void systemctl_help(void) { + + pager_open_if_enabled(); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Query or send control commands to the systemd manager.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --system Connect to system manager\n" + " --user Connect to user service manager\n" + " -H --host=[USER@]HOST\n" + " Operate on remote host\n" + " -M --machine=CONTAINER\n" + " Operate on local container\n" + " -t --type=TYPE List units of a particular type\n" + " --state=STATE List units with particular LOAD or SUB or ACTIVE state\n" + " -p --property=NAME Show only properties by this name\n" + " -a --all Show all loaded units/properties, including dead/empty\n" + " ones. To list all units installed on the system, use\n" + " the 'list-unit-files' command instead.\n" + " -l --full Don't ellipsize unit names on output\n" + " -r --recursive Show unit list of host and local containers\n" + " --reverse Show reverse dependencies with 'list-dependencies'\n" + " --job-mode=MODE Specify how to deal with already queued jobs, when\n" + " queueing a new job\n" + " --show-types When showing sockets, explicitly show their type\n" + " -i --ignore-inhibitors\n" + " When shutting down or sleeping, ignore inhibitors\n" + " --kill-who=WHO Who to send signal to\n" + " -s --signal=SIGNAL Which signal to send\n" + " --now Start or stop unit in addition to enabling or disabling it\n" + " -q --quiet Suppress output\n" + " --no-block Do not wait until operation finished\n" + " --no-wall Don't send wall message before halt/power-off/reboot\n" + " --no-reload Don't reload daemon after en-/dis-abling unit files\n" + " --no-legend Do not print a legend (column headers and hints)\n" + " --no-pager Do not pipe output into a pager\n" + " --no-ask-password\n" + " Do not ask for system passwords\n" + " --global Enable/disable unit files globally\n" + " --runtime Enable unit files only temporarily until next reboot\n" + " -f --force When enabling unit files, override existing symlinks\n" + " When shutting down, execute action immediately\n" + " --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" + " --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" + " list-units [PATTERN...] List loaded units\n" + " list-sockets [PATTERN...] List loaded sockets ordered by address\n" + " list-timers [PATTERN...] List loaded timers ordered by next elapse\n" + " start NAME... Start (activate) one or more units\n" + " stop NAME... Stop (deactivate) one or more units\n" + " reload NAME... Reload one or more units\n" + " restart NAME... Start or restart one or more units\n" + " try-restart NAME... Restart one or more units if active\n" + " reload-or-restart NAME... Reload one or more units if possible,\n" + " otherwise start or restart\n" + " try-reload-or-restart NAME... If active, reload one or more units,\n" + " if supported, otherwise restart\n" + " isolate NAME Start one unit and stop all others\n" + " kill NAME... Send signal to processes of a unit\n" + " is-active PATTERN... Check whether units are active\n" + " is-failed PATTERN... Check whether units are failed\n" + " status [PATTERN...|PID...] Show runtime status of one or more units\n" + " show [PATTERN...|JOB...] Show properties of one or more\n" + " units/jobs or the manager\n" + " cat PATTERN... Show files and drop-ins of one or more units\n" + " set-property NAME ASSIGNMENT... Sets one or more properties of a unit\n" + " help PATTERN...|PID... Show manual for one or more units\n" + " reset-failed [PATTERN...] Reset failed state for all, one, or more\n" + " units\n" + " list-dependencies [NAME] Recursively show units which are required\n" + " or wanted by this unit or by which this\n" + " 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" + " 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" + " based on preset configuration\n" + " preset-all Enable/disable all unit files based on\n" + " preset configuration\n" + " is-enabled NAME... Check whether unit files are enabled\n" + " mask NAME... Mask one or more units\n" + " unmask NAME... Unmask one or more units\n" + " link PATH... Link one or more units files into\n" + " the search path\n" + " add-wants TARGET NAME... Add 'Wants' dependency for the target\n" + " on specified one or more units\n" + " add-requires TARGET NAME... Add 'Requires' dependency for the target\n" + " on specified one or more units\n" + " edit NAME... Edit one or more unit files\n" + " get-default Get the name of the default target\n" + " set-default NAME Set the default target\n\n" + "Machine Commands:\n" + " list-machines [PATTERN...] List local containers and host\n\n" + "Job Commands:\n" + " list-jobs [PATTERN...] List jobs\n" + " cancel [JOB...] Cancel all, one, or more jobs\n\n" + "Environment Commands:\n" + " show-environment Dump environment\n" + " set-environment NAME=VALUE... Set one or more environment variables\n" + " unset-environment NAME... Unset one or more environment variables\n" + " import-environment [NAME...] Import all or some environment variables\n\n" + "Manager Lifecycle Commands:\n" + " daemon-reload Reload systemd manager configuration\n" + " daemon-reexec Reexecute systemd manager\n\n" + "System Commands:\n" + " is-system-running Check whether system is fully running\n" + " default Enter system default mode\n" + " rescue Enter system rescue mode\n" + " emergency Enter system emergency mode\n" + " halt Shut down and halt the system\n" + " poweroff Shut down and power-off the system\n" + " reboot [ARG] Shut down and reboot the system\n" + " kexec Shut down and reboot the system with kexec\n" + " exit [EXIT_CODE] Request user instance or container exit\n" + " switch-root ROOT [INIT] Change to a different root file system\n" + " suspend Suspend the system\n" + " hibernate Hibernate the system\n" + " hybrid-sleep Hibernate and suspend the system\n", + program_invocation_short_name); +} + +static void halt_help(void) { + printf("%s [OPTIONS...]%s\n\n" + "%s the system.\n\n" + " --help Show this help\n" + " --halt Halt the machine\n" + " -p --poweroff Switch off the machine\n" + " --reboot Reboot the machine\n" + " -f --force Force immediate halt/power-off/reboot\n" + " -w --wtmp-only Don't halt/power-off/reboot, just write wtmp record\n" + " -d --no-wtmp Don't write wtmp record\n" + " --no-wall Don't send wall message before halt/power-off/reboot\n", + program_invocation_short_name, + arg_action == ACTION_REBOOT ? " [ARG]" : "", + arg_action == ACTION_REBOOT ? "Reboot" : + arg_action == ACTION_POWEROFF ? "Power off" : + "Halt"); +} + +static void shutdown_help(void) { + printf("%s [OPTIONS...] [TIME] [WALL...]\n\n" + "Shut down the system.\n\n" + " --help Show this help\n" + " -H --halt Halt the machine\n" + " -P --poweroff Power-off the machine\n" + " -r --reboot Reboot the machine\n" + " -h Equivalent to --poweroff, overridden by --halt\n" + " -k Don't halt/power-off/reboot, just send warnings\n" + " --no-wall Don't send wall message before halt/power-off/reboot\n" + " -c Cancel a pending shutdown\n", + program_invocation_short_name); +} + +static void telinit_help(void) { + printf("%s [OPTIONS...] {COMMAND}\n\n" + "Send control commands to the init daemon.\n\n" + " --help Show this help\n" + " --no-wall Don't send wall message before halt/power-off/reboot\n\n" + "Commands:\n" + " 0 Power-off the machine\n" + " 6 Reboot the machine\n" + " 2, 3, 4, 5 Start runlevelX.target unit\n" + " 1, s, S Enter rescue mode\n" + " q, Q Reload init daemon configuration\n" + " u, U Reexecute init daemon\n", + program_invocation_short_name); +} + +static void runlevel_help(void) { + printf("%s [OPTIONS...]\n\n" + "Prints the previous and current runlevel of the init system.\n\n" + " --help Show this help\n", + program_invocation_short_name); +} + +static void help_types(void) { + int i; + + if (!arg_no_legend) + puts("Available unit types:"); + for (i = 0; i < _UNIT_TYPE_MAX; i++) + puts(unit_type_to_string(i)); +} + +static void help_states(void) { + int i; + + if (!arg_no_legend) + puts("Available unit load states:"); + for (i = 0; i < _UNIT_LOAD_STATE_MAX; i++) + puts(unit_load_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable unit active states:"); + for (i = 0; i < _UNIT_ACTIVE_STATE_MAX; i++) + puts(unit_active_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable automount unit substates:"); + for (i = 0; i < _AUTOMOUNT_STATE_MAX; i++) + puts(automount_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable busname unit substates:"); + for (i = 0; i < _BUSNAME_STATE_MAX; i++) + puts(busname_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable device unit substates:"); + for (i = 0; i < _DEVICE_STATE_MAX; i++) + puts(device_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable mount unit substates:"); + for (i = 0; i < _MOUNT_STATE_MAX; i++) + puts(mount_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable path unit substates:"); + for (i = 0; i < _PATH_STATE_MAX; i++) + puts(path_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable scope unit substates:"); + for (i = 0; i < _SCOPE_STATE_MAX; i++) + puts(scope_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable service unit substates:"); + for (i = 0; i < _SERVICE_STATE_MAX; i++) + puts(service_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable slice unit substates:"); + for (i = 0; i < _SLICE_STATE_MAX; i++) + puts(slice_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable socket unit substates:"); + for (i = 0; i < _SOCKET_STATE_MAX; i++) + puts(socket_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable swap unit substates:"); + for (i = 0; i < _SWAP_STATE_MAX; i++) + puts(swap_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable target unit substates:"); + for (i = 0; i < _TARGET_STATE_MAX; i++) + puts(target_state_to_string(i)); + + if (!arg_no_legend) + puts("\nAvailable timer unit substates:"); + for (i = 0; i < _TIMER_STATE_MAX; i++) + puts(timer_state_to_string(i)); +} + +static int systemctl_parse_argv(int argc, char *argv[]) { + + enum { + ARG_FAIL = 0x100, + ARG_REVERSE, + ARG_AFTER, + ARG_BEFORE, + ARG_SHOW_TYPES, + ARG_IRREVERSIBLE, + ARG_IGNORE_DEPENDENCIES, + ARG_VERSION, + ARG_USER, + ARG_SYSTEM, + ARG_GLOBAL, + ARG_NO_BLOCK, + ARG_NO_LEGEND, + ARG_NO_PAGER, + ARG_NO_WALL, + ARG_ROOT, + ARG_NO_RELOAD, + ARG_KILL_WHO, + ARG_NO_ASK_PASSWORD, + ARG_FAILED, + ARG_RUNTIME, + ARG_FORCE, + ARG_PLAIN, + ARG_STATE, + ARG_JOB_MODE, + ARG_PRESET_MODE, + ARG_FIRMWARE_SETUP, + ARG_NOW, + ARG_MESSAGE, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "type", required_argument, NULL, 't' }, + { "property", required_argument, NULL, 'p' }, + { "all", no_argument, NULL, 'a' }, + { "reverse", no_argument, NULL, ARG_REVERSE }, + { "after", no_argument, NULL, ARG_AFTER }, + { "before", no_argument, NULL, ARG_BEFORE }, + { "show-types", no_argument, NULL, ARG_SHOW_TYPES }, + { "failed", no_argument, NULL, ARG_FAILED }, /* compatibility only */ + { "full", no_argument, NULL, 'l' }, + { "job-mode", required_argument, NULL, ARG_JOB_MODE }, + { "fail", no_argument, NULL, ARG_FAIL }, /* compatibility only */ + { "irreversible", no_argument, NULL, ARG_IRREVERSIBLE }, /* compatibility only */ + { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES }, /* compatibility only */ + { "ignore-inhibitors", no_argument, NULL, 'i' }, + { "user", no_argument, NULL, ARG_USER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "global", no_argument, NULL, ARG_GLOBAL }, + { "no-block", no_argument, NULL, ARG_NO_BLOCK }, + { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "no-wall", no_argument, NULL, ARG_NO_WALL }, + { "quiet", no_argument, NULL, 'q' }, + { "root", required_argument, NULL, ARG_ROOT }, + { "force", no_argument, NULL, ARG_FORCE }, + { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, + { "kill-who", required_argument, NULL, ARG_KILL_WHO }, + { "signal", required_argument, NULL, 's' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "runtime", no_argument, NULL, ARG_RUNTIME }, + { "lines", required_argument, NULL, 'n' }, + { "output", required_argument, NULL, 'o' }, + { "plain", no_argument, NULL, ARG_PLAIN }, + { "state", required_argument, NULL, ARG_STATE }, + { "recursive", no_argument, NULL, 'r' }, + { "preset-mode", required_argument, NULL, ARG_PRESET_MODE }, + { "firmware-setup", no_argument, NULL, ARG_FIRMWARE_SETUP }, + { "now", no_argument, NULL, ARG_NOW }, + { "message", required_argument, NULL, ARG_MESSAGE }, + {} + }; + + const char *p; + int c, r; + + assert(argc >= 0); + assert(argv); + + /* we default to allowing interactive authorization only in systemctl (not in the legacy commands) */ + arg_ask_password = true; + + while ((c = getopt_long(argc, argv, "ht:p:alqfs:H:M:n:o:ir", options, NULL)) >= 0) + + switch (c) { + + case 'h': + systemctl_help(); + return 0; + + case ARG_VERSION: + return version(); + + case 't': { + if (isempty(optarg)) { + log_error("--type requires arguments."); + return -EINVAL; + } + + p = optarg; + for(;;) { + _cleanup_free_ char *type = NULL; + + r = extract_first_word(&p, &type, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse type: %s", optarg); + + if (r == 0) + break; + + if (streq(type, "help")) { + help_types(); + return 0; + } + + if (unit_type_from_string(type) >= 0) { + if (strv_push(&arg_types, type) < 0) + return log_oom(); + type = NULL; + continue; + } + + /* It's much nicer to use --state= for + * load states, but let's support this + * in --types= too for compatibility + * with old versions */ + if (unit_load_state_from_string(type) >= 0) { + if (strv_push(&arg_states, type) < 0) + return log_oom(); + type = NULL; + continue; + } + + log_error("Unknown unit type or load state '%s'.", type); + log_info("Use -t help to see a list of allowed values."); + return -EINVAL; + } + + break; + } + + case 'p': { + /* Make sure that if the empty property list + was specified, we won't show any properties. */ + if (isempty(optarg) && !arg_properties) { + arg_properties = new0(char*, 1); + if (!arg_properties) + return log_oom(); + } else { + p = optarg; + for(;;) { + _cleanup_free_ char *prop = NULL; + + r = extract_first_word(&p, &prop, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse property: %s", optarg); + + if (r == 0) + break; + + if (strv_push(&arg_properties, prop) < 0) + return log_oom(); + + prop = NULL; + } + } + + /* If the user asked for a particular + * property, show it to him, even if it is + * empty. */ + arg_all = true; + + break; + } + + case 'a': + arg_all = true; + break; + + case ARG_REVERSE: + arg_dependency = DEPENDENCY_REVERSE; + break; + + case ARG_AFTER: + arg_dependency = DEPENDENCY_AFTER; + break; + + case ARG_BEFORE: + arg_dependency = DEPENDENCY_BEFORE; + break; + + case ARG_SHOW_TYPES: + arg_show_types = true; + break; + + case ARG_JOB_MODE: + arg_job_mode = optarg; + break; + + case ARG_FAIL: + arg_job_mode = "fail"; + break; + + case ARG_IRREVERSIBLE: + arg_job_mode = "replace-irreversibly"; + break; + + case ARG_IGNORE_DEPENDENCIES: + arg_job_mode = "ignore-dependencies"; + break; + + case ARG_USER: + arg_scope = UNIT_FILE_USER; + break; + + case ARG_SYSTEM: + arg_scope = UNIT_FILE_SYSTEM; + break; + + case ARG_GLOBAL: + arg_scope = UNIT_FILE_GLOBAL; + break; + + case ARG_NO_BLOCK: + arg_no_block = true; + break; + + case ARG_NO_LEGEND: + arg_no_legend = true; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_NO_WALL: + arg_no_wall = true; + break; + + case ARG_ROOT: + r = parse_path_argument_and_warn(optarg, true, &arg_root); + if (r < 0) + return r; + break; + + case 'l': + arg_full = true; + break; + + case ARG_FAILED: + if (strv_extend(&arg_states, "failed") < 0) + return log_oom(); + + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_FORCE: + arg_force ++; + break; + + case 'f': + arg_force ++; + break; + + case ARG_NO_RELOAD: + arg_no_reload = true; + break; + + case ARG_KILL_WHO: + arg_kill_who = optarg; + break; + + case 's': + arg_signal = signal_from_string_try_harder(optarg); + if (arg_signal < 0) { + log_error("Failed to parse signal string %s.", optarg); + return -EINVAL; + } + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_RUNTIME: + arg_runtime = true; + break; + + case 'n': + if (safe_atou(optarg, &arg_lines) < 0) { + log_error("Failed to parse lines '%s'", optarg); + return -EINVAL; + } + break; + + case 'o': + arg_output = output_mode_from_string(optarg); + if (arg_output < 0) { + log_error("Unknown output '%s'.", optarg); + return -EINVAL; + } + break; + + case 'i': + arg_ignore_inhibitors = true; + break; + + case ARG_PLAIN: + arg_plain = true; + break; + + case ARG_FIRMWARE_SETUP: + arg_firmware_setup = true; + break; + + case ARG_STATE: { + if (isempty(optarg)) { + log_error("--signal requires arguments."); + return -EINVAL; + } + + p = optarg; + for(;;) { + _cleanup_free_ char *s = NULL; + + r = extract_first_word(&p, &s, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse signal: %s", optarg); + + if (r == 0) + break; + + if (streq(s, "help")) { + help_states(); + return 0; + } + + if (strv_push(&arg_states, s) < 0) + return log_oom(); + + s = NULL; + } + break; + } + + case 'r': + if (geteuid() != 0) { + log_error("--recursive requires root privileges."); + return -EPERM; + } + + arg_recursive = true; + break; + + case ARG_PRESET_MODE: + + arg_preset_mode = unit_file_preset_mode_from_string(optarg); + if (arg_preset_mode < 0) { + log_error("Failed to parse preset mode: %s.", optarg); + return -EINVAL; + } + + break; + + case ARG_NOW: + arg_now = true; + break; + + case ARG_MESSAGE: + if (strv_extend(&arg_wall, optarg) < 0) + return log_oom(); + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_transport != BUS_TRANSPORT_LOCAL && arg_scope != UNIT_FILE_SYSTEM) { + log_error("Cannot access user instance remotely."); + return -EINVAL; + } + + return 1; +} + +static int halt_parse_argv(int argc, char *argv[]) { + + enum { + ARG_HELP = 0x100, + ARG_HALT, + ARG_REBOOT, + ARG_NO_WALL + }; + + static const struct option options[] = { + { "help", no_argument, NULL, ARG_HELP }, + { "halt", no_argument, NULL, ARG_HALT }, + { "poweroff", no_argument, NULL, 'p' }, + { "reboot", no_argument, NULL, ARG_REBOOT }, + { "force", no_argument, NULL, 'f' }, + { "wtmp-only", no_argument, NULL, 'w' }, + { "no-wtmp", no_argument, NULL, 'd' }, + { "no-wall", no_argument, NULL, ARG_NO_WALL }, + {} + }; + + int c, r, runlevel; + + assert(argc >= 0); + assert(argv); + + if (utmp_get_runlevel(&runlevel, NULL) >= 0) + if (runlevel == '0' || runlevel == '6') + arg_force = 2; + + while ((c = getopt_long(argc, argv, "pfwdnih", options, NULL)) >= 0) + switch (c) { + + case ARG_HELP: + halt_help(); + return 0; + + case ARG_HALT: + arg_action = ACTION_HALT; + break; + + case 'p': + if (arg_action != ACTION_REBOOT) + arg_action = ACTION_POWEROFF; + break; + + case ARG_REBOOT: + arg_action = ACTION_REBOOT; + break; + + case 'f': + arg_force = 2; + break; + + case 'w': + arg_dry = true; + break; + + case 'd': + arg_no_wtmp = true; + break; + + case ARG_NO_WALL: + arg_no_wall = true; + break; + + case 'i': + case 'h': + case 'n': + /* Compatibility nops */ + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_action == ACTION_REBOOT && (argc == optind || argc == optind + 1)) { + r = update_reboot_param_file(argc == optind + 1 ? argv[optind] : NULL); + if (r < 0) + return r; + } else if (optind < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + return 1; +} + +static int parse_shutdown_time_spec(const char *t, usec_t *_u) { + assert(t); + assert(_u); + + if (streq(t, "now")) + *_u = 0; + else if (!strchr(t, ':')) { + uint64_t u; + + if (safe_atou64(t, &u) < 0) + return -EINVAL; + + *_u = now(CLOCK_REALTIME) + USEC_PER_MINUTE * u; + } else { + char *e = NULL; + long hour, minute; + struct tm tm = {}; + time_t s; + usec_t n; + + errno = 0; + hour = strtol(t, &e, 10); + if (errno > 0 || *e != ':' || hour < 0 || hour > 23) + return -EINVAL; + + minute = strtol(e+1, &e, 10); + if (errno > 0 || *e != 0 || minute < 0 || minute > 59) + return -EINVAL; + + n = now(CLOCK_REALTIME); + s = (time_t) (n / USEC_PER_SEC); + + assert_se(localtime_r(&s, &tm)); + + tm.tm_hour = (int) hour; + tm.tm_min = (int) minute; + tm.tm_sec = 0; + + assert_se(s = mktime(&tm)); + + *_u = (usec_t) s * USEC_PER_SEC; + + while (*_u <= n) + *_u += USEC_PER_DAY; + } + + return 0; +} + +static int shutdown_parse_argv(int argc, char *argv[]) { + + enum { + ARG_HELP = 0x100, + ARG_NO_WALL + }; + + static const struct option options[] = { + { "help", no_argument, NULL, ARG_HELP }, + { "halt", no_argument, NULL, 'H' }, + { "poweroff", no_argument, NULL, 'P' }, + { "reboot", no_argument, NULL, 'r' }, + { "kexec", no_argument, NULL, 'K' }, /* not documented extension */ + { "no-wall", no_argument, NULL, ARG_NO_WALL }, + {} + }; + + char **wall = NULL; + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "HPrhkKtafFc", options, NULL)) >= 0) + switch (c) { + + case ARG_HELP: + shutdown_help(); + return 0; + + case 'H': + arg_action = ACTION_HALT; + break; + + case 'P': + arg_action = ACTION_POWEROFF; + break; + + case 'r': + if (kexec_loaded()) + arg_action = ACTION_KEXEC; + else + arg_action = ACTION_REBOOT; + break; + + case 'K': + arg_action = ACTION_KEXEC; + break; + + case 'h': + if (arg_action != ACTION_HALT) + arg_action = ACTION_POWEROFF; + break; + + case 'k': + arg_dry = true; + break; + + case ARG_NO_WALL: + arg_no_wall = true; + break; + + case 't': + case 'a': + case 'f': + case 'F': + /* Compatibility nops */ + break; + + case 'c': + arg_action = ACTION_CANCEL_SHUTDOWN; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (argc > optind && arg_action != ACTION_CANCEL_SHUTDOWN) { + r = parse_shutdown_time_spec(argv[optind], &arg_when); + if (r < 0) { + log_error("Failed to parse time specification: %s", argv[optind]); + return r; + } + } else + arg_when = now(CLOCK_REALTIME) + USEC_PER_MINUTE; + + if (argc > optind && arg_action == ACTION_CANCEL_SHUTDOWN) + /* No time argument for shutdown cancel */ + wall = argv + optind; + else if (argc > optind + 1) + /* We skip the time argument */ + wall = argv + optind + 1; + + if (wall) { + arg_wall = strv_copy(wall); + if (!arg_wall) + return log_oom(); + } + + optind = argc; + + return 1; +} + +static int telinit_parse_argv(int argc, char *argv[]) { + + enum { + ARG_HELP = 0x100, + ARG_NO_WALL + }; + + static const struct option options[] = { + { "help", no_argument, NULL, ARG_HELP }, + { "no-wall", no_argument, NULL, ARG_NO_WALL }, + {} + }; + + static const struct { + char from; + enum action to; + } table[] = { + { '0', ACTION_POWEROFF }, + { '6', ACTION_REBOOT }, + { '1', ACTION_RESCUE }, + { '2', ACTION_RUNLEVEL2 }, + { '3', ACTION_RUNLEVEL3 }, + { '4', ACTION_RUNLEVEL4 }, + { '5', ACTION_RUNLEVEL5 }, + { 's', ACTION_RESCUE }, + { 'S', ACTION_RESCUE }, + { 'q', ACTION_RELOAD }, + { 'Q', ACTION_RELOAD }, + { 'u', ACTION_REEXEC }, + { 'U', ACTION_REEXEC } + }; + + unsigned i; + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) + switch (c) { + + case ARG_HELP: + telinit_help(); + return 0; + + case ARG_NO_WALL: + arg_no_wall = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind >= argc) { + log_error("%s: required argument missing.", program_invocation_short_name); + return -EINVAL; + } + + if (optind + 1 < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + if (strlen(argv[optind]) != 1) { + log_error("Expected single character argument."); + return -EINVAL; + } + + for (i = 0; i < ELEMENTSOF(table); i++) + if (table[i].from == argv[optind][0]) + break; + + if (i >= ELEMENTSOF(table)) { + log_error("Unknown command '%s'.", argv[optind]); + return -EINVAL; + } + + arg_action = table[i].to; + + optind ++; + + return 1; +} + +static int runlevel_parse_argv(int argc, char *argv[]) { + + enum { + ARG_HELP = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, ARG_HELP }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) + switch (c) { + + case ARG_HELP: + runlevel_help(); + return 0; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + return 1; +} + +static int parse_argv(int argc, char *argv[]) { + assert(argc >= 0); + assert(argv); + + if (program_invocation_short_name) { + + if (strstr(program_invocation_short_name, "halt")) { + arg_action = ACTION_HALT; + return halt_parse_argv(argc, argv); + } else if (strstr(program_invocation_short_name, "poweroff")) { + arg_action = ACTION_POWEROFF; + return halt_parse_argv(argc, argv); + } else if (strstr(program_invocation_short_name, "reboot")) { + if (kexec_loaded()) + arg_action = ACTION_KEXEC; + else + arg_action = ACTION_REBOOT; + return halt_parse_argv(argc, argv); + } else if (strstr(program_invocation_short_name, "shutdown")) { + arg_action = ACTION_POWEROFF; + return shutdown_parse_argv(argc, argv); + } else if (strstr(program_invocation_short_name, "init")) { + + if (sd_booted() > 0) { + arg_action = _ACTION_INVALID; + return telinit_parse_argv(argc, argv); + } else { + /* Hmm, so some other init system is + * running, we need to forward this + * request to it. For now we simply + * guess that it is Upstart. */ + + execv(TELINIT, argv); + + log_error("Couldn't find an alternative telinit implementation to spawn."); + return -EIO; + } + + } else if (strstr(program_invocation_short_name, "runlevel")) { + arg_action = ACTION_RUNLEVEL; + return runlevel_parse_argv(argc, argv); + } + } + + arg_action = ACTION_SYSTEMCTL; + return systemctl_parse_argv(argc, argv); +} + +_pure_ static int action_to_runlevel(void) { + + static const char table[_ACTION_MAX] = { + [ACTION_HALT] = '0', + [ACTION_POWEROFF] = '0', + [ACTION_REBOOT] = '6', + [ACTION_RUNLEVEL2] = '2', + [ACTION_RUNLEVEL3] = '3', + [ACTION_RUNLEVEL4] = '4', + [ACTION_RUNLEVEL5] = '5', + [ACTION_RESCUE] = '1' + }; + + assert(arg_action < _ACTION_MAX); + + return table[arg_action]; +} + +static int talk_initctl(void) { +#ifdef HAVE_SYSV_COMPAT + struct init_request request = { + .magic = INIT_MAGIC, + .sleeptime = 0, + .cmd = INIT_CMD_RUNLVL + }; + + _cleanup_close_ int fd = -1; + char rl; + int r; + + rl = action_to_runlevel(); + if (!rl) + return 0; + + request.runlevel = rl; + + fd = open(INIT_FIFO, O_WRONLY|O_NDELAY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open "INIT_FIFO": %m"); + } + + r = loop_write(fd, &request, sizeof(request), false); + if (r < 0) + return log_error_errno(r, "Failed to write to "INIT_FIFO": %m"); + + return 1; +#else + return 0; +#endif +} + +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 }, + { "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 }, + {} + }; + + return dispatch_verb(argc, argv, verbs, NULL); +} + +static int reload_with_fallback(void) { + + /* First, try systemd via D-Bus. */ + if (daemon_reload(0, NULL, NULL) >= 0) + return 0; + + /* Nothing else worked, so let's try signals */ + assert(arg_action == ACTION_RELOAD || arg_action == ACTION_REEXEC); + + if (kill(1, arg_action == ACTION_RELOAD ? SIGHUP : SIGTERM) < 0) + return log_error_errno(errno, "kill() failed: %m"); + + return 0; +} + +static int start_with_fallback(void) { + + /* First, try systemd via D-Bus. */ + if (start_unit(0, NULL, NULL) >= 0) + return 0; + + /* Nothing else worked, so let's try + * /dev/initctl */ + if (talk_initctl() > 0) + return 0; + + log_error("Failed to talk to init daemon."); + return -EIO; +} + +static int halt_now(enum action a) { + + /* The kernel will automaticall flush ATA disks and suchlike + * on reboot(), but the file systems need to be synce'd + * explicitly in advance. */ + (void) sync(); + + /* Make sure C-A-D is handled by the kernel from this point + * on... */ + (void) reboot(RB_ENABLE_CAD); + + switch (a) { + + case ACTION_HALT: + log_info("Halting."); + (void) reboot(RB_HALT_SYSTEM); + return -errno; + + case ACTION_POWEROFF: + log_info("Powering off."); + (void) reboot(RB_POWER_OFF); + return -errno; + + case ACTION_KEXEC: + case ACTION_REBOOT: { + _cleanup_free_ char *param = NULL; + + if (read_one_line_file(REBOOT_PARAM_FILE, ¶m) >= 0) { + log_info("Rebooting with argument '%s'.", param); + (void) syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, param); + } + + log_info("Rebooting."); + (void) reboot(RB_AUTOBOOT); + return -errno; + } + + default: + assert_not_reached("Unknown action."); + } +} + +static int logind_schedule_shutdown(void) { + +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + char date[FORMAT_TIMESTAMP_MAX]; + const char *action; + sd_bus *bus; + int r; + + (void) logind_set_wall_message(); + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + switch (arg_action) { + case ACTION_HALT: + action = "halt"; + break; + case ACTION_POWEROFF: + action = "poweroff"; + break; + case ACTION_KEXEC: + action = "kexec"; + break; + case ACTION_EXIT: + action = "exit"; + break; + case ACTION_REBOOT: + default: + action = "reboot"; + break; + } + + if (arg_dry) + action = strjoina("dry-", action); + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "ScheduleShutdown", + &error, + NULL, + "st", + action, + arg_when); + if (r < 0) + return log_warning_errno(r, "Failed to call ScheduleShutdown in logind, proceeding with immediate shutdown: %s", bus_error_message(&error, r)); + + log_info("Shutdown scheduled for %s, use 'shutdown -c' to cancel.", format_timestamp(date, sizeof(date), arg_when)); + return 0; +#else + log_error("Cannot schedule shutdown without logind support, proceeding with immediate shutdown."); + return -ENOSYS; +#endif +} + +static int halt_main(void) { + int r; + + r = logind_check_inhibitors(arg_action); + if (r < 0) + return r; + + if (arg_when > 0) + return logind_schedule_shutdown(); + + if (geteuid() != 0) { + if (arg_dry || arg_force > 0) { + log_error("Must be root."); + return -EPERM; + } + + /* Try logind if we are a normal user and no special + * mode applies. Maybe PolicyKit allows us to shutdown + * the machine. */ + if (IN_SET(arg_action, ACTION_POWEROFF, ACTION_REBOOT)) { + r = logind_reboot(arg_action); + if (r >= 0) + return r; + if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) + /* requested operation is not + * supported on the local system or + * already in progress */ + return r; + /* on all other errors, try low-level operation */ + } + } + + if (!arg_dry && !arg_force) + return start_with_fallback(); + + assert(geteuid() == 0); + + if (!arg_no_wtmp) { + if (sd_booted() > 0) + log_debug("Not writing utmp record, assuming that systemd-update-utmp is used."); + else { + r = utmp_put_shutdown(); + if (r < 0) + log_warning_errno(r, "Failed to write utmp record: %m"); + } + } + + if (arg_dry) + return 0; + + r = halt_now(arg_action); + return log_error_errno(r, "Failed to reboot: %m"); +} + +static int runlevel_main(void) { + int r, runlevel, previous; + + r = utmp_get_runlevel(&runlevel, &previous); + if (r < 0) { + puts("unknown"); + return r; + } + + printf("%c %c\n", + previous <= 0 ? 'N' : previous, + runlevel <= 0 ? 'N' : runlevel); + + return 0; +} + +static int logind_cancel_shutdown(void) { +#ifdef HAVE_LOGIND + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + sd_bus *bus; + int r; + + r = acquire_bus(BUS_FULL, &bus); + if (r < 0) + return r; + + (void) logind_set_wall_message(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.login1", + "/org/freedesktop/login1", + "org.freedesktop.login1.Manager", + "CancelScheduledShutdown", + &error, + NULL, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to talk to logind, shutdown hasn't been cancelled: %s", bus_error_message(&error, r)); + + return 0; +#else + log_error("Not compiled with logind support, cannot cancel scheduled shutdowns."); + return -ENOSYS; +#endif +} + +int main(int argc, char*argv[]) { + int r; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + /* Explicitly not on_tty() to avoid setting cached value. + * This becomes relevant for piping output which might be + * ellipsized. */ + original_stdout_is_tty = isatty(STDOUT_FILENO); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_action != ACTION_SYSTEMCTL && running_in_chroot() > 0) { + log_info("Running in chroot, ignoring request."); + r = 0; + goto finish; + } + + /* systemctl_main() will print an error message for the bus + * connection, but only if it needs to */ + + switch (arg_action) { + + case ACTION_SYSTEMCTL: + r = systemctl_main(argc, argv); + break; + + case ACTION_HALT: + case ACTION_POWEROFF: + case ACTION_REBOOT: + case ACTION_KEXEC: + r = halt_main(); + break; + + case ACTION_RUNLEVEL2: + case ACTION_RUNLEVEL3: + case ACTION_RUNLEVEL4: + case ACTION_RUNLEVEL5: + case ACTION_RESCUE: + case ACTION_EMERGENCY: + case ACTION_DEFAULT: + r = start_with_fallback(); + break; + + case ACTION_RELOAD: + case ACTION_REEXEC: + r = reload_with_fallback(); + break; + + case ACTION_CANCEL_SHUTDOWN: + r = logind_cancel_shutdown(); + break; + + case ACTION_RUNLEVEL: + r = runlevel_main(); + break; + + case _ACTION_INVALID: + default: + assert_not_reached("Unknown action"); + } + +finish: + pager_close(); + ask_password_agent_close(); + polkit_agent_close(); + + strv_free(arg_types); + strv_free(arg_states); + strv_free(arg_properties); + + 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/system/systemctl/systemd-sysv-install.SKELETON b/src/system/systemctl/systemd-sysv-install.SKELETON new file mode 100755 index 0000000000..a53a3e6221 --- /dev/null +++ b/src/system/systemctl/systemd-sysv-install.SKELETON @@ -0,0 +1,47 @@ +#!/bin/sh +# This script is called by "systemctl enable/disable" when the given unit is a +# SysV init.d script. It needs to call the distribution's mechanism for +# enabling/disabling those, such as chkconfig, update-rc.d, or similar. This +# can optionally take a --root argument for enabling a SysV init script +# in a chroot or similar. +set -e + +usage() { + echo "Usage: $0 [--root=path] enable|disable|is-enabled " >&2 + exit 1 +} + +# parse options +eval set -- "$(getopt -o r: --long root: -- "$@")" +while true; do + case "$1" in + -r|--root) + ROOT="$2" + shift 2 ;; + --) shift ; break ;; + *) usage ;; + esac +done + +NAME="$2" +[ -n "$NAME" ] || usage + +case "$1" in + enable) + # call the command to enable SysV init script $NAME here + # (consider optional $ROOT) + echo "IMPLEMENT ME: enabling SysV init.d script $NAME" + ;; + disable) + # call the command to disable SysV init script $NAME here + # (consider optional $ROOT) + echo "IMPLEMENT ME: disabling SysV init.d script $NAME" + ;; + is-enabled) + # exit with 0 if $NAME is enabled, non-zero if it is disabled + # (consider optional $ROOT) + echo "IMPLEMENT ME: checking SysV init.d script $NAME" + ;; + *) + usage ;; +esac diff --git a/src/system/systemd/Makefile b/src/system/systemd/Makefile new file mode 100644 index 0000000000..8abfa2bf9d --- /dev/null +++ b/src/system/systemd/Makefile @@ -0,0 +1,67 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_SOURCES = \ + src/core/main.c + +systemd_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +systemd_LDADD = \ + libcore.la + +dist_pkgsysconf_DATA += \ + src/core/system.conf \ + src/core/user.conf + +dist_dbuspolicy_DATA += \ + src/core/org.freedesktop.systemd1.conf + +dist_dbussystemservice_DATA += \ + src/core/org.freedesktop.systemd1.service + +polkitpolicy_in_in_files += \ + src/core/org.freedesktop.systemd1.policy.in.in + +pkgconfigdata_DATA += \ + src/core/systemd.pc + +nodist_rpmmacros_DATA = \ + src/core/macros.systemd + +EXTRA_DIST += \ + src/core/systemd.pc.in \ + src/core/macros.systemd.in \ + src/core/triggers.systemd.in + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.systemd1.busname + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.systemd1.busname + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/system/systemd/macros.systemd.in b/src/system/systemd/macros.systemd.in new file mode 100644 index 0000000000..2cace3d3ba --- /dev/null +++ b/src/system/systemd/macros.systemd.in @@ -0,0 +1,105 @@ +# -*- Mode: rpm-spec; indent-tabs-mode: nil -*- */ +# +# This file is part of systemd. +# +# Copyright 2012 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 . + +# RPM macros for packages installing systemd unit files + +%_unitdir @systemunitdir@ +%_userunitdir @userunitdir@ +%_presetdir @systempresetdir@ +%_udevhwdbdir @udevhwdbdir@ +%_udevrulesdir @udevrulesdir@ +%_journalcatalogdir @catalogdir@ +%_tmpfilesdir @tmpfilesdir@ +%_sysusersdir @sysusersdir@ +%_sysctldir @sysctldir@ +%_binfmtdir @binfmtdir@ + +%systemd_requires \ +Requires(post): systemd \ +Requires(preun): systemd \ +Requires(postun): systemd \ +%{nil} + +%systemd_post() \ +if [ $1 -eq 1 ] ; then \ + # Initial installation \ + systemctl --no-reload preset %{?*} >/dev/null 2>&1 || : \ +fi \ +%{nil} + +%systemd_user_post() %{expand:%systemd_post \\--user \\--global %%{?*}} + +%systemd_preun() \ +if [ $1 -eq 0 ] ; then \ + # Package removal, not upgrade \ + systemctl --no-reload disable --now %{?*} > /dev/null 2>&1 || : \ +fi \ +%{nil} + +%systemd_user_preun() \ +if [ $1 -eq 0 ] ; then \ + # Package removal, not upgrade \ + systemctl --no-reload --user --global disable %{?*} > /dev/null 2>&1 || : \ +fi \ +%{nil} + +%systemd_postun() %{nil} + +%systemd_user_postun() %{nil} + +%systemd_postun_with_restart() \ +if [ $1 -ge 1 ] ; then \ + # Package upgrade, not uninstall \ + systemctl try-restart %{?*} >/dev/null 2>&1 || : \ +fi \ +%{nil} + +%systemd_user_postun_with_restart() %{nil} + +%udev_hwdb_update() \ +udevadm hwdb --update >/dev/null 2>&1 || : \ +%{nil} + +%udev_rules_update() \ +udevadm control --reload >/dev/null 2>&1 || : \ +%{nil} + +%journal_catalog_update() \ +journalctl --update-catalog >/dev/null 2>&1 || : \ +%{nil} + +%tmpfiles_create() \ +systemd-tmpfiles --create %{?*} >/dev/null 2>&1 || : \ +%{nil} + +%sysusers_create() \ +systemd-sysusers %{?*} >/dev/null 2>&1 || : \ +%{nil} + +%sysusers_create_inline() \ +echo %{?*} | systemd-sysusers - >/dev/null 2>&1 || : \ +%{nil} + +%sysctl_apply() \ +@rootlibexecdir@/systemd-sysctl %{?*} >/dev/null 2>&1 || : \ +%{nil} + +%binfmt_apply() \ +@rootlibexecdir@/systemd-binfmt %{?*} >/dev/null 2>&1 || : \ +%{nil} diff --git a/src/system/systemd/main.c b/src/system/systemd/main.c new file mode 100644 index 0000000000..bc151cde85 --- /dev/null +++ b/src/system/systemd/main.c @@ -0,0 +1,2142 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SECCOMP +#include +#endif +#ifdef HAVE_VALGRIND_VALGRIND_H +#include +#endif + +#include +#include + +#include "alloc-util.h" +#include "architecture.h" +#include "build.h" +#include "bus-error.h" +#include "bus-util.h" +#include "capability-util.h" +#include "clock-util.h" +#include "conf-parser.h" +#include "cpu-set-util.h" +#include "dbus-manager.h" +#include "def.h" +#include "env-util.h" +#include "fd-util.h" +#include "fdset.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "hostname-setup.h" +#include "ima-setup.h" +#include "killall.h" +#include "kmod-setup.h" +#include "load-fragment.h" +#include "log.h" +#include "loopback-setup.h" +#include "machine-id-setup.h" +#include "manager.h" +#include "missing.h" +#include "mount-setup.h" +#include "pager.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "process-util.h" +#include "rlimit-util.h" +#include "selinux-setup.h" +#include "selinux-util.h" +#include "signal-util.h" +#include "smack-setup.h" +#include "special.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "strv.h" +#include "switch-root.h" +#include "terminal-util.h" +#include "user-util.h" +#include "virt.h" +#include "watchdog.h" + +static enum { + ACTION_RUN, + ACTION_HELP, + ACTION_VERSION, + ACTION_TEST, + ACTION_DUMP_CONFIGURATION_ITEMS, + ACTION_DONE +} arg_action = ACTION_RUN; +static char *arg_default_unit = NULL; +static ManagerRunningAs arg_running_as = _MANAGER_RUNNING_AS_INVALID; +static bool arg_dump_core = true; +static int arg_crash_chvt = -1; +static bool arg_crash_shell = false; +static bool arg_crash_reboot = false; +static bool arg_confirm_spawn = false; +static ShowStatus arg_show_status = _SHOW_STATUS_UNSET; +static bool arg_switched_root = false; +static int arg_no_pager = -1; +static char ***arg_join_controllers = NULL; +static ExecOutput arg_default_std_output = EXEC_OUTPUT_JOURNAL; +static ExecOutput arg_default_std_error = EXEC_OUTPUT_INHERIT; +static usec_t arg_default_restart_usec = DEFAULT_RESTART_USEC; +static usec_t arg_default_timeout_start_usec = DEFAULT_TIMEOUT_USEC; +static usec_t arg_default_timeout_stop_usec = DEFAULT_TIMEOUT_USEC; +static usec_t arg_default_start_limit_interval = DEFAULT_START_LIMIT_INTERVAL; +static unsigned arg_default_start_limit_burst = DEFAULT_START_LIMIT_BURST; +static usec_t arg_runtime_watchdog = 0; +static usec_t arg_shutdown_watchdog = 10 * USEC_PER_MINUTE; +static char **arg_default_environment = NULL; +static struct rlimit *arg_default_rlimit[_RLIMIT_MAX] = {}; +static uint64_t arg_capability_bounding_set = CAP_ALL; +static nsec_t arg_timer_slack_nsec = NSEC_INFINITY; +static usec_t arg_default_timer_accuracy_usec = 1 * USEC_PER_MINUTE; +static Set* arg_syscall_archs = NULL; +static FILE* arg_serialization = NULL; +static bool arg_default_cpu_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 sd_id128_t arg_machine_id = {}; + +static void pager_open_if_enabled(void) { + + if (arg_no_pager <= 0) + return; + + pager_open(false); +} + +noreturn static void freeze_or_reboot(void) { + + if (arg_crash_reboot) { + log_notice("Rebooting in 10s..."); + (void) sleep(10); + + log_notice("Rebooting now..."); + (void) reboot(RB_AUTOBOOT); + log_emergency_errno(errno, "Failed to reboot: %m"); + } + + log_emergency("Freezing execution."); + freeze(); +} + +noreturn static void crash(int sig) { + struct sigaction sa; + pid_t pid; + + if (getpid() != 1) + /* Pass this on immediately, if this is not PID 1 */ + (void) raise(sig); + else if (!arg_dump_core) + log_emergency("Caught <%s>, not dumping core.", signal_to_string(sig)); + else { + sa = (struct sigaction) { + .sa_handler = nop_signal_handler, + .sa_flags = SA_NOCLDSTOP|SA_RESTART, + }; + + /* We want to wait for the core process, hence let's enable SIGCHLD */ + (void) sigaction(SIGCHLD, &sa, NULL); + + pid = raw_clone(SIGCHLD, NULL); + if (pid < 0) + log_emergency_errno(errno, "Caught <%s>, cannot fork for core dump: %m", signal_to_string(sig)); + else if (pid == 0) { + /* Enable default signal handler for core dump */ + + sa = (struct sigaction) { + .sa_handler = SIG_DFL, + }; + (void) sigaction(sig, &sa, NULL); + + /* Don't limit the coredump size */ + (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); + + /* Just to be sure... */ + (void) chdir("/"); + + /* Raise the signal again */ + pid = raw_getpid(); + (void) kill(pid, sig); /* raise() would kill the parent */ + + assert_not_reached("We shouldn't be here..."); + _exit(EXIT_FAILURE); + } else { + siginfo_t status; + int r; + + /* Order things nicely. */ + r = wait_for_terminate(pid, &status); + if (r < 0) + log_emergency_errno(r, "Caught <%s>, waitpid() failed: %m", signal_to_string(sig)); + else if (status.si_code != CLD_DUMPED) + log_emergency("Caught <%s>, core dump failed (child "PID_FMT", code=%s, status=%i/%s).", + signal_to_string(sig), + pid, sigchld_code_to_string(status.si_code), + status.si_status, + strna(status.si_code == CLD_EXITED + ? exit_status_to_string(status.si_status, EXIT_STATUS_FULL) + : signal_to_string(status.si_status))); + else + log_emergency("Caught <%s>, dumped core as pid "PID_FMT".", signal_to_string(sig), pid); + } + } + + if (arg_crash_chvt >= 0) + (void) chvt(arg_crash_chvt); + + sa = (struct sigaction) { + .sa_handler = SIG_IGN, + .sa_flags = SA_NOCLDSTOP|SA_NOCLDWAIT|SA_RESTART, + }; + + /* Let the kernel reap children for us */ + (void) sigaction(SIGCHLD, &sa, NULL); + + if (arg_crash_shell) { + log_notice("Executing crash shell in 10s..."); + (void) sleep(10); + + pid = raw_clone(SIGCHLD, NULL); + if (pid < 0) + log_emergency_errno(errno, "Failed to fork off crash shell: %m"); + else if (pid == 0) { + (void) setsid(); + (void) make_console_stdio(); + (void) execle("/bin/sh", "/bin/sh", NULL, environ); + + log_emergency_errno(errno, "execle() failed: %m"); + _exit(EXIT_FAILURE); + } else { + log_info("Spawned crash shell as PID "PID_FMT".", pid); + (void) wait_for_terminate(pid, NULL); + } + } + + freeze_or_reboot(); +} + +static void install_crash_handler(void) { + static const struct sigaction sa = { + .sa_handler = crash, + .sa_flags = SA_NODEFER, /* So that we can raise the signal again from the signal handler */ + }; + int r; + + /* We ignore the return value here, since, we don't mind if we + * cannot set up a crash handler */ + r = sigaction_many(&sa, SIGNALS_CRASH_HANDLER, -1); + if (r < 0) + log_debug_errno(r, "I had trouble setting up the crash handler, ignoring: %m"); +} + +static int console_setup(void) { + _cleanup_close_ int tty_fd = -1; + int r; + + tty_fd = open_terminal("/dev/console", O_WRONLY|O_NOCTTY|O_CLOEXEC); + if (tty_fd < 0) + return log_error_errno(tty_fd, "Failed to open /dev/console: %m"); + + /* We don't want to force text mode. plymouth may be showing + * pictures already from initrd. */ + r = reset_terminal_fd(tty_fd, false); + if (r < 0) + return log_error_errno(r, "Failed to reset /dev/console: %m"); + + return 0; +} + +static int parse_crash_chvt(const char *value) { + int b; + + if (safe_atoi(value, &arg_crash_chvt) >= 0) + return 0; + + b = parse_boolean(value); + if (b < 0) + return b; + + if (b > 0) + arg_crash_chvt = 0; /* switch to where kmsg goes */ + else + arg_crash_chvt = -1; /* turn off switching */ + + return 0; +} + +static int set_machine_id(const char *m) { + + if (sd_id128_from_string(m, &arg_machine_id) < 0) + return -EINVAL; + + if (sd_id128_is_null(arg_machine_id)) + return -EINVAL; + + return 0; +} + +static int parse_proc_cmdline_item(const char *key, const char *value) { + + int r; + + assert(key); + + if (streq(key, "systemd.unit") && value) { + + if (!in_initrd()) + return free_and_strdup(&arg_default_unit, value); + + } else if (streq(key, "rd.systemd.unit") && value) { + + if (in_initrd()) + return free_and_strdup(&arg_default_unit, value); + + } else if (streq(key, "systemd.dump_core") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse dump core switch %s. Ignoring.", value); + else + arg_dump_core = r; + + } else if (streq(key, "systemd.crash_chvt") && value) { + + if (parse_crash_chvt(value) < 0) + log_warning("Failed to parse crash chvt switch %s. Ignoring.", value); + + } else if (streq(key, "systemd.crash_shell") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse crash shell switch %s. Ignoring.", value); + else + arg_crash_shell = r; + + } else if (streq(key, "systemd.crash_reboot") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse crash reboot switch %s. Ignoring.", value); + else + arg_crash_reboot = r; + + } else if (streq(key, "systemd.confirm_spawn") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse confirm spawn switch %s. Ignoring.", value); + else + arg_confirm_spawn = r; + + } else if (streq(key, "systemd.show_status") && value) { + + r = parse_show_status(value, &arg_show_status); + if (r < 0) + log_warning("Failed to parse show status switch %s. Ignoring.", value); + + } else if (streq(key, "systemd.default_standard_output") && value) { + + r = exec_output_from_string(value); + if (r < 0) + log_warning("Failed to parse default standard output switch %s. Ignoring.", value); + else + arg_default_std_output = r; + + } else if (streq(key, "systemd.default_standard_error") && value) { + + r = exec_output_from_string(value); + if (r < 0) + log_warning("Failed to parse default standard error switch %s. Ignoring.", value); + else + arg_default_std_error = r; + + } else if (streq(key, "systemd.setenv") && value) { + + if (env_assignment_is_valid(value)) { + char **env; + + env = strv_env_set(arg_default_environment, value); + if (env) + arg_default_environment = env; + else + log_warning_errno(ENOMEM, "Setting environment variable '%s' failed, ignoring: %m", value); + } else + log_warning("Environment variable name '%s' is not valid. Ignoring.", value); + + } else if (streq(key, "systemd.machine_id") && value) { + + r = set_machine_id(value); + if (r < 0) + log_warning("MachineID '%s' is not valid. Ignoring.", value); + + } else if (streq(key, "quiet") && !value) { + + if (arg_show_status == _SHOW_STATUS_UNSET) + arg_show_status = SHOW_STATUS_AUTO; + + } else if (streq(key, "debug") && !value) { + + /* Note that log_parse_environment() handles 'debug' + * too, and sets the log level to LOG_DEBUG. */ + + if (detect_container() > 0) + log_set_target(LOG_TARGET_CONSOLE); + + } else if (!in_initrd() && !value) { + const char *target; + + /* SysV compatibility */ + target = runlevel_to_target(key); + if (target) + return free_and_strdup(&arg_default_unit, target); + } + + return 0; +} + +#define DEFINE_SETTER(name, func, descr) \ + static int name(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 r; \ + \ + assert(filename); \ + assert(lvalue); \ + assert(rvalue); \ + \ + r = func(rvalue); \ + if (r < 0) \ + log_syntax(unit, LOG_ERR, filename, line, r, \ + "Invalid " descr "'%s': %m", \ + rvalue); \ + \ + return 0; \ + } + +DEFINE_SETTER(config_parse_level2, log_set_max_level_from_string, "log level") +DEFINE_SETTER(config_parse_target, log_set_target_from_string, "target") +DEFINE_SETTER(config_parse_color, log_show_color_from_string, "color" ) +DEFINE_SETTER(config_parse_location, log_show_location_from_string, "location") + +static int config_parse_cpu_affinity2( + 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) { + + _cleanup_cpu_free_ cpu_set_t *c = NULL; + int ncpus; + + ncpus = parse_cpu_set_and_warn(rvalue, &c, unit, filename, line, lvalue); + if (ncpus < 0) + return ncpus; + + if (sched_setaffinity(0, CPU_ALLOC_SIZE(ncpus), c) < 0) + log_warning("Failed to set CPU affinity: %m"); + + return 0; +} + +static int config_parse_show_status( + 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 k; + ShowStatus *b = data; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + k = parse_show_status(rvalue, b); + if (k < 0) { + log_syntax(unit, LOG_ERR, filename, line, k, "Failed to parse show status setting, ignoring: %s", rvalue); + return 0; + } + + return 0; +} + +static int config_parse_crash_chvt( + 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 r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = parse_crash_chvt(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CrashChangeVT= setting, ignoring: %s", rvalue); + return 0; + } + + return 0; +} + +static int config_parse_join_controllers(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) { + + const char *whole_rvalue = rvalue; + unsigned n = 0; + + assert(filename); + assert(lvalue); + assert(rvalue); + + arg_join_controllers = strv_free_free(arg_join_controllers); + + for (;;) { + _cleanup_free_ char *word = NULL; + char **l; + int r; + + r = extract_first_word(&rvalue, &word, WHITESPACE, EXTRACT_QUOTES); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid value for %s: %s", lvalue, whole_rvalue); + return r; + } + if (r == 0) + break; + + l = strv_split(word, ","); + if (!l) + return log_oom(); + strv_uniq(l); + + if (strv_length(l) <= 1) { + strv_free(l); + continue; + } + + if (!arg_join_controllers) { + arg_join_controllers = new(char**, 2); + if (!arg_join_controllers) { + strv_free(l); + return log_oom(); + } + + arg_join_controllers[0] = l; + arg_join_controllers[1] = NULL; + + n = 1; + } else { + char ***a; + char ***t; + + t = new0(char**, n+2); + if (!t) { + strv_free(l); + return log_oom(); + } + + n = 0; + + for (a = arg_join_controllers; *a; a++) { + + if (strv_overlap(*a, l)) { + if (strv_extend_strv(&l, *a, false) < 0) { + strv_free(l); + strv_free_free(t); + return log_oom(); + } + + } else { + char **c; + + c = strv_copy(*a); + if (!c) { + strv_free(l); + strv_free_free(t); + return log_oom(); + } + + t[n++] = c; + } + } + + t[n++] = strv_uniq(l); + + strv_free_free(arg_join_controllers); + arg_join_controllers = t; + } + } + if (!isempty(rvalue)) + log_syntax(unit, LOG_ERR, filename, line, 0, "Trailing garbage, ignoring."); + + return 0; +} + +static int parse_config_file(void) { + + const ConfigTableItem items[] = { + { "Manager", "LogLevel", config_parse_level2, 0, NULL }, + { "Manager", "LogTarget", config_parse_target, 0, NULL }, + { "Manager", "LogColor", config_parse_color, 0, NULL }, + { "Manager", "LogLocation", config_parse_location, 0, NULL }, + { "Manager", "DumpCore", config_parse_bool, 0, &arg_dump_core }, + { "Manager", "CrashChVT", /* legacy */ config_parse_crash_chvt, 0, NULL }, + { "Manager", "CrashChangeVT", config_parse_crash_chvt, 0, NULL }, + { "Manager", "CrashShell", config_parse_bool, 0, &arg_crash_shell }, + { "Manager", "CrashReboot", config_parse_bool, 0, &arg_crash_reboot }, + { "Manager", "ShowStatus", config_parse_show_status, 0, &arg_show_status }, + { "Manager", "CPUAffinity", config_parse_cpu_affinity2, 0, NULL }, + { "Manager", "JoinControllers", config_parse_join_controllers, 0, &arg_join_controllers }, + { "Manager", "RuntimeWatchdogSec", config_parse_sec, 0, &arg_runtime_watchdog }, + { "Manager", "ShutdownWatchdogSec", config_parse_sec, 0, &arg_shutdown_watchdog }, + { "Manager", "CapabilityBoundingSet", config_parse_capability_set, 0, &arg_capability_bounding_set }, +#ifdef HAVE_SECCOMP + { "Manager", "SystemCallArchitectures", config_parse_syscall_archs, 0, &arg_syscall_archs }, +#endif + { "Manager", "TimerSlackNSec", config_parse_nsec, 0, &arg_timer_slack_nsec }, + { "Manager", "DefaultTimerAccuracySec", config_parse_sec, 0, &arg_default_timer_accuracy_usec }, + { "Manager", "DefaultStandardOutput", config_parse_output, 0, &arg_default_std_output }, + { "Manager", "DefaultStandardError", config_parse_output, 0, &arg_default_std_error }, + { "Manager", "DefaultTimeoutStartSec", config_parse_sec, 0, &arg_default_timeout_start_usec }, + { "Manager", "DefaultTimeoutStopSec", config_parse_sec, 0, &arg_default_timeout_stop_usec }, + { "Manager", "DefaultRestartSec", config_parse_sec, 0, &arg_default_restart_usec }, + { "Manager", "DefaultStartLimitInterval", config_parse_sec, 0, &arg_default_start_limit_interval }, + { "Manager", "DefaultStartLimitBurst", config_parse_unsigned, 0, &arg_default_start_limit_burst }, + { "Manager", "DefaultEnvironment", config_parse_environ, 0, &arg_default_environment }, + { "Manager", "DefaultLimitCPU", config_parse_limit, RLIMIT_CPU, arg_default_rlimit }, + { "Manager", "DefaultLimitFSIZE", config_parse_limit, RLIMIT_FSIZE, arg_default_rlimit }, + { "Manager", "DefaultLimitDATA", config_parse_limit, RLIMIT_DATA, arg_default_rlimit }, + { "Manager", "DefaultLimitSTACK", config_parse_limit, RLIMIT_STACK, arg_default_rlimit }, + { "Manager", "DefaultLimitCORE", config_parse_limit, RLIMIT_CORE, arg_default_rlimit }, + { "Manager", "DefaultLimitRSS", config_parse_limit, RLIMIT_RSS, arg_default_rlimit }, + { "Manager", "DefaultLimitNOFILE", config_parse_limit, RLIMIT_NOFILE, arg_default_rlimit }, + { "Manager", "DefaultLimitAS", config_parse_limit, RLIMIT_AS, arg_default_rlimit }, + { "Manager", "DefaultLimitNPROC", config_parse_limit, RLIMIT_NPROC, arg_default_rlimit }, + { "Manager", "DefaultLimitMEMLOCK", config_parse_limit, RLIMIT_MEMLOCK, arg_default_rlimit }, + { "Manager", "DefaultLimitLOCKS", config_parse_limit, RLIMIT_LOCKS, arg_default_rlimit }, + { "Manager", "DefaultLimitSIGPENDING", config_parse_limit, RLIMIT_SIGPENDING, arg_default_rlimit }, + { "Manager", "DefaultLimitMSGQUEUE", config_parse_limit, RLIMIT_MSGQUEUE, arg_default_rlimit }, + { "Manager", "DefaultLimitNICE", config_parse_limit, RLIMIT_NICE, arg_default_rlimit }, + { "Manager", "DefaultLimitRTPRIO", config_parse_limit, RLIMIT_RTPRIO, arg_default_rlimit }, + { "Manager", "DefaultLimitRTTIME", config_parse_limit, RLIMIT_RTTIME, arg_default_rlimit }, + { "Manager", "DefaultCPUAccounting", config_parse_bool, 0, &arg_default_cpu_accounting }, + { "Manager", "DefaultBlockIOAccounting", config_parse_bool, 0, &arg_default_blockio_accounting }, + { "Manager", "DefaultMemoryAccounting", config_parse_bool, 0, &arg_default_memory_accounting }, + { "Manager", "DefaultTasksAccounting", config_parse_bool, 0, &arg_default_tasks_accounting }, + { "Manager", "DefaultTasksMax", config_parse_tasks_max, 0, &arg_default_tasks_max }, + {} + }; + + const char *fn, *conf_dirs_nulstr; + + fn = arg_running_as == MANAGER_SYSTEM ? + PKGSYSCONFDIR "/system.conf" : + PKGSYSCONFDIR "/user.conf"; + + conf_dirs_nulstr = arg_running_as == MANAGER_SYSTEM ? + CONF_PATHS_NULSTR("systemd/system.conf.d") : + CONF_PATHS_NULSTR("systemd/user.conf.d"); + + config_parse_many(fn, conf_dirs_nulstr, "Manager\0", config_item_table_lookup, items, false, NULL); + + /* Traditionally "0" was used to turn off the default unit timeouts. Fix this up so that we used USEC_INFINITY + * like everywhere else. */ + if (arg_default_timeout_start_usec <= 0) + arg_default_timeout_start_usec = USEC_INFINITY; + if (arg_default_timeout_stop_usec <= 0) + arg_default_timeout_stop_usec = USEC_INFINITY; + + return 0; +} + +static void manager_set_defaults(Manager *m) { + + assert(m); + + m->default_timer_accuracy_usec = arg_default_timer_accuracy_usec; + m->default_std_output = arg_default_std_output; + m->default_std_error = arg_default_std_error; + m->default_timeout_start_usec = arg_default_timeout_start_usec; + m->default_timeout_stop_usec = arg_default_timeout_stop_usec; + m->default_restart_usec = arg_default_restart_usec; + m->default_start_limit_interval = arg_default_start_limit_interval; + m->default_start_limit_burst = arg_default_start_limit_burst; + m->default_cpu_accounting = arg_default_cpu_accounting; + m->default_blockio_accounting = arg_default_blockio_accounting; + m->default_memory_accounting = arg_default_memory_accounting; + m->default_tasks_accounting = arg_default_tasks_accounting; + m->default_tasks_max = arg_default_tasks_max; + + manager_set_default_rlimits(m, arg_default_rlimit); + manager_environment_add(m, NULL, arg_default_environment); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_LOG_LEVEL = 0x100, + ARG_LOG_TARGET, + ARG_LOG_COLOR, + ARG_LOG_LOCATION, + ARG_UNIT, + ARG_SYSTEM, + ARG_USER, + ARG_TEST, + ARG_NO_PAGER, + ARG_VERSION, + ARG_DUMP_CONFIGURATION_ITEMS, + ARG_DUMP_CORE, + ARG_CRASH_CHVT, + ARG_CRASH_SHELL, + ARG_CRASH_REBOOT, + ARG_CONFIRM_SPAWN, + ARG_SHOW_STATUS, + ARG_DESERIALIZE, + ARG_SWITCHED_ROOT, + ARG_DEFAULT_STD_OUTPUT, + ARG_DEFAULT_STD_ERROR, + ARG_MACHINE_ID + }; + + static const struct option options[] = { + { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, + { "log-target", required_argument, NULL, ARG_LOG_TARGET }, + { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, + { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, + { "unit", required_argument, NULL, ARG_UNIT }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "user", no_argument, NULL, ARG_USER }, + { "test", no_argument, NULL, ARG_TEST }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "dump-configuration-items", no_argument, NULL, ARG_DUMP_CONFIGURATION_ITEMS }, + { "dump-core", optional_argument, NULL, ARG_DUMP_CORE }, + { "crash-chvt", required_argument, NULL, ARG_CRASH_CHVT }, + { "crash-shell", optional_argument, NULL, ARG_CRASH_SHELL }, + { "crash-reboot", optional_argument, NULL, ARG_CRASH_REBOOT }, + { "confirm-spawn", optional_argument, NULL, ARG_CONFIRM_SPAWN }, + { "show-status", optional_argument, NULL, ARG_SHOW_STATUS }, + { "deserialize", required_argument, NULL, ARG_DESERIALIZE }, + { "switched-root", no_argument, NULL, ARG_SWITCHED_ROOT }, + { "default-standard-output", required_argument, NULL, ARG_DEFAULT_STD_OUTPUT, }, + { "default-standard-error", required_argument, NULL, ARG_DEFAULT_STD_ERROR, }, + { "machine-id", required_argument, NULL, ARG_MACHINE_ID }, + {} + }; + + int c, r; + + assert(argc >= 1); + assert(argv); + + if (getpid() == 1) + opterr = 0; + + while ((c = getopt_long(argc, argv, "hDbsz:", options, NULL)) >= 0) + + switch (c) { + + case ARG_LOG_LEVEL: + r = log_set_max_level_from_string(optarg); + if (r < 0) { + log_error("Failed to parse log level %s.", optarg); + return r; + } + + break; + + case ARG_LOG_TARGET: + r = log_set_target_from_string(optarg); + if (r < 0) { + log_error("Failed to parse log target %s.", optarg); + return r; + } + + break; + + case ARG_LOG_COLOR: + + if (optarg) { + r = log_show_color_from_string(optarg); + if (r < 0) { + log_error("Failed to parse log color setting %s.", optarg); + return r; + } + } else + log_show_color(true); + + break; + + case ARG_LOG_LOCATION: + if (optarg) { + r = log_show_location_from_string(optarg); + if (r < 0) { + log_error("Failed to parse log location setting %s.", optarg); + return r; + } + } else + log_show_location(true); + + break; + + case ARG_DEFAULT_STD_OUTPUT: + r = exec_output_from_string(optarg); + if (r < 0) { + log_error("Failed to parse default standard output setting %s.", optarg); + return r; + } else + arg_default_std_output = r; + break; + + case ARG_DEFAULT_STD_ERROR: + r = exec_output_from_string(optarg); + if (r < 0) { + log_error("Failed to parse default standard error output setting %s.", optarg); + return r; + } else + arg_default_std_error = r; + break; + + case ARG_UNIT: + + r = free_and_strdup(&arg_default_unit, optarg); + if (r < 0) + return log_error_errno(r, "Failed to set default unit %s: %m", optarg); + + break; + + case ARG_SYSTEM: + arg_running_as = MANAGER_SYSTEM; + break; + + case ARG_USER: + arg_running_as = MANAGER_USER; + break; + + case ARG_TEST: + arg_action = ACTION_TEST; + if (arg_no_pager < 0) + arg_no_pager = true; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case ARG_VERSION: + arg_action = ACTION_VERSION; + break; + + case ARG_DUMP_CONFIGURATION_ITEMS: + arg_action = ACTION_DUMP_CONFIGURATION_ITEMS; + break; + + case ARG_DUMP_CORE: + if (!optarg) + arg_dump_core = true; + else { + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse dump core boolean: %s", optarg); + arg_dump_core = r; + } + break; + + case ARG_CRASH_CHVT: + r = parse_crash_chvt(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse crash virtual terminal index: %s", optarg); + break; + + case ARG_CRASH_SHELL: + if (!optarg) + arg_crash_shell = true; + else { + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse crash shell boolean: %s", optarg); + arg_crash_shell = r; + } + break; + + case ARG_CRASH_REBOOT: + if (!optarg) + arg_crash_reboot = true; + else { + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse crash shell boolean: %s", optarg); + arg_crash_reboot = r; + } + break; + + case ARG_CONFIRM_SPAWN: + r = optarg ? parse_boolean(optarg) : 1; + if (r < 0) { + log_error("Failed to parse confirm spawn boolean %s.", optarg); + return r; + } + arg_confirm_spawn = r; + break; + + case ARG_SHOW_STATUS: + if (optarg) { + r = parse_show_status(optarg, &arg_show_status); + if (r < 0) { + log_error("Failed to parse show status boolean %s.", optarg); + return r; + } + } else + arg_show_status = SHOW_STATUS_YES; + break; + + case ARG_DESERIALIZE: { + int fd; + FILE *f; + + r = safe_atoi(optarg, &fd); + if (r < 0 || fd < 0) { + log_error("Failed to parse deserialize option %s.", optarg); + return -EINVAL; + } + + (void) fd_cloexec(fd, true); + + f = fdopen(fd, "r"); + if (!f) + return log_error_errno(errno, "Failed to open serialization fd: %m"); + + safe_fclose(arg_serialization); + arg_serialization = f; + + break; + } + + case ARG_SWITCHED_ROOT: + arg_switched_root = true; + break; + + case ARG_MACHINE_ID: + r = set_machine_id(optarg); + if (r < 0) { + log_error("MachineID '%s' is not valid.", optarg); + return r; + } + break; + + case 'h': + arg_action = ACTION_HELP; + if (arg_no_pager < 0) + arg_no_pager = true; + break; + + case 'D': + log_set_max_level(LOG_DEBUG); + break; + + case 'b': + case 's': + case 'z': + /* Just to eat away the sysvinit kernel + * cmdline args without getopt() error + * messages that we'll parse in + * parse_proc_cmdline_word() or ignore. */ + + case '?': + if (getpid() != 1) + return -EINVAL; + else + return 0; + + default: + assert_not_reached("Unhandled option code."); + } + + if (optind < argc && getpid() != 1) { + /* Hmm, when we aren't run as init system + * let's complain about excess arguments */ + + log_error("Excess arguments."); + return -EINVAL; + } + + return 0; +} + +static int help(void) { + + printf("%s [OPTIONS...]\n\n" + "Starts up and maintains the system or user services.\n\n" + " -h --help Show this help\n" + " --test Determine startup sequence, dump it and exit\n" + " --no-pager Do not pipe output into a pager\n" + " --dump-configuration-items Dump understood unit configuration items\n" + " --unit=UNIT Set default unit\n" + " --system Run a system instance, even if PID != 1\n" + " --user Run a user instance\n" + " --dump-core[=BOOL] Dump core on crash\n" + " --crash-vt=NR Change to specified VT on crash\n" + " --crash-reboot[=BOOL] Reboot on crash\n" + " --crash-shell[=BOOL] Run shell on crash\n" + " --confirm-spawn[=BOOL] Ask for confirmation when spawning processes\n" + " --show-status[=BOOL] Show status updates on the console during bootup\n" + " --log-target=TARGET Set log target (console, journal, kmsg, journal-or-kmsg, null)\n" + " --log-level=LEVEL Set log level (debug, info, notice, warning, err, crit, alert, emerg)\n" + " --log-color[=BOOL] Highlight important log messages\n" + " --log-location[=BOOL] Include code location in log messages\n" + " --default-standard-output= Set default standard output for services\n" + " --default-standard-error= Set default standard error output for services\n", + program_invocation_short_name); + + return 0; +} + +static int prepare_reexecute(Manager *m, FILE **_f, FDSet **_fds, bool switching_root) { + _cleanup_fdset_free_ FDSet *fds = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(m); + assert(_f); + assert(_fds); + + r = manager_open_serialization(m, &f); + if (r < 0) + return log_error_errno(r, "Failed to create serialization file: %m"); + + /* Make sure nothing is really destructed when we shut down */ + m->n_reloading ++; + bus_manager_send_reloading(m, true); + + fds = fdset_new(); + if (!fds) + return log_oom(); + + r = manager_serialize(m, f, fds, switching_root); + if (r < 0) + return log_error_errno(r, "Failed to serialize state: %m"); + + if (fseeko(f, 0, SEEK_SET) == (off_t) -1) + return log_error_errno(errno, "Failed to rewind serialization fd: %m"); + + r = fd_cloexec(fileno(f), false); + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization: %m"); + + r = fdset_cloexec(fds, false); + if (r < 0) + return log_error_errno(r, "Failed to disable O_CLOEXEC for serialization fds: %m"); + + *_f = f; + *_fds = fds; + + f = NULL; + fds = NULL; + + return 0; +} + +static int bump_rlimit_nofile(struct rlimit *saved_rlimit) { + struct rlimit nl; + int r; + + assert(saved_rlimit); + + /* Save the original RLIMIT_NOFILE so that we can reset it + * later when transitioning from the initrd to the main + * systemd or suchlike. */ + if (getrlimit(RLIMIT_NOFILE, saved_rlimit) < 0) + return log_error_errno(errno, "Reading RLIMIT_NOFILE failed: %m"); + + /* Make sure forked processes get the default kernel setting */ + if (!arg_default_rlimit[RLIMIT_NOFILE]) { + struct rlimit *rl; + + rl = newdup(struct rlimit, saved_rlimit, 1); + if (!rl) + return log_oom(); + + arg_default_rlimit[RLIMIT_NOFILE] = rl; + } + + /* Bump up the resource limit for ourselves substantially */ + nl.rlim_cur = nl.rlim_max = 64*1024; + r = setrlimit_closest(RLIMIT_NOFILE, &nl); + if (r < 0) + return log_error_errno(r, "Setting RLIMIT_NOFILE failed: %m"); + + return 0; +} + +static void test_usr(void) { + + /* Check that /usr is not a separate fs */ + + if (dir_is_empty("/usr") <= 0) + return; + + log_warning("/usr appears to be on its own filesystem and is not already mounted. This is not a supported setup. " + "Some things will probably break (sometimes even silently) in mysterious ways. " + "Consult http://freedesktop.org/wiki/Software/systemd/separate-usr-is-broken for more information."); +} + +static int initialize_join_controllers(void) { + /* By default, mount "cpu" + "cpuacct" together, and "net_cls" + * + "net_prio". We'd like to add "cpuset" to the mix, but + * "cpuset" doesn't really work for groups with no initialized + * attributes. */ + + arg_join_controllers = new(char**, 3); + if (!arg_join_controllers) + return -ENOMEM; + + arg_join_controllers[0] = strv_new("cpu", "cpuacct", NULL); + if (!arg_join_controllers[0]) + goto oom; + + arg_join_controllers[1] = strv_new("net_cls", "net_prio", NULL); + if (!arg_join_controllers[1]) + goto oom; + + arg_join_controllers[2] = NULL; + return 0; + +oom: + arg_join_controllers = strv_free_free(arg_join_controllers); + return -ENOMEM; +} + +static int enforce_syscall_archs(Set *archs) { +#ifdef HAVE_SECCOMP + scmp_filter_ctx *seccomp; + Iterator i; + void *id; + int r; + + seccomp = seccomp_init(SCMP_ACT_ALLOW); + if (!seccomp) + return log_oom(); + + SET_FOREACH(id, arg_syscall_archs, i) { + r = seccomp_arch_add(seccomp, PTR_TO_UINT32(id) - 1); + if (r == -EEXIST) + continue; + if (r < 0) { + log_error_errno(r, "Failed to add architecture to seccomp: %m"); + goto finish; + } + } + + r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); + if (r < 0) { + log_error_errno(r, "Failed to unset NO_NEW_PRIVS: %m"); + goto finish; + } + + r = seccomp_load(seccomp); + if (r < 0) + log_error_errno(r, "Failed to add install architecture seccomp: %m"); + +finish: + seccomp_release(seccomp); + return r; +#else + return 0; +#endif +} + +static int status_welcome(void) { + _cleanup_free_ char *pretty_name = NULL, *ansi_color = NULL; + int r; + + r = parse_env_file("/etc/os-release", NEWLINE, + "PRETTY_NAME", &pretty_name, + "ANSI_COLOR", &ansi_color, + NULL); + if (r == -ENOENT) + r = parse_env_file("/usr/lib/os-release", NEWLINE, + "PRETTY_NAME", &pretty_name, + "ANSI_COLOR", &ansi_color, + NULL); + + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read os-release file: %m"); + + return status_printf(NULL, false, false, + "\nWelcome to \x1B[%sm%s\x1B[0m!\n", + isempty(ansi_color) ? "1" : ansi_color, + isempty(pretty_name) ? "GNU/Linux" : pretty_name); +} + +static int write_container_id(void) { + const char *c; + int r; + + c = getenv("container"); + if (isempty(c)) + return 0; + + r = write_string_file("/run/systemd/container", c, WRITE_STRING_FILE_CREATE); + if (r < 0) + return log_warning_errno(r, "Failed to write /run/systemd/container, ignoring: %m"); + + return 1; +} + +static int bump_unix_max_dgram_qlen(void) { + _cleanup_free_ char *qlen = NULL; + unsigned long v; + int r; + + /* Let's bump the net.unix.max_dgram_qlen sysctl. The kernel + * default of 16 is simply too low. We set the value really + * really early during boot, so that it is actually applied to + * all our sockets, including the $NOTIFY_SOCKET one. */ + + r = read_one_line_file("/proc/sys/net/unix/max_dgram_qlen", &qlen); + if (r < 0) + return log_warning_errno(r, "Failed to read AF_UNIX datagram queue length, ignoring: %m"); + + r = safe_atolu(qlen, &v); + if (r < 0) + return log_warning_errno(r, "Failed to parse AF_UNIX datagram queue length, ignoring: %m"); + + if (v >= DEFAULT_UNIX_MAX_DGRAM_QLEN) + return 0; + + qlen = mfree(qlen); + if (asprintf(&qlen, "%lu\n", DEFAULT_UNIX_MAX_DGRAM_QLEN) < 0) + return log_oom(); + + r = write_string_file("/proc/sys/net/unix/max_dgram_qlen", qlen, 0); + if (r < 0) + return log_full_errno(IN_SET(r, -EROFS, -EPERM, -EACCES) ? LOG_DEBUG : LOG_WARNING, r, + "Failed to bump AF_UNIX datagram queue length, ignoring: %m"); + + return 1; +} + +int main(int argc, char *argv[]) { + Manager *m = NULL; + int r, retval = EXIT_FAILURE; + usec_t before_startup, after_startup; + char timespan[FORMAT_TIMESPAN_MAX]; + FDSet *fds = NULL; + bool reexecute = false; + const char *shutdown_verb = NULL; + dual_timestamp initrd_timestamp = DUAL_TIMESTAMP_NULL; + dual_timestamp userspace_timestamp = DUAL_TIMESTAMP_NULL; + dual_timestamp kernel_timestamp = DUAL_TIMESTAMP_NULL; + dual_timestamp security_start_timestamp = DUAL_TIMESTAMP_NULL; + dual_timestamp security_finish_timestamp = DUAL_TIMESTAMP_NULL; + static char systemd[] = "systemd"; + bool skip_setup = false; + unsigned j; + bool loaded_policy = false; + bool arm_reboot_watchdog = false; + bool queue_default_job = false; + bool empty_etc = false; + char *switch_root_dir = NULL, *switch_root_init = NULL; + struct rlimit saved_rlimit_nofile = RLIMIT_MAKE_CONST(0); + const char *error_message = NULL; + +#ifdef HAVE_SYSV_COMPAT + if (getpid() != 1 && strstr(program_invocation_short_name, "init")) { + /* This is compatibility support for SysV, where + * calling init as a user is identical to telinit. */ + + errno = -ENOENT; + execv(SYSTEMCTL_BINARY_PATH, argv); + log_error_errno(errno, "Failed to exec " SYSTEMCTL_BINARY_PATH ": %m"); + return 1; + } +#endif + + dual_timestamp_from_monotonic(&kernel_timestamp, 0); + dual_timestamp_get(&userspace_timestamp); + + /* Determine if this is a reexecution or normal bootup. We do + * the full command line parsing much later, so let's just + * have a quick peek here. */ + if (strv_find(argv+1, "--deserialize")) + skip_setup = true; + + /* If we have switched root, do all the special setup + * things */ + if (strv_find(argv+1, "--switched-root")) + skip_setup = false; + + /* If we get started via the /sbin/init symlink then we are + called 'init'. After a subsequent reexecution we are then + called 'systemd'. That is confusing, hence let's call us + systemd right-away. */ + program_invocation_short_name = systemd; + prctl(PR_SET_NAME, systemd); + + saved_argv = argv; + saved_argc = argc; + + log_show_color(isatty(STDERR_FILENO) > 0); + log_set_upgrade_syslog_to_journal(true); + + /* Disable the umask logic */ + if (getpid() == 1) + umask(0); + + if (getpid() == 1 && detect_container() <= 0) { + + /* Running outside of a container as PID 1 */ + arg_running_as = MANAGER_SYSTEM; + make_null_stdio(); + log_set_target(LOG_TARGET_KMSG); + log_open(); + + if (in_initrd()) + initrd_timestamp = userspace_timestamp; + + if (!skip_setup) { + r = mount_setup_early(); + if (r < 0) { + error_message = "Failed to early mount API filesystems"; + goto finish; + } + dual_timestamp_get(&security_start_timestamp); + 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; + } + dual_timestamp_get(&security_finish_timestamp); + } + + if (mac_selinux_init(NULL) < 0) { + error_message = "Failed to initialize SELinux policy"; + goto finish; + } + + if (!skip_setup) { + if (clock_is_localtime() > 0) { + int min; + + /* + * The very first call of settimeofday() also does a time warp in the kernel. + * + * In the rtc-in-local time mode, we set the kernel's timezone, and rely on + * external tools to take care of maintaining the RTC and do all adjustments. + * This matches the behavior of Windows, which leaves the RTC alone if the + * registry tells that the RTC runs in UTC. + */ + r = clock_set_timezone(&min); + if (r < 0) + log_error_errno(r, "Failed to apply local time delta, ignoring: %m"); + else + log_info("RTC configured in localtime, applying delta of %i minutes to system time.", min); + } else if (!in_initrd()) { + /* + * 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 + * 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. + * + * Do no set the kernel's timezone. The concept of local time cannot + * be supported reliably, the time will jump or be incorrect at every daylight + * saving time change. All kernel local time concepts will be treated + * as UTC that way. + */ + (void) clock_reset_timewarp(); + } + + r = clock_apply_epoch(); + if (r < 0) + log_error_errno(r, "Current system time is before build time, but cannot correct: %m"); + else if (r > 0) + log_info("System time before build time, advancing clock."); + } + + /* Set the default for later on, but don't actually + * open the logs like this for now. Note that if we + * are transitioning from the initrd there might still + * be journal fd open, and we shouldn't attempt + * opening that before we parsed /proc/cmdline which + * might redirect output elsewhere. */ + log_set_target(LOG_TARGET_JOURNAL_OR_KMSG); + + } else if (getpid() == 1) { + /* Running inside a container, as PID 1 */ + arg_running_as = MANAGER_SYSTEM; + log_set_target(LOG_TARGET_CONSOLE); + log_close_console(); /* force reopen of /dev/console */ + log_open(); + + /* For the later on, see above... */ + log_set_target(LOG_TARGET_JOURNAL); + + /* clear the kernel timestamp, + * because we are in a container */ + kernel_timestamp.monotonic = 0ULL; + kernel_timestamp.realtime = 0ULL; + + } else { + /* Running as user instance */ + arg_running_as = MANAGER_USER; + log_set_target(LOG_TARGET_AUTO); + log_open(); + + /* clear the kernel timestamp, + * because we are not PID 1 */ + kernel_timestamp = DUAL_TIMESTAMP_NULL; + } + + if (getpid() == 1) { + /* Don't limit the core dump size, so that coredump handlers such as systemd-coredump (which honour the limit) + * will process core dumps for system services by default. */ + (void) setrlimit(RLIMIT_CORE, &RLIMIT_MAKE_CONST(RLIM_INFINITY)); + + /* But at the same time, turn off the core_pattern logic by default, so that no coredumps are stored + * until the systemd-coredump tool is enabled via sysctl. */ + if (!skip_setup) + (void) write_string_file("/proc/sys/kernel/core_pattern", "|/bin/false", 0); + } + + /* Initialize default unit */ + r = free_and_strdup(&arg_default_unit, SPECIAL_DEFAULT_TARGET); + if (r < 0) { + log_emergency_errno(r, "Failed to set default unit %s: %m", SPECIAL_DEFAULT_TARGET); + error_message = "Failed to set default unit"; + goto finish; + } + + r = initialize_join_controllers(); + if (r < 0) { + error_message = "Failed to initialize cgroup controllers"; + goto finish; + } + + /* Mount /proc, /sys and friends, so that /proc/cmdline and + * /proc/$PID/fd is available. */ + if (getpid() == 1) { + + /* Load the kernel modules early, so that we kdbus.ko is loaded before kdbusfs shall be mounted */ + if (!skip_setup) + kmod_setup(); + + r = mount_setup(loaded_policy); + if (r < 0) { + error_message = "Failed to mount API filesystems"; + goto finish; + } + } + + /* Reset all signal handlers. */ + (void) reset_all_signal_handlers(); + (void) ignore_signals(SIGNALS_IGNORE, -1); + + if (parse_config_file() < 0) { + error_message = "Failed to parse config file"; + goto finish; + } + + if (arg_running_as == MANAGER_SYSTEM) { + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + } + + /* Note that this also parses bits from the kernel command + * line, including "debug". */ + log_parse_environment(); + + if (parse_argv(argc, argv) < 0) { + error_message = "Failed to parse commandline arguments"; + goto finish; + } + + if (arg_action == ACTION_TEST && + geteuid() == 0) { + log_error("Don't run test mode as root."); + goto finish; + } + + if (arg_running_as == MANAGER_USER && + arg_action == ACTION_RUN && + sd_booted() <= 0) { + log_error("Trying to run as user instance, but the system has not been booted with systemd."); + goto finish; + } + + if (arg_running_as == MANAGER_SYSTEM && + arg_action == ACTION_RUN && + running_in_chroot() > 0) { + log_error("Cannot be run in a chroot() environment."); + goto finish; + } + + if (arg_action == ACTION_TEST) + skip_setup = true; + + pager_open_if_enabled(); + + if (arg_action == ACTION_HELP) { + retval = help(); + goto finish; + } else if (arg_action == ACTION_VERSION) { + retval = version(); + goto finish; + } else if (arg_action == ACTION_DUMP_CONFIGURATION_ITEMS) { + unit_dump_config_items(stdout); + retval = EXIT_SUCCESS; + goto finish; + } else if (arg_action == ACTION_DONE) { + retval = EXIT_SUCCESS; + goto finish; + } + + if (arg_running_as == MANAGER_USER && + !getenv("XDG_RUNTIME_DIR")) { + log_error("Trying to run as user instance, but $XDG_RUNTIME_DIR is not set."); + goto finish; + } + + assert_se(arg_action == ACTION_RUN || arg_action == ACTION_TEST); + + /* Close logging fds, in order not to confuse fdset below */ + log_close(); + + /* Remember open file descriptors for later deserialization */ + r = fdset_new_fill(&fds); + if (r < 0) { + log_emergency_errno(r, "Failed to allocate fd set: %m"); + error_message = "Failed to allocate fd set"; + goto finish; + } else + fdset_cloexec(fds, true); + + if (arg_serialization) + assert_se(fdset_remove(fds, fileno(arg_serialization)) >= 0); + + if (arg_running_as == MANAGER_SYSTEM) + /* Become a session leader if we aren't one yet. */ + setsid(); + + /* Move out of the way, so that we won't block unmounts */ + assert_se(chdir("/") == 0); + + /* Reset the console, but only if this is really init and we + * are freshly booted */ + if (arg_running_as == MANAGER_SYSTEM && arg_action == ACTION_RUN) { + + /* If we are init, we connect stdin/stdout/stderr to + * /dev/null and make sure we don't have a controlling + * tty. */ + release_terminal(); + + if (getpid() == 1 && !skip_setup) + console_setup(); + } + + /* Open the logging devices, if possible and necessary */ + log_open(); + + if (arg_show_status == _SHOW_STATUS_UNSET) + arg_show_status = SHOW_STATUS_YES; + + /* Make sure we leave a core dump without panicing the + * kernel. */ + if (getpid() == 1) { + install_crash_handler(); + + r = mount_cgroup_controllers(arg_join_controllers); + if (r < 0) + goto finish; + } + + if (arg_running_as == MANAGER_SYSTEM) { + int v; + + log_info(PACKAGE_STRING " running in %ssystem mode. (" SYSTEMD_FEATURES ")", + arg_action == ACTION_TEST ? "test " : "" ); + + v = detect_virtualization(); + if (v > 0) + log_info("Detected virtualization %s.", virtualization_to_string(v)); + + write_container_id(); + + log_info("Detected architecture %s.", architecture_to_string(uname_architecture())); + + if (in_initrd()) + log_info("Running in initial RAM disk."); + + /* Let's check whether /etc is already populated. We + * don't actually really check for that, but use + * /etc/machine-id as flag file. This allows container + * managers and installers to provision a couple of + * files already. If the container manager wants to + * provision the machine ID itself it should pass + * $container_uuid to PID 1. */ + + empty_etc = access("/etc/machine-id", F_OK) < 0; + if (empty_etc) + log_info("Running with unpopulated /etc."); + } else { + _cleanup_free_ char *t; + + t = uid_to_name(getuid()); + log_debug(PACKAGE_STRING " running in %suser mode for user "UID_FMT"/%s. (" SYSTEMD_FEATURES ")", + arg_action == ACTION_TEST ? " test" : "", getuid(), t); + } + + if (arg_running_as == MANAGER_SYSTEM && !skip_setup) { + if (arg_show_status > 0) + status_welcome(); + + hostname_setup(); + machine_id_setup(NULL, arg_machine_id); + loopback_setup(); + bump_unix_max_dgram_qlen(); + + test_usr(); + } + + if (arg_running_as == MANAGER_SYSTEM && arg_runtime_watchdog > 0) + watchdog_set_timeout(&arg_runtime_watchdog); + + if (arg_timer_slack_nsec != NSEC_INFINITY) + if (prctl(PR_SET_TIMERSLACK, arg_timer_slack_nsec) < 0) + log_error_errno(errno, "Failed to adjust timer slack: %m"); + + if (!cap_test_all(arg_capability_bounding_set)) { + r = capability_bounding_set_drop_usermode(arg_capability_bounding_set); + if (r < 0) { + log_emergency_errno(r, "Failed to drop capability bounding set of usermode helpers: %m"); + error_message = "Failed to drop capability bounding set of usermode helpers"; + goto finish; + } + r = capability_bounding_set_drop(arg_capability_bounding_set, true); + if (r < 0) { + log_emergency_errno(r, "Failed to drop capability bounding set: %m"); + error_message = "Failed to drop capability bounding set"; + goto finish; + } + } + + if (arg_syscall_archs) { + r = enforce_syscall_archs(arg_syscall_archs); + if (r < 0) { + error_message = "Failed to set syscall architectures"; + goto finish; + } + } + + if (arg_running_as == MANAGER_USER) + /* Become reaper of our children */ + if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) + log_warning_errno(errno, "Failed to make us a subreaper: %m"); + + if (arg_running_as == MANAGER_SYSTEM) { + bump_rlimit_nofile(&saved_rlimit_nofile); + + if (empty_etc) { + r = unit_file_preset_all(UNIT_FILE_SYSTEM, false, NULL, UNIT_FILE_PRESET_ENABLE_ONLY, false, NULL, 0); + if (r < 0) + log_full_errno(r == -EEXIST ? LOG_NOTICE : LOG_WARNING, r, "Failed to populate /etc with preset unit settings, ignoring: %m"); + else + log_info("Populated /etc with preset unit settings."); + } + } + + r = manager_new(arg_running_as, arg_action == ACTION_TEST, &m); + if (r < 0) { + log_emergency_errno(r, "Failed to allocate manager object: %m"); + error_message = "Failed to allocate manager object"; + goto finish; + } + + m->confirm_spawn = arg_confirm_spawn; + m->runtime_watchdog = arg_runtime_watchdog; + m->shutdown_watchdog = arg_shutdown_watchdog; + m->userspace_timestamp = userspace_timestamp; + m->kernel_timestamp = kernel_timestamp; + m->initrd_timestamp = initrd_timestamp; + m->security_start_timestamp = security_start_timestamp; + m->security_finish_timestamp = security_finish_timestamp; + + manager_set_defaults(m); + manager_set_show_status(m, arg_show_status); + manager_set_first_boot(m, empty_etc); + + /* Remember whether we should queue the default job */ + queue_default_job = !arg_serialization || arg_switched_root; + + before_startup = now(CLOCK_MONOTONIC); + + r = manager_startup(m, arg_serialization, fds); + if (r < 0) + log_error_errno(r, "Failed to fully start up daemon: %m"); + + /* This will close all file descriptors that were opened, but + * not claimed by any unit. */ + fds = fdset_free(fds); + + arg_serialization = safe_fclose(arg_serialization); + + if (queue_default_job) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + Unit *target = NULL; + Job *default_unit_job; + + log_debug("Activating default unit: %s", arg_default_unit); + + r = manager_load_unit(m, arg_default_unit, NULL, &error, &target); + if (r < 0) + log_error("Failed to load default target: %s", bus_error_message(&error, r)); + else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) + log_error_errno(target->load_error, "Failed to load default target: %m"); + else if (target->load_state == UNIT_MASKED) + log_error("Default target masked."); + + if (!target || target->load_state != UNIT_LOADED) { + log_info("Trying to load rescue target..."); + + r = manager_load_unit(m, SPECIAL_RESCUE_TARGET, NULL, &error, &target); + if (r < 0) { + log_emergency("Failed to load rescue target: %s", bus_error_message(&error, r)); + error_message = "Failed to load rescue target"; + goto finish; + } else if (target->load_state == UNIT_ERROR || target->load_state == UNIT_NOT_FOUND) { + log_emergency_errno(target->load_error, "Failed to load rescue target: %m"); + error_message = "Failed to load rescue target"; + goto finish; + } else if (target->load_state == UNIT_MASKED) { + log_emergency("Rescue target masked."); + error_message = "Rescue target masked"; + goto finish; + } + } + + assert(target->load_state == UNIT_LOADED); + + if (arg_action == ACTION_TEST) { + printf("-> By units:\n"); + manager_dump_units(m, stdout, "\t"); + } + + r = manager_add_job(m, JOB_START, target, JOB_ISOLATE, &error, &default_unit_job); + if (r == -EPERM) { + log_debug("Default target could not be isolated, starting instead: %s", bus_error_message(&error, r)); + + sd_bus_error_free(&error); + + r = manager_add_job(m, JOB_START, target, JOB_REPLACE, &error, &default_unit_job); + if (r < 0) { + log_emergency("Failed to start default target: %s", bus_error_message(&error, r)); + error_message = "Failed to start default target"; + goto finish; + } + } else if (r < 0) { + log_emergency("Failed to isolate default target: %s", bus_error_message(&error, r)); + error_message = "Failed to isolate default target"; + goto finish; + } + + m->default_unit_job_id = default_unit_job->id; + + after_startup = now(CLOCK_MONOTONIC); + log_full(arg_action == ACTION_TEST ? LOG_INFO : LOG_DEBUG, + "Loaded units and determined initial transaction in %s.", + format_timespan(timespan, sizeof(timespan), after_startup - before_startup, 100 * USEC_PER_MSEC)); + + if (arg_action == ACTION_TEST) { + printf("-> By jobs:\n"); + manager_dump_jobs(m, stdout, "\t"); + retval = EXIT_SUCCESS; + goto finish; + } + } + + for (;;) { + r = manager_loop(m); + if (r < 0) { + log_emergency_errno(r, "Failed to run main loop: %m"); + error_message = "Failed to run main loop"; + goto finish; + } + + switch (m->exit_code) { + + case MANAGER_RELOAD: + log_info("Reloading."); + + r = parse_config_file(); + if (r < 0) + log_error("Failed to parse config file."); + + manager_set_defaults(m); + + r = manager_reload(m); + if (r < 0) + log_error_errno(r, "Failed to reload: %m"); + break; + + case MANAGER_REEXECUTE: + + if (prepare_reexecute(m, &arg_serialization, &fds, false) < 0) { + error_message = "Failed to prepare for reexecution"; + goto finish; + } + + reexecute = true; + log_notice("Reexecuting."); + goto finish; + + case MANAGER_SWITCH_ROOT: + /* Steal the switch root parameters */ + switch_root_dir = m->switch_root; + switch_root_init = m->switch_root_init; + m->switch_root = m->switch_root_init = NULL; + + if (!switch_root_init) + if (prepare_reexecute(m, &arg_serialization, &fds, true) < 0) { + error_message = "Failed to prepare for reexecution"; + goto finish; + } + + reexecute = true; + log_notice("Switching root."); + goto finish; + + case MANAGER_EXIT: + retval = m->return_value; + + if (m->running_as == MANAGER_USER) { + log_debug("Exit."); + goto finish; + } + + /* fallthrough */ + case MANAGER_REBOOT: + case MANAGER_POWEROFF: + case MANAGER_HALT: + case MANAGER_KEXEC: { + static const char * const table[_MANAGER_EXIT_CODE_MAX] = { + [MANAGER_EXIT] = "exit", + [MANAGER_REBOOT] = "reboot", + [MANAGER_POWEROFF] = "poweroff", + [MANAGER_HALT] = "halt", + [MANAGER_KEXEC] = "kexec" + }; + + assert_se(shutdown_verb = table[m->exit_code]); + arm_reboot_watchdog = m->exit_code == MANAGER_REBOOT; + + log_notice("Shutting down."); + goto finish; + } + + default: + assert_not_reached("Unknown exit code."); + } + } + +finish: + pager_close(); + + if (m) + arg_shutdown_watchdog = m->shutdown_watchdog; + + m = manager_free(m); + + for (j = 0; j < ELEMENTSOF(arg_default_rlimit); j++) + arg_default_rlimit[j] = mfree(arg_default_rlimit[j]); + + arg_default_unit = mfree(arg_default_unit); + arg_join_controllers = strv_free_free(arg_join_controllers); + arg_default_environment = strv_free(arg_default_environment); + arg_syscall_archs = set_free(arg_syscall_archs); + + mac_selinux_finish(); + + if (reexecute) { + const char **args; + unsigned i, args_size; + + /* Close and disarm the watchdog, so that the new + * instance can reinitialize it, but doesn't get + * rebooted while we do that */ + watchdog_close(true); + + /* Reset the RLIMIT_NOFILE to the kernel default, so + * that the new systemd can pass the kernel default to + * its child processes */ + if (saved_rlimit_nofile.rlim_cur > 0) + (void) setrlimit(RLIMIT_NOFILE, &saved_rlimit_nofile); + + if (switch_root_dir) { + /* Kill all remaining processes from the + * initrd, but don't wait for them, so that we + * can handle the SIGCHLD for them after + * deserializing. */ + broadcast_signal(SIGTERM, false, true); + + /* And switch root with MS_MOVE, because we remove the old directory afterwards and detach it. */ + r = switch_root(switch_root_dir, "/mnt", true, MS_MOVE); + if (r < 0) + log_error_errno(r, "Failed to switch root, trying to continue: %m"); + } + + args_size = MAX(6, argc+1); + args = newa(const char*, args_size); + + if (!switch_root_init) { + char sfd[DECIMAL_STR_MAX(int) + 1]; + + /* First try to spawn ourselves with the right + * path, and with full serialization. We do + * this only if the user didn't specify an + * explicit init to spawn. */ + + assert(arg_serialization); + assert(fds); + + xsprintf(sfd, "%i", fileno(arg_serialization)); + + i = 0; + args[i++] = SYSTEMD_BINARY_PATH; + if (switch_root_dir) + args[i++] = "--switched-root"; + args[i++] = arg_running_as == MANAGER_SYSTEM ? "--system" : "--user"; + args[i++] = "--deserialize"; + 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); + + /* + * We want valgrind to print its memory usage summary before reexecution. + * Valgrind won't do this is on its own on exec(), but it will do it on exit(). + * Hence, to ensure we get a summary here, fork() off a child, let it exit() cleanly, + * so that it prints the summary, and wait() for it in the parent, before proceeding into the exec(). + */ + valgrind_summary_hack(); + + (void) execv(args[0], (char* const*) args); + } + + /* Try the fallback, if there is any, without any + * serialization. We pass the original argv[] and + * envp[]. (Well, modulo the ordering changes due to + * getopt() in argv[], and some cleanups in envp[], + * but let's hope that doesn't matter.) */ + + 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; + assert(i <= args_size); + + /* Reenable any blocked signals, especially important + * if we switch from initial ramdisk to init=... */ + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + if (switch_root_init) { + args[0] = switch_root_init; + (void) execv(args[0], (char* const*) args); + log_warning_errno(errno, "Failed to execute configured init, trying fallback: %m"); + } + + args[0] = "/sbin/init"; + (void) execv(args[0], (char* const*) args); + + if (errno == ENOENT) { + log_warning("No /sbin/init, trying fallback"); + + args[0] = "/bin/sh"; + args[1] = NULL; + (void) execv(args[0], (char* const*) args); + log_error_errno(errno, "Failed to execute /bin/sh, giving up: %m"); + } else + log_warning_errno(errno, "Failed to execute /sbin/init, giving up: %m"); + } + + arg_serialization = safe_fclose(arg_serialization); + fds = fdset_free(fds); + +#ifdef HAVE_VALGRIND_VALGRIND_H + /* If we are PID 1 and running under valgrind, then let's exit + * here explicitly. valgrind will only generate nice output on + * exit(), not on exec(), hence let's do the former not the + * latter here. */ + if (getpid() == 1 && RUNNING_ON_VALGRIND) + return 0; +#endif + + if (shutdown_verb) { + char log_level[DECIMAL_STR_MAX(int) + 1]; + char exit_code[DECIMAL_STR_MAX(uint8_t) + 1]; + const char* command_line[11] = { + SYSTEMD_SHUTDOWN_BINARY_PATH, + shutdown_verb, + "--log-level", log_level, + "--log-target", + }; + unsigned pos = 5; + _cleanup_strv_free_ char **env_block = NULL; + + assert(command_line[pos] == NULL); + env_block = strv_copy(environ); + + xsprintf(log_level, "%d", log_get_max_level()); + + switch (log_get_target()) { + + case LOG_TARGET_KMSG: + case LOG_TARGET_JOURNAL_OR_KMSG: + case LOG_TARGET_SYSLOG_OR_KMSG: + command_line[pos++] = "kmsg"; + break; + + case LOG_TARGET_NULL: + command_line[pos++] = "null"; + break; + + case LOG_TARGET_CONSOLE: + default: + command_line[pos++] = "console"; + break; + }; + + if (log_get_show_color()) + command_line[pos++] = "--log-color"; + + if (log_get_show_location()) + command_line[pos++] = "--log-location"; + + if (streq(shutdown_verb, "exit")) { + command_line[pos++] = "--exit-code"; + command_line[pos++] = exit_code; + xsprintf(exit_code, "%d", retval); + } + + assert(pos < ELEMENTSOF(command_line)); + + if (arm_reboot_watchdog && arg_shutdown_watchdog > 0) { + char *e; + + /* If we reboot let's set the shutdown + * watchdog and tell the shutdown binary to + * repeatedly ping it */ + r = watchdog_set_timeout(&arg_shutdown_watchdog); + watchdog_close(r < 0); + + /* Tell the binary how often to ping, ignore failure */ + if (asprintf(&e, "WATCHDOG_USEC="USEC_FMT, arg_shutdown_watchdog) > 0) + (void) strv_push(&env_block, e); + } else + watchdog_close(true); + + /* Avoid the creation of new processes forked by the + * kernel; at this point, we will not listen to the + * signals anyway */ + if (detect_container() <= 0) + (void) cg_uninstall_release_agent(SYSTEMD_CGROUP_CONTROLLER); + + execve(SYSTEMD_SHUTDOWN_BINARY_PATH, (char **) command_line, env_block); + log_error_errno(errno, "Failed to execute shutdown binary, %s: %m", + getpid() == 1 ? "freezing" : "quitting"); + } + + if (getpid() == 1) { + if (error_message) + manager_status_printf(NULL, STATUS_TYPE_EMERGENCY, + ANSI_HIGHLIGHT_RED "!!!!!!" ANSI_NORMAL, + "%s, freezing.", error_message); + freeze_or_reboot(); + } + + return retval; +} diff --git a/src/system/systemd/org.freedesktop.systemd1.conf b/src/system/systemd/org.freedesktop.systemd1.conf new file mode 100644 index 0000000000..6a7a37ee92 --- /dev/null +++ b/src/system/systemd/org.freedesktop.systemd1.conf @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/system/systemd/org.freedesktop.systemd1.policy.in.in b/src/system/systemd/org.freedesktop.systemd1.policy.in.in new file mode 100644 index 0000000000..cc39a9e1c3 --- /dev/null +++ b/src/system/systemd/org.freedesktop.systemd1.policy.in.in @@ -0,0 +1,70 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Send passphrase back to system + <_message>Authentication is required to send the entered passphrase back to the system. + + no + no + auth_admin_keep + + @rootlibexecdir@/systemd-reply-password + + + + <_description>Manage system services or other units + <_message>Authentication is required to manage system services or other units. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Manage system service or unit files + <_message>Authentication is required to manage system service or unit files. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Set or unset system and service manager environment variables + <_message>Authentication is required to set or unset system and service manager environment variables. + + auth_admin + auth_admin + auth_admin_keep + + + + + <_description>Reload the systemd state + <_message>Authentication is required to reload the systemd state. + + auth_admin + auth_admin + auth_admin_keep + + + + diff --git a/src/system/systemd/org.freedesktop.systemd1.service b/src/system/systemd/org.freedesktop.systemd1.service new file mode 100644 index 0000000000..d4df3e93a2 --- /dev/null +++ b/src/system/systemd/org.freedesktop.systemd1.service @@ -0,0 +1,11 @@ +# 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. + +[D-BUS Service] +Name=org.freedesktop.systemd1 +Exec=/bin/false +User=root diff --git a/src/system/systemd/system.conf b/src/system/systemd/system.conf new file mode 100644 index 0000000000..e2ded27333 --- /dev/null +++ b/src/system/systemd/system.conf @@ -0,0 +1,60 @@ +# 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. +# +# Entries in this file show the compile time defaults. +# You can change settings by editing this file. +# Defaults can be restored by simply deleting this file. +# +# See systemd-system.conf(5) for details. + +[Manager] +#LogLevel=info +#LogTarget=journal-or-kmsg +#LogColor=yes +#LogLocation=no +#DumpCore=yes +#ShowStatus=yes +#CrashChangeVT=no +#CrashShell=no +#CrashReboot=no +#CPUAffinity=1 2 +#JoinControllers=cpu,cpuacct net_cls,net_prio +#RuntimeWatchdogSec=0 +#ShutdownWatchdogSec=10min +#CapabilityBoundingSet= +#SystemCallArchitectures= +#TimerSlackNSec= +#DefaultTimerAccuracySec=1min +#DefaultStandardOutput=journal +#DefaultStandardError=inherit +#DefaultTimeoutStartSec=90s +#DefaultTimeoutStopSec=90s +#DefaultRestartSec=100ms +#DefaultStartLimitInterval=10s +#DefaultStartLimitBurst=5 +#DefaultEnvironment= +#DefaultCPUAccounting=no +#DefaultBlockIOAccounting=no +#DefaultMemoryAccounting=no +#DefaultTasksAccounting=yes +#DefaultTasksMax=512 +#DefaultLimitCPU= +#DefaultLimitFSIZE= +#DefaultLimitDATA= +#DefaultLimitSTACK= +#DefaultLimitCORE= +#DefaultLimitRSS= +#DefaultLimitNOFILE= +#DefaultLimitAS= +#DefaultLimitNPROC= +#DefaultLimitMEMLOCK= +#DefaultLimitLOCKS= +#DefaultLimitSIGPENDING= +#DefaultLimitMSGQUEUE= +#DefaultLimitNICE= +#DefaultLimitRTPRIO= +#DefaultLimitRTTIME= diff --git a/src/system/systemd/systemd.pc.in b/src/system/systemd/systemd.pc.in new file mode 100644 index 0000000000..ac52b30dd3 --- /dev/null +++ b/src/system/systemd/systemd.pc.in @@ -0,0 +1,34 @@ +# 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. + +prefix=@prefix@ +systemdutildir=@rootlibexecdir@ +systemdsystemunitdir=@systemunitdir@ +systemdsystempresetdir=@systempresetdir@ +systemduserunitdir=@userunitdir@ +systemduserpresetdir=@userpresetdir@ +systemdsystemconfdir=@pkgsysconfdir@/system +systemduserconfdir=@pkgsysconfdir@/user +systemdsystemunitpath=${systemdsystemconfdir}:/etc/systemd/system:/run/systemd/system:/usr/local/lib/systemd/system:${systemdsystemunitdir}:/usr/lib/systemd/system:/lib/systemd/system +systemduserunitpath=${systemduserconfdir}:/etc/systemd/user:/run/systemd/user:/usr/local/lib/systemd/user:/usr/local/share/systemd/user:${systemduserunitdir}:/usr/lib/systemd/user:/usr/share/systemd/user +systemdsystemgeneratordir=@systemgeneratordir@ +systemdusergeneratordir=@usergeneratordir@ +systemdsleepdir=@systemsleepdir@ +systemdshutdowndir=@systemshutdowndir@ +tmpfilesdir=@tmpfilesdir@ +sysusersdir=@sysusersdir@ +sysctldir=@sysctldir@ +binfmtdir=@binfmtdir@ +modulesloaddir=@modulesloaddir@ +catalogdir=@catalogdir@ +systemuidmax=@systemuidmax@ +systemgidmax=@systemgidmax@ + +Name: systemd +Description: systemd System and Service Manager +URL: @PACKAGE_URL@ +Version: @PACKAGE_VERSION@ diff --git a/src/system/systemd/triggers.systemd.in b/src/system/systemd/triggers.systemd.in new file mode 100644 index 0000000000..9e18a39a67 --- /dev/null +++ b/src/system/systemd/triggers.systemd.in @@ -0,0 +1,64 @@ +# -*- Mode: rpm-spec; indent-tabs-mode: nil -*- */ +# +# This file is part of systemd. +# +# Copyright 2015 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 . + +# The contents of this are an example to be copied into systemd.spec. + +%transfiletriggerin -P 900900 -p -- @systemunitdir@ /etc/systemd/system +-- This script will run after any package is initially installed or +-- upgraded. We care about the case where a package is initially +-- installed, because other cases are covered by the *un scriptlets, +-- so sometimes we will reload needlessly. + +pid = posix.fork() +if pid == 0 then + assert(posix.exec("%{_bindir}/systemctl", "daemon-reload")) +elseif pid > 0 then + posix.wait(pid) +end + +%transfiletriggerun -p -- @systemunitdir@ /etc/systemd/system +-- On removal, we need to run daemon-reload after any units have been +-- removed. %transfiletriggerpostun would be ideal, but it does not get +-- executed for some reason. +-- On upgrade, we need to run daemon-reload after any new unit files +-- have been installed, but before %postun scripts in packages get +-- executed. %transfiletriggerun gets the right list of files +-- but it is invoked too early (before changes happen). +-- %filetriggerpostun happens at the right time, but it fires for +-- every package. +-- To execute the reload at the right time, we create a state +-- file in %transfiletriggerun and execute the daemon-reload in +-- the first %filetriggerpostun. + +posix.mkdir("%{_localstatedir}/lib") +posix.mkdir("%{_localstatedir}/lib/rpm-state") +posix.mkdir("%{_localstatedir}/lib/rpm-state/systemd") +io.open("%{_localstatedir}/lib/rpm-state/systemd/needs-reload", "w") + +%filetriggerpostun -P 1000100 -p -- @systemunitdir@ /etc/systemd/system +if posix.access("%{_localstatedir}/lib/rpm-state/systemd/needs-reload") then + posix.unlink("%{_localstatedir}/lib/rpm-state/systemd/needs-reload") + posix.rmdir("%{_localstatedir}/lib/rpm-state/systemd") + pid = posix.fork() + if pid == 0 then + assert(posix.exec("%{_bindir}/systemctl", "daemon-reload")) + elseif pid > 0 then + posix.wait(pid) + end +end diff --git a/src/system/systemd/user.conf b/src/system/systemd/user.conf new file mode 100644 index 0000000000..87c8164378 --- /dev/null +++ b/src/system/systemd/user.conf @@ -0,0 +1,44 @@ +# 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. +# +# You can override the directives in this file by creating files in +# /etc/systemd/user.conf.d/*.conf. +# +# See systemd-user.conf(5) for details + +[Manager] +#LogLevel=info +#LogTarget=console +#LogColor=yes +#LogLocation=no +#SystemCallArchitectures= +#TimerSlackNSec= +#DefaultTimerAccuracySec=1min +#DefaultStandardOutput=inherit +#DefaultStandardError=inherit +#DefaultTimeoutStartSec=90s +#DefaultTimeoutStopSec=90s +#DefaultRestartSec=100ms +#DefaultStartLimitInterval=10s +#DefaultStartLimitBurst=5 +#DefaultEnvironment= +#DefaultLimitCPU= +#DefaultLimitFSIZE= +#DefaultLimitDATA= +#DefaultLimitSTACK= +#DefaultLimitCORE= +#DefaultLimitRSS= +#DefaultLimitNOFILE= +#DefaultLimitAS= +#DefaultLimitNPROC= +#DefaultLimitMEMLOCK= +#DefaultLimitLOCKS= +#DefaultLimitSIGPENDING= +#DefaultLimitMSGQUEUE= +#DefaultLimitNICE= +#DefaultLimitRTPRIO= +#DefaultLimitRTTIME= diff --git a/src/systemctl/Makefile b/src/systemctl/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/systemctl/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c deleted file mode 100644 index 908ccabf8a..0000000000 --- a/src/systemctl/systemctl.c +++ /dev/null @@ -1,7703 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering - Copyright 2013 Marc-Antoine Perennou - - 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-bus.h" -#include "sd-daemon.h" -#include "sd-login.h" - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-message.h" -#include "bus-util.h" -#include "cgroup-show.h" -#include "cgroup-util.h" -#include "copy.h" -#include "dropin.h" -#include "efivars.h" -#include "env-util.h" -#include "exit-status.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "glob-util.h" -#include "hostname-util.h" -#include "initreq.h" -#include "install.h" -#include "io-util.h" -#include "list.h" -#include "locale-util.h" -#include "log.h" -#include "logs-show.h" -#include "macro.h" -#include "mkdir.h" -#include "pager.h" -#include "parse-util.h" -#include "path-lookup.h" -#include "path-util.h" -#include "process-util.h" -#include "rlimit-util.h" -#include "set.h" -#include "signal-util.h" -#include "socket-util.h" -#include "spawn-ask-password-agent.h" -#include "spawn-polkit-agent.h" -#include "special.h" -#include "stat-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "unit-name.h" -#include "user-util.h" -#include "util.h" -#include "utmp-wtmp.h" -#include "verbs.h" -#include "virt.h" - -static char **arg_types = NULL; -static char **arg_states = NULL; -static char **arg_properties = NULL; -static bool arg_all = false; -static enum dependency { - DEPENDENCY_FORWARD, - DEPENDENCY_REVERSE, - DEPENDENCY_AFTER, - DEPENDENCY_BEFORE, - _DEPENDENCY_MAX -} arg_dependency = DEPENDENCY_FORWARD; -static const char *arg_job_mode = "replace"; -static UnitFileScope arg_scope = UNIT_FILE_SYSTEM; -static bool arg_no_block = false; -static bool arg_no_legend = false; -static bool arg_no_pager = false; -static bool arg_no_wtmp = false; -static bool arg_no_wall = false; -static bool arg_no_reload = false; -static bool arg_show_types = false; -static bool arg_ignore_inhibitors = false; -static bool arg_dry = false; -static bool arg_quiet = false; -static bool arg_full = false; -static bool arg_recursive = false; -static int arg_force = 0; -static bool arg_ask_password = false; -static bool arg_runtime = false; -static UnitFilePresetMode arg_preset_mode = UNIT_FILE_PRESET_FULL; -static char **arg_wall = NULL; -static const char *arg_kill_who = NULL; -static int arg_signal = SIGTERM; -static char *arg_root = NULL; -static usec_t arg_when = 0; -static enum action { - _ACTION_INVALID, - ACTION_SYSTEMCTL, - ACTION_HALT, - ACTION_POWEROFF, - ACTION_REBOOT, - ACTION_KEXEC, - ACTION_EXIT, - ACTION_SUSPEND, - ACTION_HIBERNATE, - ACTION_HYBRID_SLEEP, - ACTION_RUNLEVEL2, - ACTION_RUNLEVEL3, - ACTION_RUNLEVEL4, - ACTION_RUNLEVEL5, - ACTION_RESCUE, - ACTION_EMERGENCY, - ACTION_DEFAULT, - ACTION_RELOAD, - ACTION_REEXEC, - ACTION_RUNLEVEL, - ACTION_CANCEL_SHUTDOWN, - _ACTION_MAX -} arg_action = ACTION_SYSTEMCTL; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static const char *arg_host = NULL; -static unsigned arg_lines = 10; -static OutputMode arg_output = OUTPUT_SHORT; -static bool arg_plain = false; -static bool arg_firmware_setup = false; -static bool arg_now = false; - -static int daemon_reload(int argc, char *argv[], void* userdata); -static int halt_now(enum action a); -static int check_one_unit(sd_bus *bus, const char *name, const char *good_states, bool quiet); - -static bool original_stdout_is_tty; - -typedef enum BusFocus { - BUS_FULL, /* The full bus indicated via --system or --user */ - BUS_MANAGER, /* The manager itself, possibly directly, possibly via the bus */ - _BUS_FOCUS_MAX -} BusFocus; - -static sd_bus *busses[_BUS_FOCUS_MAX] = {}; - -static int acquire_bus(BusFocus focus, sd_bus **ret) { - int r; - - assert(focus < _BUS_FOCUS_MAX); - assert(ret); - - /* We only go directly to the manager, if we are using a local transport */ - if (arg_transport != BUS_TRANSPORT_LOCAL) - focus = BUS_FULL; - - if (!busses[focus]) { - bool user; - - user = arg_scope != UNIT_FILE_SYSTEM; - - if (focus == BUS_MANAGER) - r = bus_connect_transport_systemd(arg_transport, arg_host, user, &busses[focus]); - else - r = bus_connect_transport(arg_transport, arg_host, user, &busses[focus]); - if (r < 0) - return log_error_errno(r, "Failed to connect to bus: %m"); - - (void) sd_bus_set_allow_interactive_authorization(busses[focus], arg_ask_password); - } - - *ret = busses[focus]; - return 0; -} - -static void release_busses(void) { - BusFocus w; - - for (w = 0; w < _BUS_FOCUS_MAX; w++) - busses[w] = sd_bus_flush_close_unref(busses[w]); -} - -static void pager_open_if_enabled(void) { - - if (arg_no_pager) - return; - - pager_open(false); -} - -static void ask_password_agent_open_if_enabled(void) { - - /* Open the password agent as a child process if necessary */ - - if (!arg_ask_password) - return; - - if (arg_scope != UNIT_FILE_SYSTEM) - return; - - if (arg_transport != BUS_TRANSPORT_LOCAL) - return; - - ask_password_agent_open(); -} - -static void polkit_agent_open_if_enabled(void) { - - /* Open the polkit agent as a child process if necessary */ - - if (!arg_ask_password) - return; - - if (arg_scope != UNIT_FILE_SYSTEM) - return; - - if (arg_transport != BUS_TRANSPORT_LOCAL) - return; - - polkit_agent_open(); -} - -static OutputFlags get_output_flags(void) { - return - arg_all * OUTPUT_SHOW_ALL | - arg_full * OUTPUT_FULL_WIDTH | - (!on_tty() || pager_have()) * OUTPUT_FULL_WIDTH | - colors_enabled() * OUTPUT_COLOR | - !arg_quiet * OUTPUT_WARN_CUTOFF; -} - -static int translate_bus_error_to_exit_status(int r, const sd_bus_error *error) { - assert(error); - - if (!sd_bus_error_is_set(error)) - return r; - - if (sd_bus_error_has_name(error, SD_BUS_ERROR_ACCESS_DENIED) || - sd_bus_error_has_name(error, BUS_ERROR_ONLY_BY_DEPENDENCY) || - sd_bus_error_has_name(error, BUS_ERROR_NO_ISOLATION) || - sd_bus_error_has_name(error, BUS_ERROR_TRANSACTION_IS_DESTRUCTIVE)) - return EXIT_NOPERMISSION; - - if (sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT)) - return EXIT_NOTINSTALLED; - - if (sd_bus_error_has_name(error, BUS_ERROR_JOB_TYPE_NOT_APPLICABLE) || - sd_bus_error_has_name(error, SD_BUS_ERROR_NOT_SUPPORTED)) - return EXIT_NOTIMPLEMENTED; - - if (sd_bus_error_has_name(error, BUS_ERROR_LOAD_FAILED)) - return EXIT_NOTCONFIGURED; - - if (r != 0) - return r; - - return EXIT_FAILURE; -} - -static bool install_client_side(void) { - - /* Decides when to execute enable/disable/... operations - * client-side rather than server-side. */ - - if (running_in_chroot() > 0) - return true; - - if (sd_booted() <= 0) - return true; - - if (!isempty(arg_root)) - return true; - - if (arg_scope == UNIT_FILE_GLOBAL) - return true; - - /* Unsupported environment variable, mostly for debugging purposes */ - if (getenv_bool("SYSTEMCTL_INSTALL_CLIENT_SIDE") > 0) - return true; - - return false; -} - -static int compare_unit_info(const void *a, const void *b) { - const UnitInfo *u = a, *v = b; - const char *d1, *d2; - int r; - - /* First, order by machine */ - if (!u->machine && v->machine) - return -1; - if (u->machine && !v->machine) - return 1; - if (u->machine && v->machine) { - r = strcasecmp(u->machine, v->machine); - if (r != 0) - return r; - } - - /* Second, order by unit type */ - d1 = strrchr(u->id, '.'); - d2 = strrchr(v->id, '.'); - if (d1 && d2) { - r = strcasecmp(d1, d2); - if (r != 0) - return r; - } - - /* Third, order by name */ - return strcasecmp(u->id, v->id); -} - -static bool output_show_unit(const UnitInfo *u, char **patterns) { - if (!strv_fnmatch_or_empty(patterns, u->id, FNM_NOESCAPE)) - return false; - - if (arg_types) { - const char *dot; - - dot = strrchr(u->id, '.'); - if (!dot) - return false; - - if (!strv_find(arg_types, dot+1)) - return false; - } - - if (arg_all) - return true; - - if (u->job_id > 0) - return true; - - if (streq(u->active_state, "inactive") || u->following[0]) - return false; - - return true; -} - -static int output_units_list(const UnitInfo *unit_infos, unsigned c) { - unsigned circle_len = 0, id_len, max_id_len, load_len, active_len, sub_len, job_len, desc_len; - const UnitInfo *u; - unsigned n_shown = 0; - int job_count = 0; - - max_id_len = strlen("UNIT"); - load_len = strlen("LOAD"); - active_len = strlen("ACTIVE"); - sub_len = strlen("SUB"); - job_len = strlen("JOB"); - desc_len = 0; - - for (u = unit_infos; u < unit_infos + c; u++) { - max_id_len = MAX(max_id_len, strlen(u->id) + (u->machine ? strlen(u->machine)+1 : 0)); - load_len = MAX(load_len, strlen(u->load_state)); - active_len = MAX(active_len, strlen(u->active_state)); - sub_len = MAX(sub_len, strlen(u->sub_state)); - - if (u->job_id != 0) { - job_len = MAX(job_len, strlen(u->job_type)); - job_count++; - } - - if (!arg_no_legend && - (streq(u->active_state, "failed") || - STR_IN_SET(u->load_state, "error", "not-found", "masked"))) - circle_len = 2; - } - - if (!arg_full && original_stdout_is_tty) { - unsigned basic_len; - - id_len = MIN(max_id_len, 25u); - basic_len = circle_len + 5 + id_len + 5 + active_len + sub_len; - - if (job_count) - basic_len += job_len + 1; - - if (basic_len < (unsigned) columns()) { - unsigned extra_len, incr; - extra_len = columns() - basic_len; - - /* Either UNIT already got 25, or is fully satisfied. - * Grant up to 25 to DESC now. */ - incr = MIN(extra_len, 25u); - desc_len += incr; - extra_len -= incr; - - /* split the remaining space between UNIT and DESC, - * but do not give UNIT more than it needs. */ - if (extra_len > 0) { - incr = MIN(extra_len / 2, max_id_len - id_len); - id_len += incr; - desc_len += extra_len - incr; - } - } - } else - id_len = max_id_len; - - for (u = unit_infos; u < unit_infos + c; u++) { - _cleanup_free_ char *e = NULL, *j = NULL; - const char *on_loaded = "", *off_loaded = ""; - const char *on_active = "", *off_active = ""; - const char *on_circle = "", *off_circle = ""; - const char *id; - bool circle = false; - - if (!n_shown && !arg_no_legend) { - - if (circle_len > 0) - fputs(" ", stdout); - - printf("%-*s %-*s %-*s %-*s ", - id_len, "UNIT", - load_len, "LOAD", - active_len, "ACTIVE", - sub_len, "SUB"); - - if (job_count) - printf("%-*s ", job_len, "JOB"); - - if (!arg_full && arg_no_pager) - printf("%.*s\n", desc_len, "DESCRIPTION"); - else - printf("%s\n", "DESCRIPTION"); - } - - n_shown++; - - if (STR_IN_SET(u->load_state, "error", "not-found", "masked") && !arg_plain) { - on_loaded = ansi_highlight_red(); - on_circle = ansi_highlight_yellow(); - off_loaded = off_circle = ansi_normal(); - circle = true; - } else if (streq(u->active_state, "failed") && !arg_plain) { - on_circle = on_active = ansi_highlight_red(); - off_circle = off_active = ansi_normal(); - circle = true; - } - - if (u->machine) { - j = strjoin(u->machine, ":", u->id, NULL); - if (!j) - return log_oom(); - - id = j; - } else - id = u->id; - - if (arg_full) { - e = ellipsize(id, id_len, 33); - if (!e) - return log_oom(); - - id = e; - } - - if (circle_len > 0) - printf("%s%s%s ", on_circle, circle ? draw_special_char(DRAW_BLACK_CIRCLE) : " ", off_circle); - - printf("%s%-*s%s %s%-*s%s %s%-*s %-*s%s %-*s", - on_active, id_len, id, off_active, - on_loaded, load_len, u->load_state, off_loaded, - on_active, active_len, u->active_state, - sub_len, u->sub_state, off_active, - job_count ? job_len + 1 : 0, u->job_id ? u->job_type : ""); - - if (desc_len > 0) - printf("%.*s\n", desc_len, u->description); - else - printf("%s\n", u->description); - } - - if (!arg_no_legend) { - const char *on, *off; - - if (n_shown) { - puts("\n" - "LOAD = Reflects whether the unit definition was properly loaded.\n" - "ACTIVE = The high-level unit activation state, i.e. generalization of SUB.\n" - "SUB = The low-level unit activation state, values depend on unit type."); - puts(job_count ? "JOB = Pending job for the unit.\n" : ""); - on = ansi_highlight(); - off = ansi_normal(); - } else { - on = ansi_highlight_red(); - off = ansi_normal(); - } - - if (arg_all) - printf("%s%u loaded units listed.%s\n" - "To show all installed unit files use 'systemctl list-unit-files'.\n", - on, n_shown, off); - else - printf("%s%u loaded units listed.%s Pass --all to see loaded but inactive units, too.\n" - "To show all installed unit files use 'systemctl list-unit-files'.\n", - on, n_shown, off); - } - - return 0; -} - -static int get_unit_list( - sd_bus *bus, - const char *machine, - char **patterns, - UnitInfo **unit_infos, - int c, - sd_bus_message **_reply) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - size_t size = c; - int r; - UnitInfo u; - - assert(bus); - assert(unit_infos); - assert(_reply); - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListUnitsFiltered"); - - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, arg_states); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = bus_parse_unit_info(reply, &u)) > 0) { - u.machine = machine; - - if (!output_show_unit(&u, patterns)) - continue; - - if (!GREEDY_REALLOC(*unit_infos, size, c+1)) - return log_oom(); - - (*unit_infos)[c++] = u; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - *_reply = reply; - reply = NULL; - - return c; -} - -static void message_set_freep(Set **set) { - sd_bus_message *m; - - while ((m = set_steal_first(*set))) - sd_bus_message_unref(m); - - set_free(*set); -} - -static int get_unit_list_recursive( - sd_bus *bus, - char **patterns, - UnitInfo **_unit_infos, - Set **_replies, - char ***_machines) { - - _cleanup_free_ UnitInfo *unit_infos = NULL; - _cleanup_(message_set_freep) Set *replies; - sd_bus_message *reply; - int c, r; - - assert(bus); - assert(_replies); - assert(_unit_infos); - assert(_machines); - - replies = set_new(NULL); - if (!replies) - return log_oom(); - - c = get_unit_list(bus, NULL, patterns, &unit_infos, 0, &reply); - if (c < 0) - return c; - - r = set_put(replies, reply); - if (r < 0) { - sd_bus_message_unref(reply); - return log_oom(); - } - - if (arg_recursive) { - _cleanup_strv_free_ char **machines = NULL; - char **i; - - r = sd_get_machine_names(&machines); - if (r < 0) - return log_error_errno(r, "Failed to get machine names: %m"); - - STRV_FOREACH(i, machines) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL; - int k; - - r = sd_bus_open_system_machine(&container, *i); - if (r < 0) { - log_warning_errno(r, "Failed to connect to container %s, ignoring: %m", *i); - continue; - } - - k = get_unit_list(container, *i, patterns, &unit_infos, c, &reply); - if (k < 0) - return k; - - c = k; - - r = set_put(replies, reply); - if (r < 0) { - sd_bus_message_unref(reply); - return log_oom(); - } - } - - *_machines = machines; - machines = NULL; - } else - *_machines = NULL; - - *_unit_infos = unit_infos; - unit_infos = NULL; - - *_replies = replies; - replies = NULL; - - return c; -} - -static int list_units(int argc, char *argv[], void *userdata) { - _cleanup_free_ UnitInfo *unit_infos = NULL; - _cleanup_(message_set_freep) Set *replies = NULL; - _cleanup_strv_free_ char **machines = NULL; - sd_bus *bus; - int r; - - pager_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); - if (r < 0) - return r; - - qsort_safe(unit_infos, r, sizeof(UnitInfo), compare_unit_info); - return output_units_list(unit_infos, r); -} - -static int get_triggered_units( - sd_bus *bus, - const char* path, - char*** ret) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(path); - assert(ret); - - r = sd_bus_get_property_strv( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "Triggers", - &error, - ret); - if (r < 0) - return log_error_errno(r, "Failed to determine triggers: %s", bus_error_message(&error, r)); - - return 0; -} - -static int get_listening( - sd_bus *bus, - const char* unit_path, - char*** listening) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *type, *path; - int r, n = 0; - - r = sd_bus_get_property( - bus, - "org.freedesktop.systemd1", - unit_path, - "org.freedesktop.systemd1.Socket", - "Listen", - &error, - &reply, - "a(ss)"); - if (r < 0) - return log_error_errno(r, "Failed to get list of listening sockets: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(ss)", &type, &path)) > 0) { - - r = strv_extend(listening, type); - if (r < 0) - return log_oom(); - - r = strv_extend(listening, path); - if (r < 0) - return log_oom(); - - n++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - return n; -} - -struct socket_info { - const char *machine; - const char* id; - - char* type; - char* path; - - /* Note: triggered is a list here, although it almost certainly - * will always be one unit. Nevertheless, dbus API allows for multiple - * values, so let's follow that. */ - char** triggered; - - /* The strv above is shared. free is set only in the first one. */ - bool own_triggered; -}; - -static int socket_info_compare(const struct socket_info *a, const struct socket_info *b) { - int o; - - assert(a); - assert(b); - - if (!a->machine && b->machine) - return -1; - if (a->machine && !b->machine) - return 1; - if (a->machine && b->machine) { - o = strcasecmp(a->machine, b->machine); - if (o != 0) - return o; - } - - o = strcmp(a->path, b->path); - if (o == 0) - o = strcmp(a->type, b->type); - - return o; -} - -static int output_sockets_list(struct socket_info *socket_infos, unsigned cs) { - struct socket_info *s; - unsigned pathlen = strlen("LISTEN"), - typelen = strlen("TYPE") * arg_show_types, - socklen = strlen("UNIT"), - servlen = strlen("ACTIVATES"); - const char *on, *off; - - for (s = socket_infos; s < socket_infos + cs; s++) { - unsigned tmp = 0; - char **a; - - socklen = MAX(socklen, strlen(s->id)); - if (arg_show_types) - typelen = MAX(typelen, strlen(s->type)); - pathlen = MAX(pathlen, strlen(s->path) + (s->machine ? strlen(s->machine)+1 : 0)); - - STRV_FOREACH(a, s->triggered) - tmp += strlen(*a) + 2*(a != s->triggered); - servlen = MAX(servlen, tmp); - } - - if (cs) { - if (!arg_no_legend) - printf("%-*s %-*.*s%-*s %s\n", - pathlen, "LISTEN", - typelen + arg_show_types, typelen + arg_show_types, "TYPE ", - socklen, "UNIT", - "ACTIVATES"); - - for (s = socket_infos; s < socket_infos + cs; s++) { - _cleanup_free_ char *j = NULL; - const char *path; - char **a; - - if (s->machine) { - j = strjoin(s->machine, ":", s->path, NULL); - if (!j) - return log_oom(); - path = j; - } else - path = s->path; - - if (arg_show_types) - printf("%-*s %-*s %-*s", - pathlen, path, typelen, s->type, socklen, s->id); - else - printf("%-*s %-*s", - pathlen, path, socklen, s->id); - STRV_FOREACH(a, s->triggered) - printf("%s %s", - a == s->triggered ? "" : ",", *a); - printf("\n"); - } - - on = ansi_highlight(); - off = ansi_normal(); - if (!arg_no_legend) - printf("\n"); - } else { - on = ansi_highlight_red(); - off = ansi_normal(); - } - - if (!arg_no_legend) { - printf("%s%u sockets listed.%s\n", on, cs, off); - if (!arg_all) - printf("Pass --all to see loaded but inactive sockets, too.\n"); - } - - return 0; -} - -static int list_sockets(int argc, char *argv[], void *userdata) { - _cleanup_(message_set_freep) Set *replies = NULL; - _cleanup_strv_free_ char **machines = NULL; - _cleanup_free_ UnitInfo *unit_infos = NULL; - _cleanup_free_ struct socket_info *socket_infos = NULL; - const UnitInfo *u; - struct socket_info *s; - unsigned cs = 0; - size_t size = 0; - int r = 0, n; - sd_bus *bus; - - pager_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - n = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); - if (n < 0) - return n; - - for (u = unit_infos; u < unit_infos + n; u++) { - _cleanup_strv_free_ char **listening = NULL, **triggered = NULL; - int i, c; - - if (!endswith(u->id, ".socket")) - continue; - - r = get_triggered_units(bus, u->unit_path, &triggered); - if (r < 0) - goto cleanup; - - c = get_listening(bus, u->unit_path, &listening); - if (c < 0) { - r = c; - goto cleanup; - } - - if (!GREEDY_REALLOC(socket_infos, size, cs + c)) { - r = log_oom(); - goto cleanup; - } - - for (i = 0; i < c; i++) - socket_infos[cs + i] = (struct socket_info) { - .machine = u->machine, - .id = u->id, - .type = listening[i*2], - .path = listening[i*2 + 1], - .triggered = triggered, - .own_triggered = i==0, - }; - - /* from this point on we will cleanup those socket_infos */ - cs += c; - free(listening); - listening = triggered = NULL; /* avoid cleanup */ - } - - qsort_safe(socket_infos, cs, sizeof(struct socket_info), - (__compar_fn_t) socket_info_compare); - - output_sockets_list(socket_infos, cs); - - cleanup: - assert(cs == 0 || socket_infos); - for (s = socket_infos; s < socket_infos + cs; s++) { - free(s->type); - free(s->path); - if (s->own_triggered) - strv_free(s->triggered); - } - - return r; -} - -static int get_next_elapse( - sd_bus *bus, - const char *path, - dual_timestamp *next) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - dual_timestamp t; - int r; - - assert(bus); - assert(path); - assert(next); - - r = sd_bus_get_property_trivial( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Timer", - "NextElapseUSecMonotonic", - &error, - 't', - &t.monotonic); - if (r < 0) - return log_error_errno(r, "Failed to get next elapsation time: %s", bus_error_message(&error, r)); - - r = sd_bus_get_property_trivial( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Timer", - "NextElapseUSecRealtime", - &error, - 't', - &t.realtime); - if (r < 0) - return log_error_errno(r, "Failed to get next elapsation time: %s", bus_error_message(&error, r)); - - *next = t; - return 0; -} - -static int get_last_trigger( - sd_bus *bus, - const char *path, - usec_t *last) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(bus); - assert(path); - assert(last); - - r = sd_bus_get_property_trivial( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Timer", - "LastTriggerUSec", - &error, - 't', - last); - if (r < 0) - return log_error_errno(r, "Failed to get last trigger time: %s", bus_error_message(&error, r)); - - return 0; -} - -struct timer_info { - const char* machine; - const char* id; - usec_t next_elapse; - usec_t last_trigger; - char** triggered; -}; - -static int timer_info_compare(const struct timer_info *a, const struct timer_info *b) { - int o; - - assert(a); - assert(b); - - if (!a->machine && b->machine) - return -1; - if (a->machine && !b->machine) - return 1; - if (a->machine && b->machine) { - o = strcasecmp(a->machine, b->machine); - if (o != 0) - return o; - } - - if (a->next_elapse < b->next_elapse) - return -1; - if (a->next_elapse > b->next_elapse) - return 1; - - return strcmp(a->id, b->id); -} - -static int output_timers_list(struct timer_info *timer_infos, unsigned n) { - struct timer_info *t; - unsigned - nextlen = strlen("NEXT"), - leftlen = strlen("LEFT"), - lastlen = strlen("LAST"), - passedlen = strlen("PASSED"), - unitlen = strlen("UNIT"), - activatelen = strlen("ACTIVATES"); - - const char *on, *off; - - assert(timer_infos || n == 0); - - for (t = timer_infos; t < timer_infos + n; t++) { - unsigned ul = 0; - char **a; - - if (t->next_elapse > 0) { - char tstamp[FORMAT_TIMESTAMP_MAX] = "", trel[FORMAT_TIMESTAMP_RELATIVE_MAX] = ""; - - format_timestamp(tstamp, sizeof(tstamp), t->next_elapse); - nextlen = MAX(nextlen, strlen(tstamp) + 1); - - format_timestamp_relative(trel, sizeof(trel), t->next_elapse); - leftlen = MAX(leftlen, strlen(trel)); - } - - if (t->last_trigger > 0) { - char tstamp[FORMAT_TIMESTAMP_MAX] = "", trel[FORMAT_TIMESTAMP_RELATIVE_MAX] = ""; - - format_timestamp(tstamp, sizeof(tstamp), t->last_trigger); - lastlen = MAX(lastlen, strlen(tstamp) + 1); - - format_timestamp_relative(trel, sizeof(trel), t->last_trigger); - passedlen = MAX(passedlen, strlen(trel)); - } - - unitlen = MAX(unitlen, strlen(t->id) + (t->machine ? strlen(t->machine)+1 : 0)); - - STRV_FOREACH(a, t->triggered) - ul += strlen(*a) + 2*(a != t->triggered); - - activatelen = MAX(activatelen, ul); - } - - if (n > 0) { - if (!arg_no_legend) - printf("%-*s %-*s %-*s %-*s %-*s %s\n", - nextlen, "NEXT", - leftlen, "LEFT", - lastlen, "LAST", - passedlen, "PASSED", - unitlen, "UNIT", - "ACTIVATES"); - - for (t = timer_infos; t < timer_infos + n; t++) { - _cleanup_free_ char *j = NULL; - const char *unit; - char tstamp1[FORMAT_TIMESTAMP_MAX] = "n/a", trel1[FORMAT_TIMESTAMP_RELATIVE_MAX] = "n/a"; - char tstamp2[FORMAT_TIMESTAMP_MAX] = "n/a", trel2[FORMAT_TIMESTAMP_RELATIVE_MAX] = "n/a"; - char **a; - - format_timestamp(tstamp1, sizeof(tstamp1), t->next_elapse); - format_timestamp_relative(trel1, sizeof(trel1), t->next_elapse); - - format_timestamp(tstamp2, sizeof(tstamp2), t->last_trigger); - format_timestamp_relative(trel2, sizeof(trel2), t->last_trigger); - - if (t->machine) { - j = strjoin(t->machine, ":", t->id, NULL); - if (!j) - return log_oom(); - unit = j; - } else - unit = t->id; - - printf("%-*s %-*s %-*s %-*s %-*s", - nextlen, tstamp1, leftlen, trel1, lastlen, tstamp2, passedlen, trel2, unitlen, unit); - - STRV_FOREACH(a, t->triggered) - printf("%s %s", - a == t->triggered ? "" : ",", *a); - printf("\n"); - } - - on = ansi_highlight(); - off = ansi_normal(); - if (!arg_no_legend) - printf("\n"); - } else { - on = ansi_highlight_red(); - off = ansi_normal(); - } - - if (!arg_no_legend) { - printf("%s%u timers listed.%s\n", on, n, off); - if (!arg_all) - printf("Pass --all to see loaded but inactive timers, too.\n"); - } - - return 0; -} - -static usec_t calc_next_elapse(dual_timestamp *nw, dual_timestamp *next) { - usec_t next_elapse; - - assert(nw); - assert(next); - - if (next->monotonic != USEC_INFINITY && next->monotonic > 0) { - usec_t converted; - - if (next->monotonic > nw->monotonic) - converted = nw->realtime + (next->monotonic - nw->monotonic); - else - converted = nw->realtime - (nw->monotonic - next->monotonic); - - if (next->realtime != USEC_INFINITY && next->realtime > 0) - next_elapse = MIN(converted, next->realtime); - else - next_elapse = converted; - - } else - next_elapse = next->realtime; - - return next_elapse; -} - -static int list_timers(int argc, char *argv[], void *userdata) { - _cleanup_(message_set_freep) Set *replies = NULL; - _cleanup_strv_free_ char **machines = NULL; - _cleanup_free_ struct timer_info *timer_infos = NULL; - _cleanup_free_ UnitInfo *unit_infos = NULL; - struct timer_info *t; - const UnitInfo *u; - size_t size = 0; - int n, c = 0; - dual_timestamp nw; - sd_bus *bus; - int r = 0; - - pager_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - n = get_unit_list_recursive(bus, strv_skip(argv, 1), &unit_infos, &replies, &machines); - if (n < 0) - return n; - - dual_timestamp_get(&nw); - - for (u = unit_infos; u < unit_infos + n; u++) { - _cleanup_strv_free_ char **triggered = NULL; - dual_timestamp next = DUAL_TIMESTAMP_NULL; - usec_t m, last = 0; - - if (!endswith(u->id, ".timer")) - continue; - - r = get_triggered_units(bus, u->unit_path, &triggered); - if (r < 0) - goto cleanup; - - r = get_next_elapse(bus, u->unit_path, &next); - if (r < 0) - goto cleanup; - - get_last_trigger(bus, u->unit_path, &last); - - if (!GREEDY_REALLOC(timer_infos, size, c+1)) { - r = log_oom(); - goto cleanup; - } - - m = calc_next_elapse(&nw, &next); - - timer_infos[c++] = (struct timer_info) { - .machine = u->machine, - .id = u->id, - .next_elapse = m, - .last_trigger = last, - .triggered = triggered, - }; - - triggered = NULL; /* avoid cleanup */ - } - - qsort_safe(timer_infos, c, sizeof(struct timer_info), - (__compar_fn_t) timer_info_compare); - - output_timers_list(timer_infos, c); - - cleanup: - for (t = timer_infos; t < timer_infos + c; t++) - strv_free(t->triggered); - - return r; -} - -static int compare_unit_file_list(const void *a, const void *b) { - const char *d1, *d2; - const UnitFileList *u = a, *v = b; - - d1 = strrchr(u->path, '.'); - d2 = strrchr(v->path, '.'); - - if (d1 && d2) { - int r; - - r = strcasecmp(d1, d2); - if (r != 0) - return r; - } - - return strcasecmp(basename(u->path), basename(v->path)); -} - -static bool output_show_unit_file(const UnitFileList *u, char **patterns) { - if (!strv_fnmatch_or_empty(patterns, basename(u->path), FNM_NOESCAPE)) - return false; - - if (!strv_isempty(arg_types)) { - const char *dot; - - dot = strrchr(u->path, '.'); - if (!dot) - return false; - - if (!strv_find(arg_types, dot+1)) - return false; - } - - if (!strv_isempty(arg_states) && - !strv_find(arg_states, unit_file_state_to_string(u->state))) - return false; - - return true; -} - -static void output_unit_file_list(const UnitFileList *units, unsigned c) { - unsigned max_id_len, id_cols, state_cols; - const UnitFileList *u; - - max_id_len = strlen("UNIT FILE"); - state_cols = strlen("STATE"); - - for (u = units; u < units + c; u++) { - max_id_len = MAX(max_id_len, strlen(basename(u->path))); - state_cols = MAX(state_cols, strlen(unit_file_state_to_string(u->state))); - } - - if (!arg_full) { - unsigned basic_cols; - - id_cols = MIN(max_id_len, 25u); - basic_cols = 1 + id_cols + state_cols; - if (basic_cols < (unsigned) columns()) - id_cols += MIN(columns() - basic_cols, max_id_len - id_cols); - } else - id_cols = max_id_len; - - if (!arg_no_legend) - printf("%-*s %-*s\n", - id_cols, "UNIT FILE", - state_cols, "STATE"); - - for (u = units; u < units + c; u++) { - _cleanup_free_ char *e = NULL; - const char *on, *off; - const char *id; - - if (IN_SET(u->state, - UNIT_FILE_MASKED, - UNIT_FILE_MASKED_RUNTIME, - UNIT_FILE_DISABLED, - UNIT_FILE_BAD)) { - on = ansi_highlight_red(); - off = ansi_normal(); - } else if (u->state == UNIT_FILE_ENABLED) { - on = ansi_highlight_green(); - off = ansi_normal(); - } else - on = off = ""; - - id = basename(u->path); - - e = arg_full ? NULL : ellipsize(id, id_cols, 33); - - printf("%-*s %s%-*s%s\n", - id_cols, e ? e : id, - on, state_cols, unit_file_state_to_string(u->state), off); - } - - if (!arg_no_legend) - printf("\n%u unit files listed.\n", c); -} - -static int list_unit_files(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ UnitFileList *units = NULL; - UnitFileList *unit; - size_t size = 0; - unsigned c = 0; - const char *state; - char *path; - int r; - - pager_open_if_enabled(); - - if (install_client_side()) { - Hashmap *h; - UnitFileList *u; - Iterator i; - unsigned n_units; - - h = hashmap_new(&string_hash_ops); - if (!h) - return log_oom(); - - r = unit_file_get_list(arg_scope, arg_root, h); - if (r < 0) { - unit_file_list_free(h); - return log_error_errno(r, "Failed to get unit file list: %m"); - } - - n_units = hashmap_size(h); - - units = new(UnitFileList, n_units); - if (!units && n_units > 0) { - unit_file_list_free(h); - return log_oom(); - } - - HASHMAP_FOREACH(u, h, i) { - if (!output_show_unit_file(u, strv_skip(argv, 1))) - continue; - - units[c++] = *u; - free(u); - } - - assert(c <= n_units); - hashmap_free(h); - } else { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListUnitFiles", - &error, - &reply, - NULL); - if (r < 0) - return log_error_errno(r, "Failed to list unit files: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(ss)", &path, &state)) > 0) { - - if (!GREEDY_REALLOC(units, size, c + 1)) - return log_oom(); - - units[c] = (struct UnitFileList) { - path, - unit_file_state_from_string(state) - }; - - if (output_show_unit_file(&units[c], strv_skip(argv, 1))) - c ++; - - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - } - - qsort_safe(units, c, sizeof(UnitFileList), compare_unit_file_list); - output_unit_file_list(units, c); - - if (install_client_side()) { - for (unit = units; unit < units + c; unit++) - free(unit->path); - } - - return 0; -} - -static int list_dependencies_print(const char *name, int level, unsigned int branches, bool last) { - _cleanup_free_ char *n = NULL; - size_t max_len = MAX(columns(),20u); - size_t len = 0; - int i; - - if (!arg_plain) { - - for (i = level - 1; i >= 0; i--) { - len += 2; - if (len > max_len - 3 && !arg_full) { - printf("%s...\n",max_len % 2 ? "" : " "); - return 0; - } - printf("%s", draw_special_char(branches & (1 << i) ? DRAW_TREE_VERTICAL : DRAW_TREE_SPACE)); - } - len += 2; - - if (len > max_len - 3 && !arg_full) { - printf("%s...\n",max_len % 2 ? "" : " "); - return 0; - } - - printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH)); - } - - if (arg_full){ - printf("%s\n", name); - return 0; - } - - n = ellipsize(name, max_len-len, 100); - if (!n) - return log_oom(); - - printf("%s\n", n); - return 0; -} - -static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) { - - static const char *dependencies[_DEPENDENCY_MAX] = { - [DEPENDENCY_FORWARD] = "Requires\0" - "Requisite\0" - "Wants\0" - "ConsistsOf\0" - "BindsTo\0", - [DEPENDENCY_REVERSE] = "RequiredBy\0" - "RequisiteOf\0" - "WantedBy\0" - "PartOf\0" - "BoundBy\0", - [DEPENDENCY_AFTER] = "After\0", - [DEPENDENCY_BEFORE] = "Before\0", - }; - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_strv_free_ char **ret = NULL; - _cleanup_free_ char *path = NULL; - int r; - - assert(bus); - assert(name); - assert(deps); - assert_cc(ELEMENTSOF(dependencies) == _DEPENDENCY_MAX); - - path = unit_dbus_path_from_name(name); - if (!path) - return log_oom(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.DBus.Properties", - "GetAll", - &error, - &reply, - "s", "org.freedesktop.systemd1.Unit"); - if (r < 0) - return log_error_errno(r, "Failed to get properties of %s: %s", name, bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { - const char *prop; - - r = sd_bus_message_read(reply, "s", &prop); - if (r < 0) - return bus_log_parse_error(r); - - if (!nulstr_contains(dependencies[arg_dependency], prop)) { - r = sd_bus_message_skip(reply, "v"); - if (r < 0) - return bus_log_parse_error(r); - } else { - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, "as"); - if (r < 0) - return bus_log_parse_error(r); - - r = bus_message_read_strv_extend(reply, &ret); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - } - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - *deps = ret; - ret = NULL; - - return 0; -} - -static int list_dependencies_compare(const void *_a, const void *_b) { - const char **a = (const char**) _a, **b = (const char**) _b; - - if (unit_name_to_type(*a) == UNIT_TARGET && unit_name_to_type(*b) != UNIT_TARGET) - return 1; - if (unit_name_to_type(*a) != UNIT_TARGET && unit_name_to_type(*b) == UNIT_TARGET) - return -1; - - return strcasecmp(*a, *b); -} - -static int list_dependencies_one( - sd_bus *bus, - const char *name, - int level, - char ***units, - unsigned int branches) { - - _cleanup_strv_free_ char **deps = NULL; - char **c; - int r = 0; - - assert(bus); - assert(name); - assert(units); - - r = strv_extend(units, name); - if (r < 0) - return log_oom(); - - r = list_dependencies_get_dependencies(bus, name, &deps); - if (r < 0) - return r; - - qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare); - - STRV_FOREACH(c, deps) { - if (strv_contains(*units, *c)) { - if (!arg_plain) { - r = list_dependencies_print("...", level + 1, (branches << 1) | (c[1] == NULL ? 0 : 1), 1); - if (r < 0) - return r; - } - continue; - } - - if (arg_plain) - printf(" "); - else { - int state; - const char *on; - - state = check_one_unit(bus, *c, "activating\0active\0reloading\0", true); - on = state > 0 ? ansi_highlight_green() : ansi_highlight_red(); - printf("%s%s%s ", on, draw_special_char(DRAW_BLACK_CIRCLE), ansi_normal()); - } - - r = list_dependencies_print(*c, level, branches, c[1] == NULL); - if (r < 0) - return r; - - if (arg_all || unit_name_to_type(*c) == UNIT_TARGET) { - r = list_dependencies_one(bus, *c, level + 1, units, (branches << 1) | (c[1] == NULL ? 0 : 1)); - if (r < 0) - return r; - } - } - - if (!arg_plain) - strv_remove(*units, name); - - return 0; -} - -static int list_dependencies(int argc, char *argv[], void *userdata) { - _cleanup_strv_free_ char **units = NULL; - _cleanup_free_ char *unit = NULL; - const char *u; - sd_bus *bus; - int r; - - if (argv[1]) { - r = unit_name_mangle(argv[1], UNIT_NAME_NOGLOB, &unit); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - u = unit; - } else - u = SPECIAL_DEFAULT_TARGET; - - pager_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - puts(u); - - return list_dependencies_one(bus, u, 0, &units, 0); -} - -struct machine_info { - bool is_host; - char *name; - char *state; - char *control_group; - uint32_t n_failed_units; - uint32_t n_jobs; - usec_t timestamp; -}; - -static const struct bus_properties_map machine_info_property_map[] = { - { "SystemState", "s", NULL, offsetof(struct machine_info, state) }, - { "NJobs", "u", NULL, offsetof(struct machine_info, n_jobs) }, - { "NFailedUnits", "u", NULL, offsetof(struct machine_info, n_failed_units) }, - { "ControlGroup", "s", NULL, offsetof(struct machine_info, control_group) }, - { "UserspaceTimestamp", "t", NULL, offsetof(struct machine_info, timestamp) }, - {} -}; - -static void machine_info_clear(struct machine_info *info) { - if (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) { - int i; - - if (!machine_infos) - return; - - for (i = 0; i < n; i++) - machine_info_clear(&machine_infos[i]); - - free(machine_infos); -} - -static int compare_machine_info(const void *a, const void *b) { - const struct machine_info *u = a, *v = b; - - if (u->is_host != v->is_host) - return u->is_host > v->is_host ? -1 : 1; - - return strcasecmp(u->name, v->name); -} - -static int get_machine_properties(sd_bus *bus, struct machine_info *mi) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *container = NULL; - int r; - - assert(mi); - - if (!bus) { - r = sd_bus_open_system_machine(&container, mi->name); - if (r < 0) - return r; - - bus = container; - } - - r = bus_map_all_properties(bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", machine_info_property_map, mi); - if (r < 0) - return r; - - return 0; -} - -static bool output_show_machine(const char *name, char **patterns) { - return strv_fnmatch_or_empty(patterns, name, FNM_NOESCAPE); -} - -static int get_machine_list( - sd_bus *bus, - struct machine_info **_machine_infos, - char **patterns) { - - struct machine_info *machine_infos = NULL; - _cleanup_strv_free_ char **m = NULL; - _cleanup_free_ char *hn = NULL; - size_t sz = 0; - char **i; - int c = 0, r; - - hn = gethostname_malloc(); - if (!hn) - return log_oom(); - - if (output_show_machine(hn, patterns)) { - if (!GREEDY_REALLOC0(machine_infos, sz, c+1)) - return log_oom(); - - machine_infos[c].is_host = true; - machine_infos[c].name = hn; - hn = NULL; - - get_machine_properties(bus, &machine_infos[c]); - c++; - } - - r = sd_get_machine_names(&m); - if (r < 0) - return log_error_errno(r, "Failed to get machine list: %m"); - - STRV_FOREACH(i, m) { - _cleanup_free_ char *class = NULL; - - if (!output_show_machine(*i, patterns)) - continue; - - sd_machine_get_class(*i, &class); - if (!streq_ptr(class, "container")) - continue; - - if (!GREEDY_REALLOC0(machine_infos, sz, c+1)) { - free_machines_list(machine_infos, c); - return log_oom(); - } - - machine_infos[c].is_host = false; - machine_infos[c].name = strdup(*i); - if (!machine_infos[c].name) { - free_machines_list(machine_infos, c); - return log_oom(); - } - - get_machine_properties(NULL, &machine_infos[c]); - c++; - } - - *_machine_infos = machine_infos; - return c; -} - -static void output_machines_list(struct machine_info *machine_infos, unsigned n) { - struct machine_info *m; - unsigned - circle_len = 0, - namelen = sizeof("NAME") - 1, - statelen = sizeof("STATE") - 1, - failedlen = sizeof("FAILED") - 1, - jobslen = sizeof("JOBS") - 1; - - assert(machine_infos || n == 0); - - for (m = machine_infos; m < machine_infos + n; m++) { - namelen = MAX(namelen, strlen(m->name) + (m->is_host ? sizeof(" (host)") - 1 : 0)); - statelen = MAX(statelen, m->state ? strlen(m->state) : 0); - failedlen = MAX(failedlen, DECIMAL_STR_WIDTH(m->n_failed_units)); - jobslen = MAX(jobslen, DECIMAL_STR_WIDTH(m->n_jobs)); - - if (!arg_plain && !streq_ptr(m->state, "running")) - circle_len = 2; - } - - if (!arg_no_legend) { - if (circle_len > 0) - fputs(" ", stdout); - - printf("%-*s %-*s %-*s %-*s\n", - namelen, "NAME", - statelen, "STATE", - failedlen, "FAILED", - jobslen, "JOBS"); - } - - for (m = machine_infos; m < machine_infos + n; m++) { - const char *on_state = "", *off_state = ""; - const char *on_failed = "", *off_failed = ""; - bool circle = false; - - if (streq_ptr(m->state, "degraded")) { - on_state = ansi_highlight_red(); - off_state = ansi_normal(); - circle = true; - } else if (!streq_ptr(m->state, "running")) { - on_state = ansi_highlight_yellow(); - off_state = ansi_normal(); - circle = true; - } - - if (m->n_failed_units > 0) { - on_failed = ansi_highlight_red(); - off_failed = ansi_normal(); - } else - on_failed = off_failed = ""; - - if (circle_len > 0) - printf("%s%s%s ", on_state, circle ? draw_special_char(DRAW_BLACK_CIRCLE) : " ", off_state); - - if (m->is_host) - printf("%-*s (host) %s%-*s%s %s%*u%s %*u\n", - (int) (namelen - (sizeof(" (host)")-1)), strna(m->name), - on_state, statelen, strna(m->state), off_state, - on_failed, failedlen, m->n_failed_units, off_failed, - jobslen, m->n_jobs); - else - printf("%-*s %s%-*s%s %s%*u%s %*u\n", - namelen, strna(m->name), - on_state, statelen, strna(m->state), off_state, - on_failed, failedlen, m->n_failed_units, off_failed, - jobslen, m->n_jobs); - } - - if (!arg_no_legend) - printf("\n%u machines listed.\n", n); -} - -static int list_machines(int argc, char *argv[], void *userdata) { - struct machine_info *machine_infos = NULL; - sd_bus *bus; - int r; - - if (geteuid() != 0) { - log_error("Must be root."); - return -EPERM; - } - - pager_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = get_machine_list(bus, &machine_infos, strv_skip(argv, 1)); - if (r < 0) - return r; - - qsort_safe(machine_infos, r, sizeof(struct machine_info), compare_machine_info); - output_machines_list(machine_infos, r); - free_machines_list(machine_infos, r); - - return 0; -} - -static int get_default(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *_path = NULL; - const char *path; - int r; - - if (install_client_side()) { - r = unit_file_get_default(arg_scope, arg_root, &_path); - if (r < 0) - return log_error_errno(r, "Failed to get default target: %m"); - path = _path; - - } else { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetDefaultTarget", - &error, - &reply, - NULL); - if (r < 0) - return log_error_errno(r, "Failed to get default target: %s", bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "s", &path); - if (r < 0) - return bus_log_parse_error(r); - } - - if (path) - printf("%s\n", path); - - return 0; -} - -static void dump_unit_file_changes(const UnitFileChange *changes, unsigned n_changes) { - unsigned i; - - assert(changes || n_changes == 0); - - for (i = 0; i < n_changes; i++) { - if (changes[i].type == UNIT_FILE_SYMLINK) - log_info("Created symlink %s, pointing to %s.", changes[i].path, changes[i].source); - else - log_info("Removed symlink %s.", changes[i].path); - } -} - -static int set_default(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *unit = NULL; - int r; - - assert(argc >= 2); - assert(argv); - - r = unit_name_mangle_with_suffix(argv[1], UNIT_NAME_NOGLOB, ".target", &unit); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - if (install_client_side()) { - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - - r = unit_file_set_default(arg_scope, arg_root, unit, true, &changes, &n_changes); - if (r < 0) - return log_error_errno(r, "Failed to set default target: %m"); - - if (!arg_quiet) - dump_unit_file_changes(changes, n_changes); - - unit_file_changes_free(changes, n_changes); - r = 0; - } else { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _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; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SetDefaultTarget", - &error, - &reply, - "sb", unit, 1); - if (r < 0) - return log_error_errno(r, "Failed to set default target: %s", bus_error_message(&error, r)); - - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); - if (r < 0) - return r; - - /* Try to reload if enabled */ - if (!arg_no_reload) - r = daemon_reload(argc, argv, userdata); - else - r = 0; - } - - return r; -} - -struct job_info { - uint32_t id; - const char *name, *type, *state; -}; - -static void output_jobs_list(const struct job_info* jobs, unsigned n, bool skipped) { - unsigned id_len, unit_len, type_len, state_len; - const struct job_info *j; - const char *on, *off; - bool shorten = false; - - assert(n == 0 || jobs); - - if (n == 0) { - if (!arg_no_legend) { - on = ansi_highlight_green(); - off = ansi_normal(); - - printf("%sNo jobs %s.%s\n", on, skipped ? "listed" : "running", off); - } - return; - } - - pager_open_if_enabled(); - - id_len = strlen("JOB"); - unit_len = strlen("UNIT"); - type_len = strlen("TYPE"); - state_len = strlen("STATE"); - - for (j = jobs; j < jobs + n; j++) { - uint32_t id = j->id; - assert(j->name && j->type && j->state); - - id_len = MAX(id_len, DECIMAL_STR_WIDTH(id)); - unit_len = MAX(unit_len, strlen(j->name)); - type_len = MAX(type_len, strlen(j->type)); - state_len = MAX(state_len, strlen(j->state)); - } - - if (!arg_full && id_len + 1 + unit_len + type_len + 1 + state_len > columns()) { - unit_len = MAX(33u, columns() - id_len - type_len - state_len - 3); - shorten = true; - } - - if (!arg_no_legend) - printf("%*s %-*s %-*s %-*s\n", - id_len, "JOB", - unit_len, "UNIT", - type_len, "TYPE", - state_len, "STATE"); - - for (j = jobs; j < jobs + n; j++) { - _cleanup_free_ char *e = NULL; - - if (streq(j->state, "running")) { - on = ansi_highlight(); - off = ansi_normal(); - } else - on = off = ""; - - e = shorten ? ellipsize(j->name, unit_len, 33) : NULL; - printf("%*u %s%-*s%s %-*s %s%-*s%s\n", - id_len, j->id, - on, unit_len, e ? e : j->name, off, - type_len, j->type, - on, state_len, j->state, off); - } - - if (!arg_no_legend) { - on = ansi_highlight(); - off = ansi_normal(); - - printf("\n%s%u jobs listed%s.\n", on, n, off); - } -} - -static bool output_show_job(struct job_info *job, char **patterns) { - return strv_fnmatch_or_empty(patterns, job->name, FNM_NOESCAPE); -} - -static int list_jobs(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 *reply = NULL; - const char *name, *type, *state, *job_path, *unit_path; - _cleanup_free_ struct job_info *jobs = NULL; - size_t size = 0; - unsigned c = 0; - sd_bus *bus; - uint32_t id; - int r; - bool skipped = false; - - pager_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ListJobs", - &error, - &reply, - NULL); - if (r < 0) - return log_error_errno(r, "Failed to list jobs: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, 'a', "(usssoo)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(usssoo)", &id, &name, &type, &state, &job_path, &unit_path)) > 0) { - struct job_info job = { id, name, type, state }; - - if (!output_show_job(&job, strv_skip(argv, 1))) { - skipped = true; - continue; - } - - if (!GREEDY_REALLOC(jobs, size, c + 1)) - return log_oom(); - - jobs[c++] = job; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - output_jobs_list(jobs, c, skipped); - return 0; -} - -static int cancel_job(int argc, char *argv[], void *userdata) { - sd_bus *bus; - char **name; - int r = 0; - - if (argc <= 1) - return daemon_reload(argc, argv, userdata); - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - STRV_FOREACH(name, strv_skip(argv, 1)) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - uint32_t id; - int q; - - q = safe_atou32(*name, &id); - if (q < 0) - return log_error_errno(q, "Failed to parse job id \"%s\": %m", *name); - - q = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "CancelJob", - &error, - NULL, - "u", id); - if (q < 0) { - log_error_errno(q, "Failed to cancel job %"PRIu32": %s", id, bus_error_message(&error, q)); - if (r == 0) - r = q; - } - } - - return r; -} - -static int need_daemon_reload(sd_bus *bus, const char *unit) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *path; - int b, r; - - /* We ignore all errors here, since this is used to show a - * warning only */ - - /* We don't use unit_dbus_path_from_name() directly since we - * don't want to load the unit if it isn't loaded. */ - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnit", - NULL, - &reply, - "s", unit); - if (r < 0) - return r; - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return r; - - r = sd_bus_get_property_trivial( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "NeedDaemonReload", - NULL, - 'b', &b); - if (r < 0) - return r; - - return b; -} - -static void warn_unit_file_changed(const char *name) { - log_warning("%sWarning:%s %s changed on disk. Run 'systemctl%s daemon-reload' to reload units.", - ansi_highlight_red(), - ansi_normal(), - name, - arg_scope == UNIT_FILE_SYSTEM ? "" : " --user"); -} - -static int unit_file_find_path(LookupPaths *lp, const char *unit_name, char **unit_path) { - char **p; - - assert(lp); - assert(unit_name); - assert(unit_path); - - STRV_FOREACH(p, lp->unit_path) { - _cleanup_free_ char *path; - - path = path_join(arg_root, *p, unit_name); - if (!path) - return log_oom(); - - if (access(path, F_OK) == 0) { - *unit_path = path; - path = NULL; - return 1; - } - } - - return 0; -} - -static int unit_find_paths( - sd_bus *bus, - const char *unit_name, - LookupPaths *lp, - char **fragment_path, - char ***dropin_paths) { - - _cleanup_free_ char *path = NULL; - _cleanup_strv_free_ char **dropins = NULL; - int r; - - /** - * Finds where the unit is defined on disk. Returns 0 if the unit - * is not found. Returns 1 if it is found, and sets - * - the path to the unit in *path, if it exists on disk, - * - and a strv of existing drop-ins in *dropins, - * if the arg is not NULL and any dropins were found. - */ - - assert(unit_name); - assert(fragment_path); - assert(lp); - - if (!install_client_side() && !unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *unit = NULL; - - unit = unit_dbus_path_from_name(unit_name); - if (!unit) - return log_oom(); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - unit, - "org.freedesktop.systemd1.Unit", - "FragmentPath", - &error, - &path); - if (r < 0) - return log_error_errno(r, "Failed to get FragmentPath: %s", bus_error_message(&error, r)); - - if (dropin_paths) { - r = sd_bus_get_property_strv( - bus, - "org.freedesktop.systemd1", - unit, - "org.freedesktop.systemd1.Unit", - "DropInPaths", - &error, - &dropins); - if (r < 0) - return log_error_errno(r, "Failed to get DropInPaths: %s", bus_error_message(&error, r)); - } - } else { - _cleanup_set_free_ Set *names; - - names = set_new(NULL); - if (!names) - return log_oom(); - - r = set_put(names, unit_name); - if (r < 0) - return log_error_errno(r, "Failed to add unit name: %m"); - - r = unit_file_find_path(lp, unit_name, &path); - if (r < 0) - return r; - - if (r == 0) { - _cleanup_free_ char *template = NULL; - - r = unit_name_template(unit_name, &template); - if (r < 0 && r != -EINVAL) - return log_error_errno(r, "Failed to determine template name: %m"); - if (r >= 0) { - r = unit_file_find_path(lp, template, &path); - if (r < 0) - return r; - } - } - - if (dropin_paths) { - r = unit_file_find_dropin_paths(lp->unit_path, NULL, names, &dropins); - if (r < 0) - return r; - } - } - - r = 0; - - if (!isempty(path)) { - *fragment_path = path; - path = NULL; - r = 1; - } - - if (dropin_paths && !strv_isempty(dropins)) { - *dropin_paths = dropins; - dropins = NULL; - r = 1; - } - - if (r == 0) - log_error("No files found for %s.", unit_name); - - return r; -} - -static int check_one_unit(sd_bus *bus, const char *name, const char *good_states, bool quiet) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ char *buf = NULL; - const char *path, *state; - int r; - - assert(name); - - /* We don't use unit_dbus_path_from_name() directly since we don't want to load the unit unnecessarily, if it - * isn't loaded. */ - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnit", - &error, - &reply, - "s", name); - if (r < 0) { - if (!sd_bus_error_has_name(&error, BUS_ERROR_NO_SUCH_UNIT)) - return log_error_errno(r, "Failed to retrieve unit: %s", bus_error_message(&error, r)); - - /* The unit is currently not loaded, hence say it's "inactive", since all units that aren't loaded are - * considered inactive. */ - state = "inactive"; - - } else { - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "ActiveState", - &error, - &buf); - if (r < 0) - return log_error_errno(r, "Failed to retrieve unit state: %s", bus_error_message(&error, r)); - - state = buf; - } - - if (!quiet) - puts(state); - - return nulstr_contains(good_states, state); -} - -static int check_triggering_units( - sd_bus *bus, - const char *name) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *path = NULL, *n = NULL, *state = NULL; - _cleanup_strv_free_ char **triggered_by = NULL; - bool print_warning_label = true; - char **i; - int r; - - r = unit_name_mangle(name, UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - path = unit_dbus_path_from_name(n); - if (!path) - return log_oom(); - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "LoadState", - &error, - &state); - if (r < 0) - return log_error_errno(r, "Failed to get load state of %s: %s", n, bus_error_message(&error, r)); - - if (streq(state, "masked")) - return 0; - - r = sd_bus_get_property_strv( - bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "TriggeredBy", - &error, - &triggered_by); - if (r < 0) - return log_error_errno(r, "Failed to get triggered by array of %s: %s", n, bus_error_message(&error, r)); - - STRV_FOREACH(i, triggered_by) { - r = check_one_unit(bus, *i, "active\0reloading\0", true); - if (r < 0) - return log_error_errno(r, "Failed to check unit: %m"); - - if (r == 0) - continue; - - if (print_warning_label) { - log_warning("Warning: Stopping %s, but it can still be activated by:", n); - print_warning_label = false; - } - - log_warning(" %s", *i); - } - - return 0; -} - -static const struct { - const char *verb; - const char *method; -} unit_actions[] = { - { "start", "StartUnit" }, - { "stop", "StopUnit" }, - { "condstop", "StopUnit" }, - { "reload", "ReloadUnit" }, - { "restart", "RestartUnit" }, - { "try-restart", "TryRestartUnit" }, - { "condrestart", "TryRestartUnit" }, - { "reload-or-restart", "ReloadOrRestartUnit" }, - { "try-reload-or-restart", "ReloadOrTryRestartUnit" }, - { "reload-or-try-restart", "ReloadOrTryRestartUnit" }, - { "condreload", "ReloadOrTryRestartUnit" }, - { "force-reload", "ReloadOrTryRestartUnit" } -}; - -static const char *verb_to_method(const char *verb) { - uint i; - - for (i = 0; i < ELEMENTSOF(unit_actions); i++) - if (streq_ptr(unit_actions[i].verb, verb)) - return unit_actions[i].method; - - return "StartUnit"; -} - -static const char *method_to_verb(const char *method) { - uint i; - - for (i = 0; i < ELEMENTSOF(unit_actions); i++) - if (streq_ptr(unit_actions[i].method, method)) - return unit_actions[i].verb; - - return "n/a"; -} - -static int start_unit_one( - sd_bus *bus, - const char *method, - const char *name, - const char *mode, - sd_bus_error *error, - BusWaitForJobs *w) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *path; - int r; - - assert(method); - assert(name); - assert(mode); - assert(error); - - log_debug("Calling manager for %s on %s, %s", method, name, mode); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - method, - error, - &reply, - "ss", name, mode); - if (r < 0) { - const char *verb; - - if (r == -ENOENT && arg_action != ACTION_SYSTEMCTL) - /* There's always a fallback possible for - * legacy actions. */ - return -EADDRNOTAVAIL; - - verb = method_to_verb(method); - - log_error("Failed to %s %s: %s", verb, name, bus_error_message(error, r)); - - if (!sd_bus_error_has_name(error, BUS_ERROR_NO_SUCH_UNIT) && - !sd_bus_error_has_name(error, BUS_ERROR_UNIT_MASKED)) - log_error("See system logs and 'systemctl status %s' for details.", name); - - return r; - } - - r = sd_bus_message_read(reply, "o", &path); - if (r < 0) - return bus_log_parse_error(r); - - if (need_daemon_reload(bus, name) > 0) - warn_unit_file_changed(name); - - if (w) { - log_debug("Adding %s to the set", path); - r = bus_wait_for_jobs_add(w, path); - if (r < 0) - return log_oom(); - } - - return 0; -} - -static int expand_names(sd_bus *bus, char **names, const char* suffix, char ***ret) { - _cleanup_strv_free_ char **mangled = NULL, **globs = NULL; - char **name; - int r, i; - - assert(bus); - assert(ret); - - STRV_FOREACH(name, names) { - char *t; - - if (suffix) - r = unit_name_mangle_with_suffix(*name, UNIT_NAME_GLOB, suffix, &t); - else - r = unit_name_mangle(*name, UNIT_NAME_GLOB, &t); - if (r < 0) - return log_error_errno(r, "Failed to mangle name: %m"); - - if (string_is_glob(t)) - r = strv_consume(&globs, t); - else - r = strv_consume(&mangled, t); - if (r < 0) - return log_oom(); - } - - /* Query the manager only if any of the names are a glob, since - * this is fairly expensive */ - if (!strv_isempty(globs)) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ UnitInfo *unit_infos = NULL; - size_t allocated, n; - - r = get_unit_list(bus, NULL, globs, &unit_infos, 0, &reply); - if (r < 0) - return r; - - n = strv_length(mangled); - allocated = n + 1; - - for (i = 0; i < r; i++) { - if (!GREEDY_REALLOC(mangled, allocated, n+2)) - return log_oom(); - - mangled[n] = strdup(unit_infos[i].id); - if (!mangled[n]) - return log_oom(); - - mangled[++n] = NULL; - } - } - - *ret = mangled; - mangled = NULL; /* do not free */ - - return 0; -} - -static const struct { - const char *target; - const char *verb; - const char *mode; -} action_table[_ACTION_MAX] = { - [ACTION_HALT] = { SPECIAL_HALT_TARGET, "halt", "replace-irreversibly" }, - [ACTION_POWEROFF] = { SPECIAL_POWEROFF_TARGET, "poweroff", "replace-irreversibly" }, - [ACTION_REBOOT] = { SPECIAL_REBOOT_TARGET, "reboot", "replace-irreversibly" }, - [ACTION_KEXEC] = { SPECIAL_KEXEC_TARGET, "kexec", "replace-irreversibly" }, - [ACTION_RUNLEVEL2] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, - [ACTION_RUNLEVEL3] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, - [ACTION_RUNLEVEL4] = { SPECIAL_MULTI_USER_TARGET, NULL, "isolate" }, - [ACTION_RUNLEVEL5] = { SPECIAL_GRAPHICAL_TARGET, NULL, "isolate" }, - [ACTION_RESCUE] = { SPECIAL_RESCUE_TARGET, "rescue", "isolate" }, - [ACTION_EMERGENCY] = { SPECIAL_EMERGENCY_TARGET, "emergency", "isolate" }, - [ACTION_DEFAULT] = { SPECIAL_DEFAULT_TARGET, "default", "isolate" }, - [ACTION_EXIT] = { SPECIAL_EXIT_TARGET, "exit", "replace-irreversibly" }, - [ACTION_SUSPEND] = { SPECIAL_SUSPEND_TARGET, "suspend", "replace-irreversibly" }, - [ACTION_HIBERNATE] = { SPECIAL_HIBERNATE_TARGET, "hibernate", "replace-irreversibly" }, - [ACTION_HYBRID_SLEEP] = { SPECIAL_HYBRID_SLEEP_TARGET, "hybrid-sleep", "replace-irreversibly" }, -}; - -static enum action verb_to_action(const char *verb) { - enum action i; - - for (i = _ACTION_INVALID; i < _ACTION_MAX; i++) - if (streq_ptr(action_table[i].verb, verb)) - return i; - - return _ACTION_INVALID; -} - -static int start_unit(int argc, char *argv[], void *userdata) { - _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; - const char *method, *mode, *one_name, *suffix = NULL; - _cleanup_strv_free_ char **names = NULL; - sd_bus *bus; - 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; - - if (arg_action == ACTION_SYSTEMCTL) { - enum action action; - - method = verb_to_method(argv[0]); - action = verb_to_action(argv[0]); - - if (streq(argv[0], "isolate")) { - mode = "isolate"; - suffix = ".target"; - } else - mode = action_table[action].mode ?: arg_job_mode; - - one_name = action_table[action].target; - } else { - assert(arg_action < ELEMENTSOF(action_table)); - assert(action_table[arg_action].target); - - method = "StartUnit"; - - mode = action_table[arg_action].mode; - one_name = action_table[arg_action].target; - } - - if (one_name) - names = strv_new(one_name, NULL); - else { - r = expand_names(bus, strv_skip(argv, 1), suffix, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - } - - if (!arg_no_block) { - r = bus_wait_for_jobs_new(bus, &w); - if (r < 0) - return log_error_errno(r, "Could not watch jobs: %m"); - } - - STRV_FOREACH(name, names) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int q; - - q = start_unit_one(bus, method, *name, mode, &error, w); - if (r >= 0 && q < 0) - r = translate_bus_error_to_exit_status(q, &error); - } - - if (!arg_no_block) { - int q; - - q = bus_wait_for_jobs(w, arg_quiet, arg_scope != UNIT_FILE_SYSTEM ? "--user" : NULL); - if (q < 0) - return q; - - /* When stopping units, warn if they can still be triggered by - * another active unit (socket, path, timer) */ - if (!arg_quiet && streq(method, "StopUnit")) - STRV_FOREACH(name, names) - check_triggering_units(bus, *name); - } - - return r; -} - -static int logind_set_wall_message(void) { -#ifdef HAVE_LOGIND - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - _cleanup_free_ char *m = NULL; - int r; - - r = acquire_bus(BUS_FULL, &bus); - if (r < 0) - return r; - - m = strv_join(arg_wall, " "); - if (!m) - return log_oom(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "SetWallMessage", - &error, - NULL, - "sb", - m, - !arg_no_wall); - - if (r < 0) - return log_warning_errno(r, "Failed to set wall message, ignoring: %s", bus_error_message(&error, r)); - -#endif - return 0; -} - -/* Ask systemd-logind, which might grant access to unprivileged users - * through PolicyKit */ -static int logind_reboot(enum action a) { -#ifdef HAVE_LOGIND - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - const char *method, *description; - 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; - - switch (a) { - - case ACTION_REBOOT: - method = "Reboot"; - description = "reboot system"; - break; - - case ACTION_POWEROFF: - method = "PowerOff"; - description = "power off system"; - break; - - case ACTION_SUSPEND: - method = "Suspend"; - description = "suspend system"; - break; - - case ACTION_HIBERNATE: - method = "Hibernate"; - description = "hibernate system"; - break; - - case ACTION_HYBRID_SLEEP: - method = "HybridSleep"; - description = "put system into hybrid sleep"; - break; - - default: - return -EINVAL; - } - - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - method, - &error, - NULL, - "b", arg_ask_password); - if (r < 0) - return log_error_errno(r, "Failed to %s via logind: %s", description, bus_error_message(&error, r)); - - return 0; -#else - return -ENOSYS; -#endif -} - -static int logind_check_inhibitors(enum action a) { -#ifdef HAVE_LOGIND - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_strv_free_ char **sessions = NULL; - const char *what, *who, *why, *mode; - uint32_t uid, pid; - sd_bus *bus; - unsigned c = 0; - char **s; - int r; - - if (arg_ignore_inhibitors || arg_force > 0) - return 0; - - if (arg_when > 0) - return 0; - - if (geteuid() == 0) - return 0; - - if (!on_tty()) - return 0; - - r = acquire_bus(BUS_FULL, &bus); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "ListInhibitors", - NULL, - &reply, - NULL); - if (r < 0) - /* If logind is not around, then there are no inhibitors... */ - return 0; - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssuu)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(reply, "(ssssuu)", &what, &who, &why, &mode, &uid, &pid)) > 0) { - _cleanup_free_ char *comm = NULL, *user = NULL; - _cleanup_strv_free_ char **sv = NULL; - - if (!streq(mode, "block")) - continue; - - sv = strv_split(what, ":"); - if (!sv) - return log_oom(); - - if ((pid_t) pid < 0) - return log_error_errno(ERANGE, "Bad PID %"PRIu32": %m", pid); - - if (!strv_contains(sv, - IN_SET(a, - ACTION_HALT, - ACTION_POWEROFF, - ACTION_REBOOT, - ACTION_KEXEC) ? "shutdown" : "sleep")) - continue; - - get_process_comm(pid, &comm); - user = uid_to_name(uid); - - log_warning("Operation inhibited by \"%s\" (PID "PID_FMT" \"%s\", user %s), reason is \"%s\".", - who, (pid_t) pid, strna(comm), strna(user), why); - - c++; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - /* Check for current sessions */ - sd_get_sessions(&sessions); - STRV_FOREACH(s, sessions) { - _cleanup_free_ char *type = NULL, *tty = NULL, *seat = NULL, *user = NULL, *service = NULL, *class = NULL; - - if (sd_session_get_uid(*s, &uid) < 0 || uid == getuid()) - continue; - - if (sd_session_get_class(*s, &class) < 0 || !streq(class, "user")) - continue; - - if (sd_session_get_type(*s, &type) < 0 || (!streq(type, "x11") && !streq(type, "tty"))) - continue; - - sd_session_get_tty(*s, &tty); - sd_session_get_seat(*s, &seat); - sd_session_get_service(*s, &service); - user = uid_to_name(uid); - - log_warning("User %s is logged in on %s.", strna(user), isempty(tty) ? (isempty(seat) ? strna(service) : seat) : tty); - c++; - } - - if (c <= 0) - return 0; - - log_error("Please retry operation after closing inhibitors and logging out other users.\nAlternatively, ignore inhibitors and users with 'systemctl %s -i'.", - action_table[a].verb); - - return -EPERM; -#else - return 0; -#endif -} - -static int logind_prepare_firmware_setup(void) { -#ifdef HAVE_LOGIND - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - int r; - - r = acquire_bus(BUS_FULL, &bus); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "SetRebootToFirmwareSetup", - &error, - NULL, - "b", true); - if (r < 0) - return log_error_errno(r, "Cannot indicate to EFI to boot into setup mode: %s", bus_error_message(&error, r)); - - return 0; -#else - log_error("Cannot remotely indicate to EFI to boot into setup mode."); - return -ENOSYS; -#endif -} - -static int prepare_firmware_setup(void) { - int r; - - if (!arg_firmware_setup) - return 0; - - if (arg_transport == BUS_TRANSPORT_LOCAL) { - - r = efi_set_reboot_to_firmware(true); - if (r < 0) - log_debug_errno(r, "Cannot indicate to EFI to boot into setup mode, will retry via logind: %m"); - else - return r; - } - - return logind_prepare_firmware_setup(); -} - -static int set_exit_code(uint8_t code) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - int r; - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SetExitCode", - &error, - NULL, - "y", code); - if (r < 0) - return log_error_errno(r, "Failed to execute operation: %s", bus_error_message(&error, r)); - - return 0; -} - -static int start_special(int argc, char *argv[], void *userdata) { - enum action a; - int r; - - assert(argv); - - a = verb_to_action(argv[0]); - - r = logind_check_inhibitors(a); - if (r < 0) - return r; - - if (arg_force >= 2 && geteuid() != 0) { - log_error("Must be root."); - return -EPERM; - } - - r = prepare_firmware_setup(); - if (r < 0) - return r; - - if (a == ACTION_REBOOT && argc > 1) { - r = update_reboot_param_file(argv[1]); - if (r < 0) - return r; - - } else if (a == ACTION_EXIT && argc > 1) { - uint8_t code; - - /* If the exit code is not given on the command line, - * don't reset it to zero: just keep it as it might - * have been set previously. */ - - r = safe_atou8(argv[1], &code); - if (r < 0) - return log_error_errno(r, "Invalid exit code."); - - r = set_exit_code(code); - if (r < 0) - return r; - } - - if (arg_force >= 2 && - IN_SET(a, - ACTION_HALT, - ACTION_POWEROFF, - ACTION_REBOOT)) - return halt_now(a); - - if (arg_force >= 1 && - IN_SET(a, - ACTION_HALT, - ACTION_POWEROFF, - ACTION_REBOOT, - ACTION_KEXEC, - ACTION_EXIT)) - return daemon_reload(argc, argv, userdata); - - /* First try logind, to allow authentication with polkit */ - if (IN_SET(a, - ACTION_POWEROFF, - ACTION_REBOOT, - ACTION_SUSPEND, - ACTION_HIBERNATE, - ACTION_HYBRID_SLEEP)) { - r = logind_reboot(a); - if (r >= 0) - return r; - if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) - /* requested operation is not supported or already in progress */ - return r; - - /* On all other errors, try low-level operation */ - } - - return start_unit(argc, argv, userdata); -} - -static int check_unit_generic(int code, const char *good_states, char **args) { - _cleanup_strv_free_ char **names = NULL; - sd_bus *bus; - char **name; - int r; - bool found = false; - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = expand_names(bus, args, NULL, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - - STRV_FOREACH(name, names) { - int state; - - state = check_one_unit(bus, *name, good_states, arg_quiet); - if (state < 0) - return state; - if (state > 0) - found = true; - } - - /* use the given return code for the case that we won't find - * any unit which matches the list */ - return found ? 0 : code; -} - -static int check_unit_active(int argc, char *argv[], void *userdata) { - /* According to LSB: 3, "program is not running" */ - return check_unit_generic(3, "active\0reloading\0", strv_skip(argv, 1)); -} - -static int check_unit_failed(int argc, char *argv[], void *userdata) { - return check_unit_generic(1, "failed\0", strv_skip(argv, 1)); -} - -static int kill_unit(int argc, char *argv[], void *userdata) { - _cleanup_strv_free_ char **names = NULL; - char *kill_who = NULL, **name; - sd_bus *bus; - int r, q; - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - if (!arg_kill_who) - arg_kill_who = "all"; - - /* --fail was specified */ - if (streq(arg_job_mode, "fail")) - kill_who = strjoina(arg_kill_who, "-fail", NULL); - - r = expand_names(bus, strv_skip(argv, 1), NULL, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - - STRV_FOREACH(name, names) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - q = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "KillUnit", - &error, - NULL, - "ssi", *names, kill_who ? kill_who : arg_kill_who, arg_signal); - if (q < 0) { - log_error_errno(q, "Failed to kill unit %s: %s", *names, bus_error_message(&error, q)); - if (r == 0) - r = q; - } - } - - return r; -} - -typedef struct ExecStatusInfo { - char *name; - - char *path; - char **argv; - - bool ignore; - - usec_t start_timestamp; - usec_t exit_timestamp; - pid_t pid; - int code; - int status; - - LIST_FIELDS(struct ExecStatusInfo, exec); -} ExecStatusInfo; - -static void exec_status_info_free(ExecStatusInfo *i) { - assert(i); - - free(i->name); - free(i->path); - strv_free(i->argv); - free(i); -} - -static int exec_status_info_deserialize(sd_bus_message *m, ExecStatusInfo *i) { - uint64_t start_timestamp, exit_timestamp, start_timestamp_monotonic, exit_timestamp_monotonic; - const char *path; - uint32_t pid; - int32_t code, status; - int ignore, r; - - assert(m); - assert(i); - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_STRUCT, "sasbttttuii"); - if (r < 0) - return bus_log_parse_error(r); - else if (r == 0) - return 0; - - r = sd_bus_message_read(m, "s", &path); - if (r < 0) - return bus_log_parse_error(r); - - i->path = strdup(path); - if (!i->path) - return log_oom(); - - r = sd_bus_message_read_strv(m, &i->argv); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(m, - "bttttuii", - &ignore, - &start_timestamp, &start_timestamp_monotonic, - &exit_timestamp, &exit_timestamp_monotonic, - &pid, - &code, &status); - if (r < 0) - return bus_log_parse_error(r); - - i->ignore = ignore; - i->start_timestamp = (usec_t) start_timestamp; - i->exit_timestamp = (usec_t) exit_timestamp; - i->pid = (pid_t) pid; - i->code = code; - i->status = status; - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 1; -} - -typedef struct UnitStatusInfo { - const char *id; - const char *load_state; - const char *active_state; - const char *sub_state; - const char *unit_file_state; - const char *unit_file_preset; - - const char *description; - const char *following; - - char **documentation; - - const char *fragment_path; - const char *source_path; - const char *control_group; - - char **dropin_paths; - - const char *load_error; - const char *result; - - usec_t inactive_exit_timestamp; - usec_t inactive_exit_timestamp_monotonic; - usec_t active_enter_timestamp; - usec_t active_exit_timestamp; - usec_t inactive_enter_timestamp; - - bool need_daemon_reload; - bool transient; - - /* Service */ - pid_t main_pid; - pid_t control_pid; - const char *status_text; - const char *pid_file; - bool running:1; - int status_errno; - - usec_t start_timestamp; - usec_t exit_timestamp; - - int exit_code, exit_status; - - usec_t condition_timestamp; - bool condition_result; - bool failed_condition_trigger; - bool failed_condition_negate; - const char *failed_condition; - const char *failed_condition_parameter; - - usec_t assert_timestamp; - bool assert_result; - bool failed_assert_trigger; - bool failed_assert_negate; - const char *failed_assert; - const char *failed_assert_parameter; - - /* Socket */ - unsigned n_accepted; - unsigned n_connections; - bool accept; - - /* Pairs of type, path */ - char **listen; - - /* Device */ - const char *sysfs_path; - - /* Mount, Automount */ - const char *where; - - /* Swap */ - const char *what; - - /* CGroup */ - uint64_t memory_current; - uint64_t memory_limit; - uint64_t cpu_usage_nsec; - uint64_t tasks_current; - uint64_t tasks_max; - - LIST_HEAD(ExecStatusInfo, exec); -} UnitStatusInfo; - -static void print_status_info( - UnitStatusInfo *i, - bool *ellipsized) { - - ExecStatusInfo *p; - const char *active_on, *active_off, *on, *off, *ss; - usec_t timestamp; - char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1; - char since2[FORMAT_TIMESTAMP_MAX], *s2; - const char *path; - char **t, **t2; - - assert(i); - - /* This shows pretty information about a unit. See - * print_property() for a low-level property printer */ - - if (streq_ptr(i->active_state, "failed")) { - active_on = ansi_highlight_red(); - active_off = ansi_normal(); - } else if (streq_ptr(i->active_state, "active") || streq_ptr(i->active_state, "reloading")) { - active_on = ansi_highlight_green(); - active_off = ansi_normal(); - } else - active_on = active_off = ""; - - printf("%s%s%s %s", active_on, draw_special_char(DRAW_BLACK_CIRCLE), active_off, strna(i->id)); - - if (i->description && !streq_ptr(i->id, i->description)) - printf(" - %s", i->description); - - printf("\n"); - - if (i->following) - printf(" Follow: unit currently follows state of %s\n", i->following); - - if (streq_ptr(i->load_state, "error")) { - on = ansi_highlight_red(); - off = ansi_normal(); - } else - on = off = ""; - - path = i->source_path ? i->source_path : i->fragment_path; - - if (i->load_error != 0) - printf(" Loaded: %s%s%s (Reason: %s)\n", - on, strna(i->load_state), off, i->load_error); - else if (path && !isempty(i->unit_file_state) && !isempty(i->unit_file_preset)) - printf(" Loaded: %s%s%s (%s; %s; vendor preset: %s)\n", - on, strna(i->load_state), off, path, i->unit_file_state, i->unit_file_preset); - else if (path && !isempty(i->unit_file_state)) - printf(" Loaded: %s%s%s (%s; %s)\n", - on, strna(i->load_state), off, path, i->unit_file_state); - else if (path) - printf(" Loaded: %s%s%s (%s)\n", - on, strna(i->load_state), off, path); - else - printf(" Loaded: %s%s%s\n", - on, strna(i->load_state), off); - - if (i->transient) - printf("Transient: yes\n"); - - if (!strv_isempty(i->dropin_paths)) { - _cleanup_free_ char *dir = NULL; - bool last = false; - char ** dropin; - - STRV_FOREACH(dropin, i->dropin_paths) { - if (! dir || last) { - printf(dir ? " " : " Drop-In: "); - - dir = mfree(dir); - - dir = dirname_malloc(*dropin); - if (!dir) { - log_oom(); - return; - } - - printf("%s\n %s", dir, - draw_special_char(DRAW_TREE_RIGHT)); - } - - last = ! (*(dropin + 1) && startswith(*(dropin + 1), dir)); - - printf("%s%s", basename(*dropin), last ? "\n" : ", "); - } - } - - ss = streq_ptr(i->active_state, i->sub_state) ? NULL : i->sub_state; - if (ss) - printf(" Active: %s%s (%s)%s", - active_on, strna(i->active_state), ss, active_off); - else - printf(" Active: %s%s%s", - active_on, strna(i->active_state), active_off); - - if (!isempty(i->result) && !streq(i->result, "success")) - printf(" (Result: %s)", i->result); - - timestamp = (streq_ptr(i->active_state, "active") || - streq_ptr(i->active_state, "reloading")) ? i->active_enter_timestamp : - (streq_ptr(i->active_state, "inactive") || - streq_ptr(i->active_state, "failed")) ? i->inactive_enter_timestamp : - streq_ptr(i->active_state, "activating") ? i->inactive_exit_timestamp : - i->active_exit_timestamp; - - s1 = format_timestamp_relative(since1, sizeof(since1), timestamp); - s2 = format_timestamp(since2, sizeof(since2), timestamp); - - if (s1) - printf(" since %s; %s\n", s2, s1); - else if (s2) - printf(" since %s\n", s2); - else - printf("\n"); - - if (!i->condition_result && i->condition_timestamp > 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); - } - - if (!i->assert_result && i->assert_timestamp > 0) { - s1 = format_timestamp_relative(since1, sizeof(since1), i->assert_timestamp); - s2 = format_timestamp(since2, sizeof(since2), i->assert_timestamp); - - printf(" Assert: start %sassertion failed%s at %s%s%s\n", - ansi_highlight_red(), ansi_normal(), - s2, s1 ? "; " : "", strempty(s1)); - if (i->failed_assert_trigger) - printf(" none of the trigger assertions were met\n"); - else if (i->failed_assert) - printf(" %s=%s%s was not met\n", - i->failed_assert, - i->failed_assert_negate ? "!" : "", - i->failed_assert_parameter); - } - - if (i->sysfs_path) - printf(" Device: %s\n", i->sysfs_path); - if (i->where) - printf(" Where: %s\n", i->where); - if (i->what) - printf(" What: %s\n", i->what); - - STRV_FOREACH(t, i->documentation) - printf(" %*s %s\n", 9, t == i->documentation ? "Docs:" : "", *t); - - STRV_FOREACH_PAIR(t, t2, i->listen) - printf(" %*s %s (%s)\n", 9, t == i->listen ? "Listen:" : "", *t2, *t); - - if (i->accept) - printf(" Accepted: %u; Connected: %u\n", i->n_accepted, i->n_connections); - - LIST_FOREACH(exec, p, i->exec) { - _cleanup_free_ char *argv = NULL; - bool good; - - /* Only show exited processes here */ - if (p->code == 0) - continue; - - argv = strv_join(p->argv, " "); - printf(" Process: "PID_FMT" %s=%s ", p->pid, p->name, strna(argv)); - - good = is_clean_exit_lsb(p->code, p->status, NULL); - if (!good) { - on = ansi_highlight_red(); - off = ansi_normal(); - } else - on = off = ""; - - printf("%s(code=%s, ", on, sigchld_code_to_string(p->code)); - - if (p->code == CLD_EXITED) { - const char *c; - - printf("status=%i", p->status); - - c = exit_status_to_string(p->status, EXIT_STATUS_SYSTEMD); - if (c) - printf("/%s", c); - - } else - printf("signal=%s", signal_to_string(p->status)); - - printf(")%s\n", off); - - if (i->main_pid == p->pid && - i->start_timestamp == p->start_timestamp && - i->exit_timestamp == p->start_timestamp) - /* Let's not show this twice */ - i->main_pid = 0; - - if (p->pid == i->control_pid) - i->control_pid = 0; - } - - if (i->main_pid > 0 || i->control_pid > 0) { - if (i->main_pid > 0) { - printf(" Main PID: "PID_FMT, i->main_pid); - - if (i->running) { - _cleanup_free_ char *comm = NULL; - get_process_comm(i->main_pid, &comm); - if (comm) - printf(" (%s)", comm); - } else if (i->exit_code > 0) { - printf(" (code=%s, ", sigchld_code_to_string(i->exit_code)); - - if (i->exit_code == CLD_EXITED) { - const char *c; - - printf("status=%i", i->exit_status); - - c = exit_status_to_string(i->exit_status, EXIT_STATUS_SYSTEMD); - if (c) - printf("/%s", c); - - } else - 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); - - get_process_comm(i->control_pid, &c); - if (c) - printf(" (%s)", c); - } - - printf("\n"); - } - - if (i->status_text) - printf(" Status: \"%s\"\n", i->status_text); - if (i->status_errno > 0) - printf(" Error: %i (%s)\n", i->status_errno, strerror(i->status_errno)); - - if (i->tasks_current != (uint64_t) -1) { - printf(" Tasks: %" PRIu64, i->tasks_current); - - if (i->tasks_max != (uint64_t) -1) - printf(" (limit: %" PRIi64 ")\n", i->tasks_max); - else - printf("\n"); - } - - if (i->memory_current != (uint64_t) -1) { - char buf[FORMAT_BYTES_MAX]; - - printf(" Memory: %s", format_bytes(buf, sizeof(buf), i->memory_current)); - - if (i->memory_limit != (uint64_t) -1) - printf(" (limit: %s)\n", format_bytes(buf, sizeof(buf), i->memory_limit)); - else - printf("\n"); - } - - if (i->cpu_usage_nsec != (uint64_t) -1) { - char buf[FORMAT_TIMESPAN_MAX]; - printf(" CPU: %s\n", format_timespan(buf, sizeof(buf), i->cpu_usage_nsec / NSEC_PER_USEC, USEC_PER_MSEC)); - } - - if (i->control_group && - (i->main_pid > 0 || i->control_pid > 0 || - (!IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE) || cg_is_empty_recursive(SYSTEMD_CGROUP_CONTROLLER, i->control_group) == 0))) { - unsigned c; - - printf(" CGroup: %s\n", i->control_group); - - if (IN_SET(arg_transport, - BUS_TRANSPORT_LOCAL, - BUS_TRANSPORT_MACHINE)) { - unsigned k = 0; - pid_t extra[2]; - static const char prefix[] = " "; - - c = columns(); - if (c > sizeof(prefix) - 1) - c -= sizeof(prefix) - 1; - else - c = 0; - - if (i->main_pid > 0) - extra[k++] = i->main_pid; - - if (i->control_pid > 0) - extra[k++] = i->control_pid; - - show_cgroup_and_extra(SYSTEMD_CGROUP_CONTROLLER, i->control_group, prefix, c, false, extra, k, get_output_flags()); - } - } - - if (i->id && arg_transport == BUS_TRANSPORT_LOCAL) - show_journal_by_unit( - stdout, - i->id, - arg_output, - 0, - i->inactive_exit_timestamp_monotonic, - arg_lines, - getuid(), - get_output_flags() | OUTPUT_BEGIN_NEWLINE, - SD_JOURNAL_LOCAL_ONLY, - arg_scope == UNIT_FILE_SYSTEM, - ellipsized); - - if (i->need_daemon_reload) - warn_unit_file_changed(i->id); -} - -static void show_unit_help(UnitStatusInfo *i) { - char **p; - - assert(i); - - if (!i->documentation) { - log_info("Documentation for %s not known.", i->id); - return; - } - - STRV_FOREACH(p, i->documentation) - if (startswith(*p, "man:")) - show_man_page(*p + 4, false); - else - log_info("Can't show: %s", *p); -} - -static int status_property(const char *name, sd_bus_message *m, UnitStatusInfo *i, const char *contents) { - int r; - - assert(name); - assert(m); - assert(i); - - switch (contents[0]) { - - case SD_BUS_TYPE_STRING: { - const char *s; - - r = sd_bus_message_read(m, "s", &s); - if (r < 0) - return bus_log_parse_error(r); - - if (!isempty(s)) { - if (streq(name, "Id")) - i->id = s; - else if (streq(name, "LoadState")) - i->load_state = s; - else if (streq(name, "ActiveState")) - i->active_state = s; - else if (streq(name, "SubState")) - i->sub_state = s; - else if (streq(name, "Description")) - i->description = s; - else if (streq(name, "FragmentPath")) - i->fragment_path = s; - else if (streq(name, "SourcePath")) - i->source_path = s; -#ifndef NOLEGACY - else if (streq(name, "DefaultControlGroup")) { - const char *e; - e = startswith(s, SYSTEMD_CGROUP_CONTROLLER ":"); - if (e) - i->control_group = e; - } -#endif - else if (streq(name, "ControlGroup")) - i->control_group = s; - else if (streq(name, "StatusText")) - i->status_text = s; - else if (streq(name, "PIDFile")) - i->pid_file = s; - else if (streq(name, "SysFSPath")) - i->sysfs_path = s; - else if (streq(name, "Where")) - i->where = s; - else if (streq(name, "What")) - i->what = s; - else if (streq(name, "Following")) - i->following = s; - else if (streq(name, "UnitFileState")) - i->unit_file_state = s; - else if (streq(name, "UnitFilePreset")) - i->unit_file_preset = s; - else if (streq(name, "Result")) - i->result = s; - } - - break; - } - - case SD_BUS_TYPE_BOOLEAN: { - int b; - - r = sd_bus_message_read(m, "b", &b); - if (r < 0) - return bus_log_parse_error(r); - - if (streq(name, "Accept")) - i->accept = b; - else if (streq(name, "NeedDaemonReload")) - i->need_daemon_reload = b; - else if (streq(name, "ConditionResult")) - i->condition_result = b; - else if (streq(name, "AssertResult")) - i->assert_result = b; - else if (streq(name, "Transient")) - i->transient = b; - - break; - } - - case SD_BUS_TYPE_UINT32: { - uint32_t u; - - r = sd_bus_message_read(m, "u", &u); - if (r < 0) - return bus_log_parse_error(r); - - if (streq(name, "MainPID")) { - if (u > 0) { - i->main_pid = (pid_t) u; - i->running = true; - } - } else if (streq(name, "ControlPID")) - i->control_pid = (pid_t) u; - else if (streq(name, "ExecMainPID")) { - if (u > 0) - i->main_pid = (pid_t) u; - } else if (streq(name, "NAccepted")) - i->n_accepted = u; - else if (streq(name, "NConnections")) - i->n_connections = u; - - break; - } - - case SD_BUS_TYPE_INT32: { - int32_t j; - - r = sd_bus_message_read(m, "i", &j); - if (r < 0) - return bus_log_parse_error(r); - - if (streq(name, "ExecMainCode")) - i->exit_code = (int) j; - else if (streq(name, "ExecMainStatus")) - i->exit_status = (int) j; - else if (streq(name, "StatusErrno")) - i->status_errno = (int) j; - - break; - } - - case SD_BUS_TYPE_UINT64: { - uint64_t u; - - r = sd_bus_message_read(m, "t", &u); - if (r < 0) - return bus_log_parse_error(r); - - if (streq(name, "ExecMainStartTimestamp")) - i->start_timestamp = (usec_t) u; - else if (streq(name, "ExecMainExitTimestamp")) - i->exit_timestamp = (usec_t) u; - else if (streq(name, "ActiveEnterTimestamp")) - i->active_enter_timestamp = (usec_t) u; - else if (streq(name, "InactiveEnterTimestamp")) - i->inactive_enter_timestamp = (usec_t) u; - else if (streq(name, "InactiveExitTimestamp")) - i->inactive_exit_timestamp = (usec_t) u; - else if (streq(name, "InactiveExitTimestampMonotonic")) - i->inactive_exit_timestamp_monotonic = (usec_t) u; - else if (streq(name, "ActiveExitTimestamp")) - i->active_exit_timestamp = (usec_t) u; - else if (streq(name, "ConditionTimestamp")) - i->condition_timestamp = (usec_t) u; - else if (streq(name, "AssertTimestamp")) - i->assert_timestamp = (usec_t) u; - else if (streq(name, "MemoryCurrent")) - i->memory_current = u; - else if (streq(name, "MemoryLimit")) - i->memory_limit = u; - else if (streq(name, "TasksCurrent")) - i->tasks_current = u; - else if (streq(name, "TasksMax")) - i->tasks_max = u; - else if (streq(name, "CPUUsageNSec")) - i->cpu_usage_nsec = u; - - break; - } - - case SD_BUS_TYPE_ARRAY: - - if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && startswith(name, "Exec")) { - _cleanup_free_ ExecStatusInfo *info = NULL; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sasbttttuii)"); - if (r < 0) - return bus_log_parse_error(r); - - info = new0(ExecStatusInfo, 1); - if (!info) - return log_oom(); - - while ((r = exec_status_info_deserialize(m, info)) > 0) { - - info->name = strdup(name); - if (!info->name) - return log_oom(); - - LIST_PREPEND(exec, i->exec, info); - - info = new0(ExecStatusInfo, 1); - if (!info) - return log_oom(); - } - - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Listen")) { - const char *type, *path; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) { - - r = strv_extend(&i->listen, type); - if (r < 0) - return r; - - r = strv_extend(&i->listen, path); - if (r < 0) - return r; - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "DropInPaths")) { - - r = sd_bus_message_read_strv(m, &i->dropin_paths); - if (r < 0) - return bus_log_parse_error(r); - - } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "Documentation")) { - - r = sd_bus_message_read_strv(m, &i->documentation); - if (r < 0) - return bus_log_parse_error(r); - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Conditions")) { - const char *cond, *param; - int trigger, negate; - int32_t state; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sbbsi)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(sbbsi)", &cond, &trigger, &negate, ¶m, &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; - } - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Asserts")) { - const char *cond, *param; - int trigger, negate; - int32_t state; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sbbsi)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(sbbsi)", &cond, &trigger, &negate, ¶m, &state)) > 0) { - log_debug("%s %d %d %s %d", cond, trigger, negate, param, state); - if (state < 0 && (!trigger || !i->failed_assert)) { - i->failed_assert = cond; - i->failed_assert_trigger = trigger; - i->failed_assert_negate = negate; - i->failed_assert_parameter = param; - } - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - } else - goto skip; - - break; - - case SD_BUS_TYPE_STRUCT_BEGIN: - - if (streq(name, "LoadError")) { - const char *n, *message; - - r = sd_bus_message_read(m, "(ss)", &n, &message); - if (r < 0) - return bus_log_parse_error(r); - - if (!isempty(message)) - i->load_error = message; - } else - goto skip; - - break; - - default: - goto skip; - } - - return 0; - -skip: - r = sd_bus_message_skip(m, contents); - if (r < 0) - return bus_log_parse_error(r); - - return 0; -} - -static int print_property(const char *name, sd_bus_message *m, const char *contents) { - int r; - - assert(name); - assert(m); - - /* This is a low-level property printer, see - * print_status_info() for the nicer output */ - - if (arg_properties && !strv_find(arg_properties, name)) { - /* skip what we didn't read */ - r = sd_bus_message_skip(m, contents); - return r; - } - - switch (contents[0]) { - - case SD_BUS_TYPE_STRUCT_BEGIN: - - if (contents[1] == SD_BUS_TYPE_UINT32 && streq(name, "Job")) { - uint32_t u; - - r = sd_bus_message_read(m, "(uo)", &u, NULL); - if (r < 0) - return bus_log_parse_error(r); - - if (u > 0) - printf("%s=%"PRIu32"\n", name, u); - else if (arg_all) - printf("%s=\n", name); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "Unit")) { - const char *s; - - r = sd_bus_message_read(m, "(so)", &s, NULL); - if (r < 0) - return bus_log_parse_error(r); - - if (arg_all || !isempty(s)) - printf("%s=%s\n", name, s); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRING && streq(name, "LoadError")) { - const char *a = NULL, *b = NULL; - - r = sd_bus_message_read(m, "(ss)", &a, &b); - if (r < 0) - return bus_log_parse_error(r); - - if (arg_all || !isempty(a) || !isempty(b)) - printf("%s=%s \"%s\"\n", name, strempty(a), strempty(b)); - - return 0; - } else if (streq_ptr(name, "SystemCallFilter")) { - _cleanup_strv_free_ char **l = NULL; - int whitelist; - - r = sd_bus_message_enter_container(m, 'r', "bas"); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read(m, "b", &whitelist); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_read_strv(m, &l); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - if (arg_all || whitelist || !strv_isempty(l)) { - bool first = true; - char **i; - - fputs(name, stdout); - fputc('=', stdout); - - if (!whitelist) - fputc('~', stdout); - - STRV_FOREACH(i, l) { - if (first) - first = false; - else - fputc(' ', stdout); - - fputs(*i, stdout); - } - fputc('\n', stdout); - } - - return 0; - } - - break; - - case SD_BUS_TYPE_ARRAY: - - if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "EnvironmentFiles")) { - const char *path; - int ignore; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sb)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(sb)", &path, &ignore)) > 0) - printf("EnvironmentFile=%s (ignore_errors=%s)\n", path, yes_no(ignore)); - - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Paths")) { - const char *type, *path; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) - printf("%s=%s\n", type, path); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Listen")) { - const char *type, *path; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(ss)", &type, &path)) > 0) - printf("Listen%s=%s\n", type, path); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "Timers")) { - const char *base; - uint64_t value, next_elapse; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(stt)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(stt)", &base, &value, &next_elapse)) > 0) { - char timespan1[FORMAT_TIMESPAN_MAX], timespan2[FORMAT_TIMESPAN_MAX]; - - printf("%s={ value=%s ; next_elapse=%s }\n", - base, - format_timespan(timespan1, sizeof(timespan1), value, 0), - format_timespan(timespan2, sizeof(timespan2), next_elapse, 0)); - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && startswith(name, "Exec")) { - ExecStatusInfo info = {}; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(sasbttttuii)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = exec_status_info_deserialize(m, &info)) > 0) { - char timestamp1[FORMAT_TIMESTAMP_MAX], timestamp2[FORMAT_TIMESTAMP_MAX]; - _cleanup_free_ char *tt; - - tt = strv_join(info.argv, " "); - - printf("%s={ path=%s ; argv[]=%s ; ignore_errors=%s ; start_time=[%s] ; stop_time=[%s] ; pid="PID_FMT" ; code=%s ; status=%i%s%s }\n", - name, - strna(info.path), - strna(tt), - yes_no(info.ignore), - strna(format_timestamp(timestamp1, sizeof(timestamp1), info.start_timestamp)), - strna(format_timestamp(timestamp2, sizeof(timestamp2), info.exit_timestamp)), - info.pid, - sigchld_code_to_string(info.code), - info.status, - info.code == CLD_EXITED ? "" : "/", - strempty(info.code == CLD_EXITED ? NULL : signal_to_string(info.status))); - - free(info.path); - strv_free(info.argv); - zero(info); - } - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "DeviceAllow")) { - const char *path, *rwm; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(ss)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(ss)", &path, &rwm)) > 0) - printf("%s=%s %s\n", name, strna(path), strna(rwm)); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && streq(name, "BlockIODeviceWeight")) { - const char *path; - uint64_t weight; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(st)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(st)", &path, &weight)) > 0) - printf("%s=%s %" PRIu64 "\n", name, strna(path), weight); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - - } else if (contents[1] == SD_BUS_TYPE_STRUCT_BEGIN && (streq(name, "BlockIOReadBandwidth") || streq(name, "BlockIOWriteBandwidth"))) { - const char *path; - uint64_t bandwidth; - - r = sd_bus_message_enter_container(m, SD_BUS_TYPE_ARRAY, "(st)"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read(m, "(st)", &path, &bandwidth)) > 0) - printf("%s=%s %" PRIu64 "\n", name, strna(path), bandwidth); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(m); - if (r < 0) - return bus_log_parse_error(r); - - return 0; - } - - break; - } - - r = bus_print_property(name, m, arg_all); - if (r < 0) - return bus_log_parse_error(r); - - if (r == 0) { - r = sd_bus_message_skip(m, contents); - if (r < 0) - return bus_log_parse_error(r); - - if (arg_all) - printf("%s=[unprintable]\n", name); - } - - return 0; -} - -static int show_one( - const char *verb, - sd_bus *bus, - const char *path, - bool show_properties, - bool *new_line, - bool *ellipsized) { - - _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 = { - .memory_current = (uint64_t) -1, - .memory_limit = (uint64_t) -1, - .cpu_usage_nsec = (uint64_t) -1, - .tasks_current = (uint64_t) -1, - .tasks_max = (uint64_t) -1, - }; - ExecStatusInfo *p; - int r; - - assert(path); - assert(new_line); - - log_debug("Showing one %s", path); - - 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 = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "{sv}"); - if (r < 0) - return bus_log_parse_error(r); - - if (*new_line) - printf("\n"); - - *new_line = true; - - while ((r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_DICT_ENTRY, "sv")) > 0) { - const char *name, *contents; - - r = sd_bus_message_read(reply, "s", &name); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_peek_type(reply, NULL, &contents); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_VARIANT, contents); - if (r < 0) - return bus_log_parse_error(r); - - if (show_properties) - r = print_property(name, reply, contents); - else - r = status_property(name, reply, &info, contents); - if (r < 0) - return r; - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - } - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - r = 0; - - if (!show_properties) { - if (streq(verb, "help")) - show_unit_help(&info); - else - print_status_info(&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 = 1; - else - r = 3; - } - - while ((p = info.exec)) { - LIST_REMOVE(exec, info.exec, p); - exec_status_info_free(p); - } - - return r; -} - -static int get_unit_dbus_path_by_pid( - sd_bus *bus, - uint32_t pid, - char **unit) { - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - char *u; - int r; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnitByPID", - &error, - &reply, - "u", pid); - if (r < 0) - return log_error_errno(r, "Failed to get unit for PID %"PRIu32": %s", pid, bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "o", &u); - if (r < 0) - return bus_log_parse_error(r); - - u = strdup(u); - if (!u) - return log_oom(); - - *unit = u; - return 0; -} - -static int show_all( - const char* verb, - sd_bus *bus, - bool show_properties, - bool *new_line, - bool *ellipsized) { - - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - _cleanup_free_ UnitInfo *unit_infos = NULL; - const UnitInfo *u; - unsigned c; - int r, ret = 0; - - r = get_unit_list(bus, NULL, NULL, &unit_infos, 0, &reply); - if (r < 0) - return r; - - pager_open_if_enabled(); - - c = (unsigned) r; - - qsort_safe(unit_infos, c, sizeof(UnitInfo), compare_unit_info); - - for (u = unit_infos; u < unit_infos + c; u++) { - _cleanup_free_ char *p = NULL; - - p = unit_dbus_path_from_name(u->id); - if (!p) - return log_oom(); - - r = show_one(verb, bus, p, show_properties, new_line, ellipsized); - if (r < 0) - return r; - else if (r > 0 && ret == 0) - ret = r; - } - - return ret; -} - -static int show_system_status(sd_bus *bus) { - char since1[FORMAT_TIMESTAMP_RELATIVE_MAX], since2[FORMAT_TIMESTAMP_MAX]; - _cleanup_free_ char *hn = NULL; - _cleanup_(machine_info_clear) struct machine_info mi = {}; - const char *on, *off; - int r; - - hn = gethostname_malloc(); - if (!hn) - return log_oom(); - - r = bus_map_all_properties(bus, "org.freedesktop.systemd1", "/org/freedesktop/systemd1", machine_info_property_map, &mi); - if (r < 0) - return log_error_errno(r, "Failed to read server status: %m"); - - if (streq_ptr(mi.state, "degraded")) { - on = ansi_highlight_red(); - off = ansi_normal(); - } else if (!streq_ptr(mi.state, "running")) { - on = ansi_highlight_yellow(); - off = ansi_normal(); - } else - on = off = ""; - - printf("%s%s%s %s\n", on, draw_special_char(DRAW_BLACK_CIRCLE), off, arg_host ? arg_host : hn); - - printf(" State: %s%s%s\n", - on, strna(mi.state), off); - - printf(" Jobs: %u queued\n", mi.n_jobs); - printf(" Failed: %u units\n", mi.n_failed_units); - - printf(" Since: %s; %s\n", - format_timestamp(since2, sizeof(since2), mi.timestamp), - format_timestamp_relative(since1, sizeof(since1), mi.timestamp)); - - printf(" CGroup: %s\n", mi.control_group ?: "/"); - if (IN_SET(arg_transport, - BUS_TRANSPORT_LOCAL, - BUS_TRANSPORT_MACHINE)) { - static const char prefix[] = " "; - unsigned c; - - c = columns(); - if (c > sizeof(prefix) - 1) - c -= sizeof(prefix) - 1; - else - c = 0; - - show_cgroup(SYSTEMD_CGROUP_CONTROLLER, strempty(mi.control_group), prefix, c, false, get_output_flags()); - } - - return 0; -} - -static int show(int argc, char *argv[], void *userdata) { - bool show_properties, show_status, show_help, new_line = false; - bool ellipsized = false; - int r, ret = 0; - sd_bus *bus; - - assert(argv); - - show_properties = streq(argv[0], "show"); - show_status = streq(argv[0], "status"); - show_help = streq(argv[0], "help"); - - if (show_help && argc <= 1) { - log_error("This command expects one or more unit names. Did you mean --help?"); - return -EINVAL; - } - - pager_open_if_enabled(); - - if (show_status) - /* Increase max number of open files to 16K if we can, we - * might needs this when browsing journal files, which might - * 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); - - if (show_status && argc <= 1) { - - pager_open_if_enabled(); - show_system_status(bus); - new_line = true; - - if (arg_all) - ret = show_all(argv[0], bus, false, &new_line, &ellipsized); - } else { - _cleanup_free_ char **patterns = NULL; - char **name; - - STRV_FOREACH(name, strv_skip(argv, 1)) { - _cleanup_free_ char *unit = NULL; - uint32_t id; - - if (safe_atou32(*name, &id) < 0) { - if (strv_push(&patterns, *name) < 0) - return log_oom(); - - continue; - } else if (show_properties) { - /* Interpret as job id */ - if (asprintf(&unit, "/org/freedesktop/systemd1/job/%u", id) < 0) - return log_oom(); - - } else { - /* Interpret as PID */ - r = get_unit_dbus_path_by_pid(bus, id, &unit); - if (r < 0) { - ret = r; - continue; - } - } - - r = show_one(argv[0], bus, unit, show_properties, &new_line, &ellipsized); - if (r < 0) - return r; - else if (r > 0 && ret == 0) - ret = r; - } - - if (!strv_isempty(patterns)) { - _cleanup_strv_free_ char **names = NULL; - - r = expand_names(bus, patterns, NULL, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - - STRV_FOREACH(name, names) { - _cleanup_free_ char *unit; - - unit = unit_dbus_path_from_name(*name); - if (!unit) - return log_oom(); - - r = show_one(argv[0], bus, unit, show_properties, &new_line, &ellipsized); - if (r < 0) - return r; - else if (r > 0 && ret == 0) - ret = r; - } - } - } - - if (ellipsized && !arg_quiet) - printf("Hint: Some lines were ellipsized, use -l to show in full.\n"); - - return ret; -} - -static int init_home_and_lookup_paths(char **user_home, char **user_runtime, LookupPaths *lp) { - int r; - - assert(user_home); - assert(user_runtime); - assert(lp); - - if (arg_scope == UNIT_FILE_USER) { - r = user_config_home(user_home); - if (r < 0) - return log_error_errno(r, "Failed to query XDG_CONFIG_HOME: %m"); - else if (r == 0) - return log_error_errno(ENOTDIR, "Cannot find units: $XDG_CONFIG_HOME and $HOME are not set."); - - r = user_runtime_dir(user_runtime); - if (r < 0) - return log_error_errno(r, "Failed to query XDG_CONFIG_HOME: %m"); - else if (r == 0) - return log_error_errno(ENOTDIR, "Cannot find units: $XDG_RUNTIME_DIR is not set."); - } - - r = lookup_paths_init_from_scope(lp, arg_scope, arg_root); - if (r < 0) - return log_error_errno(r, "Failed to query unit lookup paths: %m"); - - return 0; -} - -static int cat_file(const char *filename, bool newline) { - _cleanup_close_ int fd; - - fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) - return -errno; - - printf("%s%s# %s%s\n", - newline ? "\n" : "", - ansi_highlight_blue(), - filename, - ansi_normal()); - fflush(stdout); - - return copy_bytes(fd, STDOUT_FILENO, (uint64_t) -1, false); -} - -static int cat(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *user_home = NULL; - _cleanup_free_ char *user_runtime = NULL; - _cleanup_lookup_paths_free_ LookupPaths lp = {}; - _cleanup_strv_free_ char **names = NULL; - char **name; - sd_bus *bus; - bool first = true; - int r; - - if (arg_transport != BUS_TRANSPORT_LOCAL) { - log_error("Cannot remotely cat units."); - return -EINVAL; - } - - r = init_home_and_lookup_paths(&user_home, &user_runtime, &lp); - if (r < 0) - return r; - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = expand_names(bus, strv_skip(argv, 1), NULL, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - - pager_open_if_enabled(); - - STRV_FOREACH(name, names) { - _cleanup_free_ char *fragment_path = NULL; - _cleanup_strv_free_ char **dropin_paths = NULL; - char **path; - - r = unit_find_paths(bus, *name, &lp, &fragment_path, &dropin_paths); - if (r < 0) - return r; - else if (r == 0) - return -ENOENT; - - if (first) - first = false; - else - puts(""); - - if (fragment_path) { - r = cat_file(fragment_path, false); - if (r < 0) - return log_warning_errno(r, "Failed to cat %s: %m", fragment_path); - } - - STRV_FOREACH(path, dropin_paths) { - r = cat_file(*path, path == dropin_paths); - if (r < 0) - return log_warning_errno(r, "Failed to cat %s: %m", *path); - } - } - - return 0; -} - -static int set_property(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *n = NULL; - sd_bus *bus; - char **i; - int r; - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SetUnitProperties"); - if (r < 0) - return bus_log_create_error(r); - - r = unit_name_mangle(argv[1], UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - r = sd_bus_message_append(m, "sb", n, arg_runtime); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_open_container(m, SD_BUS_TYPE_ARRAY, "(sv)"); - if (r < 0) - return bus_log_create_error(r); - - STRV_FOREACH(i, strv_skip(argv, 2)) { - r = bus_append_unit_property_assignment(m, *i); - if (r < 0) - return r; - } - - r = sd_bus_message_close_container(m); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, NULL); - if (r < 0) - return log_error_errno(r, "Failed to set unit properties on %s: %s", n, bus_error_message(&error, r)); - - return 0; -} - -static int daemon_reload(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; - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - if (arg_action == ACTION_RELOAD) - method = "Reload"; - else if (arg_action == ACTION_REEXEC) - method = "Reexecute"; - else { - assert(arg_action == ACTION_SYSTEMCTL); - - 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"; - } - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - method, - &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 execute operation: %s", bus_error_message(&error, r)); - - return r < 0 ? r : 0; -} - -static int reset_failed(int argc, char *argv[], void *userdata) { - _cleanup_strv_free_ char **names = NULL; - sd_bus *bus; - char **name; - int r, q; - - if (argc <= 1) - return daemon_reload(argc, argv, userdata); - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = expand_names(bus, strv_skip(argv, 1), NULL, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - - STRV_FOREACH(name, names) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - - q = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "ResetFailedUnit", - &error, - NULL, - "s", *name); - if (q < 0) { - log_error_errno(q, "Failed to reset failed state of unit %s: %s", *name, bus_error_message(&error, q)); - if (r == 0) - r = q; - } - } - - return r; -} - -static int show_environment(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 *reply = NULL; - const char *text; - sd_bus *bus; - int r; - - pager_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_get_property( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Environment", - &error, - &reply, - "as"); - if (r < 0) - return log_error_errno(r, "Failed to get environment: %s", bus_error_message(&error, r)); - - r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "s"); - if (r < 0) - return bus_log_parse_error(r); - - while ((r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_STRING, &text)) > 0) - puts(text); - if (r < 0) - return bus_log_parse_error(r); - - r = sd_bus_message_exit_container(reply); - if (r < 0) - return bus_log_parse_error(r); - - return 0; -} - -static int switch_root(int argc, char *argv[], void *userdata) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_free_ char *cmdline_init = NULL; - const char *root, *init; - sd_bus *bus; - int r; - - if (arg_transport != BUS_TRANSPORT_LOCAL) { - log_error("Cannot switch root remotely."); - return -EINVAL; - } - - if (argc < 2 || argc > 3) { - log_error("Wrong number of arguments."); - return -EINVAL; - } - - root = argv[1]; - - if (argc >= 3) - init = argv[2]; - else { - r = parse_env_file("/proc/cmdline", WHITESPACE, - "init", &cmdline_init, - NULL); - if (r < 0) - log_debug_errno(r, "Failed to parse /proc/cmdline: %m"); - - init = cmdline_init; - } - - if (isempty(init)) - init = NULL; - - if (init) { - const char *root_systemd_path = NULL, *root_init_path = NULL; - - root_systemd_path = strjoina(root, "/" SYSTEMD_BINARY_PATH); - root_init_path = strjoina(root, "/", init); - - /* If the passed init is actually the same as the - * systemd binary, then let's suppress it. */ - if (files_same(root_init_path, root_systemd_path) > 0) - init = NULL; - } - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - log_debug("Switching root - root: %s; init: %s", root, strna(init)); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SwitchRoot", - &error, - NULL, - "ss", root, init); - if (r < 0) - return log_error_errno(r, "Failed to switch root: %s", bus_error_message(&error, r)); - - return 0; -} - -static int set_environment(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; - - assert(argc > 1); - assert(argv); - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - method = streq(argv[0], "set-environment") - ? "SetEnvironment" - : "UnsetEnvironment"; - - 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); - - r = sd_bus_message_append_strv(m, strv_skip(argv, 1)); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, NULL); - if (r < 0) - return log_error_errno(r, "Failed to set environment: %s", bus_error_message(&error, r)); - - return 0; -} - -static int import_environment(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; - sd_bus *bus; - int r; - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SetEnvironment"); - if (r < 0) - return bus_log_create_error(r); - - if (argc < 2) - r = sd_bus_message_append_strv(m, environ); - else { - char **a, **b; - - r = sd_bus_message_open_container(m, 'a', "s"); - if (r < 0) - return bus_log_create_error(r); - - STRV_FOREACH(a, strv_skip(argv, 1)) { - - if (!env_name_is_valid(*a)) { - log_error("Not a valid environment variable name: %s", *a); - return -EINVAL; - } - - STRV_FOREACH(b, environ) { - const char *eq; - - eq = startswith(*b, *a); - if (eq && *eq == '=') { - - r = sd_bus_message_append(m, "s", *b); - if (r < 0) - return bus_log_create_error(r); - - break; - } - } - } - - r = sd_bus_message_close_container(m); - } - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, NULL); - if (r < 0) - return log_error_errno(r, "Failed to import environment: %s", bus_error_message(&error, r)); - - return 0; -} - -static int enable_sysv_units(const char *verb, char **args) { - int r = 0; - -#if defined(HAVE_SYSV_COMPAT) - unsigned f = 0; - _cleanup_lookup_paths_free_ LookupPaths paths = {}; - - if (arg_scope != UNIT_FILE_SYSTEM) - return 0; - - if (getenv_bool("SYSTEMCTL_SKIP_SYSV") > 0) - return 0; - - if (!STR_IN_SET(verb, - "enable", - "disable", - "is-enabled")) - return 0; - - /* Processes all SysV units, and reshuffles the array so that - * afterwards only the native units remain */ - - r = lookup_paths_init(&paths, MANAGER_SYSTEM, false, arg_root, NULL, NULL, NULL); - if (r < 0) - return r; - - r = 0; - while (args[f]) { - const char *name; - _cleanup_free_ char *p = NULL, *q = NULL, *l = NULL; - bool found_native = false, found_sysv; - unsigned c = 1; - const char *argv[6] = { ROOTLIBEXECDIR "/systemd-sysv-install", NULL, NULL, NULL, NULL }; - char **k; - int j; - pid_t pid; - siginfo_t status; - - name = args[f++]; - - if (!endswith(name, ".service")) - continue; - - if (path_is_absolute(name)) - continue; - - STRV_FOREACH(k, paths.unit_path) { - _cleanup_free_ char *path = NULL; - - path = path_join(arg_root, *k, name); - if (!path) - return log_oom(); - - found_native = access(path, F_OK) >= 0; - if (found_native) - break; - } - - /* If we have both a native unit and a SysV script, - * enable/disable them both (below); for is-enabled, prefer the - * native unit */ - if (found_native && streq(verb, "is-enabled")) - continue; - - p = path_join(arg_root, SYSTEM_SYSVINIT_PATH, name); - if (!p) - return log_oom(); - - p[strlen(p) - strlen(".service")] = 0; - found_sysv = access(p, F_OK) >= 0; - if (!found_sysv) - continue; - - if (found_native) - log_info("Synchronizing state of %s with SysV init 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); - - argv[c++] = verb; - argv[c++] = basename(p); - argv[c] = NULL; - - l = strv_join((char**)argv, " "); - if (!l) - return log_oom(); - - log_info("Executing %s", l); - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - else if (pid == 0) { - /* Child */ - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - execv(argv[0], (char**) argv); - log_error_errno(r, "Failed to execute %s: %m", argv[0]); - _exit(EXIT_FAILURE); - } - - j = wait_for_terminate(pid, &status); - if (j < 0) { - log_error_errno(j, "Failed to wait for child: %m"); - return j; - } - - if (status.si_code == CLD_EXITED) { - if (streq(verb, "is-enabled")) { - if (status.si_status == 0) { - if (!arg_quiet) - puts("enabled"); - r = 1; - } else { - if (!arg_quiet) - puts("disabled"); - } - - } else if (status.si_status != 0) - return -EINVAL; - } else - return -EPROTO; - - if (found_native) - continue; - - /* Remove this entry, so that we don't try enabling it as native unit */ - assert(f > 0); - f--; - assert(args[f] == name); - strv_remove(args, name); - } - -#endif - return r; -} - -static int mangle_names(char **original_names, char ***mangled_names) { - char **i, **l, **name; - int r; - - l = i = new(char*, strv_length(original_names) + 1); - if (!l) - return log_oom(); - - STRV_FOREACH(name, original_names) { - - /* When enabling units qualified path names are OK, - * too, hence allow them explicitly. */ - - if (is_path(*name)) { - *i = strdup(*name); - if (!*i) { - strv_free(l); - return log_oom(); - } - } else { - r = unit_name_mangle(*name, UNIT_NAME_NOGLOB, i); - if (r < 0) { - strv_free(l); - return log_error_errno(r, "Failed to mangle unit name: %m"); - } - } - - i++; - } - - *i = NULL; - *mangled_names = l; - - return 0; -} - -static int enable_unit(int argc, char *argv[], void *userdata) { - _cleanup_strv_free_ char **names = NULL; - const char *verb = argv[0]; - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - int carries_install_info = -1; - int r; - - if (!argv[1]) - return 0; - - r = mangle_names(strv_skip(argv, 1), &names); - if (r < 0) - return r; - - r = enable_sysv_units(verb, names); - if (r < 0) - return r; - - /* If the operation was fully executed by the SysV compat, - * let's finish early */ - if (strv_isempty(names)) - return 0; - - if (install_client_side()) { - if (streq(verb, "enable")) { - r = unit_file_enable(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); - carries_install_info = r; - } else if (streq(verb, "disable")) - r = unit_file_disable(arg_scope, arg_runtime, arg_root, names, &changes, &n_changes); - else if (streq(verb, "reenable")) { - r = unit_file_reenable(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); - carries_install_info = r; - } else if (streq(verb, "link")) - r = unit_file_link(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); - else if (streq(verb, "preset")) { - r = unit_file_preset(arg_scope, arg_runtime, arg_root, names, arg_preset_mode, arg_force, &changes, &n_changes); - carries_install_info = r; - } else if (streq(verb, "mask")) - r = unit_file_mask(arg_scope, arg_runtime, arg_root, names, arg_force, &changes, &n_changes); - else if (streq(verb, "unmask")) - r = unit_file_unmask(arg_scope, arg_runtime, arg_root, names, &changes, &n_changes); - else - assert_not_reached("Unknown verb"); - - if (r == -ESHUTDOWN) - return log_error_errno(r, "Unit file is masked."); - if (r < 0) - return log_error_errno(r, "Operation failed: %m"); - - if (!arg_quiet) - dump_unit_file_changes(changes, n_changes); - - r = 0; - } else { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int expect_carries_install_info = false; - bool send_force = true, send_preset_mode = false; - const char *method; - sd_bus *bus; - - polkit_agent_open_if_enabled(); - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - if (streq(verb, "enable")) { - method = "EnableUnitFiles"; - expect_carries_install_info = true; - } else if (streq(verb, "disable")) { - method = "DisableUnitFiles"; - send_force = false; - } else if (streq(verb, "reenable")) { - method = "ReenableUnitFiles"; - expect_carries_install_info = true; - } else if (streq(verb, "link")) - method = "LinkUnitFiles"; - else if (streq(verb, "preset")) { - - if (arg_preset_mode != UNIT_FILE_PRESET_FULL) { - method = "PresetUnitFilesWithMode"; - send_preset_mode = true; - } else - method = "PresetUnitFiles"; - - expect_carries_install_info = true; - } else if (streq(verb, "mask")) - method = "MaskUnitFiles"; - else if (streq(verb, "unmask")) { - method = "UnmaskUnitFiles"; - send_force = false; - } else - assert_not_reached("Unknown verb"); - - 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); - - r = sd_bus_message_append_strv(m, names); - if (r < 0) - return bus_log_create_error(r); - - if (send_preset_mode) { - r = sd_bus_message_append(m, "s", unit_file_preset_mode_to_string(arg_preset_mode)); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_message_append(m, "b", arg_runtime); - if (r < 0) - return bus_log_create_error(r); - - if (send_force) { - r = sd_bus_message_append(m, "b", arg_force); - if (r < 0) - return bus_log_create_error(r); - } - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "Failed to execute operation: %s", bus_error_message(&error, r)); - - if (expect_carries_install_info) { - r = sd_bus_message_read(reply, "b", &carries_install_info); - if (r < 0) - return bus_log_parse_error(r); - } - - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, &changes, &n_changes); - if (r < 0) - return r; - - /* Try to reload if enabled */ - if (!arg_no_reload) - r = daemon_reload(argc, argv, userdata); - else - r = 0; - } - - if (carries_install_info == 0) - log_warning("The unit files have no [Install] section. They are not meant to be enabled\n" - "using systemctl.\n" - "Possible reasons for having this kind of units are:\n" - "1) A unit may be statically enabled by being symlinked from another unit's\n" - " .wants/ or .requires/ directory.\n" - "2) A unit's purpose may be to act as a helper for some other unit which has\n" - " a requirement dependency on it.\n" - "3) A unit may be started when needed via activation (socket, path, timer,\n" - " D-Bus, udev, scripted systemctl call, ...).\n"); - - if (arg_now && n_changes > 0 && STR_IN_SET(argv[0], "enable", "disable", "mask")) { - char *new_args[n_changes + 2]; - sd_bus *bus; - unsigned i; - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - goto finish; - - new_args[0] = (char*) (streq(argv[0], "enable") ? "start" : "stop"); - for (i = 0; i < n_changes; i++) - new_args[i + 1] = basename(changes[i].path); - new_args[i + 1] = NULL; - - r = start_unit(strv_length(new_args), new_args, userdata); - } - -finish: - unit_file_changes_free(changes, n_changes); - - return r; -} - -static int add_dependency(int argc, char *argv[], void *userdata) { - _cleanup_strv_free_ char **names = NULL; - _cleanup_free_ char *target = NULL; - const char *verb = argv[0]; - UnitDependency dep; - int r = 0; - - if (!argv[1]) - return 0; - - r = unit_name_mangle_with_suffix(argv[1], UNIT_NAME_NOGLOB, ".target", &target); - if (r < 0) - return log_error_errno(r, "Failed to mangle unit name: %m"); - - r = mangle_names(strv_skip(argv, 2), &names); - if (r < 0) - return r; - - if (streq(verb, "add-wants")) - dep = UNIT_WANTS; - else if (streq(verb, "add-requires")) - dep = UNIT_REQUIRES; - else - assert_not_reached("Unknown verb"); - - if (install_client_side()) { - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - - r = unit_file_add_dependency(arg_scope, arg_runtime, arg_root, names, target, dep, arg_force, &changes, &n_changes); - if (r == -ESHUTDOWN) - return log_error_errno(r, "Unit file is masked."); - if (r < 0) - return log_error_errno(r, "Can't add dependency: %m"); - - if (!arg_quiet) - dump_unit_file_changes(changes, n_changes); - - unit_file_changes_free(changes, n_changes); - - } else { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL, *m = NULL; - _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; - - r = sd_bus_message_new_method_call( - bus, - &m, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "AddDependencyUnitFiles"); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append_strv(m, names); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_message_append(m, "ssbb", target, unit_dependency_to_string(dep), arg_runtime, arg_force); - if (r < 0) - return bus_log_create_error(r); - - r = sd_bus_call(bus, m, 0, &error, &reply); - if (r < 0) - return log_error_errno(r, "Failed to execute operation: %s", bus_error_message(&error, r)); - - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); - if (r < 0) - return r; - - if (!arg_no_reload) - r = daemon_reload(argc, argv, userdata); - else - r = 0; - } - - return r; -} - -static int preset_all(int argc, char *argv[], void *userdata) { - UnitFileChange *changes = NULL; - unsigned n_changes = 0; - int r; - - if (install_client_side()) { - - r = unit_file_preset_all(arg_scope, arg_runtime, arg_root, arg_preset_mode, arg_force, &changes, &n_changes); - if (r < 0) { - log_error_errno(r, "Operation failed: %m"); - goto finish; - } - - if (!arg_quiet) - dump_unit_file_changes(changes, n_changes); - - r = 0; - - } else { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _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; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "PresetAllUnitFiles", - &error, - &reply, - "sbb", - unit_file_preset_mode_to_string(arg_preset_mode), - arg_runtime, - arg_force); - if (r < 0) - return log_error_errno(r, "Failed to execute operation: %s", bus_error_message(&error, r)); - - r = bus_deserialize_and_dump_unit_file_changes(reply, arg_quiet, NULL, NULL); - if (r < 0) - return r; - - if (!arg_no_reload) - r = daemon_reload(argc, argv, userdata); - else - r = 0; - } - -finish: - unit_file_changes_free(changes, n_changes); - - return r; -} - -static int unit_is_enabled(int argc, char *argv[], void *userdata) { - - _cleanup_strv_free_ char **names = NULL; - bool enabled; - char **name; - int r; - - r = mangle_names(strv_skip(argv, 1), &names); - if (r < 0) - return r; - - r = enable_sysv_units(argv[0], names); - if (r < 0) - return r; - - enabled = r > 0; - - if (install_client_side()) { - - STRV_FOREACH(name, names) { - UnitFileState state; - - r = unit_file_get_state(arg_scope, arg_root, *name, &state); - if (r < 0) - return log_error_errno(state, "Failed to get unit file state for %s: %m", *name); - - if (IN_SET(state, - UNIT_FILE_ENABLED, - UNIT_FILE_ENABLED_RUNTIME, - UNIT_FILE_STATIC, - UNIT_FILE_INDIRECT)) - enabled = true; - - if (!arg_quiet) - puts(unit_file_state_to_string(state)); - } - - } else { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - STRV_FOREACH(name, names) { - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *s; - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnitFileState", - &error, - &reply, - "s", *name); - if (r < 0) - return log_error_errno(r, "Failed to get unit file state for %s: %s", *name, bus_error_message(&error, r)); - - r = sd_bus_message_read(reply, "s", &s); - if (r < 0) - return bus_log_parse_error(r); - - if (STR_IN_SET(s, "enabled", "enabled-runtime", "static", "indirect")) - enabled = true; - - if (!arg_quiet) - puts(s); - } - } - - return !enabled; -} - -static int is_system_running(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *state = NULL; - sd_bus *bus; - int r; - - if (running_in_chroot() > 0 || (arg_transport == BUS_TRANSPORT_LOCAL && !sd_booted())) { - if (!arg_quiet) - puts("offline"); - return EXIT_FAILURE; - } - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = sd_bus_get_property_string( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "SystemState", - NULL, - &state); - if (r < 0) { - if (!arg_quiet) - puts("unknown"); - return 0; - } - - if (!arg_quiet) - puts(state); - - return streq(state, "running") ? EXIT_SUCCESS : EXIT_FAILURE; -} - -static int create_edit_temp_file(const char *new_path, const char *original_path, char **ret_tmp_fn) { - _cleanup_free_ char *t = NULL; - int r; - - assert(new_path); - assert(original_path); - assert(ret_tmp_fn); - - r = tempfn_random(new_path, NULL, &t); - if (r < 0) - return log_error_errno(r, "Failed to determine temporary filename for \"%s\": %m", new_path); - - r = mkdir_parents(new_path, 0755); - if (r < 0) - return log_error_errno(r, "Failed to create directories for \"%s\": %m", new_path); - - r = copy_file(original_path, t, 0, 0644, 0); - if (r == -ENOENT) { - - r = touch(t); - if (r < 0) - 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); - - *ret_tmp_fn = t; - t = NULL; - - return 0; -} - -static int get_file_to_edit(const char *name, const char *user_home, const char *user_runtime, char **ret_path) { - _cleanup_free_ char *path = NULL, *path2 = NULL, *run = NULL; - - assert(name); - assert(ret_path); - - switch (arg_scope) { - case UNIT_FILE_SYSTEM: - path = path_join(arg_root, SYSTEM_CONFIG_UNIT_PATH, name); - if (arg_runtime) - run = path_join(arg_root, "/run/systemd/system/", name); - break; - case UNIT_FILE_GLOBAL: - path = path_join(arg_root, USER_CONFIG_UNIT_PATH, name); - if (arg_runtime) - run = path_join(arg_root, "/run/systemd/user/", name); - break; - case UNIT_FILE_USER: - assert(user_home); - assert(user_runtime); - - path = path_join(arg_root, user_home, name); - if (arg_runtime) { - path2 = path_join(arg_root, USER_CONFIG_UNIT_PATH, name); - if (!path2) - return log_oom(); - run = path_join(arg_root, user_runtime, name); - } - break; - default: - assert_not_reached("Invalid scope"); - } - if (!path || (arg_runtime && !run)) - return log_oom(); - - if (arg_runtime) { - if (access(path, F_OK) >= 0) { - log_error("Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.", run, path); - return -EEXIST; - } - - if (path2 && access(path2, F_OK) >= 0) { - log_error("Refusing to create \"%s\" because it would be overridden by \"%s\" anyway.", run, path2); - return -EEXIST; - } - - *ret_path = run; - run = NULL; - } else { - *ret_path = path; - path = NULL; - } - - return 0; -} - -static int unit_file_create_dropin(const char *unit_name, const char *user_home, const char *user_runtime, char **ret_new_path, char **ret_tmp_path) { - char *tmp_new_path, *tmp_tmp_path, *ending; - int r; - - assert(unit_name); - assert(ret_new_path); - assert(ret_tmp_path); - - ending = strjoina(unit_name, ".d/override.conf"); - r = get_file_to_edit(ending, user_home, user_runtime, &tmp_new_path); - if (r < 0) - return r; - - r = create_edit_temp_file(tmp_new_path, tmp_new_path, &tmp_tmp_path); - if (r < 0) { - free(tmp_new_path); - return r; - } - - *ret_new_path = tmp_new_path; - *ret_tmp_path = tmp_tmp_path; - - return 0; -} - -static int unit_file_create_copy( - const char *unit_name, - const char *fragment_path, - const char *user_home, - const char *user_runtime, - char **ret_new_path, - char **ret_tmp_path) { - - char *tmp_new_path, *tmp_tmp_path; - int r; - - assert(fragment_path); - assert(unit_name); - assert(ret_new_path); - assert(ret_tmp_path); - - r = get_file_to_edit(unit_name, user_home, user_runtime, &tmp_new_path); - if (r < 0) - return r; - - if (!path_equal(fragment_path, tmp_new_path) && access(tmp_new_path, F_OK) == 0) { - char response; - - r = ask_char(&response, "yn", "\"%s\" already exists. Overwrite with \"%s\"? [(y)es, (n)o] ", tmp_new_path, fragment_path); - if (r < 0) { - free(tmp_new_path); - return r; - } - if (response != 'y') { - log_warning("%s ignored", unit_name); - free(tmp_new_path); - return -1; - } - } - - 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; - } - - *ret_new_path = tmp_new_path; - *ret_tmp_path = tmp_tmp_path; - - return 0; -} - -static int run_editor(char **paths) { - pid_t pid; - int r; - - assert(paths); - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - - if (pid == 0) { - const char **args; - char *editor, **editor_args = NULL; - char **tmp_path, **original_path, *p; - unsigned n_editor_args = 0, i = 1; - size_t argc; - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - argc = strv_length(paths)/2 + 1; - - /* SYSTEMD_EDITOR takes precedence over EDITOR which takes precedence over VISUAL - * If neither SYSTEMD_EDITOR nor EDITOR nor VISUAL are present, - * we try to execute well known editors - */ - editor = getenv("SYSTEMD_EDITOR"); - if (!editor) - editor = getenv("EDITOR"); - if (!editor) - editor = getenv("VISUAL"); - - if (!isempty(editor)) { - editor_args = strv_split(editor, WHITESPACE); - if (!editor_args) { - (void) log_oom(); - _exit(EXIT_FAILURE); - } - n_editor_args = strv_length(editor_args); - argc += n_editor_args - 1; - } - args = newa(const char*, argc + 1); - - if (n_editor_args > 0) { - args[0] = editor_args[0]; - for (; i < n_editor_args; i++) - args[i] = editor_args[i]; - } - - STRV_FOREACH_PAIR(original_path, tmp_path, paths) { - args[i] = *tmp_path; - i++; - } - args[i] = NULL; - - if (n_editor_args > 0) - execvp(args[0], (char* const*) args); - - FOREACH_STRING(p, "editor", "nano", "vim", "vi") { - args[0] = p; - execvp(p, (char* const*) args); - /* We do not fail if the editor doesn't exist - * because we want to try each one of them before - * failing. - */ - if (errno != ENOENT) { - log_error_errno(errno, "Failed to execute %s: %m", editor); - _exit(EXIT_FAILURE); - } - } - - log_error("Cannot edit unit(s), no editor available. Please set either $SYSTEMD_EDITOR, $EDITOR or $VISUAL."); - _exit(EXIT_FAILURE); - } - - r = wait_for_terminate_and_warn("editor", pid, true); - if (r < 0) - return log_error_errno(r, "Failed to wait for child: %m"); - - return 0; -} - -static int find_paths_to_edit(sd_bus *bus, char **names, char ***paths) { - _cleanup_free_ char *user_home = NULL; - _cleanup_free_ char *user_runtime = NULL; - _cleanup_lookup_paths_free_ LookupPaths lp = {}; - char **name; - int r; - - assert(names); - assert(paths); - - r = init_home_and_lookup_paths(&user_home, &user_runtime, &lp); - if (r < 0) - return r; - - STRV_FOREACH(name, names) { - _cleanup_free_ char *path = NULL; - char *new_path, *tmp_path; - - 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; - } - - if (arg_full) - r = unit_file_create_copy(*name, path, user_home, user_runtime, &new_path, &tmp_path); - else - r = unit_file_create_dropin(*name, user_home, user_runtime, &new_path, &tmp_path); - if (r < 0) - return r; - - r = strv_push_pair(paths, new_path, tmp_path); - if (r < 0) - return log_oom(); - } - - return 0; -} - -static int edit(int argc, char *argv[], void *userdata) { - _cleanup_strv_free_ char **names = NULL; - _cleanup_strv_free_ char **paths = NULL; - char **original, **tmp; - sd_bus *bus; - int r; - - if (!on_tty()) { - log_error("Cannot edit units if not on a tty."); - return -EINVAL; - } - - if (arg_transport != BUS_TRANSPORT_LOCAL) { - log_error("Cannot edit units remotely."); - return -EINVAL; - } - - r = acquire_bus(BUS_MANAGER, &bus); - if (r < 0) - return r; - - r = expand_names(bus, strv_skip(argv, 1), NULL, &names); - if (r < 0) - return log_error_errno(r, "Failed to expand names: %m"); - - r = find_paths_to_edit(bus, names, &paths); - if (r < 0) - return r; - - if (strv_isempty(paths)) - return -ENOENT; - - r = run_editor(paths); - if (r < 0) - goto end; - - STRV_FOREACH_PAIR(original, tmp, paths) { - /* If the temporary file is empty we ignore it. It's - * useful if the user wants to cancel its modification - */ - if (null_or_empty_path(*tmp)) { - log_warning("Editing \"%s\" canceled: temporary file is empty.", *original); - continue; - } - - r = rename(*tmp, *original); - if (r < 0) { - r = log_error_errno(errno, "Failed to rename \"%s\" to \"%s\": %m", *tmp, *original); - goto end; - } - } - - r = 0; - - if (!arg_no_reload && !install_client_side()) - r = daemon_reload(argc, argv, userdata); - -end: - STRV_FOREACH_PAIR(original, tmp, paths) - (void) unlink(*tmp); - - return r; -} - -static void systemctl_help(void) { - - pager_open_if_enabled(); - - printf("%s [OPTIONS...] {COMMAND} ...\n\n" - "Query or send control commands to the systemd manager.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --system Connect to system manager\n" - " --user Connect to user service manager\n" - " -H --host=[USER@]HOST\n" - " Operate on remote host\n" - " -M --machine=CONTAINER\n" - " Operate on local container\n" - " -t --type=TYPE List units of a particular type\n" - " --state=STATE List units with particular LOAD or SUB or ACTIVE state\n" - " -p --property=NAME Show only properties by this name\n" - " -a --all Show all loaded units/properties, including dead/empty\n" - " ones. To list all units installed on the system, use\n" - " the 'list-unit-files' command instead.\n" - " -l --full Don't ellipsize unit names on output\n" - " -r --recursive Show unit list of host and local containers\n" - " --reverse Show reverse dependencies with 'list-dependencies'\n" - " --job-mode=MODE Specify how to deal with already queued jobs, when\n" - " queueing a new job\n" - " --show-types When showing sockets, explicitly show their type\n" - " -i --ignore-inhibitors\n" - " When shutting down or sleeping, ignore inhibitors\n" - " --kill-who=WHO Who to send signal to\n" - " -s --signal=SIGNAL Which signal to send\n" - " --now Start or stop unit in addition to enabling or disabling it\n" - " -q --quiet Suppress output\n" - " --no-block Do not wait until operation finished\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n" - " --no-reload Don't reload daemon after en-/dis-abling unit files\n" - " --no-legend Do not print a legend (column headers and hints)\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password\n" - " Do not ask for system passwords\n" - " --global Enable/disable unit files globally\n" - " --runtime Enable unit files only temporarily until next reboot\n" - " -f --force When enabling unit files, override existing symlinks\n" - " When shutting down, execute action immediately\n" - " --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" - " --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" - " list-units [PATTERN...] List loaded units\n" - " list-sockets [PATTERN...] List loaded sockets ordered by address\n" - " list-timers [PATTERN...] List loaded timers ordered by next elapse\n" - " start NAME... Start (activate) one or more units\n" - " stop NAME... Stop (deactivate) one or more units\n" - " reload NAME... Reload one or more units\n" - " restart NAME... Start or restart one or more units\n" - " try-restart NAME... Restart one or more units if active\n" - " reload-or-restart NAME... Reload one or more units if possible,\n" - " otherwise start or restart\n" - " try-reload-or-restart NAME... If active, reload one or more units,\n" - " if supported, otherwise restart\n" - " isolate NAME Start one unit and stop all others\n" - " kill NAME... Send signal to processes of a unit\n" - " is-active PATTERN... Check whether units are active\n" - " is-failed PATTERN... Check whether units are failed\n" - " status [PATTERN...|PID...] Show runtime status of one or more units\n" - " show [PATTERN...|JOB...] Show properties of one or more\n" - " units/jobs or the manager\n" - " cat PATTERN... Show files and drop-ins of one or more units\n" - " set-property NAME ASSIGNMENT... Sets one or more properties of a unit\n" - " help PATTERN...|PID... Show manual for one or more units\n" - " reset-failed [PATTERN...] Reset failed state for all, one, or more\n" - " units\n" - " list-dependencies [NAME] Recursively show units which are required\n" - " or wanted by this unit or by which this\n" - " 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" - " 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" - " based on preset configuration\n" - " preset-all Enable/disable all unit files based on\n" - " preset configuration\n" - " is-enabled NAME... Check whether unit files are enabled\n" - " mask NAME... Mask one or more units\n" - " unmask NAME... Unmask one or more units\n" - " link PATH... Link one or more units files into\n" - " the search path\n" - " add-wants TARGET NAME... Add 'Wants' dependency for the target\n" - " on specified one or more units\n" - " add-requires TARGET NAME... Add 'Requires' dependency for the target\n" - " on specified one or more units\n" - " edit NAME... Edit one or more unit files\n" - " get-default Get the name of the default target\n" - " set-default NAME Set the default target\n\n" - "Machine Commands:\n" - " list-machines [PATTERN...] List local containers and host\n\n" - "Job Commands:\n" - " list-jobs [PATTERN...] List jobs\n" - " cancel [JOB...] Cancel all, one, or more jobs\n\n" - "Environment Commands:\n" - " show-environment Dump environment\n" - " set-environment NAME=VALUE... Set one or more environment variables\n" - " unset-environment NAME... Unset one or more environment variables\n" - " import-environment [NAME...] Import all or some environment variables\n\n" - "Manager Lifecycle Commands:\n" - " daemon-reload Reload systemd manager configuration\n" - " daemon-reexec Reexecute systemd manager\n\n" - "System Commands:\n" - " is-system-running Check whether system is fully running\n" - " default Enter system default mode\n" - " rescue Enter system rescue mode\n" - " emergency Enter system emergency mode\n" - " halt Shut down and halt the system\n" - " poweroff Shut down and power-off the system\n" - " reboot [ARG] Shut down and reboot the system\n" - " kexec Shut down and reboot the system with kexec\n" - " exit [EXIT_CODE] Request user instance or container exit\n" - " switch-root ROOT [INIT] Change to a different root file system\n" - " suspend Suspend the system\n" - " hibernate Hibernate the system\n" - " hybrid-sleep Hibernate and suspend the system\n", - program_invocation_short_name); -} - -static void halt_help(void) { - printf("%s [OPTIONS...]%s\n\n" - "%s the system.\n\n" - " --help Show this help\n" - " --halt Halt the machine\n" - " -p --poweroff Switch off the machine\n" - " --reboot Reboot the machine\n" - " -f --force Force immediate halt/power-off/reboot\n" - " -w --wtmp-only Don't halt/power-off/reboot, just write wtmp record\n" - " -d --no-wtmp Don't write wtmp record\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n", - program_invocation_short_name, - arg_action == ACTION_REBOOT ? " [ARG]" : "", - arg_action == ACTION_REBOOT ? "Reboot" : - arg_action == ACTION_POWEROFF ? "Power off" : - "Halt"); -} - -static void shutdown_help(void) { - printf("%s [OPTIONS...] [TIME] [WALL...]\n\n" - "Shut down the system.\n\n" - " --help Show this help\n" - " -H --halt Halt the machine\n" - " -P --poweroff Power-off the machine\n" - " -r --reboot Reboot the machine\n" - " -h Equivalent to --poweroff, overridden by --halt\n" - " -k Don't halt/power-off/reboot, just send warnings\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n" - " -c Cancel a pending shutdown\n", - program_invocation_short_name); -} - -static void telinit_help(void) { - printf("%s [OPTIONS...] {COMMAND}\n\n" - "Send control commands to the init daemon.\n\n" - " --help Show this help\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n\n" - "Commands:\n" - " 0 Power-off the machine\n" - " 6 Reboot the machine\n" - " 2, 3, 4, 5 Start runlevelX.target unit\n" - " 1, s, S Enter rescue mode\n" - " q, Q Reload init daemon configuration\n" - " u, U Reexecute init daemon\n", - program_invocation_short_name); -} - -static void runlevel_help(void) { - printf("%s [OPTIONS...]\n\n" - "Prints the previous and current runlevel of the init system.\n\n" - " --help Show this help\n", - program_invocation_short_name); -} - -static void help_types(void) { - int i; - - if (!arg_no_legend) - puts("Available unit types:"); - for (i = 0; i < _UNIT_TYPE_MAX; i++) - puts(unit_type_to_string(i)); -} - -static void help_states(void) { - int i; - - if (!arg_no_legend) - puts("Available unit load states:"); - for (i = 0; i < _UNIT_LOAD_STATE_MAX; i++) - puts(unit_load_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable unit active states:"); - for (i = 0; i < _UNIT_ACTIVE_STATE_MAX; i++) - puts(unit_active_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable automount unit substates:"); - for (i = 0; i < _AUTOMOUNT_STATE_MAX; i++) - puts(automount_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable busname unit substates:"); - for (i = 0; i < _BUSNAME_STATE_MAX; i++) - puts(busname_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable device unit substates:"); - for (i = 0; i < _DEVICE_STATE_MAX; i++) - puts(device_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable mount unit substates:"); - for (i = 0; i < _MOUNT_STATE_MAX; i++) - puts(mount_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable path unit substates:"); - for (i = 0; i < _PATH_STATE_MAX; i++) - puts(path_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable scope unit substates:"); - for (i = 0; i < _SCOPE_STATE_MAX; i++) - puts(scope_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable service unit substates:"); - for (i = 0; i < _SERVICE_STATE_MAX; i++) - puts(service_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable slice unit substates:"); - for (i = 0; i < _SLICE_STATE_MAX; i++) - puts(slice_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable socket unit substates:"); - for (i = 0; i < _SOCKET_STATE_MAX; i++) - puts(socket_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable swap unit substates:"); - for (i = 0; i < _SWAP_STATE_MAX; i++) - puts(swap_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable target unit substates:"); - for (i = 0; i < _TARGET_STATE_MAX; i++) - puts(target_state_to_string(i)); - - if (!arg_no_legend) - puts("\nAvailable timer unit substates:"); - for (i = 0; i < _TIMER_STATE_MAX; i++) - puts(timer_state_to_string(i)); -} - -static int systemctl_parse_argv(int argc, char *argv[]) { - - enum { - ARG_FAIL = 0x100, - ARG_REVERSE, - ARG_AFTER, - ARG_BEFORE, - ARG_SHOW_TYPES, - ARG_IRREVERSIBLE, - ARG_IGNORE_DEPENDENCIES, - ARG_VERSION, - ARG_USER, - ARG_SYSTEM, - ARG_GLOBAL, - ARG_NO_BLOCK, - ARG_NO_LEGEND, - ARG_NO_PAGER, - ARG_NO_WALL, - ARG_ROOT, - ARG_NO_RELOAD, - ARG_KILL_WHO, - ARG_NO_ASK_PASSWORD, - ARG_FAILED, - ARG_RUNTIME, - ARG_FORCE, - ARG_PLAIN, - ARG_STATE, - ARG_JOB_MODE, - ARG_PRESET_MODE, - ARG_FIRMWARE_SETUP, - ARG_NOW, - ARG_MESSAGE, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "type", required_argument, NULL, 't' }, - { "property", required_argument, NULL, 'p' }, - { "all", no_argument, NULL, 'a' }, - { "reverse", no_argument, NULL, ARG_REVERSE }, - { "after", no_argument, NULL, ARG_AFTER }, - { "before", no_argument, NULL, ARG_BEFORE }, - { "show-types", no_argument, NULL, ARG_SHOW_TYPES }, - { "failed", no_argument, NULL, ARG_FAILED }, /* compatibility only */ - { "full", no_argument, NULL, 'l' }, - { "job-mode", required_argument, NULL, ARG_JOB_MODE }, - { "fail", no_argument, NULL, ARG_FAIL }, /* compatibility only */ - { "irreversible", no_argument, NULL, ARG_IRREVERSIBLE }, /* compatibility only */ - { "ignore-dependencies", no_argument, NULL, ARG_IGNORE_DEPENDENCIES }, /* compatibility only */ - { "ignore-inhibitors", no_argument, NULL, 'i' }, - { "user", no_argument, NULL, ARG_USER }, - { "system", no_argument, NULL, ARG_SYSTEM }, - { "global", no_argument, NULL, ARG_GLOBAL }, - { "no-block", no_argument, NULL, ARG_NO_BLOCK }, - { "no-legend", no_argument, NULL, ARG_NO_LEGEND }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - { "quiet", no_argument, NULL, 'q' }, - { "root", required_argument, NULL, ARG_ROOT }, - { "force", no_argument, NULL, ARG_FORCE }, - { "no-reload", no_argument, NULL, ARG_NO_RELOAD }, - { "kill-who", required_argument, NULL, ARG_KILL_WHO }, - { "signal", required_argument, NULL, 's' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "runtime", no_argument, NULL, ARG_RUNTIME }, - { "lines", required_argument, NULL, 'n' }, - { "output", required_argument, NULL, 'o' }, - { "plain", no_argument, NULL, ARG_PLAIN }, - { "state", required_argument, NULL, ARG_STATE }, - { "recursive", no_argument, NULL, 'r' }, - { "preset-mode", required_argument, NULL, ARG_PRESET_MODE }, - { "firmware-setup", no_argument, NULL, ARG_FIRMWARE_SETUP }, - { "now", no_argument, NULL, ARG_NOW }, - { "message", required_argument, NULL, ARG_MESSAGE }, - {} - }; - - const char *p; - int c, r; - - assert(argc >= 0); - assert(argv); - - /* we default to allowing interactive authorization only in systemctl (not in the legacy commands) */ - arg_ask_password = true; - - while ((c = getopt_long(argc, argv, "ht:p:alqfs:H:M:n:o:ir", options, NULL)) >= 0) - - switch (c) { - - case 'h': - systemctl_help(); - return 0; - - case ARG_VERSION: - return version(); - - case 't': { - if (isempty(optarg)) { - log_error("--type requires arguments."); - return -EINVAL; - } - - p = optarg; - for(;;) { - _cleanup_free_ char *type = NULL; - - r = extract_first_word(&p, &type, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse type: %s", optarg); - - if (r == 0) - break; - - if (streq(type, "help")) { - help_types(); - return 0; - } - - if (unit_type_from_string(type) >= 0) { - if (strv_push(&arg_types, type) < 0) - return log_oom(); - type = NULL; - continue; - } - - /* It's much nicer to use --state= for - * load states, but let's support this - * in --types= too for compatibility - * with old versions */ - if (unit_load_state_from_string(type) >= 0) { - if (strv_push(&arg_states, type) < 0) - return log_oom(); - type = NULL; - continue; - } - - log_error("Unknown unit type or load state '%s'.", type); - log_info("Use -t help to see a list of allowed values."); - return -EINVAL; - } - - break; - } - - case 'p': { - /* Make sure that if the empty property list - was specified, we won't show any properties. */ - if (isempty(optarg) && !arg_properties) { - arg_properties = new0(char*, 1); - if (!arg_properties) - return log_oom(); - } else { - p = optarg; - for(;;) { - _cleanup_free_ char *prop = NULL; - - r = extract_first_word(&p, &prop, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse property: %s", optarg); - - if (r == 0) - break; - - if (strv_push(&arg_properties, prop) < 0) - return log_oom(); - - prop = NULL; - } - } - - /* If the user asked for a particular - * property, show it to him, even if it is - * empty. */ - arg_all = true; - - break; - } - - case 'a': - arg_all = true; - break; - - case ARG_REVERSE: - arg_dependency = DEPENDENCY_REVERSE; - break; - - case ARG_AFTER: - arg_dependency = DEPENDENCY_AFTER; - break; - - case ARG_BEFORE: - arg_dependency = DEPENDENCY_BEFORE; - break; - - case ARG_SHOW_TYPES: - arg_show_types = true; - break; - - case ARG_JOB_MODE: - arg_job_mode = optarg; - break; - - case ARG_FAIL: - arg_job_mode = "fail"; - break; - - case ARG_IRREVERSIBLE: - arg_job_mode = "replace-irreversibly"; - break; - - case ARG_IGNORE_DEPENDENCIES: - arg_job_mode = "ignore-dependencies"; - break; - - case ARG_USER: - arg_scope = UNIT_FILE_USER; - break; - - case ARG_SYSTEM: - arg_scope = UNIT_FILE_SYSTEM; - break; - - case ARG_GLOBAL: - arg_scope = UNIT_FILE_GLOBAL; - break; - - case ARG_NO_BLOCK: - arg_no_block = true; - break; - - case ARG_NO_LEGEND: - arg_no_legend = true; - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case ARG_NO_WALL: - arg_no_wall = true; - break; - - case ARG_ROOT: - r = parse_path_argument_and_warn(optarg, true, &arg_root); - if (r < 0) - return r; - break; - - case 'l': - arg_full = true; - break; - - case ARG_FAILED: - if (strv_extend(&arg_states, "failed") < 0) - return log_oom(); - - break; - - case 'q': - arg_quiet = true; - break; - - case ARG_FORCE: - arg_force ++; - break; - - case 'f': - arg_force ++; - break; - - case ARG_NO_RELOAD: - arg_no_reload = true; - break; - - case ARG_KILL_WHO: - arg_kill_who = optarg; - break; - - case 's': - arg_signal = signal_from_string_try_harder(optarg); - if (arg_signal < 0) { - log_error("Failed to parse signal string %s.", optarg); - return -EINVAL; - } - break; - - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case ARG_RUNTIME: - arg_runtime = true; - break; - - case 'n': - if (safe_atou(optarg, &arg_lines) < 0) { - log_error("Failed to parse lines '%s'", optarg); - return -EINVAL; - } - break; - - case 'o': - arg_output = output_mode_from_string(optarg); - if (arg_output < 0) { - log_error("Unknown output '%s'.", optarg); - return -EINVAL; - } - break; - - case 'i': - arg_ignore_inhibitors = true; - break; - - case ARG_PLAIN: - arg_plain = true; - break; - - case ARG_FIRMWARE_SETUP: - arg_firmware_setup = true; - break; - - case ARG_STATE: { - if (isempty(optarg)) { - log_error("--signal requires arguments."); - return -EINVAL; - } - - p = optarg; - for(;;) { - _cleanup_free_ char *s = NULL; - - r = extract_first_word(&p, &s, ",", 0); - if (r < 0) - return log_error_errno(r, "Failed to parse signal: %s", optarg); - - if (r == 0) - break; - - if (streq(s, "help")) { - help_states(); - return 0; - } - - if (strv_push(&arg_states, s) < 0) - return log_oom(); - - s = NULL; - } - break; - } - - case 'r': - if (geteuid() != 0) { - log_error("--recursive requires root privileges."); - return -EPERM; - } - - arg_recursive = true; - break; - - case ARG_PRESET_MODE: - - arg_preset_mode = unit_file_preset_mode_from_string(optarg); - if (arg_preset_mode < 0) { - log_error("Failed to parse preset mode: %s.", optarg); - return -EINVAL; - } - - break; - - case ARG_NOW: - arg_now = true; - break; - - case ARG_MESSAGE: - if (strv_extend(&arg_wall, optarg) < 0) - return log_oom(); - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (arg_transport != BUS_TRANSPORT_LOCAL && arg_scope != UNIT_FILE_SYSTEM) { - log_error("Cannot access user instance remotely."); - return -EINVAL; - } - - return 1; -} - -static int halt_parse_argv(int argc, char *argv[]) { - - enum { - ARG_HELP = 0x100, - ARG_HALT, - ARG_REBOOT, - ARG_NO_WALL - }; - - static const struct option options[] = { - { "help", no_argument, NULL, ARG_HELP }, - { "halt", no_argument, NULL, ARG_HALT }, - { "poweroff", no_argument, NULL, 'p' }, - { "reboot", no_argument, NULL, ARG_REBOOT }, - { "force", no_argument, NULL, 'f' }, - { "wtmp-only", no_argument, NULL, 'w' }, - { "no-wtmp", no_argument, NULL, 'd' }, - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - {} - }; - - int c, r, runlevel; - - assert(argc >= 0); - assert(argv); - - if (utmp_get_runlevel(&runlevel, NULL) >= 0) - if (runlevel == '0' || runlevel == '6') - arg_force = 2; - - while ((c = getopt_long(argc, argv, "pfwdnih", options, NULL)) >= 0) - switch (c) { - - case ARG_HELP: - halt_help(); - return 0; - - case ARG_HALT: - arg_action = ACTION_HALT; - break; - - case 'p': - if (arg_action != ACTION_REBOOT) - arg_action = ACTION_POWEROFF; - break; - - case ARG_REBOOT: - arg_action = ACTION_REBOOT; - break; - - case 'f': - arg_force = 2; - break; - - case 'w': - arg_dry = true; - break; - - case 'd': - arg_no_wtmp = true; - break; - - case ARG_NO_WALL: - arg_no_wall = true; - break; - - case 'i': - case 'h': - case 'n': - /* Compatibility nops */ - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (arg_action == ACTION_REBOOT && (argc == optind || argc == optind + 1)) { - r = update_reboot_param_file(argc == optind + 1 ? argv[optind] : NULL); - if (r < 0) - return r; - } else if (optind < argc) { - log_error("Too many arguments."); - return -EINVAL; - } - - return 1; -} - -static int parse_shutdown_time_spec(const char *t, usec_t *_u) { - assert(t); - assert(_u); - - if (streq(t, "now")) - *_u = 0; - else if (!strchr(t, ':')) { - uint64_t u; - - if (safe_atou64(t, &u) < 0) - return -EINVAL; - - *_u = now(CLOCK_REALTIME) + USEC_PER_MINUTE * u; - } else { - char *e = NULL; - long hour, minute; - struct tm tm = {}; - time_t s; - usec_t n; - - errno = 0; - hour = strtol(t, &e, 10); - if (errno > 0 || *e != ':' || hour < 0 || hour > 23) - return -EINVAL; - - minute = strtol(e+1, &e, 10); - if (errno > 0 || *e != 0 || minute < 0 || minute > 59) - return -EINVAL; - - n = now(CLOCK_REALTIME); - s = (time_t) (n / USEC_PER_SEC); - - assert_se(localtime_r(&s, &tm)); - - tm.tm_hour = (int) hour; - tm.tm_min = (int) minute; - tm.tm_sec = 0; - - assert_se(s = mktime(&tm)); - - *_u = (usec_t) s * USEC_PER_SEC; - - while (*_u <= n) - *_u += USEC_PER_DAY; - } - - return 0; -} - -static int shutdown_parse_argv(int argc, char *argv[]) { - - enum { - ARG_HELP = 0x100, - ARG_NO_WALL - }; - - static const struct option options[] = { - { "help", no_argument, NULL, ARG_HELP }, - { "halt", no_argument, NULL, 'H' }, - { "poweroff", no_argument, NULL, 'P' }, - { "reboot", no_argument, NULL, 'r' }, - { "kexec", no_argument, NULL, 'K' }, /* not documented extension */ - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - {} - }; - - char **wall = NULL; - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "HPrhkKtafFc", options, NULL)) >= 0) - switch (c) { - - case ARG_HELP: - shutdown_help(); - return 0; - - case 'H': - arg_action = ACTION_HALT; - break; - - case 'P': - arg_action = ACTION_POWEROFF; - break; - - case 'r': - if (kexec_loaded()) - arg_action = ACTION_KEXEC; - else - arg_action = ACTION_REBOOT; - break; - - case 'K': - arg_action = ACTION_KEXEC; - break; - - case 'h': - if (arg_action != ACTION_HALT) - arg_action = ACTION_POWEROFF; - break; - - case 'k': - arg_dry = true; - break; - - case ARG_NO_WALL: - arg_no_wall = true; - break; - - case 't': - case 'a': - case 'f': - case 'F': - /* Compatibility nops */ - break; - - case 'c': - arg_action = ACTION_CANCEL_SHUTDOWN; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (argc > optind && arg_action != ACTION_CANCEL_SHUTDOWN) { - r = parse_shutdown_time_spec(argv[optind], &arg_when); - if (r < 0) { - log_error("Failed to parse time specification: %s", argv[optind]); - return r; - } - } else - arg_when = now(CLOCK_REALTIME) + USEC_PER_MINUTE; - - if (argc > optind && arg_action == ACTION_CANCEL_SHUTDOWN) - /* No time argument for shutdown cancel */ - wall = argv + optind; - else if (argc > optind + 1) - /* We skip the time argument */ - wall = argv + optind + 1; - - if (wall) { - arg_wall = strv_copy(wall); - if (!arg_wall) - return log_oom(); - } - - optind = argc; - - return 1; -} - -static int telinit_parse_argv(int argc, char *argv[]) { - - enum { - ARG_HELP = 0x100, - ARG_NO_WALL - }; - - static const struct option options[] = { - { "help", no_argument, NULL, ARG_HELP }, - { "no-wall", no_argument, NULL, ARG_NO_WALL }, - {} - }; - - static const struct { - char from; - enum action to; - } table[] = { - { '0', ACTION_POWEROFF }, - { '6', ACTION_REBOOT }, - { '1', ACTION_RESCUE }, - { '2', ACTION_RUNLEVEL2 }, - { '3', ACTION_RUNLEVEL3 }, - { '4', ACTION_RUNLEVEL4 }, - { '5', ACTION_RUNLEVEL5 }, - { 's', ACTION_RESCUE }, - { 'S', ACTION_RESCUE }, - { 'q', ACTION_RELOAD }, - { 'Q', ACTION_RELOAD }, - { 'u', ACTION_REEXEC }, - { 'U', ACTION_REEXEC } - }; - - unsigned i; - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) - switch (c) { - - case ARG_HELP: - telinit_help(); - return 0; - - case ARG_NO_WALL: - arg_no_wall = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind >= argc) { - log_error("%s: required argument missing.", program_invocation_short_name); - return -EINVAL; - } - - if (optind + 1 < argc) { - log_error("Too many arguments."); - return -EINVAL; - } - - if (strlen(argv[optind]) != 1) { - log_error("Expected single character argument."); - return -EINVAL; - } - - for (i = 0; i < ELEMENTSOF(table); i++) - if (table[i].from == argv[optind][0]) - break; - - if (i >= ELEMENTSOF(table)) { - log_error("Unknown command '%s'.", argv[optind]); - return -EINVAL; - } - - arg_action = table[i].to; - - optind ++; - - return 1; -} - -static int runlevel_parse_argv(int argc, char *argv[]) { - - enum { - ARG_HELP = 0x100, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, ARG_HELP }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "", options, NULL)) >= 0) - switch (c) { - - case ARG_HELP: - runlevel_help(); - return 0; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind < argc) { - log_error("Too many arguments."); - return -EINVAL; - } - - return 1; -} - -static int parse_argv(int argc, char *argv[]) { - assert(argc >= 0); - assert(argv); - - if (program_invocation_short_name) { - - if (strstr(program_invocation_short_name, "halt")) { - arg_action = ACTION_HALT; - return halt_parse_argv(argc, argv); - } else if (strstr(program_invocation_short_name, "poweroff")) { - arg_action = ACTION_POWEROFF; - return halt_parse_argv(argc, argv); - } else if (strstr(program_invocation_short_name, "reboot")) { - if (kexec_loaded()) - arg_action = ACTION_KEXEC; - else - arg_action = ACTION_REBOOT; - return halt_parse_argv(argc, argv); - } else if (strstr(program_invocation_short_name, "shutdown")) { - arg_action = ACTION_POWEROFF; - return shutdown_parse_argv(argc, argv); - } else if (strstr(program_invocation_short_name, "init")) { - - if (sd_booted() > 0) { - arg_action = _ACTION_INVALID; - return telinit_parse_argv(argc, argv); - } else { - /* Hmm, so some other init system is - * running, we need to forward this - * request to it. For now we simply - * guess that it is Upstart. */ - - execv(TELINIT, argv); - - log_error("Couldn't find an alternative telinit implementation to spawn."); - return -EIO; - } - - } else if (strstr(program_invocation_short_name, "runlevel")) { - arg_action = ACTION_RUNLEVEL; - return runlevel_parse_argv(argc, argv); - } - } - - arg_action = ACTION_SYSTEMCTL; - return systemctl_parse_argv(argc, argv); -} - -_pure_ static int action_to_runlevel(void) { - - static const char table[_ACTION_MAX] = { - [ACTION_HALT] = '0', - [ACTION_POWEROFF] = '0', - [ACTION_REBOOT] = '6', - [ACTION_RUNLEVEL2] = '2', - [ACTION_RUNLEVEL3] = '3', - [ACTION_RUNLEVEL4] = '4', - [ACTION_RUNLEVEL5] = '5', - [ACTION_RESCUE] = '1' - }; - - assert(arg_action < _ACTION_MAX); - - return table[arg_action]; -} - -static int talk_initctl(void) { -#ifdef HAVE_SYSV_COMPAT - struct init_request request = { - .magic = INIT_MAGIC, - .sleeptime = 0, - .cmd = INIT_CMD_RUNLVL - }; - - _cleanup_close_ int fd = -1; - char rl; - int r; - - rl = action_to_runlevel(); - if (!rl) - return 0; - - request.runlevel = rl; - - fd = open(INIT_FIFO, O_WRONLY|O_NDELAY|O_CLOEXEC|O_NOCTTY); - if (fd < 0) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open "INIT_FIFO": %m"); - } - - r = loop_write(fd, &request, sizeof(request), false); - if (r < 0) - return log_error_errno(r, "Failed to write to "INIT_FIFO": %m"); - - return 1; -#else - return 0; -#endif -} - -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 }, - { "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 }, - {} - }; - - return dispatch_verb(argc, argv, verbs, NULL); -} - -static int reload_with_fallback(void) { - - /* First, try systemd via D-Bus. */ - if (daemon_reload(0, NULL, NULL) >= 0) - return 0; - - /* Nothing else worked, so let's try signals */ - assert(arg_action == ACTION_RELOAD || arg_action == ACTION_REEXEC); - - if (kill(1, arg_action == ACTION_RELOAD ? SIGHUP : SIGTERM) < 0) - return log_error_errno(errno, "kill() failed: %m"); - - return 0; -} - -static int start_with_fallback(void) { - - /* First, try systemd via D-Bus. */ - if (start_unit(0, NULL, NULL) >= 0) - return 0; - - /* Nothing else worked, so let's try - * /dev/initctl */ - if (talk_initctl() > 0) - return 0; - - log_error("Failed to talk to init daemon."); - return -EIO; -} - -static int halt_now(enum action a) { - - /* The kernel will automaticall flush ATA disks and suchlike - * on reboot(), but the file systems need to be synce'd - * explicitly in advance. */ - (void) sync(); - - /* Make sure C-A-D is handled by the kernel from this point - * on... */ - (void) reboot(RB_ENABLE_CAD); - - switch (a) { - - case ACTION_HALT: - log_info("Halting."); - (void) reboot(RB_HALT_SYSTEM); - return -errno; - - case ACTION_POWEROFF: - log_info("Powering off."); - (void) reboot(RB_POWER_OFF); - return -errno; - - case ACTION_KEXEC: - case ACTION_REBOOT: { - _cleanup_free_ char *param = NULL; - - if (read_one_line_file(REBOOT_PARAM_FILE, ¶m) >= 0) { - log_info("Rebooting with argument '%s'.", param); - (void) syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, param); - } - - log_info("Rebooting."); - (void) reboot(RB_AUTOBOOT); - return -errno; - } - - default: - assert_not_reached("Unknown action."); - } -} - -static int logind_schedule_shutdown(void) { - -#ifdef HAVE_LOGIND - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - char date[FORMAT_TIMESTAMP_MAX]; - const char *action; - sd_bus *bus; - int r; - - (void) logind_set_wall_message(); - - r = acquire_bus(BUS_FULL, &bus); - if (r < 0) - return r; - - switch (arg_action) { - case ACTION_HALT: - action = "halt"; - break; - case ACTION_POWEROFF: - action = "poweroff"; - break; - case ACTION_KEXEC: - action = "kexec"; - break; - case ACTION_EXIT: - action = "exit"; - break; - case ACTION_REBOOT: - default: - action = "reboot"; - break; - } - - if (arg_dry) - action = strjoina("dry-", action); - - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "ScheduleShutdown", - &error, - NULL, - "st", - action, - arg_when); - if (r < 0) - return log_warning_errno(r, "Failed to call ScheduleShutdown in logind, proceeding with immediate shutdown: %s", bus_error_message(&error, r)); - - log_info("Shutdown scheduled for %s, use 'shutdown -c' to cancel.", format_timestamp(date, sizeof(date), arg_when)); - return 0; -#else - log_error("Cannot schedule shutdown without logind support, proceeding with immediate shutdown."); - return -ENOSYS; -#endif -} - -static int halt_main(void) { - int r; - - r = logind_check_inhibitors(arg_action); - if (r < 0) - return r; - - if (arg_when > 0) - return logind_schedule_shutdown(); - - if (geteuid() != 0) { - if (arg_dry || arg_force > 0) { - log_error("Must be root."); - return -EPERM; - } - - /* Try logind if we are a normal user and no special - * mode applies. Maybe PolicyKit allows us to shutdown - * the machine. */ - if (IN_SET(arg_action, ACTION_POWEROFF, ACTION_REBOOT)) { - r = logind_reboot(arg_action); - if (r >= 0) - return r; - if (IN_SET(r, -EOPNOTSUPP, -EINPROGRESS)) - /* requested operation is not - * supported on the local system or - * already in progress */ - return r; - /* on all other errors, try low-level operation */ - } - } - - if (!arg_dry && !arg_force) - return start_with_fallback(); - - assert(geteuid() == 0); - - if (!arg_no_wtmp) { - if (sd_booted() > 0) - log_debug("Not writing utmp record, assuming that systemd-update-utmp is used."); - else { - r = utmp_put_shutdown(); - if (r < 0) - log_warning_errno(r, "Failed to write utmp record: %m"); - } - } - - if (arg_dry) - return 0; - - r = halt_now(arg_action); - return log_error_errno(r, "Failed to reboot: %m"); -} - -static int runlevel_main(void) { - int r, runlevel, previous; - - r = utmp_get_runlevel(&runlevel, &previous); - if (r < 0) { - puts("unknown"); - return r; - } - - printf("%c %c\n", - previous <= 0 ? 'N' : previous, - runlevel <= 0 ? 'N' : runlevel); - - return 0; -} - -static int logind_cancel_shutdown(void) { -#ifdef HAVE_LOGIND - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - sd_bus *bus; - int r; - - r = acquire_bus(BUS_FULL, &bus); - if (r < 0) - return r; - - (void) logind_set_wall_message(); - - r = sd_bus_call_method( - bus, - "org.freedesktop.login1", - "/org/freedesktop/login1", - "org.freedesktop.login1.Manager", - "CancelScheduledShutdown", - &error, - NULL, NULL); - if (r < 0) - return log_warning_errno(r, "Failed to talk to logind, shutdown hasn't been cancelled: %s", bus_error_message(&error, r)); - - return 0; -#else - log_error("Not compiled with logind support, cannot cancel scheduled shutdowns."); - return -ENOSYS; -#endif -} - -int main(int argc, char*argv[]) { - int r; - - setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); - - /* Explicitly not on_tty() to avoid setting cached value. - * This becomes relevant for piping output which might be - * ellipsized. */ - original_stdout_is_tty = isatty(STDOUT_FILENO); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (arg_action != ACTION_SYSTEMCTL && running_in_chroot() > 0) { - log_info("Running in chroot, ignoring request."); - r = 0; - goto finish; - } - - /* systemctl_main() will print an error message for the bus - * connection, but only if it needs to */ - - switch (arg_action) { - - case ACTION_SYSTEMCTL: - r = systemctl_main(argc, argv); - break; - - case ACTION_HALT: - case ACTION_POWEROFF: - case ACTION_REBOOT: - case ACTION_KEXEC: - r = halt_main(); - break; - - case ACTION_RUNLEVEL2: - case ACTION_RUNLEVEL3: - case ACTION_RUNLEVEL4: - case ACTION_RUNLEVEL5: - case ACTION_RESCUE: - case ACTION_EMERGENCY: - case ACTION_DEFAULT: - r = start_with_fallback(); - break; - - case ACTION_RELOAD: - case ACTION_REEXEC: - r = reload_with_fallback(); - break; - - case ACTION_CANCEL_SHUTDOWN: - r = logind_cancel_shutdown(); - break; - - case ACTION_RUNLEVEL: - r = runlevel_main(); - break; - - case _ACTION_INVALID: - default: - assert_not_reached("Unknown action"); - } - -finish: - pager_close(); - ask_password_agent_close(); - polkit_agent_close(); - - strv_free(arg_types); - strv_free(arg_states); - strv_free(arg_properties); - - 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/systemctl/systemd-sysv-install.SKELETON b/src/systemctl/systemd-sysv-install.SKELETON deleted file mode 100755 index a53a3e6221..0000000000 --- a/src/systemctl/systemd-sysv-install.SKELETON +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/sh -# This script is called by "systemctl enable/disable" when the given unit is a -# SysV init.d script. It needs to call the distribution's mechanism for -# enabling/disabling those, such as chkconfig, update-rc.d, or similar. This -# can optionally take a --root argument for enabling a SysV init script -# in a chroot or similar. -set -e - -usage() { - echo "Usage: $0 [--root=path] enable|disable|is-enabled " >&2 - exit 1 -} - -# parse options -eval set -- "$(getopt -o r: --long root: -- "$@")" -while true; do - case "$1" in - -r|--root) - ROOT="$2" - shift 2 ;; - --) shift ; break ;; - *) usage ;; - esac -done - -NAME="$2" -[ -n "$NAME" ] || usage - -case "$1" in - enable) - # call the command to enable SysV init script $NAME here - # (consider optional $ROOT) - echo "IMPLEMENT ME: enabling SysV init.d script $NAME" - ;; - disable) - # call the command to disable SysV init script $NAME here - # (consider optional $ROOT) - echo "IMPLEMENT ME: disabling SysV init.d script $NAME" - ;; - is-enabled) - # exit with 0 if $NAME is enabled, non-zero if it is disabled - # (consider optional $ROOT) - echo "IMPLEMENT ME: checking SysV init.d script $NAME" - ;; - *) - usage ;; -esac diff --git a/src/systemd-activate/Makefile b/src/systemd-activate/Makefile new file mode 100644 index 0000000000..133622ffb0 --- /dev/null +++ b/src/systemd-activate/Makefile @@ -0,0 +1,36 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + + +rootlibexec_PROGRAMS += \ + systemd-activate + +systemd_activate_SOURCES = \ + src/activate/activate.c + +systemd_activate_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-activate/activate.c b/src/systemd-activate/activate.c new file mode 100644 index 0000000000..94f99028e5 --- /dev/null +++ b/src/systemd-activate/activate.c @@ -0,0 +1,511 @@ +/*** + This file is part of systemd. + + Copyright 2013 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "signal-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" + +static char** arg_listen = NULL; +static bool arg_accept = false; +static int arg_socket_type = SOCK_STREAM; +static char** arg_args = NULL; +static char** arg_setenv = NULL; +static const char *arg_fdname = NULL; +static bool arg_inetd = false; + +static int add_epoll(int epoll_fd, int fd) { + struct epoll_event ev = { + .events = EPOLLIN + }; + int r; + + assert(epoll_fd >= 0); + assert(fd >= 0); + + ev.data.fd = fd; + r = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev); + if (r < 0) + return log_error_errno(errno, "Failed to add event on epoll fd:%d for fd:%d: %m", epoll_fd, fd); + + return 0; +} + +static int open_sockets(int *epoll_fd, bool accept) { + char **address; + int n, fd, r; + int count = 0; + + n = sd_listen_fds(true); + if (n < 0) + return log_error_errno(n, "Failed to read listening file descriptors from environment: %m"); + if (n > 0) { + log_info("Received %i descriptors via the environment.", n); + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) { + r = fd_cloexec(fd, arg_accept); + if (r < 0) + return r; + + count ++; + } + } + + /* Close logging and all other descriptors */ + if (arg_listen) { + int except[3 + n]; + + for (fd = 0; fd < SD_LISTEN_FDS_START + n; fd++) + except[fd] = fd; + + log_close(); + close_all_fds(except, 3 + n); + } + + /** Note: we leak some fd's on error here. I doesn't matter + * much, since the program will exit immediately anyway, but + * would be a pain to fix. + */ + + STRV_FOREACH(address, arg_listen) { + fd = make_socket_fd(LOG_DEBUG, *address, arg_socket_type, (arg_accept*SOCK_CLOEXEC)); + if (fd < 0) { + log_open(); + return log_error_errno(fd, "Failed to open '%s': %m", *address); + } + + assert(fd == SD_LISTEN_FDS_START + count); + count ++; + } + + if (arg_listen) + log_open(); + + *epoll_fd = epoll_create1(EPOLL_CLOEXEC); + if (*epoll_fd < 0) + return log_error_errno(errno, "Failed to create epoll object: %m"); + + for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + count; fd++) { + _cleanup_free_ char *name = NULL; + + getsockname_pretty(fd, &name); + log_info("Listening on %s as %i.", strna(name), fd); + + r = add_epoll(*epoll_fd, fd); + if (r < 0) + return r; + } + + return count; +} + +static int exec_process(const char* name, char **argv, char **env, int start_fd, int n_fds) { + + _cleanup_strv_free_ char **envp = NULL; + _cleanup_free_ char *joined = NULL; + unsigned n_env = 0, length; + const char *tocopy; + unsigned i; + char **s; + int r; + + if (arg_inetd && n_fds != 1) { + log_error("--inetd only supported for single file descriptors."); + return -EINVAL; + } + + length = strv_length(arg_setenv); + + /* PATH, TERM, HOME, USER, LISTEN_FDS, LISTEN_PID, LISTEN_FDNAMES, NULL */ + envp = new0(char *, length + 8); + if (!envp) + return log_oom(); + + STRV_FOREACH(s, arg_setenv) { + + if (strchr(*s, '=')) { + char *k; + + k = strdup(*s); + if (!k) + return log_oom(); + + envp[n_env++] = k; + } else { + _cleanup_free_ char *p; + const char *n; + + p = strappend(*s, "="); + if (!p) + return log_oom(); + + n = strv_find_prefix(env, p); + if (!n) + continue; + + envp[n_env] = strdup(n); + if (!envp[n_env]) + return log_oom(); + + n_env ++; + } + } + + FOREACH_STRING(tocopy, "TERM=", "PATH=", "USER=", "HOME=") { + const char *n; + + n = strv_find_prefix(env, tocopy); + if (!n) + continue; + + envp[n_env] = strdup(n); + if (!envp[n_env]) + return log_oom(); + + n_env ++; + } + + if (arg_inetd) { + assert(n_fds == 1); + + r = dup2(start_fd, STDIN_FILENO); + if (r < 0) + return log_error_errno(errno, "Failed to dup connection to stdin: %m"); + + r = dup2(start_fd, STDOUT_FILENO); + if (r < 0) + return log_error_errno(errno, "Failed to dup connection to stdout: %m"); + + start_fd = safe_close(start_fd); + } else { + if (start_fd != SD_LISTEN_FDS_START) { + assert(n_fds == 1); + + r = dup2(start_fd, SD_LISTEN_FDS_START); + if (r < 0) + return log_error_errno(errno, "Failed to dup connection: %m"); + + safe_close(start_fd); + start_fd = SD_LISTEN_FDS_START; + } + + if (asprintf((char**)(envp + n_env++), "LISTEN_FDS=%i", n_fds) < 0) + return log_oom(); + + if (asprintf((char**)(envp + n_env++), "LISTEN_PID=" PID_FMT, getpid()) < 0) + return log_oom(); + + if (arg_fdname) { + char *e; + + e = strappend("LISTEN_FDNAMES=", arg_fdname); + if (!e) + return log_oom(); + + for (i = 1; i < (unsigned) n_fds; i++) { + char *c; + + c = strjoin(e, ":", arg_fdname, NULL); + if (!c) { + free(e); + return log_oom(); + } + + free(e); + e = c; + } + + envp[n_env++] = e; + } + } + + joined = strv_join(argv, " "); + if (!joined) + return log_oom(); + + log_info("Execing %s (%s)", name, joined); + execvpe(name, argv, envp); + + return log_error_errno(errno, "Failed to execp %s (%s): %m", name, joined); +} + +static int fork_and_exec_process(const char* child, char** argv, char **env, int fd) { + _cleanup_free_ char *joined = NULL; + pid_t parent_pid, child_pid; + + joined = strv_join(argv, " "); + if (!joined) + return log_oom(); + + parent_pid = getpid(); + + child_pid = fork(); + if (child_pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + + /* In the child */ + if (child_pid == 0) { + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + /* Make sure the child goes away when the parent dies */ + if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0) + _exit(EXIT_FAILURE); + + /* Check whether our parent died before we were able + * to set the death signal */ + if (getppid() != parent_pid) + _exit(EXIT_SUCCESS); + + exec_process(child, argv, env, fd, 1); + _exit(EXIT_FAILURE); + } + + log_info("Spawned %s (%s) as PID %d", child, joined, child_pid); + return 0; +} + +static int do_accept(const char* name, char **argv, char **envp, int fd) { + _cleanup_free_ char *local = NULL, *peer = NULL; + _cleanup_close_ int fd_accepted = -1; + + fd_accepted = accept4(fd, NULL, NULL, 0); + if (fd_accepted < 0) + return log_error_errno(errno, "Failed to accept connection on fd:%d: %m", fd); + + getsockname_pretty(fd_accepted, &local); + getpeername_pretty(fd_accepted, true, &peer); + log_info("Connection from %s to %s", strna(peer), strna(local)); + + return fork_and_exec_process(name, argv, envp, fd_accepted); +} + +/* SIGCHLD handler. */ +static void sigchld_hdl(int sig, siginfo_t *t, void *data) { + PROTECT_ERRNO; + + log_info("Child %d died with code %d", t->si_pid, t->si_status); + + /* Wait for a dead child. */ + (void) waitpid(t->si_pid, NULL, 0); +} + +static int install_chld_handler(void) { + static const struct sigaction act = { + .sa_flags = SA_SIGINFO, + .sa_sigaction = sigchld_hdl, + }; + + int r; + + r = sigaction(SIGCHLD, &act, 0); + if (r < 0) + return log_error_errno(errno, "Failed to install SIGCHLD handler: %m"); + + return 0; +} + +static void help(void) { + printf("%s [OPTIONS...]\n\n" + "Listen on sockets and launch child on connection.\n\n" + "Options:\n" + " -h --help Show this help and exit\n" + " --version Print version string and exit\n" + " -l --listen=ADDR Listen for raw connections at ADDR\n" + " -d --datagram Listen on datagram instead of stream socket\n" + " --seqpacket Listen on SOCK_SEQPACKET instead of stream socket\n" + " -a --accept Spawn separate child for each connection\n" + " -E --setenv=NAME[=VALUE] Pass an environment variable to children\n" + " --inetd Enable inetd file descriptor passing protocol\n" + "\n" + "Note: file descriptors from sd_listen_fds() will be passed through.\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_FDNAME, + ARG_SEQPACKET, + ARG_INETD, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "datagram", no_argument, NULL, 'd' }, + { "seqpacket", no_argument, NULL, ARG_SEQPACKET }, + { "listen", required_argument, NULL, 'l' }, + { "accept", no_argument, NULL, 'a' }, + { "setenv", required_argument, NULL, 'E' }, + { "environment", required_argument, NULL, 'E' }, /* legacy alias */ + { "fdname", required_argument, NULL, ARG_FDNAME }, + { "inetd", no_argument, NULL, ARG_INETD }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+hl:aEd", options, NULL)) >= 0) + switch(c) { + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case 'l': + r = strv_extend(&arg_listen, optarg); + if (r < 0) + return log_oom(); + + break; + + case 'd': + if (arg_socket_type == SOCK_SEQPACKET) { + log_error("--datagram may not be combined with --seqpacket."); + return -EINVAL; + } + + arg_socket_type = SOCK_DGRAM; + break; + + case ARG_SEQPACKET: + if (arg_socket_type == SOCK_DGRAM) { + log_error("--seqpacket may not be combined with --datagram."); + return -EINVAL; + } + + arg_socket_type = SOCK_SEQPACKET; + break; + + case 'a': + arg_accept = true; + break; + + case 'E': + r = strv_extend(&arg_setenv, optarg); + if (r < 0) + return log_oom(); + + break; + + case ARG_FDNAME: + if (!fdname_is_valid(optarg)) { + log_error("File descriptor name %s is not valid, refusing.", optarg); + return -EINVAL; + } + + arg_fdname = optarg; + break; + + case ARG_INETD: + arg_inetd = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind == argc) { + log_error("%s: command to execute is missing.", + program_invocation_short_name); + return -EINVAL; + } + + if (arg_socket_type == SOCK_DGRAM && arg_accept) { + log_error("Datagram sockets do not accept connections. " + "The --datagram and --accept options may not be combined."); + return -EINVAL; + } + + arg_args = argv + optind; + + return 1 /* work to do */; +} + +int main(int argc, char **argv, char **envp) { + int r, n; + int epoll_fd = -1; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE; + + r = install_chld_handler(); + if (r < 0) + return EXIT_FAILURE; + + n = open_sockets(&epoll_fd, arg_accept); + if (n < 0) + return EXIT_FAILURE; + if (n == 0) { + log_error("No sockets to listen on specified or passed in."); + return EXIT_FAILURE; + } + + for (;;) { + struct epoll_event event; + + r = epoll_wait(epoll_fd, &event, 1, -1); + if (r < 0) { + if (errno == EINTR) + continue; + + log_error_errno(errno, "epoll_wait() failed: %m"); + return EXIT_FAILURE; + } + + log_info("Communication attempt on fd %i.", event.data.fd); + if (arg_accept) { + r = do_accept(argv[optind], argv + optind, envp, event.data.fd); + if (r < 0) + return EXIT_FAILURE; + } else + break; + } + + exec_process(argv[optind], argv + optind, envp, SD_LISTEN_FDS_START, n); + + return EXIT_SUCCESS; +} diff --git a/src/systemd-activate/systemd-activate.xml b/src/systemd-activate/systemd-activate.xml new file mode 100644 index 0000000000..995e6eecce --- /dev/null +++ b/src/systemd-activate/systemd-activate.xml @@ -0,0 +1,204 @@ + + + + + + + + + systemd-activate + systemd + + + + Developer + Zbigniew + Jędrzejewski-Szmek + zbyszek@in.waw.pl + + + + + + systemd-activate + 8 + + + + systemd-activate + Test socket activation of daemons + + + + + /usr/lib/systemd/systemd-activate + OPTIONS + daemon + OPTIONS + + + + + Description + + systemd-activate may be used to launch a socket-activated service binary from the command + line for testing purposes. It may also be used to launch individual instances of the service binary per connection. + + + The daemon to launch and its options should be specified + after options intended for systemd-activate. + + + If the option is given, the socket file descriptor will be used as the standard + input and output of the launched process. Otherwise, standard input and output will be inherited, and sockets will + be passed through file descriptors 3 and higher. Sockets passed through $LISTEN_FDS to + systemd-activate will be passed through to the daemon, in the original positions. Other sockets + specified with will use consecutive descriptors. By default, + systemd-activate listens on a stream socket, use and + to listen on datagram or sequential packet sockets instead (see below). + + + + + Options + + + + + + Listen on this address. + Takes a string like 2000 or + 127.0.0.1:2001. + + + + + + + + Launch an instance of the service binary for each connection and pass the connection + socket. + + + + + + + Listen on a datagram socket (SOCK_DGRAM), instead of a stream socket + (SOCK_STREAM). May not be combined with . + + + + + + Listen on a sequential packet socket (SOCK_SEQPACKET), instead of a stream + socket (SOCK_STREAM). May not be combined with + . + + + + + + Use the inetd protocol for passing file descriptors, i.e. as standard input and standard + output, instead of the new-style protocol for passing file descriptors using $LISTEN_FDS + (see above). + + + + + + + Add this variable to the environment of the + launched process. If VAR is + followed by =, assume that it is a + variable–value pair. Otherwise, obtain the value from the + environment of systemd-activate itself. + + + + + NAME + + Specify a name for the activation file + descriptors. This is equivalent to setting + FileDescriptorName= in socket unit files, and + enables use of + sd_listen_fds_with_names3. + + + + + + + + + Environment variables + + + $LISTEN_FDS + $LISTEN_PID + $LISTEN_FDNAMES + + See + sd_listen_fds3. + + + + $SYSTEMD_LOG_TARGET + $SYSTEMD_LOG_LEVEL + $SYSTEMD_LOG_COLOR + $SYSTEMD_LOG_LOCATION + + Same as in + systemd1. + + + + + + Examples + + + Run an echo server on port 2000 + + $ /usr/lib/systemd/systemd-activate -l 2000 --inetd -a cat + + + + Run a socket-activated instance of <citerefentry><refentrytitle>systemd-journal-gatewayd</refentrytitle><manvolnum>8</manvolnum></citerefentry> + + $ /usr/lib/systemd/systemd-activate -l 19531 /usr/lib/systemd/systemd-journal-gatewayd + + + + + See Also + + systemd1, + systemd.socket5, + systemd.service5, + sd_listen_fds3, + sd_listen_fds_with_names3, + cat1 + + + diff --git a/src/systemd-analyze/.gitignore b/src/systemd-analyze/.gitignore new file mode 100644 index 0000000000..752ea236c8 --- /dev/null +++ b/src/systemd-analyze/.gitignore @@ -0,0 +1 @@ +/systemd-analyze diff --git a/src/systemd-analyze/Makefile b/src/systemd-analyze/Makefile new file mode 100644 index 0000000000..c8a3d805fd --- /dev/null +++ b/src/systemd-analyze/Makefile @@ -0,0 +1,39 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_analyze_SOURCES = \ + src/analyze/analyze.c \ + src/analyze/analyze-verify.c \ + src/analyze/analyze-verify.h + +systemd_analyze_CFLAGS = \ + $(AM_CFLAGS) \ + $(SECCOMP_CFLAGS) \ + $(MOUNT_CFLAGS) + +systemd_analyze_LDADD = \ + libcore.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-analyze/analyze-verify.c b/src/systemd-analyze/analyze-verify.c new file mode 100644 index 0000000000..d36c8db3d4 --- /dev/null +++ b/src/systemd-analyze/analyze-verify.c @@ -0,0 +1,306 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include + +#include "alloc-util.h" +#include "analyze-verify.h" +#include "bus-error.h" +#include "bus-util.h" +#include "log.h" +#include "manager.h" +#include "pager.h" +#include "path-util.h" +#include "strv.h" +#include "unit-name.h" + +static int prepare_filename(const char *filename, char **ret) { + int r; + const char *name; + _cleanup_free_ char *abspath = NULL; + _cleanup_free_ char *dir = NULL; + _cleanup_free_ char *with_instance = NULL; + char *c; + + assert(filename); + assert(ret); + + r = path_make_absolute_cwd(filename, &abspath); + if (r < 0) + return r; + + name = basename(abspath); + if (!unit_name_is_valid(name, UNIT_NAME_ANY)) + return -EINVAL; + + if (unit_name_is_valid(name, UNIT_NAME_TEMPLATE)) { + r = unit_name_replace_instance(name, "i", &with_instance); + if (r < 0) + return r; + } + + dir = dirname_malloc(abspath); + if (!dir) + return -ENOMEM; + + if (with_instance) + c = path_join(NULL, dir, with_instance); + else + c = path_join(NULL, dir, name); + if (!c) + return -ENOMEM; + + *ret = c; + return 0; +} + +static int generate_path(char **var, char **filenames) { + char **filename; + + _cleanup_strv_free_ char **ans = NULL; + int r; + + STRV_FOREACH(filename, filenames) { + char *t; + + t = dirname_malloc(*filename); + if (!t) + return -ENOMEM; + + r = strv_consume(&ans, t); + if (r < 0) + return r; + } + + assert_se(strv_uniq(ans)); + + r = strv_extend(&ans, ""); + if (r < 0) + return r; + + *var = strv_join(ans, ":"); + if (!*var) + return -ENOMEM; + + return 0; +} + +static int verify_socket(Unit *u) { + int r; + + assert(u); + + if (u->type != UNIT_SOCKET) + return 0; + + /* Cannot run this without the service being around */ + + /* This makes sure instance is created if necessary. */ + r = socket_instantiate_service(SOCKET(u)); + if (r < 0) { + log_unit_error_errno(u, r, "Socket cannot be started, failed to create instance: %m"); + return r; + } + + /* This checks both type of sockets */ + if (UNIT_ISSET(SOCKET(u)->service)) { + Service *service; + + service = SERVICE(UNIT_DEREF(SOCKET(u)->service)); + log_unit_debug(u, "Using %s", UNIT(service)->id); + + if (UNIT(service)->load_state != UNIT_LOADED) { + log_unit_error(u, "Service %s not loaded, %s cannot be started.", UNIT(service)->id, u->id); + return -ENOENT; + } + } + + return 0; +} + +static int verify_executable(Unit *u, ExecCommand *exec) { + if (exec == NULL) + return 0; + + if (access(exec->path, X_OK) < 0) + return log_unit_error_errno(u, errno, "Command %s is not executable: %m", exec->path); + + return 0; +} + +static int verify_executables(Unit *u) { + ExecCommand *exec; + int r = 0, k; + unsigned i; + + assert(u); + + exec = u->type == UNIT_SOCKET ? SOCKET(u)->control_command : + u->type == UNIT_MOUNT ? MOUNT(u)->control_command : + u->type == UNIT_SWAP ? SWAP(u)->control_command : NULL; + k = verify_executable(u, exec); + if (k < 0 && r == 0) + r = k; + + if (u->type == UNIT_SERVICE) + for (i = 0; i < ELEMENTSOF(SERVICE(u)->exec_command); i++) { + k = verify_executable(u, SERVICE(u)->exec_command[i]); + if (k < 0 && r == 0) + r = k; + } + + if (u->type == UNIT_SOCKET) + for (i = 0; i < ELEMENTSOF(SOCKET(u)->exec_command); i++) { + k = verify_executable(u, SOCKET(u)->exec_command[i]); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +static int verify_documentation(Unit *u, bool check_man) { + char **p; + int r = 0, k; + + STRV_FOREACH(p, u->documentation) { + log_unit_debug(u, "Found documentation item: %s", *p); + + if (check_man && startswith(*p, "man:")) { + k = show_man_page(*p + 4, true); + if (k != 0) { + if (k < 0) + log_unit_error_errno(u, r, "Can't show %s: %m", *p); + else { + log_unit_error_errno(u, r, "man %s command failed with code %d", *p + 4, k); + k = -ENOEXEC; + } + if (r == 0) + r = k; + } + } + } + + /* Check remote URLs? */ + + return r; +} + +static int verify_unit(Unit *u, bool check_man) { + _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; + int r, k; + + assert(u); + + if (log_get_max_level() >= LOG_DEBUG) + unit_dump(u, stdout, "\t"); + + log_unit_debug(u, "Creating %s/start job", u->id); + r = manager_add_job(u->manager, JOB_START, u, JOB_REPLACE, &err, NULL); + if (r < 0) + log_unit_error_errno(u, r, "Failed to create %s/start: %s", u->id, bus_error_message(&err, r)); + + k = verify_socket(u); + if (k < 0 && r == 0) + r = k; + + k = verify_executables(u); + if (k < 0 && r == 0) + r = k; + + k = verify_documentation(u, check_man); + if (k < 0 && r == 0) + r = k; + + return r; +} + +int verify_units(char **filenames, ManagerRunningAs running_as, bool check_man) { + _cleanup_(sd_bus_error_free) sd_bus_error err = SD_BUS_ERROR_NULL; + Manager *m = NULL; + FILE *serial = NULL; + FDSet *fdset = NULL; + + _cleanup_free_ char *var = NULL; + + char **filename; + int r = 0, k; + + Unit *units[strv_length(filenames)]; + int i, count = 0; + + if (strv_isempty(filenames)) + return 0; + + /* set the path */ + r = generate_path(&var, filenames); + if (r < 0) + return log_error_errno(r, "Failed to generate unit load path: %m"); + + assert_se(set_unit_path(var) >= 0); + + r = manager_new(running_as, true, &m); + if (r < 0) + return log_error_errno(r, "Failed to initialize manager: %m"); + + log_debug("Starting manager..."); + + r = manager_startup(m, serial, fdset); + if (r < 0) { + log_error_errno(r, "Failed to start manager: %m"); + goto finish; + } + + manager_clear_jobs(m); + + log_debug("Loading remaining units from the command line..."); + + STRV_FOREACH(filename, filenames) { + _cleanup_free_ char *prepared = NULL; + + log_debug("Handling %s...", *filename); + + k = prepare_filename(*filename, &prepared); + if (k < 0) { + log_error_errno(k, "Failed to prepare filename %s: %m", *filename); + if (r == 0) + r = k; + continue; + } + + k = manager_load_unit(m, NULL, prepared, &err, &units[count]); + if (k < 0) { + log_error_errno(k, "Failed to load %s: %m", *filename); + if (r == 0) + r = k; + } else + count ++; + } + + for (i = 0; i < count; i++) { + k = verify_unit(units[i], check_man); + if (k < 0 && r == 0) + r = k; + } + +finish: + manager_free(m); + + return r; +} diff --git a/src/systemd-analyze/analyze-verify.h b/src/systemd-analyze/analyze-verify.h new file mode 100644 index 0000000000..54adad93e1 --- /dev/null +++ b/src/systemd-analyze/analyze-verify.h @@ -0,0 +1,26 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#pragma once + +#include + +#include "path-lookup.h" + +int verify_units(char **filenames, ManagerRunningAs running_as, bool check_man); diff --git a/src/systemd-analyze/analyze.c b/src/systemd-analyze/analyze.c new file mode 100644 index 0000000000..13bcc8ebdd --- /dev/null +++ b/src/systemd-analyze/analyze.c @@ -0,0 +1,1492 @@ +/*** + This file is part of systemd. + + Copyright 2010-2013 Lennart Poettering + Copyright 2013 Simon Peeters + + 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 . +***/ + +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "analyze-verify.h" +#include "bus-error.h" +#include "bus-util.h" +#include "glob-util.h" +#include "hashmap.h" +#include "locale-util.h" +#include "log.h" +#include "pager.h" +#include "parse-util.h" +#include "special.h" +#include "strv.h" +#include "strxcpyx.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "util.h" + +#define SCALE_X (0.1 / 1000.0) /* pixels per us */ +#define SCALE_Y (20.0) + +#define compare(a, b) (((a) > (b))? 1 : (((b) > (a))? -1 : 0)) + +#define svg(...) printf(__VA_ARGS__) + +#define svg_bar(class, x1, x2, y) \ + svg(" \n", \ + (class), \ + SCALE_X * (x1), SCALE_Y * (y), \ + SCALE_X * ((x2) - (x1)), SCALE_Y - 1.0) + +#define svg_text(b, x, y, format, ...) \ + do { \ + svg(" ", (b) ? "left" : "right", SCALE_X * (x) + (b ? 5.0 : -5.0), SCALE_Y * (y) + 14.0); \ + svg(format, ## __VA_ARGS__); \ + svg("\n"); \ + } while(false) + +static enum dot { + DEP_ALL, + DEP_ORDER, + DEP_REQUIRE +} arg_dot = DEP_ALL; +static char** arg_dot_from_patterns = NULL; +static char** arg_dot_to_patterns = NULL; +static usec_t arg_fuzz = 0; +static bool arg_no_pager = false; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_user = false; +static bool arg_man = true; + +struct boot_times { + usec_t firmware_time; + usec_t loader_time; + usec_t kernel_time; + usec_t kernel_done_time; + usec_t initrd_time; + usec_t userspace_time; + usec_t finish_time; + usec_t security_start_time; + usec_t security_finish_time; + usec_t generators_start_time; + usec_t generators_finish_time; + usec_t unitsload_start_time; + usec_t unitsload_finish_time; + + /* + * If we're analyzing the user instance, all timestamps will be offset + * by its own start-up timestamp, which may be arbitrarily big. + * With "plot", this causes arbitrarily wide output SVG files which almost + * completely consist of empty space. Thus we cancel out this offset. + * + * This offset is subtracted from times above by acquire_boot_times(), + * but it still needs to be subtracted from unit-specific timestamps + * (so it is stored here for reference). + */ + usec_t reverse_offset; +}; + +struct unit_times { + char *name; + usec_t activating; + usec_t activated; + usec_t deactivated; + usec_t deactivating; + usec_t time; +}; + +struct host_info { + char *hostname; + char *kernel_name; + char *kernel_release; + char *kernel_version; + char *os_pretty_name; + char *virtualization; + char *architecture; +}; + +static void pager_open_if_enabled(void) { + + if (arg_no_pager) + return; + + pager_open(false); +} + +static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *interface, const char *property, uint64_t *val) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + assert(interface); + assert(property); + assert(val); + + r = sd_bus_get_property_trivial( + bus, + "org.freedesktop.systemd1", + path, + interface, + property, + &error, + 't', val); + + if (r < 0) { + log_error("Failed to parse reply: %s", bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char *property, char ***strv) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(path); + assert(property); + assert(strv); + + r = sd_bus_get_property_strv( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + property, + &error, + strv); + if (r < 0) { + log_error("Failed to get unit property %s: %s", property, bus_error_message(&error, -r)); + return r; + } + + return 0; +} + +static int compare_unit_time(const void *a, const void *b) { + return compare(((struct unit_times *)b)->time, + ((struct unit_times *)a)->time); +} + +static int compare_unit_start(const void *a, const void *b) { + return compare(((struct unit_times *)a)->activating, + ((struct unit_times *)b)->activating); +} + +static void free_unit_times(struct unit_times *t, unsigned n) { + struct unit_times *p; + + for (p = t; p < t + n; p++) + free(p->name); + + free(t); +} + +static void subtract_timestamp(usec_t *a, usec_t b) { + assert(a); + + if (*a > 0) { + assert(*a >= b); + *a -= b; + } +} + +static int acquire_boot_times(sd_bus *bus, struct boot_times **bt) { + static struct boot_times times; + static bool cached = false; + + if (cached) + goto finish; + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + + if (bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "FirmwareTimestampMonotonic", + ×.firmware_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LoaderTimestampMonotonic", + ×.loader_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "KernelTimestamp", + ×.kernel_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "InitRDTimestampMonotonic", + ×.initrd_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UserspaceTimestampMonotonic", + ×.userspace_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "FinishTimestampMonotonic", + ×.finish_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SecurityStartTimestampMonotonic", + ×.security_start_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "SecurityFinishTimestampMonotonic", + ×.security_finish_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GeneratorsStartTimestampMonotonic", + ×.generators_start_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GeneratorsFinishTimestampMonotonic", + ×.generators_finish_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnitsLoadStartTimestampMonotonic", + ×.unitsload_start_time) < 0 || + bus_get_uint64_property(bus, + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UnitsLoadFinishTimestampMonotonic", + ×.unitsload_finish_time) < 0) + return -EIO; + + if (times.finish_time <= 0) { + log_error("Bootup is not yet finished. Please try again later."); + return -EINPROGRESS; + } + + if (arg_user) { + /* + * User-instance-specific timestamps processing + * (see comment to reverse_offset in struct boot_times). + */ + times.reverse_offset = times.userspace_time; + + times.firmware_time = times.loader_time = times.kernel_time = times.initrd_time = times.userspace_time = 0; + subtract_timestamp(×.finish_time, times.reverse_offset); + + subtract_timestamp(×.security_start_time, times.reverse_offset); + subtract_timestamp(×.security_finish_time, times.reverse_offset); + + subtract_timestamp(×.generators_start_time, times.reverse_offset); + subtract_timestamp(×.generators_finish_time, times.reverse_offset); + + subtract_timestamp(×.unitsload_start_time, times.reverse_offset); + subtract_timestamp(×.unitsload_finish_time, times.reverse_offset); + } else { + if (times.initrd_time) + times.kernel_done_time = times.initrd_time; + else + times.kernel_done_time = times.userspace_time; + } + + cached = true; + +finish: + *bt = × + return 0; +} + +static void free_host_info(struct host_info *hi) { + + if (!hi) + return; + + free(hi->hostname); + free(hi->kernel_name); + free(hi->kernel_release); + free(hi->kernel_version); + free(hi->os_pretty_name); + free(hi->virtualization); + free(hi->architecture); + free(hi); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct host_info*, free_host_info); + +static int acquire_time_data(sd_bus *bus, struct unit_times **out) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r, c = 0; + struct boot_times *boot_times = NULL; + struct unit_times *unit_times = NULL; + size_t size = 0; + UnitInfo u; + + r = acquire_boot_times(bus, &boot_times); + if (r < 0) + goto fail; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnits", + &error, &reply, + NULL); + if (r < 0) { + log_error("Failed to list units: %s", bus_error_message(&error, -r)); + goto fail; + } + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); + if (r < 0) { + bus_log_parse_error(r); + goto fail; + } + + while ((r = bus_parse_unit_info(reply, &u)) > 0) { + struct unit_times *t; + + if (!GREEDY_REALLOC(unit_times, size, c+1)) { + r = log_oom(); + goto fail; + } + + t = unit_times+c; + t->name = NULL; + + assert_cc(sizeof(usec_t) == sizeof(uint64_t)); + + if (bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "InactiveExitTimestampMonotonic", + &t->activating) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "ActiveEnterTimestampMonotonic", + &t->activated) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "ActiveExitTimestampMonotonic", + &t->deactivating) < 0 || + bus_get_uint64_property(bus, u.unit_path, + "org.freedesktop.systemd1.Unit", + "InactiveEnterTimestampMonotonic", + &t->deactivated) < 0) { + r = -EIO; + goto fail; + } + + subtract_timestamp(&t->activating, boot_times->reverse_offset); + subtract_timestamp(&t->activated, boot_times->reverse_offset); + subtract_timestamp(&t->deactivating, boot_times->reverse_offset); + subtract_timestamp(&t->deactivated, boot_times->reverse_offset); + + if (t->activated >= t->activating) + t->time = t->activated - t->activating; + else if (t->deactivated >= t->activating) + t->time = t->deactivated - t->activating; + else + t->time = 0; + + if (t->activating == 0) + continue; + + t->name = strdup(u.id); + if (t->name == NULL) { + r = log_oom(); + goto fail; + } + c++; + } + if (r < 0) { + bus_log_parse_error(r); + goto fail; + } + + *out = unit_times; + return c; + +fail: + if (unit_times) + free_unit_times(unit_times, (unsigned) c); + return r; +} + +static int acquire_host_info(sd_bus *bus, struct host_info **hi) { + static const struct bus_properties_map hostname_map[] = { + { "Hostname", "s", NULL, offsetof(struct host_info, hostname) }, + { "KernelName", "s", NULL, offsetof(struct host_info, kernel_name) }, + { "KernelRelease", "s", NULL, offsetof(struct host_info, kernel_release) }, + { "KernelVersion", "s", NULL, offsetof(struct host_info, kernel_version) }, + { "OperatingSystemPrettyName", "s", NULL, offsetof(struct host_info, os_pretty_name) }, + {} + }; + + static const struct bus_properties_map manager_map[] = { + { "Virtualization", "s", NULL, offsetof(struct host_info, virtualization) }, + { "Architecture", "s", NULL, offsetof(struct host_info, architecture) }, + {} + }; + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(free_host_infop) struct host_info *host; + int r; + + host = new0(struct host_info, 1); + if (!host) + return log_oom(); + + r = bus_map_all_properties(bus, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + hostname_map, + host); + if (r < 0) + log_debug_errno(r, "Failed to get host information from systemd-hostnamed: %s", bus_error_message(&error, r)); + + r = bus_map_all_properties(bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + manager_map, + host); + if (r < 0) + return log_error_errno(r, "Failed to get host information from systemd: %s", bus_error_message(&error, r)); + + *hi = host; + host = NULL; + + return 0; +} + +static int pretty_boot_time(sd_bus *bus, char **_buf) { + char ts[FORMAT_TIMESPAN_MAX]; + struct boot_times *t; + static char buf[4096]; + size_t size; + char *ptr; + int r; + + r = acquire_boot_times(bus, &t); + if (r < 0) + return r; + + ptr = buf; + size = sizeof(buf); + + size = strpcpyf(&ptr, size, "Startup finished in "); + if (t->firmware_time) + size = strpcpyf(&ptr, size, "%s (firmware) + ", format_timespan(ts, sizeof(ts), t->firmware_time - t->loader_time, USEC_PER_MSEC)); + if (t->loader_time) + size = strpcpyf(&ptr, size, "%s (loader) + ", format_timespan(ts, sizeof(ts), t->loader_time, USEC_PER_MSEC)); + if (t->kernel_time) + size = strpcpyf(&ptr, size, "%s (kernel) + ", format_timespan(ts, sizeof(ts), t->kernel_done_time, USEC_PER_MSEC)); + if (t->initrd_time > 0) + size = strpcpyf(&ptr, size, "%s (initrd) + ", format_timespan(ts, sizeof(ts), t->userspace_time - t->initrd_time, USEC_PER_MSEC)); + + size = strpcpyf(&ptr, size, "%s (userspace) ", format_timespan(ts, sizeof(ts), t->finish_time - t->userspace_time, USEC_PER_MSEC)); + strpcpyf(&ptr, size, "= %s", format_timespan(ts, sizeof(ts), t->firmware_time + t->finish_time, USEC_PER_MSEC)); + + ptr = strdup(buf); + if (!ptr) + return log_oom(); + + *_buf = ptr; + return 0; +} + +static void svg_graph_box(double height, double begin, double end) { + long long i; + + /* outside box, fill */ + svg("\n", + SCALE_X * (end - begin), SCALE_Y * height); + + for (i = ((long long) (begin / 100000)) * 100000; i <= end; i+=100000) { + /* lines for each second */ + if (i % 5000000 == 0) + svg(" \n" + " %.01fs\n", + SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); + else if (i % 1000000 == 0) + svg(" \n" + " %.01fs\n", + SCALE_X * i, SCALE_X * i, SCALE_Y * height, SCALE_X * i, -5.0, 0.000001 * i); + else + svg(" \n", + SCALE_X * i, SCALE_X * i, SCALE_Y * height); + } +} + +static int analyze_plot(sd_bus *bus) { + _cleanup_(free_host_infop) struct host_info *host = NULL; + struct unit_times *times; + struct boot_times *boot; + int n, m = 1, y=0; + double width; + _cleanup_free_ char *pretty_times = NULL; + struct unit_times *u; + + n = acquire_boot_times(bus, &boot); + if (n < 0) + return n; + + n = pretty_boot_time(bus, &pretty_times); + if (n < 0) + return n; + + n = acquire_host_info(bus, &host); + if (n < 0) + return n; + + n = acquire_time_data(bus, ×); + if (n <= 0) + return n; + + qsort(times, n, sizeof(struct unit_times), compare_unit_start); + + width = SCALE_X * (boot->firmware_time + boot->finish_time); + if (width < 800.0) + width = 800.0; + + if (boot->firmware_time > boot->loader_time) + m++; + if (boot->loader_time) { + m++; + if (width < 1000.0) + width = 1000.0; + } + if (boot->initrd_time) + m++; + if (boot->kernel_time) + m++; + + for (u = times; u < times + n; u++) { + double text_start, text_width; + + if (u->activating < boot->userspace_time || + u->activating > boot->finish_time) { + u->name = mfree(u->name); + continue; + } + + /* If the text cannot fit on the left side then + * increase the svg width so it fits on the right. + * TODO: calculate the text width more accurately */ + text_width = 8.0 * strlen(u->name); + text_start = (boot->firmware_time + u->activating) * SCALE_X; + if (text_width > text_start && text_width + text_start > width) + width = text_width + text_start; + + if (u->deactivated > u->activating && u->deactivated <= boot->finish_time + && u->activated == 0 && u->deactivating == 0) + u->activated = u->deactivating = u->deactivated; + if (u->activated < u->activating || u->activated > boot->finish_time) + u->activated = boot->finish_time; + if (u->deactivating < u->activated || u->activated > boot->finish_time) + u->deactivating = boot->finish_time; + if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time) + u->deactivated = boot->finish_time; + m++; + } + + svg("\n" + "\n"); + + svg("\n\n", + 80.0 + width, 150.0 + (m * SCALE_Y) + + 5 * SCALE_Y /* legend */); + + /* write some basic info as a comment, including some help */ + svg("\n" + "\n" + "\n" + "\n" + "\n\n" + "\n\n", VERSION); + + /* style sheet */ + svg("\n \n\n\n"); + + svg("\n"); + svg("%s", pretty_times); + svg("%s %s (%s %s %s) %s %s", + isempty(host->os_pretty_name) ? "GNU/Linux" : host->os_pretty_name, + strempty(host->hostname), + strempty(host->kernel_name), + strempty(host->kernel_release), + strempty(host->kernel_version), + strempty(host->architecture), + strempty(host->virtualization)); + + svg("\n", 20.0 + (SCALE_X * boot->firmware_time)); + svg_graph_box(m, -(double) boot->firmware_time, boot->finish_time); + + if (boot->firmware_time) { + svg_bar("firmware", -(double) boot->firmware_time, -(double) boot->loader_time, y); + svg_text(true, -(double) boot->firmware_time, y, "firmware"); + y++; + } + if (boot->loader_time) { + svg_bar("loader", -(double) boot->loader_time, 0, y); + svg_text(true, -(double) boot->loader_time, y, "loader"); + y++; + } + if (boot->kernel_time) { + svg_bar("kernel", 0, boot->kernel_done_time, y); + svg_text(true, 0, y, "kernel"); + y++; + } + if (boot->initrd_time) { + svg_bar("initrd", boot->initrd_time, boot->userspace_time, y); + svg_text(true, boot->initrd_time, y, "initrd"); + y++; + } + svg_bar("active", boot->userspace_time, boot->finish_time, y); + svg_bar("security", boot->security_start_time, boot->security_finish_time, y); + svg_bar("generators", boot->generators_start_time, boot->generators_finish_time, y); + svg_bar("unitsload", boot->unitsload_start_time, boot->unitsload_finish_time, y); + svg_text(true, boot->userspace_time, y, "systemd"); + y++; + + for (u = times; u < times + n; u++) { + char ts[FORMAT_TIMESPAN_MAX]; + bool b; + + if (!u->name) + continue; + + svg_bar("activating", u->activating, u->activated, y); + svg_bar("active", u->activated, u->deactivating, y); + svg_bar("deactivating", u->deactivating, u->deactivated, y); + + /* place the text on the left if we have passed the half of the svg width */ + b = u->activating * SCALE_X < width / 2; + if (u->time) + svg_text(b, u->activating, y, "%s (%s)", + u->name, format_timespan(ts, sizeof(ts), u->time, USEC_PER_MSEC)); + else + svg_text(b, u->activating, y, "%s", u->name); + y++; + } + + svg("\n"); + + /* Legend */ + svg("\n"); + y++; + svg_bar("activating", 0, 300000, y); + svg_text(true, 400000, y, "Activating"); + y++; + svg_bar("active", 0, 300000, y); + svg_text(true, 400000, y, "Active"); + y++; + svg_bar("deactivating", 0, 300000, y); + svg_text(true, 400000, y, "Deactivating"); + y++; + svg_bar("security", 0, 300000, y); + svg_text(true, 400000, y, "Setting up security module"); + y++; + svg_bar("generators", 0, 300000, y); + svg_text(true, 400000, y, "Generators"); + y++; + svg_bar("unitsload", 0, 300000, y); + svg_text(true, 400000, y, "Loading unit files"); + y++; + + svg("\n\n"); + + svg("\n"); + + free_unit_times(times, (unsigned) n); + + n = 0; + return n; +} + +static int list_dependencies_print(const char *name, unsigned int level, unsigned int branches, + bool last, struct unit_times *times, struct boot_times *boot) { + unsigned int i; + char ts[FORMAT_TIMESPAN_MAX], ts2[FORMAT_TIMESPAN_MAX]; + + for (i = level; i != 0; i--) + printf("%s", draw_special_char(branches & (1 << (i-1)) ? DRAW_TREE_VERTICAL : DRAW_TREE_SPACE)); + + printf("%s", draw_special_char(last ? DRAW_TREE_RIGHT : DRAW_TREE_BRANCH)); + + if (times) { + if (times->time) + printf("%s%s @%s +%s%s", ANSI_HIGHLIGHT_RED, name, + format_timespan(ts, sizeof(ts), times->activating - boot->userspace_time, USEC_PER_MSEC), + format_timespan(ts2, sizeof(ts2), times->time, USEC_PER_MSEC), ANSI_NORMAL); + else if (times->activated > boot->userspace_time) + printf("%s @%s", name, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); + else + printf("%s", name); + } else + printf("%s", name); + printf("\n"); + + return 0; +} + +static int list_dependencies_get_dependencies(sd_bus *bus, const char *name, char ***deps) { + _cleanup_free_ char *path = NULL; + + assert(bus); + assert(name); + assert(deps); + + path = unit_dbus_path_from_name(name); + if (path == NULL) + return -ENOMEM; + + return bus_get_unit_property_strv(bus, path, "After", deps); +} + +static Hashmap *unit_times_hashmap; + +static int list_dependencies_compare(const void *_a, const void *_b) { + const char **a = (const char**) _a, **b = (const char**) _b; + usec_t usa = 0, usb = 0; + struct unit_times *times; + + times = hashmap_get(unit_times_hashmap, *a); + if (times) + usa = times->activated; + times = hashmap_get(unit_times_hashmap, *b); + if (times) + usb = times->activated; + + return usb - usa; +} + +static int list_dependencies_one(sd_bus *bus, const char *name, unsigned int level, char ***units, + unsigned int branches) { + _cleanup_strv_free_ char **deps = NULL; + char **c; + int r = 0; + usec_t service_longest = 0; + int to_print = 0; + struct unit_times *times; + struct boot_times *boot; + + if (strv_extend(units, name)) + return log_oom(); + + r = list_dependencies_get_dependencies(bus, name, &deps); + if (r < 0) + return r; + + qsort_safe(deps, strv_length(deps), sizeof (char*), list_dependencies_compare); + + r = acquire_boot_times(bus, &boot); + if (r < 0) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (times + && times->activated + && times->activated <= boot->finish_time + && (times->activated >= service_longest + || service_longest == 0)) { + service_longest = times->activated; + break; + } + } + + if (service_longest == 0 ) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (times && times->activated && times->activated <= boot->finish_time && (service_longest - times->activated) <= arg_fuzz) + to_print++; + } + + if (!to_print) + return r; + + STRV_FOREACH(c, deps) { + times = hashmap_get(unit_times_hashmap, *c); + if (!times + || !times->activated + || times->activated > boot->finish_time + || service_longest - times->activated > arg_fuzz) + continue; + + to_print--; + + r = list_dependencies_print(*c, level, branches, to_print == 0, times, boot); + if (r < 0) + return r; + + if (strv_contains(*units, *c)) { + r = list_dependencies_print("...", level + 1, (branches << 1) | (to_print ? 1 : 0), + true, NULL, boot); + if (r < 0) + return r; + continue; + } + + r = list_dependencies_one(bus, *c, level + 1, units, + (branches << 1) | (to_print ? 1 : 0)); + if (r < 0) + return r; + + if (!to_print) + break; + } + return 0; +} + +static int list_dependencies(sd_bus *bus, const char *name) { + _cleanup_strv_free_ char **units = NULL; + char ts[FORMAT_TIMESPAN_MAX]; + struct unit_times *times; + int r; + const char *id; + _cleanup_free_ char *path = NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + struct boot_times *boot; + + assert(bus); + + path = unit_dbus_path_from_name(name); + if (path == NULL) + return -ENOMEM; + + r = sd_bus_get_property( + bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "Id", + &error, + &reply, + "s"); + if (r < 0) { + log_error("Failed to get ID: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "s", &id); + if (r < 0) + return bus_log_parse_error(r); + + times = hashmap_get(unit_times_hashmap, id); + + r = acquire_boot_times(bus, &boot); + if (r < 0) + return r; + + if (times) { + if (times->time) + printf("%s%s +%s%s\n", ANSI_HIGHLIGHT_RED, id, + format_timespan(ts, sizeof(ts), times->time, USEC_PER_MSEC), ANSI_NORMAL); + else if (times->activated > boot->userspace_time) + printf("%s @%s\n", id, format_timespan(ts, sizeof(ts), times->activated - boot->userspace_time, USEC_PER_MSEC)); + else + printf("%s\n", id); + } + + return list_dependencies_one(bus, name, 0, &units, 0); +} + +static int analyze_critical_chain(sd_bus *bus, char *names[]) { + struct unit_times *times; + unsigned int i; + Hashmap *h; + int n, r; + + n = acquire_time_data(bus, ×); + if (n <= 0) + return n; + + h = hashmap_new(&string_hash_ops); + if (!h) + return -ENOMEM; + + for (i = 0; i < (unsigned)n; i++) { + r = hashmap_put(h, times[i].name, ×[i]); + if (r < 0) + return r; + } + unit_times_hashmap = h; + + pager_open_if_enabled(); + + puts("The time after the unit is active or started is printed after the \"@\" character.\n" + "The time the unit takes to start is printed after the \"+\" character.\n"); + + if (!strv_isempty(names)) { + char **name; + STRV_FOREACH(name, names) + list_dependencies(bus, *name); + } else + list_dependencies(bus, SPECIAL_DEFAULT_TARGET); + + hashmap_free(h); + free_unit_times(times, (unsigned) n); + return 0; +} + +static int analyze_blame(sd_bus *bus) { + struct unit_times *times; + unsigned i; + int n; + + n = acquire_time_data(bus, ×); + if (n <= 0) + return n; + + qsort(times, n, sizeof(struct unit_times), compare_unit_time); + + pager_open_if_enabled(); + + for (i = 0; i < (unsigned) n; i++) { + char ts[FORMAT_TIMESPAN_MAX]; + + if (times[i].time > 0) + printf("%16s %s\n", format_timespan(ts, sizeof(ts), times[i].time, USEC_PER_MSEC), times[i].name); + } + + free_unit_times(times, (unsigned) n); + return 0; +} + +static int analyze_time(sd_bus *bus) { + _cleanup_free_ char *buf = NULL; + int r; + + r = pretty_boot_time(bus, &buf); + if (r < 0) + return r; + + puts(buf); + return 0; +} + +static int graph_one_property(sd_bus *bus, const UnitInfo *u, const char* prop, const char *color, char* patterns[], char* from_patterns[], char* to_patterns[]) { + _cleanup_strv_free_ char **units = NULL; + char **unit; + int r; + bool match_patterns; + + assert(u); + assert(prop); + assert(color); + + match_patterns = strv_fnmatch(patterns, u->id, 0); + + if (!strv_isempty(from_patterns) && + !match_patterns && + !strv_fnmatch(from_patterns, u->id, 0)) + return 0; + + r = bus_get_unit_property_strv(bus, u->unit_path, prop, &units); + if (r < 0) + return r; + + STRV_FOREACH(unit, units) { + bool match_patterns2; + + match_patterns2 = strv_fnmatch(patterns, *unit, 0); + + if (!strv_isempty(to_patterns) && + !match_patterns2 && + !strv_fnmatch(to_patterns, *unit, 0)) + continue; + + if (!strv_isempty(patterns) && !match_patterns && !match_patterns2) + continue; + + printf("\t\"%s\"->\"%s\" [color=\"%s\"];\n", u->id, *unit, color); + } + + return 0; +} + +static int graph_one(sd_bus *bus, const UnitInfo *u, char *patterns[], char *from_patterns[], char *to_patterns[]) { + int r; + + assert(bus); + assert(u); + + if (IN_SET(arg_dot, DEP_ORDER, DEP_ALL)) { + r = graph_one_property(bus, u, "After", "green", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + } + + if (IN_SET(arg_dot, DEP_REQUIRE, DEP_ALL)) { + r = graph_one_property(bus, u, "Requires", "black", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + r = graph_one_property(bus, u, "Requisite", "darkblue", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + r = graph_one_property(bus, u, "Wants", "grey66", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + r = graph_one_property(bus, u, "Conflicts", "red", patterns, from_patterns, to_patterns); + if (r < 0) + return r; + } + + return 0; +} + +static int expand_patterns(sd_bus *bus, char **patterns, char ***ret) { + _cleanup_strv_free_ char **expanded_patterns = NULL; + char **pattern; + int r; + + STRV_FOREACH(pattern, patterns) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *unit = NULL, *unit_id = NULL; + + if (strv_extend(&expanded_patterns, *pattern) < 0) + return log_oom(); + + if (string_is_glob(*pattern)) + continue; + + unit = unit_dbus_path_from_name(*pattern); + if (!unit) + return log_oom(); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + unit, + "org.freedesktop.systemd1.Unit", + "Id", + &error, + &unit_id); + if (r < 0) + return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r)); + + if (!streq(*pattern, unit_id)) { + if (strv_extend(&expanded_patterns, unit_id) < 0) + return log_oom(); + } + } + + *ret = expanded_patterns; + expanded_patterns = NULL; /* do not free */ + + return 0; +} + +static int dot(sd_bus *bus, char* patterns[]) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_strv_free_ char **expanded_patterns = NULL; + _cleanup_strv_free_ char **expanded_from_patterns = NULL; + _cleanup_strv_free_ char **expanded_to_patterns = NULL; + int r; + UnitInfo u; + + r = expand_patterns(bus, patterns, &expanded_patterns); + if (r < 0) + return r; + + r = expand_patterns(bus, arg_dot_from_patterns, &expanded_from_patterns); + if (r < 0) + return r; + + r = expand_patterns(bus, arg_dot_to_patterns, &expanded_to_patterns); + if (r < 0) + return r; + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "ListUnits", + &error, + &reply, + ""); + if (r < 0) { + log_error("Failed to list units: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)"); + if (r < 0) + return bus_log_parse_error(r); + + printf("digraph systemd {\n"); + + while ((r = bus_parse_unit_info(reply, &u)) > 0) { + + r = graph_one(bus, &u, expanded_patterns, expanded_from_patterns, expanded_to_patterns); + if (r < 0) + return r; + } + if (r < 0) + return bus_log_parse_error(r); + + printf("}\n"); + + log_info(" Color legend: black = Requires\n" + " dark blue = Requisite\n" + " dark grey = Wants\n" + " red = Conflicts\n" + " green = After\n"); + + if (on_tty()) + log_notice("-- You probably want to process this output with graphviz' dot tool.\n" + "-- Try a shell pipeline like 'systemd-analyze dot | dot -Tsvg > systemd.svg'!\n"); + + return 0; +} + +static int dump(sd_bus *bus, char **args) { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + const char *text = NULL; + int r; + + if (!strv_isempty(args)) { + log_error("Too many arguments."); + return -E2BIG; + } + + pager_open_if_enabled(); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Dump", + &error, + &reply, + ""); + if (r < 0) + return log_error_errno(r, "Failed issue method call: %s", bus_error_message(&error, r)); + + r = sd_bus_message_read(reply, "s", &text); + if (r < 0) + return bus_log_parse_error(r); + + fputs(text, stdout); + return 0; +} + +static int set_log_level(sd_bus *bus, char **args) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(args); + + if (strv_length(args) != 1) { + log_error("This command expects one argument only."); + return -E2BIG; + } + + r = sd_bus_set_property( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LogLevel", + &error, + "s", + args[0]); + if (r < 0) + return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); + + return 0; +} + +static int set_log_target(sd_bus *bus, char **args) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(bus); + assert(args); + + if (strv_length(args) != 1) { + log_error("This command expects one argument only."); + return -E2BIG; + } + + r = sd_bus_set_property( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "LogTarget", + &error, + "s", + args[0]); + if (r < 0) + return log_error_errno(r, "Failed to issue method call: %s", bus_error_message(&error, r)); + + return 0; +} + +static void help(void) { + + pager_open_if_enabled(); + + printf("%s [OPTIONS...] {COMMAND} ...\n\n" + "Profile systemd, show unit dependencies, check unit files.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --system Operate on system systemd instance\n" + " --user Operate on user systemd instance\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --order Show only order in the graph\n" + " --require Show only requirement in the graph\n" + " --from-pattern=GLOB Show only origins in the graph\n" + " --to-pattern=GLOB Show only destinations in the graph\n" + " --fuzz=SECONDS Also print also services which finished SECONDS\n" + " earlier than the latest in the branch\n" + " --man[=BOOL] Do [not] check for existence of man pages\n\n" + "Commands:\n" + " time Print time spent in the kernel\n" + " blame Print list of running units ordered by time to init\n" + " critical-chain Print a tree of the time critical chain of units\n" + " plot Output SVG graphic showing service initialization\n" + " dot Output dependency graph in dot(1) format\n" + " set-log-level LEVEL Set logging threshold for manager\n" + " set-log-target TARGET Set logging target for manager\n" + " dump Output state serialization of service manager\n" + " verify FILE... Check unit files for correctness\n" + , program_invocation_short_name); + + /* When updating this list, including descriptions, apply + * changes to shell-completion/bash/systemd-analyze and + * shell-completion/zsh/_systemd-analyze too. */ +} + +static int parse_argv(int argc, char *argv[]) { + enum { + ARG_VERSION = 0x100, + ARG_ORDER, + ARG_REQUIRE, + ARG_USER, + ARG_SYSTEM, + ARG_DOT_FROM_PATTERN, + ARG_DOT_TO_PATTERN, + ARG_FUZZ, + ARG_NO_PAGER, + ARG_MAN, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "order", no_argument, NULL, ARG_ORDER }, + { "require", no_argument, NULL, ARG_REQUIRE }, + { "user", no_argument, NULL, ARG_USER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "from-pattern", required_argument, NULL, ARG_DOT_FROM_PATTERN }, + { "to-pattern", required_argument, NULL, ARG_DOT_TO_PATTERN }, + { "fuzz", required_argument, NULL, ARG_FUZZ }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "man", optional_argument, NULL, ARG_MAN }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + {} + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_USER: + arg_user = true; + break; + + case ARG_SYSTEM: + arg_user = false; + break; + + case ARG_ORDER: + arg_dot = DEP_ORDER; + break; + + case ARG_REQUIRE: + arg_dot = DEP_REQUIRE; + break; + + case ARG_DOT_FROM_PATTERN: + if (strv_extend(&arg_dot_from_patterns, optarg) < 0) + return log_oom(); + + break; + + case ARG_DOT_TO_PATTERN: + if (strv_extend(&arg_dot_to_patterns, optarg) < 0) + return log_oom(); + + break; + + case ARG_FUZZ: + r = parse_sec(optarg, &arg_fuzz); + if (r < 0) + return r; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_MAN: + if (optarg) { + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --man= argument."); + return -EINVAL; + } + + arg_man = !!r; + } else + arg_man = true; + + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option code."); + } + + return 1; /* work to do */ +} + +int main(int argc, char *argv[]) { + int r; + + setlocale(LC_ALL, ""); + setlocale(LC_NUMERIC, "C"); /* we want to format/parse floats in C style */ + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (streq_ptr(argv[optind], "verify")) + r = verify_units(argv+optind+1, + arg_user ? MANAGER_USER : MANAGER_SYSTEM, + arg_man); + else { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + + r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + if (!argv[optind] || streq(argv[optind], "time")) + r = analyze_time(bus); + else if (streq(argv[optind], "blame")) + r = analyze_blame(bus); + else if (streq(argv[optind], "critical-chain")) + r = analyze_critical_chain(bus, argv+optind+1); + else if (streq(argv[optind], "plot")) + r = analyze_plot(bus); + else if (streq(argv[optind], "dot")) + r = dot(bus, argv+optind+1); + else if (streq(argv[optind], "dump")) + r = dump(bus, argv+optind+1); + else if (streq(argv[optind], "set-log-level")) + r = set_log_level(bus, argv+optind+1); + else if (streq(argv[optind], "set-log-target")) + r = set_log_target(bus, argv+optind+1); + else + log_error("Unknown operation '%s'.", argv[optind]); + } + +finish: + pager_close(); + + strv_free(arg_dot_from_patterns); + strv_free(arg_dot_to_patterns); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-ask-password/Makefile b/src/systemd-ask-password/Makefile new file mode 100644 index 0000000000..11f3c8038a --- /dev/null +++ b/src/systemd-ask-password/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_ask_password_SOURCES = \ + src/ask-password/ask-password.c + +systemd_ask_password_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-ask-password/ask-password.c b/src/systemd-ask-password/ask-password.c new file mode 100644 index 0000000000..adc9286612 --- /dev/null +++ b/src/systemd-ask-password/ask-password.c @@ -0,0 +1,179 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include + +#include "ask-password-api.h" +#include "def.h" +#include "log.h" +#include "macro.h" +#include "strv.h" + +static const char *arg_icon = NULL; +static const char *arg_id = NULL; +static const char *arg_keyname = NULL; +static char *arg_message = NULL; +static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC; +static bool arg_multiple = false; +static AskPasswordFlags arg_flags = ASK_PASSWORD_PUSH_CACHE; + +static void help(void) { + printf("%s [OPTIONS...] MESSAGE\n\n" + "Query the user for a system passphrase, via the TTY or an UI agent.\n\n" + " -h --help Show this help\n" + " --icon=NAME Icon name\n" + " --id=ID Query identifier (e.g. \"cryptsetup:/dev/sda5\")\n" + " --keyname=NAME Kernel key name for caching passwords (e.g. \"cryptsetup\")\n" + " --timeout=SEC Timeout in seconds\n" + " --echo Do not mask input (useful for usernames)\n" + " --no-tty Ask question via agent even on TTY\n" + " --accept-cached Accept cached passwords\n" + " --multiple List multiple passwords if available\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_ICON = 0x100, + ARG_TIMEOUT, + ARG_ECHO, + ARG_NO_TTY, + ARG_ACCEPT_CACHED, + ARG_MULTIPLE, + ARG_ID, + ARG_KEYNAME, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "icon", required_argument, NULL, ARG_ICON }, + { "timeout", required_argument, NULL, ARG_TIMEOUT }, + { "echo", no_argument, NULL, ARG_ECHO }, + { "no-tty", no_argument, NULL, ARG_NO_TTY }, + { "accept-cached", no_argument, NULL, ARG_ACCEPT_CACHED }, + { "multiple", no_argument, NULL, ARG_MULTIPLE }, + { "id", required_argument, NULL, ARG_ID }, + { "keyname", required_argument, NULL, ARG_KEYNAME }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_ICON: + arg_icon = optarg; + break; + + case ARG_TIMEOUT: + if (parse_sec(optarg, &arg_timeout) < 0) { + log_error("Failed to parse --timeout parameter %s", optarg); + return -EINVAL; + } + break; + + case ARG_ECHO: + arg_flags |= ASK_PASSWORD_ECHO; + break; + + case ARG_NO_TTY: + arg_flags |= ASK_PASSWORD_NO_TTY; + break; + + case ARG_ACCEPT_CACHED: + arg_flags |= ASK_PASSWORD_ACCEPT_CACHED; + break; + + case ARG_MULTIPLE: + arg_multiple = true; + break; + + case ARG_ID: + arg_id = optarg; + break; + + case ARG_KEYNAME: + arg_keyname = optarg; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (argc > optind) { + arg_message = strv_join(argv + optind, " "); + if (!arg_message) + return log_oom(); + } + + return 1; +} + +int main(int argc, char *argv[]) { + _cleanup_strv_free_erase_ char **l = NULL; + usec_t timeout; + char **p; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_timeout > 0) + timeout = now(CLOCK_MONOTONIC) + arg_timeout; + else + timeout = 0; + + r = ask_password_auto(arg_message, arg_icon, arg_id, arg_keyname, timeout, arg_flags, &l); + if (r < 0) { + log_error_errno(r, "Failed to query password: %m"); + goto finish; + } + + STRV_FOREACH(p, l) { + puts(*p); + + if (!arg_multiple) + break; + } + +finish: + free(arg_message); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-backlight/Makefile b/src/systemd-backlight/Makefile new file mode 100644 index 0000000000..7ed3b0a64c --- /dev/null +++ b/src/systemd-backlight/Makefile @@ -0,0 +1,43 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_BACKLIGHT),) +rootlibexec_PROGRAMS += \ + systemd-backlight + +nodist_systemunit_DATA += \ + units/systemd-backlight@.service + +systemd_backlight_SOURCES = \ + src/backlight/backlight.c + +systemd_backlight_LDADD = \ + libshared.la +endif + +EXTRA_DIST += \ + units/systemd-backlight@.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-backlight/backlight.c b/src/systemd-backlight/backlight.c new file mode 100644 index 0000000000..45be135a23 --- /dev/null +++ b/src/systemd-backlight/backlight.c @@ -0,0 +1,434 @@ +/*** + 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 . +***/ + +#include "libudev.h" + +#include "alloc-util.h" +#include "def.h" +#include "escape.h" +#include "fileio.h" +#include "mkdir.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "string-util.h" +#include "udev-util.h" +#include "util.h" + +static struct udev_device *find_pci_or_platform_parent(struct udev_device *device) { + struct udev_device *parent; + const char *subsystem, *sysname; + + assert(device); + + parent = udev_device_get_parent(device); + if (!parent) + return NULL; + + subsystem = udev_device_get_subsystem(parent); + if (!subsystem) + return NULL; + + sysname = udev_device_get_sysname(parent); + if (!sysname) + return NULL; + + if (streq(subsystem, "drm")) { + const char *c; + + c = startswith(sysname, "card"); + if (!c) + return NULL; + + c += strspn(c, DIGITS); + if (*c == '-') { + /* A connector DRM device, let's ignore all but LVDS and eDP! */ + + if (!startswith(c, "-LVDS-") && + !startswith(c, "-Embedded DisplayPort-")) + return NULL; + } + + } else if (streq(subsystem, "pci")) { + const char *value; + + value = udev_device_get_sysattr_value(parent, "class"); + if (value) { + unsigned long class = 0; + + if (safe_atolu(value, &class) < 0) { + log_warning("Cannot parse PCI class %s of device %s:%s.", + value, subsystem, sysname); + return NULL; + } + + /* Graphics card */ + if (class == 0x30000) + return parent; + } + + } else if (streq(subsystem, "platform")) + return parent; + + return find_pci_or_platform_parent(parent); +} + +static bool same_device(struct udev_device *a, struct udev_device *b) { + assert(a); + assert(b); + + if (!streq_ptr(udev_device_get_subsystem(a), udev_device_get_subsystem(b))) + return false; + + if (!streq_ptr(udev_device_get_sysname(a), udev_device_get_sysname(b))) + return false; + + return true; +} + +static bool validate_device(struct udev *udev, struct udev_device *device) { + _cleanup_udev_enumerate_unref_ struct udev_enumerate *enumerate = NULL; + struct udev_list_entry *item = NULL, *first = NULL; + struct udev_device *parent; + const char *v, *subsystem; + int r; + + assert(udev); + assert(device); + + /* Verify whether we should actually care for a specific + * backlight device. For backlight devices there might be + * multiple ways to access the same control: "firmware" + * (i.e. ACPI), "platform" (i.e. via the machine's EC) and + * "raw" (via the graphics card). In general we should prefer + * "firmware" (i.e. ACPI) or "platform" access over "raw" + * access, in order not to confuse the BIOS/EC, and + * compatibility with possible low-level hotkey handling of + * screen brightness. The kernel will already make sure to + * expose only one of "firmware" and "platform" for the same + * device to userspace. However, we still need to make sure + * that we use "raw" only if no "firmware" or "platform" + * device for the same device exists. */ + + subsystem = udev_device_get_subsystem(device); + if (!streq_ptr(subsystem, "backlight")) + return true; + + v = udev_device_get_sysattr_value(device, "type"); + if (!streq_ptr(v, "raw")) + return true; + + parent = find_pci_or_platform_parent(device); + if (!parent) + return true; + + subsystem = udev_device_get_subsystem(parent); + if (!subsystem) + return true; + + enumerate = udev_enumerate_new(udev); + if (!enumerate) + return true; + + r = udev_enumerate_add_match_subsystem(enumerate, "backlight"); + if (r < 0) + return true; + + r = udev_enumerate_scan_devices(enumerate); + if (r < 0) + return true; + + first = udev_enumerate_get_list_entry(enumerate); + udev_list_entry_foreach(item, first) { + _cleanup_udev_device_unref_ struct udev_device *other; + struct udev_device *other_parent; + const char *other_subsystem; + + other = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!other) + return true; + + if (same_device(device, other)) + continue; + + v = udev_device_get_sysattr_value(other, "type"); + if (!streq_ptr(v, "platform") && !streq_ptr(v, "firmware")) + continue; + + /* OK, so there's another backlight device, and it's a + * platform or firmware device, so, let's see if we + * can verify it belongs to the same device as + * ours. */ + other_parent = find_pci_or_platform_parent(other); + if (!other_parent) + continue; + + if (same_device(parent, other_parent)) { + /* Both have the same PCI parent, that means + * we are out. */ + log_debug("Skipping backlight device %s, since device %s is on same PCI device and takes precedence.", + udev_device_get_sysname(device), + udev_device_get_sysname(other)); + return false; + } + + other_subsystem = udev_device_get_subsystem(other_parent); + if (streq_ptr(other_subsystem, "platform") && streq_ptr(subsystem, "pci")) { + /* The other is connected to the platform bus + * and we are a PCI device, that also means we + * are out. */ + log_debug("Skipping backlight device %s, since device %s is a platform device and takes precedence.", + udev_device_get_sysname(device), + udev_device_get_sysname(other)); + return false; + } + } + + return true; +} + +static unsigned get_max_brightness(struct udev_device *device) { + int r; + const char *max_brightness_str; + unsigned max_brightness; + + max_brightness_str = udev_device_get_sysattr_value(device, "max_brightness"); + if (!max_brightness_str) { + log_warning("Failed to read 'max_brightness' attribute."); + return 0; + } + + r = safe_atou(max_brightness_str, &max_brightness); + if (r < 0) { + log_warning_errno(r, "Failed to parse 'max_brightness' \"%s\": %m", max_brightness_str); + return 0; + } + + if (max_brightness <= 0) { + log_warning("Maximum brightness is 0, ignoring device."); + return 0; + } + + return max_brightness; +} + +/* Some systems turn the backlight all the way off at the lowest levels. + * clamp_brightness clamps the saved brightness to at least 1 or 5% of + * max_brightness in case of 'backlight' subsystem. This avoids preserving + * an unreadably dim screen, which would otherwise force the user to + * disable state restoration. */ +static void clamp_brightness(struct udev_device *device, char **value, unsigned max_brightness) { + int r; + unsigned brightness, new_brightness, min_brightness; + const char *subsystem; + + r = safe_atou(*value, &brightness); + if (r < 0) { + log_warning_errno(r, "Failed to parse brightness \"%s\": %m", *value); + return; + } + + subsystem = udev_device_get_subsystem(device); + if (streq_ptr(subsystem, "backlight")) + min_brightness = MAX(1U, max_brightness/20); + else + min_brightness = 0; + + new_brightness = CLAMP(brightness, min_brightness, max_brightness); + if (new_brightness != brightness) { + char *old_value = *value; + + r = asprintf(value, "%u", new_brightness); + if (r < 0) { + log_oom(); + return; + } + + log_info("Saved brightness %s %s to %s.", old_value, + new_brightness > brightness ? + "too low; increasing" : "too high; decreasing", + *value); + + free(old_value); + } +} + +int main(int argc, char *argv[]) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + _cleanup_free_ char *saved = NULL, *ss = NULL, *escaped_ss = NULL, *escaped_sysname = NULL, *escaped_path_id = NULL; + const char *sysname, *path_id; + unsigned max_brightness; + int r; + + if (argc != 3) { + log_error("This program requires two arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = mkdir_p("/var/lib/systemd/backlight", 0755); + if (r < 0) { + log_error_errno(r, "Failed to create backlight directory /var/lib/systemd/backlight: %m"); + return EXIT_FAILURE; + } + + udev = udev_new(); + if (!udev) { + log_oom(); + return EXIT_FAILURE; + } + + sysname = strchr(argv[2], ':'); + if (!sysname) { + log_error("Requires a subsystem and sysname pair specifying a backlight device."); + return EXIT_FAILURE; + } + + ss = strndup(argv[2], sysname - argv[2]); + if (!ss) { + log_oom(); + return EXIT_FAILURE; + } + + sysname++; + + if (!streq(ss, "backlight") && !streq(ss, "leds")) { + log_error("Not a backlight or LED device: '%s:%s'", ss, sysname); + return EXIT_FAILURE; + } + + errno = 0; + device = udev_device_new_from_subsystem_sysname(udev, ss, sysname); + if (!device) { + if (errno > 0) + log_error_errno(errno, "Failed to get backlight or LED device '%s:%s': %m", ss, sysname); + else + log_oom(); + + return EXIT_FAILURE; + } + + /* If max_brightness is 0, then there is no actual backlight + * device. This happens on desktops with Asus mainboards + * that load the eeepc-wmi module. + */ + max_brightness = get_max_brightness(device); + if (max_brightness == 0) + return EXIT_SUCCESS; + + escaped_ss = cescape(ss); + if (!escaped_ss) { + log_oom(); + return EXIT_FAILURE; + } + + escaped_sysname = cescape(sysname); + if (!escaped_sysname) { + log_oom(); + return EXIT_FAILURE; + } + + path_id = udev_device_get_property_value(device, "ID_PATH"); + if (path_id) { + escaped_path_id = cescape(path_id); + if (!escaped_path_id) { + log_oom(); + return EXIT_FAILURE; + } + + saved = strjoin("/var/lib/systemd/backlight/", escaped_path_id, ":", escaped_ss, ":", escaped_sysname, NULL); + } else + saved = strjoin("/var/lib/systemd/backlight/", escaped_ss, ":", escaped_sysname, NULL); + + if (!saved) { + log_oom(); + return EXIT_FAILURE; + } + + /* If there are multiple conflicting backlight devices, then + * their probing at boot-time might happen in any order. This + * means the validity checking of the device then is not + * reliable, since it might not see other devices conflicting + * with a specific backlight. To deal with this, we will + * actively delete backlight state files at shutdown (where + * device probing should be complete), so that the validity + * check at boot time doesn't have to be reliable. */ + + if (streq(argv[1], "load")) { + _cleanup_free_ char *value = NULL; + const char *clamp; + + if (shall_restore_state() == 0) + return EXIT_SUCCESS; + + if (!validate_device(udev, device)) + return EXIT_SUCCESS; + + r = read_one_line_file(saved, &value); + if (r < 0) { + + if (r == -ENOENT) + return EXIT_SUCCESS; + + log_error_errno(r, "Failed to read %s: %m", saved); + return EXIT_FAILURE; + } + + clamp = udev_device_get_property_value(device, "ID_BACKLIGHT_CLAMP"); + if (!clamp || parse_boolean(clamp) != 0) /* default to clamping */ + clamp_brightness(device, &value, max_brightness); + + r = udev_device_set_sysattr_value(device, "brightness", value); + if (r < 0) { + log_error_errno(r, "Failed to write system 'brightness' attribute: %m"); + return EXIT_FAILURE; + } + + } else if (streq(argv[1], "save")) { + const char *value; + + if (!validate_device(udev, device)) { + unlink(saved); + return EXIT_SUCCESS; + } + + value = udev_device_get_sysattr_value(device, "brightness"); + if (!value) { + log_error("Failed to read system 'brightness' attribute"); + return EXIT_FAILURE; + } + + r = write_string_file(saved, value, WRITE_STRING_FILE_CREATE); + if (r < 0) { + log_error_errno(r, "Failed to write %s: %m", saved); + return EXIT_FAILURE; + } + + } else { + log_error("Unknown verb %s.", argv[1]); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/src/systemd-binfmt/Makefile b/src/systemd-binfmt/Makefile new file mode 100644 index 0000000000..45a3d5056d --- /dev/null +++ b/src/systemd-binfmt/Makefile @@ -0,0 +1,56 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_BINFMT),) +systemd_binfmt_SOURCES = \ + src/binfmt/binfmt.c + +systemd_binfmt_LDADD = \ + libshared.la + +rootlibexec_PROGRAMS += \ + systemd-binfmt + +dist_systemunit_DATA += \ + units/proc-sys-fs-binfmt_misc.automount \ + units/proc-sys-fs-binfmt_misc.mount + +nodist_systemunit_DATA += \ + units/systemd-binfmt.service + +INSTALL_DIRS += \ + $(prefix)/lib/binfmt.d \ + $(sysconfdir)/binfmt.d + +SYSINIT_TARGET_WANTS += \ + systemd-binfmt.service \ + proc-sys-fs-binfmt_misc.automount + +endif + +EXTRA_DIST += \ + units/systemd-binfmt.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-binfmt/binfmt.c b/src/systemd-binfmt/binfmt.c new file mode 100644 index 0000000000..eeef04fb1c --- /dev/null +++ b/src/systemd-binfmt/binfmt.c @@ -0,0 +1,203 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "conf-files.h" +#include "def.h" +#include "fd-util.h" +#include "fileio.h" +#include "log.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +static const char conf_file_dirs[] = CONF_PATHS_NULSTR("binfmt.d"); + +static int delete_rule(const char *rule) { + _cleanup_free_ char *x = NULL, *fn = NULL; + char *e; + + assert(rule[0]); + + x = strdup(rule); + if (!x) + return log_oom(); + + e = strchrnul(x+1, x[0]); + *e = 0; + + fn = strappend("/proc/sys/fs/binfmt_misc/", x+1); + if (!fn) + return log_oom(); + + return write_string_file(fn, "-1", 0); +} + +static int apply_rule(const char *rule) { + int r; + + delete_rule(rule); + + r = write_string_file("/proc/sys/fs/binfmt_misc/register", rule, 0); + if (r < 0) + return log_error_errno(r, "Failed to add binary format: %m"); + + return 0; +} + +static int apply_file(const char *path, bool ignore_enoent) { + _cleanup_fclose_ FILE *f = NULL; + int r; + + assert(path); + + r = search_and_fopen_nulstr(path, "re", NULL, conf_file_dirs, &f); + if (r < 0) { + if (ignore_enoent && r == -ENOENT) + return 0; + + return log_error_errno(r, "Failed to open file '%s', ignoring: %m", path); + } + + log_debug("apply: %s", path); + for (;;) { + char l[LINE_MAX], *p; + int k; + + if (!fgets(l, sizeof(l), f)) { + if (feof(f)) + break; + + return log_error_errno(errno, "Failed to read file '%s', ignoring: %m", path); + } + + p = strstrip(l); + if (!*p) + continue; + if (strchr(COMMENTS "\n", *p)) + continue; + + k = apply_rule(p); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +static void help(void) { + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" + "Registers binary formats.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r, k; + + r = parse_argv(argc, argv); + if (r <= 0) + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = 0; + + if (argc > optind) { + int i; + + for (i = optind; i < argc; i++) { + k = apply_file(argv[i], false); + if (k < 0 && r == 0) + r = k; + } + } else { + _cleanup_strv_free_ char **files = NULL; + char **f; + + r = conf_files_list_nulstr(&files, ".conf", NULL, conf_file_dirs); + if (r < 0) { + log_error_errno(r, "Failed to enumerate binfmt.d files: %m"); + goto finish; + } + + /* Flush out all rules */ + write_string_file("/proc/sys/fs/binfmt_misc/status", "-1", 0); + + STRV_FOREACH(f, files) { + k = apply_file(*f, true); + if (k < 0 && r == 0) + r = k; + } + } + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-bootchart/Makefile b/src/systemd-bootchart/Makefile new file mode 100644 index 0000000000..91a06ed1a2 --- /dev/null +++ b/src/systemd-bootchart/Makefile @@ -0,0 +1,51 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_BOOTCHART),) +systemd_bootchart_SOURCES = \ + src/bootchart/bootchart.c \ + src/bootchart/bootchart.h \ + src/bootchart/store.c \ + src/bootchart/store.h \ + src/bootchart/svg.c \ + src/bootchart/svg.h + +systemd_bootchart_LDADD = \ + libshared.la + +rootlibexec_PROGRAMS += \ + systemd-bootchart + +dist_pkgsysconf_DATA += \ + src/bootchart/bootchart.conf + +nodist_systemunit_DATA += \ + units/systemd-bootchart.service +endif + +EXTRA_DIST += \ + units/systemd-bootchart.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-bootchart/bootchart.c b/src/systemd-bootchart/bootchart.c new file mode 100644 index 0000000000..8755b2d884 --- /dev/null +++ b/src/systemd-bootchart/bootchart.c @@ -0,0 +1,531 @@ +/*** + This file is part of systemd. + + Copyright (C) 2009-2013 Intel Corporation + + Authors: + Auke Kok + + 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 . + ***/ + +/*** + + Many thanks to those who contributed ideas and code: + - Ziga Mahkovec - Original bootchart author + - Anders Norgaard - PyBootchartgui + - Michael Meeks - bootchart2 + - Scott James Remnant - Ubuntu C-based logger + - Arjan van der Ven - for the idea to merge bootgraph.pl functionality + + ***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bootchart.h" +#include "conf-parser.h" +#include "def.h" +#include "fd-util.h" +#include "fileio.h" +#include "io-util.h" +#include "list.h" +#include "macro.h" +#include "parse-util.h" +#include "path-util.h" +#include "store.h" +#include "string-util.h" +#include "strxcpyx.h" +#include "svg.h" +#include "util.h" + +static int exiting = 0; + +#define DEFAULT_SAMPLES_LEN 500 +#define DEFAULT_HZ 25.0 +#define DEFAULT_SCALE_X 100.0 /* 100px = 1sec */ +#define DEFAULT_SCALE_Y 20.0 /* 16px = 1 process bar */ +#define DEFAULT_INIT ROOTLIBEXECDIR "/systemd" +#define DEFAULT_OUTPUT "/run/log" + +/* graph defaults */ +bool arg_entropy = false; +bool arg_initcall = true; +bool arg_relative = false; +bool arg_filter = true; +bool arg_show_cmdline = false; +bool arg_show_cgroup = false; +bool arg_pss = false; +bool arg_percpu = false; +int arg_samples_len = DEFAULT_SAMPLES_LEN; /* we record len+1 (1 start sample) */ +double arg_hz = DEFAULT_HZ; +double arg_scale_x = DEFAULT_SCALE_X; +double arg_scale_y = DEFAULT_SCALE_Y; + +char arg_init_path[PATH_MAX] = DEFAULT_INIT; +char arg_output_path[PATH_MAX] = DEFAULT_OUTPUT; + +static void signal_handler(int sig) { + exiting = 1; +} + +#define BOOTCHART_MAX (16*1024*1024) + +static void parse_conf(void) { + char *init = NULL, *output = NULL; + const ConfigTableItem items[] = { + { "Bootchart", "Samples", config_parse_int, 0, &arg_samples_len }, + { "Bootchart", "Frequency", config_parse_double, 0, &arg_hz }, + { "Bootchart", "Relative", config_parse_bool, 0, &arg_relative }, + { "Bootchart", "Filter", config_parse_bool, 0, &arg_filter }, + { "Bootchart", "Output", config_parse_path, 0, &output }, + { "Bootchart", "Init", config_parse_path, 0, &init }, + { "Bootchart", "PlotMemoryUsage", config_parse_bool, 0, &arg_pss }, + { "Bootchart", "PlotEntropyGraph", config_parse_bool, 0, &arg_entropy }, + { "Bootchart", "ScaleX", config_parse_double, 0, &arg_scale_x }, + { "Bootchart", "ScaleY", config_parse_double, 0, &arg_scale_y }, + { "Bootchart", "ControlGroup", config_parse_bool, 0, &arg_show_cgroup }, + { "Bootchart", "PerCPU", config_parse_bool, 0, &arg_percpu }, + { NULL, NULL, NULL, 0, NULL } + }; + + config_parse_many(PKGSYSCONFDIR "/bootchart.conf", + CONF_PATHS_NULSTR("systemd/bootchart.conf.d"), + NULL, config_item_table_lookup, items, true, NULL); + + if (init != NULL) + strscpy(arg_init_path, sizeof(arg_init_path), init); + if (output != NULL) + strscpy(arg_output_path, sizeof(arg_output_path), output); +} + +static void help(void) { + printf("Usage: %s [OPTIONS]\n\n" + "Options:\n" + " -r --rel Record time relative to recording\n" + " -f --freq=FREQ Sample frequency [%g]\n" + " -n --samples=N Stop sampling at [%d] samples\n" + " -x --scale-x=N Scale the graph horizontally [%g] \n" + " -y --scale-y=N Scale the graph vertically [%g] \n" + " -p --pss Enable PSS graph (CPU intensive)\n" + " -e --entropy Enable the entropy_avail graph\n" + " -o --output=PATH Path to output files [%s]\n" + " -i --init=PATH Path to init executable [%s]\n" + " -F --no-filter Disable filtering of unimportant or ephemeral processes\n" + " -C --cmdline Display full command lines with arguments\n" + " -c --control-group Display process control group\n" + " --per-cpu Draw each CPU utilization and wait bar also\n" + " -h --help Display this message\n\n" + "See bootchart.conf for more information.\n", + program_invocation_short_name, + DEFAULT_HZ, + DEFAULT_SAMPLES_LEN, + DEFAULT_SCALE_X, + DEFAULT_SCALE_Y, + DEFAULT_OUTPUT, + DEFAULT_INIT); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_PERCPU = 0x100, + }; + + static const struct option options[] = { + {"rel", no_argument, NULL, 'r' }, + {"freq", required_argument, NULL, 'f' }, + {"samples", required_argument, NULL, 'n' }, + {"pss", no_argument, NULL, 'p' }, + {"output", required_argument, NULL, 'o' }, + {"init", required_argument, NULL, 'i' }, + {"no-filter", no_argument, NULL, 'F' }, + {"cmdline", no_argument, NULL, 'C' }, + {"control-group", no_argument, NULL, 'c' }, + {"help", no_argument, NULL, 'h' }, + {"scale-x", required_argument, NULL, 'x' }, + {"scale-y", required_argument, NULL, 'y' }, + {"entropy", no_argument, NULL, 'e' }, + {"per-cpu", no_argument, NULL, ARG_PERCPU}, + {} + }; + int c, r; + + if (getpid() == 1) + opterr = 0; + + while ((c = getopt_long(argc, argv, "erpf:n:o:i:FCchx:y:", options, NULL)) >= 0) + switch (c) { + + case 'r': + arg_relative = true; + break; + case 'f': + r = safe_atod(optarg, &arg_hz); + if (r < 0) + log_warning_errno(r, "failed to parse --freq/-f argument '%s': %m", + optarg); + break; + case 'F': + arg_filter = false; + break; + case 'C': + arg_show_cmdline = true; + break; + case 'c': + arg_show_cgroup = true; + break; + case 'n': + r = safe_atoi(optarg, &arg_samples_len); + if (r < 0) + log_warning_errno(r, "failed to parse --samples/-n argument '%s': %m", + optarg); + break; + case 'o': + path_kill_slashes(optarg); + strscpy(arg_output_path, sizeof(arg_output_path), optarg); + break; + case 'i': + path_kill_slashes(optarg); + strscpy(arg_init_path, sizeof(arg_init_path), optarg); + break; + case 'p': + arg_pss = true; + break; + case 'x': + r = safe_atod(optarg, &arg_scale_x); + if (r < 0) + log_warning_errno(r, "failed to parse --scale-x/-x argument '%s': %m", + optarg); + break; + case 'y': + r = safe_atod(optarg, &arg_scale_y); + if (r < 0) + log_warning_errno(r, "failed to parse --scale-y/-y argument '%s': %m", + optarg); + break; + case 'e': + arg_entropy = true; + break; + case ARG_PERCPU: + arg_percpu = true; + break; + case 'h': + help(); + return 0; + case '?': + if (getpid() != 1) + return -EINVAL; + else + return 0; + default: + assert_not_reached("Unhandled option code."); + } + + if (arg_hz <= 0) { + log_error("Frequency needs to be > 0"); + return -EINVAL; + } + + return 1; +} + +static int do_journal_append(char *file) { + _cleanup_free_ char *bootchart_message = NULL; + _cleanup_free_ char *bootchart_file = NULL; + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -1; + struct iovec iovec[5]; + int r, j = 0; + ssize_t n; + + bootchart_file = strappend("BOOTCHART_FILE=", file); + if (!bootchart_file) + return log_oom(); + + IOVEC_SET_STRING(iovec[j++], bootchart_file); + IOVEC_SET_STRING(iovec[j++], "MESSAGE_ID=9f26aa562cf440c2b16c773d0479b518"); + IOVEC_SET_STRING(iovec[j++], "PRIORITY=7"); + bootchart_message = strjoin("MESSAGE=Bootchart created: ", file, NULL); + if (!bootchart_message) + return log_oom(); + + IOVEC_SET_STRING(iovec[j++], bootchart_message); + + p = malloc(10 + BOOTCHART_MAX); + if (!p) + return log_oom(); + + memcpy(p, "BOOTCHART=", 10); + + fd = open(file, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open bootchart data \"%s\": %m", file); + + n = loop_read(fd, p + 10, BOOTCHART_MAX, false); + if (n < 0) + return log_error_errno(n, "Failed to read bootchart data: %m"); + + iovec[j].iov_base = p; + iovec[j].iov_len = 10 + n; + j++; + + r = sd_journal_sendv(iovec, j); + if (r < 0) + log_error_errno(r, "Failed to send bootchart: %m"); + + return 0; +} + +int main(int argc, char *argv[]) { + static struct list_sample_data *sampledata; + _cleanup_closedir_ DIR *proc = NULL; + _cleanup_free_ char *build = NULL; + _cleanup_fclose_ FILE *of = NULL; + _cleanup_close_ int sysfd = -1; + struct ps_struct *ps_first; + double graph_start; + double log_start; + double interval; + char output_file[PATH_MAX]; + char datestr[200]; + int pscount = 0; + int n_cpus = 0; + int overrun = 0; + time_t t = 0; + int r, samples; + struct ps_struct *ps; + struct rlimit rlim; + struct list_sample_data *head; + struct sigaction sig = { + .sa_handler = signal_handler, + }; + + parse_conf(); + + r = parse_argv(argc, argv); + if (r < 0) + return EXIT_FAILURE; + + if (r == 0) + return EXIT_SUCCESS; + + /* + * If the kernel executed us through init=/usr/lib/systemd/systemd-bootchart, then + * fork: + * - parent execs executable specified via init_path[] (/usr/lib/systemd/systemd by default) as pid=1 + * - child logs data + */ + if (getpid() == 1) { + if (fork()) + /* parent */ + execl(arg_init_path, arg_init_path, NULL); + } + argv[0][0] = '@'; + + rlim.rlim_cur = 4096; + rlim.rlim_max = 4096; + (void) setrlimit(RLIMIT_NOFILE, &rlim); + + /* start with empty ps LL */ + ps_first = new0(struct ps_struct, 1); + if (!ps_first) { + log_oom(); + return EXIT_FAILURE; + } + + /* handle TERM/INT nicely */ + sigaction(SIGHUP, &sig, NULL); + + interval = (1.0 / arg_hz) * 1000000000.0; + + if (arg_relative) + graph_start = log_start = gettime_ns(); + else { + struct timespec n; + double uptime; + + clock_gettime(clock_boottime_or_monotonic(), &n); + uptime = (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC)); + + log_start = gettime_ns(); + graph_start = log_start - uptime; + } + + if (graph_start < 0.0) { + log_error("Failed to setup graph start time.\n\n" + "The system uptime probably includes time that the system was suspended. " + "Use --rel to bypass this issue."); + return EXIT_FAILURE; + } + + LIST_HEAD_INIT(head); + + /* main program loop */ + for (samples = 0; !exiting && samples < arg_samples_len; samples++) { + int res; + double sample_stop; + double elapsed; + double timeleft; + + sampledata = new0(struct list_sample_data, 1); + if (sampledata == NULL) { + log_oom(); + return EXIT_FAILURE; + } + + sampledata->sampletime = gettime_ns(); + sampledata->counter = samples; + + if (sysfd < 0) + sysfd = open("/sys", O_RDONLY|O_CLOEXEC); + + if (!build) { + if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &build, NULL) == -ENOENT) + parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &build, NULL); + } + + if (proc) + rewinddir(proc); + else + proc = opendir("/proc"); + + /* wait for /proc to become available, discarding samples */ + if (proc) { + r = log_sample(proc, samples, ps_first, &sampledata, &pscount, &n_cpus); + if (r < 0) + return EXIT_FAILURE; + } + + sample_stop = gettime_ns(); + + elapsed = (sample_stop - sampledata->sampletime) * 1000000000.0; + timeleft = interval - elapsed; + + /* + * check if we have not consumed our entire timeslice. If we + * do, don't sleep and take a new sample right away. + * we'll lose all the missed samples and overrun our total + * time + */ + if (timeleft > 0) { + struct timespec req; + + req.tv_sec = (time_t)(timeleft / 1000000000.0); + req.tv_nsec = (long)(timeleft - (req.tv_sec * 1000000000.0)); + + res = nanosleep(&req, NULL); + if (res) { + if (errno == EINTR) + /* caught signal, probably HUP! */ + break; + log_error_errno(errno, "nanosleep() failed: %m"); + return EXIT_FAILURE; + } + } else { + overrun++; + /* calculate how many samples we lost and scrap them */ + arg_samples_len -= (int)(-timeleft / interval); + } + LIST_PREPEND(link, head, sampledata); + } + + /* do some cleanup, close fd's */ + ps = ps_first; + while (ps->next_ps) { + ps = ps->next_ps; + ps->schedstat = safe_close(ps->schedstat); + ps->sched = safe_close(ps->sched); + ps->smaps = safe_fclose(ps->smaps); + } + + if (!of) { + t = time(NULL); + r = strftime(datestr, sizeof(datestr), "%Y%m%d-%H%M", localtime(&t)); + assert_se(r > 0); + + snprintf(output_file, PATH_MAX, "%s/bootchart-%s.svg", arg_output_path, datestr); + of = fopen(output_file, "we"); + } + + if (!of) { + log_error("Error opening output file '%s': %m\n", output_file); + return EXIT_FAILURE; + } + + r = svg_do(of, strna(build), head, ps_first, + samples, pscount, n_cpus, graph_start, + log_start, interval, overrun); + + if (r < 0) { + log_error_errno(r, "Error generating svg file: %m"); + return EXIT_FAILURE; + } + + log_info("systemd-bootchart wrote %s\n", output_file); + + r = do_journal_append(output_file); + if (r < 0) + return EXIT_FAILURE; + + /* nitpic cleanups */ + ps = ps_first->next_ps; + while (ps->next_ps) { + struct ps_struct *old; + + old = ps; + old->sample = ps->first; + ps = ps->next_ps; + while (old->sample->next) { + struct ps_sched_struct *oldsample = old->sample; + + old->sample = old->sample->next; + free(oldsample); + } + free(old->cgroup); + free(old->sample); + free(old); + } + + free(ps->cgroup); + free(ps->sample); + free(ps); + + sampledata = head; + while (sampledata->link_prev) { + struct list_sample_data *old_sampledata = sampledata; + sampledata = sampledata->link_prev; + free(old_sampledata); + } + free(sampledata); + + /* don't complain when overrun once, happens most commonly on 1st sample */ + if (overrun > 1) + log_warning("systemd-bootchart: sample time overrun %i times\n", overrun); + + return 0; +} diff --git a/src/systemd-bootchart/bootchart.conf b/src/systemd-bootchart/bootchart.conf new file mode 100644 index 0000000000..4f5e50936e --- /dev/null +++ b/src/systemd-bootchart/bootchart.conf @@ -0,0 +1,26 @@ +# 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. +# +# Entries in this file show the compile time defaults. +# You can change settings by editing this file. +# Defaults can be restored by simply deleting this file. +# +# See bootchart.conf(5) for details. + +[Bootchart] +#Samples=500 +#Frequency=25 +#Relative=no +#Filter=yes +#Output= +#Init=/path/to/init-binary +#PlotMemoryUsage=no +#PlotEntropyGraph=no +#ScaleX=100 +#ScaleY=20 +#ControlGroup=no +#PerCPU=no diff --git a/src/systemd-bootchart/bootchart.h b/src/systemd-bootchart/bootchart.h new file mode 100644 index 0000000000..1b445b954b --- /dev/null +++ b/src/systemd-bootchart/bootchart.h @@ -0,0 +1,119 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2009-2013 Intel Corporation + + Authors: + Auke Kok + + 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 . +***/ + +#include + +#include "list.h" + +#define MAXCPUS 16 +#define MAXPIDS 65535 + +struct block_stat_struct { + /* /proc/vmstat pgpgin & pgpgout */ + int bi; + int bo; +}; + +struct cpu_stat_sample_struct { + /* /proc/schedstat fields 10 & 11 (after name) */ + double runtime; + double waittime; +}; + +/* per process, per sample data we will log */ +struct ps_sched_struct { + /* /proc//schedstat fields 1 & 2 */ + double runtime; + double waittime; + int pss; + struct list_sample_data *sampledata; + struct ps_sched_struct *next; + struct ps_sched_struct *prev; + struct ps_sched_struct *cross; /* cross pointer */ + struct ps_struct *ps_new; +}; + +struct list_sample_data { + double runtime[MAXCPUS]; + double waittime[MAXCPUS]; + double sampletime; + int entropy_avail; + struct block_stat_struct blockstat; + LIST_FIELDS(struct list_sample_data, link); /* DLL */ + int counter; +}; + +/* process info */ +struct ps_struct { + struct ps_struct *next_ps; /* SLL pointer */ + struct ps_struct *parent; /* ppid ref */ + struct ps_struct *children; /* children */ + struct ps_struct *next; /* siblings */ + + /* must match - otherwise it's a new process with same PID */ + char name[256]; + int pid; + int ppid; + char *cgroup; + + /* cache fd's */ + int sched; + int schedstat; + FILE *smaps; + + /* pointers to first/last seen timestamps */ + struct ps_sched_struct *first; + struct ps_sched_struct *last; + + /* records actual start time, may be way before bootchart runs */ + double starttime; + + /* record human readable total cpu time */ + double total; + + /* largest PSS size found */ + int pss_max; + + /* for drawing connection lines later */ + double pos_x; + double pos_y; + + struct ps_sched_struct *sample; +}; + +extern bool arg_relative; +extern bool arg_filter; +extern bool arg_show_cmdline; +extern bool arg_show_cgroup; +extern bool arg_pss; +extern bool arg_entropy; +extern bool arg_percpu; +extern bool arg_initcall; +extern int arg_samples_len; +extern double arg_hz; +extern double arg_scale_x; +extern double arg_scale_y; + +extern char arg_output_path[PATH_MAX]; +extern char arg_init_path[PATH_MAX]; diff --git a/src/systemd-bootchart/store.c b/src/systemd-bootchart/store.c new file mode 100644 index 0000000000..42cb8043ce --- /dev/null +++ b/src/systemd-bootchart/store.c @@ -0,0 +1,555 @@ +/*** + This file is part of systemd. + + Copyright (C) 2009-2013 Intel Corporation + + Authors: + Auke Kok + + 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 . + ***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "bootchart.h" +#include "cgroup-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "parse-util.h" +#include "store.h" +#include "string-util.h" +#include "strxcpyx.h" +#include "time-util.h" +#include "util.h" + +/* + * Alloc a static 4k buffer for stdio - primarily used to increase + * PSS buffering from the default 1k stdin buffer to reduce + * read() overhead. + */ +static char smaps_buf[4096]; +static int skip = 0; + +double gettime_ns(void) { + struct timespec n; + + clock_gettime(CLOCK_MONOTONIC, &n); + + return (n.tv_sec + (n.tv_nsec / (double) NSEC_PER_SEC)); +} + +static char *bufgetline(char *buf) { + char *c; + + if (!buf) + return NULL; + + c = strchr(buf, '\n'); + if (c) + c++; + + return c; +} + +static int pid_cmdline_strscpy(int procfd, char *buffer, size_t buf_len, int pid) { + char filename[PATH_MAX]; + _cleanup_close_ int fd = -1; + ssize_t n; + + sprintf(filename, "%d/cmdline", pid); + fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) + return -errno; + + n = read(fd, buffer, buf_len-1); + if (n > 0) { + int i; + for (i = 0; i < n; i++) + if (buffer[i] == '\0') + buffer[i] = ' '; + buffer[n] = '\0'; + } + + return 0; +} + +int log_sample(DIR *proc, + int sample, + struct ps_struct *ps_first, + struct list_sample_data **ptr, + int *pscount, + int *cpus) { + + static int vmstat = -1; + _cleanup_free_ char *buf_schedstat = NULL; + char buf[4096]; + char key[256]; + char val[256]; + char rt[256]; + char wt[256]; + char *m; + int r; + int c; + int p; + int mod; + static int e_fd = -1; + ssize_t s; + ssize_t n; + struct dirent *ent; + int fd; + struct list_sample_data *sampledata; + struct ps_sched_struct *ps_prev = NULL; + int procfd; + int taskfd = -1; + + sampledata = *ptr; + + procfd = dirfd(proc); + if (procfd < 0) + return -errno; + + if (vmstat < 0) { + /* block stuff */ + vmstat = openat(procfd, "vmstat", O_RDONLY|O_CLOEXEC); + if (vmstat < 0) + return log_error_errno(errno, "Failed to open /proc/vmstat: %m"); + } + + n = pread(vmstat, buf, sizeof(buf) - 1, 0); + if (n <= 0) { + vmstat = safe_close(vmstat); + if (n < 0) + return -errno; + return -ENODATA; + } + + buf[n] = '\0'; + + m = buf; + while (m) { + if (sscanf(m, "%s %s", key, val) < 2) + goto vmstat_next; + if (streq(key, "pgpgin")) + sampledata->blockstat.bi = atoi(val); + if (streq(key, "pgpgout")) { + sampledata->blockstat.bo = atoi(val); + break; + } +vmstat_next: + m = bufgetline(m); + if (!m) + break; + } + + /* Parse "/proc/schedstat" for overall CPU utilization */ + r = read_full_file("/proc/schedstat", &buf_schedstat, NULL); + if (r < 0) + return log_error_errno(r, "Unable to read schedstat: %m"); + + m = buf_schedstat; + while (m) { + if (sscanf(m, "%s %*s %*s %*s %*s %*s %*s %s %s", key, rt, wt) < 3) + goto schedstat_next; + + if (strstr(key, "cpu")) { + r = safe_atoi((const char*)(key+3), &c); + if (r < 0 || c > MAXCPUS -1) + /* Oops, we only have room for MAXCPUS data */ + break; + sampledata->runtime[c] = atoll(rt); + sampledata->waittime[c] = atoll(wt); + + if (c == *cpus) + *cpus = c + 1; + } +schedstat_next: + m = bufgetline(m); + if (!m) + break; + } + + if (arg_entropy) { + if (e_fd < 0) { + e_fd = openat(procfd, "sys/kernel/random/entropy_avail", O_RDONLY|O_CLOEXEC); + if (e_fd < 0) + return log_error_errno(errno, "Failed to open /proc/sys/kernel/random/entropy_avail: %m"); + } + + n = pread(e_fd, buf, sizeof(buf) - 1, 0); + if (n <= 0) { + e_fd = safe_close(e_fd); + } else { + buf[n] = '\0'; + sampledata->entropy_avail = atoi(buf); + } + } + + while ((ent = readdir(proc)) != NULL) { + char filename[PATH_MAX]; + int pid; + struct ps_struct *ps; + + if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9')) + continue; + + pid = atoi(ent->d_name); + + if (pid >= MAXPIDS) + continue; + + ps = ps_first; + while (ps->next_ps) { + ps = ps->next_ps; + if (ps->pid == pid) + break; + } + + /* end of our LL? then append a new record */ + if (ps->pid != pid) { + _cleanup_fclose_ FILE *st = NULL; + char t[32]; + struct ps_struct *parent; + + ps->next_ps = new0(struct ps_struct, 1); + if (!ps->next_ps) + return log_oom(); + + ps = ps->next_ps; + ps->pid = pid; + ps->sched = -1; + ps->schedstat = -1; + + ps->sample = new0(struct ps_sched_struct, 1); + if (!ps->sample) + return log_oom(); + + ps->sample->sampledata = sampledata; + + (*pscount)++; + + /* mark our first sample */ + ps->first = ps->last = ps->sample; + ps->sample->runtime = atoll(rt); + ps->sample->waittime = atoll(wt); + + /* get name, start time */ + if (ps->sched < 0) { + sprintf(filename, "%d/sched", pid); + ps->sched = openat(procfd, filename, O_RDONLY|O_CLOEXEC); + if (ps->sched < 0) + continue; + } + + s = pread(ps->sched, buf, sizeof(buf) - 1, 0); + if (s <= 0) { + ps->sched = safe_close(ps->sched); + continue; + } + buf[s] = '\0'; + + if (!sscanf(buf, "%s %*s %*s", key)) + continue; + + strscpy(ps->name, sizeof(ps->name), key); + + /* cmdline */ + if (arg_show_cmdline) + pid_cmdline_strscpy(procfd, ps->name, sizeof(ps->name), pid); + + /* discard line 2 */ + m = bufgetline(buf); + if (!m) + continue; + + m = bufgetline(m); + if (!m) + continue; + + if (!sscanf(m, "%*s %*s %s", t)) + continue; + + r = safe_atod(t, &ps->starttime); + if (r < 0) + continue; + + ps->starttime /= 1000.0; + + if (arg_show_cgroup) + /* if this fails, that's OK */ + cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, + ps->pid, &ps->cgroup); + + /* ppid */ + sprintf(filename, "%d/stat", pid); + fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) + continue; + + st = fdopen(fd, "re"); + if (!st) { + close(fd); + continue; + } + + if (!fscanf(st, "%*s %*s %*s %i", &p)) + continue; + + ps->ppid = p; + + /* + * setup child pointers + * + * these are used to paint the tree coherently later + * each parent has a LL of children, and a LL of siblings + */ + if (pid == 1) + continue; /* nothing to do for init atm */ + + /* kthreadd has ppid=0, which breaks our tree ordering */ + if (ps->ppid == 0) + ps->ppid = 1; + + parent = ps_first; + while ((parent->next_ps && parent->pid != ps->ppid)) + parent = parent->next_ps; + + if (parent->pid != ps->ppid) { + /* orphan */ + ps->ppid = 1; + parent = ps_first->next_ps; + } + + ps->parent = parent; + + if (!parent->children) { + /* it's the first child */ + parent->children = ps; + } else { + /* walk all children and append */ + struct ps_struct *children; + children = parent->children; + while (children->next) + children = children->next; + + children->next = ps; + } + } + + /* else -> found pid, append data in ps */ + + /* below here is all continuous logging parts - we get here on every + * iteration */ + + /* rt, wt */ + if (ps->schedstat < 0) { + sprintf(filename, "%d/schedstat", pid); + ps->schedstat = openat(procfd, filename, O_RDONLY|O_CLOEXEC); + if (ps->schedstat < 0) + continue; + } + + s = pread(ps->schedstat, buf, sizeof(buf) - 1, 0); + if (s <= 0) { + /* clean up our file descriptors - assume that the process exited */ + close(ps->schedstat); + ps->schedstat = -1; + ps->sched = safe_close(ps->sched); + continue; + } + + buf[s] = '\0'; + + if (!sscanf(buf, "%s %s %*s", rt, wt)) + continue; + + ps->sample->next = new0(struct ps_sched_struct, 1); + if (!ps->sample->next) + return log_oom(); + + ps->sample->next->prev = ps->sample; + ps->sample = ps->sample->next; + ps->last = ps->sample; + ps->sample->runtime = atoll(rt); + ps->sample->waittime = atoll(wt); + ps->sample->sampledata = sampledata; + ps->sample->ps_new = ps; + if (ps_prev) + ps_prev->cross = ps->sample; + + ps_prev = ps->sample; + ps->total = (ps->last->runtime - ps->first->runtime) + / 1000000000.0; + + /* Take into account CPU runtime/waittime spent in non-main threads of the process + * by parsing "/proc/[pid]/task/[tid]/schedstat" for all [tid] != [pid] + * See https://github.com/systemd/systemd/issues/139 + */ + + /* Browse directory "/proc/[pid]/task" to know the thread ids of process [pid] */ + snprintf(filename, sizeof(filename), PID_FMT "/task", pid); + taskfd = openat(procfd, filename, O_RDONLY|O_DIRECTORY|O_CLOEXEC); + if (taskfd >= 0) { + _cleanup_closedir_ DIR *taskdir = NULL; + + taskdir = fdopendir(taskfd); + if (!taskdir) { + safe_close(taskfd); + return -errno; + } + FOREACH_DIRENT(ent, taskdir, break) { + int tid = -1; + _cleanup_close_ int tid_schedstat = -1; + long long delta_rt; + long long delta_wt; + + if ((ent->d_name[0] < '0') || (ent->d_name[0] > '9')) + continue; + + /* Skip main thread as it was already accounted */ + r = safe_atoi(ent->d_name, &tid); + if (r < 0 || tid == pid) + continue; + + /* Parse "/proc/[pid]/task/[tid]/schedstat" */ + snprintf(filename, sizeof(filename), PID_FMT "/schedstat", tid); + tid_schedstat = openat(taskfd, filename, O_RDONLY|O_CLOEXEC); + + if (tid_schedstat == -1) + continue; + + s = pread(tid_schedstat, buf, sizeof(buf) - 1, 0); + if (s <= 0) + continue; + buf[s] = '\0'; + + if (!sscanf(buf, "%s %s %*s", rt, wt)) + continue; + + r = safe_atolli(rt, &delta_rt); + if (r < 0) + continue; + r = safe_atolli(rt, &delta_wt); + if (r < 0) + continue; + ps->sample->runtime += delta_rt; + ps->sample->waittime += delta_wt; + } + } + + if (!arg_pss) + goto catch_rename; + + /* Pss */ + if (!ps->smaps) { + sprintf(filename, "%d/smaps", pid); + fd = openat(procfd, filename, O_RDONLY|O_CLOEXEC); + if (fd < 0) + continue; + ps->smaps = fdopen(fd, "re"); + if (!ps->smaps) { + close(fd); + continue; + } + setvbuf(ps->smaps, smaps_buf, _IOFBF, sizeof(smaps_buf)); + } else { + rewind(ps->smaps); + } + + /* test to see if we need to skip another field */ + if (skip == 0) { + if (fgets(buf, sizeof(buf), ps->smaps) == NULL) { + continue; + } + if (fread(buf, 1, 28 * 15, ps->smaps) != (28 * 15)) { + continue; + } + if (buf[392] == 'V') { + skip = 2; + } + else { + skip = 1; + } + rewind(ps->smaps); + } + + while (1) { + int pss_kb; + + /* skip one line, this contains the object mapped. */ + if (fgets(buf, sizeof(buf), ps->smaps) == NULL) { + break; + } + /* then there's a 28 char 14 line block */ + if (fread(buf, 1, 28 * 14, ps->smaps) != 28 * 14) { + break; + } + pss_kb = atoi(&buf[61]); + ps->sample->pss += pss_kb; + + /* skip one more line if this is a newer kernel */ + if (skip == 2) { + if (fgets(buf, sizeof(buf), ps->smaps) == NULL) + break; + } + } + + if (ps->sample->pss > ps->pss_max) + ps->pss_max = ps->sample->pss; + +catch_rename: + /* catch process rename, try to randomize time */ + mod = (arg_hz < 4.0) ? 4.0 : (arg_hz / 4.0); + if (((sample - ps->pid) + pid) % (int)(mod) == 0) { + + /* re-fetch name */ + /* get name, start time */ + if (ps->sched < 0) { + sprintf(filename, "%d/sched", pid); + ps->sched = openat(procfd, filename, O_RDONLY|O_CLOEXEC); + if (ps->sched < 0) + continue; + } + + s = pread(ps->sched, buf, sizeof(buf) - 1, 0); + if (s <= 0) { + /* clean up file descriptors */ + ps->sched = safe_close(ps->sched); + ps->schedstat = safe_close(ps->schedstat); + continue; + } + + buf[s] = '\0'; + + if (!sscanf(buf, "%s %*s %*s", key)) + continue; + + strscpy(ps->name, sizeof(ps->name), key); + + /* cmdline */ + if (arg_show_cmdline) + pid_cmdline_strscpy(procfd, ps->name, sizeof(ps->name), pid); + } + } + + return 0; +} diff --git a/src/systemd-bootchart/store.h b/src/systemd-bootchart/store.h new file mode 100644 index 0000000000..6e9acf2a6f --- /dev/null +++ b/src/systemd-bootchart/store.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2009-2013 Intel Corporation + + Authors: + Auke Kok + + 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 . +***/ + +#include + +#include "bootchart.h" + +double gettime_ns(void); +void log_uptime(void); +int log_sample(DIR *proc, + int sample, + struct ps_struct *ps_first, + struct list_sample_data **ptr, + int *pscount, + int *cpus); diff --git a/src/systemd-bootchart/svg.c b/src/systemd-bootchart/svg.c new file mode 100644 index 0000000000..f2af535061 --- /dev/null +++ b/src/systemd-bootchart/svg.c @@ -0,0 +1,1375 @@ +/*** + This file is part of systemd. + + Copyright (C) 2009-2013 Intel Corporation + + Authors: + Auke Kok + + 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 . + ***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "architecture.h" +#include "bootchart.h" +#include "fd-util.h" +#include "fileio.h" +#include "list.h" +#include "macro.h" +#include "stdio-util.h" +#include "store.h" +#include "svg.h" +#include "utf8.h" +#include "util.h" + +#define time_to_graph(t) ((t) * arg_scale_x) +#define ps_to_graph(n) ((n) * arg_scale_y) +#define kb_to_graph(m) ((m) * arg_scale_y * 0.0001) +#define to_color(n) (192.0 - ((n) * 192.0)) + +static const char * const colorwheel[12] = { + "rgb(255,32,32)", // red + "rgb(32,192,192)", // cyan + "rgb(255,128,32)", // orange + "rgb(128,32,192)", // blue-violet + "rgb(255,255,32)", // yellow + "rgb(192,32,128)", // red-violet + "rgb(32,255,32)", // green + "rgb(255,64,32)", // red-orange + "rgb(32,32,255)", // blue + "rgb(255,192,32)", // yellow-orange + "rgb(192,32,192)", // violet + "rgb(32,192,32)" // yellow-green +}; + +static double idletime = -1.0; +static int pfiltered = 0; +static int pcount = 0; +static int kcount = 0; +static double psize = 0; +static double ksize = 0; +static double esize = 0; +static struct list_sample_data *sampledata; +static struct list_sample_data *prev_sampledata; + +static void svg_header(FILE *of, struct list_sample_data *head, double graph_start, int n_cpus) { + double w; + double h; + struct list_sample_data *sampledata_last; + + assert(head); + + sampledata_last = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + sampledata_last = sampledata; + } + + /* min width is about 1600px due to the label */ + w = 150.0 + 10.0 + time_to_graph(sampledata_last->sampletime - graph_start); + w = ((w < 1600.0) ? 1600.0 : w); + + /* height is variable based on pss, psize, ksize */ + h = 400.0 + (arg_scale_y * 30.0) /* base graphs and title */ + + (arg_pss ? (100.0 * arg_scale_y) + (arg_scale_y * 7.0) : 0.0) /* pss estimate */ + + psize + ksize + esize + (n_cpus * 15 * arg_scale_y); + + fprintf(of, "\n"); + fprintf(of, "\n"); + + //fprintf(of, "\n", 1000 + 150 + (pcount * 20)); + fprintf(of, "\n\n"); + + /* write some basic info as a comment, including some help */ + fprintf(of, "\n"); + fprintf(of, "\n"); + fprintf(of, "\n"); + fprintf(of, "\n"); + fprintf(of, "\n\n"); + + fprintf(of, "\n", VERSION); + fprintf(of, "\n", arg_hz, arg_samples_len); + fprintf(of, "\n", arg_scale_x, arg_scale_y); + fprintf(of, "\n", arg_relative, arg_filter); + fprintf(of, "\n", arg_pss, arg_entropy); + fprintf(of, "\n\n", arg_output_path, arg_init_path); + + /* style sheet */ + fprintf(of, "\n \n\n\n"); +} + +static int svg_title(FILE *of, const char *build, int pscount, double log_start, int overrun) { + _cleanup_free_ char *cmdline = NULL; + _cleanup_free_ char *model = NULL; + _cleanup_free_ char *buf = NULL; + char date[256] = "Unknown"; + const char *cpu; + char *c; + time_t t; + int r; + struct utsname uts; + + r = read_one_line_file("/proc/cmdline", &cmdline); + if (r < 0) { + log_error_errno(r, "Unable to read cmdline: %m"); + return r; + } + + /* extract root fs so we can find disk model name in sysfs */ + /* FIXME: this works only in the simple case */ + c = strstr(cmdline, "root=/dev/"); + if (c) { + char rootbdev[4]; + char filename[32]; + + strncpy(rootbdev, &c[10], sizeof(rootbdev) - 1); + rootbdev[3] = '\0'; + xsprintf(filename, "/sys/block/%s/device/model", rootbdev); + + r = read_one_line_file(filename, &model); + if (r < 0) + log_info("Error reading disk model for %s: %m\n", rootbdev); + } + + /* various utsname parameters */ + r = uname(&uts); + if (r < 0) { + log_error("Error getting uname info\n"); + return -errno; + } + + /* date */ + t = time(NULL); + r = strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S %z", localtime(&t)); + assert_se(r > 0); + + /* CPU type */ + r = get_proc_field("/proc/cpuinfo", PROC_CPUINFO_MODEL, "\n", &buf); + if (r < 0) + cpu = "Unknown"; + else + cpu = buf; + + fprintf(of, "Bootchart for %s - %s\n", + uts.nodename, date); + fprintf(of, "System: %s %s %s %s\n", + uts.sysname, uts.release, uts.version, uts.machine); + fprintf(of, "CPU: %s\n", cpu); + if (model) + fprintf(of, "Disk: %s\n", model); + fprintf(of, "Boot options: %s\n", cmdline); + fprintf(of, "Build: %s\n", build); + fprintf(of, "Log start time: %.03fs\n", log_start); + fprintf(of, "Idle time: "); + + if (idletime >= 0.0) + fprintf(of, "%.03fs", idletime); + else + fprintf(of, "Not detected"); + + fprintf(of, "\n"); + fprintf(of, "Graph data: %.03f samples/sec, recorded %i total, dropped %i samples, %i processes, %i filtered\n", + arg_hz, arg_samples_len, overrun, pscount, pfiltered); + + return 0; +} + +static void svg_graph_box(FILE *of, struct list_sample_data *head, int height, double graph_start) { + double d = 0.0; + int i = 0; + double finalsample = 0.0; + struct list_sample_data *sampledata_last; + + sampledata_last = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + sampledata_last = sampledata; + } + + finalsample = sampledata_last->sampletime; + + /* outside box, fill */ + fprintf(of, "\n", + time_to_graph(0.0), + time_to_graph(finalsample - graph_start), + ps_to_graph(height)); + + for (d = graph_start; d <= finalsample; + d += (arg_scale_x < 2.0 ? 60.0 : arg_scale_x < 10.0 ? 1.0 : 0.1)) { + /* lines for each second */ + if (i % 50 == 0) + fprintf(of, " \n", + time_to_graph(d - graph_start), + time_to_graph(d - graph_start), + ps_to_graph(height)); + else if (i % 10 == 0) + fprintf(of, " \n", + time_to_graph(d - graph_start), + time_to_graph(d - graph_start), + ps_to_graph(height)); + else + fprintf(of, " \n", + time_to_graph(d - graph_start), + time_to_graph(d - graph_start), + ps_to_graph(height)); + + /* time label */ + if (i % 10 == 0) + fprintf(of, " %.01fs\n", + time_to_graph(d - graph_start), + -5.0, d - graph_start); + + i++; + } +} + +/* xml comments must not contain "--" */ +static char* xml_comment_encode(const char* name) { + char *enc_name, *p; + + enc_name = strdup(name); + if (!enc_name) + return NULL; + + for (p = enc_name; *p; p++) + if (p[0] == '-' && p[1] == '-') + p[1] = '_'; + + return enc_name; +} + +static void svg_pss_graph(FILE *of, + struct list_sample_data *head, + struct ps_struct *ps_first, + double graph_start) { + struct ps_struct *ps; + int i; + struct list_sample_data *sampledata_last; + + sampledata_last = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + sampledata_last = sampledata; + } + + + fprintf(of, "\n\n\n"); + + fprintf(of, "\n Memory allocation - Pss\n"); + + /* vsize 1000 == 1000mb */ + svg_graph_box(of, head, 100, graph_start); + /* draw some hlines for usable memory sizes */ + for (i = 100000; i < 1000000; i += 100000) { + fprintf(of, " \n", + time_to_graph(.0), + kb_to_graph(i), + time_to_graph(sampledata_last->sampletime - graph_start), + kb_to_graph(i)); + fprintf(of, " %dM\n", + time_to_graph(sampledata_last->sampletime - graph_start) + 5, + kb_to_graph(i), (1000000 - i) / 1000); + } + fprintf(of, "\n"); + + /* now plot the graph itself */ + i = 1; + prev_sampledata = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int bottom; + int top; + struct ps_sched_struct *cross_place; + + bottom = 0; + top = 0; + + /* put all the small pss blocks into the bottom */ + ps = ps_first; + while (ps->next_ps) { + ps = ps->next_ps; + if (!ps) + continue; + ps->sample = ps->first; + while (ps->sample->next) { + ps->sample = ps->sample->next; + if (ps->sample->sampledata == sampledata) + break; + } + if (ps->sample->sampledata == sampledata) { + if (ps->sample->pss <= (100 * arg_scale_y)) + top += ps->sample->pss; + break; + } + } + while (ps->sample->cross) { + cross_place = ps->sample->cross; + ps = ps->sample->cross->ps_new; + ps->sample = cross_place; + if (ps->sample->pss <= (100 * arg_scale_y)) + top += ps->sample->pss; + } + + fprintf(of, " \n", + "rgb(64,64,64)", + time_to_graph(prev_sampledata->sampletime - graph_start), + kb_to_graph(1000000.0 - top), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + kb_to_graph(top - bottom)); + bottom = top; + + /* now plot the ones that are of significant size */ + ps = ps_first; + while (ps->next_ps) { + ps = ps->next_ps; + if (!ps) + continue; + ps->sample = ps->first; + while (ps->sample->next) { + ps->sample = ps->sample->next; + if (ps->sample->sampledata == sampledata) + break; + } + /* don't draw anything smaller than 2mb */ + if (ps->sample->sampledata != sampledata) + continue; + if (ps->sample->pss > (100 * arg_scale_y)) { + top = bottom + ps->sample->pss; + fprintf(of, " \n", + colorwheel[ps->pid % 12], + time_to_graph(prev_sampledata->sampletime - graph_start), + kb_to_graph(1000000.0 - top), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + kb_to_graph(top - bottom)); + bottom = top; + } + break; + } + + while ((cross_place = ps->sample->cross)) { + ps = ps->sample->cross->ps_new; + ps->sample = cross_place; + if (ps->sample->pss > (100 * arg_scale_y)) { + top = bottom + ps->sample->pss; + fprintf(of, " \n", + colorwheel[ps->pid % 12], + time_to_graph(prev_sampledata->sampletime - graph_start), + kb_to_graph(1000000.0 - top), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + kb_to_graph(top - bottom)); + bottom = top; + } + } + + prev_sampledata = sampledata; + i++; + } + + /* overlay all the text labels */ + i = 1; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int bottom; + int top = 0; + struct ps_sched_struct *prev_sample; + struct ps_sched_struct *cross_place; + + /* put all the small pss blocks into the bottom */ + ps = ps_first->next_ps; + while (ps->next_ps) { + ps = ps->next_ps; + if (!ps) + continue; + + ps->sample = ps->first; + while (ps->sample->next) { + ps->sample = ps->sample->next; + if (ps->sample->sampledata == sampledata) + break; + } + + if (ps->sample->sampledata == sampledata) { + if (ps->sample->pss <= (100 * arg_scale_y)) + top += ps->sample->pss; + + break; + } + } + + while ((cross_place = ps->sample->cross)) { + ps = ps->sample->cross->ps_new; + ps->sample = cross_place; + if (ps->sample->pss <= (100 * arg_scale_y)) + top += ps->sample->pss; + } + bottom = top; + + /* now plot the ones that are of significant size */ + ps = ps_first; + while (ps->next_ps) { + prev_sample = ps->sample; + ps = ps->next_ps; + if (!ps) + continue; + ps->sample = ps->first; + while (ps->sample->next) { + prev_sample = ps->sample; + ps->sample = ps->sample->next; + if (ps->sample->sampledata == sampledata) + break; + } + /* don't draw anything smaller than 2mb */ + if (ps->sample->sampledata == sampledata) { + if (ps->sample->pss > (100 * arg_scale_y)) { + top = bottom + ps->sample->pss; + /* draw a label with the process / PID */ + if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y))) + fprintf(of, " %s [%i]\n", + time_to_graph(sampledata->sampletime - graph_start), + kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)), + ps->name, ps->pid); + bottom = top; + } + break; + } + } + while ((cross_place = ps->sample->cross)) { + ps = ps->sample->cross->ps_new; + ps->sample = cross_place; + prev_sample = ps->sample->prev; + if (ps->sample->pss > (100 * arg_scale_y)) { + top = bottom + ps->sample->pss; + /* draw a label with the process / PID */ + if ((i == 1) || (prev_sample->pss <= (100 * arg_scale_y))) + fprintf(of, " %s [%i]\n", + time_to_graph(sampledata->sampletime - graph_start), + kb_to_graph(1000000.0 - bottom - ((top - bottom) / 2)), + ps->name, ps->pid); + bottom = top; + } + } + + i++; + } + + /* debug output - full data dump */ + fprintf(of, "\n\n\n"); + ps = ps_first; + while (ps->next_ps) { + _cleanup_free_ char *enc_name = NULL; + ps = ps->next_ps; + if (!ps) + continue; + + enc_name = xml_comment_encode(ps->name); + if (!enc_name) + continue; + + fprintf(of, "\n"); + } + +} + +static void svg_io_bi_bar(FILE *of, + struct list_sample_data *head, + int n_samples, + double graph_start, + double interval) { + + double max = 0.0; + double range; + int max_here = 0; + int i; + int k; + struct list_sample_data *start_sampledata; + struct list_sample_data *stop_sampledata; + + fprintf(of, "\n"); + fprintf(of, "IO utilization - read\n"); + + /* + * calculate rounding range + * + * We need to round IO data since IO block data is not updated on + * each poll. Applying a smoothing function loses some burst data, + * so keep the smoothing range short. + */ + range = 0.25 / (1.0 / arg_hz); + if (range < 2.0) + range = 2.0; /* no smoothing */ + + /* surrounding box */ + svg_graph_box(of, head, 5, graph_start); + + /* find the max IO first */ + i = 1; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int start; + int stop; + int diff; + double tot; + + start = MAX(i - ((range / 2) - 1), 0); + stop = MIN(i + (range / 2), n_samples - 1); + diff = (stop - start); + + start_sampledata = sampledata; + stop_sampledata = sampledata; + + for (k = 0; k < ((range/2) - 1) && start_sampledata->link_next; k++) + start_sampledata = start_sampledata->link_next; + + for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) + stop_sampledata = stop_sampledata->link_prev; + + tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; + + if (tot > max) { + max = tot; + max_here = i; + } + + tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) / diff; + + if (tot > max) + max = tot; + + i++; + } + + /* plot bi */ + i = 1; + prev_sampledata = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int start; + int stop; + int diff; + double tot; + double pbi = 0; + + start = MAX(i - ((range / 2) - 1), 0); + stop = MIN(i + (range / 2), n_samples); + diff = (stop - start); + + start_sampledata = sampledata; + stop_sampledata = sampledata; + + for (k = 0; k < ((range/2)-1) && start_sampledata->link_next; k++) + start_sampledata = start_sampledata->link_next; + + for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) + stop_sampledata = stop_sampledata->link_prev; + + tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; + + if (max > 0) + pbi = tot / max; + + if (pbi > 0.001) + fprintf(of, "\n", + time_to_graph(prev_sampledata->sampletime - graph_start), + (arg_scale_y * 5) - (pbi * (arg_scale_y * 5)), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + pbi * (arg_scale_y * 5)); + + /* labels around highest value */ + if (i == max_here) + fprintf(of, " %0.2fmb/sec\n", + time_to_graph(sampledata->sampletime - graph_start) + 5, + ((arg_scale_y * 5) - (pbi * (arg_scale_y * 5))) + 15, + max / 1024.0 / (interval / 1000000000.0)); + + i++; + prev_sampledata = sampledata; + } +} + +static void svg_io_bo_bar(FILE *of, + struct list_sample_data *head, + int n_samples, + double graph_start, + double interval) { + double max = 0.0; + double range; + int max_here = 0; + int i; + int k; + struct list_sample_data *start_sampledata; + struct list_sample_data *stop_sampledata; + + fprintf(of, "\n"); + fprintf(of, "IO utilization - write\n"); + + /* + * calculate rounding range + * + * We need to round IO data since IO block data is not updated on + * each poll. Applying a smoothing function loses some burst data, + * so keep the smoothing range short. + */ + range = 0.25 / (1.0 / arg_hz); + if (range < 2.0) + range = 2.0; /* no smoothing */ + + /* surrounding box */ + svg_graph_box(of, head, 5, graph_start); + + /* find the max IO first */ + i = 0; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int start; + int stop; + int diff; + double tot; + + start = MAX(i - ((range / 2) - 1), 0); + stop = MIN(i + (range / 2), n_samples - 1); + diff = (stop - start); + + start_sampledata = sampledata; + stop_sampledata = sampledata; + + for (k = 0; k < (range/2) - 1 && start_sampledata->link_next; k++) + start_sampledata = start_sampledata->link_next; + + for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) + stop_sampledata = stop_sampledata->link_prev; + + tot = (double)(stop_sampledata->blockstat.bi - start_sampledata->blockstat.bi) / diff; + if (tot > max) + max = tot; + + tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) / diff; + if (tot > max) { + max = tot; + max_here = i; + } + + i++; + } + + /* plot bo */ + prev_sampledata = head; + i = 1; + + LIST_FOREACH_BEFORE(link, sampledata, head) { + int start, stop, diff; + double tot, pbo; + + pbo = 0; + + start = MAX(i - ((range / 2) - 1), 0); + stop = MIN(i + (range / 2), n_samples); + diff = (stop - start); + + start_sampledata = sampledata; + stop_sampledata = sampledata; + + for (k = 0; k < ((range/2)-1) && start_sampledata->link_next; k++) + start_sampledata = start_sampledata->link_next; + + for (k = 0; k < (range/2) && stop_sampledata->link_prev; k++) + stop_sampledata = stop_sampledata->link_prev; + + tot = (double)(stop_sampledata->blockstat.bo - start_sampledata->blockstat.bo) + / diff; + + if (max > 0) + pbo = tot / max; + + if (pbo > 0.001) + fprintf(of, "\n", + time_to_graph(prev_sampledata->sampletime - graph_start), + (arg_scale_y * 5) - (pbo * (arg_scale_y * 5)), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + pbo * (arg_scale_y * 5)); + + /* labels around highest bo value */ + if (i == max_here) + fprintf(of, " %0.2fmb/sec\n", + time_to_graph(sampledata->sampletime - graph_start) + 5, + ((arg_scale_y * 5) - (pbo * (arg_scale_y * 5))), + max / 1024.0 / (interval / 1000000000.0)); + + i++; + prev_sampledata = sampledata; + } +} + +static void svg_cpu_bar(FILE *of, struct list_sample_data *head, int n_cpus, int cpu_num, double graph_start) { + + fprintf(of, "\n"); + + if (cpu_num < 0) + fprintf(of, "CPU[overall] utilization\n"); + else + fprintf(of, "CPU[%d] utilization\n", cpu_num); + + /* surrounding box */ + svg_graph_box(of, head, 5, graph_start); + + /* bars for each sample, proportional to the CPU util. */ + prev_sampledata = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int c; + double trt; + double ptrt; + + ptrt = trt = 0.0; + + if (cpu_num < 0) + for (c = 0; c < n_cpus; c++) + trt += sampledata->runtime[c] - prev_sampledata->runtime[c]; + else + trt = sampledata->runtime[cpu_num] - prev_sampledata->runtime[cpu_num]; + + trt = trt / 1000000000.0; + + if (cpu_num < 0) + trt = trt / (double)n_cpus; + + if (trt > 0.0) + ptrt = trt / (sampledata->sampletime - prev_sampledata->sampletime); + + if (ptrt > 1.0) + ptrt = 1.0; + + if (ptrt > 0.001) + fprintf(of, "\n", + time_to_graph(prev_sampledata->sampletime - graph_start), + (arg_scale_y * 5) - (ptrt * (arg_scale_y * 5)), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + ptrt * (arg_scale_y * 5)); + + prev_sampledata = sampledata; + } +} + +static void svg_wait_bar(FILE *of, struct list_sample_data *head, int n_cpus, int cpu_num, double graph_start) { + + fprintf(of, "\n"); + + if (cpu_num < 0) + fprintf(of, "CPU[overall] wait\n"); + else + fprintf(of, "CPU[%d] wait\n", cpu_num); + + /* surrounding box */ + svg_graph_box(of, head, 5, graph_start); + + /* bars for each sample, proportional to the CPU util. */ + prev_sampledata = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + int c; + double twt; + double ptwt; + + ptwt = twt = 0.0; + + if (cpu_num < 0) + for (c = 0; c < n_cpus; c++) + twt += sampledata->waittime[c] - prev_sampledata->waittime[c]; + else + twt = sampledata->waittime[cpu_num] - prev_sampledata->waittime[cpu_num]; + + twt = twt / 1000000000.0; + + if (cpu_num < 0) + twt = twt / (double)n_cpus; + + if (twt > 0.0) + ptwt = twt / (sampledata->sampletime - prev_sampledata->sampletime); + + if (ptwt > 1.0) + ptwt = 1.0; + + if (ptwt > 0.001) + fprintf(of, "\n", + time_to_graph(prev_sampledata->sampletime - graph_start), + ((arg_scale_y * 5) - (ptwt * (arg_scale_y * 5))), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + ptwt * (arg_scale_y * 5)); + + prev_sampledata = sampledata; + } +} + +static void svg_entropy_bar(FILE *of, struct list_sample_data *head, double graph_start) { + + fprintf(of, "\n"); + + fprintf(of, "Entropy pool size\n"); + /* surrounding box */ + svg_graph_box(of, head, 5, graph_start); + + /* bars for each sample, scale 0-4096 */ + prev_sampledata = head; + LIST_FOREACH_BEFORE(link, sampledata, head) { + fprintf(of, "\n", + time_to_graph(prev_sampledata->sampletime - graph_start), + ((arg_scale_y * 5) - ((sampledata->entropy_avail / 4096.) * (arg_scale_y * 5))), + time_to_graph(sampledata->sampletime - prev_sampledata->sampletime), + (sampledata->entropy_avail / 4096.) * (arg_scale_y * 5)); + prev_sampledata = sampledata; + } +} + +static struct ps_struct *get_next_ps(struct ps_struct *ps, struct ps_struct *ps_first) { + /* + * walk the list of processes and return the next one to be + * painted + */ + if (ps == ps_first) + return ps->next_ps; + + /* go deep */ + if (ps->children) + return ps->children; + + /* find siblings */ + if (ps->next) + return ps->next; + + /* go back for parent siblings */ + for (;;) { + if (ps->parent && ps->parent->next) + return ps->parent->next; + + ps = ps->parent; + if (!ps) + return ps; + } + + return NULL; +} + +static bool ps_filter(struct ps_struct *ps) { + if (!arg_filter) + return false; + + /* can't draw data when there is only 1 sample (need start + stop) */ + if (ps->first == ps->last) + return true; + + /* don't filter kthreadd */ + if (ps->pid == 2) + return false; + + /* drop stuff that doesn't use any real CPU time */ + if (ps->total <= 0.001) + return true; + + return 0; +} + +static void svg_do_initcall(FILE *of, struct list_sample_data *head, int count_only, double graph_start) { + _cleanup_pclose_ FILE *f = NULL; + double t; + char func[256]; + int ret; + int usecs; + + /* can't plot initcall when disabled or in relative mode */ + if (!arg_initcall || arg_relative) { + kcount = 0; + return; + } + + if (!count_only) { + fprintf(of, "\n"); + fprintf(of, "Kernel init threads\n"); + /* surrounding box */ + svg_graph_box(of, head, kcount, graph_start); + } + + kcount = 0; + + /* + * Initcall graphing - parses dmesg buffer and displays kernel threads + * This somewhat uses the same methods and scaling to show processes + * but looks a lot simpler. It's overlaid entirely onto the PS graph + * when appropriate. + */ + + f = popen("dmesg", "r"); + if (!f) + return; + + while (!feof(f)) { + int c; + int z = 0; + char l[256]; + + if (fgets(l, sizeof(l) - 1, f) == NULL) + continue; + + c = sscanf(l, "[%lf] initcall %s %*s %d %*s %d %*s", + &t, func, &ret, &usecs); + if (c != 4) { + /* also parse initcalls done by module loading */ + c = sscanf(l, "[%lf] initcall %s %*s %*s %d %*s %d %*s", + &t, func, &ret, &usecs); + if (c != 4) + continue; + } + + /* chop the +0xXX/0xXX stuff */ + while(func[z] != '+') + z++; + func[z] = 0; + + if (count_only) { + /* filter out irrelevant stuff */ + if (usecs >= 1000) + kcount++; + continue; + } + + fprintf(of, "\n", + func, t, usecs, ret); + + if (usecs < 1000) + continue; + + /* rect */ + fprintf(of, " \n", + time_to_graph(t - (usecs / 1000000.0)), + ps_to_graph(kcount), + time_to_graph(usecs / 1000000.0), + ps_to_graph(1)); + + /* label */ + fprintf(of, " %s %.03fs\n", + time_to_graph(t - (usecs / 1000000.0)) + 5, + ps_to_graph(kcount) + 15, + func, usecs / 1000000.0); + + kcount++; + } +} + +static void svg_ps_bars(FILE *of, + struct list_sample_data *head, + int n_samples, + int n_cpus, + struct ps_struct *ps_first, + double graph_start, + double interval) { + + struct ps_struct *ps; + int i = 0; + int j = 0; + int pid; + double w = 0.0; + + fprintf(of, "\n"); + fprintf(of, "Processes\n"); + + /* surrounding box */ + svg_graph_box(of, head, pcount, graph_start); + + /* pass 2 - ps boxes */ + ps = ps_first; + while ((ps = get_next_ps(ps, ps_first))) { + _cleanup_free_ char *enc_name = NULL, *escaped = NULL; + double endtime; + double starttime; + int t; + + if (!utf8_is_printable(ps->name, strlen(ps->name))) + escaped = utf8_escape_non_printable(ps->name); + + enc_name = xml_comment_encode(escaped ? escaped : ps->name); + if (!enc_name) + continue; + + /* leave some trace of what we actually filtered etc. */ + fprintf(of, "\n", enc_name, ps->pid, + ps->ppid, ps->total); + + starttime = ps->first->sampledata->sampletime; + + if (!ps_filter(ps)) { + /* remember where _to_ our children need to draw a line */ + ps->pos_x = time_to_graph(starttime - graph_start); + ps->pos_y = ps_to_graph(j+1); /* bottom left corner */ + } else if (ps->parent){ + /* hook children to our parent coords instead */ + ps->pos_x = ps->parent->pos_x; + ps->pos_y = ps->parent->pos_y; + + /* if this is the last child, we might still need to draw a connecting line */ + if ((!ps->next) && (ps->parent)) + fprintf(of, " \n", + ps->parent->pos_x, + ps_to_graph(j-1) + 10.0, /* whee, use the last value here */ + ps->parent->pos_x, + ps->parent->pos_y); + continue; + } + + endtime = ps->last->sampledata->sampletime; + fprintf(of, " \n", + time_to_graph(starttime - graph_start), + ps_to_graph(j), + time_to_graph(ps->last->sampledata->sampletime - starttime), + ps_to_graph(1)); + + /* paint cpu load over these */ + ps->sample = ps->first; + t = 1; + while (ps->sample->next) { + double rt, prt; + double wt, wrt; + struct ps_sched_struct *prev; + + prev = ps->sample; + ps->sample = ps->sample->next; + + /* calculate over interval */ + rt = ps->sample->runtime - prev->runtime; + wt = ps->sample->waittime - prev->waittime; + + prt = (rt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime); + wrt = (wt / 1000000000) / (ps->sample->sampledata->sampletime - prev->sampledata->sampletime); + + /* this can happen if timekeeping isn't accurate enough */ + if (prt > 1.0) + prt = 1.0; + if (wrt > 1.0) + wrt = 1.0; + + if ((prt < 0.1) && (wrt < 0.1)) /* =~ 26 (color threshold) */ + continue; + + fprintf(of, " \n", + time_to_graph(prev->sampledata->sampletime - graph_start), + ps_to_graph(j), + time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime), + ps_to_graph(wrt)); + + /* draw cpu over wait - TODO figure out how/why run + wait > interval */ + fprintf(of, " \n", + time_to_graph(prev->sampledata->sampletime - graph_start), + ps_to_graph(j + (1.0 - prt)), + time_to_graph(ps->sample->sampledata->sampletime - prev->sampledata->sampletime), + ps_to_graph(prt)); + t++; + } + + /* determine where to display the process name */ + if ((endtime - starttime) < 1.5) + /* too small to fit label inside the box */ + w = endtime; + else + w = starttime; + + /* text label of process name */ + fprintf(of, " %s [%i]%.03fs %s\n", + time_to_graph(w - graph_start) + 5.0, + ps_to_graph(j) + 14.0, + escaped ? escaped : ps->name, + ps->pid, + (ps->last->runtime - ps->first->runtime) / 1000000000.0, + arg_show_cgroup ? ps->cgroup : ""); + /* paint lines to the parent process */ + if (ps->parent) { + /* horizontal part */ + fprintf(of, " \n", + time_to_graph(starttime - graph_start), + ps_to_graph(j) + 10.0, + ps->parent->pos_x, + ps_to_graph(j) + 10.0); + + /* one vertical line connecting all the horizontal ones up */ + if (!ps->next) + fprintf(of, " \n", + ps->parent->pos_x, + ps_to_graph(j) + 10.0, + ps->parent->pos_x, + ps->parent->pos_y); + } + + j++; /* count boxes */ + + fprintf(of, "\n"); + } + + /* last pass - determine when idle */ + pid = getpid(); + /* make sure we start counting from the point where we actually have + * data: assume that bootchart's first sample is when data started + */ + + ps = ps_first; + while (ps->next_ps) { + ps = ps->next_ps; + if (ps->pid == pid) + break; + } + + /* need to know last node first */ + ps->sample = ps->first; + i = ps->sample->next->sampledata->counter; + + while (ps->sample->next && i<(n_samples-(arg_hz/2))) { + double crt; + double brt; + int c; + int ii; + struct ps_sched_struct *sample_hz; + + ps->sample = ps->sample->next; + sample_hz = ps->sample; + for (ii = 0; (ii < (int)arg_hz/2) && sample_hz->next; ii++) + sample_hz = sample_hz->next; + + /* subtract bootchart cpu utilization from total */ + crt = 0.0; + for (c = 0; c < n_cpus; c++) + crt += sample_hz->sampledata->runtime[c] - ps->sample->sampledata->runtime[c]; + + brt = sample_hz->runtime - ps->sample->runtime; + /* + * our definition of "idle": + * + * if for (hz / 2) we've used less CPU than (interval / 2) ... + * defaults to 4.0%, which experimentally, is where atom idles + */ + if ((crt - brt) < (interval / 2.0)) { + idletime = ps->sample->sampledata->sampletime - graph_start; + fprintf(of, "\n\n", idletime); + fprintf(of, "\n", + time_to_graph(idletime), + -arg_scale_y, + time_to_graph(idletime), + ps_to_graph(pcount) + arg_scale_y); + fprintf(of, "%.01fs\n", + time_to_graph(idletime) + 5.0, + ps_to_graph(pcount) + arg_scale_y, + idletime); + break; + } + + i++; + } +} + +static void svg_top_ten_cpu(FILE *of, struct ps_struct *ps_first) { + struct ps_struct *top[10]; + struct ps_struct emptyps = {}; + struct ps_struct *ps; + int n, m; + + for (n = 0; n < (int) ELEMENTSOF(top); n++) + top[n] = &emptyps; + + /* walk all ps's and setup ptrs */ + ps = ps_first; + while ((ps = get_next_ps(ps, ps_first))) { + for (n = 0; n < 10; n++) { + if (ps->total <= top[n]->total) + continue; + /* cascade insert */ + for (m = 9; m > n; m--) + top[m] = top[m-1]; + top[n] = ps; + break; + } + } + + fprintf(of, "Top CPU consumers:\n"); + for (n = 0; n < 10; n++) + fprintf(of, "%3.03fs - %s [%d]\n", + 20 + (n * 13), + top[n]->total, + top[n]->name, + top[n]->pid); +} + +static void svg_top_ten_pss(FILE *of, struct ps_struct *ps_first) { + struct ps_struct *top[10]; + struct ps_struct emptyps = {}; + struct ps_struct *ps; + int n, m; + + for (n = 0; n < (int) ELEMENTSOF(top); n++) + top[n] = &emptyps; + + /* walk all ps's and setup ptrs */ + ps = ps_first; + while ((ps = get_next_ps(ps, ps_first))) { + for (n = 0; n < 10; n++) { + if (ps->pss_max <= top[n]->pss_max) + continue; + + /* cascade insert */ + for (m = 9; m > n; m--) + top[m] = top[m-1]; + top[n] = ps; + break; + } + } + + fprintf(of, "Top PSS consumers:\n"); + for (n = 0; n < 10; n++) + fprintf(of, "%dK - %s [%d]\n", + 20 + (n * 13), + top[n]->pss_max, + top[n]->name, + top[n]->pid); +} + +int svg_do(FILE *of, + const char *build, + struct list_sample_data *head, + struct ps_struct *ps_first, + int n_samples, + int pscount, + int n_cpus, + double graph_start, + double log_start, + double interval, + int overrun) { + + struct ps_struct *ps; + double offset = 7; + int r, c; + + sampledata = head; + LIST_FIND_TAIL(link, sampledata, head); + ps = ps_first; + + /* count initcall thread count first */ + svg_do_initcall(of, head, 1, graph_start); + ksize = kcount ? ps_to_graph(kcount) + (arg_scale_y * 2) : 0; + + /* then count processes */ + while ((ps = get_next_ps(ps, ps_first))) { + if (!ps_filter(ps)) + pcount++; + else + pfiltered++; + } + psize = ps_to_graph(pcount) + (arg_scale_y * 2); + + esize = (arg_entropy ? arg_scale_y * 7 : 0); + + /* after this, we can draw the header with proper sizing */ + svg_header(of, head, graph_start, arg_percpu ? n_cpus : 0); + fprintf(of, "\n\n"); + + fprintf(of, "\n"); + svg_io_bi_bar(of, head, n_samples, graph_start, interval); + fprintf(of, "\n\n"); + + fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); + svg_io_bo_bar(of, head, n_samples, graph_start, interval); + fprintf(of, "\n\n"); + + for (c = -1; c < (arg_percpu ? n_cpus : 0); c++) { + offset += 7; + fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); + svg_cpu_bar(of, head, n_cpus, c, graph_start); + fprintf(of, "\n\n"); + + offset += 7; + fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); + svg_wait_bar(of, head, n_cpus, c, graph_start); + fprintf(of, "\n\n"); + } + + if (kcount) { + offset += 7; + fprintf(of, "\n", 400.0 + (arg_scale_y * offset)); + svg_do_initcall(of, head, 0, graph_start); + fprintf(of, "\n\n"); + } + + offset += 7; + fprintf(of, "\n", 400.0 + (arg_scale_y * offset) + ksize); + svg_ps_bars(of, head, n_samples, n_cpus, ps_first, graph_start, interval); + fprintf(of, "\n\n"); + + fprintf(of, "\n"); + r = svg_title(of, build, pscount, log_start, overrun); + fprintf(of, "\n\n"); + + if (r < 0) + return r; + + fprintf(of, "\n"); + svg_top_ten_cpu(of, ps_first); + fprintf(of, "\n\n"); + + if (arg_entropy) { + fprintf(of, "\n", 400.0 + (arg_scale_y * offset) + ksize + psize); + svg_entropy_bar(of, head, graph_start); + fprintf(of, "\n\n"); + } + + if (arg_pss) { + fprintf(of, "\n", 400.0 + (arg_scale_y * offset) + ksize + psize + esize); + svg_pss_graph(of, head, ps_first, graph_start); + fprintf(of, "\n\n"); + + fprintf(of, "\n"); + svg_top_ten_pss(of, ps_first); + fprintf(of, "\n\n"); + } + + /* fprintf footer */ + fprintf(of, "\n\n"); + + return 0; +} diff --git a/src/systemd-bootchart/svg.h b/src/systemd-bootchart/svg.h new file mode 100644 index 0000000000..6e06b5ad97 --- /dev/null +++ b/src/systemd-bootchart/svg.h @@ -0,0 +1,35 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2009-2013 Intel Corporation + + Authors: + Auke Kok + + 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 . +***/ + +int svg_do(FILE *of, + const char *build, + struct list_sample_data *head, + struct ps_struct *ps_first, + int n_samples, + int pscount, + int n_cpus, + double graph_start, + double log_start, + double interval, + int overrun); diff --git a/src/systemd-cgls/Makefile b/src/systemd-cgls/Makefile new file mode 100644 index 0000000000..72f43638b1 --- /dev/null +++ b/src/systemd-cgls/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_cgls_SOURCES = \ + src/cgls/cgls.c + +systemd_cgls_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-cgls/cgls.c b/src/systemd-cgls/cgls.c new file mode 100644 index 0000000000..5862cdb282 --- /dev/null +++ b/src/systemd-cgls/cgls.c @@ -0,0 +1,287 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "cgroup-show.h" +#include "cgroup-util.h" +#include "fileio.h" +#include "log.h" +#include "output-mode.h" +#include "pager.h" +#include "path-util.h" +#include "unit-name.h" +#include "util.h" + +static bool arg_no_pager = false; +static bool arg_kernel_threads = false; +static bool arg_all = false; +static int arg_full = -1; +static char* arg_machine = NULL; + +static void help(void) { + printf("%s [OPTIONS...] [CGROUP...]\n\n" + "Recursively show control group contents.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " -a --all Show all groups, including empty\n" + " -l --full Do not ellipsize output\n" + " -k Include kernel threads in output\n" + " -M --machine= Show container\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_NO_PAGER = 0x100, + ARG_VERSION, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "all", no_argument, NULL, 'a' }, + { "full", no_argument, NULL, 'l' }, + { "machine", required_argument, NULL, 'M' }, + {} + }; + + int c; + + assert(argc >= 1); + assert(argv); + + while ((c = getopt_long(argc, argv, "hkalM:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 'a': + arg_all = true; + break; + + case 'l': + arg_full = true; + break; + + case 'k': + arg_kernel_threads = true; + break; + + case 'M': + arg_machine = optarg; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int get_cgroup_root(char **ret) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *unit = NULL, *path = NULL; + const char *m; + int r; + + if (!arg_machine) { + r = cg_get_root_path(ret); + if (r == -ENOMEDIUM) + return log_error_errno(r, "Failed to get root control group path: No cgroup filesystem mounted on /sys/fs/cgroup"); + else if (r < 0) + return log_error_errno(r, "Failed to get root control group path: %m"); + + return 0; + } + + m = strjoina("/run/systemd/machines/", arg_machine); + r = parse_env_file(m, NEWLINE, "SCOPE", &unit, NULL); + if (r < 0) + return log_error_errno(r, "Failed to load machine data: %m"); + + path = unit_dbus_path_from_name(unit); + if (!path) + return log_oom(); + + r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, false, &bus); + if (r < 0) + return log_error_errno(r, "Failed to create bus connection: %m"); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + path, + unit_dbus_interface_from_name(unit), + "ControlGroup", + &error, + ret); + if (r < 0) + return log_error_errno(r, "Failed to query unit control group path: %s", bus_error_message(&error, r)); + + return 0; +} + +static void show_cg_info(const char *controller, const char *path) { + + if (cg_unified() <= 0 && controller && !streq(controller, SYSTEMD_CGROUP_CONTROLLER)) + printf("Controller %s; ", controller); + + printf("Control group %s:\n", isempty(path) ? "/" : path); + fflush(stdout); +} + +int main(int argc, char *argv[]) { + int r, output_flags; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (!arg_no_pager) { + r = pager_open(false); + if (r > 0 && arg_full < 0) + arg_full = true; + } + + output_flags = + arg_all * OUTPUT_SHOW_ALL | + (arg_full > 0) * OUTPUT_FULL_WIDTH; + + if (optind < argc) { + _cleanup_free_ char *root = NULL; + int i; + + r = get_cgroup_root(&root); + if (r < 0) + goto finish; + + for (i = optind; i < argc; i++) { + int q; + + if (path_startswith(argv[i], "/sys/fs/cgroup")) { + + printf("Directory %s:\n", argv[i]); + fflush(stdout); + + q = show_cgroup_by_path(argv[i], NULL, 0, arg_kernel_threads, output_flags); + } else { + _cleanup_free_ char *c = NULL, *p = NULL, *j = NULL; + const char *controller, *path; + + r = cg_split_spec(argv[i], &c, &p); + if (r < 0) { + log_error_errno(r, "Failed to split argument %s: %m", argv[i]); + goto finish; + } + + controller = c ?: SYSTEMD_CGROUP_CONTROLLER; + if (p) { + j = strjoin(root, "/", p, NULL); + if (!j) { + r = log_oom(); + goto finish; + } + + path_kill_slashes(j); + path = j; + } else + path = root; + + show_cg_info(controller, path); + + q = show_cgroup(controller, path, NULL, 0, arg_kernel_threads, output_flags); + } + + if (q < 0) + r = q; + } + + } else { + bool done = false; + + if (!arg_machine) { + _cleanup_free_ char *cwd = NULL; + + cwd = get_current_dir_name(); + if (!cwd) { + r = log_error_errno(errno, "Cannot determine current working directory: %m"); + goto finish; + } + + if (path_startswith(cwd, "/sys/fs/cgroup")) { + printf("Working directory %s:\n", cwd); + fflush(stdout); + + r = show_cgroup_by_path(cwd, NULL, 0, arg_kernel_threads, output_flags); + done = true; + } + } + + if (!done) { + _cleanup_free_ char *root = NULL; + + r = get_cgroup_root(&root); + if (r < 0) + goto finish; + + show_cg_info(SYSTEMD_CGROUP_CONTROLLER, root); + + printf("-.slice\n"); + r = show_cgroup(SYSTEMD_CGROUP_CONTROLLER, root, NULL, 0, arg_kernel_threads, output_flags); + } + } + + if (r < 0) + log_error_errno(r, "Failed to list cgroup tree: %m"); + +finish: + pager_close(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-cgroups-agent/Makefile b/src/systemd-cgroups-agent/Makefile new file mode 100644 index 0000000000..5c7d9d03ab --- /dev/null +++ b/src/systemd-cgroups-agent/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_cgroups_agent_SOURCES = \ + src/cgroups-agent/cgroups-agent.c + +systemd_cgroups_agent_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-cgroups-agent/cgroups-agent.c b/src/systemd-cgroups-agent/cgroups-agent.c new file mode 100644 index 0000000000..79098b5f2c --- /dev/null +++ b/src/systemd-cgroups-agent/cgroups-agent.c @@ -0,0 +1,65 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include + +#include "bus-util.h" +#include "log.h" + +int main(int argc, char *argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + if (argc != 2) { + log_error("Incorrect number of arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + /* We send this event to the private D-Bus socket and then the + * system instance will forward this to the system bus. We do + * this to avoid an activation loop when we start dbus when we + * are called when the dbus service is shut down. */ + + r = bus_connect_system_systemd(&bus); + if (r < 0) { + /* If we couldn't connect we assume this was triggered + * while systemd got restarted/transitioned from + * initrd to the system, so let's ignore this */ + log_debug_errno(r, "Failed to get D-Bus connection: %m"); + return EXIT_FAILURE; + } + + r = sd_bus_emit_signal(bus, + "/org/freedesktop/systemd1/agent", + "org.freedesktop.systemd1.Agent", + "Released", + "s", argv[1]); + if (r < 0) { + log_debug_errno(r, "Failed to send signal message on private connection: %m"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/src/systemd-cgtop/Makefile b/src/systemd-cgtop/Makefile new file mode 100644 index 0000000000..5e379895a8 --- /dev/null +++ b/src/systemd-cgtop/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_cgtop_SOURCES = \ + src/cgtop/cgtop.c + +systemd_cgtop_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-cgtop/cgtop.c b/src/systemd-cgtop/cgtop.c new file mode 100644 index 0000000000..5d021e5ef4 --- /dev/null +++ b/src/systemd-cgtop/cgtop.c @@ -0,0 +1,1097 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "cgroup-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hashmap.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "stdio-util.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "util.h" + +typedef struct Group { + char *path; + + bool n_tasks_valid:1; + bool cpu_valid:1; + bool memory_valid:1; + bool io_valid:1; + + uint64_t n_tasks; + + unsigned cpu_iteration; + nsec_t cpu_usage; + nsec_t cpu_timestamp; + double cpu_fraction; + + uint64_t memory; + + unsigned io_iteration; + uint64_t io_input, io_output; + nsec_t io_timestamp; + uint64_t io_input_bps, io_output_bps; +} Group; + +static unsigned arg_depth = 3; +static unsigned arg_iterations = (unsigned) -1; +static bool arg_batch = false; +static bool arg_raw = false; +static usec_t arg_delay = 1*USEC_PER_SEC; +static char* arg_machine = NULL; + +enum { + COUNT_PIDS, + COUNT_USERSPACE_PROCESSES, + COUNT_ALL_PROCESSES, +} arg_count = COUNT_PIDS; +static bool arg_recursive = true; + +static enum { + ORDER_PATH, + ORDER_TASKS, + ORDER_CPU, + ORDER_MEMORY, + ORDER_IO, +} arg_order = ORDER_CPU; + +static enum { + CPU_PERCENT, + CPU_TIME, +} arg_cpu_type = CPU_PERCENT; + +static void group_free(Group *g) { + assert(g); + + free(g->path); + free(g); +} + +static void group_hashmap_clear(Hashmap *h) { + Group *g; + + while ((g = hashmap_steal_first(h))) + group_free(g); +} + +static void group_hashmap_free(Hashmap *h) { + group_hashmap_clear(h); + hashmap_free(h); +} + +static const char *maybe_format_bytes(char *buf, size_t l, bool is_valid, uint64_t t) { + if (!is_valid) + return "-"; + if (arg_raw) { + snprintf(buf, l, "%jd", t); + return buf; + } + return format_bytes(buf, l, t); +} + +static int process( + const char *controller, + const char *path, + Hashmap *a, + Hashmap *b, + unsigned iteration, + Group **ret) { + + Group *g; + int r; + + assert(controller); + assert(path); + assert(a); + + g = hashmap_get(a, path); + if (!g) { + g = hashmap_get(b, path); + if (!g) { + g = new0(Group, 1); + if (!g) + return -ENOMEM; + + g->path = strdup(path); + if (!g->path) { + group_free(g); + return -ENOMEM; + } + + r = hashmap_put(a, g->path, g); + if (r < 0) { + group_free(g); + return r; + } + } else { + r = hashmap_move_one(a, b, path); + if (r < 0) + return r; + + g->cpu_valid = g->memory_valid = g->io_valid = g->n_tasks_valid = false; + } + } + + if (streq(controller, SYSTEMD_CGROUP_CONTROLLER) && IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES)) { + _cleanup_fclose_ FILE *f = NULL; + pid_t pid; + + r = cg_enumerate_processes(controller, path, &f); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + g->n_tasks = 0; + while (cg_read_pid(f, &pid) > 0) { + + if (arg_count == COUNT_USERSPACE_PROCESSES && is_kernel_thread(pid) > 0) + continue; + + g->n_tasks++; + } + + if (g->n_tasks > 0) + g->n_tasks_valid = true; + + } else if (streq(controller, "pids") && arg_count == COUNT_PIDS) { + _cleanup_free_ char *p = NULL, *v = NULL; + + r = cg_get_path(controller, path, "pids.current", &p); + if (r < 0) + return r; + + r = read_one_line_file(p, &v); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + r = safe_atou64(v, &g->n_tasks); + if (r < 0) + return r; + + if (g->n_tasks > 0) + g->n_tasks_valid = true; + + } else if (streq(controller, "cpuacct") && cg_unified() <= 0) { + _cleanup_free_ char *p = NULL, *v = NULL; + uint64_t new_usage; + nsec_t timestamp; + + r = cg_get_path(controller, path, "cpuacct.usage", &p); + if (r < 0) + return r; + + r = read_one_line_file(p, &v); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + r = safe_atou64(v, &new_usage); + if (r < 0) + return r; + + timestamp = now_nsec(CLOCK_MONOTONIC); + + if (g->cpu_iteration == iteration - 1 && + (nsec_t) new_usage > g->cpu_usage) { + + nsec_t x, y; + + x = timestamp - g->cpu_timestamp; + if (x < 1) + x = 1; + + y = (nsec_t) new_usage - g->cpu_usage; + g->cpu_fraction = (double) y / (double) x; + g->cpu_valid = true; + } + + g->cpu_usage = (nsec_t) new_usage; + g->cpu_timestamp = timestamp; + g->cpu_iteration = iteration; + + } else if (streq(controller, "memory")) { + _cleanup_free_ char *p = NULL, *v = NULL; + + if (cg_unified() <= 0) + r = cg_get_path(controller, path, "memory.usage_in_bytes", &p); + else + r = cg_get_path(controller, path, "memory.current", &p); + if (r < 0) + return r; + + r = read_one_line_file(p, &v); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + r = safe_atou64(v, &g->memory); + if (r < 0) + return r; + + if (g->memory > 0) + g->memory_valid = true; + + } else if (streq(controller, "blkio") && cg_unified() <= 0) { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *p = NULL; + uint64_t wr = 0, rd = 0; + nsec_t timestamp; + + r = cg_get_path(controller, path, "blkio.io_service_bytes", &p); + if (r < 0) + return r; + + f = fopen(p, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + return -errno; + } + + for (;;) { + char line[LINE_MAX], *l; + uint64_t k, *q; + + if (!fgets(line, sizeof(line), f)) + break; + + l = strstrip(line); + l += strcspn(l, WHITESPACE); + l += strspn(l, WHITESPACE); + + if (first_word(l, "Read")) { + l += 4; + q = &rd; + } else if (first_word(l, "Write")) { + l += 5; + q = ≀ + } else + continue; + + l += strspn(l, WHITESPACE); + r = safe_atou64(l, &k); + if (r < 0) + continue; + + *q += k; + } + + timestamp = now_nsec(CLOCK_MONOTONIC); + + if (g->io_iteration == iteration - 1) { + uint64_t x, yr, yw; + + x = (uint64_t) (timestamp - g->io_timestamp); + if (x < 1) + x = 1; + + if (rd > g->io_input) + yr = rd - g->io_input; + else + yr = 0; + + if (wr > g->io_output) + yw = wr - g->io_output; + else + yw = 0; + + if (yr > 0 || yw > 0) { + g->io_input_bps = (yr * 1000000000ULL) / x; + g->io_output_bps = (yw * 1000000000ULL) / x; + g->io_valid = true; + } + } + + g->io_input = rd; + g->io_output = wr; + g->io_timestamp = timestamp; + g->io_iteration = iteration; + } + + if (ret) + *ret = g; + + return 0; +} + +static int refresh_one( + const char *controller, + const char *path, + Hashmap *a, + Hashmap *b, + unsigned iteration, + unsigned depth, + Group **ret) { + + _cleanup_closedir_ DIR *d = NULL; + Group *ours; + int r; + + assert(controller); + assert(path); + assert(a); + + if (depth > arg_depth) + return 0; + + r = process(controller, path, a, b, iteration, &ours); + if (r < 0) + return r; + + r = cg_enumerate_subgroups(controller, path, &d); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + for (;;) { + _cleanup_free_ char *fn = NULL, *p = NULL; + Group *child = NULL; + + r = cg_read_subgroup(d, &fn); + if (r < 0) + return r; + if (r == 0) + break; + + p = strjoin(path, "/", fn, NULL); + if (!p) + return -ENOMEM; + + path_kill_slashes(p); + + r = refresh_one(controller, p, a, b, iteration, depth + 1, &child); + if (r < 0) + return r; + + if (arg_recursive && + IN_SET(arg_count, COUNT_ALL_PROCESSES, COUNT_USERSPACE_PROCESSES) && + child && + child->n_tasks_valid && + streq(controller, SYSTEMD_CGROUP_CONTROLLER)) { + + /* Recursively sum up processes */ + + if (ours->n_tasks_valid) + ours->n_tasks += child->n_tasks; + else { + ours->n_tasks = child->n_tasks; + ours->n_tasks_valid = true; + } + } + } + + if (ret) + *ret = ours; + + return 1; +} + +static int refresh(const char *root, Hashmap *a, Hashmap *b, unsigned iteration) { + int r; + + assert(a); + + r = refresh_one(SYSTEMD_CGROUP_CONTROLLER, root, a, b, iteration, 0, NULL); + if (r < 0) + return r; + r = refresh_one("cpuacct", root, a, b, iteration, 0, NULL); + if (r < 0) + return r; + r = refresh_one("memory", root, a, b, iteration, 0, NULL); + if (r < 0) + return r; + r = refresh_one("blkio", root, a, b, iteration, 0, NULL); + if (r < 0) + return r; + r = refresh_one("pids", root, a, b, iteration, 0, NULL); + if (r < 0) + return r; + + return 0; +} + +static int group_compare(const void*a, const void *b) { + const Group *x = *(Group**)a, *y = *(Group**)b; + + if (arg_order != ORDER_TASKS || arg_recursive) { + /* Let's make sure that the parent is always before + * the child. Except when ordering by tasks and + * recursive summing is off, since that is actually + * not accumulative for all children. */ + + if (path_startswith(y->path, x->path)) + return -1; + if (path_startswith(x->path, y->path)) + return 1; + } + + switch (arg_order) { + + case ORDER_PATH: + break; + + case ORDER_CPU: + if (arg_cpu_type == CPU_PERCENT) { + if (x->cpu_valid && y->cpu_valid) { + if (x->cpu_fraction > y->cpu_fraction) + return -1; + else if (x->cpu_fraction < y->cpu_fraction) + return 1; + } else if (x->cpu_valid) + return -1; + else if (y->cpu_valid) + return 1; + } else { + if (x->cpu_usage > y->cpu_usage) + return -1; + else if (x->cpu_usage < y->cpu_usage) + return 1; + } + + break; + + case ORDER_TASKS: + if (x->n_tasks_valid && y->n_tasks_valid) { + if (x->n_tasks > y->n_tasks) + return -1; + else if (x->n_tasks < y->n_tasks) + return 1; + } else if (x->n_tasks_valid) + return -1; + else if (y->n_tasks_valid) + return 1; + + break; + + case ORDER_MEMORY: + if (x->memory_valid && y->memory_valid) { + if (x->memory > y->memory) + return -1; + else if (x->memory < y->memory) + return 1; + } else if (x->memory_valid) + return -1; + else if (y->memory_valid) + return 1; + + break; + + case ORDER_IO: + if (x->io_valid && y->io_valid) { + if (x->io_input_bps + x->io_output_bps > y->io_input_bps + y->io_output_bps) + return -1; + else if (x->io_input_bps + x->io_output_bps < y->io_input_bps + y->io_output_bps) + return 1; + } else if (x->io_valid) + return -1; + else if (y->io_valid) + return 1; + } + + return path_compare(x->path, y->path); +} + +static void display(Hashmap *a) { + Iterator i; + Group *g; + Group **array; + signed path_columns; + unsigned rows, n = 0, j, maxtcpu = 0, maxtpath = 3; /* 3 for ellipsize() to work properly */ + char buffer[MAX3(21, FORMAT_BYTES_MAX, FORMAT_TIMESPAN_MAX)]; + + assert(a); + + if (on_tty()) + fputs(ANSI_HOME_CLEAR, stdout); + + array = alloca(sizeof(Group*) * hashmap_size(a)); + + HASHMAP_FOREACH(g, a, i) + if (g->n_tasks_valid || g->cpu_valid || g->memory_valid || g->io_valid) + array[n++] = g; + + qsort_safe(array, n, sizeof(Group*), group_compare); + + /* Find the longest names in one run */ + for (j = 0; j < n; j++) { + unsigned cputlen, pathtlen; + + format_timespan(buffer, sizeof(buffer), (usec_t) (array[j]->cpu_usage / NSEC_PER_USEC), 0); + cputlen = strlen(buffer); + maxtcpu = MAX(maxtcpu, cputlen); + + pathtlen = strlen(array[j]->path); + maxtpath = MAX(maxtpath, pathtlen); + } + + if (arg_cpu_type == CPU_PERCENT) + xsprintf(buffer, "%6s", "%CPU"); + else + xsprintf(buffer, "%*s", maxtcpu, "CPU Time"); + + rows = lines(); + if (rows <= 10) + rows = 10; + + if (on_tty()) { + const char *on, *off; + + path_columns = columns() - 36 - strlen(buffer); + if (path_columns < 10) + path_columns = 10; + + on = ansi_highlight_underline(); + off = ansi_underline(); + + printf("%s%s%-*s%s %s%7s%s %s%s%s %s%8s%s %s%8s%s %s%8s%s%s\n", + ansi_underline(), + arg_order == ORDER_PATH ? on : "", path_columns, "Control Group", + arg_order == ORDER_PATH ? off : "", + arg_order == ORDER_TASKS ? on : "", arg_count == COUNT_PIDS ? "Tasks" : arg_count == COUNT_USERSPACE_PROCESSES ? "Procs" : "Proc+", + arg_order == ORDER_TASKS ? off : "", + arg_order == ORDER_CPU ? on : "", buffer, + arg_order == ORDER_CPU ? off : "", + arg_order == ORDER_MEMORY ? on : "", "Memory", + arg_order == ORDER_MEMORY ? off : "", + arg_order == ORDER_IO ? on : "", "Input/s", + arg_order == ORDER_IO ? off : "", + arg_order == ORDER_IO ? on : "", "Output/s", + arg_order == ORDER_IO ? off : "", + ansi_normal()); + } else + path_columns = maxtpath; + + for (j = 0; j < n; j++) { + _cleanup_free_ char *ellipsized = NULL; + const char *path; + + if (on_tty() && j + 6 > rows) + break; + + g = array[j]; + + path = isempty(g->path) ? "/" : g->path; + ellipsized = ellipsize(path, path_columns, 33); + printf("%-*s", path_columns, ellipsized ?: path); + + if (g->n_tasks_valid) + printf(" %7" PRIu64, g->n_tasks); + else + fputs(" -", stdout); + + if (arg_cpu_type == CPU_PERCENT) { + if (g->cpu_valid) + printf(" %6.1f", g->cpu_fraction*100); + else + fputs(" -", stdout); + } else + printf(" %*s", maxtcpu, format_timespan(buffer, sizeof(buffer), (usec_t) (g->cpu_usage / NSEC_PER_USEC), 0)); + + printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->memory_valid, g->memory)); + printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_input_bps)); + printf(" %8s", maybe_format_bytes(buffer, sizeof(buffer), g->io_valid, g->io_output_bps)); + + putchar('\n'); + } +} + +static void help(void) { + printf("%s [OPTIONS...]\n\n" + "Show top control groups by their resource usage.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -p --order=path Order by path\n" + " -t --order=tasks Order by number of tasks/processes\n" + " -c --order=cpu Order by CPU load (default)\n" + " -m --order=memory Order by memory load\n" + " -i --order=io Order by IO load\n" + " -r --raw Provide raw (not human-readable) numbers\n" + " --cpu=percentage Show CPU usage as percentage (default)\n" + " --cpu=time Show CPU usage as time\n" + " -P Count userspace processes instead of tasks (excl. kernel)\n" + " -k Count all processes instead of tasks (incl. kernel)\n" + " --recursive=BOOL Sum up process count recursively\n" + " -d --delay=DELAY Delay between updates\n" + " -n --iterations=N Run for N iterations before exiting\n" + " -b --batch Run in batch mode, accepting no input\n" + " --depth=DEPTH Maximum traversal depth (default: %u)\n" + " -M --machine= Show container\n" + , program_invocation_short_name, arg_depth); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_DEPTH, + ARG_CPU_TYPE, + ARG_ORDER, + ARG_RECURSIVE, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "delay", required_argument, NULL, 'd' }, + { "iterations", required_argument, NULL, 'n' }, + { "batch", no_argument, NULL, 'b' }, + { "raw", no_argument, NULL, 'r' }, + { "depth", required_argument, NULL, ARG_DEPTH }, + { "cpu", optional_argument, NULL, ARG_CPU_TYPE }, + { "order", required_argument, NULL, ARG_ORDER }, + { "recursive", required_argument, NULL, ARG_RECURSIVE }, + { "machine", required_argument, NULL, 'M' }, + {} + }; + + bool recursive_unset = false; + int c, r; + + assert(argc >= 1); + assert(argv); + + while ((c = getopt_long(argc, argv, "hptcmin:brd:kPM:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_CPU_TYPE: + if (optarg) { + if (streq(optarg, "time")) + arg_cpu_type = CPU_TIME; + else if (streq(optarg, "percentage")) + arg_cpu_type = CPU_PERCENT; + else { + log_error("Unknown argument to --cpu=: %s", optarg); + return -EINVAL; + } + } else + arg_cpu_type = CPU_TIME; + + break; + + case ARG_DEPTH: + r = safe_atou(optarg, &arg_depth); + if (r < 0) { + log_error("Failed to parse depth parameter."); + return -EINVAL; + } + + break; + + case 'd': + r = parse_sec(optarg, &arg_delay); + if (r < 0 || arg_delay <= 0) { + log_error("Failed to parse delay parameter."); + return -EINVAL; + } + + break; + + case 'n': + r = safe_atou(optarg, &arg_iterations); + if (r < 0) { + log_error("Failed to parse iterations parameter."); + return -EINVAL; + } + + break; + + case 'b': + arg_batch = true; + break; + + case 'r': + arg_raw = true; + break; + + case 'p': + arg_order = ORDER_PATH; + break; + + case 't': + arg_order = ORDER_TASKS; + break; + + case 'c': + arg_order = ORDER_CPU; + break; + + case 'm': + arg_order = ORDER_MEMORY; + break; + + case 'i': + arg_order = ORDER_IO; + break; + + case ARG_ORDER: + if (streq(optarg, "path")) + arg_order = ORDER_PATH; + else if (streq(optarg, "tasks")) + arg_order = ORDER_TASKS; + else if (streq(optarg, "cpu")) + arg_order = ORDER_CPU; + else if (streq(optarg, "memory")) + arg_order = ORDER_MEMORY; + else if (streq(optarg, "io")) + arg_order = ORDER_IO; + else { + log_error("Invalid argument to --order=: %s", optarg); + return -EINVAL; + } + break; + + case 'k': + arg_count = COUNT_ALL_PROCESSES; + break; + + case 'P': + arg_count = COUNT_USERSPACE_PROCESSES; + break; + + case ARG_RECURSIVE: + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --recursive= argument: %s", optarg); + return r; + } + + arg_recursive = r; + recursive_unset = r == 0; + break; + + case 'M': + arg_machine = optarg; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind < argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + if (recursive_unset && arg_count == COUNT_PIDS) { + log_error("Non-recursive counting is only supported when counting processes, not tasks. Use -P or -k."); + return -EINVAL; + } + + return 1; +} + +static const char* counting_what(void) { + if (arg_count == COUNT_PIDS) + return "tasks"; + else if (arg_count == COUNT_ALL_PROCESSES) + return "all processes (incl. kernel)"; + else + return "userspace processes (excl. kernel)"; +} + +static int get_cgroup_root(char **ret) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *unit = NULL, *path = NULL; + const char *m; + int r; + + if (!arg_machine) { + r = cg_get_root_path(ret); + if (r < 0) + return log_error_errno(r, "Failed to get root control group path: %m"); + + return 0; + } + + m = strjoina("/run/systemd/machines/", arg_machine); + r = parse_env_file(m, NEWLINE, "SCOPE", &unit, NULL); + if (r < 0) + return log_error_errno(r, "Failed to load machine data: %m"); + + path = unit_dbus_path_from_name(unit); + if (!path) + return log_oom(); + + r = bus_connect_transport_systemd(BUS_TRANSPORT_LOCAL, NULL, false, &bus); + if (r < 0) + return log_error_errno(r, "Failed to create bus connection: %m"); + + r = sd_bus_get_property_string( + bus, + "org.freedesktop.systemd1", + path, + unit_dbus_interface_from_name(unit), + "ControlGroup", + &error, + ret); + if (r < 0) + return log_error_errno(r, "Failed to query unit control group path: %s", bus_error_message(&error, r)); + + return 0; +} + +int main(int argc, char *argv[]) { + int r; + Hashmap *a = NULL, *b = NULL; + unsigned iteration = 0; + usec_t last_refresh = 0; + bool quit = false, immediate_refresh = false; + _cleanup_free_ char *root = NULL; + CGroupMask mask; + + log_parse_environment(); + log_open(); + + r = cg_mask_supported(&mask); + if (r < 0) { + log_error_errno(r, "Failed to determine supported controllers: %m"); + goto finish; + } + + arg_count = (mask & CGROUP_MASK_PIDS) ? COUNT_PIDS : COUNT_USERSPACE_PROCESSES; + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = get_cgroup_root(&root); + if (r < 0) { + log_error_errno(r, "Failed to get root control group path: %m"); + goto finish; + } + + a = hashmap_new(&string_hash_ops); + b = hashmap_new(&string_hash_ops); + if (!a || !b) { + r = log_oom(); + goto finish; + } + + signal(SIGWINCH, columns_lines_cache_reset); + + if (arg_iterations == (unsigned) -1) + arg_iterations = on_tty() ? 0 : 1; + + while (!quit) { + Hashmap *c; + usec_t t; + char key; + char h[FORMAT_TIMESPAN_MAX]; + + t = now(CLOCK_MONOTONIC); + + if (t >= last_refresh + arg_delay || immediate_refresh) { + + r = refresh(root, a, b, iteration++); + if (r < 0) { + log_error_errno(r, "Failed to refresh: %m"); + goto finish; + } + + group_hashmap_clear(b); + + c = a; + a = b; + b = c; + + last_refresh = t; + immediate_refresh = false; + } + + display(b); + + if (arg_iterations && iteration >= arg_iterations) + break; + + if (!on_tty()) /* non-TTY: Empty newline as delimiter between polls */ + fputs("\n", stdout); + fflush(stdout); + + if (arg_batch) + (void) usleep(last_refresh + arg_delay - t); + else { + r = read_one_char(stdin, &key, last_refresh + arg_delay - t, NULL); + if (r == -ETIMEDOUT) + continue; + if (r < 0) { + log_error_errno(r, "Couldn't read key: %m"); + goto finish; + } + } + + if (on_tty()) { /* TTY: Clear any user keystroke */ + fputs("\r \r", stdout); + fflush(stdout); + } + + if (arg_batch) + continue; + + switch (key) { + + case ' ': + immediate_refresh = true; + break; + + case 'q': + quit = true; + break; + + case 'p': + arg_order = ORDER_PATH; + break; + + case 't': + arg_order = ORDER_TASKS; + break; + + case 'c': + arg_order = ORDER_CPU; + break; + + case 'm': + arg_order = ORDER_MEMORY; + break; + + case 'i': + arg_order = ORDER_IO; + break; + + case '%': + arg_cpu_type = arg_cpu_type == CPU_TIME ? CPU_PERCENT : CPU_TIME; + break; + + case 'k': + arg_count = arg_count != COUNT_ALL_PROCESSES ? COUNT_ALL_PROCESSES : COUNT_PIDS; + fprintf(stdout, "\nCounting: %s.", counting_what()); + fflush(stdout); + sleep(1); + break; + + case 'P': + arg_count = arg_count != COUNT_USERSPACE_PROCESSES ? COUNT_USERSPACE_PROCESSES : COUNT_PIDS; + fprintf(stdout, "\nCounting: %s.", counting_what()); + fflush(stdout); + sleep(1); + break; + + case 'r': + if (arg_count == COUNT_PIDS) + fprintf(stdout, "\n\aCannot toggle recursive counting, not available in task counting mode."); + else { + arg_recursive = !arg_recursive; + fprintf(stdout, "\nRecursive process counting: %s", yes_no(arg_recursive)); + } + fflush(stdout); + sleep(1); + break; + + case '+': + if (arg_delay < USEC_PER_SEC) + arg_delay += USEC_PER_MSEC*250; + else + arg_delay += USEC_PER_SEC; + + fprintf(stdout, "\nIncreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0)); + fflush(stdout); + sleep(1); + break; + + case '-': + if (arg_delay <= USEC_PER_MSEC*500) + arg_delay = USEC_PER_MSEC*250; + else if (arg_delay < USEC_PER_MSEC*1250) + arg_delay -= USEC_PER_MSEC*250; + else + arg_delay -= USEC_PER_SEC; + + fprintf(stdout, "\nDecreased delay to %s.", format_timespan(h, sizeof(h), arg_delay, 0)); + fflush(stdout); + sleep(1); + break; + + case '?': + case 'h': + +#define ON ANSI_HIGHLIGHT +#define OFF ANSI_NORMAL + + fprintf(stdout, + "\t<" ON "p" OFF "> By path; <" ON "t" OFF "> By tasks/procs; <" ON "c" OFF "> By CPU; <" ON "m" OFF "> By memory; <" ON "i" OFF "> By I/O\n" + "\t<" ON "+" OFF "> Inc. delay; <" ON "-" OFF "> Dec. delay; <" ON "%%" OFF "> Toggle time; <" ON "SPACE" OFF "> Refresh\n" + "\t<" ON "P" OFF "> Toggle count userspace processes; <" ON "k" OFF "> Toggle count all processes\n" + "\t<" ON "r" OFF "> Count processes recursively; <" ON "q" OFF "> Quit"); + fflush(stdout); + sleep(3); + break; + + default: + if (key < ' ') + fprintf(stdout, "\nUnknown key '\\x%x'. Ignoring.", key); + else + fprintf(stdout, "\nUnknown key '%c'. Ignoring.", key); + fflush(stdout); + sleep(1); + break; + } + } + + r = 0; + +finish: + group_hashmap_free(a); + group_hashmap_free(b); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-cryptsetup/Makefile b/src/systemd-cryptsetup/Makefile new file mode 100644 index 0000000000..c97df5f959 --- /dev/null +++ b/src/systemd-cryptsetup/Makefile @@ -0,0 +1,59 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(HAVE_LIBCRYPTSETUP),) +rootlibexec_PROGRAMS += \ + systemd-cryptsetup + +systemgenerator_PROGRAMS += \ + systemd-cryptsetup-generator + +dist_systemunit_DATA += \ + units/cryptsetup.target \ + units/cryptsetup-pre.target + +systemd_cryptsetup_SOURCES = \ + src/cryptsetup/cryptsetup.c + +systemd_cryptsetup_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBCRYPTSETUP_CFLAGS) + +systemd_cryptsetup_LDADD = \ + libshared.la \ + $(LIBCRYPTSETUP_LIBS) + +systemd_cryptsetup_generator_SOURCES = \ + src/cryptsetup/cryptsetup-generator.c + +systemd_cryptsetup_generator_LDADD = \ + libshared.la + +SYSINIT_TARGET_WANTS += \ + cryptsetup.target + +endif + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-cryptsetup/cryptsetup-generator.c b/src/systemd-cryptsetup/cryptsetup-generator.c new file mode 100644 index 0000000000..8ac5ab730a --- /dev/null +++ b/src/systemd-cryptsetup/cryptsetup-generator.c @@ -0,0 +1,509 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include + +#include "alloc-util.h" +#include "dropin.h" +#include "fd-util.h" +#include "fileio.h" +#include "fstab-util.h" +#include "generator.h" +#include "hashmap.h" +#include "log.h" +#include "mkdir.h" +#include "parse-util.h" +#include "path-util.h" +#include "proc-cmdline.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" +#include "util.h" + +typedef struct crypto_device { + char *uuid; + char *keyfile; + char *name; + char *options; + bool create; +} crypto_device; + +static const char *arg_dest = "/tmp"; +static bool arg_enabled = true; +static bool arg_read_crypttab = true; +static bool arg_whitelist = false; +static Hashmap *arg_disks = NULL; +static char *arg_default_options = NULL; +static char *arg_default_keyfile = NULL; + +static int create_disk( + const char *name, + const char *device, + const char *password, + const char *options) { + + _cleanup_free_ char *p = NULL, *n = NULL, *d = NULL, *u = NULL, *to = NULL, *e = NULL, + *filtered = NULL; + _cleanup_fclose_ FILE *f = NULL; + bool noauto, nofail, tmp, swap; + char *from; + int r; + + assert(name); + assert(device); + + noauto = fstab_test_yes_no_option(options, "noauto\0" "auto\0"); + nofail = fstab_test_yes_no_option(options, "nofail\0" "fail\0"); + tmp = fstab_test_option(options, "tmp\0"); + swap = fstab_test_option(options, "swap\0"); + + if (tmp && swap) { + log_error("Device '%s' cannot be both 'tmp' and 'swap'. Ignoring.", name); + return -EINVAL; + } + + e = unit_name_escape(name); + if (!e) + return log_oom(); + + r = unit_name_build("systemd-cryptsetup", e, ".service", &n); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + p = strjoin(arg_dest, "/", n, NULL); + if (!p) + return log_oom(); + + u = fstab_node_to_udev_node(device); + if (!u) + return log_oom(); + + r = unit_name_from_path(u, ".device", &d); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + f = fopen(p, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", p); + + fputs( + "# Automatically generated by systemd-cryptsetup-generator\n\n" + "[Unit]\n" + "Description=Cryptography Setup for %I\n" + "Documentation=man:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)\n" + "SourcePath=/etc/crypttab\n" + "DefaultDependencies=no\n" + "Conflicts=umount.target\n" + "BindsTo=dev-mapper-%i.device\n" + "IgnoreOnIsolate=true\n" + "After=cryptsetup-pre.target\n", + f); + + if (!nofail) + fprintf(f, + "Before=cryptsetup.target\n"); + + if (password) { + if (STR_IN_SET(password, "/dev/urandom", "/dev/random", "/dev/hw_random")) + fputs("After=systemd-random-seed.service\n", f); + else if (!streq(password, "-") && !streq(password, "none")) { + _cleanup_free_ char *uu; + + uu = fstab_node_to_udev_node(password); + if (!uu) + return log_oom(); + + if (!path_equal(uu, "/dev/null")) { + + if (is_device_path(uu)) { + _cleanup_free_ char *dd = NULL; + + r = unit_name_from_path(uu, ".device", &dd); + if (r < 0) + return log_error_errno(r, "Failed to generate unit name: %m"); + + fprintf(f, "After=%1$s\nRequires=%1$s\n", dd); + } else + fprintf(f, "RequiresMountsFor=%s\n", password); + } + } + } + + if (is_device_path(u)) + fprintf(f, + "BindsTo=%s\n" + "After=%s\n" + "Before=umount.target\n", + d, d); + else + fprintf(f, + "RequiresMountsFor=%s\n", + u); + + r = generator_write_timeouts(arg_dest, device, name, options, &filtered); + if (r < 0) + return r; + + fprintf(f, + "\n[Service]\n" + "Type=oneshot\n" + "RemainAfterExit=yes\n" + "TimeoutSec=0\n" /* the binary handles timeouts anyway */ + "ExecStart=" SYSTEMD_CRYPTSETUP_PATH " attach '%s' '%s' '%s' '%s'\n" + "ExecStop=" SYSTEMD_CRYPTSETUP_PATH " detach '%s'\n", + name, u, strempty(password), strempty(filtered), + name); + + if (tmp) + fprintf(f, + "ExecStartPost=/sbin/mke2fs '/dev/mapper/%s'\n", + name); + + if (swap) + fprintf(f, + "ExecStartPost=/sbin/mkswap '/dev/mapper/%s'\n", + name); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write file %s: %m", p); + + from = strjoina("../", n); + + if (!noauto) { + + to = strjoin(arg_dest, "/", d, ".wants/", n, NULL); + if (!to) + return log_oom(); + + mkdir_parents_label(to, 0755); + if (symlink(from, to) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + + free(to); + if (!nofail) + to = strjoin(arg_dest, "/cryptsetup.target.requires/", n, NULL); + else + to = strjoin(arg_dest, "/cryptsetup.target.wants/", n, NULL); + if (!to) + return log_oom(); + + mkdir_parents_label(to, 0755); + if (symlink(from, to) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + } + + free(to); + to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n, NULL); + if (!to) + return log_oom(); + + mkdir_parents_label(to, 0755); + if (symlink(from, to) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + + if (!noauto && !nofail) { + _cleanup_free_ char *dmname; + dmname = strjoin("dev-mapper-", e, ".device", NULL); + if (!dmname) + return log_oom(); + + r = write_drop_in(arg_dest, dmname, 90, "device-timeout", + "# Automatically generated by systemd-cryptsetup-generator \n\n" + "[Unit]\nJobTimeoutSec=0"); + if (r < 0) + return log_error_errno(r, "Failed to write device drop-in: %m"); + } + + return 0; +} + +static void free_arg_disks(void) { + crypto_device *d; + + while ((d = hashmap_steal_first(arg_disks))) { + free(d->uuid); + free(d->keyfile); + free(d->name); + free(d->options); + free(d); + } + + hashmap_free(arg_disks); +} + +static crypto_device *get_crypto_device(const char *uuid) { + int r; + crypto_device *d; + + assert(uuid); + + d = hashmap_get(arg_disks, uuid); + if (!d) { + d = new0(struct crypto_device, 1); + if (!d) + return NULL; + + d->create = false; + d->keyfile = d->options = d->name = NULL; + + d->uuid = strdup(uuid); + if (!d->uuid) { + free(d); + return NULL; + } + + r = hashmap_put(arg_disks, d->uuid, d); + if (r < 0) { + free(d->uuid); + free(d); + return NULL; + } + } + + return d; +} + +static int parse_proc_cmdline_item(const char *key, const char *value) { + int r; + crypto_device *d; + _cleanup_free_ char *uuid = NULL, *uuid_value = NULL; + + if (STR_IN_SET(key, "luks", "rd.luks") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse luks switch %s. Ignoring.", value); + else + arg_enabled = r; + + } else if (STR_IN_SET(key, "luks.crypttab", "rd.luks.crypttab") && value) { + + r = parse_boolean(value); + if (r < 0) + log_warning("Failed to parse luks crypttab switch %s. Ignoring.", value); + else + arg_read_crypttab = r; + + } else if (STR_IN_SET(key, "luks.uuid", "rd.luks.uuid") && value) { + + d = get_crypto_device(startswith(value, "luks-") ? value+5 : value); + if (!d) + return log_oom(); + + d->create = arg_whitelist = true; + + } else if (STR_IN_SET(key, "luks.options", "rd.luks.options") && value) { + + r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); + if (r == 2) { + d = get_crypto_device(uuid); + if (!d) + return log_oom(); + + free(d->options); + d->options = uuid_value; + uuid_value = NULL; + } else if (free_and_strdup(&arg_default_options, value) < 0) + return log_oom(); + + } else if (STR_IN_SET(key, "luks.key", "rd.luks.key") && value) { + + r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); + if (r == 2) { + d = get_crypto_device(uuid); + if (!d) + return log_oom(); + + free(d->keyfile); + d->keyfile = uuid_value; + uuid_value = NULL; + } else if (free_and_strdup(&arg_default_keyfile, value) < 0) + return log_oom(); + + } else if (STR_IN_SET(key, "luks.name", "rd.luks.name") && value) { + + r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); + if (r == 2) { + d = get_crypto_device(uuid); + if (!d) + return log_oom(); + + d->create = arg_whitelist = true; + + free(d->name); + d->name = uuid_value; + uuid_value = NULL; + } else + log_warning("Failed to parse luks name switch %s. Ignoring.", value); + + } + + return 0; +} + +static int add_crypttab_devices(void) { + struct stat st; + unsigned crypttab_line = 0; + _cleanup_fclose_ FILE *f = NULL; + + if (!arg_read_crypttab) + return 0; + + f = fopen("/etc/crypttab", "re"); + if (!f) { + if (errno != ENOENT) + log_error_errno(errno, "Failed to open /etc/crypttab: %m"); + return 0; + } + + if (fstat(fileno(f), &st) < 0) { + log_error_errno(errno, "Failed to stat /etc/crypttab: %m"); + return 0; + } + + for (;;) { + int r, k; + char line[LINE_MAX], *l, *uuid; + crypto_device *d = NULL; + _cleanup_free_ char *name = NULL, *device = NULL, *keyfile = NULL, *options = NULL; + + if (!fgets(line, sizeof(line), f)) + break; + + crypttab_line++; + + l = strstrip(line); + if (*l == '#' || *l == 0) + continue; + + k = sscanf(l, "%ms %ms %ms %ms", &name, &device, &keyfile, &options); + if (k < 2 || k > 4) { + log_error("Failed to parse /etc/crypttab:%u, ignoring.", crypttab_line); + continue; + } + + uuid = startswith(device, "UUID="); + if (!uuid) + uuid = path_startswith(device, "/dev/disk/by-uuid/"); + if (!uuid) + uuid = startswith(name, "luks-"); + if (uuid) + d = hashmap_get(arg_disks, uuid); + + if (arg_whitelist && !d) { + log_info("Not creating device '%s' because it was not specified on the kernel command line.", name); + continue; + } + + r = create_disk(name, device, keyfile, (d && d->options) ? d->options : options); + if (r < 0) + return r; + + if (d) + d->create = false; + } + + return 0; +} + +static int add_proc_cmdline_devices(void) { + int r; + Iterator i; + crypto_device *d; + + HASHMAP_FOREACH(d, arg_disks, i) { + const char *options; + _cleanup_free_ char *device = NULL; + + if (!d->create) + continue; + + if (!d->name) { + d->name = strappend("luks-", d->uuid); + if (!d->name) + return log_oom(); + } + + device = strappend("UUID=", d->uuid); + if (!device) + return log_oom(); + + if (d->options) + options = d->options; + else if (arg_default_options) + options = arg_default_options; + else + options = "timeout=0"; + + r = create_disk(d->name, device, d->keyfile ?: arg_default_keyfile, options); + if (r < 0) + return r; + } + + return 0; +} + +int main(int argc, char *argv[]) { + int r = EXIT_FAILURE; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[1]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + arg_disks = hashmap_new(&string_hash_ops); + if (!arg_disks) + goto cleanup; + + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) { + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + r = EXIT_FAILURE; + } + + if (!arg_enabled) { + r = EXIT_SUCCESS; + goto cleanup; + } + + if (add_crypttab_devices() < 0) + goto cleanup; + + if (add_proc_cmdline_devices() < 0) + goto cleanup; + + r = EXIT_SUCCESS; + +cleanup: + free_arg_disks(); + free(arg_default_options); + free(arg_default_keyfile); + + return r; +} diff --git a/src/systemd-cryptsetup/cryptsetup.c b/src/systemd-cryptsetup/cryptsetup.c new file mode 100644 index 0000000000..9d8156697d --- /dev/null +++ b/src/systemd-cryptsetup/cryptsetup.c @@ -0,0 +1,753 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "ask-password-api.h" +#include "device-util.h" +#include "escape.h" +#include "fileio.h" +#include "log.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +static const char *arg_type = NULL; /* CRYPT_LUKS1, CRYPT_TCRYPT or CRYPT_PLAIN */ +static char *arg_cipher = NULL; +static unsigned arg_key_size = 0; +static int arg_key_slot = CRYPT_ANY_SLOT; +static unsigned arg_keyfile_size = 0; +static unsigned arg_keyfile_offset = 0; +static char *arg_hash = NULL; +static char *arg_header = NULL; +static unsigned arg_tries = 3; +static bool arg_readonly = false; +static bool arg_verify = false; +static bool arg_discards = false; +static bool arg_tcrypt_hidden = false; +static bool arg_tcrypt_system = false; +static char **arg_tcrypt_keyfiles = NULL; +static uint64_t arg_offset = 0; +static uint64_t arg_skip = 0; +static usec_t arg_timeout = 0; + +/* Options Debian's crypttab knows we don't: + + precheck= + check= + checkargs= + noearly= + loud= + keyscript= +*/ + +static int parse_one_option(const char *option) { + assert(option); + + /* Handled outside of this tool */ + if (STR_IN_SET(option, "noauto", "auto", "nofail", "fail")) + return 0; + + if (startswith(option, "cipher=")) { + char *t; + + t = strdup(option+7); + if (!t) + return log_oom(); + + free(arg_cipher); + arg_cipher = t; + + } else if (startswith(option, "size=")) { + + if (safe_atou(option+5, &arg_key_size) < 0) { + log_error("size= parse failure, ignoring."); + return 0; + } + + if (arg_key_size % 8) { + log_error("size= not a multiple of 8, ignoring."); + return 0; + } + + arg_key_size /= 8; + + } else if (startswith(option, "key-slot=")) { + + arg_type = CRYPT_LUKS1; + if (safe_atoi(option+9, &arg_key_slot) < 0) { + log_error("key-slot= parse failure, ignoring."); + return 0; + } + + } else if (startswith(option, "tcrypt-keyfile=")) { + + arg_type = CRYPT_TCRYPT; + if (path_is_absolute(option+15)) { + if (strv_extend(&arg_tcrypt_keyfiles, option + 15) < 0) + return log_oom(); + } else + log_error("Key file path '%s' is not absolute. Ignoring.", option+15); + + } else if (startswith(option, "keyfile-size=")) { + + if (safe_atou(option+13, &arg_keyfile_size) < 0) { + log_error("keyfile-size= parse failure, ignoring."); + return 0; + } + + } else if (startswith(option, "keyfile-offset=")) { + + if (safe_atou(option+15, &arg_keyfile_offset) < 0) { + log_error("keyfile-offset= parse failure, ignoring."); + return 0; + } + + } else if (startswith(option, "hash=")) { + char *t; + + t = strdup(option+5); + if (!t) + return log_oom(); + + free(arg_hash); + arg_hash = t; + + } else if (startswith(option, "header=")) { + arg_type = CRYPT_LUKS1; + + if (!path_is_absolute(option+7)) { + log_error("Header path '%s' is not absolute, refusing.", option+7); + return -EINVAL; + } + + if (arg_header) { + log_error("Duplicate header= options, refusing."); + return -EINVAL; + } + + arg_header = strdup(option+7); + if (!arg_header) + return log_oom(); + + } else if (startswith(option, "tries=")) { + + if (safe_atou(option+6, &arg_tries) < 0) { + log_error("tries= parse failure, ignoring."); + return 0; + } + + } else if (STR_IN_SET(option, "readonly", "read-only")) + arg_readonly = true; + else if (streq(option, "verify")) + arg_verify = true; + else if (STR_IN_SET(option, "allow-discards", "discard")) + arg_discards = true; + else if (streq(option, "luks")) + arg_type = CRYPT_LUKS1; + else if (streq(option, "tcrypt")) + arg_type = CRYPT_TCRYPT; + else if (streq(option, "tcrypt-hidden")) { + arg_type = CRYPT_TCRYPT; + arg_tcrypt_hidden = true; + } else if (streq(option, "tcrypt-system")) { + arg_type = CRYPT_TCRYPT; + arg_tcrypt_system = true; + } else if (STR_IN_SET(option, "plain", "swap", "tmp")) + arg_type = CRYPT_PLAIN; + else if (startswith(option, "timeout=")) { + + if (parse_sec(option+8, &arg_timeout) < 0) { + log_error("timeout= parse failure, ignoring."); + return 0; + } + + } else if (startswith(option, "offset=")) { + + if (safe_atou64(option+7, &arg_offset) < 0) { + log_error("offset= parse failure, refusing."); + return -EINVAL; + } + + } else if (startswith(option, "skip=")) { + + if (safe_atou64(option+5, &arg_skip) < 0) { + log_error("skip= parse failure, refusing."); + return -EINVAL; + } + + } else if (!streq(option, "none")) + log_error("Encountered unknown /etc/crypttab option '%s', ignoring.", option); + + return 0; +} + +static int parse_options(const char *options) { + const char *word, *state; + size_t l; + int r; + + assert(options); + + FOREACH_WORD_SEPARATOR(word, l, options, ",", state) { + _cleanup_free_ char *o; + + o = strndup(word, l); + if (!o) + return -ENOMEM; + r = parse_one_option(o); + if (r < 0) + return r; + } + + /* sanity-check options */ + if (arg_type != NULL && !streq(arg_type, CRYPT_PLAIN)) { + if (arg_offset) + log_warning("offset= ignored with type %s", arg_type); + if (arg_skip) + log_warning("skip= ignored with type %s", arg_type); + } + + return 0; +} + +static void log_glue(int level, const char *msg, void *usrptr) { + log_debug("%s", msg); +} + +static int disk_major_minor(const char *path, char **ret) { + struct stat st; + + assert(path); + + if (stat(path, &st) < 0) + return -errno; + + if (!S_ISBLK(st.st_mode)) + return -EINVAL; + + if (asprintf(ret, "/dev/block/%d:%d", major(st.st_rdev), minor(st.st_rdev)) < 0) + return -errno; + + return 0; +} + +static char* disk_description(const char *path) { + + static const char name_fields[] = + "ID_PART_ENTRY_NAME\0" + "DM_NAME\0" + "ID_MODEL_FROM_DATABASE\0" + "ID_MODEL\0"; + + _cleanup_(sd_device_unrefp) sd_device *device = NULL; + struct stat st; + const char *i; + int r; + + assert(path); + + if (stat(path, &st) < 0) + return NULL; + + if (!S_ISBLK(st.st_mode)) + return NULL; + + r = sd_device_new_from_devnum(&device, 'b', st.st_rdev); + if (r < 0) + return NULL; + + NULSTR_FOREACH(i, name_fields) { + const char *name; + + r = sd_device_get_property_value(device, i, &name); + if (r >= 0 && !isempty(name)) + return strdup(name); + } + + return NULL; +} + +static char *disk_mount_point(const char *label) { + _cleanup_free_ char *device = NULL; + _cleanup_endmntent_ FILE *f = NULL; + struct mntent *m; + + /* Yeah, we don't support native systemd unit files here for now */ + + if (asprintf(&device, "/dev/mapper/%s", label) < 0) + return NULL; + + f = setmntent("/etc/fstab", "r"); + if (!f) + return NULL; + + while ((m = getmntent(f))) + if (path_equal(m->mnt_fsname, device)) + return strdup(m->mnt_dir); + + return NULL; +} + +static int get_password(const char *vol, const char *src, usec_t until, bool accept_cached, char ***ret) { + _cleanup_free_ char *description = NULL, *name_buffer = NULL, *mount_point = NULL, *maj_min = NULL, *text = NULL, *escaped_name = NULL; + _cleanup_strv_free_erase_ char **passwords = NULL; + const char *name = NULL; + char **p, *id; + int r = 0; + + assert(vol); + assert(src); + assert(ret); + + description = disk_description(src); + mount_point = disk_mount_point(vol); + + if (description && streq(vol, description)) + /* If the description string is simply the + * volume name, then let's not show this + * twice */ + description = mfree(description); + + if (mount_point && description) + r = asprintf(&name_buffer, "%s (%s) on %s", description, vol, mount_point); + else if (mount_point) + r = asprintf(&name_buffer, "%s on %s", vol, mount_point); + else if (description) + r = asprintf(&name_buffer, "%s (%s)", description, vol); + + if (r < 0) + return log_oom(); + + name = name_buffer ? name_buffer : vol; + + if (asprintf(&text, "Please enter passphrase for disk %s!", name) < 0) + return log_oom(); + + if (src) + (void) disk_major_minor(src, &maj_min); + + if (maj_min) { + escaped_name = maj_min; + maj_min = NULL; + } else + escaped_name = cescape(name); + + if (!escaped_name) + return log_oom(); + + id = strjoina("cryptsetup:", escaped_name); + + r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until, + ASK_PASSWORD_PUSH_CACHE | (accept_cached*ASK_PASSWORD_ACCEPT_CACHED), + &passwords); + if (r < 0) + return log_error_errno(r, "Failed to query password: %m"); + + if (arg_verify) { + _cleanup_strv_free_erase_ char **passwords2 = NULL; + + assert(strv_length(passwords) == 1); + + if (asprintf(&text, "Please enter passphrase for disk %s! (verification)", name) < 0) + return log_oom(); + + id = strjoina("cryptsetup-verification:", escaped_name); + + r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until, ASK_PASSWORD_PUSH_CACHE, &passwords2); + if (r < 0) + return log_error_errno(r, "Failed to query verification password: %m"); + + assert(strv_length(passwords2) == 1); + + if (!streq(passwords[0], passwords2[0])) { + log_warning("Passwords did not match, retrying."); + return -EAGAIN; + } + } + + strv_uniq(passwords); + + STRV_FOREACH(p, passwords) { + char *c; + + if (strlen(*p)+1 >= arg_key_size) + continue; + + /* Pad password if necessary */ + c = new(char, arg_key_size); + if (!c) + return log_oom(); + + strncpy(c, *p, arg_key_size); + free(*p); + *p = c; + } + + *ret = passwords; + passwords = NULL; + + return 0; +} + +static int attach_tcrypt( + struct crypt_device *cd, + const char *name, + const char *key_file, + char **passwords, + uint32_t flags) { + + int r = 0; + _cleanup_free_ char *passphrase = NULL; + struct crypt_params_tcrypt params = { + .flags = CRYPT_TCRYPT_LEGACY_MODES, + .keyfiles = (const char **)arg_tcrypt_keyfiles, + .keyfiles_count = strv_length(arg_tcrypt_keyfiles) + }; + + assert(cd); + assert(name); + assert(key_file || (passwords && passwords[0])); + + if (arg_tcrypt_hidden) + params.flags |= CRYPT_TCRYPT_HIDDEN_HEADER; + + if (arg_tcrypt_system) + params.flags |= CRYPT_TCRYPT_SYSTEM_HEADER; + + if (key_file) { + r = read_one_line_file(key_file, &passphrase); + if (r < 0) { + log_error_errno(r, "Failed to read password file '%s': %m", key_file); + return -EAGAIN; + } + + params.passphrase = passphrase; + } else + params.passphrase = passwords[0]; + params.passphrase_size = strlen(params.passphrase); + + r = crypt_load(cd, CRYPT_TCRYPT, ¶ms); + if (r < 0) { + if (key_file && r == -EPERM) { + log_error("Failed to activate using password file '%s'.", key_file); + return -EAGAIN; + } + return r; + } + + return crypt_activate_by_volume_key(cd, name, NULL, 0, flags); +} + +static int attach_luks_or_plain(struct crypt_device *cd, + const char *name, + const char *key_file, + const char *data_device, + char **passwords, + uint32_t flags) { + int r = 0; + bool pass_volume_key = false; + + assert(cd); + assert(name); + assert(key_file || passwords); + + if (!arg_type || streq(arg_type, CRYPT_LUKS1)) { + r = crypt_load(cd, CRYPT_LUKS1, NULL); + if (r < 0) { + log_error("crypt_load() failed on device %s.\n", crypt_get_device_name(cd)); + return r; + } + + if (data_device) + r = crypt_set_data_device(cd, data_device); + } + + if ((!arg_type && r < 0) || streq_ptr(arg_type, CRYPT_PLAIN)) { + struct crypt_params_plain params = { + .offset = arg_offset, + .skip = arg_skip, + }; + const char *cipher, *cipher_mode; + _cleanup_free_ char *truncated_cipher = NULL; + + if (arg_hash) { + /* plain isn't a real hash type. it just means "use no hash" */ + if (!streq(arg_hash, "plain")) + params.hash = arg_hash; + } else if (!key_file) + /* for CRYPT_PLAIN, the behaviour of cryptsetup + * package is to not hash when a key file is provided */ + params.hash = "ripemd160"; + + if (arg_cipher) { + size_t l; + + l = strcspn(arg_cipher, "-"); + truncated_cipher = strndup(arg_cipher, l); + if (!truncated_cipher) + return log_oom(); + + cipher = truncated_cipher; + cipher_mode = arg_cipher[l] ? arg_cipher+l+1 : "plain"; + } else { + cipher = "aes"; + cipher_mode = "cbc-essiv:sha256"; + } + + /* for CRYPT_PLAIN limit reads + * from keyfile to key length, and + * ignore keyfile-size */ + arg_keyfile_size = arg_key_size; + + /* In contrast to what the name + * crypt_setup() might suggest this + * doesn't actually format anything, + * it just configures encryption + * parameters when used for plain + * mode. */ + r = crypt_format(cd, CRYPT_PLAIN, cipher, cipher_mode, NULL, NULL, arg_keyfile_size, ¶ms); + + /* hash == NULL implies the user passed "plain" */ + pass_volume_key = (params.hash == NULL); + } + + if (r < 0) + return log_error_errno(r, "Loading of cryptographic parameters failed: %m"); + + log_info("Set cipher %s, mode %s, key size %i bits for device %s.", + crypt_get_cipher(cd), + crypt_get_cipher_mode(cd), + crypt_get_volume_key_size(cd)*8, + crypt_get_device_name(cd)); + + if (key_file) { + r = crypt_activate_by_keyfile_offset(cd, name, arg_key_slot, key_file, arg_keyfile_size, arg_keyfile_offset, flags); + if (r < 0) { + log_error_errno(r, "Failed to activate with key file '%s': %m", key_file); + return -EAGAIN; + } + } else { + char **p; + + STRV_FOREACH(p, passwords) { + if (pass_volume_key) + r = crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags); + else + r = crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags); + + if (r >= 0) + break; + } + } + + return r; +} + +static int help(void) { + + printf("%s attach VOLUME SOURCEDEVICE [PASSWORD] [OPTIONS]\n" + "%s detach VOLUME\n\n" + "Attaches or detaches an encrypted block device.\n", + program_invocation_short_name, + program_invocation_short_name); + + return 0; +} + +int main(int argc, char *argv[]) { + int r = EXIT_FAILURE; + struct crypt_device *cd = NULL; + + if (argc <= 1) { + help(); + return EXIT_SUCCESS; + } + + if (argc < 3) { + log_error("This program requires at least two arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (streq(argv[1], "attach")) { + uint32_t flags = 0; + int k; + unsigned tries; + usec_t until; + crypt_status_info status; + const char *key_file = NULL; + + /* Arguments: systemd-cryptsetup attach VOLUME SOURCE-DEVICE [PASSWORD] [OPTIONS] */ + + if (argc < 4) { + log_error("attach requires at least two arguments."); + goto finish; + } + + if (argc >= 5 && + argv[4][0] && + !streq(argv[4], "-") && + !streq(argv[4], "none")) { + + if (!path_is_absolute(argv[4])) + log_error("Password file path '%s' is not absolute. Ignoring.", argv[4]); + else + key_file = argv[4]; + } + + if (argc >= 6 && argv[5][0] && !streq(argv[5], "-")) { + if (parse_options(argv[5]) < 0) + goto finish; + } + + /* A delicious drop of snake oil */ + mlockall(MCL_FUTURE); + + if (arg_header) { + log_debug("LUKS header: %s", arg_header); + k = crypt_init(&cd, arg_header); + } else + k = crypt_init(&cd, argv[3]); + if (k) { + log_error_errno(k, "crypt_init() failed: %m"); + goto finish; + } + + crypt_set_log_callback(cd, log_glue, NULL); + + status = crypt_status(cd, argv[2]); + if (status == CRYPT_ACTIVE || status == CRYPT_BUSY) { + log_info("Volume %s already active.", argv[2]); + r = EXIT_SUCCESS; + goto finish; + } + + if (arg_readonly) + flags |= CRYPT_ACTIVATE_READONLY; + + if (arg_discards) + flags |= CRYPT_ACTIVATE_ALLOW_DISCARDS; + + if (arg_timeout > 0) + until = now(CLOCK_MONOTONIC) + arg_timeout; + else + until = 0; + + arg_key_size = (arg_key_size > 0 ? arg_key_size : (256 / 8)); + + if (key_file) { + struct stat st; + + /* Ideally we'd do this on the open fd, but since this is just a + * warning it's OK to do this in two steps. */ + if (stat(key_file, &st) >= 0 && S_ISREG(st.st_mode) && (st.st_mode & 0005)) + log_warning("Key file %s is world-readable. This is not a good idea!", key_file); + } + + for (tries = 0; arg_tries == 0 || tries < arg_tries; tries++) { + _cleanup_strv_free_erase_ char **passwords = NULL; + + if (!key_file) { + k = get_password(argv[2], argv[3], until, tries == 0 && !arg_verify, &passwords); + if (k == -EAGAIN) + continue; + else if (k < 0) + goto finish; + } + + if (streq_ptr(arg_type, CRYPT_TCRYPT)) + k = attach_tcrypt(cd, argv[2], key_file, passwords, flags); + else + k = attach_luks_or_plain(cd, + argv[2], + key_file, + arg_header ? argv[3] : NULL, + passwords, + flags); + if (k >= 0) + break; + else if (k == -EAGAIN) { + key_file = NULL; + continue; + } else if (k != -EPERM) { + log_error_errno(k, "Failed to activate: %m"); + goto finish; + } + + log_warning("Invalid passphrase."); + } + + if (arg_tries != 0 && tries >= arg_tries) { + log_error("Too many attempts; giving up."); + r = EXIT_FAILURE; + goto finish; + } + + } else if (streq(argv[1], "detach")) { + int k; + + k = crypt_init_by_name(&cd, argv[2]); + if (k) { + log_error_errno(k, "crypt_init() failed: %m"); + goto finish; + } + + crypt_set_log_callback(cd, log_glue, NULL); + + k = crypt_deactivate(cd, argv[2]); + if (k < 0) { + log_error_errno(k, "Failed to deactivate: %m"); + goto finish; + } + + } else { + log_error("Unknown verb %s.", argv[1]); + goto finish; + } + + r = EXIT_SUCCESS; + +finish: + + if (cd) + crypt_free(cd); + + free(arg_cipher); + free(arg_hash); + free(arg_header); + strv_free(arg_tcrypt_keyfiles); + + return r; +} diff --git a/src/systemd-delta/Makefile b/src/systemd-delta/Makefile new file mode 100644 index 0000000000..b9124f86ea --- /dev/null +++ b/src/systemd-delta/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_delta_SOURCES = \ + src/delta/delta.c + +systemd_delta_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-delta/delta.c b/src/systemd-delta/delta.c new file mode 100644 index 0000000000..a54fc89de6 --- /dev/null +++ b/src/systemd-delta/delta.c @@ -0,0 +1,643 @@ +/*** + This file is part of systemd. + + Copyright 2012 Lennart Poettering + Copyright 2013 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "hashmap.h" +#include "locale-util.h" +#include "log.h" +#include "pager.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "util.h" + +static const char prefixes[] = + "/etc\0" + "/run\0" + "/usr/local/lib\0" + "/usr/local/share\0" + "/usr/lib\0" + "/usr/share\0" +#ifdef HAVE_SPLIT_USR + "/lib\0" +#endif + ; + +static const char suffixes[] = + "sysctl.d\0" + "tmpfiles.d\0" + "modules-load.d\0" + "binfmt.d\0" + "systemd/system\0" + "systemd/user\0" + "systemd/system-preset\0" + "systemd/user-preset\0" + "udev/rules.d\0" + "modprobe.d\0"; + +static const char have_dropins[] = + "systemd/system\0" + "systemd/user\0"; + +static bool arg_no_pager = false; +static int arg_diff = -1; + +static enum { + SHOW_MASKED = 1 << 0, + SHOW_EQUIVALENT = 1 << 1, + SHOW_REDIRECTED = 1 << 2, + SHOW_OVERRIDDEN = 1 << 3, + SHOW_UNCHANGED = 1 << 4, + SHOW_EXTENDED = 1 << 5, + + SHOW_DEFAULTS = + (SHOW_MASKED | SHOW_EQUIVALENT | SHOW_REDIRECTED | SHOW_OVERRIDDEN | SHOW_EXTENDED) +} arg_flags = 0; + +static void pager_open_if_enabled(void) { + + if (arg_no_pager) + return; + + pager_open(false); +} + +static int equivalent(const char *a, const char *b) { + _cleanup_free_ char *x = NULL, *y = NULL; + + x = canonicalize_file_name(a); + if (!x) + return -errno; + + y = canonicalize_file_name(b); + if (!y) + return -errno; + + return path_equal(x, y); +} + +static int notify_override_masked(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_MASKED)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight_red(), "[MASKED]", ansi_normal(), + top, draw_special_char(DRAW_ARROW), bottom); + return 1; +} + +static int notify_override_equivalent(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_EQUIVALENT)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight_green(), "[EQUIVALENT]", ansi_normal(), + top, draw_special_char(DRAW_ARROW), bottom); + return 1; +} + +static int notify_override_redirected(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_REDIRECTED)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight(), "[REDIRECTED]", ansi_normal(), + top, draw_special_char(DRAW_ARROW), bottom); + return 1; +} + +static int notify_override_overridden(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_OVERRIDDEN)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight(), "[OVERRIDDEN]", ansi_normal(), + top, draw_special_char(DRAW_ARROW), bottom); + return 1; +} + +static int notify_override_extended(const char *top, const char *bottom) { + if (!(arg_flags & SHOW_EXTENDED)) + return 0; + + printf("%s%s%s %s %s %s\n", + ansi_highlight(), "[EXTENDED]", ansi_normal(), + top, draw_special_char(DRAW_ARROW), bottom); + return 1; +} + +static int notify_override_unchanged(const char *f) { + if (!(arg_flags & SHOW_UNCHANGED)) + return 0; + + printf("[UNCHANGED] %s\n", f); + return 1; +} + +static int found_override(const char *top, const char *bottom) { + _cleanup_free_ char *dest = NULL; + int k; + pid_t pid; + + assert(top); + assert(bottom); + + if (null_or_empty_path(top) > 0) + return notify_override_masked(top, bottom); + + k = readlink_malloc(top, &dest); + if (k >= 0) { + if (equivalent(dest, bottom) > 0) + return notify_override_equivalent(top, bottom); + else + return notify_override_redirected(top, bottom); + } + + k = notify_override_overridden(top, bottom); + if (!arg_diff) + return k; + + putchar('\n'); + + fflush(stdout); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork off diff: %m"); + else if (pid == 0) { + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + execlp("diff", "diff", "-us", "--", bottom, top, NULL); + log_error_errno(errno, "Failed to execute diff: %m"); + _exit(EXIT_FAILURE); + } + + wait_for_terminate_and_warn("diff", pid, false); + putchar('\n'); + + return k; +} + +static int enumerate_dir_d(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *toppath, const char *drop) { + _cleanup_free_ char *unit = NULL; + _cleanup_free_ char *path = NULL; + _cleanup_strv_free_ char **list = NULL; + char **file; + char *c; + int r; + + assert(!endswith(drop, "/")); + + path = strjoin(toppath, "/", drop, NULL); + if (!path) + return -ENOMEM; + + log_debug("Looking at %s", path); + + unit = strdup(drop); + if (!unit) + return -ENOMEM; + + c = strrchr(unit, '.'); + if (!c) + return -EINVAL; + *c = 0; + + r = get_files_in_directory(path, &list); + if (r < 0) + return log_error_errno(r, "Failed to enumerate %s: %m", path); + + STRV_FOREACH(file, list) { + Hashmap *h; + int k; + char *p; + char *d; + + if (!endswith(*file, ".conf")) + continue; + + p = strjoin(path, "/", *file, NULL); + if (!p) + return -ENOMEM; + d = p + strlen(toppath) + 1; + + log_debug("Adding at top: %s %s %s", d, draw_special_char(DRAW_ARROW), p); + k = hashmap_put(top, d, p); + if (k >= 0) { + p = strdup(p); + if (!p) + return -ENOMEM; + d = p + strlen(toppath) + 1; + } else if (k != -EEXIST) { + free(p); + return k; + } + + log_debug("Adding at bottom: %s %s %s", d, draw_special_char(DRAW_ARROW), p); + free(hashmap_remove(bottom, d)); + k = hashmap_put(bottom, d, p); + if (k < 0) { + free(p); + return k; + } + + h = hashmap_get(drops, unit); + if (!h) { + h = hashmap_new(&string_hash_ops); + if (!h) + return -ENOMEM; + hashmap_put(drops, unit, h); + unit = strdup(unit); + if (!unit) + return -ENOMEM; + } + + p = strdup(p); + if (!p) + return -ENOMEM; + + log_debug("Adding to drops: %s %s %s %s %s", + unit, draw_special_char(DRAW_ARROW), basename(p), draw_special_char(DRAW_ARROW), p); + k = hashmap_put(h, basename(p), p); + if (k < 0) { + free(p); + if (k != -EEXIST) + return k; + } + } + return 0; +} + +static int enumerate_dir(Hashmap *top, Hashmap *bottom, Hashmap *drops, const char *path, bool dropins) { + _cleanup_closedir_ DIR *d; + + assert(top); + assert(bottom); + assert(drops); + assert(path); + + log_debug("Looking at %s", path); + + d = opendir(path); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open %s: %m", path); + } + + for (;;) { + struct dirent *de; + int k; + char *p; + + errno = 0; + de = readdir(d); + if (!de) + return -errno; + + dirent_ensure_type(d, de); + + if (dropins && de->d_type == DT_DIR && endswith(de->d_name, ".d")) + enumerate_dir_d(top, bottom, drops, path, de->d_name); + + if (!dirent_is_file(de)) + continue; + + p = strjoin(path, "/", de->d_name, NULL); + if (!p) + return -ENOMEM; + + log_debug("Adding at top: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p); + k = hashmap_put(top, basename(p), p); + if (k >= 0) { + p = strdup(p); + if (!p) + return -ENOMEM; + } else if (k != -EEXIST) { + free(p); + return k; + } + + log_debug("Adding at bottom: %s %s %s", basename(p), draw_special_char(DRAW_ARROW), p); + free(hashmap_remove(bottom, basename(p))); + k = hashmap_put(bottom, basename(p), p); + if (k < 0) { + free(p); + return k; + } + } +} + +static int process_suffix(const char *suffix, const char *onlyprefix) { + const char *p; + char *f; + Hashmap *top, *bottom, *drops; + Hashmap *h; + char *key; + int r = 0, k; + Iterator i, j; + int n_found = 0; + bool dropins; + + assert(suffix); + assert(!startswith(suffix, "/")); + assert(!strstr(suffix, "//")); + + dropins = nulstr_contains(have_dropins, suffix); + + top = hashmap_new(&string_hash_ops); + bottom = hashmap_new(&string_hash_ops); + drops = hashmap_new(&string_hash_ops); + if (!top || !bottom || !drops) { + r = -ENOMEM; + goto finish; + } + + NULSTR_FOREACH(p, prefixes) { + _cleanup_free_ char *t = NULL; + + t = strjoin(p, "/", suffix, NULL); + if (!t) { + r = -ENOMEM; + goto finish; + } + + k = enumerate_dir(top, bottom, drops, t, dropins); + if (r == 0) + r = k; + } + + HASHMAP_FOREACH_KEY(f, key, top, i) { + char *o; + + o = hashmap_get(bottom, key); + assert(o); + + if (!onlyprefix || startswith(o, onlyprefix)) { + if (path_equal(o, f)) { + notify_override_unchanged(f); + } else { + k = found_override(f, o); + if (k < 0) + r = k; + else + n_found += k; + } + } + + h = hashmap_get(drops, key); + if (h) + HASHMAP_FOREACH(o, h, j) + if (!onlyprefix || startswith(o, onlyprefix)) + n_found += notify_override_extended(f, o); + } + +finish: + hashmap_free_free(top); + hashmap_free_free(bottom); + + HASHMAP_FOREACH_KEY(h, key, drops, i){ + hashmap_free_free(hashmap_remove(drops, key)); + hashmap_remove(drops, key); + free(key); + } + hashmap_free(drops); + + return r < 0 ? r : n_found; +} + +static int process_suffixes(const char *onlyprefix) { + const char *n; + int n_found = 0, r; + + NULSTR_FOREACH(n, suffixes) { + r = process_suffix(n, onlyprefix); + if (r < 0) + return r; + + n_found += r; + } + + return n_found; +} + +static int process_suffix_chop(const char *arg) { + const char *p; + + assert(arg); + + if (!path_is_absolute(arg)) + return process_suffix(arg, NULL); + + /* Strip prefix from the suffix */ + NULSTR_FOREACH(p, prefixes) { + const char *suffix; + + suffix = startswith(arg, p); + if (suffix) { + suffix += strspn(suffix, "/"); + if (*suffix) + return process_suffix(suffix, NULL); + else + return process_suffixes(arg); + } + } + + log_error("Invalid suffix specification %s.", arg); + return -EINVAL; +} + +static void help(void) { + printf("%s [OPTIONS...] [SUFFIX...]\n\n" + "Find overridden configuration files.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --diff[=1|0] Show a diff when overridden files differ\n" + " -t --type=LIST... Only display a selected set of override types\n" + , program_invocation_short_name); +} + +static int parse_flags(const char *flag_str, int flags) { + const char *word, *state; + size_t l; + + FOREACH_WORD_SEPARATOR(word, l, flag_str, ",", state) { + if (strneq("masked", word, l)) + flags |= SHOW_MASKED; + else if (strneq ("equivalent", word, l)) + flags |= SHOW_EQUIVALENT; + else if (strneq("redirected", word, l)) + flags |= SHOW_REDIRECTED; + else if (strneq("overridden", word, l)) + flags |= SHOW_OVERRIDDEN; + else if (strneq("unchanged", word, l)) + flags |= SHOW_UNCHANGED; + else if (strneq("extended", word, l)) + flags |= SHOW_EXTENDED; + else if (strneq("default", word, l)) + flags |= SHOW_DEFAULTS; + else + return -EINVAL; + } + return flags; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_NO_PAGER = 0x100, + ARG_DIFF, + ARG_VERSION + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "diff", optional_argument, NULL, ARG_DIFF }, + { "type", required_argument, NULL, 't' }, + {} + }; + + int c; + + assert(argc >= 1); + assert(argv); + + while ((c = getopt_long(argc, argv, "ht:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case 't': { + int f; + f = parse_flags(optarg, arg_flags); + if (f < 0) { + log_error("Failed to parse flags field."); + return -EINVAL; + } + arg_flags = f; + break; + } + + case ARG_DIFF: + if (!optarg) + arg_diff = 1; + else { + int b; + + b = parse_boolean(optarg); + if (b < 0) { + log_error("Failed to parse diff boolean."); + return -EINVAL; + } + + arg_diff = b; + } + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r, k, n_found = 0; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_flags == 0) + arg_flags = SHOW_DEFAULTS; + + if (arg_diff < 0) + arg_diff = !!(arg_flags & SHOW_OVERRIDDEN); + else if (arg_diff) + arg_flags |= SHOW_OVERRIDDEN; + + pager_open_if_enabled(); + + if (optind < argc) { + int i; + + for (i = optind; i < argc; i++) { + path_kill_slashes(argv[i]); + + k = process_suffix_chop(argv[i]); + if (k < 0) + r = k; + else + n_found += k; + } + + } else { + k = process_suffixes(NULL); + if (k < 0) + r = k; + else + n_found += k; + } + + if (r >= 0) + printf("%s%i overridden configuration files found.\n", n_found ? "\n" : "", n_found); + +finish: + pager_close(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-escape/Makefile b/src/systemd-escape/Makefile new file mode 100644 index 0000000000..6589e06525 --- /dev/null +++ b/src/systemd-escape/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_escape_SOURCES = \ + src/escape/escape.c + +systemd_escape_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-escape/escape.c b/src/systemd-escape/escape.c new file mode 100644 index 0000000000..9f39049577 --- /dev/null +++ b/src/systemd-escape/escape.c @@ -0,0 +1,237 @@ +/*** + This file is part of systemd. + + Copyright 2014 Michael Biebl + + 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 . +***/ + +#include +#include +#include + +#include "alloc-util.h" +#include "log.h" +#include "string-util.h" +#include "strv.h" +#include "unit-name.h" + +static enum { + ACTION_ESCAPE, + ACTION_UNESCAPE, + ACTION_MANGLE +} arg_action = ACTION_ESCAPE; +static const char *arg_suffix = NULL; +static const char *arg_template = NULL; +static bool arg_path = false; + +static void help(void) { + printf("%s [OPTIONS...] [NAME...]\n\n" + "Show system and user paths.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --suffix=SUFFIX Unit suffix to append to escaped strings\n" + " --template=TEMPLATE Insert strings as instance into template\n" + " -u --unescape Unescape strings\n" + " -m --mangle Mangle strings\n" + " -p --path When escaping/unescaping assume the string is a path\n" + , program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_SUFFIX, + ARG_TEMPLATE + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "suffix", required_argument, NULL, ARG_SUFFIX }, + { "template", required_argument, NULL, ARG_TEMPLATE }, + { "unescape", no_argument, NULL, 'u' }, + { "mangle", no_argument, NULL, 'm' }, + { "path", no_argument, NULL, 'p' }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hump", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_SUFFIX: + + if (unit_type_from_string(optarg) < 0) { + log_error("Invalid unit suffix type %s.", optarg); + return -EINVAL; + } + + arg_suffix = optarg; + break; + + case ARG_TEMPLATE: + + if (!unit_name_is_valid(optarg, UNIT_NAME_TEMPLATE)) { + log_error("Template name %s is not valid.", optarg); + return -EINVAL; + } + + arg_template = optarg; + break; + + case 'u': + arg_action = ACTION_UNESCAPE; + break; + + case 'm': + arg_action = ACTION_MANGLE; + break; + + case 'p': + arg_path = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind >= argc) { + log_error("Not enough arguments."); + return -EINVAL; + } + + if (arg_template && arg_suffix) { + log_error("--suffix= and --template= may not be combined."); + return -EINVAL; + } + + if ((arg_template || arg_suffix) && arg_action != ACTION_ESCAPE) { + log_error("--suffix= and --template= are not compatible with --unescape or --mangle."); + return -EINVAL; + } + + if (arg_path && !IN_SET(arg_action, ACTION_ESCAPE, ACTION_UNESCAPE)) { + log_error("--path may not be combined with --mangle."); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + char **i; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + STRV_FOREACH(i, argv + optind) { + _cleanup_free_ char *e = NULL; + + switch (arg_action) { + + case ACTION_ESCAPE: + if (arg_path) { + r = unit_name_path_escape(*i, &e); + if (r < 0) { + log_error_errno(r, "Failed to escape string: %m"); + goto finish; + } + } else { + e = unit_name_escape(*i); + if (!e) { + r = log_oom(); + goto finish; + } + } + + if (arg_template) { + char *x; + + r = unit_name_replace_instance(arg_template, e, &x); + if (r < 0) { + log_error_errno(r, "Failed to replace instance: %m"); + goto finish; + } + + free(e); + e = x; + } else if (arg_suffix) { + char *x; + + x = strjoin(e, ".", arg_suffix, NULL); + if (!x) { + r = log_oom(); + goto finish; + } + + free(e); + e = x; + } + + break; + + case ACTION_UNESCAPE: + if (arg_path) + r = unit_name_path_unescape(*i, &e); + else + r = unit_name_unescape(*i, &e); + + if (r < 0) { + log_error_errno(r, "Failed to unescape string: %m"); + goto finish; + } + break; + + case ACTION_MANGLE: + r = unit_name_mangle(*i, UNIT_NAME_NOGLOB, &e); + if (r < 0) { + log_error_errno(r, "Failed to mangle name: %m"); + goto finish; + } + break; + } + + if (i != argv+optind) + fputc(' ', stdout); + + fputs(e, stdout); + } + + fputc('\n', stdout); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-notify/Makefile b/src/systemd-notify/Makefile new file mode 100644 index 0000000000..c5402385b9 --- /dev/null +++ b/src/systemd-notify/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_notify_SOURCES = \ + src/notify/notify.c + +systemd_notify_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-notify/notify.c b/src/systemd-notify/notify.c new file mode 100644 index 0000000000..b18fb5938f --- /dev/null +++ b/src/systemd-notify/notify.c @@ -0,0 +1,203 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "env-util.h" +#include "formats-util.h" +#include "log.h" +#include "parse-util.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +static bool arg_ready = false; +static pid_t arg_pid = 0; +static const char *arg_status = NULL; +static bool arg_booted = false; + +static void help(void) { + printf("%s [OPTIONS...] [VARIABLE=VALUE...]\n\n" + "Notify the init system about service status updates.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --ready Inform the init system about service start-up completion\n" + " --pid[=PID] Set main pid of daemon\n" + " --status=TEXT Set status text\n" + " --booted Check if the system was booted up with systemd\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_READY = 0x100, + ARG_VERSION, + ARG_PID, + ARG_STATUS, + ARG_BOOTED, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "ready", no_argument, NULL, ARG_READY }, + { "pid", optional_argument, NULL, ARG_PID }, + { "status", required_argument, NULL, ARG_STATUS }, + { "booted", no_argument, NULL, ARG_BOOTED }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) { + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_READY: + arg_ready = true; + break; + + case ARG_PID: + + if (optarg) { + if (parse_pid(optarg, &arg_pid) < 0) { + log_error("Failed to parse PID %s.", optarg); + return -EINVAL; + } + } else + arg_pid = getppid(); + + break; + + case ARG_STATUS: + arg_status = optarg; + break; + + case ARG_BOOTED: + arg_booted = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + } + + if (optind >= argc && + !arg_ready && + !arg_status && + !arg_pid && + !arg_booted) { + help(); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char* argv[]) { + _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL; + _cleanup_strv_free_ char **final_env = NULL; + char* our_env[4]; + unsigned i = 0; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_booted) + return sd_booted() <= 0; + + if (arg_ready) + our_env[i++] = (char*) "READY=1"; + + if (arg_status) { + status = strappend("STATUS=", arg_status); + if (!status) { + r = log_oom(); + goto finish; + } + + our_env[i++] = status; + } + + if (arg_pid > 0) { + if (asprintf(&cpid, "MAINPID="PID_FMT, arg_pid) < 0) { + r = log_oom(); + goto finish; + } + + our_env[i++] = cpid; + } + + our_env[i++] = NULL; + + final_env = strv_env_merge(2, our_env, argv + optind); + if (!final_env) { + r = log_oom(); + goto finish; + } + + if (strv_length(final_env) <= 0) { + r = 0; + goto finish; + } + + n = strv_join(final_env, "\n"); + if (!n) { + r = log_oom(); + goto finish; + } + + r = sd_pid_notify(arg_pid ? arg_pid : getppid(), false, n); + if (r < 0) { + log_error_errno(r, "Failed to notify init system: %m"); + goto finish; + } else if (r == 0) { + log_error("No status data could be sent: $NOTIFY_SOCKET was not set"); + r = -EOPNOTSUPP; + } + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-nspawn/.gitignore b/src/systemd-nspawn/.gitignore new file mode 100644 index 0000000000..85c81fff24 --- /dev/null +++ b/src/systemd-nspawn/.gitignore @@ -0,0 +1 @@ +/nspawn-gperf.c diff --git a/src/systemd-nspawn/Makefile b/src/systemd-nspawn/Makefile new file mode 100644 index 0000000000..742d8ad82e --- /dev/null +++ b/src/systemd-nspawn/Makefile @@ -0,0 +1,69 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_nspawn_SOURCES = \ + src/nspawn/nspawn.c \ + src/nspawn/nspawn-settings.c \ + src/nspawn/nspawn-settings.h \ + src/nspawn/nspawn-mount.c \ + src/nspawn/nspawn-mount.h \ + src/nspawn/nspawn-network.c \ + src/nspawn/nspawn-network.h \ + src/nspawn/nspawn-expose-ports.c \ + src/nspawn/nspawn-expose-ports.h \ + src/nspawn/nspawn-cgroup.c \ + src/nspawn/nspawn-cgroup.h \ + src/nspawn/nspawn-register.c \ + src/nspawn/nspawn-register.h \ + src/nspawn/nspawn-setuid.c \ + src/nspawn/nspawn-setuid.h \ + src/nspawn/nspawn-stub-pid1.c \ + src/nspawn/nspawn-stub-pid1.h \ + src/core/mount-setup.c \ + src/core/mount-setup.h \ + src/core/loopback-setup.c \ + src/core/loopback-setup.h + +nodist_systemd_nspawn_SOURCES = \ + src/nspawn/nspawn-gperf.c + +gperf_gperf_sources += \ + src/nspawn/nspawn-gperf.gperf + +systemd_nspawn_CFLAGS = \ + $(AM_CFLAGS) \ + $(BLKID_CFLAGS) \ + $(SECCOMP_CFLAGS) + +systemd_nspawn_LDADD = \ + libshared.la \ + $(BLKID_LIBS) + +ifneq ($(HAVE_LIBIPTC),) +systemd_nspawn_LDADD += \ + libfirewall.la +endif + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-nspawn/nspawn-cgroup.c b/src/systemd-nspawn/nspawn-cgroup.c new file mode 100644 index 0000000000..1db5ba7116 --- /dev/null +++ b/src/systemd-nspawn/nspawn-cgroup.c @@ -0,0 +1,163 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "mkdir.h" +#include "nspawn-cgroup.h" +#include "string-util.h" +#include "strv.h" +#include "util.h" + +int chown_cgroup(pid_t pid, uid_t uid_shift) { + _cleanup_free_ char *path = NULL, *fs = NULL; + _cleanup_close_ int fd = -1; + const char *fn; + int r; + + r = cg_pid_get_path(NULL, pid, &path); + if (r < 0) + return log_error_errno(r, "Failed to get container cgroup path: %m"); + + r = cg_get_path(SYSTEMD_CGROUP_CONTROLLER, path, NULL, &fs); + if (r < 0) + return log_error_errno(r, "Failed to get file system path for container cgroup: %m"); + + fd = open(fs, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", fs); + + FOREACH_STRING(fn, + ".", + "tasks", + "notify_on_release", + "cgroup.procs", + "cgroup.events", + "cgroup.clone_children", + "cgroup.controllers", + "cgroup.subtree_control", + "cgroup.populated") + if (fchownat(fd, fn, uid_shift, uid_shift, 0) < 0) + log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_WARNING, errno, + "Failed to chown() cgroup file %s, ignoring: %m", fn); + + return 0; +} + +int sync_cgroup(pid_t pid, bool unified_requested) { + _cleanup_free_ char *cgroup = NULL; + char tree[] = "/tmp/unifiedXXXXXX", pid_string[DECIMAL_STR_MAX(pid) + 1]; + bool undo_mount = false; + const char *fn; + int unified, r; + + unified = cg_unified(); + if (unified < 0) + return log_error_errno(unified, "Failed to determine whether the unified hierachy is used: %m"); + + if ((unified > 0) == unified_requested) + return 0; + + /* When the host uses the legacy cgroup setup, but the + * container shall use the unified hierarchy, let's make sure + * we copy the path from the name=systemd hierarchy into the + * unified hierarchy. Similar for the reverse situation. */ + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, pid, &cgroup); + if (r < 0) + return log_error_errno(r, "Failed to get control group of " PID_FMT ": %m", pid); + + /* In order to access the unified hierarchy we need to mount it */ + if (!mkdtemp(tree)) + return log_error_errno(errno, "Failed to generate temporary mount point for unified hierarchy: %m"); + + if (unified) + r = mount("cgroup", tree, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "none,name=systemd,xattr"); + else + r = mount("cgroup", tree, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "__DEVEL__sane_behavior"); + if (r < 0) { + r = log_error_errno(errno, "Failed to mount unified hierarchy: %m"); + goto finish; + } + + undo_mount = true; + + fn = strjoina(tree, cgroup, "/cgroup.procs"); + (void) mkdir_parents(fn, 0755); + + sprintf(pid_string, PID_FMT, pid); + r = write_string_file(fn, pid_string, 0); + if (r < 0) + log_error_errno(r, "Failed to move process: %m"); + +finish: + if (undo_mount) + (void) umount(tree); + + (void) rmdir(tree); + return r; +} + +int create_subcgroup(pid_t pid, bool unified_requested) { + _cleanup_free_ char *cgroup = NULL; + const char *child; + int unified, r; + CGroupMask supported; + + /* In the unified hierarchy inner nodes may only 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 + * the container into two separate subcgroups. */ + + if (!unified_requested) + return 0; + + unified = cg_unified(); + if (unified < 0) + return log_error_errno(unified, "Failed to determine whether the unified hierachy is used: %m"); + if (unified == 0) + return 0; + + r = cg_mask_supported(&supported); + if (r < 0) + return log_error_errno(r, "Failed to determine supported controllers: %m"); + + r = cg_pid_get_path(SYSTEMD_CGROUP_CONTROLLER, 0, &cgroup); + if (r < 0) + return log_error_errno(r, "Failed to get our control group: %m"); + + child = strjoina(cgroup, "/payload"); + r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, child, pid); + if (r < 0) + return log_error_errno(r, "Failed to create %s subcgroup: %m", child); + + child = strjoina(cgroup, "/supervisor"); + r = cg_create_and_attach(SYSTEMD_CGROUP_CONTROLLER, child, 0); + if (r < 0) + return log_error_errno(r, "Failed to create %s subcgroup: %m", child); + + /* Try to enable as many controllers as possible for the new payload. */ + (void) cg_enable_everywhere(supported, supported, cgroup); + return 0; +} diff --git a/src/systemd-nspawn/nspawn-cgroup.h b/src/systemd-nspawn/nspawn-cgroup.h new file mode 100644 index 0000000000..1ff35a299a --- /dev/null +++ b/src/systemd-nspawn/nspawn-cgroup.h @@ -0,0 +1,27 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include + +int chown_cgroup(pid_t pid, uid_t uid_shift); +int sync_cgroup(pid_t pid, bool unified_requested); +int create_subcgroup(pid_t pid, bool unified_requested); diff --git a/src/systemd-nspawn/nspawn-expose-ports.c b/src/systemd-nspawn/nspawn-expose-ports.c new file mode 100644 index 0000000000..8122a14f7b --- /dev/null +++ b/src/systemd-nspawn/nspawn-expose-ports.c @@ -0,0 +1,245 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "firewall-util.h" +#include "in-addr-util.h" +#include "local-addresses.h" +#include "netlink-util.h" +#include "nspawn-expose-ports.h" +#include "parse-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "util.h" + +int expose_port_parse(ExposePort **l, const char *s) { + + const char *split, *e; + uint16_t container_port, host_port; + int protocol; + ExposePort *p; + int r; + + assert(l); + assert(s); + + if ((e = startswith(s, "tcp:"))) + protocol = IPPROTO_TCP; + else if ((e = startswith(s, "udp:"))) + protocol = IPPROTO_UDP; + else { + e = s; + protocol = IPPROTO_TCP; + } + + split = strchr(e, ':'); + if (split) { + char v[split - e + 1]; + + memcpy(v, e, split - e); + v[split - e] = 0; + + r = safe_atou16(v, &host_port); + if (r < 0 || host_port <= 0) + return -EINVAL; + + r = safe_atou16(split + 1, &container_port); + } else { + r = safe_atou16(e, &container_port); + host_port = container_port; + } + + if (r < 0 || container_port <= 0) + return -EINVAL; + + LIST_FOREACH(ports, p, *l) + if (p->protocol == protocol && p->host_port == host_port) + return -EEXIST; + + p = new(ExposePort, 1); + if (!p) + return -ENOMEM; + + p->protocol = protocol; + p->host_port = host_port; + p->container_port = container_port; + + LIST_PREPEND(ports, *l, p); + + return 0; +} + +void expose_port_free_all(ExposePort *p) { + + while (p) { + ExposePort *q = p; + LIST_REMOVE(ports, p, q); + free(q); + } +} + +int expose_port_flush(ExposePort* l, union in_addr_union *exposed) { + ExposePort *p; + int r, af = AF_INET; + + assert(exposed); + + if (!l) + return 0; + + if (in_addr_is_null(af, exposed)) + return 0; + + log_debug("Lost IP address."); + + LIST_FOREACH(ports, p, l) { + r = fw_add_local_dnat(false, + af, + p->protocol, + NULL, + NULL, 0, + NULL, 0, + p->host_port, + exposed, + p->container_port, + NULL); + if (r < 0) + log_warning_errno(r, "Failed to modify firewall: %m"); + } + + *exposed = IN_ADDR_NULL; + return 0; +} + +int expose_port_execute(sd_netlink *rtnl, ExposePort *l, union in_addr_union *exposed) { + _cleanup_free_ struct local_address *addresses = NULL; + _cleanup_free_ char *pretty = NULL; + union in_addr_union new_exposed; + ExposePort *p; + bool add; + int af = AF_INET, r; + + assert(exposed); + + /* Invoked each time an address is added or removed inside the + * container */ + + if (!l) + return 0; + + r = local_addresses(rtnl, 0, af, &addresses); + if (r < 0) + return log_error_errno(r, "Failed to enumerate local addresses: %m"); + + add = r > 0 && + addresses[0].family == af && + addresses[0].scope < RT_SCOPE_LINK; + + if (!add) + return expose_port_flush(l, exposed); + + new_exposed = addresses[0].address; + if (in_addr_equal(af, exposed, &new_exposed)) + return 0; + + in_addr_to_string(af, &new_exposed, &pretty); + log_debug("New container IP is %s.", strna(pretty)); + + LIST_FOREACH(ports, p, l) { + + r = fw_add_local_dnat(true, + af, + p->protocol, + NULL, + NULL, 0, + NULL, 0, + p->host_port, + &new_exposed, + p->container_port, + in_addr_is_null(af, exposed) ? NULL : exposed); + if (r < 0) + log_warning_errno(r, "Failed to modify firewall: %m"); + } + + *exposed = new_exposed; + return 0; +} + +int expose_port_send_rtnl(int send_fd) { + _cleanup_close_ int fd = -1; + int r; + + assert(send_fd >= 0); + + fd = socket(PF_NETLINK, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, NETLINK_ROUTE); + if (fd < 0) + return log_error_errno(errno, "Failed to allocate container netlink: %m"); + + /* Store away the fd in the socket, so that it stays open as + * long as we run the child */ + r = send_one_fd(send_fd, fd, 0); + if (r < 0) + return log_error_errno(r, "Failed to send netlink fd: %m"); + + return 0; +} + +int expose_port_watch_rtnl( + sd_event *event, + int recv_fd, + sd_netlink_message_handler_t handler, + union in_addr_union *exposed, + sd_netlink **ret) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int fd, r; + + assert(event); + assert(recv_fd >= 0); + assert(ret); + + fd = receive_one_fd(recv_fd, 0); + if (fd < 0) + return log_error_errno(fd, "Failed to recv netlink fd: %m"); + + r = sd_netlink_open_fd(&rtnl, fd); + if (r < 0) { + safe_close(fd); + return log_error_errno(r, "Failed to create rtnl object: %m"); + } + + r = sd_netlink_add_match(rtnl, RTM_NEWADDR, handler, exposed); + if (r < 0) + return log_error_errno(r, "Failed to subscribe to RTM_NEWADDR messages: %m"); + + r = sd_netlink_add_match(rtnl, RTM_DELADDR, handler, exposed); + if (r < 0) + return log_error_errno(r, "Failed to subscribe to RTM_DELADDR messages: %m"); + + r = sd_netlink_attach_event(rtnl, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to add to even loop: %m"); + + *ret = rtnl; + rtnl = NULL; + + return 0; +} diff --git a/src/systemd-nspawn/nspawn-expose-ports.h b/src/systemd-nspawn/nspawn-expose-ports.h new file mode 100644 index 0000000000..0e9f8f7e88 --- /dev/null +++ b/src/systemd-nspawn/nspawn-expose-ports.h @@ -0,0 +1,44 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include + +#include +#include + +#include "in-addr-util.h" +#include "list.h" + +typedef struct ExposePort { + int protocol; + uint16_t host_port; + uint16_t container_port; + LIST_FIELDS(struct ExposePort, ports); +} ExposePort; + +void expose_port_free_all(ExposePort *p); +int expose_port_parse(ExposePort **l, const char *s); + +int expose_port_watch_rtnl(sd_event *event, int recv_fd, sd_netlink_message_handler_t handler, union in_addr_union *exposed, sd_netlink **ret); +int expose_port_send_rtnl(int send_fd); + +int expose_port_execute(sd_netlink *rtnl, ExposePort *l, union in_addr_union *exposed); +int expose_port_flush(ExposePort* l, union in_addr_union *exposed); diff --git a/src/systemd-nspawn/nspawn-gperf.gperf b/src/systemd-nspawn/nspawn-gperf.gperf new file mode 100644 index 0000000000..116655cdd2 --- /dev/null +++ b/src/systemd-nspawn/nspawn-gperf.gperf @@ -0,0 +1,41 @@ +%{ +#include +#include "conf-parser.h" +#include "nspawn-settings.h" +#include "nspawn-expose-ports.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name nspawn_gperf_hash +%define lookup-function-name nspawn_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Exec.Boot, config_parse_boot, 0, 0 +Exec.ProcessTwo, config_parse_pid2, 0, 0, +Exec.Parameters, config_parse_strv, 0, offsetof(Settings, parameters) +Exec.Environment, config_parse_strv, 0, offsetof(Settings, environment) +Exec.User, config_parse_string, 0, offsetof(Settings, user) +Exec.Capability, config_parse_capability, 0, offsetof(Settings, capability) +Exec.DropCapability, config_parse_capability, 0, offsetof(Settings, drop_capability) +Exec.KillSignal, config_parse_signal, 0, offsetof(Settings, kill_signal) +Exec.Personality, config_parse_personality, 0, offsetof(Settings, personality) +Exec.MachineID, config_parse_id128, 0, offsetof(Settings, machine_id) +Exec.WorkingDirectory, config_parse_path, 0, offsetof(Settings, working_directory) +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 +Files.BindReadOnly, config_parse_bind, 1, 0 +Files.TemporaryFileSystem, config_parse_tmpfs, 0, 0 +Network.Private, config_parse_tristate, 0, offsetof(Settings, private_network) +Network.Interface, config_parse_strv, 0, offsetof(Settings, network_interfaces) +Network.MACVLAN, config_parse_strv, 0, offsetof(Settings, network_macvlan) +Network.IPVLAN, config_parse_strv, 0, offsetof(Settings, network_ipvlan) +Network.VirtualEthernet, config_parse_tristate, 0, offsetof(Settings, network_veth) +Network.VirtualEthernetExtra, config_parse_veth_extra, 0, 0 +Network.Bridge, config_parse_string, 0, offsetof(Settings, network_bridge) +Network.Port, config_parse_expose_port, 0, 0 diff --git a/src/systemd-nspawn/nspawn-mount.c b/src/systemd-nspawn/nspawn-mount.c new file mode 100644 index 0000000000..70cca15278 --- /dev/null +++ b/src/systemd-nspawn/nspawn-mount.c @@ -0,0 +1,942 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include + +#include "alloc-util.h" +#include "cgroup-util.h" +#include "escape.h" +#include "fs-util.h" +#include "label.h" +#include "mkdir.h" +#include "mount-util.h" +#include "nspawn-mount.h" +#include "parse-util.h" +#include "path-util.h" +#include "rm-rf.h" +#include "set.h" +#include "stat-util.h" +#include "string-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t) { + CustomMount *c, *ret; + + assert(l); + assert(n); + assert(t >= 0); + assert(t < _CUSTOM_MOUNT_TYPE_MAX); + + c = realloc(*l, (*n + 1) * sizeof(CustomMount)); + if (!c) + return NULL; + + *l = c; + ret = *l + *n; + (*n)++; + + *ret = (CustomMount) { .type = t }; + + return ret; +} + +void custom_mount_free_all(CustomMount *l, unsigned n) { + unsigned i; + + for (i = 0; i < n; i++) { + CustomMount *m = l + i; + + free(m->source); + free(m->destination); + free(m->options); + + if (m->work_dir) { + (void) rm_rf(m->work_dir, REMOVE_ROOT|REMOVE_PHYSICAL); + free(m->work_dir); + } + + strv_free(m->lower); + } + + free(l); +} + +int custom_mount_compare(const void *a, const void *b) { + const CustomMount *x = a, *y = b; + int r; + + r = path_compare(x->destination, y->destination); + if (r != 0) + return r; + + if (x->type < y->type) + return -1; + if (x->type > y->type) + return 1; + + return 0; +} + +int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only) { + _cleanup_free_ char *source = NULL, *destination = NULL, *opts = NULL; + const char *p = s; + CustomMount *m; + int r; + + assert(l); + assert(n); + + r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &source, &destination, NULL); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + if (r == 1) { + destination = strdup(source); + if (!destination) + return -ENOMEM; + } + + if (r == 2 && !isempty(p)) { + opts = strdup(p); + if (!opts) + return -ENOMEM; + } + + if (!path_is_absolute(source)) + return -EINVAL; + + if (!path_is_absolute(destination)) + return -EINVAL; + + m = custom_mount_add(l, n, CUSTOM_MOUNT_BIND); + if (!m) + return log_oom(); + + m->source = source; + m->destination = destination; + m->read_only = read_only; + m->options = opts; + + source = destination = opts = NULL; + return 0; +} + +int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s) { + _cleanup_free_ char *path = NULL, *opts = NULL; + const char *p = s; + CustomMount *m; + int r; + + assert(l); + assert(n); + assert(s); + + r = extract_first_word(&p, &path, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0) + return -EINVAL; + + if (isempty(p)) + opts = strdup("mode=0755"); + else + opts = strdup(p); + if (!opts) + return -ENOMEM; + + if (!path_is_absolute(path)) + return -EINVAL; + + m = custom_mount_add(l, n, CUSTOM_MOUNT_TMPFS); + if (!m) + return -ENOMEM; + + m->destination = path; + m->options = opts; + + path = opts = NULL; + return 0; +} + +static int tmpfs_patch_options( + const char *options, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context, + char **ret) { + + char *buf = NULL; + + if (userns && uid_shift != 0) { + assert(uid_shift != UID_INVALID); + + if (options) + (void) asprintf(&buf, "%s,uid=" UID_FMT ",gid=" UID_FMT, options, uid_shift, uid_shift); + else + (void) asprintf(&buf, "uid=" UID_FMT ",gid=" UID_FMT, uid_shift, uid_shift); + if (!buf) + return -ENOMEM; + + options = buf; + } + +#ifdef HAVE_SELINUX + if (selinux_apifs_context) { + char *t; + + if (options) + t = strjoin(options, ",context=\"", selinux_apifs_context, "\"", NULL); + else + t = strjoin("context=\"", selinux_apifs_context, "\"", NULL); + if (!t) { + free(buf); + return -ENOMEM; + } + + free(buf); + buf = t; + } +#endif + + *ret = buf; + return !!buf; +} + +int mount_sysfs(const char *dest) { + const char *full, *top, *x; + int r; + + top = prefix_roota(dest, "/sys"); + r = path_check_fstype(top, SYSFS_MAGIC); + if (r < 0) + return log_error_errno(r, "Failed to determine filesystem type of %s: %m", top); + /* /sys might already be mounted as sysfs by the outer child in the + * !netns case. In this case, it's all good. Don't touch it because we + * don't have the right to do so, see https://github.com/systemd/systemd/issues/1555. + */ + if (r > 0) + return 0; + + full = prefix_roota(top, "/full"); + + (void) mkdir(full, 0755); + + if (mount("sysfs", full, "sysfs", MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) < 0) + return log_error_errno(errno, "Failed to mount sysfs to %s: %m", full); + + FOREACH_STRING(x, "block", "bus", "class", "dev", "devices", "kernel") { + _cleanup_free_ char *from = NULL, *to = NULL; + + from = prefix_root(full, x); + if (!from) + return log_oom(); + + to = prefix_root(top, x); + if (!to) + return log_oom(); + + (void) mkdir(to, 0755); + + if (mount(from, to, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Failed to mount /sys/%s into place: %m", x); + + if (mount(NULL, to, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, NULL) < 0) + return log_error_errno(errno, "Failed to mount /sys/%s read-only: %m", x); + } + + if (umount(full) < 0) + return log_error_errno(errno, "Failed to unmount %s: %m", full); + + if (rmdir(full) < 0) + return log_error_errno(errno, "Failed to remove %s: %m", full); + + x = prefix_roota(top, "/fs/kdbus"); + (void) mkdir(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); + + return 0; +} + +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) { + + typedef struct MountPoint { + const char *what; + const char *where; + const char *type; + const char *options; + unsigned long flags; + bool fatal; + bool in_userns; + bool use_netns; + } 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 }, +#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 */ +#endif + }; + + unsigned k; + int r; + + for (k = 0; k < ELEMENTSOF(mount_table); k++) { + _cleanup_free_ char *where = NULL, *options = NULL; + const char *o; + + if (in_userns != mount_table[k].in_userns) + continue; + + if (!use_netns && mount_table[k].use_netns) + continue; + + where = prefix_root(dest, mount_table[k].where); + if (!where) + return log_oom(); + + r = path_is_mount_point(where, AT_SYMLINK_FOLLOW); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to detect whether %s is a mount point: %m", where); + + /* Skip this entry if it is not a remount. */ + if (mount_table[k].what && r > 0) + continue; + + r = mkdir_p(where, 0755); + if (r < 0) { + 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); + 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); + if (r < 0) + return log_oom(); + if (r > 0) + o = options; + } + + if (mount(mount_table[k].what, + where, + mount_table[k].type, + mount_table[k].flags, + o) < 0) { + + if (mount_table[k].fatal) + return log_error_errno(errno, "mount(%s) failed: %m", where); + + log_warning_errno(errno, "mount(%s) failed, ignoring: %m", where); + } + } + + return 0; +} + +static int parse_mount_bind_options(const char *options, unsigned long *mount_flags, char **mount_opts) { + const char *p = options; + unsigned long flags = *mount_flags; + char *opts = NULL; + + assert(options); + + for (;;) { + _cleanup_free_ char *word = NULL; + int r = extract_first_word(&p, &word, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to extract mount option: %m"); + if (r == 0) + break; + + if (streq(word, "rbind")) + flags |= MS_REC; + else if (streq(word, "norbind")) + flags &= ~MS_REC; + else { + log_error("Invalid bind mount option: %s", word); + return -EINVAL; + } + } + + *mount_flags = flags; + /* in the future mount_opts will hold string options for mount(2) */ + *mount_opts = opts; + + return 0; +} + +static int mount_bind(const char *dest, CustomMount *m) { + struct stat source_st, dest_st; + const char *where; + unsigned long mount_flags = MS_BIND | MS_REC; + _cleanup_free_ char *mount_opts = NULL; + int r; + + assert(m); + + if (m->options) { + r = parse_mount_bind_options(m->options, &mount_flags, &mount_opts); + if (r < 0) + return r; + } + + if (stat(m->source, &source_st) < 0) + return log_error_errno(errno, "Failed to stat %s: %m", m->source); + + where = prefix_roota(dest, m->destination); + + if (stat(where, &dest_st) >= 0) { + if (S_ISDIR(source_st.st_mode) && !S_ISDIR(dest_st.st_mode)) { + log_error("Cannot bind mount directory %s on file %s.", m->source, where); + return -EINVAL; + } + + if (!S_ISDIR(source_st.st_mode) && S_ISDIR(dest_st.st_mode)) { + log_error("Cannot bind mount file %s on directory %s.", m->source, where); + return -EINVAL; + } + + } else if (errno == ENOENT) { + r = mkdir_parents_label(where, 0755); + if (r < 0) + return log_error_errno(r, "Failed to make parents of %s: %m", where); + } else { + return log_error_errno(errno, "Failed to stat %s: %m", where); + } + + /* Create the mount point. Any non-directory file can be + * mounted on any non-directory file (regular, fifo, socket, + * char, block). + */ + if (S_ISDIR(source_st.st_mode)) + r = mkdir_label(where, 0755); + else + r = touch(where); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Failed to create mount point %s: %m", where); + + if (mount(m->source, where, NULL, mount_flags, mount_opts) < 0) + return log_error_errno(errno, "mount(%s) failed: %m", where); + + if (m->read_only) { + r = bind_remount_recursive(where, true); + if (r < 0) + return log_error_errno(r, "Read-only bind mount failed: %m"); + } + + return 0; +} + +static int mount_tmpfs( + const char *dest, + CustomMount *m, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + const char *where, *options; + _cleanup_free_ char *buf = NULL; + int r; + + assert(dest); + assert(m); + + where = prefix_roota(dest, m->destination); + + r = mkdir_p_label(where, 0755); + 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); + if (r < 0) + return log_oom(); + options = r > 0 ? buf : m->options; + + if (mount("tmpfs", where, "tmpfs", MS_NODEV|MS_STRICTATIME, options) < 0) + return log_error_errno(errno, "tmpfs mount to %s failed: %m", where); + + return 0; +} + +static char *joined_and_escaped_lower_dirs(char * const *lower) { + _cleanup_strv_free_ char **sv = NULL; + + sv = strv_copy(lower); + if (!sv) + return NULL; + + strv_reverse(sv); + + if (!strv_shell_escape(sv, ",:")) + return NULL; + + return strv_join(sv, ":"); +} + +static int mount_overlay(const char *dest, CustomMount *m) { + _cleanup_free_ char *lower = NULL; + const char *where, *options; + int r; + + assert(dest); + assert(m); + + where = prefix_roota(dest, m->destination); + + r = mkdir_label(where, 0755); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Creating mount point for overlay %s failed: %m", where); + + (void) mkdir_p_label(m->source, 0755); + + lower = joined_and_escaped_lower_dirs(m->lower); + if (!lower) + return log_oom(); + + if (m->read_only) { + _cleanup_free_ char *escaped_source = NULL; + + escaped_source = shell_escape(m->source, ",:"); + if (!escaped_source) + return log_oom(); + + options = strjoina("lowerdir=", escaped_source, ":", lower); + } else { + _cleanup_free_ char *escaped_source = NULL, *escaped_work_dir = NULL; + + assert(m->work_dir); + (void) mkdir_label(m->work_dir, 0700); + + escaped_source = shell_escape(m->source, ",:"); + if (!escaped_source) + return log_oom(); + escaped_work_dir = shell_escape(m->work_dir, ",:"); + if (!escaped_work_dir) + return log_oom(); + + options = strjoina("lowerdir=", lower, ",upperdir=", escaped_source, ",workdir=", escaped_work_dir); + } + + if (mount("overlay", where, "overlay", m->read_only ? MS_RDONLY : 0, options) < 0) + return log_error_errno(errno, "overlay mount to %s failed: %m", where); + + return 0; +} + +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) { + + unsigned i; + int r; + + assert(dest); + + for (i = 0; i < n; i++) { + CustomMount *m = mounts + i; + + switch (m->type) { + + case CUSTOM_MOUNT_BIND: + r = mount_bind(dest, m); + break; + + case CUSTOM_MOUNT_TMPFS: + r = mount_tmpfs(dest, m, userns, uid_shift, uid_range, selinux_apifs_context); + break; + + case CUSTOM_MOUNT_OVERLAY: + r = mount_overlay(dest, m); + break; + + default: + assert_not_reached("Unknown custom mount type"); + } + + 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; + + to = strjoina(strempty(dest), "/sys/fs/cgroup/", hierarchy); + + r = path_is_mount_point(to, 0); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "Failed to determine if %s is mounted already: %m", to); + if (r > 0) + return 0; + + mkdir_p(to, 0755); + + /* The superblock mount options of the mount point need to be + * identical to the hosts', and hence writable... */ + if (mount("cgroup", to, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, controller) < 0) + return log_error_errno(errno, "Failed to mount to %s: %m", to); + + /* ... hence let's only make the bind mount read-only, not the + * superblock. */ + if (read_only) { + if (mount(NULL, to, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) + return log_error_errno(errno, "Failed to remount %s read-only: %m", to); + } + return 1; +} + +static int mount_legacy_cgroups( + 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; + + cgroup_root = prefix_roota(dest, "/sys/fs/cgroup"); + + (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; + + r = tmpfs_patch_options("mode=755", userns, uid_shift, uid_range, 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 = cg_kernel_controllers(controllers); + if (r < 0) + return log_error_errno(r, "Failed to determine cgroup controllers: %m"); + + for (;;) { + _cleanup_free_ char *controller = NULL, *origin = NULL, *combined = NULL; + + controller = set_steal_first(controllers); + if (!controller) + break; + + origin = prefix_root("/sys/fs/cgroup/", controller); + if (!origin) + return log_oom(); + + r = readlink_malloc(origin, &combined); + if (r == -EINVAL) { + /* Not a symbolic link, but directly a single cgroup hierarchy */ + + r = mount_legacy_cgroup_hierarchy(dest, controller, controller, true); + if (r < 0) + return r; + + } else if (r < 0) + return log_error_errno(r, "Failed to read link %s: %m", origin); + else { + _cleanup_free_ char *target = NULL; + + target = prefix_root(dest, origin); + if (!target) + return log_oom(); + + /* A symbolic link, a combination of controllers in one hierarchy */ + + if (!filename_is_valid(combined)) { + log_warning("Ignoring invalid combined hierarchy %s.", combined); + continue; + } + + r = mount_legacy_cgroup_hierarchy(dest, combined, combined, true); + if (r < 0) + return r; + + r = symlink_idempotent(combined, target); + if (r == -EINVAL) { + log_error("Invalid existing symlink for combined hierarchy"); + return r; + } + if (r < 0) + return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m"); + } + } + +skip_controllers: + r = mount_legacy_cgroup_hierarchy(dest, "none,name=systemd,xattr", "systemd", false); + if (r < 0) + return r; + + 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; +} + +static int mount_unified_cgroups(const char *dest) { + const char *p; + int r; + + assert(dest); + + p = prefix_roota(dest, "/sys/fs/cgroup"); + + (void) mkdir_p(p, 0755); + + r = path_is_mount_point(p, AT_SYMLINK_FOLLOW); + if (r < 0) + return log_error_errno(r, "Failed to determine if %s is mounted already: %m", p); + if (r > 0) { + p = prefix_roota(dest, "/sys/fs/cgroup/cgroup.procs"); + if (access(p, F_OK) >= 0) + return 0; + if (errno != ENOENT) + return log_error_errno(errno, "Failed to determine if mount point %s contains the unified cgroup hierarchy: %m", p); + + log_error("%s is already mounted but not a unified cgroup hierarchy. Refusing.", p); + return -EINVAL; + } + + if (mount("cgroup", p, "cgroup", MS_NOSUID|MS_NOEXEC|MS_NODEV, "__DEVEL__sane_behavior") < 0) + return log_error_errno(errno, "Failed to mount unified cgroup hierarchy to %s: %m", p); + + return 0; +} + +int mount_cgroups( + const char *dest, + bool unified_requested, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + if (unified_requested) + return mount_unified_cgroups(dest); + else + return mount_legacy_cgroups(dest, userns, uid_shift, uid_range, selinux_apifs_context); +} + +int mount_systemd_cgroup_writable( + const char *dest, + bool unified_requested) { + + _cleanup_free_ char *own_cgroup_path = NULL; + const char *systemd_root, *systemd_own; + int r; + + assert(dest); + + r = cg_pid_get_path(NULL, 0, &own_cgroup_path); + if (r < 0) + return log_error_errno(r, "Failed to determine our own cgroup path: %m"); + + /* If we are living in the top-level, then there's nothing to do... */ + if (path_equal(own_cgroup_path, "/")) + return 0; + + if (unified_requested) { + systemd_own = strjoina(dest, "/sys/fs/cgroup", own_cgroup_path); + systemd_root = prefix_roota(dest, "/sys/fs/cgroup"); + } else { + systemd_own = strjoina(dest, "/sys/fs/cgroup/systemd", own_cgroup_path); + systemd_root = prefix_roota(dest, "/sys/fs/cgroup/systemd"); + } + + /* Make our own cgroup a (writable) bind mount */ + if (mount(systemd_own, systemd_own, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Failed to turn %s into a bind mount: %m", own_cgroup_path); + + /* And then remount the systemd cgroup root read-only */ + if (mount(NULL, systemd_root, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0) + return log_error_errno(errno, "Failed to mount cgroup root read-only: %m"); + + return 0; +} + +int setup_volatile_state( + const char *directory, + VolatileMode mode, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + _cleanup_free_ char *buf = NULL; + const char *p, *options; + int r; + + assert(directory); + + if (mode != VOLATILE_STATE) + return 0; + + /* --volatile=state means we simply overmount /var + with a tmpfs, and the rest read-only. */ + + r = bind_remount_recursive(directory, true); + if (r < 0) + return log_error_errno(r, "Failed to remount %s read-only: %m", directory); + + p = prefix_roota(directory, "/var"); + r = mkdir(p, 0755); + if (r < 0 && errno != EEXIST) + 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); + if (r < 0) + return log_oom(); + if (r > 0) + options = buf; + + if (mount("tmpfs", p, "tmpfs", MS_STRICTATIME, options) < 0) + return log_error_errno(errno, "Failed to mount tmpfs to /var: %m"); + + return 0; +} + +int setup_volatile( + const char *directory, + VolatileMode mode, + bool userns, uid_t uid_shift, uid_t uid_range, + const char *selinux_apifs_context) { + + bool tmpfs_mounted = false, bind_mounted = false; + char template[] = "/tmp/nspawn-volatile-XXXXXX"; + _cleanup_free_ char *buf = NULL; + const char *f, *t, *options; + int r; + + assert(directory); + + if (mode != VOLATILE_YES) + return 0; + + /* --volatile=yes means we mount a tmpfs to the root dir, and + the original /usr to use inside it, and that read-only. */ + + if (!mkdtemp(template)) + 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); + if (r < 0) + return log_oom(); + if (r > 0) + options = buf; + + if (mount("tmpfs", template, "tmpfs", MS_STRICTATIME, options) < 0) { + r = log_error_errno(errno, "Failed to mount tmpfs for root directory: %m"); + goto fail; + } + + tmpfs_mounted = true; + + f = prefix_roota(directory, "/usr"); + t = prefix_roota(template, "/usr"); + + r = mkdir(t, 0755); + if (r < 0 && errno != EEXIST) { + r = log_error_errno(errno, "Failed to create %s: %m", t); + goto fail; + } + + if (mount(f, t, NULL, MS_BIND|MS_REC, NULL) < 0) { + r = log_error_errno(errno, "Failed to create /usr bind mount: %m"); + goto fail; + } + + bind_mounted = true; + + r = bind_remount_recursive(t, true); + if (r < 0) { + log_error_errno(r, "Failed to remount %s read-only: %m", t); + goto fail; + } + + if (mount(template, directory, NULL, MS_MOVE, NULL) < 0) { + r = log_error_errno(errno, "Failed to move root mount: %m"); + goto fail; + } + + (void) rmdir(template); + + return 0; + +fail: + if (bind_mounted) + (void) umount(t); + + if (tmpfs_mounted) + (void) umount(template); + (void) rmdir(template); + return r; +} + +VolatileMode volatile_mode_from_string(const char *s) { + int b; + + if (isempty(s)) + return _VOLATILE_MODE_INVALID; + + b = parse_boolean(s); + if (b > 0) + return VOLATILE_YES; + if (b == 0) + return VOLATILE_NO; + + if (streq(s, "state")) + return VOLATILE_STATE; + + return _VOLATILE_MODE_INVALID; +} diff --git a/src/systemd-nspawn/nspawn-mount.h b/src/systemd-nspawn/nspawn-mount.h new file mode 100644 index 0000000000..0daf145412 --- /dev/null +++ b/src/systemd-nspawn/nspawn-mount.h @@ -0,0 +1,69 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include + +typedef enum VolatileMode { + VOLATILE_NO, + VOLATILE_YES, + VOLATILE_STATE, + _VOLATILE_MODE_MAX, + _VOLATILE_MODE_INVALID = -1 +} VolatileMode; + +typedef enum CustomMountType { + CUSTOM_MOUNT_BIND, + CUSTOM_MOUNT_TMPFS, + CUSTOM_MOUNT_OVERLAY, + _CUSTOM_MOUNT_TYPE_MAX, + _CUSTOM_MOUNT_TYPE_INVALID = -1 +} CustomMountType; + +typedef struct CustomMount { + CustomMountType type; + bool read_only; + char *source; /* for overlayfs this is the upper directory */ + char *destination; + char *options; + char *work_dir; + char **lower; +} CustomMount; + +CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t); + +void custom_mount_free_all(CustomMount *l, unsigned n); +int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only); +int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s); + +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_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); + +int setup_volatile(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); +int setup_volatile_state(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); + +VolatileMode volatile_mode_from_string(const char *s); diff --git a/src/systemd-nspawn/nspawn-network.c b/src/systemd-nspawn/nspawn-network.c new file mode 100644 index 0000000000..d03fd001a7 --- /dev/null +++ b/src/systemd-nspawn/nspawn-network.c @@ -0,0 +1,540 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include + +#include "libudev.h" +#include +#include + +#include "alloc-util.h" +#include "ether-addr-util.h" +#include "netlink-util.h" +#include "nspawn-network.h" +#include "siphash24.h" +#include "string-util.h" +#include "udev-util.h" +#include "util.h" + +#define HOST_HASH_KEY SD_ID128_MAKE(1a,37,6f,c7,46,ec,45,0b,ad,a3,d5,31,06,60,5d,b1) +#define CONTAINER_HASH_KEY SD_ID128_MAKE(c3,c4,f9,19,b5,57,b2,1c,e6,cf,14,27,03,9c,ee,a2) +#define VETH_EXTRA_HOST_HASH_KEY SD_ID128_MAKE(48,c7,f6,b7,ea,9d,4c,9e,b7,28,d4,de,91,d5,bf,66) +#define VETH_EXTRA_CONTAINER_HASH_KEY SD_ID128_MAKE(af,50,17,61,ce,f9,4d,35,84,0d,2b,20,54,be,ce,59) +#define MACVLAN_HASH_KEY SD_ID128_MAKE(00,13,6d,bc,66,83,44,81,bb,0c,f9,51,1f,24,a6,6f) + +static int generate_mac( + const char *machine_name, + struct ether_addr *mac, + sd_id128_t hash_key, + uint64_t idx) { + + uint64_t result; + size_t l, sz; + uint8_t *v, *i; + int r; + + l = strlen(machine_name); + sz = sizeof(sd_id128_t) + l; + if (idx > 0) + sz += sizeof(idx); + + v = alloca(sz); + + /* fetch some persistent data unique to the host */ + r = sd_id128_get_machine((sd_id128_t*) v); + if (r < 0) + return r; + + /* combine with some data unique (on this host) to this + * container instance */ + i = mempcpy(v + sizeof(sd_id128_t), machine_name, l); + if (idx > 0) { + idx = htole64(idx); + memcpy(i, &idx, sizeof(idx)); + } + + /* Let's hash the host machine ID plus the container name. We + * use a fixed, but originally randomly created hash key here. */ + result = htole64(siphash24(v, sz, hash_key.bytes)); + + assert_cc(ETH_ALEN <= sizeof(result)); + memcpy(mac->ether_addr_octet, &result, ETH_ALEN); + + /* see eth_random_addr in the kernel */ + mac->ether_addr_octet[0] &= 0xfe; /* clear multicast bit */ + mac->ether_addr_octet[0] |= 0x02; /* set local assignment bit (IEEE802) */ + + return 0; +} + +static int add_veth( + sd_netlink *rtnl, + pid_t pid, + const char *ifname_host, + const struct ether_addr *mac_host, + const char *ifname_container, + const struct ether_addr *mac_container) { + + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int r; + + assert(rtnl); + assert(ifname_host); + assert(mac_host); + assert(ifname_container); + assert(mac_container); + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, ifname_host); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, mac_host); + if (r < 0) + return log_error_errno(r, "Failed to add netlink MAC address: %m"); + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "veth"); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container(m, VETH_INFO_PEER); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, ifname_container); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, mac_container); + if (r < 0) + return log_error_errno(r, "Failed to add netlink MAC address: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); + if (r < 0) + return log_error_errno(r, "Failed to add netlink namespace field: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add new veth interfaces (%s:%s): %m", ifname_host, ifname_container); + + return 0; +} + +int setup_veth(const char *machine_name, + pid_t pid, + char iface_name[IFNAMSIZ], + bool bridge) { + + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + struct ether_addr mac_host, mac_container; + int r, i; + + assert(machine_name); + assert(pid > 0); + assert(iface_name); + + /* Use two different interface name prefixes depending whether + * we are in bridge mode or not. */ + snprintf(iface_name, IFNAMSIZ - 1, "%s-%s", + bridge ? "vb" : "ve", machine_name); + + r = generate_mac(machine_name, &mac_container, CONTAINER_HASH_KEY, 0); + if (r < 0) + return log_error_errno(r, "Failed to generate predictable MAC address for container side: %m"); + + r = generate_mac(machine_name, &mac_host, HOST_HASH_KEY, 0); + if (r < 0) + return log_error_errno(r, "Failed to generate predictable MAC address for host side: %m"); + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + r = add_veth(rtnl, pid, iface_name, &mac_host, "host0", &mac_container); + if (r < 0) + return r; + + i = (int) if_nametoindex(iface_name); + if (i <= 0) + return log_error_errno(errno, "Failed to resolve interface %s: %m", iface_name); + + return i; +} + +int setup_veth_extra( + const char *machine_name, + pid_t pid, + char **pairs) { + + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + uint64_t idx = 0; + char **a, **b; + int r; + + assert(machine_name); + assert(pid > 0); + + if (strv_isempty(pairs)) + return 0; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + STRV_FOREACH_PAIR(a, b, pairs) { + struct ether_addr mac_host, mac_container; + + r = generate_mac(machine_name, &mac_container, VETH_EXTRA_CONTAINER_HASH_KEY, idx); + if (r < 0) + return log_error_errno(r, "Failed to generate predictable MAC address for container side of extra veth link: %m"); + + r = generate_mac(machine_name, &mac_host, VETH_EXTRA_HOST_HASH_KEY, idx); + if (r < 0) + return log_error_errno(r, "Failed to generate predictable MAC address for container side of extra veth link: %m"); + + r = add_veth(rtnl, pid, *a, &mac_host, *b, &mac_container); + if (r < 0) + return r; + + idx ++; + } + + return 0; +} + +int setup_bridge(const char *veth_name, const char *bridge_name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + int r, bridge_ifi; + + assert(veth_name); + assert(bridge_name); + + bridge_ifi = (int) if_nametoindex(bridge_name); + if (bridge_ifi <= 0) + return log_error_errno(errno, "Failed to resolve interface %s: %m", bridge_name); + + 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, &m, RTM_SETLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_rtnl_message_link_set_flags(m, IFF_UP, IFF_UP); + if (r < 0) + return log_error_errno(r, "Failed to set IFF_UP flag: %m"); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, veth_name); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name field: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_MASTER, bridge_ifi); + if (r < 0) + return log_error_errno(r, "Failed to add netlink master field: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add veth interface to bridge: %m"); + + return bridge_ifi; +} + +static int parse_interface(struct udev *udev, const char *name) { + _cleanup_udev_device_unref_ struct udev_device *d = NULL; + char ifi_str[2 + DECIMAL_STR_MAX(int)]; + int ifi; + + ifi = (int) if_nametoindex(name); + if (ifi <= 0) + return log_error_errno(errno, "Failed to resolve interface %s: %m", name); + + sprintf(ifi_str, "n%i", ifi); + d = udev_device_new_from_device_id(udev, ifi_str); + if (!d) + return log_error_errno(errno, "Failed to get udev device for interface %s: %m", name); + + if (udev_device_get_is_initialized(d) <= 0) { + log_error("Network interface %s is not initialized yet.", name); + return -EBUSY; + } + + return ifi; +} + +int move_network_interfaces(pid_t pid, char **ifaces) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + char **i; + int r; + + if (strv_isempty(ifaces)) + return 0; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + udev = udev_new(); + if (!udev) { + log_error("Failed to connect to udev."); + return -ENOMEM; + } + + STRV_FOREACH(i, ifaces) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + int ifi; + + ifi = parse_interface(udev, *i); + if (ifi < 0) + return ifi; + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, ifi); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); + if (r < 0) + return log_error_errno(r, "Failed to append namespace PID to netlink message: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to move interface %s to namespace: %m", *i); + } + + return 0; +} + +int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + unsigned idx = 0; + char **i; + int r; + + if (strv_isempty(ifaces)) + return 0; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + udev = udev_new(); + if (!udev) { + log_error("Failed to connect to udev."); + return -ENOMEM; + } + + STRV_FOREACH(i, ifaces) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + _cleanup_free_ char *n = NULL; + struct ether_addr mac; + int ifi; + + ifi = parse_interface(udev, *i); + if (ifi < 0) + return ifi; + + r = generate_mac(machine_name, &mac, MACVLAN_HASH_KEY, idx++); + if (r < 0) + return log_error_errno(r, "Failed to create MACVLAN MAC address: %m"); + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_LINK, ifi); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface index: %m"); + + n = strappend("mv-", *i); + if (!n) + return log_oom(); + + strshorten(n, IFNAMSIZ-1); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_ether_addr(m, IFLA_ADDRESS, &mac); + if (r < 0) + return log_error_errno(r, "Failed to add netlink MAC address: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); + if (r < 0) + return log_error_errno(r, "Failed to add netlink namespace field: %m"); + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "macvlan"); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_MACVLAN_MODE, MACVLAN_MODE_BRIDGE); + if (r < 0) + return log_error_errno(r, "Failed to append macvlan mode: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add new macvlan interfaces: %m"); + } + + return 0; +} + +int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + char **i; + int r; + + if (strv_isempty(ifaces)) + return 0; + + r = sd_netlink_open(&rtnl); + if (r < 0) + return log_error_errno(r, "Failed to connect to netlink: %m"); + + udev = udev_new(); + if (!udev) { + log_error("Failed to connect to udev."); + return -ENOMEM; + } + + STRV_FOREACH(i, ifaces) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + _cleanup_free_ char *n = NULL; + int ifi; + + ifi = parse_interface(udev, *i); + if (ifi < 0) + return ifi; + + r = sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0); + if (r < 0) + return log_error_errno(r, "Failed to allocate netlink message: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_LINK, ifi); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface index: %m"); + + n = strappend("iv-", *i); + if (!n) + return log_oom(); + + strshorten(n, IFNAMSIZ-1); + + r = sd_netlink_message_append_string(m, IFLA_IFNAME, n); + if (r < 0) + return log_error_errno(r, "Failed to add netlink interface name: %m"); + + r = sd_netlink_message_append_u32(m, IFLA_NET_NS_PID, pid); + if (r < 0) + return log_error_errno(r, "Failed to add netlink namespace field: %m"); + + r = sd_netlink_message_open_container(m, IFLA_LINKINFO); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "ipvlan"); + if (r < 0) + return log_error_errno(r, "Failed to open netlink container: %m"); + + r = sd_netlink_message_append_u16(m, IFLA_IPVLAN_MODE, IPVLAN_MODE_L2); + if (r < 0) + return log_error_errno(r, "Failed to add ipvlan mode: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_message_close_container(m); + if (r < 0) + return log_error_errno(r, "Failed to close netlink container: %m"); + + r = sd_netlink_call(rtnl, m, 0, NULL); + if (r < 0) + return log_error_errno(r, "Failed to add new ipvlan interfaces: %m"); + } + + return 0; +} + +int veth_extra_parse(char ***l, const char *p) { + _cleanup_free_ char *a = NULL, *b = NULL; + int r; + + r = extract_first_word(&p, &a, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0 || isempty(a)) + return -EINVAL; + + r = extract_first_word(&p, &b, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return r; + if (r == 0 || isempty(b)) { + free(b); + b = strdup(a); + if (!b) + return -ENOMEM; + } + + if (p) + return -EINVAL; + + r = strv_push_pair(l, a, b); + if (r < 0) + return -ENOMEM; + + a = b = NULL; + return 0; +} diff --git a/src/systemd-nspawn/nspawn-network.h b/src/systemd-nspawn/nspawn-network.h new file mode 100644 index 0000000000..9ab1606d1c --- /dev/null +++ b/src/systemd-nspawn/nspawn-network.h @@ -0,0 +1,36 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include +#include + +int setup_veth(const char *machine_name, pid_t pid, char iface_name[IFNAMSIZ], bool bridge); +int setup_veth_extra(const char *machine_name, pid_t pid, char **pairs); + +int setup_bridge(const char *veth_name, const char *bridge_name); + +int setup_macvlan(const char *machine_name, pid_t pid, char **ifaces); +int setup_ipvlan(const char *machine_name, pid_t pid, char **ifaces); + +int move_network_interfaces(pid_t pid, char **ifaces); + +int veth_extra_parse(char ***l, const char *p); diff --git a/src/systemd-nspawn/nspawn-register.c b/src/systemd-nspawn/nspawn-register.c new file mode 100644 index 0000000000..3b0d778f43 --- /dev/null +++ b/src/systemd-nspawn/nspawn-register.c @@ -0,0 +1,235 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include + +#include "bus-error.h" +#include "bus-util.h" +#include "nspawn-register.h" +#include "stat-util.h" +#include "strv.h" +#include "util.h" + +int register_machine( + const char *machine_name, + pid_t pid, + const char *directory, + sd_id128_t uuid, + int local_ifindex, + const char *slice, + CustomMount *mounts, + unsigned n_mounts, + int kill_signal, + char **properties, + bool keep_unit, + const char *service) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + r = sd_bus_default_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to open system bus: %m"); + + if (keep_unit) { + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "RegisterMachineWithNetwork", + &error, + NULL, + "sayssusai", + machine_name, + SD_BUS_MESSAGE_APPEND_ID128(uuid), + service, + "container", + (uint32_t) pid, + strempty(directory), + local_ifindex > 0 ? 1 : 0, local_ifindex); + } else { + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL; + char **i; + unsigned j; + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "CreateMachineWithNetwork"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append( + m, + "sayssusai", + machine_name, + SD_BUS_MESSAGE_APPEND_ID128(uuid), + service, + "container", + (uint32_t) pid, + strempty(directory), + local_ifindex > 0 ? 1 : 0, local_ifindex); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + if (!isempty(slice)) { + r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_append(m, "(sv)", "DevicePolicy", "s", "strict"); + if (r < 0) + return bus_log_create_error(r); + + /* If you make changes here, also make sure to update + * 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, + /* 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); + + for (j = 0; j < n_mounts; j++) { + CustomMount *cm = mounts + j; + + if (cm->type != CUSTOM_MOUNT_BIND) + continue; + + r = is_device_node(cm->source); + if (r < 0) + return log_error_errno(r, "Failed to stat %s: %m", cm->source); + + if (r) { + r = sd_bus_message_append(m, "(sv)", "DeviceAllow", "a(ss)", 1, + cm->source, cm->read_only ? "r" : "rw"); + if (r < 0) + return log_error_errno(r, "Failed to append message arguments: %m"); + } + } + + if (kill_signal != 0) { + r = sd_bus_message_append(m, "(sv)", "KillSignal", "i", kill_signal); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "(sv)", "KillMode", "s", "mixed"); + if (r < 0) + return bus_log_create_error(r); + } + + STRV_FOREACH(i, properties) { + r = bus_append_unit_property_assignment(m, *i); + if (r < 0) + return r; + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_call(bus, m, 0, &error, NULL); + } + + if (r < 0) { + log_error("Failed to register machine: %s", bus_error_message(&error, r)); + return r; + } + + return 0; +} + +int terminate_machine(pid_t pid) { + _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 *path; + int r; + + r = sd_bus_default_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to open system bus: %m"); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "GetMachineByPID", + &error, + &reply, + "u", + (uint32_t) pid); + if (r < 0) { + /* Note that the machine might already have been + * cleaned up automatically, hence don't consider it a + * failure if we cannot get the machine object. */ + log_debug("Failed to get machine: %s", bus_error_message(&error, r)); + return 0; + } + + r = sd_bus_message_read(reply, "o", &path); + if (r < 0) + return bus_log_parse_error(r); + + r = sd_bus_call_method( + bus, + "org.freedesktop.machine1", + path, + "org.freedesktop.machine1.Machine", + "Terminate", + &error, + NULL, + NULL); + if (r < 0) { + log_debug("Failed to terminate machine: %s", bus_error_message(&error, r)); + return 0; + } + + return 0; +} diff --git a/src/systemd-nspawn/nspawn-register.h b/src/systemd-nspawn/nspawn-register.h new file mode 100644 index 0000000000..c7a50f7477 --- /dev/null +++ b/src/systemd-nspawn/nspawn-register.h @@ -0,0 +1,29 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include + +#include + +#include "nspawn-mount.h" + +int register_machine(const char *machine_name, pid_t pid, const char *directory, sd_id128_t uuid, int local_ifindex, const char *slice, CustomMount *mounts, unsigned n_mounts, int kill_signal, char **properties, bool keep_unit, const char *service); +int terminate_machine(pid_t pid); diff --git a/src/systemd-nspawn/nspawn-settings.c b/src/systemd-nspawn/nspawn-settings.c new file mode 100644 index 0000000000..4fb0054698 --- /dev/null +++ b/src/systemd-nspawn/nspawn-settings.c @@ -0,0 +1,394 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include "alloc-util.h" +#include "cap-list.h" +#include "conf-parser.h" +#include "nspawn-network.h" +#include "nspawn-settings.h" +#include "parse-util.h" +#include "process-util.h" +#include "strv.h" +#include "util.h" + +int settings_load(FILE *f, const char *path, Settings **ret) { + _cleanup_(settings_freep) Settings *s = NULL; + int r; + + assert(path); + assert(ret); + + s = new0(Settings, 1); + if (!s) + return -ENOMEM; + + s->start_mode = _START_MODE_INVALID; + s->personality = PERSONALITY_INVALID; + + s->read_only = -1; + s->volatile_mode = _VOLATILE_MODE_INVALID; + + s->private_network = -1; + s->network_veth = -1; + + r = config_parse(NULL, path, f, + "Exec\0" + "Network\0" + "Files\0", + config_item_perf_lookup, nspawn_gperf_lookup, + false, + false, + true, + s); + if (r < 0) + return r; + + *ret = s; + s = NULL; + + return 0; +} + +Settings* settings_free(Settings *s) { + + if (!s) + return NULL; + + strv_free(s->parameters); + strv_free(s->environment); + free(s->user); + free(s->working_directory); + + strv_free(s->network_interfaces); + strv_free(s->network_macvlan); + strv_free(s->network_ipvlan); + strv_free(s->network_veth_extra); + free(s->network_bridge); + expose_port_free_all(s->expose_ports); + + custom_mount_free_all(s->custom_mounts, s->n_custom_mounts); + free(s); + + return NULL; +} + +bool settings_private_network(Settings *s) { + assert(s); + + return + s->private_network > 0 || + s->network_veth > 0 || + s->network_bridge || + s->network_interfaces || + s->network_macvlan || + s->network_ipvlan || + s->network_veth_extra; +} + +bool settings_network_veth(Settings *s) { + assert(s); + + return + s->network_veth > 0 || + s->network_bridge; +} + +DEFINE_CONFIG_PARSE_ENUM(config_parse_volatile_mode, volatile_mode, VolatileMode, "Failed to parse volatile mode"); + +int config_parse_expose_port( + 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) { + + Settings *s = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = expose_port_parse(&s->expose_ports, rvalue); + if (r == -EEXIST) { + log_syntax(unit, LOG_ERR, filename, line, r, "Duplicate port specification, ignoring: %s", rvalue); + return 0; + } + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse host port %s: %m", rvalue); + return 0; + } + + return 0; +} + +int config_parse_capability( + 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 u = 0, *result = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + for (;;) { + _cleanup_free_ char *word = NULL; + int cap; + + r = extract_first_word(&rvalue, &word, NULL, 0); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract capability string, ignoring: %s", rvalue); + return 0; + } + if (r == 0) + break; + + cap = capability_from_name(word); + if (cap < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse capability, ignoring: %s", word); + continue; + } + + u |= 1 << ((uint64_t) cap); + } + + if (u == 0) + return 0; + + *result |= u; + return 0; +} + +int config_parse_id128( + 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) { + + sd_id128_t t, *result = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = sd_id128_from_string(rvalue, &t); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse 128bit ID/UUID, ignoring: %s", rvalue); + return 0; + } + + *result = t; + return 0; +} + +int config_parse_bind( + 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) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = bind_mount_parse(&settings->custom_mounts, &settings->n_custom_mounts, rvalue, ltype); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid bind mount specification %s: %m", rvalue); + return 0; + } + + return 0; +} + +int config_parse_tmpfs( + 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) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = tmpfs_mount_parse(&settings->custom_mounts, &settings->n_custom_mounts, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid temporary file system specification %s: %m", rvalue); + return 0; + } + + return 0; +} + +int config_parse_veth_extra( + 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) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = veth_extra_parse(&settings->network_veth_extra, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Invalid extra virtual Ethernet link specification %s: %m", rvalue); + return 0; + } + + return 0; +} + +int config_parse_boot( + 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) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse Boot= parameter %s, ignoring: %m", rvalue); + return 0; + } + + if (r > 0) { + if (settings->start_mode == START_PID2) + goto conflict; + + settings->start_mode = START_BOOT; + } else { + if (settings->start_mode == START_BOOT) + goto conflict; + + if (settings->start_mode < 0) + settings->start_mode = START_PID1; + } + + return 0; + +conflict: + log_syntax(unit, LOG_ERR, filename, line, r, "Conflicting Boot= or ProcessTwo= setting found. Ignoring."); + return 0; +} + +int config_parse_pid2( + 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) { + + Settings *settings = data; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + r = parse_boolean(rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse ProcessTwo= parameter %s, ignoring: %m", rvalue); + return 0; + } + + if (r > 0) { + if (settings->start_mode == START_BOOT) + goto conflict; + + settings->start_mode = START_PID2; + } else { + if (settings->start_mode == START_PID2) + goto conflict; + + if (settings->start_mode < 0) + settings->start_mode = START_PID1; + } + + return 0; + +conflict: + log_syntax(unit, LOG_ERR, filename, line, r, "Conflicting Boot= or ProcessTwo= setting found. Ignoring."); + return 0; +} diff --git a/src/systemd-nspawn/nspawn-settings.h b/src/systemd-nspawn/nspawn-settings.h new file mode 100644 index 0000000000..a017405cd9 --- /dev/null +++ b/src/systemd-nspawn/nspawn-settings.h @@ -0,0 +1,101 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include + +#include "macro.h" +#include "nspawn-expose-ports.h" +#include "nspawn-mount.h" + +typedef enum StartMode { + START_PID1, /* Run parameters as command line as process 1 */ + START_PID2, /* Use stub init process as PID 1, run parameters as command line as process 2 */ + START_BOOT, /* Search for init system, pass arguments as parameters */ + _START_MODE_MAX, + _START_MODE_INVALID = -1 +} StartMode; + +typedef enum SettingsMask { + SETTING_START_MODE = 1 << 0, + SETTING_ENVIRONMENT = 1 << 1, + SETTING_USER = 1 << 2, + SETTING_CAPABILITY = 1 << 3, + SETTING_KILL_SIGNAL = 1 << 4, + SETTING_PERSONALITY = 1 << 5, + SETTING_MACHINE_ID = 1 << 6, + SETTING_NETWORK = 1 << 7, + SETTING_EXPOSE_PORTS = 1 << 8, + SETTING_READ_ONLY = 1 << 9, + SETTING_VOLATILE_MODE = 1 << 10, + SETTING_CUSTOM_MOUNTS = 1 << 11, + SETTING_WORKING_DIRECTORY = 1 << 12, + _SETTINGS_MASK_ALL = (1 << 13) -1 +} SettingsMask; + +typedef struct Settings { + /* [Run] */ + StartMode start_mode; + char **parameters; + char **environment; + char *user; + uint64_t capability; + uint64_t drop_capability; + int kill_signal; + unsigned long personality; + sd_id128_t machine_id; + char *working_directory; + + /* [Image] */ + int read_only; + VolatileMode volatile_mode; + CustomMount *custom_mounts; + unsigned n_custom_mounts; + + /* [Network] */ + int private_network; + int network_veth; + char *network_bridge; + char **network_interfaces; + char **network_macvlan; + char **network_ipvlan; + char **network_veth_extra; + ExposePort *expose_ports; +} Settings; + +int settings_load(FILE *f, const char *path, Settings **ret); +Settings* settings_free(Settings *s); + +bool settings_network_veth(Settings *s); +bool settings_private_network(Settings *s); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Settings*, settings_free); + +const struct ConfigPerfItem* nspawn_gperf_lookup(const char *key, unsigned length); + +int config_parse_capability(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_id128(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_expose_port(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_volatile_mode(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_bind(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_tmpfs(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_veth_extra(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_boot(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_pid2(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/systemd-nspawn/nspawn-setuid.c b/src/systemd-nspawn/nspawn-setuid.c new file mode 100644 index 0000000000..ee15a47e93 --- /dev/null +++ b/src/systemd-nspawn/nspawn-setuid.c @@ -0,0 +1,273 @@ +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "mkdir.h" +#include "nspawn-setuid.h" +#include "process-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "user-util.h" +#include "util.h" + +static int spawn_getent(const char *database, const char *key, pid_t *rpid) { + int pipe_fds[2]; + pid_t pid; + + assert(database); + assert(key); + assert(rpid); + + if (pipe2(pipe_fds, O_CLOEXEC) < 0) + return log_error_errno(errno, "Failed to allocate pipe: %m"); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork getent child: %m"); + else if (pid == 0) { + int nullfd; + char *empty_env = NULL; + + if (dup3(pipe_fds[1], STDOUT_FILENO, 0) < 0) + _exit(EXIT_FAILURE); + + if (pipe_fds[0] > 2) + safe_close(pipe_fds[0]); + if (pipe_fds[1] > 2) + safe_close(pipe_fds[1]); + + nullfd = open("/dev/null", O_RDWR); + if (nullfd < 0) + _exit(EXIT_FAILURE); + + if (dup3(nullfd, STDIN_FILENO, 0) < 0) + _exit(EXIT_FAILURE); + + if (dup3(nullfd, STDERR_FILENO, 0) < 0) + _exit(EXIT_FAILURE); + + if (nullfd > 2) + safe_close(nullfd); + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + close_all_fds(NULL, 0); + + execle("/usr/bin/getent", "getent", database, key, NULL, &empty_env); + execle("/bin/getent", "getent", database, key, NULL, &empty_env); + _exit(EXIT_FAILURE); + } + + pipe_fds[1] = safe_close(pipe_fds[1]); + + *rpid = pid; + + return pipe_fds[0]; +} + +int change_uid_gid(const char *user, char **_home) { + char line[LINE_MAX], *x, *u, *g, *h; + const char *word, *state; + _cleanup_free_ uid_t *uids = NULL; + _cleanup_free_ char *home = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_close_ int fd = -1; + unsigned n_uids = 0; + size_t sz = 0, l; + uid_t uid; + gid_t gid; + pid_t pid; + int r; + + assert(_home); + + if (!user || streq(user, "root") || streq(user, "0")) { + /* Reset everything fully to 0, just in case */ + + r = reset_uid_gid(); + if (r < 0) + return log_error_errno(r, "Failed to become root: %m"); + + *_home = NULL; + return 0; + } + + /* First, get user credentials */ + fd = spawn_getent("passwd", user, &pid); + if (fd < 0) + return fd; + + f = fdopen(fd, "r"); + if (!f) + return log_oom(); + 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; + } + + truncate_nl(line); + + wait_for_terminate_and_warn("getent passwd", pid, true); + + x = strchr(line, ':'); + if (!x) { + log_error("/etc/passwd entry has invalid user field."); + return -EIO; + } + + u = strchr(x+1, ':'); + if (!u) { + log_error("/etc/passwd entry has invalid password field."); + return -EIO; + } + + u++; + g = strchr(u, ':'); + if (!g) { + log_error("/etc/passwd entry has invalid UID field."); + return -EIO; + } + + *g = 0; + g++; + x = strchr(g, ':'); + if (!x) { + log_error("/etc/passwd entry has invalid GID field."); + return -EIO; + } + + *x = 0; + h = strchr(x+1, ':'); + if (!h) { + log_error("/etc/passwd entry has invalid GECOS field."); + return -EIO; + } + + h++; + x = strchr(h, ':'); + if (!x) { + log_error("/etc/passwd entry has invalid home directory field."); + return -EIO; + } + + *x = 0; + + r = parse_uid(u, &uid); + if (r < 0) { + log_error("Failed to parse UID of user."); + return -EIO; + } + + r = parse_gid(g, &gid); + if (r < 0) { + log_error("Failed to parse GID of user."); + return -EIO; + } + + home = strdup(h); + if (!home) + return log_oom(); + + /* Second, get group memberships */ + fd = spawn_getent("initgroups", user, &pid); + if (fd < 0) + return fd; + + fclose(f); + f = fdopen(fd, "r"); + if (!f) + return log_oom(); + 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; + } + + truncate_nl(line); + + wait_for_terminate_and_warn("getent initgroups", pid, true); + + /* Skip over the username and subsequent separator whitespace */ + x = line; + x += strcspn(x, WHITESPACE); + x += strspn(x, WHITESPACE); + + FOREACH_WORD(word, l, x, state) { + char c[l+1]; + + memcpy(c, word, l); + c[l] = 0; + + if (!GREEDY_REALLOC(uids, sz, n_uids+1)) + return log_oom(); + + r = parse_uid(c, &uids[n_uids++]); + if (r < 0) { + log_error("Failed to parse group data from getent."); + return -EIO; + } + } + + r = mkdir_parents(home, 0775); + if (r < 0) + return log_error_errno(r, "Failed to make home root directory: %m"); + + r = mkdir_safe(home, 0755, uid, gid); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Failed to make home directory: %m"); + + (void) fchown(STDIN_FILENO, uid, gid); + (void) fchown(STDOUT_FILENO, uid, gid); + (void) fchown(STDERR_FILENO, uid, gid); + + if (setgroups(n_uids, uids) < 0) + return log_error_errno(errno, "Failed to set auxiliary groups: %m"); + + if (setresgid(gid, gid, gid) < 0) + return log_error_errno(errno, "setresgid() failed: %m"); + + if (setresuid(uid, uid, uid) < 0) + return log_error_errno(errno, "setresuid() failed: %m"); + + if (_home) { + *_home = home; + home = NULL; + } + + return 0; +} diff --git a/src/systemd-nspawn/nspawn-setuid.h b/src/systemd-nspawn/nspawn-setuid.h new file mode 100644 index 0000000000..b4968ba1fc --- /dev/null +++ b/src/systemd-nspawn/nspawn-setuid.h @@ -0,0 +1,22 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2015 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 . +***/ + +int change_uid_gid(const char *user, char **ret); diff --git a/src/systemd-nspawn/nspawn-stub-pid1.c b/src/systemd-nspawn/nspawn-stub-pid1.c new file mode 100644 index 0000000000..2de87e3c63 --- /dev/null +++ b/src/systemd-nspawn/nspawn-stub-pid1.c @@ -0,0 +1,170 @@ +/*** + 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 . +***/ + +#include +#include +#include + +#include "fd-util.h" +#include "log.h" +#include "nspawn-stub-pid1.h" +#include "process-util.h" +#include "signal-util.h" +#include "time-util.h" +#include "def.h" + +int stub_pid1(void) { + enum { + STATE_RUNNING, + STATE_REBOOT, + STATE_POWEROFF, + } state = STATE_RUNNING; + + sigset_t fullmask, oldmask, waitmask; + usec_t quit_usec = USEC_INFINITY; + pid_t pid; + int r; + + /* Implements a stub PID 1, that reaps all processes and processes a couple of standard signals. This is useful + * for allowing arbitrary processes run in a container, and still have all zombies reaped. */ + + assert_se(sigfillset(&fullmask) >= 0); + assert_se(sigprocmask(SIG_BLOCK, &fullmask, &oldmask) >= 0); + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork child pid: %m"); + + if (pid == 0) { + /* Return in the child */ + assert_se(sigprocmask(SIG_SETMASK, &oldmask, NULL) >= 0); + setsid(); + return 0; + } + + reset_all_signal_handlers(); + + log_close(); + close_all_fds(NULL, 0); + log_open(); + + rename_process("STUBINIT"); + + assert_se(sigemptyset(&waitmask) >= 0); + assert_se(sigset_add_many(&waitmask, + SIGCHLD, /* posix: process died */ + SIGINT, /* sysv: ctrl-alt-del */ + SIGRTMIN+3, /* systemd: halt */ + SIGRTMIN+4, /* systemd: poweroff */ + SIGRTMIN+5, /* systemd: reboot */ + SIGRTMIN+6, /* systemd: kexec */ + SIGRTMIN+13, /* systemd: halt */ + SIGRTMIN+14, /* systemd: poweroff */ + SIGRTMIN+15, /* systemd: reboot */ + SIGRTMIN+16, /* systemd: kexec */ + -1) >= 0); + + /* Note that we ignore SIGTERM (sysv's reexec), SIGHUP (reload), and all other signals here, since we don't + * support reexec/reloading in this stub process. */ + + for (;;) { + siginfo_t si; + usec_t current_usec; + + si.si_pid = 0; + r = waitid(P_ALL, 0, &si, WEXITED|WNOHANG); + if (r < 0) { + r = log_error_errno(errno, "Failed to reap children: %m"); + goto finish; + } + + current_usec = now(CLOCK_MONOTONIC); + + if (si.si_pid == pid || current_usec >= quit_usec) { + + /* The child we started ourselves died or we reached a timeout. */ + + if (state == STATE_REBOOT) { /* dispatch a queued reboot */ + (void) reboot(RB_AUTOBOOT); + r = log_error_errno(errno, "Failed to reboot: %m"); + goto finish; + + } else if (state == STATE_POWEROFF) + (void) reboot(RB_POWER_OFF); /* if this fails, fall back to normal exit. */ + + if (si.si_pid == pid && si.si_code == CLD_EXITED) + r = si.si_status; /* pass on exit code */ + else + r = 255; /* signal, coredump, timeout, … */ + + goto finish; + } + if (si.si_pid != 0) + /* We reaped something. Retry until there's nothing more to reap. */ + continue; + + if (quit_usec == USEC_INFINITY) + r = sigwaitinfo(&waitmask, &si); + else { + struct timespec ts; + r = sigtimedwait(&waitmask, &si, timespec_store(&ts, quit_usec - current_usec)); + } + if (r < 0) { + if (errno == EINTR) /* strace -p attach can result in EINTR, let's handle this nicely. */ + continue; + if (errno == EAGAIN) /* timeout reached */ + continue; + + r = log_error_errno(errno, "Failed to wait for signal: %m"); + goto finish; + } + + if (si.si_signo == SIGCHLD) + continue; /* Let's reap this */ + + if (state != STATE_RUNNING) + continue; + + /* Would love to use a switch() statement here, but SIGRTMIN is actually a function call, not a + * constant… */ + + if (si.si_signo == SIGRTMIN+3 || + si.si_signo == SIGRTMIN+4 || + si.si_signo == SIGRTMIN+13 || + si.si_signo == SIGRTMIN+14) + + state = STATE_POWEROFF; + + else if (si.si_signo == SIGINT || + si.si_signo == SIGRTMIN+5 || + si.si_signo == SIGRTMIN+6 || + si.si_signo == SIGRTMIN+15 || + si.si_signo == SIGRTMIN+16) + + state = STATE_REBOOT; + else + assert_not_reached("Got unexpected signal"); + + /* (void) kill_and_sigcont(pid, SIGTERM); */ + quit_usec = now(CLOCK_MONOTONIC) + DEFAULT_TIMEOUT_USEC; + } + +finish: + _exit(r < 0 ? EXIT_FAILURE : r); +} diff --git a/src/systemd-nspawn/nspawn-stub-pid1.h b/src/systemd-nspawn/nspawn-stub-pid1.h new file mode 100644 index 0000000000..36c1aaf5dd --- /dev/null +++ b/src/systemd-nspawn/nspawn-stub-pid1.h @@ -0,0 +1,22 @@ +#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 . +***/ + +int stub_pid1(void); diff --git a/src/systemd-nspawn/nspawn.c b/src/systemd-nspawn/nspawn.c new file mode 100644 index 0000000000..add66be183 --- /dev/null +++ b/src/systemd-nspawn/nspawn.c @@ -0,0 +1,3700 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#ifdef HAVE_BLKID +#include +#endif +#include +#include +#include +#include +#ifdef HAVE_SECCOMP +#include +#endif +#ifdef HAVE_SELINUX +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "alloc-util.h" +#include "barrier.h" +#include "base-filesystem.h" +#include "blkid-util.h" +#include "btrfs-util.h" +#include "cap-list.h" +#include "capability-util.h" +#include "cgroup-util.h" +#include "copy.h" +#include "dev-setup.h" +#include "env-util.h" +#include "fd-util.h" +#include "fdset.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "gpt.h" +#include "hostname-util.h" +#include "log.h" +#include "loopback-setup.h" +#include "machine-image.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "mount-util.h" +#include "netlink-util.h" +#include "nspawn-cgroup.h" +#include "nspawn-expose-ports.h" +#include "nspawn-mount.h" +#include "nspawn-network.h" +#include "nspawn-register.h" +#include "nspawn-settings.h" +#include "nspawn-setuid.h" +#include "nspawn-stub-pid1.h" +#include "parse-util.h" +#include "path-util.h" +#include "process-util.h" +#include "ptyfwd.h" +#include "random-util.h" +#include "rm-rf.h" +#ifdef HAVE_SECCOMP +#include "seccomp-util.h" +#endif +#include "signal-util.h" +#include "socket-util.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "udev-util.h" +#include "umask-util.h" +#include "user-util.h" +#include "util.h" + +typedef enum ContainerStatus { + CONTAINER_TERMINATED, + CONTAINER_REBOOTED +} ContainerStatus; + +typedef enum LinkJournal { + LINK_NO, + LINK_AUTO, + LINK_HOST, + LINK_GUEST +} LinkJournal; + +static char *arg_directory = NULL; +static char *arg_template = NULL; +static char *arg_chdir = NULL; +static char *arg_user = NULL; +static sd_id128_t arg_uuid = {}; +static char *arg_machine = NULL; +static const char *arg_selinux_context = NULL; +static const char *arg_selinux_apifs_context = NULL; +static const char *arg_slice = NULL; +static bool arg_private_network = false; +static bool arg_read_only = false; +static StartMode arg_start_mode = START_PID1; +static bool arg_ephemeral = false; +static LinkJournal arg_link_journal = LINK_AUTO; +static bool arg_link_journal_try = false; +static uint64_t arg_retain = + (1ULL << CAP_CHOWN) | + (1ULL << CAP_DAC_OVERRIDE) | + (1ULL << CAP_DAC_READ_SEARCH) | + (1ULL << CAP_FOWNER) | + (1ULL << CAP_FSETID) | + (1ULL << CAP_IPC_OWNER) | + (1ULL << CAP_KILL) | + (1ULL << CAP_LEASE) | + (1ULL << CAP_LINUX_IMMUTABLE) | + (1ULL << CAP_NET_BIND_SERVICE) | + (1ULL << CAP_NET_BROADCAST) | + (1ULL << CAP_NET_RAW) | + (1ULL << CAP_SETGID) | + (1ULL << CAP_SETFCAP) | + (1ULL << CAP_SETPCAP) | + (1ULL << CAP_SETUID) | + (1ULL << CAP_SYS_ADMIN) | + (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); +static CustomMount *arg_custom_mounts = NULL; +static unsigned arg_n_custom_mounts = 0; +static char **arg_setenv = NULL; +static bool arg_quiet = false; +static bool arg_share_system = false; +static bool arg_register = true; +static bool arg_keep_unit = false; +static char **arg_network_interfaces = NULL; +static char **arg_network_macvlan = NULL; +static char **arg_network_ipvlan = NULL; +static bool arg_network_veth = false; +static char **arg_network_veth_extra = NULL; +static char *arg_network_bridge = NULL; +static unsigned long arg_personality = PERSONALITY_INVALID; +static char *arg_image = NULL; +static VolatileMode arg_volatile_mode = VOLATILE_NO; +static ExposePort *arg_expose_ports = NULL; +static char **arg_property = NULL; +static uid_t arg_uid_shift = UID_INVALID, arg_uid_range = 0x10000U; +static bool arg_userns = false; +static int arg_kill_signal = 0; +static bool arg_unified_cgroup_hierarchy = false; +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 void help(void) { + printf("%s [OPTIONS...] [PATH] [ARGUMENTS...]\n\n" + "Spawn a minimal namespace container for debugging, testing and building.\n\n" + " -h --help Show this help\n" + " --version Print version string\n" + " -q --quiet Do not show status information\n" + " -D --directory=PATH Root directory for the container\n" + " --template=PATH Initialize root directory from template directory,\n" + " if missing\n" + " -x --ephemeral Run container with snapshot of root directory, and\n" + " remove it after exit\n" + " -i --image=PATH File system device or disk image for the container\n" + " -a --as-pid2 Maintain a stub init as PID1, invoke binary as PID2\n" + " -b --boot Boot up full system (i.e. invoke init)\n" + " --chdir=PATH Set working directory in the container\n" + " -u --user=USER Run the command under specified user or uid\n" + " -M --machine=NAME Set the machine name for the container\n" + " --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" + " --private-users[=UIDBASE[:NUIDS]]\n" + " Run within user namespace\n" + " --private-network Disable network in container\n" + " --network-interface=INTERFACE\n" + " Assign an existing network interface to the\n" + " container\n" + " --network-macvlan=INTERFACE\n" + " Create a macvlan network interface based on an\n" + " existing network interface to the container\n" + " --network-ipvlan=INTERFACE\n" + " Create a ipvlan network interface based on an\n" + " existing network interface to the container\n" + " -n --network-veth Add a virtual Ethernet connection between host\n" + " and container\n" + " --network-veth-extra=HOSTIF[:CONTAINERIF]\n" + " 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" + " -p --port=[PROTOCOL:]HOSTPORT[:CONTAINERPORT]\n" + " Expose a container IP port on the host\n" + " -Z --selinux-context=SECLABEL\n" + " Set the SELinux security context to be used by\n" + " processes in the container\n" + " -L --selinux-apifs-context=SECLABEL\n" + " Set the SELinux security context to be used by\n" + " API/tmpfs file systems in the container\n" + " --capability=CAP In addition to the default, retain specified\n" + " capability\n" + " --drop-capability=CAP Drop the specified capability from the default set\n" + " --kill-signal=SIGNAL Select signal to use for shutting down PID 1\n" + " --link-journal=MODE Link up guest journal, one of no, auto, guest, \n" + " host, try-guest, try-host\n" + " -j Equivalent to --link-journal=try-guest\n" + " --read-only Mount the root directory read-only\n" + " --bind=PATH[:PATH[:OPTIONS]]\n" + " Bind mount a file or directory from the host into\n" + " the container\n" + " --bind-ro=PATH[:PATH[:OPTIONS]\n" + " Similar, but creates a read-only bind mount\n" + " --tmpfs=PATH:[OPTIONS] Mount an empty tmpfs to the specified directory\n" + " --overlay=PATH[:PATH...]:PATH\n" + " Create an overlay mount from the host to \n" + " the container\n" + " --overlay-ro=PATH[:PATH...]:PATH\n" + " Similar, but creates a read-only overlay mount\n" + " --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" + , program_invocation_short_name); +} + + +static int custom_mounts_prepare(void) { + unsigned i; + int r; + + /* Ensure the mounts are applied prefix first. */ + qsort_safe(arg_custom_mounts, arg_n_custom_mounts, sizeof(CustomMount), custom_mount_compare); + + /* Allocate working directories for the overlay file systems that need it */ + for (i = 0; i < arg_n_custom_mounts; i++) { + CustomMount *m = &arg_custom_mounts[i]; + + if (arg_userns && arg_uid_shift == UID_INVALID && path_equal(m->destination, "/")) { + log_error("--private-users with automatic UID shift may not be combined with custom root mounts."); + return -EINVAL; + } + + if (m->type != CUSTOM_MOUNT_OVERLAY) + continue; + + if (m->work_dir) + continue; + + if (m->read_only) + continue; + + r = tempfn_random(m->source, NULL, &m->work_dir); + if (r < 0) + return log_error_errno(r, "Failed to generate work directory from %s: %m", m->source); + } + + return 0; +} + +static int detect_unified_cgroup_hierarchy(void) { + const char *e; + int r; + + /* Allow the user to control whether the unified hierarchy is used */ + e = getenv("UNIFIED_CGROUP_HIERARCHY"); + if (e) { + r = parse_boolean(e); + if (r < 0) + return log_error_errno(r, "Failed to parse $UNIFIED_CGROUP_HIERARCHY."); + + arg_unified_cgroup_hierarchy = r; + return 0; + } + + /* Otherwise inherit the default from the host system */ + r = cg_unified(); + if (r < 0) + return log_error_errno(r, "Failed to determine whether the unified cgroups hierarchy is used: %m"); + + arg_unified_cgroup_hierarchy = r; + return 0; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_PRIVATE_NETWORK, + ARG_UUID, + ARG_READ_ONLY, + ARG_CAPABILITY, + ARG_DROP_CAPABILITY, + ARG_LINK_JOURNAL, + ARG_BIND, + ARG_BIND_RO, + ARG_TMPFS, + ARG_OVERLAY, + ARG_OVERLAY_RO, + ARG_SETENV, + ARG_SHARE_SYSTEM, + ARG_REGISTER, + ARG_KEEP_UNIT, + ARG_NETWORK_INTERFACE, + ARG_NETWORK_MACVLAN, + ARG_NETWORK_IPVLAN, + ARG_NETWORK_BRIDGE, + ARG_NETWORK_VETH_EXTRA, + ARG_PERSONALITY, + ARG_VOLATILE, + ARG_TEMPLATE, + ARG_PROPERTY, + ARG_PRIVATE_USERS, + ARG_KILL_SIGNAL, + ARG_SETTINGS, + ARG_CHDIR, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "directory", required_argument, NULL, 'D' }, + { "template", required_argument, NULL, ARG_TEMPLATE }, + { "ephemeral", no_argument, NULL, 'x' }, + { "user", required_argument, NULL, 'u' }, + { "private-network", no_argument, NULL, ARG_PRIVATE_NETWORK }, + { "as-pid2", no_argument, NULL, 'a' }, + { "boot", no_argument, NULL, 'b' }, + { "uuid", required_argument, NULL, ARG_UUID }, + { "read-only", no_argument, NULL, ARG_READ_ONLY }, + { "capability", required_argument, NULL, ARG_CAPABILITY }, + { "drop-capability", required_argument, NULL, ARG_DROP_CAPABILITY }, + { "link-journal", required_argument, NULL, ARG_LINK_JOURNAL }, + { "bind", required_argument, NULL, ARG_BIND }, + { "bind-ro", required_argument, NULL, ARG_BIND_RO }, + { "tmpfs", required_argument, NULL, ARG_TMPFS }, + { "overlay", required_argument, NULL, ARG_OVERLAY }, + { "overlay-ro", required_argument, NULL, ARG_OVERLAY_RO }, + { "machine", required_argument, NULL, 'M' }, + { "slice", required_argument, NULL, 'S' }, + { "setenv", required_argument, NULL, ARG_SETENV }, + { "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 }, + { "register", required_argument, NULL, ARG_REGISTER }, + { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, + { "network-interface", required_argument, NULL, ARG_NETWORK_INTERFACE }, + { "network-macvlan", required_argument, NULL, ARG_NETWORK_MACVLAN }, + { "network-ipvlan", required_argument, NULL, ARG_NETWORK_IPVLAN }, + { "network-veth", no_argument, NULL, 'n' }, + { "network-veth-extra", required_argument, NULL, ARG_NETWORK_VETH_EXTRA}, + { "network-bridge", required_argument, NULL, ARG_NETWORK_BRIDGE }, + { "personality", required_argument, NULL, ARG_PERSONALITY }, + { "image", required_argument, NULL, 'i' }, + { "volatile", optional_argument, NULL, ARG_VOLATILE }, + { "port", required_argument, NULL, 'p' }, + { "property", required_argument, NULL, ARG_PROPERTY }, + { "private-users", optional_argument, NULL, ARG_PRIVATE_USERS }, + { "kill-signal", required_argument, NULL, ARG_KILL_SIGNAL }, + { "settings", required_argument, NULL, ARG_SETTINGS }, + { "chdir", required_argument, NULL, ARG_CHDIR }, + {} + }; + + int c, r; + const char *p, *e; + uint64_t plus = 0, minus = 0; + bool mask_all_settings = false, mask_no_settings = false; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+hD:u:abL:M:jS:Z:qi:xp:n", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case 'D': + r = parse_path_argument_and_warn(optarg, false, &arg_directory); + if (r < 0) + return r; + break; + + case ARG_TEMPLATE: + r = parse_path_argument_and_warn(optarg, false, &arg_template); + if (r < 0) + return r; + break; + + case 'i': + r = parse_path_argument_and_warn(optarg, false, &arg_image); + if (r < 0) + return r; + break; + + case 'x': + arg_ephemeral = true; + break; + + case 'u': + r = free_and_strdup(&arg_user, optarg); + if (r < 0) + return log_oom(); + + arg_settings_mask |= SETTING_USER; + break; + + case ARG_NETWORK_BRIDGE: + r = free_and_strdup(&arg_network_bridge, optarg); + if (r < 0) + return log_oom(); + + /* fall through */ + + case 'n': + arg_network_veth = true; + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; + + case ARG_NETWORK_VETH_EXTRA: + r = veth_extra_parse(&arg_network_veth_extra, optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --network-veth-extra= parameter: %s", optarg); + + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; + + case ARG_NETWORK_INTERFACE: + if (strv_extend(&arg_network_interfaces, optarg) < 0) + return log_oom(); + + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; + + case ARG_NETWORK_MACVLAN: + if (strv_extend(&arg_network_macvlan, optarg) < 0) + return log_oom(); + + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; + + case ARG_NETWORK_IPVLAN: + if (strv_extend(&arg_network_ipvlan, optarg) < 0) + return log_oom(); + + /* fall through */ + + case ARG_PRIVATE_NETWORK: + arg_private_network = true; + arg_settings_mask |= SETTING_NETWORK; + break; + + case 'b': + if (arg_start_mode == START_PID2) { + log_error("--boot and --as-pid2 may not be combined."); + return -EINVAL; + } + + arg_start_mode = START_BOOT; + arg_settings_mask |= SETTING_START_MODE; + break; + + case 'a': + if (arg_start_mode == START_BOOT) { + log_error("--boot and --as-pid2 may not be combined."); + return -EINVAL; + } + + arg_start_mode = START_PID2; + arg_settings_mask |= SETTING_START_MODE; + break; + + case ARG_UUID: + r = sd_id128_from_string(optarg, &arg_uuid); + if (r < 0) { + log_error("Invalid UUID: %s", optarg); + return r; + } + + arg_settings_mask |= SETTING_MACHINE_ID; + break; + + case 'S': + arg_slice = optarg; + break; + + case 'M': + if (isempty(optarg)) + arg_machine = mfree(arg_machine); + else { + if (!machine_name_is_valid(optarg)) { + log_error("Invalid machine name: %s", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_machine, optarg); + if (r < 0) + return log_oom(); + + break; + } + + case 'Z': + arg_selinux_context = optarg; + break; + + case 'L': + arg_selinux_apifs_context = optarg; + break; + + case ARG_READ_ONLY: + arg_read_only = true; + arg_settings_mask |= SETTING_READ_ONLY; + break; + + case ARG_CAPABILITY: + case ARG_DROP_CAPABILITY: { + p = optarg; + for(;;) { + _cleanup_free_ char *t = NULL; + + r = extract_first_word(&p, &t, ",", 0); + if (r < 0) + return log_error_errno(r, "Failed to parse capability %s.", t); + + if (r == 0) + break; + + if (streq(t, "all")) { + if (c == ARG_CAPABILITY) + plus = (uint64_t) -1; + else + minus = (uint64_t) -1; + } else { + int cap; + + cap = capability_from_name(t); + if (cap < 0) { + log_error("Failed to parse capability %s.", t); + return -EINVAL; + } + + if (c == ARG_CAPABILITY) + plus |= 1ULL << (uint64_t) cap; + else + minus |= 1ULL << (uint64_t) cap; + } + } + + arg_settings_mask |= SETTING_CAPABILITY; + break; + } + + case 'j': + arg_link_journal = LINK_GUEST; + arg_link_journal_try = true; + break; + + case ARG_LINK_JOURNAL: + if (streq(optarg, "auto")) { + arg_link_journal = LINK_AUTO; + arg_link_journal_try = false; + } else if (streq(optarg, "no")) { + arg_link_journal = LINK_NO; + arg_link_journal_try = false; + } else if (streq(optarg, "guest")) { + arg_link_journal = LINK_GUEST; + arg_link_journal_try = false; + } else if (streq(optarg, "host")) { + arg_link_journal = LINK_HOST; + arg_link_journal_try = false; + } else if (streq(optarg, "try-guest")) { + arg_link_journal = LINK_GUEST; + arg_link_journal_try = true; + } else if (streq(optarg, "try-host")) { + arg_link_journal = LINK_HOST; + arg_link_journal_try = true; + } else { + log_error("Failed to parse link journal mode %s", optarg); + return -EINVAL; + } + + break; + + case ARG_BIND: + case ARG_BIND_RO: + r = bind_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg, c == ARG_BIND_RO); + if (r < 0) + return log_error_errno(r, "Failed to parse --bind(-ro)= argument %s: %m", optarg); + + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + break; + + case ARG_TMPFS: + r = tmpfs_mount_parse(&arg_custom_mounts, &arg_n_custom_mounts, optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --tmpfs= argument %s: %m", optarg); + + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + break; + + case ARG_OVERLAY: + case ARG_OVERLAY_RO: { + _cleanup_free_ char *upper = NULL, *destination = NULL; + _cleanup_strv_free_ char **lower = NULL; + CustomMount *m; + unsigned n = 0; + char **i; + + r = strv_split_extract(&lower, optarg, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (r == -ENOMEM) + return log_oom(); + else if (r < 0) { + log_error("Invalid overlay specification: %s", optarg); + return r; + } + + STRV_FOREACH(i, lower) { + if (!path_is_absolute(*i)) { + log_error("Overlay path %s is not absolute.", *i); + return -EINVAL; + } + + n++; + } + + if (n < 2) { + log_error("--overlay= needs at least two colon-separated directories specified."); + return -EINVAL; + } + + if (n == 2) { + /* If two parameters are specified, + * the first one is the lower, the + * second one the upper directory. And + * we'll also define the destination + * mount point the same as the upper. */ + upper = lower[1]; + lower[1] = NULL; + + destination = strdup(upper); + if (!destination) + return log_oom(); + + } else { + upper = lower[n - 2]; + destination = lower[n - 1]; + lower[n - 2] = NULL; + } + + m = custom_mount_add(&arg_custom_mounts, &arg_n_custom_mounts, CUSTOM_MOUNT_OVERLAY); + if (!m) + return log_oom(); + + m->destination = destination; + m->source = upper; + m->lower = lower; + m->read_only = c == ARG_OVERLAY_RO; + + upper = destination = NULL; + lower = NULL; + + arg_settings_mask |= SETTING_CUSTOM_MOUNTS; + break; + } + + case ARG_SETENV: { + char **n; + + if (!env_assignment_is_valid(optarg)) { + log_error("Environment variable assignment '%s' is not valid.", optarg); + return -EINVAL; + } + + n = strv_env_set(arg_setenv, optarg); + if (!n) + return log_oom(); + + strv_free(arg_setenv); + arg_setenv = n; + + arg_settings_mask |= SETTING_ENVIRONMENT; + break; + } + + case 'q': + arg_quiet = true; + break; + + case ARG_SHARE_SYSTEM: + arg_share_system = true; + break; + + case ARG_REGISTER: + r = parse_boolean(optarg); + if (r < 0) { + log_error("Failed to parse --register= argument: %s", optarg); + return r; + } + + arg_register = r; + break; + + case ARG_KEEP_UNIT: + arg_keep_unit = true; + break; + + case ARG_PERSONALITY: + + arg_personality = personality_from_string(optarg); + if (arg_personality == PERSONALITY_INVALID) { + log_error("Unknown or unsupported personality '%s'.", optarg); + return -EINVAL; + } + + arg_settings_mask |= SETTING_PERSONALITY; + break; + + case ARG_VOLATILE: + + if (!optarg) + arg_volatile_mode = VOLATILE_YES; + else { + VolatileMode m; + + m = volatile_mode_from_string(optarg); + if (m < 0) { + log_error("Failed to parse --volatile= argument: %s", optarg); + return -EINVAL; + } else + arg_volatile_mode = m; + } + + arg_settings_mask |= SETTING_VOLATILE_MODE; + break; + + case 'p': + r = expose_port_parse(&arg_expose_ports, optarg); + if (r == -EEXIST) + return log_error_errno(r, "Duplicate port specification: %s", optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse host port %s: %m", optarg); + + arg_settings_mask |= SETTING_EXPOSE_PORTS; + break; + + case ARG_PROPERTY: + if (strv_extend(&arg_property, optarg) < 0) + return log_oom(); + + break; + + case ARG_PRIVATE_USERS: + if (optarg) { + _cleanup_free_ char *buffer = NULL; + const char *range, *shift; + + range = strchr(optarg, ':'); + if (range) { + buffer = strndup(optarg, range - optarg); + if (!buffer) + return log_oom(); + shift = buffer; + + range++; + if (safe_atou32(range, &arg_uid_range) < 0 || arg_uid_range <= 0) { + log_error("Failed to parse UID range: %s", range); + return -EINVAL; + } + } else + shift = optarg; + + if (parse_uid(shift, &arg_uid_shift) < 0) { + log_error("Failed to parse UID: %s", optarg); + return -EINVAL; + } + } + + arg_userns = true; + break; + + case ARG_KILL_SIGNAL: + arg_kill_signal = signal_from_string_try_harder(optarg); + if (arg_kill_signal < 0) { + log_error("Cannot parse signal: %s", optarg); + return -EINVAL; + } + + arg_settings_mask |= SETTING_KILL_SIGNAL; + break; + + case ARG_SETTINGS: + + /* no → do not read files + * yes → read files, do not override cmdline, trust only subset + * override → read files, override cmdline, trust only subset + * trusted → read files, do not override cmdline, trust all + */ + + r = parse_boolean(optarg); + if (r < 0) { + if (streq(optarg, "trusted")) { + mask_all_settings = false; + mask_no_settings = false; + arg_settings_trusted = true; + + } else if (streq(optarg, "override")) { + mask_all_settings = false; + mask_no_settings = true; + arg_settings_trusted = -1; + } else + return log_error_errno(r, "Failed to parse --settings= argument: %s", optarg); + } else if (r > 0) { + /* yes */ + mask_all_settings = false; + mask_no_settings = false; + arg_settings_trusted = -1; + } else { + /* no */ + mask_all_settings = true; + mask_no_settings = false; + arg_settings_trusted = false; + } + + break; + + case ARG_CHDIR: + if (!path_is_absolute(optarg)) { + log_error("Working directory %s is not an absolute path.", optarg); + return -EINVAL; + } + + r = free_and_strdup(&arg_chdir, optarg); + if (r < 0) + return log_oom(); + + arg_settings_mask |= SETTING_WORKING_DIRECTORY; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (arg_share_system) + arg_register = false; + + if (arg_start_mode != START_PID1 && arg_share_system) { + log_error("--boot and --share-system may not be combined."); + return -EINVAL; + } + + if (arg_keep_unit && cg_pid_get_owner_uid(0, NULL) >= 0) { + log_error("--keep-unit may not be used when invoked from a user session."); + return -EINVAL; + } + + if (arg_directory && arg_image) { + log_error("--directory= and --image= may not be combined."); + return -EINVAL; + } + + if (arg_template && arg_image) { + log_error("--template= and --image= may not be combined."); + return -EINVAL; + } + + if (arg_template && !(arg_directory || arg_machine)) { + log_error("--template= needs --directory= or --machine=."); + return -EINVAL; + } + + if (arg_ephemeral && arg_template) { + log_error("--ephemeral and --template= may not be combined."); + return -EINVAL; + } + + if (arg_ephemeral && arg_image) { + log_error("--ephemeral and --image= may not be combined."); + return -EINVAL; + } + + if (arg_ephemeral && !IN_SET(arg_link_journal, LINK_NO, LINK_AUTO)) { + log_error("--ephemeral and --link-journal= may not be combined."); + return -EINVAL; + } + + if (arg_userns && access("/proc/self/uid_map", F_OK) < 0) + return log_error_errno(EOPNOTSUPP, "--private-users= is not supported, kernel compiled without user namespace support."); + + if (argc > optind) { + arg_parameters = strv_copy(argv + optind); + if (!arg_parameters) + return log_oom(); + + arg_settings_mask |= SETTING_START_MODE; + } + + /* Load all settings from .nspawn files */ + if (mask_no_settings) + arg_settings_mask = 0; + + /* Don't load any settings from .nspawn files */ + if (mask_all_settings) + arg_settings_mask = _SETTINGS_MASK_ALL; + + arg_retain = (arg_retain | plus | (arg_private_network ? 1ULL << CAP_NET_ADMIN : 0)) & ~minus; + + r = detect_unified_cgroup_hierarchy(); + if (r < 0) + return r; + + e = getenv("SYSTEMD_NSPAWN_CONTAINER_SERVICE"); + if (e) + arg_container_service_name = e; + + return 1; +} + +static int verify_arguments(void) { + + if (arg_volatile_mode != VOLATILE_NO && arg_read_only) { + log_error("Cannot combine --read-only with --volatile. Note that --volatile already implies a read-only base hierarchy."); + return -EINVAL; + } + + if (arg_expose_ports && !arg_private_network) { + log_error("Cannot use --port= without private networking."); + return -EINVAL; + } + + if (arg_start_mode == START_BOOT && arg_kill_signal <= 0) + arg_kill_signal = SIGRTMIN+3; + + return 0; +} + +static int userns_lchown(const char *p, uid_t uid, gid_t gid) { + assert(p); + + if (!arg_userns) + return 0; + + if (uid == UID_INVALID && gid == GID_INVALID) + return 0; + + if (uid != UID_INVALID) { + uid += arg_uid_shift; + + if (uid < arg_uid_shift || uid >= arg_uid_shift + arg_uid_range) + return -EOVERFLOW; + } + + if (gid != GID_INVALID) { + gid += (gid_t) arg_uid_shift; + + if (gid < (gid_t) arg_uid_shift || gid >= (gid_t) (arg_uid_shift + arg_uid_range)) + return -EOVERFLOW; + } + + if (lchown(p, uid, gid) < 0) + return -errno; + + return 0; +} + +static int userns_mkdir(const char *root, const char *path, mode_t mode, uid_t uid, gid_t gid) { + const char *q; + + q = prefix_roota(root, path); + if (mkdir(q, mode) < 0) { + if (errno == EEXIST) + return 0; + return -errno; + } + + return userns_lchown(q, uid, gid); +} + +static int setup_timezone(const char *dest) { + _cleanup_free_ char *p = NULL, *q = NULL; + const char *where, *check, *what; + char *z, *y; + int r; + + assert(dest); + + /* Fix the timezone, if possible */ + r = readlink_malloc("/etc/localtime", &p); + if (r < 0) { + log_warning("/etc/localtime is not a symlink, not updating container timezone."); + return 0; + } + + z = path_startswith(p, "../usr/share/zoneinfo/"); + if (!z) + z = path_startswith(p, "/usr/share/zoneinfo/"); + if (!z) { + log_warning("/etc/localtime does not point into /usr/share/zoneinfo/, not updating container timezone."); + return 0; + } + + where = prefix_roota(dest, "/etc/localtime"); + r = readlink_malloc(where, &q); + if (r >= 0) { + y = path_startswith(q, "../usr/share/zoneinfo/"); + if (!y) + y = path_startswith(q, "/usr/share/zoneinfo/"); + + /* Already pointing to the right place? Then do nothing .. */ + if (y && streq(y, z)) + return 0; + } + + check = strjoina("/usr/share/zoneinfo/", z); + check = prefix_roota(dest, check); + if (laccess(check, F_OK) < 0) { + log_warning("Timezone %s does not exist in container, not updating container timezone.", z); + return 0; + } + + r = unlink(where); + if (r < 0 && errno != ENOENT) { + log_error_errno(errno, "Failed to remove existing timezone info %s in container: %m", where); + return 0; + } + + what = strjoina("../usr/share/zoneinfo/", z); + if (symlink(what, where) < 0) { + log_error_errno(errno, "Failed to correct timezone of container: %m"); + return 0; + } + + r = userns_lchown(where, 0, 0); + if (r < 0) + return log_warning_errno(r, "Failed to chown /etc/localtime: %m"); + + return 0; +} + +static int setup_resolv_conf(const char *dest) { + const char *where = NULL; + int r; + + assert(dest); + + if (arg_private_network) + return 0; + + /* Fix resolv.conf, if possible */ + where = prefix_roota(dest, "/etc/resolv.conf"); + + 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 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); + return 0; + } + + r = userns_lchown(where, 0, 0); + if (r < 0) + log_warning_errno(r, "Failed to chown /etc/resolv.conf: %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) { + const char *from, *to; + sd_id128_t rnd = {}; + char as_uuid[37]; + int r; + + if (arg_share_system) + return 0; + + /* Generate a new randomized boot ID, so that each boot-up of + * the container gets a new one */ + + from = prefix_roota(dest, "/run/proc-sys-kernel-random-boot-id"); + to = prefix_roota(dest, "/proc/sys/kernel/random/boot_id"); + + r = sd_id128_randomize(&rnd); + 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); + 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"); + + unlink(from); + return r; +} + +static int copy_devnodes(const char *dest) { + + static const char devnodes[] = + "null\0" + "zero\0" + "full\0" + "random\0" + "urandom\0" + "tty\0" + "net/tun\0"; + + const char *d; + int r = 0; + _cleanup_umask_ mode_t u; + + assert(dest); + + u = umask(0000); + + /* Create /dev/net, so that we can create /dev/net/tun in it */ + if (userns_mkdir(dest, "/dev/net", 0755, 0, 0) < 0) + return log_error_errno(r, "Failed to create /dev/net directory: %m"); + + NULSTR_FOREACH(d, devnodes) { + _cleanup_free_ char *from = NULL, *to = NULL; + struct stat st; + + from = strappend("/dev/", d); + to = prefix_root(dest, from); + + if (stat(from, &st) < 0) { + + if (errno != ENOENT) + return log_error_errno(errno, "Failed to stat %s: %m", from); + + } else if (!S_ISCHR(st.st_mode) && !S_ISBLK(st.st_mode)) { + + log_error("%s is not a char or block device, cannot copy.", from); + return -EIO; + + } else { + if (mknod(to, st.st_mode, st.st_rdev) < 0) { + if (errno != EPERM) + return log_error_errno(errno, "mknod(%s) failed: %m", to); + + /* Some systems abusively restrict mknod but + * allow bind mounts. */ + r = touch(to); + if (r < 0) + return log_error_errno(r, "touch (%s) failed: %m", to); + if (mount(from, to, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Both mknod and bind mount (%s) failed: %m", to); + } + + r = userns_lchown(to, 0, 0); + if (r < 0) + return log_error_errno(r, "chown() of device node %s failed: %m", to); + } + } + + return r; +} + +static int setup_pts(const char *dest) { + _cleanup_free_ char *options = NULL; + const char *p; + int r; + +#ifdef HAVE_SELINUX + if (arg_selinux_apifs_context) + (void) asprintf(&options, + "newinstance,ptmxmode=0666,mode=620,gid=" GID_FMT ",context=\"%s\"", + arg_uid_shift + TTY_GID, + arg_selinux_apifs_context); + else +#endif + (void) asprintf(&options, + "newinstance,ptmxmode=0666,mode=620,gid=" GID_FMT, + arg_uid_shift + TTY_GID); + + if (!options) + return log_oom(); + + /* Mount /dev/pts itself */ + p = prefix_roota(dest, "/dev/pts"); + if (mkdir(p, 0755) < 0) + return log_error_errno(errno, "Failed to create /dev/pts: %m"); + if (mount("devpts", p, "devpts", MS_NOSUID|MS_NOEXEC, options) < 0) + return log_error_errno(errno, "Failed to mount /dev/pts: %m"); + r = userns_lchown(p, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to chown /dev/pts: %m"); + + /* Create /dev/ptmx symlink */ + p = prefix_roota(dest, "/dev/ptmx"); + if (symlink("pts/ptmx", p) < 0) + return log_error_errno(errno, "Failed to create /dev/ptmx symlink: %m"); + r = userns_lchown(p, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to chown /dev/ptmx: %m"); + + /* And fix /dev/pts/ptmx ownership */ + p = prefix_roota(dest, "/dev/pts/ptmx"); + r = userns_lchown(p, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to chown /dev/pts/ptmx: %m"); + + return 0; +} + +static int setup_dev_console(const char *dest, const char *console) { + _cleanup_umask_ mode_t u; + const char *to; + int r; + + assert(dest); + assert(console); + + u = umask(0000); + + r = chmod_and_chown(console, 0600, arg_uid_shift, arg_uid_shift); + if (r < 0) + return log_error_errno(r, "Failed to correct access mode for TTY: %m"); + + /* We need to bind mount the right tty to /dev/console since + * ptys can only exist on pts file systems. To have something + * to bind mount things on we create a empty regular file. */ + + to = prefix_roota(dest, "/dev/console"); + r = touch(to); + if (r < 0) + return log_error_errno(r, "touch() for /dev/console failed: %m"); + + if (mount(console, to, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Bind mount for /dev/console failed: %m"); + + return 0; +} + +static int setup_kmsg(const char *dest, int kmsg_socket) { + const char *from, *to; + _cleanup_umask_ mode_t u; + int fd, r; + + assert(kmsg_socket >= 0); + + u = umask(0000); + + /* We create the kmsg FIFO as /run/kmsg, but immediately + * delete it after bind mounting it to /proc/kmsg. While FIFOs + * on the reading side behave very similar to /proc/kmsg, + * their writing side behaves differently from /dev/kmsg in + * that writing blocks when nothing is reading. In order to + * avoid any problems with containers deadlocking due to this + * we simply make /dev/kmsg unavailable to the container. */ + from = prefix_roota(dest, "/run/kmsg"); + to = prefix_roota(dest, "/proc/kmsg"); + + if (mkfifo(from, 0600) < 0) + return log_error_errno(errno, "mkfifo() for /run/kmsg failed: %m"); + if (mount(from, to, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Bind mount for /proc/kmsg failed: %m"); + + fd = open(from, O_RDWR|O_NDELAY|O_CLOEXEC); + if (fd < 0) + return log_error_errno(errno, "Failed to open fifo: %m"); + + /* Store away the fd in the socket, so that it stays open as + * long as we run the child */ + r = send_one_fd(kmsg_socket, fd, 0); + safe_close(fd); + + if (r < 0) + return log_error_errno(r, "Failed to send FIFO fd: %m"); + + /* And now make the FIFO unavailable as /run/kmsg... */ + (void) unlink(from); + + return 0; +} + +static int on_address_change(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { + union in_addr_union *exposed = userdata; + + assert(rtnl); + assert(m); + assert(exposed); + + expose_port_execute(rtnl, arg_expose_ports, exposed); + return 0; +} + +static int setup_hostname(void) { + + if (arg_share_system) + return 0; + + if (sethostname_idempotent(arg_machine) < 0) + return -errno; + + return 0; +} + +static int setup_journal(const char *directory) { + sd_id128_t machine_id, this_id; + _cleanup_free_ char *b = NULL, *d = NULL; + const char *etc_machine_id, *p, *q; + bool try; + char *id; + int r; + + /* Don't link journals in ephemeral mode */ + if (arg_ephemeral) + return 0; + + if (arg_link_journal == LINK_NO) + return 0; + + try = arg_link_journal_try || arg_link_journal == LINK_AUTO; + + etc_machine_id = prefix_roota(directory, "/etc/machine-id"); + + r = read_one_line_file(etc_machine_id, &b); + if (r == -ENOENT && try) + return 0; + else if (r < 0) + return log_error_errno(r, "Failed to read machine ID from %s: %m", etc_machine_id); + + id = strstrip(b); + if (isempty(id) && try) + return 0; + + /* Verify validity */ + r = sd_id128_from_string(id, &machine_id); + if (r < 0) + return log_error_errno(r, "Failed to parse machine ID from %s: %m", etc_machine_id); + + r = sd_id128_get_machine(&this_id); + if (r < 0) + return log_error_errno(r, "Failed to retrieve machine ID: %m"); + + if (sd_id128_equal(machine_id, this_id)) { + log_full(try ? LOG_WARNING : LOG_ERR, + "Host and machine ids are equal (%s): refusing to link journals", id); + if (try) + return 0; + return -EEXIST; + } + + r = userns_mkdir(directory, "/var", 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create /var: %m"); + + r = userns_mkdir(directory, "/var/log", 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create /var/log: %m"); + + r = userns_mkdir(directory, "/var/log/journal", 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create /var/log/journal: %m"); + + p = strjoina("/var/log/journal/", id); + q = prefix_roota(directory, p); + + if (path_is_mount_point(p, 0) > 0) { + if (try) + return 0; + + log_error("%s: already a mount point, refusing to use for journal", p); + return -EEXIST; + } + + if (path_is_mount_point(q, 0) > 0) { + if (try) + return 0; + + log_error("%s: already a mount point, refusing to use for journal", q); + return -EEXIST; + } + + r = readlink_and_make_absolute(p, &d); + if (r >= 0) { + if ((arg_link_journal == LINK_GUEST || + arg_link_journal == LINK_AUTO) && + path_equal(d, q)) { + + r = userns_mkdir(directory, p, 0755, 0, 0); + if (r < 0) + log_warning_errno(r, "Failed to create directory %s: %m", q); + return 0; + } + + if (unlink(p) < 0) + return log_error_errno(errno, "Failed to remove symlink %s: %m", p); + } else if (r == -EINVAL) { + + if (arg_link_journal == LINK_GUEST && + rmdir(p) < 0) { + + if (errno == ENOTDIR) { + log_error("%s already exists and is neither a symlink nor a directory", p); + return r; + } else + return log_error_errno(errno, "Failed to remove %s: %m", p); + } + } else if (r != -ENOENT) + return log_error_errno(r, "readlink(%s) failed: %m", p); + + if (arg_link_journal == LINK_GUEST) { + + if (symlink(q, p) < 0) { + if (try) { + log_debug_errno(errno, "Failed to symlink %s to %s, skipping journal setup: %m", q, p); + return 0; + } else + return log_error_errno(errno, "Failed to symlink %s to %s: %m", q, p); + } + + r = userns_mkdir(directory, p, 0755, 0, 0); + if (r < 0) + log_warning_errno(r, "Failed to create directory %s: %m", q); + return 0; + } + + if (arg_link_journal == LINK_HOST) { + /* don't create parents here -- if the host doesn't have + * permanent journal set up, don't force it here */ + + if (mkdir(p, 0755) < 0 && errno != EEXIST) { + if (try) { + log_debug_errno(errno, "Failed to create %s, skipping journal setup: %m", p); + return 0; + } else + return log_error_errno(errno, "Failed to create %s: %m", p); + } + + } else if (access(p, F_OK) < 0) + return 0; + + if (dir_is_empty(q) == 0) + log_warning("%s is not empty, proceeding anyway.", q); + + r = userns_mkdir(directory, p, 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create %s: %m", q); + + if (mount(p, q, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Failed to bind mount journal from host into guest: %m"); + + return 0; +} + +static int drop_capabilities(void) { + return capability_bounding_set_drop(arg_retain, false); +} + +static int reset_audit_loginuid(void) { + _cleanup_free_ char *p = NULL; + int r; + + if (arg_share_system) + return 0; + + r = read_one_line_file("/proc/self/loginuid", &p); + if (r == -ENOENT) + return 0; + if (r < 0) + return log_error_errno(r, "Failed to read /proc/self/loginuid: %m"); + + /* Already reset? */ + if (streq(p, "4294967295")) + return 0; + + r = write_string_file("/proc/self/loginuid", "4294967295", 0); + if (r < 0) { + log_error_errno(r, + "Failed to reset audit login UID. This probably means that your kernel is too\n" + "old and you have audit enabled. Note that the auditing subsystem is known to\n" + "be incompatible with containers on old kernels. Please make sure to upgrade\n" + "your kernel or to off auditing with 'audit=0' on the kernel command line before\n" + "using systemd-nspawn. Sleeping for 5s... (%m)"); + + sleep(5); + } + + return 0; +} + +static int setup_seccomp(void) { + +#ifdef HAVE_SECCOMP + static const struct { + 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) }, + }; + + scmp_filter_ctx seccomp; + unsigned i; + int r; + + seccomp = seccomp_init(SCMP_ACT_ALLOW); + if (!seccomp) + return log_oom(); + + r = seccomp_add_secondary_archs(seccomp); + if (r < 0) { + log_error_errno(r, "Failed to add secondary archs to seccomp filter: %m"); + goto finish; + } + + for (i = 0; i < ELEMENTSOF(blacklist); i++) { + if (arg_retain & (1ULL << blacklist[i].capability)) + continue; + + r = seccomp_rule_add(seccomp, 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"); + goto finish; + } + } + + + /* + Audit is broken in containers, much of the userspace audit + hookup will fail if running inside a container. We don't + care and just turn off creation of audit sockets. + + This will make socket(AF_NETLINK, *, NETLINK_AUDIT) fail + with EAFNOSUPPORT which audit userspace uses as indication + that audit is disabled in the kernel. + */ + + r = seccomp_rule_add( + seccomp, + SCMP_ACT_ERRNO(EAFNOSUPPORT), + SCMP_SYS(socket), + 2, + SCMP_A0(SCMP_CMP_EQ, AF_NETLINK), + SCMP_A2(SCMP_CMP_EQ, NETLINK_AUDIT)); + if (r < 0) { + log_error_errno(r, "Failed to add audit seccomp rule: %m"); + goto finish; + } + + r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0); + if (r < 0) { + log_error_errno(r, "Failed to unset NO_NEW_PRIVS: %m"); + goto finish; + } + + r = seccomp_load(seccomp); + if (r == -EINVAL) { + log_debug_errno(r, "Kernel is probably not configured with CONFIG_SECCOMP. Disabling seccomp audit filter: %m"); + r = 0; + goto finish; + } + if (r < 0) { + log_error_errno(r, "Failed to install seccomp audit filter: %m"); + goto finish; + } + +finish: + seccomp_release(seccomp); + return r; +#else + return 0; +#endif + +} + +static int setup_propagate(const char *root) { + const char *p, *q; + int r; + + (void) mkdir_p("/run/systemd/nspawn/", 0755); + (void) mkdir_p("/run/systemd/nspawn/propagate", 0600); + p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); + (void) mkdir_p(p, 0600); + + r = userns_mkdir(root, "/run/systemd", 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create /run/systemd: %m"); + + r = userns_mkdir(root, "/run/systemd/nspawn", 0755, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create /run/systemd/nspawn: %m"); + + r = userns_mkdir(root, "/run/systemd/nspawn/incoming", 0600, 0, 0); + if (r < 0) + return log_error_errno(r, "Failed to create /run/systemd/nspawn/incoming: %m"); + + q = prefix_roota(root, "/run/systemd/nspawn/incoming"); + if (mount(p, q, NULL, MS_BIND, NULL) < 0) + return log_error_errno(errno, "Failed to install propagation bind mount."); + + if (mount(NULL, q, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY, NULL) < 0) + return log_error_errno(errno, "Failed to make propagation mount read-only"); + + return 0; +} + +static int setup_image(char **device_path, int *loop_nr) { + struct loop_info64 info = { + .lo_flags = LO_FLAGS_AUTOCLEAR|LO_FLAGS_PARTSCAN + }; + _cleanup_close_ int fd = -1, control = -1, loop = -1; + _cleanup_free_ char* loopdev = NULL; + struct stat st; + int r, nr; + + assert(device_path); + assert(loop_nr); + assert(arg_image); + + fd = open(arg_image, O_CLOEXEC|(arg_read_only ? O_RDONLY : O_RDWR)|O_NONBLOCK|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", arg_image); + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat %s: %m", arg_image); + + if (S_ISBLK(st.st_mode)) { + char *p; + + p = strdup(arg_image); + if (!p) + return log_oom(); + + *device_path = p; + + *loop_nr = -1; + + r = fd; + fd = -1; + + return r; + } + + if (!S_ISREG(st.st_mode)) { + log_error("%s is not a regular file or block device.", arg_image); + return -EINVAL; + } + + control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + if (control < 0) + return log_error_errno(errno, "Failed to open /dev/loop-control: %m"); + + nr = ioctl(control, LOOP_CTL_GET_FREE); + if (nr < 0) + return log_error_errno(errno, "Failed to allocate loop device: %m"); + + if (asprintf(&loopdev, "/dev/loop%i", nr) < 0) + return log_oom(); + + loop = open(loopdev, O_CLOEXEC|(arg_read_only ? O_RDONLY : O_RDWR)|O_NONBLOCK|O_NOCTTY); + if (loop < 0) + return log_error_errno(errno, "Failed to open loop device %s: %m", loopdev); + + if (ioctl(loop, LOOP_SET_FD, fd) < 0) + return log_error_errno(errno, "Failed to set loopback file descriptor on %s: %m", loopdev); + + if (arg_read_only) + info.lo_flags |= LO_FLAGS_READ_ONLY; + + if (ioctl(loop, LOOP_SET_STATUS64, &info) < 0) + return log_error_errno(errno, "Failed to set loopback settings on %s: %m", loopdev); + + *device_path = loopdev; + loopdev = NULL; + + *loop_nr = nr; + + r = loop; + loop = -1; + + return r; +} + +#define PARTITION_TABLE_BLURB \ + "Note that the disk image needs to either contain only a single MBR partition of\n" \ + "type 0x83 that is marked bootable, or a single GPT partition of type " \ + "0FC63DAF-8483-4772-8E79-3D69D8477DE4 or follow\n" \ + " http://www.freedesktop.org/wiki/Specifications/DiscoverablePartitionsSpec/\n" \ + "to be bootable with systemd-nspawn." + +static int dissect_image( + int fd, + char **root_device, bool *root_device_rw, + char **home_device, bool *home_device_rw, + char **srv_device, bool *srv_device_rw, + bool *secondary) { + +#ifdef HAVE_BLKID + int home_nr = -1, srv_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_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; + _cleanup_udev_unref_ struct udev *udev = NULL; + struct udev_list_entry *first, *item; + bool home_rw = true, root_rw = true, secondary_root_rw = true, srv_rw = true, generic_rw = true; + bool is_gpt, is_mbr, multiple_generic = false; + const char *pttype = NULL; + blkid_partlist pl; + struct stat st; + unsigned i; + int r; + + assert(fd >= 0); + assert(root_device); + assert(home_device); + assert(srv_device); + assert(secondary); + assert(arg_image); + + b = blkid_new_probe(); + if (!b) + return log_oom(); + + errno = 0; + r = blkid_probe_set_device(b, fd, 0, 0); + if (r != 0) { + if (errno == 0) + return log_oom(); + + return log_error_errno(errno, "Failed to set device on blkid probe: %m"); + } + + blkid_probe_enable_partitions(b, 1); + blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -2 || r == 1) { + log_error("Failed to identify any partition table on\n" + " %s\n" + PARTITION_TABLE_BLURB, arg_image); + return -EINVAL; + } else if (r != 0) { + if (errno == 0) + errno = EIO; + return log_error_errno(errno, "Failed to probe: %m"); + } + + (void) blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL); + + is_gpt = streq_ptr(pttype, "gpt"); + is_mbr = streq_ptr(pttype, "dos"); + + if (!is_gpt && !is_mbr) { + log_error("No GPT or MBR partition table discovered on\n" + " %s\n" + PARTITION_TABLE_BLURB, arg_image); + return -EINVAL; + } + + errno = 0; + pl = blkid_probe_get_partitions(b); + if (!pl) { + if (errno == 0) + return log_oom(); + + log_error("Failed to list partitions of %s", arg_image); + return -errno; + } + + udev = udev_new(); + if (!udev) + return log_oom(); + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Failed to stat block device: %m"); + + d = udev_device_new_from_devnum(udev, 'b', st.st_rdev); + if (!d) + return log_oom(); + + for (i = 0;; i++) { + int n, m; + + if (i >= 10) { + log_error("Kernel partitions never appeared."); + return -ENXIO; + } + + e = udev_enumerate_new(udev); + if (!e) + return log_oom(); + + r = udev_enumerate_add_match_parent(e, d); + if (r < 0) + return log_oom(); + + r = udev_enumerate_scan_devices(e); + if (r < 0) + return log_error_errno(r, "Failed to scan for partition devices of %s: %m", arg_image); + + /* Count the partitions enumerated by the kernel */ + n = 0; + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) + n++; + + /* Count the partitions enumerated by blkid */ + m = blkid_partlist_numof_partitions(pl); + if (n == m + 1) + break; + if (n > m + 1) { + log_error("blkid and kernel partition list do not match."); + return -EIO; + } + if (n < m + 1) { + unsigned j; + + /* The kernel has probed fewer partitions than + * blkid? Maybe the kernel prober is still + * running or it got EBUSY because udev + * already opened the device. Let's reprobe + * the device, which is a synchronous call + * that waits until probing is complete. */ + + for (j = 0; j < 20; j++) { + + r = ioctl(fd, BLKRRPART, 0); + if (r < 0) + r = -errno; + if (r >= 0 || r != -EBUSY) + break; + + /* If something else has the device + * open, such as an udev rule, the + * ioctl will return EBUSY. Since + * there's no way to wait until it + * isn't busy anymore, let's just wait + * a bit, and try again. + * + * This is really something they + * should fix in the kernel! */ + + usleep(50 * USEC_PER_MSEC); + } + + if (r < 0) + return log_error_errno(r, "Failed to reread partition table: %m"); + } + + e = udev_enumerate_unref(e); + } + + first = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(item, first) { + _cleanup_udev_device_unref_ struct udev_device *q; + const char *node; + unsigned long long flags; + blkid_partition pp; + dev_t qn; + int nr; + + errno = 0; + q = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); + if (!q) { + if (!errno) + errno = ENOMEM; + + return log_error_errno(errno, "Failed to get partition device of %s: %m", arg_image); + } + + qn = udev_device_get_devnum(q); + if (major(qn) == 0) + continue; + + if (st.st_rdev == qn) + continue; + + node = udev_device_get_devnode(q); + if (!node) + continue; + + pp = blkid_partlist_devno_to_partition(pl, qn); + if (!pp) + continue; + + flags = blkid_partition_get_flags(pp); + + nr = blkid_partition_get_partno(pp); + if (nr < 0) + continue; + + if (is_gpt) { + sd_id128_t type_id; + const char *stype; + + if (flags & GPT_FLAG_NO_AUTO) + continue; + + stype = blkid_partition_get_type_string(pp); + if (!stype) + continue; + + if (sd_id128_from_string(stype, &type_id) < 0) + continue; + + if (sd_id128_equal(type_id, GPT_HOME)) { + + if (home && nr >= home_nr) + continue; + + home_nr = nr; + home_rw = !(flags & GPT_FLAG_READ_ONLY); + + r = free_and_strdup(&home, node); + if (r < 0) + return log_oom(); + + } else if (sd_id128_equal(type_id, GPT_SRV)) { + + if (srv && nr >= srv_nr) + continue; + + srv_nr = nr; + srv_rw = !(flags & GPT_FLAG_READ_ONLY); + + r = free_and_strdup(&srv, node); + if (r < 0) + return log_oom(); + } +#ifdef GPT_ROOT_NATIVE + else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) { + + if (root && nr >= root_nr) + continue; + + root_nr = nr; + root_rw = !(flags & GPT_FLAG_READ_ONLY); + + r = free_and_strdup(&root, node); + if (r < 0) + return log_oom(); + } +#endif +#ifdef GPT_ROOT_SECONDARY + else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY)) { + + if (secondary_root && nr >= secondary_root_nr) + continue; + + secondary_root_nr = nr; + secondary_root_rw = !(flags & GPT_FLAG_READ_ONLY); + + r = free_and_strdup(&secondary_root, node); + if (r < 0) + return log_oom(); + } +#endif + else if (sd_id128_equal(type_id, GPT_LINUX_GENERIC)) { + + if (generic) + multiple_generic = true; + else { + generic_rw = !(flags & GPT_FLAG_READ_ONLY); + + r = free_and_strdup(&generic, node); + if (r < 0) + return log_oom(); + } + } + + } else if (is_mbr) { + int type; + + if (flags != 0x80) /* Bootable flag */ + continue; + + type = blkid_partition_get_type(pp); + if (type != 0x83) /* Linux partition */ + continue; + + if (generic) + multiple_generic = true; + else { + generic_rw = true; + + r = free_and_strdup(&root, node); + if (r < 0) + return log_oom(); + } + } + } + + if (root) { + *root_device = root; + root = NULL; + + *root_device_rw = root_rw; + *secondary = false; + } else if (secondary_root) { + *root_device = secondary_root; + secondary_root = NULL; + + *root_device_rw = secondary_root_rw; + *secondary = true; + } else if (generic) { + + /* There were no partitions with precise meanings + * around, but we found generic partitions. In this + * case, if there's only one, we can go ahead and boot + * it, otherwise we bail out, because we really cannot + * make any sense of it. */ + + if (multiple_generic) { + log_error("Identified multiple bootable Linux partitions on\n" + " %s\n" + PARTITION_TABLE_BLURB, arg_image); + return -EINVAL; + } + + *root_device = generic; + generic = NULL; + + *root_device_rw = generic_rw; + *secondary = false; + } else { + log_error("Failed to identify root partition in disk image\n" + " %s\n" + PARTITION_TABLE_BLURB, arg_image); + return -EINVAL; + } + + if (home) { + *home_device = home; + home = NULL; + + *home_device_rw = home_rw; + } + + if (srv) { + *srv_device = srv; + srv = NULL; + + *srv_device_rw = srv_rw; + } + + return 0; +#else + log_error("--image= is not supported, compiled without blkid support."); + return -EOPNOTSUPP; +#endif +} + +static int mount_device(const char *what, const char *where, const char *directory, bool rw) { +#ifdef HAVE_BLKID + _cleanup_blkid_free_probe_ blkid_probe b = NULL; + const char *fstype, *p; + int r; + + assert(what); + assert(where); + + if (arg_read_only) + rw = false; + + if (directory) + p = strjoina(where, directory); + else + p = where; + + 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 for %s: %m", what); + } + + blkid_probe_enable_superblocks(b, 1); + blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -1 || r == 1) { + log_error("Cannot determine file system type of %s", what); + return -EINVAL; + } else if (r != 0) { + if (errno == 0) + errno = EIO; + return log_error_errno(errno, "Failed to probe %s: %m", what); + } + + errno = 0; + if (blkid_probe_lookup_value(b, "TYPE", &fstype, NULL) < 0) { + if (errno == 0) + errno = EINVAL; + log_error("Failed to determine file system type of %s", what); + return -errno; + } + + if (streq(fstype, "crypto_LUKS")) { + log_error("nspawn currently does not support LUKS disk images."); + return -EOPNOTSUPP; + } + + if (mount(what, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), NULL) < 0) + return log_error_errno(errno, "Failed to mount %s: %m", what); + + return 0; +#else + log_error("--image= is not supported, compiled without blkid support."); + return -EOPNOTSUPP; +#endif +} + +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) { + int r; + + assert(where); + + if (root_device) { + r = mount_device(root_device, arg_directory, NULL, root_device_rw); + if (r < 0) + return log_error_errno(r, "Failed to mount root directory: %m"); + } + + if (home_device) { + r = mount_device(home_device, arg_directory, "/home", home_device_rw); + if (r < 0) + return log_error_errno(r, "Failed to mount home directory: %m"); + } + + if (srv_device) { + r = mount_device(srv_device, arg_directory, "/srv", srv_device_rw); + if (r < 0) + return log_error_errno(r, "Failed to mount server data directory: %m"); + } + + return 0; +} + +static void loop_remove(int nr, int *image_fd) { + _cleanup_close_ int control = -1; + int r; + + if (nr < 0) + return; + + if (image_fd && *image_fd >= 0) { + r = ioctl(*image_fd, LOOP_CLR_FD); + if (r < 0) + log_debug_errno(errno, "Failed to close loop image: %m"); + *image_fd = safe_close(*image_fd); + } + + control = open("/dev/loop-control", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + if (control < 0) { + log_warning_errno(errno, "Failed to open /dev/loop-control: %m"); + return; + } + + r = ioctl(control, LOOP_CTL_REMOVE, nr); + if (r < 0) + log_debug_errno(errno, "Failed to remove loop %d: %m", nr); +} + +/* + * Return values: + * < 0 : wait_for_terminate() failed to get the state of the + * container, the container was terminated by a signal, or + * failed for an unknown reason. No change is made to the + * container argument. + * > 0 : The program executed in the container terminated with an + * error. The exit code of the program executed in the + * container is returned. The container argument has been set + * to CONTAINER_TERMINATED. + * 0 : The container is being rebooted, has been shut down or exited + * successfully. The container argument has been set to either + * CONTAINER_TERMINATED or CONTAINER_REBOOTED. + * + * That is, success is indicated by a return value of zero, and an + * error is indicated by a non-zero value. + */ +static int wait_for_container(pid_t pid, ContainerStatus *container) { + siginfo_t status; + int r; + + r = wait_for_terminate(pid, &status); + if (r < 0) + return log_warning_errno(r, "Failed to wait for container: %m"); + + switch (status.si_code) { + + case CLD_EXITED: + if (status.si_status == 0) { + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s exited successfully.", arg_machine); + + } else + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s failed with error code %i.", arg_machine, status.si_status); + + *container = CONTAINER_TERMINATED; + return status.si_status; + + case CLD_KILLED: + if (status.si_status == SIGINT) { + + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s has been shut down.", arg_machine); + *container = CONTAINER_TERMINATED; + return 0; + + } else if (status.si_status == SIGHUP) { + + log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, "Container %s is being rebooted.", arg_machine); + *container = CONTAINER_REBOOTED; + return 0; + } + + /* CLD_KILLED fallthrough */ + + case CLD_DUMPED: + log_error("Container %s terminated by signal %s.", arg_machine, signal_to_string(status.si_status)); + return -EIO; + + default: + log_error("Container %s failed due to unknown reason.", arg_machine); + return -EIO; + } + + return r; +} + +static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { + pid_t pid; + + pid = PTR_TO_PID(userdata); + if (pid > 0) { + if (kill(pid, arg_kill_signal) >= 0) { + log_info("Trying to halt container. Send SIGTERM again to trigger immediate termination."); + sd_event_source_set_userdata(s, NULL); + return 0; + } + } + + sd_event_exit(sd_event_source_get_event(s), 0); + return 0; +} + +static int determine_names(void) { + int r; + + if (arg_template && !arg_directory && arg_machine) { + + /* If --template= was specified then we should not + * search for a machine, but instead create a new one + * in /var/lib/machine. */ + + arg_directory = strjoin("/var/lib/machines/", arg_machine, NULL); + if (!arg_directory) + return log_oom(); + } + + if (!arg_image && !arg_directory) { + if (arg_machine) { + _cleanup_(image_unrefp) Image *i = NULL; + + r = image_find(arg_machine, &i); + if (r < 0) + return log_error_errno(r, "Failed to find image for machine '%s': %m", arg_machine); + else if (r == 0) { + log_error("No image for machine '%s': %m", arg_machine); + return -ENOENT; + } + + if (i->type == IMAGE_RAW) + r = free_and_strdup(&arg_image, i->path); + else + r = free_and_strdup(&arg_directory, i->path); + if (r < 0) + return log_error_errno(r, "Invalid image directory: %m"); + + if (!arg_ephemeral) + arg_read_only = arg_read_only || i->read_only; + } else + arg_directory = get_current_dir_name(); + + if (!arg_directory && !arg_machine) { + log_error("Failed to determine path, please use -D or -i."); + return -EINVAL; + } + } + + if (!arg_machine) { + if (arg_directory && path_equal(arg_directory, "/")) + arg_machine = gethostname_malloc(); + else + arg_machine = strdup(basename(arg_image ?: arg_directory)); + + if (!arg_machine) + return log_oom(); + + hostname_cleanup(arg_machine); + if (!machine_name_is_valid(arg_machine)) { + log_error("Failed to determine machine name automatically, please use -M."); + return -EINVAL; + } + + if (arg_ephemeral) { + char *b; + + /* Add a random suffix when this is an + * ephemeral machine, so that we can run many + * instances at once without manually having + * to specify -M each time. */ + + if (asprintf(&b, "%s-%016" PRIx64, arg_machine, random_u64()) < 0) + return log_oom(); + + free(arg_machine); + arg_machine = b; + } + } + + return 0; +} + +static int determine_uid_shift(const char *directory) { + int r; + + if (!arg_userns) { + arg_uid_shift = 0; + return 0; + } + + if (arg_uid_shift == UID_INVALID) { + struct stat st; + + r = stat(directory, &st); + if (r < 0) + return log_error_errno(errno, "Failed to determine UID base of %s: %m", directory); + + arg_uid_shift = st.st_uid & UINT32_C(0xffff0000); + + if (arg_uid_shift != (st.st_gid & UINT32_C(0xffff0000))) { + log_error("UID and GID base of %s don't match.", directory); + return -EINVAL; + } + + arg_uid_range = UINT32_C(0x10000); + } + + if (arg_uid_shift > (uid_t) -1 - arg_uid_range) { + log_error("UID base too high for UID range."); + return -EINVAL; + } + + log_info("Using user namespaces with base " UID_FMT " and range " UID_FMT ".", arg_uid_shift, arg_uid_range); + return 0; +} + +static int inner_child( + Barrier *barrier, + const char *directory, + bool secondary, + int kmsg_socket, + int rtnl_socket, + FDSet *fds) { + + _cleanup_free_ char *home = NULL; + unsigned n_env = 1; + const char *envp[] = { + "PATH=" DEFAULT_PATH_SPLIT_USR, + NULL, /* container */ + NULL, /* TERM */ + NULL, /* HOME */ + NULL, /* USER */ + NULL, /* LOGNAME */ + NULL, /* container_uuid */ + NULL, /* LISTEN_FDS */ + NULL, /* LISTEN_PID */ + NULL + }; + + _cleanup_strv_free_ char **env_use = NULL; + int r; + + assert(barrier); + assert(directory); + assert(kmsg_socket >= 0); + + cg_unified_flush(); + + if (arg_userns) { + /* Tell the parent, that it now can write the UID map. */ + (void) barrier_place(barrier); /* #1 */ + + /* Wait until the parent wrote the UID map */ + if (!barrier_place_and_sync(barrier)) { /* #2 */ + log_error("Parent died too early"); + return -ESRCH; + } + } + + r = mount_all(NULL, arg_userns, true, arg_uid_shift, arg_private_network, arg_uid_range, arg_selinux_apifs_context); + if (r < 0) + return r; + + r = mount_sysfs(NULL); + if (r < 0) + return r; + + /* Wait until we are cgroup-ified, so that we + * can mount the right cgroup path writable */ + if (!barrier_place_and_sync(barrier)) { /* #3 */ + log_error("Parent died too early"); + return -ESRCH; + } + + r = mount_systemd_cgroup_writable("", arg_unified_cgroup_hierarchy); + if (r < 0) + return r; + + r = reset_uid_gid(); + if (r < 0) + return log_error_errno(r, "Couldn't become new root: %m"); + + r = setup_boot_id(NULL); + if (r < 0) + return r; + + r = setup_kmsg(NULL, kmsg_socket); + if (r < 0) + return r; + kmsg_socket = safe_close(kmsg_socket); + + umask(0022); + + if (setsid() < 0) + return log_error_errno(errno, "setsid() failed: %m"); + + if (arg_private_network) + loopback_setup(); + + if (arg_expose_ports) { + r = expose_port_send_rtnl(rtnl_socket); + if (r < 0) + return r; + rtnl_socket = safe_close(rtnl_socket); + } + + r = drop_capabilities(); + if (r < 0) + return log_error_errno(r, "drop_capabilities() failed: %m"); + + setup_hostname(); + + if (arg_personality != PERSONALITY_INVALID) { + if (personality(arg_personality) < 0) + return log_error_errno(errno, "personality() failed: %m"); + } else if (secondary) { + if (personality(PER_LINUX32) < 0) + return log_error_errno(errno, "personality() failed: %m"); + } + +#ifdef HAVE_SELINUX + if (arg_selinux_context) + if (setexeccon((security_context_t) arg_selinux_context) < 0) + return log_error_errno(errno, "setexeccon(\"%s\") failed: %m", arg_selinux_context); +#endif + + r = change_uid_gid(arg_user, &home); + if (r < 0) + return r; + + /* LXC sets container=lxc, so follow the scheme here */ + envp[n_env++] = strjoina("container=", arg_container_service_name); + + envp[n_env] = strv_find_prefix(environ, "TERM="); + if (envp[n_env]) + n_env ++; + + if ((asprintf((char**)(envp + n_env++), "HOME=%s", home ? home: "/root") < 0) || + (asprintf((char**)(envp + n_env++), "USER=%s", arg_user ? arg_user : "root") < 0) || + (asprintf((char**)(envp + n_env++), "LOGNAME=%s", arg_user ? arg_user : "root") < 0)) + return log_oom(); + + if (!sd_id128_equal(arg_uuid, SD_ID128_NULL)) { + char as_uuid[37]; + + if (asprintf((char**)(envp + n_env++), "container_uuid=%s", id128_format_as_uuid(arg_uuid, as_uuid)) < 0) + return log_oom(); + } + + if (fdset_size(fds) > 0) { + r = fdset_cloexec(fds, false); + if (r < 0) + return log_error_errno(r, "Failed to unset O_CLOEXEC for file descriptors."); + + if ((asprintf((char **)(envp + n_env++), "LISTEN_FDS=%u", fdset_size(fds)) < 0) || + (asprintf((char **)(envp + n_env++), "LISTEN_PID=1") < 0)) + return log_oom(); + } + + env_use = strv_env_merge(2, envp, arg_setenv); + if (!env_use) + return log_oom(); + + /* Let the parent know that we are ready and + * wait until the parent is ready with the + * setup, too... */ + if (!barrier_place_and_sync(barrier)) { /* #4 */ + log_error("Parent died too early"); + return -ESRCH; + } + + if (arg_chdir) + if (chdir(arg_chdir) < 0) + return log_error_errno(errno, "Failed to change to specified working directory %s: %m", arg_chdir); + + if (arg_start_mode == START_PID2) { + r = stub_pid1(); + if (r < 0) + return r; + } + + /* Now, explicitly close the log, so that we + * then can close all remaining fds. Closing + * the log explicitly first has the benefit + * that the logging subsystem knows about it, + * and is thus ready to be reopened should we + * need it again. Note that the other fds + * closed here are at least the locking and + * barrier fds. */ + log_close(); + (void) fdset_close_others(fds); + + if (arg_start_mode == START_BOOT) { + char **a; + size_t m; + + /* Automatically search for the init system */ + + m = 1 + strv_length(arg_parameters); + a = newa(char*, m + 1); + if (strv_isempty(arg_parameters)) + a[1] = NULL; + else + memcpy(a + 1, arg_parameters, m * sizeof(char*)); + + a[0] = (char*) "/usr/lib/systemd/systemd"; + execve(a[0], a, env_use); + + a[0] = (char*) "/lib/systemd/systemd"; + execve(a[0], a, env_use); + + a[0] = (char*) "/sbin/init"; + execve(a[0], a, env_use); + } else if (!strv_isempty(arg_parameters)) + execvpe(arg_parameters[0], arg_parameters, env_use); + else { + if (!arg_chdir) + chdir(home ?: "/root"); + + execle("/bin/bash", "-bash", NULL, env_use); + execle("/bin/sh", "-sh", NULL, env_use); + } + + r = -errno; + (void) log_open(); + return log_error_errno(r, "execv() failed: %m"); +} + +static int outer_child( + Barrier *barrier, + const char *directory, + const char *console, + const char *root_device, bool root_device_rw, + const char *home_device, bool home_device_rw, + const char *srv_device, bool srv_device_rw, + bool interactive, + bool secondary, + int pid_socket, + int kmsg_socket, + int rtnl_socket, + int uid_shift_socket, + FDSet *fds) { + + pid_t pid; + ssize_t l; + int r; + + assert(barrier); + assert(directory); + assert(console); + assert(pid_socket >= 0); + assert(kmsg_socket >= 0); + + cg_unified_flush(); + + if (prctl(PR_SET_PDEATHSIG, SIGKILL) < 0) + return log_error_errno(errno, "PR_SET_PDEATHSIG failed: %m"); + + if (interactive) { + close_nointr(STDIN_FILENO); + close_nointr(STDOUT_FILENO); + close_nointr(STDERR_FILENO); + + r = open_terminal(console, O_RDWR); + if (r != STDIN_FILENO) { + if (r >= 0) { + safe_close(r); + r = -EINVAL; + } + + return log_error_errno(r, "Failed to open console: %m"); + } + + if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO || + dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO) + return log_error_errno(errno, "Failed to duplicate console: %m"); + } + + r = reset_audit_loginuid(); + if (r < 0) + return r; + + /* Mark everything as slave, so that we still + * receive mounts from the real root, but don't + * propagate mounts to the real root. */ + if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) + return log_error_errno(errno, "MS_SLAVE|MS_REC failed: %m"); + + r = mount_devices(directory, + root_device, root_device_rw, + home_device, home_device_rw, + srv_device, srv_device_rw); + if (r < 0) + return r; + + r = determine_uid_shift(directory); + if (r < 0) + return r; + + if (arg_userns) { + l = send(uid_shift_socket, &arg_uid_shift, sizeof(arg_uid_shift), MSG_NOSIGNAL); + if (l < 0) + return log_error_errno(errno, "Failed to send UID shift: %m"); + if (l != sizeof(arg_uid_shift)) { + log_error("Short write while sending UID shift."); + return -EIO; + } + } + + /* Turn directory into bind mount */ + if (mount(directory, directory, NULL, MS_BIND|MS_REC, NULL) < 0) + return log_error_errno(errno, "Failed to make bind mount: %m"); + + r = setup_volatile(directory, arg_volatile_mode, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_context); + if (r < 0) + return r; + + r = setup_volatile_state(directory, arg_volatile_mode, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_context); + if (r < 0) + return r; + + r = base_filesystem_create(directory, arg_uid_shift, (gid_t) arg_uid_shift); + if (r < 0) + return r; + + if (arg_read_only) { + r = bind_remount_recursive(directory, true); + if (r < 0) + return log_error_errno(r, "Failed to make tree read-only: %m"); + } + + r = mount_all(directory, arg_userns, false, arg_private_network, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); + if (r < 0) + return r; + + r = copy_devnodes(directory); + if (r < 0) + return r; + + dev_setup(directory, arg_uid_shift, arg_uid_shift); + + r = setup_pts(directory); + if (r < 0) + return r; + + r = setup_propagate(directory); + if (r < 0) + return r; + + r = setup_dev_console(directory, console); + if (r < 0) + return r; + + r = setup_seccomp(); + if (r < 0) + return r; + + r = setup_timezone(directory); + if (r < 0) + return r; + + r = setup_resolv_conf(directory); + if (r < 0) + return r; + + r = setup_journal(directory); + if (r < 0) + return r; + + r = mount_custom(directory, arg_custom_mounts, arg_n_custom_mounts, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); + if (r < 0) + return r; + + r = mount_cgroups(directory, arg_unified_cgroup_hierarchy, arg_userns, arg_uid_shift, arg_uid_range, arg_selinux_apifs_context); + if (r < 0) + return r; + + r = mount_move_root(directory); + if (r < 0) + return log_error_errno(r, "Failed to move root directory: %m"); + + pid = raw_clone(SIGCHLD|CLONE_NEWNS| + (arg_share_system ? 0 : CLONE_NEWIPC|CLONE_NEWPID|CLONE_NEWUTS) | + (arg_private_network ? CLONE_NEWNET : 0) | + (arg_userns ? CLONE_NEWUSER : 0), + NULL); + if (pid < 0) + return log_error_errno(errno, "Failed to fork inner child: %m"); + if (pid == 0) { + pid_socket = safe_close(pid_socket); + uid_shift_socket = safe_close(uid_shift_socket); + + /* The inner child has all namespaces that are + * requested, so that we all are owned by the user if + * user namespaces are turned on. */ + + r = inner_child(barrier, directory, secondary, kmsg_socket, rtnl_socket, fds); + if (r < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + l = send(pid_socket, &pid, sizeof(pid), MSG_NOSIGNAL); + if (l < 0) + return log_error_errno(errno, "Failed to send PID: %m"); + if (l != sizeof(pid)) { + log_error("Short write while sending PID."); + return -EIO; + } + + pid_socket = safe_close(pid_socket); + kmsg_socket = safe_close(kmsg_socket); + rtnl_socket = safe_close(rtnl_socket); + + return 0; +} + +static int setup_uid_map(pid_t pid) { + char uid_map[strlen("/proc//uid_map") + DECIMAL_STR_MAX(uid_t) + 1], line[DECIMAL_STR_MAX(uid_t)*3+3+1]; + int r; + + assert(pid > 1); + + xsprintf(uid_map, "/proc/" PID_FMT "/uid_map", pid); + xsprintf(line, UID_FMT " " UID_FMT " " UID_FMT "\n", 0, arg_uid_shift, arg_uid_range); + r = write_string_file(uid_map, line, 0); + if (r < 0) + return log_error_errno(r, "Failed to write UID map: %m"); + + /* We always assign the same UID and GID ranges */ + xsprintf(uid_map, "/proc/" PID_FMT "/gid_map", pid); + r = write_string_file(uid_map, line, 0); + if (r < 0) + return log_error_errno(r, "Failed to write GID map: %m"); + + return 0; +} + +static int load_settings(void) { + _cleanup_(settings_freep) Settings *settings = NULL; + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *p = NULL; + const char *fn, *i; + int r; + + /* If all settings are masked, there's no point in looking for + * the settings file */ + if ((arg_settings_mask & _SETTINGS_MASK_ALL) == _SETTINGS_MASK_ALL) + return 0; + + fn = strjoina(arg_machine, ".nspawn"); + + /* We first look in the admin's directories in /etc and /run */ + FOREACH_STRING(i, "/etc/systemd/nspawn", "/run/systemd/nspawn") { + _cleanup_free_ char *j = NULL; + + j = strjoin(i, "/", fn, NULL); + if (!j) + return log_oom(); + + f = fopen(j, "re"); + if (f) { + p = j; + j = NULL; + + /* By default, we trust configuration from /etc and /run */ + if (arg_settings_trusted < 0) + arg_settings_trusted = true; + + break; + } + + if (errno != ENOENT) + return log_error_errno(errno, "Failed to open %s: %m", j); + } + + if (!f) { + /* After that, let's look for a file next to the + * actual image we shall boot. */ + + if (arg_image) { + p = file_in_same_dir(arg_image, fn); + if (!p) + return log_oom(); + } else if (arg_directory) { + p = file_in_same_dir(arg_directory, fn); + if (!p) + return log_oom(); + } + + if (p) { + f = fopen(p, "re"); + if (!f && errno != ENOENT) + return log_error_errno(errno, "Failed to open %s: %m", p); + + /* By default, we do not trust configuration from /var/lib/machines */ + if (arg_settings_trusted < 0) + arg_settings_trusted = false; + } + } + + if (!f) + return 0; + + log_debug("Settings are trusted: %s", yes_no(arg_settings_trusted)); + + r = settings_load(f, p, &settings); + if (r < 0) + return r; + + /* Copy over bits from the settings, unless they have been + * explicitly masked by command line switches. */ + + if ((arg_settings_mask & SETTING_START_MODE) == 0 && + settings->start_mode >= 0) { + arg_start_mode = settings->start_mode; + + strv_free(arg_parameters); + arg_parameters = settings->parameters; + settings->parameters = NULL; + } + + if ((arg_settings_mask & SETTING_WORKING_DIRECTORY) == 0 && + settings->working_directory) { + free(arg_chdir); + arg_chdir = settings->working_directory; + settings->working_directory = NULL; + } + + if ((arg_settings_mask & SETTING_ENVIRONMENT) == 0 && + settings->environment) { + strv_free(arg_setenv); + arg_setenv = settings->environment; + settings->environment = NULL; + } + + if ((arg_settings_mask & SETTING_USER) == 0 && + settings->user) { + free(arg_user); + arg_user = settings->user; + settings->user = NULL; + } + + if ((arg_settings_mask & SETTING_CAPABILITY) == 0) { + uint64_t plus; + + plus = settings->capability; + if (settings_private_network(settings)) + plus |= (1ULL << CAP_NET_ADMIN); + + if (!arg_settings_trusted && plus != 0) { + if (settings->capability != 0) + log_warning("Ignoring Capability= setting, file %s is not trusted.", p); + } else + arg_retain |= plus; + + arg_retain &= ~settings->drop_capability; + } + + if ((arg_settings_mask & SETTING_KILL_SIGNAL) == 0 && + settings->kill_signal > 0) + arg_kill_signal = settings->kill_signal; + + if ((arg_settings_mask & SETTING_PERSONALITY) == 0 && + settings->personality != PERSONALITY_INVALID) + arg_personality = settings->personality; + + if ((arg_settings_mask & SETTING_MACHINE_ID) == 0 && + !sd_id128_is_null(settings->machine_id)) { + + if (!arg_settings_trusted) + log_warning("Ignoring MachineID= setting, file %s is not trusted.", p); + else + arg_uuid = settings->machine_id; + } + + if ((arg_settings_mask & SETTING_READ_ONLY) == 0 && + settings->read_only >= 0) + arg_read_only = settings->read_only; + + if ((arg_settings_mask & SETTING_VOLATILE_MODE) == 0 && + settings->volatile_mode != _VOLATILE_MODE_INVALID) + arg_volatile_mode = settings->volatile_mode; + + if ((arg_settings_mask & SETTING_CUSTOM_MOUNTS) == 0 && + settings->n_custom_mounts > 0) { + + if (!arg_settings_trusted) + log_warning("Ignoring TemporaryFileSystem=, Bind= and BindReadOnly= settings, file %s is not trusted.", p); + else { + custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts); + arg_custom_mounts = settings->custom_mounts; + arg_n_custom_mounts = settings->n_custom_mounts; + + settings->custom_mounts = NULL; + settings->n_custom_mounts = 0; + } + } + + if ((arg_settings_mask & SETTING_NETWORK) == 0 && + (settings->private_network >= 0 || + settings->network_veth >= 0 || + settings->network_bridge || + settings->network_interfaces || + settings->network_macvlan || + settings->network_ipvlan || + settings->network_veth_extra)) { + + if (!arg_settings_trusted) + log_warning("Ignoring network settings, file %s is not trusted.", p); + else { + arg_network_veth = settings_network_veth(settings); + arg_private_network = settings_private_network(settings); + + strv_free(arg_network_interfaces); + arg_network_interfaces = settings->network_interfaces; + settings->network_interfaces = NULL; + + strv_free(arg_network_macvlan); + arg_network_macvlan = settings->network_macvlan; + settings->network_macvlan = NULL; + + strv_free(arg_network_ipvlan); + arg_network_ipvlan = settings->network_ipvlan; + settings->network_ipvlan = NULL; + + strv_free(arg_network_veth_extra); + arg_network_veth_extra = settings->network_veth_extra; + settings->network_veth_extra = NULL; + + free(arg_network_bridge); + arg_network_bridge = settings->network_bridge; + settings->network_bridge = NULL; + } + } + + if ((arg_settings_mask & SETTING_EXPOSE_PORTS) == 0 && + settings->expose_ports) { + + if (!arg_settings_trusted) + log_warning("Ignoring Port= setting, file %s is not trusted.", p); + else { + expose_port_free_all(arg_expose_ports); + arg_expose_ports = settings->expose_ports; + settings->expose_ports = NULL; + } + } + + 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; + 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; + int r, n_fd_passed, loop_nr = -1; + char veth_name[IFNAMSIZ]; + bool secondary = false, remove_subvol = false; + sigset_t mask_chld; + pid_t pid = 0; + int ret = EXIT_SUCCESS; + union in_addr_union exposed = {}; + _cleanup_release_lock_file_ LockFile tree_global_lock = LOCK_FILE_INIT, tree_local_lock = LOCK_FILE_INIT; + bool interactive; + + log_parse_environment(); + log_open(); + + /* Make sure rename_process() in the stub init process can work */ + saved_argv = argv; + saved_argc = argc; + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (geteuid() != 0) { + log_error("Need to be root."); + r = -EPERM; + goto finish; + } + r = determine_names(); + if (r < 0) + goto finish; + + r = load_settings(); + if (r < 0) + goto finish; + + r = verify_arguments(); + if (r < 0) + goto finish; + + n_fd_passed = sd_listen_fds(false); + if (n_fd_passed > 0) { + r = fdset_new_listen_fds(&fds, false); + if (r < 0) { + log_error_errno(r, "Failed to collect file descriptors: %m"); + goto finish; + } + } + + if (arg_directory) { + assert(!arg_image); + + if (path_equal(arg_directory, "/") && !arg_ephemeral) { + log_error("Spawning container on root directory is not supported. Consider using --ephemeral."); + r = -EINVAL; + goto finish; + } + + if (arg_ephemeral) { + _cleanup_free_ char *np = NULL; + + /* If the specified path is a mount point we + * generate the new snapshot immediately + * inside it under a random name. However if + * the specified is not a mount point we + * create the new snapshot in the parent + * directory, just next to it. */ + r = path_is_mount_point(arg_directory, 0); + if (r < 0) { + log_error_errno(r, "Failed to determine whether directory %s is mount point: %m", arg_directory); + goto finish; + } + if (r > 0) + r = tempfn_random_child(arg_directory, "machine.", &np); + else + r = tempfn_random(arg_directory, "machine.", &np); + if (r < 0) { + log_error_errno(r, "Failed to generate name for snapshot: %m"); + goto finish; + } + + r = image_path_lock(np, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); + if (r < 0) { + log_error_errno(r, "Failed to lock %s: %m", np); + goto finish; + } + + r = btrfs_subvol_snapshot(arg_directory, np, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); + if (r < 0) { + log_error_errno(r, "Failed to create snapshot %s from %s: %m", np, arg_directory); + goto finish; + } + + free(arg_directory); + arg_directory = np; + np = NULL; + + remove_subvol = true; + + } else { + r = image_path_lock(arg_directory, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); + if (r == -EBUSY) { + log_error_errno(r, "Directory tree %s is currently busy.", arg_directory); + goto finish; + } + if (r < 0) { + log_error_errno(r, "Failed to lock %s: %m", arg_directory); + return r; + } + + if (arg_template) { + r = btrfs_subvol_snapshot(arg_template, arg_directory, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); + if (r == -EEXIST) { + if (!arg_quiet) + log_info("Directory %s already exists, not populating from template %s.", arg_directory, arg_template); + } else if (r < 0) { + log_error_errno(r, "Couldn't create snapshot %s from %s: %m", arg_directory, arg_template); + goto finish; + } else { + if (!arg_quiet) + log_info("Populated %s from template %s.", arg_directory, arg_template); + } + } + } + + if (arg_start_mode == START_BOOT) { + if (path_is_os_tree(arg_directory) <= 0) { + log_error("Directory %s doesn't look like an OS root directory (os-release file is missing). Refusing.", arg_directory); + r = -EINVAL; + goto finish; + } + } else { + const char *p; + + p = strjoina(arg_directory, "/usr/"); + if (laccess(p, F_OK) < 0) { + log_error("Directory %s doesn't look like it has an OS tree. Refusing.", arg_directory); + r = -EINVAL; + goto finish; + } + } + + } else { + char template[] = "/tmp/nspawn-root-XXXXXX"; + + assert(arg_image); + assert(!arg_template); + + r = image_path_lock(arg_image, (arg_read_only ? LOCK_SH : LOCK_EX) | LOCK_NB, &tree_global_lock, &tree_local_lock); + if (r == -EBUSY) { + r = log_error_errno(r, "Disk image %s is currently busy.", arg_image); + goto finish; + } + if (r < 0) { + r = log_error_errno(r, "Failed to create image lock: %m"); + goto finish; + } + + if (!mkdtemp(template)) { + log_error_errno(errno, "Failed to create temporary directory: %m"); + r = -errno; + goto finish; + } + + arg_directory = strdup(template); + if (!arg_directory) { + r = log_oom(); + goto finish; + } + + image_fd = setup_image(&device_path, &loop_nr); + if (image_fd < 0) { + r = image_fd; + goto finish; + } + + r = dissect_image(image_fd, + &root_device, &root_device_rw, + &home_device, &home_device_rw, + &srv_device, &srv_device_rw, + &secondary); + if (r < 0) + goto finish; + } + + r = custom_mounts_prepare(); + if (r < 0) + goto finish; + + interactive = + isatty(STDIN_FILENO) > 0 && + isatty(STDOUT_FILENO) > 0; + + master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY); + if (master < 0) { + r = log_error_errno(errno, "Failed to acquire pseudo tty: %m"); + goto finish; + } + + r = ptsname_malloc(master, &console); + if (r < 0) { + r = log_error_errno(r, "Failed to determine tty name: %m"); + goto finish; + } + + if (unlockpt(master) < 0) { + r = log_error_errno(errno, "Failed to unlock tty: %m"); + goto finish; + } + + if (!arg_quiet) + log_info("Spawning container %s on %s.\nPress ^] three times within 1s to kill container.", + arg_machine, arg_image ?: arg_directory); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGCHLD, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); + + assert_se(sigemptyset(&mask_chld) == 0); + assert_se(sigaddset(&mask_chld, SIGCHLD) == 0); + + if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) { + r = log_error_errno(errno, "Failed to become subreaper: %m"); + goto finish; + } + + for (;;) { + _cleanup_close_pair_ int kmsg_socket_pair[2] = { -1, -1 }, rtnl_socket_pair[2] = { -1, -1 }, pid_socket_pair[2] = { -1, -1 }, uid_shift_socket_pair[2] = { -1, -1 }; + ContainerStatus container_status; + _cleanup_(barrier_destroy) Barrier barrier = BARRIER_NULL; + static const struct sigaction sa = { + .sa_handler = nop_signal_handler, + .sa_flags = SA_NOCLDSTOP, + }; + int ifi = 0; + ssize_t l; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + char last_char = 0; + + r = barrier_create(&barrier); + if (r < 0) { + log_error_errno(r, "Cannot initialize IPC barrier: %m"); + goto finish; + } + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, kmsg_socket_pair) < 0) { + r = log_error_errno(errno, "Failed to create kmsg socket pair: %m"); + goto finish; + } + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, rtnl_socket_pair) < 0) { + r = log_error_errno(errno, "Failed to create rtnl socket pair: %m"); + goto finish; + } + + if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pid_socket_pair) < 0) { + r = log_error_errno(errno, "Failed to create pid socket pair: %m"); + goto finish; + } + + if (arg_userns) + 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"); + goto finish; + } + + /* Child can be killed before execv(), so handle SIGCHLD + * in order to interrupt parent's blocking calls and + * give it a chance to call wait() and terminate. */ + r = sigprocmask(SIG_UNBLOCK, &mask_chld, NULL); + if (r < 0) { + r = log_error_errno(errno, "Failed to change the signal mask: %m"); + goto finish; + } + + r = sigaction(SIGCHLD, &sa, NULL); + if (r < 0) { + r = log_error_errno(errno, "Failed to install SIGCHLD handler: %m"); + goto finish; + } + + pid = raw_clone(SIGCHLD|CLONE_NEWNS, NULL); + if (pid < 0) { + if (errno == EINVAL) + r = log_error_errno(errno, "clone() failed, do you have namespace support enabled in your kernel? (You need UTS, IPC, PID and NET namespacing built in): %m"); + else + r = log_error_errno(errno, "clone() failed: %m"); + + goto finish; + } + + if (pid == 0) { + /* The outer child only has a file system namespace. */ + barrier_set_role(&barrier, BARRIER_CHILD); + + master = safe_close(master); + + kmsg_socket_pair[0] = safe_close(kmsg_socket_pair[0]); + rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); + pid_socket_pair[0] = safe_close(pid_socket_pair[0]); + uid_shift_socket_pair[0] = safe_close(uid_shift_socket_pair[0]); + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + r = outer_child(&barrier, + arg_directory, + console, + root_device, root_device_rw, + home_device, home_device_rw, + srv_device, srv_device_rw, + interactive, + secondary, + pid_socket_pair[1], + kmsg_socket_pair[1], + rtnl_socket_pair[1], + uid_shift_socket_pair[1], + fds); + if (r < 0) + _exit(EXIT_FAILURE); + + _exit(EXIT_SUCCESS); + } + + barrier_set_role(&barrier, BARRIER_PARENT); + + fds = fdset_free(fds); + + kmsg_socket_pair[1] = safe_close(kmsg_socket_pair[1]); + rtnl_socket_pair[1] = safe_close(rtnl_socket_pair[1]); + pid_socket_pair[1] = safe_close(pid_socket_pair[1]); + uid_shift_socket_pair[1] = safe_close(uid_shift_socket_pair[1]); + + /* Wait for the outer child. */ + r = wait_for_terminate_and_warn("namespace helper", pid, NULL); + if (r < 0) + goto finish; + if (r != 0) { + r = -EIO; + goto finish; + } + pid = 0; + + /* And now retrieve the PID of the inner child. */ + l = recv(pid_socket_pair[0], &pid, sizeof(pid), 0); + if (l < 0) { + r = log_error_errno(errno, "Failed to read inner child PID: %m"); + goto finish; + } + if (l != sizeof(pid)) { + log_error("Short read while reading inner child PID."); + r = EIO; + goto finish; + } + + log_debug("Init process invoked as PID " PID_FMT, pid); + + if (arg_userns) { + if (!barrier_place_and_sync(&barrier)) { /* #1 */ + log_error("Child died too early."); + r = -ESRCH; + goto finish; + } + + l = recv(uid_shift_socket_pair[0], &arg_uid_shift, sizeof(arg_uid_shift), 0); + if (l < 0) { + r = log_error_errno(errno, "Failed to read UID shift: %m"); + goto finish; + } + if (l != sizeof(arg_uid_shift)) { + log_error("Short read while reading UID shift."); + r = EIO; + goto finish; + } + + r = setup_uid_map(pid); + if (r < 0) + goto finish; + + (void) barrier_place(&barrier); /* #2 */ + } + + if (arg_private_network) { + + r = move_network_interfaces(pid, arg_network_interfaces); + if (r < 0) + goto finish; + + if (arg_network_veth) { + r = setup_veth(arg_machine, pid, veth_name, !!arg_network_bridge); + if (r < 0) + goto finish; + else if (r > 0) + ifi = r; + + if (arg_network_bridge) { + r = setup_bridge(veth_name, arg_network_bridge); + if (r < 0) + goto finish; + if (r > 0) + ifi = r; + } + } + + r = setup_veth_extra(arg_machine, pid, arg_network_veth_extra); + if (r < 0) + goto finish; + + r = setup_macvlan(arg_machine, pid, arg_network_macvlan); + if (r < 0) + goto finish; + + r = setup_ipvlan(arg_machine, pid, arg_network_ipvlan); + if (r < 0) + goto finish; + } + + if (arg_register) { + r = register_machine( + arg_machine, + pid, + arg_directory, + arg_uuid, + ifi, + arg_slice, + arg_custom_mounts, arg_n_custom_mounts, + arg_kill_signal, + arg_property, + arg_keep_unit, + arg_container_service_name); + if (r < 0) + goto finish; + } + + r = sync_cgroup(pid, arg_unified_cgroup_hierarchy); + if (r < 0) + goto finish; + + if (arg_keep_unit) { + r = create_subcgroup(pid, arg_unified_cgroup_hierarchy); + if (r < 0) + goto finish; + } + + r = chown_cgroup(pid, arg_uid_shift); + if (r < 0) + goto finish; + + /* Notify the child that the parent is ready with all + * its setup (including cgroup-ification), and that + * the child can now hand over control to the code to + * run inside the container. */ + (void) barrier_place(&barrier); /* #3 */ + + /* Block SIGCHLD here, before notifying child. + * process_pty() will handle it with the other signals. */ + assert_se(sigprocmask(SIG_BLOCK, &mask_chld, NULL) >= 0); + + /* Reset signal to default */ + r = default_signals(SIGCHLD, -1); + if (r < 0) { + log_error_errno(r, "Failed to reset SIGCHLD: %m"); + 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."); + r = -ESRCH; + goto finish; + } + + 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_kill_signal > 0) { + /* Try to kill the init system on SIGINT or SIGTERM */ + sd_event_add_signal(event, NULL, SIGINT, on_orderly_shutdown, PID_TO_PTR(pid)); + sd_event_add_signal(event, NULL, SIGTERM, on_orderly_shutdown, PID_TO_PTR(pid)); + } else { + /* Immediately exit */ + sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + } + + /* simply exit on sigchld */ + sd_event_add_signal(event, NULL, SIGCHLD, NULL, NULL); + + if (arg_expose_ports) { + r = expose_port_watch_rtnl(event, rtnl_socket_pair[0], on_address_change, &exposed, &rtnl); + if (r < 0) + goto finish; + + (void) expose_port_execute(rtnl, arg_expose_ports, &exposed); + } + + rtnl_socket_pair[0] = safe_close(rtnl_socket_pair[0]); + + r = pty_forward_new(event, master, PTY_FORWARD_IGNORE_VHANGUP | (interactive ? 0 : PTY_FORWARD_READ_ONLY), &forward); + if (r < 0) { + log_error_errno(r, "Failed to create PTY forwarder: %m"); + goto finish; + } + + r = sd_event_loop(event); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + goto finish; + } + + pty_forward_get_last_char(forward, &last_char); + + forward = pty_forward_free(forward); + + if (!arg_quiet && last_char != '\n') + putc('\n', stdout); + + /* Kill if it is not dead yet anyway */ + if (arg_register && !arg_keep_unit) + terminate_machine(pid); + + /* Normally redundant, but better safe than sorry */ + kill(pid, SIGKILL); + + r = wait_for_container(pid, &container_status); + pid = 0; + + if (r < 0) + /* We failed to wait for the container, or the + * container exited abnormally */ + goto finish; + else if (r > 0 || container_status == CONTAINER_TERMINATED){ + /* The container exited with a non-zero + * status, or with zero status and no reboot + * was requested. */ + ret = r; + break; + } + + /* CONTAINER_REBOOTED, loop again */ + + if (arg_keep_unit) { + /* Special handling if we are running as a + * service: instead of simply restarting the + * machine we want to restart the entire + * service, so let's inform systemd about this + * with the special exit code 133. The service + * file uses RestartForceExitStatus=133 so + * that this results in a full nspawn + * restart. This is necessary since we might + * have cgroup parameters set we want to have + * flushed out. */ + ret = 133; + r = 0; + break; + } + + expose_port_flush(arg_expose_ports, &exposed); + } + +finish: + sd_notify(false, + "STOPPING=1\n" + "STATUS=Terminating..."); + + if (pid > 0) + kill(pid, SIGKILL); + + /* Try to flush whatever is still queued in the pty */ + if (master >= 0) + (void) copy_bytes(master, STDOUT_FILENO, (uint64_t) -1, false); + + loop_remove(loop_nr, &image_fd); + + if (remove_subvol && arg_directory) { + int k; + + k = btrfs_subvol_remove(arg_directory, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); + if (k < 0) + log_warning_errno(k, "Cannot remove subvolume '%s', ignoring: %m", arg_directory); + } + + if (arg_machine) { + const char *p; + + p = strjoina("/run/systemd/nspawn/propagate/", arg_machine); + (void) rm_rf(p, REMOVE_ROOT); + } + + expose_port_flush(arg_expose_ports, &exposed); + + free(arg_directory); + free(arg_template); + free(arg_image); + free(arg_machine); + free(arg_user); + free(arg_chdir); + strv_free(arg_setenv); + free(arg_network_bridge); + strv_free(arg_network_interfaces); + strv_free(arg_network_macvlan); + strv_free(arg_network_ipvlan); + strv_free(arg_network_veth_extra); + strv_free(arg_parameters); + custom_mount_free_all(arg_custom_mounts, arg_n_custom_mounts); + expose_port_free_all(arg_expose_ports); + + return r < 0 ? EXIT_FAILURE : ret; +} diff --git a/src/systemd-path/Makefile b/src/systemd-path/Makefile new file mode 100644 index 0000000000..19eb6bd10a --- /dev/null +++ b/src/systemd-path/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_path_SOURCES = \ + src/path/path.c + +systemd_path_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-path/path.c b/src/systemd-path/path.c new file mode 100644 index 0000000000..80268ed874 --- /dev/null +++ b/src/systemd-path/path.c @@ -0,0 +1,198 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "util.h" + +static const char *arg_suffix = NULL; + +static const char* const path_table[_SD_PATH_MAX] = { + [SD_PATH_TEMPORARY] = "temporary", + [SD_PATH_TEMPORARY_LARGE] = "temporary-large", + [SD_PATH_SYSTEM_BINARIES] = "system-binaries", + [SD_PATH_SYSTEM_INCLUDE] = "system-include", + [SD_PATH_SYSTEM_LIBRARY_PRIVATE] = "system-library-private", + [SD_PATH_SYSTEM_LIBRARY_ARCH] = "system-library-arch", + [SD_PATH_SYSTEM_SHARED] = "system-shared", + [SD_PATH_SYSTEM_CONFIGURATION_FACTORY] = "system-configuration-factory", + [SD_PATH_SYSTEM_STATE_FACTORY] = "system-state-factory", + [SD_PATH_SYSTEM_CONFIGURATION] = "system-configuration", + [SD_PATH_SYSTEM_RUNTIME] = "system-runtime", + [SD_PATH_SYSTEM_RUNTIME_LOGS] = "system-runtime-logs", + [SD_PATH_SYSTEM_STATE_PRIVATE] = "system-state-private", + [SD_PATH_SYSTEM_STATE_LOGS] = "system-state-logs", + [SD_PATH_SYSTEM_STATE_CACHE] = "system-state-cache", + [SD_PATH_SYSTEM_STATE_SPOOL] = "system-state-spool", + [SD_PATH_USER_BINARIES] = "user-binaries", + [SD_PATH_USER_LIBRARY_PRIVATE] = "user-library-private", + [SD_PATH_USER_LIBRARY_ARCH] = "user-library-arch", + [SD_PATH_USER_SHARED] = "user-shared", + [SD_PATH_USER_CONFIGURATION] = "user-configuration", + [SD_PATH_USER_RUNTIME] = "user-runtime", + [SD_PATH_USER_STATE_CACHE] = "user-state-cache", + [SD_PATH_USER] = "user", + [SD_PATH_USER_DOCUMENTS] = "user-documents", + [SD_PATH_USER_MUSIC] = "user-music", + [SD_PATH_USER_PICTURES] = "user-pictures", + [SD_PATH_USER_VIDEOS] = "user-videos", + [SD_PATH_USER_DOWNLOAD] = "user-download", + [SD_PATH_USER_PUBLIC] = "user-public", + [SD_PATH_USER_TEMPLATES] = "user-templates", + [SD_PATH_USER_DESKTOP] = "user-desktop", + [SD_PATH_SEARCH_BINARIES] = "search-binaries", + [SD_PATH_SEARCH_LIBRARY_PRIVATE] = "search-library-private", + [SD_PATH_SEARCH_LIBRARY_ARCH] = "search-library-arch", + [SD_PATH_SEARCH_SHARED] = "search-shared", + [SD_PATH_SEARCH_CONFIGURATION_FACTORY] = "search-configuration-factory", + [SD_PATH_SEARCH_STATE_FACTORY] = "search-state-factory", + [SD_PATH_SEARCH_CONFIGURATION] = "search-configuration", +}; + +static int list_homes(void) { + uint64_t i = 0; + int r = 0; + + for (i = 0; i < ELEMENTSOF(path_table); i++) { + _cleanup_free_ char *p = NULL; + int q; + + q = sd_path_home(i, arg_suffix, &p); + if (q == -ENXIO) + continue; + if (q < 0) { + log_error_errno(r, "Failed to query %s: %m", path_table[i]); + r = q; + continue; + } + + printf("%s: %s\n", path_table[i], p); + } + + return r; +} + +static int print_home(const char *n) { + uint64_t i = 0; + int r; + + for (i = 0; i < ELEMENTSOF(path_table); i++) { + if (streq(path_table[i], n)) { + _cleanup_free_ char *p = NULL; + + r = sd_path_home(i, arg_suffix, &p); + if (r < 0) + return log_error_errno(r, "Failed to query %s: %m", n); + + printf("%s\n", p); + return 0; + } + } + + log_error("Path %s not known.", n); + return -EOPNOTSUPP; +} + +static void help(void) { + printf("%s [OPTIONS...] [NAME...]\n\n" + "Show system and user paths.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --suffix=SUFFIX Suffix to append to paths\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_SUFFIX, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "suffix", required_argument, NULL, ARG_SUFFIX }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_SUFFIX: + arg_suffix = optarg; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +int main(int argc, char* argv[]) { + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (argc > optind) { + int i, q; + + for (i = optind; i < argc; i++) { + q = print_home(argv[i]); + if (q < 0) + r = q; + } + } else + r = list_homes(); + + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-quotacheck/Makefile b/src/systemd-quotacheck/Makefile new file mode 100644 index 0000000000..3b90e6efab --- /dev/null +++ b/src/systemd-quotacheck/Makefile @@ -0,0 +1,46 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_QUOTACHECK),) +rootlibexec_PROGRAMS += \ + systemd-quotacheck + +nodist_systemunit_DATA += \ + units/systemd-quotacheck.service + +systemd_quotacheck_SOURCES = \ + src/quotacheck/quotacheck.c + +systemd_quotacheck_LDADD = \ + libshared.la +endif + +EXTRA_DIST += \ + units/systemd-quotacheck.service.in + +nodist_systemunit_DATA += \ + units/quotaon.service + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-quotacheck/quotacheck.c b/src/systemd-quotacheck/quotacheck.c new file mode 100644 index 0000000000..6d8c05f046 --- /dev/null +++ b/src/systemd-quotacheck/quotacheck.c @@ -0,0 +1,124 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "proc-cmdline.h" +#include "process-util.h" +#include "signal-util.h" +#include "string-util.h" +#include "util.h" + +static bool arg_skip = false; +static bool arg_force = false; + +static int parse_proc_cmdline_item(const char *key, const char *value) { + + if (streq(key, "quotacheck.mode") && value) { + + if (streq(value, "auto")) + arg_force = arg_skip = false; + else if (streq(value, "force")) + arg_force = true; + else if (streq(value, "skip")) + arg_skip = true; + else + log_warning("Invalid quotacheck.mode= parameter '%s'. Ignoring.", value); + } + +#ifdef HAVE_SYSV_COMPAT + else if (streq(key, "forcequotacheck") && !value) { + log_warning("Please use 'quotacheck.mode=force' rather than 'forcequotacheck' on the kernel command line."); + arg_force = true; + } +#endif + + return 0; +} + +static void test_files(void) { + +#ifdef HAVE_SYSV_COMPAT + if (access("/forcequotacheck", F_OK) >= 0) { + log_error("Please pass 'quotacheck.mode=force' on the kernel command line rather than creating /forcequotacheck on the root file system."); + arg_force = true; + } +#endif +} + +int main(int argc, char *argv[]) { + + static const char * const cmdline[] = { + QUOTACHECK, + "-anug", + NULL + }; + + pid_t pid; + int r; + + if (argc > 1) { + log_error("This program takes no arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = parse_proc_cmdline(parse_proc_cmdline_item); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + + test_files(); + + if (!arg_force) { + if (arg_skip) + return EXIT_SUCCESS; + + if (access("/run/systemd/quotacheck", F_OK) < 0) + return EXIT_SUCCESS; + } + + pid = fork(); + if (pid < 0) { + log_error_errno(errno, "fork(): %m"); + return EXIT_FAILURE; + } else if (pid == 0) { + + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0); + + execv(cmdline[0], (char**) cmdline); + _exit(1); /* Operational error */ + } + + r = wait_for_terminate_and_warn("quotacheck", pid, true); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-random-seed/Makefile b/src/systemd-random-seed/Makefile new file mode 100644 index 0000000000..d4695c8fb4 --- /dev/null +++ b/src/systemd-random-seed/Makefile @@ -0,0 +1,47 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_RANDOMSEED),) +rootlibexec_PROGRAMS += \ + systemd-random-seed + +nodist_systemunit_DATA += \ + units/systemd-random-seed.service + +systemd_random_seed_SOURCES = \ + src/random-seed/random-seed.c + +systemd_random_seed_LDADD = \ + libshared.la + +SYSINIT_TARGET_WANTS += \ + systemd-random-seed.service + +endif + +EXTRA_DIST += \ + units/systemd-random-seed.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-random-seed/random-seed.c b/src/systemd-random-seed/random-seed.c new file mode 100644 index 0000000000..6748bb9dd3 --- /dev/null +++ b/src/systemd-random-seed/random-seed.c @@ -0,0 +1,176 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "log.h" +#include "mkdir.h" +#include "string-util.h" +#include "util.h" + +#define POOL_SIZE_MIN 512 + +int main(int argc, char *argv[]) { + _cleanup_close_ int seed_fd = -1, random_fd = -1; + _cleanup_free_ void* buf = NULL; + size_t buf_size = 0; + ssize_t k; + int r, open_rw_error; + FILE *f; + bool refresh_seed_file = true; + + if (argc != 2) { + log_error("This program requires one argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + /* Read pool size, if possible */ + f = fopen("/proc/sys/kernel/random/poolsize", "re"); + if (f) { + if (fscanf(f, "%zu", &buf_size) > 0) + /* poolsize is in bits on 2.6, but we want bytes */ + buf_size /= 8; + + fclose(f); + } + + if (buf_size <= POOL_SIZE_MIN) + buf_size = POOL_SIZE_MIN; + + buf = malloc(buf_size); + if (!buf) { + r = log_oom(); + goto finish; + } + + r = mkdir_parents_label(RANDOM_SEED, 0755); + if (r < 0) { + log_error_errno(r, "Failed to create directory " RANDOM_SEED_DIR ": %m"); + goto finish; + } + + /* When we load the seed we read it and write it to the device + * and then immediately update the saved seed with new data, + * to make sure the next boot gets seeded differently. */ + + if (streq(argv[1], "load")) { + + seed_fd = open(RANDOM_SEED, O_RDWR|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600); + open_rw_error = -errno; + if (seed_fd < 0) { + refresh_seed_file = false; + + seed_fd = open(RANDOM_SEED, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (seed_fd < 0) { + bool missing = errno == ENOENT; + + log_full_errno(missing ? LOG_DEBUG : LOG_ERR, + open_rw_error, "Failed to open " RANDOM_SEED " for writing: %m"); + r = log_full_errno(missing ? LOG_DEBUG : LOG_ERR, + errno, "Failed to open " RANDOM_SEED " for reading: %m"); + if (missing) + r = 0; + + goto finish; + } + } + + random_fd = open("/dev/urandom", O_RDWR|O_CLOEXEC|O_NOCTTY, 0600); + if (random_fd < 0) { + random_fd = open("/dev/urandom", O_WRONLY|O_CLOEXEC|O_NOCTTY, 0600); + if (random_fd < 0) { + r = log_error_errno(errno, "Failed to open /dev/urandom: %m"); + goto finish; + } + } + + k = loop_read(seed_fd, buf, buf_size, false); + if (k < 0) + r = log_error_errno(k, "Failed to read seed from " RANDOM_SEED ": %m"); + else if (k == 0) { + r = 0; + log_debug("Seed file " RANDOM_SEED " not yet initialized, proceeding."); + } else { + (void) lseek(seed_fd, 0, SEEK_SET); + + r = loop_write(random_fd, buf, (size_t) k, false); + if (r < 0) + log_error_errno(r, "Failed to write seed to /dev/urandom: %m"); + } + + } else if (streq(argv[1], "save")) { + + seed_fd = open(RANDOM_SEED, O_WRONLY|O_CLOEXEC|O_NOCTTY|O_CREAT, 0600); + if (seed_fd < 0) { + r = log_error_errno(errno, "Failed to open " RANDOM_SEED ": %m"); + goto finish; + } + + random_fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (random_fd < 0) { + r = log_error_errno(errno, "Failed to open /dev/urandom: %m"); + goto finish; + } + + } else { + log_error("Unknown verb '%s'.", argv[1]); + r = -EINVAL; + goto finish; + } + + if (refresh_seed_file) { + + /* This is just a safety measure. Given that we are root and + * most likely created the file ourselves the mode and owner + * should be correct anyway. */ + (void) fchmod(seed_fd, 0600); + (void) fchown(seed_fd, 0, 0); + + k = loop_read(random_fd, buf, buf_size, false); + if (k < 0) { + r = log_error_errno(k, "Failed to read new seed from /dev/urandom: %m"); + goto finish; + } + if (k == 0) { + log_error("Got EOF while reading from /dev/urandom."); + r = -EIO; + goto finish; + } + + r = loop_write(seed_fd, buf, (size_t) k, false); + if (r < 0) + log_error_errno(r, "Failed to write new random seed file: %m"); + } + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-remount-fs/Makefile b/src/systemd-remount-fs/Makefile new file mode 100644 index 0000000000..91baa27bf1 --- /dev/null +++ b/src/systemd-remount-fs/Makefile @@ -0,0 +1,34 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_remount_fs_SOURCES = \ + src/remount-fs/remount-fs.c \ + src/core/mount-setup.c \ + src/core/mount-setup.h + +systemd_remount_fs_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-remount-fs/remount-fs.c b/src/systemd-remount-fs/remount-fs.c new file mode 100644 index 0000000000..6468d1eecd --- /dev/null +++ b/src/systemd-remount-fs/remount-fs.c @@ -0,0 +1,155 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "exit-status.h" +#include "log.h" +#include "mount-setup.h" +#include "mount-util.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "strv.h" +#include "util.h" + +/* Goes through /etc/fstab and remounts all API file systems, applying + * options that are in /etc/fstab that systemd might not have + * respected */ + +int main(int argc, char *argv[]) { + _cleanup_hashmap_free_free_ Hashmap *pids = NULL; + _cleanup_endmntent_ FILE *f = NULL; + struct mntent* me; + int r; + + if (argc > 1) { + log_error("This program takes no argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + f = setmntent("/etc/fstab", "r"); + if (!f) { + if (errno == ENOENT) { + r = 0; + goto finish; + } + + r = log_error_errno(errno, "Failed to open /etc/fstab: %m"); + goto finish; + } + + pids = hashmap_new(NULL); + if (!pids) { + r = log_oom(); + goto finish; + } + + while ((me = getmntent(f))) { + pid_t pid; + int k; + char *s; + + /* Remount the root fs, /usr and all API VFS */ + if (!mount_point_is_api(me->mnt_dir) && + !path_equal(me->mnt_dir, "/") && + !path_equal(me->mnt_dir, "/usr")) + continue; + + log_debug("Remounting %s", me->mnt_dir); + + pid = fork(); + if (pid < 0) { + r = log_error_errno(errno, "Failed to fork: %m"); + goto finish; + } + + if (pid == 0) { + /* Child */ + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + (void) prctl(PR_SET_PDEATHSIG, SIGTERM); + + execv(MOUNT_PATH, STRV_MAKE(MOUNT_PATH, me->mnt_dir, "-o", "remount")); + + log_error_errno(errno, "Failed to execute " MOUNT_PATH ": %m"); + _exit(EXIT_FAILURE); + } + + /* Parent */ + + s = strdup(me->mnt_dir); + if (!s) { + r = log_oom(); + goto finish; + } + + k = hashmap_put(pids, PID_TO_PTR(pid), s); + if (k < 0) { + free(s); + r = log_oom(); + goto finish; + } + } + + r = 0; + while (!hashmap_isempty(pids)) { + siginfo_t si = {}; + char *s; + + if (waitid(P_ALL, 0, &si, WEXITED) < 0) { + + if (errno == EINTR) + continue; + + r = log_error_errno(errno, "waitid() failed: %m"); + goto finish; + } + + s = hashmap_remove(pids, PID_TO_PTR(si.si_pid)); + if (s) { + if (!is_clean_exit(si.si_code, si.si_status, NULL)) { + if (si.si_code == CLD_EXITED) + log_error(MOUNT_PATH " for %s exited with exit status %i.", s, si.si_status); + else + log_error(MOUNT_PATH " for %s terminated by signal %s.", s, signal_to_string(si.si_status)); + + r = -ENOEXEC; + } + + free(s); + } + } + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-reply-password/Makefile b/src/systemd-reply-password/Makefile new file mode 100644 index 0000000000..30283141aa --- /dev/null +++ b/src/systemd-reply-password/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_reply_password_SOURCES = \ + src/reply-password/reply-password.c + +systemd_reply_password_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-reply-password/reply-password.c b/src/systemd-reply-password/reply-password.c new file mode 100644 index 0000000000..e291758969 --- /dev/null +++ b/src/systemd-reply-password/reply-password.c @@ -0,0 +1,98 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include + +#include "fd-util.h" +#include "log.h" +#include "macro.h" +#include "string-util.h" +#include "util.h" + +static int send_on_socket(int fd, const char *socket_name, const void *packet, size_t size) { + union { + struct sockaddr sa; + struct sockaddr_un un; + } sa = { + .un.sun_family = AF_UNIX, + }; + + assert(fd >= 0); + assert(socket_name); + assert(packet); + + strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path)); + + if (sendto(fd, packet, size, MSG_NOSIGNAL, &sa.sa, offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)) < 0) + return log_error_errno(errno, "Failed to send: %m"); + + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_close_ int fd = -1; + char packet[LINE_MAX]; + size_t length; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + if (argc != 3) { + log_error("Wrong number of arguments."); + return EXIT_FAILURE; + } + + if (streq(argv[1], "1")) { + + packet[0] = '+'; + if (!fgets(packet+1, sizeof(packet)-1, stdin)) { + r = log_error_errno(errno, "Failed to read password: %m"); + goto finish; + } + + truncate_nl(packet+1); + length = 1 + strlen(packet+1) + 1; + } else if (streq(argv[1], "0")) { + packet[0] = '-'; + length = 1; + } else { + log_error("Invalid first argument %s", argv[1]); + r = -EINVAL; + goto finish; + } + + fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) { + r = log_error_errno(errno, "socket() failed: %m"); + goto finish; + } + + r = send_on_socket(fd, argv[2], packet, length); + +finish: + memory_erase(packet, sizeof(packet)); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-rfkill/Makefile b/src/systemd-rfkill/Makefile new file mode 100644 index 0000000000..331ecb7130 --- /dev/null +++ b/src/systemd-rfkill/Makefile @@ -0,0 +1,46 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_RFKILL),) +rootlibexec_PROGRAMS += \ + systemd-rfkill + +nodist_systemunit_DATA += \ + units/systemd-rfkill.service + +dist_systemunit_DATA += \ + units/systemd-rfkill.socket + +systemd_rfkill_SOURCES = \ + src/rfkill/rfkill.c + +systemd_rfkill_LDADD = \ + libshared.la +endif + +EXTRA_DIST += \ + units/systemd-rfkill.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-rfkill/rfkill.c b/src/systemd-rfkill/rfkill.c new file mode 100644 index 0000000000..f0b0ad9275 --- /dev/null +++ b/src/systemd-rfkill/rfkill.c @@ -0,0 +1,426 @@ +/*** + 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 . +***/ + +#include +#include + +#include "libudev.h" +#include + +#include "alloc-util.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "io-util.h" +#include "mkdir.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "string-table.h" +#include "string-util.h" +#include "udev-util.h" +#include "util.h" + +#define EXIT_USEC (5 * USEC_PER_SEC) + +static const char* const rfkill_type_table[NUM_RFKILL_TYPES] = { + [RFKILL_TYPE_ALL] = "all", + [RFKILL_TYPE_WLAN] = "wlan", + [RFKILL_TYPE_BLUETOOTH] = "bluetooth", + [RFKILL_TYPE_UWB] = "uwb", + [RFKILL_TYPE_WIMAX] = "wimax", + [RFKILL_TYPE_WWAN] = "wwan", + [RFKILL_TYPE_GPS] = "gps", + [RFKILL_TYPE_FM] = "fm", + [RFKILL_TYPE_NFC] = "nfc", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(rfkill_type, int); + +static int find_device( + struct udev *udev, + const struct rfkill_event *event, + struct udev_device **ret) { + + _cleanup_free_ char *sysname = NULL; + struct udev_device *device; + const char *name; + + assert(udev); + assert(event); + assert(ret); + + if (asprintf(&sysname, "rfkill%i", event->idx) < 0) + return log_oom(); + + device = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname); + if (!device) + return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m"); + + name = udev_device_get_sysattr_value(device, "name"); + if (!name) { + log_debug("Device has no name, ignoring."); + udev_device_unref(device); + return -ENOENT; + } + + log_debug("Operating on rfkill device '%s'.", name); + + *ret = device; + return 0; +} + +static int wait_for_initialized( + struct udev *udev, + struct udev_device *device, + struct udev_device **ret) { + + _cleanup_udev_monitor_unref_ struct udev_monitor *monitor = NULL; + struct udev_device *d; + const char *sysname; + int watch_fd, r; + + assert(udev); + assert(device); + assert(ret); + + if (udev_device_get_is_initialized(device) != 0) { + *ret = udev_device_ref(device); + return 0; + } + + assert_se(sysname = udev_device_get_sysname(device)); + + /* Wait until the device is initialized, so that we can get + * access to the ID_PATH property */ + + monitor = udev_monitor_new_from_netlink(udev, "udev"); + if (!monitor) + return log_error_errno(errno, "Failed to acquire monitor: %m"); + + r = udev_monitor_filter_add_match_subsystem_devtype(monitor, "rfkill", NULL); + if (r < 0) + return log_error_errno(r, "Failed to add rfkill udev match to monitor: %m"); + + r = udev_monitor_enable_receiving(monitor); + if (r < 0) + return log_error_errno(r, "Failed to enable udev receiving: %m"); + + watch_fd = udev_monitor_get_fd(monitor); + if (watch_fd < 0) + return log_error_errno(watch_fd, "Failed to get watch fd: %m"); + + /* Check again, maybe things changed */ + d = udev_device_new_from_subsystem_sysname(udev, "rfkill", sysname); + if (!d) + return log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to open device: %m"); + + if (udev_device_get_is_initialized(d) != 0) { + *ret = d; + return 0; + } + + for (;;) { + _cleanup_udev_device_unref_ struct udev_device *t = NULL; + + r = fd_wait_for_event(watch_fd, POLLIN, USEC_INFINITY); + if (r == -EINTR) + continue; + if (r < 0) + return log_error_errno(r, "Failed to watch udev monitor: %m"); + + t = udev_monitor_receive_device(monitor); + if (!t) + continue; + + if (streq_ptr(udev_device_get_sysname(device), sysname)) { + *ret = udev_device_ref(t); + return 0; + } + } +} + +static int determine_state_file( + struct udev *udev, + const struct rfkill_event *event, + struct udev_device *d, + char **ret) { + + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + const char *path_id, *type; + char *state_file; + int r; + + assert(event); + assert(d); + assert(ret); + + r = wait_for_initialized(udev, d, &device); + if (r < 0) + return r; + + assert_se(type = rfkill_type_to_string(event->type)); + + path_id = udev_device_get_property_value(device, "ID_PATH"); + if (path_id) { + _cleanup_free_ char *escaped_path_id = NULL; + + escaped_path_id = cescape(path_id); + if (!escaped_path_id) + return log_oom(); + + state_file = strjoin("/var/lib/systemd/rfkill/", escaped_path_id, ":", type, NULL); + } else + state_file = strjoin("/var/lib/systemd/rfkill/", type, NULL); + + if (!state_file) + return log_oom(); + + *ret = state_file; + return 0; +} + +static int load_state( + int rfkill_fd, + struct udev *udev, + const struct rfkill_event *event) { + + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + _cleanup_free_ char *state_file = NULL, *value = NULL; + struct rfkill_event we; + ssize_t l; + int b, r; + + assert(rfkill_fd >= 0); + assert(udev); + assert(event); + + if (shall_restore_state() == 0) + return 0; + + r = find_device(udev, event, &device); + if (r < 0) + return r; + + r = determine_state_file(udev, event, device, &state_file); + if (r < 0) + return r; + + r = read_one_line_file(state_file, &value); + if (r == -ENOENT) { + /* No state file? Then save the current state */ + + r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); + if (r < 0) + return log_error_errno(r, "Failed to write state file %s: %m", state_file); + + log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to read state file %s: %m", state_file); + + b = parse_boolean(value); + if (b < 0) + return log_error_errno(b, "Failed to parse state file %s: %m", state_file); + + we = (struct rfkill_event) { + .op = RFKILL_OP_CHANGE, + .idx = event->idx, + .soft = b, + }; + + l = write(rfkill_fd, &we, sizeof(we)); + if (l < 0) + return log_error_errno(errno, "Failed to restore rfkill state for %i: %m", event->idx); + if (l != sizeof(we)) { + log_error("Couldn't write rfkill event structure, too short."); + return -EIO; + } + + log_debug("Loaded state '%s' from %s.", one_zero(b), state_file); + return 0; +} + +static int save_state( + int rfkill_fd, + struct udev *udev, + const struct rfkill_event *event) { + + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + _cleanup_free_ char *state_file = NULL; + int r; + + assert(rfkill_fd >= 0); + assert(udev); + assert(event); + + r = find_device(udev, event, &device); + if (r < 0) + return r; + + r = determine_state_file(udev, event, device, &state_file); + if (r < 0) + return r; + + r = write_string_file(state_file, one_zero(event->soft), WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); + if (r < 0) + return log_error_errno(r, "Failed to write state file %s: %m", state_file); + + log_debug("Saved state '%s' to %s.", one_zero(event->soft), state_file); + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_udev_unref_ struct udev *udev = NULL; + _cleanup_close_ int rfkill_fd = -1; + bool ready = false; + int r, n; + + if (argc > 1) { + log_error("This program requires no arguments."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + udev = udev_new(); + if (!udev) { + r = log_oom(); + goto finish; + } + + r = mkdir_p("/var/lib/systemd/rfkill", 0755); + if (r < 0) { + log_error_errno(r, "Failed to create rfkill directory: %m"); + goto finish; + } + + n = sd_listen_fds(false); + if (n < 0) { + r = log_error_errno(n, "Failed to determine whether we got any file descriptors passed: %m"); + goto finish; + } + if (n > 1) { + log_error("Got too many file descriptors."); + r = -EINVAL; + goto finish; + } + + if (n == 0) { + rfkill_fd = open("/dev/rfkill", O_RDWR|O_CLOEXEC|O_NOCTTY|O_NONBLOCK); + if (rfkill_fd < 0) { + if (errno == ENOENT) { + log_debug_errno(errno, "Missing rfkill subsystem, or no device present, exiting."); + r = 0; + goto finish; + } + + r = log_error_errno(errno, "Failed to open /dev/rfkill: %m"); + goto finish; + } + } else { + rfkill_fd = SD_LISTEN_FDS_START; + + r = fd_nonblock(rfkill_fd, 1); + if (r < 0) { + log_error_errno(r, "Failed to make /dev/rfkill socket non-blocking: %m"); + goto finish; + } + } + + for (;;) { + struct rfkill_event event; + const char *type; + ssize_t l; + + l = read(rfkill_fd, &event, sizeof(event)); + if (l < 0) { + if (errno == EAGAIN) { + + if (!ready) { + /* Notify manager that we are + * now finished with + * processing whatever was + * queued */ + (void) sd_notify(false, "READY=1"); + ready = true; + } + + /* Hang around for a bit, maybe there's more coming */ + + r = fd_wait_for_event(rfkill_fd, POLLIN, EXIT_USEC); + if (r == -EINTR) + continue; + if (r < 0) { + log_error_errno(r, "Failed to poll() on device: %m"); + goto finish; + } + if (r > 0) + continue; + + log_debug("All events read and idle, exiting."); + break; + } + + log_error_errno(errno, "Failed to read from /dev/rfkill: %m"); + } + + if (l != RFKILL_EVENT_SIZE_V1) { + log_error("Read event structure of invalid size."); + r = -EIO; + goto finish; + } + + type = rfkill_type_to_string(event.type); + if (!type) { + log_debug("An rfkill device of unknown type %i discovered, ignoring.", event.type); + continue; + } + + switch (event.op) { + + case RFKILL_OP_ADD: + log_debug("A new rfkill device has been added with index %i and type %s.", event.idx, type); + (void) load_state(rfkill_fd, udev, &event); + break; + + case RFKILL_OP_DEL: + log_debug("An rfkill device has been removed with index %i and type %s", event.idx, type); + break; + + case RFKILL_OP_CHANGE: + log_debug("An rfkill device has changed state with index %i and type %s", event.idx, type); + (void) save_state(rfkill_fd, udev, &event); + break; + + default: + log_debug("Unknown event %i from /dev/rfkill for index %i and type %s, ignoring.", event.op, event.idx, type); + break; + } + } + + r = 0; + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-run/Makefile b/src/systemd-run/Makefile new file mode 100644 index 0000000000..afa2d0f34c --- /dev/null +++ b/src/systemd-run/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_run_SOURCES = \ + src/run/run.c + +systemd_run_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-run/run.c b/src/systemd-run/run.c new file mode 100644 index 0000000000..67706f84a0 --- /dev/null +++ b/src/systemd-run/run.c @@ -0,0 +1,1258 @@ +/*** + 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 . +***/ + +#include +#include + +#include +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "calendarspec.h" +#include "env-util.h" +#include "fd-util.h" +#include "formats-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "ptyfwd.h" +#include "signal-util.h" +#include "spawn-polkit-agent.h" +#include "strv.h" +#include "terminal-util.h" +#include "unit-name.h" +#include "user-util.h" + +static bool arg_ask_password = true; +static bool arg_scope = false; +static bool arg_remain_after_exit = false; +static bool arg_no_block = false; +static const char *arg_unit = NULL; +static const char *arg_description = NULL; +static const char *arg_slice = NULL; +static bool arg_send_sighup = false; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static const char *arg_host = NULL; +static bool arg_user = false; +static const char *arg_service_type = NULL; +static const char *arg_exec_user = NULL; +static const char *arg_exec_group = NULL; +static int arg_nice = 0; +static bool arg_nice_set = false; +static char **arg_environment = NULL; +static char **arg_property = NULL; +static bool arg_pty = false; +static usec_t arg_on_active = 0; +static usec_t arg_on_boot = 0; +static usec_t arg_on_startup = 0; +static usec_t arg_on_unit_active = 0; +static usec_t arg_on_unit_inactive = 0; +static const char *arg_on_calendar = NULL; +static char **arg_timer_property = NULL; +static bool arg_quiet = false; + +static void polkit_agent_open_if_enabled(void) { + + /* Open the polkit agent as a child process if necessary */ + if (!arg_ask_password) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + polkit_agent_open(); +} + +static void help(void) { + printf("%s [OPTIONS...] {COMMAND} [ARGS...]\n\n" + "Run the specified command in a transient scope or service or timer\n" + "unit. If timer option is specified and unit is exist which is\n" + "specified with --unit option then command can be omitted.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --no-ask-password Do not prompt for password\n" + " --user Run as user unit\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --scope Run this as scope rather than service\n" + " --unit=UNIT Run under the specified unit name\n" + " -p --property=NAME=VALUE Set unit property\n" + " --description=TEXT Description for unit\n" + " --slice=SLICE Run in the specified slice\n" + " --no-block Do not wait until operation finished\n" + " -r --remain-after-exit Leave service around until explicitly stopped\n" + " --send-sighup Send SIGHUP when terminating\n" + " --service-type=TYPE Service type\n" + " --uid=USER Run as system user\n" + " --gid=GROUP Run as system group\n" + " --nice=NICE Nice level\n" + " --setenv=NAME=VALUE Set environment\n" + " -t --pty Run service on pseudo tty\n" + " -q --quiet Suppress information messages during runtime\n\n" + "Timer options:\n\n" + " --on-active=SECONDS Run after SECONDS delay\n" + " --on-boot=SECONDS Run SECONDS after machine was booted up\n" + " --on-startup=SECONDS Run SECONDS after systemd activation\n" + " --on-unit-active=SECONDS Run SECONDS after the last activation\n" + " --on-unit-inactive=SECONDS Run SECONDS after the last deactivation\n" + " --on-calendar=SPEC Realtime timer\n" + " --timer-property=NAME=VALUE Set timer unit property\n", + program_invocation_short_name); +} + +static bool with_timer(void) { + return arg_on_active || arg_on_boot || arg_on_startup || arg_on_unit_active || arg_on_unit_inactive || arg_on_calendar; +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_ASK_PASSWORD, + ARG_USER, + ARG_SYSTEM, + ARG_SCOPE, + ARG_UNIT, + ARG_DESCRIPTION, + ARG_SLICE, + ARG_SEND_SIGHUP, + ARG_EXEC_USER, + ARG_EXEC_GROUP, + ARG_SERVICE_TYPE, + ARG_NICE, + ARG_SETENV, + ARG_TTY, + ARG_ON_ACTIVE, + ARG_ON_BOOT, + ARG_ON_STARTUP, + ARG_ON_UNIT_ACTIVE, + ARG_ON_UNIT_INACTIVE, + ARG_ON_CALENDAR, + ARG_TIMER_PROPERTY, + ARG_NO_BLOCK, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "user", no_argument, NULL, ARG_USER }, + { "system", no_argument, NULL, ARG_SYSTEM }, + { "scope", no_argument, NULL, ARG_SCOPE }, + { "unit", required_argument, NULL, ARG_UNIT }, + { "description", required_argument, NULL, ARG_DESCRIPTION }, + { "slice", required_argument, NULL, ARG_SLICE }, + { "remain-after-exit", no_argument, NULL, 'r' }, + { "send-sighup", no_argument, NULL, ARG_SEND_SIGHUP }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "service-type", required_argument, NULL, ARG_SERVICE_TYPE }, + { "uid", required_argument, NULL, ARG_EXEC_USER }, + { "gid", required_argument, NULL, ARG_EXEC_GROUP }, + { "nice", required_argument, NULL, ARG_NICE }, + { "setenv", required_argument, NULL, ARG_SETENV }, + { "property", required_argument, NULL, 'p' }, + { "tty", no_argument, NULL, 't' }, + { "quiet", no_argument, NULL, 'q' }, + { "on-active", required_argument, NULL, ARG_ON_ACTIVE }, + { "on-boot", required_argument, NULL, ARG_ON_BOOT }, + { "on-startup", required_argument, NULL, ARG_ON_STARTUP }, + { "on-unit-active", required_argument, NULL, ARG_ON_UNIT_ACTIVE }, + { "on-unit-inactive", required_argument, NULL, ARG_ON_UNIT_INACTIVE }, + { "on-calendar", required_argument, NULL, ARG_ON_CALENDAR }, + { "timer-property", required_argument, NULL, ARG_TIMER_PROPERTY }, + { "no-block", no_argument, NULL, ARG_NO_BLOCK }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + {}, + }; + + int r, c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "+hrH:M:p:tq", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case ARG_VERSION: + return version(); + + case ARG_USER: + arg_user = true; + break; + + case ARG_SYSTEM: + arg_user = false; + break; + + case ARG_SCOPE: + arg_scope = true; + break; + + case ARG_UNIT: + arg_unit = optarg; + break; + + case ARG_DESCRIPTION: + arg_description = optarg; + break; + + case ARG_SLICE: + arg_slice = optarg; + break; + + case ARG_SEND_SIGHUP: + arg_send_sighup = true; + break; + + case 'r': + arg_remain_after_exit = true; + break; + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_SERVICE_TYPE: + arg_service_type = optarg; + break; + + case ARG_EXEC_USER: + arg_exec_user = optarg; + break; + + case ARG_EXEC_GROUP: + arg_exec_group = optarg; + break; + + case ARG_NICE: + r = safe_atoi(optarg, &arg_nice); + if (r < 0 || arg_nice < PRIO_MIN || arg_nice >= PRIO_MAX) { + log_error("Failed to parse nice value"); + return -EINVAL; + } + + arg_nice_set = true; + break; + + case ARG_SETENV: + if (strv_extend(&arg_environment, optarg) < 0) + return log_oom(); + + break; + + case 'p': + if (strv_extend(&arg_property, optarg) < 0) + return log_oom(); + + break; + + case 't': + arg_pty = true; + break; + + case 'q': + arg_quiet = true; + break; + + case ARG_ON_ACTIVE: + + r = parse_sec(optarg, &arg_on_active); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_BOOT: + + r = parse_sec(optarg, &arg_on_boot); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_STARTUP: + + r = parse_sec(optarg, &arg_on_startup); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_UNIT_ACTIVE: + + r = parse_sec(optarg, &arg_on_unit_active); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_UNIT_INACTIVE: + + r = parse_sec(optarg, &arg_on_unit_inactive); + if (r < 0) { + log_error("Failed to parse timer value: %s", optarg); + return r; + } + + break; + + case ARG_ON_CALENDAR: { + CalendarSpec *spec = NULL; + + r = calendar_spec_from_string(optarg, &spec); + if (r < 0) { + log_error("Invalid calendar spec: %s", optarg); + return r; + } + + calendar_spec_free(spec); + arg_on_calendar = optarg; + break; + } + + case ARG_TIMER_PROPERTY: + + if (strv_extend(&arg_timer_property, optarg) < 0) + return log_oom(); + + break; + + case ARG_NO_BLOCK: + arg_no_block = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if ((optind >= argc) && (!arg_unit || !with_timer())) { + log_error("Command line to execute required."); + return -EINVAL; + } + + if (arg_user && arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Execution in user context is not supported on non-local systems."); + return -EINVAL; + } + + if (arg_scope && arg_transport != BUS_TRANSPORT_LOCAL) { + log_error("Scope execution is not supported on non-local systems."); + return -EINVAL; + } + + if (arg_scope && (arg_remain_after_exit || arg_service_type)) { + log_error("--remain-after-exit and --service-type= are not supported in --scope mode."); + return -EINVAL; + } + + if (arg_pty && (with_timer() || arg_scope)) { + log_error("--pty is not compatible in timer or --scope mode."); + return -EINVAL; + } + + if (arg_pty && arg_transport == BUS_TRANSPORT_REMOTE) { + log_error("--pty is only supported when connecting to the local system or containers."); + return -EINVAL; + } + + if (arg_scope && with_timer()) { + log_error("Timer options are not supported in --scope mode."); + return -EINVAL; + } + + if (arg_timer_property && !with_timer()) { + log_error("--timer-property= has no effect without any other timer options."); + return -EINVAL; + } + + return 1; +} + +static int transient_unit_set_properties(sd_bus_message *m, char **properties) { + char **i; + int r; + + r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description); + if (r < 0) + return r; + + STRV_FOREACH(i, properties) { + r = bus_append_unit_property_assignment(m, *i); + if (r < 0) + return r; + } + + return 0; +} + +static int transient_cgroup_set_properties(sd_bus_message *m) { + int r; + assert(m); + + if (!isempty(arg_slice)) { + _cleanup_free_ char *slice; + + r = unit_name_mangle_with_suffix(arg_slice, UNIT_NAME_NOGLOB, ".slice", &slice); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice); + if (r < 0) + return r; + } + + return 0; +} + +static int transient_kill_set_properties(sd_bus_message *m) { + assert(m); + + if (arg_send_sighup) + return sd_bus_message_append(m, "(sv)", "SendSIGHUP", "b", arg_send_sighup); + else + return 0; +} + +static int transient_service_set_properties(sd_bus_message *m, char **argv, const char *pty_path) { + int r; + + assert(m); + + r = transient_unit_set_properties(m, arg_property); + if (r < 0) + return r; + + r = transient_kill_set_properties(m); + if (r < 0) + return r; + + r = transient_cgroup_set_properties(m); + if (r < 0) + return r; + + if (arg_remain_after_exit) { + r = sd_bus_message_append(m, "(sv)", "RemainAfterExit", "b", arg_remain_after_exit); + if (r < 0) + return r; + } + + if (arg_service_type) { + r = sd_bus_message_append(m, "(sv)", "Type", "s", arg_service_type); + if (r < 0) + return r; + } + + if (arg_exec_user) { + r = sd_bus_message_append(m, "(sv)", "User", "s", arg_exec_user); + if (r < 0) + return r; + } + + if (arg_exec_group) { + r = sd_bus_message_append(m, "(sv)", "Group", "s", arg_exec_group); + if (r < 0) + return r; + } + + if (arg_nice_set) { + r = sd_bus_message_append(m, "(sv)", "Nice", "i", arg_nice); + if (r < 0) + return r; + } + + if (pty_path) { + const char *e; + + r = sd_bus_message_append(m, + "(sv)(sv)(sv)(sv)", + "StandardInput", "s", "tty", + "StandardOutput", "s", "tty", + "StandardError", "s", "tty", + "TTYPath", "s", pty_path); + if (r < 0) + return r; + + e = getenv("TERM"); + if (e) { + char *n; + + n = strjoina("TERM=", e); + r = sd_bus_message_append(m, + "(sv)", + "Environment", "as", 1, n); + if (r < 0) + return r; + } + } + + if (!strv_isempty(arg_environment)) { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", "Environment"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'v', "as"); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(m, arg_environment); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + /* Exec container */ + { + r = sd_bus_message_open_container(m, 'r', "sv"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", "ExecStart"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'v', "a(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'a', "(sasb)"); + if (r < 0) + return r; + + r = sd_bus_message_open_container(m, 'r', "sasb"); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "s", argv[0]); + if (r < 0) + return r; + + r = sd_bus_message_append_strv(m, argv); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "b", false); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + + r = sd_bus_message_close_container(m); + if (r < 0) + return r; + } + + return 0; +} + +static int transient_scope_set_properties(sd_bus_message *m) { + int r; + + assert(m); + + r = transient_unit_set_properties(m, arg_property); + if (r < 0) + return r; + + r = transient_kill_set_properties(m); + if (r < 0) + return r; + + r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, (uint32_t) getpid()); + if (r < 0) + return r; + + return 0; +} + +static int transient_timer_set_properties(sd_bus_message *m) { + int r; + + assert(m); + + r = transient_unit_set_properties(m, arg_timer_property); + if (r < 0) + return r; + + /* Automatically clean up our transient timers */ + r = sd_bus_message_append(m, "(sv)", "RemainAfterElapse", "b", false); + if (r < 0) + return r; + + if (arg_on_active) { + r = sd_bus_message_append(m, "(sv)", "OnActiveSec", "t", arg_on_active); + if (r < 0) + return r; + } + + if (arg_on_boot) { + r = sd_bus_message_append(m, "(sv)", "OnBootSec", "t", arg_on_boot); + if (r < 0) + return r; + } + + if (arg_on_startup) { + r = sd_bus_message_append(m, "(sv)", "OnStartupSec", "t", arg_on_startup); + if (r < 0) + return r; + } + + if (arg_on_unit_active) { + r = sd_bus_message_append(m, "(sv)", "OnUnitActiveSec", "t", arg_on_unit_active); + if (r < 0) + return r; + } + + if (arg_on_unit_inactive) { + r = sd_bus_message_append(m, "(sv)", "OnUnitInactiveSec", "t", arg_on_unit_inactive); + if (r < 0) + return r; + } + + if (arg_on_calendar) { + r = sd_bus_message_append(m, "(sv)", "OnCalendar", "s", arg_on_calendar); + if (r < 0) + return r; + } + + return 0; +} + +static int make_unit_name(sd_bus *bus, UnitType t, char **ret) { + const char *unique, *id; + char *p; + int r; + + assert(bus); + assert(t >= 0); + assert(t < _UNIT_TYPE_MAX); + + r = sd_bus_get_unique_name(bus, &unique); + if (r < 0) { + sd_id128_t rnd; + + /* We couldn't get the unique name, which is a pretty + * common case if we are connected to systemd + * directly. In that case, just pick a random uuid as + * name */ + + r = sd_id128_randomize(&rnd); + if (r < 0) + return log_error_errno(r, "Failed to generate random run unit name: %m"); + + if (asprintf(ret, "run-r" SD_ID128_FORMAT_STR ".%s", SD_ID128_FORMAT_VAL(rnd), unit_type_to_string(t)) < 0) + return log_oom(); + + return 0; + } + + /* We managed to get the unique name, then let's use that to + * name our transient units. */ + + id = startswith(unique, ":1."); + if (!id) { + log_error("Unique name %s has unexpected format.", unique); + return -EINVAL; + } + + p = strjoin("run-u", id, ".", unit_type_to_string(t), NULL); + if (!p) + return log_oom(); + + *ret = p; + return 0; +} + +static int start_transient_service( + sd_bus *bus, + char **argv) { + + _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; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_free_ char *service = NULL, *pty_path = NULL; + _cleanup_close_ int master = -1; + int r; + + assert(bus); + assert(argv); + + if (arg_pty) { + + if (arg_transport == BUS_TRANSPORT_LOCAL) { + master = posix_openpt(O_RDWR|O_NOCTTY|O_CLOEXEC|O_NDELAY); + if (master < 0) + return log_error_errno(errno, "Failed to acquire pseudo tty: %m"); + + r = ptsname_malloc(master, &pty_path); + if (r < 0) + return log_error_errno(r, "Failed to determine tty name: %m"); + + if (unlockpt(master) < 0) + return log_error_errno(errno, "Failed to unlock tty: %m"); + + } else if (arg_transport == BUS_TRANSPORT_MACHINE) { + _cleanup_(sd_bus_unrefp) sd_bus *system_bus = NULL; + const char *s; + + r = sd_bus_default_system(&system_bus); + if (r < 0) + return log_error_errno(r, "Failed to connect to system bus: %m"); + + r = sd_bus_call_method(system_bus, + "org.freedesktop.machine1", + "/org/freedesktop/machine1", + "org.freedesktop.machine1.Manager", + "OpenMachinePTY", + &error, + &reply, + "s", arg_host); + if (r < 0) { + log_error("Failed to get machine PTY: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "hs", &master, &s); + if (r < 0) + return bus_log_parse_error(r); + + reply = sd_bus_message_unref(reply); + + master = fcntl(master, F_DUPFD_CLOEXEC, 3); + if (master < 0) + return log_error_errno(errno, "Failed to duplicate master fd: %m"); + + pty_path = strdup(s); + if (!pty_path) + return log_oom(); + } else + assert_not_reached("Can't allocate tty via ssh"); + } + + if (!arg_no_block) { + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_error_errno(r, "Could not watch jobs: %m"); + } + + if (arg_unit) { + r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".service", &service); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + } else { + r = make_unit_name(bus, UNIT_SERVICE, &service); + if (r < 0) + return r; + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + /* Name and mode */ + r = sd_bus_message_append(m, "ss", service, "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_service_set_properties(m, argv, pty_path); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + /* Auxiliary units */ + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) + return bus_log_create_error(r); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) + return log_error_errno(r, "Failed to start transient service unit: %s", bus_error_message(&error, r)); + + if (w) { + const char *object; + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, arg_quiet); + if (r < 0) + return r; + } + + if (master >= 0) { + _cleanup_(pty_forward_freep) PTYForward *forward = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + char last_char = 0; + + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get event loop: %m"); + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGWINCH, SIGTERM, SIGINT, -1) >= 0); + + (void) sd_event_add_signal(event, NULL, SIGINT, NULL, NULL); + (void) sd_event_add_signal(event, NULL, SIGTERM, NULL, NULL); + + if (!arg_quiet) + log_info("Running as unit %s.\nPress ^] three times within 1s to disconnect TTY.", service); + + r = pty_forward_new(event, master, PTY_FORWARD_IGNORE_INITIAL_VHANGUP, &forward); + if (r < 0) + return log_error_errno(r, "Failed to create PTY forwarder: %m"); + + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Failed to run event loop: %m"); + + pty_forward_get_last_char(forward, &last_char); + + forward = pty_forward_free(forward); + + if (!arg_quiet && last_char != '\n') + fputc('\n', stdout); + + } else if (!arg_quiet) + log_info("Running as unit %s.", service); + + return 0; +} + +static int start_transient_scope( + sd_bus *bus, + char **argv) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_strv_free_ char **env = NULL, **user_env = NULL; + _cleanup_free_ char *scope = NULL; + const char *object = NULL; + int r; + + assert(bus); + assert(argv); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_oom(); + + if (arg_unit) { + r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".scope", &scope); + if (r < 0) + return log_error_errno(r, "Failed to mangle scope name: %m"); + } else { + r = make_unit_name(bus, UNIT_SCOPE, &scope); + if (r < 0) + return r; + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + /* Name and Mode */ + r = sd_bus_message_append(m, "ss", scope, "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_scope_set_properties(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + /* Auxiliary units */ + r = sd_bus_message_append(m, "a(sa(sv))", 0); + if (r < 0) + return bus_log_create_error(r); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to start transient scope unit: %s", bus_error_message(&error, -r)); + return r; + } + + if (arg_nice_set) { + if (setpriority(PRIO_PROCESS, 0, arg_nice) < 0) + return log_error_errno(errno, "Failed to set nice level: %m"); + } + + if (arg_exec_group) { + gid_t gid; + + r = get_group_creds(&arg_exec_group, &gid); + if (r < 0) + return log_error_errno(r, "Failed to resolve group %s: %m", arg_exec_group); + + if (setresgid(gid, gid, gid) < 0) + return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); + } + + if (arg_exec_user) { + const char *home, *shell; + uid_t uid; + gid_t gid; + + r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell); + if (r < 0) + return log_error_errno(r, "Failed to resolve user %s: %m", arg_exec_user); + + r = strv_extendf(&user_env, "HOME=%s", home); + if (r < 0) + return log_oom(); + + r = strv_extendf(&user_env, "SHELL=%s", shell); + if (r < 0) + return log_oom(); + + r = strv_extendf(&user_env, "USER=%s", arg_exec_user); + if (r < 0) + return log_oom(); + + r = strv_extendf(&user_env, "LOGNAME=%s", arg_exec_user); + if (r < 0) + return log_oom(); + + if (!arg_exec_group) { + if (setresgid(gid, gid, gid) < 0) + return log_error_errno(errno, "Failed to change GID to " GID_FMT ": %m", gid); + } + + if (setresuid(uid, uid, uid) < 0) + return log_error_errno(errno, "Failed to change UID to " UID_FMT ": %m", uid); + } + + env = strv_env_merge(3, environ, user_env, arg_environment); + if (!env) + return log_oom(); + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, arg_quiet); + if (r < 0) + return r; + + if (!arg_quiet) + log_info("Running scope as unit %s.", scope); + + execvpe(argv[0], argv, env); + + return log_error_errno(errno, "Failed to execute: %m"); +} + +static int start_transient_timer( + sd_bus *bus, + char **argv) { + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL; + _cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL; + _cleanup_free_ char *timer = NULL, *service = NULL; + const char *object = NULL; + int r; + + assert(bus); + assert(argv); + + r = bus_wait_for_jobs_new(bus, &w); + if (r < 0) + return log_oom(); + + if (arg_unit) { + switch (unit_name_to_type(arg_unit)) { + + case UNIT_SERVICE: + service = strdup(arg_unit); + if (!service) + return log_oom(); + + r = unit_name_change_suffix(service, ".timer", &timer); + if (r < 0) + return log_error_errno(r, "Failed to change unit suffix: %m"); + break; + + case UNIT_TIMER: + timer = strdup(arg_unit); + if (!timer) + return log_oom(); + + r = unit_name_change_suffix(timer, ".service", &service); + if (r < 0) + return log_error_errno(r, "Failed to change unit suffix: %m"); + break; + + default: + r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".service", &service); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + r = unit_name_mangle_with_suffix(arg_unit, UNIT_NAME_NOGLOB, ".timer", &timer); + if (r < 0) + return log_error_errno(r, "Failed to mangle unit name: %m"); + + break; + } + } else { + r = make_unit_name(bus, UNIT_SERVICE, &service); + if (r < 0) + return r; + + r = unit_name_change_suffix(service, ".timer", &timer); + if (r < 0) + return log_error_errno(r, "Failed to change unit suffix: %m"); + } + + r = sd_bus_message_new_method_call( + bus, + &m, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "StartTransientUnit"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_set_allow_interactive_authorization(m, arg_ask_password); + if (r < 0) + return bus_log_create_error(r); + + /* Name and Mode */ + r = sd_bus_message_append(m, "ss", timer, "fail"); + if (r < 0) + return bus_log_create_error(r); + + /* Properties */ + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_timer_set_properties(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sa(sv))"); + if (r < 0) + return bus_log_create_error(r); + + if (argv[0]) { + r = sd_bus_message_open_container(m, 'r', "sa(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_append(m, "s", service); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_open_container(m, 'a', "(sv)"); + if (r < 0) + return bus_log_create_error(r); + + r = transient_service_set_properties(m, argv, NULL); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + } + + r = sd_bus_message_close_container(m); + if (r < 0) + return bus_log_create_error(r); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call(bus, m, 0, &error, &reply); + if (r < 0) { + log_error("Failed to start transient timer unit: %s", bus_error_message(&error, -r)); + return r; + } + + r = sd_bus_message_read(reply, "o", &object); + if (r < 0) + return bus_log_parse_error(r); + + r = bus_wait_for_jobs_one(w, object, arg_quiet); + if (r < 0) + return r; + + log_info("Running timer as unit %s.", timer); + if (argv[0]) + log_info("Will run service as unit %s.", service); + + return 0; +} + +int main(int argc, char* argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_free_ char *description = NULL, *command = NULL; + int r; + + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (argc > optind && arg_transport == BUS_TRANSPORT_LOCAL) { + /* Patch in an absolute path */ + + r = find_binary(argv[optind], &command); + if (r < 0) { + log_error_errno(r, "Failed to find executable %s: %m", argv[optind]); + goto finish; + } + + argv[optind] = command; + } + + if (!arg_description) { + description = strv_join(argv + optind, " "); + if (!description) { + r = log_oom(); + goto finish; + } + + if (arg_unit && isempty(description)) { + r = free_and_strdup(&description, arg_unit); + if (r < 0) + goto finish; + } + + arg_description = description; + } + + r = bus_connect_transport_systemd(arg_transport, arg_host, arg_user, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + if (arg_scope) + r = start_transient_scope(bus, argv + optind); + else if (with_timer()) + r = start_transient_timer(bus, argv + optind); + else + r = start_transient_service(bus, argv + optind); + +finish: + strv_free(arg_environment); + strv_free(arg_property); + strv_free(arg_timer_property); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-shutdown/Makefile b/src/systemd-shutdown/Makefile new file mode 100644 index 0000000000..0695f3927d --- /dev/null +++ b/src/systemd-shutdown/Makefile @@ -0,0 +1,38 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_shutdown_SOURCES = \ + src/core/umount.c \ + src/core/umount.h \ + src/core/shutdown.c \ + src/core/mount-setup.c \ + src/core/mount-setup.h \ + src/core/killall.h \ + src/core/killall.c + +systemd_shutdown_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-timedate/.gitignore b/src/systemd-timedate/.gitignore new file mode 100644 index 0000000000..48757f0968 --- /dev/null +++ b/src/systemd-timedate/.gitignore @@ -0,0 +1 @@ +org.freedesktop.timedate1.policy diff --git a/src/systemd-timedate/Makefile b/src/systemd-timedate/Makefile new file mode 100644 index 0000000000..ea9b36efbc --- /dev/null +++ b/src/systemd-timedate/Makefile @@ -0,0 +1,79 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_TIMEDATED),) +systemd_timedated_SOURCES = \ + src/timedate/timedated.c + +systemd_timedated_LDADD = \ + libshared.la + +rootlibexec_PROGRAMS += \ + systemd-timedated + +dist_dbussystemservice_DATA += \ + src/timedate/org.freedesktop.timedate1.service + +dist_dbuspolicy_DATA += \ + src/timedate/org.freedesktop.timedate1.conf + +nodist_systemunit_DATA += \ + units/systemd-timedated.service + +dist_systemunit_DATA_busnames += \ + units/org.freedesktop.timedate1.busname + +polkitpolicy_files += \ + src/timedate/org.freedesktop.timedate1.policy + +SYSTEM_UNIT_ALIASES += \ + systemd-timedated.service dbus-org.freedesktop.timedate1.service + +BUSNAMES_TARGET_WANTS += \ + org.freedesktop.timedate1.busname + +timedatectl_SOURCES = \ + src/timedate/timedatectl.c + +timedatectl_LDADD = \ + libshared.la + +bin_PROGRAMS += \ + timedatectl + +dist_bashcompletion_data += \ + shell-completion/bash/timedatectl + +dist_zshcompletion_data += \ + shell-completion/zsh/_timedatectl +endif + +polkitpolicy_in_files += \ + src/timedate/org.freedesktop.timedate1.policy.in + +EXTRA_DIST += \ + units/systemd-timedated.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-timedate/org.freedesktop.timedate1.conf b/src/systemd-timedate/org.freedesktop.timedate1.conf new file mode 100644 index 0000000000..36557d5841 --- /dev/null +++ b/src/systemd-timedate/org.freedesktop.timedate1.conf @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + diff --git a/src/systemd-timedate/org.freedesktop.timedate1.policy.in b/src/systemd-timedate/org.freedesktop.timedate1.policy.in new file mode 100644 index 0000000000..aa30b70831 --- /dev/null +++ b/src/systemd-timedate/org.freedesktop.timedate1.policy.in @@ -0,0 +1,62 @@ + + + + + + + + The systemd Project + http://www.freedesktop.org/wiki/Software/systemd + + + <_description>Set system time + <_message>Authentication is required to set the system time. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + org.freedesktop.timedate1.set-timezone org.freedesktop.timedate1.set-ntp + + + + <_description>Set system timezone + <_message>Authentication is required to set the system timezone. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Set RTC to local timezone or UTC + <_message>Authentication is required to control whether + the RTC stores the local or UTC time. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + + <_description>Turn network time synchronization on or off + <_message>Authentication is required to control whether + network time synchronization shall be enabled. + + auth_admin_keep + auth_admin_keep + auth_admin_keep + + + + diff --git a/src/systemd-timedate/org.freedesktop.timedate1.service b/src/systemd-timedate/org.freedesktop.timedate1.service new file mode 100644 index 0000000000..875f4bec78 --- /dev/null +++ b/src/systemd-timedate/org.freedesktop.timedate1.service @@ -0,0 +1,12 @@ +# 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. + +[D-BUS Service] +Name=org.freedesktop.timedate1 +Exec=/bin/false +User=root +SystemdService=dbus-org.freedesktop.timedate1.service diff --git a/src/systemd-timedate/timedatectl.c b/src/systemd-timedate/timedatectl.c new file mode 100644 index 0000000000..28546a0ad1 --- /dev/null +++ b/src/systemd-timedate/timedatectl.c @@ -0,0 +1,514 @@ +/*** + This file is part of systemd. + + Copyright 2012 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 . +***/ + +#include +#include +#include +#include + +#include + +#include "bus-error.h" +#include "bus-util.h" +#include "pager.h" +#include "parse-util.h" +#include "spawn-polkit-agent.h" +#include "strv.h" +#include "terminal-util.h" +#include "util.h" + +static bool arg_no_pager = false; +static bool arg_ask_password = true; +static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; +static char *arg_host = NULL; +static bool arg_adjust_system_clock = false; + +static void pager_open_if_enabled(void) { + + if (arg_no_pager) + return; + + pager_open(false); +} + +static void polkit_agent_open_if_enabled(void) { + + /* Open the polkit agent as a child process if necessary */ + if (!arg_ask_password) + return; + + if (arg_transport != BUS_TRANSPORT_LOCAL) + return; + + polkit_agent_open(); +} + +typedef struct StatusInfo { + usec_t time; + char *timezone; + + usec_t rtc_time; + bool rtc_local; + + bool ntp_enabled; + bool ntp_capable; + bool ntp_synced; +} StatusInfo; + +static void status_info_clear(StatusInfo *info) { + if (info) { + free(info->timezone); + zero(*info); + } +} + +static void print_status_info(const StatusInfo *i) { + char a[FORMAT_TIMESTAMP_MAX]; + struct tm tm; + time_t sec; + bool have_time = false; + const char *old_tz = NULL, *tz; + int r; + + assert(i); + + /* Save the old $TZ */ + tz = getenv("TZ"); + if (tz) + old_tz = strdupa(tz); + + /* Set the new $TZ */ + if (setenv("TZ", isempty(i->timezone) ? "UTC" : i->timezone, true) < 0) + log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m"); + else + tzset(); + + if (i->time != 0) { + sec = (time_t) (i->time / USEC_PER_SEC); + have_time = true; + } else if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) { + sec = time(NULL); + have_time = true; + } else + log_warning("Could not get time from timedated and not operating locally, ignoring."); + + if (have_time) { + xstrftime(a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)); + printf(" Local time: %.*s\n", (int) sizeof(a), a); + + xstrftime(a, "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)); + printf(" Universal time: %.*s\n", (int) sizeof(a), a); + } else { + printf(" Local time: %s\n", "n/a"); + printf(" Universal time: %s\n", "n/a"); + } + + if (i->rtc_time > 0) { + time_t rtc_sec; + + rtc_sec = (time_t) (i->rtc_time / USEC_PER_SEC); + xstrftime(a, "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm)); + printf(" RTC time: %.*s\n", (int) sizeof(a), a); + } else + printf(" RTC time: %s\n", "n/a"); + + if (have_time) + xstrftime(a, "%Z, %z", localtime_r(&sec, &tm)); + + /* Restore the $TZ */ + if (old_tz) + r = setenv("TZ", old_tz, true); + else + r = unsetenv("TZ"); + if (r < 0) + log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m"); + else + tzset(); + + printf(" Time zone: %s (%.*s)\n" + " Network time on: %s\n" + "NTP synchronized: %s\n" + " RTC in local TZ: %s\n", + strna(i->timezone), (int) sizeof(a), have_time ? a : "n/a", + i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a", + yes_no(i->ntp_synced), + yes_no(i->rtc_local)); + + if (i->rtc_local) + fputs("\n" ANSI_HIGHLIGHT + "Warning: The system is configured to read the RTC time in the local time zone.\n" + " This mode can not be fully supported. It will create various problems\n" + " with time zone changes and daylight saving time adjustments. The RTC\n" + " time is never updated, it relies on external facilities to maintain it.\n" + " If at all possible, use RTC in UTC by calling\n" + " 'timedatectl set-local-rtc 0'." ANSI_NORMAL "\n", stdout); +} + +static int show_status(sd_bus *bus, char **args, unsigned n) { + _cleanup_(status_info_clear) StatusInfo info = {}; + static const struct bus_properties_map map[] = { + { "Timezone", "s", NULL, offsetof(StatusInfo, timezone) }, + { "LocalRTC", "b", NULL, offsetof(StatusInfo, rtc_local) }, + { "NTP", "b", NULL, offsetof(StatusInfo, ntp_enabled) }, + { "CanNTP", "b", NULL, offsetof(StatusInfo, ntp_capable) }, + { "NTPSynchronized", "b", NULL, offsetof(StatusInfo, ntp_synced) }, + { "TimeUSec", "t", NULL, offsetof(StatusInfo, time) }, + { "RTCTimeUSec", "t", NULL, offsetof(StatusInfo, rtc_time) }, + {} + }; + int r; + + assert(bus); + + r = bus_map_all_properties(bus, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + map, + &info); + if (r < 0) + return log_error_errno(r, "Failed to query server: %m"); + + print_status_info(&info); + + return r; +} + +static int set_time(sd_bus *bus, char **args, unsigned n) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + bool relative = false, interactive = arg_ask_password; + usec_t t; + int r; + + assert(args); + assert(n == 2); + + polkit_agent_open_if_enabled(); + + r = parse_timestamp(args[1], &t); + if (r < 0) { + log_error("Failed to parse time specification: %s", args[1]); + return r; + } + + r = sd_bus_call_method(bus, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "SetTime", + &error, + NULL, + "xbb", (int64_t)t, relative, interactive); + if (r < 0) + log_error("Failed to set time: %s", bus_error_message(&error, -r)); + + return r; +} + +static int set_timezone(sd_bus *bus, char **args, unsigned n) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + + assert(args); + assert(n == 2); + + polkit_agent_open_if_enabled(); + + r = sd_bus_call_method(bus, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "SetTimezone", + &error, + NULL, + "sb", args[1], arg_ask_password); + if (r < 0) + log_error("Failed to set time zone: %s", bus_error_message(&error, -r)); + + return r; +} + +static int set_local_rtc(sd_bus *bus, char **args, unsigned n) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r, b; + + assert(args); + assert(n == 2); + + polkit_agent_open_if_enabled(); + + b = parse_boolean(args[1]); + if (b < 0) { + log_error("Failed to parse local RTC setting: %s", args[1]); + return b; + } + + r = sd_bus_call_method(bus, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "SetLocalRTC", + &error, + NULL, + "bbb", b, arg_adjust_system_clock, arg_ask_password); + if (r < 0) + log_error("Failed to set local RTC: %s", bus_error_message(&error, -r)); + + return r; +} + +static int set_ntp(sd_bus *bus, char **args, unsigned n) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int b, r; + + assert(args); + assert(n == 2); + + polkit_agent_open_if_enabled(); + + b = parse_boolean(args[1]); + if (b < 0) { + log_error("Failed to parse NTP setting: %s", args[1]); + return b; + } + + r = sd_bus_call_method(bus, + "org.freedesktop.timedate1", + "/org/freedesktop/timedate1", + "org.freedesktop.timedate1", + "SetNTP", + &error, + NULL, + "bb", b, arg_ask_password); + if (r < 0) + log_error("Failed to set ntp: %s", bus_error_message(&error, -r)); + + return r; +} + +static int list_timezones(sd_bus *bus, char **args, unsigned n) { + _cleanup_strv_free_ char **zones = NULL; + int r; + + assert(args); + assert(n == 1); + + r = get_timezones(&zones); + if (r < 0) + return log_error_errno(r, "Failed to read list of time zones: %m"); + + pager_open_if_enabled(); + strv_print(zones); + + return 0; +} + +static void help(void) { + printf("%s [OPTIONS...] COMMAND ...\n\n" + "Query or change system time and date settings.\n\n" + " -h --help Show this help message\n" + " --version Show package version\n" + " --no-pager Do not pipe output into a pager\n" + " --no-ask-password Do not prompt for password\n" + " -H --host=[USER@]HOST Operate on remote host\n" + " -M --machine=CONTAINER Operate on local container\n" + " --adjust-system-clock Adjust system clock when changing local RTC mode\n\n" + "Commands:\n" + " status Show current time settings\n" + " set-time TIME Set system time\n" + " set-timezone ZONE Set system time zone\n" + " list-timezones Show known time zones\n" + " set-local-rtc BOOL Control whether RTC is in local time\n" + " set-ntp BOOL Enable or disable network time synchronization\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_NO_PAGER, + ARG_ADJUST_SYSTEM_CLOCK, + ARG_NO_ASK_PASSWORD + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "host", required_argument, NULL, 'H' }, + { "machine", required_argument, NULL, 'M' }, + { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, + { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case 'H': + arg_transport = BUS_TRANSPORT_REMOTE; + arg_host = optarg; + break; + + case 'M': + arg_transport = BUS_TRANSPORT_MACHINE; + arg_host = optarg; + break; + + case ARG_NO_ASK_PASSWORD: + arg_ask_password = false; + break; + + case ARG_ADJUST_SYSTEM_CLOCK: + arg_adjust_system_clock = true; + break; + + case ARG_NO_PAGER: + arg_no_pager = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + return 1; +} + +static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) { + + static const struct { + const char* verb; + const enum { + MORE, + LESS, + EQUAL + } argc_cmp; + const int argc; + int (* const dispatch)(sd_bus *bus, char **args, unsigned n); + } verbs[] = { + { "status", LESS, 1, show_status }, + { "set-time", EQUAL, 2, set_time }, + { "set-timezone", EQUAL, 2, set_timezone }, + { "list-timezones", EQUAL, 1, list_timezones }, + { "set-local-rtc", EQUAL, 2, set_local_rtc }, + { "set-ntp", EQUAL, 2, set_ntp, }, + }; + + int left; + unsigned i; + + assert(argc >= 0); + assert(argv); + + left = argc - optind; + + if (left <= 0) + /* Special rule: no arguments means "status" */ + i = 0; + else { + if (streq(argv[optind], "help")) { + help(); + return 0; + } + + for (i = 0; i < ELEMENTSOF(verbs); i++) + if (streq(argv[optind], verbs[i].verb)) + break; + + if (i >= ELEMENTSOF(verbs)) { + log_error("Unknown operation %s", argv[optind]); + return -EINVAL; + } + } + + switch (verbs[i].argc_cmp) { + + case EQUAL: + if (left != verbs[i].argc) { + log_error("Invalid number of arguments."); + return -EINVAL; + } + + break; + + case MORE: + if (left < verbs[i].argc) { + log_error("Too few arguments."); + return -EINVAL; + } + + break; + + case LESS: + if (left > verbs[i].argc) { + log_error("Too many arguments."); + return -EINVAL; + } + + break; + + default: + assert_not_reached("Unknown comparison operator."); + } + + return verbs[i].dispatch(bus, argv + optind, left); +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + setlocale(LC_ALL, ""); + log_parse_environment(); + log_open(); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + r = bus_connect_transport(arg_transport, arg_host, false, &bus); + if (r < 0) { + log_error_errno(r, "Failed to create bus connection: %m"); + goto finish; + } + + r = timedatectl_main(bus, argc, argv); + +finish: + pager_close(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-timedate/timedated.c b/src/systemd-timedate/timedated.c new file mode 100644 index 0000000000..f5ca319640 --- /dev/null +++ b/src/systemd-timedate/timedated.c @@ -0,0 +1,733 @@ +/*** + This file is part of systemd. + + Copyright 2011 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 . +***/ + +#include +#include +#include + +#include +#include +#include + +#include "alloc-util.h" +#include "bus-common-errors.h" +#include "bus-error.h" +#include "bus-util.h" +#include "clock-util.h" +#include "def.h" +#include "fileio-label.h" +#include "fs-util.h" +#include "path-util.h" +#include "selinux-util.h" +#include "strv.h" +#include "user-util.h" +#include "util.h" + +#define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n" +#define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n" + +static BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map timedated_errors[] = { + SD_BUS_ERROR_MAP("org.freedesktop.timedate1.NoNTPSupport", EOPNOTSUPP), + SD_BUS_ERROR_MAP_END +}; + +typedef struct Context { + char *zone; + bool local_rtc; + bool can_ntp; + bool use_ntp; + Hashmap *polkit_registry; +} Context; + +static void context_free(Context *c) { + assert(c); + + free(c->zone); + bus_verify_polkit_async_registry_free(c->polkit_registry); +} + +static int context_read_data(Context *c) { + _cleanup_free_ char *t = NULL; + int r; + + assert(c); + + r = get_timezone(&t); + if (r == -EINVAL) + log_warning_errno(r, "/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/."); + else if (r < 0) + log_warning_errno(r, "Failed to get target of /etc/localtime: %m"); + + free(c->zone); + c->zone = t; + t = NULL; + + c->local_rtc = clock_is_localtime() > 0; + + return 0; +} + +static int context_write_data_timezone(Context *c) { + _cleanup_free_ char *p = NULL; + int r = 0; + + assert(c); + + if (isempty(c->zone)) { + if (unlink("/etc/localtime") < 0 && errno != ENOENT) + r = -errno; + + return r; + } + + p = strappend("../usr/share/zoneinfo/", c->zone); + if (!p) + return log_oom(); + + r = symlink_atomic(p, "/etc/localtime"); + if (r < 0) + return r; + + return 0; +} + +static int context_write_data_local_rtc(Context *c) { + int r; + _cleanup_free_ char *s = NULL, *w = NULL; + + assert(c); + + r = read_full_file("/etc/adjtime", &s, NULL); + if (r < 0) { + if (r != -ENOENT) + return r; + + if (!c->local_rtc) + return 0; + + w = strdup(NULL_ADJTIME_LOCAL); + if (!w) + return -ENOMEM; + } else { + char *p, *e; + size_t a, b; + + p = strchr(s, '\n'); + if (!p) + return -EIO; + + p = strchr(p+1, '\n'); + if (!p) + return -EIO; + + p++; + e = strchr(p, '\n'); + if (!e) + return -EIO; + + a = p - s; + b = strlen(e); + + w = new(char, a + (c->local_rtc ? 5 : 3) + b + 1); + if (!w) + return -ENOMEM; + + *(char*) mempcpy(stpcpy(mempcpy(w, s, a), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0; + + if (streq(w, NULL_ADJTIME_UTC)) { + if (unlink("/etc/adjtime") < 0) + if (errno != ENOENT) + return -errno; + + return 0; + } + } + + mac_selinux_init("/etc"); + return write_string_file_atomic_label("/etc/adjtime", w); +} + +static int context_read_ntp(Context *c, sd_bus *bus) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + const char *s; + int r; + + assert(c); + assert(bus); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "GetUnitFileState", + &error, + &reply, + "s", + "systemd-timesyncd.service"); + + if (r < 0) { + if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND) || + sd_bus_error_has_name(&error, "org.freedesktop.systemd1.LoadFailed") || + sd_bus_error_has_name(&error, "org.freedesktop.systemd1.NoSuchUnit")) + return 0; + + return r; + } + + r = sd_bus_message_read(reply, "s", &s); + if (r < 0) + return r; + + c->can_ntp = true; + c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime"); + + return 0; +} + +static int context_start_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) { + int r; + + assert(bus); + assert(error); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + enabled ? "StartUnit" : "StopUnit", + error, + NULL, + "ss", + "systemd-timesyncd.service", + "replace"); + if (r < 0) { + if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) || + sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") || + sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) + return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported."); + + return r; + } + + return 0; +} + +static int context_enable_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) { + int r; + + assert(bus); + assert(error); + + if (enabled) + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "EnableUnitFiles", + error, + NULL, + "asbb", 1, + "systemd-timesyncd.service", + false, true); + else + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "DisableUnitFiles", + error, + NULL, + "asb", 1, + "systemd-timesyncd.service", + false); + + if (r < 0) { + if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) + return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported."); + + return r; + } + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "Reload", + error, + NULL, + NULL); + if (r < 0) + return r; + + return 0; +} + +static int property_get_rtc_time( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + struct tm tm; + usec_t t; + int r; + + zero(tm); + r = clock_get_hwclock(&tm); + if (r == -EBUSY) { + log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp."); + t = 0; + } else if (r == -ENOENT) { + log_debug("/dev/rtc not found."); + t = 0; /* no RTC found */ + } else if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m"); + else + t = (usec_t) timegm(&tm) * USEC_PER_SEC; + + return sd_bus_message_append(reply, "t", t); +} + +static int property_get_time( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME)); +} + +static int property_get_ntp_sync( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + return sd_bus_message_append(reply, "b", ntp_synced()); +} + +static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error *error) { + Context *c = userdata; + const char *z; + int interactive; + char *t; + int r; + + assert(m); + assert(c); + + r = sd_bus_message_read(m, "sb", &z, &interactive); + if (r < 0) + return r; + + if (!timezone_is_valid(z)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z); + + if (streq_ptr(z, c->zone)) + return sd_bus_reply_method_return(m, NULL); + + r = bus_verify_polkit_async( + m, + CAP_SYS_TIME, + "org.freedesktop.timedate1.set-timezone", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + t = strdup(z); + if (!t) + return -ENOMEM; + + free(c->zone); + c->zone = t; + + /* 1. Write new configuration file */ + r = context_write_data_timezone(c); + if (r < 0) { + log_error_errno(r, "Failed to set time zone: %m"); + return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %m"); + } + + /* 2. Tell the kernel our timezone */ + clock_set_timezone(NULL); + + if (c->local_rtc) { + struct timespec ts; + struct tm *tm; + + /* 3. Sync RTC from system clock, with the new delta */ + assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + assert_se(tm = localtime(&ts.tv_sec)); + clock_set_hwclock(tm); + } + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE), + "TIMEZONE=%s", c->zone, + LOG_MESSAGE("Changed time zone to '%s'.", c->zone), + NULL); + + (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL); + + return sd_bus_reply_method_return(m, NULL); +} + +static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int lrtc, fix_system, interactive; + Context *c = userdata; + struct timespec ts; + int r; + + assert(m); + assert(c); + + r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive); + if (r < 0) + return r; + + if (lrtc == c->local_rtc) + return sd_bus_reply_method_return(m, NULL); + + r = bus_verify_polkit_async( + m, + CAP_SYS_TIME, + "org.freedesktop.timedate1.set-local-rtc", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; + + c->local_rtc = lrtc; + + /* 1. Write new configuration file */ + r = context_write_data_local_rtc(c); + if (r < 0) { + log_error_errno(r, "Failed to set RTC to local/UTC: %m"); + return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %m"); + } + + /* 2. Tell the kernel our timezone */ + clock_set_timezone(NULL); + + /* 3. Synchronize clocks */ + assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); + + if (fix_system) { + struct tm tm; + + /* Sync system clock from RTC; first, + * initialize the timezone fields of + * struct tm. */ + if (c->local_rtc) + tm = *localtime(&ts.tv_sec); + else + tm = *gmtime(&ts.tv_sec); + + /* Override the main fields of + * struct tm, but not the timezone + * fields */ + if (clock_get_hwclock(&tm) >= 0) { + + /* And set the system clock + * with this */ + if (c->local_rtc) + ts.tv_sec = mktime(&tm); + else + ts.tv_sec = timegm(&tm); + + clock_settime(CLOCK_REALTIME, &ts); + } + + } else { + struct tm *tm; + + /* Sync RTC from system clock */ + if (c->local_rtc) + tm = localtime(&ts.tv_sec); + else + tm = gmtime(&ts.tv_sec); + + clock_set_hwclock(tm); + } + + log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC"); + + (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL); + + return sd_bus_reply_method_return(m, NULL); +} + +static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int relative, interactive; + Context *c = userdata; + int64_t utc; + struct timespec ts; + usec_t start; + struct tm* tm; + int r; + + assert(m); + assert(c); + + if (c->use_ntp) + return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled"); + + /* this only gets used if dbus does not provide a timestamp */ + start = now(CLOCK_MONOTONIC); + + r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive); + if (r < 0) + return r; + + if (!relative && utc <= 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time"); + + if (relative && utc == 0) + return sd_bus_reply_method_return(m, NULL); + + if (relative) { + usec_t n, x; + + n = now(CLOCK_REALTIME); + x = n + utc; + + if ((utc > 0 && x < n) || + (utc < 0 && x > n)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow"); + + timespec_store(&ts, x); + } else + timespec_store(&ts, (usec_t) utc); + + r = bus_verify_polkit_async( + m, + CAP_SYS_TIME, + "org.freedesktop.timedate1.set-time", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; + + /* adjust ts for time spent in program */ + r = sd_bus_message_get_monotonic_usec(m, &start); + /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */ + if (r < 0 && r != -ENODATA) + return r; + + timespec_store(&ts, timespec_load(&ts) + (now(CLOCK_MONOTONIC) - start)); + + /* Set system clock */ + if (clock_settime(CLOCK_REALTIME, &ts) < 0) { + log_error_errno(errno, "Failed to set local time: %m"); + return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m"); + } + + /* Sync down to RTC */ + if (c->local_rtc) + tm = localtime(&ts.tv_sec); + else + tm = gmtime(&ts.tv_sec); + clock_set_hwclock(tm); + + log_struct(LOG_INFO, + LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE), + "REALTIME="USEC_FMT, timespec_load(&ts), + LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)), + NULL); + + return sd_bus_reply_method_return(m, NULL); +} + +static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error) { + int enabled, interactive; + Context *c = userdata; + int r; + + assert(m); + assert(c); + + r = sd_bus_message_read(m, "bb", &enabled, &interactive); + if (r < 0) + return r; + + if ((bool)enabled == c->use_ntp) + return sd_bus_reply_method_return(m, NULL); + + r = bus_verify_polkit_async( + m, + CAP_SYS_TIME, + "org.freedesktop.timedate1.set-ntp", + NULL, + interactive, + UID_INVALID, + &c->polkit_registry, + error); + if (r < 0) + return r; + if (r == 0) + return 1; + + r = context_enable_ntp(sd_bus_message_get_bus(m), error, enabled); + if (r < 0) + return r; + + r = context_start_ntp(sd_bus_message_get_bus(m), error, enabled); + if (r < 0) + return r; + + c->use_ntp = enabled; + log_info("Set NTP to %s", enabled ? "enabled" : "disabled"); + + (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL); + + return sd_bus_reply_method_return(m, NULL); +} + +static const sd_bus_vtable timedate_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0), + SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0), + SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0), + SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0), + SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END, +}; + +static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) { + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + assert(c); + assert(event); + assert(_bus); + + r = sd_bus_default_system(&bus); + if (r < 0) + return log_error_errno(r, "Failed to get system bus connection: %m"); + + r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c); + if (r < 0) + return log_error_errno(r, "Failed to register object: %m"); + + r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0); + if (r < 0) + return log_error_errno(r, "Failed to register name: %m"); + + r = sd_bus_attach_event(bus, event, 0); + if (r < 0) + return log_error_errno(r, "Failed to attach bus to event loop: %m"); + + *_bus = bus; + bus = NULL; + + return 0; +} + +int main(int argc, char *argv[]) { + Context context = {}; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc != 1) { + log_error("This program takes no arguments."); + r = -EINVAL; + goto finish; + } + + r = sd_event_default(&event); + if (r < 0) { + log_error_errno(r, "Failed to allocate event loop: %m"); + goto finish; + } + + sd_event_set_watchdog(event, true); + + r = connect_bus(&context, event, &bus); + if (r < 0) + goto finish; + + (void) sd_bus_negotiate_timestamp(bus, true); + + r = context_read_data(&context); + if (r < 0) { + log_error_errno(r, "Failed to read time zone data: %m"); + goto finish; + } + + r = context_read_ntp(&context, bus); + if (r < 0) { + log_error_errno(r, "Failed to determine whether NTP is enabled: %m"); + goto finish; + } + + r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + goto finish; + } + +finish: + context_free(&context); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-timesync/.gitignore b/src/systemd-timesync/.gitignore new file mode 100644 index 0000000000..35f4d76f79 --- /dev/null +++ b/src/systemd-timesync/.gitignore @@ -0,0 +1,2 @@ +/timesyncd.conf +/timesyncd-gperf.c diff --git a/src/systemd-timesync/Makefile b/src/systemd-timesync/Makefile new file mode 100644 index 0000000000..a6ad358562 --- /dev/null +++ b/src/systemd-timesync/Makefile @@ -0,0 +1,64 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_TIMESYNCD),) +systemd_timesyncd_SOURCES = \ + src/timesync/timesyncd.c \ + src/timesync/timesyncd-manager.c \ + src/timesync/timesyncd-manager.h \ + src/timesync/timesyncd-conf.c \ + src/timesync/timesyncd-conf.h \ + src/timesync/timesyncd-server.c \ + src/timesync/timesyncd-server.h + +nodist_systemd_timesyncd_SOURCES = \ + src/timesync/timesyncd-gperf.c + +systemd_timesyncd_LDADD = \ + libsystemd-network.la \ + libshared.la + +rootlibexec_PROGRAMS += \ + systemd-timesyncd + +nodist_systemunit_DATA += \ + units/systemd-timesyncd.service + +GENERAL_ALIASES += \ + $(systemunitdir)/systemd-timesyncd.service $(pkgsysconfdir)/system/sysinit.target.wants/systemd-timesyncd.service + +nodist_pkgsysconf_DATA += \ + src/timesync/timesyncd.conf + +endif + +gperf_gperf_sources += \ + src/timesync/timesyncd-gperf.gperf + +EXTRA_DIST += \ + units/systemd-timesyncd.service.in \ + src/timesync/timesyncd.conf.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-timesync/timesyncd-conf.c b/src/systemd-timesync/timesyncd-conf.c new file mode 100644 index 0000000000..20c64a3354 --- /dev/null +++ b/src/systemd-timesync/timesyncd-conf.c @@ -0,0 +1,106 @@ +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 . +***/ + +#include "alloc-util.h" +#include "def.h" +#include "extract-word.h" +#include "string-util.h" +#include "timesyncd-conf.h" +#include "timesyncd-manager.h" +#include "timesyncd-server.h" + +int manager_parse_server_string(Manager *m, ServerType type, const char *string) { + ServerName *first; + int r; + + assert(m); + assert(string); + + first = type == SERVER_FALLBACK ? m->fallback_servers : m->system_servers; + + for (;;) { + _cleanup_free_ char *word = NULL; + bool found = false; + ServerName *n; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return log_error_errno(r, "Failed to parse timesyncd server syntax \"%s\": %m", string); + if (r == 0) + break; + + /* Filter out duplicates */ + LIST_FOREACH(names, n, first) + if (streq_ptr(n->string, word)) { + found = true; + break; + } + + if (found) + continue; + + r = server_name_new(m, NULL, type, word); + if (r < 0) + return r; + } + + return 0; +} + +int config_parse_servers( + 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) { + + Manager *m = userdata; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + + if (isempty(rvalue)) + manager_flush_server_names(m, ltype); + else { + r = manager_parse_server_string(m, ltype, rvalue); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse NTP server string '%s'. Ignoring.", rvalue); + return 0; + } + } + + return 0; +} + +int manager_parse_config_file(Manager *m) { + assert(m); + + return config_parse_many(PKGSYSCONFDIR "/timesyncd.conf", + CONF_PATHS_NULSTR("systemd/timesyncd.conf.d"), + "Time\0", + config_item_perf_lookup, timesyncd_gperf_lookup, + false, m); +} diff --git a/src/systemd-timesync/timesyncd-conf.h b/src/systemd-timesync/timesyncd-conf.h new file mode 100644 index 0000000000..cba0724b1b --- /dev/null +++ b/src/systemd-timesync/timesyncd-conf.h @@ -0,0 +1,31 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 . +***/ + +#include "conf-parser.h" +#include "timesyncd-manager.h" + +const struct ConfigPerfItem* timesyncd_gperf_lookup(const char *key, unsigned length); + +int manager_parse_server_string(Manager *m, ServerType type, const char *string); + +int config_parse_servers(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_parse_config_file(Manager *m); diff --git a/src/systemd-timesync/timesyncd-gperf.gperf b/src/systemd-timesync/timesyncd-gperf.gperf new file mode 100644 index 0000000000..29a2cfeef6 --- /dev/null +++ b/src/systemd-timesync/timesyncd-gperf.gperf @@ -0,0 +1,19 @@ +%{ +#include +#include "conf-parser.h" +#include "timesyncd-conf.h" +%} +struct ConfigPerfItem; +%null_strings +%language=ANSI-C +%define slot-name section_and_lvalue +%define hash-function-name timesyncdd_gperf_hash +%define lookup-function-name timesyncd_gperf_lookup +%readonly-tables +%omit-struct-type +%struct-type +%includes +%% +Time.NTP, config_parse_servers, SERVER_SYSTEM, 0 +Time.Servers, config_parse_servers, SERVER_SYSTEM, 0 +Time.FallbackNTP, config_parse_servers, SERVER_FALLBACK, 0 diff --git a/src/systemd-timesync/timesyncd-manager.c b/src/systemd-timesync/timesyncd-manager.c new file mode 100644 index 0000000000..6a4b52af69 --- /dev/null +++ b/src/systemd-timesync/timesyncd-manager.c @@ -0,0 +1,1156 @@ +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "list.h" +#include "log.h" +#include "missing.h" +#include "network-util.h" +#include "ratelimit.h" +#include "socket-util.h" +#include "sparse-endian.h" +#include "string-util.h" +#include "strv.h" +#include "time-util.h" +#include "timesyncd-conf.h" +#include "timesyncd-manager.h" +#include "util.h" + +#ifndef ADJ_SETOFFSET +#define ADJ_SETOFFSET 0x0100 /* add 'time' to current time */ +#endif + +/* expected accuracy of time synchronization; used to adjust the poll interval */ +#define NTP_ACCURACY_SEC 0.2 + +/* + * "A client MUST NOT under any conditions use a poll interval less + * than 15 seconds." + */ +#define NTP_POLL_INTERVAL_MIN_SEC 32 +#define NTP_POLL_INTERVAL_MAX_SEC 2048 + +/* + * Maximum delta in seconds which the system clock is gradually adjusted + * (slew) to approach the network time. Deltas larger that this are set by + * letting the system time jump. The kernel's limit for adjtime is 0.5s. + */ +#define NTP_MAX_ADJUST 0.4 + +/* NTP protocol, packet header */ +#define NTP_LEAP_PLUSSEC 1 +#define NTP_LEAP_MINUSSEC 2 +#define NTP_LEAP_NOTINSYNC 3 +#define NTP_MODE_CLIENT 3 +#define NTP_MODE_SERVER 4 +#define NTP_FIELD_LEAP(f) (((f) >> 6) & 3) +#define NTP_FIELD_VERSION(f) (((f) >> 3) & 7) +#define NTP_FIELD_MODE(f) ((f) & 7) +#define NTP_FIELD(l, v, m) (((l) << 6) | ((v) << 3) | (m)) + +/* Maximum acceptable root distance in seconds. */ +#define NTP_MAX_ROOT_DISTANCE 5.0 + +/* Maximum number of missed replies before selecting another source. */ +#define NTP_MAX_MISSED_REPLIES 2 + +/* + * "NTP timestamps are represented as a 64-bit unsigned fixed-point number, + * in seconds relative to 0h on 1 January 1900." + */ +#define OFFSET_1900_1970 UINT64_C(2208988800) + +#define RETRY_USEC (30*USEC_PER_SEC) +#define RATELIMIT_INTERVAL_USEC (10*USEC_PER_SEC) +#define RATELIMIT_BURST 10 + +#define TIMEOUT_USEC (10*USEC_PER_SEC) + +struct ntp_ts { + be32_t sec; + be32_t frac; +} _packed_; + +struct ntp_ts_short { + be16_t sec; + be16_t frac; +} _packed_; + +struct ntp_msg { + uint8_t field; + uint8_t stratum; + int8_t poll; + int8_t precision; + struct ntp_ts_short root_delay; + struct ntp_ts_short root_dispersion; + char refid[4]; + struct ntp_ts reference_time; + struct ntp_ts origin_time; + struct ntp_ts recv_time; + struct ntp_ts trans_time; +} _packed_; + +static int manager_arm_timer(Manager *m, usec_t next); +static int manager_clock_watch_setup(Manager *m); +static int manager_listen_setup(Manager *m); +static void manager_listen_stop(Manager *m); + +static double ntp_ts_short_to_d(const struct ntp_ts_short *ts) { + return be16toh(ts->sec) + (be16toh(ts->frac) / 65536.0); +} + +static double ntp_ts_to_d(const struct ntp_ts *ts) { + return be32toh(ts->sec) + ((double)be32toh(ts->frac) / UINT_MAX); +} + +static double ts_to_d(const struct timespec *ts) { + return ts->tv_sec + (1.0e-9 * ts->tv_nsec); +} + +static int manager_timeout(sd_event_source *source, usec_t usec, void *userdata) { + _cleanup_free_ char *pretty = NULL; + Manager *m = userdata; + + assert(m); + assert(m->current_server_name); + assert(m->current_server_address); + + server_address_pretty(m->current_server_address, &pretty); + log_info("Timed out waiting for reply from %s (%s).", strna(pretty), m->current_server_name->string); + + return manager_connect(m); +} + +static int manager_send_request(Manager *m) { + _cleanup_free_ char *pretty = NULL; + struct ntp_msg ntpmsg = { + /* + * "The client initializes the NTP message header, sends the request + * to the server, and strips the time of day from the Transmit + * Timestamp field of the reply. For this purpose, all the NTP + * header fields are set to 0, except the Mode, VN, and optional + * Transmit Timestamp fields." + */ + .field = NTP_FIELD(0, 4, NTP_MODE_CLIENT), + }; + ssize_t len; + int r; + + assert(m); + assert(m->current_server_name); + assert(m->current_server_address); + + m->event_timeout = sd_event_source_unref(m->event_timeout); + + r = manager_listen_setup(m); + if (r < 0) + return log_warning_errno(r, "Failed to setup connection socket: %m"); + + /* + * Set transmit timestamp, remember it; the server will send that back + * as the origin timestamp and we have an indication that this is the + * matching answer to our request. + * + * The actual value does not matter, We do not care about the correct + * NTP UINT_MAX fraction; we just pass the plain nanosecond value. + */ + assert_se(clock_gettime(clock_boottime_or_monotonic(), &m->trans_time_mon) >= 0); + assert_se(clock_gettime(CLOCK_REALTIME, &m->trans_time) >= 0); + ntpmsg.trans_time.sec = htobe32(m->trans_time.tv_sec + OFFSET_1900_1970); + ntpmsg.trans_time.frac = htobe32(m->trans_time.tv_nsec); + + server_address_pretty(m->current_server_address, &pretty); + + len = sendto(m->server_socket, &ntpmsg, sizeof(ntpmsg), MSG_DONTWAIT, &m->current_server_address->sockaddr.sa, m->current_server_address->socklen); + if (len == sizeof(ntpmsg)) { + m->pending = true; + log_debug("Sent NTP request to %s (%s).", strna(pretty), m->current_server_name->string); + } else { + log_debug_errno(errno, "Sending NTP request to %s (%s) failed: %m", strna(pretty), m->current_server_name->string); + return manager_connect(m); + } + + /* re-arm timer with increasing timeout, in case the packets never arrive back */ + if (m->retry_interval > 0) { + if (m->retry_interval < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC) + m->retry_interval *= 2; + } else + m->retry_interval = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; + + r = manager_arm_timer(m, m->retry_interval); + if (r < 0) + return log_error_errno(r, "Failed to rearm timer: %m"); + + m->missed_replies++; + if (m->missed_replies > NTP_MAX_MISSED_REPLIES) { + r = sd_event_add_time( + m->event, + &m->event_timeout, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + TIMEOUT_USEC, 0, + manager_timeout, m); + if (r < 0) + return log_error_errno(r, "Failed to arm timeout timer: %m"); + } + + return 0; +} + +static int manager_timer(sd_event_source *source, usec_t usec, void *userdata) { + Manager *m = userdata; + + assert(m); + + return manager_send_request(m); +} + +static int manager_arm_timer(Manager *m, usec_t next) { + int r; + + assert(m); + + if (next == 0) { + m->event_timer = sd_event_source_unref(m->event_timer); + return 0; + } + + if (m->event_timer) { + r = sd_event_source_set_time(m->event_timer, now(clock_boottime_or_monotonic()) + next); + if (r < 0) + return r; + + return sd_event_source_set_enabled(m->event_timer, SD_EVENT_ONESHOT); + } + + return sd_event_add_time( + m->event, + &m->event_timer, + clock_boottime_or_monotonic(), + now(clock_boottime_or_monotonic()) + next, 0, + manager_timer, m); +} + +static int manager_clock_watch(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + + assert(m); + + /* rearm timer */ + manager_clock_watch_setup(m); + + /* skip our own jumps */ + if (m->jumped) { + m->jumped = false; + return 0; + } + + /* resync */ + log_debug("System time changed. Resyncing."); + m->poll_resync = true; + + return manager_send_request(m); +} + +/* wake up when the system time changes underneath us */ +static int manager_clock_watch_setup(Manager *m) { + + struct itimerspec its = { + .it_value.tv_sec = TIME_T_MAX + }; + + int r; + + assert(m); + + m->event_clock_watch = sd_event_source_unref(m->event_clock_watch); + safe_close(m->clock_watch_fd); + + m->clock_watch_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC); + if (m->clock_watch_fd < 0) + return log_error_errno(errno, "Failed to create timerfd: %m"); + + if (timerfd_settime(m->clock_watch_fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) + return log_error_errno(errno, "Failed to set up timerfd: %m"); + + r = sd_event_add_io(m->event, &m->event_clock_watch, m->clock_watch_fd, EPOLLIN, manager_clock_watch, m); + if (r < 0) + return log_error_errno(r, "Failed to create clock watch event source: %m"); + + return 0; +} + +static int manager_adjust_clock(Manager *m, double offset, int leap_sec) { + struct timex tmx = {}; + int r; + + assert(m); + + /* + * For small deltas, tell the kernel to gradually adjust the system + * clock to the NTP time, larger deltas are just directly set. + */ + if (fabs(offset) < NTP_MAX_ADJUST) { + tmx.modes = ADJ_STATUS | ADJ_NANO | ADJ_OFFSET | ADJ_TIMECONST | ADJ_MAXERROR | ADJ_ESTERROR; + tmx.status = STA_PLL; + tmx.offset = offset * NSEC_PER_SEC; + tmx.constant = log2i(m->poll_interval_usec / USEC_PER_SEC) - 4; + tmx.maxerror = 0; + tmx.esterror = 0; + log_debug(" adjust (slew): %+.3f sec", offset); + } else { + tmx.modes = ADJ_STATUS | ADJ_NANO | ADJ_SETOFFSET; + + /* ADJ_NANO uses nanoseconds in the microseconds field */ + tmx.time.tv_sec = (long)offset; + tmx.time.tv_usec = (offset - tmx.time.tv_sec) * NSEC_PER_SEC; + + /* the kernel expects -0.3s as {-1, 7000.000.000} */ + if (tmx.time.tv_usec < 0) { + tmx.time.tv_sec -= 1; + tmx.time.tv_usec += NSEC_PER_SEC; + } + + m->jumped = true; + log_debug(" adjust (jump): %+.3f sec", offset); + } + + /* + * An unset STA_UNSYNC will enable the kernel's 11-minute mode, + * which syncs the system time periodically to the RTC. + * + * In case the RTC runs in local time, never touch the RTC, + * we have no way to properly handle daylight saving changes and + * mobile devices moving between time zones. + */ + if (m->rtc_local_time) + tmx.status |= STA_UNSYNC; + + switch (leap_sec) { + case 1: + tmx.status |= STA_INS; + break; + case -1: + tmx.status |= STA_DEL; + break; + } + + r = clock_adjtime(CLOCK_REALTIME, &tmx); + if (r < 0) + return -errno; + + /* If touch fails, there isn't much we can do. Maybe it'll work next time. */ + (void) touch("/var/lib/systemd/clock"); + + m->drift_ppm = tmx.freq / 65536; + + log_debug(" status : %04i %s\n" + " time now : %li.%03llu\n" + " constant : %li\n" + " offset : %+.3f sec\n" + " freq offset : %+li (%i ppm)\n", + tmx.status, tmx.status & STA_UNSYNC ? "unsync" : "sync", + tmx.time.tv_sec, (unsigned long long) (tmx.time.tv_usec / NSEC_PER_MSEC), + tmx.constant, + (double)tmx.offset / NSEC_PER_SEC, + tmx.freq, m->drift_ppm); + + return 0; +} + +static bool manager_sample_spike_detection(Manager *m, double offset, double delay) { + unsigned int i, idx_cur, idx_new, idx_min; + double jitter; + double j; + + assert(m); + + m->packet_count++; + + /* ignore initial sample */ + if (m->packet_count == 1) + return false; + + /* store the current data in our samples array */ + idx_cur = m->samples_idx; + idx_new = (idx_cur + 1) % ELEMENTSOF(m->samples); + m->samples_idx = idx_new; + m->samples[idx_new].offset = offset; + m->samples[idx_new].delay = delay; + + /* calculate new jitter value from the RMS differences relative to the lowest delay sample */ + jitter = m->samples_jitter; + for (idx_min = idx_cur, i = 0; i < ELEMENTSOF(m->samples); i++) + if (m->samples[i].delay > 0 && m->samples[i].delay < m->samples[idx_min].delay) + idx_min = i; + + j = 0; + for (i = 0; i < ELEMENTSOF(m->samples); i++) + j += pow(m->samples[i].offset - m->samples[idx_min].offset, 2); + m->samples_jitter = sqrt(j / (ELEMENTSOF(m->samples) - 1)); + + /* ignore samples when resyncing */ + if (m->poll_resync) + return false; + + /* always accept offset if we are farther off than the round-trip delay */ + if (fabs(offset) > delay) + return false; + + /* we need a few samples before looking at them */ + if (m->packet_count < 4) + return false; + + /* do not accept anything worse than the maximum possible error of the best sample */ + if (fabs(offset) > m->samples[idx_min].delay) + return true; + + /* compare the difference between the current offset to the previous offset and jitter */ + return fabs(offset - m->samples[idx_cur].offset) > 3 * jitter; +} + +static void manager_adjust_poll(Manager *m, double offset, bool spike) { + assert(m); + + if (m->poll_resync) { + m->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; + m->poll_resync = false; + return; + } + + /* set to minimal poll interval */ + if (!spike && fabs(offset) > NTP_ACCURACY_SEC) { + m->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; + return; + } + + /* increase polling interval */ + if (fabs(offset) < NTP_ACCURACY_SEC * 0.25) { + if (m->poll_interval_usec < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC) + m->poll_interval_usec *= 2; + return; + } + + /* decrease polling interval */ + if (spike || fabs(offset) > NTP_ACCURACY_SEC * 0.75) { + if (m->poll_interval_usec > NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC) + m->poll_interval_usec /= 2; + return; + } +} + +static int manager_receive_response(sd_event_source *source, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + struct ntp_msg ntpmsg; + + struct iovec iov = { + .iov_base = &ntpmsg, + .iov_len = sizeof(ntpmsg), + }; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct timeval))]; + } control; + union sockaddr_union server_addr; + struct msghdr msghdr = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + .msg_name = &server_addr, + .msg_namelen = sizeof(server_addr), + }; + struct cmsghdr *cmsg; + struct timespec *recv_time; + ssize_t len; + double origin, receive, trans, dest; + double delay, offset; + double root_distance; + bool spike; + int leap_sec; + int r; + + assert(source); + assert(m); + + if (revents & (EPOLLHUP|EPOLLERR)) { + log_warning("Server connection returned error."); + return manager_connect(m); + } + + len = recvmsg(fd, &msghdr, MSG_DONTWAIT); + if (len < 0) { + if (errno == EAGAIN) + return 0; + + log_warning("Error receiving message. Disconnecting."); + return manager_connect(m); + } + + /* Too short or too long packet? */ + if (iov.iov_len < sizeof(struct ntp_msg) || (msghdr.msg_flags & MSG_TRUNC)) { + log_warning("Invalid response from server. Disconnecting."); + return manager_connect(m); + } + + if (!m->current_server_name || + !m->current_server_address || + !sockaddr_equal(&server_addr, &m->current_server_address->sockaddr)) { + log_debug("Response from unknown server."); + return 0; + } + + recv_time = NULL; + CMSG_FOREACH(cmsg, &msghdr) { + if (cmsg->cmsg_level != SOL_SOCKET) + continue; + + switch (cmsg->cmsg_type) { + case SCM_TIMESTAMPNS: + recv_time = (struct timespec *) CMSG_DATA(cmsg); + break; + } + } + if (!recv_time) { + log_error("Invalid packet timestamp."); + return -EINVAL; + } + + if (!m->pending) { + log_debug("Unexpected reply. Ignoring."); + return 0; + } + + m->missed_replies = 0; + + /* check our "time cookie" (we just stored nanoseconds in the fraction field) */ + if (be32toh(ntpmsg.origin_time.sec) != m->trans_time.tv_sec + OFFSET_1900_1970 || + be32toh(ntpmsg.origin_time.frac) != m->trans_time.tv_nsec) { + log_debug("Invalid reply; not our transmit time. Ignoring."); + return 0; + } + + m->event_timeout = sd_event_source_unref(m->event_timeout); + + if (be32toh(ntpmsg.recv_time.sec) < TIME_EPOCH + OFFSET_1900_1970 || + be32toh(ntpmsg.trans_time.sec) < TIME_EPOCH + OFFSET_1900_1970) { + log_debug("Invalid reply, returned times before epoch. Ignoring."); + return manager_connect(m); + } + + if (NTP_FIELD_LEAP(ntpmsg.field) == NTP_LEAP_NOTINSYNC || + ntpmsg.stratum == 0 || ntpmsg.stratum >= 16) { + log_debug("Server is not synchronized. Disconnecting."); + return manager_connect(m); + } + + if (!IN_SET(NTP_FIELD_VERSION(ntpmsg.field), 3, 4)) { + log_debug("Response NTPv%d. Disconnecting.", NTP_FIELD_VERSION(ntpmsg.field)); + return manager_connect(m); + } + + if (NTP_FIELD_MODE(ntpmsg.field) != NTP_MODE_SERVER) { + log_debug("Unsupported mode %d. Disconnecting.", NTP_FIELD_MODE(ntpmsg.field)); + return manager_connect(m); + } + + root_distance = ntp_ts_short_to_d(&ntpmsg.root_delay) / 2 + ntp_ts_short_to_d(&ntpmsg.root_dispersion); + if (root_distance > NTP_MAX_ROOT_DISTANCE) { + log_debug("Server has too large root distance. Disconnecting."); + return manager_connect(m); + } + + /* valid packet */ + m->pending = false; + m->retry_interval = 0; + + /* Stop listening */ + manager_listen_stop(m); + + /* announce leap seconds */ + if (NTP_FIELD_LEAP(ntpmsg.field) & NTP_LEAP_PLUSSEC) + leap_sec = 1; + else if (NTP_FIELD_LEAP(ntpmsg.field) & NTP_LEAP_MINUSSEC) + leap_sec = -1; + else + leap_sec = 0; + + /* + * "Timestamp Name ID When Generated + * ------------------------------------------------------------ + * Originate Timestamp T1 time request sent by client + * Receive Timestamp T2 time request received by server + * Transmit Timestamp T3 time reply sent by server + * Destination Timestamp T4 time reply received by client + * + * The round-trip delay, d, and system clock offset, t, are defined as: + * d = (T4 - T1) - (T3 - T2) t = ((T2 - T1) + (T3 - T4)) / 2" + */ + origin = ts_to_d(&m->trans_time) + OFFSET_1900_1970; + receive = ntp_ts_to_d(&ntpmsg.recv_time); + trans = ntp_ts_to_d(&ntpmsg.trans_time); + dest = ts_to_d(recv_time) + OFFSET_1900_1970; + + offset = ((receive - origin) + (trans - dest)) / 2; + delay = (dest - origin) - (trans - receive); + + spike = manager_sample_spike_detection(m, offset, delay); + + manager_adjust_poll(m, offset, spike); + + log_debug("NTP response:\n" + " leap : %u\n" + " version : %u\n" + " mode : %u\n" + " stratum : %u\n" + " precision : %.6f sec (%d)\n" + " root distance: %.6f sec\n" + " reference : %.4s\n" + " origin : %.3f\n" + " receive : %.3f\n" + " transmit : %.3f\n" + " dest : %.3f\n" + " offset : %+.3f sec\n" + " delay : %+.3f sec\n" + " packet count : %"PRIu64"\n" + " jitter : %.3f%s\n" + " poll interval: " USEC_FMT "\n", + NTP_FIELD_LEAP(ntpmsg.field), + NTP_FIELD_VERSION(ntpmsg.field), + NTP_FIELD_MODE(ntpmsg.field), + ntpmsg.stratum, + exp2(ntpmsg.precision), ntpmsg.precision, + root_distance, + ntpmsg.stratum == 1 ? ntpmsg.refid : "n/a", + origin - OFFSET_1900_1970, + receive - OFFSET_1900_1970, + trans - OFFSET_1900_1970, + dest - OFFSET_1900_1970, + offset, delay, + m->packet_count, + m->samples_jitter, spike ? " spike" : "", + m->poll_interval_usec / USEC_PER_SEC); + + if (!spike) { + m->sync = true; + r = manager_adjust_clock(m, offset, leap_sec); + if (r < 0) + log_error_errno(r, "Failed to call clock_adjtime(): %m"); + } + + log_debug("interval/delta/delay/jitter/drift " USEC_FMT "s/%+.3fs/%.3fs/%.3fs/%+ippm%s", + m->poll_interval_usec / USEC_PER_SEC, offset, delay, m->samples_jitter, m->drift_ppm, + spike ? " (ignored)" : ""); + + if (!m->good) { + _cleanup_free_ char *pretty = NULL; + + m->good = true; + + server_address_pretty(m->current_server_address, &pretty); + log_info("Synchronized to time server %s (%s).", strna(pretty), m->current_server_name->string); + sd_notifyf(false, "STATUS=Synchronized to time server %s (%s).", strna(pretty), m->current_server_name->string); + } + + r = manager_arm_timer(m, m->poll_interval_usec); + if (r < 0) + return log_error_errno(r, "Failed to rearm timer: %m"); + + return 0; +} + +static int manager_listen_setup(Manager *m) { + union sockaddr_union addr = {}; + static const int tos = IPTOS_LOWDELAY; + static const int on = 1; + int r; + + assert(m); + + if (m->server_socket >= 0) + return 0; + + assert(!m->event_receive); + assert(m->current_server_address); + + addr.sa.sa_family = m->current_server_address->sockaddr.sa.sa_family; + + m->server_socket = socket(addr.sa.sa_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); + if (m->server_socket < 0) + return -errno; + + r = bind(m->server_socket, &addr.sa, m->current_server_address->socklen); + if (r < 0) + return -errno; + + r = setsockopt(m->server_socket, SOL_SOCKET, SO_TIMESTAMPNS, &on, sizeof(on)); + if (r < 0) + return -errno; + + (void) setsockopt(m->server_socket, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); + + return sd_event_add_io(m->event, &m->event_receive, m->server_socket, EPOLLIN, manager_receive_response, m); +} + +static void manager_listen_stop(Manager *m) { + assert(m); + + m->event_receive = sd_event_source_unref(m->event_receive); + m->server_socket = safe_close(m->server_socket); +} + +static int manager_begin(Manager *m) { + _cleanup_free_ char *pretty = NULL; + int r; + + assert(m); + assert_return(m->current_server_name, -EHOSTUNREACH); + assert_return(m->current_server_address, -EHOSTUNREACH); + + m->good = false; + m->missed_replies = NTP_MAX_MISSED_REPLIES; + if (m->poll_interval_usec == 0) + m->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; + + server_address_pretty(m->current_server_address, &pretty); + log_debug("Connecting to time server %s (%s).", strna(pretty), m->current_server_name->string); + sd_notifyf(false, "STATUS=Connecting to time server %s (%s).", strna(pretty), m->current_server_name->string); + + r = manager_clock_watch_setup(m); + if (r < 0) + return r; + + return manager_send_request(m); +} + +void manager_set_server_name(Manager *m, ServerName *n) { + assert(m); + + if (m->current_server_name == n) + return; + + m->current_server_name = n; + m->current_server_address = NULL; + + manager_disconnect(m); + + if (n) + log_debug("Selected server %s.", n->string); +} + +void manager_set_server_address(Manager *m, ServerAddress *a) { + assert(m); + + if (m->current_server_address == a) + return; + + m->current_server_address = a; + /* If a is NULL, we are just clearing the address, without + * changing the name. Keep the existing name in that case. */ + if (a) + m->current_server_name = a->name; + + manager_disconnect(m); + + if (a) { + _cleanup_free_ char *pretty = NULL; + server_address_pretty(a, &pretty); + log_debug("Selected address %s of server %s.", strna(pretty), a->name->string); + } +} + +static int manager_resolve_handler(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata) { + Manager *m = userdata; + int r; + + assert(q); + assert(m); + assert(m->current_server_name); + + m->resolve_query = sd_resolve_query_unref(m->resolve_query); + + if (ret != 0) { + log_debug("Failed to resolve %s: %s", m->current_server_name->string, gai_strerror(ret)); + + /* Try next host */ + return manager_connect(m); + } + + for (; ai; ai = ai->ai_next) { + _cleanup_free_ char *pretty = NULL; + ServerAddress *a; + + assert(ai->ai_addr); + assert(ai->ai_addrlen >= offsetof(struct sockaddr, sa_data)); + + if (!IN_SET(ai->ai_addr->sa_family, AF_INET, AF_INET6)) { + log_warning("Unsuitable address protocol for %s", m->current_server_name->string); + continue; + } + + r = server_address_new(m->current_server_name, &a, (const union sockaddr_union*) ai->ai_addr, ai->ai_addrlen); + if (r < 0) + return log_error_errno(r, "Failed to add server address: %m"); + + server_address_pretty(a, &pretty); + log_debug("Resolved address %s for %s.", pretty, m->current_server_name->string); + } + + if (!m->current_server_name->addresses) { + log_error("Failed to find suitable address for host %s.", m->current_server_name->string); + + /* Try next host */ + return manager_connect(m); + } + + manager_set_server_address(m, m->current_server_name->addresses); + + return manager_begin(m); +} + +static int manager_retry_connect(sd_event_source *source, usec_t usec, void *userdata) { + Manager *m = userdata; + + assert(m); + + return manager_connect(m); +} + +int manager_connect(Manager *m) { + int r; + + assert(m); + + manager_disconnect(m); + + m->event_retry = sd_event_source_unref(m->event_retry); + if (!ratelimit_test(&m->ratelimit)) { + log_debug("Slowing down attempts to contact servers."); + + r = sd_event_add_time(m->event, &m->event_retry, clock_boottime_or_monotonic(), now(clock_boottime_or_monotonic()) + RETRY_USEC, 0, manager_retry_connect, m); + if (r < 0) + return log_error_errno(r, "Failed to create retry timer: %m"); + + return 0; + } + + /* If we already are operating on some address, switch to the + * next one. */ + if (m->current_server_address && m->current_server_address->addresses_next) + manager_set_server_address(m, m->current_server_address->addresses_next); + else { + struct addrinfo hints = { + .ai_flags = AI_NUMERICSERV|AI_ADDRCONFIG, + .ai_socktype = SOCK_DGRAM, + }; + + /* Hmm, we are through all addresses, let's look for the next host instead */ + if (m->current_server_name && m->current_server_name->names_next) + manager_set_server_name(m, m->current_server_name->names_next); + else { + ServerName *f; + bool restart = true; + + /* Our current server name list is exhausted, + * let's find the next one to iterate. First + * we try the system list, then the link list. + * After having processed the link list we + * jump back to the system list. However, if + * both lists are empty, we change to the + * fallback list. */ + if (!m->current_server_name || m->current_server_name->type == SERVER_LINK) { + f = m->system_servers; + if (!f) + f = m->link_servers; + } else { + f = m->link_servers; + if (!f) + f = m->system_servers; + else + restart = false; + } + + if (!f) + f = m->fallback_servers; + + if (!f) { + manager_set_server_name(m, NULL); + log_debug("No server found."); + return 0; + } + + if (restart && !m->exhausted_servers && m->poll_interval_usec) { + log_debug("Waiting after exhausting servers."); + r = sd_event_add_time(m->event, &m->event_retry, clock_boottime_or_monotonic(), now(clock_boottime_or_monotonic()) + m->poll_interval_usec, 0, manager_retry_connect, m); + if (r < 0) + return log_error_errno(r, "Failed to create retry timer: %m"); + + m->exhausted_servers = true; + + /* Increase the polling interval */ + if (m->poll_interval_usec < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC) + m->poll_interval_usec *= 2; + + return 0; + } + + m->exhausted_servers = false; + + manager_set_server_name(m, f); + } + + /* Tell the resolver to reread /etc/resolv.conf, in + * case it changed. */ + res_init(); + + /* Flush out any previously resolved addresses */ + server_name_flush_addresses(m->current_server_name); + + log_debug("Resolving %s...", m->current_server_name->string); + + r = sd_resolve_getaddrinfo(m->resolve, &m->resolve_query, m->current_server_name->string, "123", &hints, manager_resolve_handler, m); + if (r < 0) + return log_error_errno(r, "Failed to create resolver: %m"); + + return 1; + } + + r = manager_begin(m); + if (r < 0) + return r; + + return 1; +} + +void manager_disconnect(Manager *m) { + assert(m); + + m->resolve_query = sd_resolve_query_unref(m->resolve_query); + + m->event_timer = sd_event_source_unref(m->event_timer); + + manager_listen_stop(m); + + m->event_clock_watch = sd_event_source_unref(m->event_clock_watch); + m->clock_watch_fd = safe_close(m->clock_watch_fd); + + m->event_timeout = sd_event_source_unref(m->event_timeout); + + sd_notifyf(false, "STATUS=Idle."); +} + +void manager_flush_server_names(Manager *m, ServerType t) { + assert(m); + + if (t == SERVER_SYSTEM) + while (m->system_servers) + server_name_free(m->system_servers); + + if (t == SERVER_LINK) + while (m->link_servers) + server_name_free(m->link_servers); + + if (t == SERVER_FALLBACK) + while (m->fallback_servers) + server_name_free(m->fallback_servers); +} + +void manager_free(Manager *m) { + if (!m) + return; + + manager_disconnect(m); + manager_flush_server_names(m, SERVER_SYSTEM); + manager_flush_server_names(m, SERVER_LINK); + manager_flush_server_names(m, SERVER_FALLBACK); + + sd_event_source_unref(m->event_retry); + + sd_event_source_unref(m->network_event_source); + sd_network_monitor_unref(m->network_monitor); + + sd_resolve_unref(m->resolve); + sd_event_unref(m->event); + + free(m); +} + +static int manager_network_read_link_servers(Manager *m) { + _cleanup_strv_free_ char **ntp = NULL; + ServerName *n, *nx; + char **i; + int r; + + assert(m); + + r = sd_network_get_ntp(&ntp); + if (r < 0) + goto clear; + + LIST_FOREACH(names, n, m->link_servers) + n->marked = true; + + STRV_FOREACH(i, ntp) { + bool found = false; + + LIST_FOREACH(names, n, m->link_servers) + if (streq(n->string, *i)) { + n->marked = false; + found = true; + break; + } + + if (!found) { + r = server_name_new(m, NULL, SERVER_LINK, *i); + if (r < 0) + goto clear; + } + } + + LIST_FOREACH_SAFE(names, n, nx, m->link_servers) + if (n->marked) + server_name_free(n); + + return 0; + +clear: + manager_flush_server_names(m, SERVER_LINK); + return r; +} + +static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + Manager *m = userdata; + bool connected, online; + int r; + + assert(m); + + sd_network_monitor_flush(m->network_monitor); + + manager_network_read_link_servers(m); + + /* check if the machine is online */ + online = network_is_online(); + + /* check if the client is currently connected */ + connected = m->server_socket >= 0 || m->resolve_query || m->exhausted_servers; + + if (connected && !online) { + log_info("No network connectivity, watching for changes."); + manager_disconnect(m); + + } else if (!connected && online) { + log_info("Network configuration changed, trying to establish connection."); + + if (m->current_server_address) + r = manager_begin(m); + else + r = manager_connect(m); + if (r < 0) + return r; + } + + return 0; +} + +static int manager_network_monitor_listen(Manager *m) { + int r, fd, events; + + assert(m); + + r = sd_network_monitor_new(&m->network_monitor, NULL); + if (r < 0) + return r; + + fd = sd_network_monitor_get_fd(m->network_monitor); + if (fd < 0) + return fd; + + events = sd_network_monitor_get_events(m->network_monitor); + if (events < 0) + return events; + + r = sd_event_add_io(m->event, &m->network_event_source, fd, events, manager_network_event_handler, m); + if (r < 0) + return r; + + return 0; +} + +int manager_new(Manager **ret) { + _cleanup_(manager_freep) Manager *m = NULL; + int r; + + assert(ret); + + m = new0(Manager, 1); + if (!m) + return -ENOMEM; + + m->server_socket = m->clock_watch_fd = -1; + + RATELIMIT_INIT(m->ratelimit, RATELIMIT_INTERVAL_USEC, RATELIMIT_BURST); + + r = manager_parse_server_string(m, SERVER_FALLBACK, NTP_SERVERS); + if (r < 0) + return r; + + r = sd_event_default(&m->event); + if (r < 0) + return r; + + sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); + sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); + + sd_event_set_watchdog(m->event, true); + + r = sd_resolve_default(&m->resolve); + if (r < 0) + return r; + + r = sd_resolve_attach_event(m->resolve, m->event, 0); + if (r < 0) + return r; + + r = manager_network_monitor_listen(m); + if (r < 0) + return r; + + manager_network_read_link_servers(m); + + *ret = m; + m = NULL; + + return 0; +} diff --git a/src/systemd-timesync/timesyncd-manager.h b/src/systemd-timesync/timesyncd-manager.h new file mode 100644 index 0000000000..fd25647725 --- /dev/null +++ b/src/systemd-timesync/timesyncd-manager.h @@ -0,0 +1,104 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 . +***/ + +#include +#include +#include + +#include "list.h" +#include "ratelimit.h" + +typedef struct Manager Manager; + +#include "timesyncd-server.h" + +struct Manager { + sd_event *event; + sd_resolve *resolve; + + LIST_HEAD(ServerName, system_servers); + LIST_HEAD(ServerName, link_servers); + LIST_HEAD(ServerName, fallback_servers); + + RateLimit ratelimit; + bool exhausted_servers; + + /* network */ + sd_event_source *network_event_source; + sd_network_monitor *network_monitor; + + /* peer */ + sd_resolve_query *resolve_query; + sd_event_source *event_receive; + ServerName *current_server_name; + ServerAddress *current_server_address; + int server_socket; + int missed_replies; + uint64_t packet_count; + sd_event_source *event_timeout; + bool good; + + /* last sent packet */ + struct timespec trans_time_mon; + struct timespec trans_time; + usec_t retry_interval; + bool pending; + + /* poll timer */ + sd_event_source *event_timer; + usec_t poll_interval_usec; + bool poll_resync; + + /* history data */ + struct { + double offset; + double delay; + } samples[8]; + unsigned int samples_idx; + double samples_jitter; + + /* last change */ + bool jumped; + bool sync; + int drift_ppm; + + /* watch for time changes */ + sd_event_source *event_clock_watch; + int clock_watch_fd; + + /* Retry connections */ + sd_event_source *event_retry; + + /* RTC runs in local time, leave it alone */ + bool rtc_local_time; +}; + +int manager_new(Manager **ret); +void manager_free(Manager *m); + +DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); + +void manager_set_server_name(Manager *m, ServerName *n); +void manager_set_server_address(Manager *m, ServerAddress *a); +void manager_flush_server_names(Manager *m, ServerType t); + +int manager_connect(Manager *m); +void manager_disconnect(Manager *m); diff --git a/src/systemd-timesync/timesyncd-server.c b/src/systemd-timesync/timesyncd-server.c new file mode 100644 index 0000000000..6bda86fe6e --- /dev/null +++ b/src/systemd-timesync/timesyncd-server.c @@ -0,0 +1,150 @@ +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 . +***/ + +#include "alloc-util.h" +#include "timesyncd-server.h" + +int server_address_new( + ServerName *n, + ServerAddress **ret, + const union sockaddr_union *sockaddr, + socklen_t socklen) { + + ServerAddress *a, *tail; + + assert(n); + assert(sockaddr); + assert(socklen >= offsetof(struct sockaddr, sa_data)); + assert(socklen <= sizeof(union sockaddr_union)); + + a = new0(ServerAddress, 1); + if (!a) + return -ENOMEM; + + memcpy(&a->sockaddr, sockaddr, socklen); + a->socklen = socklen; + + LIST_FIND_TAIL(addresses, n->addresses, tail); + LIST_INSERT_AFTER(addresses, n->addresses, tail, a); + a->name = n; + + if (ret) + *ret = a; + + return 0; +} + +ServerAddress* server_address_free(ServerAddress *a) { + if (!a) + return NULL; + + if (a->name) { + LIST_REMOVE(addresses, a->name->addresses, a); + + if (a->name->manager && a->name->manager->current_server_address == a) + manager_set_server_address(a->name->manager, NULL); + } + + free(a); + return NULL; +} + +int server_name_new( + Manager *m, + ServerName **ret, + ServerType type, + const char *string) { + + ServerName *n, *tail; + + assert(m); + assert(string); + + n = new0(ServerName, 1); + if (!n) + return -ENOMEM; + + n->type = type; + n->string = strdup(string); + if (!n->string) { + free(n); + return -ENOMEM; + } + + if (type == SERVER_SYSTEM) { + LIST_FIND_TAIL(names, m->system_servers, tail); + LIST_INSERT_AFTER(names, m->system_servers, tail, n); + } else if (type == SERVER_LINK) { + LIST_FIND_TAIL(names, m->link_servers, tail); + LIST_INSERT_AFTER(names, m->link_servers, tail, n); + } else if (type == SERVER_FALLBACK) { + LIST_FIND_TAIL(names, m->fallback_servers, tail); + LIST_INSERT_AFTER(names, m->fallback_servers, tail, n); + } else + assert_not_reached("Unknown server type"); + + n->manager = m; + + if (type != SERVER_FALLBACK && + m->current_server_name && + m->current_server_name->type == SERVER_FALLBACK) + manager_set_server_name(m, NULL); + + log_debug("Added new server %s.", string); + + if (ret) + *ret = n; + + return 0; +} + +ServerName *server_name_free(ServerName *n) { + if (!n) + return NULL; + + server_name_flush_addresses(n); + + if (n->manager) { + if (n->type == SERVER_SYSTEM) + LIST_REMOVE(names, n->manager->system_servers, n); + else if (n->type == SERVER_LINK) + LIST_REMOVE(names, n->manager->link_servers, n); + else if (n->type == SERVER_FALLBACK) + LIST_REMOVE(names, n->manager->fallback_servers, n); + else + assert_not_reached("Unknown server type"); + + if (n->manager->current_server_name == n) + manager_set_server_name(n->manager, NULL); + } + + log_debug("Removed server %s.", n->string); + + free(n->string); + free(n); + + return NULL; +} + +void server_name_flush_addresses(ServerName *n) { + assert(n); + + while (n->addresses) + server_address_free(n->addresses); +} diff --git a/src/systemd-timesync/timesyncd-server.h b/src/systemd-timesync/timesyncd-server.h new file mode 100644 index 0000000000..8a19e41d67 --- /dev/null +++ b/src/systemd-timesync/timesyncd-server.h @@ -0,0 +1,65 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 . +***/ + +#include "list.h" +#include "socket-util.h" + +typedef struct ServerAddress ServerAddress; +typedef struct ServerName ServerName; + +typedef enum ServerType { + SERVER_SYSTEM, + SERVER_FALLBACK, + SERVER_LINK, +} ServerType; + +#include "timesyncd-manager.h" + +struct ServerAddress { + ServerName *name; + + union sockaddr_union sockaddr; + socklen_t socklen; + + LIST_FIELDS(ServerAddress, addresses); +}; + +struct ServerName { + Manager *manager; + + ServerType type; + char *string; + + bool marked:1; + + LIST_HEAD(ServerAddress, addresses); + LIST_FIELDS(ServerName, names); +}; + +int server_address_new(ServerName *n, ServerAddress **ret, const union sockaddr_union *sockaddr, socklen_t socklen); +ServerAddress* server_address_free(ServerAddress *a); +static inline int server_address_pretty(ServerAddress *a, char **pretty) { + return sockaddr_pretty(&a->sockaddr.sa, a->socklen, true, true, pretty); +} + +int server_name_new(Manager *m, ServerName **ret, ServerType type,const char *string); +ServerName *server_name_free(ServerName *n); +void server_name_flush_addresses(ServerName *n); diff --git a/src/systemd-timesync/timesyncd.c b/src/systemd-timesync/timesyncd.c new file mode 100644 index 0000000000..388eefac48 --- /dev/null +++ b/src/systemd-timesync/timesyncd.c @@ -0,0 +1,164 @@ +/*** + This file is part of systemd. + + Copyright 2014 Kay Sievers, 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 . +***/ + +#include +#include + +#include "capability-util.h" +#include "clock-util.h" +#include "fd-util.h" +#include "fs-util.h" +#include "network-util.h" +#include "signal-util.h" +#include "timesyncd-conf.h" +#include "timesyncd-manager.h" +#include "user-util.h" + +static int load_clock_timestamp(uid_t uid, gid_t gid) { + _cleanup_close_ int fd = -1; + usec_t min = TIME_EPOCH * USEC_PER_SEC; + usec_t ct; + int r; + + /* Let's try to make sure that the clock is always + * monotonically increasing, by saving the clock whenever we + * have a new NTP time, or when we shut down, and restoring it + * when we start again. This is particularly helpful on + * systems lacking a battery backed RTC. We also will adjust + * the time to at least the build time of systemd. */ + + fd = open("/var/lib/systemd/clock", O_RDWR|O_CLOEXEC, 0644); + if (fd >= 0) { + struct stat st; + usec_t stamp; + + /* check if the recorded time is later than the compiled-in one */ + r = fstat(fd, &st); + if (r >= 0) { + stamp = timespec_load(&st.st_mtim); + if (stamp > min) + min = stamp; + } + + /* Try to fix the access mode, so that we can still + touch the file after dropping priviliges */ + (void) fchmod(fd, 0644); + (void) fchown(fd, uid, gid); + + } else + /* create stamp file with the compiled-in date */ + (void) touch_file("/var/lib/systemd/clock", true, min, uid, gid, 0644); + + ct = now(CLOCK_REALTIME); + if (ct < min) { + struct timespec ts; + char date[FORMAT_TIMESTAMP_MAX]; + + log_info("System clock time unset or jumped backwards, restoring from recorded timestamp: %s", + format_timestamp(date, sizeof(date), min)); + + if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, min)) < 0) + log_error_errno(errno, "Failed to restore system clock: %m"); + } + + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_(manager_freep) Manager *m = NULL; + const char *user = "systemd-timesync"; + uid_t uid; + gid_t gid; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_set_facility(LOG_CRON); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argc != 1) { + log_error("This program does not take arguments."); + r = -EINVAL; + goto finish; + } + + r = get_user_creds(&user, &uid, &gid, NULL, NULL); + if (r < 0) { + log_error_errno(r, "Cannot resolve user name %s: %m", user); + goto finish; + } + + r = load_clock_timestamp(uid, gid); + if (r < 0) + goto finish; + + r = drop_privileges(uid, gid, (1ULL << CAP_SYS_TIME)); + if (r < 0) + goto finish; + + assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); + + r = manager_new(&m); + if (r < 0) { + log_error_errno(r, "Failed to allocate manager: %m"); + goto finish; + } + + if (clock_is_localtime() > 0) { + log_info("The system is configured to read the RTC time in the local time zone. " + "This mode can not be fully supported. All system time to RTC updates are disabled."); + m->rtc_local_time = true; + } + + r = manager_parse_config_file(m); + if (r < 0) + log_warning_errno(r, "Failed to parse configuration file: %m"); + + log_debug("systemd-timesyncd running as pid " PID_FMT, getpid()); + sd_notify(false, + "READY=1\n" + "STATUS=Daemon is running"); + + if (network_is_online()) { + r = manager_connect(m); + if (r < 0) + goto finish; + } + + r = sd_event_loop(m->event); + if (r < 0) { + log_error_errno(r, "Failed to run event loop: %m"); + goto finish; + } + + /* if we got an authoritative time, store it in the file system */ + if (m->sync) + (void) touch("/var/lib/systemd/clock"); + + sd_event_get_exit_code(m->event, &r); + +finish: + sd_notify(false, + "STOPPING=1\n" + "STATUS=Shutting down..."); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-timesync/timesyncd.conf.in b/src/systemd-timesync/timesyncd.conf.in new file mode 100644 index 0000000000..b6a2ada273 --- /dev/null +++ b/src/systemd-timesync/timesyncd.conf.in @@ -0,0 +1,16 @@ +# 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. +# +# Entries in this file show the compile time defaults. +# You can change settings by editing this file. +# Defaults can be restored by simply deleting this file. +# +# See timesyncd.conf(5) for details. + +[Time] +#NTP= +#FallbackNTP=@NTP_SERVERS@ diff --git a/src/systemd-tmpfiles/Makefile b/src/systemd-tmpfiles/Makefile new file mode 100644 index 0000000000..ffb01ae3d8 --- /dev/null +++ b/src/systemd-tmpfiles/Makefile @@ -0,0 +1,84 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_TMPFILES),) +systemd_tmpfiles_SOURCES = \ + src/tmpfiles/tmpfiles.c + +systemd_tmpfiles_LDADD = \ + libshared.la + +rootbin_PROGRAMS += \ + systemd-tmpfiles + +dist_systemunit_DATA += \ + units/systemd-tmpfiles-clean.timer + +nodist_systemunit_DATA += \ + units/systemd-tmpfiles-setup-dev.service \ + units/systemd-tmpfiles-setup.service \ + units/systemd-tmpfiles-clean.service + +nodist_tmpfiles_DATA = \ + tmpfiles.d/systemd.conf \ + tmpfiles.d/etc.conf + +dist_tmpfiles_DATA = \ + tmpfiles.d/systemd-nologin.conf \ + tmpfiles.d/tmp.conf \ + tmpfiles.d/x11.conf \ + tmpfiles.d/var.conf \ + tmpfiles.d/home.conf \ + tmpfiles.d/systemd-nspawn.conf \ + tmpfiles.d/journal-nocow.conf + +ifneq ($(HAVE_SYSV_COMPAT),) +dist_tmpfiles_DATA += \ + tmpfiles.d/legacy.conf +endif + +SYSINIT_TARGET_WANTS += \ + systemd-tmpfiles-setup-dev.service \ + systemd-tmpfiles-setup.service + +dist_zshcompletion_data += \ + shell-completion/zsh/_systemd-tmpfiles + +TIMERS_TARGET_WANTS += \ + systemd-tmpfiles-clean.timer + +INSTALL_DIRS += \ + $(tmpfilesdir) \ + $(sysconfdir)/tmpfiles.d +endif + +EXTRA_DIST += \ + tmpfiles.d/systemd.conf.m4 \ + tmpfiles.d/etc.conf.m4 \ + units/systemd-tmpfiles-setup-dev.service.in \ + units/systemd-tmpfiles-setup.service.in \ + units/systemd-tmpfiles-clean.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-tmpfiles/tmpfiles.c b/src/systemd-tmpfiles/tmpfiles.c new file mode 100644 index 0000000000..7b105a6bd4 --- /dev/null +++ b/src/systemd-tmpfiles/tmpfiles.c @@ -0,0 +1,2364 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering, Kay Sievers + Copyright 2015 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "acl-util.h" +#include "alloc-util.h" +#include "btrfs-util.h" +#include "capability-util.h" +#include "chattr-util.h" +#include "conf-files.h" +#include "copy.h" +#include "def.h" +#include "escape.h" +#include "fd-util.h" +#include "fileio.h" +#include "formats-util.h" +#include "fs-util.h" +#include "glob-util.h" +#include "io-util.h" +#include "label.h" +#include "log.h" +#include "macro.h" +#include "missing.h" +#include "mkdir.h" +#include "mount-util.h" +#include "parse-util.h" +#include "path-util.h" +#include "rm-rf.h" +#include "selinux-util.h" +#include "set.h" +#include "specifier.h" +#include "stat-util.h" +#include "stdio-util.h" +#include "string-table.h" +#include "string-util.h" +#include "strv.h" +#include "umask-util.h" +#include "user-util.h" +#include "util.h" + +/* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates + * them in the file system. This is intended to be used to create + * properly owned directories beneath /tmp, /var/tmp, /run, which are + * volatile and hence need to be recreated on bootup. */ + +typedef enum ItemType { + /* These ones take file names */ + CREATE_FILE = 'f', + TRUNCATE_FILE = 'F', + CREATE_DIRECTORY = 'd', + TRUNCATE_DIRECTORY = 'D', + CREATE_SUBVOLUME = 'v', + CREATE_SUBVOLUME_INHERIT_QUOTA = 'q', + CREATE_SUBVOLUME_NEW_QUOTA = 'Q', + CREATE_FIFO = 'p', + CREATE_SYMLINK = 'L', + CREATE_CHAR_DEVICE = 'c', + CREATE_BLOCK_DEVICE = 'b', + COPY_FILES = 'C', + + /* These ones take globs */ + WRITE_FILE = 'w', + SET_XATTR = 't', + RECURSIVE_SET_XATTR = 'T', + SET_ACL = 'a', + RECURSIVE_SET_ACL = 'A', + SET_ATTRIBUTE = 'h', + RECURSIVE_SET_ATTRIBUTE = 'H', + IGNORE_PATH = 'x', + IGNORE_DIRECTORY_PATH = 'X', + REMOVE_PATH = 'r', + RECURSIVE_REMOVE_PATH = 'R', + RELABEL_PATH = 'z', + RECURSIVE_RELABEL_PATH = 'Z', + ADJUST_MODE = 'm', /* legacy, 'z' is identical to this */ +} ItemType; + +typedef struct Item { + ItemType type; + + char *path; + char *argument; + char **xattrs; +#ifdef HAVE_ACL + acl_t acl_access; + acl_t acl_default; +#endif + uid_t uid; + gid_t gid; + mode_t mode; + usec_t age; + + dev_t major_minor; + unsigned attribute_value; + unsigned attribute_mask; + + bool uid_set:1; + bool gid_set:1; + bool mode_set:1; + bool age_set:1; + bool mask_perms:1; + bool attribute_set:1; + + bool keep_first_level:1; + + bool force:1; + + bool done:1; +} Item; + +typedef struct ItemArray { + Item *items; + size_t count; + size_t size; +} ItemArray; + +static bool arg_create = false; +static bool arg_clean = false; +static bool arg_remove = false; +static bool arg_boot = false; + +static char **arg_include_prefixes = NULL; +static char **arg_exclude_prefixes = NULL; +static char *arg_root = NULL; + +static const char conf_file_dirs[] = CONF_PATHS_NULSTR("tmpfiles.d"); + +#define MAX_DEPTH 256 + +static OrderedHashmap *items = NULL, *globs = NULL; +static Set *unix_sockets = NULL; + +static const Specifier specifier_table[] = { + { 'm', specifier_machine_id, NULL }, + { 'b', specifier_boot_id, NULL }, + { 'H', specifier_host_name, NULL }, + { 'v', specifier_kernel_release, NULL }, + {} +}; + +static bool needs_glob(ItemType t) { + return IN_SET(t, + WRITE_FILE, + IGNORE_PATH, + IGNORE_DIRECTORY_PATH, + REMOVE_PATH, + RECURSIVE_REMOVE_PATH, + ADJUST_MODE, + RELABEL_PATH, + RECURSIVE_RELABEL_PATH, + SET_XATTR, + RECURSIVE_SET_XATTR, + SET_ACL, + RECURSIVE_SET_ACL, + SET_ATTRIBUTE, + RECURSIVE_SET_ATTRIBUTE); +} + +static bool takes_ownership(ItemType t) { + return IN_SET(t, + CREATE_FILE, + TRUNCATE_FILE, + CREATE_DIRECTORY, + TRUNCATE_DIRECTORY, + CREATE_SUBVOLUME, + CREATE_SUBVOLUME_INHERIT_QUOTA, + CREATE_SUBVOLUME_NEW_QUOTA, + CREATE_FIFO, + CREATE_SYMLINK, + CREATE_CHAR_DEVICE, + CREATE_BLOCK_DEVICE, + COPY_FILES, + WRITE_FILE, + IGNORE_PATH, + IGNORE_DIRECTORY_PATH, + REMOVE_PATH, + RECURSIVE_REMOVE_PATH); +} + +static struct Item* find_glob(OrderedHashmap *h, const char *match) { + ItemArray *j; + Iterator i; + + ORDERED_HASHMAP_FOREACH(j, h, i) { + unsigned n; + + for (n = 0; n < j->count; n++) { + Item *item = j->items + n; + + if (fnmatch(item->path, match, FNM_PATHNAME|FNM_PERIOD) == 0) + return item; + } + } + + return NULL; +} + +static void load_unix_sockets(void) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + + if (unix_sockets) + return; + + /* We maintain a cache of the sockets we found in + * /proc/net/unix to speed things up a little. */ + + unix_sockets = set_new(&string_hash_ops); + if (!unix_sockets) + return; + + f = fopen("/proc/net/unix", "re"); + if (!f) + return; + + /* Skip header */ + if (!fgets(line, sizeof(line), f)) + goto fail; + + for (;;) { + char *p, *s; + int k; + + if (!fgets(line, sizeof(line), f)) + break; + + truncate_nl(line); + + p = strchr(line, ':'); + if (!p) + continue; + + if (strlen(p) < 37) + continue; + + p += 37; + p += strspn(p, WHITESPACE); + p += strcspn(p, WHITESPACE); /* skip one more word */ + p += strspn(p, WHITESPACE); + + if (*p != '/') + continue; + + s = strdup(p); + if (!s) + goto fail; + + path_kill_slashes(s); + + k = set_consume(unix_sockets, s); + if (k < 0 && k != -EEXIST) + goto fail; + } + + return; + +fail: + set_free_free(unix_sockets); + unix_sockets = NULL; +} + +static bool unix_socket_alive(const char *fn) { + assert(fn); + + load_unix_sockets(); + + if (unix_sockets) + return !!set_get(unix_sockets, (char*) fn); + + /* We don't know, so assume yes */ + return true; +} + +static int dir_is_mount_point(DIR *d, const char *subdir) { + + union file_handle_union h = FILE_HANDLE_INIT; + int mount_id_parent, mount_id; + int r_p, r; + + r_p = name_to_handle_at(dirfd(d), ".", &h.handle, &mount_id_parent, 0); + if (r_p < 0) + r_p = -errno; + + h.handle.handle_bytes = MAX_HANDLE_SZ; + r = name_to_handle_at(dirfd(d), subdir, &h.handle, &mount_id, 0); + if (r < 0) + r = -errno; + + /* got no handle; make no assumptions, return error */ + if (r_p < 0 && r < 0) + return r_p; + + /* got both handles; if they differ, it is a mount point */ + if (r_p >= 0 && r >= 0) + return mount_id_parent != mount_id; + + /* got only one handle; assume different mount points if one + * of both queries was not supported by the filesystem */ + if (r_p == -ENOSYS || r_p == -EOPNOTSUPP || r == -ENOSYS || r == -EOPNOTSUPP) + return true; + + /* return error */ + if (r_p < 0) + return r_p; + return r; +} + +static DIR* xopendirat_nomod(int dirfd, const char *path) { + DIR *dir; + + dir = xopendirat(dirfd, path, O_NOFOLLOW|O_NOATIME); + if (dir) + return dir; + + log_debug_errno(errno, "Cannot open %sdirectory \"%s\": %m", dirfd == AT_FDCWD ? "" : "sub", path); + if (errno != EPERM) + return NULL; + + dir = xopendirat(dirfd, path, O_NOFOLLOW); + if (!dir) + log_debug_errno(errno, "Cannot open %sdirectory \"%s\": %m", dirfd == AT_FDCWD ? "" : "sub", path); + + return dir; +} + +static DIR* opendir_nomod(const char *path) { + return xopendirat_nomod(AT_FDCWD, path); +} + +static int dir_cleanup( + Item *i, + const char *p, + DIR *d, + const struct stat *ds, + usec_t cutoff, + dev_t rootdev, + bool mountpoint, + int maxdepth, + bool keep_this_level) { + + struct dirent *dent; + struct timespec times[2]; + bool deleted = false; + int r = 0; + + while ((dent = readdir(d))) { + struct stat s; + usec_t age; + _cleanup_free_ char *sub_path = NULL; + + if (STR_IN_SET(dent->d_name, ".", "..")) + continue; + + if (fstatat(dirfd(d), dent->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) { + if (errno == ENOENT) + continue; + + /* FUSE, NFS mounts, SELinux might return EACCES */ + if (errno == EACCES) + log_debug_errno(errno, "stat(%s/%s) failed: %m", p, dent->d_name); + else + log_error_errno(errno, "stat(%s/%s) failed: %m", p, dent->d_name); + r = -errno; + continue; + } + + /* Stay on the same filesystem */ + if (s.st_dev != rootdev) { + log_debug("Ignoring \"%s/%s\": different filesystem.", p, dent->d_name); + continue; + } + + /* Try to detect bind mounts of the same filesystem instance; they + * do not differ in device major/minors. This type of query is not + * supported on all kernels or filesystem types though. */ + if (S_ISDIR(s.st_mode) && dir_is_mount_point(d, dent->d_name) > 0) { + log_debug("Ignoring \"%s/%s\": different mount of the same filesystem.", + p, dent->d_name); + continue; + } + + /* Do not delete read-only files owned by root */ + if (s.st_uid == 0 && !(s.st_mode & S_IWUSR)) { + log_debug("Ignoring \"%s/%s\": read-only and owner by root.", p, dent->d_name); + continue; + } + + sub_path = strjoin(p, "/", dent->d_name, NULL); + if (!sub_path) { + r = log_oom(); + goto finish; + } + + /* Is there an item configured for this path? */ + if (ordered_hashmap_get(items, sub_path)) { + log_debug("Ignoring \"%s\": a separate entry exists.", sub_path); + continue; + } + + if (find_glob(globs, sub_path)) { + log_debug("Ignoring \"%s\": a separate glob exists.", sub_path); + continue; + } + + if (S_ISDIR(s.st_mode)) { + + if (mountpoint && + streq(dent->d_name, "lost+found") && + s.st_uid == 0) { + log_debug("Ignoring \"%s\".", sub_path); + continue; + } + + if (maxdepth <= 0) + log_warning("Reached max depth on \"%s\".", sub_path); + else { + _cleanup_closedir_ DIR *sub_dir; + int q; + + sub_dir = xopendirat_nomod(dirfd(d), dent->d_name); + if (!sub_dir) { + if (errno != ENOENT) + r = log_error_errno(errno, "opendir(%s) failed: %m", sub_path); + + continue; + } + + q = dir_cleanup(i, sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1, false); + if (q < 0) + r = q; + } + + /* Note: if you are wondering why we don't + * support the sticky bit for excluding + * directories from cleaning like we do it for + * other file system objects: well, the sticky + * bit already has a meaning for directories, + * so we don't want to overload that. */ + + if (keep_this_level) { + log_debug("Keeping \"%s\".", sub_path); + continue; + } + + /* Ignore ctime, we change it when deleting */ + age = timespec_load(&s.st_mtim); + if (age >= cutoff) { + char a[FORMAT_TIMESTAMP_MAX]; + /* Follows spelling in stat(1). */ + log_debug("Directory \"%s\": modify time %s is too new.", + sub_path, + format_timestamp_us(a, sizeof(a), age)); + continue; + } + + age = timespec_load(&s.st_atim); + if (age >= cutoff) { + char a[FORMAT_TIMESTAMP_MAX]; + log_debug("Directory \"%s\": access time %s is too new.", + sub_path, + format_timestamp_us(a, sizeof(a), age)); + continue; + } + + log_debug("Removing directory \"%s\".", sub_path); + if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0) + if (errno != ENOENT && errno != ENOTEMPTY) { + log_error_errno(errno, "rmdir(%s): %m", sub_path); + r = -errno; + } + + } else { + /* Skip files for which the sticky bit is + * set. These are semantics we define, and are + * unknown elsewhere. See XDG_RUNTIME_DIR + * specification for details. */ + if (s.st_mode & S_ISVTX) { + log_debug("Skipping \"%s\": sticky bit set.", sub_path); + continue; + } + + if (mountpoint && S_ISREG(s.st_mode)) + if (s.st_uid == 0 && STR_IN_SET(dent->d_name, + ".journal", + "aquota.user", + "aquota.group")) { + log_debug("Skipping \"%s\".", sub_path); + continue; + } + + /* Ignore sockets that are listed in /proc/net/unix */ + if (S_ISSOCK(s.st_mode) && unix_socket_alive(sub_path)) { + log_debug("Skipping \"%s\": live socket.", sub_path); + continue; + } + + /* Ignore device nodes */ + if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode)) { + log_debug("Skipping \"%s\": a device.", sub_path); + continue; + } + + /* Keep files on this level around if this is + * requested */ + if (keep_this_level) { + log_debug("Keeping \"%s\".", sub_path); + continue; + } + + age = timespec_load(&s.st_mtim); + if (age >= cutoff) { + char a[FORMAT_TIMESTAMP_MAX]; + /* Follows spelling in stat(1). */ + log_debug("File \"%s\": modify time %s is too new.", + sub_path, + format_timestamp_us(a, sizeof(a), age)); + continue; + } + + age = timespec_load(&s.st_atim); + if (age >= cutoff) { + char a[FORMAT_TIMESTAMP_MAX]; + log_debug("File \"%s\": access time %s is too new.", + sub_path, + format_timestamp_us(a, sizeof(a), age)); + continue; + } + + age = timespec_load(&s.st_ctim); + if (age >= cutoff) { + char a[FORMAT_TIMESTAMP_MAX]; + log_debug("File \"%s\": change time %s is too new.", + sub_path, + format_timestamp_us(a, sizeof(a), age)); + continue; + } + + log_debug("unlink \"%s\"", sub_path); + + if (unlinkat(dirfd(d), dent->d_name, 0) < 0) + if (errno != ENOENT) + r = log_error_errno(errno, "unlink(%s): %m", sub_path); + + deleted = true; + } + } + +finish: + if (deleted) { + usec_t age1, age2; + char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX]; + + /* Restore original directory timestamps */ + times[0] = ds->st_atim; + times[1] = ds->st_mtim; + + age1 = timespec_load(&ds->st_atim); + age2 = timespec_load(&ds->st_mtim); + log_debug("Restoring access and modification time on \"%s\": %s, %s", + p, + format_timestamp_us(a, sizeof(a), age1), + format_timestamp_us(b, sizeof(b), age2)); + if (futimens(dirfd(d), times) < 0) + log_error_errno(errno, "utimensat(%s): %m", p); + } + + return r; +} + +static int path_set_perms(Item *i, const char *path) { + _cleanup_close_ int fd = -1; + struct stat st; + + assert(i); + assert(path); + + /* We open the file with O_PATH here, to make the operation + * somewhat atomic. Also there's unfortunately no fchmodat() + * with AT_SYMLINK_NOFOLLOW, hence we emulate it here via + * O_PATH. */ + + fd = open(path, O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH|O_NOATIME); + if (fd < 0) + return log_error_errno(errno, "Adjusting owner and mode for %s failed: %m", path); + + if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0) + return log_error_errno(errno, "Failed to fstat() file %s: %m", path); + + if (S_ISLNK(st.st_mode)) + log_debug("Skipping mode an owner fix for symlink %s.", path); + else { + char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; + xsprintf(fn, "/proc/self/fd/%i", fd); + + /* not using i->path directly because it may be a glob */ + if (i->mode_set) { + mode_t m = i->mode; + + if (i->mask_perms) { + if (!(st.st_mode & 0111)) + m &= ~0111; + if (!(st.st_mode & 0222)) + m &= ~0222; + if (!(st.st_mode & 0444)) + m &= ~0444; + if (!S_ISDIR(st.st_mode)) + m &= ~07000; /* remove sticky/sgid/suid bit, unless directory */ + } + + if (m == (st.st_mode & 07777)) + log_debug("\"%s\" has right mode %o", path, st.st_mode); + else { + log_debug("chmod \"%s\" to mode %o", path, m); + if (chmod(fn, m) < 0) + return log_error_errno(errno, "chmod(%s) failed: %m", path); + } + } + + if ((i->uid != st.st_uid || i->gid != st.st_gid) && + (i->uid_set || i->gid_set)) { + log_debug("chown \"%s\" to "UID_FMT"."GID_FMT, + path, + i->uid_set ? i->uid : UID_INVALID, + i->gid_set ? i->gid : GID_INVALID); + if (chown(fn, + i->uid_set ? i->uid : UID_INVALID, + i->gid_set ? i->gid : GID_INVALID) < 0) + return log_error_errno(errno, "chown(%s) failed: %m", path); + } + } + + fd = safe_close(fd); + + return label_fix(path, false, false); +} + +static int parse_xattrs_from_arg(Item *i) { + const char *p; + int r; + + assert(i); + assert(i->argument); + + p = i->argument; + + for (;;) { + _cleanup_free_ char *name = NULL, *value = NULL, *xattr = NULL, *xattr_replaced = NULL; + + r = extract_first_word(&p, &xattr, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE); + if (r < 0) + log_warning_errno(r, "Failed to parse extended attribute '%s', ignoring: %m", p); + if (r <= 0) + break; + + r = specifier_printf(xattr, specifier_table, NULL, &xattr_replaced); + if (r < 0) + return log_error_errno(r, "Failed to replace specifiers in extended attribute '%s': %m", xattr); + + r = split_pair(xattr_replaced, "=", &name, &value); + if (r < 0) { + log_warning_errno(r, "Failed to parse extended attribute, ignoring: %s", xattr); + continue; + } + + if (isempty(name) || isempty(value)) { + log_warning("Malformed extended attribute found, ignoring: %s", xattr); + continue; + } + + if (strv_push_pair(&i->xattrs, name, value) < 0) + return log_oom(); + + name = value = NULL; + } + + return 0; +} + +static int path_set_xattrs(Item *i, const char *path) { + char **name, **value; + + assert(i); + assert(path); + + STRV_FOREACH_PAIR(name, value, i->xattrs) { + int n; + + n = strlen(*value); + log_debug("Setting extended attribute '%s=%s' on %s.", *name, *value, path); + if (lsetxattr(path, *name, *value, n, 0) < 0) { + log_error("Setting extended attribute %s=%s on %s failed: %m", *name, *value, path); + return -errno; + } + } + return 0; +} + +static int parse_acls_from_arg(Item *item) { +#ifdef HAVE_ACL + int r; + + assert(item); + + /* If force (= modify) is set, we will not modify the acl + * afterwards, so the mask can be added now if necessary. */ + + r = parse_acl(item->argument, &item->acl_access, &item->acl_default, !item->force); + if (r < 0) + log_warning_errno(r, "Failed to parse ACL \"%s\": %m. Ignoring", item->argument); +#else + log_warning_errno(ENOSYS, "ACLs are not supported. Ignoring"); +#endif + + return 0; +} + +#ifdef HAVE_ACL +static int path_set_acl(const char *path, const char *pretty, acl_type_t type, acl_t acl, bool modify) { + _cleanup_(acl_free_charpp) char *t = NULL; + _cleanup_(acl_freep) acl_t dup = NULL; + int r; + + /* Returns 0 for success, positive error if already warned, + * negative error otherwise. */ + + if (modify) { + r = acls_for_file(path, type, acl, &dup); + if (r < 0) + return r; + + r = calc_acl_mask_if_needed(&dup); + if (r < 0) + return r; + } else { + dup = acl_dup(acl); + if (!dup) + return -errno; + + /* the mask was already added earlier if needed */ + } + + r = add_base_acls_if_needed(&dup, path); + if (r < 0) + return r; + + t = acl_to_any_text(dup, NULL, ',', TEXT_ABBREVIATE); + log_debug("Setting %s ACL %s on %s.", + type == ACL_TYPE_ACCESS ? "access" : "default", + strna(t), pretty); + + r = acl_set_file(path, type, dup); + if (r < 0) + /* Return positive to indicate we already warned */ + return -log_error_errno(errno, + "Setting %s ACL \"%s\" on %s failed: %m", + type == ACL_TYPE_ACCESS ? "access" : "default", + strna(t), pretty); + + return 0; +} +#endif + +static int path_set_acls(Item *item, const char *path) { + int r = 0; +#ifdef HAVE_ACL + char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; + _cleanup_close_ int fd = -1; + struct stat st; + + assert(item); + assert(path); + + fd = open(path, O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH|O_NOATIME); + if (fd < 0) + return log_error_errno(errno, "Adjusting ACL of %s failed: %m", path); + + if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0) + return log_error_errno(errno, "Failed to fstat() file %s: %m", path); + + if (S_ISLNK(st.st_mode)) { + log_debug("Skipping ACL fix for symlink %s.", path); + return 0; + } + + xsprintf(fn, "/proc/self/fd/%i", fd); + + if (item->acl_access) + r = path_set_acl(fn, path, ACL_TYPE_ACCESS, item->acl_access, item->force); + + if (r == 0 && item->acl_default) + r = path_set_acl(fn, path, ACL_TYPE_DEFAULT, item->acl_default, item->force); + + if (r > 0) + return -r; /* already warned */ + else if (r == -EOPNOTSUPP) { + log_debug_errno(r, "ACLs not supported by file system at %s", path); + return 0; + } else if (r < 0) + log_error_errno(r, "ACL operation on \"%s\" failed: %m", path); +#endif + return r; +} + +#define ATTRIBUTES_ALL \ + (FS_NOATIME_FL | \ + FS_SYNC_FL | \ + FS_DIRSYNC_FL | \ + FS_APPEND_FL | \ + FS_COMPR_FL | \ + FS_NODUMP_FL | \ + FS_EXTENT_FL | \ + FS_IMMUTABLE_FL | \ + FS_JOURNAL_DATA_FL | \ + FS_SECRM_FL | \ + FS_UNRM_FL | \ + FS_NOTAIL_FL | \ + FS_TOPDIR_FL | \ + FS_NOCOW_FL) + +static int parse_attribute_from_arg(Item *item) { + + static const struct { + char character; + unsigned value; + } attributes[] = { + { 'A', FS_NOATIME_FL }, /* do not update atime */ + { 'S', FS_SYNC_FL }, /* Synchronous updates */ + { 'D', FS_DIRSYNC_FL }, /* dirsync behaviour (directories only) */ + { 'a', FS_APPEND_FL }, /* writes to file may only append */ + { 'c', FS_COMPR_FL }, /* Compress file */ + { 'd', FS_NODUMP_FL }, /* do not dump file */ + { 'e', FS_EXTENT_FL }, /* Top of directory hierarchies*/ + { 'i', FS_IMMUTABLE_FL }, /* Immutable file */ + { 'j', FS_JOURNAL_DATA_FL }, /* Reserved for ext3 */ + { 's', FS_SECRM_FL }, /* Secure deletion */ + { 'u', FS_UNRM_FL }, /* Undelete */ + { 't', FS_NOTAIL_FL }, /* file tail should not be merged */ + { 'T', FS_TOPDIR_FL }, /* Top of directory hierarchies*/ + { 'C', FS_NOCOW_FL }, /* Do not cow file */ + }; + + enum { + MODE_ADD, + MODE_DEL, + MODE_SET + } mode = MODE_ADD; + + unsigned value = 0, mask = 0; + const char *p; + + assert(item); + + p = item->argument; + if (p) { + if (*p == '+') { + mode = MODE_ADD; + p++; + } else if (*p == '-') { + mode = MODE_DEL; + p++; + } else if (*p == '=') { + mode = MODE_SET; + p++; + } + } + + if (isempty(p) && mode != MODE_SET) { + log_error("Setting file attribute on '%s' needs an attribute specification.", item->path); + return -EINVAL; + } + + for (; p && *p ; p++) { + unsigned i, v; + + for (i = 0; i < ELEMENTSOF(attributes); i++) + if (*p == attributes[i].character) + break; + + if (i >= ELEMENTSOF(attributes)) { + log_error("Unknown file attribute '%c' on '%s'.", *p, item->path); + return -EINVAL; + } + + v = attributes[i].value; + + if (mode == MODE_ADD || mode == MODE_SET) + value |= v; + else + value &= ~v; + + mask |= v; + } + + if (mode == MODE_SET) + mask |= ATTRIBUTES_ALL; + + assert(mask != 0); + + item->attribute_mask = mask; + item->attribute_value = value; + item->attribute_set = true; + + return 0; +} + +static int path_set_attribute(Item *item, const char *path) { + _cleanup_close_ int fd = -1; + struct stat st; + unsigned f; + int r; + + if (!item->attribute_set || item->attribute_mask == 0) + return 0; + + fd = open(path, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOATIME|O_NOFOLLOW); + if (fd < 0) { + if (errno == ELOOP) + return log_error_errno(errno, "Skipping file attributes adjustment on symlink %s.", path); + + return log_error_errno(errno, "Cannot open '%s': %m", path); + } + + if (fstat(fd, &st) < 0) + return log_error_errno(errno, "Cannot stat '%s': %m", path); + + /* Issuing the file attribute ioctls on device nodes is not + * safe, as that will be delivered to the drivers, not the + * file system containing the device node. */ + if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) { + log_error("Setting file flags is only supported on regular files and directories, cannot set on '%s'.", path); + return -EINVAL; + } + + f = item->attribute_value & item->attribute_mask; + + /* Mask away directory-specific flags */ + if (!S_ISDIR(st.st_mode)) + f &= ~FS_DIRSYNC_FL; + + r = chattr_fd(fd, f, item->attribute_mask); + if (r < 0) + log_full_errno(r == -ENOTTY ? LOG_DEBUG : LOG_WARNING, + r, + "Cannot set file attribute for '%s', value=0x%08x, mask=0x%08x: %m", + path, item->attribute_value, item->attribute_mask); + + return 0; +} + +static int write_one_file(Item *i, const char *path) { + _cleanup_close_ int fd = -1; + int flags, r = 0; + struct stat st; + + assert(i); + assert(path); + + flags = i->type == CREATE_FILE ? O_CREAT|O_APPEND|O_NOFOLLOW : + i->type == TRUNCATE_FILE ? O_CREAT|O_TRUNC|O_NOFOLLOW : 0; + + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(path, S_IFREG); + fd = open(path, flags|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode); + mac_selinux_create_file_clear(); + } + + if (fd < 0) { + if (i->type == WRITE_FILE && errno == ENOENT) { + log_debug_errno(errno, "Not writing \"%s\": %m", path); + return 0; + } + + r = -errno; + if (!i->argument && errno == EROFS && stat(path, &st) == 0 && + (i->type == CREATE_FILE || st.st_size == 0)) + goto check_mode; + + return log_error_errno(r, "Failed to create file %s: %m", path); + } + + if (i->argument) { + _cleanup_free_ char *unescaped = NULL, *replaced = NULL; + + log_debug("%s to \"%s\".", i->type == CREATE_FILE ? "Appending" : "Writing", path); + + r = cunescape(i->argument, 0, &unescaped); + if (r < 0) + return log_error_errno(r, "Failed to unescape parameter to write: %s", i->argument); + + r = specifier_printf(unescaped, specifier_table, NULL, &replaced); + if (r < 0) + return log_error_errno(r, "Failed to replace specifiers in parameter to write '%s': %m", unescaped); + + r = loop_write(fd, replaced, strlen(replaced), false); + if (r < 0) + return log_error_errno(r, "Failed to write file \"%s\": %m", path); + } else + log_debug("\"%s\" has been created.", path); + + fd = safe_close(fd); + + if (stat(path, &st) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", path); + + check_mode: + if (!S_ISREG(st.st_mode)) { + log_error("%s is not a file.", path); + return -EEXIST; + } + + r = path_set_perms(i, path); + if (r < 0) + return r; + + return 0; +} + +typedef int (*action_t)(Item *, const char *); + +static int item_do_children(Item *i, const char *path, action_t action) { + _cleanup_closedir_ DIR *d; + int r = 0; + + assert(i); + assert(path); + + /* This returns the first error we run into, but nevertheless + * tries to go on */ + + d = opendir_nomod(path); + if (!d) + return errno == ENOENT || errno == ENOTDIR ? 0 : -errno; + + for (;;) { + _cleanup_free_ char *p = NULL; + struct dirent *de; + int q; + + errno = 0; + de = readdir(d); + if (!de) { + if (errno > 0 && r == 0) + r = -errno; + + break; + } + + if (STR_IN_SET(de->d_name, ".", "..")) + continue; + + p = strjoin(path, "/", de->d_name, NULL); + if (!p) + return -ENOMEM; + + q = action(i, p); + if (q < 0 && q != -ENOENT && r == 0) + r = q; + + if (IN_SET(de->d_type, DT_UNKNOWN, DT_DIR)) { + q = item_do_children(i, p, action); + if (q < 0 && r == 0) + r = q; + } + } + + return r; +} + +static int glob_item(Item *i, action_t action, bool recursive) { + _cleanup_globfree_ glob_t g = { + .gl_closedir = (void (*)(void *)) closedir, + .gl_readdir = (struct dirent *(*)(void *)) readdir, + .gl_opendir = (void *(*)(const char *)) opendir_nomod, + .gl_lstat = lstat, + .gl_stat = stat, + }; + int r = 0, k; + char **fn; + + errno = 0; + k = glob(i->path, GLOB_NOSORT|GLOB_BRACE|GLOB_ALTDIRFUNC, NULL, &g); + if (k != 0 && k != GLOB_NOMATCH) + return log_error_errno(errno ?: EIO, "glob(%s) failed: %m", i->path); + + STRV_FOREACH(fn, g.gl_pathv) { + k = action(i, *fn); + if (k < 0 && r == 0) + r = k; + + if (recursive) { + k = item_do_children(i, *fn, action); + if (k < 0 && r == 0) + r = k; + } + } + + return r; +} + +typedef enum { + CREATION_NORMAL, + CREATION_EXISTING, + CREATION_FORCE, + _CREATION_MODE_MAX, + _CREATION_MODE_INVALID = -1 +} CreationMode; + +static const char *creation_mode_verb_table[_CREATION_MODE_MAX] = { + [CREATION_NORMAL] = "Created", + [CREATION_EXISTING] = "Found existing", + [CREATION_FORCE] = "Created replacement", +}; + +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(creation_mode_verb, CreationMode); + +static int create_item(Item *i) { + _cleanup_free_ char *resolved = NULL; + struct stat st; + int r = 0; + int q = 0; + CreationMode creation; + + assert(i); + + log_debug("Running create action for entry %c %s", (char) i->type, i->path); + + switch (i->type) { + + case IGNORE_PATH: + case IGNORE_DIRECTORY_PATH: + case REMOVE_PATH: + case RECURSIVE_REMOVE_PATH: + return 0; + + case CREATE_FILE: + case TRUNCATE_FILE: + r = write_one_file(i, i->path); + if (r < 0) + return r; + break; + + case COPY_FILES: { + r = specifier_printf(i->argument, specifier_table, NULL, &resolved); + if (r < 0) + return log_error_errno(r, "Failed to substitute specifiers in copy source %s: %m", i->argument); + + log_debug("Copying tree \"%s\" to \"%s\".", resolved, i->path); + r = copy_tree(resolved, i->path, false); + + if (r == -EROFS && stat(i->path, &st) == 0) + r = -EEXIST; + + if (r < 0) { + struct stat a, b; + + if (r != -EEXIST) + return log_error_errno(r, "Failed to copy files to %s: %m", i->path); + + if (stat(resolved, &a) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", resolved); + + if (stat(i->path, &b) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", i->path); + + if ((a.st_mode ^ b.st_mode) & S_IFMT) { + log_debug("Can't copy to %s, file exists already and is of different type", i->path); + return 0; + } + } + + r = path_set_perms(i, i->path); + if (r < 0) + return r; + + break; + + case WRITE_FILE: + r = glob_item(i, write_one_file, false); + if (r < 0) + return r; + + break; + + case CREATE_DIRECTORY: + case TRUNCATE_DIRECTORY: + case CREATE_SUBVOLUME: + case CREATE_SUBVOLUME_INHERIT_QUOTA: + case CREATE_SUBVOLUME_NEW_QUOTA: + + RUN_WITH_UMASK(0000) + mkdir_parents_label(i->path, 0755); + + if (IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA)) { + + if (btrfs_is_subvol(isempty(arg_root) ? "/" : arg_root) <= 0) + + /* Don't create a subvolume unless the + * root directory is one, too. We do + * this under the assumption that if + * the root directory is just a plain + * directory (i.e. very light-weight), + * we shouldn't try to split it up + * into subvolumes (i.e. more + * heavy-weight). Thus, chroot() + * environments and suchlike will get + * a full brtfs subvolume set up below + * their tree only if they + * specifically set up a btrfs + * subvolume for the root dir too. */ + + r = -ENOTTY; + else { + RUN_WITH_UMASK((~i->mode) & 0777) + r = btrfs_subvol_make(i->path); + } + } else + r = 0; + + if (IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY) || r == -ENOTTY) + RUN_WITH_UMASK(0000) + r = mkdir_label(i->path, i->mode); + + if (r < 0) { + int k; + + if (r != -EEXIST && r != -EROFS) + return log_error_errno(r, "Failed to create directory or subvolume \"%s\": %m", i->path); + + k = is_dir(i->path, false); + if (k == -ENOENT && r == -EROFS) + return log_error_errno(r, "%s does not exist and cannot be created as the file system is read-only.", i->path); + if (k < 0) + return log_error_errno(k, "Failed to check if %s exists: %m", i->path); + if (!k) { + log_warning("\"%s\" already exists and is not a directory.", i->path); + return 0; + } + + creation = CREATION_EXISTING; + } else + creation = CREATION_NORMAL; + + log_debug("%s directory \"%s\".", creation_mode_verb_to_string(creation), i->path); + + if (IN_SET(i->type, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)) { + r = btrfs_subvol_auto_qgroup(i->path, 0, i->type == CREATE_SUBVOLUME_NEW_QUOTA); + if (r == -ENOTTY) + log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" because of unsupported file system or because directory is not a subvolume: %m", i->path); + else if (r == -EROFS) + log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" because of read-only file system: %m", i->path); + else if (r == -ENOPROTOOPT) + log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" because quota support is disabled: %m", i->path); + else if (r < 0) + q = log_error_errno(r, "Failed to adjust quota for subvolume \"%s\": %m", i->path); + else if (r > 0) + log_debug("Adjusted quota for subvolume \"%s\".", i->path); + else if (r == 0) + log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path); + } + + r = path_set_perms(i, i->path); + if (q < 0) + return q; + if (r < 0) + return r; + + break; + + case CREATE_FIFO: + + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(i->path, S_IFIFO); + r = mkfifo(i->path, i->mode); + mac_selinux_create_file_clear(); + } + + if (r < 0) { + if (errno != EEXIST) + return log_error_errno(errno, "Failed to create fifo %s: %m", i->path); + + if (lstat(i->path, &st) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", i->path); + + if (!S_ISFIFO(st.st_mode)) { + + if (i->force) { + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(i->path, S_IFIFO); + r = mkfifo_atomic(i->path, i->mode); + mac_selinux_create_file_clear(); + } + + if (r < 0) + return log_error_errno(r, "Failed to create fifo %s: %m", i->path); + creation = CREATION_FORCE; + } else { + log_warning("\"%s\" already exists and is not a fifo.", i->path); + return 0; + } + } else + creation = CREATION_EXISTING; + } else + creation = CREATION_NORMAL; + log_debug("%s fifo \"%s\".", creation_mode_verb_to_string(creation), i->path); + + r = path_set_perms(i, i->path); + if (r < 0) + return r; + + break; + } + + case CREATE_SYMLINK: { + r = specifier_printf(i->argument, specifier_table, NULL, &resolved); + if (r < 0) + return log_error_errno(r, "Failed to substitute specifiers in symlink target %s: %m", i->argument); + + mac_selinux_create_file_prepare(i->path, S_IFLNK); + r = symlink(resolved, i->path); + mac_selinux_create_file_clear(); + + if (r < 0) { + _cleanup_free_ char *x = NULL; + + if (errno != EEXIST) + return log_error_errno(errno, "symlink(%s, %s) failed: %m", resolved, i->path); + + r = readlink_malloc(i->path, &x); + if (r < 0 || !streq(resolved, x)) { + + if (i->force) { + mac_selinux_create_file_prepare(i->path, S_IFLNK); + r = symlink_atomic(resolved, i->path); + mac_selinux_create_file_clear(); + + if (r < 0) + return log_error_errno(r, "symlink(%s, %s) failed: %m", resolved, i->path); + + creation = CREATION_FORCE; + } else { + log_debug("\"%s\" is not a symlink or does not point to the correct path.", i->path); + return 0; + } + } else + creation = CREATION_EXISTING; + } else + + creation = CREATION_NORMAL; + log_debug("%s symlink \"%s\".", creation_mode_verb_to_string(creation), i->path); + break; + } + + case CREATE_BLOCK_DEVICE: + case CREATE_CHAR_DEVICE: { + mode_t file_type; + + if (have_effective_cap(CAP_MKNOD) == 0) { + /* In a container we lack CAP_MKNOD. We + shouldn't attempt to create the device node in + that case to avoid noise, and we don't support + virtualized devices in containers anyway. */ + + log_debug("We lack CAP_MKNOD, skipping creation of device node %s.", i->path); + return 0; + } + + file_type = i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR; + + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(i->path, file_type); + r = mknod(i->path, i->mode | file_type, i->major_minor); + mac_selinux_create_file_clear(); + } + + if (r < 0) { + if (errno == EPERM) { + log_debug("We lack permissions, possibly because of cgroup configuration; " + "skipping creation of device node %s.", i->path); + return 0; + } + + if (errno != EEXIST) + return log_error_errno(errno, "Failed to create device node %s: %m", i->path); + + if (lstat(i->path, &st) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", i->path); + + if ((st.st_mode & S_IFMT) != file_type) { + + if (i->force) { + + RUN_WITH_UMASK(0000) { + mac_selinux_create_file_prepare(i->path, file_type); + r = mknod_atomic(i->path, i->mode | file_type, i->major_minor); + mac_selinux_create_file_clear(); + } + + if (r < 0) + return log_error_errno(r, "Failed to create device node \"%s\": %m", i->path); + creation = CREATION_FORCE; + } else { + log_debug("%s is not a device node.", i->path); + return 0; + } + } else + creation = CREATION_EXISTING; + } else + creation = CREATION_NORMAL; + + log_debug("%s %s device node \"%s\" %u:%u.", + creation_mode_verb_to_string(creation), + i->type == CREATE_BLOCK_DEVICE ? "block" : "char", + i->path, major(i->mode), minor(i->mode)); + + r = path_set_perms(i, i->path); + if (r < 0) + return r; + + break; + } + + case ADJUST_MODE: + case RELABEL_PATH: + r = glob_item(i, path_set_perms, false); + if (r < 0) + return r; + break; + + case RECURSIVE_RELABEL_PATH: + r = glob_item(i, path_set_perms, true); + if (r < 0) + return r; + break; + + case SET_XATTR: + r = glob_item(i, path_set_xattrs, false); + if (r < 0) + return r; + break; + + case RECURSIVE_SET_XATTR: + r = glob_item(i, path_set_xattrs, true); + if (r < 0) + return r; + break; + + case SET_ACL: + r = glob_item(i, path_set_acls, false); + if (r < 0) + return r; + break; + + case RECURSIVE_SET_ACL: + r = glob_item(i, path_set_acls, true); + if (r < 0) + return r; + break; + + case SET_ATTRIBUTE: + r = glob_item(i, path_set_attribute, false); + if (r < 0) + return r; + break; + + case RECURSIVE_SET_ATTRIBUTE: + r = glob_item(i, path_set_attribute, true); + if (r < 0) + return r; + break; + } + + return 0; +} + +static int remove_item_instance(Item *i, const char *instance) { + int r; + + assert(i); + + switch (i->type) { + + case REMOVE_PATH: + if (remove(instance) < 0 && errno != ENOENT) + return log_error_errno(errno, "rm(%s): %m", instance); + + break; + + case TRUNCATE_DIRECTORY: + case RECURSIVE_REMOVE_PATH: + /* FIXME: we probably should use dir_cleanup() here + * instead of rm_rf() so that 'x' is honoured. */ + log_debug("rm -rf \"%s\"", instance); + r = rm_rf(instance, (i->type == RECURSIVE_REMOVE_PATH ? REMOVE_ROOT|REMOVE_SUBVOLUME : 0) | REMOVE_PHYSICAL); + if (r < 0 && r != -ENOENT) + return log_error_errno(r, "rm_rf(%s): %m", instance); + + break; + + default: + assert_not_reached("wut?"); + } + + return 0; +} + +static int remove_item(Item *i) { + int r = 0; + + assert(i); + + log_debug("Running remove action for entry %c %s", (char) i->type, i->path); + + switch (i->type) { + + case CREATE_FILE: + case TRUNCATE_FILE: + case CREATE_DIRECTORY: + case CREATE_SUBVOLUME: + case CREATE_SUBVOLUME_INHERIT_QUOTA: + case CREATE_SUBVOLUME_NEW_QUOTA: + case CREATE_FIFO: + case CREATE_SYMLINK: + case CREATE_CHAR_DEVICE: + case CREATE_BLOCK_DEVICE: + case IGNORE_PATH: + case IGNORE_DIRECTORY_PATH: + case ADJUST_MODE: + case RELABEL_PATH: + case RECURSIVE_RELABEL_PATH: + case WRITE_FILE: + case COPY_FILES: + case SET_XATTR: + case RECURSIVE_SET_XATTR: + case SET_ACL: + case RECURSIVE_SET_ACL: + case SET_ATTRIBUTE: + case RECURSIVE_SET_ATTRIBUTE: + break; + + case REMOVE_PATH: + case TRUNCATE_DIRECTORY: + case RECURSIVE_REMOVE_PATH: + r = glob_item(i, remove_item_instance, false); + break; + } + + return r; +} + +static int clean_item_instance(Item *i, const char* instance) { + _cleanup_closedir_ DIR *d = NULL; + struct stat s, ps; + bool mountpoint; + usec_t cutoff, n; + char timestamp[FORMAT_TIMESTAMP_MAX]; + + assert(i); + + if (!i->age_set) + return 0; + + n = now(CLOCK_REALTIME); + if (n < i->age) + return 0; + + cutoff = n - i->age; + + d = opendir_nomod(instance); + if (!d) { + if (errno == ENOENT || errno == ENOTDIR) { + log_debug_errno(errno, "Directory \"%s\": %m", instance); + return 0; + } + + log_error_errno(errno, "Failed to open directory %s: %m", instance); + return -errno; + } + + if (fstat(dirfd(d), &s) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", i->path); + + if (!S_ISDIR(s.st_mode)) { + log_error("%s is not a directory.", i->path); + return -ENOTDIR; + } + + if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0) + return log_error_errno(errno, "stat(%s/..) failed: %m", i->path); + + mountpoint = s.st_dev != ps.st_dev || s.st_ino == ps.st_ino; + + log_debug("Cleanup threshold for %s \"%s\" is %s", + mountpoint ? "mount point" : "directory", + instance, + format_timestamp_us(timestamp, sizeof(timestamp), cutoff)); + + return dir_cleanup(i, instance, d, &s, cutoff, s.st_dev, mountpoint, + MAX_DEPTH, i->keep_first_level); +} + +static int clean_item(Item *i) { + int r = 0; + + assert(i); + + log_debug("Running clean action for entry %c %s", (char) i->type, i->path); + + switch (i->type) { + case CREATE_DIRECTORY: + case CREATE_SUBVOLUME: + case CREATE_SUBVOLUME_INHERIT_QUOTA: + case CREATE_SUBVOLUME_NEW_QUOTA: + case TRUNCATE_DIRECTORY: + case IGNORE_PATH: + case COPY_FILES: + clean_item_instance(i, i->path); + break; + case IGNORE_DIRECTORY_PATH: + r = glob_item(i, clean_item_instance, false); + break; + default: + break; + } + + return r; +} + +static int process_item_array(ItemArray *array); + +static int process_item(Item *i) { + int r, q, p, t = 0; + _cleanup_free_ char *prefix = NULL; + + assert(i); + + if (i->done) + return 0; + + i->done = true; + + prefix = malloc(strlen(i->path) + 1); + if (!prefix) + return log_oom(); + + PATH_FOREACH_PREFIX(prefix, i->path) { + ItemArray *j; + + j = ordered_hashmap_get(items, prefix); + if (j) { + int s; + + s = process_item_array(j); + if (s < 0 && t == 0) + t = s; + } + } + + r = arg_create ? create_item(i) : 0; + q = arg_remove ? remove_item(i) : 0; + p = arg_clean ? clean_item(i) : 0; + + return t < 0 ? t : + r < 0 ? r : + q < 0 ? q : + p; +} + +static int process_item_array(ItemArray *array) { + unsigned n; + int r = 0, k; + + assert(array); + + for (n = 0; n < array->count; n++) { + k = process_item(array->items + n); + if (k < 0 && r == 0) + r = k; + } + + return r; +} + +static void item_free_contents(Item *i) { + assert(i); + free(i->path); + free(i->argument); + strv_free(i->xattrs); + +#ifdef HAVE_ACL + acl_free(i->acl_access); + acl_free(i->acl_default); +#endif +} + +static void item_array_free(ItemArray *a) { + unsigned n; + + if (!a) + return; + + for (n = 0; n < a->count; n++) + item_free_contents(a->items + n); + free(a->items); + free(a); +} + +static int item_compare(const void *a, const void *b) { + const Item *x = a, *y = b; + + /* Make sure that the ownership taking item is put first, so + * that we first create the node, and then can adjust it */ + + if (takes_ownership(x->type) && !takes_ownership(y->type)) + return -1; + if (!takes_ownership(x->type) && takes_ownership(y->type)) + return 1; + + return (int) x->type - (int) y->type; +} + +static bool item_compatible(Item *a, Item *b) { + assert(a); + assert(b); + assert(streq(a->path, b->path)); + + if (takes_ownership(a->type) && takes_ownership(b->type)) + /* check if the items are the same */ + return streq_ptr(a->argument, b->argument) && + + a->uid_set == b->uid_set && + a->uid == b->uid && + + a->gid_set == b->gid_set && + a->gid == b->gid && + + a->mode_set == b->mode_set && + a->mode == b->mode && + + a->age_set == b->age_set && + a->age == b->age && + + a->mask_perms == b->mask_perms && + + a->keep_first_level == b->keep_first_level && + + a->major_minor == b->major_minor; + + return true; +} + +static bool should_include_path(const char *path) { + char **prefix; + + STRV_FOREACH(prefix, arg_exclude_prefixes) + if (path_startswith(path, *prefix)) { + log_debug("Entry \"%s\" matches exclude prefix \"%s\", skipping.", + path, *prefix); + return false; + } + + STRV_FOREACH(prefix, arg_include_prefixes) + if (path_startswith(path, *prefix)) { + log_debug("Entry \"%s\" matches include prefix \"%s\".", path, *prefix); + return true; + } + + /* no matches, so we should include this path only if we + * have no whitelist at all */ + if (strv_length(arg_include_prefixes) == 0) + return true; + + log_debug("Entry \"%s\" does not match any include prefix, skipping.", path); + return false; +} + +static int parse_line(const char *fname, unsigned line, const char *buffer) { + + _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL; + _cleanup_(item_free_contents) Item i = {}; + ItemArray *existing; + OrderedHashmap *h; + int r, pos; + bool force = false, boot = false; + + assert(fname); + assert(line >= 1); + assert(buffer); + + r = extract_many_words( + &buffer, + NULL, + EXTRACT_QUOTES, + &action, + &path, + &mode, + &user, + &group, + &age, + NULL); + if (r < 0) + return log_error_errno(r, "[%s:%u] Failed to parse line: %m", fname, line); + else if (r < 2) { + log_error("[%s:%u] Syntax error.", fname, line); + return -EIO; + } + + if (!isempty(buffer) && !streq(buffer, "-")) { + i.argument = strdup(buffer); + if (!i.argument) + return log_oom(); + } + + if (isempty(action)) { + log_error("[%s:%u] Command too short '%s'.", fname, line, action); + return -EINVAL; + } + + for (pos = 1; action[pos]; pos++) { + if (action[pos] == '!' && !boot) + boot = true; + else if (action[pos] == '+' && !force) + force = true; + else { + log_error("[%s:%u] Unknown modifiers in command '%s'", + fname, line, action); + return -EINVAL; + } + } + + if (boot && !arg_boot) { + log_debug("Ignoring entry %s \"%s\" because --boot is not specified.", + action, path); + return 0; + } + + i.type = action[0]; + i.force = force; + + r = specifier_printf(path, specifier_table, NULL, &i.path); + if (r < 0) { + log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, path); + return r; + } + + switch (i.type) { + + case CREATE_DIRECTORY: + case CREATE_SUBVOLUME: + case CREATE_SUBVOLUME_INHERIT_QUOTA: + case CREATE_SUBVOLUME_NEW_QUOTA: + case TRUNCATE_DIRECTORY: + case CREATE_FIFO: + case IGNORE_PATH: + case IGNORE_DIRECTORY_PATH: + case REMOVE_PATH: + case RECURSIVE_REMOVE_PATH: + case ADJUST_MODE: + case RELABEL_PATH: + case RECURSIVE_RELABEL_PATH: + if (i.argument) + log_warning("[%s:%u] %c lines don't take argument fields, ignoring.", fname, line, i.type); + + break; + + case CREATE_FILE: + case TRUNCATE_FILE: + break; + + case CREATE_SYMLINK: + if (!i.argument) { + i.argument = strappend("/usr/share/factory/", i.path); + if (!i.argument) + return log_oom(); + } + break; + + case WRITE_FILE: + if (!i.argument) { + log_error("[%s:%u] Write file requires argument.", fname, line); + return -EBADMSG; + } + break; + + case COPY_FILES: + if (!i.argument) { + i.argument = strappend("/usr/share/factory/", i.path); + if (!i.argument) + return log_oom(); + } else if (!path_is_absolute(i.argument)) { + log_error("[%s:%u] Source path is not absolute.", fname, line); + return -EBADMSG; + } + + path_kill_slashes(i.argument); + break; + + case CREATE_CHAR_DEVICE: + case CREATE_BLOCK_DEVICE: { + unsigned major, minor; + + if (!i.argument) { + log_error("[%s:%u] Device file requires argument.", fname, line); + return -EBADMSG; + } + + if (sscanf(i.argument, "%u:%u", &major, &minor) != 2) { + log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i.argument); + return -EBADMSG; + } + + i.major_minor = makedev(major, minor); + break; + } + + case SET_XATTR: + case RECURSIVE_SET_XATTR: + if (!i.argument) { + log_error("[%s:%u] Set extended attribute requires argument.", fname, line); + return -EBADMSG; + } + r = parse_xattrs_from_arg(&i); + if (r < 0) + return r; + break; + + case SET_ACL: + case RECURSIVE_SET_ACL: + if (!i.argument) { + log_error("[%s:%u] Set ACLs requires argument.", fname, line); + return -EBADMSG; + } + r = parse_acls_from_arg(&i); + if (r < 0) + return r; + break; + + case SET_ATTRIBUTE: + case RECURSIVE_SET_ATTRIBUTE: + if (!i.argument) { + log_error("[%s:%u] Set file attribute requires argument.", fname, line); + return -EBADMSG; + } + r = parse_attribute_from_arg(&i); + if (r < 0) + return r; + break; + + default: + log_error("[%s:%u] Unknown command type '%c'.", fname, line, (char) i.type); + return -EBADMSG; + } + + if (!path_is_absolute(i.path)) { + log_error("[%s:%u] Path '%s' not absolute.", fname, line, i.path); + return -EBADMSG; + } + + path_kill_slashes(i.path); + + if (!should_include_path(i.path)) + return 0; + + if (arg_root) { + char *p; + + p = prefix_root(arg_root, i.path); + if (!p) + return log_oom(); + + free(i.path); + i.path = p; + } + + if (!isempty(user) && !streq(user, "-")) { + const char *u = user; + + r = get_user_creds(&u, &i.uid, NULL, NULL, NULL); + if (r < 0) { + log_error("[%s:%u] Unknown user '%s'.", fname, line, user); + return r; + } + + i.uid_set = true; + } + + if (!isempty(group) && !streq(group, "-")) { + const char *g = group; + + r = get_group_creds(&g, &i.gid); + if (r < 0) { + log_error("[%s:%u] Unknown group '%s'.", fname, line, group); + return r; + } + + i.gid_set = true; + } + + if (!isempty(mode) && !streq(mode, "-")) { + const char *mm = mode; + unsigned m; + + if (*mm == '~') { + i.mask_perms = true; + mm++; + } + + if (parse_mode(mm, &m) < 0) { + log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode); + return -EBADMSG; + } + + i.mode = m; + i.mode_set = true; + } else + i.mode = IN_SET(i.type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA) ? 0755 : 0644; + + if (!isempty(age) && !streq(age, "-")) { + const char *a = age; + + if (*a == '~') { + i.keep_first_level = true; + a++; + } + + if (parse_sec(a, &i.age) < 0) { + log_error("[%s:%u] Invalid age '%s'.", fname, line, age); + return -EBADMSG; + } + + i.age_set = true; + } + + h = needs_glob(i.type) ? globs : items; + + existing = ordered_hashmap_get(h, i.path); + if (existing) { + unsigned n; + + for (n = 0; n < existing->count; n++) { + if (!item_compatible(existing->items + n, &i)) { + log_warning("[%s:%u] Duplicate line for path \"%s\", ignoring.", + fname, line, i.path); + return 0; + } + } + } else { + existing = new0(ItemArray, 1); + r = ordered_hashmap_put(h, i.path, existing); + if (r < 0) + return log_oom(); + } + + if (!GREEDY_REALLOC(existing->items, existing->size, existing->count + 1)) + return log_oom(); + + memcpy(existing->items + existing->count++, &i, sizeof(i)); + + /* Sort item array, to enforce stable ordering of application */ + qsort_safe(existing->items, existing->count, sizeof(Item), item_compare); + + zero(i); + return 0; +} + +static void help(void) { + printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" + "Creates, deletes and cleans up volatile and temporary files and directories.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --create Create marked files/directories\n" + " --clean Clean up marked directories\n" + " --remove Remove marked files/directories\n" + " --boot Execute actions only safe at boot\n" + " --prefix=PATH Only apply rules with the specified prefix\n" + " --exclude-prefix=PATH Ignore rules with the specified prefix\n" + " --root=PATH Operate on an alternate filesystem root\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_VERSION = 0x100, + ARG_CREATE, + ARG_CLEAN, + ARG_REMOVE, + ARG_BOOT, + ARG_PREFIX, + ARG_EXCLUDE_PREFIX, + ARG_ROOT, + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "create", no_argument, NULL, ARG_CREATE }, + { "clean", no_argument, NULL, ARG_CLEAN }, + { "remove", no_argument, NULL, ARG_REMOVE }, + { "boot", no_argument, NULL, ARG_BOOT }, + { "prefix", required_argument, NULL, ARG_PREFIX }, + { "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX }, + { "root", required_argument, NULL, ARG_ROOT }, + {} + }; + + int c, r; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_CREATE: + arg_create = true; + break; + + case ARG_CLEAN: + arg_clean = true; + break; + + case ARG_REMOVE: + arg_remove = true; + break; + + case ARG_BOOT: + arg_boot = true; + break; + + case ARG_PREFIX: + if (strv_push(&arg_include_prefixes, optarg) < 0) + return log_oom(); + break; + + case ARG_EXCLUDE_PREFIX: + if (strv_push(&arg_exclude_prefixes, optarg) < 0) + return log_oom(); + break; + + case ARG_ROOT: + r = parse_path_argument_and_warn(optarg, true, &arg_root); + if (r < 0) + return r; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (!arg_clean && !arg_create && !arg_remove) { + log_error("You need to specify at least one of --clean, --create or --remove."); + return -EINVAL; + } + + return 1; +} + +static int read_config_file(const char *fn, bool ignore_enoent) { + _cleanup_fclose_ FILE *f = NULL; + char line[LINE_MAX]; + Iterator iterator; + unsigned v = 0; + Item *i; + int r; + + assert(fn); + + r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &f); + if (r < 0) { + if (ignore_enoent && r == -ENOENT) { + log_debug_errno(r, "Failed to open \"%s\": %m", fn); + return 0; + } + + return log_error_errno(r, "Failed to open '%s', ignoring: %m", fn); + } + log_debug("Reading config file \"%s\".", fn); + + FOREACH_LINE(line, f, break) { + char *l; + int k; + + v++; + + l = strstrip(line); + if (*l == '#' || *l == 0) + continue; + + k = parse_line(fn, v, l); + if (k < 0 && r == 0) + r = k; + } + + /* we have to determine age parameter for each entry of type X */ + ORDERED_HASHMAP_FOREACH(i, globs, iterator) { + Iterator iter; + Item *j, *candidate_item = NULL; + + if (i->type != IGNORE_DIRECTORY_PATH) + continue; + + ORDERED_HASHMAP_FOREACH(j, items, iter) { + if (!IN_SET(j->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA)) + continue; + + if (path_equal(j->path, i->path)) { + candidate_item = j; + break; + } + + if ((!candidate_item && path_startswith(i->path, j->path)) || + (candidate_item && path_startswith(j->path, candidate_item->path) && (fnmatch(i->path, j->path, FNM_PATHNAME | FNM_PERIOD) == 0))) + candidate_item = j; + } + + if (candidate_item && candidate_item->age_set) { + i->age = candidate_item->age; + i->age_set = true; + } + } + + if (ferror(f)) { + log_error_errno(errno, "Failed to read from file %s: %m", fn); + if (r == 0) + r = -EIO; + } + + return r; +} + +int main(int argc, char *argv[]) { + int r, k; + ItemArray *a; + Iterator iterator; + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + mac_selinux_init(NULL); + + items = ordered_hashmap_new(&string_hash_ops); + globs = ordered_hashmap_new(&string_hash_ops); + + if (!items || !globs) { + r = log_oom(); + goto finish; + } + + r = 0; + + if (optind < argc) { + int j; + + for (j = optind; j < argc; j++) { + k = read_config_file(argv[j], false); + if (k < 0 && r == 0) + r = k; + } + + } else { + _cleanup_strv_free_ char **files = NULL; + char **f; + + r = conf_files_list_nulstr(&files, ".conf", arg_root, conf_file_dirs); + if (r < 0) { + log_error_errno(r, "Failed to enumerate tmpfiles.d files: %m"); + goto finish; + } + + STRV_FOREACH(f, files) { + k = read_config_file(*f, true); + if (k < 0 && r == 0) + r = k; + } + } + + /* The non-globbing ones usually create things, hence we apply + * them first */ + ORDERED_HASHMAP_FOREACH(a, items, iterator) { + k = process_item_array(a); + if (k < 0 && r == 0) + r = k; + } + + /* The globbing ones usually alter things, hence we apply them + * second. */ + ORDERED_HASHMAP_FOREACH(a, globs, iterator) { + k = process_item_array(a); + if (k < 0 && r == 0) + r = k; + } + +finish: + while ((a = ordered_hashmap_steal_first(items))) + item_array_free(a); + + while ((a = ordered_hashmap_steal_first(globs))) + item_array_free(a); + + ordered_hashmap_free(items); + ordered_hashmap_free(globs); + + free(arg_include_prefixes); + free(arg_exclude_prefixes); + free(arg_root); + + set_free_free(unix_sockets); + + mac_selinux_finish(); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-tty-ask-password-agent/Makefile b/src/systemd-tty-ask-password-agent/Makefile new file mode 100644 index 0000000000..37b51da33b --- /dev/null +++ b/src/systemd-tty-ask-password-agent/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_tty_ask_password_agent_SOURCES = \ + src/tty-ask-password-agent/tty-ask-password-agent.c + +systemd_tty_ask_password_agent_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-tty-ask-password-agent/tty-ask-password-agent.c b/src/systemd-tty-ask-password-agent/tty-ask-password-agent.c new file mode 100644 index 0000000000..7b67831e54 --- /dev/null +++ b/src/systemd-tty-ask-password-agent/tty-ask-password-agent.c @@ -0,0 +1,681 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "ask-password-api.h" +#include "conf-parser.h" +#include "def.h" +#include "dirent-util.h" +#include "fd-util.h" +#include "io-util.h" +#include "mkdir.h" +#include "path-util.h" +#include "process-util.h" +#include "signal-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" +#include "terminal-util.h" +#include "util.h" +#include "utmp-wtmp.h" + +static enum { + ACTION_LIST, + ACTION_QUERY, + ACTION_WATCH, + ACTION_WALL +} arg_action = ACTION_QUERY; + +static bool arg_plymouth = false; +static bool arg_console = false; + +static int ask_password_plymouth( + const char *message, + usec_t until, + AskPasswordFlags flags, + const char *flag_file, + char ***ret) { + + _cleanup_close_ int fd = -1, notify = -1; + union sockaddr_union sa = PLYMOUTH_SOCKET; + _cleanup_free_ char *packet = NULL; + ssize_t k; + int r, n; + struct pollfd pollfd[2] = {}; + char buffer[LINE_MAX]; + size_t p = 0; + enum { + POLL_SOCKET, + POLL_INOTIFY + }; + + assert(ret); + + if (flag_file) { + notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); + if (notify < 0) + return -errno; + + r = inotify_add_watch(notify, flag_file, IN_ATTRIB); /* for the link count */ + if (r < 0) + return -errno; + } + + fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); + if (fd < 0) + return -errno; + + r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)); + if (r < 0) + return -errno; + + if (flags & ASK_PASSWORD_ACCEPT_CACHED) { + packet = strdup("c"); + n = 1; + } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) + packet = NULL; + if (!packet) + return -ENOMEM; + + r = loop_write(fd, packet, n + 1, true); + if (r < 0) + return r; + + pollfd[POLL_SOCKET].fd = fd; + pollfd[POLL_SOCKET].events = POLLIN; + pollfd[POLL_INOTIFY].fd = notify; + pollfd[POLL_INOTIFY].events = POLLIN; + + for (;;) { + int sleep_for = -1, j; + + if (until > 0) { + usec_t y; + + y = now(CLOCK_MONOTONIC); + + if (y > until) { + r = -ETIME; + goto finish; + } + + sleep_for = (int) ((until - y) / USEC_PER_MSEC); + } + + if (flag_file && access(flag_file, F_OK) < 0) { + r = -errno; + goto finish; + } + + j = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for); + if (j < 0) { + if (errno == EINTR) + continue; + + r = -errno; + goto finish; + } else if (j == 0) { + r = -ETIME; + goto finish; + } + + if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0) + flush_fd(notify); + + if (pollfd[POLL_SOCKET].revents == 0) + continue; + + k = read(fd, buffer + p, sizeof(buffer) - p); + if (k < 0) { + if (errno == EINTR || errno == EAGAIN) + continue; + + r = -errno; + goto finish; + } else if (k == 0) { + r = -EIO; + goto finish; + } + + p += k; + + if (p < 1) + continue; + + if (buffer[0] == 5) { + + if (flags & ASK_PASSWORD_ACCEPT_CACHED) { + /* Hmm, first try with cached + * passwords failed, so let's retry + * with a normal password request */ + packet = mfree(packet); + + if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) { + r = -ENOMEM; + goto finish; + } + + r = loop_write(fd, packet, n+1, true); + if (r < 0) + goto finish; + + flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + p = 0; + continue; + } + + /* No password, because UI not shown */ + r = -ENOENT; + goto finish; + + } else if (buffer[0] == 2 || buffer[0] == 9) { + uint32_t size; + char **l; + + /* One or more answers */ + if (p < 5) + continue; + + memcpy(&size, buffer+1, sizeof(size)); + size = le32toh(size); + if (size + 5 > sizeof(buffer)) { + r = -EIO; + goto finish; + } + + if (p-5 < size) + continue; + + l = strv_parse_nulstr(buffer + 5, size); + if (!l) { + r = -ENOMEM; + goto finish; + } + + *ret = l; + break; + + } else { + /* Unknown packet */ + r = -EIO; + goto finish; + } + } + + r = 0; + +finish: + memory_erase(buffer, sizeof(buffer)); + return r; +} + +static int send_passwords(const char *socket_name, char **passwords) { + _cleanup_free_ char *packet = NULL; + _cleanup_close_ int socket_fd = -1; + union sockaddr_union sa = { .un.sun_family = AF_UNIX }; + size_t packet_length = 1; + char **p, *d; + int r; + + assert(socket_name); + + STRV_FOREACH(p, passwords) + packet_length += strlen(*p) + 1; + + packet = new(char, packet_length); + if (!packet) + return -ENOMEM; + + packet[0] = '+'; + + d = packet + 1; + STRV_FOREACH(p, passwords) + d = stpcpy(d, *p) + 1; + + socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); + if (socket_fd < 0) { + r = log_debug_errno(errno, "socket(): %m"); + goto finish; + } + + strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path)); + + r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, + offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)); + if (r < 0) + r = log_debug_errno(errno, "sendto(): %m"); + +finish: + memory_erase(packet, packet_length); + return r; +} + +static int parse_password(const char *filename, char **wall) { + _cleanup_free_ char *socket_name = NULL, *message = NULL; + bool accept_cached = false, echo = false; + uint64_t not_after = 0; + unsigned pid = 0; + + const ConfigTableItem items[] = { + { "Ask", "Socket", config_parse_string, 0, &socket_name }, + { "Ask", "NotAfter", config_parse_uint64, 0, ¬_after }, + { "Ask", "Message", config_parse_string, 0, &message }, + { "Ask", "PID", config_parse_unsigned, 0, &pid }, + { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached }, + { "Ask", "Echo", config_parse_bool, 0, &echo }, + {} + }; + + int r; + + assert(filename); + + r = config_parse(NULL, filename, NULL, + NULL, + config_item_table_lookup, items, + true, false, true, NULL); + if (r < 0) + return r; + + if (!socket_name) { + log_error("Invalid password file %s", filename); + return -EBADMSG; + } + + if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after) + return 0; + + if (pid > 0 && !pid_is_alive(pid)) + return 0; + + if (arg_action == ACTION_LIST) + printf("'%s' (PID %u)\n", message, pid); + + else if (arg_action == ACTION_WALL) { + char *_wall; + + if (asprintf(&_wall, + "%s%sPassword entry required for \'%s\' (PID %u).\r\n" + "Please enter password with the systemd-tty-ask-password-agent tool!", + strempty(*wall), + *wall ? "\r\n\r\n" : "", + message, + pid) < 0) + return log_oom(); + + free(*wall); + *wall = _wall; + + } else { + _cleanup_strv_free_erase_ char **passwords = NULL; + + assert(arg_action == ACTION_QUERY || + arg_action == ACTION_WATCH); + + if (access(socket_name, W_OK) < 0) { + if (arg_action == ACTION_QUERY) + log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid); + + return 0; + } + + if (arg_plymouth) + r = ask_password_plymouth(message, not_after, accept_cached ? ASK_PASSWORD_ACCEPT_CACHED : 0, filename, &passwords); + else { + char *password = NULL; + int tty_fd = -1; + + if (arg_console) { + tty_fd = acquire_terminal("/dev/console", false, false, false, USEC_INFINITY); + if (tty_fd < 0) + return log_error_errno(tty_fd, "Failed to acquire /dev/console: %m"); + + r = reset_terminal_fd(tty_fd, true); + if (r < 0) + log_warning_errno(r, "Failed to reset terminal, ignoring: %m"); + } + + r = ask_password_tty(message, NULL, not_after, echo ? ASK_PASSWORD_ECHO : 0, filename, &password); + + if (arg_console) { + tty_fd = safe_close(tty_fd); + release_terminal(); + } + + if (r >= 0) + r = strv_push(&passwords, password); + + if (r < 0) + string_free_erase(password); + } + + /* If the query went away, that's OK */ + if (IN_SET(r, -ETIME, -ENOENT)) + return 0; + + if (r < 0) + return log_error_errno(r, "Failed to query password: %m"); + + r = send_passwords(socket_name, passwords); + if (r < 0) + return log_error_errno(r, "Failed to send: %m"); + } + + return 0; +} + +static int wall_tty_block(void) { + _cleanup_free_ char *p = NULL; + dev_t devnr; + int fd, r; + + r = get_ctty_devnr(0, &devnr); + if (r == -ENXIO) /* We have no controlling tty */ + return -ENOTTY; + if (r < 0) + return log_error_errno(r, "Failed to get controlling TTY: %m"); + + if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0) + return log_oom(); + + mkdir_parents_label(p, 0700); + mkfifo(p, 0600); + + fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd < 0) + return log_debug_errno(errno, "Failed to open %s: %m", p); + + return fd; +} + +static bool wall_tty_match(const char *path, void *userdata) { + _cleanup_free_ char *p = NULL; + _cleanup_close_ int fd = -1; + struct stat st; + + if (!path_is_absolute(path)) + path = strjoina("/dev/", path); + + if (lstat(path, &st) < 0) { + log_debug_errno(errno, "Failed to stat %s: %m", path); + return true; + } + + if (!S_ISCHR(st.st_mode)) { + log_debug("%s is not a character device.", path); + return true; + } + + /* We use named pipes to ensure that wall messages suggesting + * password entry are not printed over password prompts + * already shown. We use the fact here that opening a pipe in + * non-blocking mode for write-only will succeed only if + * there's some writer behind it. Using pipes has the + * advantage that the block will automatically go away if the + * process dies. */ + + if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) { + log_oom(); + return true; + } + + fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); + if (fd < 0) { + log_debug_errno(errno, "Failed top open the wall pipe: %m"); + return 1; + } + + /* What, we managed to open the pipe? Then this tty is filtered. */ + return 0; +} + +static int show_passwords(void) { + _cleanup_closedir_ DIR *d; + struct dirent *de; + int r = 0; + + d = opendir("/run/systemd/ask-password"); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open /run/systemd/ask-password: %m"); + } + + FOREACH_DIRENT_ALL(de, d, return log_error_errno(errno, "Failed to read directory: %m")) { + _cleanup_free_ char *p = NULL, *wall = NULL; + int q; + + /* We only support /dev on tmpfs, hence we can rely on + * d_type to be reliable */ + + if (de->d_type != DT_REG) + continue; + + if (hidden_file(de->d_name)) + continue; + + if (!startswith(de->d_name, "ask.")) + continue; + + p = strappend("/run/systemd/ask-password/", de->d_name); + if (!p) + return log_oom(); + + q = parse_password(p, &wall); + if (q < 0 && r == 0) + r = q; + + if (wall) + (void) utmp_wall(wall, NULL, NULL, wall_tty_match, NULL); + } + + return r; +} + +static int watch_passwords(void) { + enum { + FD_INOTIFY, + FD_SIGNAL, + _FD_MAX + }; + + _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1; + struct pollfd pollfd[_FD_MAX] = {}; + sigset_t mask; + int r; + + tty_block_fd = wall_tty_block(); + + (void) mkdir_p_label("/run/systemd/ask-password", 0755); + + notify = inotify_init1(IN_CLOEXEC); + if (notify < 0) + return log_error_errno(errno, "Failed to allocate directory watch: %m"); + + if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) + return log_error_errno(errno, "Failed to add /run/systemd/ask-password to directory watch: %m"); + + assert_se(sigemptyset(&mask) >= 0); + assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0); + assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) >= 0); + + signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); + if (signal_fd < 0) + return log_error_errno(errno, "Failed to allocate signal file descriptor: %m"); + + pollfd[FD_INOTIFY].fd = notify; + pollfd[FD_INOTIFY].events = POLLIN; + pollfd[FD_SIGNAL].fd = signal_fd; + pollfd[FD_SIGNAL].events = POLLIN; + + for (;;) { + r = show_passwords(); + if (r < 0) + log_error_errno(r, "Failed to show password: %m"); + + if (poll(pollfd, _FD_MAX, -1) < 0) { + if (errno == EINTR) + continue; + + return -errno; + } + + if (pollfd[FD_INOTIFY].revents != 0) + (void) flush_fd(notify); + + if (pollfd[FD_SIGNAL].revents != 0) + break; + } + + return 0; +} + +static void help(void) { + printf("%s [OPTIONS...]\n\n" + "Process system password requests.\n\n" + " -h --help Show this help\n" + " --version Show package version\n" + " --list Show pending password requests\n" + " --query Process pending password requests\n" + " --watch Continuously process password requests\n" + " --wall Continuously forward password requests to wall\n" + " --plymouth Ask question with Plymouth instead of on TTY\n" + " --console Ask question on /dev/console instead of current TTY\n", + program_invocation_short_name); +} + +static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_LIST = 0x100, + ARG_QUERY, + ARG_WATCH, + ARG_WALL, + ARG_PLYMOUTH, + ARG_CONSOLE, + ARG_VERSION + }; + + static const struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "list", no_argument, NULL, ARG_LIST }, + { "query", no_argument, NULL, ARG_QUERY }, + { "watch", no_argument, NULL, ARG_WATCH }, + { "wall", no_argument, NULL, ARG_WALL }, + { "plymouth", no_argument, NULL, ARG_PLYMOUTH }, + { "console", no_argument, NULL, ARG_CONSOLE }, + {} + }; + + int c; + + assert(argc >= 0); + assert(argv); + + while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) + + switch (c) { + + case 'h': + help(); + return 0; + + case ARG_VERSION: + return version(); + + case ARG_LIST: + arg_action = ACTION_LIST; + break; + + case ARG_QUERY: + arg_action = ACTION_QUERY; + break; + + case ARG_WATCH: + arg_action = ACTION_WATCH; + break; + + case ARG_WALL: + arg_action = ACTION_WALL; + break; + + case ARG_PLYMOUTH: + arg_plymouth = true; + break; + + case ARG_CONSOLE: + arg_console = true; + break; + + case '?': + return -EINVAL; + + default: + assert_not_reached("Unhandled option"); + } + + if (optind != argc) { + log_error("%s takes no arguments.", program_invocation_short_name); + return -EINVAL; + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + r = parse_argv(argc, argv); + if (r <= 0) + goto finish; + + if (arg_console) { + (void) setsid(); + (void) release_terminal(); + } + + if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL)) + r = watch_passwords(); + else + r = show_passwords(); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-update-done/Makefile b/src/systemd-update-done/Makefile new file mode 100644 index 0000000000..ae592db650 --- /dev/null +++ b/src/systemd-update-done/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_update_done_SOURCES = \ + src/update-done/update-done.c + +systemd_update_done_LDADD = \ + libshared.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-update-done/update-done.c b/src/systemd-update-done/update-done.c new file mode 100644 index 0000000000..931e583785 --- /dev/null +++ b/src/systemd-update-done/update-done.c @@ -0,0 +1,115 @@ +/*** + This file is part of systemd. + + Copyright 2014 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 . +***/ + +#include "fd-util.h" +#include "io-util.h" +#include "selinux-util.h" +#include "util.h" + +#define MESSAGE \ + "This file was created by systemd-update-done. Its only \n" \ + "purpose is to hold a timestamp of the time this directory\n" \ + "was updated. See systemd-update-done.service(8).\n" + +static int apply_timestamp(const char *path, struct timespec *ts) { + struct timespec twice[2] = { + *ts, + *ts + }; + struct stat st; + + assert(path); + assert(ts); + + if (stat(path, &st) >= 0) { + /* Is the timestamp file already newer than the OS? If + * so, there's nothing to do. We ignore the nanosecond + * component of the timestamp, since some file systems + * do not support any better accuracy than 1s and we + * have no way to identify the accuracy + * available. Most notably ext4 on small disks (where + * 128 byte inodes are used) does not support better + * accuracy than 1s. */ + if (st.st_mtim.tv_sec > ts->tv_sec) + return 0; + + /* It is older? Then let's update it */ + if (utimensat(AT_FDCWD, path, twice, AT_SYMLINK_NOFOLLOW) < 0) { + + if (errno == EROFS) + return log_debug("Can't update timestamp file %s, file system is read-only.", path); + + return log_error_errno(errno, "Failed to update timestamp on %s: %m", path); + } + + } else if (errno == ENOENT) { + _cleanup_close_ int fd = -1; + int r; + + /* The timestamp file doesn't exist yet? Then let's create it. */ + + r = mac_selinux_create_file_prepare(path, S_IFREG); + if (r < 0) + return log_error_errno(r, "Failed to set SELinux context for %s: %m", path); + + fd = open(path, O_CREAT|O_EXCL|O_WRONLY|O_TRUNC|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644); + mac_selinux_create_file_clear(); + + if (fd < 0) { + if (errno == EROFS) + return log_debug("Can't create timestamp file %s, file system is read-only.", path); + + return log_error_errno(errno, "Failed to create timestamp file %s: %m", path); + } + + (void) loop_write(fd, MESSAGE, strlen(MESSAGE), false); + + if (futimens(fd, twice) < 0) + return log_error_errno(errno, "Failed to update timestamp on %s: %m", path); + } else + log_error_errno(errno, "Failed to stat() timestamp file %s: %m", path); + + return 0; +} + +int main(int argc, char *argv[]) { + struct stat st; + int r, q = 0; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + if (stat("/usr", &st) < 0) { + log_error_errno(errno, "Failed to stat /usr: %m"); + return EXIT_FAILURE; + } + + r = mac_selinux_init(NULL); + if (r < 0) { + log_error_errno(r, "SELinux setup failed: %m"); + goto finish; + } + + r = apply_timestamp("/etc/.updated", &st.st_mtim); + q = apply_timestamp("/var/.updated", &st.st_mtim); + +finish: + return r < 0 || q < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-update-utmp/Makefile b/src/systemd-update-utmp/Makefile new file mode 100644 index 0000000000..182c7534e7 --- /dev/null +++ b/src/systemd-update-utmp/Makefile @@ -0,0 +1,37 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_update_utmp_SOURCES = \ + src/update-utmp/update-utmp.c + +systemd_update_utmp_CFLAGS = \ + $(AM_CFLAGS) \ + $(AUDIT_CFLAGS) + +systemd_update_utmp_LDADD = \ + libshared.la \ + $(AUDIT_LIBS) + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-update-utmp/update-utmp.c b/src/systemd-update-utmp/update-utmp.c new file mode 100644 index 0000000000..fedcaef91c --- /dev/null +++ b/src/systemd-update-utmp/update-utmp.c @@ -0,0 +1,283 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include + +#ifdef HAVE_AUDIT +#include +#endif + +#include + +#include "alloc-util.h" +#include "bus-error.h" +#include "bus-util.h" +#include "formats-util.h" +#include "log.h" +#include "macro.h" +#include "special.h" +#include "unit-name.h" +#include "util.h" +#include "utmp-wtmp.h" + +typedef struct Context { + sd_bus *bus; +#ifdef HAVE_AUDIT + int audit_fd; +#endif +} Context; + +static usec_t get_startup_time(Context *c) { + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + usec_t t = 0; + int r; + + assert(c); + + r = sd_bus_get_property_trivial( + c->bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UserspaceTimestamp", + &error, + 't', &t); + if (r < 0) { + log_error_errno(r, "Failed to get timestamp: %s", bus_error_message(&error, r)); + return 0; + } + + return t; +} + +static int get_current_runlevel(Context *c) { + static const struct { + const int runlevel; + const char *special; + } table[] = { + /* The first target of this list that is active or has + * a job scheduled wins. We prefer runlevels 5 and 3 + * here over the others, since these are the main + * runlevels used on Fedora. It might make sense to + * change the order on some distributions. */ + { '5', SPECIAL_GRAPHICAL_TARGET }, + { '3', SPECIAL_MULTI_USER_TARGET }, + { '1', SPECIAL_RESCUE_TARGET }, + }; + + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + int r; + unsigned i; + + assert(c); + + for (i = 0; i < ELEMENTSOF(table); i++) { + _cleanup_free_ char *state = NULL, *path = NULL; + + path = unit_dbus_path_from_name(table[i].special); + if (!path) + return log_oom(); + + r = sd_bus_get_property_string( + c->bus, + "org.freedesktop.systemd1", + path, + "org.freedesktop.systemd1.Unit", + "ActiveState", + &error, + &state); + if (r < 0) + return log_warning_errno(r, "Failed to get state: %s", bus_error_message(&error, r)); + + if (streq(state, "active") || streq(state, "reloading")) + return table[i].runlevel; + } + + return 0; +} + +static int on_reboot(Context *c) { + int r = 0, q; + usec_t t; + + assert(c); + + /* We finished start-up, so let's write the utmp + * record and send the audit msg */ + +#ifdef HAVE_AUDIT + if (c->audit_fd >= 0) + if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && + errno != EPERM) { + r = log_error_errno(errno, "Failed to send audit message: %m"); + } +#endif + + /* If this call fails it will return 0, which + * utmp_put_reboot() will then fix to the current time */ + t = get_startup_time(c); + + q = utmp_put_reboot(t); + if (q < 0) { + log_error_errno(q, "Failed to write utmp record: %m"); + r = q; + } + + return r; +} + +static int on_shutdown(Context *c) { + int r = 0, q; + + assert(c); + + /* We started shut-down, so let's write the utmp + * record and send the audit msg */ + +#ifdef HAVE_AUDIT + if (c->audit_fd >= 0) + if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && + errno != EPERM) { + r = log_error_errno(errno, "Failed to send audit message: %m"); + } +#endif + + q = utmp_put_shutdown(); + if (q < 0) { + log_error_errno(q, "Failed to write utmp record: %m"); + r = q; + } + + return r; +} + +static int on_runlevel(Context *c) { + int r = 0, q, previous, runlevel; + + assert(c); + + /* We finished changing runlevel, so let's write the + * utmp record and send the audit msg */ + + /* First, get last runlevel */ + q = utmp_get_runlevel(&previous, NULL); + + if (q < 0) { + if (q != -ESRCH && q != -ENOENT) + return log_error_errno(q, "Failed to get current runlevel: %m"); + + previous = 0; + } + + /* Secondly, get new runlevel */ + runlevel = get_current_runlevel(c); + + if (runlevel < 0) + return runlevel; + + if (previous == runlevel) + return 0; + +#ifdef HAVE_AUDIT + if (c->audit_fd >= 0) { + _cleanup_free_ char *s = NULL; + + if (asprintf(&s, "old-level=%c new-level=%c", + previous > 0 ? previous : 'N', + runlevel > 0 ? runlevel : 'N') < 0) + return log_oom(); + + if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && errno != EPERM) + r = log_error_errno(errno, "Failed to send audit message: %m"); + } +#endif + + q = utmp_put_runlevel(runlevel, previous); + if (q < 0 && q != -ESRCH && q != -ENOENT) { + log_error_errno(q, "Failed to write utmp record: %m"); + r = q; + } + + return r; +} + +int main(int argc, char *argv[]) { + Context c = { +#ifdef HAVE_AUDIT + .audit_fd = -1 +#endif + }; + int r; + + if (getppid() != 1) { + log_error("This program should be invoked by init only."); + return EXIT_FAILURE; + } + + if (argc != 2) { + log_error("This program requires one argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + +#ifdef HAVE_AUDIT + /* If the kernel lacks netlink or audit support, + * don't worry about it. */ + c.audit_fd = audit_open(); + if (c.audit_fd < 0 && errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT) + log_error_errno(errno, "Failed to connect to audit log: %m"); +#endif + r = bus_connect_system_systemd(&c.bus); + if (r < 0) { + log_error_errno(r, "Failed to get D-Bus connection: %m"); + r = -EIO; + goto finish; + } + + log_debug("systemd-update-utmp running as pid "PID_FMT, getpid()); + + if (streq(argv[1], "reboot")) + r = on_reboot(&c); + else if (streq(argv[1], "shutdown")) + r = on_shutdown(&c); + else if (streq(argv[1], "runlevel")) + r = on_runlevel(&c); + else { + log_error("Unknown command %s", argv[1]); + r = -EINVAL; + } + + log_debug("systemd-update-utmp stopped as pid "PID_FMT, getpid()); + +finish: +#ifdef HAVE_AUDIT + if (c.audit_fd >= 0) + audit_close(c.audit_fd); +#endif + + sd_bus_flush_close_unref(c.bus); + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/systemd-user-sessions/user-sessions.c b/src/systemd-user-sessions/user-sessions.c new file mode 100644 index 0000000000..8bf44e2100 --- /dev/null +++ b/src/systemd-user-sessions/user-sessions.c @@ -0,0 +1,84 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include + +#include "fileio.h" +#include "fileio-label.h" +#include "log.h" +#include "selinux-util.h" +#include "string-util.h" +#include "util.h" + +int main(int argc, char*argv[]) { + + if (argc != 2) { + log_error("This program requires one argument."); + return EXIT_FAILURE; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + mac_selinux_init(NULL); + + if (streq(argv[1], "start")) { + int r = 0; + + if (unlink("/run/nologin") < 0 && errno != ENOENT) + r = log_error_errno(errno, + "Failed to remove /run/nologin file: %m"); + + if (unlink("/etc/nologin") < 0 && errno != ENOENT) { + /* If the file doesn't exist and /etc simply + * was read-only (in which case unlink() + * returns EROFS even if the file doesn't + * exist), don't complain */ + + if (errno != EROFS || access("/etc/nologin", F_OK) >= 0) { + log_error_errno(errno, "Failed to remove /etc/nologin file: %m"); + return EXIT_FAILURE; + } + } + + if (r < 0) + return EXIT_FAILURE; + + } else if (streq(argv[1], "stop")) { + int r; + + r = write_string_file_atomic_label("/run/nologin", "System is going down."); + if (r < 0) { + log_error_errno(r, "Failed to create /run/nologin: %m"); + return EXIT_FAILURE; + } + + } else { + log_error("Unknown verb %s.", argv[1]); + return EXIT_FAILURE; + } + + mac_selinux_finish(); + + return EXIT_SUCCESS; +} diff --git a/src/systemd-vconsole/.gitignore b/src/systemd-vconsole/.gitignore new file mode 100644 index 0000000000..82741b2fb3 --- /dev/null +++ b/src/systemd-vconsole/.gitignore @@ -0,0 +1 @@ +/90-vconsole.rules diff --git a/src/systemd-vconsole/90-vconsole.rules.in b/src/systemd-vconsole/90-vconsole.rules.in new file mode 100644 index 0000000000..35b9ad5151 --- /dev/null +++ b/src/systemd-vconsole/90-vconsole.rules.in @@ -0,0 +1,10 @@ +# 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. + +# Each vtcon keeps its own state of fonts. +# +ACTION=="add", SUBSYSTEM=="vtconsole", KERNEL=="vtcon*", RUN+="@rootlibexecdir@/systemd-vconsole-setup" diff --git a/src/systemd-vconsole/Makefile b/src/systemd-vconsole/Makefile new file mode 100644 index 0000000000..987745d298 --- /dev/null +++ b/src/systemd-vconsole/Makefile @@ -0,0 +1,50 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_VCONSOLE),) +systemd_vconsole_setup_SOURCES = \ + src/vconsole/vconsole-setup.c + +systemd_vconsole_setup_LDADD = \ + libshared.la + +rootlibexec_PROGRAMS += \ + systemd-vconsole-setup + +nodist_udevrules_DATA += \ + src/vconsole/90-vconsole.rules + +nodist_systemunit_DATA += \ + units/systemd-vconsole-setup.service + +SYSINIT_TARGET_WANTS += \ + systemd-vconsole-setup.service +endif + +EXTRA_DIST += \ + src/vconsole/90-vconsole.rules.in \ + units/systemd-vconsole-setup.service.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/systemd-vconsole/vconsole-setup.c b/src/systemd-vconsole/vconsole-setup.c new file mode 100644 index 0000000000..8a1b824e65 --- /dev/null +++ b/src/systemd-vconsole/vconsole-setup.c @@ -0,0 +1,326 @@ +/*** + This file is part of systemd. + + Copyright 2010 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "io-util.h" +#include "locale-util.h" +#include "log.h" +#include "process-util.h" +#include "signal-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "terminal-util.h" +#include "util.h" +#include "virt.h" + +static bool is_vconsole(int fd) { + unsigned char data[1]; + + data[0] = TIOCL_GETFGCONSOLE; + return ioctl(fd, TIOCLINUX, data) >= 0; +} + +static int disable_utf8(int fd) { + int r = 0, k; + + if (ioctl(fd, KDSKBMODE, K_XLATE) < 0) + r = -errno; + + k = loop_write(fd, "\033%@", 3, false); + if (k < 0) + r = k; + + k = write_string_file("/sys/module/vt/parameters/default_utf8", "0", 0); + if (k < 0) + r = k; + + if (r < 0) + log_warning_errno(r, "Failed to disable UTF-8: %m"); + + return r; +} + +static int enable_utf8(int fd) { + int r = 0, k; + long current = 0; + + if (ioctl(fd, KDGKBMODE, ¤t) < 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; + } + + k = loop_write(fd, "\033%G", 3, false); + if (k < 0) + r = k; + + k = write_string_file("/sys/module/vt/parameters/default_utf8", "1", 0); + if (k < 0) + r = k; + + if (r < 0) + log_warning_errno(r, "Failed to enable UTF-8: %m"); + + return r; +} + +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; + pid_t pid; + + /* An empty map means kernel map */ + if (isempty(map)) + return 1; + + args[i++] = KBD_LOADKEYS; + args[i++] = "-q"; + args[i++] = "-C"; + args[i++] = vc; + if (utf8) + args[i++] = "-u"; + args[i++] = map; + if (map_toggle) + args[i++] = map_toggle; + args[i++] = NULL; + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + else if (pid == 0) { + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + execv(args[0], (char **) args); + _exit(EXIT_FAILURE); + } + + r = wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true); + if (r < 0) + return r; + + return r == 0; +} + +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; + pid_t pid; + + /* An empty font means kernel font */ + if (isempty(font)) + return 1; + + args[i++] = KBD_SETFONT; + args[i++] = "-C"; + args[i++] = vc; + args[i++] = font; + if (map) { + args[i++] = "-m"; + args[i++] = map; + } + if (unimap) { + args[i++] = "-u"; + args[i++] = unimap; + } + args[i++] = NULL; + + pid = fork(); + if (pid < 0) + return log_error_errno(errno, "Failed to fork: %m"); + else if (pid == 0) { + + (void) reset_all_signal_handlers(); + (void) reset_signal_mask(); + + execv(args[0], (char **) args); + _exit(EXIT_FAILURE); + } + + r = wait_for_terminate_and_warn(KBD_SETFONT, pid, true); + if (r < 0) + return r; + + return r == 0; +} + +/* + * A newly allocated VT uses the font from the active VT. Here + * 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. + */ +static void font_copy_to_all_vcs(int fd) { + struct vt_stat vcs = {}; + unsigned char map8[E_TABSZ]; + unsigned short map16[E_TABSZ]; + struct unimapdesc unimapd; + struct unipair unipairs[USHRT_MAX]; + int i, r; + + /* 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"); + 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 = {}; + + if (i == vcs.v_active) + continue; + + /* skip non-allocated ttys */ + xsprintf(vcname, "/dev/vcs%i", i); + if (access(vcname, F_OK) < 0) + continue; + + xsprintf(vcname, "/dev/tty%i", i); + vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC); + if (vcfd < 0) + 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); + + /* copy map of 8bit chars */ + if (ioctl(fd, GIO_SCRNMAP, map8) >= 0) + (void) ioctl(vcfd, PIO_SCRNMAP, map8); + + /* copy map of 8bit chars -> 16bit Unicode values */ + if (ioctl(fd, GIO_UNISCRNMAP, map16) >= 0) + (void) ioctl(vcfd, PIO_UNISCRNMAP, map16); + + /* 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 }; + + (void) ioctl(vcfd, PIO_UNIMAPCLR, &adv); + (void) ioctl(vcfd, PIO_UNIMAP, &unimapd); + } + } +} + +int main(int argc, char **argv) { + const char *vc; + _cleanup_free_ char + *vc_keymap = NULL, *vc_keymap_toggle = NULL, + *vc_font = NULL, *vc_font_map = NULL, *vc_font_unimap = NULL; + _cleanup_close_ int fd = -1; + bool utf8, font_copy = false, font_ok, keyboard_ok; + int r = EXIT_FAILURE; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (argv[1]) + vc = argv[1]; + else { + vc = "/dev/tty0"; + font_copy = true; + } + + fd = open_terminal(vc, O_RDWR|O_CLOEXEC); + if (fd < 0) { + log_error_errno(fd, "Failed to open %s: %m", vc); + return EXIT_FAILURE; + } + + if (!is_vconsole(fd)) { + log_error("Device %s is not a virtual console.", vc); + return EXIT_FAILURE; + } + + utf8 = is_locale_utf8(); + + r = parse_env_file("/etc/vconsole.conf", NEWLINE, + "KEYMAP", &vc_keymap, + "KEYMAP_TOGGLE", &vc_keymap_toggle, + "FONT", &vc_font, + "FONT_MAP", &vc_font_map, + "FONT_UNIMAP", &vc_font_unimap, + NULL); + + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read /etc/vconsole.conf: %m"); + + /* Let the kernel command line override /etc/vconsole.conf */ + if (detect_container() <= 0) { + r = parse_env_file("/proc/cmdline", WHITESPACE, + "vconsole.keymap", &vc_keymap, + "vconsole.keymap.toggle", &vc_keymap_toggle, + "vconsole.font", &vc_font, + "vconsole.font.map", &vc_font_map, + "vconsole.font.unimap", &vc_font_unimap, + NULL); + + if (r < 0 && r != -ENOENT) + 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; + + /* Only copy the font when we executed setfont successfully */ + if (font_copy && font_ok) + (void) font_copy_to_all_vcs(fd); + + return font_ok && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/systemd/Makefile b/src/systemd/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/systemd/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/systemd/_sd-common.h b/src/systemd/_sd-common.h deleted file mode 100644 index 2d4e1f26e1..0000000000 --- a/src/systemd/_sd-common.h +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef foosdcommonhfoo -#define foosdcommonhfoo - -/*** - 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 . -***/ - -/* This is a private header; never even think of including this directly! */ - -#if __INCLUDE_LEVEL__ <= 1 -#error "Do not include _sd-common.h directly; it is a private header." -#endif - -#ifndef _sd_printf_ -# if __GNUC__ >= 4 -# define _sd_printf_(a,b) __attribute__ ((format (printf, a, b))) -# else -# define _sd_printf_(a,b) -# endif -#endif - -#ifndef _sd_sentinel_ -# define _sd_sentinel_ __attribute__((sentinel)) -#endif - -#ifndef _sd_packed_ -# define _sd_packed_ __attribute__((packed)) -#endif - -#ifndef _sd_pure_ -# define _sd_pure_ __attribute__((pure)) -#endif - -#ifndef _SD_STRINGIFY -# define _SD_XSTRINGIFY(x) #x -# define _SD_STRINGIFY(x) _SD_XSTRINGIFY(x) -#endif - -#ifndef _SD_BEGIN_DECLARATIONS -# ifdef __cplusplus -# define _SD_BEGIN_DECLARATIONS \ - extern "C" { \ - struct _sd_useless_struct_to_allow_trailing_semicolon_ -# else -# define _SD_BEGIN_DECLARATIONS \ - struct _sd_useless_struct_to_allow_trailing_semicolon_ -# endif -#endif - -#ifndef _SD_END_DECLARATIONS -# ifdef __cplusplus -# define _SD_END_DECLARATIONS \ - } \ - struct _sd_useless_cpp_struct_to_allow_trailing_semicolon_ -# else -# define _SD_END_DECLARATIONS \ - struct _sd_useless_struct_to_allow_trailing_semicolon_ -# endif -#endif - -#define _SD_DEFINE_POINTER_CLEANUP_FUNC(type, func) \ - static inline void func##p(type **p) { \ - if (*p) \ - func(*p); \ - } \ - struct _sd_useless_struct_to_allow_trailing_semicolon_ - -#endif diff --git a/src/systemd/sd-bus-protocol.h b/src/systemd/sd-bus-protocol.h deleted file mode 100644 index 47b256d5b9..0000000000 --- a/src/systemd/sd-bus-protocol.h +++ /dev/null @@ -1,102 +0,0 @@ -#ifndef foosdbusprotocolhfoo -#define foosdbusprotocolhfoo - -/*** - 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 . -***/ - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -/* Types of message */ - -enum { - _SD_BUS_MESSAGE_TYPE_INVALID = 0, - SD_BUS_MESSAGE_METHOD_CALL, - SD_BUS_MESSAGE_METHOD_RETURN, - SD_BUS_MESSAGE_METHOD_ERROR, - SD_BUS_MESSAGE_SIGNAL, - _SD_BUS_MESSAGE_TYPE_MAX -}; - -/* Primitive types */ - -enum { - _SD_BUS_TYPE_INVALID = 0, - SD_BUS_TYPE_BYTE = 'y', - SD_BUS_TYPE_BOOLEAN = 'b', - SD_BUS_TYPE_INT16 = 'n', - SD_BUS_TYPE_UINT16 = 'q', - SD_BUS_TYPE_INT32 = 'i', - SD_BUS_TYPE_UINT32 = 'u', - SD_BUS_TYPE_INT64 = 'x', - SD_BUS_TYPE_UINT64 = 't', - SD_BUS_TYPE_DOUBLE = 'd', - SD_BUS_TYPE_STRING = 's', - SD_BUS_TYPE_OBJECT_PATH = 'o', - SD_BUS_TYPE_SIGNATURE = 'g', - SD_BUS_TYPE_UNIX_FD = 'h', - SD_BUS_TYPE_ARRAY = 'a', - SD_BUS_TYPE_VARIANT = 'v', - SD_BUS_TYPE_STRUCT = 'r', /* not actually used in signatures */ - SD_BUS_TYPE_STRUCT_BEGIN = '(', - SD_BUS_TYPE_STRUCT_END = ')', - SD_BUS_TYPE_DICT_ENTRY = 'e', /* not actually used in signatures */ - SD_BUS_TYPE_DICT_ENTRY_BEGIN = '{', - SD_BUS_TYPE_DICT_ENTRY_END = '}', -}; - -/* Well-known errors. Note that this is only a sanitized subset of the - * errors that the reference implementation generates. */ - -#define SD_BUS_ERROR_FAILED "org.freedesktop.DBus.Error.Failed" -#define SD_BUS_ERROR_NO_MEMORY "org.freedesktop.DBus.Error.NoMemory" -#define SD_BUS_ERROR_SERVICE_UNKNOWN "org.freedesktop.DBus.Error.ServiceUnknown" -#define SD_BUS_ERROR_NAME_HAS_NO_OWNER "org.freedesktop.DBus.Error.NameHasNoOwner" -#define SD_BUS_ERROR_NO_REPLY "org.freedesktop.DBus.Error.NoReply" -#define SD_BUS_ERROR_IO_ERROR "org.freedesktop.DBus.Error.IOError" -#define SD_BUS_ERROR_BAD_ADDRESS "org.freedesktop.DBus.Error.BadAddress" -#define SD_BUS_ERROR_NOT_SUPPORTED "org.freedesktop.DBus.Error.NotSupported" -#define SD_BUS_ERROR_LIMITS_EXCEEDED "org.freedesktop.DBus.Error.LimitsExceeded" -#define SD_BUS_ERROR_ACCESS_DENIED "org.freedesktop.DBus.Error.AccessDenied" -#define SD_BUS_ERROR_AUTH_FAILED "org.freedesktop.DBus.Error.AuthFailed" -#define SD_BUS_ERROR_NO_SERVER "org.freedesktop.DBus.Error.NoServer" -#define SD_BUS_ERROR_TIMEOUT "org.freedesktop.DBus.Error.Timeout" -#define SD_BUS_ERROR_NO_NETWORK "org.freedesktop.DBus.Error.NoNetwork" -#define SD_BUS_ERROR_ADDRESS_IN_USE "org.freedesktop.DBus.Error.AddressInUse" -#define SD_BUS_ERROR_DISCONNECTED "org.freedesktop.DBus.Error.Disconnected" -#define SD_BUS_ERROR_INVALID_ARGS "org.freedesktop.DBus.Error.InvalidArgs" -#define SD_BUS_ERROR_FILE_NOT_FOUND "org.freedesktop.DBus.Error.FileNotFound" -#define SD_BUS_ERROR_FILE_EXISTS "org.freedesktop.DBus.Error.FileExists" -#define SD_BUS_ERROR_UNKNOWN_METHOD "org.freedesktop.DBus.Error.UnknownMethod" -#define SD_BUS_ERROR_UNKNOWN_OBJECT "org.freedesktop.DBus.Error.UnknownObject" -#define SD_BUS_ERROR_UNKNOWN_INTERFACE "org.freedesktop.DBus.Error.UnknownInterface" -#define SD_BUS_ERROR_UNKNOWN_PROPERTY "org.freedesktop.DBus.Error.UnknownProperty" -#define SD_BUS_ERROR_PROPERTY_READ_ONLY "org.freedesktop.DBus.Error.PropertyReadOnly" -#define SD_BUS_ERROR_UNIX_PROCESS_ID_UNKNOWN "org.freedesktop.DBus.Error.UnixProcessIdUnknown" -#define SD_BUS_ERROR_INVALID_SIGNATURE "org.freedesktop.DBus.Error.InvalidSignature" -#define SD_BUS_ERROR_INCONSISTENT_MESSAGE "org.freedesktop.DBus.Error.InconsistentMessage" -#define SD_BUS_ERROR_MATCH_RULE_NOT_FOUND "org.freedesktop.DBus.Error.MatchRuleNotFound" -#define SD_BUS_ERROR_MATCH_RULE_INVALID "org.freedesktop.DBus.Error.MatchRuleInvalid" -#define SD_BUS_ERROR_INTERACTIVE_AUTHORIZATION_REQUIRED \ - "org.freedesktop.DBus.Error.InteractiveAuthorizationRequired" - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-bus-vtable.h b/src/systemd/sd-bus-vtable.h deleted file mode 100644 index 6ad6d51979..0000000000 --- a/src/systemd/sd-bus-vtable.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef foosdbusvtablehfoo -#define foosdbusvtablehfoo - -/*** - 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 . -***/ - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_bus_vtable sd_bus_vtable; - -#include "sd-bus.h" - -enum { - _SD_BUS_VTABLE_START = '<', - _SD_BUS_VTABLE_END = '>', - _SD_BUS_VTABLE_METHOD = 'M', - _SD_BUS_VTABLE_SIGNAL = 'S', - _SD_BUS_VTABLE_PROPERTY = 'P', - _SD_BUS_VTABLE_WRITABLE_PROPERTY = 'W', -}; - -enum { - SD_BUS_VTABLE_DEPRECATED = 1ULL << 0, - SD_BUS_VTABLE_HIDDEN = 1ULL << 1, - SD_BUS_VTABLE_UNPRIVILEGED = 1ULL << 2, - SD_BUS_VTABLE_METHOD_NO_REPLY = 1ULL << 3, - SD_BUS_VTABLE_PROPERTY_CONST = 1ULL << 4, - SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE = 1ULL << 5, - SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION = 1ULL << 6, - SD_BUS_VTABLE_PROPERTY_EXPLICIT = 1ULL << 7, - _SD_BUS_VTABLE_CAPABILITY_MASK = 0xFFFFULL << 40 -}; - -#define SD_BUS_VTABLE_CAPABILITY(x) ((uint64_t) (((x)+1) & 0xFFFF) << 40) - -struct sd_bus_vtable { - /* Please do not initialize this structure directly, use the - * macros below instead */ - - uint8_t type:8; - uint64_t flags:56; - union { - struct { - size_t element_size; - } start; - struct { - const char *member; - const char *signature; - const char *result; - sd_bus_message_handler_t handler; - size_t offset; - } method; - struct { - const char *member; - const char *signature; - } signal; - struct { - const char *member; - const char *signature; - sd_bus_property_get_t get; - sd_bus_property_set_t set; - size_t offset; - } property; - } x; -}; - -#define SD_BUS_VTABLE_START(_flags) \ - { \ - .type = _SD_BUS_VTABLE_START, \ - .flags = _flags, \ - .x.start.element_size = sizeof(sd_bus_vtable), \ - } - -#define SD_BUS_METHOD_WITH_OFFSET(_member, _signature, _result, _handler, _offset, _flags) \ - { \ - .type = _SD_BUS_VTABLE_METHOD, \ - .flags = _flags, \ - .x.method.member = _member, \ - .x.method.signature = _signature, \ - .x.method.result = _result, \ - .x.method.handler = _handler, \ - .x.method.offset = _offset, \ - } -#define SD_BUS_METHOD(_member, _signature, _result, _handler, _flags) \ - SD_BUS_METHOD_WITH_OFFSET(_member, _signature, _result, _handler, 0, _flags) - -#define SD_BUS_SIGNAL(_member, _signature, _flags) \ - { \ - .type = _SD_BUS_VTABLE_SIGNAL, \ - .flags = _flags, \ - .x.signal.member = _member, \ - .x.signal.signature = _signature, \ - } - -#define SD_BUS_PROPERTY(_member, _signature, _get, _offset, _flags) \ - { \ - .type = _SD_BUS_VTABLE_PROPERTY, \ - .flags = _flags, \ - .x.property.member = _member, \ - .x.property.signature = _signature, \ - .x.property.get = _get, \ - .x.property.offset = _offset, \ - } - -#define SD_BUS_WRITABLE_PROPERTY(_member, _signature, _get, _set, _offset, _flags) \ - { \ - .type = _SD_BUS_VTABLE_WRITABLE_PROPERTY, \ - .flags = _flags, \ - .x.property.member = _member, \ - .x.property.signature = _signature, \ - .x.property.get = _get, \ - .x.property.set = _set, \ - .x.property.offset = _offset, \ - } - -#define SD_BUS_VTABLE_END \ - { \ - .type = _SD_BUS_VTABLE_END, \ - } - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-bus.h b/src/systemd/sd-bus.h deleted file mode 100644 index 2a2ef0eb98..0000000000 --- a/src/systemd/sd-bus.h +++ /dev/null @@ -1,456 +0,0 @@ -#ifndef foosdbushfoo -#define foosdbushfoo - -/*** - 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 . -***/ - -#include -#include -#include -#include - -#include "sd-event.h" -#include "sd-id128.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -/* Types */ - -typedef struct sd_bus sd_bus; -typedef struct sd_bus_message sd_bus_message; -typedef struct sd_bus_slot sd_bus_slot; -typedef struct sd_bus_creds sd_bus_creds; -typedef struct sd_bus_track sd_bus_track; - -typedef struct { - const char *name; - const char *message; - int _need_free; -} sd_bus_error; - -typedef struct { - const char* name; - int code; -} sd_bus_error_map; - -/* Flags */ - -enum { - SD_BUS_CREDS_PID = 1ULL << 0, - SD_BUS_CREDS_TID = 1ULL << 1, - SD_BUS_CREDS_PPID = 1ULL << 2, - SD_BUS_CREDS_UID = 1ULL << 3, - SD_BUS_CREDS_EUID = 1ULL << 4, - SD_BUS_CREDS_SUID = 1ULL << 5, - SD_BUS_CREDS_FSUID = 1ULL << 6, - SD_BUS_CREDS_GID = 1ULL << 7, - SD_BUS_CREDS_EGID = 1ULL << 8, - SD_BUS_CREDS_SGID = 1ULL << 9, - SD_BUS_CREDS_FSGID = 1ULL << 10, - SD_BUS_CREDS_SUPPLEMENTARY_GIDS = 1ULL << 11, - SD_BUS_CREDS_COMM = 1ULL << 12, - SD_BUS_CREDS_TID_COMM = 1ULL << 13, - SD_BUS_CREDS_EXE = 1ULL << 14, - SD_BUS_CREDS_CMDLINE = 1ULL << 15, - SD_BUS_CREDS_CGROUP = 1ULL << 16, - SD_BUS_CREDS_UNIT = 1ULL << 17, - SD_BUS_CREDS_SLICE = 1ULL << 18, - SD_BUS_CREDS_USER_UNIT = 1ULL << 19, - SD_BUS_CREDS_USER_SLICE = 1ULL << 20, - SD_BUS_CREDS_SESSION = 1ULL << 21, - SD_BUS_CREDS_OWNER_UID = 1ULL << 22, - SD_BUS_CREDS_EFFECTIVE_CAPS = 1ULL << 23, - SD_BUS_CREDS_PERMITTED_CAPS = 1ULL << 24, - SD_BUS_CREDS_INHERITABLE_CAPS = 1ULL << 25, - SD_BUS_CREDS_BOUNDING_CAPS = 1ULL << 26, - SD_BUS_CREDS_SELINUX_CONTEXT = 1ULL << 27, - SD_BUS_CREDS_AUDIT_SESSION_ID = 1ULL << 28, - SD_BUS_CREDS_AUDIT_LOGIN_UID = 1ULL << 29, - SD_BUS_CREDS_TTY = 1ULL << 30, - SD_BUS_CREDS_UNIQUE_NAME = 1ULL << 31, - SD_BUS_CREDS_WELL_KNOWN_NAMES = 1ULL << 32, - SD_BUS_CREDS_DESCRIPTION = 1ULL << 33, - SD_BUS_CREDS_AUGMENT = 1ULL << 63, /* special flag, if on sd-bus will augment creds struct, in a potentially race-full way. */ - _SD_BUS_CREDS_ALL = (1ULL << 34) -1, -}; - -enum { - SD_BUS_NAME_REPLACE_EXISTING = 1ULL << 0, - SD_BUS_NAME_ALLOW_REPLACEMENT = 1ULL << 1, - SD_BUS_NAME_QUEUE = 1ULL << 2, -}; - -/* Callbacks */ - -typedef int (*sd_bus_message_handler_t)(sd_bus_message *m, void *userdata, sd_bus_error *ret_error); -typedef int (*sd_bus_property_get_t) (sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *reply, void *userdata, sd_bus_error *ret_error); -typedef int (*sd_bus_property_set_t) (sd_bus *bus, const char *path, const char *interface, const char *property, sd_bus_message *value, void *userdata, sd_bus_error *ret_error); -typedef int (*sd_bus_object_find_t) (sd_bus *bus, const char *path, const char *interface, void *userdata, void **ret_found, sd_bus_error *ret_error); -typedef int (*sd_bus_node_enumerator_t) (sd_bus *bus, const char *prefix, void *userdata, char ***ret_nodes, sd_bus_error *ret_error); -typedef int (*sd_bus_track_handler_t) (sd_bus_track *track, void *userdata); - -#include "sd-bus-protocol.h" -#include "sd-bus-vtable.h" - -/* Connections */ - -int sd_bus_default(sd_bus **ret); -int sd_bus_default_user(sd_bus **ret); -int sd_bus_default_system(sd_bus **ret); - -int sd_bus_open(sd_bus **ret); -int sd_bus_open_user(sd_bus **ret); -int sd_bus_open_system(sd_bus **ret); -int sd_bus_open_system_remote(sd_bus **ret, const char *host); -int sd_bus_open_system_machine(sd_bus **ret, const char *machine); - -int sd_bus_new(sd_bus **ret); - -int sd_bus_set_address(sd_bus *bus, const char *address); -int sd_bus_set_fd(sd_bus *bus, int input_fd, int output_fd); -int sd_bus_set_exec(sd_bus *bus, const char *path, char *const argv[]); -int sd_bus_get_address(sd_bus *bus, const char **address); -int sd_bus_set_bus_client(sd_bus *bus, int b); -int sd_bus_is_bus_client(sd_bus *bus); -int sd_bus_set_server(sd_bus *bus, int b, sd_id128_t bus_id); -int sd_bus_is_server(sd_bus *bus); -int sd_bus_set_anonymous(sd_bus *bus, int b); -int sd_bus_is_anonymous(sd_bus *bus); -int sd_bus_set_trusted(sd_bus *bus, int b); -int sd_bus_is_trusted(sd_bus *bus); -int sd_bus_set_monitor(sd_bus *bus, int b); -int sd_bus_is_monitor(sd_bus *bus); -int sd_bus_set_description(sd_bus *bus, const char *description); -int sd_bus_get_description(sd_bus *bus, const char **description); -int sd_bus_negotiate_creds(sd_bus *bus, int b, uint64_t creds_mask); -int sd_bus_negotiate_timestamp(sd_bus *bus, int b); -int sd_bus_negotiate_fds(sd_bus *bus, int b); -int sd_bus_can_send(sd_bus *bus, char type); -int sd_bus_get_creds_mask(sd_bus *bus, uint64_t *creds_mask); -int sd_bus_set_allow_interactive_authorization(sd_bus *bus, int b); -int sd_bus_get_allow_interactive_authorization(sd_bus *bus); - -int sd_bus_start(sd_bus *ret); - -int sd_bus_try_close(sd_bus *bus); -void sd_bus_close(sd_bus *bus); - -sd_bus *sd_bus_ref(sd_bus *bus); -sd_bus *sd_bus_unref(sd_bus *bus); -sd_bus *sd_bus_flush_close_unref(sd_bus *bus); - -void sd_bus_default_flush_close(void); - -int sd_bus_is_open(sd_bus *bus); - -int sd_bus_get_bus_id(sd_bus *bus, sd_id128_t *id); -int sd_bus_get_scope(sd_bus *bus, const char **scope); -int sd_bus_get_tid(sd_bus *bus, pid_t *tid); -int sd_bus_get_owner_creds(sd_bus *bus, uint64_t creds_mask, sd_bus_creds **ret); - -int sd_bus_send(sd_bus *bus, sd_bus_message *m, uint64_t *cookie); -int sd_bus_send_to(sd_bus *bus, sd_bus_message *m, const char *destination, uint64_t *cookie); -int sd_bus_call(sd_bus *bus, sd_bus_message *m, uint64_t usec, sd_bus_error *ret_error, sd_bus_message **reply); -int sd_bus_call_async(sd_bus *bus, sd_bus_slot **slot, sd_bus_message *m, sd_bus_message_handler_t callback, void *userdata, uint64_t usec); - -int sd_bus_get_fd(sd_bus *bus); -int sd_bus_get_events(sd_bus *bus); -int sd_bus_get_timeout(sd_bus *bus, uint64_t *timeout_usec); -int sd_bus_process(sd_bus *bus, sd_bus_message **r); -int sd_bus_process_priority(sd_bus *bus, int64_t max_priority, sd_bus_message **r); -int sd_bus_wait(sd_bus *bus, uint64_t timeout_usec); -int sd_bus_flush(sd_bus *bus); - -sd_bus_slot* sd_bus_get_current_slot(sd_bus *bus); -sd_bus_message* sd_bus_get_current_message(sd_bus *bus); -sd_bus_message_handler_t sd_bus_get_current_handler(sd_bus *bus); -void* sd_bus_get_current_userdata(sd_bus *bus); - -int sd_bus_attach_event(sd_bus *bus, sd_event *e, int priority); -int sd_bus_detach_event(sd_bus *bus); -sd_event *sd_bus_get_event(sd_bus *bus); - -int sd_bus_add_filter(sd_bus *bus, sd_bus_slot **slot, sd_bus_message_handler_t callback, void *userdata); -int sd_bus_add_match(sd_bus *bus, sd_bus_slot **slot, const char *match, sd_bus_message_handler_t callback, void *userdata); -int sd_bus_add_object(sd_bus *bus, sd_bus_slot **slot, const char *path, sd_bus_message_handler_t callback, void *userdata); -int sd_bus_add_fallback(sd_bus *bus, sd_bus_slot **slot, const char *prefix, sd_bus_message_handler_t callback, void *userdata); -int sd_bus_add_object_vtable(sd_bus *bus, sd_bus_slot **slot, const char *path, const char *interface, const sd_bus_vtable *vtable, void *userdata); -int sd_bus_add_fallback_vtable(sd_bus *bus, sd_bus_slot **slot, const char *prefix, const char *interface, const sd_bus_vtable *vtable, sd_bus_object_find_t find, void *userdata); -int sd_bus_add_node_enumerator(sd_bus *bus, sd_bus_slot **slot, const char *path, sd_bus_node_enumerator_t callback, void *userdata); -int sd_bus_add_object_manager(sd_bus *bus, sd_bus_slot **slot, const char *path); - -/* Slot object */ - -sd_bus_slot* sd_bus_slot_ref(sd_bus_slot *slot); -sd_bus_slot* sd_bus_slot_unref(sd_bus_slot *slot); - -sd_bus* sd_bus_slot_get_bus(sd_bus_slot *slot); -void *sd_bus_slot_get_userdata(sd_bus_slot *slot); -void *sd_bus_slot_set_userdata(sd_bus_slot *slot, void *userdata); -int sd_bus_slot_set_description(sd_bus_slot *slot, const char *description); -int sd_bus_slot_get_description(sd_bus_slot *slot, const char **description); - -sd_bus_message* sd_bus_slot_get_current_message(sd_bus_slot *slot); -sd_bus_message_handler_t sd_bus_slot_get_current_handler(sd_bus_slot *bus); -void *sd_bus_slot_get_current_userdata(sd_bus_slot *slot); - -/* Message object */ - -int sd_bus_message_new_signal(sd_bus *bus, sd_bus_message **m, const char *path, const char *interface, const char *member); -int sd_bus_message_new_method_call(sd_bus *bus, sd_bus_message **m, const char *destination, const char *path, const char *interface, const char *member); -int sd_bus_message_new_method_return(sd_bus_message *call, sd_bus_message **m); -int sd_bus_message_new_method_error(sd_bus_message *call, sd_bus_message **m, const sd_bus_error *e); -int sd_bus_message_new_method_errorf(sd_bus_message *call, sd_bus_message **m, const char *name, const char *format, ...) _sd_printf_(4, 5); -int sd_bus_message_new_method_errno(sd_bus_message *call, sd_bus_message **m, int error, const sd_bus_error *e); -int sd_bus_message_new_method_errnof(sd_bus_message *call, sd_bus_message **m, int error, const char *format, ...) _sd_printf_(4, 5); - -sd_bus_message* sd_bus_message_ref(sd_bus_message *m); -sd_bus_message* sd_bus_message_unref(sd_bus_message *m); - -int sd_bus_message_get_type(sd_bus_message *m, uint8_t *type); -int sd_bus_message_get_cookie(sd_bus_message *m, uint64_t *cookie); -int sd_bus_message_get_reply_cookie(sd_bus_message *m, uint64_t *cookie); -int sd_bus_message_get_priority(sd_bus_message *m, int64_t *priority); - -int sd_bus_message_get_expect_reply(sd_bus_message *m); -int sd_bus_message_get_auto_start(sd_bus_message *m); -int sd_bus_message_get_allow_interactive_authorization(sd_bus_message *m); - -const char *sd_bus_message_get_signature(sd_bus_message *m, int complete); -const char *sd_bus_message_get_path(sd_bus_message *m); -const char *sd_bus_message_get_interface(sd_bus_message *m); -const char *sd_bus_message_get_member(sd_bus_message *m); -const char *sd_bus_message_get_destination(sd_bus_message *m); -const char *sd_bus_message_get_sender(sd_bus_message *m); -const sd_bus_error *sd_bus_message_get_error(sd_bus_message *m); -int sd_bus_message_get_errno(sd_bus_message *m); - -int sd_bus_message_get_monotonic_usec(sd_bus_message *m, uint64_t *usec); -int sd_bus_message_get_realtime_usec(sd_bus_message *m, uint64_t *usec); -int sd_bus_message_get_seqnum(sd_bus_message *m, uint64_t* seqnum); - -sd_bus* sd_bus_message_get_bus(sd_bus_message *m); -sd_bus_creds *sd_bus_message_get_creds(sd_bus_message *m); /* do not unref the result */ - -int sd_bus_message_is_signal(sd_bus_message *m, const char *interface, const char *member); -int sd_bus_message_is_method_call(sd_bus_message *m, const char *interface, const char *member); -int sd_bus_message_is_method_error(sd_bus_message *m, const char *name); -int sd_bus_message_is_empty(sd_bus_message *m); -int sd_bus_message_has_signature(sd_bus_message *m, const char *signature); - -int sd_bus_message_set_expect_reply(sd_bus_message *m, int b); -int sd_bus_message_set_auto_start(sd_bus_message *m, int b); -int sd_bus_message_set_allow_interactive_authorization(sd_bus_message *m, int b); - -int sd_bus_message_set_destination(sd_bus_message *m, const char *destination); -int sd_bus_message_set_priority(sd_bus_message *m, int64_t priority); - -int sd_bus_message_append(sd_bus_message *m, const char *types, ...); -int sd_bus_message_append_basic(sd_bus_message *m, char type, const void *p); -int sd_bus_message_append_array(sd_bus_message *m, char type, const void *ptr, size_t size); -int sd_bus_message_append_array_space(sd_bus_message *m, char type, size_t size, void **ptr); -int sd_bus_message_append_array_iovec(sd_bus_message *m, char type, const struct iovec *iov, unsigned n); -int sd_bus_message_append_array_memfd(sd_bus_message *m, char type, int memfd, uint64_t offset, uint64_t size); -int sd_bus_message_append_string_space(sd_bus_message *m, size_t size, char **s); -int sd_bus_message_append_string_iovec(sd_bus_message *m, const struct iovec *iov, unsigned n); -int sd_bus_message_append_string_memfd(sd_bus_message *m, int memfd, uint64_t offset, uint64_t size); -int sd_bus_message_append_strv(sd_bus_message *m, char **l); -int sd_bus_message_open_container(sd_bus_message *m, char type, const char *contents); -int sd_bus_message_close_container(sd_bus_message *m); -int sd_bus_message_copy(sd_bus_message *m, sd_bus_message *source, int all); - -int sd_bus_message_read(sd_bus_message *m, const char *types, ...); -int sd_bus_message_read_basic(sd_bus_message *m, char type, void *p); -int sd_bus_message_read_array(sd_bus_message *m, char type, const void **ptr, size_t *size); -int sd_bus_message_read_strv(sd_bus_message *m, char ***l); /* free the result! */ -int sd_bus_message_skip(sd_bus_message *m, const char *types); -int sd_bus_message_enter_container(sd_bus_message *m, char type, const char *contents); -int sd_bus_message_exit_container(sd_bus_message *m); -int sd_bus_message_peek_type(sd_bus_message *m, char *type, const char **contents); -int sd_bus_message_verify_type(sd_bus_message *m, char type, const char *contents); -int sd_bus_message_at_end(sd_bus_message *m, int complete); -int sd_bus_message_rewind(sd_bus_message *m, int complete); - -/* Bus management */ - -int sd_bus_get_unique_name(sd_bus *bus, const char **unique); -int sd_bus_request_name(sd_bus *bus, const char *name, uint64_t flags); -int sd_bus_release_name(sd_bus *bus, const char *name); -int sd_bus_list_names(sd_bus *bus, char ***acquired, char ***activatable); /* free the results */ -int sd_bus_get_name_creds(sd_bus *bus, const char *name, uint64_t mask, sd_bus_creds **creds); /* unref the result! */ -int sd_bus_get_name_machine_id(sd_bus *bus, const char *name, sd_id128_t *machine); - -/* Convenience calls */ - -int sd_bus_call_method(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, sd_bus_message **reply, const char *types, ...); -int sd_bus_call_method_async(sd_bus *bus, sd_bus_slot **slot, const char *destination, const char *path, const char *interface, const char *member, sd_bus_message_handler_t callback, void *userdata, const char *types, ...); -int sd_bus_get_property(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, sd_bus_message **reply, const char *type); -int sd_bus_get_property_trivial(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char type, void *ret_ptr); -int sd_bus_get_property_string(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char **ret); /* free the result! */ -int sd_bus_get_property_strv(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, char ***ret); /* free the result! */ -int sd_bus_set_property(sd_bus *bus, const char *destination, const char *path, const char *interface, const char *member, sd_bus_error *ret_error, const char *type, ...); - -int sd_bus_reply_method_return(sd_bus_message *call, const char *types, ...); -int sd_bus_reply_method_error(sd_bus_message *call, const sd_bus_error *e); -int sd_bus_reply_method_errorf(sd_bus_message *call, const char *name, const char *format, ...) _sd_printf_(3, 4); -int sd_bus_reply_method_errno(sd_bus_message *call, int error, const sd_bus_error *e); -int sd_bus_reply_method_errnof(sd_bus_message *call, int error, const char *format, ...) _sd_printf_(3, 4); - -int sd_bus_emit_signal(sd_bus *bus, const char *path, const char *interface, const char *member, const char *types, ...); - -int sd_bus_emit_properties_changed_strv(sd_bus *bus, const char *path, const char *interface, char **names); -int sd_bus_emit_properties_changed(sd_bus *bus, const char *path, const char *interface, const char *name, ...) _sd_sentinel_; - -int sd_bus_emit_object_added(sd_bus *bus, const char *path); -int sd_bus_emit_object_removed(sd_bus *bus, const char *path); -int sd_bus_emit_interfaces_added_strv(sd_bus *bus, const char *path, char **interfaces); -int sd_bus_emit_interfaces_added(sd_bus *bus, const char *path, const char *interface, ...) _sd_sentinel_; -int sd_bus_emit_interfaces_removed_strv(sd_bus *bus, const char *path, char **interfaces); -int sd_bus_emit_interfaces_removed(sd_bus *bus, const char *path, const char *interface, ...) _sd_sentinel_; - -int sd_bus_query_sender_creds(sd_bus_message *call, uint64_t mask, sd_bus_creds **creds); -int sd_bus_query_sender_privilege(sd_bus_message *call, int capability); - -/* Credential handling */ - -int sd_bus_creds_new_from_pid(sd_bus_creds **ret, pid_t pid, uint64_t creds_mask); -sd_bus_creds *sd_bus_creds_ref(sd_bus_creds *c); -sd_bus_creds *sd_bus_creds_unref(sd_bus_creds *c); -uint64_t sd_bus_creds_get_mask(const sd_bus_creds *c); -uint64_t sd_bus_creds_get_augmented_mask(const sd_bus_creds *c); - -int sd_bus_creds_get_pid(sd_bus_creds *c, pid_t *pid); -int sd_bus_creds_get_ppid(sd_bus_creds *c, pid_t *ppid); -int sd_bus_creds_get_tid(sd_bus_creds *c, pid_t *tid); -int sd_bus_creds_get_uid(sd_bus_creds *c, uid_t *uid); -int sd_bus_creds_get_euid(sd_bus_creds *c, uid_t *euid); -int sd_bus_creds_get_suid(sd_bus_creds *c, uid_t *suid); -int sd_bus_creds_get_fsuid(sd_bus_creds *c, uid_t *fsuid); -int sd_bus_creds_get_gid(sd_bus_creds *c, gid_t *gid); -int sd_bus_creds_get_egid(sd_bus_creds *c, gid_t *egid); -int sd_bus_creds_get_sgid(sd_bus_creds *c, gid_t *sgid); -int sd_bus_creds_get_fsgid(sd_bus_creds *c, gid_t *fsgid); -int sd_bus_creds_get_supplementary_gids(sd_bus_creds *c, const gid_t **gids); -int sd_bus_creds_get_comm(sd_bus_creds *c, const char **comm); -int sd_bus_creds_get_tid_comm(sd_bus_creds *c, const char **comm); -int sd_bus_creds_get_exe(sd_bus_creds *c, const char **exe); -int sd_bus_creds_get_cmdline(sd_bus_creds *c, char ***cmdline); -int sd_bus_creds_get_cgroup(sd_bus_creds *c, const char **cgroup); -int sd_bus_creds_get_unit(sd_bus_creds *c, const char **unit); -int sd_bus_creds_get_slice(sd_bus_creds *c, const char **slice); -int sd_bus_creds_get_user_unit(sd_bus_creds *c, const char **unit); -int sd_bus_creds_get_user_slice(sd_bus_creds *c, const char **slice); -int sd_bus_creds_get_session(sd_bus_creds *c, const char **session); -int sd_bus_creds_get_owner_uid(sd_bus_creds *c, uid_t *uid); -int sd_bus_creds_has_effective_cap(sd_bus_creds *c, int capability); -int sd_bus_creds_has_permitted_cap(sd_bus_creds *c, int capability); -int sd_bus_creds_has_inheritable_cap(sd_bus_creds *c, int capability); -int sd_bus_creds_has_bounding_cap(sd_bus_creds *c, int capability); -int sd_bus_creds_get_selinux_context(sd_bus_creds *c, const char **context); -int sd_bus_creds_get_audit_session_id(sd_bus_creds *c, uint32_t *sessionid); -int sd_bus_creds_get_audit_login_uid(sd_bus_creds *c, uid_t *loginuid); -int sd_bus_creds_get_tty(sd_bus_creds *c, const char **tty); -int sd_bus_creds_get_unique_name(sd_bus_creds *c, const char **name); -int sd_bus_creds_get_well_known_names(sd_bus_creds *c, char ***names); -int sd_bus_creds_get_description(sd_bus_creds *c, const char **name); - -/* Error structures */ - -#define SD_BUS_ERROR_MAKE_CONST(name, message) ((const sd_bus_error) {(name), (message), 0}) -#define SD_BUS_ERROR_NULL SD_BUS_ERROR_MAKE_CONST(NULL, NULL) - -void sd_bus_error_free(sd_bus_error *e); -int sd_bus_error_set(sd_bus_error *e, const char *name, const char *message); -int sd_bus_error_setf(sd_bus_error *e, const char *name, const char *format, ...) _sd_printf_(3, 4); -int sd_bus_error_set_const(sd_bus_error *e, const char *name, const char *message); -int sd_bus_error_set_errno(sd_bus_error *e, int error); -int sd_bus_error_set_errnof(sd_bus_error *e, int error, const char *format, ...) _sd_printf_(3, 4); -int sd_bus_error_set_errnofv(sd_bus_error *e, int error, const char *format, va_list ap) _sd_printf_(3,0); -int sd_bus_error_get_errno(const sd_bus_error *e); -int sd_bus_error_copy(sd_bus_error *dest, const sd_bus_error *e); -int sd_bus_error_is_set(const sd_bus_error *e); -int sd_bus_error_has_name(const sd_bus_error *e, const char *name); - -#define SD_BUS_ERROR_MAP(_name, _code) \ - { \ - .name = _name, \ - .code = _code, \ - } -#define SD_BUS_ERROR_MAP_END \ - { \ - .name = NULL, \ - .code = - 'x', \ - } - -int sd_bus_error_add_map(const sd_bus_error_map *map); - -/* Auxiliary macros */ - -#define SD_BUS_MESSAGE_APPEND_ID128(x) 16, \ - (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], \ - (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], \ - (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], \ - (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15] - -#define SD_BUS_MESSAGE_READ_ID128(x) 16, \ - &(x).bytes[0], &(x).bytes[1], &(x).bytes[2], &(x).bytes[3], \ - &(x).bytes[4], &(x).bytes[5], &(x).bytes[6], &(x).bytes[7], \ - &(x).bytes[8], &(x).bytes[9], &(x).bytes[10], &(x).bytes[11], \ - &(x).bytes[12], &(x).bytes[13], &(x).bytes[14], &(x).bytes[15] - -/* Label escaping */ - -int sd_bus_path_encode(const char *prefix, const char *external_id, char **ret_path); -int sd_bus_path_encode_many(char **out, const char *path_template, ...); -int sd_bus_path_decode(const char *path, const char *prefix, char **ret_external_id); -int sd_bus_path_decode_many(const char *path, const char *path_template, ...); - -/* Tracking peers */ - -int sd_bus_track_new(sd_bus *bus, sd_bus_track **track, sd_bus_track_handler_t handler, void *userdata); -sd_bus_track* sd_bus_track_ref(sd_bus_track *track); -sd_bus_track* sd_bus_track_unref(sd_bus_track *track); - -sd_bus* sd_bus_track_get_bus(sd_bus_track *track); -void *sd_bus_track_get_userdata(sd_bus_track *track); -void *sd_bus_track_set_userdata(sd_bus_track *track, void *userdata); - -int sd_bus_track_add_sender(sd_bus_track *track, sd_bus_message *m); -int sd_bus_track_remove_sender(sd_bus_track *track, sd_bus_message *m); -int sd_bus_track_add_name(sd_bus_track *track, const char *name); -int sd_bus_track_remove_name(sd_bus_track *track, const char *name); - -unsigned sd_bus_track_count(sd_bus_track *track); -const char* sd_bus_track_contains(sd_bus_track *track, const char *names); -const char* sd_bus_track_first(sd_bus_track *track); -const char* sd_bus_track_next(sd_bus_track *track); - -/* Define helpers so that __attribute__((cleanup(sd_bus_unrefp))) and similar may be used. */ -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus, sd_bus_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus, sd_bus_flush_close_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_slot, sd_bus_slot_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_message, sd_bus_message_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_creds, sd_bus_creds_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_bus_track, sd_bus_track_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-daemon.h b/src/systemd/sd-daemon.h deleted file mode 100644 index e6787b0a64..0000000000 --- a/src/systemd/sd-daemon.h +++ /dev/null @@ -1,289 +0,0 @@ -#ifndef foosddaemonhfoo -#define foosddaemonhfoo - -/*** - 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 . -***/ - -#include -#include - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -/* - The following functionality is provided: - - - Support for logging with log levels on stderr - - File descriptor passing for socket-based activation - - Daemon startup and status notification - - Detection of systemd boots - - See sd-daemon(3) for more information. -*/ - -/* - Log levels for usage on stderr: - - fprintf(stderr, SD_NOTICE "Hello World!\n"); - - This is similar to printk() usage in the kernel. -*/ -#define SD_EMERG "<0>" /* system is unusable */ -#define SD_ALERT "<1>" /* action must be taken immediately */ -#define SD_CRIT "<2>" /* critical conditions */ -#define SD_ERR "<3>" /* error conditions */ -#define SD_WARNING "<4>" /* warning conditions */ -#define SD_NOTICE "<5>" /* normal but significant condition */ -#define SD_INFO "<6>" /* informational */ -#define SD_DEBUG "<7>" /* debug-level messages */ - -/* The first passed file descriptor is fd 3 */ -#define SD_LISTEN_FDS_START 3 - -/* - Returns how many file descriptors have been passed, or a negative - errno code on failure. Optionally, removes the $LISTEN_FDS and - $LISTEN_PID file descriptors from the environment (recommended, but - problematic in threaded environments). If r is the return value of - this function you'll find the file descriptors passed as fds - SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative - errno style error code on failure. This function call ensures that - the FD_CLOEXEC flag is set for the passed file descriptors, to make - sure they are not passed on to child processes. If FD_CLOEXEC shall - not be set, the caller needs to unset it after this call for all file - descriptors that are used. - - See sd_listen_fds(3) for more information. -*/ -int sd_listen_fds(int unset_environment); - -int sd_listen_fds_with_names(int unset_environment, char ***names); - -/* - Helper call for identifying a passed file descriptor. Returns 1 if - the file descriptor is a FIFO in the file system stored under the - specified path, 0 otherwise. If path is NULL a path name check will - not be done and the call only verifies if the file descriptor - refers to a FIFO. Returns a negative errno style error code on - failure. - - See sd_is_fifo(3) for more information. -*/ -int sd_is_fifo(int fd, const char *path); - -/* - Helper call for identifying a passed file descriptor. Returns 1 if - the file descriptor is a special character device on the file - system stored under the specified path, 0 otherwise. - If path is NULL a path name check will not be done and the call - only verifies if the file descriptor refers to a special character. - Returns a negative errno style error code on failure. - - See sd_is_special(3) for more information. -*/ -int sd_is_special(int fd, const char *path); - -/* - Helper call for identifying a passed file descriptor. Returns 1 if - the file descriptor is a socket of the specified family (AF_INET, - ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If - family is 0 a socket family check will not be done. If type is 0 a - socket type check will not be done and the call only verifies if - the file descriptor refers to a socket. If listening is > 0 it is - verified that the socket is in listening mode. (i.e. listen() has - been called) If listening is == 0 it is verified that the socket is - not in listening mode. If listening is < 0 no listening mode check - is done. Returns a negative errno style error code on failure. - - See sd_is_socket(3) for more information. -*/ -int sd_is_socket(int fd, int family, int type, int listening); - -/* - Helper call for identifying a passed file descriptor. Returns 1 if - the file descriptor is an Internet socket, of the specified family - (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM, - SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version - check is not done. If type is 0 a socket type check will not be - done. If port is 0 a socket port check will not be done. The - listening flag is used the same way as in sd_is_socket(). Returns a - negative errno style error code on failure. - - See sd_is_socket_inet(3) for more information. -*/ -int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port); - -/* - Helper call for identifying a passed file descriptor. Returns 1 if - the file descriptor is an AF_UNIX socket of the specified type - (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0 - a socket type check will not be done. If path is NULL a socket path - check will not be done. For normal AF_UNIX sockets set length to - 0. For abstract namespace sockets set length to the length of the - socket name (including the initial 0 byte), and pass the full - socket path in path (including the initial 0 byte). The listening - flag is used the same way as in sd_is_socket(). Returns a negative - errno style error code on failure. - - See sd_is_socket_unix(3) for more information. -*/ -int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length); - -/* - Helper call for identifying a passed file descriptor. Returns 1 if - the file descriptor is a POSIX Message Queue of the specified name, - 0 otherwise. If path is NULL a message queue name check is not - done. Returns a negative errno style error code on failure. - - See sd_is_mq(3) for more information. -*/ -int sd_is_mq(int fd, const char *path); - -/* - Informs systemd about changed daemon state. This takes a number of - newline separated environment-style variable assignments in a - string. The following variables are known: - - READY=1 Tells systemd that daemon startup is finished (only - relevant for services of Type=notify). The passed - argument is a boolean "1" or "0". Since there is - little value in signaling non-readiness the only - value daemons should send is "READY=1". - - STATUS=... Passes a single-line status string back to systemd - that describes the daemon state. This is free-form - and can be used for various purposes: general state - feedback, fsck-like programs could pass completion - percentages and failing programs could pass a human - readable error message. Example: "STATUS=Completed - 66% of file system check..." - - ERRNO=... If a daemon fails, the errno-style error code, - formatted as string. Example: "ERRNO=2" for ENOENT. - - BUSERROR=... If a daemon fails, the D-Bus error-style error - code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut" - - MAINPID=... The main pid of a daemon, in case systemd did not - fork off the process itself. Example: "MAINPID=4711" - - WATCHDOG=1 Tells systemd to update the watchdog timestamp. - Services using this feature should do this in - regular intervals. A watchdog framework can use the - timestamps to detect failed services. Also see - sd_watchdog_enabled() below. - - FDSTORE=1 Store the file descriptors passed along with the - message in the per-service file descriptor store, - and pass them to the main process again on next - invocation. This variable is only supported with - sd_pid_notify_with_fds(). - - Daemons can choose to send additional variables. However, it is - recommended to prefix variable names not listed above with X_. - - Returns a negative errno-style error code on failure. Returns > 0 - if systemd could be notified, 0 if it couldn't possibly because - systemd is not running. - - Example: When a daemon finished starting up, it could issue this - call to notify systemd about it: - - sd_notify(0, "READY=1"); - - See sd_notifyf() for more complete examples. - - See sd_notify(3) for more information. -*/ -int sd_notify(int unset_environment, const char *state); - -/* - Similar to sd_notify() but takes a format string. - - Example 1: A daemon could send the following after initialization: - - sd_notifyf(0, "READY=1\n" - "STATUS=Processing requests...\n" - "MAINPID=%lu", - (unsigned long) getpid()); - - Example 2: A daemon could send the following shortly before - exiting, on failure: - - sd_notifyf(0, "STATUS=Failed to start up: %s\n" - "ERRNO=%i", - strerror(errno), - errno); - - See sd_notifyf(3) for more information. -*/ -int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_(2,3); - -/* - Similar to sd_notify(), but send the message on behalf of another - process, if the appropriate permissions are available. -*/ -int sd_pid_notify(pid_t pid, int unset_environment, const char *state); - -/* - Similar to sd_notifyf(), but send the message on behalf of another - process, if the appropriate permissions are available. -*/ -int sd_pid_notifyf(pid_t pid, int unset_environment, const char *format, ...) _sd_printf_(3,4); - -/* - Similar to sd_pid_notify(), but also passes the specified fd array - to the service manager for storage. This is particularly useful for - FDSTORE=1 messages. -*/ -int sd_pid_notify_with_fds(pid_t pid, int unset_environment, const char *state, const int *fds, unsigned n_fds); - -/* - Returns > 0 if the system was booted with systemd. Returns < 0 on - error. Returns 0 if the system was not booted with systemd. Note - that all of the functions above handle non-systemd boots just - fine. You should NOT protect them with a call to this function. Also - note that this function checks whether the system, not the user - session is controlled by systemd. However the functions above work - for both user and system services. - - See sd_booted(3) for more information. -*/ -int sd_booted(void); - -/* - Returns > 0 if the service manager expects watchdog keep-alive - events to be sent regularly via sd_notify(0, "WATCHDOG=1"). Returns - 0 if it does not expect this. If the usec argument is non-NULL - returns the watchdog timeout in µs after which the service manager - will act on a process that has not sent a watchdog keep alive - message. This function is useful to implement services that - recognize automatically if they are being run under supervision of - systemd with WatchdogSec= set. It is recommended for clients to - generate keep-alive pings via sd_notify(0, "WATCHDOG=1") every half - of the returned time. - - See sd_watchdog_enabled(3) for more information. -*/ -int sd_watchdog_enabled(int unset_environment, uint64_t *usec); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-device.h b/src/systemd/sd-device.h deleted file mode 100644 index 5bfca6ecec..0000000000 --- a/src/systemd/sd-device.h +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef foosddevicehfoo -#define foosddevicehfoo - -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2014-2015 Tom Gundersen - - 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 . -***/ - -#include -#include - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_device sd_device; -typedef struct sd_device_enumerator sd_device_enumerator; - -/* device */ - -sd_device *sd_device_ref(sd_device *device); -sd_device *sd_device_unref(sd_device *device); - -int sd_device_new_from_syspath(sd_device **ret, const char *syspath); -int sd_device_new_from_devnum(sd_device **ret, char type, dev_t devnum); -int sd_device_new_from_subsystem_sysname(sd_device **ret, const char *subsystem, const char *sysname); -int sd_device_new_from_device_id(sd_device **ret, const char *id); - -int sd_device_get_parent(sd_device *child, sd_device **ret); -int sd_device_get_parent_with_subsystem_devtype(sd_device *child, const char *subsystem, const char *devtype, sd_device **ret); - -int sd_device_get_syspath(sd_device *device, const char **ret); -int sd_device_get_subsystem(sd_device *device, const char **ret); -int sd_device_get_devtype(sd_device *device, const char **ret); -int sd_device_get_devnum(sd_device *device, dev_t *devnum); -int sd_device_get_ifindex(sd_device *device, int *ifindex); -int sd_device_get_driver(sd_device *device, const char **ret); -int sd_device_get_devpath(sd_device *device, const char **ret); -int sd_device_get_devname(sd_device *device, const char **ret); -int sd_device_get_sysname(sd_device *device, const char **ret); -int sd_device_get_sysnum(sd_device *device, const char **ret); - -int sd_device_get_is_initialized(sd_device *device, int *initialized); -int sd_device_get_usec_since_initialized(sd_device *device, uint64_t *usec); - -const char *sd_device_get_tag_first(sd_device *device); -const char *sd_device_get_tag_next(sd_device *device); -const char *sd_device_get_devlink_first(sd_device *device); -const char *sd_device_get_devlink_next(sd_device *device); -const char *sd_device_get_property_first(sd_device *device, const char **value); -const char *sd_device_get_property_next(sd_device *device, const char **value); -const char *sd_device_get_sysattr_first(sd_device *device); -const char *sd_device_get_sysattr_next(sd_device *device); - -int sd_device_has_tag(sd_device *device, const char *tag); -int sd_device_get_property_value(sd_device *device, const char *key, const char **value); -int sd_device_get_sysattr_value(sd_device *device, const char *sysattr, const char **_value); - -int sd_device_set_sysattr_value(sd_device *device, const char *sysattr, char *value); - -/* device enumerator */ - -int sd_device_enumerator_new(sd_device_enumerator **ret); -sd_device_enumerator *sd_device_enumerator_ref(sd_device_enumerator *enumerator); -sd_device_enumerator *sd_device_enumerator_unref(sd_device_enumerator *enumerator); - -sd_device *sd_device_enumerator_get_device_first(sd_device_enumerator *enumerator); -sd_device *sd_device_enumerator_get_device_next(sd_device_enumerator *enumerator); -sd_device *sd_device_enumerator_get_subsystem_first(sd_device_enumerator *enumerator); -sd_device *sd_device_enumerator_get_subsystem_next(sd_device_enumerator *enumerator); - -int sd_device_enumerator_add_match_subsystem(sd_device_enumerator *enumerator, const char *subsystem, int match); -int sd_device_enumerator_add_match_sysattr(sd_device_enumerator *enumerator, const char *sysattr, const char *value, int match); -int sd_device_enumerator_add_match_property(sd_device_enumerator *enumerator, const char *property, const char *value); -int sd_device_enumerator_add_match_sysname(sd_device_enumerator *enumerator, const char *sysname); -int sd_device_enumerator_add_match_tag(sd_device_enumerator *enumerator, const char *tag); -int sd_device_enumerator_add_match_parent(sd_device_enumerator *enumerator, sd_device *parent); -int sd_device_enumerator_allow_uninitialized(sd_device_enumerator *enumerator); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device, sd_device_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_device_enumerator, sd_device_enumerator_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h deleted file mode 100644 index 2b865a80e1..0000000000 --- a/src/systemd/sd-dhcp-client.h +++ /dev/null @@ -1,124 +0,0 @@ -#ifndef foosddhcpclienthfoo -#define foosddhcpclienthfoo - -/*** - This file is part of systemd. - - Copyright (C) 2013 Intel Corporation. All rights reserved. - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (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 . -***/ - -#include -#include -#include -#include - -#include "sd-dhcp-lease.h" -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -enum { - SD_DHCP_CLIENT_EVENT_STOP = 0, - SD_DHCP_CLIENT_EVENT_IP_ACQUIRE = 1, - SD_DHCP_CLIENT_EVENT_IP_CHANGE = 2, - SD_DHCP_CLIENT_EVENT_EXPIRED = 3, - SD_DHCP_CLIENT_EVENT_RENEW = 4, -}; - -enum { - SD_DHCP_OPTION_PAD = 0, - SD_DHCP_OPTION_SUBNET_MASK = 1, - SD_DHCP_OPTION_TIME_OFFSET = 2, - SD_DHCP_OPTION_ROUTER = 3, - SD_DHCP_OPTION_DOMAIN_NAME_SERVER = 6, - SD_DHCP_OPTION_HOST_NAME = 12, - SD_DHCP_OPTION_BOOT_FILE_SIZE = 13, - SD_DHCP_OPTION_DOMAIN_NAME = 15, - SD_DHCP_OPTION_ROOT_PATH = 17, - SD_DHCP_OPTION_ENABLE_IP_FORWARDING = 19, - SD_DHCP_OPTION_ENABLE_IP_FORWARDING_NL = 20, - SD_DHCP_OPTION_POLICY_FILTER = 21, - SD_DHCP_OPTION_INTERFACE_MDR = 22, - SD_DHCP_OPTION_INTERFACE_TTL = 23, - SD_DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24, - SD_DHCP_OPTION_INTERFACE_MTU = 26, - SD_DHCP_OPTION_BROADCAST = 28, - SD_DHCP_OPTION_STATIC_ROUTE = 33, - SD_DHCP_OPTION_NTP_SERVER = 42, - SD_DHCP_OPTION_VENDOR_SPECIFIC = 43, - SD_DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, - SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, - SD_DHCP_OPTION_OVERLOAD = 52, - SD_DHCP_OPTION_MESSAGE_TYPE = 53, - SD_DHCP_OPTION_SERVER_IDENTIFIER = 54, - SD_DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, - SD_DHCP_OPTION_ERROR_MESSAGE = 56, - SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, - SD_DHCP_OPTION_RENEWAL_T1_TIME = 58, - SD_DHCP_OPTION_REBINDING_T2_TIME = 59, - SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, - SD_DHCP_OPTION_CLIENT_IDENTIFIER = 61, - SD_DHCP_OPTION_FQDN = 81, - SD_DHCP_OPTION_NEW_POSIX_TIMEZONE = 100, - SD_DHCP_OPTION_NEW_TZDB_TIMEZONE = 101, - SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, - SD_DHCP_OPTION_PRIVATE_BASE = 224, - SD_DHCP_OPTION_PRIVATE_LAST = 254, - SD_DHCP_OPTION_END = 255, -}; - -typedef struct sd_dhcp_client sd_dhcp_client; - -typedef void (*sd_dhcp_client_cb_t)(sd_dhcp_client *client, int event, - void *userdata); -int sd_dhcp_client_set_callback(sd_dhcp_client *client, sd_dhcp_client_cb_t cb, - void *userdata); - -int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option); -int sd_dhcp_client_set_request_address(sd_dhcp_client *client, - const struct in_addr *last_address); -int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast); -int sd_dhcp_client_set_index(sd_dhcp_client *client, int interface_index); -int sd_dhcp_client_set_mac(sd_dhcp_client *client, const uint8_t *addr, - size_t addr_len, uint16_t arp_type); -int sd_dhcp_client_set_client_id(sd_dhcp_client *client, uint8_t type, - const uint8_t *data, size_t data_len); -int sd_dhcp_client_get_client_id(sd_dhcp_client *client, uint8_t *type, - const uint8_t **data, size_t *data_len); -int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu); -int sd_dhcp_client_set_hostname(sd_dhcp_client *client, const char *hostname); -int sd_dhcp_client_set_vendor_class_identifier(sd_dhcp_client *client, const char *vci); -int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret); - -int sd_dhcp_client_stop(sd_dhcp_client *client); -int sd_dhcp_client_start(sd_dhcp_client *client); - -sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client); -sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client); - -int sd_dhcp_client_new(sd_dhcp_client **ret); - -int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int priority); -int sd_dhcp_client_detach_event(sd_dhcp_client *client); -sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_client, sd_dhcp_client_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-dhcp-lease.h b/src/systemd/sd-dhcp-lease.h deleted file mode 100644 index 2f565ca825..0000000000 --- a/src/systemd/sd-dhcp-lease.h +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef foosddhcpleasehfoo -#define foosddhcpleasehfoo - -/*** - This file is part of systemd. - - Copyright (C) 2013 Intel Corporation. All rights reserved. - Copyright (C) 2014 Tom Gundersen - - 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 . -***/ - -#include -#include -#include -#include - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_dhcp_lease sd_dhcp_lease; -typedef struct sd_dhcp_route sd_dhcp_route; - -sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease); -sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease); - -int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime); -int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1); -int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2); -int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr); -int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr); -int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu); -int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname); -int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname); -int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path); -int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes); -int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len); -int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len); -int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **timezone); - -int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination); -int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length); -int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h deleted file mode 100644 index 8658197e80..0000000000 --- a/src/systemd/sd-dhcp-server.h +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef foosddhcpserverhfoo -#define foosddhcpserverhfoo - -/*** - This file is part of systemd. - - Copyright (C) 2013 Intel Corporation. All rights reserved. - Copyright (C) 2014 Tom Gundersen - - 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 . -***/ - -#include -#include - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_dhcp_server sd_dhcp_server; - -int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex); - -sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server); -sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server); - -int sd_dhcp_server_attach_event(sd_dhcp_server *client, sd_event *event, int priority); -int sd_dhcp_server_detach_event(sd_dhcp_server *client); -sd_event *sd_dhcp_server_get_event(sd_dhcp_server *client); - -int sd_dhcp_server_is_running(sd_dhcp_server *server); - -int sd_dhcp_server_start(sd_dhcp_server *server); -int sd_dhcp_server_stop(sd_dhcp_server *server); - -int sd_dhcp_server_configure_pool(sd_dhcp_server *server, struct in_addr *address, unsigned char prefixlen, uint32_t offset, uint32_t size); - -int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *timezone); -int sd_dhcp_server_set_dns(sd_dhcp_server *server, const struct in_addr ntp[], unsigned n); -int sd_dhcp_server_set_ntp(sd_dhcp_server *server, const struct in_addr dns[], unsigned n); - -int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint32_t t); -int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint32_t t); - -int sd_dhcp_server_forcerenew(sd_dhcp_server *server); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_server, sd_dhcp_server_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-dhcp6-client.h b/src/systemd/sd-dhcp6-client.h deleted file mode 100644 index 9608060830..0000000000 --- a/src/systemd/sd-dhcp6-client.h +++ /dev/null @@ -1,112 +0,0 @@ -#ifndef foosddhcp6clienthfoo -#define foosddhcp6clienthfoo - -/*** - 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 . -***/ - -#include -#include -#include - -#include "sd-dhcp6-lease.h" -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -enum { - SD_DHCP6_CLIENT_EVENT_STOP = 0, - SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE = 10, - SD_DHCP6_CLIENT_EVENT_RETRANS_MAX = 11, - SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE = 12, - SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST = 13, -}; - -enum { - SD_DHCP6_OPTION_CLIENTID = 1, - SD_DHCP6_OPTION_SERVERID = 2, - SD_DHCP6_OPTION_IA_NA = 3, - SD_DHCP6_OPTION_IA_TA = 4, - SD_DHCP6_OPTION_IAADDR = 5, - SD_DHCP6_OPTION_ORO = 6, - SD_DHCP6_OPTION_PREFERENCE = 7, - SD_DHCP6_OPTION_ELAPSED_TIME = 8, - SD_DHCP6_OPTION_RELAY_MSG = 9, - /* option code 10 is unassigned */ - SD_DHCP6_OPTION_AUTH = 11, - SD_DHCP6_OPTION_UNICAST = 12, - SD_DHCP6_OPTION_STATUS_CODE = 13, - SD_DHCP6_OPTION_RAPID_COMMIT = 14, - SD_DHCP6_OPTION_USER_CLASS = 15, - SD_DHCP6_OPTION_VENDOR_CLASS = 16, - SD_DHCP6_OPTION_VENDOR_OPTS = 17, - SD_DHCP6_OPTION_INTERFACE_ID = 18, - SD_DHCP6_OPTION_RECONF_MSG = 19, - SD_DHCP6_OPTION_RECONF_ACCEPT = 20, - - SD_DHCP6_OPTION_DNS_SERVERS = 23, /* RFC 3646 */ - SD_DHCP6_OPTION_DOMAIN_LIST = 24, /* RFC 3646 */ - - SD_DHCP6_OPTION_SNTP_SERVERS = 31, /* RFC 4075, deprecated */ - - /* option code 35 is unassigned */ - - SD_DHCP6_OPTION_NTP_SERVER = 56, /* RFC 5908 */ - - /* option codes 89-142 are unassigned */ - /* option codes 144-65535 are unassigned */ -}; - -typedef struct sd_dhcp6_client sd_dhcp6_client; - -typedef void (*sd_dhcp6_client_cb_t)(sd_dhcp6_client *client, int event, - void *userdata); -int sd_dhcp6_client_set_callback(sd_dhcp6_client *client, - sd_dhcp6_client_cb_t cb, void *userdata); - -int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index); -int sd_dhcp6_client_set_local_address(sd_dhcp6_client *client, const struct in6_addr *local_address); -int sd_dhcp6_client_set_mac(sd_dhcp6_client *client, const uint8_t *addr, - size_t addr_len, uint16_t arp_type); -int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, uint16_t type, uint8_t *duid, - size_t duid_len); -int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled); -int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enabled); -int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, - uint16_t option); - -int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret); - -int sd_dhcp6_client_stop(sd_dhcp6_client *client); -int sd_dhcp6_client_start(sd_dhcp6_client *client); -int sd_dhcp6_client_is_running(sd_dhcp6_client *client); -int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, - int priority); -int sd_dhcp6_client_detach_event(sd_dhcp6_client *client); -sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client); -sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client); -sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client); -int sd_dhcp6_client_new(sd_dhcp6_client **ret); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_client, sd_dhcp6_client_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-dhcp6-lease.h b/src/systemd/sd-dhcp6-lease.h deleted file mode 100644 index 184fbb8e0d..0000000000 --- a/src/systemd/sd-dhcp6-lease.h +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef foosddhcp6leasehfoo -#define foosddhcp6leasehfoo - -/*** - This file is part of systemd. - - Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014-2015 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 . -***/ - -#include -#include - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_dhcp6_lease sd_dhcp6_lease; - -void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease); -int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, - struct in6_addr *addr, - uint32_t *lifetime_preferred, - uint32_t *lifetime_valid); - -int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, struct in6_addr **addrs); -int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains); -int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, - struct in6_addr **addrs); -int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn); - -sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease); -sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_lease, sd_dhcp6_lease_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-event.h b/src/systemd/sd-event.h deleted file mode 100644 index 1ea97e47f8..0000000000 --- a/src/systemd/sd-event.h +++ /dev/null @@ -1,138 +0,0 @@ -#ifndef foosdeventhfoo -#define foosdeventhfoo - -/*** - 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 . -***/ - -#include -#include -#include -#include -#include - -#include "_sd-common.h" - -/* - Why is this better than pure epoll? - - - Supports event source prioritization - - Scales better with a large number of time events because it does not require one timerfd each - - Automatically tries to coalesce timer events system-wide - - Handles signals and child PIDs -*/ - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_event sd_event; -typedef struct sd_event_source sd_event_source; - -enum { - SD_EVENT_OFF = 0, - SD_EVENT_ON = 1, - SD_EVENT_ONESHOT = -1 -}; - -enum { - SD_EVENT_INITIAL, - SD_EVENT_ARMED, - SD_EVENT_PENDING, - SD_EVENT_RUNNING, - SD_EVENT_EXITING, - SD_EVENT_FINISHED, - SD_EVENT_PREPARING, -}; - -enum { - /* And everything in-between and outside is good too */ - SD_EVENT_PRIORITY_IMPORTANT = -100, - SD_EVENT_PRIORITY_NORMAL = 0, - SD_EVENT_PRIORITY_IDLE = 100 -}; - -typedef int (*sd_event_handler_t)(sd_event_source *s, void *userdata); -typedef int (*sd_event_io_handler_t)(sd_event_source *s, int fd, uint32_t revents, void *userdata); -typedef int (*sd_event_time_handler_t)(sd_event_source *s, uint64_t usec, void *userdata); -typedef int (*sd_event_signal_handler_t)(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata); -typedef int (*sd_event_child_handler_t)(sd_event_source *s, const siginfo_t *si, void *userdata); - -int sd_event_default(sd_event **e); - -int sd_event_new(sd_event **e); -sd_event* sd_event_ref(sd_event *e); -sd_event* sd_event_unref(sd_event *e); - -int sd_event_add_io(sd_event *e, sd_event_source **s, int fd, uint32_t events, sd_event_io_handler_t callback, void *userdata); -int sd_event_add_time(sd_event *e, sd_event_source **s, clockid_t clock, uint64_t usec, uint64_t accuracy, sd_event_time_handler_t callback, void *userdata); -int sd_event_add_signal(sd_event *e, sd_event_source **s, int sig, sd_event_signal_handler_t callback, void *userdata); -int sd_event_add_child(sd_event *e, sd_event_source **s, pid_t pid, int options, sd_event_child_handler_t callback, void *userdata); -int sd_event_add_defer(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); -int sd_event_add_post(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); -int sd_event_add_exit(sd_event *e, sd_event_source **s, sd_event_handler_t callback, void *userdata); - -int sd_event_prepare(sd_event *e); -int sd_event_wait(sd_event *e, uint64_t usec); -int sd_event_dispatch(sd_event *e); -int sd_event_run(sd_event *e, uint64_t usec); -int sd_event_loop(sd_event *e); -int sd_event_exit(sd_event *e, int code); - -int sd_event_now(sd_event *e, clockid_t clock, uint64_t *usec); - -int sd_event_get_fd(sd_event *e); -int sd_event_get_state(sd_event *e); -int sd_event_get_tid(sd_event *e, pid_t *tid); -int sd_event_get_exit_code(sd_event *e, int *code); -int sd_event_set_watchdog(sd_event *e, int b); -int sd_event_get_watchdog(sd_event *e); - -sd_event_source* sd_event_source_ref(sd_event_source *s); -sd_event_source* sd_event_source_unref(sd_event_source *s); - -sd_event *sd_event_source_get_event(sd_event_source *s); -void* sd_event_source_get_userdata(sd_event_source *s); -void* sd_event_source_set_userdata(sd_event_source *s, void *userdata); - -int sd_event_source_set_description(sd_event_source *s, const char *description); -int sd_event_source_get_description(sd_event_source *s, const char **description); -int sd_event_source_set_prepare(sd_event_source *s, sd_event_handler_t callback); -int sd_event_source_get_pending(sd_event_source *s); -int sd_event_source_get_priority(sd_event_source *s, int64_t *priority); -int sd_event_source_set_priority(sd_event_source *s, int64_t priority); -int sd_event_source_get_enabled(sd_event_source *s, int *enabled); -int sd_event_source_set_enabled(sd_event_source *s, int enabled); -int sd_event_source_get_io_fd(sd_event_source *s); -int sd_event_source_set_io_fd(sd_event_source *s, int fd); -int sd_event_source_get_io_events(sd_event_source *s, uint32_t* events); -int sd_event_source_set_io_events(sd_event_source *s, uint32_t events); -int sd_event_source_get_io_revents(sd_event_source *s, uint32_t* revents); -int sd_event_source_get_time(sd_event_source *s, uint64_t *usec); -int sd_event_source_set_time(sd_event_source *s, uint64_t usec); -int sd_event_source_get_time_accuracy(sd_event_source *s, uint64_t *usec); -int sd_event_source_set_time_accuracy(sd_event_source *s, uint64_t usec); -int sd_event_source_get_time_clock(sd_event_source *s, clockid_t *clock); -int sd_event_source_get_signal(sd_event_source *s); -int sd_event_source_get_child_pid(sd_event_source *s, pid_t *pid); - -/* Define helpers so that __attribute__((cleanup(sd_event_unrefp))) and similar may be used. */ -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event, sd_event_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_event_source, sd_event_source_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-hwdb.h b/src/systemd/sd-hwdb.h deleted file mode 100644 index 7105920492..0000000000 --- a/src/systemd/sd-hwdb.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef foosdhwdbhfoo -#define foosdhwdbhfoo - -/*** - This file is part of systemd. - - Copyright 2008-2012 Kay Sievers - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_hwdb sd_hwdb; - -sd_hwdb *sd_hwdb_ref(sd_hwdb *hwdb); -sd_hwdb *sd_hwdb_unref(sd_hwdb *hwdb); - -int sd_hwdb_new(sd_hwdb **ret); - -int sd_hwdb_get(sd_hwdb *hwdb, const char *modalias, const char *key, const char **value); - -int sd_hwdb_seek(sd_hwdb *hwdb, const char *modalias); -int sd_hwdb_enumerate(sd_hwdb *hwdb, const char **key, const char **value); - -/* the inverse condition avoids ambiguity of dangling 'else' after the macro */ -#define SD_HWDB_FOREACH_PROPERTY(hwdb, modalias, key, value) \ - if (sd_hwdb_seek(hwdb, modalias) < 0) { } \ - else while (sd_hwdb_enumerate(hwdb, &(key), &(value)) > 0) - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_hwdb, sd_hwdb_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-id128.h b/src/systemd/sd-id128.h deleted file mode 100644 index a3bf5897b8..0000000000 --- a/src/systemd/sd-id128.h +++ /dev/null @@ -1,115 +0,0 @@ -#ifndef foosdid128hfoo -#define foosdid128hfoo - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -/* 128-bit ID APIs. See sd-id128(3) for more information. */ - -typedef union sd_id128 sd_id128_t; - -union sd_id128 { - uint8_t bytes[16]; - uint64_t qwords[2]; -}; - -#define SD_ID128_STRING_MAX 33 - -char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]); - -int sd_id128_from_string(const char *s, sd_id128_t *ret); - -int sd_id128_randomize(sd_id128_t *ret); - -int sd_id128_get_machine(sd_id128_t *ret); - -int sd_id128_get_boot(sd_id128_t *ret); - -#define SD_ID128_MAKE(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ - ((const sd_id128_t) { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ - 0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }}) - -#define SD_ID128_ARRAY(v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) \ - { .bytes = { 0x##v0, 0x##v1, 0x##v2, 0x##v3, 0x##v4, 0x##v5, 0x##v6, 0x##v7, \ - 0x##v8, 0x##v9, 0x##v10, 0x##v11, 0x##v12, 0x##v13, 0x##v14, 0x##v15 }} - -/* Note that SD_ID128_FORMAT_VAL will evaluate the passed argument 16 - * times. It is hence not a good idea to call this macro with an - * expensive function as parameter or an expression with side - * effects */ - -#define SD_ID128_FORMAT_STR "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" -#define SD_ID128_FORMAT_VAL(x) (x).bytes[0], (x).bytes[1], (x).bytes[2], (x).bytes[3], (x).bytes[4], (x).bytes[5], (x).bytes[6], (x).bytes[7], (x).bytes[8], (x).bytes[9], (x).bytes[10], (x).bytes[11], (x).bytes[12], (x).bytes[13], (x).bytes[14], (x).bytes[15] - -#define SD_ID128_CONST_STR(x) \ - ((const char[SD_ID128_STRING_MAX]) { \ - ((x).bytes[0] >> 4) >= 10 ? 'a' + ((x).bytes[0] >> 4) - 10 : '0' + ((x).bytes[0] >> 4), \ - ((x).bytes[0] & 15) >= 10 ? 'a' + ((x).bytes[0] & 15) - 10 : '0' + ((x).bytes[0] & 15), \ - ((x).bytes[1] >> 4) >= 10 ? 'a' + ((x).bytes[1] >> 4) - 10 : '0' + ((x).bytes[1] >> 4), \ - ((x).bytes[1] & 15) >= 10 ? 'a' + ((x).bytes[1] & 15) - 10 : '0' + ((x).bytes[1] & 15), \ - ((x).bytes[2] >> 4) >= 10 ? 'a' + ((x).bytes[2] >> 4) - 10 : '0' + ((x).bytes[2] >> 4), \ - ((x).bytes[2] & 15) >= 10 ? 'a' + ((x).bytes[2] & 15) - 10 : '0' + ((x).bytes[2] & 15), \ - ((x).bytes[3] >> 4) >= 10 ? 'a' + ((x).bytes[3] >> 4) - 10 : '0' + ((x).bytes[3] >> 4), \ - ((x).bytes[3] & 15) >= 10 ? 'a' + ((x).bytes[3] & 15) - 10 : '0' + ((x).bytes[3] & 15), \ - ((x).bytes[4] >> 4) >= 10 ? 'a' + ((x).bytes[4] >> 4) - 10 : '0' + ((x).bytes[4] >> 4), \ - ((x).bytes[4] & 15) >= 10 ? 'a' + ((x).bytes[4] & 15) - 10 : '0' + ((x).bytes[4] & 15), \ - ((x).bytes[5] >> 4) >= 10 ? 'a' + ((x).bytes[5] >> 4) - 10 : '0' + ((x).bytes[5] >> 4), \ - ((x).bytes[5] & 15) >= 10 ? 'a' + ((x).bytes[5] & 15) - 10 : '0' + ((x).bytes[5] & 15), \ - ((x).bytes[6] >> 4) >= 10 ? 'a' + ((x).bytes[6] >> 4) - 10 : '0' + ((x).bytes[6] >> 4), \ - ((x).bytes[6] & 15) >= 10 ? 'a' + ((x).bytes[6] & 15) - 10 : '0' + ((x).bytes[6] & 15), \ - ((x).bytes[7] >> 4) >= 10 ? 'a' + ((x).bytes[7] >> 4) - 10 : '0' + ((x).bytes[7] >> 4), \ - ((x).bytes[7] & 15) >= 10 ? 'a' + ((x).bytes[7] & 15) - 10 : '0' + ((x).bytes[7] & 15), \ - ((x).bytes[8] >> 4) >= 10 ? 'a' + ((x).bytes[8] >> 4) - 10 : '0' + ((x).bytes[8] >> 4), \ - ((x).bytes[8] & 15) >= 10 ? 'a' + ((x).bytes[8] & 15) - 10 : '0' + ((x).bytes[8] & 15), \ - ((x).bytes[9] >> 4) >= 10 ? 'a' + ((x).bytes[9] >> 4) - 10 : '0' + ((x).bytes[9] >> 4), \ - ((x).bytes[9] & 15) >= 10 ? 'a' + ((x).bytes[9] & 15) - 10 : '0' + ((x).bytes[9] & 15), \ - ((x).bytes[10] >> 4) >= 10 ? 'a' + ((x).bytes[10] >> 4) - 10 : '0' + ((x).bytes[10] >> 4), \ - ((x).bytes[10] & 15) >= 10 ? 'a' + ((x).bytes[10] & 15) - 10 : '0' + ((x).bytes[10] & 15), \ - ((x).bytes[11] >> 4) >= 10 ? 'a' + ((x).bytes[11] >> 4) - 10 : '0' + ((x).bytes[11] >> 4), \ - ((x).bytes[11] & 15) >= 10 ? 'a' + ((x).bytes[11] & 15) - 10 : '0' + ((x).bytes[11] & 15), \ - ((x).bytes[12] >> 4) >= 10 ? 'a' + ((x).bytes[12] >> 4) - 10 : '0' + ((x).bytes[12] >> 4), \ - ((x).bytes[12] & 15) >= 10 ? 'a' + ((x).bytes[12] & 15) - 10 : '0' + ((x).bytes[12] & 15), \ - ((x).bytes[13] >> 4) >= 10 ? 'a' + ((x).bytes[13] >> 4) - 10 : '0' + ((x).bytes[13] >> 4), \ - ((x).bytes[13] & 15) >= 10 ? 'a' + ((x).bytes[13] & 15) - 10 : '0' + ((x).bytes[13] & 15), \ - ((x).bytes[14] >> 4) >= 10 ? 'a' + ((x).bytes[14] >> 4) - 10 : '0' + ((x).bytes[14] >> 4), \ - ((x).bytes[14] & 15) >= 10 ? 'a' + ((x).bytes[14] & 15) - 10 : '0' + ((x).bytes[14] & 15), \ - ((x).bytes[15] >> 4) >= 10 ? 'a' + ((x).bytes[15] >> 4) - 10 : '0' + ((x).bytes[15] >> 4), \ - ((x).bytes[15] & 15) >= 10 ? 'a' + ((x).bytes[15] & 15) - 10 : '0' + ((x).bytes[15] & 15), \ - 0 }) - -_sd_pure_ static inline int sd_id128_equal(sd_id128_t a, sd_id128_t b) { - return memcmp(&a, &b, 16) == 0; -} - -_sd_pure_ static inline int sd_id128_is_null(sd_id128_t a) { - return a.qwords[0] == 0 && a.qwords[1] == 0; -} - -#define SD_ID128_NULL ((const sd_id128_t) { .qwords = { 0, 0 }}) - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-ipv4acd.h b/src/systemd/sd-ipv4acd.h deleted file mode 100644 index 3a2219c82c..0000000000 --- a/src/systemd/sd-ipv4acd.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef foosdipv4acdfoo -#define foosdipv4acdfoo - -/*** - This file is part of systemd. - - Copyright (C) 2014 Axis Communications AB. All rights reserved. - Copyright (C) 2015 Tom Gundersen - - 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 . -***/ - -#include -#include - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -enum { - SD_IPV4ACD_EVENT_STOP = 0, - SD_IPV4ACD_EVENT_BIND = 1, - SD_IPV4ACD_EVENT_CONFLICT = 2, -}; - -typedef struct sd_ipv4acd sd_ipv4acd; -typedef void (*sd_ipv4acd_cb_t)(sd_ipv4acd *ll, int event, void *userdata); - -int sd_ipv4acd_detach_event(sd_ipv4acd *ll); -int sd_ipv4acd_attach_event(sd_ipv4acd *ll, sd_event *event, int priority); -int sd_ipv4acd_get_address(sd_ipv4acd *ll, struct in_addr *address); -int sd_ipv4acd_set_callback(sd_ipv4acd *ll, sd_ipv4acd_cb_t cb, void *userdata); -int sd_ipv4acd_set_mac(sd_ipv4acd *ll, const struct ether_addr *addr); -int sd_ipv4acd_set_index(sd_ipv4acd *ll, int interface_index); -int sd_ipv4acd_set_address(sd_ipv4acd *ll, const struct in_addr *address); -int sd_ipv4acd_is_running(sd_ipv4acd *ll); -int sd_ipv4acd_start(sd_ipv4acd *ll); -int sd_ipv4acd_stop(sd_ipv4acd *ll); -sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *ll); -sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *ll); -int sd_ipv4acd_new(sd_ipv4acd **ret); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4acd, sd_ipv4acd_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-ipv4ll.h b/src/systemd/sd-ipv4ll.h deleted file mode 100644 index 67c566fe0d..0000000000 --- a/src/systemd/sd-ipv4ll.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef foosdipv4llfoo -#define foosdipv4llfoo - -/*** - This file is part of systemd. - - Copyright (C) 2014 Axis Communications AB. 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 . -***/ - -#include -#include - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -enum { - SD_IPV4LL_EVENT_STOP = 0, - SD_IPV4LL_EVENT_BIND = 1, - SD_IPV4LL_EVENT_CONFLICT = 2, -}; - -typedef struct sd_ipv4ll sd_ipv4ll; -typedef void (*sd_ipv4ll_cb_t)(sd_ipv4ll *ll, int event, void *userdata); - -int sd_ipv4ll_detach_event(sd_ipv4ll *ll); -int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int priority); -int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address); -int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_cb_t cb, void *userdata); -int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr); -int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index); -int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address); -int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, unsigned seed); -int sd_ipv4ll_is_running(sd_ipv4ll *ll); -int sd_ipv4ll_start(sd_ipv4ll *ll); -int sd_ipv4ll_stop(sd_ipv4ll *ll); -sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll); -sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll); -int sd_ipv4ll_new (sd_ipv4ll **ret); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4ll, sd_ipv4ll_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h deleted file mode 100644 index abb9eca576..0000000000 --- a/src/systemd/sd-journal.h +++ /dev/null @@ -1,172 +0,0 @@ -#ifndef foosdjournalhfoo -#define foosdjournalhfoo - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include -#include -#include - -#include "sd-id128.h" - -#include "_sd-common.h" - -/* Journal APIs. See sd-journal(3) for more information. */ - -_SD_BEGIN_DECLARATIONS; - -/* Write to daemon */ -int sd_journal_print(int priority, const char *format, ...) _sd_printf_(2, 3); -int sd_journal_printv(int priority, const char *format, va_list ap) _sd_printf_(2, 0); -int sd_journal_send(const char *format, ...) _sd_printf_(1, 0) _sd_sentinel_; -int sd_journal_sendv(const struct iovec *iov, int n); -int sd_journal_perror(const char *message); - -/* Used by the macros below. You probably don't want to call this directly. */ -int sd_journal_print_with_location(int priority, const char *file, const char *line, const char *func, const char *format, ...) _sd_printf_(5, 6); -int sd_journal_printv_with_location(int priority, const char *file, const char *line, const char *func, const char *format, va_list ap) _sd_printf_(5, 0); -int sd_journal_send_with_location(const char *file, const char *line, const char *func, const char *format, ...) _sd_printf_(4, 0) _sd_sentinel_; -int sd_journal_sendv_with_location(const char *file, const char *line, const char *func, const struct iovec *iov, int n); -int sd_journal_perror_with_location(const char *file, const char *line, const char *func, const char *message); - -/* implicitly add code location to messages sent, if this is enabled */ -#ifndef SD_JOURNAL_SUPPRESS_LOCATION - -#define sd_journal_print(priority, ...) sd_journal_print_with_location(priority, "CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, __VA_ARGS__) -#define sd_journal_printv(priority, format, ap) sd_journal_printv_with_location(priority, "CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, format, ap) -#define sd_journal_send(...) sd_journal_send_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, __VA_ARGS__) -#define sd_journal_sendv(iovec, n) sd_journal_sendv_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, iovec, n) -#define sd_journal_perror(message) sd_journal_perror_with_location("CODE_FILE=" __FILE__, "CODE_LINE=" _SD_STRINGIFY(__LINE__), __func__, message) - -#endif - -int sd_journal_stream_fd(const char *identifier, int priority, int level_prefix); - -/* Browse journal stream */ - -typedef struct sd_journal sd_journal; - -/* Open flags */ -enum { - SD_JOURNAL_LOCAL_ONLY = 1, - SD_JOURNAL_RUNTIME_ONLY = 2, - SD_JOURNAL_SYSTEM = 4, - SD_JOURNAL_CURRENT_USER = 8, - - SD_JOURNAL_SYSTEM_ONLY = SD_JOURNAL_SYSTEM, /* deprecated name */ -}; - -/* Wakeup event types */ -enum { - SD_JOURNAL_NOP, - SD_JOURNAL_APPEND, - SD_JOURNAL_INVALIDATE -}; - -int sd_journal_open(sd_journal **ret, int flags); -int sd_journal_open_directory(sd_journal **ret, const char *path, int flags); -int sd_journal_open_files(sd_journal **ret, const char **paths, int flags); -int sd_journal_open_container(sd_journal **ret, const char *machine, int flags); -void sd_journal_close(sd_journal *j); - -int sd_journal_previous(sd_journal *j); -int sd_journal_next(sd_journal *j); - -int sd_journal_previous_skip(sd_journal *j, uint64_t skip); -int sd_journal_next_skip(sd_journal *j, uint64_t skip); - -int sd_journal_get_realtime_usec(sd_journal *j, uint64_t *ret); -int sd_journal_get_monotonic_usec(sd_journal *j, uint64_t *ret, sd_id128_t *ret_boot_id); - -int sd_journal_set_data_threshold(sd_journal *j, size_t sz); -int sd_journal_get_data_threshold(sd_journal *j, size_t *sz); - -int sd_journal_get_data(sd_journal *j, const char *field, const void **data, size_t *l); -int sd_journal_enumerate_data(sd_journal *j, const void **data, size_t *l); -void sd_journal_restart_data(sd_journal *j); - -int sd_journal_add_match(sd_journal *j, const void *data, size_t size); -int sd_journal_add_disjunction(sd_journal *j); -int sd_journal_add_conjunction(sd_journal *j); -void sd_journal_flush_matches(sd_journal *j); - -int sd_journal_seek_head(sd_journal *j); -int sd_journal_seek_tail(sd_journal *j); -int sd_journal_seek_monotonic_usec(sd_journal *j, sd_id128_t boot_id, uint64_t usec); -int sd_journal_seek_realtime_usec(sd_journal *j, uint64_t usec); -int sd_journal_seek_cursor(sd_journal *j, const char *cursor); - -int sd_journal_get_cursor(sd_journal *j, char **cursor); -int sd_journal_test_cursor(sd_journal *j, const char *cursor); - -int sd_journal_get_cutoff_realtime_usec(sd_journal *j, uint64_t *from, uint64_t *to); -int sd_journal_get_cutoff_monotonic_usec(sd_journal *j, const sd_id128_t boot_id, uint64_t *from, uint64_t *to); - -int sd_journal_get_usage(sd_journal *j, uint64_t *bytes); - -int sd_journal_query_unique(sd_journal *j, const char *field); -int sd_journal_enumerate_unique(sd_journal *j, const void **data, size_t *l); -void sd_journal_restart_unique(sd_journal *j); - -int sd_journal_enumerate_fields(sd_journal *j, const char **field); -void sd_journal_restart_fields(sd_journal *j); - -int sd_journal_get_fd(sd_journal *j); -int sd_journal_get_events(sd_journal *j); -int sd_journal_get_timeout(sd_journal *j, uint64_t *timeout_usec); -int sd_journal_process(sd_journal *j); -int sd_journal_wait(sd_journal *j, uint64_t timeout_usec); -int sd_journal_reliable_fd(sd_journal *j); - -int sd_journal_get_catalog(sd_journal *j, char **text); -int sd_journal_get_catalog_for_message_id(sd_id128_t id, char **text); - -int sd_journal_has_runtime_files(sd_journal *j); -int sd_journal_has_persistent_files(sd_journal *j); - -/* The inverse condition avoids ambiguity of dangling 'else' after the macro */ -#define SD_JOURNAL_FOREACH(j) \ - if (sd_journal_seek_head(j) < 0) { } \ - else while (sd_journal_next(j) > 0) - -/* The inverse condition avoids ambiguity of dangling 'else' after the macro */ -#define SD_JOURNAL_FOREACH_BACKWARDS(j) \ - if (sd_journal_seek_tail(j) < 0) { } \ - else while (sd_journal_previous(j) > 0) - -/* Iterate through the data fields of the current journal entry */ -#define SD_JOURNAL_FOREACH_DATA(j, data, l) \ - for (sd_journal_restart_data(j); sd_journal_enumerate_data((j), &(data), &(l)) > 0; ) - -/* Iterate through the all known values of a specific field */ -#define SD_JOURNAL_FOREACH_UNIQUE(j, data, l) \ - for (sd_journal_restart_unique(j); sd_journal_enumerate_unique((j), &(data), &(l)) > 0; ) - -/* Iterate through all known field names */ -#define SD_JOURNAL_FOREACH_FIELD(j, field) \ - for (sd_journal_restart_fields(j); sd_journal_enumerate_fields((j), &(field)) > 0; ) - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_journal, sd_journal_close); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-lldp.h b/src/systemd/sd-lldp.h deleted file mode 100644 index ea952ef187..0000000000 --- a/src/systemd/sd-lldp.h +++ /dev/null @@ -1,87 +0,0 @@ -#ifndef foosdlldphfoo -#define foosdlldphfoo - -/*** - This file is part of systemd. - - Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014 Susant Sahani - - 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 . -***/ - -#include -#include - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -enum { - SD_LLDP_EVENT_UPDATE_INFO = 0, -}; - -enum { - SD_LLDP_DESTINATION_TYPE_NEAREST_BRIDGE, - SD_LLDP_DESTINATION_TYPE_NEAREST_NON_TPMR_BRIDGE, - SD_LLDP_DESTINATION_TYPE_NEAREST_CUSTOMER_BRIDGE, -}; - -typedef struct sd_lldp sd_lldp; -typedef struct sd_lldp_packet sd_lldp_packet; - -typedef void (*sd_lldp_cb_t)(sd_lldp *lldp, int event, void *userdata); - -int sd_lldp_new(int ifindex, const char *ifname, const struct ether_addr *mac, sd_lldp **ret); -sd_lldp* sd_lldp_unref(sd_lldp *lldp); - -int sd_lldp_start(sd_lldp *lldp); -int sd_lldp_stop(sd_lldp *lldp); - -int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int priority); -int sd_lldp_detach_event(sd_lldp *lldp); - -int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_cb_t cb, void *userdata); -int sd_lldp_save(sd_lldp *lldp, const char *file); - -int sd_lldp_packet_read_chassis_id(sd_lldp_packet *tlv, uint8_t *type, uint8_t **data, uint16_t *length); -int sd_lldp_packet_read_port_id(sd_lldp_packet *tlv, uint8_t *type, uint8_t **data, uint16_t *length); -int sd_lldp_packet_read_ttl(sd_lldp_packet *tlv, uint16_t *ttl); -int sd_lldp_packet_read_system_name(sd_lldp_packet *tlv, char **data, uint16_t *length); -int sd_lldp_packet_read_system_description(sd_lldp_packet *tlv, char **data, uint16_t *length); -int sd_lldp_packet_read_system_capability(sd_lldp_packet *tlv, uint16_t *data); -int sd_lldp_packet_read_port_description(sd_lldp_packet *tlv, char **data, uint16_t *length); - -/* IEEE 802.1 organizationally specific TLVs */ -int sd_lldp_packet_read_port_vlan_id(sd_lldp_packet *tlv, uint16_t *id); -int sd_lldp_packet_read_port_protocol_vlan_id(sd_lldp_packet *tlv, uint8_t *flags, uint16_t *id); -int sd_lldp_packet_read_vlan_name(sd_lldp_packet *tlv, uint16_t *vlan_id, char **name, uint16_t *length); -int sd_lldp_packet_read_management_vid(sd_lldp_packet *tlv, uint16_t *id); -int sd_lldp_packet_read_link_aggregation(sd_lldp_packet *tlv, uint8_t *status, uint32_t *id); - -sd_lldp_packet *sd_lldp_packet_ref(sd_lldp_packet *tlv); -sd_lldp_packet *sd_lldp_packet_unref(sd_lldp_packet *tlv); - -int sd_lldp_packet_get_destination_type(sd_lldp_packet *tlv, int *dest); - -int sd_lldp_get_packets(sd_lldp *lldp, sd_lldp_packet ***tlvs); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp, sd_lldp_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_packet, sd_lldp_packet_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-login.h b/src/systemd/sd-login.h deleted file mode 100644 index 3c10ff032f..0000000000 --- a/src/systemd/sd-login.h +++ /dev/null @@ -1,245 +0,0 @@ -#ifndef foosdloginhfoo -#define foosdloginhfoo - -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include - -#include "_sd-common.h" - -/* - * A few points: - * - * Instead of returning an empty string array or empty uid array, we - * may return NULL. - * - * Free the data the library returns with libc free(). String arrays - * are NULL terminated, and you need to free the array itself, in - * addition to the strings contained. - * - * We return error codes as negative errno, kernel-style. On success, we - * return 0 or positive. - * - * These functions access data in /proc, /sys/fs/cgroup, and /run. All - * of these are virtual file systems; therefore, accesses are - * relatively cheap. - * - * See sd-login(3) for more information. - */ - -_SD_BEGIN_DECLARATIONS; - -/* Get session from PID. Note that 'shared' processes of a user are - * not attached to a session, but only attached to a user. This will - * return an error for system processes and 'shared' processes of a - * user. */ -int sd_pid_get_session(pid_t pid, char **session); - -/* Get UID of the owner of the session of the PID (or in case the - * process is a 'shared' user process, the UID of that user is - * returned). This will not return the UID of the process, but rather - * the UID of the owner of the cgroup that the process is in. This will - * return an error for system processes. */ -int sd_pid_get_owner_uid(pid_t pid, uid_t *uid); - -/* Get systemd non-slice unit (i.e. service) name from PID, for system - * services. This will return an error for non-service processes. */ -int sd_pid_get_unit(pid_t pid, char **unit); - -/* Get systemd non-slice unit (i.e. service) name from PID, for user - * services. This will return an error for non-user-service - * processes. */ -int sd_pid_get_user_unit(pid_t pid, char **unit); - -/* Get slice name from PID. */ -int sd_pid_get_slice(pid_t pid, char **slice); - -/* Get user slice name from PID. */ -int sd_pid_get_user_slice(pid_t pid, char **slice); - -/* Get machine name from PID, for processes assigned to a VM or - * container. This will return an error for non-machine processes. */ -int sd_pid_get_machine_name(pid_t pid, char **machine); - -/* Get the control group from a PID, relative to the root of the - * hierarchy. */ -int sd_pid_get_cgroup(pid_t pid, char **cgroup); - -/* Similar to sd_pid_get_session(), but retrieves data about the peer - * of a connected AF_UNIX socket */ -int sd_peer_get_session(int fd, char **session); - -/* Similar to sd_pid_get_owner_uid(), but retrieves data about the peer of - * a connected AF_UNIX socket */ -int sd_peer_get_owner_uid(int fd, uid_t *uid); - -/* Similar to sd_pid_get_unit(), but retrieves data about the peer of - * a connected AF_UNIX socket */ -int sd_peer_get_unit(int fd, char **unit); - -/* Similar to sd_pid_get_user_unit(), but retrieves data about the peer of - * a connected AF_UNIX socket */ -int sd_peer_get_user_unit(int fd, char **unit); - -/* Similar to sd_pid_get_slice(), but retrieves data about the peer of - * a connected AF_UNIX socket */ -int sd_peer_get_slice(int fd, char **slice); - -/* Similar to sd_pid_get_user_slice(), but retrieves data about the peer of - * a connected AF_UNIX socket */ -int sd_peer_get_user_slice(int fd, char **slice); - -/* Similar to sd_pid_get_machine_name(), but retrieves data about the - * peer of a a connected AF_UNIX socket */ -int sd_peer_get_machine_name(int fd, char **machine); - -/* Similar to sd_pid_get_cgroup(), but retrieves data about the peer - * of a connected AF_UNIX socket. */ -int sd_peer_get_cgroup(pid_t pid, char **cgroup); - -/* Get state from UID. Possible states: offline, lingering, online, active, closing */ -int sd_uid_get_state(uid_t uid, char **state); - -/* Return primary session of user, if there is any */ -int sd_uid_get_display(uid_t uid, char **session); - -/* Return 1 if UID has session on seat. If require_active is true, this will - * look for active sessions only. */ -int sd_uid_is_on_seat(uid_t uid, int require_active, const char *seat); - -/* Return sessions of user. If require_active is true, this will look for - * active sessions only. Returns the number of sessions. - * If sessions is NULL, this will just return the number of sessions. */ -int sd_uid_get_sessions(uid_t uid, int require_active, char ***sessions); - -/* Return seats of user is on. If require_active is true, this will look for - * active seats only. Returns the number of seats. - * If seats is NULL, this will just return the number of seats. */ -int sd_uid_get_seats(uid_t uid, int require_active, char ***seats); - -/* Return 1 if the session is active. */ -int sd_session_is_active(const char *session); - -/* Return 1 if the session is remote. */ -int sd_session_is_remote(const char *session); - -/* Get state from session. Possible states: online, active, closing. - * This function is a more generic version of sd_session_is_active(). */ -int sd_session_get_state(const char *session, char **state); - -/* Determine user ID of session */ -int sd_session_get_uid(const char *session, uid_t *uid); - -/* Determine seat of session */ -int sd_session_get_seat(const char *session, char **seat); - -/* Determine the (PAM) service name this session was registered by. */ -int sd_session_get_service(const char *session, char **service); - -/* Determine the type of this session, i.e. one of "tty", "x11", "wayland", "mir" or "unspecified". */ -int sd_session_get_type(const char *session, char **type); - -/* Determine the class of this session, i.e. one of "user", "greeter" or "lock-screen". */ -int sd_session_get_class(const char *session, char **clazz); - -/* Determine the desktop brand of this session, i.e. something like "GNOME", "KDE" or "systemd-console". */ -int sd_session_get_desktop(const char *session, char **desktop); - -/* Determine the X11 display of this session. */ -int sd_session_get_display(const char *session, char **display); - -/* Determine the remote host of this session. */ -int sd_session_get_remote_host(const char *session, char **remote_host); - -/* Determine the remote user of this session (if provided by PAM). */ -int sd_session_get_remote_user(const char *session, char **remote_user); - -/* Determine the TTY of this session. */ -int sd_session_get_tty(const char *session, char **display); - -/* Determine the VT number of this session. */ -int sd_session_get_vt(const char *session, unsigned *vtnr); - -/* Return active session and user of seat */ -int sd_seat_get_active(const char *seat, char **session, uid_t *uid); - -/* Return sessions and users on seat. Returns number of sessions. - * If sessions is NULL, this returns only the number of sessions. */ -int sd_seat_get_sessions(const char *seat, char ***sessions, uid_t **uid, unsigned *n_uids); - -/* Return whether the seat is multi-session capable */ -int sd_seat_can_multi_session(const char *seat); - -/* Return whether the seat is TTY capable, i.e. suitable for showing console UIs */ -int sd_seat_can_tty(const char *seat); - -/* Return whether the seat is graphics capable, i.e. suitable for showing graphical UIs */ -int sd_seat_can_graphical(const char *seat); - -/* Return the class of machine */ -int sd_machine_get_class(const char *machine, char **clazz); - -/* Return the list if host-side network interface indices of a machine */ -int sd_machine_get_ifindices(const char *machine, int **ifindices); - -/* Get all seats, store in *seats. Returns the number of seats. If - * seats is NULL, this only returns the number of seats. */ -int sd_get_seats(char ***seats); - -/* Get all sessions, store in *sessions. Returns the number of - * sessions. If sessions is NULL, this only returns the number of sessions. */ -int sd_get_sessions(char ***sessions); - -/* Get all logged in users, store in *users. Returns the number of - * users. If users is NULL, this only returns the number of users. */ -int sd_get_uids(uid_t **users); - -/* Get all running virtual machines/containers */ -int sd_get_machine_names(char ***machines); - -/* Monitor object */ -typedef struct sd_login_monitor sd_login_monitor; - -/* Create a new monitor. Category must be NULL, "seat", "session", - * "uid", or "machine" to get monitor events for the specific category - * (or all). */ -int sd_login_monitor_new(const char *category, sd_login_monitor** ret); - -/* Destroys the passed monitor. Returns NULL. */ -sd_login_monitor* sd_login_monitor_unref(sd_login_monitor *m); - -/* Flushes the monitor */ -int sd_login_monitor_flush(sd_login_monitor *m); - -/* Get FD from monitor */ -int sd_login_monitor_get_fd(sd_login_monitor *m); - -/* Get poll() mask to monitor */ -int sd_login_monitor_get_events(sd_login_monitor *m); - -/* Get timeout for poll(), as usec value relative to CLOCK_MONOTONIC's epoch */ -int sd_login_monitor_get_timeout(sd_login_monitor *m, uint64_t *timeout_usec); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_login_monitor, sd_login_monitor_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-messages.h b/src/systemd/sd-messages.h deleted file mode 100644 index 8a72576ec8..0000000000 --- a/src/systemd/sd-messages.h +++ /dev/null @@ -1,93 +0,0 @@ -#ifndef foosdmessageshfoo -#define foosdmessageshfoo - -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include "sd-id128.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -/* Hey! If you add a new message here, you *must* also update the - * message catalog with an appropriate explanation */ - -/* And if you add a new ID here, make sure to generate a random one - * with journalctl --new-id128. Do not use any other IDs, and do not - * count them up manually. */ - -#define SD_MESSAGE_JOURNAL_START SD_ID128_MAKE(f7,73,79,a8,49,0b,40,8b,be,5f,69,40,50,5a,77,7b) -#define SD_MESSAGE_JOURNAL_STOP SD_ID128_MAKE(d9,3f,b3,c9,c2,4d,45,1a,97,ce,a6,15,ce,59,c0,0b) -#define SD_MESSAGE_JOURNAL_DROPPED SD_ID128_MAKE(a5,96,d6,fe,7b,fa,49,94,82,8e,72,30,9e,95,d6,1e) -#define SD_MESSAGE_JOURNAL_MISSED SD_ID128_MAKE(e9,bf,28,e6,e8,34,48,1b,b6,f4,8f,54,8a,d1,36,06) -#define SD_MESSAGE_JOURNAL_USAGE SD_ID128_MAKE(ec,38,7f,57,7b,84,4b,8f,a9,48,f3,3c,ad,9a,75,e6) - -#define SD_MESSAGE_COREDUMP SD_ID128_MAKE(fc,2e,22,bc,6e,e6,47,b6,b9,07,29,ab,34,a2,50,b1) - -#define SD_MESSAGE_SESSION_START SD_ID128_MAKE(8d,45,62,0c,1a,43,48,db,b1,74,10,da,57,c6,0c,66) -#define SD_MESSAGE_SESSION_STOP SD_ID128_MAKE(33,54,93,94,24,b4,45,6d,98,02,ca,83,33,ed,42,4a) -#define SD_MESSAGE_SEAT_START SD_ID128_MAKE(fc,be,fc,5d,a2,3d,42,80,93,f9,7c,82,a9,29,0f,7b) -#define SD_MESSAGE_SEAT_STOP SD_ID128_MAKE(e7,85,2b,fe,46,78,4e,d0,ac,cd,e0,4b,c8,64,c2,d5) -#define SD_MESSAGE_MACHINE_START SD_ID128_MAKE(24,d8,d4,45,25,73,40,24,96,06,83,81,a6,31,2d,f2) -#define SD_MESSAGE_MACHINE_STOP SD_ID128_MAKE(58,43,2b,d3,ba,ce,47,7c,b5,14,b5,63,81,b8,a7,58) - -#define SD_MESSAGE_TIME_CHANGE SD_ID128_MAKE(c7,a7,87,07,9b,35,4e,aa,a9,e7,7b,37,18,93,cd,27) -#define SD_MESSAGE_TIMEZONE_CHANGE SD_ID128_MAKE(45,f8,2f,4a,ef,7a,4b,bf,94,2c,e8,61,d1,f2,09,90) - -#define SD_MESSAGE_STARTUP_FINISHED SD_ID128_MAKE(b0,7a,24,9c,d0,24,41,4a,82,dd,00,cd,18,13,78,ff) - -#define SD_MESSAGE_SLEEP_START SD_ID128_MAKE(6b,bd,95,ee,97,79,41,e4,97,c4,8b,e2,7c,25,41,28) -#define SD_MESSAGE_SLEEP_STOP SD_ID128_MAKE(88,11,e6,df,2a,8e,40,f5,8a,94,ce,a2,6f,8e,bf,14) - -#define SD_MESSAGE_SHUTDOWN SD_ID128_MAKE(98,26,88,66,d1,d5,4a,49,9c,4e,98,92,1d,93,bc,40) - -#define SD_MESSAGE_UNIT_STARTING SD_ID128_MAKE(7d,49,58,e8,42,da,4a,75,8f,6c,1c,dc,7b,36,dc,c5) -#define SD_MESSAGE_UNIT_STARTED SD_ID128_MAKE(39,f5,34,79,d3,a0,45,ac,8e,11,78,62,48,23,1f,bf) -#define SD_MESSAGE_UNIT_STOPPING SD_ID128_MAKE(de,5b,42,6a,63,be,47,a7,b6,ac,3e,aa,c8,2e,2f,6f) -#define SD_MESSAGE_UNIT_STOPPED SD_ID128_MAKE(9d,1a,aa,27,d6,01,40,bd,96,36,54,38,aa,d2,02,86) -#define SD_MESSAGE_UNIT_FAILED SD_ID128_MAKE(be,02,cf,68,55,d2,42,8b,a4,0d,f7,e9,d0,22,f0,3d) -#define SD_MESSAGE_UNIT_RELOADING SD_ID128_MAKE(d3,4d,03,7f,ff,18,47,e6,ae,66,9a,37,0e,69,47,25) -#define SD_MESSAGE_UNIT_RELOADED SD_ID128_MAKE(7b,05,eb,c6,68,38,42,22,ba,a8,88,11,79,cf,da,54) - -#define SD_MESSAGE_SPAWN_FAILED SD_ID128_MAKE(64,12,57,65,1c,1b,4e,c9,a8,62,4d,7a,40,a9,e1,e7) - -#define SD_MESSAGE_FORWARD_SYSLOG_MISSED SD_ID128_MAKE(00,27,22,9c,a0,64,41,81,a7,6c,4e,92,45,8a,fa,2e) - -#define SD_MESSAGE_OVERMOUNTING SD_ID128_MAKE(1d,ee,03,69,c7,fc,47,36,b7,09,9b,38,ec,b4,6e,e7) - -#define SD_MESSAGE_LID_OPENED SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,6f) -#define SD_MESSAGE_LID_CLOSED SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,70) -#define SD_MESSAGE_SYSTEM_DOCKED SD_ID128_MAKE(f5,f4,16,b8,62,07,4b,28,92,7a,48,c3,ba,7d,51,ff) -#define SD_MESSAGE_SYSTEM_UNDOCKED SD_ID128_MAKE(51,e1,71,bd,58,52,48,56,81,10,14,4c,51,7c,ca,53) -#define SD_MESSAGE_POWER_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,71) -#define SD_MESSAGE_SUSPEND_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,72) -#define SD_MESSAGE_HIBERNATE_KEY SD_ID128_MAKE(b7,2e,a4,a2,88,15,45,a0,b5,0e,20,0e,55,b9,b0,73) - -#define SD_MESSAGE_INVALID_CONFIGURATION SD_ID128_MAKE(c7,72,d2,4e,9a,88,4c,be,b9,ea,12,62,5c,30,6c,01) - -#define SD_MESSAGE_BOOTCHART SD_ID128_MAKE(9f,26,aa,56,2c,f4,40,c2,b1,6c,77,3d,04,79,b5,18) - -#define SD_MESSAGE_DNSSEC_FAILURE SD_ID128_MAKE(16,75,d7,f1,72,17,40,98,b1,10,8b,f8,c7,dc,8f,5d) -#define SD_MESSAGE_DNSSEC_TRUST_ANCHOR_REVOKED SD_ID128_MAKE(4d,44,08,cf,d0,d1,44,85,91,84,d1,e6,5d,7c,8a,65) -#define SD_MESSAGE_DNSSEC_DOWNGRADE SD_ID128_MAKE(36,db,2d,fa,5a,90,45,e1,bd,4a,f5,f9,3e,1c,f0,57) - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-ndisc.h b/src/systemd/sd-ndisc.h deleted file mode 100644 index 762947531d..0000000000 --- a/src/systemd/sd-ndisc.h +++ /dev/null @@ -1,84 +0,0 @@ -#ifndef foosdndiscfoo -#define foosdndiscfoo - -/*** - 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 . -***/ - -#include -#include - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -enum { - SD_NDISC_EVENT_STOP = 0, - SD_NDISC_EVENT_TIMEOUT = 1, -}; - -typedef struct sd_ndisc sd_ndisc; - -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_index(sd_ndisc *nd, int interface_index); -int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr); - -int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int priority); -int sd_ndisc_detach_event(sd_ndisc *nd); -sd_event *sd_ndisc_get_event(sd_ndisc *nd); - -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_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" - -#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]) - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-netlink.h b/src/systemd/sd-netlink.h deleted file mode 100644 index b4798d2476..0000000000 --- a/src/systemd/sd-netlink.h +++ /dev/null @@ -1,161 +0,0 @@ -#ifndef foosdnetlinkhfoo -#define foosdnetlinkhfoo - -/*** - This file is part of systemd. - - Copyright 2013 Tom Gundersen - - 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 . -***/ - -#include -#include -#include -#include -#include - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -typedef struct sd_netlink sd_netlink; -typedef struct sd_netlink_message sd_netlink_message; - -/* callback */ - -typedef int (*sd_netlink_message_handler_t)(sd_netlink *nl, sd_netlink_message *m, void *userdata); - -/* bus */ -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); - -sd_netlink *sd_netlink_ref(sd_netlink *nl); -sd_netlink *sd_netlink_unref(sd_netlink *nl); - -int sd_netlink_send(sd_netlink *nl, sd_netlink_message *message, uint32_t *serial); -int sd_netlink_call_async(sd_netlink *nl, sd_netlink_message *message, - sd_netlink_message_handler_t callback, - void *userdata, uint64_t usec, uint32_t *serial); -int sd_netlink_call_async_cancel(sd_netlink *nl, uint32_t serial); -int sd_netlink_call(sd_netlink *nl, sd_netlink_message *message, uint64_t timeout, - sd_netlink_message **reply); - -int sd_netlink_get_events(sd_netlink *nl); -int sd_netlink_get_timeout(sd_netlink *nl, uint64_t *timeout); -int sd_netlink_process(sd_netlink *nl, sd_netlink_message **ret); -int sd_netlink_wait(sd_netlink *nl, uint64_t timeout); - -int sd_netlink_add_match(sd_netlink *nl, uint16_t match, sd_netlink_message_handler_t c, void *userdata); -int sd_netlink_remove_match(sd_netlink *nl, uint16_t match, sd_netlink_message_handler_t c, void *userdata); - -int sd_netlink_attach_event(sd_netlink *nl, sd_event *e, int priority); -int sd_netlink_detach_event(sd_netlink *nl); - -int sd_netlink_message_append_string(sd_netlink_message *m, unsigned short type, const char *data); -int sd_netlink_message_append_flag(sd_netlink_message *m, unsigned short type); -int sd_netlink_message_append_u8(sd_netlink_message *m, unsigned short type, uint8_t data); -int sd_netlink_message_append_u16(sd_netlink_message *m, unsigned short type, uint16_t data); -int sd_netlink_message_append_u32(sd_netlink_message *m, unsigned short type, uint32_t data); -int sd_netlink_message_append_data(sd_netlink_message *m, unsigned short type, const void *data, size_t len); -int sd_netlink_message_append_in_addr(sd_netlink_message *m, unsigned short type, const struct in_addr *data); -int sd_netlink_message_append_in6_addr(sd_netlink_message *m, unsigned short type, const struct in6_addr *data); -int sd_netlink_message_append_ether_addr(sd_netlink_message *m, unsigned short type, const struct ether_addr *data); -int sd_netlink_message_append_cache_info(sd_netlink_message *m, unsigned short type, const struct ifa_cacheinfo *info); - -int sd_netlink_message_open_container(sd_netlink_message *m, unsigned short type); -int sd_netlink_message_open_container_union(sd_netlink_message *m, unsigned short type, const char *key); -int sd_netlink_message_close_container(sd_netlink_message *m); - -int sd_netlink_message_read_string(sd_netlink_message *m, unsigned short type, const char **data); -int sd_netlink_message_read_u8(sd_netlink_message *m, unsigned short type, uint8_t *data); -int sd_netlink_message_read_u16(sd_netlink_message *m, unsigned short type, uint16_t *data); -int sd_netlink_message_read_u32(sd_netlink_message *m, unsigned short type, uint32_t *data); -int sd_netlink_message_read_ether_addr(sd_netlink_message *m, unsigned short type, struct ether_addr *data); -int sd_netlink_message_read_cache_info(sd_netlink_message *m, unsigned short type, struct ifa_cacheinfo *info); -int sd_netlink_message_read_in_addr(sd_netlink_message *m, unsigned short type, struct in_addr *data); -int sd_netlink_message_read_in6_addr(sd_netlink_message *m, unsigned short type, struct in6_addr *data); -int sd_netlink_message_enter_container(sd_netlink_message *m, unsigned short type); -int sd_netlink_message_exit_container(sd_netlink_message *m); - -int sd_netlink_message_rewind(sd_netlink_message *m); - -sd_netlink_message *sd_netlink_message_next(sd_netlink_message *m); - -sd_netlink_message *sd_netlink_message_ref(sd_netlink_message *m); -sd_netlink_message *sd_netlink_message_unref(sd_netlink_message *m); - -int sd_netlink_message_request_dump(sd_netlink_message *m, int dump); -int sd_netlink_message_is_error(sd_netlink_message *m); -int sd_netlink_message_get_errno(sd_netlink_message *m); -int sd_netlink_message_get_type(sd_netlink_message *m, uint16_t *type); -int sd_netlink_message_set_flags(sd_netlink_message *m, uint16_t flags); -int sd_netlink_message_is_broadcast(sd_netlink_message *m); - -/* rtnl */ - -int sd_rtnl_message_new_link(sd_netlink *nl, sd_netlink_message **ret, uint16_t msg_type, int index); -int sd_rtnl_message_new_addr_update(sd_netlink *nl, sd_netlink_message **ret, int index, int family); -int sd_rtnl_message_new_addr(sd_netlink *nl, sd_netlink_message **ret, uint16_t msg_type, int index, int family); -int sd_rtnl_message_new_route(sd_netlink *nl, sd_netlink_message **ret, uint16_t nlmsg_type, int rtm_family, unsigned char rtm_protocol); -int sd_rtnl_message_new_neigh(sd_netlink *nl, sd_netlink_message **ret, uint16_t msg_type, int index, int nda_family); - -int sd_rtnl_message_get_family(sd_netlink_message *m, int *family); - -int sd_rtnl_message_addr_set_prefixlen(sd_netlink_message *m, unsigned char prefixlen); -int sd_rtnl_message_addr_set_scope(sd_netlink_message *m, unsigned char scope); -int sd_rtnl_message_addr_set_flags(sd_netlink_message *m, unsigned char flags); -int sd_rtnl_message_addr_get_family(sd_netlink_message *m, int *family); -int sd_rtnl_message_addr_get_prefixlen(sd_netlink_message *m, unsigned char *prefixlen); -int sd_rtnl_message_addr_get_scope(sd_netlink_message *m, unsigned char *scope); -int sd_rtnl_message_addr_get_flags(sd_netlink_message *m, unsigned char *flags); -int sd_rtnl_message_addr_get_ifindex(sd_netlink_message *m, int *ifindex); - -int sd_rtnl_message_link_set_flags(sd_netlink_message *m, unsigned flags, unsigned change); -int sd_rtnl_message_link_set_type(sd_netlink_message *m, unsigned type); -int sd_rtnl_message_link_set_family(sd_netlink_message *m, unsigned family); -int sd_rtnl_message_link_get_ifindex(sd_netlink_message *m, int *ifindex); -int sd_rtnl_message_link_get_flags(sd_netlink_message *m, unsigned *flags); -int sd_rtnl_message_link_get_type(sd_netlink_message *m, unsigned *type); - -int sd_rtnl_message_route_set_dst_prefixlen(sd_netlink_message *m, unsigned char prefixlen); -int sd_rtnl_message_route_set_src_prefixlen(sd_netlink_message *m, unsigned char prefixlen); -int sd_rtnl_message_route_set_scope(sd_netlink_message *m, unsigned char scope); -int sd_rtnl_message_route_set_flags(sd_netlink_message *m, unsigned flags); -int sd_rtnl_message_route_get_flags(sd_netlink_message *m, unsigned *flags); -int sd_rtnl_message_route_get_family(sd_netlink_message *m, int *family); -int sd_rtnl_message_route_get_protocol(sd_netlink_message *m, unsigned char *protocol); -int sd_rtnl_message_route_get_scope(sd_netlink_message *m, unsigned char *scope); -int sd_rtnl_message_route_get_tos(sd_netlink_message *m, unsigned char *tos); -int sd_rtnl_message_route_get_table(sd_netlink_message *m, unsigned char *table); -int sd_rtnl_message_route_get_dst_prefixlen(sd_netlink_message *m, unsigned char *dst_len); -int sd_rtnl_message_route_get_src_prefixlen(sd_netlink_message *m, unsigned char *src_len); - -int sd_rtnl_message_neigh_set_flags(sd_netlink_message *m, uint8_t flags); -int sd_rtnl_message_neigh_set_state(sd_netlink_message *m, uint16_t state); -int sd_rtnl_message_neigh_get_family(sd_netlink_message *m, int *family); -int sd_rtnl_message_neigh_get_ifindex(sd_netlink_message *m, int *family); -int sd_rtnl_message_neigh_get_state(sd_netlink_message *m, uint16_t *state); -int sd_rtnl_message_neigh_get_flags(sd_netlink_message *m, uint8_t *flags); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_netlink, sd_netlink_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_netlink_message, sd_netlink_message_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-network.h b/src/systemd/sd-network.h deleted file mode 100644 index e20d12c44d..0000000000 --- a/src/systemd/sd-network.h +++ /dev/null @@ -1,178 +0,0 @@ -#ifndef foosdnetworkhfoo -#define foosdnetworkhfoo - -/*** - This file is part of systemd. - - Copyright 2011 Lennart Poettering - Copyright 2014 Tom Gundersen - - 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 . -***/ - -#include -#include - -#include "_sd-common.h" - -/* - * A few points: - * - * Instead of returning an empty string array or empty integer array, we - * may return NULL. - * - * Free the data the library returns with libc free(). String arrays - * are NULL terminated, and you need to free the array itself in - * addition to the strings contained. - * - * We return error codes as negative errno, kernel-style. On success, we - * return 0 or positive. - * - * These functions access data in /run. This is a virtual file system; - * therefore, accesses are relatively cheap. - * - * See sd-network(3) for more information. - */ - -_SD_BEGIN_DECLARATIONS; - -/* Get overall operational state - * Possible states: down, up, dormant, carrier, degraded, routable - * Possible return codes: - * -ENODATA: networkd is not aware of any links - */ -int sd_network_get_operational_state(char **state); - -/* Get DNS entries for all links. These are string representations of - * IP addresses */ -int sd_network_get_dns(char ***dns); - -/* Get NTP entries for all links. These are domain names or string - * representations of IP addresses */ -int sd_network_get_ntp(char ***ntp); - -/* Get the search domains for all links. */ -int sd_network_get_search_domains(char ***domains); - -/* Get the search domains for all links. */ -int sd_network_get_route_domains(char ***domains); - -/* Get setup state from ifindex. - * Possible states: - * pending: udev is still processing the link, we don't yet know if we will manage it - * failed: networkd failed to manage the link - * configuring: in the process of retrieving configuration or configuring the link - * configured: link configured successfully - * unmanaged: networkd is not handling the link - * linger: the link is gone, but has not yet been dropped by networkd - * Possible return codes: - * -ENODATA: networkd is not aware of the link - */ -int sd_network_link_get_setup_state(int ifindex, char **state); - -/* Get operational state from ifindex. - * Possible states: - * off: the device is powered down - * no-carrier: the device is powered up, but it does not yet have a carrier - * dormant: the device has a carrier, but is not yet ready for normal traffic - * carrier: the link has a carrier - * degraded: the link has carrier and addresses valid on the local link configured - * routable: the link has carrier and routable address configured - * Possible return codes: - * -ENODATA: networkd is not aware of the link - */ -int sd_network_link_get_operational_state(int ifindex, char **state); - -/* Get path to .network file applied to link */ -int sd_network_link_get_network_file(int ifindex, char **filename); - -/* Get DNS entries for a given link. These are string representations of - * IP addresses */ -int sd_network_link_get_dns(int ifindex, char ***addr); - -/* Get NTP entries for a given link. These are domain names or string - * representations of IP addresses */ -int sd_network_link_get_ntp(int ifindex, char ***addr); - -/* Indicates whether or not LLMNR should be enabled for the link - * Possible levels of support: yes, no, resolve - * Possible return codes: - * -ENODATA: networkd is not aware of the link - */ -int sd_network_link_get_llmnr(int ifindex, char **llmnr); - -/* Indicates whether or not MulticastDNS should be enabled for the - * link. - * Possible levels of support: yes, no, resolve - * Possible return codes: - * -ENODATA: networkd is not aware of the link - */ -int sd_network_link_get_mdns(int ifindex, char **mdns); - -/* Indicates whether or not DNSSEC should be enabled for the link - * Possible levels of support: yes, no, allow-downgrade - * Possible return codes: - * -ENODATA: networkd is not aware of the link - */ -int sd_network_link_get_dnssec(int ifindex, char **dnssec); - -/* Returns the list of per-interface DNSSEC negative trust anchors - * Possible return codes: - * -ENODATA: networkd is not aware of the link, or has no such data - */ -int sd_network_link_get_dnssec_negative_trust_anchors(int ifindex, char ***nta); - -int sd_network_link_get_lldp(int ifindex, char **lldp); - -/* Get the search DNS domain names for a given link. */ -int sd_network_link_get_search_domains(int ifindex, char ***domains); - -/* Get the route DNS domain names for a given link. */ -int sd_network_link_get_route_domains(int ifindex, char ***domains); - -/* Get the CARRIERS to which current link is bound to. */ -int sd_network_link_get_carrier_bound_to(int ifindex, char ***carriers); - -/* Get the CARRIERS that are bound to current link. */ -int sd_network_link_get_carrier_bound_by(int ifindex, char ***carriers); - -/* Get the timezone that was learnt on a specific link. */ -int sd_network_link_get_timezone(int ifindex, char **timezone); - -/* Monitor object */ -typedef struct sd_network_monitor sd_network_monitor; - -/* Create a new monitor. Category must be NULL, "links" or "leases". */ -int sd_network_monitor_new(sd_network_monitor **ret, const char *category); - -/* Destroys the passed monitor. Returns NULL. */ -sd_network_monitor* sd_network_monitor_unref(sd_network_monitor *m); - -/* Flushes the monitor */ -int sd_network_monitor_flush(sd_network_monitor *m); - -/* Get FD from monitor */ -int sd_network_monitor_get_fd(sd_network_monitor *m); - -/* Get poll() mask to monitor */ -int sd_network_monitor_get_events(sd_network_monitor *m); - -/* Get timeout for poll(), as usec value relative to CLOCK_MONOTONIC's epoch */ -int sd_network_monitor_get_timeout(sd_network_monitor *m, uint64_t *timeout_usec); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_network_monitor, sd_network_monitor_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-path.h b/src/systemd/sd-path.h deleted file mode 100644 index be6abdcd03..0000000000 --- a/src/systemd/sd-path.h +++ /dev/null @@ -1,91 +0,0 @@ -#ifndef foosdpathhfoo -#define foosdpathhfoo - -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -enum { - /* Temporary files */ - SD_PATH_TEMPORARY = 0x0ULL, - SD_PATH_TEMPORARY_LARGE, - - /* Vendor supplied data */ - SD_PATH_SYSTEM_BINARIES, - SD_PATH_SYSTEM_INCLUDE, - SD_PATH_SYSTEM_LIBRARY_PRIVATE, - SD_PATH_SYSTEM_LIBRARY_ARCH, - SD_PATH_SYSTEM_SHARED, - SD_PATH_SYSTEM_CONFIGURATION_FACTORY, - SD_PATH_SYSTEM_STATE_FACTORY, - - /* System configuration, runtime, state, ... */ - SD_PATH_SYSTEM_CONFIGURATION, - SD_PATH_SYSTEM_RUNTIME, - SD_PATH_SYSTEM_RUNTIME_LOGS, - SD_PATH_SYSTEM_STATE_PRIVATE, - SD_PATH_SYSTEM_STATE_LOGS, - SD_PATH_SYSTEM_STATE_CACHE, - SD_PATH_SYSTEM_STATE_SPOOL, - - /* Vendor supplied data */ - SD_PATH_USER_BINARIES, - SD_PATH_USER_LIBRARY_PRIVATE, - SD_PATH_USER_LIBRARY_ARCH, - SD_PATH_USER_SHARED, - - /* User configuration, state, runtime ... */ - SD_PATH_USER_CONFIGURATION, /* takes both actual configuration (like /etc) and state (like /var/lib) */ - SD_PATH_USER_RUNTIME, - SD_PATH_USER_STATE_CACHE, - - /* User resources */ - SD_PATH_USER, /* $HOME itself */ - SD_PATH_USER_DOCUMENTS, - SD_PATH_USER_MUSIC, - SD_PATH_USER_PICTURES, - SD_PATH_USER_VIDEOS, - SD_PATH_USER_DOWNLOAD, - SD_PATH_USER_PUBLIC, - SD_PATH_USER_TEMPLATES, - SD_PATH_USER_DESKTOP, - - /* Search paths */ - SD_PATH_SEARCH_BINARIES, - SD_PATH_SEARCH_LIBRARY_PRIVATE, - SD_PATH_SEARCH_LIBRARY_ARCH, - SD_PATH_SEARCH_SHARED, - SD_PATH_SEARCH_CONFIGURATION_FACTORY, - SD_PATH_SEARCH_STATE_FACTORY, - SD_PATH_SEARCH_CONFIGURATION, - - _SD_PATH_MAX, -}; - -int sd_path_home(uint64_t type, const char *suffix, char **path); -int sd_path_search(uint64_t type, const char *suffix, char ***paths); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-resolve.h b/src/systemd/sd-resolve.h deleted file mode 100644 index 903b917f70..0000000000 --- a/src/systemd/sd-resolve.h +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef foosdresolvehfoo -#define foosdresolvehfoo - -/*** - This file is part of systemd. - - Copyright 2005-2014 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 . -***/ - -#include -#include -#include -#include - -#include "sd-event.h" - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -/* An opaque sd-resolve session structure */ -typedef struct sd_resolve sd_resolve; - -/* An opaque sd-resolve query structure */ -typedef struct sd_resolve_query sd_resolve_query; - -/* A callback on completion */ -typedef int (*sd_resolve_getaddrinfo_handler_t)(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata); -typedef int (*sd_resolve_getnameinfo_handler_t)(sd_resolve_query *q, int ret, const char *host, const char *serv, void *userdata); - -enum { - SD_RESOLVE_GET_HOST = UINT64_C(1), - SD_RESOLVE_GET_SERVICE = UINT64_C(2), - SD_RESOLVE_GET_BOTH = UINT64_C(3), -}; - -int sd_resolve_default(sd_resolve **ret); - -/* Allocate a new sd-resolve session. */ -int sd_resolve_new(sd_resolve **ret); - -/* Free a sd-resolve session. This destroys all attached - * sd_resolve_query objects automatically. */ -sd_resolve* sd_resolve_unref(sd_resolve *resolve); -sd_resolve* sd_resolve_ref(sd_resolve *resolve); - -/* Return the UNIX file descriptor to poll() for events on. Use this - * function to integrate sd-resolve with your custom main loop. */ -int sd_resolve_get_fd(sd_resolve *resolve); - -/* Return the poll() events (a combination of flags like POLLIN, - * POLLOUT, ...) to check for. */ -int sd_resolve_get_events(sd_resolve *resolve); - -/* Return the poll() timeout to pass. Returns (uint64_t) -1 as - * timeout if no timeout is needed. */ -int sd_resolve_get_timeout(sd_resolve *resolve, uint64_t *timeout_usec); - -/* Process pending responses. After this function is called, you can - * get the next completed query object(s) using - * sd_resolve_get_next(). */ -int sd_resolve_process(sd_resolve *resolve); - -/* Wait for a resolve event to complete. */ -int sd_resolve_wait(sd_resolve *resolve, uint64_t timeout_usec); - -int sd_resolve_get_tid(sd_resolve *resolve, pid_t *tid); - -int sd_resolve_attach_event(sd_resolve *resolve, sd_event *e, int priority); -int sd_resolve_detach_event(sd_resolve *resolve); -sd_event *sd_resolve_get_event(sd_resolve *resolve); - -/* Issue a name-to-address query on the specified session. The - * arguments are compatible with those of libc's - * getaddrinfo(3). The function returns a new query object. When the - * query is completed, you may retrieve the results using - * sd_resolve_getaddrinfo_done(). */ -int sd_resolve_getaddrinfo(sd_resolve *resolve, sd_resolve_query **q, const char *node, const char *service, const struct addrinfo *hints, sd_resolve_getaddrinfo_handler_t callback, void *userdata); - -/* Issue an address-to-name query on the specified session. The - * arguments are compatible with those of libc's - * getnameinfo(3). The function returns a new query object. When the - * query is completed, you may retrieve the results using - * sd_resolve_getnameinfo_done(). Set gethost (resp. getserv) to non-zero - * if you want to query the hostname (resp. the service name). */ -int sd_resolve_getnameinfo(sd_resolve *resolve, sd_resolve_query **q, const struct sockaddr *sa, socklen_t salen, int flags, uint64_t get, sd_resolve_getnameinfo_handler_t callback, void *userdata); - -sd_resolve_query *sd_resolve_query_ref(sd_resolve_query* q); -sd_resolve_query *sd_resolve_query_unref(sd_resolve_query* q); - -/* Returns non-zero when the query operation specified by q has been completed. */ -int sd_resolve_query_is_done(sd_resolve_query*q); - -void *sd_resolve_query_get_userdata(sd_resolve_query *q); -void *sd_resolve_query_set_userdata(sd_resolve_query *q, void *userdata); - -sd_resolve *sd_resolve_query_get_resolve(sd_resolve_query *q); - -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_resolve, sd_resolve_unref); -_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_resolve_query, sd_resolve_query_unref); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/systemd/sd-utf8.h b/src/systemd/sd-utf8.h deleted file mode 100644 index 6781983878..0000000000 --- a/src/systemd/sd-utf8.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef foosdutf8hfoo -#define foosdutf8hfoo - -/*** - 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 . -***/ - -#include "_sd-common.h" - -_SD_BEGIN_DECLARATIONS; - -_sd_pure_ const char *sd_utf8_is_valid(const char *s); -_sd_pure_ const char *sd_ascii_is_valid(const char *s); - -_SD_END_DECLARATIONS; - -#endif diff --git a/src/sysusers/Makefile b/src/sysusers/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/sysusers/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/sysusers/Makefile b/src/sysusers/Makefile new file mode 100644 index 0000000000..648100e5de --- /dev/null +++ b/src/sysusers/Makefile @@ -0,0 +1,61 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ifneq ($(ENABLE_SYSUSERS),) +systemd_sysusers_SOURCES = \ + src/sysusers/sysusers.c + +systemd_sysusers_LDADD = \ + libshared.la + +rootbin_PROGRAMS += \ + systemd-sysusers + +nodist_systemunit_DATA += \ + units/systemd-sysusers.service + +SYSINIT_TARGET_WANTS += \ + systemd-sysusers.service + +nodist_sysusers_DATA = \ + sysusers.d/systemd.conf \ + sysusers.d/basic.conf + +ifneq ($(HAVE_REMOTE),) +nodist_sysusers_DATA += \ + sysusers.d/systemd-remote.conf +endif + +INSTALL_DIRS += \ + $(sysusersdir) +endif + +EXTRA_DIST += \ + units/systemd-sysusers.service.in \ + sysusers.d/systemd.conf.m4 \ + sysusers.d/systemd-remote.conf.m4 \ + sysusers.d/basic.conf.in + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/sysv-generator/Makefile b/src/sysv-generator/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/sysv-generator/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/sysv-generator/Makefile b/src/sysv-generator/Makefile new file mode 100644 index 0000000000..f6de146408 --- /dev/null +++ b/src/sysv-generator/Makefile @@ -0,0 +1,32 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +systemd_sysv_generator_SOURCES = \ + src/sysv-generator/sysv-generator.c + +systemd_sysv_generator_LDADD = \ + libcore.la + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/test/Makefile b/src/test/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/test/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/test/Makefile b/src/test/Makefile new file mode 100644 index 0000000000..f416b4aadf --- /dev/null +++ b/src/test/Makefile @@ -0,0 +1,35 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +test_id128_SOURCES = \ + src/test/test-id128.c + +test_id128_LDADD = \ + libshared.la + +tests += \ + test-id128 + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/test/test-condition.c b/src/test/test-condition.c index 8903d10db7..c4ff41dd0e 100644 --- a/src/test/test-condition.c +++ b/src/test/test-condition.c @@ -17,7 +17,7 @@ along with systemd; If not, see . ***/ -#include "sd-id128.h" +#include #include "alloc-util.h" #include "apparmor-util.h" diff --git a/src/test/test-daemon.c b/src/test/test-daemon.c index 4ce00f4b1f..d181bee214 100644 --- a/src/test/test-daemon.c +++ b/src/test/test-daemon.c @@ -19,7 +19,7 @@ #include -#include "sd-daemon.h" +#include #include "strv.h" diff --git a/src/test/test-helper.h b/src/test/test-helper.h index ddb10f88fd..01068da347 100644 --- a/src/test/test-helper.h +++ b/src/test/test-helper.h @@ -19,7 +19,7 @@ along with systemd; If not, see . ***/ -#include "sd-daemon.h" +#include #include "macro.h" diff --git a/src/test/test-id128.c b/src/test/test-id128.c index 96aa008c06..90c4860e2a 100644 --- a/src/test/test-id128.c +++ b/src/test/test-id128.c @@ -19,8 +19,8 @@ #include -#include "sd-daemon.h" -#include "sd-id128.h" +#include +#include #include "alloc-util.h" #include "macro.h" diff --git a/src/test/test-netlink-manual.c b/src/test/test-netlink-manual.c index 79ff6ae74d..fe15a56aba 100644 --- a/src/test/test-netlink-manual.c +++ b/src/test/test-netlink-manual.c @@ -23,7 +23,7 @@ #include #include -#include "sd-netlink.h" +#include #include "macro.h" #include "util.h" diff --git a/src/timedate/.gitignore b/src/timedate/.gitignore deleted file mode 100644 index 48757f0968..0000000000 --- a/src/timedate/.gitignore +++ /dev/null @@ -1 +0,0 @@ -org.freedesktop.timedate1.policy diff --git a/src/timedate/Makefile b/src/timedate/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/timedate/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/timedate/org.freedesktop.timedate1.conf b/src/timedate/org.freedesktop.timedate1.conf deleted file mode 100644 index 36557d5841..0000000000 --- a/src/timedate/org.freedesktop.timedate1.conf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/timedate/org.freedesktop.timedate1.policy.in b/src/timedate/org.freedesktop.timedate1.policy.in deleted file mode 100644 index aa30b70831..0000000000 --- a/src/timedate/org.freedesktop.timedate1.policy.in +++ /dev/null @@ -1,62 +0,0 @@ - - - - - - - - The systemd Project - http://www.freedesktop.org/wiki/Software/systemd - - - <_description>Set system time - <_message>Authentication is required to set the system time. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - org.freedesktop.timedate1.set-timezone org.freedesktop.timedate1.set-ntp - - - - <_description>Set system timezone - <_message>Authentication is required to set the system timezone. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - - - - <_description>Set RTC to local timezone or UTC - <_message>Authentication is required to control whether - the RTC stores the local or UTC time. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - - - - <_description>Turn network time synchronization on or off - <_message>Authentication is required to control whether - network time synchronization shall be enabled. - - auth_admin_keep - auth_admin_keep - auth_admin_keep - - - - diff --git a/src/timedate/org.freedesktop.timedate1.service b/src/timedate/org.freedesktop.timedate1.service deleted file mode 100644 index 875f4bec78..0000000000 --- a/src/timedate/org.freedesktop.timedate1.service +++ /dev/null @@ -1,12 +0,0 @@ -# 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. - -[D-BUS Service] -Name=org.freedesktop.timedate1 -Exec=/bin/false -User=root -SystemdService=dbus-org.freedesktop.timedate1.service diff --git a/src/timedate/timedatectl.c b/src/timedate/timedatectl.c deleted file mode 100644 index 097963b41b..0000000000 --- a/src/timedate/timedatectl.c +++ /dev/null @@ -1,514 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2012 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 . -***/ - -#include -#include -#include -#include - -#include "sd-bus.h" - -#include "bus-error.h" -#include "bus-util.h" -#include "pager.h" -#include "parse-util.h" -#include "spawn-polkit-agent.h" -#include "strv.h" -#include "terminal-util.h" -#include "util.h" - -static bool arg_no_pager = false; -static bool arg_ask_password = true; -static BusTransport arg_transport = BUS_TRANSPORT_LOCAL; -static char *arg_host = NULL; -static bool arg_adjust_system_clock = false; - -static void pager_open_if_enabled(void) { - - if (arg_no_pager) - return; - - pager_open(false); -} - -static void polkit_agent_open_if_enabled(void) { - - /* Open the polkit agent as a child process if necessary */ - if (!arg_ask_password) - return; - - if (arg_transport != BUS_TRANSPORT_LOCAL) - return; - - polkit_agent_open(); -} - -typedef struct StatusInfo { - usec_t time; - char *timezone; - - usec_t rtc_time; - bool rtc_local; - - bool ntp_enabled; - bool ntp_capable; - bool ntp_synced; -} StatusInfo; - -static void status_info_clear(StatusInfo *info) { - if (info) { - free(info->timezone); - zero(*info); - } -} - -static void print_status_info(const StatusInfo *i) { - char a[FORMAT_TIMESTAMP_MAX]; - struct tm tm; - time_t sec; - bool have_time = false; - const char *old_tz = NULL, *tz; - int r; - - assert(i); - - /* Save the old $TZ */ - tz = getenv("TZ"); - if (tz) - old_tz = strdupa(tz); - - /* Set the new $TZ */ - if (setenv("TZ", isempty(i->timezone) ? "UTC" : i->timezone, true) < 0) - log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m"); - else - tzset(); - - if (i->time != 0) { - sec = (time_t) (i->time / USEC_PER_SEC); - have_time = true; - } else if (IN_SET(arg_transport, BUS_TRANSPORT_LOCAL, BUS_TRANSPORT_MACHINE)) { - sec = time(NULL); - have_time = true; - } else - log_warning("Could not get time from timedated and not operating locally, ignoring."); - - if (have_time) { - xstrftime(a, "%a %Y-%m-%d %H:%M:%S %Z", localtime_r(&sec, &tm)); - printf(" Local time: %.*s\n", (int) sizeof(a), a); - - xstrftime(a, "%a %Y-%m-%d %H:%M:%S UTC", gmtime_r(&sec, &tm)); - printf(" Universal time: %.*s\n", (int) sizeof(a), a); - } else { - printf(" Local time: %s\n", "n/a"); - printf(" Universal time: %s\n", "n/a"); - } - - if (i->rtc_time > 0) { - time_t rtc_sec; - - rtc_sec = (time_t) (i->rtc_time / USEC_PER_SEC); - xstrftime(a, "%a %Y-%m-%d %H:%M:%S", gmtime_r(&rtc_sec, &tm)); - printf(" RTC time: %.*s\n", (int) sizeof(a), a); - } else - printf(" RTC time: %s\n", "n/a"); - - if (have_time) - xstrftime(a, "%Z, %z", localtime_r(&sec, &tm)); - - /* Restore the $TZ */ - if (old_tz) - r = setenv("TZ", old_tz, true); - else - r = unsetenv("TZ"); - if (r < 0) - log_warning_errno(errno, "Failed to set TZ environment variable, ignoring: %m"); - else - tzset(); - - printf(" Time zone: %s (%.*s)\n" - " Network time on: %s\n" - "NTP synchronized: %s\n" - " RTC in local TZ: %s\n", - strna(i->timezone), (int) sizeof(a), have_time ? a : "n/a", - i->ntp_capable ? yes_no(i->ntp_enabled) : "n/a", - yes_no(i->ntp_synced), - yes_no(i->rtc_local)); - - if (i->rtc_local) - fputs("\n" ANSI_HIGHLIGHT - "Warning: The system is configured to read the RTC time in the local time zone.\n" - " This mode can not be fully supported. It will create various problems\n" - " with time zone changes and daylight saving time adjustments. The RTC\n" - " time is never updated, it relies on external facilities to maintain it.\n" - " If at all possible, use RTC in UTC by calling\n" - " 'timedatectl set-local-rtc 0'." ANSI_NORMAL "\n", stdout); -} - -static int show_status(sd_bus *bus, char **args, unsigned n) { - _cleanup_(status_info_clear) StatusInfo info = {}; - static const struct bus_properties_map map[] = { - { "Timezone", "s", NULL, offsetof(StatusInfo, timezone) }, - { "LocalRTC", "b", NULL, offsetof(StatusInfo, rtc_local) }, - { "NTP", "b", NULL, offsetof(StatusInfo, ntp_enabled) }, - { "CanNTP", "b", NULL, offsetof(StatusInfo, ntp_capable) }, - { "NTPSynchronized", "b", NULL, offsetof(StatusInfo, ntp_synced) }, - { "TimeUSec", "t", NULL, offsetof(StatusInfo, time) }, - { "RTCTimeUSec", "t", NULL, offsetof(StatusInfo, rtc_time) }, - {} - }; - int r; - - assert(bus); - - r = bus_map_all_properties(bus, - "org.freedesktop.timedate1", - "/org/freedesktop/timedate1", - map, - &info); - if (r < 0) - return log_error_errno(r, "Failed to query server: %m"); - - print_status_info(&info); - - return r; -} - -static int set_time(sd_bus *bus, char **args, unsigned n) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - bool relative = false, interactive = arg_ask_password; - usec_t t; - int r; - - assert(args); - assert(n == 2); - - polkit_agent_open_if_enabled(); - - r = parse_timestamp(args[1], &t); - if (r < 0) { - log_error("Failed to parse time specification: %s", args[1]); - return r; - } - - r = sd_bus_call_method(bus, - "org.freedesktop.timedate1", - "/org/freedesktop/timedate1", - "org.freedesktop.timedate1", - "SetTime", - &error, - NULL, - "xbb", (int64_t)t, relative, interactive); - if (r < 0) - log_error("Failed to set time: %s", bus_error_message(&error, -r)); - - return r; -} - -static int set_timezone(sd_bus *bus, char **args, unsigned n) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - - assert(args); - assert(n == 2); - - polkit_agent_open_if_enabled(); - - r = sd_bus_call_method(bus, - "org.freedesktop.timedate1", - "/org/freedesktop/timedate1", - "org.freedesktop.timedate1", - "SetTimezone", - &error, - NULL, - "sb", args[1], arg_ask_password); - if (r < 0) - log_error("Failed to set time zone: %s", bus_error_message(&error, -r)); - - return r; -} - -static int set_local_rtc(sd_bus *bus, char **args, unsigned n) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r, b; - - assert(args); - assert(n == 2); - - polkit_agent_open_if_enabled(); - - b = parse_boolean(args[1]); - if (b < 0) { - log_error("Failed to parse local RTC setting: %s", args[1]); - return b; - } - - r = sd_bus_call_method(bus, - "org.freedesktop.timedate1", - "/org/freedesktop/timedate1", - "org.freedesktop.timedate1", - "SetLocalRTC", - &error, - NULL, - "bbb", b, arg_adjust_system_clock, arg_ask_password); - if (r < 0) - log_error("Failed to set local RTC: %s", bus_error_message(&error, -r)); - - return r; -} - -static int set_ntp(sd_bus *bus, char **args, unsigned n) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int b, r; - - assert(args); - assert(n == 2); - - polkit_agent_open_if_enabled(); - - b = parse_boolean(args[1]); - if (b < 0) { - log_error("Failed to parse NTP setting: %s", args[1]); - return b; - } - - r = sd_bus_call_method(bus, - "org.freedesktop.timedate1", - "/org/freedesktop/timedate1", - "org.freedesktop.timedate1", - "SetNTP", - &error, - NULL, - "bb", b, arg_ask_password); - if (r < 0) - log_error("Failed to set ntp: %s", bus_error_message(&error, -r)); - - return r; -} - -static int list_timezones(sd_bus *bus, char **args, unsigned n) { - _cleanup_strv_free_ char **zones = NULL; - int r; - - assert(args); - assert(n == 1); - - r = get_timezones(&zones); - if (r < 0) - return log_error_errno(r, "Failed to read list of time zones: %m"); - - pager_open_if_enabled(); - strv_print(zones); - - return 0; -} - -static void help(void) { - printf("%s [OPTIONS...] COMMAND ...\n\n" - "Query or change system time and date settings.\n\n" - " -h --help Show this help message\n" - " --version Show package version\n" - " --no-pager Do not pipe output into a pager\n" - " --no-ask-password Do not prompt for password\n" - " -H --host=[USER@]HOST Operate on remote host\n" - " -M --machine=CONTAINER Operate on local container\n" - " --adjust-system-clock Adjust system clock when changing local RTC mode\n\n" - "Commands:\n" - " status Show current time settings\n" - " set-time TIME Set system time\n" - " set-timezone ZONE Set system time zone\n" - " list-timezones Show known time zones\n" - " set-local-rtc BOOL Control whether RTC is in local time\n" - " set-ntp BOOL Enable or disable network time synchronization\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_NO_PAGER, - ARG_ADJUST_SYSTEM_CLOCK, - ARG_NO_ASK_PASSWORD - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, - { "host", required_argument, NULL, 'H' }, - { "machine", required_argument, NULL, 'M' }, - { "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD }, - { "adjust-system-clock", no_argument, NULL, ARG_ADJUST_SYSTEM_CLOCK }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "hH:M:", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case 'H': - arg_transport = BUS_TRANSPORT_REMOTE; - arg_host = optarg; - break; - - case 'M': - arg_transport = BUS_TRANSPORT_MACHINE; - arg_host = optarg; - break; - - case ARG_NO_ASK_PASSWORD: - arg_ask_password = false; - break; - - case ARG_ADJUST_SYSTEM_CLOCK: - arg_adjust_system_clock = true; - break; - - case ARG_NO_PAGER: - arg_no_pager = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - return 1; -} - -static int timedatectl_main(sd_bus *bus, int argc, char *argv[]) { - - static const struct { - const char* verb; - const enum { - MORE, - LESS, - EQUAL - } argc_cmp; - const int argc; - int (* const dispatch)(sd_bus *bus, char **args, unsigned n); - } verbs[] = { - { "status", LESS, 1, show_status }, - { "set-time", EQUAL, 2, set_time }, - { "set-timezone", EQUAL, 2, set_timezone }, - { "list-timezones", EQUAL, 1, list_timezones }, - { "set-local-rtc", EQUAL, 2, set_local_rtc }, - { "set-ntp", EQUAL, 2, set_ntp, }, - }; - - int left; - unsigned i; - - assert(argc >= 0); - assert(argv); - - left = argc - optind; - - if (left <= 0) - /* Special rule: no arguments means "status" */ - i = 0; - else { - if (streq(argv[optind], "help")) { - help(); - return 0; - } - - for (i = 0; i < ELEMENTSOF(verbs); i++) - if (streq(argv[optind], verbs[i].verb)) - break; - - if (i >= ELEMENTSOF(verbs)) { - log_error("Unknown operation %s", argv[optind]); - return -EINVAL; - } - } - - switch (verbs[i].argc_cmp) { - - case EQUAL: - if (left != verbs[i].argc) { - log_error("Invalid number of arguments."); - return -EINVAL; - } - - break; - - case MORE: - if (left < verbs[i].argc) { - log_error("Too few arguments."); - return -EINVAL; - } - - break; - - case LESS: - if (left > verbs[i].argc) { - log_error("Too many arguments."); - return -EINVAL; - } - - break; - - default: - assert_not_reached("Unknown comparison operator."); - } - - return verbs[i].dispatch(bus, argv + optind, left); -} - -int main(int argc, char *argv[]) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - setlocale(LC_ALL, ""); - log_parse_environment(); - log_open(); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - r = bus_connect_transport(arg_transport, arg_host, false, &bus); - if (r < 0) { - log_error_errno(r, "Failed to create bus connection: %m"); - goto finish; - } - - r = timedatectl_main(bus, argc, argv); - -finish: - pager_close(); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/timedate/timedated.c b/src/timedate/timedated.c deleted file mode 100644 index 2a10135fba..0000000000 --- a/src/timedate/timedated.c +++ /dev/null @@ -1,733 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2011 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 . -***/ - -#include -#include -#include - -#include "sd-bus.h" -#include "sd-event.h" -#include "sd-messages.h" - -#include "alloc-util.h" -#include "bus-common-errors.h" -#include "bus-error.h" -#include "bus-util.h" -#include "clock-util.h" -#include "def.h" -#include "fileio-label.h" -#include "fs-util.h" -#include "path-util.h" -#include "selinux-util.h" -#include "strv.h" -#include "user-util.h" -#include "util.h" - -#define NULL_ADJTIME_UTC "0.0 0 0\n0\nUTC\n" -#define NULL_ADJTIME_LOCAL "0.0 0 0\n0\nLOCAL\n" - -static BUS_ERROR_MAP_ELF_REGISTER const sd_bus_error_map timedated_errors[] = { - SD_BUS_ERROR_MAP("org.freedesktop.timedate1.NoNTPSupport", EOPNOTSUPP), - SD_BUS_ERROR_MAP_END -}; - -typedef struct Context { - char *zone; - bool local_rtc; - bool can_ntp; - bool use_ntp; - Hashmap *polkit_registry; -} Context; - -static void context_free(Context *c) { - assert(c); - - free(c->zone); - bus_verify_polkit_async_registry_free(c->polkit_registry); -} - -static int context_read_data(Context *c) { - _cleanup_free_ char *t = NULL; - int r; - - assert(c); - - r = get_timezone(&t); - if (r == -EINVAL) - log_warning_errno(r, "/etc/localtime should be a symbolic link to a time zone data file in /usr/share/zoneinfo/."); - else if (r < 0) - log_warning_errno(r, "Failed to get target of /etc/localtime: %m"); - - free(c->zone); - c->zone = t; - t = NULL; - - c->local_rtc = clock_is_localtime() > 0; - - return 0; -} - -static int context_write_data_timezone(Context *c) { - _cleanup_free_ char *p = NULL; - int r = 0; - - assert(c); - - if (isempty(c->zone)) { - if (unlink("/etc/localtime") < 0 && errno != ENOENT) - r = -errno; - - return r; - } - - p = strappend("../usr/share/zoneinfo/", c->zone); - if (!p) - return log_oom(); - - r = symlink_atomic(p, "/etc/localtime"); - if (r < 0) - return r; - - return 0; -} - -static int context_write_data_local_rtc(Context *c) { - int r; - _cleanup_free_ char *s = NULL, *w = NULL; - - assert(c); - - r = read_full_file("/etc/adjtime", &s, NULL); - if (r < 0) { - if (r != -ENOENT) - return r; - - if (!c->local_rtc) - return 0; - - w = strdup(NULL_ADJTIME_LOCAL); - if (!w) - return -ENOMEM; - } else { - char *p, *e; - size_t a, b; - - p = strchr(s, '\n'); - if (!p) - return -EIO; - - p = strchr(p+1, '\n'); - if (!p) - return -EIO; - - p++; - e = strchr(p, '\n'); - if (!e) - return -EIO; - - a = p - s; - b = strlen(e); - - w = new(char, a + (c->local_rtc ? 5 : 3) + b + 1); - if (!w) - return -ENOMEM; - - *(char*) mempcpy(stpcpy(mempcpy(w, s, a), c->local_rtc ? "LOCAL" : "UTC"), e, b) = 0; - - if (streq(w, NULL_ADJTIME_UTC)) { - if (unlink("/etc/adjtime") < 0) - if (errno != ENOENT) - return -errno; - - return 0; - } - } - - mac_selinux_init("/etc"); - return write_string_file_atomic_label("/etc/adjtime", w); -} - -static int context_read_ntp(Context *c, sd_bus *bus) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; - const char *s; - int r; - - assert(c); - assert(bus); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "GetUnitFileState", - &error, - &reply, - "s", - "systemd-timesyncd.service"); - - if (r < 0) { - if (sd_bus_error_has_name(&error, SD_BUS_ERROR_FILE_NOT_FOUND) || - sd_bus_error_has_name(&error, "org.freedesktop.systemd1.LoadFailed") || - sd_bus_error_has_name(&error, "org.freedesktop.systemd1.NoSuchUnit")) - return 0; - - return r; - } - - r = sd_bus_message_read(reply, "s", &s); - if (r < 0) - return r; - - c->can_ntp = true; - c->use_ntp = STR_IN_SET(s, "enabled", "enabled-runtime"); - - return 0; -} - -static int context_start_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) { - int r; - - assert(bus); - assert(error); - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - enabled ? "StartUnit" : "StopUnit", - error, - NULL, - "ss", - "systemd-timesyncd.service", - "replace"); - if (r < 0) { - if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND) || - sd_bus_error_has_name(error, "org.freedesktop.systemd1.LoadFailed") || - sd_bus_error_has_name(error, "org.freedesktop.systemd1.NoSuchUnit")) - return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported."); - - return r; - } - - return 0; -} - -static int context_enable_ntp(sd_bus *bus, sd_bus_error *error, bool enabled) { - int r; - - assert(bus); - assert(error); - - if (enabled) - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "EnableUnitFiles", - error, - NULL, - "asbb", 1, - "systemd-timesyncd.service", - false, true); - else - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "DisableUnitFiles", - error, - NULL, - "asb", 1, - "systemd-timesyncd.service", - false); - - if (r < 0) { - if (sd_bus_error_has_name(error, SD_BUS_ERROR_FILE_NOT_FOUND)) - return sd_bus_error_set_const(error, "org.freedesktop.timedate1.NoNTPSupport", "NTP not supported."); - - return r; - } - - r = sd_bus_call_method( - bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "Reload", - error, - NULL, - NULL); - if (r < 0) - return r; - - return 0; -} - -static int property_get_rtc_time( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - struct tm tm; - usec_t t; - int r; - - zero(tm); - r = clock_get_hwclock(&tm); - if (r == -EBUSY) { - log_warning("/dev/rtc is busy. Is somebody keeping it open continuously? That's not a good idea... Returning a bogus RTC timestamp."); - t = 0; - } else if (r == -ENOENT) { - log_debug("/dev/rtc not found."); - t = 0; /* no RTC found */ - } else if (r < 0) - return sd_bus_error_set_errnof(error, r, "Failed to read RTC: %m"); - else - t = (usec_t) timegm(&tm) * USEC_PER_SEC; - - return sd_bus_message_append(reply, "t", t); -} - -static int property_get_time( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - return sd_bus_message_append(reply, "t", now(CLOCK_REALTIME)); -} - -static int property_get_ntp_sync( - sd_bus *bus, - const char *path, - const char *interface, - const char *property, - sd_bus_message *reply, - void *userdata, - sd_bus_error *error) { - - return sd_bus_message_append(reply, "b", ntp_synced()); -} - -static int method_set_timezone(sd_bus_message *m, void *userdata, sd_bus_error *error) { - Context *c = userdata; - const char *z; - int interactive; - char *t; - int r; - - assert(m); - assert(c); - - r = sd_bus_message_read(m, "sb", &z, &interactive); - if (r < 0) - return r; - - if (!timezone_is_valid(z)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid time zone '%s'", z); - - if (streq_ptr(z, c->zone)) - return sd_bus_reply_method_return(m, NULL); - - r = bus_verify_polkit_async( - m, - CAP_SYS_TIME, - "org.freedesktop.timedate1.set-timezone", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ - - t = strdup(z); - if (!t) - return -ENOMEM; - - free(c->zone); - c->zone = t; - - /* 1. Write new configuration file */ - r = context_write_data_timezone(c); - if (r < 0) { - log_error_errno(r, "Failed to set time zone: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set time zone: %m"); - } - - /* 2. Tell the kernel our timezone */ - clock_set_timezone(NULL); - - if (c->local_rtc) { - struct timespec ts; - struct tm *tm; - - /* 3. Sync RTC from system clock, with the new delta */ - assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); - assert_se(tm = localtime(&ts.tv_sec)); - clock_set_hwclock(tm); - } - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_TIMEZONE_CHANGE), - "TIMEZONE=%s", c->zone, - LOG_MESSAGE("Changed time zone to '%s'.", c->zone), - NULL); - - (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "Timezone", NULL); - - return sd_bus_reply_method_return(m, NULL); -} - -static int method_set_local_rtc(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int lrtc, fix_system, interactive; - Context *c = userdata; - struct timespec ts; - int r; - - assert(m); - assert(c); - - r = sd_bus_message_read(m, "bbb", &lrtc, &fix_system, &interactive); - if (r < 0) - return r; - - if (lrtc == c->local_rtc) - return sd_bus_reply_method_return(m, NULL); - - r = bus_verify_polkit_async( - m, - CAP_SYS_TIME, - "org.freedesktop.timedate1.set-local-rtc", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; - - c->local_rtc = lrtc; - - /* 1. Write new configuration file */ - r = context_write_data_local_rtc(c); - if (r < 0) { - log_error_errno(r, "Failed to set RTC to local/UTC: %m"); - return sd_bus_error_set_errnof(error, r, "Failed to set RTC to local/UTC: %m"); - } - - /* 2. Tell the kernel our timezone */ - clock_set_timezone(NULL); - - /* 3. Synchronize clocks */ - assert_se(clock_gettime(CLOCK_REALTIME, &ts) == 0); - - if (fix_system) { - struct tm tm; - - /* Sync system clock from RTC; first, - * initialize the timezone fields of - * struct tm. */ - if (c->local_rtc) - tm = *localtime(&ts.tv_sec); - else - tm = *gmtime(&ts.tv_sec); - - /* Override the main fields of - * struct tm, but not the timezone - * fields */ - if (clock_get_hwclock(&tm) >= 0) { - - /* And set the system clock - * with this */ - if (c->local_rtc) - ts.tv_sec = mktime(&tm); - else - ts.tv_sec = timegm(&tm); - - clock_settime(CLOCK_REALTIME, &ts); - } - - } else { - struct tm *tm; - - /* Sync RTC from system clock */ - if (c->local_rtc) - tm = localtime(&ts.tv_sec); - else - tm = gmtime(&ts.tv_sec); - - clock_set_hwclock(tm); - } - - log_info("RTC configured to %s time.", c->local_rtc ? "local" : "UTC"); - - (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "LocalRTC", NULL); - - return sd_bus_reply_method_return(m, NULL); -} - -static int method_set_time(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int relative, interactive; - Context *c = userdata; - int64_t utc; - struct timespec ts; - usec_t start; - struct tm* tm; - int r; - - assert(m); - assert(c); - - if (c->use_ntp) - return sd_bus_error_setf(error, BUS_ERROR_AUTOMATIC_TIME_SYNC_ENABLED, "Automatic time synchronization is enabled"); - - /* this only gets used if dbus does not provide a timestamp */ - start = now(CLOCK_MONOTONIC); - - r = sd_bus_message_read(m, "xbb", &utc, &relative, &interactive); - if (r < 0) - return r; - - if (!relative && utc <= 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid absolute time"); - - if (relative && utc == 0) - return sd_bus_reply_method_return(m, NULL); - - if (relative) { - usec_t n, x; - - n = now(CLOCK_REALTIME); - x = n + utc; - - if ((utc > 0 && x < n) || - (utc < 0 && x > n)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Time value overflow"); - - timespec_store(&ts, x); - } else - timespec_store(&ts, (usec_t) utc); - - r = bus_verify_polkit_async( - m, - CAP_SYS_TIME, - "org.freedesktop.timedate1.set-time", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; - - /* adjust ts for time spent in program */ - r = sd_bus_message_get_monotonic_usec(m, &start); - /* when sd_bus_message_get_monotonic_usec() returns -ENODATA it does not modify &start */ - if (r < 0 && r != -ENODATA) - return r; - - timespec_store(&ts, timespec_load(&ts) + (now(CLOCK_MONOTONIC) - start)); - - /* Set system clock */ - if (clock_settime(CLOCK_REALTIME, &ts) < 0) { - log_error_errno(errno, "Failed to set local time: %m"); - return sd_bus_error_set_errnof(error, errno, "Failed to set local time: %m"); - } - - /* Sync down to RTC */ - if (c->local_rtc) - tm = localtime(&ts.tv_sec); - else - tm = gmtime(&ts.tv_sec); - clock_set_hwclock(tm); - - log_struct(LOG_INFO, - LOG_MESSAGE_ID(SD_MESSAGE_TIME_CHANGE), - "REALTIME="USEC_FMT, timespec_load(&ts), - LOG_MESSAGE("Changed local time to %s", ctime(&ts.tv_sec)), - NULL); - - return sd_bus_reply_method_return(m, NULL); -} - -static int method_set_ntp(sd_bus_message *m, void *userdata, sd_bus_error *error) { - int enabled, interactive; - Context *c = userdata; - int r; - - assert(m); - assert(c); - - r = sd_bus_message_read(m, "bb", &enabled, &interactive); - if (r < 0) - return r; - - if ((bool)enabled == c->use_ntp) - return sd_bus_reply_method_return(m, NULL); - - r = bus_verify_polkit_async( - m, - CAP_SYS_TIME, - "org.freedesktop.timedate1.set-ntp", - NULL, - interactive, - UID_INVALID, - &c->polkit_registry, - error); - if (r < 0) - return r; - if (r == 0) - return 1; - - r = context_enable_ntp(sd_bus_message_get_bus(m), error, enabled); - if (r < 0) - return r; - - r = context_start_ntp(sd_bus_message_get_bus(m), error, enabled); - if (r < 0) - return r; - - c->use_ntp = enabled; - log_info("Set NTP to %s", enabled ? "enabled" : "disabled"); - - (void) sd_bus_emit_properties_changed(sd_bus_message_get_bus(m), "/org/freedesktop/timedate1", "org.freedesktop.timedate1", "NTP", NULL); - - return sd_bus_reply_method_return(m, NULL); -} - -static const sd_bus_vtable timedate_vtable[] = { - SD_BUS_VTABLE_START(0), - SD_BUS_PROPERTY("Timezone", "s", NULL, offsetof(Context, zone), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("LocalRTC", "b", bus_property_get_bool, offsetof(Context, local_rtc), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("CanNTP", "b", bus_property_get_bool, offsetof(Context, can_ntp), 0), - SD_BUS_PROPERTY("NTP", "b", bus_property_get_bool, offsetof(Context, use_ntp), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), - SD_BUS_PROPERTY("NTPSynchronized", "b", property_get_ntp_sync, 0, 0), - SD_BUS_PROPERTY("TimeUSec", "t", property_get_time, 0, 0), - SD_BUS_PROPERTY("RTCTimeUSec", "t", property_get_rtc_time, 0, 0), - SD_BUS_METHOD("SetTime", "xbb", NULL, method_set_time, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetTimezone", "sb", NULL, method_set_timezone, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetLocalRTC", "bbb", NULL, method_set_local_rtc, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_METHOD("SetNTP", "bb", NULL, method_set_ntp, SD_BUS_VTABLE_UNPRIVILEGED), - SD_BUS_VTABLE_END, -}; - -static int connect_bus(Context *c, sd_event *event, sd_bus **_bus) { - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - assert(c); - assert(event); - assert(_bus); - - r = sd_bus_default_system(&bus); - if (r < 0) - return log_error_errno(r, "Failed to get system bus connection: %m"); - - r = sd_bus_add_object_vtable(bus, NULL, "/org/freedesktop/timedate1", "org.freedesktop.timedate1", timedate_vtable, c); - if (r < 0) - return log_error_errno(r, "Failed to register object: %m"); - - r = sd_bus_request_name(bus, "org.freedesktop.timedate1", 0); - if (r < 0) - return log_error_errno(r, "Failed to register name: %m"); - - r = sd_bus_attach_event(bus, event, 0); - if (r < 0) - return log_error_errno(r, "Failed to attach bus to event loop: %m"); - - *_bus = bus; - bus = NULL; - - return 0; -} - -int main(int argc, char *argv[]) { - Context context = {}; - _cleanup_(sd_event_unrefp) sd_event *event = NULL; - _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - if (argc != 1) { - log_error("This program takes no arguments."); - r = -EINVAL; - goto finish; - } - - r = sd_event_default(&event); - if (r < 0) { - log_error_errno(r, "Failed to allocate event loop: %m"); - goto finish; - } - - sd_event_set_watchdog(event, true); - - r = connect_bus(&context, event, &bus); - if (r < 0) - goto finish; - - (void) sd_bus_negotiate_timestamp(bus, true); - - r = context_read_data(&context); - if (r < 0) { - log_error_errno(r, "Failed to read time zone data: %m"); - goto finish; - } - - r = context_read_ntp(&context, bus); - if (r < 0) { - log_error_errno(r, "Failed to determine whether NTP is enabled: %m"); - goto finish; - } - - r = bus_event_loop_with_idle(event, bus, "org.freedesktop.timedate1", DEFAULT_EXIT_USEC, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finish; - } - -finish: - context_free(&context); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/timesync/.gitignore b/src/timesync/.gitignore deleted file mode 100644 index 35f4d76f79..0000000000 --- a/src/timesync/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/timesyncd.conf -/timesyncd-gperf.c diff --git a/src/timesync/Makefile b/src/timesync/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/timesync/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/timesync/timesyncd-conf.c b/src/timesync/timesyncd-conf.c deleted file mode 100644 index 20c64a3354..0000000000 --- a/src/timesync/timesyncd-conf.c +++ /dev/null @@ -1,106 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 . -***/ - -#include "alloc-util.h" -#include "def.h" -#include "extract-word.h" -#include "string-util.h" -#include "timesyncd-conf.h" -#include "timesyncd-manager.h" -#include "timesyncd-server.h" - -int manager_parse_server_string(Manager *m, ServerType type, const char *string) { - ServerName *first; - int r; - - assert(m); - assert(string); - - first = type == SERVER_FALLBACK ? m->fallback_servers : m->system_servers; - - for (;;) { - _cleanup_free_ char *word = NULL; - bool found = false; - ServerName *n; - - r = extract_first_word(&string, &word, NULL, 0); - if (r < 0) - return log_error_errno(r, "Failed to parse timesyncd server syntax \"%s\": %m", string); - if (r == 0) - break; - - /* Filter out duplicates */ - LIST_FOREACH(names, n, first) - if (streq_ptr(n->string, word)) { - found = true; - break; - } - - if (found) - continue; - - r = server_name_new(m, NULL, type, word); - if (r < 0) - return r; - } - - return 0; -} - -int config_parse_servers( - 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) { - - Manager *m = userdata; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - - if (isempty(rvalue)) - manager_flush_server_names(m, ltype); - else { - r = manager_parse_server_string(m, ltype, rvalue); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse NTP server string '%s'. Ignoring.", rvalue); - return 0; - } - } - - return 0; -} - -int manager_parse_config_file(Manager *m) { - assert(m); - - return config_parse_many(PKGSYSCONFDIR "/timesyncd.conf", - CONF_PATHS_NULSTR("systemd/timesyncd.conf.d"), - "Time\0", - config_item_perf_lookup, timesyncd_gperf_lookup, - false, m); -} diff --git a/src/timesync/timesyncd-conf.h b/src/timesync/timesyncd-conf.h deleted file mode 100644 index cba0724b1b..0000000000 --- a/src/timesync/timesyncd-conf.h +++ /dev/null @@ -1,31 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 . -***/ - -#include "conf-parser.h" -#include "timesyncd-manager.h" - -const struct ConfigPerfItem* timesyncd_gperf_lookup(const char *key, unsigned length); - -int manager_parse_server_string(Manager *m, ServerType type, const char *string); - -int config_parse_servers(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_parse_config_file(Manager *m); diff --git a/src/timesync/timesyncd-gperf.gperf b/src/timesync/timesyncd-gperf.gperf deleted file mode 100644 index 29a2cfeef6..0000000000 --- a/src/timesync/timesyncd-gperf.gperf +++ /dev/null @@ -1,19 +0,0 @@ -%{ -#include -#include "conf-parser.h" -#include "timesyncd-conf.h" -%} -struct ConfigPerfItem; -%null_strings -%language=ANSI-C -%define slot-name section_and_lvalue -%define hash-function-name timesyncdd_gperf_hash -%define lookup-function-name timesyncd_gperf_lookup -%readonly-tables -%omit-struct-type -%struct-type -%includes -%% -Time.NTP, config_parse_servers, SERVER_SYSTEM, 0 -Time.Servers, config_parse_servers, SERVER_SYSTEM, 0 -Time.FallbackNTP, config_parse_servers, SERVER_FALLBACK, 0 diff --git a/src/timesync/timesyncd-manager.c b/src/timesync/timesyncd-manager.c deleted file mode 100644 index d5e16db3a0..0000000000 --- a/src/timesync/timesyncd-manager.c +++ /dev/null @@ -1,1156 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "sd-daemon.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "list.h" -#include "log.h" -#include "missing.h" -#include "network-util.h" -#include "ratelimit.h" -#include "socket-util.h" -#include "sparse-endian.h" -#include "string-util.h" -#include "strv.h" -#include "time-util.h" -#include "timesyncd-conf.h" -#include "timesyncd-manager.h" -#include "util.h" - -#ifndef ADJ_SETOFFSET -#define ADJ_SETOFFSET 0x0100 /* add 'time' to current time */ -#endif - -/* expected accuracy of time synchronization; used to adjust the poll interval */ -#define NTP_ACCURACY_SEC 0.2 - -/* - * "A client MUST NOT under any conditions use a poll interval less - * than 15 seconds." - */ -#define NTP_POLL_INTERVAL_MIN_SEC 32 -#define NTP_POLL_INTERVAL_MAX_SEC 2048 - -/* - * Maximum delta in seconds which the system clock is gradually adjusted - * (slew) to approach the network time. Deltas larger that this are set by - * letting the system time jump. The kernel's limit for adjtime is 0.5s. - */ -#define NTP_MAX_ADJUST 0.4 - -/* NTP protocol, packet header */ -#define NTP_LEAP_PLUSSEC 1 -#define NTP_LEAP_MINUSSEC 2 -#define NTP_LEAP_NOTINSYNC 3 -#define NTP_MODE_CLIENT 3 -#define NTP_MODE_SERVER 4 -#define NTP_FIELD_LEAP(f) (((f) >> 6) & 3) -#define NTP_FIELD_VERSION(f) (((f) >> 3) & 7) -#define NTP_FIELD_MODE(f) ((f) & 7) -#define NTP_FIELD(l, v, m) (((l) << 6) | ((v) << 3) | (m)) - -/* Maximum acceptable root distance in seconds. */ -#define NTP_MAX_ROOT_DISTANCE 5.0 - -/* Maximum number of missed replies before selecting another source. */ -#define NTP_MAX_MISSED_REPLIES 2 - -/* - * "NTP timestamps are represented as a 64-bit unsigned fixed-point number, - * in seconds relative to 0h on 1 January 1900." - */ -#define OFFSET_1900_1970 UINT64_C(2208988800) - -#define RETRY_USEC (30*USEC_PER_SEC) -#define RATELIMIT_INTERVAL_USEC (10*USEC_PER_SEC) -#define RATELIMIT_BURST 10 - -#define TIMEOUT_USEC (10*USEC_PER_SEC) - -struct ntp_ts { - be32_t sec; - be32_t frac; -} _packed_; - -struct ntp_ts_short { - be16_t sec; - be16_t frac; -} _packed_; - -struct ntp_msg { - uint8_t field; - uint8_t stratum; - int8_t poll; - int8_t precision; - struct ntp_ts_short root_delay; - struct ntp_ts_short root_dispersion; - char refid[4]; - struct ntp_ts reference_time; - struct ntp_ts origin_time; - struct ntp_ts recv_time; - struct ntp_ts trans_time; -} _packed_; - -static int manager_arm_timer(Manager *m, usec_t next); -static int manager_clock_watch_setup(Manager *m); -static int manager_listen_setup(Manager *m); -static void manager_listen_stop(Manager *m); - -static double ntp_ts_short_to_d(const struct ntp_ts_short *ts) { - return be16toh(ts->sec) + (be16toh(ts->frac) / 65536.0); -} - -static double ntp_ts_to_d(const struct ntp_ts *ts) { - return be32toh(ts->sec) + ((double)be32toh(ts->frac) / UINT_MAX); -} - -static double ts_to_d(const struct timespec *ts) { - return ts->tv_sec + (1.0e-9 * ts->tv_nsec); -} - -static int manager_timeout(sd_event_source *source, usec_t usec, void *userdata) { - _cleanup_free_ char *pretty = NULL; - Manager *m = userdata; - - assert(m); - assert(m->current_server_name); - assert(m->current_server_address); - - server_address_pretty(m->current_server_address, &pretty); - log_info("Timed out waiting for reply from %s (%s).", strna(pretty), m->current_server_name->string); - - return manager_connect(m); -} - -static int manager_send_request(Manager *m) { - _cleanup_free_ char *pretty = NULL; - struct ntp_msg ntpmsg = { - /* - * "The client initializes the NTP message header, sends the request - * to the server, and strips the time of day from the Transmit - * Timestamp field of the reply. For this purpose, all the NTP - * header fields are set to 0, except the Mode, VN, and optional - * Transmit Timestamp fields." - */ - .field = NTP_FIELD(0, 4, NTP_MODE_CLIENT), - }; - ssize_t len; - int r; - - assert(m); - assert(m->current_server_name); - assert(m->current_server_address); - - m->event_timeout = sd_event_source_unref(m->event_timeout); - - r = manager_listen_setup(m); - if (r < 0) - return log_warning_errno(r, "Failed to setup connection socket: %m"); - - /* - * Set transmit timestamp, remember it; the server will send that back - * as the origin timestamp and we have an indication that this is the - * matching answer to our request. - * - * The actual value does not matter, We do not care about the correct - * NTP UINT_MAX fraction; we just pass the plain nanosecond value. - */ - assert_se(clock_gettime(clock_boottime_or_monotonic(), &m->trans_time_mon) >= 0); - assert_se(clock_gettime(CLOCK_REALTIME, &m->trans_time) >= 0); - ntpmsg.trans_time.sec = htobe32(m->trans_time.tv_sec + OFFSET_1900_1970); - ntpmsg.trans_time.frac = htobe32(m->trans_time.tv_nsec); - - server_address_pretty(m->current_server_address, &pretty); - - len = sendto(m->server_socket, &ntpmsg, sizeof(ntpmsg), MSG_DONTWAIT, &m->current_server_address->sockaddr.sa, m->current_server_address->socklen); - if (len == sizeof(ntpmsg)) { - m->pending = true; - log_debug("Sent NTP request to %s (%s).", strna(pretty), m->current_server_name->string); - } else { - log_debug_errno(errno, "Sending NTP request to %s (%s) failed: %m", strna(pretty), m->current_server_name->string); - return manager_connect(m); - } - - /* re-arm timer with increasing timeout, in case the packets never arrive back */ - if (m->retry_interval > 0) { - if (m->retry_interval < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC) - m->retry_interval *= 2; - } else - m->retry_interval = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; - - r = manager_arm_timer(m, m->retry_interval); - if (r < 0) - return log_error_errno(r, "Failed to rearm timer: %m"); - - m->missed_replies++; - if (m->missed_replies > NTP_MAX_MISSED_REPLIES) { - r = sd_event_add_time( - m->event, - &m->event_timeout, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + TIMEOUT_USEC, 0, - manager_timeout, m); - if (r < 0) - return log_error_errno(r, "Failed to arm timeout timer: %m"); - } - - return 0; -} - -static int manager_timer(sd_event_source *source, usec_t usec, void *userdata) { - Manager *m = userdata; - - assert(m); - - return manager_send_request(m); -} - -static int manager_arm_timer(Manager *m, usec_t next) { - int r; - - assert(m); - - if (next == 0) { - m->event_timer = sd_event_source_unref(m->event_timer); - return 0; - } - - if (m->event_timer) { - r = sd_event_source_set_time(m->event_timer, now(clock_boottime_or_monotonic()) + next); - if (r < 0) - return r; - - return sd_event_source_set_enabled(m->event_timer, SD_EVENT_ONESHOT); - } - - return sd_event_add_time( - m->event, - &m->event_timer, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()) + next, 0, - manager_timer, m); -} - -static int manager_clock_watch(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - - assert(m); - - /* rearm timer */ - manager_clock_watch_setup(m); - - /* skip our own jumps */ - if (m->jumped) { - m->jumped = false; - return 0; - } - - /* resync */ - log_debug("System time changed. Resyncing."); - m->poll_resync = true; - - return manager_send_request(m); -} - -/* wake up when the system time changes underneath us */ -static int manager_clock_watch_setup(Manager *m) { - - struct itimerspec its = { - .it_value.tv_sec = TIME_T_MAX - }; - - int r; - - assert(m); - - m->event_clock_watch = sd_event_source_unref(m->event_clock_watch); - safe_close(m->clock_watch_fd); - - m->clock_watch_fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC); - if (m->clock_watch_fd < 0) - return log_error_errno(errno, "Failed to create timerfd: %m"); - - if (timerfd_settime(m->clock_watch_fd, TFD_TIMER_ABSTIME|TFD_TIMER_CANCEL_ON_SET, &its, NULL) < 0) - return log_error_errno(errno, "Failed to set up timerfd: %m"); - - r = sd_event_add_io(m->event, &m->event_clock_watch, m->clock_watch_fd, EPOLLIN, manager_clock_watch, m); - if (r < 0) - return log_error_errno(r, "Failed to create clock watch event source: %m"); - - return 0; -} - -static int manager_adjust_clock(Manager *m, double offset, int leap_sec) { - struct timex tmx = {}; - int r; - - assert(m); - - /* - * For small deltas, tell the kernel to gradually adjust the system - * clock to the NTP time, larger deltas are just directly set. - */ - if (fabs(offset) < NTP_MAX_ADJUST) { - tmx.modes = ADJ_STATUS | ADJ_NANO | ADJ_OFFSET | ADJ_TIMECONST | ADJ_MAXERROR | ADJ_ESTERROR; - tmx.status = STA_PLL; - tmx.offset = offset * NSEC_PER_SEC; - tmx.constant = log2i(m->poll_interval_usec / USEC_PER_SEC) - 4; - tmx.maxerror = 0; - tmx.esterror = 0; - log_debug(" adjust (slew): %+.3f sec", offset); - } else { - tmx.modes = ADJ_STATUS | ADJ_NANO | ADJ_SETOFFSET; - - /* ADJ_NANO uses nanoseconds in the microseconds field */ - tmx.time.tv_sec = (long)offset; - tmx.time.tv_usec = (offset - tmx.time.tv_sec) * NSEC_PER_SEC; - - /* the kernel expects -0.3s as {-1, 7000.000.000} */ - if (tmx.time.tv_usec < 0) { - tmx.time.tv_sec -= 1; - tmx.time.tv_usec += NSEC_PER_SEC; - } - - m->jumped = true; - log_debug(" adjust (jump): %+.3f sec", offset); - } - - /* - * An unset STA_UNSYNC will enable the kernel's 11-minute mode, - * which syncs the system time periodically to the RTC. - * - * In case the RTC runs in local time, never touch the RTC, - * we have no way to properly handle daylight saving changes and - * mobile devices moving between time zones. - */ - if (m->rtc_local_time) - tmx.status |= STA_UNSYNC; - - switch (leap_sec) { - case 1: - tmx.status |= STA_INS; - break; - case -1: - tmx.status |= STA_DEL; - break; - } - - r = clock_adjtime(CLOCK_REALTIME, &tmx); - if (r < 0) - return -errno; - - /* If touch fails, there isn't much we can do. Maybe it'll work next time. */ - (void) touch("/var/lib/systemd/clock"); - - m->drift_ppm = tmx.freq / 65536; - - log_debug(" status : %04i %s\n" - " time now : %li.%03llu\n" - " constant : %li\n" - " offset : %+.3f sec\n" - " freq offset : %+li (%i ppm)\n", - tmx.status, tmx.status & STA_UNSYNC ? "unsync" : "sync", - tmx.time.tv_sec, (unsigned long long) (tmx.time.tv_usec / NSEC_PER_MSEC), - tmx.constant, - (double)tmx.offset / NSEC_PER_SEC, - tmx.freq, m->drift_ppm); - - return 0; -} - -static bool manager_sample_spike_detection(Manager *m, double offset, double delay) { - unsigned int i, idx_cur, idx_new, idx_min; - double jitter; - double j; - - assert(m); - - m->packet_count++; - - /* ignore initial sample */ - if (m->packet_count == 1) - return false; - - /* store the current data in our samples array */ - idx_cur = m->samples_idx; - idx_new = (idx_cur + 1) % ELEMENTSOF(m->samples); - m->samples_idx = idx_new; - m->samples[idx_new].offset = offset; - m->samples[idx_new].delay = delay; - - /* calculate new jitter value from the RMS differences relative to the lowest delay sample */ - jitter = m->samples_jitter; - for (idx_min = idx_cur, i = 0; i < ELEMENTSOF(m->samples); i++) - if (m->samples[i].delay > 0 && m->samples[i].delay < m->samples[idx_min].delay) - idx_min = i; - - j = 0; - for (i = 0; i < ELEMENTSOF(m->samples); i++) - j += pow(m->samples[i].offset - m->samples[idx_min].offset, 2); - m->samples_jitter = sqrt(j / (ELEMENTSOF(m->samples) - 1)); - - /* ignore samples when resyncing */ - if (m->poll_resync) - return false; - - /* always accept offset if we are farther off than the round-trip delay */ - if (fabs(offset) > delay) - return false; - - /* we need a few samples before looking at them */ - if (m->packet_count < 4) - return false; - - /* do not accept anything worse than the maximum possible error of the best sample */ - if (fabs(offset) > m->samples[idx_min].delay) - return true; - - /* compare the difference between the current offset to the previous offset and jitter */ - return fabs(offset - m->samples[idx_cur].offset) > 3 * jitter; -} - -static void manager_adjust_poll(Manager *m, double offset, bool spike) { - assert(m); - - if (m->poll_resync) { - m->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; - m->poll_resync = false; - return; - } - - /* set to minimal poll interval */ - if (!spike && fabs(offset) > NTP_ACCURACY_SEC) { - m->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; - return; - } - - /* increase polling interval */ - if (fabs(offset) < NTP_ACCURACY_SEC * 0.25) { - if (m->poll_interval_usec < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC) - m->poll_interval_usec *= 2; - return; - } - - /* decrease polling interval */ - if (spike || fabs(offset) > NTP_ACCURACY_SEC * 0.75) { - if (m->poll_interval_usec > NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC) - m->poll_interval_usec /= 2; - return; - } -} - -static int manager_receive_response(sd_event_source *source, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - struct ntp_msg ntpmsg; - - struct iovec iov = { - .iov_base = &ntpmsg, - .iov_len = sizeof(ntpmsg), - }; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(struct timeval))]; - } control; - union sockaddr_union server_addr; - struct msghdr msghdr = { - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - .msg_name = &server_addr, - .msg_namelen = sizeof(server_addr), - }; - struct cmsghdr *cmsg; - struct timespec *recv_time; - ssize_t len; - double origin, receive, trans, dest; - double delay, offset; - double root_distance; - bool spike; - int leap_sec; - int r; - - assert(source); - assert(m); - - if (revents & (EPOLLHUP|EPOLLERR)) { - log_warning("Server connection returned error."); - return manager_connect(m); - } - - len = recvmsg(fd, &msghdr, MSG_DONTWAIT); - if (len < 0) { - if (errno == EAGAIN) - return 0; - - log_warning("Error receiving message. Disconnecting."); - return manager_connect(m); - } - - /* Too short or too long packet? */ - if (iov.iov_len < sizeof(struct ntp_msg) || (msghdr.msg_flags & MSG_TRUNC)) { - log_warning("Invalid response from server. Disconnecting."); - return manager_connect(m); - } - - if (!m->current_server_name || - !m->current_server_address || - !sockaddr_equal(&server_addr, &m->current_server_address->sockaddr)) { - log_debug("Response from unknown server."); - return 0; - } - - recv_time = NULL; - CMSG_FOREACH(cmsg, &msghdr) { - if (cmsg->cmsg_level != SOL_SOCKET) - continue; - - switch (cmsg->cmsg_type) { - case SCM_TIMESTAMPNS: - recv_time = (struct timespec *) CMSG_DATA(cmsg); - break; - } - } - if (!recv_time) { - log_error("Invalid packet timestamp."); - return -EINVAL; - } - - if (!m->pending) { - log_debug("Unexpected reply. Ignoring."); - return 0; - } - - m->missed_replies = 0; - - /* check our "time cookie" (we just stored nanoseconds in the fraction field) */ - if (be32toh(ntpmsg.origin_time.sec) != m->trans_time.tv_sec + OFFSET_1900_1970 || - be32toh(ntpmsg.origin_time.frac) != m->trans_time.tv_nsec) { - log_debug("Invalid reply; not our transmit time. Ignoring."); - return 0; - } - - m->event_timeout = sd_event_source_unref(m->event_timeout); - - if (be32toh(ntpmsg.recv_time.sec) < TIME_EPOCH + OFFSET_1900_1970 || - be32toh(ntpmsg.trans_time.sec) < TIME_EPOCH + OFFSET_1900_1970) { - log_debug("Invalid reply, returned times before epoch. Ignoring."); - return manager_connect(m); - } - - if (NTP_FIELD_LEAP(ntpmsg.field) == NTP_LEAP_NOTINSYNC || - ntpmsg.stratum == 0 || ntpmsg.stratum >= 16) { - log_debug("Server is not synchronized. Disconnecting."); - return manager_connect(m); - } - - if (!IN_SET(NTP_FIELD_VERSION(ntpmsg.field), 3, 4)) { - log_debug("Response NTPv%d. Disconnecting.", NTP_FIELD_VERSION(ntpmsg.field)); - return manager_connect(m); - } - - if (NTP_FIELD_MODE(ntpmsg.field) != NTP_MODE_SERVER) { - log_debug("Unsupported mode %d. Disconnecting.", NTP_FIELD_MODE(ntpmsg.field)); - return manager_connect(m); - } - - root_distance = ntp_ts_short_to_d(&ntpmsg.root_delay) / 2 + ntp_ts_short_to_d(&ntpmsg.root_dispersion); - if (root_distance > NTP_MAX_ROOT_DISTANCE) { - log_debug("Server has too large root distance. Disconnecting."); - return manager_connect(m); - } - - /* valid packet */ - m->pending = false; - m->retry_interval = 0; - - /* Stop listening */ - manager_listen_stop(m); - - /* announce leap seconds */ - if (NTP_FIELD_LEAP(ntpmsg.field) & NTP_LEAP_PLUSSEC) - leap_sec = 1; - else if (NTP_FIELD_LEAP(ntpmsg.field) & NTP_LEAP_MINUSSEC) - leap_sec = -1; - else - leap_sec = 0; - - /* - * "Timestamp Name ID When Generated - * ------------------------------------------------------------ - * Originate Timestamp T1 time request sent by client - * Receive Timestamp T2 time request received by server - * Transmit Timestamp T3 time reply sent by server - * Destination Timestamp T4 time reply received by client - * - * The round-trip delay, d, and system clock offset, t, are defined as: - * d = (T4 - T1) - (T3 - T2) t = ((T2 - T1) + (T3 - T4)) / 2" - */ - origin = ts_to_d(&m->trans_time) + OFFSET_1900_1970; - receive = ntp_ts_to_d(&ntpmsg.recv_time); - trans = ntp_ts_to_d(&ntpmsg.trans_time); - dest = ts_to_d(recv_time) + OFFSET_1900_1970; - - offset = ((receive - origin) + (trans - dest)) / 2; - delay = (dest - origin) - (trans - receive); - - spike = manager_sample_spike_detection(m, offset, delay); - - manager_adjust_poll(m, offset, spike); - - log_debug("NTP response:\n" - " leap : %u\n" - " version : %u\n" - " mode : %u\n" - " stratum : %u\n" - " precision : %.6f sec (%d)\n" - " root distance: %.6f sec\n" - " reference : %.4s\n" - " origin : %.3f\n" - " receive : %.3f\n" - " transmit : %.3f\n" - " dest : %.3f\n" - " offset : %+.3f sec\n" - " delay : %+.3f sec\n" - " packet count : %"PRIu64"\n" - " jitter : %.3f%s\n" - " poll interval: " USEC_FMT "\n", - NTP_FIELD_LEAP(ntpmsg.field), - NTP_FIELD_VERSION(ntpmsg.field), - NTP_FIELD_MODE(ntpmsg.field), - ntpmsg.stratum, - exp2(ntpmsg.precision), ntpmsg.precision, - root_distance, - ntpmsg.stratum == 1 ? ntpmsg.refid : "n/a", - origin - OFFSET_1900_1970, - receive - OFFSET_1900_1970, - trans - OFFSET_1900_1970, - dest - OFFSET_1900_1970, - offset, delay, - m->packet_count, - m->samples_jitter, spike ? " spike" : "", - m->poll_interval_usec / USEC_PER_SEC); - - if (!spike) { - m->sync = true; - r = manager_adjust_clock(m, offset, leap_sec); - if (r < 0) - log_error_errno(r, "Failed to call clock_adjtime(): %m"); - } - - log_debug("interval/delta/delay/jitter/drift " USEC_FMT "s/%+.3fs/%.3fs/%.3fs/%+ippm%s", - m->poll_interval_usec / USEC_PER_SEC, offset, delay, m->samples_jitter, m->drift_ppm, - spike ? " (ignored)" : ""); - - if (!m->good) { - _cleanup_free_ char *pretty = NULL; - - m->good = true; - - server_address_pretty(m->current_server_address, &pretty); - log_info("Synchronized to time server %s (%s).", strna(pretty), m->current_server_name->string); - sd_notifyf(false, "STATUS=Synchronized to time server %s (%s).", strna(pretty), m->current_server_name->string); - } - - r = manager_arm_timer(m, m->poll_interval_usec); - if (r < 0) - return log_error_errno(r, "Failed to rearm timer: %m"); - - return 0; -} - -static int manager_listen_setup(Manager *m) { - union sockaddr_union addr = {}; - static const int tos = IPTOS_LOWDELAY; - static const int on = 1; - int r; - - assert(m); - - if (m->server_socket >= 0) - return 0; - - assert(!m->event_receive); - assert(m->current_server_address); - - addr.sa.sa_family = m->current_server_address->sockaddr.sa.sa_family; - - m->server_socket = socket(addr.sa.sa_family, SOCK_DGRAM | SOCK_CLOEXEC, 0); - if (m->server_socket < 0) - return -errno; - - r = bind(m->server_socket, &addr.sa, m->current_server_address->socklen); - if (r < 0) - return -errno; - - r = setsockopt(m->server_socket, SOL_SOCKET, SO_TIMESTAMPNS, &on, sizeof(on)); - if (r < 0) - return -errno; - - (void) setsockopt(m->server_socket, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); - - return sd_event_add_io(m->event, &m->event_receive, m->server_socket, EPOLLIN, manager_receive_response, m); -} - -static void manager_listen_stop(Manager *m) { - assert(m); - - m->event_receive = sd_event_source_unref(m->event_receive); - m->server_socket = safe_close(m->server_socket); -} - -static int manager_begin(Manager *m) { - _cleanup_free_ char *pretty = NULL; - int r; - - assert(m); - assert_return(m->current_server_name, -EHOSTUNREACH); - assert_return(m->current_server_address, -EHOSTUNREACH); - - m->good = false; - m->missed_replies = NTP_MAX_MISSED_REPLIES; - if (m->poll_interval_usec == 0) - m->poll_interval_usec = NTP_POLL_INTERVAL_MIN_SEC * USEC_PER_SEC; - - server_address_pretty(m->current_server_address, &pretty); - log_debug("Connecting to time server %s (%s).", strna(pretty), m->current_server_name->string); - sd_notifyf(false, "STATUS=Connecting to time server %s (%s).", strna(pretty), m->current_server_name->string); - - r = manager_clock_watch_setup(m); - if (r < 0) - return r; - - return manager_send_request(m); -} - -void manager_set_server_name(Manager *m, ServerName *n) { - assert(m); - - if (m->current_server_name == n) - return; - - m->current_server_name = n; - m->current_server_address = NULL; - - manager_disconnect(m); - - if (n) - log_debug("Selected server %s.", n->string); -} - -void manager_set_server_address(Manager *m, ServerAddress *a) { - assert(m); - - if (m->current_server_address == a) - return; - - m->current_server_address = a; - /* If a is NULL, we are just clearing the address, without - * changing the name. Keep the existing name in that case. */ - if (a) - m->current_server_name = a->name; - - manager_disconnect(m); - - if (a) { - _cleanup_free_ char *pretty = NULL; - server_address_pretty(a, &pretty); - log_debug("Selected address %s of server %s.", strna(pretty), a->name->string); - } -} - -static int manager_resolve_handler(sd_resolve_query *q, int ret, const struct addrinfo *ai, void *userdata) { - Manager *m = userdata; - int r; - - assert(q); - assert(m); - assert(m->current_server_name); - - m->resolve_query = sd_resolve_query_unref(m->resolve_query); - - if (ret != 0) { - log_debug("Failed to resolve %s: %s", m->current_server_name->string, gai_strerror(ret)); - - /* Try next host */ - return manager_connect(m); - } - - for (; ai; ai = ai->ai_next) { - _cleanup_free_ char *pretty = NULL; - ServerAddress *a; - - assert(ai->ai_addr); - assert(ai->ai_addrlen >= offsetof(struct sockaddr, sa_data)); - - if (!IN_SET(ai->ai_addr->sa_family, AF_INET, AF_INET6)) { - log_warning("Unsuitable address protocol for %s", m->current_server_name->string); - continue; - } - - r = server_address_new(m->current_server_name, &a, (const union sockaddr_union*) ai->ai_addr, ai->ai_addrlen); - if (r < 0) - return log_error_errno(r, "Failed to add server address: %m"); - - server_address_pretty(a, &pretty); - log_debug("Resolved address %s for %s.", pretty, m->current_server_name->string); - } - - if (!m->current_server_name->addresses) { - log_error("Failed to find suitable address for host %s.", m->current_server_name->string); - - /* Try next host */ - return manager_connect(m); - } - - manager_set_server_address(m, m->current_server_name->addresses); - - return manager_begin(m); -} - -static int manager_retry_connect(sd_event_source *source, usec_t usec, void *userdata) { - Manager *m = userdata; - - assert(m); - - return manager_connect(m); -} - -int manager_connect(Manager *m) { - int r; - - assert(m); - - manager_disconnect(m); - - m->event_retry = sd_event_source_unref(m->event_retry); - if (!ratelimit_test(&m->ratelimit)) { - log_debug("Slowing down attempts to contact servers."); - - r = sd_event_add_time(m->event, &m->event_retry, clock_boottime_or_monotonic(), now(clock_boottime_or_monotonic()) + RETRY_USEC, 0, manager_retry_connect, m); - if (r < 0) - return log_error_errno(r, "Failed to create retry timer: %m"); - - return 0; - } - - /* If we already are operating on some address, switch to the - * next one. */ - if (m->current_server_address && m->current_server_address->addresses_next) - manager_set_server_address(m, m->current_server_address->addresses_next); - else { - struct addrinfo hints = { - .ai_flags = AI_NUMERICSERV|AI_ADDRCONFIG, - .ai_socktype = SOCK_DGRAM, - }; - - /* Hmm, we are through all addresses, let's look for the next host instead */ - if (m->current_server_name && m->current_server_name->names_next) - manager_set_server_name(m, m->current_server_name->names_next); - else { - ServerName *f; - bool restart = true; - - /* Our current server name list is exhausted, - * let's find the next one to iterate. First - * we try the system list, then the link list. - * After having processed the link list we - * jump back to the system list. However, if - * both lists are empty, we change to the - * fallback list. */ - if (!m->current_server_name || m->current_server_name->type == SERVER_LINK) { - f = m->system_servers; - if (!f) - f = m->link_servers; - } else { - f = m->link_servers; - if (!f) - f = m->system_servers; - else - restart = false; - } - - if (!f) - f = m->fallback_servers; - - if (!f) { - manager_set_server_name(m, NULL); - log_debug("No server found."); - return 0; - } - - if (restart && !m->exhausted_servers && m->poll_interval_usec) { - log_debug("Waiting after exhausting servers."); - r = sd_event_add_time(m->event, &m->event_retry, clock_boottime_or_monotonic(), now(clock_boottime_or_monotonic()) + m->poll_interval_usec, 0, manager_retry_connect, m); - if (r < 0) - return log_error_errno(r, "Failed to create retry timer: %m"); - - m->exhausted_servers = true; - - /* Increase the polling interval */ - if (m->poll_interval_usec < NTP_POLL_INTERVAL_MAX_SEC * USEC_PER_SEC) - m->poll_interval_usec *= 2; - - return 0; - } - - m->exhausted_servers = false; - - manager_set_server_name(m, f); - } - - /* Tell the resolver to reread /etc/resolv.conf, in - * case it changed. */ - res_init(); - - /* Flush out any previously resolved addresses */ - server_name_flush_addresses(m->current_server_name); - - log_debug("Resolving %s...", m->current_server_name->string); - - r = sd_resolve_getaddrinfo(m->resolve, &m->resolve_query, m->current_server_name->string, "123", &hints, manager_resolve_handler, m); - if (r < 0) - return log_error_errno(r, "Failed to create resolver: %m"); - - return 1; - } - - r = manager_begin(m); - if (r < 0) - return r; - - return 1; -} - -void manager_disconnect(Manager *m) { - assert(m); - - m->resolve_query = sd_resolve_query_unref(m->resolve_query); - - m->event_timer = sd_event_source_unref(m->event_timer); - - manager_listen_stop(m); - - m->event_clock_watch = sd_event_source_unref(m->event_clock_watch); - m->clock_watch_fd = safe_close(m->clock_watch_fd); - - m->event_timeout = sd_event_source_unref(m->event_timeout); - - sd_notifyf(false, "STATUS=Idle."); -} - -void manager_flush_server_names(Manager *m, ServerType t) { - assert(m); - - if (t == SERVER_SYSTEM) - while (m->system_servers) - server_name_free(m->system_servers); - - if (t == SERVER_LINK) - while (m->link_servers) - server_name_free(m->link_servers); - - if (t == SERVER_FALLBACK) - while (m->fallback_servers) - server_name_free(m->fallback_servers); -} - -void manager_free(Manager *m) { - if (!m) - return; - - manager_disconnect(m); - manager_flush_server_names(m, SERVER_SYSTEM); - manager_flush_server_names(m, SERVER_LINK); - manager_flush_server_names(m, SERVER_FALLBACK); - - sd_event_source_unref(m->event_retry); - - sd_event_source_unref(m->network_event_source); - sd_network_monitor_unref(m->network_monitor); - - sd_resolve_unref(m->resolve); - sd_event_unref(m->event); - - free(m); -} - -static int manager_network_read_link_servers(Manager *m) { - _cleanup_strv_free_ char **ntp = NULL; - ServerName *n, *nx; - char **i; - int r; - - assert(m); - - r = sd_network_get_ntp(&ntp); - if (r < 0) - goto clear; - - LIST_FOREACH(names, n, m->link_servers) - n->marked = true; - - STRV_FOREACH(i, ntp) { - bool found = false; - - LIST_FOREACH(names, n, m->link_servers) - if (streq(n->string, *i)) { - n->marked = false; - found = true; - break; - } - - if (!found) { - r = server_name_new(m, NULL, SERVER_LINK, *i); - if (r < 0) - goto clear; - } - } - - LIST_FOREACH_SAFE(names, n, nx, m->link_servers) - if (n->marked) - server_name_free(n); - - return 0; - -clear: - manager_flush_server_names(m, SERVER_LINK); - return r; -} - -static int manager_network_event_handler(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - Manager *m = userdata; - bool connected, online; - int r; - - assert(m); - - sd_network_monitor_flush(m->network_monitor); - - manager_network_read_link_servers(m); - - /* check if the machine is online */ - online = network_is_online(); - - /* check if the client is currently connected */ - connected = m->server_socket >= 0 || m->resolve_query || m->exhausted_servers; - - if (connected && !online) { - log_info("No network connectivity, watching for changes."); - manager_disconnect(m); - - } else if (!connected && online) { - log_info("Network configuration changed, trying to establish connection."); - - if (m->current_server_address) - r = manager_begin(m); - else - r = manager_connect(m); - if (r < 0) - return r; - } - - return 0; -} - -static int manager_network_monitor_listen(Manager *m) { - int r, fd, events; - - assert(m); - - r = sd_network_monitor_new(&m->network_monitor, NULL); - if (r < 0) - return r; - - fd = sd_network_monitor_get_fd(m->network_monitor); - if (fd < 0) - return fd; - - events = sd_network_monitor_get_events(m->network_monitor); - if (events < 0) - return events; - - r = sd_event_add_io(m->event, &m->network_event_source, fd, events, manager_network_event_handler, m); - if (r < 0) - return r; - - return 0; -} - -int manager_new(Manager **ret) { - _cleanup_(manager_freep) Manager *m = NULL; - int r; - - assert(ret); - - m = new0(Manager, 1); - if (!m) - return -ENOMEM; - - m->server_socket = m->clock_watch_fd = -1; - - RATELIMIT_INIT(m->ratelimit, RATELIMIT_INTERVAL_USEC, RATELIMIT_BURST); - - r = manager_parse_server_string(m, SERVER_FALLBACK, NTP_SERVERS); - if (r < 0) - return r; - - r = sd_event_default(&m->event); - if (r < 0) - return r; - - sd_event_add_signal(m->event, NULL, SIGTERM, NULL, NULL); - sd_event_add_signal(m->event, NULL, SIGINT, NULL, NULL); - - sd_event_set_watchdog(m->event, true); - - r = sd_resolve_default(&m->resolve); - if (r < 0) - return r; - - r = sd_resolve_attach_event(m->resolve, m->event, 0); - if (r < 0) - return r; - - r = manager_network_monitor_listen(m); - if (r < 0) - return r; - - manager_network_read_link_servers(m); - - *ret = m; - m = NULL; - - return 0; -} diff --git a/src/timesync/timesyncd-manager.h b/src/timesync/timesyncd-manager.h deleted file mode 100644 index efe3e60d3e..0000000000 --- a/src/timesync/timesyncd-manager.h +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 . -***/ - -#include "sd-event.h" -#include "sd-network.h" -#include "sd-resolve.h" - -#include "list.h" -#include "ratelimit.h" - -typedef struct Manager Manager; - -#include "timesyncd-server.h" - -struct Manager { - sd_event *event; - sd_resolve *resolve; - - LIST_HEAD(ServerName, system_servers); - LIST_HEAD(ServerName, link_servers); - LIST_HEAD(ServerName, fallback_servers); - - RateLimit ratelimit; - bool exhausted_servers; - - /* network */ - sd_event_source *network_event_source; - sd_network_monitor *network_monitor; - - /* peer */ - sd_resolve_query *resolve_query; - sd_event_source *event_receive; - ServerName *current_server_name; - ServerAddress *current_server_address; - int server_socket; - int missed_replies; - uint64_t packet_count; - sd_event_source *event_timeout; - bool good; - - /* last sent packet */ - struct timespec trans_time_mon; - struct timespec trans_time; - usec_t retry_interval; - bool pending; - - /* poll timer */ - sd_event_source *event_timer; - usec_t poll_interval_usec; - bool poll_resync; - - /* history data */ - struct { - double offset; - double delay; - } samples[8]; - unsigned int samples_idx; - double samples_jitter; - - /* last change */ - bool jumped; - bool sync; - int drift_ppm; - - /* watch for time changes */ - sd_event_source *event_clock_watch; - int clock_watch_fd; - - /* Retry connections */ - sd_event_source *event_retry; - - /* RTC runs in local time, leave it alone */ - bool rtc_local_time; -}; - -int manager_new(Manager **ret); -void manager_free(Manager *m); - -DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); - -void manager_set_server_name(Manager *m, ServerName *n); -void manager_set_server_address(Manager *m, ServerAddress *a); -void manager_flush_server_names(Manager *m, ServerType t); - -int manager_connect(Manager *m); -void manager_disconnect(Manager *m); diff --git a/src/timesync/timesyncd-server.c b/src/timesync/timesyncd-server.c deleted file mode 100644 index 6bda86fe6e..0000000000 --- a/src/timesync/timesyncd-server.c +++ /dev/null @@ -1,150 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 . -***/ - -#include "alloc-util.h" -#include "timesyncd-server.h" - -int server_address_new( - ServerName *n, - ServerAddress **ret, - const union sockaddr_union *sockaddr, - socklen_t socklen) { - - ServerAddress *a, *tail; - - assert(n); - assert(sockaddr); - assert(socklen >= offsetof(struct sockaddr, sa_data)); - assert(socklen <= sizeof(union sockaddr_union)); - - a = new0(ServerAddress, 1); - if (!a) - return -ENOMEM; - - memcpy(&a->sockaddr, sockaddr, socklen); - a->socklen = socklen; - - LIST_FIND_TAIL(addresses, n->addresses, tail); - LIST_INSERT_AFTER(addresses, n->addresses, tail, a); - a->name = n; - - if (ret) - *ret = a; - - return 0; -} - -ServerAddress* server_address_free(ServerAddress *a) { - if (!a) - return NULL; - - if (a->name) { - LIST_REMOVE(addresses, a->name->addresses, a); - - if (a->name->manager && a->name->manager->current_server_address == a) - manager_set_server_address(a->name->manager, NULL); - } - - free(a); - return NULL; -} - -int server_name_new( - Manager *m, - ServerName **ret, - ServerType type, - const char *string) { - - ServerName *n, *tail; - - assert(m); - assert(string); - - n = new0(ServerName, 1); - if (!n) - return -ENOMEM; - - n->type = type; - n->string = strdup(string); - if (!n->string) { - free(n); - return -ENOMEM; - } - - if (type == SERVER_SYSTEM) { - LIST_FIND_TAIL(names, m->system_servers, tail); - LIST_INSERT_AFTER(names, m->system_servers, tail, n); - } else if (type == SERVER_LINK) { - LIST_FIND_TAIL(names, m->link_servers, tail); - LIST_INSERT_AFTER(names, m->link_servers, tail, n); - } else if (type == SERVER_FALLBACK) { - LIST_FIND_TAIL(names, m->fallback_servers, tail); - LIST_INSERT_AFTER(names, m->fallback_servers, tail, n); - } else - assert_not_reached("Unknown server type"); - - n->manager = m; - - if (type != SERVER_FALLBACK && - m->current_server_name && - m->current_server_name->type == SERVER_FALLBACK) - manager_set_server_name(m, NULL); - - log_debug("Added new server %s.", string); - - if (ret) - *ret = n; - - return 0; -} - -ServerName *server_name_free(ServerName *n) { - if (!n) - return NULL; - - server_name_flush_addresses(n); - - if (n->manager) { - if (n->type == SERVER_SYSTEM) - LIST_REMOVE(names, n->manager->system_servers, n); - else if (n->type == SERVER_LINK) - LIST_REMOVE(names, n->manager->link_servers, n); - else if (n->type == SERVER_FALLBACK) - LIST_REMOVE(names, n->manager->fallback_servers, n); - else - assert_not_reached("Unknown server type"); - - if (n->manager->current_server_name == n) - manager_set_server_name(n->manager, NULL); - } - - log_debug("Removed server %s.", n->string); - - free(n->string); - free(n); - - return NULL; -} - -void server_name_flush_addresses(ServerName *n) { - assert(n); - - while (n->addresses) - server_address_free(n->addresses); -} diff --git a/src/timesync/timesyncd-server.h b/src/timesync/timesyncd-server.h deleted file mode 100644 index 8a19e41d67..0000000000 --- a/src/timesync/timesyncd-server.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 . -***/ - -#include "list.h" -#include "socket-util.h" - -typedef struct ServerAddress ServerAddress; -typedef struct ServerName ServerName; - -typedef enum ServerType { - SERVER_SYSTEM, - SERVER_FALLBACK, - SERVER_LINK, -} ServerType; - -#include "timesyncd-manager.h" - -struct ServerAddress { - ServerName *name; - - union sockaddr_union sockaddr; - socklen_t socklen; - - LIST_FIELDS(ServerAddress, addresses); -}; - -struct ServerName { - Manager *manager; - - ServerType type; - char *string; - - bool marked:1; - - LIST_HEAD(ServerAddress, addresses); - LIST_FIELDS(ServerName, names); -}; - -int server_address_new(ServerName *n, ServerAddress **ret, const union sockaddr_union *sockaddr, socklen_t socklen); -ServerAddress* server_address_free(ServerAddress *a); -static inline int server_address_pretty(ServerAddress *a, char **pretty) { - return sockaddr_pretty(&a->sockaddr.sa, a->socklen, true, true, pretty); -} - -int server_name_new(Manager *m, ServerName **ret, ServerType type,const char *string); -ServerName *server_name_free(ServerName *n); -void server_name_flush_addresses(ServerName *n); diff --git a/src/timesync/timesyncd.c b/src/timesync/timesyncd.c deleted file mode 100644 index 23e19159e0..0000000000 --- a/src/timesync/timesyncd.c +++ /dev/null @@ -1,164 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 Kay Sievers, 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 . -***/ - -#include "sd-daemon.h" -#include "sd-event.h" - -#include "capability-util.h" -#include "clock-util.h" -#include "fd-util.h" -#include "fs-util.h" -#include "network-util.h" -#include "signal-util.h" -#include "timesyncd-conf.h" -#include "timesyncd-manager.h" -#include "user-util.h" - -static int load_clock_timestamp(uid_t uid, gid_t gid) { - _cleanup_close_ int fd = -1; - usec_t min = TIME_EPOCH * USEC_PER_SEC; - usec_t ct; - int r; - - /* Let's try to make sure that the clock is always - * monotonically increasing, by saving the clock whenever we - * have a new NTP time, or when we shut down, and restoring it - * when we start again. This is particularly helpful on - * systems lacking a battery backed RTC. We also will adjust - * the time to at least the build time of systemd. */ - - fd = open("/var/lib/systemd/clock", O_RDWR|O_CLOEXEC, 0644); - if (fd >= 0) { - struct stat st; - usec_t stamp; - - /* check if the recorded time is later than the compiled-in one */ - r = fstat(fd, &st); - if (r >= 0) { - stamp = timespec_load(&st.st_mtim); - if (stamp > min) - min = stamp; - } - - /* Try to fix the access mode, so that we can still - touch the file after dropping priviliges */ - (void) fchmod(fd, 0644); - (void) fchown(fd, uid, gid); - - } else - /* create stamp file with the compiled-in date */ - (void) touch_file("/var/lib/systemd/clock", true, min, uid, gid, 0644); - - ct = now(CLOCK_REALTIME); - if (ct < min) { - struct timespec ts; - char date[FORMAT_TIMESTAMP_MAX]; - - log_info("System clock time unset or jumped backwards, restoring from recorded timestamp: %s", - format_timestamp(date, sizeof(date), min)); - - if (clock_settime(CLOCK_REALTIME, timespec_store(&ts, min)) < 0) - log_error_errno(errno, "Failed to restore system clock: %m"); - } - - return 0; -} - -int main(int argc, char *argv[]) { - _cleanup_(manager_freep) Manager *m = NULL; - const char *user = "systemd-timesync"; - uid_t uid; - gid_t gid; - int r; - - log_set_target(LOG_TARGET_AUTO); - log_set_facility(LOG_CRON); - log_parse_environment(); - log_open(); - - umask(0022); - - if (argc != 1) { - log_error("This program does not take arguments."); - r = -EINVAL; - goto finish; - } - - r = get_user_creds(&user, &uid, &gid, NULL, NULL); - if (r < 0) { - log_error_errno(r, "Cannot resolve user name %s: %m", user); - goto finish; - } - - r = load_clock_timestamp(uid, gid); - if (r < 0) - goto finish; - - r = drop_privileges(uid, gid, (1ULL << CAP_SYS_TIME)); - if (r < 0) - goto finish; - - assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); - - r = manager_new(&m); - if (r < 0) { - log_error_errno(r, "Failed to allocate manager: %m"); - goto finish; - } - - if (clock_is_localtime() > 0) { - log_info("The system is configured to read the RTC time in the local time zone. " - "This mode can not be fully supported. All system time to RTC updates are disabled."); - m->rtc_local_time = true; - } - - r = manager_parse_config_file(m); - if (r < 0) - log_warning_errno(r, "Failed to parse configuration file: %m"); - - log_debug("systemd-timesyncd running as pid " PID_FMT, getpid()); - sd_notify(false, - "READY=1\n" - "STATUS=Daemon is running"); - - if (network_is_online()) { - r = manager_connect(m); - if (r < 0) - goto finish; - } - - r = sd_event_loop(m->event); - if (r < 0) { - log_error_errno(r, "Failed to run event loop: %m"); - goto finish; - } - - /* if we got an authoritative time, store it in the file system */ - if (m->sync) - (void) touch("/var/lib/systemd/clock"); - - sd_event_get_exit_code(m->event, &r); - -finish: - sd_notify(false, - "STOPPING=1\n" - "STATUS=Shutting down..."); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/timesync/timesyncd.conf.in b/src/timesync/timesyncd.conf.in deleted file mode 100644 index b6a2ada273..0000000000 --- a/src/timesync/timesyncd.conf.in +++ /dev/null @@ -1,16 +0,0 @@ -# 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. -# -# Entries in this file show the compile time defaults. -# You can change settings by editing this file. -# Defaults can be restored by simply deleting this file. -# -# See timesyncd.conf(5) for details. - -[Time] -#NTP= -#FallbackNTP=@NTP_SERVERS@ diff --git a/src/tmpfiles/Makefile b/src/tmpfiles/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/tmpfiles/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c deleted file mode 100644 index 7b105a6bd4..0000000000 --- a/src/tmpfiles/tmpfiles.c +++ /dev/null @@ -1,2364 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 Lennart Poettering, Kay Sievers - Copyright 2015 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "acl-util.h" -#include "alloc-util.h" -#include "btrfs-util.h" -#include "capability-util.h" -#include "chattr-util.h" -#include "conf-files.h" -#include "copy.h" -#include "def.h" -#include "escape.h" -#include "fd-util.h" -#include "fileio.h" -#include "formats-util.h" -#include "fs-util.h" -#include "glob-util.h" -#include "io-util.h" -#include "label.h" -#include "log.h" -#include "macro.h" -#include "missing.h" -#include "mkdir.h" -#include "mount-util.h" -#include "parse-util.h" -#include "path-util.h" -#include "rm-rf.h" -#include "selinux-util.h" -#include "set.h" -#include "specifier.h" -#include "stat-util.h" -#include "stdio-util.h" -#include "string-table.h" -#include "string-util.h" -#include "strv.h" -#include "umask-util.h" -#include "user-util.h" -#include "util.h" - -/* This reads all files listed in /etc/tmpfiles.d/?*.conf and creates - * them in the file system. This is intended to be used to create - * properly owned directories beneath /tmp, /var/tmp, /run, which are - * volatile and hence need to be recreated on bootup. */ - -typedef enum ItemType { - /* These ones take file names */ - CREATE_FILE = 'f', - TRUNCATE_FILE = 'F', - CREATE_DIRECTORY = 'd', - TRUNCATE_DIRECTORY = 'D', - CREATE_SUBVOLUME = 'v', - CREATE_SUBVOLUME_INHERIT_QUOTA = 'q', - CREATE_SUBVOLUME_NEW_QUOTA = 'Q', - CREATE_FIFO = 'p', - CREATE_SYMLINK = 'L', - CREATE_CHAR_DEVICE = 'c', - CREATE_BLOCK_DEVICE = 'b', - COPY_FILES = 'C', - - /* These ones take globs */ - WRITE_FILE = 'w', - SET_XATTR = 't', - RECURSIVE_SET_XATTR = 'T', - SET_ACL = 'a', - RECURSIVE_SET_ACL = 'A', - SET_ATTRIBUTE = 'h', - RECURSIVE_SET_ATTRIBUTE = 'H', - IGNORE_PATH = 'x', - IGNORE_DIRECTORY_PATH = 'X', - REMOVE_PATH = 'r', - RECURSIVE_REMOVE_PATH = 'R', - RELABEL_PATH = 'z', - RECURSIVE_RELABEL_PATH = 'Z', - ADJUST_MODE = 'm', /* legacy, 'z' is identical to this */ -} ItemType; - -typedef struct Item { - ItemType type; - - char *path; - char *argument; - char **xattrs; -#ifdef HAVE_ACL - acl_t acl_access; - acl_t acl_default; -#endif - uid_t uid; - gid_t gid; - mode_t mode; - usec_t age; - - dev_t major_minor; - unsigned attribute_value; - unsigned attribute_mask; - - bool uid_set:1; - bool gid_set:1; - bool mode_set:1; - bool age_set:1; - bool mask_perms:1; - bool attribute_set:1; - - bool keep_first_level:1; - - bool force:1; - - bool done:1; -} Item; - -typedef struct ItemArray { - Item *items; - size_t count; - size_t size; -} ItemArray; - -static bool arg_create = false; -static bool arg_clean = false; -static bool arg_remove = false; -static bool arg_boot = false; - -static char **arg_include_prefixes = NULL; -static char **arg_exclude_prefixes = NULL; -static char *arg_root = NULL; - -static const char conf_file_dirs[] = CONF_PATHS_NULSTR("tmpfiles.d"); - -#define MAX_DEPTH 256 - -static OrderedHashmap *items = NULL, *globs = NULL; -static Set *unix_sockets = NULL; - -static const Specifier specifier_table[] = { - { 'm', specifier_machine_id, NULL }, - { 'b', specifier_boot_id, NULL }, - { 'H', specifier_host_name, NULL }, - { 'v', specifier_kernel_release, NULL }, - {} -}; - -static bool needs_glob(ItemType t) { - return IN_SET(t, - WRITE_FILE, - IGNORE_PATH, - IGNORE_DIRECTORY_PATH, - REMOVE_PATH, - RECURSIVE_REMOVE_PATH, - ADJUST_MODE, - RELABEL_PATH, - RECURSIVE_RELABEL_PATH, - SET_XATTR, - RECURSIVE_SET_XATTR, - SET_ACL, - RECURSIVE_SET_ACL, - SET_ATTRIBUTE, - RECURSIVE_SET_ATTRIBUTE); -} - -static bool takes_ownership(ItemType t) { - return IN_SET(t, - CREATE_FILE, - TRUNCATE_FILE, - CREATE_DIRECTORY, - TRUNCATE_DIRECTORY, - CREATE_SUBVOLUME, - CREATE_SUBVOLUME_INHERIT_QUOTA, - CREATE_SUBVOLUME_NEW_QUOTA, - CREATE_FIFO, - CREATE_SYMLINK, - CREATE_CHAR_DEVICE, - CREATE_BLOCK_DEVICE, - COPY_FILES, - WRITE_FILE, - IGNORE_PATH, - IGNORE_DIRECTORY_PATH, - REMOVE_PATH, - RECURSIVE_REMOVE_PATH); -} - -static struct Item* find_glob(OrderedHashmap *h, const char *match) { - ItemArray *j; - Iterator i; - - ORDERED_HASHMAP_FOREACH(j, h, i) { - unsigned n; - - for (n = 0; n < j->count; n++) { - Item *item = j->items + n; - - if (fnmatch(item->path, match, FNM_PATHNAME|FNM_PERIOD) == 0) - return item; - } - } - - return NULL; -} - -static void load_unix_sockets(void) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - - if (unix_sockets) - return; - - /* We maintain a cache of the sockets we found in - * /proc/net/unix to speed things up a little. */ - - unix_sockets = set_new(&string_hash_ops); - if (!unix_sockets) - return; - - f = fopen("/proc/net/unix", "re"); - if (!f) - return; - - /* Skip header */ - if (!fgets(line, sizeof(line), f)) - goto fail; - - for (;;) { - char *p, *s; - int k; - - if (!fgets(line, sizeof(line), f)) - break; - - truncate_nl(line); - - p = strchr(line, ':'); - if (!p) - continue; - - if (strlen(p) < 37) - continue; - - p += 37; - p += strspn(p, WHITESPACE); - p += strcspn(p, WHITESPACE); /* skip one more word */ - p += strspn(p, WHITESPACE); - - if (*p != '/') - continue; - - s = strdup(p); - if (!s) - goto fail; - - path_kill_slashes(s); - - k = set_consume(unix_sockets, s); - if (k < 0 && k != -EEXIST) - goto fail; - } - - return; - -fail: - set_free_free(unix_sockets); - unix_sockets = NULL; -} - -static bool unix_socket_alive(const char *fn) { - assert(fn); - - load_unix_sockets(); - - if (unix_sockets) - return !!set_get(unix_sockets, (char*) fn); - - /* We don't know, so assume yes */ - return true; -} - -static int dir_is_mount_point(DIR *d, const char *subdir) { - - union file_handle_union h = FILE_HANDLE_INIT; - int mount_id_parent, mount_id; - int r_p, r; - - r_p = name_to_handle_at(dirfd(d), ".", &h.handle, &mount_id_parent, 0); - if (r_p < 0) - r_p = -errno; - - h.handle.handle_bytes = MAX_HANDLE_SZ; - r = name_to_handle_at(dirfd(d), subdir, &h.handle, &mount_id, 0); - if (r < 0) - r = -errno; - - /* got no handle; make no assumptions, return error */ - if (r_p < 0 && r < 0) - return r_p; - - /* got both handles; if they differ, it is a mount point */ - if (r_p >= 0 && r >= 0) - return mount_id_parent != mount_id; - - /* got only one handle; assume different mount points if one - * of both queries was not supported by the filesystem */ - if (r_p == -ENOSYS || r_p == -EOPNOTSUPP || r == -ENOSYS || r == -EOPNOTSUPP) - return true; - - /* return error */ - if (r_p < 0) - return r_p; - return r; -} - -static DIR* xopendirat_nomod(int dirfd, const char *path) { - DIR *dir; - - dir = xopendirat(dirfd, path, O_NOFOLLOW|O_NOATIME); - if (dir) - return dir; - - log_debug_errno(errno, "Cannot open %sdirectory \"%s\": %m", dirfd == AT_FDCWD ? "" : "sub", path); - if (errno != EPERM) - return NULL; - - dir = xopendirat(dirfd, path, O_NOFOLLOW); - if (!dir) - log_debug_errno(errno, "Cannot open %sdirectory \"%s\": %m", dirfd == AT_FDCWD ? "" : "sub", path); - - return dir; -} - -static DIR* opendir_nomod(const char *path) { - return xopendirat_nomod(AT_FDCWD, path); -} - -static int dir_cleanup( - Item *i, - const char *p, - DIR *d, - const struct stat *ds, - usec_t cutoff, - dev_t rootdev, - bool mountpoint, - int maxdepth, - bool keep_this_level) { - - struct dirent *dent; - struct timespec times[2]; - bool deleted = false; - int r = 0; - - while ((dent = readdir(d))) { - struct stat s; - usec_t age; - _cleanup_free_ char *sub_path = NULL; - - if (STR_IN_SET(dent->d_name, ".", "..")) - continue; - - if (fstatat(dirfd(d), dent->d_name, &s, AT_SYMLINK_NOFOLLOW) < 0) { - if (errno == ENOENT) - continue; - - /* FUSE, NFS mounts, SELinux might return EACCES */ - if (errno == EACCES) - log_debug_errno(errno, "stat(%s/%s) failed: %m", p, dent->d_name); - else - log_error_errno(errno, "stat(%s/%s) failed: %m", p, dent->d_name); - r = -errno; - continue; - } - - /* Stay on the same filesystem */ - if (s.st_dev != rootdev) { - log_debug("Ignoring \"%s/%s\": different filesystem.", p, dent->d_name); - continue; - } - - /* Try to detect bind mounts of the same filesystem instance; they - * do not differ in device major/minors. This type of query is not - * supported on all kernels or filesystem types though. */ - if (S_ISDIR(s.st_mode) && dir_is_mount_point(d, dent->d_name) > 0) { - log_debug("Ignoring \"%s/%s\": different mount of the same filesystem.", - p, dent->d_name); - continue; - } - - /* Do not delete read-only files owned by root */ - if (s.st_uid == 0 && !(s.st_mode & S_IWUSR)) { - log_debug("Ignoring \"%s/%s\": read-only and owner by root.", p, dent->d_name); - continue; - } - - sub_path = strjoin(p, "/", dent->d_name, NULL); - if (!sub_path) { - r = log_oom(); - goto finish; - } - - /* Is there an item configured for this path? */ - if (ordered_hashmap_get(items, sub_path)) { - log_debug("Ignoring \"%s\": a separate entry exists.", sub_path); - continue; - } - - if (find_glob(globs, sub_path)) { - log_debug("Ignoring \"%s\": a separate glob exists.", sub_path); - continue; - } - - if (S_ISDIR(s.st_mode)) { - - if (mountpoint && - streq(dent->d_name, "lost+found") && - s.st_uid == 0) { - log_debug("Ignoring \"%s\".", sub_path); - continue; - } - - if (maxdepth <= 0) - log_warning("Reached max depth on \"%s\".", sub_path); - else { - _cleanup_closedir_ DIR *sub_dir; - int q; - - sub_dir = xopendirat_nomod(dirfd(d), dent->d_name); - if (!sub_dir) { - if (errno != ENOENT) - r = log_error_errno(errno, "opendir(%s) failed: %m", sub_path); - - continue; - } - - q = dir_cleanup(i, sub_path, sub_dir, &s, cutoff, rootdev, false, maxdepth-1, false); - if (q < 0) - r = q; - } - - /* Note: if you are wondering why we don't - * support the sticky bit for excluding - * directories from cleaning like we do it for - * other file system objects: well, the sticky - * bit already has a meaning for directories, - * so we don't want to overload that. */ - - if (keep_this_level) { - log_debug("Keeping \"%s\".", sub_path); - continue; - } - - /* Ignore ctime, we change it when deleting */ - age = timespec_load(&s.st_mtim); - if (age >= cutoff) { - char a[FORMAT_TIMESTAMP_MAX]; - /* Follows spelling in stat(1). */ - log_debug("Directory \"%s\": modify time %s is too new.", - sub_path, - format_timestamp_us(a, sizeof(a), age)); - continue; - } - - age = timespec_load(&s.st_atim); - if (age >= cutoff) { - char a[FORMAT_TIMESTAMP_MAX]; - log_debug("Directory \"%s\": access time %s is too new.", - sub_path, - format_timestamp_us(a, sizeof(a), age)); - continue; - } - - log_debug("Removing directory \"%s\".", sub_path); - if (unlinkat(dirfd(d), dent->d_name, AT_REMOVEDIR) < 0) - if (errno != ENOENT && errno != ENOTEMPTY) { - log_error_errno(errno, "rmdir(%s): %m", sub_path); - r = -errno; - } - - } else { - /* Skip files for which the sticky bit is - * set. These are semantics we define, and are - * unknown elsewhere. See XDG_RUNTIME_DIR - * specification for details. */ - if (s.st_mode & S_ISVTX) { - log_debug("Skipping \"%s\": sticky bit set.", sub_path); - continue; - } - - if (mountpoint && S_ISREG(s.st_mode)) - if (s.st_uid == 0 && STR_IN_SET(dent->d_name, - ".journal", - "aquota.user", - "aquota.group")) { - log_debug("Skipping \"%s\".", sub_path); - continue; - } - - /* Ignore sockets that are listed in /proc/net/unix */ - if (S_ISSOCK(s.st_mode) && unix_socket_alive(sub_path)) { - log_debug("Skipping \"%s\": live socket.", sub_path); - continue; - } - - /* Ignore device nodes */ - if (S_ISCHR(s.st_mode) || S_ISBLK(s.st_mode)) { - log_debug("Skipping \"%s\": a device.", sub_path); - continue; - } - - /* Keep files on this level around if this is - * requested */ - if (keep_this_level) { - log_debug("Keeping \"%s\".", sub_path); - continue; - } - - age = timespec_load(&s.st_mtim); - if (age >= cutoff) { - char a[FORMAT_TIMESTAMP_MAX]; - /* Follows spelling in stat(1). */ - log_debug("File \"%s\": modify time %s is too new.", - sub_path, - format_timestamp_us(a, sizeof(a), age)); - continue; - } - - age = timespec_load(&s.st_atim); - if (age >= cutoff) { - char a[FORMAT_TIMESTAMP_MAX]; - log_debug("File \"%s\": access time %s is too new.", - sub_path, - format_timestamp_us(a, sizeof(a), age)); - continue; - } - - age = timespec_load(&s.st_ctim); - if (age >= cutoff) { - char a[FORMAT_TIMESTAMP_MAX]; - log_debug("File \"%s\": change time %s is too new.", - sub_path, - format_timestamp_us(a, sizeof(a), age)); - continue; - } - - log_debug("unlink \"%s\"", sub_path); - - if (unlinkat(dirfd(d), dent->d_name, 0) < 0) - if (errno != ENOENT) - r = log_error_errno(errno, "unlink(%s): %m", sub_path); - - deleted = true; - } - } - -finish: - if (deleted) { - usec_t age1, age2; - char a[FORMAT_TIMESTAMP_MAX], b[FORMAT_TIMESTAMP_MAX]; - - /* Restore original directory timestamps */ - times[0] = ds->st_atim; - times[1] = ds->st_mtim; - - age1 = timespec_load(&ds->st_atim); - age2 = timespec_load(&ds->st_mtim); - log_debug("Restoring access and modification time on \"%s\": %s, %s", - p, - format_timestamp_us(a, sizeof(a), age1), - format_timestamp_us(b, sizeof(b), age2)); - if (futimens(dirfd(d), times) < 0) - log_error_errno(errno, "utimensat(%s): %m", p); - } - - return r; -} - -static int path_set_perms(Item *i, const char *path) { - _cleanup_close_ int fd = -1; - struct stat st; - - assert(i); - assert(path); - - /* We open the file with O_PATH here, to make the operation - * somewhat atomic. Also there's unfortunately no fchmodat() - * with AT_SYMLINK_NOFOLLOW, hence we emulate it here via - * O_PATH. */ - - fd = open(path, O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH|O_NOATIME); - if (fd < 0) - return log_error_errno(errno, "Adjusting owner and mode for %s failed: %m", path); - - if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0) - return log_error_errno(errno, "Failed to fstat() file %s: %m", path); - - if (S_ISLNK(st.st_mode)) - log_debug("Skipping mode an owner fix for symlink %s.", path); - else { - char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; - xsprintf(fn, "/proc/self/fd/%i", fd); - - /* not using i->path directly because it may be a glob */ - if (i->mode_set) { - mode_t m = i->mode; - - if (i->mask_perms) { - if (!(st.st_mode & 0111)) - m &= ~0111; - if (!(st.st_mode & 0222)) - m &= ~0222; - if (!(st.st_mode & 0444)) - m &= ~0444; - if (!S_ISDIR(st.st_mode)) - m &= ~07000; /* remove sticky/sgid/suid bit, unless directory */ - } - - if (m == (st.st_mode & 07777)) - log_debug("\"%s\" has right mode %o", path, st.st_mode); - else { - log_debug("chmod \"%s\" to mode %o", path, m); - if (chmod(fn, m) < 0) - return log_error_errno(errno, "chmod(%s) failed: %m", path); - } - } - - if ((i->uid != st.st_uid || i->gid != st.st_gid) && - (i->uid_set || i->gid_set)) { - log_debug("chown \"%s\" to "UID_FMT"."GID_FMT, - path, - i->uid_set ? i->uid : UID_INVALID, - i->gid_set ? i->gid : GID_INVALID); - if (chown(fn, - i->uid_set ? i->uid : UID_INVALID, - i->gid_set ? i->gid : GID_INVALID) < 0) - return log_error_errno(errno, "chown(%s) failed: %m", path); - } - } - - fd = safe_close(fd); - - return label_fix(path, false, false); -} - -static int parse_xattrs_from_arg(Item *i) { - const char *p; - int r; - - assert(i); - assert(i->argument); - - p = i->argument; - - for (;;) { - _cleanup_free_ char *name = NULL, *value = NULL, *xattr = NULL, *xattr_replaced = NULL; - - r = extract_first_word(&p, &xattr, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE); - if (r < 0) - log_warning_errno(r, "Failed to parse extended attribute '%s', ignoring: %m", p); - if (r <= 0) - break; - - r = specifier_printf(xattr, specifier_table, NULL, &xattr_replaced); - if (r < 0) - return log_error_errno(r, "Failed to replace specifiers in extended attribute '%s': %m", xattr); - - r = split_pair(xattr_replaced, "=", &name, &value); - if (r < 0) { - log_warning_errno(r, "Failed to parse extended attribute, ignoring: %s", xattr); - continue; - } - - if (isempty(name) || isempty(value)) { - log_warning("Malformed extended attribute found, ignoring: %s", xattr); - continue; - } - - if (strv_push_pair(&i->xattrs, name, value) < 0) - return log_oom(); - - name = value = NULL; - } - - return 0; -} - -static int path_set_xattrs(Item *i, const char *path) { - char **name, **value; - - assert(i); - assert(path); - - STRV_FOREACH_PAIR(name, value, i->xattrs) { - int n; - - n = strlen(*value); - log_debug("Setting extended attribute '%s=%s' on %s.", *name, *value, path); - if (lsetxattr(path, *name, *value, n, 0) < 0) { - log_error("Setting extended attribute %s=%s on %s failed: %m", *name, *value, path); - return -errno; - } - } - return 0; -} - -static int parse_acls_from_arg(Item *item) { -#ifdef HAVE_ACL - int r; - - assert(item); - - /* If force (= modify) is set, we will not modify the acl - * afterwards, so the mask can be added now if necessary. */ - - r = parse_acl(item->argument, &item->acl_access, &item->acl_default, !item->force); - if (r < 0) - log_warning_errno(r, "Failed to parse ACL \"%s\": %m. Ignoring", item->argument); -#else - log_warning_errno(ENOSYS, "ACLs are not supported. Ignoring"); -#endif - - return 0; -} - -#ifdef HAVE_ACL -static int path_set_acl(const char *path, const char *pretty, acl_type_t type, acl_t acl, bool modify) { - _cleanup_(acl_free_charpp) char *t = NULL; - _cleanup_(acl_freep) acl_t dup = NULL; - int r; - - /* Returns 0 for success, positive error if already warned, - * negative error otherwise. */ - - if (modify) { - r = acls_for_file(path, type, acl, &dup); - if (r < 0) - return r; - - r = calc_acl_mask_if_needed(&dup); - if (r < 0) - return r; - } else { - dup = acl_dup(acl); - if (!dup) - return -errno; - - /* the mask was already added earlier if needed */ - } - - r = add_base_acls_if_needed(&dup, path); - if (r < 0) - return r; - - t = acl_to_any_text(dup, NULL, ',', TEXT_ABBREVIATE); - log_debug("Setting %s ACL %s on %s.", - type == ACL_TYPE_ACCESS ? "access" : "default", - strna(t), pretty); - - r = acl_set_file(path, type, dup); - if (r < 0) - /* Return positive to indicate we already warned */ - return -log_error_errno(errno, - "Setting %s ACL \"%s\" on %s failed: %m", - type == ACL_TYPE_ACCESS ? "access" : "default", - strna(t), pretty); - - return 0; -} -#endif - -static int path_set_acls(Item *item, const char *path) { - int r = 0; -#ifdef HAVE_ACL - char fn[strlen("/proc/self/fd/") + DECIMAL_STR_MAX(int)]; - _cleanup_close_ int fd = -1; - struct stat st; - - assert(item); - assert(path); - - fd = open(path, O_RDONLY|O_NOFOLLOW|O_CLOEXEC|O_PATH|O_NOATIME); - if (fd < 0) - return log_error_errno(errno, "Adjusting ACL of %s failed: %m", path); - - if (fstatat(fd, "", &st, AT_EMPTY_PATH) < 0) - return log_error_errno(errno, "Failed to fstat() file %s: %m", path); - - if (S_ISLNK(st.st_mode)) { - log_debug("Skipping ACL fix for symlink %s.", path); - return 0; - } - - xsprintf(fn, "/proc/self/fd/%i", fd); - - if (item->acl_access) - r = path_set_acl(fn, path, ACL_TYPE_ACCESS, item->acl_access, item->force); - - if (r == 0 && item->acl_default) - r = path_set_acl(fn, path, ACL_TYPE_DEFAULT, item->acl_default, item->force); - - if (r > 0) - return -r; /* already warned */ - else if (r == -EOPNOTSUPP) { - log_debug_errno(r, "ACLs not supported by file system at %s", path); - return 0; - } else if (r < 0) - log_error_errno(r, "ACL operation on \"%s\" failed: %m", path); -#endif - return r; -} - -#define ATTRIBUTES_ALL \ - (FS_NOATIME_FL | \ - FS_SYNC_FL | \ - FS_DIRSYNC_FL | \ - FS_APPEND_FL | \ - FS_COMPR_FL | \ - FS_NODUMP_FL | \ - FS_EXTENT_FL | \ - FS_IMMUTABLE_FL | \ - FS_JOURNAL_DATA_FL | \ - FS_SECRM_FL | \ - FS_UNRM_FL | \ - FS_NOTAIL_FL | \ - FS_TOPDIR_FL | \ - FS_NOCOW_FL) - -static int parse_attribute_from_arg(Item *item) { - - static const struct { - char character; - unsigned value; - } attributes[] = { - { 'A', FS_NOATIME_FL }, /* do not update atime */ - { 'S', FS_SYNC_FL }, /* Synchronous updates */ - { 'D', FS_DIRSYNC_FL }, /* dirsync behaviour (directories only) */ - { 'a', FS_APPEND_FL }, /* writes to file may only append */ - { 'c', FS_COMPR_FL }, /* Compress file */ - { 'd', FS_NODUMP_FL }, /* do not dump file */ - { 'e', FS_EXTENT_FL }, /* Top of directory hierarchies*/ - { 'i', FS_IMMUTABLE_FL }, /* Immutable file */ - { 'j', FS_JOURNAL_DATA_FL }, /* Reserved for ext3 */ - { 's', FS_SECRM_FL }, /* Secure deletion */ - { 'u', FS_UNRM_FL }, /* Undelete */ - { 't', FS_NOTAIL_FL }, /* file tail should not be merged */ - { 'T', FS_TOPDIR_FL }, /* Top of directory hierarchies*/ - { 'C', FS_NOCOW_FL }, /* Do not cow file */ - }; - - enum { - MODE_ADD, - MODE_DEL, - MODE_SET - } mode = MODE_ADD; - - unsigned value = 0, mask = 0; - const char *p; - - assert(item); - - p = item->argument; - if (p) { - if (*p == '+') { - mode = MODE_ADD; - p++; - } else if (*p == '-') { - mode = MODE_DEL; - p++; - } else if (*p == '=') { - mode = MODE_SET; - p++; - } - } - - if (isempty(p) && mode != MODE_SET) { - log_error("Setting file attribute on '%s' needs an attribute specification.", item->path); - return -EINVAL; - } - - for (; p && *p ; p++) { - unsigned i, v; - - for (i = 0; i < ELEMENTSOF(attributes); i++) - if (*p == attributes[i].character) - break; - - if (i >= ELEMENTSOF(attributes)) { - log_error("Unknown file attribute '%c' on '%s'.", *p, item->path); - return -EINVAL; - } - - v = attributes[i].value; - - if (mode == MODE_ADD || mode == MODE_SET) - value |= v; - else - value &= ~v; - - mask |= v; - } - - if (mode == MODE_SET) - mask |= ATTRIBUTES_ALL; - - assert(mask != 0); - - item->attribute_mask = mask; - item->attribute_value = value; - item->attribute_set = true; - - return 0; -} - -static int path_set_attribute(Item *item, const char *path) { - _cleanup_close_ int fd = -1; - struct stat st; - unsigned f; - int r; - - if (!item->attribute_set || item->attribute_mask == 0) - return 0; - - fd = open(path, O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOATIME|O_NOFOLLOW); - if (fd < 0) { - if (errno == ELOOP) - return log_error_errno(errno, "Skipping file attributes adjustment on symlink %s.", path); - - return log_error_errno(errno, "Cannot open '%s': %m", path); - } - - if (fstat(fd, &st) < 0) - return log_error_errno(errno, "Cannot stat '%s': %m", path); - - /* Issuing the file attribute ioctls on device nodes is not - * safe, as that will be delivered to the drivers, not the - * file system containing the device node. */ - if (!S_ISREG(st.st_mode) && !S_ISDIR(st.st_mode)) { - log_error("Setting file flags is only supported on regular files and directories, cannot set on '%s'.", path); - return -EINVAL; - } - - f = item->attribute_value & item->attribute_mask; - - /* Mask away directory-specific flags */ - if (!S_ISDIR(st.st_mode)) - f &= ~FS_DIRSYNC_FL; - - r = chattr_fd(fd, f, item->attribute_mask); - if (r < 0) - log_full_errno(r == -ENOTTY ? LOG_DEBUG : LOG_WARNING, - r, - "Cannot set file attribute for '%s', value=0x%08x, mask=0x%08x: %m", - path, item->attribute_value, item->attribute_mask); - - return 0; -} - -static int write_one_file(Item *i, const char *path) { - _cleanup_close_ int fd = -1; - int flags, r = 0; - struct stat st; - - assert(i); - assert(path); - - flags = i->type == CREATE_FILE ? O_CREAT|O_APPEND|O_NOFOLLOW : - i->type == TRUNCATE_FILE ? O_CREAT|O_TRUNC|O_NOFOLLOW : 0; - - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(path, S_IFREG); - fd = open(path, flags|O_NDELAY|O_CLOEXEC|O_WRONLY|O_NOCTTY, i->mode); - mac_selinux_create_file_clear(); - } - - if (fd < 0) { - if (i->type == WRITE_FILE && errno == ENOENT) { - log_debug_errno(errno, "Not writing \"%s\": %m", path); - return 0; - } - - r = -errno; - if (!i->argument && errno == EROFS && stat(path, &st) == 0 && - (i->type == CREATE_FILE || st.st_size == 0)) - goto check_mode; - - return log_error_errno(r, "Failed to create file %s: %m", path); - } - - if (i->argument) { - _cleanup_free_ char *unescaped = NULL, *replaced = NULL; - - log_debug("%s to \"%s\".", i->type == CREATE_FILE ? "Appending" : "Writing", path); - - r = cunescape(i->argument, 0, &unescaped); - if (r < 0) - return log_error_errno(r, "Failed to unescape parameter to write: %s", i->argument); - - r = specifier_printf(unescaped, specifier_table, NULL, &replaced); - if (r < 0) - return log_error_errno(r, "Failed to replace specifiers in parameter to write '%s': %m", unescaped); - - r = loop_write(fd, replaced, strlen(replaced), false); - if (r < 0) - return log_error_errno(r, "Failed to write file \"%s\": %m", path); - } else - log_debug("\"%s\" has been created.", path); - - fd = safe_close(fd); - - if (stat(path, &st) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", path); - - check_mode: - if (!S_ISREG(st.st_mode)) { - log_error("%s is not a file.", path); - return -EEXIST; - } - - r = path_set_perms(i, path); - if (r < 0) - return r; - - return 0; -} - -typedef int (*action_t)(Item *, const char *); - -static int item_do_children(Item *i, const char *path, action_t action) { - _cleanup_closedir_ DIR *d; - int r = 0; - - assert(i); - assert(path); - - /* This returns the first error we run into, but nevertheless - * tries to go on */ - - d = opendir_nomod(path); - if (!d) - return errno == ENOENT || errno == ENOTDIR ? 0 : -errno; - - for (;;) { - _cleanup_free_ char *p = NULL; - struct dirent *de; - int q; - - errno = 0; - de = readdir(d); - if (!de) { - if (errno > 0 && r == 0) - r = -errno; - - break; - } - - if (STR_IN_SET(de->d_name, ".", "..")) - continue; - - p = strjoin(path, "/", de->d_name, NULL); - if (!p) - return -ENOMEM; - - q = action(i, p); - if (q < 0 && q != -ENOENT && r == 0) - r = q; - - if (IN_SET(de->d_type, DT_UNKNOWN, DT_DIR)) { - q = item_do_children(i, p, action); - if (q < 0 && r == 0) - r = q; - } - } - - return r; -} - -static int glob_item(Item *i, action_t action, bool recursive) { - _cleanup_globfree_ glob_t g = { - .gl_closedir = (void (*)(void *)) closedir, - .gl_readdir = (struct dirent *(*)(void *)) readdir, - .gl_opendir = (void *(*)(const char *)) opendir_nomod, - .gl_lstat = lstat, - .gl_stat = stat, - }; - int r = 0, k; - char **fn; - - errno = 0; - k = glob(i->path, GLOB_NOSORT|GLOB_BRACE|GLOB_ALTDIRFUNC, NULL, &g); - if (k != 0 && k != GLOB_NOMATCH) - return log_error_errno(errno ?: EIO, "glob(%s) failed: %m", i->path); - - STRV_FOREACH(fn, g.gl_pathv) { - k = action(i, *fn); - if (k < 0 && r == 0) - r = k; - - if (recursive) { - k = item_do_children(i, *fn, action); - if (k < 0 && r == 0) - r = k; - } - } - - return r; -} - -typedef enum { - CREATION_NORMAL, - CREATION_EXISTING, - CREATION_FORCE, - _CREATION_MODE_MAX, - _CREATION_MODE_INVALID = -1 -} CreationMode; - -static const char *creation_mode_verb_table[_CREATION_MODE_MAX] = { - [CREATION_NORMAL] = "Created", - [CREATION_EXISTING] = "Found existing", - [CREATION_FORCE] = "Created replacement", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(creation_mode_verb, CreationMode); - -static int create_item(Item *i) { - _cleanup_free_ char *resolved = NULL; - struct stat st; - int r = 0; - int q = 0; - CreationMode creation; - - assert(i); - - log_debug("Running create action for entry %c %s", (char) i->type, i->path); - - switch (i->type) { - - case IGNORE_PATH: - case IGNORE_DIRECTORY_PATH: - case REMOVE_PATH: - case RECURSIVE_REMOVE_PATH: - return 0; - - case CREATE_FILE: - case TRUNCATE_FILE: - r = write_one_file(i, i->path); - if (r < 0) - return r; - break; - - case COPY_FILES: { - r = specifier_printf(i->argument, specifier_table, NULL, &resolved); - if (r < 0) - return log_error_errno(r, "Failed to substitute specifiers in copy source %s: %m", i->argument); - - log_debug("Copying tree \"%s\" to \"%s\".", resolved, i->path); - r = copy_tree(resolved, i->path, false); - - if (r == -EROFS && stat(i->path, &st) == 0) - r = -EEXIST; - - if (r < 0) { - struct stat a, b; - - if (r != -EEXIST) - return log_error_errno(r, "Failed to copy files to %s: %m", i->path); - - if (stat(resolved, &a) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", resolved); - - if (stat(i->path, &b) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->path); - - if ((a.st_mode ^ b.st_mode) & S_IFMT) { - log_debug("Can't copy to %s, file exists already and is of different type", i->path); - return 0; - } - } - - r = path_set_perms(i, i->path); - if (r < 0) - return r; - - break; - - case WRITE_FILE: - r = glob_item(i, write_one_file, false); - if (r < 0) - return r; - - break; - - case CREATE_DIRECTORY: - case TRUNCATE_DIRECTORY: - case CREATE_SUBVOLUME: - case CREATE_SUBVOLUME_INHERIT_QUOTA: - case CREATE_SUBVOLUME_NEW_QUOTA: - - RUN_WITH_UMASK(0000) - mkdir_parents_label(i->path, 0755); - - if (IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA)) { - - if (btrfs_is_subvol(isempty(arg_root) ? "/" : arg_root) <= 0) - - /* Don't create a subvolume unless the - * root directory is one, too. We do - * this under the assumption that if - * the root directory is just a plain - * directory (i.e. very light-weight), - * we shouldn't try to split it up - * into subvolumes (i.e. more - * heavy-weight). Thus, chroot() - * environments and suchlike will get - * a full brtfs subvolume set up below - * their tree only if they - * specifically set up a btrfs - * subvolume for the root dir too. */ - - r = -ENOTTY; - else { - RUN_WITH_UMASK((~i->mode) & 0777) - r = btrfs_subvol_make(i->path); - } - } else - r = 0; - - if (IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY) || r == -ENOTTY) - RUN_WITH_UMASK(0000) - r = mkdir_label(i->path, i->mode); - - if (r < 0) { - int k; - - if (r != -EEXIST && r != -EROFS) - return log_error_errno(r, "Failed to create directory or subvolume \"%s\": %m", i->path); - - k = is_dir(i->path, false); - if (k == -ENOENT && r == -EROFS) - return log_error_errno(r, "%s does not exist and cannot be created as the file system is read-only.", i->path); - if (k < 0) - return log_error_errno(k, "Failed to check if %s exists: %m", i->path); - if (!k) { - log_warning("\"%s\" already exists and is not a directory.", i->path); - return 0; - } - - creation = CREATION_EXISTING; - } else - creation = CREATION_NORMAL; - - log_debug("%s directory \"%s\".", creation_mode_verb_to_string(creation), i->path); - - if (IN_SET(i->type, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)) { - r = btrfs_subvol_auto_qgroup(i->path, 0, i->type == CREATE_SUBVOLUME_NEW_QUOTA); - if (r == -ENOTTY) - log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" because of unsupported file system or because directory is not a subvolume: %m", i->path); - else if (r == -EROFS) - log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" because of read-only file system: %m", i->path); - else if (r == -ENOPROTOOPT) - log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" because quota support is disabled: %m", i->path); - else if (r < 0) - q = log_error_errno(r, "Failed to adjust quota for subvolume \"%s\": %m", i->path); - else if (r > 0) - log_debug("Adjusted quota for subvolume \"%s\".", i->path); - else if (r == 0) - log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path); - } - - r = path_set_perms(i, i->path); - if (q < 0) - return q; - if (r < 0) - return r; - - break; - - case CREATE_FIFO: - - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(i->path, S_IFIFO); - r = mkfifo(i->path, i->mode); - mac_selinux_create_file_clear(); - } - - if (r < 0) { - if (errno != EEXIST) - return log_error_errno(errno, "Failed to create fifo %s: %m", i->path); - - if (lstat(i->path, &st) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->path); - - if (!S_ISFIFO(st.st_mode)) { - - if (i->force) { - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(i->path, S_IFIFO); - r = mkfifo_atomic(i->path, i->mode); - mac_selinux_create_file_clear(); - } - - if (r < 0) - return log_error_errno(r, "Failed to create fifo %s: %m", i->path); - creation = CREATION_FORCE; - } else { - log_warning("\"%s\" already exists and is not a fifo.", i->path); - return 0; - } - } else - creation = CREATION_EXISTING; - } else - creation = CREATION_NORMAL; - log_debug("%s fifo \"%s\".", creation_mode_verb_to_string(creation), i->path); - - r = path_set_perms(i, i->path); - if (r < 0) - return r; - - break; - } - - case CREATE_SYMLINK: { - r = specifier_printf(i->argument, specifier_table, NULL, &resolved); - if (r < 0) - return log_error_errno(r, "Failed to substitute specifiers in symlink target %s: %m", i->argument); - - mac_selinux_create_file_prepare(i->path, S_IFLNK); - r = symlink(resolved, i->path); - mac_selinux_create_file_clear(); - - if (r < 0) { - _cleanup_free_ char *x = NULL; - - if (errno != EEXIST) - return log_error_errno(errno, "symlink(%s, %s) failed: %m", resolved, i->path); - - r = readlink_malloc(i->path, &x); - if (r < 0 || !streq(resolved, x)) { - - if (i->force) { - mac_selinux_create_file_prepare(i->path, S_IFLNK); - r = symlink_atomic(resolved, i->path); - mac_selinux_create_file_clear(); - - if (r < 0) - return log_error_errno(r, "symlink(%s, %s) failed: %m", resolved, i->path); - - creation = CREATION_FORCE; - } else { - log_debug("\"%s\" is not a symlink or does not point to the correct path.", i->path); - return 0; - } - } else - creation = CREATION_EXISTING; - } else - - creation = CREATION_NORMAL; - log_debug("%s symlink \"%s\".", creation_mode_verb_to_string(creation), i->path); - break; - } - - case CREATE_BLOCK_DEVICE: - case CREATE_CHAR_DEVICE: { - mode_t file_type; - - if (have_effective_cap(CAP_MKNOD) == 0) { - /* In a container we lack CAP_MKNOD. We - shouldn't attempt to create the device node in - that case to avoid noise, and we don't support - virtualized devices in containers anyway. */ - - log_debug("We lack CAP_MKNOD, skipping creation of device node %s.", i->path); - return 0; - } - - file_type = i->type == CREATE_BLOCK_DEVICE ? S_IFBLK : S_IFCHR; - - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(i->path, file_type); - r = mknod(i->path, i->mode | file_type, i->major_minor); - mac_selinux_create_file_clear(); - } - - if (r < 0) { - if (errno == EPERM) { - log_debug("We lack permissions, possibly because of cgroup configuration; " - "skipping creation of device node %s.", i->path); - return 0; - } - - if (errno != EEXIST) - return log_error_errno(errno, "Failed to create device node %s: %m", i->path); - - if (lstat(i->path, &st) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->path); - - if ((st.st_mode & S_IFMT) != file_type) { - - if (i->force) { - - RUN_WITH_UMASK(0000) { - mac_selinux_create_file_prepare(i->path, file_type); - r = mknod_atomic(i->path, i->mode | file_type, i->major_minor); - mac_selinux_create_file_clear(); - } - - if (r < 0) - return log_error_errno(r, "Failed to create device node \"%s\": %m", i->path); - creation = CREATION_FORCE; - } else { - log_debug("%s is not a device node.", i->path); - return 0; - } - } else - creation = CREATION_EXISTING; - } else - creation = CREATION_NORMAL; - - log_debug("%s %s device node \"%s\" %u:%u.", - creation_mode_verb_to_string(creation), - i->type == CREATE_BLOCK_DEVICE ? "block" : "char", - i->path, major(i->mode), minor(i->mode)); - - r = path_set_perms(i, i->path); - if (r < 0) - return r; - - break; - } - - case ADJUST_MODE: - case RELABEL_PATH: - r = glob_item(i, path_set_perms, false); - if (r < 0) - return r; - break; - - case RECURSIVE_RELABEL_PATH: - r = glob_item(i, path_set_perms, true); - if (r < 0) - return r; - break; - - case SET_XATTR: - r = glob_item(i, path_set_xattrs, false); - if (r < 0) - return r; - break; - - case RECURSIVE_SET_XATTR: - r = glob_item(i, path_set_xattrs, true); - if (r < 0) - return r; - break; - - case SET_ACL: - r = glob_item(i, path_set_acls, false); - if (r < 0) - return r; - break; - - case RECURSIVE_SET_ACL: - r = glob_item(i, path_set_acls, true); - if (r < 0) - return r; - break; - - case SET_ATTRIBUTE: - r = glob_item(i, path_set_attribute, false); - if (r < 0) - return r; - break; - - case RECURSIVE_SET_ATTRIBUTE: - r = glob_item(i, path_set_attribute, true); - if (r < 0) - return r; - break; - } - - return 0; -} - -static int remove_item_instance(Item *i, const char *instance) { - int r; - - assert(i); - - switch (i->type) { - - case REMOVE_PATH: - if (remove(instance) < 0 && errno != ENOENT) - return log_error_errno(errno, "rm(%s): %m", instance); - - break; - - case TRUNCATE_DIRECTORY: - case RECURSIVE_REMOVE_PATH: - /* FIXME: we probably should use dir_cleanup() here - * instead of rm_rf() so that 'x' is honoured. */ - log_debug("rm -rf \"%s\"", instance); - r = rm_rf(instance, (i->type == RECURSIVE_REMOVE_PATH ? REMOVE_ROOT|REMOVE_SUBVOLUME : 0) | REMOVE_PHYSICAL); - if (r < 0 && r != -ENOENT) - return log_error_errno(r, "rm_rf(%s): %m", instance); - - break; - - default: - assert_not_reached("wut?"); - } - - return 0; -} - -static int remove_item(Item *i) { - int r = 0; - - assert(i); - - log_debug("Running remove action for entry %c %s", (char) i->type, i->path); - - switch (i->type) { - - case CREATE_FILE: - case TRUNCATE_FILE: - case CREATE_DIRECTORY: - case CREATE_SUBVOLUME: - case CREATE_SUBVOLUME_INHERIT_QUOTA: - case CREATE_SUBVOLUME_NEW_QUOTA: - case CREATE_FIFO: - case CREATE_SYMLINK: - case CREATE_CHAR_DEVICE: - case CREATE_BLOCK_DEVICE: - case IGNORE_PATH: - case IGNORE_DIRECTORY_PATH: - case ADJUST_MODE: - case RELABEL_PATH: - case RECURSIVE_RELABEL_PATH: - case WRITE_FILE: - case COPY_FILES: - case SET_XATTR: - case RECURSIVE_SET_XATTR: - case SET_ACL: - case RECURSIVE_SET_ACL: - case SET_ATTRIBUTE: - case RECURSIVE_SET_ATTRIBUTE: - break; - - case REMOVE_PATH: - case TRUNCATE_DIRECTORY: - case RECURSIVE_REMOVE_PATH: - r = glob_item(i, remove_item_instance, false); - break; - } - - return r; -} - -static int clean_item_instance(Item *i, const char* instance) { - _cleanup_closedir_ DIR *d = NULL; - struct stat s, ps; - bool mountpoint; - usec_t cutoff, n; - char timestamp[FORMAT_TIMESTAMP_MAX]; - - assert(i); - - if (!i->age_set) - return 0; - - n = now(CLOCK_REALTIME); - if (n < i->age) - return 0; - - cutoff = n - i->age; - - d = opendir_nomod(instance); - if (!d) { - if (errno == ENOENT || errno == ENOTDIR) { - log_debug_errno(errno, "Directory \"%s\": %m", instance); - return 0; - } - - log_error_errno(errno, "Failed to open directory %s: %m", instance); - return -errno; - } - - if (fstat(dirfd(d), &s) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", i->path); - - if (!S_ISDIR(s.st_mode)) { - log_error("%s is not a directory.", i->path); - return -ENOTDIR; - } - - if (fstatat(dirfd(d), "..", &ps, AT_SYMLINK_NOFOLLOW) != 0) - return log_error_errno(errno, "stat(%s/..) failed: %m", i->path); - - mountpoint = s.st_dev != ps.st_dev || s.st_ino == ps.st_ino; - - log_debug("Cleanup threshold for %s \"%s\" is %s", - mountpoint ? "mount point" : "directory", - instance, - format_timestamp_us(timestamp, sizeof(timestamp), cutoff)); - - return dir_cleanup(i, instance, d, &s, cutoff, s.st_dev, mountpoint, - MAX_DEPTH, i->keep_first_level); -} - -static int clean_item(Item *i) { - int r = 0; - - assert(i); - - log_debug("Running clean action for entry %c %s", (char) i->type, i->path); - - switch (i->type) { - case CREATE_DIRECTORY: - case CREATE_SUBVOLUME: - case CREATE_SUBVOLUME_INHERIT_QUOTA: - case CREATE_SUBVOLUME_NEW_QUOTA: - case TRUNCATE_DIRECTORY: - case IGNORE_PATH: - case COPY_FILES: - clean_item_instance(i, i->path); - break; - case IGNORE_DIRECTORY_PATH: - r = glob_item(i, clean_item_instance, false); - break; - default: - break; - } - - return r; -} - -static int process_item_array(ItemArray *array); - -static int process_item(Item *i) { - int r, q, p, t = 0; - _cleanup_free_ char *prefix = NULL; - - assert(i); - - if (i->done) - return 0; - - i->done = true; - - prefix = malloc(strlen(i->path) + 1); - if (!prefix) - return log_oom(); - - PATH_FOREACH_PREFIX(prefix, i->path) { - ItemArray *j; - - j = ordered_hashmap_get(items, prefix); - if (j) { - int s; - - s = process_item_array(j); - if (s < 0 && t == 0) - t = s; - } - } - - r = arg_create ? create_item(i) : 0; - q = arg_remove ? remove_item(i) : 0; - p = arg_clean ? clean_item(i) : 0; - - return t < 0 ? t : - r < 0 ? r : - q < 0 ? q : - p; -} - -static int process_item_array(ItemArray *array) { - unsigned n; - int r = 0, k; - - assert(array); - - for (n = 0; n < array->count; n++) { - k = process_item(array->items + n); - if (k < 0 && r == 0) - r = k; - } - - return r; -} - -static void item_free_contents(Item *i) { - assert(i); - free(i->path); - free(i->argument); - strv_free(i->xattrs); - -#ifdef HAVE_ACL - acl_free(i->acl_access); - acl_free(i->acl_default); -#endif -} - -static void item_array_free(ItemArray *a) { - unsigned n; - - if (!a) - return; - - for (n = 0; n < a->count; n++) - item_free_contents(a->items + n); - free(a->items); - free(a); -} - -static int item_compare(const void *a, const void *b) { - const Item *x = a, *y = b; - - /* Make sure that the ownership taking item is put first, so - * that we first create the node, and then can adjust it */ - - if (takes_ownership(x->type) && !takes_ownership(y->type)) - return -1; - if (!takes_ownership(x->type) && takes_ownership(y->type)) - return 1; - - return (int) x->type - (int) y->type; -} - -static bool item_compatible(Item *a, Item *b) { - assert(a); - assert(b); - assert(streq(a->path, b->path)); - - if (takes_ownership(a->type) && takes_ownership(b->type)) - /* check if the items are the same */ - return streq_ptr(a->argument, b->argument) && - - a->uid_set == b->uid_set && - a->uid == b->uid && - - a->gid_set == b->gid_set && - a->gid == b->gid && - - a->mode_set == b->mode_set && - a->mode == b->mode && - - a->age_set == b->age_set && - a->age == b->age && - - a->mask_perms == b->mask_perms && - - a->keep_first_level == b->keep_first_level && - - a->major_minor == b->major_minor; - - return true; -} - -static bool should_include_path(const char *path) { - char **prefix; - - STRV_FOREACH(prefix, arg_exclude_prefixes) - if (path_startswith(path, *prefix)) { - log_debug("Entry \"%s\" matches exclude prefix \"%s\", skipping.", - path, *prefix); - return false; - } - - STRV_FOREACH(prefix, arg_include_prefixes) - if (path_startswith(path, *prefix)) { - log_debug("Entry \"%s\" matches include prefix \"%s\".", path, *prefix); - return true; - } - - /* no matches, so we should include this path only if we - * have no whitelist at all */ - if (strv_length(arg_include_prefixes) == 0) - return true; - - log_debug("Entry \"%s\" does not match any include prefix, skipping.", path); - return false; -} - -static int parse_line(const char *fname, unsigned line, const char *buffer) { - - _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL; - _cleanup_(item_free_contents) Item i = {}; - ItemArray *existing; - OrderedHashmap *h; - int r, pos; - bool force = false, boot = false; - - assert(fname); - assert(line >= 1); - assert(buffer); - - r = extract_many_words( - &buffer, - NULL, - EXTRACT_QUOTES, - &action, - &path, - &mode, - &user, - &group, - &age, - NULL); - if (r < 0) - return log_error_errno(r, "[%s:%u] Failed to parse line: %m", fname, line); - else if (r < 2) { - log_error("[%s:%u] Syntax error.", fname, line); - return -EIO; - } - - if (!isempty(buffer) && !streq(buffer, "-")) { - i.argument = strdup(buffer); - if (!i.argument) - return log_oom(); - } - - if (isempty(action)) { - log_error("[%s:%u] Command too short '%s'.", fname, line, action); - return -EINVAL; - } - - for (pos = 1; action[pos]; pos++) { - if (action[pos] == '!' && !boot) - boot = true; - else if (action[pos] == '+' && !force) - force = true; - else { - log_error("[%s:%u] Unknown modifiers in command '%s'", - fname, line, action); - return -EINVAL; - } - } - - if (boot && !arg_boot) { - log_debug("Ignoring entry %s \"%s\" because --boot is not specified.", - action, path); - return 0; - } - - i.type = action[0]; - i.force = force; - - r = specifier_printf(path, specifier_table, NULL, &i.path); - if (r < 0) { - log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, path); - return r; - } - - switch (i.type) { - - case CREATE_DIRECTORY: - case CREATE_SUBVOLUME: - case CREATE_SUBVOLUME_INHERIT_QUOTA: - case CREATE_SUBVOLUME_NEW_QUOTA: - case TRUNCATE_DIRECTORY: - case CREATE_FIFO: - case IGNORE_PATH: - case IGNORE_DIRECTORY_PATH: - case REMOVE_PATH: - case RECURSIVE_REMOVE_PATH: - case ADJUST_MODE: - case RELABEL_PATH: - case RECURSIVE_RELABEL_PATH: - if (i.argument) - log_warning("[%s:%u] %c lines don't take argument fields, ignoring.", fname, line, i.type); - - break; - - case CREATE_FILE: - case TRUNCATE_FILE: - break; - - case CREATE_SYMLINK: - if (!i.argument) { - i.argument = strappend("/usr/share/factory/", i.path); - if (!i.argument) - return log_oom(); - } - break; - - case WRITE_FILE: - if (!i.argument) { - log_error("[%s:%u] Write file requires argument.", fname, line); - return -EBADMSG; - } - break; - - case COPY_FILES: - if (!i.argument) { - i.argument = strappend("/usr/share/factory/", i.path); - if (!i.argument) - return log_oom(); - } else if (!path_is_absolute(i.argument)) { - log_error("[%s:%u] Source path is not absolute.", fname, line); - return -EBADMSG; - } - - path_kill_slashes(i.argument); - break; - - case CREATE_CHAR_DEVICE: - case CREATE_BLOCK_DEVICE: { - unsigned major, minor; - - if (!i.argument) { - log_error("[%s:%u] Device file requires argument.", fname, line); - return -EBADMSG; - } - - if (sscanf(i.argument, "%u:%u", &major, &minor) != 2) { - log_error("[%s:%u] Can't parse device file major/minor '%s'.", fname, line, i.argument); - return -EBADMSG; - } - - i.major_minor = makedev(major, minor); - break; - } - - case SET_XATTR: - case RECURSIVE_SET_XATTR: - if (!i.argument) { - log_error("[%s:%u] Set extended attribute requires argument.", fname, line); - return -EBADMSG; - } - r = parse_xattrs_from_arg(&i); - if (r < 0) - return r; - break; - - case SET_ACL: - case RECURSIVE_SET_ACL: - if (!i.argument) { - log_error("[%s:%u] Set ACLs requires argument.", fname, line); - return -EBADMSG; - } - r = parse_acls_from_arg(&i); - if (r < 0) - return r; - break; - - case SET_ATTRIBUTE: - case RECURSIVE_SET_ATTRIBUTE: - if (!i.argument) { - log_error("[%s:%u] Set file attribute requires argument.", fname, line); - return -EBADMSG; - } - r = parse_attribute_from_arg(&i); - if (r < 0) - return r; - break; - - default: - log_error("[%s:%u] Unknown command type '%c'.", fname, line, (char) i.type); - return -EBADMSG; - } - - if (!path_is_absolute(i.path)) { - log_error("[%s:%u] Path '%s' not absolute.", fname, line, i.path); - return -EBADMSG; - } - - path_kill_slashes(i.path); - - if (!should_include_path(i.path)) - return 0; - - if (arg_root) { - char *p; - - p = prefix_root(arg_root, i.path); - if (!p) - return log_oom(); - - free(i.path); - i.path = p; - } - - if (!isempty(user) && !streq(user, "-")) { - const char *u = user; - - r = get_user_creds(&u, &i.uid, NULL, NULL, NULL); - if (r < 0) { - log_error("[%s:%u] Unknown user '%s'.", fname, line, user); - return r; - } - - i.uid_set = true; - } - - if (!isempty(group) && !streq(group, "-")) { - const char *g = group; - - r = get_group_creds(&g, &i.gid); - if (r < 0) { - log_error("[%s:%u] Unknown group '%s'.", fname, line, group); - return r; - } - - i.gid_set = true; - } - - if (!isempty(mode) && !streq(mode, "-")) { - const char *mm = mode; - unsigned m; - - if (*mm == '~') { - i.mask_perms = true; - mm++; - } - - if (parse_mode(mm, &m) < 0) { - log_error("[%s:%u] Invalid mode '%s'.", fname, line, mode); - return -EBADMSG; - } - - i.mode = m; - i.mode_set = true; - } else - i.mode = IN_SET(i.type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA) ? 0755 : 0644; - - if (!isempty(age) && !streq(age, "-")) { - const char *a = age; - - if (*a == '~') { - i.keep_first_level = true; - a++; - } - - if (parse_sec(a, &i.age) < 0) { - log_error("[%s:%u] Invalid age '%s'.", fname, line, age); - return -EBADMSG; - } - - i.age_set = true; - } - - h = needs_glob(i.type) ? globs : items; - - existing = ordered_hashmap_get(h, i.path); - if (existing) { - unsigned n; - - for (n = 0; n < existing->count; n++) { - if (!item_compatible(existing->items + n, &i)) { - log_warning("[%s:%u] Duplicate line for path \"%s\", ignoring.", - fname, line, i.path); - return 0; - } - } - } else { - existing = new0(ItemArray, 1); - r = ordered_hashmap_put(h, i.path, existing); - if (r < 0) - return log_oom(); - } - - if (!GREEDY_REALLOC(existing->items, existing->size, existing->count + 1)) - return log_oom(); - - memcpy(existing->items + existing->count++, &i, sizeof(i)); - - /* Sort item array, to enforce stable ordering of application */ - qsort_safe(existing->items, existing->count, sizeof(Item), item_compare); - - zero(i); - return 0; -} - -static void help(void) { - printf("%s [OPTIONS...] [CONFIGURATION FILE...]\n\n" - "Creates, deletes and cleans up volatile and temporary files and directories.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --create Create marked files/directories\n" - " --clean Clean up marked directories\n" - " --remove Remove marked files/directories\n" - " --boot Execute actions only safe at boot\n" - " --prefix=PATH Only apply rules with the specified prefix\n" - " --exclude-prefix=PATH Ignore rules with the specified prefix\n" - " --root=PATH Operate on an alternate filesystem root\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_VERSION = 0x100, - ARG_CREATE, - ARG_CLEAN, - ARG_REMOVE, - ARG_BOOT, - ARG_PREFIX, - ARG_EXCLUDE_PREFIX, - ARG_ROOT, - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "create", no_argument, NULL, ARG_CREATE }, - { "clean", no_argument, NULL, ARG_CLEAN }, - { "remove", no_argument, NULL, ARG_REMOVE }, - { "boot", no_argument, NULL, ARG_BOOT }, - { "prefix", required_argument, NULL, ARG_PREFIX }, - { "exclude-prefix", required_argument, NULL, ARG_EXCLUDE_PREFIX }, - { "root", required_argument, NULL, ARG_ROOT }, - {} - }; - - int c, r; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_CREATE: - arg_create = true; - break; - - case ARG_CLEAN: - arg_clean = true; - break; - - case ARG_REMOVE: - arg_remove = true; - break; - - case ARG_BOOT: - arg_boot = true; - break; - - case ARG_PREFIX: - if (strv_push(&arg_include_prefixes, optarg) < 0) - return log_oom(); - break; - - case ARG_EXCLUDE_PREFIX: - if (strv_push(&arg_exclude_prefixes, optarg) < 0) - return log_oom(); - break; - - case ARG_ROOT: - r = parse_path_argument_and_warn(optarg, true, &arg_root); - if (r < 0) - return r; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (!arg_clean && !arg_create && !arg_remove) { - log_error("You need to specify at least one of --clean, --create or --remove."); - return -EINVAL; - } - - return 1; -} - -static int read_config_file(const char *fn, bool ignore_enoent) { - _cleanup_fclose_ FILE *f = NULL; - char line[LINE_MAX]; - Iterator iterator; - unsigned v = 0; - Item *i; - int r; - - assert(fn); - - r = search_and_fopen_nulstr(fn, "re", arg_root, conf_file_dirs, &f); - if (r < 0) { - if (ignore_enoent && r == -ENOENT) { - log_debug_errno(r, "Failed to open \"%s\": %m", fn); - return 0; - } - - return log_error_errno(r, "Failed to open '%s', ignoring: %m", fn); - } - log_debug("Reading config file \"%s\".", fn); - - FOREACH_LINE(line, f, break) { - char *l; - int k; - - v++; - - l = strstrip(line); - if (*l == '#' || *l == 0) - continue; - - k = parse_line(fn, v, l); - if (k < 0 && r == 0) - r = k; - } - - /* we have to determine age parameter for each entry of type X */ - ORDERED_HASHMAP_FOREACH(i, globs, iterator) { - Iterator iter; - Item *j, *candidate_item = NULL; - - if (i->type != IGNORE_DIRECTORY_PATH) - continue; - - ORDERED_HASHMAP_FOREACH(j, items, iter) { - if (!IN_SET(j->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA)) - continue; - - if (path_equal(j->path, i->path)) { - candidate_item = j; - break; - } - - if ((!candidate_item && path_startswith(i->path, j->path)) || - (candidate_item && path_startswith(j->path, candidate_item->path) && (fnmatch(i->path, j->path, FNM_PATHNAME | FNM_PERIOD) == 0))) - candidate_item = j; - } - - if (candidate_item && candidate_item->age_set) { - i->age = candidate_item->age; - i->age_set = true; - } - } - - if (ferror(f)) { - log_error_errno(errno, "Failed to read from file %s: %m", fn); - if (r == 0) - r = -EIO; - } - - return r; -} - -int main(int argc, char *argv[]) { - int r, k; - ItemArray *a; - Iterator iterator; - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - mac_selinux_init(NULL); - - items = ordered_hashmap_new(&string_hash_ops); - globs = ordered_hashmap_new(&string_hash_ops); - - if (!items || !globs) { - r = log_oom(); - goto finish; - } - - r = 0; - - if (optind < argc) { - int j; - - for (j = optind; j < argc; j++) { - k = read_config_file(argv[j], false); - if (k < 0 && r == 0) - r = k; - } - - } else { - _cleanup_strv_free_ char **files = NULL; - char **f; - - r = conf_files_list_nulstr(&files, ".conf", arg_root, conf_file_dirs); - if (r < 0) { - log_error_errno(r, "Failed to enumerate tmpfiles.d files: %m"); - goto finish; - } - - STRV_FOREACH(f, files) { - k = read_config_file(*f, true); - if (k < 0 && r == 0) - r = k; - } - } - - /* The non-globbing ones usually create things, hence we apply - * them first */ - ORDERED_HASHMAP_FOREACH(a, items, iterator) { - k = process_item_array(a); - if (k < 0 && r == 0) - r = k; - } - - /* The globbing ones usually alter things, hence we apply them - * second. */ - ORDERED_HASHMAP_FOREACH(a, globs, iterator) { - k = process_item_array(a); - if (k < 0 && r == 0) - r = k; - } - -finish: - while ((a = ordered_hashmap_steal_first(items))) - item_array_free(a); - - while ((a = ordered_hashmap_steal_first(globs))) - item_array_free(a); - - ordered_hashmap_free(items); - ordered_hashmap_free(globs); - - free(arg_include_prefixes); - free(arg_exclude_prefixes); - free(arg_root); - - set_free_free(unix_sockets); - - mac_selinux_finish(); - - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/tty-ask-password-agent/Makefile b/src/tty-ask-password-agent/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/tty-ask-password-agent/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/tty-ask-password-agent/tty-ask-password-agent.c b/src/tty-ask-password-agent/tty-ask-password-agent.c deleted file mode 100644 index 7b67831e54..0000000000 --- a/src/tty-ask-password-agent/tty-ask-password-agent.c +++ /dev/null @@ -1,681 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "ask-password-api.h" -#include "conf-parser.h" -#include "def.h" -#include "dirent-util.h" -#include "fd-util.h" -#include "io-util.h" -#include "mkdir.h" -#include "path-util.h" -#include "process-util.h" -#include "signal-util.h" -#include "socket-util.h" -#include "string-util.h" -#include "strv.h" -#include "terminal-util.h" -#include "util.h" -#include "utmp-wtmp.h" - -static enum { - ACTION_LIST, - ACTION_QUERY, - ACTION_WATCH, - ACTION_WALL -} arg_action = ACTION_QUERY; - -static bool arg_plymouth = false; -static bool arg_console = false; - -static int ask_password_plymouth( - const char *message, - usec_t until, - AskPasswordFlags flags, - const char *flag_file, - char ***ret) { - - _cleanup_close_ int fd = -1, notify = -1; - union sockaddr_union sa = PLYMOUTH_SOCKET; - _cleanup_free_ char *packet = NULL; - ssize_t k; - int r, n; - struct pollfd pollfd[2] = {}; - char buffer[LINE_MAX]; - size_t p = 0; - enum { - POLL_SOCKET, - POLL_INOTIFY - }; - - assert(ret); - - if (flag_file) { - notify = inotify_init1(IN_CLOEXEC|IN_NONBLOCK); - if (notify < 0) - return -errno; - - r = inotify_add_watch(notify, flag_file, IN_ATTRIB); /* for the link count */ - if (r < 0) - return -errno; - } - - fd = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0); - if (fd < 0) - return -errno; - - r = connect(fd, &sa.sa, offsetof(struct sockaddr_un, sun_path) + 1 + strlen(sa.un.sun_path+1)); - if (r < 0) - return -errno; - - if (flags & ASK_PASSWORD_ACCEPT_CACHED) { - packet = strdup("c"); - n = 1; - } else if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) - packet = NULL; - if (!packet) - return -ENOMEM; - - r = loop_write(fd, packet, n + 1, true); - if (r < 0) - return r; - - pollfd[POLL_SOCKET].fd = fd; - pollfd[POLL_SOCKET].events = POLLIN; - pollfd[POLL_INOTIFY].fd = notify; - pollfd[POLL_INOTIFY].events = POLLIN; - - for (;;) { - int sleep_for = -1, j; - - if (until > 0) { - usec_t y; - - y = now(CLOCK_MONOTONIC); - - if (y > until) { - r = -ETIME; - goto finish; - } - - sleep_for = (int) ((until - y) / USEC_PER_MSEC); - } - - if (flag_file && access(flag_file, F_OK) < 0) { - r = -errno; - goto finish; - } - - j = poll(pollfd, notify >= 0 ? 2 : 1, sleep_for); - if (j < 0) { - if (errno == EINTR) - continue; - - r = -errno; - goto finish; - } else if (j == 0) { - r = -ETIME; - goto finish; - } - - if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0) - flush_fd(notify); - - if (pollfd[POLL_SOCKET].revents == 0) - continue; - - k = read(fd, buffer + p, sizeof(buffer) - p); - if (k < 0) { - if (errno == EINTR || errno == EAGAIN) - continue; - - r = -errno; - goto finish; - } else if (k == 0) { - r = -EIO; - goto finish; - } - - p += k; - - if (p < 1) - continue; - - if (buffer[0] == 5) { - - if (flags & ASK_PASSWORD_ACCEPT_CACHED) { - /* Hmm, first try with cached - * passwords failed, so let's retry - * with a normal password request */ - packet = mfree(packet); - - if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) { - r = -ENOMEM; - goto finish; - } - - r = loop_write(fd, packet, n+1, true); - if (r < 0) - goto finish; - - flags &= ~ASK_PASSWORD_ACCEPT_CACHED; - p = 0; - continue; - } - - /* No password, because UI not shown */ - r = -ENOENT; - goto finish; - - } else if (buffer[0] == 2 || buffer[0] == 9) { - uint32_t size; - char **l; - - /* One or more answers */ - if (p < 5) - continue; - - memcpy(&size, buffer+1, sizeof(size)); - size = le32toh(size); - if (size + 5 > sizeof(buffer)) { - r = -EIO; - goto finish; - } - - if (p-5 < size) - continue; - - l = strv_parse_nulstr(buffer + 5, size); - if (!l) { - r = -ENOMEM; - goto finish; - } - - *ret = l; - break; - - } else { - /* Unknown packet */ - r = -EIO; - goto finish; - } - } - - r = 0; - -finish: - memory_erase(buffer, sizeof(buffer)); - return r; -} - -static int send_passwords(const char *socket_name, char **passwords) { - _cleanup_free_ char *packet = NULL; - _cleanup_close_ int socket_fd = -1; - union sockaddr_union sa = { .un.sun_family = AF_UNIX }; - size_t packet_length = 1; - char **p, *d; - int r; - - assert(socket_name); - - STRV_FOREACH(p, passwords) - packet_length += strlen(*p) + 1; - - packet = new(char, packet_length); - if (!packet) - return -ENOMEM; - - packet[0] = '+'; - - d = packet + 1; - STRV_FOREACH(p, passwords) - d = stpcpy(d, *p) + 1; - - socket_fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0); - if (socket_fd < 0) { - r = log_debug_errno(errno, "socket(): %m"); - goto finish; - } - - strncpy(sa.un.sun_path, socket_name, sizeof(sa.un.sun_path)); - - r = sendto(socket_fd, packet, packet_length, MSG_NOSIGNAL, &sa.sa, - offsetof(struct sockaddr_un, sun_path) + strlen(socket_name)); - if (r < 0) - r = log_debug_errno(errno, "sendto(): %m"); - -finish: - memory_erase(packet, packet_length); - return r; -} - -static int parse_password(const char *filename, char **wall) { - _cleanup_free_ char *socket_name = NULL, *message = NULL; - bool accept_cached = false, echo = false; - uint64_t not_after = 0; - unsigned pid = 0; - - const ConfigTableItem items[] = { - { "Ask", "Socket", config_parse_string, 0, &socket_name }, - { "Ask", "NotAfter", config_parse_uint64, 0, ¬_after }, - { "Ask", "Message", config_parse_string, 0, &message }, - { "Ask", "PID", config_parse_unsigned, 0, &pid }, - { "Ask", "AcceptCached", config_parse_bool, 0, &accept_cached }, - { "Ask", "Echo", config_parse_bool, 0, &echo }, - {} - }; - - int r; - - assert(filename); - - r = config_parse(NULL, filename, NULL, - NULL, - config_item_table_lookup, items, - true, false, true, NULL); - if (r < 0) - return r; - - if (!socket_name) { - log_error("Invalid password file %s", filename); - return -EBADMSG; - } - - if (not_after > 0 && now(CLOCK_MONOTONIC) > not_after) - return 0; - - if (pid > 0 && !pid_is_alive(pid)) - return 0; - - if (arg_action == ACTION_LIST) - printf("'%s' (PID %u)\n", message, pid); - - else if (arg_action == ACTION_WALL) { - char *_wall; - - if (asprintf(&_wall, - "%s%sPassword entry required for \'%s\' (PID %u).\r\n" - "Please enter password with the systemd-tty-ask-password-agent tool!", - strempty(*wall), - *wall ? "\r\n\r\n" : "", - message, - pid) < 0) - return log_oom(); - - free(*wall); - *wall = _wall; - - } else { - _cleanup_strv_free_erase_ char **passwords = NULL; - - assert(arg_action == ACTION_QUERY || - arg_action == ACTION_WATCH); - - if (access(socket_name, W_OK) < 0) { - if (arg_action == ACTION_QUERY) - log_info("Not querying '%s' (PID %u), lacking privileges.", message, pid); - - return 0; - } - - if (arg_plymouth) - r = ask_password_plymouth(message, not_after, accept_cached ? ASK_PASSWORD_ACCEPT_CACHED : 0, filename, &passwords); - else { - char *password = NULL; - int tty_fd = -1; - - if (arg_console) { - tty_fd = acquire_terminal("/dev/console", false, false, false, USEC_INFINITY); - if (tty_fd < 0) - return log_error_errno(tty_fd, "Failed to acquire /dev/console: %m"); - - r = reset_terminal_fd(tty_fd, true); - if (r < 0) - log_warning_errno(r, "Failed to reset terminal, ignoring: %m"); - } - - r = ask_password_tty(message, NULL, not_after, echo ? ASK_PASSWORD_ECHO : 0, filename, &password); - - if (arg_console) { - tty_fd = safe_close(tty_fd); - release_terminal(); - } - - if (r >= 0) - r = strv_push(&passwords, password); - - if (r < 0) - string_free_erase(password); - } - - /* If the query went away, that's OK */ - if (IN_SET(r, -ETIME, -ENOENT)) - return 0; - - if (r < 0) - return log_error_errno(r, "Failed to query password: %m"); - - r = send_passwords(socket_name, passwords); - if (r < 0) - return log_error_errno(r, "Failed to send: %m"); - } - - return 0; -} - -static int wall_tty_block(void) { - _cleanup_free_ char *p = NULL; - dev_t devnr; - int fd, r; - - r = get_ctty_devnr(0, &devnr); - if (r == -ENXIO) /* We have no controlling tty */ - return -ENOTTY; - if (r < 0) - return log_error_errno(r, "Failed to get controlling TTY: %m"); - - if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(devnr), minor(devnr)) < 0) - return log_oom(); - - mkdir_parents_label(p, 0700); - mkfifo(p, 0600); - - fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (fd < 0) - return log_debug_errno(errno, "Failed to open %s: %m", p); - - return fd; -} - -static bool wall_tty_match(const char *path, void *userdata) { - _cleanup_free_ char *p = NULL; - _cleanup_close_ int fd = -1; - struct stat st; - - if (!path_is_absolute(path)) - path = strjoina("/dev/", path); - - if (lstat(path, &st) < 0) { - log_debug_errno(errno, "Failed to stat %s: %m", path); - return true; - } - - if (!S_ISCHR(st.st_mode)) { - log_debug("%s is not a character device.", path); - return true; - } - - /* We use named pipes to ensure that wall messages suggesting - * password entry are not printed over password prompts - * already shown. We use the fact here that opening a pipe in - * non-blocking mode for write-only will succeed only if - * there's some writer behind it. Using pipes has the - * advantage that the block will automatically go away if the - * process dies. */ - - if (asprintf(&p, "/run/systemd/ask-password-block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) { - log_oom(); - return true; - } - - fd = open(p, O_WRONLY|O_CLOEXEC|O_NONBLOCK|O_NOCTTY); - if (fd < 0) { - log_debug_errno(errno, "Failed top open the wall pipe: %m"); - return 1; - } - - /* What, we managed to open the pipe? Then this tty is filtered. */ - return 0; -} - -static int show_passwords(void) { - _cleanup_closedir_ DIR *d; - struct dirent *de; - int r = 0; - - d = opendir("/run/systemd/ask-password"); - if (!d) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open /run/systemd/ask-password: %m"); - } - - FOREACH_DIRENT_ALL(de, d, return log_error_errno(errno, "Failed to read directory: %m")) { - _cleanup_free_ char *p = NULL, *wall = NULL; - int q; - - /* We only support /dev on tmpfs, hence we can rely on - * d_type to be reliable */ - - if (de->d_type != DT_REG) - continue; - - if (hidden_file(de->d_name)) - continue; - - if (!startswith(de->d_name, "ask.")) - continue; - - p = strappend("/run/systemd/ask-password/", de->d_name); - if (!p) - return log_oom(); - - q = parse_password(p, &wall); - if (q < 0 && r == 0) - r = q; - - if (wall) - (void) utmp_wall(wall, NULL, NULL, wall_tty_match, NULL); - } - - return r; -} - -static int watch_passwords(void) { - enum { - FD_INOTIFY, - FD_SIGNAL, - _FD_MAX - }; - - _cleanup_close_ int notify = -1, signal_fd = -1, tty_block_fd = -1; - struct pollfd pollfd[_FD_MAX] = {}; - sigset_t mask; - int r; - - tty_block_fd = wall_tty_block(); - - (void) mkdir_p_label("/run/systemd/ask-password", 0755); - - notify = inotify_init1(IN_CLOEXEC); - if (notify < 0) - return log_error_errno(errno, "Failed to allocate directory watch: %m"); - - if (inotify_add_watch(notify, "/run/systemd/ask-password", IN_CLOSE_WRITE|IN_MOVED_TO) < 0) - return log_error_errno(errno, "Failed to add /run/systemd/ask-password to directory watch: %m"); - - assert_se(sigemptyset(&mask) >= 0); - assert_se(sigset_add_many(&mask, SIGINT, SIGTERM, -1) >= 0); - assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) >= 0); - - signal_fd = signalfd(-1, &mask, SFD_NONBLOCK|SFD_CLOEXEC); - if (signal_fd < 0) - return log_error_errno(errno, "Failed to allocate signal file descriptor: %m"); - - pollfd[FD_INOTIFY].fd = notify; - pollfd[FD_INOTIFY].events = POLLIN; - pollfd[FD_SIGNAL].fd = signal_fd; - pollfd[FD_SIGNAL].events = POLLIN; - - for (;;) { - r = show_passwords(); - if (r < 0) - log_error_errno(r, "Failed to show password: %m"); - - if (poll(pollfd, _FD_MAX, -1) < 0) { - if (errno == EINTR) - continue; - - return -errno; - } - - if (pollfd[FD_INOTIFY].revents != 0) - (void) flush_fd(notify); - - if (pollfd[FD_SIGNAL].revents != 0) - break; - } - - return 0; -} - -static void help(void) { - printf("%s [OPTIONS...]\n\n" - "Process system password requests.\n\n" - " -h --help Show this help\n" - " --version Show package version\n" - " --list Show pending password requests\n" - " --query Process pending password requests\n" - " --watch Continuously process password requests\n" - " --wall Continuously forward password requests to wall\n" - " --plymouth Ask question with Plymouth instead of on TTY\n" - " --console Ask question on /dev/console instead of current TTY\n", - program_invocation_short_name); -} - -static int parse_argv(int argc, char *argv[]) { - - enum { - ARG_LIST = 0x100, - ARG_QUERY, - ARG_WATCH, - ARG_WALL, - ARG_PLYMOUTH, - ARG_CONSOLE, - ARG_VERSION - }; - - static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "list", no_argument, NULL, ARG_LIST }, - { "query", no_argument, NULL, ARG_QUERY }, - { "watch", no_argument, NULL, ARG_WATCH }, - { "wall", no_argument, NULL, ARG_WALL }, - { "plymouth", no_argument, NULL, ARG_PLYMOUTH }, - { "console", no_argument, NULL, ARG_CONSOLE }, - {} - }; - - int c; - - assert(argc >= 0); - assert(argv); - - while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0) - - switch (c) { - - case 'h': - help(); - return 0; - - case ARG_VERSION: - return version(); - - case ARG_LIST: - arg_action = ACTION_LIST; - break; - - case ARG_QUERY: - arg_action = ACTION_QUERY; - break; - - case ARG_WATCH: - arg_action = ACTION_WATCH; - break; - - case ARG_WALL: - arg_action = ACTION_WALL; - break; - - case ARG_PLYMOUTH: - arg_plymouth = true; - break; - - case ARG_CONSOLE: - arg_console = true; - break; - - case '?': - return -EINVAL; - - default: - assert_not_reached("Unhandled option"); - } - - if (optind != argc) { - log_error("%s takes no arguments.", program_invocation_short_name); - return -EINVAL; - } - - return 1; -} - -int main(int argc, char *argv[]) { - int r; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - r = parse_argv(argc, argv); - if (r <= 0) - goto finish; - - if (arg_console) { - (void) setsid(); - (void) release_terminal(); - } - - if (IN_SET(arg_action, ACTION_WATCH, ACTION_WALL)) - r = watch_passwords(); - else - r = show_passwords(); - -finish: - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/udev/Makefile b/src/udev/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/Makefile b/src/udev/Makefile new file mode 100644 index 0000000000..22fe5075cf --- /dev/null +++ b/src/udev/Makefile @@ -0,0 +1,287 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +INSTALL_DIRS += \ + $(sysconfdir)/udev/rules.d + +dist_network_DATA = \ + network/99-default.link \ + network/80-container-host0.network \ + network/80-container-ve.network + +dist_udevrules_DATA += \ + rules/50-udev-default.rules \ + rules/60-block.rules \ + rules/60-drm.rules \ + rules/60-evdev.rules \ + rules/60-persistent-storage-tape.rules \ + rules/60-persistent-input.rules \ + rules/60-persistent-alsa.rules \ + rules/60-persistent-storage.rules \ + rules/60-serial.rules \ + rules/64-btrfs.rules \ + rules/70-mouse.rules \ + rules/75-net-description.rules \ + rules/78-sound-card.rules \ + rules/80-net-setup-link.rules + +nodist_udevrules_DATA += \ + rules/99-systemd.rules + +udevconfdir = $(sysconfdir)/udev +dist_udevconf_DATA = \ + src/udev/udev.conf + +pkgconfigdata_DATA += \ + src/udev/udev.pc + +EXTRA_DIST += \ + rules/99-systemd.rules.in \ + src/udev/udev.pc.in + +EXTRA_DIST += \ + units/systemd-udevd.service.in \ + units/systemd-udev-trigger.service.in \ + units/systemd-udev-settle.service.in + +SOCKETS_TARGET_WANTS += \ + systemd-udevd-control.socket \ + systemd-udevd-kernel.socket + +SYSINIT_TARGET_WANTS += \ + systemd-udevd.service \ + systemd-udev-trigger.service + +rootbin_PROGRAMS += \ + udevadm + +rootlibexec_PROGRAMS += \ + systemd-udevd + +noinst_LTLIBRARIES += \ + libudev-core.la + +$(outdir)/keyboard-keys-list.txt: + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(CPP) $(CFLAGS) $(AM_CPPFLAGS) $(CPPFLAGS) -dM -include linux/input.h - < /dev/null | $(AWK) '/^#define[ \t]+KEY_[^ ]+[ \t]+[0-9K]/ { if ($$2 != "KEY_MAX") { print $$2 } }' > $@ + +$(outdir)/keyboard-keys-from-name.gperf: src/udev/keyboard-keys-list.txt + $(AM_V_GEN)$(AWK) 'BEGIN{ print "struct key { const char* name; unsigned short id; };"; print "%null-strings"; print "%%";} { print tolower(substr($$1 ,5)) ", " $$1 }' < $< > $@ + +$(outdir)/keyboard-keys-from-name.h: src/udev/keyboard-keys-from-name.gperf + $(AM_V_GPERF)$(GPERF) -L ANSI-C -t -N keyboard_lookup_key -H hash_key_name -p -C < $< > $@ + +gperf_txt_sources += \ + src/udev/keyboard-keys-list.txt + +libudev_core_la_SOURCES = \ + src/udev/udev.h \ + src/udev/udev-event.c \ + src/udev/udev-watch.c \ + src/udev/udev-node.c \ + src/udev/udev-rules.c \ + src/udev/udev-ctrl.c \ + src/udev/udev-builtin.c \ + src/udev/udev-builtin-btrfs.c \ + src/udev/udev-builtin-hwdb.c \ + src/udev/udev-builtin-input_id.c \ + src/udev/udev-builtin-keyboard.c \ + src/udev/udev-builtin-net_id.c \ + src/udev/udev-builtin-net_setup_link.c \ + src/udev/udev-builtin-path_id.c \ + src/udev/udev-builtin-usb_id.c \ + src/udev/net/link-config.h \ + src/udev/net/link-config.c \ + src/udev/net/ethtool-util.h \ + src/udev/net/ethtool-util.c + +nodist_libudev_core_la_SOURCES = \ + src/udev/keyboard-keys-from-name.h \ + src/udev/net/link-config-gperf.c + +gperf_gperf_sources += \ + src/udev/net/link-config-gperf.gperf + +libudev_core_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(BLKID_CFLAGS) \ + $(KMOD_CFLAGS) + +libudev_core_la_LIBADD = \ + libsystemd-network.la \ + libshared.la \ + $(BLKID_LIBS) \ + $(KMOD_LIBS) + +ifneq ($(HAVE_KMOD),) +libudev_core_la_SOURCES += \ + src/udev/udev-builtin-kmod.c + +dist_udevrules_DATA += \ + rules/80-drivers.rules +endif + +ifneq ($(HAVE_BLKID),) +libudev_core_la_SOURCES += \ + src/udev/udev-builtin-blkid.c +endif + +ifneq ($(HAVE_ACL),) +libudev_core_la_SOURCES += \ + src/udev/udev-builtin-uaccess.c \ + src/login/logind-acl.c \ + src/libsystemd/sd-login/sd-login.c \ + src/systemd/sd-login.h +endif + +systemd_udevd_SOURCES = \ + src/udev/udevd.c + +systemd_udevd_LDADD = \ + libudev-core.la + +udevadm_SOURCES = \ + src/udev/udevadm.c \ + src/udev/udevadm-info.c \ + src/udev/udevadm-control.c \ + src/udev/udevadm-monitor.c \ + src/udev/udevadm-hwdb.c \ + src/udev/udevadm-settle.c \ + src/udev/udevadm-trigger.c \ + src/udev/udevadm-test.c \ + src/udev/udevadm-test-builtin.c \ + src/udev/udevadm-util.c \ + src/udev/udevadm-util.h + +udevadm_LDADD = \ + libudev-core.la + +# ------------------------------------------------------------------------------ +ifneq ($(ENABLE_HWDB),) +INSTALL_DIRS += \ + $(sysconfdir)/udev/hwdb.d + +systemd_hwdb_SOURCES = \ + src/libsystemd/sd-hwdb/hwdb-internal.h \ + src/hwdb/hwdb.c + +systemd_hwdb_LDADD = \ + libshared.la + +rootbin_PROGRAMS += \ + systemd-hwdb + +dist_udevhwdb_DATA = \ + hwdb/20-pci-vendor-model.hwdb \ + hwdb/20-pci-classes.hwdb \ + hwdb/20-usb-vendor-model.hwdb \ + hwdb/20-usb-classes.hwdb \ + hwdb/20-sdio-vendor-model.hwdb \ + hwdb/20-sdio-classes.hwdb \ + hwdb/20-bluetooth-vendor-product.hwdb \ + hwdb/20-acpi-vendor.hwdb \ + hwdb/20-OUI.hwdb \ + hwdb/20-net-ifname.hwdb \ + hwdb/60-evdev.hwdb \ + hwdb/60-keyboard.hwdb \ + hwdb/70-mouse.hwdb \ + hwdb/70-pointingstick.hwdb + +SYSINIT_TARGET_WANTS += \ + systemd-hwdb-update.service + +# Update hwdb on installation. Do not bother if installing +# in DESTDIR, since this is likely for packaging purposes. +hwdb-update-hook: + -test -n "$(DESTDIR)" || $(rootbindir)/systemd-hwdb update + +INSTALL_DATA_HOOKS += \ + hwdb-update-hook + +hwdb-remove-hook: + -test -n "$(DESTDIR)" || rm -f /etc/udev/hwdb.bin +endif + +EXTRA_DIST += \ + units/systemd-hwdb-update.service.in \ + hwdb/ids-update.pl \ + hwdb/sdio.ids + +# ------------------------------------------------------------------------------ +ifneq ($(ENABLE_TESTS),) +TESTS += \ + test/udev-test.pl + +ifneq ($(HAVE_PYTHON),) +TESTS += \ + test/rule-syntax-check.py + +ifneq ($(HAVE_SYSV_COMPAT),) +TESTS += \ + test/sysv-generator-test.py +endif +endif +endif + +manual_tests += \ + test-libudev \ + test-udev + +test_libudev_SOURCES = \ + src/test/test-libudev.c + +test_libudev_LDADD = \ + libshared.la + +test_udev_SOURCES = \ + src/test/test-udev.c + +test_udev_LDADD = \ + libudev-core.la \ + $(BLKID_LIBS) \ + $(KMOD_LIBS) + +ifneq ($(ENABLE_TESTS),) +check_DATA += \ + test/sys +endif + +# packed sysfs test tree +$(outdir)/sys: + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)tar -C test/ -xJf $(top_srcdir)/test/sys.tar.xz + +test-sys-distclean: + -rm -rf test/sys +DISTCLEAN_LOCAL_HOOKS += test-sys-distclean + +EXTRA_DIST += \ + test/sys.tar.xz \ + test/udev-test.pl \ + test/rule-syntax-check.py \ + test/sysv-generator-test.py \ + test/mocks/fsck + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/udev/ata_id/Makefile b/src/udev/ata_id/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/ata_id/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/ata_id/Makefile b/src/udev/ata_id/Makefile new file mode 100644 index 0000000000..e70e2573bd --- /dev/null +++ b/src/udev/ata_id/Makefile @@ -0,0 +1,35 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +ata_id_SOURCES = \ + src/udev/ata_id/ata_id.c + +ata_id_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + ata_id + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/udev/cdrom_id/Makefile b/src/udev/cdrom_id/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/cdrom_id/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/cdrom_id/Makefile b/src/udev/cdrom_id/Makefile new file mode 100644 index 0000000000..f7583cfb70 --- /dev/null +++ b/src/udev/cdrom_id/Makefile @@ -0,0 +1,38 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +cdrom_id_SOURCES = \ + src/udev/cdrom_id/cdrom_id.c + +cdrom_id_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + cdrom_id + +dist_udevrules_DATA += \ + rules/60-cdrom_id.rules + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/udev/collect/Makefile b/src/udev/collect/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/collect/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/collect/Makefile b/src/udev/collect/Makefile new file mode 100644 index 0000000000..88dbaa1d82 --- /dev/null +++ b/src/udev/collect/Makefile @@ -0,0 +1,35 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +collect_SOURCES = \ + src/udev/collect/collect.c + +collect_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + collect + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/udev/mtd_probe/Makefile b/src/udev/mtd_probe/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/mtd_probe/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/mtd_probe/Makefile b/src/udev/mtd_probe/Makefile new file mode 100644 index 0000000000..719993ed0f --- /dev/null +++ b/src/udev/mtd_probe/Makefile @@ -0,0 +1,37 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +mtd_probe_SOURCES = \ + src/udev/mtd_probe/mtd_probe.c \ + src/udev/mtd_probe/mtd_probe.h \ + src/udev/mtd_probe/probe_smartmedia.c + +dist_udevrules_DATA += \ + rules/75-probe_mtd.rules + +udevlibexec_PROGRAMS += \ + mtd_probe + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/udev/net/Makefile b/src/udev/net/Makefile deleted file mode 120000 index 94aaae2c4d..0000000000 --- a/src/udev/net/Makefile +++ /dev/null @@ -1 +0,0 @@ -../../Makefile \ No newline at end of file diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index 15145fc5eb..e712d2ce73 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -20,7 +20,7 @@ #include #include -#include "sd-netlink.h" +#include #include "alloc-util.h" #include "conf-files.h" diff --git a/src/udev/scsi_id/Makefile b/src/udev/scsi_id/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/scsi_id/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/scsi_id/Makefile b/src/udev/scsi_id/Makefile new file mode 100644 index 0000000000..eaf5a51734 --- /dev/null +++ b/src/udev/scsi_id/Makefile @@ -0,0 +1,41 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +scsi_id_SOURCES =\ + src/udev/scsi_id/scsi_id.c \ + src/udev/scsi_id/scsi_serial.c \ + src/udev/scsi_id/scsi.h \ + src/udev/scsi_id/scsi_id.h + +scsi_id_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + scsi_id + +EXTRA_DIST += \ + src/udev/scsi_id/README + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c index ed0ea5ce5f..62cd93264b 100644 --- a/src/udev/udev-builtin-blkid.c +++ b/src/udev/udev-builtin-blkid.c @@ -27,7 +27,7 @@ #include #include -#include "sd-id128.h" +#include #include "alloc-util.h" #include "efivars.h" diff --git a/src/udev/udev-builtin-hwdb.c b/src/udev/udev-builtin-hwdb.c index f4a065a97d..b96f39ba20 100644 --- a/src/udev/udev-builtin-hwdb.c +++ b/src/udev/udev-builtin-hwdb.c @@ -22,7 +22,7 @@ #include #include -#include "sd-hwdb.h" +#include #include "alloc-util.h" #include "hwdb-util.h" diff --git a/src/udev/udev-builtin-uaccess.c b/src/udev/udev-builtin-uaccess.c index 3ebe36f043..2c27116ae9 100644 --- a/src/udev/udev-builtin-uaccess.c +++ b/src/udev/udev-builtin-uaccess.c @@ -22,7 +22,7 @@ #include #include -#include "sd-login.h" +#include #include "login-util.h" #include "logind-acl.h" diff --git a/src/udev/udev.h b/src/udev/udev.h index 1f9c8120c0..91dfc950b4 100644 --- a/src/udev/udev.h +++ b/src/udev/udev.h @@ -22,7 +22,7 @@ #include #include "libudev.h" -#include "sd-netlink.h" +#include #include "label.h" #include "libudev-private.h" diff --git a/src/udev/udevd.c b/src/udev/udevd.c index bb92f16352..37489c6ce7 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -40,8 +40,8 @@ #include #include -#include "sd-daemon.h" -#include "sd-event.h" +#include +#include #include "alloc-util.h" #include "cgroup-util.h" diff --git a/src/udev/v4l_id/Makefile b/src/udev/v4l_id/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/udev/v4l_id/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/udev/v4l_id/Makefile b/src/udev/v4l_id/Makefile new file mode 100644 index 0000000000..c2815b6809 --- /dev/null +++ b/src/udev/v4l_id/Makefile @@ -0,0 +1,38 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew Jędrzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +v4l_id_SOURCES = \ + src/udev/v4l_id/v4l_id.c + +v4l_id_LDADD = \ + libshared.la + +udevlibexec_PROGRAMS += \ + v4l_id + +dist_udevrules_DATA += \ + rules/60-persistent-v4l.rules + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/update-done/Makefile b/src/update-done/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/update-done/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/update-done/update-done.c b/src/update-done/update-done.c deleted file mode 100644 index 931e583785..0000000000 --- a/src/update-done/update-done.c +++ /dev/null @@ -1,115 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2014 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 . -***/ - -#include "fd-util.h" -#include "io-util.h" -#include "selinux-util.h" -#include "util.h" - -#define MESSAGE \ - "This file was created by systemd-update-done. Its only \n" \ - "purpose is to hold a timestamp of the time this directory\n" \ - "was updated. See systemd-update-done.service(8).\n" - -static int apply_timestamp(const char *path, struct timespec *ts) { - struct timespec twice[2] = { - *ts, - *ts - }; - struct stat st; - - assert(path); - assert(ts); - - if (stat(path, &st) >= 0) { - /* Is the timestamp file already newer than the OS? If - * so, there's nothing to do. We ignore the nanosecond - * component of the timestamp, since some file systems - * do not support any better accuracy than 1s and we - * have no way to identify the accuracy - * available. Most notably ext4 on small disks (where - * 128 byte inodes are used) does not support better - * accuracy than 1s. */ - if (st.st_mtim.tv_sec > ts->tv_sec) - return 0; - - /* It is older? Then let's update it */ - if (utimensat(AT_FDCWD, path, twice, AT_SYMLINK_NOFOLLOW) < 0) { - - if (errno == EROFS) - return log_debug("Can't update timestamp file %s, file system is read-only.", path); - - return log_error_errno(errno, "Failed to update timestamp on %s: %m", path); - } - - } else if (errno == ENOENT) { - _cleanup_close_ int fd = -1; - int r; - - /* The timestamp file doesn't exist yet? Then let's create it. */ - - r = mac_selinux_create_file_prepare(path, S_IFREG); - if (r < 0) - return log_error_errno(r, "Failed to set SELinux context for %s: %m", path); - - fd = open(path, O_CREAT|O_EXCL|O_WRONLY|O_TRUNC|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW, 0644); - mac_selinux_create_file_clear(); - - if (fd < 0) { - if (errno == EROFS) - return log_debug("Can't create timestamp file %s, file system is read-only.", path); - - return log_error_errno(errno, "Failed to create timestamp file %s: %m", path); - } - - (void) loop_write(fd, MESSAGE, strlen(MESSAGE), false); - - if (futimens(fd, twice) < 0) - return log_error_errno(errno, "Failed to update timestamp on %s: %m", path); - } else - log_error_errno(errno, "Failed to stat() timestamp file %s: %m", path); - - return 0; -} - -int main(int argc, char *argv[]) { - struct stat st; - int r, q = 0; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - if (stat("/usr", &st) < 0) { - log_error_errno(errno, "Failed to stat /usr: %m"); - return EXIT_FAILURE; - } - - r = mac_selinux_init(NULL); - if (r < 0) { - log_error_errno(r, "SELinux setup failed: %m"); - goto finish; - } - - r = apply_timestamp("/etc/.updated", &st.st_mtim); - q = apply_timestamp("/var/.updated", &st.st_mtim); - -finish: - return r < 0 || q < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/update-utmp/Makefile b/src/update-utmp/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/update-utmp/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/update-utmp/update-utmp.c b/src/update-utmp/update-utmp.c deleted file mode 100644 index 8ae4a8a833..0000000000 --- a/src/update-utmp/update-utmp.c +++ /dev/null @@ -1,283 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include - -#ifdef HAVE_AUDIT -#include -#endif - -#include "sd-bus.h" - -#include "alloc-util.h" -#include "bus-error.h" -#include "bus-util.h" -#include "formats-util.h" -#include "log.h" -#include "macro.h" -#include "special.h" -#include "unit-name.h" -#include "util.h" -#include "utmp-wtmp.h" - -typedef struct Context { - sd_bus *bus; -#ifdef HAVE_AUDIT - int audit_fd; -#endif -} Context; - -static usec_t get_startup_time(Context *c) { - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - usec_t t = 0; - int r; - - assert(c); - - r = sd_bus_get_property_trivial( - c->bus, - "org.freedesktop.systemd1", - "/org/freedesktop/systemd1", - "org.freedesktop.systemd1.Manager", - "UserspaceTimestamp", - &error, - 't', &t); - if (r < 0) { - log_error_errno(r, "Failed to get timestamp: %s", bus_error_message(&error, r)); - return 0; - } - - return t; -} - -static int get_current_runlevel(Context *c) { - static const struct { - const int runlevel; - const char *special; - } table[] = { - /* The first target of this list that is active or has - * a job scheduled wins. We prefer runlevels 5 and 3 - * here over the others, since these are the main - * runlevels used on Fedora. It might make sense to - * change the order on some distributions. */ - { '5', SPECIAL_GRAPHICAL_TARGET }, - { '3', SPECIAL_MULTI_USER_TARGET }, - { '1', SPECIAL_RESCUE_TARGET }, - }; - - _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; - int r; - unsigned i; - - assert(c); - - for (i = 0; i < ELEMENTSOF(table); i++) { - _cleanup_free_ char *state = NULL, *path = NULL; - - path = unit_dbus_path_from_name(table[i].special); - if (!path) - return log_oom(); - - r = sd_bus_get_property_string( - c->bus, - "org.freedesktop.systemd1", - path, - "org.freedesktop.systemd1.Unit", - "ActiveState", - &error, - &state); - if (r < 0) - return log_warning_errno(r, "Failed to get state: %s", bus_error_message(&error, r)); - - if (streq(state, "active") || streq(state, "reloading")) - return table[i].runlevel; - } - - return 0; -} - -static int on_reboot(Context *c) { - int r = 0, q; - usec_t t; - - assert(c); - - /* We finished start-up, so let's write the utmp - * record and send the audit msg */ - -#ifdef HAVE_AUDIT - if (c->audit_fd >= 0) - if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_BOOT, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && - errno != EPERM) { - r = log_error_errno(errno, "Failed to send audit message: %m"); - } -#endif - - /* If this call fails it will return 0, which - * utmp_put_reboot() will then fix to the current time */ - t = get_startup_time(c); - - q = utmp_put_reboot(t); - if (q < 0) { - log_error_errno(q, "Failed to write utmp record: %m"); - r = q; - } - - return r; -} - -static int on_shutdown(Context *c) { - int r = 0, q; - - assert(c); - - /* We started shut-down, so let's write the utmp - * record and send the audit msg */ - -#ifdef HAVE_AUDIT - if (c->audit_fd >= 0) - if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_SHUTDOWN, "", "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && - errno != EPERM) { - r = log_error_errno(errno, "Failed to send audit message: %m"); - } -#endif - - q = utmp_put_shutdown(); - if (q < 0) { - log_error_errno(q, "Failed to write utmp record: %m"); - r = q; - } - - return r; -} - -static int on_runlevel(Context *c) { - int r = 0, q, previous, runlevel; - - assert(c); - - /* We finished changing runlevel, so let's write the - * utmp record and send the audit msg */ - - /* First, get last runlevel */ - q = utmp_get_runlevel(&previous, NULL); - - if (q < 0) { - if (q != -ESRCH && q != -ENOENT) - return log_error_errno(q, "Failed to get current runlevel: %m"); - - previous = 0; - } - - /* Secondly, get new runlevel */ - runlevel = get_current_runlevel(c); - - if (runlevel < 0) - return runlevel; - - if (previous == runlevel) - return 0; - -#ifdef HAVE_AUDIT - if (c->audit_fd >= 0) { - _cleanup_free_ char *s = NULL; - - if (asprintf(&s, "old-level=%c new-level=%c", - previous > 0 ? previous : 'N', - runlevel > 0 ? runlevel : 'N') < 0) - return log_oom(); - - if (audit_log_user_comm_message(c->audit_fd, AUDIT_SYSTEM_RUNLEVEL, s, "systemd-update-utmp", NULL, NULL, NULL, 1) < 0 && errno != EPERM) - r = log_error_errno(errno, "Failed to send audit message: %m"); - } -#endif - - q = utmp_put_runlevel(runlevel, previous); - if (q < 0 && q != -ESRCH && q != -ENOENT) { - log_error_errno(q, "Failed to write utmp record: %m"); - r = q; - } - - return r; -} - -int main(int argc, char *argv[]) { - Context c = { -#ifdef HAVE_AUDIT - .audit_fd = -1 -#endif - }; - int r; - - if (getppid() != 1) { - log_error("This program should be invoked by init only."); - return EXIT_FAILURE; - } - - if (argc != 2) { - log_error("This program requires one argument."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - -#ifdef HAVE_AUDIT - /* If the kernel lacks netlink or audit support, - * don't worry about it. */ - c.audit_fd = audit_open(); - if (c.audit_fd < 0 && errno != EAFNOSUPPORT && errno != EPROTONOSUPPORT) - log_error_errno(errno, "Failed to connect to audit log: %m"); -#endif - r = bus_connect_system_systemd(&c.bus); - if (r < 0) { - log_error_errno(r, "Failed to get D-Bus connection: %m"); - r = -EIO; - goto finish; - } - - log_debug("systemd-update-utmp running as pid "PID_FMT, getpid()); - - if (streq(argv[1], "reboot")) - r = on_reboot(&c); - else if (streq(argv[1], "shutdown")) - r = on_shutdown(&c); - else if (streq(argv[1], "runlevel")) - r = on_runlevel(&c); - else { - log_error("Unknown command %s", argv[1]); - r = -EINVAL; - } - - log_debug("systemd-update-utmp stopped as pid "PID_FMT, getpid()); - -finish: -#ifdef HAVE_AUDIT - if (c.audit_fd >= 0) - audit_close(c.audit_fd); -#endif - - sd_bus_flush_close_unref(c.bus); - return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; -} diff --git a/src/user-sessions/Makefile b/src/user-sessions/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/user-sessions/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/user-sessions/user-sessions.c b/src/user-sessions/user-sessions.c deleted file mode 100644 index 8bf44e2100..0000000000 --- a/src/user-sessions/user-sessions.c +++ /dev/null @@ -1,84 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include - -#include "fileio.h" -#include "fileio-label.h" -#include "log.h" -#include "selinux-util.h" -#include "string-util.h" -#include "util.h" - -int main(int argc, char*argv[]) { - - if (argc != 2) { - log_error("This program requires one argument."); - return EXIT_FAILURE; - } - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - mac_selinux_init(NULL); - - if (streq(argv[1], "start")) { - int r = 0; - - if (unlink("/run/nologin") < 0 && errno != ENOENT) - r = log_error_errno(errno, - "Failed to remove /run/nologin file: %m"); - - if (unlink("/etc/nologin") < 0 && errno != ENOENT) { - /* If the file doesn't exist and /etc simply - * was read-only (in which case unlink() - * returns EROFS even if the file doesn't - * exist), don't complain */ - - if (errno != EROFS || access("/etc/nologin", F_OK) >= 0) { - log_error_errno(errno, "Failed to remove /etc/nologin file: %m"); - return EXIT_FAILURE; - } - } - - if (r < 0) - return EXIT_FAILURE; - - } else if (streq(argv[1], "stop")) { - int r; - - r = write_string_file_atomic_label("/run/nologin", "System is going down."); - if (r < 0) { - log_error_errno(r, "Failed to create /run/nologin: %m"); - return EXIT_FAILURE; - } - - } else { - log_error("Unknown verb %s.", argv[1]); - return EXIT_FAILURE; - } - - mac_selinux_finish(); - - return EXIT_SUCCESS; -} diff --git a/src/vconsole/.gitignore b/src/vconsole/.gitignore deleted file mode 100644 index 82741b2fb3..0000000000 --- a/src/vconsole/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/90-vconsole.rules diff --git a/src/vconsole/90-vconsole.rules.in b/src/vconsole/90-vconsole.rules.in deleted file mode 100644 index 35b9ad5151..0000000000 --- a/src/vconsole/90-vconsole.rules.in +++ /dev/null @@ -1,10 +0,0 @@ -# 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. - -# Each vtcon keeps its own state of fonts. -# -ACTION=="add", SUBSYSTEM=="vtconsole", KERNEL=="vtcon*", RUN+="@rootlibexecdir@/systemd-vconsole-setup" diff --git a/src/vconsole/Makefile b/src/vconsole/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/vconsole/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/vconsole/vconsole-setup.c b/src/vconsole/vconsole-setup.c deleted file mode 100644 index 8a1b824e65..0000000000 --- a/src/vconsole/vconsole-setup.c +++ /dev/null @@ -1,326 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2010 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "alloc-util.h" -#include "fd-util.h" -#include "fileio.h" -#include "io-util.h" -#include "locale-util.h" -#include "log.h" -#include "process-util.h" -#include "signal-util.h" -#include "stdio-util.h" -#include "string-util.h" -#include "terminal-util.h" -#include "util.h" -#include "virt.h" - -static bool is_vconsole(int fd) { - unsigned char data[1]; - - data[0] = TIOCL_GETFGCONSOLE; - return ioctl(fd, TIOCLINUX, data) >= 0; -} - -static int disable_utf8(int fd) { - int r = 0, k; - - if (ioctl(fd, KDSKBMODE, K_XLATE) < 0) - r = -errno; - - k = loop_write(fd, "\033%@", 3, false); - if (k < 0) - r = k; - - k = write_string_file("/sys/module/vt/parameters/default_utf8", "0", 0); - if (k < 0) - r = k; - - if (r < 0) - log_warning_errno(r, "Failed to disable UTF-8: %m"); - - return r; -} - -static int enable_utf8(int fd) { - int r = 0, k; - long current = 0; - - if (ioctl(fd, KDGKBMODE, ¤t) < 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; - } - - k = loop_write(fd, "\033%G", 3, false); - if (k < 0) - r = k; - - k = write_string_file("/sys/module/vt/parameters/default_utf8", "1", 0); - if (k < 0) - r = k; - - if (r < 0) - log_warning_errno(r, "Failed to enable UTF-8: %m"); - - return r; -} - -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; - pid_t pid; - - /* An empty map means kernel map */ - if (isempty(map)) - return 1; - - args[i++] = KBD_LOADKEYS; - args[i++] = "-q"; - args[i++] = "-C"; - args[i++] = vc; - if (utf8) - args[i++] = "-u"; - args[i++] = map; - if (map_toggle) - args[i++] = map_toggle; - args[i++] = NULL; - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - else if (pid == 0) { - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - execv(args[0], (char **) args); - _exit(EXIT_FAILURE); - } - - r = wait_for_terminate_and_warn(KBD_LOADKEYS, pid, true); - if (r < 0) - return r; - - return r == 0; -} - -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; - pid_t pid; - - /* An empty font means kernel font */ - if (isempty(font)) - return 1; - - args[i++] = KBD_SETFONT; - args[i++] = "-C"; - args[i++] = vc; - args[i++] = font; - if (map) { - args[i++] = "-m"; - args[i++] = map; - } - if (unimap) { - args[i++] = "-u"; - args[i++] = unimap; - } - args[i++] = NULL; - - pid = fork(); - if (pid < 0) - return log_error_errno(errno, "Failed to fork: %m"); - else if (pid == 0) { - - (void) reset_all_signal_handlers(); - (void) reset_signal_mask(); - - execv(args[0], (char **) args); - _exit(EXIT_FAILURE); - } - - r = wait_for_terminate_and_warn(KBD_SETFONT, pid, true); - if (r < 0) - return r; - - return r == 0; -} - -/* - * A newly allocated VT uses the font from the active VT. Here - * 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. - */ -static void font_copy_to_all_vcs(int fd) { - struct vt_stat vcs = {}; - unsigned char map8[E_TABSZ]; - unsigned short map16[E_TABSZ]; - struct unimapdesc unimapd; - struct unipair unipairs[USHRT_MAX]; - int i, r; - - /* 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"); - 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 = {}; - - if (i == vcs.v_active) - continue; - - /* skip non-allocated ttys */ - xsprintf(vcname, "/dev/vcs%i", i); - if (access(vcname, F_OK) < 0) - continue; - - xsprintf(vcname, "/dev/tty%i", i); - vcfd = open_terminal(vcname, O_RDWR|O_CLOEXEC); - if (vcfd < 0) - 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); - - /* copy map of 8bit chars */ - if (ioctl(fd, GIO_SCRNMAP, map8) >= 0) - (void) ioctl(vcfd, PIO_SCRNMAP, map8); - - /* copy map of 8bit chars -> 16bit Unicode values */ - if (ioctl(fd, GIO_UNISCRNMAP, map16) >= 0) - (void) ioctl(vcfd, PIO_UNISCRNMAP, map16); - - /* 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 }; - - (void) ioctl(vcfd, PIO_UNIMAPCLR, &adv); - (void) ioctl(vcfd, PIO_UNIMAP, &unimapd); - } - } -} - -int main(int argc, char **argv) { - const char *vc; - _cleanup_free_ char - *vc_keymap = NULL, *vc_keymap_toggle = NULL, - *vc_font = NULL, *vc_font_map = NULL, *vc_font_unimap = NULL; - _cleanup_close_ int fd = -1; - bool utf8, font_copy = false, font_ok, keyboard_ok; - int r = EXIT_FAILURE; - - log_set_target(LOG_TARGET_AUTO); - log_parse_environment(); - log_open(); - - umask(0022); - - if (argv[1]) - vc = argv[1]; - else { - vc = "/dev/tty0"; - font_copy = true; - } - - fd = open_terminal(vc, O_RDWR|O_CLOEXEC); - if (fd < 0) { - log_error_errno(fd, "Failed to open %s: %m", vc); - return EXIT_FAILURE; - } - - if (!is_vconsole(fd)) { - log_error("Device %s is not a virtual console.", vc); - return EXIT_FAILURE; - } - - utf8 = is_locale_utf8(); - - r = parse_env_file("/etc/vconsole.conf", NEWLINE, - "KEYMAP", &vc_keymap, - "KEYMAP_TOGGLE", &vc_keymap_toggle, - "FONT", &vc_font, - "FONT_MAP", &vc_font_map, - "FONT_UNIMAP", &vc_font_unimap, - NULL); - - if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to read /etc/vconsole.conf: %m"); - - /* Let the kernel command line override /etc/vconsole.conf */ - if (detect_container() <= 0) { - r = parse_env_file("/proc/cmdline", WHITESPACE, - "vconsole.keymap", &vc_keymap, - "vconsole.keymap.toggle", &vc_keymap_toggle, - "vconsole.font", &vc_font, - "vconsole.font.map", &vc_font_map, - "vconsole.font.unimap", &vc_font_unimap, - NULL); - - if (r < 0 && r != -ENOENT) - 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; - - /* Only copy the font when we executed setfont successfully */ - if (font_copy && font_ok) - (void) font_copy_to_all_vcs(fd); - - return font_ok && keyboard_ok ? EXIT_SUCCESS : EXIT_FAILURE; -} -- cgit v1.2.3-54-g00ecf